在前面文章中,我们已经系统了解了神经网络的部分概念,以及如何去搭建一个简单的神经网络模型。这篇文章我将主要讲解损失函数、反向传播等神经网络知识。
1. 损失函数
之前我们已经提到,损失函数就是用来计算我们的结果与真实值之间的误差,从而指导进一步的训练向正确的方向进行。
损失函数可以分为两大类:回归、分类。
1.1 回归损失
回归类的损失函数主要有MAE和MSE等。
1.1.1 MAE(Mean Absolute Error)
MAE为平均绝对误差,简单说就是计算输出值与真实值之间的误差绝对值大小的平均值。MAE是一种线性分数,所有个体差异在平均值上的权重都相等,比如,10和0之间的绝对误差是5和0之间绝对误差的两倍:
$$MAE(X,h)=\frac{1} {m}\sum_{i=1}^m | h(x_i)-y_i) |$$
- 在高纬任务中表现比较好
- 预测速度快
- 对outliers(异常值)不敏感
1.1.2 MSE(Mean Square Error)
MSE为均方误差:
$$MSE(X,h)=\frac{1} {m}\sum_{i=1}^m (h(x_i)-y_i))^2$$
- 比绝对误差函数得到的结果更精准
- 对大的误差输出更敏感
- 对outliers很敏感
1.2 分类损失
1.2.1 Cross-Entropy Loss
交叉熵损失,常在分类问题中使用,随着预测概率偏离实际标签,交叉熵会逐渐增加:
$$H(y,f)=-\sum_{i=1}^m y_i log(f(x_i))$$
- 交叉熵能够衡量同一个随机变量中的两个不同概率分布的差异程度,在机器学习中就表示为真实概率分布与预测概率分布之间的差异。
交叉熵经常搭配softmax使用,将输出的结果进行处理,使其多个分类的预测值和为1,再通过交叉熵来计算损失。
损失函数已经简单介绍完了,我们该如何训练神经网络,使得我们的损失最小呢?我们知道,梯度下降法是机器学习中一种用来“学习”参数的方法,神经网络中则可称为“反向传播”法。
2. 反向传播
前向传递输入信号直至输出产生误差,反向传播误差信息更新权重矩阵。
简单来说就是求偏导以及高数中的链式法则。(这里放个链接:反向传播,此文讲解的非常清楚)
假如我们有这样的一个数据集:
姓名 | 身高 | 体重 | 性别 |
---|---|---|---|
张三 | 170 | 62 | 男 |
陈四 | 175 | 65 | 男 |
王霞 | 160 | 45 | 女 |
李红 | 165 | 50 | 女 |
为了方便处理,对其进行一定的处理:
我们现在的目标就是训练一个神经网络,来预测性别:
代码示例:
import numpy as np
def sigmoid(x):
# activation function
return 1 / (1 + np.exp(-x))
def deriv_sigmoid(x):
# derivative of sigmoid
z = sigmoid(x)
return z*(1 - z)
def mse_loss(y, yhat):
# loss functionon
return ((y - yhat) ** 2).mean()
class my_NN(object):
# set all weights and biases
def __init__(self):
self.w1, self.w2, self.w3, self.w4, self.w5, self.w6 = np.random.randn(6)
self.b1, self.b2, self.b3 = np.random.randn(3)
def train(self, inputs, y_true):
learn_rate = 0.1 # learning rate
epochs = 1000 # number of times to loop through the entire dataset
for epoch in range(epochs):
# feedforward
for x, y in zip(inputs, y_true):
out_h1 = sigmoid(self.w1 * x[0] + self.w2 * x[1] + self.b1)
out_h2 = sigmoid(self.w3 * x[0] + self.w4 * x[1] + self.b2)
out_o1 = self.w5 * out_h1 + self.w6 * out_h2 + self.b3
y_pred = sigmoid(out_o1)
# - - - Naming: d_L_d_w1 represents "partial L / partial w1"
# start calculating the backward error
d_L_d_ypred = -2 * (y_true - y_pred)
# the neuron in the output layer
d_ypred_d_w5 = out_h1 * deriv_sigmoid(out_o1)
d_ypred_d_w6 = out_h2 * deriv_sigmoid(out_o1)
d_ypred_d_b3 = deriv_sigmoid(out_o1)
d_ypred_d_h1 = self.w5 * deriv_sigmoid(out_o1)
d_ypred_d_h2 = self.w6 * deriv_sigmoid(out_o1)
# the one neuron in the hidden layer
d_h1_d_w1 = x[0] * deriv_sigmoid(out_h1)
d_h1_d_w2 = x[1] * deriv_sigmoid(out_h1)
d_h1_d_b1 = deriv_sigmoid(out_h1)
# another one
d_h2_d_w3 = x[0] * deriv_sigmoid(out_h2)
d_h2_d_w4 = x[0] * deriv_sigmoid(out_h2)
d_h2_d_b2 = deriv_sigmoid(out_h2)
# - - - update weights and biases
# the neuron in the output layer
self.w5 -= learn_rate * d_L_d_ypred * d_ypred_d_w5
self.w6 -= learn_rate * d_L_d_ypred * d_ypred_d_w6
self.b3 -= learn_rate * d_L_d_ypred * d_ypred_d_b3
# the one neuron in the hidden layer
self.w1 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w1
self.w2 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_w2
self.b1 -= learn_rate * d_L_d_ypred * d_ypred_d_h1 * d_h1_d_b1
# another one
self.w3 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_w3
self.w4 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_w4
self.b2 -= learn_rate * d_L_d_ypred * d_ypred_d_h2 * d_h2_d_b2
if (epoch+1)% 100 == 0:
loss = mse_loss(y_true, y_pred)
print("第%d次,loss为 %.5f" %(epoch+1, loss))
x = np.array([[15, 22],
[20, 25],
[5, 5],
[10, 10],])
y = np.array([1,
1,
0,
0,
])
mynn=my_NN()
mynn.train(x, y)
训练结果为:
第100次,loss为 0.00252
第200次,loss为 0.00119
第300次,loss为 0.00077
第400次,loss为 0.00057
第500次,loss为 0.00045
第600次,loss为 0.00038
第700次,loss为 0.00032
第800次,loss为 0.00028
第900次,loss为 0.00025
第1000次,loss为 0.00022
小结:梯度下降法是general的优化算法,反向传播法是其在神经网络上的具体实现方式。(开始以为它们两个都是相互独立的算法...)