最鄰近算法(KNN)識別數字驗證碼


應用場景

  對於簡單的數字型驗證碼的自動識別。前期已經完成的工作是通過切割將驗證碼圖片切割成一個一個的單個數字的圖片,並按照對應的數字表征類別進行分類(即哪些圖片表示數字7,哪些表示8),將各種數字的圖片轉換成32×32的二值矩陣,並存放在.txt中,每一種數字表示所對應的.txt的文件名為:“數字類標號_序號.txt”。取一部分這樣的.txt作為已知樣本集,另一部分作為驗證集。使用最鄰近算法KNN實現對數字進行識別。

最鄰近算法(KNN,K-Nearest Neighbor)

  可以說KNN是最簡單的分類算法了。已知的數據集是帶類標號的數據。KNN本質是基於一種數據統計的方法!其實很多機器學習算法也是基於數據統計的。

  KNN是一種memory-based learning,也叫instance-based learning,屬於lazy learning。即它沒有明顯的前期訓練過程,而是程序開始運行時,把數據集加載到內存后,不需要進行訓練,就可以開始分類了。下面的源碼部分的注釋,我用到了“訓練”這樣的表述,是為了在這個領域方便交流的表達。

  具體是每次來一個未知的樣本點,就在附近找K個最近的點進行投票。或者說,就是計算這個未知的樣本點(或者在數據挖掘領域叫“元組”)和所有已知點的歐幾里得距離,選擇距離最近的前k個已知樣本進行投票,即這k個已知樣本中,哪一種類型占的比例最大,那么就認為這個未知的樣本屬於哪一個分類。

源碼

from numpy import *
import operator
from os import listdir


def knn(k, testdata, traindata, labels):	#k為人為設定的閾值,表示在最接近的k個訓練元組中獲得類標號
    
	# testdata:[特征1,特征2,特征3,……,特征1024]
    # traindata:[[特征1,特征2,特征3,……,特征1024],[特征1,特征2,特征3,……,特征1024],[特征1,特征2,特征3,……,特征1024],……]
    #調用(numpy中的)shape函數來返回traindata的長度,也就是traindata.shape[0]返回的是訓練集的元組個數
    traindatasize = traindata.shape[0]

    #調用(numpy中的)tile將一維的testdata擴展成一個二維和traindata規模相同的矩陣。
    #tile得到的矩陣和traindata作差,得到的矩陣為dif
    dif = tile(testdata, (traindatasize, 1)) - traindata
   
    sqdif = dif ** 2		 #計算每個特征的差的平方
    
	# sumsqdif已經成為一維列表
    sumsqdif = sqdif.sum(axis=1)		# axis=1 是按行求和(axis=0是列求和)
    
	#每個和數作開平方操作,存放到一維的distance中
    distance = sumsqdif ** 0.5
    
	#對distance中的元素進行排序,argsort函數返回的是數組值從小到大的索引值,即返回的第一個元素是distance中最小值的索引值,以此類推
    sortdistance = distance.argsort()	# sortdistance指的是測試數據與各訓練數據的距離由近到遠排序之后的結果列表

    #聲明一個字典count存放排序前k小的類標號及其出現頻數
    count = {}  # {"類別":"次數"}
    for i in range(0, k):
        vote = labels[sortdistance[i]]  # 當前距離的類別是誰(由近至遠)
        #count.get(vote, 0)的作用是在字典count中查找vote並返回其值,如果vote鍵不存在則返回0
        count[vote] = count.get(vote, 0) + 1
    
	#使用sorted()產生一個新的排序后的列表,當reverse參數置為真時,這個列表是降序排序的
    #item()方法把字典中每對key和value組成一個元組,並把這些元組放在列表中返回。
    #key=operator.itemgetter(1)表示按第2個域排序
    #因為count.items()返回一個二維列表,所以排序后sortcount也是二維的。
    sortcount = sorted(count.items(), key=operator.itemgetter(1), reverse=True)
    
	#取sortcount第一行(出現次數最多的鍵值對)的第一個元素就是對應的預測的分類標號。
    return sortcount[0][0]


# 數據加載
#預處理,將源訓練數據處理成一個長度為1024的整型列表
def datatoarray(fname):
    arr = []
    fh = open(fname)
    for i in range(0, 32):
        #逐行讀入
        thisline = fh.readline()
        for j in range(0, 32):
            arr.append(int(thisline[j]))
    return arr



# 取文件名前綴(類別)
def seplabel(fname):
    #用split來切割訓練元組的文件名,分隔符是下划線,split返回的是一個字符串列表,我們只需要這個列表的第一個元素,來作為這個元組的類標號,並強轉為整型
    label = int(fname.split("_")[0])

    return label


# 建立訓練數據
# labels:[類別,類別,類別,類別,……]有多少個訓練元組就有多少個類別標號class label
# trainarr:[[特征1,特征2,特征3,……,特征1024],[特征1,特征2,特征3,……,特征1024],[特征1,特征2,特征3,……,特征1024],……]一個字符用一個特征表示
# 可見,trainarr是一個矩陣(二維數組),有num行,1024列。num是訓練元組個數,也等於lables數組的長度。一個字符用一個特征表示
def traindata():
    labels = []

    #trainfile是一個存儲訓練元組對應的文件名的一維數組
    trainfile = listdir("./traindata")

    num = len(trainfile)
    #(numpy中的)zeros函數功能是創建給定類型的矩陣,並初始化為0。在這里用來初始化一個num行,1024列的矩陣trainarr。
    trainarr = zeros((num, 1024))

    #遍歷每一個訓練元組
    for i in range(0, num):
        thisname = trainfile[i]
        
		#提取當前這個訓練元組的類標號(分類)
        thislabel = seplabel(thisname)
       
		#將當前訓練元組的類標號存放進labels數組
        labels.append(thislabel)
        
		#將當前這個訓練元組(第i個)的內容轉換成一個一維的特征序列覆蓋trainarr的第i行
        trainarr[i, :] = datatoarray("./traindata/" + thisname)
    
	#函數返回訓練數據集trainarr和對應的類標號數組
    return trainarr, labels



#執行測試
trainarr, labels = traindata()

#當前要測試的元組
thistestfile = "6_21.txt"

#將這個測試元組轉換成一維的特征序列(長度為1024)
testarr = datatoarray("./testdata/" + thistestfile)

#調用函數knn,返回結果
rst = knn(3, testarr, trainarr, labels)
print(rst)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM