1. 程式人生 > >機器學習工程師 - Udacity 專案:實現一個狗品種識別演算法App

機器學習工程師 - Udacity 專案:實現一個狗品種識別演算法App

在這個notebook中,你將邁出第一步,來開發可以作為移動端或 Web應用程式一部分的演算法。在這個專案的最後,你的程式將能夠把使用者提供的任何一個影象作為輸入。如果可以從影象中檢測到一隻狗,它會輸出對狗品種的預測。如果影象中是一個人臉,它會預測一個與其最相似的狗的種類。下面這張圖展示了完成專案後可能的輸出結果。(……實際上我們希望每個學生的輸出結果不相同!)

Sample Dog Output

在現實世界中,你需要拼湊一系列的模型來完成不同的任務;舉個例子,用來預測狗種類的演算法會與預測人類的演算法不同。在做專案的過程中,你可能會遇到不少失敗的預測,因為並不存在完美的演算法和模型。你最終提交的不完美的解決方案也一定會給你帶來一個有趣的學習經驗!

專案內容

我們將這個notebook分為不同的步驟,你可以使用下面的連結來瀏覽此notebook。

  • Step 0: 匯入資料集
  • Step 1: 檢測人臉
  • Step 2: 檢測狗狗
  • Step 3: 從頭建立一個CNN來分類狗品種
  • Step 4: 使用一個CNN來區分狗的品種(使用遷移學習)
  • Step 5: 建立一個CNN來分類狗的品種(使用遷移學習)
  • Step 6: 完成你的演算法
  • Step 7: 測試你的演算法

在該專案中包含了如下的問題:


 

步驟 0: 匯入資料集

匯入狗資料集

在下方的程式碼單元(cell)中,我們匯入了一個狗影象的資料集。我們使用 scikit-learn 庫中的 load_files 函式來獲取一些變數:

  • train_filesvalid_filestest_files - 包含影象的檔案路徑的numpy陣列
  • train_targetsvalid_targetstest_targets - 包含獨熱編碼分類標籤的numpy陣列
  • dog_names - 由字串構成的與標籤相對應的狗的種類
In [2]:            
from sklearn.datasets import load_files 
from keras.utils import np_utils
import numpy as np
from glob import glob
# define function to load train, test, and validation datasets
def load_dataset(path):
    data = load_files(path)
    dog_files = np.array(data['filenames'])
    dog_targets = np_utils.to_categorical(np.array(data['target']), 133)
    return dog_files, dog_targets
# load train, test, and validation datasets
train_files, train_targets = load_dataset('/data/dog_images/train')
valid_files, valid_targets = load_dataset('/data/dog_images/valid')
test_files, test_targets = load_dataset('/data/dog_images/test')
# load list of dog names
dog_names = [item[20:-1] for item in sorted(glob("/data/dog_images/train/*/"))]
# print statistics about the dataset
print('There are %d total dog categories.' % len(dog_names))
print('There are %s total dog images.\n' % len(np.hstack([train_files, valid_files, test_files])))
print('There are %d training dog images.' % len(train_files))
print('There are %d validation dog images.' % len(valid_files))
print('There are %d test dog images.'% len(test_files))
     
Using TensorFlow backend.
 
There are 133 total dog categories.
There are 8351 total dog images.

There are 6680 training dog images.
There are 835 validation dog images.
There are 836 test dog images.
 

匯入人臉資料集

在下方的程式碼單元中,我們匯入人臉影象資料集,檔案所在路徑儲存在名為 human_files 的 numpy 陣列。

In [3]:          
import random
random.seed(8675309)
# 載入打亂後的人臉資料集的檔名
human_files = np.array(glob("/data/lfw/*/*"))
random.shuffle(human_files)
# 列印資料集的資料量
print('There are %d total human images.' % len(human_files))
     
There are 13233 total human images.
 

 

步驟1:檢測人臉

我們將使用 OpenCV 中的 Haar feature-based cascade classifiers 來檢測影象中的人臉。OpenCV 提供了很多預訓練的人臉檢測模型,它們以XML檔案儲存在 github。我們已經下載了其中一個檢測模型,並且把它儲存在 haarcascades 的目錄中。

在如下程式碼單元中,我們將演示如何使用這個檢測模型在樣本影象中找到人臉。

In [4]:          
import cv2                
import matplotlib.pyplot as plt 
%matplotlib inline                               
# 提取預訓練的人臉檢測模型
face_cascade = cv2.CascadeClassifier('haarcascades/haarcascade_frontalface_alt.xml')
# 載入彩色(通道順序為BGR)影象
img = cv2.imread(human_files[3])
# 將BGR影象進行灰度處理
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 在影象中找出臉
faces = face_cascade.detectMultiScale(gray)
# 列印影象中檢測到的臉的個數
print('Number of faces detected:', len(faces))
# 獲取每一個所檢測到的臉的識別框
for (x,y,w,h) in faces:
    # 在人臉影象中繪製出識別框
    cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)
 
           
# 將BGR影象轉變為RGB影象以列印
cv_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 展示含有識別框的影象
plt.imshow(cv_rgb)
plt.show()
     
Number of faces detected: 1
   

在使用任何一個檢測模型之前,將影象轉換為灰度圖是常用過程。detectMultiScale 函式使用儲存在 face_cascade 中的的資料,對輸入的灰度影象進行分類。

在上方的程式碼中,faces 以 numpy 陣列的形式,儲存了識別到的面部資訊。它其中每一行表示一個被檢測到的臉,該資料包括如下四個資訊:前兩個元素 xy 代表識別框左上角的 x 和 y 座標(參照上圖,注意 y 座標的方向和我們預設的方向不同);後兩個元素代表識別框在 x 和 y 軸兩個方向延伸的長度 w 和 d

寫一個人臉識別器

我們可以將這個程式封裝為一個函式。該函式的輸入為人臉影象的路徑,當影象中包含人臉時,該函式返回 True,反之返回 False。該函式定義如下所示。

In [5]:          
# 如果img_path路徑表示的影象檢測到了臉,返回"True" 
def face_detector(img_path):
    img = cv2.imread(img_path)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    faces = face_cascade.detectMultiScale(gray)
    return len(faces) > 0
     

【練習】 評估人臉檢測模型

 

 

問題 1:

在下方的程式碼塊中,使用 face_detector 函式,計算:

  • human_files 的前100張影象中,能夠檢測到人臉的影象佔比多少?
  • dog_files 的前100張影象中,能夠檢測到人臉的影象佔比多少?

理想情況下,人影象中檢測到人臉的概率應當為100%,而狗影象中檢測到人臉的概率應該為0%。你會發現我們的演算法並非完美,但結果仍然是可以接受的。我們從每個資料集中提取前100個影象的檔案路徑,並將它們儲存在human_files_shortdog_files_short中。

In [6]:          
human_files_short = human_files[:100]
dog_files_short = train_files[:100]
## 請不要修改上方程式碼
## TODO: 基於human_files_short和dog_files_short
## 中的影象測試face_detector的表現
print(np.mean([face_detector(human) for human in human_files_short]))
print(np.mean([face_detector(dog) for dog in dog_files_short]))
     
1.0
0.11
 

 

問題 2:

就演算法而言,該演算法成功與否的關鍵在於,使用者能否提供含有清晰面部特徵的人臉影象。 那麼你認為,這樣的要求在實際使用中對使用者合理嗎?如果你覺得不合理,你能否想到一個方法,即使影象中並沒有清晰的面部特徵,也能夠檢測到人臉?

回答: 不合理;使用CNN;

 

 

選做:

我們建議在你的演算法中使用opencv的人臉檢測模型去檢測人類影象,不過你可以自由地探索其他的方法,尤其是嘗試使用深度學習來解決它:)。請用下方的程式碼單元來設計和測試你的面部監測演算法。如果你決定完成這個選做任務,你需要報告演算法在每一個數據集上的表現。

In [7]:          
## (選做) TODO: 報告另一個面部檢測演算法在LFW資料集上的表現
### 你可以隨意使用所需的程式碼單元數
     

 

步驟 2: 檢測狗狗

在這個部分中,我們使用預訓練的 ResNet-50 模型去檢測影象中的狗。下方的第一行程式碼就是下載了 ResNet-50 模型的網路結構引數,以及基於 ImageNet 資料集的預訓練權重。

ImageNet 這目前一個非常流行的資料集,常被用來測試影象分類等計算機視覺任務相關的演算法。它包含超過一千萬個 URL,每一個都連結到 1000 categories 中所對應的一個物體的影象。任給輸入一個影象,該 ResNet-50 模型會返回一個對影象中物體的預測結果。

In [8]:          
from keras.applications.resnet50 import ResNet50
# 定義ResNet50模型
ResNet50_model = ResNet50(weights='imagenet')
     
Downloading data from https://github.com/fchollet/deep-learning-models/releases/download/v0.2/resnet50_weights_tf_dim_ordering_tf_kernels.h5
102858752/102853048 [==============================] - 16s 0us/step
 

資料預處理

  • 在使用 TensorFlow 作為後端的時候,在 Keras 中,CNN 的輸入是一個4維陣列(也被稱作4維張量),它的各維度尺寸為 (nb_samples, rows, columns, channels)。其中 nb_samples 表示影象(或者樣本)的總數,rowscolumns, 和 channels 分別表示影象的行數、列數和通道數。
  • 下方的 path_to_tensor 函式實現如下將彩色影象的字串型的檔案路徑作為輸入,返回一個4維張量,作為 Keras CNN 輸入。因為我們的輸入影象是彩色影象,因此它們具有三個通道( channels 為 3)。
    1. 該函式首先讀取一張影象,然後將其縮放為 224×224 的影象。
    2. 隨後,該影象被調整為具有4個維度的張量。
    3. 對於任一輸入影象,最後返回的張量的維度是:(1, 224, 224, 3)
  • paths_to_tensor 函式將影象路徑的字串組成的 numpy 陣列作為輸入,並返回一個4維張量,各維度尺寸為 (nb_samples, 224, 224, 3)。 在這裡,nb_samples是提供的影象路徑的資料中的樣本數量或影象數量。你也可以將 nb_samples 理解為資料集中3維張量的個數(每個3維張量表示一個不同的影象。
In [9]:          
from keras.preprocessing import image 
from tqdm import tqdm
def path_to_tensor(img_path):
    # 用PIL載入RGB影象為PIL.Image.Image型別
    img = image.load_img(img_path, target_size=(224, 224))
    # 將PIL.Image.Image型別轉化為格式為(224, 224, 3)的3維張量
    x = image.img_to_array(img)
    # 將3維張量轉化為格式為(1, 224, 224, 3)的4維張量並返回
    return np.expand_dims(x, axis=0)
def paths_to_tensor(img_paths):
    list_of_tensors = [path_to_tensor(img_path) for img_path in tqdm(img_paths)]
    return np.vstack(list_of_tensors)
     

基於 ResNet-50 架構進行預測

對於通過上述步驟得到的四維張量,在把它們輸入到 ResNet-50 網路、或 Keras 中其他類似的預訓練模型之前,還需要進行一些額外的處理:

  1. 首先,這些影象的通道順序為 RGB,我們需要重排他們的通道順序為 BGR。
  2. 其次,預訓練模型的輸入都進行了額外的歸一化過程。因此我們在這裡也要對這些張量進行歸一化,即對所有影象所有畫素都減去畫素均值 [103.939, 116.779, 123.68](以 RGB 模式表示,根據所有的 ImageNet 影象算出)。

匯入的 preprocess_input 函式實現了這些功能。如果你對此很感興趣,可以在 這裡 檢視 preprocess_input的程式碼。

在實現了影象處理的部分之後,我們就可以使用模型來進行預測。這一步通過 predict 方法來實現,它返回一個向量,向量的第 i 個元素表示該影象屬於第 i 個 ImageNet 類別的概率。這通過如下的 ResNet50_predict_labels 函式實現。

通過對預測出的向量取用 argmax 函式(找到有最大概率值的下標序號),我們可以得到一個整數,即模型預測到的物體的類別。進而根據這個 清單,我們能夠知道這具體是哪個品種的狗狗。

In [10]:          
from keras.applications.resnet50 import preprocess_input, decode_predictions
def ResNet50_predict_labels(img_path):
    # 返回img_path路徑的影象的預測向量
    img = preprocess_input(path_to_tensor(img_path))
    return np.argmax(ResNet50_model.predict(img))
     

完成狗檢測模型

在研究該 清單 的時候,你會注意到,狗類別對應的序號為151-268。因此,在檢查預訓練模型判斷影象是否包含狗的時候,我們只需要檢查如上的 ResNet50_predict_labels 函式是否返回一個介於151和268之間(包含區間端點)的值。

我們通過這些想法來完成下方的 dog_detector 函式,如果從影象中檢測到狗就返回 True,否則返回 False

In [11]:          
def dog_detector(img_path):
    prediction = ResNet50_predict_labels(img_path)
    return ((prediction <= 268) & (prediction >= 151)) 
     

【作業】評估狗狗檢測模型


 

問題 3:

在下方的程式碼塊中,使用 dog_detector 函式,計算:

  • human_files_short中影象檢測到狗狗的百分比?
  • dog_files_short中影象檢測到狗狗的百分比?
In [12]:          
### TODO: 測試dog_detector函式在human_files_short和dog_files_short的表現
print(np.mean([dog_detector(human) for human in human_files_short]))
print(np.mean([dog_detector(dog) for dog in dog_files_short]))
     
0.0
1.0
 

 

步驟 3: 從頭開始建立一個CNN來分類狗品種

現在我們已經實現了一個函式,能夠在影象中識別人類及狗狗。但我們需要更進一步的方法,來對狗的類別進行識別。在這一步中,你需要實現一個卷積神經網路來對狗的品種進行分類。你需要從頭實現你的卷積神經網路(在這一階段,你還不能使用遷移學習),並且你需要達到超過1%的測試集準確率。在本專案的步驟五種,你還有機會使用遷移學習來實現一個準確率大大提高的模型。

在添加捲積層的時候,注意不要加上太多的(可訓練的)層。更多的引數意味著更長的訓練時間,也就是說你更可能需要一個 GPU 來加速訓練過程。萬幸的是,Keras 提供了能夠輕鬆預測每次迭代(epoch)花費時間所需的函式。你可以據此推斷你演算法所需的訓練時間。

值得注意的是,對狗的影象進行分類是一項極具挑戰性的任務。因為即便是一個正常人,也很難區分佈列塔尼犬和威爾士史賓格犬。

布列塔尼犬(Brittany) 威爾士史賓格犬(Welsh Springer Spaniel)

不難發現其他的狗品種會有很小的類間差別(比如金毛尋回犬和美國水獵犬)。

金毛尋回犬(Curly-Coated Retriever) 美國水獵犬(American Water Spaniel)

同樣,拉布拉多犬(labradors)有黃色、棕色和黑色這三種。那麼你設計的基於視覺的演算法將不得不克服這種較高的類間差別,以達到能夠將這些不同顏色的同類狗分到同一個品種中。

黃色拉布拉多犬(Yellow Labrador) | 棕色拉布拉多犬(Chocolate Labrador) | 黑色拉布拉多犬(Black Labrador)

  • | -||

我們也提到了隨機分類將得到一個非常低的結果:不考慮品種略有失衡的影響,隨機猜測到正確品種的概率是1/133,相對應的準確率是低於1%的。

請記住,在深度學習領域,實踐遠遠高於理論。大量嘗試不同的框架吧,相信你的直覺!當然,玩得開心!

資料預處理

通過對每張影象的畫素值除以255,我們對影象實現了歸一化處理。

In [13]:          
from PIL import ImageFile                            
ImageFile.LOAD_TRUNCATED_IMAGES = True                 
# Keras中的資料預處理過程
train_tensors = paths_to_tensor(train_files).astype('float32')/255
valid_tensors = paths_to_tensor(valid_files).astype('float32')/255
test_tensors = paths_to_tensor(test_files).astype('float32')/255