• Softmax 回归(PyTorch)


    https://courses.d2l.ai/zh-v2/

    Softmax 回归

    回归 vs 分类

    • 回归估计一个连续值
    • 分类预测一个离散类别

    MNIST:手写数字识别(10类)
    ImageNet:自然物体分类(1000类)

    从回归到多类分类

    在这里插入图片描述

    均方损失

    在这里插入图片描述

    one hot编码

    无校验比例

    • 对类别进行一位有效编码
    • 最大值最为预测
      y ^ = a r g m a x i o i {\hat y}=\mathop{argmax}\limits_ i o_i y^=iargmaxoi
    • 需要更置信的识别正确类(大余量)
      o y − o i ≥ Δ ( y , i ) o_y-o_i\ge \Delta(y,i) oyoiΔ(y,i)

    校验比例

    • 输出匹配概率(非负,和为1)
      y ^ = s o f t m a x ( o ) y ^ i = e x p ( o i ) ∑ k e x p ( o k ) {\hat y}=softmax(o)\\ {\hat y_i}={exp(o_i) \over \sum_kexp(o_k)} y^=softmax(o)y^i=kexp(ok)exp(oi)

    • 概率 y y y y ^ \hat y y^ 的区别作为损失

    引入 softmax 操作子
    指数的好处是不管什么值,都能把它变成非负

    Softmax 和交叉熵损失

    • 交叉熵常用来衡量两个概率的区别 H ( p , q ) = ∑ i − p i l o g ( q i ) H(p,q)=\sum\limits_i-p_ilog(q_i) H(p,q)=ipilog(qi)
    • 将它作为损失
      l ( y , y ^ ) = − ∑ i y i l o g y ^ i = − l o g y ^ y l(y,\hat y)=-\sum_iy_ilog\hat y_i=-log\hat y_y l(y,y^)=iyilogy^i=logy^y
    • 其梯度是真实概率和预测概率的区别
      ∂ o i l ( y , y ^ ) = s o f t m a x ( o ) i − y i \partial_{o_i}l(y,\hat y)=softmax(o)_i - y_i oil(y,y^)=softmax(o)iyi

    分类问题我们不关心非正确类的预测值,我们只关心对正确类的预测值。

    总结

    • Softmax 回归是一个多分类模型
    • 使用 Softmax 操作子得到每个类的预测置信度
    • 使用交叉熵来衡量预测和标号的区别

    损失函数

    L2 LOSS

    l ( y , y ′ ) = 1 2 ( y − y ′ ) 2 l(y,y')={1 \over 2}(y-y')^2 l(y,y)=21(yy)2

    1 2 是 为 了 求 导 的 时 候 消 去 {1 \over 2}是为了求导的时候消去 21

    三个曲线都是y=0时,损失随着x变化而变化
    蓝色曲线表示我的预测值 y ′ y{'} y
    绿色曲线表示似然函数的规律
    橙色曲线表示损失函数的梯度

    在这里插入图片描述

    L1 Loss

    l ( y , y ′ ) = ∣ y − y ′ ∣ l(y,y')=|y-y'| l(y,y)=yy

    在这里插入图片描述

    • 最大似然估计函数直观理解:就是你确定参数取值,使你的模型结果跟你的样本集合最接近,这个参数的取值就是最大似然估计。
    • 最大似然估计就是让所有样本的概率总体达到最大,也就是最大程度地使样本落在我的模型估计结果周围。
    • 似然函数是表示带有参数的数学模型表达时,在已知模型的结果时,对于参数的可能性、即在此的参数概率。
    • 似然函数就是已知结果但是未知参数时,参数的可能性。

    这个“最有可能”,就是概率,就是似然函数的值,也就对应了绿色曲线的顶点。
    从图可以看到,在 y = 0 y=0 y=0的时候,当某个参数使得 y ′ y{'} y能取值为0,等价于此时损失最小,那么这个参数最有可能接近样本参数。
    绿色曲线代表的似然函数表示了 y ′ y{'} y在哪儿取值时,这个 y ′ y{'} y对应的参数概率最大。
    按我的理解:y固定, y ′ y{'} y变化,蓝色曲线代表了预测值 y ′ y{'} y偏离实际值y的程度。

    Huber’s Robust Loss

    l ( y , y ′ ) = { ∣ y − y ′ ∣ − 1 2 i f ∣ y − y ′ ∣ > 1 1 2 ( y − y ′ ) 2 o t h e r w i s e l(y,y')=

    {|yy|12if|yy|>112(yy)2otherwise
    l(y,y)={yy21ifyy>121(yy)2otherwise

    - 1 2 1 \over 2 21使得曲线能够连接

    在这里插入图片描述

    蓝色是损失函数,黄色是损失函数的导数,绿色是似然函数
    所以选择损失函数都是选择可导的。

    图片分类数据集

    MNIST数据集 [LeCun et al., 1998] 是图像分类中广泛使用的数据集之一,但作为基准数据集过于简单。 我们将使用类似但更复杂的Fashion-MNIST数据集 [Xiao et al., 2017]。

    %matplotlib inline
    import torch
    import torchvision
    from torch.utils import data
    from torchvision import transforms
    from d2l import torch as d2l
    
    d2l.use_svg_display()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    %matplotlib 只适合 jupyter notebook
    如果报错,直接import matplotlib 就好了

    读取数据集

    我们可以通过框架中的内置函数将Fashion-MNIST数据集下载并读取到内存中。

    # 通过ToTensor实例将图像数据从PIL类型变换成32位浮点数格式,
    # 并除以255使得所有像素的数值均在0到1之间
    trans = transforms.ToTensor()
    mnist_train = torchvision.datasets.FashionMNIST(
        root="../data", train=True, transform=trans, download=True)
    mnist_test = torchvision.datasets.FashionMNIST(
        root="../data", train=False, transform=trans, download=True)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    测试数据集不参与训练,用来验证模型训练的好坏。

    Fashion-MNIST由10个类别的图像组成, 每个类别由训练数据集(train dataset)中的6000张图像 和测试数据集(test dataset)中的1000张图像组成。 因此,训练集和测试集分别包含60000和10000张图像。 测试数据集不会用于训练,只用于评估模型性能。

    len(mnist_train), len(mnist_test)
    
    • 1

    (60000, 10000)

    每个输入图像的高度和宽度均为28像素。 数据集由灰度图像组成,其通道数为1。 为了简洁起见,本书将高度像素、宽度像素图像的形状记为或(,)。

    mnist_train[0][0].shape
    
    • 1

    torch.Size([1, 28, 28])

    Fashion-MNIST中包含的10个类别,分别为t-shirt(T恤)、trouser(裤子)、pullover(套衫)、dress(连衣裙)、coat(外套)、sandal(凉鞋)、shirt(衬衫)、sneaker(运动鞋)、bag(包)和ankle boot(短靴)。 以下函数用于在数字标签索引及其文本名称之间进行转换。

    def get_fashion_mnist_labels(labels):  #@save
        """返回Fashion-MNIST数据集的文本标签"""
        text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat',
                       'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot']
        return [text_labels[int(i)] for i in labels]
    
    • 1
    • 2
    • 3
    • 4
    • 5

    我们现在可以创建一个函数来可视化这些样本。

    def show_images(imgs, num_rows, num_cols, titles=None, scale=1.5):  #@save
        """绘制图像列表"""
        figsize = (num_cols * scale, num_rows * scale)
        _, axes = d2l.plt.subplots(num_rows, num_cols, figsize=figsize)
        axes = axes.flatten()
        for i, (ax, img) in enumerate(zip(axes, imgs)):
            if torch.is_tensor(img):
                # 图片张量
                ax.imshow(img.numpy())
            else:
                # PIL图片
                ax.imshow(img)
            ax.axes.get_xaxis().set_visible(False)
            ax.axes.get_yaxis().set_visible(False)
            if titles:
                ax.set_title(titles[i])
        return axes
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    以下是训练数据集中前几个样本的图像及其相应的标签。

    X, y = next(iter(data.DataLoader(mnist_train, batch_size=18)))
    show_images(X.reshape(18, 28, 28), 2, 9, titles=get_fashion_mnist_labels(y));
    
    • 1
    • 2

    在这里插入图片描述

    读取小批量

    为了使我们在读取训练集和测试集时更容易,我们使用内置的数据迭代器,而不是从零开始创建。 回顾一下,在每次迭代中,数据加载器每次都会读取一小批量数据,大小为batch_size。 通过内置数据迭代器,我们可以随机打乱了所有样本,从而无偏见地读取小批量。

    batch_size = 256
    
    def get_dataloader_workers():  #@save
        """使用4个进程来读取数据"""
        return 4
    
    train_iter = data.DataLoader(mnist_train, batch_size, shuffle=True,
                                 num_workers=get_dataloader_workers())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    我们看一下训练数据所需要的时间。

    timer = d2l.Timer()
    for X, y in train_iter:
        continue
    f'{timer.stop():.2f} sec'
    
    • 1
    • 2
    • 3
    • 4

    报错的话,把上面核数改小就好。

    整合所有组件

    现在我们定义load_data_fashion_mnist函数,用于获取和读取Fashion-MNIST数据集。 这个函数返回训练集和验证集的数据迭代器。 此外,这个函数还接受一个可选参数resize,用来将图像大小调整为另一种形状。

    def load_data_fashion_mnist(batch_size, resize=None):  #@save
        """下载Fashion-MNIST数据集,然后将其加载到内存中"""
        trans = [transforms.ToTensor()]
        if resize:
            trans.insert(0, transforms.Resize(resize))
        trans = transforms.Compose(trans)
        mnist_train = torchvision.datasets.FashionMNIST(
            root="../data", train=True, transform=trans, download=True)
        mnist_test = torchvision.datasets.FashionMNIST(
            root="../data", train=False, transform=trans, download=True)
        return (data.DataLoader(mnist_train, batch_size, shuffle=True,
                                num_workers=get_dataloader_workers()),
                data.DataLoader(mnist_test, batch_size, shuffle=False,
                                num_workers=get_dataloader_workers()))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    Softmax 回归的从零开始实现

    就像我们从零开始实现线性回归一样, softmax回归也是重要的基础,因此应该知道实现softmax回归的细节。

    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    和之前线性回归的例子一样,这里的每个样本都将用固定长度的向量表示。 原始数据集中的每个样本都是 28 ∗ 28 28 * 28 2828的图像。 在本节中,我们将展平每个图像,把它们看作长度为784的向量。 在后面的章节中,我们将讨论能够利用图像空间结构的特征, 但现在我们暂时只把每个像素位置看作一个特征。

    回想一下,在softmax回归中,我们的输出与类别一样多。 因为我们的数据集有10个类别,所以网络输出维度为10。 因此,权重将构成一个的 784 ∗ 10 784*10 78410矩阵, 偏置将构成一个 1 ∗ 10 1*10 110的行向量。 与线性回归一样,我们将使用正态分布初始化我们的权重W,偏置初始化为0。

    num_inputs = 784	# 28*28
    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)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    定义softmax 操作

    在实现softmax回归模型之前,我们简要回顾一下sum运算符如何沿着张量中的特定维度工作。 给定一个矩阵X,我们可以对所有元素求和(默认情况下。也可以只求同一个轴上的元素,即同一列(轴0)或同一行(轴1)。 如果X是一个形状为(2, 3)的张量,我们对列进行求和, 则结果将是一个具有形状(,3)的向量。 当调用sum运算符时,我们可以指定保持在原始张量的轴数,而不折叠求和的维度。 这将产生一个具有形状(1, 3)的二维张量。

    X = torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
    X.sum(0, keepdim=True), X.sum(1, keepdim=True)
    
    • 1
    • 2

    (tensor([[5., 7., 9.]]),
    tensor([[ 6.],
    [15.]]))

    实现softmax由三个步骤组成:

    1. 对每个项求幂(使用exp);

    2. 对每一行求和(小批量中每个样本是一行),得到每个样本的规范化常数;

    3. 将每一行除以其规范化常数,确保结果的和为1。

    在查看代码之前,我们回顾一下这个表达式:

    s o f t m a x ( X ) i j = e x p ( X i j ) ∑ k e x p ( X i k ) softmax(X)_{ij}={exp(X_{ij}) \over \sum_kexp(X_{ik})} softmax(X)ij=kexp(Xik)exp(Xij)

    分母或规范化常数,有时也称为配分函数(其对数称为对数-配分函数)。 该名称来自统计物理学中一个模拟粒子群分布的方程。

    def softmax(X):
        X_exp = torch.exp(X)
        partition = X_exp.sum(1, keepdim=True)
        return X_exp / partition  # 这里应用了广播机制
    
    • 1
    • 2
    • 3
    • 4

    是矩阵的原因是因为有多个一行个返回的结果,实际上是对每行的一个输出向量求softmax。

    正如你所看到的,对于任何随机输入,我们将每个元素变成一个非负数。 此外,依据概率原理,每行总和为1。

    X = torch.normal(0, 1, (2, 5))
    X_prob = softmax(X)
    X_prob, X_prob.sum(1)
    
    • 1
    • 2
    • 3

    (tensor([[0.1200, 0.1912, 0.2446, 0.2854, 0.1588],
    [0.0221, 0.6759, 0.1827, 0.1135, 0.0058]]),
    tensor([1.0000, 1.0000]))

    注意,虽然这在数学上看起来是正确的,但我们在代码实现中有点草率。 矩阵中的非常大或非常小的元素可能造成数值上溢或下溢,但我们没有采取措施来防止这点。

    定义模型

    定义softmax操作后,我们可以实现softmax回归模型。 下面的代码定义了输入如何通过网络映射到输出。 注意,将数据传递到模型之前,我们使用reshape函数将每张原始图像展平为向量。

    def net(X):
        return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)
    
    • 1
    • 2

    -1 表示这个维度的大小由 numpy 去计算,只需要固定一个维度就好。
    其实第一个维度应该等于我们的批量大小。

    定义损失函数

    交叉熵损失函数可能是深度学习中最常见的损失函数,因为目前分类问题的数量远远超过回归问题的数量。

    回顾一下,交叉熵采用真实标签的预测概率的负对数似然。 这里我们不使用Python的for循环迭代预测(这往往是低效的), 而是通过一个运算符选择所有元素。 下面,我们创建一个数据样本y_hat,其中包含2个样本在3个类别的预测概率, 以及它们对应的标签y。 有了y,我们知道在第一个样本中,第一类是正确的预测; 而在第二个样本中,第三类是正确的预测。 然后使用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]
    
    • 1
    • 2
    • 3

    tensor([0.1000, 0.5000])

    假设这里有3类,对2个样本做预测
    最有一句话可以理解成,y中的两个元素0和2,是y_hat的索引。
    得到我对真实标号的那个类的预测值是多少。

    实现交叉熵损失函数

    现在我们只需一行代码就可以实现交叉熵损失函数。

    def cross_entropy(y_hat, y):
        return - torch.log(y_hat[range(len(y_hat)), y])
    
    cross_entropy(y_hat, y)
    
    • 1
    • 2
    • 3
    • 4

    tensor([2.3026, 0.6931])

    len 是元素数量不是长度
    拿出来对应真实标号的预测值

    分类精度

    给定预测概率分布y_hat,当我们必须输出硬预测(hard prediction)时, 我们通常选择预测概率最高的类。 许多应用都要求我们做出选择。如Gmail必须将电子邮件分类为“Primary(主要邮件)”、 “Social(社交邮件)”、“Updates(更新邮件)”或“Forums(论坛邮件)”。 Gmail做分类时可能在内部估计概率,但最终它必须在类中选择一个。

    当预测与标签分类y一致时,即是正确的。 分类精度即正确预测数量与总预测数量之比。 虽然直接优化精度可能很困难(因为精度的计算不可导), 但精度通常是我们最关心的性能衡量标准,我们在训练分类器时几乎总会关注它。

    为了计算精度,我们执行以下操作。 首先,如果y_hat是矩阵,那么假定第二个维度存储每个类的预测分数。 我们使用argmax获得每行中最大元素的索引来获得预测类别。 然后我们将预测类别与真实y元素进行比较。 由于等式运算符“==”对数据类型很敏感, 因此我们将y_hat的数据类型转换为与y的数据类型一致。 结果是一个包含0(错)和1(对)的张量。 最后,我们求和会得到正确预测的数量。

    def accuracy(y_hat, y):  #@save
        """计算预测正确的数量"""
        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())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    我们将继续使用之前定义的变量y_hat和y分别作为预测的概率分布和标签。 可以看到,第一个样本的预测类别是2(该行的最大元素为0.6,索引为2),这与实际标签0不一致。 第二个样本的预测类别是2(该行的最大元素为0.5,索引为2),这与实际标签2一致。 因此,这两个样本的分类精度率为0.5。

    accuracy(y_hat, y) / len(y)
    
    • 1

    0.5

    同样,对于任意数据迭代器data_iter可访问的数据集, 我们可以评估在任意模型net的精度。

    def evaluate_accuracy(net, data_iter):  #@save
        """计算在指定数据集上模型的精度"""
        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]	# 分类正确样本数/总样本数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    评估模式,只输入后得出结果,用来评估模型正确率,不做反向传播

    这里定义一个实用程序类Accumulator,用于对多个变量进行累加。 在上面的evaluate_accuracy函数中, 我们在Accumulator实例中创建了2个变量, 分别用于存储正确预测的数量和预测的总数量。 当我们遍历数据集时,两者都将随着时间的推移而累加。

    class Accumulator:  #@save
        """在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]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    由于我们使用随机权重初始化net模型, 因此该模型的精度应接近于随机猜测。 例如在有10个类别情况下的精度为0.1。

    evaluate_accuracy(net, test_iter)
    
    • 1

    0.1568

    训练

    首先,我们定义一个函数来训练一个迭代周期。 请注意,updater是更新模型参数的常用函数,它接受批量大小作为参数。 它可以是d2l.sgd函数,也可以是框架的内置优化函数。

    def train_epoch_ch3(net, train_iter, loss, updater):  #@save
        """训练模型一个迭代周期(定义见第3章)"""
        # 将模型设置为训练模式
        if isinstance(net, torch.nn.Module):
            net.train()
        # 训练损失总和、训练准确度总和、样本数
        metric = Accumulator(3)	# 长度为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]	# loss累加 所有分类正确样本数	总样本数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在展示训练函数的实现之前,我们定义一个在动画中绘制数据的实用程序类Animator, 它能够简化其余部分的代码。

    class Animator:  #@save
        """在动画中绘制数据"""
        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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

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

    def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater):  #@save
        """训练模型"""
        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 <= 1 and train_acc > 0.7, train_acc
        assert test_acc <= 1 and test_acc > 0.7, test_acc
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

    lr = 0.1
    
    def updater(batch_size):
        return d2l.sgd([W, b], lr, batch_size)
    
    • 1
    • 2
    • 3
    • 4

    现在,我们训练模型10个迭代周期。 请注意,迭代周期(num_epochs)和学习率(lr)都是可调节的超参数。 通过更改它们的值,我们可以提高模型的分类精度。

    num_epochs = 10
    train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater)
    
    • 1
    • 2

    预测

    现在训练已经完成,我们的模型已经准备好对图像进行分类预测。 给定一系列图像,我们将比较它们的实际标签(文本输出的第一行)和模型预测(文本输出的第二行)

    def predict_ch3(net, test_iter, n=6):  #@save
        """预测标签(定义见第3章)"""
        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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述

    softmax 回归的简洁实现

    通过深度学习框架的高级API能够使实现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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    初始化模型参数

    softmax回归的输出层是一个全连接层。 因此,为了实现我们的模型, 我们只需在Sequential中添加一个带有10个输出的全连接层。 同样,在这里Sequential并不是必要的, 但它是实现深度模型的基础。 我们仍然以均值0和标准差0.01随机初始化权重。

    # 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)
    
    net.apply(init_weights);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    softmax 不会显示的来调整我们输入的性状,所以我们需要用flatten,将一个任何维度的tensor变成一个2D的tensor,其中第0维度保留,剩下的维度全部展成向量。
    第0维度是batch_size

    损失函数

    在交叉熵损失函数中传递未归一化的预测,并同时计算softmax及其对数

    loss = nn.CrossEntropyLoss(reduction='none')
    
    • 1

    优化算法

    在这里,我们使用学习率为0.1的小批量随机梯度下降作为优化算法。 这与我们在线性回归例子中的相同,这说明了优化器的普适性。

    trainer = torch.optim.SGD(net.parameters(), lr=0.1)
    
    • 1

    训练

    接下来我们调用 3.6节中 定义的训练函数来训练模型。

    num_epochs = 10
    d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
    
    • 1
    • 2
  • 相关阅读:
    小插曲 -- 使用Visual Studio Code远程连接香橙派
    Express框架
    第二章 JAVA基础语法
    【C++】内存管理
    Python - 深度学习系列36 重塑实体识别3
    进程理论和实操
    【LeetCode】二叉树题总结(持续更新)
    手把手教你CSP系列之object-src
    Easyui 常用语法-其他
    【JAVA核心知识】深度了解MySql的innodb引擎
  • 原文地址:https://blog.csdn.net/qq_51491920/article/details/125505982