距离度量
好的我们开始学习聚类,聚类是一种无监督手段,尝试仅仅依靠自变量的分布把数据进行分类。很多聚类算法都基于向量的欧式距离进行,所以正确处理各特征的尺度以及确定各特征之间的权重比是聚类是否有效的关键。
最常用的距离度量方法是“闵可夫斯基距离”(Minkowski distance):
当p=1时,闵可夫斯基距离即曼哈顿距离(Manhattan distance):
当p=2时,闵可夫斯基距离即欧氏距离(Euclidean distance)
对于无序属性,我们一般采用VDM进行距离的计算,例如:对于离散属性的两个取值a和b,定义:
K-means
K-Means的思想十分简单,首先随机指定类中心,根据样本与类中心的远近划分类簇,接着重新计算类中心,迭代直至收敛。但是其中迭代的过程并不是主观地想象得出,事实上,若将样本的类别看做为“隐变量”(latent variable),类中心看作样本的分布参数,这一过程正是通过EM算法的两步走策略而计算出,其根本的目的是为了最小化平方误差函数E
K-Means的算法流程如下所示:
简单实现,并用于鸢尾花聚类。
import numpy as np
import random
from copy import deepcopy
import matplotlib
from matplotlib import pyplot as plt
from sklearn.metrics import accuracy_score
import warnings
from sklearn.datasets import load_iris
warnings.filterwarnings('ignore')
iris = load_iris()
descr = iris['DESCR']
data = iris['data']
feature_names = iris['feature_names']
target = iris['target']
target_names = iris['target_names']
def Dist(x1,x2):
#Minkowski distance
p = len(x1)
return np.sum((x1-x2)**p)**(1/p)
def Kmeans(data,k):
data = list(data)
us = random.sample(data,k)
N = len(data)
labels = [0 for _ in range(N)]#聚类标签
while(1):
for j in range(N):
for i in range(k):
c = labels[j]
if Dist(us[i],data[j])<Dist(us[c],data[j]):
labels[j] = i
new_us = [np.zeros(len(data[0])) for _ in range(k)]
counter = [0 for _ in range(k)]
for j in range(N):
c = labels[j]
new_us[c] += data[j]
counter[c]+=1
flag = 1
for i in range(k):
new_us[i]/=counter[i]
if (new_us[i]!=us[i]).all():
flag = 0
if flag:
return labels
us = new_us
return None
labels = Kmeans(data,3)
plt.figure(figsize=(10,4), dpi=80)
plt.subplot(1,2,1)
plt.scatter(data[:,0].reshape(-1), data[:,1].reshape(-1),edgecolors='black',
c=target)
plt.xlabel('sepal length (cm)')
plt.ylabel('sepal width (cm)')
plt.title('Iris original distribution')
plt.subplot(1,2,2)
plt.scatter(data[:,0].reshape(-1), data[:,1].reshape(-1),edgecolors='black',
c=labels)
plt.xlabel('sepal length (cm)')
plt.ylabel('sepal width (cm)')
plt.title('Distribution by Kmeans')
高斯混合聚类
看出K-Means与LVQ都试图以类中心作为原型指导聚类,高斯混合聚类则采用高斯分布来描述原型。现假设每个类簇中的样本都服从一个多维高斯分布,那么空间中的样本可以看作由k个多维高斯分布混合而成。
对于多维高斯分布,其概率密度函数如下所示:
其中u表示均值向量,∑表示协方差矩阵,可以看出一个多维高斯分布完全由这两个参数所确定。接着定义高斯混合分布为:
α称为混合系数,这样空间中样本的采集过程则可以抽象为:(1)先选择一个类簇(高斯分布),(2)再根据对应高斯分布的密度函数进行采样,这时候贝叶斯公式又能大展身手了:
此时只需要选择PM最大时的类簇并将该样本划分到其中,看到这里很容易发现:这和那个传说中的贝叶斯分类不是神似吗,都是通过贝叶斯公式展开,然后计算类先验概率和类条件概率。但遗憾的是:这里没有真实类标信息,对于类条件概率,并不能像贝叶斯分类那样通过最大似然法计算出来,因为这里的样本可能属于所有的类簇,这里的似然函数变为:
可以看出:简单的最大似然法根本无法求出所有的参数,这样PM也就没法计算。这里就要召唤出之前的EM大法,首先对高斯分布的参数及混合系数进行随机初始化,计算出各个PM(即γji,第i个样本属于j类),再最大化似然函数(即LL(D)分别对α、u和∑求偏导 ),对参数进行迭代更新。
高斯混合聚类的算法流程如下图所示:
简单实现并用于鸢尾花,观察聚类中心。
#高斯混合聚类,基于EM算法
#高斯混合模型是混合多个高斯分布得到的
#每个模型包含均值,协方差矩阵以及模型权重
def GMM(data,k,iter_num=20):
#初始化
N,d = data.shape
alpha = np.random.rand(k)
alpha /= alpha.sum()
means = np.random.rand(k,d)
covs = np.empty((k,d,d))
for i in range(k):
covs[i] = np.eye(d)*np.random.rand(1)*k
for iteration in range(iter_num):
gamma = np.zeros((N,k))
#计算后验概率
for j in range(N):
p = np.zeros(k)
for i in range(k):
vec = (data[j]-means[i]).reshape(1,-1)
num = np.exp(-0.5*vec.dot(np.linalg.pinv(covs[i])).dot(np.transpose(vec)))
den = (2*np.pi)**d*np.linalg.det(covs[i])
p[i] = num/den
s = (p*alpha).sum()
for i in range(k):
gamma[j][i] = alpha[i]*p[i]/s
#最大化期望
for i in range(k):
num = 0;den = 0;
for j in range(N):
num+=gamma[j][i]*data[j]
den+=gamma[j][i]
means[i] = num/den
num = np.zeros((d,d));
for j in range(N):
vec = data[j]-means[i].reshape(1,-1)
num+=gamma[j][i]*np.transpose(vec).dot(vec)
covs[i] = num/den
alpha[i] = den/N
return alpha,means,covs
alpha,means,covs = GMM(data,3,30)
plt.figure(figsize=(10,6.5), dpi=80)
plt.scatter(data[:,2].reshape(-1), data[:,3].reshape(-1),edgecolors='black',
c=target)
plt.xlabel('petal length (cm)')
plt.ylabel('petal width (cm)')
plt.title('Means vectors of Gussian mixture model')
plt.scatter(means[:,2].reshape(-1), means[:,3].reshape(-1),marker='*',c='r',linewidth=5)
密度聚类
挺简单的,设置阈值表示连通性,设置点数表示是不是聚类中心,就可以用密度可达性得到一张连通图,进而进行聚类。
常用的DBSCAN算法如下
#密度聚类,使用密度可达关系导出连通性
#基于连通性定理把数据分块
import queue
def DBscan(data,epsilon,minpts):
core = set()
L = len(data)
AM = [[0 for _ in range(L)] for _ in range(L)]#联通与否使用bool值表示
#建立初步邻接矩阵并确定核心对象
for i in range(L):
for j in range(L):
AM[i][j] = (Dist(data[i],data[j])<epsilon)
#不考虑本身时,密度直达的点的数目大于等于minpts,则该点为core object
for i in range(L):
if sum(AM[i])-1>=minpts:
core.add(i)
visited = set()#使用集合表示是否被访问过
#实行广度优先搜索获得多个簇
cluster_cnt = 0
labels = [-1 for _ in range(L)]#-1表示该点不属于任何一个核心对象的簇,即异常值
while (len(core)):
#随机获取一个core object
co = core.pop()
if co in visited:
continue
q = queue.Queue()
q.put(co)
while (not q.empty()):
p = q.get()
labels[p] = cluster_cnt#置p为第n个簇
visited.add(p)
for i in range(L):
if AM[p][i] and p!=i and (i not in visited):
q.put(i)
cluster_cnt += 1
return labels
labels = DBscan(data,0.3,5)
plt.figure(figsize=(10,4), dpi=80)
plt.subplot(1,2,1)
plt.scatter(data[:,0].reshape(-1), data[:,1].reshape(-1),edgecolors='black',
c=target)
plt.xlabel('sepal length (cm)')
plt.ylabel('sepal width (cm)')
plt.title('Iris original distribution')
plt.subplot(1,2,2)
plt.scatter(data[:,0].reshape(-1), data[:,1].reshape(-1),edgecolors='black',
c=labels)
plt.xlabel('sepal length (cm)')
plt.ylabel('sepal width (cm)')
plt.title('Distribution by dbscan clustering')
层次聚类
使用的距离度量不是点与点直接的距离,而是簇与簇之间的距离。根据簇之间的相似度不断进行合并,算法的核心在于“如何度量簇与簇之间的距离。
def synthesis_dist(a, b):
D = [[Dist(a[i], b[j]) for j in range(len(b))] for i in range(len(a))]
minz = []
for z in range(len(b)):
minz.append(min(D[x][z] for x in range(len(a))))
minx = []
for x in range(len(a)):
minx.append(min(D[x][z] for z in range(len(b))))
return max(max(minz),max(minx))
def hierarchical_clustering(data, k):
#初始化n个簇,每个簇只有一个元素
clusters = [[ele] for ele in data]
N = len(data)
M = np.zeros((N,N))
INF = float("inf")
#设置簇与自身的dist为无穷大,因为簇不可能与自身合并
for i in range(N):
M[i][i] = INF
for j in range(i+1,N):
M[i][j] = synthesis_dist(clusters[i],clusters[j])
M[j][i] = M[i][j]
while(len(clusters)>k):
#寻找距离最近的两个簇
L =len(clusters)
cur_dist = M[0][0]
curi = curj = 0
for i in range(L):
for j in range(i+1,L):
if cur_dist>M[i][j]:
cur_dist = M[i][j]
curi = i
curj = j
#合并i、j两簇,并删除clusters和M中的i、j行列
clusters[curi].extend(deepcopy(clusters[curj]))
clusters.pop(curj)
index = np.arange(0,L)
index = np.delete(index,curj)
M = M[:,index][index]
#更新距离矩阵
for j in range(L-1):
if curi==j:
M[curi][j]=INF
M[j][curi]=INF
else:
D = synthesis_dist(clusters[curi],clusters[j])
M[curi][j] = D
M[j][curi] = D
#设计labels
labels = np.zeros(N)
dic = {}
for i in range(k):
for ele in clusters[i]:
key = tuple(ele)
dic[key] = i
for i in range(N):
labels[i] = dic[tuple(data[i])]
return labels
labels = hierarchical_clustering(data,3)
plt.figure(figsize=(10,4), dpi=80)
plt.subplot(1,2,1)
plt.scatter(data[:,0].reshape(-1), data[:,1].reshape(-1),edgecolors='black',
c=target)
plt.xlabel('sepal length (cm)')
plt.ylabel('sepal width (cm)')
plt.title('Iris original distribution')
plt.subplot(1,2,2)
plt.scatter(data[:,0].reshape(-1), data[:,1].reshape(-1),edgecolors='black',
c=labels)
plt.xlabel('sepal length (cm)')
plt.ylabel('sepal width (cm)')
plt.title('Distribution by AGNES clustering')
本文代码均为原创,转载请声明