手写朴素贝叶斯实现豆瓣影评情感分类:从数据清洗到模型评估

发布时间:2026/10/12 13:52:02

手写朴素贝叶斯实现豆瓣影评情感分类:从数据清洗到模型评估 简介面向高校程序设计课程设计场景这份压缩包围绕豆瓣影评情感分类任务提供基于朴素贝叶斯算法的完整Python实现方案覆盖从原理学习到编码实践的完整链路。资源共10个文件包含4个Python脚本、1个Jupyter notebook以及csv数据集、停用词与用户词典和说明文档等辅助文件压缩包仅3.27MB结构紧凑适合初学者快速上手目前已有398人学习下载。项目从数据导入、清洗、特征提取到模型训练与评估均有对应代码支撑数据预处理涉及分词、去除停用词和词干提取特征工程可使用TF-IDF或词袋模型量化关键词重要性训练脚本与测试脚本分离便于理解朴素贝叶斯建模和超参数调整。Jupyter notebook适合逐步运行观察中间结果py脚本则方便直接执行或二次改造读者通过本项目可掌握MultinomialNB的实际调用方式熟悉文本分类完整流程对理解贝叶斯原理及情感分析实战均有直接帮助。1. 豆瓣影评情感分类课程设计为什么这个“老古董”模型仍值得认真做如果课程设计题目落到“基于朴素贝叶斯的豆瓣影评情感分类”上很多人第一反应是查资料、找现成代码、跑通交差。但真正动手时你会发现教材不会告诉你的事全冒出来了短评里大量重复文本、停用词表把“不”删掉导致极性反转、拉普拉斯平滑参数选多大、训练测试如何切分才能让指标不虚高。这个项目的本质是走一条完整的文本分类流水线——数据清洗、中文分词、特征表示、概率建模、评估分析。朴素贝叶斯的好处是数学透明每一处概率都能解释清楚非常适合课程设计答辩在豆瓣短评这种长度短、口语化强、噪声相对可控的文本上朴素贝叶斯通常能拿到80%到90%的准确率并不输给一些一上来就撸深度学习的方案。适合谁读准备做文本挖掘课设、想快速验证Baseline、或者想把分类原理吃透的同学。2. 把原始短评洗成训练集清洗、分词与停用词的取舍2.1 原始短评长什么样数据字段与标注口径豆瓣影评项目的数据通常是一个CSV表格核心字段就两个短评原文text和标签labellabel用0表示负面、1表示正面。常见做法是公开的豆瓣影评情感分类数据集有些带星级字段可以直接按1到2星标负面、4到5星标正面、3星丢弃来生成标签如果是手工标注尽量保证正负样本各一半。课程设计阶段1500到2000条足够起步数据量过大反而会让手写统计过程变慢数据量太少则先验概率和类条件概率都估不准。拿到原始文本后第一步是清洗。清洗不是越狠越好目标是去掉与情感无关的噪声同时保留“不”“很”“烂”这类情感线索。下面这个清洗函数覆盖了绝大多数情况import pandas as pd import re def clean_text(raw: str) - str: if not isinstance(raw, str): return text raw.strip() text text.replace(\u3000, ) # 全角空格统一为半角 text re.sub(rhttp[s]?://\S|www\.\S, , text) # URL text re.sub(r[^], , text) # HTML标签 text re.sub(r[a-zA-Z];, , text) # nbsp;这类转义字符 text re.sub(r[a-zA-Z0-9], , text) # 英文和数字通常不是情感线索 text re.sub(r\s, , text) return text.strip()这里多数同学会纠结要不要删除数字。我的习惯是先看数据样本再决定。像“等了120分钟”这种短评数字确实不承载情感但如果是“三小时无尿点”删掉数字并不影响情感判断。所以默认删除英文数字是安全的能明显缩小词表、减少稀疏性。要注意的点在于不要在清洗阶段删除标点“”和“”本身带有情绪后面做特征时可以考虑保留。如果数据里混入大量空白行还要在加载CSV时用dropna(subset[text, label])把空值删掉否则分词阶段直接报错。2.2 分词与停用词为什么“不”不能被停用词表误删中文文本必须分词。这个项目里jieba是事实标准用精确模式jieba.lcut()就好专业名词或者演员名如果切分不理想可以jieba.add_word(星际穿越)手动补词典。分词本身不复杂真正的坑在停用词表。很多通用停用词表会包含“不”“没”“别”“无”这类否定词。如果分词后直接把停用词全部滤掉“这部电影不好看”会变成“电影好看”情感极性完全反转评测指标却可能依然很高因为错误是系统性的准确率的假象会骗你。常见做法有两种要么不加载通用停用词表只维护一个包含“的、了、是、我、你”等高频无意义词的轻量停用集要么加载通用词表但显式保留否定词。相对稳妥的是第一种因为短评本身文本短词表噪音不如长文本严重。import jieba LIGHT_STOPWORDS {的, 了, 是, 我, 你, 他, 她, 它, 在, 也, 都, 和, 与, 就} NEGATIVE_WORDS {不, 没, 别, 无, 莫} def tokenize(text: str) - list: words jieba.lcut(clean_text(text)) result [] for w in words: w w.strip() if not w: continue if w in NEGATIVE_WORDS: result.append(w) # 否定词必须保留哪怕它只有一个字 elif len(w) 1 and w not in LIGHT_STOPWORDS: result.append(w) return result注意len(w) 1这个条件分词结果里的“烂”“爽”这类单字情感词会被过滤损失信息。如果你的数据集中单字情感词出现频繁就把长度限制去掉只靠停用集合过滤。这个取舍需要在预处理阶段快速验证方法很简单随机挑200条短评打过Tokenizer看结果扫一眼就知道过滤是否合理。分词结果可以保存到新列方便后面手写朴素贝叶斯时反复使用不用每跑一次都重新分词。3. 手写朴素贝叶斯分类器从词频统计到对数概率3.1 从词频到概率表朴素贝叶斯的内部构造与拉普拉斯平滑朴素贝叶斯的核心逻辑一句话就能说清楚对于一条句子我们计算它分别在正面、负面两类下的后验概率谁大就判给谁。计算后验概率时朴素假设让每个词独立参与连乘公式展开后就是先验概率P(类别)乘以每个词的类条件概率P(词|类别)的累积。重点在于实际实现时几乎不会直接做连乘因为几百个小于1的小数相乘会无限逼近0浮点数直接下溢。所以必须对所有概率取对数把连乘变成连加这也是评判一份课程设计代码是否专业的一个细节。手写实现我一般分成两个函数nb_fit统计训练集上的先验和对数条件概率nb_predict对新句子做预测。下面是训练部分import math from collections import Counter def nb_fit(train_texts, train_labels, alpha1.0): # 按类别收集分词结果 class_docs {0: [], 1: []} for text, label in zip(train_texts, train_labels): class_docs[label].append(tokenize(text)) log_prior {} class_word_count {} vocab set() for label, docs in class_docs.items(): counter Counter() for doc in docs: counter.update(doc) # 统计该类下每个词出现次数 class_word_count[label] counter vocab.update(counter.keys()) # 词表只在训练集上构建 log_prior[label] math.log(len(docs) / len(train_texts)) vocab_size len(vocab) # 拉普拉斯平滑时的分母项之一 log_cond {} for label, counter in class_word_count.items(): total_words sum(counter.values()) log_cond[label] { w: math.log((counter.get(w, 0) alpha) / (total_words alpha * vocab_size)) for w in vocab } return log_prior, log_cond, vocab这个函数里alpha就是拉普拉斯平滑系数默认取1.0等价于假设每个词在每类中至少出现过一次避免某个词在训练集中没出现导致概率为0。分母里的alpha * vocab_size是平滑的累计增量词表越大分母膨胀越多这会让低频词的类条件概率被压得更低。训练时只统计训练集的词表千万不要把测试集的分词结果也并进来否则会造成“词表泄漏”后面会专门讲这个坑。预测时每个句子按类别累加对数概率def nb_predict(text, log_prior, log_cond, vocab): tokens tokenize(text) if not tokens: # 空文本直接按先验概率返回 total sum(math.exp(s) for s in log_prior.values()) return {label: math.exp(s) / total for label, s in log_prior.items()} scores {} for label in log_prior: score log_prior[label] for w in tokens: if w in vocab: # OOV词跳过平滑已兜底 score log_cond[label][w] scores[label] score # 归一化成概率方便答辩时解释置信度 total sum(math.exp(s) for s in scores.values()) return {label: math.exp(s) / total for label, s in scores.items()}预测阶段跳过了词表中不存在的词这是很多手写实现会报KeyError的根源。跳过OOV词的逻辑是合理的这个词在训练集里完全没见过它对类别判断没有任何信息强行加入反而只会增加噪声。但要注意训练阶段必须保留OOV词在log_cond字典外面而不是给它分配一个概率。归一化用softmax的做法把两个类别的分数变成“正面概率0.92、负面概率0.08”这种直观表述答辩时展示效果很好。3.2 用sklearn做对照实验MultinomialNB的一行流与参数边界手写实现的价值在于解释原理但课程设计里最好同时用sklearn跑一个对照证明你的实现与成熟库的行为一致。sklearn对应的是多项式朴素贝叶斯MultinomialNB它直接吃词频矩阵或TF-IDF矩阵。文本向量化我一般用TfidfVectorizer并传自定义的tokenizer让分词逻辑和手写实现保持一致from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.naive_bayes import MultinomialNB from sklearn.pipeline import make_pipeline vectorizer TfidfVectorizer( preprocessorlambda x: x, # 清洗已在tokenize里做过避免二次处理 tokenizertokenize, lowercaseFalse, # tokenize内部已经lowercase过 ngram_range(1, 2), # 考虑“不好”这类相邻词组合 min_df2, # 去掉只出现过一次的词 max_features5000 # 控制特征维度防止词表过大拖慢训练 ) model make_pipeline(vectorizer, MultinomialNB(alpha1.0)) model.fit(train_texts, train_df[label]) pred model.predict(test_texts)这里有几处容易翻车的参数。第一preprocessorlambda x: x必须有因为自定义tokenizer已经做完全部清洗如果让sklearn默认的preprocessor再跑一遍lowercase你的NEGATIVE_WORDS集合里的“不”可能被改写成其他形式或者产生重复清洗。第二ngram_range(1,2)会让“好看”和“不好看”都成为特征对情感分类帮助很大代价是特征维度翻倍所以配合max_features5000限制。第三min_df2把只在一条短评里出现一次的词丢掉这些低频词通常是错别字、乱码和人名保留它们只会让模型记住噪声。对比实验结果时手写模型和sklearn模型的准确率差距通常在1到3个百分点以内。如果差了10个百分点以上先检查手写实现的分词函数和sklearn的tokenizer是否真的共用同一个常见问题是sklearn管线里分词又被执行了两次或者手写模型把测试集也塞进了词表。如果两者都正常那么差距主要来自TF-IDF的权重设计手写模型用的是纯频次sklearn用的是词频乘以逆文档频率两者在短文本上差距不大但确实存在这可以在答辩时作为模型对比的讨论点。4. 训练与评估从数据切分到F1指标别让准确率骗了你4.1 数据怎么切train_test_split的分层参数和种子设置情感分类这种二分类任务数据集切分必须做分层抽样。普通随机切分在正负样本不均匀时会出问题如果数据集里80%是正面评论随机切分后测试集可能只剩20%负面样本模型就算全部预测正面也能拿到80%准确率但负面识别能力完全没体现。处理方法是显式指定stratify参数from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( df[text], df[label], test_size0.2, random_state42, stratifydf[label] # 保证训练集和测试集正负比例一致 )random_state固定为42既是为了实验结果可复现也是课程设计答辩时的一个加分项你能解释为什么固定随机种子评审老师才知道你不是碰巧跑出好结果。切分顺序必须放在向量化之前。如果先把所有文本向量化再切分测试集信息已经混入词表准确率会虚高到不真实的水平。以后所有特征提取器都只能对训练集fit对测试集只做transform。4.2 评估指标为什么只看分类准确率会被带偏分类准确率是课程设计里最常被引用的指标但在正负样本比例不均衡时它是最没有说服力的。更好的做法是输出混淆矩阵和F1值。F1是精确率与召回率的调和平均在负面评论这种少数类上表现不好时F1会直接掉到很低一眼就能暴露问题。from sklearn.metrics import classification_report, confusion_matrix print(confusion_matrix(y_test, pred)) print(classification_report(y_test, pred, target_names[负面, 正面]))跑完这两个函数你会得到四个关键数字。混淆矩阵的四个格子分别对应实际负面预测负面、实际负面预测正面、实际正面预测负面、实际正面预测正面。后面的classification_report会给出每一类的precision、recall、f1-score和support以及macro avg和weighted avg。课程设计报告里我最推荐展示macro F1因为它对类别不均衡不敏感能真实反映模型在少数类上的表现。单纯报告accuracy等于把模型最好的一面藏起来评审老师一问负面类召回率就会卡壳。评估这个环节还可以顺带做一件很加分的对比把手写朴素贝叶斯的预测结果和sklearn的预测结果分别打印混淆矩阵。多数情况下两者混淆矩阵极其相似说明你的实现是对的。如果手写模型把负面类全判成正面优先检查训练函数里类别标签的顺序以及在class_docs[label]往Counter里传值时label是否被错误地当成了字符串而不是整数。5. 避开这几个项目中的常见坑现象、原因与解决5.1 四类高发问题从样本分层到概率下溢的排错记录第一条坑训练集测试集切分后测试集准确率接近100%新数据预测却一塌糊涂。现象是评估时指标好得离谱但拿几条没见过的短评一测预测结果与直觉完全不符。原因多半是数据泄漏一种是切分前就做了全量向量化测试集的词表信息被训练器看到另一种是原始短评里有大量重复内容比如同一部电影的热门短评被重复抓取训练集和测试集里出现同一条文本。解决方法是先切分再fit同时用df[text].duplicated()检查重复短评把重复行去重后重新切分。课程设计阶段用1500条去重后的短评通常比用5000条含重复的更可靠。第二条坑情感极性整体反转“好看”被判成负面“不好看”被判成正面。这个现象非常迷惑因为准确率可能还有70%以上分析单条样本才发现预测标签完全颠倒。原因最大概率是停用词表吃掉了否定词其次是清洗函数里把某些字符映射成了空串导致“不”字消失。解决方法是回到分词函数打印tokenize(这部电影真的不好看)的输出如果结果里没有“不”字删除通用停用词表或把否定词强制加回保留名单。还有一个更隐蔽的原因是同一条短评里“不好看但很真实”这种转折结构朴素贝叶斯确实无法区分转折语义只能统计词汇和类别共现的频率这是模型假设的边界不是bug。第三条坑手写模型在预测时抛KeyError报错指向某个词不在词表。这个坑只出现在手写实现里。朴素假设下预测时遇到训练词表外的词OOV应该跳过但很多实现直接用log_cond[label][w]取值OOV词没有这个键就直接崩溃。解决方法在nb_predict里加if w in vocab判断没有则跳过。这个问题教材里不会写但几乎每个做手写朴素贝叶斯的同学都会遇到代码评审时能主动说明“这里对OOV做了跳过”反而是一个亮点。要注意跳过的同时不要给OOV词分配一个均匀概率那会稀释真实词的概率贡献。第四条坑分类概率总是贴近0.5或0.99没有中间地带。现象是输出概率呈现双峰分布或者几乎永远是某一边。如果概率总是0.99说明训练样本太少或词表太小模型只凭少数几个词就下了判断如果概率总在0.5附近说明特征区分度不够可能是清洗阶段把“非常”“太”“超”这类程度副词全删了。解决方法是先检查ngram_range是否只用了1增加二元词组能抓住更多组合特征再看min_df设置为2或3能过滤掉噪声但如果设得太高很多真实情感词也会被丢弃。这个调参过程就像拧螺丝需要配合验证集一条条看输出而不是盯着整体准确率。5.2 一分钟诊断自查按输出结果定位问题很多报错和异常现象不需要一行行调试按顺序做一遍快速检查就能定位。先把训练集和测试集的行数打印出来看类别分布是否均衡再对训练集跑一遍tokenize看否定词是否还在接着用手写模型预测一条“电影很烂”打印两个类别的对数分数最后用sklearn的classification_report对比手写模型看两者预测差异集中在哪一类。我一般把这四个输出做成一个诊断函数每次调参后跑一遍比盯着单一准确率有效得多。整个项目里最花时间的不是写模型而是这些边界条件和数据质量的排查这部分经验在答辩时比推导公式更值钱。6. 收尾进阶概率阈值调整与人工抽查验证朴素贝叶斯天然输出的是后验概率但默认的0.5判定阈值不一定是这个数据集上的最优解。如果希望分类器更谨慎一点可以减少边界误判做法是在验证集上尝试不同的阈值def predict_with_threshold(text, log_prior, log_cond, vocab, threshold0.6): probs nb_predict(text, log_prior, log_cond, vocab) pos_prob probs[1] # 正面概率 if pos_prob threshold: return 正面, pos_prob if pos_prob 1 - threshold: return 负面, pos_prob return 待定, pos_probthreshold调成0.6意味着只有模型比较有把握时才给结论。这个技巧可以让模型的误判率下降但会多出一部分“待定”样本。课程设计做二分类时“待定”可以作为后续讨论点而不是错误答案。调阈值要基于验证集不要在测试集上调否则测试集指标又失真了。最后建议做一个很简单的抽查验证从测试集之外随机抽20条短评人工标好正负然后与模型预测结果做对比。这个步骤看起来多余实际上能最快暴露数据标注错误和分词问题。我自己跑这类项目时最后悔的就是一上来调模型却忽视了停用词对极性翻转的影响等到报告写完才发现“不”字被过滤器静默吞掉重跑整个实验耗时一天。先做数据诊断再做阈值调参这个顺序比模型的选型更值得坚持。希望这些经验能帮你在课程设计里少走几步弯路。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/12 13:47:01

Python深度学习图像处理最小可行系统:工业级分类检测部署

简介:本资源是一套基于Python与PyTorch实现的深度学习图像处理完整源码工程,面向计算机视觉初学者、算法工程师及高校研究者,聚焦图像分类、目标检测与语义分割等核心任务,提供从模型训练到服务部署的全链路实践参考。压缩包共436…

2026/10/12 13:47:01

从零搭建可运行Agent系统:主循环调度、工具注册与上下文管理实战

简介:这份资源是面向具备一定Python基础的开发者与AI应用学习者的Agent系统搭建指南配套源码包,聚焦从零构建可自主搜索、生成报告并自动归档笔记的智能体系统,帮助读者理解Researcher、Editor、Note Taker三角色分工与工具调用机制。压缩包共…

2026/10/12 13:47:01

AI助手跨会话记忆中间层:从无状态到可检索的智能对话记忆方案

最近折腾了一套代号叫claude-mem的对话记忆中间层,目的是解决一个再常见不过的问题:AI 助手聊完一次就“六亲不认”,第二次见面照样把你当陌生人。这套方案的核心思路不是让模型“记住”,而是让模型“查得到”——把每一次对话沉淀…

2026/10/12 14:57:15

基于SSM的社区养老信息App开发指南与答辩要点

以下是根据你的标题和内容要求生成的博文正文,直接以 Markdown 格式输出。请查收。 1. 先说清楚:这类"社区养老信息App"到底在做什么 每年到了三四月份,都会有一批计算机专业的学生开始为毕业设计发愁。如果你打开过选题清单&…

2026/10/12 14:57:15

偏振图像去噪实战:Python资源与配套数据集详解

简介:面向计算机视觉与图像处理研究者的偏振图像去噪Python项目,重点解决强反光、眩光场景下偏振数据的噪声抑制与细节恢复问题。资源共38个文件,压缩包仅906KB,其中包含15个Python源码、14个编译后的pyc缓存、5张数据集示例图以及…

2026/10/12 14:57:15

Python实现图书推荐系统:协同过滤与文本相似度实战

简介:这份资源是一套基于协同过滤与文本相似度算法的Python图书推荐系统完整源码,面向计算机、软件工程等专业需要完成课程设计、毕业设计或推荐算法入门实践的学生与开发者,帮助解决推荐系统从算法到前后端落地的完整实现问题。压缩包共1828…

2026/10/12 14:57:15

wx-ipad协议8049双端适配:Linux与Windows跨平台构建实战

简介:该资源为微信iPad协议8049的Linux与Windows双版本实现,面向需要对接微信通信协议、搭建服务端接口的开发者与运维人员。包内共27个文件,以js脚本、map映射、conf配置、html页面及png图标为主,另含exe可执行程序、yml接口描述…

2026/10/12 14:57:15

2026年安全运维拐点:AI原生安全与行为基线分析实战

2025年做安全运维的时候,我最大的感受是:告警越来越不像人制造的,更像是一台不知疲倦的机器在试探我的边界。几个大项目一跑,很多团队的负责人都在问同一个问题——2026年我们到底该往哪儿投钱、往哪儿堆人?这篇东西不…

2026/10/12 14:52:12

Java开发者必看:LangChain4j大模型应用开发入门实战

1. 为什么Java开发者需要一个专属的LLM框架先说个现象:这半年陆续有几个做Java后端的朋友问我,说他们想在自己的项目里接入大模型能力,结果一搜教程全是Python。什么LangChain、LlamaIndex,Demo跑起来倒是热闹,可真要落…

2026/10/11 0:02:13

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/12 12:01:26

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/12 12:54:07

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/12 0:04:22

绝缘子缺陷检测数据集清洗与工业级训练实战指南

简介:本资源是面向电力AI研发人员、工业视觉工程师及智能巡检系统开发者的绝缘子缺陷检测专用YOLO格式数据集,解决无人机航拍场景下绝缘子破损、污闪、积雪等9类典型缺陷的精准识别与定位难题。数据集共2139张真实巡检图像(含训练/验证/测试集…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑