多元线性回归与梯度下降法原理及公式推导(附Python代码)

以线性方程为例,设有线性方程:

y t r u e = θ 0 + θ 1 x 1 + θ 2 x 2 … + θ n x n y_{true} = \theta_0 + \theta_1x_1 + \theta_2x_2…+ \theta_nx_n ytrue=θ0+θ1x1+θ2x2+θnxn

如果现在我们手头上已经有了N组数据,其中第 i i i组数据为 [ x i , 1 x i , 2 x i , 3 … x i , n ] [x_{i,1} x_{i,2} x_{i,3}…x_{i,n}] [xi,1xi,2xi,3xi,n]和其对应的 y i , t r u e y_{i,true} yi,true

我们想要通过这N组数据,估计出上述方程中的 θ 0 \theta_0 θ0 θ n \theta_n θn

现在问题转化为:

我们想求出一组 θ ^ \hat{\theta} θ^,使得这组 θ ^ \hat{\theta} θ^无限逼近上述方程中的 θ \theta θ,又因为真实的 θ \theta θ是未知的,我们无法直接比较两者的误差关系,所以我们引入Lose Function即误差函数来描述我们求得的 θ ^ \hat{\theta} θ^到底准确与否。

有:

L o s e = J = 1 N ∑ i = 1 N ( y i , t u r e − y i ^ ) 2 Lose = J = \frac{1}{N}\sum^{N}_{i = 1}(y_{i, ture} - \hat{y_i})^2 Lose=J=N1i=1N(yi,tureyi^)2

又因为,若设 x 0 = 1 x_0 = 1 x0=1有:

y ^ = θ 0 ^ x 0 + θ 1 ^ x 1 + θ 2 ^ x 2 … + θ n ^ x n \hat{y} = \hat{\theta_0}x_0 + \hat{\theta_1}x_1 + \hat{\theta_2}x_2 … + \hat{\theta_n}x_n y^=θ0^x0+θ1^x1+θ2^x2+θn^xn

L o s e F u n c t i o n Lose Function LoseFunction可改写为:

J = 1 N ∑ i = 1 N ( y i , t u r e − θ 0 ^ x 0 − θ 1 ^ x 1 − θ 2 ^ x 2 … − θ n ^ x n ) 2 J = \frac{1}{N}\sum^{N}_{i = 1}(y_{i, ture} - \hat{\theta_0}x_0 - \hat{\theta_1}x_1 - \hat{\theta_2}x_2 … - \hat{\theta_n}x_n)^2 J=N1i=1N(yi,tureθ0^x0θ1^x1θ2^x2θn^xn)2

可知上述误差函数 J J J有极小值,且使得 J J J最小的那一组 θ ^ \hat{\theta} θ^最接近真实的 θ \theta θ

形象理解梯度下降

我们把问题简化一下若要求的线性方程为:

y = θ x y = \theta x y=θx

则根据上述 L o s e F u n c t i o n Lose Function LoseFunction可得出以下关系:
在这里插入图片描述
要想让误差值即 J J J的值最小,目前估计的点P就需要朝着蓝色箭头的方向移动,直到找到这个二次曲线的极小值即最小值点。在程序中,若想让P向目标点移动,需要加上 − η d J d θ , θ = θ p -\eta\frac{dJ}{d\theta}, \theta = \theta_p ηdθdJ,θ=θp。这里的 η \eta η就是学习率

通过不断的移动P点,使得该次计算的 J J J值与上一次计算的 J J J值只差小于一个阀值,我们就认为找到了这个极小值点,且当前的 θ ^ \hat{\theta} θ^接近真实的 θ \theta θ

而这个过程就是对 L o s e F u n c t i o n Lose Function LoseFunction优化的过程。

梯度下降解决线性回归的原理

回到之前我们讨论的线性方程:

y t r u e = θ 0 + θ 1 x 1 + θ 2 x 2 … + θ n x n y_{true} = \theta_0 + \theta_1x_1 + \theta_2x_2…+ \theta_nx_n ytrue=θ0+θ1x1+θ2x2+θnxn

对于多元线性方程的优化过程,由于参数由一个变成多个,所以这里的导数也上升为梯度,即 J J J在其所在点要对当前组中 θ 0 \theta_0 θ0 θ n \theta_n θn均求偏导,而这样一组求偏导结果构成该点的梯度。

我们令 x 0 = 1 x_0 = 1 x0=1,则原方程可以改写为:

y t r u e = θ 0 x 0 + θ 1 x 1 + θ 2 x 2 … + θ n x n y_{true} = \theta_0x_0 + \theta_1x_1 + \theta_2x_2…+ \theta_nx_n ytrue=θ0x0+θ1x1+θ2x2+θnxn

L o s e F u n c t i o n Lose Function LoseFunction为:

J = 1 N ∑ i = 1 N ( y i , t u r e − θ 0 ^ x 0 − θ 1 ^ x 1 − θ 2 ^ x 2 … − θ n ^ x n ) 2 J = \frac{1}{N}\sum^{N}_{i = 1}(y_{i, ture} - \hat{\theta_0}x_0 - \hat{\theta_1}x_1 - \hat{\theta_2}x_2 … - \hat{\theta_n}x_n)^2 J=N1i=1N(yi,tureθ0^x0θ1^x1θ2^x2θn^xn)2

上式 J J J θ 0 \theta _0 θ0求偏导的结果为:

∂ J ∂ θ 0 = 2 N ∑ i = 1 N ( y i , t r u e − θ 0 ^ x 0 − θ 1 ^ x 1 − θ 2 ^ x 2 … − θ n ^ x n ) ( − x 0 ) \frac{\partial J}{\partial \theta_0} = \frac{2}{N}\sum^{N}_{i = 1}(y_{i, true} - \hat{\theta_0}x_0 - \hat{\theta_1}x_1 - \hat{\theta_2}x_2 … - \hat{\theta_n}x_n)(-x_0) θ0J=N2i=1N(yi,trueθ0^x0θ1^x1θ2^x2θn^xn)(x0)

即:

∂ J ∂ θ 0 = 2 N ∑ i = 1 N ( y i , t r u e − θ ^ x ) ( − x 0 ) \frac{\partial J}{\partial \theta_0} = \frac{2}{N}\sum^{N}_{i = 1}(y_{i, true} - \hat{\theta}x)(-x_0) θ0J=N2i=1N(yi,trueθ^x)(x0)

=> ∂ J ∂ θ 0 = 2 N ∑ i = 1 N ( θ ^ x − y i , t r u e ) ( x 0 ) \frac{\partial J}{\partial \theta_0} = \frac{2}{N}\sum^{N}_{i = 1}(\hat{\theta}x - y_{i, true} )(x_0) θ0J=N2i=1N(θ^xyi,true)(x0),左式中 θ ^ \hat{\theta} θ^为行向量, x x x为列向量。

J J J θ i \theta _i θi求偏导的结果为:

∂ J ∂ θ 0 = 2 N ∑ i = 1 N ( θ ^ x − y i , t r u e ) ( x i ) \frac{\partial J}{\partial \theta_0} = \frac{2}{N}\sum^{N}_{i = 1}(\hat{\theta}x - y_{i, true} )(x_i) θ0J=N2i=1N(θ^xyi,true)(xi)

这样求完所有 θ \theta θ的偏导,构成该点的梯度:

∇ J ∇ θ = [ ∂ J ∂ θ 0 = 2 N ∑ i = 1 N ( θ ^ x − y i , t r u e ) ( x 0 ) ∂ J ∂ θ 1 = 2 N ∑ i = 1 N ( θ ^ x − y i , t r u e ) ( x 1 ) ∂ J ∂ θ 2 = 2 N ∑ i = 1 N ( θ ^ x − y i , t r u e ) ( x 2 ) … … ∂ J ∂ θ n = 2 N ∑ i = 1 N ( θ ^ x − y i , t r u e ) ( x n ) ] \frac{\nabla J}{\nabla \theta} =\left[ \begin {matrix}\frac{\partial J}{\partial \theta_0} = \frac{2}{N}\sum^{N}_{i = 1}(\hat{\theta}x - y_{i, true} )(x_0) \\ \frac{\partial J}{\partial \theta_1} = \frac{2}{N}\sum^{N}_{i = 1}(\hat{\theta}x - y_{i, true} )(x_1) \\ \frac{\partial J}{\partial \theta_2} = \frac{2}{N}\sum^{N}_{i = 1}(\hat{\theta}x - y_{i, true} )(x_2) \\ … \\ … \\ \frac{\partial J}{\partial \theta_n} = \frac{2}{N}\sum^{N}_{i = 1}(\hat{\theta}x - y_{i, true} )(x_n)\end {matrix} \right] θJ=θ0J=N2i=1N(θ^xyi,true)(x0)θ1J=N2i=1N(θ^xyi,true)(x1)θ2J=N2i=1N(θ^xyi,true)(x2)θnJ=N2i=1N(θ^xyi,true)(xn)

若此时估计的 θ \theta θ为:

θ ^ n o w = [ θ 0 ^ θ 1 ^ . . . θ n ^ ] \hat{\theta }_{now} = \left[ \begin {matrix} \hat{\theta_0} \\ \hat{\theta_1} \\ . \\ . \\ . \\\hat{\theta_n}\end {matrix}\right] θ^now=θ0^θ1^...θn^

若学习率为 η \eta η,则下一次迭代的 θ ^ n e x t \hat{\theta}_{next} θ^next为:

θ ^ n e x t = θ ^ n o w − η ∇ J ∇ θ = [ θ 0 ^ θ 1 ^ θ 2 ^ . . θ n ^ ] − 2 η N [ ∑ i = 1 N ( θ ^ x − y i , t r u e ) ( x 0 ) ∑ i = 1 N ( θ ^ x − y i , t r u e ) ( x 1 ) ∑ i = 1 N ( θ ^ x − y i , t r u e ) ( x 2 ) … … ∑ i = 1 N ( θ ^ x − y i , t r u e ) ( x n ) ] \hat{\theta}_{next} = \hat{\theta }_{now} - \eta\frac{\nabla J}{\nabla \theta} = \left[ \begin {matrix} \hat{\theta_0} \\ \hat{\theta_1} \\ \hat{\theta_2} \\ . \\ . \\\hat{\theta_n}\end {matrix}\right] - \frac{2\eta}{N}\left[ \begin {matrix}\sum^{N}_{i = 1}(\hat{\theta}x - y_{i, true} )(x_0) \\ \sum^{N}_{i = 1}(\hat{\theta}x - y_{i, true} )(x_1) \\ \sum^{N}_{i = 1}(\hat{\theta}x - y_{i, true} )(x_2) \\ … \\ … \\ \sum^{N}_{i = 1}(\hat{\theta}x - y_{i, true} )(x_n)\end {matrix} \right] θ^next=θ^nowηθJ=θ0^θ1^θ2^..θn^N2ηi=1N(θ^xyi,true)(x0)i=1N(θ^xyi,true)(x1)i=1N(θ^xyi,true)(x2)i=1N(θ^xyi,true)(xn)

有的地方将除以 N N N凑成除以 2 N 2N 2N这样后边的因数化简为 − η N -\frac{\eta}{N} Nη

以上便是梯度下降解决线性回归的原理,大家可以自行对公式进行推导加深理解,附上Python仿真代码。

Python仿真代码

import matplotlib.pyplot as plt
import numpy as np


# 根据当前的theta求Y的估计值
# 传入的data_x的最左侧列为全1,即设X_0 = 1
def return_Y_estimate(theta_now, data_x):
    # 确保theta_now为列向量
    theta_now = theta_now.reshape(-1, 1)
    _Y_estimate = np.dot(data_x, theta_now)

    return _Y_estimate


# 求当前theta的梯度
# 传入的data_x的最左侧列为全1,即设X_0 = 1
def return_dJ(theta_now, data_x, y_true):
    y_estimate = return_Y_estimate(theta_now, data_x)
    # 共有_N组数据
    _N = data_x.shape[0]
    # 求解的theta个数
    _num_of_features = data_x.shape[1]
    # 构建
    _dJ = np.zeros([_num_of_features, 1])
    
    for i in range(_num_of_features):
        _dJ[i, 0] = 2 * np.dot((y_estimate - y_true).T, data_x[:, i]) / _N
    
    return _dJ


# 计算J的值
# 传入的data_x的最左侧列为全1,即设X_0 = 1
def return_J(theta_now, data_x, y_true):
    # 共有N组数据
    N = data_x.shape[0]
    temp = y_true - np.dot(data_x, theta_now)
    _J = np.dot(temp.T, temp) / N
    
    return _J


# 梯度下降法求解线性回归
# data_x的一行为一组数据
# data_y为列向量,每一行对应data_x一行的计算结果
# 学习率默认为0.3
# 误差默认为1e-8
# 默认最大迭代次数为1e4
def gradient_descent(data_x, data_y, Learning_rate = 0.01, ER = 1e-10, MAX_LOOP = 1e5):
    # 样本个数为
    _num_of_samples = data_x.shape[0]
    # 在data_x的最左侧拼接全1列
    X_0 = np.ones([_num_of_samples, 1])
    new_x = np.column_stack((X_0, data_x))
    # 确保data_y为列向量
    new_y = data_y.reshape(-1, 1)
    # 求解的未知元个数为
    _num_of_features = new_x.shape[1]
    # 初始化theta向量
    theta = np.zeros([_num_of_features, 1]) * 0.3
    flag = 0  	# 定义跳出标志位
    last_J = 0  # 用来存放上一次的Lose Function的值
    ct = 0  	# 用来计算迭代次数
    
    while flag == 0 and ct < MAX_LOOP:
        last_theta = theta
        # 更新theta
        gradient =  return_dJ(theta, new_x, new_y)
        theta = theta - Learning_rate * gradient
        er = abs(return_J(last_theta, new_x, new_y) - return_J(theta, new_x, new_y))
        
        # 误差达到阀值则刷新跳出标志位
        if er < ER :
            flag = 1
        
        # 叠加迭代次数
        ct += 1
        
    return theta
            

def main():
    # =================== 样本数据生成 =======================
    # 生成数据以1元为例,要估计的theta数为2个
    num_of_features = 1
    num_of_samples = 2000
    # 设置噪声系数
    rate = 0
    X = []
    
    for i in range(num_of_features):
        X.append(np.random.random([1, num_of_samples]) * 10)
        
    X = np.array(X).reshape(num_of_samples, num_of_features)
    print("X的数据规模为 : ", X.shape)
    
    # 利用方程生成X对应的Y
    Y = []

    for i in range(num_of_samples):
        Y.append(3 + 3.27 * X[i][0] + np.random.rand() * rate)

    Y = np.array(Y).reshape(-1, 1)
    print("Y的数据规模为 : ", Y.shape)
    # ======================================================
    
   	# 计算并打印结果
    print(gradient_descent(X, Y))
    

if __name__ == '__main__':
    main()
  • 33
    点赞
  • 142
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值