1. 程式人生 > >機器學習實戰(第二篇)-k-近鄰演算法改進約會網站配對結果

機器學習實戰(第二篇)-k-近鄰演算法改進約會網站配對結果

    前面幾篇中,我們學習了機器學習演算法中k-近鄰演算法,本章我們將使用該演算法進行改進約會網站配對結果的工作。首先我們先進入背景介紹:

     我的朋友海倫一直使用線上約會網站尋找適合自己的約會物件。儘管約會網站會推薦不同的人選,但她沒有從中找到喜歡的人。經過一番總結,她發現曾交往過三種類型的人:

      不喜歡的人;

      魅力一般的人;

      極具魅力的人;

      儘管發現了上述規律,但海倫依然無法將約會網站推薦的匹配物件歸入恰當的分類。她覺得可以在週一到週五約會那些魅力一般的人。週末則更喜歡與那些極具魅力的人為伴。海倫希望我們分類軟體可以更好地幫助她匹配物件劃分到確切的分類中。此外海倫還收集了一些約會網站未曾記錄的資料資訊,她認為這些資料更有助於幫助匹配物件的分類。在約會網站上使用k-近鄰演算法:

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

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

    (3)分析資料:使用Matplotlib畫二維擴散圖;

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

    (5)測試演算法:使用海倫提供的部分資料作為測試樣本。測試樣本和非測試樣本的區別在於:測試樣本是已經完成分類的資料,如果預測分類與實際類別不同,則標記為一個錯誤;

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

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

    海倫收集約會資料已經有了一段時間,她把這些資料存放在文字檔案datingTestSet.txt中,每個樣本資料佔據一行,總共有1000行,海倫的樣本主要包含以下三種特徵:每年獲得的飛行常客里程數,玩視訊遊戲所耗時間百分比;每週消費的冰淇淋公斤數。在將上述特徵資料輸入到分類器之前,必須將待處理資料的格式改變為分類器可以接受的格式。在kNN.py中建立名為file2matrix的函式,以此來處理輸入格式問題。該函式的輸入為檔名字串,輸出為訓練樣本矩陣和類標籤向量。

    函式的程式碼如下:

def file2matrix(filename):
	fr=open(filename)
	arrayOfLines=fr.readlines()
	numberOfLines=len(arrayOfLines)
	returnMat=zeros((numberOfLines,3))
	classLabelVector=[]
	fr=open(filename)
	index=0
	for line in fr.readlines():
		line=line.strip()
		listFromLine=line.split('\t')
		returnMat[index,:]=listFromLine[0:3]
		classLabelVector.append(int(listFromLines[-1]))
		index+=1
	return returnMat,classLabelVector
   從上面的程式碼可以看出,Python處理文字文非常容易,首先我們需要知道文字檔案包含多少行。開啟檔案,得到檔案的行數。然後建立以零填充的矩陣NumPy(實際上,NumPy是一個二維陣列,這裡暫時不用考慮其用途)。為了簡化處理,我們將該矩陣的另一維度設定為固定值3,你可以按照自己的實際需求增加相應的程式碼以適應變化的輸入值。迴圈處理檔案中的每行資料,首先使用函式line.strip()擷取掉所有的回車字元,然後使用tab字元\t將上一步得到的整行資料分割成一個元素列表。接著我們選取前三個元素,將它們儲存到特徵矩陣中。Python語言可以使用索引值-1表示列表中的最後一列元素,利用這種負索引,我們可以很方便地將列表的最後一列儲存到想象classLabelVector中。需要注意的是,我們必須明確地通知直譯器,告訴它列表中儲存的元素值為整形,否則Python語言會將這些元素當做字串處理。以前我們必須自己處理這些變數型別值的問題,現在這些細節可以交給NumPy函式庫來處理。

    若修改的部分程式碼,需要重新載入Python模組時,需要先執行from importlib import reload後,再次執行reload(kNN),即可實現重新載入。執行檔案轉化為資料矩陣和標籤結果如下:


    現在已經從文字檔案中匯入了資料,並將其格式化為想要的格式。接著我們需要了解資料的真實含義。當然我們可以直接瀏覽文字檔案,但是這種方法非常不友好,一般來說,我們會採用圖形化的方式直觀地展示資料。下面急用Python工具來圖形化展會資料內容,以便識別一些資料模式。在實際計算過程中我們會將datingLabels轉換為數字格式的檔案。

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

    首先我們使用Matplotlib製作原始資料的散點圖,在Python命令列環境下,輸入下列命令:


   作圖結果如下,橫座標代表玩視訊遊戲所耗時間百分比,縱軸為協助消費的冰激凌公升數。


     由於沒有使用樣本分類的特徵值,我們很難從上圖中看到任何有用的資料模式資訊。一般來說,我們會採用色彩或其他的記號來標記不同樣本分類,以便更好地理解資料資訊。Matplotlib庫提供的scatter函式支援個性化標記散點圖上的點標記散點圖上的點。重新輸入上面的程式碼,呼叫scatter函式時使用下列引數,此時需要修改下file2matrix函式中的classLabelVector.append(int(listFromLine[-1])),需加上int:

ax.scatter(datingDataMat[:,1],datingDataMat[:,2],15.0*array(datingLabels),15.0*array(datingLabels))
plt.show()
執行後,圖形結果如下:



我們利用顏色尺寸標識了資料點的屬性類別,因而我們基本上可以從上圖上看到資料點所屬三個樣本分類。上圖使用的矩陣屬性列0個1展示資料,雖然可以區別,但是區分度不高。

    下面我們使用每年贏得的飛行常客里程數與玩視訊遊戲所佔百分比的約會資料散點圖,約會資料有三個特徵,通過下圖的展示的兩個特徵更容易區分資料點從屬的類別;


3. 準備資料:歸一化數值

     下面的表中提取了四組資料,如果想要計算樣本3和樣本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
在處理這種不同取值範圍的特徵值時,我們通常採用的方法時將數值歸一化,如將取值範圍處理為0-1或者-1到1之間。下面的公式可以將任意取值範圍的特徵值轉換到0到1區間內的取值, newValue=(oldValue-min)/(max-min)     其中,max和min分別代表資料集中的最大特徵值和最小特徵值。雖然改變數值取值範圍增加了分類器的複雜度,但為了得到準確結果,我們必須這樣做。我們需要在檔案kNN.py中增加了一個新函式autopNorm(),該函式可以將數字特徵值轉換為0-1區間。autoNorm()的程式碼如下:
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
  在函式autoNorm()中,我們將每列的最小值放在變數minVals中,將最大值放在變數maxVals中,其中dataSet.min(0)中的引數0使得函式可以從列中選取最小值,而不是選取當前行的最小值。然後,函式計算可能的取值範圍,並建立新的返回矩陣。正如前面給出的公式,為了歸一化特徵值,我們必須使用當前值減去最小值,然後除以取值範圍。需要注意的是,特徵矩陣有1000X3個值,而minVals和range的值都為1X3.為了解決這個問題,我們使用numpy庫中的tile()函式將變數內容複製成輸入矩陣同樣大小的矩陣,注意這是具體特徵值相除,而對於某些數值處理軟體包,/可能意味著矩陣除法,矩陣除法需要使用函式linalg.slove(matA,matB).    在Python命令提示符下,重新載入kNN.py,執行autoNorm函式,檢測函式的執行結果。

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

   上面我們已經按照需求做了處理,下面我們將測試分類器的結果,如果分類器正確率滿足要求,海倫就可以使用這個軟體來處理約會網站提供的約會名單了。機器學習演算法一個很重要的工作就是評估演算法的正確率,通常我們只提供已有資料的90%作為訓練樣本來訓練分類器,而使用其餘的10%資料去測試分類器,檢測分類器的正確率。後面我們會使用更高階的方法,當前我們還是採用最原始的方法。但是要注意的是,10%的測試資料應該是隨機選擇的,由於海倫提供的資料並沒有按照特定的資料排序,所以我們可以隨意選擇10%資料而不影響隨機性。     前面我們已經提到可以使用錯誤率來檢測分類器的效能。對於分類器來說,錯誤率就是分類器給出錯誤結果的次數除以測試資料的總數,完美分類器的錯誤率為0,而錯誤率為1.0的分類器不會給出任何正確的分類結果。程式碼裡我們定義一個計數器變數,每次分類器錯誤地分類資料,計數器就加1,程式執行完成之後計數器的結果除以資料點總數即是錯誤率。     為了測試分類器效果,在kNN.py檔案中建立函式datingClassTest,該函式是自包含的,你可以在任何時候在Python執行環境中使用該函式測試分類器效果,在kNN.py中輸入下面的程式碼:
def datingClassTest():
	hoRatio=0.10
	datingDataMat,datingLabels=file2matrix('datingTestSet2.txt')
	normMat,ranges,minVals=autoNorm(datingDataMat)
	m=normMat.shape[0]
	numTestVecs=int(m*hoRatio)
	errorCount=0.0
	for i in range(numTestVecs):
		classifieResult=classify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3)
		print ("the classifier came back with: %d, the real answer is: %d" %(classifieResult,datingLabels[i]))
		if(classifieResult!=datingLabels[i]):
			errorCount+=1
		print ("the total error rate is: %f" %(errorCount/float(numTestVecs)))
   函式datingClassTest首先使用了file2matrix和autoNorm函式從檔案中讀取資料並且將其轉換為歸一化特徵值。接著計算測試向量的數量,此步決定了normMat向量中哪些資料用於測試,哪些資料用於分類器的訓練樣本;然後將這兩部分資料輸入到原始kNN分類器函式classfy0.最後,函式計算錯誤率並輸出結果。注意此處我們使用原始分類器,這部分我們講解了如何處理資料,如何將資料改造為分類器可以使用的特徵值。得到可靠的資料同樣重要。     在Python中重新載入kNN模組,輸入kNN.datingClassTest(),執行分類器測試程式,我們將得到下面的輸出結果:


   可以看到分類器的錯誤率為0.05,這是一個不錯的結果。我們可以改變函式datingClassTest內變數hoRatio和變數k的值,檢測錯誤率是否隨著變數的變化而增加。依賴於分類演算法、資料集合程式設定,分類器的輸出結果可能有很大的不同。

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

    上面我們對分類器進行了測試,現在終於可以使用這個分類器為海倫來對人們進行分類。我們增加一小段程式,通過該程式海倫在約會網站上找到某個人並輸入他的資訊。程式會給出他對對方喜歡程度的預測值。     將下面的程式碼加入到kNN.py並重新載入kNN,執行過程和結果如下圖:
   到目前為止,我們已經看到如何在資料上構建分類器。這裡所有的資料讓人看起來都很容易,但是如何在人不大容易看懂的資料上使用分類器呢?下一篇文章中我們將學習如何在二進位制儲存的影象資料上使用kNN。