交叉熵的原理
为什么使用交叉熵
当我们使用sigmoid函数作为激活函数,计算损失值时所用到的函数是二次代价函数(真实值减去与测试的平方),调整权值时的快慢与激活函数的导数有关.
当损失值较大的时候,应该调整的快一些,
当损失值较小的时候,可以调整的慢一些.
但是,使用二次代价函数,并不能实现这个功能.
引出交叉熵
因此改变计算损失值的代价函数改为交叉熵代价函数
t表示实际的值
y表示预测值
z
=
ω
x
z = \omega x
z=ωx
E
=
−
(
ln
y
+
(
1
−
t
)
ln
(
1
−
y
)
)
E = -(\ln y+(1-t)\ln (1-y))
E=−(lny+(1−t)ln(1−y))
y
=
f
(
z
)
y = f(z)
y=f(z)
对于sigmoid函数
f
(
z
)
′
=
f
(
z
)
(
1
−
f
(
z
)
)
f(z)' = f(z)(1-f(z))
f(z)′=f(z)(1−f(z))
对E求对应的导数:
∂
E
∂
ω
=
−
(
y
f
(
z
)
−
1
−
t
1
−
f
(
z
)
)
∂
f
∂
ω
=
x
(
f
(
z
)
−
t
)
\frac{\partial E}{\partial \omega}=-(\frac{\mathrm y}{\mathrm f(z)}-\frac{1-t}{1-f(z)})\frac{\partial f}{\partial \omega}=x(f(z)-t)
∂ω∂E=−(f(z)y−1−f(z)1−t)∂ω∂f=x(f(z)−t)
通过观察上述求导式可知,权值改变的大小与
(
f
(
z
)
−
t
)
(f(z)-t)
(f(z)−t)即
(
y
−
t
)
(y-t)
(y−t)有关,
因此当误差较大时,权值改变的速度会变快,误差较小时,权值更改速度变慢.
交叉熵的实际使用
代码如下:
import numpy as np
from torch import nn, optim
from torch.autograd import Variable
import torch
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
# 载入数据集
# 训练集
train_data = datasets.MNIST(root='./',
train=True, # 表示载入训练集数据
transform=transforms.ToTensor(), # 将数据转换为Tensor的格式
download=True) # 将数据进行下载
test_data = datasets.MNIST(root='./',
train=False,
transform=transforms.ToTensor(),
download=True)
# 设置批次大小,每次训练传入的数据量
batch_size = 64
# 定义训练集的数据生成器
train_loader = DataLoader(dataset=train_data,
batch_size=batch_size, # 每次生成64个数据
shuffle=True) # 生成之前将数据打乱
# 定义测试集的数据生成器
test_loader = DataLoader(dataset=test_data,
batch_size=batch_size,
shuffle=True)
# 遍历
for i, data in enumerate(train_loader):
inputs, labels = data # 数据带有输入和标签两个量
print(inputs.shape) # torch.Size([64, 1, 28, 28]),1表示通道数,彩色图片表示3,后面是图片大小
print(labels.shape) # 对应的是数字
break
print(len(train_loader)) # 一共938个批次
# 定义网络结构
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.fcl = nn.Linear(784, 10)
self.softmax = nn.Softmax(dim=1) # dim=1表示转换维度为1,{64,10},将输出值转换为概率,就是将输出10个数据转换为对应的概率
def forward(self,x):
#数据输入是4维数据,[64, 1, 28, 28]->[64,784]
x = x.view(x.size()[0],-1)#获得值64,view相当于reshape,第一个值为64,第二个值为-1相当于自动匹配
x = self.fcl(x)
x = self.softmax(x)
return x
#d定义模型:
LR=0.5
model = Net()
#定义代价函数
ce_loss = nn.CrossEntropyLoss()
#定义优化器
optimizer = optim.SGD(model.parameters(),LR)
#定义训练模型
def train():
for i,data in enumerate(train_loader):
inputs,labels = data#获得一个批次的数据和标签
#模型预测结果
out = model(inputs)#(64,10)
#交叉熵代价函数out(batch,C),labels(batch)
#C表示类别的数量,就是10
#使用交叉熵之后不需要用到独热编码,已经封装好了
# #to onehot数据标签变成独热编码,(64)->(64,1)
# labels = labels.reshape(-1,1)
# #1表示对哪个维度进行独热编码
# #labels:将zeros中对应位置变成1
# #1对应位置需要改变的数
# one_hot = torch.zeros(inputs.shape[0],10).scatter(1,labels,1)#一行表示一个解
#计算loss,mse_loss的两个shape要相同
loss = ce_loss(out,labels)
#梯度清零
optimizer.zero_grad()
#计算梯度
loss.backward()
#修改权值
optimizer.step()
def test():
correct = 0
for i, data in enumerate(test_loader):
inputs,labels = data
out = model(inputs)
#获得最大值以及最大值所在位置
_,predicted = torch.max(out,1)#1表示维度为1的位置->10
#用计算值与labels进行类比
correct +=(predicted==labels).sum()
print("Test acc:{0}".format(correct.item()/len(test_data)))
for epoch in range(10):
print("epoch:",epoch)
train()
test()
下图是使用mse代价函数计算损失值,前几次迭代的准确率:
下图是使用交叉熵代价函数计算损失值,前几次迭代的准确率:
通过对比可知,使用交叉熵代价函数,能够使结果收敛速度更快.