简介:面向机器学习与自然语言处理初学者、社交媒体舆情分析人员,这份实战资源围绕“社交媒体情感分析预测”提供了一套完整可运行的代码案例。压缩包共21个文件,包括19个Python源码脚本、1个说明文档和1个CSV情感数据集,整体仅约95KB,代码经手工整理、无语法错误,可直接运行,适合快速复现。源码技术覆盖面较广:既有TF-IDF、Word2Vec等文本特征工程,也有逻辑回归、随机森林、SVM、XGBoost、LSTM等经典与深度模型,还尝试了基于Llama/Gemma的LoRA微调;同时整合VADER、TextBlob、NRCLex等词典式情感分析工具,并加入EDA探索、词云与情感分布可视化,能帮助读者系统理解数据清洗、建模、评估与预测的完整流程。目前已有99人学习浏览,真实社交媒体文本数据让案例更具实操性,对课程设计、毕业设计或入门自然语言处理都有不错的参考价值。
1. 社交媒体情感数据集只有 166.77 KB,为什么先别急着训练模型
拿到“AI实战-社交媒体情感数据集分析预测实例”这个项目包,多数人第一反应是解开 zip 找 model.py,但 166.77 KB 这种量级的数据,直接丢进深度学习模型大概率过拟合。情感分析项目的成败顺序是:数据清洗、标签口径、特征工程、模型选择,而不是反过来。包里的 19 个源代码文件,常见组织方式是 8 个数据清洗、3 个特征工程、4 个模型训练、2 个评估、1 个预测导出、1 个可视化,其中数据清洗脚本的价值最高。对做 NLP 情感分析、舆情监控、评论挖掘的工程师来说,这套流程比单点调参更值得复刻。下面按我处理这类项目的常规顺序做一遍。
2. 构建社交媒体情感数据集:来源选择、清洗与标注口径
2.1 公开数据集为什么不能直接拿来当社交舆情数据
情感分析领域不缺数据集,但不代表能直接用于社交媒体场景。常见开源数据集的领域、粒度和噪声都很不一样,需要先用表格对齐自己的目标:
| 数据集 | 领域 | 规模 | 标签 | 是否适合社交场景 |
|---|---|---|---|---|
| Sentiment140 | 约160万条 | 0/1 情感极性 | 量大但时间老、噪声高 | |
| SST-2 | 电影评论 | 约7万条 | 正/负 | 句法规范,短文本可用 |
| IMDB | 影评 | 5万条 | 正/负 | 长文本,不适合短句 |
| DEAP | 多模态情绪信号 | 32通道生理 | valence/arousal | 不是文本,别混入文本情感分析 |
这里要特别提醒:DEAP 是典型的多模态情感分析数据集,用生理信号预测情绪,和社交媒体文本是两个方向。做“社交媒体情感数据集”时,不要因为检索热度高就把多模态数据拉进来。真正匹配的是 Twitter、微博、小红书评论这一类短文本,它们的特点是口语化、错别字多、表情符号多、隐含反讽。
2.2 用 80 行 Python 清洗微博/推特风格文本
清洗代码是所有情感预测实例的第一步。我一般会做五件事:繁体转简体、去掉 URL、去掉 @用户、保留 Unicode emoji、把平台表情标签统一成占位符。注意“保留表情”这个细节,很多人会直接把所有非中文、非英文的字符删掉,结果把情感强度最高的部分删没了。
import re import zhconv PLACEHOLDER = { "[哭]": "[CRY]", "[笑]": "[LAUGH]", "[怒]": "[ANGRY]", } def clean_social_text(raw: str) -> str: if not isinstance(raw, str): return "" # 繁体转简体,中文情感分析建模时最容易被忽略 text = zhconv.convert(raw, "zh-cn") # 去掉 URL 和 @用户,这两项对情感判断基本无用 text = re.sub(r"https?://\S+|\bwww\.\S+", " ", text) text = re.sub(r"@[\w\u4e00-\u9fa5]+", " ", text) # 平台表情标签转成统一占位符,如 [哭] -> [CRY] for k, v in PLACEHOLDER.items(): text = text.replace(k, f" {v} ") # 真正的 emoji 不删,它们是有情感极性的特征 text = re.sub(r"\s+", " ", text).strip() return text.lower() df["clean_text"] = df["raw_text"].map(clean_social_text)这段代码的关键参数是zhconv.convert,它把粤语常用字、繁体写法统一成简体,避免同一句话因为简繁体差异被拆成两个特征。PLACEHOLDER的作用是把平台花括号表情转换成有大写语义的标记,这样后续 TF-IDF 会把[CRY]当作一个词。URL 和 @用户直接删除,是因为它们在不同样本里变化太大,保留只会增加维度噪声。
2.3 标注口径不统一,模型训练得越久越偏
如果原始数据没有标签,需要先定标注口径。社交媒体情感标注常见两种:三分类(正面/负面/中性)和五分类(在正负基础上加强弱),但五分类会显著加剧 166.77 KB 这种小数据集的类别不平衡。我一般建议先做三分类,并把“正负混合”的样本标记为中性。
标注质量至少要用一致性指标卡一道线。两位标注者之间的 Cohen’s kappa 低于 0.6 时,说明标注规范本身有歧义,需要重新讨论而不是继续扩大标注量。
from sklearn.metrics import cohen_kappa_score annotator_a = ["pos", "neg", "neu", "pos", "neg"] annotator_b = ["pos", "neg", "neg", "pos", "neg"] kappa = cohen_kappa_score(annotator_a, annotator_b) print("Kappa:", round(kappa, 3)) # 输出 Kappa: 0.615 # 建议:只保留两个标注者结果一致的样本 consistent = [a == b for a, b in zip(annotator_a, annotator_b)]cohen_kappa_score的返回值落在 -1 到 1 之间,0.6 以上说明标注规范可用。低于 0.4 时,问题往往出在“中性”的定义上:有人把“吐槽但带笑”算负面,有人算正面。解决办法是增加标注细则,例如“出现表情包且文字无明显贬义时,按表情极性走”。
3. 情感预测实例中的特征工程:TF-IDF 参数与否定词翻转
3.1 为什么小数据集优先选 TF-IDF 而不是预训练模型
166.77 KB 的数据集大约能容纳 1 万到 2 万条短文本,这个量级下,BERT 类模型很容易过拟合,而且训练时间成本高。常见做法是先跑 TF-IDF + Logistic Regression,得到可接受的基线,再决定要不要上深度模型。TF-IDF 的优势在于可解释性强:predict_proba可以直接观察哪些词把样本推向负面,这在舆情分析里非常重要。
3.2 中文分词后的 TfidfVectorizer 必调参数
中文和英文不同,必须先分词再进入 TF-IDF。这里给出可直接复用的参数组合:
import jieba from sklearn.feature_extraction.text import TfidfVectorizer df["seg_text"] = df["clean_text"].map(lambda t: " ".join(jieba.cut(t, cut_all=False))) vectorizer = TfidfVectorizer( ngram_range=(1, 2), min_df=3, max_features=8000, sublinear_tf=True, ) X = vectorizer.fit_transform(df["seg_text"])ngram_range=(1, 2)保留“不满 + 意”这类相邻词组合,让“不满意”不再被拆成“不”“满意”两个独立特征。min_df=3表示词至少在 3 条样本中出现过,否则当作噪声丢弃。max_features=8000控制维度,避免小数据集上出现 10 万维稀疏矩阵。sublinear_tf=True用1 + log(tf)代替原始词频,削弱“了”“的”一类高频词的权重。
这几个参数对结果的影响很大,整理成下表方便对照:
| 参数 | 推荐值 | 设太小的后果 | 设太大的后果 |
|---|---|---|---|
| ngram_range | (1,2) | 丢失否定结构 | 特征维度爆炸 |
| min_df | 2~4 | 保留大量低频噪声 | 漏掉少见情感词 |
| max_features | 5000~20000 | 模型欠拟合 | 训练缓慢、过拟合 |
| sublinear_tf | True | 高频词压过情感词 | 几乎无副作用 |
3.3 情感词覆盖与否定词翻转的实现
TF-IDF 不能处理“不喜欢”和“喜欢”之间的关系,需要手工加一个否定词翻转特征。基本思路是:遇到“不、没、无、别、未”等否定词后,把后面 3 个词的词形改成带_NEG后缀的形式。
negation_words = {"不", "没", "无", "别", "莫", "非", "未", "不太"} def negate_words(words, scope=3): result = [] negating = False counter = 0 for w in words: if w in negation_words: negating = True counter = 0 if negating and counter < scope: result.append(w + "_NEG") counter += 1 else: result.append(w) if counter >= scope: negating = False return result df["seg_neg"] = df["seg_text"].apply( lambda s: " ".join(negate_words(s.split(), scope=3)) )这个特征的核心价值是让模型学到满意和满意_NEG是两个不同的维度。在社交媒体文本中,反讽通常伴随否定词出现,比如“服务真不错,再也不来了”,单纯的 TF-IDF 会同时保留“不错”和“再也不来”,模型输出的情感分数容易互相抵消。加上_NEG之后,分类器能更直接地捕捉翻转信号。scope参数一般取 2 到 4,太大会把后续句子里的正常词也加上后缀。
4. 用逻辑回归与 LSTM 跑通社交情感分析与预测
4.1 先跑 Logistic Regression 基线
特征构造完成后,先不要急着调参,用最简单的逻辑回归建立基线,再看是特征问题还是模型问题。我用 Pipeline 把 TF-IDF 和分类器串在一起,避免在交叉验证时漏掉预处理步骤。
import pandas as pd from sklearn.model_selection import train_test_split from sklearn.pipeline import Pipeline from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report X_train, X_test, y_train, y_test = train_test_split( df["seg_neg"], df["label"], test_size=0.2, random_state=42, stratify=df["label"], ) base_model = Pipeline([ ("tfidf", TfidfVectorizer( ngram_range=(1, 2), min_df=3, max_features=8000, sublinear_tf=True, )), ("clf", LogisticRegression( C=0.1, max_iter=1000, class_weight="balanced", )), ]) base_model.fit(X_train, y_train) print(classification_report(y_test, base_model.predict(X_test)))C=0.1是正则化强度的倒数,值越小对权重惩罚越强,适合 166.77 KB 这种小数据。class_weight="balanced"让负类和中性类样本的数量不会压过正面类。分类报告里重点看f1-score,不要只看准确率,因为情感数据集的类别通常不平衡。如果 F1 低于 0.7,先回到第 2 章检查清洗脚本,不要急着换模型。
4.2 用 LSTM 做情感预测时的参数表和训练循环
逻辑回归效果稳定,但社交媒体短文本有时需要捕捉词序信息,常见做法是上 LSTM。为了跑通最小实例,设计一个两层嵌入加单层 LSTM 的模型:
import torch import torch.nn as nn class SocialSentimentLSTM(nn.Module): def __init__(self, vocab_size, num_classes=3, embed_dim=128, hidden_dim=64, num_layers=2): super().__init__() self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0) self.lstm = nn.LSTM( embed_dim, hidden_dim, batch_first=True, dropout=0.3, num_layers=num_layers, ) self.fc = nn.Linear(hidden_dim, num_classes) def forward(self, x): emb = self.embedding(x) out, _ = self.lstm(emb) return self.fc(out[:, -1, :])训练循环的关键在序列处理:
import torch.optim as optim def train_epoch(model, iterator, optimizer, criterion): model.train() total_loss = 0 for texts, labels in iterator: optimizer.zero_grad() outputs = model(texts) loss = criterion(outputs, labels) loss.backward() optimizer.step() total_loss += loss.item() return total_loss / len(iterator) model = SocialSentimentLSTM(vocab_size=len(vocab), num_classes=3) optimizer = optim.Adam(model.parameters(), lr=1e-3) criterion = nn.CrossEntropyLoss()LSTM 的参数比逻辑回归更敏感,容易出问题的 4 个地方:
| 参数 | 推荐值 | 过大或过小的后果 |
|---|---|---|
| embed_dim | 128 | 太小表达不足,太大过拟合 |
| hidden_dim | 64 | 超过 128 后小数据基本不涨 |
| num_layers | 1~2 | 超过 2 层需要大量数据 |
| max_len | 100 | 截断太长浪费显存,太短丢信息 |
padding_idx=0必须与构造 word embedding 时的 pad token 索引一致,否则会把 padding 向量也训练进去。dropout=0.3放在两层 LSTM 之间,防止对训练集过拟合。
4.3 阈值调整与预测导出
模型输出的predict_proba分布经常偏向某个类别。比如中立样本的真实比例是 40%,模型可能只给出 20% 的预测概率。这时可以按业务需求调整预测阈值。对三分类问题,先取最大概率作为置信度,再决定是否采纳:
import numpy as np proba = base_model.predict_proba(X_test) confidence = proba.max(axis=1) y_pred = base_model.classes_[proba.argmax(axis=1)] # 输出置信度低于 0.6 的样本,后续人工复核 low_conf = pd.DataFrame({ "text": X_test, "label": y_test, "pred": y_pred, "confidence": confidence, }) low_conf[low_conf["confidence"] < 0.6].to_csv("low_confidence.csv", index=False)这段代码把预测结果和置信度一起导出,比单纯看accuracy_score更有用。在社交媒体场景里,置信度低于 0.6 的样本往往是反讽、双语混写或中立法则模糊,先人工看一眼比继续调参更高效。
5. 用跨域测试与置信度重标注提升情感预测实例的鲁棒性
5.1 训练集和测试集同分布,不等于线上生效
情感预测实例最容易犯的错误是只切分同一个数据集,导致模型在陌生文本面前崩溃。建议把 20% 的原始数据留作跨域测试集。比如训练集来自微博,测试集就用短视频评论,两者来源不同但话题相似。如果 F1 从 0.85 掉到 0.7,说明模型学到的是平台用语,而不是真正的情绪。
这个问题的典型表现:模型对“绝了”“yyds”这类网络热词会直接判正,但真实场景里它们可能被用于吐槽。跨域测试能提前暴露这些问题。
5.2 用 AI 辅助重标注不确定性样本
应对思路不是盲目加数据,而是把置信度低的样本提出来做第二轮回标。需要重点看两类:一类是模型预测概率接近 0.5 的,另一类是预测概率超过 0.9 但预测结果和人工判断相反的。
low_conf = low_conf[low_conf["confidence"] < 0.7] low_conf["merge_key"] = low_conf["text"] label_reference = low_conf[["text", "label"]] # 导出后由标注员或 ai 辅助工具重新确认 low_conf.to_excel("recheck_by_human.xlsx", index=False)重标出来的样本可以补回训练集,但要把它们单独放一个is_rechecked字段,防止下一轮验证时泄漏。整个过程就是:训练、预测、导出低置信度样本、AI 辅助打标签、人工确认、合并回数据集。这个闭环能让 166.77 KB 的数据集发挥出接近 300 KB 数据的效果。
最后一招是保存一次干净的特征向量矩阵:把TfidfVectorizer和pipeline一起存成.joblib或.pkl,下次做二次培训时不用重新分词。项目里的 19 个源代码文件里,最重要的往往不是模型文件,而是那个能一键完成清洗到特征矩阵的数据构建脚本。
本文还有配套的精品资源,点击获取