目录
- Lasso线性回归学习笔记(公式与代码实现)
- 1 为什么要在线性回归中引入正则化项(简介)
- 2 常见正则化项
- 3 损失函数图像与正则化之后的图像
- 3.1损失函数图像
- 3.2 加了 L~1~ 正则项之后的损失函数图像
- 4 L~1~ 范数正则化的解中有更多零的原因
- 5 Lasso 线性回归
- 6 Lasso线性回归的优化算法(求最优解)
- 6.1 梯度下降(Gradient Descent)
- - 为什么梯度方向是函数上升最快方向?
- - 向量的方向角和方向余弦
- - 方向导数
- -方向导数与梯度的关系
- 6.2 近端梯度下降(Proximal Gradient Descent)
- - 大致思想
- - 推导过程
- 7 代码实现
Lasso线性回归学习笔记(公式与代码实现)
1 为什么要在线性回归中引入正则化项(简介)
(主要参考《机器学习》周志华 第三章)
首先,多元线性回归的基本形式为,为便于讨论,把 和 吸收入向量形式 ,相应的,把数据集 表示为一个 大小的矩阵 (其中为样本条数,为特征数),即
再把标记也写成向量形式 ,
则根据最小二乘法,参数估计值应该满足
令 ,对求导得到(相关矩阵求导知识链接)
令上式得零则可得到
- 情况一:当 为满秩矩阵时,即可逆,令上式得零,可直接通过移项求的
- 情况二:然而现实任务中 往往不是满秩矩阵,例如在高维数据中,变量的数目会超过样例数目,导致 的列数多于行数,而 秩等于 的秩,显然 不满秩,此时可解出多个 (个人参考了《2022张宇线代9讲》P29 矩阵方程),它们都能使均方误差最小化,选择哪一个作为输出,常见的做法是引入正则化(regularization)项。
2 常见正则化项
首先介绍常见范数——P-范数 (范数详见百度百科),即
L1范数正则化项,即令 = 1,并针对参数向量,即为 ,其中 为正则化参数。对于 L1 正则化,
类似地,L2范数正则化项为
3 损失函数图像与正则化之后的图像
3.1损失函数图像
为了可视化,假设一个回归函数及相应的损失函数如下:
回归函数:
损失函数(均方差):其中m表示样本量,
为了保持简单,只取一个样本点(1,1)代入上面的代价函数方程中,即
以下则是上式的图像
可以看出,有无穷多个解可以使损失函数达到最小值。根据前面的论证,在只有一个样本点情况下,外加上最后一列是 1,得到的反应数据集 的矩阵 是一行两列,
3.2 加了 L1 正则项之后的损失函数图像
加了 L1 正则项之后的损失函数: (令)
上式图像为
可以看出,有唯一最优解了
4 L1 范数正则化的解中有更多零的原因
(《机器学习》周志华 p253)
经典图片了,不多说了。
5 Lasso 线性回归
就是在损失函数中加入 L1正则化项的线性回归,值得注意的是在测试集中验证预测效果的时候的损失函数是不带正则化项的。
6 Lasso线性回归的优化算法(求最优解)
6.1 梯度下降(Gradient Descent)
- 为什么梯度方向是函数上升最快方向?
首先介绍经典的梯度下降算法。
可能都知道,梯度下降算法的迭代公式是
其中 ,而
便是优化目标中的损失函数
- 向量的方向角和方向余弦
(1)非零向量 与 轴、 轴、 轴正向的夹角 、、 称为 的方向角。
(2) 、、 称为 的方向余弦,且、、
(3)由(2)可知 称为向量 的单位向量
以上基础知识有利于从二元直接向更多元推广。
- 方向导数
要想解决以上疑问,首先要知道方向导数。在许多问题中,不仅要知道函数在坐标轴方向上的变化率(即偏导数),而且还要设法求得函数在某点沿着其他特定方向上的变化率。这就是方向倒数。
- 首先给出明确定义:(此部分我感觉参考文章写的更好理解,以下定义更精确、好推广)
设二元函数 在点 有定义, 为从 出发的射线, 为在 上的任一点,则
其中 表示 与 之间的距离。
则极限
此极限便是函数 在点 沿方向 的方向导数,记作 - 方向导数计算公式:设函数 在点 点出可微分,则 在点 处沿任意方向 的方向导数都存在,且
其中 、 为方向 的方向余弦, 部分利用的多元函数的泰勒展开式
-方向导数与梯度的关系
从上式可以看出,方向导数其实就是 梯度 与 方向余弦组成的单位向量 的内积,即
其中
那么如果此时想让 最大,自然是 为 的时候,也就是方向与梯度一致的时候的方向导数最大,即变化率最大且是正的,那么如果沿着该方向前进,可以上升(增加)的最快;同理,如果 为
【注】以上证明角度最直观好想,对于梯度下降算法的证明还有其他角度,例如在一定条件下直接利用泰勒展开近似目标函数,然后直接求解,得到的就是梯度下降迭代式,在下文中的近端梯度下降其实就利用到了这个思想。
6.2 近端梯度下降(Proximal Gradient Descent)
近端梯度下降法是众多梯度下降 (gradient descent) 方法中的一种,将proximal翻译成“近端”可能是想表达 “接近,近似” 的意思,但在这点上,经典梯度算法其实也是近似的。与经典的梯度下降法相比,近端梯度下降法 主要是想解决目标函数中存在不可微或不方便微分的部分。如对于凸优化问题,当其目标函数存在 L1正则化项,近端梯度下降法就会派上用场。
(个人理解:其实就是在经典梯度下降的基础上,最后用分段函数的方法对L1正则化项求导)
具体来说,设优化目标
令 表示微分算子, 即为 (
- 大致思想
(1)在一定条件下,通过泰勒展开式将目标函数可微的部分化简
(2)以分段函数的形式,对
- 推导过程
L-Lipschitz 条件简介:它是一个比“连续”更强的光滑性条件.直觉上,利普希茨连续函数限制了函数改变的速度,符合利普希茨条件的函数的斜率,必小于一个称为利普希茨常数的实数 L(该常数依函数而定,在梯度下降中,可以将此参数理解为学习率 的倒数,在训练中应该自己给出)(以上仅为个人理解,详见百度百科)
若 可导,且 满足 L-Lipschitz 条件,即存在常数 使得
(上式可看成是 的二阶导数恒不大于 ,即 )
则在 附近可将 通过二阶泰勒展开式近似为
其中 是与 无关的常数(后续的求解过程也是求导,就直接忽略了,上述具体推导过程参见《机器学习公式详解》(俗称南瓜书)第11章)
也就是说在 附近可将目标函数进行简化,那么问题可转化为在这个小范围内寻找可使目标函数最小的 ,然后再进行迭代(对于经典梯度下降算法来说,其实也是这么个思想),即
令 表示 的第 个分量,将上式按分量展开可看出,其中不存在 这样的项,也就是说各分量直间互不影响,假设只有一个分量 带入展开,求导,令导得零,便可以求得这个小范围内的最小值点,推广可得上式解为:
可先计算
(如果不考虑 L1 正则化项,该步骤得解就是 经典梯度下降 的迭代公式)
7 代码实现
import numpy as np
# 导入数据
filepath = "D:\\机器学习\\housing.csv"
data = np.loadtxt(filepath, delimiter=',', skiprows=1, encoding='utf-8')
# 选择特征与标签
x = data[0:20, 1:]
y = data[0:20, 0]
# 加一列
X = np.column_stack((x, np.ones((x.shape[0], 1))))
# 划分训练集与测试集
X_train, y_train = x[:10], y[:10]
X_test, y_test = x[10:], y[10:]
print(X_train.shape, y_train.shape, X_test.shape, y_test.shape)
# 定义初始化参数
def initialize(dims):
w = np.zeros((dims))
return w
# 在 MSE 基础上定义 Lasso 损失函数值
def l1_loss(X, y, w, lambda_):
num_train = X.shape[0]
y_hat = np.dot(X, w)
loss = np.sum((y_hat - y) ** 2) / num_train + np.sum(lambda_ * abs(w))
return y_hat, loss, num_train
# 定义训练过程
def lasso_train(X, y, learn_rate=50, lambda_=50, epochs=300):
loss_list = []
w = initialize(X.shape[1])
temporary_para = lambda_ * learn_rate
for i in range(epochs):
y_hat, loss, num_train = l1_loss(X, y, w, lambda_)
z = w - learn_rate * (np.dot(X.T, (y_hat - y)) * 2 / num_train)
a = np.dot(X.T, (y_hat - y))
b = X.T
c = (y_hat - y)
y_hat[0]
w_list = []
for z_i in z:
if z_i > temporary_para:
w_i = z_i - temporary_para
elif z_i < temporary_para:
w_i = z_i + temporary_para
else:
w_i = 0
w_list.append(w_i)
w = np.array(w_list)
loss_list.append(loss)
if i % 50 == 0:
print('epoch %d loss %f' % (i, loss))
params = w
return loss, loss_list, params
loss, loss_list, params = lasso_train(X,
y,
learn_rate=50,
lambda_=50,
epochs=300)