8.1. K 最近傍

目標:

この章では、次のことを学習する必要があります。

  • この章では、k 近傍 (kNN) アルゴリズムの概念を理解します。
  • kNN に関する知識を活用して、基本的な OCR アプリケーションを構築します。
  • OpenCV に付属の数値データとアルファベットデータを使用してみます。

k最近傍を理解する

理論

kNN は、教師あり学習の最も単純な分類アルゴリズムの 1 つです。アイデアも非常にシンプルで、特徴空間内でテスト データの最近傍を見つけるというものです。以下の図を用いてご紹介します。

画像1
上の図のオブジェクトは、青い四角と赤い三角形の 2 つのグループに分けることができます。各グループはクラスと呼ぶこともできます。これらすべてのオブジェクトは町の家と考えることができ、すべての家はそれぞれ青と赤のファミリーに属しており、この町はいわゆる特徴空間です。(特徴空間は、すべての点の投影が配置される空間と考えることができます。たとえば、2D 座標空間では、各データには x 座標と y 座標という 2 つの特徴があります。これらのデータは 2D 座標で表すことができます。空間 .各データに 3 つの特徴がある場合、3 次元空間が必要です。N つの特徴には N 次元空間が必要で、この N 次元空間が特徴空間です。上図では、2 つの特徴的な色があると考えることができます2D 空間)。

今、町に新しい男がいます。彼の新しい家は緑色の円盤で表されています。彼の家の位置に基づいて、彼を青い家族または赤い家族に分類します。これをプロセス分類と呼びます。私たちは何をすべきか?kNN について学習しているので、このアルゴリズムを使用してみましょう。

1 つの方法は、最も近い隣人がどの家族に属しているかを確認することです。画像から、最も近いのは赤い三角形の家族であることがわかります。それで彼は赤の家族に割り当てられました。この方法は、分類によって最近傍のみが決定されるため、単純最近傍と呼ばれます。

しかし、ここで別の問題が発生します。赤い三角形が最も近いかもしれませんが、その周りに青い四角形がたくさんある場合はどうなるでしょうか? この時点では、青い四角形の方が赤い三角形よりも局所的な影響が大きいはずです。したがって、最近傍を検出するだけでは十分ではありません。したがって、k 個の最近傍を検出します。k 個の近隣メンバーの中で過半数を占める人が、新しいメンバーのそのカテゴリーに属します。k が 3 に等しい場合、つまり、上の画像では 3 つの最近傍が検出されます。彼には赤の隣人が 2 人、青の隣人が 1 人いるため、依然として赤の家族に属しています。しかし、k が 7 に等しい場合はどうなるでしょうか? 彼には青が 5 人、赤が 2 人の隣人がいます。今度は青の家族に割り当てられます。k の値は結果に大きな影響を与えます。さらに興味深いのは、k が 4 に等しい場合はどうなるでしょうか? 赤が2つ、青が2つです。これは行き止まりです。したがって、kの値は奇数であることが好ましい。k 個の最近傍に基づいて分類する方法は kNN と呼ばれます。

kNN では、k 個の最近傍を考慮しますが、これらの近傍に等しい重みを与えます。これは公平でしょうか? 例として k が 4 である場合、彼女はデッドノットであると言います。しかし、2 つの赤い三角形は 2 つの青い四角よりも新しいメンバーに近いです。したがって、彼は赤い家族として分類されるはずです。それを数学ではどう表現すればいいのでしょうか?新しい家からの距離に基づいて、各家に異なる重みを与えたいと考えています。近いものは重みが高く、遠くあるものは重みが低くなります。そして、両家族の重みの合計に基づいて新居の所有権を判定し、重みが大きい方がどちらに属することになります。これを修正 kNN と呼びます。

それで、ここでわかる重要なことは何ですか?

  • 町全体のすべての家に関する情報が必要です。なぜなら、新規参入者とすべての既存の家との距離を測定し、その中で最も近い家を見つけたいからです。そこに多くの家がある場合、多くのメモリと計算時間が必要になります。

  • トレーニングと処理にはほとんど時間がかかりません。

それでは、OpenCV で見てみましょう。

OpenCV の K 最近傍

ここで、上記と同じ 2 つのクラスを使用した簡単な例を見てみましょう。次のセクションでは、より良い例を示します。

ここでは、赤色のファミリーをクラス 0 としてマークし、青色のファミリーをクラス 1 としてマークします。さらに 25 個のトレーニング データを作成し、それらにクラス 0 またはクラス 1 のラベルを付ける必要があります。Numpy の乱数​​ジェネレーターは、このタスクの達成に役立ちます。

これらの点は、Matplotlib を使用してプロットされます。赤いファミリーは赤い三角形で示され、青いファミリーは青い四角形で示されます。

import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt

# Feature set containing (x,y) values of 25 known/training data
trainData = np.random.randint(0,100,(25,2)).astype(np.float32)

# Labels each one either Red or Blue with numbers 0 and 1
responses = np.random.randint(0,2,(25,1)).astype(np.float32)

# Take Red families and plot them
red = trainData[responses.ravel()==0]
plt.scatter(red[:,0],red[:,1],80,'r','^')

# Take Blue families and plot them
blue = trainData[responses.ravel()==1]
plt.scatter(blue[:,0],blue[:,1],80,'b','s')

plt.show()

上記のようなグラフが得られるかもしれませんが、乱数ジェネレーターを使用しているため、コードを実行するたびに異なる結果が得られるため、まったく同じではありません。

以下は、kNN アルゴリズム分類器の初期化です。kNN 分類器をトレーニングする (検索ツリーを構築する) ために、トレーニング データ セットとトレーニング データに対応する分類を渡す必要があります。

最後に、OpenCV で kNN 分類器を使用するために、それにテスト データを与えて分類させます。kNN を使用する前に、テスト データについて知っておく必要があります。データは、データ数とフィーチャ数の積に等しいサイズの浮動小数点配列である必要があります。次に、計算によってテスト データの最近傍を見つけることができます。返される最近傍の数を設定できます。戻り値には次のものが含まれます。

  1. kNNアルゴリズムによって計算されたテストデータのクラスフラグ(0または1)。最近傍アルゴリズムを使用する場合は、k を 1 に設定します。ここで、k は最近傍アルゴリズムの数です。

  2. k の最近傍のクラス フラグ。

  3. テスト データへの各最近傍の距離。

どのように機能するかを見てみましょう。テストデータは緑色でマークされます。

newcomer = np.random.randint(0,100,(1,2)).astype(np.float32)
plt.scatter(newcomer[:,0],newcomer[:,1],80,'g','o')

knn = cv.ml.KNearest_create()
knn.train(trainData, cv.ml.ROW_SAMPLE, responses)
ret, results, neighbours ,dist = knn.findNearest(newcomer, 3)

print( "result: {}\n".format(results) )
print( "neighbours: {}\n".format(neighbours) )
print( "distance: {}\n".format(dist) )

plt.show()

得られる結果は次のとおりです。

result: [[ 1.]]
neighbours: [[ 1. 1. 1.]]
distance: [[ 53. 58. 61.]]

これは、テスト データに 3 つの近傍があり、それらはすべて青色であるため、青色ファミリーに分類されることを意味します。次の図に示すように、結果は明らかです。

画像2
テストするデータが大量にある場合は、配列を直接渡すことができます。対応する結果も配列になります。

# 10 new comers
newcomers = np.random.randint(0,100,(10,2)).astype(np.float32)
ret, results,neighbours,dist = knn.findNearest(newcomer, 3)
# The results also will contain 10 labels.

kNNを使用した手書きデータのOCR

kNN を使用して手書き数字を OCR する

私たちの目標は、手書きの数字を認識できるプログラムを作成することです。これを達成するには、トレーニング データとテスト データが必要です。OpenCV には、5000 個の手書き数字 (各数字が 500 回繰り返されたもの) を含む画像 Digits.png (opencv/samples/data/ フォルダー内) が付属しています。各番号は 20x20 の画像です。したがって、最初のステップは、この画像を 5000 個の異なる数値に分割することです。各番号の分割画像を、連続 400 ピクセルを含む新しい画像に再配置しています。これは私たちの特徴セットであり、すべてのピクセルのグレー値です。これは作成できる最も単純な機能セットです。各数値の最初の 250 サンプルをトレーニング データとして使用し、残りの 250 をテスト データとして使用します。まず準備しましょう:

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

img = cv.imread('digits.png')
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)

# Now we split the image to 5000 cells, each 20x20 size
cells = [np.hsplit(row,100) for row in np.vsplit(gray,50)]

# Make it into a Numpy array. It size will be (50,100,20,20)
x = np.array(cells)

# Now we prepare train_data and test_data.
train = x[:,:50].reshape(-1,400).astype(np.float32) # Size = (2500,400)
test = x[:,50:100].reshape(-1,400).astype(np.float32) # Size = (2500,400)

# Create labels for train and test data
k = np.arange(10)
train_labels = np.repeat(k,250)[:,np.newaxis]
test_labels = train_labels.copy()

# Initiate kNN, train the data, then test it with test data for k=1
knn = cv.ml.KNearest_create()
knn.train(train, cv.ml.ROW_SAMPLE, train_labels)
ret,result,neighbours,dist = knn.findNearest(test,k=5)

# Now we check the accuracy of classification
# For that, compare the result with test_labels and check which are wrong
matches = result==test_labels
correct = np.count_nonzero(matches)
accuracy = correct*100.0/result.size
print( accuracy )

最も基本的な OCR プログラムの準備ができたので、この例では 91% の精度が得られました。精度を向上させる 1 つの方法は、より多くのトレーニング データ、特に間違った数値を提供することです。プログラムを実行するたびに分類器を準備してトレーニングする必要がないようにするには、次回実行するときにファイルからデータを読み取って分類を開始するだけで済むように、分類器を保存しておくことをお勧めします。
Numpy 関数 np.savetxt、np.load などがこれを行うのに役立ちます。

# save the data
np.savez('knn_data.npz',train=train, train_labels=train_labels)

# Now load the data
with np.load('knn_data.npz') as data:
    print( data.files )
    train = data['train']
    train_labels = data['train_labels']

私のシステムでは、占有スペースは約 4.4M です。現在、グレースケール値 (unint8) を特徴量として使用しているため、保存する前にこれらのデータを np.uint8 形式に変換し、1.1M のスペースしか消費しないようにすることが最善です。データをロードするときに float32 に変換します。

英字のOCR

次に、英文字のOCRを行ってみましょう。上記と同じですが、データと特徴セットが少し異なります。OpenCV が提供するのは画像ではなく、データ ファイル (/samples/cpp/letter-recognition.data) です。開いてみると 20,000 行あり、最初の行はゴミのように見えます。実際、各行の最初の列は文字マークの 1 つです。次の 16 の数字はその異なる特性です。これらの機能は UCI Machine LearningRepository から提供されます。詳細については、このページをご覧ください。

利用可能なサンプルは 20,000 個あり、最初の 10,000 個をトレーニング サンプルとして使用し、残りの 10,000 個をテスト サンプルとして使用します。文字を直接扱っているわけではないので、最初にアルファベットを asc コードに変換する必要があります。

import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt

# Load the data, converters convert the letter to a number
data= np.loadtxt('letter-recognition.data', dtype= 'float32', delimiter = ',',
converters= {
    
    0: lambda ch: ord(ch)-ord('A')})

# split the data to two, 10000 each for train and test
train, test = np.vsplit(data,2)

# split trainData and testData to features and responses
responses, trainData = np.hsplit(train,[1])
labels, testData = np.hsplit(test,[1])

# Initiate the kNN, classify, measure accuracy.
knn = cv.ml.KNearest_create()
knn.train(trainData, cv.ml.ROW_SAMPLE, responses)
ret, result, neighbours, dist = knn.findNearest(testData, k=5)

correct = np.count_nonzero(result == labels)
accuracy = correct*100.0/10000
print( accuracy )

正解率は93.22%に達しました。同様に、トレーニング サンプルの数を増やすことで精度を向上させることができます。

おすすめ

転載: blog.csdn.net/qq_33319476/article/details/130435799