[深度学习]\(Softmax\)回归、损失函数、分类
1、\(Softmax\)回归模型
\(Softmax\)回归虽然它的名字是回归,其实它是一个分类问题。
\(Softmax\)回归跟线性回归一样将输入特征与权值做线性叠加。与线性回归主要不同的在于:\(Softmax\)回归的输出值个数等于标签里的类别数。
比如一共有4种特征和3种输出动物类别(狗、猫、鸡),则权重包含12个标量(带下标的\(w\))、偏差包含3个标量(带下标的\(b\)),且对每个输入计算\(o_1,o_2,o_3\)这\(3\)个输出:
\[o_1 = x_1w_{11} + x_2w_{21}+x_3w_{31}+x_4w_{41}+b_1,\\ o_2 = x_1w_{12} + x_2w_{22}+x_3w_{32}+x_4w_{42}+b_2,\\ o_2 = x_1w_{13} + x_2w_{23}+x_3w_{33}+x_4w_{43}+b_3,\\ \]最后,在对这些输出值进行\(Softmax\)函数运算。
\(Softmax\)回归同线性回归一样,也是一个单层神经网络。由于每个输出\(o_1,o_2,o_3\)的计算都要依赖于所有的输入\(x_1,x_2,x_3,x_4\)。所以\(Softmax\)回归的输出层也是一个全连接层。
2、\(Softmax\)函数
\(Softmax\)用于多分类过程中,它将多个神经元的输出(比如\(o_1,o_2,o_3\))映射到\((0,1)\)区间内,可以看成概率来理解从而来进行多分类。
它们通过下式将输出值变成值为正数且和为\(1\)的概率分布。
\[\hat{y_1},\hat{y_2},\hat{y_3} = softmax(o_1,o_2,o_3) \]其中:
\[\hat{y_1}= \dfrac{\exp(o_1)}{\sum_{i = 1}^{3}\exp(o_i)},\hat{y_2}= \dfrac{\exp(o_2)}{\sum_{i = 1}^{3}\exp(o_i)},\hat{y_3}= \dfrac{\exp(o_3)}{\sum_{i = 1}^{3}\exp(o_i)}, \]容易看出\(\hat{y_1}+\hat{y_2}+\hat{y_3} = 1\)且\(0\le\hat{y_1},\hat{y_1},\hat{y_1}\le1\),因此\(\hat{y}_1,\hat{y}_2,\hat{y}_3\)是一个合法的概率分布。这时候如果\(\hat{y}_2 = 0.8\),则不管\(\hat{y}_1\)和\(\hat{y}_3\)的值是多少,我们都知道图像类别为猫的概率是\(80\%\)。
\[argmax (o_i) = argmax(\hat{y}_i) \]尽管\(o_i\)的原始值和\(\hat{y}_i\)的概率值不同,但是\(softmax\)函数的单调性保证了不会改变原始最大值的索引。这意味着,无论是\(softmax\)变换前后,最大的输入\(o_i\)的索引,都对应于最大概率\(\hat{y}_i\)的索引。
因此,如果你在未经\(softmax\)变换的输出\(o\)上找到了最大的索引,那么经过\(softmax\)变换后,概率的最高值的索引仍然是不变的。
3.\(Softmax回归的决策函数\)
\(softmax\)回归的决策函数是基于\(softmax\)函数和模型参数来进行分类决策的方法。\(softmax\)回归通常用于多分类问题,其中样本可能属于两个以上的类别。决策函数的核心是计算每个类别的得分,然后将样本分配给得分最高的那个类别。
线性函数:\(o = Wx + b\),这个函数给出每个类别的元素得分(也称\(logits\))
\[W = \left[ \begin{matrix} w_{11} & w_{12} & w_{13} & w_{14}\\ w_{21} & w_{22} & w_{23} & w_{24} \\ w_{31} & w_{32} & w_{33} & w_{34} \end{matrix} \right] ,b = \left[ \begin{matrix} b_{1} & b_{2} & b_{3} \end{matrix} \right] \]设高和宽分别为2个像素样本\(i\)的特征为
\[x^{(i)} = \left[ \begin{matrix} x^{(i)}_{1} & x^{(i)}_{2} & x^{(i)}_{3} & x^{(i)}_{4}\\ \end{matrix} \right], \]则输出层的输出为:
\[o^{(i)} = \left[ \begin{matrix} o^{(i)}_{1} & o^{(i)}_{2} & o^{(i)}_{3}\\ \end{matrix} \right], \]预测狗、猫、鸡的概率分布为:
\[\hat{y}^{(i)}= \left[ \begin{matrix} \hat{y}^{(i)}_{1} & \hat{y}^{(i)}_{2} & \hat{y}^{(i)}_{3}\\ \end{matrix} \right], \]那么总结一下\(Softmax\)回归对样本\(i\)分类的矢量计算就是:
\[o^{(i)} = X^{(i)}W^{T} + b\\ \hat{y}^{(i)} = softmax(o^{(i)}) = \dfrac{\exp(o_i)}{\sum_{j = 1}^{n}\exp(o_j)} \]则\(Softmax\)回归的决策函数可以表示为:
\[\hat{y} = argmax(\hat{y}^{(i)}) \]4、参数估计
4.1 交叉熵损失函数(用于分类问题)
在使用\(softmax\)函数进行多分类问题时,常用的损失函数是交叉熵损失(Cross-Entropy Loss)。这种损失函数可以衡量真实概率分布和预测概率分布之间的差异。
对于单个样本和多分类问题,交叉熵损失函数定义如下:
\[L(y,\hat{y}) = -\sum_{i = 1}^C y_i \log(\hat{y}_i) \]其中:
- \(C\)是类别的总数
- \(y\)是一个\(one-hot\)编码的向量。表示真实的标签。在这个向量中,正确类别的索引处的值为1,其余为0。这意味着,交叉熵损失只计算真实类别对应的预测概率的负对数。如果模型对真实类别非常有信心,即其预测概率\(\hat{y}_i\)接近于1,那么损失将接近于零。相反,如果模型对真实类别的预测概率很低,损失将会很高。
- \(\hat{y}\)是模型的输出,即在\(softmax\)函数后得到的预测概率分布,也是一个长度为\(C\)的向量,每个元素\(\hat{y}_i\)代表模型预测样本属于第\(i\)个类别的概率。
这个损失函数将真实标签的概率乘以预测概率的负对数。由于是one-hot编码的,所以除了实际类别对应的项,其他项都会是零,不会对损失函数的求和有贡献。这意味着损失函数实质上简化为:
\[L(y,\hat{y}) = -\log(\hat{y}_k) \]这里的\(k\)的真实类别的索引。
对于所有训练样本的平均交叉熵损失计算如下:
假设你有一个由\(m\)个样本组成的数据集,\(C\)是类别总数,\(y^{(i)}\)是第个\(i\)样本的真实标签(通常是one-hot编码形式),\(\hat{y}^{(i)}\)是模型对第\(i\)个样本的预测概率。那么整个数据集的平均交叉熵损失可以通过下面的公式计算:
\[L(y,\hat{y}) = -\sum_{i = 1}^C y_i \log(\hat{y}_i)= -\log(\hat{y}_k),\\ J(W) = \dfrac{1}{m}\sum_{i = 1}^{m}L(y^{(i)},\hat{y}^{(i)})= -\dfrac{1}{m}\sum_{i = 1}^{m}\sum_{j = 1}^{C}y^{(i)}_j\log (\hat{y}^{(i)}_j) \]这里:
- \(J(W)\)表示关于模型权重的代价函数,\(W\)包含了权重和偏置
- \(m\)是样本的总数。
- \(y^{(i)}_j\)是真实标签的第\(j\)个元素,针对第\(i\)个样本。如果第个\(i\)样本的真实类别是\(j\),就是1,否则是0。
- \(\hat{y}^{(i)}_j\)是模型预测出的第\(i\)个样本属于类别的概率。
- \(C\)是类别的总数。
- 外层的求和\(\sum_{i = 1}^{m}\)是对所有训练样本进行遍历。
- 内层的求和\(\sum_{j = 1}^{C}\)是对所有类别进行遍历。
- \(k\)代表的是正确类别的索引
注意:
对于单个样本,交叉熵损失函数定义为:
\[L(y,\hat{y}) = -\sum_{i = 1}^C y_i \log(\hat{y}_i) \]而对于所有训练样本的平均损失定义为 :
\[J(W) = \frac{1}{m}\sum_{i = 1}^{m}L(y^{(i)}, \hat{y}^{(i)}) = -\frac{1}{m}\sum_{i = 1}^{m}\sum_{j = 1}^{C}y_j^{(i)}\log(\hat{y}_j^{(i)}) \]这个平均损失\(J(W)\)考虑了所有样本的损失,并且不能进一步简化为只包含一个类别索引\(k\) ,因为它必须对数据集中所有可能的类别组合进行求和。
4.2 均方误差损失函数(用于回归问题)
对于单个样本\((x^{(i)},y^{(i)})\),模型的输出\(\hat{y}^{(i)}\)就是输入特征\(x^{(i)}\)和权重\(W\)的点积,即\(\hat{y}^{(i)} = W^Tx^{(i)}\)。
假设我们有一个线性回归模型,损失函数\(J(W)\)是计算所以样本上的预测误差的平均值,使用均方误差,损失函数如下:
\[J(W) = \dfrac{1}{2m}\sum_{i = 1}^{m}(\hat{y}^{(i)}-y^{(i)})^2 \]为什么是均方误差的一半?这只是为了在求导时可以抵消掉系数2,简化计算过程。
\[\dfrac{\partial J(W)}{\partial W} =\dfrac{\partial}{\partial W}(\dfrac{1}{2m}\sum_{i = 1}^{m}(\hat{y}^{(i)}-y^{(i)})^2) \]应用链式法则,我们首先对内部进行求导(关于预测\(\hat{y}^{(i)}\) 的偏导数):
\[\frac{\partial J(W)}{\partial \hat{y}^{(i)}} = \frac{\partial}{\partial \hat{y}^{(i)}} \left( \frac{1}{2m} (\hat{y}^{(i)} - y^{(i)})^2 \right) = \frac{1}{m} (\hat{y}^{(i)} - y^{(i)}) \]接下来,由于$ ( \hat{y}^{(i)} = W^T x^{(i)})$,我们计算 $\hat{y}^{(i)} $关于 $W $的偏导数:
\[ \frac{\partial \hat{y}^{(i)}}{\partial W} = \frac{\partial}{\partial W} (W^T x^{(i)}) = x^{(i)} \]利用链式法则,结合上面的两个偏导数,我们可以得到梯度如下:
\[ \frac{\partial J(W)}{\partial W} = \frac{\partial J(W)}{\partial \hat{y}^{(i)}} \cdot \frac{\partial \hat{y}^{(i)}}{\partial W} = \frac{1}{m} \sum_{i=1}^{m} (\hat{y}^{(i)} - y^{(i)}) x^{(i)} \]考虑到梯度下降的方向是损失函数减少的方向,我们取上式的负值:
\[\nabla_W J(W) = -\frac{1}{m} \sum_{i=1}^{m} x^{(i)} (\hat{y}^{(i)} - y^{(i)}) \]这就是参数 \(W\)对损失函数\(J(W)\)梯度的推导过程。
5.\(Softmax\)回归的从零开始实现
我们将使用Fashion-MNIST数据集【简介】,进行图像多分类问题。
\(Step0\):导包
import torch
from IPython import display
from d2l import torch as d2l
\(Step1\):读取数据集
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
① 将展平每个图像,将它们视为长度784的向量。向量的每个元素与w相乘,所以w也需要784行。
② 因为数据集有10个类别,所以网络输出维度为10.
\(Step2:\)初始化模型参数
将展平每个图像,把它们看作长度为\(784\)的向量(已知每一个输入是高和宽均为\(28\)像素的图像,模型的输入向量的长度是\(28\times 28 = 784\))。 因为我们的数据集有\(10\)个类别,所以网络输出维度为\(10\)。
因此softmax回归的权重和偏差参数分别为\(784 × 10 和1 × 10\)的矩阵。
num_inputs = 784
num_outputs = 10
# 随机初始化参数,均值为0,方差为0.01,形状,并为模型参数附上梯度
W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True)
b = torch.zeros(num_outputs, requires_grad=True)
\(Step3:\)定义\(Softmax\)函数
\[softmax(X)_{ij} = \dfrac{\exp(X_{ij})}{\sum_{k}\exp(X_{ik})} \]def softmax(X):
X_exp = torch.exp(X) # 每个都进行指数运算
partition = X_exp.sum(1, keepdim=True)# 按维度1(行)求和,结果为列向量
return X_exp / partition # 这里应用了广播机制
我们将每个元素变成一个非负数。 此外,依据概率原理,每行总和为1
\(Step4\):实现\(softmax\)回归模型
def net(X):
return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)#-1为默认的批量大小,表示有多少个图片,每个图片用一维的784列个元素表示,即X被reshape为256*784形状的矩阵 ,并且行和为1
\(Step5:\)实现交叉熵损失函数
① 创建一个数据y_hat,其中包含2个样本在3个类别的预测概率,使用y作为y_hat中概率的索引。
y = torch.tensor([0,2]) # 标号索引
y_hat = torch.tensor([[0.1,0.3,0.6],[0.3,0.2,0.5]]) # 两个样本在3个类别的预测概率
y_hat[[0,1],y] # 把第0个样本对应标号"0"的预测值拿出来、第1个样本对应标号"2"的预测值拿出来,比如这里就是拿出y_hat[0][y[0]] = 0.1 和 y_hat[1][y[1]] = 0.5
⑧ 实现交叉熵损失函数。
def cross_entropy(y_hat, y):
return -torch.log(y_hat[range(len(y_hat)), y])
\(Step6:\)计算分类准确率
给定一个类别的预测概率分布y_hat,我们把预测概率最大的类别作为输出类别。如果它与真实类别y一致,说明这次预测是正确的。分类准确率即正确预测数量与总预测数量之比。
为了演示准确率的计算,下面定义准确率accuracy函数。其中y_hat.argmax(axis=1)返回矩阵y_hat每行中最大元素的索引,且返回结果与变量y形状相同。相等条件判别式(y_hat.argmax(axis=1) == y)是一个类型为ByteTensor的Tensor,我们用float()将其转换为值为0(相等为假)或1(相等为真)的浮点型Tensor。
将预测类别与真实y
元素进行比较
def accuracy(y_hat,y):
"""计算预测正确的数量"""
if len(y_hat.shape) > 1 and y_hat.shape[1] > 1: # y_hat.shape[1]>1表示不止一个类别,每个类别有各自的概率
y_hat = y_hat.argmax(axis=1) # y_hat.argmax(axis=1)为求行最大值的索引(最大的作为预测)
cmp = y_hat.type(y.dtype) == y # 先判断逻辑运算符==,再赋值给cmp,cmp为布尔类型的数据
return float(cmp.type(y.dtype).sum()) # 获得y.dtype的类型作为传入参数,将cmp的类型转为y的类型(int型),然后再求和
\(Step7:\)评价任意模型net在数据集data_iter上的准确率。
# 可以评估在任意模型net的准确率
def evaluate_accuracy(net,data_iter):
"""计算在指定数据集上模型的精度"""
if isinstance(net,torch.nn.Module): # 如果net模型是torch.nn.Module实现的神经网络的话,将它变成评估模式
net.eval() # 将模型设置为评估模式
metric = Accumulator(2) # 正确预测数、预测总数,metric为累加器的实例化对象,里面存了两个数
for X, y in data_iter:
metric.add(accuracy(net(X),y),y.numel()) # net(X)将X输入模型,获得预测值。y.numel()为样本总数
return metric[0] / metric[1] # 分类正确的样本数 / 总样本数
\(Accumulator\)的实现
# Accumulator实例中创建了2个变量,用于分别存储正确预测的数量和预测的总数量
class Accumulator:
"""在n个变量上累加"""
def __init__(self,n):
self.data = [0,0] * n
def add(self, *args):
self.data = [a+float(b) for a,b in zip(self.data,args)] # zip函数把两个列表第一个位置元素打包、第二个位置元素打包....
def reset(self):
self.data = [0.0] * len(self.data)
def __getitem__(self,idx):
return self.data[idx]
\(Step8:\)训练模型
\(Softmax\)回归的训练
# 训练函数
def train_epoch_ch3(net, train_iter, loss, updater):
if isinstance(net, torch.nn.Module):
net.train() # 开启训练模式
metric = Accumulator(3)
for X, y in train_iter:
y_hat = net(X)
l = loss(y_hat,y) # 计算损失
if isinstance(updater, torch.optim.Optimizer): # 如果updater是pytorch的优化器的话
updater.zero_grad() # 梯度置0
l.backward() # 计算梯度
updater.step() # 参数自更新
metric.add(float(l)*len(y),accuracy(y_hat,y),y.size().numel()) # 总的训练损失、样本正确数、样本总数
else:
l.sum().backward()
updater(X.shape[0])
metric.add(float(l.sum()),accuracy(y_hat,y),y.numel())
return metric[0] / metric[2], metric[1] / metric[2] # 所有loss累加除以样本总数,总的正确个数除以样本总数
动画绘制(辅助函数)
class Animator:
def __init__(self, xlabel=None, ylabel=None, legend=None, xlim=None,
ylim=None, xscale='linear',yscale='linear',
fmts=('-','m--','g-.','r:'),nrows=1,ncols=1,
figsize=(3.5,2.5)):
if legend is None:
legend = []
d2l.use_svg_display()
self.fig, self.axes = d2l.plt.subplots(nrows,ncols,figsize=figsize)
if nrows * ncols == 1:
self.axes = [self.axes,]
self.config_axes = lambda: d2l.set_axes(self.axes[0],xlabel,ylabel,xlim,ylim,xscale,yscale,legend)
self.X, self.Y, self.fmts = None, None, fmts
def add(self, x, y):
if not hasattr(y, "__len__"):
y = [y]
n = len(y)
if not hasattr(x, "__len__"):
x = [x] * n
if not self.X:
self.X = [[] for _ in range(n)]
if not self.Y:
self.Y = [[] for _ in range(n)]
for i, (a,b) in enumerate(zip(x,y)):
if a is not None and b is not None:
self.X[i].append(a)
self.Y[i].append(b)
self.axes[0].cla()
for x, y, fmt in zip(self.X, self.Y, self.fmts):
self.axes[0].plot(x, y, fmt)
self.config_axes()
display.display(self.fig)
display.clear_output(wait=True)
总训练函数
def train_ch3(net,train_iter,test_iter,loss,num_epochs,updater):
animator = Animator(xlabel='epoch',xlim=[1,num_epochs],ylim=[0.3,0.9],
legend=['train loss','train acc','test acc'])# 可视化
for epoch in range(num_epochs): # 变量num_epochs遍数据
train_metrics = train_epoch_ch3(net,train_iter,loss,updater) # 返回两个值,一个总损失、一个总正确率
test_acc = evaluate_accuracy(net, test_iter) # 测试数据集上评估精度,仅返回一个值,总正确率
animator.add(epoch+1,train_metrics+(test_acc,)) # train_metrics+(test_acc,) 仅将两个值的正确率相加,
train_loss, train_acc = train_metrics
lr = 0.1
def updater(batch_size):
return d2l.sgd([W,b], lr, batch_size)
\(Step9:\)预测
def predict_ch3(net,test_iter,n=6):
for X, y in test_iter:
break # 仅拿出一批六个数据
trues = d2l.get_fashion_mnist_labels(y)
preds = d2l.get_fashion_mnist_labels(net(X).argmax(axis=1))
titles = [true + '\n' + pred for true, pred in zip(trues,preds)]
d2l.show_images(X[0:n].reshape((n,28,28)),1,n,titles=titles[0:n])
predict_ch3(net,test_iter)
5.\(Softmax\)回归的简洁实现(使用框架)
import torch
from torch import nn
from d2l import torch as d2l
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
# Softmax回归的输出是一个全连接层
# PyTorch不会隐式地调整输入的形状
# 因此,我们定义了展平层(flatten)在线性层前调整网络输入的形状
net = nn.Sequential(nn.Flatten(),nn.Linear(784,10))
def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01) # 均值为0(默认),方差为0.01的随机值
net.apply(init_weights)
print(net.apply(init_weights)) # net网络的参数用的是init_weights初始化参数
# 在交叉熵损失函数中传递未归一化的预测,并同时计算softmax及其对数
loss = nn.CrossEntropyLoss()
# 使用学习率为0.1的小批量随即梯度下降作为优化算法
trainer = torch.optim.SGD(net.parameters(),lr=0.1)
num_epochs = 10
train_ch3(net,train_iter,test_iter,loss,num_epochs,trainer)
参考:
标签:函数,self,Softmax,深度,类别,net,hat,sum From: https://www.cnblogs.com/nannandbk/p/18120082