🎯 学习目标
- 理解文本分类任务与情感分析的关系、典型应用
- 掌握机器学习文本分类完整流水线
- 掌握词袋、n-gram、TF-IDF 特征与 sklearn 向量化器
- 了解朴素贝叶斯、逻辑回归、SVM 的原理直觉与选型
- 会用准确率/精确率/召回率/F1/混淆矩阵评估模型
- 掌握词典法(SnowNLP)与机器学习法两种情感分析路线
1任务全景
文本分类(Text Classification):给定预定义类别集合,让模型为文本打上一个(单标签)或多个(多标签)类别标签。
| 应用 | 文本 | 类别 |
|---|---|---|
| 垃圾邮件/短信过滤 | "恭喜中奖点击领取" | 垃圾 / 正常 |
| 新闻分类 | "上证指数今日收涨…" | 财经/体育/科技… |
| 客服工单路由 | "我的订单还没发货" | 物流/退款/售后 |
| 情感分析 | "手机拍照真烂,后悔买了" | 正面 / 负面 |
| 意图识别 | "帮我订明天去上海的票" | 订票/查询/闲聊 |
情感分析(Sentiment Analysis)本质就是类别为"正/负/中性"的文本分类;更细粒度还有基于方面的情感分析(ABSA):"屏幕很好但电池拉胯"→ 屏幕正面、电池负面。
2机器学习文本分类五步流水线
① 数据准备 文本 X + 标签 y(人工标注/公开数据集)
│ 划分训练集 / 测试集
▼
② 文本预处理 清洗 → jieba 分词 → 空格拼接
▼
③ 特征工程 词袋 / n-gram / TF-IDF 把文本变成数值向量
▼
④ 训练模型 朴素贝叶斯 / 逻辑回归 / SVM(fit)
▼
⑤ 评估与预测 测试集 P/R/F1 → 调参 → 对新文本 predict
关键认知:传统机器学习模型(贝叶斯/SVM)只能处理数字,不能直接读文字。所以第③步"文本→向量"是整个流程的核心,这一步叫特征提取。
3文本如何变成向量
One-Hot 与词袋模型(Bag of Words)
把语料中所有词收集成词表(vocabulary),每个词占一维;文档用一个向量表示,每一维记录该词是否出现/出现次数。忽略词序,像把词倒进袋子,故名词袋。
词表(按语料构建):[喜欢, 好吃, 难吃, 服务, 速度, 慢, 快]
文档1 "喜欢 好吃 喜欢" → [2,1,0,0,0,0,0]
文档2 "难吃 服务 慢" → [0,0,1,1,0,1,0]
文档3 "速度 快 好吃" → [0,1,0,0,1,0,1]
缺点:① 词表可达几万~几十万维,极度稀疏;② 不含语序("狗咬人"="人咬狗");③ 常见词权重过大。TF-IDF 加权缓解第③点,n-gram 缓解第②点。
n-gram:保留局部词序
unigram(1元):不好 / 吃 → 两个独立词,情感被拆散!
bigram(2元):不好吃 / 好吃 → 能捕捉短语
trigram:更大粒度
实践:ngram_range=(1,2) 一元+二元一起用,通常效果最好
sklearn 向量化器
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
docs = ["喜欢 好吃 喜欢", "难吃 服务 慢", "速度 快 好吃"]
# CountVectorizer:词频计数
cv = CountVectorizer()
X = cv.fit_transform(docs) # 稀疏矩阵!3 文档 × 7 词
print(X.toarray())
print(cv.get_feature_names_out()) # 词表
# TfidfVectorizer:分词后直接做 TF-IDF(文本分类首选,一步到位)
tv = TfidfVectorizer()
X2 = tv.fit_transform(docs)
# 中文:把 jieba 接进向量化器(它默认按空白分词,所以先 jieba+空格)
tv_zh = TfidfVectorizer(
token_pattern=r"(?u)\b\w+\b", # 让单字也能进词表
ngram_range=(1, 2), # 一元+二元
min_df=2, # 至少在2篇出现(去极罕见噪声词)
max_df=0.9, # 在90%以上文档出现的词忽略(近似停用词)
max_features=10000, # 词表上限
sublinear_tf=True) # 用 1+log(tf) 削弱高频影响
4三种常用分类算法(直觉理解)
| 算法 | 核心直觉 | 特点 |
|---|---|---|
| 多项式朴素贝叶斯 MultinomialNB | 用贝叶斯公式,在"词独立"假设下比较各类别生成该文本的概率 | 极快、小数据表现好、适合词频特征;文本分类经典基线 |
| 逻辑回归 LogisticRegression | 学一条线性决策边界,输出属于各类的概率 | 稳、可解释(权重显示哪些词导向正/负类),文本分类强基线 |
| 支持向量机 LinearSVC | 在高维空间找间隔最大的分类超平面 | 高维稀疏文本上非常强,长文本/大词表常夺冠 |
# 朴素贝叶斯的直觉(了解即可,公式不必死记)
# P(类别|文本) ∝ P(类别) × Π P(每个词|类别)
# "垃圾"类文章里"中奖"出现概率很高 → 看到"中奖"就给垃圾类加分
# 拉普拉斯平滑避免某词在某类从未出现导致概率为 0
# sklearn 中三者调用接口完全一致:fit / predict / predict_proba / score
5完整实战:中文评论情感分类
下面使用内置的小语料演示可直接运行的完整流程;真实项目把 texts/labels 换成从 CSV 读取的上万条数据即可。
# ===== 1. 准备数据(texts 原始文本,labels 1正面 0负面)=====
texts = [
"这个手机拍照非常清晰,续航也很好,很满意",
"物流很快,包装完整,客服态度好,五星好评",
"性价比超高,用了一个月依然流畅,推荐购买",
"餐厅环境不错,菜品新鲜,下次还会再来",
"书的质量很好,印刷清晰,内容深入浅出",
"屏幕显示效果惊艳,运行速度飞快,喜欢",
"物流太慢了,等了一周才到,非常不满意",
"手机经常卡顿,电池掉电飞快,后悔购买",
"客服态度恶劣,退货流程繁琐,差评",
"菜品又贵又难吃,服务也很差,再也不来",
"包装破损严重,商品有划痕,申请售后",
"噪音大得离谱,发热严重,完全不值这个价",
]
labels = [1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0]
# ===== 2. jieba 分词并空格拼接(sklearn 按空白取词)=====
import jieba
def tokenize(text):
return " ".join(jieba.lcut(text))
texts_cut = [tokenize(t) for t in texts]
# ===== 3. 划分训练集/测试集 =====
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
texts_cut, labels, test_size=0.25, random_state=42)
# ===== 4. TF-IDF 特征 + 逻辑回归,用 Pipeline 串成一条流水线 =====
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
pipe = Pipeline([
("tfidf", TfidfVectorizer(ngram_range=(1, 2),
token_pattern=r"(?u)\b\w+\b")),
("clf", LogisticRegression(max_iter=1000)),
])
pipe.fit(X_train, y_train) # 训练:向量化 + 分类一步完成
# ===== 5. 评估与预测 =====
print("测试集准确率:", pipe.score(X_test, y_test))
def predict_sentiment(text):
vec = tokenize(text)
prob = pipe.predict_proba([vec])[0]
label = pipe.predict([vec])[0]
return ("正面" if label == 1 else "负面", round(max(prob), 3))
print(predict_sentiment("电池很耐用,拍照效果也很棒")) # ('正面', ...)
print(predict_sentiment("卡顿严重,体验极差")) # ('负面', ...)
换成朴素贝叶斯 / SVM(只改一行)
from sklearn.naive_bayes import MultinomialNB
from sklearn.svm import LinearSVC
pipe_nb = Pipeline([("tfidf", TfidfVectorizer()),
("clf", MultinomialNB())]).fit(X_train, y_train)
pipe_svm = Pipeline([("tfidf", TfidfVectorizer(ngram_range=(1,2))),
("clf", LinearSVC())]).fit(X_train, y_train)
6模型评估四件套
预测为正 预测为负
实际为正 TP 真正例 FN 假负例(漏报)
实际为负 FP 假正例 TN 真负例
(以"负面"为正类时同样道理)
| 指标 | 公式 | 关注场景 |
|---|---|---|
| 准确率 Accuracy | (TP+TN)/全部 | 类别均衡时的总体正确率 |
| 精确率 Precision | TP/(TP+FP) | 报警了的有多少是真的(垃圾邮件:别误伤正常邮件) |
| 召回率 Recall | TP/(TP+FN) | 真正的坏样本抓到多少(风控/疾病:宁抓错勿放过) |
| F1 | 2PR/(P+R) | P 与 R 的调和平均,综合指标 |
from sklearn.metrics import (classification_report, confusion_matrix,
accuracy_score)
y_pred = pipe.predict(X_test)
print("准确率:", accuracy_score(y_test, y_pred))
print(classification_report(y_test, y_pred,
target_names=["负面", "正面"], digits=3))
print("混淆矩阵:\n", confusion_matrix(y_test, y_pred))
# 混淆矩阵读法(二分类):
# [[TN, FP]
# [FN, TP]]
类别不平衡陷阱:1000 条里 990 条正常、10 条垃圾,模型全猜"正常"也有 99% 准确率,但毫无用处。此时应看少数类的 F1 / ROC-AUC,并用 class_weight="balanced"、过采样(SMOTE)等手段。
7情感分析路线一:情感词典法
不训练模型,直接查"情感词表"打分:正面词(好、棒、满意)加分,负面词(差、烂、失望)减分,再考虑否定词翻转、程度副词加权。
# pip install snownlp (SnowNLP:自带中文语料训练的情感模型,开箱即用)
from snownlp import SnowNLP
comments = [
"这家店味道真好,服务也很周到,强烈推荐",
"太难吃了,等了一个小时,态度还差,再也不来",
"价格一般,味道中规中矩吧",
]
for c in comments:
score = SnowNLP(c).sentiments # 0~1,越接近1越"正面"
print(f"{score:.3f} {c}")
# 0.9+ 这家店味道真好…
# 0.0- 太难吃了…
# 0.4~0.6 一般…(中性往往落在中间带)
# SnowNLP 还提供分词、拼音、关键词、摘要等功能
s = SnowNLP("这个手机真的非常好用")
print(s.keywords(3), s.pinyin[:3])
# 手写词典法(原理演示,工业级需搭配否定/程度副词表)
POS = {"好": 2, "棒": 2, "喜欢": 2, "满意": 2, "快": 1, "推荐": 2}
NEG = {"差": -2, "烂": -2, "慢": -1, "难吃": -2, "后悔": -2, "卡顿": -2}
NEGATION = {"不", "没", "别", "无", "难以"}
def sentiment_score(text):
words = jieba.lcut(text)
score = 0
for i, w in enumerate(words):
val = POS.get(w, 0) + NEG.get(w, 0)
if i > 0 and words[i-1] in NEGATION:
val = -val # 否定翻转极性
score += val
return score
print(sentiment_score("不好")) # -2("不"翻转了"好")
print(sentiment_score("喜欢不慢")) # 2 + 1 = 3
常用中文情感词典资源:知网 HowNet 情感词表、大连理工情感词汇本体库、BosonNLP 情感词典,GitHub 均可找到。
8两条路线对比与选型
| 词典法 | 机器学习法(本章) | 深度学习/大模型 | |
|---|---|---|---|
| 需要标注数据 | 不需要 | 需要(千~万条) | 少量/零样本 |
| 反讽/隐含情感 | 很差 | 一般 | 好 |
| 可解释性 | 强(可列出命中词) | 较强(看权重) | 较弱 |
| 领域迁移 | 换词典即可 | 需重新标注训练 | 提示工程/微调 |
| 适用 | 冷启动、规则明确 | 有标注数据的业务主力方案 | 高精度、复杂语义 |
实战建议:上线初期用词典法快速跑通基线;积累几千条人工标注后上 TF-IDF + 逻辑回归/SVM(简单、快、稳);数据过十万或精度不足时再上 BERT 微调;大模型 API 适合零样本冷启动或作为标注助手(让 LLM 预标注再人工校正)。
9从真实数据读取:CSV 模板
# 数据文件 waimai.csv:
# label,text
# 1,味道不错送餐也快
# 0,等了一小时还没到
import pandas as pd
from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.svm import LinearSVC
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
import jieba
df = pd.read_csv("waimai.csv")
df["text_cut"] = df["text"].astype(str).map(
lambda x: " ".join(jieba.lcut(x)))
Xtr, Xte, ytr, yte = train_test_split(
df["text_cut"], df["label"], test_size=0.2,
random_state=42, stratify=df["label"]) # stratify 保持类别比例
clf = Pipeline([
("tfidf", TfidfVectorizer(ngram_range=(1, 2),
token_pattern=r"(?u)\b\w+\b",
min_df=2, max_df=0.9)),
("svm", LinearSVC()),
]).fit(Xtr, ytr)
print(classification_report(yte, clf.predict(Xte), digits=3))
10本章小结与练习
📌 关键结论
- 流水线:标注数据 → 清洗分词 → TF-IDF(1,2-gram)→ LR/SVM → 评估。
- 小数据 NB 起步,稀疏高维文本 LinearSVC/逻辑回归常是最强传统基线。
- 不平衡数据别看准确率,要看少数类 F1;用 Pipeline 串联避免数据泄漏。
- 情感分析:词典法冷启动,机器学习做主力,BERT/LLM 冲精度。