pytorch搭建CNN速记

pytorch使用CNN总结

安装pytorch

如果cuda版本不对,是无法使用gpu的。
请查看官网,获得本系统与cuda版本一致的命令后进行安装,如下图:
在这里插入图片描述
判断cuda是否可用

torch.cuda.is_available()

判断pytorch所需cuda版本

torch.version.cuda

判断实际运行时使用的 cuda 目录(把cuda移动到这个目录)

   >>> import torch
   >>> import torch.utils
   >>> import torch.utils.cpp_extension
   >>> torch.utils.cpp_extension.CUDA_HOME

更多pytorch关于cuda的部分请查阅嗨呀库,点我


如何定义数据

  • 数据的输入也是采用tensor的形式

定义常量

  • 将数据转换为tensor的方式和numpy转为数组的方式很像

    import torch
    
    #将list转为tensor
    torch.FloatTensor(list)
    
    #将tensor转换为numpy的数组
    tensor.numpy()
    
    #将numpy转换为tensor
    Torch.from_numpy(np_data)
    
  • pytorch中的tensor运算和numpy基本一致


定义变量

  • 变量类所处的包

    from torch.autograd import Varivale
    
  • 定义变量

    #requires_grad表示是否用于反向传播计算,即要不要计算梯度
    Variable(tensor,requires_grad=True)
    
  • 变量用于构建动态计算图

  • 计算变量的梯度

    tensor = torch.FloatTensor([[1,2],[3,4]])
    variable = Variable(tensor, requires_grad=True)
    v_out = torch.mean(variable*variable)
    #反向传播梯度计算
    v_out.backward()
    #获得梯度
    print(variable.grad)
    #获得Varibale中的数据,得到的类型为tensor
    print(variable.data)
    

PyTorch里面的torch.nn.Parameter()

除了上述两种类型外,pytorch中还有Parameter类型,该类型主要用于模型参数的反向传播更新。
训练模型时,我们需要将模型的Parameter类型参数传递到优化器对象中,优化器只对Parameter类型的参数进行更新,具体查看PyTorch里面的torch.nn.Parameter()


如何定义模型

快速搭建

import torch
net=torch.nn.Sequential(
    torch.nn.Linear(1, 10),
    torch.nn.ReLU(),
    torch.nn.Linear(10, 1)
)

个性化搭建

创建一个继承了torch.nn.Module的类,在__init__函数中定义所有的层属性,所有的层属性必须是类的直接属性,如果不是,需要使用setattr进行设置,在forward(x)函数中定义层与层之间的连接关系,其实就是定义前向传播

import torch
import torch.nn.functional as F

class Net(torch.nn.Module):
    def __init__(self, n_feature, n_hidden, n_output):
        super(Net, self).__init__()  
        self.hidden = torch.nn.Linear(n_feature, n_hidden)
        self.out = torch.nn.Linear(n_hidden, n_output) 

    def forward(self, x):
        x = F.relu(self.hidden(x))
        x = self.out(x)
        return x

上面例子中的层都直接定义为了类的直接属性,从而加入到了计算图中,下面这个例子演示了如何不通过类属性将层加入到计算图中

import torch
import torch.nn.functional as F

class Net(torch.nn.Module):
    def __init__(self):
     		fc = nn.Linear(input_size, 10)
     		#等价于self.fc1=fc
     		setattr(self, 'fc1', fc)

如何定义激活函数

  • 激活函数所在包

    torch.nn.functional
    
  • 常用激活函数

    import torch.nn.functional as F
    F.relu(variable)
    F.sigmoid(varibale)
    F.tanh(variable)
    

如何定义优化算法

  • 优化算法所在的包

    torch.optim
    
  • 常用的优化算法

    import torch.optim as F
    #输入参数为模型以及对应的优化算法的参数,例如学习率,动量值等
    F.SGD(model.parameters(), lr=LR)
    F.SGD(model.parameters(), lr=LR, momentum=0.8)
    F.RMSprop(net_RMSprop.parameters(), lr=LR, alpha=0.9)
    F.Adam(net_Adam.parameters(), lr=LR, betas=(0.9, 0.99))
    

如何定义loss函数

  • 损失函数所在的包

    torch.nn.functional
    
  • 常用损失函数

    快,快点我,我等不及了

    和keras把正则化放在层中,以此实现损失函数的正则化不同,pytorch定义了含有正则项的损失函数


如何训练模型

  • 一般流程

    • 定义模型
    • 定义优化算法
    • 定义损失函数
    • 给模型喂数据
    • 计算损失函数的值
    • 清空上一步残余更新参数值
    • 误差反向传播,计算梯度
    • 应用梯度,进行参数更新
  • 代码示例

    # 模型定义省去
    # 定义优化算法
    optimizer = torch.optim.SGD(net.parameters(), lr=0.2)
    # 定义损失函数
    loss_func = torch.nn.MSELoss()
    
    for t in range(100):
    		# 给模型喂数据
        prediction = net(x)
        # 计算损失函数的值
        loss = loss_func(prediction, y)
    		# 清空上一步残余更新参数值
        optimizer.zero_grad()
        # 误差反向传播
        loss.backward()
        # 参数更新
        optimizer.step()
    

如何保存与读取模型


保存

import torch
# 定义模型,省略
net1=.....
# 保存整个模型
torch.save(net1,'net1.pkl')
# 保存网络中的参数
torch.save(net1.state_dict(),'net_params.pkl')

读取

import torch
# 加载模型
net1=torch.load('net1.pkl')

# 定义与net1.pkl相同的模型结构
net2=.....
# 加载模型参数
net2.load_state_dict(torch.load('net_params.pkl'))

使用GPU训练的模型,现在使用cpu进行测试
torch.load('G_38_4.893.pth',map_location='cpu')

如何进行批训练

DataLoader可用于批量迭代数据,常用于批训练,具体步骤如下

  • 将数据转换为tensor
  • 将数据放入DataLoader中
  • 训练数据

DataLoader位于的包

torch.utils.data

代码如下

import torch
import torch.utils.data as Data
torch.manual_seed(1)    # reproducible

BATCH_SIZE = 5      # 批训练的数据个数

x = torch.linspace(1, 10, 10)       # x data (torch tensor)
y = torch.linspace(10, 1, 10)       # y data (torch tensor)

# 先转换成 torch 能识别的 Dataset
torch_dataset = Data.TensorDataset(data_tensor=x, target_tensor=y)

# 把 dataset 放入 DataLoader
loader = Data.DataLoader(
    dataset=torch_dataset,      # torch TensorDataset format
    batch_size=BATCH_SIZE,      # mini batch size
    shuffle=True,               # 要不要打乱数据 (打乱比较好)
    num_workers=2,              # 多线程来读数据
)

for epoch in range(3):   # 训练所有!整套!数据 3 次
    for step, (batch_x, batch_y) in enumerate(loader):  # 每一步 loader 释放一小批数据用来学习
        # 模型训练

        # 打印日志
        print('Epoch: ', epoch, '| Step: ', step, '| batch x: ',
              batch_x.numpy(), '| batch y: ', batch_y.numpy())

如何在单GPU上训练模型

  • 判断GPU是否可以使用

    若torch.cuda.is_available()返回为true,则可以使用GPU

  • 将tensor存储到显存

    ten1 = torch.FloatTensor(2)
    ten1_cuda = ten1.cuda()
    
  • 将Variable存储到显存

    有两种创建方式

    方式一:利用CPU上的张量创建变量,在将变量迁移至显存
    ten1 = torch.FloatTensor(2)
    V1_cpu = autograd.Variable(ten1)
    V1 = V1_cpu.cuda()
    
    方式二:利用GPU上的张量创建变量,此时变量直接位于显存
    ten1_cuda = ten1.cuda()
    V2 = autograd.Variable(ten1_cuda)
    
  • 模型迁移

    # 定义模型,具体过程略
    model=....
    # 转移模型到GPU,此时模型上的所有操作都会在GPU中进行
    model.cuda()
    

pytorch中常用的CNN层

二维卷积 与keras这么多封装好的卷积不同,pytorch只有普通的卷积,有点贫穷……

全连接层

dropout层

池化层

二维BN


pytorch自定义损失函数

Pytorch自定义损失函数的方法有很多,此处只讲解自定义函数方式,以MSE损失函数为例

# 所有的数学操作都必须使用tensor操作完成
def my_MSE_loss(x,y):
	return torch.mean(torch.pow(x-y,2))

以使用自定义的MSE损失函数训练线性回归为例

import torch
import torch.optim as opt
import torch.utils.data as Data

class LinearModel(torch.nn.Module):
    def __init__(self):
        super(LinearModel,self).__init__()
        self.model=torch.nn.Linear(5,1)

    def forward(self,x):
        return self.model(x)

# 自定义损失函数
def my_MSE_loss(x,y):
    return torch.mean(torch.pow(x-y,2))

model=LinearModel()
loss_fuc=torch.nn.MSELoss()
optimizer=opt.SGD(model.parameters(),lr=0.01)

# 定义参数
x=torch.randn((10,5))
y=torch.mm(x,torch.Tensor([[1],[2],[3],[4],[5]]))+5+0.3*torch.rand(10)

# 定义批训练器
dataTensorSet=Data.TensorDataset(x,y)
loader=Data.DataLoader(dataset=dataTensorSet,
                                shuffle=True,
                                batch_size=5)

# 模型训练
for epoch in range(500):
    for step,(batch_x,batch_y) in enumerate(loader):
        predict=model(batch_x)
        #loss=loss_fuc(predict,batch_y)
        #使用自定义的损失函数
        loss=my_MSE_loss(predict,batch_y)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        print("epoch=%s,step=%s,loss=%4f"%(epoch,step,loss.data.numpy()))
        
#打印训练参数
print(model.state_dict())

pytorch对图片进行预处理

在进行训练时,我们通常需要对图片进行某些预处理操作。例如resize,对图片进行数据增强等,pytorch中通过transforms类实现,具体实现如下:

import torchvision.transforms as transforms
import cv2

img_path=""
# 定义一系列的预处理操作,ToTensor表示归一化操作
PicTransform=transforms.Compose([transforms.Resize((32,32)),
                   transforms.ToTensor()])
img = cv2.imread(img_path)
PicTransform(img)

关于transforms定义的更多预处理方法,请查看快,快点我,我等不了了


pytorch批量读取图片

pytorch通过ImageFolder来批量读取图片,并且还可以对图片进行预处理操作,以猫狗识别的CNN为例:

import torch.nn as nn
# 对输入图像进行操作的类,例如图像的resize、图像的归一化等
import torchvision.transforms as transforms
# 批量读取数据的包
from torchvision.datasets import ImageFolder
import torch.utils.data as Data
import torch.optim as opt

root='/Users/菜到怀疑人生/Desktop/数据集/猫狗识别数据集'

class Model(nn.Module):
    def __init__(self):
        super(Model,self).__init__()
        self.model=nn.Sequential(nn.Conv2d(3,6,5),
                    nn.ReLU(),
                    nn.MaxPool2d(2,2),
                    nn.Conv2d(6,16,5),
                    nn.ReLU(),
                    nn.MaxPool2d(2,2),
                    )
        self.classifier=nn.Sequential(nn.Linear(400,800),
                                      nn.ReLU(),
                                      nn.Linear(800,120),
                                      nn.ReLU(),
                                      nn.Linear(120,2))

    def forward(self, x):
        x=self.model(x)
        print(x.shape)
        x=x.view(-1,400)
        x=self.classifier(x)
        return x


# 定义图像预处理器
PicTransform=transforms.Compose([transforms.Resize((32,32)),
                   transforms.ToTensor()])

# 用于批量读取图片,并对图像进行预处理
train_data=ImageFolder(root,PicTransform)
train_loader=Data.DataLoader(dataset=train_data,
                             shuffle=True,
                             batch_size=8)

model=Model()
print(model)

loss_fuc=nn.CrossEntropyLoss()
optimizer=opt.SGD(model.parameters(),lr=0.01)

for epoch in range(10):
    for step,(batch_x,batch_y) in enumerate(train_loader):
        predict=model(batch_x)
        loss=loss_fuc(predict,batch_y)

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        print("epoch=%s,step=%s,loss=%s"%(epoch,step,loss.data.numpy()))

pytorch的反卷积操作ConvTranspose2d

网络上解释清楚ConvTranspose2d概念的文章没见到,而且有些人写错了,估计也是百度搜索引擎的原因,通过阅读文档,现对ConvTranspose2d的使用进行一个总结

函数原型

torch.nn.ConvTranspose2d(in_channels,
						 out_channels, 
						 kernel_size, 
						 stride=1,
					 	 padding=0, 
					 	 output_padding=0, 
					 	 groups=1, 
					 	 bias=True, 
					 	 dilation=1, 
					 	 padding_mode='zeros')

这里对经常使用的参数进行总结

  • in_channels:输入特征图的个数
  • out_channels:输出特征图的个数
  • kernel_size:卷积核大小
  • stride:对输入特征图进行填充

    假设输入特征图为的大小为 H ∗ W H*W HW,stride设置为 S S S,对于宽度 W W W来说,每两列之间插入 W ∗ ( S − 1 ) W*(S-1) W(S1)维的零矩阵,此时特征图的宽变为 W ∗ ( S − 1 ) + W = ( W − 1 ) ∗ S + 1 W*(S-1)+W=(W-1)*S+1 W(S1)+W=(W1)S+1对于高度 H H H,同理。插入完毕后,特征图的大小为 [ ( H − 1 ) ∗ S + 1 ] ∗ [ ( W − 1 ) ∗ S + 1 ] [(H-1)*S+1]*[(W-1)*S+1] [(H1)S+1][(W1)S+1]

  • dilation:

    应该是空洞卷积,其实和stride对于特征图的处理类似,只是dilation对于卷积核进行处理,可以查看Dilated convolution animations进行了解
    如果参数dilation的值为 d d d,卷积核大小为 k ∗ k k*k kk,可将卷积核大小看成 [ ( k − 1 ) ∗ d + 1 ] ∗ [ ( k − 1 ) ∗ d + 1 ] [(k-1)*d+1]*[(k-1)*d+1] [(k1)d+1][(k1)d+1]

  • padding

    pytorch的padding与平常意义的不同,设参数dilation的值为 d d d,卷积核大小为 k ∗ k k*k kk,padding数目为 p p p,在特征图周边padding的数目为 d ∗ ( k − 1 ) − p d*(k-1)-p d(k1)p

  • outputpadding:对输出特征图进行的padding数,设为 O O O

基于上述定义,同时结合卷积后特征图大小的公式,具体查看卷积过后特征图的大小,沿用上述符号,可得pytorch的反卷积操作后特征图大小 H o u t ∗ W o u t H_{out}*W_{out} HoutWout为:

H o u t = ( H − 1 ) ∗ S + 1 + 2 ∗ ( d ∗ ( k − 1 ) − p ) − [ ( k − 1 ) ∗ d + 1 ] + 1 = ( H − 1 ) ∗ S + d ∗ ( k − 1 ) − 2 P + 1 + O W o u t = ( W − 1 ) ∗ S + 1 + 2 ∗ ( d ∗ ( k − 1 ) − p ) − [ ( k − 1 ) ∗ d + 1 ] + 1 = ( W − 1 ) ∗ S + d ∗ ( k − 1 ) − 2 P + 1 + O \begin{aligned} H_{out}=&(H-1)*S+1+2*(d*(k-1)-p)-[(k-1)*d+1]+1\\ =&(H-1)*S+d*(k-1)-2P+1+O\\ W_{out}=&(W-1)*S+1+2*(d*(k-1)-p)-[(k-1)*d+1]+1\\ =&(W-1)*S+d*(k-1)-2P+1+O \end{aligned} Hout==Wout==(H1)S+1+2(d(k1)p)[(k1)d+1]+1(H1)S+d(k1)2P+1+O(W1)S+1+2(d(k1)p)[(k1)d+1]+1(W1)S+d(k1)2P+1+O

其实就是pytorch官网上写的计算公式ConvTranspose2d


模型的简单可视化

https://www.aiuai.cn/aifarm467.html


自动求导机制

pytorch基于计算图,所有的tensor以及tensor操作都会成为计算图中的一个部分。

autograd机制基于计算图,计算图中的结点分为叶结点和非叶结点,均为tensor,非叶结点需要由其他结点通过运算得到,如下图,图源自pytorch的计算图
在这里插入图片描述
a、b、c即叶结点,f、g、y由其他结点通过运算得到,为非叶结点。


backward

tensor类型存在一个函数backward,该函数可以实现自动求导机制,但是pytorch的求导只能基于标量进行,backward的gradient参数指明对tensor的哪个标量进行求导,默认参数为None,即 [ 1 ] [1] [1],默认情况下,调用backward的tensor,shape必须为1,官网的定义如下:
在这里插入图片描述

举个例子,假如有一个计算图的输出 a a a的shape为 [ 3 ] [3] [3],即有三个标量,设为 y 1 = f ( x ) 、 y 2 = f ( x ) 、 y 3 = f ( x ) y_1=f(x)、y_2=f(x)、y_3=f(x) y1=f(x)y2=f(x)y3=f(x),如果我想对这三个标量同时求导,代码如下

b=torch.tensor([1,1,1])
a.backward(b)

上述代码的功能类似于
b [ 0 ] ∂ y 1 ∂ x 、 b [ 1 ] ∂ y 2 ∂ x 、 b [ 2 ] ∂ y 3 ∂ x b[0]\frac{\partial y_1}{\partial x}、b[1]\frac{\partial y_2}{\partial x}、b[2]\frac{\partial y_3}{\partial x} b[0]xy1b[1]xy2b[2]xy3

梯度传播到叶子节点后,会分别累加,类似于
b [ 0 ] ∂ y 1 ∂ x + b [ 1 ] ∂ y 2 ∂ x + b [ 2 ] ∂ y 3 ∂ x b[0]\frac{\partial y_1}{\partial x}+b[1]\frac{\partial y_2}{\partial x}+b[2]\frac{\partial y_3}{\partial x} b[0]xy1+b[1]xy2+b[2]xy3

所以每次调用时,需要依据自己的需求清空计算图中的梯度,对于继承了nn.Module的类而言,可以调用zero_grad()函数清空计算图中的所有梯度。

一次forward对应一次backward,每次调用完backward后,计算图都会被释放,如果需要针对一个计算图多次调用backward,则将backward函数的retain_graph置为true即可,多次调用会出现问题,如下代码所示,代码来源pytorch的计算图

net = nn.Linear(3, 4)
input = Variable(torch.randn(2, 3), requires_grad=True)
output = net(input)
loss = torch.sum(output)

loss.backward()
loss.backward()

RuntimeError: Trying to backward through the graph a second time, but the buffers have already been freed.
net = nn.Linear(3, 4)
input = Variable(torch.randn(2, 3), requires_grad=True)
output = net(input)
loss = torch.sum(output)
loss.backward(retain_graph=True) # 添加retain_graph=True标识,让计算图不被立即释放
loss.backward()

tensor中的梯度

调用完backward后,我想知道每个变量具体的梯度,怎么办?tensor类型中存在grad属性,可以查看该节点的梯度值,只有叶子节点的grad属性会存储梯度,如果想要查看非叶子节点的梯度,就需要register_hook函数了,具体如下:

class FeatureExtractor():
    """ Class for extracting activations and
    registering gradients from targetted intermediate layers """

    def __init__(self, model, target_layers):
        self.model = model
        self.target_layers = target_layers
        self.gradients = []

    def save_gradient(self, grad):
        self.gradients.append(grad)

    def __call__(self, x):
        outputs = []
        self.gradients = []
        # 获得各层信息,name为层的名字,module为层的具体操作
        for name, module in self.model._modules.items():
            # 在这里进行了前向传播
            x = module(x)
            # 如果是目标层
            if name in self.target_layers:
                # 添加一个钩子,这个钩子在该层的梯度被计算时会被执行,此处特指记录该层的梯度
                x.register_hook(self.save_gradient)
                outputs += [x]
        return outputs, x

上述代码是grad-cam实现代码的一部分

可以按下列代码清空某个节点的梯度

# input为一个tensor
input.grad.data.zero_()

如果一个tensor的grad属性为None

  1. 压根没求梯度
  2. 该tensor是计算图中的非叶结点
  3. 该tensor本身不需要求导

针对第三点,我们引出tensor中的相应标志位,与梯度相关的标志位主要有requires_grad,只有requires_grad为true的tensor会被求导,从而有梯度,requires_grad具有传递性,例如

a=torch.tensor([1,2,3],requires_grad=True)
b=torch.tensor([1,2,3],requires_grad=False)
c=torch.sum(a,b,dim=1)

则c的requires_grad默认也为true,这样梯度才能从c传递到a。

与梯度相关的函数主要有detach()函数,假设x为一个tensor,x.detach()会创建一个与x相同,但是requires_grad为false的节点x’,用于替换计算图中x的位置,此时梯度回传到x‘后,便不会在向后回传


PyTorch的上采样

PyTorch学习笔记(10)——上采样和PixelShuffle


Pytorch中的学习率衰减方法

Pytorch中的学习率衰减方法


Pytorch多GPU训练模型

pytorch使用记录(三) 多GPU训练
pytorch多GPU训练以及多线程加载数据

上述两个链接请看完,第二个链接有踩坑记录


pytorch冻结层

pytorch 两种冻结层的方式,两种方式的区别,假设网络前向传播为ABC,我们想冻结B层,第一种方式可以让梯度从B向后传播到A,从而让A可以更新,第二种方式,梯度无法从B向后传播至A,A无法更新。

冻结BN层


pytorch如何对结点的梯度进行操作

register_hook的使用


如何让Pytorch运行速度更快(个人经验)

  • 使用dataloader时,指定多线程读取数据,即num_worker参数
  • 使用多GPU训练模型
  • 对于数据的操作尽可能使用dataloader,通过指定num_worker,往往比自己操作数据快得多
  • 对于python而言,尽可能少的使用for循环,多使用矩阵运算
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页