文章目录

  • pytorch使用CNN总结
  • 安装pytorch
  • 如何定义数据
  • 定义常量
  • 定义变量
  • PyTorch里面的torch.nn.Parameter()
  • 如何定义模型
  • 快速搭建
  • 个性化搭建
  • 如何定义激活函数
  • 如何定义优化算法
  • 如何定义loss函数
  • 如何训练模型
  • 如何保存与读取模型
  • 保存
  • 读取
  • 如何进行批训练
  • 如何在单GPU上训练模型
  • pytorch中常用的CNN层
  • pytorch自定义损失函数
  • pytorch对图片进行预处理
  • pytorch批量读取图片
  • pytorch的反卷积操作ConvTranspose2d
  • 模型的简单可视化
  • 自动求导机制
  • backward
  • tensor中的梯度
  • PyTorch的上采样
  • Pytorch中的学习率衰减方法
  • Pytorch多GPU训练模型
  • pytorch冻结层
  • pytorch如何对结点的梯度进行操作
  • 如何让Pytorch运行速度更快(个人经验)


pytorch使用CNN总结

安装pytorch

如果cuda版本不对,是无法使用gpu的。

请查看官网,获得本系统与cuda版本一致的命令后进行安装,如下图:

pytorch自己构建batch pytorch搭建cnn_数据


判断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


如何定义数据

  • 数据的输入也是采用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)


    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:对输入特征图进行填充

    假设输入特征图为的大小为pytorch自己构建batch pytorch搭建cnn_损失函数_02,stride设置为pytorch自己构建batch pytorch搭建cnn_损失函数_03,对于宽度pytorch自己构建batch pytorch搭建cnn_数据_04来说,每两列之间插入pytorch自己构建batch pytorch搭建cnn_损失函数_05维的零矩阵,此时特征图的宽变为pytorch自己构建batch pytorch搭建cnn_pytorch自己构建batch_06对于高度pytorch自己构建batch pytorch搭建cnn_数据_07,同理。插入完毕后,特征图的大小为pytorch自己构建batch pytorch搭建cnn_pytorch自己构建batch_08

    • dilation:

    应该是空洞卷积,其实和stride对于特征图的处理类似,只是dilation对于卷积核进行处理,可以查看Dilated convolution animations进行了解
    如果参数dilation的值为pytorch自己构建batch pytorch搭建cnn_损失函数_09,卷积核大小为pytorch自己构建batch pytorch搭建cnn_pytorch自己构建batch_10,可将卷积核大小看成pytorch自己构建batch pytorch搭建cnn_pytorch自己构建batch_11

    • padding

    pytorch的padding与平常意义的不同,设参数dilation的值为pytorch自己构建batch pytorch搭建cnn_损失函数_09,卷积核大小为pytorch自己构建batch pytorch搭建cnn_pytorch自己构建batch_10,padding数目为pytorch自己构建batch pytorch搭建cnn_损失函数_14,在特征图周边padding的数目为pytorch自己构建batch pytorch搭建cnn_pytorch自己构建batch_15

    • outputpadding:对输出特征图进行的padding数,设为pytorch自己构建batch pytorch搭建cnn_2d_16

    基于上述定义,同时结合卷积后特征图大小的公式,具体查看卷积过后特征图的大小,沿用上述符号,可得pytorch的反卷积操作后特征图大小pytorch自己构建batch pytorch搭建cnn_数据_17为:

    pytorch自己构建batch pytorch搭建cnn_数据_18

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



    模型的简单可视化



    自动求导机制

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

    autograd机制基于计算图,计算图中的结点分为叶结点和非叶结点,均为tensor,非叶结点需要由其他结点通过运算得到,如下图,图源自pytorch的计算图

    pytorch自己构建batch pytorch搭建cnn_损失函数_19


    a、b、c即叶结点,f、g、y由其他结点通过运算得到,为非叶结点。


    backward

    tensor类型存在一个函数backward,该函数可以实现自动求导机制,但是pytorch的求导只能基于标量进行,backward的gradient参数指明对tensor的哪个标量进行求导,默认参数为None,即pytorch自己构建batch pytorch搭建cnn_2d_20,默认情况下,调用backward的tensor,shape必须为1,官网的定义如下:

    pytorch自己构建batch pytorch搭建cnn_数据_21

    举个例子,假如有一个计算图的输出pytorch自己构建batch pytorch搭建cnn_数据_22的shape为pytorch自己构建batch pytorch搭建cnn_数据_23,即有三个标量,设为pytorch自己构建batch pytorch搭建cnn_损失函数_24,如果我想对这三个标量同时求导,代码如下

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

    上述代码的功能类似于
    pytorch自己构建batch pytorch搭建cnn_数据_25

    梯度传播到叶子节点后,会分别累加,类似于
    pytorch自己构建batch pytorch搭建cnn_2d_26

    所以每次调用时,需要依据自己的需求清空计算图中的梯度,对于继承了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循环,多使用矩阵运算