ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

中文情绪分析实战:用大连理工情感词汇本体库做Python基线

中文情绪分析实战:用大连理工情感词汇本体库做Python基线 做中文文本情绪分析我建议你先别急着上大型语言模型把大连理工大学发布的中文情感词汇本体库用熟很多问题反而简单。这个本体库本质上是一份带情绪标注的中文情感词典但它不只是“正面词/负面词”的简单列表而是给每个词标了7大类情感、强度、极性甚至还有辅助情感信息。拿它做情绪分析好处是结果可解释、运行快、不依赖标注数据适合做基线、快速验证和各类冷启动场景。这篇文章我会从本体库的结构讲起给出完整的Python情绪分析代码再把实际项目里踩过的坑一并列出来。无论你是刚接触NLP的学生还是在业务里做评论分析、日志预警的工程师都可以直接照着做。1. 这个本体库到底是什么1.1 一句话定位中文情感词的“新华字典”大连理工大学这个本体库全称一般叫“大连理工大学中文情感词汇本体库”由大连理工大学信息检索实验室整理发布。它把大量中文词汇按照情绪语义归类并标注了词性、情感类别、强度、极性等字段。常见整理版的词汇量在两万七千词左右覆盖普通情感词、成语、网络用语和少量敬语具体数量以你实际下载到的版本为准。我第一次接触它的时候第一反应是“这不就是一份情感词典吗”。但仔细看完字段之后发现它和那种只有“正面/负面”两列的词典完全不是一回事。它给每个词标注的信息非常细比如一个词属于“乐”“好”“怒”“哀”“惧”“恶”“惊”哪一类强度是多少极性是褒还是贬甚至一个词可能同时触发第二情绪。这种信息密度已经不只是“词典”而是接近一个情绪知识库。用它来做中文文本的情绪分析相当于手头有了一本“情感新华字典”。1.2 为什么做情绪分析要先吃透词级资源现在很多教程一上来就教BERT、微调大模型好像不做深度学习就不配叫情绪分析。但我个人的体会是词级资源永远是地基。你不管最后用规则方法、传统机器学习还是深度模型情绪最终都要落到“哪些词表达了什么情绪”这个最小单元上。基于词典的情绪分析方法核心逻辑就是扫描文本里的词遇到情感词就累加情绪值最后汇总成正负面得分或情绪类别分布。这个方法看起来朴素但它有三个大优势第一是结果完全可解释“这句话负面分高是因为里面有‘糟糕’‘敷衍’‘失望’这些词”第二是运行开销极低分词加查表毫秒级完成不需要GPU第三是不依赖标注数据下载词典就能开工特别适合冷启动。即便后续要上模型词级资源也不会白费。你可以把词典分数作为特征喂给分类器也可以用词典给无标注语料打伪标签再去微调深度模型。所以我的习惯是任何情绪分析任务先拿这套词典跑一版基线分数再决定要不要上重武器。1.3 一份典型的词典文件长什么样你从网上搜索“大连理工情感词汇本体库”下载到的文件通常是Excel格式常见文件名叫“大连理工大学情感词汇本体库.xlsx”之类。打开后是表格不是文本文件这一点经常有人第一次没反应过来。表格一般有10列常用字段名是词语、词性种类、词义数、词义序号、情感分类、强度、极性、辅助情感分类、辅助情感强度、辅助情感极性。不同整理版可能字段名稍有差异比如有的叫“情感强度”而不是“强度”但含义一样。我随便挑两个词做示意重点是让你看懂每一列在说什么具体数值以你下载到的文件为准词语词性种类词义数词义序号情感分类强度极性辅助情感分类辅助情感强度辅助情感极性高兴adj21PA51PE31怒骂verb11NA72空空空“高兴”这个例子说明一个词可以有多条记录词义数2表示有两个义项词义序号1表示当前这条记录是第一个义项。辅助情感字段非空说明这个词除了主情绪“快乐”还带了一点“安心”的感觉。实际操作时如果只是做简单打点只看主情感字段就够了。2. 字段拆解与情绪体系2.1 7大类怎么记本体库把情绪分成7个大类每个大类又向下细分成若干小类。7个大类是乐、好、怒、哀、惧、恶、惊。这个分类和日常说“喜怒哀乐悲恐惊”有点接近但更工程化每个小类还有独立编码比如“PA”代表“快乐”“PE”代表“安心”。常见的小类编码和含义整理如下这个映射表在后面做代码的时候会直接用到大类小类代码小类含义乐PA快乐乐PE安心好PD尊敬好PH赞扬好PG相信好PB喜爱好PK祝愿怒NA愤怒哀NB悲伤哀NJ失望哀NH内疚哀PF思哀NI忧伤惧NC慌惧NG恐惧惧NE羞恶ND烦闷恶NN憎恶恶NT贬责恶NM妒忌恶NS怀疑惊NF惊奇这里有一些版本差异有的整理口径说21个小类有的列出来是22个主要区别是“哀”下面的“思”和“惧”下面的“羞”是否被单独算作小类。我做项目时不太纠结这个数字因为代码里只要拿着小类编码往上映射到大类得到“乐、好、怒、哀、惧、恶、惊”7个维度的分数就够了。2.2 强度和极性的量化强度字段通常取1、3、5、7、9数字越大情绪越强烈。“略有些不满”和“愤怒到极点”显然不能按同一个权重处理。实际操作里我会把强度除以9归一化到0到1之间这样最后算出来的正负面得分可以直观理解为“文本里情绪表达的相对强度”。极性字段是0、1、2、3四类0代表中性1代表褒义2代表贬义3代表兼褒兼贬。注意“中性”不代表没有情绪它只是说这个词本身不天然带褒贬倾向需要结合上下文判断。而极性3的词比较特殊比如某些词在不同语境下既能表达正面也能表达负面这类词依赖上下文非常强单纯用词典打分容易误判。2.3 辅助情感字段的妙用辅助情感分类、辅助情感强度、辅助情感极性这三列是本体库比普通词典“高级”的地方。它想表达的是一个词可以同时承载两套情感信息。比如“悲喜交加”主情绪可能是“哀”但辅助情绪可能是“乐”两个情感同时存在。代码层面做简单版本时可以直接无视辅助字段只看主情感。但如果要做细粒度情绪分析辅助字段可以这样用主情感的强度占70%权重辅助情感的强度占30%权重这样既保留了主要情绪又不会丢掉次要情绪。我实际测下来对于“又爱又恨”这类词启用辅助字段比不启用要准不少。3. 环境准备与数据加载3.1 装好这几个库整个环境只需要Python 3.8以上版本加四个库pandas读Excel、openpyxl提供xlsx读取引擎、jieba做中文分词、matplotlib画情绪分布图。如果你用的是WSL Ubuntu这类Linux环境强烈建议先建虚拟环境再安装不要直接往系统Python里塞包不然日后装深度学习框架时会遇到一堆版本冲突。python3 -m venv venv source venv/bin/activate pip install pandas openpyxl jieba matplotlibpandas读Excel依赖openpyxl很多人以为装了pandas就能读xlsx结果报“Missing optional dependency openpyxl”就是因为少装了后者。如果你下载到的文件是旧版.xls格式还要额外装一个xlrd库。3.2 读取词典并建立查询结构把Excel文件放在和脚本同一个目录下读取代码非常简单import pandas as pd df pd.read_excel(大连理工大学情感词汇本体库.xlsx, engineopenpyxl) print(df.head()) print(df.columns.tolist()) print(len(df))先打印前几行看看列名是不是和你预期一致这一步别省。不同整理版的文件列名可能从“词语”变成“word”从“情感分类”变成“emotion”你直接按名字取列就会KeyError。看到实际列名之后再构建一个字典把词作为key情感信息作为value这样后面分词后查询单词的时间复杂度是O(1)几万词的数据完全没压力。emotion_words {} for _, row in df.iterrows(): word str(row[词语]).strip() if not word or word nan: continue emotion_words[word] { kind: str(row[情感分类]), intensity: int(row[强度]), polarity: int(row[极性]), } print(加载词数, len(emotion_words))构建字典时要注意两件事。第一空行和nan要跳过不然会混入无效key。第二转换成字符串后一定要strip掉空格因为Excel里某些词前后可能带不可见空格直接拿来匹配会查不到。3.3 数据加载过程中的常见坑读Excel最常遇到的问题就是列名不一致。我拿到的版本和网上下载的版本字段名基本一致但有个别整理者把“情感分类”改成了“情感类别”把“强度”改成了“情感强度”。解决办法只有一条加载后先print列名再写代码。另外一个坑是编码问题。Excel的xlsx格式内部是XMLpandas处理时一般不会乱码但如果你把词表另存成了CSV再用pandas读取很可能遇到GBK和UTF-8编码错乱。建议只操作xlsx不要中途转格式。如果必须转CSV读取时加上encodingutf-8试试乱码就换成encodinggbk。4. 情绪分析代码的实现4.1 整体分析流程基于词典做情绪分析整体流程可以用一句话概括中文分词后逐个查情感词典把命中的情感词的强度、极性、情感类别累加起来。具体展开分四步第一步用jieba对原始文本分词。中文没有空格分词必须靠分词工具把句子拆成词的序列。第二步遍历分词结果跳过不在词典里的词。第三步对命中词典的词读取它的极性和强度按照极性把强度加到正面分或负面分上同时把强度累加到对应的小类编码上。第四步把文本级别的正负面得分、7大类情绪得分输出。4.2 基础打分逻辑先写一个最朴素的版本不考虑否定词和程度副词只看词本身。这样逻辑最直白也方便理解后面的优化。import jieba def analyze_emotion_basic(text): words jieba.lcut(text) pos_score 0.0 neg_score 0.0 emotion_counter {} detail [] for w in words: if w in emotion_words: info emotion_words[w] intensity info[intensity] / 9.0 polarity info[polarity] if polarity 1: pos_score intensity elif polarity 2: neg_score intensity kind info[kind] emotion_counter[kind] emotion_counter.get(kind, 0) intensity detail.append((w, kind, polarity, intensity)) return pos_score, neg_score, emotion_counter, detail这里的得分为什么除以9因为强度范围是1到9除以9之后每个词对总分的贡献就变成了一个0到1之间的小数。这样打出来的分不是“出现了几个词”而是“情绪表达的相对强度”不同文本之间对比更公平。4.3 否定词和程度副词修正只按词查表有一个明显问题“很高兴”和“不高兴”用上面的代码算出来都是正面得分因为分词后是“很/高兴”和“不/高兴”“很”不在词典里“不”也不在词典里最后都只累计“高兴”的强度。这显然不对。解决办法是引入否定词表和程度副词表在做累加时往前看一个词。常用的否定词有不、没、无、非、未、莫、勿、别、休、甭、未曾、不曾、并非。程度副词分两类增强类比如很、非常、极其、特别、太、十分、格外减弱类比如有点、稍微、略、些许、不太。我的处理方式是如果情感词前一个词是否定词就把极性反转同时强度乘以0.7的衰减系数如果前一个词是程度副词就把强度乘上对应的系数。neg_words {不, 没, 无, 非, 未, 莫, 勿, 别, 休, 甭, 未曾, 不曾, 并非} degree_words { 很: 1.5, 非常: 1.8, 极其: 2.0, 特别: 1.8, 太: 1.7, 十分: 1.6, 格外: 1.6, 超: 1.5, 略: 0.7, 稍微: 0.7, 有点: 0.6, 有些: 0.6, 不太: 0.5 } def analyze_emotion_with_context(text): words jieba.lcut(text) pos_score 0.0 neg_score 0.0 emotion_counter {} for i, w in enumerate(words): if w not in emotion_words: continue info emotion_words[w] intensity info[intensity] / 9.0 if i 0 and words[i - 1] in degree_words: intensity * degree_words[words[i - 1]] if i 0 and words[i - 1] in neg_words: intensity * 0.7 if info[polarity] 1: neg_score intensity elif info[polarity] 2: pos_score intensity else: if info[polarity] 1: pos_score intensity elif info[polarity] 2: neg_score intensity kind info[kind] emotion_counter[kind] emotion_counter.get(kind, 0) intensity return pos_score, neg_score, emotion_counter这个版本已经能处理“很不高兴”这类组合。“不”反转极性“很”放大程度结果就是负面分增加且幅度不小。当然真正的语言远比这个复杂比如“不是很喜欢”和“不是特别喜欢”需要把否定词和程度副词的顺序组合做更细的规则但那个已经超出词典范畴了。作为基线版本这样够用。4.4 输出7大类情绪分布小类编码有二十来个直接看细节会头晕。所以我习惯先把小类映射到大类再统计输出。映射关系就用前面分类表里的对应关系。kind_to_big { PA: 乐, PE: 乐, PD: 好, PH: 好, PG: 好, PB: 好, PK: 好, NA: 怒, NB: 哀, NJ: 哀, NH: 哀, PF: 哀, NI: 哀, NC: 惧, NG: 惧, NE: 惧, ND: 恶, NN: 恶, NT: 恶, NM: 恶, NS: 恶, NF: 惊 } def aggregate_to_big(emotion_counter): result {} for kind, score in emotion_counter.items(): big kind_to_big.get(kind, kind) result[big] result.get(big, 0) score return result把小类得分合成大类得分后再做可视化。一张柱状图可以把文本的情绪画像显示出来直观程度远超一行行的数字。import matplotlib.pyplot as plt def plot_emotion_distribution(emotion_big): categories [乐, 好, 怒, 哀, 惧, 恶, 惊] values [emotion_big.get(c, 0) for c in categories] plt.figure(figsize(8, 5)) plt.bar(categories, values, color[#5B8FF9, #5AD8A6, #F6BD16, #E8684A, #6DC8EC, #9270CA, #FF9D4D]) plt.title(文本情绪分布) plt.ylabel(情绪强度得分) plt.show()4.5 完整可运行脚本把加载、分析、聚合、绘图拼在一起就是一个可以直接跑的脚本。下面这段我平时做Demo就用它复制到本地改一下Excel文件路径就能跑。import re import jieba import pandas as pd import matplotlib.pyplot as plt EXCEL_PATH 大连理工大学情感词汇本体库.xlsx df pd.read_excel(EXCEL_PATH, engineopenpyxl) print(列名, df.columns.tolist()) emotion_words {} for _, row in df.iterrows(): word str(row[词语]).strip() if not word or word nan: continue emotion_words[word] { kind: str(row[情感分类]), intensity: int(row[强度]), polarity: int(row[极性]), } neg_words {不, 没, 无, 非, 未, 莫, 勿, 别, 休, 甭, 未曾, 不曾, 并非} degree_words { 很: 1.5, 非常: 1.8, 极其: 2.0, 特别: 1.8, 太: 1.7, 十分: 1.6, 格外: 1.6, 超: 1.5, 略: 0.7, 稍微: 0.7, 有点: 0.6, 有些: 0.6, 不太: 0.5 } kind_to_big { PA: 乐, PE: 乐, PD: 好, PH: 好, PG: 好, PB: 好, PK: 好, NA: 怒, NB: 哀, NJ: 哀, NH: 哀, PF: 哀, NI: 哀, NC: 惧, NG: 惧, NE: 惧, ND: 恶, NN: 恶, NT: 恶, NM: 恶, NS: 恶, NF: 惊 } def analyze_emotion_with_context(text): words jieba.lcut(text) pos_score 0.0 neg_score 0.0 emotion_counter {} for i, w in enumerate(words): if w not in emotion_words: continue info emotion_words[w] intensity info[intensity] / 9.0 if i 0 and words[i - 1] in degree_words: intensity * degree_words[words[i - 1]] if i 0 and words[i - 1] in neg_words: intensity * 0.7 if info[polarity] 1: neg_score intensity elif info[polarity] 2: pos_score intensity else: if info[polarity] 1: pos_score intensity elif info[polarity] 2: neg_score intensity kind info[kind] emotion_counter[kind] emotion_counter.get(kind, 0) intensity return pos_score, neg_score, emotion_counter def aggregate_to_big(emotion_counter): result {} for kind, score in emotion_counter.items(): big kind_to_big.get(kind, kind) result[big] result.get(big, 0) score return result if __name__ __main__: text 产品很好发货也快但客服态度很差回答敷衍。 pos, neg, counter analyze_emotion_with_context(text) big aggregate_to_big(counter) print(文本, text) print(正面得分, round(pos, 3)) print(负面得分, round(neg, 3)) print(情绪分布, big) categories [乐, 好, 怒, 哀, 惧, 恶, 惊] values [big.get(c, 0) for c in categories] plt.bar(categories, values) plt.title(文本情绪分布) plt.ylabel(情绪强度得分) plt.show()这段代码跑出来的示例输出大致是正面得分约0.6到0.9负面得分约1.0以上负面明显压过正面情绪分布里“好”和“恶”或“哀”类得分较高。具体数值取决于你下载的版本里“好”“快”“差”“敷衍”这几个词的强度和情感分类标注不同版本有差异很正常只要趋势符合“这篇文本整体偏负面”说明流程就是对的。5. 应用场景与边界5.1 这些场景用词库打分会很爽第一个场景是电商评论粗筛。比如拿到一批“客服态度差”“物流慢”“质量垃圾”这类文本分词后命中负面词负面分就会明显拉高。你可以设一个阈值负面分超过阈值就直接进入人工复核队列。这个方法不需要任何训练数据当天就能上线。第二个场景是舆情监控。对新闻、论坛、社交媒体的文本流做实时情绪统计重点不是某一个词的准确性而是整体趋势。比如某个品牌当天新增文本的负面分突然上升系统就告警。词典方法速度快适合这种高吞吐量场景。第三个场景是弱监督打标。你要训练一个更复杂的模型但手头没有标注数据就可以先用词典给大量历史文本打上“正面”“负面”的伪标签再用这份伪标签数据去训练分类模型。这比纯人工标注省力得多很多实际项目我都是这么起步的。第四个场景是客服对话满意度分析。用户在对话结尾说的话通常能体现对本次服务的整体态度。“谢谢”“满意”“辛苦”这类词命中正面负面分低基本可以判断是满意会话。词典方法虽然粗但胜在稳定适合做业务侧的实时统计。5.2 别硬上词典的场景词典方法有明确的天花板遇到反讽基本失灵。“你真是太厉害了”在特定语境下可能是嘲讽但单词层面全是正面词词典会给很高分。这类文本必须结合上下文语义模型才能判断。新词和行业词覆盖不够也是硬伤。比如“太卷了”如果下载的版本比较旧“卷”这个词不一定有对应情绪。还有大量网络新流行语比如“破防”“上头”“yyds”都未必在表里。解决方法有两种要么定期手动扩充词表要么把词典分数作为特征再喂给更强的模型去补语义信息。跨句依赖的情况词典也处理不了。比如“产品本身不错但是客服让我等了整整四十分钟我最后决定退货。”前半句有“不错”是正面后半句没有明显情感词词典方法可能会判断整句话中性但实际语义明显是负面。这类长文本需要句子级建模词典只能作为特征输入。6. 常见问题与调试指南6.1 Excel读取报错最容易撞见的是Missing optional dependency openpyxl。原因很简单pandas读xlsx需要openpyxl作为引擎但你只装了pandas。执行一遍pip install openpyxl就能解决。如果你拿到的文件是.xls后缀老的pandas版本可能报错提示需要安装xlrd。注意xlrd的2.0以上版本只支持xls不支持xlsx所以安装的时候直接pip install xlrd就好。这里有个实际经验最高效的方案是拿到文件后用Excel或WPS把它另存为xlsx各种麻烦都少很多。还有一种情况读取成功后词数始终为0那是因为列名不匹配。有人把“词语”改成了“单词”或者把“情感分类”合并到了其他列。用print(df.columns.tolist())看一遍再按实际列名改代码。6.2 分词匹配不上的坑先看这两个词“不高兴”和“不高兴”。看起来一样但jieba可能把前者切分成“不/高兴”把后者切分成“不高兴”取决于上下文和词典。切片不同前者能匹配“高兴”后者整体可能不在情感词典里导致漏分。解决方法是主动干预分词。在你自己的情感分析脚本里加载完jieba后把本体库里所有情感词都加入用户词典让分词器优先按词表切分for word in emotion_words.keys(): jieba.add_word(word)这样能显著减少分词粒度和词典粒度不一致的问题。代价是分词结果可能过度切分成词表里的词影响精度所以要权衡。如果发现某个领域词频繁被切碎也可以单独用jieba.add_word(某个专业词)处理。6.3 打分结果明显不对时的三个排查方向如果一段明显正面的文本得了低分先看分词结果。把jieba.lcut(text)打印出来人工检查是不是把“赞不绝口”切成了“赞/不/绝口”导致词完全没进词典。如果分词没问题但分数偏低看词表覆盖。本体库毕竟是通用领域资源行业词、品牌词、专业术语几乎不会覆盖。这时需要自己扩充词典比如把“做工扎实”“手感顺滑”这两个词直接加入自己的情感词表。如果词语都没问题但总分方向反了多半是极性判断的问题。本体库的极性字段是上下文无关的静态标注碰到“无语”这类词可能被标成中性或贬义具体要去查表确认。遇到这种和语境强绑定的词建议在代码里做一次白名单覆盖按你业务里的实际语义修正。6.4 测试用例速查表调试阶段可以拿下面这些用例验证代码每一类都有明确的预期测试文本预期结果检查点产品很好发货也快正面分大于负面分基础正面词累计客服态度很差回答敷衍负面分大于正面分基础负面词累计这个价格不太合理负面分大于正面分“不太”削弱“合理”的正面程度我一点也不高兴负面分大于正面分“不”反转“高兴”极性虽然有点失望但整体能接受正负分接近正负情绪同时存在看谁占主导注意最后一条如果输出负面分被“失望”拉得特别高而“能接受”这种偏正面的词没有被识别可能需要手动把“能接受”加入词表。“能接受”在通用词典里可能算中性但在电商场景里明显偏正面这种场景差异只能靠业务词汇表弥补。7. 我踩过几次坑后的经验总结7.1 词典方法不是旧东西而是基线有一个误区是“用词典做情感分析太简单不够高级”。我实际做过几次对比后发现词典方法在很多中文文本上的表现和训练过的分类模型差距并没有想象中大。尤其当你的训练数据量很小、领域又比较垂直的时候直接用词典的结果往往比一个训练不到位的Boring模型还要稳。它永远不会过拟合也永远可解释。所以我现在的习惯是任何情绪分析任务先用这套词典跑一版分数再决定要不要上模型。这个基线分数非常值钱后续模型效果如果连它都打不过说明模型本身设计就有问题。7.2 后续扩展可以从三个方向入手第一个方向是垂直领域词典扩充。通用词表处理不了行业黑话比如金融领域的“回调”、医疗领域的“预后”需要你自己准备一个领域词表把本体库覆盖不到的词补进去。扩充时注意保持相同的字段结构加词之后整个流程不用改。第二个方向是把词典得分当特征。把正负面得分、7大类得分、命中情感词数、平均强度这些数值拼成一个特征向量丢给逻辑回归、随机森林或者XGBoost往往能在词典基础上再提升几个点。这个方法属于“手工特征传统机器学习”在小样本场景下非常实用。第三个方向是用词典打伪标签微调深度模型。给几万条无标注文本用词典打分按分数高低取正负样本训练一个文本分类模型甚至微调BERT。这个过程可以反复迭代模型训练好后再用模型预测结果反过来修正词典打分不合理的样本形成一个半自动升级循环。最后再分享一个小技巧把这份词库和行业积累结合没必要照单全收。我自己的做法是保留原始词表作为通用底表另建一份业务覆盖表放在前面优先匹配。业务词表优先级高通用词表兜底这样既保住了通用性又能让模型在具体场景里更准。这个思路比研究单个词的标注是否正确要有效得多。
返回列表