• 【PyTorch】2-主要组成模块(数据读入、模型构建、损失函数、评价指标、训练和测试、优化器)


    PyTorch:2-主要组成模块

    注:所有资料来源且归属于thorough-pytorch(https://datawhalechina.github.io/thorough-pytorch/),下文仅为学习记录

    2.1:深度学习的必要部分

    机器学习步骤

    【1】数据预处理

    【2】划分train、valid、test

    【3】选择模型,设置loss-function和optimizer

    【4】拟合train,在valid和test上计算模型表现

    深度学习和机器学习的不同

    【1】深度学习所需的样本量很大,一次加载全部数据运行可能会超出内存容量而无法实现

    【2】有批(batch)训练等提高模型表现的策略,需要每次训练读取固定数量的样本送入模型中训练

    【3】需要“逐层”搭建,或者预先定义好可以实现特定功能的模块,再把这些模块组装起来

    【4】损失函数和优化器要能够保证反向传播能够在用户自行定义的模型结构上实现

    2.2:基本配置

    常用依赖包

    import os 
    import numpy as np 
    import torch
    import torch.nn as nn							
    from torch.utils.data import Dataset, DataLoader # 数据读取
    import torch.optim as optimizer					#优化器
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    常用超参数

    • batch size
    • 初始学习率
    • 训练次数(max_epochs)
    • GPU配置
    batch_size = 16		# 批次的大小
    lr = 1e-4			# 优化器的学习率
    max_epochs = 100	# iterations
    
    • 1
    • 2
    • 3

    超参数存储方式

    【1】直接设置在训练的代码里

    【2】使用yamljsondict等文件

    显式调用GPU方法

    【1】使用os.environ

    import os
    os.environ['CUDA_VISIBLE_DEVICES'] = '0,1' 
    # 指明调用的GPU为0,1号
    
    • 1
    • 2
    • 3

    【2】使用device,后续对要使用GPU的变量用.to(device)

    device = torch.device("cuda:1" if torch.cuda.is_available() else "cpu") 
    # 指明调用的GPU为1号
    
    • 1
    • 2

    2.3:数据读入

    通过Dataset + DataLoader的方式完成

    【1】Dataset定义好数据的格式和数据变换形式

    【2】DataLoader用iterative的方式不断读入批次数据

    定义Dataset类

    • __init__:向类中传入外部参数,定义样本集
    • __getitem__:逐个读取样本集合中的元素,可进行一定变换,并返回训练/验证所需的数据
    • __len__:返回数据集的样本数

    【以cifar10数据集为例】自带的ImageFolder类

    import torch
    from torchvision import datasets
    train_data = datasets.ImageFolder(train_path, transform=data_transform)
    val_data = datasets.ImageFolder(val_path, transform=data_transform)
    
    • 1
    • 2
    • 3
    • 4

    其中data_transform可以对图像进行一定的变换,如翻转、裁剪

    【自定义Dataset类】

    第一步:构建MyDataset类

    import os
    import pandas as pd
    from torchvision.io import read_image
    
    class MyDataset(Dataset):
        # 标签文件、图像路径、train图像变换、test图像变换
        def __init__(self, annotations_file, img_dir, transform=None, target_transform=None):
            self.img_labels = pd.read_csv(annotations_file)
            self.img_dir = img_dir
            self.transform = transform
            self.target_transform = target_transform
            
    	# 返回样本数
        def __len__(self):
            return len(self.img_labels)
        	
        # 逐个读取图像
        def __getitem__(self, idx):
            img_path = os.path.join(self.img_dir, self.img_labels.iloc[idx, 0])
            image = read_image(img_path)
            label = self.img_labels.iloc[idx, 1]
            if self.transform:
                image = self.transform(image)
            if self.target_transform:
                label = self.target_transform(label)
            return image, label
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    标签形式:

    image1.jpg, 0
    image2.jpg, 1

    image9.jpg, 9

    第二步:通过DataLoader读取数据

    from torch.utils.data import DataLoader
    
    train_loader = DataLoader(train_data, batch_size=batch_size, num_workers=4, shuffle=True, drop_last=True)
    val_loader = DataLoader(val_data, batch_size=batch_size, num_workers=4, shuffle=False)
    
    • 1
    • 2
    • 3
    • 4
    • batch_size:每个批次读入的样本数
    • num_workers:用于读取数据的进程梳理,Windows下为0,Linux下常见为4、8
    • shuffle:是否将读入的数据打乱,一般在训练集中设置为True,验证集中设置为False
    • drop_last:对于样本最后一部分没有达到批次数的样本,使其不再参与训练

    查看加载数据

    DataLoader的读取可以使用next和iter来完成

    import matplotlib.pyplot as plt
    images, labels = next(iter(val_loader))
    print(images.shape)
    plt.imshow(images[0].transpose(1,2,0))
    plt.show()
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.4:模型构建

    PyTorch中神经网络构造一般基于nn.Module类的模型来完成

    构造

    Module 类的 __init__ 函数和 forward 函数,分别用于创建模型参数和定义前向传播

    案例:MLP

    第一步:构造MLP类

    import torch
    from torch import nn
    
    class MLP(nn.Module):
      # 声明带有模型参数的层,这里声明了两个全连接层
      def __init__(self, **kwargs):
        # 调用MLP父类Block的构造函数来进行必要的初始化
        super(MLP, self).__init__(**kwargs)
        self.hidden = nn.Linear(784, 256)
        self.act = nn.ReLU()
        self.output = nn.Linear(256,10)
        
      # 定义模型的前向计算,即如何根据输入x计算返回所需要的模型输出
      def forward(self, x):
        o = self.act(self.hidden(x))
        return self.output(o)   
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    ⽆须定义反向传播函数,系统通过autograd自动生成反向传播所需的 backward 函数。

    第二步:实例化MLP类

    X = torch.rand(2,784) # 设置一个随机的输入张量
    net = MLP() # 实例化模型
    print(net) # 打印模型
    net(X) # 前向计算
    
    • 1
    • 2
    • 3
    • 4

    该类是一个可供自由组建的部件。

    它的子类既可以是⼀个层(如PyTorch提供的 Linear 类),又可以是一个模型(如这里定义的 MLP 类),或者是模型的⼀个部分。

    常见层

    神经网络常用层:全连接层、卷积层、池化层、循环层

    不含模型参数的层

    案例:MyLayer类

    自定义了一个将输入减掉均值后输出的层,并将层的计算定义在了 forward 函数里

    import torch
    from torch import nn
    
    class MyLayer(nn.Module):
        def __init__(self, **kwargs):
            super(MyLayer, self).__init__(**kwargs)
        def forward(self, x):
            return x - x.mean()  
        
    
    layer = Mylayer() # 实例化
    layer(torch.tensor([1,2,3,4,5],dtype=torch.float))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    含有模型参数的层

    Parameter 类其实是 Tensor 的子类。

    除了直接定义成 Parameter 类外,还可以使用 ParameterListParameterDict 分别定义参数的列表和字典。

    案例:参数列表

    import torch
    from torch import nn
    
    class MyListDense(nn.Module):
        def __init__(self):
            super(MyListDense, self).__init__()
            self.params = nn.ParameterList([nn.Parameter(torch.randn(4, 4)) for i in range(3)])  # 3个4*4
            self.params.append(nn.Parameter(torch.randn(4, 1)))  # 1个4*1
    
        def forward(self, x):
            for i in range(len(self.params)):
                x = torch.mm(x, self.params[i])
            return x
    net = MyListDense()
    print(net)
    
    """
    MyListDense(
      (params): ParameterList(
          (0): Parameter containing: [torch.float32 of size 4x4]
          (1): Parameter containing: [torch.float32 of size 4x4]
          (2): Parameter containing: [torch.float32 of size 4x4]
          (3): Parameter containing: [torch.float32 of size 4x1]
      )
    )
    """
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    案例:参数字典

    import torch
    from torch import nn
    
    class MyDictDense(nn.Module):
        def __init__(self):
            super(MyDictDense, self).__init__()
            self.params = nn.ParameterDict({
                    'linear1': nn.Parameter(torch.randn(4, 4)),
                    'linear2': nn.Parameter(torch.randn(4, 1))
            })
            self.params.update({'linear3': nn.Parameter(torch.randn(4, 2))}) # 新增4*2
    
        def forward(self, x, choice='linear1'):
            return torch.mm(x, self.params[choice])
    
    net = MyDictDense()
    print(net)
    
    """
    MyDictDense(
      (params): ParameterDict(
          (linear1): Parameter containing: [torch.FloatTensor of size 4x4]
          (linear2): Parameter containing: [torch.FloatTensor of size 4x1]
          (linear3): Parameter containing: [torch.FloatTensor of size 4x2]
      )
    )
    """
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    二维卷积层

    卷积层 = 卷积核 + 标量偏差

    不带有填充的卷积层

    import torch
    from torch import nn
    
    # 卷积运算(二维互相关)
    def corr2d(X, K): 
        h, w = K.shape
        X, K = X.float(), K.float()
        Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1))
        for i in range(Y.shape[0]):
            for j in range(Y.shape[1]):
                Y[i, j] = (X[i: i + h, j: j + w] * K).sum()
        return Y
    
    # 二维卷积层
    class Conv2D(nn.Module):
        def __init__(self, kernel_size):
            super(Conv2D, self).__init__()
            self.weight = nn.Parameter(torch.randn(kernel_size))
            self.bias = nn.Parameter(torch.randn(1))
    
        def forward(self, x):
            return corr2d(x, self.weight) + self.bias
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    带有填充的卷积层

    import torch
    from torch import nn
    
    # 注意这里是两侧分别填充1⾏或列,所以在两侧一共填充2⾏或列
    conv2d = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3,padding=1)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    带有步幅的卷积层

    conv2d = nn.Conv2d(1, 1, kernel_size=(3, 5), padding=(0, 1), stride=(3, 4))
    
    • 1

    池化层

    池化层直接计算池化窗口内元素的属性(均值、最大值等),对应平均池化和最大池化

    import torch
    from torch import nn
    
    def pool2d(X, pool_size, mode='max'):
        p_h, p_w = pool_size
        Y = torch.zeros((X.shape[0] - p_h + 1, X.shape[1] - p_w + 1))
        for i in range(Y.shape[0]):
            for j in range(Y.shape[1]):
                if mode == 'max':
                    Y[i, j] = X[i: i + p_h, j: j + p_w].max()
                elif mode == 'avg':
                    Y[i, j] = X[i: i + p_h, j: j + p_w].mean()
        return Y
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    nn包则依赖于autograd包来定义模型并求导。

    一个nn.Module包含各个层和一个forward(input)方法,该方法返回output

    模型示例

    神经网络训练过程

    1. 定义包含一些可学习参数(权重)的神经网络
    2. 在输入数据集上迭代
    3. 通过网络处理输入
    4. 计算 loss (输出和正确答案的距离)
    5. 将梯度反向传播给网络的参数
    6. 更新网络的权重,一般使用一个简单的规则:weight = weight - learning_rate * gradient

    LeNet

    import torch
    import torch.nn as nn
    import torch.nn.functional as F
    
    class Net(nn.Module):
        def __init__(self):
            super(Net, self).__init__()
            # 输入图像channel:1;输出channel:6;5x5卷积核
            self.conv1 = nn.Conv2d(1, 6, 5)
            self.conv2 = nn.Conv2d(6, 16, 5)
            # an affine operation: y = Wx + b
            self.fc1 = nn.Linear(16 * 5 * 5, 120)
            self.fc2 = nn.Linear(120, 84)
            self.fc3 = nn.Linear(84, 10)
    
        def forward(self, x):
            # 2x2 Max pooling
            x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
            # 如果是方阵,则可以只使用一个数字进行定义
            x = F.max_pool2d(F.relu(self.conv2(x)), 2)
            x = x.view(-1, self.num_flat_features(x))
            x = F.relu(self.fc1(x))
            x = F.relu(self.fc2(x))
            x = self.fc3(x)
            return x
    
        def num_flat_features(self, x):
            size = x.size()[1:]  # 除去批处理维度的其他所有维度
            num_features = 1
            for s in size:
                num_features *= s
            return num_features
    
    net = Net()
    print(net)
    
    """
    Net(
      (conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
      (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
      (fc1): Linear(in_features=400, out_features=120, bias=True)
      (fc2): Linear(in_features=120, out_features=84, bias=True)
      (fc3): Linear(in_features=84, out_features=10, bias=True)
    )
    """
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    模型可学习参数

    一个模型的可学习参数可以通过net.parameters()返回

    params = list(net.parameters())
    print(len(params))
    print(params[0].size())  # conv1的权重
    
    """
    10
    torch.Size([6, 1, 5, 5])
    """
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    nn.Conv2d 接受一个4维的张量,即nSamples x nChannels x Height x Width

    如果是一个单独的样本,只需要使用input.unsqueeze(0) 来添加一个“假的”批大小维度。

    模型包含的内容

    • torch.Tensor
    • nn.Module
    • nn.Parameter
    • autograd.Function

    AlexNet

    class AlexNet(nn.Module):
        def __init__(self):
            super(AlexNet, self).__init__()
            self.conv = nn.Sequential(
                # Conv2d: in_channels, out_channels, kernel_size, stride, padding
                nn.Conv2d(1, 96, 11, 4), 
                nn.ReLU(),
                # Pool2d: kernel_size, stride
                nn.MaxPool2d(3, 2), 
                # 减小卷积窗口,使用填充为2来使得输入与输出的高和宽一致,且增大输出通道数
                nn.Conv2d(96, 256, 5, 1, 2),
                nn.ReLU(),
                nn.MaxPool2d(3, 2),
                # 连续3个卷积层,且使用更小的卷积窗口。除了最后的卷积层外,进一步增大了输出通道数。
                # 前两个卷积层后不使用池化层来减小输入的高和宽
                nn.Conv2d(256, 384, 3, 1, 1),
                nn.ReLU(),
                nn.Conv2d(384, 384, 3, 1, 1),
                nn.ReLU(),
                nn.Conv2d(384, 256, 3, 1, 1),
                nn.ReLU(),
                nn.MaxPool2d(3, 2)
            )
             # 这里全连接层的输出个数比LeNet中的大数倍。使用丢弃层来缓解过拟合
            self.fc = nn.Sequential(
                nn.Linear(256*5*5, 4096),
                nn.ReLU(),
                nn.Dropout(0.5),
                nn.Linear(4096, 4096),
                nn.ReLU(),
                nn.Dropout(0.5),
                # 输出层。由于这里使用Fashion-MNIST,所以用类别数为10,而非论文中的1000
                nn.Linear(4096, 10),
            )
    
        def forward(self, img):
            feature = self.conv(img)
            output = self.fc(feature.view(img.shape[0], -1))
            return output
    
    net = AlexNet()
    print(net)
    
    """
    AlexNet(
      (conv): Sequential(
        (0): Conv2d(1, 96, kernel_size=(11, 11), stride=(4, 4))
        (1): ReLU()
        (2): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
        (3): Conv2d(96, 256, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
        (4): ReLU()
        (5): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
        (6): Conv2d(256, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        (7): ReLU()
        (8): Conv2d(384, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        (9): ReLU()
        (10): Conv2d(384, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
        (11): ReLU()
        (12): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
      )
      (fc): Sequential(
        (0): Linear(in_features=6400, out_features=4096, bias=True)
        (1): ReLU()
        (2): Dropout(p=0.5)
        (3): Linear(in_features=4096, out_features=4096, bias=True)
        (4): ReLU()
        (5): Dropout(p=0.5)
        (6): Linear(in_features=4096, out_features=10, bias=True)
      )
    )
    """
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    2.5:模型初始化

    PyTorch 在torch.nn.init中提供了常用的初始化方法。

    官方文档:https://pytorch.org/docs/stable/nn.init.html

    初始化方法

    1 . torch.nn.init.uniform_(tensor, a=0.0, b=1.0)

    2 . torch.nn.init.normal_(tensor, mean=0.0, std=1.0)

    3 . torch.nn.init.constant_(tensor, val)

    4 . torch.nn.init.ones_(tensor)

    5 . torch.nn.init.zeros_(tensor)

    6 . torch.nn.init.eye_(tensor)

    7 . torch.nn.init.dirac_(tensor, groups=1)

    8 . torch.nn.init.xavier_uniform_(tensor, gain=1.0)

    9 . torch.nn.init.xavier_normal_(tensor, gain=1.0)

    10 . torch.nn.init.kaiming_uniform_(tensor, a=0, mode=‘fan__in’, nonlinearity=‘leaky_relu’) _

    11 . torch.nn.init.kaiming_normal_(tensor, a=0, mode=‘fan_in’, nonlinearity=‘leaky_relu’)

    12 . torch.nn.init.orthogonal_(tensor, gain=1)

    13 . torch.nn.init.sparse_(tensor, sparsity, std=0.01)

    14 . torch.nn.init.calculate_gain(nonlinearity, param=None)

    除了calculate_gain,所有函数的后缀都带有下划线,意味着这些函数将会直接原地更改输入张量的值。

    基本使用

    使用isinstance()来进行判断模块类型判断

    import torch
    import torch.nn as nn
    
    conv = nn.Conv2d(1,3,3)
    linear = nn.Linear(10,1)
    
    isinstance(conv,nn.Conv2d) # 判断conv是否是nn.Conv2d类型
    isinstance(linear,nn.Conv2d) # 判断linear是否是nn.Conv2d类型
    
    """
    True
    False
    """
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    查看参数

    # 查看随机初始化的conv参数
    conv.weight.data
    # 查看linear的参数
    linear.weight.data
    
    # 对conv进行kaiming初始化
    torch.nn.init.kaiming_normal_(conv.weight.data)
    conv.weight.data
    # 对linear进行常数初始化
    torch.nn.init.constant_(linear.weight.data,0.3)
    linear.weight.data
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    初始化函数的封装

    initialize_weights()函数初始化参数

    def initialize_weights(model):
    	for m in model.modules():
    		# 判断是否属于Conv2d
    		if isinstance(m, nn.Conv2d):
    			torch.nn.init.zeros_(m.weight.data)
    			# 判断是否有偏置
    			if m.bias is not None:
    				torch.nn.init.constant_(m.bias.data,0.3)
    		elif isinstance(m, nn.Linear):
    			torch.nn.init.normal_(m.weight.data, 0.1)
    			if m.bias is not None:
    				torch.nn.init.zeros_(m.bias.data)
    		elif isinstance(m, nn.BatchNorm2d):
    			m.weight.data.fill_(1) 		 
    			m.bias.data.zeros_()	
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    遍历当前模型的每一层,根据类型设定不同的权值初始化方法。

    案例:MLP初始化

    class MLP(nn.Module):
      def __init__(self, **kwargs):
        super(MLP, self).__init__(**kwargs)
        self.hidden = nn.Conv2d(1,1,3)
        self.act = nn.ReLU()
        self.output = nn.Linear(10,1)
      
      def forward(self, x):
        o = self.act(self.hidden(x))
        return self.output(o)
    
    mlp = MLP()
    print(mlp.hidden.weight.data)
    
    print("-------初始化-------")
    mlp.apply(initialize_weights)
    # 或者initialize_weights(mlp)
    print(mlp.hidden.weight.data)
    
    """
    tensor([[[[ 0.3069, -0.1865,  0.0182],
              [ 0.2475,  0.3330,  0.1352],
              [-0.0247, -0.0786,  0.1278]]]])
    -------初始化-------
    tensor([[[[0., 0., 0.],
              [0., 0., 0.],
              [0., 0., 0.]]]])
    """
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    在初始化时,可以使用其他初始化方法或者将模型初始化为一个很小的值,如0.01,0.1等,如果初始化为0会导致梯度消失。

    2.6:损失函数

    使用时直接定义loss

    m = nn.activationFunction() # 激活函数
    loss = nn.lossFunction()	# 损失函数
    output = loss(m(intput),target) # 输出	
    
    • 1
    • 2
    • 3

    二分类交叉熵

    torch.nn.BCELoss(weight=None, size_average=None, reduce=None, reduction='mean')
    
    • 1

    主要参数

    weight:每个类别的loss设置权值

    size_average:数据为bool,为True时,返回的loss为平均值;为False时,返回的各样本的loss之和。

    reduce:数据类型为bool,为True时,loss的返回是标量。

    在二分类中,label是{0,1}。

    交叉熵

    torch.nn.CrossEntropyLoss(weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='mean')
    
    • 1

    主要参数

    ignore_index:忽略某个类的损失函数。

    (其他和二分类交叉熵一模一样)

    L1

    torch.nn.L1Loss(size_average=None, reduce=None, reduction='mean')
    
    • 1

    功能: 计算输出y和真实标签target之差的绝对值。

    主要参数

    reduction:决定计算模式。

    none:逐个元素计算,返回结果和输入元素相同尺寸。

    sum:所有元素求和,返回标量。

    mean:加权平均,返回标量。

    MSE

    torch.nn.MSELoss(size_average=None, reduce=None, reduction='mean')
    
    • 1

    功能: 计算输出y和真实标签target之差的平方。

    主要参数:同L1

    Smooth L1

    torch.nn.SmoothL1Loss(size_average=None, reduce=None, reduction='mean', beta=1.0)
    
    • 1

    功能: L1的平滑输出,减轻离群点带来的影响

    主要参数:同L1

    泊松分布的负对数似然

    torch.nn.PoissonNLLLoss(log_input=True, full=False, size_average=None, eps=1e-08, reduce=None, reduction='mean')
    
    • 1

    功能: 泊松分布的负对数似然损失函数

    主要参数:

    log_input:输入是否为对数形式,决定计算公式。

    full:计算所有 loss,默认为 False。

    eps:修正项,避免 input 为 0 时,log(input) 为 nan 的情况。

    KL散度

    torch.nn.KLDivLoss(size_average=None, reduce=None, reduction='mean', log_target=False)
    
    • 1

    功能: 计算KL散度,即相对熵。用于连续分布的距离度量,或对离散采用的连续输出空间分布进行回归

    主要参数:

    reduction:计算模式,可为 none/sum/mean/batchmean

    batchmean:batchsize 维度求平均值。

    MarginRanking

    torch.nn.MarginRankingLoss(margin=0.0, size_average=None, reduce=None, reduction='mean')
    
    • 1

    功能: 计算两个向量之间的相似度,用于排序任务或计算两组数据之间的差异。

    主要参数:

    margin:边界值,x1 与 x2 之间的差异值。

    多标签边界

    torch.nn.MultiLabelMarginLoss(size_average=None, reduce=None, reduction='mean')
    
    • 1

    功能: 多标签分类问题

    二分类

    torch.nn.SoftMarginLoss(size_average=None, reduce=None, reduction='mean')
    
    • 1

    功能: 计算二分类的 logistic 损失。

    多分类折页

    torch.nn.MultiMarginLoss(p=1, margin=1.0, weight=None, size_average=None, reduce=None, reduction='mean')
    
    • 1

    功能: 计算多分类的折页损失

    主要参数:

    reduction:计算模式,可为 none/sum/mean。

    p:可选 1 或 2。

    weight:各类别的 loss 设置权值。

    margin:边界值

    三元组

    torch.nn.TripletMarginLoss(margin=1.0, p=2.0, eps=1e-06, swap=False, size_average=None, reduce=None, reduction='mean')
    
    • 1

    功能: 计算三元组损失。

    三元组: 一种数据的存储或者使用格式。<实体1,关系,实体2>。

    eg: < anchor, positive examples , negative examples>

    我们希望去anchor的距离更接近positive examples,而远离negative examples

    主要参数:

    reduction:计算模式,可为 none/sum/mean。

    p:可选 1 或 2。

    margin:边界值

    HingEmbedding

    torch.nn.HingeEmbeddingLoss(margin=1.0, size_average=None, reduce=None, reduction='mean')
    
    • 1

    功能: 对输出的embedding结果做Hing损失计算

    余弦相似度

    torch.nn.CosineEmbeddingLoss(margin=0.0, size_average=None, reduce=None, reduction='mean')
    
    • 1

    功能: 对两个向量做余弦相似度

    主要参数:

    reduction:计算模式,可为 none/sum/mean。

    margin:可取值[-1,1] ,推荐为[0,0.5] 。

    CTC

    torch.nn.CTCLoss(blank=0, reduction='mean', zero_infinity=False)
    
    • 1

    功能: 解决时序类数据的分类

    主要参数:

    reduction:计算模式,可为 none/sum/mean。

    blank:blank label。

    zero_infinity:无穷大的值或梯度值为

    2.7:训练和评估

    设置模型的状态

    如果是训练状态,那么模型的参数应该支持反向传播的修改

    如果是验证/测试状态,则不应该修改模型参数

    model.train()   	# 训练状态
    model.eval()  	 	# 验证/测试状态
    
    • 1
    • 2

    训练步骤

    【1】用for循环读取DataLoader中的全部数据

    【2】将数据放到GPU上用于后续计算

    【3】开始用当前批次数据做训练时,将优化器的梯度置零

    【4】将data送入模型中训练

    【5】根据预先定义的criterion计算损失函数

    【6】将loss反向传播回网络

    【7】使用优化器更新模型参数

    验证/测试与训练的不同点

    • 需要预先设置torch.no_grad,以及将model调至eval模式
    • 不需要将优化器的梯度置零
    • 不需要将loss反向回传到网络
    • 不需要更新optimizer

    一个完整的训练过程

    def train(epoch):
        model.train()
        train_loss = 0	# 训练损失初始化
        for data, label in train_loader:
            data, label = data.cuda(), label.cuda()
            optimizer.zero_grad()
            output = model(data)
            loss = criterion(output, label)
            loss.backward()
            optimizer.step()
            train_loss += loss.item()*data.size(0) # 损失更新
        train_loss = train_loss/len(train_loader.dataset)
    		print('Epoch: {} \tTraining Loss: {:.6f}'.format(epoch, train_loss))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    一个完整的验证过程

    def val(epoch):       
        model.eval()
        val_loss = 0		# 验证损失初始化
        with torch.no_grad(): # 取消梯度记录
            for data, label in val_loader:
                data, label = data.cuda(), label.cuda()
                output = model(data)
                preds = torch.argmax(output, 1)
                loss = criterion(output, label)
                val_loss += loss.item()*data.size(0)
                running_accu += torch.sum(preds == label.data)
        val_loss = val_loss/len(val_loader.dataset)
        print('Epoch: {} \tTraining Loss: {:.6f}'.format(epoch, val_loss))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    对于图像分类任务,可以使用sklearn.metrics中的classification_report函数来计算模型的准确率、召回率、F1值等指标。

    from sklearn.metrics import classification_report
    """
    将下方代码的labels和preds替换为模型预测出来的所有label和preds,
    target_names替换为类别名称
    """
    print(classification_report(labels.cpu(), preds.cpu(), target_names=class_names))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    还可以使用torchevaltorchmetric来对模型进行评估。

    2.8:可视化

    可视化内容

    分类的ROC曲线,卷积网络中的卷积核,训练/验证过程的损失函数曲线…

    2.9:优化器

    优化器:根据网络反向传播的梯度信息来更新网络的参数,以起到降低loss函数计算值,使得模型输出更加接近真实标签。

    提供的优化器

    优化器的库torch.optim

    • torch.optim.SGD
    • torch.optim.ASGD
    • torch.optim.Adadelta
    • torch.optim.Adagrad
    • torch.optim.Adam
    • torch.optim.AdamW
    • torch.optim.Adamax
    • torch.optim.RAdam
    • torch.optim.NAdam
    • torch.optim.SparseAdam
    • torch.optim.LBFGS
    • torch.optim.RMSprop
    • torch.optim.Rprop

    优化算法均继承于Optimizer

    class Optimizer(object):
        def __init__(self, params, defaults):        
            self.defaults = defaults
            self.state = defaultdict(dict)
            self.param_groups = []
    
    • 1
    • 2
    • 3
    • 4
    • 5

    三个属性:

    • defaults:存储优化器的超参数(例如,学习率、动量、dampening、权重衰减、nesterov)
    • tate:参数的缓存
    • param_groups:管理的参数组,是一个list,其中每个元素是一个字典,顺序是params,lr,momentum,dampening,weight_decay,nesterov

    其他方法:

    • zero_grad():清空所管理参数的梯度
    • step():执行一步梯度更新,参数更新
    • add_param_group():添加参数组
    • load_state_dict() :加载状态参数字典,可以用来进行模型的断点续训练,继续上次的参数进行训练
    • state_dict():获取优化器当前状态信息字典

    注意事项

    【1】每个优化器都是一个类,一定要进行实例化才能使用

    【2】optimizer在一个神经网络的epoch中需要实现下面两个步骤:

    1. 梯度置零optimizer.zero_grad()
    2. 梯度更新optimizer.step()

    【3】给网络不同的层赋予不同的优化器参数

    from torch import optim
    from torchvision.models import resnet18
    
    net = resnet18()
    
    optimizer = optim.SGD([
        {'params':net.fc.parameters()},#fc的lr使用默认的1e-5
        {'params':net.layer4[0].conv1.parameters(),'lr':1e-2}],lr=1e-5)
    
    # 可以使用param_groups查看属性
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
  • 相关阅读:
    铁死亡细胞实验相关抑制剂、激动剂
    PMP每日一练 | 考试不迷路-11.11(包含敏捷+多选)
    Nginx
    C++ lower_bound() upper_bound() 函数用法详解(深入了解,一文学会)
    spark读取和保存本机文件
    MYBatis-Plus常用注解@TableName、@TableId、@TableField、@TableLogic
    redis集群相关
    Java后端学习路线经验分享--全网最靠谱
    你的代码会说话吗?(上)
    【开源项目】Branchless UTF-8 Decoder 无分支的 UTF-8 解码器
  • 原文地址:https://blog.csdn.net/m0_65787507/article/details/138046638