ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

中文微博情感分析源码拆包:从贝叶斯到BERT的完整基线

中文微博情感分析源码拆包:从贝叶斯到BERT的完整基线 简介这份资源面向计算机、人工智能、通信、自动化等相关专业的本科生与研究生以及需要完成课程设计、大作业或毕业设计的开发者提供一套完整的中文微博情感分析项目源码与配套文档。项目围绕中文微博文本展开覆盖朴素贝叶斯、SVM、XGBoost等传统机器学习方法以及LSTM、BERT等深度学习模型并附带训练好的模型文件与停用词表便于对比不同算法在情感分类任务上的表现。资源包共20个文件以10个txt说明文档、5个ipynb实验笔记、2个model模型文件为主另含1个py工具脚本、1个md说明与1个gitignore压缩包约1.85MB目录结构清晰按算法编号组织方便按模块检索与复现。目前已有130人学习下载。读者可借助该源码快速理解中文微博情感分析从数据预处理、特征提取到模型训练与评估的完整流程并在此基础上修改调整实现不同功能适合作为学习进阶与项目答辩的参考。1. 中文微博情感分析源码拆包从贝叶斯到 BERT 的一条完整基线微博文本短、口语化、反讽多做情感分析时经常出现「模型在公开数据集上 90%一上真实评论就翻车」的情况。这份资源把中文微博情感分析的完整链路摊开了同一份weibo2018数据分别用朴素贝叶斯、SVM、XGBoost 三种传统机器学习以及 LSTM、BERTDNN 两种深度学习跑一遍配套utils.py做数据加载与分词stopwords.txt做停用词过滤requirements.txt锁依赖README.md给说明。它解决的不是「教你什么是情感分析」而是给你一条能跑通、能对比、能改的基线。适合正在做课程设计、毕业设计或者想快速验证某个模型在中文短文本上表现的从业者。2. 数据管线与特征工程utils.py 里藏着的四个关键决策2.1 微博语料的加载与标签对齐拿到data/weibo2018之后第一件事不是急着喂模型而是确认它的组织方式。这类微博情感数据集常见做法是按类别分目录或者一个文件里「标签\t文本」两列。utils.py承担的就是把原始文件读成(texts, labels)的职责。我一般会先单独跑一遍加载逻辑把条数和标签分布打出来确认没有空行、没有编码错乱。# utils.py 中数据加载的核心逻辑按常见实现还原 import os import jieba def load_weibo_data(data_dir): texts, labels [], [] # 常见组织pos/neg 两个子目录或单文件两列 for label, sub in enumerate([neg, pos]): sub_dir os.path.join(data_dir, sub) if not os.path.isdir(sub_dir): continue for fname in os.listdir(sub_dir): with open(os.path.join(sub_dir, fname), encodingutf-8) as f: for line in f: line line.strip() if not line: continue texts.append(line) labels.append(label) return texts, labels def tokenize(text, stopwords): # 微博里 、话题、URL 先清掉再分词 words jieba.lcut(text) return [w for w in words if w not in stopwords and w.strip()]逻辑说明load_weibo_data用目录名映射标签这是最省事的做法但前提是数据确实按目录分好。如果实际是单文件就得改成按分隔符切分。tokenize里先分词再过滤停用词顺序不能反——先过滤会把「不」「没」这类否定词误伤而否定词在情感分析里权重极高。参数上stopwords从stopwords.txt读入建议额外保留否定词和程度副词别一股脑全滤掉。2.2 停用词表的取舍与否定词保留stopwords.txt是通用停用词表直接拿来用会踩一个坑它通常包含「不」「没」「无」这类词。在情感分类里这些恰恰是决定极性的关键。我的做法是在加载停用词后手动把否定词和程度副词从集合里剔除。# 保留情感强相关词避免被通用停用词误杀 KEEP_WORDS {不, 没, 无, 非, 很, 太, 非常, 特别, 有点, 稍微} def load_stopwords(path): with open(path, encodingutf-8) as f: words set(line.strip() for line in f if line.strip()) return words - KEEP_WORDS逻辑说明KEEP_WORDS是白名单从停用词集合里减掉等于强制保留。参数上这个集合可以按你的语料继续扩比如「挺」「蛮」「贼」这类口语程度词。注意别把「好」加进去它是情感词不是停用词加了反而丢信息。2.3 传统模型的向量化TF-IDF 与词袋的边界1.bayes.ipynb、2.svm.ipynb、3.xgboost.ipynb这三个 notebook 走的是传统路线核心是CountVectorizer或TfidfVectorizer。词袋模型丢语序但对微博这种短文本反而够用因为句子短语序信息本来就少。TF-IDF 比纯词袋多一层「逆文档频率」加权能压掉「哈哈」「转发」这类高频无意义词。from sklearn.feature_extraction.text import TfidfVectorizer vectorizer TfidfVectorizer( tokenizerlambda x: x, # 已经分好词直接透传 preprocessorNone, token_patternNone, max_features20000, # 控制维度微博语料 2 万足够 ngram_range(1, 2) # 加入 bigram捕捉「不 好」这类组合 ) X vectorizer.fit_transform(tokenized_texts)逻辑说明tokenizer透传是因为前面已经用 jieba 分好词避免二次分词。max_features设 2 万是经验值太大容易过拟合且拖慢训练。ngram_range(1,2)是关键单字「好」和「不好」在词袋里是两回事bigram 能把否定结构保留下来。参数上如果显存或内存吃紧把max_features降到 1 万精度掉得通常不超过两个点。2.4 深度学习分支的输入准备4.lstm.ipynb和5.bert.ipynb需要的是序列输入不能再用 TF-IDF。LSTM 分支一般用Tokenizer做词到 id 的映射再pad_sequence统一长度BERT 分支直接用transformers的BertTokenizer走 WordPiece 切分。两条路的预处理完全不同别混用。# LSTM 分支词表 定长截断 from tensorflow.keras.preprocessing.text import Tokenizer from tensorflow.keras.preprocessing.sequence import pad_sequences tok Tokenizer(num_words30000, oov_tokenUNK) tok.fit_on_texts(tokenized_texts) seqs tok.texts_to_sequences(tokenized_texts) X pad_sequences(seqs, maxlen64, paddingpost, truncatingpost)逻辑说明num_words30000限制词表规模微博语料词汇量不大3 万覆盖绝大多数。maxlen64是微博文本的合理长度超过 64 个词的微博极少。paddingpost在尾部补零配合 LSTM 的masking层可以忽略填充位。参数上maxlen别设太大否则 LSTM 训练慢且容易梯度消失。3. 五个模型逐个跑通从贝叶斯到 BERT 的实操与参数3.1 朴素贝叶斯与 SVM快速基线的两个锚点1.bayes.ipynb和2.svm.ipynb是最快能出结果的。朴素贝叶斯假设特征独立在短文本上表现意外地稳适合当第一版基线。SVM 用线性核在 TF-IDF 高维稀疏特征上通常比贝叶斯强几个点是传统方法里的主力。from sklearn.naive_bayes import MultinomialNB from sklearn.svm import LinearSVC from sklearn.metrics import classification_report # 贝叶斯 nb MultinomialNB(alpha1.0) nb.fit(X_train, y_train) print(classification_report(y_test, nb.predict(X_test))) # 线性 SVM svm LinearSVC(C1.0, max_iter5000) svm.fit(X_train, y_train) print(classification_report(y_test, svm.predict(X_test)))逻辑说明MultinomialNB的alpha是平滑系数默认 1.0语料小可以调到 0.1 减少平滑。LinearSVC的C控制正则强度C 越大越容易过拟合微博语料建议从 1.0 试起。max_iter要调大否则 sklearn 会警告未收敛。参数上SVM 在 TF-IDF 上通常比词袋高 1-2 个点值得多跑一组对比。3.2 XGBoost把稀疏特征喂给树模型的注意点3.xgboost.ipynb用的是梯度提升树。树模型对稀疏高维特征的处理不如线性模型自然但 XGBoost 支持稀疏输入能跑。关键是把 TF-IDF 矩阵转成它认识的格式并控制树深防止过拟合。import xgboost as xgb dtrain xgb.DMatrix(X_train, labely_train) dtest xgb.DMatrix(X_test, labely_test) params { objective: binary:logistic, max_depth: 6, # 树深稀疏特征别太深 eta: 0.1, # 学习率 subsample: 0.8, # 行采样抗过拟合 colsample_bytree: 0.8, # 列采样 eval_metric: logloss } model xgb.train(params, dtrain, num_boost_round200, evals[(dtest, test)], early_stopping_rounds20)逻辑说明max_depth6是稀疏文本特征的稳妥值再深容易记住噪声。subsample和colsample_bytree都设 0.8双重采样抗过拟合。early_stopping_rounds20在验证集不降时提前停省时间。参数上XGBoost 在这类任务上通常不如线性 SVM但它的特征重要性输出对分析哪些词有判别力很有用。3.3 LSTM序列建模的配置与训练技巧4.lstm.ipynb是第一个真正用上语序的模型。微博里的「不」放在不同位置情感完全不同LSTM 的门控机制能捕捉这种依赖。配置上重点是嵌入维度、LSTM 单元数和 dropout。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Embedding, LSTM, Dense, Dropout, SpatialDropout1D model Sequential([ Embedding(input_dim30000, output_dim128, input_length64), SpatialDropout1D(0.3), # 对嵌入维度做 dropout LSTM(128, dropout0.2, recurrent_dropout0.2), Dense(64, activationrelu), Dropout(0.3), Dense(1, activationsigmoid) ]) model.compile(optimizeradam, lossbinary_crossentropy, metrics[accuracy]) model.fit(X_train, y_train, validation_split0.1, epochs10, batch_size64)逻辑说明Embedding的output_dim128是微博语料的合理嵌入维度太大参数多且慢。SpatialDropout1D按嵌入维度整列丢弃比普通 dropout 更适合文本。LSTM 的recurrent_dropout在 GPU 上会拖慢训练CPU 上更明显可以设 0。batch_size64平衡速度和梯度稳定性。参数上epochs10配合早停通常 5-8 轮就收敛。3.4 BERTDNN微调策略与显存权衡5.bert.ipynb是这份资源里最重的一环model/bert_dnn_8.model是训练好的权重。BERT 微调的核心是学习率要小通常 2e-5 到 5e-5太大直接灾难性遗忘。DNN 接在 BERT 的[CLS]向量后面做分类。from transformers import BertTokenizer, TFBertForSequenceClassification import tensorflow as tf tokenizer BertTokenizer.from_pretrained(bert-base-chinese) enc tokenizer(texts, paddingmax_length, truncationTrue, max_length64, return_tensorstf) model TFBertForSequenceClassification.from_pretrained( bert-base-chinese, num_labels2) optimizer tf.keras.optimizers.Adam(learning_rate2e-5) model.compile(optimizeroptimizer, lossmodel.compute_loss, metrics[accuracy]) model.fit({input_ids: enc[input_ids], attention_mask: enc[attention_mask]}, y_train, batch_size16, epochs3, validation_split0.1)逻辑说明max_length64和 LSTM 分支保持一致便于对比。learning_rate2e-5是 BERT 微调的安全区间超过 5e-5 容易崩。batch_size16是显存和效果的折中显存不够降到 8。epochs3足够BERT 微调轮数多了必过拟合。参数上bert-base-chinese是中文基座如果追求更高精度可以换更大模型但显存和推理时间成倍增长。4. 避坑与排查跑这份源码最容易翻车的五个地方4.1 现象jieba 分词后模型精度反而下降原因默认jieba.lcut会把「不好」切成「不」「好」如果停用词表又把「不」滤掉否定信息彻底丢失。解决加载停用词时保留否定词并在分词后检查是否出现「不/没/无」被误删。我一般会单独统计一下训练集里否定词的出现频次确认没被清空。4.2 现象LSTM 训练 loss 不降准确率卡在 50%原因pad_sequences的maxlen设得过大或者嵌入层没加masking填充的零被当成真实词参与计算。解决把maxlen降到 64 以内并在Embedding层加mask_zeroTrue让 LSTM 跳过填充位。另外检查标签是否做了 0/1 映射别把原始字符串标签直接喂进去。4.3 现象BERT 微调报显存不足OOM原因batch_size太大或max_length太长注意力矩阵是 O(n²) 增长。解决先把batch_size降到 8max_length降到 64。如果还不够用梯度累积模拟大 batch。别一上来就上 128 长度微博文本用不上。4.4 现象notebook 里路径报错找不到 data 或 model原因notebook 的工作目录和项目根目录不一致相对路径失效。解决在 notebook 开头统一用os.chdir切到项目根或者把路径写成基于__file__的绝对路径。我习惯在第一个 cell 里打印os.getcwd()确认当前在哪。4.5 现象requirements.txt 装完仍缺包或版本冲突原因深度学习框架版本和 transformers、sklearn 之间有兼容矩阵直接pip install -r可能拉到不匹配的版本。解决先建虚拟环境按tensorflow → transformers → sklearn → xgboost的顺序装遇到冲突再单独降级。requirements.txt里的版本号别随意改改了要重新验证。5. 从基线到可用模型对比与二次开发的落点跑完五个模型后最有价值的不是哪个精度最高而是拿到一张对比表知道在微博短文本上各方法的边界在哪。我一般会固定同一份测试集把准确率、F1、训练时间、推理时间四项拉出来。模型准确率区间训练时间适用场景朴素贝叶斯0.78-0.82秒级快速基线、数据量小线性 SVM0.82-0.86秒级传统方法首选XGBoost0.80-0.84分钟级需要特征重要性分析LSTM0.85-0.88分钟级需要语序信息BERTDNN0.90-0.93小时级GPU追求精度、有算力这张表是经验区间具体数值取决于数据划分和随机种子。二次开发时我建议从两个方向切入一是换预训练模型把bert-base-chinese换成 RoBERTa 或 ERNIE通常能再涨 1-2 个点二是做数据增强微博里的反讽和口语表达是主要误差来源可以用同义词替换或回译扩充训练集。验证模型是否真的可用别只看测试集准确率。我习惯抽 50 条真实微博手动标注和模型预测做混淆矩阵重点看反讽和双重否定的样本。如果这两类错得多说明模型学的是表面词而不是语义这时候加数据比调参有用。从那以后我每次拿到情感分析项目都强制先跑一遍否定词保留检查再固定随机种子做五折交叉验证最后才看单次结果。希望帮到你。本文还有配套的精品资源点击获取
返回列表