測試回合

以 C# 進行 Naive Bayes 分類

James McCaffrey

下載代碼示例

 

James McCaffrey
樸素貝葉斯分類是一種機器學習技術,可以用來預測特定資料案例屬於哪一類。在這篇文章我解釋的樸素貝葉斯分類的工作原理和目前與 C# 語言編碼示例。

有很多獨立的工具可用,可以執行的樸素貝葉斯分類。然而,這些工具可以很難或不可能直接集成到您的應用程式,和難以進行自訂以滿足特定需要。並且它們可能隱藏的版權問題。這篇文章會給你堅實的樸素貝葉斯分類功能添加.NET 應用程式中,而不依賴于任何外部依賴項。

最好理解的樸素貝葉斯分類是什麼,看看我在朝文章是審查的演示程式中截圖圖 1。所生成的資料將用來訓練分類器 40 行開始的演示程式。在大多數情況下您將使用現有的資料來源,但是我生成的虛擬資料以保持簡單的演示。資料的第一行是"行政、 右側、 720,女性"。第一個欄位是一種職業、 第二個是占主導地位的手、 第三個是以英寸為單位的高度和第四個是性別。分類器的目標是預測性從一組給定的佔領、 統治和高度值。因變數性別有兩個可能的值,因為這是二元分類的示例。

Naive Bayes Classification Demo
圖 1 樸素貝葉斯分類演示

生成的原始資料之後, 該演示程式將每個數位高度欄位轉換為一個類別 — — 短期、 中期或高 — — 由正道高度。我會解釋,正道成分類資料的數值資料是有優點和缺點的方法。 培訓資料已經被冷藏後,該演示程式掃描 40 行的分類資料,並計算聯合計數。例如,該人的職業是行政和人的性別是男性的資料情況下的數是 2。 此外,每個從屬的值 (以可預測的、 男的還是女的在此示例中的屬性) 的總數來計算。你可以看到有 24 男女 16 培訓資料中。

該演示程式然後已分類資料一例新的職業是教育,占主導地位是正確、 高高的性別所需的所有資訊。在此示例中,它原來確定的演示資料案例是一男的概率是 0.3855 和這種情況是女的概率是 0.6145,所以系統的結論資料案例最有可能是一位女性。

在下面的章節中我會首先解釋樸素貝葉斯分類如何工作、 引導您通過該演示程式中的代碼和描述如何修改演示,滿足自己的需要。本文假定您至少擁有開始程式設計技巧與 C 家庭的語言,但不會假設你知道有關的樸素貝葉斯分類。演示程式的代碼是有點太長,無法顯示全部在這裡,但從在 MSDN 下載網站,完整的原始程式碼是可用 archive.msdn.microsoft.com/mag201302TestRun

作品如何樸素貝葉斯分類

使用中所示的示例圖 1,目的是預測其職業是的教育,誰是慣用右手,其高度是高大的人的性別 (男或女) (大於或等於 71.0 英寸為單位)。為此,我們可以計算概率的人是男性給予這一資訊,和概率的人是女性得到的資料,然後預測與較大的概率性。象徵性地表示,我們想要知道 P(male |X),通常理解為,"男性的概率給定獨立變數值 X") 和 P(female |X),其中 X 是 (教育、 右、 高)。在樸素貝葉斯的"幼稚"一詞是指所有屬性 X 被假定為數學上的獨立,從而極大地簡化了分類。您可以找到許多線上引用的解釋頗為有趣數學背後的樸素貝葉斯分類,但結果是相對比較簡單。象徵意義:

P(male | X) =
  [ P(education | male) * P(right | male) * P(tall | male) * P(male) ] /
    [ PP(male | X) + PP(female | X) ]

請注意方程是一小部分。為分子,有時鬆散稱為局部概率由相乘的四個條款組成。在這篇文章為一個局部概率術語使用 PP 非標準符號。分母是兩個術語的總和,其中之一就是分母。要計算的第一是 P(education | male),或一個人的職業是教育,既然他是男的概率。這一點,事實證明,可以通過估計的計數的培訓教育是職業和性別是男性,除以的是男性 (與任何職業) 的個案數目的情況下這樣:

P(education | male ) = count(education & male) / count(male) = 2/24 = 0.0833

用相同的邏輯:

P(right | male) = count(right & male) / count(male) = 17/24 = 0.7083
P(tall | male) = count(tall & male) / count(male) = 4/24 = 0.1667

下的片拼圖是 P(male)。在樸素貝葉斯術語中,這稱為事先。有一些關於如何最好地計算先驗的辯論。一方面,我們可以推測沒有任何理由認為男性的存在是更多或更少可能比女性的存在,所以分配到 P(male) 的 0.5。另一方面,我們可以使用這一事實的培訓資料已 24 16 男女和估計概率的 24/40 = 0.6000 為 P(male)。我更喜歡這種做法,在先驗估計使用培訓資料的情況下。

現在,如果你指的早期的計算公式為 P(male |X),您就會注意它包含 PP(female |X)。底部總和 PP(male |X) + PP(female |X),有時稱為證據。PP(female | 的件X) 的計算就像這樣:

P(education | female) = count(education & female) / count(female) = 4/16 = 0.2500
P(right | female) = count(right & female) / count(female) = 14/16 = 0.8750
P(tall | female) = count(tall & female) / count(female) = 2/16 = 0.1250
P(female) = 16/40 = 0.4000

所以 P(male | 的部分概率分子X) 是:

PP(male | X) = 0.0833 * 0.7083 * 0.1667 * 0.6000 = 0.005903

使用同樣的邏輯,部分的概率為女性由於 X = (教育、 右、 高) 是:

PP(female | X) = 0.2500 * 0.8750 * 0.1250 * 0.4000 = 0.010938

而且,最後,男性和女性的總體概率是:

P(male | X) = 0.005903 / (0.005903 + 0.010938) = 0.3505
P(female | X) = 0.010938 / (0.005903 + 0.010938) = 0.6495

這些總體概率有時被稱為色後牙 8x1。因為 P(female |X) 大於 P(male |X),該系統的結論不明身份的人的性別是女性。可是,等一下,這兩個的幾率,0.3505 和 0.6495,都接近于但絕對不相同的兩個概率,0.3855 和 0.6145,所示圖 1。這種差異的原因是該演示程式使用重要的可選修改基本樸素貝葉斯稱為拉普拉斯平滑。

拉普拉斯平滑

如果您引用圖 1,你可以看到,訓練情況下人在佔領的計數 = 施工和性別 = 女為 0。 在演示中,X 值是 (教育、 權利、 高),其中並不包括施工。但是,假設 X 了 (建築,右,高)。在 PP(female | 的計算方法X) 它有必要計算 P(construction | female) = count(construction & 女性) / count(female),這將是 0,和這反過來又會零出整個局部概率。簡而言之,壞的是,一個聯合的計數為 0 時。 最常用的技術,來避免這種情況是只添加 1 對所有聯合罪名。這已被黑掉的感覺,但事實上,有扎實的數學基礎。技術被稱為添加一個平滑,這是某種特定的拉普拉斯平滑。

與拉普拉斯平滑,如果 X = (教育、 右、 高層) 上一節,P(male |X) 和 P(female |X) 計算,如下所示:

P(education | male ) =
count(education & male) + 1 / count(male) + 3 = 3/27 = 0.1111
P(right | male) =
count(right & male) + 1 / count(male) + 3 = 18/27 = 0.6667
P(tall | male) =
count(tall & male) + 1 / count(male) + 3 = 5/27 = 0.1852
P(male) = 24/40 = 0.6000
P(education | female) =
count(education & female) + 1 / count(female) + 3 = 5/19 = 0.2632
P(right | female) =
count(right & female) + 1 / count(female) + 3 = 15/19 = 0.7895
P(tall | female) =
count(tall & female) + 1 / count(female) + 3 = 3/19 = 0.1579
P(female) = 16/40 = 0.4000

部分的概率是:

PP(male | X) = 0.1111 * 0.6667 * 0.1852 * 0.6000 = 0.008230
PP(female | X) = 0.2632 * 0.7895 * 0.1579 * 0.4000 = 0.013121

於是最後兩個概率是:

P(male | X) = 0.008230 / (0.008230 + 0.013121) = 0.3855
P(female | X) = 0.013121 / (0.008230 + 0.013121) = 0.6145

這些都是在螢幕擷取畫面中所示的值圖 1。請注意,1 添加到每個聯合計數,但 3 添加到分母 count(male) 和 count(female)。3 是在某種程度上任意在意義上拉普拉斯平滑不指定任何要使用的特定值。在這種情況下,它是屬性 (佔領、 統治、 高度) X 的個數。這是最常用的值將添加到分母部分概率的拉普拉斯平滑,但您可能希望使用其他值進行試驗。要添加到分母的值經常被符號 k 樸素貝葉斯數學文獻。另外,請注意,影像、 P(male) 和 P(female),通常不中樸素貝葉斯普拉斯平滑修改。

程式的總體結構

顯示運行的演示程式圖 1 是一個 C# 主控台應用程式。在 Main 方法中,刪除,一些 WriteLine 語句中列出圖 2

圖 2 樸素貝葉斯程式結構

using System;
namespace NaiveBayes
{
  class Program
  {
    static Random ran = new Random(25); // Arbitrary
    static void Main(string[] args)
    {
      try
      {
        string[] attributes = new string[] { "occupation", "dominance",
          "height", "sex"};
        string[][] attributeValues = new string[attributes.Length][];
        attributeValues[0] = new string[] { "administrative",
          "construction", "education", "technology" };
        attributeValues[1] = new string[] { "left", "right" };
        attributeValues[2] = new string[] { "short", "medium", "tall" };
        attributeValues[3] = new string[] { "male", "female" };
        double[][] numericAttributeBorders = new double[1][];
        numericAttributeBorders[0] = new double[] { 64.0, 71.0 };
        string[] data = MakeData(40);
        for (int i = 0; i < 4; ++i)
          Console.WriteLine(data[i]);
        string[] binnedData = BinData(data, attributeValues,
          numericAttributeBorders);
        for (int i = 0; i < 4; ++i)
          Console.WriteLine(binnedData[i]);
        int[][][] jointCounts = MakeJointCounts(binnedData, attributes,
          attributeValues);
        int[] dependentCounts = MakeDependentCounts(jointCounts, 2);
        Console.WriteLine("Total male = " + dependentCounts[0]);
        Console.WriteLine("Total female = " + dependentCounts[1]);
        ShowJointCounts(jointCounts, attributeValues);
        string occupation = "education";
        string dominance = "right";
        string height = "tall";
        bool withLaplacian = true;
        Console.WriteLine(" occupation = " + occupation);
        Console.WriteLine(" dominance = " + dominance);
        Console.WriteLine(" height = " + height);
        int c = Classify(occupation, dominance, height, jointCounts,
          dependentCounts, withLaplacian, 3);
        if (c == 0)
          Console.WriteLine("\nData case is most likely male");
        else if (c == 1)
          Console.WriteLine("\nData case is most likely female");
        Console.WriteLine("\nEnd demo\n");
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.Message);
      }
    } // End Main
    // Methods to create data
    // Method to bin data
    // Method to compute joint counts
    // Helper method to compute partial probabilities
    // Method to classify a data case
  } // End class Program
}

程式開始通過硬編碼 X 屬性佔領、 統治地位,和高度和依賴屬性性設置。在某些情況下,您可能願意掃描您現有的資料來源,以確定的屬性,特別是在源帶有標題的資料檔案或 SQL 表的列的名稱。該演示程式還指定 X 屬性值斷然的九個:(行政、 建築、 教育、 技術) 的職業 ; 及 (左、 右) 的主導地位 ; 和 (短、 中期,高) 的高度。在此示例中,有兩個因變數屬性值:(男、 女) 發生性關係。再次,您可能希望以程式設計方式確定屬性值通過掃描您的資料。

演示設置 64.0 和 710 的硬編碼邊界值以 bin 數位高度值,這樣,值小於或等於 64.0 被歸類為短 ; 高地 64.0 和 710 之間是介質 ; 和高度大於或等於 710 都高。時為樸素貝葉斯正道的數值資料,邊界值的數量將是一個小於類別的數目。在此示例中,64.0 和 710 是由掃描 (57.0 和 78.0) 的最小和最大高度值的培訓資料、 計算差異,21.0,然後除以數的高度類別,3,給予 7.0 計算間隔大小決定的。在大多數情況下,就會想要確定邊界值的數值 X 屬性以程式設計方式而不是手動。

該演示程式調用 MakeData 來生成有些隨機培訓資料的説明器方法。MakeData 要求傭工 MakeSex、 MakeOccupation、 MakeDominance 和 MakeHeight。例如,這些傭工生成資料,以便男性職業更有可能是施工和技術、 男性占主導地位是更有可能是正確的和男性的高度是最有可能 66.0 和 72.0 英寸之間。

在 Main 中調用的關鍵方法是 BinData 進行分類,高度的資料 ; MakeJointCounts 掃描冷藏資料和計算聯合計數 ; MakeDependentCounts 來計算總數的男性和女性 ; 和分類,其中使用聯合計數和依賴項罪名來執行樸素貝葉斯分類。

面中繼資料

中列出方法 BinData 圖 3。該方法接受一個陣列的以逗號分隔的字串,其中每個字串看起來像"教育離開,67.5,男性"。在許多情況下,您會被閱讀訓練資料從文字檔的每一行都是一個字串。該方法使用 String.Split,每個字串分析為標記。權杖 [2] 是的高度。它是從字串轉換為雙使用雙類型。分析方法。直到找到高度的間隔,則確定相應高度類別作為字串數值的高度進行比較的邊界值。結果字串被縫合在一起使用的舊的權杖、 逗號分隔符號和新的計算高度類別字串。

圖 3 為分類高度的方法 BinData

static string[] BinData(string[] data, string[][] attributeValues,
  double[][] numericAttributeBorders)
{
  string[] result = new string[data.Length];
  string[] tokens;
  double heightAsDouble;
  string heightAsBinnedString;
  for (int i = 0; i < data.Length; ++i)
  {
    tokens = data[i].Split(',');
    heightAsDouble = double.Parse(tokens[2]);
    if (heightAsDouble <= numericAttributeBorders[0][0]) // Short
      heightAsBinnedString = attributeValues[2][0];
    else if (heightAsDouble >= numericAttributeBorders[0][1]) // Tall
      heightAsBinnedString = attributeValues[2][2];
    else
      heightAsBinnedString = attributeValues[2][1]; // Medium
    string s = tokens[0] + "," + tokens[1] + "," + heightAsBinnedString +
      "," + tokens[3];
    result[i] = s;
  }
  return result;
}

它不是要求時執行的樸素貝葉斯分類的垃圾桶數值資料。樸素貝葉斯可以直接,處理數值資料,但這些技術已超出本文的範圍。正道資料具有簡單和避免需要作出任何特別的顯式資料 (如高斯或泊松) 的數學分佈假設的優點。然而,實質上正道丟失資訊,不會要求您確定並指定分為多少類,將資料劃分。

確定聯合進行計數

樸素貝葉斯分類的關鍵計算聯合計數。在演示的示例中,有九個總獨立 X 屬性值 (行政、 建築、 ... 高) 和兩個依賴屬性值 (男、 女),這樣總共有 9 * 2 = 18 聯合計數必須計算和存儲。我首選的方法是將聯合計數存儲在三維陣列 int [] [] [] jointCounts。第一個索引指示獨立 X 屬性 ; 第二個索引指示屬性值 ; X 獨立 和第三項索引指示的依賴屬性值。例如,jointCounts [0] [3] [1] 指 0 (佔領) 屬性、 屬性值 3 (技術) 和性別 (女),1 或換句話說處 jointCounts [0] [3] [1] 的值是培訓的情況下佔領是技術性別是女性的計數。中列出方法 MakeJointCounts 圖 4

圖 4 方法 MakeJointCounts

static int[][][] MakeJointCounts(string[] binnedData, string[] attributes,
  string[][] attributeValues)
{
  int[][][] jointCounts = new int[attributes.Length - 1][][]; // -1 (no sex)
  jointCounts[0] = new int[4][]; // 4 occupations
  jointCounts[1] = new int[2][]; // 2 dominances
  jointCounts[2] = new int[3][]; // 3 heights
  jointCounts[0][0] = new int[2]; // 2 sexes for administrative
  jointCounts[0][1] = new int[2]; // construction
  jointCounts[0][2] = new int[2]; // education
  jointCounts[0][3] = new int[2]; // technology
  jointCounts[1][0] = new int[2]; // left
  jointCounts[1][1] = new int[2]; // right
  jointCounts[2][0] = new int[2]; // short
  jointCounts[2][1] = new int[2]; // medium
  jointCounts[2][2] = new int[2]; // tall
  for (int i = 0; i < binnedData.Length; ++i)
  {
    string[] tokens = binnedData[i].Split(',');
    int occupationIndex = AttributeValueToIndex(0, tokens[0]);
    int dominanceIndex = AttributeValueToIndex(1, tokens[1]);
    int heightIndex = AttributeValueToIndex(2, tokens[2]);
    int sexIndex = AttributeValueToIndex(3, tokens[3]);
    ++jointCounts[0][occupationIndex][sexIndex];
    ++jointCounts[1][dominanceIndex][sexIndex];
    ++jointCounts[2][heightIndex][sexIndex];
  }
  return jointCounts;
}

執行了很多的硬編碼值,以使它更易於理解。例如,這三種語句可改為一個單一的分配空間在陣列 attributeValues 中使用長度屬性的 for 迴圈:

jointCounts[0] = new int[4][]; // 4 occupations
jointCounts[1] = new int[2][]; // 2 dominances
jointCounts[2] = new int[3][]; // 3 heights

Helper 函數 AttributeValueToIndex 接受索引屬性和屬性值的字串,並返回相應的索引。例如,AttributeValueToIndex (2,"中等") 返回"中等"的索引在高度屬性,這是 1。

該演示程式使用 MakeDependentCounts 的一種方法來確定數量的男性和女性資料病例數。有許多方式可以執行此工作。如果您引用圖 1,你會觀察一種方法是添加的三個屬性的任何聯合計數的數量。例如的男性人數是 count(administrative & 的總和 男性),count(construction & 男性),count(education & 男性) 和 count(technology & 男):

static int[] MakeDependentCounts(int[][][] jointCounts,
  int numDependents)
{
  int[] result = new int[numDependents];
  for (int k = 0; k < numDependents; ++k) 
  // Male then female
    for (int j = 0; j < jointCounts[0].Length; ++j)
    // Scanning attribute 0
      result[k] += jointCounts[0][j][k];
  return result;
}

分類資料案例

方法分類、 所示圖 5,是短暫的因為它依賴的説明器方法。

圖 5 方法分類

static int Classify(string occupation, string dominance, string height,
  int[][][] jointCounts, int[] dependentCounts, bool withSmoothing,
  int xClasses)
{
  double partProbMale = PartialProbability("male", occupation, dominance,
    height, jointCounts, dependentCounts, withSmoothing, xClasses);
  double partProbFemale = PartialProbability("female", occupation, dominance,
    height, jointCounts, dependentCounts, withSmoothing, xClasses);
  double evidence = partProbMale + partProbFemale;
  double probMale = partProbMale / evidence;
  double probFemale = partProbFemale / evidence;
  if (probMale > probFemale) return 0;
  else return 1;
}

分類方法接受的 jointCounts 和 dependentCounts 的陣列 ; 一個 boolean 類型的值的欄位,以指示要使用拉普拉斯平滑 ; 和參數 xClasses,這在本示例中將是 3,因為有三個獨立變數 (佔領、 主導地位、 高度)。從 jointCounts 參數,也可以推斷此參數。

方法分類返回 int 類型的值,它表示預測因變數的索引。相反,您可能想要返回一個陣列的每個變數的概率。請注意分類基於 probMale 和 probFemale,這兩種計算局部概率除以的證據價值。您可能希望只是忽略證據一詞,只是自己比較的部分概率值。

分類方法返回具有最大概率的因變數的索引。另一種方法是提供閾值的值。例如,假設 probMale 是 0.5001 和 probFemale 是 0.4999。你不妨考慮這些值太關閉調用並返回一個分類值,表示"不確定"。

方法 PartialProbability 做了大部分的分類工作,並被列入圖 6

圖 6 方法 PartialProbability

static double PartialProbability(string sex, string occupation, string dominance,
  string height, int[][][] jointCounts, int[] dependentCounts,
  bool withSmoothing, int xClasses)
{
  int sexIndex = AttributeValueToIndex(3, sex);
  int occupationIndex = AttributeValueToIndex(0, occupation);
  int dominanceIndex = AttributeValueToIndex(1, dominance);
  int heightIndex = AttributeValueToIndex(2, height);
  int totalMale = dependentCounts[0];
  int totalFemale = dependentCounts[1];
  int totalCases = totalMale + totalFemale;
  int totalToUse = 0;
  if (sex == "male") totalToUse = totalMale;
  else if (sex == "female") totalToUse = totalFemale;
  double p0 = (totalToUse * 1.0) / (totalCases); // Prob male or female
  double p1 = 0.0;
  double p2 = 0.0;
  double p3 = 0.0;
  if (withSmoothing == false)
  {
    p1 = (jointCounts[0][occupationIndex][sexIndex] * 1.0) / totalToUse
    p2 = (jointCounts[1][dominanceIndex][sexIndex] * 1.0) / totalToUse;  
    p3 = (jointCounts[2][heightIndex][sexIndex] * 1.0) / totalToUse;     
  }
  else if (withSmoothing == true)
  {
    p1 = (jointCounts[0][occupationIndex][sexIndex] + 1) /
     ((totalToUse + xClasses) * 1.0); 
    p2 = (jointCounts[1][dominanceIndex][sexIndex] + 1) /
     ((totalToUse + xClasses) * 1.0 ;
    p3 = (jointCounts[2][heightIndex][sexIndex] + 1) /
     ((totalToUse + xClasses) * 1.0);
  }
  //return p0 * p1 * p2 * p3; // Risky if any very small values
  return Math.Exp(Math.Log(p0) + Math.Log(p1) + Math.Log(p2) + Math.Log(p3));
}

方法 PartialProbability 是大多是硬編碼為清楚起見。例如,有四個概率件、 p0、 p1、 p2 和 p3。您可以通過使用概率的陣列,陣列的大小由 jointCounts 陣列更一般的 PartialProbability。

請注意而不是返回的四個概率件產品,該方法返回的每個日誌的總和相等 Exp。使用日誌概率是機器學習演算法中的標準技術,可用於防止數位出現錯誤,可以很小的實際數值。

總結

這裡介紹的例子應該給你將樸素貝葉斯分類功能添加到您的.NET 應用程式奠定良好的基礎。樸素貝葉斯分類是一種較粗的技術,但它確實有更加複雜的替代品,如神經網路分類、 回歸分類和支援向量機分類的幾個優勢。樸素貝葉斯簡單,較容易實現,可以擴展好到非常大的資料集。與樸素貝葉斯輕鬆地擴展到多項式分類問題 — — 那些有三個或多個變數。

博士。 James麥卡弗裡 為伏資訊科學 Inc.,他在管理工作在華盛頓雷德蒙的微軟校園軟體工程師的技術培訓工作。他曾經參與過多項 Microsoft 產品的研發,包括 Internet Explorer 和 MSN Search。他是作者的".NET 測試自動化食譜"(Apress,2006年),並可以在達成 jammc@microsoft.com

感謝以下 Microsoft 技術專家對本文的審閱:富卡魯阿納