[Python] Clustering avec un modèle gaussien infiniment mélangé

L'une des méthodes de regroupement consiste à estimer un modèle gaussien mixte. Il s'agit d'une méthode pour exprimer et regrouper un ensemble de données avec plusieurs distributions gaussiennes. Normalement, ces distributions gaussiennes sont supposées être finies, mais s'il y avait un nombre infini, quel genre de regroupement serait-il?

Dans cet article, en référence à Machine Learning Professional Series «Non-parametric Bayes Point Process and Statistical Machine Learning Mathematics» Présentation du contour et de la mise en œuvre du «modèle gaussien infiniment mélangé» et «non paramétrique». Je recommande ce livre car il est très facile à comprendre.

Je voudrais omettre la théorie détaillée et me concentrer sur le plan et l'histoire. En outre, l'implémentation doit être limitée au code source.

Introduction du modèle gaussien mixte infini

Dans un modèle gaussien mixte normal, l'ensemble de données est représenté par un nombre fini de distributions gaussiennes, et chaque donnée est associée à la distribution gaussienne qu'il contient. Le nombre de claspers (= nombre de distributions gaussiennes) doit être déterminé à l'avance, mais le nombre approprié de clusters varie en fonction de l'ensemble de données, et il n'est pas facile de le saisir.

Si le nombre de clusters est automatiquement déterminé lors de la mise en cluster, vous n'aurez pas à vous soucier des problèmes ci-dessus. Le ** modèle gaussien infiniment mélangé ** rend cela possible.

Le modèle gaussien infiniment mélangé est basé sur l'idée qu'il existe un nombre infini de distributions gaussiennes et que les données de l'ensemble de données sont associées à un nombre fini d'entre elles. La figure suivante montre le résultat du clustering, qui sera montré plus tard. (Parce que c'est gênant, je vais utiliser à nouveau le chiffre. Veuillez comprendre.)

nonparabayes_1.png

Cet ensemble de données est représenté par une distribution gaussienne de trois. Alors qu'un modèle gaussien mixte normal pense qu'il y a exactement trois distributions gaussiennes dans ce monde, un modèle gaussien mixte infini pense qu'il existe un nombre infini de distributions gaussiennes potentielles qui ne sont pas liées à un ensemble de données. En pensant de cette manière, en plus de la probabilité que les données soient connectées à trois clusters existants, la probabilité que les données soient connectées à un cluster inconnu où la moyenne est inconnue peut être calculée.

En d'autres termes, dans le clustering avec le modèle gaussien infiniment mélangé, les données sont affectées à un nouveau cluster, tandis que les clusters existants disparaissent sans être connectés à aucune donnée, augmentant ou diminuant le nombre de clusters, et finalement convergeant vers un nombre approprié. Vous pouvez.

Calcul de la distribution de probabilité

Je publierai la formule de calcul à partir d'ici. Dans une distribution gaussienne mixte normale, la *** distribution catégorielle *** est appliquée à la distribution de probabilité de $ z_i $ (lorsque $ x_ {1: N} $ [^ 4] n'est pas observé).

python


P(z_i = k | \pi) = \pi_k

Où $ \ pi: = (\ pi_1, \ cdots, \ pi_K) $ est un point sur la dimension $ (K-1) $ seule. [^ 1] Comme méthode de détermination de la valeur de $ \ pi $, il existe une méthode pour supposer une distribution uniforme ou une méthode d'estimation bayésienne en supposant une distribution a priori [^ 2].

D'autre part, le modèle gaussien infiniment mélangé applique un processus stochastique appelé ** processus de cuisson chinois **. Il s'agit d'un mécanisme pour calculer la distribution de probabilité du cluster $ z_i $ des $ i $ th données du cluster $ z_ {1: N} ^ {\ backslash i} $ des données autres que les $ i $ th.

python


P(z_i = k | z_{1:N}^{\backslash i}, \alpha) =
\left\{
\begin{array}{ll}
\frac{n_k}{N - 1 + \alpha} & (k = 1, \cdots, K) \\
\frac{\alpha}{N - 1 + \alpha} & (k = K + 1)
\end{array}
\right.

$ k = 1, \ cdots, K $ correspond au cluster existant et $ k = K + 1 $ correspond au nouveau cluster. Ici, $ n_k $ est le nombre de $ j $ tel que $ i \ neq j, z_j = k $, et $ \ alpha $ est un hyper paramètre qui prend une valeur positive. Comme vous pouvez le voir dans la formule, plus $ \ alpha $ est grand, plus il est probable qu'il soit affecté à un nouveau cluster. À partir de maintenant, j'écrirai cette distribution de probabilité sous la forme $ {\ rm CRP} (k | z_ {1: N} ^ {\ backslash i}, \ alpha) $.

Ensuite, considérons la distribution de probabilité lors de l'observation de $ x_ {1: N} $. Dans un modèle gaussien mixte normal, il peut être calculé comme suit.

python


\begin{align}
P(z_i = k | x_{1:N}, \mu_{1:K}, \Sigma_{1:K}, \pi) 
&= \frac{P(z_i = k | \pi) N(x_i | \mu_k, \Sigma_k)}{\sum_{l=1}^{K}P(z_i = l | \pi) N(x_i | \mu_l, \Sigma_l)} \\
&= \frac{\pi_k N(x_i | \mu_k, \Sigma_k)}{\sum_{l=1}^{K}\pi_l N(x_i | \mu_l, \Sigma_l)}
\end{align}

$ N (x | \ mu, \ Sigma) $ est la fonction de densité de probabilité de la distribution gaussienne multivariée. Dans le modèle gaussien infiniment mélangé, la distribution de probabilité peut être obtenue en changeant $ P (z_i = k | \ pi) $ dans l'équation ci-dessus en l'équation pour le processus de cuisson chinois.

python


\begin{align}
P(z_i = k | x_{1:N}, \mu_{1:K}, \Sigma_{1:K}, z_{1:N}^{\backslash i}, \alpha) 
&= \frac{{\rm CRP}(k | z_{1:N}^{\backslash i}, \alpha) N(x_i | \mu_k, \Sigma_k)}{\sum_{l=1}^{K+1}{\rm CRP}(l | z_{1:N}^{\backslash i}, \alpha) N(x_i | \mu_l, \Sigma_l)}
\end{align}

Il y a une mise en garde ici. Pour les clusters existants $ k = 1, \ cdots, K $, il est possible d'estimer la moyenne $ \ mu_k $ et la matrice distribuée co-distribuée $ \ Sigma_k $ à partir des données appartenant à chaque cluster. Cependant, pour le nouveau cluster $ k = K + 1 $, il n'est pas possible d'estimer directement la moyenne et la matrice distribuée co-distribuée car il n'y a pas de données appartenant au cluster. Par conséquent, cette fois, nous adopterons la méthode suivante.

-Supposant une distribution a priori pour $ \ mu_k $, marginalisant la distribution simultanée $ P (x_i, \ mu_k | \ cdots) $, et trouvant la distribution de probabilité sans estimer explicitement $ \ mu_k $ -Soit $ \ Sigma_k $ la matrice commune $ \ sigma ^ 2 I $ pour tous les clusters. ($ \ Sigma ^ 2 $ est un hyper paramètre)

Pour la moyenne, l'estimation bayésienne et la marginalisation calculent la distribution de probabilité uniquement à partir de la distribution antérieure. En ce qui concerne la matrice de co-distribution de distribution, la fixer comme ci-dessus est suffisante pour le clustering, nous ne l'évaluerons donc pas cette fois. [^ 5] Bien entendu, il est également possible d'estimer la variance et de la regrouper.

Comme il est absurde de séparer les cas, cette méthode est également appliquée lorsque $ k = 1, \ cdots, K + 1 $. Veuillez vous référer à Article précédent pour la marginalisation.

Clustering avec des baies non paramétriques

Afin d'estimer le cluster $ z_ {1: N} $ de $ N $ données, nous échantillonnerons de manière probabiliste un par un de $ z_1 $ à $ z_N $. Cette méthode est appelée ** échantillonnage de Gibbs **, et la méthode basée sur la distribution de probabilité dimensionnelle infinie comme cette fois est particulièrement appelée ** baies non paramétriques **.

Pour exécuter cette méthode, nous devons faire quelques hypothèses sur le clustering initial $ z_ {1: N} $. Cette fois, avec le même cluster $ k = 1 $ que le clustering initial, l'échantillonnage de $ z_1 $ à $ z_N $ sera répété plusieurs fois. Au départ, il n'y a qu'un seul cluster, mais c'est une image que des données éloignées de la moyenne sont combinées avec un nouveau cluster et un anneau de classe progresse tout en augmentant le nombre de clusters.

la mise en oeuvre

Implémentation du clustering avec un modèle gaussien infiniment mélangé. Il est basé sur l'implémentation de Dernière fois, mais il a été globalement repensé et la différence peut être importante.

<détails> <résumé> Code source </ résumé>

python


from collections import Counter
from functools import partial
from typing import Sequence, Tuple
import numpy as np
from scipy.special import logsumexp


def _log_gaussian(x: np.ndarray, mu: np.ndarray, var: float) -> np.ndarray:
    #2 dans l'article précédent*np.J'ai omis pi, mais par souci de clarté, je vais en tenir compte cette fois.
    d = x.shape[0]
    norm_squared = ((x - mu) * (x - mu)).sum(axis=1)
    return - d * np.log(2 * np.pi * var) / 2 - norm_squared / (2 * var)


#Classe Array pour stocker le cluster
class InfiniteClusterArray(object):
    def __init__(self, array: Sequence[int]):
        self._array = np.array(array, dtype=int)
        self._counter = Counter(array)
        self._n_clusters = max(array) + 1

    def update(self, i: int, k: int):
        assert k <= self._n_clusters

        pre_value = self._array[i]
        if pre_value == k:
            return

        if self._counter[pre_value] > 0:
            self._counter[pre_value] -= 1
        self._array[i] = k
        self._counter[k] += 1

        if k == self._n_clusters:
            self._n_clusters += 1

        if not self._counter[pre_value]:
            self._array -= 1 * (self._array > pre_value)
            self._counter = Counter(self._array)
            self._n_clusters -= 1

    @property
    def array(self) -> np.ndarray:
        return self._array.copy()

    @property
    def n_clusters(self) -> int:
        return self._n_clusters

    def count(self, k: int, excluded=None) -> int:
        assert (excluded is None) or isinstance(excluded, int)
        if excluded is None:
            return self._counter[k]

        counted = self._counter[k] - bool(self._array[excluded] == k)
        return counted

    def __getitem__(self, i: int) -> int:
        return self._array[i]

            

#Classe Array pour stocker l'ensemble de données
class DataArray(object):
    def __init__(self, array: np.ndarray):
        assert array.ndim == 2
        self._array = array

    @property
    def n(self) -> int:
        return self._array.shape[0]

    @property
    def d(self) -> int:
        return self._array.shape[1]

    def mean(self, excluded=None) -> np.ndarray:
        assert (excluded is None) or isinstance(excluded, Sequence)

        if excluded is None:
            return self._array.mean(axis=0)
        else:
            return self._array[excluded].mean(axis=0)

    def __getitem__(self, i):
        return self._array.__getitem__(i)

    def __iter__(self):
        for i in range(self.n):
            yield self._array[i]

    def __str__(self) -> str:
        return f'DataArray({self._array})'


#Classe d'échantillonnage Gaussian Gibbs infiniment mélangée
class IGMMGibbsSampling(object):
    def __init__(self, var: float, var_pri: float, alpha: float, seed=None):
        #La moyenne de la distribution précédente est définie sur la moyenne de l'ensemble de données.
        assert (var > 0) and (var_pri > 0)
        assert alpha > 0

        self.var = var
        self.var_pri = var_pri
        self.alpha = alpha
        self._random = np.random.RandomState(seed)

    def predict_clusters(self, X: np.ndarray, n_iter: int, init_z=None) -> np.ndarray:
        #Méthode d'estimation du cluster
        assert X.ndim == 2
        X = DataArray(X)
        mu_pri = X.mean()

        if init_z is None:
            init_z = np.zeros(X.n, dtype=int)
        z = InfiniteClusterArray(init_z)

        for _ in range(n_iter):
            compute_probs = partial(self._compute_pmf_zi, X, z, mu_pri)
            for i in range(X.n):
                probs = compute_probs(i)
                z_i = self._random.multinomial(1, probs)
                z_i = np.where(z_i)[0][0]  # One-hot to index
                z.update(i, z_i)

        return z.array

    def _compute_pmf_zi(self, X: DataArray, z: InfiniteClusterArray, mu_pri: np.ndarray, i: int) -> np.ndarray:
        mu, var = self._compute_marginal_distribution(X, z, mu_pri, i)
        pi = np.array([
            z.count(k, excluded=i) / (X.n - 1 + self.alpha)
            if k < z.n_clusters
            else self.alpha / (X.n - 1 + self.alpha)
            for k in range(z.n_clusters + 1)
        ])

        log_pdf_xi = _log_gaussian(X[i], mu, var)
        with np.errstate(divide='ignore'):
            pmf_zi = np.exp(np.log(pi) + log_pdf_xi - logsumexp(np.log(pi) + log_pdf_xi)[np.newaxis])

        return pmf_zi

    def _compute_marginal_distribution(self, X: DataArray, z: InfiniteClusterArray,
                                       mu_pri: np.ndarray, i: int) -> Tuple[np.ndarray, float]:

        n_vec = np.array([
            z.count(k, excluded=i)
            for k in range(z.n_clusters + 1)
        ], dtype=np.int)

        tmp_vec = 1 / (n_vec / self.var + 1 / self.var_pri)

        mu = np.tile(mu_pri / self.var_pri, (z.n_clusters + 1, 1))  # Shape (z.n_clusters + 1, X.d)
        for k in range(z.n_clusters):
            excluded_list = [
                j for j in range(X.n)
                if (j != i) and (z[j] == k)
            ]
            if excluded_list:
                mean = X.mean(excluded=excluded_list)
                mu[k] += n_vec[k] * mean / self.var
        mu *= tmp_vec[:, np.newaxis]
        var = tmp_vec + self.var

        return mu, var

Pratique de clustering

À titre de test, essayez le clustering en utilisant la classe auto-créée implémentée. Cette fois, appliquez à l'ensemble de données suivant.

nonparabayes_2.png

<détails> <résumé> Code source </ résumé>

J'utilise un bloc-notes Jupyter.

python


import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
%matplotlib inline

np.random.seed(0)
cmap = plt.rcParams['axes.prop_cycle'].by_key()['color']

pi = [0.6, 0.2, 0.2]
mu = np.array([[1, 2], [-2, 0], [3, -2]])
var = [0.8, 0.4, 0.2]
size = 100
ndim = 2

def make_data():
    z = np.random.multinomial(1, pvals=pi)
    z = np.where(z==1)[0][0]
    cov = var[z] * np.eye(2)
    
    return np.random.multivariate_normal(mu[z], cov)

X = np.array([make_data() for _ in range(100)])

fig = plt.figure()
ax = plt.axes()
ax.scatter(X[:, 0], X[:, 1], color='gray')
ax.set_aspect('equal')
plt.show()

Il s'agit de la version non clusterisée de l'ensemble de données que je vous ai montré plus tôt. Le nombre de données est de 100, et il est créé en sélectionnant au hasard parmi 3 distributions gaussiennes. Par conséquent, il convient de le diviser en trois groupes.

Maintenant, regroupons.

python


model = IGMMGibbsSampling(var=0.5, var_pri=4, alpha=0.1, seed=0)  #Passer les hyper paramètres au constructeur
clusters = model.predict_clusters(X, n_iter=10)  #Exécution du clustering

#Visualisation
fig = plt.figure()
ax = plt.axes()
for k in range(max(clusters) + 1):
    data = np.array([
        X[i, :] for i in range(X.shape[0])
        if clusters[i] == k
    ])
    ax.scatter(data[:, 0], data[:, 1], label=f'cluster {k + 1}', color=cmap[k])

ax.set_aspect('equal')
plt.legend()
plt.show()

La distribution commune var dans le cluster est un petit ensemble de données de 0,5 $, la distribution de la distribution antérieure moyenne var_pri est de 4 $ pour la faire ressembler à une distribution uniforme, et le paramètre du processus de cuisson chinois ʻalpha` est de 0,1 $. Il est défini sur $. À propos, la moyenne de la distribution antérieure de la moyenne est implémentée pour être la moyenne de l'ensemble de données. Nous transmettons l'ensemble de données et le nombre d'itérations «n_iter» à la méthode de clustering «predict_clusters». Ici, le nombre d'itérations est fixé à 10.

Le résultat est le suivant.

nonparabayes_3.png

Même si je n'ai pas spécifié le nombre de clusters et que je suis parti du même état de cluster, ils ont été divisés en 3 clusters.

Comme c'est un gros problème, examinons la transition du clustering pour chaque nombre d'itérations. Les résultats de regroupement à «n_iter = 2, 4, 6, 8, 10» sont affichés.

nonparabayes_niter2.png

nonparabayes_niter4.png

nonparabayes_niter6.png

nonparabayes_niter8.png

nonparabayes_niter10.png

Il semble qu'un grand nombre de clusters ont été créés à partir de l'état initial, puis le nombre de clusters a progressivement diminué à trois. Au fait, même si j'augmentais n_iter à partir d'ici, le nombre de clusters maintenait fondamentalement 3 états.

Réglage des hyper paramètres

Comme mentionné ci-dessus, plus l'hyper paramètre $ \ alpha $ est grand dans le processus de cuisson chinois, plus il est facile de former de nouveaux clusters. À l'origine, il y avait une raison d'omettre l'estimation du nombre de clusters, et si vous avez du mal à ajuster ce $ \ alpha $, l'importance de son introduction sera diminuée. Quand j'ai essayé en changeant $ \ alpha $, j'ai eu l'impression qu'en gros, il n'y a pas de problème s'il est réduit. La figure suivante montre le résultat de $ \ alpha = 0,0001 $ et 10 itérations.

nonparabayes_alpha10-4.png

Il est fermement divisé en 3 clusters. Même si $ \ alpha $ est assez petit, il semble qu'il puisse être clusterisé correctement sans grand impact sur le nombre d'itérations.

À propos, lorsque $ \ alpha = 1 $ a été défini, 5 clusters ont été formés au moment de 10 itérations, et le nombre n'a pas diminué à 3 après cela. Il semble que le nombre de clusters ne puisse pas être bien déterminé si $ \ alpha $ est trop grand. Il a les mêmes propriétés que la largeur de pas de l'algorithme d'optimisation utilisé en apprentissage automatique.

en conclusion

Jusqu'à présent, j'ai essayé de regrouper avec un modèle gaussien infiniment mélangé. C'était intéressant car il était divisé en nombre de grappes comme prévu.

[^ 1]: Un ensemble de $ (\ pi_1, \ cdots, \ pi_K) $ qui satisfait $ \ pi_1, \ cdots, \ pi_K \ ge 0 $ et $ \ sum_ {k = 1} ^ K \ pi_k = 1 $ Est appelée l'unité de dimension $ (K-1) $. Initialement utilisée en géométrie, l'unité de dimension $ 0 $ représente un point, l'unité de dimension $ 1 $ représente un segment de ligne et l'unité de dimension $ 2 $ représente un triangle. Si vous lisez le livre d'estimation bayésienne, vous verrez cet ensemble utilisé dans le contexte de la probabilité. [^ 2]: Il existe aussi une méthode pour estimer $ \ pi $ le plus à partir de l'observation de $ z_ {1: N} ^ {\ backslash i} $, mais cette méthode toujours $ \ lorsque le cluster $ k $ disparaît. Notez que pi_k = 0 $ et le cluster ne sera pas réactivé. [^ 4]: $ N $ variables $ x_1, \ cdots, x_N $ seront abrégés en $ x_ {1: N} $. Aussi, j'écrirai $ x_ {1: N} ^ {\ backslash i} $ pour les variables $ N-1 $ à l'exclusion des $ i $ th. [^ 5]: Même si $ \ Sigma_k $ est une valeur fixe commune aux clusters, je pense que le clustering peut être effectué avec la même précision que k-means. C'est parce que la méthode des k-moyennes équivaut à l'estimation la plus probable de chaque $ \ mu_k $ et $ z_i $ dans le modèle gaussien mixte avec une variance fixe comme décrit ci-dessus.

Recommended Posts

[Python] Clustering avec un modèle gaussien infiniment mélangé
[Python] Implémentation du clustering à l'aide d'un modèle gaussien mixte
[Python] Modèle gaussien mixte avec Pyro
Essayez le clustering avec un modèle gaussien mixte sur Jupyter Notebook
Créer un œuf avec python
Découpez une image avec python
J'ai envoyé un SMS avec Python
Modèle d'espace d'état gaussien général en Python
Dessinez une illustration avec Python + OpenCV
[Python] Envoyez des e-mails avec Outlook
[Python] Création d'un environnement avec Anaconda [Mac]
PRML Chapitre 9 Implémentation Python de distribution gaussienne mixte
Remarques lors de la création d'un environnement avec python
[Python] Créez rapidement une API avec Flask
Scraping depuis un site authentifié avec python
Ajouter du bruit gaussien aux images avec python2.7
Créez une application de mots anglais avec python
Envoyer un e-mail avec Amazon SES + Python
Rejoignez un juge en ligne avec Python 3.x
Développons un algorithme d'investissement avec Python 1
Traitement d'image par Python 100 knock # 9 Filtre Gaussien
Expliquons l'allocation d'actifs par le modèle Black Ritterman (avec un exemple d'exécution par Python)
Créez une application qui devine les étudiants avec Python
Envoyez un email à l'adresse de Spushi avec python
Construire un environnement Anaconda pour Python avec pyenv
Écrivons FizzBuzz avec une erreur: Version Python
Comment recadrer une image avec Python + OpenCV
Créer une page qui se charge indéfiniment avec python
Générez une instruction d'insertion à partir de CSV avec Python.
Créer une image avec des caractères avec python (japonais)
Résolution du modèle Lorenz 96 avec Julia et Python
J'ai essayé d'envoyer un email avec SendGrid + Python
Optimisation de portefeuille avec Python (modèle de distribution moyenne de Markovitz)
Envoyer un e-mail avec Excel en pièce jointe en Python
Créez rapidement un serveur API avec Python + Falcon
Clustering avec python-louvain
Statistiques avec python
Python avec Go
Twilio avec Python
Intégrer avec Python
Jouez avec 2016-Python
AES256 avec python
Testé avec Python
python commence par ()
Clustering avec scikit-learn (1)
avec syntaxe (Python)
Clustering avec scikit-learn (2)
Bingo avec python
Zundokokiyoshi avec python
Excel avec Python
Micro-ordinateur avec Python
Cast avec python
Créer un serveur local GIF animé avec Python + Flask
Simulez une bonne date de Noël avec un modèle optimisé Python
[# 2] Créez Minecraft avec Python. ~ Dessin du modèle et implémentation du lecteur ~
Introduction au traitement parallèle distribué Python par Ray
Note de lecture: Introduction à l'analyse de données avec Python