Kaggle KNN實現Digit Recognizer
本文參考了https://blog.csdn.net/u012162613/article/details/41929171,然後總結一下自己的理解。主要從資料準備、資料分析、核心演算法三個方面介紹。
資料準備
Kaggle官網中搜索Digit Recognizer,從‘Data’中下載csv檔案:train.csv,test.csv,sample_submission.csv。
從github下載knn_benchmark.csv(官方給出的參考結果,可與自己訓練的結果對比)https://github.com/clytwynec/digit_recognition/blob/master/data/knn_benchmark.csv
資料分析
訓練集和測試集是包含0-9共10個手寫數字的灰度影象,每張圖的畫素是28*28=784,每個畫素有一個單一的畫素值,畫素值是0-255的整數。
train.csv
train.csv是訓練樣本集,大小42001*785,第一行是文字描述,所以實際的樣本資料大小是42000*785。其中第一列的每一個數字是它對應行的label,可以將第一列單獨取出來,得到42000*1的向量trainLabel,剩下的就是42000*784的特徵向量集trainData。程式碼如下:
def loadTrainData(): l=[] with open('train.csv') as file: #open()返回了一個檔案物件file lines=csv.reader(file) #reader()返回reader()物件lines,lines是一個列表 for line in lines: #按行加入到l列表中 l.append(line) #42001*785 l.remove(l[0]) #第一行是文字描述,刪除第一行 l=array(l) #列表轉換為陣列 label=l[:,0] #取l中所有行的第0個數據 data=l[:,1:] #取l中所有行,第一列到最後一列的資料 return nomalizing(toInt(data)),toInt(label) #label 1*42000 data 42000*784
其中有兩個函式需要說明一下,toInt()函式,是將字串轉換為整數,因為從csv檔案讀取出來的,是字串型別的,而運算過程中需要整數型別的,因此需要轉換。toInt()函式如下:
def toInt(array): array=mat(array) #用mat()轉換為矩陣之後可以進行一些線性代數的操作 m,n=shape(array) newArray=zeros((m,n)) for i in range(m): for j in range(n): newArray[i,j]=int(array[i,j]) return newArray
nomalizing()函式做的工作是歸一化,因為train.csv裡面提供的表示影象的資料是0~255的,為了簡化運算,我們可以將其轉化為二值影象,因此將所有非0的數字,即1~255都歸一化為1。nomalizing()函式如下:
def nomalizing(array):
m,n=shape(array)
for i in range(m):
for j in range(n):
if array[i,j]!=0:
array[i,j]=1
return array
test.csv
test.csv裡的資料大小是28001*784,第一行是文字描述,因此實際的測試資料樣本是28000*784,與train.csv不同,沒有label,28000*784即28000個測試樣本,需要做的工作就是為這28000個測試樣本找出正確的label。
knn_benchmark.csv
knn_benchmark.csv裡的資料是28001*2,第一行是文字說明,可以去掉,第一列表示圖片序號1~28000,第二列是圖片對應的數字。
核心演算法
採用KNN演算法來分類。K最近鄰分類演算法是資料探勘分類技術中最簡單的方法之一。kNN演算法的核心思想是如果一個樣本在特徵空間中的k個最相鄰的樣本中的大多數屬於某一個類別,則該樣本也屬於這個類別,並具有這個類別上樣本的特性。本文衡量樣本間的距離使用的是歐式距離。核心程式碼:
def classify(inX, dataSet, labels, k):
inX=mat(inX) #inX是輸入的單個樣本
dataSet=mat(dataSet) #訓練樣本集
labels=mat(labels) #對應的標籤向量
dataSetSize = dataSet.shape[0] #得出dataSet的行數,即樣本個數
diffMat = tile(inX, (dataSetSize,1)) - dataSet #tile(A,(m,n))將陣列A作為元素構造m行n列的陣列
sqDiffMat = array(diffMat)**2
sqDistances = sqDiffMat.sum(axis=1) #array.sum(axis=1)按行累加,axis=0為按列累加
distances = sqDistances**0.5
sortedDistIndicies = distances.argsort() #array.argsort()返回陣列值從小到大的索引值
classCount={}
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i],0]
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1 #get(key,x)從字典中獲取key對應的value,沒有key的話返回0
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True) #sorted()函式,按照第二個元素即次序逆向(reverse=True)排序
return sortedClassCount[0][0]
簡單說明一下,inX就是輸入的單個樣本,是一個特徵向量。dataSet是訓練樣本,對應上面的trainData,labels對應trainLabel,k是knn演算法選定的k,一般選擇0~20之間的數字。這個函式將返回inX的label,即圖片inX對應的數字。
對於測試集裡28000個樣本,呼叫28000次這個函式即可。
執行結果
以上使用了20000個訓練樣本進行訓練,最後預測的錯誤率為3.5%。