現(xiàn)成調(diào)包>底層編寫>算法改進(jìn)——我對k-means由淺入深的理解

前言

最近研究中要用到聚類。K-means是第一個(gè)去研究的算法池充,覺得這個(gè)簡單的算法也很有意思眠冈,這期推送把它的來龍去脈記錄下略号,順便分享!

原理與數(shù)據(jù)

k-means的原理洋闽,相對簡單玄柠,總結(jié)起來就一句話:依據(jù)“組內(nèi)距離最大,組間距離最小”原則把點(diǎn)劃分為k類诫舅。不贅述羽利,原理可參考:

《機(jī)器學(xué)習(xí)》周志華

《機(jī)器學(xué)習(xí)實(shí)戰(zhàn)》

網(wǎng)絡(luò)博客、百度

因?yàn)檠芯繑?shù)據(jù)不公開刊懈,本期數(shù)據(jù)采用隨機(jī)構(gòu)造數(shù)據(jù)这弧,如下代碼:

##【代碼1】隨機(jī)產(chǎn)生點(diǎn)##
import numpy as np
import matplotlib.pyplot as plt

def get_data(real_center= [(1, 1), (1, 2), (2, 2), (2, 1)]):
    # 先在四個(gè)中心點(diǎn)附近產(chǎn)生一堆數(shù)據(jù)
    point_number = 50

    points_x = []
    points_y = []

    for center in real_center:
        offset_x, offset_y = np.random.randn(point_number) * 0.3, np.random.randn(point_number) * 0.25
        x_val, y_val = center[0] + offset_x, center[1] + offset_y
        points_x.append(x_val)
        points_y.append(y_val)

    points_x = np.concatenate(points_x)
    points_y = np.concatenate(points_y)
    p_list = np.stack([points_x, points_y], axis=1)
    return p_list
real_center = [(1, 1), (1, 2), (2, 2), (2, 1)]
points=get_data(real_center)
plt.scatter(np.array(real_center)[:,0],np.array(real_center)[:,1],c='r',marker='o',s=100)
plt.scatter(points[:,0],points[:,1])
plt.show()

我們以 (1, 1), (1, 2), (2, 2), (2, 1) 四個(gè)點(diǎn)為中心產(chǎn)生了隨機(jī)分布的點(diǎn),可視化一下虚汛,會(huì)生成四個(gè)可見的簇匾浪。

調(diào)包

<pre style="background:white">最偷懶的方法就是spss操作、是Python調(diào)包卷哩、R語言調(diào)包了蛋辈,比如說Python中內(nèi)置的sklearn.cluster 就提供了k-means算法。程序調(diào)用如下:</pre>

##【代碼2】sklearn調(diào)用##
from sklearn.cluster import KMeans
datas=get_data()
kmeans_model = KMeans(n_clusters=4, n_init=15).fit(datas)  # 隨機(jī)選擇初始中心
labels = kmeans_model.labels_
plt.scatter(datas[:, 0], datas[:, 1], c=labels, cmap='rainbow')
plt.show()

這是內(nèi)置k-means出圖的結(jié)果将谊,大致上是已經(jīng)滿足聚類出結(jié)果的需求了冷溶,符合構(gòu)造數(shù)據(jù)集的原始特點(diǎn)。但是我想觀察它是怎樣演化的尊浓,并且輸出一些自定義的參數(shù)逞频,如鄧恩指數(shù)。這種方法限制了我們對結(jié)果的分析栋齿,還是自己編寫吧C缯汀!瓦堵!

自編寫

編寫的代碼如下(代碼還沒來得及“美化”基协,但算法可行的)

##【代碼3】k-means自編寫##
import numpy as np
import matplotlib.pyplot as plt
def distance(vector1,vector2):
    #把距離函數(shù)單獨(dú)拿出來的原因是距離的定義可能會(huì)變化的,比如經(jīng)緯度和普通數(shù)字的不同
    op1 = np.sqrt(np.sum(np.square(vector1 - vector2)))
    op2 = np.linalg.norm(vector1 - vector2)
    return op2

def my_Kmeans(points,class_num):
    ...
        :輸入待分類的點(diǎn)集谷丸、分類數(shù)目堡掏;輸出分類的結(jié)果(【1,0刨疼,2泉唁、、揩慕、】)亭畜,可視化表達(dá)
    :points的類型為np數(shù)組
    ...
    i=1
    rand_index = np.random.choice(range(len(points)), size=class_num, replace=False)#replace代表不重復(fù)
    centre=points[rand_index, :]
    centre_diff=(np.sum(centre==0)-class_num)
    while centre_diff !=0 and i <=500:
        i+=1
        class_points=[[]for i in range(class_num)]#用來存放點(diǎn)
        class_results=[]#用來存放類別數(shù)字,如1迎卤,2拴鸵,
        #將全部點(diǎn)歸屬到類
        for point in points:
            all_dis=[]
            for sta in centre:
                dis=distance(point,sta)
                all_dis.append(dis)
            index=all_dis.index(min(all_dis))
            class_results.append(index)
            class_points[index].append(point)
        #從新計(jì)算各類中心
        new_centre=[]
        for each in class_points:
            each_centre=np.array(each).mean(axis=0)
            new_centre.append(list(each_centre))
        minus =centre-np.array(new_centre)
        centre=np.array(new_centre)
        centre_diff=(np.sum(minus==0)-class_num)
    return class_results

 #plt.scatter(X_scatter[:, 0], X_scatter[:, 1], c=lables, cmap='rainbow')

 #這種方法參考下,沒必要這么麻煩的
 def my_plt(points,class_reslts,class_num):
    ...
    :param points: points的類型為np數(shù)組
    :param class_reslts: 分類結(jié)果,是一個(gè)列表
    ...
    for clss in range(class_num):
        filter=points[np.array(class_reslts)==clss]
        print(filter)
        plt.scatter(filter[:, 0],filter[:, 1])
    plt.show()

 if __name__=='__main__':
    class_num=3
    np.random.seed(0)
    a=np.random.randint(low=0,high=20,size=20)#產(chǎn)生10對(x,y)
    A=a.reshape(-1,2)
    class_reslts=my_Kmeans(A,class_num)
    print(class_reslts)
    my_plt(A,class_reslts,class_num)

自編寫的方便多了劲藐,來個(gè)演化過程的動(dòng)圖

模型改進(jìn)Kmedoids

(一) 理論

因?yàn)閗-means算法是一個(gè)最基礎(chǔ)的基于點(diǎn)劃分的模型八堡,今20年來很多研究者對原模型進(jìn)行的改進(jìn),比如說:

對初始值敏感: K-means++聘芜,intelligent K-means兄渺, genetic K-means;

對噪聲敏感:k-medoids, k-medians,

僅僅適用于數(shù)值型:k-modes

不能解決非凸數(shù)據(jù):kernel K-means

(二) 實(shí)戰(zhàn)

改進(jìn)算法很多汰现,根據(jù)我個(gè)人實(shí)際需求(數(shù)據(jù)集中有明顯離群點(diǎn))挂谍,接著解了下k-medoids算法。當(dāng)存在噪音和孤立點(diǎn)時(shí), K-medoids 比 K-means 更健壯瞎饲。

k-means與k-medoids之間的差異就是可以理解為對于數(shù)據(jù)樣本的平均值和中位數(shù)之間的差異:前者的取值范圍可以是連續(xù)空間中的任意值口叙,而后者的取值卻只能是數(shù)據(jù)樣本范圍中的樣本。

舉個(gè)例子說明:在聚類中心更替的時(shí)候嗅战,左圖很明顯有兩類中心妄田。但是在右圖中出現(xiàn)了個(gè)異常點(diǎn),導(dǎo)致中心變到了中心點(diǎn)1仗哨,影響后續(xù)的聚類形庭。但是k-medois中會(huì)選擇中心點(diǎn)2作為中心(median oids),顯然更加合理厌漂。改進(jìn)算法的代碼如下:

##【代碼4】k-medoids自編寫##
import numpy as np
import matplotlib.pyplot as plt
from sklearn import metrics

def initial(points,k):
    #給定初始的點(diǎn)萨醒,返回隨機(jī)的k個(gè)初始點(diǎn)(點(diǎn)不重復(fù))
    i=0
    return_centre=[]
    while i<k:
        rand_index = np.random.choice(range(len(points)), size=1, replace=False)  # replace代表不重復(fù)
        temp =list(points[rand_index, :][0])
        if temp not in return_centre:
            return_centre.append(temp)
            i+=1
    return np.array(return_centre)
def distance(vector1,vector2):
    #把距離函數(shù)單獨(dú)拿出來的原因是距離的定義可能會(huì)變化的,比如經(jīng)緯度和普通數(shù)字的不同
    op1 = np.sqrt(np.sum(np.square(vector1 - vector2)))
    op2 = np.linalg.norm(vector1 - vector2)
    return op1

def get_better_centre(class_points):
    # 輸入為二維列表[[],[],[]...],每個(gè)子類存放了一類分類點(diǎn)苇倡;輸出為每個(gè)子類的medoids富纸,列表形式[]
    new_centre=[]
    for each_class in class_points:
        evaluation=[]
        for each_points in each_class:
            diff=np.array(each_class)-np.array(each_points)
            diff_square = [[diff[i][j] ** 2 for j in range(len(diff[i]))] for i in range(len(diff))]
            diff_sum=sum(sum(i) for i in diff_square)
            evaluation.append(diff_sum)
        min_index=evaluation.index(min(evaluation))
        point=each_class[min_index]
        new_centre.append(point)
    return new_centre

def my_kmedoids(points,class_num):
    '''
    輸入待分類的點(diǎn)集、分類數(shù)目旨椒;輸出分類的結(jié)果(【1晓褪,0,2综慎、涣仿、、】)示惊,可視化表達(dá)
    points的類型為np數(shù)組
    '''
    i=1
    centre=initial(points,class_num)
    # print(type(centre))
    centre_diff=(np.sum(centre==0)-class_num)
    data_dim = points.shape[1]  # 每個(gè)點(diǎn)數(shù)據(jù)有多少個(gè)維度
    # print(centre)
    final_label=[[]]
    while (centre_diff !=0 and i <=1000):#迭代完500次之后好港,即使出現(xiàn)不收斂的情況也停止迭代
        # global class_
        i+=1
        class_points=[[]for i in range(class_num)]#用來存放點(diǎn)
        class_=[]#用來存放類別數(shù)字,如1米罚,2钧汹,
                #將全部點(diǎn)歸屬到類
        for point in points:
            all_dis=[]
            for sta in centre:
                dis=distance(point,sta)
                all_dis.append(dis)
            index=all_dis.index(min(all_dis))
            class_.append(index)
            class_points[index].append(point)
        final_label[0]=class_
        #從新計(jì)算各類中心
        new_centre=get_better_centre(class_points)#調(diào)用函數(shù),求新的中心點(diǎn)
        minus =centre-np.array(new_centre)
        centre=np.array(new_centre)
        # print(np.sum(minus==0))
        centre_diff=(np.sum(minus==0)/data_dim-class_num)
    final_class=final_label[0]
    # print(i)
    return final_class

def k_medoids_(np_points,class_num,repetition=50):#np格式的數(shù)據(jù)录择,分類數(shù)拔莱,重復(fù)運(yùn)行次數(shù)
    eval=[]
    all_labs=[]
    for i in range(repetition):
        # print('重復(fù)了%次',i)
        lables=my_kmedoids(np_points,class_num)
        # print(np_points)
        # print("數(shù)據(jù)",len(np_points))
        # print("分類",len(lables))
        silhouettescore = metrics.silhouette_score(np_points, lables, metric='euclidean')  # 指標(biāo)值在[-1,1]之間碗降,越接近1越好
        all_labs.append(lables)
        eval.append(silhouettescore)
    index=eval.index(max(eval))
    return all_labs[index]

if __name__ == '__main__':
    #構(gòu)造一個(gè)數(shù)據(jù)集
    class_num=3
    repetition=20#隨機(jī)初始運(yùn)行的次數(shù),最后取最優(yōu)的結(jié)果
    np.random.seed(0)
    a=np.random.randint(low=0,high=20,size=20)#產(chǎn)生10對(x,y)
    A=a.reshape(-1,2)
    #調(diào)用函數(shù)
    lables=k_medoids_(A,class_num,repetition)
    print(type(lables))
    ##可視化分類結(jié)果
    plt.scatter(A[:, 0], A[:, 1], c=lables, cmap='rainbow')
    plt.show()

##繪制動(dòng)圖##
k=4
from clustering.k_medoids import k_medoids_
datas=get_data()
labels=k_medoids_(datas,k,repetition=1)
plt.show()

雙動(dòng)圖比較塘秦,迭代次數(shù)比較讼渊。

本期內(nèi)容結(jié)束,關(guān)注【交通科研Lab】微信公眾號(hào)嗤形,點(diǎn)擊“閱讀原文”精偿,獲取全部代碼。歡迎關(guān)注下一期《如何科學(xué)地選擇聚類數(shù)K赋兵?》

如果您覺得本篇文章對您有用,請不吝點(diǎn)贊搔预!您的鼓勵(lì)與支持是我創(chuàng)作的最大動(dòng)力E凇!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拯田,一起剝皮案震驚了整個(gè)濱河市历造,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌船庇,老刑警劉巖吭产,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異鸭轮,居然都是意外死亡臣淤,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進(jìn)店門窃爷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來邑蒋,“玉大人,你說我怎么就攤上這事按厘∫降酰” “怎么了?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵逮京,是天一觀的道長卿堂。 經(jīng)常有香客問我,道長懒棉,這世上最難降的妖魔是什么草描? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮漓藕,結(jié)果婚禮上陶珠,老公的妹妹穿的比我還像新娘。我一直安慰自己享钞,他們只是感情好揍诽,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布诀蓉。 她就那樣靜靜地躺著,像睡著了一般暑脆。 火紅的嫁衣襯著肌膚如雪渠啤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天添吗,我揣著相機(jī)與錄音沥曹,去河邊找鬼。 笑死碟联,一個(gè)胖子當(dāng)著我的面吹牛妓美,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鲤孵,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼壶栋,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了普监?” 一聲冷哼從身側(cè)響起贵试,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎凯正,沒想到半個(gè)月后毙玻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡廊散,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年桑滩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奸汇。...
    茶點(diǎn)故事閱讀 38,814評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡施符,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出擂找,到底是詐尸還是另有隱情戳吝,我是刑警寧澤,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布贯涎,位于F島的核電站听哭,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏塘雳。R本人自食惡果不足惜陆盘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望败明。 院中可真熱鬧隘马,春花似錦、人聲如沸妻顶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至幔嗦,卻和暖如春酿愧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背邀泉。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工嬉挡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人汇恤。 一個(gè)月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓庞钢,卻偏偏與公主長得像,于是被迫代替她去往敵國和親屁置。 傳聞我的和親對象是個(gè)殘疾皇子焊夸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評論 2 351

推薦閱讀更多精彩內(nèi)容