手把手教学 M L P MLP MLP 算法实现
在上一次的推送 M L P MLP MLP算法推导小记中对反向传播进行了详细的推导, 本文将详细讲解该算法如何用 n u m p y numpy numpy来实现。
本次实验中输入层为4个结点,隐藏层为20个结点,再加上偏置,激活函数为 t a n h tanh tanh,输出层为3个结点,损失函数为均方误差 ,上次的推导也是基于此进行的。
对于不同的激活函数或者损失函数的推导都类似,可以基于上次的推送重新推导出网络权值更新公式即可。本文的激活函数和损失函数都是固定的,是小鱼我之前一次实验的精简版本,后面如果大家需要,可以推出能够自定义激活函数和损失函数的版本。
M L P MLP MLP也可以称为全连接前馈神经网络,理解了 M L P MLP MLP也可以帮助我们更好的理解深度学习框架中的全连接层。
1. M L P MLP MLP 理解
对于 M L P MLP MLP网络已经很熟悉的就可以跳过这部分, 这里主要是可以让我们对多层感知器的工作流程有一个直观的认识。
首先要明白这次实验是要对鸢尾花数据集进行分类, 网络可以简单的看为一个函数,函数中有参数可以调整,从而调整函数的输出值。
-
输入有四个数据, 分别为四个特征
-
输出有三个数据, 形式为[1,0,0](类别1), [ 1 , 0 , 0 ] , [ 0 , 1 , 0 ] , [ 0 , 0 , 1 ] [1,0,0],[0,1,0],[0,0,1] [1,0,0],[0,1,0],[0,0,1]代表三个类别.
对于训练过程就是给一组四个特征,通过调整函数里的参数,使得函数的输出为对应的类别,如[1,0,0]。本次实验中的batch大小为1,即每次采用一组数据进行训练。将训练集中的所有数据都训练一遍就称为一个epoch。
当通过函数里调整过的参数计算得出的输出,能够让我们判断该组特征是属于鸢尾花的什么类别,就可以认为训练完成。
测试时,我们只需要将测试数据输入函数,取输出三个数据中最大的下标,就可以得到该特征对应的鸢尾花预测类别。
2. 网络工作流程
为了对网络工作的流程说明清楚, 本文取数据集中的第一组数据为例, 在实验中将所有数据一组一组的送入网络进行训练即可
sepal length | sepal width | petal length | petal width | class |
---|---|---|---|---|
5.1cm | 3.5cm | 1.4cm | 0.2cm | Iris-setosa |
那么对于 I r i s − s e t o s a Iris-setosa Iris−setosa 这一类别的label表示为
y_{1} | y_{2} | y_{3} |
---|---|---|
1 | 0 | 0 |
首先要初始化网络, 包括输入结点数为4, 隐藏层结点数为20, 输出层结点数为3, 网络权值 w i j [ 5 , 20 ] w_{i j}[5 ,20] wij[5,20] (其中第一行1*20为隐藏层的偏置), w j k [ 20 , 3 ] w_{j k}[20,3] wjk[20,3]的随机初始化 ,学习率 η \eta η 为0.001
对于训练过程
正向传播
-
输入层:输入大小为1*4的矩阵 [ 5.1 , 3.5 , 1.4 , 0.2 ] [5.1, 3.5, 1.4, 0.2] [5.1,3.5,1.4,0.2]
-
隐藏层:输入的1*4的矩阵加上偏置项,相当于输入(1,5)的矩阵 [ 1 , 5.1 , 3.5 , 1.4 , 0.2 ] [1,5.1, 3.5, 1.4, 0.2] [1,5.1,3.5,1.4,0.2] 与隐藏层的网络权值 w i j [ 5 , 20 ] w_{i j}[5 ,20] wij[5,20] 内积得到(1,20)的矩阵
-
输出层:输入(1,20)的矩阵与输出层的网络权值 w j k [ 20 , 3 ] w_{j k}[20,3] wjk[20,3] 内积得到(1,3)的矩阵
-
(1,3)的矩阵即为 y k y_{k} yk ,但因为网络权值在一定范围内随机初始化的,因此现在的输出肯定是不满足要求 [ 1 , 0 , 0 ] [1,0,0] [1,0,0]
反向传播
-
由于此时的输出不满足要求,因此要去更新网络权值, 根据前面推出的公式就可以计算出 ∂ E n ∂ w k j \frac{\partial E_{n}}{\partial w_{k j}} ∂wkj∂En以及 ∂ E n ∂ w j i \frac{\partial E_{n}}{\partial w_{j i}} ∂wji∂En, 对于梯度的计算,小鱼我认为主要在于推导,当推导出公式后,其计算过程就是将公式用代码表示出来就可以了。
-
接着分别去更新 w i j [ 5 , 20 ] w_{i j}[5 ,20] wij[5,20], w j k [ 20 , 3 ] w_{j k}[20,3] wjk[20,3] 即可
就这样进行迭代训练, 在一次又一次的训练中, 正向传播的输出就被慢慢纠正, 最后趋近于 [ 1 , 0 , 0 ] [1,0,0] [1,0,0] 了
对于测试过程, 那就是只要用到正向传播的过程, 得到网络的输出, 判断是哪一类别, 就可以得知分类是否正确
3. 正向传播过程
3.1 输入层
输入层为4个特征结点, 表示为
x
i
x_{i}
xi, 其中
i
{i}
i 代表第
i
{i}
i 个特征, 其中有一个常数1的输入为偏置。
x
i
′
=
[
1
,
x
i
]
x_{i}' =\left[\begin{array}{ll} {1},&x_{i} \end{array}\right]
xi′=[1,xi]
3.2 隐藏层
隐藏层为20个结点, 因此隐藏层网络的权值为 w i j w_{i j} wij, 即一个大小为(5, 20)的矩阵, 其转置为 w j i w_{j i} wji
进入隐藏层第一部分后输出为
a
j
a_{j}
aj , 输出为一个大小(1,20)的向量其为
a
j
=
∑
i
=
1
d
w
j
i
[
x
1
,
.
.
.
,
x
i
]
+
w
j
0
=
∑
i
=
0
d
w
j
i
(
1
)
x
i
′
\begin{aligned} a_{j} &=\sum_{i=1}^{d} w_{j i} [x_{1},...,x_{i}] + w_{j 0}\\&=\sum_{i=0}^{d} w_{j i}^{(1)} x'_{i} \end{aligned}
aj=i=1∑dwji[x1,...,xi]+wj0=i=0∑dwji(1)xi′
d
d
d 为输入结点的个数
经过隐藏层的激活函数
t
a
n
h
(
x
)
tanh \left(x \right)
tanh(x) , 输出为
z
j
z_{j}
zj , 仍然为一个(1,20)的向量
z
j
=
tanh
(
a
j
)
\begin{aligned} z_{j} &=\tanh \left(a_{j}\right) \end{aligned}
zj=tanh(aj)
隐藏层的 t a n h tanh tanh 函数的实现如下
def tanh(x):
return (np.exp(x)-np.exp(-x))/(np.exp(x)+np.exp(-x))
由于输入的为(5,1)的数组, 因此需要转置, 再与 w j i w_{j i} wji 进行矩阵点乘, 输出 z j z_{j} zj 为(1,20)的数组
# Calculate hidden layer output
# input: (1,4), W_ij:{Bias(0,20),W_ij(1:,20)}, output: (1,20)
a_j = np.dot(X,self.W_ij[1:,:]) + self.W_ij[0,:]
z_j = self.tanh(a_j)
3.3 输出层
输出层为3个结点, 因为采用独热编码表示, 并且本文中一共有三个分类, 因此输出层的网络权值为
w
j
k
w_{j k}
wjk , 即一个大小为(20, 3)的矩阵, 其转置矩阵为
w
k
j
w_{k j}
wkj , 输出为
y
k
=
∑
j
=
0
K
w
k
j
(
2
)
z
j
\begin{aligned} y_{k} &=\sum_{j=0}^{K} w_{k j}^{(2)} z_{j} \end{aligned}
yk=j=0∑Kwkj(2)zj
输入的为(1,20)的数组, 再与 w j k w_{j k} wjk 进行矩阵点乘, 输出为(1,3)的数组
# Calculate the output layer output
# input (1,20) W_kj(20,3) output (1,3)
y_k = np.dot(z_j, self.W_jk)
4. 损失函数
对于输出的(1,3)的标签
y
k
y_{k}
yk , 采用均方差误差计算损失,
E
n
=
1
2
∑
k
=
1
L
(
y
k
−
t
k
)
2
E_{n} = \frac{1}{2} \sum_{k=1}^{L}\left(y_{k}-t_{k}\right)^{2}
En=21k=1∑L(yk−tk)2
输出层输出的 y k y_{k} yk 与该组特征对应的标签 y l a b e l y_{label} ylabel 相减, 对数组中每个数据分别求平方, 再求和除以 2 2 2 , 最终得到 l o s s loss loss
def loss_function(self, y_k, y_label):
loss = np.sum(np.square(y_k - y_label))/ 2
return loss
5. 反向传播过程
5.1 损失函数对输出层输出 y k y_{k} yk 的偏导
损失函数对输出层输出
y
k
y_{k}
yk 的偏导
∂
E
n
∂
y
k
\frac{\partial E_{n}}{\partial y_{k}}
∂yk∂En , 即
δ
k
\delta_{k}
δk
δ
k
=
y
k
−
t
k
\begin{aligned} \delta_{k}=y_{k}-t_{k} \end{aligned}
δk=yk−tk
# the gradient of the output
# input: y_k(1,3) y_label(1,3) ,output: gradient_y_k (1,3)
gradient_y_k = y_k - y_label
5.2 损失函数对隐藏层输出 a j a_{j} aj的偏导
损失函数对隐藏层输出 a j a_{j} aj的偏导为 ∂ E n ∂ a j = ∂ E n ∂ z j ∂ z j ∂ a j \frac{\partial E_{n}}{\partial a_{j}} = \frac{\partial E_{n}}{\partial z_{j}} \frac{\partial z_{j}}{\partial a_{j}} ∂aj∂En=∂zj∂En∂aj∂zj ,即 δ j \delta_{j } δj
其中
∂
E
n
∂
z
j
\frac{\partial E_{n}}{\partial z_{j}}
∂zj∂En 的偏导求解为
∂
E
n
∂
z
j
=
∂
E
y
1
∂
z
j
+
.
.
.
+
∂
E
y
k
∂
z
j
=
∑
k
w
k
j
δ
k
\begin{aligned} \frac{\partial E_{n}}{\partial z_{j}} &= \frac{\partial E_{y_1}}{\partial z_{j}} +...+ \frac{\partial E_{y_k}}{\partial z_{j}} \\ &= \sum_{k} w_{k j} \delta_{k} \end{aligned}
∂zj∂En=∂zj∂Ey1+...+∂zj∂Eyk=k∑wkjδk
∂
z
j
∂
a
j
\frac{\partial z_{j}}{\partial a_{j}}
∂aj∂zj 的偏导, 即为对激活函数的偏导, 求解为
∂
z
j
∂
a
j
=
tanh
(
a
j
)
′
=
(
1
−
z
j
2
)
\begin{aligned} \frac{\partial z_{j}}{\partial a_{j}} &= \tanh \left(a_{j}\right)^{\prime} \\ &= \left(1-z_{j}^{2}\right) \end{aligned}
∂aj∂zj=tanh(aj)′=(1−zj2)
因此可以得到
δ
j
=
(
1
−
z
j
2
)
∗
∑
k
w
k
j
δ
k
\delta_{j } = \left(1-z_{j}^{2}\right) * \sum_{k} w_{k j} \delta_{k}
δj=(1−zj2)∗k∑wkjδk
# the gradient of the tanh
# input: z_j(1,20) , output: d_tanh(1,20)
d_tanh = 1 - np.square(z_j)
# the gradient of the z_j input: gradient_y_k(1,3) W_jk(20,3), output:(1,20)
gradient_z_j = np.dot(gradient_y_k, np.transpose(self.W_jk))
# the gradient of the a_j input: gradient_z_j(1,20) d_tanh(1,20), output:(1,20)
gradient_a_j = gradient_z_j* d_tanh
5.3 损失函数对网络权值$w_{j i} $的梯度
损失函数对网络权值$w_{j i}
的
梯
度
为
的梯度为
的梯度为\frac{\partial E_{n}}{\partial w_{j i}^{(1)} }$ ,
∂
E
n
∂
w
j
i
=
∂
E
n
∂
z
j
∂
z
j
∂
a
j
∂
a
j
∂
w
j
i
\begin{aligned} \frac{\partial E_{n}}{\partial w_{j i}}= \frac{\partial E_{n}}{\partial z_{j}} \frac{\partial z_{j}}{\partial a_{j}} \frac{\partial a_{j}}{\partial w_{j i}} \end{aligned}
∂wji∂En=∂zj∂En∂aj∂zj∂wji∂aj
∂
a
j
∂
w
j
i
(
1
)
\frac{\partial a_{j}}{\partial w_{j i}^{(1)}}
∂wji(1)∂aj 的偏导求解为
∂
a
j
∂
w
j
i
=
x
i
\frac{\partial a_{j}}{\partial w_{j i}} = x_{i}
∂wji∂aj=xi
由于
δ
j
=
(
1
−
z
j
2
)
∗
∑
k
w
k
j
δ
k
\delta_{j} =\left(1-z_{j}^{2}\right)* \sum_{k} w_{k j} \delta_{k}
δj=(1−zj2)∗∑kwkjδk ,则可将公式简化为
∂
E
n
∂
w
j
i
=
δ
j
⊗
x
i
\begin{aligned} \frac{\partial E_{n}}{\partial w_{j i}}=\delta_{j} \otimes x_{i} \end{aligned}
∂wji∂En=δj⊗xi
对于隐藏层的偏置,其实就相当于上式子中的
x
0
=
1
x_{0}=1
x0=1,
∂
E
n
∂
w
j
0
=
δ
j
\begin{aligned} \frac{\partial E_{n}}{\partial w_{j 0}}=\delta_{j} \end{aligned}
∂wj0∂En=δj
# the gradient of the w_ij input: x(1,4) gradient_a_j(1,20), output:(4,20)
gradient_w_ij = np.outer(x, gradient_a_j)
# the gradient of the W_i_j_1(Bias), input: gradient_a_j(1,20), output:(1,20)
gradient_W_i_j_1 = gradient_a_j
5.4 损失函数对网络权值 w k j w_{k j} wkj的梯度
根据损失函数对网络权值
w
k
j
w_{k j}
wkj的梯度为
∂
E
n
∂
w
k
j
(
2
)
\frac{\partial E_{n}}{\partial w_{k j}^{(2)} }
∂wkj(2)∂En ,
∂
E
n
∂
w
k
j
(
2
)
=
∂
E
n
∂
y
k
∂
y
k
∂
w
k
j
(
2
)
\begin{aligned} \frac{\partial E_{n}}{\partial w_{k j}^{(2)}}= \frac{\partial E_{n}}{\partial y_{k}} \frac{\partial y_{k}}{\partial w_{k j}^{(2)}} \end{aligned}
∂wkj(2)∂En=∂yk∂En∂wkj(2)∂yk
y
k
y_{k}
yk 对网络权值
w
k
j
(
2
)
w_{k j}^{(2)}
wkj(2) 的偏导
∂
y
k
∂
w
k
j
(
2
)
\frac{\partial y_{k}}{\partial w_{k j}^{(2)}}
∂wkj(2)∂yk ,
∂
y
k
∂
w
k
j
(
2
)
=
z
j
\begin{aligned} \frac{\partial y_{k}}{\partial w_{k j}^{(2)}}= z_{j} \end{aligned}
∂wkj(2)∂yk=zj
因此损失函数对网络权值
w
k
j
w_{k j}
wkj的梯度为 ,
∂
E
n
∂
w
k
j
(
2
)
=
δ
k
⊗
z
j
\frac{\partial E_{n}}{\partial w_{k j}^{(2)} } = \delta_{k} \otimes z_{j}
∂wkj(2)∂En=δk⊗zj
# the gradient of the w_jk input: z_j(1,20) gradient_y_k(1,3), output:(20,3)
gradient_w_jk = np.outer(z_j , gradient_y_k )
6. 梯度更新公式
对于网络权值
w
k
j
w_{k j}
wkj的参数更新公式为
w
j
k
=
w
j
k
−
η
∂
E
n
∂
w
k
j
(
2
)
\begin{aligned} w_{j k}=w_{j k}-\eta \frac{\partial E_{n}}{\partial w_{k j}^{(2)}} \end{aligned}
wjk=wjk−η∂wkj(2)∂En
对于网络权值
w
j
i
w_{j i}
wji的参数更新公式为
w
i
j
=
w
i
j
−
η
∂
E
n
∂
w
j
i
(
1
)
\begin{aligned} w_{i j}=w_{i j}-\eta \frac{\partial E_{n}}{\partial w_{j i}^{(1)}} \end{aligned}
wij=wij−η∂wji(1)∂En
梯度更新公式的代码实现, 返回网络当前计算的损失, 其中learning_rate = 0.001
# weight update
self.W_jk = self.W_jk - self.learning_rate*gradient_w_jk
self.W_ij[1:,:] = self.W_ij[1:,:] - self.learning_rate*gradient_w_ij
self.W_ij[0,:] = self.W_ij[0,:] - self.learning_rate*gradient_W_i_j_1
7. 模型的使用
本次模型采用类实现,其调用过程如下,当想要训练其他的数据时,只需要将对应的数据按相应的格式读取,初始化对应大小的网络,然后将数据输入网络中即可。需要注意的是train方法只是使用一组数据更新一次,因此需要使用循环将所有数据进行训练,具体可参照完整源代码的实现。
# Initialize w ,learning_rate
model = MLP_model.MLP_Classification(n_features, n_hidden,
n_classes, learning_rate)
# Train the model x(1,4) label(1,3)
loss_ = model.train( x, label)
# Calculate the accuracy of the data x(n,4) label(n,3)
accuracy = model.accuracy(x, label)
对于模型的保存和载入,该模型较为简单,因为网络参数只有 w k j , w j i w_{k j},w_{j i} wkj,wji,因此我们只需要保存这两个参数就可以了。
# save model
path = os.path.join('MLP_Parameters/model%s/' % (crossValidation))
folder = os.getcwd() +'/'+ path
if not os.path.exists(folder):
os.makedirs(folder)
model.save(path)
# load model
model = MLP_model.MLP_Classification(n_features, n_hidden,
n_classes, learning_rate)
model.load(path)
ref
- D. Dua and C. Graff, “UCI machine learning repository,” 2017. [Online]. Available:http://archive.ics.uci.edu/ml
本文仅供参考, 若有错误, 欢迎指出, 共同学习
关注 钰哥lab 公众号,发送 MLP,即可获取完整源码