【深度学习】神经网络的学习
源代码文件请点击此处!
常用损失函数
均方误差函数(mean squared error)
其中 \(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)
其中 \(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)
这是我们最熟悉的形式:
后向差分(backward difference)
这是另外一种形式,也很常见:
中心差分(central difference)
实际情况下,我们不会使用前向差分和后向差分,这是因为这两种方法计算出来的误差值较大,因此采用改进后的中心差分,如下式:
代码实现如下:
# 数值微分(中心差分方法)
# 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)\),权重矩阵为:
则计算梯度值后得到的梯度矩阵为:
实现代码如下:
# 计算损失/误差值(回调函数)
# 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)
)对测试集进行推理,此处就不再赘述了。
基于数值微分实现的梯度下降算法虽然容易实现,但运算速度非常慢。下一篇将介绍一种更为快速的方法——误差反向传播法。