一、聚類(無監督)的目標
使同一類對象的相似度盡可能地大;不同類對象之間的相似度盡可能地小。
二、層次聚類
層次聚類算法實際上分為兩類:自上而下或自下而上。自下而上的算法在一開始就將每個數據點視為一個單一的聚類,然后依次合並(或聚集)類,直到所有類合並成一個包含所有數據點的單一聚類。因此,自下而上的層次聚類稱為合成聚類或HAC。聚類的層次結構用一棵樹(或樹狀圖)表示。樹的根是收集所有樣本的唯一聚類,而葉子是只有一個樣本的聚類。在繼續學習算法步驟之前,先查看下面的圖表

1.我們首先將每個數據點作為一個單獨的聚類進行處理。如果我們的數據集有X個數據點,那么我們就有了X個聚類。然后我們選擇一個度量兩個聚類之間距離的距離度量。作為一個示例,我們將使用平均連接(average linkage)聚類,它定義了兩個聚類之間的距離,即第一個聚類中的數據點和第二個聚類中的數據點之間的平均距離。
2.在每次迭代中,我們將兩個聚類合並為一個。將兩個聚類合並為具有最小平均連接的組。比如說根據我們選擇的距離度量,這兩個聚類之間的距離最小,因此是最相似的,應該組合在一起。
3.重復步驟2直到我們到達樹的根。我們只有一個包含所有數據點的聚類。通過這種方式,我們可以選擇最終需要多少個聚類,只需選擇何時停止合並聚類,也就是我們停止建造這棵樹的時候!
層次聚類算法不要求我們指定聚類的數量,我們甚至可以選擇哪個聚類看起來最好。此外,該算法對距離度量的選擇不敏感;它們的工作方式都很好,而對於其他聚類算法,距離度量的選擇是至關重要的。層次聚類方法的一個特別好的用例是,當底層數據具有層次結構時,你可以恢復層次結構;而其他的聚類算法無法做到這一點。層次聚類的優點是以低效率為代價的,因為它具有O(n³)的時間復雜度,與K-Means和高斯混合模型的線性復雜度不同。
層次聚類方法對給定的數據集進行層次的分解,直到某種條件滿足或者達到最大迭代次數。具體又可分為:
凝聚的層次聚類(AGNES算法):一種自底向上的策略,首先將每個對象作為一個簇,然后合並這些原子簇為越來越大的簇(一般是計算所有簇的中心之間的距離,選取距離最小的兩個簇合並),直到某個終結條件被滿足或者達到最大迭代次數。
分裂的層次聚類(DIANA算法):采用自頂向下的策略,它首先將所有對象置於一個簇中,然后逐漸細分為越來越小的簇(一般是每次迭代分裂一個簇為兩個),直到達到了某個終結條件或者達到最大迭代次數。
import sys,os
import numpy as np
class Hierarchical:
def __init__(self,center,left=None,right=None,flag=None,distance=0.0):
self.center = center
self.left = left
self.right = right
self.flag = flag
self.distance = distance
def traverse(node):
if node.left==None and node.right==None:
return [node.center]
else:
return traverse(node.left)+traverse(node.right)
def distance(v1,v2):
if len(v1)!=len(v2):
print("出現錯誤了")
distance = 0
for i in range(len(v1)):
distance+=(v1[i]-v2[i])**2
distance = np.sqrt(distance)
return distance
def hcluster(data,n):
if len(data)<=0:
print('invalid data')
clusters = [Hierarchical(data[i],flag=i) for i in range(len(data))]
#print(clusters)
distances = {}
min_id1 = None
min_id2 = None
currentCluster = -1
while len(clusters)>n:
minDist = 100000000000000
for i in range(len(clusters)-1):
for j in range(i+1,len(clusters)):
#print(distances.get((clusters[i], clusters[j])))
if distances.get((clusters[i],clusters[j]))==None:
distances[(clusters[i].flag,clusters[j].flag)]=distance(clusters[i].center,clusters[j].center)
if distances[(clusters[i].flag,clusters[j].flag)]<= minDist:
min_id1 = i
min_id2 = j
minDist = distances[(clusters[i].flag,clusters[j].flag)]
if min_id1!=None and min_id2!=None and minDist!=1000000000:
newCenter = [(clusters[min_id1].center[i]+clusters[min_id2].center[i])/2 for i in range(len(clusters[min_id2].center))]
newFlag = currentCluster
currentCluster -= 1
newCluster = Hierarchical(newCenter,clusters[min_id1],clusters[min_id2],newFlag,minDist)
del clusters[min_id2]
del clusters[min_id1]
clusters.append(newCluster)
finalCluster = [traverse(clusters[i]) for i in range(len(clusters))]
return finalCluster
if __name__ == '__main__':
data = [[123,321,434,4325,345345],[23124,141241,434234,9837489,34743],\
[128937,127,12381,424,8945],[322,4348,5040,8189,2348],\
[51249,42190,2713,2319,4328],[13957,1871829,8712847,34589,30945],
[1234,45094,23409,13495,348052],[49853,3847,4728,4059,5389]]
#print(len(data))
finalCluster = hcluster(data,3)
print(finalCluster)
#print(len(finalCluster[0]))
在聚類過程中,基於相關系數的距離是區分價格指數序列的主要指標,而 Manhattan距離則用於處理相關系數無法區分的部分。分裂的層次聚類方法優於凝聚的層次聚類方法,因為凝聚的方法無法判斷使用何種距離度量,而分裂的方法則依次使用基於相關系數的距離和 Manhattan距離
【褚洪洋,柴躍廷,劉義.基於層次分裂算法的價格指數序列聚類[J].清華大學學報(自然科學版),2015,55(11):1178-1183.】大家可以參考這個文獻對比層次聚類中的這兩算法。論文的主要內容。
(該文提出一種基於層次分裂算法的價格指數序列聚類方法,選擇基於相關系數的距離和 Manhattan距 離 作 為 距 離 度 量,分 兩 步 對 價格指數序列進行聚類。算法通過設置不同的終止條件停止分裂,不需要事先設置簇數。
本文提出一種價格指數序列的層次分裂算法,使用基於相關系數的距 離和 Manhattan 距離作為距離度量,分兩步對價格指數序列進行聚類。在編制網購價格指數時,
算法能夠有效划分價格指數序列,找到同類商品的基本價格指數,為分類價格指數的編制提供依據。與其他時序數據的聚類算法相比,本算法有如下特點:
1)不直接采用相關系數作為距離度量,而是采用剔除整體趨勢后的相關系數,能夠更准確地反映價格指數序列間的關系;
2)結合價格指數序列的實際情況,分別采用兩種不同的距離度量對價格指 數序列進行分裂聚類,有效地對價格指數序列進行划分;
3)不需要事先設置簇數,通過分裂終止條件控制簇分裂的停止,算法根據價格指數序列數據和終止條件參數自動獲取簇數。)
三、基於密度的聚類方法
基於密度的聚類算法從樣本密度的角度考察樣本之間的可連接性,並基於可連接樣本不斷擴展聚類簇得到最終結果。
DBSCAN:具有噪聲的基於聚類方法,能夠處理非球狀結構數據


大致思想如下:
初始化核心對象集合T為空,遍歷一遍樣本集D中所有的樣本,計算每個樣本點的ε-鄰域中包含樣本的個數,如果個數大於等於MinPts,則將該樣本點加入到核心對象集合中。初始化聚類簇數k = 0, 初始化未訪問樣本集和為P = D。
當T集合中存在樣本時執行如下步驟:
2.1記錄當前未訪問集合P_old = P
2.2從T中隨機選一個核心對象o,初始化一個隊列Q = [o]
2.3P = P-o(從T中刪除o)
2.4當Q中存在樣本時執行:
2.4.1取出隊列中的首個樣本q
2.4.2計算q的ε-鄰域中包含樣本的個數,如果大於等於MinPts,則令S為q的ε-鄰域與P的交集,
Q = Q+S, P = P-S
2.5 k = k + 1,生成聚類簇為Ck = P_old - P
2.6 T = T - Ck
划分為C= {C1, C2, ……, Ck}

import numpy as np
import pylab as pl
data = '''
1,0.697,0.46,2,0.774,0.376,3,0.634,0.264,4,0.608,0.318,5,0.556,0.215,
6,0.403,0.237,7,0.481,0.149,8,0.437,0.211,9,0.666,0.091,10,0.243,0.267,
11,0.245,0.057,12,0.343,0.099,13,0.639,0.161,14,0.657,0.198,15,0.36,0.37,
16,0.593,0.042,17,0.719,0.103,18,0.359,0.188,19,0.339,0.241,20,0.282,0.257,
21,0.748,0.232,22,0.714,0.346,23,0.483,0.312,24,0.478,0.437,25,0.525,0.369,
26,0.751,0.489,27,0.532,0.472,28,0.473,0.376,29,0.725,0.445,30,0.446,0.459'''
a = data.split(',')
dataset = [(float(a[i]),float(a[i+1])) for i in range(1,len(a)-1,3)]
#計算歐幾里得距離,a,b分別為兩個元組
def dist(a,b):
return np.sqrt(pow((a[0]-b[0]),2))+np.sqrt(pow((a[1]-b[1]),2))
def DBSCAN(D,e,Minpts):# 初始化核心對象集合T,聚類個數k,聚類集合C, 未訪問集合P
T =set();k=0;C = [];P = set(D)
for d in D:
if len([i for i in D if dist(d,i)<=e])>= Minpts:
T.add(d)
#開始聚類
while len(T):
P_old = P
o = list(T)[np.random.randint(0,len(T))]
P = P - set(o)
Q = [];Q.append(o)
while len(Q):
q = Q[0]
Nq = [i for i in D if dist(q,i)<=e]
if len(Nq)>=Minpts:
S = P&set(Nq)
Q += (list(S))
P = P - S
Q.remove(q)
k += 1
Ck = list(P_old-P)
T = T - set(Ck)
C.append(Ck)
return C
#畫圖
def draw(C):
colValue = ['r', 'y', 'g', 'b', 'c', 'k', 'm']
for i in range(len(C)):
coo_X = [] #x坐標列表
coo_Y = [] #y坐標列表
for j in range(len(C[i])):
coo_X.append(C[i][j][0])
coo_Y.append(C[i][j][1])
pl.scatter(coo_X, coo_Y, marker='x', color=colValue[i%len(colValue)], label=i)
pl.legend(loc='upper right')
pl.show()
C = DBSCAN(dataset, 0.11, 5)
draw(C)
運行結果如下:

