拉格朗日乘数法(Lagrange multiplier,以数学家约瑟夫·拉格朗日命名),在数学中的最优化问题中,是一种寻找多元函数在其变量受到一个或多个条件的约束时的局部极值的方法。
1. 最优化问题 最优化,是应用数学的一个分支。主要研究在特定情况下最大化或最小化某一特定函数或变量。对于没有条件约束的一元函数可以求出所有导数为零的点,代入原函数得出目标函数的极值,对于多元函数可以使用梯度下降法求得。而对于有条件约束的目标函数,上面两种方法似乎就不太可行了。对于这类问题主要有两种解体思路。
1.1 转化为无条件极值问题
例:求 在条件 下的最小值。
解:由 易得 , 带入到目标函数可得: . 显然当 时, 目标函数有最小值
这类方法显示不适用于约束条件复杂,或者目标函数有很多自变量的问题。
1.2 拉格朗日乘数法 拉格朗日乘数法可以将一个有 个变量与 个约束条件的最优化问题转换为一个解有 个变量的方程组的解的问题。这种方法中引入了一个或一组新的未知数,即拉格朗日乘数,又称拉格朗日乘子,或拉氏乘子,它们是在转换后的方程,即约束方程中作为梯度的线性组合中各个向量的系数。
比如,要求 , 在 时的局部极值时,我们可以引入新变量拉格朗日乘数 ,这时我们只需要求下列拉格朗日函数的局部极值:
更一般地,对含 个变量和 个约束的情况,有:
下面是对于 和 的推导过程:
设 是函数 在条件 下的极小值点.
另外, 由 , 得出关于变量 的隐函数 , 即 是 的极值点, 可以得到:
上述方程组中的 被称为”拉格朗日乘子“,满足此方程组的点即可能为目标函数在约束条件下的一个极值点。为此可构建函数:
满足 的点为潜在的极值点。
e.g.
之后求上式的导数,后面过程省略。
2. 线性回归模型 2.1 多元线性回归 线性模型的基本结构为:
写成向量形式为:
其中 。定义损失函数为:
其实就是 范数的平方结果,损失函数的求解目标可以转化为:
对 求偏导:
令 导数结果为0,得到:
左乘一个 :
令 ,将 代入 ,得:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import numpy as npfrom numpy.typing import ArrayLikeclass MLR : def __init__ (self ): self._w = None @property def w (self ): return self._w def fit (self, X: ArrayLike, y: ArrayLike ): size = X.shape[0 ] ones = np.ones((size, 1 )) X_ones = np.concatenate((ones, X), axis=1 ) X_mat = np.mat(X_ones) y_mat = np.mat(y.reshape(-1 , 1 )) temp = X_mat.T * X_mat self._w = temp.I * X_mat.T * y_mat def predict (self, X_test: ArrayLike ): assert self._w is not None _X_test = np.array(X_test) m, n = X_test.shape assert n == (self.w.shape[0 ] - 1 ) ones = np.ones((m, 1 )) X_mat = np.mat(np.concatenate((ones, X_test), axis=1 )) yi = X_mat * self.w return np.array(yi).flatten()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import matplotlib.pyplot as pltfrom sklearn.datasets import make_regression _X, _y = make_regression(n_features=1 , noise=5 , random_state=777 ) xMin = _X.min () xMax = _X.max () _noise_X = np.linspace(1 , 2 , 5 ).reshape(-1 , 1 ) _noise_y = np.array([350 , 380 , 410 , 430 , 480 ]) _Xn = np.r_[_X, _noise_X] _yn = np.r_[_y, _noise_y] f, (ax1, ax2) = plt.subplots(2 , 1 , figsize=(8 , 9 )) base_model = MLR() base_model.fit(_X, _y) base_pred = base_model.predict(_X) base_model_ = MLR() base_model_.fit(_Xn, _yn) base_pred_ = base_model_.predict(_Xn) ax1.scatter(_X, _y.reshape(-1 , 1 ), color='y' , label="data without outlier" ) ax2.scatter(_Xn, _yn.reshape(-1 , 1 ), color='y' , label="data with outlier" ) ax1.plot(_X, base_pred, lw=2 , label='MLR without outlier' ) ax2.plot(_Xn, base_pred_, lw=2 , label='MLR with outlier' ) ax1.legend() ax2.legend() plt.show()
在上面的例子中,除了随机生成了100个线性相关的点,还增加了5个离群值,并分别训练了2个模型。可以看到,使用离群值训练的模型受到了较大的影响。
2.2 岭回归 然而,现实任务中 往往不是满秩矩阵。例如在许多任务中我们会遇到大量的变量,其数目甚至超过样本例数,导致 的列数多于行数, 显然不满秩。此时可解出多个 ,它们都能使均方差误差最小化。选择哪一个解作为输出,将由学习算法的归纳偏好决定,常见的做法是引入正则化(regularization)项。若使用 ,则有:
式 被称为“岭回归”(ridge regression),其中正则化参数 。相较于式 ,多了正则化项 。式 同样对 求偏导:
令式 等于0:
式 左乘 :
同样,令 ,将 代入 ,得:
多元线性回归中的 未必是满秩的, 由于在主对角线上加上了 ,在多数情况下此矩阵会是满秩的,在很大程度上可以减轻(消除)数据中多重共线性对偏最小二乘的影响,但会以牺牲模型性能为代价。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 import numpy as npfrom numpy.typing import ArrayLikeclass RidgeRegr : def __init__ (self, alpha: float = 0 ): self._w = None self._alpha = alpha @property def w (self ): return self._w @property def alpha (self ): return self._alpha def fit (self, X: ArrayLike, y: ArrayLike ): X = np.array(X) size = X.shape[0 ] ones = np.ones((size, 1 )) X_hat = np.concatenate((ones, X), axis=1 ) X_mat = np.mat(X_hat) temp = X_mat.T * X_mat eye = np.mat(np.eye(temp.shape[1 ])) y_mat = np.mat(y.reshape(-1 , 1 )) self._w = (temp + self._alpha * eye).I * X_mat.T * y_mat def predict (self, X_test: ArrayLike ): assert self._w is not None _X_test = np.array(X_test) m, n = X_test.shape assert n == (self.w.shape[0 ] - 1 ) ones = np.ones((m, 1 )) X_mat = np.mat(np.concatenate((ones, X_test), axis=1 )) yi = X_mat * self.w return np.array(yi).flatten()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import matplotlib.pyplot as pltfrom sklearn.datasets import make_regression _X, _y = make_regression(n_features=1 , noise=5 , random_state=777 ) xMin = _X.min () xMax = _X.max () _noise_X = np.linspace(1 , 2 , 5 ).reshape(-1 , 1 ) _noise_y = np.array([350 , 380 , 410 , 430 , 480 ]) _Xn = np.r_[_X, _noise_X] _yn = np.r_[_y, _noise_y] f, (ax1, ax2) = plt.subplots(2 , 1 , figsize=(8 , 9 )) ax1.scatter(_X, _y.reshape(-1 , 1 ), color='y' , label="data without outlier" ) ax2.scatter(_Xn, _yn.reshape(-1 , 1 ), color='c' , label="data with outlier" )for _alpha in [0 , 10 , 100 , 200 , 1000 ]: ridge1 = RidgeRegr(alpha=_alpha) ridge1.fit(_X, _y) y_pred1 = ridge1.predict(_X) ax1.plot(_X, y_pred1, lw=2 , label=fr'$Ridge \ with \ outlier (\lambda = {_alpha} )$' ) ridge2 = RidgeRegr(alpha=_alpha) ridge2.fit(_Xn, _yn) y_pred2 = ridge2.predict(_Xn) ax2.plot(_Xn, y_pred2, lw=2 , label=fr'$Ridge \ with \ outlier (\lambda = {_alpha} )$' ) ax1.legend() ax2.legend() plt.show()
可以看到,随着正则化参数 的增加,虽然在没有添加离群值的数据集中模型的欠拟合现象愈发严重。但在真实的数据集中,离群值是很难避免的,岭回归由于添加了正则化项,模型泛化能力得到提升。
2.3 LASSO 和岭回归一样,LASSO(Least Absolute Shrinkage and Selection Operator)也是被 创作用于多重共线性问题的算法,不过LASSO使用的是 正则化,其损失函数为:
对式 中的 求偏导:
令 等于零,左乘 ,化简:
通过增大 ,可以为 计算增加一个负向,从而限制参数估计中 的大小,而防止多重共线性问题引起的参数被估计过大而导致模型失准的问题。另外, 范数和 范数正则化都有助于降低过拟合风险,但前者还会带来一个额外的好处:它比后者更易于获得”稀疏“(sparse)解,即它求得的 会有更少的非零分量,因此经常被用于特征选择。在scikit-learn中求解LASSO使用的方法为坐标下降法:
将损失函数 调整为:
其中, 为第 个样本的第 个特征,由于式 存在绝对值项,采取残差求导的方式,将前半部分的函数记为 ,其对参数 的偏导数为:
令:
因此有:
对式 中的正则项 求偏导有:
将式 , 代入到 中:
令式 为0:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 import numpy as npfrom numpy.typing import ArrayLikeclass LASSORegr : def __init__ (self, alpha: float = 0 ): self._alpha = alpha self._w = None def __str__ (self ): return f"LassoRegr(alpha={self._alpha} )" @property def alpha (self ): return self._alpha @property def w (self ): return self._w def fit (self, X_train: ArrayLike, y_train: ArrayLike, epochs: int = 100 ): m, n = X_train.shape ones = np.ones((m, 1 )) X_hat = np.concatenate((ones, X_train), axis=1 ) X_mat = np.mat(X_hat) y_mat = np.mat(y_train.reshape(-1 , 1 )) w = np.matrix(np.zeros((n + 1 , 1 ))) for _ in range (epochs): for k in range (n + 1 ): _w = w.copy() _w[k] = 0 pk = (X_mat[:, k].T * (y_mat - X_mat * _w))[0 , 0 ] zk = (X_mat[:, k].T * X_mat[:, k])[0 , 0 ] if pk < -self.alpha / 2 : wk = (pk + self.alpha / 2 ) / zk elif pk > self.alpha / 2 : wk = (pk - self.alpha / 2 ) / zk else : wk = 0 w[k, 0 ] = wk self._w = w print (model) def predict (self, X_test: ArrayLike ): _X_test = np.array(X_test) m, n = X_test.shape assert n == (self.w.shape[0 ] - 1 ) ones = np.ones((m, 1 )) X_mat = np.mat(np.concatenate((ones, X_test), axis=1 )) yi = X_mat * self.w return np.array(yi).flatten()
3. 小结 岭回归和LASSO并不属于严格意义上的“算法”,而是对多元线性回归中潜在的多重共线性问题的一种优化方法。在原有损失函数的基础上,通过加入正则化项,限制模型参数的范围。
4. 参考资料
机器学习 - 周志华
拉格朗日乘数法的推导
Ridge回归求解
LASSO回归求解