本文章是由機器翻譯。

測試回合

機器學習的 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 Studioauto­論述改名為我的類的程式。 頂部的原始程式碼中,刪除所有使用指向不需要命名空間的語句,留下只對頂級的 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 回歸概率分類、 神經網路。因為它減少了模型中的權重值的大小,正規化有時稱為重量朽爛。用正規化的主要優點是它經常會導致一個更精確的模型。最大的缺點是它引入一個額外的參數值,必須確定,正規化重量。在回歸的情況下這不太嚴重,因為通常是只是學習率參數,但當使用更複雜的分類技術,神經網路尤其是,添加另一個所謂參數可以創建大量的額外工作來優化參數的組合的值。


James McCaffrey適合在雷德蒙的微軟研究院  他曾在幾個 Microsoft 產品包括 Internet Explorer 和冰。 博士。 麥卡弗裡也可以撥打 jammc@microsoft.com

感謝以下技術專家在微軟研究院對本文的審閱:理查 · 休斯