聊聊我这两年在AI模型训练调优上踩过的坑

UIDesigner
2026-07-22 05:54
阅读 947

写在前面:最近在看机会,整理一下这两年做的一些东西,算是给自己一个交代。文章有点长,建议泡杯茶慢慢看。

引言:一个后端程序员的AI自救之路

说实话,写这篇文章的时候我正纠结要不要跳槽。

在这个组干了快两年了,说不上多热爱,但也谈不上多讨厌。每天的工作就是写写CRUD,维护维护那坨不知道谁写的祖传代码,偶尔处理一下线上报警。日子过得挺安逸的,但安逸得让人心慌。

特别是去年开始,ChatGPT火了之后,我发现自己已经重度依赖AI辅助开发了。写代码先问Claude,调Bug先问GPT,甚至连写周报都要让AI帮我润色一下。有时候想想,要是哪天AI真能完全替代程序员了,我这种水平的大概是第一波被优化的吧。

这种焦虑感在上周五晚上达到了顶峰。那天加班到十点多,改一个历史遗留的并发Bug,改到怀疑人生的时候,我突然想:我这两年到底积累了什么?除了会用各种AI工具提效,我还有什么拿得出手的东西?

于是周末我翻了自己这两年的技术笔记,发现有一块内容还挺有意思的——就是去年做的AI模型训练调优的项目。当时是为了给业务做一个智能客服的意图识别模块,从零开始搞的。虽然最后效果也就那样吧,但过程中确实踩了不少坑,也学到了一些东西。

今天就把这些经验分享出来,算是给自己这两年的后端+AI探索之路做个总结。

背景:为什么一个后端要搞AI

事情要从去年Q2说起。

当时产品经理(对,就是那个永远在改需求的产品经理)提了一个需求:要在现有的客服系统里加一个智能意图识别功能,自动判断用户发消息的意图,然后路由到对应的处理流程。

"这个简单啊,用规则引擎不就行了?"我当时是这么想的。

然后产品经理给我看了一个列表,里面密密麻麻写了200多种意图分类,而且还在不断增加。规则引擎?写到猴年马月去。

没办法,领导拍板了,说要用AI模型来做。我们组当时没有专门搞算法的,这个任务就落到了我这个"对AI有点兴趣"的后端头上。

当时的情况是这样的:

维度 现状
团队配置 3个后端 + 1个前端 + 1个测试,没有算法工程师
数据量 历史对话数据大概50万条,但标注过的只有2万条
时间要求 两个月内上线MVP版本
技术栈 后端Java为主,Python只会写脚本
GPU资源 申请了两张A10,80G显存

说实话,看到这个配置的时候我是有点懵的。但转念一想,这不正好是个学习的机会吗?反正最近也在纠结要不要跳槽,要是能把这个项目做好,简历上也能多点东西。

而且,现在AI编程工具这么好用,有ChatGPT和Claude辅助,应该不至于完全搞不定吧?

模型选型:别一上来就搞大模型

刚开始的时候,我犯了个典型的错误——想一步到位。

当时大模型正火,我心想直接微调一个LLM不就完事了?于是兴冲冲地开始研究怎么微调Llama。结果搞了两天就发现不对劲:

  1. 数据量不够:2万条标注数据,微调大模型根本不够塞牙缝的
  2. 推理成本太高:就算训出来了,线上推理的延迟和成本也扛不住
  3. 杀鸡用牛刀:意图识别本质上是个文本分类任务,用大模型太浪费了

这里我要分享一个重要的心得:模型选型一定要从业务场景出发,不要追新追大

最后我选的技术方案是这样的:

基础模型:BERT-base-chinese(预训练模型)
任务类型:文本分类(多分类)
训练框架:HuggingFace Transformers + PyTorch
部署方案:ONNX Runtime + FastAPI

为什么选BERT?

  • 中文支持好,有现成的预训练模型
  • 模型大小适中,推理速度可以接受
  • 对于文本分类这种任务,效果已经足够好了
  • 社区资料多,踩坑了容易找到解决方案

当然,如果你数据量够大、场景够复杂,也可以考虑用DeBERTa或者RoBERTa这些更强的模型。但对于我们当时的情况,BERT-base完全够用了。

数据预处理:脏活累活才是核心

搞过AI的同学应该都知道,模型训练80%的时间都在处理数据。这话一点不夸张。

我们那50万条历史对话数据,质量参差不齐。有些是测试数据,有些是乱打的,还有些一个对话里包含了好几个意图。清洗这些数据花了我整整两周。

这里分享几个数据处理的实战技巧:

1. 数据清洗的优先级

# 清洗顺序很重要,先做简单的再做复杂的
cleaning_pipeline = [
    remove_duplicates,        # 去重
    remove_test_data,         # 剔除测试数据
    filter_short_text,        # 过滤过短的文本(<5个字符)
    remove_special_chars,     # 去除特殊字符
    normalize_whitespace,     # 规范化空白字符
    check_label_quality,      # 检查标注质量
]

2. 类别不平衡处理

我们的意图分类有个严重的问题:头部意图占了80%的数据,尾部意图加起来才20%。这种长尾分布直接训模型效果会很差。

我试了几种方案:

方案 做法 效果 推荐度
过采样 对少数类进行复制 容易过拟合 ★★☆☆☆
欠采样 对多数类进行删减 丢失信息 ★★☆☆☆
Focal Loss 调整损失函数权重 效果不错 ★★★★☆
数据增强 用同义词替换、回译等 最推荐 ★★★★★

最后我综合用了Focal Loss + 数据增强,效果最好。

数据增强这块,我用了两种方法:

  • 回译增强:中文 -> 英文 -> 中文,用翻译API批量生成
  • LLM增强:用ChatGPT生成相似表述,这个效率贼高

说到LLM增强,这里有个小技巧:不要让GPT直接生成训练数据,而是让它生成"改写规则",然后你用代码批量执行。这样既保证了多样性,又可控。

# 让GPT生成改写规则的例子
prompt = """
请为以下客服对话生成5种不同的改写方式,要求:
1. 保持原意不变
2. 使用不同的表达方式
3. 包含口语化和书面化表达
4. 长度控制在10-50字之间

原始文本:{text}
意图标签:{label}
"""

用Claude批量跑了几千条改写规则,然后写了个脚本自动执行,一天就搞定了之前需要一周的数据增强工作。这就是AI提效的魅力。

训练调优:玄学中的科学

好了,数据搞定了,终于到了激动人心的训练环节。

先说结论:调参是个玄学,但也不是完全没有规律可循

学习率的选择

学习率可能是最重要的超参数了。我当时的策略是:

  1. 先用较大的学习率(1e-3)跑几个epoch,看loss下降趋势
  2. 用学习率搜索(LR Finder)找到最优范围
  3. 使用Warmup + Cosine Decay的学习率调度策略
from transformers import get_cosine_schedule_with_warmup

# 这个配置在我们场景下效果最好
optimizer = AdamW(model.parameters(), lr=2e-5, weight_decay=0.01)
scheduler = get_cosine_schedule_with_warmup(
    optimizer,
    num_warmup_steps=int(len(train_dataloader) * 0.1),  # 10% warmup
    num_training_steps=len(train_dataloader) * epochs
)

Batch Size的权衡

Batch Size不是越大越好。我们两张A10的显存,batch size最大能开到64。但实际测试下来,batch size=16 + gradient accumulation=4的效果反而更好。

原因我后来分析了一下,可能是小batch size带来了一定的正则化效果,防止过拟合。

Early Stopping很重要

千万不要傻等训练跑完。一定要设Early Stopping,不然很可能后面几个epoch就开始过拟合了。

# 我的Early Stopping配置
early_stopping = {
    'patience': 3,           # 3个epoch没提升就停
    'min_delta': 0.001,      # 最小提升阈值
    'monitor': 'val_f1',     # 监控验证集F1
    'mode': 'max'            # F1越大越好
}

混合精度训练

这个必须开,不开就是浪费生命。用AMP(Automatic Mixed Precision)可以节省将近一半的显存,训练速度也能提升30%左右。

from torch.cuda.amp import autocast, GradScaler

scaler = GradScaler()

for batch in dataloader:
    optimizer.zero_grad()
    with autocast():
        outputs = model(**batch)
        loss = outputs.loss
    scaler.scale(loss).backward()
    scaler.step(optimizer)
    scaler.update()

效果评估:别只看Accuracy

模型训完了,效果怎么评估?

很多新手只看Accuracy,这是不对的。特别是对于我们这种类别不平衡的场景,Accuracy会有很大的误导性。

我主要看这几个指标:

指标 含义 为什么重要
Macro F1 各类别F1的平均值 关注每个类别的表现
Weighted F1 按类别样本数加权的F1 反映整体表现
Per-class F1 每个类别的F1 找到薄弱类别
Confusion Matrix 混淆矩阵 分析容易混淆的类别

最后我们的模型在测试集上的表现:

Overall Accuracy: 91.3%
Macro F1: 0.847
Weighted F1: 0.908

Top-3 表现最差的类别:
- "投诉建议" F1: 0.62(样本太少,只有80条)
- "其他" F1: 0.71(这个类别本身就很模糊)
- "账户安全" F1: 0.75(和"登录问题"容易混淆)

对于表现差的类别,我们又针对性地补充了数据,重新训练了一版,最终Macro F1提升到了0.87。

工程化部署:后端的老本行

模型效果达标了,接下来就是部署上线。这块反而是我最熟悉的,毕竟后端是我的老本行。

部署方案我选了ONNX Runtime + FastAPI的组合:

  1. 模型转换:PyTorch模型转成ONNX格式,推理速度能提升2-3倍
  2. 服务框架:FastAPI,性能好,自带Swagger文档
  3. 容器化:Docker + K8s,方便扩缩容
  4. 监控告警:Prometheus + Grafana,监控推理延迟和QPS
# FastAPI服务示例
from fastapi import FastAPI
import onnxruntime as ort

app = FastAPI()
session = ort.InferenceSession("model.onnx")

@app.post("/predict")
async def predict(text: str):
    # 预处理
    inputs = tokenizer(text, return_tensors="pt", max_length=128, truncation=True)
    
    # 推理
    outputs = session.run(None, {
        "input_ids": inputs["input_ids"].numpy(),
        "attention_mask": inputs["attention_mask"].numpy()
    })
    
    # 后处理
    pred_label = label_map[np.argmax(outputs[0])]
    confidence = float(np.max(outputs[0]))
    
    return {"label": pred_label, "confidence": confidence}

上线后的性能数据:

指标 数值
P50延迟 15ms
P99延迟 45ms
QPS(单实例) 200+
显存占用 约2G
CPU使用率 约30%

这个性能完全满足业务需求了。上线后跑了几个月,没出过什么线上事故,算是比较顺利。

回顾与思考

回过头来看这个项目,有几点感触比较深:

第一,AI工具真的能大幅提效。整个项目过程中,ChatGPT和Claude帮我解决了很多问题。从数据清洗脚本的编写,到训练代码的调试,再到部署方案的调研,几乎每个环节都有AI的参与。作为一个后端转AI的选手,没有这些工具我根本不可能在两个月内把项目做出来。

第二,不要迷信大模型。BERT这种"老"模型在特定场景下完全够用,而且成本低、部署简单。大模型有它的用武之地,但不是所有场景都需要。

第三,数据和工程能力同样重要。模型调优固然重要,但数据质量、工程化部署这些"脏活累活"往往决定了项目能不能真正落地。

第四,持续学习很重要。这两年AI发展太快了,不学习真的会被淘汰。这也是我最近在看机会的原因之一——想找一个更能接触前沿技术的环境。

写在最后

如果你也是一个后端想转AI方向的同学,我的建议是:

  1. 不要一上来就搞大模型,先从经典模型入手
  2. 重视数据质量,这是地基
  3. 发挥你的工程优势,把模型落地做好
  4. 善用AI工具,让AI帮你写代码、查资料、调Bug
  5. 多动手实践,光看论文没用

好了,就写到这里吧。希望这些经验能帮到正在看这篇文章的你。

对了,如果你所在的公司有比较好的AI方向的机会,欢迎私信交流。最近确实在认真考虑换个环境,想做一些更有挑战的事情。

毕竟,在这个AI快速发展的时代,原地踏步就等于退步啊。

评论 0

最热最新
暂无评论
UIDesignerLv.1
0
影响力
0
文章
0
粉丝