1、引言
我国里程碑式科幻电影巨制《流浪地球》一经播出,在国内外引起了广泛的关注,吸引了一大批科幻爱好者前去踊跃观赏。
在电影中,时间已辗转至2075年,受到某些因素的影响,我们赖以生存的太阳即将面临毁灭。面对绝境,人类试图开启"流浪地球"计划,试图带着地球一起逃离太阳系,在浩瀚的宇宙中寻找人类宜居的新家园…
在现实中,时间回溯至2021年。当下的人类去年刚经历了人类历史上又一场天际浩劫,席卷全球的COVID-19病毒夺取了数以万计遇难者的宝贵生命,且该灾难目前仍留有余温…
接憧而至的灾难让人们捉襟见肘,我们无法预知下一次的灾难究竟会何时降临,该灾难是打击性的,还是毁灭性的。但我们能够把握的一点是,当灾难降临时,除了团结互助、众志成城外,我们人类亮出的“圣剑”无疑将是科学与技术…
而当下的科学与技术中,人工智能已悄然站在了一个时代的风口。人工智能技术正以有目共睹的速度影响着当今的各行各业。熟悉与掌握人工智能技术,不仅是时代使然,更像是一种使命,以帮助我们更好地应对变幻莫测的未来。利用科技改变生活,利用科技驱动革命…
而掌握人工智能技术的关键之一,便是学习与了解机器学习的相关概念。
为了比较直观地初步了解有关机器学习的相关概念,让我们用一个比较浅显的例子来为大家做一个简单的引入。
2、问题描述
电影《流浪地球》于2019年上映,观影人数第一年有2千万人,第二年有4千万人,第三年有6千万人,请问在2072年有多少千万人将会观看过该电影?
3、问题分析
这个问题,在我们人类看来,是十分简单的。我们假设x表示年份,y表示该年观影人数(单位:千万人)。根据问题中所给的几组数据,我们可以很容易地猜测x和y具有线性相关性。因此我们假设
y
=
k
∗
x
y=k * x
y=k∗x,将这几组数据中的任意一组代入即可得
y
=
2
∗
x
y=2*x
y=2∗x,故2072年有
y
=
2
∗
(
2072
−
2019
+
1
)
=
2
∗
54
=
108
y=2*(2072-2019+1)=2*54=108
y=2∗(2072−2019+1)=2∗54=108千万人观看过电影《流浪地球》。
以上我们仅用了简单的数学思想,便轻易地得到了该问题的答案。可对于机器而言,机器只会算术,机器可不懂数学啊!那我们如何让机器学会去思考并求解这个问题呢?
4、问题求解
4.1 数据集
对于任何一个学习任务而言,用以学习的数据必不可少。在本例中,仅有简单的三组数据作为训练数据集。
x_data = [1.0, 2.0, 3.0]
y_data = [2.0, 4.0, 6.0]
4.2 模型构造
我们知道,对于一台机器而言,机器有输入input,也有输出output,且output在大多情况下依赖于input。
在本问题中,我们将年份x看作input,观影人数y看作output,问题就转换为了让机器去求解x与y之间的依赖关系。
对于机器而言,机器一开始并不知道x和y之间的具体关系,故我们姑且可以帮助机器假设x与y之间具有最简单的线性相关关系,即
y
=
w
∗
x
y=w*x
y=w∗x。
def model(w, x):
return w*x
从而该问题的核心即是利用已知的数据去求解系数
w
w
w
可问题是,机器可不知道数学里的代数学知识,它可不会求解方程…所以它只好使用最笨的方法,给
w
w
w先取一个简单的随机值
w
0
w_0
w0。在这里,机器选择给
w
w
w取一个介于0到1之间(或介于3-4之间)的随机值…
w_0 = random.uniform(0, 1)
# w_0 = random.uniform(3, 4)
4.3 损失函数
在我们期待机器自我学习求解
w
w
w之前,让我们先思考一个问题:如何衡量机器训练时预测的数据
y
h
a
t
y_{hat}
yhat与真实值
y
y
y之间的差异?
我们先容易想到的是,通过计算
y
h
a
t
y_{hat}
yhat与
y
y
y之间的差值,即
∣
y
h
a
t
−
y
∣
|y_{hat}-y|
∣yhat−y∣。可问题是,该计算方式并不方便,该式中绝对值的存在会给我们的计算处理时带来麻烦。
由于开平方可以去绝对值,于是,我们不妨给该式加个平方,再除个2(方便后续求梯度),从而得到了该式的改进版本
1
2
∗
(
y
h
a
t
−
y
)
2
\frac{1}{2}*(y_{hat}-y)^2
21∗(yhat−y)2。
我们记训练预测值
y
h
a
t
y_{hat}
yhat和真实值
y
y
y之间的差异为损失
l
o
s
s
loss
loss,故有
l
o
s
s
=
1
2
∗
(
y
h
a
t
−
y
)
2
loss=\frac{1}{2}*(y_{hat}-y)^2
loss=21∗(yhat−y)2
def loss(w, x, y):
y_hat = model(w, x)
return (y_hat- y)**2 / 2
4.4 梯度下降
有了损失函数
l
o
s
s
loss
loss之后,我们还是不能够让机器直接开始训练学习,因为我们还需要弄明白一个问题:
w
w
w如何才能变化至我们的理想值?
要解决这个问题,我们需要引入梯度的概念。
梯度原先是为研究多元函数而提出的概念。其本意是一个向量,表示某一点在该点处的方向导数沿着该方向取得最大值,即函数上在该点处沿着该方向变化率最大。
上述的说法可能有些抽象,其实可以这么理解:你在山顶,打算下山。下山总共有3个方向,每个方向有1个坡,其中最陡的那个坡,就是你沿着梯度方向的坡,也是你下山最快的坡。故梯度下降,实际上也就是"沿着梯度下降"。
当然,如果这么解释还没有搞明白,也没事。因为在本例中,未知量仅有
w
w
w,故我们所面临的方向只有1个,故所求的梯度实际上并不是偏导数,而是导数。
那么梯度下降在本例中的具体含义是什么呢?
我们知道,在损失函数
l
o
s
s
=
1
2
∗
(
y
h
a
t
−
y
)
2
=
1
2
∗
(
w
∗
x
−
y
)
2
loss=\frac{1}{2}*(y_{hat}-y)^2=\frac{1}{2}*(w*x-y)^2
loss=21∗(yhat−y)2=21∗(w∗x−y)2中,当给定x和y后,
w
w
w为自变量,loss为因变量。loss关于w变化的函数图像大致如下所示:
很显然,该函数是一个简单的二次函数,它关于某一条直线
w
=
w
∗
w=w^*
w=w∗对称。且
l
o
s
s
loss
loss在
w
=
w
∗
w=w^*
w=w∗时取得最小值
l
o
s
s
m
i
n
=
0
loss_{min}=0
lossmin=0,此时表示机器经学习训练后所得的预测值与真实值之间没有差异,也就是我们期望达到的结果。
在本例中,
w
∗
=
2
w^*=2
w∗=2,故我们让机器学习的目标就是让一开始所设的
w
w
w值不断地朝目标
w
=
w
∗
=
2
w=w^*=2
w=w∗=2靠近。而这个“靠近”的过程,也就是梯度下降的过程,其具体可以用以下数学表达式表示:
w
t
=
w
t
−
1
−
α
∗
g
r
a
d
w_{t}=w_{t-1}-α*grad
wt=wt−1−α∗grad
其中,
α
α
α为学习率,
g
r
a
d
grad
grad表示
t
−
1
t-1
t−1时刻变量
w
w
w在
l
o
s
s
loss
loss曲线上的梯度。
以下让我们来解析该式为何如此构成,以及式中各参数的具体含义…
首先我们看梯度
g
r
a
d
grad
grad。按照上面的说法,grad既然表示某个变化率最大的方向,故在本例中,
w
w
w在
l
o
s
s
loss
loss曲线上的梯度方向应为切线方向。该切线的具体表达式为:
g
r
a
d
=
∂
l
o
s
s
∂
w
=
∂
∂
w
[
1
2
(
w
x
−
y
)
2
]
=
(
w
x
−
y
)
∗
x
grad=\frac{\partial loss}{\partial w}=\frac{\partial}{\partial w}[\frac{1}{2}(wx-y)^2]=(wx-y)*x
grad=∂w∂loss=∂w∂[21(wx−y)2]=(wx−y)∗x
def gradient(w, x, y):
return x*(w*x - y)
接下来,我们看学习率
α
α
α,它是一个大于0的正值,它用于控制梯度下降速度的快慢。
在了解了这两个参数的含义后,我们再来看下图。
该图即反映了公式
w
t
=
w
t
−
1
−
α
∗
g
r
a
d
w_{t}=w_{t-1}-α*grad
wt=wt−1−α∗grad的运行机理…
首先我们看loss曲线的左半部分。橙色点start 可以看作
w
t
−
1
w_{t-1}
wt−1,青色next 可以看作
w
t
w_{t}
wt,而蓝色的直线表示
w
t
−
1
w_{t-1}
wt−1在曲线上的切线。由于该切线的斜率显然小于0,即
g
r
a
d
<
0
grad<0
grad<0,而
α
α
α又是一个大于0的值,即
α
>
0
α>0
α>0,从而
α
∗
g
r
a
d
α*grad
α∗grad是一个小于0的值,
w
t
=
w
t
−
1
−
α
∗
g
r
a
d
w_t=w_{t-1}-α*grad
wt=wt−1−α∗grad就相当于
w
t
−
1
w_{t-1}
wt−1加上了一个大于0的值,再将该结果赋值给
w
t
w_t
wt。而在loss曲线的左半部分,随着
w
w
w的不断增大,loss将会随之不断减小,从而
w
w
w随着梯度下降的过程也就是
w
w
w不断增大趋近于
w
=
w
∗
w=w^*
w=w∗的过程。
同理,接下来我们再看loss曲线的右半部分。金色点start 可以看作
w
t
−
1
w_{t-1}
wt−1,品红色next 可以看作
w
t
w_{t}
wt,而蓝色的直线表示
w
t
−
1
w_{t-1}
wt−1在曲线上的切线。在这边,切线的斜率
g
r
a
d
>
0
grad>0
grad>0,同时
α
>
0
α>0
α>0,从而
w
t
=
w
t
−
1
−
α
∗
g
r
a
d
w_t=w_{t-1}-α*grad
wt=wt−1−α∗grad就相当于
w
t
−
1
w_{t-1}
wt−1加上了一个小于0的值,再将该结果赋值给
w
t
w_t
wt。而在loss曲线的右半部分,随着
w
w
w的不断减小,loss将会随之不断减小,从而
w
w
w随着梯度下降的过程也就是
w
w
w不断减少且趋近于
w
=
w
∗
w=w^*
w=w∗的过程。
此时,我们将会惊讶的发现:我们仅仅只使用了一个公式,就模拟了机器学习训练的过程!理论上,无论
w
w
w的初始值为多少,只要公式
w
t
=
w
t
−
1
−
α
∗
g
r
a
d
w_t=w_{t-1}-α*grad
wt=wt−1−α∗grad的迭代次数够多,
w
t
w_t
wt最终必将趋近于我们的理想值
w
∗
w^*
w∗。
def gradient_decent(x, y, lr, w):
grad = gradient(x, y)
w = w - lr*grad
return w
4.5 模型训练
掌握了梯度下降的大致原理后,便可以开始进行模型的训练了。这也是机器学习中体现机器“学习”的过程。
但在我们进行正式的训练之前,我们还需要思考以下两个问题:
- x和y在本例中总共有3组数据。我们在计算梯度grad时,应取其中任意一组数据计算的值,还是取三组数据计算的平均值?
- 学习率α在训练过程中对于梯度grad的控制作用具体如何体现?
首先对于第一个问题,其实答案很简单,即在本例中采用两种方案其中任意一种都可以。实际上,这是因为在本例中训练数据量较小的原因。但我们可以考虑当训练数据量n很庞大的时候,比如有
n
=
1
0
6
n=10^6
n=106组数据时,计算所有组数据计算的平均值可想是一个非常耗时的工作。因此此时我们可以取其中的任意一组数据来代表整体的数据,以减少计算量,提高工作效率。然而在一些情景下仅用一组数据的计算值来代表整体的数据计算的平均值,难免会带来较大的偏差,故我们可以考虑使用m组(
1
<
=
m
<
=
n
1<=m<=n
1<=m<=n)数据的平均值来代替整体数据计算的平均值。
实际上,在计算梯度grad时,取n组数据计算的平均值、取其中任意一组数据计算的值、取n组数据中任意m组数据的平均值三种不同的做法代表了梯度下降算法中计算梯度的三个不同版本:
梯度下降GD(gradient decent)
g
r
a
d
=
1
n
∑
i
=
1
n
(
w
x
i
−
y
i
)
∗
x
i
grad=\frac{1}{n}\sum_{i=1}^n(wx_i-y_i)*x_i
grad=n1i=1∑n(wxi−yi)∗xi
随机梯度下降SGD(stochastic gradient decent)
g
r
a
d
=
(
w
x
i
−
y
i
)
∗
x
i
,
i
=
r
a
n
d
i
n
t
(
1
,
n
)
grad=(wx_i-y_i)*x_i,i=randint(1,n)
grad=(wxi−yi)∗xi,i=randint(1,n)
批量梯度下降batch-SGD(batch gradient decent)
g
r
a
d
=
1
m
∑
i
=
1
m
(
w
x
i
−
y
)
∗
x
i
grad=\frac{1}{m}\sum_{i=1}^m(wx_i-y)*x_i
grad=m1i=1∑m(wxi−y)∗xi
在这里,为了简便,我们采用最为简单的SGD算法。故上述的GD算法将替换为:
def SGD(x, y, lr, w):
# stochastic index
sc_index = random.randint(0, len(x) - 1)
x_i, y_i = x[sc_index], y[sc_index]
grad = gradient(x_i, y_i)
w = w - lr*grad
return sc_index, w
接下来,让我们讨论一下学习率
α
α
α的具体作用形式。
首先我们考虑初始状态
α
=
1
,
w
=
0.56
α=1,w=0.56
α=1,w=0.56的情况,假设我们采用SGD算法进行训练。经第一轮SGD后,设随机选出的为第一组数据
x
1
=
1.0
,
y
1
=
2.0
x_1=1.0,y_1=2.0
x1=1.0,y1=2.0,计算得出
g
r
a
d
=
(
w
x
1
−
y
1
)
∗
x
1
=
(
0.56
∗
1.0
−
2.0
)
∗
1.0
=
−
1.44
grad=(wx_1-y_1)*x_1=(0.56*1.0-2.0)*1.0=-1.44
grad=(wx1−y1)∗x1=(0.56∗1.0−2.0)∗1.0=−1.44。从而第一轮对w的更新为
w
=
w
−
α
∗
g
r
a
d
=
0.56
−
(
−
1.44
)
∗
1.0
=
2.0
w=w-α*grad=0.56-(-1.44)*1.0=2.0
w=w−α∗grad=0.56−(−1.44)∗1.0=2.0,直接一步便学到位了…
这里能够一步学到位,实际上是因为本例中给的数据都是不包含噪音(不含偏差数据组)的。假如我将原先的3组数据中第一组数据改为含偏差的新数据,即
x
1
=
1.0
,
y
1
=
2.5
x_1=1.0,y_1=2.5
x1=1.0,y1=2.5,其中
y
1
=
2.5
y_1=2.5
y1=2.5与实际的真实数据
y
1
=
2
y_1=2
y1=2相差了
0.5
0.5
0.5。
此时的训练数据集为:
x_data = [1.0, 2.0, 3.0]
y_data = [2.5, 4.0, 6.0]
假设我们此时继续采用SGD算法进行训练,
w
w
w和
α
α
α的初始值与上述相同。经第一轮SGD后,设随机选出的第一组数据为
x
1
=
1.0
,
y
1
=
2.5
x_1=1.0, y_1=2.5
x1=1.0,y1=2.5,计算得出
g
r
a
d
=
(
w
∗
x
1
−
y
1
)
∗
x
1
=
(
0.56
∗
1.0
−
2.5
)
∗
1.0
=
−
1.94
grad=(w*x_1-y_1)*x_1=(0.56*1.0-2.5)*1.0=-1.94
grad=(w∗x1−y1)∗x1=(0.56∗1.0−2.5)∗1.0=−1.94。从而第一轮对w的更新为
w
=
w
−
α
∗
g
r
a
d
=
0.56
−
(
−
1.94
)
∗
1.0
=
2.56
w=w-α*grad=0.56-(-1.94)*1.0=2.56
w=w−α∗grad=0.56−(−1.94)∗1.0=2.56。接着第二轮选出了第二组数据
x
2
=
2.0
,
y
2
=
4.0
x2=2.0,y2=4.0
x2=2.0,y2=4.0,计算得出
g
r
a
d
=
2.24
,
w
=
w
−
α
∗
g
r
a
d
=
2.56
−
2.24
∗
1.0
=
0.32
grad=2.24,w=w-α*grad=2.56-2.24*1.0=0.32
grad=2.24,w=w−α∗grad=2.56−2.24∗1.0=0.32…
从这个过程中,我们发现一件奇怪的事:随着机器的不断学习训练,训练得出的
w
w
w不但没有逐渐接近我们的期望值
w
∗
=
2.0
w^*=2.0
w∗=2.0,反而却不断偏离?具体的直观表示即如下图中
w
w
w在loss曲线上以对称轴
w
=
w
∗
w=w^*
w=w∗"反复横跳",偏离目标值…
为了减小噪声数据所带来的影响,我们可以采用的一种做法即为降低学习率
α
α
α。比如我可以将
α
α
α降到0.1,那么纵使由于噪声数据的影响使得
w
w
w在曲线两端"反复横跳",也可以保证在整体趋势下
w
w
w的值是不断地趋近于我们的目标值的,较为直观的理解如下图所示。
在实际任务中,我们无法保证拿到的训练数据是无偏的,所以设置一个学习率
α
α
α,对于机器学习的训练过程是十分有必要的,
α
α
α常取的值有
0.1
,
0.01
,
0.003
0.1,0.01,0.003
0.1,0.01,0.003等。
在理解了以上两个重要概念后,训练过程对于我们来说便十分简单了。实际上,对于本例而言,仅有以下三步:
- 设置训练参数w初始值
w
0
w_0
w0,训练数据x_data、y_data、学习率lr以及训练次数num_epochs。
- 拿到训练数据x_data、y_data,学习率lr,训练参数w,使用SGD算法进行梯度下降,返回训练时所取的数据索引sc_index和训练后参数w。
- 拿到SGD返回的数据索引sc_index和训练参数w,利用loss函数衡量x_data[sc_index]*w和真实值y_data[sc_index]之间的差异。
除此之外,为了有一个直观的理解,我们将整个训练过程的结果保存下来,并做一个简单的可视化。
def train(num_epochs, x, y, w, lr):
epoch_list = [epoch + 1 for epoch in range(num_epochs)]
loss_list = []
for epoch in range(num_epochs):
sc_index, w = SGD(x_data, y_data, lr, w)
l = loss(w, x[sc_index], y[sc_index])
loss_list.append(l)
print(f'epoch:{epoch+1} loss={l:.6f} w={w:.3f}')
plt.plot(epoch_list, loss_list)
plt.xlabel("epoch")
plt.ylabel("loss")
plt.show()
return w
假设我们的训练数据与参数为:
x_data = [1.0, 2.0, 3.0]
y_data = [2.0, 4.0, 6.0]
w_0 = random.uniform(0, 1)
num_epochs, lr = 10, 0.1
train(num_epochs, x_data, y_data, w_0, lr)
最终的训练结果为:
假设我们的训练数据与参数为:
x_data = [1.0, 2.0, 3.0]
y_data = [2.5, 4.0, 6.0]
w_0 = random.uniform(0, 1)
num_epochs, lr = 20, 0.1
w_final = train(num_epochs, x_data, y_data, w_0, lr)
最终的训练结果为:
4.6 预测
在机器完成了训练之后,便得到了一个接近于理想参数
w
∗
w^*
w∗的值
w
f
i
n
a
l
w_{final}
wfinal。从而我们的预测代码为:
def prediction(w, year):
return w*(year - 2019 + 1)
print(f'2072年时观看过《流浪地球》的人数将达到{prediction(w_final, 2072):.1f}千万人')
4.7 完整实现代码
import random
import matplotlib.pyplot as plt
def model(w, x):
return w*x
def loss(w, x, y):
y_hat = model(w, x)
return (y_hat - y)**2 / 2
def gradient(w, x, y):
return x*(w*x - y)
def SGD(x, y, lr, w):
# stochastic index
sc_index = random.randint(0, len(x) - 1)
print("sc_index:", sc_index)
x_i, y_i = x[sc_index], y[sc_index]
grad = gradient(w, x_i, y_i)
w = w - lr*grad
return sc_index, w
def train(num_epochs, x, y, w, lr):
epoch_list = [epoch + 1 for epoch in range(num_epochs)]
loss_list = []
for epoch in range(num_epochs):
sc_index, w = SGD(x_data, y_data, lr, w)
l = loss(w, x[sc_index], y[sc_index])
loss_list.append(l)
print(f'epoch:{epoch+1} loss={l:.6f} w={w:.3f}')
plt.plot(epoch_list, loss_list)
plt.xlabel("epoch")
plt.ylabel("loss")
plt.show()
return w
x_data = [1.0, 2.0, 3.0]
y_data = [2.0, 4.0, 6.0]
w_0 = random.uniform(0, 1)
num_epochs, lr = 50, 0.1
w_final = train(num_epochs, x_data, y_data, w_0, lr)
def prediction(w, year):
return w*(year - 2019 + 1)
print(f'2072年时观看过《流浪地球》的人数将达到{prediction(w_final, 2072):.1f}千万人')
5、总结与思考
通过以上这么一个线性模型的简单例子,我们或许可以总结出机器学习的全过程中几个重要的组成部分:数据、模型、策略、算法。
首先就是数据,数据质量的好坏直接会影响我们的训练过程与训练结果。故在整个训练开始前,进行数据的筛选和噪音数据的剔除(即所谓的人工特征提取)对于整个机器学习过程尤为重要。
接下来是模型、策略和算法,这三个部分共同组成了机器学习的三要素。
- 模型即为我们期盼机器学习到的映射函数
y
=
f
(
x
)
y=f(x)
y=f(x),使得对于一个我们任意给定机器的一个输入
x
x
x,能得到一个接近于真实值(等于那最好)
y
y
y。
- 策略即为帮助机器从众多可能存在的假设模型学习到理想模型所使用的方法。如本例中为得到我们理想的模型
y
=
2
∗
x
y=2*x
y=2∗x,我们使用损失函数loss来度量机器经每轮训练后的所得预测值
y
h
a
t
y_{hat}
yhat与真实值
y
y
y之间的差异。
- 算法表示机器学习模型的具体方法,也就是使得模型中可训练参数接近于理想值的具体实施步骤,如本例中所采用的学习算法为SGD。
-------------------------------------------结尾分割线-------------------------------------------
正如上文所讲,以上的例子仅为了对想要学习人工智能或机器学习的朋友做一个简单的引入,可能存在诸多不严谨之处,还望各位见谅。但我个人看来,无论学习再怎么高大上的知识,先做一个深入浅出的理解还是十分有必要的…
对于本文所举的例子而言,其关键便在于对SGD算法中梯度下降的过程与学习率α的理解。如果看完后一时半会儿并没有理解,或者觉得难以理解,没关系,只要能从本例中有一些收获,日后在根据自己的理解对整个机器学习过程进行一个简单的推导,在纸上勾勾画画一下,或许你将在某个时刻茅塞顿开。
实际上,对于学习率
α
α
α的理解,除了从噪音数据的角度外,还可以再从另外的一些角度理解。如讨论在数据正常时学习率
α
>
1
α>1
α>1或学习率
α
<
1
α<1
α<1对训练过程所起到的作用。限于篇幅,具体的过程在这里我便不再赘述了…
最后,希望看到了这里的你,能收获探究人工智能与机器学习的乐趣…
2072年,受到了某种因素的影响,太阳的能源即将燃烧殆尽,《流浪地球》的电影情景猛然照进了现实…但这一次,人类不再恐慌。伴随着强人工智能AGI的发展,人类的科技高歌猛进,人类对量子力学、黑洞、虫洞、弦理论的研究有了新的突破…在灾难即将降临的那一刻,AGI按下按钮,整个地球瞬间坍缩至普朗克尺度的一个粒子,进入了高维空间,进入了虫洞,最后顺着虫洞空间跳跃至了人类早已在宇宙中物色好的新家园…