softmax和分类模型
内容包含:
softmax回归的基本概念
使用pytorch重新实现softmax回归模型
1.1 softmax的基本概念
1.1.1 分类问题
一个简单的图像分类问题,输入图像的高和宽均为2像素,色彩为灰度。
图像中的4像素分别记为x1,x2,x3,x4x1,x2,x3,x4。
假设真实标签为狗、猫或者鸡,这些标签对应的离散值为y1,y2,y3y1,y2,y3。
我们通常使用离散的数值来表示类别,例如y1=1,y2=2,y3=3y1=1,y2=2,y3=3。
1.1.2 权重矢量
o1=x1w11+x2w21+x3w31+x4w41+b1o1=x1w11+x2w21+x3w31+x4w41+b1
o2=x1w12+x2w22+x3w32+x4w42+b2o2=x1w12+x2w22+x3w32+x4w42+b2
o3=x1w13+x2w23+x3w33+x4w43+b3o3=x1w13+x2w23+x3w33+x4w43+b3
1.1.3 神经网络图
下图用神经网络图描绘了上面的计算。softmax回归同线性回归一样,也是一个单层神经网络。由于每个输出o1,o2,o3o1,o2,o3的计算都要依赖于所有的输入x1,x2,x3,x4x1,x2,x3,x4,softmax回归的输出层也是一个全连接层。
softmax回归是一个单层神经网络softmax回归是一个单层神经网络
既然分类问题需要得到离散的预测输出,一个简单的办法是将输出值oioi当作预测类别是ii的置信度,并将值最大的输出所对应的类作为预测输出,即输出 argmaxioiargmaxioi。例如,如果o1,o2,o3o1,o2,o3分别为0.1,10,0.10.1,10,0.1,由于o2o2最大,那么预测类别为2,其代表猫。
1.1.4 输出问题
直接使用输出层的输出有两个问题:
一方面,由于输出层的输出值的范围不确定,我们难以直观上判断这些值的意义。例如,刚才举的例子中的输出值10表示“很置信”图像类别为猫,因为该输出值是其他两类的输出值的100倍。但如果o1=o3=103o1=o3=103,那么输出值10却又表示图像类别为猫的概率很低。
另一方面,由于真实标签是离散值,这些离散值与不确定范围的输出值之间的误差难以衡量。
softmax运算符(softmax operator)解决了以上两个问题。它通过下式将输出值变换成值为正且和为1的概率分布:
y^1,y^2,y^3=softmax(o1,o2,o3)y^1,y^2,y^3=softmax(o1,o2,o3)
其中
y^1=exp(o1)∑3i=1exp(oi),y^2=exp(o2)∑3i=1exp(oi),y^3=exp(o3)∑3i=1exp(oi).y^1=exp(o1)∑i=13exp(oi),y^2=exp(o2)∑i=13exp(oi),y^3=exp(o3)∑i=13exp(oi).
容易看出y^1+y^2+y^3=1y^1+y^2+y^3=1且0≤y^1,y^2,y^3≤10≤y^1,y^2,y^3≤1,因此y^1,y^2,y^3y^1,y^2,y^3是一个合法的概率分布。这时候,如果y^2=0.8y^2=0.8,不管y^1y^1和y^3y^3的值是多少,我们都知道图像类别为猫的概率是80%。此外,我们注意到
argmaxioi=argmaxiy^iargmaxioi=argmaxiy^i
因此softmax运算不改变预测类别输出。
1.1.5 交叉熵损失函数
对于样本ii,我们构造向量y(i)∈Rqy(i)∈Rq ,使其第y(i)y(i)(样本ii类别的离散数值)个元素为1,其余为0。这样我们的训练目标可以设为使预测概率分布y^(i)y^(i)尽可能接近真实的标签概率分布y(i)y(i)。
平方损失估计
Loss=|y^(i)−y(i)|2/2Loss=|y^(i)−y(i)|2/2
然而,想要预测分类结果正确,我们其实并不需要预测概率完全等于标签概率。例如,在图像分类的例子里,如果y(i)=3y(i)=3,那么我们只需要y^(i)3y^3(i)比其他两个预测值y^(i)1y^1(i)和y^(i)2y^2(i)大就行了。即使y^(i)3y^3(i)值为0.6,不管其他两个预测值为多少,类别预测均正确。而平方损失则过于严格,例如y^(i)1=y^(i)2=0.2y^1(i)=y^2(i)=0.2比y^(i)1=0,y^(i)2=0.4y^1(i)=0,y^2(i)=0.4的损失要小很多,虽然两者都有同样正确的分类预测结果。
改善上述问题的一个方法是使用更适合衡量两个概率分布差异的测量函数。其中,交叉熵(cross entropy)是一个常用的衡量方法:
H(y(i),y^(i))=−∑j=1qy(i)jlogy^(i)j,H(y(i),y^(i))=−∑j=1qyj(i)logy^j(i),
其中带下标的y(i)jyj(i)是向量y(i)y(i)中非0即1的元素,需要注意将它与样本ii类别的离散数值,即不带下标的y(i)y(i)区分。在上式中,我们知道向量y(i)y(i)中只有第y(i)y(i)个元素y(i)y(i)y(i)y(i)为1,其余全为0,于是H(y(i),y^(i))=−logy^y(i)(i)H(y(i),y^(i))=−logy^y(i)(i)。也就是说,交叉熵只关心对正确类别的预测概率,因为只要其值足够大,就可以确保分类结果正确。当然,遇到一个样本有多个标签时,例如图像里含有不止一个物体时,我们并不能做这一步简化。但即便对于这种情况,交叉熵同样只关心对图像中出现的物体类别的预测概率。
假设训练数据集的样本数为nn,交叉熵损失函数定义为
ℓ(Θ)=1n∑i=1nH(y(i),y^(i)),ℓ(Θ)=1n∑i=1nH(y(i),y^(i)),
其中ΘΘ代表模型参数。同样地,如果每个样本只有一个标签,那么交叉熵损失可以简写成ℓ(Θ)=−(1/n)∑ni=1logy^(i)y(i)ℓ(Θ)=−(1/n)∑i=1nlogy^y(i)(i)。从另一个角度来看,我们知道最小化ℓ(Θ)ℓ(Θ)等价于最大化exp(−nℓ(Θ))=∏ni=1y^(i)y(i)exp(−nℓ(Θ))=∏i=1ny^y(i)(i),即最小化交叉熵损失函数等价于最大化训练数据集所有标签类别的联合预测概率。
1.1.6 模型训练和预测
在训练好softmax回归模型后,给定任一样本特征,就可以预测每个输出类别的概率。通常,我们把预测概率最大的类别作为输出类别。如果它与真实类别(标签)一致,说明这次预测是正确的。在实验中,我们将使用准确率(accuracy)来评价模型的表现。它等于正确预测数量与总预测数量之比。
2. 1 使用pytorch重新实现softmax回归模型
代码1
#softmax 回归的简单实现
import torch
import sys
import numpyas np
from torchimport nn
from torch.nnimport init
import d2lzh_pytorchas d2l
from collectionsimport OrderedDict
if __name__ =='__main__':
batch_size =256
train_iter,test_iter = load_data_fashion_mnist(batch_size)
num_inputs =28*28
num_outputs =10
#构建网络
net = nn.Sequential(
OrderedDict([
("flatten",d2l.FlattenLayer()),
("linear",nn.Linear(num_inputs,num_outputs))
]))
# print(net)
#均值为0,标准差为0.01的正太分布随机初始化模型的权重参数
init.normal_(net.linear.weight,mean=0,std=0.01)
init.constant_(net.linear.bias,val=0)
#定义损失函数以及梯度优化算法
loss = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(net.parameters(),lr=0.1)
num_epochs =5
train_ch3(net,train_iter,test_iter,loss,num_epochs,batch_size,None,None,optimizer)
def load_data_fashion_mnist(batch_size):
mnist_train = torchvision.datasets.FashionMNIST(root="./Dataset/FashionMnist",train=True,download=True,
transform=transfroms.ToTensor())
mnist_test = torchvision.datasets.FashionMNIST(root="./Dataset/FashionMnist",train=False,download=True,
transform=transfroms.ToTensor())
# 读取小批量数据,使用多进程来加速数据读取
if sys.platform.startswith('Win'):
num_workers =0 # 0表示不需要额外的进程来加速读取数据
else:
num_workers =2 # 四个进程加速 超过3会出现页面文件太小,无法操作
train_iter = Data.DataLoader(mnist_train,batch_size=batch_size,shuffle=True,num_workers=num_workers)
test_iter = Data.DataLoader(mnist_test,batch_size=batch_size,shuffle=False,num_workers=num_workers)
return train_iter,test_iter
def train_ch3(net,train_iter,test_iter,loss,num_epochs,batch_size,params=None,lr=None,optimizer =None):
for epochin range(num_epochs):
train_l_sum,train_acc_sum,n =0.0,0.0,0
for x,yin train_iter:
y_hat = net(x)
l = loss(y_hat,y).sum()
#梯度清零
if optimizeris not None:
optimizer.zero_grad()
elif paramsis not None and params[0].gradis not None:
for paramin params:
param.grad.data.zero_()
l.backward()
#梯度优化
if optimizeris None:
sgd(params,lr,batch_size)
else:
optimizer.step()
train_l_sum += l.item()
train_acc_sum += (y_hat.argmax(dim=1)==y).sum().item()
n += y.shape[0]
test_acc = evaluate_accuracy(test_iter,net)
print('epoch:{} ,loss:{:.4f}, train_acc:{:.3f}, test_acc:{:.3f}'.format(epoch+1,train_l_sum/n , train_acc_sum/n,test_acc))