要谈DEC算法,首先得从问题背景出发,DEC算法其实同k-means一样,都是一种聚类算法,属于无监督学习的一种。那我们为什么要引入这么一个算法,道理也简单,自然是我们之前的算法有其局限性。
聚类算法中的一个核心便是距离函数,可以是欧氏距离,也可以是特征之间的某种关系,但k-means等算法对距离函数的处理往往局限于原始数据空间,这也就导致,如同组会上学姐学长所说,输入维度较高时,往往无效。而DEC聚类算法则解决了这一问题,这得益于它的设计框架和独特的实现过程。
DEC通过同时学习特征空间Z中的k个聚类中心和将数据点映射到Z的DNN的参数 θ 来聚类数据。
DEC具有两个阶段:( 1)使用自动编码器进行参数初始化(AE),
(2)参数优化(即聚类),其中我们在计算辅助目标分布和最小化Kullback-Leibler(KL)散度之间进行迭代。
如上图:
1. 使用深度学习自编码进行参数初始化
2.选取AE模型中的Encoder部分,加入聚类层,使用KL散度进行训练聚类
即先将特征压缩,构造软分布和辅助分布,(p,q)
迭代过程如下
然后将梯度∂L/∂zi向下传递到DNN,并在标准反向传播中用于计算DNN的参数梯度∂L/∂θ。为了发现聚类分配,两次迭代之间更改聚类分配的点小于tol%点时,我们将停止。
代码注解:
def target_distribution(q):
weight = q ** 2 / q.sum(0)
return (weight.t() / weight.sum(1)).t()
此句对应
q = 1.0 / (1.0 + torch.sum(torch.pow(z.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()
此句对应
其中 i表示第i样本,j表示第j个聚类中心, z表示原始特征分布经过Encoder之后的表征空间。qij可以解释为样本i属于聚类j的概率
代码:
AE版块
class AE(nn.Module):
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__()
self.enc_1 = Linear(n_input, n_enc_1)
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.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):
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))
z = self.z_layer(enc_h3)
dec_h1 = F.relu(self.dec_1(z))
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)
return x_bar, enc_h1, enc_h2, enc_h3, z
SDCN:
class SDCN(nn.Module):
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__()
# 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.load_state_dict(torch.load(args.pretrain_path, map_location='cpu'))
# cluster layer
self.cluster_layer = Parameter(torch.Tensor(n_clusters, n_z))
torch.nn.init.xavier_normal_(self.cluster_layer.data)
# degree
self.v = v
def forward(self, x):
# DNN Module
x_bar, tra1, tra2, tra3, z = self.ae(x)
# Dual Self-supervised Module
q = 1.0 / (1.0 + torch.sum(torch.pow(z.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()
return x_bar, q, z
辅助分布;
def target_distribution(q):
weight = q ** 2 / q.sum(0)
return (weight.t() / weight.sum(1)).t()
在聚类层中优化参数和迭代:
def train_sdcn(dataset):
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)
optimizer = Adam(model.parameters(), lr=args.lr)
# cluster parameter initiate
data = torch.Tensor(dataset.x).to(device)
y = dataset.y
with torch.no_grad():
_, _, _, _, z = model.ae(data)
kmeans = KMeans(n_clusters=args.n_clusters, n_init=20)
y_pred = kmeans.fit_predict(z.data.cpu().numpy())
y_pred_last = y_pred
model.cluster_layer.data = torch.tensor(kmeans.cluster_centers_).to(device)
eva(y, y_pred, 'pae')
for epoch in range(200):
if epoch % 1 == 0:
# update_interval
_, tmp_q, _ = model(data)
tmp_q = tmp_q.data
p = target_distribution(tmp_q)
res1 = tmp_q.cpu().numpy().argmax(1) # Q
res3 = p.data.cpu().numpy().argmax(1) # P
eva(y, res1, str(epoch) + 'Q')
eva(y, res3, str(epoch) + 'P')
x_bar, q, _ = model(data)
kl_loss = F.kl_div(q.log(), p, reduction='batchmean')
re_loss = F.mse_loss(x_bar, data)
loss = kl_loss + re_loss
optimizer.zero_grad()
loss.backward()
optimizer.step()
参考: