Python 多层BP神经网络的实现及应用
神经网络
在深度学习中,其中一种网络架构是前向传播——反向传播,本文就讲解一下反向传播算法(Error Back Propagation),并在不调用深度学习库的情况下实现BP算法的三角函数拟合。
下面的图片是一个简单的多层神经网络(都是自己画的),可以概括为输入层、输出层、隐藏层,相邻层之间存在数学关系,这与我们给模型设置、训练的参数有关。
这里用更简单的四层的神经网络来讲解,(输入层:1;隐藏层1:2;隐藏层2:2;输出层:1)。拟合三角函数理论上是可以用简单的网络来实现,也就是一个输入一个输出,中间的隐藏层只需一层也能拟合出接近真实值的结果。
输入层就是我们输入的x;输出层的y是网络计算出的结果,而不是目标结果;隐藏层是我们将套公式的过程抽象成多个结点间的数据传递。
其中每层的架构都是:传入值-计算-传出值
前向传播(Forward propagation)
层与层之间的通过前向传播公式传递的(当前层的output是下一层的input):
公式1:
公式2:
其中z是神经元(结点)的input,a是神经元的output,W是权重,b是偏置,f是激活函数。
从第一层开始传递数据,套公式计算,套激活函数(最后一层不要用激活函数),最后一直传递到最后一层,得到该网络的结果。
反向传播(Error Back Propagation)
为了得到好的模型,要更新网络的参数W和b,才能拟合出接近结果的网络,这里就用到了反向传播算法。
这里将神经网络的损失函数记为L(W, b),W,b是指网络中所有的参数。
通过计算L(W, b)(神经网络的损失),用到梯度下降公式,从最后一层开始,逆向求出每一层W, b的梯度。然后再对W, b的值进行更新(更新W,b没有什么先后顺序)。
公式3:
公式4:
反向传播更新权重推导:
根据公式3、4可以设公式5为:
公式5:
再有公式1、2得公式6、7:
公式6:
公式7:
由公式(3)(4)(5)(6)(7)推导得:
公式8:
公式9:
直到将所有的W和b的梯度计算完,使用负梯度更新所有参数,这算完成了一轮训练。各层之间的计算是一样的,不过是利用到了高数的链式法则来求导,过程中会遇到矩阵相乘,它也只是用到了线性代数,写代码的话很容易掉到两个矩阵尺寸的问题导致无法计算的巨坑中,这时候画图思考就行。
在计算中使用到的激活函数,可以选择sigmoid函数,但是sigmoid的容易崩,对我们拟合三角函数模型的效果不明显,可以换成tanh或ReLu等更高效率的激活函数。
代码部分:
代码解决的问题:拟合y = sin(5/4πx) +8,区间(-5,5)
①创建数据集,产生x和y的值;
②设置网络层。
③创建模型,设置网络参数:导入网络层,设置W,b,设置激活函数和损失函数
④训练模型,导入训练集,设置学习率,模型在前向传播完成后开始BP反向传播更新参数,不断循环,使各层的权重向量和偏置项接近最优解(没有接近就是网络不行,继续改参数)。
⑤训练已经完成,导入数据,测试网络效果(这个代码不是预测,不能计算训练数据以外的)。
代码实现
我写的这份代码可以实现3层以上的神经网络,并且只需要修改每一层的神经元个数就可以自动完成神经网络的编辑。
我将用这个BP神经网络去拟合函数 f(x)=sin(5πx/4)+8(其中x∈(-5,5))
# python-Error Back Propagation
# coding=utf-8
import numpy
import numpy as np
import matplotlib.pyplot as plt
def loss_derivative(output_activations, y):
return 2 * (output_activations - y)
def tanh(z):
return np.tanh(z)
def tanh_derivative(z):
return 1.0 - np.tanh(z) * np.tanh(z)
def mean_squared_error(predictY, realY):
Y = numpy.array(realY)
return np.sum((predictY - Y) ** 2) / realY.shape[0]
class BP:
def __init__(self, sizes, activity, activity_derivative, loss_derivative):
self.num_layers = len(sizes)
self.sizes = sizes
self.biases = [np.zeros((nueron, 1)) for nueron in sizes[1:]]
self.weights = [np.random.randn(next_layer_nueron, nueron) for nueron, next_layer_nueron in
zip(sizes[:-1], sizes[1:])]
self.activity = activity
self.activity_derivative = activity_derivative
self.loss_derivative = loss_derivative
def predict(self, a):
re = a.T
n = len(self.biases) - 1
for i in range(n):
b, w = self.biases[i], self.weights[i]
re = self.activity(np.dot(w, re) + b)
re = np.dot(self.weights[n], re) + self.biases[n]
return re.T
def update_batch(self, batch, learning_rate):
temp_b = [np.zeros(b.shape) for b in self.biases]
temp_w = [np.zeros(w.shape) for w in self.weights]
for x, y in batch:
delta_temp_b, delta_temp_w = self.update_parameter(x, y)
temp_w = [w + dw for w, dw in zip(temp_w, delta_temp_w)]
temp_b = [b + db for b, db in zip(temp_b, delta_temp_b)]
self.weights = [sw - (learning_rate / len(batch)) * w for sw, w in zip(self.weights, temp_w)]
self.biases = [sb - (learning_rate / len(batch)) * b for sb, b in zip(self.biases, temp_b)]
def update_parameter(self, x, y):
temp_b = [np.zeros(b.shape) for b in self.biases]
temp_w = [np.zeros(w.shape) for w in self.weights]
activation = x
activations = [x]
zs = []
n = len(self.biases)
for i in range(n):
b, w = self.biases[i], self.weights[i]
z = np.dot(w, activation) + b
zs.append(z)
if i != n - 1:
activation = self.activity(z)
else:
activation = z
activations.append(activation)
d = self.loss_derivative(activations[-1], y)
temp_b[-1] = d
temp_w[-1] = np.dot(d, activations[-2].T)
for i in range(2, self.num_layers):
z = zs[-i]
d = np.dot(self.weights[-i + 1].T, d) * self.activity_derivative(z)
temp_b[-i] = d
temp_w[-i] = np.dot(d, activations[-i - 1].T)
return (temp_b, temp_w)
def fit(self, train_data, epochs, batch_size, learning_rate, validation_data=None):
n = len(train_data)
for j in range(epochs):
np.random.shuffle(train_data)
batches = [train_data[k:k + batch_size] for k in range(0, n, batch_size)]
for batch in batches:
self.update_batch(batch, learning_rate)
if (validation_data != None):
val_pre = self.predict(validation_data[0])
print("Epoch", j + 1, '/', epochs, ' val loss:%12.12f' % mean_squared_error(val_pre, validation_data[1]))
def load_data(step):
x = np.array([numpy.mgrid[-5: 5: 10 / step]]).T
y = numpy.sin(5 * numpy.pi * x / 4) + 8
return x, y
if __name__ == "__main__":
numpy.random.seed(7)
step = 500
beta = 1e-3
layer = [1, 32, 64, 128, 32, 1]
x, y = load_data(step)
data = [(np.array([x_value]), np.array([y_value])) for x_value, y_value in zip(x, y)]
model = BP(layer, tanh, tanh_derivative, loss_derivative)
model.fit(train_data=data, epochs=2000, batch_size=64, learning_rate=beta, validation_data=(x, y))
predict = model.predict(x)
plt.plot(x, y, "-r", linewidth=2, label='origin')
plt.plot(x, predict, "-b", linewidth=1, label='predict')
plt.legend()
plt.grid(True)
plt.show()
最终结果
最终结果是这样:
总的来说还是不错的,能够较好的完成函数的拟合,试了一下其他三角函数效果也不错。
待改进空间:这份代码我只实现了SGD,还有Momentum,AdaGrad,RMSProp,Adam还没实现,将来有空就给他补上吧