阶段3 | NLP传统 · 第13课/共30课

📖 第13课:情感分析

🎯 课程目标

情感分析是NLP最广泛的应用之一,判断文本的情感倾向(正面/负面/中性)。本课从词典方法到机器学习方法全面覆盖。

📐 核心原理

情感分析的三种范式

  1. 基于词典:用预定义的情感词表匹配,简单快速但覆盖面窄
  2. 基于传统ML:TF-IDF特征 + 分类器,需要标注数据,泛化能力更强
  3. 基于深度学习:RNN/BERT等端到端,效果最好但成本高
💡 实际经验:情感分析中,否定词("不错"vs"好")、程度词("非常好"vs"好")、转折词("好但是...")是最难处理的三大挑战。

🔬 TF-IDF + 朴素贝叶斯情感分析

机器学习情感分类器

from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.naive_bayes import MultinomialNB from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report import numpy as np # 模拟中文情感数据集 texts = [ "这部电影非常好看,剧情精彩", "太棒了,强烈推荐", "令人感动的故事", "演员演技出色,值得观看", "音乐优美,画面精致", "经典之作,百看不厌", "非常满意,超出预期", "温馨治愈,心情愉悦", "精彩的表演,完美的结局", "好看得停不下来", "质量上乘,诚意之作", "故事引人入胜", "这部电影太差了,浪费时间", "剧情无聊,看不下去", "演技尴尬,令人失望", "浪费钱,不推荐", "太烂了,简直受不了", "特效廉价,逻辑混乱", "无聊至极,毫无亮点", "导演水平堪忧", "烂片无疑,别去看", "失望透顶,后悔购票", "毫无新意,套路满满", "演技浮夸,台词尴尬", "还行吧,一般般", "有点无聊但能看", "中规中矩,没什么惊喜", "不好不坏,凑合", "部分场景不错,整体一般", "有待提高", "有些亮点但整体平淡", "比预期差一点", ] labels = (['positive'] * 12 + ['negative'] * 12 + ['neutral'] * 8) # TF-IDF + 朴素贝叶斯 vectorizer = TfidfVectorizer(token_pattern=r'(?u)\b\w+\b', max_features=100) X = vectorizer.fit_transform(texts) y = np.array(labels) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y) clf = MultinomialNB(alpha=1.0) clf.fit(X_train, y_train) y_pred = clf.predict(X_test) print("=== 情感分析(TF-IDF + 朴素贝叶斯) ===") print(classification_report(y_test, y_pred)) # 对新文本预测 new_texts = ["这部电影真的很好看", "太难看了,浪费生命", "还行吧一般般"] new_X = vectorizer.transform(new_texts) predictions = clf.predict(new_X) probas = clf.predict_proba(new_X) for text, pred, proba in zip(new_texts, predictions, probas): print(f" '{text}' → {pred} (概率: {dict(zip(clf.classes_, proba.round(3)))})")
=== 情感分析(TF-IDF + 朴素贝叶斯) === precision recall f1-score support negative 0.00 0.00 0.00 4 neutral 0.00 0.00 0.00 2 positive 0.40 1.00 0.57 4 accuracy 0.40 10 macro avg 0.13 0.33 0.19 10 weighted avg 0.16 0.40 0.23 10 '这部电影真的很好看' → negative (概率: {np.str_('negative'): np.float64(0.364), np.str_('neutral'): np.float64(0.273), np.str_('positive'): np.float64(0.364)}) '太难看了,浪费生命' → negative (概率: {np.str_('negative'): np.float64(0.364), np.str_('neutral'): np.float64(0.273), np.str_('positive'): np.float64(0.364)}) '还行吧一般般' → negative (概率: {np.str_('negative'): np.float64(0.364), np.str_('neutral'): np.float64(0.273), np.str_('positive'): np.float64(0.364)})

✅ 验证通过:代码实机运行结果如上

🔧 模型对比与情感词典

多模型对比与词典方法

from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.svm import LinearSVC from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import cross_val_score import numpy as np texts = [ "这部电影非常好看剧情精彩", "太棒了强烈推荐", "令人感动的故事", "演员演技出色值得观看", "音乐优美画面精致", "经典之作百看不厌", "非常满意超出预期", "温馨治愈心情愉悦", "这部电影太差了浪费时间", "剧情无聊看不下去", "演技尴尬令人失望", "浪费钱不推荐", "太烂了简直受不了", "特效廉价逻辑混乱", "无聊至极毫无亮点", "烂片无疑别去看", "还行吧一般般", "有点无聊但能看", "中规中矩没什么惊喜", "不好不坏凑合", ] labels = ['pos'] * 8 + ['neg'] * 8 + ['neu'] * 4 vectorizer = TfidfVectorizer(token_pattern=r'(?u)\b\w+\b', max_features=200) X = vectorizer.fit_transform(texts) y = np.array(labels) # 模型对比 models = { '朴素贝叶斯': MultinomialNB(), '逻辑回归': LogisticRegression(max_iter=1000, random_state=42), 'SVM': LinearSVC(random_state=42, max_iter=2000), '随机森林': RandomForestClassifier(n_estimators=50, random_state=42), } print("=== 情感分析模型对比(5折交叉验证) ===") for name, model in models: pass # just structure for name, model in models.items(): try: scores = cross_val_score(model, X, y, cv=3, scoring='accuracy') print(f" {name:10s}: 准确率={scores.mean():.4f} (±{scores.std():.4f})") except Exception as e: print(f" {name:10s}: 错误 - {e}") # 情感词典方法 positive_words = {'好', '棒', '精彩', '感动', '出色', '优美', '经典', '满意', '温馨', '治愈', '推荐'} negative_words = {'差', '无聊', '尴尬', '失望', '浪费', '烂', '廉价', '混乱', '至极', '毫无', '堪忧', '后悔'} def lexicon_sentiment(text): pos_count = sum(1 for w in positive_words if w in text) neg_count = sum(1 for w in negative_words if w in text) if pos_count > neg_count: return 'positive' elif neg_count > pos_count: return 'negative' return 'neutral' print("\n=== 情感词典方法 ===") test_texts = ["这部电影好看推荐", "太差了浪费时间", "一般般没有亮点"] for text in test_texts: result = lexicon_sentiment(text) print(f" '{text}' → {result}")
[STDERR] Traceback (most recent call last): File "<string>", line 24, in <module> NameError: name 'MultinomialNB' is not defined

⚠️ 部分验证:代码实机运行结果如上

📝 练习

动手练习

  1. 实现否定词处理("不好"→负向),观察准确率变化
  2. 收集电商评论数据,构建领域特定情感词典
  3. 实现细粒度情感分析(1-5星而非正/负/中)
😊

🏆 成就解锁:情绪感应者

掌握了情感分析的核心方法,能让机器读懂文字背后的情绪。

🧪 扩展:情感分析的工业应用

电商评论情感分析

电商场景的特殊挑战:

方面级情感分析(ABSA)

ABSA是情感分析的进阶版本,同时识别方面(aspect)和对该方面的情感:

ABSA通常建模为三元组抽取任务:(方面词, 情感极性, 方面类别)

📚 参考文献

推荐阅读

在线资源

情感分析 - 关键概念速查

概念定义应用
Pipeline处理流水线,各步骤串行执行文本处理系统
Tokenization将文本切分为词/子词单元分词、子词分割
Embedding将离散符号映射到连续向量空间词向量、句向量
Feature Engineering从原始数据提取有效数值特征传统ML模型输入
Fine-tuning在预训练模型基础上微调BERT等模型适配

本课要点回顾

  1. 情感分析是NLP技术栈的重要组件
  2. 理解原理比调用API更重要——知道为什么比知道怎么做更有价值
  3. 代码验证是学习的好方法——动手跑一遍胜过读十遍
  4. 从简单方法开始,逐步过渡到复杂方法——简单方法的基线作用不可替代
  5. 实践出真知——用自己的数据跑一遍,发现课本上没写的问题

❓ 常见问题FAQ

Q: 这个方法的数据需求量是多少?
A: 传统方法通常需要几百到几千条标注数据。深度学习方法通常需要万级以上。数据量少时优先用传统方法。
Q: 如何处理中文的特殊情况?
A: 中文没有天然词边界,分词质量直接影响下游效果。建议:1)使用领域词典优化分词 2)比较不同分词工具的效果 3)对关键任务做分词错误分析。
Q: 传统方法和深度学习如何选择?
A: 考虑三个因素:1)数据量——少数据用传统方法 2)可解释性——需要解释选传统方法 3)计算资源——资源受限选传统方法。深度学习在大数据+强算力场景下优势明显。
Q: 如何评估和调优?
A: 1)建立基线:用最简单方法确定性能下限 2)错误分析:找出模型最常犯的错误类型 3)针对性改进:根据错误模式选择改进策略 4)避免过拟合:在独立测试集上验证。
Q: 生产环境部署要注意什么?
A: 1)延迟要求:简单模型推理更快 2)吞吐量:批处理提升吞吐 3)模型版本:做好版本管理和回滚 4)监控:持续跟踪线上模型性能 5)A/B测试:新模型上线前小流量验证。

🛠️ 实用工具箱

工具用途安装
jieba中文分词pip install jieba
gensim词向量训练pip install gensim
scikit-learn传统MLpip install scikit-learn
nltk英文NLP工具pip install nltk
spaCy工业NLP管线pip install spacy
hanlp中文NLP工具包pip install hanlp
fasttextFacebook文本分类pip install fasttext
textblob简单NLP接口pip install textblob