G
N
I
D
A
O
L

【深度学习】神经网络的学习

源代码文件请点击此处

常用损失函数

均方误差函数(mean squared error)

\[E(\vec{y},\vec{t}) = \frac{1}{2m} \sum_{i=1}^{m} (y_i - t_i)^2 \]

其中 \(y_i\) 为计算得出的概率,\(t_i\) 为以 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(\vec{y},\vec{t}) = - \sum_{i=1}^{m} t_i \ln y_i \]

其中 \(y_i\) 为计算得出的概率,\(t_i\) 为以 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)

这是我们最熟悉的形式:

\[\frac{\mathrm{d} f(x)}{\mathrm{d} x} = \lim_{h \rightarrow 0} \frac{f(x+h) - f(x)}{h} \]

后向差分(backward difference)

这是另外一种形式,也很常见:

\[\frac{\mathrm{d} f(x)}{\mathrm{d} x} = \lim_{h \rightarrow 0} \frac{f(x) - f(x-h)}{h} \]

中心差分(central difference)

实际情况下,我们不会使用前向差分和后向差分,这是因为这两种方法计算出来的误差值较大,因此采用改进后的中心差分,如下式:

\[\frac{\mathrm{d} f(x)}{\mathrm{d} x} = \lim_{h \rightarrow 0} \frac{f(x+h) - f(x-h)}{2h} \]

代码实现如下:

# 数值微分(中心差分方法)
# f 为以上损失函数二选一
def _numerical_gradient(f, x):
    h = 1e-4 # 0.0001
    grad = np.zeros_like(x)

    for i in range(x.size):
        tmp = x[i]

        # 计算 f(x+h)
        x[i] = float(tmp) + h
        fxh1 = f(x)

        # 计算 f(x-h)
        x[i] = float(tmp) - h
        fxh2 = 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 1
  A2 = self.dense(A1, W2, B2, softmax_function_trick) # layer 2
  return A2

step 2-2. 使用数值微分方法进行梯度下降

推理完毕后得到结果 y,然后对损失函数 loss(y, t) 计算关于权重的梯度值。这里所做的操作是:假设损失函数为 \(L(y,t)\),权重矩阵为:

\[W = \bigg[ \begin{matrix} w_{11} \ w_{12} \ w_{13} \\ w_{21} \ w_{22} \ w_{23} \\ \end{matrix} \bigg] \]

则计算梯度值后得到的梯度矩阵为:

\[\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] \]

实现代码如下:

# 计算损失/误差值(回调函数)
# 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))对测试集进行推理,此处就不再赘述了。

基于数值微分实现的梯度下降算法虽然容易实现,但运算速度非常慢。下一篇将介绍一种更为快速的方法——误差反向传播法。

posted @ 2024-02-27 10:45  漫舞八月(Mount256)  阅读(55)  评论(1编辑  收藏  举报