Pytorch精读

  • Post author:
  • Post category:其他




构建模型

  1. 最简单的Sequential顺序构建模型,或者通过继承nn.Module类构建指定模型

    nn.Sequential()

    nn.add_module()
  2. 查看样本个数:

    from torchkeras import summary

    summary(net, input_shape=(15,))



张量数据结构

  1. test.dim(), 标量为0维, 向量为1维,矩阵为2维
  2. shape()和size()可以查看张量的维度和数据
  3. test.view(3,3)可以修改张量的维度分配, test.view(3, -1), -1表示自动推导维度
  4. test.reshape(3,4),类似于view,重新调整维度
  5. 可以用numpy方法从Tensor得到numpy数组,也可以用torch.from_numpy从numpy数组得到Tensor。

    这两种方法关联的Tensor和numpy数组是共享数据内存的。如果改变其中一个,另外一个的值也会发生改变。如果有需要,可以用张量的clone方法拷贝张量,中断这种关联。
#numpy-tensor
np1 = np.zeros(3)
tensor = torch.from_numpy(np1)
print(np1, tensor)
np.add(np1, 2., np1)
print(np1, tensor)
#tensor-numpy
tensor = torch.zeros(3)
arr = tensor.numpy()
#clone中断关联
tensor = torch.zeros(3)
#使用clone方法拷贝张量, 拷贝后的张量和原始张量内存独立
arr = tensor.clone().numpy() # 也可以使用tensor.data.numpy()
  1. 可以使用item方法从标量张量得到对应的Python数值。使用tolist方法从张量得到对应的Python数值列表。
tensor1 = torch.ones(1)
s = tensor1.item()
print(s, type(s))

tensor2 = torch.ones(3, 3)
l = tensor2.tolist()
print(l, type(l))
  1. 张量的初始化操作
#tsr = torch.tensor([1, 1])
#tsr = torch.FloatTensor([1, 1])
#tsr = torch.ones(3, 3)
#tsr = torch.zeros(3, 3)
#tsr = torch.eye(3, 3) 斜对角线置为1
#tsr = torch.rand([3, 3, 3])
#tsr = torch.randn([3, 3])
#tsr = torch.range(1, 10, step=2) range is removed
#tsr = torch.arange(1, 10, 2)
#tsr = torch.full([3, 3], 10)
#tsr = torch.linspace(1, 10, 6) #将[1,10]等额分成6等份
#tsr = torch.normal(3, 0.1, (3, 3)) # mean, std, size
#tsr2 = torch.zeros(tsr) #基于tsr的shape创建tsr2,并将结果置为0
#tsr = torch.randperm(20) #整数随机排列
tsr = torch.diag(torch.tensor([1,2,3]) #对角排列,其他位置为0
  1. 广播机制

    (1)如果张量的维度不同,将维度较小的张量进行扩展,直到两个张量的维度都一样。

    (2)如果两个张量在某个维度上的长度是相同的,或者其中一个张量在该维度上的长度为1,那么我们就说这两个张量在该维度上是相容的。

    (3)如果两个张量在所有维度上都是相容的,它们就能使用广播。

    (4)广播之后,每个维度的长度将取两个张量在该维度长度的较大值。

    (5)在任何一个维度上,如果一个张量的长度为1,另一个张量长度大于1,那么在该维度上,就好像是对第一个张量进行了复制。

    (6)torch.broadcast_tensors可以将多个张量根据广播规则转换成相同的维度。
a = torch.tensor([1,2,3])
b = torch.tensor([[1,2,3], [4,5,6], [7,8,9]])
a, b= torch.broadcast_tensors(a, b)
print(a, b)



自动微分机制



一、backward

  1. 标量求导
x = torch.tensor(1., requires_grad=True)
a = torch.tensor(2.)
b = torch.tensor(3.)
c = torch.tensor(-1.)
y = a*x**2 + b*x + c

y.backward()
grad = x.grad
print(grad)
  1. 非标量求导时,需要提供一个单位gradient进行反向传播
x = torch.tensor([[1.0, 1.0], [2.0, 2.0]], requires_grad=True)
a = torch.tensor(2.)
b = torch.tensor(3.)
c = torch.tensor(-1.)
y = a*x**2 + b*x + c
gradient = torch.Tensor([[1., 1.], [1., 1.]])

y.backward(gradient = gradient)
grad = x.grad
print(grad)
  1. 非标量的反向传播的另一种方式:
x = torch.tensor([[1.0, 1.0], [2.0, 2.0]], requires_grad=True)
a = torch.tensor(2.)
b = torch.tensor(3.)
c = torch.tensor(-1.)
y = a*x**2 + b*x + c
gradient = torch.Tensor([[1., 1.], [1., 1.]])
z = torch.sum(y*gradient) #把y转换成标量

z.backward()
grad = x.grad
print(grad)



autograd.grad

x = torch.tensor(1.0, requires_grad=True)
a = torch.tensor(2.)
b = torch.tensor(3.)
c = torch.tensor(-1.)
y = a*x**2 + b*x + c

dy_dx = torch.autograd.grad(y, x, create_graph=True)[0]
print(dy_dx.data)



自动微分和优化器获取最小值

x = torch.tensor(0., requires_grad=True)
a = torch.tensor(1.)
b = torch.tensor(-2.)
c = torch.tensor(1.)
optimizer = torch.optim.SGD(params=[x], lr=0.1)
def f(x):
    result = a*torch.pow(x,2) + b*x + c
    return(result)
for _ in range(1, 50):
    optimizer.zero_grad()
    y = f(x)
    y.backward()
    optimizer.step()
print(y.data, x.data)



nn.functional 和 nn.Module



使用方式:

  1. nn.functional 引入方式:from torch.nn import functional as F
  2. nn.Module一般用来继承,创建我们自己的模块类
  3. 常用函数
  1. (激活函数) * nn.ReLU * nn.Sigmoid * nn.Tanh * nn.Softmax
  2. (模型层) * nn.Linear * nn.Conv2d * nn.MaxPool2d * nn.Dropout2d * nn.Embedding
  3. (损失函数) * nn.BCELoss * nn.MSELoss * nn.CrossEntropyLoss

    注: 一般类似nn.ReLU()函数在创建网络结构时使用,F.relu()在forward时使用;
  1. 需要被优化器优化时,需要添加require_grad=True的参数
  2. nn.Parameter()用来管理参数,默认是梯度优化(require_grad=True), nn.Parameterlist()用来管理多个parameter, nn.ParameterDict 可以将多个nn.Parameter组成一个字典。 实践当中,一般通过继承nn.Module来构建模块类,并将所有含有需要学习的参数的部分放在构造函数中。
	ten1 = nn.Parameter(torch.randn(3, 3))
	ten2 = nn.Parameter(torch.rand(3, 3))
    print(ten1, ten2)
    #列表
    tenList = nn.ParameterList([ten1, ten2])
    print(tenList)
    #字典
    tenDic = nn.ParameterDict({"a":ten1, "b":ten2})
    print(tenDic, tenDic["a"])
  1. 利用nn.Module管理子模块,主要是children哈named_children, 后者返回网络的同时,还会返回每一层的名字:
class test(nn.Module):
    def __init__(self):
        super(test, self).__init__()
        self.conv = nn.Sequential()
        self.conv.add_module("layer 1", nn.Conv2d(1,64,kernel_size=3,stride=1))
        self.conv.add_module("layer 2", nn.ReLU())
        self.conv.add_module("layer 3", nn.Linear(64, 8))

    def forward(self):
        pass
te = test()
for child in te.children():
    print(child)
for child in te.named_children():
    print(child)



张量的操作

  1. 拼接 torch.cat(size, dims=0/1/2), torch.stack(size, dims=0/1/2)–>在指定的维度dims插入一个新的维度,一般为2
  2. 拆分 test.split(len/[2,1], dims), 将指定维度根据len拆分; test.chunk(num, dims), 按照num个数量拆分
  3. 矩阵相乘:相同位置上相乘直接*, 另一种相乘方式为torch.matmul或者@(相同行和列乘积累加)
  4. .pow函数平方函数,.sqrt函数开方
  5. .e[size], .log()取e为底的结果
  6. 梯度裁剪, grad.clamp(min)设定最小值小于10, grad.clamp(min, max)设定取值范围
  7. 范数 a.norm(1/2/3, dims=1), 对应位置数据累加,1/2/3表示开跟的次数,dims表示把哪一个维度消掉
  8. 张量累乘, prod函数,mean平均数,sum, min, max, argmax():返回最大值的索引, argmin()返回最小值的索引
  9. topk(3,dims,largest=True),返回dims维度上最大的前三个,当largest设置为False时返回维度最小的前三个

    kthvalue(8, dims=1)默认返回dims维度上第8小的值
  10. where:根据已有的条件筛选并赋值
cond = torch.tensor([[0.51, 0.6], [0.45, 0.90]])
a = torch.zeros([2, 2])
b = torch.full([2, 2], 1.)
print(a, b, cond)
c = torch.where(cond>0.5, a, b)
print(c)
  1. gather



梯度

  1. 动量法:逃离最小值的方法,类似于物理上的惯性

  2. softmax,

    分类常用的方法,压缩成概率的形式,大的之间差距会更大,小的之间差距会更小。通过Softmax函数就可以将多分类的输出值转换为范围在[0, 1]和为1的概率分布。
  3. 激活函数 sigmod 1/1+e-x, 压缩到[0,1]; tanh压缩到[-1,1]; ReLU为max(x,0),0处不可导
  4. MSE均方误差,CE,

    交叉熵

    (熵越高,越没有惊喜度;越小表示某个值越突出,这个突出的值就是我们希望得到的值, 收敛速度比MSE快)
  5. 反向传播:backward函数,利用链式法则,计算所有的偏微分和梯度,调整权重和偏置,调用优化器的步进是的loss更接近目标函数



矢量化加速

  1. 调用线性代数库完成张量的运算,而不是使用for循环这样的形式



正态分布/高斯分布

  1. normal distribution, 均值为mean,标准差为std
  2. 改变均值时图像会沿着X轴移动,增大、降低标准差会增大、降低峰值



Batch Normalization

  1. 批量规范化层, x = x-mean/方差,估计值x = rx + b, 使得所有数据逼近于0,1的正太分布
  2. 好处:收敛速度更快;更稳定,不会出现增大学习率就无法收敛的情况



数据管道

  1. 工具类: Dataset和DataLoader
  2. Dataset:类似列表的数据结构,有确定的长度,能够利用索引获取其中的数据;



模型层

  1. nn.Module内置了很多模型层,包括:
nn.Linear, nn.Flatten, nn.Dropout, nn.BatchNorm2d
nn.Conv2d,nn.AvgPool2d,nn.Conv1d,nn.ConvTranspose2d
nn.Embedding,nn.GRU,nn.LSTM
nn.Transformer


基础层

nn.Linear:全连接层。参数个数 = 输入层特征数× 输出层特征数(weight)+ 输出层特征数(bias)

nn.Flatten:压平层,用于将多维张量样本压成一维张量样本。

nn.BatchNorm1d:一维批标准化层。通过线性变换将输入批次缩放平移到稳定的均值和标准差。可以增强模型对输入不同分布的适应性,加快模型训练速度,有轻微正则化效果。一般在激活函数之前使用。可以用afine参数设置该层是否含有可以训练的参数。

nn.BatchNorm2d:二维批标准化层。

nn.BatchNorm3d:三维批标准化层。

nn.Dropout:一维随机丢弃层。一种正则化手段。

nn.Dropout2d:二维随机丢弃层。

nn.Dropout3d:三维随机丢弃层。

nn.Threshold:限幅层。当输入大于或小于阈值范围时,截断之。

nn.ConstantPad2d: 二维常数填充层。对二维张量样本填充常数扩展长度。

nn.ReplicationPad1d: 一维复制填充层。对一维张量样本通过复制边缘值填充扩展长度。

nn.ZeroPad2d:二维零值填充层。对二维张量样本在边缘填充0值.

nn.GroupNorm:组归一化。一种替代批归一化的方法,将通道分成若干组进行归一。不受batch大小限制,据称性能和效果都优于BatchNorm。

nn.LayerNorm:层归一化。较少使用。

nn.InstanceNorm2d: 样本归一化。较少使用。



卷积网络相关层

nn.Conv1d:普通一维卷积,常用于

文本

。参数个数 = 输入通道数×卷积核尺寸(如3)×卷积核个数 + 卷积核尺寸(如3)

nn.Conv2d:普通二维卷积,常用于

图像

。参数个数 = 输入通道数×卷积核尺寸(如3乘3)×卷积核个数 + 卷积核尺寸(如3乘3) 通过调整dilation参数大于1,可以变成空洞卷积,增大卷积核感受野。 通过调整groups参数不为1,可以变成分组卷积。分组卷积中不同分组使用相同的卷积核,显著减少参数数量。 当groups参数等于通道数时,相当于tensorflow中的二维深度卷积层tf.keras.layers.DepthwiseConv2D。 利用分组卷积和1乘1卷积的组合操作,可以构造相当于Keras中的二维深度可分离卷积层tf.keras.layers.SeparableConv2D。

nn.Conv3d:普通三维卷积,常用于

视频

。参数个数 = 输入通道数×卷积核尺寸(如3乘3乘3)×卷积核个数 + 卷积核尺寸(如3乘3乘3) 。

nn.MaxPool1d: 一维最大池化。

nn.MaxPool2d:二维最大池化。一种

下采样

方式。没有需要训练的参数。

nn.MaxPool3d:三维最大池化。

nn.

AdaptiveMaxPool2d:二维自适应最大池化

。无论输入图像的尺寸如何变化,

输出的图像尺寸是固定的

。 该函数的实现原理,大概是通过输入图像的尺寸和要得到的输出图像的尺寸来反向推算池化算子的padding,stride等参数。

nn.FractionalMaxPool2d:二维分数最大池化。普通最大池化通常输入尺寸是输出的整数倍。而分数最大池化则可以不必是整数。分数最大池化使用了一些随机采样策略,有一定的正则效果,可以用它来代替普通最大池化和Dropout层。

nn.AvgPool2d:二维平均池化。

nn.AdaptiveAvgPool2d:二维自适应平均池化。无论输入的维度如何变化,

输出的维度是固定的



nn.ConvTranspose2d:二维卷积转置层,俗称反卷积层。并非卷积的逆操作,但在卷积核相同的情况下,当其输入尺寸是卷积操作输出尺寸的情况下,卷积转置的输出尺寸恰好是卷积操作的输入尺寸。在语义分割中可用于上采样。

nn.Upsample:上采样层,操作效果和池化相反。可以通过mode参数控制上采样策略为”nearest”最邻近策略或”linear”线性插值策略。

nn.Unfold:滑动窗口提取层。其参数和卷积操作nn.Conv2d相同。实际上,卷积操作可以等价于nn.Unfold和nn.Linear以及nn.Fold的一个组合。 其中nn.Unfold操作可以从输入中提取各个滑动窗口的数值矩阵,并将其压平成一维。利用nn.Linear将nn.Unfold的输出和卷积核做乘法后,再使用 nn.Fold操作将结果转换成输出图片形状。

nn.Fold:逆滑动窗口提取层。



自定义模型层
  1. 继承nn.Module基类并实现forward方法即可自定义模型层, 使用时如test = Test(), 直接调用test(input)就能够调用到类的forward函数,input作为forward的参数
  2. nnSequential(), nn.add_module(“name”, 基础层集合)或者nn.Sequential(基础层集合), 第二种方式比较直观但不能给每个层命名;



损失函数

  1. 对于回归模型,通常使用的内置损失函数是

    均方损失函数

    nn.MSELoss。
  2. 对于二分类模型,通常使用的是二元交叉熵损失函数nn.BCELoss (输入已经是sigmoid激活函数之后的结果) 或者 nn.BCEWithLogitsLoss (输入尚未经过nn.Sigmoid激活函数) 。
  3. 对于多分类模型,一般推荐使用

    交叉熵

    损失函数 nn.CrossEntropyLoss。 (y_true需要是一维的,是类别编码。y_pred未经过nn.Softmax激活。)
  4. 如果有需要,也可以自定义损失函数,自定义损失函数需要接收两个张量y_pred,y_true作为输入参数,并输出一个标量作为损失函数值。
  5. 内置损失函数

    nn.

    MSELoss

    (均方误差损失,也叫做L2损失,用于回归)

    nn.L1Loss (L1损失,也叫做绝对值误差损失,用于回归)

    nn.SmoothL1Loss (平滑L1损失,当输入在-1到1之间时,平滑为L2损失,用于回归)

    nn.

    BCELoss

    (二元交叉熵,用于二分类,输入已经过nn.Sigmoid激活,对不平衡数据集可以用weigths参数调整类别权重)

    nn.BCEWithLogitsLoss (二元交叉熵,用于二分类,输入未经过nn.Sigmoid激活)

    nn.

    CrossEntropyLoss

    (交叉熵,用于多分类,要求label为稀疏编码,输入未经过nn.Softmax激活,对不平衡数据集可以用weigths参数调整类别权重)

    nn.NLLLoss (负对数似然损失,用于多分类,要求label为稀疏编码,输入经过nn.LogSoftmax激活)

    nn.CosineSimilarity(余弦相似度,可用于多分类)

    nn.AdaptiveLogSoftmaxWithLoss (一种适合非常多类别且类别分布很不均衡的损失函数,会自适应地将多个小类别合成一个cluster)



GPU训练模型

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model.to(device) # 移动模型到cuda
features = features.to(device) # 移动数据到cuda
labels = labels.to(device) # 或者  labels = labels.cuda() if torch.cuda.is_available() else labels



迁移学习

  1. 根据已有的训练结果展开新的训练,可以节省训练时间



版权声明:本文为sooguo原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。