此文章由机器翻译。

测试运行

机器学习的 L1 和 L2 规范

James McCaffrey

下载代码示例

James McCaffrey正则化 L1 和 L2 正规化是机器学习 (ML) 训练算法可以用于减少模型拟合的两种密切相关的技术。消除过学习导致做出更好的预测模型。在这篇文章中,我将解释什么正则化是从软件开发人员的角度来看。正则化背后的理念是有点难以解释,并不是因为他们是困难的而是因为那里有几个相互关联的观念

在这篇文章说明了经常化与 logistic 回归 (LR) 的分类,但正则化可用于多种类型的机器学习,特别是神经网络分类。LR 分类的目标是创建一个模型,预测变量,可以采取两个可能值之一。例如,你可能想要预测一支橄榄球队的结果 (失去 = 0,赢得 = 1) 在即将到来的比赛,基于团队的当前的胜率 (1)、 域位置 (x2) 和一些球员缺席因伤 (3)。

如果 Y 预测的值,LR 模型为这一问题将采取的形式:

z = b0 + b1(x1) + b2(x2) + b3(x3)
Y = 1.0 / (1.0 + e^-z)

这里 b0、 b1、 b2 和 b3 有重量,是必须确定的只是数字值。换句话说,你计算等于输入的值次 b 权重值 z,添加 b0 常数,然后将 z 值传递给使用数学常数 e 的方程。 原来 Y 总是会介于 0 和 1 之间。 如果 Y 是小于 0.5,你得出结论预测的输出为 0,如果 Y 大于 0.5 你得出结论的输出是 1。 请注意是否有 n 功能,将 n + 1 b 权重。

例如,假设一个团队目前已赢取的百分比为 0.75,和将被打在他们的对手场 (-1),以及有 3 个球员出损伤。并且假设 b0 = 5.0,b1 = 8.0,b2 = 3.0 和 b3 =-2.0。然后 z = 5.0 + (8.0)(0.75) + (3.0)(-1) + (-2.0)(3) = 2.0,所以 Y = 1.0 / (1.0 + e ^-2.0) = 0.88。因为 Y 大于 0.5,你所预料的队会赢他们即将到来的比赛。

我认为最好的方法来解释正则化是通过检查一个具体的例子。看看在一个演示程序的截图图 1。而不是使用真实数据,该演示程序首先生成 1000 合成数据商品。每个项目都有 12 预测变量 (通常称为毫升术语的"功能")。因变量值是中的最后一列。在创建后的 1,000 数据项目,数据集随机分成了 800 项目训练集,用于查找模型 b 重量和一个 200 项测试集,用于对所生成的模型质量进行评价。

经常化与 Logistic 回归分析分类
图 1 经常化与 Logistic 回归分析分类

接下来,该演示程序训练 LR 分类器,而无需使用正则化。生成的模型有 85.00%的准确率,培训资料和 80.50%对测试数据的准确性。80.50%的准确率是较相关的两个值,也是一个粗略的估计,如何准确你可以期望要用新数据提交时的模型。正如我会解释不久,模型是过分合身,导致平庸的预测精度。

接下来,该演示做一些处理,找到好的 L1 经常化重量和良好的 L2 经常化重量。经常化的重量的正规化进程所使用的单个数值。在这个演示中,一个好的 L1 重量被确定为 0.005 和良好的 L2 重量是 0.001。

演示第一次表演培训使用 L1 经常化,然后再与 L2 经常化。L1 经常化,生成的 LR 模型对试验数据,95.00%的准确率和与 L2 经常化 LR 模型试验数据有 94.50%的准确率。两种形式的正则化显著提高预测精度。

这篇文章假设你有至少中级编程技能,但不会假定你知道任何关于 L1 或 L2 经常化。该演示程序编码使用 C# 中,但你不应该有太多的困难,到另一种语言 (如 JavaScript 或者 Python 代码进行重构。

演示代码太长,无法在这里,但完整的源代码是本文附带的代码下载中提供。演示代码具有所有正常的错误检查已删除,以保持较小的代码的大小并尽可能清楚的主要思想。

程序的整体结构

程序的整体结构,用一些小的编辑,以节省空间,提出了图 2。若要创建演示,我发起了 Visual Studio,创建一个新 C# 控制台应用程序命名为经常化。演示有没有重大的 Microsoft.NET 框架依赖关系,因此,任何新版本的 Visual Studio 会工作。

图 2 程序的整体结构

using System;
namespace Regularization
{
  class RegularizationProgram
  {
    static void Main(string[] args)
    {
      Console.WriteLine("Begin L1 and L2 Regularization demo");
      int numFeatures = 12;
      int numRows = 1000;
      int seed = 42;
      Console.WriteLine("Generating " + numRows +
        " artificial data items with " + numFeatures + " features");
      double[][] allData = MakeAllData(numFeatures, numRows, seed);
      Console.WriteLine("Creating train and test matrices");
      double[][] trainData;
      double[][] testData;
      MakeTrainTest(allData, 0, out trainData, out testData);
      Console.WriteLine("Training data: ");
      ShowData(trainData, 4, 2, true);
      Console.WriteLine("Test data: ");
      ShowData(testData, 3, 2, true);
      Console.WriteLine("Creating LR binary classifier");
      LogisticClassifier lc = new LogisticClassifier(numFeatures);
      int maxEpochs = 1000;
      Console.WriteLine("Starting training using no regularization");
      double[] weights = lc.Train(trainData, maxEpochs,
        seed, 0.0, 0.0);
      Console.WriteLine("Best weights found:");
      ShowVector(weights, 3, weights.Length, true);
      double trainAccuracy = lc.Accuracy(trainData, weights);
      Console.WriteLine("Prediction accuracy on training data = " +
        trainAccuracy.ToString("F4"));
      double testAccuracy = lc.Accuracy(testData, weights);
      Console.WriteLine("Prediction accuracy on test data = " +
        testAccuracy.ToString("F4"));
      Console.WriteLine("Seeking good L1 weight");
      double alpha1 = lc.FindGoodL1Weight(trainData, seed);
      Console.WriteLine("L1 weight = " + alpha1.ToString("F3"));
      Console.WriteLine("Seeking good L2 weight");
      double alpha2 = lc.FindGoodL2Weight(trainData, seed);
      Console.WriteLine("L2 weight = " + alpha2.ToString("F3"));
      Console.WriteLine("Training with L1 regularization, " +
        "alpha1 = " + alpha1.ToString("F3"));
      weights = lc.Train(trainData, maxEpochs, seed, alpha1, 0.0);
      Console.WriteLine("Best weights found:");
      ShowVector(weights, 3, weights.Length, true);
      trainAccuracy = lc.Accuracy(trainData, weights);
      Console.WriteLine("Prediction accuracy on training data = " +
        trainAccuracy.ToString("F4"));
      testAccuracy = lc.Accuracy(testData, weights);
      Console.WriteLine("Prediction accuracy on test data = " +
        testAccuracy.ToString("F4"));
      Console.WriteLine("Training with L2 regularization, " +
        "alpha2 = " + alpha2.ToString("F3"));
      weights = lc.Train(trainData, maxEpochs, seed, 0.0, alpha2);
      Console.WriteLine("Best weights found:");
      ShowVector(weights, 3, weights.Length, true);
      trainAccuracy = lc.Accuracy(trainData, weights);
      Console.WriteLine("Prediction accuracy on training data = " +
        trainAccuracy.ToString("F4"));
      testAccuracy = lc.Accuracy(testData, weights);
      Console.WriteLine("Prediction accuracy on test data = " +
        testAccuracy.ToString("F4"));
      Console.WriteLine("End Regularization demo");
      Console.ReadLine();
    }
    static double[][] MakeAllData(int numFeatures,
      int numRows, int seed) { . . }
    static void MakeTrainTest(double[][] allData, int seed,
      out double[][] trainData, out double[][] testData) { . . }
    public static void ShowData(double[][] data, int numRows,
      int decimals, bool indices) { . . }
    public static void ShowVector(double[] vector, int decimals,
      int lineLen, bool newLine) { . . }
  }
  public class LogisticClassifier
  {
    private int numFeatures;
    private double[] weights;
    private Random rnd;
    public LogisticClassifier(int numFeatures) { . . }
    public double FindGoodL1Weight(double[][] trainData,
      int seed) { . . }
    public double FindGoodL2Weight(double[][] trainData,
      int seed) { . . }
    public double[] Train(double[][] trainData, int maxEpochs,
      int seed, double alpha1, double alpha2) { . . }
    private void Shuffle(int[] sequence) { . . }
    public double Error(double[][] trainData, double[] weights,
      double alpha1, double alpha2) { . . }
    public double ComputeOutput(double[] dataItem,
      double[] weights) { . . }
    public int ComputeDependent(double[] dataItem,
      double[] weights) { . . }
    public double Accuracy(double[][] trainData,
      double[] weights) { . . }
    public class Particle { . . }
  }
} // ns

模板代码加载到 Visual Studio 编辑器后,在解决方案资源管理器窗口我重命名文件 Program.cs 为更具描述性的 RegularizationProgram.cs 和 Visual Studio auto­论述改名为我的类的程序。 顶部的源代码中,删除所有使用指向不需要命名空间的语句,留下只对顶级的 System 命名空间的引用。

所有 logistic 回归逻辑都包含在一个单独的 LogisticClassifier 类。利用 logistic 混沌­分类器类包含嵌套的帮手粒子类来封装粒子群优化 (PSO) 优化算法用于训练。请注意 LogisticClassifier 类包含的方法错误,接受参数命名为 alpha1 和 α 2。这些参数是经常化重量为 L1 和 L2 的经常化。

在 Main 方法中,合成数据创建这些语句:

int numFeatures = 12;
int numRows = 1000;
int seed = 42;
double[][] allData = MakeAllData(numFeatures, numRows, seed);

只是因为该值给了很好的、 有代表性的演示输出采用 42 的种子值。方法 MakeAllData 生成 13 随机权重之间-10.0 和 + 10.0 (为每个功能,一个重量加 b0 重量)。然后该方法循环 1000 次。在每次迭代,生成了一组随机的 12 输入值,然后使用随机权重计算中间 logistic 回归分析的输出值。额外的随机值被添加到输出以使数据在吵了,也更倾向于过度臃肿。

数据被分割成培训 800 项集和模型评价对这些陈述 200 项集:

double[][] trainData;
double[][] testData;
MakeTrainTest(allData, 0, out trainData, out testData);

Logistic 回归分析预测模型被创建带有这些语句:

LogisticClassifier lc = new LogisticClassifier(numFeatures);
int maxEpochs = 1000;
double[] weights = lc.Train(trainData, maxEpochs, seed, 0.0, 0.0);
ShowVector(weights, 4, weights.Length, true);

限制值的粒子群优化算法训练算法的循环计数器变量 maxEpochs。这两个 0.0 参数传递给方法火车是 L1 和 L2 的正则化权重。通过将这些权重设置为 0.0,使用了没有经常化。该模型的质量被评价这两个语句:

double trainAccuracy = lc.Accuracy(trainData, weights);
double testAccuracy = lc.Accuracy(testData, weights);

用正则化的缺点之一是必须确定正则化权重。寻找好的正则化权重的方法之一是使用手动尝试和错误,但编程的技术通常会更好。良好的 L1 经常化重量是发现,然后用这些语句:

double alpha1 = lc.FindGoodL1Weight(trainData, seed);
weights = lc.Train(trainData, maxEpochs, seed, alpha1, 0.0);
trainAccuracy = lc.Accuracy(trainData, weights);
testAccuracy = lc.Accuracy(testData, weights);

训练使用 L2 经常化的 LR 分类语句是就像那些使用 L1 正规化:

double alpha2 = lc.FindGoodL2Weight(trainData, seed);
weights = lc.Train(trainData, maxEpochs, seed, 0.0, alpha2);
trainAccuracy = lc.Accuracy(trainData, weights);
testAccuracy = lc.Accuracy(testData, weights);

在这个演示中,alpha1 和 α 2 值决定使用 LR 对象公众范围内的方法 FindGoodL1Weight 和 FindGoodL2Weight,然后传递给方法火车。一种替代设计建议通过调用这段代码:

bool useL1 = true;
bool useL2 = false:
lc.Train(traiData, maxEpochs, useL1, useL2);

这种设计方式还允许训练方法确定正则化权重,并导致有点简洁的界面。

理解经常化

因为 L1 和 L2 的正则化技术,以减少模型过拟合现象,了解经常化,您必须了解过拟合。松散地说,如果你太多训练模型,最终你会非常好,适合培训数据的权重但当您将生成的模型应用于新数据,预测精度是很差。

Overfitting 插图中的两个图表由图 3。第一个图显示了一种假设情况,目标是进行分类两种类型的项目,由红色和绿色的圆点表示。平滑的蓝色曲线表示真正分离的两个类,属于上面的曲线和绿色的圆点曲线以下归属的红点。请注意由于数据中的随机误差,两个红色的点是曲线以下两个绿点在上面的曲线。良好的培训,不发生过拟合的情况下,会导致平滑的蓝色曲线对应的权重。假设在进来了一个新的数据点 (3,7)。数据项将上面的曲线,并正确预测,是红色的班级。

模型拟合
图 3 模型拟合

在第二幅图图 3 有相同点,但不同的蓝色曲线的拟合结果。这一次所有的红点是上面的曲线和所有绿色的圆点曲线以下。但曲线太复杂。在新的数据项 (3,7) 将低于曲线和预测错误作为类绿色。

Overfitting 生成非光滑预测曲线,即是说,那些不"正规"。这种恶劣、 复杂的预测曲线通常的特点是具有非常大或非常小的值的权重。因此,减少过度臃肿的一种方法是防止模型权重很小或大。这是正则化的动机。

当正在训练的 ML 模型时,必须使用某种程度的错误确定好的权重。有几种不同的方法测量误差。最常见的技术之一就是均方的误差,在训练数据中,找到一组权重值的计算的输出值和已知的、 正确的输出值差值的平方的总和,然后将这笔金额除以培训项目的数目。例如,假设为候选集的 logistic 回归举重,只是三个培训项目、 计算的产出和正确的输出值 (有时称为所需或目标值):

computed  desired
  0.60      1.0
  0.30      0.0
  0.80      1.0

在这里,均方的误差将是:

((0.6 - 1.0)^2 + (0.3 - 0.0)^2 + (0.8 - 1.0)^2) / 3 =
(0.16 + 0.09 + 0.04) / 3 =
0.097

表示象征,意味着不能写入平方的误差:

E = Sum(o - t)^2 / n

其中总和累积总和超过所有的培训项目、 o 表示计算出的输出,t 是目标输出,n 是培训数据的项数。该错误是什么培训最小化使用十几个数值技术之一的名字,如梯度下降法,迭代牛顿-拉夫逊法,L-BFGS 反向传播和粒子群优化。

要防止成为大模型权重值的大小,正则化的想法是惩罚通过将这些重量值添加到计算的误差项的权重值。如果权重值包含在总误差期限被最小化,然后小权重值将生成较小的误差值。L1 重量经常化惩罚权重值通过将其绝对值总和添加到错误的词。象征性地:

E = Sum(o - t)^2 / n + Sum(Abs(w))

L2 重量经常化惩罚权值通过向误差项添加他们的平方值的总和。象征性地:

E = Sum(o - t)^2 / n + Sum(w^2)

假设本例中有四个权重待定和它们的当前值是 (-4.0,-3.0,1.0 2.0)。添加到 0.097 的均方误差的 L1 重量刑罚会 (2.0 + 3.0 + 1.0 + 4.0) = 10.0。L2 的重量代价将为 2.0 ^2 +-3.0 ^2 + 1.0 ^2 +-4.0 ^2 = 4.0 + 9.0 + 1.0 + 16.0 = 30.0。

综上所述,大型模型权重可以导致 overfitting,导致贫穷的预测精度。正则化通过添加刑罚为权重的模型误差函数限制模型权重的大小。L1 经常化使用绝对值的重量的总和。L2 经常化使用重量的平方值的总和。

为什么两种不同的正则化吗?

L1 和 L2 经常化很相似。这是更好?底线是,即使有一些理论上的指导关于哪种形式的正则化是在某些问题的情况下,在我看来,你必须通过实验找到哪种类型的正则化的实践中更好是好,或使用正则化是否更好。

事实证明,使用 L1 经常化有时可以有有益的副作用,开车去 0.0,这实际上意味着关联的功能并不需要的一个或多个的权重值。这是所谓的特征选择的一种形式。例如,在演示中在运行图 1,与 L1 经常化的最后一个模型重量为 0.0。这意味着与上次的预测值并不有助于 LR 模型。L2 经常化限制模型权重值,但通常不会修剪任何权重完全通过将它们设置为 0.0。

所以,看来 L1 经常化优于 L2 经常化。然而,使用 L1 经常化的缺点是这项技术不能与一些毫升训练算法,特别那些使用微积分计算所谓的梯度的算法很容易用。L2 经常化可以用任何类型的训练算法。

综上所述,L1 经常化有时有可爱的副作用的修剪出不需要的功能通过将其关联的权重设置为 0.0 但 L1 经常化不容易与各种形式的培训工作。L2 经常化工作与各种形式的培训,但不会给你的隐式特征选择。在实践中,你必须使用试验和错误来确定哪些形式的正则化 (或不) 是更好地为一个特别的问题。

实施正规化

实施 L1 和 L2 的正则化是相对容易的。该演示程序使用粒子群优化算法训练具有显式的误差函数,所以一切必要是要添加的 L1 和 L2 的重量刑罚。方法误差的定义的开头:

public double Error(double[][] trainData, double[] weights,
  double alpha1, double alpha2)
{
  int yIndex = trainData[0].Length - 1;
  double sumSquaredError = 0.0;
  for (int i = 0; i < trainData.Length; ++i)
  {
    double computed = ComputeOutput(trainData[i], weights);
    double desired = trainData[i][yIndex];
    sumSquaredError += (computed - desired) * (computed - desired);
  }
...

第一步是通过求和的计算的输出和目标输出差值的平方计算均方的误差。(另一种常见的错误称为互熵误差)。下一步,计算了 L1 刑罚:

double sumAbsVals = 0.0; // L1 penalty
for (int i = 0; i < weights.Length; ++i)
  sumAbsVals += Math.Abs(weights[i]);

然后计算 L2 刑罚:

double sumSquaredVals = 0.0; // L2 penalty
for (int i = 0; i < weights.Length; ++i)
  sumSquaredVals += (weights[i] * weights[i]);

方法错误返回 MSE 加罚则:

...
  return (sumSquaredError / trainData.Length) +
    (alpha1 * sumAbsVals) +
    (alpha2 * sumSquaredVals);
}

演示使用显式错误函数。有些训练算法,如梯度下降法和反向传播使用误差函数隐式计算误差函数微积分偏导数 (称为渐变)。对于那些训练算法,使用 L2 经常化 (因为导数的 w ^2 是 2w),你只是添加 2w 术语向梯度 (尽管细节可能会有点棘手)。

寻找好的正则化权重

有几种方法,要找到好的 (但不是一定是最佳的) 经常化重量。该演示程序建立起一套候选值,计算每名候选人,与关联的错误并返回找到的最佳人选。要找到一个好的 L1 重量的方法开始:

public double FindGoodL1Weight(double[][] trainData, int seed)
{
  double result = 0.0;
  double bestErr = double.MaxValue;
  double currErr = double.MaxValue;
  double[] candidates = new double[] { 0.000, 0.001, 0.005,
    0.010, 0.020, 0.050, 0.100, 0.150 };
  int maxEpochs = 1000;
  LogisticClassifier c =
    new LogisticClassifier(this.numFeatures);

添加额外的候选人会给你更好的机会找到最优正则化重量以时间为代价。接下来,每一位候选人进行了评价,并返回找到的最佳人选:

for (int trial = 0; trial < candidates.Length; ++trial) {
    double alpha1 = candidates[trial];
    double[] wts = c.Train(trainData, maxEpochs, seed, alpha1, 0.0);
    currErr = Error(trainData, wts, 0.0, 0.0);
    if (currErr < bestErr) {
      bestErr = currErr; result = candidates[trial];
    }
  }
  return result;
}

请注意候选人经常化体重用来训练评价分类器,但没有正则化重量计算错误。

总结

正则化可以用任何毫升分类技术是基于数学方程。例子包括 logistic 回归概率分类、 神经网络。因为它减少了模型中的权重值的大小,正则化有时称为重量朽烂。用正则化的主要优点是它经常会导致一个更精确的模型。最大的缺点是它引入一个额外的参数值,必须确定,正规化重量。在回归的情况下这不太严重,因为通常是只是学习率参数,但当使用更复杂的分类技术,神经网络尤其是,添加另一个所谓参数可以创建大量的额外工作来优化参数的组合的值。


Dr. James McCaffrey 适合在雷德蒙的微软研究院 他曾在几个 Microsoft 产品包括 Internet Explorer 和冰。 博士。 麦卡弗里也可以拨打 jammc@microsoft.com

感谢以下技术专家在微软研究院对本文的审阅:理查德 · 休斯