1. 程式人生 > >k-means演算法及python實現

k-means演算法及python實現

本篇文章主要講解聚類分析中的一種常用的演算法k-means,它的全稱叫作k均值演算法。

k-means原理

k-means演算法是一種基於原型的、劃分的聚類技術。

基於原型可以理解為基於質心,也就是說,每個物件到定義該簇質心的距離比到其他簇質心的距離更近。當質心沒有意義時,原型可以視為最靠近中心的點。當然,還有其它的基於圖的、基於密度的、基於概念的簇。

基於劃分的意思是,可以將資料物件集合劃分成不重疊的子集,使得每個物件恰在一個子集中。與之相對應的是,基於層次的聚類,例如說,將整個集合看成是一個簇,然後從中分成兩個子簇,這樣不斷地劃分子簇,直到類別個數達到所要求的標準。類似於一棵決策樹,不斷地劃分節點,直到滿足類別要求。

k-means演算法
隨機選擇k個點作為初始質心
repeat
    將每個點指派到最近的質心,形成k個簇
    重新計算每個簇的質心
until 質心不再發生變化

簡單來說,這個演算法完成的是這樣的一個過程:首先,隨機選擇k個質心,然後將每個點按照距離分到離它最近的那個簇,這樣的話,每個簇上都有若干個點,根據這些點,求它們的質心,然後再次進行分配點,重複這個過程,知道質心不再發生變化。

python實現k-means演算法

首先,我們假定有四類資料,每類資料都有一箇中心點,我們看若干次迭代後是否可以將所有的資料完全分開。在本次實驗中,我們採用經典的歐氏距離作為衡量兩個點之間差距的指標。

1.生成資料:假設四類點的中心分別是(2,2),(4,3),(3,5),(1,4),以它們為中心,各自生成100個方差在0.5左右的點,它們的分佈如下圖所示:

import matplotlib.pyplot as plt
import numpy as np
import math

N = 100
x1 = np.random.normal(2,0.5,N)
y1 = np.random.normal(2,0.4,N)
plt.scatter(x1,y1,c='r',marker='o')
x2 = np.random.normal(4,0.3,N)
y2 = np.random.normal(3,0.6,N)
plt.scatter(x2,y2,c='r',marker='o')
x3 = np.random.normal(3,0.5,N)
y3 = np.random.normal(5,0.4,N)
plt.scatter(x3,y3,c='r',marker='o')
x4 = np.random.normal(1,0.3,N)
y4 = np.random.normal(4,0.6,N)
plt.scatter(x4,y4,c='r',marker='o')
plt.xlim(0,6)
plt.ylim(0,6)
plt.show()

2.隨機選擇四個點作為初始的k個點。(x1_pre,y1_pre),(x2_pre,y2_pre),(x3_pre,y3_pre),(x4_pre,y4_pre)

x1_pre = np.random.random(1)*5
y1_pre = np.random.random(1)*5
x2_pre = np.random.random(1)*5
y2_pre = np.random.random(1)*5
x3_pre = np.random.random(1)*5
y3_pre = np.random.random(1)*5
x4_pre = np.random.random(1)*5
y4_pre = np.random.random(1)*5

3.將所有點分配到這四個簇中去,我這裡用不同的顏色標出。

for i in range(0,len(x_data)):
	x = x_data[i]
	y = y_data[i]
	dis1 = math.sqrt((x-x1_pre)**2+(y-y1_pre)**2)
	dis2 = math.sqrt((x-x2_pre)**2+(y-y2_pre)**2)
	dis3 = math.sqrt((x-x3_pre)**2+(y-y3_pre)**2)
	dis4 = math.sqrt((x-x4_pre)**2+(y-y4_pre)**2)
	dis_min = min(dis1,dis2,dis3,dis4)
	if dis_min==dis1:
		plt.scatter(x,y,color='b',marker='+')
		x1.append(x)
		y1.append(y)
	elif dis_min==dis2:
		plt.scatter(x,y,color='y',marker='^')
		x2.append(x)
		y2.append(y)
	elif dis_min==dis3:
		plt.scatter(x,y,color='g',marker='s')
		x3.append(x)
		y3.append(y)
	else:
		plt.scatter(x,y,color='r',marker='o')
		x4.append(x)
		y4.append(y)
plt.show()

4.然後是根據每個簇中的所有點,求它們的中心點,之後是分配所有點的過程。

完整程式碼如下:

import matplotlib.pyplot as plt
import numpy as np
import math

N = 100
x1 = np.random.normal(2,0.5,N)
y1 = np.random.normal(2,0.4,N)
plt.scatter(x1,y1,c='r',marker='o')
x2 = np.random.normal(4,0.3,N)
y2 = np.random.normal(3,0.6,N)
plt.scatter(x2,y2,c='r',marker='o')
x3 = np.random.normal(3,0.5,N)
y3 = np.random.normal(5,0.4,N)
plt.scatter(x3,y3,c='r',marker='o')
x4 = np.random.normal(1,0.3,N)
y4 = np.random.normal(4,0.6,N)
plt.scatter(x4,y4,c='r',marker='o')
plt.xlim(0,6)
plt.ylim(0,6)
plt.show()

x1_pre = np.random.random(1)*5
y1_pre = np.random.random(1)*5
x2_pre = np.random.random(1)*5
y2_pre = np.random.random(1)*5
x3_pre = np.random.random(1)*5
y3_pre = np.random.random(1)*5
x4_pre = np.random.random(1)*5
y4_pre = np.random.random(1)*5

x_data = []
for num in x1:
	x_data.append(num)
for num in x2:
	x_data.append(num)
for num in x3:
	x_data.append(num)
for num in x4:
	x_data.append(num)
y_data = []
for num in y1:
	y_data.append(num)
for num in y2:
	y_data.append(num)
for num in y3:
	y_data.append(num)
for num in y4:
	y_data.append(num)	
	
for index in range(10):
	x1 = []
	y1 = []
	x2 = []
	y2 = []
	x3 = []
	y3 = []
	x4 = []
	y4 = []
	for i in range(0,len(x_data)):
		x = x_data[i]
		y = y_data[i]
		dis1 = math.sqrt((x-x1_pre)**2+(y-y1_pre)**2)
		dis2 = math.sqrt((x-x2_pre)**2+(y-y2_pre)**2)
		dis3 = math.sqrt((x-x3_pre)**2+(y-y3_pre)**2)
		dis4 = math.sqrt((x-x4_pre)**2+(y-y4_pre)**2)
		dis_min = min(dis1,dis2,dis3,dis4)
		if dis_min==dis1:
			plt.scatter(x,y,color='b',marker='+')
			x1.append(x)
			y1.append(y)
		elif dis_min==dis2:
			plt.scatter(x,y,color='y',marker='^')
			x2.append(x)
			y2.append(y)
		elif dis_min==dis3:
			plt.scatter(x,y,color='g',marker='s')
			x3.append(x)
			y3.append(y)
		else:
			plt.scatter(x,y,color='r',marker='o')
			x4.append(x)
			y4.append(y)
	plt.show()
	x1_pre = float(sum(x1))/len(x1)
	y1_pre = float(sum(y1))/len(y1)
	x2_pre = float(sum(x2))/len(x2)
	y2_pre = float(sum(y2))/len(y2)
	x3_pre = float(sum(x3))/len(y3)
	y3_pre = float(sum(y3))/len(y3)
	x4_pre = float(sum(x4))/len(x4)
	y4_pre = float(sum(y4))/len(y4)
	

大約是迭代了9次左右,簇內的所有的點都不在變化,演算法結束。

k-means演算法的優缺點:

優點:演算法比較簡單,易於實現,並且適用於各種資料型別

缺點:k值需要提前確定;對異常點敏感;不能處理非球形簇、不同尺寸和不同密度的簇。