機械学習の3、単純ベイズ

概要

単純ベイズ分類器(Classifier)はベイズ定理(Bayes’ theorem)の条件付き確率(事後確率、事前確率)による分類器また分類法である。
$$ \small P(Class | F_1, F_2,,,F_n) = \frac{P(F_1, F_2,,,F_n | Class) P(Class)}{P(F_1, F_2,,,F_n)} $$
\( P(Class | F_1, F_2,,,F_n) \)は事後確率、\( P(F_1, F_2,,,F_n | Class) \)は事前確率で、P(Class)とも先に求められるとされる。{P(F_1, F_2,,,F_n)は分類空間の類(Class)同士に変わらないので、計算が省略可能となる。

また特徴空間にある特徴(Feature)同士がそれぞれ相関しないことに簡略化して、単純ベイズ分類器となる。
$$ \scriptsize P = \frac{P(F_1|Class)P(F_2|Class)…P(F_n|Class)|Class) P(Class)}{P(F_1, F_2,,,F_n)} $$

前述kNN、kd-treeとも分類法だが、条件付き確率のベイズ定理を生かした、計算量が少ない確率論らしい分類法である。

実装の例

訓練データセットと単純ベイズ分類器により、ある言葉リストから暴力傾向あるかどうかを判別する。

'''
Created on Oct 19, 2010
@author: Peter
'''
from numpy import *
#
# Load postingList -> myVocabList.
#
def loadDataSet():
  postingList=[['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'],
               ['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
               ['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'],
               ['stop', 'posting', 'stupid', 'worthless', 'garbage'],
               ['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'],
               ['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']]
  classVec = [0,1,0,1,0,1]    #1 is abusive, 0 not
  return postingList,classVec
#
# Create myVocabList from postingList.
#
def createVocabList(dataSet):
  vocabSet = set([])  #create empty set
  for document in dataSet:
      vocabSet = vocabSet | set(document) #union of the two sets
  return list(vocabSet)
#
# Create 2 vectors(2 categories) from myVocabList.
#
def setOfWords2Vec(vocabList, inputSet):
  returnVec = [0]*len(vocabList)
  for word in inputSet:
      if word in vocabList:
          returnVec[vocabList.index(word)] = 1
      else: print(f"the word: {word} is not in my Vocabulary!")
  return returnVec
#
# Create 2 conditional probability vectors(p0Vect & p1Vect) from trainMatrix.
# Create 1 category probabilitiesies(pAbusive) from trainCategory.
#
def trainNB0(trainMatrix,trainCategory):
  numTrainDocs = len(trainMatrix)
  numWords = len(trainMatrix[0])
  pAbusive = sum(trainCategory)/float(numTrainDocs)
  p0Num = ones(numWords); p1Num = ones(numWords)      #change to ones() 
  p0Denom = 2.0; p1Denom = 2.0                        #change to 2.0
  for i in range(numTrainDocs):
      if trainCategory[i] == 1:
          p1Num += trainMatrix[i]
          p1Denom += sum(trainMatrix[i])
      else:
          p0Num += trainMatrix[i]
          p0Denom += sum(trainMatrix[i])
  p1Vect = log(p1Num/p1Denom)          #change to log()
  p0Vect = log(p0Num/p0Denom)          #change to log()
  return p0Vect,p1Vect,pAbusive
#
# Perform bayes formula. 1: testEntry is abusive, 0: not abusive.
#
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
  p1 = sum(vec2Classify * p1Vec) + log(pClass1)    #element-wise mult
  p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
  if p1 > p0:
      return 1
  else: 
      return 0
#
# Test 2 testEntries.
#  
def testingNB():
  listOPosts,listClasses = loadDataSet()
  myVocabList = createVocabList(listOPosts)
  trainMat=[]
  for postinDoc in listOPosts:
      trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
  p0V,p1V,pAb = trainNB0(array(trainMat),array(listClasses))
  testEntry = ['love', 'my', 'dalmation']
  thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
  print(f"{testEntry}, 'classified as:' {classifyNB(thisDoc,p0V,p1V,pAb)}")
  testEntry = ['stupid', 'garbage']
  thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
  print(f"{testEntry}, 'classified as:' {classifyNB(thisDoc,p0V,p1V,pAb)}")
#
# main.
#
if __name__ == '__main__':
  testingNB()

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

結果

bayes_result
bayes_result

参考文献

「Machine Learning in Action」、Peter Harrington氏

追記

単純ベイズは、回帰に使われた報道がある。
参考文献 Technical Note:Naive Bayes for Regression

0

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

深層学習の4、RNN LSTM stateful

概要

RNNやLSTMのstatefulとは、各Batchのサンプルの状態が次のバッチのサンプルのための初期状態として再利用されるのであれば、Trueに設定するという。計算の便宜を図ってbatch_size毎に分けて処理するが、株価、天気など時系列データに対して、状態を引き継ぐのであれば、stateful=Trueのケースに当たる。stateful=Trueの場合、以下で対応する。

・batch_size引数をモデルの最初のLayerに渡して、batch_sizeを明記する。例えば、サンプル数が32、time_stepsが10、input_dim(入力データの特徴数)が16の場合には,batch_size=32と明記する。
・RNN、LSTMでstateful=Trueに指定する.
・model.fit()を呼ぶときにshuffle=Falseに指定する。
・model.reset_states()を実行して、モデルの全てのLayerの状態を更新する。または、layer.reset_states()を実行して、特定のLayerの状態を更新する。
・model.predict()、model.train_on_batch()、model.predict_classes()関数はいずれもmodel.reset_states()を実行してstatefulに指定したLayerの状態を更新する。
・model.fit()を呼ぶときに、以下例のようにbatch毎にmodel.reset_states()を実行する。

注意点は、model.reset_states()は0にリセットではなく、前回のbatchの最終状態値を次回のbatchの初期状態値として引き継ぐ。

実装の例

print('Build STATEFUL model...')
model = Sequential()
model.add(LSTM(10, batch_input_shape=(1, 1, 1), return_sequences=False, stateful=True))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
...
class ResetStatesCallback(Callback):
    def __init__(self):
        self.counter = 0

    def on_batch_begin(self, batch, logs={}):
        if self.counter % max_len == 0:
            self.model.reset_states()
        self.counter += 1
...        
model.fit(x, y, callbacks=[ResetStatesCallback()], batch_size=1, shuffle=False)
...
print('Train...')
for epoch in range(15):
    for i in range(len(X_train)):
        tr_loss, tr_acc = model.train_on_batch(np.expand_dims(np.expand_dims(X_train[i][j], axis=1), axis=1),
                                               np.array([y_true]))
        model.reset_states()
...
    for i in range(len(X_test)):
        te_loss, te_acc = model.test_on_batch(np.expand_dims(np.expand_dims(X_test[i][j], axis=1), axis=1),
                                              y_test[i])
        model.reset_states()
...
        y_pred = model.predict_on_batch(np.expand_dims(np.expand_dims(X_test[i][j], axis=1), axis=1))
        model.reset_states()

【追記】

model.fit()のcallbackについて、以下アラームが出る。

RuntimeWarning: Method (on_train_batch_begin) is slow compared to the batch update. Check your callbacks.

これは、model.fit()のcallbackがbatch updateより遅いという、なおかつ、KERASの解釈では「RNNをstatefulにするとは,各バッチのサンプルの状態が,次のバッチのサンプルのための初期状態として再利用されるということを意味する」とあるので、model.fit()を以下のように修正する。

for i in range(NUM_EPOCHS):
    for j in range(BATCH_SIZE*NUM_BATCHES):
        model.fit(x, y, batch_size=1, epochs=1, shuffle=False)
        model.reset_states()

ただし、batchが順番に処理してGPU/TPUの並行処理ができなくなるので、処理時間が余儀なく伸びる。精度と処理時間の兼ね合いから課題として、stateless、batch_sizeを増やすのと、statefulにするのと、どちらがよいのかを案件ごとに実験することが大切だと考える。

参考文献

Keras FAQ
・「Deep Learning with Keras」、by Antonio Gulli氏

1+

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

機械学習の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翔・電子部品ストア