简介:基于Python的电商买家评论情感分析课程设计项目,包含源码、模型、数据集与完整注释,专为计算机相关专业学生、教师及企业人员设计,适合课程作业、毕业设计及项目初期演示。代码已测试可稳定运行,答辩平均分达94.5,包含从数据爬取、预处理、情感打分到可视化展示的完整链路。
压缩包共1379个文件,大小约53.95MB,主要类型为478个Python脚本、237个csv数据集、178个txt说明、125个html页面及多个Jupyter Notebook与虚拟环境配置,各类型文件按功能分层存放,便于修改和二次开发。数据涵盖不同品牌和年份的电器评论,含正面/负面标注,并针对评论分数与内容不吻合、外观等特征关键词做了专门处理。同时提供了需求文档、接口文档、变更记录、流程图等开发文档,以及config、decorator、日志等工程化设计思路。
目前已有1120人学习下载,适合需要快速启动情感分析项目、学习完整工程规范或进行算法对比优化的开发者参考。
1. 打开这份电商评论情感分析课程大作业,先别急着跑代码
如果你拿到的是「基于python的电商买家评论数据情感分析源码+模型+数据集+代码注释(课程大作业).zip」这类压缩包,第一反应多半是解压、装依赖、跑一遍、改几个参数交差。但我见过太多人卡在同一个地方:训练集里好评占了九成,模型在测试集上的准确率却有 0.93,一到答辩被问“为什么负面评论的召回率只有 0.2”就答不上来。问题根本不在模型选得不够新,而在数据划分、类别平衡和训练预测一致性这些基本功上。
这个标题背后其实是四件事:源码是流程骨架,模型是训练产物,数据集是原材料,代码注释是给答辩用的“话术提示”。适合正在做课程大作业、需要一份能自圆其说方案的人。接下来我按自己的习惯,从解压目录开始讲到演示脚本,把每步的参数、边界和坑都摊开说。
2. 从压缩包到可复现环境:目录结构、依赖与数据格式约定
2.1 先看目录和文件清单:源码、模型、数据集、注释各在哪
拿到压缩包先解压,然后别急着双击 README,先打开文件管理器看一遍顶层目录。一份结构清晰的课程大作业,通常长这样:
| 路径 | 内容 | 复用方式 |
|---|---|---|
| data/reviews.csv | 原始评论数据,含评论文本和情感标签 | 直接读,不要改格式 |
| data/stopwords.txt | 中文停用词表 | 按需追加行业词 |
| models/sentiment_lr.joblib | 训练好的逻辑回归模型 | 预测时加载 |
| models/tfidf_vec.joblib | 对应的 TF-IDF 向量化器 | 必须与模型成对加载 |
| src/config.py | 路径与超参数集中管理 | 训练预测统一引用 |
| src/data_clean.py | 文本清洗与分词 | 训练和预测必须共用同一份清洗函数 |
| src/train.py | 模型训练入口 | 重新训练时使用 |
| src/predict.py | 单条预测入口 | 演示时使用 |
| requirements.txt | 第三方依赖列表 | 建虚拟环境用 |
常见做法是让训练脚本把向量化器和模型一起保存,因为这两者像钥匙和锁的关系。只看models/目录的话,你分不清哪个 pkl 是模型、哪个是向量器,好在文件名里写了tfidf_vec和sentiment_lr这种区分词。如果你拿到的压缩包没有这种区分,我一般建议先joblib.load打印一下对象类型,避免预测时张冠李戴。
2.2 两个必须手工确认的文件:requirements.txt 和原始数据集
解压后我做的第一件事是看requirements.txt里有没有锁版本号,再看数据集第一行是中文还是英文表头。先说前者,很多作业压缩包里的依赖写得很松,比如只写了sklearn、jieba,这本身没问题,但如果你直接pip install -r requirements.txt装到系统 Python 里,大概率会和别的项目打架。
我一般会新建一个虚拟环境,整个环境只服务这一次训练:
python -m venv venv # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate pip install -r requirements.txt python src/train.py这里的venv是虚拟环境名,可以随意改。装依赖这步如果报错,多半是网络问题或者某个包只支持特定 Python 版本,我建议先python --version确认解释器版本,再对照requirements.txt里的说明逐个装。别用pip install --upgrade pip顺手升级,在课程作业阶段没必要。
再说数据集。电商评论数据常见的格式是 CSV,表头一般有review和label两列,label要么是0/1(负面/正面),要么是1/2/3或1~5星。我拿到数据后会先快速过一遍最原始的几行,确认有没有乱码、缺值和标签错位。这一步是为了避免后面训练时才发现文本和标签对不上,回头查数据的成本比重新训练高得多。
2.3 用虚拟环境跑通最小命令
环境装好后,跑训练脚本之前先做一次“冒烟测试”:只加载数据、清洗一列、看输出,不训练。我习惯在命令行里直接跑一个短命令验证链路:
python -c " import pandas as pd from src.data_clean import clean_text from src.config import DATA_PATH df = pd.read_csv(DATA_PATH, encoding='utf-8-sig') print(df.shape) print(df.head(3).to_string()) print(clean_text(df['review'].iloc[0])) "这段代码里做了三件事:读取原始数据、打印前几行、对第一条评论做清洗。如果这条命令能跑通,说明数据路径、编码和清洗函数三个环节都没断。如果clean_text里用到了自定义词典或停用词表,这步同时验证了config.py里的路径是否相对当前目录解析正确。很多人翻车在路径上:训练脚本用相对路径时没问题,换成从项目根目录启动就找不到data/,所以我习惯把路径写进config.py,并且所有脚本都从项目根目录启动。
注意:如果
pd.read_csv报UnicodeDecodeError,先试试把encoding换成utf-8-sig。这个问题我在第 5 章专门展开。
3. 电商评论文本预处理:jieba 分词、停用词与短文本策略
3.1 为什么清洗顺序影响最终分数
电商评论文本和新闻语料不一样,它短、口语化、错别字多、还夹杂表情符号和平台专用词。“衣服质量不错,就是尺码偏小,客服态度一般,物流给力”这种话,如果直接扔给 TF-IDF,会被切成一堆低频词,向量的稀疏程度高到模型记不住关键信号。
我常用的清洗顺序是:去链接、去 HTML 标签、统一小写、去特殊符号、分词、去停用词、按语料情况过滤长度。顺序不能乱,因为jieba.cut对特殊符号敏感,像“666”“!!!”这种字符串会干扰分词结果。先做字符级清理,再做词级过滤,效果稳定。
import re import jieba def load_stopwords(path): with open(path, "r", encoding="utf-8") as f: return set(w.strip() for w in f if w.strip()) def clean_text(s, stopwords, min_len=2): s = s.lower() s = re.sub(r"https?://\S+|www\.\S+", "", s) # 去掉URL s = re.sub(r"<[^>]+>", "", s) # 去掉HTML标签 s = re.sub(r"[^\u4e00-\u9fa5a-zA-Z0-9!?,。]", " ", s) # 只留中文、英文、数字和基础标点 words = jieba.lcut(s) # 精确模式分词 words = [w for w in words if w.strip() and w not in stopwords] words = [w for w in words if len(w) >= min_len] return " ".join(words)这段代码里有几个值得说明的参数。min_len=2表示只保留长度不低于 2 的词,目的是过滤“的”“了”“啊”这种单字虚词,但当停用词表足够好时,这个参数的作用就没那么大,我通常保留它作为兜底。jieba.lcut返回的是 list,比jieba.cut直接返回生成器更适合后续加工。正则的范围[^\u4e00-\u9fa5a-zA-Z0-9!?,。]会把英文逗号、分号、引号全换成空格,所以评论里的“但是;然而”会变成“但是 然而”,分词会更碎,但对情感分析影响不大。
3.2 jieba 分词参数:精确模式与自定义词典
jieba.lcut(s)默认走精确模式,这也是课程作业最稳妥的选择。搜索引擎模式会在精确分词结果上补充长词,产生冗余,比如“客服态度”在搜索引擎模式里可能同时出现“客服”和“态度”,会增加向量维度和噪音。
真正值得花时间的是自定义词典。电商评论里有一类词是通用词典分不好的,比如“性价比”“回购”“好评返现”“踩雷”,如果不加自定义词,会被切成“性价/比”“回/购”这种错误组合,后面做特征分析时看不到这些强信号词。常见做法是在项目里放一个userdict.txt,每行一个词:
性价比 5 n 回购 5 v 好评返现 5 n 踩雷 3 v然后加载它:
jieba.load_userdict("data/userdict.txt")5是词频,n/v是词性。这个文件的作用是让 Jieba 优先把这些词当成整体。我一般只在清洗函数里加这一行加载,不需要在训练脚本里重复加载。如果你的评论里有明显的领域黑话,比如美妆类的“搓泥”、数码类的“掉电快”,把它们加进用户词典的效果比微调模型超参数还明显。
3.3 停用词表不能一刀切:不要误删“不”“没”“太”
停用词表是文本预处理里最大的隐藏炸弹。网上流传的中文停用词表动辄上千词,里面常有“不”“没”“太”“很”“都”这种词。如果直接套用,情感分析等于废了,因为“不好”“不行”“不太满意”里的“不”被删了,剩下的词全是正面词,模型会把负面评论错分成正面。
判断停用词表是否合理,我有个土办法:清洗完文本后,随机抽 20 条评论看清洗结果。如果“不好”变成“好”,“没用”变成“用”,那就说明停用词表里误删了否定词或转折词,需要把“不、没、太、但、却”这类词从停用词表里摘掉。
一个安全的做法是把停用词表拆成“基础停用词”和“情感相关弱停用词”两部分,基础停用词表保留“的、了、也、就、都”这种纯结构词,情感相关词不加入:
# 以下词不要出现在停用词表里 不 没 太 但 却 很如果你不确定当前停用词表里有没有这些词,写一段校验代码很容易露馅:
stopwords = load_stopwords("data/stopwords.txt") danger = {"不", "没", "太", "但", "却"} print("危险词被包含:", sorted(danger & stopwords))输出为空才说明停用词表及格。这一步在答辩时也是一个很好的加分点,表明你处理过否定词的边界问题。相比之下,忽略停用词、直接分词然后全保留,反而不会出现这种系统性的方向性错误。
3.4 短文本与无效评论的取舍
电商评论里大量存在“好的”“不错”“还行”“已收到”这种 2~4 个字的短文本,以及“!!!!”“无”“。”这种无意义文本。前者虽然有情感倾向,但信息太少,模型学不到什么稳定特征;后者纯粹是噪音。
我一般设MIN_TEXT_LEN = 2,在config.py里把清洗后分词数量少于 2 的样本直接从训练集剔除。注意这里统计的是清洗后的词数,而不是原始字符串长度,因为“哈哈哈”分词后只有“哈哈哈”一个词,信息量依然很低。
对于 2~4 字的有效短文本,比如“不好”“好用”“太慢了”,我的建议是保留,并且不额外做特殊处理。理由这类短文本在真实评论中占比不小,删掉会进一步放大类别不平衡问题。如果答辩时有人问“为什么不按长度过滤掉所有短文本”,你可以回答:短文本里有强情感信号,保留能让模型在极端短句上也有判断力,真正需要过滤的是清洗后只剩无效词的那部分。
4. 特征与模型选型:TF-IDF 基线、Word2Vec 进阶与超参数调法
4.1 基线:TF-IDF + 朴素贝叶斯或逻辑回归,为什么是稳妥起点
课程大作业最怕的不是分数不够高,而是不知道当前结果到底算好还是差。用 TF-IDF 加线性模型作为基线,好处是训练快、可解释、不容易翻车,而且它能给你一个明确的参照系:后续换深度模型,如果涨点不明显,说明问题在数据而不在模型。
我常用的是 TF-IDF + 逻辑回归,而不是朴素贝叶斯。逻辑回归的C参数可调,配合class_weight="balanced"对类别不平衡有直接帮助,朴素贝叶斯在这两点上控制力较弱。
from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split, GridSearchCV import joblib import pandas as pd from src.data_clean import load_stopwords, clean_text from src.config import RANDOM_STATE sw = load_stopwords("data/stopwords.txt") df = pd.read_csv("data/reviews.csv", encoding="utf-8-sig") df["clean_review"] = df["review"].apply(lambda x: clean_text(x, sw)) # 先切分再fit,避免测试集信息提前进入向量化器 X_train, X_test, y_train, y_test = train_test_split( df["clean_review"], df["label"], test_size=0.2, stratify=df["label"], random_state=RANDOM_STATE ) vec = TfidfVectorizer( min_df=2, max_df=0.8, ngram_range=(1, 2), sublinear_tf=True ) X_train_vec = vec.fit_transform(X_train) X_test_vec = vec.transform(X_test) clf = LogisticRegression(C=1.0, max_iter=1000, class_weight="balanced") clf.fit(X_train_vec, y_train) joblib.dump(vec, "models/tfidf_vec.joblib") joblib.dump(clf, "models/sentiment_lr.joblib") print("train acc:", clf.score(X_train_vec, y_train)) print("test acc:", clf.score(X_test_vec, y_test))代码里有几个参数需要专门说明。min_df=2表示至少在 2 条评论里出现过的词才保留,用于过滤低频噪音;max_df=0.8表示在超过 80% 的样本里出现的词会被忽略,这类词往往是“可以”“东西”“快递”这种没有情感区分度的词;ngram_range=(1, 2)同时保留单个词和相邻双词,比如“不好吃”里“不好”和“好吃”是两个方向相反的 bigram,保留后模型能利用局部搭配信息。sublinear_tf=True用1 + log(tf)替代原始词频,弱化高频词的压倒性影响。
class_weight="balanced"是应对好评远多于差评时的主要手段。它的作用不是让样本数量变平均,而是让少数类的误分类代价更高,模型会更愿意把不确定的样本预测成少数类。如果不用这个参数,逻辑回归很可能把所有样本都预测成多数类,训练分数很好看,但混淆矩阵里某一个类别的召回率是 0。
4.2 进阶:Word2Vec + LSTM 的跑法与参数
如果你的课程要求里写了“深度学习模型”,那 TF-IDF 只能算半程,后半程通常要上 Word2Vec 加 LSTM。常见流程是先训练或加载 Word2Vec 词向量,把评论中的每个词映射成一个向量,再用 LSTM 编码序列,最后接一个全连接层输出二分类概率。
from gensim.models import Word2Vec from tensorflow.keras.preprocessing.text import Tokenizer from tensorflow.keras.preprocessing.sequence import pad_sequences from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Embedding, LSTM, Dense, Dropout # 1. 训练Word2Vec,窗口取5,向量维度取100 w2v = Word2Vec(sentences=[r.split() for r in X_train], vector_size=100, window=5, min_count=2, workers=4) word_vectors = {w: w2v.wv[w] for w in w2v.wv.index_to_key} # 2. 序列化 tokenizer = Tokenizer(num_words=20000, oov_token="<UNK>") tokenizer.fit_on_texts(X_train) X_train_seq = pad_sequences(tokenizer.texts_to_sequences(X_train), maxlen=100) X_test_seq = pad_sequences(tokenizer.texts_to_sequences(X_test), maxlen=100) # 3. 建嵌入矩阵(课程作业可以直接用Embedding层随机初始化,效果差不了太多) embedding_matrix = np.zeros((min(20000, len(tokenizer.word_index)) + 1, 100)) for word, idx in tokenizer.word_index.items(): if word in word_vectors and idx < 20000: embedding_matrix[idx] = word_vectors[word] model = Sequential([ Embedding(input_dim=min(20000, len(tokenizer.word_index)) + 1, output_dim=100, weights=[embedding_matrix], trainable=True, mask_zero=True), LSTM(64, dropout=0.3, recurrent_dropout=0.3), Dense(1, activation="sigmoid") ]) model.compile(optimizer="adam", loss="binary_crossentropy", metrics=["accuracy"])这段代码里真正影响结果的参数有三个。window=5控制 Word2Vec 的上下文窗口,评论句子短,窗口太大反而会把不相关的词拉近;maxlen=100是序列截断长度,电商评论平均长度在 30~80 词之间,截到 100 基本不会丢主要信息;LSTM(64)的 64 是隐层维度,课程作业的语料规模下 32~64 足够,加到 128 除了变慢,提升非常有限。
mask_zero=True在这里是为了让填充的 0 不参与计算。缺点是不能在 Embedding 层用 Word2Vec 预训练向量时设置trainable=False,因为预训练向量并不覆盖所有词,留一部分随机初始化让模型自己微调,效果通常更好。如果你发现 LSTM 收敛很慢,试一下去掉recurrent_dropout,它在小数据集上经常把梯度信息丢得太狠。
4.3 超参数怎么调:重点调这 4 个就行了
课程作业不需要 GridSearch 跑几十组,那样既费时间又不一定解释得清楚。我习惯只调四个参数,而且是在基线模型上逐个调,不是组合调。
TF-IDF 侧调min_df、max_df、ngram_range,模型侧调C。min_df在 1~5 之间试,max_df在 0.6~0.9 之间试,ngram_range看要不要考虑 bigram。逻辑回归的C是正则强度的倒数,当前语料量级下试0.1/1/10三档就够。
param_grid = { "min_df": [1, 2, 3], "max_df": [0.6, 0.8], "ngram_range": [(1, 1), (1, 2)], "C": [0.1, 1, 10] } grid = GridSearchCV( LogisticRegression(max_iter=1000, class_weight="balanced"), param_grid, scoring="f1_macro", cv=5, n_jobs=-1 ) grid.fit(X_train_vec, y_train) print(grid.best_params_)注意这里GridSearchCV的输入是已经向量化好的X_train_vec,不是原始文本,否则每一步都要重复fit_transform,时间会爆炸。scoring="f1_macro"是关键,因为类别不平衡时accuracy会误导你,用f1_macro能同时看到好坏类的平均表现。如果你只调了C但没调 TF-IDF 参数,模型对负面的召回率可能一直都在低位。
LSTM 侧最值得调的是maxlen和LSTM隐层大小,学习率用默认的 Adam 就够了。比起微调超参数,我更建议去看训练曲线:如果验证 loss 在第三个 epoch 就开始回升,立刻启用EarlyStopping,课程作业里这一步的救命效果比调任何参数都强。
4.4 模型保存与加载:词表不配对,预测全白费
训练完模型通常需要打包成文件,最常见的是用joblib,因为处理 sklearn 对象比pickle更稳。这里最重要的原则是:向量化器必须和模型一起保存,单独保存模型文件会丢掉“词表”。没有词表,预测时新评论里的每个词都可能落到未知索引上,模型输出基本等于随机数。
import joblib # 保存:两个文件,缺一不可 joblib.dump(vec, "models/tfidf_vec.joblib") joblib.dump(clf, "models/sentiment_lr.joblib") # 加载:先向量化,再预测 vec_loaded = joblib.load("models/tfidf_vec.joblib") clf_loaded = joblib.load("models/sentiment_lr.joblib") test_vec = vec_loaded.transform(["这个手机壳手感不错"]) prob = clf_loaded.predict_proba(test_vec)[0]我在很多项目里见过同一个错误:预测脚本里重新TfidfVectorizer()了一次,然后fit到待预测的评论上。这一步的结果是维度从几万变成几十,模型接口直接报维度不匹配错误。正确逻辑是先加载训练时保存的向量化器,只调用transform,不要调fit。
5. 避坑:中文编码、类别不平衡、数据泄漏与训练预测不一致
5.1 现象一:UnicodeDecodeError 反复出现
症状是写pd.read_csv("data/reviews.csv")报错:UnicodeDecodeError: 'utf-8' codec can't decode byte 0xbb in position 0。原因通常是数据集不是 UTF-8 编码,而是 GBK 或 GB2312,也可能是 Windows 上 Excel 另存为 CSV 时自动加了 UTF-8 BOM,导致 pandas 的默认编码参数读不进。
解决方式是先用utf-8-sig试,不行再换gbk。注意utf-8-sig可以兼容无 BOM 的 UTF-8 文件,所以优先级最高。
df = pd.read_csv("data/reviews.csv", encoding="utf-8-sig")如果你在同一个项目里要反复读写 CSV,就统一在建表时就指定utf-8-sig编码写入,省得每次猜。写代码时不要用encoding="utf-8"读含 BOM 的文件,会留下一个\ufeff在第一个字段名上,导致取列名时莫名多一个不可见字符。
5.2 现象二:准确率 0.93 但负面评论全部漏检
这个是课程作业里最隐蔽的问题。假设原始数据有 9000 条好评、1000 条差评,模型把全部样本都预测成好评,准确率是 0.9,看起来不错,但差评召回率是 0。报错日志里只有accuracy: 0.93时,根本不会暴露这个问题。
解决方法是看混淆矩阵和 f1_score,并且在切分数据时显式传stratify。
from sklearn.metrics import classification_report, confusion_matrix y_pred = clf.predict(X_test_vec) print(confusion_matrix(y_test, y_pred)) print(classification_report(y_test, y_pred, target_names=["差评", "好评"]))如果确认是类别不平衡,优先加class_weight="balanced",这比上采样、下采样更简单可靠。上采样会复制少数类样本,增加训练时间且容易过拟合;下采样会丢掉大量多数类信息,我觉得对课程作业都不划算。
5.3 现象三:网格搜索分数很高,测试集翻车
如果你用GridSearchCV直接在向量化后的全量数据上搜索,然后重新切分测试集,会看到训练分数接近 1.0、测试分数大幅下跌。这不是模型 bug,而是你在搜索时把测试集的信息也喂进去了。更常见的泄漏路径是:先对整个df["clean_review"]做fit_transform,再train_test_split。TF-IDF 的 IDF 值是按全量语料算的,这意味着测试集文档的分布影响了词权重,严格来说测试集不再是无偏的。
解决方式很简单:先切分,再对训练集fit,对测试集只transform。前面训练代码里已经这么写了。如果你用的是 Word2Vec,也尽量只对X_train训练词向量;如果作业要求全量训练,就在答辩里主动说明这是简化做法,不影响结果对比。
5.4 现象四:模型文件加载后预测全部错乱
症状是运行predict.py不报错,但所有待预测文本的输出概率都接近 0.5 或同一个值。原因大概率是预测脚本里重新构造了一个向量化器,或者加载了和模型不匹配的词表。还有一种可能:模型保存时用的是独热编码后的标签,比如label列里是字符串“正面/负面”,训练时忘记用LabelEncoder,预测时却传了 0 和 1。
判断这种问题的方法是打印clf.classes_,看看模型实际学习到的类别顺序是什么。如果标签是['正面', '负面'],而你的预测代码里用0/1去解读输出,结果会整个反转,但准确率看上去还是“对的”。
print(clf_loaded.classes_)如果类别顺序是字符串,就统一用LabelEncoder在训练前把标签编码,并且把编码器也保存下来。最稳妥的做法是让train.py把vec、clf、label_encoder三个对象打包到一个字典里,预测脚本只加载这一份文件,避免各存各的导致不匹配。
5.5 现象五:两次跑训练,结果差很多
原因很常见:训练脚本里没有设置随机种子,train_test_split每次切分出来的数据集不同,模型初始权重每次也不同。第一次测试 f1 是 0.78,第二次变成 0.71,答辩时没法解释清楚。
解决办法是把随机种子写进config.py,并在训练脚本开头统一设置。
import random import numpy as np def set_seed(seed): random.seed(seed) np.random.seed(seed) from tensorflow.keras.utils import set_random_seed set_random_seed(seed)注意 LSTM 在 GPU 上即使设置了随机种子,也可能因为 cuDNN 的算法选择产生微小差异,这是正常现象。作业报告里只要说明“所有比较实验使用相同随机种子”,就不会被认为结果不可靠。
6. 答辩和演示:最后一天怎么把这套项目讲成自己的
6.1 准备三张图和一张表
答辩时最吃亏的不是模型效果差,而是讲不清“你的数据长什么样、模型为什么选它”。我不建议贴一大段代码,评委更想看到的是三张图:第一张是标签分布柱状图,说明类别不平衡是你所有调参决策的起点;第二张是混淆矩阵热力图,直接说出“差评召回率从 0 提高到提交版本前的某个数值”;第三张是评论长度分布图,用来解释为什么maxlen=100是合理的。再配一张 TF-IDF+逻辑回归和 LSTM 的对比表,表里写清楚Accuracy、Precision、Recall、F1四个指标就够了。
6.2 演示脚本要输出能解释的中间结果
演示环节常见的死法是只输出一行“积极/消极”,一旦评委问“为什么这么判”就卡住。我给predict.py加了一个--verbose参数,让它打印出预处理后的文本和每个类别的概率:
def predict(comment_text): cleaned = clean_text(comment_text, sw) vec_text = vec_loaded.transform([cleaned]) prob = clf_loaded.predict_proba(vec_text)[0] label = clf_loaded.predict(vec_text)[0] return cleaned, label, prob # 演示一条 comment = "衣服收到了,质量很好,但尺码偏小一码,客服爱答不理" cleaned, label, prob = predict(comment) print("原始评论:", comment) print("清洗后:", cleaned) print("预测结果:", "正面" if label == 1 else "负面") print("正面概率: %.1f%%, 负面概率: %.1f%%" % (prob[1] * 100, prob[0] * 100))清洗后的文本能直接看出分词和停用词表是否合理,概率值能支撑“模型为什么犹豫”的解释。如果概率接近 0.5,你可以坦然说“这条包含正负两种信号,模型置信度不足,属于真实业务中需要人工复核的边界样本”,这比硬着头皮解释一个错的判例好得多。
6.3 最后一个习惯:做 20 条人工抽样验证
答辩前一天,我从测试集里随机抽 20 条样本,自己先标一遍“正面/负面”,再跑模型的预测结果。这个习惯帮我躲过两次翻车:一次是停用词表误删了“不”,导致所有“不好看”都判成正面;另一次是标签编码顺序写反,预测结果整体颠倒但准确率却恰好接近 50%——两种都是只看指标根本发现不了的问题。
如果你时间有限,就抽 10 条负面评论和 10 条正面评论,一条一条看清洗后的文本和置信度。这一步花不了 20 分钟,但它决定了答辩演示时敢不敢当场跑随机的样例。我一直保留这个习惯,不是因为模型会背叛你,而是因为预处理和编码这种基础环节出的问题,往往在最后一刻才暴露。希望帮到你。
本文还有配套的精品资源,点击获取