資料探勘之KMeans演算法應用與簡單理解

micDavid發表於2019-07-23

一、背景

煤礦地磅產生了一系列資料:

 

我想從這些資料中,取出最能反映當前車輛重量的資料(有很多資料是車輛上磅過程中產生的資料)。我於是想到了聚類演算法KMeans,該演算法思想比較簡單。

二、演算法步驟

1、從樣本中隨機取出k個值,作為初始中心

2、以k箇中心劃分這些資料,分為k個組

3、重新計算出每個組的中心,作為新中心

4、如果初始中心和新中心不相等,則把新中心作為初始中心,重複2,3。反之,結束

注意:

1、我沒有用嚴格的演算法定義,怕不好理解

2、KMeans善於處理球形資料,因此隨機取k個質心,每個質心吸引離它最近的資料

3、由於質心的取值是不科學的,所以需要不斷地計算調整,直到質心名副其實

三、演算法分析及特點

1、從演算法步驟當中可以看出有兩個問題,需要解決:

   首先,如何計算每個組(簇)的質心?

   其次,如何把值劃分到不同的組?

2、解決上面兩個問題,因場景和要求不同而有不同的小演算法,由於我的資料是一維的,而不是點,所以可以簡單處理:

   a、以每個組的平均值作為質心

   b、根據值離質心的距離(相減),選擇距離最近的組加入

3、此演算法有兩個缺點:

   1)某個組(簇)劃分不充分,還可以再劃分為更小的組。(容易陷入區域性最優)

   2)需要使用者指定k,聚類結果對初始質心的選擇較為敏感(初始選擇不同,聚類結果可能不同)

4、優點:簡單易理解和上手

四、實現

 

    public class KMeans
    {
        /*
        * 聚類函式主體。
        * 針對一維 decimal 陣列。指定聚類數目 k。
        * 將資料聚成 k 類。
        */
        public static decimal[][] cluster(decimal[] p, int k)
        {
            // 存放聚類舊的聚類中心
            decimal[] c = new decimal[k];
            // 存放新計算的聚類中心
            decimal[] nc = new decimal[k];
            // 存放放回結果
            decimal[][] g;
            // 初始化聚類中心
            // 經典方法是隨機選取 k 個
            // 本例中採用前 k 個作為聚類中心
            // 聚類中心的選取不影響最終結果
            for (int i = 0; i < k; i++)
                c[i] = p[i];
            // 迴圈聚類,更新聚類中心
            // 到聚類中心不變為止
            while (true)
            {
                // 根據聚類中心將元素分類
                g = group(p, c);
                // 計算分類後的聚類中心
                for (int i = 0; i < g.Length; i++)
                {
                    nc[i] = center(g[i]);
                }
                // 如果聚類中心不同
                if (!equal(nc, c))
                {
                    c = nc;
                    nc = new decimal[k];
                }
                else
                    break;
            }
            return g;
        }
        /*
         * 聚類中心函式
         * 簡單的一維聚類返回其算數平均值
         * 可擴充套件
         */
        public static decimal center(decimal[] p)
        {
            if (p.Length == 0) return 0;
            return sum(p) / p.Length;
        }
        /*
         * 給定 decimal 型陣列 p 和聚類中心 c。
         * 根據 c 將 p 中元素聚類。返回二維陣列。
         * 存放各組元素。
         */
        public static decimal[][] group(decimal[] p, decimal[] c)
        {
            // 中間變數,用來分組標記
            int[] gi = new int[p.Length];
            // 考察每一個元素 pi 同聚類中心 cj 的距離
            // pi 與 cj 的距離最小則歸為 j 類
            for (int i = 0; i < p.Length; i++)
            {
                // 存放距離
                decimal[] d = new decimal[c.Length];
                // 計算到每個聚類中心的距離
                for (int j = 0; j < c.Length; j++)
                {
                    d[j] = distance(p[i], c[j]);
                }
                // 找出最小距離
                int ci = min(d);
                // 標記屬於哪一組
                gi[i] = ci;
            }
            // 存放分組結果
            decimal[][] g = new decimal[c.Length][];
            // 遍歷每個聚類中心,分組
            for (int i = 0; i < c.Length; i++)
            {
                // 中間變數,記錄聚類後每一組的大小
                int s = 0;
                // 計算每一組的長度
                for (int j = 0; j < gi.Length; j++)
                    if (gi[j] == i)
                        s++;
                // 儲存每一組的成員
                g[i] = new decimal[s];
                s = 0;
                // 根據分組標記將各元素歸位
                for (int j = 0; j < gi.Length; j++)
                    if (gi[j] == i)
                    {
                        g[i][s] = p[j];
                        s++;
                    }
            }
            // 返回分組結果
            return g;
        }

        /*
         * 計算兩個點之間的距離, 這裡採用最簡單得一維歐氏距離, 可擴充套件。
         */
        public static decimal distance(decimal x, decimal y)
        {
            return Math.Abs(x - y);
        }

        /*
         * 返回給定 decimal 陣列各元素之和。
         */
        public static decimal sum(decimal[] p)
        {
            decimal sum = 0.0M;
            for (int i = 0; i < p.Length; i++)
                sum += p[i];
            return sum;
        }

        /*
         * 給定 decimal 型別陣列,返回最小值得下標。
         */
        public static int min(decimal[] p)
        {
            int i = 0;
            decimal m = p[0];
            for (int j = 1; j < p.Length; j++)
            {
                if (p[j] < m)
                {
                    i = j;
                    m = p[j];
                }
            }
            return i;
        }

        /*
         * 判斷兩個 decimal 陣列是否相等。 長度一樣且對應位置值相同返回真。
         */
        public static bool equal(decimal[] a, decimal[] b)
        {
            if (a.Length != b.Length)
                return false;
            else
            {
                for (int i = 0; i < a.Length; i++)
                {
                    if (a[i] != b[i])
                        return false;
                }
            }
            return true;
        }
    }

 

客戶端呼叫:

 1        static void Main(string[] args)
 2         {
 3             var path = string.Empty;
 4             int k = 0;
 5             try
 6             {
 7                 path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "blanceTest.txt");//資料檔案路徑
 8                 k = 4;
 9             }
10             catch (Exception)
11             {
12                 Console.Write("引數錯誤");
13                 return;
14             }
15 
16             decimal[] p = { 1, 2, 3, 5, 6, 7, 9, 10, 11, 20, 21, 22, 23, 27, 40, 41, 42, 43, 61, 62, 63, 100, 150, 200, 1000 };
17 
18             List<decimal> pList = new List<decimal>();
19 
20             var lines = File.ReadAllLines(path);
21 
22             foreach (var line in lines)
23             {
24                 var data = System.Text.RegularExpressions.Regex.Replace(line, @" +", " ");
25                 var datas = data.Split(' ');
26 
27                 pList.AddRange(datas.Where(d => d != "").Select(d => Convert.ToDecimal(d)));
28             }
29 
30             p = pList.ToArray();
31 
32             k = 5;
33             decimal[][] g;
34             g = KMeans.cluster(p, k);
35             for (int i = 0; i < g.Length; i++)
36             {
37                 for (int j = 0; j < g[i].Length; j++)
38                 {
39                     Console.WriteLine(g[i][j]);
40                 }
41                 Console.WriteLine("----------------------");
42             }
43             Console.ReadKey();
44 
45         }

注意:

1、如果資料檔案為空或不存在,則用初始化的p陣列,作為測試資料

2、檔案中的資料,見開篇截圖

 

參考文章:

一維陣列的 K-Means 聚類演算法理解

深入理解K-Means聚類演算法

 

   

 

相關文章