注:本文代码参考李沐书籍,这里只做代码解析。
1. 交叉熵定义
交叉熵是信息论中的一个重要概念,主要用于度量两个概率分布间的差异性,要理解交叉熵,需要先了解下面几个概念
- 信息量
香农大佬说过"信息是用来消除随机不确定的东西",就是用信息量来表示一个事件从不确定到确定这种状态的量度单位。信息量的大小和信息发生的概率成反比。假设某一事件发生的概率为P(X),我们将信息量I(X)定义如下
I ( X ) = − log ( P ( X ) ) (1) I(X)=-\log(P(X))\tag1 I(X)=−log(P(X))(1) - 信息熵 H(X)
信息熵表示的信息量的期望的和,我们知道,期望就是概率乘以值;那么可以得到。
H ( X ) = − ∑ i = 1 n P ( x i ) log P ( x i ) (2) H(X)=-\sum_{i=1}^{n}P(x_i)\log P(x_i)\tag2 H(X)=−i=1∑nP(xi)logP(xi)(2)
注:当我们求得的结果为两种情况,即二项式分布时候, n =2 时。那么
H ( X ) = − [ P ( x i ) log P ( x i ) + ( 1 − P ( x i ) ) log ( 1 − P ( x i ) ) ] (3) H(X)=-[P(x_i)\log P(x_i)+(1-P(x_i))\log(1-P(x_i))]\tag3 H(X)=−[P(xi)logP(xi)+(1−P(xi))log(1−P(xi))](3) - 相对熵(KL 散度)
如果对于同一个随机变量X有两个单独概率分布,P(X) 和 Q(X),我们通过 KL散度来描述两个概率分布之间的差异。那为什么要这样做呢?因为我们已经有了概率分布 P(X)的具体参数,我们希望对于未知的概率分布 Q(X) 的进行分析,最好是有一个参数 KL 散度 来描述这两个概率分布的差异,当 KL散度很小时,那么我们就可以说明这两个分布相似。
D K L ( p ∣ ∣ q ) = ∑ i = 1 n p ( x i ) log ( p ( x i ) q ( x i ) ) (4) D_{KL}(p||q)=\sum_{i=1}^np(x_i)\log{(\frac{p(x_i)}{q(x_i)})}\tag 4 DKL(p∣∣q)=i=1∑np(xi)log(q(xi)p(xi))(4)
D K L ( p ∣ ∣ q ) = ∑ i = 1 n p ( x i ) log ( p ( x i ) ) ⏟ p ( x i ) 的 信 息 熵 + − ∑ i = 1 n p ( x i ) log ( q ( x i ) ) ⏟ p ( x i ) 和 q ( x i ) 的 交 叉 熵 (5) D_{KL}(p||q)=\underbrace{\sum_{i=1}^np(x_i)\log{(p(x_i))}}_{p(x_i)的信息熵}+\underbrace{-\sum_{i=1}^np(x_i)\log{(q(x_i))}}_{p(x_i)和q(x_i)的交叉熵}\tag {5} DKL(p∣∣q)=p(xi)的信息熵 i=1∑np(xi)log(p(xi))+p(xi)和q(xi)的交叉熵 −i=1∑np(xi)log(q(xi))(5)
注:这里我们就出现了交叉熵了: - 交叉熵:
− ∑ i = 1 n p ( x i ) log ( q ( x i ) ) (6) -\sum_{i=1}^np(x_i)\log{(q(x_i))}\tag{6} −i=1∑np(xi)log(q(xi))(6)
2.交叉熵损失思考
那为什么交叉熵能够表示两个概率之间的差异呢?为什么它就很方便的表达了。这里我们假设这里的
p
(
x
i
)
p(x_i)
p(xi)为模型中实际的概率标签
y
i
y_i
yi,
q
(
x
i
)
q(x_i)
q(xi)为模型预测的
y
i
^
\hat{y_i}
yi^,假设 类别数n = q代入上式可得:
l
(
y
i
,
y
i
^
)
=
−
∑
j
=
1
q
y
i
log
y
i
^
(7)
l(y_i,\hat{y_i})=-\sum_{j=1}^q y_i\log{\hat{y_i}}\tag{7}
l(yi,yi^)=−j=1∑qyilogyi^(7)
假设模型预测的
y
i
^
\hat{y_i}
yi^是通过 softmax运算得到的,满足如下:
y
j
^
=
s
o
f
t
m
a
x
(
o
j
)
=
e
x
p
(
o
j
)
∑
k
e
x
p
(
o
k
)
(8)
\hat{y_j}=softmax(o_j)=\frac{exp(o_j)}{\sum_kexp(o_k)}\tag{8}
yj^=softmax(oj)=∑kexp(ok)exp(oj)(8)
将 (8) 带入到 (7) 可得:
l
(
y
i
,
y
i
^
)
=
−
∑
j
=
1
q
y
j
log
e
x
p
(
o
j
)
∑
k
e
x
p
(
o
k
)
(9)
l(y_i,\hat{y_i})=-\sum_{j=1}^q y_j\log{\frac{exp(o_j)}{\sum_kexp(o_k)}}\tag{9}
l(yi,yi^)=−j=1∑qyjlog∑kexp(ok)exp(oj)(9)
l
(
y
i
,
y
i
^
)
=
∑
j
=
1
q
y
j
log
∑
k
e
x
p
(
o
k
)
−
∑
j
=
1
q
y
j
o
j
(10)
l(y_i,\hat{y_i})=\sum_{j=1}^q y_j\log\sum_kexp(o_k)-\sum_{j=1}^qy_jo_j\tag{10}
l(yi,yi^)=j=1∑qyjlogk∑exp(ok)−j=1∑qyjoj(10)
注:当我们用 one-hot 独热编码表示
y
i
y_i
yi时,那么除了第 j 项为1,其他均为 0 ,则:
l
(
y
i
,
y
i
^
)
=
log
∑
k
=
1
q
e
x
p
(
o
k
)
−
∑
j
=
1
q
y
j
o
j
(11)
l(y_i,\hat{y_i})=\log\sum_{k=1}^qexp(o_k)-\sum_{j=1}^qy_jo_j\tag{11}
l(yi,yi^)=logk=1∑qexp(ok)−j=1∑qyjoj(11)
对上式求导可得:
∂
l
(
y
i
,
y
i
^
)
∂
o
j
=
e
x
p
(
o
j
)
∑
k
=
1
q
e
x
p
(
o
k
)
−
y
j
(12)
\frac{\partial l(y_i,\hat{y_i})}{\partial o_j}=\frac{exp(o_j)}{\sum_{k=1}^qexp(o_k)}-y_j\tag{12}
∂oj∂l(yi,yi^)=∑k=1qexp(ok)exp(oj)−yj(12)
注意:此时我们发现
y
j
^
=
s
o
f
t
m
a
x
(
o
j
)
=
e
x
p
(
o
j
)
∑
k
e
x
p
(
o
k
)
\hat{y_j}=softmax(o_j)=\frac{exp(o_j)}{\sum_kexp(o_k)}
yj^=softmax(oj)=∑kexp(ok)exp(oj)
∂
l
(
y
i
,
y
i
^
)
∂
o
j
=
y
j
^
−
y
j
(13)
\frac{\partial l(y_i,\hat{y_i})}{\partial o_j}=\hat{y_j}-y_j\tag{13}
∂oj∂l(yi,yi^)=yj^−yj(13)
重点:上式说明了,当我们用交叉熵表示损失函数的时候,我们模型中预测用到 softmax 时候,交叉熵的导数即为预测标签
y
i
^
\hat{y_i}
yi^与真实标签
y
i
y_i
yi的差。这样就非常方便了,我们只需要用交叉熵为损失函数就可以了。
我们可以类比最小二乘法的损失函数 :
l
(
y
i
,
y
i
^
)
=
1
2
(
y
j
^
−
y
j
)
2
(14)
l(y_i,\hat{y_i})=\frac{1}{2}(\hat{y_j}-y_j)^2\tag{14}
l(yi,yi^)=21(yj^−yj)2(14)
∂
l
(
y
i
,
y
i
^
)
∂
o
j
=
y
j
^
−
y
j
(15)
\frac{\partial l(y_i,\hat{y_i})}{\partial o_j}=\hat{y_j}-y_j\tag{15}
∂oj∂l(yi,yi^)=yj^−yj(15)
这样类比后,我们发现,用交叉熵做损失函数也能跟用最小二乘法表示一样实现。
3.交叉熵损失代码
- 代码核心思想:交叉熵采用真实标签的预测概率的负对数似然
举例1 :
假设我们有真实标签 y i y_i yi,用它来表示样本中真实样本的位置
y = torch.tensor([0,2]) # 0,2 表示样本中真实正确标签的位置
我们用 y i ^ \hat{y_i} yi^来表示样本中各个类别的概率分布,并用 y 来告诉程序哪个概率是正确的。
y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])
结合起来如下:
y_hat[[0, 1], y] # 等价于 y_hat[[0, 1], [0,2]]
# 就是 0 对应于0; 1 对应于 2
# 第 0 个 [0.1, 0.3, 0.6] 中选择 第 0 个 ,即 0.1
# 第 1 个 [0.3, 0.2, 0.5] 中选择 第 2 个 ,即 0.5
# 所以以上代码输出结果为:tensor([0.1000, 0.5000])
那么我们就可以将上述得到的值求负对数似然即可
- 交叉熵代码
def cross_entropy(y_hat, y):
return -torch.log(y_hat[range(len(y_hat)), y])
4. softmax 回归的简洁实现
我们这里应用pytorch 框架的相关函数来搭建 softmax 回归,在搭建过程中十分的方便。
4.1 代码
# -*- coding: utf-8 -*-
# @Project: zc
# @Author: zc
# @File name: softmax的简洁实现
# @Create time: 2021/11/22 18:09
# 1.导入相关库
import torch
from torch import nn
from d2l import torch as d2l
import matplotlib.pyplot as plt
# 2.将数据导入并生成train_iter训练集,test_iter 测试集
batch_size = 256 # 设置批量大小
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size) # 将数据加载到迭代器中
# 3. 定义模型
# PyTorch不会隐式地调整输⼊的形状。因此,
# 我们在线性层前定义了展平层(flatten),来调整⽹络输⼊的形状
net = nn.Sequential(
nn.Flatten(), nn.Linear(784, 10)
)
# 4.初始化模型值
def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01)
net.apply(init_weights)
# 5. 定义损失函数
loss = nn.CrossEntropyLoss()
# 6. 定义优化器
trainer = torch.optim.SGD(net.parameters(), lr=0.1)
# 7.开始训练模型
num_epochs = 10 # 训练次数为 10
# 8.开始训练
d2l.train_ch3(net=net, train_iter=train_iter, test_iter=test_iter, loss=loss, num_epochs=num_epochs, updater=trainer)
# 9.预测
d2l.predict_ch3(net,test_iter)
plt.show() # 显示结果
4.2 结果
- 训练
- 预测
5. torch.nn.Softmax代码测试
5.1 说明
softmax的作用是将张量中的值变成非负值,并且总和值为1;
- 官网解释
将Softmax函数应用于一个n维输入张量,对其进行缩放,使n维输出张量的元素位于[0,1]范围内,总和为1 - 公式
S o f t m a x ( x i ) = exp ( x i ) ∑ j exp ( x j ) Softmax(x_i)=\frac{\exp(x_i)}{\sum_j\exp(x_j)} Softmax(xi)=∑jexp(xj)exp(xi) - 函数
torch.nn.Softmax(dim=None)
- dim
表示对指定维度进行Softmax计算
dim=0,是 行与行 之间进行 Softmax 计算;dim=1,是 列与列 之间进行 Softmax 计算; - 代码
import torch
from torch.nn import functional as F
x = torch.Tensor([[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]])
# dim=0 ,先将此x[i]求指数再除以行与行之间的指数值的和
# y_dim_0[0,0] = [exp^(1)]/{[exp^(1)]+[exp^(1)]+[exp^(1)]}=0.333
y_dim_0 = F.softmax(x, dim=0)
# dim=1 ,先将此x[i]求指数再除以列与列之间的指数值的和
# y_dim_1[0,0] = [exp^(1)]/{[exp^(1)]+[exp^(2)]+[exp^(3)]+[exp^(4)]}=0.321
y_dim_1 = F.softmax(x, dim=1)
print(f'x={x}')
print(f'x={x.shape}')
print(f'y_dim_0={y_dim_0}')
print(f'y_dim_0_shape={y_dim_0.shape}')
print(f'y_dim_1={y_dim_1}')
print(f'y_dim_1_shape={y_dim_1.shape}')
- 结果
x=tensor([[1., 2., 3., 4.],
[1., 2., 3., 4.],
[1., 2., 3., 4.]])
x=torch.Size([3, 4])
y_dim_0=tensor([[0.3333, 0.3333, 0.3333, 0.3333],
[0.3333, 0.3333, 0.3333, 0.3333],
[0.3333, 0.3333, 0.3333, 0.3333]])
y_dim_0_shape=torch.Size([3, 4])
y_dim_1=tensor([[0.0321, 0.0871, 0.2369, 0.6439],
[0.0321, 0.0871, 0.2369, 0.6439],
[0.0321, 0.0871, 0.2369, 0.6439]])
y_dim_1_shape=torch.Size([3, 4])
- 小结
程序计算的结果跟分析的一致。