当前位置:   article > 正文

循环神经网络实例——序列预测

循环神经网络实例——序列预测

我们生活的世界充满了形形色色的序列数据,只要是有顺序的数据统统都可以看作是序列数据,比如文字是字符的序列,音乐是音符组成的序列,股价数据也是序列,连DNA序列也属于序列数据。循环神经网络RNN天生就具有处理序列数据的能力。我们在上一篇文章中介绍了循环神经网络,这次我们来看一个具体的序列生成问题:

假设一个简单的序列生成问题,01序列生成。一个序列数据由0和1组成,并且每个0之间是连续的,每个1之间也是连续的,并且0和1的个数相等,比如:

000111

00001111,...,等等

这种序列被称为“上下文无关文法”。如果现在的数据是0000,那下一位其实不好判断,可能是0,也可能是1;如果现在的数据是00001,那下一位一定是1;如果现在数据是00001111,那由于0和1相等,接下来应该结束这个序列。

我们首先尝试生成这种数据:

  1. class MyDataset(Dataset):
  2. def __init__(self, samples = 2000, max_size=10, transform=None):
  3. max_size=10
  4. self.data = []
  5. for m in range(samples):
  6. probability = 1.0*np.array([10,6,4,3,1,1,1,1,1,1])
  7. probability = probability[:max_size]
  8. # 生成概率值
  9. probability = probability/np.sum(probability)
  10. # 生成训练样本
  11. # 对于每一个生成的字符串,随机选择一个n,n被选择的权重记录在probability中
  12. n = np.random.choice(range(1, max_size+1), p=probability)
  13. inputs = [0]*n+[1]*n # 生成仅包含0和1的序列
  14. inputs.insert(0,3) # 序列开始处插入3,作为标志位
  15. inputs.append(2) # 序列结束处插入2,作为标志位
  16. self.data.append(inputs)
  17. def __len__(self):
  18. return len(self.data)
  19. def __getitem__(self, idx):
  20. return self.data[idx]

这里,我们首先默认生成2000个样本,每个样本的最大长度为10个数字,probability代表的是生成10个概率值,我们可以把这10个概率值输出来看看:

  1. array([0.34482759, 0.20689655, 0.13793103, 0.10344828, 0.03448276,
  2. 0.03448276, 0.03448276, 0.03448276, 0.03448276, 0.03448276])

可以看到第一个概率最大,接近0.35,之后依次减少,最后六个概率都仅仅是第一个概率的十分之一,0.035左右。np.random.choice(array, prob),是指根据prob指明的概率值输出array中的值,我们可以把生成的n打印出来:

  1. for i in range(10):
  2. n = np.random.choice(range(1, max_size+1), p=probability)
  3. print(n, end=' ') # 以空格分隔
  4. # 这其实就是说,从[1,2,3,4,5,6,7,8,9,10]中,按概率probability随机抽取数字
  5. # 输出:
  6. # 5 3 1 1 1 1 1 1 6 7

可以多尝试几次,总之,1出现的概率最大,1和2出现的次数都很多,符合我们的直观印象。目的就是为了生成长度为n的随机的01字符串(n个0加上0个1)。同时在字符串的前端加上3,表示字符串起始位置,在字符串的末尾加上2,表示字符串的结束位置,如3000011112,30001112,30000001111112。

我们把生成的样本库用pytorch的dataset和dataloader包装一下,并取出来观察一下:

  1. if __name__ == '__main__':
  2. # 定义训练数据集
  3. trainDataset = MyDataset()
  4. trainDataloader = DataLoader(dataset=trainDataset, batch_size=1, shuffle=True)
  5. # 查看数据集内容
  6. for i,seq in enumerate(trainDataloader):
  7. print(seq)
  8. # 输出
  9. [tensor([3]), tensor([0]), tensor([1]), tensor([2])]
  10. [tensor([3]), tensor([0]), tensor([0]), tensor([0]), tensor([0]), tensor([1]), tensor([1]), tensor([1]), tensor([1]), tensor([2])]
  11. [tensor([3]), tensor([0]), tensor([0]), tensor([1]), tensor([1]), tensor([2])]
  12. [tensor([3]), tensor([0]), tensor([0]), tensor([0]), tensor([1]), tensor([1]), tensor([1]), tensor([2])]
  13. [tensor([3]), tensor([0]), tensor([1]), tensor([2])]
  14. [tensor([3]), tensor([0]), tensor([1]), tensor([2])]

可以看到,生成了3开头,2结尾的01序列,只不过都转成了Tensor类型。

下面,我们来实现一下自定义的RNN模型:

  1. class MyRnn(nn.Module):
  2. def __init__(self, input_size, hidden_size, output_size, num_layers = 1):
  3. # 定义
  4. super(MyRnn, self).__init__()
  5. self.hidden_size = hidden_size
  6. self.num_layers = num_layers
  7. # 一个embedding层
  8. self.embedding = nn.Embedding(input_size, hidden_size)
  9. # PyTorch的RNN层,batch_first标识可以让输入的张量的第一个维度表示batch指标
  10. self.rnn = nn.RNN(hidden_size, hidden_size, num_layers, batch_first = True)
  11. # 输出的全连接层
  12. self.fc = nn.Linear(hidden_size, output_size)
  13. # 最后的LogSoftmax层
  14. self.softmax = nn.LogSoftmax(dim=1)
  15. def forward(self, input):
  16. # 运算过程
  17. # 先进行embedding层的计算
  18. # 它可以把一个数值先转化为one-hot向量,再把这个向量转化为一个hidden_size维的向量
  19. # input的尺寸为:batch_size, num_step, data_dim
  20. x = self.embedding(input)
  21. # 从输入层到隐含层的计算
  22. # x的尺寸为:batch_size, num_step, hidden_size
  23. h0 = torch.zeros(self.num_layers, 1, self.hidden_size)
  24. output, hidden = self.rnn(x, h0)
  25. # 从输出output中取出最后一个时间步的数值,注意output包含了所有时间步的结果
  26. # output尺寸为:batch_size, num_step, hidden_size
  27. output = output[:,-1,:]
  28. # output尺寸为:batch_size, hidden_size
  29. # 把前面的结果输入给最后一层全连接网络
  30. output = self.fc(output)
  31. # output尺寸为:batch_size, output_size
  32. # softmax函数
  33. output = self.softmax(output)
  34. return output, hidden

这里,有个地方需要注意,和我上次的循环神经网络简介中介绍的RNN模型不同,多了一个embedding层。这里要特别提一下,embedding层是RNN,特别是自然语言处理(NLP)等工作中特别重要的一个步骤。embedding就是做一个查表,把输入数据映射到一个新的表空间的,本质上其实就是进行了一次降维或升维操作,有点类似卷积神经网络的1x1卷积的作用,下面我们来具体看看embedding操作到底是干了什么。

  1. import torch
  2. import torch.nn as nn
  3. embedding = nn.Embedding(10, 3) # an Embedding module containing 10 tensors of size 3
  4. input = torch.LongTensor([[1,2,4,5],[4,3,2,9]]) # a batch of 2 samples of 4 indices each
  5. e = embedding(input)
  6. print(e)
  7. # 输出
  8. tensor([[[-1.1222, 0.5364, -1.5284],
  9. [-0.4762, 0.7091, -0.0703],
  10. [-1.1492, 0.2443, 0.4336],
  11. [ 1.8044, 0.5492, 1.1109]],
  12. [[-1.1492, 0.2443, 0.4336],
  13. [-0.4599, -0.3097, 0.2294],
  14. [-0.4762, 0.7091, -0.0703],
  15. [ 0.6926, 1.7407, 1.5432]]], grad_fn=<EmbeddingBackward0>)

是不是很奇怪?我们的输入仅仅是一个2x4的二维张量,怎么变成了2x4x3的三维张量了,并且这些值是哪儿来的呢?别急,我们一步步来看。

  1. print(input.shape)
  2. print(e.shape)
  3. print(embedding.weight)
  4. print(embedding.weight.shape)
  5. # 输出
  6. torch.Size([2, 4])
  7. torch.Size([2, 4, 3])
  8. Parameter containing:
  9. tensor([[ 1.5728, 0.1152, 2.1069],
  10. [-1.1222, 0.5364, -1.5284],
  11. [-0.4762, 0.7091, -0.0703],
  12. [-0.4599, -0.3097, 0.2294],
  13. [-1.1492, 0.2443, 0.4336],
  14. [ 1.8044, 0.5492, 1.1109],
  15. [-1.3228, 0.2966, 0.5020],
  16. [-0.4501, -0.1242, -0.2341],
  17. [ 0.0743, 0.8168, -1.2459],
  18. [ 0.6926, 1.7407, 1.5432]], requires_grad=True)
  19. torch.Size([10, 3])

可以看到,我们输入的大小确实是(2,4),经过embedding的输出也确实是(2,4,3)了,我们可以看到,embedding的大小是(10,3),其实简单来说就是,embedding生成了一张10行3列的表,表中的数据是embedding自动计算出来的权重,input里面的值可以看成是索引,[[1,2,4,5],[4,3,2,9]],我们看到第一个数字1,那我们从embedding表中取到第1行,[-1.1222, 0.5364, -1.5284],依次类推,得到最后的结果,如下图所示:

等于说input里面的值变成了embedding权重表里面的索引值,根据这个索引值去查询embedding表的权重,组成的结果就是把输入进行embedding后的结果。

下面我们再来详细分析一下我们的循环神经网络模型MyRnn里面的运算过程,只要真正理解了这个过程,那就明白了模型里面的运作原理了。

首先,我有一个输入,假设是tensor[1]

  1. input = torch.LongTensor([1]).unsqueeze(0) # 扩展一个维度,作为batch_size
  2. print(input.shape)
  3. print(input)
  4. # 输出
  5. torch.Size([1, 1])
  6. tensor([[1]])

下面,我们进行embedding操作,我们的模型假设input_size=4,hidden_size=2,output_size=3,因为输入有0,1,2,3四种可能,输出有0,1,2三种可能。

  1. # input_size=4,hidden_size=2
  2. embedding = nn.Embedding(4, 2)
  3. print(embedding.weight)
  4. print(embedding.weight.shape)
  5. x = embedding(input) # embedding操作
  6. print(x)
  7. print(x.shape)
  8. # 输出
  9. Parameter containing:
  10. tensor([[ 0.1870, 0.5711],
  11. [ 0.2994, -0.4649],
  12. [-0.3611, -0.1150],
  13. [-1.0868, -0.1419]], requires_grad=True)
  14. torch.Size([4, 2])
  15. tensor([[[ 0.2994, -0.4649]]], grad_fn=<EmbeddingBackward0>)
  16. torch.Size([1, 1, 2])

可以看到,做了embedding操作后,我们的输入从[[1]]变成了[[[ 0.2994, -0.4649]]],接下来,我们把这个结果和初始化的隐藏层hidden一起送入RNN。

  1. h = torch.zeros(1, x.size(0), 2) # 隐藏层参数layer_size,batch_size,hidden_size
  2. print(h)
  3. print(h.shape)
  4. rnn = nn.RNN(2, 2, batch_first=True) # RNN模型,因为经过了embedding,input的shape变成了和hidden一样的shape

隐藏层的三个参数分别为隐藏层大小layer_size,这里取1,batch_size这里就是x第一个维度,也就是1,hidden_size,设置隐藏层的大小为2。

  1. out, _ = rnn(x, h) # RNN结果
  2. print(out.shape)
  3. print(out)
  4. print(out[:,-1,:]) # 取出结果
  5. # 输出
  6. torch.Size([1, 1, 2])
  7. tensor([[[-0.2823, -0.5443]]], grad_fn=<TransposeBackward1>)
  8. tensor([[-0.2823, -0.5443]], grad_fn=<SliceBackward0>)

可以看到我们现在取出的out结果的shape从[1,1,2]变成了[1,2],最后我们做一个全连接运算,把分类结果输出:

  1. fc = nn.Linear(2, 3) # 全连接层
  2. out = fc(out[:, -1, :]) # 分类结果
  3. print(out)
  4. # 输出
  5. tensor([[0.7543, 0.5506, 0.8528]], grad_fn=<AddmmBackward0>)

可以看到得到了三个值的分类结果,根据这个值再去获得概率最高的结果,就是最终的输出结果。好了,这就是我们整个模型内部的运行过程。下面给出完整的训练和测试代码:

训练代码:

  1. def train(net, dataloader, epochs=50):
  2. criterion = torch.nn.NLLLoss()
  3. optimizer = torch.optim.Adam(net.parameters(), lr=0.001)
  4. for epoch in range(epochs):
  5. train_loss = 0.
  6. for i, seq in enumerate(dataloader):
  7. loss = 0
  8. hidden = net.initHidden()
  9. for k in range(len(seq)-1):
  10. x = torch.LongTensor([seq[k]]).unsqueeze(0) # 增加一个batch_size的维度
  11. y = torch.LongTensor([seq[k+1]])
  12. #print(x)
  13. #print(y)
  14. output, hidden = net(x, hidden)
  15. #output, _ = net(x)
  16. loss += criterion(output, y)
  17. loss = 1.0*loss/len(seq) # 计算每个字符的损失值
  18. optimizer.zero_grad()
  19. loss.backward()
  20. optimizer.step()
  21. train_loss += loss
  22. if i>0 and i%500==0:
  23. print('第{}轮,第{}个,训练Loss:{:.2f}'.format(epoch, i, train_loss.data.numpy()/i))
  24. torch.save(net, 'checkpoints/RNN.pt')

验证代码:

  1. def val(net, dataloader):
  2. valid_loss = 0
  3. errors = 0
  4. show_out = ''
  5. net = torch.load('checkpoints/RNN.pt')
  6. net.eval()
  7. with torch.no_grad():
  8. for i, seq in enumerate(dataloader):
  9. # 对valid_set中的每一个字符串进行循环
  10. loss = 0
  11. outstring = ''
  12. targets = ''
  13. diff = 0
  14. hidden = net.initHidden() # 初始化隐含神经元
  15. for t in range(len(seq) - 1):
  16. # 对每一个字符进行循环
  17. x = torch.LongTensor([seq[t]]).unsqueeze(0)
  18. # x尺寸:batch_size = 1, time_steps = 1, data_dimension = 1
  19. y = torch.LongTensor([seq[t + 1]])
  20. # y尺寸:batch_size = 1, data_dimension = 1
  21. output, hidden = net(x, hidden)
  22. # output尺寸:batch_size, output_size = 3
  23. # hidden尺寸:layer_size =1, batch_size=1, hidden_size
  24. mm = torch.max(output, 1)[1][0] # 将概率最大的元素作为输出
  25. outstring += str(mm.data.numpy()) # 合成预测的字符串
  26. targets += str(y.data.numpy()[0]) # 合成目标字符串
  27. #loss += criterion(output, y) # 计算损失函数
  28. diff += 1 - mm.eq(y).data.numpy()[0] # 计算模型输出字符串与目标字符串之间存在差异的字符数量
  29. #loss = 1.0 * loss / len(seq)
  30. #valid_loss += loss # 累积损失函数值
  31. errors += diff # 计算累积错误数
  32. show_out = outstring + '\n' + targets
  33. # 打印结果
  34. print(output[0][2].data.numpy())
  35. print(show_out)

测试代码:

  1. def test():
  2. net = torch.load('checkpoints/RNN.pt')
  3. net.eval()
  4. for n in range(20):
  5. inputs = [0]*n + [1]*n
  6. inputs.insert(0,3)
  7. inputs.append(2)
  8. outstring = ''
  9. targets = ''
  10. diff = 0
  11. hiddens = []
  12. hidden = net.initHidden()
  13. for t in range(len(inputs)-1):
  14. x = Variable(torch.LongTensor([inputs[t]]).unsqueeze(0))
  15. y = Variable(torch.LongTensor([inputs[t+1]]))
  16. output,hidden = net(x, hidden)
  17. hiddens.append(hidden.data.numpy()[0][0])
  18. mm = torch.max(output, 1)[1][0]
  19. outstring += str(mm.data.numpy())
  20. targets += str(y.data.numpy()[0])
  21. diff += 1-mm.eq(y).data.numpy()[0]
  22. # 打印每一个生成的字符串和目标字符串
  23. print("n======",n)
  24. print(outstring)
  25. print(targets)
  26. print('Diff:{}'.format(diff))

最终输出:

  1. 49轮,第500个,训练Loss:0.24
  2. 49轮,第1000个,训练Loss:0.24
  3. 49轮,第1500个,训练Loss:0.24
  4. -0.030197786
  5. 0100112
  6. 0001112
  7. n====== 0
  8. 0
  9. 2
  10. Diff:1
  11. n====== 1
  12. 012
  13. 012
  14. Diff:0
  15. n====== 2
  16. 01012
  17. 00112
  18. Diff:2
  19. n====== 3
  20. 0100112
  21. 0001112
  22. Diff:2
  23. n====== 4
  24. 010001112
  25. 000011112
  26. Diff:2
  27. n====== 5
  28. 01000011112
  29. 00000111112
  30. Diff:2
  31. n====== 6
  32. 0100000111112
  33. 0000001111112
  34. Diff:2
  35. n====== 7
  36. 010000001111112
  37. 000000011111112
  38. Diff:2
  39. n====== 8
  40. 01000000011111112
  41. 00000000111111112
  42. Diff:2
  43. n====== 9
  44. 0100000000111111112
  45. 0000000001111111112
  46. Diff:2
  47. n====== 10
  48. 010000000001111111112
  49. 000000000011111111112
  50. Diff:2
  51. n====== 11
  52. 01000000000011111111112
  53. 00000000000111111111112
  54. Diff:2
  55. n====== 12
  56. 0100000000000111111111112
  57. 0000000000001111111111112
  58. Diff:2
  59. n====== 13
  60. 010000000000001111111111112
  61. 000000000000011111111111112
  62. Diff:2
  63. n====== 14
  64. 01000000000000011111111111112
  65. 00000000000000111111111111112
  66. Diff:2
  67. n====== 15
  68. 0100000000000000111111111111112
  69. 0000000000000001111111111111112
  70. Diff:2
  71. n====== 16
  72. 010000000000000001111111111111112
  73. 000000000000000011111111111111112
  74. Diff:2
  75. n====== 17
  76. 01000000000000000011111111111111112
  77. 00000000000000000111111111111111112
  78. Diff:2
  79. n====== 18
  80. 0100000000000000000111111111111111112
  81. 0000000000000000001111111111111111112
  82. Diff:2
  83. n====== 19
  84. 010000000000000000001111111111111111112
  85. 000000000000000000011111111111111111112
  86. Diff:2

可以看到,模型预测还是比较准确的,就是第二个数字预测错误,以及由0变1的那个位置会预测错误。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/473512
推荐阅读
相关标签
  

闽ICP备14008679号