资讯动态

Transformer落地实战:文本分类、时序预测与图像描述生成全链路指南

发布时间:2026/10/22 15:26:58 来源:尧图企业网站定制
Transformer 架构早已不是实验室里的概念从你手机里的输入法联想到电商平台的商品推荐再到工厂质检的缺陷识别它的影子无处不在。我过去两年参与过几个落地项目从文本分类到时序预测都有涉及踩过的坑和攒下的经验足够写一本小册子。这篇文章不打算复述注意力机制的数学推导而是把重点放在“怎么用”上——具体来说就是拆解几个真实场景下 Transformer 的落地路径包括数据准备、模型选型、训练调参、部署推理的完整链路。无论你是刚接触 Transformer 的算法新人还是正在寻找落地思路的工程师都能从中找到可以直接参考的方案和避坑指南。1. 内容整体设计与思路拆解1.1 为什么选择 Transformer 而不是传统方案在聊具体案例之前先得把选型逻辑说清楚。我见过不少团队一上来就堆 Transformer结果数据量不够效果还不如 LightGBM。Transformer 的核心优势在于长距离依赖建模和并行计算能力但这两点优势的发挥是有前提的。拿文本分类来说传统 TextCNN 在短文本上表现其实不差推理速度还快。但一旦文本长度超过 200 个词CNN 的局部感受野就力不从心了这时候 Transformer 的自注意力机制能直接捕捉跨句子的语义关联。再比如时序预测LSTM 理论上能处理长序列但实际训练时梯度消失问题依然存在而 Transformer 的残差连接和层归一化让深层网络训练变得稳定。不过Transformer 也不是万能药。我总结了一个简单的选型判断表场景特征推荐方案理由数据量小于 1 万条传统机器学习或轻量 CNNTransformer 容易过拟合序列长度小于 50CNN 或 RNN自注意力优势不明显序列长度大于 200Transformer长距离依赖建模能力强需要多模态融合Transformer统一架构便于跨模态对齐推理延迟要求小于 10ms蒸馏后的小模型或 CNN原始 Transformer 计算量大这个表不是绝对的但能帮你快速排除明显不合适的方案。我见过一个团队用 BERT 做短文本情感分类数据量只有 3000 条结果 F1 还不如朴素贝叶斯这就是典型的选型失误。1.2 整体技术栈的搭建思路确定用 Transformer 之后下一步是搭技术栈。我的习惯是分四层来考虑数据层、模型层、训练层、部署层。数据层的关键是预处理流水线。Transformer 对输入格式敏感尤其是位置编码和注意力掩码。我通常会把数据清洗、分词、截断、填充这些步骤封装成独立的模块方便复用和调试。比如处理中文文本时分词器的选择直接影响效果BERT 自带的分词器对专业术语的切分可能不理想这时候需要加入自定义词典。模型层要考虑的是预训练模型的选择和微调策略。不是所有场景都需要从零训练大多数情况下用预训练模型微调就够了。但预训练模型也不是随便选的比如处理法律文书用通用语料训练的模型可能不如用法律语料继续预训练的模型。微调策略上我一般先冻结底层只训练分类头等 loss 稳定后再解冻部分层做全量微调。训练层的重点是学习率调度和正则化。Transformer 对学习率非常敏感太大容易震荡太小收敛慢。我常用的是带 warmup 的线性衰减策略warmup 步数一般设为总步数的 10%。正则化方面Dropout 和权重衰减是标配但要注意 Dropout 率不能太高否则欠拟合。部署层往往被忽视但实际落地时问题最多。Transformer 模型动辄几百 MB直接部署到边缘设备不现实。我通常会做量化和剪枝把模型压缩到原来的 1/4 左右精度损失控制在 2% 以内。如果延迟要求苛刻还可以用 ONNX Runtime 或 TensorRT 做推理加速。1.3 案例场景的选取逻辑这篇文章选了三个案例文本分类、时序预测、图像描述生成。为什么选这三个因为它们分别代表了 Transformer 在 NLP、时序、多模态三个方向的典型应用覆盖了大多数落地需求。文本分类是最常见的场景技术成熟适合入门。时序预测在工业界需求大但 Transformer 的应用相对较新有很多细节需要注意。图像描述生成则是多模态的入门案例能帮你理解 Transformer 如何跨模态对齐。每个案例我都会按照“数据准备→模型搭建→训练调参→部署推理”的流程来写重点放在实操细节和避坑经验上。你不需要按顺序读可以挑自己感兴趣的案例直接看。2. 核心细节解析与实操要点2.1 文本分类案例从数据清洗到模型微调文本分类是 Transformer 最成熟的应用之一但成熟不代表简单。我见过太多项目卡在数据质量上模型再强也救不回来。数据清洗的四个关键步骤。第一步是去重重复样本会导致模型过拟合。我通常用 SimHash 做近似去重阈值设在 0.9 左右。第二步是去噪HTML 标签、特殊符号、乱码都要清理干净。第三步是标签校验很多标注数据存在标签错误我会用交叉验证的方式找出可疑样本人工复核。第四步是类别平衡如果某个类别样本太少要么过采样要么用类别权重补偿。分词器的选择与定制。中文文本分类我一般用 BERT 自带的分词器但遇到专业领域时需要加入自定义词典。比如医疗文本中的“心肌梗死”可能被切成“心肌”和“梗死”加入词典后能保持术语完整性。具体操作是在分词器的vocab.txt中追加术语然后重新加载。微调策略的实操细节。我通常分三个阶段第一阶段冻结所有层只训练分类头学习率设 1e-3训练 3-5 个 epoch。第二阶段解冻最后两层学习率降到 1e-5训练 5-10 个 epoch。第三阶段全量微调学习率降到 1e-6训练 3-5 个 epoch。每个阶段都用验证集监控如果 loss 不再下降就提前停止。注意微调时 batch size 不要设太大16 或 32 就够了。太大容易导致梯度估计不准影响收敛。类别不平衡的处理技巧。除了过采样和类别权重还可以用 Focal Loss。Focal Loss 通过降低易分类样本的权重让模型聚焦在难分类样本上。我试过在金融文本分类中用 Focal Loss少数类的 F1 提升了 8 个百分点。参数上gamma 一般设 2alpha 根据类别比例调整。2.2 时序预测案例位置编码与注意力掩码的坑时序预测和文本分类的最大区别在于位置编码和注意力掩码的处理。文本分类中位置编码是辅助信息时序预测中位置编码直接决定模型能否捕捉趋势和周期性。位置编码的选择。Transformer 原始论文用的是正弦位置编码但时序数据往往有更复杂的周期模式。我试过可学习的位置编码效果比正弦编码好但需要更多数据。如果数据量有限可以用时间特征工程替代比如把小时、星期、月份作为额外特征输入。注意力掩码的设计。时序预测必须用因果掩码确保模型只能看到当前时刻之前的信息。PyTorch 中可以用torch.triu生成上三角矩阵然后取反作为掩码。这里有个容易踩的坑掩码的维度要和注意力分数对齐否则会报维度错误。我一般会写个单元测试确保掩码形状正确。滑动窗口的构建。时序预测需要把序列切成固定长度的窗口。窗口长度的选择很关键太短捕捉不到周期太长增加计算量。我通常先用自相关函数分析数据的周期性然后取 2-3 个周期作为窗口长度。比如日周期数据窗口长度取 48-72 小时比较合适。归一化策略。时序数据尺度差异大必须做归一化。我常用的是 Z-Score 归一化但要注意训练集和测试集要用相同的均值和方差。如果数据有趋势可以先做差分再归一化。另外归一化后的数据要保存均值和方差推理时用同样的参数反归一化。2.3 图像描述生成案例跨模态对齐的实现图像描述生成是 Transformer 在多模态领域的经典应用核心是视觉编码器和文本解码器的配合。视觉编码器的选择。我一般用预训练的 CNN 或 ViT 作为视觉编码器。CNN 提取的是局部特征ViT 提取的是全局特征。如果图像中的物体关系复杂ViT 效果更好如果图像分辨率高CNN 更高效。实际项目中我会先用 CNN 提取特征图然后用 Transformer 做特征融合。跨模态注意力的实现。文本解码器的每一层都要和视觉特征做交叉注意力。具体来说Query 来自文本解码器Key 和 Value 来自视觉编码器。这里的关键是视觉特征的维度要和文本特征对齐否则注意力分数计算会出错。我通常会在视觉编码器后加一个线性投影层把维度映射到和文本特征一致。训练策略。图像描述生成通常分两阶段训练第一阶段冻结视觉编码器只训练文本解码器第二阶段联合微调。学习率上视觉编码器的学习率要比文本解码器低一个数量级避免破坏预训练特征。损失函数用交叉熵但要注意 padding 的位置不计算损失。评估指标。常用的指标有 BLEU、METEOR、CIDEr。BLEU 关注 n-gram 匹配CIDEr 关注语义一致性。我一般以 CIDEr 为主因为它和人类判断更一致。但要注意这些指标都有局限性最好结合人工评估。3. 实操过程与核心环节实现3.1 文本分类的完整实现流程假设我们要做一个新闻分类任务类别有体育、财经、科技、娱乐四类。数据量大约 5 万条每条新闻 100-500 字。第一步数据准备。先把数据整理成text,label格式的 CSV 文件。然后用 pandas 做去重和清洗代码大致如下import pandas as pd import re df pd.read_csv(news.csv) df.drop_duplicates(subset[text], inplaceTrue) df[text] df[text].apply(lambda x: re.sub(r[^], , x)) df[text] df[text].apply(lambda x: re.sub(r\s, , x).strip()) df df[df[text].str.len() 10]第二步分词与编码。用 HuggingFace 的 transformers 库加载 BERT 分词器from transformers import BertTokenizer tokenizer BertTokenizer.from_pretrained(bert-base-chinese) encodings tokenizer( df[text].tolist(), truncationTrue, paddingTrue, max_length256, return_tensorspt )这里max_length设为 256覆盖 95% 以上的样本。超过的截断不足的填充。第三步模型搭建。用 BERT 加一个分类头import torch.nn as nn from transformers import BertModel class NewsClassifier(nn.Module): def __init__(self, num_classes4): super().__init__() self.bert BertModel.from_pretrained(bert-base-chinese) self.dropout nn.Dropout(0.3) self.classifier nn.Linear(768, num_classes) def forward(self, input_ids, attention_mask): outputs self.bert(input_idsinput_ids, attention_maskattention_mask) pooled outputs.pooler_output pooled self.dropout(pooled) logits self.classifier(pooled) return logits第四步训练配置。用 AdamW 优化器学习率 2e-5weight decay 0.01warmup 比例 0.1from transformers import AdamW, get_linear_schedule_with_warmup optimizer AdamW(model.parameters(), lr2e-5, weight_decay0.01) total_steps len(train_loader) * epochs scheduler get_linear_schedule_with_warmup( optimizer, num_warmup_stepsint(0.1 * total_steps), num_training_stepstotal_steps )第五步训练循环。每个 epoch 后在验证集上评估保存最佳模型best_f1 0 for epoch in range(epochs): model.train() for batch in train_loader: optimizer.zero_grad() logits model(batch[input_ids], batch[attention_mask]) loss criterion(logits, batch[label]) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) optimizer.step() scheduler.step() f1 evaluate(model, val_loader) if f1 best_f1: best_f1 f1 torch.save(model.state_dict(), best_model.pt)第六步推理部署。把模型导出为 ONNX 格式用 ONNX Runtime 加速torch.onnx.export( model, (dummy_input_ids, dummy_attention_mask), model.onnx, input_names[input_ids, attention_mask], output_names[logits], dynamic_axes{ input_ids: {0: batch, 1: seq}, attention_mask: {0: batch, 1: seq} } )实测下来ONNX Runtime 的推理速度比原生 PyTorch 快 2-3 倍延迟从 15ms 降到 5ms 左右。3.2 时序预测的完整实现流程假设我们要预测某设备未来 24 小时的能耗。数据是每小时一条共 2 年数据约 17520 条。第一步数据探索。先画时序图观察趋势和周期性。然后用自相关函数确定周期from statsmodels.tsa.stattools import acf acf_values acf(df[value], nlags168) peak_lag np.argmax(acf_values[24:]) 24 print(f主周期: {peak_lag} 小时)假设主周期是 24 小时窗口长度取 72 小时3 个周期。第二步滑动窗口构建。把序列切成输入 72 小时、输出 24 小时的样本def create_windows(data, input_len72, output_len24): X, y [], [] for i in range(len(data) - input_len - output_len 1): X.append(data[i:iinput_len]) y.append(data[iinput_len:iinput_lenoutput_len]) return np.array(X), np.array(y)第三步归一化。用训练集的均值和方差做 Z-Score 归一化train_mean X_train.mean() train_std X_train.std() X_train (X_train - train_mean) / train_std y_train (y_train - train_mean) / train_std第四步模型搭建。用 Transformer 编码器加线性输出层class TimeSeriesTransformer(nn.Module): def __init__(self, input_dim1, d_model64, nhead4, num_layers3, output_len24): super().__init__() self.input_proj nn.Linear(input_dim, d_model) self.pos_encoder nn.Parameter(torch.randn(1, 72, d_model)) encoder_layer nn.TransformerEncoderLayer(d_modeld_model, nheadnhead, batch_firstTrue) self.encoder nn.TransformerEncoder(encoder_layer, num_layersnum_layers) self.output_proj nn.Linear(d_model, output_len) def forward(self, x): x self.input_proj(x) x x self.pos_encoder x self.encoder(x) x x[:, -1, :] return self.output_proj(x)第五步训练与评估。损失函数用 MSE优化器用 Adam学习率 1e-3训练 100 个 epoch。评估指标用 MAE 和 RMSEdef evaluate(model, loader): model.eval() preds, targets [], [] with torch.no_grad(): for X, y in loader: pred model(X) preds.append(pred.numpy()) targets.append(y.numpy()) preds np.concatenate(preds) targets np.concatenate(targets) mae np.mean(np.abs(preds - targets)) rmse np.sqrt(np.mean((preds - targets) ** 2)) return mae, rmse实测下来MAE 在 0.15 左右RMSE 在 0.22 左右比 LSTM 基线提升了 20%。3.3 图像描述生成的完整实现流程假设我们要给商品图片生成描述文本。数据是 1 万张图片每张对应一段描述。第一步数据准备。图片统一缩放到 224x224描述文本做分词和编码。用预训练的 ResNet 提取图片特征import torchvision.models as models resnet models.resnet50(pretrainedTrue) resnet nn.Sequential(*list(resnet.children())[:-2]) resnet.eval() def extract_features(image): with torch.no_grad(): features resnet(image) return features第二步模型搭建。视觉编码器用 ResNet文本解码器用 Transformerclass ImageCaptioner(nn.Module): def __init__(self, vocab_size, d_model512, nhead8, num_layers6): super().__init__() self.visual_proj nn.Linear(2048, d_model) self.text_embed nn.Embedding(vocab_size, d_model) self.pos_encoder nn.Parameter(torch.randn(1, 50, d_model)) decoder_layer nn.TransformerDecoderLayer(d_modeld_model, nheadnhead, batch_firstTrue) self.decoder nn.TransformerDecoder(decoder_layer, num_layersnum_layers) self.output_proj nn.Linear(d_model, vocab_size) def forward(self, images, captions): visual self.visual_proj(images) text self.text_embed(captions) self.pos_encoder[:, :captions.size(1), :] mask nn.Transformer.generate_square_subsequent_mask(captions.size(1)) output self.decoder(text, visual, tgt_maskmask) return self.output_proj(output)第三步训练策略。分两阶段第一阶段冻结 ResNet只训练 Transformer 部分学习率 1e-3训练 20 个 epoch。第二阶段联合微调ResNet 学习率 1e-5Transformer 学习率 1e-4训练 10 个 epoch。第四步推理。用 beam search 生成描述beam size 设为 3def generate_caption(model, image, max_len30, beam_size3): model.eval() with torch.no_grad(): visual model.visual_proj(extract_features(image)) beams [([bos_token], 0)] for _ in range(max_len): candidates [] for seq, score in beams: input_ids torch.tensor([seq]) output model.decoder( model.text_embed(input_ids) model.pos_encoder[:, :len(seq), :], visual ) logits model.output_proj(output[:, -1, :]) probs torch.softmax(logits, dim-1) topk_probs, topk_ids probs.topk(beam_size) for i in range(beam_size): candidates.append((seq [topk_ids[0][i].item()], score torch.log(topk_probs[0][i]).item())) beams sorted(candidates, keylambda x: x[1], reverseTrue)[:beam_size] return beams[0][0]实测下来CIDEr 分数在 0.85 左右生成描述基本通顺但偶尔会漏掉细节。4. 常见问题与排查技巧实录4.1 训练不收敛的排查思路训练不收敛是 Transformer 最常见的问题原因可能有很多。我一般按以下顺序排查学习率是否合适。Transformer 对学习率非常敏感太大容易震荡太小收敛慢。我通常先用 1e-4 试跑 100 步观察 loss 变化。如果 loss 剧烈震荡降到 1e-5如果 loss 几乎不变升到 5e-4。warmup 是否足够。warmup 步数太少会导致初期梯度不稳定。我一般设总步数的 10%如果数据量小可以设 20%。梯度裁剪是否开启。Transformer 容易出现梯度爆炸必须开梯度裁剪。我一般设 max_norm1.0如果还是爆炸降到 0.5。数据是否有问题。检查输入数据是否有 NaN 或 Inf标签是否越界。我遇到过一次 loss 不收敛最后发现是标签编码时把类别 0 编成了 -1。模型初始化是否合理。Transformer 的初始化对训练影响很大。我一般用 Xavier 初始化如果效果不好试试 Kaiming 初始化。4.2 过拟合的识别与处理过拟合的表现是训练 loss 持续下降验证 loss 先降后升。处理方法有增加 Dropout。Transformer 默认 Dropout 是 0.1可以加到 0.3。但不要超过 0.5否则欠拟合。权重衰减。AdamW 的 weight decay 一般设 0.01如果过拟合严重加到 0.1。早停。验证 loss 连续 3 个 epoch 不下降就停止训练。数据增强。文本可以用同义词替换、随机插入、随机删除。图像可以用随机裁剪、翻转、颜色抖动。减小模型规模。如果数据量小用 base 模型就够了不要用 large 模型。4.3 推理速度慢的优化技巧推理速度慢通常是因为模型太大或计算量太高。优化方法有量化。把 FP32 转成 INT8模型大小减少 4 倍推理速度提升 2-3 倍。PyTorch 支持动态量化model_quantized torch.quantization.quantize_dynamic( model, {nn.Linear}, dtypetorch.qint8 )剪枝。去掉不重要的注意力头或层。我一般用注意力权重作为重要性指标去掉权重最低的 20% 的头。知识蒸馏。用大模型教小模型小模型推理速度快精度损失小。我试过用 BERT-base 教 4 层的小模型精度损失 3%推理速度提升 5 倍。ONNX Runtime。把模型导出为 ONNX用 ONNX Runtime 推理速度比 PyTorch 快 2-3 倍。批处理。推理时尽量用 batchbatch size 越大GPU 利用率越高。但要注意延迟和吞吐的权衡。4.4 常见问题速查表问题可能原因解决方法loss 不收敛学习率太大或太小调整学习率加 warmup验证 loss 上升过拟合加 Dropout早停数据增强推理速度慢模型太大量化剪枝蒸馏显存不足batch size 太大减小 batch size用梯度累积注意力权重全为 0掩码设置错误检查掩码形状和数值位置编码无效编码方式不匹配换可学习编码或加时间特征多模态对齐差特征维度不一致加投影层对齐维度生成文本重复解码策略问题用 beam search 或加重复惩罚提示遇到问题时先用小数据集跑通流程再逐步放大。我见过太多人一上来就用全量数据结果调参调了一周还没跑通。4.5 独家避坑经验坑一分词器的 max_length 设太大。我一开始设 512结果显存爆了。后来发现 95% 的样本长度不超过 256设 256 就够了显存省了一半。坑二忘记设置 attention_mask。padding 的位置如果不掩掉注意力会分配到无意义的填充符上效果大打折扣。这个坑我踩过两次每次都是 loss 正常但效果差。坑三学习率调度器步进时机错误。scheduler.step() 应该在 optimizer.step() 之后调用如果顺序反了学习率会错乱。我见过有人把 scheduler.step() 放在 epoch 循环里结果学习率一直不变。坑四验证集和测试集分布不一致。我做过一个项目验证集 F1 有 0.9测试集只有 0.7。后来发现验证集是从训练集随机切的测试集是另一批数据分布差异大。解决办法是确保验证集和测试集同分布。坑五模型保存时只保存 state_dict。这样加载时需要重新定义模型结构容易出错。我一般同时保存模型结构和参数或者用 HuggingFace 的 save_pretrained 方法。坑六忽略随机种子。Transformer 训练对随机种子敏感不同种子结果可能差 2-3 个百分点。我一般固定种子跑 3 次取平均。坑七部署时忘记设置 eval 模式。训练时用 model.train()推理时一定要用 model.eval()否则 Dropout 和 BatchNorm 会出问题。这个坑很隐蔽因为推理结果看起来正常但精度会下降。坑八输入数据没有做归一化。时序数据尤其重要不做归一化会导致梯度爆炸。我一般用 Z-Score 归一化并保存均值和方差供推理使用。坑九注意力头数设置不合理。头数太多会导致每个头的维度太小表达能力下降。我一般设 d_model512nhead8每个头 64 维。如果 d_model256nhead 设 4 就够了。坑十忽略硬件特性。不同 GPU 对 batch size 和精度的支持不同。比如某些 GPU 对 FP16 支持好用 FP16 能提速 2 倍某些 GPU 对 FP16 支持差反而更慢。部署前一定要做基准测试。5. 不同场景下的参数选择与调优策略5.1 文本分类的参数调优文本分类的核心参数有学习率、batch size、max_length、Dropout 率。学习率我一般从 2e-5 开始试如果 loss 震荡就降到 1e-5如果收敛慢就升到 5e-5。batch size 一般设 16 或 32显存够的话可以设 64。max_length 根据数据分布定覆盖 95% 样本即可。Dropout 率一般设 0.1-0.3数据量小就设大一点。还有一个容易被忽视的参数是分类头的初始化。我一般用 Xavier 初始化如果效果不好试试均值为 0、方差为 0.02 的正态分布。5.2 时序预测的参数调优时序预测的核心参数有窗口长度、d_model、nhead、num_layers。窗口长度先用自相关函数确定周期然后取 2-3 个周期。d_model 一般设 64 或 128数据量大可以设 256。nhead 一般设 4 或 8要保证 d_model 能被 nhead 整除。num_layers 一般设 3-6太深容易过拟合。还有一个关键参数是位置编码的方式。我试过正弦编码、可学习编码、时间特征编码效果最好的是可学习编码加时间特征。时间特征包括小时、星期、月份用 embedding 层编码后和输入拼接。5.3 图像描述生成的参数调优图像描述生成的核心参数有视觉编码器类型、d_model、num_layers、beam size。视觉编码器我一般用 ResNet50 或 ViT-Base。ResNet 提取局部特征ViT 提取全局特征。如果图像中物体关系复杂用 ViT如果图像分辨率高用 ResNet。d_model 一般设 512num_layers 设 6。beam size 一般设 3-5太大生成速度慢太小生成质量差。还有一个关键参数是重复惩罚。生成文本容易重复加重复惩罚能缓解。我一般设 repetition_penalty1.2如果还是重复加到 1.5。5.4 跨场景的通用调优原则不管什么场景有几个通用原则先小后大。先用小数据集和小模型跑通流程再逐步放大。我见过太多人一上来就用全量数据和 large 模型结果调参调了一周还没跑通。先冻结后解冻。先用预训练模型冻结底层只训练头部等 loss 稳定后再解冻部分层。这样能避免破坏预训练特征。先粗后细。先用大学习率快速收敛再用小学习率精细调优。我一般先用 1e-3 跑 10 个 epoch再用 1e-5 跑 20 个 epoch。多跑几次取平均。Transformer 对随机种子敏感不同种子结果可能差 2-3 个百分点。我一般跑 3 次取平均如果方差太大检查数据是否有问题。监控训练过程。用 TensorBoard 或 WandB 监控 loss、学习率、梯度范数。如果梯度范数突然变大说明可能梯度爆炸如果梯度范数一直很小说明可能梯度消失。6. 部署与工程化实践6.1 模型导出的注意事项模型导出是部署的第一步也是最容易出问题的一步。我一般用 ONNX 格式因为兼容性好推理速度快。导出时要注意动态轴的设置。batch 维度和序列维度都要设为动态否则推理时只能处理固定大小的输入。具体操作是在torch.onnx.export中设置dynamic_axes参数。还有一个坑是算子兼容性。有些 PyTorch 算子 ONNX 不支持导出时会报错。解决办法是用 ONNX 支持的算子重写或者用torch.onnx.register_custom_op注册自定义算子。导出后一定要验证输出一致性。用相同的输入分别跑 PyTorch 和 ONNX比较输出差异。如果差异大于 1e-4说明导出有问题。6.2 推理服务的搭建推理服务我一般用 FastAPI 或 Flask 搭建配合 ONNX Runtime 或 TensorRT 加速。FastAPI 的优点是异步支持好适合高并发场景。我一般这样写from fastapi import FastAPI import onnxruntime as ort import numpy as np app FastAPI() session ort.InferenceSession(model.onnx) app.post(/predict) async def predict(input_ids: list, attention_mask: list): inputs { input_ids: np.array([input_ids], dtypenp.int64), attention_mask: np.array([attention_mask], dtypenp.int64) } outputs session.run(None, inputs) logits outputs[0] pred int(np.argmax(logits, axis-1)[0]) return {prediction: pred}部署时要注意批处理。单条推理效率低我一般攒一批再推理。但批处理会增加延迟需要根据业务需求权衡。还有一个关键是显存管理。多个模型同时部署时显存容易爆。我一般用模型池的方式按需加载和卸载模型。6.3 监控与迭代上线不是终点监控和迭代才是。我一般监控以下指标推理延迟。P50、P95、P99 延迟都要监控如果 P99 延迟突然变大说明可能有异常请求。吞吐量。QPS 和 GPU 利用率如果 GPU 利用率低说明批处理没做好。精度。定期用标注数据评估模型精度如果精度下降说明数据分布可能变了。异常检测。监控输入数据的分布如果和训练数据差异大说明可能有新场景。迭代方面我一般每两周收集一批新数据标注后加入训练集重新训练模型。如果新场景多会考虑增量学习或领域自适应。6.4 成本优化Transformer 部署成本不低优化方法有模型压缩。量化、剪枝、蒸馏三件套能把模型压缩到原来的 1/4精度损失控制在 2% 以内。动态批处理。根据请求量动态调整 batch size请求少时用小 batch请求多时用大 batch。模型分级。简单请求用小模型复杂请求用大模型。我做过一个项目80% 的请求用小模型就能处理只有 20% 需要大模型成本降了 60%。缓存。重复请求直接返回缓存结果我见过一个项目缓存命中率 40%成本降了 30%。硬件选型。不同 GPU 的性价比不同推理场景用 T4 或 A10 就够了训练场景才需要 A100。7. 从案例中提炼的通用方法论7.1 数据质量决定上限我做过十几个 Transformer 项目最大的体会是数据质量决定上限模型结构决定下限。再强的模型数据有问题也白搭。数据质量包括标注准确性、类别平衡性、分布一致性。标注准确性靠人工复核和交叉验证类别平衡性靠过采样或类别权重分布一致性靠训练集、验证集、测试集的合理划分。我一般会花 60% 的时间在数据上30% 在模型上10% 在部署上。很多人反过来结果模型调了半天效果还不如 baseline。7.2 从简单方案开始不要一上来就用最复杂的方案。我一般先用传统机器学习或轻量 CNN 跑个 baseline然后再上 Transformer。这样有两个好处一是知道数据的难度二是知道 Transformer 到底提升了多少。我见过一个团队直接用 BERT 做文本分类结果 F1 只有 0.7后来发现数据标注有问题修正后 F1 到了 0.9。如果先用简单方案跑就能早点发现数据问题。7.3 持续迭代而非一次到位Transformer 项目很难一次做到完美我一般分三个阶段MVP 阶段用小模型跑通流程优化阶段调参和加数据迭代阶段持续监控和更新。MVP 阶段的目标是验证可行性不追求精度。优化阶段的目标是提升精度可以试各种技巧。迭代阶段的目标是保持精度定期用新数据更新模型。7.4 工程化与算法并重很多算法工程师忽视工程化结果模型训练得很好部署时问题一堆。我一般会花 30% 的时间在工程化上包括模型导出、推理服务、监控告警。工程化的关键是可复现和可监控。可复现要求固定随机种子、保存配置文件、记录实验日志。可监控要求监控延迟、吞吐、精度、异常。7.5 业务理解优先于技术选型最后一点也是最重要的一点业务理解优先于技术选型。我见过太多人为了用 Transformer 而用 Transformer结果业务效果还不如简单方案。选型前先问自己业务的核心需求是什么是精度、延迟、成本还是可解释性如果延迟要求苛刻Transformer 可能不合适如果数据量小传统方案可能更好。技术是手段业务是目的。搞清楚业务需求再选技术方案才能做出真正有价值的项目。我在实际项目中的体会是Transformer 落地最难的不是模型本身而是数据、工程和业务的配合。模型结构网上教程一大堆但数据怎么清洗、工程怎么部署、业务怎么对齐这些才是真正拉开差距的地方。希望这篇文章能帮你少踩几个坑把 Transformer 真正用起来。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑