cs231n學習筆記——圖像分類

寫在前面的廢話

在機器學習和深度學習的道路上一直在當混子,喪了很久決定。決定從新出發,決定好好學習下經典的cs231n 2017的課程(2019英語水平的原因暫時啃不動),寫博客可能更多的原因並不是想分享給別人看的,而是寫給自己的,算是學後總結吧。在這裏還是要感謝爲cs231n課程做了精心翻譯的人。在下面附上鍊接,我也是在看了視頻後,又反過來看他做的翻譯筆記重新梳理複習的。接下來的順序也是根據課程講解的知識順序進行整理的,一些自己的理解和原來錯誤的認識我也會寫出來的。

CS231n課程筆記翻譯:https://zhuanlan.zhihu.com/p/20894041?refer=intelligentunit

1.圖像分類

其實圖像分類的原理更像是再教小孩子,你拿很多的球(也可以是別的)到孩子面前,告訴他教他哪些是籃球、哪些是足球、哪些是網球等等。之後呢,你又拿了一個其他的球,讓孩子來通過剛纔學習的經驗分辨這個是什麼球。圖像分類就是這樣:就是用已有固定的分類標籤集合,然後對於輸入的圖像,從分類標籤集合中找出一個分類標籤,最後把分類標籤分配給該輸入圖像。

下面來舉個例子:首先科普一下,我們計算機中打開一張彩色圖片,寬248像素,高400像素。這個圖片包含了三個維度的信息,長x寬x3(RGB這個3代表的是紅、綠和藍3個顏色通道)。所以當我們寫代碼將一張圖片加載後就形成了一個248X400X3=297600個元素的數組。每個元素的範圍是0~255,因爲每個像素點顏色範圍不會超過255,0代表黑,255代表白。就是下面這個樣子。
在這裏插入圖片描述
這個打開後看到的這堆亂七八糟的數字。舉個類似的例子,其實就是像生物的DNA,現在圖像分類要做的就是根據這些DNA來分辨,打開的這個圖片是什麼物種。識別的難點我就不說了,人有分不清的時候機器就更分不清了:例如角度變化、光線、變形等等。

2.數據驅動

課程講到數據驅動的時候,我明顯感覺眼前一亮,一個很恰當的詞語。理解上講,就是以往我們在做算法實現去識別某一個事物的時候,都是根據這個東西的特性量身去定製。但是我們覺得這樣很麻煩,我們希望我給你什麼東西,你就能自己根據我給你的變化識別。還是拿孩子的例子,就是我給你什麼你就根據我給你的東西去學習分辨,你能夠學到什麼完全是由我給你的東西決定的。數據驅動就是這個道理。用數據驅動填充模型,也決定了模型能夠識別什麼東西。

3.圖形分類流程

輸入:輸入N個圖片的集合,其中每張圖片標籤(類別)是已知的。這個集合叫做訓練集。
訓練:訓練其實就是學習一個分類器,使用輸入的訓練集來學習如何判斷各個分類。
預測與評價:給定一張沒見過的圖片,讓模型來預測是哪個分類標籤。然後根據它分類的正確與否我們來評價這個分類器的質量。

4.L1距離(曼哈頓距離)

在這裏插入圖片描述
曼哈頓距離其實是一種座標距離,表示在標準座標軸上的軸距絕對值。在模型之間使用曼哈頓距離往往樣本具有某些重要意義的特徵值,更適合用到這個距離,例如1個人的工資條,其中有工齡、收入、職位等等。每個特徵都代表了這個人的某種身份,而計算2個人之間的樣本距離,曼哈頓是比較合適的。下面是計算公式:
d(I1,I2)=pI1PI2P d(I_1,I_2)=\sum_{p}|I_1^P-I_2^P|
在這裏插入圖片描述
可以看得出L1距離取決於你選擇的座標系,一旦你轉動座標系,兩點之間的距離就會隨之發生變化。而相比L2距離就不存在這個問題,你可以隨意轉動座標軸,兩點的距離都是確定的。

5.L2距離(歐氏距離)

歐式距離我就不多解釋了,初中就學過了。
d(I1,I2)=P(I1PI2P)2d(I_1,I_2)=\sqrt{\sum_{P}(I_1^P-I_2^P)^2}
在這裏插入圖片描述
這裏比較有趣的就是模型選擇到底用哪個距離進行計算,這就要看實際情況來進行取捨了。兩個距離都不存在誰更好的問題,如果把握不準的話就都試一試。

6. Nearest Neighbor分類器

首先我先講一下這個近鄰(Nearest Neighbor)算法的優缺點,下面的K近鄰同樣適用這個優缺點。
優點:

  • 簡單,無需預訓練
  • 無需調參

缺點:

  • 需要更多的訓練數據才能改善模型,而且可能導致維度災難。
  • 計算成本高,需要計算到每個樣本之間的距離。

這個模型很是辣雞,就是因爲它比較簡單,更適合教學講解。而且幾乎不會再實際中使用,爲什麼呢?而與它相反的例子是:卷積神經網絡,在一般爲用戶提供使用的模型都是訓練好的,所以往往公司會花大量的時間去提前訓練好模型,在使用過程中直接進行分類即可速度非常快,不會佔用用戶太多空間和時間。而NN模型所謂的訓練其實就是把所有的數據保存起來,進行預測的時候,要實時化大量時間去計算,這樣的方式簡直是不可接受的。下面在介紹模型前,先介紹兩個相關概念,因爲之後模型會用到,而且這兩個都是可以根據實際情況搭配使用的。
———————————————————————————

1.CIFAR-10數據集說明

這裏在驗證Nearest Neighbor分類器的時候使用了CIFAR-10數據集。
CIFAR-10:CIFAR-10數據集包含10個類別的60000個32x32彩色圖像,有50000張訓練圖像和10000張測試圖像。這個數據集包含了10種分類標籤,每個類別6000個圖像。下圖中你可以看見10個類的10張隨機圖片。
在這裏插入圖片描述
左邊:從CIFAR-10數據庫中的樣本圖像。右邊:第一列是輸入模型的測試圖像,然後第一列的每個測試圖像右邊是使用Nearest Neighbor算法,從訓練集中選出的10張最類似的圖片。由於圖不是很清楚就直接說,上面10個分類中,只有3個是準確的。

那麼如何比較距離呢?就是將每張圖片轉化成向量(張量)然後使用L1距離進行運算。向量中的每個數值做減法。
在這裏插入圖片描述
得到的456就是,兩張圖片的距離,差別越大數值越大。如果數值爲0那麼就可以認爲這兩張圖片是一模一樣的了。
———————————————————————————
大致講一下過程,首先我們把50000張的訓練集,拿給Nearest Neighbor分類器進行訓練(其實就是存起來了),之後用10000張的測試集,去挨個和訓練集進行距離比較。然後每張測試圖片會把,存儲訓練集的所有圖片中和自己距離最小的那張的標籤,賦給自己,這就是預測的標籤。
———————————————————————————

2.NN代碼實現

下面代碼實現和講解部分,課程筆記講的很清楚,我就直接給考過來了。
下面,讓我們看看如何用代碼來實現這個分類器。首先,我們將CIFAR-10的數據加載到內存中,並分成4個數組:訓練數據和標籤,測試數據和標籤。在下面的代碼中,Xtr(大小是50000x32x32x3)存有訓練集中所有的圖像,Ytr是對應的長度爲50000的1維數組,存有圖像對應的分類標籤(從0到9):

# CIFAR10數據讀取這部分代碼課程上沒有,我在這裏補充好了。
def load_CIFAR10(path):
	# path = './cifar-10-batches-py/'
    Xtr, Ytr, Xte, Yte = [], [], [], []
	
    for i in range(1, 6):
        filename = os.path.join(path, 'data_batch_%d' % i)
        with open(filename, 'rb') as f:
            data = pickle.load(f, encoding='latin1') # 讀取訓練集
            Xtr.extend(data['data'])
            Ytr.extend(data['labels'])

    with open(os.path.join(path, 'test_batch'), 'rb') as f:
        data = pickle.load(f, encoding='latin1') # 讀取測試集
        Xte.extend(data['data'])
        Yte.extend(data['labels'])
    return np.array(Xtr), np.array(Ytr), np.array(Xte), np.array(Yte)
    
Xtr, Ytr, Xte, Yte = load_CIFAR10('data/cifar10/') # a magic function we provide
# flatten out all images to be one-dimensional
Xtr_rows = Xtr.reshape(Xtr.shape[0], 32 * 32 * 3) # Xtr_rows becomes 50000 x 3072
Xte_rows = Xte.reshape(Xte.shape[0], 32 * 32 * 3) # Xte_rows becomes 10000 x 3072

現在我們得到所有的圖像數據,並且把他們拉長成爲行向量了。接下來展示如何訓練並評價一個分類器:

nn = NearestNeighbor() # create a Nearest Neighbor classifier class
nn.train(Xtr_rows, Ytr) # train the classifier on the training images and labels
Yte_predict = nn.predict(Xte_rows) # predict labels on the test images

# and now print the classification accuracy, which is the average number
# of examples that are correctly predicted (i.e. label matches)
print 'accuracy: %f' % ( np.mean(Yte_predict == Yte) )

作爲評價標準,我們常常使用準確率,它描述了我們預測正確的得分。請注意以後我們實現的所有分類器都需要有這個API:train(X, y)函數。該函數使用訓練集的數據和標籤來進行訓練。從其內部來看,類應該實現一些關於標籤和標籤如何被預測的模型。這裏還有個predict(X)函數,它的作用是預測輸入的新數據的分類標籤。現在還沒介紹分類器的實現,下面就是使用L1距離的Nearest Neighbor分類器的實現套路:

import numpy as np
import pickle
import os

class NearestNeighbor(object):
  def __init__(self):
    pass

  def train(self, X, y):
    """ X is N x D where each row is an example. Y is 1-dimension of size N """
    # the nearest neighbor classifier simply remembers all the training data
    self.Xtr = X
    self.ytr = y

  def predict(self, X):
    """ X is N x D where each row is an example we wish to predict label for """
    num_test = X.shape[0]
    # lets make sure that the output type matches the input type
    Ypred = np.zeros(num_test, dtype = self.ytr.dtype)

    # loop over all test rows
    for i in xrange(num_test):
      # find the nearest training image to the i'th test image
      # using the L1 distance (sum of absolute value differences)
      distances = np.sum(np.abs(self.Xtr - X[i,:]), axis = 1)
      min_index = np.argmin(distances) # get the index with smallest distance
      Ypred[i] = self.ytr[min_index] # predict the label of the nearest example
    
    return Ypred

如果你用這段代碼跑CIFAR-10,你會發現準確率能達到38.6%。這比隨機猜測的10%要好,但是比人類識別的水平(據研究推測是94%)和卷積神經網絡能達到的95%還是差多了。點擊查看基於CIFAR-10數據的Kaggle算法競賽排行榜。

**距離選擇:**計算向量間的距離有很多種方法,另一個常用的方法是L2距離,從幾何學的角度,可以理解爲它在計算兩個向量間的歐式距離。公式在上面已經介紹過了。
換句話說,我們依舊是在計算像素間的差值,只是先求其平方,然後把這些平方全部加起來,最後對這個和開方。在Numpy中,我們只需要替換上面代碼中的1行代碼就行:

distances = np.sqrt(np.sum(np.square(self.Xtr - X[i,:]), axis = 1))

注意在這裏使用了np.sqrt,開根號其實可有可無,因爲他是個單調函數,如果開根號雖然數值變了但是整體都變大了,但是排列分佈還會保持一致。

L1和L2比較。比較這兩個度量方式是挺有意思的。在面對兩個向量之間的差異時,L2比L1更加不能容忍這些差異。也就是說,相對於1個巨大的差異,L2距離更傾向於接受多箇中等程度的差異。L1和L2都是在p-norm常用的特殊形式

7. K Nearest Neighbor分類器

Nearest Neighbor分類器如何在進行優化呢?可以看出Nearest Neighbor分類器在進行標籤判別的時候是尋找離自己距離最近的一張圖片,那麼如果選擇多張圖片是不能能夠帶來更大的泛化能力呢?K Nearest Neighbor分類器就很自然的被想到了。道理很簡單,將距離自己一張圖片的判別換成通過離自己最近的K張圖片,然後再通過多數投票的方式產生,標籤這樣能帶來更好的泛化能力。
在這裏插入圖片描述
最左邊的是原始數據的分佈,中間的是Nearest Neighbor分類器的效果圖,最右邊的是5-Nearest Neighbor分類器的效果圖。數據包括3類(紅綠藍)不同的顏色代表不同的決策邊界。可以看得出5NN的邊界效果更加的圓滑,而且NN分類器產生的不正確的數據孤島(藍色區域裏包裹的綠色區域),5NN對它做了平滑,能夠更加包容異常點,帶來了更好的泛化能力。而5NN的白色區域代表着模糊的決策區域,例如投票的分類綠色2個,紅色2個,藍色1個。

1.確定K的值(超參數)

超參數簡單理解就是,需要我們進行調優不斷嘗試變換的參數或者是不同函數例如:L1、L2距離。那麼KNN中的K其實就是超參數,需要我們進行調優尋找最合適的值。嗯!機器學習和深度學習其實就是在既定的模型下不斷地調超參數

這個過程肯定是要經歷不斷地嘗試的。但是切記!!==不要使用測試集進行調優。==如果使用測試集進行調優的話,可能算法效果看起來不錯,但是實際部署會效果很差。這樣的方式其實就等於你把測試集當做了,訓練集。通過測試集訓練出來的模型使用測試集進行測試自然效果很好,但是實際上就可能過擬合。

測試數據集只使用一次,即在訓練完成後評價最終的模型時使用。

爲了避免這種情況,我們又設置了驗證集。(其實一開始我自己看書的過程里根本分不清什麼是驗證集和測試集)但是在課程裏講的非常的清楚。測試集就是最後模型完成後,只使用一次進行分類器質量測試使用。超參數的調優後的測試都通過驗證集來完成。

2.KNN實現

還是上面NN的那段代碼,以CIFAR-10爲例,我們可以用49000個圖像作爲訓練集,用1000個圖像作爲驗證集,下面就是劃分驗證集和KNN實現代碼:

import numpy as np
import pickle
import os
from collections import Counter

def load_CIFAR10(path):
    Xtr, Ytr, Xte, Yte = [], [], [], []

    for i in range(1, 6):
        filename = os.path.join(path, 'data_batch_%d' % i)
        with open(filename, 'rb') as f:
            data = pickle.load(f, encoding='latin1')
            Xtr.extend(data['data'])
            Ytr.extend(data['labels'])

    with open(os.path.join(path, 'test_batch'), 'rb') as f:
        data = pickle.load(f, encoding='latin1')
        Xte.extend(data['data'])
        Yte.extend(data['labels'])
    return np.array(Xtr), np.array(Ytr), np.array(Xte), np.array(Yte)
    

class NearestNeighbor(object):
    def __init__(self):
        pass

    def train(self, X, y):
        self.Xtr = X
        self.ytr = y

    def predict(self, X, k=1):
        num_test = X.shape[0]
        Ypred = np.zeros(num_test, dtype=self.ytr.dtype)
        Ypre_k = np.zeros(k, dtype=self.ytr.dtype)

        for i in range(num_test):
            distance = np.sum(np.abs(self.Xtr - X[i, :]), axis= 1)
            min_dis = np.sort(distance)[:k] # 獲取前K個最小的值的索引
            for j in range(k):
                Ypre_k[j] = self.ytr[min_index[j]] # 直接獲取標籤
            
            Ypred[i] = Counter(Ypre_k).most_common(1)[0][0] # 對標籤進行統計並取出第一個頻率最高的標籤

        return Ypred   

nn = NearestNeighbor()
Xtr, ytr, Xte, yte = load_CIFAR10(r'C:\Users\Mango\Desktop\cifar-10-batches-py')
Xtr_rows = Xtr.reshape(Xtr.shape[0], 32*32*3)
Xte_rows = Xte.reshape(Xte.shape[0], 32*32*3)
Xval_row = Xtr_rows[:1000, :]
Yval = ytr[:1000]
Xtr_rows = Xtr_rows[1000:, :]
ytr = ytr[1000:]
vaildation_accuracies = []

for k in [1, 3, 5, 10, 20, 50]:
    nn.train(Xte_rows, ytr)
    Yte_predict = nn.predict(Xval_row, k = k)
    acc = np.mean(Yte_predict == ytr)
    print("accuracy: %f" % acc)
    vaildation_accuracies.append((k, acc))

最後我們會繪出分析圖,來分析那個K值表現最好。然後再用該K值跑測試集,來得到該分類器真正的性能。下面還是貼出那句重要的話。

把訓練集分成訓練集和驗證集。使用驗證集來對所有超參數調優。最後只在測試集上跑一次並報告結果。

3.交叉驗證

一般當數據集較小的情況下,爲了能夠充分的利用數據集。我們將數據集分成5份,其中4份是訓練集,一份是驗證集,然後循環着其中4份做訓練集使用,另一份做驗證集使用。最後取五次的平均作爲結果。下面是交叉驗證下繪製的分析圖。
在這裏插入圖片描述
上圖就是交叉驗證5份的K值調優圖。X軸代表着在調優過程中K值的變化,Y軸代表着準確率。可以看出K=7的時候性能最佳。
在這裏插入圖片描述
交叉驗證一般被分割成1份、3份、10份,常用的分割模式,大概是使用數據集50%~90%。然後訓練集會被均分。圖上綠色的代表訓練集,黃色的是驗證集,紅色的是測試集。如果是交叉驗證那麼久循環着使用訓練集和驗證集。

8.小結

NN在圖像分類中幾乎不會應用。因爲NN分類器在數據維度較低的情況下效果不錯,而圖像往往具有較高的維度。而高維度之間的距離通常是反直覺的(意思就是跟你常識不一樣)例如下圖:
在這裏插入圖片描述
右邊幾張圖片與最左邊的原始圖片L2距離是一樣的,所以感官上的感覺往往與像素距離是不同的。這也就是爲什麼NN分類器比較辣雞的原因。

發佈了9 篇原創文章 · 獲贊 0 · 訪問量 2712
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章