本文章是由機器翻譯。

測試回合

C# 的 Probit 分類

James McCaffrey

下載代碼示例

James McCaffreyProbit ("概率單位") 分類是機器學習 (毫升) 的技術可以用來在變數來預測二進位的情況下作出的預測 — — 那就是,它可以只是兩個可能值之一。概率分類也稱為概率單位回歸和概率建模。

概率分類是非常類似于 logistic 回歸 (LR) 分類。這兩種技術適用于相同類型的問題和傾向于給出類似的結果,並選擇使用的概率或 LR 分類通常取決於你工作的紀律。Probit 常用於經濟學和金融,雖然 LR 是更普遍的在其他欄位中。

為了瞭解什麼 probit 分類是,看看演示程式中圖 1

Probit 分類在行動
圖 1 Probit 分類在行動

演示使用概率分類來創建一個模型,預測醫院病人是否會死的基於年齡、 性別和腎測試的結果。這些資料是完全人工的。第一個的原始資料項目目是:

48.00   +1.00   4.40   0

原始資料組成的 30 個專案。性別被編碼為男性-1 和 + 1 為女性。變數來預測,死時,最後一列中,編碼為 0 = 的 false (因此人躲過了),1 = true。因此,第一個資料項目標誌著腎得分 4.40 倖存的 48 歲女性。演示一開始的年齡和腎的資料進行正常化處理,以使所有值都具有大致相同的幅度。第一個資料項目後正常化,是:

-0.74   +1.00   -0.61   0.00

歸一化的值小於 0.0 (在這裡,年齡和腎分數) 均低於平均水準,而值大於 0.0 都高於平均水準。

來源資料然後隨機分成 24 項訓練集來創建模型和六項測試集來估計模型時應用到新的資料與未知結果的準確性。

然後,該演示程式將創建一個概率模型。在幕後,使用一種叫做單純形優化法,用的最大數目設置為 100 的反覆運算技術進行了訓練。 經過訓練後,將顯示定義的模型的權重 {-1.29、-4.26、 2.30、 3.45}。

第一次的權重值,-4.26,是一般的常量,並且不能應用於任何一個特定的預測變數。第二個的重量、 2.30,適用于年齡 ; 第三個的重量,-1.29,適用于性 ; 而第四個的重量、 3.45,適用于腎得分。積極的權重,例如那些與年齡和腎的分數,意味著較大的值預測器的指示變數,死了,將會更大 — — 那就是,更接近于真實。

演示計算模型 24 項訓練集 (100%正確) 和測試集 (83.33%,或五正確和一個錯誤) 的準確性。更重要的這兩個值是測試精度。它是一個粗略的估計概率模型的整體準確性。

這篇文章假設你有至少中級程式設計技能和 ML 的分類,一個基本的瞭解,但並不認為你知道任何關於概率的分類。該演示程式編碼使用 C# 中,但你應該能夠重構演示,其他.NET 語言沒有太多的麻煩。演示代碼太長,無法顯示其全部內容,但所有的代碼是在本文附帶的下載中提供 msdn.microsoft.com/magazine/msdnmag1014。所有正常的錯誤檢查已被都刪除,以保持明確的主要思想。

理解概率分類

簡單的方法來預測死亡年齡、 性別和腎的分數將形成的線沿線的線性組合:

died = b0 + (b1)(age) + (b2)(sex) + (b3)(kidney)

在哪裡 b0、 b1、 b2、 b3 是必須以某種方式確定所以上訓練資料的計算的輸出值密切匹配已知的變數值的權重。Logistic 迴歸分析延伸這一想法與一個更複雜的預測函數:

z = b0 + (b1)(age) + (b2)(sex) + (b3)(kidney)
died = 1.0 / (1.0 + e-z)

數學是很深,但預測函數,稱為邏輯斯諦的乙狀結腸函數,方便地始終返回一個值介於 0.0 和 1.0,可以被解釋為一個概率。概率分類使用了不同的預測函數:

z = b0 + (b1)(age) + (b2)(sex) + (b3)(kidney)
died = Phi(z)

標準正態累積分佈函式呼叫 Phi(z) 函數 (通常是縮寫 CDF) 而且它總是返回一個值介於 0.0 和 1.0 之間。CDF 是棘手的因為沒有為它的簡單方程。值 z CDF 是一種著名的鐘形曲線函數 (高斯函數) 從負無窮到 z 下地區。

這聽起來很多,比真是複雜的。看看圖中圖 2。該圖顯示邏輯斯諦乙狀結腸函數和繪製肩並肩的 CDF 函數。最重要的一點是對於任何值 z,即使底層的函數是非常不同的這兩個函數返回一個值介於 0.0 和 1.0 可以被解釋為一個概率。

的累積密度函數圖
圖 2 的累積密度函數圖

所以,從開發人員的角度來看,第一個挑戰是編寫一個函數,計算值 z CDF。 沒有簡單的方程式來計算民防部隊,但有很多的富異國情調的近似。最常見的方式來逼近 CDF 函數之一就是要計算這個東西使用稱為方程的 erf 函數 (簡稱誤差函數) A & S 7.1.26,然後使用 erf 確定民防部隊。代碼 CDF 函數給出了在圖 3

圖 3 中 C# 的 CDF 函數

static double CumDensity(double z)
{
  double p = 0.3275911;
  double a1 = 0.254829592;
  double a2 = -0.284496736;
  double a3 = 1.421413741;
  double a4 = -1.453152027;
  double a5 = 1.061405429;
  int sign;
  if (z < 0.0)
    sign = -1;
  else
    sign = 1;
  double x = Math.Abs(z) / Math.Sqrt(2.0);
  double t = 1.0 / (1.0 + p * x);
  double erf = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) *
    t * Math.Exp(-x * x);
  return 0.5 * (1.0 + (sign * erf));
}

當編寫 probit 分類代碼的第二個挑戰是確定權重值,這樣,當面對訓練資料,計算出的輸出值密切匹配已知的輸出值。另一種方式看待這一問題的是目標是儘量減少計算和已知的輸出值之間的誤差。這就被所謂培訓使用數值優化的模型。

那裡是沒有簡單的方法訓練大多數毫升分類,包括概率分類器。大約有十幾個主要技術,您可以使用,每種技術有數十個不同種類。常見的培訓技巧包括簡單的梯度下降法、 反向傳播、 牛頓-拉夫遜法、 粒子群優化演算法、 進化優化演算法和 L BFGS。該演示程式使用的最古老和最簡單的訓練技術之一 — — 單純形優化法。

理解單純形優化法

單純鬆散地說,是一個三角形。單純形優化背後的想法是要有三個可能的解決辦法 (因此,"單純")。一種解決辦法將是"最好"(有極小的差錯),一個將是"最壞"(最大錯誤),和第三種稱為"其它"。下一步,單純形優化創建三個新的潛在解決方案,稱為"擴大,""反射,"和"簡約"。其中每一個與當前最好的解決方案,進行比較,如果有任何新的候選人是更好的 (小錯誤),最好的解決方案更換。

單純形優化法說明在圖 4。在一個簡單的例子,在那裡的解決方案包含兩個值,如 (1.23、 4.56),你能想到的解決方案作為一個點上 (x,y) 平面。左邊的圖 4 顯示了三個新的候選解決方案從目前最好的最壞的情況生成和"其他"的解決方案。

單純形優化法
圖 4 單純形優化法

首先,質心計算。質心是平均的最好和其他的解決辦法。在兩個維度,這是最好的和其他點之間的中點。接下來,假想的線創建時,在最糟的時候開始,並延伸到質心。承包的候選人是之間的差和質心點。該反射的候選人是上的假想線,過去的質心。而擴大的候選人是過去的反射點。

在每個反覆運算中的單純形優化法,如果擴大、 反射或承包候選人之一是比當前更好最壞的情況最糟糕的解決辦法,取而代之的是那位候選人。如果生成的三個候選人都不比最壞的解決方案更好,當前最壞和其他解決方案被移往最好的解決辦法至點在某個地方最好的解決方案,與他們當前的位置的右側所示圖 4

每次反覆運算後形成一個新的虛擬"最好-其他壞"三角形,越來越接近于最優解。如果每個三角形的快照,當按循序搜尋,換擋三角形類似于尖尖的 blob 在平面中,像是一種單細胞的阿米巴變形蟲中移動。為此,單純形優化有時稱為阿米巴變形蟲方法優化。

有很多變化的單純形優化法,在多遠承包、 反射,和擴大候選的解決方案是從目前的質心和候選的解決方案檢查,以查看它們是否比目前最好的解決方案更好的順序不同。最常見的單純形優化形式稱為單純形演算法。該演示程式使用一種簡單的變化,不具有特定名稱。

概率分類,每個潛在的解決方案是一組權重值。圖 5 在偽代碼,顯示變化的單純形優化法在演示程式中使用。

圖 5 演示程式中使用的單純形優化的偽代碼

randomly initialize best, worst other solutions
loop maxEpochs times
  create centroid from worst and other
  create expanded
  if expanded is better than worst, replace worst with expanded,
    continue loop
  create reflected
  if reflected  is better than worst, replace worst with reflected,
    continue loop
  create contracted
  if contracted  is better than worst, replace worst with contracted,
    continue loop
  create a random solution
  if  random solution is better than worst, replace worst,
    continue loop
  shrink worst and other toward best
end loop
return best solution found

單純形優化法,像所有其他毫升優化演算法,有利有弊。 然而,它是實現 (相對的) 簡單和通常 — — 雖然並不總是 — — 在實踐中行之有效。

該演示程式

若要創建該演示程式,推出Visual Studio選擇 C# 主控台應用程式範本和將它命名為 ProbitClassification。這個演示網站有沒有重大的 Microsoft.NET 框架版本依賴性,所以任何較新版本的Visual Studio工作。範本代碼載入後,在解決方案資源管理器視窗,我改名檔 Program.cs 到 ProbitProgram.cs 和Visual Studio自動重命名類程式。

圖 6 演示代碼的開頭

using System;
namespace ProbitClassification
{
  class ProbitProgram
  {
    static void Main(string[] args)
    {
      Console.WriteLine("\nBegin Probit Binary Classification demo");
      Console.WriteLine("Goal is to predict death (0 = false, 1 = true)");
      double[][] data = new double[30][];
      data[0] = new double[] { 48, +1, 4.40, 0 };
      data[1] = new double[] { 60, -1, 7.89, 1 };
      // Etc.
      data[29] = new double[] { 68, -1, 8.38, 1 };
...

演示代碼的開頭所示圖 6。虛擬資料是硬編碼到程式。在非演示的情況下,您的資料將存儲在一個文字檔中,你要寫一個公用程式方法,將資料載入到記憶體中。接下來,使用程式定義的説明器方法,ShowData 顯示的來源資料:

Console.WriteLine("\nRaw data: \n");
Console.WriteLine("       Age       Sex      Kidney   Died");
Console.WriteLine("=======================================");
ShowData(data, 5, 2, true);

接下來,列 0 和 2 的來源資料的歸一化處理:

Console.WriteLine("Normalizing age and kidney data");
int[] columns = new int[] { 0, 2 };
double[][] means = Normalize(data, columns); // Normalize, save means & stdDevs
Console.WriteLine("Done");
Console.WriteLine("\nNormalized data: \n");
ShowData(data, 5, 2, true);

標準化的方法保存並返回所有列的標準差與手段,以便遇到新的資料時,可以恢復正常使用相同的參數,用來訓練模型。下一步,歸一化的資料分成訓練集 (80%) 和測試集 (20%):

Console.WriteLine("Creating train (80%) and test (20%) matrices");
double[][] trainData;
double[][] testData;
MakeTrainTest(data, 0, out trainData, out testData);
Console.WriteLine("Done");
Console.WriteLine("\nNormalized training data: \n");
ShowData(trainData, 3, 2, true);

你可能想要參數化方法 MakeTrainTest 接受放置在訓練集的條目的百分比。接下來,程式定義的概率分類器物件進行具現化:

int numFeatures = 3; // Age, sex, kidney
Console.WriteLine("Creating probit binary classifier");
ProbitClassifier pc = new ProbitClassifier(numFeatures);

然後 probit 分類器訓練,利用單純形優化法來尋找權重值,以便計算輸出值緊密匹配的已知的輸出值:

int maxEpochs = 100; // 100 gives a representative demo
Console.WriteLine("Setting maxEpochs = " + maxEpochs);
Console.WriteLine("Starting training");
double[] bestWeights = pc.Train(trainData, maxEpochs, 0);
Console.WriteLine("Training complete");
Console.WriteLine("\nBest weights found:");
ShowVector(bestWeights, 4, true);

該演示程式最後通過計算模型訓練資料和測試資料的分類精度:

...
  double testAccuracy = pc.Accuracy(testData, bestWeights);
  Console.WriteLine("Prediction accuracy on test data = 
    " + testAccuracy.ToString("F4"));
  Console.WriteLine("\nEnd probit binary classification demo\n");
  Console.ReadLine();
} // Main

演示不做一個預測,以前看不見的資料。製作一個預測可能看起來像:

// Slightly older, male, higher kidney score
double[] unknownNormalized = new double[] { 0.25, -1.0, 0.50 };
int died = pc.ComputeDependent(unknownNormalized, bestWeights);
if (died == 0)
  Console.WriteLine("Predict survive");
else if (died == 1)
  Console.WriteLine("Predict die");

此代碼假定獨立的 x 變數 — — 年齡、 性別和腎分數 — — 已經使用的手段和標準差的培訓資料正常化進程走向正常化。

ProbitClassifier 類

ProbitClassifier 類的總體結構提出了圖 7。ProbitClassifier 定義中包含一個名為解決方案的嵌套的類。該類分來自 IComparable 介面以便三個解決方案物件的陣列可以自動排序,給的最好,其他和最壞的解決方案。通常我不喜歡花哨的編碼技術,但在這種情況,益處稍大於增加的複雜性。

圖 7 ProbitClassifier 類

public class ProbitClassifier
{
  private int numFeatures; // Number of independent variables
  private double[] weights; // b0 = constant
  private Random rnd;
  public ProbitClassifier(int numFeatures) { . . }
  public double[] Train(double[][] trainData, int maxEpochs, int seed) { . . }
  private double[] Expanded(double[] centroid, double[] worst) { . . }
  private double[] Contracted(double[] centroid, double[] worst) { . . }
  private double[] RandomSolution() { . . }
  private double Error(double[][] trainData, double[] weights) { . . }
  public void SetWeights(double[] weights) { . . }
  public double[] GetWeights() { . . }
  public double ComputeOutput(double[] dataItem, double[] weights) { . . }
  private static double CumDensity(double z) { . . }
  public int ComputeDependent(double[] dataItem, double[] weights) { . . }
  public double Accuracy(double[][] trainData, double[] weights) { . . }
  private class Solution : IComparable<Solution>
  {
    // Defined here
  }
}

ProbitClassifier 有兩個輸出方法。方法計算­輸出返回介於 0.0 和 1.0 之間的一個值,並在訓練期間用於計算錯誤值。方法 ComputeDependent 是 ComputeOutput 的包裝,並且返回 0,如果輸出為小於或等於 0.5 或 1 如果輸出大於 0.5。這些返回的值用於計算精度。

總結

概率分類是古老的 ML 技術之一。因為 probit 分類是如此相似 logistic 迴歸分析分類,共同的智慧是使用一種技術或其他。因為 LR 是稍微容易實現比概率,概率分類不常用,並隨著時間的推移已成為有些毫升二等公民。然而,概率分類往往是非常有效,可以您的 ML 工具組寶貴的補充。


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

感謝以下的微軟研究院技術專家對本文的審閱:彌敦道布朗和KirkOlynyk。