RNN介绍
- 零基础介绍
- 语言处理技术基本介绍
- 自然语言处理
- RNN介绍
- RNN的变种:LSTM
-
- [1. Forget Gate](#1. Forget Gate)
- [2. Input Gate](#2. Input Gate)
- [3. Update Memory](#3. Update Memory)
- [4. Output Gate](#4. Output Gate)
- RNN的变种:GRU
- [Attention 注意力机制](#Attention 注意力机制)
- RNN的应用
- RNN代码
零基础介绍
循环神经网络 (RNN, Recurrent Neural Network)是一种特殊的神经网络,用于处理和分析序列数据(比如时间序列、文本、语音等)。它的特别之处在于,它能够"记住"之前的信息并用来处理当前的数据,这使得它比普通的前馈神经网络(比如卷积神经网络 CNN)更适合处理时间序列数据。
很多实际问题中,当前的输入往往和之前的数据有关系,比如:
语音识别:一个单词的发音会受到前后音节的影响。
语言翻译:一个句子的理解不仅要看当前的词,还要结合前面的词。
股票预测:当前股票的价格会受到过去价格的影响。
RNN的工作原理:
RNN的优缺点:
优点:
处理序列数据 :RNN非常适合处理和预测序列数据,可以记住历史的信息,理解当前输入与过去输入之间的关系。
灵活性高:可以处理任意长度的序列,不需要固定输入长度。
缺点:
梯度消失/爆炸问题 :在长时间序列中,RNN会遇到"梯度消失"或者"梯度爆炸"问题,即网络在训练时,梯度可能会变得非常小或非常大,导致学习过程困难。
训练速度慢:由于需要考虑历史信息,RNN的训练过程通常比普通神经网络慢。
RNN的变种:
为了克服RNN的一些缺点,研究者提出了一些变种模型,最常见的有:
长短期记忆网络 (LSTM, Long Short-Term Memory):通过引入"门控机制",LSTM能够更好地处理长时间依赖问题,避免梯度消失问题。
门控循环单元(GRU, Gated Recurrent Unit):GRU是LSTM的一个简化版本,同样可以有效处理长时间依赖问题,但结构更简单。
语言处理技术基本介绍
分词算法
词法分析工具
文本分类与聚类
情感分析
自然语言处理
词向量
词向量学习模型
1. 神经网络语言模型
2. CBOW 和 skip-gram
3. 层次化softmax方法
4. 负采样方法
RNN介绍
传统的前馈神经网络(Feedforward Neural Network,简称 FNN),其中最常见的一种形式就是 多层感知器(Multilayer Perceptron,简称 MLP)
自然语言处理(NLP, Natural Language Processing)
MLP是典型的前馈神经网络,在输入层和输出层之间没有任何循环结构。它的每个输入都独立处理,无法处理序列数据和时间依赖关系。而RNN能够通过隐藏状态和递归结构处理时序数据,捕捉序列中的上下文信息。
MLP适合处理独立的输入数据(如静态图像分类、非时序特征等),而RNN更适合处理有时间依赖的序列数据(如文本生成、机器翻译、语音识别等)。
引入RNN:
于是引入 LSTM
RNN的变种:LSTM
STM引入了三个门控(输入门、遗忘门和输出门)来控制信息的流动:
遗忘门(Forget Gate):决定上一时刻的记忆单元状态有多少需要被"忘记"。
输入门(Input Gate):决定当前输入有多少需要被写入记忆单元。
输出门(Output Gate):决定当前的记忆单元状态对输出的贡献
1. Forget Gate
2. Input Gate
3. Update Memory
4. Output Gate
RNN的变种:GRU
GRU是LSTM的一种简化版本,只有两个门(更新门和重置门):
更新门(Update Gate):控制当前隐藏状态的更新程度。
重置门(Reset Gate):决定当前输入与过去的隐藏状态结合的程度。
Attention 注意力机制
RNN的应用
1. 序列到类别
2. 同步序列到序列
(1)中文分词
(2)命名实体识别
3. 异步序列到序列
(1)机器翻译
(2)对话系统
4. 看图说话
5. 自动摘要
6. 自动写诗
7. 自动作曲
RNN代码
一个简单的RNN代码
每10个训练周期会打印出损失值,训练完成后,图表会显示模型的预测结果和真实的正弦曲线对比。
(使用均方误差损失函数(MSELoss)来计算模型的误差,优化器选择了Adam。)
python
import torch
import torch.nn as nn
import torch.optim as optim
# 定义一个简单的RNN模型
class SimpleRNN(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(SimpleRNN, self).__init__()
self.hidden_size = hidden_size
# 定义RNN层
self.rnn = nn.RNN(input_size, hidden_size, batch_first=True)
# 定义输出层
self.fc = nn.Linear(hidden_size, output_size)
def forward(self, x):
# 初始化隐藏状态(初始时刻为0)
h0 = torch.zeros(1, x.size(0), self.hidden_size).to(x.device)
# 通过RNN层得到输出和最终的隐藏状态
out, hn = self.rnn(x, h0)
# 获取最后一个时刻的输出
out = out[:, -1, :]
# 通过输出层得到预测结果
out = self.fc(out)
return out
# 超参数设置
input_size = 1 # 每个时间步的输入特征数(例如,序列中的每个元素)
hidden_size = 5 # 隐藏层的大小
output_size = 1 # 输出的特征数
learning_rate = 0.01 # 学习率
num_epochs = 100 # 训练的轮数
# 构造一个简单的序列输入(比如一个简单的正弦波或者线性序列)
x_train = torch.linspace(0, 10, steps=100).view(-1, 1, 1) # 100个时间步,每个时间步的输入是一个1维数据
y_train = torch.sin(x_train).view(-1, 1) # 正弦函数作为目标输出
# 创建RNN模型
model = SimpleRNN(input_size, hidden_size, output_size)
# 定义损失函数和优化器
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
# 训练模型
for epoch in range(num_epochs):
model.train()
# 前向传播
outputs = model(x_train)
# 计算损失
loss = criterion(outputs, y_train)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (epoch+1) % 10 == 0:
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')
# 预测
model.eval()
with torch.no_grad():
predicted = model(x_train)
# 可视化结果
import matplotlib.pyplot as plt
plt.plot(x_train.numpy(), y_train.numpy(), label='True')
plt.plot(x_train.numpy(), predicted.numpy(), label='Predicted')
plt.legend()
plt.show()
电影评论情感分析
使用IMDb数据集,它包含大量的电影评论,并且每条评论都已经标注为"正面"或"负面"情感。
python
import torch
import torch.nn as nn
import torch.optim as optim
from torchtext.datasets import IMDB
from torchtext.data import Field, BucketIterator
import random
import numpy as np
python
SEED = 1234
random.seed(SEED)
torch.manual_seed(SEED)
torch.backends.cudnn.deterministic = True
python
# 定义Field
TEXT = Field(sequential=True, lower=True, include_lengths=True)
LABEL = Field(sequential=False, use_vocab=True, is_target=True)
# 加载IMDb数据集
train_data, test_data = IMDB.splits(TEXT, LABEL)
# 构建词汇表
TEXT.build_vocab(train_data, max_size=25000, vectors="glove.6B.100d", unk_init=torch.Tensor.normal_)
LABEL.build_vocab(train_data)
# 创建数据迭代器
BATCH_SIZE = 64
train_iterator, test_iterator = BucketIterator.splits(
(train_data, test_data),
batch_size=BATCH_SIZE,
device=torch.device("cuda" if torch.cuda.is_available() else "cpu"),
sort_within_batch=True,
sort_key=lambda x: len(x.text),
)
python
class RNN_Sentiment(nn.Module):
def __init__(self, input_dim, embedding_dim, hidden_dim, output_dim, n_layers, dropout):
super(RNN_Sentiment, self).__init__()
self.embedding = nn.Embedding(input_dim, embedding_dim)
self.rnn = nn.RNN(embedding_dim, hidden_dim, n_layers, batch_first=True, dropout=dropout)
self.fc = nn.Linear(hidden_dim, output_dim)
self.dropout = nn.Dropout(dropout)
def forward(self, text, text_lengths):
embedded = self.embedding(text)
# RNN expects packed sequences
packed_embedded = nn.utils.rnn.pack_padded_sequence(embedded, text_lengths.cpu(), batch_first=True, enforce_sorted=False)
packed_output, hidden = self.rnn(packed_embedded)
# Get the last hidden state (for classification)
hidden = self.dropout(hidden[-1])
output = self.fc(hidden)
return output
python
# 定义超参数
INPUT_DIM = len(TEXT.vocab)
EMBEDDING_DIM = 100
HIDDEN_DIM = 256
OUTPUT_DIM = 1
N_LAYERS = 2
DROPOUT = 0.5
model = RNN_Sentiment(INPUT_DIM, EMBEDDING_DIM, HIDDEN_DIM, OUTPUT_DIM, N_LAYERS, DROPOUT)
# 使用预训练的GloVe词向量初始化嵌入层
model.embedding.weight.data.copy_(TEXT.vocab.vectors)
# 如果有UNK和PAD的token,它们的向量应该是随机初始化
model.embedding.weight.data[TEXT.vocab.stoi[TEXT.pad_token]] = torch.zeros(EMBEDDING_DIM)
model.embedding.weight.data[TEXT.vocab.stoi[TEXT.unk_token]] = torch.randn(EMBEDDING_DIM)
# 将模型转移到GPU(如果可用)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = model.to(device)
# 定义损失函数和优化器
optimizer = optim.Adam(model.parameters())
criterion = nn.BCEWithLogitsLoss()
# 将损失函数放到GPU(如果可用)
criterion = criterion.to(device)
# 训练模型
def train(model, iterator, optimizer, criterion):
model.train()
epoch_loss = 0
epoch_acc = 0
for batch in iterator:
text, text_lengths = batch.text
labels = batch.label
optimizer.zero_grad()
predictions = model(text, text_lengths).squeeze(1)
loss = criterion(predictions, labels)
acc = binary_accuracy(predictions, labels)
loss.backward()
optimizer.step()
epoch_loss += loss.item()
epoch_acc += acc.item()
return epoch_loss / len(iterator), epoch_acc / len(iterator)
# 计算准确率
def binary_accuracy(preds, y):
rounded_preds = torch.round(torch.sigmoid(preds))
correct = (rounded_preds == y).float()
acc = correct.sum() / len(correct)
return acc
# 开始训练
N_EPOCHS = 5
for epoch in range(N_EPOCHS):
train_loss, train_acc = train(model, train_iterator, optimizer, criterion)
print(f'Epoch {epoch+1}, Train Loss: {train_loss:.3f}, Train Accuracy: {train_acc*100:.2f}%')
python
def evaluate(model, iterator, criterion):
model.eval()
epoch_loss = 0
epoch_acc = 0
with torch.no_grad():
for batch in iterator:
text, text_lengths = batch.text
labels = batch.label
predictions = model(text, text_lengths).squeeze(1)
loss = criterion(predictions, labels)
acc = binary_accuracy(predictions, labels)
epoch_loss += loss.item()
epoch_acc += acc.item()
return epoch_loss / len(iterator), epoch_acc / len(iterator)
# 测试模型
test_loss, test_acc = evaluate(model, test_iterator, criterion)
print(f'Test Loss: {test_loss:.3f}, Test Accuracy: {test_acc*100:.2f}%')
- 数据预处理:使用torchtext加载和处理IMDb数据集。
- 模型设计:使用RNN进行文本情感分类,经过词向量嵌入、RNN层处理和全连接层输出。
- 训练和评估:通过计算损失函数和准确率来训练模型并评估其性能。
与之前的CNN比较: