مدل آمیخته گوسی در خوشه بندی – پیاده‌سازی در پایتون

۲۵۸۹ بازدید
آخرین به‌روزرسانی: ۸ خرداد ۱۴۰۲
زمان مطالعه: ۱۴ دقیقه
دانلود PDF مقاله
مدل آمیخته گوسی در خوشه بندی – پیاده‌سازی در پایتونمدل آمیخته گوسی در خوشه بندی – پیاده‌سازی در پایتون

از الگوریتم‌های معروف در خوشه‌بندی، می‌توان به روش‌هایی اشاره کرد که براساس «مدل آمیخته گوسی» (Gaussian Mixture Model) عمل می‌کنند. به طور خلاصه این روش را با GMM نیز نشان می‌دهند. همانطور که مشخص است، در این شیوه خوشه‌بندی، فرض بر این است که هر خوشه از داده‌هایی با توزیع نرمال (گوسی) تشکیل شده و در حالت کلی نیز داده‌ها نمونه‌ای از توزیع آمیخته نرمال هستند. هدف از خوشه‌بندی مدل آمیخته گوسی یا نرمال، برآورد پارامترهای توزیع هر یک از خوشه‌ها و تعیین برچسب برای مشاهدات است. به این ترتیب مشخص می‌شود که هر مشاهده به کدام خوشه تعلق دارد. چنین روشی را در یادگیری ماشین، خوشه‌بندی برمبنای مدل می‌نامند.

997696

در شیوه خوشه‌بندی با مدل آمیخته گاوسی، برآورد پارامترهای توزیع آمیخته از یک «متغیر پنهان» (Latent Variable) استفاده می‌شود. به این ترتیب به کمک الگوریتم (EM (Expectation Maximization می‌توان برآورد مناسب برای پارامترها و مقدار متغیر پنهان به دست آورد.

برای آشنایی بیشتر با شیوه به کارگیری الگوریتم EM به مطلب الگوریتم EM و کاربردهای آن — به زبان ساده مراجعه کنید. همچنین برای آگاهی از اصول خوشه‌بندی نوشتار آشنایی با خوشه‌بندی (Clustering) و شیوه‌های مختلف آن را بخوانید. البته خواندن مطلب تابع درستنمایی (Likelihood Function) و کاربردهای آن — به زبان ساده و قضیه بیز در احتمال شرطی و کاربردهای آن نیز خالی از لطف نیست.

مدل آمیخته گوسی در خوشه‌ بندی

روش‌های خوشه‌بندی مانند kmeans قادر به شناسایی خوشه‌های کروی هستند و اگر ساختار خوشه‌ها خارج از این شکل باشد، عمل خوشه‌بندی توسط این الگوریتم‌ها به خوبی صورت نمی‌گیرد. به عنوان یک راه حل در چنین مواردی می‌توان از خوشه‌بندی برمبنای مدل یا همان مدل آمیخته گوسی استفاده کرد.

البته با توجه به ساختار خوشه‌های می‌توان از توزیع‌های دیگری نیز در این میان کمک گرفت ولی به طور معمول فرض می‌شود که مشاهدات مربوط به خوشه‌ها دارای توزیع نرمال هستند.

یک مدل آمیخته گاوسی را با نماد ریاضی به شکل زیر می‌نویسند. در اینجا منظور از πk\pi_k وزن توزیع kام یا فراوانی مشاهدات از آن توزیع است.

p(x)=k=1KπkN(xμk,Σk),      k=1Kπk=1\large p(x)=\sum_{k=1}^K\pi_k\cal{N}(x|\mu_k,\Sigma_k),\;\;\;\sum_{k=1}^K\pi_k=1

فرض کنید تعداد خوشه‌ها برابر با kk باشد. در اینجا پارامترهای توزیع گوسی (نرمال) نیز با θ\theta نشان داده می‌شوند. متغیر پنهان که به صورت یک بردار، برچسب هر یک از مشاهدات را نشان می‌دهد با ZZ‌ مشخص شده‌ است. در این صورت مدل آمیخته گوسی یک بُعدی برای این خوشه‌های به صورت زیر نوشته می‌شود.

p(Xθ)=zp(X,Zθ)\large p(X|\theta)=\sum_z p(X,Z|\theta)

در نتیجه هدف از اجرای خوشه‌بندی برمبنای مدل، بیشینه‌سازی این تابع درستنمایی است.

نکته: توجه داشته باشید که مقدار z به صورت دو دویی یا باینری است به این معنی که اگر مشاهده nام به خوشه kام تعلق داشته باشد مقدار این متغیر برابر با ۱ و در غیر اینصورت برابر با صفر خواهد بود.

البته از آنجایی که بیشینه‌سازی این تابع با بیشینه‌سازی لگاریتم آن مشابه است و لگاریتم‌گیری، فرم تابع احتمال آمیخته گوسی (نرمال) را ساده‌تر می‌کند از رابطه زیر برای اجرای الگوریتم خوشه‌بندی برمبنای مدل استفاده خواهیم کرد.

ln[(p(Xθ)]=ln{zp(X,Zθ)}\large \ln[(p(X|\theta)]=\ln \{\sum_z p(X,Z|\theta)\}

در تصویر زیر یک نمونه از توزیع آمیخته گاوسی (ترکیب سه متغیر تصادفی نرمال) را مشاهده می‌کنید.

Gaussian-mixture-example

متاسفانه وجود پارامترهای زیاد در این تابع (مانند میانگین و واریانس و همچنین کوواریانس بین خوشه‌ها) امکان بیشینه‌سازی آن را با روش تحلیلی نمی‌دهد. بنابراین در چنین مواقعی استفاده از تکنیک‌های عددی مانند الگوریتم EM کارساز است.

الگوریتم EM

به منظور برآورد پارامترهای مدل آمیخته گاوسی از الگوریتم EM استفاده خواهیم کرد. این الگوریتم دارای دو بخش یا گام است. گام متوسط گیری یا Expectation و گام بیشینه‌سازی یا Maximization.

در گام اول (E-step) یا گام متوسط‌گیری، هدف برآورد توزیع متغیر پنهان به شرط وزن (π\pi)، میانگین‌ها (Means) و ماتریس کوواریانس (Σ\Sigma) توزیع آمیخته نرمال است. بردار پارامترها را در این جا با نماد θ\theta نشان می‌دهیم. برای این کار ابتدا یک حدس اولیه برای این پارامترها زده می‌شود سپس گام‌های به ترتیب برداشته می‌شوند. همانطور که خواهید دید، حدس‌های اولیه را می‌توان بوسیله الگوریتم kmeans بدست آورد. به این معنی که برای خوشه‌های حاصل از الگوریتم kmeans، میانگین، ماتریس کوواریانس و وزن‌ها محاسبه می‌شود. توجه داشته باشید که منظور از وزن، درصدی (احتمال) از داده‌ها است که در یک خوشه قرار دارند.

در گام دوم (M-step) با استفاده از متغیرهای پنهان، سعی خواهیم کرد تابع درستنمایی را نسبت به پارامترهای θ\theta بیشینه کنیم. این مراحل (گام E و گام M) تا زمانی که الگوریتم همگرا شود (مقدار تابع درستنمایی تغییر نکند)، ادامه خواهد داشت. به این ترتیب الگوریتم EM علاوه بر برآورد پارامترهای توزیع آمیخته گاوسی، برچسب‌ها یا مقدار متغیر پنهان را نیز مشخص می‌کند.

گام اول (E-Step) برای GMM

همانطور که قبلا اشاره شد، تابع توزیع آمیخته گاوسی را می‌توان به صورت ترکیب وزنی از چندین توزیع نرمال نوشت. بنابراین اگر وزن‌ها را با πk\pi_k نشان دهیم، برای KK خوشه یا توزیع نرمال، تابع چگالی آمیخته در نقطه xx به صورت زیر نوشته خواهد شد.

p(x)=k=1KπkN(xul,Σk)\large p(x)=\sum_{k=1}^K \pi_k\cal{N}(x|\,u_l,\Sigma_k)

به کمک قانون بیز در احتمال شرطی تابع احتمال پسین برای متغیر پنهان ZZ که به صورت γ(xnk)\gamma(x_{nk}) نشان داده شده، به صورت زیر در خواهد آمد. البته در نظر داشته باشید که تابع احتمال پیشین در این حالت همان π\pi است.

γ(znk)=πkN(xnμk,Σk)j=1KπjN(xnμj,Σj)\large \gamma(z_{nk})=\dfrac{\pi_k\cal{N}(x_n|\mu_k,\Sigma_k)}{\sum_{j=1}^K\pi_j\cal{N}(x_n|\mu_j,\Sigma_j)}

مشخص است که znkz_{nk} مقدار متغیر پنهان برای مشاهده nام در خوشه kام است. همچنین باید توجه داشت که منظور از μk\mu_k، میانگین و Σk\Sigma_k نیز ماتریس کوواریانس خوشه kام در نظر گرفته شده است. برای سادگی به جای نوشتن تابع چگالی احتمال توزیع نرمال از نماد N(xμ,Σ)\cal{N}(x|\mu,\Sigma) استفاده کرده‌ایم.

گام دوم (M-Step) برای GMM

پس از محاسبه تابع احتمال پسین برای متغیر پنهان، باید پارامترهای توزیع آمیخته گاوسی را برآورد کرده سپس مقدار لگاریتم تابع درستنمایی را محاسبه کنیم. این گام‌ها تا رسیدن به همگرایی در مقدار تابع درستنمایی تکرار خواهد شد.

میانگین برای خوشه‌ kام به صورت زیر برآورد می‌شود.

μknew=1NKn=1Nγ(znk)xn\large \mu_k^{new}=\dfrac{1}{N_K}\sum_{n=1}^N\gamma(z_{nk})x_n

ذکر این نکته نیز ضروری است که در رابطه بالا، NkN_k تعداد اعضای خوشه kام را نشان می‌دهد. همچنین برای برآورد ماتریس کوواریانس خوشه kام نیز از رابطه زیر کمک می‌گیریم.

Σknew=1NKn=1Nγ(znk)(xnμknew)(xnμknew)T\large \Sigma_k^{new}=\dfrac{1}{N_K}\sum_{n=1}^N\gamma(z_{nk})(x_n-\mu_k^{new})(x_n-\mu_k^{new})^T

برای برآورد وزن‌ برای هر یک از توزیع‌ها نیز از رابطه زیر استفاده می‌کنیم.

πknew=NkN,      Nk=n=1Nγ(xznk)\large \pi_k^{new}=\dfrac{N_k}{N}, \;\;\;N_k=\sum_{n=1}^N\gamma(xz_{nk})

به این ترتیب تابع که در ادامه قابل مشاهده است باید بیشینه شود.

lnp(Xμ,Σ,π)=n=1Nln{k=1KπkN(xnμk,Σk)}\large \ln p(X|\mu,\Sigma,\pi)=\sum_{n=1}^N\ln \Big\{\sum_{k=1}^K\pi_k\cal{N}(x_n|\mu_k,\Sigma_k)\Big\}

حال با توجه به اینکه لگاریتم یک تابع مقعر محسوب می‌شود از نامساوی جنسن (Jensen Inequlity) استفاده کرده و سعی می‌کنیم تابع زیر که کران پایین برای تابع بالا است را بیشینه کنیم.

L=n=1Nk=1KE[znk]{lnπk+lnN(xnμk,Σk)}\large L=\sum_{n=1}^N\sum_{k=1}^K \operatorname{E}[z_{nk}]\Big\{\ln \pi_k+\ln {N}(x_n|\mu_k,\Sigma_k)\Big\}

این کارها و مراحل مبنای الگوریتم EM محسوب می‌شوند. در ادامه به کمک زبان برنامه‌نویسی پایتون و توابع آن الگوریتم EM را برای خوشه‌بندی برمبنای مدل GMM به کار خواهیم گرفت.

الگوریتم EM و پیاده سازی در پایتون

بعد از اینکه در مورد تئوری الگوریتم خوشه‌بندی برمبنای مدل آمیخته گاوسی صحبت کردیم، زمان پیاده‌سازی آن با پایتون رسیده است. به این منظور نقاط یا حدس اولیه را براساس روش خوشه‌بندی kmeans محاسبه و از این مقدارها برای الگوریتم EM استفاده خواهیم کرد.

کد زیر به این منظور تهیه شده است. البته توجه داشته باشید که منبع داده یک فایل متنی است که مربوط به مشخصات ای‌میل‌ها کاربران است. ابتدا کتابخانه‌ها را به همراه فایل داده فراخوانی می‌کنیم.

1import numpy as np # linear algebra
2import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
3from sklearn.cluster import KMeans 
4from sklearn.feature_extraction.text import TfidfVectorizer
5from sklearn.decomposition import PCA
6from sklearn.preprocessing import normalize
7from sklearn.metrics import pairwise_distances
8
9import nltk
10import string
11
12import matplotlib.pyplot as plt
13%matplotlib inline
14plt.style.use('fivethirtyeight')
15
16
17# email module has some useful functions
18import os, sys, email,re
19
20df = pd.read_csv('../input/emails.csv',nrows = 35000)
21
22sklearn_pca = PCA(n_components = 2)
23Y_sklearn = sklearn_pca.fit_transform(tf_idf_array)
24kmeans = KMeans(n_clusters=3, max_iter=600, algorithm = 'auto')
25fitted = kmeans.fit(Y_sklearn)
26prediction = kmeans.predict(Y_sklearn)
مشاهده کامل کدها

مشخص است که برای از بین بردن واحد اندازه‌گیری داده‌ها از تکنیک PCA‌ استفاده شده است. با توجه به خوشه‌سازی اولیه توسط الگوریتم kmeans بهتر است آن‌ها را به تصویر بکشیم. توجه دارید که خوشه‌بندی به روش kmeans ساختار کروی (Spherical) ایجاد کرده و تقریبا تعداد اعضای خوشه‌های یکسان خواهد بود. به همین دلیل این تکنیک برای داده‌های ما که ساختاری کروی ندارند، مناسب نخواهد بود. ولی به عنوان نقطه آغاز از نتایج آن استفاده خواهیم کرد. کد زیر به منظور ترسیم خوشه‌های به شیوه kmeans برای مشاهدات ایجاد شده است.

1plt.figure(figsize = (10,8))
2from scipy.spatial.distance import cdist
3def plot_kmeans(kmeans, X, n_clusters=3, rseed=0, ax=None):
4    labels = kmeans.fit_predict(X)
5
6    # plot the input data
7    ax = ax or plt.gca()
8    ax.axis('equal')
9    ax.scatter(X[:, 0], X[:, 1], c=labels, s=40, cmap='viridis', zorder=2)
10
11    # plot the representation of the KMeans model
12    centers = kmeans.cluster_centers_
13    radii = [cdist(X[labels == i], [center]).max()
14             for i, center in enumerate(centers)]
15    for c, r in zip(centers, radii):
16        ax.add_patch(plt.Circle(c, r, fc='#CCCCCC', lw=3, alpha=0.5, zorder=1))
17        
18plot_kmeans(kmeans, Y_sklearn)
مشاهده کامل کدها

نتیجه اجرای این کد، نموداری به صورت زیر است.

k-means spherical shape

در تصویر زیر نتیجه خوشه‌بندی براساس GMM را روی یک مجموعه داده فرضی می‌بینید. به وضوح مشخص است که سه خوشه وجود دارد که البته ساختار کروی نیز دارند.

GMM simple data set

کلاس GMM در پایتون

برای تعریف خوشه‌بندی برمبنای مدل GMM کدهای زیر را تهیه کرده‌ایم.

این کد براساس تعداد تکراری که در max_iter مشخص شده، محاسبات مربوط به الگوریتم EM را تکرار می‌کند.

1class GMM:
2    """ Gaussian Mixture Model
3    
4    Parameters
5    -----------
6        k: int , number of gaussian distributions
7        
8        seed: int, will be randomly set if None
9        
10        max_iter: int, number of iterations to run algorithm, default: 200
11        
12    Attributes
13    -----------
14       centroids: array, k, number_features
15       
16       cluster_labels: label for each data point
17       
18    """
19    def __init__(self, C, n_runs):
20        self.C = C # number of Guassians/clusters
21        self.n_runs = n_runs
22        
23    
24    def get_params(self):
25        return (self.mu, self.pi, self.sigma)
26    
27    
28        
29    def calculate_mean_covariance(self, X, prediction):
30        """Calculate means and covariance of different
31            clusters from k-means prediction
32        
33        Parameters:
34        ------------
35        prediction: cluster labels from k-means
36        
37        X: N*d numpy array data points 
38        
39        Returns:
40        -------------
41        intial_means: for E-step of EM algorithm
42        
43        intial_cov: for E-step of EM algorithm
44        
45        """
46        d = X.shape[1]
47        labels = np.unique(prediction)
48        self.initial_means = np.zeros((self.C, d))
49        self.initial_cov = np.zeros((self.C, d, d))
50        self.initial_pi = np.zeros(self.C)
51        
52        counter=0
53        for label in labels:
54            ids = np.where(prediction == label) # returns indices
55            self.initial_pi[counter] = len(ids[0]) / X.shape[0]
56            self.initial_means[counter,:] = np.mean(X[ids], axis = 0)
57            de_meaned = X[ids] - self.initial_means[counter,:]
58            Nk = X[ids].shape[0] # number of data points in current gaussian
59            self.initial_cov[counter,:, :] = np.dot(self.initial_pi[counter] * de_meaned.T, de_meaned) / Nk
60            counter+=1
61        assert np.sum(self.initial_pi) == 1    
62            
63        return (self.initial_means, self.initial_cov, self.initial_pi)
مشاهده کامل کدها

قطعه کد بعدی به منظور استفاده از نقاط اولیه که توسط الگوریتم kmeans ایجاد شد، نوشته شده است. توجه داشته باشید که این قسمت فقط یک تابع است که در داخل برنامه اصلی صدا زده می‌شود. در داخلی این تابع شیوه محاسبه ماتریس کوواریانس که در قسمت قبل محاسبه شده است بازخوانی شده و به کار گرفته می‌شود.

1class GMM:
2    """ Gaussian Mixture Model
3    
4    Parameters
5    -----------
6        k: int , number of gaussian distributions
7        
8        seed: int, will be randomly set if None
9        
10        max_iter: int, number of iterations to run algorithm, default: 200
11        
12    Attributes
13    -----------
14       centroids: array, k, number_features
15       
16       cluster_labels: label for each data point
17       
18    """
19    def __init__(self, C, n_runs):
20        self.C = C # number of Guassians/clusters
21        self.n_runs = n_runs
22        
23    
24    def get_params(self):
25        return (self.mu, self.pi, self.sigma)
26    
27    
28        
29    def calculate_mean_covariance(self, X, prediction):
30        """Calculate means and covariance of different
31            clusters from k-means prediction
32        
33        Parameters:
34        ------------
35        prediction: cluster labels from k-means
36        
37        X: N*d numpy array data points 
38        
39        Returns:
40        -------------
41        intial_means: for E-step of EM algorithm
42        
43        intial_cov: for E-step of EM algorithm
44        
45        """
46        d = X.shape[1]
47        labels = np.unique(prediction)
48        self.initial_means = np.zeros((self.C, d))
49        self.initial_cov = np.zeros((self.C, d, d))
50        self.initial_pi = np.zeros(self.C)
51        
52        counter=0
53        for label in labels:
54            ids = np.where(prediction == label) # returns indices
55            self.initial_pi[counter] = len(ids[0]) / X.shape[0]
56            self.initial_means[counter,:] = np.mean(X[ids], axis = 0)
57            de_meaned = X[ids] - self.initial_means[counter,:]
58            Nk = X[ids].shape[0] # number of data points in current gaussian
59            self.initial_cov[counter,:, :] = np.dot(self.initial_pi[counter] * de_meaned.T, de_meaned) / Nk
60            counter+=1
61        assert np.sum(self.initial_pi) == 1    
62            
63        return (self.initial_means, self.initial_cov, self.initial_pi)
مشاهده کامل کدها

حال لازم است که بخش E-step را در پایتون پیاده‌سازی کنیم. از آنجایی که در این گام به توزیع چندمتغیره نرمال احتیاج داریم بهتر است که با دستور زیر کتابخانه مربوطه را فراخوانی کنیم.

1from scipy.stats import multivariate_normal as mvn

در کد زیر بخش مربوط به E-step برای الگوریتم خوشه‌بندی برمبنای مدل GMM نوشته شده است.

1      def _e_step(self, X, pi, mu, sigma):
2        """Performs E-step on GMM model
3
4        Parameters:
5        ------------
6        X: (N x d), data points, m: no of features
7        pi: (C), weights of mixture components
8        mu: (C x d), mixture component means
9        sigma: (C x d x d), mixture component covariance matrices
10
11        Returns:
12        ----------
13        gamma: (N x C), probabilities of clusters for objects
14        """
15        N = X.shape[0] 
16        self.gamma = np.zeros((N, self.C))
17
18        const_c = np.zeros(self.C)
19        
20        
21        self.mu = self.mu if self._initial_means is None else self._initial_means
22        self.pi = self.pi if self._initial_pi is None else self._initial_pi
23        self.sigma = self.sigma if self._initial_cov is None else self._initial_cov
24
25        for c in range(self.C):
26            # Posterior Distribution using Bayes Rule
27            self.gamma[:,c] = self.pi[c] * mvn.pdf(X, self.mu[c,:], self.sigma[c])
28
29        # normalize across columns to make a valid probability
30        gamma_norm = np.sum(self.gamma, axis=1)[:,np.newaxis]
31        self.gamma /= gamma_norm
32
33        return self.gamma
مشاهده کامل کدها

پس از آنکه تابع احتمال پسین برای Z بدست آمد، گام بیشینه‌سازی یا M-step آغاز می‌شود و برآورد برای پرامترهای توزیع آمیخته بدست می‌آید.

1      def _m_step(self, X, gamma):
2        """Performs M-step of the GMM
3        We need to update our priors, our means
4        and our covariance matrix.
5
6        Parameters:
7        -----------
8        X: (N x d), data 
9        gamma: (N x C), posterior distribution of lower bound 
10
11        Returns:
12        ---------
13        pi: (C)
14        mu: (C x d)
15        sigma: (C x d x d)
16        """
17        N = X.shape[0] # number of objects
18        C = self.gamma.shape[1] # number of clusters
19        d = X.shape[1] # dimension of each object
20
21        # responsibilities for each gaussian
22        self.pi = np.mean(self.gamma, axis = 0)
23
24        self.mu = np.dot(self.gamma.T, X) / np.sum(self.gamma, axis = 0)[:,np.newaxis]
25
26        for c in range(C):
27            x = X - self.mu[c, :] # (N x d)
28            
29            gamma_diag = np.diag(self.gamma[:,c])
30            x_mu = np.matrix(x)
31            gamma_diag = np.matrix(gamma_diag)
32
33            sigma_c = x.T * gamma_diag * x
34            self.sigma[c,:,:]=(sigma_c) / np.sum(self.gamma, axis = 0)[:,np.newaxis][c]
35
36        return self.pi, self.mu, self.sigma
37    
38    
39    def _compute_loss_function(self, X, pi, mu, sigma):
40        """Computes lower bound loss function
41        
42        Parameters:
43        -----------
44        X: (N x d), data 
45        
46        Returns:
47        ---------
48        pi: (C)
49        mu: (C x d)
50        sigma: (C x d x d)
51        """
52        N = X.shape[0]
53        C = self.gamma.shape[1]
54        self.loss = np.zeros((N, C))
55
56        for c in range(C):
57            dist = mvn(self.mu[c], self.sigma[c],allow_singular=True)
58            self.loss[:,c] = self.gamma[:,c] * (np.log(self.pi[c]+0.00001)+dist.logpdf(X)-np.log(self.gamma[:,c]+0.000001))
59        self.loss = np.sum(self.loss)
60        return self.loss
مشاهده کامل کدها

همانطور که دیده شد، ابتدا با استفاده از kmeans نقاط اولیه تولید و سپس با گام‌های E-step و M-step الگوریتم EM فعال شدند. با توجه به تعداد تکرار مجاز برای الگوریتم، مقدارهای پارامترها از جمله متغیر پنهان که برچسب مربوط به مشاهدات و تعلق به خوشه را مشخص می‌کند، برآورد شدند.

1      def fit(self, X):
2        """Compute the E-step and M-step and
3            Calculates the lowerbound
4        
5        Parameters:
6        -----------
7        X: (N x d), data 
8        
9        Returns:
10        ----------
11        instance of GMM
12        
13        """
14        
15        d = X.shape[1]
16        self.mu, self.sigma, self.pi =  self._initialise_parameters(X)
17        
18        try:
19            for run in range(self.n_runs):  
20                self.gamma  = self._e_step(X, self.mu, self.pi, self.sigma)
21                self.pi, self.mu, self.sigma = self._m_step(X, self.gamma)
22                loss = self._compute_loss_function(X, self.pi, self.mu, self.sigma)
23                
24                if run % 10 == 0:
25                    print("Iteration: %d Loss: %0.6f" %(run, loss))
26
27        
28        except Exception as e:
29            print(e)
30            
31        
32        return self
مشاهده کامل کدها

کدی که در ادامه قابل مشاهده است به منظور استفاده از مدل حاصل برای خوشه‌بندی داده‌هایی است که باید برچسب آن‌ها پبیش‌بینی شود. به همین دلیل تابع predict را تعریف کرده‌ایم. به این ترتیب احتمال تعلق هر مشاهده به خوشه‌ها توسط predict_proba محاسبه می‌شود. با این کار می‌توان هر مشاهده را به خوشه‌ای متعلق دانست که بزرگترین احتمال را برای آن دارد.

1     def predict(self, X):
2        """Returns predicted labels using Bayes Rule to
3        Calculate the posterior distribution
4        
5        Parameters:
6        -------------
7        X: N*d numpy array
8        
9        Returns:
10        ----------
11        labels: predicted cluster based on 
12        highest responsibility gamma.
13        
14        """
15        labels = np.zeros((X.shape[0], self.C))
16        
17        for c in range(self.C):
18            labels [:,c] = self.pi[c] * mvn.pdf(X, self.mu[c,:], self.sigma[c])
19        labels  = labels.argmax(1)
20        return labels 
21    
22    def predict_proba(self, X):
23        """Returns predicted labels
24        
25        Parameters:
26        -------------
27        X: N*d numpy array
28        
29        Returns:
30        ----------
31        labels: predicted cluster based on 
32        highest responsibility gamma.
33        
34        """
35        post_proba = np.zeros((X.shape[0], self.C))
36        
37        for c in range(self.C):
38            # Posterior Distribution using Bayes Rule
39            post_proba[:,c] = self.pi[c] * mvn.pdf(X, self.mu[c,:], self.sigma[c])
40    
41        return post_proba
مشاهده کامل کدها

در ادامه برنامه‌ای نوشته شده است که براساس سه توزیع مختلف نرمال، داده‌ها را خوشه‌بندی می‌کند. از نتایج حاصل از این برنامه برای ترسیم خوشه‌ها استفاده خواهیم کرد.

1model = GMM(3, n_runs = 30)
2
3fitted_values = model.fit(Y_sklearn)
4predicted_values = model.predict(Y_sklearn)
5
6# # compute centers as point of highest density of distribution
7centers = np.zeros((3,2))
8for i in range(model.C):
9    density = mvn(cov=model.sigma[i], mean=model.mu[i]).logpdf(Y_sklearn)
10    centers[i, :] = Y_sklearn[np.argmax(density)]
11    
12plt.figure(figsize = (10,8))
13plt.scatter(Y_sklearn[:, 0], Y_sklearn[:, 1],c=predicted_values ,s=50, cmap='viridis', zorder=1)
14
15plt.scatter(centers[:, 0], centers[:, 1],c='black', s=300, alpha=0.5, zorder=2);
16
17w_factor = 0.2 / model.pi.max()
18for pos, covar, w in zip(model.mu, model.sigma, model.pi):
19    draw_ellipse(pos, covar, alpha = w)
مشاهده کامل کدها

تصویری که توسط این برنامه ایجاد می‌شود، خوشه‌ها را به خوبی نشان می‌دهد. مشخص است که ظاهر این خوشه‌ها کروی نبوده و حتی ممکن است بعضی از داده‌ها مربوط به دو خوشه به یکدیگر نزدیکتر (از نظر فاصله اقلیدسی) نیز باشند ولی از آنجایی که در توزیع احتمال آن خوشه قرار ندارند، با هم در یک خوشه قرار نگرفته‌اند.

GMM with Gaussian full model

همانطور که مشخص است برعکس تصویر قبلی توزیع‌ها به یکدیگر وابسته بوده و دارای کوواریانس غیر صفر (یا همبستگی مخالف صفر) هستند.

استفاده از کتابخانه sklearn برای GMM

کتابخانه sklearn نیز ابزارات متنوع و خوبی برای خوشه‌بندی برمبنای مدل برای توزیع آمیخته گاوسی (GMM) دارد.

به منظور بررسی ابزارات این کتابخانه، مسئله قبلی را با توجه به توابع موجود در این کتابخانه مجددا مورد بررسی قرار می‌دهیم.

1from sklearn.mixture import GaussianMixture
2sklearn_pca = PCA(n_components = 2)
3Y_sklearn = sklearn_pca.fit_transform(tf_idf_array)
4gmm = GaussianMixture(n_components=3, covariance_type='full').fit(Y_sklearn)
5prediction_gmm = gmm.predict(Y_sklearn)
6probs = gmm.predict_proba(Y_sklearn)
7
8centers = np.zeros((3,2))
9for i in range(3):
10    density = mvn(cov=gmm.covariances_[i], mean=gmm.means_[i]).logpdf(Y_sklearn)
11    centers[i, :] = Y_sklearn[np.argmax(density)]
12
13plt.figure(figsize = (10,8))
14plt.scatter(Y_sklearn[:, 0], Y_sklearn[:, 1],c=prediction_gmm ,s=50, cmap='viridis')
15plt.scatter(centers[:, 0], centers[:, 1],c='black', s=300, alpha=0.6);
مشاهده کامل کدها

به نظر می‌رسد نتایج شبیه هستند. البته اگر در مقدار مربوط به مراکز هر خوشه تفاوتی به نظر برسد، علت می‌تواند استفاده از نقاط اولیه متفاوت در کتابخانه sklearn و کدهایی باشد که در بخش بالایی ظاهر شده بود. به هر حال حاصل خوشه‌بندی مشاهدات به یکدیگر بسیار شبیه است.

GMM with Gaussian full model sklearn

به منظور دسترسی به کدها و حفظ توالی آن‌ها، در ادامه برنامه کامل GMM در پایتون در قالب یک class نوشته شده است.

1class GMM:
2    """ Gaussian Mixture Model
3    
4    Parameters
5    -----------
6        k: int , number of gaussian distributions
7        
8        seed: int, will be randomly set if None
9        
10        max_iter: int, number of iterations to run algorithm, default: 200
11        
12    Attributes
13    -----------
14       centroids: array, k, number_features
15       
16       cluster_labels: label for each data point
17       
18    """
19    def __init__(self, C, n_runs):
20        self.C = C # number of Guassians/clusters
21        self.n_runs = n_runs
22        
23    
24    def get_params(self):
25        return (self.mu, self.pi, self.sigma)
26    
27    
28        
29    def calculate_mean_covariance(self, X, prediction):
30        """Calculate means and covariance of different
31            clusters from k-means prediction
32        
33        Parameters:
34        ------------
35        prediction: cluster labels from k-means
36        
37        X: N*d numpy array data points 
38        
39        Returns:
40        -------------
41        intial_means: for E-step of EM algorithm
42        
43        intial_cov: for E-step of EM algorithm
44        
45        """
46        d = X.shape[1]
47        labels = np.unique(prediction)
48        self.initial_means = np.zeros((self.C, d))
49        self.initial_cov = np.zeros((self.C, d, d))
50        self.initial_pi = np.zeros(self.C)
51        
52        counter=0
53        for label in labels:
54            ids = np.where(prediction == label) # returns indices
55            self.initial_pi[counter] = len(ids[0]) / X.shape[0]
56            self.initial_means[counter,:] = np.mean(X[ids], axis = 0)
57            de_meaned = X[ids] - self.initial_means[counter,:]
58            Nk = X[ids].shape[0] # number of data points in current gaussian
59            self.initial_cov[counter,:, :] = np.dot(self.initial_pi[counter] * de_meaned.T, de_meaned) / Nk
60            counter+=1
61        assert np.sum(self.initial_pi) == 1    
62            
63        return (self.initial_means, self.initial_cov, self.initial_pi)
64    
65    
66    
67    def _initialise_parameters(self, X):
68        """Implement k-means to find starting
69            parameter values.
70            https://datascience.stackexchange.com/questions/11487/how-do-i-obtain-the-weight-and-variance-of-a-k-means-cluster
71
72        Parameters:
73        ------------
74        X: numpy array of data points
75        
76        Returns:
77        ----------
78        tuple containing initial means and covariance
79        
80        _initial_means: numpy array: (C*d)
81        
82        _initial_cov: numpy array: (C,d*d)
83        
84        
85        """
86        n_clusters = self.C
87        kmeans = KMeans(n_clusters= n_clusters, init="k-means++", max_iter=500, algorithm = 'auto')
88        fitted = kmeans.fit(X)
89        prediction = kmeans.predict(X)
90        self._initial_means, self._initial_cov, self._initial_pi = self.calculate_mean_covariance(X, prediction)
91        
92        
93        return (self._initial_means, self._initial_cov, self._initial_pi)
94            
95        
96        
97    def _e_step(self, X, pi, mu, sigma):
98        """Performs E-step on GMM model
99
100        Parameters:
101        ------------
102        X: (N x d), data points, m: no of features
103        pi: (C), weights of mixture components
104        mu: (C x d), mixture component means
105        sigma: (C x d x d), mixture component covariance matrices
106
107        Returns:
108        ----------
109        gamma: (N x C), probabilities of clusters for objects
110        """
111        N = X.shape[0] 
112        self.gamma = np.zeros((N, self.C))
113
114        const_c = np.zeros(self.C)
115        
116        
117        self.mu = self.mu if self._initial_means is None else self._initial_means
118        self.pi = self.pi if self._initial_pi is None else self._initial_pi
119        self.sigma = self.sigma if self._initial_cov is None else self._initial_cov
120
121        for c in range(self.C):
122            # Posterior Distribution using Bayes Rule
123            self.gamma[:,c] = self.pi[c] * mvn.pdf(X, self.mu[c,:], self.sigma[c])
124
125        # normalize across columns to make a valid probability
126        gamma_norm = np.sum(self.gamma, axis=1)[:,np.newaxis]
127        self.gamma /= gamma_norm
128
129        return self.gamma
130    
131    
132    def _m_step(self, X, gamma):
133        """Performs M-step of the GMM
134        We need to update our priors, our means
135        and our covariance matrix.
136
137        Parameters:
138        -----------
139        X: (N x d), data 
140        gamma: (N x C), posterior distribution of lower bound 
141
142        Returns:
143        ---------
144        pi: (C)
145        mu: (C x d)
146        sigma: (C x d x d)
147        """
148        N = X.shape[0] # number of objects
149        C = self.gamma.shape[1] # number of clusters
150        d = X.shape[1] # dimension of each object
151
152        # responsibilities for each gaussian
153        self.pi = np.mean(self.gamma, axis = 0)
154
155        self.mu = np.dot(self.gamma.T, X) / np.sum(self.gamma, axis = 0)[:,np.newaxis]
156
157        for c in range(C):
158            x = X - self.mu[c, :] # (N x d)
159            
160            gamma_diag = np.diag(self.gamma[:,c])
161            x_mu = np.matrix(x)
162            gamma_diag = np.matrix(gamma_diag)
163
164            sigma_c = x.T * gamma_diag * x
165            self.sigma[c,:,:]=(sigma_c) / np.sum(self.gamma, axis = 0)[:,np.newaxis][c]
166
167        return self.pi, self.mu, self.sigma
168    
169    
170    def _compute_loss_function(self, X, pi, mu, sigma):
171        """Computes lower bound loss function
172        
173        Parameters:
174        -----------
175        X: (N x d), data 
176        
177        Returns:
178        ---------
179        pi: (C)
180        mu: (C x d)
181        sigma: (C x d x d)
182        """
183        N = X.shape[0]
184        C = self.gamma.shape[1]
185        self.loss = np.zeros((N, C))
186
187        for c in range(C):
188            dist = mvn(self.mu[c], self.sigma[c],allow_singular=True)
189            self.loss[:,c] = self.gamma[:,c] * (np.log(self.pi[c]+0.00001)+dist.logpdf(X)-np.log(self.gamma[:,c]+0.000001))
190        self.loss = np.sum(self.loss)
191        return self.loss
192    
193    
194    
195    def fit(self, X):
196        """Compute the E-step and M-step and
197            Calculates the lowerbound
198        
199        Parameters:
200        -----------
201        X: (N x d), data 
202        
203        Returns:
204        ----------
205        instance of GMM
206        
207        """
208        
209        d = X.shape[1]
210        self.mu, self.sigma, self.pi =  self._initialise_parameters(X)
211        
212        try:
213            for run in range(self.n_runs):  
214                self.gamma  = self._e_step(X, self.mu, self.pi, self.sigma)
215                self.pi, self.mu, self.sigma = self._m_step(X, self.gamma)
216                loss = self._compute_loss_function(X, self.pi, self.mu, self.sigma)
217                
218                if run % 10 == 0:
219                    print("Iteration: %d Loss: %0.6f" %(run, loss))
220
221        
222        except Exception as e:
223            print(e)
224            
225        
226        return self
227    
228    
229    
230    
231    def predict(self, X):
232        """Returns predicted labels using Bayes Rule to
233        Calculate the posterior distribution
234        
235        Parameters:
236        -------------
237        X: ?*d numpy array
238        
239        Returns:
240        ----------
241        labels: predicted cluster based on 
242        highest responsibility gamma.
243        
244        """
245        labels = np.zeros((X.shape[0], self.C))
246        
247        for c in range(self.C):
248            labels [:,c] = self.pi[c] * mvn.pdf(X, self.mu[c,:], self.sigma[c])
249        labels  = labels .argmax(1)
250        return labels 
251    
252    def predict_proba(self, X):
253        """Returns predicted labels
254        
255        Parameters:
256        -------------
257        X: N*d numpy array
258        
259        Returns:
260        ----------
261        labels: predicted cluster based on 
262        highest responsibility gamma.
263        
264        """
265        post_proba = np.zeros((X.shape[0], self.C))
266        
267        for c in range(self.C):
268            # Posterior Distribution using Bayes Rule, try and vectorise
269            post_proba[:,c] = self.pi[c] * mvn.pdf(X, self.mu[c,:], self.sigma[c])
270    
271        return post_proba
مشاهده کامل کدها

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

^^

بر اساس رای ۱۰ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
دانلود PDF مقاله
۵ دیدگاه برای «مدل آمیخته گوسی در خوشه بندی – پیاده‌سازی در پایتون»

سلام من از این mvn سر درنیاوردم میگه تعریف شده نیست فکر کنم از قلم افتاده این

باسلام
من این کد رو که اجرا میکنم این ارور رو میده
NameError: name ‘tf_idf_array’ is not defined

علتش رو میشه بگین؟

سلام لگاریتم یک تابع مقعر است نه محدب، لطفا اصلاح شود . ممنون

سلام.
متن اصلاح شد.
از همراهی و بازخوردتان سپاسگزاریم.

سلام/ تشکّر/ موفق باشید و سلامت

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *