对于分类数据进行层次聚类,常用的距离度量方法是基于匹配(matching coefficient)、杰卡德相似系数(Jaccard similarity coefficient)或余弦相似系数(cosine similarity coefficient)等方法。下面给出一个基于匹配的层次聚类。
自底向上
此代码实现的基本思路如下:
- 初始化每个样本为一个簇。
- 计算每对簇之间的相异度,这里使用简单匹配系数。
- 找到相异度最大的一对簇。
- 合并相异度最大的一对簇。
- 重复步骤 3 和 4,直到只剩下 k 个簇。
def match_hierarchy_clustering(X, K):
n_samples, n_features = X.shape
# 初始化簇
clusters = [[i] for i in range(n_samples)]
# 计算相异度矩阵
S = np.zeros((n_samples, n_samples))
for i in range(n_samples):
for j in range(i + 1, n_samples):
matches = np.sum(X[i, :] == X[j, :])
S[i, j] = matches / n_features
S[j, i] = matches / n_features
# 循环合并簇,直到只剩 K 个簇
while len(clusters) > K:
# 找到相异度最大的一对簇
max_sim = -1
max_i, max_j = -1, -1
for i in range(len(clusters)):
for j in range(i + 1, len(clusters)):
sim = 0
for k in clusters[i]:
for L in clusters[j]:
sim += S[k, L]
sim /= len(clusters[i]) * len(clusters[j])
if sim > max_sim:
max_sim = sim
max_i = i
max_j = j
# 合并相异度最大的一对簇
clusters[max_i].extend(clusters[max_j])
del clusters[max_j]
print("clusters")
print(clusters)
# 计算每个簇的聚类中心
centroids = []
for cluster in clusters:
if len(cluster) == 2:
r = random.choice(cluster)
centroid = X[r, :]
else:
centroid = []
for dd in range(n_features):
centroid.append(stats.mode(X[cluster, dd])[0][0])
centroid = np.array(centroid)
centroids.append(centroid.astype(int))
return np.array(centroids)
自顶向下 DIANA 算法
DIANA(Divisive Analysis)算法是一种自顶向下(Top-Down)的层次聚类算法,其主要思想是从一个包含所有样本的大簇开始,逐渐分割成越来越小的子簇。其步骤如下:
-
初始化:将所有样本看作一个大簇。
-
计算相异度:计算每个样本与其它样本的相异度,可使用欧氏距离、曼哈顿距离、余弦相似度等。
-
选择相异度最大的簇:找到当前相异度最大的簇,将其分成两个子簇。
-
计算子簇的相异度:对于新产生的两个子簇,重新计算它们与其它簇之间的相异度。
-
重复3和4步骤,直到所有簇的大小都为1。
-
层次聚类:根据产生的簇层次结构,将簇组织成一个树形结构(簇树)。
-
利用簇树进行聚类:从树的顶部开始,根据簇之间的相异度,将簇逐渐合并成更大的簇,直到达到预定的簇的个数或达到某个相异度的阈值。
需要注意的是,DIANA算法是一种贪心算法,它只考虑当前时刻相异度最大的簇,而不是全局最优。因此,它可能会陷入局部最优解,而无法得到全局最优解。此外,DIANA算法的计算复杂度较高,因为每一次划分都需要重新计算所有样本之间的相异度。
def diana_clustering(X, k):
"""
对分类数据进行 DIANA 聚类
参数:
X -- 二维的 numpy 数组,每一行表示一个样本,每一列表示一个特征
k -- 聚类的簇数
返回:
clusters -- 一个列表,其中的每个元素是一个列表,表示一个簇
centroids -- 一个二维的 numpy 数组,每一行表示一个聚类中心
"""
n_samples, n_features = X.shape
# 初始化簇
clusters = [[i] for i in range(n_samples)]
# 计算相异度矩阵
S = np.zeros((n_samples, n_samples))
for i in range(n_samples):
for j in range(i + 1, n_samples):
matches = np.sum(X[i, :] == X[j, :])
S[i, j] = matches / n_features
S[j, i] = matches / n_features
# 循环合并簇,直到只剩 k 个簇
while len(clusters) > k:
# 找到相异度最大的簇
max_sim = -1
max_cluster = None
for cluster in clusters:
sim = np.mean(S[cluster][:, cluster])
if sim > max_sim:
max_sim = sim
max_cluster = cluster
# 计算簇内相异度矩阵
S_cluster = S[max_cluster][:, max_cluster]
# 找到相异度最大的一对样本
i, j = np.unravel_index(np.argmax(S_cluster), S_cluster.shape)
# 将相异度最大的一对样本分别分配到不同的簇
new_cluster_1 = [max_cluster[i]]
new_cluster_2 = [max_cluster[j]]
for index in range(len(max_cluster)):
if index not in [i, j]:
if S_cluster[index, i] > S_cluster[index, j]:
new_cluster_1.append(max_cluster[index])
else:
new_cluster_2.append(max_cluster[index])
# 更新簇列表
clusters.remove(max_cluster)
clusters.append(new_cluster_1)
clusters.append(new_cluster_2)
# 计算聚类中心
centroids = np.zeros((k, n_features))
for i, cluster in enumerate(clusters):
if len(cluster) == 2:
centroids[i] = np.mean(X[cluster], axis=0)
else:
for dd in range(n_features):
centroids[i][dd] = stats.mode(X[cluster, dd])[0][0]
return centroids
其他:
如果遇到层次聚类过程中出现某一个簇只有一个值的情况该怎么解决?
-
增加数据量:增加数据量可以减少簇中只有一个样本的情况发生的概率。
-
选择合适的相似度度量方法:选择适合数据类型的相似度度量方法可以使得簇内样本的相似度更高,从而减少簇中只有一个样本的情况发生的概率。
-
调整聚类算法参数:例如可以调整聚类过程中簇的数量、阈值等参数,以获得更合理的聚类结果。
-
结合其他聚类方法:可以将层次聚类与其他聚类方法结合使用,例如K-Means算法等,以获得更好的聚类结果。