LVQ聚類與k-means不同之處在於,它是有標記的聚類,設定帶標簽的k個原型向量(即團簇中心),根據樣本標簽是否與原型向量的標簽一致,對原型向量進行更新。
最后,根據樣本到原型向量的距離,對樣本進行團簇划分。
偽代碼如下:
python實現如下:
1,算法部分
# 學習向量量化LVQ:有標記的聚類 import numpy as np
import random def dis(x,y): return np.sqrt(np.sum(np.power(x[:-1]-y[:-1],2))) # lvq算法 def lvq(data,labels,k=4,lr=0.01,epochs=1000,delta=1e-3): ''' data:np.array,last feature is the label. labels:1-dimension list or array,label of the data. k:num_group lr:learning rate epochs:max epoch to stop running earlier delta: max distance for two vectors to be 'equal'. ''' # 學習向量 q=np.empty(shape=(k,data.shape[-1]),dtype=np.float32) # 確認是否所有向量更新完了 all_vectors_updated=np.empty(shape=(k,),dtype=np.bool) num_labels=len(labels) # 初始化原型向量,從每一類中隨機選取樣本,如果類別數小於聚類數,循環隨機取各類別中的樣本 for i in range(k): q[i]=random.choice(data[data[:,-1]==labels[i%num_labels]]) step=0 while not all_vectors_updated.all() and step<epochs: # 從樣本中隨機選取樣本,書上是這么寫的,為啥不循環,要隨機呢?np.random的choice只支持一維 x=random.choice(data) min_dis=np.inf index=0 for i in range(k): distance=dis(x,q[i]) if distance<min_dis: min_dis=distance index=i # 保存更新前向量 temp_q=q[index].copy() # 如果標簽相同,則q更新后接近樣本x,否則遠離 if x[-1]==q[index][-1]: q[index][:-1]=q[index][:-1]+lr*(x[:-1]-q[index][:-1]) else: q[index][:-1]=q[index][:-1]-lr*(x[:-1]-q[index][:-1]) # 更新記錄數組 if dis(temp_q,q[index])<delta: all_vectors_updated[index]=True step+=1 # 訓練完后,樣本划分到最近的原型向量簇中 categoried_data=[] for i in range(k): categoried_data.append([]) for item in data: min_dis=np.inf index=0 for i in range(k): distance=dis(item,q[i]) if distance<min_dis: min_dis=distance index=i categoried_data[index].append(item) return q,categoried_data
2,驗證、測試
2.1 隨機x-y平面上的點,根據y=x將數據划分為2個類別,然后聚類
先看看原始數據分布:
x=np.random.randint(-50,50,size=100) y=np.random.randint(-50,50,size=100) x=np.array(list(zip(x,y))) import matplotlib.pyplot as plt %matplotlib inline plt.plot([item[0] for item in x],[item[1] for item in x],'ro')
處理輸入數據:
# y>x:1 y<=x:0 y=np.array([ 1&(item[1]>item[0]) for item in x]) y=np.expand_dims(y,axis=-1) data=np.concatenate((x,y),axis=1).astype(np.float32)
訓練,顯示結果
q,categoried_data=lvq(data,np.array([0.,1.]),k=4) color=['bo','ko','go','co','yo','ro'] for i in range(len(categoried_data)): data_i=categoried_data[i] plt.plot([item[0] for item in data_i],[item[1] for item in data_i],color[i]) plt.plot([item[0] for item in q],[item[1] for item in q],color[-1]) plt.show()
這里執行了2次,可以看出與k-means一樣,對初值敏感
總結:
根據上圖可以看出,聚類的效果是在標記的前提下進行的,即團簇是很少跨過分類邊界y=x的。相當於對每一個類別,進行了細分。因為每次訓練根據一個樣本更新,epochs應該設置大一點。
另外,感覺我這個算法有點問題(不知道是不是沒理解好lvq),當團簇數大於分類數時,團簇標記會重疊,這就導致同一個類下的2個團簇,當進行原型向量更新時,可能導致向量靠近另一個團簇的樣本。從直覺上看,k-means那種基於多個樣本的中心更新看起來更靠譜一些。