在自然语言处理领域情感分析是最经典、最具实用价值的任务之一。本文将带你从零开始完成一套完整的微博文本四分类情感分析实战基于公开微博数据集完成数据读取→词汇表构建→数据预处理→TextRNN 模型搭建→模型训练与评估全流程最终实现喜悦、愤怒、厌恶、低落四种情感的自动分类。一、项目背景与数据集介绍本项目使用微博四分类情感数据集simplifyweibo_4_moods.csv数据集包含带标签的微博文本标签对应四种核心情感• 0喜悦• 1愤怒• 2厌恶• 3低落我们的目标是训练一个深度学习模型自动识别微博文本对应的情感类别适用于舆情监控、用户反馈分析等实际场景。二、完整代码1.第一步先对数据集进行基础分析查看数据总量与各类别分布确保数据均衡性读取微博情感数据集统计数据集总规模、各情感类别样本数量随机查看10条样本快速了解数据格式和分布特点为后续预处理提供依据。import pandas as pd fill_allpd.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.labellabel].shape[0])) print(fill_all.sample(10))运行结果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 你讨厌啦我宝宝喝多了可漂亮了喝多了金牌大风。金牌大风。金牌大风。。哈哈哈哈哈哈哈。。。2.第二步构建词汇表Vocab——情感分析词的词表onehat建立NLP 模型无法直接处理文本需要将文字转换为数字因此我们需要构建词汇表统计文本中高频词汇为每个字分配唯一索引# 导入进度条库处理文件时显示进度 from tqdm import tqdm # 导入pickle用于把字典保存成文件 import pickle as pkl # 配置参数 # 词表最大容量最多保留 4760 个常用字 MAX_VOCAB_SIZE 4760 # 两个特殊符号 # UNK 代表不认识的字生僻字 # PAD 代表填充符号把句子补成一样长 UNK, PAD UNK, PAD # 核心函数构建词表 # 函数作用读取CSV文本 → 统计字频 → 生成【字→数字ID】词典 → 保存文件 def build_vocab(file_path, max_size, min_freq): # 定义分词器把一句话拆成【一个一个的字】 tokenizer lambda x: [y for y in x] # 创建空字典用来统计【每个字出现多少次】 vocab_dic {} # 打开CSV文件读取微博内容 with open(file_path, r, encodingUTF-8) as f: i 0 # 用来跳过第一行表头 # 逐行读取文件tqdm显示进度条 for line in tqdm(f): # 第一行是表头 label,review直接跳过 if i 0: i 1 continue # 去掉每行前2个字符标签逗号并去除空格 # 例如0,今天很开心 → 变成今天很开心 lin line[2:].strip() # 如果这一行是空的跳过 if not lin: continue # 把句子拆成单个字然后统计每个字出现次数 for word in tokenizer(lin): # 字计数 1 vocab_dic[word] vocab_dic.get(word, 0) 1 # 筛选常用字 # 1. 只保留出现次数 min_freq 的字 # 2. 按出现次数从高到低排序 # 3. 最多保留 max_size 个字4760 vocab_list sorted([_ for _ in vocab_dic.items() if _[1] min_freq], keylambda x: x[1], reverseTrue)[:max_size] # 给每个字分配数字ID # 把字变成字典格式{字1:0, 字2:1, 字3:2 ...} 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文件给后面训练模型使用 pkl.dump(vocab_dic, open(simplifyweibo_4_moods.pkl, wb)) # 打印词表大小 print(fVocab size:{len(vocab_dic)}) # 返回生成好的词表 return vocab_dic # 程序入口运行这里开始 if __name__ __main__: # 调用函数生成词表 # 参数CSV文件路径最大词数最少出现次数3次以上才保留 vocab build_vocab(simplifyweibo_4_moods.csv, MAX_VOCAB_SIZE, 3) # 打印提示表示执行完成 print(vocab)运行结果3.第三步评论转化为独热编码读取 vocab_create.py 生成的词汇表文件对原始文本进行按字切分、文本转索引、统一文本长度等预处理划分训练集、验证集、测试集同时实现自定义数据集迭代器将预处理后的数据按批次转换为PyTorch张量适配模型训练。from tqdm import tqdm import pickle as pkl import random import torch UNK,PAD UNK,PAD#未知字padding符号 def load_dataset(path,pad_size70):#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,encodingUTF-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_lenpad_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.batchesbatches self.n_batcheslen(batches)//batch_size self.residue False if len(batches)% self.n_batches!0: self.residue True self.index0 self.devicedevice 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.index1 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.index1)*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_dataload_dataset(simplifyweibo_4_moods.csv) print(train_data,dev_data,test_data) print(结束)运行结果4.第四步TextRNN 模型搭建我们使用双向 LSTM搭建 TextRNN 模型LSTM 能有效捕捉文本的上下文依赖关系双向结构可同时利用前文和后文信息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: # 加载预训练词向量指定填充符索引设置freezeFalse允许微调 self.embedding nn.Embedding.from_pretrained(embedding_pretrained,padding_idxn_vocab-1,freezeFalse) else: # 随机初始化嵌入层输入维度为词汇表大小输出维度为词向量维度 self.embedding nn.Embedding(n_vocab,embed,padding_idxn_vocab-1) # 定义双向LSTM层输入维度词向量维度隐藏层维度1283层批量优先 dropout0.3防止过拟合 self.lstm nn.LSTM( input_sizeembed, # 词向量维度 hidden_size128, # 记忆单元大小 num_layers3, # 3层LSTM bidirectionalTrue, # 双向LSTM batch_firstTrue, dropout0.3 # 防止过拟合 ) # 定义全连接层输入维度双向LSTM输出维度128*2输出维度类别数量四分类 self.fc nn.Linear(128*2,num_classes) # 前向传播定义模型的计算流程 def forward(self,x): # 提取输入文本的索引部分x为元组第一元素为文本索引 x,_ x # 文本嵌入将文本索引转换为词向量 outself.embedding(x) # LSTM特征提取输出包含所有时间步特征此处暂不使用隐藏状态 out self.lstm(out) # 取最后一个时间步的特征输入全连接层输出分类结果 outself.fc(out[:,-1,:]) return out兼容腾讯预训练词向量提升模型效果3 层双向 LSTM增强特征提取能力Dropout 防止过拟合提升模型泛化性一、定义LSTM 是循环神经网络RNN的改进版全称长短时记忆网络Long Short-Term Memory核心定位是解决传统 RNN 的 “短时记忆缺陷”能捕捉长序列文本、语音等连续关联数据中的上下文依赖是深度学习中处理长序列任务的核心模型。二、核心原理核心是「门控机制 细胞状态」类比 “带记忆的管家管理仓库”具体的细胞状态记忆仓库贯穿整个序列负责存储长期信息像传送带一样稳定传递避免信息丢失三大门控管家的筛选动作遗忘门筛选并丢弃无用信息如文本中 “我”“的” 等无关词输入门筛选并存储新的有用信息更新细胞状态输出门提取当前需要的记忆作为输出并传递给下一个时间步。三、核心作用与优势解决传统 RNN 的 “梯度消失” 问题让早期信息能稳定传递到后期捕捉长序列依赖能记住长文本、时间序列中的关键关联信息如理解句子上下文适配多种场景尤其在自然语言处理文本分类、机器翻译等和时间序列预测中应用广泛。LSTM结构图训练 / 验证 / 测试 train_eval_test.py# coding: UTF-8 import numpy as np import torch import torch.nn as nn import torch.nn.functional as F from sklearn import metrics #机器学习 import time def evaluate(class_list, model, data_iter, testFalse):#验证集的处理 model.eval()#进入测试模型将model的w设置为只读模式w中的值都没被修改的权限保护模型不被修改。 loss_total 0 predict_all np.array([], dtypeint) labels_all np.array([], dtypeint) with torch.no_grad(): #一个上下文管理器关闭梯度计算。当你确认不会调用Tensor.backward()的时候。这可以减少计算所用内存消耗。 for texts, labels in data_iter: outputs model(texts)#它就是输出。 loss F.cross_entropy(outputs, labels) loss_total loss labels labels.data.cpu().numpy() predic torch.max(outputs.data, 1)[1].cpu().numpy()#代表的就是输出的结果 labels_all np.append(labels_all, labels) predict_all np.append(predict_all, predic) acc metrics.accuracy_score(labels_all, predict_all) if test: report metrics.classification_report(labels_all, predict_all, target_namesclass_list, digits4) return acc, loss_total / len(data_iter), report return acc, loss_total / len(data_iter) def test(model, test_iter , class_list): # test # model.load_state_dict(torch.load(TextRNN.ckpt)) model.eval()#进入测试模式w只有读的权限 start_time time.time()#当前的时间 test_acc, test_loss, test_report evaluate(class_list, model, test_iter, testTrue)# msg Test Loss: {0:5.2}, Test Acc: {1:6.2%} print(msg.format(test_loss, test_acc)) print(test_report) def train(model, train_iter, dev_iter, test_iter,class_list): model.train() #进入训练模式允许model训练的权限w optimizer torch.optim.Adam(model.parameters(), lr1e-3)#优化器 total_batch 0 # 记录进行到多少batch dev_best_loss float(inf) #表示无穷大inf 无穷大 last_improve 0 # 记录上次验证集loss下降的batch数 flag False # 记录是否很久没有效果提升 epochs 20 #设置训练次数 for epoch in range(epochs): #训练次数 print(Epoch [{}/{}].format(epoch 1, epochs)) for i, (trains, labels) in enumerate(train_iter):#(([23,34,..,13],79),2)__getitem__,里面包含__next__ #经过DatasetIterater中的_to_tensor返回的数据格式为(x, seq_len), y outputs model(trains) loss F.cross_entropy(outputs, labels)# loss_fn nn·)创建一个交叉熵损失函数层 model.zero_grad() loss.backward() optimizer.step() if total_batch % 100 0: # 每多少轮输出在训练集和验证集上的效果 predic torch.max(outputs.data, 1)[1].cpu() train_acc metrics.accuracy_score(labels.data.cpu(), predic)# dev_acc, dev_loss evaluate(class_list, model, dev_iter) #将验证数据集传入模型获得验证结果 if dev_loss dev_best_loss: dev_best_loss dev_loss #保存最优模型 torch.save(model.state_dict(), TextRNN.ckpt) last_improve total_batch #保存最优模型的batch值 800batchs 21000 msg Iter: {0:6}, Train Loss: {1:5.2}, Train Acc: {2:6.2%}, Val Loss: {3:5.2}, Val Acc: {4:6.2%} print(msg.format(total_batch, loss.item(), train_acc, dev_loss, dev_acc)) model.train() total_batch 1 if total_batch - last_improve 10000: # 验证集loss超过1000batch没下降结束训练 4000 14001 print(No optimization for a long time, auto-stopping...) flag True if flag: break # writer.close() test(model, test_iter,class_list) #作业完成输入一句话得到最终的结果。主程序main.py整合前文三个文件的输出结果配置训练相关参数随机种子、运行设备加载预训练词向量初始化TextRNN模型调用训练函数train启动模型的训练、验证与测试是整个实战的“启动入口”。# 导入所需库和自定义模块 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.deterministicTrue# 固定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)运行结果