首先来简谈一下梯度消失和梯度爆炸。这个概念很常见,梯度消失指在反向传播过程中出现由于某一层的导数是0导致之后所有层的导数都是0从而导致权重参数得不到改变,从而导致训练结果不好。导数是针对激活函数,所以这个主要取决于激活函数,当然还和网络结构有关系。经典的例子就是残差网络ResNet,在这个网络中因为有跳跃连接所以参数是共享的,只要有一层参数发生变话所有层都会有一定改变。从而解决了梯度消失的问题。
梯度爆炸指进行反向传播时导数随着梯度的增加而变得差别很大,导致权重变化过大,导致网络结构不稳定,训练效果不好。这个与激活函数有关系,优化器,学习率等有关。
Rnn,循环神经网络:
rnn解决了dnn的时间序列依赖问题。dnn很简单就是线形层堆叠。这样的结构导致对于有先后顺序的序列没有识别先后的能力。rnn就是加入了上下文,可以让下一个输入中加入先前输入的一些特性。保证了时间序列之间存在依赖。
源码:
class SimpleRNN(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(SimpleRNN, self).__init__()
self.hidden_size = hidden_size
self.i2h = nn.Linear(input_size + hidden_size, hidden_size)
self.h2o = nn.Linear(hidden_size, output_size)
self.tanh = nn.Tanh()
def forward(self, input, hidden):
combined = torch.cat((input, hidden), 1)
hidden = self.tanh(self.i2h(combined))
output = self.h2o(hidden)
return output, hidden
def init_hidden(self):
return torch.zeros(1, self.hidden_size)
# 超参数
input_size = 1
hidden_size = 50
output_size = 1
learning_rate = 0.001
num_epochs = 50
# 模型实例化
model = SimpleRNN(input_size, hidden_size, output_size)
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
# 训练模型
hidden = model.init_hidden()
for epoch in range(num_epochs):
for batch_X, batch_y in dataloader:
optimizer.zero_grad()
loss = 0
for t in range(batch_X.size(1)):
output, hidden = model(batch_X[:, t], hidden)
loss += criterion(output, batch_y[:, t])
loss.backward()
optimizer.step()
if (epoch + 1) % 10 == 0:
print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}')
self.i2h = nn.Linear(input_size + hidden_size, hidden_size)
self.h2o = nn.Linear(hidden_size, output_size)
这个input_size + hidden_size这个就是精髓,输入的数据要加上上一层的一些数据,就是在这里体现。hidden_size这个就是代表上一层数据的维度。combined = torch.cat((input, hidden), 1)这个是在做数据处理把输入数据和上一层隐层状态给弄成一维数组。hidden = self.tanh(self.i2h(combined))这个是得到新的hidden值,可以看出来这个hidden值是指的经过激活函数之后的值。return output, hidden返回结果和这个新的hidden值。hidden = model.init_hidden()这是对hidden进行初始化。output, hidden = model(batch_X[:, t], hidden)这里就是为什么要返回hidden,这里就是在更新hidden把新的hidden传入这个模型。
Lstm(长短期记忆网络):
这个进一步解决了rnn的问题,注意到这个rnn是存在一个问题就是他每一步都存放了前一个值得一些信息,这样子做,会导致记忆很多不重要的信息,重要的信息占比会少,导致整个效果不好。lstm就是改善了这个问题,对前一个值中的一些信息做一些筛选,保留有用的信息,去除掉一些没有用得信息,他定义了三个门来解决这个问题,输入门,输出门,遗忘门。
ht-1是上一个cell这里把上一个值得一些信息定义为cell,xt是输入得信息。b是偏执。
遗忘门是得到一个值位于0-1之间作用是确定cell中又多少信息是需要被保留的。比如ft为0.8代表上一个cell中80%的信息被保留。同理输入门是确定输入中有多少信息是被保存的。细胞更新状态就是当前传递给下一个的cell是多少。第二个输出们操作就是这一层的输出真实值。
ok上述解释非常完美,上源码:
import torch
import torch.nn as nn
import torch.optim as optim
class CustomLSTM(nn.Module):
def __init__(self, input_size, hidden_size):
super(CustomLSTM, self).__init__()
self.hidden_size = hidden_size
# 遗忘门参数
self.W_f = nn.Parameter(torch.Tensor(hidden_size, hidden_size + input_size))
self.b_f = nn.Parameter(torch.Tensor(hidden_size))
# 输入门参数
self.W_i = nn.Parameter(torch.Tensor(hidden_size, hidden_size + input_size))
self.b_i = nn.Parameter(torch.Tensor(hidden_size))
# 候选记忆细胞参数
self.W_C = nn.Parameter(torch.Tensor(hidden_size, hidden_size + input_size))
self.b_C = nn.Parameter(torch.Tensor(hidden_size))
# 输出门参数
self.W_o = nn.Parameter(torch.Tensor(hidden_size, hidden_size + input_size))
self.b_o = nn.Parameter(torch.Tensor(hidden_size))
self.init_weights()
def init_weights(self):
for param in self.parameters():
nn.init.uniform_(param, -0.08, 0.08)
def forward(self, x, hidden, cell):
combined = torch.cat((hidden, x), 1)
f_t = torch.sigmoid(torch.matmul(combined, self.W_f.T) + self.b_f)
i_t = torch.sigmoid(torch.matmul(combined, self.W_i.T) + self.b_i)
C_tilde = torch.tanh(torch.matmul(combined, self.W_C.T) + self.b_C)
C_t = f_t * cell + i_t * C_tilde
o_t = torch.sigmoid(torch.matmul(combined, self.W_o.T) + self.b_o)
h_t = o_t * torch.tanh(C_t)
return h_t, C_t
def init_hidden(self, batch_size):
return (torch.zeros(batch_size, self.hidden_size),
torch.zeros(batch_size, self.hidden_size))
# 设置模型参数
input_size = 10
hidden_size = 20
batch_size = 5
sequence_length = 15
# 创建模型实例
model = CustomLSTM(input_size, hidden_size)
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 输入数据
inputs = torch.randn(sequence_length, batch_size, input_size)
targets = torch.randn(batch_size, hidden_size)
# 训练过程
num_epochs = 100
hidden, cell = model.init_hidden(batch_size)
for epoch in range(num_epochs):
model.zero_grad()
for t in range(sequence_length):
hidden, cell = model(inputs[t], hidden, cell)
loss = criterion(hidden, targets)
loss.backward()
optimizer.step()
if (epoch + 1) % 10 == 0:
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')
f_t = torch.sigmoid(torch.matmul(combined, self.W_f.T) + self.b_f) i_t = torch.sigmoid(torch.matmul(combined, self.W_i.T) + self.b_i) C_tilde = torch.tanh(torch.matmul(combined, self.W_C.T) + self.b_C) C_t = f_t * cell + i_t * C_tilde o_t = torch.sigmoid(torch.matmul(combined, self.W_o.T) + self.b_o) h_t = o_t * torch.tanh(C_t)
整个对应的是函数实现。nn.Parameter(torch.Tensor(hidden_size))这是偏执。hidden, cell = model(inputs[t], hidden, cell)整个跟rnn同理
需要注意的是,这个整个lstm这只是一层,所以你会看到这些定义的层之间没有什么输入输出限制,因为这里这么多层只是为了得到一层的输出。