此版是copy的别人的代码,对其进行了注释理解,其中的公式推导可参考BP神经网络-从推导到实现_river_19的博客-CSDN博客
代码目录结构:
其中mnist.pkl.gz文件的下载地址:https://academictorrents.com/browse.php?search=mnist.pkl.gz
""" mnist_loader.py文件代码 """
import pickle
import gzip
import numpy as np
def load_data():
f = gzip.open('mnist.pkl.gz', 'rb')
training_data, validation_data, test_data = pickle.load(f, encoding='bytes')
f.close()
return training_data, validation_data, test_data
def vectorized_result(j):
e = np.zeros((10, 1))
e[j] = 1.0
return e
def load_data_wrapper():
tr_data, va_data, te_d = load_data()
# training data
training_inputs = [np.reshape(x, (784, 1)) for x in tr_data[0]]
training_results = [vectorized_result(y) for y in tr_data[1]]
training_data = zip(training_inputs, training_results)
# validation data
validation_inputs = [np.reshape(x, (784, 1)) for x in va_data[0]]
validation_results = [vectorized_result(y) for y in va_data[1]]
validation_data = zip(validation_inputs, validation_results)
# test_data
test_inputs = [np.reshape(x, (784, 1)) for x in te_d[0]]
test_data = zip(test_inputs, te_d[1])
return(training_data, validation_data, test_data)
def get_Dataset():
t, v, tt = load_data_wrapper()
validation_data = list(v)
# training_data = list(t) + validation_data
training_data = list(t)
testing_data = list(tt)
# len_t = len(training_data)
# len_tdi = len(training_data[0][0])
# len_tl = len(training_data[0][1])
# x_train = np.zeros((len_t, len_tdi))
# y_train = np.zeros((len_t, len_tl))
# for i in range(len_t):
# x_train[i] = np.array(training_data[i][0]).transpose()
# y_train[i] = np.array(training_data[i][1]).transpose()
#
# len_tt = len(testing_data)
# x_test = np.zeros((len_tt, len_tdi))
# y_test = np.zeros(len_tt)
# for i in range(len_tt):
# x_test[i] = np.array(testing_data[i][0]).transpose()
# y_test[i] = testing_data[i][1]
# return x_train, y_train, x_test, y_test
return training_data, validation_data, testing_data
""" 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 = network1.Network([784, 30, 10]) #初始化每层(从下往上)的神经元个数,此句其实是new了一个对象
net.SGD(training_data, 30, 10, 3.0, test_data = test_data) #30表示训练的总次数,10表示把样本分批后每批有10个样本,一份一份训练,3.0表示学习率
""" network1文件代码 """
import random
import numpy as np
def sigmoid(Z):
return 1.0 / (1 + np.exp(-Z)) #此处是e的矩阵次方
#导数
def sigmoid_prime(Z):
return sigmoid(Z) * (1 - sigmoid(Z))
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):
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 # 返回此批样本的累计误差
#反向传播(从后向前层算出通过一个训练样本所得到的每个神经元的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
activation = x #输出值(激活值)(输入层的激活值就是x,因为输入层前面没有其他层了)
activations = [x] #用列表一列列地存储每一层的输出值(激活值)
zs = [] #用列表一列列地存储每一层(输入层除外)的输入值
#算出每一层的输入值和输出值,下面求梯度要用
for b,w in zip(self.biases, self.weights): #循环次数=总层数-1
z = np.dot(w, activation) + b #w是30*784,activation是784*1,是输入层的输入值;所以z是30*1的,是隐层的30个接收值
zs.append(z)
activation = sigmoid(z) #通过激活函数映射将隐层的输出值(激活值)(此神经网络选择的激活函数是sigmoid函数)
activations.append(activation)
E = sum(sum((activations[2] - y) * (activations[2] - y)))/2 #单个样本的误差(观察用)
#计算最后一层(输出层)的单个样本的误差,用误差算梯度
delta = self.cost_derivative(activations[-1], y) * sigmoid_prime(zs[-1]) #流程2公式
nabla_b[-1] = delta #最后一层偏置更新公式里的梯度 #流程4中b的公式
nabla_w[-1] = np.dot(delta, activations[-2].transpose()) #最后一层权值更新公式里的梯度 #流程4中w的公式
#计算从倒数第二层至第二层的单个样本的误差,用误差算梯度
for i in range(2, self.num_layers):
z = zs[-i]
sp = sigmoid_prime(z)
delta = np.dot(self.weights[-i +1].transpose(), delta) * sp #前一层的误差 #流程3公式
nabla_b[-i] = delta #前一层偏置更新公式里的梯度 #流程4中b的公式
nabla_w[-i] = np.dot(delta, activations[-i - 1].transpose()) #前一层权值更新公式里的梯度 #流程4中w的公式
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]
return sum(int(x == y) for (x, y) in test_results)
# 前向运算(通过输入层输入的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
def cost_derivative(self, output_activations, y):
return (output_activations - y)