
AI数据工程的核心不是写几个pandas脚本,而是把采集→校验→清洗→特征→版本→服务做成可复现、可回溯、可监控的流水线。模型效果的上限由数据决定,而数据工程决定了这个上限能否稳定兑现。下面按生产级流水线拆解,每环给可运行代码。
没有契约的数据入湖就是灾难。用Pydantic定义数据契约,在入口处强制校验,脏数据不进管道。
# contracts.py
from pydantic import BaseModel, Field, field_validator
from datetime import datetime
class RawEvent(BaseModel):
user_id: str = Field(min_length=1)
item_id: str = Field(min_length=1)
event_type: str
ts: datetime
features: dict = Field(default_factory=dict)
@field_validator("event_type")
@classmethod
def valid_type(cls, v):
assert v in {"click","view","buy","fav"}, f"非法事件类型:{v}"
return v
@field_validator("ts")
@classmethod
def not_future(cls, v):
assert v <= datetime.utcnow(), "时间戳不能是未来"
return v契约的价值在于:校验失败的数据直接进死信队列(DLQ),而不是悄悄污染训练集。生产上建议用Great Expectations或Pandera做批量校验,Pydantic负责流式入口。
# ingest.py
import json, hashlib
from contracts import RawEvent
def ingest(line: str, sink):
try:
evt = RawEvent(**json.loads(line))
except Exception as e:
sink.dlq(line, str(e)); return
# 幂等键:用户+物品+事件+时间戳哈希,重复消费不重复写
key = hashlib.sha1(f"{evt.user_id}{evt.item_id}{evt.event_type}{evt.ts}".encode()).hexdigest()
sink.upsert(key, evt.model_dump())工程约束:幂等键必须包含时间戳,否则用户同一秒重复点击会被误去重;upsert而非insert,保证至少一次语义下不产生重复样本。
# clean.py
import pandas as pd
def clean(df: pd.DataFrame) -> pd.DataFrame:
df = df.drop_duplicates(subset=["user_id","item_id","event_type","ts"])
# 规则:异常时长、缺失关键字段
df = df[df["features"].apply(lambda x: x.get("dwell_ms",0) < 3600_000)]
df = df.dropna(subset=["user_id","item_id"])
# 语义去重:文本特征做minhash近似去重(伪代码示意)
# from datasketch import MinHashLSH
return df.reset_index(drop=True)专业点:规则清洗负责硬约束(空值、越界、格式),语义去重负责软重复(同一内容的改写、近重复样本)。训练集里的近重复样本会直接导致模型过拟合和评测虚高。
这是AI数据工程最容易翻车的地方——离线用pandas算,在线用Java重写,两边口径不一致,线上线下效果对不上。正确做法是特征定义只写一次,离线批算和在线点查共用同一份逻辑。
# features.py
from feast import Entity, FeatureView, Field, FileSource
from feast.types import Float32, Int64
from datetime import timedelta
user = Entity(name="user_id", join_keys=["user_id"])
src = FileSource(path="data/features.parquet", timestamp_field="event_ts")
user_fv = FeatureView(
name="user_behavior",
entities=[user],
ttl=timedelta(days=7),
schema=[
Field(name="click_cnt_7d", dtype=Int64),
Field(name="buy_rate_30d", dtype=Float32),
Field(name="avg_dwell_ms", dtype=Float32),
],
source=src,
online=True, # 关键:同一份定义同时产出离线和在线特征
)用Feast这类特征仓库,materialize把离线特征推到在线存储(Redis),训练时用get_historical_features做point-in-time join,严格防止特征穿越——只用标签发生时刻之前可用的特征,否则离线AUC虚高、上线暴跌。
# versioning.py
import dvc.api
def train_dataset(path="data/train.parquet"):
# DVC跟踪数据版本,git commit关联数据hash
with dvc.api.open(path, repo=".", rev="v1.2.0") as f:
return load(f)
def log_lineage(dataset_hash, feature_view_ver, model_ver):
lineage = {"dataset": dataset_hash, "feature_view": feature_view_ver, "model": model_ver}
# 写入元数据库,任何一次模型故障都能反查是哪版数据、哪版特征
db.lineage.insert_one(lineage)工程约束:数据、特征定义、模型三者版本必须绑定。线上模型出问题,第一件事是反查训练数据版本,没有血缘就只能盲猜。
# monitor.py
from scipy.stats import ks_2samp
def drift_check(ref, cur, cols, thr=0.05):
alerts = []
for c in cols:
stat, p = ks_2samp(ref[c].dropna(), cur[c].dropna())
if p < thr:
alerts.append({"col": c, "ks": round(stat,3), "p": round(p,4)})
return alerts # 空列表表示无显著漂移KS检验盯特征分布漂移,PSI盯分箱稳定性,再叠加标签延迟监控(购买标签往往T+1甚至T+7才回传)。漂移告警要联动重训练流水线,而不是只发钉钉。
总结:AI数据工程的专业度体现在契约先行、幂等入湖、特征同源、版本血缘、漂移监控五件事上。代码量不大,但每一环的工程约束决定了数据能不能被信任。能给别人讲清楚"为什么离线AUC高上线却崩",你就抓住了数据工程的命门。觉得有用点赞收藏,需要完整工程目录留言。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。