动手学深度学习
侧边栏壁纸
  • 累计撰写 16 篇文章
  • 累计收到 2 条评论

动手学深度学习

晨旭不想写程序
2024-06-02 / 0 评论 / 9 阅读 / 正在检测是否收录...

深度学习环境安装

在之前做数据分析就已经安装过anaconda,所以不安装miniconda

安装完成之后,由于国内的pip太慢,所以要更换镜像源

上海交通大学 Linux 用户组 软件源镜像服务 (sjtu.edu.cn)

然后经过一些配置之后

安装git也弄过了

国内git速度巨慢无比,加速在此GitHub Proxy 代理加速 (ghproxy.com)

数学基础学习了高等数学中的微积分模块与对于梯度的学习,链式法则,补习了线代范式内容,后续会更新数学笔记

课程环境搭建

已搭建成功

准备工作

数据操作

张量

张量表示一个由数值组成的数组,这个数组可能有多个维度。 具有一个轴的张量对应数学上的向量(vector); 具有两个轴的张量对应数学上的矩阵(matrix); 具有两个轴以上的张量没有特殊的数学名称。
一维张量 对应向量
二维张量 对应矩阵
三维以及以上不命名

重要函数

reshape

要想改变一个张量的形状而不改变元素数量和元素值,可以调用reshape函数。 例如,可以把张量x从形状为(12,)的行向量转换为形状为(3,4)的矩阵。 这个新的张量包含与转换前相同的值,但是它被看成一个3行4列的矩阵。 要重点说明一下,虽然张量的形状发生了改变,但其元素值并没有变。 注意,通过改变张量的形状,张量的大小不会改变。

x = torch.arange(12)
x
X = x.reshape(3, 4)
X
tensor([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11])
tensor([[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11]])
我们不需要通过手动指定每个维度来改变形状。 也就是说,如果我们的目标形状是(高度,宽度), 那么在知道宽度后,高度会被自动计算得出,不必我们自己做除法。 在上面的例子中,为了获得一个3行的矩阵,我们手动指定了它有3行和4列。 幸运的是,我们可以通过-1来调用此自动计算出维度的功能。 即我们可以用x.reshape(-1,4)x.reshape(3,-1)来取代x.reshape(3,4)

zeros,ones,randn

有时,我们希望使用全0、全1、其他常量,或者从特定分布中随机采样的数字来初始化矩阵。 我们可以创建一个形状为(2,3,4)的张量,其中所有元素都设置为0。代码如下:
torch.zeros((2, 3, 4))
torch.ones((2, 3, 4))
tensor([[[0., 0., 0., 0.],
         [0., 0., 0., 0.],
         [0., 0., 0., 0.]],

        [[0., 0., 0., 0.],
         [0., 0., 0., 0.],
         [0., 0., 0., 0.]]])
         
tensor([[[1., 1., 1., 1.],
         [1., 1., 1., 1.],
         [1., 1., 1., 1.]],

        [[1., 1., 1., 1.],
         [1., 1., 1., 1.],
         [1., 1., 1., 1.]]])
有时我们想通过从某个特定的概率分布中随机采样来得到张量中每个元素的值。 例如,当我们构造数组来作为神经网络中的参数时,我们通常会随机初始化参数的值。 以下代码创建一个形状为(3,4)的张量。 其中的每个元素都从均值为0、标准差为1的标准高斯分布(正态分布)中随机采样。
torch.randn(3, 4)
tensor([[ 0.7277, -1.3848, -0.2607,  0.9701],
        [-2.3290, -0.3754,  0.2457,  0.0760],
        [-1.2832, -0.3600, -0.3321,  0.8184]])

运算

张量可以直接通过运算符直接按元素计算
x = torch.tensor([1.0, 2, 4, 8])
y = torch.tensor([2, 2, 2, 2])
x + y, x - y, x * y, x / y, x ** y  
(tensor([ 3.,  4.,  6., 10.]),
 tensor([-1.,  0.,  2.,  6.]),
 tensor([ 2.,  4.,  8., 16.]),
 tensor([0.5000, 1.0000, 2.0000, 4.0000]),
 tensor([ 1.,  4., 16., 64.]))

还可以通过函数连结在一起,连结时选择参数,按照轴0还是轴1,按照轴0就是按行结合,1是按照列结合

X = torch.arange(12, dtype=torch.float32).reshape((3,4))
Y = torch.tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
torch.cat((X, Y), dim=0), torch.cat((X, Y), dim=1)
(tensor([[ 0.,  1.,  2.,  3.],
         [ 4.,  5.,  6.,  7.],
         [ 8.,  9., 10., 11.],
         [ 2.,  1.,  4.,  3.],
         [ 1.,  2.,  3.,  4.],
         [ 4.,  3.,  2.,  1.]]),
 tensor([[ 0.,  1.,  2.,  3.,  2.,  1.,  4.,  3.],
         [ 4.,  5.,  6.,  7.,  1.,  2.,  3.,  4.],
         [ 8.,  9., 10., 11.,  4.,  3.,  2.,  1.]]))

还可以通过逻辑运算符来获得特殊张量

X == Y
tensor([[False,  True, False,  True],
        [False, False, False, False],
        [False, False, False, False]])
在某些情况下,即使形状不同,我们仍然可以通过调用 广播机制(broadcasting mechanism)来执行按元素操作。 这种机制的工作方式如下(就是逐行去逐列进行计算):

1.通过适当复制元素来扩展一个或两个数组,以便在转换之后,两个张量具有相同的形状;

2.对生成的数组执行按元素操作。

a = torch.arange(3).reshape((3, 1))
b = torch.arange(2).reshape((1, 2))
a, b
a + b
(tensor([[0],
         [1],
         [2]]),
tensor([[0, 1]]))

tensor([[0, 1],
        [1, 2],
        [2, 3]])

索引跟切片与普通列表大差不差,所以不对其再进行论述

节省内存

一些操作可能会导致新结果分配新的内存,在对大量数据进行操作时,会占用很大的存储空间,故采用方法进行规避

before = id(Y)
Y = Y + X
id(Y) == before
False

综上,这样加合的习惯会带来不小的麻烦,我们采取原地操作

执行原地操作非常简单。 我们可以使用切片表示法将操作的结果分配给先前分配的数组

如果在后续计算中没有重复使用X, 我们也可以使用X[:] = X + YX += Y来减少操作的内存开销。

数据预处理

在学习中,我们要使用pandas预处理原始数据,并将原始数据转换为张量格式

首先我们无论自己手动编写,还是外界下载,要得到一个CSV格式的文件

通过pandas的read_csv函数对数据进行格式转化

pandas.read_csv(文件)

处理缺失值

在数据处理中,会有一些值由于某种原因缺失,这种情况下我们要对其进行一定的处理。通常用的有两种方法,插值法删除法, 其中插值法用一个替代值弥补缺失值,而删除法则直接忽略缺失值。 在这里,我们将考虑插值法。

例如这样的一个表

   NumRooms Alley   Price
0       NaN  Pave  127500
1       2.0   NaN  106000
2       4.0   NaN  178100
3       NaN   NaN  140000

通过位置索引iloc,我们将data分成inputsoutputs, 其中前者为data的前两列,而后者为data的最后一列。 对于inputs中缺少的数值,我们用同一列的均值替换“NaN”项。

inputs, outputs = data.iloc[:, 0:2], data.iloc[:, 2]
inputs = inputs.fillna(inputs.mean())
print(inputs)
   NumRooms Alley
0       3.0  Pave
1       2.0   NaN
2       4.0   NaN
3       3.0   NaN

对于inputs中的类别值或离散值,我们将“NaN”视为一个类别。 由于“巷子类型”(“Alley”)列只接受两种类型的类别值“Pave”和“NaN”, pandas可以自动将此列转换为两列“Alley_Pave”和“Alley_nan”。 巷子类型为“Pave”的行会将“Alley_Pave”的值设置为1,“Alley_nan”的值设置为0。 缺少巷子类型的行会将“Alley_Pave”和“Alley_nan”分别设置为0和1。

inputs = pd.get_dummies(inputs, dummy_na=True)
print(inputs)
   NumRooms  Alley_Pave  Alley_nan
0       3.0           1          0
1       2.0           0          1
2       4.0           0          1
3       3.0           0          1

现在inputsoutputs中的所有条目都是数值类型,它们可以转换为张量格式。

import torch

X, y = torch.tensor(inputs.values), torch.tensor(outputs.values)
X, y
(tensor([[3., 1., 0.],
         [2., 0., 1.],
         [4., 0., 1.],
         [3., 0., 1.]], dtype=torch.float64),
 tensor([127500, 106000, 178100, 140000]))

线性代数

数学知识
之前已经了解了表示标量向量矩阵的方法,接下来不算总结,仅为摘要书上内容

线性代数计算的实现

1.矩阵转置

.t

例子:

A = torch.arange(20).reshape(5, 4)
A
tensor([[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11],
        [12, 13, 14, 15],
        [16, 17, 18, 19]])
A.T
tensor([[ 0,  4,  8, 12, 16],
        [ 1,  5,  9, 13, 17],
        [ 2,  6, 10, 14, 18],
        [ 3,  7, 11, 15, 19]])

2.降维

.sum

对于向量,降维就是访问sum

x = torch.arange(4, dtype=torch.float32)
x, x.sum()
(tensor([0., 1., 2., 3.]), tensor(6.))

默认情况下,调用求和函数会沿所有的轴降低张量的维度,使它变为一个标量。 我们还可以指定张量沿哪一个轴来通过求和降低维度。 以矩阵为例,为了通过求和所有行的元素来降维(轴0),可以在调用函数时指定axis=0。 由于输入矩阵沿0轴降维以生成输出向量,因此输入轴0的维数在输出形状中消失。

A_sum_axis0 = A.sum(axis=0)
A_sum_axis0, A_sum_axis0.shape
(tensor([40., 45., 50., 55.]), torch.Size([4]))

指定axis=1将通过汇总所有列的元素降维(轴1)。因此,输入轴1的维数在输出形状中消失。

A_sum_axis1 = A.sum(axis=1)
A_sum_axis1, A_sum_axis1.shape
(tensor([ 6., 22., 38., 54., 70.]), torch.Size([5]))

3.平均值

.mean

A.mean()
(tensor(9.5000), tensor(9.5000))

4.非降维求和

sum_A = A.sum(axis=1, keepdims=True)
sum_A
tensor([[ 6.],
        [22.],
        [38.],
        [54.],
        [70.]])

.cumsum

A.cumsum(axis=0)
tensor([[ 0.,  1.,  2.,  3.],
        [ 4.,  6.,  8., 10.],
        [12., 15., 18., 21.],
        [24., 28., 32., 36.],
        [40., 45., 50., 55.]])

5.点积

采用按元素乘的和即可

y = torch.ones(4, dtype = torch.float32)
x, y, torch.dot(x, y)
(tensor([0., 1., 2., 3.]), tensor([1., 1., 1., 1.]), tensor(6.))
torch.sum(x * y)
tensor(6.)

6.矩阵-向量积

.mv

在代码中使用张量表示矩阵-向量积,我们使用mv函数。 当我们为矩阵A和向量x调用torch.mv(A, x)时,会执行矩阵-向量积。 注意,A的列维数(沿轴1的长度)必须与x的维数(其长度)相同。

A.shape, x.shape, torch.mv(A, x)
(torch.Size([5, 4]), torch.Size([4]), tensor([ 14.,  38.,  62.,  86., 110.]))

7.矩阵-矩阵乘法

.mm

B = torch.ones(4, 3)
torch.mm(A, B)
tensor([[ 6.,  6.,  6.],
        [22., 22., 22.],
        [38., 38., 38.],
        [54., 54., 54.],
        [70., 70., 70.]])

矩阵-矩阵乘法可以简单地称为矩阵乘法,不应与”Hadamard积(就是同位置简单相乘)”混淆。

8.范式

线性代数中最有用的一些运算符是范数(norm)。 非正式地说,向量的范数是表示一个向量有多大。 这里考虑的大小(size)概念不涉及维度,而是分量的大小。

uTools_1679494004613.pnguTools_1679494014794.pnguTools_1679494023950.png

.abs().sum()

L1范式的使用

torch.abs(u).sum()
tensor(7.)

.norm

L2范式的使用

u = torch.tensor([3.0, -4.0])
torch.norm(u)
tensor(5.)

线性回归

首先是书上基础的概念

它在回归的各种标准工具中最简单而且最流行。 线性回归基于几个简单的假设: 首先,假设自变量x和因变量y之间的关系是线性的, 即y可以表示为x中元素的加权和,这里通常允许包含观测值的一些噪声; 其次,我们假设任何噪声都比较正常,如噪声遵循正态分布。

线性模型

线性模型很简单,就是加权和,是各个参数经过加权后再加上偏置得到的值,主要是对于参数与权重的确定,而偏置是对其准确性的调整

损失函数

损失函数(loss function)能够量化目标的实际值与预测值之间的差距。 通常我们会选择非负数作为损失,且数值越小表示损失越小,完美预测时的损失为0。 回归问题中最常用的损失函数是平方误差函数。

解析解

线性回归刚好是一个很简单的优化问题。 与我们将在本书中所讲到的其他大部分模型不同,线性回归的解可以用一个公式简单地表达出来, 这类解叫作解析解(analytical solution)。

微信图片_20230330161247.jpg

微信图片_20230330142050.jpg

像线性回归这样的简单问题存在解析解,但并不是所有的问题都存在解析解。 解析解可以进行很好的数学分析,但解析解对问题的限制很严格,导致它无法广泛应用在深度学习里。

随机梯度下降

解析解并不好寻找,我们用到一种名为梯度下降(gradient descent)的方法, 这种方法几乎可以优化所有深度学习模型。 它通过不断地在损失函数递减的方向上更新参数来降低误差。

梯度下降最简单的用法是计算损失函数(数据集中所有样本的损失均值) 关于模型参数的导数(在这里也可以称为梯度)。 但实际中的执行可能会非常慢:因为在每一次更新参数之前,我们必须遍历整个数据集。 因此,我们通常会在每次需要计算更新的时候随机抽取一小批样本, 这种变体叫做小批量随机梯度下降

然后用已经学习的线性回归模型就能对目标进行预测。

向量化加速

在训练我们的模型时,我们经常希望能够同时处理整个小批量的样本(如利用小批量样本实现随机梯度下降)。 为了实现这一点,需要我们对计算进行矢量化, 从而利用线性代数库,而不是在Python中编写开销高昂的for循环。

正态分布与平方损失

正态分布与线性回归密切相关,本次研究针对于对于噪声分布的假设,服从正态分布的噪声很理想化,均值为0,似然就是对于预测值的接近,最大似然值就是对于预测值的估计

uTools_1680163627216.png

反向传播

反向传播依靠计算图实现,见深度学习入门鱼书p146

理论知识到这里

softmax回归

前面我们学习了线性回归,线性回归主要用于对于问题的预测,输出一个结果值,但问题往往不止这一种,我们每天也在处理很多分类的问题,要的结果是哪一种。所以本节学习softmax回归模型

分类问题

对于分类问题,我们要的结果是输出一个类别

统计学家很早以前就发明了一种表示分类数据的简单方法:独热编码(one-hot encoding)。 独热编码是一个向量,它的分量和类别一样多。 类别对应的分量设置为1,其他所有分量设置为0。

例如(1,0,0,)(0,1,0)(0,0,1)这三个向量分别代表三个类别

为了估计所有可能类别的条件概率,我们需要一个有多个输出的模型,每个类别对应一个输出。 为了解决线性模型的分类问题,我们需要和输出一样多的仿射函数(affine function)。 每个输出对应于它自己的仿射函数。 在我们的例子中,由于我们有4个特征和3个可能的输出类别, 我们将需要12个标量来表示权重(带下标的w), 3个标量来表示偏置(带下标的b)。 下面我们为每个输入计算三个未规范化的预测(logit):o1、o2和o3。

$$ \begin{split}\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}\end{split} $$

与线性回归一样,softmax回归也是一个单层神经网络。

运算

对于softmax的运算,我们要介绍的是softmax函数

$$ \hat{\mathbf{y}} = \mathrm{softmax}(\mathbf{o})\quad \text{其中}\quad \hat{y}_j = \frac{\exp(o_j)}{\sum_k \exp(o_k)} $$

这个公式y_hat代表的是正确的概率分布,取幂的目的是为了让数保持非负数,为了确保最终输出的概率值总和为1,我们再让每个求幂后的结果除以它们的总和。

$$ \operatorname*{argmax}_j \hat y_j = \operatorname*{argmax}_j o_j. $$

如上,我们进行求最大输出值,输出可能最大的概率。尽管softmax是一个非线性函数,但softmax回归的输出仍然由输入特征的仿射变换决定。 因此,softmax回归是一个线性模型(linear model)。

损失函数

损失函数的确定也十分重要,我们使用极大似然估计法,求对数似然来作为损失函数

$$ -\log P(\mathbf{Y} \mid \mathbf{X}) = \sum_{i=1}^n -\log P(\mathbf{y}^{(i)} \mid \mathbf^{(i)}) = \sum_{i=1}^n l(\mathbf{y}^{(i)}, \hat{\mathbf{y}}^{(i)}), $$

最后得出损失函数

$$ l(\mathbf{y}, \hat{\mathbf{y}}) = - \sum_{j=1}^q y_j \log \hat{y}_j. $$

这个损失函数通常被称为交叉熵损失,能够表示出不确定性程度,比较直观的反应分类损失,使用交叉熵还因为它有一个特性是其他损失函数不容易替代的,就是交叉熵更强烈的惩罚错误的输出。如果有非常错误的输出,它的值就会变化很大,反馈很强,并且导数更大。

损失函数的导数

寻找损失函数的导数是一个很重要的问题

$$ \begin{split}\begin{aligned} l(\mathbf{y}, \hat{\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_j o_j\\ &= \log \sum_{k=1}^q \exp(o_k) - \sum_{j=1}^q y_j o_j. \end{aligned}\end{split} $$

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

由此得出该导数,以便于后续计算

由于softmax常用于分类,对于图片的分类需要采用图像数据集,在此插入图像分类数据集的相关知识

图像分类数据集

首先引入数据集所需要的准备工作(代码用课上的)
%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()
接下来读取数据集
# 通过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)
Fashion-MNIST中包含的10个类别,分别为t-shirt(T恤)、trouser(裤子)、pullover(套衫)、dress(连衣裙)、coat(外套)、sandal(凉鞋)、shirt(衬衫)、sneaker(运动鞋)、bag(包)和ankle boot(短靴)。 以下函数用于在数字标签索引及其文本名称之间进行转换。
创建函数可视化样本
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
为了使我们在读取训练集和测试集时更容易,我们使用内置的数据迭代器,而不是从零开始创建。 回顾一下,在每次迭代中,数据加载器每次都会读取一小批量数据,大小为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())
现在我们定义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

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

实现softmax由三个步骤组成:

  1. 对每个项求幂(使用exp);
  2. 对每一行求和(小批量中每个样本是一行),得到每个样本的规范化常数;
  3. 将每一行除以其规范化常数,确保结果的和为1。
softmax函数
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])

cross_entropy(y_hat, y)
分类精度
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())
累加类
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]

实现

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]

训练函数

def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater):  #@save
    """训练模型(定义见第3章)"""
    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)

简洁实现

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)
初始化
# 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);
损失函数
loss = nn.CrossEntropyLoss(reduction='none')
优化算法
trainer = torch.optim.SGD(net.parameters(), lr=0.1)

onehot 独热

就像手写数字识别中的 0 1 0 0 0 0 0 0 0 0一样,只有正确值赋值1而其他赋值0的方式就是onehot表示

多层感知机

前面咱们使用过单层的感知机了,多层感知机就是在原有的基础上,加入隐藏层,从而克服线性隐藏层的限制,要做到这一点,最简单的方法是将许多全连接层堆叠在一起。 每一层都输出到上面的层,直到生成最后的输出。 通常缩写为MLP。要实现

数值稳定性

数值稳定性在深度学习中是十分重要的
在进行反向传播求梯度时,根据链式法则,我们知道,梯度计算的结果是有许多的矩阵与一个梯度向量的乘积,会受到数值下溢的影响,引起梯度爆炸或梯度消失,原理很简单,就是过多的概率相乘带来的结果,不稳定的梯度带来的风险很大

梯度消失

参数更新过小,导致模型无法学习

sigmoid函数就是导致梯度消失的常见原因,由于sigmoid函数是饱和函数,在输入很大或很小时其梯度都会消失。导致模型梯度被切断

梯度爆炸

参数更新过大,破坏了模型的稳定收敛

与模型消失相反,但同样让人烦恼,模型爆炸也是一种不可避免的问题

对称性

神经网络设计中的另一个问题是其参数化所固有的对称性。

在这种情况下,我们可以对第一层的权重进行重排列, 并且同样对输出层的权重进行重排列,可以获得相同的函数。

在基于梯度的迭代(例如,小批量随机梯度下降)之后, W1的所有元素仍然采用相同的值。 这样的迭代永远不会打破对称性,我们可能永远也无法实现网络的表达能力。 隐藏层的行为就好像只有一个单元。 请注意,虽然小批量随机梯度下降不会打破这种对称性,但暂退法正则化可以。

模型初始化

在模型训练中,我们想努力使训练更稳定,目标就是要让梯度值在合理的范围内。

可使用的方法有:

  • 把乘法变加法
  • 归一化
  • 合理的权重初始和激活函数

权重初始化

  • 在合理值区间里随机初始参数
  • 训练开始的时候更容易有数值不稳定

    • 远离最优解的地方损失函数表面可能很复杂
    • 最优解附近表面会比较平

Xavier初始

不能同时满足前一层与后一层的方差=1,采用折中的办法

这节,我学不懂,等以后会概率论了再说吧

只听懂了一点就是激活函数的由来
假设我有一个线性函数等于ax+b,我要实现通过这个函数,让我的均值与方差保持不变,可以求解得到a=1,b=0。由此达到relu函数为什么好用了。

检查激活函数

使用泰勒展开检查 可以发现各个激活函数的合理性

环境与分布偏移

模型的数据来源,数据精度是很重要的问题,我们在训练模型的时候一定关注这些问题,当数据分布改变时,模型部署可能会出现灾难性的失败。

分布偏移类型

0

评论 (0)

取消