文章目录
- 神经元
- BP原理及实现
- 测试
BP,就是后向传播(back propagation),说明BP网络要向后传递一个什么东西,这个东西就是误差。
而神经网络,就是由神经元组成的网络,所以在考虑BP之前,还不得不弄清楚神经元是什么。
神经元
泛泛地说,神经元,就是一个函数 w ( x ) w(x) w(x),而且这个函数往往比较友好,可能是一个线性函数,可以表示为
w ( x ) = ∑ i w i x i w(x)=\sum_i w_ix_i w(x)=i∑wixi
其中 x i x_i xi为 x x x的诸分量,而且这个分量很可能不是一个标量,而是一个数组,甚至矩阵,即多维数组。
神经网络的目的,就是在给定 x , y x,y x,y的情况下,根据 y = w ( x ) y=w(x) y=w(x)求出 w i w_i wi的值。
如果仅仅是这个程度,那么神经元的地位就和矩阵计算发生了重叠,神经网络就直接退化成最小二乘法了,所以神经元在进行线性代数的计算之后,要引入一个非线性的函数;同时考虑到神经元对中心的偏移,所以网络中还要加一个 B B B参数,故整体可以表示为
w ( x ) = f ( ∑ i w i x i + b i ) w(x)=f\bigg(\sum_i w_ix_i+b_i\bigg) w(x)=f(i∑wixi+bi)
这里的 f f f是一个非线性函数,一般叫做激活函数,被这个函数一激活,那么平平无奇的矩阵计算,就华华丽丽地变身成了神经元。
这个激活函数并不需要十分复杂,但一般要起到归一化的作用,比如下面将要用到的sigmoid函数
σ ( x ) = 1 1 + exp − x \sigma(x)=\frac{1}{1+\exp -x} σ(x)=1+exp−x1
BP原理及实现
单个神经元虽然组不成网络,但也有一个专门的名称,即单层感知机,可用于数据二分,由于用途单一,所以就不讲了,直接开始多层的BP网络的编写。
那么在书写之前,先声明一下要做的事情。现有一组 x x x和一组 y y y,我们希望建立一个神经网络 W W W,在 x x x经过 W W W之后,得到的结果 Y Y Y与 y y y的差值小于误差要求。
如前文所述,神经网络是由多个神经元组成的,而神经元最重要的两个参数是 W W W和 B B B,用以完成对函数 y y y的拟合,这个拟合的过程,即由 x x x得到 Y Y Y的过程,便是前向过程,相应地 W 1 W_1 W1和 B 1 B_1 B1构成的就是前向网络。
而后向过程传递的是误差,这也同样需要神经元的帮助,从而需要另一组神经元 W 2 , B 2 W_2, B_2 W2,B2,这些参数的初值可以随机选取。考虑到BP网络实现起来非常轻便,故先将代码列在下面,然后再解读其含义
import numpy as np
sigmoid = lambda x : 1/(1+np.exp(-np.array(xs)))
def bpnn(xs, ys, nIter=101, th=0.005, nHide=10):
W1 = np.random.rand(nHide)
B1 = np.random.rand(nHide)
W2 = np.random.rand(nHide)
B2 = np.random.rand()
for k in range(nIter):
Y = []
for xi,yi in zip(xs,ys):
L1 = xi*W1-B1 # 隐含层输入数据
L2 = sigmoid(L1) #隐含层的输出数据
Y.append(np.sum(W2*L2) - B2) #模型输出
err = Y[-1] - yi # 模型误差
##反馈,修改参数
dB2 = -1*th*err
dW2 = err*th*L2
dB1 = W2*L2*(1-L2)*(-1)*err*th
dW1 = W2*L2*(1-L2)*xi*err*th
W1 = W1 - dW1
B1 = B1 - dB1
W2 = W2 - dW2
B2 = B2 - dB2
if k%100==0:
print(k)
return Y
bpnn
的输入除了将要被拟合的xs,ys
之外,还有迭代次数nIter
, 学习率th
以及神经元的权重个数nHide
。
其中, W 1 , B 1 , W 2 , B 2 W_1,B_1,W_2,B_2 W1,B1,W2,B2便是上文提到的网络参数,前两者用于前向传播,也就是根据 x s xs xs得到 Y Y Y,后两者用于后向传播,就是根据 Y Y Y和 y s ys ys得到误差。
这段程序的核心过程是for xi,yi in zip(xs,ys)
中的内容,表示对每一组
x
,
y
x,y
x,y点对进行神经网络的学习,其中
L
1
L_1
L1和
L
2
L_2
L2对应
W
1
,
B
1
W_1, B_1
W1,B1和
W
2
,
B
2
W_2, B_2
W2,B2的两个隐藏层。
数据的流动过程为
x->L1->L2->Y->err
首先第一步,根据当前的
W
1
,
B
1
W_1,B_1
W1,B1计算得到L1
,通过激活函数,生成L2
,通过L2
得到拟合结果Y
,通过比对Y
和y
的值,就可以得到误差err
。此为前向传播过程。
接下来就是根据err
,来逆推参数的变化量,其基本流程为前向传播的逆过程,第一步得到dB2
,其值为
δ B 2 = − θ ∗ σ \delta B_2=-\theta*\sigma δB2=−θ∗σ
其中
θ
,
σ
\theta, \sigma
θ,σ分别对应代码中的th, err
,表示根据学习率和误差,对B2
参数进行微调。
然后是dW2
,值为
δ W 2 = θ ∗ σ ∗ L 2 \delta W_2=\theta*\sigma*L_2 δW2=θ∗σ∗L2
即除了受到误差和学习率的影响之外,也要考虑L2
的影响。那么这个值是怎么来的呢?
σ = w 2 L 2 + b 2 − y ∂ σ ∂ w 2 = L 2 \begin{aligned} \sigma &= w_2L_2+b_2-y\\ \frac{\partial\sigma}{\partial w_2}&=L_2 \end{aligned} σ∂w2∂σ=w2L2+b2−y=L2
则 ∂ σ ∂ w 2 \frac{\partial\sigma}{\partial w_2} ∂w2∂σ相当于是 w 2 w_2 w2的微小变动对 σ \sigma σ的影响。之所以要引入学习率,乃因直接通过 σ L 2 \sigma L_2 σL2得到的值可能过大,从而跳过真值。
第三步是dB1
,值为
δ B 1 = − θ σ w 2 L 2 ( 1 − L 2 ) \delta B_1=-\theta\sigma w_2L_2(1-L_2) δB1=−θσw2L2(1−L2)
其缘由为
σ = w 2 L 2 + b 2 − y = w 2 f ( w 1 L 1 + b 1 ) + b 2 − y ∂ σ ∂ w 2 = ∂ f ∂ w 1 \begin{aligned} \sigma&=w_2L_2+b_2-y=w_2f(w_1L_1+b1)+b_2-y\\ \frac{\partial\sigma}{\partial w_2}&=\frac{\partial f}{\partial w_1} \end{aligned} σ∂w2∂σ=w2L2+b2−y=w2f(w1L1+b1)+b2−y=∂w1∂f
考虑到 f f f的表达式 f ( x ) = 1 1 + exp − x f(x)=\frac{1}{1+\exp -x} f(x)=1+exp−x1,则
d f d x = e − x ( 1 + e − x ) = ( 1 − 1 1 + e − x ) ( 1 1 + e − x ) = f ( x ) ( 1 − f ( x ) ) \frac{\text d f}{\text d x}=\frac{e^{-x}}{(1+e^{-x})}=(1-\frac{1}{1+e^{-x}})(\frac{1}{1+e^{-x}})=f(x)(1-f(x)) dxdf=(1+e−x)e−x=(1−1+e−x1)(1+e−x1)=f(x)(1−f(x))
考虑到其所谓的
x
x
x由
w
1
L
1
w_1L_1
w1L1构成,所以得到的结果就是w_1L_2(1-L_2)
同理,得到最后一组参数W1
的更新方案,
测试
最后,又到了喜闻乐见的测试环节
if __name__=="__main__":
xs = np.arange(1000)*0.01
ys = np.sin(xs)
Y = bpnn(xs, ys, nIter=501, th=0.005, nHide=10)
fig = plt.figure()
plt.plot(xs,ys)
plt.plot(xs,Y,color='red',linestyle='--')
plt.show()
得到结果为