[PYTHON] Clustering embarqué profond avec Chainer 2.0

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

Qu'est-ce que le clustering intégré profond?

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.

  1. Pré-apprentissage du réseau neuronal
  2. Optimisation de la cartographie et du centre de gravité

L'image entière est la suivante. DEC.png

1. Pré-apprentissage du réseau neuronal

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.

2. Optimisation de la cartographie et du centre de gravité

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)

Essayez de bouger

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.

DEC_final.png

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.

À la fin

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.

Les références

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

Clustering embarqué profond avec Chainer 2.0
Clustering avec python-louvain
Classez les visages d'anime avec l'apprentissage en profondeur avec Chainer
Essayez avec Chainer Deep Q Learning - Lancement
Clustering avec scikit-learn (1)
Clustering avec scikit-learn (2)
Clustering avec scikit-learn + DBSCAN
Utiliser tensorboard avec Chainer
DBSCAN (clustering) avec scikit-learn
Essayez l'apprentissage en profondeur avec TensorFlow
Tester les logiciels embarqués avec Google Test
Essayez d'implémenter RBM avec chainer.
J'ai essayé le clustering avec PyCaret
Apprenez les orbites elliptiques avec Chainer
Apprentissage profond du noyau avec Pyro
Essayez le Deep Learning avec FPGA
Seq2Seq (3) ~ Edition CopyNet ~ avec chainer
Utilisation du chainer avec Jetson TK1
Réseau de neurones commençant par Chainer
Implémentation du GAN conditionnel avec chainer
Implémentation de SmoothGrad avec Chainer v2
Un peu coincé dans le chainer
Générez des Pokémon avec Deep Learning
Introduction au Deep Learning (2) - Essayez votre propre régression non linéaire avec Chainer-
Essayez le Deep Learning avec les concombres FPGA-Select
Faites de l'art ASCII avec l'apprentissage en profondeur
Apprenons Deep SEA avec Selene
Essayez l'apprentissage en profondeur avec TensorFlow Partie 2
[Chainer] Apprentissage de XOR avec perceptron multicouche
Première reconnaissance faciale d'anime avec Chainer
Vérifiez la forme de squat avec l'apprentissage en profondeur
Catégoriser les articles de presse grâce au Deep Learning
Utilisation de Chainer avec CentOS7 [Construction de l'environnement]
Prévisions des ventes de collations avec apprentissage en profondeur
Essayez l'apprentissage de la représentation commune avec le chainer
Faites sourire les gens avec le Deep Learning
Seq2Seq (2) ~ Attention Model edition ~ avec chainer
(python) Principes de base du chaînage de la bibliothèque d'apprentissage en profondeur