【李沐】动手学深度学习 学习笔记
chenxiaowai_ 2024-09-03 09:35:02 阅读 97
目录
【内容介绍】动手学深度学习-基于pytorch版本【脉络梳理】预备知识数据操作数据预处理线性代数矩阵计算自动求导
线性神经网络线性回归深度学习的基础优化算法线性回归的从零开始实现线性回归的简洁实现Softmax回归损失函数图像分类数据集Softmax回归的从零开始实现Softmax回归的简洁实现
多层感知机感知机多层感知机多层感知机的从零开始实现多层感知机的简洁实现模型选择过拟合和欠拟合权重衰退暂退法(Dropout)数值稳定性模型初始化和激活函数
深度学习计算层和块参数管理自定义层读写文件
卷积神经网络从全连接层到卷积图像卷积填充和步幅多输入多输出通道池化层(汇聚层)LeNet
现代卷积神经网络深度卷积神经网络(AlexNet)使用块的网络(VGG)网络中的网络(NiN)含并行连结的网络(GoogLeNet)批量规范化(归一化)残差网络(ResNet)
计算性能深度学习硬件(CPU和GPU)深度学习硬件(TPU和其他)单机多卡并行分布式训练
计算机视觉图像增广微调物体检测和数据集锚框物体检测算法:R-CNN,SSD,YOLO单发多框检测(SSD)YOLO语义分割转置卷积全连接卷积神经网络 FCN样式迁移
循环神经网络序列模型语言模型循环神经网络
现代循环神经网络门控循环单元GRU长短期记忆网络(LSTM)深度循环神经网络双向循环神经网络编码器-解码器序列到序列学习束搜索注意力机制注意力分数使用注意力机制的seq2seq自注意力Transformer
自然语言处理:预训练BERT预训练
自然语言处理:应用BERT微调
优化算法优化算法
【内容介绍】动手学深度学习-基于pytorch版本
你好! 这是【李沐】动手学深度学习v2-基于pytorch版本的学习笔记
教材
源代码
安装教程(安装pytorch不要用pip,改成conda,pip太慢了,下载不下来)
个人推荐学习学习笔记
【脉络梳理】
预备知识
数据操作
本节代码文件在源代码文件的chapter_preliminaries/ndarray.ipynb中
创建数组
创建数组需要:
①形状
②每个元素的数据类型
③每个元素的值访问元素
①一个元素:[1,2]
②一行:[1,:]
③一列:[:,1]
④子区域:[1:3,1:] #第1到2行,第1到最后1列
⑤子区域:[::3,::2] #从第0行开始,每3行一跳,从第0列开始,每2列一跳。
数据预处理
本节代码文件在源代码文件的chapter_preliminaries/pandas.ipynb中
reshape函数
使用reshape函数后不指向同一块内存,但数据改变是同步的
<code>import torch
a=torch.arange(12)
b=a.reshape((3,4))
b[:]=2 # 改变b,a也会随之改变
print(a) # tensor([2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2])
a[:]=1 # 改变a,b也会随之改变
print(b) # tensor([[1, 1, 1, 1],[1, 1, 1, 1],[1, 1, 1, 1]])
print(id(b)==id(a)) # False
# 但a、b内存不同
print(id(a)) # 2157597781424
print(id(b)) # 2157597781424
线性代数
本节代码文件在源代码文件的chapter_preliminaries/linear-algebra.ipynb中
张量的按元素操作
标量、向量、矩阵和任意数量轴的张量(统称为张量)有一些实用的属性。 例如,你可能已经从按元素操作的定义中注意到,任何按元素的一元运算都不会改变其操作数的形状。 同样,给定具有相同形状的任意两个张量,任何按元素二元运算的结果都将是相同形状的张量。
例如,将两个相同形状的矩阵相加,会在这两个矩阵上执行按元素加法;两个矩阵的按元素乘法称为Hadamard积(Hadamard product)(数学符号
⨀
\bigodot
⨀)。 对于矩阵
A
∈
R
m
n
,
B
∈
R
m
n
\bf A\in\mathbb R^{mn},\bf B\in\mathbb R^{mn}
A∈Rmn,B∈Rmn, 其中第
i
\it i
i行和第
j
\it j
j列的元素是
b
i
j
b_{ij}
bij。 矩阵
A
\bf A
A和
B
\bf B
B的Hadamard积为
<code>import torch
A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
B = A.clone() # 通过分配新内存,将A的一个副本分配给B
print(A)
#tensor([[ 0., 1., 2., 3.],[ 4., 5., 6., 7.],[ 8., 9., 10., 11.],[12., 13., 14., 15.],[16., 17., 18., 19.]])
print(A+B)
#tensor([[ 0., 2., 4., 6.],[ 8., 10., 12., 14.],[ 16., 18., 20., 22.],[24., 26., 28., 30.],[32., 34., 36., 38.]])
print(A*B)
#tensor([[0., 1., 4., 9.],[16., 25., 36., 49.],[64., 81., 100., 121.],[144., 169., 196., 225.],[256., 289., 324., 361.]])
降维求和
①原始shape:[5,4]
· axis=0 sum:[ 4 ]
· axis=1 sum:[ 5 ]
②原始shape:[2,5,4]
· axis=1 sum:[2,4]
· axis=2 sum:[2,5]
· axis=[1,2] sum:[ 4 ]非降维求和
原始shape:[2,5,4] 参数 keepdims=True
· axis=1 sum:[2,1,4]
· axis=1 sum:[2,1,1]
矩阵计算
本节代码文件在源代码文件的chapter_preliminaries/calculus.ipynb中
将导数拓展到向量
标量对列向量求导
其中,
a
不是关于
x
的函数,
0
和
1
是向量
;
其中,\it a不是关于\bf x的函数,\color{black} 0和\bf 1是向量;
其中,a不是关于x的函数,0和1是向量;
列向量对列向量求导
结果是矩阵
样例:
x
∈
R
n
,
y
∈
R
m
,
∂
y
∂
x
∈
R
m
n
;
a
,
a
和
A
不是关于
x
的函数,
0
和
I
是矩阵
;
\bf x\in\mathbb R^{n} ,\bf y\in\mathbb R^{m},\frac{\partial\bf y}{\partial\bf x}\in\mathbb R^{mn};\it a,\bf a和\bf A不是关于\bf x的函数,\color{black} 0和\bf I是矩阵;
x∈Rn,y∈Rm,∂x∂y∈Rmn;a,a和A不是关于x的函数,0和I是矩阵;
将导数拓展到矩阵
自动求导
本节代码文件在源代码文件的chapter_preliminaries/autograd.ipynb中
向量链式法则
样例:
自动求导的两种模式
反向累积模式
样例:
正向累积与反向累积复杂度比较
正向累积内存复杂度为O(1),反向累积内存复杂度为O(n);但神经网络通常不会用正向累积,因为正向累积每计算一个变量的梯度都需要扫一遍,计算复杂度太高。
线性神经网络
线性回归
本节代码文件在源代码文件的chapter_linear-networks/linear-regression.ipynb中
线性模型
线性模型可以看做是单层神经网络:
衡量预估质量(损失函数):
训练数据
参数学习
显示解:
总结
深度学习的基础优化算法
梯度下降
通过不断地在损失函数递减的方向上更新参数来降低误差。
学习率不能太大也不能太小:
小批量随机梯度下降
批量大小不能太大也不能太小:
总结
线性回归的从零开始实现
本节代码文件在源代码文件的chapter_linear-networks/linear-regression-scratch.ipynb中
实现流程
其中,定义模型包括定义损失函数和定义优化算法
线性回归的简洁实现
本节代码文件在源代码文件的chapter_linear-networks/linear-regression-concise.ipynb中
简洁实现是指通过使用深度学习框架来实现线性回归模型,具体流程与从零开始实现大体相同,不过一些常用函数不需要我们自己写了(直接导库,用别人写好的)
实现流程
Softmax回归
本节代码文件在源代码文件的chapter_linear-networks/softmax-regression.ipynb中
回归vs分类(从回归到多类分类)
回归估计一个连续值;分类预测一个离散类别。
从回归到多类分类 — 均方损失
从回归到多类分类 — 无校验比例
从回归到多类分类 — 校验比例
Softmax和交叉熵损失
总结
损失函数
本节代码文件在源代码文件的chapter_linear-networks/softmax-regression.ipynb中
损失函数用来衡量预测值与真实值之间的区别,是机器学习里非常重要的概念。下面介绍三种常用的损失函数。
①L2 Loss
l
(
y
,
y
′
)
=
∣
y
−
y
′
∣
\it l(y,y') = \mid y-y' \mid
l(y,y′)=∣y−y′∣
蓝色曲线:表示当y=0时,变换预测值y’。
绿色曲线:表示似然函数。
橙色曲线:表示损失函数的梯度,可以发现,当y与y’相差较大的时候,梯度的绝对值也较大。②L1 Loss
l
(
y
,
y
′
)
=
1
2
(
y
−
y
′
)
2
\it l(y,y') = \frac{1}{2} ( y-y')^2
l(y,y′)=21(y−y′)2
蓝色曲线:表示当y=0时,变换预测值y’。
绿色曲线:表示似然函数。
橙色曲线:表示损失函数的梯度,可以发现,当y’>0时,导数为1,当y’<0时,导数为-1。③Huber’s Robust Loss(鲁棒损失)
蓝色曲线:表示当y=0时,变换预测值y’。
绿色曲线:表示似然函数。
橙色曲线:表示损失函数的梯度。
图像分类数据集
本节代码文件在源代码文件的chapter_linear-networks/image-classification-dataset.ipynb中
MNIST数据集
是图像分类中广泛使用的数据集之一,但作为基准数据集过于简单。 我们将使用类似但更复杂的Fashion-MNIST数据集。Fashion-MNIST数据集
①Fashion-MNIST由10个类别的图像组成,分别为t-shirt(T恤)、trouser(裤子)、pullover(套衫)、dress(连衣裙)、coat(外套)、sandal(凉鞋)、shirt(衬衫)、sneaker(运动鞋)、bag(包)和ankle boot(短靴)。
② 每个类别由训练数据集(train dataset)中的6000张图像 和测试数据集(test dataset)中的1000张图像组成。
③ 每个输入图像的高度和宽度均为28像素。 数据集由灰度图像组成,其通道数为1。
Softmax回归的从零开始实现
本节代码文件在源代码文件的chapter_linear-networks/softmax-regression-scratch.ipynb中
实现流程
我们使用Fashion-MNIST数据集, 并设置数据迭代器的批量大小为256。每个样本都将用固定长度的向量表示。 原始数据集中的每个样本都是 28×28 的图像。 在本节中,我们将展平每个图像,把它们看作长度为784的向量。 在后面的章节中,我们将讨论能够利用图像空间结构的特征, 但现在我们暂时只把每个像素位置看作一个特征。
分类精度即正确预测数量与总预测数量之比
Softmax回归的简洁实现
本节代码文件在源代码文件的chapter_linear-networks/softmax-regression-concise.ipynb中
通过深度学习框架的高级API也能更方便地实现softmax回归模型。
实现流程
本节 继续使用Fashion-MNIST数据集,并保持批量大小为256。
多层感知机
感知机
感知机
感知机与回归和Softmax回归的区别
感知机是二分类(1或-1),而回归的输出是实数,Softmax回归的输出是概率。训练感知机
收敛定理
XOR问题
总结
多层感知机
本节代码文件在源代码文件的chapter_multilayer-perceptrons/mlp.ipynb中
学习XOR
单隐藏层
隐藏层大小是超参数(输入和输出大小由数据决定,输出大小人为决定。)
单隐藏层 — 单分类
只有一个输出,即输出是标量。
Q:为什么需要非线性的激活函数?(σ(x)不可以等于x,也不可以等于nx)
A: 如果激活函数是线性的,那么单隐藏层的多层感知机就变成了最简单的线性模型。
激活函数
①Sigmoid 激活函数
②Tanh 激活函数
③ReLU 激活函数
单隐藏层 — 多类分类
多隐藏层
总结
多层感知机的从零开始实现
本节代码文件在源代码文件的chapter_multilayer-perceptrons/mlp-scratch.ipynb中
实现流程
我们实现一个具有单隐藏层的多层感知机, 它包含256个隐藏单元。 注意,我们可以将这两个变量都视为超参数。 通常,我们选择2的若干次幂作为层的宽度。 因为内存在硬件中的分配和寻址方式,这么做往往可以在计算上更高效。
多层感知机的简洁实现
本节代码文件在源代码文件的chapter_multilayer-perceptrons/mlp-concise.ipynb中
与softmax回归的简洁实现相比
唯一的区别是我们添加了2个全连接层(之前我们只添加了1个全连接层)。第一层是隐藏层,它包含256个隐藏单元,并使用了ReLU激活函数。第二层是输出层。
训练过程的实现与我们实现softmax回归时完全相同。
模型选择
本节代码文件在源代码文件的chapter_multilayer-perceptrons/underfit-overfit.ipynb中
训练误差和泛化误差
验证数据集和测试数据集
K-折交叉验证
总结
过拟合和欠拟合
本节代码文件在源代码文件的chapter_multilayer-perceptrons/underfit-overfit.ipynb中
过拟合和欠拟合
数据复杂度
模型容量
模型容量的影响
估计模型容量
其中,d+1中的1是偏移,m是隐藏层大小,k是分类的类别数VC 维
线性分类器的 VC 维
VC 维的用处
总结
权重衰退
本节代码文件在源代码文件的chapter_multilayer-perceptrons/weight-decay.ipynb中
权重衰退是一种常见的处理过拟合(模型复杂度过高)的方法。
使用均方范数作为硬性限制
使用均方范数作为柔性限制
演示对最优解的影响
参数更新法则
总结
暂退法(Dropout)
本节代码文件在源代码文件的chapter_multilayer-perceptrons/dropout.ipynb中
暂退法(丢弃法)的动机
无偏差的加入噪音
使用丢弃法
推理中的丢弃法
总结
数值稳定性
本节代码文件在源代码文件的chapter_multilayer-perceptrons/numerical-stability-and-init.ipynb中
神经网络的梯度
其中,矩阵乘法容易带来梯度爆炸和梯度消失的问题数值稳定性的常见两个问题
例子:MLP
↑ ↑ ↑ 此时,就会造成梯度爆炸
↑ ↑ ↑ 此时,就会造成梯度消失梯度爆炸的问题
梯度消失的问题
总结
模型初始化和激活函数
本节代码文件在源代码文件的chapter_multilayer-perceptrons/numerical-stability-and-init.ipynb中
让训练更加稳定
让每层的方差是个常数
权重初始化
例子:MLP
①假设没有激活函数:
正向方差:
反向均值和方差:
②假设线性的激活函数:
正向:
反向:
Xavier 初始
Xavier 是一种常见的权重初始化方法
检查常用激活函数
总结
深度学习计算
层和块
本节代码文件在源代码文件的chapter_deep-learning-computation/model-construction.ipynb中
块的组成
块(block)可以描述单个层、由多个层组成的组件或整个模型本身。 使用块进行抽象的一个好处是可以将一些块组合成更大的组件, 这一过程通常是递归的。
从编程的角度来看,块由类(class)表示。 它的任何子类都必须定义一个将其输入转换为输出的前向传播函数, 并且必须存储任何必需的参数。 注意,有些块不需要任何参数。 最后,为了计算梯度,块必须具有反向传播函数。 在定义我们自己的块时,由于自动微分提供了一些后端实现,我们只需要考虑前向传播函数和必需的参数。
块需要提供的基本功能
①将输入数据作为其前向传播函数的参数。
②通过前向传播函数来生成输出。注:输出的形状可能与输入的形状不同。
③计算其输出关于输入的梯度,可通过其反向传播函数进行访问。通常这是自动发生的。
④存储和访问前向传播计算所需的参数。
⑤根据需要初始化模型参数。
自定义块
在下面的代码片段中,我们从零开始编写一个块。 它包含一个多层感知机,其具有256个隐藏单元的隐藏层和一个10维输出层。 注意,下面的MLP类继承了表示块的类。我们的实现只需要提供我们自己的构造函数(Python中的__init__函数)和前向传播函数。
<code>class MLP(nn.Module):
# 用模型参数声明层。这里,我们声明两个全连接的层
def __init__(self):
# 调用MLP的父类Module的构造函数来执行必要的初始化。
# 这样,在类实例化时也可以指定其他函数参数,例如模型参数params(稍后将介绍)
super().__init__()
self.hidden = nn.Linear(20, 256) # 隐藏层
self.out = nn.Linear(256, 10) # 输出层
# 定义模型的前向传播,即如何根据输入X返回所需的模型输出
def forward(self, X):
# 注意,这里我们使用ReLU的函数版本,其在nn.functional模块中定义。
return self.out(F.relu(self.hidden(X)))
注意一些关键细节: 首先,我们定制的__init__函数通过super().init() 调用父类的__init__函数, 省去了重复编写模版代码的痛苦。 然后,我们实例化两个全连接层, 分别为self.hidden和self.out。 注意,除非我们实现一个新的运算符, 否则我们不必担心反向传播函数或参数初始化, 系统将自动生成这些。
顺序块
回想一下Sequential的设计是为了把其他模块串起来。 为了构建我们自己的简化的MySequential, 我们只需要定义两个关键函数:
①一种将块逐个追加到列表中的函数。
②一种前向传播函数,用于将输入按追加块的顺序传递给块组成的“链条”。
下面的MySequential类提供了与默认Sequential类相同的功能:
class MySequential(nn.Module):
def __init__(self, *args):
super().__init__()
for idx, module in enumerate(args):
# 这里,module是Module子类的一个实例。我们把它保存在'Module'类的成员
# 变量_modules中。_module的类型是OrderedDict
self._modules[str(idx)] = module
def forward(self, X):
# OrderedDict保证了按照成员添加的顺序遍历它们
for block in self._modules.values():
X = block(X)
return X
_modules:__init__函数将每个模块逐个添加到有序字典_modules中,_modules的主要优点是: 在模块的参数初始化过程中, 系统知道在_modules字典中查找需要初始化参数的子块。
参数管理
本节代码文件在源代码文件的chapter_deep-learning-computation/parameters.ipynb中
参数访问
为方便解释,先定义如下神经网络:
import torch
from torch import nn
net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 1))
X = torch.rand(size=(2, 4))
net(X)
我们从已有模型中访问参数。 当通过Sequential类定义模型时, 我们可以通过索引来访问模型的任意层。 这就像模型是一个列表一样,每层的参数都在其属性中。 如下所示,我们可以检查第二个全连接层的参数。
print(net[2].state_dict())
#OrderedDict([('weight', tensor([[ 0.0251, -0.2952, -0.1204, 0.3436, -0.3450, -0.0372, 0.0462, 0.2307]])), ('bias', tensor([0.2871]))])
提取目标参数
从第二个全连接层(即第三个神经网络层)提取偏置, 提取后返回的是一个参数类实例,并进一步访问该参数的值:
print(type(net[2].bias))
#<class 'torch.nn.parameter.Parameter'>
print(net[2].bias)
#Parameter containing:
#tensor([0.2871], requires_grad=True)
print(net[2].bias.data)
#tensor([0.2871])
参数是复合的对象,包含值、梯度和额外信息。 这就是我们需要显式参数值的原因。 除了值之外,我们还可以访问每个参数的梯度。 在上面这个网络中,由于我们还没有调用反向传播,所以参数的梯度处于初始状态。
print(net[2].weight.grad == None)
#True
一次性访问所有参数
当我们需要对所有参数执行操作时,逐个访问它们可能会很麻烦。 当我们处理更复杂的块(例如,嵌套块)时,情况可能会变得特别复杂, 因为我们需要递归整个树来提取每个子块的参数。 下面,我们将通过演示来比较访问第一个全连接层的参数和访问所有层:
print(*[(name, param.shape) for name, param in net[0].named_parameters()])
#('weight', torch.Size([8, 4])) ('bias', torch.Size([8]))
print(*[(name, param.shape) for name, param in net.named_parameters()])
#('0.weight', torch.Size([8, 4])) ('0.bias', torch.Size([8])) ('2.weight', torch.Size([1, 8])) ('2.bias', torch.Size([1]))
这为我们提供了另一种访问网络参数的方式,如下所示:
net.state_dict()['2.bias'].data
#tensor([0.2871])
从嵌套块收集参数
我们首先定义一个生成块的函数(可以说是“块工厂”),然后将这些块组合到更大的块中。
def block1():
return nn.Sequential(nn.Linear(4, 8), nn.ReLU(),
nn.Linear(8, 4), nn.ReLU())
def block2():
net = nn.Sequential()
for i in range(4):
# 在这里嵌套
net.add_module(f'block { i}', block1())
return net
rgnet = nn.Sequential(block2(), nn.Linear(4, 1))
rgnet(X)
#tensor([[0.1713],
# [0.1713]], grad_fn=<AddmmBackward0>)
设计了网络后,我们看看它是如何工作的:
print(rgnet)
'''
Sequential(
(0): Sequential(
(block 0): Sequential(
(0): Linear(in_features=4, out_features=8, bias=True)
(1): ReLU()
(2): Linear(in_features=8, out_features=4, bias=True)
(3): ReLU()
)
(block 1): Sequential(
(0): Linear(in_features=4, out_features=8, bias=True)
(1): ReLU()
(2): Linear(in_features=8, out_features=4, bias=True)
(3): ReLU()
)
(block 2): Sequential(
(0): Linear(in_features=4, out_features=8, bias=True)
(1): ReLU()
(2): Linear(in_features=8, out_features=4, bias=True)
(3): ReLU()
)
(block 3): Sequential(
(0): Linear(in_features=4, out_features=8, bias=True)
(1): ReLU()
(2): Linear(in_features=8, out_features=4, bias=True)
(3): ReLU()
)
)
(1): Linear(in_features=4, out_features=1, bias=True)
)
'''
因为层是分层嵌套的,所以我们也可以像通过嵌套列表索引一样访问它们。 下面,我们访问第一个主要的块中、第二个子块的第一层的偏置项:
rgnet[0][1][0].bias.data
#tensor([-0.0444, -0.4451, -0.4149, 0.0549, -0.0969, 0.2053, -0.2514, 0.0220])
参数初始化
深度学习框架提供默认随机初始化, 也允许我们创建自定义初始化方法, 满足我们通过其他规则实现初始化权重。默认情况下,PyTorch会根据一个范围均匀地初始化权重和偏置矩阵, 这个范围是根据输入和输出维度计算出的。 PyTorch的nn.init模块提供了多种预置初始化方法。
①内置初始化
下面的代码将所有权重参数初始化为标准差为0.01的高斯随机变量, 且将偏置参数设置为0。
def init_normal(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, mean=0, std=0.01)
nn.init.zeros_(m.bias)
net.apply(init_normal)
net[0].weight.data[0], net[0].bias.data[0]
#(tensor([-0.0017, 0.0232, -0.0026, 0.0026]), tensor(0.))
我们还可以将所有参数初始化为给定的常数,比如初始化为1:
def init_constant(m):
if type(m) == nn.Linear:
nn.init.constant_(m.weight, 1)
nn.init.zeros_(m.bias)
net.apply(init_constant)
net[0].weight.data[0], net[0].bias.data[0]
#(tensor([1., 1., 1., 1.]), tensor(0.))
我们还可以对某些块应用不同的初始化方法。 例如,下面我们使用Xavier初始化方法初始化第一个神经网络层, 然后将第三个神经网络层初始化为常量值42:
def xavier(m):
if type(m) == nn.Linear:
nn.init.xavier_uniform_(m.weight)
def init_42(m):
if type(m) == nn.Linear:
nn.init.constant_(m.weight, 42)
net[0].apply(xavier)
net[2].apply(init_42)
print(net[0].weight.data[0])
#tensor([-0.4645, 0.0062, -0.5186, 0.3513])
print(net[2].weight.data)
#tensor([[42., 42., 42., 42., 42., 42., 42., 42.]])
②自定义初始化
有时,深度学习框架没有提供我们需要的初始化方法。 在下面的例子中,我们使用以下的分布为任意权重参数 𝑤 定义初始化方法:
w
∽
{
U
(
5
,
10
)
,
可能性1/4
0
,
可能性1/2
U
(
−
10
,
−
5
)
,
可能性1/4
w \backsim \begin{cases} U(5,10), & \text{可能性1/4} \\ 0, & \text{可能性1/2} \\ U(-10,-5), & \text{可能性1/4} \\ \end{cases}
w∽⎩
⎨
⎧U(5,10),0,U(−10,−5),可能性1/4可能性1/2可能性1/4
def my_init(m):
if type(m) == nn.Linear:
print("Init", *[(name, param.shape)
for name, param in m.named_parameters()][0])
nn.init.uniform_(m.weight, -10, 10)
m.weight.data *= m.weight.data.abs() >= 5
'''
Init weight torch.Size([8, 4])
Init weight torch.Size([1, 8])
'''
net.apply(my_init)
net[0].weight[:2]
'''
tensor([[ 8.8025, 6.4078, 0.0000, -8.4598],
[-0.0000, 9.0582, 8.8258, 7.4997]], grad_fn=<SliceBackward0>)
'''
注意,我们始终可以直接设置参数:
net[0].weight.data[:] += 1
net[0].weight.data[0, 0] = 42
net[0].weight.data[0]
#tensor([42.0000, 7.4078, 1.0000, -7.4598])
参数绑定
有时我们希望在多个层间共享参数: 我们可以定义一个稠密层,然后使用它的参数来设置另一个层的参数。
# 我们需要给共享层一个名称,以便可以引用它的参数
shared = nn.Linear(8, 8)
net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(),
shared, nn.ReLU(),
shared, nn.ReLU(),
nn.Linear(8, 1))
net(X)
# 检查参数是否相同
print(net[2].weight.data[0] == net[4].weight.data[0])
'''
tensor([True, True, True, True, True, True, True, True])
'''
net[2].weight.data[0, 0] = 100
# 确保它们实际上是同一个对象,而不只是有相同的值
print(net[2].weight.data[0] == net[4].weight.data[0])
'''
tensor([True, True, True, True, True, True, True, True])
'''
这个例子表明第三个和第五个神经网络层的参数是绑定的。 它们不仅值相等,而且由相同的张量表示。 因此,如果我们改变其中一个参数,另一个参数也会改变。 你可能会思考:当参数绑定时,梯度会发生什么情况? 答案是由于模型参数包含梯度,因此在反向传播期间第二个隐藏层 (即第三个神经网络层)和第三个隐藏层(即第五个神经网络层)的梯度会加在一起。
自定义层
本节代码文件在源代码文件的chapter_deep-learning-computation/custom-layer.ipynb中
不带参数的层
下面的CenteredLayer类要从其输入中减去均值。 要构建它,我们只需继承基础层类并实现前向传播功能。
import torch
import torch.nn.functional as F
from torch import nn
class CenteredLayer(nn.Module):
def __init__(self):
super().__init__()
def forward(self, X):
return X - X.mean()
让我们向该层提供一些数据,验证它是否能按预期工作。
layer = CenteredLayer()
layer(torch.FloatTensor([1, 2, 3, 4, 5]))
#tensor([-2., -1., 0., 1., 2.])
我们可以将层作为组件合并到更复杂的模型中,比如:
net = nn.Sequential(nn.Linear(8, 128), CenteredLayer())
带参数的层
下面我们定义具有参数的层, 这些参数可以通过训练进行调整。 我们可以使用内置函数来创建参数,这些函数提供一些基本的管理功能。 比如管理访问、初始化、共享、保存和加载模型参数。 这样做的好处之一是:我们不需要为每个自定义层编写自定义的序列化程序。
现在,让我们实现自定义版本的全连接层。该层需要两个参数,一个用于表示权重,另一个用于表示偏置项。 在此实现中,我们使用修正线性单元作为激活函数。该层需要输入参数:in_units和units,分别表示输入数(输入维度)和输出数(输出维度)。
class MyLinear(nn.Module):
def __init__(self, in_units, units):
super().__init__()
self.weight = nn.Parameter(torch.randn(in_units, units))
self.bias = nn.Parameter(torch.randn(units,))
def forward(self, X):
linear = torch.matmul(X, self.weight.data) + self.bias.data
return F.relu(linear)
linear = MyLinear(5, 3)
linear.weight
'''
Parameter containing:
tensor([[ 1.9054, -3.4102, -0.9792],
[ 1.5522, 0.8707, 0.6481],
[ 1.0974, 0.2568, 0.4034],
[ 0.1416, -1.1389, 0.5875],
[-0.7209, 0.4432, 0.1222]], requires_grad=True)
'''
我们可以使用自定义层直接执行前向传播计算:
linear(torch.rand(2, 5))
'''
tensor([[2.4784, 0.0000, 0.8991],
[3.6132, 0.0000, 1.1160]])
'''
我们还可以使用自定义层构建模型,就像使用内置的全连接层一样使用自定义层:
net = nn.Sequential(MyLinear(64, 8), MyLinear(8, 1))
net(torch.rand(2, 64))
'''
tensor([[0.],
[0.]])
'''
读写文件
本节代码文件在源代码文件的chapter_deep-learning-computation/read-write.ipynb中
有时我们希望保存训练的模型, 以备将来在各种环境中使用(比如在部署中进行预测)。 此外,当运行一个耗时较长的训练过程时, 最佳的做法是定期保存中间结果, 以确保在服务器电源被不小心断掉时,我们不会损失几天的计算结果。
加载和保存张量
对于单个张量,我们可以直接调用load和save函数分别读写它们。 这两个函数都要求我们提供一个名称,save要求将要保存的变量作为输入。
import torch
from torch import nn
from torch.nn import functional as F
x = torch.arange(4)
torch.save(x, 'x-file')
我们现在可以将存储在文件中的数据读回内存。
x2 = torch.load('x-file')
x2
#tensor([0, 1, 2, 3])
我们可以存储一个张量列表,然后把它们读回内存。
y = torch.zeros(4)
torch.save([x, y],'x-files')
x2, y2 = torch.load('x-files')
(x2, y2)
#(tensor([0, 1, 2, 3]), tensor([0., 0., 0., 0.]))
我们甚至可以写入或读取从字符串映射到张量的字典。 当我们要读取或写入模型中的所有权重时,这很方便。
mydict = { 'x': x, 'y': y}
torch.save(mydict, 'mydict')
mydict2 = torch.load('mydict')
mydict2
#{'x': tensor([0, 1, 2, 3]), 'y': tensor([0., 0., 0., 0.])}
加载和保存模型参数
保存单个权重向量(或其他张量)确实有用, 但是如果我们想保存整个模型,并在以后加载它们, 单独保存每个向量则会变得很麻烦。 毕竟,我们可能有数百个参数散布在各处。 因此,深度学习框架提供了内置函数来保存和加载整个网络。 需要注意的一个重要细节是,这将保存模型的参数而不是保存整个模型。 例如,如果我们有一个3层多层感知机,我们需要单独指定架构。 因为模型本身可以包含任意代码,所以模型本身难以序列化。 因此,为了恢复模型,我们需要用代码生成架构, 然后从磁盘加载参数。 让我们从熟悉的多层感知机开始尝试一下。
class MLP(nn.Module):
def __init__(self):
super().__init__()
self.hidden = nn.Linear(20, 256)
self.output = nn.Linear(256, 10)
def forward(self, x):
return self.output(F.relu(self.hidden(x)))
net = MLP()
X = torch.randn(size=(2, 20))
Y = net(X)
接下来,我们将模型的参数存储在一个叫做“mlp.params”的文件中。
torch.save(net.state_dict(), 'mlp.params')
为了恢复模型,我们实例化了原始多层感知机模型的一个备份。 这里我们不需要随机初始化模型参数,而是直接读取文件中存储的参数。
clone = MLP()
clone.load_state_dict(torch.load('mlp.params'))
clone.eval()
'''
MLP(
(hidden): Linear(in_features=20, out_features=256, bias=True)
(output): Linear(in_features=256, out_features=10, bias=True)
)
'''
由于两个实例具有相同的模型参数,在输入相同的X时, 两个实例的计算结果应该相同。 让我们来验证一下。
Y_clone = clone(X)
Y_clone == Y
'''
tensor([[True, True, True, True, True, True, True, True, True, True],
[True, True, True, True, True, True, True, True, True, True]])
'''
卷积神经网络
从全连接层到卷积
本节代码文件在源代码文件的chapter_convolutional-neural-networks/why-conv.ipynb中
重新考察全连接层
平移不变性
不管检测对象出现在图像中的哪个位置,神经网络的前面几层应该对相同的图像区域具有相似的反应,即为“平移不变性”。
局部性
全连接层与卷积层的关系
图像卷积
本节代码文件在源代码文件的chapter_convolutional-neural-networks/conv-layer.ipynb中
互相关运算
在卷积层中,输入张量和核张量通过互相关运算产生输出张量。
不同的卷积核可以带来不同的效果:
交叉相关vs卷积
一维和三维交叉相关
一维和三维交叉相关
填充和步幅
本节代码文件在源代码文件的chapter_convolutional-neural-networks/padding-and-strides.ipynb中
填充和步幅是卷积层的两个控制输出大小的超参数
填充
由于卷积核的宽度和高度通常大于1,在应用多层卷积时,我们常常丢失边缘像素。 由于我们通常使用小卷积核,因此对于任何单个卷积,我们可能只会丢失几个像素。 但随着我们应用许多连续卷积层,累积丢失的像素数就多了。 解决这个问题的简单方法即为填充:
填充原则:
步幅
有时候为了高效计算或是缩减采样次数,卷积窗口可以跳过中间位置,每次滑动多个元素。
步幅原则:
总结
多输入多输出通道
本节代码文件在源代码文件的chapter_convolutional-neural-networks/channels.ipynb中
多个输入通道
当输入包含多个通道时,需要构造一个与输入数据具有相同输入通道数的卷积核,以便与输入数据进行互相关运算。
多个输出通道
多个输入和输出通道
1x1卷积层
二维卷积层
总结
池化层(汇聚层)
本节代码文件在源代码文件的chapter_convolutional-neural-networks/pooling.ipynb中
卷积层的缺点
二维最大池化层
池化层vs卷积层
池化层没有可学习的参数,也不会融合多输入通道。
平均池化层
总结
LeNet
本节代码文件在源代码文件的chapter_convolutional-neural-networks/lenet.ipynb中
手写数字识别
MNIST数据集
LeNet
LeNet是最早发布的卷积神经网络之一,因其在计算机视觉任务中的高效性能而受到广泛关注。 这个模型是由AT&T贝尔实验室的研究员Yann LeCun在1989年提出的(并以其命名),目的是识别图像中的手写数字。 当时,LeNet取得了与支持向量机性能相媲美的成果,成为监督学习的主流方法。 LeNet被广泛用于自动取款机(ATM)机中,帮助识别处理支票的数字。 时至今日,一些自动取款机仍在运行Yann LeCun和他的同事Leon Bottou在上世纪90年代写的代码呢!
总体来看,LeNet(LeNet-5)由两个部分组成:
①卷积编码器:由两个卷积层组成。
②全连接层密集块:由三个全连接层组成。
总结
现代卷积神经网络
深度卷积神经网络(AlexNet)
本节代码文件在源代码文件的chapter_convolutional-modern/alexnet.ipynb中
机器学习VS神经网路
在上世纪90年代初到2012年之间的大部分时间里,神经网络往往被其他机器学习方法超越,如支持向量机(support vector machines)。
计算机视觉与几何学
卷积神经网络通常用在计算机视觉,在2000年时,计算机视觉的知识主要来源于几何学。
计算机视觉与特征工程
15年前,计算机视觉中,最重要的是特征工程。
深度学习的崛起原因
尽管一直有一群执着的研究者不断钻研,试图学习视觉数据的逐级表征,然而很长一段时间里这些尝试都未有突破。深度卷积神经网络的突破出现在2012年。突破可归因于两个关键因素。
①硬件
2012年,当Alex Krizhevsky和Ilya Sutskever实现了可以在GPU硬件上运行的深度卷积神经网络时,一个重大突破出现了。他们意识到卷积神经网络中的计算瓶颈:卷积和矩阵乘法,都是可以在硬件上并行化的操作。 于是,他们使用两个显存为3GB的NVIDIA GTX580 GPU实现了快速卷积运算。他们的创新cuda-convnet几年来它一直是行业标准,并推动了深度学习热潮。
②数据
2009年,ImageNet数据集发布,并发起ImageNet挑战赛:要求研究人员从100万个样本中训练模型,以区分1000个不同类别的对象。ImageNet数据集由斯坦福教授李飞飞小组的研究人员开发,利用谷歌图像搜索(Google Image Search)对每一类图像进行预筛选,并利用亚马逊众包(Amazon Mechanical Turk)来标注每张图片的相关类别。这种规模是前所未有的。这项被称为ImageNet的挑战赛推动了计算机视觉和机器学习研究的发展,挑战研究人员确定哪些模型能够在更大的数据规模下表现最好。
AlexNet vs LeNet
AlexNet
总结
使用块的网络(VGG)
本节代码文件在源代码文件的chapter_convolutional-modern/vgg.ipynb中
VGG 出现的背景
VGG 块
VGG 架构
进度(发展)
下图横坐标代表速率,纵坐标代表准确率,圆的大小代表内存占用的大小:
总结
网络中的网络(NiN)
本节代码文件在源代码文件的chapter_convolutional-modern/nin.ipynb中
全连接层的问题
NiN 块
NiN 架构
NiN 网络
总结
含并行连结的网络(GoogLeNet)
本节代码文件在源代码文件的chapter_convolutional-modern/googlenet.ipynb中
GoogLeNet 出现的背景
我们往往不确定到底选取什么样的层效果更好,到底是3X3卷积层还是5X5的卷积层,诸如此类的问题是GooLeNet选择了另一种思路“小学生才做选择,我全都要”,这也使得GooLeNet成为了第一个模型中超过1000个层的模型。
Inception 块
Inception块由四条并行路径组成。 前三条路径使用窗口大小为1x1、3x3和5x5的卷积层,从不同空间大小中提取信息。 中间的两条路径在输入上执行1x1卷积,以减少通道数,从而降低模型的复杂性。 第四条路径使用3x3最大汇聚层,然后使用1x1卷积层来改变通道数。 这四条路径都使用合适的填充来使输入与输出的高和宽一致,最后我们将每条线路的输出在通道维度上连结,并构成Inception块的输出。在Inception块中,通常调整的超参数是每层输出通道数。
GoogLeNet
GoogLeNet一共使用9个Inception块和全局平均汇聚层(Global AvgPool)的堆叠来生成其估计值。Inception块之间的最大汇聚层可降低维度。 第一个模块类似于AlexNet和LeNet,Inception块的组合从VGG继承,全局平均汇聚层避免了在最后使用全连接层(FC)。
Inception 有各种后续变种
v3是在v2基础上变化的:
Inception与其他网络的比较
下图横坐标代表速率,纵坐标代表准确率,圆的大小代表内存占用的大小。由图可见,Inception V3速率较低,占用内存较大,但准确率很高。
总结
批量规范化(归一化)
本节代码文件在源代码文件的chapter_convolutional-modern/batch-norm.ipynb中
背景
批量归一化
批量归一化层
批量归一化在做什么?
总结
残差网络(ResNet)
本节代码文件在源代码文件的chapter_convolutional-modern/resnet.ipynb中
加更多的层总是改进精度吗?
残差块
ResNet 块细节
不同的残差块
ResNet 块
ResNet 架构
ResNet与其他网络的比较
下图横坐标代表速率,纵坐标代表准确率,圆的大小代表内存占用的大小。由图可见,ResNet 152 速率较低,但占用内存较小,且准确率很高。
总结
计算性能
深度学习硬件(CPU和GPU)
电脑CPU、GPU、内存之间的关系
CPU芯片图
提升CPU利用率
GPU芯片图
GPU vs CPU
提升GPU利用率
CPU/GPU 带宽
更多的 CPUs 和 GPUs
CPU/GPU 高性能计算编程
总结
深度学习硬件(TPU和其他)
手机内部的芯片
DSP:数字信号处理
可编程阵列(FPGA)
AI ASIC
Systolic Array
总结
· 灵活性、易用性:Intel(CPU) > GPU > DSP > FPGA > ASIC
· 性能功耗:Intel(CPU) < GPU < DSP < FPGA < ASIC
单机多卡并行
多GPU并行
单机多卡并行
数据并行 vs 模型并行
数据并行
总结
分布式训练
分布式计算
本质上来说和之前讲的单机多卡并行没有区别。二者之间的区别是分布式计算是通过网络把数据从一台机器搬到另一台机器。
GPU机器架构
总的来说,gpu到gpu的通讯是很快的,gpu到cpu慢一点。机器到机器更慢。因而总体性能的关键就是尽量在本地做通讯而少在机器之间做通讯。
举例:计算一个小批量:
同步 SGD
性能
性能的权衡
实践时的建议
总结
计算机视觉
图像增广
本节代码文件在源代码文件的chapter_computer-vision/image-augmentation.ipynb中
数据增广
数据增强
使用增强数据训练
翻转
切割
颜色
几十种其他办法
总结
微调
本节代码文件在源代码文件的chapter_computer-vision/fine-tuning.ipynb中
微调的原因
神经网络的网络架构
微调中的权重初始化
训练时的微调
重用分类器权重
固定一些层
总结
物体检测和数据集
本节代码文件在源代码文件的chapter_computer-vision/bounding-box.ipynb中
图片分类与目标检测的区别
目标检测更加复杂,需要进行多个物体的识别,还要找出每个物体的位置。目标检测的应用场景也更多。
边缘框
目标检测数据集
总结
锚框
本节代码文件在源代码文件的chapter_computer-vision/anchor.ipynb中
锚框
IoU - 交并比
赋予锚框标号
使用非极大值抑制(NMS)输出
总结
物体检测算法:R-CNN,SSD,YOLO
本节代码文件在源代码文件的chapter_computer-vision/rcnn.ipynb中
R-CNN
兴趣区域(RoI)池化层
目的:每个锚框都可以变成想要的形状
Fast RCNN
RCNN需要对每个锚框进行CNN运算,这些特征抽取计算有重复,并且锚框数量大,特征抽取的计算量也大。Fast RCNN改进了这种计算量大的问题,使用CNN对整张图片抽取特征(快的关键),再使用Rol池化层对每个锚框(将在原图片中搜索到的锚框,映射到CNN得到的结果上),生成固定长度的特征。
Faster RCNN
将CNN结果输入到卷积层,然后用锚框去圈区域,这些锚框很多有好有坏,然后进行预测,binary 预测是预测这个锚框的好坏,即有没有有效的圈住物体,bounding box prediction预测是对锚框进行一些改进,最后用NMS(非极大值抑制)对锚框进行合并。
Mask RCNN
如图,Faster RCNN精度高但是速度慢(贵):
总结
单发多框检测(SSD)
本节代码文件在源代码文件的chapter_computer-vision/ssd.ipynb中
生成锚框
SSD模型
如图,SSD速度快但精度不高:
总结
YOLO
YOLO(你只看一次)
如图,相同精度下YoLo比SSD速度快:
语义分割
本节代码文件在源代码文件的chapter_computer-vision/semantic-segmentation-and-dataset.ipynb中
语义分割
应用:背景虚化
应用:路面分割
应用:实例分割
转置卷积
本节代码文件在源代码文件的chapter_computer-vision/transposed-conv.ipynb中
转置卷积
为什么称之为“转置”
转置卷积是一种卷积
重新排列输入和核
形状换算
同反卷积的关系
总结
全连接卷积神经网络 FCN
本节代码文件在源代码文件的chapter_computer-vision/fcn.ipynb中
全连接卷积神经网络(FCN)
样式迁移
本节代码文件在源代码文件的chapter_computer-vision/neural-style.ipynb中
样式迁移
基于CNN的样式迁移
循环神经网络
序列模型
本节代码文件在源代码文件的chapter_recurrent-neural-networks/sequence.ipynb中
序列数据
序列数据 - 更多例子
统计工具
序列模型
总结
语言模型
本节代码文件在源代码文件的chapter_recurrent-neural-networks/language-models-and-dataset.ipynb中
语言模型
使用计数来建模
N元语法
总结
循环神经网络
本节代码文件在源代码文件的chapter_recurrent-neural-networks/rnn.ipynb中
潜变量自回归模型
循环神经网络
使用循环神经网络的语言模型
困惑度(perplexity)
梯度裁剪
更多的应用RNNs
总结
现代循环神经网络
门控循环单元GRU
本节代码文件在源代码文件的chapter_recurrent-modern/gru.ipynb中
门控循环单元GRU
关注一个序列
门
候选隐状态
隐状态
总结
长短期记忆网络(LSTM)
本节代码文件在源代码文件的chapter_recurrent-modern/lstm.ipynb中
长短期记忆网络
门
候选记忆单元
记忆单元
隐状态
总结
深度循环神经网络
本节代码文件在源代码文件的chapter_recurrent-modern/deep-rnn.ipynb中
回顾:循环神经网络
Plan A - Nonlinearity in the units
更深
总结
双向循环神经网络
本节代码文件在源代码文件的chapter_recurrent-modern/bi-rnn.ipynb中
未来很重要
双向RNN
推理
总结
编码器-解码器
本节代码文件在源代码文件的chapter_recurrent-modern/encoder-decoder.ipynb中
重新考察CNN
重新考察RNN
编码器-解码器架构
总结
序列到序列学习
本节代码文件在源代码文件的chapter_recurrent-modern/seq2seq.ipynb中
序列到序列学习(seq2seq)
机器翻译
Seq2seq
编码器-解码器细节
训练
衡量生成序列的好坏的BLEU
总结
束搜索
本节代码文件在源代码文件的chapter_recurrent-modern/beam-search.ipynb中
贪心搜索
穷举搜索
束搜索
总结
注意力机制
本节代码文件在源代码文件的chapter_attention-mechanisms/attention-cues.ipynb中
心理学
注意力机制
非参注意力池化层
Nadaraya-Watson核回归
参数化的注意力机制
总结
注意力分数
本节代码文件在源代码文件的chapter_attention-mechanisms/attention-scoring-functions.ipynb中
注意力分数
拓展到高维度
Additive Attention
Scaled Dot-Product Attention
总结
使用注意力机制的seq2seq
本节代码文件在源代码文件的chapter_attention-mechanisms/bahdanau-attention.ipynb中
动机
加入注意力
总结
自注意力
本节代码文件在源代码文件的chapter_attention-mechanisms/self-attention-and-positional-encoding.ipynb中
自注意力
跟CNN,RNN对比
位置编码
位置编码矩阵
绝对位置信息
总结
Transformer
本节代码文件在源代码文件的chapter_attention-mechanisms/transformer.ipynb中
Transformer架构
多头注意力
有掩码的多头注意力
基于位置的前馈网络
层归一化
信息传递
预测
总结
自然语言处理:预训练
BERT预训练
本节代码文件在源代码文件的chapter_natural-language-processing-pretraining/bert-pretraining.ipynb中
NLP里的迁移学习
BERT的动机
BERT架构
对输入的修改
预训练任务1:带掩码的语言模型
预训练任务2:下一句子预测
总结
自然语言处理:应用
BERT微调
本节代码文件在源代码文件的chapter_natural-language-processing-applications/finetuning-bert.ipynb中
微调 Bert
句子分类
命名实体识别
问题回答
总结
优化算法
优化算法
本节代码文件在源代码文件的chapter_optimization/optimization-intro.ipynb中
优化问题
局部最小 vs 全局最小
凸集
凸函数
凸函数优化
凸和非凸例子
梯度下降
随机梯度下降
小批量随机梯度下降
冲量法
Adam
总结
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。