最近被问机器学习是怎么训练的,那么就顺便再深入理解梯度下降算法。
一、导数
首先我们需要复习一下导数的知识。导数是什么?一个导数是描述一个函数上的一个点在自变量增大时,因变量的变化率。在这里我讨论导数的对象为二维空间上的函数。我们用极限来表达导数,当左右侧导数相等时,这个点才存在导数。
及
这里强调一下导数的方向。用一个二次函数为例子,这个函数的导函数就是。在区间中,是随着的增加而减少的,说明这时函数上的每个点都是负增长(沿正方向),这就是为什么导函数这时。
二、偏导数
将导数从二维空间推广到多维空间,产生了多元函数的导数,就是偏导数。以二元函数为例,如果只有自变量变化,而另一个自变量固定不变(看成常量),那么这就是的一元函数,这个函数对x的导数就是二元函数z对于x的偏导数(对y的偏导数同理可得)。与导数相似,偏导数的方向也是对应自变量增大(座标轴正方向)的方向,在其他自变量不变的情况下表达因变量的变化率。
三、方向导数
导数与偏导数反映的是函数沿座标轴方向的变化率。那么我们如何知道函数沿任意方向的变化率呢?那么这就产生了方向导数。以二元函数为例,在点可微分,那么函数在该点沿任意方向的方向导数存在,其中和分别是方向与x轴和y轴的夹角。这样我们就可以得到在所有自变量都发生一定改变的方向下,因变量的变化率。
四、梯度
梯度的本意是一个向量,表示某一函数在该点处的方向导数沿着该方向取得最大值,即函数在该点处沿着该方向(此梯度的方向)变化最快,变化率最大(为该梯度的模)。换句话说,梯度就是使一个点的方向导数达到最大的方向。那么我们怎么求出某个点的梯度?我们还是以二元函数为例。
根据方向导数的定义,我们可以知道,一个方向导数是函数的各个偏导数与对应夹角余弦值的组合。更加直观的讲,就是先将偏导数作用到各个对应基向量(单位向量),再通过类似平行四边形法则合成的向量,就是梯度的方向。可以理解为,梯度的方向更偏向偏导数更大的基方向。如图例所示,假设i方向的偏导数更大,所以红色即为梯度方向。
将梯度推广到多元函数中,我们就可以得到一个向量,表示对应点的梯度。
五、梯度下降
机器学习的本质目标就是使损失函数达到全局最小,我们就可以利用函数的梯度来求解。因为梯度是一个有大小与方向的向量,所以我们直接用矩阵运算来表示这个算法。以二元函数为例,假设有原函数,即我们需要拟合的函数h,我们将函数的输入规定为(shape=[3,1]),那么参数向量就是(shape=[3,1])。那么就是输入的m个样本(shape=[m,3]),为拟合值(shape=[m,1]),为真实值(shape=[m,1])。
用均方差作损失函数,这个函数的自变量是原函数的参数,因为X是已知的样本值,m是样本数。这里涉及矩阵函数求导的问题,具体方法自行查询。
迭代训练开始之前,我们初始化,有以下迭代公式。其中,就是学习率。本质上梯度还是自变量增大的方向,只是每个自变量增大的幅度不同。注意上面偏导数的写法,是h-y,这样才是当偏导数>0的时候,因变量为正增长,所以要将对应变小。
六、代码实现
在这里我用python代码模拟梯度下降是如何一步一步到达函数的全局最小值点。
import numpy as np
# y = 1 + 2x1 + 3x2
x = np.random.random_integers(1,100,[10,3])
x[:,0] = 1
theta = np.zeros([3])
theta_real = np.asarray([1,2,3])
def PartialTheta0(x,theta):
J = 0
for i in range(len(x)):
J += (np.matmul(theta,x[i]) - np.matmul(theta_real,x[i]))
return J / len(x)
def PartialTheta1(x,theta):
J = 0
for i in range(len(x)):
J += ((np.matmul(theta,x[i]) - np.matmul(theta_real,x[i])) * x[i][1])
return J / len(x)
def PartialTheta2(x,theta):
J = 0
for i in range(len(x)):
J += ((np.matmul(theta, x[i]) - np.matmul(theta_real, x[i])) * x[i][2])
return J / len(x)
for _ in range(100000):
theta -= 0.000001 * np.asarray([PartialTheta0(x,theta),PartialTheta1(x,theta),PartialTheta2(x,theta)])
# theta -= 0.000001 * np.matmul(x.transpose(),np.matmul(x,theta)-np.matmul(x,theta_real)) / len(x)
print(theta)
通过运行输出可以看到,参数在一步步靠近真实值,说明算法是有效的。