
疫情期间网民情绪识别这个项目我在线下分享时提过几次每次都有朋友误以为又要上人脸识别或者语音情绪那套多模态方案。其实这是个典型的机器学习文本分类任务输入一条网民评论输出它的情绪倾向。真正动手做下来你会发现难点根本不在模型选得多前沿而在于任务边界怎么划、疫情语料怎么搞、文本特征怎么提以及评估指标怎么定才不会被虚高的准确率骗过去。这篇是这个系列里我认为最贴近实用场景的一篇适合正在做舆情分析、评论挖掘或者想拿真实中文语料练手文本分类的同学参考。我尽量把从数据到评估的全过程拆开讲包括那些踩过才知道的坑。1. 先把任务边界讲清楚做几分类、用什么粒度、要什么产出1.1 为什么这里情绪识别其实是情感倾向分类很多人听到情绪识别第一反应是识别喜、怒、哀、乐、惊、恐这类离散情绪。但疫情场景下的网民评论大部分是短文本指望一条十几字的评论能分出七八种细腻情绪既不现实也不实用。实际操作中我把它收敛成一个三分类问题正面、中性、负面。这不是偷懒而是经过标注成本和应用场景双重权衡的结果。从应用端看疫情期间舆情监测最核心的诉求是发现负面情绪聚集而不是精确数出愤怒比悲伤多多少个百分点。三分类的负面类别足以支撑预警和趋势分析。从模型端看七分类或更细粒度类别之间标注一致性会急剧下降——两个人对惊讶和恐惧的边界判断很容易不一致最后模型学到的只是标注者的主观噪声。1.2 明确不做什么同样重要我在项目启动时列了一个不做什么清单不做多模态不处理图片、视频、音频不做用户级画像不分析某个人连续几天情绪变化不做时序预测不预测未来负面情绪的涨跌不追求实时流式处理只做批量离线分析加一个简单的查询接口这么定是因为第一版的目标是尽快交付一个可靠的基线系统。把单文本分类做好就已经覆盖了舆情分析的主要需求。时序、画像这些都是后续叠加能力不该在第一阶段拖慢节奏。事实证明这个决策非常正确——很多团队在做类似项目时把第一步铺得太大数据管道都还没稳定就先设计了一堆模块最后全部烂尾。标签定义参考标签定义示例特征正面表达支持、感谢、信心、感动、幽默乐观医护人员辛苦了相信很快会好起来中性客观陈述事实、提问、转发、无明显情绪倾向今日新增XX例在哪里预约核酸负面表达焦虑、愤怒、抱怨、恐惧、失望、悲伤买不到口罩太气人了什么时候是个头2. 疫情语料从哪来采集、清洗与标注比想象中更花时间2.1 数据来源与时间窗口选择的逻辑文本分类项目里语料质量直接决定模型上限。这个项目的语料我主要从公开渠道的评论区入手新闻客户端评论、微博评论、论坛帖子以及公众号文章留言。采集时注意只保留纯文本内容不抓取用户ID等隐私信息处理完全部脱敏后再进训练集。时间窗口我选了2020年1月到4月也就是疫情从爆发到国内逐步复工复产的阶段。为什么要拉开这个窗口因为不同阶段的网民情绪结构差异非常大。1月底是恐慌和求助信息集中2月中开始有对防控措施的支持与质疑交织3月底到4月则出现了大量对复工政策的讨论。如果只取某一周的数据模型会对这个阶段的表达习惯过拟合后面换时间段效果会下降。一个真实的情况是分时段的样本比例必须人为控制。按自然流量采集某个舆论热点事件可能让某一天的数据量暴增导致模型被这部分语料主导。我的做法是每周设置采样配额比如每周最多保留3000条评论超出部分随机下采样确保各阶段都有代表。2.2 标注流程两个人打底第三人仲裁标注是这类项目最容易被低估的环节。我见过不少团队让一个实习生快速标一遍结果模型训练出来一看测试集上的不错效果全是标注噪声造成的假象。我的标注策略是每个样本由两个标注者独立标注采用正面/中性/负面三选一两人一致时直接采用不一致时交给第三人仲裁标注前编写一份标注指南明确边界案例的处理方式每批次标注完做一致性抽检用Cohens Kappa系数衡量低于0.6就暂停标注并重新对齐标准这里有个经验标注指南一定要带例子。比如医生加油你们是最棒的标正面这个没人会犹豫但看到这个数字沉默了这种呢我倾向标负面因为它传达的是低落的情绪。这种案例不写进指南标注员就只能凭感觉一致性必然差。2.3 清洗流水线宁可多洗不可漏洗清洗这一步的产出直接影响特征质量。我的清洗流程按顺序执行去掉非文本杂讯网页链接、用户、图片占位符、HTML标签处理重复内容完全重复的句子去重转发微博这类机械复制的填充文本单独过滤统一英文与数字全角转半角数字替换为固定占位符数字避免模型过度记忆具体数字处理表情符号emoji转换成文字标记比如 - emoji_口罩而不是直接删除去除内容过短的样本少于4个汉字的评论通常信息量不足直接丢弃第3点要特别说明为什么数字要替换成占位符因为疫情通报文本里新增12例新增34例如果保留原始数字模型会把这些数值当成不同的特征分词后特征空间迅速膨胀但语义上它们其实是同一类表达。替换成数字这个词后特征能更好泛化。3. 文本向量化的两条路线TF-IDF与Word2Vec先别急着追新3.1 分词是中文文本向量化的第一道关卡中文处理绕不开分词。我用的分词工具是jieba模式选精确模式。不过直接拿默认词典分疫情语料会出问题——方舱医院会被切分成方舱/医院核酸检测会被切分成核酸/检测。语义差别不大但作为特征就不够聚合。解决办法是维护一个领域自定义词典把疫情高频词加入进去方舱医院、核酸检测、流调、密接、次密接、无症状感染者、健康码、行程卡、封控区、管控区、防范区、抗原检测。加完之后jieba在精确模式下也能把这些词切成一个完整token。分词之后可以顺手做词性过滤但我不建议一刀切把所有动词形容词以外的词都删掉。因为医院口罩这类名词恰恰是判断话题的关键特征。词性过滤更适合做关键词提取不太适合直接用在这类任务的特征工程里。3.2 TF-IDF参数调优少看默认值多跑几组对比TF-IDF的直觉理解是一个词在文档里出现越多越重要但如果所有文档都高频出现说明它没什么区分度需要降权。这个思路在舆情短文本上效果一直很稳。我用TfidfVectorizer时重点调这几个参数参数我的配置调整逻辑max_features50000控制特征维度防止稀疏矩阵过大ngram_range(1,2)加入二元词组捕捉不要不行太难了这类否定和程度表达min_df2只在1篇文档出现的词通常是噪声直接丢弃sublinear_tfTrue用1log(tf)平滑词频削弱高频词的绝对优势token_pattern按需要调整保留中文和英文单词去掉纯标点这里最值得说的就是ngram_range设为(1,2)。一开始我用默认的(1,1)只看单个词结果不要出门和要出门在特征上几乎是镜像的不要和要是两个不同词模型很难学到不要X整体是否定倾向。加上二元词组后模型能直接使用不要这个组合特征负面类别的F1明显提升。3.3 Word2Vec的适用边界别被语义两个字迷惑Word2Vec能学习词的分布式表示这是事实。但在情绪识别这种任务里它有一个天然短板句子向量化时语义信息会被平均池化稀释。比如一条评论物资配送太慢了怒里面怒是强情绪词但全句平均后这个词的强度被其他中性词冲淡了。TF-IDF相反它看重的是词在文档中的权重强情绪词往往权重更高不会被平白稀释。不过Word2Vec也不是没有用处。我的项目中它被用在一个辅助场景扩充同义表达。比如训练好的词向量里口罩的近邻词有防护服护目镜消毒液这些词在语义上相关。这种语义相似性用来做检索扩展、做词典构建都有用直接作为分类特征时性价比不高。实操上如果非要用词向量做分类我会优先考虑加权的词向量平均用TF-IDF权重做池化权重而不是简单平均。实测比简单平均好一些但整体依然不如直接上TF-IDF特征来得快、稳、可解释。3.4 两条路线的取舍结论表格对比对比维度TF-IDFWord2Vec平均池化训练速度快几秒到几十秒需要先训练词向量慢一个量级可解释性高能直接看到每类的高权重词低向量层面难解释短文本表现好关键情绪词权重集中一般平均池化稀释强度特征稀疏度高维稀疏低维稠密对生词的处理直接忽略可以近似表示但也可能引入偏差我在迭代过程中基本都是TF-IDF打底先用它把逻辑回归调通再看要不要引入词向量或其他特征。一个模型如果能靠TF-IDF跑出不错的F1再上深度学习才有个比较基准一上来就用复杂特征的团队常常搞不清涨点到底涨在哪。4. 五款基线模型的搭建顺序与实测要点4.1 为什么要先搭五个模型当基线很多人一上来就想上BERT、RoBERTa但这类大模型在这个项目里的问题是标注数据量不够微调容易过拟合推理部署成本高解释性弱舆情场景下用户业务方经常问为什么这条被判成负面。传统模型加TF-IDF方案跑个全流程只需要几分钟还能输出每个类别的关键特征词业务方看得懂信任度自然就上来了。所以我定了个规矩先上传统模型再谈深度学习。具体顺序是逻辑回归、朴素贝叶斯、线性SVM、随机森林、然后才轮到TextCNN和BiLSTM。4.2 逻辑回归最值得优先调的模型逻辑回归在这个任务里被严重低估。它训练快、显存占用低、有概率输出、系数可解释而且在高维稀疏特征上效果稳定。我的参数配置如下from sklearn.linear_model import LogisticRegression from sklearn.pipeline import Pipeline from sklearn.feature_extraction.text import TfidfVectorizer model_lr Pipeline([ (tfidf, TfidfVectorizer( max_features50000, ngram_range(1, 2), min_df2, sublinear_tfTrue )), (clf, LogisticRegression(C1.0, max_iter200, solverliblinear)) ])重点说C值。C是正则化强度的倒数C越小正则越强。疫情评论数据里有些词出现次数少但情绪极强如果C设太大模型容易记住这些词C设太小又会把区分信息平滑掉。我一般用验证集做log空间上的网格搜索C从0.1到10之间取几个值看F1的变化。还有一点类别权重class_weight要设成balanced。真实舆情评论里负面通常占四成到五成正面偏少设置了balanced能避免模型偏向多数类。4.3 朴素贝叶斯与线性SVM两个风格不同的对手朴素贝叶斯我用的是MultinomialNB它建立在词频的先验概率之上实现简单、参数少。文本分类里它表现好的原因是特征独立性假设在稀疏高维下反而显得挺能打。注意平滑参数alpha我设成0.3到0.5之间比默认的1.0稍微降一点对稀有情绪词的处理更友好。线性SVM我用LinearSVC它本质是带正则的合页损失优化在高维稀疏特征上通常比非线性SVM好使且快得多。用LinearSVC时要关掉内置的intercept_scaling默认值带来的影响并设定max_iter足够大我遇到过默认迭代次数不够导致不收敛的情况设成4000后问题消失。从效果上看在TF-IDF特征下逻辑回归和线性SVM的F1往往非常接近甚至难分高下。这种情况下我不会两个都部署而是选逻辑回归——因为它的概率输出更标定后续做置信度阈值过滤时更好用。4.4 随机森林进来了但要控制预期随机森林在文本分类里常常不如线性模型因为它对高维稀疏特征不敏感树模型天然偏好少量强特征但TF-IDF的特征重要性是分散的。我加它只是为了做一个对照告诉大家不是集成就一定更强。如果随机森林某次在你的数据上表现得特别好反而要怀疑特征里混入了泄漏信息。4.5 深度学习TextCNN和BiLSTM的取舍等到传统模型都跑完我才开始上深度学习。短文本舆情分类我的首选是TextCNN而不是LSTM或BiLSTM。原因很简单舆情评论大多不超过50个字情绪的表达主要靠局部的关键词组合太慢了简直无语加油这些本质上是n-gram模式的识别CNN的卷积核天然适合抓这种局部特征。LSTM擅长的是长距离依赖建模但一条20字的评论里需要跨越很远的依赖关系并不多反而训练更慢、更容易过拟合。TextCNN的常见配置embedding维度100到200卷积核尺寸取3/4/5每个尺寸64到128个卷积核池化用全局最大池化全连接层加dropoutdropout率0.5。训练轮数设10轮左右用Adam优化器学习率1e-3并配合早停。如果要用BiLSTM我会只在文本长度更长比如用户留言长文的场景下才推荐。短评上BiLSTM相对TextCNN没有明显优势参数反而多不少。4.6 数据集划分stratify不是可选项数据划分我坚持训练集:验证集:测试集 8:1:1并且都用stratify按类别比例分层抽样。这里有个容易被忽略的点同一个用户的多条评论要放进同一个集合不能因为它恰好被分到训练集和测试集导致模型作弊。我在处理时按用户ID做分组再在组级别上做划分。验证集用来做模型选择和调参测试集整个过程只能碰一次。这个纪律很重要很多新手反复用测试集调参最后报出来的指标全是乐观偏差。5. 疫情文本特有的清洗陷阱这些坑不说你可能要踩好几遍5.1 网络用语与疫情黑话疫情评论里有大量特殊表达它们在分词和特征提取时都会出问题。比如囤货表达焦虑和恐慌阴转阳这种医学表达被网友用来描述状态变化绿码红码是客观名词但带情绪的比例很高躺平破防绝绝子拴Q等网络热词语义随上下文变化极大这些词的语义在疫情前后会发生迁移。我的做法是建了一张疫情情绪词典把这些带有明确情绪倾向的词或短语标记为正面、负面或中性作为额外特征拼接到TF-IDF特征后面。这个操作虽然简单但对负面类别的召回提升明显。5.2 emoji和颜文字别一股脑删掉很多清洗教程都说去掉表情符号但在情绪识别任务里这是错误操作。比文字我很难过的情绪强度更高直接删掉等于丢掉高价值信号。我的处理方式是把emoji映射为文本标记比如 - emoji_cry - emoji_mask - emoji_thumb_up映射好的标记作为普通token参与TF-IDF特征构建。颜文字同理把:(、:)这类转成text_negative_smiley、text_positive_smiley。这样模型能学到带哭脸的评论大概率负面这种强规则。5.3 否定词与转折词的组合陷阱中文的否定表达比较麻烦。比如不是不担心是不够恐慌这种句子拿二元词组处理也不一定能抓住。我的处理是基于规则加一个额外的sentiment flipping判断检测到不情绪词时把情绪词的反向倾向特征加进去。不过要牢记规则永远是辅助不是主体。这类句子的占比本身不高把大量精力投入去抠句式性价比很低。我在这部分的投入控制在两天以内能涨0.5个点就收手。5.4 格式痕迹与数据泄漏微博数据里最常见的格式痕迹是转发微博四个字。因为抓的很多评论是转发的原始文本会带上这个标记。它会成为一个超级强的特征——模型只要见到转发微博就猜中性准确率还不低但如果后续部署时新数据没有这个标记模型性能就会当场跳水。我在清洗时一律把转发微博替换为空并检查其他可能的平台格式痕迹比如#话题#里的内容会保留但话题标记本身不作为特征。5.5 数据增强在这类任务上的真实效果我还试过同义词替换、随机删除等文本增强方式。效果很遗憾提升幅度约等于噪声。原因在于情绪识别的标签和个别关键词强相关你把口罩替换成面罩语义虽然接近但在真实疫情评论语境里分布完全不同。强行增强反而引入标注噪声。所以在这类短文本分类任务上我更推荐用反复清洗补充同义特征来提升而不是花里胡哨的增强。6. 评估体系怎么搭准确率会骗人F1和错误样本不会6.1 准确率在这个任务里是最没用的指标如果测试集里中性样本占40%负面占40%正面占20%一个全预测中性的傻瓜模型准确率都有40%。放到某些更倾斜的数据分布下准确率甚至能到60%以上但完全没法用。所以我的评估指标核心是一张混淆矩阵加各类别的precision、recall、F1。舆情分析里业务方最关心的是负面言论有没有被漏掉这对应负面类别的recall同时也不希望把中性评论大量误判成负面这对应负面的precision。最终我以macro-F1作为模型横向对比的主要指标同时单独盯负面类别的F1作为辅助指标。6.2 分时间段评估比全局指标更有说服力疫情评论的情绪分布随时间变化一个只在1月数据上表现好的模型放到4月可能就不灵。为了避免这种情况我在评估集里额外按时间切片做报告分别计算第1-2周、第3-4周、第5-6周等各时间段的指标。这样做有一个额外收益能定位模型衰退是发生在语料风格变化时还是标签分布变化时对后续数据补充策略有直接指导意义。比如实测中我们发现模型对2月中旬物资类投诉的负面召回下降后来定位是出现了一批新的表达方式没进过训练集于是定向补充了该时段的语料再次训练指标就恢复回来了。6.3 置信度阈值与人工复核通道所有模型最后都要输出概率而不是只给标签。我用逻辑回归时会根据验证集找出一个最优置信度阈值。比如预测负面的概率超过0.65才定为负面概率在0.4到0.65之间的样本标记为待人工复核。这套机制在舆情场景非常实用。负面预警宁可多筛一些让业务方人工看也不要漏掉真负面。我在项目里把待复核样本单独存下来每两天抽一次让运营人员快速过一遍既保证了数据质量又给后续训练集积累了新标注样本。6.4 错题本驱动迭代最容易被忽视的高效路径模型迭代我不看花里胡哨的训练曲线我看的是错误样本清单。每次模型调完从测试集里抽100条错误样本逐条去看为什么错是不是标注就错了数据问题是不是分词把关键词切碎了特征问题是不是这个表达在训练集里根本没出现过数据覆盖问题是不是规则把某类词误伤了清洗问题这个错题本机制比任何调参技巧都管用。在疫情情绪识别项目里我发现大量负面被漏判的样本集中在反讽和欲扬先抑两种表达上比如好的呢反正也没人管。这种句式用TF-IDF和TextCNN都很难搞定后来配合外部知识特征才提升了一部分召回。但这部分属于后期优化了不是第一版系统的目标。我在实际跑这个项目时体会最深的一点是别急着追求模型结构新颖先把数据、特征、评估这三件事做扎实。文本分类任务做到最后限制你的往往不是你选了什么网络而是你泄露了多少信息、漏洗了多少噪声、忽略了多少少数类表达。这套流程跑完你手上会有一份能解释、可复现、可扩展的基线系统这才是继续往深度学习方向深挖的底气。