GitHub項目地址:Mr.Miaow GitHub 如果對你有幫助歡迎
Star
或者Fork
。
機器學習深度學習面試題總結
項目介紹
本項目是優達學城的其中一個納米學位畢業項目 -- 貓狗大戰。
項目要求使用深度學習方法識別一張圖片是貓還是狗
- 輸入:一張彩色圖片
- 輸出:是貓還是狗
注意:本項目代碼只供參考,嚴禁用于其他用途,如果使用請說明出處。
項目概述
本項目使用的卷積神經網絡(Convolutional Neural Network, CNN),卷積神經網絡是深度學習技術中極具代表的網絡結構之一,在圖像處理領域取得了很大的成功,在國際標準的 ImageNet 數據集上,許多成功的模型都是基于 CNN 的。CNN 相較于傳統的圖像處理算法的優點之一在于,避免了對圖像復雜的前期預處理過程(提取人工特征等),可以直接輸入原始圖像。CNN 網絡對圖片進行多次卷基層和池化層處理,在輸出層給出兩個節點并進行 softmax 計算得到兩個類別各自的概率。
本項目最終需要訓練基于 CNN 的機器學習模型,對測試樣本進行分類,并將最終結果上傳 kaggle 進行最終評判。
本項目同時也實現了使用 Keras 和 Flask 搭建部署一個簡單易用的深度學習圖像網頁應用,可以通過網頁導入一張彩色貓或者狗的圖片預測是貓或者狗的概率。
問題稱述
數據集中大部分圖片是正常的,有少部分異常圖片和低分辨率圖片,對于訓練集來說這些異常數據是要剔除掉的。
數據集中的文件名是以 type.num.jpg 方式命名的,比如 cat.0.jpg。使用 Keras 的 ImageDataGenerator 需要將不同種類的圖片分在不同的文件夾中。
數據集中的圖像大小是不固定的,但是神經網絡輸入節點的個數是固定的。所以在將圖像的像素作為輸入之前,需要將圖像的大小進行 resize。
評價指標
對數損失(Log loss)亦被稱為邏輯回歸損失(Logistic regression loss)或交叉熵損失(Cross-entropy loss)。 交叉熵是常用的評價方式之一,它實際上刻畫的是兩個概率分布之間的距離,是分類問題中使用廣泛的一種損失函數。
本文實際上是二分類問題, 因此可以采用 logloss 損失函數作為評價指標, 計算公式如下:
其中:
n 是測試集中圖片數量
是圖片預測為狗的概率
如果圖像是狗,則為1,如果是貓,則為0
是自然(基數
)對數
采用交叉熵作為損失函數可以有效的解決梯度消失和梯度爆炸的問題。
交叉熵損失越小,代表模型的性能越好。上述評估指標可用于評估該項目的解決方案以及基準模型。
II. 分析
數據的探索
下載 kaggle 貓狗數據集解壓后分為 3 個文件 train.zip、 test.zip 和 sample_submission.csv。
train 訓練集包含了 25000 張貓狗的圖片,貓狗各一半,每張圖片包含圖片本身和圖片名。命名規則根據 “type.num.jpg” 方式命名。
test 測試集包含了 12500 張貓狗的圖片,沒有標定是貓還是狗,每張圖片命名規則根據 “num.jpg”,需要注意的是測試集編號從 1 開始,而訓練集的編號從 0 開始。
sample_submission.csv 需要將最終測試集的測試結果寫入.csv 文件中,上傳至 kaggle 進行打分。
從訓練集中隨機提取圖片可視化如下:
訓練集中圖片的尺寸散點分布圖:
測試集中圖片的尺寸散點分布圖:
通過對圖片中的色彩-像素比進行 IQR 分析,可以發現很多分辨率低、無關的圖片,下面是其中一些不合格的圖片:
經過觀察數據,數據集中大部分圖片是正常的,有少部分異常圖片,對于訓練集來說這些異常數據是要剔除掉的。
算法和技術
問題分析
在給定一張圖片,系統需要預測出圖像屬于預先定義類別中的哪一類。在計算機視覺領域,目前解決這類問題的核心技術框架是深度學習(Deep Learning),特別地,針對圖像類型的數據,是深度學習中的卷積神經網絡(Convolutional Neural Networks, ConvNets)架構。常見的卷積神經網絡架構如下:
卷積神經網絡中卷積層和池化層主要是對圖片的幾何特征進行抽取,比如淺層的卷積池化層可以抽取出一些直線,角點等簡單的抽象信息,深層的卷積池化層可以抽取人臉等復雜的抽象信息,最后的全連接層才是對圖片分類的關鍵處理。
因此可以利用已經訓練好的卷積神經網絡提取圖片中復雜的幾何特征,即將原始圖片用已經訓練好的卷積神經網絡處理之后的輸出,作為新的輸入,然后加上自己的全連接層,去進行分類。在模型訓練的過程中,只改變新加的全連接層的權重。
總的來說,卷積神經網絡是一種特殊的神經網絡結構,即通過卷積操作可以實現對圖像特征的自動學習,選取那些有用的視覺特征以最大化圖像分類的準確率。
上圖給出了一個簡單的貓狗識別的卷積神經網絡結構,在最底下(同時也是最大的)的點塊表示的是網絡的輸入層(Input Layer),通常這一層作用是讀入圖像作為網絡的數據輸入。在最上面的點塊是網絡的輸出層(Output Layer),其作用是預測并輸出讀入圖像的類別,在這里由于只需要區分貓和狗,因此輸出層只有 2 個神經計算單元。而位于輸入和輸出層的,都稱之為隱含層(Hidden Layer),圖中有 3 個隱含層,圖像分類的隱含層都是由卷積操作完成的,因此這樣的隱含層也成為卷積層(Convolutional Layer)。因此,輸入層、卷積層、輸出層的結構及其對應的參數就構成了一個典型的卷積神經網絡。
當然,在實際中使用的卷積神經網絡要比這個示例的結構更加復雜,自 2012 年的 ImageNet 比賽起,幾乎每一年都會有新的網絡結構誕生,已經被大家認可的常見網絡有 AlexNet, VGG-Net, GoogLeNet, Inception V2-V4, ResNet 等等。這些卷積神經網絡都是在 ImageNet 數據集上表現非常優異的神經網絡,具體準確率和模型大小如下圖所示。
模型選擇及技術
由于每一種神經網絡提取的特征都不一樣,因此本項目將多個神經網絡處理的結果拼接,作為最后一層全連接層的輸入,這樣做可以有效地降低方差。
本項目遷移學習部分使用 Keras 實現,而 Keras 中可以導入的模型有 Xception,VGG16,VGG19,ResNet50,InceptionV3,InceptionResNet -V2,MobileNet. 綜合考慮模型的分類準確率和大小,選用遷移學習的基礎模型為 ResNet50,InceptionV3 和 Xception。
卷積神經網絡結構演化圖:
ResNet:
ResNet引入了殘差網絡結構(residual network),通過這種殘差網絡結構,可以把網絡層弄的很深(據說目前可以達到 1000 多層),并且最終的分類效果也非常好,殘差網絡的基本結構如下圖所示,很明顯,該圖是帶有跳躍結構的:
殘差網絡借鑒了高速網絡(Highway Network)的跨層鏈接思想,但對其進行改進(殘差項原本是帶權值的,但 ResNet 用恒等映射代替之)。
假定某段神經網絡的輸入是 ,期望輸出是
,即
是期望的復雜潛在映射,如果是要學習這樣的模型,則訓練難度會比較大;回想前面的假設,如果已經學習到較飽和的準確率(或者當發現下層的誤差變大時),那么接下來的學習目標就轉變為恒等映射的學習,也就是使輸入
近似于輸出
,以保持在后面的層次中不會造成精度下降。
在上圖的殘差網絡結構圖中,通過“shortcut connections(捷徑連接)”的方式,直接把輸入傳到輸出作為初始結果,輸出結果為
,當
時,那么
,也就是上面所提到的恒等映射。于是,ResNet 相當于將學習目標改變了,不再是學習一個完整的輸出,而是目標值
和
的差值,也就是所謂的殘差
,因此,后面的訓練目標就是要將殘差結果逼近于
,使到隨著網絡加深,準確率不下降。
這種殘差跳躍式的結構,打破了傳統的神經網絡 層的輸出只能給
層作為輸入的慣例,使某一層的輸出可以直接跨過幾層作為后面某一層的輸入,其意義在于為疊加多層網絡而使得整個學習模型的錯誤率不降反升的難題提供了新的方向。
在此之前,深度神經網絡常常會有梯度消失問題的困擾,即來自誤差函數的梯度信號會在反向傳播回更早的層時指數級地下降。本質上講,在誤差信號反向回到更早的層時,它們會變得非常小以至于網絡無法學習。但是,因為 ResNet 的梯度信號可以直接通過捷徑連接回到更早的層,所以一下子就可以構建 50 層、101 層、152 層甚至 1000 層以上的網絡了,而且它們的表現依然良好。那時候,這在當時最佳的基礎上實現了巨大的飛躍——這個 22 層的網絡贏得了 ILSVRC 2014 挑戰賽。
Inception V3:
Inception 模塊之間特征圖的縮小,主要有下面兩種方式:
右圖是先進行 Inception 操作,再進行池化來下采樣,但是這樣參數量明顯多于左圖(比較方式同前文的降維后 Inception 模塊),因此 v2 采用的是左圖的方式,即在不同的 Inception 之間(35/17/8 的梯度)采用池化來進行下采樣。
但是,左圖這種操作會造成表達瓶頸問題,也就是說特征圖的大小不應該出現急劇的衰減(只經過一層就驟降)。如果出現急劇縮減,將會丟失大量的信息,對模型的訓練造成困難。
因此,在2015年12月提出的 Inception V3結構借鑒 Inception 的結構設計了采用一種并行的降維結構,如下圖:
經過優化后的inception v3網絡與其他網絡識別誤差率對比如表所示:
Inception V3 一個最重要的改進是分解(Factorization),將 7x7
分解成兩個一維的卷積(1x7,7x1)
,3x3
也是一樣(1x3,3x1)
,這樣的好處,既可以加速計算,又可以將 1 個卷積拆成 2 個卷積,使得網絡深度進一步增加,增加了網絡的非線性(每增加一層都要進行 ReLU)。
另外,網絡輸入從 224*224
變為了 299*299
。
Xception:
Xception 實際上采用了類似于 ResNet 的網絡結構,主體部分采用了模塊化設計。如下圖所示:
Xception 是 google 繼 Inception 后提出的對 Inception v3 的另一種改進,主要是采用 depthwise separable convolution 來替換原來Inception v3中的卷積操作。
Xception 取名的由來是 "Extreme Inception",Inception V3 的演進過程:
"極端形式"同 SeparableConv 的區別主要有兩點:
3x3
卷積和1x1
卷積的先后順序。 原來的 Inception 結構是先1x1
卷積,后3x3
卷積。作者認為這里的區別并不重要。兩個卷積層之間是否有激活函數。 原來的 Inception 中間是有 ReLU 激活的。 但實驗結果證明不加激活效果更好。
基準模型
本項目的最低要求是 kaggle Public Leaderboard 前 10%。在 kaggle 上,總共有 1314 只隊伍參加了比賽,所以需要最終的結果排在 131 位之前,131 位的得分是 0.06127,所以目標是模型預測結果要小于 0.06127。
III. 方法
數據預處理
對于異常數據的清理采用 優達學習筆記 提供的“預處理模型”方法實現異常數據清洗。
評價 ImageNet 有指標 Top-1 和 Top-5:
本項目使用 InceptionV3 top-10 訓練train訓練集。訓練過程中將圖片的名稱和預測 top-10 的結果保存到字典里,訓練結束后保存字典為 train_decode_predictions.csv
。將模型預測結果與 ImageNetClasses.csv
進行異常數據排查。具體實現代碼參考 outlier_detection.ipynb
。
使用 InceptionV3 模型排查出的異常圖片總數為:131張,其中有些圖片是正常的,經過篩選后選出 43 張異常圖片作為本試驗要清理的異常圖片。這些異常圖片如下圖:
去除異常數據后數據分布如下:
數據集清洗后,貓的數量:12482,狗的數量:12475,測試集圖片數量:12500。
由于我們的數據集的文件名是以 type.num.jpg 這樣的方式命名的,比如 cat.0.jpg,但是使用 Keras 的 ImageDataGenerator
需要將不同種類的圖片分在不同的文件夾中,因此我們需要對數據集進行預處理。這里我們采取的思路是創建符號鏈接(symbol link),這樣的好處是不用復制一遍圖片,占用不必要的空間。
文件目錄結構如下:
image/
├── test
├── img_test
│ ├── test -> ../test/
├── train
├── img_train
│ ├── cat
│ └── dog
執行過程
生成遷移學習特征向量
Xception,InceptionV3 和 ResNet50 這三個模型對于輸入數據都有各自的默認值,比如在輸入圖片大小維度上,Xception 和 InceptionV3 默認輸入圖片大小是 299*299
,ResNet50 默認輸入圖片大小是 224*224
;在輸入數值維度上,Xception 和 InceptionV3 默認輸入數值在 (-1, 1) 范圍內。當要輸入與默認圖片大小不同的圖片時,只需傳入當前圖片大小即可。ResNet50 需要對圖片進行中心化處理,由于載入的 ResNet50 模型是在 ImageNet 數據上訓練出來的,所以在預處理中每個像素點都要減去 ImageNet 均值。當要輸入與默認圖片大小不同的圖片時,只需傳入當前圖片大小即可。當輸入數值不符合默認要求時,使用每個模型的預處理函數 preprocess_input
即可將輸入圖片處理成該模型的標準輸入。
常見的卷積神經網絡結構在前面的若干層都是卷積池化層及其各種變種,后面幾層都是全連接層,這些全連接層之前的網絡層被稱為瓶頸層 (bottleneck). 將新的圖片通過訓練好的卷積神經網絡直到瓶頸層的過程可以看做是對圖像進行特征提取的過程。一般情況下,為了減少內存的消耗, 加快計算的過程,再將瓶頸層的結果輸入全連接層之前,做一次全局平均 池化,比如 ResNet50 瓶頸層輸出結果是 7*7*2048
,如果直接輸入到全連接層,參數會非常多,所以進行一次全局平均池化,將輸出矩陣調整為 1*1*2048
,這么做還有一個好處,那就是可以降低過擬合的程度。
在 Keras 中載入模型并進行全局平均池化,只需要在載入模型的時候,設置 include_top=False
, pooling='avg'
. 每個模型都將圖片處理成一個 1*2048
的行向量,將這三個行向量進行拼接,得到一個 1*6144
的行向量,作為數據預處理的結果。
def write_gap(MODEL, image_size, lambda_func=None):
width = image_size[0]
height = image_size[1]
input_tensor = Input((height, width, 3))
x = input_tensor
if lambda_func:
x = Lambda(lambda_func)(x)
base_model = MODEL(input_tensor=x, weights='imagenet', include_top=False, pooling='avg')
model = Model(base_model.input, GlobalAveragePooling2D()(base_model.output))
gen = ImageDataGenerator()
train_generator = gen.flow_from_directory("img_train", image_size, shuffle=False,
batch_size=16)
test_generator = gen.flow_from_directory("img_test", image_size, shuffle=False,
batch_size=16, class_mode=None)
train = model.predict_generator(train_generator, train_generator.nb_sample)
test = model.predict_generator(test_generator, test_generator.nb_sample)
with h5py.File("gap_%s.h5"%MODEL.__name__) as h:
h.create_dataset("train", data=train)
h.create_dataset("test", data=test)
h.create_dataset("label", data=train_generator.classes)
載入特征向量
經過上面的代碼以后,我們獲得了三個特征向量文件,分別是:
- gap_ResNet50.h5
- gap_InceptionV3.h5
- gap_Xception.h5
這里需要載入這些特征向量,并且將它們合成一條特征向量,然后記得把 X 和 y 打亂,不然之后設置 validation_split
的時候會出問題。這里設置了 numpy 的隨機數種子為 2018。
np.random.seed(2018)
X_train = []
X_test = []
for filename in ["gap_ResNet50.h5", "gap_Xception.h5", "gap_InceptionV3.h5"]:
with h5py.File(filename, 'r') as h:
X_train.append(np.array(h['train']))
X_test.append(np.array(h['test']))
y_train = np.array(h['label'])
X_train = np.concatenate(X_train, axis=1)
X_test = np.concatenate(X_test, axis=1)
X_train, y_train = shuffle(X_train, y_train)
構建模型
載入預處理的數據之后,先進行一次概率為 0.5 的 dropout,然后直接連接輸出層,激活函數為 Sigmoid,優化器為 Adadelta,輸出一個零維張量,表示某張圖片中有狗的概率。
input_tensor = Input(X_train.shape[1:])
x = input_tensor
x = Dropout(0.5)(x)
x = Dense(1, activation='sigmoid')(x)
model = Model(input_tensor, x)
model.compile(optimizer='adadelta',
loss='binary_crossentropy',
metrics=['accuracy'])
整個遷移學習的神經網格結構如下所示:
訓練模型
模型構件好了以后,我們就可以進行訓練了,這里我們設置驗證集大小為 20% ,也就是說訓練集是 19964 張圖,驗證集是 4991 張圖。
model_history = model.fit(X_train,
y_train,
batch_size=128,
nb_epoch=8,
verbose=1,
validation_split=0.2,
callbacks = [TensorBoard(log_dir='./Graph')])
model.save('model.h5')
保存模型為:model.h5
完善
本項目通過對圖片中的色彩-像素比進行 IQR 分析,經人工篩選后剔除了分辨率低、無關的圖片,這種方法并不能把所有的異常圖片都剔除掉。比如還有一些圖片被別的物體遮擋,對訓練造成干擾等等。后期可以考慮多方面對圖片做篩選處理
圖片中貓狗的拍攝角度不盡相同,而且貓狗占整張圖片的比例也有所差別。為了讓模型盡量不受這些因素的干擾,增強模型的泛化能力,需要對原始圖片進行一些隨機操作,比如旋轉、剪切變換、縮放、水平翻轉等。Keras 提供的圖片生成器
ImageDataGenerator
可以很方便地對圖片進行提升對模型參數的優化,通過不同的優化器的嘗試得到更好的優化結果
IV. 結果
模型的評價與驗證
我們可以看到,訓練的過程很快,十秒以內就能訓練完,準確率也很高,在驗證集上最高達到了99.4%的準確率,這相當于一千張圖只錯了6張,可以說比我還厲害。
訓練過中的 loss 和 accuracy 如下:
將測試集的處理結果提交到kaggle上,loss為0.04150,和驗證集的loss近似。
合理性分析
單個 ResNet50 模型 5 次迭代訓練結果:
- 訓練集loss:0.0755,驗證集loss:0.0419。
使用 Xception,InceptionV3 和 ResNet50 這三個模型進行遷移學習 5 次迭代訓練結果:
- 訓練集loss:0.0152,驗證集loss:0.0170。
原始的 Xception 有 126 層,原始的 InceptionV3 有 159 層,原始的 ResNet50 有 168 層,更多的層數,不同的卷積核各種各樣的的組合,可以更好的抽取圖片中的泛化特征,這樣既可以提高分類的準確率,又可以降低模型的過擬合風險,所以現在在各種比賽中斬頭露角的神經網絡層數都非常的多,深度很深,這也是類似的神經網絡被稱為深度學習的一個主要原因。
Xception,InceptionV3 和 ResNet50 這三個模型進行組合遷移學習,效果比先單個神經網絡模型效果好。這里利用了 bagging 的思想,通過多個模型處理數據并進行組合,可以有效降低模型的方差,減少過擬合程度,提高分類準確率。
V. 項目結論
結果可視化
下圖是使用優化之前的 ResNet50 模型進行預測的結果展示,可以看到都是正確的結果,只是精度達不到項目要求。使用了知乎上優達學城官方方法進行了實現,完全能達到項目的要求。
對項目的思考
深度學習毫無疑問是處理圖像問題最佳的機器模型,近年來各大賽的前幾名均是通過深度學習獲取了前幾名的好成績。但是相比于傳統的機器學習模型,深度學習需要更多的數據,更強大的算力和資源。本項目在訓練遷移學習模型使用的是自己的 Mac,足足跑了三天訓練完,淚崩。建議在使用的時候用云計算平臺去訓練。
kaggle 上貓狗大戰前幾名的 loss 達到了 0.0330,相比于本文中的 0.0415,絕對值減少了 0.0085,說明還是有較大的改進空間。本文只是使用的 Xception,InceptionV3 和 ResNet50 這三個模型進行了提取特征向量,然后將特征向量直接拼接,忽略了特征之間的位置關系。除了這三個模型,還可以增加更多新的模型,或者使用stacking的方法進行模型融合,進一步降低方差,提高分類的準確率。還可以從原始樣本上入手,有些圖片的分類概率正確,但是不夠確定,可以先做一部分處理,然后讓模型更加確定。
需要作出的改進
相比 Keras,TensorFlow 真的真的太麻煩了,但是 Google 為什么要把 TensorFlow 做的這么麻煩呢?個人認為是為了運行的高效率和極大的靈活性做出了讓步。TensorFlow 是經過工業界實際產品考驗過的框架,生態及其豐富,想實現一個功能,有多種寫法,學起來有一種 C++ 的感覺,Keras 更像是為了快速出活的框架,如果想做一些改動十分的麻煩,Keras 的默認 backend 就是 TensorFlow,所以 Keras 的執行效率是比 TensorFlow 慢很多的。TensorFlow1.4 里面已經把 tf.contrib.keras
更改為核心模塊 tf.keras
,所以 TensorFlow 以后用起來應該也會方便很多。最后還想說的是 PyTorch,好多人都推薦,說不僅僅有 TensorFlow 的高效率,而且很 pythonic,可以在任意層和 numpy 數組進行轉化。
項目部署
項目使用 Keras 和 Flask 搭建部署一個簡單易用的深度學習圖像網頁應用,可以通過網頁導入一張彩色貓或者狗的圖片預測是貓或者狗的概率。
項目目錄結構:
.
├── README.md
├── ResNet50_image_predict.ipynb
├── app.py
├── environmert.yml
├── static
│ ├── css
│ │ └── main.css
│ └── js
│ └── main.js
├── templates
│ ├── base.html
│ └── index.html
├── models
│ └── ResNet50_catdog_model.h5
├── uploads
│ ├── test01.jpg
│ └── test02.jpg
└── webapp_image_predict.ipynb
環境搭建
$ conda env create -f environmert.yml
運行
$ python app.py
這時候用瀏覽器打開 http://localhost:5000/ 就可以進行網頁導入圖片預測圖片是狗的概率了。
如果不想搭建環境復現實驗結果,可以按照以下操作分分鐘復現實驗結果:
$ docker pull miaowmiaow/webapp:1.1.0
$ docker run -p 5000:5000 miaowmiaow/webapp:1.1.0
到此就可以在瀏覽器中輸入 http://localhost:5000 就可以使用網頁對導入的貓狗圖片做預測了。
下圖為預測的效果圖:
VI. 參考文獻
[1] K. He, X. Zhang, S. Ren, and J. Sun. Deep residual learning for image recognition. arXiv preprint arXiv:1512.03385, 2015.
[2] Christian Szegedy, Wei Liu, Yangqing Jia. Going Deeper with Convolutions arXiv:1409.4842, 2014
[3] Fran?ois Chollet. Xception: Deep Learning with Depthwise Separable Convolutions arXiv:1610.02357, 2016
[5] Karen Simonyan and Andrew Zisserman. VERY DEEP CONVOLUTIONAL NETWORKS FOR LARGE- SCALE IMAGE RECOGNITION. At ICLR,2015.
[6] K. Simonyan and A. Zisserman. Very deep convolutional networks for large-scale image recognition. In ICLR, 2015.
[7] Building powerful image classification models using very little data.
[8] Dogs vs. Cats: Image Classification with Deep Learning using TensorFlow in Python.
[9] ImageNet: VGGNet, ResNet, Inception, and Xception with Keras.
[10] The residual module in ResNet as originally proposed by He et al. in 2015.
[11] An Analysis of Deep Neural Network Models for Practical Applications. arXiv:1605.07678, 2017.