激活函数
g′(z)=g(z)(1−g(z)) 
tanh:g(z)=tanh(z)=a=ez+e−zez−e−z, g′(z)=(1−g2(z)) 
ReLU: g(z)=a=max(0,z) 
leaky ReLU: a=max(0.01z,z) 
损失函数
常见的损失函数有均方误差和交叉熵误差
均方误差(MSE)
公式:E=21∑k(yk−tk)2,其中 yk 表示神经网络的输出,tk 表示监督数据,k 表示数据的维数。
python 实现:
1 2
| def mean_squared_error(y, t): return 0.5 * np.sum((y-t)**2)
|
交叉熵误差
公式:E=−∑ktklogyk
python 实现:
1 2 3
| def cross_entropy_error(y, t): delta = 1e-7 return -np.sum(t * np.log(y + delta))
|
这里,参数 y 和 t 是 NumPy 数组。函数内部在计算 np.log 时,加上了一个微小值 delta。这是因为,当出现 np.log(0)时,np.log(0)会变为负无限大的-inf,这样一来就会导致后续计算无法进行。作为保护性对策,添加一个微小值可以防止负无限大的发生。
one-hot 编码
将正确解标签表示为 1,其他标签表示为 0 的表示方法称为 one-hot 编码
mini-batch
神经网络的学习也是从训练数据中选出一批数据,然后对每个 mini-batch 进行学习。比如,从 60000 个训练数据中随机选择 100 笔,再用这 100 笔数据进行学习。这种学习方式称为 mini-batch 学习,同时,mini-batch 的损失函数也是利用 一部分样本数据来近似地计算整体
mini-batch 版交叉熵误差的实现
1 2 3 4 5 6
| def cross_entropy_error(y, t): if y.ndim == 1: t = t.reshape(1, t.size) y = y.reshape(1, y.size) batch_size = y.shape[0] return -np.sum(t * np.log(y + 1e-7)) / batch_size
|
当监督数据是标签形式(非 one-hot 表示,而是像“2”“ 7”这样的标签)时,交叉熵误差可通过如下代码实现。
1 2 3 4 5 6
| def cross_entropy_error(y, t): if y.ndim == 1: t = t.reshape(1, t.size) y = y.reshape(1, y.size) batch_size = y.shape[0] return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size
|
实现的要点是,由于 one-hot 表示中 t 为 0 的元素的交叉熵误差也为 0,因 此针对这些元素的计算可以忽略。换言之,如果可以获得神经网络在正确解标签处的输出,就可以计算交叉熵误差。因此,t 为 one-hot 表示时通过 t * np.log(y) 计算的地方,在 t 为标签形式时,可用 np.log( y [np.arange (batch_size), t] )实现相同的处理
梯度法
通过使用梯度来寻找函数最小值(或者尽可能小的值)的方法,即通过不断地沿梯度方向前进, 逐渐减小函数值的过程,常见的参数更新公式 x0=x0−η∂x0∂f、x1=x1−η∂x1∂f 就来自于此
通过梯度下降法更新参数,并且当使用的数据是随机选择的 mini batch 数据时,称该方法为随机梯度下降法(SGD)
神经网络的学习过程
前提
神经网络存在合适的权重和偏置,调整权重和偏置以便拟合训练数据的过程称为“学习”。神经网络的学习分成下面 4 个步骤。
步骤 1(mini-batch)
从训练数据中随机选出一部分数据,这部分数据称为 mini-batch。我们的目标是减小 mini-batch 的损失函数的值。
步骤 2(计算梯度)
为了减小 mini-batch 的损失函数的值,需要求出各个权重参数的梯度。 梯度表示损失函数的值减小最多的方向。
步骤 3(更新参数)
将权重参数沿梯度方向进行微小更新。
步骤 4(重复)
重复步骤 1、步骤 2、步骤 3。
Logistic Regression cost function
y^=σ(wTx+b),where σ(z)=1+e−z1
Loss function: 通常采用 L(y^,y)=21(y^−y)2 作为 loss function,在逻辑回归中不这么做,因为其对梯度下降法很可能找不到全局最优值
我们采用的是对数损失函数 L(y^,y)=−(ylogy^+(1−y)log(1−y^)),它是一个凸函数
Cost function: J(w,b)=m1∑i=1mL(y^(i),y(i))
层的思想在神经网络中的应用
乘法层:正向传播计算乘积。反向传播会将上游传来的导数乘以“翻转值”(即正向传播时的另一个输入值)。需要保存正向传播时的输入变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class MulLayer: def __init__(self): self.x = None self.y = None
def forward(self, x, y): self.x = x self.y = y return x * y
def backward(self, dout): dx = dout * self.y dy = dout * self.x return dx, dy
|
加法层:正向传播计算和。反向传播将上游导数原封不动地传递给下游(乘以 1)。不需要保存输入变量。
1 2 3 4 5 6 7 8
| class AddLayer: def forward(self, x, y): return x + y
def backward(self, dout): dx = dout * 1 dy = dout * 1 return dx, dy
|
激活函数层:
ReLU:
1 2 3 4 5 6 7 8 9 10 11 12
| class Relu: def __init__(self): self.mask = None def forward(self, x): self.mask = (x <= 0) out = x.copy() out[self.mask] = 0 return out def backward(self, dout): dout[self.mask] = 0 dx = dout return dx
|
Sigmoid:
1 2 3 4 5 6 7 8 9 10 11 12
| class Sigmoid: def __init__(self): self.out = None
def forward(self, x): out = 1 / (1 + np.exp(-x)) self.out = out return out
def backward(self, dout): dx = dout * (1.0 - self.out) * self.out return dx
|
Affine:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Affine: def __init__(self, W, b): self.W = W self.b = b self.x = None self.dW = None self.db = None
def forward(self, x): self.x = x out = np.dot(x, self.W) + self.b return out
def backward(self, dout): dx = np.dot(dout, self.W.T) self.dW = np.dot(self.x.T, dout) self.db = np.sum(dout, axis=0) return dx
|
Softmax-with-Loss:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class SoftmaxWithLoss: def __init__(self): self.loss = None self.y = None self.t = None def forward(self, x, t): self.t = t self.y = softmax(x) self.loss = cross_entropy_error(self.y, self.t) return self.loss def backward(self, dout=1): batch_size = self.t.shape[0] dx = (self.y - self.t) / batch_size return dx
|
应用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| import sys, os sys.path.append(os.pardir) import numpy as np from common.layers import * from common.gradient import numerical_gradient from collections import OrderedDict class TwoLayerNet: def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01): 初始化权重 self.params = {} self.params['W1'] = weight_init_std * \ np.random.randn(input_size, hidden_size) self.params['b1'] = np.zeros(hidden_size) self.params['W2'] = weight_init_std * \ np.random.randn(hidden_size, output_size) self.params['b2'] = np.zeros(output_size) 生成层 self.layers = OrderedDict() self.layers['Affine1'] = \ Affine(self.params['W1'], self.params['b1']) self.layers['Relu1'] = Relu() self.layers['Affine2'] = \ Affine(self.params['W2'], self.params['b2']) self.lastLayer = SoftmaxWithLoss() def predict(self, x): for layer in self.layers.values(): x = layer.forward(x) return x def loss(self, x, t): y = self.predict(x) return self.lastLayer.forward(y, t) def accuracy(self, x, t): y = self.predict(x) y = np.argmax(y, axis=1) if t.ndim != 1 : t = np.argmax(t, axis=1) accuracy = np.sum(y == t) / float(x.shape[0]) return accuracy def numerical_gradient(self, x, t): loss_W = lambda W: self.loss(x, t) grads = {} grads['W1'] = numerical_gradient(loss_W, self.params['W1']) grads['b1'] = numerical_gradient(loss_W, self.params['b1']) grads['W2'] = numerical_gradient(loss_W, self.params['W2']) grads['b2'] = numerical_gradient(loss_W, self.params['b2']) return grads def gradient(self, x, t): self.loss(x, t) dout = 1 dout = self.lastLayer.backward(dout) layers = list(self.layers.values()) layers.reverse() for layer in layers: dout = layer.backward(dout) grads = {} grads['W1'] = self.layers['Affine1'].dW grads['b1'] = self.layers['Affine1'].db grads['W2'] = self.layers['Affine2'].dW grads['b2'] = self.layers['Affine2'].db return grads
|
与学习相关的技巧
参数的更新
神经网络学习的目的是找到使损失函数的值尽可能小的参数。这是寻找最优参数的问题,解决这个问题的过程称为最优化
SGD
使用参数的梯度,沿梯度方向更新参数,重复这个步骤多次,从而靠近最优参数,这个过程称为随机梯度下降法(SGD)
W←W−η∂W∂L
1 2 3 4 5 6
| class SGD: def __init__(self, lr=0.01): self.lr = lr def update(self, params, grads): for key in params.keys(): params[key] -= self.lr * grads[key]
|
它的缺点是呈“之”字形朝最小值(0,0)移动,效率低
Momentum
更新过程原理类似于物理中的动量,它的更新路径就像小球在碗中滚动。这里新出现了一个变量v,对应物理上的速度。
v←αv−η∂W∂L
W←W+v
AdaGrad
在有关学习率的技巧中,有一种称为学习率衰减的方法,即随着学习的进行,学习率逐渐减小。这个想法相当于“全体”参数的学习率值一起降低。AdaGrad进一步发展了这个想法,针对一个一个的参数,赋予其定制的值
h←h+∂W∂L⊙∂W∂L
W←W−ηh1∂W∂L
这里出现了新变量h,它保存了以前的所有梯度值的平方和(⊙为对应矩阵元素的乘法)。然后在更新参数时通过乘h1,就可以调整学习率的尺度,这意味着参数的元素中变动较大的元素学习率将变小
AdaGrad会记录所有梯度的平方和,因此学习越深入更新的幅度就越小,如果无止境地学习,更新量就有可能变为0.为了解决这个问题可以使用RMSProm方法,RMSProp方法并不是将过去所有的梯度一视同仁地相加,而是逐渐地遗忘过去的梯度,在做加法运算时将新梯度的信息更多地反映出来。
Adam
Adam综合了Momentum和AdaGrad两种方法
下图为四种方式在梯度下降时的对比
目前并不存在能在所有问题中都表现良好的方法。这4种方法各有各的特点,都有各自擅长解决的问题和不擅长解决的问题
权重的初始值
1
| w = np.random.randn(node_num, node_num) * 0.01
|
在使用高斯分布随机生成权重时,通常会乘一个小值,是为了让各层激活值分布均匀。下图为使用标准差为1的高斯分布作为权重初始值时的各层激活值的分布

当数据分布具有偏向性时,如果使用了S型函数,会出现梯度消失的情况。为此我们通常使用标准差为一个小值的高斯分布
这次不会发生梯度消失的问题。但是,激活值的分布有所偏向,说明在表现力上会有很大问题。比如,如果100个神经元都输出几乎相同的值,那么也可以由1个神经元来表达基本相同的事情
综上,我们需要一个合理的初始值。
Xavier初始值
Xavier初始值:与前一层有n个节点连接时,初始值使用标准差为n1的分布。下图为实现效果
1
| w = np.random.randn(node_num, node_num) / np.sqrt(node_num)
|

后面的层呈稍微歪斜的形状,如果用tanh代替sigmoid,这个问题就会得到解决
He初始值
Xavier初始值是以激活函数是线性函数为前提而推导出来的。因为 sigmoid函数和tanh函数左右对称,且中央附近可以视作线性函数,所以适合使用Xavier初始值。但当激活函数使用ReLU时,一般推荐使用ReLU专用的初始值,即He初始值。
He初始值:与前一层有n个节点连接时,初始值使用标准差为n2的分布。
下为激活函数为ReLU时三种初始值的对比
总结一下,当激活函数使用ReLU时,权重初始值使用He初始值,当激活函数为sigmoid或tanh等S型曲线函数时,初始值使用Xavier初始值。
Batch Normalization
为了使各层拥有适当的广度,尝试“强制性”地调整激活值的分布,Batch Normalization方法是基于这个想法而产生的。Batch Norm的思路是调整各层的激活值分布使其拥有适当的广度
Batch Norm以进行学习时的mini-batch为单位,按mini batch进行正规化。具体而言,就是进行使数据分布的均值为0、方差为1的正规化。
μB←m1i=1∑mxi
σB2←m1i=1∑m(xi−μB)2
x^i←σB2+εxi−μB
式中的ε是一个微小值(比如,10e-7等),它是为了防止出现除以0的情况。该式所做的是将mini-batch的输入数据变换为均值为0、方差为1的数据,通过将这个处理插入到激活函数的前面(或者后面),可以减小数据分布的偏向。接着,Batch Norm层会对正规化后的数据进行缩放和平移的变换
yi←γx^i+β
一开始γ=1,β=0,然后再通过学习调整到合适的值
几乎所有的情况下都是使用Batch Norm时学习进行得更快。它可以推动学习的进行。并对权重初始值变得健壮,不那么依赖初始值
正则化
训练中会出现过拟合的情况,为了尽可能减小这种情况出现的可能性,我们有以下对策
权值衰减
权值衰减:通过在学习的过程中对大的权重进行惩罚,来抑制过拟合。很多过拟合是由于权重参数取值过大才发生的。它通过为损失函数加上L1范数或L2范数(21λW2)来抑制权重变大,以L2范数为例,对于所有权重,权值衰减方法都会为损失函数加上21λW2。因此也要在求权重梯度的计算中,要为之前的误差反向传播法的结果加上正则化项的导数λW
Dropout
如果网络的模型变得很复杂,只用权值衰减就难以应对了。Dropout是一种在学习的过程中随机删除神经元的方法。训练时,随机选出隐藏层的神经元,然后将其删除。被删除的神经元不再进行信号的传递

如图所示。训练时,每传递一次数据,就会随机选择要删除的神经元。然后,测试时,虽然会传递所有的神经元信号,但是对于各个神经元的输出, 要乘上训练时的删除比例后再输出。