文章目录
1. 从全连接层到卷积
因为我们遵循平移不变形和局部性,所以我们在全连接层的基础上形成了卷积神经网络,故卷积神经网络是一种特殊的全连接层。
1.1 平移不变性
不管检测对象出现在图像的哪个位置,神经网络的前几层应该对相同的图像区域具有相似的反应。
我们定义两个变量:
- [ X ] i , j [X]_{i,j} [X]i,j:表示输入图像的中位置 (i,j) 的像素
-
[
H
]
i
,
j
[H]_{i,j}
[H]i,j:表示隐藏的中位置 (i,j) 的像素
为了使每个隐藏神经元都能接收到每个输⼊像素的信息,我们将参数从权重矩阵(如同我们先前在多层感知机中所做的那样)替换为四阶权重张量 W。假设 U 包含偏置参数,我们可以将全连接层形式化地表⽰为:
[ H ] i , j = [ U ] i , j + ∑ k ∑ l [ W ] i , j , k , l [ X ] k , l (1) [H]_{i,j}=[U]_{i,j}+\sum_k\sum_l[W]_{i,j,k,l}[X]_{k,l}\tag1 [H]i,j=[U]i,j+k∑l∑[W]i,j,k,l[X]k,l(1)
[ H ] i , j = [ U ] i , j + ∑ a ∑ b [ V ] i , j , a , b [ X ] i + a , j + b (2) [H]_{i,j}=[U]_{i,j}+\sum_a\sum_b[V]_{i,j,a,b}[X]_{i+a,j+b}\tag2 [H]i,j=[U]i,j+a∑b∑[V]i,j,a,b[X]i+a,j+b(2)
从 W到V之间的转换只是形式的转换,因为在这两个四阶张量的元素之间存在一一对应的关系,我们只需重新索引下标(k,l);k=i+a;l=j+b;因此可得如下:
[ V ] i , j , a , b = [ W ] i , j , i + a , j + b (3) [V]_{i,j,a,b}=[W]_{i,j,i+a,j+b}\tag{3} [V]i,j,a,b=[W]i,j,i+a,j+b(3)
索引 a,b 通过在正偏移和负偏移之间移动覆盖整个图像。对于隐藏表示中任意给定位置 (i,j)处的像素值 [ H ] i , j [H]_{i,j} [H]i,j,可以通过在 x 中以 (i,j) 为中心对像素进行加权求和得到,加权使用的权重为 [ V ] i , j , a , b [V]_{i,j,a,b} [V]i,j,a,b,平移不变性指的是在检测对象在输入 X 中的平移,应该仅仅导致隐藏表示 H中的平移。也就是 U 和 V实际上不依赖 i,j 的值。数学可表达为:
[ V ] i , j , a , b = [ V ] a , b (4) [V]_{i,j,a,b}=[V]_{a,b}\tag{4} [V]i,j,a,b=[V]a,b(4)
其中 U 可以看作是常数 u.则可以表示如下:
[ H ] i , j = u + ∑ a ∑ b [ V ] a , b [ X ] i + a , j + b (5) [H]_{i,j}=u+\sum_a\sum_b[V]_{a,b}[X]_{i+a,j+b}\tag{5} [H]i,j=u+a∑b∑[V]a,b[X]i+a,j+b(5)
上述为卷积,表示的是使用系数 [ V ] a , b [V]_{a,b} [V]a,b对位置 [i,j] 附近的像素(i+a,j+b)进行加权得到 [ H ] i , j [H]_{i,j} [H]i,j。因为卷积考虑到局部性和平移不变性,所以参数表示上小了很多,是巨大的进步,但是其本质上还是来自全连接层。
1.2 局部性
局部性指的是我们卷积只对局部部分进行加权计算,而不考虑局部以外的值。假设局部区间为△,我们希望:
∣
a
∣
>
△
|a|>△
∣a∣>△或者
∣
b
∣
>
△
|b|>△
∣b∣>△时,其训练参数
[
V
]
a
,
b
=
0
[V]_{a,b}=0
[V]a,b=0,故卷积层表示如下:
[
H
]
i
,
j
=
u
+
∑
a
=
−
△
△
∑
b
=
−
△
△
[
V
]
a
,
b
[
X
]
i
+
a
,
j
+
b
(6)
[H]_{i,j}=u+\sum_{a=-△}^{△}\sum_{b=-△}^{△}[V]_{a,b}[X]_{i+a,j+b}\tag{6}
[H]i,j=u+a=−△∑△b=−△∑△[V]a,b[X]i+a,j+b(6)
2. 图像卷积
2.1 卷积计算图示
2.2 学习卷积核
关于卷积核的学习,详见下述链接。
深度学习到底是学习了什么?卷积核学习思考
2.3 小结
- 二维卷积层的核心计算为二维互相关计算,跟数学上的卷积计算有所差别
- 我们可以通过卷积核计算来对图像进行不同的处理,如锐化,高斯处理等
- 我们可以通过输入数据X和最终数据Y,通过深度学习来学习到卷积核的值
- 当需要检测输入特征中更广阔的区域,我们需要设计更深的网络。
3. 填充和步幅
3.1 填充
填充的意义在于,当我们的卷积核的大小大于1的时候,我们总会丢失部分的边缘像素,当神经网络比较浅的时候,我们可能看不出来。但当我们的网络非常深的时候,那么最终我们就会丢失掉很多信息,所以我们需要填充输入的矩阵。
- 填充方式,对称填充
如果我们添加
p
h
p_h
ph行来填充(一半在顶部,一半在底部),添加
p
w
p_w
pw列(一半在最左列,一半在最右列),输出形状为:
(
n
h
−
k
h
+
p
h
+
1
)
×
(
n
w
−
k
w
+
p
w
+
1
)
(7)
(n_h-k_h+p_h+1)\times(n_w-k_w+p_w+1)\tag{7}
(nh−kh+ph+1)×(nw−kw+pw+1)(7)
- 注:
n h : 输 入 矩 阵 行 数 ; k h : 卷 积 核 行 数 ; p h : 填 充 的 行 数 n_h:输入矩阵行数;k_h:卷积核行数;p_h:填充的行数 nh:输入矩阵行数;kh:卷积核行数;ph:填充的行数
n w : 输 出 矩 阵 列 数 ; k w : 卷 积 核 列 数 ; p w : 填 充 的 列 数 n_w:输出矩阵列数;k_w:卷积核列数;p_w:填充的列数 nw:输出矩阵列数;kw:卷积核列数;pw:填充的列数
我们知道输入矩阵 X 的大小为
(
n
h
,
n
w
)
(n_h,n_w)
(nh,nw),输出矩阵 Y 的大小为
(
n
h
−
k
h
+
p
h
+
1
)
×
(
n
w
−
k
w
+
p
w
+
1
)
(n_h-k_h+p_h+1)\times(n_w-k_w+p_w+1)
(nh−kh+ph+1)×(nw−kw+pw+1);为了保证输出大小和输入大小不变,我们通常假设填充如下:
−
k
h
+
p
h
+
1
=
0
;
−
k
w
+
p
w
+
1
=
0
(8)
-k_h+p_h+1=0;-k_w+p_w+1=0\tag{8}
−kh+ph+1=0;−kw+pw+1=0(8)
- 整理可得如下:
p h = k h − 1 ; p w = k w − 1 (9) p_h=k_h-1;p_w=k_w-1\tag{9} ph=kh−1;pw=kw−1(9)
3.2 填充 - Pytorch
- pytorch
在 Pytorch 二维卷积 nn.Conv2d中的参数 padding 值是单边填充量,比如
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1)
那么输入矩阵 X 的填充是 顶部 1,底部 1,所以输出矩阵大小应该为
(
n
h
−
k
h
+
2
∗
p
h
+
1
)
×
(
n
w
−
k
w
+
2
∗
p
w
+
1
)
(10)
(n_h-k_h+2*p_h+1)\times(n_w-k_w+2*p_w+1)\tag{10}
(nh−kh+2∗ph+1)×(nw−kw+2∗pw+1)(10)
代入可得:
(
n
h
−
3
+
2
∗
1
+
1
)
×
(
n
w
−
3
+
2
∗
1
+
1
)
(11)
(n_h-3+2*1+1)\times(n_w-3+2*1+1)\tag{11}
(nh−3+2∗1+1)×(nw−3+2∗1+1)(11)
输出矩阵Y的形状如下,这样输入矩阵X和输出矩阵Y的大小不变
n
h
×
n
w
(12)
n_h\times n_w\tag{12}
nh×nw(12)
- 代码
# -*- coding: utf-8 -*-
# @Project: zc
# @Author: zc
# @File name: padding
# @Create time: 2021/12/4 15:56
# 1. 导入相关数据库
import torch
from torch import nn
from d2l import torch as d2l
# 2.矩阵大小计算
# 输入 conv2d: 卷积核,x :输入矩阵
# 返回 y:输出矩阵,y.shape :输出矩阵的形状
def comp_conv2d(conv2d, x):
# 将矩阵 x 加入批量大小和通道数:(批量大小,通道数,输入矩阵)
x = x.reshape((1, 1) + x.shape)
# 卷积计算
y = conv2d(x)
# 去掉矩阵中的 (批量大小,通道数)
y = y.reshape(y.shape[2:])
return y, y.shape
# 3. 定义二维卷积运算
conv2d = nn.Conv2d(1, 1, kernel_size=3, padding=1)
# 4. 获取卷积矩阵的填充值(padding) ,卷积核大小(kernel_size)
padding = conv2d.padding
kernel_size = conv2d.kernel_size
# 5. 初始化 x ,
x = torch.randn(8, 8)
x_shape = x.shape
# 6. 获取相关参数
n_h, n_w = x_shape[0], x_shape[1]
k_h, k_w = kernel_size[0], kernel_size[1]
p_h, p_w = padding[0], padding[1]
y_h, y_w = x_shape[0], x_shape[1]
# 7. 卷积计算,得到输出矩阵及形状
y, y_shape = comp_conv2d(conv2d, x)
# 8. 打印相关数据
print(f'x_shape={x_shape},kernel_size={kernel_size},padding={padding},y_shape={y_shape}')
print(f'{y_h}:(y_h)={n_h}:(n_h)-{k_h}:(k_h)+{2*p_h}:(2*p_h)+1')
print(f'{y_w}:(y_w)={n_w}:(n_w)-{k_w}:(k_w)+{2*p_w}:(2*p_w)+1')
- 结果
x_shape=torch.Size([8, 8]),kernel_size=(3, 3),padding=(1, 1),y_shape=torch.Size([8, 8])
8:(y_h)=8:(n_h)-3:(k_h)+2:(2*p_h)+1
8:(y_w)=8:(n_w)-3:(k_w)+2:(2*p_w)+1
3.3 步幅 stride
步幅指的是卷积核每次滑动元素的数量。步幅的作用是为了采样的压缩,以前是一步一步的移动卷积核,如果像素之间相似东西太多,我们其实没必要进行一步一步的移动采集,我们完全可以跳着采集嘛,所以一般步幅的作用为了压缩数据,步幅一般是2,比如,输入矩阵是(8,8),当我们的步幅stride = 2的时候,我们得到的矩阵大小一般是(4,4),具体公式如下:
- 输出矩阵:
⌊ ( n h − k h + p h + s h ) / s h ⌋ × ⌊ ( n w − k w + p w + s w ) / s w ⌋ (13) \lfloor (n_h-k_h+p_h+s_h)/s_h\rfloor \times \lfloor (n_w-k_w+p_w+s_w)/s_w\rfloor\tag{13} ⌊(nh−kh+ph+sh)/sh⌋×⌊(nw−kw+pw+sw)/sw⌋(13) - 设置卷积核大小
p h = k h − 1 ; p w = k w − 1 (14) p_h=k_h-1;p_w=k_w-1\tag{14} ph=kh−1;pw=kw−1(14) - 输出矩阵更新为:
⌊ ( n h + s h − 1 ) / s h ⌋ × ⌊ ( n w + s w − 1 ) / s w ⌋ (15) \lfloor (n_h+s_h-1)/s_h\rfloor \times \lfloor (n_w+s_w-1)/s_w\rfloor\tag{15} ⌊(nh+sh−1)/sh⌋×⌊(nw+sw−1)/sw⌋(15) - 一般能被整除,最后简化为:
( n h / s h ) × ( n w / s w ) (16) (n_h/s_h) \times (n_w/s_w)\tag{16} (nh/sh)×(nw/sw)(16)
4. 多输入和多输出通道
4.1 多通道
以前我们常常进行的是矩阵运算,其有长宽两个参数,一般为二维通道,当我们的图片是彩色的.而颜色是由RGB三种颜色组成的.所以每个RGB输入图像具有 3 × h × w 3 \times h \times w 3×h×w,那么这个3就是通道的意思。
4.2 多输入通道 -> 单输出通道
假设我们输入为
c
i
×
n
h
×
n
w
c_i\times n_h \times n_w
ci×nh×nw;卷积核为
c
i
×
k
h
×
k
w
c_i \times k_h \times k_w
ci×kh×kw,输出为
o
h
×
o
w
o_h \times o_w
oh×ow;为了保证单通道输出,我们可以将每个通道卷积计算出来的结果求和。具体如下
解析:
- input 输入大小为:2 x 3 x 3
- kernel 核大小为: 2 x 2 x 2
- padding 为:0;
- output 输出大小为: 1 x 2 x 2注: 3-2+1=2; 1 表示的是求和;
代码如下:
# -*- coding: utf-8 -*-
# @Project: zc
# @Author: zc
# @File name: channel
# @Create time: 2021/12/4 21:28
import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l
def corr2d_multi_in(x, k):
return sum(d2l.corr2d(x, k) for x, k in zip(x, k))
x = torch.tensor([[[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]],
[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]])
k = torch.tensor([[[0.0, 1.0], [2.0, 3.0]], [[1.0, 2.0], [3.0, 4.0]]])
y = corr2d_multi_in(x, k)
print(f'y={y}')
- 结果
y=tensor([[ 56., 72.],
[104., 120.]])
4.3 多个输入通道,单输出通道
输入 X:
c
i
×
n
h
×
n
w
c_i \times n_h \times n_w
ci×nh×nw;
核 w:
c
i
×
k
h
×
k
w
c_i \times k_h \times k_w
ci×kh×kw
输出 Y:
m
h
×
m
w
m_h \times m_w
mh×mw
Y
=
∑
i
=
0
c
i
X
i
,
:
,
:
∗
W
i
,
:
,
:
(17)
Y=\sum_{i=0}^{c_i}X_{i,:,:} * W_{i,:,:}\tag{17}
Y=i=0∑ciXi,:,:∗Wi,:,:(17)
4.4 多输入通道,多输出通道
输入 X:
c
i
×
n
h
×
n
w
c_i \times n_h \times n_w
ci×nh×nw;
核 w:
c
o
×
c
i
×
k
h
×
k
w
c_o \times c_i \times k_h \times k_w
co×ci×kh×kw
输出 Y:
c
o
×
m
h
×
m
w
c_o \times m_h \times m_w
co×mh×mw
Y
=
X
i
,
:
,
:
∗
W
i
,
:
,
:
,
:
;
f
o
r
i
=
1
,
2
,
.
.
.
,
c
o
(18)
Y=X_{i,:,:} * W_{i,:,:,:} \quad ;for \quad i = 1,2,...,c_o\tag{18}
Y=Xi,:,:∗Wi,:,:,:;fori=1,2,...,co(18)
4.5 输出通道的意义
- 每个输出通道可以识别特定模式;
比如说,我们输入有三个通道,可能 A 通道是识别猫的脚, B 通道是识别猫的颜色,C 通道是识别猫的尾巴,最后通过融合我们就知道了图片是一个猫。 - 输入通道核识别并组合输入中的模式
4.6 1 X 1 卷积层
当卷积核的大小由原来的
k
h
×
k
w
k_h \times k_w
kh×kw变成 1 X 1 的时候,那么卷积核就失去了原来的作用,以前我们通过卷积核来抽取卷积核范围内的信息,现在变成了 1 X 1 ,所以在使用 1 X 1 卷积时候,我们只作用到了通道里面。
注:当以每像素为基础应⽤时,1 X 1 卷积层相当于全连接层
5. 汇聚层
5.1 池化层
池化层(pooling)有两重作用:
- 降低卷积层对位置的敏感性
- 降低对空间降采样表示的敏感性
注:汇聚层是没有可以学习的参数的层
5.2 最大汇聚层
- 定义:计算池化窗口中所有元素的最大值
m a x ( 0 , 1 , 3 , 4 ) = 4 (19) max(0,1,3,4)=4\tag{19} max(0,1,3,4)=4(19)
m a x ( 1 , 2 , 4 , 5 ) = 5 max(1,2,4,5)=5 max(1,2,4,5)=5
m a x ( 3 , 4 , 6 , 7 ) = 7 max(3,4,6,7)=7 max(3,4,6,7)=7
m a x ( 4 , 5 , 7 , 8 ) = 8 max(4,5,7,8)=8 max(4,5,7,8)=8 - 代码
# 1. 导入数据库
import torch
from torch import nn
from d2l import torch as d2l
# 2. 定义池化层 ,mode = 'max': 最大池化层,mode ='avg':平均池化层
def pool2d(x, pool_size, mode='max'):
p_h, p_w = pool_size
y = torch.zeros((x.shape[0] - p_h + 1, x.shape[1] - p_w + 1))
for i in range(y.shape[0]):
for j in range(y.shape[1]):
if mode == 'max':
y[i, j] = x[i:i + p_h, j:j + p_w].max()
elif mode == 'avg':
y[i, j] = x[i:i + p_h, j:j + p_w].mean()
return y
# 3. 定义初始化参数
x = torch.tensor([[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]])
pool_size = (2, 2)
# 4. 初始化最大池化层后的矩阵,平均池化层后的矩阵
y_max_pooling = pool2d(x, pool_size, 'max')
y_avg_pooling = pool2d(x, pool_size, 'avg')
# 5. 输出相关结果
print(f'y_max_pooling={y_max_pooling}')
print(f'y_avg_pooling={y_avg_pooling}')
- 结果
y_max_pooling=tensor([[4., 5.],
[7., 8.]])
y_avg_pooling=tensor([[2., 3.],
[5., 6.]])
- 分析:代码结果跟我们手算的结果一致。
- 代码 : 引用 nn.MaxPool2d
# 1. 导入数据库
import torch
from torch import nn
# 2. 定义初始化参数
x = torch.tensor([[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]])
# 3. 形状变化为 4 维
x = x.reshape(1, 1, 3, 3)
# 4. 实例化最大池化层
pool2d = nn.MaxPool2d(2,padding=0,stride=1)
# 5. 对张量 X 进行最大池化层计算
y = pool2d(x)
# 6. 将 Y 的形状改变
y = y.reshape(2,2)
# 7. 输出相关参数
print(f'x={x}')
print(f'x_shape={x.shape}')
print(f'y={y}')
print(f'y_shape={y.shape}')
- 结果:
x=tensor([[[[0., 1., 2.],
[3., 4., 5.],
[6., 7., 8.]]]])
x_shape=torch.Size([1, 1, 3, 3])
y=tensor([[4., 5.],
[7., 8.]])
y_shape=torch.Size([2, 2])
5.3 平均汇聚层
- 定义:计算池化窗口中所有元素的平均值
6. 卷积神经网络 LeNet
注:本文代码来自 李沐的书籍,这里只做学习笔记。
- 运行环境:
- Pytorch GPU-1.9.1 ;Pycharm
6.1 LeNet 思路简图
6.2 LeNet 卷积网络大小分析
Tensor [output_shape]: torch.Size([1, 1, 28, 28])
Conv2d [output_shape]: torch.Size([1, 6, 28, 28])
Sigmoid [output_shape]: torch.Size([1, 6, 28, 28])
AvgPool2d [output_shape]: torch.Size([1, 6, 14, 14])
Conv2d [output_shape]: torch.Size([1, 16, 10, 10])
Sigmoid [output_shape]: torch.Size([1, 16, 10, 10])
AvgPool2d [output_shape]: torch.Size([1, 16, 5, 5])
Flatten [output_shape]: torch.Size([1, 400])
Linear [output_shape]: torch.Size([1, 120])
Sigmoid [output_shape]: torch.Size([1, 120])
Linear [output_shape]: torch.Size([1, 84])
Sigmoid [output_shape]: torch.Size([1, 84])
Linear [output_shape]: torch.Size([1, 10])
6.3 代码
# -*- coding: utf-8 -*-
# @Project: zc
# @Author: zc
# @File name: Lenet
# @Create time: 2021/12/6 6:39
# 1. 导入相关数据库
import torch
from torch import nn
from d2l import torch as d2l
import matplotlib.pyplot as plt
# 2. 定义整形类,主要是把原始图片变成(28,28)的大小
class Reshape(nn.Module):
def forward(self, x):
return x.view(-1, 1, 28, 28)
# 3. 定义网络 LeNet
net = nn.Sequential(
Reshape(),
nn.Conv2d(1, 6, kernel_size=5, padding=2), nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Conv2d(6, 16, kernel_size=5), nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Flatten(),
nn.Linear(16 * 5 * 5, 120), nn.Sigmoid(),
nn.Linear(120, 84), nn.Sigmoid(),
nn.Linear(84, 10))
# 4. 加载数据
batch_size = 256 # 批量大小
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size) # 训练集和测试集
# 5. 使用GPU计算模型在数据集上的精度
# net.eval() 推断模式的情况下,不启用 BatchNormalization 和 Dropout
def evaluate_accuracy_gpu(net, data_iter, device=None): # @save
"""使用GPU计算模型在数据集上的精度。"""
if isinstance(net, nn.Module):
net.eval() # 设置为评估模式
if not device: # 如果没有 GPU ,就查看下网路参数在哪里,将 Device设置为 'CPU'
device = next(iter(net.parameters())).device
# 正确预测的数量,总预测的数量
metric = d2l.Accumulator(2)
with torch.no_grad(): # 被with torch.no_grad()包住的代码,不用跟踪反向梯度计算
for X, y in data_iter: # 从 data_iter 中拿出数据
if isinstance(X, list):
# BERT微调所需的(之后将介绍)
X = [x.to(device) for x in X] # 将数据 x 转到 GPU 中
else:
X = X.to(device)
y = y.to(device) # 将数据 y 转到 GPU 中
metric.add(d2l.accuracy(net(X), y), y.numel())
return metric[0] / metric[1]
# 6.定义训练模型函数
def train_ch6(net, train_iter, test_iter, num_epochs, lr, device):
"""用GPU训练模型(在第六章定义)。"""
# 初始化权重参数
def init_weights(m):
if type(m) == nn.Linear or type(m) == nn.Conv2d:
nn.init.xavier_uniform_(m.weight) # 如果是卷积或者是全连接层,那么就用 xavier 进行初始化权重
net.apply(init_weights) # 网络参数初始化
print('training on', device)
net.to(device) # 将网络转移到 GPU 上
optimizer = torch.optim.SGD(net.parameters(), lr=lr) # 设置神经网络优化器 SGD 随机梯度下降
loss = nn.CrossEntropyLoss() # 设置交叉熵损失来判断 y 和 y_hat 的差距
animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs],
legend=['train loss', 'train acc', 'test acc'])
timer, num_batches = d2l.Timer(), len(train_iter)
for epoch in range(num_epochs):
# 训练损失之和,训练准确率之和,范例数
metric = d2l.Accumulator(3)
net.train() # 开启网络训练模式
for i, (X, y) in enumerate(train_iter): # 开始抽取相关数据 (x,y)
timer.start() # 开始计时
optimizer.zero_grad() # 优化器清零
X, y = X.to(device), y.to(device) # 将数据放在 GPU 上训练
y_hat = net(X)
# 这里有三个值,X,y 都是样本给的,X表示输入,
# 我们希望通过网络得到 y_hat ,最后比较 y 和 y_hat
l = loss(y_hat, y) # 比较 y_hat 和 y 的差异
l.backward() # 损失回传
optimizer.step() # 优化器通过梯度来更新参数
with torch.no_grad(): # 被with torch.no_grad()包住的代码,不用跟踪反向梯度计算
metric.add(l * X.shape[0], d2l.accuracy(y_hat, y), X.shape[0])
timer.stop() # 计时结束
train_l = metric[0] / metric[2] # 训练损失值
train_acc = metric[1] / metric[2] # 训练精确度
if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1:
animator.add(epoch + (i + 1) / num_batches,
(train_l, train_acc, None))
test_acc = evaluate_accuracy_gpu(net, test_iter)
animator.add(epoch + 1, (None, None, test_acc))
print(f'loss {train_l:.3f}, train acc {train_acc:.3f}, '
f'test acc {test_acc:.3f}')
print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec '
f'on {str(device)}')
# 7. 定义超参数
lr, num_epochs = 0.1, 50
# 8. 开始训练模型
train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
# 9. 显示结果
plt.show()
- 结果
loss 0.494, train acc 0.819, test acc 0.807
54179.3 examples/sec on cuda:0
6.4 小结
- 在训练模式的情况时我们需要用 net.train();在预测模式的情况下我们需要用 net.eval()
net.train() # 训练模式
net.eval() # 预测模式
- 需要将数据 X,Y, net 放到 GPU 上
X, y = X.to(device), y.to(device) # 将数据放在 GPU 上训练
net.to(device) # 将神经网络转移到 GPU 上