
做情感分析这行当最容易被人低估的一步从来不是调模型而是把“情感”这两个字翻译成机器能算的东西。一条“东西是好东西就是这快递我真服了”的评论到底算正、算负还是算中性三个标注员能给你三个答案。所以这篇内容我不打算写成一份教科书式的入门指南而是把我在几个实际项目里跑通的那套流程完整摊开从机器学习情感分析的问题定义、语料构建、标注口径到词典法、TF-IDF 加线性模型、词向量加深度网络、预训练模型微调这四条路线的取舍再到完整的可复现代码、评估指标怎么选、badcase 怎么挖、上线后怎么监控。适合两类人看一类是刚接手一个评论分类需求、不知道从哪下手的新手照着第五节的代码能当天跑出一个 baseline另一类是已经做过一版、但线上效果总比测试集低十几个点的老手第四、五节的排查思路应该能帮你找到漏在哪。1. 动手前先把题面翻译清楚我见过太多项目死在第一步需求方说“帮我做个情感分析”双方脑子里想的根本不是同一件事。有人想的是整条评论的正负判断有人想的是从一千条反馈里自动摘出“物流慢”这个具体抱怨点。这两者的建模方式、标注成本、交付周期差着三倍以上。所以在写第一行代码之前必须把题面拆到能对应到具体输出的程度。1.1 情感分析的四个粒度选错就是白干从工程落地角度看情感分析至少有四个不同的粒度每一个对应的模型结构和标注方式都不一样值得花半小时和需求方对齐。文档级是最常见的一条评论输出一个极性标签正、负、中性三选一或者再加一档“强烈负面”。它的好处是标注便宜、模型简单、指标好解释缺点是丢信息——“质量好但客服差”这种句子只输出一个标签等于把一半信息扔了。适合做大盘情绪监控、NPS 趋势分析这类“看整体走势”的场景。句子级是把长文本切成句子逐句判极性再做聚合。中文用户的长评里经常一句话一个观点句子级能保留更多细节但对切句质量很敏感遇到不带标点的连续吐槽就抓瞎。方面级Aspect-Based是工业界真正值钱的那一档输出的是“针对哪个对象、什么情感”。比如一条评论同时抽出{物流: 负, 质量: 正, 价格: 中性}。它需要额外做实体抽取和观点配对标注成本大概是文档级的四到六倍但如果你的目标是驱动业务改进——哪个环节该被优化——只有它能直接给出答案。情绪细分则是在正负之外再分喜、怒、哀、惊、期待这些细类常用于舆情预警。老实说除非业务方明确要这一层否则不要自己加戏五分类的情绪标注一致性掉得特别快。我的一般建议是第一版永远做文档级三分类跑通链路、验证数据质量、拿到一个能看的指标再考虑往下钻。上来就做方面级八成会在标注阶段就把预算烧光。1.2 标注口径定不下来后面全是返工这一节是我认为整篇文章里最值得反复读的部分。标注规范没写好你后面调参调得再漂亮模型学的也是一堆自相矛盾的标签。几个必须提前写进规范文档的边界情况混合情感怎么算。我的做法是“按结论句或转折词之后的部分定极性”因为中文表达习惯里转折后的内容承载真实态度。“东西不错但是……”后面基本就是真实想法。规范里要明确写以“但、不过、然而、可惜”等转折词后的分句为准。三星到底算啥。五分制打分映射到三类时1-2 星负、4-5 星正、3 星中性这个映射看起来天经地义但中文语境里三星评论大量是“凑合”“还行吧”“就那样”语气偏冷淡。我在一个项目里试过把三星单独做一版结果发现它和负类的语义距离比和正类近得多。后来直接改成 1-3 星负、4-5 星正二分类F1 反而涨了六个点。中性类样本占比低于 15% 的时候果断合并成二分类别硬撑三分类。反讽必须单独培训。“真是太好了用三天就坏了”这种句子字符层面的情感词全是正的。规范里要放至少二十条反讽样例并且明确要求标注员遇到疑似反讽按真实态度标注。无条件情绪不要标。“请问这个什么时候发货”是询问不是负向但很多标注员会因为它“语气着急”标成负。规范里要留一个其他/无关标签兜底这部分样本训练时丢弃别污染模型。规范写完之后抽 10% 的样本做双人标注算一下 Cohens Kappa。Kappa 低于 0.6 说明规范有歧义回去改文档0.6 到 0.75 之间可以接受但要复盘分歧样本0.75 以上才算能开工。这一步花两天能省后面两周。1.3 技术路线选型四条路什么场景走哪条选型这件事我判断的核心依据只有两个你有多少标注数据你的延迟预算有多少。精度是结果不是先决条件。路线标注数据量级典型指标宏F1训练成本推理延迟适用场景词典 规则0只需情感词典0.60–0.72几乎为零极低冷启动、可解释性要求高、领域固定TF-IDF 线性模型每类 500 条起0.80–0.88CPU 分钟级极低Baseline、快速验证、线上兜底词向量 TextCNN/BiLSTM每类 2000 条起0.86–0.92单卡 GPU 小时级低中等数据、需要捕捉语序预训练模型微调每类 1000–3000 条0.92–0.97单卡 GPU 数小时中上线主力、精度优先大模型少样本提示几十条示例0.85–0.93无训练高单条百毫秒级冷启动、多语言、长尾类目几个实操提醒。第一别跳过 TF-IDF 直接上 BERT。听起来很诱人但 TF-IDF 加逻辑回归半小时能出结果它能立刻告诉你数据本身有没有问题——如果 TF-IDF 只能到 0.65说明标签噪声大或者任务定义本身模糊这时候上预训练模型最多也就到 0.8而且你会误以为是模型不行反复调参浪费一周。第二预训练模型不是万能的在样本量少于两千条、且文本很短少于十字的场景里它相对 TF-IDF 的提升可能只有两三个点但推理成本涨十倍这笔账要算清楚。第三大模型少样本提示在跨领域冷启动时非常好用可以先用它给无标注数据打伪标签人工抽检修正后再拿去微调小模型这是个很实用的冷启动路径。2. 数据环节决定效果上限模型结构决定的只是你能不能逼近这个上限数据决定的才是上限在哪。我做过一个粗略统计同一套 BERT 微调脚本在清洗前和清洗后的数据上跑宏 F1 差 5 到 8 个点是常事而这个差距靠调参基本补不回来。2.1 数据从哪来怎么采才不偏常见的几个来源各有各的坑。评论区抓取的问题是有强烈的自我选择偏差——愿意写评论的人要么特别满意要么特别愤怒中间态的人根本不说话导致分布呈两极分化中性样本严重稀缺。客服工单正好相反几乎全是负向正类样本得另想办法。问卷开放题质量最高但量最少通常只有几百条适合做种子集和验证集。站内搜索词、弹幕、社区帖子噪声大但能补充大量非标准表达。我比较推荐的组合是公开情感语料打底做流程验证自有业务数据做主力训练问卷开放题做高质量验证集。如果自有数据不够还有一种低成本扩容方式——用星级、点赞数、复购行为这些弱信号做远监督比如把 4-5 星评论当正类种子。但要注意远监督的噪声率通常在 10% 到 20%用之前必须人工抽检 200 条估算准确率噪声太高就要先做一轮过滤比如剔除长度少于 8 字、纯表情、纯符号的样本。采样策略上有个细节容易被忽略同一用户的多条评论要去重或分在同一侧。否则同一个人的表达习惯会被模型当成特征测试集里出现同一用户的相似句子指标虚高上线就打回原形。2.2 清洗能砍掉的噪声比想象中多中文评论的脏数据有几类固定模式处理顺序建议从粗到细完全重复与近似重复。完全重复直接drop_duplicates。近似重复用 SimHash 或者简单的编辑距离阈值相似度大于 0.9 视为重复去重。刷单评论往往是模板化改几个词这一步能砍掉不少。非评论文本。二维码引导语、返现卡文案、微商广告、“加V领红包”这类用关键词黑名单加正则过滤。我一般会先人工扫 500 条把高频的广告模板收集成规则表。无意义内容。纯数字、“hhhh”、“1111”、单个标点、纯表情。长度小于 4 个汉字的样本基本没有判别价值直接丢。格式规整。HTML 标签剥离、全角半角统一、繁体转简体、连续重复字符压缩“太好啦啦啦啦”压成“太好啦”。表情符处理。这里有个分歧有人直接删 emoji有人保留。我的做法是把高频表情映射成中文短语再拼回文本比如竖起大拇指映射为“赞”哭脸映射为“难过”。在短文本场景里一个表情携带的情感信息可能超过整句话直接删掉是浪费。脱敏。手机号、身份证号、具体地址这类必须正则替换这既是合规要求也能避免模型学到与任务无关的模式。清洗完一定要做一次分布对比清洗前后各类别的占比变化超过 5 个点说明你的清洗规则有偏可能把某一类样本大量误删了。这一步我用一个简单的柱状对比就能看出来不用复杂工具。2.3 类别不平衡与数据切分真实业务里正负比 1:4 到 1:9 都很常见。处理手段有三种我按推荐顺序排第一优先是调权重。逻辑回归里class_weightbalanced预训练模型里用加权交叉熵权重按w_c N / (K * N_c)计算N 是总样本数K 是类别数N_c 是该类样本数。举个例子10000 条数据正类 8000 负类 2000二分类那负类权重就是10000 / (2 * 2000) 2.5正类权重是10000 / (2 * 8000) 0.625。这样改的是损失函数不动数据分布最安全。第二是上采样少数类但要注意别简单复制同一条样本重复三次会让模型过拟合这几条。可以用回译翻译成英文再翻回来或者同义词替换做增强不过中文回译质量参差用完必须抽检。第三才是下采样多数类样本量大十万级以上的时候可以用否则丢信息太可惜。数据切分这块唯一必须遵守的规则是分层切分stratifyy保证训练集、验证集、测试集的类别比例一致。如果数据带时间戳而且业务分布随时间变化明显比如新品上市前后评论差异巨大建议用时间切分而不是随机切分随机切分会造成信息泄漏指标虚高。测试集占比 15% 到 20%一旦切好就封存迭代过程中只看验证集。3. 四条技术路线的具体拆解这一节按复杂度从低到高讲每条路线我都会说清楚它解决了什么问题、什么情况下该停在这里不要再往上走。3.1 词典加规则先跑通链路再谈精度词典法的逻辑很朴素维护一张情感词典正向词加一分负向词减一分加上否定词表不、没、别、毫无和程度副词表很、非常、稍微、有点再配几条规则处理否定翻转和程度加权最后按总分阈值判极性。NEG_WORDS {不, 没, 没有, 别, 毫无, 并非, 谈不上} DEGREE {极其: 2.0, 非常: 1.8, 很: 1.5, 比较: 1.2, 有点: 0.8, 稍微: 0.6} POS_WORDS {好, 满意, 推荐, 喜欢, 划算, 耐用, 惊喜} NEG_SENTI {差, 垃圾, 失望, 退货, 慢, 贵, 难用} def rule_score(text): score, i, tokens 0.0, 0, list(text) while i len(tokens): w tokens[i] if w in POS_WORDS or w in NEG_SENTI: base 1.0 if w in POS_WORDS else -1.0 # 往前看两个字符判断是否有否定词和程度副词 window .join(tokens[max(0, i - 3):i]) for neg in NEG_WORDS: if neg in window: base * -0.8 # 否定不直接取反打八折 break for deg, factor in DEGREE.items(): if deg in window: base * factor break score base i 1 return score def rule_predict(text, thr0.5): s rule_score(text) if s thr: return 正向 if s -thr: return 负向 return 中性这段代码半小时能写完准确率大概 0.6 到 0.7看起来很低但它的价值不在准确率。第一它能在你还没标注数据的时候先把整条链路输入、处理、输出、存储、展示跑通让下游知道自己要接什么格式。第二它提供了一个可解释的对照基准后面模型判错的时候你能拿规则结果做参照快速判断是模型的问题还是标签的问题。第三规则可以作为特征喂给后面的模型把rule_score作为一个数值特征拼到 TF-IDF 特征后面通常能涨一个点。词典法的硬伤也很明显领域迁移几乎为零“这手机薄得离谱”里的“离谱”在通用词典里是负向在这句里是褒义。所以它只能当过渡方案。3.2 TF-IDF 加线性模型性价比之王这条路是我所有项目的默认起点。原因很简单训练只要几十秒可解释性好能直接看哪些词权重最高在小样本下不容易过拟合而且它给出的指标能帮你判断数据质量。中文场景下第一步是分词。jieba是绕不开的选择但有三个参数必须调自定义词典。把领域词加进去比如“续航”“客服态度”“性价比”“包装破损”否则会被切碎成没有意义的片段。这一步对短文本情感分析的影响能到两三个点。停用词表。通用停用词表要补评论里的“的、了、是、就、还、也”这类虚词对极性判断贡献很小但“不、没、别”这类否定词绝对不能进停用词表进一次错一片。词性过滤。只保留名词、动词、形容词、副词能有效降维并去掉大量噪声。特征工程部分TfidfVectorizer有几个参数值得花时间调from sklearn.feature_extraction.text import TfidfVectorizer vec TfidfVectorizer( tokenizerjieba_tokenize, # 自定义分词函数 ngram_range(1, 2), # 单词 二元组 min_df3, # 出现在少于 3 篇文档里的词丢弃 max_df0.85, # 出现在 85% 以上文档里的词丢弃太通用 max_features50000, # 特征维度上限 sublinear_tfTrue, # 用 1log(tf) 替代 tf压制高频词 use_idfTrue, norml2, )sublinear_tfTrue这个参数特别值得说。原始词频是线性的一个词出现十次权重就是出现一次的十倍但情感表达往往不是这样——“差”出现一次和出现五次情感强度差别没那么大。取对数之后高频词的权重被压下来模型的稳定性明显提升。ngram_range(1,2)是为了捕捉“不好”“太贵”这种二元组合中文里否定和程度修饰经常是相邻两个字二元组能部分解决否定翻转问题。分类器我用逻辑回归起步C值在 0.5 到 4 之间网格搜一下solverliblinear在中等规模稀疏数据上比较快。想要再高一点点可以试 LinearSVC 或者朴素贝叶斯做对比通常差距在一两个点内选哪个看你的部署环境。什么时候该停在这条路线如果宏 F1 已经到 0.85 以上且业务方对精度没有极致要求我建议就用它。它的推理成本低到可以忽略一千条评论批量处理不到一秒维护成本也低模型文件就几兆。很多团队是被“必须用深度学习”的心理惯性推着走最后发现多花了两周涨了两个点推理成本涨了二十倍。3.3 词向量加深度模型捕捉语序的中间档当数据量上到每类两千条以上TF-IDF 的天花板开始显现——它本质上是个词袋词序信息全丢了“狗咬人”和“人咬狗”在它眼里一样。这时候可以考虑词向量加卷积或循环网络。常见的做法是用预训练词向量初始化嵌入层中文可以用在大规模语料上训好的向量然后接 TextCNN 或多层 BiLSTM。TextCNN 的结构很简单不同尺寸的卷积核3、4、5并行提取 n-gram 特征最大池化全连接输出。它在短文本上效果很好训练也快是性价比不错的选择。BiLSTM 加注意力在长文本上表现更好但训练慢调参更麻烦。这条路线的性价比我认为是四条里最低的——它比 TF-IDF 涨三到五个点比预训练模型低三到五个点处在一个尴尬的中间位置。除非你的场景是长文本、强语序依赖比如带大量转折的长篇测评否则我一般会跳过它直接从 TF-IDF 跳到预训练微调。如果要试有两点经验嵌入层先冻结训练两轮再解冻不然随机初始化的分类头梯度太大会把预训练词向量带偏另外一定要加dropout0.3 到 0.5和早停这类模型在小数据上过拟合得飞快。3.4 预训练模型微调精度天花板与现实成本到这一档微调的套路已经很成熟了加载中文预训练模型接一个分类头用较小的学习率2e-5到5e-5训练三到四个 epochwarmup_ratio设 0.1max_length中文短评论 64 到 128 基本够用。几个实战参数我给一下具体值这些是我反复试出来比较稳的参数推荐值说明学习率2e-5超过 5e-5 容易在第二轮就崩batch size16 或 32显存够就往上小 batch 噪声大但泛化可能更好epoch3–4超过 4 轮基本都在过拟合max_length64短评论/ 128长文按实际长度分布的 95 分位定warmup_ratio0.1前 10% 步数线性升温weight_decay0.01抑制过拟合早停指标验证集宏 F1不要用准确率关于max_length我建议先统计一遍训练集长度分布取 95 分位数。超长文本截断是有信息损失的一个处理办法是分段预测再聚合把长文按句切块每块单独预测概率最后按块长加权平均或者取最负的那一块作为整体极性因为负面信号往往藏在某一两句里取平均会被稀释。预训练模型的现实成本也要说清楚单卡 GPU 微调一个 1 亿参数级别的模型一万条数据大概二十到四十分钟推理阶段CPU 上单条几十毫秒GPU 批量可以做到单条几毫秒。如果要上高并发做动态量化把权重从 float32 压到 int8通常能提速两到三倍精度损失在一个点以内这个交易很划算。4. 一套可复现的完整流程这一节我把从原始数据到上线推理的完整代码串一遍用的是一份假设的两万条中文商品评论二分类正负。你可以把这套脚本当成模板改改路径就能跑自己的数据。4.1 目录结构与依赖我习惯按下面这个结构组织好处是每一阶段的产物都落盘方便回溯和复用不用每次从头跑sentiment/ ├── data/ │ ├── raw/ # 原始数据 │ ├── clean/ # 清洗后 │ └── split/ # 切分后的 train/val/test ├── models/ # 模型文件 ├── reports/ # 评估报告、混淆矩阵 └── src/ ├── clean.py ├── train_baseline.py ├── train_bert.py └── predict.py依赖很简单pandas、scikit-learn、jieba、numpy微调阶段再加torch和transformers。版本不要追新锁一个跑得通的版本写进requirements.txt半年后回来复现能省很多事。4.2 清洗与预处理import re, pandas as pd def normalize(text: str) - str: text re.sub(r[^], , text) # 剥 HTML text re.sub(rhttp\S, , text) # 去链接 text re.sub(r(.)\1{2,}, r\1\1, text) # 连续重复字符压缩 text text.replace(\u3000, ).strip() text re.sub(r\s, , text) return text AD_PATTERN re.compile(r(加V|领红包|返现|二维码|薇信|威信|私聊)) def is_valid(row): t row[text] if len(t) 4: return False if AD_PATTERN.search(t): return False if re.fullmatch(r[\d\W_], t): return False # 纯数字符号 return True df pd.read_csv(data/raw/reviews.csv) df[text] df[text].astype(str).map(normalize) df df.drop_duplicates(subset[text]) df df[df.apply(is_valid, axis1)] df[label] (df[star] 4).astype(int) # 1 正 0 负三星及以下归负 df[[text, label]].to_csv(data/clean/train_ready.csv, indexFalse)注意star 4这个映射是我在 1.2 节提到的经验值。你可以先用 3 星做中性单独跑一版对比看指标和业务需求的匹配度再定。切分用分层抽样from sklearn.model_selection import train_test_split train, rest train_test_split(df, test_size0.3, stratifydf[label], random_state42) val, test train_test_split(rest, test_size0.5, stratifyrest[label], random_state42)4.3 Baseline 训练与交叉验证import jieba, numpy as np from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.pipeline import Pipeline from sklearn.model_selection import StratifiedKFold, cross_val_predict from sklearn.metrics import classification_report, confusion_matrix, f1_score STOP set(open(stopwords.txt, encodingutf-8).read().split()) KEEP_NEG {不, 没, 没有, 别, 无, 非} STOP - KEEP_NEG # 保护否定词别被停用词表误杀 def tok(text): return [w for w in jieba.lcut(text) if w.strip() and w not in STOP] pipe Pipeline([ (tfidf, TfidfVectorizer(tokenizertok, token_patternNone, ngram_range(1, 2), min_df3, max_df0.85, max_features50000, sublinear_tfTrue)), (clf, LogisticRegression(C1.0, class_weightbalanced, max_iter1000, solverliblinear)), ]) skf StratifiedKFold(n_splits5, shuffleTrue, random_state42) oof_pred cross_val_predict(pipe, train[text], train[label], cvskf, methodpredict) print(classification_report(train[label], oof_pred, digits4)) print(confusion_matrix(train[label], oof_pred))用cross_val_predict拿到的折外预测做评估比单次切分稳得多尤其样本量小的时候单次切分的方差可能有三四个点。4.4 评估别只看准确率这里必须强调一件事在不平衡数据上准确率是个骗人的指标。负类占 80% 的数据集模型全predict 负类准确率 80%但它一点用都没有。要看的是宏平均 F1各类 F1 的算术平均对少数类公平这是我的主指标。少数类的召回率如果业务是“把负面反馈全捞出来”漏报FN的代价远高于误报FP那就该盯召回。混淆矩阵看错误集中在哪个方向。如果大量真负被判成正通常是标签口径或特征缺失问题。阈值曲线predict_proba输出的概率配合不同阈值画出查准率与查全率的权衡曲线让业务方自己选工作点。阈值调整这块有个具体做法模型默认阈值 0.5但如果业务要“宁可错杀不可放过”把阈值降到 0.35召回能涨五到八个点代价是精确率掉三到五个点。这个取舍不该由算法工程师拍板应该拿着曲线去找需求方谈。再补一个容易忽略的点分领域看指标。整体 F1 0.90但如果按品类拆开家电类 0.93、服饰类 0.81那问题很可能出在服饰类的文本特点大量描述尺码、面料的主观词汇上需要针对性补标注。整体指标掩盖分层问题是最常见的事故来源。4.5 Badcase 分析模型提升的真正来源我看过很多人的迭代流程训练、看指标、调参、再训练循环十几次涨了一个点。真正高效的做法是把一半时间花在 badcase 上。我一般抽 200 条验证集判错的样本逐条打标签归因分类整理成下面这种表错误类型占比典型样本特征处理方式反讽未识别18%字面正向、实际负向补 300 条反讽样本进训练集混合情感25%前后态度相反改标注口径为“转折后为准”重标标签错误12%人工看也觉得标错了修正标签重新训练领域词歧义20%“薄”“轻”在不同品类极性相反加品类特征或分品类建模否定跨度长15%“不能说不好用” 类加二元/三元 n-gram 或换模型短文本信息不足10%“就那样”归入中性或直接丢弃这张表出来之后迭代方向就非常明确了。如果“标签错误”占比超过 10%别再调模型了回去清理数据。这是最浪费时间的坑——你在拟合噪声指标虚高上线必崩。另一个实用技巧是看模型置信度分布。把验证集样本按预测概率排序看两端接近 0 和接近 1 的判错的样本这些是“模型非常自信但错了”的硬骨头往往能暴露特征工程的根本缺陷看中间段0.4 到 0.6判错的多半是标签模糊属于正常损耗不用过度优化。4.6 上线前的推理优化模型训完不代表能用。上线前要处理三件事批处理与缓存。单条推理的开销主要浪费在框架调用上批量推理能提升五到十倍的吞吐。对于重复文本比如模板化的好评加一层结果缓存命中率往往不低。模型量化与导出。把模型导出成通用推理格式做动态量化int8在 CPU 上通常能提速两到三倍内存占用降一半以上。这一步的精度损失我实测一般在一个点以内值得做。监控指标埋点。上线不是终点。要监控预测标签的分布漂移正负比突然从 7:3 变成 5:5说明数据源变了、输入文本长度分布、低置信度样本占比、以及定期的人工抽检准确率。我一般每周抽 100 条人工复核这个数字比任何离线指标都真实。5. 踩过的坑与排查速查表下面这些是我在不同项目里真实撞过的按“症状—可能原因—排查动作—解决”整理出问题的时候可以直接对着查。5.1 常见问题速查症状可能原因排查动作解决测试集 0.92线上只有 0.7数据泄漏 / 分布漂移检查切分是否含同一用户、同一时间段改时间切分重训并做线上抽检全部预测为多数类严重不平衡 未加权看混淆矩阵是否退化成一行加class_weight调阈值短评论判错多特征太少、信息不足统计错分样本长度分布长度 6 的归入“中性/其他”或丢弃反讽全判反训练集无反讽样本人工捞疑似反讽 100 条验证补样本标注规范加反讽条款新词/热词判错分词器词表过旧打印错分样本的分词结果加自定义词典或走字级特征否定句极性翻转停用词误删否定词检查停用词表保护否定词表加二元 n-gram表情密集文本判错emoji 被清洗掉对比清洗前后样本emoji 映射成中文短语保留长文判错截断丢信息看错分样本长度分布分段预测再聚合训练 loss 不降学习率过大/标签错乱用 20 条样本过拟合测试降 lr检查标签编码训练 loss 降但验证不降过拟合看训练/验证曲线分叉加 dropout、早停、减层推理 P99 延迟超标单条推理、未量化压测看单条耗时批量推理 动态量化不同批次指标波动大样本量小、切分随机性多次随机种子重跑用 5 折交叉验证报告均值±方差5.2 几条不写在文档里的经验第一先做数据审计再做模型。每个新项目我都会先跑三件事类别分布、文本长度分布、随机抽 100 条人工看。这三步二十分钟能提前发现 80% 的问题。我见过一个项目训了一周精度上不去最后发现是 CSV 导出时把标签列错位了一行全部标签都是错的。第二把“中性”当成垃圾筐要谨慎。很多人习惯把所有模棱两可的样本都扔进中性类结果中性类变成一个大杂烩模型在里面学不到任何东西还拉低了整体指标。中性类的定义必须清晰明确表达无倾向“收到了还没用”而不是“我判断不出来”。第三别用测试集调参。这条听起来是常识但实际操作中特别容易破功——你在验证集上试了二十组参数其实已经在过拟合验证集了。我的做法是留一个只跑一次的最终测试集迭代全程不碰它最后报告的那个数才是可信的。第四模型上线后指标下降先查上游。九成的情况不是模型退化而是数据管道出问题字段格式变了、爬虫抓到了新板块、清洗规则把某类合法样本误杀了。上线后第一个月我每周都会对一遍线上样本和训练样本的文本长度分布两次都靠这个提前发现了上游改动。第五考虑做一个人工复核队列。把模型置信度在 0.4 到 0.6 之间的样本自动送人工复核复核结果回流成训练数据。这个闭环跑三个月模型的表现会比任何离线调优都扎实——它在学你业务里真实存在的边界情况而不是测试集里的。6. 一点个人体会前面这套流程我反复用在不同的项目上每次都会有点微调但骨架没变过规则跑通 → baseline 立标尺 → badcase 找方向 → 预训练提精度 → 监控防漂移。有几次我贪快跳过 baseline 直接上预训练最后都绕回来补做了因为没有一个便宜的对照组你根本判断不了模型当前的表现是好还是坏。另外说个我最近越来越认同的观点情感分析这类任务的瓶颈早就不在模型结构上了。同一个预训练模型标签干净、口径统一的团队能做到 0.95标注混乱的团队怎么调都是 0.85。如果你现在手上这个项目卡在 0.85 上不去别急着换模型去把验证集里判错的样本一条条看一遍多半能发现问题——而且大概率不是模型的问题。至于后面还能往哪扩展我自己试过的两条路都值得走一是往方面级走用句子级切分加实体抽取把“物流慢”“质量好”拆出来这东西对业务的价值比整体极性高得多二是做跨领域迁移在一个品类上训好的模型拿到新品类上用少量样本微调能省掉大半标注成本。这两条路都比在原地调参有意思也更能解决问题。