「機械学習の実践」第 2 章 K 最近傍アルゴリズム学習の概要

2.1 アルゴリズムの概要

k 最近傍アルゴリズム (KNN) アルゴリズムは、分類のために異なる特徴値間の距離を測定する方法を使用します

アルゴリズムの動作原理:サンプル データ セット (トレーニング サンプル セット) があり、サンプル セット内の各データ (一連のサンプル要素の行ベクトル) にはラベルがあります (分類作業は完了しており、各データとそれが属するカテゴリとの対応関係)。テストサンプルが入力されると(非収集、未分類のデータ、ラベルが存在しない)、新しいデータの各特徴がサンプルセット内のデータの対応する特徴と比較され、アルゴリズムによって最も類似した分類ラベルが抽出されます。トレーニング サンプル セット内のデータ (最近傍)。一般に、サンプル セット内の最も類似した上位 k 個のデータのみが選択され、k は通常 20 以下の整数であり、最終的に k 個の類似データの中で最も多く出現したカテゴリが新しいデータ カテゴリとして選択されます。

長所: 高精度、異常値の影響を受けにくい、データ入力の仮定がない
短所: 計算の複雑さ、空間の複雑さの高さ
使用されるデータ範囲: 数値および名目 (限られたサンプルセット)


例:映画のジャンル分類を例にとると、トレーニング サンプル セットの特徴 (つまり、サンプル データまたは特徴、戦闘シーンの数、キス シーン) と特徴ラベル (つまり、分類、映画の種類: ロマンス、アクション) がわかっています。

テストサンプルを入力し、サンプル内の特徴量とトレーニングサンプルセット内の各サンプルの特徴量との距離を計算し、既知のムービーとロケーションムービー間の距離を取得し、距離の昇順に並べ、最初の k を取得します。 k=3 の映画、これによると、これまでにいくつかの映画タイプが映画タイプを判断します。


k 最近傍アルゴリズムの一般的なフローは次のとおりです。

(1) データ収集: 任意の方法を使用できます
(2) データ準備: 距離の計算に必要な値、できれば構造化データ形式での値
(3) データ分析: 任意の方法を使用できます
(4) トレーニング アルゴリズム: このステップk 近傍アルゴリズムには適用されません
(5) テスト アルゴリズム: 誤り率を計算します
(6) アルゴリズムを使用します: まず、サンプル データと構造化された出力結果を入力する必要があります。次に、k 近傍アルゴリズムを実行して決定します。入力データがどのカテゴリに属する​​かを判断し、最終的に計算された値を適用して分類し、その後の処理を実行します

2.1.1 準備: Python を使用してデータをインポートする

トレーニング サンプル セットを作成するための KNN.py ファイルを作成します (ライブラリ関数として呼び出すことができます)。

from numpy import *
import operator

def creatDataSet():
    group=array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
    labels=['A','A','B','B']
    return group,labels

2.1.2 テキスト ファイルからのデータの解析

K 最近傍アルゴリズムの疑似コード:

カテゴリ属性が不明なデータ セット内の各点に対して、次の操作を順番に実行します。
(1) 既知のカテゴリ データ内の点と現在の点の間の距離を計算します。
(2) 距離が大きい順に並べ替えます。
(3) 選択します。距離が最小の現在の点 k 点
(4) 最初の k 点のカテゴリの出現頻度を決定します
(5) 最初の k 点の出現頻度が最も高いカテゴリを現在の点の予測分類として返します

プログラムリスト 2-1 k 最近傍アルゴリズム

関数には 4 つの入力パラメーターがあります。分類用の入力ベクトル (テスト サンプル) inX、入力トレーニング サンプル セットはdataSet、ラベル ベクトルはラベル、最後のパラメーター k は最近傍、ラベルの選択に使用される番号を示します。ベクトル要素とトレーニング サンプル セットの行数は同じです。

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={}
    
    #选择距离最小的k个点
    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

ベクトル点 xA と xB の間の距離を計算するユークリッド距離計算式:

 すべてのポイント間の距離を計算した後、データを昇順に並べ替え、距離が最も小さい最初の k 個の要素の主分類 (特徴) を決定します。入力 k は常に正の整数です。最後に、classCount 辞書は次のように分解されます。タプルのリストを取得し、プログラムの 2 行目にインポートされた演算子モジュールの itemgetter メソッドを使用して、タプルを 2 番目の要素の順序で並べ替えます。ここでの並べ替えは逆の順序で行われます。つまり、最大から最小の順に並べ替えられ、最後に出現頻度が最も高い要素ラベルが返されます。

2.1.3 分類器をテストする方法

分類器は、既知の特性のデータ セットを使用してテストできます。大量のテスト データを使用すると、分類器のエラー率(分類器が間違った結果を出した回数をテストの総実行数で割った値) を取得できます。エラー率は主に、分類器が特定のデータセットでどの程度うまく機能するかを評価するために使用されます。

2.2 例: k 最近傍アルゴリズムを使用して出会い系サイトでのマッチング効果を向上させる

例: 出会い系サイトでの k 最近傍アルゴリズムの使用:

(1) データ収集: テキスト ファイルを提供します。
(2) データの準備: Python を使用してテキスト ファイルを解析します。
(3) データの分析: Matplotlib を使用して 2 次元拡散マップを描画します。
(4) トレーニング アルゴリズム: このステップは、k 最近傍アルゴリズムには適していません。
(5) テストアルゴリズム: Helen から提供されたデータの一部をテストサンプルとして使用します。
   テストサンプルと非テストサンプルの違いは、テストサンプルは分類されたデータであるため、予測された分類が
   実際のカテゴリと異なる場合、エラーとしてマークされます。
(6) アルゴリズムを使用する: 簡単なコマンド ライン プログラムを生成すると、ヘレンはいくつかの特徴データを入力して、相手が自分の
好みのタイプであるかどうかを判断できます。

2.2.1 データの準備: テキスト ファイルからのデータの解析

リスト 2-2 は、テキスト レコードを Numpy のパーサーに変換します。

def file2matrix(filename):
    fr=open(filename)
    arrayOLines=fr.readlines()#读取文件所有行(直到结束符 EOF)并返回列表
    numberOfLines=len(arrayOLines)#得到文件行数
    returnMat=zeros((numberOfLines,3))#创建返回的numpy数组
    classLabeVector=[]
    index=0
    #对数据进行解析
    for line in arrayOLines:
        line =line.strip()#移除字符串头尾指定的字符(默认为空格或换行符)或字符序列,该处去除首尾空格符\n
        listFormLine=line.split('\t')#以\t为间隔拆分字符串,通过指定分隔符对字符串进行切片,并返回分割后的字符串列表(list)
        returnMat[index,:]=listFormLine[0:3]#将分割出的前3个字符串存入数组中,数组中第1、2、3数据分别表示特征“每年获得的飞行常客里程数”、“玩视频游戏所耗时间百分比”和“每周所消费的冰淇淋公升数”
        classLabeVector.append(listFormLine[-1])#将标签存入数组中
        index+=1
    return returnMat,classLabeVector

2.2.2 データの分析: Matplotlib を使用した散布図の作成

Matplotlib を使用して生データの散布図を作成します。

datingDataMat,datingLabels=file2matrix('datingTestSet.txt')
fig=plt.figure()
ax=fig.add_subplot(111)
ax.scatter(datingDataMat[:,1],datingDataMat[:,2])
plt.show()

デートデータマット行列の列 2 と列 3 のデータを使用して、それぞれ「ビデオ ゲームのプレイに費やした時間の割合」と「1 週間あたり消費したアイスクリームのリットル」の特徴を表します。

トレーニング サンプル セット内の特定の固有値が他の固有値よりもはるかに大きい場合、その固有値は計算結果に重大な影響を与えます。したがって、異なる値範囲の固有値を扱う場合は、通常、数値正規化手法を使用して 0 と 1、または -1 と 1 の間の固有値を処理します。  

 max と min は、それぞれデータセット内の最大と最小の固有値です。

リスト 2-3 正規化された固有値

def autoNorm(dataSet):
    minVals=dataSet.min(0)
    maxVals=dataSet.max(0)
    ranges=maxVals-minVals
    normDataSet=zeros(shape(dataSet))#生成一个和输入矩阵相同形状的零矩阵
    m=dataSet.shape[0]#取零矩阵的行数
    normDataSet=dataSet-tile(minVals,(m,1))#b = tile(a,(m,n)):即是把a数组里面的元素复制n次放进一个数组c中,然后再把数组c复制m次放进一个数组b中
    normDataSet=normDataSet/tile(ranges,(m,1))
    return normDataSet,ranges,minVals

2.2.4 アルゴリズムのテスト: 分類器を完全なプログラムとして検証する

分類器のパフォーマンスは、分類器が間違った結果を出した回数をテスト データの総数で割ったエラー率によってテストされます。

コード リスト 2-4 出会い系サイトの分類子テスト コード

def datingClassTest():
    hoRatio=0.10
    datingDataMat,datingLabels=file2matrix('datingTestSet.txt')#首先从文件中读取数据
    normMat, ranges, minVals = autoNorm(datingDataMat)#将特征值进行归一化处理
    m=normMat.shape[0]#获得向量数量
    numTestVec=int(m*hoRatio)#确定测试向量的数量
    errorCount=0.0
    #将测试向量输入分类器函数classify0,最后计算错误率并输出结果
    for i in range(numTestVec):
        classifierResult=classify0(normMat[i,:],normMat[numTestVec:m,:],datingLabels[numTestVec:m],3)
        print('the classifier came back with:%d,the real answer is :%d'% (classifierResult[0][0],datingLabels[i]))
        if(classifierResult[0][0]!=datingLabels[i]):
            errorCount+=1.0
    print('the total errot rate is :%f'%(errorCount/float(numTestVec)))

テスト結果の誤差は5%で、関数デートClassTestの変数hoRatio変数kの値を変更することで分類の正答率を調整できます。

2.2.5 アルゴリズムの使用: 完全に使用可能なシステムの構築

リスト 2-5 出会い系サイトの予測関数

def classifyPerson():
    resultList=['in large doses','in small doses','not at all']
    #获得对象的三个主要特征
    percentTats=float(input('percentage of time spent playing video games?'))
    ffMiles=float(input('frequent flier miles earned per year?'))
    iceCream=float(input('liters of ice cream consumed per year?'))
    datingDataMat, datingLabels = file2matrix('datingTestSet.txt')  # 从文件中读取数据
    normMat,ranges,minVals=autoNorm(datingDataMat)#将特征值进行归一化处理
    inArr=array([ffMiles,percentTats,iceCream])#将对象特征值便会数组
    classifierResult=classify0((inArr-minVals)/ranges,normMat,datingLabels,3)#将测试向量输入分类器函数classify0,进行分类
    print('you will probably like the person:',resultList[classifierResult[0][0]-1])

2.3 手書き認識システム

2.3.1 データの準備: 画像をテスト ベクトルに変換する

例: k 最近傍アルゴリズムを使用した手書き認識システム

(1) データ収集: テキスト ファイルを提供します。
(2) データの準備: 画像形式を分類子が使用するリスト形式に変換する関数 assign0() を記述します。
(3) データの分析: Python コマンド プロンプトでデータをチェックし、要件を満たしていることを確認します。
(4) トレーニング アルゴリズム: このステップは、k 最近傍アルゴリズムには適していません。
(5) テストアルゴリズム: 提供されたデータセットの一部をテストサンプルとして使用する関数を記述します. テストサンプルと非テストサンプルの違いは、テストサンプルが分類されたデータであることです
。予測された分類が実際のカテゴリと異なる場合、
エラーとしてマークされます。
(6) アルゴリズムを使用する: この例では、このステップは完了していません。興味があれば、完全なアプリケーション プログラムを構築し、画像から番号
を抽出し .システムです。

画像をベクトルに変換する関数: この関数は、1 x 1024 NumPy 配列を作成し、指定されたファイルを開き、ファイルの最初の 32 行をループし、各行の最初の 32 文字の値を NumPy に保存します。配列を返し、最後に配列を返します。

#function6:32x32图像转1x1024向量函数
def img2vector(filename):
    retrunVect = zeros((1,1024))
    fr=open(filename)
    for i in range(32):
        linStr=fr.readline()
        for j in range(32):
            retrunVect[0,32*i+j]=int(linStr[j])
    return retrunVect

2.3.2 テスト アルゴリズム: k 最近傍アルゴリズムを使用して手書き数字を認識する

リスト 2-6 手書き数字認識システムのテスト コード

def handwritingClassTest():
    hwLabels = []
    trainingFileList = listdir('digits/trainingDigits')#获得训练集目录内容
    m = len(trainingFileList)#获取目录内文件名数量
    trainingMat = zeros((m,1024))
    #从文件名中解析出分类的数字
    for i in range(m):
        fileNameStr = trainingFileList[i]
        fileStr = fileNameStr.split('.')[0]#以.作为分隔符,取左边第一个字符串
        classNumStr = int(fileStr.split('_')[0])#以_作为分隔符,取左边第一个字符串
        hwLabels.append(classNumStr)#将从文件名中分离出的数字加入到数组中
        trainingMat[i,:] = img2vector('digits/trainingDigits/%s' % fileNameStr)#将32x32图像转1x1024向量,存入矩阵中
    testFileList = listdir('digits/testDigits')#获得测试集目录内容
    errorCount = 0.0
    mTest = len(testFileList)
    for i in range(mTest):
        fileNameStr = testFileList[i]
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0])
        vectorUnderTest = img2vector('digits/testDigits/%s' % fileNameStr)
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
        print("the classifier came back with: %d, the real answer is: %d" % (classifierResult[0][0], classNumStr))
        if (classifierResult[0][0] != classNumStr):
            errorCount += 1.0
    print("\nthe total number of errors is: %d" % errorCount)
    print("\nthe total error rate is: %f" % (errorCount/float(mTest)))

 

 変数 k の値の変更、関数によってランダムに選択されたトレーニング サンプルの変更、トレーニング サンプルの数の変更はすべて、分類関数の精度を変更する可能性があります。

2.4 概要

k 最近傍アルゴリズムは、データを分類するための最も単純かつ効果的なアルゴリズムです。

k 最近傍アルゴリズムではすべてのデータ セットを保存する必要があるため、トレーニング データ セットが大きい場合は、大量のストレージ スペースを使用する必要があります。さらに、データセット内のデータごとに距離値を計算する必要があるため、実際には非常に時間がかかる可能性があります。k 最近傍アルゴリズムのもう 1 つの欠点は、データの基本的な構造情報を与えることができないため、平均的なインスタンス サンプルと典型的なインスタンス サンプルの特性を知ることができないことです。

記憶域スペースと計算時間のオーバーヘッドを削減するアルゴリズムはありますか? k 決定ツリーは、k 最近傍アルゴリズムの最適化されたバージョンであり、計算オーバーヘッドを大幅に節約できます。

from numpy import *
import operator
import matplotlib
import matplotlib.pyplot as plt
from os import listdir

def creatDataSet():
    group=array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
    labels=['A','A','B','B']
    return group,labels

#function1:简单分类函数
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={}

    # 选择距离最小的k个点
    for i in range(k):
        voteIlabel=labels[sortedDistIndicies[i]]
        classCount[voteIlabel]=classCount.get(voteIlabel,0)+1

    # 升序排序
    sortedClassCount=sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
    return sortedClassCount

#fuction2:使用Matplotlib创建散点图
def file2matrix(filename):
    fr=open(filename)
    arrayOLines=fr.readlines()#读取文件所有行(直到结束符 EOF)并返回列表
    numberOfLines=len(arrayOLines)#得到文件行数
    returnMat=zeros((numberOfLines,3))#创建返回的numpy数组
    classLabeVector=[]
    index=0
    #对数据进行解析
    for line in arrayOLines:
        line =line.strip()#移除字符串头尾指定的字符(默认为空格或换行符)或字符序列,该处去除首尾空格符\n
        listFormLine=line.split('\t')#以\t为间隔拆分字符串,通过指定分隔符对字符串进行切片,并返回分割后的字符串列表(list)
        returnMat[index,:]=listFormLine[0:3]#将分割出的前3个字符串存入数组中,数组中第1、2、3数据分别表示特征“每年获得的飞行常客里程数”、“玩视频游戏所耗时间百分比”和“每周所消费的冰淇淋公升数”
        classLabeVector.append(int(listFormLine[-1]))#将标签存入数组中,1,2,3分别表示喜欢、一般喜欢、不喜欢
        index+=1
    return returnMat,classLabeVector

#function3:归一化特征值,该函数会自动对每列元素即所有特征值都进行归一化处理
def autoNorm(dataSet):
    minVals=dataSet.min(0)
    maxVals=dataSet.max(0)
    ranges=maxVals-minVals
    normDataSet=zeros(shape(dataSet))#生成一个和输入矩阵相同形状的零矩阵
    m=dataSet.shape[0]#取零矩阵的行数
    normDataSet=dataSet-tile(minVals,(m,1))#b = tile(a,(m,n)):即是把a数组里面的元素复制n次放进一个数组c中,然后再把数组c复制m次放进一个数组b中
    normDataSet=normDataSet/tile(ranges,(m,1))
    return normDataSet,ranges,minVals

#function4:分类器针对约会网站的测试代码
def datingClassTest():
    hoRatio=0.10
    datingDataMat,datingLabels=file2matrix('datingTestSet.txt')#首先从文件中读取数据
    normMat, ranges, minVals = autoNorm(datingDataMat)#将特征值进行归一化处理
    m=normMat.shape[0]#获得向量数量
    numTestVec=int(m*hoRatio)#确定测试向量的数量
    errorCount=0.0
    #将测试向量输入分类器函数classify0,最后计算错误率并输出结果
    for i in range(numTestVec):
        classifierResult=classify0(normMat[i,:],normMat[numTestVec:m,:],datingLabels[numTestVec:m],3)
        print('the classifier came back with:%d,the real answer is :%d'% (classifierResult[0][0],datingLabels[i]))
        if(classifierResult[0][0]!=datingLabels[i]):
            errorCount+=1.0
    print('the total errot rate is :%f'%(errorCount/float(numTestVec)))

#fuction5:约会网站预测函数
def classifyPerson():
    resultList=['in large doses','in small doses','not at all']
    #获得对象的三个主要特征
    percentTats=float(input('percentage of time spent playing video games?'))
    ffMiles=float(input('frequent flier miles earned per year?'))
    iceCream=float(input('liters of ice cream consumed per year?'))
    datingDataMat, datingLabels = file2matrix('datingTestSet.txt')  # 从文件中读取数据
    normMat,ranges,minVals=autoNorm(datingDataMat)#将特征值进行归一化处理
    inArr=array([ffMiles,percentTats,iceCream])#将对象特征值便会数组
    classifierResult=classify0((inArr-minVals)/ranges,normMat,datingLabels,3)#将测试向量输入分类器函数classify0,进行分类
    print('you will probably like the person:',resultList[classifierResult[0][0]-1])

#function6:32x32图像转1x1024向量函数
def img2vector(filename):
    retrunVect = zeros((1,1024))
    fr=open(filename)
    for i in range(32):
        linStr=fr.readline()
        for j in range(32):
            retrunVect[0,32*i+j]=int(linStr[j])
    return retrunVect

#function7:手写数字识别系统测试代码
def handwritingClassTest():
    hwLabels = []
    trainingFileList = listdir('digits/trainingDigits')#获得训练集目录内容
    m = len(trainingFileList)#获取目录内文件名数量
    trainingMat = zeros((m,1024))
    #从文件名中解析出分类的数字
    for i in range(m):
        fileNameStr = trainingFileList[i]
        fileStr = fileNameStr.split('.')[0]#以.作为分隔符,取左边第一个字符串
        classNumStr = int(fileStr.split('_')[0])#以_作为分隔符,取左边第一个字符串
        hwLabels.append(classNumStr)#将从文件名中分离出的数字加入到数组中
        trainingMat[i,:] = img2vector('digits/trainingDigits/%s' % fileNameStr)#将32x32图像转1x1024向量,存入矩阵中
    testFileList = listdir('digits/testDigits')#获得测试集目录内容
    errorCount = 0.0
    mTest = len(testFileList)
    for i in range(mTest):
        fileNameStr = testFileList[i]
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0])
        vectorUnderTest = img2vector('digits/testDigits/%s' % fileNameStr)
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
        print("the classifier came back with: %d, the real answer is: %d" % (classifierResult[0][0], classNumStr))
        if (classifierResult[0][0] != 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__':
    # 简单分类函数示例
    # group,lables=creatDataSet()
    # result=classify0([1,1],group,lables,3)
    # print(result[0][0])

    #使用Matplotlib创建散点图
    # datingDataMat,datingLabels=file2matrix('datingTestSet.txt')
    # fig=plt.figure()
    # ax=fig.add_subplot(111)
    # ax.scatter(datingDataMat[:,1],datingDataMat[:,2],15.0*array(datingLabels),15.0*array(datingLabels))
    # plt.show()

    #归一化特征值
    # datingDataMat, datingLabels = file2matrix('datingTestSet.txt')
    # normMat,ranges,minVals=autoNorm(datingDataMat)

    #分类器测试
    # datingClassTest()

    #约会网站预测
    # classifyPerson()

    #图片向量转化
    # testVector=img2vector('digits/testDigits/0_13.txt')
    # print(testVector[0,0:31])

    #手写数字识别测试
    handwritingClassTest()

おすすめ

転載: blog.csdn.net/weixin_45182459/article/details/125975649