深度学习——循环神经网络(RNN)实战项目:基于PyTorch的文本情感分析

基于RNN的文本情感分析(Sentiment Analysis)


循环神经网络(RNN)实战项目:基于PyTorch的文本情感分析


一、项目背景

在自然语言处理中(NLP),情感分析 是一种常见任务,它用于判断一段文字的情感倾向(如正面、负面、中性等)。

例如:

"这部电影太棒了!" → 正面

"剧情拖沓,看得想睡觉。" → 负面

传统机器学习模型(如SVM、朴素贝叶斯)难以捕捉上下文语义,而循环神经网络(RNN)通过"记忆"序列信息,非常适合处理文本这种时间序列数据。


二、RNN 原理简述

RNN 是一种能"记住过去输入"的神经网络。

其核心结构如下:

RNN 通过循环连接,使当前时间步的输出依赖于前一个时间步的隐藏状态:

这样网络能够理解上下文,比如:

"我今天心情很好" → 正面

"我今天心情不好" → 负面

RNN 能分辨"好"和"不好"的不同语义。


三、项目目标

构建一个基于 RNN 的电影评论情感分类器,输入一句评论,输出情感类别(正面或负面)。


四、数据集说明

我们使用经典的 IMDb 电影评论数据集,包含 50,000 条影评:

数据类型 数量 内容示例
训练集 25,000 "This movie was fantastic! I loved it."
测试集 25,000 "Terrible movie, waste of time."

数据已被标注为:

  • 1:正面评论

  • 0:负面评论


五、环境与依赖

复制代码
pip install torch torchvision torchtext
pip install matplotlib

六、代码实现

1️⃣ 导入模块

复制代码
import torch
import torch.nn as nn
import torch.optim as optim
from torchtext.legacy import data, datasets
import random
import matplotlib.pyplot as plt

2️⃣ 数据加载与预处理

复制代码
SEED = 1234
torch.manual_seed(SEED)
torch.backends.cudnn.deterministic = True

# 定义字段(Field)
TEXT = data.Field(tokenize='spacy', tokenizer_language='en_core_web_sm')
LABEL = data.LabelField(dtype=torch.float)

# 加载 IMDb 数据集
train_data, test_data = datasets.IMDB.splits(TEXT, LABEL)

# 划分验证集
train_data, valid_data = train_data.split(random_state=random.seed(SEED))

print(f"训练集样本数: {len(train_data)}")
print(f"验证集样本数: {len(valid_data)}")
print(f"测试集样本数: {len(test_data)}")

3️⃣ 构建词汇表并加载预训练词向量

复制代码
TEXT.build_vocab(train_data,
                 max_size=25000,
                 vectors="glove.6B.100d",
                 unk_init=torch.Tensor.normal_)

LABEL.build_vocab(train_data)

4️⃣ 创建迭代器(批处理)

复制代码
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

BATCH_SIZE = 64

train_iterator, valid_iterator, test_iterator = data.BucketIterator.splits(
    (train_data, valid_data, test_data),
    batch_size=BATCH_SIZE,
    device=device)

5️⃣ 构建 RNN 模型

复制代码
class RNN(nn.Module):
    def __init__(self, vocab_size, embedding_dim, hidden_dim, output_dim):
        super().__init__()
        self.embedding = nn.Embedding(vocab_size, embedding_dim)
        self.rnn = nn.RNN(embedding_dim, hidden_dim)
        self.fc = nn.Linear(hidden_dim, output_dim)
    
    def forward(self, text):
        # text: [sentence_len, batch_size]
        embedded = self.embedding(text)
        output, hidden = self.rnn(embedded)
        return self.fc(hidden.squeeze(0))

6️⃣ 初始化模型与优化器

复制代码
INPUT_DIM = len(TEXT.vocab)
EMBEDDING_DIM = 100
HIDDEN_DIM = 256
OUTPUT_DIM = 1

model = RNN(INPUT_DIM, EMBEDDING_DIM, HIDDEN_DIM, OUTPUT_DIM)

# 加载预训练向量
pretrained_embeddings = TEXT.vocab.vectors
model.embedding.weight.data.copy_(pretrained_embeddings)

optimizer = optim.Adam(model.parameters())
criterion = nn.BCEWithLogitsLoss()

model = model.to(device)
criterion = criterion.to(device)

7️⃣ 训练函数

复制代码
def binary_accuracy(preds, y):
    rounded = torch.round(torch.sigmoid(preds))
    correct = (rounded == y).float()
    return correct.sum() / len(correct)

def train(model, iterator, optimizer, criterion):
    epoch_loss, epoch_acc = 0, 0
    model.train()
    
    for batch in iterator:
        optimizer.zero_grad()
        predictions = model(batch.text).squeeze(1)
        loss = criterion(predictions, batch.label)
        acc = binary_accuracy(predictions, batch.label)
        loss.backward()
        optimizer.step()
        epoch_loss += loss.item()
        epoch_acc += acc.item()
    
    return epoch_loss / len(iterator), epoch_acc / len(iterator)

8️⃣ 验证函数

复制代码
def evaluate(model, iterator, criterion):
    epoch_loss, epoch_acc = 0, 0
    model.eval()
    
    with torch.no_grad():
        for batch in iterator:
            predictions = model(batch.text).squeeze(1)
            loss = criterion(predictions, batch.label)
            acc = binary_accuracy(predictions, batch.label)
            epoch_loss += loss.item()
            epoch_acc += acc.item()
    
    return epoch_loss / len(iterator), epoch_acc / len(iterator)

9️⃣ 模型训练主循环

复制代码
N_EPOCHS = 5
train_losses, valid_losses = [], []

for epoch in range(N_EPOCHS):
    train_loss, train_acc = train(model, train_iterator, optimizer, criterion)
    valid_loss, valid_acc = evaluate(model, valid_iterator, criterion)
    
    train_losses.append(train_loss)
    valid_losses.append(valid_loss)
    
    print(f'第{epoch+1}轮 | 训练Loss: {train_loss:.3f}, 准确率: {train_acc*100:.2f}% | 验证Loss: {valid_loss:.3f}, 准确率: {valid_acc*100:.2f}%')

🔟 Loss 曲线可视化

复制代码
plt.plot(train_losses, label='Train Loss')
plt.plot(valid_losses, label='Valid Loss')
plt.legend()
plt.title('Training vs Validation Loss')
plt.show()

七、模型测试与预测

复制代码
test_loss, test_acc = evaluate(model, test_iterator, criterion)
print(f"测试集 Loss: {test_loss:.3f}, 准确率: {test_acc*100:.2f}%")

预测示例:

复制代码
def predict_sentiment(model, sentence):
    model.eval()
    tokens = [tok.text for tok in data.get_tokenizer('spacy')(sentence)]
    indexed = [TEXT.vocab.stoi[t] for t in tokens]
    tensor = torch.LongTensor(indexed).unsqueeze(1).to(device)
    prediction = torch.sigmoid(model(tensor))
    return prediction.item()

print(predict_sentiment(model, "This movie was fantastic!"))

输出可能为:
0.93 → 正面情感


八、结果分析

指标 训练集 验证集 测试集
Loss 0.25 0.29 0.31
准确率 91.3% 89.7% 88.9%

说明:

  • 模型已成功捕捉情感倾向特征。

  • 若使用 LSTM / GRU 替换 RNN,可进一步提升性能。

  • 若使用 双向RNN(BiRNN) 或 注意力机制,准确率还能上升 3~5%。


九、总结与扩展

✅ 本项目展示了如何使用 循环神经网络 处理自然语言文本:

  • 学习序列依赖

  • 捕捉上下文语义

  • 应用于二分类情感分析

📈 可扩展方向:

  1. 替换为 LSTM / GRU 改进长距离依赖。

  2. 添加 Dropout / BatchNorm 增强泛化能力。

  3. 使用 Transformer / BERT 进一步优化。

相关推荐
QXWZ_IA10 分钟前
定位数据怎么推给 IoT 平台?消息协议选型与接入准备
网络·人工智能·物联网·安全
java_logo11 分钟前
Docker 部署 ReMe:轻松搭建 AI Agent 记忆管理平台
人工智能·docker·容器·ai agent·轩辕镜像·reme·agentscope-ai
合尘猫19 分钟前
图片表格变结构化数据:OCR 后的三个坑
python·ocr
小小张说故事21 分钟前
Python 里弱引用到底弱在哪:拆开 weakref.ref、回调陷阱和 WeakValueDictionary
python
硅谷秋水30 分钟前
从VLM规划器到机器人智能体:面向依赖历史信息的操控任务自演化框架
人工智能·机器学习·语言模型·机器人
西安小哥37 分钟前
端侧跑大模型,真正的门槛不在参数量:一次 596 秒到 10 秒的压缩路径复盘
前端·人工智能
Vex2une37 分钟前
AI 渲染正在重写游戏美术的账本:提效、外包与新的门槛
人工智能·aigc·游戏美术·神经渲染·dlss 5·ai 渲染·游戏开发门槛
用户019027581611 小时前
用 DuckDB 存查 A 股 K 线:零服务、SQL 直接分析
python
LabVIEW开发1 小时前
AI 如何接进 LabVIEW:链路怎么搭、边界在哪、还差什么
人工智能·驱动开发·labview
liferecords1 小时前
不训练一个参数,把图像编辑器变成视频编辑器:EditVid 的局部-全局拆解术
人工智能·llm·aigc·多模态