利用梯度下降法及Python手动实现线性回归

欢迎访问我的博客https://nick-zhy.github.io

线性回归是机器学习的基础,在日常使用中,我们经常调用函数来求解参数,这样虽然方便,但在学习初期不能加深对模型及代码的理解。本文则讲述了梯度下降法(包括Adagrad法)求解线性回归的理论以及如何用Python实现,最后应用到 diabetes 数据集,并将结果与 sklearn 中的API进行对比。

1. Introduction

Linear regression is the fundement of machine learning. We often apply functions from packages like sklearn to get the parameters. But doing so can't help us to comprehend the model and algrithms. In this article, we introduce GD (Gradient Descent) and its realization by NumPy to calculate the linear model. Finally, our algorithms are applied to the dataset diabetes in sklearn to show the perfomance comparing with linear regression API in sklearn

2. 理论基础

2.1 最小二乘法

线性回归模型可表示为:Y=X_{n,m+1} w (严格地讲,这里的w应该用\hat{w})

其中Yn维列向量。wm+1维的列向量,之所以多了一个维度,是因为我们把常数项参数b也放在了w里。即w=[b, w_{1}, w_{2} ... w_{m}]^{T}。相应的,X_{n,m+1}也多了一个维度,该矩阵的第一列全为1。如果无法理解这种形式的表达,可以将矩阵在草稿纸上写出来,就看得明白了,这样表达能方便后续求解。

求解线性回归最简单的算法是最小二乘法,即找到一组参数,使模型的损失函数(残差平方和)最小化,用数学模型表示就是:
\begin{aligned} w^{*} &= \underset{w}{\arg \min } {\mathrm{L}{(w)}}\\\\ &=\underset{w}{\arg \min }{(Xw-Y)^T(Xw-Y)} \end{aligned}

写成矩阵的形式能够方便求梯度,可能下面这种形式是大家更熟悉的最小二乘法形式:
\begin{aligned} w^{*} &=\underset{w}{\arg \min } \sum_{i=1}^{n}\left(f\left(x_{i}\right)-y_{i}\right)^{2} \\\\ &=\underset{w}{\arg \min } \sum_{i=1}^{n}\left(w x_{i}-y_{i}\right)^{2} \end{aligned}其中x_i是第i个个体的自变量向量,wx_i是向量的内积。这种形式是大家更为熟悉的最小二乘法形式。

2.2 梯度

上文提到,最小二乘法的损失函数为:
\mathrm{L}(w)=(Xw-Y)^T(Xw-Y)

这是一个标量,而w是一个向量,标量对向量求导,其实就是该标量对向量每一个元素求导,再组合成列向量的形式。用数学表达就是:
\nabla \mathrm{L}=\nabla \mathrm{L}\left(w_{0}, w_{1}, w_{2}, \ldots, w_{m}\right)=\left(\begin{array}{c} \frac{\partial \mathrm{L}}{\partial w_{0}} \\ \frac{\partial \mathrm{L}}{\partial w_{1}} \\ \frac{\partial \mathrm{L}}{\partial w_{2}} \\ \dots \\ \frac{\partial \mathrm{L}}{\partial w_{m}} \end{array}\right)

如何求解\nabla \mathrm{L}(w)呢?将损失函数\mathrm{L}{(w)}展开:
\begin{aligned} \mathrm{L}(w)&=(Y^T-w^{T}X^{T})(Y-Xw) \\ &=Y^{T}Y-Y^{T}Xw-w^{T}X^{T}Y+w^{T}X^{T}Xw \\ &=Y^{T}Y-Y^{T}Xw-(Y^{T}Xw)^{T}+w^{T}X^{T}Xw \\ &=Y^{T}Y-2Y^{T}Xw+w^{T}X^{T}Xw \end{aligned}

利用矩阵求导法则:
\begin{aligned} \nabla \mathrm{L}(w)&=\frac{\partial \mathrm{L}(w)}{\partial{w}} \\ &=\frac{\partial Y^{T}Y}{\partial{w}}-\frac{\partial 2Y^{T}Xw}{\partial{w}}+\frac{\partial w^{T}X^{T}Xw}{\partial{w}} \\ &=0-2X^{T}Y+2X^{T}Xw \\ &=2X^{T}Xw-2X^{T}Y \end{aligned}

此时,如果我们令\nabla \mathrm{L}(w)=0就可以求得w的解析解,即:w^{*}=(X^{T}X)^{-1}X^{T}Y

但是在梯度下降法中,我们当然不能直接带这个公式算,因为这个公式只对线性回归模型成立,对其他模型不成立,而梯度下降则是对各种模型都能使用。下面讲述梯度下降法的原理。

2.3 梯度下降

什么是梯度?从几何上解释,沿着梯度方向,函数上升的最快。可以类比二次函数的导数,二次函数在某一点上升最快的方向就是那一点切线的方向。这段话另一面就是说逆着梯度方向,函数下降的最快。梯度下降法的逻辑就是:我任意给定一个初始参数向量w_0^{*},算出损失函数值,这个值肯定不是最小的,但是如果我算出w_0^{*}这一点的梯度\nabla \mathrm{L}(w_0^{*}),那么我反着这个梯度走,就可以减小损失函数值,如果我不停重复这个过程,就能达到一个最小值。

这一段可以结合台湾大学李宏毅老师的机器学习课程理解。也可以参考这个博文
用数学过程表示为:
\begin{aligned} &w^1=w^0-\eta \frac{\partial \mathrm{L}(w)}{\partial{w}}_{w=w^0} \\ &w^2=w^1-\eta \frac{\partial \mathrm{L}(w)}{\partial{w}}_{w=w^1} \\ &w^3=w^2-\eta \frac{\partial \mathrm{L}(w)}{\partial{w}}_{w=w^2} \\ &... \\\\ &w^{i+1}=w^i-\eta \frac{\partial \mathrm{L}(w)}{\partial{w}}|_{w=w^i} \\ &if \ \ (\frac{\partial \mathrm{L}(w)}{\partial{w}}_{w=w^i}==0) \ \ then \ \ stop; \end{aligned}

其中\eta是学习率,该值是手动设置的参数,如果过大,可能会学习过快错过最低点,如果过小,会使学习速度太慢。实际操作中,当梯度等于零的时候循环结束不太现实,因为不可能达到零,因此更多地是大量地迭代,去逼近解析值。

这就是最经典的梯度下降法,可以看到梯度下降法的一大缺点就是固定的学习率,但一个常识是:通常是随着参数的更新越来越小的。这很容易理解,学习越好的同学越可以少努力学习,学习越差的同学则应该更努力学习。有没有什么办法能让我们随着迭代更新\eta呢?接下来我们介绍Adagrad。

2.4 Adagrad

Adagrad的过程表示如下:
\begin{array}{ll} w^{1} \leftarrow w^{0}-\frac{\eta^{0}}{\sigma^{0}} g_{0}^{0} \quad \sigma^{0}=\sqrt{\left(g^{0}\right)^{2}} \\ w^{2} \leftarrow w^{1}-\frac{\eta^{1}}{\sigma^{1}} g^{1} \quad \sigma^{1}=\sqrt{\frac{1}{2}\left[\left(g^{0}\right)^{2}+\left(g^{1}\right)^{2}\right]} \\ w^{3} \leftarrow w^{2}-\frac{\eta^{2}}{\sigma^{2}} g^{2} \quad \sigma^{2}=\sqrt{\frac{1}{3}\left[\left(g^{0}\right)^{2}+\left(g^{1}\right)^{2}+\left(g^{2}\right)^{2}\right]} \\ \dots \\ w^{t+1} \leftarrow w^{t}-\frac{\eta^{t}}{\sigma^{t}} g^{t} \quad \sigma^{t}=\sqrt{\frac{1}{t+1} \sum_{i=0}^{t}\left(g^{i}\right)^{2}} \end{array}

其中w是参数向量,t表示第t次更新参数,\eta^t = \sqrt{\frac{\eta}{1+t}}

\sigma^t是之前所有梯度均方根。g^t表示损失函数对w的偏微分,即梯度\frac{\partial \mathrm{L}(w)}{\partial{w}}|_{w=w^t}

需要注意,由于g^t是一个向量,式子中 (g^t)^2 或者\sqrt{\sum_{i=0}^{t}\left(g^{i}\right)^{2}}都是对每向量每一个元素进行计算。

\eta^t\sigma^t都有\sqrt{\frac{1}{1+t}},因此可以约分,化解后的更新过程为:
w^{t+1}=w^t-\frac{\eta}{\sqrt{\sum\limits_{i=0}^t(g^i)^2}}\cdot g^t

在计算机编程中,为了防止出现分母为零的情况,更新过程下的分母一般会加上一个非常小的值\epsilon,即:
w^{t+1}=w^t-\frac{\eta}{\sqrt{\epsilon+\sum\limits_{i=0}^t(g^i)^2}}\cdot g^t

3. 代码实现

本部分展示梯度下降法求解多元线性回归的Python代码,结合sklearn中的diabetes数据集为例进行简单的训练,同时与用sklearn提供的linear_model得到的结果进行对比。

import numpy as np
import pandas as pd

def grad_desc(X, y, learning_rate=0.01, epochs=1000, eps=0.0000000001, measure='gd'):
    '''
    This funchtion is used to calculate parameters of linear regression by gradient descend method\n
    :param X:  sample matrix, shape(n_samples, n_features)\n
    :param y: matrix-like, shape (n_samples, 1)\n
    :param learning_rate: learning rate, 0.01 by default\n
    :param epochs: times of iteration, 1000 by default\n
    :param eps: the small positive number used in Adagrad prevent zero denominator\n
    :param return: the parameters of linear regression\n
    :param gd_type: measures to do gradient descend, can choose 'gd' or 'Adagrad'\n
    '''
    n = X.shape[0] # 样本数量
    dim = X.shape[1] + 1 # 特征数量,+1是因为有常数项
    x = np.concatenate((np.ones([n, 1]), X), axis = 1).astype(float) 
    # 同样由于有常数项,x矩阵需要多加一列1
    y = np.matrix(y).reshape(-1, 1).astype(float) # y转化为列向量,方便矩阵运算
    w = np.zeros([dim, 1]) # 初始化参数
    ## 常规的梯度下降法
    if measure == 'gd':
        for i in range(epochs):
            loss = np.sum(np.power(np.dot(x, w) - y, 2))/n
            if (i % 100 == 0):
                print(str(i) + ":" + str(loss))
            gradient = 2 * np.dot(x.transpose(), np.dot(x, w) - y)/n
            w = w - learning_rate * gradient
    ## Adagrad法
    if measure == 'Adagrad':
        adagrad = np.zeros([dim, 1])
        for i in range(epochs):
            loss = np.sum(np.power(np.dot(x, w) - y, 2))/n
            if (i % 100 == 0):
                print(str(i) + ":" + str(loss))
            gradient = 2 * np.dot(x.transpose(), np.dot(x, w) - y)/n
            adagrad += np.square(gradient)
            w = w - learning_rate * gradient / np.sqrt(adagrad + eps)
    return w


def predict(w, test_X, test_y):
    '''
    This function is use to calculate the MSE of a given linear regression model\n
    :param w: matrix-like, shape (n_features+1, 1)\n
    :test_X: test sample matrix, shape(n_samples, n_features)\n
    :test_y: true targets of test set, matrix-like, shape (n_samples, 1)\n
    '''
    test_X = np.concatenate((np.ones([test_X.shape[0], 1]), test_X), axis = 1).astype(float)
    test_y = np.matrix(test_y).reshape(-1, 1).astype(float)
    predict_y = np.dot(test_X, w)
    mse = np.sqrt(np.average(np.square(predict_y - test_y)))
    return mse, predict_y


if __name__ == '__main__':
    # 导入deabetes数据集
    from sklearn.datasets import load_diabetes
    from sklearn.utils import shuffle
    diabetes = load_diabetes()
    X = diabetes.data
    y = diabetes.target

    # 划分训练集和测试集
    offset = int(X.shape[0] * 0.9)
    X_train, y_train = X[:offset], y[:offset]
    X_test, y_test = X[offset:], y[offset:]
    y_train = y_train.reshape((-1,1))
    y_test = y_test.reshape((-1,1))
    print('X_train=', X_train.shape)
    print('X_test=', X_test.shape)
    print('y_train=', y_train.shape)
    print('y_test=', y_test.shape)
    
    # 使用grad_desc()函数求解
    w = grad_desc(X_train, y_train, learning_rate=0.03,epochs=10000, measure='gd')
    print('使用grad_desc()函数的MSE:', predict(w, X_test, y_test)[0])

    # 利用sklearn的线性回归api来做对比
    import sklearn.linear_model as sl
    import sklearn.metrics as sm
    model = sl.LinearRegression()
    model.fit(X_train, y_train)
    pred_y = model.predict(X_test)
    print('使用sklearn线性模型函数的MSE:', sm.mean_absolute_error(y_test, pred_y))

最终得到结果:

使用grad_desc()函数的MSE:41.768875313581226
使用sklearn线性模型函数的MSE: 32.085174947765125

可以看到,我们手动实现的梯度下降法表现明显不如api提供的好,这也是很正常的事情,因为本文所采用的梯度下降法是最简单的两种:普通的GD法和Adagrad。而梯度下降的更新方法有许多提高措施,如随机梯度下降法(SGD),使用动量的随机梯度下降法,RMSProp法等,详见博文梯度下降。有兴趣读者可在本文基础上加上这些提高措施,改善性能。

4. 总结

本文主要讲解了梯度下降法求解线性回归模型的理论与Python代码实现,最终效果一般,但是梯度下降的思想和方法是最重要的,因为深度学习的基础就是梯度下降。

作为一个金融科班出身的学生,我深知光学金融学或者经济学的知识是远远不够的,现在各个学科都在鼓励交叉,最近几年金融科技也逐渐收到社会的关注,因此我在硕士课程之余经常自学机器学习、深度学习的内容。一开始是只企图掌握API的使用(即掉包、调参侠),但我逐渐发现,许多有创造的论文都是对经典的模型有一些改动,加上在金融领域的应用,才能成为优秀的论文。因此我又从头开始,试图能从源码上理解基础模型,既能加深对模型的印象,也会对未来创作有帮助。

希望这篇博文能帮助到,欢迎对深度学习、机器学习感兴趣的金融、经济学领域同学与我交(P)流(Y)。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容