在自然语言处理领域,情感分析是最经典、最具实用价值的任务之一。本文将带你从零开始,完成一套完整的微博文本四分类情感分析实战:基于公开微博数据集,完成数据读取→词汇表构建→数据预处理→TextRNN 模型搭建→模型训练与评估全流程,最终实现喜悦、愤怒、厌恶、低落四种情感的自动分类。
一、项目背景与数据集介绍
本项目使用微博四分类情感数据集(simplifyweibo_4_moods.csv),数据集包含带标签的微博文本,标签对应四种核心情感:
• 0:喜悦
• 1:愤怒
• 2:厌恶
• 3:低落
我们的目标是训练一个深度学习模型,自动识别微博文本对应的情感类别,适用于舆情监控、用户反馈分析等实际场景。
二、完整代码
第一步先对数据集进行基础分析,查看数据总量与各类别分布,确保数据均衡性:
读取微博情感数据集,统计数据集总规模、各情感类别样本数量,随机查看10条样本,快速了解数据格式和分布特点,为后续预处理提供依据。
python
import pandas as pd
fill_all=pd.read_csv('simplifyweibo_4_moods.csv')
moods={0:'喜悦',1:'愤怒',2:'厌恶',3:'低落'}
print('微博总数目:%d' % fill_all.shape[0])
for label,mood in moods.items():
print('微博数目({}):{}'.format(mood,fill_all[fill_all.label==label].shape[0]))
print(fill_all.sample(10))
运行结果:
bash
C:\Users\Dell\AppData\Local\Programs\Python\Python39\python.exe D:\software\Pycharm\循环卷积神经RNN\Data_presentation.py
微博总数目:361743
微博数目(喜悦):199495
微博数目(愤怒):51714
微博数目(厌恶):55267
微博数目(低落):55267
label review
243030 1 还真没见过
218636 1 親 熱 啊。儿童节,真的好怀念当时的食物------"崩爆米花"。其实,我们更怀念那个能够"爆炸"的...
104109 0 一起来吧靠摘这个发家致富,然后走上康庄大道吧~ ~ 谁打翻钻石,在夜空挥霍.
246811 1 真的去希腊了..对说:谢娜同学,你的电话咋关机了,还不和我联系,到时候那谁那事就来不及了,哈...
179509 0 怎么我没有我纯呀!原来没名字的才是纯!这只猫咪,好趣致~ 好纯正。你丫哪个星座的?您的星座血...
310795 3 其实..如果放假时间没有我妈我婆婆我外公对房间里不知道在做社么的我实行"快吃饭"的轮番轰炸,...
142874 0 顺便说点什么吧...我承认~ ~ ~ 我是王导的侧脸控~ ~ ~
137848 0 估计周杰伦要是看到了得伤心了。要了亲命了...优乐美卖不动了KAO 我就知道你坏!!大坏蛋!!凤...
227547 1 你滷 味!等天收啦你個 爛 地方!你妹的穷死你了!穷死拉倒破地方!唔药医,第一次了解菲律宾这...
203957 1 你讨厌啦我宝宝喝多了可漂亮了喝多了?金牌大风。金牌大风。金牌大风。。哈哈哈哈哈哈哈。。。
情感分析词的词表onehat建立(构建词汇表(Vocab))
NLP 模型无法直接处理文本,需要将文字转换为数字,因此我们需要构建词汇表,统计文本中高频词汇,为每个字分配唯一索引:
python
from tqdm import tqdm
import pickle as pkl
MAX_VOCAB_SIZE = 4760
UNK,PAD = '<UNK>','<PAD>'
def build_vocab(file_path,max_size,min_freq):
tokenizer = lambda x:[y for y in x]
# tokenizer = lambda x:[y for y in x]
# 拆开写
# def tokenizer(x):
# ls=[]
# for y in x:
# ls.append(y)
# return ls
vocab_dic = {}
with open(file_path,'r',encoding='UTF-8') as f:
i = 0
for line in tqdm(f):
if i ==0:
i+=1
continue
lin = line[2:].strip()
if not lin:
continue
for word in tokenizer(lin):
vocab_dic[word]=vocab_dic.get(word,0)+1
vocab_list = sorted([_ for _ in vocab_dic.items() if _[1] > min_freq],key=lambda x:x[1],reverse =True )[:max_size]
vocab_dic = {word_count[0]:idx for idx,word_count in enumerate(vocab_list)}
vocab_dic.update({UNK:len(vocab_dic),PAD:len(vocab_dic)+1})
print(vocab_dic)
pkl.dump(vocab_dic,open('simplifyweibo_4_moods.pkl','wb'))
print(f'Vocab size:{len(vocab_dic)}')
return vocab_dic
if __name__=="__main__":
vocab = build_vocab('simplifyweibo_4_moods.csv',MAX_VOCAB_SIZE,3)
print('vocab')
运行结果:

评论转化为独热编码
读取 vocab_create.py 生成的词汇表文件,对原始文本进行按字切分、文本转索引、统一文本长度等预处理,划分训练集、验证集、测试集,同时实现自定义数据集迭代器,将预处理后的数据按批次转换为PyTorch张量,适配模型训练。
python
from tqdm import tqdm
import pickle as pkl
import random
import torch
UNK,PAD ='<UNK>','<PAD>'#未知字,padding符号
def load_dataset(path,pad_size=70):#pad_size表示超过70截断,不超过70用UNK填充
contents=[]#用来存储转换为数值标号的句子
vocab = pkl.load(open('simplifyweibo_4_moods.pkl','rb'))#读取vocab 文作
tokenizers = lambda x:[y for y in x]#创建一个还函数
with open(path,'r',encoding='UTF-8') as f:
i = 0
for line in tqdm(f):
if i ==0:
i +=1
continue
if not line:#是不是空行
continue
label = int(line[0])
content = line[2:].strip('\n')
words_line=[]
token = tokenizers(content)# 将每一行的内容进行分字
seq_len = len(token)#获取一行实际内容的长度
if pad_size:#判断每条评论是否超过70 个字
if len(token)<pad_size:# 如果一行的字少于70,则补充<PAD>
token.extend([PAD] *(pad_size-len(token)))
else:#如果一行的字大于70,则只取前70个字
token = token[:pad_size]#如果一条评论内的字大于或等于70个字,索引的切分
seq_len=pad_size#当前评论的长度
for word in token:
words_line.append(vocab.get(word,vocab.get(UNK)))#把每一条评论转换为独热编码
contents.append((words_line,int(label),seq_len))#独热编码,标签值,句子长度
random.shuffle(contents)#打乱顺序
train_data = contents[: int(len(contents)*0.8)]#前80%的评论数据作为训练集
dev_data = contents[int(len(contents)*0.8): int(len(contents)*0.9)]#把80%-90%的评论数据集作为验证数据
test_data = contents[int(len(contents)*0.9):]#90%------最后的数据作为测试数据集
return vocab,train_data,dev_data,test_data
class DatasetIterater(object):
#将数据batches切分成Batchs_size的包
def __init__(self,batches,batch_size,device):
self.batches=batches
self.n_batches=len(batches)//batch_size
self.residue = False
if len(batches)% self.n_batches!=0:
self.residue = True
self.index=0
self.device=device
def _to_tensor(self,datas):#自己定义的一个函数,并不是内置的函数功能
x = torch.LongTensor([_0] for _ in datas).to(self.device)#评论内容
y = torch.LongTensor(_[1] for _ in datas).to(self.device)
#pad前的长度
seq_len = torch.LongTensor([_[2] for _ in datas]).to(self.device)
return(x,seq_len),y
def __next__(self):
if self.residue and self.index == self.n_batches:
batches = self.batches[self.index * self.bach_size:len(self.batches)]
self.index+=1
batches = self._to_tensor(batches)
return batches
elif self.index >self.n_batches:#当读取完最后一个batch时:
self.index = 0
raise StopIteration
else:
batches = self.batches[self.index * self.batche_size:(self.index+1)*self.batch_size]
self.index +=1
batches = self._to_tensor(batches)
return batches
def __iter__(self):
return self
def __len__(self):
if self.residue:
return self.n_batches +1
else:
return self.n_batches
if __name__=="__main__":
vocab,train_data,dev_data,test_data=load_dataset('simplifyweibo_4_moods.csv')
print(train_data,dev_data,test_data)
print('结束')
运行结果:

TextRNN 模型搭建
我们使用双向 LSTM搭建 TextRNN 模型,LSTM 能有效捕捉文本的上下文依赖关系,双向结构可同时利用前文和后文信息:
python
import torch.nn as nn
class Model(nn.Module):
def __init__(self,embedding_pretrained,n_vocab,embed,num_classes):
super(Model,self).__init__()
# 初始化嵌入层:优先使用预训练词向量,无则使用随机初始化
if embedding_pretrained is not None:
# 加载预训练词向量,指定填充符索引,设置freeze=False允许微调
self.embedding = nn.Embedding.from_pretrained(embedding_pretrained,padding_idx=n_vocab-1,freeze=False)
else:
# 随机初始化嵌入层,输入维度为词汇表大小,输出维度为词向量维度
self.embedding = nn.Embedding(n_vocab,embed,padding_idx=n_vocab-1)
# 定义双向LSTM层:输入维度=词向量维度,隐藏层维度=128,3层,批量优先, dropout=0.3防止过拟合
self.lstm = nn.LSTM(
input_size=embed, # 词向量维度
hidden_size=128, # 记忆单元大小
num_layers=3, # 3层LSTM
bidirectional=True, # 双向LSTM
batch_first=True,
dropout=0.3 # 防止过拟合
)
# 定义全连接层:输入维度=双向LSTM输出维度(128*2),输出维度=类别数量(四分类)
self.fc = nn.Linear(128*2,num_classes)
# 前向传播:定义模型的计算流程
def forward(self,x):
# 提取输入文本的索引部分(x为元组,第一元素为文本索引)
x,_ = x
# 文本嵌入:将文本索引转换为词向量
out=self.embedding(x)
# LSTM特征提取:输出包含所有时间步特征,此处暂不使用隐藏状态
out = self.lstm(out)
# 取最后一个时间步的特征输入全连接层,输出分类结果
out=self.fc(out[:,-1,:])
return out
兼容腾讯预训练词向量,提升模型效果;
3 层双向 LSTM,增强特征提取能力;
Dropout 防止过拟合,提升模型泛化性
一、定义
LSTM 是循环神经网络(RNN)的改进版,全称长短时记忆网络(Long Short-Term Memory),核心定位是解决传统 RNN 的 "短时记忆缺陷",能捕捉长序列(文本、语音等连续关联数据)中的上下文依赖,是深度学习中处理长序列任务的核心模型。
二、核心原理
核心是「门控机制 + 细胞状态」,类比 "带记忆的管家管理仓库",具体的:
-
细胞状态(记忆仓库):贯穿整个序列,负责存储长期信息,像传送带一样稳定传递,避免信息丢失;
-
三大门控(管家的筛选动作):
- 遗忘门:筛选并丢弃无用信息(如文本中 "我""的" 等无关词);
- 输入门:筛选并存储新的有用信息,更新细胞状态;
- 输出门:提取当前需要的记忆,作为输出并传递给下一个时间步。
三、核心作用与优势
-
解决传统 RNN 的 "梯度消失" 问题,让早期信息能稳定传递到后期;
-
捕捉长序列依赖,能记住长文本、时间序列中的关键关联信息(如理解句子上下文);
-
适配多种场景,尤其在自然语言处理(文本分类、机器翻译等)和时间序列预测中应用广泛。

LSTM结构图
整合前文三个文件的输出结果,配置训练相关参数(随机种子、运行设备),加载预训练词向量,初始化TextRNN模型,调用训练函数(train)启动模型的训练、验证与测试,是整个实战的"启动入口"。
python
# 导入所需库和自定义模块
import torch # 深度学习核心库
import numpy as np # 数值计算库
import load_dataset,TextRNN # 自定义数据加载、模型模块
from train_eval_test import train # 导入训练函数
# 自动选择运行设备(GPU优先,无则用CPU)
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
# 设置随机种子,保证实验结果可复现
np.random.seed(1)# 设置numpy随机种子
torch.manual_seed(1)# 设置torch随机种子
torch.cuda.manual_seed_all(1)# 设置所有CUDA设备随机种子
torch.backends.cudnn.deterministic=True# 固定cudnn算法,确保结果一致
# 加载预处理后的数据集(词汇表、训练/验证/测试集)
vocab,train_data,dev_data,test_data = load_dataset.load_dataset('simplifyweibo_4_moods.csv')
# 构建批次迭代器,每批128条数据,适配模型批量训练
train_iter = load_dataset.DatasetIterater(train_data,128,device)# 训练集迭代器
dev_iter = load_dataset.DatasetIterater(dev_data,128,device)# 验证集迭代器
test_iter = load_dataset.DatasetIterater(test_data,128,device)# 测试集迭代器
# 加载腾讯预训练词向量,转换为torch张量
embedding_pretrained = torch.tensor(np.load('embedding_Tencent.npz')["embedding"].astype('float32'))
# 配置词向量维度(优先用预训练维度,无则默认200维)
embed = embedding_pretrained.size(1) if embedding_pretrained is not None else 200# 词向量维度
# 定义情感类别,计算类别数量(四分类)
class_list=['喜悦','愤怒','厌恶','低落']
num_classes = len(class_list)
# 初始化TextRNN模型,转移到指定设备
model = TextRNN.Model(embedding_pretrained,len(vocab),embed,num_classes).to(device)
# 启动模型训练、验证与测试
train(model,train_iter,dev_iter,test_iter,class_list)