TensorFlowでK最近傍法を実装

私は今のところ業務ではKerasをメインで使っていますが、TensorFlowも個人で学習しています。
応用範囲が広いですし、ドキュメントも多いですからね。
そこで今回は練習も兼ねて、TensorFlowを使ってk最近傍法(KNN法)を実装してみました。

TensorFlowって言ったらディープラーニングのライブラリなんじゃないの?と思われるかもしれません。

しかし、TensorFlowは別にディープラーニング専用のライブラリではなく、グラフ計算のライブラリです。そこがKerasやChainerなどとの大きな違いです。

なのでTensorFlowを使えば、ディープラーニング以外にも様々な問題を解くことができるのです。最近はSVMやランダムフォレストなど、Scikit-learnにあるような機械学習アルゴリズムも用意され始めているようです。

なんでKNN法?


そんな機械学習アルゴリズムの中でなぜKNNなのかですが、これは単純に私が好きだからです(笑)

ベクトル同士の距離を比較するという誰にでも理解できそうな単純さと、2クラス分類から自然に他クラスへと拡張できるところが気に入っています。同じような理由でランダムフォレストもお気に入りです。

ソースコード全体


公式のチュートリアルを魔改造して作りました。

import tensorflow as tf
import numpy as np
from tensorflow.contrib.learn.python.learn.datasets.mnist import read_data_sets

# 学習データを取得
mnist = read_data_sets("data", one_hot=True)

# 学習データが多いと重くなるので、1万件だけ使う
X_traning,Y_traning = mnist.train.next_batch(10000)
X_test,Y_test = mnist.test.next_batch(200)

# モデルの入出力の設定
x_train = tf.placeholder(tf.float32, [None,28*28]) #traning input
y_train = tf.placeholder(tf.float32, [None,10]) #traning label
x_test = tf.placeholder(tf.float32, [28*28]) #testing input

# 比較する近傍ベクトルの数を設定
K = 3 
nearest_neighbors = tf.Variable(tf.zeros([K]))

# 入力ベクトルと近傍ベクトルの距離を計算
distance = tf.negative(tf.reduce_sum(tf.abs(tf.add(x_train, tf.negative(x_test))),axis=1)) #L1
# 上でマイナスを付けたので、大きい順に取り出せばいい
values, indices = tf.nn.top_k(distance, k=K, sorted=False)

# 近傍値を保存するリスト
nn_vector = []
for i in range(K):
    nn_vector.append(tf.argmax(y_train[indices[i]], 0)) 

# 近傍ベクトルのリストをtensorflowの変数へ変換する
nearest_neighbors = nn_vector

# クラスラベル、そのインデックス、個数を計算
y, idx, count = tf.unique_with_counts(nearest_neighbors)
"""
例:
x = [1, 1, 2, 4, 4, 4, 7, 8, 8]
y, idx, count = unique_with_counts(x)
y ==> [1, 2, 4, 7, 8]
idx ==> [0, 0, 1, 2, 2, 2, 3, 4, 4]
count ==> [2, 1, 3, 1, 2]
"""

# 最も個数の多いクラスを取り出す
pred = tf.slice(y, begin=[tf.argmax(count, 0)], size=tf.constant([1], dtype=tf.int64))[0]
# y = [ 1,  2,  3,  4, 11] count = [3, 2, 2, 7, 1] の時、 1を返す

accuracy = 0 
init = tf.global_variables_initializer()

with tf.Session() as sess:

    for i in range(X_test.shape[0]):
        predicted_value = sess.run(pred,feed_dict={x_train:X_traning, y_train:Y_traning, x_test:X_test[i,:]})

        print("Test", i, "Prediction", predicted_value, "True Class:", np.argmax(Y_test[i]))

        if predicted_value == np.argmax(Y_test[i]):
                accuracy += 1.0
    
    accuracy /= len(Y_test)
    print("Accuracy:", accuracy)

データの準備


今回はTensorFlowのmnistデータを使用します。
と言っても全てではなく、一万枚だけを学習データに使います。

# 学習データを取得
mnist = read_data_sets("data", one_hot=True)

# 学習データが多いと重くなるので、1万件だけ使う
X_traning,Y_traning = mnist.train.next_batch(10000)
X_test,Y_test = mnist.test.next_batch(200)

モデルの構築


モデルを記述している部分です

# モデルの入出力の設定
x_train = tf.placeholder(tf.float32, [None,28*28]) #traning input
y_train = tf.placeholder(tf.float32, [None,10]) #traning label
x_test = tf.placeholder(tf.float32, [28*28]) #testing input

# 比較する近傍ベクトルの数を設定
K = 3 
nearest_neighbors = tf.Variable(tf.zeros([K]))

# 入力ベクトルと近傍ベクトルの距離を計算
distance = tf.negative(tf.reduce_sum(tf.abs(tf.add(x_train, tf.negative(x_test))),axis=1)) #L1
# 上でマイナスを付けたので、大きい順に取り出せばいい
values, indices = tf.nn.top_k(distance, k=K, sorted=False)

# 近傍値を保存するリスト
nn_vector = []
for i in range(K):
    nn_vector.append(tf.argmax(y_train[indices[i]], 0)) 

# 近傍ベクトルのリストをtensorflowの変数へ変換する
nearest_neighbors = nn_vector

# クラスラベル、そのインデックス、個数を計算
y, idx, count = tf.unique_with_counts(nearest_neighbors)
"""
例:
x = [1, 1, 2, 4, 4, 4, 7, 8, 8]
y, idx, count = unique_with_counts(x)
y ==> [1, 2, 4, 7, 8]
idx ==> [0, 0, 1, 2, 2, 2, 3, 4, 4]
count ==> [2, 1, 3, 1, 2]
"""

# 最も個数の多いクラスを取り出す
pred = tf.slice(y, begin=[tf.argmax(count, 0)], size=tf.constant([1], dtype=tf.int64))[0]
# y = [ 1,  2,  3,  4, 11] count = [3, 2, 2, 7, 1] の時、 1を返す
重要なところはこの部分です
# 入力ベクトルと近傍ベクトルの距離を計算
distance = tf.negative(tf.reduce_sum(tf.abs(tf.add(x_train, tf.negative(x_test))),axis=1)) #L1
# 上でマイナスを付けたので、大きい順に取り出せばいい
values, indices = tf.nn.top_k(distance, k=K, sorted=False)

2行目ではベクトル同士の距離を計算していますが、計算結果をマイナスの値にしています。
こうすることで、距離が短い = distanceの値が大きいとなります。
TensorFlowには小さい順にk個取り出す関数が無いようなので、このような形にしてます。

最頻値を計算


k個の中に最も多く含まれていたクラスを計算します。
tf.unique_with_countsやtf.sliceは使ったことのない関数だったので、いい勉強になりました。

# 近傍値を保存するリスト
nn_vector = []
for i in range(K):
    nn_vector.append(tf.argmax(y_train[indices[i]], 0)) 

# 近傍ベクトルのリストをtensorflowの変数へ変換する
nearest_neighbors = nn_vector

# クラスラベル、そのインデックス、個数を計算
y, idx, count = tf.unique_with_counts(nearest_neighbors)
"""
例:
x = [1, 1, 2, 4, 4, 4, 7, 8, 8]
y, idx, count = unique_with_counts(x)
y ==> [1, 2, 4, 7, 8]
idx ==> [0, 0, 1, 2, 2, 2, 3, 4, 4]
count ==> [2, 1, 3, 1, 2]
"""

# 最も個数の多いクラスを取り出す
pred = tf.slice(y, begin=[tf.argmax(count, 0)], size=tf.constant([1], dtype=tf.int64))[0]
# y = [ 1,  2,  3,  4, 11] count = [3, 2, 2, 7, 1] の時、 1を返す

以下のテストの部分は省略します。

おわりに


今回実装してみて実感しましたが、KNN法を使うならScikit-learnを使った方がはるかに楽です(笑)

TensorFlowでやるメリットはGPU計算できることや、TensorFlow Servingを使えることでしょうか。KNN関数が用意されるなどして便利になってきたら、選択肢に入って来るかもしれませんね。

シェアする

  • このエントリーをはてなブックマークに追加

フォローする