机器学习(课堂笔记)Day05:梯度下降法

目录

0x00 什么是梯度下降法

0x01 模拟实现梯度下降法

0x02 线性回归问题中的梯度下降法

0x03 实现线性回归中的梯度下降法

0x04 梯度下降法的向量化 和数据标准化

0x05 随机梯度下降法 Stochastic Gradient Descent

0x06 scikit-learn中的随机梯度下降法

0x07 如何确定梯度计算的准确性?调试梯度下降法

0x08 总结:


0x00 什么是梯度下降法

梯度下降法不是一个机器学习算法,而是一种基于搜索的最优化方法,用于求损失函数的最小值,与之相对还有梯度上升法,用于求效用函数的最大值。

一元函数中梯度就是导数,多元函数中梯度是对各元求偏导组成的列向量。

下面以一元函数为例,详细分析下一元函数的梯度下降法:

任取一点,如果该点的导数不为0,那么说明该点一定不是极值点

导数就是因变量沿x轴的变化率,变化率为正说明因变量沿该x轴正方向走会增加

变化率为负说明因变量沿x正方向走会减小

如果把变化率看成一个一维向量(从原点出发指向 x轴上对应坐标的一条向量),那么变化率指向的方向(取决于变化率的符号) 就是因变量增加的方向

那么变化率向量的反方向,就是因变量减少的方向

下图中导数为负值,说明J增大的方向是x轴的负方向,即 J(θ+ dJ/dθ) > J(θ)

那么,J(θ-η *dJ/dθ) < J(θ) ,即该点的x值减去该点的导数值 必然是向着函数值减小的方向移动的。至于移动的幅度多大,由η 来决定。η  就称为学习率,如果该值取得过大,可能导致移动幅度过大,从曲线的一头一口气移动到另一头。该值是梯度下降法的一个超参数,我们可以通过网格搜索来求得一个最佳的学习率

梯度下降法的过程可以理解为一个小球顺着山坡向下滑,这个滑动一次的步幅就由η 来决定

注意:

并不是所有函数都有唯一的极值点,例如:

如果我们只选择一次初始点,通过梯度下降法得到可能只是局部最优解,而不是全局最优解

解决方案:

多次运行,随机化初始点

所以,梯度下降法的初始点也是一个超参数

注:线性回归问题的损失函数具有唯一最优解,不需要多次运行,寻找初始点

0x01 模拟实现梯度下降法

选取0.0点为theta的初始值。然后不断循环,求导数,让theta向梯度的反方向移动即可

如果学习率选择的太大,可能导致如下情况:

0x02 线性回归问题中的梯度下降法

将梯度下降法扩展到高维:(多元函数求极值问题)

高维度下自变量 theta 不在只是一个值, 而一个向量 = (theta0,theta1,...)

复习下高数中的相关定义:

梯度▽J = J对各theta 求偏导组成的向量

函数沿l方向的变换率 = 梯度  点乘  l方向的单位方向向量el  = |梯度| * |el|*夹角余弦值= 梯度的模 * 夹角余弦值 (证明过程见高数教材)

el = (cosα,cosβ,...)其中α为l方向与第一个坐标轴的夹角,β为l方向与第二个坐标轴的夹角...

当夹角余弦值值为1 的时候,即l方向为梯度向量的方向时,函数增加最快

当夹角余弦值为-1的时候,即l方向为梯度向量相反的方向时,函数减少最快

当夹角余弦值为0的时候,即l方向与梯度向量方向正交的时候,函数的变换率为0

线性回归问题中,我们要求的是损失函数的最小值,损失函数如下:

 

那么我们首先需要需要求出损失函数的梯度向量即可,思路很简单,对该多元函数的各元求偏导,然后组合成一个列向量即可,具体求解过程如下:

其实直接用上式作为损失函数并不是很好。因为它的值和m的大小有关,其中m为训练集的元素个数,我们可以将上面的式子除以m,减弱m的影响。

即将误差平方的均值作为损失函数:

接下来我们只需要:

让自变量组成的向量沿着梯度的反方向移动,便可以逐步达到因变量的极小值

让自变量组成的向量沿着梯度方向移动,便可以逐步达到因变量的极大值

0x03 实现线性回归中的梯度下降法

注意下式中对求和公式的转化:

将需要求和的每个样本,叠加成一个向量。

然后用np.sum(该向量) 将该向量中的元素加起来即可

封装线性回归算法:

'''
Author: your name
Date: 2020-11-12 14:21:32
LastEditTime: 2020-11-14 16:04:17
LastEditors: Please set LastEditors
Description: In User Settings Edit
FilePath: /ML/playML/LinearRegression.py
'''
import numpy as np
from .metrics import r2_score


class LinearRegression:
    def __init__(self):
        self.coef_ = None  # 系数
        self.interception = None  # 截距
        self._theta = None  # θ

    def fit_normal(self, X_train, y_train):
        assert X_train.shape[0] == y_train.shape[0],\
            "X_train中的样本数量和y_train中的标记数量必须相等"
        X_b = np.hstack([np.ones((len(X_train), 1)), X_train])
        # 给X_train左边加上1列,全是1 => np.ones( (len(X_train),1) ) 行数和X_train一样,列数为1
        # 直接套用公式
        self._theta = np.linalg.inv(X_b.T.dot(X_b)).dot(X_b.T).dot(y_train)
        # 复习一下:np.linalg.inv() 求矩阵的逆
        self.interception = self._theta[0]  # 截距就是θ的第一个元素
        self.coef_ = self._theta[1:]
        return self

    def fit_gd(self, X_train, y_train, eta=0.01, n_iters=1e4):
        def J(theta, X_b, y):
            try:
                return np.sum((y - X_b.dot(theta))**2)/len(y)
            # theta 向量 和 X 列向量 相乘 就是预测结果组成的向量
            except:
                return float('inf')

        def dJ(theta, X_b, y):  # J 对theta中的每个元素求偏导数
            res = np.empty(len(theta))
            res[0] = np.sum(X_b.dot(theta) - y)
            for i in range(1, len(theta)):
                res[i] = np.sum((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(theta, X_b, y)-J(last_theta, X_b, y)) < epsilon):
                    break
                i_iter += 1
            return theta
        X_b = np.hstack([np.ones((len(X_train),1)),X_train])
        initial_theta = np.zeros(X_b.shape[1]) #等于Xb的列数
        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

    def predict(self, X_predict):
        assert self.interception is not None and self.coef_ is not None,\
            "在predict之前请先fit"
        X_b = np.hstack([np.ones((len(X_predict), 1)), X_predict])
        y_predict = X_b.dot(self._theta)
        return y_predict

    def score(self, X_test, y_test):
        y_predict = self.predict(X_test)
        return r2_score(y_test, y_predict)

    def __repr__(self):
        return "LinearRegression()"

测试:

0x04 梯度下降法的向量化 和数据标准化

上述的梯度公式还可以进一步转化:

即可以将原来的求和公式转化为两个矩阵相乘的形式,这个转化的过程就称为向量化

推导过程如下:

=   * 

  1 x m 维度 * m x(n+1) = 1 x(n+1) 一行n+1列,是一个行

向量,但是一般情况下我们使用列向量来表示梯度

所以,我们需要对该结果再进行一次转置,得到:

梯度 =  

友情提醒:X的上标i 是样本的标号,X的下标是样本特征的标号

def dJ(theta,X_b,y): #J 对theta中的每个元素求偏导数
#     res = np.empty(len(theta))
#     res[0] =np.sum(X_b.dot(theta) - y)
#     for i in range(1,len(theta)):
#         res[i] =np.sum((X_b.dot(theta) - y).dot(X_b[:,i]))
#     return res * 2 / len(X_b)
    return X_b.T.dot(X_b.dot(theta)-y) * 2. / len(X_b)

数据归一化:

梯度下降法的优势:

相比于正规方程解的方法,梯度下降法的时间复杂度更小,速度更快

0x05 随机梯度下降法 Stochastic Gradient Descent

我们之前使用的梯度下降法,每次训练/更新 参数 都需要全部样本集参与计算,这种梯度下降法称为“批量梯度下降法(Batch Gradient Descent)”。这种梯度下降法最大的缺点便是:当我们需要训练参数或者更新参数时,不得不使用全部的数据集,计算量巨大。

我们知道:

梯度方向 是 函数增大最快的方向

梯度的反方向是函数值减小最快的方向

那么如果我们随机选取一个样本,从该样本中训练中出一个方向,向该方向搜索,是否也能得到最小值呢?

如果只考虑一个样本,那么损失函数就变成了:即该样本和拟合曲线的预测值之间的差距的平方

我们想办法求该损失函数的最小值。只要我们循环足够多次,且每次循环都随机取一个样本,让该样本和拟合曲线之间差距最小,最终一定能达到整个样本 和 拟合曲线之前的差距最小。

换句话说:我们不断随机取样本点,让拟合线向这些点逼近,那么最终得到结果便是拟合线 更好的拟合所有样本点。

这种方法便称为随机梯度下降法

求该损失函数的梯度:

 

友情提醒:X的上标i 是样本的标号,X的下标是样本特征的标号

也就是说原来我们是取了所有的样本,求整体的损失函数的最小值

现在我们只是随机取出一个样本,求该单个样本损失函数的最小值,然后不断循环,让每个样本的损失函数最小,那么整体的损失函数就达到了最小值。

对随机梯度下降法来说,学习率的选取非常重要,学习率应该随着循环次数的增加而减少

比如:

一位直接取倒数,可能导致学习率变化太快,例如:

i_iters 从1 变到2 ,学习率就从1 变到了 0.5

i_iters 从10000 变道10001,学习率才下降了1/10000

前后学习率变化幅度差别太大了

所以可以给循环次数再加一个常数,然后取倒数,例如该常数取50

i_iters 从1到2,学习率 从1/51 到1/52

i_iters 从10000 到10001 ,学习率从 1/10050 到 1/10051

同样我们的分子也可以取a,不一定取1

这样,a 和b 的值就成为了该算法的两个超参数

以上方法,模拟了退火的思想:

打造钢铁,火焰的温度随时间增大而减小

实现随机梯度下降法:

封装成函数:

    #随机梯度下降法
    def fit_sgd(self,X_train,y_train,n_iter=5,t0=5,t1=50):
        #计算梯度方向
        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)
            #n_iter表示需要将所有的样本循环几遍
            for cur_iter in range(n_iters*m):
                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.zeros(X_b.shape[1]) #等于Xb的列数
        self._theta = sgd(X_b,y_train,initial_theta,n_iter)
        self.interception = self._theta[0]
        self.coef_ = self._theta[1:]
        return self

0x06 scikit-learn中的随机梯度下降法

0x07 如何确定梯度计算的准确性?调试梯度下降法

调试思路:

用一种更简单更可靠的方法去求偏导,进而得到梯度。(虽然这种方法的时间复杂度比较高)

然后用我们数学推导的公式去求梯度,然后用一个较小的数据集测试两者得到的结果。如果得到的结果差不多,就说明我们推导的梯度求导公式没有问题。

下面来介绍这种简单可靠的求偏导的思路:

J(theta)函数的导数近似等于 theta前后各取一点得到的割线的斜率

低维情况下:

推广到高维情况下,求偏导

程序实现:

0x08 总结:

之前我们讨论了:

批量梯度下降法(Batch Gradient Descent): 一次拟合所有的样本

随机梯度下降法(Stochastic Gradient Descent):一次只随机拟合一个样本,拟合多次达到拟合所有样本的效果

那么能不能将两者综合起来呢?这便是小批量梯度下降法。感兴趣可以自己实现这种梯度下降法

再次强调一下:梯度下降法不是一个机器学习领域的算法,而是一种基于搜索的最优化方法,我们可以用它来最小化一个损失函数

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值