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中动态调节学习率的方法, 这里会简单说明一下同时使用两种动态调节的方式,也会介绍一下另一种动态调节的方式,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

Pytorch中图片数据集处理方式

Pytorch中有比较简单的图片数据集的处理方式, 我们可以使用ImageFolder来完成图片数据集的导入. 下面我们来看一个简单的例子.

官方文档ImageFolder介绍

  1. trans = transforms.Compose([
  2.     transforms.Resize(64),
  3.     transforms.ToTensor(),
  4.     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
  5. ])
  6. # 注意这里文件夹的路径
  7. dataset = datasets.ImageFolder('./data', transform=trans) # 数据路径
  8. dataloader = torch.utils.data.DataLoader(dataset,
  9.                                      batch_size=16, # 批量大小
  10.                                      shuffle=True# 乱序
  11.                                      num_workers=2 # 多进程
  12.                                      )

同时我们需要注意的是, 这个类的root路径的书写. 他的文件格式是如下所示的.

  1. root/dog/xxx.png
  2. root/dog/xxy.png
  3. root/dog/xxz.png
  4. root/cat/123.png
  5. root/cat/nsdf3.png
  6. root/cat/asd932_.png

也就是他只需要写到root目录, 然后把每一类图片分别放在对应的文件夹内. 我们不能直接将图片放在root目录下, 不然就会出现如下的报错: RuntimeError: Found 0 images in subfolders of: ./data

上面报错解决链接: RuntimeError: Found 0 images in subfolders of: ./data

 

Pytorch中下划线的含义

关于Pytorch种下划线使用的情况, 详细的内容见下面的参考链接. 总的来说, 就是如果没有很大的内存压力, 就:

  • First of all on the PyTorch sites it is recommended to not use in-place operations in most cases. Unless working under heavy memory pressure it is more efficient in most cases to not use in-place operations.
  • Secondly there can be problems calculating the gradients when using in-place operations

下面看一个简单的例子, 就可以知道下划线加和不加的区别.

PyTorch使用记录02

参考资料: What does the underscore suffix in PyTorch functions mean?

 

Pytorch Tensor get the index of specific value(tensor获得指定值的index)

参考链接: How Pytorch Tensor get the index of specific value

我们有的时候会需要找出Tensor中特定值的index, 我们可以使用下面的方式进行寻找. 如下面的例子中, 我们想要找出value=1的位置所在.

  1. test = torch.Tensor([1,2,3,1,2,3,1,2,3])

我们可以使用nonzero()来完成相关操作.

  1. (test==1).nonzero().squeeze()
  2. """
  3. tensor([0, 3, 6])
  4. """

我们看一下完整的步骤, 下面有每一步操作的结果的显示.

PyTorch使用记录02

Pytorch中tensor的拼接

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

我们可以看一下上面拼接前后shape的改变. 拼接前的shape分别是(2, 1, 3)和(2, 1, 3), 拼接之后的shape为(4, 1, 3)如果dim=0的时候. 我们使用同样的数据使用torch.stack进行测试, 看一下两者有什么不同.

torch.stack测试

还是使用上面的x_test和y_test, 他们的shape都是(2, 1, 3), 我们看一下使用np.stack后的结果.

  1. torch.stack((x_test, y_test)).shape

使用之后的shape转换为torch.Size([2, 2, 1, 3]), 就是直接在外面堆叠. 这个在我们处理训练样本的时候, 可以使用这种方式将不同的样本合成一个batch. 如下面这个简单的例子.

  1. torch.stack(([dataset[i][0] for i in range(36)])).shape

torch.norm的简单使用说明

torch.norm是用来计算范数的, 我们简单来讲解一下用法.

关于范数的相关介绍, 可以查看以下的链接: 正则化技术介绍–L1,L2范数

其中的参数, p表示计算什么范数(l1范数, l2范数, 等), dim表示在哪一个维度进行计算. 下面看一个例子.

计算L1和L2范数, 我们还在最后验证了以下l2范数的计算的结果.

PyTorch使用记录02

除了上面使用torch.norm以外, 我们还可以按下面的方式进行使用.

PyTorch使用记录02

官方文档链接Pytorch--torch.norm

中文的指南torch.norm的理解

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中常见的计算

torch.mm-Pytorch中的矩阵乘法

我们可以使用torch.mm来完成矩阵乘法. 下面看一个简单的例子.

  1. a = torch.from_numpy(np.array([[0,1,0],[1,0,1]]))
  2. b = torch.from_numpy(np.array([[1,4],[2,5],[3,6]]))

最后的计算结果如下, 上面是一个2 3的矩阵乘3 2的矩阵, 结果是一个2 * 2的矩阵.

  1. torch.mm(a,b)
  2. """
  3. tensor([[ 2,  5],
  4.         [ 4, 10]], dtype=torch.int32)
  5. """

 

torch.mul--Pytorch中对应位相乘

我们可以使用torch.mul来完成矩阵的对应位置的相乘. 我们可以看下面的一个例子.

  1. a = torch.from_numpy(np.array([[0,1,0],[1,0,1]]))
  2. b = torch.from_numpy(np.array([[1,2,3],[4,5,6]]))

我们进行计算, 得到如下的结果.

  1. torch.mul(a,b)
  2. """
  3. tensor([[0, 2, 0],[4, 0, 6]], dtype=torch.int32)
  4. """

可以看到结果是对应位进行了相乘, 1的地方还是保留了原来的数值.

同样, 我们可以将mul直接写在数字后面, 进行相乘. 看下面的一个例子. 我们先将所有的数字乘0.5, 再求指数.

  1. torch.from_numpy(np.array([-1.0,2.0,10.0])).mul(0.5).exp()
  2. """
  3. tensor([  0.6065, 2.7183, 148.4132], dtype=torch.float64)
  4. """

 

torch.bmm-Pytorch中的batch的矩阵乘法

下面我们看一下在Pytorch中如何进行batch矩阵乘法。我们测试的矩阵乘法如下所示, 我们简单说明一下, 下面是213, 231, 得到的结果是211. (这个也就是在batch内部, 还是在进行矩阵乘法)

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. """

参考资料torch.bmm官方文档

 

Pytorch生成随机数

生成服从正态分布的随机数

我们生成指定形状的正态分布的数据. 我们可以直接传入tensor的size.

  1. a = torch.from_numpy(np.array([[1,2,3],[4,5,6]]))
  2. torch.randn(*a.size())
  3. """
  4. tensor([[-2.4163, -0.9349, -0.1467],
  5.         [-1.4294, -0.3001,  0.7097]])
  6. """

 

Softmax的一些用法

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

weight clip的用法

有的时候我们在训练网络的时候, 我们会进行梯度裁剪, 下面简单看以下梯度裁剪的例子.

  1. a = torch.tensor([-1.1, 0.8, 1.2, -0.6, 0.9])
  2. a.data.clamp_(-1, 1) # 这个是可以改变原始的a的

上面的意思是将a的data裁剪在范围是(-1, 1)之间. 下面看一下完整的图片.

PyTorch使用记录02

torch.autograd简单使用

官方链接: AUTOMATIC DIFFERENTIATION PACKAGE - TORCH.AUTOGRAD

这里会介绍两个函数, 分别是torch.autograd.backwardtorch.autograd.grad. 看一下两个使用的例子. 我们定义一个函数为, f(x)=x^2+x.

torch.autograd.grad

这个会在WGAN-GP中使用, 计算penalty gradient的时候, 我们可以理解为他是计算某个特定的x的梯度的.

PyTorch使用记录02

torch.autograd.backward

我们可以理解为他是计算整个计算图上每一个变量的梯度. 还是上面相同的例子, 计算出的x_input处的梯度也是相同的.

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: