• 自然语言→有顺序关系 —— 依赖于词

# 循环神经网络:Recurrent Neural Network

循环神经网络(Recurrent Neural Network,RNN)是一种特别适合处理序列数据的神经网络模型。RNN 的设计理念是利用网络中的循环结构,使得网络能够记住序列中的前面信息,从而捕捉数据的时间依赖性。

# RNN 基本结构

RNN 的基本单元包含一个隐藏层,该隐藏层的输出不仅依赖于当前输入,还依赖于前一个时刻的隐藏层输出。这种依赖关系可以用以下公式表示:

ht=σ(Whhht1+Wxhxt+bh)yt=Whyht+by\begin{aligned}&h_{t}=\sigma(W_{hh}h_{t-1}+W_{xh}x_{t}+b_{h})\\&y_{t}=W_{hy}h_t+b_y\end{aligned}

其中:

  • hth_t 是时刻 $ t $ 的隐藏状态。
  • xtx_t 是时刻 $ t$ 的输入。
  • yty_t 是时刻 $ t$ 的输出。
  • WhhW_{hh}, WxhW_{xh}WhyW_{hy} 是权重矩阵。
  • bhb_hbyb_y 是偏置。
  • σ\sigma 是激活函数,如 tanh 或 ReLU。

# RNN 的特点

  1. 处理序列数据:RNN 可以处理任意长度的序列数据,这使得它特别适合时间序列分析、自然语言处理等任务。
  2. 记忆能力:通过隐藏层的循环连接,RNN 可以记住前面时刻的信息,并将其用于当前时刻的输出计算。
  3. 参数共享:RNN 在每个时间步共享相同的参数,这使得模型参数数量不会随着序列长度的增加而增加。

# RNN 的运用

自然语言处理(NLP)

  • 语言模型:预测句子中的下一个词。
  • 机器翻译:将一句话从一种语言翻译成另一种语言。
  • 文本生成:生成与训练数据相似的文本。

时间序列预测

  • 股价预测:预测未来的股价走势。
  • 天气预报:基于历史天气数据预测未来天气。

语音识别

  • 将语音信号转换为文本。

# RNN 的局限性

  1. 梯度消失和梯度爆炸问题:在长序列训练中,标准 RNN 会遇到梯度消失和梯度爆炸的问题,导致模型难以捕捉长时间依赖。
  2. 训练复杂性:由于循环依赖,RNN 的训练相对复杂,计算开销较大。

# RNN 计算过程

# tanh 激活函数

tanh(x)=exexex+ex\tanh(x)=\frac{e^x-e^{-x}}{e^x+e^{-x}}

# 流程图

QQ_1722242360278.png

QQ_1722242466536.png

# 代码设计

# RNN Cell

1
cell=torch.nn.RNNCell(input_size=input_size,hidden_size=hidden_size)

  • 两个值:确定了权重维度和偏置维度

1
hidden=cell(input,hidden)

  • 更新 hidden

# input

  • input of shape(batch,input_size)
  • hiadden of shape(batch,hidden_size)

# output

  • hidden of shape(batch,hidden_size)

# 假设

# 假设我们有以下属性的序列:

  • batchSize = 1:批量大小为 1,表示每次处理一个样本。
  • seqLen = 3:序列长度为 3,表示每个样本有 3 个时间步。
  • inputSize = 4:输入大小为 4,表示每个时间步有 4 个输入特征。
  • hiddenSize = 2:隐藏状态大小为 2,表示 RNN 的隐藏层维度为 2。

# RNNCell 的输入和输出形状

  • 输入形状input.shape(input.shape)
    • 输入的形状为 (batchSize,inputSize)(\text{batchSize}, \text{inputSize}),即 (1,4)(1, 4)
  • 输出形状output.shape(output.shape)
    • 输出的形状为 (batchSize,hiddenSize)(\text{batchSize}, \text{hiddenSize}),即(1,2)(1, 2)

# 序列可以包装在一个张量中,形状为:

  • 数据集形状dataset.shape(dataset.shape)
    • 数据集的形状为(seqLen,batchSize,inputSize)(\text{seqLen}, \text{batchSize}, \text{inputSize}),即 (3,1,4)(3, 1, 4)
    • 这个形状表示整个数据集包含 3 个时间步,每个时间步有一个样本,每个样本有 4 个输入特征。

# 代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import torch

batch_size = 1
seq_len = 3
input_size = 4
hidden_size = 2

cell = torch.nn.RNNCell(input_size=input_size, hidden_size=hidden_size)

# (seq, batch, features)
dataset = torch.randn(seq_len, batch_size, input_size)
hidden = torch.zeros(batch_size, hidden_size)

for idx, input in enumerate(dataset):
print('=' * 20, idx, '=' * 20)
print('Input size: ', input.shape)

hidden = cell(input, hidden)

print('Outputs size: ', hidden.shape)
print(hidden)

# 如何使用 RNN?

QQ_1722246317088.png

  • 输入的时候送所有序列h0h_0 即可

# 代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import torch

batch_size = 1
seq_len = 3
input_size = 4
hidden_size = 2
num_layers = 1

cell = torch.nn.RNN(input_size=input_size, hidden_size=hidden_size, num_layers=num_layers)

# (seqLen, batchSize, inputSize)
inputs = torch.randn(seq_len, batch_size, input_size)
hidden = torch.zeros(num_layers, batch_size, hidden_size)

out, hidden = cell(inputs, hidden)

print('Output size:', out.shape)
print('Output:', out)
print('Hidden size:', hidden.shape)
print('Hidden:', hidden)

# 自然语言处理

# 处理字符

  • 构建单词表 / 字母表
  • 构建索引

QQ_1722247061496.png

input_size=4

QQ_1722247146678.png

QQ_1722247299337.png

# 代码设计

1
2
3
4
5
6
7
8
9
10
11
12
13
idx2char = ['e', 'h', 'l', 'o']
x_data = [1, 0, 2, 2, 3]
y_data = [3, 1, 2, 3, 2]

one_hot_lookup = [[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]]

x_one_hot = [one_hot_lookup[x] for x in x_data]

inputs = torch.Tensor(x_one_hot).view(-1, batch_size, input_size)
labels = torch.LongTensor(y_data).view(-1, 1)

# 模型设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import torch

class Model(torch.nn.Module):
def __init__(self, input_size, hidden_size, batch_size):
super(Model, self).__init__()
self.batch_size = batch_size # 设置批处理大小
self.input_size = input_size # 设置输入大小
self.hidden_size = hidden_size # 设置隐藏状态大小
self.rnncell = torch.nn.RNNCell(input_size=self.input_size, hidden_size=self.hidden_size) # 初始化RNN单元

def forward(self, input, hidden):
hidden = self.rnncell(input, hidden) # 前向传播,计算新的隐藏状态
return hidden # 返回隐藏状态

def init_hidden(self):
return torch.zeros(self.batch_size, self.hidden_size) # 初始化隐藏状态为零张量

# 初始化模型
net = Model(input_size, hidden_size, batch_size)

# 训练

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
for epoch in range(15):
loss = 0
optimizer.zero_grad() # 梯度清零
hidden = net.init_hidden() # 初始化隐藏状态
print('Predicted string: ', end='')

for input, label in zip(inputs, labels):
hidden = net(input, hidden) # 前向传播
loss += criterion(hidden, label) # 计算损失

_, idx = hidden.max(dim=1)
print(idx2char[idx.item()], end='')

loss.backward() # 反向传播
optimizer.step() # 更新参数
print(', Epoch [%d/15] loss=%.4f' % (epoch+1, loss.item()))