欢迎访问我的博客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 最小二乘法
线性回归模型可表示为: (严格地讲,这里的应该用)
其中是维列向量。是维的列向量,之所以多了一个维度,是因为我们把常数项参数也放在了里。即。相应的,也多了一个维度,该矩阵的第一列全为。如果无法理解这种形式的表达,可以将矩阵在草稿纸上写出来,就看得明白了,这样表达能方便后续求解。
求解线性回归最简单的算法是最小二乘法,即找到一组参数,使模型的损失函数(残差平方和)最小化,用数学模型表示就是:
写成矩阵的形式能够方便求梯度,可能下面这种形式是大家更熟悉的最小二乘法形式:
其中是第个个体的自变量向量,是向量的内积。这种形式是大家更为熟悉的最小二乘法形式。
2.2 梯度
上文提到,最小二乘法的损失函数为:
这是一个标量,而是一个向量,标量对向量求导,其实就是该标量对向量每一个元素求导,再组合成列向量的形式。用数学表达就是:
如何求解呢?将损失函数展开:
利用矩阵求导法则:
此时,如果我们令就可以求得的解析解,即:
但是在梯度下降法中,我们当然不能直接带这个公式算,因为这个公式只对线性回归模型成立,对其他模型不成立,而梯度下降则是对各种模型都能使用。下面讲述梯度下降法的原理。
2.3 梯度下降
什么是梯度?从几何上解释,沿着梯度方向,函数上升的最快。可以类比二次函数的导数,二次函数在某一点上升最快的方向就是那一点切线的方向。这段话另一面就是说逆着梯度方向,函数下降的最快。梯度下降法的逻辑就是:我任意给定一个初始参数向量,算出损失函数值,这个值肯定不是最小的,但是如果我算出这一点的梯度,那么我反着这个梯度走,就可以减小损失函数值,如果我不停重复这个过程,就能达到一个最小值。
这一段可以结合台湾大学李宏毅老师的机器学习课程理解。也可以参考这个博文。
用数学过程表示为:
其中是学习率,该值是手动设置的参数,如果过大,可能会学习过快错过最低点,如果过小,会使学习速度太慢。实际操作中,当梯度等于零的时候循环结束不太现实,因为不可能达到零,因此更多地是大量地迭代,去逼近解析值。
这就是最经典的梯度下降法,可以看到梯度下降法的一大缺点就是固定的学习率,但一个常识是:通常是随着参数的更新越来越小的。这很容易理解,学习越好的同学越可以少努力学习,学习越差的同学则应该更努力学习。有没有什么办法能让我们随着迭代更新呢?接下来我们介绍Adagrad。
2.4 Adagrad
Adagrad的过程表示如下:
其中是参数向量,表示第次更新参数,。
是之前所有梯度均方根。表示损失函数对的偏微分,即梯度。
需要注意,由于是一个向量,式子中 或者都是对每向量每一个元素进行计算。
与都有,因此可以约分,化解后的更新过程为:
在计算机编程中,为了防止出现分母为零的情况,更新过程下的分母一般会加上一个非常小的值,即:
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)。