社区发现 -- 数据挖掘

作者:林子
链接:https://blog.csdn.net/Leoch007/article/details/80206759
欢迎转载,记得注明出处


社区发现

图聚类,也称为社区发现、图划分,其与聚类相似又不同于聚类(旨在将相似的数据点划分为一类),社区发现的目的是通过将数据点划分到不同的簇中,使得簇内的边尽量地多,簇之间的边尽可能地少。

    • 社区发现
        • Normalized Cut
        • Louvain
        • NMF

Normalized Cut

  1. 算法解析

    图聚类也叫图划分,就是试图找到最好的切割方式将图划分成不同的部分(社区),而一种很经典的方式便是最小割,即尝试将图划分为K个子图,这K个子图中之间的割最小。

    割的定义则是 cut(A,B)=uA,vBw(u,v) c u t ( A , B ) = ∑ u ∈ A , v ∈ B w ( u , v ) ,表示A、B子图之间的边权重之和。

    该算法思想很朴素也很直观,满足我们希望得到较好图划分的需求;但是,由于其只考虑子图之间的边权重之和,而往往单节点和其补图之间连接的边数是最少的,故其会倾向于分离单个或者少量的簇节点,而这往往不是最佳的图划分方式,如下图所示:

    而Normalized Cut的提出正是要解决MinCut最小割的这一问题。Normalized Cut首次提出是在论文《Normalized cuts and image segmentation》中,其目的是解决最小割中极易划分出单节点簇的弱点。Normalized Cut对最小割做了进一步的优化,其数学定义为: Ncut(A,B)=cut(A,B)assoc(A,V)+cut(A,B)assoc(B,V) N c u t ( A , B ) = c u t ( A , B ) a s s o c ( A , V ) + c u t ( A , B ) a s s o c ( B , V ) ,其中 cut(A,B) c u t ( A , B ) 正是A、B子图之间的最小割,而 assoc(A,V)=uA,tVw(u,t) a s s o c ( A , V ) = ∑ u ∈ A , t ∈ V w ( u , t ) 表示A子图中每个点与图中所有点的边权总和。从Ncut的数学定义中,我们也可以看出,其对割做了标准化的处理,使得单节点的cut虽然小,但其assoc也很小,总体依然较大,最小化Ncut时则不会出现偏向于划分出单节点簇的情况。

    此外,最小化Normalized Cut的过程中,可以将其转成矩阵特征值和特征向量的求解问题:W表示相似度矩阵,D为对角的度矩阵,则NCut的最小值问题转化为:

    minxNcut(x)=minyyT(DW)yyTDy m i n x N c u t ( x ) = m i n y y T ( D − W ) y y T D y 其中 y(i){1,1} y ( i ) ∈ { 1 , − 1 } ;这个形式属于Rayleigh商数,如果把y的取值范围放宽到实数,那么最小化Ncut相当于求解广义特征值系统下的方程: (DW)y=λDy ( D − W ) y = λ D y 即化为了特征值和特征向量的求解问题,故而可以采取和谱聚类思路一致的算法步骤,分为 2-路划分和 k-路划分两种:

    2-路划分:

    (1)计算相似度矩阵W和度矩阵D

    (2)计算标准化拉普拉斯矩阵 D12(DW)D12 D − 1 2 ( D − W ) D − 1 2 ​

    (3)取第二小的特征值对应的特征向量,选取一个阈值对其划分,大于该阈值所对应的数据点为一类,小于该阈值所对应的数据点为另一类

    2-路划分算法要求对划分得到的两个社区再分别进行上述(1)(2)(3)步骤进行划分,如此层次化地不断迭代直至满足已设定的收敛条件

    k-路划分:

    (1)计算相似度矩阵W和度矩阵D

    (2)计算标准化拉普拉斯矩阵 D12(DW)D12 D − 1 2 ( D − W ) D − 1 2

    (3)从第二小的特征值开始找k’个最小的特征值对应的特征向量构造 n×k n × k ′ 维度的特征矩阵F

    (4)对特征矩阵F按行进行标准化后,进行Kmeans聚类得到 k k ′ 个簇

    (5)在这 k k ′ 个簇中,每次选取两个簇进行合并,直到最后剩下k个簇,选取的策略是最小化Ncut时的合并组合

  2. 算法实现

    (1)构建相似度矩阵W和计算度矩阵D;由于有每个节点的特征向量feature vector,故而节点之间的相似性可以通过对应特征向量的余弦距离度量出来;度矩阵中,一个节点的度实则是其连接的所有边的权重之和,故度矩阵为对角矩阵,只有主对角线上有值,且值为相似度矩阵W中每行数据值之和:

    %构建相似度矩阵
    W = zeros(length(A),length(A));
    for i = 1:length(A)
       for j = 1:i-1
           W(i,j) = 1-pdist2(F(i,:),F(j,:),'cosine'); %余弦距离
           W(j,i) = W(i,j);
       end
    end
    %构建度矩阵
    D = eye(length(A));
    D = D .* sum(W,2); %根据相似度计算度矩阵

    (2)计算标准化拉普拉斯矩阵;由度矩阵D和相似度矩阵W可得拉普拉斯矩阵L,再计算 D12LD12 D − 1 2 L D − 1 2 对拉普拉斯矩阵进行标准化:

    %得到标准化拉普拉斯矩阵
    L = D-W;
    L = D^(-.5)*L*D^(-.5);

    (3)构建特征矩阵;计算标准化拉普拉斯矩阵的特征值和特征向量,从第二小的特征值开始选取 k k ′ 个最小的特征值对应的特征向量组成特征矩阵:

    %得到特征矩阵
    [eigVector,eigvalue] = eig(L); %特征向量矩阵 & 特征值矩阵
    [eigvalue,index] = sort(diag(eigvalue));
    FM = eigVector(:,index(2:KK+1));
    FM = mynormalize(FM); %按行标准化

    其中,mynormalize()函数是我自己写的一个对特征矩阵F按行进行z-score标准化的函数,目的是消去同一数据点不同维度上量纲的差别:

    %按行进行标准化
    function X = mynormalize(X)
    for i = 1:length(X)
       X(i,:) = ( X(i,:)-mean(X(i,:)) )/ std(X(i,:));
    end

    (4)对特征矩阵聚成 k k ′ 个簇;经由第(3)步后,实则对原数据集做了特征提取,聚成 k k ′ 个簇也是属于对原最小化Ncut k-路划分问题的预处理:

    %对特征矩阵进行聚类
    rng('default'); %保证实验可重复性
    CC = kmeans(FM,KK); % 默认距离 -欧式 默认初始方式 -Kmeans++ 默认最大迭代次数 -100

    (5)不断选取两个簇合并直至剩下k个簇,选取哪两个簇进行合并取决于 此时哪两个簇合并得到的Ncut值最小,Ncut的计算: Ncut=cut(A1,VA1)assoc(A1,V)+cut(A2,VA2)assoc(A2,V)+ N c u t = c u t ( A 1 , V − A 1 ) a s s o c ( A 1 , V ) + c u t ( A 2 , V − A 2 ) a s s o c ( A 2 , V ) + ⋅ ⋅ ⋅

    %合并策略
    while KK~=K
       Nmin = 100000000; combine = [-1, -1];
    
       %所有合并组合情况
       for i = 1:KK
           for j = 1:i-1
               Ccopy = CC;
               Ccopy(Ccopy==i) = j; %拟合并
               Ccluster = unique(Ccopy); %合并后的所有类标
    
               Ncut = 0; %合并后的Ncut
               for l = 1:length(Ccluster) %遍历合并后的所有类标
                   Ncut = Ncut + ( sum(sum(W(Ccopy==Ccluster(l),Ccopy~=Ccluster(l)))) /  sum(sum(W(Ccopy==Ccluster(l),:))) );
               end
               if Ncut%最小化Ncut的合并组合方式
                   Nmin = Ncut;
                   combine = [i,j];
               end
           end
       end
       CC(CC==combine(1)) = combine(2); %真实合并
       KK = KK-1;
    end
  3. 算法效果

    测试数据集为给定的四个数据集Cornell、Texas、Washington、Wisconsin,分别使用邻接矩阵A和使用特征向量之间的余弦距离构造相似度矩阵W上得到的purity纯度如下表所示:

相似度矩阵 Cornell Texas Washington Wisconsin
邻接矩阵 0.4462 0.5508 0.4913 0.4717
cos(特征向量)构造矩阵 0.5795 0.6952 0.6783 0.5774

可以看到,简单地使用邻接矩阵直接作为相似度矩阵不如使用特征向量构造的矩阵效果来得好;因此,我考虑在相似度的度量上做一些尝试,当然结果也和预期的一样,简单地变换相似度度量为欧氏距离的倒数或者街区距离的倒数效果都没有余弦距离来得好,这是由于在高纬度中(特征向量的特征维度有1703维)余弦距离的表现一般优于欧式距离和街区距离,余弦距离只考虑向量之间的夹角而忽略向量本身的大小。

考虑到Normalized Cut和谱聚类之间千丝万缕的联系,我打算采用谱聚类中常用的相似度度量方式:全连接度量模式下使用高斯核函数,我将两个数据点计算得到的余弦距离cosine进行如下处理: wij=ecosine2σ2 w i j = e − c o s i n e 2 σ 2 作为这两个数据点的相似度度量,其他算法实现部分不变,得到的效果见下表的第一行数据,各个数据集上都能有所提升,不过由于引入超参数 σ σ ,也引入了调参这一工作;

变化 Cornell Texas Washington Wisconsin
高斯核函数 0.6667 0.7433 0.7087 0.7057
去掉合并策略 0.6821 0.7487 0.6913 0.6679

此外,鉴于图聚类本身具备聚类的属性,且Normalized Cut和谱聚类之间的连续,它们的算法思想和步骤较为类似,故而我去掉最后显式地最小化Normalized Cut合并策略的那一步骤,即算法实现中的(5),令 k=k k ′ = k k k ′ 就等于我想要得到的簇个数,得到 n×k n × k ′ 的特征矩阵后之间通过Kmeans聚类得到最后的簇划分结果;实则算法实现即为谱聚类Spectral Clustering,而谱聚类中本身对标准化拉普拉斯矩阵进行求解特征值和特征向量即在最小化Normalized Cut,故而从理论层面也解释得通,而这么做了后算法得效果我们可以看上表得第二行数据:

可以看到,整体上效果并没有太大的差别,这是因为1)谱聚类算法中对标准化拉普拉斯矩阵进行求解特征值和特征向量的做法得到的为近似解;2)显式地最小化Ncut中合并策略为贪心策略,也无法保证其为最优解;故而两者都有误差都为近似解,但前者在大数据少社群的数据中会表现得不如后者好,这是因为前者在大数据上运行时算法最后仅仅依靠少量的k个特征向量进行簇划分,而后者可先通过前者预处理得到k’个簇后再显式地进行最小化Ncut,利用了更多的数据量也保留了更多的维度信息。

Louvain

  1. 算法解析

    Louvain算法是层次聚类的图聚类算法,是论文《Fast unfolding of communities in large networks》提出的一种基于模块度的社区发现算法。社区划分(亦或图聚类)的好坏是较难评估的,而Newman等人为了更好地评估社区划分的效果,提出了模块度(modularity)的概念。

    模块度,就是网络中连接社区结构内部顶点的边所占比例,减去在同样的社团结构下任意连接这两个节点的比例的期望值,其数学定义如下: Q=12mi,j[Aijkikj2m]δ(ci,cj) Q = 1 2 m ∑ i , j [ A i j − k i k j 2 m ] δ ( c i , c j )

    其中 Aij A i j 表示节点i和节点j之间的权重, m=12i,jAij m = 1 2 ∑ i , j A i j 表示整个网络所有的权重之和,而 ki=jAij k i = ∑ j A i j 表示节点i连接的边的权重之和, ci c i 表示节点i被分到的社区, δ(ci,cj) δ ( c i , c j ) ci=cj c i = c j 时取值为1,否则取值为0

    上述模块度公式可转写简化为 Q=c[in2m(tot2m)2] Q = ∑ c [ ∑ i n 2 m − ( ∑ t o t 2 m ) 2 ]

    其中 in ∑ i n 表示的是社区c内部边的权重,而 tot ∑ t o t 表示的是与社区c中点连接的边的权重,包括内部的边和外部的边。

    而论文《Fast unfolding of communities in large networks》中提出的算法(我们称之为Louvain)便是基于上述模块度进行的,其思想是最大化模块度,采用启发式的贪心的算法,每次合并时选取能使模块度最大化的合并策略,如图所示主要分为两个过程:

    ①Modularity Optimization,主要是将每个节点划分到与其邻接的节点所在的社区中,以使得模块度的值不断变大–对应下面算法步骤(2);

    ②Community Aggregation,主要是将第一步划分出来的社区聚合成为一个点,即根据上一步生成的社区结构重新构造网络–对应下面算法步骤(3);

    Louvain算法步骤如下:

    (1)初始化每个数据点为一个社区;

    (2)对每个数据点,尝试加入其邻居所在的社区,计算比较加入前后的模块度增益 ΔQ Δ Q ,选出增益最大的那个邻居社区,若其对应的增益 ΔQ>0 Δ Q > 0 ,则该数据点加入这个社区,否则不改变其原来社区划分;

    (3)将得到的社区视为一个节点,社区内节点之间边权重转化为新节点环的权重,社区间的边权重转化为新节点间的边权重;

    (4)重复(2)(3)步骤,直至满足收敛条件。

    收敛条件可以是迭代了一定的次数,亦或是模块度不再增加。

  2. 算法实现

    (1)初始化每个数据点为一个社区:

    C = 1:length(A); %类标 初始化为各自不同社区

    (2)计算模块度的函数,由于代码中多次需要计算模块度的值,故封装一个函数来调用以减少重复的代码编写,计算公式采用的是简化后的模块度计算 Q=c[in2m(tot2m)2] Q = ∑ c [ ∑ i n 2 m − ( ∑ t o t 2 m ) 2 ]

    function modularity = modular(C,A,m)
    
    current_com = unique(C); %社区
    modularity = 0; %模块度
    for i = 1:length(current_com)
       member = C==current_com(i); %成员
       outer = C~=current_com(i); %非成员
       current_out = sum(sum( A(member,outer) )); %外部总边权
       current_in = sum(sum( A(member,member) ))/2; %内部总边权
       current_tot = current_in + current_out; %总边权
       modularity = modularity + ( current_in/(2*m) - current_tot/(2*m) * current_tot/(2*m) );
    end

    (3)对每个数据点,尝试加入其邻居所在的社区,计算比较加入前后的模块度增益 ΔQ Δ Q ,记录最大的模块度增益max和它对应的社区max_c,若最大增益max>0,则将该数据点加入对应的社区max_c:特别地,这里的数据点既指单个节点,也指一个社区,故统一按社区视为数据点来处理,只是单个数据点算只有一个节点的社区罢了

    %遍历每个社区 判断是否合并
    for i = 1:length(community)
       neighbor= zeros(1,length(A)); %邻居
       communitymember = find(C==community(i)); %社区成员
    
       for j = 1:length(communitymember)
           neighbor( A(communitymember(j),:)==1 )=1; %找所有邻居
       end
       neighbor(communitymember)=0; %除去社区成员
    
       cluster = unique(C(neighbor==1)); %邻居的社群
       rng('default'); %保证实验可重复性
       cluster = cluster( randperm(length(cluster)) ); %随机打乱顺序
    
       old_modularity = modular(C,A,m); %原模块度
       max = 0; max_c = -1;
       %遍历各个相邻社群
       for j = 1:length(cluster) 
           Ccopy = C;
           Ccopy(communitymember) = cluster(j); %假设合并
           new_modularity = modular(Ccopy,A,m); %新模块度
           delta = new_modularity - old_modularity; %模块度增益
    
           %记录增益最大的
           if delta > max 
               max = delta;
               max_c = cluster(j);
           end
       end
    
       %有增益 加入该社区
       if max > 0 
           C(communitymember) = max_c;
       end
    end

    (4)终止条件:模块度不再增加,停止迭代聚类

    %终止条件
    current_m = modular(C,A,m);
    if current_m <= past_m %模块度不再增加
       break;
    end
    past_m = current_m;
  3. 算法效果

    这里本人尝试了①只用邻接矩阵计算边权重(边权=1)和②使用特征向量计算节点之间的相似度作为边权两种方案,发现效果有些许提升,高斯核函数的参数没有过多的尝试,而其对相似度度量的影响不言而喻。高斯核函数的数学公式是: wij=exixj222σ2 w i j = e − ‖ x i − x j ‖ 2 2 2 σ 2 ,其中 σ σ 为超参数

    以下为两种策略在四个数据集上的效果:

边权度量 Cornell Texas Washington Wisconsin
邻接矩阵度量 0.4815 0.5668 0.5652 0.5132
特征向量构造高斯核 0.5485 0.6462 0.6096 0.5849

NMF

  1. 算法解析

    非负矩阵分解,简称NMF,是Daniel D. Lee等人在《Nature》上发表的一篇《Learning the parts of objects by non-negative matrix factorization》论文中提到的方法,其从发表至今已经应用在多个领域中,诸如特征学习、图像分析、话题识别、语音处理、时序分割等,可参考博客https://blog.csdn.net/qq_26225295/article/details/51211529中对NMF应用的概述部分。

    而NMF之所以能得到这么广泛的应用,显然和NMF算法的特性相关,科学研究和实际应用中都有很多方法需要通过矩阵形式对大规模数据进行处理和分析,而NMF则为大规模数据矩阵形式的处理提供了一种新的途径:对大规模数据,NMF一方面能够通过矩阵分解对原数据进行维度约简,另一方面也可以对大量的数据进行压缩和概括。

    NMF算法的基本思想可以这么认为,如上图所示,对于一个给定的非负矩阵 ARn×n A ∈ R n × n ,可以找到非负矩阵 URn×k U ∈ R n × k 和非负矩阵 VRn×k V ∈ R n × k ,使得 UVT U V T 的乘积等于A,其中U可以认为是“代表”矩阵,V可以认为是“权重”矩阵亦或是基矩阵。通过这样的非负矩阵分解,我们就找到了维度降低后的压缩代表矩阵U,而通过U我们就可以做一些我们想做的操作,比如在这里的,图聚类。

    根据上述的NMF思想,我们的目标就应该是最小化目标函数 AUVT2F ‖ A − U V T ‖ F 2 ,使用梯度下降法求解该目标函数,可以得到对U和V的更新公式:(其中, α α 表示学习率)

    Uij=αUij(AV)ij(UVTV)ij U i j = α ∗ U i j ( A V ) i j ( U V T V ) i j Vij=αVij(ATU)ij(VUTU)ij V i j = α ∗ V i j ( A T U ) i j ( V U T U ) i j

    而对于目标函数收敛后得到的U,我们可以根据其每行(对应每个数据点)的最大值对应的列数,得到该数据点对应的类标(即将它分配给这个社区),故而可以得到如下NMF算法的算法步骤:

    (1)初始化U、V矩阵(非负)

    (2)根据U、V的更新公式异步迭代更新

    (3)满足终止条件后终止更新U、V

    (4)找到U中每个数据点最大值对应的列数,分配其作为类标

  2. 算法实现

    (1)初始化非负矩阵U、V:

    %随机初始化UV矩阵
    N = length(A); %数据个数
    rng(10); %保证实验可重复性
    U = rand(N,K); %N*K非负矩阵
    rng(3);
    V = rand(N,K);

    (2)根据公式 Uij=αUij(AV)ij(UVTV)ij U i j = α ∗ U i j ( A V ) i j ( U V T V ) i j 以及 Vij=αVij(ATU)ij(VUTU)ij V i j = α ∗ V i j ( A T U ) i j ( V U T U ) i j 异步迭代更新矩阵U、V:

    %迭代更新UV矩阵
    iteration = 0;
    while true
       U_temp = U; V_temp = V; %防止同步更新
       AV = A*V; UVV = U*V'*V;
       AU = A'*U; VUU = V*U'*U;
       %U
       for i = 1:N
           for j = 1:K
               U_temp(i,j) = U(i,j)*AV(i,j)/UVV(i,j); %异步更新
           end
       end
       %V
       for i = 1:N
           for j = 1:K
               V_temp(i,j) = V(i,j)*AU(i,j)/VUU(i,j); %异步更新
           end
       end
       %更新UV
       U = U_temp; V = V_temp; 
       iteration = iteration+1;
    
       %终止条件
       if iteration >= 100
           break;
       end
    end

    (3)找到U中每个数据点最大值对应的列数,分配其作为类标:

    %分配类标
    [mvalue,midx] = max(U,[],2); %U每一行最大值
    C = midx; 
  3. 算法效果

    本人分别尝试了以下两种策略来得到最后的图划分结果:1)直接将U中每个数据点最大值对应的列数作为类标进行分配,2)将得到的“代表”矩阵U看作是降维后得到的特征矩阵,再对其采用Kmeans进行聚类

    两种策略在四个数据集上的效果如下:

处理U的策略 Cornell Texas Washington Wisconsin
选取最大值对应类标进行分配 0.4821 0.6578 0.6096 0.5509
对U进行Kmeans聚类 0.4667 0.6462 0.6478 0.5208

可以看到两种策略效果差别不大,这可以说明除了一般的方法,策略1)选取最大值对应类标进行分配以外,策略2)Kmeans对U进行聚类处理也是行得通的,这更进一步说明了NMF非负矩阵分解得到的“代表”矩阵U是有效的,是经过降维后的对原矩阵的有效压缩,个人认为也可以看作对原network表示(即邻接矩阵A)的一种较为简单有效的embedding方式。

你可能感兴趣的:(数据挖掘)