根据配套的教材:西瓜书
首先整个流程就是先是前向传输,再是反向传播,根据最后的输出值与实际值的差距来更改我们的阈值(在实际代码中用biases表示)和权值,利用梯度下降法找到我们的最优解。
我们利用这个模型来解释整个过程,我们采用sigmod函数讲隐藏层和输出层的输入转化为输出。
在西瓜书里面每一层的输出就是sigmod(输入-阈值),输入呢就是x1,x2,x3乘以相应的权值的和,而最下面的数其实就是对应我们下一层的阈值了,这样画就是看起来方便,每一列进行一次相加减得到下一列的数。图和公式就跟书上对应起来了。
整个算法的流程如下:
前向传输(Feed-Forward)
从输入层=>隐藏层=>输出层,一层一层的计算所有神经元输出值的过程。
逆向反馈(Back Propagation)
因为输出层的值与真实的值会存在误差,我们可以用均方误差来衡量预测值和真实值之间的误差。
书上是采用均方误差来定义的,目的肯定是尽可能的让均方误差变小,那么我们的输出值肯定就要更贴近我们的实际值,就必须要调整我们的权值和阈值:
书上有一点点不一样但是两个公式放在一起就不会有影响了。然后就是推导我们的误差了,根据链式法则whj先影响到的是j个输出层的输入值Zj,在影响到其输出值aj,最后再影响到均方误差的,所以有:
又因为:
而sigmod函数有一个非常好的性质就是:
所以就可以求得我们的误差(将式子的前两项定义为误差函数):
实际上,上式的后一项也可以写为,在写代码的过程中我们一般就是利用这个式子
来写的,并没有用上面那个。
所以我们的w,b的更新量就能算出来
跟我们书上的公式是完全一样的,用一句话概括我们的w更新值:输出值与实际值之间的运算(a(1-a)(y-a))乘以上一层的输出值
当我们得到了输出层的w和b之后我们就利用我们算好的误差函数来计算隐藏层的w和b,书上虽然只有三层,但是把输入层的xi换了,其实就自动扩展为很多层的w和b了,具体公式如下:
还是用一句话概括:上一层的误差函数乘以上一层的权值的和,乘以本层的输出(a(1-a))(上面的导数拆开就是输出值*(1-输出值)),概括的虽然不太准确但是更方便记忆。
推导公式如下:
整个流程就是这样的了,然后附上python代码并有注释。
第一步我们先定义一些量:
def __init__(self, sizes):
"""
:param sizes: list类型,储存每层神经网络的神经元数目
譬如说:sizes = [2, 3, 2] 表示输入层有两个神经元、
隐藏层有3个神经元以及输出层有2个神经元
"""
# 有几层神经网络
self.num_layers = len(sizes)
self.sizes = sizes
# 除去输入层,随机产生每层中 y 个神经元的 biase 值(0 - 1)
self.biases = [np.random.randn(y, 1) for y in sizes[1:]]
# 随机产生每条连接线的 weight 值(0 - 1)
self.weights = [np.random.randn(y, x)
for x, y in zip(sizes[:-1], sizes[1:])]
一般我们把,输入的某一个值的全部属性都放在一行,所以输入有多少行就是有多少数据,根据公式是wx+b来计算,w的矩阵形式就是后一层每一个神经元所拥有的权值都放在一行,最后就是b了,把每一个神经的阈值都按行分开。
首先向前传输得到我们的输出值
def feedforward(self, a):
"""
前向传输计算每个神经元的值
:param a: 输入值
:return: 计算后每个神经元的值
"""
for b, w in zip(self.biases, self.weights):
# 加权求和以及加上 biase
# print(w.shape)
a = sigmoid(np.dot(w, a) + b)
return a
源码里使用的是随机梯度下降(Stochastic Gradient Descent,简称 SGD),原理与梯度下降相似,不同的是随机梯度下降算法每次迭代只取数据集中一部分的样本来更新 w 和 b 的值,速度比梯度下降快,但是,它不一定会收敛到局部极小值,可能会在局部极小值附近徘徊。
def SGD(self, training_data, epochs, mini_batch_size, eta,
test_data=None):
"""
随机梯度下降
:param training_data: 输入的训练集
:param epochs: 迭代次数
:param mini_batch_size: 小样本数量
:param eta: 学习率
:param test_data: 测试数据集
"""
if test_data: n_test = len(test_data)
n = len(training_data)
for j in range(epochs):
# 搅乱训练集,让其排序顺序发生变化
random.shuffle(training_data)#那么mini_batches就是随机抽取了
# 按照小样本数量划分训练集
mini_batches = [
training_data[k:k+mini_batch_size]
for k in range(0, n, mini_batch_size)]
for mini_batch in mini_batches:
# 根据每个小样本来更新 w 和 b,代码在下一段
self.update_mini_batch(mini_batch, eta)
# 输出测试每轮结束后,神经网络的准确度
if test_data:
print("Epoch {0}: {1} / {2}".format(
j, self.evaluate(test_data), n_test) )
else:
print("Epoch {0} complete".format(j) )
update_mini_batch的定义如下:
def update_mini_batch(self, mini_batch, eta):
"""
更新 w 和 b 的值
:param mini_batch: 一部分的样本
:param eta: 学习率
"""
# 根据 biases 和 weights 的行列数创建对应的全部元素值为 0 的空矩阵
nabla_b = [np.zeros(b.shape) for b in self.biases]
nabla_w = [np.zeros(w.shape) for w in self.weights]
for x, y in mini_batch:
# 根据样本中的每一个输入 x 的其输出 y,计算 w 和 b 的偏导数
delta_nabla_b, delta_nabla_w = self.backprop(x, y)
# 累加储存偏导值 delta_nabla_b 和 delta_nabla_w
nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)]
nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]
# 更新根据累加的偏导值更新 w 和 b,这里因为用了小样本,
# 所以 eta 要除于小样本的长度
self.weights = [w-(eta/len(mini_batch))*nw
for w, nw in zip(self.weights, nabla_w)]
self.biases = [b-(eta/len(mini_batch))*nb
for b, nb in zip(self.biases, nabla_b)]
这段代码当中有一个backprop函数,他也是我们这段程序的核心代码了,也就是我们的反向传播过程:
def backprop(self, x, y):
"""
:param x:
:param y:
:return:
"""
nabla_b = [np.zeros(b.shape) for b in self.biases] #阈值保存在这里
nabla_w = [np.zeros(w.shape) for w in self.weights] #权值保存在这里
# 前向传输
activation = x
# 储存每层的神经元的值的矩阵,下面循环会 append 每层的神经元的值
activations = [x]
# 储存每个未经过 sigmoid 计算的神经元的值
zs = []
for b, w in zip(self.biases, self.weights):
z = np.dot(w, activation)+b#隐含层的输入
zs.append(z)
activation = sigmoid(z)
activations.append(activation)
# 求 δ 的值
# activations[-1]输出层结果
delta = (activations[-1] - y) * sigmoid_prime(zs[-1])#输出层误差的求解
nabla_b[-1] = delta#用于求隐含层与输出层之间的偏差b
# 乘于前一层的输出值
nabla_w[-1] = np.dot(delta, activations[-2].transpose())#输出层的权重Wjk
for l in range(2, self.num_layers):#倒数第二层开始往前求出各自的w和b
# 从倒数第 **l** 层开始更新,**-l** 是 python 中特有的语法表示从倒数第 l 层开始计算
# 下面这里利用 **l+1** 层的 δ 值来计算 **l** 的 δ 值
z = zs[-l]
sp = sigmoid_prime(z)
delta = np.dot(self.weights[-l+1].transpose(), delta) * sp
nabla_b[-l] = delta
nabla_w[-l] = np.dot(delta, activations[-l-1].transpose())
return (nabla_b, nabla_w)
其中前面的evaluate是一个评估函数如下:
def evaluate(self, test_data):
# 获得预测结果
test_results = [(np.argmax(self.feedforward(x)),np.argmax(y))
for (x, y) in test_data]
return sum(x == y for (x, y) in test_results)
核心部分就这么多了,有不懂的地方可以给我留言。