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

文章目录

  • softmax-regression-concise
    • torch.nn.init.normal_
    • net.apply方法
    • torch.tensor & torch.Tensor ?
  • 交叉熵损失函数是什么?手动实现一下
    • 我们尝试实现一下 NLLLoss
    • TORCH.NN.FUNCTIONAL.ONE_HOT


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)

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

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

在我们学习 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. 实现上面的三个 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')

李沐老师 PyTorch版——线性回归 + softmax回归的简洁实现(3)_第1张图片

原始数据为:
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. 我们尝试直接使用 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}')

结果输出如下:

经过CrossEntropyLoss处理后为:
tensor([1.1178, 1.4284, 0.4738])

经过CrossEntropyLoss sum 处理后为:
3.0201

经过CrossEntropyLoss mean 处理后为:
1.0067

我们尝试实现一下 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')

输出的结果如下:

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])

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]])

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

你可能感兴趣的:(动手学深度学习,DeepLearning,pytorch,线性回归)