Lorsque j'étudiais la méthode de compression de dimension de données, mon junior a recherché et m'a appris la méthode d'apprentissage en profondeur «** Deep Embedded Clustering **» qui apprend la compression de dimension + le clustering en même temps, alors implémentons-la avec Chainer. C'est cet article. Le code implémenté est publié sur Github. https://github.com/ymym3412/DeepEmbeddedClustering
Deep Embedded Clustering est une méthode de clustering proposée dans l'article "Unsupervised Deep Embedding for Clustering Analysis". D'autres méthodes de compression dimensionnelle et de regroupement sont les suivantes.
--k-means, modèle de Gauss mixte (GMM)
Clustering intégré profond par rapport à la méthode ci-dessus
Il existe un avantage tel que.
L'apprentissage est en gros divisé en deux étapes.
L'image entière est la suivante.
Utilisez AutoEncoder empilé (Denoising) pour le pré-apprentissage des réseaux neuronaux. Le code définit la couche 1 comme Denoising Autoencoder et Stacked AutoEncoder comme ChainList.
class DenoisingAutoEncoder(chainer.Chain):
def __init__(self, input_size, output_size, encoder_activation=True, decoder_activation=True):
w = chainer.initializers.Normal(scale=0.01)
super(DenoisingAutoEncoder, self).__init__()
with self.init_scope():
self.encoder = L.Linear(input_size, output_size, initialW=w)
self.decoder = L.Linear(output_size, input_size, initialW=w)
# encode,Paramètres qui déterminent la présence ou l'absence d'une fonction d'activation lors du décodage
self.encoder_activation = encoder_activation
self.decoder_activation = decoder_activation
def __call__(self, x):
if self.encoder_activation:
h = F.relu(self.encoder(F.dropout(x, 0.2)))
else:
h = self.encoder(F.dropout(x, 0.2))
if self.decoder_activation:
h = F.relu(self.decoder(F.dropout(h, 0.2)))
else:
h = self.decoder(F.dropout(h, 0.2))
return h
def encode(self, x):
if self.encoder_activation:
h = F.relu(self.encoder(x))
else:
h = self.encoder(x)
return h
def decode(self, x):
if self.decoder_activation:
h = F.relu(self.decoder(x))
else:
h = self.decoder(x)
return h
class StackedDenoisingAutoEncoder(chainer.ChainList):
def __init__(self, input_dim):
#La dimension de chaque couche est la dimension d'entrée en fonction du papier->500->500->2000->10
super(StackedDenoisingAutoEncoder, self).__init__(
DenoisingAutoEncoder(input_dim, 500, decoder_activation=False),
DenoisingAutoEncoder(500, 500),
DenoisingAutoEncoder(500, 2000),
DenoisingAutoEncoder(2000, 10, encoder_activation=False)
)
def __call__(self, x):
# encode
models = []
for dae in self.children():
x = dae.encode(x)
models.append(dae)
# decode
for dae in reversed(models):
x = dae.decode(x)
return x
Le pré-apprentissage est également divisé en deux étapes: "Layer-Wise Pretrain", qui forme chaque couche comme un encodeur automatique, et "Fine Turing", qui entraîne l'ensemble du réseau.
# Layer-Wise Pretrain
print("Layer-Wise Pretrain")
#Obtenez chaque couche et apprenez en tant qu'AutoEncoder
for i, dae in enumerate(model.children()):
print("Layer {}".format(i+1))
train_tuple = tuple_dataset.TupleDataset(train, train)
train_iter = iterators.SerialIterator(train_tuple, batchsize)
clf = L.Classifier(dae, lossfun=mean_squared_error)
clf.compute_accuracy = False
if chainer.cuda.available and args.gpu >= 0:
clf.to_gpu(gpu_id)
optimizer = optimizers.MomentumSGD(lr=0.1)
optimizer.setup(clf)
updater = training.StandardUpdater(train_iter, optimizer, device=gpu_id)
trainer = training.Trainer(updater, (50000, "iteration"), out="mnist_result")
trainer.extend(extensions.LogReport())
trainer.extend(extensions.PrintReport(['iteration', 'main/loss', 'elapsed_time']))
#Taux d'apprentissage pour chaque 20000 itération(lr)1/Définir sur 10
trainer.extend(ChangeLearningRate(), trigger=(20000, "iteration"))
trainer.run()
#784 dimensions de données d'entraînement en fonction de l'entraînement pour chaque couche->500->500->Convertir en 2000
train = dae.encode(train).data
# Finetuning
print("fine tuning")
#Pas de décrochage pendant le réglage fin
with chainer.using_config("train", False):
train, _ = mnist.get_mnist()
train, _ = convert.concat_examples(train, device=gpu_id)
train_tuple = tuple_dataset.TupleDataset(train, train)
train_iter = iterators.SerialIterator(train_tuple, batchsize)
model = L.Classifier(model, lossfun=mean_squared_error)
model.compute_accuracy = False
if chainer.cuda.available and args.gpu >= 0:
model.to_gpu(gpu_id)
optimizer = optimizers.MomentumSGD(lr=0.1)
optimizer.setup(model)
updater = training.StandardUpdater(train_iter, optimizer, device=gpu_id)
trainer = training.Trainer(updater, (100000, "iteration"), out="mnist_result")
trainer.extend(extensions.LogReport())
trainer.extend(extensions.PrintReport(['iteration', 'main/loss', 'elapsed_time']))
trainer.extend(ChangeLearningRate(), trigger=(20000, "iteration"))
trainer.run()
La partie Encode du réseau neuronal formé ici est réutilisée telle quelle en tant que mappage vers une dimension inférieure des données.
La caractéristique de Deep Embedded Clustering est qu'il apprend en même temps «la relocalisation des données vers une dimension inférieure» et le «centre de gravité du cluster». Un réseau neuronal est utilisé pour convertir les données d'origine $ x_i $ en une dimension inférieure. Soit $ z_i $ la version de faible dimension de $ x_i $, et multiplions $ z_i $ par k-means pour initialiser le centre de gravité du cluster $ u_j $. Les paramètres de réseau neuronal qui convertissent $ x_i $ en $ z_i $ et le centre de gravité du cluster $ u_j $ sont entraînés. L'apprentissage des paramètres est effectué pour minimiser la divergence KL des deux distributions Q et P représentées ci-dessous. Dans l'apprentissage non supervisé, les paramètres ne peuvent pas être ajustés par validation croisée, donc $ \ alpha $ est fixé à 1.
q_{ij} = \frac{(1 + \|z_i - u_j\|^2 / \alpha)^{-\frac{\alpha+1}{2}}}{\sum_{j^{'}}(1 + \|z_i - u_{j^{'}}\|^2 / \alpha)^{-\frac{\alpha + 1}{2}}}
p_{ij} = \frac{q^2_{ij} / f_j}{\sum_{j^{'}}q^2_{ij^{'}} / f_{j^{'}}}\\
* Cependant, f_j = \sum_i q_{ij}
L'entraînement se poursuit jusqu'à ce que le nombre de données dont l'allocation de cluster change soit inférieur à 0,1%. Cette fois, je n'avais pas la bonne fonction d'erreur (je pense), alors je l'ai faite moi-même.
class TdistributionKLDivergence(chainer.Function):
def forward(self, inputs):
xp = cuda.get_array_module(*inputs)
z, us = inputs[0], xp.array(inputs[1:], dtype=xp.float32)
#Une matrice dans laquelle la composante ij est la distance euclidienne entre zi et uj
dist_matrix = xp.linalg.norm(xp.vstack(chain.from_iterable(map(lambda v: repeat(v, us.shape[0]), z))) - xp.vstack(repeat(us, z.shape[0])), axis= 1).reshape(z.shape[0], us.shape[0])
q_matrix = (self.tdistribution_kernel(dist_matrix).T / self.tdistribution_kernel(dist_matrix).sum(axis=1)).T
p_matrix = self.compute_pmatrix(q_matrix)
kl_divergence = (p_matrix * (xp.log(p_matrix) - xp.log(q_matrix))).sum()
return xp.array(kl_divergence),
def backward(self, inputs, grad_outputs):
xp = cuda.get_array_module(*inputs)
z, us = inputs[0], xp.array(inputs[1:], dtype=xp.float32)
gloss, = grad_outputs
gloss = gloss / z.shape[0]
# z
norms = xp.vstack(chain.from_iterable(map(lambda v: repeat(v, us.shape[0]), z))) - xp.vstack(repeat(us, z.shape[0]))
#composant ij(zi-uj)Vecteur à 10 dimensions de
#la forme est({Le nombre de données},{Numéro centroïde},10)
z_norm_matrix = norms.reshape(z.shape[0], us.shape[0], z.shape[1])
dist_matrix = xp.linalg.norm(norms, axis= 1).reshape(z.shape[0], us.shape[0])
q_matrix = (self.tdistribution_kernel(dist_matrix).T / self.tdistribution_kernel(dist_matrix).sum(axis=1)).T
p_matrix = self.compute_pmatrix(q_matrix)
#Calculer le gradient de zi et uj
gz = 2 * ((((p_matrix - q_matrix) * self.tdistribution_kernel(dist_matrix)) * z_norm_matrix.transpose(2,0,1)).transpose(1,2,0)).sum(axis=1) * gloss
gus = -2 * ((((p_matrix - q_matrix) * self.tdistribution_kernel(dist_matrix)) * z_norm_matrix.transpose(2,0,1)).transpose(1,2,0)).sum(axis=0) * gloss
g = [gz]
g.extend(gus)
grads = tuple(g)
return grads
def tdistribution_kernel(self, norm):
xp = cuda.get_array_module(norm)
return xp.power((1 + norm), -1)
def compute_pmatrix(self, q_matrix):
xp = cuda.get_array_module(q_matrix)
fj = q_matrix.sum(axis=0)
matrix = xp.power(q_matrix, 2) / fj
p_matrix = (matrix.T / matrix.sum(axis=1)).T
return p_matrix
def tdistribution_kl_divergence(z, us):
return TdistributionKLDivergence()(z, *us)
J'ai essayé le clustering MNIST en utilisant le modèle entraîné. La couleur est modifiée pour chaque étiquette et les centres de gravité du cluster sont tracés avec des triangles noirs. 500 points ont été extraits au hasard et compressés en deux dimensions avec t-SNE.
Une expérience utilisant MNIST est écrite dans l'article, mais elle n'a pas pu être séparée aussi proprement. Surtout "4" "7" "9" sont assez désordonnés. J'ai senti que cela dépendait du pré-apprentissage et de la position initiale du centre de gravité pour voir s'il se divise proprement.
Cette fois, nous avons utilisé Chainer 2.0 pour implémenter la méthode de clustering d'apprentissage en profondeur «Deep Embedded Clustering». Je n'y ai pas touché depuis que la version de Chainer est passée à 2, donc c'était une bonne étude. Si vous utilisez GPU, le temps de calcul peut aller du pré-apprentissage à l'optimisation de la cartographie et du centroïde en environ 40 minutes, j'ai donc pensé que ce ne serait pas un fardeau. Enfin, je tiens à remercier mes juniors de m'avoir présenté une technique intéressante.
Unsupervised Deep Embedding for Clustering Analysis [Session de lecture ICML] Intégration profonde non supervisée pour l'analyse de clustering Chainer: Tutoriel pour les débutants Vol.1 J'ai essayé Stacked Auto-Encoder avec Chainer Chainer Docs-Define your own function
Recommended Posts