• 李沐老师 PyTorch版——线性回归 + softmax回归的简洁实现(3)



    softmax-regression-concise


    torch.nn.init.normal_

    torch.nn.init.normal_文档
    torch.nn.init.normal_(tensor, mean=0.0, std=1.0)
    使用取自标准正太分布的数字,初始化我们填入的 tensor 变量.

    net.apply方法

    net.apply官方文档
    net.apply 方法典型用途包括:初始化模型的参数。

    @torch.no_grad()
    def init_weights(m):
        print(m)
        if type(m) == nn.Linear:
            nn.init.normal_(m.weight)
            print(m.weight)
    net = nn.Sequential(nn.Linear(2, 2), nn.Linear(2, 2))
    net.apply(init_weights)
    ---output---
    Linear(in_features=2, out_features=2, bias=True)
    Parameter containing:
    tensor([[ 3.6839, -1.3050],
            [-0.4905, -0.2887]], requires_grad=True)
    Linear(in_features=2, out_features=2, bias=True)
    Parameter containing:
    tensor([[-0.1326, -0.8289],
            [ 0.6380,  0.7813]], requires_grad=True)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    torch.tensor & torch.Tensor ?

    torch.Tensor 在创建张量时,默认数据类型是 32 位 Float 类型的数字,它不能指定数据类型。但是,torch.tensor 会根据原始数据类型生成相应类型的张量。

    >>> import torch
    >>> from torch import nn
    >>> dataTensor = torch.Tensor([1,2,3])
    >>> dataTensor.dtype
    torch.float32 # 默认是float32
    >>> datatensor = torch.tensor([1,2,3])
    >>> datatensor.dtype
    torch.int64
    >>> datatensor = torch.tensor([1.,2,3]) # 注意这里是 1.
    >>> datatensor.dtype
    torch.float32
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    交叉熵损失函数是什么?手动实现一下

    在我们学习 softmax regression 简洁实现的过程中,我们需要传递未规范化的预测 y ^ \hat{y} y^ 进入损失函数 torch.nn.CrossEntropyLoss 中,然而交叉熵损失函数在其中做了什么工作,这是我们需要进行学习的内容。

    首先,对这个损失函数最直观的介绍,在官方文档中说的很清楚,该函数计算 input 和 target 之间的交叉熵损失。然后我们分别介绍一下 input 和 target 应该是什么样的形式作为参数传递。

    对于 input 而言,我们要求传入的是未经标准序列化的输入,也就是我们预测的结果 y ^ \hat{y} y^,由损失函数帮我们进行处理,包括 softmax + log + nll(negative log likelihood loss),如果传入一个 batch 的样本,则 input 的形状应该是 ( m i n i b a t c h , C ) (minibatch,C) (minibatch,C)

    对于 target而言,在官网文档中介绍了两种方式,我这里仅介绍李沐老师课程用到的方法、即 target 是类别的下标。假设我们的分类问题一共有 C 个类别,我们的 target 应该是 [ 0 , C ) [0,C) [0,C) 的取值范围,如果传入的是一个 batch 的样本,batch_number = N,那么我们的 target 应该是一个大小为 N 的向量。

    最后,重点介绍一下 CrossEntropyLoss 的参数 reduction,这个参数有点类似上次文章介绍的 MSELoss,这里再说一下吧。在文档中写着 if redection=‘none’,则 ℓ ( x , y ) = L = { l 1 , … , l N } ⊤ \ell(x, y)=L=\left\{l_{1}, \ldots, l_{N}\right\}^{\top} (x,y)=L={l1,,lN} l n = − w y n log ⁡ exp ⁡ ( x n , y n ) ∑ c = 1 C exp ⁡ ( x n , c ) ⋅ 1 l_{n}=-w_{y_{n}} \log \frac{\exp \left(x_{n, y_{n}}\right)}{\sum_{c=1}^{C} \exp \left(x_{n, c}\right)} \cdot 1 ln=wynlogc=1Cexp(xn,c)exp(xn,yn)1 y n ≠  ignore_index  y_{n} \neq \text { ignore\_index } yn= ignore_index ,这里还是弄不太懂,不过我们已经可以尝试按照交叉熵的公式进行模拟实现 CrossEntropyLoss 。

    1. 我们定义 input,也就是 y ^ \hat{y} y^;target,代表着样本的真实类别下标。在这里我们有三个样本、三个特征。
    import torch
    from torch import nn
    
    '''
    手动实现交叉熵损失函数
    1. softmax 规范化处理
    2. 对数处理
    3. Nllloss处理
    '''
    
    input = torch.randn(3, 3)
    print(f'原始数据为:\n{input}\n')
    # 这里的 target 形式参考上面的讲解
    target = torch.tensor([0, 1, 2])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    1. 实现上面的三个 step,这里用到了 NLLLoss(负对数似然损失),这里链接文章参考,但是该文章对 NLLLoss 的公式没有做过多解释,我这里替博主解释一下这个公式的前提。在 NLLLoss 文档中明确提到,redection=‘mean’ 是参数的默认值。所以博主的公式中取了平均值。这里把文章的 NLLLoss 部分截图一下,感谢博主的文章。
    # step1 softmax处理
    softmax_data = torch.softmax(input,dim=1)
    print(f'softmax处理后的input为:\n{softmax_data}\n')
    # step2 对数处理
    log_softmax_data = torch.log(softmax_data)
    print(f'经过log处理后的softmax_data为:\n{log_softmax_data}\n')
    # step3 Nllloss处理
    loss = torch.nn.NLLLoss(reduction='none')
    output = loss(log_softmax_data, target)
    print(f'经过NLLLoss处理后为:\n{output}\n')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    原始数据为:
    tensor([[-1.2486, -0.9439, -1.6025],
            [ 1.7775,  0.7296, -0.4082],
            [ 0.8146, -0.5394,  1.5450]])
    
    softmax处理后的input为:
    tensor([[0.3270, 0.4435, 0.2295],
            [0.6835, 0.2397, 0.0768],
            [0.2999, 0.0774, 0.6226]])
    
    经过log处理后的softmax_data为:
    tensor([[-1.1178, -0.8131, -1.4717],
            [-0.3805, -1.4284, -2.5662],
            [-1.2042, -2.5583, -0.4738]])
    
    经过NLLLoss处理后为:
    tensor([1.1178, 1.4284, 0.4738])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    1. 我们尝试直接使用 CrossEntropyLoss 进行处理。
    '''直接使用CrossEntropyLoss'''
    loss = torch.nn.CrossEntropyLoss(reduction='none')
    output = loss(input, target)
    print(f'经过CrossEntropyLoss处理后为:\n{output}\n')
    
    loss = torch.nn.CrossEntropyLoss(reduction='sum')
    output = loss(input, target)
    print(f'经过CrossEntropyLoss sum 处理后为:\n{output:.4f}\n')
    
    loss = torch.nn.CrossEntropyLoss(reduction='mean')
    output = loss(input, target)
    print(f'经过CrossEntropyLoss mean 处理后为:\n{output:.4f}')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    结果输出如下:

    经过CrossEntropyLoss处理后为:
    tensor([1.1178, 1.4284, 0.4738])
    
    经过CrossEntropyLoss sum 处理后为:
    3.0201
    
    经过CrossEntropyLoss mean 处理后为:
    1.0067
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    我们尝试实现一下 NLLLoss

    负对数似然估计 NLLLoss 的实现,我在网上看到一个公式,公式内容比上面链接到的知乎博主文章所述更加形象的,如下所示:
    N L L ( log ⁡ ( softmax ⁡ (  input  ) ) ,  target  ) = −  OneHot  ( target ⁡ ) i × log ⁡ ( softmax ⁡ ( input ⁡ ) i ) \mathrm{NLL}(\log (\operatorname{softmax}(\text { input })), \text { target })=- \text { OneHot }(\operatorname{target})_{\mathrm{i}} \times \log \left(\operatorname{softmax}(\operatorname{input})_{\mathrm{i}}\right) NLL(log(softmax( input )), target )= OneHot (target)i×log(softmax(input)i)可以看出,NLLLoss 帮我们对 target 进行了独热编码,我们仅需要传入一个分类标签的下标向量,并且公式这里是对应 reduction=‘none’ 的情况,也就是计算的结果是一个向量。大家可以根据需要将代码进行重写推演,这里我进行了一个自我实现。

    # step3.1 手动实现 Negative log likelihood loss
    # step3.1.1 获取 target one_hot编码
    target_one_hot = nn.functional.one_hot(target)
    # step3.1.2 target_one_hot*log_softmax*(-1)
    mulResult = (target_one_hot*log_softmax_data)
    print(f'one_hot编码点乘以log_softmax结果为:\n{mulResult}\n')
    mulResult = (-1)*mulResult
    print(f'mulResult * -1 结果为:\n{mulResult}\n')
    # step3.1.3 取出每一个样本中不为 0 的数字
    manualNllOutput = mulResult.max(axis=1)[0]
    print(f'手动实现nllloss的结果为:\n{manualNllOutput}\n')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    输出的结果如下:

    one_hot编码点乘以log_softmax结果为:
    tensor([[-1.1178, -0.0000, -0.0000],
            [-0.0000, -1.4284, -0.0000],
            [-0.0000, -0.0000, -0.4738]])
    
    mulResult * -1 结果为:
    tensor([[1.1178, 0.0000, 0.0000],
            [0.0000, 1.4284, 0.0000],
            [0.0000, 0.0000, 0.4738]])
    
    手动实现nllloss的结果为:
    tensor([1.1178, 1.4284, 0.4738])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    TORCH.NN.FUNCTIONAL.ONE_HOT

    ONE_HOT官方文档
    torch.nn.functional.one_hot(tensor, num_classes=- 1) → LongTensor 这里主要传入一个整型的 Target 向量,随后会返回给我们独热编码。

    >>> import torch
    >>> from torch import nn
    >>> target = torch.tensor([0, 1, 3])
    >>> one_hot_target = nn.functional.one_hot(target)
    >>> one_hot_target
    tensor([[1, 0, 0, 0],
            [0, 1, 0, 0],
            [0, 0, 0, 1]])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    (学点知识可真不容易,李沐老师的代码才看到了定义损失函数,学习到的知识就已经一大堆了)

  • 相关阅读:
    QT4.8xml写入(2)
    C语言,关于字节对齐的一些问题
    网络安全(黑客)自学
    vue2 解密图片地址(url)-使用blob文件-打开png格式图片
    在线pdf请你谨慎打开
    春节活动 - 高峰值奖励发放技术方案
    总结最近遇到的几个问题
    WPF列表样式
    vue3+ts项目04-国际化
    MyEclipse数据库使用教程:使用数据库表、外键和索引
  • 原文地址:https://blog.csdn.net/Mr_Yuwen_Yin/article/details/126174583