深度学习基础:神经网络
1. 目录
- 引言
- 简单问题定义
- 更复杂的问题
- 机器学习的思路
- 神经元
- 数据集
- 损失函数
- 梯度下降算法
- 算法过程
- 为什么梯度下降算法有用?
- 代码实现
2. 引言
随着人工智能技术的发展,在互联网上常听到的卷积神经网络(CNN)、循环神经网络(RNN)、图神经网络(GCN),以及目前最新的GPT,无论是哪一种,都是以神经网络(NN)为基础改进的变种。因此,非常有必要对神经网络这一基础概念进行介绍。
2.1 简单问题定义
本文设计了一个非常简单的问题,然后尝试通过神经网络解决这个问题,希望这个过程中把神经网络的核心概念讲清楚。
问题具体定义如为:给出一个学生成绩信息表,字段分别为学习时间
下面这张表是对某班级的学习时间和学习成绩统计,思考一下,如果输入
ID | ||
---|---|---|
001 | 1 | 2 |
002 | 2 | 4 |
003 | 3 | 6 |
004 | 4 | 8 |
005 | 5 | 10 |
006 | 6 | 12 |
007 | 7 | 14 |
008 | 8 | 16 |
事实上,由于这个数据表较为简单,基本无需思考就能得出规律,并归纳成公式:
2.2 更复杂的问题
但是,如果数据表没有这么简单,还能这么快找到规律吗?
如下图是一个房价数据表[2],有81个字段,好几千条数据。假如现在让你写一个程序,输入为前80个字段的值,输出为房价的预测值,你还能看出数据表中数据的规律,并得出房价和其他80个字段的函数关系吗?恐怕会有些不现实。
以下图为例,如果数据结构更复杂一些,数据表的字段是图像,和图像对应的类型(鱼、飞机、自行车...)。你能找到图片像素与其类型间的规律,并归纳成一个数学公式,以便未来作预测吗?恐怕更加不现实,因为实在是太复杂了。
2.3 机器学习的思路
复杂问题的内在规律也更加复杂,因此我们放弃了传统人手工去总结数据规律的方式,转而利用计算机强大的计算能力,去海量数据中挖掘规律。也可以理解为让计算机去归纳出一个函数,尽可能逼近实际应用问题的函数。挖掘数据规律的算法有很多种,耳熟能详的有随机森林算法、SVR、K最近邻算法、Adaboost、神经网络等。
事实上,目前常说的深度学习就是基于神经网络的技术,广泛应用在各个领域,可以说现在日常看到或听说的人工智能都是指基于神经网络的技术。其他几个算法也有一定的出场率,但相对来说会少一些,对这方面的研究热度也会低一些,一般称他们为传统的机器学习算法。
3. 神经元
神经元是神经网络的基本单元,不同神经元结合可以组成一个复杂的神经网络。现代神经网络,往往结构更加复杂,且神经元数量级巨大。但万变不离其宗,只要对一个基本的神经元进行研究,就可以理解神经网络的最核心的本质。
下面是一个基本神经元的常规定义图,结构非常简单。
其中,上图神经元结构用公式表达为:
代表输入神经元的变量。对于房价预测问题,它可以是房子的面积、颜色、层数等。对于图片分类问题,它可以代表一张图片第 个像素值。 代表权重值,这个值一开始是随机或设定某一常数,通过学习和训练,让机器自动寻找最合适的值。一般也称它为可训练参数(Trainable parameters)。 也是可训练参数,但是它在一个神经元中只有一个。 代表把 和其对应的权重 相乘并累加。 为激活函数,常见的有 、 等。可以先简单理解为 为线性函数,函数为直线形态,用了激活函数后可以让它转弯,拥有更好的表达力。
回到我们一开始提到的成绩预测问题。我们尝试用一个基本神经元去解决这个问题。为了更加简化,我们先把激活函数
其表达式为:
之前提到过,由于数据较为简单,我们猜到了
4. 数据集
先来回忆一下人类的学习模式:不断地做练习题,总结规律,然后参加考试。神经元的学习方式也类似,通过不断地做练习题,总结规律并调整自己的
在机器学习领域,每一道题目叫「样本」,所有练习题被称为「训练集」,而考试的所有题目被称为「测试集」,所有题目放一起被称为「数据集」。
在每一个样本中,作为输入到神经元的字段叫「特征」,期望神经元预测的字段叫「标签」。下面举例:
- 在成绩预测问题中,学习时间
是特征,成绩 是标签。 - 在房价预测问题中,房子面积、位置、层数是特征,房价是标签。
以成绩预测问题为例子,数据表是一个有8个样本的数据集。我们可以把它以3:1比例划分为训练集和测试集。我们取前6条作为训练集,它们分别为:
ID | ||
---|---|---|
001 | 1 | 2 |
002 | 2 | 4 |
003 | 3 | 6 |
004 | 4 | 8 |
005 | 5 | 10 |
006 | 6 | 12 |
ID | ||
---|---|---|
007 | 7 | 14 |
008 | 8 | 16 |
5. 损失函数
有了数据集后,神经元也就有了学习资料。接下去,还有一个问题就是如何评判神经元的表现好坏。我们前面提到过,
神经元表达式 | |
---|---|
6 | |
1 | |
100 |
我们以ID为003的样本为例,将
神经元表达式 | |||
---|---|---|---|
18 | 6 | 12 | |
3 | 6 | 3 | |
300 | 6 | 294 |
其中
通过上表可以看出,第三个神经元误差最大,第二个神经元误差最小,因此可以认为第二个神经元是效果最好的,我们也希望计算机能够调整出误差更小的神经元。
但是,上述都是仅针对ID为003的这条单一的样本,我们更期望得到神经元在整个训练集上的综合评价,故将公式变为:
其中
损失函数有很多种,更为常用的损失函数是均方差损失(Mean Squared Error,MSE),本文也选它作为评价神经元表现好坏的损失函数,其定义为:
6. 梯度下降算法
在上一节中,我们详细介绍了损失函数。这一节的目标是寻找一种神经元的调整算法,使其在训练集上的损失值尽可能小。
目前我们的神经元里可调的参数只有
- 每次让
随机,取损失值最小的一个神经元。 - 每次让
自增一个常数,取损失值最小的一个神经。 - ...其他不靠谱的方案
上述方法,其实都是穷举法。在如此简单场景下或许可行,问题稍微复杂一些就直接失效,无轮是算法效率还是在可行性上。
6.1 算法过程
下面介绍目前所有主流神经网络的参数调整算法:梯度下降算法。我个人认为,这是深度学习中最核心最奇妙的算法。它的步骤为:
- 定义损失函数
,它是一个关于 的函数。 - 计算损失函数的导数
。 。 为学习率,是一个正数,用于控制参数调整的步长。- 注:在成绩预测的例子中,
在这一步更新。
重复上述过程
这里还是稍有抽象,为了更加具体,我们用梯度下降算法继续解决成绩预测问题,加深对该算法的理解。
在上一节中我们已定好损失函数,因此直接跳到第二步,计算损失函数的导数,具体过程为:
其中,
6.2 为什么梯度下降算法有用?
为了方便理解,假设损失函数为平方函数:
那么它的导数为:
它的函数图像如下:
- 当
时, 递增;取任意点 ,显然 ,推出 。- 结合上图可以看出,
时, 使损失函数往更小的方向走。
- 结合上图可以看出,
- 当
时, 递减;取任意点 ,显然 ,推出 。- 结合上图可以看出,
时, 使损失函数往更小的方向走。
- 结合上图可以看出,
上述例子可能不够严谨,不过可以帮助理解为什么梯度下降算法能让神经元往损失函数值更小的方向演变。
7. 代码实现
对于成绩预测这个问题,本文并未用任何深度学习框架,用Swift实现了一个简单的神经元,及其训练的代码,具体如下:
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
46import Foundation
let x_data = [1.0,2.0,3.0,4.0,5.0,6.0] // 特征
let y_labels = [2.0,4.6,6.0,8.0,10.0,12.0] // 标签
// 初始化w
var w = 10.0
// 神经元的表达式
func forward(x: Double) -> Double {
return x * w
}
// Loss函数
func loss(x_data: [Double], y_labels: [Double]) -> Double {
var totalLoss = 0.0
for (x,y) in zip(x_data,y_labels) {
let y_predict = forward(x: x)
totalLoss = totalLoss + pow((y_predict - y),2)
}
return totalLoss / Double(x_data.count)
}
// Loss函数的导函数
func gradient(x_data: [Double], y_labels: [Double]) -> Double {
var grad = 0.0
for (x,y) in zip(x_data, y_labels) {
grad = 2.0 * x * (x * w - y)
}
return grad / Double(y_labels.count)
}
// 训练神经元
func train() {
let epoch_num = 50 // 训练次数
let alpha = 0.01 // 学习率
for epoch in 0..<epoch_num {
let loss_v = loss(x_data: x_data, y_labels: y_labels)
let grad_v = gradient(x_data: x_data, y_labels: y_labels)
w = w - alpha * grad_v //调整w参数,往Loss小的方向走
print("Epoch:\(epoch),w=\(w),loss=\(loss_v)")
}
}
train()
输出结果如下:
1 | Epoch:0,w=9.04,loss=967.5266666666666 |
可以看出,经过50次训练后,loss函数值一直在下降,最终趋近于0;神经元的参数
与我们设想的一致。这说明,我们对神经元结构的设计,以及后续训练过程中涉及的损失函数、梯度下降算法,对成绩预测问题是有效的。
现实中的问题往往较为复杂,单一神经元难以满足,因此会选用更为复杂的结构,典型的是卷积神经网络、循环神经网络等。但要论其核心思想,以及训练过程,那几乎与本文这个简单的神经元无差,故本文可作为这些复杂网络的前置知识学习。