機械学習の2、kd-tree最近傍探索

概要

kd-tree(k-dimensions tree)はk次元空間の分割を表す二分木である。kd-treeの構築は、座標軸に垂直な超平面でk次元空間を連続的に分割して、一連のk次元超長方形領域を形成する。 kd-treeの各ノードは、k次元の超長方形領域に対応する。kd-treeを使用すると、一部のインスタンスとの計算はしないため、計算量が削減される。よって、kd-treeは早く探索できるようにインスタンスポイントをk次元空間に格納するツリー型のデータ構造である。それで新しいインスタンスのクラス(属するクラス)をkd-treeによる判明する最近傍探索のアルゴリズムは以下のとおり。
【step1】k次元データセットで最大の分散をもつ次元xを選択し、次に中央値mを次元の中間点として選択してデータセットを分割し、2つのサブセットを取得する。
【step2】2つのサブセットに対してステップstep1のプロセスを繰り返して、すべてのサブセットが再分割できなくなるまで繰り返す。
【step3】step1とstep2で根から葉っぱまで木ができてしまう。
【step4】根から葉っぱまで、あるルール「\(x \lt m\)→左枝、\(x \geq m\)→右枝」で新しいインスタンスに最も近いインスタンス(葉っぱ、ノード)をみつける。
【step5】step4の葉っぱから、逆方向で根まで遡って、step4よりもっと近いインスタンスがあるか探索する。要するに、新しいインスタンスにもっとも近い既存インスタンスをみつける。
【step6】step4またはstep5でみつけたもっとも近い既存インスタンスのクラスは結果となる。

実装の例

下図から、グリーンポイント[2, 4.5]に最近傍ポイントを探索する。

kd_tree_newPoint
kd_tree_newPoint

# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
#from time import time

#
# load data from dataset file. 
#
def load_data(fileName):
    data_mat = []
    with open(fileName) as fd:
        for line in fd.readlines():
            data = line.strip().split()
            data = [float(item) for item in data]
            data_mat.append(data)
    data_mat = np.array(data_mat)
    label = data_mat[:, 2]
    data_mat = data_mat[:, :2]
    return data_mat, label
#
# create a tree for dataset.
#
def create_kdtree(dataset, depth):
    n = np.shape(dataset)[0]
    tree_node = {}
    if n == 0:
        return None
    else:
        n, m = np.shape(dataset)
        split_axis = depth % m
        depth += 1
        tree_node['split'] = split_axis
        dataset = sorted(dataset, key=lambda a: a[split_axis])
        num = n // 2
        tree_node['median'] = dataset[num]
        tree_node['left'] = create_kdtree(dataset[:num], depth)
        tree_node['right'] = create_kdtree(dataset[num + 1:], depth)
        return tree_node
#
# search k near points on the tree. 
#
def search_kdtree(tree, data):
    k = len(data)
    if tree is None:
        return [0] * k, float('inf')
    split_axis = tree['split']
    median_point = tree['median']
    if data[split_axis] <= median_point[split_axis]:
        nearest_point, nearest_distance = search_kdtree(tree['left'], data)
    else:
        nearest_point, nearest_distance = search_kdtree(tree['right'], data)
    
    # the distance between data to current point.
    now_distance = np.linalg.norm(data - median_point)
    if now_distance < nearest_distance:
        nearest_distance = now_distance
        nearest_point = median_point.copy()
        
    # the distance between hyperplane.
    split_distance = abs(data[split_axis] - median_point[split_axis])
    if split_distance > nearest_distance:
        return nearest_point, nearest_distance
    else:
        if data[split_axis] <= median_point[split_axis]:
            next_tree = tree['right']
        else:
            next_tree = tree['left']
        near_point, near_distance = search_kdtree(next_tree, data)
        if near_distance < nearest_distance:
            nearest_distance = near_distance
            nearest_point = near_point.copy()
        return nearest_point, nearest_distance
#
# main.
#
if __name__ == '__main__':
    data_mat, label = load_data('/content/drive/My Drive/Colab Notebooks/Machine_Learning/kd_tree/dataset.txt')
    fig = plt.figure(0)
    ax = fig.add_subplot(111)
    ax.scatter(data_mat[:, 0], data_mat[:, 1], c=label, cmap=plt.cm.Paired)

    new_point = [2, 4.5]
    kdtree = create_kdtree(data_mat, 0)
    print(kdtree)
    #start = time()
    nearest_point, near_dis= search_kdtree(kdtree, new_point)
    #print(time()-start)
    ax.scatter(new_point[0], new_point[1], c='g', s=50)
    ax.scatter(nearest_point[0], nearest_point[1], c='r', s=50)
    plt.show()

ソースコード→https://github.com/soarbear/Machine_Learning/tree/master/kd_tree

結果

kd_tree_findNearestPoint
kd_tree_findNearestPoint

kd-treeが小さい(例えば\(k=20\))場合、アルゴリズムの探索効率はkNNより非常に高くなる。ただし、データの次元が増えると(例えば、\(k≥100\))、探索効率が急速に低下する。データセットの次元がkであると仮定すると、効率的な探索を実現するために、データサイズNが、\(N >> 2^k\)を満たすことが必要である。kd-treeで高次元データにも適用できるように、Jeffrey S. BeisとDavid G. Loweは、改善されたアルゴリズムKd-tree with BBF(Best Bin First)の提案に貢献した。ある探索精度を確保するという前提で探索を高速化するようになる。

参考文献

「Machine Learning in Action」、Peter Harrington氏

1+

ロボット・ドローン部品お探しなら
ROBOT翔・電子部品ストア

機械学習の1、k-近傍法

概要

k-近傍法(k-NearestNeighbor、kNN)アルゴリズムは、ある入力インスタンスに対して、トレーニングデータセット内のインスタンスに最も近いK個のインスタンスを見つけて、k個のインスタンスの多数がAクラスに属すると、入力インスタンスもこのAクラスに分類される。近いかの判断基準である距離がユークリッド距離\(\sqrt{\sum(x_i-y_i)^2}\)とする。k-近傍法がほとんどの場合に使えそうで、シンプルな分類法である。実生活で少数派が多数派に従うという考え方に似ている。

実装の例

k-近傍法を利用して、0~9の手書き数字を判別する。ソースコードは下記出典から借用とする。

'''
Created on Sep 16, 2010
kNN: k Nearest Neighbors
Input:      inX: vector to compare to existing dataset (1xN)
            dataSet: size m data set of known vectors (NxM)
            labels: data set labels (1xM vector)
            k: number of neighbors to use for comparison (should be an odd number)
            
Output:     the most popular class label
@author: pbharrin
'''
from numpy import *
import operator
from os import listdir

def classify0(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0]
    diffMat = tile(inX, (dataSetSize,1)) - dataSet
    sqDiffMat = diffMat**2
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances**0.5
    sortedDistIndicies = distances.argsort()     
    classCount={}          
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
    sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]

def img2vector(filename):
    returnVect = zeros((1,1024))
    fr = open(filename)
    for i in range(32):
        lineStr = fr.readline()
        for j in range(32):
            returnVect[0,32*i+j] = int(lineStr[j])
    return returnVect

def handwritingClassTest():
    hwLabels = []
    trainingFileList = listdir('trainingDigits')           #load the training set
    m = len(trainingFileList)
    trainingMat = zeros((m,1024))
    for i in range(m):
        fileNameStr = trainingFileList[i]
        fileStr = fileNameStr.split('.')[0]     #take off .txt
        classNumStr = int(fileStr.split('_')[0])
        hwLabels.append(classNumStr)
        trainingMat[i,:] = img2vector('trainingDigits/%s' % fileNameStr)
    testFileList = listdir('testDigits')        #iterate through the test set
    errorCount = 0.0
    mTest = len(testFileList)
    for i in range(mTest):
        fileNameStr = testFileList[i]
        fileStr = fileNameStr.split('.')[0]     #take off .txt
        classNumStr = int(fileStr.split('_')[0])
        vectorUnderTest = img2vector('testDigits/%s' % fileNameStr)
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
        print "the classifier came back with: %d, the real answer is: %d" % (classifierResult, classNumStr)
        if (classifierResult != classNumStr): errorCount += 1.0
    print "\nthe total number of errors is: %d" % errorCount
    print "\nthe total error rate is: %f" % (errorCount/float(mTest))

 if __name__ == '__main__':
    handwritingClassTest()

ソースコードhttps://github.com/soarbear/Machine_Learning/blob/master/kNN/kNN.py

結果

the classifier came back with: 0, the real answer is: 0
...
the classifier came back with: 9, the real answer is: 9
the total number of errors is: 12.0
the total error rate is: 0.012684989429175475

手書きの例では、正確率98.7%の結果となったので、kNNが手書き数字の認識に使えそうな分類法だと分かる。kの選択について、通常小さな値を選択してよく実験して最適なk値を探す。つまり実験を通してkを選定する。入力インスタンスにもっとも近いk個のインスタンスを見つけるのがポイントである。トレーニングデータセット内のインスタンス毎との距離を計算するため、トレーニングデータセットの量が増えると計算量も増えて、無駄が多くなる。いかに計算のコストを下げて、効率よくkインスタンスをみつけるのが課題となり、k-dimension treeつまりkd-treeの出番となる。

出典

「Machine Learning in Action」、Peter Harrington氏

1+

ロボット・ドローン部品お探しなら
ROBOT翔・電子部品ストア

深層学習の3、複数人体検出実験

概要

yolo_v3を用いて、動画から複数人体の検出を行ってみる。

実装

GPUの確認

!nvidia-smi
nvidia_tesla_k80_spec
nvidia_tesla_k80_spec

Darknetリポジトリのクローン作成

import os
os.environ['PATH'] += ':/usr/local/cuda/bin'
!git clone https://github.com/AlexeyAB/darknet/

コンパイル環境のインストール

!apt install gcc-5 g++-5 -y
!ln -s /usr/bin/gcc-5 /usr/local/cuda/bin/gcc 
!ln -s /usr/bin/g++-5 /usr/local/cuda/bin/g++

ライブラリのコンパイル

cd darknet
!sed -i ‘s/GPU=0/GPU=1/g’ Makefile
!sed -i ‘s/OPENCV=0/OPENCV=1/g’ Makefile
!make

yolov3重みのゲット

!wget https://pjreddie.com/media/files/yolov3.weights
!chmod a+x ./darknet

動画mp4ファイルのアップコード

from google.colab import files
uploaded = files.upload()

人体検出

!./darknet detector demo cfg/coco.data cfg/yolov3.cfg yolov3.weights -dont_show fifa.mp4 -i 0 -out_filename fifa.avi -thresh 0.7

Jupyter NotebookファイルはGithubへ公開 https://github.com/soarbear/YOLOv3_detection

結果

以下の画像をクリックすると、画面がyoutube動画サイトへ遷移する。

動画から人体検出
動画から人体検出

参考文献

https://pjreddie.com/darknet/yolo/

2+

ロボット・ドローン部品お探しなら
ROBOT翔・電子部品ストア

機械学習の0、分類~回帰

概要

機械学習が解決可能な問題が、出力データにより分類か回帰カテゴリーに切り分ける。離散データ出力の場合、分類問題となり、連続データ出力の場合、回帰問題となる。それぞれの方法論もあるし、共通な方法論もある。

主な方法論

分類問題カテゴリー

ロジスティック回帰、sigmoid関数を介して線形回帰\((wx+b)\)を\(0|1\)にマッピングして2クラス問題に適用される。Nクラス分類問題の場合、\((wx+b)\)のNセットを取得してから例えばsoftmax関数で振り分ける多重分類問題に適用される。「回帰」との文字があるのに、実は分類法である。

サポートベクターマシンSVM、各サンプルポイントとの距離が最大となるマージン最大化超平面を利用した分類法である。

kNN、ユークリッド距離を利用した分類法である。

kd-tree、decision-tree、二分木を利用した分類法である。

回帰問題カテゴリー

線形回帰、\(wx+b\)の値を出力する。これは連続値であるため、回帰問題に適用される。

サポートベクター回帰SVR、出力する\(wx+b\)の値は、サンプルポイントから分類表面までの距離であり、連続値であるため、回帰モデルである。

共通カテゴリー

単純ベイズ、分類と回帰に適用される。
分類の場合、yは離散カテゴリであるため、xを指定して離散\(p(y|x)\)を取得すると事後確率が得られる。回帰の場合、確率密度関数\(p(y|x)\)を取得すると事後確率関数が得られる。

畳み込みニューラルネットワークCNN、分類と回帰に適用される。
回帰の場合、入力層~中間層~最後1つのニューロンに接続されて、\(wx+b\)を出力すると回帰問題の処理流れとなる。Nクラス分類の場合、m個のニューロンがN個のニューロンに接続されているため、異なるw値を持つ\(wx+b\)のN個のグループがあります。softmaxを使用してはNクラスの確率を得られる。

再帰ニューラルネットワークRNN、分類と回帰に適用される。回帰および分類の場合、CNNと同様に回帰、分類問題に適用されるが、違いはRNNの入力、出力が時系列データである。

参考文献

「Statictics_Learning Method」、Lihang氏

追記

サポートベクターマシンSVM、分類のみならず回帰にも適用される。

1+

ロボット・ドローン部品お探しなら
ROBOT翔・電子部品ストア

深層学習の2、LSTM Seq2Seqモデル実装

概要

株価、為替、天気、動画など時系列データの予測でよく使われるディープラーニングの代表的手法RNN(再帰型ニューラルネットワーク)の拡張バージョンに、LSTM(Long short-term memory)と呼ばれるモデルがある。今回は、LSTM Seq2Seq、Many to Manyモデルを実装して、円の第4象限の一部(訓練未実施)に対して、時系列データの予測を行ってみる。

環境

keras/LSTM, Google Colab CPU

モデル

lstm_seq2seq_model
lstm_seq2seq_model

以下モデルの概要を説明する。
・Sequentialは、あるレイヤの全ノードと、次レイヤの全ノードをつなぐRNNのモデル。
・RNNの第1 Layerとして、LSTMを追加する。第1引数は出力の次元数、activationは活性化関数で、input_shapeは入力データのフォーマット(in_time_steps x in_features)となる。このlayerがEncoderのように見える。
・RepeatVectorにより、出力を入力として繰り返す。ここでの繰り返し回数は予測範囲(out_vectors)となる。
・再度のLSTM、ただし、ここではシーケンス出力まさにmany outputつまり、return_sequencesをTrueに指定する。このlayerがDecoderのように見える。
・TimeDistributedを指定し、かつ、Dense(out_features)で、出力の次元数を指定する。
・最後にcompileメソッドで、学習時の最適化手法や、損失関数を指定する。ここでは最適化手法としてAdamを、損失関数としてMSE(Mean Squared Errorつまり平均2乗誤差)に指定する。

実装

# -*- coding: utf-8 -*-
# Brief: A Seq2Seq Model Implementation by keras Recurrent Neural Network LSTM.
# Author: Tateo_YANAGI @soarcloud.com
#
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

from keras.models import Sequential
from keras.layers.recurrent import LSTM
from keras.layers.core import Dense, Activation
from keras.layers.wrappers import TimeDistributed
from keras.layers import RepeatVector

PI = 3.1415926535
EPOCHS = 100
TRAIN_SPLIT = 0.98

#
# Class Prediction for lstm model.
#
class Prediction:
  def __init__(self):
    # Array dimension of input&output:(batch_size, time_steps or vectors, features).
    self.batch_size = 48
    self.in_time_steps = 6
    self.in_features = 2
    self.out_vectors = 3
    self.out_features = 2
    self.hidden_neurons = 100 # Neurons of hidden layer.
    self.epochs = EPOCHS # Iteration times.
    self.train_split = TRAIN_SPLIT # Partition of learning dataset.
    self.activation_hidden = 'tanh' # Activation function for hidden units.
    self.activation_output = 'linear' # Activation function for output units.
    self.optimizer= 'adam' # Optimization function.
    self.loss = 'mse' # Loss(cost) function.

#
# Create dataset.
#
  def create_dataset(self, train_split=0.8, in_time_steps=10, out_vectors=4, in_features=2, out_features=2):
    a = np.array([[np.cos(0.0), np.sin(0.0)]])
    for i in range(1, int(2*PI*10)):
      a = np.append(a, np.array([[np.cos(i*0.1), np.sin(i*0.1)]]), axis=0)
    print(f'[Debug-0]\nlen(a):{len(a)}\na:\n{a}')

    # Create dataset for train and test
    # Initialize x_train and y_train.
    x_total = a[0:in_time_steps, 0:in_features]
    y_total = a[in_time_steps:in_time_steps+out_vectors, 0:in_features]

    # Calculate length for x_total and y_total.
    total_len = a.shape[0] - in_time_steps - out_vectors + 1
    print(f'[Debug-1]\ntotal_len:{total_len}')

    # Fill out x_total and y_total.
    for i in range(1, total_len):
      x_total = np.append(x_total, a[i:i+in_time_steps], axis=0)
      y_total = np.append(y_total, a[i+in_time_steps:i+in_time_steps+out_vectors], axis=0)
    print(f'[Debug-2]\nx_total.shape[0]):{x_total.shape[0]}\nx_total:\n{x_total}\ny_total.shape[0]:{y_total.shape[0]}\ny_total:\n{y_total}')

    # Reshape x_toal and y_total from 2D to 3D.
    x_total = np.reshape(x_total[0:x_total.shape[0]], (-1, in_time_steps, in_features))
    y_total = np.reshape(y_total[0:y_total.shape[0]], (-1, out_vectors, out_features))

    # Split dataset for train and test
    x_train = x_total[0:int(x_total.shape[0]*train_split)]
    y_train = y_total[0:int(x_total.shape[0]*train_split)]
    #x_test  = x_total[x_train.shape[0]:]
    #y_test  = y_total[y_train.shape[0]:]
    x_test  = x_total[-2:]
    y_test  = y_total[-2:]
    print(f'[Debug-3]\nx_train.shape[0]:{x_train.shape[0]}\nx_train:\n{x_train}\ny_train.shape[0]:{y_train.shape[0]}\ny_train:\n{y_train}')
    print(f'[Debug-4]\nx_test.shape[0]:{x_test.shape[0]}\nx_test:\n{x_test}\ny_test.shape[0]:{y_test.shape[0]}\ny_test:\n{y_test}')

    return x_train, y_train, x_test, y_test

#
# Create lstm model.
#
  def create_model(self) :
    model = Sequential()
    # Encoder
    model.add(LSTM(self.hidden_neurons, activation=self.activation_hidden, input_shape=(self.in_time_steps, self.in_features)))
    # Output used as Input.
    model.add(RepeatVector(self.out_vectors))
    # Decoder, output sequence
    model.add(LSTM(self.hidden_neurons, activation=self.activation_hidden, return_sequences=True))
    model.add(TimeDistributed(Dense(self.out_features, activation=self.activation_output)))
    #model.add(Activation(self.activation_output))   
    model.compile(optimizer=self.optimizer, loss=self.loss, metrics=['accuracy'])
    return model

  def train_model(self,model, x_train, y_train) :
    model.fit(x_train, y_train, epochs=self.epochs, verbose=2, batch_size=self.batch_size)
    return model

#
# main()
#
if __name__ == "__main__":
  predict = Prediction()
  train_in_data = None
  # Create dataset
  x_train, y_train, x_test, y_test = predict.create_dataset(predict.train_split, predict.in_time_steps,\
                                                            predict.out_vectors, predict.in_features, predict.out_features)
  # Create model
  model = predict.create_model()
  # Train model
  model = predict.train_model(model, x_train, y_train)
  print(model.summary())
  # Test
  predicted = model.predict(x_test, verbose=1)
  print(f'[info_1]predicted:\n{predicted}')
  # Plot result
  predicted = np.reshape(predicted, (-1, predict.out_features))
  x_test = np.reshape(x_test, (-1, predict.in_features))
  y_test = np.reshape(y_test, (-1, predict.out_features))
  x_train = np.reshape(x_train, (-1, predict.in_features))
  y_train = np.reshape(y_train, (-1, predict.out_features))
  plt.figure(figsize=(8, 8))
  plt.scatter(x_train[:,0], x_train[:,1])
  plt.scatter(x_test[:,0], x_test[:,1])
  plt.scatter(y_test[:,0], y_test[:,1])
  plt.scatter(predicted[:,0], predicted[:,1])
  plt.show()

※ソースコード公開→
https://github.com/soarbear/lstm_seq2seq_model_prediction

結果

lstm_seq2seq_model_prediction
lstm_seq2seq_model_prediction

今回、epochsとneuronsを増やすことで、上図のとおり精度において納得いく結果を得た。これでコスト関数、活性化関数の選定はじめ、ハイパーパラメータの調整が精度にいかに重要なことを分かる。熱伝播、振動、柔軟ロボット関節の駆動力など元々フーリエ変換、ラプラス変換、Z変換しか解けない偏微分方程式が、LSTMモデルを適用できたらどうかと今度試してみる。

参考文献

1、LSTMネットワークを理解する(英文原稿)、Christopher Olah氏
2、Keras公式資料

2+

ロボット・ドローン部品お探しなら
ROBOT翔・電子部品ストア