論文信息
論文標題:Deep Fusion Clustering Network
論文作者:Wenxuan Tu, Sihang Zhou, Xinwang Liu, Xifeng Guo, Zhiping Cai, En Zhu, Jieren Cheng
論文來源:2020, AAAI
論文地址:download
論文代碼:download
1 Introduction
先前工作存在的問題:
-
- 缺少一種動態融合機制將屬性信息和結構信息融合起來;
- 無法從目標分布提取有用的信息;
深度圖聚類的方法可以分為以下幾種:
-
- 首先,我們從局部和全局層面整合兩種樣本嵌入來進行共識表示學習。
- 之后,通過估計潛在嵌入空間中樣本點和預先計算的聚類中心之間的相似性,我們得到了更精確的目標分布。
- 最后,我們設計了一種三元組自我監督機制,利用目標分布同時為AE、GAE和信息融合部分提供更可靠的指導。
我們開發了一種改進的具有對稱結構的圖自動編碼器(IGAE),並利用圖解碼器重構的潛在表示和特征表示重構了鄰接矩陣。
2 Method
模型可以分為四個部分:
-
- an autoencoder.
- an improved graph autoencoder.
- a fusion module.
- the optimization targets

class IGAE_encoder(nn.Module): def __init__(self, gae_n_enc_1, gae_n_enc_2, gae_n_enc_3, n_input): super(IGAE_encoder, self).__init__() self.gnn_1 = GNNLayer(n_input, gae_n_enc_1) self.gnn_2 = GNNLayer(gae_n_enc_1, gae_n_enc_2) self.gnn_3 = GNNLayer(gae_n_enc_2, gae_n_enc_3) self.s = nn.Sigmoid() def forward(self, x, adj): z = self.gnn_1(x, adj, active=False if opt.args.name == "hhar" else True) z = self.gnn_2(z, adj, active=False if opt.args.name == "hhar" else True) z_igae = self.gnn_3(z, adj, active=False) z_igae_adj = self.s(torch.mm(z_igae, z_igae.t())) return z_igae, z_igae_adj class IGAE_decoder(nn.Module): def __init__(self, gae_n_dec_1, gae_n_dec_2, gae_n_dec_3, n_input): super(IGAE_decoder, self).__init__() self.gnn_4 = GNNLayer(gae_n_dec_1, gae_n_dec_2) self.gnn_5 = GNNLayer(gae_n_dec_2, gae_n_dec_3) self.gnn_6 = GNNLayer(gae_n_dec_3, n_input) self.s = nn.Sigmoid() def forward(self, z_igae, adj): z = self.gnn_4(z_igae, adj, active=False if opt.args.name == "hhar" else True) z = self.gnn_5(z, adj, active=False if opt.args.name == "hhar" else True) z_hat = self.gnn_6(z, adj, active=False if opt.args.name == "hhar" else True) z_hat_adj = self.s(torch.mm(z_hat, z_hat.t())) return z_hat, z_hat_adj class IGAE(nn.Module): def __init__(self, gae_n_enc_1, gae_n_enc_2, gae_n_enc_3, gae_n_dec_1, gae_n_dec_2, gae_n_dec_3, n_input): super(IGAE, self).__init__() self.encoder = IGAE_encoder( gae_n_enc_1=gae_n_enc_1, gae_n_enc_2=gae_n_enc_2, gae_n_enc_3=gae_n_enc_3, n_input=n_input) self.decoder = IGAE_decoder( gae_n_dec_1=gae_n_dec_1, gae_n_dec_2=gae_n_dec_2, gae_n_dec_3=gae_n_dec_3, n_input=n_input) def forward(self, x, adj): z_igae, z_igae_adj = self.encoder(x, adj) z_hat, z_hat_adj = self.decoder(z_igae, adj) adj_hat = z_igae_adj + z_hat_adj return z_igae, z_hat, adj_hat

class AE_encoder(nn.Module): def __init__(self, ae_n_enc_1, ae_n_enc_2, ae_n_enc_3, n_input, n_z): super(AE_encoder, self).__init__() self.enc_1 = Linear(n_input, ae_n_enc_1) self.enc_2 = Linear(ae_n_enc_1, ae_n_enc_2) self.enc_3 = Linear(ae_n_enc_2, ae_n_enc_3) self.z_layer = Linear(ae_n_enc_3, n_z) self.act = nn.LeakyReLU(0.2, inplace=True) def forward(self, x): z = self.act(self.enc_1(x)) z = self.act(self.enc_2(z)) z = self.act(self.enc_3(z)) z_ae = self.z_layer(z) return z_ae class AE_decoder(nn.Module): def __init__(self, ae_n_dec_1, ae_n_dec_2, ae_n_dec_3, n_input, n_z): super(AE_decoder, self).__init__() self.dec_1 = Linear(n_z, ae_n_dec_1) self.dec_2 = Linear(ae_n_dec_1, ae_n_dec_2) self.dec_3 = Linear(ae_n_dec_2, ae_n_dec_3) self.x_bar_layer = Linear(ae_n_dec_3, n_input) self.act = nn.LeakyReLU(0.2, inplace=True) def forward(self, z_ae): z = self.act(self.dec_1(z_ae)) z = self.act(self.dec_2(z)) z = self.act(self.dec_3(z)) x_hat = self.x_bar_layer(z) return x_hat class AE(nn.Module): def __init__(self, ae_n_enc_1, ae_n_enc_2, ae_n_enc_3, ae_n_dec_1, ae_n_dec_2, ae_n_dec_3, n_input, n_z): super(AE, self).__init__() self.encoder = AE_encoder( ae_n_enc_1=ae_n_enc_1, ae_n_enc_2=ae_n_enc_2, ae_n_enc_3=ae_n_enc_3, n_input=n_input, n_z=n_z) self.decoder = AE_decoder( ae_n_dec_1=ae_n_dec_1, ae_n_dec_2=ae_n_dec_2, ae_n_dec_3=ae_n_dec_3, n_input=n_input, n_z=n_z) def forward(self, x): z_ae = self.encoder(x) x_hat = self.decoder(z_ae) return x_hat, z_ae
2.1 Notations
$\widetilde{\mathbf{A}} \in \mathbb{R}^{N \times N}= \mathbf{D}^{-\frac{1}{2}}(\mathbf{A}+\mathbf{I}) \mathbf{D}^{-\frac{1}{2}} $。
Table 1 總結了所有符號。
2.2 Fusion-based Autoencoders
2.2.1 Input of the Decoder
首先整合GAE和AE學到的特征以獲得共通的潛在表示。然后將這種嵌入作為輸入,AE和GAE的解碼器對兩個子網絡的輸入進行重構。
2.2.2 Improved Graph Autoencoder
使用 IGAE 同時重構屬性信息和結構信息,IGAE 編碼器和解碼器如下:
$\mathbf{Z}^{(l)}=\sigma\left(\widetilde{\mathbf{A}} \mathbf{Z}^{(l-1)} \mathbf{W}^{(l)}\right)\quad \quad \quad (1)$
$\widehat{\mathbf{Z}}^{(h)}=\sigma\left(\widetilde{\mathbf{A}} \widehat{\mathbf{Z}}^{(h-1)} \widehat{\mathbf{W}}^{(h)}\right)\quad \quad \quad (2)$
其中
-
- $\mathbf{W}^{(l)}$ 和 $\widehat{\mathbf{W}}^{(h)}$ 代表編碼器層和解碼器層參數矩陣;
IGAE 的損失函數如下:
$L_{I G A E}=L_{w}+\gamma L_{a}\quad \quad \quad (3)$
其中:
$L_{w} =\frac{1}{2 N}\|\widetilde{\mathbf{A}} \mathbf{X}-\widehat{\mathbf{Z}}\|_{F}^{2}\quad \quad \quad (4)$
$L_{a} =\frac{1}{2 N}\|\widetilde{\mathbf{A}}-\widehat{\mathbf{A}}\|_{F}^{2}\quad \quad \quad (5)$
2.2.3 Structure and Attribute Information Fusion
Structure and attribute information fusion (SAIF) module 包括兩部分:
-
- a crossmodality dynamic fusion mechanism.
- a triplet selfsupervised strategy.
SAIF 模塊如 Fig. 2 所示:
Part 1 :Cross-modality Dynamic Fusion Mechanism.
- 首先, 分別計算 $\mathrm{AE} \left(\mathbf{Z}_{A E} \in \mathbb{R}^{N \times d^{\prime}}\right)$ 和 IGAE $\left(\mathbf{Z}_{I G A E} \in \mathbb{R}^{N \times d^{\prime}}\right)$ 的潛在表示,然后進行線性組合:
$\mathbf{Z}_{I}=\alpha \mathbf{Z}_{A E}+(1-\alpha) \mathbf{Z}_{I G A E}\quad \quad \quad (6)$
- 其次,用類似圖卷積的操作處理組合信息 $\mathbf{Z}_{I}$。通過這個操作,我們通過考慮數據中的局部結構來增強初始融合嵌入 $\mathbf{Z}_{I} \in \mathbb{R}^{N \times d^{\prime}}$ :
$ \mathbf{Z}_{L}=\widetilde{\mathbf{A}} \mathbf{Z}_{I}\quad \quad \quad (7)$
$\mathbf{Z}_{L} \in \mathbb{R}^{N \times d^{\prime}}$ 表示對 $\mathbf{Z}_{I}$ 局部結構增強。
- 第三, 進一步引入自相關學習機制來利用樣本間初步信息融合空間中的非局部關系。我們首先通過 $Eq.8$ 計算歸一化自相關矩陣 $\mathbf{S} \in \mathbb{R}^{N \times N}$:
${\large \mathbf{S}_{i j}=\frac{e^{\left(\mathbf{Z}_{L} \mathbf{Z}_{L}^{\mathrm{T}}\right)_{i j}}}{\sum\limits _{k=1}^{N} e^{\left(\mathbf{Z}_{L} \mathbf{Z}_{L}^{\mathrm{T}}\right)_{i k}}}} \quad \quad \quad (8)$
然后,計算全局相關性:
$\mathbf{Z}_{G}=\mathbf{S} \mathbf{Z}_{L}$ .
- 最后,我們采用跳躍連接來鼓勵信息在融合機制內順利傳遞:
$\widetilde{\mathbf{Z}}=\beta \mathbf{Z}_{G}+\mathbf{Z}_{L} \quad \quad \quad (9)$
Part 2 :Triplet Self-supervised Strategy
從 AE 和 IGAE 生成聚類嵌入 $\widetilde{\mathbf{Z}} \in \mathbb{R}^{N \times d^{\prime}}$ 來指導聚類學習。
- 生成目標分布:
${\large q_{i j}=\frac{\left(1+\left\|\tilde{z}_{i}-u_{j}\right\|^{2} / v\right)^{-\frac{v+1}{2}}}{\sum_{j^{\prime}}\left(1+\left\|\tilde{z}_{i}-u_{j^{\prime}}\right\|^{2} / v\right)^{-\frac{v+1}{2}}}}\quad \quad \quad (10)$
- 生成輔助分布:
${\large p_{i j}=\frac{q_{i j}^{2} / \sum\limits _{i} q_{i j}}{\sum\limits _{j^{\prime}}\left(q_{i j^{\prime}}^{2} / \sum\limits _{i} q_{i j^{\prime}}\right)}} \quad \quad \quad (11)$
為了讓網絡作為一個整體訓練,設計了一個三聯聚類損失,通過下列的 KL散度計算:
$L_{K L}=\sum\limits _{i} \sum\limits_{j} p_{i j} \log \frac{p_{i j}}{\left(q_{i j}+q_{i j}^{\prime}+q_{i j}^{\prime \prime}\right) / 3}\quad \quad \quad (12)$
2.2.4 Joint loss and Optimization
整體學習目標由兩個主要部分組成,即AE和IGAE的重建損失,以及與目標分布相關的聚類損失:
$L=\underbrace{L_{\text {AE }}+L_{\text {IGAE }}}_{\text {Reconstruction }}+\underbrace{\lambda L_{K L}}_{\text {Clustering }} .\quad \quad \quad (13)$
其中
-
- $L_{AE}$ 代表着 AE 的MSE 損失。
DFCN 算法如 Algorithm 1 所示:

class DFCN(nn.Module): def __init__(self, ae_n_enc_1, ae_n_enc_2, ae_n_enc_3, ae_n_dec_1, ae_n_dec_2, ae_n_dec_3, gae_n_enc_1, gae_n_enc_2, gae_n_enc_3, gae_n_dec_1, gae_n_dec_2, gae_n_dec_3, n_input, n_z, n_clusters, v=1.0, n_node=None, device=None): super(DFCN, self).__init__() self.ae = AE( ae_n_enc_1=ae_n_enc_1, ae_n_enc_2=ae_n_enc_2, ae_n_enc_3=ae_n_enc_3, ae_n_dec_1=ae_n_dec_1, ae_n_dec_2=ae_n_dec_2, ae_n_dec_3=ae_n_dec_3, n_input=n_input, n_z=n_z) self.gae = IGAE( gae_n_enc_1=gae_n_enc_1, gae_n_enc_2=gae_n_enc_2, gae_n_enc_3=gae_n_enc_3, gae_n_dec_1=gae_n_dec_1, gae_n_dec_2=gae_n_dec_2, gae_n_dec_3=gae_n_dec_3, n_input=n_input) self.a = nn.Parameter(nn.init.constant_(torch.zeros(n_node, n_z), 0.5), requires_grad=True).to(device) self.b = 1 - self.a self.cluster_layer = nn.Parameter(torch.Tensor(n_clusters, n_z), requires_grad=True) torch.nn.init.xavier_normal_(self.cluster_layer.data) self.v = v self.gamma = Parameter(torch.zeros(1)) def forward(self, x, adj): z_ae = self.ae.encoder(x) z_igae, z_igae_adj = self.gae.encoder(x, adj) z_i = self.a * z_ae + self.b * z_igae #Eq.6
z_l = torch.spmm(adj, z_i) #Eq.7
s = torch.mm(z_l, z_l.t()) s = F.softmax(s, dim=1) #Eq.8
z_g = torch.mm(s, z_l) z_tilde = self.gamma * z_g + z_l #Eq.9
x_hat = self.ae.decoder(z_tilde) z_hat, z_hat_adj = self.gae.decoder(z_tilde, adj) adj_hat = z_igae_adj + z_hat_adj q = 1.0 / (1.0 + torch.sum(torch.pow((z_tilde).unsqueeze(1) - self.cluster_layer, 2), 2) / self.v) q = q.pow((self.v + 1.0) / 2.0) q = (q.t() / torch.sum(q, 1)).t() #Eq.10
q1 = 1.0 / (1.0 + torch.sum(torch.pow(z_ae.unsqueeze(1) - self.cluster_layer, 2), 2) / self.v) q1 = q1.pow((self.v + 1.0) / 2.0) q1 = (q1.t() / torch.sum(q1, 1)).t() #Eq.10
q2 = 1.0 / (1.0 + torch.sum(torch.pow(z_igae.unsqueeze(1) - self.cluster_layer, 2), 2) / self.v) q2 = q2.pow((self.v + 1.0) / 2.0) q2 = (q2.t() / torch.sum(q2, 1)).t() #Eq.10
return x_hat, z_hat, adj_hat, z_ae, z_igae, q, q1, q2, z_tilde

def Train(epoch, model, data, adj, label, lr, pre_model_save_path, final_model_save_path, n_clusters, original_acc, gamma_value, lambda_value, device): optimizer = Adam(model.parameters(), lr=lr) model.load_state_dict(torch.load(pre_model_save_path, map_location='cpu')) with torch.no_grad(): x_hat, z_hat, adj_hat, z_ae, z_igae, _, _, _, z_tilde = model(data, adj) kmeans = KMeans(n_clusters=n_clusters, n_init=20) cluster_id = kmeans.fit_predict(z_tilde.data.cpu().numpy()) model.cluster_layer.data = torch.tensor(kmeans.cluster_centers_).to(device) eva(label, cluster_id, 'Initialization') for epoch in range(epoch): # if opt.args.name in use_adjust_lr:
# adjust_learning_rate(optimizer, epoch)
x_hat, z_hat, adj_hat, z_ae, z_igae, q, q1, q2, z_tilde = model(data, adj) tmp_q = q.data p = target_distribution(tmp_q) loss_ae = F.mse_loss(x_hat, data) #L_AE loss
loss_w = F.mse_loss(z_hat, torch.spmm(adj, data)) #Eq.4
loss_a = F.mse_loss(adj_hat, adj.to_dense()) #Eq.5
loss_igae = loss_w + gamma_value * loss_a #Eq.3
loss_kl = F.kl_div((q.log() + q1.log() + q2.log()) / 3, p, reduction='batchmean') #Eq.12
loss = loss_ae + loss_igae + lambda_value * loss_kl #Eq.13
print('{} loss: {}'.format(epoch, loss)) optimizer.zero_grad() loss.backward() optimizer.step() kmeans = KMeans(n_clusters=n_clusters, n_init=20).fit(z_tilde.data.cpu().numpy()) acc, nmi, ari, f1 = eva(label, kmeans.labels_, epoch) acc_reuslt.append(acc) nmi_result.append(nmi) ari_result.append(ari) f1_result.append(f1) if acc > original_acc: original_acc = acc torch.save(model.state_dict(), final_model_save_path)
3 Experiments
數據集
節點聚類
消融實驗
IGAE 有效性分析
$GAE-L_{w}$ 或 $GAE-L_{a}$ 表示僅通過加權屬性矩陣或鄰接矩陣的重建損失函數進行優化的方法。
分析:
-
- $GAE-L _{w}$ 在六個數據集上的表現始終優於 $GAE-L _{a}$。
- 與僅構建鄰接矩陣的方法相比,IGAE 明顯提高了聚類性能。
SAIF 模塊分析
如 Fig. 4 所示,我們觀察到
-
- 與baseline相比,Baseline-C方法有大約0.5%到5.0%的性能提升,表明從局部和全局層面探索圖結構和節點屬性有助於學習共識潛在表示以更好地聚類;
- Baseline-C-T 方法在所有數據集上的性能始終優於 Baseline-C-S 方法。 原因是我們的三元組自監督策略成功地為 AE、IGAE 和融合部分的訓練生成了更可靠的指導,使它們相互受益。
- 與baseline相比,Baseline-C方法有大約0.5%到5.0%的性能提升,表明從局部和全局層面探索圖結構和節點屬性有助於學習共識潛在表示以更好地聚類;
雙源信息的影響
超參數 λ 分析
從 Eq. 13 中可以看出。 如圖所示,DFCN 引入了超參數 $\lambda$ 以在重建和聚類之間進行權衡。
圖 5 說明了當 $\lambda$ 從 $0.01$ 到 $100$ 變化時 DFCN 的性能變化。
、
From these figures, we observe that
- the hyper-parameter $\lambda$ is effective in improving the clustering performance;
- the performance of the method is stable in a wide range of $\lambda$ ;
- $ \mathrm{DFCN}$ tends to perform well by setting $\lambda$ to $10$ across all datasets.
可視化
4 Conclusion
提出三聯損失、引入雙源信息輸入。
修改歷史
2022-01-27 創建文章
2022-06-07 精度論文,並附上代碼