摘要
本周在深度学习方面,学习梯度、自动求导以及实现人造数据集进行线性回归的实验;在组合数学方面,对第一章(除了排序生成算法)进行复习。
This week, I were conducted on linear regression experiments with artificial datasets and learning gradients, automatic differentiation in terms of deep learning. In combinatorial mathematics, I also review Chapter 1 (except for sorting generation algorithms).
组合数学
加法原理:集合S的划分,|s| = |s1|+|s2|+|s3|。。。。,各个集合之间是没有交集的。
乘法原理:使用时,单个步骤不能完成目标,只能所有步骤使用完成才能完成,因此用乘法。
任何一个数都能化成素数幂的形式;整数的唯一分解定理。
比如 n =45 求能整除n 的数 2*3= 6
一一对应:将一个问题转化成另一个问题11对应,在处理组和技术问题时,常常通过问题11对应实现模型的转换。
Cayley定理:n个有标号的顶点的树的 数目等于
n
n
−
2
{{\rm{n}}^{n - 2}}
nn−2
选择顺序对乘法原理的使用:一个任务若分为若干个子任务,若完成某个子任务的途径影响整个任务的完成,那么就不能使用乘法原理。
组合和排列相关习题
循环排列
组合
这个例题与上题不同的是,没有顺序,因此在最后需要除以3!
多重集的排列和组合
有限多重集S的r-组合这个问题可以转化成求解x1+x2+x3+。。。xk=r的非负整数解的个数。
深度学习
标量导数
导数是切线的斜率
y
a
x
n
exp
(
x
)
log
(
x
)
sin
(
x
)
d
y
d
x
0
n
x
n
−
1
exp
(
x
)
1
x
cos
(
x
)
\begin{array}{c|ccccc} y & a & x^n & \exp (x) & \log (x) & \sin (x) \\ \hline \frac{d y}{d x} & 0 & n x^{n-1} & \exp (x) & \frac{1}{x} & \cos (x) \end{array}
ydxdya0xnnxn−1exp(x)exp(x)log(x)x1sin(x)cos(x)
y
u
+
v
u
v
y
=
f
(
u
)
,
u
=
g
(
x
)
d
y
d
x
d
u
d
x
+
d
v
d
x
d
u
d
x
v
+
d
v
d
x
u
d
y
d
u
d
u
d
x
\begin{array}{c|ccc} y & u+v & u v & y=f(u), u=g(x) \\ \hline \frac{d y}{d x} & \frac{d u}{d x}+\frac{d v}{d x} & \frac{d u}{d x} v+\frac{d v}{d x} u & \frac{d y}{d u} \frac{d u}{d x} \end{array}
ydxdyu+vdxdu+dxdvuvdxduv+dxdvuy=f(u),u=g(x)dudydxdu
亚导数
将导数拓展到不可微的函数
∂
∣
x
∣
∂
x
=
{
1
if
x
>
0
−
1
if
x
<
0
a
if
x
=
0
,
a
∈
[
−
1
,
1
]
\frac{\partial|x|}{\partial x}= \begin{cases}1 & \text { if } x>0 \\ -1 & \text { if } x<0 \\ a & \text { if } x=0, \quad a \in[-1,1]\end{cases}
∂x∂∣x∣=⎩
⎨
⎧1−1a if x>0 if x<0 if x=0,a∈[−1,1]
例如在x=0这一点时,当x>0,导数值取1,x<0导数值取-1;当x=0时,导数值可以取
[
−
1
,
1
]
[-1,1]
[−1,1]之间的任何值
梯度
导数在几何上可看作某一函数上在某一点的切线的斜率
1)对于单个自变量的函数,导数就是该点的梯度。
2)对于多个自变量的函数,我们对每个自变量都进行求导,
梯度就是每个导数所组成的向量。
梯度方向是某一函数在某一点上的变化率最大的方向,由此进行迭代计算可找到函数的最值。
标量和向量构成的求导表格
x x x | X X X | |
---|---|---|
y y y | ∂ y ∂ x \frac{{\partial {\rm{y}}}}{{\partial x}} ∂x∂y | ∂ y ∂ X \frac{{\partial y}}{{\partial X}} ∂X∂y |
Y Y Y | ∂ Y ∂ x \frac{{\partial Y}}{{\partial x}} ∂x∂Y | ∂ Y ∂ X \frac{{\partial Y}}{{\partial X}} ∂X∂Y |
小写字母表示标量,大写字母表示向量
-
∂
y
∂
x
\frac{{\partial {\rm{y}}}}{{\partial x}}
∂x∂y:
标量对标量的求导,结果还是标量 -
∂
y
∂
X
\frac{{\partial y}}{{\partial X}}
∂X∂y:
[n,1]向量对标量的求导,结果是得到一个[1,n]横向量
∂
y
∂
x
=
[
∂
y
∂
x
1
,
∂
y
∂
x
2
,
…
,
∂
y
∂
x
n
]
\frac{\partial y}{\partial \mathbf{x}}=\left[\frac{\partial y}{\partial x_1}, \frac{\partial y}{\partial x_2}, \ldots, \frac{\partial y}{\partial x_n}\right]
∂x∂y=[∂x1∂y,∂x2∂y,…,∂xn∂y]
-
∂
Y
∂
x
\frac{{\partial Y}}{{\partial x}}
∂x∂Y:
标量对向量的求导,结果还是列向量
∂ y ∂ x = [ ∂ y 1 ∂ x ∂ y 2 ∂ x ⋮ ∂ y m ∂ x ] \frac{\partial \mathbf{y}}{\partial x}=\left[\begin{array}{c} \frac{\partial y_1}{\partial x} \\ \frac{\partial y_2}{\partial x} \\ \vdots \\ \frac{\partial y_m}{\partial x} \end{array}\right] ∂x∂y= ∂x∂y1∂x∂y2⋮∂x∂ym -
∂
Y
∂
X
\frac{{\partial Y}}{{\partial X}}
∂X∂Y:
向量关于向量的求导,生成的是一个矩阵。
∂ y ∂ x = [ ∂ y 1 ∂ x ∂ y 2 ∂ x ⋮ ∂ y m ∂ x ] = [ ∂ y 1 ∂ x 1 , ∂ y 1 ∂ x 2 , … , ∂ y 1 ∂ x n ∂ y 2 ∂ x 1 , ∂ y 2 ∂ x 2 , … , ∂ y 2 ∂ x n ⋮ ∂ y m ∂ x 1 , ∂ y m ∂ x 2 , … , ∂ y m ∂ x n ] \frac{\partial \mathbf{y}}{\partial \mathbf{x}}=\left[\begin{array}{c} \frac{\partial y_1}{\partial \mathbf{x}} \\ \frac{\partial y_2}{\partial \mathbf{x}} \\ \vdots \\ \frac{\partial y_m}{\partial \mathbf{x}} \end{array}\right]=\left[\begin{array}{c} \frac{\partial y_1}{\partial x_1}, \frac{\partial y_1}{\partial x_2}, \ldots, \frac{\partial y_1}{\partial x_n} \\ \frac{\partial y_2}{\partial x_1}, \frac{\partial y_2}{\partial x_2}, \ldots, \frac{\partial y_2}{\partial x_n} \\ \vdots \\ \frac{\partial y_m}{\partial x_1}, \frac{\partial y_m}{\partial x_2}, \ldots, \frac{\partial y_m}{\partial x_n} \end{array}\right] ∂x∂y= ∂x∂y1∂x∂y2⋮∂x∂ym = ∂x1∂y1,∂x2∂y1,…,∂xn∂y1∂x1∂y2,∂x2∂y2,…,∂xn∂y2⋮∂x1∂ym,∂x2∂ym,…,∂xn∂ym
拓展到矩阵
也就是说,只要用向量或矩阵对标量、向量、矩阵进行求导,所用的向量或矩阵就需要进行转置。
自动求导
向量链式法则
∂
y
∂
x
=
∂
y
∂
u
∂
u
∂
x
∂
y
∂
x
=
∂
y
∂
u
∂
u
∂
x
∂
y
∂
x
=
∂
y
∂
u
∂
u
∂
x
\frac{\partial y}{\partial \mathbf{x}}=\frac{\partial y}{\partial u} \frac{\partial u}{\partial \mathbf{x}} \quad \frac{\partial y}{\partial \mathbf{x}}=\frac{\partial y}{\partial \mathbf{u}} \frac{\partial \mathbf{u}}{\partial \mathbf{x}} \quad \frac{\partial \mathbf{y}}{\partial \mathbf{x}}=\frac{\partial \mathbf{y}}{\partial \mathbf{u}} \frac{\partial \mathbf{u}}{\partial \mathbf{x}}
∂x∂y=∂u∂y∂x∂u∂x∂y=∂u∂y∂x∂u∂x∂y=∂u∂y∂x∂u
与标量的链式法则一样
例子:
例子2:
自动求导
计算图
将代码分解成操作子,将计算表示成一个无环图
自动求导的两种模式
链式法则:
1)正向累积:
先计算u1关于x的导数,再计算u2关于u1的导数,相乘后再继续一下部。
2)反向累积(反向传递):
先计算最终的函数导数,依次计算最终函数与每一个中间值的导数,然后向前推进,最后求出最前面值的偏导。使用反向传递时,需要存放正向的所有中间结果。因此内存复杂度是
O
(
n
)
O(n)
O(n),这也是为什么深度学习需要高性能大显存的GPU的原因。
自动求导代码
import torch
x = torch.arange(4.0)
x.requires_grad_(True) # 等价于x=torch.arange(4.0,requires_grad=True)
x.grad # 默认值是None
y = 2 * torch.dot(x, x)
y.backward()
x.grad == 4 * x
x.grad.zero_() #默认情况下 函数_表示重写,因为pytorch会累积梯度,因此我们需要清零
y = x.sum()
y.backward()
x.grad
非标量变量的反向传播
深度学习中非标量进行非标量求导比较少出现的情况。
# 对非标量调用backward需要传入一个gradient参数,该参数指定微分函数关于self的梯度。
x.grad.zero_()
y = x * x
# 等价于y.backward(torch.ones(len(x)))
y.sum().backward()
x.grad
将某些计算移动到记录的计算图之外
x.grad.zero_()
y = x * x
u = y.detach() #表示将u与x脱离关系,u只是一个常量数值上等于x*x
z = u * x
z.sum().backward()
x.grad == u
Python控制流的梯度计算
def f(a):
b = a * 2
while b.norm() < 1000:
b = b * 2
if b.sum() > 0:
c = b
else:
c = 100 * b
return c
a = torch.randn(size=(), requires_grad=True)
d = f(a)
d.backward()
a.grad == d / a
f(a)函数它在其输入a中是分段线性的。 因此对于任何a,存在某个常量标量k,使得f(a)=k*a,其中k的值取决于输入a,因此可以用d/a验证梯度是否正确。
线性回归
还是引入房价购买的例子,引出线性回归的内容。
同样是目标(房屋价格)可以表示为特征(面积和房龄)的加权和。
1)用下列公式来表示:
p
r
i
c
e
=
w
a
r
e
a
⋅
a
r
e
a
+
w
a
g
e
⋅
a
g
e
+
b
.
\mathrm{price} = w_{\mathrm{area}} \cdot \mathrm{area} + w_{\mathrm{age}} \cdot \mathrm{age} + b.
price=warea⋅area+wage⋅age+b.
2)机器学习领域表示:
y
^
=
w
1
x
1
+
.
.
.
+
w
d
x
d
+
b
.
\hat{y} = w_1 x_1 + ... + w_d x_d + b.
y^=w1x1+...+wdxd+b.
3)用矩阵-向量乘法表示:
y
^
=
X
w
+
b
{\hat{\mathbf{y}}} = \mathbf{X} \mathbf{w} + b
y^=Xw+b
线性模型可以看做是单层神经网络
衡量预估质量
比较真实值和预估值,例如房屋售价和股价,假设y是真实值,
平方损失函数:
l
(
y
,
y
^
)
=
1
2
(
y
−
y
^
)
2
.
l({y},\hat y) = \frac{1}{2} \left({y} -\hat y\right)^2.
l(y,y^)=21(y−y^)2.
训练数据
收集一些数据点来决定参数值(权重和偏差),这些被称为训练数据,通常越多越好,假设有n个样本,记
X
=
[
x
1
,
x
2
,
.
.
.
.
.
,
x
n
]
T
X = {[{x_{1,}}{x_{2,}}.....,{x_n}]^T}
X=[x1,x2,.....,xn]T
Y
=
[
y
1
,
y
2
,
.
.
.
.
.
,
y
n
]
T
Y = {[{y_1},{y_2},.....,{y_n}]^T}
Y=[y1,y2,.....,yn]T
参数学习
训练损失
对于模型在每一个样本上的值求均值,就能得到损失函数。
L
(
w
,
b
)
=
1
n
∑
i
=
1
n
l
(
i
)
(
w
,
b
)
=
1
n
∑
i
=
1
n
1
2
(
w
⊤
x
(
i
)
+
b
−
y
(
i
)
)
2
.
L(\mathbf{w}, b) =\frac{1}{n}\sum_{i=1}^n l^{(i)}(\mathbf{w}, b) =\frac{1}{n} \sum_{i=1}^n \frac{1}{2}\left(\mathbf{w}^\top \mathbf{x}^{(i)} + b - y^{(i)}\right)^2.
L(w,b)=n1i=1∑nl(i)(w,b)=n1i=1∑n21(w⊤x(i)+b−y(i))2.
我们的目标是要使损失函数值最小,这样使的模型与真实值的误差最小,因 此就需要最小化损失来学习参数。
w
∗
,
b
∗
=
argmin
w
,
b
L
(
w
,
b
)
.
\mathbf{w}^*, b^* = \operatorname*{argmin}_{\mathbf{w}, b}\ L(\mathbf{w}, b).
w∗,b∗=w,bargmin L(w,b).
显示解
将偏差加入权重,变成对应的X向量和W向量
ℓ
(
X
,
y
,
w
)
=
1
2
n
∥
y
−
X
w
∥
2
\ell(\mathbf{X}, \mathbf{y}, \mathbf{w})=\frac{1}{2 n}\|\mathbf{y}-\mathbf{X w}\|^2
ℓ(X,y,w)=2n1∥y−Xw∥2
然后对w求偏导
∂
∂
w
ℓ
(
X
,
y
,
w
)
=
−
1
n
(
y
−
X
w
)
T
X
\frac{\partial}{\partial \mathbf{w}} \ell(\mathbf{X}, \mathbf{y}, \mathbf{w})=-\frac{1}{n}(\mathbf{y}-\mathbf{X w})^T \mathbf{X}
∂w∂ℓ(X,y,w)=−n1(y−Xw)TX
最优解是满足以下三个条件
∂
∂
w
ℓ
(
X
,
y
,
w
)
=
0
\frac{\partial}{\partial \mathbf{w}} \ell(\mathbf{X}, \mathbf{y}, \mathbf{w})=0
∂w∂ℓ(X,y,w)=0
根据函数图像得,是凸函数,最优解是满足梯度等于0
1
n
(
y
−
X
w
)
T
X
=
0
\frac{1}{n}(\mathbf{y}-\mathbf{X w})^T \mathbf{X}=0
n1(y−Xw)TX=0
w
∗
=
(
X
T
X
)
−
1
X
y
\mathbf{w}^*=\left(\mathbf{X}^T \mathbf{X}\right)^{-1} \mathbf{X} \mathbf{y}
w∗=(XTX)−1Xy
梯度下降
当没有显示解的时候,就需要用梯度下降的方法来找到对应的解,使损失函数最小化。
学习率
η
η
η不能太大也不能太小
在实际过程中,很少使用梯度下降,深度学习中,最常见的是使用小批量随机梯度下降。是因为在使用梯度下降时,要对整个训练集上的损失函数进行求导。而损失函数是对整个样本而言的平均损失。因此计算的求导的话,代价运算代价是非常高的。
因此可以随机采样B个样本来近似损失。
1
b
∑
i
∈
I
b
t
R
(
x
i
,
y
i
,
w
)
\frac{1}{b} \sum_{i \in I_b} t^{\mathbb{R}}\left(\mathbf{x}_i, y_i, \mathbf{w}\right)
b1i∈Ib∑tR(xi,yi,w)
b是批量大小,又是另外一个重要超参数,和学习率一样,同样不能大小,不适合并行来最大利用计算资源。也不能太大,内存消耗太大。
%matplotlib inline #嵌入到matplotlib里面
import random
import torch
from d2l import torch as d2l #导入d2l,存放平常使用的函数
据带有噪声的线性模型构造一个人造数据集
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)
输出样本
d2l.set_figsize()
d2l.plt.scatter(features[:, (1)].detach().numpy(), labels.detach().numpy(), 1); #特征第一列
样本散点图
接下来我们要每次读取一个小批量样本,打乱样本下标
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
for X, y in data_iter(batch_size, features, labels):
print(X, '\n', y)
break
输出结果为:
自此人造数据集初始化完成。
定义初始化模型参数
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True) #偏差
要计算线性模型的输出,只需计算输入特征 X \mathbf{X} X和模型权重 w \mathbf{w} w的矩阵-向量乘法后加上偏置 b b b。
def linreg(X, w, b): #@save
"""线性回归模型"""
return torch.matmul(X, w) + b
定义损失函数
def squared_loss(y_hat, y):
#均方误差
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
#一个可能是行向量一个可能是列向量,因此还是同一格式
定义优化算法
def sgd(params, lr, batch_size):
# 小批量随机梯度下降
with torch.no_grad(): #先不需要计算梯度
for param in params:
param -= lr * param.grad / batch_size #因为在损失函数定义时,没有进行除以均值,因此在梯度计算的时候除。
param.grad.zero_() #因为pytorch会不断累加变量的梯度,因此每更新一次参数,就要让其对应梯度清零
训练过程
lr = 0.03
num_epochs = 3 #声明超参数 学习率η和随机率b
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的小批量损失
# 因为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}')
总结
本周在实现线性回归实验时,发现在pytorch上面存在基础问题,跟不上网课的节奏,需要下来并行学习python的内容,以及相关深度学习方面常用的库函数。