AGNES是一种采用自底向上合并策略的聚类算法,其思想为:初始将所有样本看成一个簇,然后在每一轮过程中将距离最近的两个簇合并为一个簇,簇的个数不断减少到人为指定的聚类簇数K,终止算法。该算法关键在于如何度量两个簇的距离,集合间的距离计算有如下方式:
最
小
距
离
:
d
i
s
t
(
C
i
,
C
j
)
=
m
i
n
[
x
∈
C
i
,
z
∈
C
j
]
∣
∣
x
−
z
∣
∣
2
最
大
距
离
:
d
i
s
t
(
C
i
,
C
j
)
=
m
a
x
[
x
∈
C
i
,
z
∈
C
j
]
∣
∣
x
−
z
∣
∣
2
平
均
距
离
:
d
i
s
t
(
C
i
,
C
j
)
=
1
∣
C
i
∣
∣
C
j
∣
∑
x
∈
C
i
∑
z
∈
C
j
∣
∣
x
−
z
∣
∣
2
\begin{aligned} 最小距离:dist(C_i,C_j) &= min_{[x\in C_i,z\in C_j]}||x-z||_2 \\ 最大距离:dist(C_i,C_j) &= max_{[x\in C_i,z\in C_j]}||x-z||_2 \\ 平均距离:dist(C_i,C_j)&=\cfrac{1}{|C_i||C_j|}\sum_{x\in C_i}\sum_{z\in C_j}||x-z||_2 \end{aligned}
最小距离:dist(Ci,Cj)最大距离:dist(Ci,Cj)平均距离:dist(Ci,Cj)=min[x∈Ci,z∈Cj]∣∣x−z∣∣2=max[x∈Ci,z∈Cj]∣∣x−z∣∣2=∣Ci∣∣Cj∣1x∈Ci∑z∈Cj∑∣∣x−z∣∣2
采用最小距离的AGNES称为 “单连接算法” ,单连接趋向于发现有局部邻近性的簇。采用最大距离的AGNES称为 “全连接算法”,全连接趋向于发现有全局邻近性的簇。但是,单连接和全连接对离群点敏感,因为离群点始终为单个簇无法参与合并。采用平均距离的AGNES称为 “均连接算法” ,它可以克服离群点问题。
AGNES算法流程如下:
pyhon实现如下:
#集合间的平均距离
def dist(X,Z):
n1 = X.shape[0]
n2 = Z.shape[0]
total = 0.0
for x in X:
for z in Z:
total +=math.sqrt((x-z) @ (x-z).T)
return total / (n1 * n2)
#从距离矩阵中找出最小值对应的下标
def minInD(D):
m = D.shape[0]
i_index = 0
j_index = 0
min_val = float('inf')
for i in range(0,m):
for j in range(0,m):
if i == j:continue
if D[i,j] <min_val:
min_val = D[i,j]
i_index = i
j_index = j
return [i_index,j_index]
def agnes(data,k):
m,n = data.shape
#初始化m个簇
cls = []
for i in range(0,m):
cls.append(np.array([data[i]]))
#初始化距离矩阵D
D = np.zeros((m,m))
for i in range(0,m):
for j in range(0,m):
D[i,j] = dist(cls[i],cls[j])
D[j,i] = D[i,j]
q = m#当前簇的个数
while q > k:
#找到距离最近的两个簇
l,r =minInD(D)
#合并两个簇
cls[l] = np.concatenate((cls[l],cls[r]),axis=0)
#删除原来的簇
cls = np.delete(cls,r,axis = 0)
#删除r行,r列
D = np.delete(D,r,axis = 0)
D = np.delete(D,r,axis = 1)
#更新距离矩阵D
for j in range(0,q-1):
D[l,j] = dist(cls[l],cls[j])
D[j,l] = D[l,j]
q = q - 1
return cls
DIANA是一种采用自顶向下分裂策略的聚类算法。它的思想是:初始将整个样本视为一个簇,然后进行分裂,再找到直径最大的簇,再进行分裂;分裂的方式是先找到簇中平均相异度最大的样本作为新簇的起始点,然后在旧簇中不断寻找:到新簇的平均距离小于到旧簇的平均距离的样本划分给新簇。直到分裂的簇个数达到人为指定的k值,算法终止。有一些概念:
DIANA算法流程如下:
python 实现如下:
#寻找直径最大的簇
def diameterMax(cls):
#计算簇的直径
def diameter(each):
max_d = 0
for x in each:
for y in each:
if (x == y).all() :continue
d = math.sqrt((x-y)@(x-y).T)
if d > max_d:
max_d = d
return max_d
index = -1
maxs = 0
for i in range(0,len(cls)):
dia = diameter(cls[i])
if dia > maxs:
maxs = dia
index = i
return [cls[index],index]
#计算某个样本与集合的平均相异度
def distinct(x,C):
totals = 0.0
for c in C:
totals += math.sqrt((x-c) @ (x-c).T)
return totals / C.shape[0]
def diana(data,k):
m,n = data.shape
#初始化所有样本为一个簇
cls = [data]
#记录当前簇的个数
q = 1
#分裂到指定簇数结束
while q < k:
#找到直径最大的簇及其位置
C,index = diameterMax(cls)
#找到平均相异度最大的点作为新的簇一个样本
max_val = 0
j = -1
for i in range(0,C.shape[0]):
diff = distinct(C[i],np.delete(C,i,axis=0))
if max_val < diff:
j = i
max_val = diff
#初始化分裂后的两个集合
cls_new = C[j].reshape((1,n))
cls_old = np.delete(C,j,axis = 0)
#new和old集合不再变动结束
while True:
count = 0 #标记集合是否更新过
for i in range(0,cls_old.shape[0]):
l = distinct(cls_old[i],cls_new)
r = distinct(cls_old[i],np.delete(cls_old,i,axis=0))
#若old集合的样本到new的最短距离比到自身的最短距离还要小
if l < r:
count += 1
#更新old和new
cls_new = np.concatenate((cls_new,[cls_old[i]]),axis=0)
cls_old = np.delete(cls_old,i,axis=0)
break
if count == 0:break
#一分为二
cls.pop(index)
cls.append(cls_new)
cls.append(cls_old)
q += 1
return cls