1. 程式人生 > >機器學習實戰(第二篇)-k-近鄰演算法開發手寫識別系統

機器學習實戰(第二篇)-k-近鄰演算法開發手寫識別系統

   上一篇文章中,我們學習了使用k近鄰演算法改進約會網站,實現了通過一些資料的輸入判斷人員屬於哪一個分類。但是上篇文章基於的資料都是我們能夠簡單理解的數字資訊,本篇文章我們在人不太容易看懂的資料上使用分類器。這篇文章中我們將一步步構造使用k-近鄰分類器的手寫識別系統。為了簡單起見,這裡構造的系統只能識別數字0到9。需要識別的數字已經使用圖形處理軟體,處理成具有相同色彩和大小:寬高是32畫素 X 32畫素的黑白影象。儘管採用文字格式儲存影象不能有效利用記憶體空間,但是為了方便理解,我們還是將影象轉換為文字格式。

    使用k-近鄰演算法的手寫識別系統:(1)收集資料:提供文字檔案;(2)準備資料:編寫函式classify0(),將圖形格式轉換為分類器使用的list格式;(3)分析資料:在Python命令提示符中檢查資料,確保它符合要求。(4)訓練演算法:此步驟不使用與k-近鄰演算法。(5)測試演算法:編寫函式使用提供的部分資料集作為測試樣本,測試樣本與非測試樣本的區別在於測試樣本是否已經完成分類的資料,如果預測分類和實際分類不相同,則標記為一個錯誤;(6)使用演算法:本例沒有完成此步驟,若你感興趣可以構建完整的應用程式,從影象中提取數字,並完成數字識別,美國的郵件分揀系統就是一個實際執行的類似系統。

1. 準備資料:將影象轉換為測試向量

    我們使用trainingDigits中的資料訓練分類器,使用目錄testDigits中的資料測試分類器的效果。兩組資料沒有覆蓋,你可以檢查下這些資料夾的檔案是否符合要求。

    為了使用上面所述的分類器,我們將把一個32X32的二進位制影象矩陣轉換為1X1024的向量,這樣前兩節使用的分類器就可以處理數字影象資訊。我們首先編寫一段函式img2vector,將影象轉換為向量:該函式建立1X1024的NumPy陣列,然後開啟給定的檔案,迴圈讀出檔案的前32行,並僅每行的頭32個字元值儲存在NumPy陣列中,最後返回陣列:

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
   將上述程式碼輸入到kNN.py檔案中,在Python命令列中輸入下列命令測試img2vector函式,然後與文字編輯器開啟的檔案進行比較:


二. 使用k-近鄰演算法識別手寫數字

    上面我們已經將資料處理成分類器可以識別的格式,本節我們將這些資料輸入到分類器,檢測分類器的執行效果。下面程式中包含的函式handwritingClassTest()是測試分類器的程式碼,將其寫入kNN.py中。在寫入這些程式碼之前,必須保證from os import listdir寫入檔案的起始部分,這段程式碼的主要功能是從os模組匯入函式listdir,它可以列出給定目錄的檔名。

def handwritingClassTest():
	hwLabels=[]
	trainingFileList=listdir('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('trainingDigits/%s'%fileNameStr)
	testFileList=listdir('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('testDigits/%s'%fileNameStr)
		classifierResult=classify0(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("\n the total number of errors is :%d" %(errorCount))
	print("\n the total error rate is :%f" %(errorCount/float(mTest)))	
  將trainingDigitis目錄中的檔案內容儲存在列表中,然後可以得到目錄中有多少檔案,並將其儲存在變數m中。接著,程式碼建立一個m行1024列的訓練矩陣,該矩陣的每行資料資料儲存一個影象。我們可以從檔名中解析出分類數字。該目錄下的檔案按照規則命名,如檔案9_45.txt的分類是9,它是數字9的第45個例項。然後我們可以將類程式碼儲存在hwLabels向量中,使用前面討論的img2vector函式載入影象。在下一步中,我們對testDigits目錄中的檔案執行相似的操作,不同之處在於我們並不是將這個目錄下的檔案載入矩陣中,而是使用classfy0()函式測試該目錄下的每個檔案。由於檔案中的值已經在0和1之間,本部分執行時不需要執行歸一化函式。

   執行結果如下:


k-近鄰演算法識別手寫數字資料集,錯誤率為1.2%。改變k的值、修改函式handwritingClassTest隨機選取訓練樣本、改變訓練樣本的數目,都會對k-近鄰演算法的錯誤率產生影響,感興趣的話可以改變這些值。

    實際使用這個演算法時,演算法的執行效率並不高。因為演算法需要為每個測試向量做2000次距離計算,每個距離計算包含1024個維度浮點計算,總計要執行900次。此外,我們還需要為測試向量準備2MB的儲存空間。是否存在一種演算法減少儲存空間和計算時間的開銷呢,k決策樹就是k-近鄰演算法的優化版,可以節省大量的計算開銷。

3. 總結

    k-近鄰演算法是分類資料最簡單有效的演算法。k-近鄰演算法時基於例項的學習,使用演算法時我們必須有接近實際資料的訓練樣本資料。k-近鄰演算法必須儲存全部資料集,如果訓練資料集很大,必須使用大量的儲存空間。此外,必須對資料集中的每個資料計算距離值,實際使用時可能非常耗時。

   k-近鄰演算法的另一個缺陷是它無法給出任何資料的基礎結構資訊,因此我們也無法知曉平均例項樣本和典型例項樣本具有什麼特徵。