您的位置:首页 > 理论基础 > 计算机网络

Python神经网络代码实现流程(三):反向传播与梯度下降

2017-11-06 12:36 826 查看
前向传播为输入的矩阵经过计算到输出层,而反向传播与梯度下降则是训练神经网络的核心步骤.

梯度下降算法的代码如下:

def SGD(self, training_data, epochs, mini_batch_size, eta, test_data = None):
if test_data: n_test = len(test_data)
n = len(training_data)
for j in range(epochs):   #自动定义的循环次数,也就是训练神经网络的循环次数
random.shuffle(training_data)  #shuffle为将训练接随机打乱重排,保证训练的数据的随机性
mini_batches = [training_data[k:k+mini_batch_size] for k in range(0, n, mini_batch_size)]   #这里形成了一个列表,列表的每一个元素为一个矩阵,一个矩阵就是一次循环的小型数据集.更新神经网络的时候,利用的并不是一个数据(一张图片),而是利用的一个小型矩阵.这样的向量化处理大大提高了代码的执行效率.
for mini_batch in mini_batches:
self.update_mini_batch(mini_batch, eta)  #这里就是利用小型的矩阵对神经网络的所有参数进行一次全面的更新(下面的代码会详细讲解)
if test_data:
print ("Epoch {0}: {1} / {2}". format(j, self.evaluate(test_data), n_test))  #使用了测试集的数据,进行打印显示(evaluate函数后面详述)
else:
print ("Epoch {0} complete".format(j))


以下是每次更新所有参数的代码:

def update_mini_batch(self, mini_batch, eta):
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:  #mini_batch为784行,mini_batch_size列的矩阵
delta_nabla_b, delta_nabla_w = self.backprop(x, y)  #这里是反向传播的函数,是神经网络的核心.以下的代码就是对权重和偏置的更新
#由下面的bp代码可以得到神经网络的所有参数的偏导数矩阵
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)]
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)]  #以上的代码是将没每个小型训练集的矩阵的道得到的权重和偏置都平均,然后再更新权重和偏置.这样可以避免权重和偏置因为某一个训练集数据的错误而过度的更新.


反向传播代码如下:

def backprop(self, x, y):  #这里的(x,y)为传入的一个输入和测试集中的一个输出
nabla_b = [np.zeros(b.shape) for b in self.biases]
nabla_w = [np.zeros(w.shape) for w in self.weights]
activation = x      #activation为输入层的数据
activations = [x]     #actications为经过激活函数处理后的输出矩阵
zs = []     #zs为每个层未经过激活函数处理的矩阵
for b, w in zip(self.biases, self.weights):  #for循环后,得到了两个矩阵,其中zs矩阵是未经过激活的矩阵,列数和神经网络的层数相同(不包括输入层),activations为经过激活后的矩阵,也就是每层的神经网络的输出(包括输入层),其中第一列为输入层,最后一列为神经网络的输出.
z = np.dot(w, activation)+b
zs.append(z)
activation = sigmoid(z)
activations.append(activation)
delta = self.cost_derivative(activations[-1], y) * sigmoid_prime(zs[-1])
 #由于求导的公式推出的偏导数公式,每一层偏置的偏导数等于(a L − y) ⊙ σ ′ (z L ),因此得到的delta就是输出层偏置的偏导数
nabla_b[-1] = delta  #这是偏置偏导数矩阵的赋值,在上面已经进行了矩阵的初始化
nabla_w[-1] = np.dot(delta, activations[-2].transpose())
for l in range(2, self.num_layers):  #以上是最后一层权重和偏置的偏导数赋值,下面的for循环会依次将每一层的权重和偏置的偏导数进行计算得到.
z = zs[-l]
sp = sigmoid_prime(z)
delta = np.dot(self.weights[-l+1].transpose(), delta) * sp
#因为只有最后一层有输出y,所以前面的层数不能够再利用输出求偏导数,利用下一层的偏导数求上一层的偏导数: δ l = ((w l+1 ) T δ l+1 ) ⊙ σ ′ (z l ),这里,用到了下一层权重,和下一层已经求出来的偏导数.
nabla_b[-l] = delta
nabla_w[-l] = np.dot(delta, activations[-l-1].transpose())
return (nabla_b, nabla_w)  #当循环结束,每一次的权重和偏置的偏导数矩阵都已经得到.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息