文章目录
- 常用损失函数
- 均方误差函数(mean squared error)
- 交叉熵误差函数(cross entropy error)/对数损失函数(log loss function)
- 数值微分(numerical gradient)
- 前向差分(forward difference)
- 后向差分(backward difference)
- 中心差分(central difference)
- 基于梯度下降算法的神经网络学习过程
- step 1. 初始化神经网络参数
- step 2. 神经网络的学习
- step 2-1. 进行推理(前向传播)
- step 2-2. 使用数值微分方法进行梯度下降
- step 2-3. 计算识别精度和损失值
- step 3. 学习完毕,使用神经网络进行预测
源代码文件请点击此处!
常用损失函数
均方误差函数(mean squared error)
E ( y ⃗ , t ⃗ ) = 1 2 m ∑ i = 1 m ( y i − t i ) 2 E(\vec{y},\vec{t}) = \frac{1}{2m} \sum_{i=1}^{m} (y_i - t_i)^2 E(y,t)=2m1i=1∑m(yi−ti)2
其中 y i y_i yi 为计算得出的概率, t i t_i ti 为以 one-hot 表示的监督数据(正确解标签)。
代码实现如下:
# 均方误差函数
# 监督数据 t 为 one-hot 表示
def mean_squared_error(y, t):m = y.shape[1]cost_sum = np.sum((y-t) ** 2)return cost_sum / (2 * m)
交叉熵误差函数(cross entropy error)/对数损失函数(log loss function)
E ( y ⃗ , t ⃗ ) = − ∑ i = 1 m t i ln y i E(\vec{y},\vec{t}) = - \sum_{i=1}^{m} t_i \ln y_i E(y,t)=−i=1∑mtilnyi
其中 y i y_i yi 为计算得出的概率, t i t_i ti 为以 one-hot 表示的监督数据(正确解标签)。
代码实现如下:
# 对数损失函数
# 监督数据 t 为 one-hot 表示
def log_loss_function(y, t):delta = 1e-7 # 此处是防止出现 ln0 的情况return -np.sum(t * np.log(y + delta))
数值微分(numerical gradient)
神经网络的学习依赖于梯度下降算法,因此我们首先介绍一种计算梯度的方法——数值微分法。使用数值微分计算梯度值的速度比较慢,但实现起来简单。与之相对应的是通过公式运算的解析式求解梯度(解析性求导,analytic gradient),该方法运算起来快速,但实现起来较复杂。
计算数值微分的方法有三种:
前向差分(forward difference)
这是我们最熟悉的形式:
d f ( x ) d x = lim h → 0 f ( x + h ) − f ( x ) h \frac{\mathrm{d} f(x)}{\mathrm{d} x} = \lim_{h \rightarrow 0} \frac{f(x+h) - f(x)}{h} dxdf(x)=h→0limhf(x+h)−f(x)
后向差分(backward difference)
这是另外一种形式,也很常见:
d f ( x ) d x = lim h → 0 f ( x ) − f ( x − h ) h \frac{\mathrm{d} f(x)}{\mathrm{d} x} = \lim_{h \rightarrow 0} \frac{f(x) - f(x-h)}{h} dxdf(x)=h→0limhf(x)−f(x−h)
中心差分(central difference)
实际情况下,我们不会使用前向差分和后向差分,这是因为这两种方法计算出来的误差值较大,因此采用改进后的中心差分,如下式:
d f ( x ) d x = lim h → 0 f ( x + h ) − f ( x − h ) 2 h \frac{\mathrm{d} f(x)}{\mathrm{d} x} = \lim_{h \rightarrow 0} \frac{f(x+h) - f(x-h)}{2h} dxdf(x)=h→0lim2hf(x+h)−f(x−h)
代码实现如下:
# 数值微分(中心差分方法)
# f 为以上损失函数二选一
def _numerical_gradient(f, x):h = 1e-4 # 0.0001grad = np.zeros_like(x)for i in range(x.size):tmp = x[i]# 计算 f(x+h)x[i] = float(tmp) + hfxh1 = f(x)# 计算 f(x-h)x[i] = float(tmp) - hfxh2 = f(x)# 计算中心差分grad[i] = (fxh1 - fxh2) / (2 * h)x[i] = tmp#print(grad)return grad
基于梯度下降算法的神经网络学习过程
神经网络的学习实现起来并不复杂,现在以一个 2 层神经网络为例,来看看具体的学习过程。
step 1. 初始化神经网络参数
初始化一个有 2 层(输入层+隐藏层+输出层)的神经网络的代码如下所示。注意,权重的初始值不可设置为 0(或者说不可设置为同样的数字),这是为了防止“权重均一化”问题。因此,必须随机生成初始值。我们可以使用较小的初始值,例如使用标准差为 0.01 的高斯分布作为权重初始值。关于初始值的设定问题以后再来讨论。
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)
step 2. 神经网络的学习
这里分三步走:推理、梯度下降、识别精度。
step 2-1. 进行推理(前向传播)
初始化参数后,先用这些参数进行推理(即神经网络的前向传播):
def predict(self, X):W1, W2 = self.params['W1'], self.params['W2']B1, B2 = self.params['B1'], self.params['B2']A1 = self.dense(X, W1, B1, sigmoid_function) # layer 1A2 = self.dense(A1, W2, B2, softmax_function_trick) # layer 2return A2
step 2-2. 使用数值微分方法进行梯度下降
推理完毕后得到结果 y
,然后对损失函数 loss(y, t)
计算关于权重的梯度值。这里所做的操作是:假设损失函数为 L ( y , t ) L(y,t) L(y,t),权重矩阵为:
W = [ w 11 w 12 w 13 w 21 w 22 w 23 ] W = \bigg[ \begin{matrix} w_{11} \ w_{12} \ w_{13} \\ w_{21} \ w_{22} \ w_{23} \\ \end{matrix} \bigg] W=[w11 w12 w13w21 w22 w23]
则计算梯度值后得到的梯度矩阵为:
∂ L ∂ W = [ ∂ L ∂ w 11 ∂ L ∂ w 12 ∂ L ∂ w 13 ∂ L ∂ w 21 ∂ L ∂ w 22 ∂ L ∂ w 23 ] \frac{\partial L}{\partial W} = \bigg[ \begin{matrix} \frac{\partial L}{\partial w_{11}} \ \frac{\partial L}{\partial w_{12}} \ \frac{\partial L}{\partial w_{13}} \\ \frac{\partial L}{\partial w_{21}} \ \frac{\partial L}{\partial w_{22}} \ \frac{\partial L}{\partial w_{23}} \\ \end{matrix} \bigg] ∂W∂L=[∂w11∂L ∂w12∂L ∂w13∂L∂w21∂L ∂w22∂L ∂w23∂L]
实现代码如下:
# 计算损失/误差值(回调函数)
# loss_f 为损失函数
def loss(self, loss_f, X, t):y = self.predict(X) # 先进行推理return loss_f(y, t) # 后用推理结果与正确解标签计算损失值def numerical_gradient_descent(self, loss_f, X, t):# 定义匿名函数,参数为 W,返回值为 loss_f(y, t)loss_W = lambda W: self.loss(loss_f, 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
计算各个参数的梯度值后,更新各个参数:
for key in ('W1', 'B1', 'W2', 'B2'):self.params[key] -= learning_rate * self.grads[key]
step 2-3. 计算识别精度和损失值
首先,计算识别精度的代码实现如下:
# 神经网络参数的精度评价
def accuracy(self, X, t, num):y = self.predict(X) # 再次进行推理print(y)y = np.argmax(y, axis=1)t = np.argmax(t, axis=1)accuracy = np.sum(y == t) / float(num)return accuracy
每轮梯度下降完成后,计算识别精度和损失值:
# 识别精度
accuracy = self.accuracy(X, t, X.shape[0])
print(f"epoch {epoch}: train accuracy = {accuracy}")# 计算损失值
loss = self.loss(loss_f, X, t)
self.loss_history.append(loss)
随后,重复执行 step 2-1 至 2-3,继续迭代更新各个参数值,直到精度达到要求或达到迭代次数为止,然后进行 step 3。
step 3. 学习完毕,使用神经网络进行预测
使用已学习完毕的神经网络(即调用函数 predict(X)
)对测试集进行推理,此处就不再赘述了。
基于数值微分实现的梯度下降算法虽然容易实现,但运算速度非常慢。下一篇将介绍一种更为快速的方法——误差反向传播法。