BP

BP神经网络推导及python实现


1.基本模型推导

从简单到复杂,这里我们讨论一种单隐层前馈网络:



图片来源于周志华版机器学习

以下是该网络各最基本的参数:
d d : 输入层神经元
q : 隐藏层神经元
l l : 输出层神经元
sigmoid(x)=f(x)=11+ex : 激励函数,这里采用sigmoid函数
vih v i h : 输入层第i个神经元连接隐层第h个神经元的权重
γh γ h : 隐层第h个神经元的阈值
αh α h : 隐层第h个神经元接受到的总输入
bh b h : 隐层第h个神经元的输出
ωhj ω h j : 隐层第h个神经元到输出层第j个神经元之间的连接权
βj β j : 输出层第j个神经元接收到的总输入
θj θ j : 输出层第j个神经元的阈值
yj^ y j ^ : 输出层第j个神经元的输出
Ek E k : 该网络在第k个例子上面的总均方误差
看上去不太直观?没关系,让我们关心一下数据的流动就好理解多了:

1.1 正向传播

  1. 输入数据一个向量 x=[x1,x2,,xd] x = [ x 1 , x 2 , ⋯ , x d ] , 对应输入层各神经元接收到的输入
  2. 隐含层第 h h 个神经元接收输入层输入: i=1dvihxi, 得到 αh α h
  3. 隐含层第 h h 个神经元产生输出: f(αhγh) , 得到 bh b h
  4. 这时隐含层各神经元的输出形成了新的向量 x1=[b1,b2,,bq] x 1 = [ b 1 , b 2 , ⋯ , b q ]
  5. 输出层第 j j 个神经元接收到的输入: h=1qωhjbh , 得到 βj β j
  6. 输出层第 j j 个神经元产生输出: f(βjθj), 得到 yj^ y j ^
  7. 得到结果即输出向量 y=[y1^,y2^,,yl^] y = [ y 1 ^ , y 2 ^ , ⋯ , y l ^ ]

以上就是一个完整的输入输出流程了

1.2 代价函数

既然输出向量已经得到,那么需要做的就是计算代价函数了
特别提到一点, 在周志华机器学习一书中提到的代价函数用的是均方误差,不过我们却不采用这种而是采用交叉熵(croos entropy), 因为均方误差大部分情况会学的很慢,具体的可以参见下面的文章:
https://blog.csdn.net/bixiwen_liu/article/details/52922008
(不过我是觉得对于连续变量还是均方误差要好些)
具体式子如下:

J=1mi=1mj=1l[yijlogyij^+(1yij)log(1yij^)] J = − 1 m ∑ i = 1 m ∑ j = 1 l [ y j i log ⁡ y j i ^ + ( 1 − y j i ) log ⁡ ( 1 − y j i ^ ) ]

这里可以看到和逻辑回归的代价函数可以说是非常之像了,只要把 l l 个神经元变成一个,那就是逻辑回归的 J

1.3 反向传播算法(BP)

BP实质上是一个采用梯度下降策略的迭代学习算法, 每一次迭代沿着负梯度的方向进行调整, 例如对 ωhj ω h j , 有:

ωhj=ωhjηJωhj ω h j = ω h j − η ∂ J ∂ ω h j

而只考虑第 i i 个样例的话:
(1)Jωhj=Jyj^yj^βjβjωhj(2)=(yjyj^+yj11yj^)f(βjθj)(1f(βjθj))bh(3)=(yjyj^+yj11yj^)yj^(1yj^)bh(4)=[yj(1yj^)(1yj)yj^]bh(5)=(yjyj^)bh

我们可以看到,最后的结果与 (yjyj^) ( y j − y j ^ ) 有关,所以呢这就是使用 交叉熵更为优越的地方:
误差越大,下降速度越快!
所以我们只需要很少的迭代次数,它就会很好的work啦!


2.转为线代形式并模拟训练

要进行实现的话,首先要做的工作就是把以上的公式转化为线代形式啦
接下来, 我会用(m, n)的形式描述矩阵的形状是几行几列

2.1 已知输入X和输出Y

X: (m, n_in) (训练数据矩阵, 有m个样例, 每个样例有n_in个输入特征 , 也对应着输入神经元的个数)
y: (1, m) (训练集的输出集合)
为了神经网络能更好的训练我们将y转化为神经网络式(我自己想出来的词2333)的输出矩阵:
Y: (m, n_out) (输出集合, 有m个样例, 每个样例有n_out个数(非0即1), 对应着网络的输出结果, 由y映射而来)
X_ : (m, n_in+1) 给X的第一列补1
现在,已知的有X和Y

2.2 正向传播过程

定义 V V (n_hid, n_in+1) 为从输入层到隐藏层的参数矩阵
第一次传播:从输入层输入到隐藏层输出: B=f(X_VT) : (m, n_hid) 其中 f f 为激励函数
定义 B_ 为给 B B 的第一列补1(加上偏置) (m, n_hid+1)
定义 W (n_out, n_hid+1) 为隐藏层到输出层的参数矩阵
第二次传播:从隐藏层输出到最后的输出: Ŷ =f(B_WT) Y ^ = f ( B _ W T ) (m, n_out)

2.3 代价函数与正则化

如果根据交叉熵加上正则来做的话,那么是下面的式子:

J=1m[YTcollogYcol^(1YTcol)log(1Ycol^)+λ2[Vcol;Wcol]T[Vcol;Wcol] ] J = 1 m [ − Y c o l T log ⁡ Y c o l ^ − ( 1 − Y c o l T ) log ⁡ ( 1 − Y c o l ^ ) + λ 2 [ V c o l ; W c o l ] T [ V c o l ; W c o l ]   ]

其中, Xcol X c o l 代表把这个矩阵化为只有一列的矩阵; [A;B] [ A ; B ] 代表对 A,B A , B 两个矩阵进行列拼接

2.4 反向传播(BackPropagation)


待填坑

3 python实现

以下是完整代码

import numpy as np

""" s型函数 """
def sigmoid(z):
    return 1.0/(1.0+np.exp(-z))

""" s', 即s的导数 """
def sigmoid_prime(z):
    return sigmoid(z) * (1 - sigmoid(z))

"""
    params: 所有参数
    n_in : 输入层神经元个数
    n_hid : 隐藏层神经元个数
    n_out : 输出层神经元个数
    X: (m, n_out)
    y: (m, 1)
    lam: lambda
"""
def cost(params, n_in, n_hid, n_out, X, y, lam):
    T1 = params[0: n_hid*(n_in+1)].reshape(n_hid, n_in+1) # 参数矩阵,相当于公式里的 V
    T2 = params[n_hid*(n_in+1) : ].reshape(n_out, n_hid+1) # 参数矩阵,相当于公式里的 W
    # 把 y 作 0-1映射为 Y 
    Y = np.zeros((X.shape[0], n_out))
    for i in range(n_out):
        Y[:, i] = np.int32(y == i).reshape(1, -1)
    # 求正则项, 对参数矩阵去掉第一列(不需要对bias进行正则化), 再转化为1列的形式
    T1_x = T1[:, 1:T1.shape[1]].reshape(-1,1)
    T2_x = T2[:, 1:T2.shape[1]].reshape(-1,1)
    regu = lam / 2 * np.dot( np.transpose(np.vstack((T1_x,T2_x))) , np.vstack((T1_x, T2_x)) )
    # 正向传播 (每次先对输入阵补1, 然后求矩阵相乘, 最后求个sigmoid)
    B = sigmoid(np.dot(np.hstack((np.ones((X.shape[0], 1)), X)), np.transpose(T1)))
    h = sigmoid(np.dot(np.hstack((np.ones((X.shape[0], 1)), B)), np.transpose(T2)))
    # 对实际的Y矩阵和预测出的h进行列化
    Y.reshape(-1,1)
    h.reshape(-1,1)

    J = (-np.dot(np.transpose(Y), np.log(h)) - np.dot(np.transpose(1-Y), np.log(1-h)) + regu ) / X.shape[0]

    return np.ravel(J)


"""
    返回所有参数的梯度(1行,先是T1的梯度,再是T2的梯度)
"""
def gradient(params, n_in, n_hid, n_out, X, y, lam):
    T1 = params[0: n_hid*(n_in+1)].reshape(n_hid, n_in+1).copy() # 参数矩阵,相当于公式里的 V
    T2 = params[n_hid*(n_in+1) : ].reshape(n_out, n_hid+1).copy() # 参数矩阵,相当于公式里的 W
    Y = np.zeros((X.shape[0], n_out))
    for i in range(n_out):
        Y[:, i] = np.int32(y == i).reshape(1, -1)

    # 去掉T1和T2的第一列
    T2_x = T2[:, 1:T2.shape[1]]
    T1_grad = np.zeros(T1.shape) # 记为输入层到隐藏层的权重
    T2_grad = np.zeros(T2.shape) # 记为隐藏层到输出层的权重

    a1 = np.hstack((np.ones((X.shape[0], 1)), X)) # 第一层输入为X加上全为1的第一列
    z2 = np.dot(a1, np.transpose(T1)) # 第二层输入
    a2 = sigmoid(z2) # 第二层输出
    a2 = np.hstack((np.ones((X.shape[0], 1)), a2)) # 第二层输出
    z3 = np.dot(a2, np.transpose(T2)) # 第三层输入
    h = sigmoid(z3) # 最终输出

    error3 = np.zeros((X.shape[0], n_out))
    error2 = np.zeros((X.shape[0], n_hid))
    for i in range(X.shape[0]):
        error3[i, :] = h[i, :] - Y[i, :]
        T2_grad = T2_grad + np.dot(np.transpose(error3[i, :].reshape(1,-1)), a2[i,:].reshape(1,-1))
        error2[i, :] = np.dot(error3[i, :].reshape(1,-1), T2_x) * sigmoid_prime(z2[i, :])
        T1_grad = T1_grad + np.dot(np.transpose(error2[i, :].reshape(1,-1)), a1[i,:].reshape(1,-1))

    T1[:, 0] = 0
    T2[:, 0] = 0

    """把计算结果列化并加上正则化项"""
    return np.ravel((np.vstack((T1_grad.reshape(-1,1),T2_grad.reshape(-1,1)))
             + lam*np.vstack((T1.reshape(-1,1),T2.reshape(-1,1)))) / X.shape[0])


# 初始化权重为sin(x) / 10的(n_out, n_in)大小的矩阵
def debug_init_weight(n_in, n_out):
    W = np.zeros((n_out, n_in+1))
    x = np.arange(1, n_out*(n_in+1)+1)
    return np.sin(x).reshape(W.shape) / 10


# 随机初始化权重theta
def rand_init_weight(n_in, n_out):
    W = np.zeros((n_out, n_in+1))    # 对应theta的权重
    little = (6.0/ (n_out + n_in))**0.5
    # 先产生(n_out, n_in+1)大小的随机矩阵,再乘以2乘以little之后减去little
    return np.random.rand(n_out, n_in+1)*2*little - little

"""
    预测, 模拟传播
"""
def predict(T1, T2, X):
    m = X.shape[0]
    n_out = T2.shape[0]
    X = np.hstack((np.ones((m, 1)), X))
    h1 = sigmoid(np.dot(X, np.transpose(T1)))
    h1 = np.hstack((np.ones((m, 1)), h1))
    h2 = sigmoid(np.dot(h1, np.transpose(T2)))
    p = np.array(np.where(h2[0,:] == np.max(h2, axis=1)[0]))  
    for i in np.arange(1, m):
        t = np.array(np.where(h2[i,:] == np.max(h2, axis=1)[i]))
        p = np.vstack((p,t))
    return p


from scipy import io as spio
from matplotlib import pyplot as plt
from scipy import optimize
from sklearn import datasets
from sklearn.preprocessing import StandardScaler
import time


# 加载mat文件
def loadmat_data(file_name):
    return spio.loadmat(file_name)


# 显示100个数字
def display_data(imgData):
    sum = 0
    '''
    显示100个数(若是一个一个绘制将会非常慢,可以将要画的数字整理好,放到一个矩阵中,显示这个矩阵即可)
    - 初始化一个二维数组
    - 将每行的数据调整成图像的矩阵,放进二维数组
    - 显示即可
    '''
    m,n = imgData.shape
    width = np.int32(np.round(np.sqrt(n)))
    height = np.int32(n/width);
    rows_count = np.int32(np.floor(np.sqrt(m)))
    cols_count = np.int32(np.ceil(m/rows_count))
    pad = 1
    display_array = -np.ones((pad+rows_count*(height+pad),pad+cols_count*(width+pad)))
    for i in range(rows_count):
        for j in range(cols_count):
            if sum >= m: #超过了行数,退出当前循环
                break;
            display_array[pad+i*(height+pad):pad+i*(height+pad)+height,pad+j*(width+pad):pad+j*(width+pad)+width] = imgData[sum,:].reshape(height,width,order="F")    # order=F指定以列优先,在matlab中是这样的,python中需要指定,默认以行
            sum += 1
        if sum >= m:  #超过了行数,退出当前循环
            break;

    plt.imshow(display_array,cmap='gray')   #显示灰度图像
    plt.axis('off')
    plt.show()


def neuralNetwork(input_layer_size,hidden_layer_size,out_put_layer):
    data_img = loadmat_data("data_digits.mat")
    X = data_img['X']
    y = data_img['y']

    m,n = X.shape

    ## 随机显示几行数据
    rand_indices = [t for t in [np.random.randint(x-x, m) for x in range(100)]]  # 生成100个0-m的随机数
    display_data(X[rand_indices,:])     # 显示100个数字    

    Lambda = 1

    initial_Theta1 = rand_init_weight(input_layer_size,hidden_layer_size); 
    initial_Theta2 = rand_init_weight(hidden_layer_size,out_put_layer)

    initial_nn_params = np.vstack((initial_Theta1.reshape(-1,1),initial_Theta2.reshape(-1,1)))  #展开theta    
    start = time.time()
    result = optimize.fmin_cg(nnCostFunction, initial_nn_params, fprime=gradient, args=(input_layer_size,hidden_layer_size,out_put_layer,X,y,Lambda), maxiter=100)
    print (u'执行时间:',time.time()-start)
    print (result)
    '''可视化 Theta1'''
    length = result.shape[0]
    Theta1 = result[0:hidden_layer_size*(input_layer_size+1)].reshape(hidden_layer_size,input_layer_size+1)
    Theta2 = result[hidden_layer_size*(input_layer_size+1):length].reshape(out_put_layer,hidden_layer_size+1)    
    display_data(Theta1[:,1:length])
    display_data(Theta2[:,1:length])
    '''预测'''
    p = predict(Theta1,Theta2,X)
    print (u"预测准确度为:%f%%"%np.mean(np.float64(p == y.reshape(-1,1))*100))    
    res = np.hstack((p,y.reshape(-1,1)))

训练网络

先这样, 线代推导部分以后填坑

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值