动手学习深度学习 03:线性神经网络

01 线性回归

回归(regression)是能为一个或多个自变量与因变量之间关系建模的一类方法。 在自然科学和社会科学领域,回归经常用来表示输入和输出之间的关系。

在机器学习领域中的大多数任务通常都与预测(prediction)有关。 当我们想预测一个数值时,就会涉及到回归问题。

常见的例子包括:预测价格(房屋、股票等)、预测住院时间(针对住院病人等)、 预测需求(零售销量等)。 但不是所有的预测都是回归问题。 
在后面的章节中,我们将介绍分类问题。分类问题的目标是预测数据属于一组类别中的哪一个。

下面我们从“如何在美国买房子”这个案例,了解线性回归:

总结:

  • 线性回是对n维输入的加权,外加偏差
  • 使用平方损失来衡量预测值和真是值的差异
  • 线性回归有显示解
    • 显示解意味着问题比较简单,我们之后研究的大多问题比较复杂,是没有显示解的
  • 线性回归可以看作是单层神经网络

1、线性回归的基本元素

举个例子:根据房屋的面积房龄来估算房屋价格

  • 收集一个真实的数据集,包括了房屋的销售价格、面积和房龄
  • 每行数据(比如一次房屋交易相对应的数据)称为样本(sample), 也可以称为数据点(data point)或数据样本(data instance)。
  • 试图预测的目标(比如预测房屋价格)称为标签(label)或目标(target)。
  • 预测所依据的自变量(面积和房龄)称为特征(feature)或协变量(covariate)。

在这里插入图片描述

1.1、线性模型

线性假设是指目标(房屋价格)可以表示为特征(面积和房龄)的加权和

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j4ndUmIh-1664106685354)(assets/image-20220904173948729.png)]

给定一个数据集,我们的目标是寻找模型的权重w和偏置b, 使得根据模型做出的预测大体符合数据里的真实价格。

在这里插入图片描述

在开始寻找最好的模型参数(model parameters)w和b之前, 我们还需要两个东西:

(1)一种模型质量的度量方式;

(2)一种能够更新模型以提高模型预测质量的方法。

1.2、损失函数

损失函数(loss function)能够量化目标的实际值与预测值之间的差距。

1.3、解析解

线性回归的解可以用一个公式简单地表达出来, 这类解叫作解析解(analytical solution)。
在这里插入图片描述

1.4、随机梯度下降

通过不断地在损失函数递减的方向上更新参数来降低误差

梯度下降

  • 可以参考李宏毅

总结

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

1.5、用模型进行预测

给定特征估计目标的过程通常称为预测(prediction)或推断(inference)。

2、矢量化加速

在训练我们的模型时,我们经常希望能够同时处理整个小批量的样本

为了实现这一点,需要我们对计算进行矢量化, 从而利用线性代数库,而不是编写开销高昂的for循环。

3、正太分布与平方损失

pass

4、从线性回归到深度网络

pass

02 线性回归的从零开始实现

%matplotlib inline
# %matplotlib inline 模仿命令行来访问magic函数的在IPython中独有的形式,功能是可以内嵌绘图,并且可以省略掉plt.show()这一步
import random
import torch
from d2l import torch as d2l

1、生成数据集

在下面的代码中,我们生成一个包含1000个样本的数据集, 每个样本包含从标准正态分布中采样的2个特征。
在这里插入图片描述

# (1)生成数据集
"""
根据带有噪声的线性模型构造一个人造数据集,
任务是使用这个有限样本的数据集来恢复这个模型的参数。
"""
def synthetic_data(w, b, num_examples):  #@save
    """生成y=Xw+b+噪声"""
    X = torch.normal(0, 1, (num_examples, len(w)))
    y = torch.matmul(X, w) + b
    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)
"""
生成一个包含1000个样本的数据集, 每个样本包含从标准正态分布中采样的2个特征。
features中的每一行都包含一个二维数据样本, labels中的每一行都包含一维标签值(一个标量)。
"""

看一下具体数据:

print('features:', features[0],'\nlabel:', labels[0])
features: tensor([-0.1874, -0.4533]) 
label: tensor([5.3888])

通过生成第二个特征features[:, 1]labels的散点图,直观观察到两者之间的线性关系:

d2l.set_figsize()
d2l.plt.scatter(features[:, (1)].detach().numpy(), labels.detach().numpy(), 1);

在这里插入图片描述

2、读取数据集

# (2)读取数据集
"""
打乱数据集中的样本并以小批量方式获取数据。
定义一个data_iter函数, 该函数接收批量大小、特征矩阵和标签向量作为输入,生成大小为batch_size的小批量。
"""
def data_iter(batch_size, features, labels):
    num_examples = len(features)
    indices = list(range(num_examples))
    # 这些样本是随机读取的,没有特定的顺序
    random.shuffle(indices)
    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
# 读取第一个小批量数据样本并打印。 每个批量的特征维度显示批量大小和输入特征数
# 同样的,批量的标签形状与batch_size相等
for X, y in data_iter(batch_size, features, labels):
    print(X, '\n', y)
    break
tensor([[-0.1557, -1.1682],
        [-1.3068, -0.0295],
        [ 0.8605,  1.1036],
        [-2.1143, -0.0096],
        [-0.2705,  1.6564],
        [-0.0758,  0.4954],
        [-0.4310,  0.4836],
        [ 0.2151, -0.3077],
        [-0.9516, -2.5226],
        [ 0.6774,  1.8962]]) 
 tensor([[ 7.8500e+00],
        [ 1.6852e+00],
        [ 2.1753e+00],
        [ 1.5046e-03],
        [-1.9907e+00],
        [ 2.3606e+00],
        [ 1.6979e+00],
        [ 5.6878e+00],
        [ 1.0866e+01],
        [-8.9715e-01]])

3、初始化模型参数

# (3)初始化模型参数
"""
开始用小批量随机梯度下降优化我们的模型参数之前, 我们需要先有一些参数。
在下面的代码中,我们通过从均值为0、标准差为0.01的正态分布中采样随机数来初始化权重, 并将偏置初始化为0。
"""
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)

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

4、定义模型

回想一下,要计算线性模型的输出, 我们只需计算输入特征X和模型权重w的矩阵-向量乘法后加上偏置b。

# (4)定义模型
"""
将模型的输入和参数同模型的输出关联起来。
下面,X,w是一个向量,b而是一个标量。
"""
def linreg(X, w, b):  #@save
    """线性回归模型"""
    return torch.matmul(X, w) + b

5、定义损失函数

# (5)定义损失函数
"""
计算损失函数的梯度
"""
def squared_loss(y_hat, y):  #@save
    """均方损失"""
    return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2  # 将真实值y的形状转换为和预测值y_hat的形状相同

6、定义优化算法

# (6)定义优化算法
"""
在每一步中,使用从数据集中随机抽取的一个小批量,然后根据参数计算损失的梯度。 接下来,朝着减少损失的方向更新我们的参数。
"""
def sgd(params, lr, batch_size):  #@save
    """小批量随机梯度下降"""
    with torch.no_grad():
        for param in params:
            param -= lr * param.grad / batch_size
            param.grad.zero_()

7、训练

理解这段代码至关重要,因为从事深度学习后, 你会一遍又一遍地看到几乎相同的训练过程。

在这里插入图片描述

# (7)训练
lr = 0.01
num_epochs = 5
net = linreg
loss = squared_loss

for epoch in range(num_epochs):
    for X, y in data_iter(batch_size, features, labels):
        l = loss(net(X, w, b), y)  # X和y的小批量损失(net(X, w, b):y的预测值;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}')

运行结果:

epoch 1, loss 0.803853
epoch 2, loss 0.106669
epoch 3, loss 0.014265
epoch 4, loss 0.001964
epoch 5, loss 0.000310

看一下误差:

print(f'w的估计误差: {true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差: {true_b - b}')
"""
w的估计误差: tensor([-0.0004,  0.0009], grad_fn=<SubBackward0>)
b的估计误差: tensor([0.0009], grad_fn=<RsubBackward1>)
"""

8、总结

整合一下上面的代码:

可以多跑几遍下面的代码,调参,感受一下

在这里插入图片描述

import random
import torch
from d2l import torch as d2l


# 1、创建数据
def synthetic_data(w, b, num_examples):  #@save
    """生成y=Xw+b+噪声"""
    X = torch.normal(0, 1, (num_examples, len(w)))
    y = torch.matmul(X, w) + b
    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)

# 2、读取数据
def data_iter(batch_size, features, labels):
    num_examples = len(features)
    indices = list(range(num_examples))
    # 这些样本是随机读取的,没有特定的顺序
    random.shuffle(indices)
    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]

# 3、初始化模型参数
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
        
# 4、定义模型、损失函数、优化器
def linreg(X, w, b):  #@save
    """线性回归模型"""
    return torch.matmul(X, w) + b

def squared_loss(y_hat, y):  #@save
    """均方损失"""
    return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2  # 将真实值y的形状转换为和预测值y_hat的形状相同


def sgd(params, lr, batch_size):  #@save
    """小批量随机梯度下降"""
    with torch.no_grad():
        for param in params:
            param -= lr * param.grad / batch_size
            param.grad.zero_()

# 5、训练
lr = 0.01
num_epochs = 5
net = linreg
loss = squared_loss

for epoch in range(num_epochs):
    for X, y in data_iter(batch_size, features, labels):
        l = loss(net(X, w, b), y)  # X和y的小批量损失(net(X, w, b):y的预测值;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}')

运行结果展示:

epoch 1, loss 2.183826
epoch 2, loss 0.283514
epoch 3, loss 0.036978
epoch 4, loss 0.004877
epoch 5, loss 0.000684
w的估计误差: tensor([ 0.0093, -0.0181], grad_fn=<SubBackward0>)
b的估计误差: tensor([0.0286], grad_fn=<RsubBackward1>)

总结一下上面的训练过程:

  • 创建一个迭代周期(for epoch in range(num_epochs)),在每个迭代周期中

    • 使用data_iter函数遍历整个数据集

    • 在每次迭代中,我们读取一小批量训练样本,并通过我们的模型来获得一组预测

    • 计算损失

    • 反向传播,存储每个参数的梯度

    • 最后,调用优化算法sgd来更新模型参数

03 线性回归的简介实现

本节我们基于深度学习框架,简洁的实现线性回归模型

1、生成数据集

import numpy as np
import torch
from torch.utils import data
from d2l import torch as d2l


# (1)生成数据集
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = d2l.synthetic_data(true_w, true_b, 1000)

2、读取数据集

# (2)读取数据集
def load_array(data_arrays, batch_size, is_train=True):  #@save
    # 布尔值is_train表示是否希望数据迭代器对象在每个迭代周期内打乱数据
    """构造一个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)

读取并打印第一个小批量样本:

# 读取并打印第一个小批量样本:使用iter构造Python迭代器,并使用next从迭代器中获取第一项。
next(iter(data_iter))
[tensor([[-0.5345,  1.2533],
         [-0.2713,  1.3131],
         [ 0.1256,  0.4997],
         [-1.5909,  1.3276],
         [-0.5776, -1.5652],
         [-1.9792,  0.5028],
         [ 0.6800, -1.9575],
         [-0.9301,  0.4195],
         [-0.1040, -1.4376],
         [-0.5383,  0.3953]]),
 tensor([[-1.1386],
         [-0.8192],
         [ 2.7363],
         [-3.5005],
         [ 8.3576],
         [-1.4721],
         [12.2130],
         [ 0.9074],
         [ 8.8819],
         [ 1.7698]])]

3、定义模型

# (3)定义模型
# nn是神经网络的缩写
from torch import nn

"""
我们将两个参数传递到nn.Linear中。
- 第一个指定输入特征形状,即2,
- 第二个指定输出特征形状,输出特征形状为单个标量,因此为1。
"""
net = nn.Sequential(nn.Linear(2, 1))

4、初始化模型参数

# (4)初始化模型参数
"""
通过net[0]选择网络中的第一个图层,weight.data和bias.data方法访问参数,
还可以使用替换方法normal_和fill_来重写参数值。
这里,我们指定每个权重参数应该从均值为0、标准差为0.01的正态分布中随机采样,偏置参数将初始化为零。
"""
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
tensor([0.])

5、定义损失函数

# (5)定义损失函数
# 计算均方误差使用的是MSELoss类,也称为平方范数。 默认情况下,它返回所有样本损失的平均值。、
loss = nn.MSELoss()

6、定义优化算法

小批量随机梯度下降算法是一种优化神经网络的标准工具。

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

# (6)定义优化算法
"""
小批量随机梯度下降算法是一种优化神经网络的标准工具
"""
trainer = torch.optim.SGD(net.parameters(), lr=0.03)

7、训练

在每个迭代周期里,我们将完整遍历一次数据集(train_data), 不停地从中获取一个小批量的输入和相应的标签。 对于每一个小批量,我们会进行以下步骤:

  • 通过调用net(X)生成预测并计算损失l(前向传播)。
  • 通过进行反向传播来计算梯度。
  • 通过调用优化器来更新模型参数。
# (7)训练
num_epochs = 5
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}')
epoch 1, loss 0.000496
epoch 2, loss 0.000097
epoch 3, loss 0.000097
epoch 4, loss 0.000097
epoch 5, loss 0.000097

下面我们比较生成数据集的真实参数和通过有限数据训练获得的模型参数:

首先从net访问所需的层,然后读取该层的权重和偏置,我们估计得到的参数与生成数据的真实参数非常接近。

w = net[0].weight.data
print('w的估计误差:', true_w - w.reshape(true_w.shape))
b = net[0].bias.data
print('b的估计误差:', true_b - b)
w的估计误差: tensor([1.1563e-05, 6.6280e-04])
b的估计误差: tensor([0.0007])

8、总结

整合上述代码:

import numpy as np
import torch
from torch.utils import data
from d2l import torch as d2l
from torch import nn


# (1)生成数据集
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = d2l.synthetic_data(true_w, true_b, 1000)

# (2)读取数据集
def load_array(data_arrays, batch_size, is_train=True):  #@save
    # 布尔值is_train表示是否希望数据迭代器对象在每个迭代周期内打乱数据
    """构造一个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)

# (3)定义模型
net = nn.Sequential(nn.Linear(2, 1))

# (4)初始化模型参数
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)

# (5)定义损失函数
loss = nn.MSELoss()

# (6)定义优化算法
trainer = torch.optim.SGD(net.parameters(), lr=0.03)

# (7)训练
num_epochs = 5
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)

运行结果:

epoch 1, loss 0.000232
epoch 2, loss 0.000103
epoch 3, loss 0.000103
epoch 4, loss 0.000102
epoch 5, loss 0.000102
w的估计误差: tensor([ 0.0002, -0.0006])
b的估计误差: tensor([-0.0009])

小结;

  • 我们可以使用PyTorch的高级API更简洁地实现模型。
  • 在PyTorch中,data模块提供了数据处理工具,nn模块定义了大量的神经网络层和常见损失函数。
  • 我们可以通过_结尾的方法将参数替换,从而初始化参数。

9、QA

04 softmax回归

1、Softmax回归

分类问题 VS 回归问题

  • 回归估计一个连续值
  • 分类预测一个离散类别:如手写数字识别(10分类)、自然语言处理-情感分类

在这里插入图片描述

从回归过度到分类:

在这里插入图片描述

2、损失函数

介绍了。。。

05 图像分类数据集

以Fashion-MNIST数据集为例,介绍读取数据集

%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、读取数据集

# 通过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)

看一下数据量:

len(mnist_train), len(mnist_test)  # 训练集、测试集数量
(60000, 10000)

图像信息:

mnist_train[0][0].shape  # 第0个example的第0张图片
"""
torch.Size([1, 28, 28]) :
- 数据集由灰度图像组成,其通道数为1
- 图像的高度和宽度均为28像素
"""
torch.Size([1, 28, 28])

Fashion-MNIST中包含的10个类别,以下函数用于在数字标签索引及其文本名称之间进行转换。

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
    """绘制图像列表"""
    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
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));

在这里插入图片描述

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())

看一下读取训练数据所需的时间:

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

3、整合所有组件

  • 定义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()))

通过指定resize参数来测试load_data_fashion_mnist函数的图像大小调整功能:

train_iter, test_iter = load_data_fashion_mnist(32, resize=64)
for X, y in train_iter:
    print(X.shape, X.dtype, y.shape, y.dtype)
    break
torch.Size([32, 1, 64, 64]) torch.float32 torch.Size([32]) torch.int64

06 softmax回归的从零开始实现

我们引入Fashion-MNIST数据集,展开对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、初始化模型参数

# (1)初始化模型参数
# 每个样本都是28 * 28的图像,这里我们将展平每个图像,把它们看作长度为784的向量
# 在后面的章节中,我们将讨论能够利用图像“空间结构的特征”
num_inputs = 784  
# 在softmax回归中,输出与类别一样多
# 数据集有10个类别,所以网络输出维度为10
num_outputs = 10

# 因此,权重将构成一个的矩阵, 偏置将构成一个的行向量。 
# 与线性回归一样,我们将使用正态分布初始化我们的权重W,偏置初始化为0
W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True)
b = torch.zeros(num_outputs, requires_grad=True)

2、定义softmax操作

回顾一下对元素求和:

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)
(tensor([[5., 7., 9.]]),
 tensor([[ 6.],
         [15.]]))

回想一下,实现softmax由三个步骤组成:
在这里插入图片描述

# (2)定义softmax操作
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)
X_prob, X_prob.sum(1)

结果展示:

(tensor([[0.1137, 0.3174, 0.3028, 0.1085, 0.1575],
         [0.5213, 0.3160, 0.1214, 0.0339, 0.0075]]),
 tensor([1.0000, 1.0000]))

PS:这里有点草率,矩阵中的非常大或非常小的元素可能造成数值上溢或下溢,但我们没有采取措施来防止这点。

3、定义模型

实现softmax回归模型:

  • 下面的代码定义了输入如何通过网络映射到输出
  • 注意:数据传递到模型之前,用reshape函数将每张原始图像展平为向量
# (3)定义模型
def net(X):
    return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)

4、定义损失函数

这里使用交叉熵损失函数。

下面,我们创建:

  • 标签y:包含三个标签
  • 数据样本y_hat,其中包含2个样本在3个类别的预测概率
"""
有了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]
tensor([0.1000, 0.5000])

一行代码实现交叉熵损失函数:

def cross_entropy(y_hat, y):
    return - torch.log(y_hat[range(len(y_hat)), y])

cross_entropy(y_hat, y)
array([2.3025851, 0.6931472])

5、分类精度

# (5)分类精度
def accuracy(y_hat, y):  #@save
    """计算预测正确的数量"""
    if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:  # 如果y_hat是矩阵,假定第二个维度存储每个类的预测分数
        y_hat = y_hat.argmax(axis=1)  # 使用argmax获得每行中最大元素的索引来获得预测类别
    cmp = y_hat.type(y.dtype) == y
    return float(cmp.type(y.dtype).sum())
accuracy(y_hat, y) / len(y)
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]

定义一个实用程序类Accumulator,用于对多个变量进行累加。

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]
evaluate_accuracy(net, test_iter)

结果展示:

0.1043

6、训练

首先,我们定义一个函数来训练一个迭代周期

# (6)训练
def train_epoch_ch3(net, train_iter, loss, updater):  #@save
    """训练模型一个迭代周期(定义见第3章)"""
    # 将模型设置为训练模式
    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,用于可视化

# 定义一个在动画中绘制数据的实用程序类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)

接下来我们实现一个训练函数:

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 <= 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)

训练10个迭代周期:

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

结果展示:

在这里插入图片描述

7、预测

给定一系列图像,我们将比较它们的实际标签(文本输出的第一行)和模型预测(文本输出的第二行)

# (7)预测
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)

结果展示:
在这里插入图片描述

07 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、初始化模型参数

softmax回归的输出层是一个全连接层,因此,我们只需在Sequential中添加一个带有10个输出的全连接层

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

2、重新审视Softmax的实现

我们计算了模型的输出,然后将此输出送入交叉熵损失。

  • 从数学上讲,这是一件完全合理的事情。

  • 然而,从计算角度来看,指数可能会造成数值稳定性问题
    在这里插入图片描述

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

3、优化算法

使用学习率为0.1的小批量随机梯度下降作为优化算法

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

4、训练

用上一节中定义的训练函数train_ch3来训练模型

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

效果展示:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DLNovice

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值