机器学习--梯度下降法

注意,可能会有一些错误,欢迎大家批评指正QWQ。

1. 基本原理

假设有一个函数,想要寻找的它的极值,该如何得出呢?
最直观上可以直接使用公式得出。

但是有时用公式计算的速度可能并不快,比如使用正规方程解出当前样本的最佳拟合直线,这个的计算量就很大,而且有时有些函数可能没法找到一个合适的公式来直接得出,这时就需要用到梯度下降法了。

高等数学中有这么一句话,沿着梯度方向下降/上升的速度最快。
举个例子,比如二次函数,随意找一个点,沿着它的梯度方向(这里也就是导数)肯定是最快的。
在这里插入图片描述
同时,可以发现,如果沿着梯度的反方向前进的话就可以逐渐的逼近极值。

所以就可以大概的理解梯度下降的思路了,对于一个、可导函数想要寻找它的极值,就可以从任意一点,然后沿着梯度的反方向移动,逐渐的逼近极值,最终找到极值。

2. 梯度下降法

接下来以线性回归为例实现一下梯度下降法

首先先回顾一下线性回归要求什么?
对于一个n维的样本空间和m个样本,我们需要拟合出一个直线
y ^ = θ 0 + x 1 θ 1 . . + x n θ n \hat y=\theta_0+x_1\theta_1..+x_n\theta_n y^=θ0+x1θ1..+xnθn
使 ∑ i = 1 m ( y ^ − y i ) 2 \sum\limits_{i=1}^m(\hat y-y_i)^2 i=1m(y^yi)2最小。
y ^ \hat y y^代入算式中得到 ∑ i = 1 m ( θ 0 + X 1 i θ 1 . . + X n i θ n − y i ) 2 \sum\limits_{i=1}^m(\theta_0+X^{i}_1\theta_1..+X^{i}_n\theta_n-y_i)^2 i=1m(θ0+X1iθ1..+Xniθnyi)2
设他为 J ( θ 0 . . . θ n ) J(\theta_0...\theta_n) J(θ0...θn)得到一个函数,也叫损失函数
J ( θ 0 . . . θ n ) = ∑ i = 1 m ( θ 0 + X 1 i θ 1 . . + X n i θ n − y i ) 2 J(\theta_0...\theta_n)=\sum\limits_{i=1}^m(\theta_0+X^{i}_1\theta_1..+X^{i}_n\theta_n-y_i)^2 J(θ0...θn)=i=1m(θ0+X1iθ1..+Xniθnyi)2
注意这里X和y都是已知的,只有 θ i \theta_i θi是未知的,所以他是一个关于 θ \theta θ的n + 1元函数。
而我们的目标就是让这个函数尽可能的小。

2.1 求某个点的损失函数值

注意,在下面的 sum表示对向量/矩阵中的所有元素进行求和

首先来设一个列向量 θ = ( θ 0 , θ 1 . . , θ n ) T \theta=(\theta_0, \theta_1..,\theta_n)^T θ=(θ0,θ1..,θn)T
然后我们把样本矩阵加上一列1,即变成
X = ( 1 x 1 1 . . . x 1 n 1 x 2 1 . . . x 2 n . . . 1 x m 1 . . . x m n ) X = \begin{gathered} \begin{pmatrix} 1 & x_1^1 & ... & x_1^n\\ 1 & x_2^1 & ... & x_2^n\\ ...\\ 1 & x_m^1 & ... &x_m^n \end{pmatrix} \quad \end{gathered} X=11...1x11x21xm1.........x1nx2nxmn
这样就可以方便表示参数 θ 0 \theta_0 θ0

接着把所有的预期值写成一个向量 y = ( y 1 , y 2 . . . , y m ) T y = (y_1,y_2...,y_m)^T y=(y1,y2...,ym)T
然后就可以把损失函数写成这个样子
J ( θ 0 . . . θ n ) = s u m [ ( X ∗ θ − y ) 2 ] J(\theta_0...\theta_n)=sum[(X*\theta - y)^2] J(θ0...θn)=sum[(Xθy)2]
这样就方便求出函数的值了,但这求出的是所有样本的损失的和,所以可以除一个m来表示单个的损失
J ( θ 0 . . . θ n ) = 1 m s u m [ ( X ∗ θ − y ) 2 ] J(\theta_0...\theta_n)=\frac{1}{m}sum[(X*\theta - y)^2] J(θ0...θn)=m1sum[(Xθy)2]

np.sum((X.dot(theta) - y) ** 2) / len(X)
2.2 求某个点的梯度

对于梯度,很自然就有这个梯度计算公式。
D = ( ∂ J ( θ 0 . . θ n ) ∂ θ 0 ∂ J ( θ 0 . . θ n ) ∂ θ 1 . . . ∂ J ( θ 0 . . θ n ) ∂ θ n ) D = \begin{gathered} \begin{pmatrix} \frac{\partial J(\theta_0..\theta_n)}{\partial \theta_0}\\ \frac{\partial J(\theta_0..\theta_n)}{\partial \theta_1}\\ ...\\ \frac{\partial J(\theta_0..\theta_n)}{\partial \theta_n} \end{pmatrix} \quad \end{gathered} D=θ0J(θ0..θn)θ1J(θ0..θn)...θnJ(θ0..θn)
求出就可以得到
D = 1 m ( 2 ∗ ∑ i = 1 m ( θ 0 + X 1 i θ 1 . . + X n i θ n − y i ) 2 ∗ ∑ i = 1 m ( θ 0 + X 1 i θ 1 . . + X n i θ n − y i ) ∗ X 1 ( i ) . . . 2 ∗ ∑ i = 1 m ( θ 0 + X 1 i θ 1 . . + X n i θ n − y i ) ∗ X n ( i ) ) D =\frac{1}{m} \begin{gathered} \begin{pmatrix} 2*\sum_{i = 1}\limits^m(\theta_0+X^{i}_1\theta_1..+X^{i}_n\theta_n-y_i)\\ 2*\sum_{i = 1}\limits^m(\theta_0+X^{i}_1\theta_1..+X^{i}_n\theta_n-y_i)*X_1^{(i)}\\ ...\\ 2*\sum_{i = 1}\limits^m(\theta_0+X^{i}_1\theta_1..+X^{i}_n\theta_n-y_i)*X_n^{(i)} \end{pmatrix} \quad \end{gathered} D=m12i=1m(θ0+X1iθ1..+Xniθnyi)2i=1m(θ0+X1iθ1..+Xniθnyi)X1(i)...2i=1m(θ0+X1iθ1..+Xniθnyi)Xn(i)
观察可以发现,只有D的第一个元素形式有点不同。
X 0 ( i ) = 1 X_0^{(i)}=1 X0(i)=1
D = 1 m ( 2 ∗ ∑ i = 1 m ( θ 0 + X 1 i θ 1 . . + X n i θ n − y i ) ∗ X 0 ( i ) 2 ∗ ∑ i = 1 m ( θ 0 + X 1 i θ 1 . . + X n i θ n − y i ) ∗ X 1 ( i ) . . . 2 ∗ ∑ i = 1 m ( θ 0 + X 1 i θ 1 . . + X n i θ n − y i ) ∗ X n ( i ) ) D =\frac{1}{m} \begin{gathered} \begin{pmatrix} 2*\sum_{i = 1}\limits^m(\theta_0+X^{i}_1\theta_1..+X^{i}_n\theta_n-y_i)*X_0^{(i)}\\ 2*\sum_{i = 1}\limits^m(\theta_0+X^{i}_1\theta_1..+X^{i}_n\theta_n-y_i)*X_1^{(i)}\\ ...\\ 2*\sum_{i = 1}\limits^m(\theta_0+X^{i}_1\theta_1..+X^{i}_n\theta_n-y_i)*X_n^{(i)} \end{pmatrix} \quad \end{gathered} D=m12i=1m(θ0+X1iθ1..+Xniθnyi)X0(i)2i=1m(θ0+X1iθ1..+Xniθnyi)X1(i)...2i=1m(θ0+X1iθ1..+Xniθnyi)Xn(i)
可以发现最后一列 X 0 ( i ) . . . X 1 ( i ) . . . X_0^{(i)}...X_1^{(i)}... X0(i)...X1(i)...就是 X X X矩阵的某一列。于是就可以想到这其实也是一个矩阵的乘法
于是很容易得到 D = 2 m ∗ X T ∗ ( X ∗ θ − y ) D = \frac{2}{m}*X^T*(X*\theta - y) D=m2XT(Xθy)
然后就求出D了

X.T.dot(X.dot(theta) - y) * 2 / len(X)
2.3 梯度下降的过程

首先需要确定几个参数,一个是初始的 θ \theta θ一个是最大的循环次数 m a x l o o p maxloop maxloop(也就是向着极值移动的次数),还有一个就是精度 e p s eps eps,最后一个就是步长 s e p sep sep(每次向着极值移动的距离)。

  1. 初始化计数器
  2. 计算当前位置( θ \theta θ)的梯度
  3. θ \theta θ向着梯度反方向移动 s e p sep sep
  4. 计算移动后的 θ \theta θ和移动前的 θ \theta θ,算他们差的绝对值,如果小于eps就直接结束并且当前的 θ \theta θ就是答案,某则继续
  5. 把计数器加一,如果当前计数器等于最大循环次数就直接退出,否则更新 θ \theta θ然后回到1
2.4 整体代码实现
import numpy as np


class LinearRegression:
    def __init__(self, X, y):
        self.__X = np.hstack([np.ones((len(X), 1)), X])# 这里直接加上一列
        self.__y = y
        self.theta_ = None

    def DLossFun(self, theta: np.array):
        return self.__X.T.dot(self.__X.dot(theta) - self.__y) * 2 / len(self.__X)

    def GetLossValue(self, theta: np.array):
        return np.sum((self.__y - self.__X.dot(theta)) ** 2) / len(self.__X)
        
    def fitByGradintDescent(self, init: np.array, maxloop: int=10000, sep: float=0.001, eps: float=1e-6):
        tmp = init
        cnt = 0 # 计数器
        while True:
            next = tmp - self.DLossFun(tmp) * sep # 计算接下来前到的地方
            if abs(self.GetLossValue(next) - self.GetLossValue(tmp)) < eps:
                self.theta_ = next
                break
            cnt += 1 # 计数器加一
            if cnt > maxloop: return # 大于最大循环次数就退出 
            tmp = next # 更新tmp

3. 一些细节问题

首先,关于几个参数,有一些需要注意的问题

  • sep
    对于sep如果设置过大,很有可能会导致偏移极值,而不是靠近极值。在这里插入图片描述
    如果过小就会导致靠近的速度过慢。
  • eps
    eps是精度也就是当当前的点逐渐逼近极值时逐渐的前近距离越来越小,直到值的变化小于eps时就可以判定达到极值,显然eps越小就越接近极值
3.1 数据规模与归一化

使用梯度下降法时,有时数据本身很大,就会导致在计算时速度过慢,甚至导致浮点数溢出。此时就需要对数据进行归一化

4. 随机梯度下降

4.1 原理

对于之前的梯度下降,每次计算梯度时,都十分耗费时间,假设一个m*n的样本那么每次计算的复杂度就是 O ( ( n + 1 ) ∗ ( m + 1 ) ∗ n ∗ m ) O((n + 1) * (m + 1) * n * m) O((n+1)(m+1)nm)大概就是 O ( n 2 m 2 ) O(n^2m^2) O(n2m2)级别的,当样本数和特征数比较大时仍然很费时间。

观察梯度向量:
D = 2 m ( ( θ 0 + X 1 k θ 1 . . + X n k θ n − y k ) ∗ X 0 ( k ) ( θ 0 + X 1 k θ 1 . . + X n k θ n − y k ) ∗ X 1 ( k ) . . . ( θ 0 + X 1 k θ 1 . . + X n k θ n − y k ) ∗ X n ( k ) ) D =\frac{2}{m} \begin{gathered} \begin{pmatrix} (\theta_0+X^{k}_1\theta_1..+X^{k}_n\theta_n-y_k)*X_0^{(k)}\\ (\theta_0+X^{k}_1\theta_1..+X^{k}_n\theta_n-y_k)*X_1^{(k)}\\ ...\\ (\theta_0+X^{k}_1\theta_1..+X^{k}_n\theta_n-y_k)*X_n^{(k)} \end{pmatrix} \quad \end{gathered} D=m2(θ0+X1kθ1..+Xnkθnyk)X0(k)(θ0+X1kθ1..+Xnkθnyk)X1(k)...(θ0+X1kθ1..+Xnkθnyk)Xn(k)
可以发现,前面乘了 1 m \frac{1}{m} m1,而后面又是对m个样本求和。
也就是说可以对直接让m次求和把 1 m \frac{1}{m} m1抵消了。
当然这样求出来的就不是梯度了,也就是不是沿着梯度方向(甚至方向相反),但这样每次随机的取出一个样本来计算就可以在一定程度上代替梯度。
D = 2 ∗ X ( k ) ⋅ ( X ( k ) ∗ θ − y k ) D =2*X^{(k)} \cdot(X^{(k)}*\theta - y_k) D=2X(k)(X(k)θyk)
值得注意的是,这里 ⋅ \cdot 表示点乘

这样,复杂度就大大地降低,下降到了 O ( n ) O(n) O(n)级别。

但是,在这样随机的选取一个样本然后计算的过程中还是会有一些问题。

  1. 每次都随机不能保证能把样本都取一遍
  2. 每次前进的距离不能都相同
  3. 不能以两次的值的差的距离作为结束条件

针对于1,可以把样本进行打乱,然后再对其进行遍历取值。
针对于2,这里就需要进行设置一个函数来控制每次前进的长度,可以想到,如果每次前进的步长都一样的话及时到达终点也有可能在跑出去,也就是每次前进的步长应该越来越小。
也就是随着梯度下降的过程进行,每次前进的步长越来越小,整体前进的方向越来越稳定。
针对于3,则需要设置一个次数,表示样本被遍历的次数,遍历完后就默认已经达到了极值。

4.2 代码

求D的部分

def StochasticDLossFun(self, theta: np.array, index: int):
    tmp = self.__X[index].reshape(-1, 1) * (self.__X[index].dot(theta) - self.__y[index])
    return tmp

随机梯度下降部分
length表示样本数,t1表示步长函数的分母上的常数,t0表示分子上的常数,n_iters表示样本被遍历的次数。

def search(self, init_theta: np.array, length: int, t1: float=50.0, t0: float=5.0, n_iters:int=5):
    tmp = init_theta
    cnt = 0
    def temper(x: int):
        return t0 / (x + t1)

    for i in range(n_iters):
        indexes = np.arange(length)
        np.random.shuffle(indexes)
        for j in indexes:
            tmp = tmp - self.DFun(theta=tmp, index=j) * temper(cnt)
            cnt += 1
    self.ans_ = tmp
    return

猜你喜欢

转载自blog.csdn.net/qq_36102055/article/details/109060373
今日推荐