基于network1版,将其中的权值和偏置更新公式改写成了西瓜书上的公式
代码目录结构:
?其中mnist.pkl.gz文件和mnist_loader.py文件与network1版中的完全一致
""" network2.py文件代码(与network1的区别在于backprop()函数) """
import random
import numpy as np
def sigmoid(Z):
return 1.0 / (1 + np.exp(-Z)) #此处是e的矩阵次方
class Network(object):
#网络初始化
def __init__(self,sizes):
self.num_layers = len(sizes) #sizes列表的数据表示各层神经元个数(从下层到上层) 输入层784 隐层30 输出层10;len(sizes)表示此神经网络的总层数(3层)
self.sizes = sizes
self.biases = [np.random.randn(y,1) for y in sizes[1:]] #返回一个y*1的随机矩阵 偏置b初始化[30*1, 10*1],因为b是一个数,所以是*1,因为第一层(输入层)没有偏置,所以下标从1开始
self.weights = [np.random.randn(y, x) for x, y in zip(sizes[:-1], sizes[1:])]
#权重w初始化[30*784, 10*30],因为第一层(输入层)没有权重,所以y下标从1开始,又因为每个神经元的权重w是个向量,因为输入层有784个属性值要输入到隐层,所以隐层的每个w是784维的;因为隐层有30个属性值要输入到输出层,所以输出层的w的是30维的 zip()函数用法:https://www.runoob.com/python/python-func-zip.html
#上边的weights矩阵是已每个输入为行向量拼构成的矩阵,而下面的mini_batch一小批样本矩阵是由列向量(每个样本的属性是一个784*1的列向量)构成的,目的是方便做矩阵乘法运算
#随机梯度下降算法
def SGD(self, training_data, epochs, mini_batch_size, eta, test_data = None): #epochs表示训练的总次数, mini_batch_size表示把样本分批后每批有mini_batch_size个样本,一份一份训练,eta表示学习率 #默认没有test_data,但输入也可以有test_data,此写法可提高代码的扩展性
training_data = list(training_data) #五万个样本
n = len(training_data) #训练样本的总个数
if test_data: #如果有测试样本
test_data = list(test_data) #一万个测试样本
n_test = len(test_data)
for j in range(epochs): #训练epochs次
random.shuffle(training_data) #shuffle() 方法将序列的所有元素随机排序。https://www.runoob.com/python/func-number-shuffle.html 此处是为了提高样本的随机性,已此增强泛化性
mini_batches = [training_data[k: k + mini_batch_size] for k in range(0, n, mini_batch_size)] #将五万个样本分成(50000/mini_batch_size)批,每批里有mini_batch_size个样本(mini_batches里有(50000/mini_batch_size)个列表),分别运行
"""上行代码等价于
mini_batches = []
for k in range(0, n, mini_batch_size):
mini_batches.append(training_data[k: k + mini_batch_size])
"""
lastsumE = 0 #此次迭代所有样本的累计误差
for mini_batch in mini_batches: #mini_batch一小批(10个)样本构成的列表,其中每个样本都是一个元组(每个元组长度为2,第一部分为784*1的特征向量,第二部分为10*1的标记向量),即列表里包含10个元组
sumE = self.updata_mini_batch(mini_batch, eta) #一批一批运行,每运行一批(updata_mini_batch函数执行一次),所有神经元的w和b全都更新一次,返回这一小批的累计误差
lastsumE = lastsumE + sumE
#测试
print("此次训练后所有样本的累计误差为:"+str(lastsumE))
if test_data:
print("第{}次迭代 : 预测正确{}次 / 预测总次数为{}".format(j, self.evaluate(test_data), n_test))
else:
print("Epoch {} complete".format(j))
#小批量梯度下降算法(先算出10个样本所得到的平均梯度,再更新一次所有神经元的w和b,而非每算一个样本所得到的梯度就更新一次所有神经元的w和b)
def updata_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]
E = 0 #单个样本的误差
sumE = 0 #一小批样本的累计误差
for x, y in mini_batch: #一个一个样本(一批总共10个样本)进行反向传播计算出每个样本对于所有神经元的w,b更新公式中的梯度,并累加起来 (x, y)是一个样本元组,x是特征向量(784*1),y是标记向量(10*1)
delta_nabla_b, delta_nabla_w,E = self.backprop(x, y, E)
sumE = sumE + E
nabla_b = [nb +dnb for nb, dnb in zip(nabla_b,delta_nabla_b)] #nabla_b是前一个样本算出的梯度,delta_nabla_b是当前样本算出的梯度。因为是小批量梯度下降算法,所以此处是梯度的累加,后面计算时还要 除 len(mini_batch)来得到平均梯度
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)] #更新偏置值(/len(mini_batch)*nw即平均梯度)
self.biases = [b - (eta / len(mini_batch)) * nb for b, nb in zip(self.biases, nabla_b)] #更新权值值
#只不过更新w和b,是一批一批更新的(先算出10个样本所得到的平均梯度,再更新一次所有神经元的w和b,而非每算一个样本所得到的梯度就更新一次所有神经元的w和b)。因为如果每有一个样本都要更新一次所有神经元的偏置和权重,计算量太大,此处是先算出10个样本所得到的的平均梯度,用平均梯度更新一次所有神经元的w和b的
return sumE #返回此批样本的累计误差
#标准BP算法(从后向前层算出通过一个训练样本所得到的每个神经元的w和b的更新公式中的梯度)
def backprop(self, x, y, E):
nabla_b = [np.zeros(b.shape) for b in self.biases] #zeros()函数用法:https://blog.csdn.net/lens___/article/details/83927880
nabla_w = [np.zeros(w.shape) for w in self.weights] #每一个神经元w,b更新要用的梯度,初始化为0
#以下4行为西瓜书p102图5.7的公式,计算预测值
alpha = np.dot(self.weights[0],x) # w:30*748 x:748*1 alpha:30*1
boy = sigmoid(alpha - self.biases[0]) #boy:30*1
beta = np.dot(self.weights[1],boy) #beta:10*1
predictY = sigmoid(beta - self.biases[1]) #predictY:10*1
#计算误差(观察用)
E = sum(sum((predictY - y)*(predictY - y)))/2 #单个样本的误差(因为输出层的每个神经元都有误差,所以要sum) p102式(5.4)
#计算4个更新公式中的梯度
g = predictY * (1 - predictY) * (y - predictY) #g:10*1 p103式(5.10)
e = boy *(1 - boy) * (np.dot(self.weights[1].T,g)) #e:30*1 p104式(5.15)
# 保存梯度
nabla_w[1] = -np.dot(g,boy.T) #nabla_w[1]:10*30 p103式(5.11)
nabla_b[1] = g #nabla_b[1]:10*1 p103式(5.12)
nabla_w[0] = -np.dot(e,x.T) #nabla_w[0]:30*748 p103式(5.13)
nabla_b[0] = e #nabla_b[0]:30*1 p103式(5.14)
return (nabla_b, nabla_w,E) #返回每一个神经元w,b更新要用的梯度和此样本的误差
# 估值函数
def evaluate(self, test_data):
test_results = [(np.argmax(self.feedforward(x)), y) for (x, y) in test_data] #feedforward(x)是预测标记,y是真实标记
return sum(int(predict_y == y) for (predict_y, y) in test_results) #返回预测正确的总次数 此处是通过下标来比较的,因为给的测试数据的标记比较特殊(独热码),是10个数中数字为1的下标
# 前向运算(通过输入层输入的x,直接计算出输出层输出的值)
def feedforward(self, a):
for b, w in zip(self.biases, self.weights): # 从biases和weights中取出一一对应的b和w zip()函数用法:https://www.runoob.com/python/python-func-zip.html
a = sigmoid(np.dot(w, a) + b)
return a #a:10*1 输出层每个神经元最后的输出值的意义是该值为1的概率(因为激活函数用的是sigmoid()函数)
""" test.py文件代码 """
from BPNetwork_better import mnist_loader
from BPNetwork_better import network1
from BPNetwork_better import network2
training_data, validation_data, test_data = mnist_loader.load_data_wrapper()
training_data = list(training_data) #training_data是一个长度为5万的列表,即training_data列表里有五万个长度为2的元组,每个元组是一个样本,且每个元组里有该样本的特征向量和标记向量
net = network2.Network([784, 30, 10]) #初始化每层(从下往上)的神经元个数,此句其实是new了一个对象
net.SGD(training_data, 30, 10, 0.3, test_data = test_data) #30表示训练的总次数,10表示把样本分批后每批有10个样本,一份一份训练,0.3表示学习率
|