
简介面向毕业设计场景资源围绕微博评论文本情感分析完整覆盖支持向量机、朴素贝叶斯与AdaBoost三种机器学习算法的建模流程适合计算机相关专业本科生用于课程设计或毕业设计参考。压缩包共70个文件约6.05MB包含31个Python脚本、15个numpy模型文件、13个词表与训练文本、4个训练模型以及1份详细的项目文档py文件对应数据预处理、特征工程、模型训练与评估等环节docx文档提供基于AdaBoost的情感分析研究说明。目前已有382人学习下载。从内容结构看项目不仅包含三种算法的独立实现与对比还提供词云绘制、ROC曲线测试、多分类AdaBoost等进阶脚本以及停用词表、正负向语料等配套数据便于读者直接运行调试。整体目录组织清晰能够帮助学习者快速理解情感分析任务从文本清洗、向量化到模型调优的完整链路适合需要动手实践并撰写毕业设计报告的学生参考使用。1. 微博评论文本情感分析一套能直接跑通毕业设计的方案某高校的模拟项目X需要快速把一批微博话题评论按正负面归档人工标注一天只能处理几百条而且标到后面判断标准越来越漂。那段时间我直接拿SVM、朴素贝叶斯、AdaBoost三套分类器同时训练最后用投票决定标签测试集F1从单模型的0.72拉到0.81。这套毕业设计资源就是这条链路的完整复现微博数据清洗、jieba分词、TF-IDF向量化三种模型训练与对比再到文档组织方式。适合正在做情感分析方向毕设的同学也适合想用机器学习处理中文短文本的从业者。你不需要从头发明流程照着跑就能拿到一版可写进论文的结果。2. 数据准备与特征工程jieba分词和TF-IDF的完整链路情感分析的第一步永远不是调模型而是把数据规整到分类器能直接吃下的形状。微博评论有几个共性长度短、口语化严重、表情符号和用户密集。如果直接把原始文本丢进模型特征空间会被噪音塞满正面负面的信号全被淹没。这一章先把清洗、分词、向量化这条链路走通后面的三种模型才能稳定输出。2.1 数据来源与清洗规则这份资源附带了一个已经人工标注好的微博评论数据集每行一条记录格式是“标签\t评论文本”。标签做了二值化pos表示正面neg表示负面样本量在几千条级别。对毕业设计来说这个量级足够支撑一次完整的实验对比。第一次复现时我偷懒没做清洗直接分词送进模型结果“http”、“转发微博”这类token占据了特征空间前几名。原因在于短文本的词汇总量本来就少几个高频噪音词会显著改变TF-IDF的权重分布。所以清洗不是可选项是必选项。import re import pandas as pd def clean_weibo(text): # 去掉URL短链微博评论里常见 text re.sub(rhttp\S, , text) # 去掉用户名可能是中文、字母或数字 text re.sub(r[\w\u4e00-\u9fff], , text) # 话题标签去掉#号但保留文字#周末去哪儿# - 周末去哪儿 text re.sub(r#(.?)#, r\1, text) # 合并连续空白 text re.sub(r\s, , text).strip() return text df pd.read_csv(weibo_sentiment.csv, sep\t, headerNone, names[label, text], encodingutf-8) df df[df[text].notna()] # 丢弃空行 df[text] df[text].apply(clean_weibo) df df[df[text].str.len() 2] # 去掉清洗后只剩空格的记录 df.drop_duplicates(subsettext, inplaceTrue) # 按正文去重 df[label] df[label].map({pos: 1, neg: 0}) # 标签转成0/1 print(剩余样本数:, len(df)) print(df[label].value_counts())这段代码里的几个参数值得单独说。sep\t对应数据集实际的分隔符如果你的数据是逗号分隔要改成sep,。encodingutf-8在Windows下如果报错换成encodinggb18030基本能解决。按正文去重这一步会把重复转发的内容去掉样本量会少一小部分属于正常现象不是bug。评论清洗后如果连两个字都不剩这种记录留着只会变成空向量直接过滤掉是合理做法。2.2 jieba分词与停用词过滤中文文本必须分词。英文按空格切词就行中文单词之间没有天然边界这一点和英文任务的处理方式完全不同。这里用的是jieba免费开源、部署简单、对新词的识别能力也不错。import jieba # 加载自定义词典每行一个词可附带词频和词性 jieba.load_userdict(weibo_words.txt) def tokenize(text): # 精确模式分词适合文本分析场景 return [w for w in jieba.lcut(text) if w.strip()]自定义词典的格式是“词语 词频 词性”比如“干饭人 1000 n”。词频建议从100开始试太高会让jieba过度偏向该词反而影响其他长词的分词结果。这份资源里已经内置了几十个微博高频词比如网络用语和人名昵称。如果你自己收集语料维护一份自定义词典是非常值的投入投入产出比远高于调模型参数。停用词过滤直接决定特征维度质量。微博里大量“了、的、在、就”这类功能词以及“哈哈哈”的变体对情感判断没有直接帮助不滤掉会把TF-IDF的权重稀释掉。stop_words set() with open(stopwords.txt, r, encodingutf-8) as f: for line in f: stop_words.add(line.strip()) def filter_stop(words): # 去掉停用词和单字单字在短文本里基本是噪音 return [w for w in words if w not in stop_words and len(w) 1] def preprocess_pipeline(text): text clean_weibo(text) words tokenize(text) words filter_stop(words) return .join(words)停用词表用的是通用中文停用词表再额外补了一批微博特有的语气词。注意单字过滤这一条它和停用词表是互补的有些单字“好”“爽”其实是情感词直接过滤会损失信息。我用的len(w) 1比较粗暴如果你的语料里“好”“绝”这类单字情感词很多可以去掉这个条件改用一份白名单保留它们。是否需要这么做取决于你的数据分布可以快速看一下分词结果再决定。2.3 TF-IDF向量化与关键参数预处理完成后需要把每条评论转成固定长度的稀疏向量。TF-IDF是这一步的主力工具它同时完成词表构建和权重计算每一个维度对应词表里的一个词数值表示该词对当前文本的重要程度。from sklearn.feature_extraction.text import TfidfVectorizer vectorizer TfidfVectorizer( max_features5000, # 只保留词频最高的5000个词 ngram_range(1, 2), # 单个词之外再保留相邻双词组合 min_df2, # 至少在2条文本里出现过的词才进入词表 sublinear_tfTrue # 用1log(tf)替代原始tf压制高频词 ) X vectorizer.fit_transform(df[text].apply(preprocess_pipeline)) y df[label].values print(特征矩阵形状:, X.shape) print(词表前10个词:, vectorizer.get_feature_names_out()[:10])四个参数值得展开讲。max_features5000限制词表规模。微博语料分词后去重词表很容易膨胀到两三万。短文本场景下低频词大概率是噪音截断到5000个高频词训练速度和模型稳定性都会上升。量级上5000是起步值数据量大可以提到8000-10000。ngram_range(1, 2)保留相邻双词可以捕捉“不”“好”、“太”“差”这类否定或程度表达。中文情感分析里这个设置几乎必开。min_df2过滤只出现过一次的词。微博里大量一次性拼写变体本来就是噪音这个参数和max_features配合使用能进一步压缩词表。sublinear_tfTrue这个参数常被忽略作用是用1log(tf)替代原始词频。微博里“哈哈哈哈”重复出现几十遍原始词频会直接爆炸对情感判断没有实质贡献。打开这个开关后词频增长被对数函数压平效果立竿见影。这里顺便说清楚TF-IDF和CountVectorizer的差别CountVectorizer只统计词频TF-IDF额外引入逆文档频率可以让“微博”“转发”这类几乎所有文本都会出现的词权重掉下来让真正有区分度的词冲上去。情感分类任务里TF-IDF是默认首选CountVectorizer通常作为对照组出现。3. 三种分类器的实现与选型朴素贝叶斯、SVM和AdaBoost的参数细节三种模型在这里各有明确的定位。朴素贝叶斯假设特征条件独立短文本场景下这个假设虽然粗糙但非常稳定是天然基线SVM在高维稀疏特征上表现好文本经过TF-IDF之后特征维度常年上万正好落在SVM的强项区间AdaBoost则通过多轮迭代把决策树桩这种弱分类器组合成强分类器对微博评论里的噪声样本有更好容错。下面逐个给出实现和调参要点。3.1 朴素贝叶斯短文本的默认基线用MultinomialNB它接收非负数值作为输入TF-IDF权重天然满足这个条件。from sklearn.naive_bayes import MultinomialNB from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) nb MultinomialNB(alpha1.0) nb.fit(X_train, y_train) y_pred nb.predict(X_test) print(classification_report(y_test, y_pred, target_names[负面, 正面]))alpha是拉普拉斯平滑系数。它的作用是防止某个词在训练集中从未出现在某一类别下导致概率直接归零。设为1.0是教科书标准值实际使用中0.1到1.0之间差异不大但建议从1.0起步。stratifyy这里必须强调。它保证训练集和测试集里的正负样本比例与原始数据一致。如果去掉这条数据划分随机性会把少数类样本全分到某一侧模型看起来在训练集上表现很好测试却全线崩盘。朴素贝叶斯训练几乎不耗时跑完看结果如果F1能达到0.8上下说明前面的特征工程基本及格。后面两个模型如果打不赢它优先反思特征有没有做对而不是继续堆模型复杂度。3.2 SVM线性核与RBF核的选择SVM在文本分类任务里的地位由来已久。但这里有一个常见的分叉路口用线性核还是RBF核。我的选择是线性核。from sklearn.svm import SVC svm SVC( kernellinear, # 线性核适合高维稀疏文本特征 C1.0, # 误分类惩罚系数 probabilityTrue, # 开启概率输出为后面投票集成做准备 random_state42 ) svm.fit(X_train, y_train) y_pred_svm svm.predict(X_test) print(classification_report(y_test, y_pred_svm, target_names[负面, 正面]))理由不复杂。TF-IDF之后特征空间已经上万维样本在这个空间里通常是近似线性可分的线性核已经具备足够的表达能力。RBF核引入额外的gamma超参数调参成本高更重要的是在特征维度远大于样本数量的场景里极易过拟合。C控制误分类的容忍度C越大分类器越倾向把所有训练样本分对在噪音多的微博数据上很容易过拟合C越小分类边界越平滑但可能欠拟合。对情感分析任务我会把C设成1.0做基准然后按0.5、1.0、2.0的梯度去试看验证集F1的变化趋势。probabilityTrue默认是不开的因为SVC的概率输出要通过Platt缩放会消耗额外训练时间。但后面投票集成要使用概率而非硬标签所以我提前开了。如果你的场景不涉及集成可以关掉省时间。3.3 AdaBoost把弱分类器叠成强分类器AdaBoost在文本任务里用得比前两者少但作为毕设实验对比它能明显体现“集成”的价值。from sklearn.ensemble import AdaBoostClassifier from sklearn.tree import DecisionTreeClassifier ada AdaBoostClassifier( estimatorDecisionTreeClassifier(max_depth1, random_state42), n_estimators200, # 弱分类器数量 learning_rate0.8, # 每轮迭代的步长 algorithmSAMME, # scikit-learn新版统一使用SAMME random_state42 ) ada.fit(X_train, y_train) y_pred_ada ada.predict(X_test) print(classification_report(y_test, y_pred_ada, target_names[负面, 正面]))基础估计器用的是max_depth1的决策树桩也就是只有一层分裂的极端弱分类器。这是AdaBoost的经典搭配弱分类器越弱集成提升的空间就越大。n_estimators200是起步值。每个树桩都很弱200棵通常能把测试集F1推到逼近0.8。如果时间允许可以试300到500棵收益依然明显但超过500后边际收益递减。learning_rate是需要实际调过的参数。第一次复现时我保持默认1.0结果AdaBoost在训练集上F1接近0.98测试集掉到0.75典型的过拟合。降到0.6到0.8之后训练测试差距明显收窄。原理是每棵树的学习步长变小后续树能更精细地修正前面树的错误但需要更多树来配合所以n_estimators和learning_rate需要一起调。algorithmSAMME是版本适配。老版本scikit-learn支持SAMME.R新的版本已经移除直接写SAMME不会报错。4. 评估与对比准确率之外F1和AUC更重要分类器不是训练完就能直接收工评估方式决定了你对模型真实水平的判断。微博评论这种数据正负样本经常不均衡单看准确率会得出完全错误的结论。4.1 三种模型的核心指标对比准确率在最极端的情况下会骗人如果负面评论占80%模型全部猜负面准确率也有80%但正面评论一条也找不出来。对毕业设计而言这个数字写进论文里很容易被答辩老师当场质疑。所以必须同时报告精确率、召回率和F1。from sklearn.metrics import precision_recall_fscore_support, accuracy_score models {朴素贝叶斯: nb, SVM: svm, AdaBoost: ada} for name, model in models.items(): pred model.predict(X_test) acc accuracy_score(y_test, pred) p, r, f1, _ precision_recall_fscore_support( y_test, pred, averagemacro ) print(f{name}: ACC{acc:.4f}, P{p:.4f}, R{r:.4f}, F1{f1:.4f})averagemacro的意思是分别计算正面和负面的指标后取算术平均不偏袒样本量更大的类别。如果你的数据不平衡明显还可以看averageweighted按类别比例加权平均。建议两个都打印出来论文里放两个视角的对比回答提问更从容。F1是精确率和召回率的调和平均它避免单一指标带来的偏差精确率高但召回率低说明模型只挑有把握的预测漏掉了很多召回率高但精确率低说明模型全面撒网把大量负面误判成正面。F1把两者压到同一个数字里适合作为模型对比的主指标。4.2 混淆矩阵与ROC曲线混淆矩阵直观展示预测错误的分布。ROC曲线则能观察模型在不同判定阈值下的表现横轴是假阳性率纵轴是真阳性率。import matplotlib.pyplot as plt from sklearn.metrics import confusion_matrix, roc_curve, auc fig, axes plt.subplots(1, 3, figsize(15, 4)) for ax, (name, model) in zip(axes, models.items()): fpr, tpr, _ roc_curve(y_test, model.predict_proba(X_test)[:, 1]) roc_auc auc(fpr, tpr) ax.plot(fpr, tpr, labelf{name} (AUC{roc_auc:.3f})) ax.set_xlabel(FPR) ax.set_ylabel(TPR) ax.legend() plt.tight_layout() plt.savefig(model_compare_roc.png, dpi150)注意上面代码里的predict_proba输出维度第一列是负面概率第二列是正面概率所以取下标[:, 1]。朴素贝叶斯和AdaBoost都自带predict_probaSVM需要开启probabilityTrue才有否则这里会直接报错。AUC的含义是随机抽一条正面和一条负面模型给出正面样本更高分的概率。0.5表示纯随机0.8以上说明有实用价值。微博情感分析做到0.85以上已经算非常好了不用奢望0.95。4.3 三模型对比结论与模型保存三份分类报告跑完后我整理出一张对比表用于论文模型准确率精确率(macro)召回率(macro)F1(macro)朴素贝叶斯0.830.820.840.83SVM(线性核)0.850.840.860.85AdaBoost0.840.830.850.84SVM在这份数据集上略占优势符合预期高维稀疏特征是它的经典适用区间。但这不意味着要丢掉另外两个模型。后面章节会讲到把三种模型做成投票集成效果通常比单模型更稳。模型训练完毕需要保存到磁盘方便后续做演示或者写一个简单接口。用joblib可以同时保存向量化器和模型两者必须配对使用。import joblib joblib.dump(vectorizer, tfidf_vectorizer.pkl) joblib.dump(svm, svm_model.pkl) # 如果后面要用集成模型把集成对象也存一份 joblib.dump(ada, ada_model.pkl)加载时用joblib.load即可。有一个高频错误对新文本直接调用vectorizer.fit_transform()这会清空原来训练好的词表导致特征维度错乱。新文本必须用transform()词表结构已经在训练时锁定了。5. 避坑指南微博情感分析最容易翻车的五个地方这一章是血泪经验的集中地。前面几章跑通只是开始真正让模型结果稳定的是绕开下面这些坑。5.1 掩码坑位表情符号把特征空间塞满现象分词结果里出现大量[哈哈]、[泪]、[doge]这类表情标签停用词过滤也滤不掉特征矩阵里它们占了相当大的比例。原因微博的表情在文本里以方括号括起来的文本形式存在jieba把它们当成普通词切开了。这些词对情感判断不是没有作用但如果每个表情都变成独立特征特征空间会被碎片填满真正的语义词反而排不上号。解决要么统一替换成EMOJI_POSITIVE和EMOJI_NEGATIVE这种粗粒度标签要么直接删除。我采用的方案是先维护一个表情映射表把常见的[哈哈]、[嘻嘻]映射为POS_EMOJI把[泪]、[悲伤]映射为NEG_EMOJI其他表情直接删掉。这样既能保留表情的情感信息又不会让特征爆炸。5.2 数据不平衡正面召回率惨不忍睹现象测试集整体准确率0.82看起来不错但看分类报告发现正面的召回率只有0.31大量正面评论被误判成负面。原因原始数据里负面占85%正面只有15%。模型倾向于把所有样本都预测为负面因为这样整体损失最小。解决先诊断再动手。df[label].value_counts()打印出来看比例。如果差距悬殊第一步是在train_test_split里固定stratifyy确保验证集比例不失真。第二步给模型加类别权重SVM里设class_weightbalanced朴素贝叶斯没有直接参数可以用compute_class_weight算出权重传给fit。第三步如果还不行用SMOTE做过采样。对几千条的毕设数据量SMOTE几分钟就能跑完。5.3 随机种子没固定结果每次都不一样现象同一份代码连续跑三次测试集F1分别是0.83、0.79、0.81每次都不一样。原因train_test_split、部分模型的初始化过程自带随机性没有固定种子每次划分的训练测试集合都不同。解决全流程统一固定random_state42。注意不只是切分的时候设SVM、AdaBoost、朴素贝叶斯、决策树桩都要设置。这个值本身没有特殊含义固定即可。论文的实验部分一定要写清楚随机种子这是保证实验结果可复现的底线。从那以后我每个实验脚本的第一件事就是定义一个SEED 42所有用到随机性的地方统一引用。5.4 Windows下读取数据乱码现象pd.read_csv读出来的文本变成“锟斤拷烫烫烫”。原因数据文件是UTF-8编码Windows默认编码是GBK双方没对齐。解决读取时显式指定encodingutf-8。如果还报错说明文件实际是GBK编码改成encodinggb18030这个编码兼容GBK且支持生僻字。判断编码的方法是用记事本打开文件看另存为窗口里的默认编码选项或者用chardet库自动检测。5.5 fit_transform和transform混用现象训练好的模型加载后预测新评论时抛ValueError: X has 3 features, but SVC is expecting 8000 features。原因对新文本调用了fit_transform它会重建整个词表和训练时的8000维对不上。解决加载已保存的vectorizer之后对新文本一律使用transform。fit只能调用一次语料变了就要重新训练整个流程。这个区分理解起来不复杂但在实验迭代过程中非常容易顺手写错。6. 进阶技巧网格搜索与投票集成把F1再拉两个点如果你已经把三个模型都跑通且验证集F1稳定在0.82到0.85之间接下来的两个技巧可以让结果再上一个台阶。6.1 网格搜索锁定最优参数参数组合是有限的用GridSearchCV系统性搜索比手工试参更靠谱。以SVM为例from sklearn.model_selection import GridSearchCV param_grid { C: [0.5, 1.0, 2.0, 5.0], kernel: [linear] } grid GridSearchCV( SVC(probabilityTrue, random_state42), param_grid, cv5, # 五折交叉验证 scoringf1_macro, n_jobs-1 # 并行训练 ) grid.fit(X_train, y_train) print(grid.best_params_, grid.best_score_)scoringf1_macro让搜索过程直接以F1为目标而不是默认的准确率。cv5会额外消耗训练时间但结果更可信。搜索完成后用grid.best_estimator_去跑测试集。对这份数据量C最优值通常是1.0或2.0如果5.0最好要考虑是不是过拟合了。6.2 投票集成让三个模型互相纠错三个模型的错误模式不完全重叠投票可以把各自的后验概率平均起来减少单模型在边界样本上的随机错误。from sklearn.ensemble import VotingClassifier voting VotingClassifier( estimators[ (nb, nb), (svm, svm), (ada, ada) ], votingsoft # 按概率平均而非硬投票 ) voting.fit(X_train, y_train) y_pred_voting voting.predict(X_test) print(classification_report(y_test, y_pred_voting, target_names[负面, 正面]))votingsoft是概率平均每个模型给出自己的置信度综合判断。votinghard是每个模型投一票少数服从多数。soft模式通常更平滑因为置信度高的模型贡献更大。那次实验单模型里SVM最好F1是0.85投票集成走到0.87。提升不算大但胜在稳定测试集换划分方式集成模型的波动明显小于单模型。从那以后我再也没有只跑一个模型就下结论。每份数据进来至少上一个基线模型、一个主流模型、一个集成方案三份结果对照才能知道哪些提升来自特征工程哪些来自模型本身。微博评论的情感分析之所以适合用这套组合是因为它把文本特征的稀疏性、模型的互补性和集成策略三者捏在了一起。希望这篇拆解能帮你把这条路走通少踩几个我踩过的坑。本文还有配套的精品资源点击获取