根据绘图颜色对相似度矩阵进行排序

Sort simmilarity matrix according to plot colors

我有一些文档的相似矩阵图。我想对矩阵的值进行排序,这是一个 numpynd 数组,以对颜色进行分组,同时保持它们的相对位置(对角线黄线)和标签。

path = "C:\Users\user\Desktop\texts\dataset"
text_files = os.listdir(path)
#print (text_files)

tfidf_vectorizer = TfidfVectorizer()
documents = [open(f, encoding="utf-8").read() for f in text_files if f.endswith('.txt')]
sparse_matrix = tfidf_vectorizer.fit_transform(documents)

labels = []
for f in text_files:
    if f.endswith('.txt'):
        labels.append(f)

pairwise_similarity = sparse_matrix * sparse_matrix.T

pairwise_similarity_array = pairwise_similarity.toarray()
 
fig, ax = plt.subplots(figsize=(20,20))
cax = ax.matshow(pairwise_similarity_array, interpolation='spline16')
ax.grid(True)
plt.title('News articles similarity matrix')
plt.xticks(range(23), labels, rotation=90);
plt.yticks(range(23), labels);
fig.colorbar(cax, ticks=[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1])
plt.show() 

这是一种可能。 这个想法是使用相似矩阵中的信息,如果元素相似,则将它们放在一起。如果两个项目相似,则它们在其他元素方面也应该相似,即具有相似的颜色。 我从与所有其他元素最共同的元素开始(这个选择有点武断)[a],作为下一个元素,我从剩余元素中选择最接近当前 [b] 的元素。

import numpy as np
import matplotlib.pyplot as plt

def create_dummy_sim_mat(n):
    sm = np.random.random((n, n))
    sm = (sm + sm.T) / 2
    sm[range(n), range(n)] = 1
    return sm

def argsort_sim_mat(sm):
    idx = [np.argmax(np.sum(sm, axis=1))]  # a
    for i in range(1, len(sm)):
        sm_i = sm[idx[-1]].copy()
        sm_i[idx] = -1
        idx.append(np.argmax(sm_i))  # b
    return np.array(idx)


n = 10
sim_mat = create_dummy_sim_mat(n=n)

idx = argsort_sim_mat(sim_mat)
sim_mat2 = sim_mat[idx, :][:, idx]  # apply reordering for rows and columns

# Plot results
fig, ax = plt.subplots(1, 2)
ax[0].imshow(sim_mat)
ax[1].imshow(sim_mat2)

def ticks(_ax, ti, la):
    _ax.set_xticks(ti)
    _ax.set_yticks(ti)
    _ax.set_xticklabels(la)
    _ax.set_yticklabels(la)

ticks(_ax=ax[0], ti=range(n), la=range(n))
ticks(_ax=ax[1], ti=range(n), la=idx)


meTchaikovsky 的回答后,我还在聚类相似矩阵(见第一张图片)上测试了我的想法,这种方法有效但并不完美(见第二张图片)。

因为我使用两个元素之间的相似性作为它们与所有其他元素的相似性的近似值,所以很清楚为什么这不能完美地工作。 因此,可以计算 二阶 相似度矩阵来衡量相似度的相似程度 (sorry),而不是使用初始相似度对元素进行排序。 此度量更好地描述了您感兴趣的内容。如果两行/列具有相似的颜色,则它们应该彼此接近。对矩阵排序的算法和之前一样

def add_cluster(sm, c=3):
    idx_cluster = np.array_split(np.random.permutation(np.arange(len(sm))), c)
    for ic in idx_cluster:
        cluster_noise = np.random.uniform(0.9, 1.0, (len(ic),)*2)
        sm[ic[np.newaxis, :], ic[:, np.newaxis]] = cluster_noise

def get_sim_mat2(sm):
    return 1 / (np.linalg.norm(sm[:, np.newaxis] - sm[np.newaxis], axis=-1) + 1/n)

sim_mat = create_dummy_sim_mat(n=100)
add_cluster(sim_mat, c=4)
sim_mat2 = get_sim_mat2(sim_mat)

idx = argsort_sim_mat(sim_mat)
idx2 = argsort_sim_mat(sim_mat2)
sim_mat_sorted = sim_mat[idx, :][:, idx]
sim_mat_sorted2 = sim_mat[idx2, :][:, idx2]

# Plot results
fig, ax = plt.subplots(1, 3)
ax[0].imshow(sim_mat)
ax[1].imshow(sim_mat_sorted)
ax[2].imshow(sim_mat_sorted2)

第二种方法的结果很好(见第三张图) 但我想也存在这种方法也失败的情况,所以我很高兴收到反馈。


编辑

我试着解释了一下,也用 [a] 和 [b] 对代码做了 link 的想法,但显然我做得不好,所以这里有第二个更详细的解释.

您有 n 个元素和一个 n x n 相似度矩阵 sm,其中每个单元格 (i, j) 描述了元素 i 与元素 j 的相似程度。目标是以一种可以看到相似矩阵中现有模式的方式对行/列进行排序。我实现这个的想法真的很简单。

您从一个空列表开始,然后一个一个地添加元素。下一个元素的判断标准是与当前元素的相似度。如果元素 i 在最后一步中添加,我选择元素 argmax(sm[i, :]) 作为下一步,忽略已经添加到列表中的元素。我通过将这些元素的值设置为 -1 来忽略这些元素。

您可以使用函数ticks重新排序标签:

labels = np.array(labels)  # make labels an numpy array, to index it with a list
ticks(_ax=ax[0], ti=range(n), la=labels[idx])

很优雅,但也有一个不足,就是不能设置排序后的相关矩阵的簇数。假设我们正在处理一组变量,其中一些变量是弱相关的

import string 
import numpy as np
import pandas as pd

n_variables = 20
n_clusters = 10
n_samples = 100

np.random.seed(100)
names = list(string.ascii_lowercase)[:n_variables]
belongs_to_cluster = np.random.randint(0,n_clusters,n_variables)

latent = np.random.randn(n_clusters,n_samples)
variables = np.random.rand(n_variables,n_samples)
for ind in range(n_clusters):
    mask = belongs_to_cluster == ind
    # weakening the correlation 
    if ind % 2 == 0:variables[mask] += latent[ind]*0.1
    variables[mask] += latent[ind]

df = pd.DataFrame({key:val for key,val in zip(names,variables)})
corr_mat = np.array(df.corr())

如您所见,构造了 10 个变量聚类,但是,具有偶数索引的聚类中的变量相关性较弱。如果我们只想在排序后的相关矩阵中看到大约 5 个簇,也许我们需要找到另一种方法。

基于this post, which is the accepted answer to the question "Clustering a correlation matrix",将相关矩阵排序成块,我们需要找到的是块,块内相关性高,块间相关性低.但是,当我们首先知道有多少块时,这个已接受的答案提供的解决方案效果最好,而且 更重要的是,底层块的大小相同,或者至少相似。 因此,我用新函数改进了解决方案 sort_corr_mat

def sort_corr_mat(corr_mat,clusters_guess): 
    
    def _swap_rows(corr_mat, var1, var2):
        rs = corr_mat.copy()
        rs[var2, :],rs[var1, :]= corr_mat[var1, :],corr_mat[var2, :]
        cs = rs.copy()
        cs[:, var2],cs[:, var1] = rs[:, var1],rs[:, var2]
        return cs

    # analysis
    max_iter = 500
    best_score,current_score,best_count = -1e8,-1e8,0
    num_minimua_to_visit = 20
    
    best_corr = corr_mat
    best_ordering = np.arange(n_variables)
    for i in range(max_iter):
        for row1 in range(n_variables):
            for row2 in range(n_variables):
                if row1 == row2: continue
                option_ordering = best_ordering.copy()
                option_ordering[row1],option_ordering[row2] = best_ordering[row2],best_ordering[row1]
                option_corr = _swap_rows(best_corr,row1,row2)
                option_score = score(option_corr,n_variables,clusters_guess)

                if option_score > best_score:
                    best_corr = option_corr
                    best_ordering = option_ordering
                    best_score = option_score

        if best_score > current_score:
            best_count += 1
            current_corr = best_corr
            current_ordering = best_ordering
            current_score = best_score
            
        if best_count >= num_minimua_to_visit:
            return best_corr#,best_ordering
        
    return best_corr#,best_ordering

有了这个函数和首先构建的corr_mat,我将用我的函数(右边)得到的结果与用(中间)得到的结果进行了比较

sim_mat_sorted = corr_mat[argsort_sim_mat(corr_mat), :][:, argsort_sim_mat(corr_mat)]
corr_mat_sorted = sort_corr_mat(corr_mat,clusters_guess=5)

# Plot results
fig, ax = plt.subplots(1,3,figsize=(18,6))
ax[0].imshow(corr_mat)
ax[1].imshow(sim_mat_sorted)
ax[2].imshow(corr_mat_sorted)

显然, 工作得更好更快,但我的解决方案提供了对输出模式的更多控制。