正则化技术的Lasso和Ridge回归——Python实现
在机器学习中,我们非常关心模型的预测能力,即模型在新数据上的表现,而不希望过拟合现象的的发生,我们通常使用正则化(regularization)技术来防止过拟合情况。正则化是机器学习中通过显式的控制模型复杂度来避免模型过拟合、确保泛化能力的一种有效方式。如果将模型原始的假设空间比作“天空”,那么天空飞翔的“鸟”就是模型可能收敛到的一个个最优解。在施加了模型正则化后,就好比将原假设空间(“天空”)缩小到一定的空间范围(“笼子”),这样一来,可能得到的最优解能搜索的假设空间也变得相对有限。有限空间自然对应复杂度不太高的模型,也自然对应了有限的模型表达能力。这就是“正则化有效防止模型过拟合的”一种直观解析。
一、正则化方法与应用
1.1 正则化方法
正则化技术有多种形式,常见的方法包括L1正则化、L2正则化、弹性网络正则化、Dropout、数据增强、早停和批量归一化等。
- L1正则化(Lasso)
L1正则化通过在损失函数中加入参数的绝对值之和的惩罚项来控制模型复杂度。具体来说,L1正则化的损失函数可以表示为: $$L_{\text{original}}(\theta) + \lambda \sum_{i} |\theta_i|$$ 其中,\(L_{\text{original}}(\theta)\)是原始损失函数,\(\lambda\)是正则化强度的超参数。L1正则化可以使一些参数变为零,从而实现特征选择。 - L2正则化(Ridge)
L2正则化通过在损失函数中加入参数平方和的惩罚项来控制模型复杂度。具体来说,L2正则化的损失函数可以表示为: $$L(\theta) = L_{\text{original}}(\theta) + \lambda \sum_{i} \theta_i^2$$ L2正则化会使模型参数趋向于较小的数值,但不会使它们完全为零,有助于防止模型过度复杂化。 - 弹性网络正则化(Elastic Net)
弹性网络正则化结合了L1和L2正则化的优点。其损失函数可以表示为: $$L(\theta) = L_{\text{original}}(\theta) + \lambda_1 \sum_{i} |\theta_i| + \lambda_2 \sum_{i} \theta_i^2$$ 这种方法既能选择特征(L1),又能避免某些特征过度衰减(L2)。 - Dropout
Dropout是一种针对神经网络的正则化技术。在每次训练过程中,随机丢弃一部分神经元,使其对应的权重不参与更新。这样可以避免神经网络对特定路径的过度依赖,提升模型的泛化能力。 - 数据增强
数据增强是通过对训练数据进行各种变换(如旋转、缩放、翻转等),生成更多的训练样本,以此来提高模型的泛化能力。尤其在图像分类任务中,数据增强非常常见。 - 早停(Early Stopping)
早停是在验证集上监控模型的表现,当验证集的误差不再下降时,停止训练。这样可以避免模型在训练数据上过度拟合。 - 批量归一化(Batch Normalization)
批量归一化通过在每个训练小批量数据的中间层上进行标准化处理,来加速训练并改善模型的泛化能力。它可以减少对初始权重的敏感性,并有轻微的正则化效果。
1.2 正则化在模型中的应用
正则化技术可以应用于各种机器学习模型,如线性回归、逻辑回归、支持向量机(SVM)、神经网络等。在每种模型中,正则化的具体实现和效果可能会有所不同。
- 线性回归与逻辑回归
在线性回归和逻辑回归中,L1和L2正则化是最常用的技术。它们通过限制模型参数的大小,避免模型过于复杂,提升泛化能力。 - 支持向量机(SVM)
在SVM中,正则化项是对间隔边界的一个软约束,可以允许某些样本点出现在间隔边界的错误一侧,但会对模型的损失函数加以惩罚。 - 神经网络
在神经网络中,正则化技术种类繁多。除了L1和L2正则化,还包括Dropout、数据增强、早停和批量归一化等。通过这些技术,可以有效防止神经网络的过拟合,提升其泛化性能。
1.3 机器学习过拟合和欠拟合
(1)under fit:还没有拟合到位,训练集和测试集的准确率都还没有到达最高。
(2)over fit:拟合过度,训练集的准确率升高的同时,测试集的准确率反而降低。学的过度了,做过的卷子都能再次答对,考试碰到新的没见过的题就考不好。
(3)just right:过拟合前训练集和测试集准确率都达到最高时刻。学习并不需要花费很多时间,理解的很好,考试的时候可以很好的把知识举一反三。真正工作中我们是奔着过拟合的状态去调的,但是最后要的模型肯定是没有过拟合的。如下图所示:
正则化就是防止过拟合,增加模型的鲁棒性 robust,鲁棒是 Robust 的音译,也就是强壮的意思。鲁棒性调优就是让模型拥有更好的鲁棒性,也就是让模型的泛化能力和推广能力更加的强大。正则化(鲁棒性调优)的本质就是牺牲模型在训练集上的正确率来提高推广能力,模型参数\(\theta\) 在数值上越小越好,这样能抵抗数值的扰动。同时为了保证模型的正确率参数\(\theta\)又不能极小。故而人们将原来的损失函数加上一个惩罚项,这里面损失函数就是原来固有的损失函数,比如回归的话通常是 MSE,分类的话通常是 cross entropy 交叉熵,然后在加上一部分惩罚项来使得计算出来的模型参数 \(\theta\) 相对小一些并带来泛化能力。
二、L1和L2正则项
L1正则项和L2正则项是机器学习中用来防止模型过拟合,提高模型的泛化能力,它们分别对应于L1范数和L2范数:
- L1正则项
L1正则项,也称为Lasso正则化,其公式为:
- L2正则项
L2正则项,也称为Ridge正则化,其公式为:
添加正则项会导致模型L1稀疏(L1 Sparsity)和L2平滑(L2 Smoothness)。L1正则项因为其对参数绝对值的惩罚,倾向于产生稀疏的模型,即某些参数可以完全被剔除,这使得模型更加简洁,易于解释。而L2正则项则通过平滑的惩罚项,将所有参数值都向零方向推动,但保持它们在一定范围内,从而使得模型更加平滑和稳定,见下表:
L1稀疏 | L2平滑 |
---|---|
L1正则化,也被称为Lasso正则化,它是通过在损失函数中增加L1范数(元素的绝对值之和)来惩罚模型的权重 | L2正则化,也被称为Ridge正则化,通过在损失函数中增加L2范数(权重的平方和)来惩罚模型 |
这种正则化倾向于产生稀疏解,即大部分权重变为0,从而使得模型更加简单 | 与L1正则化不同的是,L2正则化倾向于减少权重的绝对值,但不会让它们变为0,因此模型不会那么稀疏 |
在特征选择的场景中,L1正则化可以自动进行特征选择,那些不重要的特征(对应权重为0)会被自动剔除 | L2正则化可以稳定模型的斜率,使得模型的输出更加平滑,从而在训练数据上产生较为连续和平滑的预测结果 |
L1正则化能够给出更强的稀疏性,因为它倾向于将系数压缩到0,而L2正则化则不太可能做到这一点 | 相对于L1正则化,L2正则化在数值上更稳定,更容易求解,因为它不会导致权重变为0,保持了权重的连续性 |
2.1 L1正则化原理
L1正则化通过向损失函数添加模型权重的绝对值之和来惩罚复杂性。对于一个具有N个特征的线性回归模型,L1正则化的损失函数可表示为:
其中:\(\theta\)是模型的权重向量;MSE表示均方误差损失;\(\lambda\)是正则化强度(超参数),控制正则化的影响。
为了推导L1正则化的梯度,我们引入了辅助变量 \(z_i\) 来代替 \(\left|\theta_i\right|\) 。定义:\(z_i=\left|\theta_i\right|\),这样,我们的目标函数变为:
- 讨论 \(z_i\) 的导数
接下来,我们需要讨论 \(z_i\) 对于 \(\theta_i\) 的导数。因为 \(z_i\) 在 \(\theta_i\) 处不可导,我们引入一个额外的变量 \(s_i\) 作为符号函数,其定义如下:
那么 \(z_i\) 可以用 \(s_i\) 表示:
- 讨论 \(s_i\) 的导数
接下来,我们讨论 \(s_i\) 对于 \(\theta_i\) 的导数。因为 \(s_i\) 在 \(\theta_i\) 处不可导,我们使用次梯度的概念。在 \(\theta_i=0\) 处,我们可以选择任何值介于 -1 和 1 之间的值。
- 利用次梯度进行梯度下降
现在我们计算L1正则化的梯度。对于第 \(i\) 个权重 \(\theta_i\) ,我们有:
在实际计算中,我们可以用次梯度\(s_i\)的任何值替代,例如选择 -1 或 1。这样就得到了L1正则化的梯度下降规则。
2.2 L2正则化原理
L2正则化通过向损失函数添加模型权重的平方和来惩罚复杂性。对于一个具有N个特征的线性回归模型,L2正则化的损失函数可表示为:
其中:\(\theta\)是模型的权重向量;MSE表示均方误差损失;\(\lambda\)是正则化强度(超参数),控制正则化的影响。
- 计算目标函数的梯度
我们计算目标函数对权重 \(\theta_i\) 的偏导数。对于L2正则化,有:
- 利用梯度进行梯度下降
使用梯度下降法来最小化损失函数,按以下方式更新权重 \(\theta_i\) :
其中, \(\eta\) 是学习率。
- 推导过程
首先写出L2正则化的目标函数:
然后,计算损失函数关于 \(\theta_i\) 的偏导数:
对于均方误差损失(MSE),我们有:
其中 \(m\) 是样本数, \(\mathbf{X}_i^{(j)}\) 是第 \(j\) 个样本的第 \(i\) 个特征值。
最后,我们将这个梯度更新规则应用于梯度下降算法中,通过迭代来最小化L2正则化的损失函数。这有助于防止模型的权重过大,从而避免过拟合。
图1(上面的\(\theta\)就是图中的\(\omega\)) | 图2 |
---|---|
为了提高模型泛化能力,更常用的也是L2。通常我们为了去提高模型的泛化能力 L1 和 L2 都可以使用。学习率是一个关键的超参数,它决定了每次更新的步长,影响着模型收敛的速度和稳定性。L1正则化倾向于产生稀疏解,而L2正则化则倾向于均匀地缩减参数的大小。
三、Python实例
3.1 线性回归正则化
"""
线性回归示例,包含L1和L2正则化
"""
import numpy as np
import matplotlib.pyplot as plt
# 设置随机种子以获得可重复的结果
np.random.seed(42)
# 生成样本数据
X = 2 * np.random.rand(100, 1) # 100个样本,每个样本1个特征
y = 4 + 3 * X + np.random.randn(100, 1) # 真实值,线性关系加上噪声
# 添加偏置项,用于拟合截距
X_b = np.c_[np.ones((100, 1)), X]
# L1正则化梯度下降函数
def l1_regularized_gradient_descent(X, y, learning_rate, n_iterations, lambda_):
theta = np.random.randn(2, 1) # 随机初始化参数
for iteration in range(n_iterations):
# 计算梯度
gradients = 2 / len(X) * X.T.dot(X.dot(theta) - y) + lambda_ * np.sign(theta)
# 更新参数
theta = theta - learning_rate * gradients
return theta
# L2正则化梯度下降函数
def l2_regularized_gradient_descent(X, y, learning_rate, n_iterations, lambda_):
theta = np.random.randn(2, 1) # 随机初始化参数
for iteration in range(n_iterations):
# 计算梯度
gradients = 2 / len(X) * X.T.dot(X.dot(theta) - y) + 2 * lambda_ * theta
# 更新参数
theta = theta - learning_rate * gradients
return theta
# 运行L1正则化梯度下降
theta_l1 = l1_regularized_gradient_descent(X_b, y, learning_rate=0.1, n_iterations=100, lambda_=0.1)
# 运行L2正则化梯度下降
theta_l2 = l2_regularized_gradient_descent(X_b, y, learning_rate=0.1, n_iterations=100, lambda_=0.1)
# 绘制图形
plt.figure(figsize=(12, 6))
# 绘制原始数据点
plt.scatter(X, y, color='blue', label='Original Data')
# 绘制L1正则化的线性回归线
plt.plot(X, X_b.dot(theta_l1), color='red', label='L1 Regularization')
# 绘制L2正则化的线性回归线
plt.plot(X, X_b.dot(theta_l2), color='green', label='L2 Regularization')
# 添加图例和标签
plt.legend(loc='upper left')
plt.title('Linear Regression with L1 and L2 Regularization')
plt.xlabel('X')
plt.ylabel('y')
# 显示图形
plt.show()
3.2 逻辑回归正则化
机器学习常用的数据集网址:红酒数据集下载网址 。这份数据集包含来自3种不同起源的葡萄酒的共178条记录。13个属性是葡萄酒的13种化学成分。通过化学分析可以来推断葡萄酒的起源。值得一提的是所有属性变量都是连续变量。
import numpy as np
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
# 加载红酒数据集
data = load_wine()
X = data.data
y = data.target
# 数据标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 数据集划分
X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.3, random_state=42)
# 训练逻辑回归模型(L1正则化)
logreg_l1 = LogisticRegression(penalty='l1', solver='saga', max_iter=5000, multi_class='multinomial')
logreg_l1.fit(X_train, y_train)
# 训练逻辑回归模型(L2正则化)
logreg_l2 = LogisticRegression(penalty='l2', solver='saga', max_iter=5000, multi_class='multinomial')
logreg_l2.fit(X_train, y_train)
# 预测并计算准确率
y_pred_l1 = logreg_l1.predict(X_test)
y_pred_l2 = logreg_l2.predict(X_test)
accuracy_l1 = accuracy_score(y_test, y_pred_l1)
accuracy_l2 = accuracy_score(y_test, y_pred_l2)
print(f'Accuracy with L1 regularization: {accuracy_l1}')
print(f'Accuracy with L2 regularization: {accuracy_l2}')
3.3 Lasso回归
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import Lasso
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
# Generate random data
x = np.random.uniform(-3, 3, size=100)
X = x.reshape(-1, 1)
y = 0.5 + x**2 + x + 2 + np.random.normal(0, 1, size=100)
# Plot the generated data
plt.scatter(x, y)
plt.xlabel('x')
plt.ylabel('y')
plt.title('Scatter plot of the generated data')
plt.show()
# Define a function to create a Lasso regression model with polynomial features
def LassoRegression(degree, alpha):
return Pipeline([
('poly', PolynomialFeatures(degree=degree)),
('std_scaler', StandardScaler()),
('lasso_reg', Lasso(alpha=alpha))
])
# Split the data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Create and train the Lasso regression model
lasso_reg1 = LassoRegression(30, 0.0001)
lasso_reg1.fit(X_train, y_train)
# Predict using the trained model
y1_predict = lasso_reg1.predict(X_test)
# Calculate the mean squared error
mse = mean_squared_error(y_test, y1_predict)
print(f'Mean Squared Error: {mse}')
# Function to plot the model's predictions
def plot_model(model, X, y, X_test, y_test, y_pred):
plt.scatter(X, y, color='blue', label='Data')
plt.scatter(X_test, y_test, color='red', label='Test Data')
plt.scatter(X_test, y_pred, color='green', label='Predictions')
plt.xlabel('x')
plt.ylabel('y')
plt.title('Lasso Regression Model Predictions')
plt.legend()
plt.show()
# Plot the model predictions
plot_model(lasso_reg1, X, y, X_test, y_test, y1_predict)
Lasso回归 | Ridge回归 |
---|---|
3.4 Ridge回归
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import Ridge
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split
# Generate random data
x = np.random.uniform(-3, 3, size=100)
X = x.reshape(-1, 1)
y = 0.5 + x**2 + x + 2 + np.random.normal(0, 1, size=100)
# Plot the generated data
plt.scatter(x, y)
plt.xlabel('x')
plt.ylabel('y')
plt.title('Scatter plot of the generated data')
plt.show()
# Define a function to create a Ridge regression model with polynomial features
def ridgeregression(degree, alpha):
return Pipeline([
("poly", PolynomialFeatures(degree=degree)),
("standard", StandardScaler()),
("ridge_reg", Ridge(alpha=alpha))
])
# Split the data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Create and train the Ridge regression model
ridge1_reg = ridgeregression(degree=30, alpha=0.0001)
ridge1_reg.fit(X_train, y_train)
# Predict using the trained model
y1_predict = ridge1_reg.predict(X_test)
# Calculate the mean squared error
mse = mean_squared_error(y_test, y1_predict)
print(f'Mean Squared Error: {mse}')
# Function to plot the model's predictions
def plot_model(model, X, y, X_test, y_test, y_pred):
plt.scatter(X, y, color='blue', label='Data')
plt.scatter(X_test, y_test, color='red', label='Test Data')
plt.scatter(X_test, y_pred, color='green', label='Predictions')
plt.xlabel('x')
plt.ylabel('y')
plt.title('Ridge Regression Model Predictions')
plt.legend()
plt.show()
# Plot the model predictions
plot_model(ridge1_reg, X, y, X_test, y_test, y1_predict)
总结
正则化在机器学习中扮演着至关重要的角色,尤其是在防止过拟合和提高模型泛化能力方面。它通过在损失函数中引入额外的约束或惩罚项,限制模型的复杂度,从而使模型更能够适应未见过的数据。这一过程不仅仅是为了提升模型的性能,更是为了确保模型的稳定性和鲁棒性。
在实际应用中,正则化方法的选取和调参是一个需要谨慎处理的环节。不同的正则化方法适用于不同类型的数据和模型。例如,L1正则化适合用于特征选择,而L2正则化更适合处理高维数据中的多重共线性问题。弹性网络正则化则结合了两者的优点,在许多情况下表现出色。对于深度学习中的神经网络,Dropout、批量归一化和数据增强等技术则是不可或缺的。
然而,正则化并非万能。过度正则化可能导致欠拟合,使模型无法充分学习到数据的特征。因此,找到正则化强度的平衡点至关重要。这通常需要通过交叉验证等方法来进行超参数调优,以找到最佳的正则化强度。随着数据量的增加和模型复杂度的提升,正则化技术将继续发展和演进。研究人员和实践者需要不断探索和创新,以应对新挑战,确保模型在复杂环境中的性能和可靠性。总之,正则化是机器学习中不可或缺的一部分,其合理应用是构建高性能模型的关键。