torch.nn.init.normal_文档
torch.nn.init.normal_(tensor, mean=0.0, std=1.0)
使用取自标准正太分布的数字,初始化我们填入的 tensor 变量.
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 在创建张量时,默认数据类型是 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=−wynlog∑c=1Cexp(xn,c)exp(xn,yn)⋅1 , y n ≠ ignore_index y_{n} \neq \text { ignore\_index } yn= ignore_index ,这里还是弄不太懂,不过我们已经可以尝试按照交叉熵的公式进行模拟实现 CrossEntropyLoss 。
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])
# 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')
原始数据为:
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])
'''直接使用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 的实现,我在网上看到一个公式,公式内容比上面链接到的知乎博主文章所述更加形象的,如下所示:
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])
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]])
(学点知识可真不容易,李沐老师的代码才看到了定义损失函数,学习到的知识就已经一大堆了)