pytorch入门10--循环神经网络(RNN)

灰太狼 2024-03-25 15:32 165阅读 0赞

补充:torch.randn()函数返回一个张量,包含了从正态分布(均值为0,方差为1)中抽取的一组随机数。张量的形状由参数决定,参数个数任意。
例如:torch.randn(3,4,5)返回一个shape为[3,4,5]即的张量,张量的元素满足标准正态分布。
在这里插入图片描述

一、简要介绍
RNN适用了处理序列性的数据,并利用权重共享机制(即一个RNN网络使用多次)。序列性数据用一个例子来理解,比如我们想要预知某一天是否会下雨,我们采集了大量天数的气象信息,气象信息包括温度、气压和降雨,我们分四天为一组,前三天(用x1,x2,x3表示)就是一个序列,第四天作为已知结果,通过第四天的已知结果与模型的估计值进行比较求损失、求梯度来优化模型参数。使用序列模型的意义在于数据前后之间有一定的联系或影响,即x2的数据依赖于x1,x3的数据依赖于x2,x4的数据依赖于x3,即前三天气象信息与是否降雨对第四天有一定的影响(天气的变化是比较缓和的,很少出现断崖式的变化,所以可以根据前些天的天气预知后一天的天气)。注意,这里的x1,x2,x3是一个序列,而x1,x2,x3三个张量又各自包含三个特征值,分别表示温度、气压和天气。
除了天气、股市等信息外,自然语言也是具有序列关系的信息。

二、RNN神经网络
RNN cell本质上是一个也是线性层(Linear Layer,进行wx+b的线性变换)。区别在于RNN cell的网络是共享的,即使用多次。
在这里插入图片描述
上图中的左边部分是RNN模型的一般书写模式,右边部分是其展开模式。,这里的X1,X2,X3,X4就是之前提到过的序列。h0可以是初始化全为0的张量。先将X1和h0(h表示hidden,即隐层)输入RNN cell(RNN 神经网络),输出结果为h1,再将x2和h1(即RNN cell的两个输出是一样的,都是hi)输入相同的RNN cell输出结果h2,依次类推,最终输出结果h4。可以看出h2的结果融合了x1与x2的信息,h3的结果融合了x1、x2与x3的信息,h4的结果融合了x1、x2、x3与x4的信息。
RNN网络的每一个隐藏层的具体过程:
在这里插入图片描述
input_size表示输入的维度,输入Xt先做一个线性变换,h(t-1)是上一层隐层的输出,也对其做一个线性变换,再将 两个线性变换的结果相加,上图中的Wih为的shape为[hidden_size, input_size],使得Wih
Xt+bih的结果为[hidden_size,1];Whh为的shape为[hidden_size, hidden_size]。最后使用激活函数tanh()使结果位于(-1,1)的区间内,ht为该隐层的输出。
实际上,两个线性运算可以写成一个线性运算:
在这里插入图片描述
RNN cell的公式:
在这里插入图片描述
三、代码实现
自定义一个RNN cell,只需输入参数input_size(即输入值的维度,即Xi有几个分量)和hidden_size(隐藏层的维度,即hi有几个分量)。

  1. # 声明一个RNN cell
  2. cell = torch.nn.RNNCell(input_size = input_size, hidden_size = hidden_size)
  3. # 调用RNN cell,并把下列语句写下一个for循环里(实现序列多次调用RNN cell)
  4. hidden = cell(input, hidden)

注意:input.shape为[batch, input_size],batch为批量的大小,即数据集的大小,即input.shape=batch.size*input_size的二维张量,hidden.shape为[batch, hidden_size]。输出的hidden.shape为[batch, hidden_size]。
实例:
(1)使用RNN cell
RNN的输入张量的shape为[batchSize, inputSize],输出张量(隐层)的维度为[batchSize, hiddenSize]。数据集(dataset)的shape定义为[seqLen,batchSize,inputSize],这里的seqLen即为序列长度。

  1. import torch
  2. batch_size = 1 # 批量大小
  3. seq_len = 3
  4. input_size = 4
  5. hidden_size = 2
  6. cell = torch.nn.RNNCell(input_size = input_size, hidden_size = hidden_size)
  7. dataset = torch.randn(seq_len,batch_size,input_size)
  8. hidden = torch.zeros(batch_size,hidden_size) # h0
  9. # 对dataset进行遍历,每次取出序列中的一个元素,即先取x1,再去x2,最后取x3
  10. for idx,input in enumerate(dataset):
  11. print('=' * 20,idx,'='*20)
  12. print('Input size:',input.shape)
  13. hidden = cell(input, hidden) # cell中的hidden为上一次输出的隐层,等号左边的hidden为这一次的隐层。
  14. print('outputs size',hidden.shape)
  15. print(hidden)

(2)使用RNN
在这里插入图片描述

  1. # num_layers表示隐层的层数(后面图解)
  2. cell = torch.nn.RNN(input_size = input_size, hidden_size = hidden_size, num_layers= num_layers)
  3. # 调用
  4. out, hidden = cell(inputs,hidden) #input为输入的序列[x1,x2,x3,..,xN],input.shape为[seqLen, batch, input_size]
  5. # cell中的hidden表示h0,out表示[h1,h1,...,hN),等号左边的hidden为hN。hidden.shape为[numLayers, batch, hidden_size]
  6. # output.shape为[seqLen, batch, hiddden_size]

在这里插入图片描述
上图的numLayers即为3,表示三层隐藏层,同一种颜色的RNN cell为同一层(其实同一种颜色是同一个RNN cell)。

  1. import torch
  2. batch_size = 1 # 批量大小
  3. seq_len = 3
  4. input_size = 4
  5. hidden_size = 2
  6. num_layers = 1
  7. cell = torch.nn.RNN(input_size = input_size, hidden_size = hidden_size,num_layers=num_layers)
  8. inputs = torch.zeros(seq_len,batch_size,input_size)
  9. hidden = torch.zeros(num_layers,batch_size,hidden_size)
  10. out, hidden = cell(inputs,hidden)
  11. print('Output size:', output.size)
  12. print('Output:', output)
  13. print('Hidden size:', hidden.size)
  14. print('Hidden:', hidden)

四、实例
在这里插入图片描述
训练目标,将输入的“hello”经过RNN网络根据每一个隐藏层的结果输出为“ohlol”。
1.将单词转化为张量
使用独热(one-hot)编码:
在这里插入图片描述
对于hello的每一个字符都对应一个索引值,将hello转化为索引值得数组,再根据独热编码得方法转化为等长得向量,可以用一个一维含四个特征值得张量表示。注意每一次RNNcell输出的hi是一个含有四个元素的张量,通过softmax函数决定是输出结果是e,h,l,o中的哪一类:
在这里插入图片描述

  1. import torch
  2. input_size = 4
  3. hidden_size = 4
  4. batch_size = 1
  5. #准备数据
  6. idx2char = ['e','h','l','o']
  7. x_data = [1,0,2,2,3]
  8. y_data = [3,1,2,3,2]
  9. # 便于查询
  10. one_hot_lookup = [[1,0,0,0],
  11. [0,1,0,0],
  12. [0,0,1,0],
  13. [0,0,0,1]]
  14. x_one_hot = [one_hot_lookup[x] for x in x_data] # 根据x_data的索引序列生成每一位字符的独热编码(后面详解)
  15. inputs = torch.Tensor(x_one_hot).view(-1,batch_size,input_size)
  16. labels = torch.LongTensor(y_data).view(-1,1) # torch.LongTensor()用于创建长整型的张量,参数是多维数组。
  17. # torch.view()和torch.reshape()都是用来重塑tensor的shape的,view()只适用于满足连续性条件的tensor进行操作,而reshape同时还可以对不满足连续性条件的tensor进行操作。
  18. # 定义模型
  19. class Model(torch.nn.Module):
  20. def __init__(self,input_size,hidden_size,batch_size):
  21. super(Model,self).__init__()
  22. self.batch_size = batch_size
  23. self.input_size = input_size
  24. self.hidden_size = hidden_size
  25. self.rnncell = torch.nn.RNNCell(input_size = self.input_size,
  26. hidden_size=self.hidden_size)
  27. def forward(self,input,hidden):
  28. hidden = self.rnncell(input,hidden)
  29. return hidden
  30. # 生成初始的h0
  31. def init_hidden(self):
  32. return torch.zeros(self.batch_size,self.hidden_size)
  33. net = Model(input_size,hidden_size,batch_size)
  34. # 损失函数
  35. criterion = torch.nn.CrossEntryLoss()
  36. # 优化器
  37. optimizer = torch.optim.Adam(net.parameters(),lr=0.1)
  38. # 训练模型
  39. for epoch in range(15):
  40. loss = 0
  41. optimizer.zero_grad()
  42. hidden = net.init_hidden()
  43. print("Predicted stringL:",end='')
  44. for input,label in zip(inputs,labels):
  45. hidden = net(input,hidden)
  46. loss += criterion(hidden,label)
  47. _,idx = hiddden.max(dim=1)
  48. print(idx2char[idx.item()],end='')
  49. loss.backward()
  50. optimizer.step()
  51. print(',Epoch[%d/15] loss=%.4f' % (epoch+1,loss.item()))

补充:(1)torch.Tensor()与torch.tensor()的区别
在这里插入图片描述
(2)使用for循环来生成多维数组
下图中x_data中的每一个x作为下标值找到对应one_hot_lookup中的元素,最后生成一个多维数组。
在这里插入图片描述

使用RNN的书写形式:

  1. # 定义模型
  2. class Model(torch.nn.Module):
  3. def __init__(self,input_size,hidden_size,batch_sizenumlayers=1):
  4. super(Model,self).__init__()
  5. self.numlayers = num_layers
  6. self.batch_size = batch_size
  7. self.input_size = input_size
  8. self.hidden_size = hidden_size
  9. self.rnn = torch.nn.RNN(input_size = self.input_size,
  10. hidden_size=self.hidden_size,
  11. num_layers = num_layers)
  12. def forward(self,input):
  13. hidden = torch.zeros(self.num_layers,self.batch_size,slef.hidden_size)
  14. out,_=self.rnn(input,hidden)
  15. return out.view(-1,self.hidden_size) # out.shape为[seqLen*batchSize,hiddenSize]
  16. # 生成初始的h0
  17. def init_hidden(self):
  18. return torch.zeros(self.batch_size,self.hidden_size)
  19. net = Model(input_size,hidden_size,batch_size)
  20. # 损失函数
  21. criterion = torch.nn.CrossEntryLoss()
  22. # 优化器
  23. optimizer = torch.optim.Adam(net.parameters(),lr=0.1)
  24. # 训练模型
  25. for epoch in range(15):
  26. optimizer.zero_grad()
  27. outputs = net(inputs)
  28. loss = criterion(outputs,labels)
  29. loss.backwrd()
  30. optimizer.step()
  31. _,idx= outputs.max(dim=1)
  32. idx = idx.data.numpy()

2.使用嵌入层代替独热编码(embedding)
独热编码有许多缺点,维度大、张量稀疏、是硬编码(不是通过提取特征得到的)。因此想到使用低维的(lower-dimension),稠密的(dense),学习到的张量(learn from data)。
embedding即把一个高位的、稀疏的样本映射到一个低维的、稠密的空间(即数据降维):
在这里插入图片描述
加入嵌入层后:
在这里插入图片描述

加入嵌入层的网络结构:

  1. # 参数
  2. num_class = 4
  3. input_size = 4
  4. hidden_size = 8
  5. embedding_size = 10
  6. num_layers = 2
  7. batch_size = 1
  8. seq_len = 5
  9. #准备数据
  10. idx2char = ['e','h','l','o']
  11. x_data = [1,0,2,2,3]
  12. y_data = [3,1,2,3,2]
  13. inputs = torch.LongTensor(x_data)
  14. labels = torch.LongTensor(y_data).view(-1,1)
  15. class Model(torch.nn.Module):
  16. def __init__(self):
  17. super(Model,self).__init__()
  18. self.emb = torch.nn.Embedding(input_size,embedding_size) # 加入嵌入层
  19. self.rnn = torch.nn.RNN(input_size =embedding_size,
  20. hidden_size=self.hidden_size,
  21. num_layers = num_layers,
  22. batch_first=True)# batch_first=True设为true时后面输入的数据的张量的input.shape为[batchSize,seqLen,input_size],即把训练维度与样本序列进行交换,这里的input_size转变为embedding_size
  23. # output.shape为[batchSize,seqLen,hiddenSize]
  24. self.fc = torch.nn.Linear(hidden_size,num_class) # 完成输出类别的变换
  25. def forward(self,x):
  26. hidden = torch.zeros(num_layers,x.size(0),slef.hidden_size)
  27. x = self.emb(x)
  28. x,_=self.rnn(x,hidden)
  29. x =self.fc(x)
  30. return x.view(-1,num_class) # out.shape为[seqLen*batchSize,hiddenSize]
  31. # 生成初始的h0
  32. def init_hidden(self):
  33. return torch.zeros(self.batch_size,self.hidden_size)
  34. net = Model()
  35. criterion = torch.nn.CrossEntropyLoss()
  36. optimizer = torch.optim.Adam(net.parameters(),lr=0.05)
  37. for epoch in range(15):
  38. optimizer.zero_grad()
  39. outputs = net(inputs)
  40. loss = criterion(outputs,labels)
  41. loss.backward()
  42. optimizer.step()
  43. _,idx = outputs.max(dim=1)
  44. idx = idx.data.numpy()
  45. print('Predicted:',''.join([idx2char[x] for x in idx]},end='')
  46. print(',Epoch[%d/15] loss=%.3f' % (epoch +1,loss.item())

对于嵌入层可以通过下面的例子来理解:
在这里插入图片描述

嵌入层可以增大张量的维度,是通过查询的方式。比如上图中的x_data是一个shape为[5]的张量,通过对每一个元素(1,0,2,2,3)来找到一个对应的张量(张量的元素个数由embedding_size决定,为10),这样x_data就转化为了inputs,其shape为[5,10]。
注意:同一个嵌入层对象多次输入一个张量对应唯一的一个输出结果,同一个张量输入不同的嵌入层对象结果不同。
在这里插入图片描述
在这里插入图片描述

发表评论

表情:
评论列表 (有 0 条评论,165人围观)

还没有评论,来说两句吧...

相关阅读

    相关 pytorch-循环神经网络

    循环神经网络 本节介绍循环神经网络,下图展示了如何基于循环神经网络实现语言模型。我们的目的是基于当前的输入与过去的输入序列,预测序列的下一个字符。循环神经网络引入一个隐藏

    相关 循环神经网络RNN

    对于人类而言,以前见过的事物会在脑海中留下记忆,虽然随后记忆会慢慢消失,但是每当经过提醒,人们往往可以重拾记忆。在神经网络中也是一样,之前介绍的CNN模型都是与时间序列无关的模