机器学习-词向量转换2
在上篇中,我们完成了中文文本的清洗、分词、向量化,将原始文本转化为固定维度的词频矩阵。接下来要解决的核心问题是:如何让机器从这些数字中学会判断情感倾向?
本文是系列的下篇,聚焦于分类模型的构建、训练、评估与部署,涵盖朴素贝叶斯家族、数据不平衡处理、防止数据泄漏、模型评估指标与生产环境部署。
四、文本分类模型:从特征到预测
有了词频矩阵(通常非常稀疏),我们需要一个能读懂这些数字模式的模型。
4.1 三种主流文本分类模型对比
| 模型 | 原理 | 优点 | 适用场景 |
|---|---|---|---|
| 逻辑回归 | 通过 sigmoid 函数将线性加权和映射到概率区间 | 计算高效、可解释性强 | 文本特征高维稀疏时的首选 |
| 朴素贝叶斯 | 基于贝叶斯定理与特征条件独立假设 | 对高维稀疏数据表现稳定,训练速度极快 | 垃圾邮件过滤、情感分析 |
| 支持向量机 | 寻找最大化间隔的超平面 | 在高维文本特征空间中鲁棒性好 | 分类边界复杂的场景 |
💡逻辑回归的辨析:虽然名字带"回归",但本质是分类模型。通过引入多项式特征或核技巧,可以处理非线性边界。
4.2 朴素贝叶斯的概率基础
朴素贝叶斯是一类基于贝叶斯定理和特征条件独立假设的生成式分类器。
将联合概率拆为单个词概率的乘积,参数估计量从指数级降到线性级。
为什么这个假设"朴素但有效"?实际文本中,单词"非常"和"好"经常一起出现(不独立)。但条件独立假设把联合概率拆成了单个词概率的乘积。尽管这个假设常常不成立,但它极大地减少了参数数量,并且在分类决策中,只需要比较各类别的相对大小,独立假设带来的偏差往往在各类别中方向一致,最终仍能给出较好的排序。
💡 这就是它"朴素"但实用的原因:牺牲精确性,换取计算可行性和可接受的排序性能。
4.3 特征类型决定模型变体
在 scikit-learn 中,朴素贝叶斯因特征分布假设不同而衍生出多种变体:
| 变体 | 特征类型 | 适用场景 |
|---|---|---|
| 高斯朴素贝叶斯 | 连续型特征,服从正态分布 | 身高、温度、灰度值 |
| 多项式朴素贝叶斯 | 离散计数特征,非负整数 | 文本词频、TF-IDF 值 |
| 伯努利朴素贝叶斯 | 二值型特征(0 或 1) | 词是否出现,短文本分类 |
核心辨析:词频矩阵(CountVectorizer 输出)的值是整数(0, 1, 2, ...),属于离散计数数据。高斯贝叶斯假设特征服从正态分布,强行代入会导致模型错误地计算概率。
✅结论:文本情感分析中,词袋模型得到的是离散计数矩阵,多项式朴素贝叶斯是最直接匹配的选项。
Python
from sklearn.naive_bayes import MultinomialNB model = MultinomialNB(alpha=1.0) # alpha 为拉普拉斯平滑参数 model.fit(X_train_vec, y_train)
4.4 补充朴素贝叶斯——处理不平衡数据
在情感分析中,好评和差评数量往往不均衡(例如好评占 90%)。普通的多项式朴素贝叶斯会偏向先验概率大的类别(好评),导致差评(少数类)的召回率很低。
补充朴素贝叶斯(ComplementNB)的核心思想:对每个类别,不再计算属于该类的概率,而是计算样本不属于该类(即属于其它所有类别的补集)的概率,然后选择补集概率最小的类别作为预测结果。
Python
from sklearn.naive_bayes import ComplementNB model = ComplementNB(alpha=1.0) model.fit(X_train_vec, y_train)
💡为什么补集策略能改善不平衡?多数类(好评)样本量大,补集估计(即不属于差评的样本)非常稳定,能更可靠地捕捉差评的独特特征。实验表明,在处理文本分类不平衡数据集时,ComplementNB 通常优于 MultinomialNB。
五、数据不平衡问题的处理
当差评数据仅 32 条、好评数据 12677 条(比例约 1:396)时,直接训练会导致模型"只要预测为好评就能达到 99.75% 准确率",完全丧失识别差评的能力。
5.1 核心影响
模型对少数类(差评)的召回率极低,甚至趋近于0。虽然整体准确率很高,但业务目标(发现差评)完全失败。应关注精确率、召回率、F1-score(尤其对少数类)。
5.2 三种主流解决策略对比
| 策略 | 方法 | 优点 | 缺点 |
|---|---|---|---|
| 过采样 | 随机复制少数类或使用SMOTE合成新样本 | 简单,不丢失多数类信息 | 容易过拟合;SMOTE可能在少数类稀疏时生成噪声 |
| 欠采样 | 随机丢弃多数类样本 | 训练更快,缓解过拟合 | 丢弃大量多数类样本,丢失重要信息 |
| 调整类别权重 | 在损失函数中为少数类错误赋予更高惩罚 | 不改变样本分布,实现简单 | 需手动调整参数 |
5.3 实操建议
由于差评只有32条,过采样(SMOTE)或调整类别权重是合理选择。欠采样会丢掉大量好评,太过浪费。
⚠️关键注意:
不要对测试集做任何重采样!测试集必须保持原始分布。
调整权重与过/欠采样不能同时滥用,通常只选一种。
评估指标要全面:即使少数类F1-score高,也要看多数类的性能是否严重下降。
为什么SMOTE比简单复制更好?简单复制只是重复相同的样本,模型会记住这些样本的“噪声”,导致泛化能力差。SMOTE在少数类样本之间插值,生成的新样本更接近真实分布。但前提是少数类样本不是孤立点,否则SMOTE会产生不合理样本。
六、防止数据泄漏——训练集与测试集的隔离
这是整个预处理流程中最容易犯错的一步,也是面试和考试的高频考点。
6.1 数据泄漏的定义与危害
数据泄漏是指模型在训练过程中,以任何方式“偷看”了本应仅在测试阶段才能接触到的信息,从而使测试集无法公正地反映模型对未知数据的真实泛化能力。
6.2 文本分类中的数据泄漏
如果先对整个数据集进行词向量化,词库的列数是根据全部数据的词频构建的。此时,测试集中的词语会影响到词库的列数(特征数量),导致测试集特征污染训练集。
| 做法 | 操作 | 结果 |
|---|---|---|
| ❌错误 | 先对整个数据集做fit_transform,再切分 | 测试集特征污染训练集,评估虚高 |
| ✅正确 | 先切分,再用训练集fit构建词库 | 训练集和测试集完全隔离 |
6.3 核心原则
数据切分必须发生在任何与“特征”相关的统计计算之前
对训练集调用
fit或fit_transform对测试集和新数据只能调用
transform绝对不能再调用
fit
python
# ✅ 正确做法 from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split(texts, labels, test_size=0.3) vectorizer = CountVectorizer() X_train_vec = vectorizer.fit_transform(X_train) # 训练集fit+transform X_test_vec = vectorizer.transform(X_test) # 测试集只transform
💡验证方法:训练集和测试集向量矩阵的列数必须相同,都是max_features的值。
七、模型评估:准确率、召回率、F1-score
分类报告会为每个类别输出三项核心指标。
7.1 核心评估指标
| 指标 | 定义 | 公式 | 关注点 |
|---|---|---|---|
| 精确率(Precision) | 在所有被预测为“好评”的样本中,真正是好评的比例 | TP / (TP + FP) | 关注误报率 |
| 召回率(Recall) | 在所有真正的“好评”中,模型成功找出了多少 | TP / (TP + FN) | 关注漏报率 |
| F1-score | 精确率和召回率的调和平均数 | 2 × (P × R) / (P + R) | 综合指标 |
7.2 情感分析中的对称性
在情感分析任务中,差评和好评的重要性是对称的。不像医疗系统(更关注召回阳性病例),误判一条好评为差评和误判一条差评为好评,对用户体验的伤害相当。
⚠️关键辨析:不要只看“准确率(Accuracy)”。如果数据集有90%是好评,一个“永远预测好评”的模型也能达到90%准确率,但它完全无法识别差评。必须结合精确率、召回率、F1-score综合评估,特别是少数类的召回率。
7.3 过拟合诊断
对比训练集与测试集的表现:
| 情况 | 训练集准确率 | 测试集准确率 | 诊断 |
|---|---|---|---|
| 正常 | 95% | 93% | 泛化能力良好,差异在1~3个百分点内属正常 |
| 过拟合 | 95% | 70% | 模型记住了训练集噪声,泛化能力差 |
| 欠拟合 | 60% | 58% | 模型尚未学到足够特征 |
八、新样本预测与部署
8.1 两个模型都要保存
在部署时,必须同时保存和加载两个模型:
| 模型 | 作用 | 文件 |
|---|---|---|
| vectorizer(词库转换器) | 将新文本转换为词向量 | vectorizer.pkl |
| classifier(分类器) | 对词向量进行预测 | classifier.pkl |
⚠️ 单独加载分类器而忘记加载vectorizer会导致无法转换新样本。
python
import joblib 保存 joblib.dump(vectorizer, 'vectorizer.pkl') joblib.dump(classifier, 'classifier.pkl') 加载 vectorizer = joblib.load('vectorizer.pkl') classifier = joblib.load('classifier.pkl')8.2 新样本的完整预测流程
python
# 1. 新评论预处理(与训练时完全一致) def preprocess(text): words = jieba.lcut(text) return " ".join(words) processed = preprocess(new_review) 2. 使用训练好的vectorizer进行transform(不能fit!) X_new = vectorizer.transform([processed]) 3. 预测 prediction = classifier.predict(X_new)[0] probability = classifier.predict_proba(X_new)[0]
💡关键点:
新样本必须走完全相同的预处理和transform流程,否则特征空间不一致会导致预测失败。
如果新文本中有训练集没有的词,这些词会被忽略(不会出现在向量中)。
预测阶段绝对不能再调用
fit,否则会建立新的词库,导致特征维度不一致。
8.3 部署注意事项
| 注意事项 | 说明 |
|---|---|
| 环境一致性 | 确保生产环境中的分词库版本与训练时一致,否则分词结果可能不同 |
| 预处理一致性 | 新输入必须走完全相同的预处理流程,不可省略任何步骤 |
| 模型版本管理 | 记录模型版本,便于回滚和对比 |
| 容器化部署 | 推荐使用Docker确保环境一致性和可移植性 |
