调库调用太久了
以至于把最基本的给忘了
连交叉熵的数学公式都记不清了,自己写代码验证还验证错误,就是因此公式记错了
所以重新记录一下,交叉熵的求解过程
当然,交叉熵的由来是一个最优化问题,在sigmoid函数相关最优化求解里会有相关的内容,而实际上原理就是求偏导后的,得的最小值等等,这里不概述。
仅给出计算公式以及手写代码和pytorch调库结果对比
1.具体示例
2.计算步骤
- 先得到网络输出,
pred = model(x)
,model
可以用简单的fc
为例 - 对
pred
进行softmax
处理,得到概率P - 根据真实标签
label
,得到yi 以及对应的Pi - 将这些对应的Pi求log,再负号,求和
- 除以batch
下面的代码,实际上debug了很久才写出来的,注释已经很清楚了
2.1 numpy代码手动实现
以一个shape:(3,4)
的矩阵为例,计算
"""
batch=3, nc = 4
"""
import numpy as np
target = np.array([
[-1.0606, 1.5613, 1.2007, -0.2481],
[-1.9652, -0.4367, -0.0645, -0.5104],
[0.1011, -0.5904, 0.0243, 0.1002]
])
label = np.array([0, 2, 1])
def np_softmax(arr):
assert len(arr.shape) == 2
arr_exp = np.exp(arr)
arr_sum = np.sum(arr_exp, axis=1) # (3,4) -->(3,)
arr_sum = arr_sum[:, None] # 增加维度,才可以通过广播,进行矩阵除法。(3, )-->(3, 1)
return arr_exp / arr_sum
def np_onehot(nc, true_label):
"""
param nc: nc代表划分的类别数目
param true_label: 传入的标签 shape :(batch, )
return: 返回一个(batch, nc)形式的one_hot变量
"""
tmp = np.arange(nc)
tmp = tmp[None, :] # 增加行的维度,[0,1,2,3] -->[[0, 1, 2, 3]] 。后续才能广播,每一行都是0,1,2,3
true_label = true_label[:, None] # 增加列的维度,[0, 2, 1] -->[[0],[2],[1]] 。按照列广播
ans = tmp == true_label # 自动广播,返回(batch, nc)形式,为True或False
return ans.astype(int) # bool --> int
# 1.对预测结果softmax处理
target_soft = np_softmax(target)
# 2. 取log对数
target_log = np.log(target_soft)
# 3. one-hot变量
label_one = np_onehot(4, label)
# 4. python矩阵乘法,按照元素相乘; 取负数 , 负对数
res = -target_log * label_one
# 5. 按照列求和, 取均值
loss = np.mean(np.sum(res, axis=1))
print(loss)
2.2 pytorch实现
import torch
import numpy as np
import torch.nn as nn
criterion = nn.CrossEntropyLoss()
target = np.array([
[-1.0606, 1.5613, 1.2007, -0.2481],
[-1.9652, -0.4367, -0.0645, -0.5104],
[0.1011, -0.5904, 0.0243, 0.1002]
])
target = torch.tensor(target)
label = torch.tensor([0, 2, 1])
res = criterion(target, label)
print(res)
2.3 pytorch等价实现
上面使用的是nn.CrossEntropyLoss()
进行实现,但是也可以调用nn.LogSoftmax()
结合nn.NLLLoss()
,即,可以简单认为
nn.CrossEntropyLoss()
等价于nn.LogSoftmax()
加nn.NLLLoss()
import torch
import numpy as np
import torch.nn as nn
target = np.array([
[-1.0606, 1.5613, 1.2007, -0.2481],
[-1.9652, -0.4367, -0.0645, -0.5104],
[0.1011, -0.5904, 0.0243, 0.1002]
])
target = torch.tensor(target)
label = torch.tensor([0, 2, 1])
# 先取softmax,再取log操作
m = nn.LogSoftmax(dim=1)
# The negative log likelihood loss. It is useful to train a classification
# problem with `C` classes
loss = nn.NLLLoss()
tmp = m(target)
output = loss(tmp, label)
print(output)
解释如下:截图参考来源
3.sigmoid做多分类
注意BCELoss 和 BCELosswithLogit是有区别的
其实损失函数的相关计算,都是自己定义的
多分类中,使用sigmoid也是可以的。
即,每个类别的预测都是独立的、互不干扰,概率值的和也不为1
# loss_function = nn.CrossEntropyLoss()
criterion = nn.BCELoss()
for step, data in enumerate(train_bar):
images, labels = data
images = images.to(device)
labels = labels.to(device)
output = net(images.to(device))
# 对labels one-hot处理
labels = nn.functional.one_hot(labels, num_classes=5)
# 对output sigmoid处理
output = torch.sigmoid(output)
loss = criterion(output, labels.float())
optimizer.zero_grad()
loss.backward()
optimizer.step()
4. loss反向传播是本质
loss是一个tensor,为啥它可以直接反向传播呢? 随便一个tensor都可以调用backward()么?
计算图存储了梯度计算公式的
根据链式法则对所有参数进行更新