Q:分類和聚類有什么不同?
兩者的區別可以主要從兩個方面看出。第一,它們面對的根本問題不同。分類的根本問題是判斷給定的某一個樣本屬于那一個類別;聚類的根本問題是探索給定的數據集可以分成哪幾種類別。第二,兩者使用的訓練數據集有差異。分類任務使用的訓練數據集中每個樣本除了有屬性數據外,還必須有一個標記值,用以表示該樣本屬于哪一類;聚類任務的數據集中每個樣本可以只有屬性值,沒有標記值(當然也可以有)。這也可以認為是我們常說的,監督學習與無監督學習。
Q:聚類的一般方法有哪些?
聚類算法多種多樣,但是萬變不離其宗,最基本的思想還是先提出一中衡量樣本之間相似程度的的手段,如距離、相關系數等,然后逐一計算數據集中各樣本之間的相似度,盡量把相似度高的樣本放到同一類。
一般來說聚類算法有三類:基于原型的聚類、基于密度的聚類、和基于層次的聚類。當然,還可以有更多,比如混合高斯模型也能用于聚類任務。
Q:衡量樣本間相似度的指標有那些?
所謂樣本,其數學本質就是向量。所以樣本間的相似度常用距離來表示。
最簡單的距離就是內積距離
最直觀的距離則是歐氏距離
更一般的距離有明可夫斯基距離,其中p值視情況而定
上面提到的距離都是適用于有序屬性的。所謂有序屬性,就是指屬性的取值有大小之分。比如西瓜的“含糖率”這個屬性,0.1就比0.9要小。無序屬性這位是指屬性取值無大小之分的屬性。比如西瓜的“顏色”屬性,取“0”表示淺綠、取“1”表示深綠,但不能說0比1小,“顏色”屬性的取值不能比較大小。此時,要更亮無序屬性的距離要用到VDM距離,即
所以,對于屬性中既含有有序屬性,又含有無序屬性的樣本,則需要統一明可夫斯基距離和VDM距離
Q:k-means聚類算法的思路是怎樣的?
當我們去考察一堆數據的時候,一般會通過平均數和方差來獲取一個大致的了解。也就是說,平均數和方差可以代表一組數據。那么我們就會想,有沒有一個均值向量可以代表一組向量?
自然是有的(反正概念什么的都是人定的)。
上式代表類中所有向量的均值向量。
那么,如果有k個均值向量,就代表有k個類。可以分別計算某個向量與這k個均值向量的距離,哪個距離更短,就將該向量(樣本)歸到哪一類。這便是k-means(k均值)算法的基本思想。
實際的算法過程是一個循環。首先人為設定k值大小,即要劃分多少個類。然后第一輪在數據集中隨機挑選k個樣本作為初始的均值向量,接著計算剩下的樣本與這幾個均值向量的距離,并且聚類,可以得到k個類別,每個類別至少一個樣本。此時重新計算各個類別的均值向量,得到k個新的均值向量。下面重新計算距離,再度分類,再度重新計算均值向量,得到k個新的均值向量,如此循環往復,直到得到的k個均值向量與上一輪的均值向量一樣,也就是兩輪的聚類結果一樣的時候,循環結束。
Q:雖說聚類術語無監督學習,不需要用到樣本標記信息,但是既然存在標記信息,可不可以利用一下?
LVQ(學習向量量化)算法就是利用樣本標記信息來輔助聚類的算法。LVQ算法利用和k-means的均值向量類似的代表性向量(也可以叫原型向量),同樣也使用距離計算然后根據最短距離分類的的思想。不過LVQ算法的目的不是直接得到劃分好的幾個類,而是得到t個原型向量。也就是說,LVQ的輸出是t個原型向量。先上一張抽象的算法流程
LVQ算法同樣人為設定一個t值,也就是要劃分的類別數。然后初始化t個原型向量——不是像k-means一樣隨便選,而是根據預先說好了的標記,在隨便選。比如預先說好要選3個好瓜,2個壞瓜,就從好瓜中隨便選三個,壞瓜中隨便選兩個。
選好初始原型向量后,開始和k-means一樣不斷循環計算距離,不過LVQ每次都是隨機抽取訓練數據集的一個樣本,計算抽到的樣本和各個原型向量的距離。距離最短的一個原型向量,如果其標記和抽到的標記一樣,就“使原型向量向抽到的樣本向量靠近”,如果其標記和抽到的標記不同,就“使原型向量向抽到的樣本向量遠離”——
如此循環往復直到達到最大循環次數,或者原型向量的調整幅度已經小于某個閥值。
Q:有沒有不是基于原型向量的聚類算法?
除了基于原型向量的聚類算法,還有基于密度的聚類算法,如DBSCAN,和基于層次的聚類算法,如AGNES。
在了解什么是基于密度的算法前,先參考一下人際社交的生活經驗。“物以類聚,人以群分”,人群中會自發地誕生多個人際關系圈子。圈子中的核心人物一般來說往往是人脈最豐富的,或者直接說認識人最多的,這些人暫且稱為社交領袖。一個人一般來說可以很迅速地和自己的朋友建立聯系。只要兩個人處于同一個圈子之中,或者說他們處于不同的圈子,但是兩個圈子的人有交集,那么即使他們不認識,也可以通過朋友的朋友的朋友······建立聯系。弱兩個人分屬不同的圈子,且兩個圈子沒有交集,但是兩個圈子都和第三個圈子有交集,那么這兩個人同樣可以通過朋友的朋友的朋友······建立聯系。
根據上面這個例子,我們可以建立一個樣本密度模型
DBSCAN算法中的簇類比于交際關系里的圈子,鄰域類比于一個人的朋友圈,核心對象類比于社交領袖,密度直達、可達、相連分別類比于三種人與人知啊今建立聯系的方式。DBSCAN算法中的一個簇是指所有可以相互之間密度直達、可達、相連的樣本的集合。
而DBSCAN算法的原理也就很簡單了,首先確定數據集中所有核心對象,然后隨機從一個核心對象出發找出所有與之密度可達的樣本,找完以后這些樣本就是一個簇了,然后再隨機選一個不在現有的簇范圍內的核心對象,繼續上面的步驟,知道所有核心對象都被分到某個簇中。
DBSCAN算法最后可能會產生幾個不在任何簇里的樣本,他們被稱為噪聲,同時也可以視為異常數據。
基于層次的聚類算法試圖用自頂向下或者自底向上的方式在不同層次上對數據進行劃分。AGNES算法是一中自底向上的算法。算法的基本思想是在一開始把所有樣本都看作一個簇類。那么有n個樣本的數據集就有n個初始簇。接下來兩兩計算簇間距離,距離最小的兩個簇合并為一個簇(有點像霍夫曼樹的子樹合并),直到所有的簇合并為一個簇為止。各種簇間距離計算公式如下,視情況挑選
AGNES算法最終得到的結果如下圖
我們可以通過選擇簇間距離來確定到底分幾個簇。簇間距離越大,得到的簇越少,簇間距離越小,得到的簇越多。
Talk is cheap, show me the code!
下面是一個KMeans模型的簡單實現
"""
K-means clustering model
:file: supervised.py
:author: Richy Zhu
:email: rickyzhu@foxmail.com
"""
import numpy as np
class MyKMeans:
def __init__(self, k=3):
'''
K-means clustering model
:k: int, number of clusters
'''
self.k = k
self.X = None
self.labels = None
self.cluster_centers = None
def fit(self, X):
'''
Train the K-means clustering model
Parameters
----------
X: ndarray of shape (m, n)
sample data where row represent sample and column represent feature
Returns
-------
self
trained model
'''
ran_index = np.random.choice(np.arange(0,len(X)), self.k)
centres = [X[i] for i in ran_index]
prev_labels = np.zeros(len(X))
i = 0
while True:
labels = []
for x in X:
labels.append(np.argmin([np.linalg.norm(x - c) for c in centres]))
if np.array_equal(prev_labels, labels):
break
prev_labels = labels
for i in range(len(centres)):
centres[i] = np.mean(X[np.array(labels)==i], axis=0)
i += 0
self.labels = np.array(labels)
self.cluster_centers = np.array(centres)
return self
def predict(self, X):
'''
Make prediction by the trained model.
Parameters
----------
X: ndarray of shape (m, n)
data to be predicted, the same shape as trainning data
Returns
-------
C: ndarray of shape (m,)
Predicted class label per sample.
'''
labels = []
for x in X:
labels.append(np.argmin([
np.linalg.norm(x - c) for c in self.cluster_centers
]))
return np.array(labels)
測試代碼如下
import numpy as np
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from unsupervised import *
X, y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y)
print('\nK-means clustering')
print('---------------------------------------------------------------------')
mykm = MyKMeans(k=3)
mykm.fit(X_train)
print('My KMeans:')
print(mykm.cluster_centers)
print(mykm.predict(X_test))
print()
from sklearn.cluster import KMeans
skkm = KMeans(n_clusters=3)
skkm.fit(X_train)
print('Sk KMeans:')
print(skkm.cluster_centers_)
print(skkm.predict(X_test))
測試結果為
$ python unsupervised_examples.py
K-means clustering
---------------------------------------------------------------------
[[4.78333333 3.11666667 1.50555556 0.26666667]
[6.24 2.87866667 4.90133333 1.66933333]
[5.25789474 3.74210526 1.49473684 0.26315789]]
[2 1 2 1 2 1 2 1 1 0 1 1 1 1 0 1 1 1 0 0 0 1 0 1 1 0 2 1 1 2 1 1 1 0 0 1 1
0]
Sk KMeans:
[[5.025 3.46388889 1.45833333 0.24166667]
[6.78888889 3.08888889 5.72962963 2.08888889]
[5.91428571 2.75510204 4.40612245 1.42653061]]
[0 1 0 1 0 2 0 2 2 0 1 1 2 2 0 1 1 2 0 0 0 2 0 1 1 2 0 2 1 0 1 2 2 2 0 2 1
0]
更多代碼請參考https://github.com/qige96/programming-practice/tree/master/machine-learning
本作品首發于簡書 和 博客園平臺,采用知識共享署名 4.0 國際許可協議進行許可。