当前位置:   article > 正文

深度学习pytorch——RNN从表示到原理、应用、发展、实战详细讲解(终结)_pytorch rnn

pytorch rnn

时间序列的表示

题外话:在自然界中,我们接触到的信号都是模拟信号,在手机、电脑等电子设备中存储的信号是数字信号。我们通常对模拟信号进行等间隔取样得到数字信号,这个数字信号也被称为序列。将模拟信号转换为数据信号的过程称为数字信号处理,现已成为一门独立的学科,其分支数字图像处理在人工智能领域发挥着重要的作用。

序列的规范化表示方法:

[ seq_len, feature_len ] 用来表示序列,其中seq_len为序列的长度、feature_len为表示序列中每个元素的维度。序列可以是数字化后的语音信号、可以是数字化后的图片、也可以是一句话等等。

GloVe编码方式的引入:

我们都知道pytorch中是不存在字符串类型的,但是文字信息又是我们长处理的数据,我们如何表示这种数据,答案是编码。其中最简单的编码方式one-hot,我们在处理手写数字识别的时候用的就是这种编码方式,建立一个10维的向量,每个位置表示一个数字,如果是这个数字则相应位置为1,否则为0。

0 :[ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]

1 :[ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 ]

……

9 :[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 ]

将相应的数字换为单词就可以表达每个单词。

one-hot的编码方式存在稀疏高维特点。稀疏:每一个向量只能用来表达一个单词;高维:一个向量的维度等于这个句子的单词数量,你也许感觉还可以,如果是一篇英语文章呢?这种编码方式代表了巨大的数据量

由于one-hot的高度不实用性,GloVe编码方式随之诞生。GloVe表示了单词的相似性,具体怎么表示的我们看如下图:

左边的表示了与king的相似性,右边的表示了与queen的相似性。我们可以看到每个单词的后面都跟着一个数值,这个数值表示了与值对应单词的相似度,就比如kings后面的数值0.897245,便是了kings与king的形似度为0.897245。这个相似度是如何计算的呢?

使用两个单词之间的cos值表示,将每个单词看作一个向量,然后计算两向量之间的cos<w1, w2>如果你还不是很明白,就去看高中数学吧,或者看下图:

不知道这张图是否唤起了你沉睡的记忆?

接着介绍在序列在我们今天的主题RNN中是如何表示的,RNN里面提供了两种方法来表示序列:

第一种:[word num, b, word vec],其中word num表示单词的数量,b表示句子的数量,word vec表示单词的维度。

第二种:[b, word num, word vec],其中b表示句子的数量,word num表示单词的数量,word vec表示单词的维度。

这两种表示方法中的每个维度含义都是相同的,只是两者的顺序发生了改变,可以根据理解图片向量的含义来理解整体的含义。

GloVe实例:

  1. import torch
  2. import torchnlp # 自然语言处理模块,如果没有torchnlp 模块,用pip install pytorch-nlp 安装
  3. import torch.nn as nn
  4. from torchnlp import word_to_vector
  5. from torchnlp.word_to_vector import GloVe
  6. ################ nn.Embedding ################
  7. word_to_ix = {"hello": 0, "world": 1} # 编一个索引号
  8. # 查表 Word vector lookup table,已经提前编好存储在pytorch中
  9. lookup_tensor = torch.tensor([word_to_ix["hello"]], dtype=torch.long) # 得到一个索引idx
  10. embeds = nn.Embedding(2, 5) # 实例化一个词向量表,其中2表示:一个有2个单词,5表示每个单词的词向量的维度或长度。
  11. hello_embeds = embeds(lookup_tensor) # 根据索引查表,得到索引对应的feature,也就是word vector 的 representation
  12. print(hello_embeds)
  13. # tensor([[ 0.7305, -1.1242, -0.1810, -0.5458, -1.0004]],
  14. # grad_fn=<EmbeddingBackward>)
  15. # 这里没有初始化,nn.embedding这张表是随机生成的,得到的vector,非常random。
  16. # 且通过查表方式得到的vector没有办法算梯度,不能优化。
  17. # 怎样初始化,使用现成的 word2vec 或者GloVe
  18. ########## word2vec / GloVe ##########
  19. # vectors=GloVe() # 查表操作,下载太慢了
  20. vectors = GloVe('6B') # 840条词向量,每个词向量的维度是300个维度
  21. vector = vectors["hello"]
  22. print(vector.shape)
  23. # torch.Size([300])
  24. print(vector)

什么是RNN?

RNN全称为Recurrent Neural Network,中文名字“循环神经网络”。具体是什么,往下看:

当我们去做一个情感分析的时候,会尝试去评价一个句子是积极的还是消极的,先让我们以CNN的角度来看:

现在我们要分析I hate this boring movies这句话是消极的,还是积极的,我们将此句话使用一个[5,  100]的序列来表示,其中5表示一共有5个单词,100表示每个单词使用100个维度来表示。我们将每个单词都送进CNN,也就是将[100]进行线性运算,但是每个单词的weight和bias都是不一样的,这就意味着CNN是将每个单词单个处理的,丧失了语义,并且每个单词都会拥有2个参数,这意味着巨大的参数量。我们希望可以减少参数,并且将所有的单词联系起来,放在一个语境下去分析,由此权值共享的概念和长期记忆的概念被提出。

权值共享,简单明了,让所有的单词都共享同意对参数,如下图所示:

长期记忆,有一个单元来保存所有的信息,将每个单词串联起来,放在同一个语境下:

这个被用来保存所有信息为h0,每个单元的输出为h,请注意看红色箭头的指向,具体红色箭头指哪里由你自己决定。

一般我们将h0初始化为[0, 0, 0, ...],每次输入一个单词,比如序列为[5, 3, 100],送入的就是      [3,  100]。

这个模型就被称为RNN。

RNN背后的数学原理:

 RNN的使用

前提:再来看这张图,我先解释一下这个图中每个数据的特征,其中整体的数据x:[seq len, batch, feature len],其中seq len表示单词的个数、batch表示句子的个数、feature len表示一个单词使用feature len个维度来表达;一个单词xt:[batch, feature len];Wxh:[hidden len, feature len]其中hidden len表示记忆单元的维度;ht:[batch, hidden len];Whh:[hidden len, hidden len]。可以使用来表示紫色框中的公式,最后的结果就是[ batch, hidden len]。

代码实例,可以直接运行试试看,也可以改变一下feature len和hidden len:

  1. from torch import nn
  2. rnn = nn.RNN(100, 10) # RNN(feature len, hidden len)
  3. print(rnn._parameters.keys())
  4. # odict_keys(['weight_ih_l0', 'weight_hh_l0', 'bias_ih_l0', 'bias_hh_l0'])
  5. print(rnn.weight_hh_l0.shape, rnn.weight_ih_l0.shape)
  6. # torch.Size([10, 10]) torch.Size([10, 100])
  7. print(rnn.bias_hh_l0.shape, rnn.bias_ih_l0.shape)
  8. # torch.Size([10]) torch.Size([10])

在进行下面实例之前先明确两个概念:

out 是最后输出的集合,表示为[h0,h1,…,hi]的组合叠加
h 则是每一层最后隐藏单元的集合

一层RNN:

  1. import torch
  2. from torch import nn
  3. # [input dim(feature dim/word vector),hidden dim,num layers(default Settings is 1)]
  4. rnn = nn.RNN(input_size=100, hidden_size=20, num_layers=1)
  5. print(rnn)
  6. # RNN(100, 20)
  7. # x:[seq len, batch, word vector]
  8. x = torch.randn(10, 3, 100)
  9. # out:[seq len,batch,hidden dim] [10,3,20]
  10. # h0/ht:[num layers,batch,hidden dim] [1,3,20]
  11. out, h = rnn(x, torch.zeros(1, 3, 20))
  12. print(out.shape, h.shape)
  13. # torch.Size([10, 3, 20]) torch.Size([1, 3, 20])

一层RNN-Cell:

  1. import torch
  2. from torch import nn
  3. # 1-layer
  4. cell1 = nn.RNNCell(100, 20) # [word vec, h_dim]
  5. h1 = torch.zeros(3, 20) # [b,h_dim]
  6. for xt in x: # xt:每个数据,x:所有数据
  7. h1 = cell1(xt, h1)
  8. print(h1.shape)
  9. # torch.Size([3, 20])

多层:

先来看一下处理后维度的变化: 

  1. from torch import nn
  2. rnn = nn.RNN(100, 10, num_layers=2) # RNN(feature len, hidden len, num_layers)
  3. print(rnn._parameters.keys())
  4. # odict_keys(['weight_ih_l0', 'weight_hh_l0', 'bias_ih_l0', 'bias_hh_l0', 'weight_ih_l1', 'weight_hh_l1', 'bias_ih_l1', 'bias_hh_l1'])
  5. print(rnn.weight_hh_l0.shape, rnn.weight_ih_l0.shape)
  6. # torch.Size([10, 10]) torch.Size([10, 100])
  7. print(rnn.weight_hh_l1.shape, rnn.weight_ih_l1.shape)
  8. # torch.Size([10, 10]) torch.Size([10, 10])

 多层RNN:

  1. import torch
  2. from torch import nn
  3. rnn = nn.RNN(input_size=100, hidden_size=20, num_layers=4)
  4. print(rnn)
  5. # RNN(100, 20, num_layers=4)
  6. x = torch.randn(10, 3, 100)
  7. # out:[seq len,batch,hidden dim] [10,3,20]
  8. # h0/ht:[num layers,batch,hidden dim] [4,3,20]
  9. out, h = rnn(x, torch.zeros(4, 3, 20)) # rnn(所有的数据, h0)
  10. # out, h = rnn(x) #与上一行等价,这里没有传h0进来
  11. print(out.shape, h.shape)
  12. # torch.Size([10, 3, 20]) torch.Size([4, 3, 20])

多层RNN-Cell:

  1. import torch
  2. from torch import nn
  3. cell1 = nn.RNNCell(100, 30) # [word vec,h_dim],100 dim reduce to 30
  4. cell2 = nn.RNNCell(30, 20) # [word vec,h_dim],30 dim reduce to 20
  5. h1 = torch.zeros(3, 30)
  6. h2 = torch.zeros(3, 20)
  7. for xt in x:
  8. h1 = cell1(xt, h1) # 本层的输出h1作为了下一层的输入
  9. h2 = cell2(h1, h2)
  10. print(h2.shape)
  11. # torch.Size([3, 20])

时间序列的预测

  1. ############# 正弦函数预测(1 layer RNN) #############
  2. # (1)构建RNN网络
  3. # (2)training:构建优化器,迭代,加载正弦函数数据,定义loss,输出
  4. # (3)testing
  5. # (4)画图
  6. import numpy as np
  7. import torch
  8. import torch.nn as nn
  9. import torch.optim as optim
  10. from matplotlib import pyplot as plt
  11. num_time_steps = 50
  12. input_size = 1
  13. hidden_size = 16
  14. output_size = 1
  15. lr = 0.01
  16. ########构建RNN网络#######
  17. class Net(nn.Module):
  18. def __init__(self, ):
  19. super(Net, self).__init__()
  20. self.rnn = nn.RNN(
  21. input_size=input_size,
  22. hidden_size=hidden_size,
  23. num_layers=1,
  24. batch_first=True, # [batch,seq len ,word vector/feature dim]
  25. )
  26. # 正态分布的权值初始化
  27. for p in self.rnn.parameters(): # parameters:包含所有模型的迭代器.
  28. nn.init.normal_(p, mean=0.0, std=0.001) # normal_:正态分布
  29. # 连接层,对数据做处理 [49,16]→[49,1]
  30. self.linear = nn.Linear(hidden_size, output_size) # 对输入数据做线性变换y=Ax+b
  31. # 前向传播函数
  32. def forward(self, x, hidden_prev):
  33. out, hidden_prev = self.rnn(x, hidden_prev) # 前向网络参数:输入,h0
  34. # out:[b, seq, h],torch.Size([1, 49, 16])
  35. # ht:[b,num layers,hidden dim],torch.Size([1, 1, 16])
  36. out = out.view(-1, hidden_size) # [1, seq, h]=>[seq, h] torch.Size([49, 16])
  37. out = self.linear(out) # torch.Size([49, 1]), [seq, h] => [seq, 1]
  38. out = out.unsqueeze(dim=0) # torch.Size([1, 49, 1]),要个 y做一个MSE,所以增加的维度要一致 [seq, 1] => [1, seq, 1]
  39. return out, hidden_prev # torch.Size([1, 49, 1]),torch.Size([1, 1, 16])
  40. ########## 开始训练 ##############
  41. model = Net()
  42. criterion = nn.MSELoss()
  43. optimizer = optim.Adam(model.parameters(), lr)
  44. hidden_prev = torch.zeros(1, 1, hidden_size) # h0:[b,num layers,hidden dim],[1,1,16]
  45. for iter in range(6000):
  46. # np.random.randint(low, high=None, size=None)
  47. # 作用返回一个随机整型数,范围从低(包括)到高(不包括),即[low, high)。如果没有写参数high的值,则返回[0,low)的值。
  48. # 65-74行慵懒生成数据
  49. start = np.random.randint(3, size=1)[0] # 返回[0,3)的随机整型数,size=1表示维度为1,[0]取第0维的数
  50. # print(start) # scalar
  51. time_steps = np.linspace(start, start + 10, num_time_steps) # 等分数列,生成[start,start+10] 区间的50个等分数列
  52. # print(time_steps) #(50,)
  53. data = np.sin(time_steps) # 取正弦函数 (50,)
  54. data = data.reshape(num_time_steps, 1) # (50, 1)
  55. x = torch.tensor(data[:-1]).float().view(1, num_time_steps - 1, 1) # 第0-48的数字 x:(1,49,1)
  56. y = torch.tensor(data[1:]).float().view(1, num_time_steps - 1, 1) # 第1-49的数字 y:(1,49,1)
  57. # 相当于用第0-48的数字预测第1-49的数字,往后预测1位
  58. # 正式开始训练
  59. output, hidden_prev = model(x, hidden_prev) # 将x和h0送进模型,得到output和ht
  60. # tensor.detach()
  61. # 返回一个新的tensor,从当前计算图中分离下来的,但是仍指向原变量的存放位置,
  62. # 不同之处只是requires_grad为false,得到的这个tensor永远不需要计算其梯度,不具有grad
  63. hidden_prev = hidden_prev.detach()
  64. loss = criterion(output, y) # compute MSE of output and y
  65. model.zero_grad()
  66. loss.backward()
  67. optimizer.step()
  68. ########### 进行测试 #########
  69. if iter % 100 == 0: # 每100次输出结果
  70. print("Iteration: {} loss {}".format(iter, loss.item()))
  71. # 数据定义送进来
  72. start = np.random.randint(3, size=1)[0]
  73. time_steps = np.linspace(start, start + 10, num_time_steps)
  74. data = np.sin(time_steps)
  75. data = data.reshape(num_time_steps, 1)
  76. x = torch.tensor(data[:-1]).float().view(1, num_time_steps - 1, 1) # 数据
  77. y = torch.tensor(data[1:]).float().view(1, num_time_steps - 1, 1) # 预测值
  78. # 测试
  79. predictions = [] # 先把预测的东西做一个空的数组
  80. input = x[:, 0, :] # x从[1,49,1]=>[1,1]
  81. for _ in range(x.shape[1]): # '_' 是一个循环标志
  82. input = input.view(1, 1, 1) # x从[1,1]=>[1,1,1],即把seq变为1,曲线一个点一个点的动
  83. (pred, hidden_prev) = model(input, hidden_prev)
  84. input = pred # 接收刚才输出的pred,再次送入网络,seq为1,就可以实现一个点一个点的预测下去了。
  85. predictions.append(pred.detach().numpy().ravel()[0]) # append 用于在列表末尾添加新的对象, ravel展平, [0]取数值
  86. # 也就是说,将得到的预测点一个一个展示出来
  87. # 画图
  88. x = x.data.numpy().ravel() # 展平
  89. y = y.data.numpy()
  90. plt.scatter(time_steps[:-1], x.ravel(), s=90) # 原始X散点图 0-48, x,size=90
  91. plt.plot(time_steps[:-1], x.ravel()) # 原始X曲线图 0-48, x
  92. plt.scatter(time_steps[1:], predictions) # 预测曲线图 1-49, predictions,default size=20
  93. plt.show()

预测结果:

RNN训练难题

从CNN模型的经验,RNN如果进行简单的叠加也会导致很差的结果,这个很差的结果主要有两种,梯度爆炸(Gradient Exploding)梯度离散(Gradient Vanishing)。为什么会导致这种原因的发生?我们一起来看一下RNN的梯度推导公式:

如果还不明白,看下面,从小到大的心灵鸡汤: 

梯度爆炸解决:

将w.grad限制在一个范围内,就是我们给定一个预值threshold then,如果梯度超过预值了我们就对梯度进行一个缩放,缩放规则为 w.grad = threshold / ||w.grad|| * w.grad。具体的代码表现形式:

  1. loss = criterion(output, y)
  2. model.zero_grad()
  3. loss.backward()
  4. for p in model.parameters():
  5. print(p.grad.norm()) #打印出现在的W.grad,经验来讲,如果大于等于50就有可能梯度爆炸,如果等于10左右,就ok
  6. torch.nn.utils.clip_grad_norm_(p, 10) #采用clip 将梯度降成<10
  7. optimizer.step()

梯度离散解决——LSTM:

解决梯度离散的方法就是LSTM,但是为什么叫做LSTM?LSTM是 long-short-term-memory 的简称,下面来介绍一下具体为什么要叫 long-short-term-memory ,进行RNN中的时候我们定义了一个h0,用来贯穿语境,想象很美好,现实很骨感,没错h0只能记住最近的几个,前面的早忘到十万八千里了,这种现象称作 short-term-memory(短期记忆)。而让它脑子变得好使的就加了一个long,即 long-short-term-memory

先来回顾一下 short-term-memory

我们完全可以将上述模型换成如下形式:

 再来看一下LSTM,观察一下两者的区别:

蓝色对应公式:

,其中Wf表示决定记住多少。

以下两个公式表示绿色的部分,这一步决定了 我们将要存储多少信息在细胞门里面。

 Wi表示获取多少现在的信息。

橙色对应公式,更新hidden里面的值:

第一个框:通过忘记之前决定要忘记的Ft乘于旧的Ct-1。

第二个框:决定更新多少的值it乘于新的状态值Ct(上面有个~)。

紫色的对应输出,这一步我们需要决定我们想要输出什么:

我们要输出的部分:

最终我们要输出的部分:

总的来说就是:

表达成公式就是如下样子:

 极值条件下(这个很像真值表,如果你学过《离散数学》或《数字电路与逻辑设计》的话,应该知道):

输入门输出门表现行为
01记住先前的值
11加入先前的值
00清除值
10覆盖原来的值

但是这种计算方法怎么解决了梯度离散的现象呢?让我们从数学公式的角度来分析一下原因:

原来的累乘变成了累加,累加就有可能加正数和负数,这种形式的梯度很自然的就解决了梯度离散的问题。

LSTM的使用

由于LSTM如此强大,pytorch也提供了相关的类——nn.LSTM

nn.LSTM:

1、__init__

参数:input_size:feature len;hidden_size:h0的特征维度;num_layers:循环的层数,如果不填的话默认为1。

2、LSTM.forward()

参数:out, (ht, ct) = lstm(x, [ht_0, ct_0])

x: [seq, b , vec]; h/c: [num_layer, b, h]; out: [seq, b, h]

3、代码演示:

  1. lstm = nn.LSTM(input_size=100, hidden_size=20, num_layers=4)
  2. print(lstm)
  3. # LSTM(100, 20, num_layers=4)
  4. x = torch.randn(10, 3, 100)
  5. out, (h, c) = lstm(x)
  6. print(out.shape, h.shape, c.shape) # out:[seq, b, h] h/c:[num_layer, b, h]
  7. # torch.Size([10, 3, 20]) torch.Size([4, 3, 20]) torch.Size([4, 3, 20])

nn.LSTMCell:

1、__init__

参数:input_size:feature len;hidden_size:h0的特征维度;num_layers:循环的层数,如果不填的话默认为1。

2、LSTMCell.forward()

ht, ct = lstmcell(xt, [ht_1, ct_1]) 

xt: [b, word vec]

ht/ct: [b, h]

3、代码演示:

1-layer:

  1. cell = nn.LSTMCell(input_size=100, hidden_size=20)
  2. h = torch.zeros(3, 20)
  3. c = torch.zeros(3, 20)
  4. for xt in x:
  5. h, c = cell(xt, [h, c])
  6. print(h.shape, c.shape)
  7. # torch.Size([3, 20]) torch.Size([3, 20])

2-layer:

  1. cell1 = nn.LSTMCell(input_size=100, hidden_size=30)
  2. cell2 = nn.LSTMCell(input_size=30, hidden_size=20)
  3. h1 = torch.zeros(3, 30)
  4. c1 = torch.zeros(3, 30)
  5. h2 = torch.zeros(3, 20)
  6. c2 = torch.zeros(3, 20)
  7. for xt in x:
  8. h1, c1 = cell1(xt, [h1, c1])
  9. h2, c2 = cell2(h1, [h2, c2])
  10. print(h2.shape, c2.shape)
  11. # torch.Size([3, 20]) torch.Size([3, 20])

情感分类实战

Google CoLab:

在Google Colab中你可以获得连续12小时的使用、获得免费的K80 for GPU,Google Colab一般用来学习机器学习,是一个非常好用的工具,前提是你必须可以科学上网,可以使用Google。

  1. ########### 情感分类实战(基于IMDB数据集)#############
  2. # 1 加载数据:
  3. # 1.1 分割训练集测试集
  4. # 1.2 创建vocabulary
  5. # 1.3 创建 iterations
  6. # 2 定义模型(class RNN):
  7. # 2.1 __init__: 定义模型
  8. # 2.2 forward:前向传播
  9. # 2.3 使用预训练过的embedding 来替换随机初始化
  10. # 3 训练模型 :
  11. # 3.1 首先定义优化器和损失函数
  12. # 3.2 定义一个函数用于计算准确率 def binary_acc
  13. # 3.3 进入训练模式
  14. # 4 评估模型(test)
  15. # 5 运行
  16. # 6 预测输出样例
  17. import torch
  18. import torchtext #(version:0.6.0)
  19. from torch import nn, optim
  20. from torchtext import data, datasets
  21. import spacy #(version:3.3.0)
  22. # nlp=spacy.load('en_core_web_sm')
  23. print('GPU:', torch.cuda.is_available())
  24. # 为CPU设置随机种子
  25. torch.manual_seed(123)
  26. ############ 1 加载数据 ###########
  27. ######### 1.1 分割训练集测试集 ###########
  28. # 两个Field对象定义字段的处理方法(文本字段、标签字段)
  29. #Spacy 3.x以上的版本:tokenizer_language='en_core_web_sm',否则默认是tokenizer_language='en'
  30. #TEXT = data.Field(tokenize='spacy')
  31. TEXT = data.Field(tokenize='spacy',tokenizer_language='en_core_web_sm') #文本
  32. LABEL = data.LabelField(dtype=torch.float) #标签
  33. train_data, test_data = datasets.IMDB.splits(TEXT, LABEL)
  34. print('len of train data:', len(train_data)) # 25000
  35. print('len of test data:', len(test_data)) # 25000
  36. # torchtext.data.Example : 用来表示一个样本,数据+标签
  37. print(train_data.examples[15].text) # 文本:句子的单词列表
  38. print(train_data.examples[15].label) # 标签: 积极pos
  39. #当我们把句子传进模型的时候,是按照一个个batch传进去的,而且每个batch中的句子必须是相同的长度。
  40. #为了确保句子的长度相同,TorchText会把短的句子 pad到和最长的句子 等长。
  41. ########## 1.2 创建vocabulary ############
  42. # vocabulary把每个单词一一映射到一个数字。
  43. # 使用10k个单词来构建单词表(用max_size这个参数可以设定),所有其他的单词都用来表示。
  44. # word2vec, glove
  45. TEXT.build_vocab(train_data, max_size=10000, vectors='glove.6B.100d')
  46. LABEL.build_vocab(train_data)
  47. # 词典中应当有10002个单词,且有两个label,
  48. # 可以通过TEXT.vocab和TEXT.label查询,
  49. # 可以直接用stoi(stringtoint) 或者 itos(inttostring) 来查看单词表。
  50. # print(len(TEXT.vocab)) # 10002
  51. # print(TEXT.vocab.itos[:12]) # ['', '', 'the', ',', '.', 'and', 'a', 'of', 'to', 'is', 'in', 'I']
  52. # print(TEXT.vocab.stoi['and']) # 5
  53. # print(LABEL.vocab.stoi) # defaultdict(None, {'neg': 0, 'pos': 1})
  54. ############### 1.3 创建 iterations ############
  55. # 每个iterator 迭代器 中各有两部分:词(.text)和标签(.label),其中 text 全部转换成数字了。
  56. # BucketIterator会把长度差不多的句子放到同一个batch中,确保每个batch中不出现太多的padding。
  57. batchsz = 30
  58. device = torch.device('cuda:0')
  59. train_iterator, test_iterator = data.BucketIterator.splits(
  60. (train_data, test_data),
  61. batch_size = batchsz,
  62. device=device
  63. )
  64. ################### 2 定义模型 ##########################
  65. #这里的RNN是一个通称,应该叫Network
  66. class RNN(nn.Module):
  67. def __init__(self, vocab_size, embedding_dim, hidden_dim):
  68. super(RNN, self).__init__()
  69. # [0-10001] => [100]
  70. # 参数1:embedding个数(单词数), 参数2:embedding的维度(词向量维度)
  71. self.embedding = nn.Embedding(vocab_size, embedding_dim) #[10002,100]
  72. # [100] => [256]
  73. # bidirectional=True 双向LSTM,所以下面FC层使用 hidden_dim*2
  74. self.rnn = nn.LSTM(embedding_dim, hidden_dim, num_layers=2, #[100,256]
  75. bidirectional=True, dropout=0.5)
  76. # [256*2] => [1]
  77. self.fc = nn.Linear(hidden_dim*2, 1)
  78. self.dropout = nn.Dropout(0.5)
  79. def forward(self, x):
  80. """
  81. x: [seq_len, b] vs [b, 3, 28, 28]
  82. """
  83. # [seq, b, 1] => [seq, b, 100]
  84. embedding = self.dropout(self.embedding(x))
  85. # output: [seq, b, hid_dim*2]
  86. # hidden/h: [num_layers*2, b, hid_dim]
  87. # cell/c: [num_layers*2, b, hid_di]
  88. output, (hidden, cell) = self.rnn(embedding)
  89. # [num_layers*2, b, hid_dim] => 2 of [b, hid_dim] => [b, hid_dim*2]
  90. # 双向LSTM,所以要把最后两个输出连接
  91. hidden = torch.cat([hidden[-2], hidden[-1]], dim=1)
  92. # [b, hid_dim*2] => [b, 1]
  93. hidden = self.dropout(hidden)
  94. out = self.fc(hidden)
  95. return out
  96. ####### 使用预训练过的embedding 来替换随机初始化 ###############
  97. rnn = RNN(len(TEXT.vocab), 100, 256) #词个数 10002 ,词嵌入维度 100 ,输出维度 256
  98. #使用预训练过的embedding 来替换随机初始化
  99. pretrained_embedding = TEXT.vocab.vectors
  100. print('pretrained_embedding:', pretrained_embedding.shape) # torch.Size([10002, 100])
  101. rnn.embedding.weight.data.copy_(pretrained_embedding) # .copy_() 这种带着下划线的函数均代表替换inplace
  102. print('embedding layer inited.')
  103. ######### 3 训练模型 ##########
  104. ######### 3.1首先定义优化器和损失函数 #########
  105. optimizer = optim.Adam(rnn.parameters(), lr=1e-3)
  106. criteon = nn.BCEWithLogitsLoss().to(device) # BCEWithLogitsLoss是针对二分类的CrossEntropy
  107. rnn.to(device)
  108. # RNN(
  109. # (embedding): Embedding(10002, 100)
  110. # (rnn): LSTM(100, 256, num_layers=2, dropout=0.5, bidirectional=True)
  111. # (fc): Linear(in_features=512, out_features=1, bias=True)
  112. # (dropout): Dropout(p=0.5, inplace=False)
  113. # )
  114. ######### 3.2 定义一个函数用于计算准确率 #########
  115. import numpy as np
  116. def binary_acc(preds, y):
  117. """
  118. get accuracy
  119. """
  120. preds = torch.round(torch.sigmoid(preds))
  121. correct = torch.eq(preds, y).float() #correct is a tensor
  122. acc = correct.sum() / len(correct)
  123. return acc
  124. ######### 3.3 进入训练模式 #########
  125. def train(rnn, iterator, optimizer, criteon):
  126. avg_acc = []
  127. rnn.train() # 表示进入训练模式
  128. for i, batch in enumerate(iterator):
  129. # batch.text 就是上面forward函数的参数text,压缩维度是为了和batch.label维度一致
  130. pred = rnn(batch.text).squeeze(1) # [seq, b] => [b, 1] => [b]
  131. loss = criteon(pred, batch.label)
  132. # 计算每个batch的准确率
  133. acc = binary_acc(pred, batch.label).item()
  134. avg_acc.append(acc)
  135. optimizer.zero_grad() # 清零梯度准备计算
  136. loss.backward() # 反向传播
  137. optimizer.step() # 更新训练参数
  138. if i%100 == 0:
  139. print(i, acc)
  140. avg_acc = np.array(avg_acc).mean()
  141. print('avg acc:', avg_acc)
  142. ################# 4 评估模型 ######################
  143. #定义一个评估函数,和训练函数高度重合
  144. def eval(rnn, iterator, criteon):
  145. avg_acc = []
  146. rnn.eval() # 表示进入测试模式
  147. with torch.no_grad():
  148. for batch in iterator:
  149. # [b, 1] => [b]
  150. pred = rnn(batch.text).squeeze(1)
  151. loss = criteon(pred, batch.label)
  152. acc = binary_acc(pred, batch.label).item()
  153. avg_acc.append(acc)
  154. avg_acc = np.array(avg_acc).mean()
  155. print('>>test:', avg_acc)
  156. ############# 5 运行 ###########
  157. for epoch in range(10):
  158. train(rnn, train_iterator, optimizer, criteon) # 训练模型
  159. eval(rnn, test_iterator, criteon) # 评估模型
  160. # 跑的时候发现模型过拟合了
  161. ############## 6 预测输出样例 ##########
  162. # for batch in test_iterator:
  163. # # batch_size个预测
  164. # preds = rnn(batch.text).squeeze(1)
  165. # # preds = predice_test(preds) #?
  166. # print(preds)
  167. #
  168. #
  169. # i = 0
  170. # for text in batch.text:
  171. # # 遍历一句话里的每个单词
  172. # for word in text:
  173. # print(TEXT.vocab.itos[word], end=' ')
  174. #
  175. # print('')
  176. # # 输出3句话
  177. # if i == 3:
  178. # break
  179. # i = i + 1
  180. #
  181. # i = 0
  182. # for pred in preds:
  183. # idx = int(pred.item())
  184. # print(idx, LABEL.vocab.itos[idx])
  185. # # 输出3个结果(标签)
  186. # if i == 3:
  187. # break
  188. # i = i + 1
  189. # break

我是pytorch:2.1.2,torchtext:0.16.0+cpu,跑这段代码的时候会报错AttributeError: module 'torchtext.data' has no attribute 'Field',原因是因为torchtext0.12.0之后不存在Filed了,解决办法就是下载旧的版本,我是通过在命令行提示符执行

pip install torchtext==0.6.0

成功解决了问题。 

总结:

        至此我的pytorch已经学习完毕,在这个过程中我通过写博客来督促自己学习,通过输出的过程中我解决了在听课时遇到的难点,并且思考了如何清晰的表达我的语言,才可以让阅读我博客的人觉得pytorch是如此轻松,并且使我更加对pytorch的知识点更加清晰,也明白了自己的很多不足,并且严重的意识到实践才是巩固知识的唯一途径。

        在以后,我会着重于深度学习的应用,将所学知识用到AI安全方面,并且也会学习机器学习的知识以此加强对深度学习的理解。同样,为了加强我学习的动力,我将会继续采用blog记录的方式,将会开展AI-安全系列、机器学习系列,如果有对AI-安全感兴趣的小伙伴,请好好学习,然后发blog,也让我学习学习,我期待中国的AI-安全领域出现更多开放的学习资料,中国网络安全领域繁荣。

        最后的最后,请允许我将我这段时间阅读中记下的至理名言放在这里:

啊,在我们化作灰烬之前,

充分享用人生。

黄泉之下,

可没有美酒,

没有琴弦,

没有歌妓,

没有明天。 

谁说啊呦不可爱? 

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

闽ICP备14008679号