循环神经网络RNN,即一个序列当前的输出与前面的输出也有关。具体的表现形式为网络会对前面的信息进行记忆并应用于当前输出的计算中,即隐藏层之间的节点不再无连接而是有连接的,并且隐藏层的输入不仅这次的输入x,还包括上一时刻隐藏层h。
RNN公式:——>,。
一、tensor表示
输入x:[input_num,batch,feacture_num]
特征xt:[batch,feacture_num]
上一层h:[batch,hidden_num]
输出y:[b,f]@[h,f]+[b,h]@[h,h]=[b,h]
假设输入x为[5,3,10],即有5个单词分为3段,有100个特征表示。对于每个单词的特征xt为[3,100],前面传播过来的h0维度为[3,20],可以计算当前的输出y的张量为[3,100]@[20,100]+[3,20]@[20,20]=[3,20]
nn.RNN(f,h,l)
out,ht=forward(x,h0)#ht[l,b,h]返回最后一个h,out[n,b,h]返回中间所有h.
二、RNN结构
单层RNN:
#方法一nn.RNN(f,h,l)
rnn = nn.RNN(num_feature=100, hidden_size=20, num_layers=1)
print(rnn) #RNN(100, 20)
x = torch.randn(10, 3, 100) #x[n,b,f]
out, h = rnn(x, torch.zeros(1, 3, 20)) #rnn(x,h0)
print(out.shape, h.shape)#torch.Size([10, 3, 20]) torch.Size([1, 3, 20])
#方法二nn.RNNCell(f,h)
cell1 = nn.RNNCell(100, 20)
h1 = torch.zeros(3, 20)
for xt in x: #x[n,b,f]=[10,3,100] xt[b,f]=[3,100]
h1 = cell1(xt, h1) #h[l,b,h]
print(h1.shape) #torch.Size([3, 20])
多层RNN:
#方法一 nn.RNN()
rnn = nn.RNN(num_feature=100, hidden_size=20, num_layers=4)
print(rnn) #RNN(100, 20, num_layers=4)
x = torch.randn(10, 3, 100)
out, h = rnn(x, torch.zeros(4, 3, 20))
print(out.shape, h.shape)#torch.Size([10, 3, 20]) torch.Size([4, 3, 20])
#方法二 nn.RNNCell()
cell1 = nn.RNNCell(100, 30)
cell2 = nn.RNNCell(30, 20)
h1 = torch.zeros(3, 30)
h2 = torch.zeros(3, 20)
for xt in x:
h1 = cell1(xt, h1)
h2 = cell2(h1, h2)
print(h2.shape) #torch.Size([3, 20])
三、实现代码:网络的组成&训练&测试
'''采样'''
start = np.random.randint(3, size=1)[0]
time_steps = np.linspace(start, start + 10, num_time_steps)
data = np.sin(time_steps)
data = data.reshape(num_time_steps, 1)
x = torch.tensor(data[:-1]).float().view(1, num_time_steps - 1, 1) #0~48
y = torch.tensor(data[1:]).float().view(1, num_time_steps - 1, 1) #1~49
'''网络'''
class Net(nn.Module):
def __init__(self, ):
super(Net, self).__init__()
self.rnn = nn.RNN(
input_size=input_size,
hidden_size=hidden_size,
num_layers=1,
batch_first=True,
) #[n,h,l]
self.linear = nn.Linear(hidden_size, output_size)
def forward(self, x, hidden_prev):
out, hidden_prev = self.rnn(x, hidden_prev)
# [b, n , h]=> [n,h]
out = out.view(-1, hidden_size)
out = self.linear(out) #[n,h]=>[n,1]
out = out.unsqueeze(dim=0) # =>[1,seq,1]
return out, hidden_prev
'''训练'''
model = Net()
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr)
hidden_prev = torch.zeros(1, 1, hidden_size)
#采样
for iter in range(6000):
start = np.random.randint(3, size=1)[0]
time_steps = np.linspace(start, start + 10, num_time_steps)
data = np.sin(time_steps)
data = data.reshape(num_time_steps, 1)
x = torch.tensor(data[:-1]).float().view(1, num_time_steps - 1, 1)
y = torch.tensor(data[1:]).float().view(1, num_time_steps - 1, 1)
output, hidden_prev = model(x, hidden_prev)
hidden_prev = hidden_prev.detach()
#loss来更新参数Whh,Wih
loss = criterion(output, y)
model.zero_grad()
loss.backward()
optimizer.step()
if iter % 100 == 0:
print("Iteration: {} loss {}".format(iter, loss.item()))
'''测试'''
predictions = []
input = x[:, 0, :]
for _ in range(x.shape[1]):
input = input.view(1, 1, 1)
(pred, hidden_prev) = model(input, hidden_prev)
input = pred
predictions.append(pred.detach().numpy().ravel()[0])
四、RNN改进算法
RNN处理时间序列的问题的效果很好, 但是容易出现梯度消失=>0或者梯度爆炸=>∞的问题。
对于梯度消失: 由于它们都有特殊的方式存储”记忆”,那么以前梯度比较大的”记忆”不会像简单的RNN一样马上被抹除,因此可以一定程度上克服梯度消失问题。可以使用 LSTM 算法。
对于梯度爆炸:用来克服梯度爆炸的问题就是gradient clipping,也就是当你计算的梯度超过阈值c或者小于阈值-c的时候,便把此时的梯度设置成c或-c。
#对于梯度爆炸
loss=criterion(output,y)
model.zero_grad()
loss.backward()
for p in model.parameters():
print(p.grad.norm()) #查看每个梯度的模
torch.nn.utils.clip_grad_norm(p,10) #把模限制在<10
optimizet.step()
1)长短期记忆网络LSTM (待更新
RNN的Short-term-memory 只能记住比较短的时间序列,保留相近的信息。
Long-Short-term-memory 改进RNN的梯度离散和长度问题。有三个门,遗忘门,输入门,输出门。一个方向。
Bi-LSTM:双向长短期记忆网络。在单向LSTM上变成双向。
1.遗忘门
2.输入门
3.输出门
2)Bi-LSTM (待更新
3)注意力Attention模型 (待更新