[文献阅读] Unsupervised Deep Embedding for Clustering Analysis (DEC)(pytorch复现)

文章目录

Abstract:

This week I read Unsupervised Deep Embedding for Clustering Analysis .It discusses the concept of Unsupervised Deep Embedding for Clustering Analysis (DEC), a method that employs KL divergence for clustering loss to adjust a pre-trained neural network encoder. DEC is an approach that leverages deep neural networks to simultaneously learn feature representations and cluster assignments.

摘要

无监督的深度嵌入式聚类 Unsupervised Deep Embedding for Clustering Analysis http://arxiv.org/abs/1511.06335

为了解决使用Kmeans传统聚类的在高维空间失效的问题,该论文首先提出将KL散度 用于聚类损失,并使用这一损失调整一个预训练的编码器 (堆叠自编码器DAE)。称为深度嵌入聚类(DEC) ,这是一种使用深度神经网络同时学习特征表示聚类分配的方法。

为了使KL散度更好应用于聚类任务,文章创设性的设置了KL散度中两个分布,目标分布P真实分布Q。DEC学习从数据空间到低维特征空间的映射,并在其中迭代优化聚类目标。对图像和文本语料库的实验评估表明,与最先进的和传统的聚类方法,有了显著的改进。

深度聚类

引入了深度学习的聚类叫深度聚类 。通过神经网络的特征学习或者非线性映射,替代传统的手工特征提取器,学习到更高质量的特征。深度聚类算法大都由聚类损失网络损失两部分构成,高质量的特征有助于提升聚类效果,聚类结果可以引导神经网络学习更好的特征。

KL散度

kmeans和高斯混合模型(GMM)是流行的聚类方法的一个分支。这些方法快速且适用于各种各样的问题。但是,它们的距离指标 仅限于原始数据空间,并且在输入维度较高时它们往往会失效。

KL散度 (Kullback-Leibler divergence),可以以称作相对熵(relative entropy)或信息散度(information divergence)。KL散度的理论意义在于++度量两个概率分布之间的差异程度++,当KL散度越大的时候,说明两者的差异程度越大;而当KL散度小的时候,则说明两者的差异程度小。如果两者相同的话,则该KL散度应该为0。

离散的情况下用q(x)去近似p(x)的KL散度的公式(对于聚类问题,我们往往只会关心离散的情况 ):
K L ( P ∣ ∣ Q ) = ∑ p ( x ) p ( x ) q ( x ) KL(P||Q)=\sum {p(x){\frac{p(x)}{q(x)}}} KL(P∣∣Q)=∑p(x)q(x)p(x)

对上面的式子进行展开:

最后得到的第一项称作P和Q的交叉熵(cross entropy) ,后面一项就是P的自身交叉熵

H§代表着基于P分布自身的编码长度,也就是最优的编码长度(最小字节数)。而H(P,Q)则代表着用Q的分布去近似P 分布的信息,自然需要更多的编码长度。并且两个分布差异越大,需要的编码长度越大。所以两个值相减是大于等于0的一个值,代表冗余的编码长度,也就是++两个分布差异的程度++。

公式上可以看出,KL散度具有非对称性,也就是说,分布P到分布Q的KL散度,并不一定等于分布Q到分布P的KL散度。

深度嵌入式聚类(DEC)

++聚类问题的数学描述:++
考虑将n个点 x i ∈ X , n i = 1 {x_i\in X}, n_i=1 xi∈X,ni=1的集合聚类为k个类的问题,每个聚类由质心 µ j µ_j µj表示, j = 1 , . . . , k j = 1,... ,k j=1,...,k。

首先使用非线性映射 f θ f_θ fθ:X→Z变换数据,而不是直接在数据空间X中进行聚类,其中θ是可学习的参数,Z是潜在特征空间。 Z的维度通常比X小得多,以避免"维度的诅咒"。为了对fθ进行参数化,深层神经网络(DNN)由于其理论函数逼近特性和已证明的特征学习能力而成为最好的选择。

DEC算法通过同时学习特征空间Z中的k个聚类中心 { μ j ∈ Z } k j = 1 k \{{μ_j\in Z}\}^k_{k_j = 1} {μj∈Z}kj=1k和将数据点映射到Z的神经网络参数θ来对数据进行聚类。DEC有两个阶段:

  1. 非线性映射f_θ:X→Z,使用深度自动编码器进行参数初始化。
  2. KL聚类,其中我们在计算辅助目标分布与最小化KL散度之间进行迭代。

KL散度聚类

给定非线性映射 f θ f_θ fθ的初始估计值和初始聚类质心 { μ j ∈ Z } k j = 1 k \{{μ_j\in Z}\}^k_{k_j = 1} {μj∈Z}kj=1k,使用在两步之间交替的无监督算法来改善聚类。在第一步中,我们计算嵌入点和聚类质心之间的软分配 概率, 第二步,我们通过使用辅助目标分布,从当前的高置信度分配中,学习来更新映射f(θ)的参数,并更新聚类质心。重复该过程,直到满足收敛标准(数据点与其对应的质心不再改变)为止。

软分配(soft assignment)

软分配不同于硬分配,硬分配只给每个数据点分配到一个质心,软分配为每个数据点给出它分配到每个质心的概率

通过**软分配(soft assignment)**策略,将每个数据点分配到每个聚类的概率分布上,而不是硬分配到一个聚类。这种策略可以更好地处理数据点的边界情况和噪声,提高聚类的鲁棒性和准确性。

使用t分布(student学生分布)作为概率分布,来衡量嵌入点 z i z_i zi和质心 µ j µ_j µj之间的相似性:

其中 z i = f θ ( x i ) ∈ Z z_i=f_θ(x_i)∈Z zi=fθ(xi)∈Z对应于嵌入后的xi∈X,其中α是t分布的自由度,而 q i j q_{ij} qij可解释为将样本 z i z_i zi分配给聚类j的概率(即软分配)。由于我们无法在无监督的环境中对验证集上的α进行交叉验证,因此对于所有实验,我们使α= 1。

为什么使用t-分布

使用t-分布 这一灵感来自t-SNE 可视化算法。t-分布是一种长尾分布 ,与正态分布 相比之下,++t分布的尾部较高,对异常点不敏感,保证了其鲁棒性,因此其拟合结果更为合理,较好的捕获了数据的整体特征。++

KL散度损失

我们希望通过最小化将软分配与目标分布之间的损失 来训练我们的模型。为此,我们将目标损失定义为软分配 q i j q_{ij} qij和辅助分布 p i j p_{ij} pij之间的KL散度损失,如下所示:

从KL散度公式看,分布Q就是软分配使用的t分布,而目标分布P则需要++自行拟定++,来表示通过迭代质心的移动,使得真实分布Q朝着什么样的方向前进。

目标分布P的选择对于DEC的性能至关重要。

我们希望目标分布P具有以下属性:

  • (1)预测效果好(即提高簇纯度)
  • (2)更加重视具有高置信度的数据点
  • (3)归一化每个损失的贡献重心以防止大型簇扭曲隐藏的特征空间。

文章的实验中,通过先将 q i j q_{ij} qij升至第二次幂,然后通过每个聚类(簇)的频率进行归一化来计算 p i j p_{ij} pij:

其中 f j = ∑ i q i j f_j = \sum_i q_{ij} fj=∑iqij 。意义是软分配情况下的簇频率。

训练

使用具有动量的随机梯度下降(SGD)来优化参数。

编码器的初始化

文中的解码-编码器实际就是一个预训练好的泛用的嵌入模型,它能很好的将自然语言通过语义映射到高维空间。

经过逐层训练之后,我们以反向逐层训练的顺序将所有编码器层与所有解码器层连接起来,形成一个深层的自动编码器,然后对其微调以最小化重建损失。最终结果是多层深层自动编码器,中间有一个瓶颈编码层。然后我们丢弃解码器层 ,并将编码器层用作数据空间和特征空间之间的初始映射

聚类中心的初始化

为了初始化聚类中心,我们通过初始化的DNN(就是上面的编码器)传递数据以获取嵌入的数据点,然后在特征空间Z中执行标准k-means聚类以获得k个初始质心 { μ j ∈ Z } k j = 1 k \{{μ_j\in Z}\}^k_{k_j = 1} {μj∈Z}kj=1k。

实验评估

我们在一个文本数据集和两个图像数据集上评估了所提出的方法(DEC),并将其与其他算法(包括k-means,LDGMI和SEC)进行了比较。 LDGMI和SEC是基于谱聚类的算法,使用拉普拉斯矩阵和各种变换来提高聚类性能。我们展示了定性和定量结果,这些结果证明了DEC与LDGMI和SEC相比的优势。

为了研究不同算法的性能和通用性,我们对两个图像数据集和一个文本数据集进行了实验:

•MNIST:MNIST数据集由280000像素大小的70000个手写数字组成。这些数字居中并进行尺寸归一化。

•STL-10:96 x 96彩色图像的数据集。有10个类别,每个类别有1300个样本。它还包含100000张相同分辨率的无标签图像(Coates等,2011)。

•REUTERS:REUTERS包含大约810000个以类别树标签的英语新闻报道。我们使用了四个根类别:公司/工业,政府/社会,市场和经济学作为标签,并进一步修剪了由多个根类别标签的所有文档以得到685071个文章。

pytorch复现

原文章的实验使用caffe深度学习框架实现,但由于框架过于古老。这里使用pytorch的版本vlukiyanov/pt-dec: PyTorch implementation of DEC (Deep Embedding Clustering)

以下实验以MNIST数据集为例

网络结构

编码器的网络层次定义为[28 * 28, 500, 500, 2000, 10],当然解码器的层次由此对称而得来[10, 2000, 50, 50, 28*28]。所以自编码器总体网络结构为[28 * 28, 500, 500, 2000, 10, 2000, 50, 50, 28 * 28]。

可以看到,模型的瓶颈层 为10个单元,那么这一层将用于输出原始数据的特征。同时注意到两边有2000个单元的稀疏层,关于这个稀疏层 文章没有做过多的介绍,笔者认为,这个稀疏层是用于第二步解码器的训练(实际上编码器和解码器同时训练)时,为了让解码器能够从特征中更好的还原原始数据而设置的。

复制代码
autoencoder = StackedDenoisingAutoEncoder(
        [28 * 28, 500, 500, 2000, 10], final_activation=None
    )

训练步骤

整个模型的训练分三步:

  1. 堆叠编码器DAE预训练:对编码器逐层训练,再堆叠起来。
  2. 编码器和解码器训练:编码器使用上一步预训练得到的,再构造一个对称的解码器(未经训练)。两者得到的自编码器再进行对样本的重构任务训练。这一步编码器和解码器权重同时更新。
  3. KL散度聚类训练:去掉解码器,对编码器进行训练。对编码器输出的特征进行KL散度损失计算,训练时同时更新编码器权重和聚类质心μ。

DAE预训练

逐层预训练就是DAE的训练方式,具体看笔者上一篇博客https://blog.csdn.net/weixin_53834244/article/details/145214588?spm=1001.2014.3001.5501。

简单来说

第一层的输入由数据集直接得来,第一层训练完毕后。

再将第一层的编码器的输出作为第二层的输入,继续训练第二层。。。。。

这里只放出一些参数:

python 复制代码
print("Pretraining stage.")
    ae.pretrain(
        ds_train,
        autoencoder,
        cuda=cuda,
        validation=ds_val,
        epochs=pretrain_epochs,
        batch_size=batch_size,
        optimizer=lambda model: SGD(model.parameters(), lr=0.1, momentum=0.9),
        scheduler=lambda x: StepLR(x, 100, gamma=0.1),
        corruption=0.2,
    )

自编码器再训练

编码器使用上一步预训练得到的权重,再构造一个对称的解码器(未经训练),组成一个完整的自编码器,再进行同时训练。

这一步相当有必要,因为上一步的预训练只是提取特征,并且误差在逐层训练中累积太大,于是需要这一步训练。那为什么不直接进行这一步训练呢,因为上一步的编码器预训练能够提供一个更好的起步,所以能让这步的训练效果更好。

python 复制代码
print("Training stage.")
    ae_optimizer = SGD(params=autoencoder.parameters(), lr=0.1, momentum=0.9)
    ae.train(
        ds_train,
        autoencoder,
        cuda=cuda,
        validation=ds_val,
        epochs=finetune_epochs,
        batch_size=batch_size,
        optimizer=ae_optimizer,
        scheduler=StepLR(ae_optimizer, 100, gamma=0.1),
        corruption=0.2,
        update_callback=training_callback,
    )

KL聚类训练

真正的重点在于KL聚类训练过程,实验代码中称为"DEC stage."

python 复制代码
print("DEC stage.")
model = DEC(cluster_number=10, hidden_dimension=10, encoder=autoencoder.encoder)
if cuda:
    model.cuda()
dec_optimizer = SGD(model.parameters(), lr=0.01, momentum=0.9)
train(
    dataset=ds_train,
    model=model,
    epochs=100,
    batch_size=256,
    optimizer=dec_optimizer,
    stopping_delta=0.000001,
    cuda=cuda,
)
predicted, actual = predict(
    ds_train, model, 1024, silent=True, return_actual=True, cuda=cuda
)
actual = actual.cpu().numpy()
predicted = predicted.cpu().numpy()
reassignment, accuracy = cluster_accuracy(actual, predicted)
print("Final DEC accuracy: %s" % accuracy)

重点讲一下软分配的过程.

  • alpha指的是公式中的α,自由度,设置为1
  • norm_squared计算的是特征点和与质心的距离平方
python 复制代码
    def forward(self, batch: torch.Tensor) -> torch.Tensor:
        """
        Compute the soft assignment for a batch of feature vectors, returning a batch of assignments
        for each cluster.

        :param batch: FloatTensor of [batch size, embedding dimension]
        :return: FloatTensor [batch size, number of clusters]
        """
        norm_squared = torch.sum((batch.unsqueeze(1) - self.cluster_centers) ** 2, 2)
        numerator = 1.0 / (1.0 + (norm_squared / self.alpha))
        power = float(self.alpha + 1) / 2
        numerator = numerator ** power
        return numerator / torch.sum(numerator, dim=1, keepdim=True)

KL散度部分

对比pytorch官方文档的KL散度公式,注意 n n . K L D i v L o s s ( y p r e d , y t r u e ) nn.KLDivLoss(y_{pred}, y_{true}) nn.KLDivLoss(ypred,ytrue)中真实分布 y p r e d y_{pred} ypred是没有取对数的

所以传参进去之前记得给真实分布取对数。

python 复制代码
model.train()
loss_function = nn.KLDivLoss(size_average=False)  # KL散度损失
for index, batch in enumerate(data_iterator):
    if (isinstance(batch, tuple) or isinstance(batch, list)) and len(
        batch
    ) == 2:
        batch, _ = batch  # if we have a prediction label, strip it away
    if cuda:
        batch = batch.cuda(non_blocking=True)
    output = model(batch)
    target = target_distribution(output).detach()
    loss = loss_function(output.log(), target) / output.shape[0]  # 注意log
    data_iterator.set_postfix(
        epo=epoch,
        acc="%.4f" % (accuracy or 0.0),
        lss="%.8f" % float(loss.item()),
        dlb="%.4f" % (delta_label or 0.0),
    )
    optimizer.zero_grad()
    loss.backward()
    optimizer.step(closure=None)
    features.append(model.encoder(batch).detach().cpu()

结果

由于该实验的环境的cuda版本为9.0,而笔者设备最低也不支持9.0。所以只能用cpu训练,由于cpu速度太慢。

笔者pretrain epochs = 30,train epochs = 50,论文中pretrain epochs = 300,train epochs = 500.

由于epochs太少,KL聚类时(DEC stage)未能达到收敛阈值(不会停下),不过epoch=30时,聚类准确率已经达到了81.52%。

使用Kmeans的DAE只有62%左右。

总结

无监督的深度嵌入式聚类 Unsupervised Deep Embedding for Clustering Analysis

文章在无监督聚类问题上,首先将KL散度 引入,并提出深度嵌入式聚类(DEC)

KL散度是一种描述分布之间差异的函数,这对于离散数据点聚类问题具有很好的描述性。并通过KL散度作为损失 ,通过再训练的方式,使得预训练的深层神经网络编码器调整成同时学习特征表示聚类分配的网络。在网络的迭代映射和聚类收敛后,每个数据点将被分配到唯一的质心,完成聚类。

DEC的创设性 不仅在于引入了KL散度,同样在于KL散度中两个分布的设置,目标分布P真实分布Q。软分配中真实分布Q设置为t-分布,目标分布P则通过归一化的真实分布的二次幂计算。

正是这些性质使得DEC在聚类的鲁棒性和准确性都有更好的效果。

相关推荐
风象南4 小时前
Claude Code这个隐藏技能,让我告别PPT焦虑
人工智能·后端
Mintopia4 小时前
OpenClaw 对软件行业产生的影响
人工智能
陈广亮5 小时前
构建具有长期记忆的 AI Agent:从设计模式到生产实践
人工智能
会写代码的柯基犬5 小时前
DeepSeek vs Kimi vs Qwen —— AI 生成俄罗斯方块代码效果横评
人工智能·llm
Mintopia6 小时前
OpenClaw 是什么?为什么节后热度如此之高?
人工智能
爱可生开源社区6 小时前
DBA 的未来?八位行业先锋的年度圆桌讨论
人工智能·dba
叁两9 小时前
用opencode打造全自动公众号写作流水线,AI 代笔太香了!
前端·人工智能·agent
前端付豪9 小时前
LangChain记忆:通过Memory记住上次的对话细节
人工智能·python·langchain
strayCat232559 小时前
Clawdbot 源码解读 7: 扩展机制
人工智能·开源