本文章是由機器翻譯。

資料叢集

使用 k-Means 偵測異常資料

JamesMcCaffrey

 

 

請考慮這樣一個問題:如何在超大型資料集中識別異常資料項目,例如,如何識別可能具有欺騙性的信用卡交易、有風險的貸款應用程式等等。 檢測異常資料的一種方法是將資料項目分組為類似的聚類,然後在每個聚類中尋找在某種意義上與該聚類中的其他資料項目不同的資料項目。

有許多不同的聚類分析演算法。 k 平均值演算法是其中一種使用最久且最廣泛的演算法。 在本文中,我將介紹 k 平均值演算法的工作原理並提供一個完整的 C# 演示程式。 現在有很多獨立的資料聚類分析工具,為何要從頭開始創建 k 平均值聚類分析代碼呢? 現有的聚類分析工具可能很難或無法集成到軟體系統之中,它們可能無法通過定制處理異常情況,而且這些工具可能存在版權或其他智慧財產權問題。 閱讀完本文後,您將能夠嘗試進行 k 平均值聚類分析並掌握向 .NET 應用程式添加聚類分析功能的基本知識。

為了體會 k 平均值聚類分析並瞭解我要在本文中討論的問題,最好先看一下圖 1。 演示程式首先創建了一個虛擬資料集,其中包含 20 個數據項。 在聚類分析術語中,資料項目有時也稱為元組。 這裡,每個元組表示一個人,並有兩個數值屬性值:身高(以英寸為單位)和體重(以磅為單位)。 k 平均值演算法的一個局限性在於它僅適用于資料元組完全是數位的情況。

Clustering Using K-Means
圖 1 使用 k 平均值進行聚類分析

現在,這些虛擬資料已載入記憶體中的陣列。 接著,將聚類的數目設置為三個。 雖然有一些高級聚類分析方法可建議要使用的最佳聚類數目,但一般來說,資料聚類分析是一個探索性的過程,往往需要通過反復試驗才能找到要使用的最佳聚類數目。 您稍後將看到,k 平均值聚類分析是一個反覆運算過程。 演示程式有一個變數 maxCount,該變數用於限制主聚類分析迴圈的執行次數。 這裡,該值隨意設為 30。

接著,演示程式在後臺使用 k 平均值演算法將每個資料元組放入三個聚類中的一個。 可以使用許多方法對聚類分析進行編碼。 在本例中,聚類分析由一個 int 陣列定義,其中陣列索引表示元組,而關聯的陣列值表示從 0 開始的聚類 ID。 因此,在圖 1 中,元組 0 (65.0, 220.0) 分配給聚類 0,元組 1 (73.0, 160.0) 分配給聚類 1,元組 2 (59.0, 110.0) 分配給聚類 2,元組 3 (61.0, 120.0) 也分配給聚類 2,等等。 可以看到,有八個元組分配給聚類 0,五個元組分配給聚類 1,七個元組分配給聚類 2。

隨後,演示程式顯示按聚類分組的資料。 如果仔細查看聚類資料,可以看出聚類 0 可稱為大體重人員聚類,聚類 1 可稱為高個人員聚類,而聚類 2 可稱為矮個人員聚類。 演示程式通過分析分配給聚類 0 的元組,根據某種標準得出結論:元組 5 (67.0, 240.0) 是最異常的元組。

在隨後幾部分內容中,我將逐步為您介紹生成圖 1 所示螢幕擷取畫面的代碼,這樣,您將能夠根據自己的需要修改該代碼。 本文假定您至少具有 C 系列語言的中級程式設計技能,但對資料聚類分析知識不作要求。 演示程式雖以 C# 編寫,但並未採用 OOP 樣式,因此如果您願意,將該演示程式重構為其他語言應當不會太困難。 在本文中,我將提供演示程式的所有原始程式碼。 您也可以從 archive.msdn.microsoft.com/mag201302kmeans 獲取原始程式碼。

k 平均值演算法

至少在理論上來說,k 平均值演算法是相當簡單的。 但您將會發現,有些實現細節會有些棘手。 k 平均值演算法的核心概念是質心。 在資料聚類分析中,一組資料元組的質心就是該組中最具代表性的那個元組。 通過舉例可以很好地解釋這個原理。 假設您有三個身高-體重元組,類似圖 1 所示:

[a] (61.0, 100.0)
[b] (64.0, 150.0)
[c] (70.0, 140.0)

其中哪個元組最具代表性? 一種方法是計算數學平均值元組,然後將最接近該平均值元組的元組選為質心。 因此,在本例中,平均值元組為:

[m] = ((61.0 + 64.0 + 70.0) / 3, (100.0 + 150.0 + 140.0) / 3)
    = (195.0 / 3, 390.0 / 3)
    = (65.0, 130.0)

那麼現在,三個元組中的哪一個最接近 (65.0, 130.0)? 有多種方法可以定義最近元組。 最常用的方法是使用歐氏距離,這也是本演示程式中所用的方法。 從字面上定義,兩個元組之間的歐氏距離是指元組每個組分之差的平方和的平方根。 還是舉例說明最清楚。 元組 (61.0, 100.0) 與平均值元組 (65.0, 130.0) 之間的歐氏距離為:

dist(m,a) = sqrt((65.0 - 61.0)^2 + (130.0 - 100.0)^2)
          = sqrt(4.0^2 + 30.0^2)
          = sqrt(16.0 + 900.0)
          = sqrt(916.0)
          = 30.27

Similarly:

dist(m,b) = sqrt((65.0 - 64.0)^2 + (130.0 - 150.0)^2)
          = 20.02
dist(m,c) = sqrt((65.0 - 70.0)^2 + (130.0 - 140.0)^2)
          = 11.18

由於三個距離中的最小距離是數學平均值與元組 [c] 之間的距離,因此元組 [c] 就是這三個元組的質心。 您可能希望使用兩個元組之間距離的不同定義來試用演示程式,以便了解這些定義對最終生成的聚類分析有何影響。

在確定聚類質心的概念後,k 平均值演算法就比較簡單了。 偽代碼如下:

assign each tuple to a randomly selected cluster
compute the centroid for each cluster
loop until no improvement or until maxCount
  assign each tuple to best cluster
   (the cluster with closest centroid to tuple)
  update each cluster centroid
   (based on new cluster assignments)
end loop
return clustering

如果您在網上搜索,可以找到一些很好的 k 平均值演算法線上動畫來播放。 圖 2 中的圖像顯示了演示程式所生成的聚類分析。 每個聚類中帶圓圈的資料項目就是聚類質心。

Clustered Data and Centroids
圖 2 聚類資料和質心

程式的整體結構

圖 3 列出了圖 1 所示演示程式的整體結構(有少許改動)。 我使用Visual Studio2010 新建了一個名為 ClusteringKMeans 的 C# 主控台應用程式;任何最近版本的Visual Studio也都應適用。 在解決方案資源管理器視窗中,我將檔 Program.cs 重命名為 ClusteringKMeans­Program.cs,這會自動重命名範本生成的類。 我刪除了檔頂部不需要的 using 語句。

圖 3 程式的整體結構

using System;
namespace ClusteringKMeans
{
  class ClusteringKMeansProgram
  {
    static void Main(string[] args)
    {
      try
      {
        Console.WriteLine("\nBegin outlier data detection demo\n");
        Console.WriteLine("Loading all (height-weight) data into memory");
        string[] attributes = new string[] { "Height", "Weight" };
        double[][] rawData = new double[20][];
        rawData[0] = new double[] { 65.0, 220.0 };
        rawData[1] = new double[] { 73.0, 160.0 };
        rawData[2] = new double[] { 59.0, 110.0 };
        rawData[3] = new double[] { 61.0, 120.0 };
        rawData[4] = new double[] { 75.0, 150.0 };
        rawData[5] = new double[] { 67.0, 240.0 };
        rawData[6] = new double[] { 68.0, 230.0 };
        rawData[7] = new double[] { 70.0, 220.0 };
        rawData[8] = new double[] { 62.0, 130.0 };
        rawData[9] = new double[] { 66.0, 210.0 };
        rawData[10] = new double[] { 77.0, 190.0 };
        rawData[11] = new double[] { 75.0, 180.0 };
        rawData[12] = new double[] { 74.0, 170.0 };
        rawData[13] = new double[] { 70.0, 210.0 };
        rawData[14] = new double[] { 61.0, 110.0 };
        rawData[15] = new double[] { 58.0, 100.0 };
        rawData[16] = new double[] { 66.0, 230.0 };
        rawData[17] = new double[] { 59.0, 120.0 };
        rawData[18] = new double[] { 68.0, 210.0 };
        rawData[19] = new double[] { 61.0, 130.0 };
        Console.WriteLine("\nRaw data:\n");
        ShowMatrix(rawData, rawData.Length, true);
        int numAttributes = attributes.Length;
        int numClusters = 3;
        int maxCount = 30;
        Console.WriteLine("\nk = " + numClusters + " and maxCount = " + maxCount);
        int[] clustering = Cluster(rawData, numClusters, numAttributes, maxCount);
        Console.WriteLine("\nClustering complete");
        Console.WriteLine("\nClustering in internal format: \n");
        ShowVector(clustering, true);
        Console.WriteLine("\nClustered data:");
        ShowClustering(rawData, numClusters, clustering, true);
        double[] outlier = Outlier(rawData, clustering, numClusters, 0);
        Console.WriteLine("Outlier for cluster 0 is:");
        ShowVector(outlier, true);
        Console.WriteLine("\nEnd demo\n");
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.Message);
      }
    } // Main
    // 14 short static method definitions here
  }
}

為簡單起見,我使用了一種靜態方法,並刪除了所有錯誤檢查功能。 演示代碼的第一部分設置要進行聚類分析的身高和體重資料。 因為只有 20 個元組,所以我對資料進行了硬編碼並將這些資料存儲到記憶體中一個名為 rawData 的陣列中。 一般來說,資料將存儲在文字檔或 SQL 表中。 這種情況下,必須編寫一個 Helper 函數來將資料載入到記憶體中。 如果資料來源太大而無法放入電腦記憶體,您必須修改演示代碼以逐一查看外部資料源而不是資料陣列。

設置原始資料後,演示程式會調用 Helper 函數 ShowMatrix 來顯示資料。 接著,將值 2(身高和體重)、3 和 30 分別賦予變數 num­Attributes、numClusters 和 maxCount。 前面曾提到,maxCount 用於限制主演算法處理迴圈中的反覆運算次數。 k 平均值演算法往往會快速聚合,但您可能有必要使用 maxCount 值試驗一下。

所有聚類分析工作均由方法 Cluster 執行。 該方法返回一個 int 陣列,後者定義如何將每個元組分配給某一聚類。 完成後,演示程式會顯示編碼的聚類分析,還會顯示按照聚類進行分組的原始資料。

演示程式最後使用方法 Outliers 分析聚類資料,確定離群元組,也就是可能異常的元組。 該方法接受聚類 ID 並返回距聚類質心(最具代表性的元組)最遠的資料元組的值(由歐氏距離測量)。 在本例中,對於聚類 0(即大體重人員聚類),離群值元組是 (67.0, 240.0),也就是體重最重的人員。

計算聚類質心

前面提到,聚類質心是在分配給聚類的元組中最具代表性的元組,而確定聚類質心的一種方法是計算數學平均值元組,然後找到最接近該平均值元組的那個元組。 Helper 方法 UpdateMeans 計算每個聚類的數學平均值元組,如圖 4 所示。

圖 4:方法 UpdateMeans

static void UpdateMeans(double[][] rawData, int[] clustering,
  double[][] means)
{
  int numClusters = means.Length;
  for (int k = 0; k < means.Length; ++k)
    for (int j = 0; j < means[k].Length; ++j)
      means[k][j] = 0.0;
  int[] clusterCounts = new int[numClusters];
  for (int i = 0; i < rawData.Length; ++i)
  {
    int cluster = clustering[i];
    ++clusterCounts[cluster];
    for (int j = 0; j < rawData[i].Length; ++j)
      means[cluster][j] += rawData[i][j];
  }
  for (int k = 0; k < means.Length; ++k)
    for (int j = 0; j < means[k].Length; ++j)
      means[k][j] /= clusterCounts[k]; // danger
  return;
}

方法 UpdateMeans 假定已存在一個陣列的陣列,該陣列名稱為 means,因而無需創建該陣列並返回它。 由於假定陣列 means 存在,您可能希望將其設置為一個 ref 參數。 陣列 means 是使用 Helper 方法 Allocate 創建的:

static double[][] Allocate(int numClusters, int numAttributes)
{
  double[][] result = new double[numClusters][];
  for (int k = 0; k < numClusters; ++k)
    result[k] = new double[numAttributes];
  return result;
}

means 陣列中的第一個索引表示聚類 ID,第二個索引表示屬性。 例如,如果 means[0][1] = 150.33,則聚類 0 中元組的體重 (1) 值的平均值為 150.33。

方法 UpdateMeans 首先將陣列 means 中的現有值清空,逐一查看每個資料元組,記下每個聚類中元組的計數,累計每個屬性的總和,然後將每個累計總和除以對應的聚類計數。 請注意,如果有任意聚類計數為 0,則該方法將會引發異常,因此您可能需要添加錯誤檢查功能。

方法 ComputeCentroid(如圖 5 所示)確定質心值,即最接近于指定聚類的平均元組值的那個元組的值。

圖 5 方法 ComputeCentroid

static double[] ComputeCentroid(double[][] rawData, int[] clustering,
  int cluster, double[][] means)
{
  int numAttributes = means[0].Length;
  double[] centroid = new double[numAttributes];
  double minDist = double.MaxValue;
  for (int i = 0; i < rawData.Length; ++i) // walk thru each data tuple
  {
    int c = clustering[i]; 
    if (c != cluster) continue;
    double currDist = Distance(rawData[i], means[cluster]);
    if (currDist < minDist)
    {
      minDist = currDist;
      for (int j = 0; j < centroid.Length; ++j)
        centroid[j] = rawData[i][j];
    }
  }
  return centroid;
}

方法 ComputeCentroid 逐一查看資料集中的每個元組,並跳過未不在指定聚類中的元組。 對於指定聚類中的每個元組,使用 Helper 方法 Distance 計算該元組與聚類平均值之間的歐氏距離。 將存儲並返回最接近平均值(與之距離最小)的元組值。

方法 UpdateCentroids 調用每個聚類的 ComputeCentroid 以便為所有聚類指定質心:

static void UpdateCentroids(double[][] rawData, int[] clustering,
  double[][] means, double[][] centroids)
{
  for (int k = 0; k < centroids.Length; ++k)
  {
    double[] centroid = ComputeCentroid(rawData, clustering, k, means);
    centroids[k] = centroid;
  }
}

方法 UpdateCentroids 假定存在一個陣列的陣列,該陣列名稱為 centroids。 陣列 centroids 與陣列 means 十分類似:第一個索引表示聚類 ID,第二個索引表示資料屬性。

總之,每個聚類都有一個質心,即該聚類中最具代表性的元組。 計算質心值的方法是:在每個聚類中找到最接近于該聚類中的平均值元組的那個元組。 每個資料元組都將分配給其聚類質心最接近于該元組的聚類。

Distance 函數和資料正常化

方法 ComputeCentroid 調用 Distance 方法來確定哪個資料元組最接近于聚類平均值。 如前所述,歐氏距離是測量元組與平均值之間距離的最常用方法:

static double Distance(double[] tuple, double[] vector)
{
  double sumSquaredDiffs = 0.0;
  for (int j = 0; j < tuple.Length; ++j)
    sumSquaredDiffs += Math.Pow((tuple[j] - vector[j]), 2);
  return Math.Sqrt(sumSquaredDiffs);
}

您可能希望考慮使用其他方法來定義距離。 另一種十分常見的方法是使用每個組分之差的絕對值之和。 由於歐氏距離會對差值進行平方,因此較大差值的權重更加高於較小差值。

在 k 平均值聚類分析演算法中,與選擇距離函數相關的另一個重要因素是資料正常化。 演示程式使用的是未正常化的原始資料。 由於元組體重值通常是像 160.0 這樣的值,而元組身高值通常是像 67.0 這樣的值,因此體重差異要比身高差異的影響大得多。 許多情況下,除了探究對原始資料的聚類分析之外,在聚類分析之前對原始資料進行正常化也十分有用。 有許多方法可用於對資料進行正常化。 一種常用的方法是:計算每個屬性的平均值 (m) 和標準差 (sd),然後計算每個屬性值 (v) 的正常化值 nv = (v-m)/sd。

將每個元組分配給一個聚類

有了計算每個聚類的質心的方法後,便可編寫一種方法將每個元組分配給一個聚類。 方法 Assign 如圖 6 所示。

圖 6 方法 Assign

static bool Assign(double[][] rawData, 
  nt[] clustering, double[][] centroids)
{
  int numClusters = centroids.Length;
  bool changed = false;
  double[] distances = new double[numClusters];
  for (int i = 0; i < rawData.Length; ++i)
  {
    for (int k = 0; k < numClusters; ++k)
      distances[k] = Distance(rawData[i], centroids[k]);
    int newCluster = MinIndex(distances);
    if (newCluster != clustering[i])
    {
      changed = true;
      clustering[i] = newCluster;
    }
  }
  return changed;
}

方法 Assign 接受質心值的陣列並逐一查看每個資料元組。 對於每個資料元組,將計算其與每個聚類質心的距離並將該距離存儲在一個名為 distances 的局部陣列中,其中該陣列的索引表示聚類 ID。 隨後,Helper 方法 MinIndex 確定陣列 distances 中具有最小距離值的索引,即聚類中具有與元組最接近的質心的聚類 ID。

Here’s helper method MinIndex:

static int MinIndex(double[] distances)
{
  int indexOfMin = 0;
  double smallDist = distances[0];
  for (int k = 0; k < distances.Length; ++k)
  {
    if (distances[k] < smallDist)
    {
      smallDist = distances[k]; indexOfMin = k;
    }
  }
  return indexOfMin;
}

在 Assign 中,如果計算出的聚類 ID 不同于存儲在陣列 clustering 中的現有聚類 ID,則將更新陣列 clustering,並且切換一個布林型標誌以指示的聚類中至少發生了一項更改。 此標誌將用於確定何時停止主演算法迴圈,即超過最大反覆運算數或聚類中未發生任何更改時。

k 平均值演算法的這一實現假定至少總是有一個資料元組分配給每個聚類。 如圖 6 所示,方法 Assign 並不能避免聚類未獲分配元組的情況。 實際上,這通常不是問題。 但要避免這一錯誤情況卻有些棘手。 我通常使用的方法是創建一個名為 centroidIndexes 的陣列,該陣列與 centroids 陣列配合使用。 前面曾提到,陣列 centroids 存放質心值,例如,在圖 2 中 (61.0, 120.0) 是聚類 2 的質心。 陣列 centroidIndexes 存放關聯的元組索引,例如 [3]。 然後,在 Assign 方法中,第一步是將存放質心值的資料元組分配給每個聚類,然後該方法逐一查看其餘每個元組並將其分配給一個聚類。 這種方法可保證每個聚類都至少有一個元組。

The Cluster Method

圖 7 所示,方法 Cluster 是調用所有 Helper 和子 Helper 方法來實際執行資料聚類分析的高級常式。

圖 7 Cluster 方法

static int[] Cluster(double[][] rawData, int numClusters,
  int numAttributes,  int maxCount)
{
  bool changed = true;
  int ct = 0;
  int numTuples = rawData.Length;
  int[] clustering = InitClustering(numTuples, numClusters, 0);
  double[][] means = Allocate(numClusters, numAttributes);
  double[][] centroids = Allocate(numClusters, numAttributes);
  UpdateMeans(rawData, clustering, means);
  UpdateCentroids(rawData, clustering, means, centroids);
  while (changed == true && ct < maxCount)
  {
    ++ct;
    changed = Assign(rawData, clustering, centroids);
    UpdateMeans(rawData, clustering, means);
    UpdateCentroids(rawData, clustering, means, centroids);
  }
  return clustering;
}

主 while 迴圈反復將每個資料元組分配給聚類,計算每個聚類的新元組平均值,然後使用新的平均值計算每個聚類的新質心值。 當聚類分配中不再發生更改或達到某一最大計數時,該迴圈便退出。 由於 means 陣列僅用於計算質心,因此您可能希望通過在方法 UpdateCentroids 中調用 UpdateMeans 來重構聚類。

在開始處理迴圈之前,將使用方法 InitClustering 初始化 clustering 陣列。

static int[] InitClustering(int numTuples, 
  int numClusters, int randomSeed)
{
  Random random = new Random(randomSeed);
  int[] clustering = new int[numTuples];
  for (int i = 0; i < numClusters; ++i)
    clustering[i] = i;
  for (int i = numClusters; i < clustering.Length; ++i)
    clustering[i] = random.Next(0, numClusters);
  return clustering;
}

InitClustering 方法首先將元組 0 到 num­Clusters-1 分別分配給聚類 0 到 numClusters-1,這樣每個聚類最初至少分配有一個元組。 其餘元組將分配給隨機播放的聚類。

歷來對 k 平均值聚類分析初始化的研究之多有些出人意料,因此除了此處提供的方法之外,您可能也希望試用一下其他方法。 許多情況下,k 平均值演算法所生成的最終聚類分析取決於初始化聚類分析的方式。

查找異常資料

使用資料聚類分析的一種方法是只探究不同的聚類,然後查找異常或出人意料的結果。 另一種可能性是在聚類中查找異常資料元組。 演示程式將檢查聚類 0,使用名為 Outlier 的方法在該聚類中找到離聚類質心最遠的元組,該方法如圖 8 所示。

圖 8 Outlier 方法

static double[] Outlier(double[][] rawData, int[] clustering,
  int numClusters, int cluster)
{
  int numAttributes = rawData[0].Length;
  double[] outlier = new double[numAttributes];
  double maxDist = 0.0;
  double[][] means = Allocate(numClusters, numAttributes);
  double[][] centroids = Allocate(numClusters, numAttributes);
  UpdateMeans(rawData, clustering, means);
  UpdateCentroids(rawData, clustering, means, centroids);
  for (int i = 0; i < rawData.Length; ++i)
  {
    int c = clustering[i];
    if (c != cluster) continue;
    double dist = Distance(rawData[i], centroids[cluster]);
    if (dist > maxDist)
    {
      maxDist = dist;
      Array.Copy(rawData[i], outlier, rawData[i].Length);
    }
  }
  return outlier;
}

在初始化 means 和 centroids 陣列之後,方法 Outlier 逐一查看指定聚類中的每個元組,計算該元組與聚類質心的歐氏距離,然後返回與質心值距離最遠的元組的值。 您也可以考慮返回最遠資料元組的索引。

還有許多其他方法可用於檢查聚類資料的異常情況。 例如,您可能希望確定每個元組與其已分配的聚類質心之間的平均距離,或者可能希望檢查聚類質心彼此之間的距離。

顯示常式

為完整起見,這裡提供一些簡化的顯示常式。 通過代碼下載可獲得更豐富的版本。 如果使用這些簡化的常式,您必須在 Main 方法中修改其調用。 要顯示原始資料、平均值和質心,您可以使用:

static void ShowMatrix(double[][] matrix)
{
  for (int i = 0; i < numRows; ++i)
  {
    Console.Write("[" + i.ToString().PadLeft(2) + "]  ");
    for (int j = 0; j < matrix[i].Length; ++j)
      Console.Write(matrix[i][j].ToString("F1") + "  ");
    Console.WriteLine("");
  }
}

要顯示 clustering 陣列,您可以使用:

static void ShowVector(int[] vector)
{
  for (int i = 0; i < vector.Length; ++i)
    Console.Write(vector[i] + " ");
  Console.WriteLine("");
}

要顯示 outlier 的值,您可以使用:

static void ShowVector(double[] vector)
{
  for (int i = 0; i < vector.Length; ++i)
    Console.Write(vector[i].ToString("F1") + " ");
  Console.WriteLine("");
}

此外,要顯示按聚類分組的原始資料,您可以使用:

static void ShowClustering(double[][] rawData, 
  int numClusters, int[] clustering)
{
  for (int k = 0; k < numClusters; ++k) // Each cluster
  {
    for (int i = 0; i < rawData.Length; ++i) // Each tuple
      if (clustering[i] == k)
      {
        for (int j = 0; j < rawData[i].Length; ++j)
          Console.Write(rawData[i][j].ToString("F1") + " ");
        Console.WriteLine("");
      }
    Console.WriteLine("");
  }
}

總結

資料聚類分析與資料分類緊密相關,有時也會與之混淆。聚類分析是一種不受監控的方法,它將資料項目分組在一起而預先不知道分組結果。 聚類分析通常是一個探索性的過程。 相比之下,分類是一種受監控的方法,它需要在訓練資料中指定已知組,隨後將每個資料元組放入其中某個組中。 分類通常用於預測目的。

本文中提供的代碼和說明應該已經給您提供了足夠的資訊,使您可以體驗 k 平均值資料聚類分析,創建完全可自訂的獨立聚類分析工具,或者向 .NET 應用程式添加聚類分析功能而無需依賴任何外部因素。 除了 k 平均值演算法以外,還有許多其他聚類分析演算法。我將在以後的 MSDN 雜誌文章仲介紹其中一些演算法,包括資料熵最小化、類別公用程式和 Naive Bayes 推理等。

Dr.James McCaffrey 供職于 Volt Information Sciences, Inc.,在該公司他負責管理對華盛頓州雷蒙德市沃什灣 Microsoft 總部園區的軟體工程師進行的技術培訓。他參與過多項 Microsoft 產品的研發工作,其中包括 Internet Explorer 和 MSN Search。他是《.NET Test Automation Recipes》(Apress, 2006) 的作者,您可以通過以下電子郵箱位址與他聯繫:jammc@microsoft.com

衷心感謝以下技術專家對本文的審閱:DarrenGehring