单隐层BP神经网络的Python实现

上次我们谈了BP神经网络中反向传递的公式推导,这次就用Python来实现一个单隐层BP神经网络。


用BP神经网络建模的整个流程主要包括初始化模型和训练模型两部分。
初始化模型包括导入数据和根据样本的维度初始化参数;
训练模型包括以下两步:第一步,输入数据,正向计算得到隐藏层的输出Z和最终输出Y;第二步,反向传递,利用Y和Z更新参数;不断重复这两步,计算cost和正确率。在运行到设定的次数后停止。


接下来,我们按上文说的建模流程,用Python实现一个简单的单隐层BP神经网络。

import numpy as np
from math import e

我们手动设定了这个模型的样本和参数的维度:

def defsize(d,m,k,n_class):
    return d,m,k,n_class

根据defsize中设定的维度来随机初始化参数和样本

def randomize(D,M,K):
    W1 = np.random.rand(D,M)
    b1 = np.random.rand(M)
    W2 = np.random.rand(M,K)
    b2 = np.random.rand(K)
    
    return W1,b1,W2,b2
def datanize(D,K,N):
    X1 = np.random.rand(N,D) + np.array([3,0])
    X2 = np.random.rand(N,D) + np.array([0,3])
    X3 = np.random.rand(N,D) + np.array([-3,-3])
    X = np.vstack((X1,X2,X3))
    
    T_vector = np.array([0]*N+[1]*N + [2] * N)
    
    T_matrix = np.zeros((int(N*K),K))
    for i in range(K):
        T_matrix[500*int(i-1):500*int(i),i] = 1 
    
    return X,T_vector, T_matrix

上面两步是模型初始化,接下来是模型训练。
首先是正向计算Z和Y。

def forward(X,W1,b1,W2,b2):
    Z_pre = np.dot(X,W1)+ b1
    Z = sigmoid(Z_pre)
    a = np.dot(Z,W2) + b2
    Y = softmax(a)
    
    return Z,Y 

这里面用到了隐藏层的sigmoid和输出层的softmax两个激活函数。定义如下。

def sigmoid(a):
    return 1/(1+np.exp(-a))
def softmax(a):
    return np.exp(a)/np.sum(np.exp(a),keepdims=True)

接下来我们实现反向传递来更新参数的函数。为了便于与上一篇公式推导中的形式对应,我首先写了四个参数的for循环写法。由于这种写法速度很慢,其后我又写了三种速度更快的形式。

首先是W2,即隐藏层到输出层的V。

#slowest
def deri_w2(T_matrix,Y,Z):
    #W2 = np.random.rand(M,K)
    N,M = Z.shape
    K = T_matrix.shape[1]
    deri_w2 = np.zeros((M,K))
    for n in range(N):
        for m in range(M):
            for k in range(K):
                deri_w2[m,k] += (T_matrix[n,k] - Y[n,k])*Z[n,m]
    
    return deri_w2

接着是b2,即隐藏层到输出层的b。b可以直接由W的函数变化过来。

def deri_b2(T_matrix,Y):
    return np.sum(T_matrix-Y, axis = 0)
# b2 is the specification of w2, here  z(n,m) = 1 always, so it can be transferred from deri_w2

然后是W1,即输入层到隐藏层的W。

def deri_w1(T_matrix,Y,W2,Z,X):
    N,M = Z.shape
    K = T_matrix.shape[1]
    D = X.shape[1]
    deri_w1 = np.zeros((D,M))
    
    for n in range(N):
        for m in range(M):
            for d in range(D):
                for k in range(K): #Z[1500,4=M]
                    deri_w1[d,m] += (T_matrix[n,k] - Y[n,k])*W2[m,k]*Z[n,m]*(1-Z[n,m])*X[n,d]
                    
    return deri_w1

随后是b1,即输入层到隐藏层的b。

def deri_b1(T_matrix,Y,W2,Z):
    N,M = Z.shape
    K = T_matrix.shape[1]
    deri_b1 = np.zeros(M)
    
    for n in range(N):
        for m in range(M):
            for k in range(K): #Z[1500,4=M]
                deri_b1[m]+= (T_matrix[n,k] - Y[n,k])*W2[m,k]*Z[n,m]*(1-Z[n,m])*1
                    
    return deri_b1

最后我们来实现三种速度更快的写法。基本思路少用循环,多用矩阵乘法,工具是基础的线性代数。
在这里以W2为例,由上到下速度变快。

def deri_w2_1(T_matrix,Y,Z):
    N,M = Z.shape
    K = T_matrix.shape[1]
    deri_w2 = np.zeros((M,K))
    for n in range(N):
        for k in range(K):
            #T-Y is constant, Z[n,:] and deri[:,k] are M length vector
            deri_w2[:,k] += (T_matrix[n,k] - Y[n,k])*Z[n,:] 
    
    return deri_w2

def deri_w2_2(T_matrix,Y,Z):
    N,M = Z.shape
    K = T_matrix.shape[1]
    deri_w2 = np.zeros((M,K))
    
    for n in range(N):
        deri_w2 += np.outer(Z[n,:],T_matrix[n,:]-Y[n,:]) #M*K
    return deri_w2

#fastest
def deri_w2_3(T_matrix,Y,Z):
    N,M = Z.shape
    K = T_matrix.shape[1]
    deri_w2 = Z.T.dot(T-Y) #knowledge of linear algebra
    
    return deri_w2

对于整个流程,我们还需要计计算cost和正确率的函数。定义如下:

def cost(T_matrix,Y):
    return np.sum(np.multiply(T_matrix,Y))
def classification_rate(T_vector,Y):
    Y_vector = np.argmax(Y,axis=1)
    total = Y_vector.shape[0]
    correct = np.sum(Y_vector == T_vector)
    
    return correct/total

万事俱备,现在我们将整个建模流程写进一个函数中。其中参数eta是反向传递的学习率,epi是训练次数。

def ann(eta,epi):
    D,M,K,N = defsize(d=2, m=4, k=3, n_class=500)
    W1,b1,W2,b2 = randomize(D,M,K)
    X, T_vector, T_matrix = datanize(D,K,N)
    costs = []
    for i in range(epi):
        Z, Y = forward(X,W1,b1,W2,b2)
        W2 += eta * deri_w2(T_matrix,Y,Z) #W2 <- W2 - eta * d(-log)/dw = W2 - eta* (-1) * deri_w2 #thats why here is +=
        W1 += eta * deri_w1(T_matrix,Y,W2,Z,X)
        b2 += eta * deri_b2(T_matrix,Y)
        b1 += eta * deri_b1(T_matrix,Y,W2,Z)
        if i % 100 == 0:
            c = cost(T_matrix,Y)
            r = classification_rate(T_vector,Y)
            costs.append(c)
            print("The cost is : "+str(c)+" . The classification rate is : "+str(r)+" .")

这里放一个我跑的例子。

ann(1e-4,500)

输出如下。

The cost is : 0.3265428804999796 . The classification rate is : 0.3333333333333333 .
The cost is : 0.5030679473418662 . The classification rate is : 0.3333333333333333 .
The cost is : 0.5008099421421485 . The classification rate is : 0.6446666666666667 .
The cost is : 0.5003610855164803 . The classification rate is : 0.6666666666666666 .
The cost is : 0.5001991955365086 . The classification rate is : 0.6666666666666666 .

补充一点,因为我对eta的理解不深,文中eta的设置就选择了一个比较常用的值;我试过其他不同的值,有些是单纯地训练效果差,有的甚至会报溢出的错误。


以上就是用Python实现一个简单的单隐层bp神经网络的全部内容,欢迎大家批评指正。

Comments
Write a Comment