PyTorch使用记录

  • A+
所属分类:深度学习
摘要这一篇文章主要记录一下Pytorch在日常使用中,我经常使用到的一些功能,主要是一个记录的功能。

简介

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

Pytorch中的variable, tensor与numpy相互转化的方法

  1. import torch
  2. from torch.autograd import Variable
  3. import numpy as np

numpy转tensor

  1. # numpy转换为tensor
  2. inputdata = np.array([1,2])
  3. torch.from_numpy(inputdata)
PyTorch使用记录

tensor转numpy

  1. # tensor转换为numpy
  2. inputdata = torch.arange(1,10,1)
  3. inputdata.numpy()
PyTorch使用记录

tensor转换为variable

  1. # tensor转换为variable
  2. inputdata = torch.arange(1,10,1).float()
  3. Variable(inputdata,requires_grad=True)
PyTorch使用记录

variable转换为tensor

  1. # variable转换为tensor
  2. inputdata = torch.arange(1,10,1).float()
  3. Variable(inputdata,requires_grad=True).data
PyTorch使用记录

Pytorch中tensor类型转换

直接在后面增加变量类型即可,下面简单举例。

  1. torch.ones(2,2).long()
PyTorch使用记录

Pytorch中分类损失函数--CrossEntropyLoss()

一次在使用CrossEntropyLoss()的时候,出现了如下的报错 :

RuntimeError: multi-target not supported at /opt/conda/condabld/pytorch_1518243271935/work/torch/lib/THNN/generic/ClassNLLCriterion.c:22

发现是target的dimension的问题。关于target的dimension, 不应该是(batchSize x 1),这样二维的。而应该是(batchSize, ),我们可以通过labels.squeeze_()来解决这个问题.

参考资料 : pytorch error: multi-target not supported in CrossEntropyLoss()

Pytorch中交换顺序--permute

这个是RNN中经常会有被使用到,因为RNN的输入的顺序需要是n_step × batch_size × n_inputs, 一般情况下我们的顺序是batch_size × n_step × n_inputs, 这个时候就需要进行顺序的交换了。

我们下面看一个简单的例子。测试数据如下:

  1. x_test = torch.from_numpy(np.array([[[1,2,1,2],[5,6,5,6],[8,9,8,9]],[[1,2,1,2],[5,6,5,6],[8,9,8,9]]]))
  2. x_test.size()
  3. # torch.Size([2, 3, 4])
  4. x_test
  5. """
  6. tensor([[[1, 2, 1, 2],
  7.          [5, 6, 5, 6],
  8.          [8, 9, 8, 9]],
  9.         [[1, 2, 1, 2],
  10.          [5, 6, 5, 6],
  11.          [8, 9, 8, 9]]], dtype=torch.int32)
  12. """

我们交换顺序。

  1. # 我们交换一下顺序
  2. x_permute = x_test.permute(1,0,2)
  3. x_permute.size()
  4. # torch.Size([3, 2, 4])
  5. # 在将其交换回去
  6. x_permute = x_permute.permute(1,0,2)
  7. x_permute.size()
  8. # torch.Size([2, 3, 4])
  9. # 看一下最终的结果和一开始是否相同
  10. x_permute
  11. """
  12. tensor([[[1, 2, 1, 2],
  13.          [5, 6, 5, 6],
  14.          [8, 9, 8, 9]],
  15.         [[1, 2, 1, 2],
  16.          [5, 6, 5, 6],
  17.          [8, 9, 8, 9]]], dtype=torch.int32)
  18. """

查看最终打印的结果,是和交换之前是一样的。

模型的保存

整个模型的保存

  1. # 保存和加载整个模型
  2. torch.save(model_object, 'model.pkl')
  3. model = torch.load('model.pkl')

 保存模型参数

  1. # 仅保存和加载模型参数
  2. torch.save(model_object.state_dict(), 'params.pkl')
  3. model_object.load_state_dict(torch.load('params.pkl'))

model.eval()与torch.no_grad()的区别

参考链接:model.eval() vs with torch.no_grad()

These two have different goals:

  • model.eval() will notify all your layers that you are in eval mode, that way, batchnorm or dropout layers will work in eval mode instead of training mode.
  • torch.no_grad() impacts the autograd engine and deactivate it. It will reduce memory usage and speed up computations but you won’t be able to backprop (which you don’t want in an eval script).

所以在test的时候,我们通常会像下面这么书写

  1. model.eval()
  2. with torch.no_grad():
  3.     correct = 0
  4.     total = 0
  5.     for data, labels in test_dataset:
  6.         data = data.to(device)
  7.         labels = labels.to(device)
  8.         outputs = model(data)
  9.         _, predicted = torch.max(outputs.data, 1)
  10.         total += labels.size(0)
  11.         correct += (predicted == labels).sum().item()

Tensor张量

view方法修改tensor形状

view方法前后元素总数一致;且新tensor和源tensor共享内存,改变其中一个,另一个也会进行改变。要注意的是:当某一维度为1的时候,会自动计算它的大小

首先是基本用法:

PyTorch使用记录

关于共享内存的说明:

PyTorch使用记录

squeeze与unsqueeze的使用

作用:从数组的形状中删除单维度条目,即把shape中为1的维度去掉

场景:在机器学习和深度学习中,通常算法的结果是可以表示向量的数组(即包含两对或以上的方括号形式[[]]),如果直接利用这个数组进行画图可能显示界面为空。我们可以利用squeeze函数将表示向量的数组转换为秩为1的数组,这样利用matplotlib库函数画图时,就可以正常的显示结果了。

参考:Numpy库学习—squeeze()函数

PyTorch使用记录

clone的使用

我们看一下使用clone之后,改变原始值是否会对clone后的值进行改变。如下面的测试

  1. a = torch.from_numpy(np.array([[1,2,3],[4,5,6]])).float()
  2. b = a.clone()
  3. print(a)
  4. print(b)
  5. # 修改b, 测试a是否会改变
  6. print('====')
  7. b[0,1]=torch.tensor(30.0)
  8. print(a)
  9. print(b)
PyTorch使用记录

可以看到,对上面b的修改,不会改变a的值。

可视化方式

有的时候,我们需要将图像转换为tensor,或是将tensor转换为图像进行可视化,这里记录一下方法。首先导入库。

  1. from PIL import Image
  2. import torchvision.transforms as transforms

加载图片, 转换为Tensor

我们使用torchvision.transform来完成转换。 我们可以定义一个loader, 里面进行图片大小的修改和转换为Tensor.

  1. # 加载图片, 转换为tensor
  2. device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
  3. imsize = 512
  4. loader = transforms.Compose([
  5.     transforms.Resize(imsize),  # scale imported image
  6.     transforms.ToTensor()])  # transform it into a torch tensor
  7. def image_loader(image_name):
  8.     """图片load函数
  9.     """
  10.     image = Image.open(image_name)
  11.     # fake batch dimension required to fit network's input dimensions
  12.     image = loader(image).unsqueeze(0)
  13.     return image.to(device, torch.float)

这样加载图片时,可以直接使用函数image_loader即可。

  1. # 载入图片
  2. imgdata = image_loader('./test.jpg')
  3. imgdata.size()
PyTorch使用记录

将Tensor转换为图片

这里我们使用transforms.ToPILImage进行转换.

  1. # 将tensor转换为图片
  2. image = imgdata.cpu().clone()
  3. image = image.squeeze(0)
  4. unloader = transforms.ToPILImage()
  5. unloader(image) # 转换为PIL image

图片压缩过了,有些看不清代码了,代码和上面是一样的。

PyTorch使用记录

PIL: Thumbnail and end up with a square image

使用PIL将图片保存为正方形的图像。

  1. from PIL import Image, ImageOps
  2. thumb = ImageOps.fit(image, size, Image.ANTIALIAS)

测试Max Pooling, Mean Pooling

使用Max Pooling

  1. # 测试Max Pooling
  2. m = nn.MaxPool2d(kernel_size=4,stride=4)
  3. maximgdata = m(imgdata)
  4. maximgdata.size()
  5. > torch.Size([1, 3, 128, 128])
  6. # 进行可视化
  7. # 可以看到使用maxpool后人眼大概观察是相同的
  8. unloader(maximgdata.cpu().squeeze(0))
PyTorch使用记录
  1. # 测试Average Pool
  2. m = nn.AvgPool2d(kernel_size=4,stride=4)
  3. avgimgdata = m(imgdata)
  4. avgimgdata.size()
  5. > torch.Size([1, 3, 128, 128])
  6. # 使用均值效果好像更好一些
  7. unloader(avgimgdata.cpu().squeeze(0))
PyTorch使用记录

hook的使用

我们可以简单理解一下,hook是用来获得网络中间的一些值的,主要有下面的两种。

参考链接 : pytorch中的钩子(Hook)有何作用?

关于hook的一个更加好的用法,可以查看这个链接(这里定义了类, 使用起来会更加方便) : CNN可视化Convolutional Features

register_hook

register_hook,是针对Variable对象的。

  1. import torch
  2. from torch.autograd import Variable

下面我们对下图中的表达式求梯度,其中y作为中间变量,pytorch是不会保存其梯度的。

PyTorch使用记录
  1. x = Variable(torch.randn(2, 1), requires_grad=True)
  2. y = x+2
  3. z = torch.mean(torch.pow(y, 2))
  4. lr = 1e-3
  5. z.backward() # 计算梯度
  6. x.grad.data
PyTorch使用记录

但是,我们可以使用register_hook来保存y的梯度.

关于register_hook的简单使用如下:

The hook will be called every time after forward() has computed an output. It should have the following signature:

  1. hook(grad) -> Tensor or None

具体的我们看一下下面的这个例子。

  1. # 我们可以定义一个hook来保存中间的变量
  2. def variable_hook(grad):
  3.     print('y的梯度\n',grad)
  4. x = Variable(torch.randn(2, 1), requires_grad=True)
  5. y = x+2
  6. # 注册hook
  7. hook_handle = y.register_hook(variable_hook)
  8. z = torch.mean(torch.pow(y, 2))
  9. z.backward() # 计算梯度
  10. print('x的梯度\n',x.grad.data)
  11. # 使用完毕之后需要移除hook
  12. hook_handle.remove()

可以看到此时是可以打印出y的梯度的。

PyTorch使用记录

需要注意的是,在使用之后,需要移除hook,如果之后不需要使用的话.

hook module

除了上面可以hook变量外,我们还可以hook住nn.Module这个对象。下面是文档对该函数的介绍。

The hook will be called every time after forward() has computed an output. It should have the following signature:

  1. hook(module, input, output) -> None

我们还是看一下具体的例子。首先定义网络,我们定义一个简单的网络

  1. class testModel(nn.Module):
  2.     def __init__(self):
  3.         super(testModel, self).__init__()
  4.         self.linear1 = nn.Linear(2,2,bias=False)
  5.         self.linear2 = nn.Linear(2,2,bias=False)
  6.     def forward(self,x):
  7.         out = self.linear2(self.linear1(x))
  8.         return out

接着我们初始化模型,并进行参数的初始化(这一步是为了方便我们看最后的结果)

  1. # 模型初始化
  2. testmodel = testModel()
  3. # 打印初始参数
  4. print('====原始系数====')
  5. for num, (name, params) in enumerate(testmodel.named_parameters()):
  6.     print(num,params.data.shape,'\n',params.data)
  7. # 参数初始化
  8. # 做初始化系数
  9. testmodel.linear1.weight.data = torch.FloatTensor([[1,2],[3,4]])
  10. testmodel.linear2.weight.data = torch.FloatTensor([[5,6],[7,8]])
  11. # 重新打印新的系数
  12. print('====更新系数====')
  13. for num, (name, params) in enumerate(testmodel.named_parameters()):
  14.     print(num,params.data.shape,'\n',params.data)
PyTorch使用记录

接着我们定义hook,并注册hook。注意这里一定是需要return hook的,如果按照下面的这种写法。

  1. # 全局变量, 存储每一个的输出
  2. activation = {}
  3. # 定义hook
  4. def get_linear(name):
  5.     """返回某一层的传播值
  6.     """
  7.     def hook(module,input,output):
  8.         print('module:\n',module)
  9.         print('input:\n',input)
  10.         print('output:\n',output)
  11.         activation[name]=output.detach()
  12.         print('====')
  13.     return hook
  14. # 注册hook
  15. hook_handle1 = testmodel.linear1.register_forward_hook(get_linear('linear1'))
  16. hook_handle2 = testmodel.linear2.register_forward_hook(get_linear('linear2'))

注意上面代码,有两部分。第一部分是定义了一个全局变量,用来存储每一次output的输出;另一个是把每一次的input和output都打印出来.

最后我们定义变量,并进行正向传播

  1. # 定义变量
  2. input_data = Variable(torch.FloatTensor([[1,2]]))
  3. print(input_data)
  4. # 正向传播
  5. out = testmodel(input_data)
  6. print(out)

可以看到将每一步的值都进行了输出。

PyTorch使用记录

我们再看一下前面定义的全局变量,可以看到也是添加进了input的值。

PyTorch使用记录

最后我们注意要将hook进行移除(保持好的习惯)

  1. hook_handle1.remove()
  2. hook_handle2.remove()

网络传播过程去参数的初始化

还是使用上面hook例子中的网络结构,在下面再重复一遍。

  1. class testModel(nn.Module):
  2.     def __init__(self):
  3.         super(testModel, self).__init__()
  4.         self.linear1 = nn.Linear(2,2,bias=False)
  5.         self.linear2 = nn.Linear(2,2,bias=False)
  6.     def forward(self,x):
  7.         out = self.linear2(self.linear1(x))
  8.         return out

网络权重的初始化

  1. # 模型初始化
  2. testmodel = testModel()
  3. # 打印初始参数
  4. print('====原始系数====')
  5. for num, (name, params) in enumerate(testmodel.named_parameters()):
  6.     print(num,params.data.shape,'\n',params.data)
  7. # 参数初始化
  8. # 做初始化系数
  9. testmodel.linear1.weight.data = torch.FloatTensor([[1,2],[3,4]])
  10. testmodel.linear2.weight.data = torch.FloatTensor([[5,6],[7,8]])
  11. # 重新打印新的系数
  12. print('====更新系数====')
  13. for num, (name, params) in enumerate(testmodel.named_parameters()):
  14.     print(num,params.data.shape,'\n',params.data)

正向传播

  1. # 定义变量
  2. input_data = Variable(torch.FloatTensor([[1,2]]))
  3. # 正向传播
  4. out = testmodel(input_data)
  5. out
  6. > tensor([[ 91., 123.]], grad_fn=<MmBackward>)

这一步相当于下面的矩阵运算, 注意一下上面权重初始化的时候的值。

  1. # 使用矩阵运算进行测试
  2. import numpy as np
  3. # 这里相当于 [[5,6],[7,8]] * [[1,2],[3,4]] * [1.2]
  4. x_input = np.array([1,2])
  5. x1_test = np.array([[1,2],[3,4]])
  6. x2_test = np.array([[5,6],[7,8]])
  7. np.dot(x2_test,np.dot(x1_test,x_input))
  8. array([ 91, 123])

nn神经网络组件

MaxPool2d 与 MaxUnpool2d

原理解释

关于MaxUnpool2d的过程,可以简单看下面图的解释。关于Pytorch中的一些内容,可以查看下面的链接进行了解,About MaxPool and MaxUnpool

PyTorch使用记录

其中的indices会用来保存在MaxPool过程中最大点的序号;在还原的时候,序号位置为最大的值,其他位置均为0。(这是在Pytorch中MaxUnpool2d的实现的大概过程)

当然,这里会遇到一个问题,即这不是一个一一对应的关系,两个大小不同的矩阵,在经过Maxpool后大小可能是一样的,所以还原的时候需要指定output_size的大小。下面是一个简单的解释。

PyTorch使用记录

例子演示

下面看一下在Pytorch中具体的使用的方式。

  1. # MaxPooling的测试,这里一定要输出return_indices
  2. m = nn.MaxPool2d(kernel_size=2,stride=2,padding=0,return_indices=True)
  3. inputData = torch.tensor([[[[ 1.,  2,  8,  7],
  4.                             [ 3,  4,  6,  5],
  5.                             [ 9, 10, 16, 15],
  6.                             [13, 14, 12, 11]]]])
  7. out,indices = m(inputData)

我们看一下池化后的矩阵与indices的值。

PyTorch使用记录

接着我们使用MaxUnpool2d来进行还原,注意还原的过程中需要输入indicesoutput_size其余关于kernel_size和stride值得设置,只需要设置为和MaxPool2d时一样得即可.

  1. # MaxUnpool2d的使用
  2. m = nn.MaxUnpool2d(kernel_size=2,stride=2,padding=0)
  3. m(out,indices,output_size=inputData.size())
PyTorch使用记录

Conv2d使用介绍

新的pic的长宽可以通过下面的式子计算得到。

PyTorch使用记录
  1. m = nn.Conv2d(in_channels=3,out_channels=1,kernel_size=3,stride=1,padding=0)
  2. inputData = torch.ones(1, 3, 9, 9)
  3. out = m(inputData)
  4. out.size()
  5. > torch.Size([1, 1, 7, 7])

上面的例子中,我们输出的图片大小为399,经过卷积之后,变为177

ConvTransposed2d使用介绍

ConvTransposed2d其实就是进行卷积变换。直观可以参考下面链接的动态图的展示。

https://github.com/vdumoulin/conv_arithmetic

其中在Pytorch中,新的长宽可以由下面的式子计算得到:

  1. H_out = (H_in−1)×stride[0]−2×padding[0]+dilation[0]×(kernel_size[0]−1)+output_padding[0]+1
  2. W_out = (W_in−1)×stride[1]−2×padding[1]+dilation[1]×(kernel_size[1]−1)+output_padding[1]+1

我们可以看一下下面的这个例子,将上面经过卷积之后的结果进行还原。即原本是399,经过卷积之后为177,这里希望还原为399的大小。

  1. m = nn.ConvTranspose2d(in_channels=1,out_channels=3,kernel_size=3,stride=1,padding=0)
  2. inputData = torch.ones(1, 1, 7, 7)
  3. out = m(inputData)
  4. out.size()
  5. > torch.Size([1, 3, 9, 9])

上面例子是stride=1的情况,实际上在stride>1的时候,会出现和上面Pool一样的情况,即不是一一对应的关系。

还是同样的问题,原始大小为22与33的图像,在经过kernel=2,stride=2之后均为11的大小,所以在ConvTransposed2d的时候,有一个参数output_padding需要进行设置。我们看一下下面的这个例子。可以看到原始图像经过卷积后大小为11

  1. m = nn.Conv2d(in_channels=3,out_channels=1,kernel_size=2,stride=2,padding=0)
  2. inputData = torch.ones(1, 3, 3, 3)
  3. out = m(inputData)
  4. out.size()
  5. > torch.Size([1, 1, 1, 1])

output_padding=0的情况下,可以看到转换的结果为原始图片大小为2*2的。

  1. # output_padding=0的情况
  2. m = nn.ConvTranspose2d(in_channels=1,out_channels=3,kernel_size=2,stride=2,padding=0,output_padding=0)
  3. inputData = torch.ones(1, 1, 1, 1)
  4. out = m(inputData)
  5. out.size()
  6. > torch.Size([1, 3, 2, 2])

output_padding=1的情况下,可以看到转换的结果为原始图片大小为3*3的。所以,我们需要通过output_padding来控制输出的大小。

  1. # output_padding=1的情况
  2. m = nn.ConvTranspose2d(in_channels=1,out_channels=3,kernel_size=2,stride=2,padding=0,output_padding=1)
  3. inputData = torch.ones(1, 1, 1, 1)
  4. out = m(inputData)
  5. out.size()
  6. > torch.Size([1, 3, 3, 3])

ConvTransposed2d与MaxUnpool2d结合

下面把两个结合看一下,放一个小的例子.首先定义encode和decode,还有input.

  1. # encode
  2. conv = nn.Conv2d(in_channels=3,out_channels=1,kernel_size=2,stride=2,padding=0)
  3. maxpool = nn.MaxPool2d(kernel_size=2,stride=2,padding=0,return_indices=True)
  4. # decode
  5. maxunpool = nn.MaxUnpool2d(kernel_size=2,stride=2,padding=0)
  6. convtranspose = nn.ConvTranspose2d(in_channels=1,out_channels=3,kernel_size=2,stride=2,padding=0,output_padding=1)
  7. # input
  8. inputdata = torch.ones(1,3,9,9)

接着就看一下输出和输出的大小即可。

  1. # encode
  2. conv_output = conv(inputdata)
  3. encode_output,indices = maxpool(conv_output)
  4. # decode
  5. decode_output = convtranspose(maxunpool(encode_output,indices,output_size=conv_output.size()))
  6. decode_output.size()
  7. > torch.Size([1, 3, 9, 9])

可以看到可以成功还原大小。

结语

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

发表评论

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