梯度下降(jiang)是非常常用的优化算法。作为机器学习(xi)的基础知识,这是一个必须要掌握的算(suan)法。借助本文,让我们来一起详细(xi)了解一下这个算法。
前言本文的代(dai)码可以到我的Github上获取:
https://github.com/paulQuei/gradient_descent
本文的算法示例通过Python语言实现,在实现中使(shi)用到了numpy和matplotlib。如果你(ni)不熟悉这两个工具,请自(zi)行在网上搜索教程。
关于优(you)化大多数学习算(suan)法都涉及某种形式的优化。优(you)化指的是改变x以最小化或者最(zui)大化某个函数
我们通常(chang)以最小化
我们把要最小化或最大(da)化的函数成为目标函数(objective function)或准则(criterion)。
我们通常使用一(yi)个上标*表示最小化或最大(da)化函数的x值,记做这样(yang):
[x^*=arg; min; f(x)]
优化(hua)本身是一个非常大的话题。如果有(you)兴趣,可以通过《数值优(you)化》和《运筹学》的书籍进行学习。
模型与假设函数所有的模(mo)型都是错误的,但其中有些(xie)是有用的。– George Edward Pelham Box
模型是我们(men)对要分析的数据的一种假设,它(ta)是为解决某个具体问题从数据中学习到(dao)的,因此它是机器学习最核心的(de)概念。
针对一个问题,通常有大(da)量的模型可以选(xuan)择。
本文不会深入讨论这方面(mian)的内容,关于各种模型请参阅(yue)机器学习的相关书籍。本文仅以(yi)最简单的线性模型为基础来讨论梯度(du)下降算法。
这里我们先介绍一下(xia)在监督学习(supervised learning)中常见的三个符号:
m,描述训练样本的数量x,描述输入变量或特征(zheng)y,描述输出变量或者叫目标值请注意,一个样(yang)本可能有很多的特征,因此x和y通常是一个向量。不过在刚(gang)开始学习的时候,为了便于理解,你可以暂时理解为这就是一个具体的数(shu)值。
训练集会包含(han)很多的样本,我们用
x是数据样本的特征,y是(shi)其目标值。例如(ru),在预测房价的模型中,x是(shi)房子的各种信息,例如:面积,楼层,位置等等(deng),y是房子的价格。在图像识别的任(ren)务中,x是图形的所有像素点数据,y是图像中包含的目标对象(xiang)。
我们是希望寻找一个函(han)数,将x映射到y,这(zhe)个函数要足够的好,以至于
学习的(de)过程如下图所示。即:首先(xian)根据已有的数据(称之为训练集)训练我们的算法模型(xing),然后根据模型的假设(she)函数来进行新数据的预测。
img线性模型(linear model)正如其名称那样:是希(xi)望通过一个直线的形式来描述模式。线(xian)性模型的假设函数如下所(suo)示:
[h_{\theta}(x)=\theta_{0} + \theta_{1} * x]
这个公式对于大家来说应该(gai)都是非常简单的。如果把它绘制出(chu)来,其实就是一条直线。
下图是一个具体的例(li)子,即:
在实际的机器学(xue)习工程中,你会(hui)拥有大量的数据。这些数据会来自(zi)于某个数据源。它们存储在(zai)csv文件中,或者以其他(ta)的形式打包。
但是本文作为演(yan)示使用,我们通过一些简单的代码自动(dong)生成了需要的数据。为了便于计算(suan),演示的数据量也很小。
import numpy as np
max_x=10
data_size=10
theta_0=5
theta_1=2
def get_data():
x=np.linspace(1, max_x, data_size)
noise=np.random.normal(0, 0.2, len(x))
y=theta_0 + theta_1 * x + noise
return x, y
这段代码很(hen)简单,我们生成了x范围(wei)是 [1, 10] 整数的10条数据。对应的y是以线性(xing)模型的形式计算得到,其函(han)数是:
最后我们的数据(ju)如下所示:
x=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
y=[6.66, 9.11, 11.08, 12.67, 15.12, 16.76, 18.75, 21.35, 22.77, 24.56]
我们可以(yi)把这10条数据绘制出来这样就(jiu)有一个直观的了解(jie)了,如下图所示:
虽(sui)然演示用的数据是我(wo)们通过公式计算得到的。但在实际(ji)的工程中,模型(xing)的参数是需要我们通过数据学习到的。所以下文我们假设我们不知(zhi)道这里线性模式的(de)两个参数是什么(me),而是通过算法(fa)的形式求得。
最后再跟已(yi)知的参数进行对比以验证(zheng)我们的算法是否正确。
有了上面的(de)数据,我们可以尝试(shi)画一条直线来描述我们的模型。
例如,像下面这样画一条水平(ping)的直线:
很显然,这条(tiao)水平线离数据太远了,非常的(de)不匹配。
那我们可以(yi)再画一条斜线。
我们初次画的斜(xie)线可能也不贴切,它可(ke)能像下面这样:
最(zui)后我们通过不断尝(chang)试,找到了最终最合(he)适的那条,如下所示:
梯度下降(jiang)算法的计算过程,就(jiu)和这种本能式的试探是类似的,它就是不停的迭代,一步步的接近最(zui)终的结果。
代价(jia)函数上面我们尝试了几次通过一条直线来(lai)拟合(fitting)已有的数(shu)据。
二维平面(mian)上的一条直线可以通过(guo)两个参数唯一的确定,两个(ge)参数的确定也即模型的确定。那(na)如何描述模型与数(shu)据的拟合程度呢?答案(an)就是代价函数。
代价函(han)数(cost function)描述(shu)了学习到的模型与实际结果的偏差(cha)程度。以上面的三幅图为例,最后一(yi)幅图中的红线相比第(di)一条水平的绿线,其偏离程(cheng)度(代价)应该是(shi)更小的。
很显然,我们希望我们的假设(she)函数与数据尽可能的贴近,也就是(shi)说:希望代价函数(shu)的结果尽可能的小。这就涉及到(dao)结果的优化,而梯(ti)度下降就是寻找最小值(zhi)的方法之一。
代价函(han)数也叫损失函数。
对于每一个样本(ben)
很自然的,我们会想(xiang)到,通过下面这个公式(shi)来描述我们的模型与实际值的偏(pian)差程度:
[(h_\theta(x^i) - y^i)^2=(\widehat{y}^{i} - y^i)^2=(\theta_{0} + \theta_{1} * x^{i} - y^{i})^2]
请注意,
每一条数据都会存在(zai)一个偏差值,而代价函数就是对所有样(yang)本的偏差求平均值,其计算(suan)公式如下所示:
[L(\theta)=\frac {1}{m} \sum_{i=1}^{m}(h_\theta(x^i) - y^i)^2=\frac {1}{m} \sum_{i=1}^{m}(\theta_{0} + \theta_{1} * x^{i} - y^{i})^2]
当损失函数的结(jie)果越小,则意味着通过(guo)我们的假设函数估算出的结果与真实(shi)值越接近。这也(ye)就是为什么我们要最小化损失函数的(de)原因。
不(bu)同的模型可能会用不同的损失函(han)数。例如,logistic回归的(de)假设函数是这样的:
借助上面这(zhe)个公式,我们可以(yi)写一个函数来实现代价函数:
def cost_function(x, y, t0, t1):
cost_sum=0
for i in range(len(x)):
cost_item=np.power(t0 + t1 * x[i] - y[i], 2)
cost_sum +=cost_item
return cost_sum / len(x)
这个函数(shu)的代码应该不用多做解(jie)释,它就是根据上(shang)面的
我们可以尝试选(xuan)取不同的
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
theta_0=5
theta_1=2
def draw_cost(x, y):
fig=plt.figure(figsize=(10, 8))
ax=fig.gca(projection='3d')
scatter_count=100
radius=1
t0_range=np.linspace(theta_0 - radius, theta_0 + radius, scatter_count)
t1_range=np.linspace(theta_1 - radius, theta_1 + radius, scatter_count)
cost=np.zeros((len(t0_range), len(t1_range)))
for a in range(len(t0_range)):
for b in range(len(t1_range)):
cost[a][b]=cost_function(x, y, t0_range[a], t1_range[b])
t0, t1=np.meshgrid(t0_range, t1_range)
ax.set_xlabel('theta_0')
ax.set_ylabel('theta_1')
ax.plot_surface(t0, t1, cost, cmap=cm.hsv)
在这段代码中(zhong),我们对
如果我们将所有点的代价函(han)数值绘制出来,其结(jie)果如下图所示:
从这个图形中(zhong)我们可以看出,当