对聚类经典文献(DEC——无监督的深度嵌入式聚类)的学习(续)

目录

前言

一、对论文《无监督的深度嵌入式聚类》(DEC)相关知识的学习

(一)论文简介:

(二)相关知识点:

        1.KL散度

        2.软分配

        3.K-means算法

        4.AE(自编码器)

        5.反向传播算法

二、对DEC代码的学习

1.dec.py

2.evaluation.py

总结:


前言

本文主要是本人的学习心得,包括以下两个方面:

一、对论文《无监督的深度嵌入式聚类》(DEC)相关知识的学习;

二、对DEC代码的学习。


一、对论文《无监督的深度嵌入式聚类》(DEC)相关知识的学习

(一)论文简介

聚类对于许多数据驱动的应用至关重要,并且已在距离函数和分组算法方面进行了广泛的研究。在聚类表示学习方面相对的研究较少。在本论文中,我们提出了深度嵌入式聚类(DEC),一种使用深度神经网络同时学习特征表示和聚类分配的方法。DEC学习了从数据空间到低维特征空间的映射,迭代地优化了聚类目标。我们对图像和文本语料库的实验评估表明,与现有技术相比,该方法有显着改进。

(二)相关知识点:

        1. KL散度

KL(Kullback-Leibler)散度,也称为相对熵,是度量两个概率分布之间差异的一种指标。在信息理论中,KL散度用来衡量两个概率分布之间的信息损失,或者说是两个概率分布之间的距离。

假设有两个概率分布P和Q,KL散度定义为: P是真实概率分布,Q是推测的概率分布。

$KL(P||Q)=\sum_{i}^{n}P(i)*\log (\frac{P(i)}{Q(i)})$

其中,P(i)和Q(i)分别表示P和Q在第i个事件上的概率。KL散度的值越大,表示两个概率分布之间的差异越大;值为0表示两个概率分布完全相同。

KL散度常用于概率模型之间的比较和评估,例如在机器学习中用于衡量两个概率分布的相似性,或者用于评估生成模型的效果等。

        2.软分配

软分配(soft assignment)是一种数据聚类算法中常用的方法,它与硬分配(hard assignment)不同,可以将一个数据点分配到多个簇中,而不是只分配到一个簇中。

在软分配算法中,数据点与簇之间的关系不是二元的,而是通过一组权重来表示的。具体来说,对于一个数据点和多个簇,软分配算法会计算每个簇与该数据点的相似度,并将相似度转化为权重,表示该数据点被分配到该簇的概率。因此,一个数据点可能被分配到多个簇,其分配权重之和为1。

例如:

        3.K-means算法

K-Means算法的目标是最小化数据点与其所属簇的聚类中心点之间的距离之和(通常使用欧氏距离)。这个目标函数被称为簇内平方和(SSE,Sum of Squared Errors)。K-Means算法通过迭代更新聚类中心点和重新分配数据点来逐步优化SSE,以达到更好的聚类效果。

需要注意的是,K-Means算法对初始聚类中心点的选择敏感,因此不同的初始点可能会得到不同的聚类结果。为了克服这个问题,通常会进行多次运行,并选择具有最小SSE的结果作为最终聚类结果。

步骤+例子:

1、首先定义有多少个类/簇
2、将每个簇的簇心随机选在一个点(x,y)上
3、将每个数据点(均有(x,y))关联到距离最近的簇心上,即计算每个数据点到各个簇心的距离,到哪个簇心距离小,则属于哪个簇。计算每个簇有多少个数据点。
4、将各个簇中的数据点 坐标取平均值,选为新的簇心
5、再次计算每个数据点到新簇心的距离,根据距离小为依据,重新判断每个数据点所属簇,再次统计簇中的个数,直到每个簇拥有的点的个数不变

        4.AE(自编码器)

编码器-解码器(encoder-decoder)结构通常被用来实现自编码器(Autoencoder),也就是常说的 AE。其中,编码器将输入数据映射到低维空间中的编码表示,而解码器通过从编码中重构后的原始数据来产生输出。这个过程可以通过训练自编码器来学习数据的压缩表示和重建能力,从而实现特征提取、数据降维、数据去噪等任务。

整个过程如图所示:

 最简单的自动编码器如图所示:

        5.反向传播算法

AE(Autoencoder)自编码器和反向传播算法的关系:

Autoencoder是一种无监督学习算法,用于学习输入数据的低维表示。它由两部分组成:编码器(Encoder)和解码器(Decoder)。编码器将输入数据映射到低维的编码表示,而解码器则将编码表示映射回原始输入空间,以重构输入数据。

在训练过程中,Autoencoder的目标是最小化重构误差,即使解码器能够尽可能地还原输入数据。为了实现这一目标,反向传播算法被广泛应用于Autoencoder的训练中。

反向传播算法是一种基于梯度的优化算法,用于调整神经网络中的权重和偏置,使其能够最小化损失函数。在Autoencoder的训练过程中,反向传播算法通过计算重构误差的梯度来更新编码器和解码器的参数。具体地,首先计算重构误差(通常使用均方误差)和编码器输出之间的差异,然后使用反向传播算法计算梯度并将其传播回网络,从而更新网络的权重和偏置,以最小化重构误差。

通过反向传播算法的迭代优化,Autoencoder能够学习到一组有效的编码表示,这些表示能够保留输入数据的关键特征并减少冗余信息。这种学习过程是通过最小化重构误差来实现的,而反向传播算法是计算并应用梯度的关键步骤,使得网络能够逐步调整参数以提高重构的准确性。因此,反向传播算法在训练Autoencoder中起着重要的作用。

具体详细讲解可点此处:反向传播算法过程

二、对DEC代码的学习

首先先了解DNN和AE的区别:

DNN 和 AE 算法是相关但不完全相同的概念。

AE(Autoencoder)是一种无监督学习算法,它由编码器和解码器两部分组成,可以将高维输入数据压缩为低维编码表示,然后再通过解码器重构出与原始输入相似的输出结果。AE 可以用于特征提取、数据压缩、去噪等任务,在某些情况下也可作为生成模型使用。

DNN(Deep Neural Network)是一类神经网络模型,通常由多层神经元组成,每层神经元将上一层的输出作为输入,通过非线性变换和激活函数将其转化为下一层的输入,从而实现对输入数据的表征学习和分类任务等。

DNN 可以使用 AE 作为其中的编码器或解码器,来进行自编码预训练(Pre-training)以及特征学习,从而提高模型的性能。但是 DNN 并不仅限于使用 AE,它还包括了其他类型的网络结构和学习方法,如 CNN、RNN、GAN 等。因此,虽然 AE 是 DNN 的一种组件,但是它们并不是完全相同的概念。

1.dec.py

        1.1 AE类:代码如下

class AE(nn.Module): #定义了一个名为 AE 的自编码器类,继承自 nn.Module 类,以便可以使用 PyTorch 框架中的各种功能和特性
    #三个编码器层 和 三个解码器层 每个层都由一个全连接线性层和rule激活函数组成
    def __init__(self, n_enc_1, n_enc_2, n_enc_3, n_dec_1, n_dec_2, n_dec_3,
                 n_input, n_z):
        super(AE, self).__init__()
        #encode 编码器
        #全连接线性层:  y=Wx+b  x是输入向量   W是权值矩阵  b是偏置向量 y是输出向量
        #“可学习”是指神经网络中的参数(如权重和偏置)可以通过反向传播算法自动调整,以最小化训练数据上的损失函数。
        #每一个输入特征都和所有输出特征相连(即全连接) 全连接层:就是将输入的每个特征都与一组可学习的权值相乘,再加上一个偏置值。 得到一个新向量表示。
        self.enc_1 = Linear(n_input, n_enc_1)   #self.enc_1 2 等是一个Linear层对象
        self.enc_2 = Linear(n_enc_1, n_enc_2)
        #这段代码中的参数是神经网络中的层参数,包括输入维度(n_enc_1)和输出维度(n_enc_2)。self.enc_2表示这是一个全连接层(Linear),它将输入向量的维度从n_enc_1转换为n_enc_2。
        # 在神经网络训练过程中,这些参数会被反向传播算法自动学习,以优化模型的性能。

        self.enc_3 = Linear(n_enc_2, n_enc_3)
        self.z_layer = Linear(n_enc_3, n_z)
        #self.enc_1 = Linear(n_input, n_enc_1)  定义了一个名为 self.enc_1 的对象,即第一层编码器
        # 可以自动执行矩阵乘法和偏置相加操作
        #self.z_layer = Linear(n_enc_3, n_z):定义一个输入维度为 n_enc_3,输出维度为 n_z 的线性层作为编码层。


        #decode 解码器
        self.dec_1 = Linear(n_z, n_dec_1)
        self.dec_2 = Linear(n_dec_1, n_dec_2)
        self.dec_3 = Linear(n_dec_2, n_dec_3)
        self.x_bar_layer = Linear(n_dec_3, n_input)

    #定义了前向传播函数
    def forward(self, x):
        #输入一个X 输出相应的状态
        #右边:编码器的输入  等号左边为编码器的输出
        #relu激活函数: 输入值x >=0 则输出x 否则输出0    f(x)=max(0,x) 将负值归为0 正值结果不变

        enc_h1 = F.relu(self.enc_1(x))
        enc_h2 = F.relu(self.enc_2(enc_h1))
        enc_h3 = F.relu(self.enc_3(enc_h2))
        #在 forward 函数中,通过调用 self.enc_1(x) 方法,将输入数据 x 传递给第一层编码器进行线性变换。 其它相同
        #self.enc_1 对象的创建与调用 self.enc_1(x) 方法密切相关,是调用该方法的前提条件

        #最终输出
        z = self.z_layer(enc_h3)
        #将第三层编码器的输出作为输入,通过编码层进行线性转换得到编码 z。


        #把编码器的输出作为解码器的输入  ->变成解码器的状态
        dec_h1 = F.relu(self.dec_1(z))
        # 将编码 z 作为输入,通过第一层解码器进行线性转换,并经过ReLU激活函数得到第一层解码器的输出 dec_h1。
        dec_h2 = F.relu(self.dec_2(dec_h1))
        dec_h3 = F.relu(self.dec_3(dec_h2))

        #最终输出
        x_bar = self.x_bar_layer(dec_h3)
        #将第三层解码器的输出作为输入,通过重构层进行线性转换得到重构数据 x_bar。
        return x_bar, enc_h1, enc_h2, enc_h3, z
 

         1.2 SDCN类:

SDCN代表的是"Structural Deep Clustering Network",是一种深度学习算法,用于无监督学习中的聚类任务。它使用自编码器和目标函数联合训练来有效地学习数据的特征表示,并在此基础上执行聚类操作。SDCN模型能够同时优化特征提取和聚类任务,具有较高的聚类精度和鲁棒性。该模型最初由Xiaowei Zhou等人在2017年提出。

对于部分代码的解释:

其中参数v=1的作用:

在聚类算法中,通常需要对数据点和聚类中心之间的距离进行度量,其中一个常见的距离度量是欧几里得距离。然而,欧几里得距离受到不同特征的尺度影响较大,因此可能不适用于所有情况。

在 SDCN 算法中,作者引入了参数 v 来控制度量距离时各个特征之间的权重,从而使距离度量更加鲁棒。具体来说,将原始数据 x 归一化为单位范数,然后在计算距离时使用带权重的欧几里得距离,其中每个维度上的权重由参数 v 控制,即对于两个数据点 x_i 和 x_j ,它们之间的距离可以表示为:

当 v=1 时,距离度量就是标准的欧几里得距离;当 v<1 时,表示某些特征比其他特征更重要;当 v>1 时,表示某些特征比其他特征更不重要。

代码如下:

#SDCN聚类算法
class SDCN(nn.Module):
    #SDCN是一种基于自编码器的聚类算法
    #SDCN 主要思想是利用自编码器学习数据点的低维表达,并通过双重自监督方法进行聚类。
    # 具体地,SDCN 由两部分组成:自编码器和双重自监督模块
    #AE部分如上  双重自监督块则通过将数据点与聚类中心之间的距离转化为相似度,以双重自监督方式训练网络,从而实现聚类任务
    def __init__(self, n_enc_1, n_enc_2, n_enc_3, n_dec_1, n_dec_2, n_dec_3,
                 n_input, n_z, n_clusters, v=1):
        super(SDCN, self).__init__()


        #AE部分
        # autoencoder for intra information
        self.ae = AE(
            n_enc_1=n_enc_1,
            n_enc_2=n_enc_2,
            n_enc_3=n_enc_3,
            n_dec_1=n_dec_1,
            n_dec_2=n_dec_2,
            n_dec_3=n_dec_3,
            n_input=n_input,
            n_z=n_z)
        #self.ae 该属性是AE自编码器的对象
        #其各层的维度和超参数由输入参数指定

        # 从预训练模型文件中加载预训练权重并将其设置为自编码器 ae 的权重。
        self.ae.load_state_dict(torch.load(args.pretrain_path, map_location='cpu'))
        #从指定路径 args.pretrain_path 中加载预训练模型的权重参数,并将其设置为自编码器 self.ae 的权重
        #torch.load(args.pretrain_path, map_location='cpu') 函数用于从文件中加载对象,然后将其返回。
        #加载了一个之前保存的预训练模型文件,该文件包含了自编码器的权重和偏置参数。加载时,使用 map_location='cpu' 指定运行设备为 CPU
        #加载完毕后,通过 self.ae.load_state_dict() 方法将预训练模型的权重设置为自编码器 self.ae 的权重

        # cluster layer
        self.cluster_layer = Parameter(torch.Tensor(n_clusters, n_z))  #将一个张量定义为可学习Parameter变量
        #定义一个名为 self.cluster_layer 的可学习变量(即是一个 Parameter 对象),其大小为 (n_clusters, n_z),即聚类层的权重矩阵。
        #使用 Parameter 函数将一个张量封装成可训练的变量,并将其添加到模型中(self.)。
        #由于它是一个可学习变量,所以在反向传播时会自动计算梯度并更新参数
        #torch.Tensor(n_clusters, n_z) 用于创建一个大小为 (n_clusters, n_z) 的张量,它包含了聚类层的权重参数  n_z 是自编码器的输出维度
        #张量(tensor)是一种多维数组  在模型训练时,该参数会根据输入数据和损失函数不断更新,最终用于表示每个聚类的中心向量。
        # 这里使用 PyTorch 的 Parameter 函数将其封装成可训练的变量。

        torch.nn.init.xavier_normal_(self.cluster_layer.data)
        #对聚类层的权重矩阵进行 Xavier 初始化,即从均匀分布中随机采样权重值并乘以一个初始化因子。
        #Xavier 是一种常用的权重初始化方法,它根据输入和输出维度自适应地调整权重分布的方差,
        # 以避免梯度消失或梯度爆炸的问题,从而提高模型的性能和收敛速度
        #使用 torch.nn.init.xavier_normal_() 函数来实现 Xavier 初始化。
        # 具体地,self.cluster_layer.data 表示聚类层权重矩阵的数据,即一个形状为 (n_clusters, n_z) 的张量对象
        #调用 torch.nn.init.xavier_normal_(self.cluster_layer.data) 函数会对该张量进行 Xavier 初始化,并将结果保存回张量中


        # degree  设置自编码器的度数参数 v
        self.v = v

        #这段代码的作用是设置自编码器的度数参数 v。SDCN 算法中,双重自监督模块需要用到一个度数参数 v,
        # 用于计算数据点到聚类中心之间的距离。具体地,该度数参数 v 通常取值为 1 或 2,其越小则表示聚类中心之间的距离影响越大;
        # 反之则表示数据点之间的距离影响越大。在 SDCN 类的初始化函数中,通过 self.v = v 将输入参数 v 赋值给实例变量 self.v,
        # 即将度数参数 v 设置为输入参数 v,以便在后续的双重自监督模块中使用。
        #在 SDCN 算法中,作者引入了参数 v 来控制度量距离时各个特征之间的权重,从而使距离度量更加鲁棒。具体来说,将原始数据 x 归一化为单位范数,
        # 然后在计算距离时使用带权重的欧几里得距离,其中每个维度上的权重由参数 v 控制,即对于两个数据点 x_i 和 x_j ,它们之间的距离可以表示为:
        #d(x_i, x_j) = sqrt(sum((v * (x_i - x_j))^2))
        # 当 v=1 时,距离度量就是标准的欧几里得距离;当 v<1 时,表示某些特征比其他特征更重要;当 v>1 时,表示某些特征比其他特征更不重要。

    def forward(self, x):
        #前向传播过程,输入原始数据 x ,通过自编码器 ae 进行编解码操作,得到重构结果 x_bar 和编码表达 z 。
        #同时,通过双重自监督模块计算样本点和聚类中心之间的相似度 q ,并将其标准化为概率分布形式,作为输出结果返回
        # DNN Module
        x_bar, tra1, tra2, tra3, z = self.ae(x)
        #将输入数据 x 通过自编码器 ae 进行编码和解码,并返回重构结果 x_bar 和编码器每一层的输出 tra1、tra2、tra3,以及最终的编码表达 z。

        # Dual Self-supervised Module  双重自监督模块
        #这一步是软分配
        #将点到聚类中心的距离转换为相似度
        q = 1.0 / (1.0 + torch.sum(torch.pow(z.unsqueeze(1) - self.cluster_layer, 2), 2) / self.v)  #里面是计算欧式距离
        #这段代码是 SDCN 算法中的双重自监督模块,用于计算数据点到聚类中心之间的相似度矩阵 q
        #该模块通过将数据点与聚类中心之间的距离转化为相似度来判断它们是否属于同一类别
        #z 表示编码器的输出向量,即数据点经过编码器后得到的低维表达;
        # self.cluster_layer 表示聚类层的权重矩阵,其大小为 (n_clusters, n_z);
        # self.v 表示度数参数,通常取值为 1 或 2。
        #z.unsqueeze(1):将编码器的输出向量 z 在第二个维度上进行扩展,变成一个形状为 (batch_size, 1, n_z) 的三维张量。
        #z.unsqueeze(1) - self.cluster_layer:将聚类中心的权重矩阵 self.cluster_layer 广播成与 z 相同的形状,
        # 并与 z.unsqueeze(1) 相减,得到一个形状为 (batch_size, n_clusters, n_z) 的差值张量,表示每个数据点与所有聚类中心之间的距离。
        #torch.pow(z.unsqueeze(1) - self.cluster_layer, 2):对差值张量中的每个元素进行平方操作,得到一个形状为 (batch_size, n_clusters, n_z) 的平方张量。

        #首先通过torch.pow计算出数据点和每个聚类中心之间的欧式距离平方,并将其除以参数self.v(这里v的作用是调节聚类簇之间的相似度)
        #torch.sum(torch.pow(z.unsqueeze(1) - self.cluster_layer, 2), 2):对平方张量沿着第三个维度(即 n_z 维度)进行求和,
        # 得到一个形状为 (batch_size, n_clusters) 的距离矩阵,表示每个数据点到所有聚类中心的距离。
        #1.0 / (1.0 + torch.sum(torch.pow(z.unsqueeze(1) - self.cluster_layer, 2), 2) / self.v):
        # 将距离矩阵除以度数参数 self.v 并加上常数 1,然后取倒数,得到一个形状为 (batch_size, n_clusters) 的相似度矩阵 q。
        # 其中,q[i][j] 表示第 i 个数据点与第 j 个聚类中心之间的相似度。

        #这一步计算的就是样本点和聚类中心之间的相似度,也称为“软分配”(soft assignment),具体地,使用了高斯核函数来度量样本和聚类中心之间的距离。
        #其中 z 是自编码器的输出 (encoder 的输出),表示每个样本的低维嵌入向量;self.cluster_layer 是聚类层参数,表示每个聚类的中心向量;
        # self.v 是一个超参数,表示高斯核函数的方差。
        # 最终,计算得到的 q 是一个形状为 (batch_size, n_clusters) 的张量,其中每行代表一个样本对于每个聚类的软分配概率,即该样本属于每个聚类的概率。

        q = q.pow((self.v + 1.0) / 2.0)
        #对相似度 q 进行幂运算,增加相似度间的差异性。
        #这段代码用于对q张量进行幂次变换,其目的是增强概率分布的聚集性。该操作将q中每个元素取 (self.v + 1.0) / 2.0 次幂,
        #由于self.v参数通常设置为较小的值,因此该操作会增加数据点与最近聚类中心之间的相似度(即降低离群点的权重),从而更好地表达数据之间的聚合关系。最终得到一个形状
        #仍为(batch_size, n_clusters)的张量q。

        q = (q.t() / torch.sum(q, 1)).t()  #归一化:数据点属于某个聚类的概率  (一行加和为1)
        #将幂运算后的相似度 q 标准化为概率分布形式。
        #这段代码用于对q张量进行归一化操作,其目的是将每个数据点对应各个聚类的软分配概率转换为标准的概率分布。具体来说,该操作首先通过
        #torch.sum(q, 1)沿着第二维度(也就是聚类数目)计算每个数据点对应的累加值,然后使用t()
        #将结果进行转置操作,使得它可以与q张量相除。最终再次使用t()将结果转回原始形状。这样做之后,
        #q张量中的每个元素都表示了对应数据点属于某个聚类的概率,而且每行的概率之和等于1,即成为了标准的概率分布。

        return x_bar, q, z
        #返回重构结果 x_bar、聚类分布 q 和编码表达 z

         1.3定义得出软分配目标分布的函数:

def target_distribution(q):#它的输入是一个二维数组q。该函数的作用是计算q的权重并返回对应的目标分布
                            # 这段代码的目的是计算q的平方值在不同列之间的比例,然后根据这些比例得出q的目标分布
    weight = q ** 2 / q.sum(0)
    return (weight.t() / weight.sum(1)).t()
    #该函数首先计算每一列(轴0)中所有元素的平方并除以这一列中所有元素之和,得到一个新的一维数组weight。
    # 然后,将这个数组转置后再次执行相同的操作,最终得到一个新的二维数组,表示q的目标分布。

         

        1.4定义训练SDCN模型的函数:

def train_sdcn(dataset):
    #定义 SDCN 模型,并初始化模型参数
    model = SDCN(500, 500, 2000, 2000, 500, 500,
                 n_input=args.n_input,
                 n_z=args.n_z,
                 n_clusters=args.n_clusters,
                 v=1.0).to(device)
    print(model)
    #.to(device) 是 PyTorch 中的一个方法,用于将模型或张量转移到指定的设备上运行。在这里,device 是一个表示正在使用的计算设备(如 CPU 或 GPU)的变量。
    # 通过调用 .to(device) 方法,我们可以将 SDCN 模型移动到指定的设备上进行训练和推理。

    #使用 Adam 优化器来优化模型的参数。   ???
    optimizer = Adam(model.parameters(), lr=args.lr)
    #Adam 优化器是一种常用的梯度下降算法变体,它可以自适应地调整每个参数的学习率,从而加速模型的优化过程。在该代码中,使用 Adam 优化器来优化 SDCN 模型的参数。
    #具体来说,程序将模型的参数 model.parameters() 和学习率 args.lr 传递给 Adam 优化器。然后,在每次迭代时,optimizer.step() 函数会根据
    # 当前的梯度信息更新模型的权重和偏置等参数,以最小化损失函数。同时,optimizer.zero_grad() 函数会将模型的梯度清零,以避免梯度累积影响优化效果。
    #因此,通过反向传播计算网络的误差梯度,并利用 Adam 优化器对模型参数进行更新,最终实现了对 SDCN 模型的训练和优化。cluster parameter initiate 聚类参数初始化

    data = torch.Tensor(dataset.x).to(device)  #将输入数据转换为 PyTorch 张量格式,并上传到 相关设备上(GPU)运行环境中。
    y = dataset.y  #获取输入数据的标签信息,并保存到变量 y 中。
    with torch.no_grad():  #利用自编码器对输入数据进行无监督学习,将其映射到低维空间中,并提取嵌入向量 z。
        _, _, _, _, z = model.ae(data)
    #在这一步操作中,由于没有进行反向传播计算梯度,因此使用了 torch.no_grad() 上下文管理器来告诉 PyTorch 不需要计算这部分计算图的梯度。
    #with torch.no_grad(): 是 PyTorch 中的一个上下文管理器(context manager),用于控制梯度计算的开启和关闭。
    # 在 with torch.no_grad(): 的上下文中,所有的张量操作都不会被跟踪,也就是说,不会在计算图中记录它们的操作过程和梯度信息,从而减少内存占用并提高计算效率。
    # 通常情况下,with torch.no_grad(): 语句块中的代码都是一些不需要进行梯度计算的操作,例如模型的推理阶段或者预处理数据等。这样做可以避免占用大量的显存空间,同时加快计算速度。

    kmeans = KMeans(n_clusters=args.n_clusters, n_init=20)
    #创建 KMeans 聚类对象,并设置聚类中心的个数和初始点的数量
    #在进行聚类操作之前,KMeans 算法会产生若干组随机初始点来对聚类中心进行初始化,然后重复执行聚类操作,直到收敛并找到最优的聚类结果为止。
    #n_init 参数控制了随机初始化的次数,每次都使用不同的随机初始点来运行聚类算法,从而克服算法陷入局部最优解的可能性。最终选择使损失函数最小化的那一组聚类结果作为最终结果返回。

    y_pred = kmeans.fit_predict(z.data.cpu().numpy()) #使用 KMeans 算法对嵌入向量进行聚类,并获取聚类结果。
    y_pred_last = y_pred  #将最终聚类结果保存到变量 y_pred_last 中,用于后续判断停止条件。
    model.cluster_layer.data = torch.tensor(kmeans.cluster_centers_).to(device) #将聚类中心作为聚类层参数进行初始化。
    #将 KMeans 聚类算法得到的聚类中心更新到 SDCN 模型的聚类层参数上。
    #具体来说,该操作将聚类中心转换为 PyTorch 张量格式,并上传到 GPU 上(如果模型在 GPU 上运行)。然后,将此参数赋值给 SDCN 模型的聚类层,从而更新聚类中心
    #这里使用 model.cluster_layer.data 访问聚类层的参数(也就是聚类中心),并将其设置为新的聚类中心。
    #由于聚类中心是优化器无法更新的变量,因此需要手动更新聚类中心

    eva(y, y_pred, 'pae')  #计算当前聚类结果与真实标签之间的评价指标。

    for epoch in range(200): #迭代200次
        if epoch % 1 == 0: #如果当前迭代次数可以被 1 整除,则执行以下操作
            # update_interval
            _, tmp_q, _ = model(data) #对输入数据进行前向计算,得到模型的软分配概率 tmp_q
            tmp_q = tmp_q.data
            p = target_distribution(tmp_q)  #调用函数 根据软分配概率 tmp_q 计算目标分布 p。
            #软分配概率(n,k) (k为聚类中心个数)
            res1 = tmp_q.cpu().numpy().argmax(1)  # Q  将软分配概率转换为 numpy 数组类型,并获取其中概率最大的索引作为聚类结果 res1
            #代码中将模型输出的软分配概率 tmp_q 转换为 numpy 数组类型后,
            # 使用 .argmax(1) 方法获取每个样本所属的聚类类别(即概率最大的那一维的索引),并将这些聚类结果保存到变量 res1 中
            #目标分布(n,k)
            res3 = p.data.cpu().numpy().argmax(1)  # P 将目标分布转换为 numpy 数组类型,并获取其中概率最大的索引作为聚类结果 res3
            #调用评价函数 eva(),并将真实标签 y、聚类结果 res1 或 res3,以及当前迭代次数的字符串表示(用于标识不同迭代次数的聚类结果)传递给该函数,
            # 计算并输出聚类结果的评价指标。
            eva(y, res1, str(epoch) + 'Q')
            eva(y, res3, str(epoch) + 'P')

        x_bar, q, _ = model(data)  #对输入数据进行前向计算,得到重构的输出数据 x_bar 和当前的软分配概率 q

        kl_loss = F.kl_div(q.log(), p, reduction='batchmean') #计算当前模型输出的软分配概率 q 和目标分布 p 之间的 KL 散度,并将其作为 KL 散度项 kl_loss。
        #该语句中 F.kl_div() 是 PyTorch 中计算 KL 散度的函数,q.log() 表示取对数,p 表示目标分布,reduction='batchmean' 表示将所有样本的 KL 散度平均成一个标量。
        #计算当前模型输出的软分配概率 q 和目标分布 p 之间的 KL 散度,并将其保存到变量 kl_loss 中。通过最小化 KL 散度损失,SDCN 模型可以训练出更好的聚类效果
        re_loss = F.mse_loss(x_bar, data) #计算当前模型输出的重构数据 x_bar 和输入数据 data 之间的均方误差,并将其作为重构损失项 re_loss。
        #该语句中 F.mse_loss() 是 PyTorch 中计算 MSE 的函数,x_bar 表示模型输出的重构数据,data 表示原始输入数据。通过计算二者之间的平均平方差,得到重构损失项 re_loss,

        loss = kl_loss + re_loss #具体来说,该语句将 KL 散度项 kl_loss 和重构损失项 re_loss 相加,得到模型的总损失值
        #KL 散度项和重构损失项都是损失函数的一部分,它们分别反映了聚类损失和重构损失对模型训练的影响。KL 散度项可以使模型更好地进行聚类,提高聚类效果,
        # 而重构损失项可以保证模型输出的重构数据尽量接近输入数据,从而提高模型的重构能力。
        #通过最小化总损失,SDCN 模型可以不断优化模型参数,使得模型能够更好地学习数据的特征,并完成聚类任务和重构任务,从而提高模型的性能和泛化能力。


        #完成一次 SDCN 模型的反向传播和参数更新,使得模型能够不断地优化自身的参数

        #清空优化器的梯度信息,执行反向传播计算参数的梯度,并使用优化器更新模型参数。
        optimizer.zero_grad() #清空优化器 optimizer 中的梯度信息。
        loss.backward()  #对损失函数 loss 进行反向传播计算参数的梯度。
        # 反向传播通过链式法则求得每个参数对于损失函数的梯度值,然后将其保存在各自的 Tensor 中。这些梯度值即为模型优化所需的方向信号。
        optimizer.step()#使用优化器 optimizer 更新模型参数,以降低损失函数的值并提高模型性能。
        #在反向传播之后,优化器使用预定义的优化算法(如 SGD、Adam 等)根据计算出来的梯度信息来更新模型的参数。

        1.5配置参数并加载相应数据集:

if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description='train', #设置该命令行程序的简短描述。
        formatter_class=argparse.ArgumentDefaultsHelpFormatter) #设置命令行帮助信息的格式,默认为 argparse.HelpFormatter,可以使得输出的帮助信息中包含每个参数的默认值。
    #使用了 Python 内置的 argparse 模块,创建了一个 ArgumentParser 对象 parser,用于解析命令行参数。
    parser.add_argument('--name', type=str, default='cite')  #cite
    parser.add_argument('--k', type=int, default=3)
    parser.add_argument('--lr', type=float, default=1e-3)
    parser.add_argument('--n_clusters', default=3, type=int)
    parser.add_argument('--n_z', default=10, type=int)
    parser.add_argument('--pretrain_path', type=str, default='pkl')
    #这段代码定义了六个命令行参数,分别是 name、k、lr、n_clusters、n_z 和 pretrain_path,并为每个参数指定了默认值和其他属性。在命令行中可以根据需要覆盖这些默认值。

    args = parser.parse_args()
    #通过调用parser.parse_args()方法,从命令行获取这些参数的值,并将结果存储在args对象中。
    args.cuda = torch.cuda.is_available()
    print("use cuda: {}".format(args.cuda))
    device = torch.device("cuda" if args.cuda else "cpu")

    args.pretrain_path = 'data/{}.pkl'.format(args.name)
    dataset = load_data(args.name)
    #脚本根据获取到的args.name参数值加载相应的数据集,并根据数据集类型设置相应的其他超参数。最后,脚本调用train_sdcn()函数训练聚类模型。
    if args.name == 'usps':
        args.n_clusters = 10
        args.n_input = 256

    if args.name == 'hhar':
        args.k = 5
        args.n_clusters = 6
        args.n_input = 561

    if args.name == 'reut':
        args.lr = 1e-4
        args.n_clusters = 4
        args.n_input = 2000

    if args.name == 'acm':
        args.k = None
        args.n_clusters = 3
        args.n_input = 1870

    if args.name == 'dblp':
        args.k = None
        args.n_clusters = 4
        args.n_input = 334

    if args.name == 'cite':
        args.lr = 1e-3
        args.k = None
        args.n_clusters = 6
        args.n_input = 3703

    if args.name == 'abstract':
        args.lr = 1e-4
        args.k = None
        args.n_clusters = 3
        args.n_input = 10000

    if args.name == 'bbc':
        args.lr = 1e-4
        args.k = None
        args.n_clusters = 4
        args.n_input = 9635

    print(args)
    train_sdcn(dataset)
#该代码定义了一个主函数,首先通过argparse库设置了命令行参数,包括name,k,lr,n_clusters,n_z等参数,
# 然后加载数据集,然后加载数据集,并根据数据集的不同调整参数的值,最后调用train_sdcn()函数对数据集进行训练。
# 其中,train_sdcn()函数的作用是对数据集进行聚类,使用SDCN模型进行特征学习和聚类,并输出评估结果。

这些是数据集的名称或缩写,代表了在本文使用的SDCN算法中用于聚类任务的各个数据集。具体来说:
usps: USPS手写数字识别数据集。
hhar:活动识别数据集,包含多种动作的加速度计和陀螺仪信号。. reut: Reuters新闻语料库数据集,包含多篇新闻文本。
acm: ACM学术出版社文章引用关系数据集。
dblp: DBLP学术会议论文引用关系数据集。
cite: CiteSeerX学术论文引用关系数据集。
abstract:文章概要数据集,由30个子集组成。
bbc:英国广播公司(BBC)新闻分类数据集。

这些数据集都是常用的聚类算法评估数据集,可以用于测试SDCN算法的聚类效果。
 

2.evaluation.py

代码如下:

import numpy as np
from munkres import Munkres, print_matrix
from sklearn.metrics.cluster import normalized_mutual_info_score as nmi_score
from sklearn.metrics import adjusted_rand_score as ari_score
from scipy.optimize import linear_sum_assignment as linear
from sklearn import metrics


def cluster_acc(y_true, y_pred):
    y_true = y_true - np.min(y_true)  #将 y_true 中所有标签值减去最小值,确保它们从 0 开始连续编号。

    l1 = list(set(y_true))  #获取 y_true 中出现过的不同标签值,并将其转换为列表形式。
    numclass1 = len(l1)  #计算 y_true 中不同标签值的数量,即真实类别数。

    l2 = list(set(y_pred))
    numclass2 = len(l2)

    ind = 0
    if numclass1 != numclass2: #如果真实类别数和预测类别数不相等
        for i in l1:  #遍历 l1 中的每个标签值 i
            if i in l2: #如果标签值 i 在 l2 中出现过,则不做任何操作
                pass
            else: #否则,在 y_pred 的第 ind 个位置添加标签值 i,并将 ind 加 1。
                y_pred[ind] = i
                ind += 1

    l2 = list(set(y_pred)) #重新获取更新后的 y_pred 中出现过的不同标签值。
    numclass2 = len(l2)

    if numclass1 != numclass2:   #再次检查真实类别数和预测类别数是否相等。如果不相等,则输出错误信息并返回。
        print('error')
        return

    cost = np.zeros((numclass1, numclass2), dtype=int)  #创建一个 numclass1 行 numclass2 列的二维数组 cost,并将其初始化为零
    #接下来,函数创建一个二维数组 cost,其中每行对应于 y_true 中的一个标签值,每列对应于 y_pred 中的一个标签值。
    #cost[i][j] 表示 y_true 中属于第 i 类的样本中有多少个被预测为属于第 j 类。
    for i, c1 in enumerate(l1): #遍历 l1 中的每个标签值 c1,并获取属于第 c1 类的真实样本集合 mps
        mps = [i1 for i1, e1 in enumerate(y_true) if e1 == c1] #通过列表推导式,得到所有真实标签为 c1 的样本在 y_true 中的索引列表 mps
        for j, c2 in enumerate(l2): #遍历 l2 中的每个标签值 c2。#
            mps_d = [i1 for i1 in mps if y_pred[i1] == c2] #通过列表推导式,得到在 mps 中与 y_pred 中标签为 c2 的样本对应的索引列表 mps_d
            cost[i][j] = len(mps_d)  #将 mps_d 中元素的数量赋给 cost[i][j],即真实标签为 c1 且预测标签为 c2 的样本数量

    # match two clustering results by Munkres algorithm
    m = Munkres() #创建一个 Munkres 对象 m
    cost = cost.__neg__().tolist() #将 cost 中所有元素取相反数,以便使用 Munkres 算法最小化矩阵总和。然后,将其转换为列表形式
    indexes = m.compute(cost) #使用 Munkres 算法计算 cost 的最优匹配,并返回一个包含匹配结果的二元组列表 indexes

    # get the match results
    new_predict = np.zeros(len(y_pred)) #创建一个长度为 y_pred 的全零数组 new_predict,用于存储新的预测标签
    for i, c in enumerate(l1): #遍历 l1 中的每个标签值 c
        # correponding label in l2:
        c2 = l2[indexes[i][1]] #获取在 l2 中与 c 对应的标签值 c2

        # ai is the index with label==c2 in the pred_label list
        ai = [ind for ind, elm in enumerate(y_pred) if elm == c2] #通过列表推导式,得到所有预测标签为 c2 的样本在 y_pred 中的索引列表 ai
        new_predict[ai] = c #将 new_predict 中索引为 ai 的元素赋值为 c,即将预测标签为 c2 的样本都重新赋值为真实标签 c

    acc = metrics.accuracy_score(y_true, new_predict)
    f1_macro = metrics.f1_score(y_true, new_predict, average='macro')
    precision_macro = metrics.precision_score(y_true, new_predict, average='macro')
    recall_macro = metrics.recall_score(y_true, new_predict, average='macro')
    f1_micro = metrics.f1_score(y_true, new_predict, average='micro')
    precision_micro = metrics.precision_score(y_true, new_predict, average='micro')
    recall_micro = metrics.recall_score(y_true, new_predict, average='micro')
    return acc, f1_macro


def eva(y_true, y_pred, epoch=0):
    acc, f1 = cluster_acc(y_true, y_pred)
    nmi = nmi_score(y_true, y_pred, average_method='arithmetic')
    ari = ari_score(y_true, y_pred)
    print(epoch, ':acc {:.4f}'.format(acc), ', nmi {:.4f}'.format(nmi), ', ari {:.4f}'.format(ari),
            ', f1 {:.4f}'.format(f1))


总结:

详细讲解点击此处:(论文解读)

DEC(Deep Embedded Clustering)是一种基于深度学习的无监督聚类方法,它通过将自编码器(Autoencoder)与K-Means聚类相结合来实现端到端的聚类任务。以下是对DEC文献的总结:

1. 标题:"Unsupervised Deep Embedding for Clustering Analysis"(无监督的深度嵌入聚类分析)

2. 作者:Junyuan Xie, Ross Girshick, and Ali Farhadi

3. 发表年份:2016

4. 主要内容:

   - DEC提出了一种将深度嵌入学习与聚类相结合的框架,以解决传统聚类方法的局限性。
   - 使用自编码器(Autoencoder)进行无监督的特征学习。自编码器通过将输入数据压缩到低维编码表示,并通过解码器进行重构来学习有意义的特征表示。
   - 引入目标分布来指导聚类。DEC通过将K-Means聚类算法与自编码器结合,将自编码器的编码层输出作为聚类任务的输入,并引入目标分布来指导自编码器的训练。
   - 使用深度嵌入聚类算法(Deep Embedded Clustering)进行迭代优化。DEC的核心思想是通过迭代优化来联合训练自编码器和K-Means聚类。迭代过程中,首先使用K-Means对编码层输出进行聚类,然后将聚类结果作为目标分布,重新训练自编码器。迭代过程将不断优化编码层的特征表示和聚类结果,直到收敛。
   - 提出了一种软分配(soft assignment)策略。DEC引入了软分配策略,将每个数据点分配到每个聚类的概率分布上,而不是硬分配到一个聚类。这样可以更好地处理数据点的边界情况和噪声,提高聚类的鲁棒性和准确性。

5. 创新点和贡献:

   - DEC通过结合自编码器和K-Means聚类,提供了一种无监督的端到端聚类方法,避免了传统方法中特征工程和聚类分离的问题。
   - 引入了目标分布和软分配策略,提高了聚类的鲁棒性和准确性。
   - 实验证明,DEC在多个真实数据集上取得了优于传统方法的聚类性能。

总体而言,DEC是一种创新的无监督聚类方法,通过深度嵌入学习和软分配策略,克服了传统聚类方法的一些限制。

附:输出结果

acc:表示算法在数据集上的准确率。
nmi:表示归一化互信息(Normalized Mutual nformation)指标,用于衡量算法的聚类质量。
ari:表示调整兰德指数(Adjusted Rand Index)指标,也是用于衡量算法的聚类质量。
f1:表示F1分数指标,综合考虑精确度和召回率,用于衡量分类模型的性能。

  • 7
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值