基於DL的計算機視覺(2)--實現圖像分類最簡單的方法:KNN

1. 圖像分類問題

這是人每天自然而然會做的事情,普通到大部分時候,我們都感知不到我們在完成一個個這樣的任務。早晨起牀洗漱,你要看看洗漱臺一堆東西中哪個是杯子,哪個是你的牙刷;喫早餐的時候你要分辨食物和碗碟… 
抽象一下,對於一張輸入的圖片,要判定它屬於給定的一些標籤/類別中的哪一個。看似很簡單的一個問題,這麼多年卻一直是計算機視覺的一個核心問題,應用場景也很多。它的重要性還體現在,其實其他的一些計算機視覺的問題(比如說物體定位和識別、圖像內容分割等)都可以基於它去完成。

咱們舉個例子從機器學習的角度描述一下這個問題^_^

計算機拿到一張圖片(如下圖所示),然後需要給出它對應{貓,狗,帽子,杯子}4類的概率。人類是灰常牛逼的生物,我們一瞥就知道這貨是貓。然而對計算機而言,他們是沒辦法像人一樣『看』到整張圖片的。對它而言,這是一個3維的大矩陣,包含248*400個像素點,每個像素點又有紅綠藍(RGB)3個顏色通道的值,每個值在0(黑)-255(白)之間,計算機就需要根據這248*400*3=297600個數值去判定這貨是『貓』

貓圖像=>矩陣

1.1 圖像識別的難點

圖像識別看似很直接。但實際上包含很多挑戰,人類可是經過數億年的進化才獲得如此強大的大腦,對於各種物體有着精準的視覺理解力。總體而言,我們想『教』會計算機去認識一類圖,會有下面這樣一些困難:

  • 視角不同,每個事物旋轉或者側視最後的構圖都完全不同
  • 尺寸大小不統一,相同內容的圖片也可大可小
  • 變形,很多東西處於特殊的情形下,會有特殊的擺放和形狀
  • 光影等干擾/幻象
  • 背景干擾
  • 同類內的差異(比如椅子有靠椅/吧椅/餐椅/躺椅…)

圖像面臨的挑戰

1.2 識別的途徑

首先,大家想想就知道,這個算法並不像『對一個數組排序』或者『求有向圖的最短路徑』,我們沒辦法提前制定一個流程和規則去解決。定義『貓』這種動物本身就是一件很難的事情了,更不要說去定義一隻貓在圖像上的固定表現形式。所以我們寄希望於機器學習,使用『Data-driven approach/數據驅動法』來做做嘗試。簡單說來,就是對於每個類別,我們都找一定量的圖片數據,『喂』給計算機,讓它自己去『學習和總結』每一類的圖片的特點。對了,這個過程和小盆友學習新鮮事物是一樣一樣的。『喂』給計算機學習的圖片數據就和下圖的貓/狗/杯子/帽子一樣:

Data-driven approach

1.3 機器學習解決圖像分類的流程/Pipeline

整體的流程和普通機器學習完全一致,簡單說來,也就下面三步:

  • 輸入:我們的給定K個類別的N張圖片,作爲計算機學習的訓練集
  • 學習:讓計算機逐張圖片地『觀察』和『學習』
  • 評估:就像我們上學學了東西要考試檢測一樣,我們也得考考計算機學得如何,於是我們給定一些計算機不知道類別的圖片讓它判別,然後再比對我們已知的正確答案。

2. 最近鄰分類器(Nearest Neighbor Classifier)

先從簡單的方法開始說,先提一提最近鄰分類器/Nearest Neighbor Classifier,不過事先申明,它和深度學習中的卷積神經網/Convolutional Neural Networks其實一點關係都沒有,我們只是從基礎到前沿一點一點推進,最近鄰是圖像識別一個相對簡單和基礎的實現方式。

2.1 CIFAR-10

CIFAR-10是一個非常常用的圖像分類數據集。數據集包含60000張32*32像素的小圖片,每張圖片都有一個類別標註(總共有10類),分成了50000張的訓練集和10000張的測試集。如下是一些圖片示例:

CIFAR-10例子

上圖中左邊是十個類別和對應的一些示例圖片,右邊是給定一張圖片後,根據像素距離計算出來的,最近的10張圖片。

2.2 基於最近鄰的簡單圖像類別判定

假如現在用CIFAR-10數據集做訓練集,判斷一張未知的圖片屬於CIFAR-10中的哪一類,應該怎麼做呢。一個很直觀的想法就是,既然我們現在有每個像素點的值,那我們就根據輸入圖片的這些值,計算和訓練集中的圖片距離,找最近的圖片的類別,作爲它的類別,不就行了嗎。

恩,想法很直接,這就是『最近鄰』的思想。偷偷說一句,這種直接的做法在圖像識別中,其實效果並不是特別好。比如上圖是按照這個思想找的最近鄰,其實只有3個圖片的最近鄰是正確的類目。

即使這樣,作爲最基礎的方法,還是得掌握,我們來簡單實現一下吧。我們需要一個圖像距離評定準則,比如最簡單的方式就是,比對兩個圖像像素向量之間的l1距離(也叫曼哈頓距離/cityblock距離),公式如下:

d1(I1,I2)=pIp1Ip2

其實就是計算了所有像素點之間的差值,然後做了加法,直觀的理解如下圖:

矩陣的l1距離

我們先把數據集讀進內存:

<code class="language-python hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">#! /usr/bin/env python</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">#coding=utf-8</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">import</span> os
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">import</span> sys
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">import</span> numpy <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">as</span> np

<span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">def</span> <span class="hljs-title" style="box-sizing: border-box;">load_CIFAR_batch</span><span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(filename)</span>:</span>
    <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"""
    cifar-10數據集是分batch存儲的,這是載入單個batch

    @參數 filename: cifar文件名
    @r返回值: X, Y: cifar batch中的 data 和 labels
    """</span>

    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">with</span> open(filename, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'r'</span>) <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">as</span> f:
        datadict=pickle.load(f)

        X=datadict[<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'data'</span>]
        Y=datadict[<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'labels'</span>]

        X=X.reshape(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10000</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">3</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">32</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">32</span>).transpose(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>,<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>,<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">3</span>,<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>).astype(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"float"</span>)
        Y=np.array(Y)

        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> X, Y


<span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">def</span> <span class="hljs-title" style="box-sizing: border-box;">load_CIFAR10</span><span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(ROOT)</span>:</span>
    <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"""
    讀取載入整個 CIFAR-10 數據集

    @參數 ROOT: 根目錄名
    @return: X_train, Y_train: 訓練集 data 和 labels
             X_test, Y_test: 測試集 data 和 labels
    """</span>

    xs=[]
    ys=[]

    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> b <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">in</span> range(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>,<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">6</span>):
        f=os.path.join(ROOT, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"data_batch_%d"</span> % (b, ))
        X, Y=load_CIFAR_batch(f)
        xs.append(X)
        ys.append(Y)

    X_train=np.concatenate(xs)
    Y_train=np.concatenate(ys)

    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">del</span> X, Y

    X_test, Y_test=load_CIFAR_batch(os.path.join(ROOT, <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"test_batch"</span>))

    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> X_train, Y_train, X_test, Y_test

<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 載入訓練和測試數據集</span>
X_train, Y_train, X_test, Y_test = load_CIFAR10(<span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'data/cifar10/'</span>) 
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 把32*32*3的多維數組展平</span>
Xtr_rows = X_train.reshape(X_train.shape[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>], <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">32</span> * <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">32</span> * <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">3</span>) <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># Xtr_rows : 50000 x 3072</span>
Xte_rows = X_test.reshape(X_test.shape[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>], <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">32</span> * <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">32</span> * <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">3</span>) <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># Xte_rows : 10000 x 3072</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li><li style="box-sizing: border-box; padding: 0px 5px;">37</li><li style="box-sizing: border-box; padding: 0px 5px;">38</li><li style="box-sizing: border-box; padding: 0px 5px;">39</li><li style="box-sizing: border-box; padding: 0px 5px;">40</li><li style="box-sizing: border-box; padding: 0px 5px;">41</li><li style="box-sizing: border-box; padding: 0px 5px;">42</li><li style="box-sizing: border-box; padding: 0px 5px;">43</li><li style="box-sizing: border-box; padding: 0px 5px;">44</li><li style="box-sizing: border-box; padding: 0px 5px;">45</li><li style="box-sizing: border-box; padding: 0px 5px;">46</li><li style="box-sizing: border-box; padding: 0px 5px;">47</li><li style="box-sizing: border-box; padding: 0px 5px;">48</li><li style="box-sizing: border-box; padding: 0px 5px;">49</li><li style="box-sizing: border-box; padding: 0px 5px;">50</li><li style="box-sizing: border-box; padding: 0px 5px;">51</li><li style="box-sizing: border-box; padding: 0px 5px;">52</li><li style="box-sizing: border-box; padding: 0px 5px;">53</li><li style="box-sizing: border-box; padding: 0px 5px;">54</li><li style="box-sizing: border-box; padding: 0px 5px;">55</li><li style="box-sizing: border-box; padding: 0px 5px;">56</li><li style="box-sizing: border-box; padding: 0px 5px;">57</li><li style="box-sizing: border-box; padding: 0px 5px;">58</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li><li style="box-sizing: border-box; padding: 0px 5px;">37</li><li style="box-sizing: border-box; padding: 0px 5px;">38</li><li style="box-sizing: border-box; padding: 0px 5px;">39</li><li style="box-sizing: border-box; padding: 0px 5px;">40</li><li style="box-sizing: border-box; padding: 0px 5px;">41</li><li style="box-sizing: border-box; padding: 0px 5px;">42</li><li style="box-sizing: border-box; padding: 0px 5px;">43</li><li style="box-sizing: border-box; padding: 0px 5px;">44</li><li style="box-sizing: border-box; padding: 0px 5px;">45</li><li style="box-sizing: border-box; padding: 0px 5px;">46</li><li style="box-sizing: border-box; padding: 0px 5px;">47</li><li style="box-sizing: border-box; padding: 0px 5px;">48</li><li style="box-sizing: border-box; padding: 0px 5px;">49</li><li style="box-sizing: border-box; padding: 0px 5px;">50</li><li style="box-sizing: border-box; padding: 0px 5px;">51</li><li style="box-sizing: border-box; padding: 0px 5px;">52</li><li style="box-sizing: border-box; padding: 0px 5px;">53</li><li style="box-sizing: border-box; padding: 0px 5px;">54</li><li style="box-sizing: border-box; padding: 0px 5px;">55</li><li style="box-sizing: border-box; padding: 0px 5px;">56</li><li style="box-sizing: border-box; padding: 0px 5px;">57</li><li style="box-sizing: border-box; padding: 0px 5px;">58</li></ul>

下面我們實現最近鄰的思路:

<code class="language-python hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">NearestNeighbor</span>:</span>
  <span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">def</span> <span class="hljs-title" style="box-sizing: border-box;">__init__</span><span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(self)</span>:</span>
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">pass</span>

  <span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">def</span> <span class="hljs-title" style="box-sizing: border-box;">train</span><span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(self, X, y)</span>:</span>
    <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">""" 
    這個地方的訓練其實就是把所有的已有圖片讀取進來 -_-||
    """</span>
    <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># the nearest neighbor classifier simply remembers all the training data</span>
    self.Xtr = X
    self.ytr = y

  <span class="hljs-function" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">def</span> <span class="hljs-title" style="box-sizing: border-box;">predict</span><span class="hljs-params" style="color: rgb(102, 0, 102); box-sizing: border-box;">(self, X)</span>:</span>
    <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">""" 
    所謂的預測過程其實就是掃描所有訓練集中的圖片,計算距離,取最小的距離對應圖片的類目
    """</span>
    num_test = X.shape[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>]
    <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 要保證維度一致哦</span>
    Ypred = np.zeros(num_test, dtype = self.ytr.dtype)

    <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 把訓練集掃一遍 -_-||</span>
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> i <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">in</span> xrange(num_test):
      <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 計算l1距離,並找到最近的圖片</span>
      distances = np.sum(np.abs(self.Xtr - X[i,:]), axis = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>)
      min_index = np.argmin(distances) <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 取最近圖片的下標</span>
      Ypred[i] = self.ytr[min_index] <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 記錄下label</span>

    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> Ypred

nn = NearestNeighbor() <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 初始化一個最近鄰對象</span>
nn.train(Xtr_rows, Y_train) <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 訓練...其實就是讀取訓練集</span>
Yte_predict = nn.predict(Xte_rows) <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 預測</span>
<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 比對標準答案,計算準確率</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">print</span> <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'accuracy: %f'</span> % ( np.mean(Yte_predict == Y_test) )</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li></ul>

最近鄰的思想在CIFAR上得到的準確度爲38.6%,我們知道10各類別,我們隨機猜測的話準確率差不多是1/10=10%,所以說還是有識別效果的,但是顯然這距離人的識別準確率(94%)實在是低太多了,不那麼實用。

2.3 關於最近鄰的距離準則

我們這裏用的距離準則是l1距離,實際上除掉l1距離,我們還有很多其他的距離準則。

  • 比如說l2距離(也就是大家熟知的歐氏距離)的計算準則如下:

d2(I1,I2)=p(Ip1Ip2)2

  • 比如餘弦距離計算準則如下:

1I1I2||I1||||I2||

更多的距離準則可以參見scipy相關計算頁面.

3. K最近鄰分類器(K Nearest Neighbor Classifier)

這是對最近鄰的思想的一個調整。其實我們在使用最近鄰分類器分類,掃描CIFAR訓練集的時候,會發現,有時候不一定距離最近的和當前圖片是同類,但是最近的一些裏有很多和當前圖片是同類。所以我們自然而然想到,把最近鄰擴展爲最近的N個臨近點,然後統計一下這些點的類目分佈,取最多的那個類目作爲自己的類別。

恩,這就是KNN的思想。

KNN其實是一種特別常用的分類算法。但是有個問題,我們的K值應該取多少呢。換句話說,我們找多少鄰居來投票,比較靠譜呢?

3.1 交叉驗證與參數選擇

在現在的場景下,假如我們確定使用KNN來完成圖片類別識別問題。我們發現有一些參數是肯定會影響最後的識別結果的,比如:

  • 距離的選擇(l1,l2,cos等等)
  • 近鄰個數K的取值。

每組參數下其實都能產生一個新的model,所以這可以視爲一個模型選擇/model selection問題。而對於模型選擇問題,最常用的辦法就是在交叉驗證集上實驗。

數據總量就那麼多,如果我們在test data上做模型參數選擇,又用它做效果評估,顯然不是那麼合理(因爲我們的模型參數很有可能是在test data上過擬合的,不能很公正地評估結果)。所以我們通常會把訓練數據分爲兩個部分,一大部分作爲訓練用,另外一部分就是所謂的cross validation數據集,用來進行模型參數選擇的。比如說我們有50000訓練圖片,我們可以把它分爲49000的訓練集和1000的交叉驗證集。

<code class="language-python hljs  has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 假定已經有Xtr_rows, Ytr, Xte_rows, Yte了,其中Xtr_rows爲50000*3072 矩陣</span>
Xval_rows = Xtr_rows[:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1000</span>, :] <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 構建1000的交叉驗證集</span>
Yval = Ytr[:<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1000</span>]
Xtr_rows = Xtr_rows[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1000</span>:, :] <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 保留49000的訓練集</span>
Ytr = Ytr[<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1000</span>:]

<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 設置一些k值,用於試驗</span>
validation_accuracies = []
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> k <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">in</span> [<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">3</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">5</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">7</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">20</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">50</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">100</span>]:

  <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 初始化對象</span>
  nn = NearestNeighbor()
  nn.train(Xtr_rows, Ytr)
  <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 修改一下predict函數,接受 k 作爲參數</span>
  Yval_predict = nn.predict(Xval_rows, k = k)
  acc = np.mean(Yval_predict == Yval)
  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">print</span> <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">'accuracy: %f'</span> % (acc,)

  <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;"># 輸出結果</span>
  validation_accuracies.append((k, acc))</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li></ul>

這裏提一個在很多地方會看到的概念,叫做k-fold cross-validation,意思其實就是把原始數據分成k份,輪流使用其中k-1份作爲訓練數據,而剩餘的1份作爲交叉驗證數據(因此其實對於k-fold cross-validation我們會得到k個accuracy)。以下是5-fold cross-validation的一個示例:

k-fold 交叉驗證

以下是我們使用5-fold cross-validation,取不同的k值時,得到的accuracy曲線(補充一下,因爲是5-fold cross-validation,所以在每個k值上有5個取值,我們取其均值作爲此時的準確度)

5-fold 交叉驗證

可以看出大概在k=7左右有最佳的準確度。

3.2 最近鄰方法的優缺點

K最近鄰的優點大家都看出來了,思路非常簡單清晰,而且完全不需要訓練…不過也正因爲如此,最後的predict過程非常耗時,因爲要和全部訓練集中的圖片比對一遍。

實際應用中,我們其實更加關心實施predict所消耗的時間,如果有一個圖像識別app返回結果要半小時一小時,你一定第一時間把它卸了。我們反倒不那麼在乎訓練時長,訓練時間稍微長一點沒關係,只要最後應用的時候識別速度快效果好,就很贊。後面會提到的深度神經網絡就是這樣,深度神經網絡解決圖像問題時訓練是一個相對耗時間的過程,但是識別的過程非常快。

另外,不得不多說一句的是,優化計算K最近鄰時間問題,實際上依舊到現在都是一個非常熱門的問題。Approximate Nearest Neighbor (ANN)算法是犧牲掉一小部分的準確度,而提高很大程度的速度,能比較快地找到近似的K最近鄰,現在已經有很多這樣的庫,比如說FLANN.

最後,我們用一張圖來說明一下,用圖片像素級別的距離來實現圖像類別識別,有其不足之處,我們用一個叫做t-SNE的技術把CIFAR-10的所有圖片按兩個維度平鋪出來,靠得越近的圖片表示其像素級別的距離越接近。然而我們瞄一眼,發現,其實靠得最近的並不一定是同類別的。

像素級別圖像距離排列

其實觀察一下,你就會發現,像素級別接近的圖片,在整張圖的顏色分佈上,有很大的共性,然而在圖像內容上,有時候也只能無奈地呵呵嗒,畢竟顏色分佈相同的不同物體也是非常多的。

參考資料與原文

cs231n 圖像分類與KNN

作者: 寒小陽 
時間:2015年11月。 
出處:http://blog.csdn.net/han_xiaoyang/article/details/49949535 
聲明:版權所有,轉載請註明出處,謝謝

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章