聚類
- 聚類就是對大量未知標注的數據集,按數據 的內在相似性將數據集劃分為多個類別,使 類別內的數據相似度較大而類別間的數據相 似度較小
- 無監督
如何計算相似度/距離
-
閔可夫斯基距離Minkowski/歐式距離 (針對坐標點):
-
杰卡德相似系數(Jaccard)(針對集合):
-
余弦相似度(cosine similarity)(針對向量):
-
Pearson相似系數
-
相對熵(K-L距離)(K-L距離一般不對稱):
-
Hellinger距離
余弦相似度與Pearson相關系數的區別:
-
n維向量x和y的夾角記做θ,根據余弦定理,其余弦值為:
-
這兩個向量的相關系數是:
相關系數即將x、y坐標向量各自平移到原點后的夾角余弦
- 這即解釋了為何文檔間求距離使用夾角余弦——因為這一物理
量表征了文檔去均值化后的隨機向量間相關系數。
聚類的基本思想:
給定一個有N個對象的數據集,構造數據的k 個簇,k≤n。滿足下列條件:
- 每一個簇至少包含一個對象
- 每一個對象屬于且僅屬于一個簇
- 將滿足上述條件的k個簇稱作一個合理劃分
基本思想:對于給定的類別數目k,首先給 出初始劃分,通過迭代改變樣本和簇的隸屬 關系,使得每一次改進之后的劃分方案都較 前一次好。
K-means算法:
輸入:k, data[n];
- (1) 選擇k個初始中心點,例如c[0]=data[0],…c[k-1]=data[k-1];
- (2) 對于data[0]….data[n], 分別與c[0]…c[k-1]比較,假定與c[i]差值最少,就標記為i;
- (3) 對于所有標記為i點,重新計算c[i]={ 所有標記為i的data[j]之和}/標記為i的個數;
- (4) 重復(2)(3),直到所有c[i]值的變化小于給定閾值。
K-means是對初值敏感的
簇近似為高斯分布的的時候,效果較好
迭代終止條件:
- 迭代次數
- 簇中心變化率
- 最小平方差
import numpy as np
def kmeans(X, k, maxIt):
'''
:param X: 數據集,數據集的最后一列表示標簽值(或者組號)
:param k: k個分類
:param maxIt: 循環幾次
:return:
'''
numPoints, numDim = X.shape
dataSet = np.zeros((numPoints, numDim + 1))
dataSet[:, :-1] = X
# Initialize centroids randomly
centroids = dataSet[np.random.randint(numPoints, size = k), :]
centroids = dataSet[0:2, :]
#Randomly assign labels to initial centorid
centroids[:, -1] = range(1, k +1)
# Initialize book keeping vars.
iterations = 0
oldCentroids = None
# Run the main k-means algorithm
while not shouldStop(oldCentroids, centroids, iterations, maxIt):
print("iteration: \n", iterations)
print ("dataSet: \n", dataSet)
print ("centroids: \n", centroids)
# Save old centroids for convergence test. Book keeping.
oldCentroids = np.copy(centroids)
iterations += 1
# Assign labels to each datapoint based on centroids
updateLabels(dataSet, centroids)
# Assign centroids based on datapoint labels
centroids = getCentroids(dataSet, k)
# We can get the labels too by calling getLabels(dataSet, centroids)
return dataSet
def shouldStop(oldCentroids,centroids,iterations,maxIt):
if iterations > maxIt:
return True
return np.array_equal(oldCentroids,centroids)
def updateLabels(dataset,centroids):
numPoints,numDim = dataset.shape
#算每一行的點離哪個中心點最近
for i in range(0,numPoints):
dataset[i,-1] = getLabelFromClosestCentroid(dataset[i,:-1],centroids)
def getLabelFromClosestCentroid(dataRow,centroids):
label = centroids[0,-1]
#numpy.linalg.norm傳入任意兩個向量,-為距離
minDist = np.linalg.norm(dataRow-centroids[0,:-1])
#找最小距離
for i in range(1,centroids.shape[0]):
dist = np.linalg.norm(dataRow-centroids[i,:-1])
if dist < minDist:
minDist = dist
label = centroids[i,-1]
print("minDist:"+str(minDist))
return label
def getCentroids(dataSet,k):
result = np.zeros((k,dataSet.shape[1]))
for i in range(1,k+1):
#所有求 標簽值為i的值
oneCluster = dataSet[dataSet[:,-1]==i,:-1]
#axis =0 對行求平均值,axis=1 對列求平均值
result[i-1,:-1]=np.mean(oneCluster,axis=0)
#最后賦值標簽
result[i-1,-1]=i
return result
x1 = np.array([1, 1])
x2 = np.array([2, 1])
x3 = np.array([4, 3])
x4 = np.array([5, 4])
testX = np.vstack((x1, x2, x3, x4))
result = kmeans(testX,2,10)
print("final result:\n"+str(result))
輪廓系數(Silhouette)
Silhouette系數是對聚類結果有效性的解釋跟驗證
計算樣本i到同簇其他樣本的平均距離為ai.ai越小,說明樣本i越應該被聚類到該簇.稱ai為樣本i的簇內不相似度
計算樣本i到其他某簇C1的所有樣本的平均距離bil,稱為樣本i到簇c1的不相似度.bi越大,說明樣本i越不屬于其他簇
輪廓系數si:
$$S(i)=b(i)-a(i)/max{a(i),b(i)}$$
si接近1,說明樣本i聚類合理,si接近-1,則說明樣本i更應該分類到其他簇;若si 近似于0,說明樣本i在兩個簇的邊界.
所有樣本的si的均值為聚類結果的輪廓系數
密度聚類
DBSCAN算法
若干概念:
對象的ε-領域,給定對象在半徑ε內的區域
核心對象:對于給定的數目m,如果一個對象的ε-領域至少包含m個對象,則稱該對象為核心對象.
ε-領域內,而q是一個核心對象,我們說對象p從對象q出發是直接密度可達的.
密度可達:如果存在一個對象煉p1p2p3pn,如果p1=q,pn=p,則pi+1是從pi 關于ε 和m直接密度可達,
密度相連:如果對象集合D中存在一個對象o,使得對象p和q 是從o關于ε和m密度可達的,那么對象p和q是關于ε和m密 度相連的。
簇:一個基于密度的簇是最大的密度相連對象的集合。
噪聲:不包含在任何簇中的對象稱為噪聲。
DBSCAN算法過程:
* 如果一個點p的ε-鄰域包含多于m個對象,則創建一個p 作為核心對象的新簇;
* 尋找并合并核心對象直接密度可達的對象;
* 沒有新點可以更新簇時,算法結束。
密度最大值聚類
局部密度:
dc是一個截斷距離, ρi即到對象i的距離小于dc的對象的個 數。由于該算法只對ρi的相對值敏感, 所以對dc的選擇是 穩健的,一種推薦做法是選擇dc,使得平均每個點的鄰 居數為所有點的1%-2%
高局部密度點距離:
簡單的,就是:在密度高于對象i的所有對象中,到對象i最近 的距離,即高局部密度點距離.
#####簇中心的識別:
那些有著比較大的局部密度ρi和很大的高密 距離δi的點被認為是簇的中心;
高密距離δi較大但局部密度ρi較小的點是 異常點;
####密度最大值的分類過程:
###譜與譜矩陣
方陣作為線性算子,它所有的特征值的全體統 成為方陣的譜.
* 方陣的譜半徑為最大的特征值
* 矩陣A的譜半徑:(ATA)的最大特征值
#####譜分析過程:
給定一組數據x1,x2,...xn,記任意兩個點之間 的相似度(“距離”的減函數)為sij=<xi,xj>,形 成相似度圖(similarity graph):G=(V,E) 。如 果xi和xj之間的相似度sij大于一定的閾值,那 么,兩個點是連接的,權值記做sij。
相似度圖G的建立:
* 全連接圖:
高斯相似度函數:距離越大,相似度越小
* ε近鄰圖
小于ε的邊裁掉
ε的選定:
圖G的權值的均值
圖G的最小生成樹的最大邊
* k近鄰圖(k-nearest neighbor graph)
直接選K個最近的
###拉普拉斯矩陣:
計算點之間的鄰接相似度矩陣W
若兩個點的相似度值越大,表示這兩個點越相似;
同時,定義wij=0表示vi,vj兩個點沒有任何相似性(無窮遠)
W的第i行元素的和為vi的度,形成了頂點度對角陣D
dii表示第i個點的度
除主對角線元素,D其他位置為0
未正則的拉普拉斯矩陣:$$L=D-W$$
隨機游走拉普拉斯矩陣:
未正則拉普拉斯矩陣譜聚類算法:
輸入:n個點{pi},簇的數目k
計算n×n的相似度矩陣W和度矩陣D;
計算拉普拉斯矩陣L=D-W;
計算L的前k個特征向量u1,u2,...,uk(從小到大);特征值的意義:降維處理
將k個列向量u1,u2,...,uk組成矩陣U,U∈Rn×k;
對于i=1,2,...,n,令yi∈Rk是U的第i行的向量;
使用k-means算法將點(yi)i=1,2,...,n聚類成簇 C1,C2,...Ck;
輸出簇A1,A2,...Ak,其中,Ai={j|yj∈Ci}
hierarchical clustering 層次聚類
假如有N個待聚類的樣本,步驟:
- (初始化)把每個樣本歸為一類,計算類之間的距離,(樣本之間的相似度)
- 尋找各個類之間最近的兩個類,把它們歸為一類
- 重新計算新生產的這個類與各個舊類之間的相識度;
- 重復2,3,直到所有的樣本點都歸為一類.
距離的選擇:
- SingleLinkage(nearest-neighbor)兩個類中距離最近的兩個點的距離作為兩個類的距離.
- CompleteLinkage 正好相反.兩個集合中距離最遠的兩個點的距離作為集合距離
- AverageLinkage 兩個集合中點兩兩距離全部放在一起求均值
代碼:
import numpy as np
class cluster_node(object):
def __init__(self,vec,left = None,right = None,distance = 0.0,id = None,count =1):
self.left = left
self.right = right
self.vec = vec
self.id = id
self.distance = distance
self.count = count
def L2dist(v1,v2):
return np.sqrt(np.sum((v1-v2)**2))
def L1dist(v1,v2):
return np.sum(np.abs(v1-v2))
def hcluster(features,distance = L2dist):
distances = {}
currentclustid = 1
clust = [cluster_node(np.array(features[i]),id=i) for i in range(len(features))]
##聚類過程
while len(clust) > 1:
lowestpair = (0,1)
closest = distance(clust[0].vec,clust[1].vec)
for i in range(len(clust)):
for j in range(i+1,len(clust)):
if (clust[i].id,clust[j].id) not in distances:
distances[(clust[i].id,clust[j].id)] = distance(clust[i].vec,clust[j].vec)
##找最短距離
d = distances[(clust[i].id,clust[j].id)]
if d < closest:
closest = d
lowestpair = (i,j)
##兩個類的合并
mergevec = [(clust[lowestpair[0]].vec[i] + clust[lowestpair[1]].vec[i]) /2.0 \
for i in range(len(clust[0].vec))]
newcluster = cluster_node(np.array(mergevec),left=clust[lowestpair[0]],
right=clust[lowestpair[1]],distance=closest,id=currentclustid)
currentclustid = -1
del clust[lowestpair[0]]
del clust[lowestpair[1]]
clust.append(newcluster)
return clust[0]
def extract_clusters(clust,dist):
clusters = {}
if clust.distance < dist:
return [clust]
if clust.left != None:
cl = extract_clusters(clust.left,dist = dist)
if clust.right != None:
cr =extract_clusters(clust.right,dist = dist)
def get_cluster_elements(clust):
# return ids for elements in a cluster sub-tree
if clust.id>=0:
# positive id means that this is a leaf
return [clust.id]
else:
# check the right and left branches
cl = []
cr = []
if clust.left!=None:
cl = get_cluster_elements(clust.left)
if clust.right!=None:
cr = get_cluster_elements(clust.right)
return cl+cr
def printclust(clust, labels=None, n=0):
# indent to make a hierarchy layout
for i in range(n): print
(' '),
if clust.id < 0:
# negative id means that this is branch
print
('-')
else:
# positive id means that this is an endpoint
if labels == None:
print (clust.id)
else:
print(labels[clust.id])
# now print the right and left branches
if clust.left != None: printclust(clust.left, labels=labels, n=n + 1)
if clust.right != None: printclust(clust.right, labels=labels, n=n + 1)
def getheight(clust):
# Is this an endpoint? Then the height is just 1
if clust.left == None and clust.right == None: return 1
# Otherwise the height is the same of the heights of
# each branch
return getheight(clust.left) + getheight(clust.right)
def getdepth(clust):
# The distance of an endpoint is 0.0
if clust.left == None and clust.right == None: return 0
# The distance of a branch is the greater of its two sides
# plus its own distance
return max(getdepth(clust.left), getdepth(clust.right)) + clust.distance