測試回合

將數值資料轉換成類別資料

James McCaffrey

下載代碼示例

James McCaffrey在機器學習領域的一項基本任務將數值資料轉換為分類資料。 例如,如果您有一個資料集的人們的高度以英寸為單位,例如 59.5、 64.0 和 75.5,可能要將此數值的資料轉換成分類的資料,例如 0、 1 和 2,表示短、 中和高。 不拘形式地,這個過程有時稱為進倉資料。 在機器學習文學,過程通常稱為連續資料的離散化。

有幾種方案可能要離散化資料。許多機器學習演算法,如樸素貝葉斯分類和預測,只能使用分類資料工作。因此,如果您的原始資料為數字,你想要應用樸素貝葉斯,你離散化資料。你可能還混合數值和分類資料,例如經常發現在 Excel 試算表中的資料。很少的機器學習演算法處理混合資料,所以你可以將數值資料轉換為分類資料,然後使用一種機器學習演算法,適用于分類資料。資料聚類使用類別實用程式是一個例子。

或許因為題目不是非常迷人的有幾個資源可用,描述如何實現離散化演算法。在這篇文章中,我將介紹一種功能強大的離散化演算法。雖然想法並不是新的據我所知的最佳執行本文仲介紹尚未發佈之前。

看到這篇文章去哪兒的最佳方法是檢查中所示的演示程式圖 1。演示設置 20 代表人民的高度以英寸為單位的資料點。在長條圖中顯示的原始資料圖 2。演示分析資料並創建一個 discretizer 物件,然後顯示 discretizer 的內部表示形式。Discretizer 維護從原始資料在已排序的非重複值的副本 (從高到低值) 名為 data 的陣列。類別的數目已被計算為三個,並存儲在成員變數 k。

Converting Numeric Data to Categorical Data圖 1 將數值資料轉換為分類資料

The Raw Data to Categorize圖 2 原始資料進行分類

每個資料點已被分配給三個類別之一。每個類別的編碼方式為從零開始的整數,0 到 2,並分配資訊存儲在一個名為聚類陣列。第三個資料點 62.0 61.0 60.0) 已分配給類 0。接下來的四個資料點 (66.0 67.0 68.0,69.0) 已分配到類別 1,和最後的四個資料點 (73.0、 74.0、 760、 78.0) 已分配給第二類。第三個資料點的類別 0 的算術平均值是 61.00,和類別 1 和 2 中的資料點的方法分別是 67.50 和 75.25。

創建 discretizer 物件後,該演示程式設置三個現有資料值 73.0 66.0 62.0) 和三個新的資料值 80.5 75.5 59.5)。這裡的要點是有時你有一個固定的資料集,但在其他情況下,新的資料動態生成的必須進行轉換。Discretizer 每個六個數字資料點轉換為分類資料:0、 1、 2、 和 0、 2、 2,分別。

本文假定您有至少中間級的程式設計技能。我編碼的離散化演算法使用 C#,但你不應該太麻煩了,到另一種語言,如 Python 或Visual Basic代碼重構。省略最正常的錯誤檢查,以保持較小的代碼大小和明確的主要思想。演示代碼太長,目前其整體在這篇文章,但整個原始程式碼是可在 archive.msdn.microsoft.com/mag201308TestRun

沒有看起來的那麼簡單

起初以為,將數值資料轉換為分類資料似乎像一個容易出問題。一個簡單的方法是原始的來源資料分成相等的時間間隔。例如,對於在演示資料和圖 2,範圍是 78.0 60.0 = 18.0。這除以 k = 3 間隔給出的 6.0 英寸間隔寬度。所以 60.0 66.0 之間的任何資料將被分配到類別 0,66.0 和 72.0 之間的資料將會被分配到類別 1、 和 72.0 和 78.0 之間的資料將會被分配到類別 2。此等間距的方法的問題是它忽略資料中的天然符。如果你看看圖 2,你會看到一個好的離散化演算法應不在 66.0 中斷 63.0 65.0,之間的資料。

離散化天真第二種方法是使用相同的頻率。示例資料,因為有 11 個不同的資料點,k = 3 類別和 11 / 3 = 3 (用整數司截斷),你可以分配到類別 0、 到類別 1,第二個三個資料點和最後五個資料點的類別 2 第三個資料點。同等頻率的做法也忽略資料中的天然符。

本文仲介紹的離散化演算法使用資料聚類的方法。原始資料叢集索引使用 k-均值演算法 (它不會考慮到資料中的天然符),然後使用生成的聚類演算法的手段進行分類的新資料。例如,在圖 1,三個手段是 61.00、 67.50 和 75.25。要將關聯到一個絕對的值的數值 62.5,discretizer 確定其中的三個意思是值最接近 62.5 (這是 610),然後指定群集值作為類別值為 62.5 61.0 (即 0) 相關。

K-均值聚類

K-均值聚類演算法是相當簡單的。有許多不同的演算法。最基本的形式,對於一組給定資料點和給定的簇數 k,初始化過程指派隨機選定的群集的每個資料點。然後被計算在群集的每個資料點的手段。接下來,每個資料點是掃描並重新分配給該群集具有最接近的資料點的平均值。計算方法,重新分配群集的步驟不斷重複,直到沒有資料點被重新分配給新的群集。

程式的總體結構

中所示的演示程式圖 1 是一個單一的主控台應用程式。我用Visual Studio2012 年,但演示已沒有明顯的相關性和任何版本的 Microsoft.NET Framework 2.0 或更高版本的Visual Studio應該工作。我創建一個新的 C# 主控台應用,並將它命名為 BinningData。載入範本代碼,在解決方案資源管理器視窗中後, 改名 program.cs,然後從檔到更具描述性的 BinningProgram.cs,和Visual Studio將自動重命名程式類。在原始程式碼的頂部,刪除除系統和 Collections.Generic 的所有命名空間引用。

程式的總體結構,與一些輕微的編輯,介紹在圖 3。調用語句的關鍵可以歸納如下所示:

double[] rawData = new int[] { 66.0, 66.0, ...
};
Discretizer d = new Discretizer(rawData);
double numericVal = 75.5;
int catVal = d.Discretize(numericVal);

圖 3 演示的程式結構

using System;
using System.Collections.Generic;
namespace BinningData
{
  class BinningProgram
  {
    static void Main(string[] args)
    {
      try
      {
        Console.WriteLine("\nBegin discretization of continuous data demo\n");
        double[] rawData = new double[20] {
          66, 66, 66, 67, 67, 67, 67, 68, 68, 69,
          73, 73, 73, 74, 76, 78,
          60, 61, 62, 62 };
        Console.WriteLine("Raw data:");
        ShowVector(rawData, 2, 10);
        Console.WriteLine("\nCreating a discretizer on the raw data");
        Discretizer d = new Discretizer(rawData);
        Console.WriteLine("\nDiscretizer creation complete");
        Console.WriteLine("\nDisplaying internal structure of the discretizer:\n");
        Console.WriteLine(d.ToString());
        Console.WriteLine("\nGenerating three existing and three new data values");
        double[] newData = new double[6] { 62.0, 66.0, 73.0, 59.5, 75.5, 80.5 };
        Console.WriteLine("\nData values:");
        ShowVector(newData, 2, 10);
        Console.WriteLine("\nDiscretizing the data:\n");
        for (int i = 0; i < newData.Length; ++i)
        {
          int cat = d.Discretize(newData[i]);
          Console.WriteLine(newData[i].ToString("F2") + " -> " + cat);
        }
        Console.WriteLine("\n\nEnd discretization demo");
        Console.ReadLine();
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.Message);
        Console.ReadLine();
      }
    } // Main
    public static void ShowVector(double[] vector, int decimals,
      int itemsPerRow) { .
.
}
  } // Program
  public class Discretizer
  {
    public Discretizer(double[] rawData) { .
.
}
    private static double[] GetDistinctValues(double[] array) { .
.
}
    private static bool AreEqual(double x1, double x2) { .
.
}
    public int Discretize(double x) { .
.
}
    public override string ToString() { .
.
}
    private void InitializeClustering() { .
.
}
    private int[] GetInitialIndexes() { .
.
}
    private int InitialCluster(int di, int[] initialIndexes) { .
.
}
    private void Cluster() { .
.
}
    private bool ComputeMeans() { .
.
}
    private bool AssignAll() { .
.
}
    private int MinIndex(double[] distances) { .
.
}
    private static double Distance(double x1, double x2) { .
.
}
  }
} // ns

Discretizer 類

Discretizer 類有四個資料成員:

private double[] data;
private int k;
private double[] means;
private int[] clustering;

Discretizer 建構函式使用數值資料啟用 Discretize 方法接受一個數值,並返回斷然從零開始的整數值。 請注意 Discretizer 將自動確定類別的數目。

陣列資料包含從原始資料的非重複值和用於創建群集。 整數 k 是聯網數目要分配的資料,這也是資料類別的數目。 命名為手段的陣列大小 k,包含分配給每個群集在任何給定時間在聚類演算法的執行過程中的資料點算術均數。

名為聚類的陣列進行編碼的資料如何在任何給定的點聚集在時間。 聚類的陣列的索引指示存儲陣列的資料,在資料點的索引和聚類的陣列中的值表示的當前群集分配。 例如,如果聚類 [9] = 2,則資料 [9] 的資料點分配給第二組。

Discretizer 建構函式

Discretizer 建構函式定義如下:

public Discretizer(double[] rawData)
{
  double[] sortedRawData = new double[rawData.Length];
  Array.Copy(rawData, sortedRawData, rawData.Length);
  Array.Sort(sortedRawData);
  this.data = GetDistinctValues(sortedRawData);
  this.clustering = new int[data.Length];
  this.k = (int)Math.Sqrt(data.Length); // heuristic
  this.means = new double[k];
  this.Cluster();
}

第一步是從原始資料中提取的非重複值。 有幾個方式 可做到這點。 此處的代碼排序的原始資料的副本,然後調用説明器方法,GetDistinctValues。 一旦確定了不同的值,可以分配的聚類的陣列。

這裡是 GetDistinctValues 方法:

private static double[] GetDistinctValues(double[] array)
{
  List<double> distinctList = new List<double>();
  distinctList.Add(array[0]);
  for (int i = 0; i < array.Length - 1; ++i)
    if (AreEqual(array[i], array[i + 1]) == false)
      distinctList.Add(array[i+1]);
  double[] result = new double[distinctList.Count];
  distinctList.CopyTo(result);
  return result;
}

因為已排序的資料來源,方法可以執行單一掃描,尋找兩個連續的值不是平等的實例。 原始資料是類型雙,這意味著,比較兩個完全相等的值可以冒險,所以 AreEqual,説明器方法,用於:

private static bool AreEqual(double x1, double x2)
{
  if (Math.Abs(x1 - x2) < 0.000001) return true;
  else return false;
}

AreEqual 方法使用 0.000001 任意接近閾值。 你可能想要將此值傳遞到 Discretizer 物件作為輸入參數。 一個名為 ε 變數常用的在這種情況。

從原始資料中提取的非重複值之後下, 一步是確定 k、 簇的數量,也是類別的數目。 這裡使用一個經驗法則是,k 成為平方根的資料項目的數目。 替代方法是 k 的寫建構函式,這樣值作為參數傳遞的。 確定最優值是 k 的基本上未破的機器學習研究問題。

已計算的 k 值後,該建構函式分配空間為數組的手段,然後調用群集的方法。 該方法執行的 k-均值聚類資料中的和值陣列可用於將類別值分配給任意數值的最後手段。

聚類演算法

Discretizer 類的心臟是執行 k-均值聚類的代碼。 中列出的群集方法圖 4

圖 4 聚類方法

private void Cluster()
{
  InitializeClustering();
  ComputeMeans();
  bool changed = true; bool success = true;
  int ct = 0;
  int maxCt = data.Length * 10; // Heuristic
  while (changed == true && success == true && ct < maxCt) {
    ++ct;
    changed = AssignAll();
    success = ComputeMeans();
  }
}

方法群集是相對較短,因為它所有的説明器方法的辛勤工作的農場。 InitializeClustering 方法將所有的資料點分配給初始群集。 然後分配給每個群集的資料點的方法是使用的聚類分配來計算的。

聚類演算法的主迴圈,裡面所有資料點的方法 AssignAll 都分配給群集。 AssignAll 方法調用的距離和 MinIndex 的説明器方法。 方法距離定義了兩個資料點之間的距離:

private static double Distance(double x1, double x2)
{
  return Math.Sqrt((x1 - x2) * (x1 - x2));
}

這裡使用的是歐氏距離 (定義為平方差的平方根)。 因為資料點是單個值,而不是向量,歐氏距離等同于 Math.Abs (x 1-x 2),所以您可能想要使用這個簡單的計算。

當聚類的陣列,表示傳回值的 AssignAll,在沒有改變時不能計算陣列,因為計數的值分配給群集的手段是零,或達到最大的迴圈計數器的值時,迴圈將退出。 在這裡,maxCt 是任意分配 10 倍的數目的資料點的值。 一般情況下,這裡的聚類演算法收斂速度極快,並達成 maxCt 的迴圈允出準則可能是由於一個邏輯錯誤,所以你可能想要為此檢查。

聚類過程反復重新分配到群集的值,因為它是可能的值分配給群集的數目可能成為零,使意思不可能計算。 ComputeMeans 嘗試計算所有 k 的 helper 方法意味著,但如果計數為零時返回 false。 該方法在提交圖 5

圖 5 ComputeMeans 方法

private bool ComputeMeans()
{
  double[] sums = new double[k];
  int[] counts = new int[k];
  for (int i = 0; i < data.Length; ++i)
  {
    int c = clustering[i]; // Cluster ID
    sums[c] += data[i];
    counts[c]++;
  }
  for (int c = 0; c < sums.Length; ++c)
  {
    if (counts[c] == 0)
      return false; // fail
    else
      sums[c] = sums[c] / counts[c];
  }
  sums.CopyTo(this.means, 0);
  return true; // Success
}

初始化的聚類

聚類的初始化過程是有點棘手。 假設中所示的資料由組成 11 排序值的圖 1,和 k,集群,數目已被設置為三個。 初始化後,目標是為聚類的陣列成員有三個 0 值儲存格通過三個 1-值中的儲存格 3 到 5,2,0 和其餘五個 2-中值細胞通過 10 6。 換句話說,聚類分析應均勻分佈的頻率。

第一步是生成邊界值 {3,6,9},隱式定義間隔 0-2,3-5 和 6 更大。 這是由 helper 方法 GetInitialIndexes,而只是將資料點的數量除以簇的數目:

private int[] GetInitialIndexes()
{
  int interval = data.Length / k;
  int[] result = new int[k];
  for (int i = 0; i < k; ++i)
    result[i] = interval * (i + 1);
  return result;
}

第二步是定義 helper 方法,計算給定的資料的索引值,使用邊界值的群集值:

private int InitialCluster(int di, int[] initialIndexes)
{
  for (int i = 0; i < initialIndexes.Length; ++i)
    if (di < initialIndexes[i])
      return i;
  return initialIndexes.Length - 1; // Last cluster
}

第三步是要將所有的資料索引配置給群集:

private void InitializeClustering()
{
  int[] initialIndexes = GetInitialIndexes();
  for (int di = 0; di < data.Length; ++di)
  {
    int c = InitialCluster(di, initialIndexes);
    clustering[di] = c;
  }
}

本質上,初始化過程是在本文前面所述的同等頻率方法。

離散化方法

資料已經被聚集後,最終值在陣列可用於將從零開始的斷然值賦給一個數位值的手段。 離散化方法是:

public int Discretize(double x)
{
  double[] distances = new double[k];
  for (int c = 0; c < k; ++c)
    distances[c] = Distance(x, data[means[c]]);
  return MinIndex(distances);
}

該方法計算的距離從輸入值對每個 k x 意味著,然後返回最接近的意思,其中一個群集 ID,也是一個絕對的值的索引。 例如,如果最終手段 61.00、 67.50 和 75.25,並且 x 是 70.00,x [0] 的意思是從距離 = sqrt ((70.00-61.00) ^2) = sqrt(81.00) = 9.00。 同樣,意思是 [1] = sqrt ((70.00-67.50) ^2) = 2.50,意思是 [2] = sqrt ((70.00-75.25) ^2) = 5.25 英寸。 最小的距離是 2.50,這是索引為 [1],所以 70.00 被分配到絕對價值 1。

總結

本文仲介紹的代碼可以使用如是,為您提供高品質的數位分類資料轉換為機器學習。 你可能想要封裝類的庫,而不是應用程式中直接嵌入代碼中的 Discretizer 類。

你可能想要自訂的主要功能是確定數目的類別,k。 一種可能性是設置閾值的值。 低於閾值,每個資料點生成類別的值。 例如,假設你打交道的人的年齡。 假設這些年齡範圍從 1 到 120。 與僅 120 不同的可能值,而不是計算 k 平方根的 120 (這會給你 10 類別),作為可以只設置 k 等於 120。

**博士。**JamesMcCaffrey 工程為微軟公司的華盛頓州雷德蒙德,校園。他曾經參與過多項 Microsoft 產品的研發,包括 Internet Explorer 和 MSN Search。他是作者的".NET 測試自動化食譜"(Apress,2006 年),並可以在達成 jammc@microsoft.com

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