Softmax基本原理与python代码实现

您所在的位置:网站首页 买药送到家里的app Softmax基本原理与python代码实现

Softmax基本原理与python代码实现

#Softmax基本原理与python代码实现| 来源: 网络整理| 查看: 265

1、Softmax本质

不同于线性回归是预测多少的问题(比如预测房屋价格),softmax回归是一种用于多分类的问题,它被用于预测样本属于给定类中的哪一类(比如预测图像描绘的是狗、猫还是鸡)。它的本质是把全连接层的输出序列变成一个概率序列:

S(\mathbf{a}):\left[\begin{array}{l} a_1 \\ a_2 \\ \cdots \\ a_N \end{array}\right] \rightarrow\left[\begin{array}{l} S_1 \\ S_2 \\ \ldots \\ S_N \end{array}\right]\\ S_j=\frac{e^{a_j}}{\sum_{k=1}^N e^{a_k}} \quad \forall j \in 1 . . N\\

那么便可以用这个概率序列来表示各输出的相对概率大小,取最大概率对应输出下标即为分类结果索引。这样做的好处是使得在后续梯度下降算法中梯度的求解更为方便。

2、Softmax分类问题实例分析 (1)Softmax网络结构

从一个图像分类问题开始。 假设每次输入是一个2×2的灰度图像,每个图像对应四个特征 x_1,x_2,x_3,x_4 。此外,假设每个图像属于类别“猫“”“鸡”和“狗”中的一个。选择一个三维向量 y \in\{(1,0,0),(0,1,0),(0,0,1)\} 作为分类标签,其中 (1,0,0) 对应“猫”, (0,1,0) 对应“鸡”, (0,0,1) 对应“狗”。这样便有了一个多输出模型:

\begin{aligned} & o_1=x_1 w_{11}+x_2 w_{12}+x_3 w_{13}+x_4 w_{14}+b_1 \\ & o_2=x_1 w_{21}+x_2 w_{22}+x_3 w_{23}+x_4 w_{24}+b_2 \\ & o_3=x_1 w_{31}+x_2 w_{32}+x_3 w_{33}+x_4 w_{34}+b_3 \end{aligned}\\ 其中,带下标的 \omega 为权重, o_1、o_2、o_3表示未规范化的输出序列,带下标的b为偏置。通过向量形式可记为 o=Wx+b 。用神经网络图来描述即为:

图1 单层全连接神经网络

紧接着,需要对序列 o_1、o_2、o_3 进行规范化变换,使得所有输出非负且总和为1,同时保证模型可导。即如下式所示:

\hat{\mathbf{y}}=\operatorname{softmax}(\mathbf{o}) \quad \text { 其中 } \quad \hat{y}_j=\frac{\exp \left(o_j\right)}{\sum_k \exp \left(o_k\right)}\\ 这样保证了对于所有 j 总有 0 \leq \hat{y}_j \leq 1 , \hat{y} 便可视为一个概率分布,并且softmax运算不会改变未规范化的预测o之间的大小次序,只会根据其大小确定分配给每个类别一定的概率。通过 \underset{j}{\operatorname{argmax}} \hat{y}_j=\underset{j}{\operatorname{argmax}} o_j 便可得出最有可能的类别下标。这里需要说明的是,在对训练好的模型上进行预测时可不需要进行softmax运算便可得到最佳分类,但在训练模型时,由于用到梯度下降算法,使用softmax运算配合交叉熵损失函数可以使得在求解梯度的时候更加方便。

(2)损失函数

softmax函数给出了一个向量 \hat{y} ,可以将其视为“对给定任意输入x的每个类的条件概率”。 例如, \hat{y}_1=P(y=\text { 猫 } \mid \mathbf{x}) 。假设整个数据集 \left\{ X,Y \right\} 具有 n 个样本, 其中索引 i 的样本由特征向量 x^{(i)} 和独热标签向量 y^{(i)} 组成。 将估计值与实际值进行比较:

P(\mathbf{Y} \mid \mathbf{X})=\prod_{i=1}^n P\left(\mathbf{y}^{(i)} \mid \mathbf{x}^{(i)}\right)\\ 根据最大似然估计,最大化 P(\mathbf{Y} \mid \mathbf{X}) 相当于最小化负对数似然: -\log P(\mathbf{Y} \mid \mathbf{X})=\sum_{i=1}^n-\log P\left(\mathbf{y}^{(i)} \mid \mathbf{x}^{(i)}\right)=\sum_{i=1}^n l\left(\mathbf{y}^{(i)}, \hat{\mathbf{y}}^{(i)}\right)\\ 其中,对于任何标签 y 和模型预测 \hat{y} ,损失函数写为:

l(\mathbf{y},\hat{\mathbf{y}})=-\sum_{j=1}^q y_j\log\hat{y}_j\\ 这通常被称为交叉熵损失(cross-entropy loss)。

(3)损失函数求导

根据softmax定义可以得到:

\begin{aligned}l(\mathbf{y},\mathbf{y})&=-\sum_{j=1}^q y_j\log\frac{\exp(o_j)}{\sum_{k=1}^q\exp(o_k)}\\ &=\sum_{j=1}^q y_j\log\sum_{k=1}^q\exp(o_k)-\sum_{j=1}^q y_jo_j\\ &=\log\sum_{k=1}^q\exp(o_k)-\sum_{j=1}^q y_jo_j.\end{aligned}\\ 考虑对 o_j 求导,得到:

\partial_{o_j}l(\mathbf{y},\hat{\mathbf{y}})=\dfrac{\exp(o_j)}{\sum_{k=1}^q\exp(o_k)}-y_j=\mathrm{softmax}(\mathbf{o})_j-y_j\\

换句话说,导数是softmax模型分配的概率与实际发生的情况(由独热标签向量表示)之间的差异。 同样的,在线性回归中,梯度是观测值 y 和估计值 \hat{y} 之间的差异。实际上这不是巧合,在任何指数族分布模型中 , 对数似然的梯度正是由此得出的。 这就使得梯度计算在实践中变得容易很多。

3、Softmax代码实现(完整)

代码参照李沐《动手学深度学习》第二版pytorch代码,用到的d2l包下载地址:https://github.com/d2l-ai/d2l-zh。

(1)引入的Fashion-MNIST数据集, 并设置数据迭代器的批量大小为256。import torch from IPython import display from d2l import torch as d2l batch_size = 256 train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)(2)初始化模型参数

原始数据集中的每个样本都是28×28的图像,展平每个图像,把它们看作长度为784的向量,把每个像素位置看作一个特征。数据集有10个类别,因而权重矩阵为784×10的矩阵,偏置向量为1×10。使用正态分布初始化我们的权重w,偏置初始化为0。

num_inputs = 784 num_outputs = 10 W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True) b = torch.zeros(num_outputs, requires_grad=True)(3)定义softmax函数def softmax(X): X_exp = torch.exp(X) partition = X_exp.sum(1, keepdim=True) return X_exp / partition # 这里应用了广播机制(4)定义回归模型def net(X): return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)(5)定义交叉熵损失函数def cross_entropy(y_hat, y): return - torch.log(y_hat[range(len(y_hat)), y])(6)定义分类精度计算函数

定义acuracy函数用于计算预测正确的数量。

def accuracy(y_hat, y): """计算预测正确的数量""" if len(y_hat.shape) > 1 and y_hat.shape[1] > 1: y_hat = y_hat.argmax(axis=1) cmp = y_hat.type(y.dtype) == y return float(cmp.type(y.dtype).sum())

定义evaluate_accuracy函数用于计算net模型在data_iter指定访问的数据集上的预测正确率。

def evaluate_accuracy(net, data_iter): """计算在指定数据集上模型的精度""" if isinstance(net, torch.nn.Module): net.eval() # 将模型设置为评估模式 metric = Accumulator(2) # 正确预测数、预测总数 with torch.no_grad(): for X, y in data_iter: metric.add(accuracy(net(X), y), y.numel()) return metric[0] / metric[1]

定义一个实用程序类Accumulator,用于对多个变量进行累加。 在上面的evaluate_accuracy函数中, 我们在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)] def reset(self): self.data = [0.0] * len(self.data) def __getitem__(self, idx): return self.data[idx](7)训练

定义一个迭代周期的训练函数

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): # 使用PyTorch内置的优化器和损失函数 updater.zero_grad() l.mean().backward() updater.step() 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]

在展示训练函数的实现之前,定义一个在动画中绘制数据的实用程序类Animator。

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, ] # 使用lambda函数捕获参数 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)

接下来实现一个训练函数, 它会在train_iter访问到的训练数据集上训练一个模型net。 该训练函数将会运行多个迭代周期(由num_epochs指定)。 在每个迭代周期结束时,利用test_iter访问到的测试数据集对模型进行评估。 另外,利用Animator类来可视化训练进度。

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): 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_loss, train_acc = train_metrics assert train_loss < 0.5, train_loss assert train_acc 0.7, train_acc assert test_acc 0.7, test_acc

设置学习率为0.1,使用小批量随机梯度下降来优化模型的损失函数。

lr = 0.1 def updater(batch_size): return d2l.sgd([W, b], lr, batch_size)

训练模型10个迭代周期。

num_epochs = 10 train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater)图2 训练结果可视化进度图(8)预测

给定一系列图像进行分类预测,实际标签打印在第一行,预测标签打印在第二行,从结果可以看出测试数据集中前6张图的预测结果均正确。

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)图3 预测结果与实际标签对比4、Softmax简洁版代码实现(d2l包调用)

由于本帖代码中acuracy、evaluate_accuracy、Accumulator、train_epoch_ch3、train_ch3、predict_ch3函数以及Animator类均在d2l包中有封装,为便于理解均完全列出,实际使用时可直接通过d2l调用即可。

训练:

import torch from IPython import display from d2l import torch as d2l batch_size = 256 train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size) num_inputs = 784 num_outputs = 10 W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True) b = torch.zeros(num_outputs, requires_grad=True) def softmax(X): X_exp = torch.exp(X) partition = X_exp.sum(1, keepdim=True) return X_exp / partition # 这里应用了广播机制 def net(X): return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b) def cross_entropy(y_hat, y): return - torch.log(y_hat[range(len(y_hat)), y]) lr = 0.1 def updater(batch_size): return d2l.sgd([W, b], lr, batch_size) num_epochs = 10 d2l.train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater)

测试:

d2l.predict_ch3(net, test_iter)



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3