序
前兩個月在做項目突然發(fā)現(xiàn)Canopy算法發(fā)現(xiàn)網上直接用python實現(xiàn)的不多,因為Mahout已經包含了這個算法,需要使用的時候僅需要執(zhí)行Mahout幾條命令即可,并且多數(shù)和MapReduce以及Hadoop分布式框架一起使用,感興趣的可以在網上查閱。但出于學習和興趣的態(tài)度,我更想嘗試用python來親自實現(xiàn)一些底層算法。
簡介
The canopy clustering algorithm is an unsupervised pre-clustering algorithm introduced by Andrew McCallum, Kamal Nigam and Lyle Ungar in 2000.[1]
It is often used as preprocessing step for the K-means algorithm or the Hierarchical clustering algorithm. It is intended to speed up clustering operations on large data sets, where using another algorithm directly may be impractical due to the size of the data set.
以上面出自于維基百科.
Canopy算法是2000年由Andrew McCallum, Kamal Nigam and Lyle Ungar提出來的,它是對k-means聚類算法和層次聚類算法的預處理。眾所周知,kmeans的一個不足之處在于k值需要通過人為的進行調整,后期可以通過肘部法則(Elbow Method)和輪廓系數(shù)(Silhouette Coefficient)來對k值進行最終的確定,但是這些方法都是屬于“事后”判斷的,而Canopy算法的作用就在于它是通過事先粗聚類的方式,為k-means算法確定初始聚類中心個數(shù)和聚類中心點。
Canopy算法過程:
The algorithm proceeds as follows, using two thresholds
T1 (the loose distance) and T2(the tight distance), whereT1>T2[1][2]
1.Begin with the set of data points to be clustered.
2.Remove a point from the set, beginning a new 'canopy'.
3.For each point left in the set, assign it to the new canopy if the distance less than the loose distance T1.
4.If the distance of the point is additionally less than the tight distance T2, remove it from the original set.
5.Repeat from step 2 until there are no more data points in the set to cluster.
6.These relatively cheaply clustered canopies can be sub-clustered using a more expensive but accurate algorithm.
中文說明可以參考Canopy聚類算法(經典,看圖就明白)。
代碼實現(xiàn)
使用的包:
# -*- coding: utf-8 -*-
# @Author: Alan Lau
# @Date: 2017-09-05 22:56:16
# @Last Modified by: Alan Lau
# @Last Modified time: 2017-09-05 22:56:16
import math
import random
import numpy as np
from datetime import datetime
from pprint import pprint as p
import matplotlib.pyplot as plt
1.首先我在算法中預設了一個二維(為了方便后期畫圖呈現(xiàn)在二維平面上)數(shù)據(jù)dataset。當然也可以使用高緯度的數(shù)據(jù),并且我將canopy核心算法寫入了類中,后期可以通過直接調用的方式對任何維度的數(shù)據(jù)進行處理,當然只是小批量的,大批量的數(shù)據(jù)可以移步Mahout和Hadoop了,反正我的算法肯定沒它們好哈哈。
# 隨機生成500個二維[0,1)平面點
dataset = np.random.rand(500, 2)
2.然后生成個類,類的屬性如下
class Canopy:
def __init__(self, dataset):
self.dataset = dataset
self.t1 = 0
self.t2 = 0
加入設定t1和t2初始值以及判斷大小函數(shù)
# 設置初始閾值
def setThreshold(self, t1, t2):
if t1 > t2:
self.t1 = t1
self.t2 = t2
else:
print('t1 needs to be larger than t2!')
3.距離計算,各個中心點之間的距離計算方法我使用的歐式距離。
# 使用歐式距離進行距離的計算
def euclideanDistance(self, vec1, vec2):
return math.sqrt(((vec1 - vec2)**2).sum())
4.再寫個從dataset中根據(jù)dataset的長度隨機選擇下標的函數(shù)
# 根據(jù)當前dataset的長度隨機選擇一個下標
def getRandIndex(self):
return random.randint(0, len(self.dataset) - 1)
5.核心算法
def clustering(self):
if self.t1 == 0:
print('Please set the threshold.')
else:
canopies = [] # 用于存放最終歸類結果
while len(self.dataset) != 0:
rand_index = self.getRandIndex()
current_center = self.dataset[rand_index] # 隨機獲取一個中心點,定為P點
current_center_list = [] # 初始化P點的canopy類容器
delete_list = [] # 初始化P點的刪除容器
self.dataset = np.delete(
self.dataset, rand_index, 0) # 刪除隨機選擇的中心點P
for datum_j in range(len(self.dataset)):
datum = self.dataset[datum_j]
distance = self.euclideanDistance(
current_center, datum) # 計算選取的中心點P到每個點之間的距離
if distance < self.t1:
# 若距離小于t1,則將點歸入P點的canopy類
current_center_list.append(datum)
if distance < self.t2:
delete_list.append(datum_j) # 若小于t2則歸入刪除容器
# 根據(jù)刪除容器的下標,將元素從數(shù)據(jù)集中刪除
self.dataset = np.delete(self.dataset, delete_list, 0)
canopies.append((current_center, current_center_list))
return canopies
為了方便后面的數(shù)據(jù)可視化,我這里的canopies定義的是一個數(shù)組,當然也可以使用dict。
6.main()函數(shù)
def main():
t1 = 0.6
t2 = 0.4
gc = Canopy(dataset)
gc.setThreshold(t1, t2)
canopies = gc.clustering()
print('Get %s initial centers.' % len(canopies))
#showCanopy(canopies, dataset, t1, t2)
Canopy聚類可視化代碼
def showCanopy(canopies, dataset, t1, t2):
fig = plt.figure()
sc = fig.add_subplot(111)
colors = ['brown', 'green', 'blue', 'y', 'r', 'tan', 'dodgerblue', 'deeppink', 'orangered', 'peru', 'blue', 'y', 'r',
'gold', 'dimgray', 'darkorange', 'peru', 'blue', 'y', 'r', 'cyan', 'tan', 'orchid', 'peru', 'blue', 'y', 'r', 'sienna']
markers = ['*', 'h', 'H', '+', 'o', '1', '2', '3', ',', 'v', 'H', '+', '1', '2', '^',
'<', '>', '.', '4', 'H', '+', '1', '2', 's', 'p', 'x', 'D', 'd', '|', '_']
for i in range(len(canopies)):
canopy = canopies[i]
center = canopy[0]
components = canopy[1]
sc.plot(center[0], center[1], marker=markers[i],
color=colors[i], markersize=10)
t1_circle = plt.Circle(
xy=(center[0], center[1]), radius=t1, color='dodgerblue', fill=False)
t2_circle = plt.Circle(
xy=(center[0], center[1]), radius=t2, color='skyblue', alpha=0.2)
sc.add_artist(t1_circle)
sc.add_artist(t2_circle)
for component in components:
sc.plot(component[0], component[1],
marker=markers[i], color=colors[i], markersize=1.5)
maxvalue = np.amax(dataset)
minvalue = np.amin(dataset)
plt.xlim(minvalue - t1, maxvalue + t1)
plt.ylim(minvalue - t1, maxvalue + t1)
plt.show()
我把每個點都染上了其歸屬聚類中心點的顏色,還是挺漂亮的,這就是所謂的數(shù)據(jù)之美吧...
當然也有人問,t1和t2的初始值如何設定,后期的聚類中心點完全依賴這兩個值的變化。t1和t2可以通過交叉驗證的方式獲得,具體怎么做,得視乎數(shù)據(jù)以及用戶的需求,具體可以參考相關的論文。