使用K-近鄰算法構(gòu)建手寫識(shí)別系統(tǒng)

1 寫在前面

前段時(shí)間開(kāi)始看機(jī)器學(xué)習(xí)的相關(guān)知識(shí)谤祖,PLA? break point? dvc? 或是被一堆算法的數(shù)學(xué)公式推導(dǎo)搞得云里霧里涂炎,我開(kāi)始意識(shí)到如果只拘泥于理論部分的學(xué)習(xí)喉悴,我的興趣早晚會(huì)被消磨殆盡允乐,必須在學(xué)習(xí)理論知識(shí)的同時(shí)嘗試使用機(jī)器學(xué)習(xí)的算法解決一些“實(shí)際”問(wèn)題畴椰。一方面瓣喊,讓自己明白機(jī)器學(xué)習(xí)各個(gè)算法是如何解決實(shí)際問(wèn)題的;另一方面芽世,通過(guò)實(shí)際編碼加深對(duì)算法本身的理解挚赊。
今天記錄一個(gè)使用K-緊鄰算法(kNN)解決的問(wèn)題,可以說(shuō)KNN是最簡(jiǎn)單的機(jī)器學(xué)習(xí)算法之一济瓢。

2 kNN原理

kNN屬于監(jiān)督學(xué)習(xí)(supervised learning)的范疇荠割,因?yàn)閗NN的樣本數(shù)據(jù)集中的每個(gè)數(shù)據(jù)都存在標(biāo)簽(label),即我們知道樣本集中每個(gè)數(shù)據(jù)與所屬分類的對(duì)應(yīng)關(guān)系旺矾。輸入沒(méi)有l(wèi)abel的新數(shù)據(jù)后蔑鹦,將新數(shù)據(jù)的每個(gè)特征與樣本集中數(shù)據(jù)對(duì)應(yīng)的特征進(jìn)行比較,然后算法提取樣本集中與新數(shù)據(jù)特征最相似(最近鄰)的前k個(gè)樣本數(shù)據(jù)的label箕宙,最后選擇這k個(gè)最相似數(shù)據(jù)中出現(xiàn)次數(shù)最多的label嚎朽,作為新數(shù)據(jù)的label。

新數(shù)據(jù)與樣本數(shù)據(jù)是如何比較的呢柬帕?

通常輸入的一個(gè)“數(shù)據(jù)”是由多個(gè)特征數(shù)據(jù)(feature)組成的哟忍,我們把這個(gè)“數(shù)據(jù)”放到空間中,一個(gè)特征數(shù)據(jù)代表一個(gè)維度陷寝,受人腦的限制锅很,我們無(wú)法想象多維的情景。為了方便想象凤跑,假定每個(gè)“數(shù)據(jù)”只有兩個(gè)特征爆安,那么放到空間中就是我們最熟悉的二維平面,在這個(gè)平面中兩個(gè)特征可以確定一個(gè)點(diǎn)仔引,新數(shù)據(jù)與樣本數(shù)據(jù)之間的比較就是求兩點(diǎn)之間的“距離”扔仓,通常使用歐式距離,以二維為例:

二維空間中的歐式距離

計(jì)算新數(shù)據(jù)與每個(gè)樣本數(shù)據(jù)的距離咖耘,選取前k個(gè)距離最小的樣本數(shù)據(jù)当辐,取得這k個(gè)數(shù)據(jù)的label,最后選擇這k個(gè)label中出現(xiàn)次數(shù)最多的label作為新數(shù)據(jù)的label鲤看。

KNN算法的代碼實(shí)現(xiàn)

最開(kāi)始需要導(dǎo)入兩個(gè)模塊:第一個(gè)是科學(xué)計(jì)算包NumPy,第二個(gè)是運(yùn)算符模塊(operator)耍群。

from numpy import *
import operator

算法的核心代碼:

def k_classify (inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0]   # 獲取數(shù)據(jù)集第一維度
    diffMat = tile(inX, (dataSetSize, 1)) - dataSet
    sqDiffMat = diffMat ** 2
    sqDistance = sqDiffMat.sum(axis=1)
    distance = sqDistance ** 0.5    # 計(jì)算距離
    sortedDistIndicies = distance.argsort()   # 返回距離從小到大排列后數(shù)值原先的索引值(下標(biāo))
    classCount = {}
    for i in range(k):    # 選擇距離最小的k個(gè)點(diǎn)义桂,并統(tǒng)計(jì)label
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
    sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)  # 按label的個(gè)數(shù),對(duì)其降序排序
    return sortedClassCount[0][0]     # 返回個(gè)數(shù)最大的label

該算法函數(shù)有4個(gè)參數(shù)蹈垢,inX是要測(cè)試的新數(shù)據(jù)慷吊,dataSet是訓(xùn)練樣本集,labels是標(biāo)簽集曹抬,k通常取不大于20的整數(shù)溉瓶。

3 構(gòu)建手寫識(shí)別系統(tǒng)

收集數(shù)據(jù)

為了簡(jiǎn)單起見(jiàn),這里構(gòu)造的系統(tǒng)只能識(shí)別0到9的數(shù)字,需要識(shí)別的數(shù)字已經(jīng)使用圖形軟件處理成了寬和高是32x32像素的黑白圖像堰酿,并且將圖像轉(zhuǎn)換為了文本格式疾宏,如下圖1所示。


圖1:數(shù)字6

數(shù)據(jù)集有兩個(gè)触创,一個(gè)為trainningDigits文件中包含2000個(gè)例子坎藐,每個(gè)數(shù)字有200個(gè)樣本,部分樣本如圖2所示哼绑。另一個(gè)為testDigits文件中包含大約900個(gè)測(cè)試數(shù)據(jù)岩馍。

圖2:訓(xùn)練集部分樣本

說(shuō)明:文件名格式均為a_b.txt,其中a表示該文件中圖形的數(shù)字抖韩,之后將作為label使用蛀恩;b為該數(shù)字的序號(hào)(不重要),相同的數(shù)字之間形狀上均有不同的地方茂浮。

準(zhǔn)備數(shù)據(jù)

需要識(shí)別的數(shù)字的文本文件有了双谆,接下來(lái)需要將其轉(zhuǎn)化為算法函數(shù)可處理的數(shù)據(jù)類型。
這里將32x32的二進(jìn)制圖像矩陣轉(zhuǎn)換為1x1024的向量励稳,并將其存在1x1024的NumPy數(shù)組中佃乘。

def imgVector(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      # 返回1x1024的數(shù)組

測(cè)試與運(yùn)行算法

def handwritingClassTest():
    hwLabels = []
    trainingFileList = listdir('trainingDigits')      # 讀取訓(xùn)練集
    m = len(trainingFileList)
    trainingMat = zeros((m, 1024))
    for i in range(m):
        fileNameStr = trainingFileList[i]
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0])    # 獲取label
        hwLabels.append(classNumStr)      # 構(gòu)建標(biāo)簽集
        trainingMat[i,:] = imgVector('trainingDigits/%s' % fileNameStr) # 處理訓(xùn)練集數(shù)據(jù)
    errorCount = 0.0
    testFileList = listdir('testDigits')    #讀取測(cè)試集,進(jìn)行測(cè)試
    mTest = len(testFileList)
    for i in range(mTest):
        fileNameStr = testFileList[i]
        fileStr = fileNameStr.split('.')[0]
        classNumStr = int(fileStr.split('_')[0])
        vectUnderTest = imgVector('testDigits/%s' % fileNameStr)
        classifierResult = k_classify(vectUnderTest, trainingMat, hwLabels, 3)    # 使用kNN算法
        if (classifierResult != classNumStr):     # 將kNN輸出label與實(shí)際label對(duì)比
            errorCount += 1.0
    print "the total error number of error is: %d" % errorCount
    print "\nthe total error rate is: %f" % (errorCount/float(mTest)) # 輸出錯(cuò)誤率
運(yùn)行結(jié)果
圖3:運(yùn)行結(jié)果

通過(guò)使用測(cè)試集測(cè)試驹尼,在大約900個(gè)數(shù)字中有11個(gè)數(shù)字趣避,KNN算法識(shí)別錯(cuò)誤,正確率達(dá)98.8%新翎,可見(jiàn)kNN在這個(gè)數(shù)字識(shí)別問(wèn)題上的表現(xiàn)是不錯(cuò)的程帕。

收獲

通過(guò)實(shí)際編碼實(shí)現(xiàn)kNN算法,并將其運(yùn)用在一個(gè)實(shí)際例子中地啰,讓我對(duì)該算法的原理有了更深的理解愁拭。
第一次接觸到了科學(xué)計(jì)算包NumPy,學(xué)習(xí)到了這個(gè)庫(kù)中幾個(gè)重要方法亏吝,如:zeros() shape() tile() sum(axis=*) argsort() 以及使用了python中幾個(gè)方法岭埠,如:listdir() split() strip()
其實(shí),這個(gè)算法的執(zhí)行效率并不高蔚鸥。測(cè)試集有大約900個(gè)惜论,所以kNN算法要執(zhí)行900次,那每次的計(jì)算量多少呢止喷?由于訓(xùn)練集樣本有2000個(gè)馆类,因此就需要做2000次距離計(jì)算,每個(gè)距離計(jì)算還有1024個(gè)維度浮點(diǎn)運(yùn)算弹谁,可見(jiàn)計(jì)算量不小乾巧,在我電腦上大約運(yùn)行了十幾秒才出結(jié)果...那么有什么方法可以減少計(jì)算時(shí)間的開(kāi)銷呢句喜?這個(gè)之后再說(shuō)。
萬(wàn)事開(kāi)頭難...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末沟于,一起剝皮案震驚了整個(gè)濱河市咳胃,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌社裆,老刑警劉巖拙绊,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異泳秀,居然都是意外死亡标沪,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門嗜傅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)金句,“玉大人,你說(shuō)我怎么就攤上這事吕嘀∥ツ” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵偶房,是天一觀的道長(zhǎng)趁曼。 經(jīng)常有香客問(wèn)我,道長(zhǎng)棕洋,這世上最難降的妖魔是什么挡闰? 我笑而不...
    開(kāi)封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮掰盘,結(jié)果婚禮上摄悯,老公的妹妹穿的比我還像新娘。我一直安慰自己愧捕,他們只是感情好奢驯,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著次绘,像睡著了一般瘪阁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上邮偎,一...
    開(kāi)封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天罗洗,我揣著相機(jī)與錄音,去河邊找鬼钢猛。 笑死,一個(gè)胖子當(dāng)著我的面吹牛轩缤,可吹牛的內(nèi)容都是我干的命迈。 我是一名探鬼主播贩绕,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼壶愤!你這毒婦竟也來(lái)了淑倾?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤征椒,失蹤者是張志新(化名)和其女友劉穎娇哆,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體勃救,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡碍讨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蒙秒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勃黍。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖晕讲,靈堂內(nèi)的尸體忽然破棺而出覆获,到底是詐尸還是另有隱情,我是刑警寧澤瓢省,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布弄息,位于F島的核電站,受9級(jí)特大地震影響勤婚,放射性物質(zhì)發(fā)生泄漏摹量。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一蛔六、第九天 我趴在偏房一處隱蔽的房頂上張望荆永。 院中可真熱鬧,春花似錦国章、人聲如沸具钥。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)骂删。三九已至,卻和暖如春四啰,著一層夾襖步出監(jiān)牢的瞬間宁玫,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工柑晒, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留欧瘪,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓匙赞,卻偏偏與公主長(zhǎng)得像佛掖,于是被迫代替她去往敵國(guó)和親妖碉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容