本专栏主要介绍Pytorch中比较好用的API

nn.Flatten()

torch.nn.Flatten(start_dim=1, end_dim=-1)

​ 作用:将连续的维度范围展平为张量

​ 参数:开始维度,结束维度

input = torch.randn(32, 1, 5, 5)  # 随机数
# With default parameters
m = nn.Flatten() # 默认维度展开 (1, -1)
output = m(input)
output.size()
#torch.Size([32, 25])
# With non-default parameters
m = nn.Flatten(0, 2) # 指定维度展开 (0, -2)
output = m(input)
output.size()
#torch.Size([160, 5])

next()和iter()

​ 经常会遇到next和iter联合使用,一般用于取数据时X, y = next(iter(data.DataLoader(mnist_train, batch_size=18)))

​ iter(可迭代对象)

​ 补充:可迭代对象Iterable:

    • 一类是:list、tuple、dict、set、str
    • 二类是:generator(都是Iterator对象),包含生成器和带yield的generator function
      生成器不但可以作用于for,还可以被next函数不断调用并且返回下一个值,可以被next函数不断调用返回下一个值的对象称为迭代器(Iterator)。可迭代的对象如list、dict等需要用iter()函数转化成Iterator

​ next(iterator[, default])

  • iterator –可迭代对象
  • default –可选,用于设置在没有下一个元素时返回该默认值,如果不设置,又没有下一个元素则会触发 StopIteration 异常
list_ = [1, 2, 3, 4, 5]
it = iter(list_)
for i in range(5):
line = next(it)
print("第%d 行, %s" %(i, line))

输出结果:
0 行, 1
1 行, 2
2 行, 3
3 行, 4
4 行, 5

layer.__class__.__name__

​ 用于调试神经网络时,可以打印出网络每层的名称和形状

X = torch.rand(size=(1, 1, 28, 28), dtype=torch.float32)
for layer in net:
X = layer(X)
print(layer.__class__.__name__, 'output shape: \t', X.shape)

summary()

​ 类似tensorflow中的summary,可以显示使用pytorch写的网络结构

pip install torchsummary

import torch
import torch.nn as nn
import torch.nn.functional as F
from torchsummary import summary # 导入summary函数


class FC(nn.Module):
def __init__(self):
super().__init__()
self.liner_1 = nn.Linear(40 * 40, 120)
self.liner_2 = nn.Linear(120, 84)
self.liner_3 = nn.Linear(84, 2)

def forward(self, input):
x = input.view(-1, 40 * 40)
x = F.relu(self.liner_1(x))
x = F.relu(self.liner_2(x))
x = self.liner_3(x)
return x


device = torch.device("cuda:0")


model = FC().to(device)
print(model)
summary(model, (3, 40, 40))
# 输出
FC(
(liner_1): Linear(in_features=1600, out_features=120, bias=True)
(liner_2): Linear(in_features=120, out_features=84, bias=True)
(liner_3): Linear(in_features=84, out_features=2, bias=True)
)
----------------------------------------------------------------
Layer (type) Output Shape Param #
================================================================
Linear-1 [-1, 120] 192,120
Linear-2 [-1, 84] 10,164
Linear-3 [-1, 2] 170
================================================================
Total params: 202,454
Trainable params: 202,454
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.02
Forward/backward pass size (MB): 0.00
Params size (MB): 0.77
Estimated Total Size (MB): 0.79
----------------------------------------------------------------

net.fc和net.fc.weight

​ net是实例化后的网络,fc是网络中某层的名称

net.fcnet.fc.weight分别是直接访问net的某层和该层的权重,可以直接通过赋值来修改层和参数

finetune_net = torchvision.models.resnet18(pretrained=True)  # 参数表示不仅定义模型,而且取出在ImageNet上训练好的参数
finetune_net.fc = nn.Linear(finetune_net.fc.in_features, 2) # 修改最后一层
nn.init.xavier_uniform_(finetune_net.fc.weight) # 只对最后一层的weight做初始化

net.children()

​ net是实例化后的网络,children()方法是提取出该对象的所有层,通常的用法:

list(net.children())[-3:]  # 取出网络的最后三层

net.add_module()

​ 网络添加层

net.add_module('transpose_conv', nn.ConvTranspose2d(num_classes, num_classes, kernel_size=64, padding=16, stride=32))

tensor.detach()

​ 通常用在冻结网络部分结构

​ 返回一个新的tensor,从当前计算图中分离下来的,但是仍指向原变量的存放位置,不同之处只是requires_grad为false,得到的这个tensor永远不需要计算其梯度,不具有grad。即使之后重新将它的requires_grad置为true,它也不会具有梯度grad

​ 这样我们就会继续使用这个新的tensor进行计算,后面当我们进行反向传播时,到该调用detach()的tensor就会停止,不能再继续向前进行传播

​ 注:使用detach返回的tensor和原始的tensor共同一个内存,即一个修改另一个也会跟着改变

import torch

a = torch.tensor([1, 2, 3.], requires_grad=True)
print(a.grad)
out = a.sigmoid()

out.sum().backward()
print(a.grad)
'''返回:
None
tensor([0.1966, 0.1050, 0.0452])
'''
import torch

a = torch.tensor([1, 2, 3.], requires_grad=True)
print(a.grad)
out = a.sigmoid()
print(out)

#添加detach(),c的requires_grad为False
c = out.detach()
print(c)

#这时候没有对c进行更改,所以并不会影响backward()
out.sum().backward()
print(a.grad)

'''返回:
None
tensor([0.7311, 0.8808, 0.9526], grad_fn=<SigmoidBackward>)
tensor([0.7311, 0.8808, 0.9526])
tensor([0.1966, 0.1050, 0.0452])
'''

​ 从上可见tensor c是由out分离得到的,但是我也没有去改变这个c,这个时候依然对原来的out求导是不会有错误的,即c,out之间的区别是c是没有梯度的,out是有梯度的,但是需要注意的是下面两种情况是汇报错的

​ ① 当使用detach()分离tensor,然后用这个分离出来的tensor去求导数,会影响backward(),会出现错误

​ ② 当使用detach()分离tensor并且更改这个tensor时,即使再对原来的out求导数,会影响backward(),会出现错误

torch.cat()

>>> x = torch.randn(2, 3)
>>> x
tensor([[ 0.6580, -1.0969, -0.4614],
[-0.1034, -0.5790, 0.1497]])
>>> torch.cat((x, x, x), 0) # 第0维,最外层
tensor([[ 0.6580, -1.0969, -0.4614],
[-0.1034, -0.5790, 0.1497],
[ 0.6580, -1.0969, -0.4614],
[-0.1034, -0.5790, 0.1497],
[ 0.6580, -1.0969, -0.4614],
[-0.1034, -0.5790, 0.1497]])
>>> torch.cat((x, x, x), 1) # 第1维,最内层
tensor([[ 0.6580, -1.0969, -0.4614, 0.6580, -1.0969, -0.4614, 0.6580,
-1.0969, -0.4614],
[-0.1034, -0.5790, 0.1497, -0.1034, -0.5790, 0.1497, -0.1034,
-0.5790, 0.1497]])

torch.squeeze()和torch.unsqueeze()

torch.squeeze(A, N)

​ 减少张量A指定位置N的维度,如果张量A维度为(1, 1, 3),执行torch.squeeze(A, 1)后A的维度变为(1, 3)

​ 如果指定的维度大于1,那么操作无效

​ 如果不指定维度N,那么将删除所有维度为1的维度

torch.unsqueeze(A, N)

​ 增加数组A指定位置N的维度,例如两行三列的数组A维度为(2,3),那么这个数组就有三个位置可以增加维度,分别是( [位置0] 2,[位置1] 3 [位置2] )或者是 ( [位置-3] 2,[位置-2] 3 [位置-1] ),如果执行 torch.unsqueeze(A,1),数据的维度就变为了 (2,1,3)

.to(device)

​ torch.tensor的本质是对象,所有的tensor对象都具有这个方法,可将tensor数据放入gpu上运行

​ torch中定义的变量、常量默认都在cpu上,如果想放在GPU上,就需要data.to(device)来指明

​ 将GPU上的数据搬回CPU,可以使用.to(device)或者直接data.cpu(),第二种方式需要注意data必须是no_grad的状态,可以使用with torch.no_grad():来指明

​ device的常用写法:device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

Module类

​ 注意,在Pytorch中,to the best of my knowledge,torch.nn提供的所有模块、网络都是继承nn.Module,这意味着在Pytorch中没有像Tensorflow中的layer概念,直接是全部的Module拼接在一起,那么我们可以选择性地保存部分Module参数

​ 在Pytorch中,只有继承自Module类的子类,程序才会自动地、默认地处理batch这个维度,我们只需要专心对除了batch以外的维度进行操作即可,因为Module类帮我们处理了batch

复数处理

​ 在Pytorch中,如果需要在网络中使用到复数的相关计算,会提示无法实现复数的微分,因此涉及到复数处理部分,可以使用with torch.no_grad():让这部分 不用计算梯度,处理完成后,再为某tensor数据使用data.requires_grad_(True)方法,使下面的操作再次需要计算梯度。这意味着只要父类数据需要计算梯度或者父类数据处于GPU上,那么由它计算得出的子类数据也都是需要计算梯度并且处于GPU上的

但是请注意!!!上述处理方法尽可能少使用,因为如果网络需要grad的变量之间相隔较远,会极大影响网络性能,甚至导致不收敛!

torch.nn.ConvTranspose2d

​ 使用前提:stride > 1

是通过padding使得卷积之后输出的特征图大小保持不变(相对于输入特征图),不代表得到的输出特征图的大小与输入特征图的大小完全相同,而是他们之间的比例保持为 输入特征图大小/输出特征图大小 = stride

​ 比如输入特征图为66,stride=2, kernel_size = 3, 所以进行same卷机操作得输出特征图为33 (6/2 = 3)

​ 如果输入特征图为55,stride=2,kernel_size = 3,这时候设置padding = 1,那么也会得到输出特征图为33

​ 那么这样的情况就会导致在逆卷积时出现一个问题。

​ 问题就是,不同大小的图片经过卷积运算能得到相同尺寸的输出,那么作为逆运算,同样的一张输入图像经过反卷积是否会有不同尺寸的合法输出?这样的话就存在争议了

​ 上面还只是进行same卷积的情况,如果考虑valid卷积,stride=2, kernel_size = 3,padding=0时,输入特征图为77和88的结果也是3*3

​ 解决争议的办法就是使用output_padding参数

​ output_padding的作用是:

​ 当stride > 1时,Conv2d将多个输入形状映射到相同的输出形状。output_padding通过在一边有效地增加计算出的输出形状来解决这种模糊性。

​ 首先我们要认同一个前提:

​ **大多数情况下我们都希望经过卷积/反卷积处理后的图像尺寸比例与步长相等,即**输入特征图大小/输出特征图大小 = stride**,也就是same模式**。

​ 所以我们只要通过添加output_padding这一参数来使得结果满足这一前提,那么输出的图片的大小就能够保证为输入图片*stride的大小,而不是任意可能的大小

​ 实现办法:

​ 因为pytorch将参数padding(注意与output_padding区别)建议设置为(kernel_size - 1)/2,由式子padding= kernel - 1 - padding转换而来

​ 当我们希望得到****输入特征图大小/输出特征图大小 = stride****的话,代入上面的式子能够得到结果:

​ padding = (kernel_size - stride + output_padding )/2

​ 所以为了让padding = (kernel_size - 1)/2,则output_padding应该取值为stride - 1,这样就能够满足*输入特征图大小/输出特征图大小 = stride*

​ 当然,你可以取别的值,这并不会影响到逆卷积的计算,但是在后面进行有关大小的操作时就很可能出现问题,因为输出的图片的大小并不能保证是 输入图片stride的大小,可能是任意正确的大小,如上面举的例子,可能是77或8*8等

torch.einsum()

​ ein 就是爱因斯坦的ein,sum就是求和。einsum就是爱因斯坦求和约定,其实作用就是把求和符号省略

https://www.youtube.com/watch?v=pkVwUVEHmfI

​ 参考https://blog.csdn.net/zhaohongfei_358/article/details/125273126?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-125273126-blog-96462827.235%5Ev38%5Epc_relevant_anti_vip_base&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-125273126-blog-96462827.235%5Ev38%5Epc_relevant_anti_vip_base&utm_relevant_index=1

torch.permute()

​ 用于交换tensor维度,和transpose很像,但是它一步到位

a = torch.tensor([[1,2],[3,4]])
a = a.permute(1, 0) # 将a的0维和1维交换
b.permute(0, 2, 1) # 可以任意交换维度,数字就是在原tensor中的维度索引