之前寫的入門級介紹,有點久遠有些ref找不着了
文章目錄
簡介
- 根據在數據中發現的描述對象及其關係的信息,將數據對象分組。
- 對大量未知標註的數據集,按數據的內在相似性將數據集劃分爲多個類別,使類別內的數據相似度極大而類別間的數據相似度極小。
目標
- 組內的對象相互之間是相似的,不同組中的對象是不同的。
作用
-
易於理解
🌰生物學:界門綱目科屬種
🌰信息檢索:搜索引擎返回結果分類
🌰商業:對客戶進行分類
-
更加實用
🌰降維:樣本數的降維、特徵數的降維
🌰發現最近鄰:減少計算量
類型
分類 | 解釋 |
---|---|
層次的 劃分的 |
層次的: 嵌套的,樹狀,可在不同層次上得到不同數目的類別。 劃分的: 非嵌套的。每個樣本恰在一個子集中。 |
互斥的 重疊的 模糊的 |
互斥的: 每個樣本僅在一個類中。 重疊的: 一個樣本可同時屬於多個類。 模糊的: 每個樣本以0(絕對不屬於)和1(絕對屬於)之間的權值屬於每個類。 |
完全的 部分的 |
完全的: 每個樣本至少屬於一個類。 部分的: 部分樣本不屬於任何一個類,如噪聲、離羣點等。 |
聚類vs分類
import random
import matplotlib.pyplot as plt
import pandas as pd
from sklearn import datasets
from pandas import DataFrame
# 造數據
# 圈圈
noisy_circles=datasets.make_circles(n_samples=1000,factor=.5,noise=.05)
circle = DataFrame()
circle['x1'] = noisy_circles[0][:,0]
circle['x2'] = noisy_circles[0][:,1]
circle['label'] = noisy_circles[1]
# 堆堆
def random_sample(num, mean, st):
'''
num:個數
mean:均值
st:標準差
'''
sample = []
i = 0
while i < num:
sample.append(random.gauss(mean, st))
i += 1
return sample
num = 300
sample_0 = pd.DataFrame({'x1':random_sample(num, 4, 0.5), 'x2': random_sample(num, 4, 0.5), 'y': [0 for i in range(num)]})
sample_1 = pd.DataFrame({'x1':random_sample(num, 2, 0.5), 'x2': random_sample(num, 2, 0.5), 'y': [1 for i in range(num)]})
sample = pd.concat([sample_0, sample_1])
# 聚類vs分類
plt.figure(figsize=(16,8))
plt.subplot(1,2,1)
plt.scatter(sample_0['x1'], sample_0['x2'], marker = 'o', c = 'k')
plt.scatter(sample_1['x1'], sample_1['x2'], marker = 'o', c = 'k')
plt.title('聚類(無監督)', fontsize =25)
plt.subplot(1,2,2)
plt.scatter(sample_0['x1'], sample_0['x2'], marker = 'o', c = 'b')
plt.scatter(sample_1['x1'], sample_1['x2'], marker = 'o', c = 'r')
plt.title('分類(有監督)', fontsize =25)
plt.show()
K-means
[K-means] 僞代碼
#####################################
- 選擇個點作爲初始質心。
- repeat
- 將每個點指派到最近的質心,形成個簇。
- 重新計算每個簇的質心。
- until 質心不發生變化。
#####################################
[K-means] 過程詳解
ref: GitHub: snippet-code
# 修改
data = sample
repeat = 20
# ================開始================
# 導入數據
points = [[data.iloc[i].x1, data.iloc[i].x2] for i in range(len(data))]
# 初始質心
currentCenter1 = [data.x1.min() + (data.x1.max()-data.x1.min())*random.random(), data.x1.min() + (data.x2.max()-data.x2.min())*random.random()]
currentCenter2 = [data.x1.min() + (data.x1.max()-data.x1.min())*random.random(), data.x1.min() + (data.x2.max()-data.x2.min())*random.random()]
# 記錄每次迭代後每個簇的質心的更新軌跡
center1 = [currentCenter1]; center2 = [currentCenter2]
# 丟點圖
plt.figure(figsize=(18,18))
ax = plt.subplot(3,3, 1)
change_ax(ax)
plt.title('step 0', fontsize = 15)
plt.plot([eachpoint[0] for eachpoint in points], [eachpoint[1] for eachpoint in points], '.', color='grey')
plt.scatter([eachcenter[0] for eachcenter in center1], [eachcenter[1] for eachcenter in center1], marker='x', c='b', s=100)
plt.scatter([eachcenter[0] for eachcenter in center2], [eachcenter[1] for eachcenter in center2], marker='x', c='g', s=100)
# 兩個簇
group1 = []; group2 = []
for runtime in range(repeat):
group1 = []; group2 = []
for eachpoint in points:
# 計算每個點到質心的距離
distance1 = pow(abs(eachpoint[0]-currentCenter1[0]),2) + pow(abs(eachpoint[1]-currentCenter1[1]),2)
distance2 = pow(abs(eachpoint[0]-currentCenter2[0]),2) + pow(abs(eachpoint[1]-currentCenter2[1]),2)
# 將該點指派到離它最近的質心所在的簇
mindis = min(distance1,distance2)
if(mindis == distance1):
group1.append(eachpoint)
else:
group2.append(eachpoint)
if runtime < 5 or runtime > (repeat - 4):
# 只可視化前五次最後三次
if runtime < 5:
ax = plt.subplot(3,3, runtime+2)
change_ax(ax)
plt.title('step %d'%(runtime+1), fontsize = 15)
else:
ax = plt.subplot(3,3, 10-repeat+runtime)
change_ax(ax)
plt.title('step %d'%(runtime+1), fontsize = 15)
# 打印所有的點,用顏色標識該點所屬的簇
plt.plot([eachpoint[0] for eachpoint in group1], [eachpoint[1] for eachpoint in group1], '.', color='lightsteelblue')#, linewidth=1)
plt.plot([eachpoint[0] for eachpoint in group2], [eachpoint[1] for eachpoint in group2], '.', color='lightgreen')#, linewidth=0.3)
# 畫質心
plt.plot([eachcenter[0] for eachcenter in center1], [eachcenter[1] for eachcenter in center1],'bx')
plt.plot([eachcenter[0] for eachcenter in center1], [eachcenter[1] for eachcenter in center1], 'b--', linewidth=0.5)
plt.plot([eachcenter[0] for eachcenter in center2], [eachcenter[1] for eachcenter in center2],'gx')
plt.plot([eachcenter[0] for eachcenter in center2], [eachcenter[1] for eachcenter in center2], 'g--', linewidth=0.5)
# 指派完所有的點後,更新每個簇的質心
currentCenter1 = [sum([eachpoint[0] for eachpoint in group1])/len(group1),sum([eachpoint[1] for eachpoint in group1])/len(group1)]
currentCenter2 = [sum([eachpoint[0] for eachpoint in group2])/len(group2),sum([eachpoint[1] for eachpoint in group2])/len(group2)]
# 記錄該次對質心的更新
center1.append(currentCenter1)
center2.append(currentCenter2)
plt.show()
[K-means] 初始點的選擇
-
隨機選取
-
在選取第一個聚類中心時同樣通過隨機的方法。
假設已經選取了個初始聚類中心,則在選取第個聚類中心時:距離當前個聚類中心越遠的點會有更高的概率被選爲第個聚類中心。
-
結合其他算法
選用層次聚類或算法進行初始聚類,然後從個類別中分別隨機選取個點,來作爲的初始聚類中心點。
[K-means] 距離的度量
-
曼哈頓距離
-
歐幾里得距離
-
餘弦相似度
其中,""表示向量點積,是向量x的長度。 -
散度
其中,是一個嚴格的凸函數,是在上計算的的梯度,是與的向量差,而是兩者的內積。
[K-means] 質心的選擇
鄰近度函數 | 質心 |
---|---|
曼哈頓距離 | 中位數 |
平方歐幾里得距離 | 均值 |
餘弦 | 均值 |
散度 | 均值 |
[K-means] 參數確定
K值確定
-
根據實際情況確定
🌰設計新款時確定潛在客戶(新款數已知)
-
肘部法則
肘部法則會把不同值的成本函數值畫出來。隨着值的增大,平均畸變程度會減小;每個類包含的樣本數會減少,於是樣本離其質心會更近。
但是,隨着值繼續增大,平均畸變程度的改善效果會不斷減低。k值增大過程中,畸變程度的改善效果下降幅度最大的位置對應的值就是肘部。
from sklearn.cluster import KMeans
cri = pd.DataFrame(columns=['k', 'j'])
for i in range(1,11):
km = KMeans(n_clusters=i)
km.fit(sample[['x1', 'x2']])
cri.loc[i] = [i, km.inertia_]
plt.figure(figsize=(13,7))
plt.plot(cri['k'], cri['j'])
plt.title("肘部法則", fontsize = 25)
plt.xlabel('k(聚類個數)', fontsize =15)
plt.ylabel('損失函數', fontsize = 15)
plt.show()
-
與層次聚類結合
首先採用層次凝聚算法決定結果粗的數目,並找到一個初始聚類,然後用迭代重定位來改進該聚類。
-
穩定性方法、系統演化方法、準則法、輪廓係數法等(參考從零開始實現Kmeans聚類算法、kmeans算法原理以及實踐操作(多種k值確定以及如何選取初始點方法))
[K-means] 優缺點
優點:
- 算法快速、簡單
- 容易解釋
- 適用於高維
缺點:
- 對離羣點敏感,對噪聲點和孤立點很敏感
- 需要自定義
- 不同的初始點可能得到的結果完全不同
- 不適用於非凸的簇或大小差別很大的簇
AHC
(凝聚層次聚類):
[AHC] 層次聚類類型
產生層次聚類的基本方法
凝聚的: 從點作爲個體簇開始,每一步合併兩個最接近的簇。
分裂的: 從包含所有點的某個簇開始,每一步分裂一個簇
[AHC] 僞代碼
#####################################
- 如果需要,計算鄰近度矩陣。
- repeat
- 合併最接近的兩個簇。
- 更新鄰近性矩陣,以反映新的簇與原來的簇之間的鄰近性。
- until 僅剩下一個簇。
#####################################
[AHC] 過程詳解
歐式距離矩陣(組平均):
num = 4
dist = pd.DataFrame(squareform(sample))
plt.figure(figsize=(8,5))
row_clusters = linkage(sample, method='average')
row_dendr = dendrogram(row_clusters,labels=list(range(num)))
plt.annotate('A', (9.5,0.6), fontsize=30, color='tomato')
plt.annotate('B', (30,3.6), fontsize=30, color='tomato')
plt.annotate('C', (19,4.35), fontsize=30, color='tomato')
plt.ylabel('距離', fontsize=20)
plt.xlabel('樣本', fontsize=20)
plt.tight_layout()
[AHC] 鄰近度度量
- MIN(單鏈): 兩個簇的兩個最近的點之間的距離。
- MAX(全鏈): 兩個簇的兩個最遠的點之間的距離。
- 組平均: 兩個簇的所有點對鄰近度的平均值。
4. Ward法: 兩個簇合並時導致的平方誤差的增量。
5. 質心法: 兩個簇質心之間的距離。
[AHC] 優缺點
優點:
- 不用提前確定聚類個數(想要分多少個簇都可以直接根據樹結構來得到結果)
缺點:
- 計算量和存儲量大
- 不可撤銷(簇之間不能交換對象)
DBSCAN
[DBSCAN] 僞代碼1
[DBSCAN] 過程詳解
……
[DBSCAN] 三個點
核心點: 以該點爲圓心,半徑內有大於等於個樣本。
邊界點: 以該點爲圓心,半徑內僅有小於個樣本,但在覈心點的鄰域中。
噪聲點: 以該點爲圓心,半徑內僅有小於個樣本,且不在覈心點的鄰域中。
[DBSCAN] 僞代碼2
#####################################
- 將所有點標記爲核心點、邊界點或噪聲點。
- 刪除噪聲點。
- 爲距離在之內的所有核心點之間賦予一條邊。
- 每組連通的核心點形成一個簇。
- 將每個邊界點指派到一個與之關聯的核心點的簇中。
#####################################
[DBSCAN] 參數確定
-
Eps:
a. 試…
from sklearn import datasets
from sklearn.cluster import DBSCAN
def lin_dbscan(sample, eps, minpts):
'''
單個DBSCAN結果畫圖
'''
dbscan = DBSCAN(eps = eps, min_samples = minpts)
sample['dbscan'] = dbscan.fit_predict(sample[['x1', 'x2']])
a = pd.DataFrame(sample.dbscan.value_counts())
clusters = a.index
try:
clusters = clusters.drop([-1])
noise = sample[sample['dbscan'] == -1]
ns = plt.scatter(noise['x1'], noise['x2'], marker='x')
plt.legend([ns], ['噪聲點'], fontsize = 13, loc='lower right')
except:
#print('eps=%g, MinPts=%d 沒有噪聲點'%(dbscan.eps, dbscan.min_samples))
pass
for i in clusters:
temp = sample[sample['dbscan'] == i]
plt.scatter(temp['x1'], temp['x2'])
plt.title('Eps=%g, MinPts=%d \n 分爲%d類'%(dbscan.eps, dbscan.min_samples, len(clusters)), fontsize =20)
param = {1: [0.1, 10],
2: [0.1, 20],
3: [0.1, 30],
4: [0.2, 25],
5: [0.2, 32],
6: [0.2, 35]}
plt.figure(figsize=(16,10))
plt.subplots_adjust(hspace = 0.3)
for i in param.keys():
plt.subplot(2,3,i)
eps = param.get(i)[0]
minpts = param.get(i)[1]
lin_dbscan(circle, eps, minpts)
b. -距離曲線
給定鄰域參數,對於數據中的每個點,計算對應的第個最近鄰域距離,並將數據集所有點對應的最近鄰域距離按照降序方式排序,稱這幅圖爲排序的距離圖,選擇該圖中第一個谷值點位置對應的k距離值設定爲。
import numpy as np
def cal_dist(a,b):
'''
計算兩個變量之間的距離
'''
c = a-b
dist = np.sqrt(np.dot(c, c.transpose()))
return dist
def single_sample_dist(sample, sample_no, k):
'''
計算單個變量的第k近鄰
'''
k_sample = []
a = np.array([sample.iloc[sample_no].x1, sample.iloc[sample_no].x2])
for i in range(len(sample)):
b = np.array([sample.iloc[i].x1, sample.iloc[i].x2])
k_sample.append(cal_dist(a,b))
k_sample.sort()
if np.mod(sample_no, 100) == 0:
print("第%d個變量距離計算完畢!"%(sample_no))
return k_sample[k]
def sample_dist(sample, k):
'''
計算所有變量的第k近鄰
'''
dists = []
for i in range(len(sample)):
dists.append(single_sample_dist(sample, i, k))
return dists
def plot_dist(sample, k):
'''
畫出k近鄰的距離圖
'''
dists = sample_dist(sample, k)
dists.sort()
plt.figure(figsize=(15,8))
plt.plot(dists)
plt.title('%dth近鄰距離圖'%(k), fontsize = 25)
plt.savefig('./pic/%dth近鄰距離圖.jpg'%(k))
plt.show()
return dists
dists = plot_dist(circle, 10)
2. MinPts:MinPts≥dim+1
表示待聚類數據的維度
[DBSCAN] 優缺點
優點:
- 無需指定
- 可以發現任何形狀的簇
- 不受噪聲的影響,且可以找出數據中的噪聲
缺點:
- 不同參數聚類效果差異很大,參數確定比較困難
- 對密度不均勻、簇間距離相差很大的數據集,聚類效果比較差
不同數據集效果對比
Source: Comparing different clustering algorithms on toy datasets
其他知識點
- 分類效果的評價指標
- 應用場景
- 各算法的時間、空間複雜性
- 算法改進