機械学習のKNNアルゴリズム概要

序文

KNN (K-Nearest Neighbor) アルゴリズムは、最もシンプルで非常に実用的な機械学習アルゴリズムであり、書籍「 Machine Learning in Practice 」で最初に紹介されたアルゴリズムです。これは、サンプルベースの教師あり学習アルゴリズムに属しており、トレーニングする必要がなく、データの特性を要約するモデルを取得せず、適切なパラメーター K を選択するだけで適用できます。KNN の目標は、トレーニング データ内で最適な K 近傍を見つけ、これらの近傍のラベルに基づいて新しいデータのラベルを予測することです。KNN が予測に使用されるたびに、すべてのトレーニング データが計算に参加します。

kNN には多くのアプリケーション シナリオがあります。

  • 分類問題、音楽の特徴に応じて複数の種類に分類するなどの多重分類問題にも自然に対応できます。
  • ユーザーの過去の行動に基づいて、類似のアイテムやサービスを推奨する推奨システム
  • 顔認識、ナンバープレート認識などの画像認識。

1.コンセプト

1.1 機械学習の基本概念

機械学習は人工知能の分野における非常に重要な分野であり、大量のデータからパターンを発見し、予測を行うのに役立ちます。

機械学習は、教師あり学習、教師なし学習、半教師あり学習の 3 つのタイプに分類できます。

  • 教師あり学習とは、トレーニング データに正解がマークされており、これらのデータを通じてモデルがトレーニングされ、その後、新しいデータが予測されることを意味します。
  • 教師なし学習とは、トレーニング データに正解がマークされず、データのクラスタリングや次元削減などの操作を通じてデータの法則が発見されることを意味します。
  • 半教師あり学習は、教師あり学習と教師なし学習の中間の方法です。

次の表は、機械学習のいくつかの基本概念の説明です。

コンセプト 説明 述べる
分類 データセットをさまざまなカテゴリに分割する 教師あり学習に属します
クラスタリング データセットを類似オブジェクトのクラスに分割するプロセス 教師なし学習に属する
戻る 連続した数値データを予測することを指します。 教師あり学習に属します
サンプルセット 通常、モデルをトレーニングするために使用されるデータ セットを指し、通常はトレーニング セットとテスト セットに分けられます。 サンプル セットの各サンプルには 1 つ以上の特徴とラベルが含まれています。
特徴 サンプルの特性や特徴を説明するために使用されます 通常、トレーニング サンプル セットの列は独立した測定の結果であり、複数の特徴がリンクされてトレーニング サンプルを形成します。
ラベル サンプルが属するカテゴリまたは結果
モデル トレーニング データから学習された規則性またはパターン。 機械学習では、モデルを使用して新しいデータのラベルまたは値を予測できます
勾配 ある点における関数の変化率を指します。 機械学習では、勾配を使用して損失関数を最小化することでモデル パラメーターを最適化できます。

1.2k値

k の値は、複数の近傍のうち、最も類似した上位 k 個の近傍のカテゴリを選択して、現在のサンプルのカテゴリを決定することを意味します。通常、k は 20 以下の整数で、多くの場合、3 または 5 が選択されます。

1.3 距離メトリクス

距離測定とは、kNN アルゴリズムでサンプル間の距離を計算するために使用される方法を指します。一般的に使用される距離指標には、ユークリッド距離、マンハッタン距離、チェビシェフ距離、ミンコフスキー距離などが含まれます。

  • ユークリッド距離

    • 二次元平面

      d = ( x 1 − x 2 ) 2 + ( y 1 − y 2 ) 2 d = \sqrt{(x_1 - x_2)^2 + (y_1 - y_2)^2}d=( ×1バツ2)2+( y1y2)2

    • n次元

      d = ∑ i = 1 n ∣ xi − yi ∣ 2 d=\sqrt{\sum_{i=1}^{n}{\left| x_{i}-y_{i} \right|^{2}}}d=i = 1x私はy私は2

  • マンハッタンの距離

    d = ∑ i = 1 n ∣ xi − yi ∣ d= \sum_{i=1}^{n}|x_i - y_i|d=i = 1x私はy私は

  • チェビシェフ距離

    d = max ( ∣ x 1 − x 2 ∣ , ∣ y 1 − y 2 ∣ , ⋯ , ∣ xi − yi ∣ ) d= max(|x_1 - x_2|, |y_1 - y_2|, \cdots, |x_i - y_i|)d=最大x ( x1バツ2y1y2x私はy私は)

  • ミンコフスキー距離

    d = ∑ i = 1 n ( ∣ xi − yi ∣ ) ppd = \sqrt[p]{\sum_{i=1}^{n}(|x_i - y_i|)^p}d=pi = 1( x私はy私は)p

1.4 重み付け方法

KNN アルゴリズムの重み付け方法とは、距離を計算するときに、距離が異なるサンプルに対して異なる重みを使用することを指します。これらの重みは、サンプル データのソースからの距離、または異なるサンプル間の距離にすることができます。実際の状況に応じて重み付け方法を選択して、より良い分類または予測効果を実現できます。

一般的に使用される数値データの重み付け方法は次のとおりです。

  1. 加重平均: K 個の近傍の属性値の加重平均が、新しいデータ ポイントの予測値として使用されます。
  2. 平均法: K 個の近傍の属性値の平均を新しいデータ ポイントの予測値として取得します。
  3. 最悪値: K 個の近傍の属性値の最小値と最大値を取得し、その平均値を新しいデータ ポイントの予測値として取得します。

一般的な離散データの重み付け方法は次のとおりです。

  1. 逆関数
  2. ガウス関数
  3. 多項式関数

実際の状況に応じて異なる重み付け方法を選択して、より良い分類または予測効果を実現できます。

二つ、悟る

手書きの数字データ セットは、「Machine Learning in Action」の第 2 章で提供されるデータ セットです: https://github.com/pbharrin/machinelearninginaction

2.1 手書き実装

import numpy as np
from collections import Counter
import operator
import math
from os import listdir

# inX 输入向量
# dataSet 训练集
# labels 训练集所代表的标签
# k 最近邻居数目
# output: label
def classify0(inX, dataSet, labels, k):
    sortedDistIndicies=euclideanDistance(inX, dataSet)
    classCount = {
    
    }
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) +  1.0 * weight(sortedDistIndicies[i])
    sortedClassCount = sorted(classCount.items(), key = operator.itemgetter(1), reverse = True)

    return sortedClassCount[0][0]

def euclideanDistance(inX, dataSet):
    dataSetSize = dataSet.shape[0]
    diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
    sqDiffMat = diffMat ** 2
    sqDistances = sqDiffMat.sum(axis = 1)
    distances = sqDistances ** 0.5
    sortedDistIndicies = distances.argsort()
    return sortedDistIndicies

def weight(dist):
    return 1


def classify1(test, train, trainLabel, k):
    distances = []
    for i in range(len(train)):
        distance = np.sqrt(np.sum(np.square(test - train[i, :])))
        distances.append([distance, i])
    distances = sorted(distances)
    targets = [trainLabel[distances[i][1]] for i in range(k)]
    return Counter(targets).most_common(1)[0][0]

def img2vector(filename):
    returnVect = np.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 handWritingDataSet(inputDir):
    hwLabels = []
    fileNames = []
    dataFileList = listdir(inputDir)           
    m = len(dataFileList)
    dataMat = np.zeros((m,1024))
    for i in range(m):
        fileNameStr = dataFileList[i]
        fileStr = fileNameStr.split('.')[0]     
        classNumStr = int(fileStr.split('_')[0])
        hwLabels.append(classNumStr)
        fileNames.append(fileStr)
        dataMat[i,:] = img2vector( inputDir + '/%s' % fileNameStr)
    return dataMat,hwLabels,fileNames

trainMat, trainLabels, _ = handWritingDataSet('digits/trainingDigits/')
testMat, testLabels,testFileNames = handWritingDataSet('digits/testDigits/')

errorCount = 0
k = 3
for idx, testData in enumerate(testMat):
    prefictLabel = classify0(testData, trainMat, trainLabels, k)
    # prefictLabel = classify1(testData, trainMat, trainLabels, k)
    if testLabels[idx] != prefictLabel:
        errorCount+=1
        print("错误数据:%s.txt, 预测数字:%d" % (testFileNames[idx], prefictLabel))
print("k值:%d, 错误数量:%d, 错误率:%.3f%%" %(k, errorCount, errorCount / 1.0 / np.size(testMat, 0) * 100))

knn_classfi

2.2 Scikit-learnライブラリの調整

ドキュメントアドレス: https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KNeighborsClassifier.html#sklearn.neighbors.KNeighborsClassifier

from sklearn.neighbors import KNeighborsClassifier

trainMat, trainLabels, _ = handWritingDataSet('digits/trainingDigits/')
testMat, testLabels,testFileNames = handWritingDataSet('digits/testDigits/')

errorCount = 0
k = 3

neigh = KNeighborsClassifier(n_neighbors=k)
neigh.fit(trainMat, trainLabels)

for idx, testData in enumerate(testMat):
    prefictLabel = neigh.predict([testData])
    if testLabels[idx] != prefictLabel:
        errorCount+=1
        print("错误数据:%s.txt, 预测数字:%d" % (testFileNames[idx], prefictLabel))
print("k值:%d, 错误数量:%d, 错误率:%.3f%%" %(k, errorCount, errorCount / 1.0 / np.size(testMat, 0) * 100))

knn_scikit-learn

2.3 独自のデータをテストする

上記の手書きデータ セットには 1934 のトレーニング セットと 946 のテスト セットがあり、それらはすべて 32x32 の画像から変換されたテキストです。独自の手書きの数字をテストしたい場合は、まず手書きの数字の画像を 32x32 ピクセル形式の画像に変換してから、それをテキストに変換する必要があります。以下は画像からテキストへのコードです。

import cv2
import os

def img2txt(inputDir):
    dataFileList = os.listdir(inputDir)

    for file in dataFileList:
        if not file.endswith('png'):
            continue
        img = cv2.imread(inputDir + file, cv2.IMREAD_GRAYSCALE)
        fr = open(inputDir + file.split('.')[0] + '.txt', 'w')
        height, width = img.shape[0:2]

        for row in range(height):
            line = ''
            for col in range(width):
                if img[row, col] > 250:
                    line+='0'
                else:
                    line+='1'
            fr.write(line)
            fr.write('\n')

        fr.close()
if __name__ == '__main__':
    img2txt('img/')

次に、テスト用に自分で手書きした0~9の数字を10個用意し、以下の数字をWindowsの描画ツールで32×32ピクセルに切り取り、マウスで手書きして実現します。

自分自身の数字

テストのために 10 個の数値をテキストに変換すると、結果のエラー率は 30% になります。

self_knn

3. まとめ

3.1 分析

  • テストセット内の手書き数字を認識する場合、正しく認識できないサンプルが必ず存在しますが、それは他のカテゴリの特徴に比較的近いためであることが観察により判明しました。
  • 独自の手書き数字認識を使用すると、サンプルが似ているため間違っているわけではありません。たとえば、4 が 7 として認識されます。これはあまり明確ではありませんが、サンプルの特性に関連している可能性があります。

3.2 KNN の長所と短所

  • アドバンテージ
    1. アイデアはシンプルで、理論は成熟しており、分類と回帰の両方に使用できます
    2. 最も近い k を選択するため、外れ値の影響を受けません。
  • 欠点がある
    1. KNN は各テスト サンプルとすべてのトレーニング サンプルの間の距離を計算する必要があるため、時間の複雑さと計算コストが高くなります。
    2. データの基礎となる構造情報を提供できません
    3. アルゴリズムは比較的単純ですが、トレーニング データが小さい場合、異なるタイプの非常によく似たデータを区別するのは困難です。

参考

  1. https://github.com/pbharrin/machinelearninginaction

おすすめ

転載: blog.csdn.net/qq_23091073/article/details/130468441