2018-07-19
全部谷歌渣翻加略微修改 大家将就的看哈 建议大佬们还是看看原文
其中用到的示例文件 multi-output-classification 大家可以点击 下载 。
几周前,我们讨论了如何使用Keras和深度学习进行多标签分类。
今天我们将讨论一种称为多输出分类的更先进的技术。
那么,两者之间的区别是什么?你怎么跟踪学习所有这些东西呢?
虽然它可能有点令人困惑,特别是如果你不熟悉深度学习,这就是我如何区分它们的:
- 在多标签分类中,您的网络在网络末端只有一组完全连接的层(即“头”)负责分类。
- 但在多输出分类中,您的网络分支至少两次(有时更多),在网络末端创建 多组完全连接的磁头 然后您的网络可以为每个磁头预测一组类标签,从而可以学习不相交的标签组合。
您甚至可以将多标签分类与多输出分类相结合,以便每个完全连接的头可以预测多个输出!
如果这开始让你头晕目眩,不用担心 - - 我设计了今天的教程,引导你通过Keras进行多种输出分类。它实际上比听起来容易得多。
也就是说,这是我们今天要介绍的更先进的深度学习技术,所以如果你还没有读过我在Keras的多标签分类上的第一篇文章,请确保你现在就去看一下。
从那里,您将准备好使用多个丢失功能训练您的网络并从网络获得多个输出。
要了解如何使用Keras使用多个输出和多个损失,请继续阅读!
Keras:多输出和多重损失
图1: 使用Keras我们可以执行多输出分类,其中多组完全连接的磁头可以学习不相交的标签组合。此动画演示了多个多输出分类结果。
在今天的博客文章中,我们将学习如何利用:
- 多种损失功能
- 多个输出
...使用Keras深度学习库。
正如本教程的介绍中所提到的,多标签和多输出预测之间存在差异。
通过多标签分类,我们利用一个可以预测多个标签的全连接头。
但是对于多输出分类,我们至少有两个完全连接的磁头 - 每个磁头负责执行特定的分类任务。
我们甚至可以将多输出分类与多标签分类相结合 -- 在这种情况下,每个多输出头也将负责计算多个标签!
你的眼睛可能会开始变得光彩照人,或者你可能会感到头痛的第一次痛苦,所以不要继续讨论多输出与多标签分类,而是让我们深入了解我们的项目。我相信这篇文章中提供的代码将有助于巩固您的概念。
我们将首先回顾一下我们将用于构建多输出Keras分类器的数据集。
从那里我们将实施和培训我们的Keras结构,FashionNet,它将用于在结构中使用两个单独的叉子对服装/时尚物品进行分类:
- 一个分支负责对给定输入图像(例如,衬衫,连衣裙,牛仔裤,鞋子等)的服装类型进行分类。
- 和第二分支负责颜色分类服装(黑色,红色,蓝色等)的。
最后,我们将使用我们训练有素的网络对示例图像进行分类并获得多输出分类。
让我们继续吧!
多输出深度学习数据集
图2:我们的多输出分类数据集是使用本文中讨论的技术创建的。请注意,我们的数据集不包含红色/蓝色鞋子或黑色连衣裙/衬衫。我们在本博文中讨论的使用Keras方法的多输出分类仍然能够对这些组合做出正确的预测。
我们将在今天的Keras多输出分类教程中使用的数据集基于我们之前关于多标签分类的文章 中的一个例外 - -我添加了一个358张“黑鞋”图像的文件夹。
总的来说,我们的数据集包含7种颜色+类别组合的2,525个 图像 ,包括:
- 黑色牛仔裤(344图像)
- 黑色皮鞋(358张图片)
- 蓝色连衣裙(386图像)
- 蓝色牛仔裤(356图像)
- 蓝色衬衫(369图像)
- 红色连衣裙(380图像)
- 红色衬衫(332图像)
我使用我之前的教程“ 如何(快速)构建深度学习图像数据集”中描述的方法创建了此数据集。
下载图像并手动删除七种组合中每种组合的不相关图像的整个过程大约需要30分钟。在构建自己的深度学习图像数据集时,请确保遵循上面链接的教程。
我们今天的目标与上次几乎相同 - 预测颜色和服装类型......
...随着能够预测我们的网络没有接受过培训的服装类型+ 图像颜色的附加测试。
例如,给出下面的“黑色礼服”图像(再次,我们的网络将不会被训练这一张):
图3:虽然“黑色礼服”的图像未包含在今天的数据集中,但我们仍将尝试使用Keras和深度学习的多输出分类对它们进行正确分类。
我们的目标是正确预测此图像的“黑色”+“礼服”。
我们的Keras +深度学习项目结构
要完成今天的代码演练以及在您自己的图像上训练+测试FashionNet,请看此文章顶部的下载。
从那里, unzip 存档并更改目录( cd ),如下所示。然后,利用 tree 命令,您可以以有组织的方式查看文件和文件夹:
...
$ cd multi-output-classification
$ tree --filelimit 10 --dirsfirst
.
├── dataset
│ ├── black_jeans [344 entries]
│ ├── black_shoes [358 entries]
│ ├── blue_dress [386 entries]
│ ├── blue_jeans [356 entries]
│ ├── blue_shirt [369 entries]
│ ├── red_dress [380 entries]
│ └── red_shirt [332 entries]
├── examples
│ ├── black_dress.jpg
│ ├── black_jeans.jpg
│ ├── blue_shoes.jpg
│ ├── red_shirt.jpg
│ └── red_shoes.jpg
├── output
│ ├── fashion.model
│ ├── category_lb.pickle
│ ├── color_lb.pickle
│ ├── output_accs.png
│ └── output_losses.png
├── pyimagesearch
│ ├── __init__.py
│ └── fashionnet.py
├── train.py
└── classify.py
上面你可以找到我们的项目结构,但在我们继续之前,让我们首先回顾一下内容。
有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之前,让我们可视化每个组件,第一个是分支:
图4:以Keras编码的多输出分类网络的顶部。服装类分支可以看到在 左边和对颜色的分支权。每个分支都有一个完全连接的头部。
在此网络架构图中,您可以看到我们的网络接受 96 x 96 x 3 输入图像。
然后我们立即创建两个分支:
- 左边的分支负责对服装类别进行分类。
- 右边的分支处理颜色分类。
每个分支执行其各自的卷积,激活,批量标准化,池化和丢失操作,直到我们达到最终输出:
图5:我们的深度学习Keras多输出分类网络可以学习不相交的标签组合。
请注意这些完全连接(FC)头的集合看起来像我们在本博客上检查的其他架构的FC层 - 但现在有两个,每个负责其给定的分类任务。
网络右侧的分支比左分支明显浅(不深)。预测颜色比预测服装类别容易得多,因此颜色分支比较浅。
为了了解我们如何实现这样的架构,让我们继续我们的下一部分。
实施我们的“FashionNet”架构
图6: Keras深度学习库具有执行多输出分类所需的所有功能。
由于使用多个损失函数训练具有多个输出的网络是更先进的技术,我将假设您了解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%
您可以在下面找到我们多次亏损的情节:
图7:我们的Keras深度学习多输出分类训练损失用matplotlib绘制。我们的总损失(上图),服装类别损失(中间)和颜色损失(下图)是独立绘制的,用于分析。
以及我们的多重准确性:
图8:使用Keras培训了一个多输出分类网络FashionNet。为了分析培训,最好在单独的图中显示精度。服装类培训准确情节(上图)。颜色训练精度图(下)。
通过应用数据扩充可以获得更高的准确性。
实现多输出分类脚本
现在我们已经训练了我们的网络,让我们学习如何将它应用于输入不属于我们训练集的图像。
打开 分类。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模型返回多个输出的顺序。有关详细信息,请参阅*上的此主题。
从那里,我们将提取类别和颜色的最高概率的指数(第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%)
|
图9:深度学习多标签分类允许我们识别不相交的标签组合,例如服装类型和服装颜色。我们的网络已将此图像正确分类为“牛仔裤”和“黑色”。
正如所料,我们的网络正确地将图像分类为“牛仔裤”和“黑色”。
我们试试一件“红色衬衫”:
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%)
|
图10: “红色衬衫”的图像是不在我们的深度学习图像数据集中的测试图像。我们的Keras多输出网络有; 然而,见过其他红色衬衫。它可以100%置信地使用两个标签轻松地对此图像进行分类。
对于两个类别标签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:我们的深度学习多输出分类网络以前从未见过“红色”和“鞋子”的组合。在训练过程中,我们确实呈现了“鞋子”(尽管是“黑色”),我们确实呈现了“红色”(“衬衫”和“连衣裙”)。令人惊讶的是,我们的网络激发神经元,为这个“红鞋”图像生成正确的多输出标签。成功!
答对了!
看图像中的结果,我们成功了。
在呈现不熟悉的多输出组合时,我们有了一个良好的开端。我们的网络设计+培训得到了回报,我们能够高度准确地识别“红鞋”。
我们还没有完成 - 让我们的网络呈现一个包含“黑色连衣裙”的图像。请记住,以前这个相同的图像在我们的多标签分类 教程中没有产生正确的结果 。
我想这次我们有很大的成功机会,所以在你的终端输入以下命令:
Shell
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%)
|
图12:虽然“黑色礼服”的图像未包含在今天的数据集中,但我们仍然能够使用Keras和深度学习的多输出分类对它们进行正确分类。
查看图片左上角的课程标签!
我们在类别和 颜色方面都实现了正确的分类, 报告的 准确率均高于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%)
|
图13:虽然多标签分类可能会因不熟悉的标签组合而失败,但多输出分类可以优雅地处理任务。
相同的操作得到确认 - 我们的网络没有接受过“蓝鞋”图像的培训,但我们能够通过使用我们的两个子网以及多输出和多损失分类来正确地对它们进行分类。