Now Loading...

Now Loading...

k近傍法(knn)とは

k近傍法(k-nearest neighbor algorithm, k-nn)は機械学習のアルゴリズムの一つです。簡単に説明すると、データをグループ分けするにあたり、対象とするあるデータがどのグループに含まれるかを周囲のデータの多数決で推測するという手法です。これだけではいまいちイメージがわかないと思いますので、より詳しく見ていきましょう。

k近傍法のアルゴリズム

例えば以下の図のようなデータがあったとします。赤、青、緑の三つのクラスがあるのがわかります。

図1. データ

このデータを我々人間がグループ分けする場合、我々は塊になっているところのデータをだいたい同じグループと分類するでしょう。つまり上図の☆は青に属するのではないかと考えるわけです。

図2. 分類後

これと同じことをアルゴリズムとして実装したいのです。そこで以下のように、☆に近い点を近い順に任意の数、k個だけ選びますその選んだ点に最も多く含まれているグループに☆も分類されると考えます。これがk近傍法です。

図3. Kの意味

例えば仮にk=5に設定した場合、図4のように5個取ります。この場合は取った5個のうち青は 4個、緑は1個で最も多いのは青だったため、☆は青に分類されることがわかります。

図4. 多数決

※ k近傍法において、範囲内のどのクラスの数も同じ場合には多数決で決定できないのではないかと思われたかもしれません。そのような場合、まず距離の大きさで近い方を取り、それでも距離も同じ場合にはデータセットのラベル順によって決定されることが多いようです(scikit-learnのk近傍法ではこの方法を取っています、参考:https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KNeighborsClassifier.html#sklearn.neighbors.KNeighborsClassifier)。今回の私たちの実装においては、このような場合にはただデータセットのラベル順で先に来る方に分類されるようにします。

なお、このように分類対象のデータをその周りの教師データを用いて分類することから、k近傍法はアルゴリズムとしては教師あり学習に分類されます。

k近傍法の利点、欠点

k近傍法は非常にシンプルな手法であるため、学習を必要としない、外れ値に頑健であるといったメリットがあります。一方、それぞれのデータについて多数決を取らなければならないという性質上、データ数が増えるに伴って計算量も非常に多くなってしまうというデメリットがあります。

k近傍法の実装

irisデータセットの分類

ここまででk近傍法の詳細について理解できたかと思います。では次に、k近傍法アルゴリズムを実装することで動作を確認してみましょう。

まずは分類対象のデータを用意しましょう。今回はscikit-learnのirisデータセットを用いることにします。irisデータセットはアヤメの特徴量のデータセットです。データをロードして、その詳細を見てみましょう。

 

データの個数は150個あり、その特徴量の数は4つとわかりました。

また、その特徴量の詳細を見てみましょう。

 

このことから、特徴量4つは具体的にはsepal length(ガクの長さ)、sepal width(ガクの幅)、petal length(花弁の長さ)、petal width(花弁の幅)であるということがわかりました。

また、クラスは何種類なのか見てみます。

 

クラスは3種類で、アヤメはsetosa, versicolor, virginicaという種類に分類できるようです。

では、データの詳細が分かったので早速k近傍法を実装したいところですが、今回は2次元平面上に描画することをイメージしやすいように、適当に特徴量を4つから2つに限定しましょう(特徴量を減らす場合、データの性質をよく表している特徴量を残すべきですが、今回はその説明は省略しています)。

 

また、データは訓練用データと試験用データに分割する必要があるのでした。sickit-learnにはデータを訓練用と試験用に分割してくれるtrain_test_splitがあります。

 

データを訓練用と試験用に分割したことで、現在のデータの形状は以下のようになっていることがわかります。

図5. データ分割後の形状

では、データの準備ができたのでk近傍法の詳細を実装していきましょう。k近傍法ではまず、対象の試験データと周りの訓練データ全てとの距離を計算する必要があります。訓練データは学習する必要はなく、ただモデル内に取り込むだけです。取り込んでおいた訓練データと試験データの距離には、ユークリッド距離を用います。

 #距離の計算の実装例(訓練データx_trainはあらかじめモデル内に記憶してあるものとする)

def calc_dist(x_test): #x_testは試験データ、距離の計算結果の配列を返す

    #距離を格納する配列
    dist = np.zeros([x_test.shape[0], x_train.shape[0]])
        
    for i in range(x_test.shape[0]):
        for j in range(x_train.shape[0]):
            distance = np.sum((x_test[i] - x_train[j])**2) #距離の計算にはユークリッド距離を用いる
            dist[i][j] = distance
                
    return dist

距離が計算できたら次に、試験データに最も近いk個の訓練データで多数決を行います。ここでもっとも多かったクラスを所属先のクラスとして設定します。

#クラスの予測の実装例(訓練データのラベルy_trainはあらかじめモデル内に記憶してあるものとする)

def predict(x_test, k): #x_testは試験データ、kはいくつのデータを考慮するか
    distance = calc_dist(x_test) #距離の配列を取得

    pred = np.zeros(x_test.shape[0]) #予測結果を格納する配列

    for i in range(x_test.shape[0]):
        sorted_index = np.argsort(distance[i,:]) #距離の配列のインデックスを昇順にソート(並び替え)する
        sorted_labels = y_train[sorted_index][:k] #ラベルの配列も距離の配列のインデックスに合わせて並び替え、上からk個を取り出す
        
        u, counts = np.unique(sorted_labels, return_counts=True) #取り出したラベルの頻度(個数)をカウント
        
        pred[i] = u[np.argmax(counts)] #最も頻度が高かったクラスを予測結果とする
        
    return pred   


これらを踏まえて、k近傍法モデルの実装は以下のようになります。

import numpy as np

class KNN():
    def __init__(self, k):
        self.k = k

    #訓練はただ訓練データを取り込むだけ
    def train(self, x_train, y_train):
        self._x_train = x_train
        self._y_train = y_train

    #予測を行うメソッド    
    def predict(self, x_test):
      
        distance = self.calc_dist(x_test) #距離の配列を取得

        pred = np.zeros(x_test.shape[0]) #予測結果を格納する配列

        for i in range(x_test.shape[0]):
            sorted_index = np.argsort(distance[i,:]) #距離の配列のインデックスを昇順にソート(並び替え)する
            
            sorted_labels = self._y_train[sorted_index][:self.k] #ラベルの配列も距離の配列のインデックスに合わせて並び替え、上からk個を取り出す
        
            u, counts = np.unique(sorted_labels, return_counts=True) #取り出したラベルの頻度(個数)をカウント
        
            pred[i] = u[np.argmax(counts)] #最も頻度が高かったクラスを予測結果とする
        
        return pred

    #距離を計算し、その結果を返すメソッド
    def calc_dist(self, x_test):
     
        dist = np.zeros([x_test.shape[0], self._x_train.shape[0]]) #距離を格納する配列

        for i in range(x_test.shape[0]):
            for j in range(self._x_train.shape[0]):
                distance = np.sum((x_test[i]-self._x_train[j])**2) #距離の計算にはユークリッド距離を用いる
                dist[i][j] = distance
                
        return dist

では、このモデルを用いて実際に試験データを分類してみましょう。また、予測結果がどのくらい正しかったのか、正解率も計算してみましょう。

 

100%に近く、非常に高い正解率を出せていることがわかります。

MNISTデータセットの分類

さて、k近傍法の実装を理解できたところで、次により難しいデータセットを分類してみます。今度は手書き数字の画像データセットである、MNISTデータセットを分類してみましょう。

MNISTデータセットはどんなデータセットなのか、まずデータをロードしていくつか表示してみましょう。

 

表示してみてわかる通り、MNISTデータセットは手書き数字の画像データです。また、データのサイズは訓練データが(5000, 28, 28)、テストデータが(800, 28, 28)となっていました。ここで、train_setに関して画像1枚のサイズは縦×横で(28, 28)となっており、それが5000枚あるという意味です。test_setについても画像の枚数が800枚であるというだけで、同じです。

扱いやすくするため、28×28の画像を1次元ベクトルに直してしまいましょう。

 

先ほどのirisデータセットでは2つしか特徴量がなかった一方、こちらのデータセットの特徴量はなんと784もあります。しかも数字は1~10まであるので10クラス分類です。

大変そうに見えますが理屈は特徴量が2つのときと全く同じなので、今回実装したk近傍法モデルは特徴量やクラス数が多くても問題なく動作するように実装してあります。実際に学習させ、正解率を表示してみましょう。結構時間がかかるかもしれません。

 

irisデータセットを分類したときとはうってかわって、精度がかなり低いことが分かります。k近傍法は画像データセットのような特徴量が多すぎるデータに対しては効果的でないことがわかりました。

まとめ

今回はk近傍法について解説しました。仕組み自体はそれほど複雑なものではないと思いますが、機械学習における代表的な手法の一つですのでよく理解しておきましょう。次はk平均法について解説しようと思いますので、ぜひそちらも併せてご覧ください。

\ シェア /

E資格スピードパッケージ

E資格スピードパッケージ2023#2修了者合格率100%達成

zero to one E資格 jdla

zero to oneの「E資格」向け認定プログラム

日本ディープラーニング協会の実施するE資格の受験ならzero to oneの「E資格」向け認定プログラム (税込165,000円) をおすすめします。当講座は、東京大学大学院工学系研究科の松尾豊教授と東北大学大学院情報科学研究科の岡谷貴之教授が監修する実践的なプログラムとなっています。
厚生労働省の教育訓練給付制度対象のE資格認定プログラムの中では最安値※となり、実質負担額49,500円~(支給割合70%の場合)で受講可能です。※2023年弊社調べ zero to one E資格 jdla

人工知能基礎講座を提供中

人工知能の第一人者である東京大学の松尾豊教授が監修した人工知能基礎講座を受講してみませんか? 人工知能の歴史から自然言語処理、機械学習、深層学習といった最先端のトピックやAIに関わる法律問題まで網羅しているので全てのビジネスパーソン・AIの初学者におすすめです。

サンプル動画

人工知能基礎講座はこちら↓ zero to one G検定 人工知能基礎 jdla

AI初学者・ビジネスパーソン向けのG検定対策講座

G検定受験前にトレーニングしたい方向けの問題集「G検定実践トレーニング」も提供中です。 zero to one E資格 jdla