《機器學習實戰》學習筆記(一): k-近鄰算法(手寫識別系統實戰)

《機器學習實戰》學習筆記(一): k-近鄰算法(手寫識別系統實戰)

一、k-近鄰法簡介

k近鄰法(k-nearest neighbor, k-NN)是1967年由Cover T和Hart P提出的一種基本分類與迴歸方法。它的工作原理是:存在一個樣本數據集合,

也稱作爲訓練樣本集,並且樣本集中每個數據都存在標籤,即我們知道樣本集中每一個數據與所屬分類的對應關係。輸入沒有標籤的新數據後,

將新的數據的每個特徵與樣本集中數據對應的特徵進行比較,然後算法提取樣本最相似數據(最近鄰)的分類標籤。一般來說,我們只選擇樣本數據集

中前k個最相似的數據,這就是k-近鄰算法中k的出處,通常k是不大於20的整數。最後,選擇k個最相似數據中出現次數最多的分類,作爲新數據的分類。

說白了,就是將新數據和訓練集中每個數據進行比較,找到距離新數據最近的K個數據,對這K個數據的標籤進行統計,支持數最高的標籤即可認爲

是新數據的標籤。比如K=10,其中有9的標籤是A類,1個的標籤是B類,那麼這個新數據的標籤被認爲是A。

二、手寫識別系統問題

1:背景知識

對於需要識別的數字已經使用圖形處理軟件,處理成具有相同的色彩和大小:寬高是32像素x32像素。儘管採用本文格式存儲圖像不能有效地利用內存空間,但是爲了方便理解,我們將圖片轉換爲文本格式,數字的文本格式如圖所示。

與此同時,這些文本格式存儲的數字的文件命名也很有特點,格式爲:數字的值_該數字的樣本序號

對於這樣已經整理好的文本,我們可以直接使用Python處理,進行數字預測。數據集分爲訓練集和測試集,使用K-近鄰的方法,

自己設計k-近鄰算法分類器,可以實現分類。數據集和實現代碼下載地址:https://github.com/miraitowa/Machine-Learning/tree/master/KNN/3.數字識別

2、sklearn簡介

Scikit learn 也簡稱sklearn,是機器學習領域當中最知名的python模塊之一。sklearn包含了很多機器學習的方式:

1. Classification 分類
1. Regression 迴歸
1. Clustering 非監督分類
1. Dimensionality reduction 數據降維
1. Model Selection 模型選擇
1. Preprocessing 數據與處理

使用sklearn可以很方便地讓我們實現一個機器學習算法。一個複雜度算法的實現,使用sklearn可能只需要調用幾行API即可。所以學習sklearn,可以有效減少我們特定任務的實現週期。

3、sklearn安裝

由於我使用Anaconda安裝,推薦Anaconda,因爲裏面已經內置了NumPy,SciPy等常用工具

conda install scikit-learn

具體安裝教程以及步驟可以查閱:http://blackblog.tech/2018/02/05/十分鐘上手sklearn-1/

KNneighborsClassifier參數說明:

  1. n_neighbors:默認爲5,就是k-NN的k的值,選取最近的k個點。

  2. weights:默認是uniform,參數可以是uniform、distance,也可以是用戶自己定義的函數。uniform是均等的權重,就說所有的鄰近點的權重都是相等的。distance是不均等的權重,距離近的點比距離遠的點的影響大。用戶自定義的函數,接收距離的數組,返回一組維數相同的權重。

  3. algorithm:快速k近鄰搜索算法,默認參數爲auto,可以理解爲算法自己決定合適的搜索算法。除此之外,用戶也可以自己指定搜索算法ball_tree、kd_tree、brute方法進行搜索,brute是蠻力搜索,也就是線性掃描,當訓練集很大時,計算非常耗時。kd_tree,構造kd樹存儲數據以便對其進行快速檢索的樹形數據結構,kd樹也就是數據結構中的二叉樹。以中值切分構造的樹,每個結點是一個超矩形,在維數小於20時效率高。ball tree是爲了克服kd樹高緯失效而發明的,其構造過程是以質心C和半徑r分割樣本空間,每個節點是一個超球體。

  4. leaf_size:默認是30,這個是構造的kd樹和ball樹的大小。這個值的設置會影響樹構建的速度和搜索速度,同樣也影響着存儲樹所需的內存大小。需要根據問題的性質選擇最優的大小。

  5. metric:用於距離度量,默認度量是minkowski,也就是p=2的歐氏距離(歐幾里德度量)。

  6. p:距離度量公式。在上小結,我們使用歐氏距離公式進行距離度量。除此之外,還有其他的度量方法,例如曼哈頓距離。這個參數默認爲2,也就是默認使用歐式距離公式進行距離度量。也可以設置爲1,使用曼哈頓距離公式進行距離度量。

  7. metric_params:距離公式的其他關鍵參數,這個可以不管,使用默認的None即可。

  8. n_jobs:並行處理設置。默認爲1,臨近點搜索並行工作數。如果爲-1,那麼CPU的所有cores都用於並行工作

sklearn小試牛刀

我們知道數字圖片是32x32的二進制圖像,爲了方便計算,我們可以將32x32的二進制圖像轉換爲1x1024的向量。對於sklearn的KNeighborsClassifier輸入可以是矩陣,不用一定轉換爲向量,不過爲了跟自己寫的k-近鄰算法分類器對應上,這裏也做了向量化處理。

然後構建kNN分類器,利用分類器做預測

# -*- coding: UTF-8 -*-
import numpy as np
import operator
from os import listdir
from sklearn.neighbors import KNeighborsClassifier as kNN



"""
函數說明:將32x32的二進制圖像轉換爲1x1024向量。

Parameters:
    filename - 文件名
Returns:
    returnVect - 返回的二進制圖像的1x1024向量

"""
def img2vector(filename):
    #創建1x1024零向量
    returnVect = np.zeros((1, 1024))
    #打開文件
    fr = open(filename)
    #按行讀取
    for i in range(32):
        #讀一行數據
        lineStr = fr.readline()
        #每一行的前32個元素依次添加到returnVect中
        for j in range(32):
            returnVect[0, 32*i+j] = int(lineStr[j])
    #返回轉換後的1x1024向量
    return returnVect

"""
函數說明:手寫數字分類測試

Parameters:
    無
Returns:
    無

"""
def handwritingClassTest():
    #測試集的Labels
    hwLabels = []
    #返回trainingDigits目錄下的文件名
    trainingFileList = listdir('trainingDigits')
    #返回文件夾下文件的個數
    m = len(trainingFileList)
    #初始化訓練的Mat矩陣,測試集
    trainingMat = np.zeros((m, 1024))
    #從文件名中解析出訓練集的類別
    for i in range(m):
        #獲得文件的名字
        fileNameStr = trainingFileList[i]
        #獲得分類的數字
        classNumber = int(fileNameStr.split('_')[0])
        #將獲得的類別添加到hwLabels中
        hwLabels.append(classNumber)
        #將每一個文件的1x1024數據存儲到trainingMat矩陣中
        trainingMat[i,:] = img2vector('trainingDigits/%s' % (fileNameStr))
    #構建kNN分類器
    neigh = kNN(n_neighbors = 3, algorithm = 'auto')
    #擬合模型, trainingMat爲測試矩陣,hwLabels爲對應的標籤
    neigh.fit(trainingMat, hwLabels)
    #返回testDigits目錄下的文件列表
    testFileList = listdir('testDigits')
    #錯誤檢測計數
    errorCount = 0.0
    #測試數據的數量
    mTest = len(testFileList)
    #從文件中解析出測試集的類別並進行分類測試
    for i in range(mTest):
        #獲得文件的名字
        fileNameStr = testFileList[i]
        #獲得分類的數字
        classNumber = int(fileNameStr.split('_')[0])
        #獲得測試集的1x1024向量,用於訓練
        vectorUnderTest = img2vector('testDigits/%s' % (fileNameStr))
        #獲得預測結果
        # classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
        classifierResult = neigh.predict(vectorUnderTest)
        print("分類返回結果爲%d\t真實結果爲%d" % (classifierResult, classNumber))
        if(classifierResult != classNumber):
            errorCount += 1.0
    print("總共錯了%d個數據\n錯誤率爲%f%%" % (errorCount, errorCount/mTest * 100))


"""
函數說明:main函數

Parameters:
    無
Returns:
    無
"""
if __name__ == '__main__':
    handwritingClassTest()

上述代碼使用的algorithm參數是auto,更改algorithm參數爲brute,使用暴力搜索,你會發現,運行時間變長了,變爲10s+。

更改n_neighbors參數,你會發現,不同的值,檢測精度也是不同的。自己可以嘗試更改這些參數的設置,加深對其函數的理解。

而進行簡單的優化處理就會發現

# -*- coding:utf-8 -*-
from numpy import *
import operator
from os import listdir
import matplotlib.pyplot as plt
from sklearn.neighbors import KNeighborsClassifier as kNN

"""函數說明:將32x32的二進制圖像轉換爲1x1024向量。"""

def img2vector(filename): 
    #創建1x1024零向量
    returnVect = np.zeros((1,1024))
    #打開文件
    fr = open(filename)
    #按行讀取
    for i in range(32):
         #讀一行數據
        lineStr = fr.readline()
        #每一行的前32個元素依次添加到returnVect中
        for j in range(32):
            returnVect[0,32*i+j] = int(lineStr[j])
            #返回轉換後的1x1024向量
    return returnVect

"""函數說明:手寫數字分類測試"""

def handwritingClassTest():
     #測試集的Labels
    hwLabels = []
    #返回trainingDigits目錄下的文件名
    trainingFileList = listdir('trainingDigits')
    #返回文件夾下文件的個數
    m = len(trainingFileList)
    #初始化訓練的Mat矩陣,測試集
    trainingMat = np.zeros((m,1024))
    #從文件名中解析出訓練集的類別
    for i in range(m):
         #獲得文件的名字
        fileNameStr = trainingFileList[i]
         #獲得分類的數字
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0])
         #將獲得的類別添加到hwLabels中
        hwLabels.append(classNumStr)
          #將每一個文件的1x1024數據存儲到trainingMat矩陣中
        trainingMat[i,:] = img2vector('trainingDigits/%s' % fileNameStr)
    testFileList = listdir('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('testDigits/%s' % fileNameStr)
        classifierResult = classify(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()

四、總結

1、kNN算法的優缺點

優點

簡單好用,容易理解,精度高,理論成熟,既可以用來做分類也可以用來做迴歸;

可用於數值型數據和離散型數據;

訓練時間複雜度爲O(n);無數據輸入假定;

對異常值不敏感

缺點

計算複雜性高;空間複雜性高;

樣本不平衡問題(即有些類別的樣本數量很多,而其它樣本的數量很少);

一般數值很大的時候不用這個,計算量太大。但是單個樣本又不能太少,否則容易發生誤分。

最大的缺點是無法給出數據的內在含義。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章