机器学习损失函数:L1与L2损失函数原理、对比与实战选型指南

📅 发布时间:2026/8/15 6:17:03
机器学习损失函数:L1与L2损失函数原理、对比与实战选型指南 1. 损失函数模型训练的“裁判”与“教练”在机器学习与深度学习的项目实践中我们常常会听到一个词损失函数。它就像一位严格的裁判时刻评判着模型预测结果的好坏又像一位耐心的教练指引着模型朝着正确的方向调整。今天我们就来深入聊聊两位在回归任务中最常见、也最核心的“裁判”——平均绝对值误差和均方误差也就是大家常说的L1 Loss和L2 Loss。无论你是刚入门的新手还是在调参路上摸索的从业者理解这两个损失函数的本质、差异以及适用场景都至关重要。它们不仅仅是公式更是你设计模型、诊断问题、优化性能的关键工具。选择哪一个往往决定了你的模型在面对异常值时的“抗压能力”也影响了模型参数更新的“性格”。接下来我将结合多年的实战经验为你拆解这两个损失函数的方方面面从数学原理到代码实现从优缺点对比到实战选型让你不仅会用更懂为何而用。2. L1 Loss 与 L2 Loss 的数学本质与直观理解2.1 平均绝对值误差稳健的“城市街区距离”平均绝对值误差顾名思义计算的是预测值与真实值之间绝对差值的平均数。它的数学表达式非常直观MAE (1/n) * Σ|y_i - ŷ_i|其中n是样本数量y_i是第i个样本的真实值ŷ_i是模型对应的预测值Σ表示对所有样本求和。这个公式描述的就是L1 范数下的误差。为什么叫“城市街区距离”呢想象一下你在曼哈顿的网格状街道上从A点走到B点你不能斜穿大楼只能沿着街道走直角你走过的总距离就是横向距离和纵向距离的绝对值之和。MAE 计算的就是这种“拐来拐去”的直线距离它对每个点的误差都一视同仁给予相同的权重。它的核心特性是稳健性。因为使用了绝对值单个样本上的大误差比如因为数据录入错误产生的异常值不会被平方放大从而对整个损失函数的影响相对较小。这使得 MAE 对数据中的异常值不那么敏感。在业务场景中如果你的数据清洗不够彻底或者业务本身就会偶尔产生一些“离群点”比如金融交易中的偶发大额错误、传感器偶尔的失灵使用 MAE 作为损失函数可以防止模型为了拟合这些极少数异常点而“带偏”了整体趋势。2.2 均方误差强调大误差的“欧几里得距离”均方误差计算的是预测值与真实值之间差值的平方的平均数。它的表达式是MSE (1/n) * Σ(y_i - ŷ_i)²这对应的是L2 范数下的误差。回到距离的比喻这次我们身处一片开阔的草原从A点到B点我们可以走直线。这个直线距离就是欧几里得距离计算的是两点间的平方和再开方在 MSE 中我们只取平方部分因为开方不影响单调性便于求导。关键在于“平方”这个操作。平方操作带来了一个核心特性放大大的误差。如果一个样本的预测误差是 10在 MAE 里它贡献 10在 MSE 里它贡献 100。如果另一个样本误差是 1在 MAE 里贡献 1在 MSE 里只贡献 1。这意味着MSE 会极度关注那些预测得很差的样本模型在训练时会想尽办法首先去减少这些大误差哪怕牺牲一些小误差的精度。这使得 MSE 对异常值非常敏感。一个异常值会因其巨大的平方项而主导整个损失函数导致模型训练过程被这个异常点“绑架”。但在很多情况下这恰恰是我们想要的当数据质量很高几乎没有异常值并且我们绝对不能容忍出现巨大偏差的预测时比如预测桥梁压力一个小误差的平方后影响被放大迫使模型必须严防死守大误差MSE 是更优的选择。同时由于其光滑可导的性质导数连续在优化计算上往往比 MAE 更高效、稳定。2.3 从导数看优化行为的差异理解损失函数如何指导模型学习关键要看它的导数梯度。梯度决定了参数更新的方向和步长。L1 Loss 的导数d(MAE)/d(ŷ) sign(y - ŷ)。导数是误差的符号函数取值只有 -1 0 1。这意味着无论误差大小是 10 还是 0.1参数更新的步长幅度只由学习率决定方向由误差符号决定。这好比一个“匀速”调整的教练不管错得多离谱每次调整的力度都一样大。这带来了训练的稳定性但也可能导致在误差接近零时因为导数不连续从-1跳变到1而产生震荡收敛速度变慢。L2 Loss 的导数d(MSE)/d(ŷ) 2*(ŷ - y)。导数值与误差本身的大小成正比。误差越大梯度越大参数更新的步长也就越大。这就像一个“加速”调整的教练错得越厉害纠正的力度就越大。这使得 MSE 在初始阶段收敛通常很快但在接近最优解时过大的梯度可能导致在最小值点附近反复震荡需要仔细调整学习率。注意在实际的深度学习框架中对于 MAE 在零点不可导的问题通常采用次梯度等方法处理在实现上我们无需担心但理解其数学特性有助于我们分析训练曲线。3. 核心差异深度对比与场景选型指南理解了基本概念我们通过一个详细的对比表格将 L1 Loss 和 L2 Loss 的核心差异一目了然地呈现出来这是你进行选型决策的速查手册。特性维度平均绝对值误差均方误差数学形式MAE mean(|y - ŷ|)MSE mean((y - ŷ)²)别名L1 Loss, MAEL2 Loss, MSE对异常值的敏感性不敏感稳健非常敏感损失曲面性质在零点不可导整体呈“V”字形尖锥处处可导光滑的二次曲面碗状梯度特性梯度恒定符号函数与误差大小无关梯度与误差大小成正比误差大则梯度大收敛速度在误差较大时相对较慢近零点可能震荡初始收敛快但近最优点可能因步长过大而震荡最优解指向指向条件中位数指向条件均值计算复杂度略低无需平方运算略高需要平方运算主要优势对异常值鲁棒解更稳定数学性质好凸且光滑优化效率高强调惩罚大误差主要劣势在零点优化困难收敛可能较慢容易被异常值主导导致模型偏离3.1 如何根据场景选择从数据特性出发选择 L1 还是 L2不是一个孰优孰劣的问题而是一个场景匹配的问题。我的经验是首先问自己以下几个关于数据的问题我的数据中是否存在明显的异常值是且无法彻底清洗优先考虑L1 Loss。例如房价预测中偶尔混入的单价为0或极高的记录用户行为数据中由机器人或脚本产生的非正常点击流。否或已严格清洗L2 Loss是安全且高效的选择。例如实验室环境下生成的物理仿真数据、经过严格验证的金融时间序列数据。我的业务是否能容忍个别的大误差还是必须严格限制最大误差可以容忍个别大误差更关注整体平均表现L1 Loss更合适。例如预测一个大型电商网站明天的总销售额个别商品的预测严重失准对总和影响不大我们更希望总和准确。绝对不能出现大的预测偏差L2 Loss更合适。例如自动驾驶中预测与前车的距离、医疗影像中预测肿瘤尺寸一个大的误差可能导致严重后果必须用平方项对其进行严厉惩罚。我追求的是预测值的平均数还是中位数这是一个统计学视角。L2 Loss 最小化时预测值会逼近真实值的条件期望均值。L1 Loss 最小化时预测值会逼近真实值的条件中位数。中位数对极端值不敏感。如果你的目标变量分布严重偏斜例如个人收入数据少数高收入者拉高了均值那么预测中位数L1可能比预测均值L2更具现实意义。3.2 实战心得不止二选一在实际项目中我们常常有更灵活的策略组合使用Huber Loss这是一种结合了 L1 和 L2 优点的平滑损失函数。它设定一个阈值 δ。当误差绝对值小于 δ 时采用类似 MSE 的二次形式保证在零点附近光滑易优化当误差大于 δ 时采用类似 MAE 的一次形式降低对异常值的敏感性。这就像一位因材施教的教练对小错误温和纠正对大错误果断处理。在 PyTorch 或 TensorFlow 中可以直接调用HuberLoss。# PyTorch 示例 criterion torch.nn.HuberLoss(delta1.0) # delta 是需要调参的阈值分位数损失当你不仅想预测中位数还想预测其他分位数如 90% 分位数时可以使用分位数损失。这在金融风险价值VaR预测等领域非常有用。先 L2 后 L1在训练初期使用 L2 Loss 快速收敛到一个较优的区域在训练后期切换到 L1 Loss 进行微调以获得更稳健的最终模型。这需要一些训练技巧但有时能取得不错的效果。4. 代码实现、可视化与效果对比理论说得再多不如一行代码和一张图来得直观。我们用一个简单的线性回归例子在合成数据上对比 L1 和 L2 的表现。4.1 生成包含异常值的合成数据import numpy as np import matplotlib.pyplot as plt import torch import torch.nn as nn import torch.optim as optim # 设置随机种子确保结果可复现 torch.manual_seed(42) np.random.seed(42) # 生成正常数据 n_samples 100 X torch.randn(n_samples, 1) * 2 # 特征 true_w, true_b 1.5, 0.8 # 真实参数 y true_w * X true_b torch.randn(n_samples, 1) * 0.5 # 加入高斯噪声的真实值 # 故意添加几个异常值 outlier_indices [10, 50, 90] y[outlier_indices] 10 # 给这三个点加上巨大的正偏差 # 可视化数据 plt.figure(figsize(10, 6)) plt.scatter(X.numpy(), y.numpy(), alpha0.7, labelData (with outliers)) plt.plot(X.numpy(), (true_w * X true_b).numpy(), r--, labelTrue Line, linewidth2) plt.xlabel(Feature X) plt.ylabel(Target y) plt.title(Synthetic Data with Outliers) plt.legend() plt.grid(True) plt.show()4.2 使用 L1 Loss 和 L2 Loss 分别训练模型我们将定义两个相同的简单线性模型分别用 L1 和 L2 损失进行训练。# 定义模型 class LinearRegression(nn.Module): def __init__(self): super().__init__() self.linear nn.Linear(1, 1) # 输入输出维度都是1 def forward(self, x): return self.linear(x) # 准备数据 X_train, y_train X, y # 训练函数 def train_model(loss_name, criterion, model, X, y, epochs1000, lr0.01): optimizer optim.SGD(model.parameters(), lrlr) loss_history [] for epoch in range(epochs): model.train() optimizer.zero_grad() y_pred model(X_train) loss criterion(y_pred, y_train) loss.backward() optimizer.step() loss_history.append(loss.item()) if (epoch 1) % 200 0: print(f{loss_name} - Epoch [{epoch1}/{epochs}], Loss: {loss.item():.4f}) return model, loss_history # 实例化两个模型 model_l1 LinearRegression() model_l2 LinearRegression() # 定义损失函数 criterion_l1 nn.L1Loss() # MAE criterion_l2 nn.MSELoss() # MSE # 训练 L1 模型 print(Training with L1 Loss (MAE):) model_l1_trained, loss_hist_l1 train_model(L1, criterion_l1, model_l1, X_train, y_train) # 训练 L2 模型 (需要重新初始化一个模型) model_l2 LinearRegression() # 重新初始化确保起点公平 print(\nTraining with L2 Loss (MSE):) model_l2_trained, loss_hist_l2 train_model(L2, criterion_l2, model_l2, X_train, y_train)4.3 可视化拟合结果与损失曲线现在让我们看看两个“裁判”指导下的模型最终画出了怎样的回归线。# 获取拟合参数 w_l1, b_l1 model_l1_trained.linear.weight.item(), model_l1_trained.linear.bias.item() w_l2, b_l2 model_l2_trained.linear.weight.item(), model_l2_trained.linear.bias.item() print(fL1 (MAE) 拟合结果: y {w_l1:.3f} * x {b_l1:.3f}) print(fL2 (MSE) 拟合结果: y {w_l2:.3f} * x {b_l2:.3f}) print(f真实参数: y {true_w} * x {true_b}) # 可视化拟合直线 plt.figure(figsize(14, 5)) # 子图1拟合直线对比 plt.subplot(1, 2, 1) plt.scatter(X.numpy(), y.numpy(), alpha0.6, labelData) x_range torch.linspace(X.min(), X.max(), 100).reshape(-1, 1) plt.plot(x_range.numpy(), (true_w * x_range true_b).numpy(), k--, labelTrue Line, linewidth3) plt.plot(x_range.numpy(), model_l1_trained(x_range).detach().numpy(), r-, labelfL1 Fit (MAE), linewidth2) plt.plot(x_range.numpy(), model_l2_trained(x_range).detach().numpy(), b-, labelfL2 Fit (MSE), linewidth2) plt.xlabel(Feature X) plt.ylabel(Target y) plt.title(Model Fitting Comparison with Outliers) plt.legend() plt.grid(True) # 子图2损失下降曲线对比 plt.subplot(1, 2, 2) plt.plot(loss_hist_l1, r-, labelL1 Loss (MAE), alpha0.8) plt.plot(loss_hist_l2, b-, labelL2 Loss (MSE), alpha0.8) plt.xlabel(Training Epoch) plt.ylabel(Loss Value) plt.title(Training Loss Curve Comparison) plt.legend() plt.grid(True) plt.yscale(log) # 使用对数坐标更清晰地观察下降趋势 plt.tight_layout() plt.show()结果分析 从拟合直线图中你可以清晰地看到L2 Loss蓝线明显地被三个异常值“拉偏”了整个直线的斜率发生了改变试图去迁就那些异常高的点。而L1 Loss红线则几乎无视了这些异常值拟合出的直线非常接近真实的黑虚线表现出了极强的稳健性。这正是我们之前理论分析所预测的。从损失曲线图对数坐标可以看到L2 Loss 的初始值非常大因为平方项放大了异常值造成的误差。在整个训练过程中L2 Loss 的下降过程可能更“曲折”因为它要处理几个主导性的巨大误差。而 L1 Loss 的曲线则相对平稳。4.4 扩展到其他相关指标RMSE, MAPE, R²在实际评估模型时我们不仅看训练损失还要看一系列评估指标。这些指标常和 MAE、MSE 一同出现均方根误差就是 MSE 的平方根。RMSE sqrt(MSE)。它的量纲和原始数据y一致比 MSE 更易于解释。例如预测房价MSE 的单位是“元²”而 RMSE 的单位是“元”可以直接理解为“平均来看预测误差大概是多少元”。RMSE 继承了 MSE 对异常值敏感的特性。平均绝对百分比误差MAPE (1/n) * Σ|(y_i - ŷ_i)/y_i|。它衡量的是相对误差非常适合不同量级数据间的比较。比如比较预测销售额和预测用户数的模型哪个相对更准。但它的缺点是当真实值y_i为0或接近0时公式会爆炸无法计算。决定系数 R²R² 1 - (Σ(y_i - ŷ_i)² / Σ(y_i - y_mean)²)。它表示模型能够解释的目标变量方差的比例。取值范围在负无穷到1之间越接近1表示模型拟合越好。R² 是基于 MSE 计算的因此其评估结果也受到异常值的影响。一个完整的模型评估报告通常会同时给出 MAE、RMSE 和 R²。例如 “我们的房价预测模型在测试集上表现如下MAE 为 5.2 万元RMSE 为 7.8 万元R² 为 0.89。MAE 显示平均预测偏差约5万RMSE 略高说明存在一些预测偏差较大的样本但 R² 达到0.89表明模型解释了大部分价格波动。”5. 高级话题与实战避坑指南5.1 自定义损失函数当标准 L1/L2 不够用时有时业务需求需要我们自定义损失函数。例如在预测商品需求时预测过高导致库存积压和预测过低导致销售损失的成本是不同的。这时我们可以基于 L1 Loss 进行加权。class AsymmetricMAELoss(nn.Module): 非对称 MAE 损失函数。 over_cost: 预测过高的单位成本如库存成本 under_cost: 预测过低的单位成本如缺货损失成本 def __init__(self, over_cost1.0, under_cost1.0): super().__init__() self.over_cost over_cost self.under_cost under_cost def forward(self, y_pred, y_true): error y_pred - y_true # 当 error 0 (预测过高)使用 over_cost否则使用 under_cost loss torch.where(error 0, self.over_cost * torch.abs(error), self.under_cost * torch.abs(error)) return loss.mean() # 使用示例假设缺货损失是库存积压成本的2倍 criterion_custom AsymmetricMAELoss(over_cost1.0, under_cost2.0)5.2 训练过程中的常见问题与排查损失值 NaN 或爆炸可能原因L2学习率过大梯度爆炸。特别是当数据中存在巨大异常值时MSE 的梯度会非常大。排查首先检查输入数据进行标准化/归一化。其次使用梯度裁剪torch.nn.utils.clip_grad_norm_。最后降低学习率。可能原因L1虽然较少见但如果学习率设置极高也可能导致参数更新跳跃过大。实操心得在训练初期可以添加一个简单的损失值print或使用torch.isnan(loss).any()进行检查。对于 MSE数据标准化是标配能将特征缩放到合理范围如均值为0方差为1极大提升训练稳定性。模型收敛缓慢或震荡L1 Loss 在零点震荡这是由其导数的不连续性导致的。可以观察到损失曲线在后期下降缓慢并伴有小幅上下波动。解决方案使用动态学习率衰减策略如StepLR,ReduceLROnPlateau。或者考虑切换到 Huber Loss它在零点附近是光滑的。L2 Loss 在最优解附近震荡学习率在训练后期显得过大。解决方案同样使用学习率衰减。或者使用 Adam 等自适应优化器它们能为每个参数调整学习率通常比 SGD 更平滑。如何判断该用 L1 还是 L2—— 一个简单的诊断流程步骤一可视化。永远第一步是画出y_true和y_pred的散点图或误差分布直方图。肉眼观察是否有明显的异常点。步骤二计算指标。在验证集上同时计算 MAE 和 RMSE。比较两者。如果RMSE MAE例如 RMSE 是 MAE 的 2 倍以上说明数据中存在少数但误差非常大的点RMSE 被这些点严重拉高。此时 L2 Loss 可能已被这些点主导应优先尝试 L1 Loss 或 Huber Loss。如果RMSE ≈ MAE说明误差分布相对均匀没有特别突出的离群点L2 Loss 可以放心使用。步骤三业务验证。将模型在极端情况或历史异常数据下的预测结果给业务方评估看哪种损失的预测模式更符合业务直觉和容忍度。5.3 在多任务学习与复杂模型中的应用在复杂的神经网络中L1 和 L2 也常以正则化项的形式出现即L1 正则化Lasso和L2 正则化Ridge。这里不要与损失函数混淆L1 正则化在损失函数后添加模型权重的绝对值之和λ * Σ|w|。它倾向于产生稀疏解即让一部分不重要的特征权重直接变为0常用于特征选择。L2 正则化在损失函数后添加模型权重的平方和λ * Σ|w|²。它倾向于让所有权重均匀地缩小但不会精确为0主要用于防止过拟合使模型更平滑。在深度学习框架中通常直接在优化器里设置weight_decay参数来实现 L2 正则化而 L1 正则化需要手动添加到损失函数中。# L2 正则化权重衰减 - 内置方式 optimizer optim.Adam(model.parameters(), lr0.001, weight_decay1e-5) # weight_decay 就是 λ # L1 正则化 - 手动添加 l1_lambda 0.001 prediction model(data) main_loss criterion(prediction, target) l1_reg torch.tensor(0.) for param in model.parameters(): l1_reg torch.norm(param, p1) # L1 范数 total_loss main_loss l1_lambda * l1_reg total_loss.backward()理解 L1 和 L2 作为损失函数和正则化项的双重角色能让你更全面地驾驭它们在模型复杂度和预测精度之间找到最佳平衡。