計算機視覺—kNN識別手寫數字(10)

Kervin_Chan發表於2018-06-04

一、MNIST手寫數字介紹

1、獲取樣本

手寫數字的MNIST資料庫可從此頁面獲得,其中包含60,000個示例的訓練集以及10,000個示例的測試集。它是NIST提供的更大集合的子集。這些數字已經過尺寸標準化並以固定尺寸的影像為中心。

下載連結:http://yann.lecun.com/exdb/mnist/

樣本包含了四個部分:

訓練集圖片: train-images-idx3-ubyte.gz
(9.9 MB, 解壓後 47 MB, 包含 60,000 個樣本)

訓練集圖片對應的標籤: train-labels-idx1-ubyte.gz
(29 KB, 解壓後 60 KB, 包含 60,000 個標籤)

Test set images: t10k-images-idx3-ubyte.gz
(1.6 MB, 解壓後 7.8 MB, 包含 10,000 個樣本)

Test set labels: t10k-labels-idx1-ubyte.gz
(5KB, 解壓後 10 KB, 包含 10,000 個標籤)

2、MNIST解析

MNIST是深度學習的經典入門demo,他是由6萬張訓練圖片和1萬張測試圖片構成的,每張圖片都是28*28大小(如下圖),而且都是黑白色構成(這裡的黑色是一個0-1的浮點數,黑色越深表示數值越靠近1),這些圖片是採集的不同的人手寫從0到9的數字。TensorFlow將這個資料集和相關操作封裝到了庫中,下面我們來一步步解讀深度學習MNIST的過程。

計算機視覺—kNN識別手寫數字(10)

上圖就是4張MNIST圖片。這些圖片並不是傳統意義上的png或者jpg格式的圖片,因為png或者jpg的圖片格式,會帶有很多干擾資訊(如:資料塊,圖片頭,圖片尾,長度等等),這些圖片會被處理成很簡易的二維陣列,如圖:

計算機視覺—kNN識別手寫數字(10)

可以看到,矩陣中有值的地方構成的圖形,跟左邊的圖形很相似。之所以這樣做,是為了讓模型更簡單清晰。特徵更明顯。

二、kNN原理

1、kNN演算法概述

  kNN演算法的核心思想是如果一個樣本在特徵空間中的k個最相鄰的樣本中的大多數屬於某一個類別,則該樣本也屬於這個類別,並具有這個類別上樣本的特性。該方法在確定分類決策上只依據最鄰近的一個或者幾個樣本的類別來決定待分樣本所屬的類別。

2、kNN演算法介紹

  最簡單最初級的分類器是將全部的訓練資料所對應的類別都記錄下來,當測試物件的屬性和某個訓練物件的屬性完全匹配時,便可以對其進行分類。但是怎麼可能所有測試物件都會找到與之完全匹配的訓練物件呢,其次就是存在一個測試物件同時與多個訓練物件匹配,導致一個訓練物件被分到了多個類的問題,基於這些問題呢,就產生了kNN。

  kNN是通過測量不同特徵值之間的距離進行分類。它的的思路是:如果一個樣本在特徵空間中的k個最相似(即特徵空間中最鄰近)的樣本中的大多數屬於某一個類別,則該樣本也屬於這個類別。K通常是不大於20的整數。kNN演算法中,所選擇的鄰居都是已經正確分類的物件。該方法在定類決策上只依據最鄰近的一個或者幾個樣本的類別來決定待分樣本所屬的類別。

下面通過一個簡單的例子說明一下:

  如下圖,綠色圓要被決定賦予哪個類,是紅色三角形還是藍色四方形?如果K=3,由於紅色三角形所佔比例為2/3,綠色圓將被賦予紅色三角形那個類,如果K=5,由於藍色四方形比例為3/5,因此綠色圓被賦予藍色四方形類。

計算機視覺—kNN識別手寫數字(10)

由此也說明了kNN演算法的結果很大程度取決於K的選擇。

  在kNN中,通過計算物件間距離來作為各個物件之間的非相似性指標,避免了物件之間的匹配問題,在這裡距離一般使用歐氏距離或曼哈頓距離:

計算機視覺—kNN識別手寫數字(10)

  同時,kNN通過依據k個物件中佔優的類別進行決策,而不是單一的物件類別決策。這兩點就是kNN演算法的優勢。

接下來對kNN演算法的思想總結一下:

  就是在訓練集中資料和標籤已知的情況下,輸入測試資料,將測試資料的特徵與訓練集中對應的特徵進行相互比較,找到訓練集中與之最為相似的前K個資料,則該測試資料對應的類別就是K個資料中出現次數最多的那個分類,其演算法的描述為:

1)計算測試資料與各個訓練資料之間的距離;

2)按照距離的遞增關係進行排序;

3)選取距離最小的K個點;

4)確定前K個點所在類別的出現頻率;

5)返回前K個點中出現頻率最高的類別作為測試資料的預測分類。

原文連結:https://www.cnblogs.com/sxron/p/5451923.html

三、例項講解

1、載入MNIST資料

TensorFlow已經準備了一個指令碼來自動下載和匯入MNIST資料集。它會自動建立一個'MNIST_data'的目錄來儲存資料。

import tensorflow as tf
import numpy as np
import random 
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('MNIST_data',one_hot=True)
# one_hot介紹 :https://blog.csdn.net/lanhaier0591/article/details/78702558
複製程式碼

這裡,mnist是一個輕量級的類。它以Numpy陣列的形式儲存著訓練、校驗和測試資料集。同時提供了一個函式,用於在迭代中獲得minibatch,後面我們將會用到。

2、設定屬性

trainNum = 60000
# 訓練圖片總數
testNum = 10000
# 測試圖片總數
trainSize = 500
# 訓練的時候使用的圖片數量
testSize = 5
# 測試的時候使用的圖片數量
k = 4
# 距離最小的K個圖片
複製程式碼

3、資料分解

我們前面說過,每張圖片都是28 * 28 = 784畫素,所以在testData.shape= (5, 784)中,5代表圖片個數,784代表每張圖片的畫素。

那testLabel.shape= (5, 10),代表什麼?

先看看:

  • testLabel= [[0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
    [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
    [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
    [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
    [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]]

testLabel中第1個標籤[0. 0. 0. 0. 0. 0. 1. 0. 0. 0.] 為數字6,和testData的第1個資料相對應。 以此類推,第2個資料的標籤是0,第3個資料的標籤是4,第4個資料的標籤是0,第5個資料的標籤是6。

trainIndex = np.random.choice(trainNum,trainSize,replace=False)
# 在trainNum資料中,生成trainSize個不重複的隨機數 
testIndex = np.random.choice(testNum,testSize,replace=False)

trainData = mnist.train.images[trainIndex]
# 訓練資料集的圖片是 mnist.train.images
trainLabel = mnist.train.labels[trainIndex]
# 訓練資料集的標籤是 mnist.train.labels
testData = mnist.test.images[testIndex]
# 測試資料集的圖片是 mnist.test.images
testLabel = mnist.test.labels[testIndex]
# 測試資料集的標籤是 mnist.test.labels
複製程式碼
print('trainData.shape=',trainData.shape)
print('trainLabel.shape=',trainLabel.shape)
print('testData.shape=',testData.shape)
print('testLabel.shape=',testLabel.shape)
print('testLabel=',testLabel)
複製程式碼

結果:

trainData.shape= (500, 784)
trainLabel.shape= (500, 10)
testData.shape= (5, 784)
testLabel.shape= (5, 10)
testLabel= [[0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
 [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
 [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]]
複製程式碼

4、資料訓練

(1)設定變數

tf.placeholder(dtype, shape=None, name=None)
此函式可以理解為形參,用於定義過程,在執行的時候再賦具體的值 引數:

  • dtype:資料型別。常用的是tf.float32,tf.float64等數值型別
  • shape:資料形狀。預設是None,就是一維值,也可以是多維,比如[2,3], [None, 3]表示列是3,行不定
  • name:名稱。
trainDataInput = tf.placeholder(shape=[None,784],dtype=tf.float32)
trainLabelInput = tf.placeholder(shape=[None,10],dtype=tf.float32)
testDataInput = tf.placeholder(shape=[None,784],dtype=tf.float32)
testLabelInput = tf.placeholder(shape=[None,10],dtype=tf.float32)
複製程式碼

(2)計算kNN的距離

使用曼哈頓距離:

計算機視覺—kNN識別手寫數字(10)

f1 = tf.expand_dims(testDataInput,1) 
# expand_dim()來增加維度
f2 = tf.subtract(trainDataInput,f1)
# subtract()相減,得到一個三維資料
f3 = tf.reduce_sum(tf.abs(f2),reduction_indices=2)
# tf.abs()求資料絕對值
# tf.reduce_sum()完成資料累加,把資料放到f3中
複製程式碼
with tf.Session() as sess:
    p1 = sess.run(f1,feed_dict={testDataInput:testData[0:5]})
    print('p1=',p1.shape)
    # p1= (5, 1, 784)
    p2 = sess.run(f2,feed_dict={trainDataInput:trainData,testDataInput:testData[0:5]})
    print('p2=',p2.shape)
    # p2= (5, 500, 784) 
    p3 = sess.run(f3,feed_dict={trainDataInput:trainData,testDataInput:testData[0:5]})
    print('p3=',p3.shape)
    # p3= (5, 500)
    print('p3[0,0]=',p3[0,0]) 
    # p3[0,0]= 132.96472,表示第一張測試圖片和第一張訓練圖片的距離是132.96472
複製程式碼

結果:

p1= (5, 1, 784)
p2= (5, 500, 784)
p3= (5, 500)
p3[0,0]= 132.96472
複製程式碼

###(3)選取距離最小的K個圖片

tf.nn.top_k(input, k, name=None)
這個函式的作用是返回 input 中每行最大的 k 個數,並且返回它們所在位置的索引。

輸入引數:

  • input: 一個張量,資料型別必須是以下之一:float32、float64、int32、int64、uint8、int16、int8。資料維度是 batch_size 乘上 x 個類別。
  • k: 一個整型,必須 >= 1。在每行中,查詢最大的 k 個值。
  • name: 為這個操作取個名字。

輸出引數:

  • 一個元組 Tensor ,資料元素是 (values, indices),具體如下:
  • values: 一個張量,資料型別和 input 相同。資料維度是 batch_size 乘上 k 個最大值。
  • indices: 一個張量,資料型別是 int32 。每個最大值在 input 中的索引位置。
f4 = tf.negative(f3)
# tf.negative(x,name=None),取負運算(f4 =-f3 = -132.96472)
f5,f6 = tf.nn.top_k(f4,k=4) 
# f5,選取f4最大的四個值,即f3最小的四個值
# f6,這四個值對應的索引
複製程式碼
with tf.Session() as sess:
    p4 = sess.run(f4,feed_dict={trainDataInput:trainData,testDataInput:testData[0:5]})
    print('p4=',p4.shape)
    print('p4[0,0]=',p4[0,0])
    p5,p6 = sess.run((f5,f6),feed_dict={trainDataInput:trainData,testDataInput:testData[0:5]})
    # p5= (5, 4),每一張測試圖片(共5張),分別對應4張最近訓練圖片,共20張
    print('p5=',p5.shape)
    print('p6=',p6.shape)
    print('p5',p5)
    print('p6',p6)
複製程式碼

結果:

p4= (5, 500)
p4[0,0]= -132.96472
p5= (5, 4)
p6= (5, 4)
p5= [[-54.49804  -54.87059  -55.690197 -59.97647 ]
     [-49.09412  -64.74118  -68.22353  -68.76863 ]
     [-65.36079  -69.278435 -72.60785  -74.84314 ]
     [-75.46667  -78.19216  -78.36864  -80.44706 ]
     [-42.478436 -61.517654 -62.36863  -63.42353 ]]
p6= [[150 167 493 226]
     [402 268 279 164]
     [300  97  78 237]
     [387 164 268 311]
     [258 107 226 207]]
複製程式碼

(3)確定K個圖片在型別出現的頻率

tf.gather()
根據索引從引數軸上收集切片。 索引必須是任何維度的整數張量 (通常為 0-D 或 1-D)。生成輸出張量該張量的形狀為:params.shape[:axis] + indices.shape + params.shape[axis + 1:]

輸入引數:

  • params:一個張量。這個張量是用來收集數值的。該張量的秩必須至少是 axis + 1。
  • indices:一個張量。必須是以下型別之一:int32,int64。索引張量必須在 [0, params.shape[axis]) 範圍內。
  • axis:一個張量。必須是以下型別之一:int32,int64。在引數軸從中收集索引。預設為第一個維度。支援負索引。
  • name:操作的名稱(可選)。

輸出引數:

  • 該函式返回一個張量。與引數具有相同的型別。引數值從索引給定的索引中收集而來,並且形狀為:params.shape[:axis] + indices.shape + params.shape[axis + 1:]。
f7 = tf.gather(trainLabelInput,f6)
# 根據索引找到對應的標籤值
f8 = tf.reduce_sum(f7,reduction_indices=1)
# 累加維度1的數值
f9 = tf.argmax(f8,dimension=1)
# 返回的是f8中的最大值的索引號
複製程式碼
with tf.Session() as sess:
    p7 = sess.run(f7,feed_dict={trainDataInput:trainData,testDataInput:testData[0:5],trainLabelInput:trainLabel})
    print('p7=',p7.shape)
    print('p7[]',p7)
    p8 = sess.run(f8,feed_dict={trainDataInput:trainData,testDataInput:testData[0:5],trainLabelInput:trainLabel})
    print('p8=',p8.shape)
    print('p8[]=',p8)
    p9 = sess.run(f9,feed_dict={trainDataInput:trainData,testDataInput:testData[0:5],trainLabelInput:trainLabel})
    print('p9=',p9.shape)
    print('p9[]=',p9)
複製程式碼

結果:

p7= (5, 4, 10)
p7[]= [[[0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
  [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
  [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
  [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]]

 [[1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
  [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
  [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
  [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]

 [[0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
  [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
  [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
  [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]]

 [[1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
  [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
  [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
  [1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]

 [[0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
  [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
  [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
  [0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]]]
p8= (5, 10)
p8[]= [[0. 0. 0. 0. 0. 0. 4. 0. 0. 0.]
 [4. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 4. 0. 0. 0. 0. 0. 0.]
 [4. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 3. 0. 0. 1.]]
p9= (5,)
p9[]= [6 0 3 0 6]
複製程式碼

(4)檢驗結果

with tf.Session() as sess:
 p10 = np.argmax(testLabel[0:5],axis=1)
 # p9=p10,代表正確
    print('p10[]=',p10)
j = 0
for i in range(0,5):
    if p10[i] == p9[i]:
        j = j+1
print('ac=',j*100/5)
# 正確率
複製程式碼

結果:

p10[]= [6 0 3 0 6]
ac= 100.0
複製程式碼

相關文章