一、感知机
与输出实数的线性回归模型以及输出分布概率的Softmax多分类模型不同,感知机(神经元)模型使用Sign函数进行输出处理,输出结果是离散变量,本质属于二分类模型。
对于给定的一组输入特征向量
x
i
∈
T
x_{i \in T}
xi∈T,权重
w
w
w以及偏差
b
b
b,感知机的输出如下:
O
=
σ
(
⟨
w
,
x
i
⟩
+
b
)
σ
(
x
)
=
{
1
i
f
x
i
>
0
−
1
o
t
h
e
r
w
i
s
e
O=\sigma(\langle w,x_i \rangle + b) \qquad \qquad \sigma(x)= \begin{cases} 1 \quad if \ x_i>0 \\ -1 \quad otherwise \end{cases}
O=σ(⟨w,xi⟩+b)σ(x)={1if xi>0−1otherwise
感知机的迭代策略
Initialize w = 0 w=0 w=0and b = 0 b=0 b=0
repeat
if y i [ ⟨ w , x i ⟩ + b ] ≤ 0 y_i[\langle w,x_i\rangle + b] \leq 0 yi[⟨w,xi⟩+b]≤0then
w ← w + y i x i w \leftarrow w+y_ix_i w←w+yixiand b ← b + y i b \leftarrow b+y_i b←b+yi
end if
until all classified correctly
实际上,感知机算法可以理解为一个采用随机梯度下降法的误分类驱动算法,即根据错误分类样本去优化模型,算法尝试在每轮训练迭代中着重于对过去的错误分类结果进行修正,使得下一次预测结果更接近于真实情况。
在上述感知机迭代策略中,
x
i
x_i
xi为样本
i
i
i的特征向量,
y
i
y_i
yi为样本
i
i
i 的标签(-1或1),
⟨
w
,
x
i
⟩
+
b
\langle w,x_i \rangle + b
⟨w,xi⟩+b为对应预测结果。当标签与预测值同号时,例如
y
i
>
0
y_i > 0
yi>0且
⟨
w
,
x
i
⟩
+
b
>
0
\langle w,x_i \rangle + b >0
⟨w,xi⟩+b>0时,结果分类正确,否则为误分类结果,需要对权重参数
w
w
w和偏差值
b
b
b进行更新,直到全部分类正确为止。
![](https://i-blog.csdnimg.cn/blog_migrate/941477f27311fa9355c5bb4e45ecb9fc.png)
![](https://i-blog.csdnimg.cn/blog_migrate/ed1782ce34c8a315ddd012268222b3a3.png)
![](https://i-blog.csdnimg.cn/blog_migrate/1876d23ddfd8893e587fe0d878ee802d.png)
![](https://i-blog.csdnimg.cn/blog_migrate/84d80298ea78edeb958ce9aaf70a85b7.png)
如图,在输入空间中存在狗和猫,以及一个分类超平面, 在不断添加狗和猫的过程中,超平面在不断变动,最终实现 对猫狗的正确分类。可以看到,对于超平面
⟨
w
,
x
i
⟩
+
b
\langle w,x_i \rangle + b
⟨w,xi⟩+b ,
w
w
w 可理解为斜率,
b
b
b 可理解为截距。在不断调整的过程中,超平面在沿着误分类方向不断进行调整,直到正确分类。这个过程也是误分类驱动的一种体现。
因此,上述过程可以理解为一个对参数
w
,
b
w,b
w,b不断优化,使得最终损失函数
L
(
y
,
w
,
b
)
L(y,w,b)
L(y,w,b)足够小的过程,即
l
(
y
,
x
,
w
)
=
m
a
x
(
0
,
−
y
⟨
w
,
x
⟩
+
b
)
l(y,x,w)=max(0,-y\langle w,x\rangle+b)
l(y,x,w)=max(0,−y⟨w,x⟩+b)
当分类正确时,
0
>
−
y
⟨
w
,
x
⟩
+
b
0 > -y\langle w,x\rangle+b
0>−y⟨w,x⟩+b,此时梯度为常数,不会进行优化;分类错误时,
0
<
−
y
⟨
w
,
x
⟩
+
b
0 < -y\langle w,x\rangle+b
0<−y⟨w,x⟩+b,即进入if条件,对
w
,
b
w,b
w,b进行更新。
在该损失函数中,每次优化时并非一次性优化全部误分类点,而是设置批量大小为
1
1
1,每次随机优化一个误分类点,使其梯度下降。对于每个固定的误分类点
(
x
i
,
y
i
)
(x_i,y_i)
(xi,yi),该损失函数对于
w
,
b
w,b
w,b的导数分别为
−
y
i
x
i
,
−
y
i
-y_ix_i, \ -y_i
−yixi, −yi,对
w
,
b
w,b
w,b的分别进行更新:
w
←
w
+
y
i
x
i
w \leftarrow w+y_ix_i
w←w+yixi
b
←
b
+
y
i
b \leftarrow b+y_i
b←b+yi
感知机的收敛定理
收敛定理负责决定感知机的运行何时停止。
对于数据半径为
r
r
r的数据空间,对于空间内的点
{
x
i
,
y
i
}
i
∈
1
:
N
\{x_i,y_i\}_{i \in 1:N}
{xi,yi}i∈1:N,存在一个平面,可以将点全部正确分类,满足
∣
∣
w
∣
∣
2
+
b
2
≤
1
||w||^2+b^2 \leq 1
∣∣w∣∣2+b2≤1,此时对于数据空间内的所有点,都存在一个余量
ρ
>
0
\rho > 0
ρ>0,使得超平面刚好完美分开数据集,此时存在:
y
i
(
w
′
⋅
x
+
b
′
)
≥
ρ
y_i(w' \cdot x + b') \geq \rho
yi(w′⋅x+b′)≥ρ
其中
w
′
,
b
′
w',b'
w′,b′为超平面刚好完美分开数据集时的参数,当对于第
k
k
k组点分类失误时,感知机对初始值
w
w
w和
b
b
b进行迭代,存在:
w
k
=
w
k
−
1
+
y
i
x
i
w_k=w_{k-1}+y_ix_i
wk=wk−1+yixi,
b
k
=
b
k
−
1
+
y
i
b_k=b_{k-1}+y_i
bk=bk−1+yi,即
w
^
k
=
w
^
k
−
1
+
y
i
x
i
\hat{w}_k=\hat{w}_{k-1}+y_ix_i
w^k=w^k−1+yixi,其中
w
^
\hat{w}
w^是将偏置
b
b
b并入权重
w
w
w所得。此时对于半径
r
r
r感知机算法在数据空间中的迭代次数
k
k
k存在上界:
k
≤
r
2
ρ
2
k \leq \frac{r^2}{\rho^2}
k≤ρ2r2(具体证明可见统计学习方法 P31)
感知机与XOR问题
感知机本质是一个线性的二分类算法,依靠一个线性超平面对数据进行分类,但是在面对XOR(异或)问题时,XOR在二维平面的分布如下图所示:
可以看到,不存在一个线性超平面可以将数据区分。
正因为感知机无法对XOR这类多分类函数进行拟合,导致了第一次机器学习寒冬的到来。
二、多层感知机与激活函数
感知机模型本质是一个线性模型,无法解决XOR这类非线性问题。因此提出了多层感知机的概念,通过两个感知机层对两层线性结果进行同或操作,即可求解XOR问题。
如上图所示,第一层感知机为蓝色,将
1
,
3
1,3
1,3分为一类,
2
,
4
2,4
2,4分为一类。第二层感知机为黄色,将
1
,
2
1,2
1,2分为一类,
3
,
4
3,4
3,4分为一类。最后再对两个感知机层的结果进行同或操作,相同时为真,相异时为假。即可求解XOR问题。
隐藏层
感知机模型在输出时通过施加Sign函数完成二分类任务,属于线性函数,无法拟合非线性问题。多层感知机是对感知机模型的一种拓展,通过加入的多个隐藏层共同完成对非线性函数的拟合。
以一个单隐藏层的分类模型为例,模型的输入由数据维度决定,输出由类别数决定,这二者由数据所决定,中间的隐藏层的大小和层数则是一个超参数,可进行设置修改。以下图为例:
需要注意的是,为方便进行矩阵运算,输入层到隐藏层以及隐藏层到输出层的权重数据维度需要匹配。
激活函数
激活函数主要通过计算加权和以及增添偏置,来决定该神经元是否被激活,以模拟真实神经元的激活过程。
从输入层到隐藏层,每个元素需要经历一个非线性的激活函数,来使得模型得以求解非线性问题。即:
h
=
σ
(
w
1
x
+
b
1
)
o
=
w
2
T
h
+
b
2
h = \sigma(w_1x+b_1) \\ o = w_2^Th+b_2
h=σ(w1x+b1)o=w2Th+b2
如果不存在非线性的激活函数,无论经过多少隐藏层,施加多少权重,输出结果
o
o
o得到的将仍是一个线性结果
o
=
W
′
x
+
b
o=W'x+b
o=W′x+b,此时模型将无法对非线性问题进行拟合。
非线性函数的存在可以使得模型不再轻易退化为非线性模型,而且多个施加非线性函数的隐藏层可以使得模型更好的拟合多种复杂特征,具备更好的表达性,但随之而来的代价是计算量的增大。
最初采用的激活情况是符号函数,定义域大于零时激活,小于零时失活:
但后续人们发现,Sign函数并非处处可导,且可导处为0,因此在训练时存在梯度消失问题,逐渐被放弃。
Sigmoid 激活函数
Sigmoid函数算是对
σ
\sigma
σ函数的一种软化版本,解决了
σ
\sigma
σ函数难求导的问题,其值域为
(
0
,
1
)
(0,1)
(0,1) 区间内。
s
i
g
m
o
i
d
(
x
)
=
1
1
+
e
x
p
(
−
x
)
sigmoid(x)=\frac{1}{1+exp(-x)}
sigmoid(x)=1+exp(−x)1
Tanh 激活函数
tanh激活函数与sigmoid激活函相似,属于Sign函数的软化版本,其值域为
(
−
1
,
1
)
(-1,1)
(−1,1).
t
a
n
h
(
x
)
=
1
−
e
x
p
(
−
2
x
)
1
+
e
x
p
(
−
2
x
)
tanh(x)=\frac{1-exp(-2x)}{1+exp(-2x)}
tanh(x)=1+exp(−2x)1−exp(−2x)
tanh激活函数可以理解为sigmoid函数的一种变形,二者关系如下:
t
a
n
h
(
x
)
+
1
=
2
s
i
g
m
o
i
d
(
2
x
)
tanh(x)+1=2sigmoid(2x)
tanh(x)+1=2sigmoid(2x)
在计算机计算时,由于二进制无法精确表达十进制浮点数,例如一个十进制小数,在计算机中依靠01二进制是无法精确表达的,只能依靠多个 1 2 n \frac{1}{2^n} 2n1之和来不断逼近,而且计算机中没有无限的空间去存储这些小数,需要根据float或double数据类型规定长度。这种误差会导致上述公式在计算时左右两侧不相等,实际在需要忽略掉精度误差后,二者输出是相等的。
二者导数图像如下:
![](https://i-blog.csdnimg.cn/blog_migrate/253bbea8f77855dc165041cc7c008f5d.png)
![](https://i-blog.csdnimg.cn/blog_migrate/02341097d49e7b72e96aabf1e32eff86.png)
可以发现,sigmoid函数导数最大值为$0.25$,在进行反向传播进行连续相乘时,sigmoid的导数值会使得梯度消失现象非常严重;tanh函数导数最大值为$1$,可以对梯度消失现象进行缓解。 #### ReLU 激活函数 观察sigmoid和tanh函数可以发现,二者属于指数运算,这些指数运算会导致非常大的时间开销,ReLU的诞生很好的解决了这一问题,无需进行指数运算,收敛速度远快于sigmoid和tanh函数,成为最常用的激活函数。
R
e
l
U
(
x
)
=
m
a
x
(
x
,
0
)
RelU(x)=max(x,0)
RelU(x)=max(x,0)
此外,ReLU会使得部分神经元的输出为0,某种程度上实现了类似Dropout的效果,造成了网络的稀疏性,有效减少参数间依存关系,从而缓解了过拟合现象的发生。
需要注意的是,如果输入精确等于0,此时ReLU函数并不可导,此时默认使用左侧导数,即令输入为0时的导数为0,忽略掉不可导的情况。
如果微妙的边界条件很重要,我们很可能是在研究数学而非工程
三、多层感知机代码实现
import torch
from torch import nn
from d2l import torch as d2l
# 数据集引用及划分
batch_size = 256
# 引入fashion_mnist数据集,每次按batch_size获取数据,对模型进行训练
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
# 模型初始化
num_inputs = 784 # 输入维度
num_hiddens = 256 # 隐藏层个数
num_outputs = 10 # 输出维度(种类数)
# 权重设置及初始化
w_1 = nn.Parameter(torch.randn(num_inputs, num_hiddens, requires_grad=True) * 0.01) # 输入层 -> 隐藏层 权重
b_1 = nn.Parameter(torch.zeros(num_hiddens, requires_grad=True))
w_2 = nn.Parameter(torch.randn(num_hiddens, num_outputs, requires_grad=True) * 0.01) # 隐藏层 -> 输出层 权重
b_2 = nn.Parameter(torch.zeros(num_outputs, requires_grad=True))
params = [w_1, b_1, w_2, b_2]
在SGD等函数中,存在backward()函数,requires_grad=True可以开启参数追踪,实现参数的自动求导
# 激活函数
def relu(X):
a = torch.zeros_like(X) # 设置一个与X维度相同的全 0 矩阵
return torch.max(X, a) # relu = max(0,x)
# 模型设计
def net(X):
X = X.reshape((-1, num_inputs)) # 将X的矩阵维度重新设计,使得可以与W_1矩阵进行矩阵运算
H = relu(X@w_1 + b_1) # @:矩阵乘
output = H@w_2 + b_2
return output
# 损失函数
loss = nn.CrossEntropyLoss(reduction='none')
# reduction='none': 每个step的loss单独保留,返回值(batch_size, num_steps)
# 训练
num_epuchs = 10 # epoch决定训练轮次
lr = 0.1 # 学习率设置
updater = torch.optim.SGD(params, lr) # 优化函数,负责在每轮中对权重进行更新
d2l.train_ch3(net, train_iter, test_iter, loss, num_epuchs, updater)
# 在测试集中应用该模型
d2l.predict_ch3(net, test_iter)
四、作业
- 在所有其他参数保持不变的情况下,更改超参数num_hiddens的值,并查看此超参数的变化对结果有何影响。确定此超参数的最佳值。
设置num_hiddens = 8,16,256,512,结果如下:
8 | 16 |
---|---|
![]() | ![]() |
256 | 512 |
![]() | ![]() |
根据实验,随着num_hiddens的增加,模型的train_loss也随之降低,模型拟合的也越好,因此超参数num_hiddens的值设置在256左右即可,当超过256时,模型的提升并不明显。
-
尝试添加更多的隐藏层,并查看它对结果有何影响。
额外添加一个隐藏层,num_hiddens均为256
额外添加两个隐藏层,所有num_hiddens均为256
可以看到,在隐藏层个数增加到三个时,模型性能反而下降。这也说明了,在数据并不复杂时,隐藏层数量并非越多越好。过多的隐藏层数量反而会使得模型计算开销增大,性能下降。 -
改变学习速率会如何影响结果?保持模型架构和其他超参数(包括轮数)不变,学习率设置为多少会带来最好的结果?
学习率设置为0.01
在不改变epoch的前提下,降低学习率会使得模型的loss收敛速度降低,在有限epoch内无法很好的完成训练。学习率设置为1
当模型增大学习率时,可以看到模型发生了振荡,导致模型无法收敛,lioss值无法降低到最优位置。
结论: 模型的学习率不应过大或过小,过大的学习率会使得模型无法找到最优解,甚至螺旋上升。过小的学习率则会使得模型收敛速度下降,要么在有限的epoch内无法完成训练,要么在局部最小值而非全局最小值处收敛。