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反向传播求导结果,由于公式过多,手写。