1. 程式人生 > >機器學習實戰 KNN實戰

機器學習實戰 KNN實戰


學習《機器學習實戰》

1、KNN演算法的一般流程

1、蒐集資料:可以使用任何方法

2、準備資料:距離計算所需要的數值,最好是結構化的資料格式

# _*_ encoding=utf8 _*_

from numpy import *
import operator


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

3、分析資料:可以使用任何方法

四個點很明顯可以看見,分為兩類,A標籤有兩個點,B標籤有兩個點。

4、訓練演算法:此步驟不適用於KNN演算法

KNN的虛擬碼思想:
對未知類別屬性的資料集中的每個點依次執行以下操作:
(1)、計算已知類別資料集中的點與當前點之間的操作;
(2)、按照距離遞增次序排序
(3)、選取與當前點距離最小的k個點
(4)、確定前k個點所在類別出現的概率
(5)、返回前k個點出現概率最高的類別作為當前點的預測分類
KNN的python程式碼

def classify(inX,dataSet,labels,k):
    dataSetSize = dataSet.shape[0]
    print(dataSetSize) # 獲取第一維的維度  4*2  也就是4
    # tile 重複inX維度(4,1) inX = [0,0]  ==>先橫向複製4次,縱向複製1次,也就是本身 最後的維度(4,2)
# 這兒求歐式距離,都是求到0 0 的距離 以下幾行都是求歐式距離 diffMat = tile(inX, (dataSetSize, 1)) - dataSet sqDiffMat = diffMat ** 2 # 按列求和,也可按行求和axis=1 sqDistances = sqDiffMat.sum(axis=1) print(sqDistances) distances = sqDistances ** 0.5 print(distances) # 返回從小到大的數值的索引 sortedDistIndicies =
distances.argsort() print(sortedDistIndicies) classCount = {} for i in range(k): voteIlabel = labels[sortedDistIndicies[i]] # 統計離0 0 最近的範圍的標籤是什麼, classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1 sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True) print(sortedClassCount[0][0]) # 輸出預測屬於哪一類

5、測試演算法:計算錯誤率

檢測分類的結果

if __name__ == '__main__':
    group,labels = createDataSet()
    classify([0,0],group,labels,3)

6、使用演算法:首先需求輸入樣本資料和結構化的輸出結果,然後執行KNN演算法判定輸入資料分別屬於哪一個分類,最後應用對計算出的分類執行後續的處理。

2、約會問題

線上約會想找一個適合自己的物件,她把每一個約會物件劃分為三類人。

  • 不喜歡的人
  • 魅力一般的人
  • 極具魅力的人

根據哪些特徵可以把一個人劃分到其中一類呢,她是根據三個特點的分值佔比,將一個人劃分到某一類,這三個特點是:

  • 每年獲得飛行常客里程數
  • 玩視訊遊戲所佔時間百分比
  • 每週消費的冰淇淋公升數
    資料特徵如下:
40920 8.326976 0.953952 3
14488 7.153469 1.673904 2
26052 1.441871 0.805124 1
75136 13.147394 0.428964 1
38344 1.669788 0.134296 1
72993 10.141740 1.032955 1
35948 6.830792 1.213192 3
42666 13.276369 0.543880 3

首先讓檔案資料讀入記憶體,訓練資料和標記資料分開儲存

def file2matrix(filename):
    fr = open(filename)
    numberOfLines = len(fr.readlines())
    returnMat = zeros((numberOfLines,3))
    classLabelVector = []
    fr = codecs.open(filename,'r','utf-8')
    index = 0
    for line in fr.readlines():
        line = line.strip()
        listFromLine = line.split('\t')
        returnMat[index,:] = listFromLine[0:3]
        classLabelVector.append(int(listFromLine[-1]))
        index += 1
    return returnMat,classLabelVector

檢視一下記憶體中的訓練資料和標籤資料
程式碼:

if __name__ == '__main__':
    # group,labels = createDataSet()
    # classify([0,0],group,labels,3)
    datingDataMat,datingLabels = file2matrix('../data/datingTestSet2.txt')
    print(datingDataMat[:3])
    print(datingLabels[0:20])

結果:

[[4.092000e+04 8.326976e+00 9.539520e-01]
 [1.448800e+04 7.153469e+00 1.673904e+00]
 [2.605200e+04 1.441871e+00 8.051240e-01]]
[3, 2, 1, 1, 1, 1, 3, 3, 1, 3, 1, 1, 2, 1, 1, 1, 1, 1, 2, 3]

有了資料,有了程式碼,有了結果標籤,就可以從訓練資料抽取出一條資料,看看結果預測的對不對
選取最後一條資料,資料如下,把他從訓練資料檔案中刪除:

43757	7.882601	1.332446	3

找前3個點出現的概率,測試程式碼如下:

if __name__ == '__main__':
    # group,labels = createDataSet()
    # classify([0,0],group,labels,3)
    datingDataMat,datingLabels = file2matrix('../data/datingTestSet2.txt')
    print(datingDataMat[:3])
    print(datingLabels[0:20])
    classify([43757,7.882601,1.332446], datingDataMat, datingLabels, 3)

。。。預測出來是1,看來是拿到的特徵不夠,改一行程式碼試試,找與前100個點的距離

classify([43757,7.882601,1.332446], datingDataMat, datingLabels, 3)

nice,預測出來是3了。

3、使用Matplotlib建立散點圖

還是使用剛剛從檔案讀取出來的資料,檢視玩視訊遊戲消耗時間佔比和每週消費冰淇淋的公升數這兩個數值,(第一個值太大,不壓縮的話其他特徵就看不見了,所以不選第一列的值)
程式碼:

if __name__ == '__main__':
    datingDataMat,datingLabels = file2matrix('../data/datingTestSet2.txt')
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.scatter(datingDataMat[:,1],datingDataMat[:,2])
    plt.show()

結果圖
在這裡插入圖片描述
橫軸是玩視訊遊戲消耗時間佔比,縱軸是每週消費冰淇淋的公升數
由於顏色一樣,這樣的資料顯示沒什麼太大的意義,現在加上標籤分類,區分顏色檢視這個點屬於那一類。下圖就是檢視第二列和第三列資料,顯示的屬於哪一類(不喜歡、一般魅力、極具魅力)
修改的程式碼:

ax.scatter(datingDataMat[:,1],datingDataMat[:,2],
               15.0*array(datingLabels),15.0*array(datingLabels))

結果圖:
在這裡插入圖片描述

4、歸一化數值

剛剛所說,第一列的資料範圍遠遠大於第二列和第三列的數值範圍,所以第一列帶來的影響也遠遠大於其他兩列,
歸一化就是將資料利用一定的比例壓縮在0-1之間,這兒使用的方法如下
newValue = (oldValue-min)/(max-min)
也就是,(當前資料-該列最小資料)/(該列的最大資料-該列的最小資料)
程式碼:

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))
    normDataSet = normDataSet/tile(ranges, (m,1))
    return normDataSet, ranges, minVals

好像給了測試資料,測試方法如下:

def datingClassTest():
    hoRatio = 0.50      #hold out 10%
    datingDataMat,datingLabels = file2matrix('../data/datingTestSet2.txt')       
    normMat, ranges, minVals = autoNorm(datingDataMat)
    m = normMat.shape[0]
    numTestVecs = int(m*hoRatio)
    errorCount = 0.0
    for i in range(numTestVecs):
        classifierResult = classify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3)
        print("the classifier came back with: %d, the real answer is: %d" % (classifierResult, datingLabels[i]))
        if (classifierResult != datingLabels[i]): errorCount += 1.0
    print("the total error rate is: %f" % (errorCount/float(numTestVecs)))
    print(errorCount)

執行測試程式碼:

if __name__ == '__main__':
    datingClassTest()

檢視結果:

...
the classifier came back with: 2, the real answer is: 2
500
the classifier came back with: 1, the real answer is: 1
500
the classifier came back with: 1, the real answer is: 1
the total error rate is: 0.064128

可以看見約會問題的分類錯誤率是6.4128%
全部程式碼

# _*_ encoding=utf8 _*_

from numpy import *
import operator
import codecs
import matplotlib
import matplotlib.pyplot as plt



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

def classify(inX,dataSet,labels,k):
    dataSetSize = dataSet.shape[0]
    print(dataSetSize) # 獲取第一維的維度  4*2  也就是4
    # tile 重複inX維度(4,1) inX = [0,0]  ==>先橫向複製4次,縱向複製1次,也就是本身 最後的維度(4,2)
    # 這兒求歐式距離,都是求到0 0 的距離 以下幾行都是求歐式距離
    diffMat = tile(inX, (dataSetSize, 1)) - dataSet
    sqDiffMat = diffMat ** 2
    # 按列求和,也可按行求和axis=1
    sqDistances = sqDiffMat.sum(axis=1)
    # print(sqDistances)
    distances = sqDistances ** 0.5

    # print(distances)
    # 返回從小到大的數值的索引
    sortedDistIndicies = distances.argsort()
    # print(sortedDistIndicies)
    classCount = {}
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        # 統計離0 0 最近的範圍的標籤是什麼,
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    # print(sortedClassCount[0][0]) # 輸出預測屬於哪一類
    return sortedClassCount[0][0]

def file2matrix(filename):
    fr = open(filename)
    numberOfLines = len(fr.readlines())
    returnMat = zeros((numberOfLines,3))
    classLabelVector = []
    fr = codecs.open(filename,'r','utf-8')
    index = 0
    for line in fr.readlines():
        line = line.strip()
        listFromLine = line.split('\t')
        returnMat[index,:] = listFromLine[0:3]
        classLabelVector.append(int(listFromLine[-1]))
        index += 1
    return returnMat,classLabelVector

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))
    normDataSet = normDataSet/tile(ranges, (m,1))
    return normDataSet, ranges, minVals

def datingClassTest():
    hoRatio = 0.50      #hold out 10%
    datingDataMat,datingLabels = file2matrix('../data/datingTestSet2.txt')       #load data setfrom file
    normMat, ranges, minVals = autoNorm(datingDataMat)
    m = normMat.shape[0]
    numTestVecs = int(m*hoRatio)
    errorCount = 0.0
    for i in range(numTestVecs):
        classifierResult = classify(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3)
        print("the classifier came back with: %d, the real answer is: %d" % (classifierResult, datingLabels[i]))
        if (classifierResult != datingLabels[i]): errorCount += 1.0
    print("the total error rate is: %f" % (errorCount/float(numTestVecs)))
    print(errorCount)

if __name__ == '__main__':
    # group,labels = createDataSet()
    # classify([0,0],group,labels,3)

    # datingDataMat,datingLabels = file2matrix('../data/datingTestSet2.txt')
    # print(datingDataMat[:3])
    # print(datingLabels[0:20])
    # classify([43757,7.882601,1.332446], datingDataMat, datingLabels, 100)

    # fig = plt.figure()
    # ax = fig.add_subplot(111)
    # ax.scatter(datingDataMat[:,1],datingDataMat[:,2],
    #            15.0*array(datingLabels),15.0*array(datingLabels))
    # plt.show()
    datingClassTest()

5、手寫體識別資料分類

類似之前的約會問題,構造輸入和標籤(10分類)就好。
構造輸入方法也就是讀取檔案的方法:
檢視手寫體的檔案,如下這樣一個矩陣刻畫出來的阿拉伯數字。

00000000000001111000000000000000
00000000000011111110000000000000
00000000001111111111000000000000
00000001111111111111100000000000
00000001111111011111100000000000
00000011111110000011110000000000
00000011111110000000111000000000
00000011111110000000111100000000
00000011111110000000011100000000
00000011111110000000011100000000
00000011111100000000011110000000
00000011111100000000001110000000
00000011111100000000001110000000
00000001111110000000000111000000
00000001111110000000000111000000
00000001111110000000000111000000
00000001111110000000000111000000
00000011111110000000001111000000
00000011110110000000001111000000
00000011110000000000011110000000
00000001111000000000001111000000
00000001111000000000011111000000
00000001111000000000111110000000
00000001111000000001111100000000
00000000111000000111111000000000
00000000111100011111110000000000
00000000111111111111110000000000
00000000011111111111110000000000
00000000011111111111100000000000
00000000001111111110000000000000
00000000000111110000000000000000
00000000000011000000000000000000
# 手寫體識別是32*32的
def img2vector(filename):
    returnVect = 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 handwritingClassTest():
    hwLabels = []
    trainingFileList = os.listdir('../data/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('../data/trainingDigits/%s' % fileNameStr)
    testFileList = os.listdir('../data/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('../data/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)))

結果:

the total number of errors is: 11

the total error rate is: 0.011628

錯誤率很低,非常不錯了