1. 程式人生 > >機器學習實戰筆記一:K-近鄰演算法在約會網站上的應用

機器學習實戰筆記一:K-近鄰演算法在約會網站上的應用

K-近鄰演算法概述

簡單的說,K-近鄰演算法採用不同特徵值之間的距離方法進行分類

 K-近鄰演算法

優點:精度高、對異常值不敏感、無資料輸入假定。

缺點:計算複雜度高、空間複雜度高。

適用範圍:數值型和標稱型。

 

k-近鄰演算法的一般流程

  1. 收集資料:可使用任何方法
  2. 準備資料:距離計算所需要的數值,最好是結構化的資料格式。
  3. 分析資料:可以使用任何方法。
  4. 訓練演算法:此步驟不適用於K-近鄰演算法
  5. 使用演算法:首先需要輸入樣本資料和節後話的輸出結果,然後執行k-近鄰演算法判定輸入資料分別屬於哪個分類,最後應用對計算出的分類執行後續的處理
#kNN分類器
def classify0(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0]  #得到資料總量
    diffMat = tile(inX,(dataSetSize,1)) - dataSet #將輸入資料擴充成與資料集同樣大小的矩陣並作差
    sqDiffMat = diffMat**2
    sqDistances = sqDiffMat.sum(axis=1) #axis = 1 引數是維度引數等於1在此處表示將一個矩陣的每一行向量相加
    distances = sqDistances** 0.5
    sortedDistancesIndicies 
= distances .argsort() #將列表值進行對比返回一個按照數值升序的下標值 classCount={} for i in range(k): voteIlabel = labels[sortedDistancesIndicies[i]] classCount[voteIlabel] = classCount.get(voteIlabel,0)+1 #dict.get("key") 返回value dict.get("key",default= None)如果能找到就返回對應的value找不到返回預設值 sortedClassCount = sorted(classCount.items(),key = operator.itemgetter(1),reverse=True)
#sorted 返回一個list operator.itemgetter(x,y)表示根據x+1維度的第y+1維度 return sortedClassCount[0][0]

 

K-近鄰演算法在約會網站改進的應用

在約會網站上使用K-近鄰演算法

(1)收集資料:提供文字檔案。

(2)準備資料:使用python解析文字檔案。

(3)分析資料:使用Matplotlib繪畫二維擴充套件圖。

(4)訓練演算法:此步驟不適用於K-近鄰演算法。

(5)測試演算法:使用提供的部分資料作為測試樣本。

         測試樣本和非測試樣本的區別在於:測試樣本是已經完成分類的資料,如果預測分類與實際不同,則標記為一個錯誤。

(6)使用方法:產生簡單的命令列程式,然後可以輸入一些特徵資料以判斷對方是否為自己喜歡的型別

 

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

提供的文字檔案datingTestSet2.txt中,每個樣本資料佔一行,總共有1000行。主要包括以下特徵:

1.每年獲得的飛行常客里程數

2.玩視訊遊戲所耗時間百分比

3.每週消費的冰淇淋公升數

將上述特徵資料輸入到分類器前,必須將待處理資料的格式改變為分類器可以接受的格式。建立名為file2matrix的函式,以此來處理格式問題。函式的輸入為檔名,輸出為訓練樣本和類標籤向量。

# Author:Thomas Wang
from numpy import *
def file2matrix(filename):
    with open(filename,'r') as fr:
        arrayOLines = fr.readlines()
        numberOfLines = len(arrayOLines)#儲存檔案資料數目
        returnMat = zeros((numberOfLines,3))#準備接收資料的numpy陣列
        classLabelVector = []#準備接收標籤向量的陣列python列表
        index = 0
        for line in arrayOLines:
            line = line.strip() #截去每行的回車
            listFromLine = line.split('\t')#以‘\t’為分隔符將字串擷取成字串陣列
            returnMat[index,:] = listFromLine[0:3]  #[index,:] = [index][:] 將字串陣列賦值給numpy陣列自動轉換為浮點型
            classLabelVector.append(int(listFromLine[-1]))
            index +=1
        return returnMat,classLabelVector

需要注意的是:

1、numpy陣列中[index,:] 與python 列表中[index][:] 作用相同

2、numpy函式庫可以自動解決變數值問題,而python並不可以必須明確告訴直譯器才可以處理

 

通過以下命令檢視資料內容

datingDataMat,datingLabels = file2matrix('datingTestSet2.txt')
print(datingDataMat)
print(datingLabels[0:20])
[[4.0920000e+04 8.3269760e+00 9.5395200e-01]
 [1.4488000e+04 7.1534690e+00 1.6739040e+00]
 [2.6052000e+04 1.4418710e+00 8.0512400e-01]
 ...
 [2.6575000e+04 1.0650102e+01 8.6662700e-01]
 [4.8111000e+04 9.1345280e+00 7.2804500e-01]
 [4.3757000e+04 7.8826010e+00 1.3324460e+00]]
[3, 2, 1, 1, 1, 1, 3, 3, 1, 3, 1, 1, 2, 1, 1, 1, 1, 1, 2, 3]

分析資料:使用Matplotlib建立散點圖

 我們藉助Matplotlib可以讓我們對我們得到的資料更加直接的展示在我們的面前(具體的Matplotlib可以根據需要進行了解學習)

import matplotlib
import  matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(datingDataMat[:,0],datingDataMat[:,2],15.0*array(datingLabels),15.0*array(datingLabels))
plt.show()

 

 

準備資料:歸一化數值

約會網站原始資料改進之後的樣本資料
  玩視訊遊戲所耗時間百分比 每年獲得的飛行常客里程數 每週消費的冰激凌公升數 樣本分類
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

根據上表中樣本3和樣本4的資料,計算樣本3和樣本4之間的距離:

\[\sqrt {{{(0 - 67)}^2} + {{(20000 - 32000)}^2} + {{(1.1 - 0.1)}^2}} \]

我們可以發現,上面方程中數字差值最大的屬性對計算結果的影響最大,其中飛行常客里程數對計算結果的影響將遠遠大於其他兩個特徵值。所以,作為三個等權重的特徵值之一,飛行常客里程數不應該

如此嚴重的影響計算結果。

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

newValue = (oldValue - min)/(max - min)


其中min和max是資料集中最小特徵值和最大特徵值。我們可以通過建構函式autoNorm()自動將數字特徵函式轉化到0到1之間。

def autoNorm(dataSet):
    minVals = dataSet.min(0) #Return the minimum along a given axis.
    maxVals = dataSet.max(0) #Return the maximum along a given axis.
    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

 

測試演算法:作為完整程式驗證分類器

使用錯誤率來檢測分類器的效能,對於分類器來說,錯誤率就是分類器給出錯誤結果的次數初一測試資料的總數數值處於0到1之間。程式碼中需要定義一個計數器變數,每次分類器錯誤的分類資料,計數器

就加1,程式執行完成之後計數器的結果初一資料點總數即是錯誤率。需要注意的是訓練資料和測試資料都是按比例隨機取得。

 
 
#完整程式驗證分類器
def datingClassTest():
hoRatio = 0.1 #測試資料比例
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt') #文字檔案轉換成可以處理的矩陣
normDataSet, ranges, minVals = autoNorm(datingDataMat) #將資料進行歸一化
m = normDataSet.shape[0] #獲取資料總量
numTestVecs = int(m * hoRatio) #獲取測試資料數量
errorCount = 0.0 #錯誤資料個數計數器
for i in range(numTestVecs):
#對每個測試資料利用KNN演算法進行分類
classifierResult = classify0(normDataSet[i,:],normDataSet[numTestVecs:m,:],datingLabels[numTestVecs:m],4)
#列印預測結果與實際結果
print("分類器結果:%s,真實結果:%s"%(classifierResult,datingLabels[i]))
#計算總錯誤個數並計算錯誤率
if (classifierResult != datingLabels[i]): errorCount+=1.0
print("the total error rate is: %f"%(errorCount/float(numTestVecs)))
datingClassTest()
分類器結果:3,真實結果:3
分類器結果:2,真實結果:2
分類器結果:1,真實結果:1
......
分類器結果:2,真實結果:2
分類器結果:2,真實結果:1
分類器結果:1,真實結果:1
the total error rate is: 0.040000

 我們可以改變datingClassTest內變數hoRatio和變數K的值,潔廁錯誤率是否會隨著變數的增加而增加。取決於分類演算法、資料集和程式設計,分類器的輸出結果可能會有很大的不同。

使用演算法:構建完整可用系統

資料已經在分類器上進行了測試,我們將給使用者一段小程式,通過使用者輸入的資訊。程式會給出符合使用者的預測值。

#完整可用系統
def classifyPerson():
    resultList = ['not at all','in small does','in large does']
    percetTats = float(input("玩視訊遊戲所耗時間百分比:"))
    ffMiles = float(input("每年獲取的飛行常客里程數:"))
    iceCream = float(input("每年消耗冰淇淋的公升數:"))
    datingDataMat,datingLabels = file2matrix("datingTestSet2.txt")
    normMat,ranges,minVals = autoNorm(datingDataMat)
    inArr = array([ffMiles,percetTats,iceCream])
    classifierResult = classify0((inArr-minVals)/ranges,normMat,datingLabels,4)
    print("You probably like this person:",resultList[classifierResult-1])

這樣一個簡單可用的針對約會網站的完整可用系統就完成了,程式碼理解並不是很困難。

總結

第一篇部落格內容不會很詳盡,希望大家留言指導改進。本篇部落格通過一個簡單的約會網站預測例項入手,論述了一個機器學習演算法實踐從無到有的過程:準備資料--->分析資料--->準備資料:歸一化數值--->

測試演算法--->使用演算法。