梯度下降法总结.推到.以及实现

 

一.前言

二.简述

三.简单线性回归中的梯度下降实现

四.多元线性回归下的梯度下降算法

五.随机梯度下降算法

六.scikit-learn中的随机梯度下降算法使用

七.总结


一.前言

很久不总结,对于很多知识都出现了遗忘,很久以前曾经写过一篇有关线性回归算法博文,所以打算接着上一次的来经行总结。说到线性回归就不得不提及与它有着密切关系的一种优化算法也就是梯度下降法。梯度下降法本身并不是一种机器学习的算法,它只是一种基于搜索的最优化算法,它的作用简单来讲就是最小化一个损失函数,这个损失函数是从哪里来的呢,就是从我们的线性回归算法而来。

二.简述

首先,我们先来简单地画一张图来简单地解释一下我们地梯度下降算法是干什么地。

我们假设现在我们只有一个参数,每一个参数对应一个损失函数地值,而我们引入梯度下降法地目的,就在于去取这个参数所对应的损失函数的最小值。当然对于这个图来讲 求这个极值是非常简单的,只需要求导等于零即可,但是我们梯度下降法的目的却是为了求解更高维的也就是不止一个参数的情况下求解我们损失函数最小值的方法。梯度下降法的思路是这样的,我们随机在这个函数上取一个点给他求导,在直线方程中,倒数代表斜率,在曲线方程中,倒数代表切线的斜率,而在我们的这张图上来讲,倒数代表theta单位变化时,J相应的变化。倒数在这里我们也可以想象成一个方向,我们每取一个Theata点,J也随着进行变化我们可以想象,theta取值越大在没有到达极值点时,我们的J总是在减小的,同时我们会发现我们的这个倒数是在增加的。但是我们的目的是为了寻找这个函数的最小值,所以我们不能只停留在一个点上我们需要移动,移动就需要一个大小,我们在此称其为步长,在梯度下降算法中我们也称其为学习率。因为我们在这里求的方向导数是负数,而我们的目的是为了求最小值,所以我们要让它向负方向移动,也就是乘以一个负数步长。然后我们就用原来的参数theta加上我们所计算的这一点所求的到数值来更新我们的theata值。也就是这样的一个公式:\Theta -\eta \frac{dj}{d\Theta }。不停的更新我们的theta值我们最终就会找到我们需要的损失函数最小时的参数值。

注:1.这里我们需要注意一下这个步长,也就是我们所说的学习率,这里的学习率我们在面的不同的损失函数时,我们所使用的学习率是不同的,当然没有标准不代表我们可以随意设置,学习率的取值会影响到我们的获取最优解的速率,举个简单的例子,我们步长如果太短,那么我们就有可能话费很长的时间来更新我们的参数值,而如果我们的步长过大,或者极其不合理,那么我们就有可能找不到我们的最优解。

       2.同时我们需要注意的另外一点就是,并非所有的损失函数都有极值点,相对的并非所有的损失函数都只有一个极值点,我们随便画一个曲线我们都可以画出很多个最低点,但它门并非最低,我们把这些点成为局部最优解。

 

 

 

 

三.简单线性回归中的梯度下降实现

接下来我们举一个简单的例子来说明一下我们的梯度下降算法的实现。我们在线性回归算法中曾得到过一个损失函数,我们给定其参数为了使其极值最小,当时我们曾使用最小二乘法来找出其极值,现在我们使用梯度下降法来找出其极值。损失函数如下图:

下面我们来看一下我们实现的代码,因为这是一个简单的线性回归的损失函数,所以我们自己手动先随便写一个曲线的函数。

import numpy as np
import matplotlib.pyplot as plt 
plot_x = np.linspace(-5,5,150) #为了保证线性我用linspace在-5到5之间生成150个均匀的点
plot_y = (plot_x-2.5)**2-1#随机设计的曲线方程
def dJ(theta):
    return 2*(theta-2.5)
def J(theta):
    return (theta-2.5)**2-1
def Grandientdescent(initial_theta,eta,L_iteration,epsilon = 0.000000001):
    theta = initial_theta 
    theata_pool.append(initial_theta)
    iteration = 1
    while True:
        grandient = dJ(theta)
        last_theta = theta
        theta = theta - eta * grandient
        theata_pool.append(theta)
        if(abs(J(theta)-J(last_theta)) < epsilon):
#这里的判断是为了判断我们的损失函数较上一个theta值是否有变化, 当其插值小于一个我们设置的非常小的值的时候,我们可以认为它取到了我们的最优值,跳出这个循环
           break
        iteration += 1
theata_pool = []

Grandientdescent(0,0.1,1000)
plt.plot(plot_x,J(plot_x))
plt.plot(np.array(theata_pool),J(np.array(theata_pool)),color = 'r',marker ='+')
plt.show()

这就是一个简单的梯度下降的算法实现,在此我简单对立面的一些参数解释一下,我设置了一个L_iteration计数器这是我为了防止因为学习率设置不正常导致最终无法找到最低点进入死循环或反而得到过大数字而报错。同时我在函数里还设置了一个数组,用以存放每次更新的学习率用以可视化。当然我最后用一张图将我们的学习过程打印了出来如下。可以很明显的看出,红寺的部分就是我们参数更新的过程。

四.多元线性回归下的梯度下降算法

相对于简单线性方程来讲,较为复杂和常见的就是多元线性方程了。也就是从y= ax+b变成y=ax+by+c等等的多元线性回归方程,但是其实理解起来并不难,就是从原来的一个参数变为现在的多个参数,我们从原来的对于一个参数求导,变为对于其中每一个参数求偏导而已。如图。这里用图来解释一下也很简单。

然后我们将其带入到我们的目标损失函数中去。在这里我们需要对其经行一个简单的数学推到,我用图来表示下面的步骤

我们所需要求出来的值最终需要的值和m是无关的所以再次我们再同时除以M

 

下面是我们的实现代码,这里我们和简单线性回归的实现的区别在于,上面的是单一的数,而这里我们实现的则是多个向量代入的算法,为了验证我们的算法,同样的这里我们也自己随意写了一个方程,只不过为了简单演示,我们依然只放了一个参数,与上面简单线性方程不同的是这里我们放入的参数是一个列向量。

x = 2*np.random.random(size=100)
y = x * 3. + 4. +np.random.normal(size = 100)

X = x.reshape(-1,1)#这里为了验证我们这个算法是适用于向量的所以这里我们把原来的数组转换成一个列
def J(theta,x_m,y):#损失函数
    return np.sum((y-x_m.dot(theta))**2)/len(x_m)
def dJ(theta,x_m,y):#损失函数求导
    res = np.empty(len(theta))
    res[0] = np.sum(x_m.dot(theta)-y)
    for i in range (1,len(theta)):
        res[i] = np.sum((x_m.dot(theta)-y).dot(x_m[:,i]))
    return res *2 /len(x_m)
def Gradientdescent(x_m,y,initial_theta,eta,epsilon = 0.000000001):#这里只是多了一个X和y
    theta = initial_theta 
    iteration = 1
    while True:
        gradient = dJ(theta,x_m,y)
        last_theta = theta
        theta = theta - eta * gradient
        if(abs(J(theta,x_m,y)-J(last_theta,x_m,y)) < epsilon):
           break
    return theta
x_m = np.hstack([np.ones((len(X),1)),X])#这里我们需要给我们的参数加一列1因为我们第一个x默认是1
initial_theta = np.zeros(x_m.shape[1])#这里我们创建一个空的数组来放我们的theta
eta = 0.00005#学习率这里实验的话大家自行调整。
theta = Gradientdescent(x_m,y,initial_theta,eta)





当然这个由于是为了方便我们理解,所以最后的结果可能相比于Scikit-learn中封装的算法运算出来的效果要差一些。这个原因有很多,当然超参数学习率eta也是一个重要元素,大家可以多尝试几个参数试一试。

在这里,我们其实还可以对上面的算法进行简单的优化,观察我们上面的推导式可知,我们这里其实可以再优化,对于第一项如果我们如果添加一个X0我们将可以把这个式子变成一个矩阵乘法,而不是像上面的哪个算法一样对于第一项我们要分开进行处理。

这样我们在对损失函数求导时也可以简化为如下

def dJ(theta,x_m,y):
#    res = np.empty(len(theta))
#    res[0] = np.sum(x_m.dot(theta)-y)
#    for i in range (1,len(theta)):
#        res[i] = np.sum((x_m.dot(theta)-y).dot(x_m[:,i]))
#    return res *2 /len(x_m)
    return x_m.T.dot(x_m.dot(theta)-y)*2./len(x_m)

在使用我们上面的梯度下降法的同时,有的同学可能也发现了,当我们的学习率也就是步长过小的时候,我们运行这个代码所花费的时间就越长,这是因为我们的算法在计算的时候是对于我们的每一个给出的参数都进行了矩阵的乘法,我们现在所说的梯度下降法也可以说是批量梯度下降法。不难想象,当参数的个数特别巨大的时候,所耗费的时间将会非常的长。为了减少我们的时间耗费,在这里我们再讲一种算法。

五.随机梯度下降算法

 

随机梯度 下降法是为了适应我们在面对大批量数据时所使用的一种用牺牲部分精度来减少时间花费的一种算法。我们先来简单的看一下它的思路。我们依然来看我们上面所提到的损失函数求导的函数。在最后计算时,我们不可避免的要将每一个向量与矩阵相乘然后求和,最后除以我们的数据数量求得平均值得出我们最后的参数值。这样花费的时间是巨大的,但是我们其实完全可以随机取其中的一条数据来与矩阵相乘,这样我们可以省去求平均和求和的过程。得到梯度后,我们再随机取一条数据来与之进行调整。如下图

 

随机下降法的随机也告诉了我们它最大的问题就是随机,也就时说它的梯度有可能不是下降最快的方向,更有甚者它的下降法向有可能时相反的因为我们的参数取值的随机性导致了这一问题,进而,我们也不难想象,它在下降的过程中有可能会越过最低点,也有可能会达不到我们要求的最低点。这也就我们开头所讲到的,牺牲部分精度的原因,但是我们有方法可以取尽量减小我们的精度的损失,那就是对我们的学习率进行相应的调整以适应我们每一次的下降。所以在随机梯度下降的算法中我们的学习率不再是一个固定值,而为了让它更加接近最低点,我们调整学习率让它也就是步长逐渐减小适应我们的下降。我们在这次的算法中设置一个计数器n,让我们的学习率等于1/n,这样的学习率是下降的但是在循环次数较小的时候,下降的有些过快所以我们再给分母添加一个固定值b,同时为了在计数器过大时下降幅度较小,所以我们把分母替换成另一个固定值a,当然,这两个值也就理所当然的成了我们这个算法中的超参数。

下面我们来看一下这一部分的代码

import numpy as np
import matplotlib.pyplot as plt
k = 100000
x = 2*np.random.random(size=k)
X = x.reshape(-1,1)
y = x * 4. + 3. +np.random.normal(0,3,size = k)
def J(theta,x_m,y):
    return np.sum((y-x_m.dot(theta))**2)/len(x_m)
def dJ(theta,x_m_i,y_i):
    return x_m_i.T.dot(x_m_i.dot(theta)-y_i)*2.#这里我们不用求平均数因为时随机的一条
def ST_Gradientdescent(iteration,x_m,y,initial_theta):
#这里的参数我们去掉自动变化的学习率
    a = 5
    b = 50
    theta = initial_theta 
    def learnrat(iteration):#这是学习率的计算
        return a/(b+iteration)
    theta = initial_theta
    for n_iteration in range(iteration):#这里我们设置的循环次数也就是随机次数
        rand_i = np.random.randint(len(x_m))#这里我用随机数产生我们要用的条数
        gradient = dJ(theta,x_m[rand_i],y[rand_i])
        theta = theta - learnrat(iteration) * gradient
    return theta
x_m = np.hstack([np.ones((len(X),1)),X])
initial_theta = np.zeros(x_m.shape[1])
theta = ST_Gradientdescent(len(x_m)//3,x_m,y,initial_theta)

这里我简单的对我的代码做一下解释,前面部分的和简单线性回归的并无太大区别,我们的区别主要在于我们的判断跳出方式,原来我们有两层判断 一个是判断我们的迭代次数是否达到上限,另一个则是我们判断我们的损失函数的值是否有在变化达到我们的理想值而跳出。在随机梯度下降中,我们的算法有了稍许不同在于我们不是一直在梯度下降,我们的损失函数有可能比前一个的值更大,所以我们更改了我们的判断依据,在这里我们手动设置随机次数以此来控制我们的整个算法。这就是我们常用的随机梯度算法的大致实现过程。

六.scikit-learn中的随机梯度下降算法使用

当然这里我们是为了方便理解而总结, 当然实际在对数据操作时,我们肯定还是会选择使用有着完整封装的库里面写好的算法(当然时调包舒服拉~小声BB)而且库里封装的类有着更为完善的函数让我们使用起来更加直观。这里我也就大概提一下在sickit-learn中的随机梯度下降算法的使用。这里只是简单的提一下。

from sklearn.liner_model import SGDRegressor
sgd_reg = SGDRegressor(n_iter = 100)#生成对象 赋一个随机次数
sgd_reg .fit(x_train,y_train)#训练 也就是迭代
sgd_reg.score(x_test,y_test)#这是一个肥肠好用的R2查看我们准确率的函数 但是这里就不细说了

七.总结

梯度下降算法虽然不是一种机器学习算法,但是却是一种非常有效的优化算法,它可以帮助我们处理线性回归问题中一系列的优化问题,算是一个非常常用的算法, 在这里我只是总结并推到了一下该算法,对于萌新的我以及同样萌新的童鞋应该是可以有着加深理解的作用,这里我只是介绍了两种梯度下降算法,一种时批量的一种是随机,各有各的优缺点,当然还有一种兼具两者特性的梯度下降算法也就是小批量下降算法,这里我没有提及,我会在后面的博文继续总结。希望各位看过的萌新大佬们多多支持,共同进步。

ps:有一部分图来自网络,感谢这些大佬们的指引。

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值