☰
情感词典+机器学习:新闻与微博评论情感分析实战
2026/10/5 2:59:40 网站建设 项目流程

简介:压缩包内含一套情感词典与机器学习相结合的新闻、微博评论情感分析完整项目。项目面向自然语言处理入门者及需要分析公众舆论的开发者,以哈工大、知网情感词典为基础进行特征识别,并实现朴素贝叶斯、支持向量机、随机森林等分类模型,覆盖从数据抓取、文本清洗、情感判定到结果评估的常见流程。资源共39个文件,规模约2.63MB,以16个Python脚本、9个Markdown说明文档、7个CSV数据文件为主,脚本用于爬虫采集与模型训练,文档梳理了研究背景和数据获取方法,CSV则提供词典及中间结果,另含数据备份和附赠内容,便于对照复现。目前已有34人学习下载。借助这套资源可快速搭建情感分析实验环境,理解情感词典构造、特征工程与模型调参的完整思路,还能借助爬虫脚本扩展至其他新闻平台,适合课程设计、毕业设计或舆情分析练习。

1. 新闻与微博评论情感分析:为什么情感词典和机器学习要一起上

假设产品经理丢给你一份需求:把新闻客户端每篇文章底部的评论,以及官方微博下面的热评,按正面、负面、中性切成三类,输出成舆情日报。你翻一圈资料会发现,所有教程都指向两个方向——要么情感词典,要么机器学习。情感词典当天就能跑,不需要一条标注数据,但微博里那些反讽口气和层出不穷的网络新词,词典基本招架不住;纯机器学习模型对上下文敏感、准确率上限高,可冷启动阶段手上连一份像样的标注数据都没有。标题把两条路线放在一起,真正的意思是让它们协作:用情感词典快速打底,用机器学习模型去补词典覆盖不到的长尾。下面顺着这个思路,把两类方法的边界、落地步骤、参数选择和坑位讲清楚,适合正在做评论舆情、品牌口碑监控,或者拿情感分析做课程设计的读者。

2. 先理清两套方法:情感词典打分与机器学习模型的边界与选型

在动手写代码之前,得先回答一个问题:为什么不能只选其中一条路线?因为新闻评论和微博评论根本是两种文本分布。新闻评论句子完整、用词书面、观点直接;微博评论短、口语化、大量使用网络新词和反讽。情感词典对前者有效,对后者很容易失效;机器学习模型对前者需要大量标注,对后者则依赖分词和特征质量。而真实项目里,两类文本往往混在一起出现,一个舆情系统不可能只处理其中一种。这个前提先立住,后面找落地方案才不会白费功夫。

2.1 情感词典方法:可解释、零标注,但被反讽和网络新词打爆

情感词典的思路很简单:维护一张带情感极性和强度权重的词表,再叠加否定词、程度副词的规则,把文本里每个情感词的分值累加起来。比如“这部电影太棒了”,命中“棒”这个正向词,再检测到“太”这个程度副词,把分数乘以1.5,得到一个较高的正分。整个过程可解释、可追溯,老板问起来你能明确指出是哪个词贡献了情绪分。

它最大的优势是冷启动快。只要词典覆盖得住,不需要一条标注数据就能跑起来,适合舆情核查这类对解释性有要求的场景。但短板也很致命:反讽。比如“真是太好了,等了两年就等到这种剧情”,词典会给出正分,因为它识别出“好”是正向词,人眼却明明白白看出这是负向吐槽。再一个就是网络新词。“绝绝子”“栓Q”只要不在词表里,分词器还可能把它们拆成“绝”“绝”“子”,导致整句情感分退化成0甚至相互抵消。词典方法在新闻评论里表现尚可,一换到微博评论,覆盖率立刻打骨折。这也是为什么对口语化语料,词典方法只配打底,不能单独扛主线。

2.2 机器学习模型强在哪:能学上下文,却败给冷启动

机器学习的思路是把情感分析当成一个文本分类问题:先对文本做特征抽取,再用分类器学习“什么样的词组合对应正向或负向”。特征层面常用的是TF-IDF加n-gram,分类器用逻辑回归或LightGBM。逻辑回归在文本场景里性价比很高,训练快、自带概率输出,方便和词典分数融合,也更容易解释特征权重。

机器学习模型的最大优势是能捕捉上下文组合。比如“不怎么样”这种带否定词的词组,只要在训练数据里出现足够多次,模型就能学到“不+正面词”的组合权重偏负。另一个优势是对新词相对宽容:网络新词只要在语料里反复出现,模型会把它吸收成一个特征,不存在词典命不中的问题。它解决的是词典方法在口语域的致命短板。

但它有两个硬伤。第一是冷启动:起步阶段没有标注数据,模型只能对着空气训练。回到本标题的语境下,最常见的做法是让词典先去打标,用词典分数生成一批带噪音的伪标签,再用伪标签做半监督训练,这就是机器学习应用流程里常说的“伪标签”路线。第二是领域漂移:新闻评论语料训出来的模型,直接扔到微博评论上准确率会掉得很明显;反过来也一样。如果两类文本混在一个模型里,经常出现“整体准确率还行,拆开一看其中一类F1很低”的假象。

2.3 结合路线怎么选:词典打底、模型补位,这是最稳的工程架构

结合思路目前业界和开源社区里最常用的是“词典出伪标签 + 模型训练 + 融合决策”三段式。第一段,先用词典给全部无标注数据打分,分数高的记为正向,分数低的记为负向,中间地带丢弃。第二段,用这部分带噪音的样本训练一个机器学习模型,模型学到的其实是词典规则的平滑泛化版,能自动把词典没覆盖到的同义表达归到对应类别。第三段,预测时词典和模型同时输出再做加权融合:词典分接近0的样本说明词典拿不准,直接交给模型决定;词典分明确的,以词典为主,模型作为参考。

为什么推荐这个架构而不是“纯规则”或“纯模型”?纯规则在新闻评论上有底线保障,但微博覆盖率不足;纯模型在标注不足时无法启动,模型本身的黑匣子特性也让舆情类项目难以解释。用表格对比两套方法在五个关键维度上的差异:

维度情感词典机器学习模型
是否需要标注数据不需要需要
可解释性高,可溯源到具体词低,基本是黑匣子
对反讽与转折基本失灵依赖特征和标注质量
对新词的容忍度词典没有就失效有词频就能吸收
冷启动速度当天就能跑通必须先攒标注

这套架构在情感分析这类文本任务里已经有相当多的实战验证,也正好对应机器学习实战里的常见项目路径。接下来就按这个顺序,把每一段的代码和参数逐一跑通。

3. 词典+机器学习落地管道:从分词、伪标签到模型训练的完整代码

整个流程分四步:预处理与词典构建、词典打分生成伪标签、伪标签训练机器学习模型、融合输出。我用 Python + jieba + sklearn 这套最常见的技术栈来写,不需要GPU,一台开发机就能跑通,这也是 python机器学习入门 阶段最容易上手的中文文本分析组合。

3.1 预处理与词典构建:jieba分词、情感词表、否定词和程度副词

先做文本预处理。新闻评论和微博评论在这一步有差异:微博文本要去掉@用户、话题标签和URL,新闻评论主要是去HTML标签和多余空白。表情符号如果数据源已经转成文本(比如“[哈哈]”),可以替换成对应的情感词占位,这样词典才能命中。

import re import jieba def preprocess_text(text, source='weibo'): text = re.sub(r'<.*?>', '', text) # 去HTML标签,新闻评论里常见 text = re.sub(r'https?://\S+', '', text) # 去URL if source == 'weibo': text = re.sub(r'#.*?#', '', text) # 去微博话题 text = re.sub(r'@[\w\u4e00-\u9fa5]+', '', text) # 去@用户 # 表情占位符替换成带情感的词,词典才能命中 text = text.replace('[哈哈]', ' 开心 ') text = text.replace('[泪]', ' 难过 ') return text.strip()

逻辑说明:代码先把两种来源的文本做共通清洗,再为微博单独去话题和@用户。“#.*?#”用非贪婪匹配去话题内容,避免把整段话都吃掉;@用户的正则里混了中英文,因为微博昵称常带中文。替换表情占位符时用的“开心”“难过”都是词典里的常见情感词,实际工程里我会维护一张表情映射表,把微博客户端常见的几十个表情都映射一遍。如果数据源是Unicode emoji,先用库转成文字再做同样映射。

接着构建情感词典和规则表。常见的公开情感词典格式是“词、词性、极性、强度”多列,解析时按逗号或Tab切分就行:

def load_senti_dict(path, sep=','): word_polarity = {} with open(path, 'r', encoding='utf-8') as f: for line in f: parts = line.strip().split(sep) if len(parts) < 3: continue word, polarity, intensity = parts[0], int(parts[2]), float(parts[3]) # polarity: 1 正向, -1 负向, 0 中性 # intensity: 0~1 之间的强度,越大情绪越强 if polarity != 0: word_polarity[word] = (polarity, intensity) return word_polarity

逻辑说明:词典解析是整套方案的第一道坎。不同词典的列顺序不一样,务必先打印前几行确认“词、极性、强度”三列的位置,否则读出来的数据全是错位。我把中性词过滤掉了,因为打分用不上,但后续做特征时它们可以留作停用词参考。

否定词表和程度副词表直接用集合和字典维护。程度副词分几档系数,高程度词给1.8,低程度给0.5,中间值按语感插进去:

neg_words = {"不", "没", "没有", "别", "莫", "无", "未曾", "难以", "不太", "并不"} degree_words = { "极其": 1.8, "非常": 1.8, "特别": 1.6, "很": 1.5, "太": 1.5, "挺": 1.3, "有点": 0.6, "稍微": 0.5, "不怎么": 0.3, "几乎不": 0.2, }

3.2 词典基线:打分规则、阈值与伪标签生成

有了词表就能写打分函数。核心逻辑是对分词后的词序列做单遍扫描:命中情感词,就往前看一个窗口(这里设3个词),窗口内出现否定词则极性翻转,出现程度副词则取最大系数放大,最后累加。

def lexicon_score(text, senti_dict, neg_words, degree_words, window=3): words = jieba.lcut(text) score = 0.0 for i, w in enumerate(words): if w not in senti_dict: continue local_neg = 1.0 for j in range(max(0, i - window), i): if words[j] in neg_words: local_neg = -1.0 break local_degree = 1.0 for j in range(max(0, i - window), i): if words[j] in degree_words: local_degree = max(local_degree, degree_words[words[j]]) polarity, intensity = senti_dict[w] score += polarity * intensity * local_degree * local_neg return score

逻辑说明:window=3 是词典打分最需要调的参数,它决定最多往回看几个词去识别“太不怎么样”这类前置修饰。窗口过小会漏掉修饰,窗口过大会把前面分句里的否定词也算进来。代码里用了两次窗口扫描,第一次找否定,第二次找程度副词,逻辑直白但稍慢,数据量大时可以合并成一次遍历。

接着生成伪标签。把词典分映射成正、负、中性三类,阈值控制伪标签的数量和纯度:

def make_pseudo_label(score, pos_th=0.5, neg_th=-0.5): if score >= pos_th: return 1 if score <= neg_th: return -1 return 0 # 中性样本在训练时丢弃 # 假设 df 是已经读进来的 DataFrame,包含 text 和 source 两列 df['lex_score'] = df['text'].apply( lambda x: lexicon_score(x, senti_dict, neg_words, degree_words)) df['pseudo_label'] = df['lex_score'].apply(make_pseudo_label) train_df = df[df['pseudo_label'] != 0].copy() train_df['label'] = (train_df['pseudo_label'] + 1) // 2 # -1->0, 1->1

逻辑说明:pos_th和neg_th是控制伪标签数量的关键参数。阈值调大,伪标签更干净但数量更少;调小,数量多但噪音大。我对新闻评论一般设±0.5,微博短文本因为词密度低会降到±0.3。到达这一步,你已经拿到一份可训练的监督信号,不用人工标一条数据。

3.3 伪标签训练机器学习模型:TF-IDF + 逻辑回归的参数细节

特征工程上,中文文本的标准做法是TF-IDF加n-gram。分词连接成空格文本,交给TfidfVectorizer。这里有个细节:tokenizer接收的是已经分好词的文本,所以先对训练集做一次分词:

stopwords = set(open('stopwords.txt', encoding='utf-8').read().split()) def tokenize_for_tfidf(text): # 去掉停用词和单字词,避免注入无区分度的特征 return ' '.join([w for w in jieba.lcut(text) if w not in stopwords and len(w.strip()) > 1]) train_df['seg_text'] = train_df['text'].apply(tokenize_for_tfidf)

逻辑说明:停用词表我一般用公开的中文停用词表,再手动加上平台特有词,比如微博评论里的“转发”“超话”这类高频噪音。过滤单字词是因为它们在短文本分类里区分度很低,留着只会拉低模型训练速度。

然后构建TF-IDF特征并训练逻辑回归:

from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report vec = TfidfVectorizer( tokenizer=lambda s: s.split(), ngram_range=(1, 2), min_df=2, max_features=10000, ) X = vec.fit_transform(train_df['seg_text']) y = train_df['label'].values X_train, X_val, y_train, y_val = train_test_split( X, y, test_size=0.2, stratify=y, random_state=42) clf = LogisticRegression(C=1.0, class_weight='balanced', max_iter=1000) clf.fit(X_train, y_train) print(classification_report(y_val, clf.predict(X_val)))

参数说明:ngram_range=(1, 2)让模型同时看到单字词和相邻双词组合,能抓到“不怎么样”这类短语;min_df=2过滤只在一条评论里出现一次的词,这些词对泛化没帮助;max_features=10000控制特征维度,防止内存爆掉和过拟合;class_weight='balanced'自动给样本量少的类别加权,处理情感类别不平衡;max_iter=1000在高维稀疏特征下是必要配置,默认的100经常在训练中途告警不收敛。

为什么用逻辑回归而不是朴素贝叶斯?朴素贝叶斯假设特征独立,在TF-IDF的高维特征下独立性很差,会把词典带来的系统性噪音放大;逻辑回归对特征共线更容忍,输出概率更平滑,融合时更好控制。这也是机器学习入门教程讲文本分类时最终都滑向逻辑回归的原因,它在中小规模情感分析任务里是最不容易翻车的默认选择。

注意:上面的分类报告是在伪标签验证集上的指标,不等于真实准确率。训练标签来自词典规则,模型边界天然继承词典偏差。要评估真实效果,必须单独留出一小批人工标注样本,做法见第5章。

3.4 融合输出:把词典分和模型概率放到同一把尺子上

模型训练好之后,预测阶段词典与模型并行跑。这里最容易犯的错是直接拿词典原始分和模型概率做加权,因为量纲不一致,加权结果会被词典分主导。我先用tanh把词典分压缩到0到1区间,再和模型概率加权:

import numpy as np def fuse_score(lex_score, model_prob, alpha=0.5, scale=3.0): # tanh把词典分压到(-1,1),再转到(0,1) lex_norm = (np.tanh(lex_score / scale) + 1) / 2 return alpha * lex_norm + (1 - alpha) * model_prob def fused_with_fallback(lex_score, model_prob, neutral_band=0.2, alpha=0.5, scale=3.0): # 词典分接近0说明词典拿不准,直接信模型 if abs(lex_score) < neutral_band: return model_prob return fuse_score(lex_score, model_prob, alpha, scale) def final_label(fused, pos_th=0.6, neg_th=0.4): if fused >= pos_th: return 1 if fused <= neg_th: return -1 return 0 # 中性

逻辑说明:scale控制词典分的压缩尺度,一般按词典打分实际范围来定;alpha是词典侧权重。对新闻评论这类词典表现好的语料,alpha给0.6到0.7;对微博评论,alpha给0.4到0.5,让模型多发力。融合后,最终的正负中性阈值要再拿人工标注微调,不能沿用伪标签阶段的阈值。

到这里,词典打分、伪标签训练、模型融合三段式已经跑通,几千条新闻评论和微博评论上做出一个可用的情感分类器完全够用。

4. 避坑:情感词典与机器学习结合最容易翻车的五个地方

代码能跑通只是第一步。实操里最常见的坑我一个个列出来,每个都按现象、原因、解决三段写清楚。这些坑位基本都在情感分析项目里踩到过,写出来给你当后悔药。

4.1 微博新词拆分把词典分打到0

现象:一句“这家店的东西绝绝子”,情感词典打分结果是0,机器学习模型因为伪标签里没见过这类表达,同样判成中性。人眼明显是正向表达,系统却完全看不到。

原因:一是“绝绝子”不在情感词典里;二是jieba默认词典会把“绝绝子”拆成“绝”“绝”“子”,而“绝”在通用词典里的极性通常是负向的,两个负向词加在一起反而互相抵消。

解决:处理微博语料前,先跑一批高频词统计,把词频Top里现有词典没覆盖的词抽出来,人工确认极性后加进jieba自定义词典和情感词典。对于网络新词,更快的做法是单独维护一张weibo_senti_extra.txt,把“绝绝子”“yyds”这类高热度表达按词面极性和当前语料的情感分布直接收录,加载方式和主词典一致。

4.2 反讽样本被词典判成正面的连环坑

现象:新闻评论“真是太好了,等了两年就看到这么个结局”,词典给+1.4分,伪标签记为正向。这条样本进入训练集后,逻辑回归把“太好了”学成强正向特征,后续类似的反讽评论全部中招。

原因:词典规则读不到语气和语境。“真是太好了”字面全是正向词,整句却是反讽;伪标签训练把这种错误系统地传给了模型,属于典型的伪标签错误自强化。

解决:反讽没有银弹,工程上一般加一条转折词规则:情感词命中后,如果句子里出现“但是”“可”“然而”这类转折词,就把该情感词的贡献压缩甚至反向。另一个有效做法是在伪标签阶段把“词典高置信但句子含转折标记”的样本单独拎出来人工批量修正,而不是直接丢进训练集。

4.3 词典分和模型概率量纲不一致,融合公式形同虚设

现象:代码写成 score = 0.6 * lex_score + 0.4 * model_prob,结果输出全部偏正或偏负。细看数据,词典分范围是-3到+3,模型概率是0到1,加权后模型那部分几乎不起作用,结果还是由词典单独说了算。

原因:两个信号不在一个量纲里,直接线性加权是错的。这也是刚接触机器学习的人做多信号融合时最常犯的失误。

解决:先把词典分做归一化。scale按语料实际分布取2到3,用tanh压到-1到1再转到0到1,然后再加权,就是第3章里fuse_score的做法。融合后必须重新划阈值,不能沿用单一信号的0.5阈值,融合概率的分布会整体偏移,阈值需要用正样本分位来标定。

4.4 伪标签噪音让模型越训越偏

现象:第一轮词典伪标签在人工抽查里准确率只有75%,模型在验证集上表现还行,上线后错误集中在一类句子上:带双重否定的长句和反讽句。把模型预测置信度高但预测错误的样本挑出来看,训练集里这类样本的标签本来就是错的。

原因:伪标签的错误是系统性的,不是随机噪声。逻辑回归擅长记住训练集里的模式,模型把词典的偏见当真理去拟合,再用偏见去预测。半监督训练在这个场景里最大的风险不是噪音量大,而是噪音方向一致。

解决:用伪标签时只取词典分绝对值大的样本,中间区域全部丢弃,不硬凑数量;伪标签样本量控制在人工标注量的3到5倍以内。每训练一轮,抽200条模型高置信度的预测结果做一次人工核对,发现某一类错误率超过20%就停一轮,修正标签后再加回来。

4.5 新闻评论和微博评论混着训练,F1互相拖累

现象:把新闻和微博评论混成一个训练集,训练出一个所谓通用模型。整体验证集F1大概0.7,一拆开看,新闻评论F1有0.78,微博评论只有0.62。单独训微博模型能到0.7以上,混合模型反而变差了。

原因:两个域的文本分布差异太大。微博的词频和句长分布与新闻评论完全不同,一个模型试图同时拟合两个峰,拟合出来的是中间地带,两个域都得不到最优解。

解决:要么分开训练两个分类器,要么在特征里加一个domain标记列(来源id),让模型知道样本来自哪个域,学习域间差异。评估时必须按域分开报告指标,别拿一个混合数字骗自己。我自己的习惯是:语料足够就分开训,语料少就加domain特征打乱训练。

5. 进阶验证:拆出词典和模型各自的功劳,再回流长尾数据

5.1 用300条人工标注拆出词典和模型的真实贡献

方案上线后最常被问到的问题是“效果到底是谁的功劳”。词典规则和模型融合在一起,准确率上升了你不知道提升来自哪里。我习惯的做法是做一轮消融对比:挑300到500条人工标注样本,分别跑“纯词典”“纯模型”“词典+模型融合”三个配置,报告各自的F1。这样能直接回答词典基线差在哪、模型补了什么、融合值不值。

一个典型的小规模标注验证结果长这样:

配置新闻评论F1微博评论F1
纯词典0.680.51
纯模型(伪标签训练)0.710.58
词典+模型融合0.750.65

表格里的数字只是示意趋势,不代表你的数据集也会得到同样的结果。重点看差值:如果融合相对纯模型的提升不明显,说明词典没有提供超出模型的新信息,这时应该把精力放在增大标注量和模型迭代上,而不是继续调词表;如果微博侧纯模型明显强,说明词典在口语域的覆盖率还太差,优先补网络新词小词典。

5.2 长尾数据回流与坏案例检查

另一个我会一直做的动作是长尾数据回流。线上预测时,把“词典分很低但模型置信度很高”的样本每天捞出来。这类样本往往是词典不认识的新表达,人工标一批之后,每周回流一次词典,再增量训练模型,让词典和模型两边都保持在线更新的状态。我一直养成的一个习惯是:每次迭代后把预测错的坏案例打印出来通读一遍,数字会骗人,病句不会。看几页之后,你就能讲出错误模式,系统才能有下一步的改进方向。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询