
简介二零一八年CCF-BDCI汽车行业用户观点主题及情感识别挑战赛第七名解决方案是一份基于Python语言的完整竞赛代码包面向机器学习与自然语言处理方向的学习者、科研人员和竞赛选手旨在解决汽车用户评论的主题抽取与情感分类难题。压缩包共三十九个文件以三十一个Python脚本和两个Jupyter分析笔记为主体另含两个文本说明文件和模型示意图片整体仅一点一二MB目录按数据、源码、模型等模块划分结构清晰。方案完整覆盖了从原始评论数据清洗、停用词过滤、词形还原到词频逆文档频率特征构建、多种分类模型训练与交叉验证再到结果融合输出的全流程并提供了堆叠模型融合和ELMo语义表示等进阶实现可直接参考或迁移到其他文本分类任务。资源目前已有九百六十四人学习下载是理解和复现竞赛级自然语言处理解决方案的优质范例适合想深入掌握Python文本分析与模型调优的开发者。 CCF BDCI 2018年汽车行业用户观点主题及情感识别赛题到现在回头看依然是一个非常适合练手的中文NLP文本分类任务。它的任务形态很纯粹给定汽车垂直媒体上的用户评论原文先识别出评论讨论的是哪个/哪些主题动力、空间、油耗、操控等再判断整句话的情感倾向。放到今天这套流程可以直接迁移到电商评价分析、舆情监控、用户反馈自动打标等工业场景里。当时我以微弱差距拿到第7名复盘的时候发现真正拉开差距的地方远不是模型结构而是对数据的理解深度和很多容易忽略的工程细节。记忆最深的一点主题分类和情感识别这两个任务放在一起做联合建模比分头训练再合并预测效果明显更好。这种多任务共享表征的收益很多参赛队其实没有吃到。这篇文章我会把从数据分析到模型融合的完整决策链路写清楚包括哪些尝试有效、哪些无效以及为什么。1. 赛题拆解这个比赛到底在解决什么问题1.1 任务定义与业务背景这个比赛的核心应用场景是汽车口碑分析。用户在汽车垂直社区发一条评论比如空间很大后排坐三个人不挤但隔音太差了高速上风噪明显这条文本里同时涉及空间和隔音/舒适两个主题而且对不同主题的情感倾向还不一样。官方把任务拆成两个子任务主题识别判断一条评论涉及哪些预定义主题属于多标签分类问题情感识别判断这条评论整体情感极性通常是正、负、中三类为什么这两个任务值得放在一起做从业务角度很好理解光知道用户谈论动力还不够厂商和产品经理真正想知道的是用户对动力是满意还是抱怨。主题是坐标轴情感是该坐标轴上的值。两个任务共享同一个底层文本表征天然适合多任务学习。1.2 数据形态与评价指标训练集大约是两万多条汽车评论每条评论是一句或几句话带着主题标签和情感标签。主题一共十几个常见的有动力、空间、油耗、操控、内饰、外观、配置、舒适性、安全性等。情感标签则是三分类。评测指标以准确率Accuracy为主主题部分考察多标签命中情况情感部分直接比对预测类别。这个指标设置意味着模型宁可保守不能乱猜。我做完数据分布统计之后发现这两个任务都存在不低的无观点比例也就是一条评论可能只是客观陈述不表达明确情感。如果模型强行在情感维度做三选一误判率会很高。当时在方案设计前我先明确了几个底层判断中文短文本强口语化标点乱用网络用语多主题之间不是互斥关系一条评论命中两个以上主题很正常情感判断高度依赖评价对象先知道用户在谈论什么才知道他这句话是夸还是骂这三个判断基本决定了后面所有技术选型的方向。1.3 数据的脏是名副其实的训练集里大量评论来自真实车主语言形态千奇百怪。有的评论通篇都是错别字油耗底发冻机声音大有的夹杂数字和英文高速120码1.5T动力还行有的用口语词指代专用概念大屏天窗。如果不做清洗直接分词喂给模型词表会被噪声撑得很零散低频词和真正有区分度的领域词会混在一起。我自己统计过清洗前和清洗后训练集字符级去重之后的有效词表能差出将近15%的量。这些差异不会让模型从不可用变成可用但绝对会吃掉两到三个点的准确率。在名次咬得很紧的比赛里这点分数就是前排和中间的差距。2. 数据探查决定成绩下限的关键一步2.1 标签分布与类别不平衡我第一件事不是搭模型而是把标签分布彻底摸了一遍。主题标签的分布非常不均衡头部主题比如动力、空间加起来能占到一半以上尾部主题可能只有几百条样本。这带来一个很直接的问题深度模型在尾部主题上几乎学不充分预测时系统性地偏向头部主题。情感标签也有类似问题正面评论远多于负面和中性。负面样本少不是因为用户不爱抱怨而是因为很多抱怨是隐藏在整体正面评论里的。比如车不错就是油耗有点高这句话主题是油耗情感是正面中带负面标签确实很难打。我当时的处理思路分两层模型层面不直接用原始Accuracy作为唯一优化目标改用带权重的损失函数策略层面对尾部主题做类别保留避免在预处理阶段误删有效样本主题的类别不平衡不要急着用复杂方法先看测试集分布是不是同构的。是的话权重调整就有用不是的话过采样会加剧过拟合。这个判断直接影响后续所有实验。2.2 文本长度与信息密度汽车评论的文本普遍较短平均长度在40到80个字符之间但长短差异很大。短的只有几个字省油好看长的能凑成一段小作文。短文本信息密度低分词之后有效特征更稀薄这是TextCNN这类局部n-gram模型最容易吃亏的地方。我做了个简单实验把训练集按文本长度分成五档分别统计模型在各档上的准确率差异。结果非常有意思——中等长度的评论准确率最高极短的评论和超长的评论准确率都往下掉。极短评论的问题是特征不足超长评论的问题是冗余信息干扰了情感判断。这个结果直接导致我在后续方案里给两种极端长度文本分别定制了处理路径短文本做词表扩充长文本做关键句截断。经验是预处理不该是统一流水线而应该按照数据形态分桶处理。2.3 第一次建模前的预处理策略预处理我没有做很重的操作核心就三件事统一全半角符号清理无意义字符用正则表达式压缩重复标点和空格基于汽车领域词典增强jieba分词效果汽车领域的专有名词比如涡轮增压自适应巡航全景天窗默认分词器很容易切碎切碎之后词向量的语义表征就不够完整。我额外爬取了一批汽车垂直网站的语料来扩充词表让分词结果更贴合领域表达。这一步看起来简单但后续所有模型效果的上限都建立在这上面。另外一个关键点是不要轻易过滤所谓的停用词。在一般任务里的了确实没什么信息量但在短文本里了常常是判断时态和情感强度的线索。比如动力太弱了和动力太弱的情感浓度差别很大。我一律保留这些词让模型自己去学习它们的权重。3. 模型选型与迭代从Baseline到单模型最优3.1 Baseline的选择与说服力我的Baseline并不是神经网络而是TF-IDF 线性分类器。原因很实际这个方案可以在十分钟内跑完一版结果快速验证数据预处理和标签定义是否合理。深度模型跑一轮要几十分钟甚至更久如果数据层面有严重问题浪费的时间不值得。TF-IDF 逻辑回归的结果是主题分类Accuracy大概在68%左右情感识别大概在70%左右。这个数字作为起点不算差但明显还有很大提升空间。我拿这个结果做了两件事第一逐条看预测错误的bad case确认错误类型是标签噪声还是特征缺失第二作为后续所有深度模型的对比基线任何新模型如果提不升超过两个点说明问题出在数据表达上而不是模型结构上。3.2 深度学习模型的中期形态Baseline之后我把方案切到了Word2vec 深度模型的路线。词向量用了两份语料做训练一份是通用的中文维基百科语料另一份是汽车垂直网站的爬取语料两份按比例混合后训练了300维的Word2vec向量。模型结构方面我试了TextCNN和BiLSTM两种主流方案最后单模型效果最好的是TextCNN和BiLSTM的集成版本TextCNN多尺度卷积核2/3/4每类核128个池化后接全连接层BiLSTM隐层维度128后接Attention机制两个模型在主题和情感两个任务上分别共享底层EmbeddingTextCNN的优势在于它能抓住局部语序的强特征比如太肉了很难开顶腿这种固定搭配。BiLSTM的优势在于能建模更长的依赖关系比如虽然动力不错但油耗太高这种转折只看局部看不出来必须结合上下文。这两个模型的预测结果做概率平均融合后单模型准确率能到72%到73%左右。到这里整个方案已经比Baseline有了约5个点的提升但还不能进前排。3.3 多任务学习的引入逻辑我真正拍板把主题和情感放到一个模型里联合训练是在看了bad case之后。有一类错误特别典型模型把指向具体的评价对象给忽略了。比如中控台按钮松垮模型在情感上误判成中性但从人的理解来看按钮松垮显然带着负面的评价。模型之所以误判是因为它没有先识别出中控台这个主题没能把松垮和主题关联起来。多任务架构可以让主题识别的中间表征参与情感判断。具体做法是共享Embedding和共享底层Encoder主题分类和情感分类各自接一个独立的输出头。主题输出头的信息通过共享层指导情感输出头的特征提取方向。训练时两个损失相加回传主题任务的梯度可以让Encoder学到更多与评价对象相关的语义特征。这个改动带来的提升是实实在在的情感识别的准确率大约提高了1.5个百分点主题分类也有小幅提升。一个隐形的收益是训练效率更高了——一个模型解决两个任务推理成本比两个独立模型更低。4. 提分细节与融合策略第7名是怎么挤进前排的4.1 五折交叉验证与OOF预测数据量不大所以从第一版实验开始我就在用五折交叉验证。每一折训练四个模型TextCNN、BiLSTM的不同初始化用OOFOut-Of-Fold预测结果做验证集上的对比和融合权重学习。OOF预测有个好处它能提供模型在没见过的数据上是如何犯错的这个信息比单纯在验证集上看一个整体的Accuracy数字更能暴露问题。我通过OOF预测进一步确认了主题之间的混淆模式比如空间和舒适性经常互相误判因为很多评论说后排舒服时同时映射了这两个主题。交叉验证还有一个容易被忽视的作用它天然是一个Bagging的过程。不同折上的模型分布略有差异把所有折的预测概率叠加求平均之后方差被压低了最后测试集上的稳定性会好很多。到后期融合阶段五折OOF预测就是所有策略的输入基础。4.2 伪标签被验证有效的提升手段在比赛任务里伪标签是我用得最谨慎也收益最稳定的技巧之一。思路是先把测试集用现有模型预测一遍然后挑出置信度最高的样本连同预测标签一起加入训练集再迭代一轮训练。但伪标签不是无脑用。我的操作方式是这样的先用第一轮模型对测试集做预测根据预测概率筛选出情感维度上置信度超过0.85的样本主题维度上多标签命中的样本同样按高置信度筛选把这些样本和原始训练集合并重新训练模型这个操作带来了大约0.5个百分点的提升。为什么有效核心原因是测试集和训练集来自同一批数据源高置信度预测样本相当于把训练集的整体分布向外扩展了一圈。但它的风险在于如果模型一开始有系统性偏差伪标签会放大这种偏差。所以我在实验里做了一道保险伪标签样本在损失中的权重是原始样本的一半让标注数据继续主导训练方向。4.3 模型融合平均之外还要看相关性我在融合阶段试过几种策略简单概率平均、加权平均、Stacking和基于排序的融合。结果最稳定、提升最明显的是概率平均和加权平均的组合。为什么不用Stacking因为Stacking需要额外的验证集来训练元模型本来数据量就不大再切一层出来收益得不偿失。而概率平均对不同模型之间的相关性反而有天然的容忍度。我做融合时特别注意一个指标两个模型之间的错误重叠率。如果两个模型在同一批样本上犯的错高度一致那融合几乎没有增益只有模型之间的错误互补融合才有意义。TextCNN和BiLSTM在错误模式上确实互补明显——TextCNN更擅长短文本强特征BiLSTM更擅长长文本语义推断。最终方案是每个单模型在五折上分别预测测试集概率按折平均得到每个模型的OOF概率和测试概率TextCNN和BiLSTM概率按经验权重0.5比0.5平均主题部分做多标签阈值选择情感部分取最高概率类别融合后的最终成绩比最优单模型又上涨了约1至1.2个百分点也是这次进入前7的关键一步。4.4 分词与词向量的最终形态分词器最终选择了结巴分词加载自定义汽车领域词典。领域词典这个细节看起来不起眼但直接影响词向量的质量。词向量训练语料混入了汽车垂直网站的车型口碑文章让油耗胎噪推背感顿挫这些领域词的向量表征明显比通用语料训练的更接近真实语义关系。有一个我当时反复验证的观察领域词向量对评论里品牌词的处理非常关键。比如宝马奥迪奔驰这类品牌词在通用语料里往往带上公司的商业新闻语境但在汽车评论里它更接近用户在表达对某个车型的偏好。如果不做领域语料微调品牌词附近的向量邻居会偏向企业发布这类高层语义和操控内饰完全不搭。这一点在分类模型里会引入不少噪声。5. 复盘与可迁移经验比赛之外还留了什么5.1 有效尝试与无效尝试清单每次比赛复盘我都会把做过的事情分成有效和无效两类这次也不例外。有效文本分桶处理短、中、长分别优化汽车领域词表和领域词向量多任务共享表征主题情感伪标签降权策略五折OOF概率平均融合无效尝试用RNN代替CNN处理短文本效果没有本质提升尝试在Embedding层拼接字级别的Bigram特征工程复杂度高收益不明显尝试用LightGBM做Stacking元模型带来过拟合平均下来不如概率平均最典型的无效尝试是字级别模型的尝试。当时我把所有输入换成单个汉字序列想彻底规避分词误差结果准确率掉了两三个点。原因很简单汽车领域的很多评价词是固定短语比如推背感胎噪字级别无法有效建模这种固定搭配向量表征被切得太碎。5.2 从比赛到业务的转化视角这个赛题的形态放到今天的业务场景里依然高度适用。很多电商平台、外卖平台、内容社区都在做用户评论的自动结构化分析核心逻辑一模一样先识别用户讨论的对象主题维度再判断用户态度情感维度。比赛里学到的多任务架构在生产环境里意味着更少的部署资源和更快的推理速度对实时打分场景是很实际的收益。伪标签的思路也可以迁移到冷启动场景。如果业务方有大量无标注文本先用离线小样本模型预测高置信度样本再交给人工抽检能大幅降低标注成本。当然生产环境里要更严格地加上置信度阈值和抽检比例。5.3 给后来者的建议这个比赛如果放到今天BERT系列模型肯定会被大量使用但我个人认为把预训练模型直接套上去并不等于万事大吉。数据清洗、标签关系建模、多任务架构设计这些底层工作的价值并不会因为模型更強而消失。一个直接可复用的经验是先把数据理解透把单一模型调到底再考虑模型结构升级和融合。顺序反了后面每一步都会踩在前面错误数据的坑里。还有一个很实在的建议比赛过程中一定要把每次实验的配置、结果、bad case截图记录清楚。这个比赛里很多微小的提升点比如某类评论在某种预处理下准确率掉了1个点如果不记录重现实验的成本会翻好几倍。记录本身就是一种隐形的生产力。最后说一个我自己体会最深的事情这个比赛让我认识到所谓的第7名从来不是靠某一个惊艳的想法拿到的而是靠把每一个环节都往前走一小步积累出来的。这就是竞赛复盘真正的价值所在。本文还有配套的精品资源点击获取