(BP算法参数推导)误差逆传播算法-实现异或感知机

预训练多层网络,误差逆传播算法简称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
(BP算法参数推导)误差逆传播算法-实现异或感知机_第1张图片
上面是西瓜书中给出的插图解释。
同理可以得出第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=21j=1l(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,jEkΔθj=ηθjEkΔvi,h=ηvi,hEkΔγh=ηγhEk
写是好写,可是算很难算,我用了两页半纸,求完了这几个导数。
害,大家要是也想推导一下的话可以一起走一遍流程。
先推导 Δ ω 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=ηgjbhΔθj=ηgjΔvi,h=ηehxiΔγ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)之间的随机数,正好用来初始化系数矩阵,以后这种方法会很常用;
准备好了之后,我们采用自上而下的设计方式;就是我们从主函数开始,进行操作,需要什么操作,先把函数定义出来,然后在慢慢的补充细节;这样思路会很清晰

首先这种系统,主要思路是:

  1. 遍历数据集
  2. 算出预测值Y
  3. 计算误差
  4. 修正参数

所以开肯定是一个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(αhrh)表示

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=ηgjbhΔθj=ηgjΔvi,h=ηehxiΔγ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

依然有不足,后期补充

你可能感兴趣的:(机器学习,python,深度学习,机器学习)