1. 程式人生 > >機器學習實戰之KNN演算法

機器學習實戰之KNN演算法

前段時間在京東上購買了這本很多人都推薦的書---機器學習實戰。剛剛看完第一章,感覺本書很適合   初學者,特別是對急於應用機器學習但又不想深究理論的小白(像我這樣的)。不過在這裡還是推薦一下李航老師的那本《統計學習方法》,該書注重理論推導及挖掘演算法背後的數學本質,和《機器學習實戰》配合起來學習,可以達到事半功倍的效果。有這兩本書基本可以讓我們這些小白開始起飛了。

                                                                   

       看完《統計學習方法》的KNN演算法推導,雖然知道了該演算法的理論基礎,但是對KNN演算法還是沒有一個巨集觀的把握,這樣估計幾天就會忘得一乾二淨。這幾天抽空將《機器學習實戰》中的KNN程式碼動手自己寫了一遍,實踐以後確實對它有了更深的理解,也知道了它的適用範圍。本書是用python語言寫的,確實是個正確的選擇,感覺用起來很像matlab,特別方便。不過聽實驗室某位大神說搞大資料R語言用的也不少,現在真心感覺要學語言實在太多了。在大資料領域,兩大王牌軟體spark和hadoop分別是用scala和java寫的,統計處理又推薦使用R,平時模擬又是python。對於我們這種一直用C/C++的,平時沒寫過什麼軟體應用,一下子要學這麼多高階語言,實在有點招架不住啊!尷尬

廢話不多說,誰讓我鐵了心要進軍大資料的奮鬥。兄弟們,拿起python來。開幹。。。

       KNN演算法概況起來一句話:在樣本資料中,距離測試樣本點的一定範圍內,尋找最多的一種樣本型別,作為測試點的型別。距離可以是歐式距離,也可以非歐式距離,看具體的情況而定。至於範圍大小的選取至關重要,目前貌似學術界也沒什麼辦法可以算出哪個範圍最好,所以工程上一般都是窮舉測試。然後就是樣本點和測試點的構建了,其實也就是特徵提取,這部分是最最重要的,這直接決定了機器學習演算法的準確性。如果特徵不好,即使你的機器學習演算法再好也沒用。如果特徵很棒,即使用的不是最優的機器學習演算法效果也不錯。這是實驗室大牛說的。他說在各種大資料競賽裡,後面起決定性作用的是特徵選擇與提取,而不是演算法(目前是這樣的)。當然,非監督學習除外,因為它根本就不用特徵提取。至於怎樣選擇特徵最有效,這就要結合具體資料,具體問題而言了。本菜也不在這裡YY了。

      書中用了兩個例項來測試KNN演算法, 一個是約會物件判斷,一個是數字識別。本人覺得還是數字識別比較有趣,因為可以自己動手測試一下,特別有成就感吐舌頭。廢話不多說,上程式碼:

#!/usr/bin/env python
#-*- coding: utf-8 -*-

"""
Created on Aug 29, 2015

@author: freestyle4568
this module is kNN method, classifying something.
"""
import numpy as np
import operator

def createdataset():
    """該函式用於產生kNN實驗用列,返回樣本資料集(numpy陣列)和樣本型別集(列表)
    
    Keyword arguments:
    None
    """
    group = np.array([[1.0, 1.1], 
                      [1.0, 1.0],
                      [0, 0], 
                      [0, 0.1]
                      ])
    labels = ['A', 'A', 'B', 'B']
    return group, labels

def classify(testData, dataSet, labels, k):
    """應用KNN方法對測試點進行分類,返回一個結果型別
    
    Keyword argument:
    testData: 待測試點,格式為陣列
    dataSet: 訓練樣本集合,格式為矩陣
    labels: 訓練樣本型別集合,格式為陣列
    k: 近鄰點數
    """
    dataSetSize = dataSet.shape[0]
    multitestData = np.tile(testData, (dataSetSize, 1))
    diffMat = multitestData - dataSet
    sqdiffMat = diffMat**2
    sqdistance = sqdiffMat.sum(axis=1)
    #print(sqdistance)
    distance = sqdistance**0.5
    sortedDistIndex = distance.argsort()
    classCount = {}
    for i in range(k):
        voteIlabel = labels[sortedDistIndex[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
    sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
    #print(sortedClassCount)
    return sortedClassCount[0][0]


if __name__ == '__main__':
    import matplotlib.pyplot as plt
    group, labels = createdataset()
    print('group is:')
    print(group)
    print('labels is:')
    print(labels)
    plt.plot(group[:, 0], group[:, 1], 'o')
    plt.xlim(-0.1, 1.2)
    plt.ylim(-0.1, 1.2)
    plt.show()
    test = [0, 0]
    print('test is:')
    print(test)
    print('classify result is:')
    print(classify(test, group, labels, 3))
    
執行結果:

1.繪製出了測試點在特徵空間中的座標


2.測試點為main函式中寫的,(0,0),分類結果為B,KNN演算法函式編寫正確。

       KNN演算法構建好了,來進行第一個有趣的實驗,約會物件分類實驗。實驗說明:本人特別喜歡約會,並且會給各個約會物件打分,分為三個檔次:非常喜歡,一般喜歡,不喜歡。每個物件特徵現成的,已經提取好了,如:鼻子多少分,嘴巴多少分,喜歡運動否,每天跑多少步等。再進行KNN的時候有個坑,就是各個特徵範圍不一樣,有的大有的小,在計算歐式距離時小範圍的特徵當然不佔優勢,為了避免這種情況需要將特徵進行歸一化處理。然後再進行KNN分類,作者給出了測試用例和樣本資料,所以可以藉此來看看KNN演算法的可靠性。

       約會例項構建:(裡面用到前面我們寫的KNN演算法,所以記得import kNN哦)

#!/usr/bin/env python
# -*- coding: utf-8 -*-

'''
Created on Aug 29, 2015

@author: freestyle4568
'''
import numpy as np
import kNN



def file2matrix(filename):
    """該函式將約會檔案內容轉換成資料處理格式,返回一個測試特徵集(格式二維陣列),和測試集類別集(格式列表)
    
    Keyword argument:
    filename -- 約會資料所在的路徑,最好是絕對路徑
    """
    fr = open(filename)
    arrayOnLines = fr.readlines()
    numberOfLines = len(arrayOnLines)
    returnMat = np.zeros((numberOfLines, 3))
    classLabel = []
    for i in range(numberOfLines):
        line = arrayOnLines[i]
        line = line.strip()
        listFromLine = line.split('\t')
        returnMat[i, :] = listFromLine[0:3]
        classLabel.append(int(listFromLine[-1]))
    return returnMat, classLabel

def autoNorm(dataSet):
    """該函式將資料集的所以特徵歸一化,返回歸一化後的特徵集(格式為陣列),特徵集最小值(格式為一維陣列),特徵集範圍(格式為一維陣列)
    
    Keyword argument:
    dataSet -- 特徵資料集
    """
    minVals = dataSet.min(axis=0)
    maxVals = dataSet.max(axis=0)
    ranges = maxVals - minVals
    dataSize = dataSet.shape[0]
    normDataSet = dataSet - np.tile(minVals, (dataSize, 1))
    normDataSet = normDataSet / np.tile(ranges, (dataSize, 1))
    return normDataSet, minVals, ranges
    
def dateClassTest(filename, k):
    """測試KNN演算法對於約會資料的錯誤率
    
    Keyword argument:
    None
    """
    ratio = 0.10
    dataSet, labels = file2matrix(filename)
    normDateSet, minVals, ranges = autoNorm(dataSet)
    dataSize = dataSet.shape[0]
    numTestVecs = int(dataSize*ratio)
    errorcount = 0.0
    print("m : %d" % dataSize)
    for i in range(numTestVecs):
        classifyResult = kNN.classify(normDateSet[i, :], normDateSet[numTestVecs:dataSize, :],\
                                      labels[numTestVecs:dataSize], k)
        if classifyResult != labels[i]:
            errorcount += 1
        print("the real answer is: %d, the classify answer is: %d" % (labels[i], classifyResult))
    print("the total error ratio is %f" % (errorcount/float(numTestVecs)))
            
    
    
        
if __name__ == '__main__':
    filename = '/home/freestyle4568/lesson/machineLearning/machinelearninginaction/Ch02/datingTestSet2.txt'
    dateClassTest(filename, 3)
測試結果如下:


可以到在本例中KNN的錯誤率為5%。還是相當不錯的。(不過書上測試的書2.4%,難道資料更新過了,還是我KNN寫錯了,應該不會吧疑問

這裡選取K值為3。感興趣的同學可以試試改改K值看看,通過改變範圍大小,KNN的準確率怎麼變化。

當K值取所以樣本數量時,會發現KNN準確率會怎麼樣?當K取1時會怎麼樣?微笑

留給各位思考啦!!!

       再看下面一個更有趣的例子----數字識別。這個很好理解,就是給一個0-9的數字圖片,然後通過KNN看分類結果是多少。

看具體程式碼:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

'''
Created on Aug 31, 2015

@author: freestyle4568
'''

import numpy as np
import os
import kNN

def img2vector(filename): 
    """函式將以文字格式出現的32*32的0-1圖片,轉變成一維特徵陣列,返回一維陣列
    
    Keyword argument:
    filename -- 文字格式的圖片檔案
    """
    
    imgvect = np.zeros((1, 1024))
    fr = open(filename)
    for i in range(32):
        linestr = fr.readline()
        for j in range(32):
            imgvect[0, 32*i + j] = int(linestr[j])
    return imgvect

def handwriteClassfiy(testfile, trainfile, k):
    """函式將trainfile中的文字圖片轉換成樣本特徵集和樣本型別集,用testfile中的測試樣本測試,無返回值
    
    Keyword argument:
    testfile -- 測試圖片目錄
    trainfile -- 樣本圖片目錄
    """
    
    trainFileList = os.listdir(trainfile)
    trainFileSize = len(trainFileList)
    labels = []
    trainDataSet = np.zeros((trainFileSize, 1024))
    for i in range(trainFileSize):
        filenameStr = trainFileList[i]
        digitnameStr = filenameStr.split('.')[0]
        digitLabels = digitnameStr.split('_')[0]
        labels.append(digitLabels)
        trainDataSet[i, :] = img2vector(trainfile + '/' + filenameStr)
    testFileList = os.listdir(testfile)
    testNumber = len(testFileList)
    errorcount = 0.0
    for testname in testFileList:
        testdigit = img2vector(testfile + '/' + testname)
        classifyresult = kNN.classify(testdigit, trainDataSet, labels, k)
        testStr = testname.split('.')[0]
        testDigitLabel = testStr.split('_')[0]
        if classifyresult != testDigitLabel:
            errorcount += 1.0
        #print('this test real digit is:%s, and the result is: %s' % (testDigitLabel, classifyresult))
    print('k = %d, errorRatio is: %f' % (k, errorcount/float(testNumber)))
    return

if __name__ == '__main__':
    filename = '/home/freestyle4568/lesson/machineLearning/machinelearninginaction/Ch02/digits/trainingDigits/0_0.txt'
    traindir= '/home/freestyle4568/lesson/machineLearning/machinelearninginaction/Ch02/digits/trainingDigits'
    testdir = '/home/freestyle4568/lesson/machineLearning/machinelearninginaction/Ch02/digits/testDigits'
    handwriteClassfiy(testdir, traindir, 3)


           正如程式碼中寫的,我們先讀入圖片所在目錄,然後將目錄中的文字格式的二值圖片檔案轉變為一維特徵矩陣。然後代入我們的KNN演算法,得出分類結果,還是一樣的,讓我們看看這次數字識別的效果。


運行了很長的時間,電腦風扇狂轉有沒有,最後結果準確率高達1.3%。大笑

被KNN的準確率震驚到了。不過細細一想,也可能是由於作者準備的數字測試圖片恰好比較規範,和測試樣例比較接近,導致了這麼高的準確率。抱著這種想法,我就打算自己在網上搜點手寫數字圖片下來,用python將圖片二值化一下,大小轉換成統一的32*32。然後按照作者的命名方式放在mydigit目錄下面。

這些是我在百度圖片裡面下的手寫圖片:


為了節約時間,這裡就不把所有圖片貼出來了,感興趣的同學可以自己下一些測試一下。

我先將下來的圖片放在一個imgfile目錄裡面,然後將處理過的文字圖片放在一個mydigit目錄下面。

貼一個我處理過的圖片4:

00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000001111000000000000
00000000000000001111000000000000
00000000000000011111000000000000
00000000000000111111100000000000
00000000000001111111100000000000
00000000000011111111100000000000
00000000000011110111000000000000
00000000000111100111000000000000
00000000011111000111000000000000
00000000011110000111000000000000
00000000111100000111000000000000
00000000111100000111000000000000
00000001111000001111000000000000
00000011111111100111110000000000
00000011111111111111111111111000
00000001111111111111111111111000
00000000000000011111001111110000
00000000000000001111000000000000
00000000000000001111000000000000
00000000000000001111000000000000
00000000000000001110000000000000
00000000000000001110000000000000
00000000000000001110000000000000
00000000000000011110000000000000
00000000000000011110000000000000
00000000000000001110000000000000
00000000000000001110000000000000
00000000000000000110000000000000
00000000000000000000000000000000

是不是很帶感,再來一個8吧:

00000000000000000000000000000000
00000000000000000000000000000000
00000000000011111110000000000000
00000000000010000110000000000000
00000000001100000010000000000000
00000000001100000011000000000000
00000000001100000011000000000000
00000000001100000001000000000000
00000000000100000001000000000000
00000000001110000011000000000000
00000000000110000011000000000000
00000000000011000011000000000000
00000000000001111110000000000000
00000000000000011110000000000000
00000000000000111110000000000000
00000000000001110110000000000000
00000000000001110011000000000000
00000000000011000011000000000000
00000000000011000011100000000000
00000000000110000000100000000000
00000000000110000001101000000000
00000000000110000000111000000000
00000000000100000000110000000000
00000000001100000000111000000000
00000000001100000000011000000000
00000000001000000000010000000000
00000000001100000000110000000000
00000000000110000000010000000000
00000000000101000000100000000000
00000000000111000001100000000000
00000000000011111111000000000000
00000000000000000000000000000000
我的處理程式碼:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

'''
Created on Sep 1, 2015

@author: freestyle4568
'''

import numpy as np
import os
import Image

def changeImgFile(imgDir):
    """該函式將圖片格式的檔案轉換成32*32的文字格式圖片,並刪除原來的圖片格式檔案,無返回值
    
    Keyword argument:
    imgDir -- 儲存圖片的目錄
    """
    
    imgFileList = os.listdir(imgDir)
    print imgFileList
    imgNumber = len(imgFileList)
    for i in range(imgNumber):
        imgName = imgFileList[i]
        print imgName
        imgBig = Image.open(imgDir + '/' + imgName)
        imgNormal = imgBig.resize((32, 32))
        digitImg = imgNormal.convert('L')
        digit_mat = digitImg.load()
        fr = open(imgDir+'/../mydigit/'+str(i), 'w')
        for i in range(32):
            for j in range(32):
                if digit_mat[j, i] != 255:
                    fr.write('1')
                else:
                    fr.write('0')
            fr.write('\n')
        fr.close()
    
if __name__ == '__main__':
    imgDir = '/home/freestyle4568/lesson/machineLearning/kNNinAction/imgfile'
    changeImgFile(imgDir)

測試圖片處理好了,下面直接呼叫我們之前的程式碼,只需要將目錄路徑改一下就可以了。

快來看看測試結果:


尖叫吧,歡呼吧,居然只有一個錯了,可喜可賀,這裡終於可以看到KNN的真正威力了。

可是為什麼我這裡K取16呢?

答對了,我是先掃描,再取錯誤率最小的k值的。想不想看看我的掃描結果(電腦可是嗡嗡直叫啊),可是我怎麼一點也不心疼。。。。


好啦,今天的KNN實踐就見到這裡啦,具體的理論看有時間在補上。(這可是我在教研室偷偷碼的)