深度学习系列笔记02线性回归与softmax回归

文章目录

  • 1 线性回归
  • 2 基础优化方法
    • 2.1 梯度下降
    • 2.2 小批量随机梯度下降
    • 2.3 总结:
  • 3 线性回归从零开始实行
    • 3.1 生成数据集
      • 函数笔记:
    • 3.2 读取数据集
      • 函数笔记:
    • 3.3 初始化模型参数
    • 3.4 定义模型
    • 3.5 定义损失函数
    • 3.6 定义优化算法
    • 3.7 训练
  • 4 线性回归的简洁实现
    • 4.1 生成数据集
    • 4.2 读取数据集
      • 函数笔记:
    • 4.3 定义模型
    • 4.4 初始化模型参数
    • 4.5 定义损失函数
    • 4.6 定义优化算法
    • 4.7 训练
      • 函数笔记:
  • 5 softmax回归——分类问题
    • 5.1 网络结构
    • 5.2 全连接层的参数开销
    • 5.3 softmax运算
    • 5.4 小批量样本的矢量化
  • 6 图像分类数据集
    • 6.1 读取数据集
    • 6.2 数字标签索引及其文本名称与可视化
    • 6.3 读取小批量
    • 6.4 整合所有组件
  • 7 softmax回归的从零开始实现
    • 7.1 初始化模型参数
    • 7.2 定义softmax操作
    • 7.3 定义模型
    • 7.4 定义交叉熵损失函数
        • 交叉熵理解的还不是很透彻。日后继续更新!
    • 7.5 分类准确率
      • 函数笔记:
    • 7.6 训练
      • 函数笔记:
      • 动画绘制数据的实用程序类
      • 训练函数
    • 7.8 预测
  • 8 softmax回归的简洁实现
    • 8.1 初始化模型参数
      • Sequential函数记录:
    • 8.2 重新审视softmax的实现
    • 8.3 优化算法
    • 8.4 训练

1 线性回归

深度学习系列笔记02线性回归与softmax回归_第1张图片

深度学习系列笔记02线性回归与softmax回归_第2张图片
这是一个需要仔细考虑的点。
深度学习系列笔记02线性回归与softmax回归_第3张图片
损失函数(Lost Function) 。这里的1/2,是为了求导后可以消去平方带来的系数。

线性回归步骤:
深度学习系列笔记02线性回归与softmax回归_第4张图片

深度学习系列笔记02线性回归与softmax回归_第5张图片

2 基础优化方法

2.1 梯度下降

深度学习系列笔记02线性回归与softmax回归_第6张图片
沿梯度下降的方向增加损失函数的值。
学习率(learning rate):步长的超参数(就是你需要自己设定的值)

我这篇博客已经介绍了。
机器学习笔记01_机器学习基本概念(上)

2.2 小批量随机梯度下降

然而,为了计算一次梯度要遍历所有数据,实在太不明智了。

深度学习系列笔记02线性回归与softmax回归_第7张图片

2.3 总结:

① 梯度下降通过不断沿着梯度下降的方向更新参数求解
② 小批量随机梯度下降是深度学习默认的求解算法
③ 两个重要的超参数是批量大小和超参数

3 线性回归从零开始实行

3.1 生成数据集

我们将根据带有噪声的线性模型构造一个人造数据集。我们的任务是使用这个有限样本的数据集来恢复这个模型的参数。

我们生成一个包含1000个样本的数据集,每个样本包含从标准正态分布中采样的2个特征

深度学习系列笔记02线性回归与softmax回归_第8张图片

import random
import torch
import matplotlib.pyplot as plt


def synthetic_data(w, b, num_examples):
    """生产y = Xw + b + 噪声。"""
    X = torch.normal(0, 1, (num_examples, len(w)))  # 均值为0,方差为1的随机数
    y = torch.matmul(X, w) + b  # matmul 矩阵相乘
    y += torch.normal(0, 0.01, y.shape)  # 在生成的数据中加入噪声
    return X, y.reshape((-1, 1))  # 列向量返回


true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)

函数笔记:

torch.normal(means, std, out=None)

means (Tensor,optional) – 所有分布均值
std (Tensor) – 每个元素的标准差
out (Tensor) – 可选的输出张量(指定shape)

torch.matmul(a, b)

如果是二维的矩阵相乘,那就跟平时咱们做的矩阵乘法一样;
要求第一维度相同,后两个维度能满足矩阵相乘条件。A.shape =(b,m,n);B.shape = (b,n,k)
numpy.matmul(A,B) 结果shape为(b,m,k)

可视化:

fig = plt.figure(figsize=(5, 4))  # 建立一个大小为5*4的画板

plt.scatter(features[:, 1].detach().numpy(),
            labels.detach().numpy(), s=5)

plt.show()

注意:要转化为numpy中的数组才能进行调用。

深度学习系列笔记02线性回归与softmax回归_第9张图片

3.2 读取数据集

训练模型时要对数据集进行遍历,每次抽取一小批量样本,并使用它们来更新我们的模型。 由于这个过程是训练机器学习算法的基础,所以有必要定义一个函数,该函数能打乱数据集中的样本并以小批量方式获取数据。

def data_iter(batch_size, features, labels):
    num_examples = len(features)
    indices = list(range(num_examples))
    # 样本为随机读取
    random.shuffle(indices)  # shuffle()方法将序列的所有元素随机排序
    for i in range(0, num_examples, batch_size):
        batch_indices = torch.tensor(
            indices[i: min(i + batch_size, num_examples)]
        )
        yield features[batch_indices], labels[batch_indices]

batch_size = 10

for X, y in data_iter(batch_size, features, labels):
    print(X, '\n', y)
    break

函数笔记:

python中yield的用法详解——最简单,最清晰的解释

在这里插入图片描述

3.3 初始化模型参数

w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True)
# w = torch.zeros(2, 1, requires_grad=True)
b = torch.zeros(1, requires_grad=True)

在初始化参数之后,我们的任务是更新这些参数,直到这些参数足够拟合我们的数据。

3.4 定义模型

def linreg(X, w, b):
    """线性回归模型"""
    return torch.matmul(X, w) + b

广播机制: 当我们用一个向量加一个标量时,标量会被加到向量的每个分量上。

3.5 定义损失函数

def squared_loss(y_hat, y):
    """均方损失。"""
    return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2

在实现中,我们需要将真实值y的形状转换为和预测值y_hat的形状相同。

3.6 定义优化算法

线性回归有解析解。然而,这是一本关于深度学习的书,而不是一本关于线性回归的书。 由于这本书介绍的其他模型都没有解析解,下面我们将在这里介绍小批量随机梯度下降的工作示例。

在每一步中,使用从数据集中随机抽取的一个小批量,然后根据参数计算损失的梯度。接下来,朝着减少损失的方向更新我们的参数。 下面的函数实现小批量随机梯度下降更新。该函数接受模型参数集合、学习速率和批量大小作为输入。每一步更新的大小由学习速率lr决定。 因为我们计算的损失是一个批量样本的总和,所以我们用**批量大小(batch_size)**来归一化步长,这样步长大小就不会取决于我们对批量大小的选择。

def sgd(params, lr, batch_size):
    """小批量随机梯度下降。"""
    with torch.no_grad():
        for param in params:
            param -= lr * param.grad / batch_size
            param.grad.zero_() # 清零,防止影响下一次和梯度计算

当某一变量var在函数外面已经声明时 (如var=v0),函数内部默认var为全局变量且可以访问该变量,除非在函数内部有修改变量var的行为(如重新赋值 var=v1 或者代数运算 var=var+v1 等)。在这种修改变量的情况下,变量var会被定义为局部变量并被重新分配内存,它在函数内部的变化不会影响到外部的全局变量var的值(即var=v0保持不变)。
以上规则无论变量有没有被传入函数都适用,即 f(var…) 或者 f(…)。
特殊之处在于本节sgd中使用的运算符(-=)会执行原地操作(in-place operation),也就是运算结果会赋给同一块内存。由于params本身就是全局变量,修改后的结果仍然赋给它的内存,所以变化的也就是全局变量了。如修改为 param = param - … 结果就不对了,大家可以试一试。
类似的运算符还有 +=, *=, /= … 关于 in-place operation的讲解可以参考之前的“节省内存”。

在这里插入图片描述

3.7 训练

理解这段代码至关重要,因为在整个深度学习的职业生涯中,你会一遍又一遍地看到几乎相同的训练过程。

①在每次迭代中,我们读取一小批量训练样本,并通过我们的模型来获得一组预测。
②计算完损失后,我们开始反向传播,存储每个参数的梯度。最后,我们调用优化算法SGD来更新模型参数。

深度学习系列笔记02线性回归与softmax回归_第10张图片

lr = 0.03
num_epoch = 3
net = linreg
loss = squared_loss

for epoch in range(num_epoch):
    for X, y in data_iter(batch_size, features, labels):
        l = loss(net(X, w, b), y)  # `X`和`y`的小批量损失
        # 因为`l`形状是(`batch_size`, 1),而不是一个标量。`l`中的所有元素被加到一起,
        # 并以此计算关于[`w`, `b`]的梯度
        l.sum().backward()
        sgd([w, b], lr, batch_size)
    with torch.no_grad():
        train_l = loss(net(features, w, b), labels)
        print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')

print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')

深度学习系列笔记02线性回归与softmax回归_第11张图片

4 线性回归的简洁实现

在上面我们只依赖了:(1)通过张量来进行数据存储和线性代数;(2)通过自动微分来计算梯度。

实际上,由于数据迭代器、损失函数、优化器和神经网络层很常用,现代深度学习库也为我们实现了这些组件。

4.1 生成数据集

import torch
import matplotlib.pyplot as plt
from torch.utils import data
from d2l import torch as d2l
from torch import nn


true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = d2l.synthetic_data(true_w, true_b, 1000)

fig = plt.figure(figsize=(5, 4))  # 建立一个大小为5*4的画板
plt.scatter(features[:, 1].detach().numpy(),
            labels.detach().numpy(), s=5)
plt.show()

4.2 读取数据集

我们可以调用框架中现有的API来读取数据。我们将features和labels作为API的参数传递,并在实例化数据迭代器对象时指定batch_size。此外,布尔值is_train表示是否希望数据迭代器对象在每个迭代周期内打乱数据。

def load_array(data_arrays, batch_size, is_train=True):  #@save
    """构造一个PyTorch数据迭代器。"""
    dataset = data.TensorDataset(*data_arrays)
    return data.DataLoader(dataset, batch_size, shuffle=is_train)

batch_size = 10
data_iter = load_array((features, labels), batch_size)

for i, j in enumerate(data_iter):  # enumerate将其组成一个索引序列,利用它可以同时获得索引和值
    x, y = j
    print(f'batch:{i} x:{x} y:{y}')
    
A = next(iter(data_iter))
print(A)

函数笔记:

TensorDataset

from torch.utils import data
data.TensorDataset(a,b)

该类通过每一个 tensor 的第一个维度进行索引。因此,该类中的 tensor 第一维度必须相等。

data.DataLoader(train_data, batch_size=1, shuffle=True)
import torch
from torch.utils import data

# a的形状为(4*3)
a = torch.tensor([[1, 1, 1], [2, 2, 2], [3, 3, 3], [4, 4, 4]])
# b的第一维与a相同
b = torch.tensor([1, 2, 3, 4])
train_data = data.TensorDataset(a, b)
print(train_data[0:4])

data = data.DataLoader(train_data, batch_size=1, shuffle=True)
for i, j in enumerate(data):  # enumerate将其组成一个索引序列,利用它可以同时获得索引和值
    x, y = j
    print(f'batch:{i} x:{x} y:{y}')

深度学习系列笔记02线性回归与softmax回归_第12张图片

4.3 定义模型

对于标准操作,我们可以使用框架的预定义好的层。这使我们只需关注使用哪些层来构造模型,而不必关注层的实现细节。

Sequential类为串联在一起的多个层定义了一个容器。当给定输入数据,Sequential实例将数据传入到第一层,然后将第一层的输出作为第二层的输
入,依此类推。

回顾之前的单层网络架构,这一单层被称为全连接层(fully-connected layer),因为它的每一个输入都通过矩阵-向量乘法连接到它的每个输出。
深度学习系列笔记02线性回归与softmax回归_第13张图片

我们首先定义一个模型变量net,它是一个Sequential类的实例。

# `nn` 是神经网络的缩写
from torch import nn

net = nn.Sequential(nn.Linear(2, 1))

在PyTorch中,全连接层在Linear类中定义。值得注意的是,我们将两个参数传递到nn.Linear中。第一个指定输入特征形状,即2,第二个指定输出特征形状,输出特征形状为单个标量,因此为1。

4.4 初始化模型参数

在使用net之前,我们需要初始化模型参数。 深度学习框架通常有预定义的方法来初始化参数。

net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)

4.5 定义损失函数

计算均方误差使用的是MSELoss类,也称为平方 L2 范数。默认情况下,它返回所有样本损失的平均值。

loss = nn.MSELoss()

4.6 定义优化算法

PyTorch在optim模块中实现了该算法的许多变种。当我们实例化SGD实例时,我们要指定优化的参数 (可通过net.parameters()从我们的模型中获得) 以及优化算法所需的超参数字典。小批量随机梯度下降只需要设置lr值,这里设置为0.03。

trainer = torch.optim.SGD(net.parameters(), lr=0.03)

4.7 训练

深度学习系列笔记02线性回归与softmax回归_第14张图片
计算损失——反向传播算梯度——更新参数

num_epochs = 3
for epoch in range(num_epochs):
    for X, y in data_iter:
        l = loss(net(X) ,y)
        trainer.zero_grad()
        l.backward()
        trainer.step()
    l = loss(net(features), labels)
    print(f'epoch {epoch + 1}, loss {l:f}')
    
w = net[0].weight.data
print('w的估计误差:', true_w - w.reshape(true_w.shape))
b = net[0].bias.data
print('b的估计误差:', true_b - b)

深度学习系列笔记02线性回归与softmax回归_第15张图片

函数笔记:

loss

import torch
import numpy as np

loss_fn = torch.nn.MSELoss(reduce=False, size_average=False)  # 1、返回向量
loss_fn1 = torch.nn.MSELoss(reduce=True, size_average=True)  # 2、返回平均值
a = np.array([[1, 2],
              [3, 4]])
b = np.array([[3, 5],
              [2.5, 6]])

input = torch.autograd.Variable(torch.from_numpy(a))
target = torch.autograd.Variable(torch.from_numpy(b))
loss = loss_fn(input.float(), target.float())
loss1 = loss_fn1(input.float(), target.float())
print(loss)
print(loss1)

5 softmax回归——分类问题

5.1 分类Classification

Softmax 函数及其作用(含推导)

5.1 网络结构

深度学习系列笔记02线性回归与softmax回归_第16张图片
深度学习系列笔记02线性回归与softmax回归_第17张图片
在这里插入图片描述

5.2 全连接层的参数开销

深度学习系列笔记02线性回归与softmax回归_第18张图片

5.3 softmax运算

在这里要采取的主要方法是将模型的输出视作为概率。我们将优化参数以最大化观测数据的概率。为了得到预测结果,我们将设置一个阈值,如选择具有最大概率的标签。

模型的输出 y^j 可以视为属于类 j 的概率。

要将输出视为概率,我们必须保证在任何数据上的输出都是非负的且总和为1。

我们首先对每个未归一化的预测求幂,这样可以确保输出非负。为了确保最终输出的总和为1,我们再对每个求幂后的结果除以它们的总和。

在这里插入图片描述
深度学习系列笔记02线性回归与softmax回归_第19张图片
尽管softmax是一个非线性函数,但softmax回归的输出仍然由输入特征的仿射变换决定。因此,softmax回归是一个线性模型。

5.4 小批量样本的矢量化

深度学习系列笔记02线性回归与softmax回归_第20张图片
X 中的每一行代表一个数据样本,每一列代表一个特征。

W中的每一行代表一个特征,每一列代表一个类别。

对于 O 的每一行,我们先对所有项进行幂运算,然后通过求和对它们进行标准化。

XW+b 的求和会使用广播,小批量的未归一化预测O和输出概率Y^都是形状为n×q的矩阵。
深度学习系列笔记02线性回归与softmax回归_第21张图片

6 图像分类数据集

import torch
import torchvision
from torch.utils import data
from torchvision import transforms
from d2l import torch as d2l
import matplotlib.pyplot as plt

d2l.use_svg_display() # 以SVG格式保存

6.1 读取数据集

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

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

# 通过ToTensor实例将图像数据从PIL类型变换成32位浮点数格式
# 并除以255使得所有像素的数值均在0到1之间
trans = transforms.ToTensor()
# PIL.Image或者numpy.narray数据类型转变为torch.FloatTensor类型,shape是CHW,
# 数值范围缩小为[0.0, 1.0]。
mnist_train = torchvision.datasets.FashionMNIST(
    root="P:/Project_Python/LIMU_dongshouxueshenduxuexi/classification",
    train=True, transform=trans, download=True)

mnist_test = torchvision.datasets.FashionMNIST(
    root="P:/Project_Python/LIMU_dongshouxueshenduxuexi/classification",
    train=False, transform=trans, download=True)

print(len(mnist_train), len(mnist_test))
a = mnist_train[0][0].shape
print(a)

6.2 数字标签索引及其文本名称与可视化

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]

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

def show_images(imgs, num_rows, num_cols, titles=None, scale=1.5):  # @save
    """Plot a list of images."""
    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])
    plt.show()
    return axes
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))

深度学习系列笔记02线性回归与softmax回归_第22张图片

6.3 读取小批量

为了使我们在读取训练集和测试集时更容易,我们使用内置的数据迭代器,而不是从零开始创建一个。 回顾一下,在每次迭代中,数据加载器每次都会读取一小批量数据,大小为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())
timer = d2l.Timer()
for X, y in train_iter:
    continue
print(f'{timer.stop():.2f} sec')

深度学习系列笔记02线性回归与softmax回归_第23张图片

6.4 整合所有组件

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)
    mnist_test = torchvision.datasets.FashionMNIST(
        root="../data", train=False, transform=trans)
    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()))


train_iter, test_iter = load_data_fashion_mnist(32, resize=64)
for X, y in train_iter:
    print(f'X.shape:{X.shape} X.dtype:{X.dtype}''\n'
          f'y.shape:{y.shape} y.dtype:{y.dtype}')
    break
show_images(X.reshape(32, 64, 64), 4, 8, titles=get_fashion_mnist_labels(y))

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

深度学习系列笔记02线性回归与softmax回归_第24张图片

7 softmax回归的从零开始实现

原始数据集中的每个样本都是 28×28 的图像。权重将构成一个 784×10 的矩阵,偏置将构成一个 1×10 的行向量。

7.1 初始化模型参数

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)

7.2 定义softmax操作

回想一下,softmax由三个步骤组成:
(1)对每个项求幂(使用exp);
(2)对每一行求和(小批量中每个样本是一行),得到每个样本的归一化常数;
(3)将每一行除以其归一化常数,确保结果的和为1。 在查看代码之前,让我们回顾一下这个表达式:

在这里插入图片描述

def softmax(X):
    X_exp = torch.exp(X)
    partition = X_exp.sum(1, keepdim=True)  # 对行进行求和
    return X_exp / partition

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

X(m,n)行是样本,列是特征。

7.3 定义模型

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

使用reshape函数将每张原始图像展平为向量。

7.4 定义交叉熵损失函数

一文搞懂交叉熵在机器学习中的使用

交叉熵理解的还不是很透彻。日后继续更新!

y = torch.tensor([2, 1])  # 为序号
y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])
y_hat[[0, 1], y]
#####
tensor([0.6000, 0.2000])  #第0组中的第2元素, 第一组中的第1个元素。从0开始
def cross_entropy(y_hat, y):
    return - torch.log(y_hat[range(len(y_hat)), y])

c = cross_entropy(y_hat, y)
print(c)

7.5 分类准确率

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())
print(accuracy(y_hat, y) / len(y))

给定预测概率分布y_hat,当我们必须输出硬预测(hard prediction)时,我们通常选择预测概率最高的类。

在这里插入图片描述

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

这里Accumulator是一个实用程序类,用于对多个变量进行累加。

我们在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]

函数笔记:

getitem: __ getitem __(self,key):

7.6 训练

首先,我们定义一个函数来训练一个迭代周期。请注意,updater是更新模型参数的常用函数,它接受批量大小作为参数。

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.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]

函数笔记:

isinstance:

a = 1
print(isinstance(a,int))
print(isinstance(a,float))
返回 True False

动画绘制数据的实用程序类

(了解一下)

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指定)。

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
lr = 0.1

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

num_epochs = 10
train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater)
plt.show()

深度学习系列笔记02线性回归与softmax回归_第25张图片

7.8 预测

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

def predict_ch3(net, test_iter, n=6):  #@save
    """预测标签"""
    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)
plt.show()

深度学习系列笔记02线性回归与softmax回归_第26张图片

8 softmax回归的简洁实现

import torch
from torch import nn
from d2l import torch as d2l
import matplotlib.pyplot as plt

batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)

8.1 初始化模型参数

# 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);

Sequential函数记录:

Pytorch学习之旅(8)——nn.Sequential()容器

nn.Sequential()是nn.module()的容器,用于按顺序包装一组网络层

顺序性:各个网络层之间严格按照顺序构造。 自带forward():自带的forward中,通过for循环依次执行前向传播运算。

8.2 重新审视softmax的实现

我们计算了模型的输出,然后将此输出送入交叉熵损失。从数学上讲,这是一件完全合理的事情。然而,从计算角度来看,指数可能会造成数值稳定性问题

在这里插入图片描述

oj 是未归一化的预测 o 的第 j 个元素。如果 ok 中的一些数值非常大,那么 exp(ok) 可能大于数据类型容许的最大数字(即上溢(overflow))

这将使分母或分子变为inf(无穷大),我们最后遇到的是0、inf或nan(不是数字)的 y^j 。

解决这个问题的一个技巧是,在继续softmax计算之前,先从所有 ok 中减去 max(ok) 。

在减法和归一化步骤之后,可能有些 oj 具有较大的负值。由于精度受限, exp(oj) 将有接近零的值,即下溢(underflow)。

这些值可能会四舍五入为零,使 y^j 为零,并且使得 log(y^j) 的值为-inf。

尽管我们要计算指数函数,但我们最终在计算交叉熵损失时会取它们的对数。 通过将softmax和交叉熵结合在一起,可以避免反向传播过程中可能会困扰我们的数值稳定性问题。
深度学习系列笔记02线性回归与softmax回归_第27张图片

loss = nn.CrossEntropyLoss()

8.3 优化算法

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

8.4 训练

num_epochs = 10
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
plt.show()

深度学习系列笔记02线性回归与softmax回归_第28张图片

你可能感兴趣的:(深度学习,pytorch,机器学习)