1. 程式人生 > >Python3 機器學習實戰自我講解(二) K-近鄰法-海倫約會-手寫字型識別

Python3 機器學習實戰自我講解(二) K-近鄰法-海倫約會-手寫字型識別

第二章 k近鄰法

2.1 概念

2.1.1 k近鄰法簡介

 k近鄰法(k-nearest neighbor, k-NN)是1967年由Cover T和Hart P提出的一種基本分類與迴歸方法。它的工作原理是:存在一個樣本資料集合,也稱作為訓練樣本集,並且樣本集中每個資料都存在標籤,即我們知道樣本集中每一個數據與所屬分類的對應關係。輸入沒有標籤的新資料後,將新的資料的每個特徵與樣本集中資料對應的特徵進行比較,然後演算法提取樣本最相似資料(最近鄰)的分類標籤。一般來說,我們只選擇樣本資料集中前k個最相似的資料,這就是k-近鄰演算法中k的出處,通常k是不大於20的整數。最後,選擇k個最相似資料中出現次數最多的分類,作為新資料的分類。
K近鄰演算法
優 點 :精度高、對異常值不敏感、無資料輸入假定。
缺點:計算複雜度高、空間複雜度高。
適用資料範圍:數值型和標稱型

舉個例子,我們來使用k近鄰法來分類愛情片和動作片。有人曾統計過很多電影的打鬥鏡頭和接吻鏡頭,那麼如何確定一部沒看過的電影是愛情片還是動作片呢?我們可以使用k-nn來解決這個問題。

電影名稱 打鬥鏡頭 接吻鏡頭 電影型別
California Man 3 104 愛情片
He’s Not Really into Dudes 2 100 愛情片
Robo Slayer 3000 99 5 動作片
Amped ll 98 2 動作片
18 90 未知

​ 每部電影的打鬥鏡頭數、接吻鏡頭數以及電影評估型別

現在我們如何判斷該電影的型別呢?當然是通過最近鄰居來判斷啦~如何判斷鄰居的距離呢,首先我們需要定義距離。

2.1.2 距離度量

我們一般運用歐氏距離:

AB||=(x1x2)2+(y1y2)2

這樣我們可以得到

電影名稱 與未知電影的距離
California Man 20.5
He’s Not Really into Dudes 18.7
Robo Slayer 3000 117.4
Amped ll 118.9

由此可知最近的鄰居為《California Man》,所以我們未知電影的型別可以判定為愛情片。

k-近鄰演算法的一般流程:
(1)收集資料:可以使用任何方法。
(2)準備資料:距離計算所需要的數值,最好是結構化的資料格式。
(3)分析資料:可以使用任何方法。
(4)訓練演算法:此步驟不適用於k近鄰演算法。
(5)測試演算法:計算錯誤率。
(6)使用演算法:首先需要輸入樣本資料和結構化的輸出結果,然後執行k -近鄰演算法判定輸
入資料分別屬於哪個分類,最後應用對計算出的分類執行後續的處理。

2.1.3 實戰環節

python3實現:

# -*- coding: UTF-8 -*-
import numpy as np
#資料集,標籤
dataSet=np.array([[3,104],[2,100],[99,5],[98,2]])
labels=['愛情片','愛情片','動作片','動作片']

def classy(inx,dataSet,labels):
    diffMat=inx-dataSet
    distanceMat=np.sqrt(np.sum(diffMat**2,axis=1))
    sorted_distanceMat=np.argsort(distanceMat)
    classylabel=labels[sorted_distanceMat[0]]
    return classylabel
label=classy([18,90],dataSet,labels)
print("電影型別為:{0:s}".format(label))

這裡寫圖片描述

可以看到,分類結果根據我們的”經驗”,是正確的

接下來我們來挑戰更有難度的。

def classify0(inX, dataSet, labels, k):
    diffMat = inX- dataSet                              #python的廣播機制
    sqDiffMat = diffMat**2
    sqDistances = sqDiffMat.sum(axis=1)
    distances = sqDistances**0.5                        #距離計算
    sortedDistIndicies = distances.argsort()            #距離排序從小到大
    classCount={}                                       #建立字典
    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)    #python2 使用classCount.iteritems()
    return sortedClassCount[0][0]                       #選擇k個距離最小的中票數最多的

2.2 海倫約會

2.2.1 k-近鄰演算法實戰之約會網站配對效果判定

上一小結學習了簡單的k-近鄰演算法的實現方法,但是這並不是完整的k-近鄰演算法流程,k-近鄰演算法的一般流程:

  1. 收集資料:可以使用爬蟲進行資料的收集,也可以使用第三方提供的免費或收費的資料。一般來講,資料放在txt文字檔案中,按照一定的格式進行儲存,便於解析及處理。
  2. 準備資料:使用Python解析、預處理資料。
  3. 分析資料:可以使用很多方法對資料進行分析,例如使用Matplotlib將資料視覺化。
  4. 測試演算法:計算錯誤率。
  5. 使用演算法:錯誤率在可接受範圍內,就可以執行k-近鄰演算法進行分類。

​ 已經瞭解了k-近鄰演算法的一般流程,下面開始進入實戰內容。

2.2.2 實戰背景

海倫女士一直使用線上約會網站尋找適合自己的約會物件。儘管約會網站會推薦不同的任選,但她並不是喜歡每一個人。經過一番總結,她發現自己交往過的人可以進行如下分類:

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

​ 海倫收集約會資料已經有了一段時間,她把這些資料存放在文字檔案datingTestSet.txt中,每個樣本資料佔據一行,總共有1000行。

資料集下方下載:

海倫收集的樣本資料主要包含以下3種特徵:

  • 每年獲得的飛行常客里程數
  • 玩視訊遊戲所消耗時間百分比
  • 每週消費的冰淇淋公升數

​ 開啟txt文字檔案,資料格式如圖2.1所示。

這裡寫圖片描述

2.2.3 準備資料:從文字檔案中解析資料

於是我們要寫一個函式來將文字檔案裡的資訊轉化為矩陣資訊,話不多說動手!!

#得到檔案行數,建立矩陣,建立標籤向量
def file2matrix(filename):
    f = open(filename)
    arrayOfLines=f.readlines()
    numberOfLines = len(arrayOfLines)         #get the number of lines in the file
    returnMat = zeros((numberOfLines,3))        #prepare matrix to return
    classLabelVector = []                       #prepare labels return   
    index = 0
    for line in arrayOfLines:
        listFromLine = line.split('\t')
        returnMat[index,:] = listFromLine[0:3]
        classLabelVector.append(int(listFromLine[-1]))
        index += 1
    return returnMat,classLabelVector

這裡寫圖片描述
我們檢查一下資料內容,沒有問題。

2.2.4 歸一化特徵

如果我們不歸一化特徵那麼就會使結果偏向數值較大的特徵,顯然是不正確的。

樣本 玩遊戲所耗時間百分比 每年獲得的飛行常用里程數 每週消費的冰淇淋公升數 樣本分類
1 0.8 400 0.5 1
2 12 134000 0.9 3
3 0 20000 1.1 2
4 67 32000 0.1 2

​ 我們很容易發現,上面方程中數字差值最大的屬性對計算結果的影響最大,也就是說,每年獲取的飛行常客里程數對於計算結果的影響將遠遠大於表2.1中其他兩個特徵-玩視訊遊戲所耗時間佔比和每週消費冰淇淋公斤數的影響。而產生這種現象的唯一原因,僅僅是因為飛行常客里程數遠大於其他特徵值。但海倫認為這三種特徵是同等重要的,因此作為三個等權重的特徵之一,飛行常客里程數並不應該如此嚴重地影響到計算結果。

​ 在處理這種不同取值範圍的特徵值時,我們通常採用的方法是將數值歸一化,如將取值範圍處理為0到1或者-1到1之間。下面的公式可以將任意取值範圍的特徵值轉化為0到1區間內的值

歸一化特徵的公式如下:

newvalue=(oldvalueminvalue)/(maxvalueminvalue)

def autoNorm(dataSet):
    minValues=dataSet.min(axis=0)                       #按行求最小值
    maxValues=dataSet.max(axis=0)                       #按行求最大值
    ranges=maxValues-minValues
    normDataSet=(dataSet-minValues)/ranges              #python的廣播機制
    return normDataSet,ranges,minValues
dataMat,labels=file2matrix("datingTestSet2.txt")

這裡寫圖片描述

檢查一下,沒有問題,都在0-1之間。

2.2.5 分類器針對約會網站的測試程式碼

def datingClassTest():
    hoRatio=0.1                                         #留百分之10作為測試資料
    datingDataMat,datingLabels=file2matrix("datingTestSet2.txt")
    normMat,ranges,minValues=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("預測結果:{0},實際結果:{1}".format(classifierResult,datingLabels[i]))
        if(classifierResult!=datingLabels[i]):
            errorCount+=1.0
    print("正確率為:{0:f}".format(1.0-errorCount/float(numTestVecs)))

這裡寫圖片描述

正確率高達百分之95!

2.2.6 最後一步使用演算法

def classifyPerson():
    resultList=['不感興趣','有那麼一丟丟意思','很感興趣']
    percentTats=float(input("每天多少小時玩遊戲?"))              #如果是python2請使用raw_input
    ffMiles=float(input("每年飛多少公里數?"))
    iceCream=float(input("每年吃多少公升冰激凌?"))
    datingDataMat,datingLabels=file2matrix('datingTestSet2.txt')
    normMat,ranges,minValues=autoNorm(datingDataMat)
    inArr=array([percentTats,ffMiles,iceCream])
    classifierResult=classify0(inArr,datingDataMat,datingLabels,3)
    print("海倫對他:{}".format(resultList[classifierResult-1]))

這裡寫圖片描述

2.3 手寫體識別實戰

思考

機器學習六步走:

  1. 收集資料:下載
  2. 準備資料:編寫函式classify0()
  3. 分析資料:檢視是否有缺漏
  4. 訓練演算法:KNN不需要
  5. 測試演算法:提取部分作為測試樣本
  6. 使用演算法:有興趣有空可以嘗試

2.3.1 準備資料:將影象轉化為向量

下載資料

目錄trainingDigits中包含了大約2000個例子,每個例子如下圖所示,每個數字大約有200個樣本,目錄testDigits中包含大約900個測試資料。我們使用目錄trainingDigits中的資料訓練分類器,使用目錄testDigits中的資料測試分類器效果。

這裡寫圖片描述

首先我們要將影象格式化處理為一個向量。程式碼如下:

def img2vector(filename):
    """ 將影象轉為向量
        影象32*32轉為向量1*1024

        輸入檔名
        輸出向量 """
    returnMat=zeros((1,1024))
    f=open(filename)
    for i in range(32):
        line_str=f.readline()
        for j in range(32):
            returnMat[0,32*i+j]=int(line_str[i])
    return returnMat

這裡寫圖片描述
有了特徵向量後,我們就能夠進行預測啦!

類似之前的約會預測,話不多說直接上程式碼:

這裡寫圖片描述

正確率高達98.9%

2.4 總結

KNN演算法

給一個訓練資料集和一個新的例項,在訓練資料集中找出與這個新例項最近的k個訓練例項,然後統計最近的k個訓練例項中所屬類別計數最多的那個類,就是新例項的類

三要素:

  1. k值的選擇
  2. 距離的度量(常見的距離度量有歐式距離,馬氏距離等)
  3. 分類決策規則 (多數表決規則)

k值的選擇

  1. k值越小表明模型越複雜,更加容易過擬合
  2. 但是k值越大,模型越簡單,如果k=N的時候就表明無論什麼點都是訓練集中類別最多的那個類

所以一般k會取一個較小的值,然後用過交叉驗證來確定

這裡所謂的交叉驗證就是將樣本劃分一部分出來為預測樣本,比如95%訓練,5%預測,然後k分別取1,2,3,4,5之類的,進行預測,計算最後的分類誤差,選擇誤差最小的k

KNN的迴歸

在找到最近的k個例項之後,可以計算這k個例項的平均值作為預測值。或者還可以給這k個例項新增一個權重再求平均值,這個權重與度量距離成反比(越近權重越大)。

優缺點:

KNN演算法的優點:

  1. 思想簡單,理論成熟,既可以用來做分類也可以用來做迴歸;
  2. 可用於非線性分類;
  3. 訓練時間複雜度為O(n);
  4. 準確度高,對資料沒有假設,對outlier不敏感;

缺點:

  1. 計算量大;
  2. 樣本不平衡問題(即有些類別的樣本數量很多,而其它樣本的數量很少);
  3. 需要大量的記憶體;

有空再補充。。。