1、公式的推导
公式比较繁琐且难以理解,如果看不懂的话,推荐去下面这个网址观看公式的讲解:
【机器学习实战】【python3版本】【代码讲解】_哔哩哔哩_bilibili
2、代码实现
2.1、第一种实现方法(由于代码过长这里只给出关键代码)
- 首先导入我们需要用到的包
import random
import numpy as np
- 创建一个Network类
class Network(object):
# 网络初始化
def __init__(self, sizes):
self.num_layers = len(sizes) # 各层神经元个数 784 30 10
self.sizes = sizes
self.biases = [np.random.randn(y, 1) for y in sizes[1:]]
# 偏置初始化 [30x1,10x1],随机初始化
self.weights = [np.random.randn(y, x)
for x, y in zip(sizes[:-1], sizes[1:])]
# 权重初始化 [30x784,10x30],zip表示合并,如果有多个取最小长度,其余省略
在这段代码中,列表 sizes 包含各层神经元的数量。例如,如果我们想创建⼀个在第⼀层有 2 个神经元,第⼆层有 3 个神经元,最后层有 1 个神经元的 Network 对象,我们应这样写代码:
net = Network([2, 3, 1])//表示有三层网络
Network 对象中的偏置和权重都是被随机初始化的,使⽤ Numpy 的 np.random.randn 函数来⽣ 成均值为 0,标准差为 1 的⾼斯分布。
我们然后对 Network 类添加⼀个 feedforward ⽅法,对于⽹络给定⼀个输⼊ a,返回对应的输 出。这个⽅法所做的是对每⼀层应⽤⽅程。
# 前向运算
def feedforward(self, a):
for b, w in zip(self.biases, self.weights):
a = sigmoid(np.dot(w, a) + b)
return a
为了进行学习,我们创建一个SGD方法实现随机梯度下降法
def SGD(self, training_data, epochs, mini_batch_size, eta,
test_data=None):
training_data = list(training_data) # 50000个样本
n = len(training_data)
if test_data:
test_data = list(test_data) # 10000个样本
n_test = len(test_data)
for j in range(epochs):
random.shuffle(training_data)
mini_batches = [
training_data[k:k + mini_batch_size]
for k in range(0, n, mini_batch_size)] # 将50000个样本分成mini_batch_size批分别运行
for mini_batch in mini_batches:
self.update_mini_batch(mini_batch, eta) # 一批一批运行
if test_data:
print("Epoch {} : {} / {}".format(j, self.evaluate(test_data), n_test))
else:
print("Epoch {} complete".format(j))
其中:training_data 是⼀个 (x, y) 元组的列表,表⽰训练输⼊和其对应的期望输出。变量 epochs 和 mini_batch_size 正如你预料的——迭代期数量,和采样时的⼩批量数据的⼤⼩。eta 是学习速率, η。
同样为了方便进行梯度下降和更新,我们创建了简单的从训练数据的随机采样⽅法:
# 小批量梯度下降法
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: # 还是一个一个样本进行反向传播计算
delta_nabla_b, delta_nabla_w = self.backprop(x, y)
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):
nabla_b = [np.zeros(b.shape) for b in self.biases]
nabla_w = [np.zeros(w.shape) for w in self.weights]
# feedforward
activation = x # 存储每层的z和a值
activations = [x] # list to store all the activations, layer by layer
zs = [] # list to store all the z vectors, layer by layer
for b, w in zip(self.biases, self.weights):
z = np.dot(w, activation) + b
zs.append(z)
activation = sigmoid(z)
activations.append(activation)
# backward pass 计算最后一层的误差
delta = self.cost_derivative(activations[-1], y) * \
sigmoid_prime(zs[-1])
nabla_b[-1] = delta
nabla_w[-1] = np.dot(delta, activations[-2].transpose())
# 计算从倒数第二层至第二层的误差
for l in range(2, self.num_layers):
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)
2.2、BP神经网络的改进
2.2.1、BP神经网络改进
- 交叉熵代价函数:
“严重错误”导致学习缓慢,如果在初始化权重和偏置时,故意产生一个背离预期较大的 输出,那么训练网络的过程中需要用很多次迭代,才能抵消掉这种背离,恢复正常的学习。
(1) 引入交叉熵代价函数目的是解决一些实例在刚开始训练时学习得非常慢的问题,其 主要针对激活函数为 Sigmod 函数
(2) 如果采用一种不会出现饱和状态的激活函数,那么可以继续使用误差平方和作为损 失函数
(3) 如果在输出神经元是 S 型神经元时,交叉熵一般都是更好的选择
(4) 输出神经元是线性的那么二次代价函数不再会导致学习速度下降的问题。在此情形 下,二次代价函数就是一种合适的选择
(5) 交叉熵无法改善隐藏层中神经元发生的学习缓慢
(6) 交叉熵损失函数只对网络输出“明显背离预期”时发生的学习缓慢有改善效果
(7) 应用交叉熵损失并不能改善或避免神经元饱和,而是当输出层神经元发生饱和时, 能够避免其学习缓慢的问题。
- 4 种规范化技术:
(1) 早停止。跟踪验证数据集上的准确率随训练变化情况。如果我们看到验证数据上的 准确率不再提升,那么我们就停止训练
(2) 正则化
(3) 弃权(Dropout)
(4) 扩增样本集
- 更好的权重初始化方法
不好的权重初始化方法会导致出现饱和问题,好的权重初始化方法不仅仅能够带来训练 速度的加快,有时候在最终性能上也有很大的提升。
2.3、第二种实现方法(代码过多,这里只给出改进部分)c
- 误差平方和代价函数
class QuadraticCost(object): # 误差平方和代价函数
@staticmethod
def fn(a, y):
return 0.5*np.linalg.norm(a-y)**2
@staticmethod
def delta(z, a, y):
"""Return the error delta from the output layer."""
return (a-y) * sigmoid_prime(z)
-
交叉熵代价函数
class CrossEntropyCost(object): # 交叉熵代价函数
@staticmethod
def fn(a, y):
return np.sum(np.nan_to_num(-y*np.log(a)-(1-y)*np.log(1-a)))
# 使用0代替nan 一个较大值代替inf
@staticmethod
def delta(z, a, y):
return (a-y)
- 初始化改进:
def default_weight_initializer(self): # 推荐的权重初始化方式
self.biases = [np.random.randn(y, 1) for y in self.sizes[1:]] # 偏差初始化方式不变
self.weights = [np.random.randn(y, x)/np.sqrt(x) # 将方差减少,避免饱和
for x, y in zip(self.sizes[:-1], self.sizes[1:])]