机器学习基础-4.梯度下降法

一、梯度下降法

1.梯度下降法概念

梯度下降法本身不是一个算法,而是一种基于搜索的最优化方法,作用是最小化一个损失函数,这个损失函数不能够或者很难通过公式推导的方式得出一个数学解,所以需要用基于搜索的方法一步一步得到最终解。相对的有梯度上升法,即最大化一个效用函数,思想是完全一样的。





梯度下降法只能找到局部的最优解,但是对于一般情况,可能存在多个极值点。如下图。


解决的方法:多次运行,随机初始点。由此可见,这个初始点也是一个超参数。

2.编程模拟梯度下降法

import numpy as np
import matplotlib.pyplot as plt
plot_x = np.linspace(-1,6,141)
plot_y = (plot_x-2.5)**2-1 #这里给出我们的函数,接下来用梯度下降法求得最小值
plt.plot(plot_x,plot_y)
plt.show()

def dj(theta):
    return 2*(theta-2.5)
def j(theta):
    return (theta-2.5)**2-1
theta =0.0
eta = 0.1
epsilon = 1e-8
while True:
    gradient = dj(theta)
    last_theta = theta
    theta = theta - eta*gradient
    if (abs(j(theta)-j(last_theta))<epsilon):
        break
print(theta)
print(j(theta))

为了方便可视化,将上面代码可以修改如下。

def dj(theta):
    return 2*(theta-2.5)
def j(theta):
    return (theta-2.5)**2-1

theta =0.0
theta_history =[theta]
eta = 0.1
epsilon = 1e-8
while True:
    gradient = dj(theta)
    last_theta = theta
    theta_history.append(theta)
    theta = theta - eta*gradient
    if (abs(j(theta)-j(last_theta))<epsilon):
        break
plt.plot(plot_x,j(plot_x))
plt.plot(np.array(theta_history),j(np.array(theta_history)),color='red',marker='+')

可以清楚地看到下降的过程,开始时候下降比较快,这是因为斜率比较大的原因,可以通过len(theta_history)查看下降了多次得到最终的最优解。


通过调整eta值,查看效果。左图eta=0.01,右图eta=0.8,如果eta=1,将会直接出现值过大的异常。

二、多元线性回归梯度下降法

1.求解思路





为了使得梯度和样本的数目m无关,对函数进行调整。最终的损失函数即求MSE。


2.代码实现

import numpy as np
import matplotlib.pyplot as plt

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

X = x.reshape(-1,1)
plt.scatter(x,y)
plt.show()
def j(theta,X_b,y):
    try:
        return np.sum((y-X_b.dot(theta))**2)/len(X_b)
    except:
        return float('inf')
    
def dj(theta,X_b,y):
    res = np.empty(len(theta))
    res[0] = np.sum(X_b.dot(theta)-y)
    for i in range(1,len(theta)):
        res[i] = (X_b.dot(theta)-y).dot(X_b[:,i])
    return res * 2 / len(X_b)

def gradient_descent(X_b,y,initial_theta,eta,n_iters=1e4,epsilon=1e-8):
    theta = initial_theta
    i_iter = 0
    while i_iter< n_iters:
        gradient = dj(theta,X_b,y)
        last_theta = theta
        theta = theta-eta*gradient
       
        if(abs(j(last_theta,X_b,y)-j(theta,X_b,y))<epsilon):
            break
        i_iter +=1
    return theta
X_b = np.hstack([np.ones((len(x),1)),x.reshape(-1,1)])
initial_theta = np.zeros(X_b.shape[1])
eta = 0.01
theta = gradient_descent(X_b,y,initial_theta,eta)
theta

3.LinearRegression

创建LinearRegression将代码进行封装。

import numpy as np
from .metrics import r2_score
class LinearRegression:
    def fit_gd(self, X_train, y_train, eta=0.01, n_iters=1e4):
        """根据训练数据集X_train, y_train, 使用梯度下降法训练Linear Regression模型"""
        assert X_train.shape[0] == y_train.shape[0], \
            "the size of X_train must be equal to the size of y_train"

        def J(theta, X_b, y):
            try:
                return np.sum((y - X_b.dot(theta)) ** 2) / len(y)
            except:
                return float('inf')

        def dJ(theta, X_b, y):
            res = np.empty(len(theta))
            res[0] = np.sum(X_b.dot(theta) - y)
            for i in range(1, len(theta)):
                res[i] = (X_b.dot(theta) - y).dot(X_b[:, i])
            return res * 2 / len(X_b)
            
        def gradient_descent(X_b, y, initial_theta, eta, n_iters=1e4, epsilon=1e-8):

            theta = initial_theta
            cur_iter = 0

            while cur_iter < n_iters:
                gradient = dJ(theta, X_b, y)
                last_theta = theta
                theta = theta - eta * gradient
                if (abs(J(theta, X_b, y) - J(last_theta, X_b, y)) < epsilon):
                    break

                cur_iter += 1

            return theta

        X_b = np.hstack([np.ones((len(X_train), 1)), X_train])
        initial_theta = np.zeros(X_b.shape[1])
        self._theta = gradient_descent(X_b, y_train, initial_theta, eta, n_iters)

        self.intercept_ = self._theta[0]
        self.coef_ = self._theta[1:]

        return self

测试代码,最终的截距和系数和之前得到的一致。

from ml.LinearRegression import LinearRegression
linreg = LinearRegression()
linreg.fit_gd(X,y)
linreg.coef_
linreg.intercept_

三、线性回归中梯度下降法的向量化

1.向量化

对求得的梯度公式进行变形,对于第一项乘以值为1的X0。



根据线性代数交换两个项的位置,可以得到。


因此上面求dj的代码可以改变成如下。

def dJ(theta, X_b, y):
   return X_b.T.dot(X_b.dot(theta) - y) * 2. / len(X_b)

现在对代码进行实际使用,调用的数据为波士顿房价。

import numpy as np  
import matplotlib.pyplot as plt  
from sklearn import datasets  
boston = datasets.load_boston()  
x = boston.data #使用所有特征值  
y = boston.target  
x = x[ y < 50.0]   
y = y[ y < 50.0]  
from ml.model_selection import train_test_split  
x_train,x_test,y_train,y_test = train_test_split(x,y,seed=666)  
from ml.LinearRegression import LinearRegression  
reg = LinearRegression()  
reg.fit_gd(x_train,y_train)  
reg.coef_  
reg.intercept_  
reg.score(x_test,y_test) 

运行发现值溢出。原因是步长过大。

将eta值改为0.000001,由于eta变小,所以迭代次数需要相应的增加。

reg.fit_gd(x_train,y_train,eta=0.000001,n_iters=1e6)  

最终得到的分值为0.7541852353980764,依然没有达到最高的分数(根据线性回归博客中,得到的分数是0.8129)。主要原因是数据规模不在一个量级上。

2.归一化


归一化的方式采用knn博客中的方式,这里调用sklearn的归一化函数模块。

from sklearn.preprocessing import StandardScaler
standscaler = StandardScaler()
standscaler.fit(x_train)
x_train_standard = standscaler.transform(x_train)
reg = LinearRegression()
reg.fit_gd(x_train_standard,y_train)
x_test_standard = standscaler.transform(x_test)
reg.score(x_test_standard,y_test)

四、随机梯度下降法

梯度下降法每次求梯度都需要将所有的样本代入进行运算,当样本的数量较大时,耗费的时间将会很多,为了改进,提出了随机梯度下降法。它采用的公式如下,即每次只取1个样本进行运算。由于是随机的,所以不能保证每次损失函数都能是下降的趋势。


1.思想

这里对每次的步长进行动态的调整,使之随着迭代次数不断地减小,这里取a=5,b=50。【模拟退火】


2.代码实现

ef j(theta,X_b,y):  
    try:  
        return np.sum((y-X_b.dot(theta))**2)/len(X_b)  
    except:  
        return float('inf')  
      
def dj_sgd(theta,X_b_i,y_i):  
     return X_b_i.T.dot(X_b_i.dot(theta) - y_i) * 2. 
  
def sgd(X_b,y,initial_theta,n_iters=1e4):  
    t0=5
    t1=50
    def learning_rate(t):
        return t0/(t+t1)
    theta = initial_theta  
    i_iter = 0  
    while i_iter< n_iters: 
        rand_i = np.random.randint(len(X_b))
        gradient = dj_sgd(theta,X_b[rand_i],y[rand_i])  
        i_iter +=1  
    return theta

测试效果:

%%time
X_b = np.hstack([np.ones((len(X),1)),X])
initial_theta = np.zeros(X_b.shape[1])
theta = sgd(X_b,y,initial_theta,n_iters=len(X_b)//3)

,可以将结果和梯度下降法进行比较,发现快了非常多。


3.封装我们的代码,并做改进

import numpy as np
from .metrics import r2_score

class LinearRegression:
    def fit_sgd(self, X_train, y_train, n_iters=5, t0=5, t1=50): #这里的n_iters代表将样本循环看几遍
        """根据训练数据集X_train, y_train, 使用梯度下降法训练Linear Regression模型"""
        assert X_train.shape[0] == y_train.shape[0], \
            "the size of X_train must be equal to the size of y_train"
        assert n_iters >= 1

        def dJ_sgd(theta, X_b_i, y_i):
            return X_b_i * (X_b_i.dot(theta) - y_i) * 2.

        def sgd(X_b, y, initial_theta, n_iters, t0=5, t1=50):

            def learning_rate(t):
                return t0 / (t + t1)

            theta = initial_theta
            m = len(X_b)

            for cur_iter in range(n_iters):
                indexes = np.random.permutation(m)#将样本乱序,保证每个样本被看一遍,也就是模拟了随机梯度
                X_b_new = X_b[indexes]
                y_new = y[indexes]
                for i in range(m):
                    gradient = dJ_sgd(theta, X_b_new[i], y_new[i])
                    theta = theta - learning_rate(cur_iter * m + i) * gradient

            return theta

        X_b = np.hstack([np.ones((len(X_train), 1)), X_train])
        initial_theta = np.random.randn(X_b.shape[1])
        self._theta = sgd(X_b, y_train, initial_theta, n_iters, t0, t1)

        self.intercept_ = self._theta[0]
        self.coef_ = self._theta[1:]

        return self

4.sklearn中随机梯度下降

from sklearn.linear_model import SGDRegressor #因为是在线性回归中个,所以sklearn中的随机梯度下降只能运用在线性回归中
sgd_reg = SGDRegressor(n_iter=100) #可传入参数,代表将全部样本看100遍
%time sgd_reg.fit(X_train_standard,y_train)
sgd_reg.score(X_test_standard,y_test)

需要注意的是:sklearn中随机梯度更加优化和复杂,上面封装的代码只是为了学习基本的原理。

五、关于梯度公式的验证

有时候梯度求解可能会出错,但是运算过程不会报错。为了验证所求梯度公式的正确性,需要先用梯度求解的原始思想来求解梯度【即逼近的思想】,用这种方式求得的梯度代入数据进行运算,并记录结果。之后再将求得的梯度公式也用同样的数据进行运算,检查所得的结果是否和之前求得一致。如果一致,说明梯度求解的公式是正确的。


这里采用求导数最原始的思想,即逼近的思想,当epsilon逼近0的时候,就是在该处的导数。对于更高纬度的有如下形式。


对于某个方向上的导数公式如下。


import numpy as np  
import matplotlib.pyplot as plt  
np.random.seed(666)
x = np.random.random(size=(1000,10))
t_theta = np.arange(1,12,dtype=float)
x_b = np.hstack([np.ones((len(x),1)),x])
y = x_b.dot(t_theta)+np.random.normal(size=1000)
def j(theta,x_b,y):  
    try:  
        return np.sum((y-x_b.dot(theta))**2)/len(x_b)  
    except:  
        return float('inf')  
def dj_math(theta,x_b,y):
    return x_b.T.dot(x_b.dot(theta) - y) * 2. /len(x_b)  
def dj_debug(theta,x_b,y,epsilon=0.01):
    res = np.empty(len(theta))
    for i in range(len(theta)):
        theta_1 = theta.copy()
        theta_2 = theta.copy()
        theta_1[i] += epsilon
        theta_2[i] -= epsilon
        res[i] =(j(theta_1,x_b,y)-j(theta_2,x_b,y))/(2*epsilon)
    return res 
def gd (dj,x_b,y,initial_theta,eta,n_iters =1e4,epsilon=1e-8):
    theta = initial_theta
    cur_iter =0
    while cur_iter < n_iters:
        gradient = dj(theta,x_b,y)
        last_theta = theta
        theta = theta-eta*gradient
        if (abs(j(theta,x_b,y)-j(last_theta,x_b,y))<epsilon):
            break
        cur_iter +=1
    return theta

代码中,dj_debug代表调试梯度的代码,dj_math代表用数学推导得出的梯度。下面进行比较。可以发现调试时候的运算时间比较长,因此这仅仅是适用于验证公式正确性时候采用的。


  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值