2018-07-19
全部谷歌渣翻加略微修改 大家將就的看哈 建議大佬們還是看看原文
其中用到的示例文件 multi-output-classification 大家可以點擊 下載 。
幾周前,我們討論了如何使用Keras和深度學習進行多標簽分類。
今天我們將討論一種稱為多輸出分類的更先進的技術。
那么,兩者之間的區別是什么?你怎么跟蹤學習所有這些東西呢?
雖然它可能有點令人困惑,特別是如果你不熟悉深度學習,這就是我如何區分它們的:
- 在多標簽分類中,您的網絡在網絡末端只有一組完全連接的層(即“頭”)負責分類。
- 但在多輸出分類中,您的網絡分支至少兩次(有時更多),在網絡末端創建 多組完全連接的磁頭 然后您的網絡可以為每個磁頭預測一組類標簽,從而可以學習不相交的標簽組合。
您甚至可以將多標簽分類與多輸出分類相結合,以便每個完全連接的頭可以預測多個輸出!
如果這開始讓你頭暈目眩,不用擔心 - - 我設計了今天的教程,引導你通過Keras進行多種輸出分類。它實際上比聽起來容易得多。
也就是說,這是我們今天要介紹的更先進的深度學習技術,所以如果你還沒有讀過我在Keras的多標簽分類上的第一篇文章,請確保你現在就去看一下。
從那里,您將准備好使用多個丟失功能訓練您的網絡並從網絡獲得多個輸出。
要了解如何使用Keras使用多個輸出和多個損失,請繼續閱讀!
Keras:多輸出和多重損失
在今天的博客文章中,我們將學習如何利用:
- 多種損失功能
- 多個輸出
...使用Keras深度學習庫。
正如本教程的介紹中所提到的,多標簽和多輸出預測之間存在差異。
通過多標簽分類,我們利用一個可以預測多個標簽的全連接頭。
但是對於多輸出分類,我們至少有兩個完全連接的磁頭 - 每個磁頭負責執行特定的分類任務。
我們甚至可以將多輸出分類與多標簽分類相結合 -- 在這種情況下,每個多輸出頭也將負責計算多個標簽!
你的眼睛可能會開始變得光彩照人,或者你可能會感到頭痛的第一次痛苦,所以不要繼續討論多輸出與多標簽分類,而是讓我們深入了解我們的項目。我相信這篇文章中提供的代碼將有助於鞏固您的概念。
我們將首先回顧一下我們將用於構建多輸出Keras分類器的數據集。
從那里我們將實施和培訓我們的Keras結構,FashionNet,它將用於在結構中使用兩個單獨的叉子對服裝/時尚物品進行分類:
- 一個分支負責對給定輸入圖像(例如,襯衫,連衣裙,牛仔褲,鞋子等)的服裝類型進行分類。
- 和第二分支負責顏色分類服裝(黑色,紅色,藍色等)的。
最后,我們將使用我們訓練有素的網絡對示例圖像進行分類並獲得多輸出分類。
讓我們繼續吧!
多輸出深度學習數據集

圖2:我們的多輸出分類數據集是使用本文中討論的技術創建的。請注意,我們的數據集不包含紅色/藍色鞋子或黑色連衣裙/襯衫。我們在本博文中討論的使用Keras方法的多輸出分類仍然能夠對這些組合做出正確的預測。
我們將在今天的Keras多輸出分類教程中使用的數據集基於我們之前關於多標簽分類的文章 中的一個例外 - -我添加了一個358張“黑鞋”圖像的文件夾。
總的來說,我們的數據集包含7種顏色+類別組合的2,525個 圖像 ,包括:
- 黑色牛仔褲(344圖像)
- 黑色皮鞋(358張圖片)
- 藍色連衣裙(386圖像)
- 藍色牛仔褲(356圖像)
- 藍色襯衫(369圖像)
- 紅色連衣裙(380圖像)
- 紅色襯衫(332圖像)
我使用我之前的教程“ 如何(快速)構建深度學習圖像數據集”中描述的方法創建了此數據集。
下載圖像並手動刪除七種組合中每種組合的不相關圖像的整個過程大約需要30分鍾。在構建自己的深度學習圖像數據集時,請確保遵循上面鏈接的教程。
我們今天的目標與上次幾乎相同 - 預測顏色和服裝類型......
...隨着能夠預測我們的網絡沒有接受過培訓的服裝類型+ 圖像顏色的附加測試。
例如,給出下面的“黑色禮服”圖像(再次,我們的網絡將不會被訓練這一張):
我們的目標是正確預測此圖像的“黑色”+“禮服”。
我們的Keras +深度學習項目結構
要完成今天的代碼演練以及在您自己的圖像上訓練+測試FashionNet,請看此文章頂部的下載。
從那里, unzip 存檔並更改目錄( cd ),如下所示。然后,利用 tree 命令,您可以以有組織的方式查看文件和文件夾:
上面你可以找到我們的項目結構,但在我們繼續之前,讓我們首先回顧一下內容。
有3個值得注意的Python文件:
- pyimagesearch / fashionnet .py :我們的多輸出分類網絡文件包含由三種方法組成的FashionNet體系結構類: build_category_branch , build_color_branch 和 build 。我們將在下一節中詳細介紹這些方法。
- train .py :這個腳本將訓練 FashionNet 模型並生成輸出文件夾中的所有文件。
- classify .py :此腳本加載我們訓練有素的網絡,並使用多輸出分類對示例圖像進行分類。
我們還有4個頂級目錄:
- dataset / :我們的時尚數據集是使用他們的API從Bing Image Search中刪除的。我們在上一節中介紹了數據集。要像我一樣創建自己的數據集,請參閱如何(快速)構建深度學習圖像數據集。
- examples / :我們有一些示例圖像,我們將在 本博文的最后一節中與我們的 classify.py腳本一起使用 。
- output / :我們的 train .py 腳本生成一些輸出文件:
- fashion .model :我們的序列化 Keras模型。
- category_lb .pickle :服裝類別的序列化 LabelBinarizer 對象由scikit-learn生成。我們的分類可以加載此文件(並調用標簽) 。py 腳本。
- color_lb .pickle : 用於顏色的 LabelBinarizer對象。
- output_accs .png :精確訓練情節圖像。
- output_losses .png :損失訓練情節圖像。
- pyimagesearch / :這是一個包含 FashionNet 類的Python模塊 。
快速回顧我們的多輸出Keras架構
要使用Keras執行多輸出預測,我們將實現一個稱為FashionNet的特殊網絡架構(我為此博客文章創建)。
FashionNet架構包含兩個特殊組件,包括:
- 網絡早期的一個分支,將網絡分成兩個“子網絡” - 一個負責服裝類型分類,另一個負責顏色分類。
- 網絡末端有兩個(不相交的)全連接磁頭,每個磁頭負責各自的分類任務。
在我們開始實施FashionNet之前,讓我們可視化每個組件,第一個是分支:
在此網絡架構圖中,您可以看到我們的網絡接受 96 x 96 x 3 輸入圖像。
然后我們立即創建兩個分支:
- 左邊的分支負責對服裝類別進行分類。
- 右邊的分支處理顏色分類。
每個分支執行其各自的卷積,激活,批量標准化,池化和丟失操作,直到我們達到最終輸出:
請注意這些完全連接(FC)頭的集合看起來像我們在本博客上檢查的其他架構的FC層 - 但現在有兩個,每個負責其給定的分類任務。
網絡右側的分支比左分支明顯淺(不深)。預測顏色比預測服裝類別容易得多,因此顏色分支比較淺。
為了了解我們如何實現這樣的架構,讓我們繼續我們的下一部分。
實施我們的“FashionNet”架構
由於使用多個損失函數訓練具有多個輸出的網絡是更先進的技術,我將假設您了解CNN的基本原理,而是專注於使多輸出/多輸出訓練成為可能的元素。
如果您是深度學習和圖像分類領域的新手,您應該考慮使用我的書“使用Python進行計算機視覺深度學習”來幫助您加快速度。
確保在繼續之前已從“下載 ” 部分下載了文件和數據 。
一旦下載好了,讓我們打開 fashionnet .py 並查看:
1
2
3
4
5
6
7
8
9
10
11
12
|
# import the necessary packages
from keras.models import Model
from keras.layers.normalization import BatchNormalization
from keras.layers.convolutional import Conv2D
from keras.layers.convolutional import MaxPooling2D
from keras.layers.core import Activation
from keras.layers.core import Dropout
from keras.layers.core import Lambda
from keras.layers.core import Dense
from keras.layers import Flatten
from keras.layers import Input
import tensorflow as tf
|
我們首先從Keras庫導入模塊並導入TensorFlow本身。
由於我們的網絡由兩個子網絡組成,我們將定義兩個負責構建每個分支的功能。
用於對服裝類型進行分類的第一個 build_category_branch定義如下:
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
class FashionNet:
@staticmethod
def build_category_branch(inputs, numCategories,
finalAct="softmax", chanDim=-1):
# utilize a lambda layer to convert the 3 channel input to a
# grayscale representation
x = Lambda(lambda c: tf.image.rgb_to_grayscale(c))(inputs)
# CONV => RELU => POOL
x = Conv2D(32, (3, 3), padding="same")(x)
x = Activation("relu")(x)
x = BatchNormalization(axis=chanDim)(x)
x = MaxPooling2D(pool_size=(3, 3))(x)
x = Dropout(0.25)(x)
|
該 build_category_branch 函數被定義上 線16和17與三個顯着的參數:
- 輸入 :我們的類別分支子網絡的輸入量。
- numCategories :“連衣裙”,“鞋子”,“牛仔褲”,“襯衫”等類別的數量。
- finalAct :最終激活層類型,默認為softmax分類器。如果您同時執行多輸出和 多標簽分類,則需要將此激活更改為sigmoid。
密切注意 第20行 ,我們使用 Lambda 圖層將圖像從RGB轉換為灰度。
為什么這樣?
嗯,無論是紅色,藍色,綠色,黑色還是紫色,禮服都是禮服,對吧?
因此,我們決定丟棄任何顏色信息,而是專注於圖像中的實際結構組件,確保我們的網絡不會學習將特定顏色與服裝類型聯合關聯。
注意: Lambda在Python 3.5和Python 3.6中的工作方式不同。我使用Python 3.5訓練了這個模型,所以如果你只運行 classify .py 腳本用Python 3.6測試帶有示例圖像的模型,你可能會遇到困難。如果您遇到與Lambda層相關的錯誤,我建議您(a)嘗試使用Python 3.5或(b)在Python 3.6上進行訓練和分類。不需要更改代碼。
然后我們繼續構建我們的 CONV = > RELU = > POOL 塊,並在第23-27行進行退出 。
我們的第一個 CONV 層有 32個 濾波器,具有 3 x 3 內核和 RELU 激活(整流線性單元)。我們應用批量標准化,最大池化和25%的退出。
Dropout是將節點從當前 層隨機斷開 到 下 一層的過程。這種隨機斷開的過程自然有助於網絡減少過度擬合,因為層中沒有一個單個節點負責預測某個類,對象,邊緣或角落。
接下來是我們的兩組 (CONV = > RELU )* 2 = > POOL 塊:
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
# (CONV => RELU) * 2 => POOL
x = Conv2D(64, (3, 3), padding="same")(x)
x = Activation("relu")(x)
x = BatchNormalization(axis=chanDim)(x)
x = Conv2D(64, (3, 3), padding="same")(x)
x = Activation("relu")(x)
x = BatchNormalization(axis=chanDim)(x)
x = MaxPooling2D(pool_size=(2, 2))(x)
x = Dropout(0.25)(x)
# (CONV => RELU) * 2 => POOL
x = Conv2D(128, (3, 3), padding="same")(x)
x = Activation("relu")(x)
x = BatchNormalization(axis=chanDim)(x)
x = Conv2D(128, (3, 3), padding="same")(x)
x = Activation("relu")(x)
x = BatchNormalization(axis=chanDim)(x)
x = MaxPooling2D(pool_size=(2, 2))(x)
x = Dropout(0.25)(x)
|
此代碼塊中的過濾器,內核和池大小的變化協同工作,逐步減小空間大小但增加深度。
讓我們將它與FC = > RELU 層結合在一起 :
49
50
51
52
53
54
55
56
57
58
59
60
|
# define a branch of output layers for the number of different
# clothing categories (i.e., shirts, jeans, dresses, etc.)
x = Flatten()(x)
x = Dense(256)(x)
x = Activation("relu")(x)
x = BatchNormalization()(x)
x = Dropout(0.5)(x)
x = Dense(numCategories)(x)
x = Activation(finalAct, name="category_output")(x)
# return the category prediction sub-network
return x
|
最后一個激活層是完全連接的,並且具有與numcategories相同數量的神經元/輸出 。
請注意,我們已 在第57行命名了最終激活層 “category_output”。這很重要,因為我們稍后將在train .py中按名稱引用此圖層 。
讓我們定義用於構建多輸出分類網絡的第二個函數。這個名為 build_color_branch ,顧名思義,負責對圖像中的顏色進行分類:
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
@staticmethod
def build_color_branch(inputs, numColors, finalAct="softmax",
chanDim=-1):
# CONV => RELU => POOL
x = Conv2D(16, (3, 3), padding="same")(inputs)
x = Activation("relu")(x)
x = BatchNormalization(axis=chanDim)(x)
x = MaxPooling2D(pool_size=(3, 3))(x)
x = Dropout(0.25)(x)
# CONV => RELU => POOL
x = Conv2D(32, (3, 3), padding="same")(x)
x = Activation("relu")(x)
x = BatchNormalization(axis=chanDim)(x)
x = MaxPooling2D(pool_size=(2, 2))(x)
x = Dropout(0.25)(x)
# CONV => RELU => POOL
x = Conv2D(32, (3, 3), padding="same")(x)
x = Activation("relu")(x)
x = BatchNormalization(axis=chanDim)(x)
x = MaxPooling2D(pool_size=(2, 2))(x)
x = Dropout(0.25)(x)
|
我們對build_color_branch的參數 與build_category_branch基本相同 。我們區分在與最后層的激活次數 numColors (來自不同 numCategories )。
這次,我們不會應用 Lambda 灰度轉換層,因為我們實際上關注網絡的這個區域的顏色。如果我們轉換為灰度,我們將丟失所有顏色信息!
網絡的這個分支明顯比服裝類別分支淺,因為手頭的任務要簡單得多。我們要求我們的子網絡完成的就是對顏色進行分類 - 子網絡不必那么深。
就像我們的類別分支一樣,我們有第二個完全連接的頭。讓我們構建 FC = > RELU 塊來完成:
86
87
88
89
90
91
92
93
94
95
96
97
|
# define a branch of output layers for the number of different
# colors (i.e., red, black, blue, etc.)
x = Flatten()(x)
x = Dense(128)(x)
x = Activation("relu")(x)
x = BatchNormalization()(x)
x = Dropout(0.5)(x)
x = Dense(numColors)(x)
x = Activation(finalAct, name="color_output")(x)
# return the color prediction sub-network
return x
|
為了區分顏色分支的最終激活層,我 在第94行提供了 name = “color_output”關鍵字參數 。我們將在訓練腳本中引用該名稱。
我們構建FashionNet的最后一步 是將我們的兩個分支組合在一起並 構建 最終的架構:
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
@staticmethod
def build(width, height, numCategories, numColors,
finalAct="softmax"):
# initialize the input shape and channel dimension (this code
# assumes you are using TensorFlow which utilizes channels
# last ordering)
inputShape = (height, width, 3)
chanDim = -1
# construct both the "category" and "color" sub-networks
inputs = Input(shape=inputShape)
categoryBranch = FashionNet.build_category_branch(inputs,
numCategories, finalAct=finalAct, chanDim=chanDim)
colorBranch = FashionNet.build_color_branch(inputs,
numColors, finalAct=finalAct, chanDim=chanDim)
# create the model using our input (the batch of images) and
# two separate outputs -- one for the clothing category
# branch and another for the color branch, respectively
model = Model(
inputs=inputs,
outputs=[categoryBranch, colorBranch],
name="fashionnet")
# return the constructed network architecture
return model
|
我們的 構建 函數在第100行定義, 並有5個不言自明的參數。
在 構建 功能使得我們使用TensorFlow和渠道最后順序的假設。這在第105行清楚地表明 我們的 inputShape 元組是明確排序的 (高度,寬度,3 ) ,其中3代表RGB通道。
如果你想使用后端 其他比TensorFlow你需要修改代碼以:(1)正確適當的渠道排序為你的后台和(2)實現自定義的層來處理RGB到灰度轉換。
從那里,我們定義網絡的兩個分支(第110-113行),然后將它們放在一個 模型中 (第118-121行)。
關鍵的一點是,我們的分支有一個共同的輸入,但有兩個不同的輸出(衣服類型和顏色分類)。
實現多輸出和多損失訓練腳本
現在我們已經實施了我們的 FashionNet 架構,讓我們來訓練吧!
當你准備好了,打開 train.py 然后我們開始訓練吧:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
# set the matplotlib backend so figures can be saved in the background
import matplotlib
matplotlib.use("Agg")
# import the necessary packages
from keras.optimizers import Adam
from keras.preprocessing.image import img_to_array
from sklearn.preprocessing import LabelBinarizer
from sklearn.model_selection import train_test_split
from pyimagesearch.fashionnet import FashionNet
from imutils import paths
import matplotlib.pyplot as plt
import numpy as np
import argparse
import random
import pickle
import cv2
import os
|
我們首先導入腳本的必要包。
從那里我們解析命令行參數:
20
21
22
23
24
25
26
27
28
29
30
31
32
|
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-d", "--dataset", required=True,
help="path to input dataset (i.e., directory of images)")
ap.add_argument("-m", "--model", required=True,
help="path to output model")
ap.add_argument("-l", "--categorybin", required=True,
help="path to output category label binarizer")
ap.add_argument("-c", "--colorbin", required=True,
help="path to output color label binarizer")
ap.add_argument("-p", "--plot", type=str, default="output",
help="base filename for generated plots")
args = vars(ap.parse_args())
|
我們將很快看到如何運行培訓腳本。現在,只需要知道 - dataset 是我們數據集的輸入文件路徑,而 - model , - categorybin , - colorbin 都是三個輸出文件路徑。
或者,您可以使用- plot 參數為生成的精度/損失圖指定基本文件名 。當我們在腳本中遇到它們時,我會再次指出這些命令行參數。如果第21-32行 看起來很怪,請參閱我的argparse +命令行參數博客文章。
現在,讓我們建立四個重要的訓練變量:
34
35
36
37
38
39
|
# initialize the number of epochs to train for, initial learning rate,
# batch size, and image dimensions
EPOCHS = 50
INIT_LR = 1e-3
BS = 32
IMAGE_DIMS = (96, 96, 3)
|
我們在第36-39行設置以下變量 :
- EPOCHS :時期數設定為 50 。通過實驗,我發現 50個 時代產生了一個低損失的模型,並且沒有過度裝配到訓練集(或者沒有盡可能地過度裝配)。
- INIT_LR :我們的初始學習率設定為 0.001 。學習率控制着我們沿着漸變的“步驟”。較小的值表示較小的步驟,較大的值表示較大的步驟。我們很快就會看到,我們將使用Adam優化器,同時逐漸降低學習速率。
- BS :我們將以 32的批量培訓我們的網絡 。
- IMAGE_DIMS :所有的輸入圖像將被調整為 96 X 96 與 3個 通道(RGB)。我們正在接受這些維度的培訓,我們的網絡架構輸入維度也反映了這些。當我們在后面的部分中使用示例圖像測試我們的網絡時,測試維度 必須與訓練維度相 匹配。
我們的下一步是抓住我們的圖像路徑並隨機洗牌。我們還將初始化列表以分別保存圖像以及服裝類別和顏色:
41
42
43
44
45
46
47
48
49
50
51
|
# grab the image paths and randomly shuffle them
print("[INFO] loading images...")
imagePaths = sorted(list(paths.list_images(args["dataset"])))
random.seed(42)
random.shuffle(imagePaths)
# initialize the data, clothing category labels (i.e., shirts, jeans,
# dresses, etc.) along with the color labels (i.e., red, blue, etc.)
data = []
categoryLabels = []
colorLabels = []
|
然后,我們將循環遍歷 imagePaths ,預處理並填充 數據 , categoryLabels 和colorLabels 列表:
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
# loop over the input images
for imagePath in imagePaths:
# load the image, pre-process it, and store it in the data list
image = cv2.imread(imagePath)
image = cv2.resize(image, (IMAGE_DIMS[1], IMAGE_DIMS[0]))
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
image = img_to_array(image)
data.append(image)
# extract the clothing color and category from the path and
# update the respective lists
(color, cat) = imagePath.split(os.path.sep)[-2].split("_")
categoryLabels.append(cat)
colorLabels.append(color)
|
我們開始 在第54行上 遍歷我們的 imagePaths。
在循環內部,我們將圖像加載並調整大小到 IMAGE_DIMS 。我們還將圖像從BGR排序轉換為RGB。我們為什么要進行這種轉換?回想一下我們 在build_category_branch 函數中的FashionNet類 ,我們 在Lambda函數/圖層中使用了TensorFlow的 rgb_to_grayscale轉換。因此,我們首先在第58行轉換為RGB ,並最終將預處理后的圖像附加到 數據 列表中。
接下來,仍然在循環內部,我們 從當前圖像所在的目錄名稱中提取顏色和類別標簽(第64行)。
要查看此操作,只需在終端中啟動Python,並提供示例 imagePath 進行實驗,如下所示:
1
2
3
4
5
6
7
8
|
$ python
>>> import os
>>> imagePath = "dataset/red_dress/00000000.jpg"
>>> (color, cat) = imagePath.split(os.path.sep)[-2].split("_")
>>> color
'red'
>>> cat
'dress'
|
您當然可以按照自己的方式組織目錄結構(但您必須修改代碼)。我最喜歡的兩種方法包括(1)使用每個標簽的子目錄或(2)將所有圖像存儲 在單個目錄中,然后創建CSV或JSON文件以將圖像文件名映射到其標簽。
讓我們將三個列表轉換為NumPy數組,對標簽進行二值化,並將數據分區為訓練和測試分割:
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
|
# scale the raw pixel intensities to the range [0, 1] and convert to
# a NumPy array
data = np.array(data, dtype="float") / 255.0
print("[INFO] data matrix: {} images ({:.2f}MB)".format(
len(imagePaths), data.nbytes / (1024 * 1000.0)))
# convert the label lists to NumPy arrays prior to binarization
categoryLabels = np.array(categoryLabels)
colorLabels = np.array(colorLabels)
# binarize both sets of labels
print("[INFO] binarizing labels...")
categoryLB = LabelBinarizer()
colorLB = LabelBinarizer()
categoryLabels = categoryLB.fit_transform(categoryLabels)
colorLabels = colorLB.fit_transform(colorLabels)
# partition the data into training and testing splits using 80% of
# the data for training and the remaining 20% for testing
split = train_test_split(data, categoryLabels, colorLabels,
test_size=0.2, random_state=42)
(trainX, testX, trainCategoryY, testCategoryY,
trainColorY, testColorY) = split
|
我們最后的預處理步驟-轉換為NumPy的陣列和縮放原始像素強度以 [ 0 ,1 ] -可以一舉上執行 第70行。
我們還將categoryLabels 和 colorLabels轉換 為NumPy數組(第75和76行)。這是必要的,因為在下一步我們將使用 我們之前導入的scikit-learn的LabelBinarizer對標簽進行二值化 (第80-83行)。由於我們的網絡有兩個獨立的分支,我們可以使用兩個獨立的標簽二進制化器 - 這與我們使用 MultiLabelBinarizer (也來自scikit-learn)的多標簽分類不同。
接下來,我們對數據集執行典型的80%訓練/ 20%測試分割(第87-96行)。
讓我們構建網絡,定義我們的獨立損失,並編譯我們的模型:
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
|
# initialize our FashionNet multi-output network
model = FashionNet.build(96, 96,
numCategories=len(categoryLB.classes_),
numColors=len(colorLB.classes_),
finalAct="softmax")
# define two dictionaries: one that specifies the loss method for
# each output of the network along with a second dictionary that
# specifies the weight per loss
losses = {
"category_output": "categorical_crossentropy",
"color_output": "categorical_crossentropy",
}
lossWeights = {"category_output": 1.0, "color_output": 1.0}
# initialize the optimizer and compile the model
print("[INFO] compiling model...")
opt = Adam(lr=INIT_LR, decay=INIT_LR / EPOCHS)
model.compile(optimizer=opt, loss=losses, loss_weights=lossWeights,
metrics=["accuracy"])
|
在 第93-96行,我們實例化了我們的多輸出 FashionNet 模型。我們在創建FashionNet 類並在 其中構建函數時剖析了參數 ,因此請務必查看我們實際提供的值。
接下來,我們需要 為每個完全連接的磁頭定義兩個 損耗(第101-104行)。
使用每個分支激活層的名稱使用字典來定義多個損失 - 這就是我們在FashionNet實現中命名輸出層的原因! 每次損失都將使用分類交叉熵,這是訓練網絡用於> 2級分類時使用的標准損失方法。
我們還在第105行 的單獨字典(具有相同值的相同名稱鍵)中定義了相等的 lossWeights。在您的特定應用中,您可能希望比另一個更重地減輕一個損失。
現在我們已經實例化我們的模型並創建了我們的 損失 + lossWeights 字典,讓我們 用學習率衰減初始化 Adam優化器(第109行)並 編譯 我們的 模型 (第110和111行)。
我們的下一個區塊只是啟動了培訓過程:
113
114
115
116
117
118
119
120
121
122
123
|
# train the network to perform multi-output classification
H = model.fit(trainX,
{"category_output": trainCategoryY, "color_output": trainColorY},
validation_data=(testX,
{"category_output": testCategoryY, "color_output": testColorY}),
epochs=EPOCHS,
verbose=1)
# save the model to disk
print("[INFO] serializing network...")
model.save(args["model"])
|
回想一下 87-90行,我們將數據分成訓練( trainX )和測試( testX )。在 114-119行,我們在提供數據的同時啟動培訓流程。請注意 第115行,我們將標簽作為字典傳遞。對於 第116行和第117行也是如此,我們傳入一個2元組的驗證數據。在使用Keras執行多輸出分類時,需要以這種方式傳遞訓練和驗證標簽 。我們需要指示Keras哪組目標標簽對應於網絡的哪個輸出分支。
使用我們的命令行參數( args [ “model” ] ),我們將序列化模型保存到磁盤以供將來調用。
我們也會將標簽二進制文件保存為序列化的pickle文件:
125
126
127
128
129
130
131
132
133
134
135
|
# save the category binarizer to disk
print("[INFO] serializing category label binarizer...")
f = open(args["categorybin"], "wb")
f.write(pickle.dumps(categoryLB))
f.close()
# save the color binarizer to disk
print("[INFO] serializing color label binarizer...")
f = open(args["colorbin"], "wb")
f.write(pickle.dumps(colorLB))
f.close()
|
使用命令行參數路徑( args [ “categorybin” ] 和 args [ “colorbin” ] ),我們將兩個標簽二進制文件( categoryLB 和 colorLB )寫入磁盤上的序列化pickle文件。
從那里開始就是在這個腳本中繪制結果:
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
# plot the total loss, category loss, and color loss
lossNames = ["loss", "category_output_loss", "color_output_loss"]
plt.style.use("ggplot")
(fig, ax) = plt.subplots(3, 1, figsize=(13, 13))
# loop over the loss names
for (i, l) in enumerate(lossNames):
# plot the loss for both the training and validation data
title = "Loss for {}".format(l) if l != "loss" else "Total loss"
ax[i].set_title(title)
ax[i].set_xlabel("Epoch #")
ax[i].set_ylabel("Loss")
ax[i].plot(np.arange(0, EPOCHS), H.history[l], label=l)
ax[i].plot(np.arange(0, EPOCHS), H.history["val_" + l],
label="val_" + l)
ax[i].legend()
# save the losses figure and create a new figure for the accuracies
plt.tight_layout()
plt.savefig("{}_losses.png".format(args["plot"]))
plt.close()
|
上面的代碼塊負責在單獨但堆疊的圖上繪制每個損失函數的損失歷史,包括:
- 總體損耗
- 類別輸出損失
- 顏色輸出損失
同樣,我們將在一個單獨的圖像文件中繪制精度:
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
|
# create a new figure for the accuracies
accuracyNames = ["category_output_acc", "color_output_acc"]
plt.style.use("ggplot")
(fig, ax) = plt.subplots(2, 1, figsize=(8, 8))
# loop over the accuracy names
for (i, l) in enumerate(accuracyNames):
# plot the loss for both the training and validation data
ax[i].set_title("Accuracy for {}".format(l))
ax[i].set_xlabel("Epoch #")
ax[i].set_ylabel("Accuracy")
ax[i].plot(np.arange(0, EPOCHS), H.history[l], label=l)
ax[i].plot(np.arange(0, EPOCHS), H.history["val_" + l],
label="val_" + l)
ax[i].legend()
# save the accuracies figure
plt.tight_layout()
plt.savefig("{}_accs.png".format(args["plot"]))
plt.close()
|
我們的類別精度和顏色精度圖最好單獨查看,因此它們在一個圖像中作為單獨的圖堆疊。
訓練多輸出/多損失Keras模型
請務必使用此博客文章的 “下載”部分來獲取代碼和數據集。
不要忘記:我使用Python 3.5來訓練本教程下載中包含的網絡。只要保持一致(Python 3.5或Python 3.6),就不應該對Lambda實現不一致有任何問題。你甚至可以運行Python 2.7(尚未測試過)。
打開終端。然后粘貼以下命令開始訓練過程(如果你沒有GPU,你也想要拿啤酒):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
$ python train.py --dataset dataset --model output/fashion.model \
--categorybin output/category_lb.pickle --colorbin output/color_lb.pickle
Using TensorFlow backend.
[INFO] loading images...
[INFO] data matrix: 2521 images (544.54MB)
[INFO] binarizing labels...
[INFO] compiling model...
Train on 2016 samples, validate on 505 samples
Epoch 1/50
2018-05-28 08:38:36.888148: I tensorflow/core/common_runtime/gpu/gpu_device.cc:955] Found device 0 with properties:
name: GeForce GTX TITAN X
major: 5 minor: 2 memoryClockRate (GHz) 1.076
pciBusID 0000:0a:00.0
Total memory: 11.92GiB
Free memory: 11.71GiB
2018-05-28 08:38:36.888187: I tensorflow/core/common_runtime/gpu/gpu_device.cc:976] DMA: 0
2018-05-28 08:38:36.888194: I tensorflow/core/common_runtime/gpu/gpu_device.cc:986] 0: Y
2018-05-28 08:38:36.888201: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1045] Creating TensorFlow device (/gpu:0) -> (device: 0, name: GeForce GTX TITAN X, pci bus id: 0000:0a:00.0)
2016/2016 [==============================] - 5s - loss: 0.9671 - category_output_loss: 0.5805 - color_output_loss: 0.3866 - category_output_acc: 0.8259 - color_output_acc: 0.8700 - val_loss: 4.2295 - val_category_output_loss: 2.3647 - val_color_output_loss: 1.8648 - val_category_output_acc: 0.3188 - val_color_output_acc: 0.4455
Epoch 2/50
2016/2016 [==============================] - 3s - loss: 0.4406 - category_output_loss: 0.2873 - color_output_loss: 0.1534 - category_output_acc: 0.9048 - color_output_acc: 0.9454 - val_loss: 7.4341 - val_category_output_loss: 3.6817 - val_color_output_loss: 3.7524 - val_category_output_acc: 0.3188 - val_color_output_acc: 0.4416
Epoch 3/50
2016/2016 [==============================] - 3s - loss: 0.3147 - category_output_loss: 0.2191 - color_output_loss: 0.0956 - category_output_acc: 0.9266 - color_output_acc: 0.9658 - val_loss: 4.3351 - val_category_output_loss: 1.3560 - val_color_output_loss: 2.9791 - val_category_output_acc: 0.4693 - val_color_output_acc: 0.4455
...
Epoch 48/50
2016/2016 [==============================] - 3s - loss: 0.0489 - category_output_loss: 0.0252 - color_output_loss: 0.0237 - category_output_acc: 0.9921 - color_output_acc: 0.9926 - val_loss: 0.5122 - val_category_output_loss: 0.2902 - val_color_output_loss: 0.2219 - val_category_output_acc: 0.9465 - val_color_output_acc: 0.9465
Epoch 49/50
2016/2016 [==============================] - 3s - loss: 0.0329 - category_output_loss: 0.0164 - color_output_loss: 0.0166 - category_output_acc: 0.9945 - color_output_acc: 0.9945 - val_loss: 0.2171 - val_category_output_loss: 0.1520 - val_color_output_loss: 0.0651 - val_category_output_acc: 0.9683 - val_color_output_acc: 0.9782
Epoch 50/50
2016/2016 [==============================] - 3s - loss: 0.0356 - category_output_loss: 0.0215 - color_output_loss: 0.0140 - category_output_acc: 0.9931 - color_output_acc: 0.9936 - val_loss: 0.3848 - val_category_output_loss: 0.3186 - val_color_output_loss: 0.0662 - val_category_output_acc: 0.9347 - val_color_output_acc: 0.9782
[INFO] serializing network...
[INFO] serializing category label binarizer...
[INFO] serializing color label binarizer...
|
對於我們的類別輸出我們獲得:
- 訓練集的准確率為99.31%
- 測試集的准確率為93.47%
對於我們達到的顏色輸出:
- 訓練集的准確率為99.31%
- 測試集的准確率為97.82%
您可以在下面找到我們多次虧損的情節:
以及我們的多重准確性:
通過應用數據擴充可以獲得更高的准確性。
實現多輸出分類腳本
現在我們已經訓練了我們的網絡,讓我們學習如何將它應用於輸入不屬於我們訓練集的圖像。
打開 分類。py 並插入以下代碼:
1
2
3
4
5
6
7
8
9
|
# import the necessary packages
from keras.preprocessing.image import img_to_array
from keras.models import load_model
import tensorflow as tf
import numpy as np
import argparse
import imutils
import pickle
import cv2
|
首先,我們導入所需的包,然后解析命令行參數:
11
12
13
14
15
16
17
18
19
20
21
|
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-m", "--model", required=True,
help="path to trained model model")
ap.add_argument("-l", "--categorybin", required=True,
help="path to output category label binarizer")
ap.add_argument("-c", "--colorbin", required=True,
help="path to output color label binarizer")
ap.add_argument("-i", "--image", required=True,
help="path to input image")
args = vars(ap.parse_args())
|
我們有四個命令行參數,這些參數是在終端中運行此腳本所必需的:
- - model :我們剛訓練的序列化模型文件的路徑(我們之前腳本的輸出)。
- - categorybin :類別標簽二進制文件的路徑(我們以前腳本的輸出)。
- - colorbin :顏色標簽二進制化器的路徑(我們之前腳本的輸出)。
- - image :我們的測試圖像文件路徑 - 此圖像將來自我們的 examples / 目錄。
從那里,我們加載我們的圖像並預處理它:
23
24
25
26
27
28
29
30
31
32
|
# load the image
image = cv2.imread(args["image"])
output = imutils.resize(image, width=400)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# preprocess the image for classification
image = cv2.resize(image, (96, 96))
image = image.astype("float") / 255.0
image = img_to_array(image)
image = np.expand_dims(image, axis=0)
|
在我們進行推理之前,需要預處理我們的圖像。在上面的塊中,我們加載圖像,調整大小以用於輸出目的,並交換顏色通道(第24-26行),這樣我們就可以在 FashionNet 的Lambda層中使用TensorFlow的RGB灰度函數 。然后我們調整RGB圖像的大小( 從我們的訓練腳本中調用IMAGE_DIMS),將其縮放到[0,1],轉換為NumPy數組,並為批處理添加維度(第29-32行)。
這是關鍵的預處理步驟按照我們的訓練腳本采取相同的動作。
接下來,讓我們加載序列化模型和兩個標簽二進制文件:
34
35
36
37
38
39
|
# load the trained convolutional neural network from disk, followed
# by the category and color label binarizers, respectively
print("[INFO] loading network...")
model = load_model(args["model"], custom_objects={"tf": tf})
categoryLB = pickle.loads(open(args["categorybin"], "rb").read())
colorLB = pickle.loads(open(args["colorbin"], "rb").read())
|
使用三個我們的四個命令行參數的 第37-39行,我們載入 模型 , categoryLB 和 colorLB 。
既然(1)多輸出Keras模型和(2)標簽二進制化器都在內存中,我們可以對圖像進行分類:
41
42
43
44
45
46
47
48
49
50
51
|
# classify the input image using Keras' multi-output functionality
print("[INFO] classifying image...")
(categoryProba, colorProba) = model.predict(image)
# find indexes of both the category and color outputs with the
# largest probabilities, then determine the corresponding class
# labels
categoryIdx = categoryProba[0].argmax()
colorIdx = colorProba[0].argmax()
categoryLabel = categoryLB.classes_[categoryIdx]
colorLabel = colorLB.classes_[colorIdx]
|
我們執行多輸出分類上 線43導致得到用於概率二者類別和顏色( categoryProba 和colorProba 分別地)。
注意:我沒有包含包含代碼,因為它有點冗長,但您可以通過檢查輸出張量的名稱來確定TensorFlow + Keras模型返回多個輸出的順序。有關詳細信息,請參閱StackOverflow上的此主題。
從那里,我們將提取類別和顏色的最高概率的指數(第48和49行)。
使用高概率指數,我們可以提取類名(第50和51行)。
這似乎有點 太容易了,不是嗎?但這就是將Keras應用於新輸入圖像的多輸出分類!
讓我們顯示結果來證明它:
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
|
# draw the category label and color label on the image
categoryText = "category: {} ({:.2f}%)".format(categoryLabel,
categoryProba[0][categoryIdx] * 100)
colorText = "color: {} ({:.2f}%)".format(colorLabel,
colorProba[0][colorIdx] * 100)
cv2.putText(output, categoryText, (10, 25), cv2.FONT_HERSHEY_SIMPLEX,
0.7, (0, 255, 0), 2)
cv2.putText(output, colorText, (10, 55), cv2.FONT_HERSHEY_SIMPLEX,
0.7, (0, 255, 0), 2)
# display the predictions to the terminal as well
print("[INFO] {}".format(categoryText))
print("[INFO] {}".format(colorText))
# show the output image
cv2.imshow("Output", output)
cv2.waitKey(0)
|
我們在輸出 圖像上顯示結果 (第54-61行)。如果遇到“紅色連衣裙”,它會在左上角的綠色文字中看起來像這樣的東西:
- 類別:連衣裙(89.04%)
- 顏色:紅色(95.07%)
相同的信息被打印到在終端 線64和65在此之后, 輸出 圖像在屏幕(上顯示68行)。
使用Keras執行多輸出分類
現在是玩樂部分的時候了!
在本節中,我們將展示我們的網絡在五個圖像 的例子 目錄這是不是訓練集的一部分。
踢球者是我們的網絡只經過專門培訓才能識別兩個示例圖像類別。前兩個圖像(“黑色牛仔褲”和“紅色襯衫”)應該特別容易讓我們的網絡正確地分類類別和顏色。
剩下的三張照片對我們的模特來說完全陌生 - 我們沒有用“紅鞋”,“藍色鞋子”或“黑色禮服”進行訓練,但我們將嘗試多輸出分類,看看會發生什么。
讓我們從“黑色牛仔褲”開始 - 這個應該很簡單,因為訓練數據集中有很多類似的圖像。一定要使用四個命令行參數,如下所示:
1
2
3
4
5
6
7
8
|
$ python classify.py --model output/fashion.model \
--categorybin output/category_lb.pickle --colorbin output/color_lb.pickle \
--image examples/black_jeans.jpg
Using TensorFlow backend.
[INFO] loading network...
[INFO] classifying image...
[INFO] category: jeans (100.00%)
[INFO] color: black (97.04%)
|
正如所料,我們的網絡正確地將圖像分類為“牛仔褲”和“黑色”。
我們試試一件“紅色襯衫”:
1
2
3
4
5
6
7
8
|
$ python classify.py --model fashion.model \
--categorybin output/category_lb.pickle --colorbin output/color_lb.pickle \
--image examples/red_shirt.jpg
Using TensorFlow backend.
[INFO] loading network...
[INFO] classifying image...
[INFO] category: shirt (100.00%)
[INFO] color: red (100.00%)
|
對於兩個類別標簽100%有信心,我們的圖像肯定包含“紅色襯衫”。請記住,我們的網絡 已經在訓練過程中看到的“紅衫軍”的其他例子。
現在讓我們退一步吧。
看一下我們的數據集,回想一下之前從未見過“紅鞋”,但它看到“紅色”的形式是“連衣裙”和“襯衫”以及“鞋子”的“黑色”。
是否有可能使這個區別陌生的測試圖像包含“鞋”是“紅”?
我們來看看:
1
2
3
4
5
6
7
|
$ python classify.py --model fashion.model \
--categorybin output/category_lb.pickle --colorbin output/color_lb.pickle \
--image examples/red_shoes.jpgUsing TensorFlow backend.
[INFO] loading network...
[INFO] classifying image...
[INFO] category: shoes (82.20%)
[INFO] color: red (99.99%)
|

圖11:我們的深度學習多輸出分類網絡以前從未見過“紅色”和“鞋子”的組合。在訓練過程中,我們確實呈現了“鞋子”(盡管是“黑色”),我們確實呈現了“紅色”(“襯衫”和“連衣裙”)。令人驚訝的是,我們的網絡激發神經元,為這個“紅鞋”圖像生成正確的多輸出標簽。成功!
答對了!
看圖像中的結果,我們成功了。
在呈現不熟悉的多輸出組合時,我們有了一個良好的開端。我們的網絡設計+培訓得到了回報,我們能夠高度准確地識別“紅鞋”。
我們還沒有完成 - 讓我們的網絡呈現一個包含“黑色連衣裙”的圖像。請記住,以前這個相同的圖像在我們的多標簽分類 教程中沒有產生正確的結果 。
我想這次我們有很大的成功機會,所以在你的終端輸入以下命令:
1
2
3
4
5
6
7
8
|
$ python classify.py --model fashion.model \
--categorybin output/category_lb.pickle --colorbin output/color_lb.pickle \
--image examples/black_dress.jpg
Using TensorFlow backend.
[INFO] loading network...
[INFO] classifying image...
[INFO] category: dress (98.80%)
[INFO] color: black (98.65%)
|
查看圖片左上角的課程標簽!
我們在類別和 顏色方面都實現了正確的分類, 報告的 准確率均高於98%。我們完成了目標!
為了理智,讓我們嘗試一個更陌生的組合: “藍色鞋子”。在終端中輸入相同的命令,此時改變- 圖像 參數 的例子/ blue_shoes .JPG :
1
2
3
4
5
6
7
8
|
$ python classify.py --model fashion.model \
--categorybin output/category_lb.pickle --colorbin output/color_lb.pickle \
--image examples/blue_shoes.jpg
Using TensorFlow backend.
[INFO] loading network...
[INFO] classifying image...
[INFO] category: shoes (100.00%)
[INFO] color: blue (99.05%)
|
相同的操作得到確認 - 我們的網絡沒有接受過“藍鞋”圖像的培訓,但我們能夠通過使用我們的兩個子網以及多輸出和多損失分類來正確地對它們進行分類。