手寫一個K-Means算法

主函數(shù)

# -*- coding: utf-8 -*-
"""
    參考:     https://gist.github.com/iandanforth/5862470
"""

import random
from kmeans_tools import Cluster, get_distance, gen_random_sample
import matplotlib.pyplot as plt
from matplotlib import colors as mcolors


def kmeans(samples, k, cutoff):
    """
        kmeans函數(shù)
    """

    # 隨機選k個樣本點作為初始聚類中心
    init_samples = random.sample(samples, k)

    # 創(chuàng)建k個聚類衰齐,聚類的中心分別為隨機初始的樣本點
    clusters = [Cluster([sample]) for sample in init_samples]

    # 迭代循環(huán)直到聚類劃分穩(wěn)定
    n_loop = 0
    while True:
        # 初始化一組空列表用于存儲每個聚類內(nèi)的樣本點
        lists = [[] for _ in clusters]

        # 開始迭代
        n_loop += 1
        # 遍歷樣本集中的每個樣本
        for sample in samples:
            # 計算樣本點sample和第一個聚類中心的距離
            smallest_distance = get_distance(sample, clusters[0].centroid)
            # 初始化屬于聚類 0
            cluster_index = 0

            # 計算和其他聚類中心的距離
            for i in range(k - 1):
                # 計算樣本點sample和聚類中心的距離
                distance = get_distance(sample, clusters[i+1].centroid)
                # 如果存在更小的距離获列,更新距離
                if distance < smallest_distance:
                    smallest_distance = distance
                    cluster_index = i + 1

            # 找到最近的聚類中心丰泊,更新所屬聚類
            lists[cluster_index].append(sample)

        # 初始化最大移動距離
        biggest_shift = 0.0

        # 計算本次迭代中,聚類中心移動的距離
        for i in range(k):
            shift = clusters[i].update(lists[i])
            # 記錄最大移動距離
            biggest_shift = max(biggest_shift, shift)

        # 如果聚類中心移動的距離小于收斂閾值循帐,即:聚類穩(wěn)定
        if biggest_shift < cutoff:
            print("第{}次迭代后,聚類穩(wěn)定庸娱。".format(n_loop))
            break
    # 返回聚類結(jié)果
    return clusters


def run_main():
    """
        主函數(shù)
    """
    # 樣本個數(shù)
    n_samples = 1000

    # 特征個數(shù) (特征維度)
    n_feat = 2

    # 特征數(shù)值范圍
    lower = 0
    upper = 200

    # 聚類個數(shù)
    n_cluster = 3

    # 生成隨機樣本
    samples = [gen_random_sample(n_feat, lower, upper) for _ in range(n_samples)]

    # 收斂閾值
    cutoff = 0.2

    clusters = kmeans(samples, n_cluster, cutoff)

    # 輸出結(jié)果
    for i, c in enumerate(clusters):
        for sample in c.samples:
            print('聚類--{}啃炸,樣本點--{}'.format(i, sample))

    # 可視化結(jié)果
    plt.subplot()
    color_names = list(mcolors.cnames)
    for i, c in enumerate(clusters):
        x = []
        y = []
        random.choice
        color = [color_names[i]] * len(c.samples)
        for sample in c.samples:
            x.append(sample.coords[0])
            y.append(sample.coords[1])
        plt.scatter(x, y, c=color)
    plt.show()

if __name__ == '__main__':
    run_main()

k-means_tools

# -*- coding: utf-8 -*-

"""
    參考:     https://gist.github.com/iandanforth/5862470
"""

import math
import random


class Cluster(object):
    """
        聚類
    """

    def __init__(self, samples):
        if len(samples) == 0:
            # 如果聚類中無樣本點
            raise Exception("錯誤:一個空的聚類赃额!")

        # 屬于該聚類的樣本點
        self.samples = samples

        # 該聚類中樣本點的維度
        self.n_dim = samples[0].n_dim

        # 判斷該聚類中所有樣本點的維度是否相同
        for sample in samples:
            if sample.n_dim != self.n_dim:
                raise Exception("錯誤: 聚類中樣本點的維度不一致加派!")

        # 設(shè)置初始化的聚類中心
        self.centroid = self.cal_centroid()

    def __repr__(self):
        """
            輸出對象信息
        """
        return str(self.samples)

    def update(self, samples):
        """
            計算之前的聚類中心和更新后聚類中心的距離
        """

        old_centroid = self.centroid
        self.samples = samples
        self.centroid = self.cal_centroid()
        shift = get_distance(old_centroid, self.centroid)
        return shift

    def cal_centroid(self):
        """
           對于一組樣本點計算其中心點
        """
        n_samples = len(self.samples)
        # 獲取所有樣本點的坐標(biāo)(特征)
        coords = [sample.coords for sample in self.samples]
        unzipped = zip(*coords)
        # 計算每個維度的均值
        centroid_coords = [math.fsum(d_list)/n_samples for d_list in unzipped]

        return Sample(centroid_coords)


class Sample(object):
    """
        樣本點類
    """
    def __init__(self, coords):
        self.coords = coords    # 樣本點包含的坐標(biāo)
        self.n_dim = len(coords)    # 樣本點維度

    def __repr__(self):
        """
            輸出對象信息
        """
        return str(self.coords)


def get_distance(a, b):
    """
        返回樣本點a, b的歐式距離
        參考:https://en.wikipedia.org/wiki/Euclidean_distance#n_dimensions
    """
    if a.n_dim != b.n_dim:
        # 如果樣本點維度不同
        raise Exception("錯誤: 樣本點維度不同阁簸,無法計算距離!")

    acc_diff = 0.0
    for i in range(a.n_dim):
        square_diff = pow((a.coords[i]-b.coords[i]), 2)
        acc_diff += square_diff
    distance = math.sqrt(acc_diff)

    return distance


def gen_random_sample(n_dim, lower, upper):
    """
        生成隨機樣本
    """
    sample = Sample([random.uniform(lower, upper) for _ in range(n_dim)])
    return sample


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末哼丈,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子筛严,更是在濱河造成了極大的恐慌醉旦,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件桨啃,死亡現(xiàn)場離奇詭異车胡,居然都是意外死亡,警方通過查閱死者的電腦和手機照瘾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門匈棘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人析命,你說我怎么就攤上這事主卫。” “怎么了鹃愤?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵簇搅,是天一觀的道長。 經(jīng)常有香客問我软吐,道長瘩将,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任凹耙,我火速辦了婚禮姿现,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘肖抱。我一直安慰自己备典,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布虐沥。 她就那樣靜靜地躺著熊经,像睡著了一般。 火紅的嫁衣襯著肌膚如雪欲险。 梳的紋絲不亂的頭發(fā)上镐依,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天,我揣著相機與錄音天试,去河邊找鬼槐壳。 笑死,一個胖子當(dāng)著我的面吹牛喜每,可吹牛的內(nèi)容都是我干的务唐。 我是一名探鬼主播雳攘,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼枫笛!你這毒婦竟也來了吨灭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤刑巧,失蹤者是張志新(化名)和其女友劉穎喧兄,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體啊楚,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡吠冤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了恭理。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拯辙。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖颜价,靈堂內(nèi)的尸體忽然破棺而出涯保,到底是詐尸還是另有隱情,我是刑警寧澤拍嵌,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布遭赂,位于F島的核電站,受9級特大地震影響横辆,放射性物質(zhì)發(fā)生泄漏撇他。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一狈蚤、第九天 我趴在偏房一處隱蔽的房頂上張望困肩。 院中可真熱鬧,春花似錦脆侮、人聲如沸锌畸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽潭枣。三九已至,卻和暖如春幻捏,著一層夾襖步出監(jiān)牢的瞬間盆犁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工篡九, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留谐岁,地道東北人。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像伊佃,于是被迫代替她去往敵國和親窜司。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,685評論 2 360

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