目录
- 过拟合问题与正则化的提出
- 1.1 过拟合的本质
- 1.2 正则化的基本思想
- L1正则化(Lasso回归)
- 2.1 数学定义
- 2.2 L1正则化的数学原理
- 2.3 L1正则化的特性
- L2正则化(Ridge回归)
- 3.1 数学定义
- 3.2 L2正则化的数学原理
- 3.3 L2正则化的特性
- L1与L2正则化的对比
- 4.1 数学特性对比
- 4.2 应用场景对比
- 4.3 案例分析
- 实现带有不同正则项的逻辑回归模型
- 5.1 数据准备
- 5.2 实现不同正则化的逻辑回归
- 5.3 性能评估
- 5.4 可视化权重分布
- 5.5 正则化强度的影响
- 5.6 从头实现带正则化的逻辑回归
- 总结与展望
在机器学习的旅程中,我们常常追求构建能够洞察数据本质、预测未来的模型。然而,模型并非越复杂越好,过度的复杂性往往会导致一个令人头疼的问题——过拟合。想象一下,一位学霸在考试前夜死记硬背了所有习题答案,结果考试时题目稍作变化就束手无策。机器学习模型也是如此,当它过度专注于训练数据的细节和噪声时,就会失去对新数据的泛化能力。
1. 过拟合问题与正则化的提出
1.1 过拟合的本质
过拟合,简单来说,就是模型在训练集上表现得过于优秀,但在未见过的测试集上却表现糟糕。它就像是模型“记住”了训练集中的每一个细节,包括那些无关紧要的噪声,而不是学习到数据背后真正的模式。
从数学的角度来看,过拟合通常与模型参数的Magnitude过大有关。参数越大,模型曲线就可能越弯曲,越能拟合训练数据中的每一个点,但也更容易受到噪声的影响。
我们可以用一个形象的例子来理解。假设我们想要用多项式拟合一组数据点,这些数据点实际上呈现线性关系。如果使用高阶多项式,模型可能会为了穿过每一个训练点而变得曲折蜿蜒,最终完美拟合训练集,但这条曲线在新数据上的表现很可能惨不忍睹。
1.2 正则化的基本思想
为了对抗过拟合这只“拦路虎”,正则化技术应运而生。它的核心思想是在模型的损失函数中加入一个正则化项(惩罚项),如同给模型的学习过程加上一道“紧箍咒”,限制模型的复杂度,使其参数值保持在一个合理的范围内。
正则化的数学表达简洁而优雅:
J r e g u l a r i z e d ( θ ) = J ( θ ) + λ R ( θ ) J_{regularized}(\theta) = J(\theta) + \lambda R(\theta) Jregularized(θ)=J(θ)+λR(θ)
让我们来解读一下这个公式:
- J ( θ ) J(\theta) J(θ): 这是原始的损失函数,衡量模型预测与真实值之间的差距。我们的目标是最小化这个差距,让模型尽可能准确地拟合数据。
- R ( θ ) R(\theta) R(θ): 这就是正则化项,它是模型参数 θ \theta θ 的函数,用来惩罚模型的复杂度。常见的正则化项有L1正则化和L2正则化。
- λ \lambda λ: 这是正则化强度,也称为正则化系数,是一个超参数,用于控制正则化的程度。 λ \lambda λ 越大,正则化效果越强,模型参数越趋向于变小。
通过在损失函数中加入正则化项,我们希望模型在追求最小化预测误差的同时,也尽量保持参数的“苗条”,避免过度膨胀,从而提高模型的泛化能力。
2. L1正则化(Lasso回归)
L1正则化,又名 Lasso (Least Absolute Shrinkage and Selection Operator) 回归,以其能够产生稀疏解的特性而备受青睐。它就像一位精明的特征选择专家,能够自动剔除模型中不重要的特征,让模型更加简洁高效。
2.1 数学定义
L1正则化的正则化项定义为模型参数绝对值之和,用数学公式表达如下:
R ( θ ) = ∑ i = 1 n ∣ θ i ∣ R(\theta) = \sum_{i=1}^{n} |\theta_i| R(θ)=i=1∑n∣θi∣
将L1正则化项加入到原始损失函数中,我们就得到了带L1正则化的损失函数:
J L 1 ( θ ) = J ( θ ) + λ ∑ i = 1 n ∣ θ i ∣ J_{L1}(\theta) = J(\theta) + \lambda \sum_{i=1}^{n} |\theta_i| JL1(θ)=J(θ)+λi=1∑n∣θi∣
2.2 L1正则化的数学原理
L1正则化之所以能够产生稀疏解,与其独特的几何特性密不可分。我们可以从优化的角度来理解这一现象。
当我们最小化带有L1正则项的损失函数时,实际上是在原始损失函数的等高线和以原点为中心的L1球(在二维空间中表现为菱形)的交点处寻找最优解。
由于L1球的菱角往往与坐标轴相交,因此最优解更容易出现在坐标轴上,这意味着某些参数的值会变为零。参数为零就意味着对应的特征被模型“抛弃”了,从而实现了特征选择的效果。
在梯度下降的优化过程中,L1正则化的梯度也体现了其特性:
∂ ∂ θ j J L 1 ( θ ) = ∂ ∂ θ j J ( θ ) + λ sign ( θ j ) \frac{\partial}{\partial \theta_j} J_{L1}(\theta) = \frac{\partial}{\partial \theta_j} J(\theta) + \lambda \text{sign}(\theta_j) ∂θj∂JL1(θ)=∂θj∂J(θ)+λsign(θj)
其中, sign ( θ j ) \text{sign}(\theta_j) sign(θj) 是符号函数,它的作用是为每个参数施加一个恒定大小的“力”,方向取决于参数的符号,始终指向零点。当参数 θ j \theta_j θj 为正时,梯度会减去 λ \lambda λ,当参数 θ j \theta_j θj 为负时,梯度会加上 λ \lambda λ。这种“推向零”的力量对于较小的参数来说尤其明显,很容易将它们直接推到零点,从而实现稀疏化。
2.3 L1正则化的特性
总结一下L1正则化的特性:
- 特征选择 (Feature Selection):L1正则化最突出的特点就是能够自动进行特征选择,将不重要特征的权重压缩至零,保留重要特征,简化模型。
- 稀疏性 (Sparsity):L1正则化倾向于产生稀疏解,即模型的大部分参数为零。稀疏模型不仅更易于理解和解释,而且在存储和计算上也更高效。
- 鲁棒性 (Robustness):由于L1范数对异常值不敏感,因此L1正则化模型对数据中的异常值具有一定的鲁棒性。
- 非平滑性 (Non-smoothness):L1范数在原点处不可微,这给优化算法带来了一些挑战,例如不能直接使用标准的梯度下降法,需要使用次梯度下降等方法。但现代优化库通常已经很好地解决了这个问题。
3. L2正则化(Ridge回归)
L2正则化,又称为 Ridge回归 或 权重衰减 (Weight Decay),是另一种常用的正则化技术。与L1正则化不同,L2正则化不会产生稀疏解,而是使模型的所有参数都趋向于变小,但通常不会变为零。
3.1 数学定义
L2正则化的正则化项定义为模型参数的平方和,数学公式如下:
R ( θ ) = ∑ i = 1 n θ i 2 R(\theta) = \sum_{i=1}^{n} \theta_i^2 R(θ)=i=1∑nθi2
将L2正则化项加入到原始损失函数中,我们就得到了带L2正则化的损失函数:
J L 2 ( θ ) = J ( θ ) + λ ∑ i = 1 n θ i 2 J_{L2}(\theta) = J(\theta) + \lambda \sum_{i=1}^{n} \theta_i^2 JL2(θ)=J(θ)+λi=1∑nθi2
3.2 L2正则化的数学原理
L2正则化的作用是使模型参数均匀地缩小,但通常不会压缩至零。这同样可以从几何角度和优化过程来理解。
当我们最小化带有L2正则项的损失函数时,我们是在原始损失函数的等高线和以原点为中心的L2球(在二维空间中表现为圆形)的交点处寻找最优解。
与菱形不同,圆形的边界在各个方向上都是平滑的,与等高线的交点通常不会落在坐标轴上,因此L2正则化很少将参数压缩为零。
在梯度下降的优化过程中,L2正则化的梯度为:
∂ ∂ θ j J L 2 ( θ ) = ∂ ∂ θ j J ( θ ) + 2 λ θ j \frac{\partial}{\partial \theta_j} J_{L2}(\theta) = \frac{\partial}{\partial \theta_j} J(\theta) + 2\lambda \theta_j ∂θj∂JL2(θ)=∂θj∂J(θ)+2λθj
可以看到,L2正则化对参数的惩罚力度与参数值的大小成正比。参数值越大,惩罚越大,参数向零收缩的“力量”也越大。但当参数接近零时,这个力量也会减小,因此参数会趋近于零,但通常不会精确地变为零。这就是“权重衰减”名称的由来。
3.3 L2正则化的特性
总结L2正则化的特性:
- 权重衰减 (Weight Decay):L2正则化使所有权重都向零收缩,但通常不会变为零。它通过减小参数的Magnitude来降低模型的复杂度。
- 平滑性 (Smoothness):L2范数处处可微,这使得基于梯度的优化算法(如梯度下降法)能够更稳定、更高效地工作。
- 处理多重共线性 (Multicollinearity):当特征之间存在高度相关性(多重共线性)时,L2正则化表现良好。它倾向于将相关特征的权重均匀地分散,而不是像L1正则化那样只选择一个特征并完全忽略其他相关特征。
- 计算效率 (Computational Efficiency):由于L2范数是处处可微的,且计算简单,因此L2正则化在计算上通常比L1正则化更高效。
4. L1与L2正则化的对比
L1和L2正则化都是有效的正则化技术,但它们有着不同的数学特性和应用场景。理解它们的差异,有助于我们在实际问题中做出更明智的选择。
4.1 数学特性对比
我们可以从以下几个方面对比L1和L2正则化的数学特性:
特性 | L1正则化 | L2正则化 |
---|---|---|
正则化项 | $\sum_{i=1}^{n} | \theta_i |
几何形状 | 菱形 (L1球) | 圆形 (L2球) |
导数 | sign ( θ j ) \text{sign}(\theta_j) sign(θj) | 2 θ j 2\theta_j 2θj |
可微性 | 在原点不可微 | 处处可微 |
解的特性 | 稀疏解 (参数趋向于零) | 非稀疏解 (参数趋向于小但不为零) |
特征选择 | 自动进行特征选择,产生稀疏模型 | 不进行特征选择,所有特征权重都缩小 |
优化难度 | 相对较难,原点不可微,可能需要次梯度下降等方法 | 相对容易,处处可微,可以使用标准梯度下降法 |
对异常值敏感性 | 相对鲁棒 | 相对敏感 |
4.2 应用场景对比
根据L1和L2正则化的特性,我们可以总结它们各自更适合的应用场景:
L1正则化适用于:
- 特征数量庞大,需要进行特征选择的场景:例如,文本分类、基因分析等领域,特征维度很高,但只有少数特征真正重要。
- 希望得到稀疏模型的场景:稀疏模型更简洁、可解释性更强,且计算效率更高。
- 数据中存在大量无关特征的场景:L1正则化可以自动剔除这些无关特征,提高模型的泛化能力。
L2正则化适用于:
- 几乎所有特征都可能对结果有贡献的场景:例如,图像识别、语音识别等领域,每个像素或频谱分量都可能包含信息。
- 数据中存在多重共线性的场景:L2正则化可以有效地缓解多重共线性带来的模型不稳定问题。
- 希望模型更加稳定、泛化能力更强的场景:L2正则化通过缩小所有参数,降低模型的整体复杂度,提高泛化能力。
4.3 案例分析
让我们通过一个案例来更直观地理解L1和L2正则化的应用场景。
案例:房价预测
假设我们有一个房价预测问题,数据集包含房屋的100个特征,例如面积、卧室数量、地理位置、周边设施、房屋朝向、建筑材料等等。
-
情景一:特征选择。如果我们认为只有少数几个关键特征(例如面积、位置、房龄)真正决定房价,而其他特征可能是噪声或冗余信息,那么 L1正则化 是一个更好的选择。它可以帮助我们自动筛选出最重要的特征,构建一个简洁而有效的房价预测模型。
-
情景二:所有特征都相关。如果我们在另一个城市进行房价预测,那里的房价可能受到更多因素的综合影响,例如房屋的每一个细节都可能影响买家的心理价位。这时,L2正则化 可能更合适。它可以缩小所有特征的权重,避免模型过度依赖于某些特征,从而提高模型的稳定性和泛化能力。
当然,在实际应用中,我们通常需要根据具体问题和数据特点来选择合适的正则化方法,甚至可以尝试 弹性网络 (Elastic Net),它结合了L1和L2正则化的优点,通过调节 L1_ratio 参数来平衡两种正则化的效果。
5. 实现带有不同正则项的逻辑回归模型
理论学习固然重要,但实践才是检验真理的唯一标准。接下来,我们将通过Python代码实现带有L1和L2正则化的逻辑回归模型,并比较它们在合成数据集上的表现。
5.1 数据准备
首先,我们生成一个合成数据集,其中包含100个特征,但只有前5个特征与目标变量真正相关。这模拟了真实世界中特征冗余的情况,可以用来检验正则化方法的特征选择能力。
import numpy as np
import matplotlib.pyplot as plt
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, roc_auc_score
import pandas as pd
# 设置随机种子,保证实验可重复性
np.random.seed(42)
# 生成合成数据
n_samples = 1000 # 样本数量
n_features = 100 # 特征数量
# 生成特征矩阵,只有前5个特征是有用的
X = np.random.randn(n_samples, n_features) # 生成服从标准正态分布的随机特征
# 生成目标变量,只依赖于前5个特征
true_weights = np.zeros(n_features) # 初始化权重向量为零
true_weights[:5] = np.random.randn(5) # 前5个特征赋予随机权重
z = np.dot(X, true_weights) # 线性组合特征和权重
prob = 1 / (1 + np.exp(-z)) # Sigmoid函数,将线性输出转换为概率
y = np.random.binomial(1, prob) # 根据概率生成二元标签 (0或1)
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 标准化特征,提高模型训练效率和稳定性
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train) # 训练集上拟合标准化模型并转换
X_test = scaler.transform(X_test) # 测试集上使用训练集拟合的标准化模型进行转换
这段代码首先生成了一个包含1000个样本和100个特征的合成数据集。关键在于 true_weights
的设置,我们只给前5个特征赋予了随机权重,其余特征的真实权重为零。这意味着理想的模型应该只关注前5个特征,而忽略后面的95个特征。
接着,我们将数据集划分为训练集和测试集,并使用 StandardScaler
对特征进行标准化处理。特征标准化是机器学习预处理的重要步骤,可以加速梯度下降的收敛,并提高模型的稳定性。
5.2 实现不同正则化的逻辑回归
接下来,我们使用 scikit-learn 库中的 LogisticRegression
类,分别实现不带正则化、带L1正则化、带L2正则化以及弹性网络正则化的逻辑回归模型。
# 不带正则化的逻辑回归
lr_no_reg = LogisticRegression(penalty=None, random_state=42, max_iter=1000)
lr_no_reg.fit(X_train, y_train) # 使用训练数据拟合模型
y_pred_no_reg = lr_no_reg.predict(X_test) # 预测测试集标签
y_prob_no_reg = lr_no_reg.predict_proba(X_test)[:, 1] # 预测测试集概率 (正类概率)
# L1正则化的逻辑回归
lr_l1 = LogisticRegression(penalty='l1', C=1.0, solver='liblinear', random_state=42, max_iter=1000)
lr_l1.fit(X_train, y_train)
y_pred_l1 = lr_l1.predict(X_test)
y_prob_l1 = lr_l1.predict_proba(X_test)[:, 1]
# L2正则化的逻辑回归
lr_l2 = LogisticRegression(penalty='l2', C=1.0, random_state=42, max_iter=1000)
lr_l2.fit(X_train, y_train)
y_pred_l2 = lr_l2.predict(X_test)
y_prob_l2 = lr_l2.predict_proba(X_test)[:, 1]
# 弹性网络(Elastic Net,结合L1和L2)
lr_elastic = LogisticRegression(penalty='elasticnet', C=1.0, solver='saga',
l1_ratio=0.5, random_state=42, max_iter=1000)
lr_elastic.fit(X_train, y_train)
y_pred_elastic = lr_elastic.predict(X_test)
y_prob_elastic = lr_elastic.predict_proba(X_test)[:, 1]
在上面的代码中,我们通过 penalty
参数来指定正则化类型:
penalty=None
:不使用正则化。penalty='l1'
:使用L1正则化 (Lasso回归)。 注意solver
参数需要设置为 ‘liblinear’ 或 ‘saga’ 等支持L1正则化的求解器。penalty='l2'
:使用L2正则化 (Ridge回归)。 这是LogisticRegression
的默认选项。penalty='elasticnet'
:使用弹性网络正则化。l1_ratio
参数控制L1正则化和L2正则化的混合比例。l1_ratio=0.5
表示L1和L2正则化各占一半。solver
需要设置为 ‘saga’。
C
参数是正则化强度的倒数,C=1.0
表示使用默认的正则化强度。random_state
用于设置随机种子,保证实验的可重复性。max_iter
设置最大迭代次数。
5.3 性能评估
我们使用准确率 (Accuracy) 和 ROC曲线下面积 (AUC) 作为评估指标,来衡量不同正则化模型的性能。同时,我们还统计了各个模型中非零权重的数量,以考察L1正则化的特征选择效果。
# 计算性能指标
results = pd.DataFrame({
'Model': ['No Regularization', 'L1 Regularization', 'L2 Regularization', 'Elastic Net'],
'Accuracy': [
accuracy_score(y_test, y_pred_no_reg), # 计算准确率
accuracy_score(y_test, y_pred_l1),
accuracy_score(y_test, y_pred_l2),
accuracy_score(y_test, y_pred_elastic)
],
'AUC': [
roc_auc_score(y_test, y_prob_no_reg), # 计算AUC值
roc_auc_score(y_test, y_prob_l1),
roc_auc_score(y_test, y_prob_l2),
roc_auc_score(y_test, y_prob_elastic)
]
})
print(results) # 打印性能指标表格
# 计算非零权重的数量
n_nonzero_no_reg = np.sum(lr_no_reg.coef_ != 0) # 统计无正则化模型非零权重数量
n_nonzero_l1 = np.sum(lr_l1.coef_ != 0) # 统计L1正则化模型非零权重数量
n_nonzero_l2 = np.sum(lr_l2.coef_ != 0) # 统计L2正则化模型非零权重数量
n_nonzero_elastic = np.sum(lr_elastic.coef_ != 0) # 统计弹性网络模型非零权重数量
print("\n非零权重数量:")
print(f"No Regularization: {n_nonzero_no_reg}")
print(f"L1 Regularization: {n_nonzero_l1}")
print(f"L2 Regularization: {n_nonzero_l2}")
print(f"Elastic Net: {n_nonzero_elastic}")
运行这段代码,我们可以得到不同模型的性能指标和非零权重数量。在我们的合成数据集上,我们通常会观察到以下现象:
- L1正则化 模型具有最高的稀疏性,非零权重数量最少,验证了L1正则化的特征选择能力。
- L1和L2正则化 模型通常比 无正则化 模型具有更高的泛化性能 (Accuracy 和 AUC),表明正则化有助于缓解过拟合。
- 弹性网络 模型的性能往往介于L1和L2正则化模型之间,可以通过调节
l1_ratio
参数来平衡稀疏性和泛化能力。
5.4 可视化权重分布
为了更直观地比较不同正则化方法对模型权重的影响,我们可以将模型的权重分布可视化出来。
plt.figure(figsize=(12, 10))
# 绘制真实权重
plt.subplot(2, 2, 1)
plt.stem(true_weights) # 绘制茎叶图,用于可视化离散数据
plt.title('True Weights')
plt.xlabel('Feature Index')
plt.ylabel('Weight Value')
# 绘制无正则化的权重
plt.subplot(2, 2, 2)
plt.stem(lr_no_reg.coef_[0]) # 绘制无正则化模型权重
plt.title('No Regularization')
plt.xlabel('Feature Index')
plt.ylabel('Weight Value')
# 绘制L1正则化的权重
plt.subplot(2, 2, 3)
plt.stem(lr_l1.coef_[0]) # 绘制L1正则化模型权重
plt.title('L1 Regularization')
plt.xlabel('Feature Index')
plt.ylabel('Weight Value')
# 绘制L2正则化的权重
plt.subplot(2, 2, 4)
plt.stem(lr_l2.coef_[0]) # 绘制L2正则化模型权重
plt.title('L2 Regularization')
plt.xlabel('Feature Index')
plt.ylabel('Weight Value')
plt.tight_layout() # 自动调整子图参数,避免重叠
plt.show() # 显示图像
这段代码使用 matplotlib
库绘制了四个子图,分别展示了真实权重、无正则化模型权重、L1正则化模型权重和L2正则化模型权重。通过对比这些子图,我们可以清晰地看到:
- 无正则化模型 的权重分布较为分散,很多与真实标签无关的特征也获得了较大的权重。
- L1正则化模型 的权重分布非常稀疏,大部分权重被压缩为零,只有少数与真实标签相关的特征保留了较大的权重,体现了特征选择的效果。
- L2正则化模型 的权重分布相对平滑,所有权重都向零收缩,但没有像L1正则化那样产生大量的零权重。
5.5 正则化强度的影响
正则化强度
λ
\lambda
λ (或 LogisticRegression
中的 C
参数,C = 1/λ
) 是一个重要的超参数,它控制着正则化的程度。正则化强度过大或过小都可能影响模型的性能。为了探究正则化强度对模型性能和稀疏性的影响,我们可以进行如下实验:
# 测试不同正则化强度的影响
C_values = [0.001, 0.01, 0.1, 1, 10, 100] # 不同的C值,对应不同的正则化强度 (C越大,正则化越弱)
l1_nonzero = [] # 存储不同C值下L1正则化模型的非零权重数量
l2_nonzero = [] # 存储不同C值下L2正则化模型的非零权重数量
l1_accuracy = [] # 存储不同C值下L1正则化模型的准确率
l2_accuracy = [] # 存储不同C值下L2正则化模型的准确率
for C in C_values:
# L1正则化
lr_l1 = LogisticRegression(penalty='l1', C=C, solver='liblinear', random_state=42, max_iter=1000)
lr_l1.fit(X_train, y_train)
l1_nonzero.append(np.sum(lr_l1.coef_ != 0)) # 记录非零权重数量
l1_accuracy.append(accuracy_score(y_test, lr_l1.predict(X_test))) # 记录准确率
# L2正则化
lr_l2 = LogisticRegression(penalty='l2', C=C, random_state=42, max_iter=1000)
lr_l2.fit(X_train, y_train)
l2_nonzero.append(np.sum(lr_l2.coef_ != 0)) # 记录非零权重数量
l2_accuracy.append(accuracy_score(y_test, lr_l2.predict(X_test))) # 记录准确率
plt.figure(figsize=(14, 6))
# 绘制非零权重数量与正则化强度的关系
plt.subplot(1, 2, 1)
plt.semilogx(C_values, l1_nonzero, 'o-', label='L1') # 绘制L1正则化非零权重数量曲线,x轴为对数刻度
plt.semilogx(C_values, l2_nonzero, 's-', label='L2') # 绘制L2正则化非零权重数量曲线,x轴为对数刻度
plt.xlabel('C (1/λ)')
plt.ylabel('Number of Non-Zero Weights')
plt.title('Feature Selection vs. Regularization Strength')
plt.legend() # 显示图例
plt.grid(True) # 显示网格
# 绘制准确率与正则化强度的关系
plt.subplot(1, 2, 2)
plt.semilogx(C_values, l1_accuracy, 'o-', label='L1') # 绘制L1正则化准确率曲线,x轴为对数刻度
plt.semilogx(C_values, l2_accuracy, 's-', label='L2') # 绘制L2正则化准确率曲线,x轴为对数刻度
plt.xlabel('C (1/λ)')
plt.ylabel('Accuracy')
plt.title('Accuracy vs. Regularization Strength')
plt.legend() # 显示图例
plt.grid(True) # 显示网格
plt.tight_layout() # 自动调整子图参数,避免重叠
plt.show() # 显示图像
这段代码遍历了一系列 C
值,分别训练了L1和L2正则化的逻辑回归模型,并记录了每个模型在不同正则化强度下的非零权重数量和准确率。然后,我们将这些数据可视化为曲线图,横轴为 C
值 (对数刻度),纵轴分别为非零权重数量和准确率。
通过观察这些曲线图,我们通常会发现:
- 随着
C
值增大 (正则化强度减弱),L1正则化模型的非零权重数量逐渐增加,L2正则化模型的非零权重数量基本保持不变 (接近于特征总数)。 - 在一定的
C
值范围内,L1和L2正则化模型的准确率都高于无正则化模型,且随着C
值的变化,准确率会呈现先升高后降低的趋势,表明存在一个最优的正则化强度。 - 当
C
值过大 (正则化强度过弱) 时,模型的准确率可能会下降,表明模型可能过拟合。 - 当
C
值过小 (正则化强度过强) 时,模型的准确率也可能会下降,表明模型可能欠拟合。
因此,在实际应用中,我们需要通过 交叉验证 等方法来选择合适的正则化强度,以达到模型性能和稀疏性之间的最佳平衡。
5.6 从头实现带正则化的逻辑回归
为了更深入地理解正则化的工作原理,我们不妨从头开始实现一个带有L1和L2正则化的逻辑回归模型。这部分代码将展示如何手动计算损失函数、梯度,并使用梯度下降法进行优化。
class CustomLogisticRegression:
def __init__(self, penalty='none', C=1.0, l1_ratio=0.5, lr=0.01, max_iter=1000, tol=1e-4, random_state=42):
self.penalty = penalty # 正则化类型 ('none', 'l1', 'l2', 'elasticnet')
self.C = C # 正则化强度的倒数
self.l1_ratio = l1_ratio # 弹性网络中L1正则化比例
self.lr = lr # 学习率
self.max_iter = max_iter # 最大迭代次数
self.tol = tol # 收敛容忍度
self.random_state = random_state # 随机种子
self.coef_ = None # 模型权重系数
self.intercept_ = None # 模型截距
def _sigmoid(self, z):
return 1 / (1 + np.exp(-z)) # Sigmoid函数
def _loss_grad(self, X, y, coef):
z = np.dot(X, coef) # 线性组合
y_prob = self._sigmoid(z) # 预测概率
loss = -np.mean(y * np.log(y_prob + 1e-8) + (1 - y) * np.log(1 - y_prob + 1e-8)) # 交叉熵损失函数
grad = np.dot(X.T, (y_prob - y)) / len(y) # 交叉熵损失函数梯度
# 添加正则化项和梯度
if self.penalty == 'l1':
loss += (self.C / 2) * np.sum(np.abs(coef)) # L1正则化项
grad += (self.C / 2) * np.sign(coef) # L1正则化梯度
elif self.penalty == 'l2':
loss += (self.C / 2) * np.sum(coef ** 2) # L2正则化项
grad += self.C * coef # L2正则化梯度
elif self.penalty == 'elasticnet':
loss += (self.C / 2) * (self.l1_ratio * np.sum(np.abs(coef)) + (1 - self.l1_ratio) * np.sum(coef ** 2)) # 弹性网络正则化项
grad += (self.C / 2) * (self.l1_ratio * np.sign(coef) + 2 * (1 - self.l1_ratio) * coef) # 弹性网络正则化梯度
return loss, grad
def fit(self, X, y):
np.random.seed(self.random_state) # 设置随机种子
n_features = X.shape[1] # 特征数量
self.coef_ = np.random.randn(n_features) # 初始化权重系数
self.intercept_ = 0.0 # 初始化截距
X_b = np.c_[X, np.ones(len(X))] # 添加偏置项 (全为1的列)
initial_coef = np.r_[self.coef_, self.intercept_] # 合并权重系数和截距
coef = initial_coef.copy() # 复制初始系数
losses = [] # 存储损失值
for iteration in range(self.max_iter):
current_coef = coef[:-1].copy() # 当前迭代的权重系数 (不包含截距)
loss, grad_coef = self._loss_grad(X_b, y, current_coef) # 计算损失和梯度
grad = np.r_[grad_coef, np.array([np.mean(y_prob - y)])] # 梯度,截距部分的梯度为预测概率与真实标签之差的均值
coef = coef - self.lr * grad # 梯度下降更新系数
losses.append(loss) # 记录损失值
if iteration > 1 and np.abs(losses[-2] - losses[-1]) < self.tol: # 检查是否收敛
break
self.coef_ = coef[:-1] # 提取权重系数
self.intercept_ = coef[-1] # 提取截距
return self
def predict_proba(self, X):
X_b = np.c_[X, np.ones(len(X))] # 添加偏置项
z = np.dot(X_b, np.r_[self.coef_, self.intercept_]) # 线性组合
return self._sigmoid(z) # 返回预测概率
def predict(self, X, threshold=0.5):
y_prob = self.predict_proba(X) # 预测概率
return (y_prob >= threshold).astype(int) # 根据阈值将概率转换为标签 (0或1)
这个 CustomLogisticRegression
类实现了以下功能:
- 支持 L1, L2 和 Elastic Net 正则化:通过
penalty
参数选择正则化类型,通过C
和l1_ratio
参数控制正则化强度。 - 交叉熵损失函数:使用交叉熵损失函数作为优化目标。
- 梯度下降法:使用批量梯度下降法进行模型优化。
- Sigmoid 激活函数:使用 Sigmoid 函数将线性输出转换为概率。
- 预测概率和预测标签:提供
predict_proba
和predict
方法进行预测。
我们可以使用这个自定义的 CustomLogisticRegression
类来训练带正则化的逻辑回归模型,并与 scikit-learn 库中的 LogisticRegression
进行比较,验证我们实现的正确性。
# 使用自定义逻辑回归模型进行训练和预测 (L1正则化)
custom_lr_l1 = CustomLogisticRegression(penalty='l1', C=1.0, lr=0.1, max_iter=2000, tol=1e-4, random_state=42)
custom_lr_l1.fit(X_train, y_train)
y_pred_custom_l1 = custom_lr_l1.predict(X_test)
y_prob_custom_l1 = custom_lr_l1.predict_proba(X_test)
# 评估自定义L1正则化逻辑回归模型性能
print("\nCustom L1 Regularization Logistic Regression:")
print(f"Accuracy: {accuracy_score(y_test, y_pred_custom_l1)}")
print(f"AUC: {roc_auc_score(y_test, y_prob_custom_l1)}")
print(f"Non-zero weights: {np.sum(custom_lr_l1.coef_ != 0)}")
# 使用自定义逻辑回归模型进行训练和预测 (L2正则化)
custom_lr_l2 = CustomLogisticRegression(penalty='l2', C=1.0, lr=0.1, max_iter=2000, tol=1e-4, random_state=42)
custom_lr_l2.fit(X_train, y_train)
y_pred_custom_l2 = custom_lr_l2.predict(X_test)
y_prob_custom_l2 = custom_lr_l2.predict_proba(X_test)
# 评估自定义L2正则化逻辑回归模型性能
print("\nCustom L2 Regularization Logistic Regression:")
print(f"Accuracy: {accuracy_score(y_test, y_pred_custom_l2)}")
print(f"AUC: {roc_auc_score(y_test, y_prob_custom_l2)}")
print(f"Non-zero weights: {np.sum(custom_lr_l2.coef_ != 0)}")
通过比较自定义模型和 scikit-learn 库模型的性能指标,我们可以验证自定义实现的正确性,并更深入地理解正则化在模型训练过程中的作用。
6. 总结与展望
正则化技术是机器学习工具箱中不可或缺的利器,它能够有效地对抗过拟合,提高模型的泛化能力和鲁棒性。L1和L2正则化作为两种最常用的正则化方法,各有千秋,适用于不同的应用场景。
- L1正则化 擅长特征选择,能够产生稀疏模型,适用于高维稀疏数据和需要模型解释性的场景。
- L2正则化 倾向于产生平滑的模型,对多重共线性问题具有一定的缓解作用,适用于大多数场景,特别是当所有特征都可能相关时。
- 弹性网络 结合了L1和L2正则化的优点,可以通过调节混合比例来适应不同的数据和任务。
在实际应用中,选择合适的正则化方法和强度通常需要进行实验和调优。交叉验证是一种常用的超参数选择方法。此外,还有诸如 Dropout、Batch Normalization 等更高级的正则化技术,它们在深度学习领域发挥着重要作用。
希望本文能够帮助读者深入理解正则化技术的数学原理和应用方法,在机器学习的实践中更加游刃有余!