PyTorch教程-4.4. 从头开始实现 Softmax 回归

因为 softmax 回归是如此基础,我们相信您应该知道如何自己实现它。在这里,我们限制自己定义模型的 softmax 特定方面,并重用线性回归部分的其他组件,包括训练循环。
import torchfrom d2l import torch as d2l
from mxnet import autograd, gluon, np, npxfrom d2l import mxnet as d2lnpx.set_np()
from functools import partialimport jaxfrom flax import linen as nnfrom jax import numpy as jnpfrom d2l import jax as d2l
no gpu/tpu found, falling back to cpu. (set tf_cpp_min_log_level=0 and rerun for more info.)
import tensorflow as tffrom d2l import tensorflow as d2l  
4.4.1. softmax
让我们从最重要的部分开始:从标量到概率的映射。作为复习,请回忆一下在张量中沿特定维度的求和运算符,如第 2.3.6 节和 第 2.3.7 节中所讨论的。给定一个矩阵,x我们可以对所有元素(默认情况下)或仅对同一轴上的元素求和。该axis变量让我们计算行和列的总和:
x = torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])x.sum(0, keepdims=true), x.sum(1, keepdims=true)
(tensor([[5., 7., 9.]]), tensor([[ 6.], [15.]]))
x = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])x.sum(0, keepdims=true), x.sum(1, keepdims=true)
(array([[5., 7., 9.]]), array([[ 6.], [15.]]))
x = jnp.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])x.sum(0, keepdims=true), x.sum(1, keepdims=true)
(array([[5., 7., 9.]], dtype=float32), array([[ 6.], [15.]], dtype=float32))
x = tf.constant([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])tf.reduce_sum(x, 0, keepdims=true), tf.reduce_sum(x, 1, keepdims=true)
(, )  
计算 softmax 需要三个步骤:(i)每一项取幂;(ii) 对每一行求和以计算每个示例的归一化常数;(iii) 将每一行除以其归一化常数,确保结果之和为 1。
(4.4.1)softmax(x)ij=exp⁡(xij)∑kexp⁡(xik).
分母的(对数)称为(对数)配分函数。它是在统计物理学中引入的 ,用于对热力学系综中的所有可能状态求和。实现很简单:
def softmax(x): x_exp = torch.exp(x) partition = x_exp.sum(1, keepdims=true) return x_exp / partition # the broadcasting mechanism is applied here
def softmax(x): x_exp = np.exp(x) partition = x_exp.sum(1, keepdims=true) return x_exp / partition # the broadcasting mechanism is applied here
def softmax(x): x_exp = jnp.exp(x) partition = x_exp.sum(1, keepdims=true) return x_exp / partition # the broadcasting mechanism is applied here
def softmax(x): x_exp = tf.exp(x) partition = tf.reduce_sum(x_exp, 1, keepdims=true) return x_exp / partition # the broadcasting mechanism is applied here  
对于任何输入x,我们将每个元素变成一个非负数。每行总和为 1,这是概率所要求的。注意:上面的代码对于非常大或非常小的参数并不稳健。虽然这足以说明正在发生的事情,但您不应 将此代码逐字用于任何严肃的目的。深度学习框架内置了这样的保护,我们将在未来使用内置的 softmax。
x = torch.rand((2, 5))x_prob = softmax(x)x_prob, x_prob.sum(1)
(tensor([[0.1560, 0.2128, 0.2260, 0.2372, 0.1680], [0.1504, 0.2473, 0.1132, 0.2779, 0.2112]]), tensor([1.0000, 1.0000]))
x = np.random.rand(2, 5)x_prob = softmax(x)x_prob, x_prob.sum(1)
(array([[0.17777154, 0.1857739 , 0.20995119, 0.23887765, 0.18762572], [0.24042214, 0.1757977 , 0.23786479, 0.15572716, 0.19018826]]), array([1., 1.]))
x = jax.random.uniform(jax.random.prngkey(d2l.get_seed()), (2, 5))x_prob = softmax(x)x_prob, x_prob.sum(1)
(array([[0.17380024, 0.13607854, 0.29826194, 0.18967763, 0.20218161], [0.24212085, 0.19360834, 0.21299706, 0.17635451, 0.17491929]], dtype=float32), array([1., 1.], dtype=float32))
x = tf.random.uniform((2, 5))x_prob = softmax(x)x_prob, tf.reduce_sum(x_prob, 1)
(, )  
4.4.2. 该模型
我们现在拥有了实现 softmax 回归模型所需的一切。与我们的线性回归示例一样,每个实例都将由一个固定长度的向量表示。由于这里的原始数据包括28×28像素图像,我们将每个图像展平,将它们视为长度为 784 的向量。在后面的章节中,我们将介绍卷积神经网络,它以更令人满意的方式利用空间结构。
在 softmax 回归中,我们网络的输出数量应该等于类的数量。由于我们的数据集有 10 个类,我们的网络的输出维度为 10。因此,我们的权重构成784×10矩阵加一个1×10 偏差的维行向量。与线性回归一样,我们w使用高斯噪声初始化权重。偏差被初始化为零。
class softmaxregressionscratch(d2l.classifier): def __init__(self, num_inputs, num_outputs, lr, sigma=0.01): super().__init__() self.save_hyperparameters() self.w = torch.normal(0, sigma, size=(num_inputs, num_outputs), requires_grad=true) self.b = torch.zeros(num_outputs, requires_grad=true) def parameters(self): return [self.w, self.b]
class softmaxregressionscratch(d2l.classifier): def __init__(self, num_inputs, num_outputs, lr, sigma=0.01): super().__init__() self.save_hyperparameters() self.w = np.random.normal(0, sigma, (num_inputs, num_outputs)) self.b = np.zeros(num_outputs) self.w.attach_grad() self.b.attach_grad() def collect_params(self): return [self.w, self.b]
class softmaxregressionscratch(d2l.classifier): num_inputs: int num_outputs: int lr: float sigma: float = 0.01 def setup(self): self.w = self.param('w', nn.initializers.normal(self.sigma), (self.num_inputs, self.num_outputs)) self.b = self.param('b', nn.initializers.zeros, self.num_outputs)
class softmaxregressionscratch(d2l.classifier): def __init__(self, num_inputs, num_outputs, lr, sigma=0.01): super().__init__() self.save_hyperparameters() self.w = tf.random.normal((num_inputs, num_outputs), 0, sigma) self.b = tf.zeros(num_outputs) self.w = tf.variable(self.w) self.b = tf.variable(self.b)  
下面的代码定义了网络如何将每个输入映射到输出。请注意,我们将每个28×28reshape在将数据传递给我们的模型之前,将批处理中的像素图像转换为向量。
@d2l.add_to_class(softmaxregressionscratch)def forward(self, x): x = x.reshape((-1, self.w.shape[0])) return softmax(torch.matmul(x, self.w) + self.b)
@d2l.add_to_class(softmaxregressionscratch)def forward(self, x): x = x.reshape((-1, self.w.shape[0])) return softmax(np.dot(x, self.w) + self.b)
@d2l.add_to_class(softmaxregressionscratch)def forward(self, x): x = x.reshape((-1, self.w.shape[0])) return softmax(jnp.matmul(x, self.w) + self.b)
@d2l.add_to_class(softmaxregressionscratch)def forward(self, x): x = tf.reshape(x, (-1, self.w.shape[0])) return softmax(tf.matmul(x, self.w) + self.b)  
4.4.3. 交叉熵损失
接下来我们需要实现交叉熵损失函数( 4.1.2节介绍 )。这可能是所有深度学习中最常见的损失函数。目前,深度学习的应用很容易抛出分类问题,远远超过那些更好地视为回归问题的问题。
回想一下,交叉熵采用分配给真实标签的预测概率的负对数似然。为了提高效率,我们避免使用 python for 循环并改用索引。特别是,one-hot 编码y允许我们选择匹配项y^.
为了在行动中看到这一点,我们创建了样本数据,y_hat其中包含 3 个类及其对应标签的 2 个预测概率示例 y。正确的标签是0和2分别(即一等和三等)。使用y中概率的指标y_hat,我们可以有效地挑选出术语。
y = torch.tensor([0, 2])y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])y_hat[[0, 1], y]
tensor([0.1000, 0.5000])  
现在我们可以通过对所选概率的对数进行平均来实现交叉熵损失函数。
def cross_entropy(y_hat, y): return -torch.log(y_hat[list(range(len(y_hat))), y]).mean()cross_entropy(y_hat, y)
tensor(1.4979)
@d2l.add_to_class(softmaxregressionscratch)def loss(self, y_hat, y): return cross_entropy(y_hat, y)
y = np.array([0, 2])y_hat = np.array([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])y_hat[[0, 1], y]
array([0.1, 0.5])  
now we can implement the cross-entropy loss function by averaging over the logarithms of the selected probabilities.
def cross_entropy(y_hat, y): return -np.log(y_hat[list(range(len(y_hat))), y]).mean()cross_entropy(y_hat, y)
array(1.4978662)
@d2l.add_to_class(softmaxregressionscratch)def loss(self, y_hat, y): return cross_entropy(y_hat, y)
y = jnp.array([0, 2])y_hat = jnp.array([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])y_hat[[0, 1], y]
array([0.1, 0.5], dtype=float32)  
now we can implement the cross-entropy loss function by averaging over the logarithms of the selected probabilities.
note that to make use of jax.jit to speed up jax implementations, and to make sure loss is a pure function, the cross_entropy function is re-defined inside the loss to avoid usage of any global variables or functions which may render the loss function impure. we refer interested readers to the jax documentation on jax.jit and pure functions.
def cross_entropy(y_hat, y): return -jnp.log(y_hat[list(range(len(y_hat))), y]).mean()cross_entropy(y_hat, y)
array(1.4978662, dtype=float32)
@d2l.add_to_class(softmaxregressionscratch)@partial(jax.jit, static_argnums=(0))def loss(self, params, x, y, state): def cross_entropy(y_hat, y): return -jnp.log(y_hat[list(range(len(y_hat))), y]).mean() y_hat = state.apply_fn({'params': params}, *x) # the returned empty dictionary is a placeholder for auxiliary data, # which will be used later (e.g., for batch norm) return cross_entropy(y_hat, y), {}
y_hat = tf.constant([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])y = tf.constant([0, 2])tf.boolean_mask(y_hat, tf.one_hot(y, depth=y_hat.shape[-1]))
now we can implement the cross-entropy loss function by averaging over the logarithms of the selected probabilities.
def cross_entropy(y_hat, y): return -tf.reduce_mean(tf.math.log(tf.boolean_mask( y_hat, tf.one_hot(y, depth=y_hat.shape[-1]))))cross_entropy(y_hat, y)
@d2l.add_to_class(softmaxregressionscratch)def loss(self, y_hat, y): return cross_entropy(y_hat, y)  
4.4.4. 训练
我们重用第 3.4 节fit中定义的方法来训练具有 10 个 epoch 的模型。请注意,时期数 ( )、小批量大小 ( ) 和学习率 ( ) 都是可调整的超参数。这意味着虽然这些值不是在我们的主要训练循环中学习到的,但它们仍然会影响我们模型的性能、bot vis-a-vis 训练和泛化性能。在实践中,您将希望根据数据的验证拆分来选择这些值,然后最终在测试拆分上评估您的最终模型。如第 3.6.3 节所述max_epochsbatch_sizelr,我们将 fashion-mnist 的测试数据视为验证集,从而报告此拆分的验证损失和验证准确性。
data = d2l.fashionmnist(batch_size=256)model = softmaxregressionscratch(num_inputs=784, num_outputs=10, lr=0.1)trainer = d2l.trainer(max_epochs=10)trainer.fit(model, data)
data = d2l.fashionmnist(batch_size=256)model = softmaxregressionscratch(num_inputs=784, num_outputs=10, lr=0.1)trainer = d2l.trainer(max_epochs=10)trainer.fit(model, data)
data = d2l.fashionmnist(batch_size=256)model = softmaxregressionscratch(num_inputs=784, num_outputs=10, lr=0.1)trainer = d2l.trainer(max_epochs=10)trainer.fit(model, data)
data = d2l.fashionmnist(batch_size=256)model = softmaxregressionscratch(num_inputs=784, num_outputs=10, lr=0.1)trainer = d2l.trainer(max_epochs=10)trainer.fit(model, data)
4.4.5. 预言
现在训练已经完成,我们的模型已准备好对一些图像进行分类。
x, y = next(iter(data.val_dataloader()))preds = model(x).argmax(axis=1)preds.shape
torch.size([256])
x, y = next(iter(data.val_dataloader()))preds = model(x).argmax(axis=1)preds.shape
(256,)
x, y = next(iter(data.val_dataloader()))preds = model.apply({'params': trainer.state.params}, x).argmax(axis=1)preds.shape
(256,)
x, y = next(iter(data.val_dataloader()))preds = tf.argmax(model(x), axis=1)preds.shape
tensorshape([256])  
我们更感兴趣的是我们标记错误的图像。我们通过将它们的实际标签(文本输出的第一行)与模型的预测(文本输出的第二行)进行比较来可视化它们。
wrong = preds.type(y.dtype) != yx, y, preds = x[wrong], y[wrong], preds[wrong]labels = [a+'n'+b for a, b in zip( data.text_labels(y), data.text_labels(preds))]data.visualize([x, y], labels=labels)
wrong = preds.astype(y.dtype) != yx, y, preds = x[wrong], y[wrong], preds[wrong]labels = [a+'n'+b for a, b in zip( data.text_labels(y), data.text_labels(preds))]data.visualize([x, y], labels=labels)
wrong = preds.astype(y.dtype) != yx, y, preds = x[wrong], y[wrong], preds[wrong]labels = [a+'n'+b for a, b in zip( data.text_labels(y), data.text_labels(preds))]data.visualize([x, y], labels=labels)
wrong = tf.cast(preds, y.dtype) != yx, y, preds = x[wrong], y[wrong], preds[wrong]labels = [a+'n'+b for a, b in zip( data.text_labels(y), data.text_labels(preds))]data.visualize([x, y], labels=labels)
4.4.6. 概括
到目前为止,我们开始获得解决线性回归和分类问题的一些经验。有了它,我们已经达到了可以说是 1960-1970 年代统计建模的最先进水平。在下一节中,我们将向您展示如何利用深度学习框架更有效地实施该模型。
4.4.7. 练习
在本节中,我们根据 softmax 运算的数学定义直接实现了 softmax 函数。如第 4.1 节所述,这会导致数值不稳定。
测试如果softmax输入的值为100?
测试如果softmax所有输入中最大的小于−100?
通过查看相对于参数中最大条目的值来实施修复。
实现一个cross_entropy遵循交叉熵损失函数定义的函数∑iyilog⁡y^i.
在上面的代码示例中尝试一下。
为什么你认为它运行得更慢?
你应该使用它吗?在哪些情况下有意义?
你需要注意什么?提示:考虑对数的定义域。
返回最有可能的标签总是一个好主意吗?例如,你会为了医学诊断而这样做吗?你会如何尝试解决这个问题?
假设我们要使用 softmax 回归来根据某些特征预测下一个单词。大词汇量可能会带来哪些问题?
试验上面代码的超参数。尤其:
绘制验证损失如何随着学习率的变化而变化。
当您更改小批量大小时,验证和训练损失是否会发生变化?在看到效果之前,您需要多大或多小?

一文告诉你工厂如何利用大数据
能耗制动控制电路图
KEPServerEX与MES对接的几种方式
无线传感器网络具有怎样的特点
WebRTC进行压测的思路及方式和一些经验
PyTorch教程-4.4. 从头开始实现 Softmax 回归
应用材料公司与Arm以及Symetrix合作开发“神经形态”的电子开关
AR光电缆经路图:五秒钟查找光电缆基础信息,提高检修效率
华为荣耀V9开箱:荣耀V8和华为荣耀8的继承者
AI时代来临 群联推出可客制化企业级SSD解决方案FX系列
小米6评测:小米6与小米5对比评测,万变不离其宗
如何用MATLAB设计一个会自动调节的控制器
10亿元!节卡机器人完成D轮融资 成协作机器人领域融资规模最大企业
光纤的发展历程和常见分类方式
5G千兆工业路由器具有着低延时、高速率的特点
校园小型气象站的功能作用建设推广
如何提高电机轴承的使用寿命?
三星发布5G新品手机,创下中国手机市场价格新高记录
Windows10 将开始正式支持WSL 2发行版
利用硬件加速器提高处理器的性能