PyTorch使用记录02

  • A+
所属分类:深度学习
摘要这一篇文章主要记录一下Pytorch在日常使用中,我经常使用到的一些功能,主要是一个记录的功能。这是第二篇了,上一篇内容有些多了,就再开一篇进行记录。

前言

这一部分是关于PyTorch的一些使用记录,自己经常使用的一些功能会记录在这里,方便自己的查找与使用。(建议直接使用Ctrl+F来进行查找)

这是第二篇关于PyTorch的使用记录了,第一篇的链接,PyTorch使用记录。第一篇有点太长了,所以就在这一篇进行接着记录。

Pytorch动态调整学习率

有的时候,我们需要在训练的初始的时候, 将学习率设置的大一些,之后随着训练的进行,逐渐减小学习率,这个时候就需要使用optim.lr_scheduler. 下面是一个简单的例子。

我们需要注意的是,每一个epoch都需要使用exp_lr_scheduler.step().

  1. from torch.optim import lr_scheduler
  2. test_optimizer = optim.SGD([torch.randn(1, requires_grad=True)], lr=1e-3)
  3. exp_lr_scheduler = optim.lr_scheduler.StepLR(test_optimizer, step_size=3, gamma=0.5)
  4. for epoch in range(1, 12):
  5.     exp_lr_scheduler.step() # 要每一个epoch都要使用
  6.     print('Epoch {}, lr {}'.format(
  7.         epoch, test_optimizer.param_groups[0]['lr']))

上面的是每3个epoch,lr都会乘0.5(gamma)PyTorch使用记录02

于是,我们在绘制loss函数图像的时候,可以看到下面这种效果, 每次lr减小后loss能继续下降.

PyTorch使用记录02

Pytorch动态调整学习率--不同的调节方法结合

上面我们介绍了关于Pytorch中动态调节学习率的方法, 这里会简单说明一下同时使用两种动态调节的方式,也会介绍一下另一种动态调节的方式,CosineAnnealingLR,会以周期性对lr进行变化。

我们需要注意的是,要想第二个动态学习率可以从第一个学习率之后进行调整, 我们需要在第一次调节完毕之后修改optimizer的initial learning rate,调节的方式如下:

  1. test_optimizer.param_groups[0]['initial_lr'] = test_optimizer.param_groups[0]['lr'] # 需要修改optimizer的initial lr

完整的实验代码如下

  1. from torch.optim import lr_scheduler
  2. test_optimizer = torch.optim.SGD([torch.randn(1, requires_grad=True)], lr=1e-3)
  3. exp_lr_scheduler = torch.optim.lr_scheduler.StepLR(test_optimizer, step_size=3, gamma=0.5)
  4. for epoch in range(1, 12):
  5.     exp_lr_scheduler.step() # 要每一个epoch都要使用
  6.     print('Epoch {}, lr {}'.format(
  7.         epoch, test_optimizer.param_groups[0]['lr']))
  8. print('='*10)
  9. test_optimizer.param_groups[0]['initial_lr'] = test_optimizer.param_groups[0]['lr'] # 需要修改optimizer的initial lr
  10. cos_lr_scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(test_optimizer, T_max = 3, eta_min=0.00001)
  11. for epoch in range(12, 24):
  12.     cos_lr_scheduler.step() # 要每一个epoch都要使用
  13.     print('Epoch {}, lr {}'.format(
  14.         epoch, test_optimizer.param_groups[0]['lr']))

我们看一下learning rate的变化:

PyTorch使用记录02

torch.cat测试

torch.cat可以用来合并两个tensor向量。我们下面简单举一个例子,看一下最终的效果。

  1. # torch.cat测试
  2. x_test = torch.tensor([[[0,1,0]],[[1,0,1]]])
  3. y_test = torch.tensor([[[2,2,2]],[[3,3,3]]])
  4. print(x_test.shape)
  5. print('===')
  6. print(torch.cat((x_test, y_test), dim=1))
  7. print(torch.cat((x_test, y_test), dim=1).shape)
  8. print('===')
  9. print(torch.cat((x_test, y_test), dim=0))
  10. print(torch.cat((x_test, y_test), dim=0).shape)

最终输出的结果如下所示,设置不同的dim合并的维度是不同的:

PyTorch使用记录02

transform.Normalize()的简单介绍

参考资料: Understanding transform.Normalize( )

我们在进行图片的数据的时候, 有时会使用Normalize, 下面简单解释一下这个操作. 如下面的transforms.Normalize,我们对其进行解释.

  1. transform = transforms.Compose([
  2.                 transforms.ToTensor(),
  3.                 transforms.Normalize(mean=[0.5], std=[0.5])])

Normalize是进行下面的操作:  image = (image - mean) / std

在上面的例子中, mean=0.5, std=0.5, 这样使得image的范围变为[-1,1].

这是因为, 原始图片中最小值为0,于是(0-0.5)/0.5=-1变为了-1;最大值为1,(1-0.5)/0.5=1变换后还是1.

如果我们想要进行还原,可以使用下面的计算方式: image = ((image * std) + mean)

当mean=0.5, std=0.5的时候,我们可以写成下面的函数进行还原。

  1. def denorm(x):
  2.     out = (x + 1) / 2
  3.     return out.clamp(0, 1)

关于这么做是否可以使得结果变得更好, 现在不是很确定, 但是大部分的论文都是这么做的.

torch.bmm-Pytorch中的矩阵乘法

下面我们看一下在Pytorch中如何进行矩阵乘法。我们测试的矩阵乘法如下所示:

PyTorch使用记录02

我们使用Pytorch做以下测试,可以看到结果是和我们自己计算是一样的。

  1. # torch.bmm测试-这是矩阵乘法
  2. x_test = torch.tensor([[[0,1,0]],[[1,0,1]]])
  3. y_test = torch.tensor([[[1],[2],[3]],[[4],[5],[6]]])
  4. print(x_test.shape,y_test.shape)
  5. print(torch.bmm(x_test,y_test))
  6. """
  7. torch.Size([2, 1, 3]) torch.Size([2, 3, 1])
  8. tensor([[[ 2]],
  9.         [[10]]])
  10. """

Softmax的dim的一些说明

这里说一下nn.Softmax中的dim的一些说明。

  1. # Softmax测试
  2. x_test = torch.tensor([[[0,1,0]],[[1,1,1]]]).float()
  3. print(x_test.shape)
  4. F.softmax(x_test,dim=2)
  5. """
  6. torch.Size([2, 1, 3])
  7. tensor([[[0.2119, 0.5761, 0.2119]],
  8.         [[0.3333, 0.3333, 0.3333]]])
  9. """

这里设为了dim=2,即对最里面的内容进行softmax求解。

Softmax与LogSoftmax

关于这两者的比较,可以查看这一篇文章,Softmax v.s. LogSoftmax

简单说一下就是当如果在softmax_xi很大的情況下(接近1),比如說0.999,那整個反向傳遞就會多乘上(0.999)(1–0.999)=0.999*0.001=0.000999,這代表你的誤差整整縮小超過1000倍,除非loss本身就很大,不然整體網路的訓練效果會非常非常差.

同理在softmax_xi很小(接近0)的情況下也會有一樣的情況.(详细看上面的文章)

但是使用LogSoftmax越接近0相当于之前Softmax越接近于1的情况。我们可以看到下面的演示,使用softmax的时候, 越接近1,在使用log_softmax的时候就越接近于0.

PyTorch使用记录02

Using pad_packed_sequence in Pytorch

在RNN的训练中,我们有的时候需要进行padding, 从而才可以使用batch进行训练。但是这样,我们需要在后面填充0.会对最后的结果有所影响,所在在Pytorch中可以使用pack_padded_sequence()和pad_packed_sequence()来处理变长的序列。压缩的过程如下所示。

具体的说明可以看一下链接 : Pytorch中如何处理RNN输入变长序列padding

PyTorch使用记录02

需要说明一下,这么做首先也是需要进行padding的(看上图), 只不过会需要记住在padding之前,每句话的原始长度。

下面看一下在如何将pack_padded_sequence()和pad_packed_sequence()加在整个训练的过程中去。

  1. pack_padded_sequence(batch_in, seq_lengths, batch_first=True)

在使用pack_padded_sequence的时候, 需要注意batch_in中的length需要按从大到小的顺序进行排序, 第二个参数seq_length表示batch中每一句话的长度。

下面是一个整体的流程:How to use pad_packed_sequence in pytorch

  1. # SORT YOUR TENSORS BY LENGTH!(按长度进行排序)
  2. seq_lengths, perm_idx = seq_lengths.sort(0, descending=True)
  3. seq_tensor = seq_tensor[perm_idx]
  4. # utils.rnn lets you give (B,L,D) tensors where B is the batch size, L is the maxlength, if you use batch_first=True
  5. # Otherwise, give (L,B,D) tensors
  6. seq_tensor = seq_tensor.transpose(0,1) # (B,L,D) -> (L,B,D)
  7. # embed your sequences(embedding)
  8. seq_tensor = embed(seq_tensor)
  9. # pack them up nicely
  10. packed_input = pack_padded_sequence(seq_tensor, seq_lengths.cpu().numpy())
  11. # throw them through your LSTM (remember to give batch_first=True here if you packed with it)
  12. packed_output, (ht, ct) = lstm(packed_input)
  13. # unpack your output if required
  14. output, _ = pad_packed_sequence(packed_output)
  15. print (output)
  16. # Or if you just want the final hidden state?
  17. print (ht[-1])

下面是一段我在实际使用的时候写的,我们主要看forward的部分。

  1. class ClickSingleGRU(nn.Module):
  2.     def __init__(self, vocab_size, embedding_dim, hidden_units, batch_sz, output_size, layers=2, Drop_switch=False):
  3.         super(ClickSingleGRU, self).__init__()
  4.         self.Drop_switch = Drop_switch # 是否使用dropout
  5.         self.vocab_size = vocab_size # 总的单词的个数
  6.         self.embedding_dim = embedding_dim # embedding之后的元素个数
  7.         self.hidden_units = hidden_units # GRU中的hidden units的个数
  8.         self.batch_sz = batch_sz
  9.         self.output_size = output_size
  10.         self.num_layers = layers # GRU的layers
  11.         # layers
  12.         self.embedding = nn.Embedding(self.vocab_size, self.embedding_dim) # 可以将标号转为向量
  13.         self.dropout = nn.Dropout(0.5)
  14.         self.gru = nn.GRU(self.embedding_dim, self.hidden_units, num_layers=self.num_layers, batch_first = True, bidirectional=True, dropout=0.5)
  15.         self.fc = nn.Linear(self.hidden_units*2, self.output_size)
  16.         self.batchNorm = nn.BatchNorm1d(self.hidden_units*2)
  17.     def init_hidden(self):
  18.         # 使用了双向RNN, 所以num_layer*2
  19.         return torch.zeros((self.num_layers*2, self.batch_sz, self.hidden_units)).to(device1)
  20.     def forward(self, x, x_len):
  21.         x = x.to(device1)
  22.         # 按照顺序进行排序
  23.         _, perm_idx = x_len.sort(0, descending=True)
  24.         x = x[perm_idx] # 对样本x重新排序
  25.         x_len = x_len[perm_idx] # 对长度x重新排序
  26.         # 进行正向传播
  27.         self.batch_sz = x.size(0)
  28.         x = self.embedding(x)
  29.         # print(x.shape)
  30.         # print(x_len)
  31.         x = pack_padded_sequence(x, x_len, batch_first=True)
  32.         self.hidden = self.init_hidden()
  33.         output, self.hidden = self.gru(x, self.hidden)
  34.         output, _ = pad_packed_sequence(output, batch_first=True)
  35.         # print(output.shape)
  36.         # 因为是 batch*seq*output, 所以要取最后一个seq
  37.         output = output[:,-1,:]
  38.         # print(output.shape)
  39.         if self.Drop_switch:
  40.             output = self.dropout(output)
  41.         output = self.batchNorm(output)
  42.         output = self.fc(output)
  43.         # print(output.shape)
  44.         # 对输出重新排序, 这个需要与最后的label对应上
  45.         output = output[perm_idx]
  46.         # print(output.shape)
  47.         return output

forward会传入两个参数,一个是要训练的文本(这个是padding之后的,一个batch),x_len表示这个batch中每句话的实际长度。

之后首先就先按x_len的大小, 对x和x_len进行排序。进行传播后,最后还需要还原为原来的x的顺序,需要和label的顺序对应上。

下面图片可以看到,进行两次转换之后可以还原。

PyTorch使用记录02
  • 微信公众号
  • 关注微信公众号
  • weinxin
  • QQ群
  • 我们的QQ群号
  • weinxin
王 茂南

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: