思考ノイズ

無い知恵を絞りだす。無理はしない。

<はじパタ> 10.4 確率モデルによるクラスタリング

はい、ここまで10章の教師なし学習のクラスタリングをやってまいりましたが、10章も最後となりました。とりあえず一章をきちんと走り抜けられてよかったです。 で、ここは前回までのK-平均法や融合法と違い、各要素がそれぞれのクラスに入る確率を求める確率モデルによるクラスタリングをおこないます。キーワードとしては、混合正規分布モデルとEMアルゴリズムとなるようです。わかりやすい説明はこちらのリンクをご参照、とりあえず自分なりにまとめてみます。

混合正規分布モデル

クラスタの分布がそれぞれ正規分布にしたがうと仮定をしてその平均と分散が一番Fitする最尤の値を求めていくものになります。また、ある要素に関してそれぞれクラスタの割合を表現する混合比なる重みづけのためのパラメータもでてきてこの値もいじる必要があります。

EMアルゴリズム

所属しているクラスタを示す隠れ変数があるものとして、その変数の確率モデルの最尤推定値を求めるのに優れているのが、EMアルゴリズムとなるようです。E(Expectation)ステップで隠れ変数に対しての期待値を計算、M(Maximization)ステップでその隠れ変数の最大値を計算するようにします。

はじパタに限らず、いくつかの本にこのEMのステップで何が行われているかの数式や説明があるのですが、いかに示すサイトの説明が図として直感的に何をしているのかわかりやすく書かれていると思いました。こういうの貴重。

www.slideshare.net

ピーナッツを縦に割っちゃう問題

それで、コーディングを走らせてみたのですが、どうしても正解率が上がらないんです。実際にプロットをしてみると以下のように右上のピーナッツを縦にわるようなクラスタリングをしてしまうのです。

f:id:bython-chogo:20180412171806p:plain

これは初期値にだいぶよるようですね。平均値のmuの値を意図的に変えてみたところ以下のようにピーナッツを横にわるような「理想的な」クラスタに収束することもあるようです。

f:id:bython-chogo:20180412172714p:plain

初期値をランダムに置くようにして確認してみましたが、この理想的な形状に収まるのは10回に1~2回程度のようです。

合わないモデル?

初期値問題に頭を抱えて機械学習のお師匠様のところに相談したところ、「そもそも合わないモデルだったのでは」とのことです。考察としてはアヤメデータ以外でもこのEMアルゴリズムを試してみてうまく当てはまるかを確認してみたほうが良いかもしれないです。やるかはわからないですが。

ソースコード

いつも以上にとっちらかったソースになってしまっていると思います。まあ備忘録なので。

import numpy as np
from sklearn import datasets
import itertools
import matplotlib.pyplot as plt
#inline pyplot


iris = datasets.load_iris()
features = iris.data
feature_names = iris.feature_names
targets = iris.target

from collections import Counter

class Iris_PModel:
    def __init__(self):
        self.p = []
        self.x = []
        self.y = []
        self.t = []
        self.pt = []
        self.num = [50, 50, 50]
        iris = datasets.load_iris()
        features = iris.data
        targets = iris.target
    def install(self, a=2, b=3):
        self.x = features.T[a]
        self.y = features.T[b]
        self.t = targets
        
        self.pi = [1/3, 1/3, 1/3]
        self.means_dists()


    def plot_map(self, df=False):
        p = self.pt
        if df:
            p = self.t

        for t, marker, c in zip(range(3), '>ox', 'rgb'):
             
            plt.scatter(
                features[p == t, 2],
                features[p == t, 3],
                marker=marker,
                c=c,
            )
            plt.scatter(
                self.mu[t][0], self.mu[t][1], marker='s', c=c
            )
            plt.xlim(1.9, 4.5)
            plt.ylim(0.9, 7.1)
            plt.xlabel(feature_names[2])
            plt.ylabel(feature_names[3])
            plt.autoscale()
            plt.grid()

        plt.show()
    def randomize(self):
        self.pt = np.array([np.random.randint(3) for i in range(sum(self.num))])
        
    def norm(self, mu, sigma):
        n_mu = np.array(mu)
        n_sig = np.array([[sigma[0], sigma[2]],[sigma[2], sigma[1]]])
        n_det = np.linalg.det(n_sig)
        n_inv = np.linalg.inv(n_sig)
        nv = []
        
        for i in range(150):
            n_c = np.array([self.x[i], self.y[i]]) - n_mu
            nv.append((1/(2*np.pi*np.sqrt(n_det))) * np.exp(- n_c.dot(n_inv).dot(n_c).T/2))
        return(nv)
    
    def means_dists(self, init=True):
        if init:
            self.randomize()
            x_m = sum(self.x)/150
            y_m = sum(self.y)/150
            self.nu = [[np.random.normal(x_m, 1), np.random.normal(y_m, 1)], 
                       [np.random.normal(x_m, 1), np.random.normal(y_m, 1)], 
                       [np.random.normal(x_m, 1), np.random.normal(y_m, 1)]]
            print("mu", self.mu)

            self.v  = [[1.0, 1.0, 0.0], [1.0, 1.0, 0.0], [1.0, 1.0, 0.0]]

            
    def m_step(self, gamma):

        n_k = [sum([gamma[i][j] for i in range(150)]) for j in range(3)]

        self.mu = [[sum([self.x[i]*gamma[i][j] for i in range(150)])/n_k[j], 
                    sum([self.y[i]*gamma[i][j] for i in range(150)])/n_k[j]] for j in range(3)] 

        self.v = [[sum([(self.x[i]-self.mu[j][0])**2*gamma[i][j] for i in range(150)])/n_k[j],
                  sum([(self.y[i]-self.mu[j][1])**2*gamma[i][j] for i in range(150)])/n_k[j],
                  sum([(self.x[i]-self.mu[j][0])*(self.y[i]-self.mu[j][1])*gamma[i][j] for i in range(150)])/n_k[j]] for j in range(3)]                  
                  
        self.pi = [n_k[i]/150 for i in range(3)]
   

    def e_step(self, init=False):
        if init:
            self.install(2, 3)

        nv = [self.norm( self.mu[i], self.v[i]) for i in range(3)]
        snv = [nv[0][i]*self.pi[0] + nv[1][i]*self.pi[1] + nv[2][i]*self.pi[2] for i in range(150)]
       
        prop = [[nv[0][i]*self.pi[0]/snv[i], nv[1][i]*self.pi[1]/snv[i], nv[2][i]*self.pi[2]/snv[i]] for i in range(150)]
        return prop

    def process(self):
        gamma = self.e_step(True)
        self.m_step(gamma)
        
        pre_result = []
        for i in range(31):
            result = []
            for g in gamma:
                result.append(g.index(max(g)))
            self.pt = np.array(result)

            max_int = [0, 0, 0]


            if pre_result == result:
                print(self.pi)
                self.plot_map()
                break

            pre_result = list(result)
            gamma = self.e_step(False)
            self.m_step(gamma)
        

krs = Iris_PModel()
krs.process()