
简介这是一套基于Word2Vec与SVM对电商评论进行情感分析的完整Python项目面向NLP初学者、数据挖掘学习者以及需要快速落地评论分析流程的电商运营与开发人员。项目覆盖数据预处理、Word2Vec词向量训练、特征矩阵构建、SVM模型训练与调优、模型评估等关键环节并提供可运行的代码与配套数据集。压缩包共18个文件包括6个CSV数据文件、5个Python脚本、4个NumPy数组文件以及训练好的SVM模型、停用词表和说明文档整体大小约30.15MB。脚本按数据清洗、词向量训练、模型构建、模型测试等模块拆分训练好的pkl模型可直接加载使用CSV与npy文件则方便读者对照训练与验证。目前已有1050人学习下载适合希望获得可直接运行的情感分析案例、通过动手实践掌握NLP与机器学习完整流程的用户。1. 电商评论情感分析为什么用 Word2Vec SVM一个 5000 条样本就能跑通的老实方案做电商的都知道评论数据是离业务最近的文本资产。单靠人工盯评价详情页一天几百条还行到了大促期间几千条差评根本看不过来。我处理这个需求时第一反应不是上 BERT 或者 ChatGLM 微调而是用 Word2Vec SVM 这个老组合先把评论里的词转成向量再做一次二分类情感判断。原因很直接——电商评论普遍只有几十个字语法碎片化严重但情感信号集中在少数关键词上这种场景恰恰是 Word2Vec 善于捕捉的。加上 SVM 对小样本、高维特征分类稳健在几千条有标注的评论上就能产出可用模型还不需要 GPU。这套方案在真实项目里最大的价值是可控数据就是一张两列的 CSV脚本理顺之后十几分钟出结果调参路径清晰业务方要的“好评率统计”“差评预警”都能直接落地。下面从数据集准备开始一步步把流程跑通。2. 数据集准备与中文预处理从一条淘宝评论到一个干净词表2.1 数据集格式两列 CSV好评 1 差评 0几千条就能跑标题里说“含数据集可直接运行”落地时最省心的数据集格式就是 CSV两列label和review。label用整数1 表示好评0 表示差评。别把评分列混进来做三分类二分类是电商评论情感分析最常见、也最容易评估的切法等业务成熟了再扩展成中立/正面/负面三分类也不迟。数据量上5000 条有标注评论已经能跑出一个业务可用的模型少于 2000 条时SVM 的调参空间会变小但依然比随机猜强很多。如果你手头只有几百条优先保证差评数据占比别太低后面会看到这直接影响模型能不能识别差评。下面这段代码生成一个最小的示例 CSV方便你先跑通流程再替换成自己的真实数据import pandas as pd # label: 1 表示好评0 表示差评 data pd.DataFrame([ (1, 质量好孩子很喜欢推荐), (1, 发货很快包装严实客服态度也好), (0, 用了三天就坏申请退货还拖了一天), (0, 实物和图片差太远不值这个价), ], columns[label, review]) data.to_csv(ecommerce_review.csv, indexFalse, encodingutf-8-sig) print(data)这段代码的关键是列名必须叫label和review后续所有处理函数都按这两个字段写避免在数据清洗时反复改字段名。encodingutf-8-sig是给 Excel 用户留的活路——Windows 下直接双击打开 CSV带 BOM 头才不会乱码。真实数据如果来自订单系统通常会有评分字段常见做法是把 4 分及以上映射为好评1 到 2 分映射为差评3 分先丢掉或单独留存别硬塞进二分类里把边界搞脏。2.2 中文评论清洗与 jieba 分词正则、停用词、否定词别删电商评论最麻烦的不是长而是脏表情符号、火星文、英文缩写、错别字、广告信息混在一起。清洗的目标不是把文本变成标准书面语而是最大限度保住情感信号。我的处理顺序是先去掉非中文/非字母/非数字的符号再做分词最后按停用词表过滤。这里有个容易翻车的点停用词表不要包含否定词。很多开源停用词表把“不、没、别、无”也收进去了分词后删掉它们“太差了”会变成“太差”意思还在“不喜欢”变成“喜欢”情感直接反转。这是血泪经验我第一次做的时候用的通用停用词表模型 f1 一直在 0.6 附近打转后来检查预处理结果才发现差评里全是反向词被删光了。import re import jieba # 停用词集合只放真正的虚词和标点占位词不放否定词 STOP_WORDS set( 的 了 是 在 我 有 和 就 都 一 一个 很 也 你 他 她 它 这个 那个 还 又 才 吧 啊 呢 吗 哦 嗯 呀 啦.split() ) def clean_text(text: str) - str: # 只保留中文、英文字母和数字去掉表情与特殊符号 text re.sub(r[^\u4e00-\u9fa5A-Za-z0-9], , text) # 把连续空格压缩成一个避免分词时空 token 干扰 return re.sub(r\s, , text).strip() def cut_words(text: str): cleaned clean_text(text) words [w for w in jieba.cut(cleaned) if w.strip() and w not in STOP_WORDS] return words data[words] data[review].apply(cut_words) print(data[[review, words]].head())正则里的\u4e00-\u9fa5是中文 Unicode 范围加上A-Za-z0-9是为了保留“yyds”“iPhone15”这类英文数字混排的词它们在电商评论里往往带着明确情感倾向。jieba.cut默认全模式还是精确模式这里用的是默认精确模式对短文本足够。分词后过滤空字符串和停用词但注意单字词“好”“差”“快”这类有情感色彩的字别一并过滤所以停用词表里我没放“好”和“差”。如果你发现某些商品词被 jieba 切碎比如“空气炸锅”被切成“空气”“炸锅”最省事的办法是加自定义词典jieba.load_userdict(user_dict.txt) # user_dict.txt 每行一个词空气炸锅 5 n这个文件里每行格式是“词 词频 词性”词频建议写 10 以上让 jieba 优先识别成整词。新品牌名、行业黑话都丢进去能明显减少后续 Word2Vec 学到碎片向量的问题。3. 用 Gensim 自己训 Word2Vec参数怎么设句子向量怎么来3.1 为什么不直接用预训练词向量电商黑话和通用向量对不上很多人拿到中文文本第一反应是加载一个预训练词向量比如腾讯词向量或百度百科向量。它们在通用领域确实好但放到电商评论里有一个很尴尬的问题商品黑话、平台梗、营销词在公共语料里要么没收录要么上下文完全是另一套场景。“白菜价”在百科语料里可能和“蔬菜批发”共现在电商评论里却和“划算”共现——这种语义偏移会直接污染下游分类。自己用评论语料训 Word2Vec 的好处就在这里向量是从你的数据里长出来的“垃圾”的邻居会变成“差劲”“不值”“退货”而不是百科里的“废品回收”。代价只是多花几十秒训练时间对几千条评论来说完全可忽略。而且 Word2Vec 不是深度神经网络它本质上是在做词共现的矩阵分解小语料也能训。min_count 调低一点、维度给小一点效果不会因为数据量少就崩盘。这正好匹配电商评论的语料规模。3.2 用 Gensim 训练 Word2Vecvector_size、window、min_count 怎么给我用 Gensim 4.x 版本参数名和旧版有差异最典型的是size改成了vector_size照着旧教程写会直接报错。训练代码不长真正值得花时间的是参数怎么给from gensim.models import Word2Vec sentences data[words].tolist() # 每条评论是分词后的 list model Word2Vec( sentencessentences, vector_size128, # 每个词映射成 128 维向量 window5, # 上下文窗口只看左右各 5 个词 min_count1, # 词频小于 1 的词丢弃小语料设 1 保留更多词 sg1, # 1 用 Skip-gram0 用 CBOW小语料推荐 Skip-gram epochs10, # 遍历语料 10 轮 workers4, # 并行线程数 seed42 ) model.save(word2vec.model)参数里最影响效果的是min_count和sg。min_count1表示词频只有 1 的词也保留这在小语料里很重要“踩雷”“避雷”这类新词往往只出现几次丢掉就只剩下“雷”一个普通词了。sg1选 Skip-gram它在语料小时对低频词更友好CBOW 更像是在做“完形填空”平滑了高频词但对情感词不够敏锐。如果语料大到几万条以上可以试试sg0提速。vector_size128对几千条评论是合适的中位数。少于 2000 条数据时建议降到 64词表就几百个维度太高会让每个词向量都成了稀疏随机数SVM 反而不好拟合。window5保持默认即可评论平均不到 30 个字窗口太大容易把无关词拉进上下文。训练完后立刻做一次直觉验证这一步能省下后面调参的大量时间print(model.wv.most_similar(好评, topn10)) print(model.wv.most_similar(垃圾, topn10))如果“好评”的近邻里出现“质量”“满意”“推荐”说明共现关系学对了如果近邻全是不相关的高频词优先检查分词是否太碎而不是急着调 SVM。3.3 句向量生成把一条评论的平均词向量做成 SVM 的输入Word2Vec 产出的是词向量但 SVM 需要一条评论对应一个固定长度的向量。最常见、最可靠的做法是平均词向量把一条评论里所有词的向量按维度取平均得到一个 128 维向量。朴素但足够稳。有个进阶做法是按 TF-IDF 权重做加权平均让“差”“退”“贵”这类情感词贡献更大。我试过几次提升在 1 到 2 个点之间可做可不做。对于现阶段的流程普通平均已经能让 SVM 学到足够多的情感信号。import numpy as np DIM 128 # 和 Word2Vec 的 vector_size 保持一致 def sentence_vector(words, model, dimDIM): vectors [] for w in words: if w in model.wv: vectors.append(model.wv[w]) if not vectors: # 整句都没有词命中时返回零向量后续要注意 OOV 问题 return np.zeros(dim) return np.mean(vectors, axis0) X np.array([sentence_vector(words, model) for words in data[words]]) y data[label].values print(特征矩阵形状:, X.shape) print(标签分布:, np.bincount(y))np.mean(vectors, axis0)的意思是假设一条评论有 10 个词每个词 128 维得到 10 行 128 列的矩阵按第 0 维求平均输出一个 128 维的句向量。axis0经常有人写错成axis1那样得到的是 10 个数字特征维度全错了——遇到X.shape对不上 SVM 输入时先查这一步。全 OOV 返回零向量的分支看起来只是个兜底实际上是个坑源。评论里如果全是表情符号或者火星文这条向量就是 128 个 0SVM 会把它当成一个特殊聚类来处理。处理办法后面避坑章节展开。4. 训练 SVM 分类器C 与 RBF 核的调参顺序和效果验证4.1 为什么这里选 SVM小样本、高维、无 GPU 也能跑到这一步手里是 5000 行 × 128 维的特征矩阵分类器选择上有几条路逻辑回归、SVM、随机森林、XGBoost、深度学习。我选 SVM 的原因很实际样本量小的时候SVM 在高维空间里找最大间隔超平面比树模型更不容易过拟合RBF 核又能处理词向量均值后的非线性边界而逻辑回归在线性边界上吃亏。随机森林在这个任务上也不是不能用但它在高维稀疏特征上容易把噪声当信号尤其词向量均值里混杂着大量弱相关的维度。神经网络在小数据集上训练不稳定同样的数据跑两轮结果可能差好几个点。SVM 的另一个好处是训练快5000 条样本上网格搜索最多百来次拟合也就几分钟的事。我一般在第一版方案里直接给 SVC 配 RBF 核而不是 LinearSVC。RBF 是 SVM 的默认选择它把特征映射到更高维空间去找边界落地时表现更稳。前提是前面做了特征标准化不然 gamma 参数会失真。4.2 搭建训练集与测试集标准化这步漏了RBF 核直接翻车SVM 对特征的尺度敏感这是我踩过最深的一次坑。Word2Vec 产出的向量每个维度基本在 -1 到 1 附近但不同维度方差不一样直接喂进 RBF 核gamma 参数实际起作用的范围会漂移网格搜索出来的最佳 gamma 很可能只在当前数据集上碰巧有效。标准化是必修课from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy # 按标签比例分层抽样小样本下必须加 ) scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)stratifyy这句的作用是保证训练集和测试集里的好评差评比例和原始数据一致。如果数据里好评占 80%差评占 20%随机切分后测试集理论上还是这个比例但样本量小时方差很大可能出现测试集里差评只有 5% 的情况评估结果完全失真。所以小样本一律加stratify。fit_transform和transform的区别是标准的训练集上fit_transform计算均值和方差测试集上只用transform。代码里已经按这个写别在测试集上重新 fit。4.3 GridSearchCV 自动找 C 和 gamma两个参数四个坑SVM 用 RBF 核时只需要调两个核心参数正则化系数 C 和核宽度 gamma。C 越大模型越倾向于把训练集每个点都分对容易过拟合C 越小边界越平滑但欠拟合风险上升。gamma 控制 RBF 核的影响范围gamma 越大样本点的影响半径越小边界越曲折gamma 越小模型越接近线性。网格搜索范围我一般这样给from sklearn.svm import SVC from sklearn.model_selection import GridSearchCV param_grid { C: [0.1, 1, 10, 100], gamma: [0.001, 0.01, 0.1, 1], } svm SVC(kernelrbf, class_weightbalanced, random_state42) grid GridSearchCV( svm, param_grid, scoringf1, # 用 f1 而不是 accuracy原因见下一节 cv5, n_jobs-1, verbose1 ) grid.fit(X_train_scaled, y_train) print(最佳参数:, grid.best_params_) print(交叉验证 f1:, grid.best_score_)这里有两个“坑”要说清楚。第一搜索范围是 4×4 共 16 组参数5 折交叉验证就是 80 次拟合数据量在几千条时完全跑得动但如果你把 C 和 gamma 各给了 10 个值就是 500 次拟合时间会拉长到半小时以上。我一般先粗后细第一轮 C 和 gamma 各给 4 个数量级跨度确定大致区域第二轮在最优值附近加密。第二交叉验证得分和你最终期望的指标要对齐。scoringf1会对每一折计算正类的 f1然后取均值。电商评论这个场景正类通常设成差评这样网格搜索直接优化的是“差评找得准不准”而不是被好评数量带偏的准确率。另一个检查点如果最佳参数落在搜索范围的边界上比如 C100 或 gamma1 正好是最优说明你的搜索范围给窄了下一轮要往外扩一个数量级重新搜。这个习惯能省很多重复实验的时间。4.4 评估别只看 accuracy差评的召回率才是电商命门模型训完先用测试集跑一遍分类报告from sklearn.metrics import classification_report, confusion_matrix y_pred grid.predict(X_test_scaled) print(classification_report(y_test, y_pred, target_names[差评, 好评])) print(confusion_matrix(y_test, y_pred))输出的分类报告里有三列指标需要读懂。精确率表示预测为差评的评论里真正是差评的比例召回率表示真实差评里被模型找出来的比例f1 是两者的调和平均。电商场景里我更看重差评的召回率——宁可把几条好评误判成差评让运营多看一眼也不能让差评悄悄漏过去。一个漏掉的差评可能意味着一天内持续产生退货损失。有个经验值供参考在 5000 条样本、128 维词向量这个配置下差评召回率做到 0.7 以上已经可以交付使用低于 0.5 时优先回去查数据预处理而不是继续调 C 和 gamma。SVM 调参对结果的提升通常在 5 个点左右预处理改一个否定词表可能直接跳 10 个点。另外默认的 SVC 是没有概率输出的。如果业务后续要接阈值过滤比如“只把置信度超过 0.8 的差评推给人工”可以给 SVC 加probabilityTrue代价是训练和预测速度明显变慢。我一般建议先不加等业务真要概率了再加。5. 情感分析实战避坑数据泄漏、OOV 词与样本不平衡5.1 词向量在切分前训练会让评估虚高现象交叉验证的 f1 到 0.9把模型部署到线上处理新评论准确率掉到 0.7 以下。原因先把全部评论拿去训 Word2Vec再切分训练集测试集。词向量在训练阶段已经见过测试集里的词和上下文SVM 的输入特征里带上了测试集的信息等于开卷考试。这不是标签泄漏但效果一样——离线指标乐观线上现原形。解决严格按“先切分、后训词向量”的顺序来。代码顺序大概是# 错误顺序词向量用了全量语料 sentences_all data[words].tolist() word2vec_all Word2Vec(sentences_all, ...) X_all build_X(word2vec_all) X_train, X_test train_test_split(X_all, ...) # 正确顺序先切分只用训练集训词向量 train_data, test_data train_test_split(data, ...) word2vec Word2Vec(train_data[words].tolist(), ...) X_train build_X(word2vec, train_data[words]) X_test build_X(word2vec, test_data[words])注意切分是发生在分词之后的但发生在 Word2Vec 训练之前。如果你的数据量到了十万条级别用全量无标注语料训词向量在工业上是常见做法但那是半监督场景这时离线评估结果只能当作上限参考不要拿去向业务方承诺线上效果。5.2 新词和 OOV 词返回零向量情感信息被悄悄吃掉现象一条明显差评“这质量真的是 yyds 反向种草”模型预测成了好评或者“踩雷”“避雷”这类词出现时预测概率明显偏向中性。原因yyds、踩雷、种草不在 Word2Vec 词表里进sentence_vector时被直接跳过如果整句里没几个词命中词表句向量就退化成少数词的均值甚至全是零向量。SVM 拿到这样的输入等于把这条评论当成一个毫无特征的样本只能猜。解决给词表留足空间把min_count从 2 改成 1同时把 3.2 里的自定义词典持续追加新词。更关键的是要识别否定结构而不是只靠单个词向量。我之前说过停用词表别删否定词但光保留还不够最好把否定词后面的几个词做上标记NEGATIVE_WORDS {不, 没, 别, 无, 莫, 未, 难, 非} def attach_negation(words): result [] for i, w in enumerate(words): if w in NEGATIVE_WORDS: # 否定词后 3 个词加上 neg_ 前缀让 Word2Vec 学到反向语义 for j in range(i 1, min(i 4, len(words))): result.append(neg_ words[j]) else: result.append(w) return result data[words] data[words].apply(attach_negation)这段代码会把“不太行”这一串分词结果[不, 太, 行]改成[不, neg_太, neg_行]。这样训练出的neg_行向量会天然靠近“差劲”“失望”这些词情感信号就不会被“行”这个正面词稀释了。注意加了neg_前缀后必须重新训练 Word2Vec因为词表变了。5.3 “默认好评”重复评论造成的数据泄漏现象测试集 f1 很高但抽样检查时发现模型对“好评”两个字的判断接近满分对陌生评论却经常出错。原因电商订单系统里大量评论是系统自动填充的“默认好评”或者同一句模板被上千个订单复用。随机切分时同一句话既进了训练集又进了测试集SVM 相当于直接背下了这句话评估时“见过”的句子太多指标自然虚高。解决切分前先去重。最直接的是按评论文本去重data data.drop_duplicates(subset[review], keepfirst)但这里有个细节如果两个用户写了一句一模一样的真实评论比如“物流很快态度很好”按文本去重会误删。更稳妥的做法是按用户 ID 或订单 ID 分组切分保证同一个用户的评论不在训练集和测试集里同时出现。数据里没有用户 ID 的话先用文本去重再人工扫一遍高频评论把明显是模板填充的句子标记出来剔除掉。5.4 好评和差评 9:1模型学会复读现象模型预测结果全是好评accuracy 高达 0.9看混淆矩阵发现差评一个都没抓住。原因电商评论天然不平衡好评占比常在 80% 以上。SVM 在训练时为了降低整体损失发现把多数类全部猜对就能拿到很高的分数于是学会了“复读”——全部输出好评。解决两层处理。第一层在模型上给 SVC 加class_weightbalanced让算法自动放大少数类的惩罚权重第二层在评估上用 f1 和差评召回率替代 accuracy 作为主要指标。代码层面只需要改一行svm SVC(kernelrbf, class_weightbalanced, random_state42)如果加了class_weight后差评召回率依然很低再考虑对差评样本做轻量过采样。注意不要直接用 SMOTE 在词向量均值上生成合成样本合成的向量可能会落在 SVM 边界内部的空白区域反而干扰分类面。更好的做法是回到文本层对差评评论做同义替换增强比如“不好”换成“太差”“不行”然后再重新提取向量。6. 把流程收成可复用脚本保存模型、增量更新与预测新评论前面的步骤都跑通后面临的问题从“怎么训”变成“怎么用”。常见做法是把 Word2Vec、标准化器、SVM 模型三个对象分别保存再写一个预测函数。千万别只存词向量文件分词器、停用词表、自定义词典这些预处理资产不一起版本化换台机器跑预测时你会后悔。import joblib from gensim.models import Word2Vec model.save(word2vec.model) joblib.dump(scaler, scaler.pkl) joblib.dump(grid.best_estimator_, svm.pkl) def predict_single(review: str): words cut_words(review) words attach_negation(words) vec sentence_vector(words, model, dim128) vec scaler.transform(vec.reshape(1, -1)) label svm.predict(vec)[0] return 好评 if label 1 else 差评 sample 包装破损客服已读不回非常失望 print(predict_single(sample))预测函数里最容易漏的是scaler.transform(vec.reshape(1, -1))这一步。训练时特征做了标准化预测时输入也必须是标准化后的向量否则 SVM 拿到的特征尺度是乱的。vec.reshape(1, -1)是把一维的 128 维向量变成 1 行 128 列因为 sklearn 的transform和predict都要求二维输入。增量更新的逻辑也要想清楚新一批人工标注评论进来后先用model.build_vocab(new_sentences, updateTrue)扩展词表再调用model.train(new_sentences, total_exampleslen(new_sentences), epochs5)做增量训练。但词向量更新后所有句向量都会变所以 SVM 必须重新训练而不是只更新 Word2Vec。这个流程没法完全自动化我的习惯是每两周做一次增量重训并把新出现的业务词追加进自定义词典。验证方面我常用一个笨方法随机抽 100 条新评论人工标注后和模型预测结果比对把错误案例回填到训练集。回填时只看 10 条左右就够了重点挑那些模型“差评漏报”的样本。我之前因为只保存了.kv词向量文件而丢了分词器和停用词表换机器后花了半天重写预处理脚本后来把整个预处理链路的配置用 JSON 存了一份再没有翻过车。希望你也能把这套流程变成自己的标准工具箱少走这些弯路。本文还有配套的精品资源点击获取