1. 程式人生 > >資料探勘演算法之K_means演算法

資料探勘演算法之K_means演算法

轉載地址:https://blog.csdn.net/baimafujinji/article/details/50570824

 

聚類是將相似物件歸到同一個簇中的方法,這有點像全自動分類。簇內的物件越相似,聚類的效果越好。支援向量機、神經網路所討論的分類問題都是有監督的學習方式,現在我們所介紹的聚類則是無監督的。其中,K均值(K-means)是最基本、最簡單的聚類演算法。

 

學習更多機器學習演算法原理並瞭解在R中如何實現機器學習的技術,你還可以參考我的《R語言實戰:機器學習與資料分析》(電子工業出版社出版)一書。

在K均值演算法中,質心是定義聚類原型(也就是機器學習獲得的結果)的核心。在介紹演算法實施的具體過程中,我們將演示質心的計算方法。而且你將看到除了第一次的質心是被指定的以外,此後的質心都是經由計算均值而獲得的。

 

首先,選擇K個初始質心(這K個質心並不要求來自於樣本資料集),其中K是使用者指定的引數,也就是所期望的簇的個數。每個資料點都被收歸到距其最近之質心的分類中,而同一個質心所收歸的點集為一個簇。然後,根據本次分類的結果,更新每個簇的質心。重複上述資料點分類與質心變更步驟,直到簇內資料點不再改變,或者等價地說,直到質心不再改變。

 

基本的K均值演算法描述如下:

 

 

 

 

根據資料點到新質心的距離,再次對資料集中的資料進行分類,如圖13-2(c)所示。然後,演算法根據新的分類來計算新的質心,並再次根據資料點到新質心的距離,對資料集中的資料進行分類。結果發現簇內資料點不再改變,所以演算法執行結束,最終的聚類結果如圖13-2(d)所示。

對於距離函式和質心型別的某些組合,演算法總是收斂到一個解,即K均值到達一種狀態,聚類結果和質心都不再改變。但為了避免過度迭代所導致的時間消耗,實踐中,也常用一個較弱的條件替換掉“質心不再發生變化”這個條件。例如,使用“直到僅有1%的點改變簇”。

 

儘管K均值聚類比較簡單,但它也的確相當有效。它的某些變種甚至更有效, 並且不太受初始化問題的影響。但K均值並不適合所有的資料型別。它不能處理非球形簇、不同尺寸和不同密度的簇,儘管指定足夠大的簇個數時它通常可以發現純子簇。對包含離群點的資料進行聚類時,K均值也有問題。在這種情況下,離群點檢測和刪除大有幫助。K均值的另一個問題是,它對初值的選擇是敏感的,這說明不同初值的選擇所導致的迭代次數可能相差很大。此外,K值的選擇也是一個問題。顯然,演算法本身並不能自適應地判定資料集應該被劃分成幾個簇。最後,K均值僅限於具有質心(均值)概念的資料。一種相關的K中心點聚類技術沒有這種限制。在K中心點聚類中,我們每次選擇的不再是均值,而是中位數。這種演算法實現的其他細節與K均值相差不大,我們不再贅述。

 

最後我們給出一個實際應用的例子。(程式碼採用我最喜歡用做資料探勘的R語言來實現

 

一組來自世界銀行的資料統計了30個國家的兩項指標,我們用如下程式碼讀入檔案並顯示其中最開始的幾行資料。可見,資料共分三列,其中第一列是國家的名字,該項與後面的聚類分析無關,我們更關心後面兩列資訊。第二列給出的該國第三產業增加值佔GDP的比重,最後一列給出的是人口結構中年齡大於等於65歲的人口(也就是老齡人口)佔總人口的比重。

 


 

為了方便後續處理,下面對讀入的資料庫進行一些必要的預處理,主要是調整列標籤,以及用國名替換掉行標籤(同時刪除包含國名的列)。

 

如果你繪製這些資料的散點圖,不難發現這些資料大致可以分為兩組。事實上,資料中有一半的國家是OECD成員國,而另外一半則屬於發展中國家(包括一些東盟國家、南亞國家和拉美國家)。所以我們可以採用下面的程式碼來進行K均值聚類分析。

 

 

對於聚類結果,限於篇幅我們仍然只列出了最開始的幾條。但是如果用圖形來顯示的話,可能更易於接受。下面是示例程式碼。

 

上述程式碼的執行結果如圖13-3所示。

 

 

另外一種與k-means非常類似的演算法是k-median演算法。此處已經無需再詳細介紹k-中值演算法的細節了,基本上和k-means一樣,只是把所有均值出現的地方換成中值而已。這個思想看起好像很不起眼,但是你還別說,k-median演算法還真的存在,而且是k-means演算法的一個重要補充和改進。

 

 

 

 

 

用python實現的程式碼入下:(詳細程式碼見https://github.com/yantijin/Lean_DataMining

#coding=utf-8
from collections import defaultdict
from math import sqrt
from random import uniform


"""
    需要定義的函式:
        計算聚類中心;
        計算兩點之間距離;
        將所有點按照距離中心點的距離分類;
        隨機生成k箇中心點
"""

"""
    計算兩點之間的距離
    INPUT: 
        data1,data2: data of the point 
    OUTPUT:
        distance between data1 and data2
"""


def get_distance(data1,data2):
    dimensions = len(data1)
    dis = 0
    for dimension in range(dimensions):
        dis =dis +(data1[dimension]-data2[dimension])**2
    return  sqrt(dis)


"""
    計算聚類中心
    INPUT:
        data_set: an list consists of list
    OUTPUT:
        center: calculate center of the input datas
"""


def cal_center(data_set):
    center = []
    dimensions = len(data_set[0])
    amount = len(data_set)
    for dimension in range(dimensions):
        point_sum = 0
        for data in data_set :
            point_sum = point_sum +data[dimension]
        center.append(point_sum/amount)
    return center


"""
    根據現有分類,重新計算聚類中心
    INPUT:
        assignments: a list that assign each data to a center
        data_set: an list consists of list
    OUTPUT:
        center: update center by the assignments and data_set
"""


def update_center(assignments, data_set):
    center = []
    dt = defaultdict(list)
    for assignment, data in zip(assignments, data_set):
        dt[assignment].append(data)
    for points in dt.values():
        center.append(cal_center(points))
    return center


"""
    將所有點按照距離中心點的距離分類
    INPUT: 
        data_set: an list consists of list
        center: clustering center until now
    OUTPUT:
        assignments: assign each data for a center
"""


def assign_point(data_set, center):
    len_centers = len(center)
    len_data_sets = len(data_set)
    assignments = []
    for len_data_set in range(len_data_sets):
        shortest_dis = float("inf")
        assignment = -1
        for len_center in range(len_centers):
            dis = get_distance(data_set[len_data_set], center[len_center])
            if dis < shortest_dis :
                shortest_dis = dis
                assignment = len_center
        assignments.append(assignment)
    return assignments


"""
    隨機生成k箇中心點
    INPUT: 
        data_set: an list consists of list
        k: num of centers that wants for clustering
    OUTPUT:
        init_center: init random center 
"""


def generate_init_center(data_set, k):
    dimensions = len(data_set[0])
    center = []
    min_max = defaultdict(float)
    for data in data_set:

        for dimension in range(dimensions):
            min_key = 'min_%d' % dimension
            max_key = 'max_%d' % dimension
            val = data[dimension]
            if min_key not in min_max or val < min_max[min_key]:
                min_max[min_key] = val
            if max_key not in min_max or val > min_max[max_key]:
                min_max[max_key] = val
    # 此步要產生k個在min和max之間的data
    for _k in range(k):
        data = []
        for dimension in range(dimensions):
            min_val = min_max['min_%d' % dimension]
            max_val = min_max['max_%d' % dimension]
            data.append(uniform(min_val, max_val))
        center.append(data)
    return center


"""
    k_means演算法實現
    INPUT: 
        data_set: an list consists of list
        k: num of centers that wants for clustering
    OUTPUT:
"""


def kmeans(data_set, k):
    center = generate_init_center(data_set, k)
    assignments = assign_point(data_set, center)
    new_center = update_center(assignments, data_set)
    while new_center != center:
        center = new_center
        assignments = assign_point(data_set, center)
        new_center = update_center(assignments, data_set)
    return zip(assignments, data_set), new_center


"""
    檔案讀寫
"""


def fileread(filepath):
    file_object = open(filepath)
    data = []
    data1 = []
    try:
        for line in file_object.readlines():
            line.replace('\n', '', 1)
            tmp = line.split(' ')
            tmplen = len(tmp)
            for nm in range(tmplen):
               data1.append(float(tmp[nm]))
            data.append(data1)
            data1=[]

    finally:
        file_object.close()
    return data



if __name__== "__main__":
    filepath = input("請輸入檔案地址及命名\n")

    data_set = fileread(filepath)
    k = 3
    zip, center = kmeans(data_set, k)
    for i in zip:
        print(i)
    print(center)

注意檔案輸入資料的格式應該按照如下來寫,

3 3
4 10
9 6
14 8
18 11
21 7