文章目录
摘要
本周花了大量时间在组合数学的学习上,对生成排列算法重新进行复习,以及第二章母函数、整数拆分相关概念进行预习。在深度学习上面,学习了使用神经网络框架来实现简单的线性回归、生成图像分类数据集,并用于softmax回归实现。
This week I spent a lot of time studying combinatorial mathematics, and reviewing the generative permutation algorithm.I previewed concepts related to generating functions and integer splitting in Chapter 2. In deep learning aspect, I learned to use Neural Network Framework(pythorch.nn) to achieve simple linear regression, generating image classification data sets, and using it for softmax regression implementation.
组合数学
生成排列的算法
从已知排列出发,生成新的排列
序数法
自然数和序列的对应:用一个n!的全排列来表示自然数
312
=
1
⋅
2
8
+
0
⋅
2
7
+
0
⋅
2
6
+
1
⋅
2
5
+
1
⋅
2
4
+
1
⋅
2
3
+
0
⋅
2
2
+
0
⋅
2
1
+
0
⋅
2
0
312=1 \cdot 2^8+0 \cdot 2^7+0 \cdot 2^6+1 \cdot 2^5+1 \cdot 2^4+1 \cdot 2^3+0 \cdot 2^2+0 \cdot 2^1+0 \cdot 2^0
312=1⋅28+0⋅27+0⋅26+1⋅25+1⋅24+1⋅23+0⋅22+0⋅21+0⋅20
上式所用的基数为:2的幂次基数
如果用
{
(
n
−
1
)
!
,
(
n
−
2
)
!
,
…
,
2
!
,
1
!
}
\{(n-1) !,(n-2) !, \ldots, 2 !, 1 !\}
{(n−1)!,(n−2)!,…,2!,1!}为基数来表示自然数,则对自然数n!-1的表示就为:
n
!
−
1
=
(
n
−
1
)
⋅
(
n
−
1
)
!
+
(
n
−
2
)
⋅
(
n
−
2
)
!
+
…
+
1
⋅
1
!
=
∑
k
=
1
n
−
1
k
⋅
k
!
n !-1=(n-1) \cdot(n-1) !+(n-2) \cdot(n-2) !+\ldots+1 \cdot 1 !=\sum_{k=1}^{n-1} k \cdot k !
n!−1=(n−1)⋅(n−1)!+(n−2)⋅(n−2)!+…+1⋅1!=∑k=1n−1k⋅k!
因此对于任意一个整数m,
0
<
=
m
<
=
m
!
−
1
0<=m<=m!-1
0<=m<=m!−1,
m
=
a
1
(
n
−
1
)
!
+
a
2
(
n
−
2
)
!
+
…
+
a
n
−
1
1
!
0
≤
a
i
≤
n
−
i
\begin{aligned} m= & a_1(n-1) !+a_2(n-2) !+\ldots+a_{n-1} 1 ! \\ & 0 \leq a_i \leq n-i\end{aligned}
m=a1(n−1)!+a2(n−2)!+…+an−11!0≤ai≤n−i
( a 1 , a 2 , . . . . , a n − 1 ) (a1,a2,....,an-1) (a1,a2,....,an−1)与n!序列也一一对应,又与整数m一一对应,因此这一组数称为中介数。
那么首先我们需要找到中介数与整数m之间的转化关系。
序数法1
递增进位
中介数=》整数:
m
=
a
1
(
n
−
1
)
!
+
a
2
(
n
−
2
)
!
+
…
+
a
n
−
2
2
!
+
a
n
−
1
1
!
m=a_1(n-1) !+a_2(n-2) !+\ldots+a_{n-2} 2 !+a_{n-1} 1 !
m=a1(n−1)!+a2(n−2)!+…+an−22!+an−11!
整数=》中介数:
如图所示,用上一步骤的商不断除以递增的被除数,得到的新的商和余数,余数就为中介数(从右往前排)。
递减进位
把基数换了,不再是以
{
(
n
−
1
)
!
,
(
n
−
2
)
!
,
…
,
2
!
,
1
!
}
\{(n-1) !,(n-2) !, \ldots, 2 !, 1 !\}
{(n−1)!,(n−2)!,…,2!,1!}为基数,而是以
n
!
(
n
−
i
+
1
)
!
\frac{n !}{(n-i+1) !}
(n−i+1)!n!为基数,那么对应的中介数来表示整数m也就能起变化。
中介数=》整数:
m
=
a
1
+
a
2
⋅
n
!
(
n
−
1
)
!
+
a
3
⋅
n
!
(
n
−
2
)
!
+
…
+
a
n
−
1
⋅
n
!
2
!
m=a_1+a_2 \cdot \frac{n !}{(n-1) !}+a_3 \cdot \frac{n !}{(n-2) !}+\ldots+a_{n-1} \cdot \frac{n !}{2 !}
m=a1+a2⋅(n−1)!n!+a3⋅(n−2)!n!+…+an−1⋅2!n!
整数=》中介数:
每一步是除数是有大到小,由n开始,最小到2,得到的余数是a1到an-1。
但需要注意的是,递减进位的中介数,在写法上是a1在最后,
(
a
n
,
a
n
−
1
,
.
.
.
,
a
2
,
a
1
)
(an,an-1,...,a2,a1)
(an,an−1,...,a2,a1) ,a1进位是逢n+1进一,a2进位是逢n进一,依次类推。
中介数与排列的之间的对应关系
刚刚是讨论了中介数与整数(也叫序数)的对应关系,是如何转化的,接下来就需要讨论中介数与排列之间的相互转化。
排列=》中介数:
31524,从‘1’开始算起,有多少个数字在排列中,先于‘1’,且大于‘1’,可以看出是1个数字‘3’,因此中介数a1即为1,再看排序里的数字‘2’,有多少先于‘2’,且大于‘2’,数字‘3’和‘5’,因此中介数a2即为2,由此类推,中介数集合为(1,2,0,1)。(最后一位‘5’不需要写)
而中介数
(
a
1
,
a
2
,
.
.
.
.
,
a
n
−
1
)
(a1,a2,....,an-1)
(a1,a2,....,an−1)各数的取值范围也是由大到小,a1能有n中取值,a2是n-1种(因为a2表示数字2左边有多少比它大的数字的个数,最多最多能有n-1种,因为已经确定了的’1’绝对是比‘2’小的)
中介数=》排列:
因此序数法生成排列的方式就是通过中介数来生成的:
序数法2
排列=》中介数
(
b
n
,
b
n
−
1
,
…
,
b
2
)
\left(b_n, b_{n-1}, \ldots, b_2\right)
(bn,bn−1,…,b2)
bj表示与j相关的逆序数,是排列中后与j但是小于j的整数的个数。
步骤1:先从排列中,找到最大数字n,n的右边比n小的整数的个数即为bn。
步骤2:再从排列中,找到最次大数字n-1,n-1右边比n-1小的整数个数即为bn-1
…
步骤n-1:从排列中,找到2的位置,2的右边比2小的整数个数即为b2.
列如:31524的逆序列为(2,0,2,0)
中介数
(
b
n
,
b
n
−
1
,
…
,
b
2
)
\left(b_n, b_{n-1}, \ldots, b_2\right)
(bn,bn−1,…,b2)=》排列
序数=》中介数
与序数法1一样,还是利用除法,取得中介数的值。
中介数=》序数
递增进位
m
=
b
n
(
n
−
1
)
!
+
b
n
−
1
(
n
−
2
)
!
+
…
+
b
3
2
!
+
b
2
1
!
m=b_n(n-1) !+b_{n-1}(n-2) !+\ldots+b_{3} 2 !+b_2 1 !
m=bn(n−1)!+bn−1(n−2)!+…+b32!+b21!
递减进位
m
=
b
n
+
b
n
−
1
⋅
n
!
(
n
−
1
)
!
+
b
n
−
2
⋅
n
!
(
n
−
2
)
!
+
…
+
b
2
⋅
n
!
2
!
m=b_n+b_{n-1} \cdot \frac{n !}{(n-1) !}+b_{n-2} \cdot \frac{n !}{(n-2) !}+\ldots+b_{2} \cdot \frac{n !}{2 !}
m=bn+bn−1⋅(n−1)!n!+bn−2⋅(n−2)!n!+…+b2⋅2!n!
综上所述,用序数法2生成排列的方法为:
已知67342221的序列,求其排列=》839647521,序号=》68!+77!+36!+45!+24!+23!+2*2!+1
字典序法
与序数法一样,都是从已知排列出发,生成新的排列
,那字典序法生成排列的算法是:
例如:求839647521的下一个排列
1)从右开始找,先找到第一个相邻右边数字比它大的数字‘4’;
2)然后从右开始再找,第一个比步骤1)找到数字大的数字‘5’;
3)然后交换数字‘4’和数字‘5’的位置 839657421
4)最后翻转数字‘5’后各数字的数列 839651247
而在字典序法中,排列=》中介数的对应方法又与序数法中的对应法则不一样:
排列=》中介数
是从左第一个数字开始,看其右边比第一个数字小的字符个数,即为a1;
…
n
−
1
右边比
i
n
−
1
小的字符个数为
a
n
−
1
n-1右边比in-1小的字符个数为an-1
n−1右边比in−1小的字符个数为an−1
中介数=》排列
这两个数能相互推的前提是,排列中各数字是不重复的,因此才具有互推的性质。
已知中介数72642321推出其排列:
p1=首位+1,p2=对应中介数+1,然后判断左侧有没有数字是小于该数的,若无,则p2就等于中介数+1,若有,则有几个就+几,然后再次判断,直到左侧无数小于该数。依次循环,直到n+1数。839647521
字典序法里都是采用递增进位制,因此序号是除2开始,一直直到n-1.
中介数=》序号
m
=
a
1
(
n
−
1
)
!
+
a
2
(
n
−
2
)
!
+
…
+
a
n
−
2
2
!
+
a
n
−
1
1
!
m=a_1(n-1) !+a_2(n-2) !+\ldots+a_{n-2} 2 !+a_{n-1} 1 !
m=a1(n−1)!+a2(n−2)!+…+an−22!+an−11!
序号=》中介数
与序数法一样,还是利用除法,取得中介数的值。
例题:在26个字母排序时,要求元音字母不能相继出现,求排序方法有几种?
26个英文字母中有5个元音字母,21个辅音字母,先把21个辅音字母进行全排列,在所构成的21个排序后,两两之见构成一个空位(能放元音字母),因此组成了22个空位,只要把22个空位选5个出来放元音字母即可。
21!* P(22,5)
邻位对换法
一个排序的逆序个数是奇还是偶,称排列位奇排列或偶排列。
奇排列下一个数n是从右往左插n个空挡(包括两端)生成n个排列,偶排列下一个数n是从左往右插n个空挡(包括两端)生成n个排列。
递减进位
生成算法
由已知排列确定下一个排列,
1)求出最大的活动整数m;如果整数m的箭头是指向一个与其相邻的整数但是比它小,则称m是活动的。
2)交换m和其箭头所指的相邻整数
3)交换所有大于m的整数方向
任意给定一个排列=》排列中各符号的活动方向
先去掉最大的数,观察其剩余数字排列的逆序数,是奇数还偶数。列如839647521,先去掉‘9’,剩余83647521,发现是奇排列,因此知道9的活动方向是向右的,以此类推,确定该排列所有数字方向;
任意给定一个排列=》中介数
从2开始找,中介数b2=在‘2’活动相反的方向上,比2小的元素数目。
b3=在‘3’活动相反的方向上,比2小的元素数目.
…
中介数
(
b
2
,
b
3
,
b
4
,
b
5
,
b
6
,
b
7
,
b
8
,
b
9
)
(b2,b3,b4,b5,b6,b7,b8,b9)
(b2,b3,b4,b5,b6,b7,b8,b9) =(1,0,1,2,1,3,7,2)
中介数=》序数
在邻位对换法中,只使用递减进位法来计算m;
m
=
b
2
⋅
n
!
2
!
+
b
3
⋅
n
!
3
!
+
…
+
b
n
−
1
⋅
n
!
(
n
−
1
)
!
+
b
n
⋅
n
!
n
!
m=b_2 \cdot \frac{n !}{2 !}+b_3 \cdot \frac{n !}{3 !}+\ldots+b_{n-1} \cdot \frac{n !}{(n-1) !}+b_n \cdot \frac{n !}{n !}
m=b2⋅2!n!+b3⋅3!n!+…+bn−1⋅(n−1)!n!+bn⋅n!n!
可以整合各项得:
b
n
+
n
(
b
n
−
1
+
(
n
−
1
)
(
b
n
−
2
+
…
+
4
(
b
3
+
3
b
2
)
…
)
)
b_n+n\left(b_{n-1}+(n-1)\left(b_{n-2}+\ldots+4\left(b_3+3 b_2\right) \ldots\right)\right)
bn+n(bn−1+(n−1)(bn−2+…+4(b3+3b2)…))
序数=》中介数
递减进位法:
** 中介数=》排列**
先写下排列中元素1,由于1是偶排列,所以2的活动方向永远是向左的(是因为b2的值决定了2的位置,2的活动方向的反方向起数b2个元素,插入2,确定一个2排列i1i2)
判断2-排列i1i2的奇偶性来决定元素3的活动方向:偶排列向左,奇排列向右。
…
直到bn,推出最后一位排列数字的位置及其活动方向。
字典序法
设
(
a
1
,
a
2
,
.
.
.
,
a
r
)
(a1,a2,...,ar)
(a1,a2,...,ar)不是最后一个组合,那么它的直接后继为
a
1
,
a
2
,
…
a
k
−
1
,
a
k
+
1
,
a
k
+
2
,
…
,
a
k
+
r
−
k
+
1
a_1, a_2, \ldots a_{k-1}, a_k+1, a_k+2, \ldots, a_k+r-k+1
a1,a2,…ak−1,ak+1,ak+2,…,ak+r−k+1
其中ak是小于n,且使得ak+1不同于a1a2…ar这r个元素的最大整数。k=max{i|ai<n-r+i}
就是把ak换成ak+1,且ak+1后面的数都是它的递增。
不相邻组合的计数
母函数与递推关系
( 1 + x ) n = C ( n , 0 ) + C ( n , 1 ) x + C ( n , 2 ) x 2 + … + C ( n , n ) x n (1+x)^n=C(n, 0)+C(n, 1) x+C(n, 2) x^2+\ldots+C(n, n) x^n (1+x)n=C(n,0)+C(n,1)x+C(n,2)x2+…+C(n,n)xn
(
1
+
x
)
m
⋅
(
1
+
x
)
n
=
(
1
+
x
)
m
+
n
(1+x)^m \cdot(1+x)^n=(1+x)^{m+n}
(1+x)m⋅(1+x)n=(1+x)m+n
由上式子,可以推出下式
(
m
0
)
⋅
(
n
r
)
+
(
m
1
)
⋅
(
n
r
−
1
)
+
…
+
(
m
r
)
⋅
(
n
0
)
=
(
m
+
n
r
)
\left(\begin{array}{c} m \\ 0 \end{array}\right) \cdot\left(\begin{array}{l} n \\ r \end{array}\right)+\left(\begin{array}{c} m \\ 1 \end{array}\right) \cdot\left(\begin{array}{c} n \\ r-1 \end{array}\right)+\ldots+\left(\begin{array}{c} m \\ r \end{array}\right) \cdot\left(\begin{array}{l} n \\ 0 \end{array}\right)=\left(\begin{array}{c} m+n \\ r \end{array}\right)
(m0)⋅(nr)+(m1)⋅(nr−1)+…+(mr)⋅(n0)=(m+nr)
学会用函数求解组合问题:
见例题:
r、w、y的幂次看做取对应球的个数。
有时候只需要组合数,不求解具体的组合,就可以令r=w=y=x,得到下式
因此就可以看出
例题2:
有一个这样的公式:
1
−
X
n
+
1
1
−
X
=
1
+
X
+
X
2
.
.
.
+
X
n
\frac{{1 - {X^{n+1}}}}{{1 - X}} = 1 + X + {X^2}... + {X^{n}}
1−X1−Xn+1=1+X+X2...+Xn
等比数列求和的公式,求出x^7的项数即可,答案为7。
母函数和序列之间的关系
已知序列{ai}的母函数为A(x)=
∑
i
=
0
∞
a
i
X
i
\sum\limits_{i = 0}^\infty {{{\rm{a}}_i}{X^i}}
i=0∑∞aiXi
基本公式:
(1)
(2)
(3)
整数的拆分
将整数n分解为若干正整数的和(允许分解的数字重复)⇔将n个无区别的球放进n个无区别的盒子里,盒子允许放多个球或空着。
不仅能知道能称出重量的类别,同时还能知道称出重量的方案数有几种。
这道例题没有限定各类邮票的使用数量,因此就用母函数来表示,最后所得项的系数即为贴出n分邮资的方案数。
Ferrers图像
上层比下层多一个格子
1)整数n拆分成k个整数的和的拆分数,和将n拆分成最大整数数为k的拆分数相等
2)整数n拆分成最多不超过k个整数的和的拆分数 和整数n1拆分成最大不超过整数k的和的拆分数 相等
深度学习
线性回归的简洁实现
与上一课程一样,首先生成数据集
import numpy as np
import torch
from torch.utils import data
from d2l import torch as d2l
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = d2l.synthetic_data(true_w, true_b, 1000) #通过人工数据合成的函数 生成对应的x和y
def load_array(data_arrays, batch_size, is_train=True): #@save
dataset = data.TensorDataset(*data_arrays)
return data.DataLoader(dataset, batch_size, shuffle=is_train)#每一次从中随机挑选batch_size的样本出来,shuffle是是否打乱顺序的参数
batch_size = 10
data_iter = load_array((features, labels), batch_size)
next(iter(data_iter)) # iter构造Python迭代器,使用next从迭代器中获取第一项
将features和labels作为API的参数传递,并通过数据迭代器指定batch_size。 此外,布尔值is_train表示是否希望数据迭代器对象在每个迭代周期内打乱数据。
生成得到数据集为:
# nn是神经网络的缩写
from torch import nn
net = nn.Sequential(nn.Linear(2, 1))
Sequential一个序列容器,用于搭建神经网络的模块被按照被传入构造器的顺序添加到nn.Sequential()容器中
指定每个权重参数应该从均值为0、标准差为0.01的正态分布中随机采样, 偏置参数将初始化为零。等价于手动实现w,b。
net[0].weight.data.normal_(0, 0.01) #用均值为0、标准差为0.01的正态分布来替换data
net[0].bias.data.fill_(0) # 偏置参数将初始化为零
定义损失函数
loss = nn.MSELoss()
定义优化算法
trainer = torch.optim.SGD(net.parameters(), lr=0.03) #传入所有参数 指定学习率
训练模型
通过调用net(X)生成预测并计算损失l(前向传播)。
通过进行反向传播来计算梯度。
通过调用优化器来更新模型参数。
num_epochs = 3
for epoch in range(num_epochs):
for X, y in data_iter:
l = loss(net(X) ,y)
trainer.zero_grad() #先把梯度清0
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)
输出的结果为:
我们可以使用PyTorch的高级API更简洁地实现模型。在PyTorch中,data模块提供了数据处理工具,nn模块定义了大量的神经网络层和常见损失函数。
softmax回归
在另一类情景中,模型输出可以是一个像图像类别这样的离散值。对于这样的离散值预测问题,我们可以使用诸如softmax回归在内的分类模型。和线性回归不同,softmax回归的输出单元从一个变成了多个,且引入了softmax运算使输出更适合离散值的预测和训练。
分类问题
一个简单的图像分类问题,其输入图像的高和宽均为2像素,且色彩为灰度。这样每个像素值都可以用一个标量表示。我们将图像中的4像素分别记为 x1,x2,x3,x4。假设训练数据集中图像的真实标签为狗、猫或鸡,可以用4像素表示出这3种动物,这些标签分别对应离散值 y1,y2,y3。我们通常使用离散的数值来表示类别,例如 y1=1,y2=2,y3=3
。如此,一张图像的标签为1、2和3这3个数值中的一个。虽然我们仍然可以使用回归模型来进行建模,并将预测值就近定点化到1、2和3这3个离散值之一,但这种连续值到离散值的转化通常会影响到分类质量。因此我们一般使用更加适合离散值输出的模型来解决分类问题。
softmax回归模型
softmax回归跟线性回归一样将输入特征与权重做线性叠加。与线性回归的一个主要不同在于,softmax回归的输出值个数等于标签里的类别数。
神经网络图描绘了上面的计算。softmax回归同线性回归一样,也是一个单层神经网络。由于每个输出 o1,o2,o3的计算都要依赖于所有的输入 x1,x2,x3,x4
,softmax回归的输出层也是一个全连接层。
将所有权重放到矩阵w,将特征放到向量x将偏置放到向量b那么输出可以这样表示
Softmax运算
由上文可知,我们根据输入得到输出o,但是不能直接把这个结果当初概率输出,因为1)没有限制输出结果的总和为1,2)某些输出结果可能为负值。因此应该想办法把原始输出转化为概率分布后再输出。这时候Softmax就是不错的选择了。
Softmax运算可以保持向量中每个元素的相对大小,并将其转换为总和为1的概率分布。 大致步骤如下
1)对o中的每一个元素应用指数化操作,得到一个新向量e
2)计算指数化得分的总和 sum_e
3)对于每个类别的指数化得分除以 sum_e。
这样就得到了概率分布的向量。
公式为
对于向量 y里面的每一个元素都为 ,且满足 0<=yi<=1 。由于softmax运算并未改变类别次序,只是分配了概率,所以还可以通过下式来选择最有可能的类别。
小批量样本的矢量化
进行Softmax运算的时候,是每次处理一个样本,但是这样比较慢。所以我们采用矢量计算。 小批量样本的矢量化是指在机器学习中,对于一批具有相同特征维度的样本数据进行并行计算的技术。通常情况下,将一批样本数据组织成一个矩阵的形式,每一列代表一个特征,每一行代表一个样本.
小批量样本的矢量化可以提升计算效率,并且可以充分利用现代硬件(如GPU)的优势。相比逐个处理样本,矢量化操作能够在单次计算中同时处理多个样本,从而减少了循环操作的开销,提高了计算速度.
softmax回归的矢量计算表达式为:
O = X W + b , Y ^ = s o f t m a x ( O ) . \begin{aligned} \mathbf{O} &= \mathbf{X} \mathbf{W} + \mathbf{b}, \\ \hat{\mathbf{Y}} & = \mathrm{softmax}(\mathbf{O}). \end{aligned} OY^=XW+b,=softmax(O).
图像分类数据集
首先导入需要的包或模块
%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()
通过框架中的内置函数将Fashion-MNIST数据集下载并读取到内存中
# 通过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)
Fashion-MNIST由10个类别的图像组成, 每个类别由训练数据集(train dataset)中的6000张图像 和测试数据集(test dataset)中的1000张图像组成。 因此,训练集和测试集分别包含60000和10000张图像。 测试数据集不会用于训练,只用于评估模型性能。
mnist_train[0][0].shape
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
"""绘制图像列表"""
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));
为了方便读取训练集和测试集更加方便,使用内置的数据迭代器,不用从零开始创建。在每次迭代中,数据加载器每次都会读取一小批量数据,大小为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'
输出结果为:
整合所有组件
定义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
输出结果:
由此,生成好了Fashion-MNIST数据集,个服装分类数据集,由10个类别的图像组成。
softmax回归从零开始实现
引入的Fashion-MNIST数据集, 并设置数据迭代器的批量大小为256
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)
初始化模型参数
在softmax回归中,输出与类别一样多。 因为数据集有10个类别,所以网络输出维度为10。 因此,权重将构成一个(784 \times 10)的矩阵, 偏置将构成一个(1 \times 10)的行向量。 与线性回归一样,将使用正态分布初始化我们的权重W,偏置初始化为0。
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)
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)
分母或规范化常数,有时也称为配分函数(其对数称为对数-配分函数)。 该名称来自统计物理学中一个模拟粒子群分布的方程。
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)
定义模型
def net(X):
return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)
定义损失函数
建一个数据样本y_hat,其中包含2个样本在3个类别的预测概率, 以及它们对应的标签y。
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]
def cross_entropy(y_hat, y):
return - torch.log(y_hat[range(len(y_hat)), y])
cross_entropy(y_hat, y) #实现交叉熵损失函数
分类精度
给定预测概率分布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())
accuracy(y_hat, y) / len(y)
为了计算精度,执行以下操作。 首先,如果y_hat是矩阵,那么假定第二个维度存储每个类的预测分数。 使用argmax获得每行中最大元素的索引来获得预测类别。 然后将预测类别与真实y元素进行比较。 由于等式运算符“==”对数据类型很敏感, 因此将y_hat的数据类型转换为与y的数据类型一致。 结果是一个包含0(错)和1(对)的张量。
输出结果为:
第一个样本的预测类别是2(该行的最大元素为0.6,索引为2),这与实际标签0不一致。 第二个样本的预测类别是2(该行的最大元素为0.5,索引为2),这与实际标签2一致。 因此,这两个样本的分类精度率为0.5。
定义一个实用程序类Accumulator,用于对多个变量进行累加。 在上面的evaluate_accuracy函数中, 我们在Accumulator实例中创建了2个变量, 分别用于存储正确预测的数量和预测的总数量
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]
由于使用随机权重初始化net模型, 因此该模型的精度应接近于随机猜测。 例如在有10个类别情况下的精度为0.1。
evaluate_accuracy(net, test_iter)
输出结果为:0.0625
训练
首先,定义一个函数来训练一个迭代周期。 updater是更新模型参数的常用函数,它接受批量大小作为参数。 它可以是d2l.sgd函数,也可以是框架的内置优化函数。
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]
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)
实现一个训练函数, 它会在train_iter访问到的训练数据集上训练一个模型net。 该训练函数将会运行多个迭代周期(由num_epochs指定)。 在每个迭代周期结束时,利用test_iter访问到的测试数据集对模型进行评估。
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
设置学习率为0.1:
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)
图像:
预测
def predict_ch3(net, test_iter, n=6): #@save
"""预测标签(定义见第3章)"""
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)
输出:
比较它们的实际标签(文本输出的第一行)和模型预测(文本输出的第二行)
总结
通过这周用神经网络框架实现的线性回归与上周手写的线性回归结合,知道库函数的作用,一定程度上消除了对陌生代码的恐惧感。
在实现softmax回归上面,定义了相当多的训练函数、使用了迭代器、 进行Animator类的函数重载,代码理解较为复杂,需要时间进行消化,同时进一步学习pytorch的学习。