BP算法实现

1 项目信息

项目名称:bp算法实现

语言:python

平台:jupyter

2 功能介绍

实现网络定义:网络结构、激活函数、softmax、损失函数

实现前向传播、后向传播过程

实现批量数据的BP 过程

3 具体代码

import numpy as np

3.1 网络结构定义

输入模型的相关参数以确定模型的具体结构。

模型参数包括模型的层数、每层的神经元数、所使用的激活函数以及学习率。

模型默认提供ReLU和sigmoid激活函数,损失函数默认设置为交叉熵。

layerNum = int(input('layer num : '))

# 输入格式 eg: [2,3,2]
unitNums = input('unit nums : ')
unitNums = eval(unitNums)
maxUnitNum = max(unitNums[1:])

# 输入格式 eg: sigmoid/relu
actFun = input('activation function : ')

learnRate = input('learning rate : ')

3.2 初始化模型计算矩阵

根据模型的层数和每层的神经元个数,确定权重个数。

  • 权重矩阵weights[层数-1,单层最大权重个数],使用高斯函数初始化权重矩阵。
  • 权重跟新矩阵derWeights[层数-1,单层最大权重个数],用于存储经过一次反向传播后权重的更新值。
  • 隐藏层神经元矩阵hiddenUnits[层数-1,单层最大单元数],用于存储神经元的计算结果(未激活)。
  • 隐藏层神经元激活值矩阵hiddenUnitActivitys,用于存储神经元计算结果经过激活函数的结果。
  • 隐藏层神经元激活值导数矩阵derHiddenUnitActivitys,用于存储神经元计算结果经过激活函数导函数的结果
weightNums = [unitNums[i]*unitNums[i+1] for i in range(layerNum-1)]
print('weight nums : {}'.format(weightNums))
maxWeightNum = max(weightNums)
print('max weight num : {}'.format(maxWeightNum))

weights = np.random.normal(size=(layerNum-1, maxWeightNum))
for i in range(layerNum-1):
    t = weightNums[i]
    weights[i,t:] = 0
print('weights : {}'.format(weights.shape))

derWeights = np.zeros(shape=(layerNum-1, maxWeightNum))
print('derweights : {}'.format(derWeights.shape))

hiddenUnits = np.zeros(shape=(layerNum-1, maxUnitNum))
print('hidden units : {}'.format(hiddenUnits.shape))

hiddenUnitActivitys = np.zeros(shape=(layerNum-1, maxUnitNum)) 
print('hidden unit activitys : {}'.format(hiddenUnitActivitys.shape))

derHiddenUnitActivitys = np.zeros(shape=(layerNum-1, maxUnitNum))
print('der hidden unit activitys : {}'.format(derHiddenUnitActivitys.shape))

3.3 定义相关函数

定义模型相关的函数。

具体包括激活函数ReLU和sigmoid以及其对应的导函数、softmax函数。

其中激活函数及其导函数通过函数指针的方式进行调用。

def sigmoid(x):
    y = 1 / (1 + np.exp(-x))
    return y

def relu(x):
    if x>=0:
        return x
    else:
        return 0

def der_sigmoid(x):
    return sigmoid(x)*(1-sigmoid(x))

def der_relu(x):
    if x>=0:
        return 1
    else:
        return 0 
      
def softmax(x):
    temp = np.exp(x)
    result = temp/sum(temp)
    return result

# 函数指针调用激活函数及其导函数
if actFun == 'sigmoid':
    derfun = der_sigmoid
    fun = sigmoid
elif actFun == 'relu':
    derfun = der_relu
    fun = relu

3.4 前向传播

输入模型的batch_size,以及对应数量的样本。

样本由属性和标签组成,其中标签应使用one-hot编码格式输入。

# input输入格式 eg: [1,1]
# label输入格式 eg: [0,1]
batchSize = int(input('batch size : '))
inputsList = list()
labelsList = list()
for i in range(batchSize):
    inputs = input('the {}th input values({}) : '.format(i+1,unitNums[0]))
    labels = input('the {}th label values({}) : '.format(i+1,unitNums[0]))
    inputs = eval(inputs)
    labels = eval(labels)
    inputs = np.array(inputs)
    labels = np.array(labels)
    inputsList.append(inputs)
    labelsList.append(labels)

定义并调用前向传播函数。

前向传播函数每次接收一个样本,返回模型的输出矩阵、神经元矩阵以及对应的激活值矩阵。

根据模型对batch_size,调用对应次数的前向传播函数,并存储每次前向传播的结果。

def multi(x, y):
    l = list(x.shape)[0]
    result = 0
    for i in range(l):
        result = result + x[i]*y[i]
    return result

def front(layerInputs):
    print('input: {}'.format(layerInputs))
    for i in range(layerNum-1):
        stride = unitNums[i+1]
        for j in range(unitNums[i+1]):
            tempWeights = weights[i,j::stride]
            hiddenUnits[i, j] = multi(layerInputs,tempWeights)
        for j in range(unitNums[i+1]):
            hiddenUnitActivitys[i, j] = fun(hiddenUnits[i, j]) 
        layerInputs = hiddenUnitActivitys[i,:unitNums[i+1]]
    outputs = softmax(hiddenUnitActivitys[-1, :unitNums[-1]])
    print('output : {}'.format(hiddenUnitActivitys[-1, :unitNums[-1]]))
    print('softmax output : {}'.format(outputs))
    print('unit values : \n',hiddenUnits)
    print('unit activity values :\n',hiddenUnitActivitys,'\n')
    return hiddenUnits.copy(),hiddenUnitActivitys.copy(),outputs.copy()
    
hiddenUnitsList = list()
hiddenUnitActivitysList = list()
outputsList = list()

for i in range(batchSize):
    hiddenUnits,hiddenUnitActivitys,outputs = front(inputsList[i])
    hiddenUnitsList.append(hiddenUnits)
    hiddenUnitActivitysList.append(hiddenUnitActivitys)
    outputsList.append(outputs)

3.5 反向传播

定义反向传播函数。

在反向传播函数中,根据一个batch_size的样本,计算各矩阵的sum矩阵以及中间矩阵。

反向传播函数返回结果为权重刚更新矩阵。

def back(hiddenUnitsList,hiddenUnitActivitysList,inputsList,outputsList,labelsList,batchSize):
    sumHiddenUnits = np.zeros(shape=(layerNum-1, maxUnitNum))
    sumHiddenUnitActivitys = np.zeros(shape=(layerNum-1, maxUnitNum)) 
    sumInputs = np.zeros(shape=unitNums[0])
    sumOutputs = np.zeros(shape=unitNums[-1])
    sumLabels = np.zeros(shape=unitNums[-1])
    
    for i in range(batchSize):
        sumHiddenUnits = sumHiddenUnits + hiddenUnitsList[i]
        sumHiddenUnitActivitys = sumHiddenUnitActivitys + hiddenUnitActivitysList[i]
        sumInputs = sumInputs + inputsList[i]
        sumOutputs = sumOutputs + outputsList[i]
        sumLabels = sumLabels + labelsList[i]
        
    derHiddenUnitActivitys = derfun(sumHiddenUnits)
    
   	# 损失函数对softmax层的前一层的求导
    derLossAndSoftmax = [sumOutputs[i]*batchSize-sumLabels[i] for i in range(unitNums[-1])]
    
    deraz = np.zeros(shape=(layerNum-1, maxUnitNum))
    for i in range(unitNums[-1]):
        deraz[-1,i] = derHiddenUnitActivitys[-1,i] * derLossAndSoftmax[i]
    for i in range(layerNum-2):
        unitNum = unitNums[(i+2)*-1]
        nextUnitNum = unitNums[(i+1)*-1]
        tempDeraz = []
        for j in range(unitNum):
            tempDeraz.append(derHiddenUnitActivitys[(i+2)*-1, j ] *
                             multi(weights[(i+1)*-1, j*2:j*2+2], derHiddenUnitActivitys[(i+1)*-1, :nextUnitNum]))
        deraz[(i+2)*-1, :unitNum] = tempDeraz
    
    for i in range(layerNum-2):
        unitNum = unitNums[(i+2)*-1]
        nextUnitNum = unitNums[(i+1)*-1]
        tempDerWeights = []
        for j in range(unitNum):
            for k in  range(nextUnitNum):
                tempDerWeights.append(sumHiddenUnitActivitys[(i+2)*-1, j] * deraz[(i+1)*-1, k])
        derWeights[(i+1)*-1,:weightNums[(i+1)*-1]] = tempDerWeights

    tempDerWeights = []
    for j in range(unitNums[0]):
        for k in range(unitNums[1]):
            tempDerWeights.append(sumInputs[j] * deraz[0, k])
    derWeights[0, :weightNums[0]] = tempDerWeights

    print('der weights : \n', derWeights)
    print('pre weights : \n', weights)
    print('new weights : \n', weights-derWeights)
    return derWeights

调用反向传播函数,展示权重更新结果。

derWeights = back(hiddenUnitsList,hiddenUnitActivitysList,inputsList,outputsList,labelsList,batchSize)
weights = weights - derWeights

4 交叉熵、softmax求导结果

随之附上交叉熵、softmax反向传播求导结果,由于公式过多,手写。

bpr配流 python bp算法python_神经网络