
简介自然语言处理中的情感分析任务旨在让计算机自动判断文本表达的情绪倾向是舆情监测、用户反馈挖掘等场景的关键技术基础。在电商领域商品评论中蕴含着大量关于质量、物流、服务等方面的真实用户感受仅依赖系统自带的评分标签往往无法捕捉细节差异。情感分析通过文本分类技术能有效区分正向、负向与中性评价并输出置信度供业务决策。实现路线通常从TF-IDF结合逻辑回归的经典基线开始其可解释性强、训练高效适合快速验证进一步可引入TextCNN等深度学习模型利用卷积核捕捉局部语义组合在短文本评论上取得更优效果。该技术已广泛应用于电商评论分析、舆情预警、客服工单分类等场景。本文围绕一套完整的中文商品评论情感分析项目系统梳理了数据清洗、分词、特征构建、模型训练与评估的全流程实践并提供可复用的代码与数据集。 先说说这个项目是哪来的吧。做电商运营那阵子每天要跟一堆商品评论打交道光是看“好评差评”的标签远远不够用户真正吐槽的点、表扬的点全都埋在自由文本里。后来我想着与其等人力去逐条标注不如自己动手做一个“能自动判断评论情感倾向”的小系统。这个中文商品评论情感分析项目就是从那时候开始攒起来的。整个项目包含完整的源代码、可复用的数据集、训练脚本和评估脚本把评论数据从清洗、分词、建模到预测的完整链路走了一遍适合想入门NLP实战、或者需要给商品评论做舆情监测的朋友直接参考。这套项目的核心目标很明确输入一段中文商品评论文本模型输出它属于正向、负向还是中性或者细分为好评/差评并给出置信度。除了源码之外我还把整理过、去重过、标注过的数据集一起打进了压缩包方便你拿到手就能跑通不需要再辛苦四处找数据。下面我把整个项目的设计思路、数据准备、模型实现、训练评估以及实际踩过的坑都梳理一遍希望能帮你省掉几个晚上的摸索时间。1. 项目整体设计与思路拆解1.1 业务场景与核心需求先回到最开始的业务问题。商品评论的典型量级是多少我看过不少店铺后台的数据一个爆款链接的评论数少则几千条多则十几万条。用户评论里除了“好评”“差评”这种系统默认标签更多的是一两句真实反馈比如“物流很快但包装压扁了”“屏幕清晰就是电池不耐用”。这类评论往往同时包含正面和负面信息单靠标签判断会漏掉很多信号。所以这个项目的第一个设计原则是不做简单的两极分类而是用“正向、负向、中性”三分类来做情感极性判断。这样既不丢失中性表达比如“一般般”“还能用”也能为后续精细化分析留出余地。第二个原则是给出置信度模型输出要带概率值这样下游系统可以设置阈值置信度低的自动转人工复核。1.2 技术选型规则、机器学习还是深度学习情感分析的技术路线常见有四种基于情感词典的规则方法、传统机器学习TF-IDF加分类器、深度学习序列模型TextCNN/BiLSTM、预训练语言模型BERT/ERNIE。我在这套项目里是做了两条路线的对比一套用朴素贝叶斯/逻辑回归跑TF-IDF特征另一套用TextCNN做嵌入训练。为什么这么选纯词典规则方法实现最快但是中文商品评论的口语化表达、网络新词非常多比如“绝绝子”“yyds”词典永远跟不上而且规则没法处理否定词范围“不是不好”到底是好还是不好。传统机器学习从TF-IDF特征开始能覆盖词频和文档频率信息适合做baseline代码跑起来非常快CPU就能搞定对想快速上手的同学很友好。TextCNN是深度学习入门的经典模型结构简单、训练速度快在短文本分类上有稳定表现——评论正是典型的短文本TextCNN的信息抽取能力足够用。BERT效果最好但需要GPU环境显存要求也高对源码开源和易复现不太友好。最终我把重心放在传统机器学习基线和TextCNN两个方案上两者共享同一套数据预处理代码你可以随时切换模型来对比效果这是写代码时我有意保留的“性价比最高”的结构。1.3 项目文件结构与代码组织压缩包解开之后目录是这样的sentiment-analysis/ ├── data/ │ ├── raw/ # 原始采集评论 │ ├── processed/ # 去重、清洗后的数据 │ ├── train.csv # 训练集 │ ├── valid.csv # 验证集 │ └── test.csv # 测试集 ├── models/ # 训练好的模型权重和词表 ├── src/ │ ├── __init__.py │ ├── preprocess.py # 文本清洗与分词 │ ├── features.py # TF-IDF / word2vec特征构建 │ ├── train_lr.py # 逻辑回归训练脚本 │ ├── train_nb.py # 朴素贝叶斯训练脚本 │ ├── train_cnn.py # TextCNN训练脚本 │ ├── predict.py # 单条/批量预测 │ └── evaluate.py # 评估指标输出 ├── requirements.txt └── README.md这种组织方式是我在多个项目里验证过的习惯数据、模型、源码分离训练和预测解耦。evaluate.py单独拎出来避免每次训练完都要重新copy评估逻辑后面调阈值、做badcase分析都会方便很多。2. 数据集准备与处理2.1 数据来源与采集原则数据集是这类项目最关键的资产之一。我在压缩包里附带的数据集并不是从某个单一渠道抓来的而是综合了三类来源公开的中文情感分析语料类似ChnSentiCorp这类公开用途的评论数据爬取的公开电商评论数量较少仅用于扩充语料多样性采集时严格遵守网站条款并只保留非敏感内容我自己做标注的补充样本主要针对一些网络新词和商品领域词汇这里要特别说一句如果你打算自己采集电商评论务必先确认目标平台的服务条款有些平台明确禁止爬虫。公开的数据集加上自己手工标注少量数据通常是性价比最高、风险最低的组合。数据合规这一点别存侥幸心理。2.2 数据清洗与去重原始语料中最常见的问题有几类网页HTML残留、重复评论用户复制粘贴或数据采集重复、异常标点、乱码、无效短文本。清洗逻辑我写在preprocess.py里核心步骤是这样的import re import pandas as pd def clean_text(text: str) - str: # 去HTML标签 text re.sub(r.*?, , text) # 去URL text re.sub(rhttp\S|www\.\S, , text) # 去不可见字符 text re.sub(r[\x00-\x1f\x7f], , text) # 全角转半角简版 text text.replace( , ).replace(, ,).replace(。, .) # 合并多余空格 text re.sub(r\s, , text).strip() return text去重这块有个容易被忽略的细节不是简单的drop_duplicates()就完事评论经常是“同一个意思但字面略有不同”比如“很好用”和“好用很好”所以我额外做了一步基于哈希的近似去重对每条评论计算文本的char-level n-gram哈希再用相似度阈值去重。这一步能有效减少训练集里的重复噪声防止模型对高频套话过拟合。清洗之后还做了长度过滤低于2个字符的评论视为无效比如“好”、“差”这类极短文本信息量太低且容易造成标签噪声超过200个字符的长文本也会被单独处理因为这类通常是复制粘贴的营销内容或说明书式评论与真实用户体验差别较大。2.3 标注策略与标签分布我使用的是三分类标签0表示负向1表示中性2表示正向。标注策略上有一条非常重要先定规则再标注最后审核。规则尽量简单出现明显正向词满意、完美、推荐、超值且无否定词修饰 - 正向出现明显负向词差劲、失望、退货、垃圾且无否定词修饰 - 负向既无明显正向也无明显负向或同时有正负向表达 - 中性“虽然...但是...”这类转折句以后半句的情感为准实际标注下来的分布大概是正向约60%负向约24%中性约16%。这个分布其实符合电商评论的真实情况——大部分人只会在体验特别好或特别差的时候写评论中性表达占比天然偏低。如果你发现自己的数据集分布差异极大建议先做下采样或者调整损失函数里的类别权重避免模型变成“全预测正向”的懒汉。2.4 训练集/验证集/测试集划分我采用的划分比例是8:1:1并且刻意使用了分层抽样train_test_split(stratifyy)。为什么强调分层因为三分类分布本身就不均衡如果随机划分很可能验证集里负向样本特别少导致评估结果虚高或偏低不容易发现模型在少样本类别上的问题。划分时还注意了一个点同一商品的评论尽量放到同一个集合中。网上很多教程直接随机切分会带来标签泄漏的风险——同一商品的相似评论可能同时出现在训练集和测试集里模型在测试集上的表现虚高真实场景上线的效果会明显缩水。这里我按商品ID做了分组切分虽然会让数据利用变“浪费”一点但评估结果更可信。3. 核心实现细节与要点3.1 中文分词与停用词中文情感分析绕不开分词。我用的分词方案是jieba但在实际项目中光有默认词典不够。商品评论里有很多品牌名、型号、网络新词比如“徕芬”“吹风机X3”“绝绝子”默认词典根本分不对。所以项目里增加了一个自定义词典user_dict.txt格式很简单一行一个词可以带词频和词性徕芬 10 nz 绝绝子 5 nz 品控 10 n 踩雷 10 v加载方式import jieba jieba.load_userdict(src/user_dict.txt) text 这款吹风机绝绝子品控在线 words jieba.lcut(text) # [这款, 吹风机, 绝绝子, , 品控, 在线]停用词表我也单独维护了一份stopwords.txt除了“的、了、是”这类常见停用词还加上了评论里出现频率高但无情感含义的词比如“东西”“感觉”“整体”“就是”。保留这些词会稀释特征维度还会让TF-IDF的权重偏向无意义词。需要注意不要一股脑把所有标点都去掉尤其是感叹号“”和省略号“...”它们在评论情感里往往有强化信号本文里“”出现的评论通常是强情绪表达这个信息值得保留到特征中。3.2 TF-IDF特征与逻辑回归基线传统路线的特征我用TF-IDF。具体做法是from sklearn.feature_extraction.text import TfidfVectorizer vectorizer TfidfVectorizer( token_patternr\S, max_features50000, ngram_range(1, 2), min_df2, max_df0.8, ) train_features vectorizer.fit_transform(train_corpus)这里有两个关键参数值得展开说说。第一是ngram_range(1, 2)。单字/单词的TF-IDF丢失了词序信息“不好”和“不 好”在分词后都包含“不好”这个词影响不大但“不是很好”这种表达word ngram2-gram会同时捕捉“不是”和“很好”的组合对否定语义很有帮助。我实际试过只用unigram逻辑回归在验证集上的F1值低大约2个百分点加了bigram之后明显回升。第二是max_df0.8。这个参数的作用是过滤掉在80%以上文档中都出现的词比如“这个”“东西”这些词对分类几乎无贡献却会干扰模型的判断。min_df2则过滤只在1条评论里出现的生僻词避免模型记忆噪声。分类器方面逻辑回归是我首选的baseline稳定性好、可解释性强。朴素贝叶斯我也放了训练脚本但经验是在评论这种内容高度重叠的语料上朴素贝叶斯的条件独立性假设太强效果通常比逻辑回归差3到5个百分点所以实际应用我更推荐逻辑回归朴素贝叶斯可以用来做快速pipeline的验证。3.3 TextCNN模型的实现思路深度学习的路线上TextCNN是一个非常经典的选项。网上TextCNN的代码很多但把细节写清楚并和完整训练流程串好的不多。我实现的网络结构是这样的import torch import torch.nn as nn class TextCNN(nn.Module): def __init__(self, vocab_size, embed_dim128, num_class3, filter_sizes(2, 3, 4), num_filters128): super().__init__() self.embedding nn.Embedding(vocab_size, embed_dim, padding_idx0) self.convs nn.ModuleList([ nn.Conv2d(1, num_filters, (k, embed_dim), padding(k // 2, 0)) for k in filter_sizes ]) self.dropout nn.Dropout(0.5) self.fc nn.Linear(len(filter_sizes) * num_filters, num_class) def forward(self, x): x self.embedding(x) # (batch, seq_len, embed_dim) x x.unsqueeze(1) # (batch, 1, seq_len, embed_dim) conv_outputs [] for conv in self.convs: conv_out conv(x).relu() # (batch, num_filters, seq_len, 1) pool_out nn.functional.max_pool1d( conv_out.squeeze(-1), conv_out.size(2) ) # (batch, num_filters, 1) conv_outputs.append(pool_out.squeeze(-1)) x torch.cat(conv_outputs, dim1) # (batch, total_filters) x self.dropout(x) return self.fc(x)几个容易踩坑的细节必须用padding_idx0对应词表里的pad符号这样补零的位置不会参与训练。卷积核大小选2/3/4对应短文本中的bigram、trigram、4-gram这正好覆盖情感词常见的组合模式。max pooling之后特征维度固定不依赖句子长度这点对批量训练非常关键。Dropout设在池化之后、全连接之前而不是放在Embedding后实测对防止过拟合效果更好。3.4 预训练词向量的选择与处理Embedding层可以随机初始化也可以载入预训练词向量。我测试过两种方式随机初始化和使用腾讯开源的Tencent AI Lab中文词向量原谅我直接这么叫它确实是在中文NLP里很大众的预训练向量。结果很清晰在数据量较小1万条左右的情况下使用预训练词向量让F1值提升了4个百分点。这不是魔法而是因为预训练词向量包含了大规模语料的语义信息模型不需要从零学习“性价比”和“划算”在语义上接近。但用预训练词向量有个坑词表匹配。如果只保留Embedding层中词表覆盖到的词很多在向量表里没有的评论新词会被直接映射成unk效果反而下降。我的处理方式是词表里包含训练集所有词一部分词加载预训练向量未覆盖到的词用较小的随机均匀分布初始化并在训练过程中微调整个Embedding层。epochs设3到5个即可太小训练不充分太大容易过拟合。4. 实操过程与核心环节跑通4.1 环境准备与依赖安装先把环境搭好。项目requirements.txt内容大致如下jieba0.42.1 pandas1.5.3 scikit-learn1.2.2 torch1.13.1 numpy1.24.3安装命令pip install -r requirements.txtCPU版本PyTorch也能跑训练。我所有实验都在MacBook的CPU上完成1万条评论、50个epochTextCNN大概只需要5分钟这个量级根本不需要GPU。如果你用GPU记得把to(cuda)相关的条件判断代码保留好train_cnn.py里已经写好了torch.device(cuda if torch.cuda.is_available() else cpu)的逻辑。4.2 数据预处理执行流程打开项目后建议按这个顺序跑cd sentiment-analysis python src/preprocess.py --input data/raw --output data/processed这一步会读取原始数据清洗、去重、分词并生成三个文件train.csv、valid.csv、test.csv。每行包含label和text两列text是已经分好词的文本用空格分隔。手动检查一下生成的数据这是很多教程不会强调但我强烈建议做的步骤。打开train.csv快速浏览200条样本确认以下几点标签是否和文本内容匹配是否还有未清洗掉的异常字符分词结果是否符合常识比如“不 好吃”是否被正确切分这一步花10分钟能让后面训练少走很多弯路。我见过不少人直接跳过去跑代码最后模型指标莫名低回查数据才发现清洗环节就有问题。4.3 运行逻辑回归基线传统机器学习路线的训练命令很简单python src/train_lr.py --train data/processed/train.csv --valid data/processed/valid.csv脚本内部会先做TF-IDF向量化然后训练逻辑回归最后输出验证集上的准确率、精确率、召回率和F1。我用的是solverliblinear因为在小数据集上它比lbfgs收敛更快多分类问题也支持。正则化强度C我默认设成1.0实际调参时可以在0.1~10之间搜索这个参数对结果影响比较明显网上很多代码直接写死我特意在脚本里留了--C命令行参数方便调。4.4 训练TextCNN模型的完整过程TextCNN的训练入口是python src/train_cnn.py \ --train data/processed/train.csv \ --valid data/processed/valid.csv \ --epochs 30 \ --batch_size 64 \ --lr 0.001 \ --embed_dim 128训练过程中每轮都输出验证集F1并保存效果最好的模型到models/best_cnn.pt。这里有个很实际的调参经验不要只盯训练集loss你会发现训练集loss一路下降但验证集F1在某个epoch开始震荡退化。这时模型正在过拟合。我观测到的典型曲线是前8到10个epoch验证集F1稳步上升到15个epoch左右达到峰值超过20个epoch后验证集F1开始缓慢下降。所以最终我采用的早停策略是patience5即验证集指标连续5个epoch不增长就停止训练并回滚到最优模型。优化器方面我只用了Adam初始学习率0.001。这个组合在实际项目中非常稳定不需要过多调试。如果你用的是SGD那需要warmup和更细致的学习率调度对新手不友好没必要。4.5 评估指标详解与结果对比评估脚本输出这样几类指标准确率Accuracy整体预测正确的比例。精确率Precision预测为正类的样本中真的为正类的比例。召回率Recall实际为正类的样本中被正确预测的比例。F1值精确率和召回率的调和平均。在不均衡分类的场景下准确率是最有迷惑性的指标。如果负向样本只占24%模型全部预测为正向准确率也有76%看起来很“不错”但实际上负向样本一个都没识别出来。所以我在evaluate.py里额外输出每个类别的报告并使用classification_report这种格式方便逐类排查。我跑出来的最终结果大概是这样的模型准确率宏平均F1训练耗时(CPU)TF-IDF 朴素贝叶斯0.8630.81510秒TF-IDF 逻辑回归0.8910.85220秒TextCNN 预训练词向量0.9180.8955分钟注意我用的宏平均F1即三个类别的F1取平均这样能更公平地反映模型在少数类中性上的表现。TextCNN的优势在中性类上体现得最充分它比逻辑回归多了大约6个百分点因为中性评论往往含有混合情感需要捕捉短语级语义。4.6 预测模块与结果落库训练完成后预测模块提供了两种使用方式# 单条评论 python src/predict.py --model models/best_cnn.pt \ --text 物流很快但包装盒有点压扁了 # 批量预测csv python src/predict.py --model models/best_cnn.pt \ --input data/new_comments.csv --output data/predict_result.csvpredict.py的输出会包含三列原始文本、预测标签、置信度。置信度我取的是softmax概率的最大值。生产环境里我一般会设定阈值置信度低于0.6的预测系统判定为“待人工复核”。这样能大幅降低误判带来的运营风险尤其是差评漏检这种高成本错误。用户评价情感分析项目的价值并不仅仅在于训练了一个模型而是能够把预测结果以结构化的方式落到业务系统里按商品维度统计差评率、按时间维度观察情感趋势、按属性维度物流、质量、服务聚类问题这些下游应用才是情感分析真正发挥价值的地方。5. 常见问题与排查技巧实录5.1 分词结果不对怎么办最常遇到的问题就是分词不理想。比如“充电线”被切成“充电”和“线”“不咋地”被切成“不”和“咋地”。遇到这种情况先不要急着改算法最简单的方式就是往自定义词典里加词。词频参数一般写5~10词性可以留空。我维护的user_dict.txt已经包含了大量商品评论常见词但每个领域都有自己的黑话你跑自己的数据时先提取高频词人工扫一遍把明显有领域含义的词补进去效果立竿见影。如果加词还不够再考虑引入jieba.suggest_freq调整个别词的词频。这个方法在教程里提到得不多但实际很好用jieba.suggest_freq(充电线, True)这样jieba在切分时会更倾向于把“充电线”识别为一个整体。5.2 训练时报维度不匹配的错误这个错误很多人遇到过尤其是改了embed_dim或者filter_sizes之后。TextCNN里卷积核的宽度必须等于Embedding维度也就是Conv2d(1, num_filters, (k, embed_dim))中的embed_dim与nn.Embedding(vocab_size, embed_dim, padding_idx0)的embed_dim要保持一致。如果你只修改了其中一处另一处没改就会报维度错误。排查思路是检查三处是否同步Embedding层的embed_dim卷积层的kernel_size第二个值预训练词向量的向量维度如果加载的话这是一个细节活但也最容易通过报错信息定位不用慌。5.3 模型总是预测为同一个类别模型整体预测集中在一个类别、其他类别识别不出来大概率是标签分布不平衡导致的。解决办法有几种类别权重在CrossEntropyLoss中传weight参数给少数类更高的权重。过采样/欠采样对少数类过采样但要注意避免直接复制同样文本导致过拟合可以用SMOTE算法生成合成样本。收集更多少数类数据最朴素但也最有效找更多中性评论让模型看到更多“模棱两可”的真实表达。我在项目里默认已经给CrossEntropyLoss加了跟类别频率成反比的权重这样即使不调参结果也不会太难看。5.4 预测结果置信度整体偏低置信度普遍在0.5到0.7之间没有0.9以上的高置信样本说明模型不够“自信”。这种情况先看是不是训练轮数太少或者数据量太小。如果数据量只有几千条这种现象很常见加大数据量或使用预训练模型都能改善。还有一个原因是中文评论里很多表达本身就模糊。比如“还行”到底算中性还是正向“价格便宜但是质量一般”算正还是负如果是这类问题低置信度说明模型捕捉到了文本里的矛盾信号这是合理的不该强行提高。5.5 数据文件里出现乱码我打包的数据集统一采用UTF-8编码但如果你自己采集的数据是GBK或GB2312那么pandas.read_csv默认按UTF-8解析会乱码。处理方式是在读取时指定encodinggb18030这个编码兼容GBK且覆盖更多汉字比gbk更稳df pd.read_csv(data/raw/comments.csv, encodinggb18030)另外尽量在数据入库阶段就统一编码别在清洗阶段来回转码转来转去容易出问题。结语与个人经验分享这套项目从整理数据到跑通模型前后迭代了差不多两周。我踩过的最大一个坑是一开始直接拿原始评论做训练没做垃圾评论过滤结果模型把“有奖好评返现”这类营销套话学成了强正向信号上线后一堆垃圾评论被标成高置信好评整个指标虚高得离谱。后来在预处理阶段加入“疑似广告评论”过滤规则并根据置信度做人工抽检才把这个问题压住。所以最后想跟你认真说一句数据清洗和数据合规的重要性怎么强调都不过分。如果你打算把这个项目继续往深做我建议下一步可以尝试引入BERT等预训练模型或者把单条评论的情感判断升级为“情感属性”的联合抽取比如识别出“物流慢”和“质量好”分别对应哪些商品属性。这样从情感分析走向细粒度的用户反馈挖掘才是这类系统真正值钱的地方。希望这些代码和踩坑记录能帮你少走点弯路。本文还有配套的精品资源点击获取