监督学习:KNN(K-近邻)算法实现手写数字识别的三种方法
沒人會(huì)看的開場(chǎng)白:本來(lái)覺得自己從數(shù)據(jù)建模轉(zhuǎn)人工智能方向應(yīng)該問(wèn)題不大(自我感覺自己算法學(xué)的不錯(cuò))。結(jié)果一個(gè)K-鄰近實(shí)現(xiàn)手寫數(shù)字識(shí)別的代碼就讓我改了三四天。雖然網(wǎng)上這方面的代碼是很多,但是我運(yùn)行了好幾個(gè),結(jié)果都不是很理想。一次偶然的念想——為什么我不把這些代碼的優(yōu)點(diǎn)結(jié)合在一起呢,于是說(shuō)做就做,年輕人嘛,反正有時(shí)間燥起來(lái),再加上自己準(zhǔn)備的畢業(yè)論文也是這個(gè),動(dòng)動(dòng)手總有益處,于是就拙筆于此,有更好的建議與意見,歡迎指正。?
開篇說(shuō)明:內(nèi)容很多,沉不下心,想速成的朋友請(qǐng)繞行,學(xué)習(xí)新知識(shí)嘛,坑要一個(gè)一個(gè)填實(shí)。打碎的骨頭才更牢靠吧!
本篇所有的源碼資源都已上傳:https://download.csdn.net/download/zzz_cming/10377414?
–—-—-—-—-—-—-—-—-—-—-—-—–—-—-—-—-—-—-—-——-—-—-—-—-—-—-—-—-—-—-—-—-—-——-?
什么是K-近鄰算法
百度百科上的定義:K-近鄰(k-Nearest Neighbor,KNN)是分類算法,是一個(gè)理論上比較成熟的方法,也是最簡(jiǎn)單的機(jī)器學(xué)習(xí)算法之一。該方法的思路是:如果一個(gè)樣本在特征空間中的k個(gè)最相似(即特征空間中最鄰近)的樣本中的大多數(shù)屬于某一個(gè)類別,則該樣本也屬于這個(gè)類別。
K-近鄰算法怎么應(yīng)用于數(shù)字識(shí)別實(shí)現(xiàn)過(guò)程直觀點(diǎn)說(shuō):根據(jù)測(cè)試數(shù)據(jù)與每個(gè)訓(xùn)練集數(shù)據(jù)距離的大小來(lái)判斷該測(cè)試數(shù)據(jù)分屬哪個(gè)類別——我們有一堆已經(jīng)注明好它是哪個(gè)數(shù)字的圖片(這一堆圖片組成訓(xùn)練集,也叫比較集、樣本空間)。現(xiàn)在有一個(gè)測(cè)試數(shù)據(jù)“6”來(lái)了,我們要識(shí)別這個(gè)“6”的步驟就是:
(說(shuō)明:左圖是我用Windows自帶的畫圖工具寫的一個(gè)“6”,圖片大小是28*28。右圖是經(jīng)過(guò)我切割、拉伸轉(zhuǎn)化后的0-1矩陣圖)?
??
1. 將測(cè)試數(shù)據(jù)由圖片形式轉(zhuǎn)換成只有一列的0-1矩陣形式:上左圖中有像素點(diǎn)的位置記為1,沒有像素點(diǎn)的位置記為0,成上右圖(上右圖是經(jīng)過(guò)我切割、拉伸后的結(jié)果)。再將上右圖中所有后一行數(shù)字接到前一行末尾,形成一行,最后轉(zhuǎn)置即可得一列0-1矩陣形式?
2. 將所有(L個(gè))訓(xùn)練數(shù)據(jù)也都用上方法從圖片形式轉(zhuǎn)換成只有一列的0-1矩陣形式?
3. 把L個(gè)單列數(shù)據(jù)存入新矩陣A中——矩陣A每一列存儲(chǔ)一個(gè)圖片的所有信息?
4. 用測(cè)試數(shù)據(jù)與矩陣A中的每一列求距離,求得的L個(gè)距離存入距離數(shù)組中(距離 = 對(duì)應(yīng)位差值的平方和再求平方根)?
5. 從距離數(shù)組中取出最小的K個(gè)距離所對(duì)應(yīng)的訓(xùn)練集的索引?
6. 擁有最多索引的值就是預(yù)測(cè)值(有多個(gè)眾數(shù)時(shí),按距離和最小)
三個(gè)KNN實(shí)現(xiàn)數(shù)字識(shí)別的方法(說(shuō)明:這是自己在網(wǎng)上看到的三個(gè)大神寫的比較好的代碼,前兩個(gè)都能實(shí)現(xiàn)要求,后一個(gè)是預(yù)處理方法。我也只是站在大神的肩膀上做一點(diǎn)點(diǎn)修改,原理還是他們教的我,在此向他們表示致敬)
第一個(gè):圖片大小28*28,手寫數(shù)字圖片識(shí)別
來(lái)源說(shuō)明:騰訊課堂米度教育《AI人工智能 機(jī)器學(xué)習(xí) 深度學(xué)習(xí)VIP實(shí)戰(zhàn)就業(yè)班(人臉識(shí)別 無(wú)人駕駛)》第一節(jié)課。
獻(xiàn)丑貼出自己的代碼:?
優(yōu)化說(shuō)明如下:(小修改未說(shuō)明)
Pierre老師的代碼中K值是等于1,即只選取與訓(xùn)練集數(shù)據(jù)最近的那個(gè)類。優(yōu)化后成為可變的K,可自由選擇最近的K個(gè)值進(jìn)行比較
# -*- coding:utf-8 -*-
# -*- author:zzZ_CMing
# -*- 2017/12/25
# -*- python3.5
?
import numpy as np
from image import image2onebit as it
import sys
from tensorflow.examples.tutorials.mnist import input_data
import math
import datetime
?
#KNN算法主體:計(jì)算測(cè)試樣本與每一個(gè)訓(xùn)練樣本的距離
def get_index(train_data,test_data, i):
??? #1、 np.argmin(np.sqrt(np.sum(np.square(test_data[i]-train_data),axis=1)))
??? #2、a數(shù)組存入:測(cè)試樣本與每一個(gè)訓(xùn)練樣本的距離
??? all_dist = np.sqrt(np.sum(np.square(test_data[i]-train_data),axis=1)).tolist()
??? return all_dist
?
#KNN算法主體:計(jì)算查找最近的K個(gè)訓(xùn)練集所對(duì)應(yīng)的預(yù)測(cè)值
def get_number(all_dist):
??? all_number = []
??? min_index = 0
??? #print('距離列表:', all_dist,)
??? for k in range(Nearest_Neighbor_number):
??????? # 最小索引值 = 最小距離的下標(biāo)編號(hào)
??????? min_index = np.argmin(all_dist)
??????? #依據(jù)最小索引值(最小距離的下標(biāo)編號(hào)),映射查找到預(yù)測(cè)值
??????? ss = np.argmax((train_label[min_index])).tolist()
??????? print('第',k+1,'次預(yù)測(cè)值:',ss)
??????? #將預(yù)測(cè)值改為字符串形式存入新元組bb中
??????? all_number = all_number + list(str(ss))
??????? #在距離數(shù)組中,將最小的距離值刪去
??????? min_number = min(all_dist)
??????? xx = all_dist.index(min_number)
??????? del all_dist[xx]
??? print('預(yù)測(cè)值總體結(jié)果:',all_number)
??? return all_number
?
#KNN算法主體:在K個(gè)預(yù)測(cè)值中,求眾數(shù),找到分屬最多的那一類,輸出
def get_min_number(all_number):
??? c = []
??? #將string轉(zhuǎn)化為int,傳入新列表c
??? for i in range(len(all_number)):
??????? c.append(int(all_number[i]))
??? #求眾數(shù)
??? new_number = np.array(c)
??? counts = np.bincount(new_number)
??? return np.argmax(counts)
?
t1 = datetime.datetime.now()????? #計(jì)時(shí)開始
print('說(shuō)明:訓(xùn)練集數(shù)目取值范圍在[0,60000],K取值最好<10\n' )
train_sum = int(input('輸入訓(xùn)練集數(shù)目:'))
Nearest_Neighbor_number = int(input('選取最鄰近的K個(gè)值,K='))
?
#依照文件名查找,讀取訓(xùn)練與測(cè)試用的圖片數(shù)據(jù)集
mnist = input_data.read_data_sets("./MNIST_data", one_hot=True)
#取出訓(xùn)練集數(shù)據(jù)、訓(xùn)練集標(biāo)簽
train_data, train_label = mnist.train.next_batch(train_sum)
?
#調(diào)用自創(chuàng)模塊內(nèi)函數(shù)read_image():依照路徑傳入圖片處理,將圖片信息轉(zhuǎn)換成numpy.array類型
x1_tmp = it.read_image("png/55.png")
test_data = it.imageToArray(x1_tmp)
test_data = np.array(test_data)
#print('test_data',test_data)
#調(diào)用自創(chuàng)模塊內(nèi)函數(shù)show_ndarray():用字符矩陣打印圖片
it.show_ndarray(test_data)
?
#KNN算法主體
all_dist = get_index(train_data,test_data,0)
all_number = get_number(all_dist)
min_number = get_min_number(all_number )
print('最后的預(yù)測(cè)值為:',min_number)
?
t2=datetime.datetime.now()
print('耗 時(shí) = ',t2-t1)
評(píng)價(jià):使用的訓(xùn)練集、測(cè)試集數(shù)據(jù)來(lái)源于Google的那個(gè)經(jīng)典的壓縮包。程序限制圖片數(shù)據(jù)大小是28*28的,也就是說(shuō)像素點(diǎn)一共784個(gè),所以缺陷在于(應(yīng)該說(shuō)是KNN算法缺陷硬傷)
大多數(shù)數(shù)據(jù)圖片占據(jù)的像素點(diǎn)很接近,距離區(qū)分度比較低;
未考慮不同數(shù)字間的內(nèi)部結(jié)構(gòu)特征
改善的方法有:
將圖片尺寸擴(kuò)大,但這樣又會(huì)增加內(nèi)存,使計(jì)算時(shí)間變長(zhǎng)
規(guī)范的書寫測(cè)試數(shù)據(jù)
增大訓(xùn)練數(shù)據(jù)集有效空間大小
這是我學(xué)習(xí)KNN算法做數(shù)字識(shí)別的啟蒙物,再次感謝老師的幫助與指導(dǎo)
第二個(gè):32*32,0-1字符矩陣的數(shù)字識(shí)別
來(lái)源說(shuō)明:算法實(shí)現(xiàn)功能的大體步驟是不會(huì)變的,只是實(shí)現(xiàn)的方法各有不同。網(wǎng)上又看到的《Python 手寫數(shù)字識(shí)別-knn算法應(yīng)用》。雖然這位前輩一運(yùn)用的是0-1字符矩陣,與之前Pierre老師的圖片有不同,但是前輩一的代碼更細(xì)致的展示出KNN算法實(shí)現(xiàn)的過(guò)程。源碼鏈接地址:https://www.cnblogs.com/chenbjin/p/3869745.html
獻(xiàn)丑再貼出自己的代碼:?
說(shuō)明如下:大的優(yōu)化沒有,由于我有自己習(xí)慣的名稱定義,修改了源代碼中大部分的名稱,小的修改也未注明?
注意:代碼中需要的訓(xùn)練集、測(cè)試集數(shù)據(jù)均來(lái)源于上鏈接里,為了讓大家更清楚下面代碼的原理,強(qiáng)烈建議看完上面前輩的作品。
# -*- coding:utf-8 -*-
# -*- author:zzZ_CMing
# -*- 2017/12/28
# -*- python3.5
?
from os import listdir
from numpy import *
import numpy as np
import operator
import datetime
?
def KNN(test_data,train_data,train_label,k):
??? #已知分類的數(shù)據(jù)集(訓(xùn)練集)的行數(shù)
??? dataSetSize = train_data.shape[0]
??? #求所有距離:先tile函數(shù)將輸入點(diǎn)拓展成與訓(xùn)練集相同維數(shù)的矩陣,計(jì)算測(cè)試樣本與每一個(gè)訓(xùn)練樣本的距離
??? all_distances = np.sqrt(np.sum(np.square(tile(test_data,(dataSetSize,1))-train_data),axis=1))
??? #print("所有距離:",all_distances)
??? #按all_distances中元素進(jìn)行升序排序后得到其對(duì)應(yīng)索引的列表
??? sort_distance_index = all_distances.argsort()
??? #print("文件索引排序:",sort_distance_index)
??? #選擇距離最小的k個(gè)點(diǎn)
??? classCount = {}
??? for i in range(k):
??????? #返回最小距離的訓(xùn)練集的索引(預(yù)測(cè)值)
??????? voteIlabel = train_label[sort_distance_index[i]]
??????? #print('第',i+1,'次預(yù)測(cè)值',voteIlabel)
??????? classCount[voteIlabel] = classCount.get(voteIlabel,0)+1
??? #求眾數(shù):按classCount字典的第2個(gè)元素(即類別出現(xiàn)的次數(shù))從大到小排序
??? sortedClassCount = sorted(classCount.items(), key = operator.itemgetter(1), reverse = True)
??? return sortedClassCount[0][0]
?
#文本向量化 32x32 -> 1x1024
def img2vector(filename):
??? returnVect = []
??? fr = open(filename)
??? for i in range(32):
??????? lineStr = fr.readline()
??????? for j in range(32):
??????????? returnVect.append(int(lineStr[j]))
??? return returnVect
?
#從文件名中解析分類數(shù)字
def classnumCut(fileName):
??? #參考文件名格式為:0_3.txt
??? fileStr = fileName.split('.')[0]
??? classNumStr = int(fileStr.split('_')[0])
??? return classNumStr
?
#構(gòu)建訓(xùn)練集數(shù)據(jù)向量,及對(duì)應(yīng)分類標(biāo)簽向量
def trainingDataSet():
??? train_label = []
??? trainingFileList = listdir('trainingDigits')
??? m = len(trainingFileList)
??? train_data = zeros((m,1024))
??? #獲取訓(xùn)練集的標(biāo)簽
??? for i in range(m):
??????? # fileNameStr:所有訓(xùn)練集文件名
??????? fileNameStr = trainingFileList[i]
??????? # 得到訓(xùn)練集索引
??????? train_label.append(classnumCut(fileNameStr))
??????? train_data[i,:] = img2vector('trainingDigits/%s' % fileNameStr)
??? return train_label,train_data
?
#測(cè)試函數(shù)
def main():
??? t1 = datetime.datetime.now()? # 計(jì)時(shí)開始
??? Nearest_Neighbor_number = int(input('選取最鄰近的K個(gè)值,K='))
??? train_label,train_data = trainingDataSet()
??? testFileList = listdir('testDigits')
??? error_sum = 0
??? test_number = len(testFileList)
??? for i in range(test_number):
??????? #測(cè)試集文件名
??????? fileNameStr = testFileList[i]
??????? #切片后得到測(cè)試集索引
??????? classNumStr = classnumCut(fileNameStr)
??????? test_data = img2vector('testDigits/%s' % fileNameStr)
??????? #調(diào)用knn算法進(jìn)行測(cè)試
??????? classifierResult = KNN(test_data, train_data, train_label, Nearest_Neighbor_number)
??????? print ("第",i+1,"組:","預(yù)測(cè)值:",classifierResult,"真實(shí)值:",classNumStr)
??????? if (classifierResult != classNumStr):
??????????? error_sum += 1.0
??? print ("\n測(cè)試集總數(shù)為:",test_number)
??? print ("測(cè)試出錯(cuò)總數(shù):",error_sum)
??? print ("\n錯(cuò)誤率:",error_sum/float(test_number)*100,'%')
??? t2 = datetime.datetime.now()
??? print('耗 時(shí) = ', t2 - t1)
?
if __name__ == "__main__":
??? main()
評(píng)價(jià):這位前輩一所使用的訓(xùn)練集、測(cè)試集數(shù)據(jù)雖然是0-1矩陣,但是是可以通過(guò)代碼生成打印出來(lái)。除此之外,前輩一代碼的識(shí)別錯(cuò)誤率比較低,測(cè)試時(shí)候946個(gè)測(cè)試數(shù)據(jù)只出錯(cuò)11個(gè),出錯(cuò)率是1.16%,也就是成功率達(dá)98.84%。有圖有真相:?
大神一的方法也讓我檢驗(yàn)了最合適的K值選定是3(大家自己動(dòng)手試試,選取不同的K值,就能得到不同的錯(cuò)誤率)
于是用大神一的方法,再結(jié)合米度教育Pierre老師的代碼,寫了第一份自己的手寫數(shù)字識(shí)別的代碼(這里就沒有貼出了)。問(wèn)題總是在實(shí)際實(shí)現(xiàn)的過(guò)程中被發(fā)現(xiàn)——每個(gè)人用畫板寫出來(lái)的數(shù)字各不相同,形狀有大有小,筆畫有粗有細(xì),就連同一個(gè)數(shù)字的結(jié)構(gòu)比例都千差萬(wàn)別。這對(duì)識(shí)別的成功率影響很大。恰當(dāng)這時(shí),無(wú)意中發(fā)現(xiàn)了大神二的方法——統(tǒng)一不同人寫出來(lái)的數(shù)字,也就是添加圖片預(yù)處理,加入切割、拉伸函數(shù)。
第三個(gè):圖片預(yù)處理——切割、拉伸函數(shù)
大神二的原貼鏈接地址附上:http://m.blog.csdn.net/Hanpu_Liang/article/details/78237913?
大神二的思路如下:
將讀取的圖片先轉(zhuǎn)換成0-1矩陣形式
再根據(jù)灰度閾值,計(jì)算有效圖片的邊界索引,切割返回有效圖片的索引尺寸
切割后的有效圖片尺寸各不相同,運(yùn)用拉伸函數(shù)將各不相同的有效圖片轉(zhuǎn)換成尺寸相同的有效圖片
最后用轉(zhuǎn)化后的同尺寸的訓(xùn)練集、測(cè)試集數(shù)據(jù)求距離,做預(yù)測(cè)
貼出自己的代碼:注意圖片存放的路徑,標(biāo)準(zhǔn)大小N的值
# -*- coding:utf-8 -*-
# -*- author:zzZ_CMing
# -*- 2017/12/29
# -*- python3.5
?
from skimage import io
import numpy as np
import os
?
#Standard size 標(biāo)準(zhǔn)大小
N = 100
#灰度閾值
color = 100/255
?
#讀取訓(xùn)練圖片并保存
def GetTrainPicture(files):
??? Picture = np.zeros([len(files), N**2])
??? #enumerate函數(shù)用于遍歷序列中的元素以及它們的下標(biāo)(i是下標(biāo),item是元素信息)
??? for i, item in enumerate(files):
??????? #讀取這個(gè)圖片并轉(zhuǎn)為灰度值(黑死字體為0,白底為255)
??????? img = io.imread('./png1/'+item, as_grey = True)
??????? #清除噪音
??????? img[img>color] = 1
??????? #將圖片進(jìn)行切割,得到有手寫數(shù)字的的圖像
??????? img = CutPicture(img)
??????? #將圖片進(jìn)行拉伸,得到標(biāo)準(zhǔn)大小100x100
??????? img = StretchPicture(img).reshape(N**2)
??????? #將圖片存入矩陣
??????? Picture[i, 0:N**2] = img
??????? #將圖片的名字存入矩陣(需要存入名字,上面語(yǔ)句改Picture = np.zeros([len(files), N**2+1]))
??????? #Picture[i, N**2] = float(item[0])
??? return Picture
?
#切割圖象
def CutPicture(img):
??? #初始化新大小
??? size = []
??? #圖片的行數(shù)
??? length = len(img)
??? #圖片的列數(shù)
??? width = len(img[0,:])
??? #計(jì)算新大小
??? size.append(JudgeEdge(img, length, 0, [-1, -1]))
??? size.append(JudgeEdge(img, width, 1, [-1, -1]))
??? size = np.array(size).reshape(4)
??? #print('圖像尺寸(高低左右):',size)
??? return img[size[0]:size[1]+1, size[2]:size[3]+1]
?
def JudgeEdge(img, length, flag, size):
??? for i in range(length):
??????? #判斷是行是列
??????? if flag == 0:
??????????? #正序判斷該行是否有手寫數(shù)字
??????????? line1 = img[img[i,:]<color]
??????????? #倒序判斷該行是否有手寫數(shù)字
??????????? line2 = img[img[length-1-i,:]<color]
??????? else:
??????????? line1 = img[img[:,i]<color]
??????????? line2 = img[img[:,length-1-i]<color]
??????? #若有手寫數(shù)字,即到達(dá)邊界,記錄下行
??????? if len(line1)>=1 and size[0]==-1:
??????????? size[0] = i
??????? if len(line2)>=1 and size[1]==-1:
??????????? size[1] = length-1-i
??????? #若上下邊界都得到,則跳出
??????? if size[0]!=-1 and size[1]!=-1:
??????????? break
??? return size
?
#拉伸圖像
def StretchPicture(img):
??? newImg = np.ones(N**2).reshape(N, N)
??? newImg1 = np.ones(N ** 2).reshape(N, N)
??? #對(duì)每一行/列進(jìn)行拉伸/壓縮
??? #每一行拉伸/壓縮的步長(zhǎng)
??? step1 = len(img[0])/N
??? #每一列拉伸/壓縮的步長(zhǎng)
??? step2 = len(img)/N
??? #對(duì)每一行進(jìn)行操作
??? for i in range(len(img)):
??????? for j in range(N):
??????????? newImg[i, j] = img[i, int(np.floor(j*step1))]
??? #對(duì)每一列進(jìn)行操作
??? for i in range(N):
??????? for j in range(N):
??????????? newImg1[j, i] = newImg[int(np.floor(j*step2)), i]
??? return newImg1
?
#用字符矩陣打印圖片
def show_ndarray(pic):
??? for i in range(N**2):
??????? if(pic[0,i] == 0):
??????????? print ("*",end='')
??????? else:
??????????? print ("0",end='')
??????? if (i+1)%N == 0 :
??????????? print()
?
#得到在num目錄下所有文件的名稱組成的列表
filenames = os.listdir(r"png1")
#得到所有訓(xùn)練圖像向量的矩陣
pic = GetTrainPicture(filenames)
#print('圖像向量的矩陣',pic)
#調(diào)用show_ndarray()函數(shù):用字符矩陣打印圖片
show_ndarray(pic)
N*N,手寫數(shù)字識(shí)別(DIY版)
先來(lái)幾點(diǎn)說(shuō)明:
處理的圖片大小要小于設(shè)定的N
測(cè)試集圖片名稱的首字母要是真實(shí)值
當(dāng)前訓(xùn)練集庫(kù)所包含的樣本比較少,需多添加
代碼如下:(具體的注釋隨代碼附上)
# -*- coding:utf-8 -*-
# -*- author:zzZ_CMing
# -*- 2017/12/30
# -*- python3.5
?
import operator
import datetime
import numpy as np
from numpy import *
from os import listdir
from skimage import io
?
print('程序處理的圖片大小,建議不要超過(guò)200*200\n')
N = int(input('需要處理的圖片的大小(100至200),N='))
#N = 120??????????? # 圖片大小:N*N
color = 100 / 255???? # 灰度閾值
?
#KNN算法主體
def KNN(test_data,train_data,train_label,k):
??? #已知分類的數(shù)據(jù)集(訓(xùn)練集)的行數(shù)
??? dataSetSize = train_data.shape[0]
??? #求所有距離:tile函數(shù)將輸入點(diǎn)拓展成與訓(xùn)練集相同維數(shù)的矩陣,并計(jì)算測(cè)試樣本與每一個(gè)訓(xùn)練樣本的距離
??? all_distances = np.sqrt(np.sum(np.square(tile(test_data,(dataSetSize,1))-train_data),axis=1))
??? #按all_distances中元素進(jìn)行升序排序后得到其對(duì)應(yīng)索引的列表
??? sort_distance_index = all_distances.argsort()
??? #選擇距離最小的k個(gè)點(diǎn)
??? all_predictive_value = {}
??? for i in range(k):
??????? #返回最小距離的訓(xùn)練集的索引(預(yù)測(cè)值)
??????? predictive_value = train_label[sort_distance_index[i]]
??????? print('第',i+1,'次預(yù)測(cè)值',predictive_value)
??????? all_predictive_value[predictive_value] = all_predictive_value.get(predictive_value,0)+1
??? #求眾數(shù):按classCount字典的第2個(gè)元素(即類別出現(xiàn)的次數(shù))從大到小排序
? ??sorted_class_count = sorted(all_predictive_value.items(), key = operator.itemgetter(1), reverse = True)
??? return sorted_class_count[0][0]
?
#訓(xùn)練集:得到訓(xùn)練集數(shù)據(jù)矩陣、下標(biāo)簽索引
def get_all_train_data():
??? train_label = []
??? train_file_list = listdir('trainlist')? #獲取目錄內(nèi)容
??? m = len(train_file_list)?????????????????????? #m維向量的訓(xùn)練集
??? #get_train_data函數(shù):得到所有訓(xùn)練集圖像的向量矩陣
??? train_data = get_all_data(train_file_list,1)
??? for i in range(m):
??????? file_name = train_file_list[i]??????? #fileNameStr:所有訓(xùn)練集文件名
??????? train_label.append(get_number_cut(file_name))??? #得到訓(xùn)練集下標(biāo)
??? return train_label,train_data
?
#得到所有訓(xùn)練集/測(cè)試集的向量矩陣(k=1訓(xùn)練集傳入;k=0測(cè)試集傳入)
def get_all_data(file_list,k):
??? train_data = np.zeros([len(file_list), N**2])
??? #enumerate函數(shù)用于遍歷序列中的元素以及它們的下標(biāo)(i是下標(biāo),item是元素信息)
??? for i, item in enumerate(file_list):
??????? if k == 1:
??????????? #訓(xùn)練集:讀取圖片并轉(zhuǎn)為灰度值(黑字體為0,白底為255)
??????????? img = io.imread('./trainlist/'+ item, as_grey = True)
??????? else:
??????????? #測(cè)試集:讀取圖片并轉(zhuǎn)為灰度值(黑字體為0,白底為255)
??????????? img = io.imread('./testlist/' + item, as_grey = True)
??????? #降噪處理
??????? img[img>color] = 1
??????? #將圖片進(jìn)行切割,保留有值的部分
??????? img = get_cut_picture(img)
??????? #將圖片進(jìn)行拉伸,得到需求大小:N*N
??????? img = get_stretch_picture(img).reshape(N**2)
??????? #將處理后的圖片信息存入矩陣
??????? train_data[i, 0:N**2] = img
??????? #若將圖片的真實(shí)值存入矩陣(需要存入圖片索引,上面語(yǔ)句改train_data = np.zeros([len(file_list), N**2+1])
??????? #train_data[i, N**2] = float(item[0])
??? return train_data
?
#切割圖象
def get_cut_picture(img):
??? #初始化新大小
??? size = []
??? #圖片的行數(shù)
??? length = len(img)
??? #圖片的列數(shù)
??? width = len(img[0,:])
??? #計(jì)算新大小
??? size.append(get_edge(img, length, 0, [-1, -1]))
??? size.append(get_edge(img, width, 1, [-1, -1]))
??? size = np.array(size).reshape(4)
??? #print('圖像尺寸(高低左右):',size)
??? return img[size[0]:size[1]+1, size[2]:size[3]+1]
?
#獲取切割邊緣(高低左右的索引)
def get_edge(img, length, flag, size):
??? for i in range(length):
??????? #判斷是行是列
??????? if flag == 0:
??????????? #正序判斷該行是否有手寫數(shù)字
??????????? line1 = img[img[i,:]<color]
??????????? #倒序判斷該行是否有手寫數(shù)字
??????????? line2 = img[img[length-1-i,:]<color]
??????? else:
??????????? line1 = img[img[:,i]<color]
??????????? line2 = img[img[:,length-1-i]<color]
??????? #若有手寫數(shù)字,即到達(dá)邊界,記錄下行
??????? if len(line1)>=1 and size[0]==-1:
??????????? size[0] = i
??????? if len(line2)>=1 and size[1]==-1:
??????????? size[1] = length-1-i
??????? #若上下邊界都得到,則跳出
??????? if size[0]!=-1 and size[1]!=-1:
??????????? break
??? return size
?
#拉伸圖像
def get_stretch_picture(img):
??? newImg = np.ones(N**2).reshape(N, N)
??? newImg1 = np.ones(N ** 2).reshape(N, N)
??? #對(duì)每一行/列進(jìn)行拉伸/壓縮
??? #每一行拉伸/壓縮的步長(zhǎng)
??? step1 = len(img[0])/N
??? #每一列拉伸/壓縮的步長(zhǎng)
??? step2 = len(img)/N
??? #對(duì)每一行進(jìn)行操作
??? for i in range(len(img)):
??????? for j in range(N):
??????????? newImg[i, j] = img[i, int(np.floor(j*step1))]
??? #對(duì)每一列進(jìn)行操作
??? for i in range(N):
??????? for j in range(N):
??????????? newImg1[j, i] = newImg[int(np.floor(j*step2)), i]
??? return newImg1
?
#從文件名中分解出第一個(gè)數(shù)字(真實(shí)值)
def get_number_cut(file_name):
??? fileStr = file_name.split('.')[0]??? ??????????#文件名格式為:0_3.txt
??? classNumStr = int(fileStr.split('_')[0])
??? return classNumStr
?
#用字符矩陣打印圖片
def get_show(test_data):
??? for i in range(N**2):
??????? if(test_data[0,i] == 0):
??????????? print ("1",end='')
??????? else:
??????????? print ("0",end='')
??????? if (i+1)%N == 0 :
??????????? print()
?
def main():
??? t1 = datetime.datetime.now()? # 計(jì)時(shí)開始
??? Nearest_Neighbor_number = int(input('選取最鄰近的K個(gè)值(建議小于7),K='))
??? #訓(xùn)練集:get_train_data()函數(shù)得到訓(xùn)練集數(shù)據(jù)矩陣、下標(biāo)簽索引
??? train_label, train_data = get_all_train_data()
?
??? #測(cè)試集:根據(jù)路徑,獲取測(cè)試集地址
??? test_file_list = listdir('testlist')
??? file_name = test_file_list[0]
??? #測(cè)試集:運(yùn)用切片函數(shù),得到測(cè)試集下標(biāo)索引(真實(shí)值)
??? test_index = get_number_cut(file_name)
??? #測(cè)試集:得到訓(xùn)練集圖像的向量矩陣
??? test_data = get_all_data(test_file_list,0)
??? #測(cè)試集:get_show()函數(shù):用字符矩陣打印圖片
??? #get_show(test_data)
?
??? #調(diào)用knn算法進(jìn)行測(cè)試
??? Result = KNN(test_data, train_data, train_label, Nearest_Neighbor_number)
??? print ("最終預(yù)測(cè)值為:",Result,"??? 真實(shí)值:",test_index)
??? t2 = datetime.datetime.now()
??? print('耗 時(shí) = ', t2 - t1)
?
if __name__ == "__main__":
??? main()
結(jié)果如下:?
?
評(píng)價(jià):效果看起來(lái)還馬馬虎虎了,但是對(duì)于那些書寫不標(biāo)準(zhǔn)的,識(shí)別度還是較低,改善空間還是很大,歡迎大家相互指正,相互學(xué)習(xí)。
—-—-—-—-—-—-—-—-—-—-—-—–—-—-—-—-—-—-—-——-—-—-—-—-—-—-—-—-—-—-—-—-—-——-?
這里還有一篇很好玩的實(shí)現(xiàn)方法,用到的是openCV?
代碼鏈接:http://blog.csdn.net/littlethunder/article/details/51615237?
視頻鏈接:http://www.bilibili.com/video/av4904541/?
真心有意思?
–—-—-—-—-—-—-—-—-—-—-—-—–—-—-—-—-—-—-—-——-—-—-—-—-—-—-—-—-—-—-—-—-—-——-?
本篇所有的源碼資源都已上傳:https://download.csdn.net/download/zzz_cming/10377414?
–—-—-—-—-—-—-—-—-—-—-—-—–—-—-—-—-—-—-—-——-—-—-—-—-—-—-—-—-—-—-—-—-—-——-?
總結(jié)
KNN算法是一種比較簡(jiǎn)單的分類方法,人工智能入門級(jí)吧
KNN算法缺陷在于沒有考慮不同數(shù)字間在結(jié)構(gòu)特征上的差異
感謝本文作者zzZ_CMing的投稿!
總結(jié)
以上是生活随笔為你收集整理的监督学习:KNN(K-近邻)算法实现手写数字识别的三种方法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 通过听力写代码?盲人程序员就是这样做的
- 下一篇: 程序员找不到对象是因为还没遇到一个有远见