16 分钟
神经网络与深度学习笔记
http://neuralnetworksanddeeplearning.com python3描述,针对问题MNIST 数字分类
一、基本的神经网络
1、神经网络基本概念
(1)神经元
概念上
神经网络的基本单元,神经网络网络由神经元连接而来。
数学上
基本运算单元,包含多个输入和1个输出
s型神经元运算公式如下:
$$
a = \sigma(WX + b) \
\sigma(z) = \frac{1}{1+e^{-z}} \
z = WX + b
$$
符号说明:
a
代表输出- \(\sigma\)一种激活函数
W
权重b
偏置z
带权输入
(2)层
- 输入层:代表数据的层,没有权重和偏置
- 输出层:最后一层,输出的结果为最终结果,有权重和偏置参数
- 隐藏层:输入输出中间的计算层,有权重和偏置参数
(3)向量化计算
假设全连接型神经元形状为[3,4,3]
:
权重和偏置参数矩阵W、b
k:0 1 2
┌ 0 ┌ [ , , ] ┐ ┌ [ , , , ] ┐ ┐
W | j:1 | [ , , ] | | [ , , , ] | |
| 2 | [ , , ] | └ [ , , , ] ┘ |
└ 3 └ [ , , ] ┘, ┘
l: 0 1 2
┌ 0 ┌ ┐ ┌ ┐ ┐
b | j:1 | | | | |
| 2 | | └ ┘ |
└ 3 └ ┘, ┘
符号描述:
- l-层数,
- j-神经元,
- k-上层神经元的输出
数学表述
$$
a^0_j = x_j \
a^l_j = \sigma( (\sum_{k} a^{l-1}_k*W^l_{jk}) + b )
$$
2、神经网络的程序的简单设计
(1)基本要求
- 对于新的输入可以得出计算结果
- 可以进行训练
(2)程序结构
初始化神经网络: 指定层数和结构,初始化权重和偏置矩阵 神经网络计算 给定一个输入,得到网络输出结果 训练网络 给定多组输入和正确标签,使神经网络输出正确性提高 测试网络 给定测试数据,给出神经网络的性能评价
类与函数,调用关系说明:
- 全局函数
sigmoid(z)
:神经元的激活函数,参数z
为神经元的带权输入sigmoid_prime(z)
:激活函数的导数,参数z
为神经元的带权输入
- 类
Network
:神经网络的封装__init__(self,sizes)
:构造函数,size
为一个list,表示各层的神经元个数feedforward(self,a)
:给一个输入a
,计算网络的输出SGD(self, training_data, epochs, mini_batch_size, eta, test_data=None)
:神经网络训练——梯度下降算法update_mini_batch(self, mini_batch, eta)
:下批次随机下降backprop(self, x, y)
:方向传播算法cost_derivative(self, output_activations, y)
:计算\(\frac{\partial C}{\partial a^L_j}\)
evaluate(self, test_data)
:评估模型
3、获取神经网络的输出
(1)初始化神经网络
class Network(object):
def __init__(self,sizes):
"""
列表 sizes 包含各层神经元的数量。
例如,如果我们想创建⼀个在第⼀层有2个神经元,
第⼆层有3个神经元,最后层有1个神经元的 Network 对象,
我们应这样写代码:net = Network([2, 3, 1])
"""
self.num_layers = len(sizes) #成员变量,代表神经网络的层数
self.sizes = sizes #成员变量,代表神经网络的尺寸
self.biases = [np.random.randn(y,1) for y in sizes[1:]] #偏置
#print(self.biases)
self.weights = [np.random.randn(y,x) #权重
for x,y in zip(sizes[:-1],sizes[1:])]
#print(self.weights)
(3)获取神经网络的输出
def feedforward(self,a):
"""向前传播,给定输入a,计算网络的输出,并返回"""
for b, w in zip(self.biases, self.weights):
a = sigmoid(np.dot(w,a)+b)
return a
4、训练神经网络反向传播
(1)代价函数
从整体上看: 是一个关于神经网络输出和期望输出的函数,这个函数要能描述出,模型在训练数据上的性能,关系是:模型准确性越高代价函数值越低,反之…。 从训练模型上看: 是一个关于权重和偏置的函数,在训练过程中,要改变权重和偏置使代价函数值变小
一个很容易构造的代价函数:二次代价函数 $$ \begin{eqnarray} C(w,b) \equiv \frac{1}{2n} \sum_x | y(x) - a|^2 \end{eqnarray} $$
(2)梯度下降
训练的实质是:
改变权重和偏置使代价函数值变小
=>
假设代价函数减小的值为 \(\Delta C\),根据导数基本公式\(f’(x)=\frac{f(x+\Delta x) - f(x)}{\Delta x}\) 得:
$$
\Delta C =\Delta W \frac{\partial C}{\partial W} + \Delta b \frac{\partial C}{\partial b}
$$
=>
更改权重和偏置的值就可以使代价函数的值减小
$$
\begin{eqnarray}
w_k & \rightarrow & w_k’ = w_k-\eta \frac{\partial C}{\partial w_k} \
b_l & \rightarrow & b_l’ = b_l-\eta \frac{\partial C}{\partial b_l}
\end{eqnarray}
$$
- 其中\(\eta\)称之为学习率,在训练模型时根据情况指定
进一步简化:
$$
\begin{eqnarray}
\nabla C \equiv \left(\frac{\partial C}{\partial v_1}, \ldots,
\frac{\partial C}{\partial v_m}\right)^T \
v \rightarrow v’ = v-\eta \nabla C
\end{eqnarray}
$$
- \(\nabla C\) 称之为梯度
- \(v\)泛指函数的因变量
随机梯度下降
使用条件: 代价函数可以写成这种形式:\(C = \frac{1}{n} \sum_x C_x\) 这样可以使用每次选取一小批次\(X_j\)计算梯度
$$
\begin{eqnarray}
w_k & \rightarrow & w_k’ = w_k-\frac{\eta}{m}
\sum_j \frac{\partial C_{X_j}}{\partial w_k} \
b_l & \rightarrow & b_l’ = b_l-\frac{\eta}{m}
\sum_j \frac{\partial C_{X_j}}{\partial b_l},
\end{eqnarray}
$$
更进一步的在线学习
每次选取一个训练样本
$$
\begin{eqnarray}
w_k & \rightarrow & w_k’ = w_k-\frac{\eta}{n}
\sum_i \frac{\partial C_{X_i}}{\partial w_k} \
b_l & \rightarrow & b_l’ = b_l-\frac{\eta}{n}
\sum_i \frac{\partial C_{X_i}}{\partial b_l},
\end{eqnarray}
$$
编程实现:
def SGD(self, training_data, epochs, mini_batch_size, eta,
test_data=None):
"""使用小批次随机梯度下降训练神经网络。
``training_data``:
是一个(x,y)元组的列表,代表着训练数据和标签
``epochs``:表示迭代次数
``mini_batch_size``:随机批次大小
``eta``:学习率
``test_data``:
是一个(x,y)元组的列表,代表着测试数据和标签,检验模型效果
"""
if test_data: n_test = len(test_data)
n = len(training_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)
]
for mini_batch in mini_batches: #使用每一批次更新模型参数
self.update_mini_batch(mini_batch, eta)
if test_data:
acc = self.evaluate(test_data)
print("步数 %d:%d / %d 正确率%f" % (
j, self.evaluate(test_data), n_test, acc/n_test))
else:
print("步数 %d 已完成" % j)
def update_mini_batch(self, mini_batch, eta):
"""更新神经网络的权重和偏置,运用小批次数据通过使用反向传播的梯度下降算法
``mini_batch``:
是一个(x,y)元组的列表,代表着一个批次的数据和标签
``eta``:学习率
"""
nabla_b = [np.zeros(b.shape) for b in self.biases] #创建全为0的b
nabla_w = [np.zeros(w.shape) for w in self.weights] #创建全为0的w
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 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)
(3)反向传播四个基本公式
梯度下降问题的关键就是求梯度\(\nabla C\)(也就是说关于W和b的偏导数)
反向传播就是解决此问题的快速算法
前提条件
- 代价函数可以写成这个形式\(C = \frac{1}{n} \sum_x C_x\)
- 代价函数可以写成关于神经网络输出的函数\(C = f(a^L)\)
准备公式
$$ \begin{eqnarray} a^{l}_j = \sigma\left( \sum_k w^{l}_{jk} a^{l-1}_k + b^l_j \right) \end{eqnarray} $$
- \(a^{l}_j\) 表示第l层第j个神经元的输出(激活值)
向量化
$$ \begin{eqnarray} a^{l} = \sigma(w^l a^{l-1}+b^l). \end{eqnarray} $$
- 其中\(z^l \equiv w^l a^{l-1}+b^l \)
预先定义
$$ \begin{eqnarray} \delta^l_j \equiv \frac{\partial C}{\partial z^l_j} \end{eqnarray} $$
- 定义 l 层的第 j 个神经元上的误差为: \(\delta^l_j\)
公式1
输出层误差的⽅程
$$ \begin{eqnarray} \delta^L_j = \frac{\partial C}{\partial a^L_j} \sigma’(z^L_j) \end{eqnarray} $$
- \(\delta^L\) 输出层误差
若使用二次代价函数\(C = \frac{1}{2} \sum_j (y_j-a^L_j)^2\),化简后的向量化公式
$$ \begin{eqnarray} \delta^L = (a^L-y) \odot \sigma’(z^L) \end{eqnarray} $$
- 其中 \(\odot\)表示按元素乘积:[1,2]⊙[3,4]=[1∗3,2∗4]=[3,8]
公式2
递推公式
$$ \begin{eqnarray} \delta^l = ((w^{l+1})^T \delta^{l+1}) \odot \sigma’(z^l) \end{eqnarray} $$
公式3
计算关于偏置b的偏导数
$$ \begin{eqnarray} \frac{\partial C}{\partial b^l_j} =\delta^l_j \end{eqnarray} $$
公式4
计算关于权重W的偏导数 $$ \begin{eqnarray} \frac{\partial C}{\partial w^l_{jk}} = a^{l-1}_k \delta^l_j \end{eqnarray} $$
反向传播算法描述(索引从1开始)
- 输⼊ x:计算激活值\(a^1\)
- 前向传播:对每个 l = 2, 3, …, L 计算\(z^{l} = w^l a^{l-1}+b^l\)和\(a^{l} = \sigma(z^{l})\)
- 输出层误差\(\delta^{L}\):计算向量\(\delta^{L} = \nabla_a C \odot \sigma’(z^L)\)
- 反向误差传播:对每个 l = L − 1, L − 2, …, 2,计算\(\delta^{l} = ((w^{l+1})^T \delta^{l+1}) \odot \sigma’(z^{l})\)
- 输出:代价函数的梯度由\(\frac{\partial C}{\partial w^l_{jk}} = a^{l-1}_k \delta^l_j\)和\(\frac{\partial C}{\partial b^l_j} = \delta^l_j\)得出
编程实现
def sigmoid_prime(z): #全局函数
"""s型函数的导数"""
return sigmoid(z)*(1-sigmoid(z))
def backprop(self, x, y):
"""返回一个元组``(nabla_b, nabla_w)``代表C_x的梯度
``nabla_b`` 和 ``nabla_w``类型类似于
``self.biases`` 和 ``self.weights``"""
nabla_b = [np.zeros(b.shape) for b in self.biases] #拷贝b和w的形状
nabla_w = [np.zeros(w.shape) for w in self.weights]
# 向前传播
activation = x # 初始化激活值
activations = [x] # 储存每一层的激活值的列表
zs = [] # 储存每一层的z向量的列表
for b, w in zip(self.biases, self.weights):
z = np.dot(w, activation)+b # 计算当前层的z向量
zs.append(z)
activation = sigmoid(z) # 计算激活值
activations.append(activation)
# 反向传播
delta = self.cost_derivative(activations[-1], y) * \
sigmoid_prime(zs[-1]) # 计算delta^L
nabla_b[-1] = delta #b的偏导数 = delta
nabla_w[-1] = np.dot(delta, activations[-2].transpose())
# 注意在这里,l=1表示最后一层,l=2表示倒数第2层以此类推。
for l in range(2, self.num_layers):
z = zs[-l] #从后往前数,得到z
sp = sigmoid_prime(z) #计算s型函数的导数
delta = np.dot(self.weights[-l+1].transpose(), delta) * sp #计算出delta
nabla_b[-l] = delta
nabla_w[-l] = np.dot(delta, activations[-l-1].transpose())
return (nabla_b, nabla_w)
def cost_derivative(self, output_activations, y):
"""返回C_x的偏导数值"""
return (output_activations-y)
二、改进神经网络
1、使用交叉熵代价函数
(1)二次代价函数的问题
按照直观启发,当网络性能极差时,学习速度应该很快,但是二次代价函数在网络性能极差时学习速度可能很慢
(2)交叉熵代价函数
$$ \begin{eqnarray} C = -\frac{1}{n} \sum_x \left[y \ln a + (1-y ) \ln (1-a) \right] \end{eqnarray} $$
(3)优点
- 学习速度符合人的直观感受
- 在计算最后一层的误差时效率更高约掉了\(\sigma’(z)\)项
反向传播公式1化简形式 根据\(\sigma’(z) = \sigma(z)(1-\sigma(z))\)和\(a=\sigma(z)\)化简公式1 $$ \delta^L_j = a^{l}_j - y_j $$
(4)优化代码
程序结构修改:
- 将代价函数相关的代码抽象成一个类,作为
Network
的构造函数的参数传入 修改
backprop
方法的代码,将计算输出层的误差改为调用(self.cost).delta
函数#### 定义二次和交叉熵代价函数对象 class QuadraticCost(object): '''二次代价函数''' @staticmethod def fn(a, y): """返回代价函数值,用于观测网络 ``a`` :神经网络的输出 ``y`` :数据精确值 np.linalg.norm求向量的模,也就是就是长度|v| = sqrt(x1^2 + x2^2 + ... + xn^2) """ return 0.5*np.linalg.norm(a-y)**2 @staticmethod def delta(z, a, y): """返回输出层的误差,公式为:代价函数关于输出层神经元输出的偏导数 * 激活函数关于带权输入的导数""" return (a-y) * sigmoid_prime(z) class CrossEntropyCost(object): '''交叉熵函数''' @staticmethod def fn(a, y): """返回代价函数值,用于观测网络 ``a`` :神经网络的输出 ``y`` :数据精确值 np.nan_to_num表示,使用近似数字代替inf """ return np.sum(np.nan_to_num(-y*np.log(a)-(1-y)*np.log(1-a))) @staticmethod def delta(z, a, y): """返回输出层的误差,公式为:代价函数关于输出层神经元输出的偏导数 * 激活函数关于带权输入的导数""" return (a-y) def __init__(self, sizes, cost=CrossEntropyCost): #....略 self.cost=cost def backprop(self, x, y): #...略 # 反向传播 # delta = self.cost_derivative(activations[-1], y) * \ # sigmoid_prime(zs[-1]) # 计算delta^L delta = (self.cost).delta(zs[-1],activations[-1], y)
2、柔性最⼤值(softmax)
(1)定义
softmax函数类似于\(\sigma(z) = \frac{1}{1+e^{-z}}\)的作用,一般用于输出层,定义如下 $$ \begin{eqnarray} a^L_j = \frac{e^{z^L_j}}{\sum_k e^{z^L_k}} \end{eqnarray} $$
说明:
- \(a^L_j\) 表示:第L层第j个神经元的输出
- \(z^L_j = \sum_{k} w^L_{jk} a^{L-1}_k + b^L_j\) 表示:第L层第j个神经元的带权输入
- \(z^L_k\) 表示:分⺟中的求和是在所有的输出神经元上进⾏的,表示第L层神经元的第k个神经元的带权输入
(2)特性
- 加入柔性最大值的神经网络层,的各个神经元输出和=1
- 某个神经元的带权输入增加,他的输出也会增加,其他神经元的输出将减少
- 可以解决使用s型函数学习缓慢问题
- 柔性最⼤值层的输出可以被看做是⼀个概率分布
- 柔性最⼤值的⾮局部性,:任何特定的输出激活值依赖所有的带权输⼊
(3)使用柔性最大值的有对数似然代价函数
$$ \begin{eqnarray} C = -\ln a^L_y \end{eqnarray} $$ 理解:y为标签为1的对应神经元的编号
(4)使用柔性最大值的反向传播
$$ \begin{eqnarray} \delta^L_j = a^L_j -y_j \end{eqnarray} $$
(5)如何选择输出层和代价函数
几种方案:
- 交叉熵代价函数 + S 型输出层
- 对数似然代价函数 + 柔性最⼤值输出层
以上方案效果都很好
(6)编程实现
目前未实现,目前使用交叉熵代价函数 + S 型输出层的组合
3、过拟合和规范化
(1)过拟合的一些特性
- 在训练数据上,代价函数值随迭代进行一直下降
- 在测试数据上,分类准确率没有持续上升,在迭代中后期停止增长,一直波动
- 在测试数据上,代价函数值没有持续下降,在迭代中后期开始上升,图像表现为
√
形 - 在训练数据上,分类准确率持续上升,接近100%
(2)过拟合产生的原因
过度拟合是神经⽹络的⼀个主要问题。这在现代⽹络中特别正常,因为⽹络权重和偏置数量巨⼤,导致模型的泛化能力很差
(3)检测过拟合
跟踪测试数据集合上的准确率随训练变化情况,如果我们看到测试数据上的准确率不再提升,那么我们就停⽌训练。
提前停止
使用validation_data验证数据集而不是test_data测试数据集,计算分类准确率不再提升,那么我们就停⽌训练。
validation_data的作用
衡量不同的超参数(如迭代期,学习速率,最好的⽹络架构等等)的选择的效果
test_data的作用
纯粹测试模型的性能
增大训练数据规模可以抑制出现过拟合
(4)规范化(正规化)
最常见的规范化——权重衰减(weight decay)或者L2 规范化
L2 规范化的想法是增加⼀个额外的项到代价函数上,这个项叫做规范化项。下⾯是规范化的交叉熵:
$$ \begin{eqnarray} C = -\frac{1}{n} \sum_{xj} \left[ y_j \ln a^L_j+(1-y_j) \ln (1-a^L_j)\right] + \frac{\lambda}{2n} \sum_w w^2 \end{eqnarray} $$
- 第⼀个项就是常规的交叉熵的表达式
- 第⼆个现在加⼊的就是所有权重的平⽅的和
- λ/2n进行量化调整
- λ > 0 可以称为规范化参数
- n 就是训练集合的大小
⼆次代价函数。类似的规范化的形式如下:
$$ \begin{eqnarray} C = \frac{1}{2n} \sum_x |y-a^L|^2 + \frac{\lambda}{2n} \sum_w w^2 \end{eqnarray} $$ 两者都可以写成这样: $$ \begin{eqnarray} C = C_0 + \frac{\lambda}{2n} \sum_w w^2 \end{eqnarray} $$
(5)使用规范化的梯度下降
对于偏置b $$ \begin{eqnarray} b & \rightarrow & b -\eta \frac{\partial C_0}{\partial b} \end{eqnarray} $$
对于权重W
$$
\begin{eqnarray}
w & \rightarrow & w-\eta \frac{\partial C_0}{\partial
w}-\frac{\eta \lambda}{n} w \
& = & \left(1-\frac{\eta \lambda}{n}\right) w -\eta \frac{\partial
C_0}{\partial w}.
\end{eqnarray}
$$
(6)合适的规范化作用
- 防止出现过拟合,提高模型的泛化程度,以至于提高模型的性能
(7)优化代码
程序结构修改:
- 修改梯度下降方法SGD:
- 方法参数
- 添加
lmbda
参数 - 将
test_data
改为evaluation_data
- 添加相关监视器方法开关,监视在每个训练迭代的验证集代价函数值、精确度和训练集代价函数值、精确度和
- 添加
- 方法逻辑
- 添加正规化项
- 添加监视器相关代码
- 方法参数
- 修改方法update_mini_batch,添加
lmbda
和n
关于正规化的参数和逻辑 - 在Network添加方法:
accuracy(self, data, convert=False)
:计算针对数据data模型输出的正确的数目total_cost(self, data, lmbda, convert=False)
:计算针对数据data模型的代价函数值
- 添加全局函数:
vectorized_result(j,c)
向量化标签
删除Network方法:
evaluate(self, test_data)
该方法由新添加的accuracy(self, data, convert=False)
代替def vectorized_result(j,c): """将标量转换为向量表示 ``j``:表示标量值 ``c``:表示分类的数目,分成c个不同的类别 """ e = np.zeros((c, 1)) e[j] = 1.0 return e def SGD(self, training_data, epochs, mini_batch_size, eta, lmbda = 0.0, evaluation_data=None, monitor_evaluation_cost=False, monitor_evaluation_accuracy=False, monitor_training_cost=False, monitor_training_accuracy=False): """使用小批次随机梯度下降训练神经网络。 ``training_data``: 是一个(x,y)元组的列表,代表着训练数据和标签 ``epochs``:表示迭代次数 ``mini_batch_size``:随机批次大小 ``eta``:学习率 ``lmbda``:正规化参数 ``evaluation_data``: 是一个(x,y)元组的列表,代表着验证集的数据和标签,检验模型效果 ``monitor_evaluation_cost``:是否监视验证集代价函数变化 ``monitor_evaluation_accuracy``:是否监视验证集准确率变化 ``monitor_training_cost``:是否监视训练集代价函数变化 ``monitor_training_accuracy``:是否监视验证集准确率变化 """ if evaluation_data: n_data = len(evaluation_data) n = len(training_data) evaluation_cost, evaluation_accuracy = [], [] training_cost, training_accuracy = [], [] 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) ] for mini_batch in mini_batches: #使用每一批次更新模型参数 self.update_mini_batch( mini_batch, eta, lmbda, len(training_data)) print("步数 %d 已完成" % j) if monitor_training_cost: cost = self.total_cost(training_data, lmbda) training_cost.append(cost) print ("训练集代价函数: %f" % (cost)) if monitor_training_accuracy: accuracy = self.accuracy(training_data, convert=True) training_accuracy.append(accuracy) print ("训练集精确率: %d / %d = %f" % ( accuracy, n, accuracy/n)) if monitor_evaluation_cost: cost = self.total_cost(evaluation_data, lmbda, convert=True) evaluation_cost.append(cost) print ("验证集代价函数: %f" % (cost)) if monitor_evaluation_accuracy: accuracy = self.accuracy(evaluation_data) evaluation_accuracy.append(accuracy) print ("验证集精确率: %d / %d = %f" % ( accuracy, n_data, accuracy/n_data)) def accuracy(self, data, convert=False): """计算针对数据data模型输出的正确的数目 ``data``: 是一个(x,y)元组的列表,代表着一个批次的数据和标签 ``convert``: True表示数据的标签y是向量化的(训练集) False表示数据的标签y是标量为0~m(评估集和测试集) """ if convert: results = [(np.argmax(self.feedforward(x)), np.argmax(y)) for (x, y) in data] else: results = [(np.argmax(self.feedforward(x)), y) for (x, y) in data] return sum(int(x == y) for (x, y) in results) def total_cost(self, data, lmbda, convert=False): """计算针对数据data模型的代价函数值 ``data``: 是一个(x,y)元组的列表,代表着一个批次的数据和标签 ``lmbda``:正规化参数 ``convert``: True表示数据的标签y是向量化的(训练集) False表示数据的标签y是标量为0~m(评估集和测试集) """ cost = 0.0 for x, y in data: a = self.feedforward(x) if convert: y = vectorized_result(y,self.sizes[-1]) cost += self.cost.fn(a, y)/len(data) cost += 0.5*(lmbda/len(data))*sum( np.linalg.norm(w)**2 for w in self.weights) return cost
4、权重初始化
(1)标准正太分布初始化问题
因为输入像素值取值为[0,1]
,若使用标准正太吩咐初始化权重,那么带权输入的分布将是非常宽的正太分布。带入s型激活函数,权重输出将近似于0或1,表示隐藏神经元会饱和,此时改变权重,代价函数将改变很小,造成学习缓慢问题
(2)解决方法
使用均值为0,标准差为\(1 / \sqrt{n_{\rm in}}\)的正太分布
(3)优化代码
程序结构修改:
将权重初始化从构造函数中,提取成为成员函数:
- 在Network中新建权重初始化方法:
default_weight_initializer(self)
:新的权重初始化large_weight_initializer(self)
:原来的权重初始化
删除
__init__(self, sizes, cost=CrossEntropyCost)
中的权重和偏置初始化代码,改为调用default_weight_initializer()
def __init__(self, sizes, cost=CrossEntropyCost): """ 列表 sizes 包含各层神经元的数量。 例如,如果我们想创建⼀个在第⼀层有2个神经元, 第⼆层有3个神经元,最后层有1个神经元的 Network 对象, 我们应这样写代码:net = Network([2, 3, 1]) cost:表示代价函数类型 """ self.num_layers = len(sizes) #成员变量,代表神经网络的层数 self.sizes = sizes #成员变量,代表神经网络的尺寸 self.default_weight_initializer() self.cost=cost def default_weight_initializer(self): """使用均值为0,标准差为1/sqrt(直接输入个数)初始化权重""" 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:])] def large_weight_initializer(self): """使用均值为0标准差为1的随机数初始化权重偏置。 注意,第一层为输入层,按照惯例不会有权重偏差。 此方法,仅用作比较,应由以上方法替代 """ self.biases = [np.random.randn(y, 1) for y in self.sizes[1:]] self.weights = [np.random.randn(y, x) for x, y in zip(self.sizes[:-1], self.sizes[1:])]
- 在Network中新建权重初始化方法:
5、添加神经网络的保存与修改
程序结构修改:
- 导入json、sys库
- 在Network类中添加方法
save(self, filename)
:序列化对象,并保存到文件中
全局方法
load(filename)
:将对象从文件中加载def save(self, filename): """保存神经网络,定制化保存到filename文件中""" data = {"sizes": self.sizes, "weights":[w.tolist() for w in self.weights], "biases":[b.tolist for b in self.biases], "cost":str(self.cost.__name__)} f = open(filename,"w") json.dump(data,f) f.close() #### 加载神经网络 def load(filename): """从filename文件中加载神经网络,返回一个Network实例""" f = open(filename,"r") data = json.load(f) f.close() cost = getattr(sys.modules[__name__],data["cost"]) net = Network(data["sizes"], cost=cost) net.weights = [np.array(w) for w in data["weights"]] net.biases = [np.array(b) for b in data["biases"]] return net
6、如何选择神经⽹络的超参数
(1)如何设置学习速率η(常量)
- 首先选中一个阈值:在训练集上代价函数值立即开始下降,而不是震荡或者增加
- 从η=0.01开始试验
- 如果代价在训练的前面若干回合开始下降,逐步增加η的值,直到开始震荡或者增加
- 如果代价在训练的前面若干回合开始震荡或者增加,逐步减小η的值,直到开始下降
- η 实际值不应该比阈值大,应该比阈值小一些,如阈值一半
(2)使用提前停止确定迭代期数目
如果分类准确度在近 10 个回合都没有提升的时候,我们将其终止。当然也可以选定20,30等
(4)学习速率自动调整
- 设定一个初始值
- 当验证集准确性开始变差时,按照某常量降低学习速率,如10或2
- 直到变为学习率的1/1024或者1/1000,此时中止
可变学习速率虽然可以提升性能,但是也会产生大量可能的选择,开始试验,应使用固定的学习速率
(5)小批量数据大小
略
(6)自动设置超参数技术
通常的技术就是网格搜索(grid search)