1. 从零搭建AI工程体系,为什么我劝你别一上来就啃论文
这两年AI工程这个词被炒得火热,打开任何一个技术社区,满屏都是大模型微调、RAG、Agent、向量数据库这些词。很多刚入行的朋友第一反应就是去搜论文、看开源代码,结果被一堆数学公式和抽象架构图劝退,折腾两三个月连一个能跑起来的最小系统都没搭出来。我自己带过不少新人,也见过太多团队在AI工程落地上反复踩坑,最后得出一个结论:从零构建AI工程能力,关键不在于你读了多少篇论文,而在于你有没有一条清晰的、从底层到应用的实操路径。
ai-engineering-from-scratch这个项目标题本身就点明了核心诉求——不是教你调包,不是教你背概念,而是从零开始,把AI工程涉及的关键环节一层一层搭起来。它适合那些想真正理解AI系统怎么运转、而不是只会调API的开发者;也适合已经会用现成框架、但遇到问题就抓瞎、想补全底层认知的工程师。这篇文章我会按照我自己实际搭建AI工程体系的顺序,把每个阶段该做什么、为什么这么做、容易在哪里翻车,全部拆开讲清楚。你不需要有机器学习博士学位,但需要有一点编程基础和对系统设计的兴趣。
我先把整体思路摆出来:AI工程不是单一技能,它是数据处理、模型训练、推理服务、评估监控、迭代优化这一整条链路的工程化能力。很多人只盯着模型那一层,结果数据管道一塌糊涂,服务部署一上线就崩,评估全靠肉眼观察。从零搭建的意思,就是这五层你都得亲手过一遍,哪怕每层只做一个最小可用版本,也比只懂一层要强得多。
2. 整体设计与思路拆解:AI工程到底该从哪一层开始搭
2.1 为什么我选择“自底向上”而不是“自顶向下”
市面上大部分AI教程是自顶向下的:先给你一个完整的开源项目,让你跑起来,然后再慢慢往下解释每一层在干什么。这种方式上手快,但有个致命问题——你永远在别人的抽象层上工作,一旦某个环节出问题,你根本不知道是数据的问题、模型的问题还是服务的问题。我试过用这种方式带人,结果就是大家都能跑demo,但没人能独立排查故障。
自底向上的路径正好相反:先从最原始的数据和最简单的模型开始,亲手实现每一个环节,然后再逐步引入框架和工具来提效。这样做前期慢,但你对整个系统的掌控力是完全不同的。举个具体例子,如果你自己用NumPy实现过一次完整的线性回归训练循环,包括前向传播、损失计算、反向传播、参数更新,那么当你后面用PyTorch遇到loss不下降的时候,你脑子里能立刻浮现出可能出问题的几个点,而不是盲目调学习率。
ai-engineering-from-scratch的核心价值就在这里:它强迫你走一遍底层,哪怕只是最小规模的实现。我的建议是,前两周不要碰任何高级框架,就用Python标准库加NumPy,把数据加载、模型定义、训练循环、评估指标全部手写一遍。这个过程会让你对AI系统的理解产生质变。
2.2 五层架构的选型逻辑与依赖关系
我把AI工程体系拆成五层,每层都有明确的职责和选型考量。这个分层不是拍脑袋来的,而是根据实际项目中问题出现的频率和依赖关系总结的。
| 层级 | 核心职责 | 最小实现方案 | 生产级方案 | 常见翻车点 |
|---|---|---|---|---|
| 数据层 | 数据采集、清洗、版本管理 | 本地CSV+脚本 | 数据湖+特征存储 | 数据泄漏、分布偏移 |
| 训练层 | 模型定义、训练、调参 | NumPy手写循环 | PyTorch+分布式 | 过拟合、梯度爆炸 |
| 推理层 | 模型服务化、批处理 | Flask单接口 | Triton+动态批处理 | 延迟抖动、内存泄漏 |
| 评估层 | 离线评估、在线A/B | 准确率+混淆矩阵 | 多指标+影子流量 | 指标单一、评估集污染 |
| 迭代层 | 监控、反馈、再训练 | 日志+手动触发 | 自动流水线+漂移检测 | 反馈闭环断裂 |
这五层的依赖关系是严格的:数据层不稳,训练层就是空中楼阁;训练层不扎实,推理层再优化也是白搭;评估层缺失,迭代层就是盲人摸象。我见过太多团队跳过评估层直接上迭代,结果模型更新后效果反而下降,查了半天发现是评估指标选错了。
注意:不要试图一次性把五层都做到生产级。正确的做法是每层先做一个能跑通的最小版本,形成完整闭环,然后再逐层加固。这个顺序不能乱,因为闭环带来的反馈是你后续优化的唯一依据。
2.3 技术栈选择的三个原则
在具体工具选型上,我遵循三个原则。第一,优先选择能让你理解底层机制的工具。比如数据加载,pandas确实方便,但初期我建议你用Python的csv模块手写一遍,理解批处理、打乱、填充这些操作到底在做什么。第二,优先选择社区活跃、文档清晰的工具。AI工程领域变化太快,选一个半年不更新的库等于给自己挖坑。第三,优先选择能平滑过渡到生产环境的工具。比如训练框架,PyTorch从研究到生产的路径比TensorFlow更顺畅,社区生态也更活跃。
具体到每个阶段,我的推荐是:数据层用Python脚本加DVC做版本管理,训练层用NumPy入门后转PyTorch,推理层用FastAPI起步后转Triton,评估层用scikit-learn加自定义指标,迭代层用MLflow做实验追踪。这套组合的好处是每一层都有清晰的升级路径,不会出现“学了这个用不上”的情况。
3. 核心细节解析与实操要点:每一层到底该怎么动手
3.1 数据层:别急着清洗,先搞清楚数据长什么样
数据层是AI工程的地基,但很多人的做法是拿到数据直接扔进清洗脚本,结果洗完了都不知道原始数据有什么问题。我的习惯是分三步走:先探查、再清洗、后版本化。
探查阶段,我会写一个简单的脚本,输出每个字段的缺失率、唯一值数量、数值分布、类别分布。这个脚本不超过50行,但能帮你发现80%的数据问题。比如你拿到一个用户行为数据集,探查后发现某个关键字段缺失率高达60%,那你就得先搞清楚这个字段是怎么采集的,而不是直接填充均值。
清洗阶段,我遵循“最小干预”原则。能不改就不改,必须改的要有记录。比如缺失值处理,优先考虑删除而不是填充,因为填充会引入虚假信息。异常值处理,优先考虑截断而不是删除,因为删除可能丢失重要样本。每一步清洗操作都要写成可复现的脚本,而不是在Jupyter Notebook里手动改。
版本化阶段,很多人忽略这一步,结果模型效果回退时根本找不到当时用的哪版数据。我用DVC做数据版本管理,每次清洗后的数据都打上标签,和代码commit关联起来。这样当模型出问题时,我能精确回溯到当时的数据状态。
# 数据探查脚本示例 import csv from collections import Counter def profile_data(filepath): with open(filepath, 'r') as f: reader = csv.DictReader(f) rows = list(reader) total = len(rows) profile = {} for field in reader.fieldnames: values = [row[field] for row in rows] missing = sum(1 for v in values if v == '' or v is None) unique = len(set(values)) profile[field] = { 'missing_rate': missing / total, 'unique_count': unique, 'sample_values': values[:5] } return profile实操心得:数据探查一定要在清洗之前做,而且探查结果要保存下来。我习惯把探查报告存成JSON,和数据集一起版本化。这样当别人质疑你的数据质量时,你能拿出证据说明原始数据就是这样,而不是你洗坏了。
3.2 训练层:手写一遍训练循环,比看十篇教程都管用
训练层的核心是理解模型怎么从数据中学习。我的建议是,不管你后面用什么框架,先用NumPy手写一个完整的训练循环。这个循环包含五个部分:前向传播、损失计算、反向传播、参数更新、评估。
前向传播就是输入数据经过模型计算得到预测值。损失计算是衡量预测值和真实值差距的函数。反向传播是计算损失对每个参数的梯度。参数更新是根据梯度调整参数。评估是用验证集检查模型效果。这五步走一遍,你对训练过程的理解就扎实了。
import numpy as np # 手写线性回归训练循环 def train_linear_regression(X, y, lr=0.01, epochs=100): n_samples, n_features = X.shape weights = np.zeros(n_features) bias = 0 for epoch in range(epochs): # 前向传播 y_pred = X.dot(weights) + bias # 计算损失(MSE) loss = np.mean((y_pred - y) ** 2) # 反向传播 dw = (2 / n_samples) * X.T.dot(y_pred - y) db = (2 / n_samples) * np.sum(y_pred - y) # 参数更新 weights -= lr * dw bias -= lr * db if epoch % 10 == 0: print(f"Epoch {epoch}, Loss: {loss:.4f}") return weights, bias这段代码看起来简单,但它包含了训练的所有核心要素。你亲手写一遍,就会明白为什么学习率太大会震荡、太小会收敛慢;为什么需要打乱数据;为什么需要验证集来早停。这些直觉是看教程得不到的。
手写完之后,再切换到PyTorch,你会发现框架帮你处理了梯度计算、设备管理、批处理这些繁琐的事情,但底层逻辑完全一样。这时候你再用框架,就不是“调包”了,而是“用工具”。
注意:手写训练循环时,一定要用真实数据跑一遍,不要只用随机生成的数据。真实数据有噪声、有异常值、有分布不均,这些才是你真正要面对的问题。
3.3 推理层:从Flask单接口到动态批处理的演进路径
推理层是把训练好的模型变成可用服务的地方。很多人的第一个版本是用Flask写一个POST接口,接收请求、调用模型、返回结果。这个版本能跑,但有几个致命问题:没有批处理、没有并发控制、没有超时管理、没有监控。
我的演进路径是这样的:第一版用FastAPI替代Flask,因为FastAPI原生支持异步和自动文档。第二版加入批处理,把多个请求攒在一起推理,提升吞吐量。第三版加入动态批处理,根据请求量自动调整批大小。第四版加入模型版本管理和灰度发布。
from fastapi import FastAPI from pydantic import BaseModel import numpy as np app = FastAPI() class PredictRequest(BaseModel): features: list[float] class PredictResponse(BaseModel): prediction: float # 模拟加载模型 model_weights = np.array([1.0, 2.0, 3.0]) @app.post("/predict", response_model=PredictResponse) async def predict(request: PredictRequest): features = np.array(request.features) prediction = float(features.dot(model_weights)) return PredictResponse(prediction=prediction)这个最小版本能跑通,但你要清楚它的局限。当请求量上来后,每个请求单独推理会浪费大量计算资源。批处理的核心思想是把多个请求攒在一起,一次性送给模型推理,然后拆分结果返回。动态批处理则是在批处理和延迟之间找平衡,请求少的时候等一等,请求多的时候立刻处理。
实操心得:推理层最容易忽略的是输入校验。我见过太多服务因为输入格式不对直接崩溃。一定要在接口层做严格的输入校验,包括类型、范围、维度。FastAPI的Pydantic模型能帮你做大部分校验,但业务逻辑层面的校验还得自己写。
3.4 评估层:准确率是远远不够的
评估层是AI工程中最容易被低估的一层。很多人训练完模型,看一眼准确率就完事了。但准确率在类别不平衡的数据集上毫无意义。比如一个二分类问题,正样本占95%,你全预测为正就能拿到95%的准确率,但这个模型毫无价值。
我的评估体系包含四个维度:区分度、校准度、稳定性、公平性。区分度用AUC、F1、召回率等指标衡量。校准度看预测概率是否可靠,用可靠性图评估。稳定性看模型在不同时间段、不同数据分布下的表现。公平性看模型在不同群体上的表现差异。
| 评估维度 | 核心指标 | 适用场景 | 常见陷阱 |
|---|---|---|---|
| 区分度 | AUC、F1、Recall | 分类问题 | 只看准确率 |
| 校准度 | ECE、可靠性图 | 概率输出 | 忽略概率校准 |
| 稳定性 | PSI、KS | 时间序列 | 忽略分布漂移 |
| 公平性 | 群体差异指标 | 涉及人的决策 | 忽略偏见 |
评估集的选择也很关键。我习惯把数据分成三份:训练集、验证集、测试集。训练集用于训练,验证集用于调参,测试集只在最后用一次。测试集绝对不能参与任何调参过程,否则评估结果就是自欺欺人。
注意:评估指标一定要在项目开始时就确定,而不是训练完再想。指标决定了你优化的方向,如果指标选错了,模型效果再好也是南辕北辙。
4. 实操过程与核心环节实现:从零搭一个完整的最小系统
4.1 环境准备与依赖管理
动手之前,先把环境搭好。我强烈建议用虚拟环境,不要污染系统Python。conda和venv都行,我个人习惯用conda,因为管理科学计算相关的依赖更方便。
conda create -n ai-eng python=3.10 conda activate ai-eng pip install numpy pandas scikit-learn fastapi uvicorn dvc mlflow依赖管理有个坑:不要一次性装太多库。每装一个库,都要清楚它是干什么的。我见过有人装了几十个库,结果版本冲突搞了一整天。我的做法是分阶段安装,数据层装numpy和pandas,训练层装scikit-learn和torch,推理层装fastapi和uvicorn,评估层装mlflow和dvc。每个阶段跑通了再装下一阶段。
4.2 数据管道搭建:从原始数据到训练集
数据管道的核心是把原始数据变成模型能吃的格式。这个过程包括加载、清洗、特征工程、划分、批处理。我写了一个可复用的管道脚本,每个步骤都有明确的输入输出。
import pandas as pd from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler def build_pipeline(raw_path): # 加载 df = pd.read_csv(raw_path) # 清洗:删除缺失率过高的列 missing_rate = df.isnull().mean() df = df.loc[:, missing_rate < 0.3] # 清洗:填充剩余缺失值 df = df.fillna(df.median(numeric_only=True)) # 特征工程:标准化数值特征 numeric_cols = df.select_dtypes(include=['float64', 'int64']).columns scaler = StandardScaler() df[numeric_cols] = scaler.fit_transform(df[numeric_cols]) # 划分 train, test = train_test_split(df, test_size=0.2, random_state=42) train, val = train_test_split(train, test_size=0.2, random_state=42) return train, val, test, scaler这个管道看起来简单,但有几个关键决策点。缺失率阈值设0.3,是因为超过30%缺失的列填充后引入的噪声太大。用中位数填充而不是均值,是因为中位数对异常值更鲁棒。标准化用StandardScaler而不是MinMaxScaler,是因为大部分模型对正态分布的数据表现更好。
实操心得:数据管道一定要可复现。我习惯把随机种子固定,把scaler保存下来,这样推理时能用同样的参数处理新数据。很多人训练时标准化了,推理时忘了标准化,结果模型效果一落千丈。
4.3 模型训练与超参数调优
训练阶段我用PyTorch实现一个简单的多层感知机,然后用手动调参和网格搜索两种方式找最优超参数。手动调参靠经验,网格搜索靠算力,两者结合效率最高。
import torch import torch.nn as nn import torch.optim as optim class MLP(nn.Module): def __init__(self, input_dim, hidden_dim, output_dim): super().__init__() self.layers = nn.Sequential( nn.Linear(input_dim, hidden_dim), nn.ReLU(), nn.Dropout(0.2), nn.Linear(hidden_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, output_dim) ) def forward(self, x): return self.layers(x) def train_model(model, train_loader, val_loader, epochs=50, lr=0.001): criterion = nn.MSELoss() optimizer = optim.Adam(model.parameters(), lr=lr) best_val_loss = float('inf') for epoch in range(epochs): model.train() for X_batch, y_batch in train_loader: optimizer.zero_grad() output = model(X_batch) loss = criterion(output, y_batch) loss.backward() optimizer.step() model.eval() val_loss = 0 with torch.no_grad(): for X_batch, y_batch in val_loader: output = model(X_batch) val_loss += criterion(output, y_batch).item() if val_loss < best_val_loss: best_val_loss = val_loss torch.save(model.state_dict(), 'best_model.pt') return best_val_loss超参数调优我遵循“先粗后细”的原则。先调学习率,因为学习率对训练影响最大。再调网络结构,包括层数和隐藏单元数。最后调正则化参数,包括dropout率和权重衰减。每次只调一个参数,固定其他参数,这样才能看出每个参数的真实影响。
4.4 推理服务部署与性能测试
训练好的模型要部署成服务才能产生价值。我用FastAPI写了一个推理服务,然后用locust做压力测试。
from fastapi import FastAPI from pydantic import BaseModel import torch import numpy as np app = FastAPI() class MLP(nn.Module): def __init__(self, input_dim, hidden_dim, output_dim): super().__init__() self.layers = nn.Sequential( nn.Linear(input_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, output_dim) ) def forward(self, x): return self.layers(x) model = MLP(10, 64, 1) model.load_state_dict(torch.load('best_model.pt')) model.eval() class PredictRequest(BaseModel): features: list[float] @app.post("/predict") async def predict(request: PredictRequest): features = torch.tensor([request.features], dtype=torch.float32) with torch.no_grad(): prediction = model(features).item() return {"prediction": prediction}压力测试用locust,模拟不同并发数下的响应时间和吞吐量。我一般会测三个场景:低并发(10用户)、中并发(100用户)、高并发(1000用户)。每个场景跑5分钟,记录P50、P95、P99延迟和每秒请求数。
注意:推理服务一定要加超时和限流。我见过太多服务因为一个慢请求拖垮整个系统。FastAPI可以用中间件加超时,限流可以用slowapi或者自己写令牌桶。
5. 常见问题与排查技巧实录:那些我踩过的坑
5.1 训练不收敛的排查清单
训练不收敛是新手最常遇到的问题。我整理了一个排查清单,按优先级排序。
| 排查项 | 检查方法 | 常见原因 | 解决方案 |
|---|---|---|---|
| 学习率 | 打印loss曲线 | 太大震荡,太小不降 | 从1e-3开始试 |
| 数据归一化 | 检查输入范围 | 特征尺度差异大 | 标准化或归一化 |
| 梯度消失/爆炸 | 打印梯度范数 | 网络太深或激活函数不当 | 用ReLU、加BN |
| 标签噪声 | 检查标签分布 | 标注错误或类别不平衡 | 清洗标签、加权损失 |
| 批次大小 | 尝试不同batch size | 太小噪声大,太大泛化差 | 从32开始试 |
我遇到最多的是学习率问题。很多人直接用默认的0.01,结果loss震荡得厉害。我的经验是,Adam优化器用1e-3起步,SGD用1e-2起步,然后根据loss曲线调整。如果loss震荡,学习率减半;如果loss下降太慢,学习率加倍。
5.2 推理延迟高的优化路径
推理延迟高是上线后最常见的问题。优化路径按投入产出比排序:批处理 > 模型量化 > 模型剪枝 > 硬件升级。
批处理是最容易见效的,把多个请求攒在一起推理,吞吐量能提升几倍到几十倍。模型量化是把FP32权重转成INT8,模型体积缩小4倍,推理速度提升2-3倍,精度损失通常在1%以内。模型剪枝是去掉不重要的权重,需要重新训练,投入较大。硬件升级是最后的选择,成本最高。
# 动态批处理示例 import asyncio from collections import deque class DynamicBatcher: def __init__(self, model, max_batch_size=32, max_wait=0.01): self.model = model self.max_batch_size = max_batch_size self.max_wait = max_wait self.queue = deque() async def predict(self, features): future = asyncio.Future() self.queue.append((features, future)) if len(self.queue) >= self.max_batch_size: await self._process_batch() else: await asyncio.sleep(self.max_wait) if self.queue: await self._process_batch() return await future async def _process_batch(self): batch = list(self.queue) self.queue.clear() features = [item[0] for item in batch] # 批量推理 results = self.model(features) for (_, future), result in zip(batch, results): future.set_result(result)实操心得:动态批处理的等待时间很关键。等太久延迟高,等太短批处理效果差。我的经验是,在线服务等待时间设10ms左右,离线服务可以设100ms。这个值要根据实际请求量和延迟要求调整。
5.3 模型效果回退的排查思路
模型更新后效果回退是最让人头疼的问题。我的排查思路是分三步:先确认评估方法没问题,再确认数据没问题,最后确认模型没问题。
评估方法出问题的情况包括:评估集变了、评估指标变了、评估代码有bug。数据出问题的情况包括:训练数据分布变了、特征工程变了、数据泄漏了。模型出问题的情况包括:超参数变了、网络结构变了、随机种子变了。
我习惯用MLflow记录每次实验的所有参数和指标,这样回退时能精确对比两次实验的差异。没有实验追踪的话,排查全靠记忆,效率极低。
5.4 数据漂移的检测与应对
数据漂移是模型上线后效果下降的主要原因。检测方法有PSI、KS、KL散度等。我一般用PSI,因为它计算简单、解释直观。PSI小于0.1表示分布稳定,0.1到0.25表示轻微漂移,大于0.25表示显著漂移。
import numpy as np def calculate_psi(expected, actual, buckets=10): def scale_range(data, min_val, max_val): return (data - min_val) / (max_val - min_val) breakpoints = np.arange(0, buckets + 1) / buckets * 100 breakpoints = np.percentile(expected, breakpoints) expected_percents = np.histogram(expected, breakpoints)[0] / len(expected) actual_percents = np.histogram(actual, breakpoints)[0] / len(actual) expected_percents = np.clip(expected_percents, 0.0001, None) actual_percents = np.clip(actual_percents, 0.0001, None) psi = np.sum((expected_percents - actual_percents) * np.log(expected_percents / actual_percents)) return psi检测到漂移后的应对策略取决于漂移程度。轻微漂移可以继续观察,显著漂移需要重新训练模型。重新训练时要用最新数据,并且要验证新模型在旧数据上的表现,确保不会遗忘旧知识。
6. 迭代层与工程化收尾:让系统自己转起来
6.1 实验追踪与模型注册
迭代层的第一步是实验追踪。没有实验追踪,你的所有尝试都是黑盒。我用MLflow记录每次实验的参数、指标、模型文件。这样当你想复现某个结果时,能精确找到当时的配置。
import mlflow import mlflow.pytorch mlflow.set_experiment("ai-eng-from-scratch") with mlflow.start_run(): mlflow.log_param("learning_rate", 0.001) mlflow.log_param("hidden_dim", 64) mlflow.log_param("epochs", 50) # 训练模型... mlflow.log_metric("val_loss", best_val_loss) mlflow.pytorch.log_model(model, "model")模型注册是实验追踪的延伸。每次实验产出的模型都注册到模型仓库,打上版本标签。上线时从仓库拉取指定版本的模型,而不是从本地文件加载。这样做的好处是模型版本可追溯、可回滚。
6.2 监控告警与自动再训练
监控是迭代层的眼睛。我监控三类指标:服务指标、模型指标、数据指标。服务指标包括延迟、吞吐量、错误率。模型指标包括预测分布、置信度分布。数据指标包括特征分布、缺失率。
告警阈值根据业务需求设定。比如延迟P99超过500ms告警,预测分布PSI超过0.25告警。告警触发后,根据严重程度决定是人工介入还是自动处理。
自动再训练是迭代层的终极形态。当数据漂移超过阈值时,自动触发再训练流水线,训练完成后自动评估,评估通过后自动上线。这个闭环一旦建立,系统就能自己转起来。
注意:自动再训练一定要有安全阀。我见过自动再训练把坏模型上线的案例,原因是评估集被污染了。安全阀包括:人工审批、灰度发布、快速回滚。
6.3 从最小系统到生产系统的差距
最小系统和生产系统之间的差距,主要在四个方面:可靠性、可扩展性、可维护性、安全性。
可靠性方面,生产系统要有容错、重试、降级机制。可扩展性方面,生产系统要能水平扩展,支持多实例部署。可维护性方面,生产系统要有完善的日志、监控、文档。安全性方面,生产系统要有输入校验、权限控制、数据加密。
这些差距不是一蹴而就的,而是在迭代中逐步补齐的。我的建议是,先让最小系统跑起来,产生价值,然后在实际问题驱动下逐步加固。不要一开始就追求完美架构,那样永远上不了线。
6.4 我个人在实际操作中的体会
从零搭建AI工程体系这件事,我最大的体会是:慢就是快。前期花时间理解底层、手写实现、搭建闭环,后期遇到问题时排查效率会高很多。我见过太多人跳过底层直接上框架,结果遇到问题只能靠猜,浪费的时间反而更多。
另一个体会是:评估比训练重要。训练模型谁都会,但知道模型好不好、为什么好、什么时候会不好,这才是工程能力的体现。我建议你在评估层多花时间,把指标选对、把评估集管好、把监控做全。
最后分享一个小技巧:每次遇到问题,都把排查过程记录下来。我有个“踩坑日志”,记录了这几年遇到的所有问题和解决方案。这个日志现在是我最宝贵的资产,因为大部分问题都会重复出现,有日志就能快速定位。
这个内容后续还可以这样扩展:加入特征存储层,把特征工程标准化;加入模型解释性工具,理解模型决策逻辑;加入A/B测试框架,科学评估模型效果。每一步扩展都建立在当前闭环的基础上,不会推翻重来。