情感分析:从词典到预训练模型
情感分析入门:从规则词典到大规模预训练模型
情感分析(Sentiment Analysis)是自然语言处理(NLP)中最常见的应用之一,它帮助我们从文本中自动识别情感倾向(正面、负面、中性或更细粒度的情绪),广泛用于产品评论分析、社交媒体监控、舆情分析等场景。本教程将带你从最基础的规则方法走到当下主流的预训练模型方法,理清技术演进路线,并提供可直接运行的代码示例。
1. 什么是情感分析
情感分析的目标是判断一段文本所表达的情感极性或情绪类别。常见任务包括:
- 极性分类:正面 / 负面 / 中性
- 细粒度情绪分类:愤怒、喜悦、悲伤、恐惧等
- 方面级情感分析:针对特定实体或属性的情感(如“餐厅服务很好,但价格太贵”中,对服务正面、对价格负面)
本教程聚焦在极性分类任务上,向你展示从简单到复杂的多种实现思路。
2. 基于词典的情感分析:规则为王
早期的情感分析完全依赖情感词典与手工规则。这类方法不需要训练数据,实现简单、可解释性强,但精度有限。
2.1 核心原理
预先构建一个情感词典,里边每个词都标注了极性(正/负)和强度(如 +1 / -1),然后扫描输入文本,累加所有情感词的分值,最终根据总分的正负判断整体情感。
2.2 常用情感词典
- 知网 Hownet 情感词典(中英文)
- NTUSD(台湾大学通用中文情感词典)
- SentiWordNet(英文,基于 WordNet)
- VADER(英文,专为社交媒体优化,能处理表情、大写、程度修饰词)
2.3 代码实战:基于VADER的英文情感分析
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
analyzer = SentimentIntensityAnalyzer()
text = "The product is amazing! Love the sleek design, but battery life could be better."
scores = analyzer.polarity_scores(text)
print(scores) # {'neg': 0.114, 'neu': 0.551, 'pos': 0.335, 'compound': 0.6126}
# compound >= 0.05 视为正面,<= -0.05 视为负面,其余中性
label = "positive" if scores['compound'] >= 0.05 else "negative" if scores['compound'] <= -0.05 else "neutral"
print(label)
2.4 中文词典实现示例
使用 SnowNLP 快速进行中文情感判断(其内部也基于贝叶斯模型,但本质仍是词典统计思想):
from snownlp import SnowNLP
s = SnowNLP('这个电影太好看了,全程无尿点')
print(s.sentiments) # 接近1为正面,接近0为负面
# 也可以自行加载词典的简单实现
import jieba
pos_words = set(["好", "棒", "喜欢", "优秀", "性价比高"])
neg_words = set(["差", "糟", "失望", "垃圾", "烂"])
def rule_sentiment(text):
words = jieba.lcut(text)
score = sum(1 for w in words if w in pos_words) - sum(1 for w in words if w in neg_words)
return "positive" if score > 0 else "negative" if score < 0 else "neutral"
print(rule_sentiment("一部很棒的手机,但电池太差了"))
3. 基于传统机器学习的情感分析:特征工程 + 分类器
为了更准确地理解上下文,我们转向机器学习,将文本转换为数值特征,然后用分类算法进行预测。
3.1 经典流程
- 文本预处理:分词、去除停用词、词干提取(英文)或分词(中文)
- 特征提取:词袋模型(CountVectorizer)或 TF‑IDF 特征
- 训练分类器:朴素贝叶斯、逻辑回归、支持向量机(SVM)等
3.2 代码实战:TF‑IDF + 逻辑回归
使用IMDB影评数据集(英文示例,也可替换为中文外卖评论数据集):
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import train_test_split
from sklearn import metrics
# 假设已有含 'review' 和 'label' 列的 DataFrame
# df = pd.read_csv('IMDB.csv')
train_texts, test_texts, train_labels, test_labels = train_test_split(
df['review'], df['label'], test_size=0.2, random_state=42
)
pipeline = make_pipeline(
TfidfVectorizer(stop_words='english', max_features=5000, ngram_range=(1,2)),
LogisticRegression()
)
pipeline.fit(train_texts, train_labels)
preds = pipeline.predict(test_texts)
print(metrics.classification_report(test_labels, preds))
3.3 中文示例
对于中文文本,只需将 TfidfVectorizer 替换为同时进行分词的自定义分析器,或者先使用 jieba 分词再用空格连接:
import jieba
def chinese_tokenizer(text):
return " ".join(jieba.cut(text))
# 应用时指定 tokenizer = chinese_tokenizer
优点:效果远强于词典方法,且不需要深度学习硬件。缺点:特征表达能力有限,无法处理语义相似性。
4. 基于深度学习的情感分析:词向量与上下文建模
4.1 Word2Vec + LSTM / BiLSTM
用预训练的词向量(Word2Vec)将文本转化为序列向量,然后传入循环神经网络(LSTM)学习时序特征。
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.layers import Embedding, LSTM, Dense, Dropout, Bidirectional
from tensorflow.keras.models import Sequential
# 假设 texts 为原始文本列表,labels 为0/1列表
tokenizer = Tokenizer(num_words=20000)
tokenizer.fit_on_texts(X_train)
x_train = pad_sequences(tokenizer.texts_to_sequences(X_train), maxlen=150)
x_test = pad_sequences(tokenizer.texts_to_sequences(X_test), maxlen=150)
model = Sequential()
model.add(Embedding(input_dim=20000, output_dim=128, input_length=150))
model.add(Bidirectional(LSTM(64, return_sequences=False)))
model.add(Dropout(0.5))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
model.fit(x_train, y_train, batch_size=64, epochs=5, validation_split=0.2)
4.2 TextCNN:用卷积抓取局部特征
卷积神经网络同样可以捕捉文本中的n-gram特征,训练快,效果不错。
from tensorflow.keras.layers import Conv1D, GlobalMaxPooling1D
model = Sequential()
model.add(Embedding(20000, 128, input_length=150))
model.add(Conv1D(128, 5, activation='relu'))
model.add(GlobalMaxPooling1D())
model.add(Dropout(0.5))
model.add(Dense(1, activation='sigmoid'))
深度学习方法需要大量标注数据,并且自行设计网络结构。接下来我们进入模型效果更强、上手更简单的预训练时代。
5. 预训练模型时代:BERT、RoBERTa 等
预训练语言模型(Transformer 架构)彻底改变了 NLP 的玩法。你无须从零训练一个庞大的网络,只需在预训练的“底座”上增加一个分类头,用少量数据微调即可获得极佳效果。
5.1 BERT 情感分析
Hugging Face 的 transformers 库让微调成为几行代码的事。以下示例使用 bert-base-uncased(英文),对于中文可使用 bert-base-chinese。
from transformers import AutoTokenizer, AutoModelForSequenceClassification, Trainer, TrainingArguments
import torch
model_name = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=2)
# 数据预处理
def tokenize_function(examples):
return tokenizer(examples['text'], padding='max_length', truncation=True, max_length=256)
# 假设 dataset 为 HuggingFace Dataset 字典,包含 'text' 和 'label' 列
encoded_dataset = dataset.map(tokenize_function, batched=True)
encoded_dataset = encoded_dataset.rename_column("label", "labels")
encoded_dataset.set_format("torch", columns=["input_ids", "attention_mask", "labels"])
training_args = TrainingArguments(
output_dir="./results",
evaluation_strategy="epoch",
per_device_train_batch_size=16,
per_device_eval_batch_size=64,
num_train_epochs=3,
logging_steps=100,
save_strategy="epoch"
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=encoded_dataset["train"],
eval_dataset=encoded_dataset["test"],
)
trainer.train()
5.2 蒸馏版高效模型:DistilBERT
DistilBERT 参数量减少40%,推理速度快两倍,但能保留97%的理解能力。非常适合生产环境中的快速情感分析。
model_name = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=2)
# 其余训练代码完全相同
5.3 RoBERTa 等加强版模型
RoBERTa 是 BERT 的改进版,训练更充分,通常精度更高。可尝试 roberta-base 或跨语言的 xlm-roberta-base。
model_name = "roberta-base"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=2)
5.4 开箱即用:零样本情感分析
在完全没有标注数据的情况下,可以利用零样本分类 pipeline 直接进行情感分析。
from transformers import pipeline
classifier = pipeline("zero-shot-classification", model="facebook/bart-large-mnli")
result = classifier("I absolutely loved the new update!", candidate_labels=["positive", "negative", "neutral"])
print(result['labels'][0]) # 输出 positive
6. 方法对比与选择建议
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 词典规则 | 简单快速,零数据,可解释 | 精度低,无法处理反讽、上下文 | 初筛、简单极性判断 |
| 传统机器学习 | 速度快,训练成本低,效果尚可 | 需要人工特征工程,语义理解弱 | 中小规模标注数据 |
| 深度学习(LSTM/CNN) | 自动提取特征,效果优于传统ML | 需要大量标注数据,训练慢 | 中等规模数据,GPU可用 |
| 预训练模型(BERT等) | 精度极高,语义理解强,易微调 | 推理速度较慢,模型大 | 精度要求高的商业应用 |
| 零样本模型 | 零标注数据即可运行 | 精度不如微调模型,速度慢 | 无标注数据时的快速原型 |
7. 实战建议与下一步
- 从简单开始:如果你第一次做情感分析,先用 VADER 或 SnowNLP 快速原型的,再逐步过渡到机器学习或预训练模型。
- 数据为王:预处理与标注质量直接决定最终效果。考虑使用
cleanlab等工具检测标注噪声。 - 评估指标:不要只看准确率,尤其在类别不平衡时,应关注 F1 分数、召回率与精确率。
- 部署考量:预训练模型较大,部署时可考虑转为 ONNX 或使用
torch.jit加速,也可采用 DistilBERT、ALBERT 等轻量模型。 - 延伸学习:尝试方面级情感分析(如 ABGSA 算法)、多模态情感分析(图像+文本),或融合知识图谱提升情感理解能力。
情感分析技术栈已经从规则词典进化到了百亿参数的大语言模型,但理解其基础原理仍然至关重要。希望本教程能够帮助你建立起完整的知识框架,并动手实现第一个情感分析系统。