预训练多层网络,误差逆传播算法简称BP是最杰出的算法之一。
算了,先说说定义:
训练集D={(x1,y1),(x2,y2)…(xm,ym)}
输入属性:有d个属性描述。有 l 个输出神经元。q个隐含神经元。
输出层 第j个神经元的阈值用 θ j \theta_j θj表示
隐层第h个神经元的阈值用 γ h \gamma_h γh 表示
输入层第i个神经元到隐层第h个神经元之间的连接权为 v i , h v_{i,h} vi,h
隐层第h个神经元和输出层第j个神经元之间的连接权为 ω h , j \omega_{h,j} ωh,j
隐层第h个神经元的输出使用 b h b_h bh表示
输出结果用 y k ^ \hat{y^k} yk^表示,其中 y i k ^ \hat{y_i^k} yik^ ,表示第k组样本的第j个输出神经元的输出
隐层第h个神经元接收到的输入为 α h = ∑ i = 1 d v i , h x i \alpha_h=\sum_{i=1}^{d}v_{i,h}x_i αh=∑i=1dvi,hxi
输出层第j个神经元收到的输入为 β j = ∑ h = 1 q w h , j b h \beta_j=\sum_{h=1}^qw_{h,j}b_h βj=∑h=1qwh,jbh
上面是西瓜书中给出的插图解释。
同理可以得出第j个神经元的输出:
第 j 个 神 经 元 的 输 出 = y j k ^ = f ( β j − θ j ) 第j个神经元的输出=\hat{y_j^k}=f(\beta_j-\theta_j) 第j个神经元的输出=yjk^=f(βj−θj)
f()是最后一层函数, θ j \theta_j θj是输出层第j个神经元的阈值 , β j \beta_j βj是第j个输出神经元的输入。这样就很容易理解 输入值和阈值进行比较,得出所需要的结果,这也符合我们的常识。
那么这个系统的均方误差,可以得出
E k = 1 2 ∗ ∑ j = 1 l ( y j k ^ − y j k ) 2 E_k=\frac{1}{2}*\sum_{j=1}^{l}(\hat{y_j^k}-y_j^k)^2 Ek=21∗j=1∑l(yjk^−yjk)2
下面我们看看有多少参数需要研究吧!
从输入层开始,我们有d个输入神经元,然后介入到q个隐层对吧,每一个链接都需要一个权重,所以需要d * q个权重对吧
除此之外,q个隐层神经元,有q个阈值对吧。
然后我们有l个输出神经元,也就是说,隐层神经元都要连接到输出神经元,每一个链接都需要一个权重,所以需要l * q个权重。
除此之外,l个输出神经元有l个阈值。
总结一下共有(d+l+1)*q +l个参数需要确定,突然感觉有点复杂,同时还有点小兴奋。
我们的目标函数为系统的均方误差,而我们的变量为两层神经元的阈值和连接权。
隐层:阈值 γ h \gamma_h γh,连接权 ω h , j \omega_{h,j} ωh,j
输出层:阈值 θ j \theta_j θj ,连接权 v i , h v_{i,h} vi,h
我们使用梯度下降算法,进行修正我们的变量,则有如下公式:
Δ ω h , j = − η ∗ ∂ E k ∂ ω h , j Δ θ j = − η ∗ ∂ E k ∂ θ j Δ v i , h = − η ∗ ∂ E k ∂ v i , h Δ γ h = − η ∗ ∂ E k ∂ γ h \Delta \omega_{h,j}=- \eta* \frac{\partial E_k}{\partial \omega_{h,j}}\\ \Delta \theta_j=- \eta* \frac{\partial E_k}{\partial \theta_j} \\ \Delta v_{i,h}=- \eta* \frac{\partial E_k}{\partial v_{i,h}}\\ \Delta \gamma_h=- \eta* \frac{\partial E_k}{\partial \gamma_h} \\ Δωh,j=−η∗∂ωh,j∂EkΔθj=−η∗∂θj∂EkΔvi,h=−η∗∂vi,h∂EkΔγh=−η∗∂γh∂Ek
写是好写,可是算很难算,我用了两页半纸,求完了这几个导数。
害,大家要是也想推导一下的话可以一起走一遍流程。
先推导 Δ ω h , j \Delta \omega_{h,j} Δωh,j 西瓜书上也是先推到的这一个。
Δ ω h , j = η ∗ g j ∗ b h Δ θ j = − η ∗ g j Δ v i , h = η ∗ e h ⋅ x i Δ γ h = − η ∗ e h \Delta \omega_{h,j}=\eta* g_j*b_h\\ \Delta \theta_j=- \eta*g_j \\ \Delta v_{i,h}=\eta*e_h\cdot x_i\\ \Delta \gamma_h=- \eta* e_h \\ Δωh,j=η∗gj∗bhΔθj=−η∗gjΔvi,h=η∗eh⋅xiΔγh=−η∗eh
到这里就推完了,估计已经劝退好多人了;
下面开始代码的实现;
是不是刚开始无从下手,其实当时我也是的
先开始准备数据集:
import numpy as np
data_set=[[1,1,0],
[1,0,1],
[0,1,1],
[0,0,0]]
data= np.array(data_set)
x=data[:,:-1]# 和感知机的划分 方法一样。使用了numpy的花式索引
y=data[:,-1:]
n=data.shape[0]# 记录数据条目
准备好了数据集下面开始定义变量:
# 分析下系数矩阵 ,首先 输入神经元 2个 输出神经元1 个 2个隐层
#(2+1+1)*2+1=9 个参数
# 这九个参数,是放在一起还是分开放呢? 在一起可以用矩阵计算,分开放更清楚 ,怎么操作呢?
d,q,l=2,2,1 # 输入神经元,隐层神经元,输出神经元 个数
v=np.random.rand(d,q)#输入层第i个神经元到隐层第h个神经元之间的连接权
w=np.random.rand(l,q)#隐层第h个神经元和输出层第j个神经元之间的连接权
gamma=np.random.rand(q)#隐层第h个神经元的阈值
theta=np.random.rand(l)#输出层 第j个神经元的阈值
eta=0.5 # 学习率
【注】numpy的random.rand()方法,生成(0,1)之间的随机数,正好用来初始化系数矩阵,以后这种方法会很常用;
准备好了之后,我们采用自上而下的设计方式;就是我们从主函数开始,进行操作,需要什么操作,先把函数定义出来,然后在慢慢的补充细节;这样思路会很清晰
首先这种系统,主要思路是:
所以开肯定是一个for 循环,遍历数据集;
for index in range(n):
Y=f(beta[j] - theta[j])# 得到一个测试值
现在,需要补充激活函数f() 和输出层的输入beta[j] ,由于theta已经定义直接按索引取值就行。
#定义激活函数
def f(x):
return 1/(1+np.exp(-x))
下面写get_beta,先看下beta 的计算公式; β j = ∑ h = 1 q w h , j b h \beta_j=\sum_{h=1}^qw_{h,j}b_h βj=∑h=1qwh,jbh
其中有一个循环,还有一个未知的bh,需要一个参数j
def get_beta(j):
sum=0
for h in range(q):
sum+=w[h][j]*bh
return sum
解决老问题,出现了新问题,又有了一个未知数bh。
下面写get_bh(),先看下公式吧:隐层第h个神经元的输出使用 b h = f ( α h − r h ) b_h=f(\alpha_{h}-r_h) bh=f(αh−rh)表示
def get_b(h):
return f(alpha(h)-gamma[h])
可是alpha还不知道是什么;没办法,继续写,现在就像一个递归的栈;看下公式: α h = ∑ i = 1 d v i , h x i \alpha_h=\sum_{i=1}^{d}v_{i,h}x_i αh=∑i=1dvi,hxi
def get_alpha(h):
sum=0
for i in range(d):
sum+=v[i][h]*x[i]#
现在出现了一个问题,就是如果我们用x[i]表示的话得到的是一组数据[0,1 ]之类的,我们应该是得到数组中的一个数,所以我们需要一个索引,来标注,这是那一组数据。所以做如下修改;
def get_alpha(index,h):
sum=0
for i in range(d):
sum+=v[i][h]*x[index][i]
# print("get_alpha",sum)
return sum
那么相应的所有调用上述函数的函数都要新增一个参数index
def get_b(index,h):
return f(get_alpha(index,h)-gamma[h])
def get_beta(index,j):
sum=0
for h in range(q):
sum+=w[j][h]*get_b(index,h)
# print("get_beta:",sum)
return sum
等我们走完这个流程之后,会发现,我们根本不许要之前的验证函数:
for index in range(n):
Y=f(beta[j] - theta[j])# 得到一个测试值
因为我们只需要得出那几个参数的增量即可:
先看看公式
Δ ω h , j = η ∗ g j ∗ b h Δ θ j = − η ∗ g j Δ v i , h = η ∗ e h ⋅ x i Δ γ h = − η ∗ e h \Delta \omega_{h,j}=\eta* g_j*b_h\\ \Delta \theta_j=- \eta*g_j \\ \Delta v_{i,h}=\eta*e_h\cdot x_i\\ \Delta \gamma_h=- \eta* e_h \\ Δωh,j=η∗gj∗bhΔθj=−η∗gjΔvi,h=η∗eh⋅xiΔγh=−η∗eh
首先分析下 :对于 Δ ω h , j \Delta \omega_{h,j} Δωh,j我们可以发现只有g 这一项是没有发现的;所以我们先做一项这个;
由上图可知
def get_g(index,j):
Y=f(get_beta(index,j)-theta[j])
return Y*(1-Y)*(y[index]-Y)
由手推过程发现,我们还需要一个求e_h的函数,我们先看下公式
def get_e(index,h):
bh=get_b(index,h)
sum-0
for j in range(l):
sum+=get_g(index,j)*w[h][j]
return bh*(1-bh)*sum
至此,所有的子函数函数都已经修改完毕;
于是主函数将被修改为:
for k in range(10000):#将整个样本训练多少遍
for index in range(data_size):
for j in range(l): #如果有多个输出神经元
e=[]
loss_w=[]# 有点问题 以后肯定修改
g=get_g(index,j)
for h in range(q):
loss_w.append(list(eta*g*get_b(index,h))[0])# 生成w 的一行
e.append(get_eh(index,h)[0]) # 生乘e的一行
loss_theta= -eta*g
loss_gamma= [-eta*i for i in e ]
loss_v=np.zeros((d,q))
# 一行一行的修改 v矩阵的损失值
for i in range(d):
for h in range(q):
loss_v[i][h]=eta*e[h]*x[index][i]
#使用随机梯度下降 更新,因为数据集本身数据量较小,在大量的数据集下,可是哟batch 或者mini batch
v+=loss_v
w+=loss_w
theta+=loss_theta
gamma+=loss_gamma
下面是整个项目的源代码:
# 单个感知机 实现 与或非
import matplotlib.pyplot as plt
import numpy as np
data_set=[[1,1,0],
[1,0,1],
[0,1,1],
[0,0,0]]
data= np.array(data_set)
x=data[:,:-1]
y=data[:,-1:]
# 分析下系数矩阵 ,首先 输入神经元 2个 输出神经元1 个 2个隐层
#(2+1+1)*2+1=9 个参数
# 这九个参数,是放在一起还是分开放呢? 在一起可以用矩阵计算,分开放更清楚 ,怎么操作呢?
d,q,l=2,2,1 # 输入神经元,隐层神经元,输出神经元 个数
v=np.random.rand(d,q)
w=np.random.rand(l,q)
gamma=np.random.rand(q)
theta=np.random.rand(l)
print(w)
print(v)
print(gamma)
print(theta)
eta=0.5
# 至此 变量的初始化已经结束了。
def f(x):# 激活函数
return 1/(1+np.exp(-x))
def get_alpha(index,h):
sum=0
for i in range(d):
sum+=v[i][h]*x[index][i]
# print("get_alpha",sum)
return sum
def get_b(index,h):
return f(get_alpha(index,h)-gamma[h])
def get_beta(index,j):
sum=0
for h in range(q):
sum+=w[j][h]*get_b(index,h)
# print("get_beta:",sum)
return sum
def get_theta(j):
return theta[j]
def get_g(index,j):
predict_value=f(get_beta(index,j)-theta[j])
# print("predict_value",predict_value)
true_value=y[index]
return predict_value*(1-predict_value)*(true_value-predict_value)
def get_e(index,h):
bh=get_b(index,h)
sum=0
for j in range(l):
sum+=w[j][h]*get_g(index,j)
return bh*(1-bh)*sum
data_size=x.shape[0]
for k in range(10000):
for index in range(data_size):
for j in range(l): #如果有多个输出神经元
e=[]
loss_w=[]# 有点问题 以后肯定修改
g=get_g(index,j)
for h in range(q):
loss_w.append(list(eta*g*get_b(index,h))[0])
e.append(get_e(index,h)[0])
loss_theta= -eta*g
loss_gamma= [-eta*i for i in e ]
loss_v=np.zeros((d,q))
for i in range(d):
for h in range(q):
loss_v[i][h]=eta*e[h]*x[index][i]
v+=loss_v
w+=loss_w
theta+=loss_theta
gamma+=loss_gamma
def test():
j=0
count=0
for i in range(4):
Y=0 if f(get_beta(i,j)-theta[j])<0.5 else 1
print("测试值:"+str(Y)+"真实值:"+str(y[i][0]))
test()
运行结果:
初始化矩阵
[[0.00999244 0.27850476]]
[[0.21191722 0.76057257]
[0.7781017 0.24587134]]
[0.41408754 0.86375081]
[0.90933205]
测试值:0真实值:0
测试值:1真实值:1
测试值:1真实值:1
测试值:0真实值:0
Process finished with exit code 0
依然有不足,后期补充