深度学习进阶,多个输出和多个损失实现多标签分类

AI浩 发表于 2021/12/22 22:10:18 2021/12/22
【摘要】 Keras:多个输出和多个损失 几周前,我们讨论了如何使用 Keras 和深度学习执行多标签分类。 今天,我们将讨论一种更高级的技术,称为多输出分类。 那么,两者有什么区别呢? 在多标签分类中,您的网...

Keras:多个输出和多个损失

几周前,我们讨论了如何使用 Keras 和深度学习执行多标签分类。 今天,我们将讨论一种更高级的技术,称为多输出分类。 那么,两者有什么区别呢?

在多标签分类中,您的网络在负责分类的网络末端只有一组全连接层(即“头”)。 但是在多输出分类中,您的网络至少分支两次(有时更多),在网络末端创建多组完全连接的头——然后您的网络可以为每个头预测一组类标签,从而有可能学习不相交的标签组合。 您甚至可以将多标签分类与多输出分类结合起来,这样每个全连接的头部都可以预测多个输出!

今天的教程来指导你使用 Keras 进行多输出分类,要了解如何通过 TensorFlow 和 Keras 使用多个输出和多个损失,请继续阅读!

img

在今天的博客文章中,我们将学习如何使用:

  • 多重损失函数
  • 多路输出

多输出深度学习数据集

在这里插入图片描述

数据集地址:链接:https://pan.baidu.com/s/1eANXTnWl2nf853IEiLOvWg

提取码:jo4h

我们的数据集由5547张图片组成,它们来自12个不同的种类,包括:

  • black_dress(333张图片)
  • black_jeans(344张图片)
  • black_shirt(436张图片)
  • black_shoe(534张图片)
  • blue_dress(386张图片)
  • blue_jeans(356张图片)
  • blue_shirt(369张图片)
  • red_dress(384张图片)
  • red_shirt(332张图片)
  • red_shoe(486张图片)
  • white_bag(747张图片)
  • white_shoe(840张图片)

我们的卷积神经网络的目标是同时预测颜色和服饰类别。代码使用Tensorflow2.0以上版本编写。下面对我实现算法的代码作讲解:

项目结构

$ 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
├── model
│   ├── __init__.py
│   └── fashionnet.py
├── train.py
└── classify.py

  
 
  • 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

在上面你可以找到我们的项目结构,但在我们继续之前,让我们先回顾一下内容。 有 3 个值得注意的 Python 文件:

  • model/fashionnet.py :我们的多输出分类网络文件包含由三种方法组成的 FashionNet 架构类: build_category_branch 、 build_color_branch 和 build 。我们将在下一节详细回顾这些方法。
  • train.py :此脚本将训练 FashionNet 模型并在此过程中生成输出文件夹中的所有文件。
  • category.py :此脚本加载我们训练好的网络并使用多输出分类对示例图像进行分类。

我们还有 4 个顶级目录:

  • dataset/ :我们的时尚数据集,是使用他们的 API 从 Bing Image Search 中抓取的。我们在上一节中介绍了数据集。要以与我相同的方式创建您自己的数据集,请参阅如何(快速)构建深度学习图像数据集。
  • examples/ :我们有一些示例图像,我们将在本博文的最后一节中将它们与我们的分类.py 脚本结合使用。
  • output/ :我们的 train.py 脚本生成了一些输出文件:
  • fashion.model :我们的序列化 Keras 模型。
  • category_lb.pickle :服装类别的序列化 LabelBinarizer 对象由 scikit-learn 生成。这个文件可以通过我们的classify.py 脚本加载(并调用标签
  • color_lb.pickle :颜色的 LabelBinarizer 对象。
  • output_accs.png :精度训练图图像。
  • output_losses.png :损失训练图图像。
  • model/ :这是一个包含 FashionNet 类的 Python 模块。

快速回顾我们的多输出 Keras 架构

为了使用 Keras 执行多输出预测,我们将实现一个名为 FashionNet 的特殊网络架构(我为这篇博文而创建)。

FashionNet 架构包含两个特殊组件,包括:

  • 网络早期的一个分支,将网络分成两个“子网络”——一个负责服装类型分类,另一个负责颜色分类。
  • 网络末端的两个(不相交)全连接头,每个头负责各自的分类职责。

在我们开始实现 FashionNet 之前,让我们可视化这些组件中的每一个,第一个是分支:

在这里插入图片描述

在这个网络架构图中,您可以看到我们的网络接受 96 x 96 x 3 的输入图像。

然后我们立即创建两个分支:

  1. 左边的分支负责对服装类别进行分类。
  2. 右侧的分支处理颜色分类。

每个分支执行其各自的一组卷积、激活、批量归一化、池化和 dropout 操作,直到我们达到最终输出:

img

图 5:我们的深度学习 Keras 多输出分类网络可以学习不相交的标签组合。

请注意这些全连接 (FC) 头集如何与我们在本博客中研究过的其他架构中的 FC 层相似——但现在有两个,每个都负责其给定的分类任务。

网络右侧的分支明显比左侧分支浅(没有那么深)。 预测颜色比预测服装类别要容易得多,因此颜色分支相对较浅。

为了了解我们如何实现这样的架构,让我们继续下一节。

实施我们的“FashionNet”架构

img

图 6:Keras 深度学习库具有执行多输出分类所需的所有功能。

新建fashionnet.py 在里面增加:

# import the necessary packages
from tensorflow.keras.models import Model
from tensorflow.keras.layers import BatchNormalization
from tensorflow.keras.layers import Conv2D
from tensorflow.keras.layers import MaxPooling2D
from tensorflow.keras.layers import Activation
from tensorflow.keras.layers import Dropout
from tensorflow.keras.layers import Lambda
from tensorflow.keras.layers import Dense
from tensorflow.keras.layers import Flatten
from tensorflow.keras.layers import Input
import tensorflow as tf

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

我们首先从 Keras 库导入模块,然后导入 TensorFlow 本身。

由于我们的网络由两个子网络组成,我们将定义两个函数来负责构建各自的分支。

第一个, build_category_branch ,用于分类服装类型,定义如下:

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)

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

build_category_branch 具有三个值得注意的参数:

  1. 输入:我们类别分支子网络的输入量。
  2. numCategories :类别的数量,例如“连衣裙”、“鞋子”、“牛仔裤”、“衬衫”等。
  3. finalAct :最终激活层类型,默认为 softmax 分类器。如果您同时执行多输出和多标签分类,您可能希望将此激活更改为 sigmoid。

我们使用 Lambda 层将图像从 RGB 转换为灰度。

为什么要这样做? 嗯,不管是红色、蓝色、绿色、黑色还是紫色,裙子都是裙子,对吧? 因此,我们决定丢弃任何颜色信息,转而关注图像中的实际结构成分,确保我们的网络不会学习将特定颜色与服装类型联合关联。

然后我们继续构建我们的 CONV => RELU => POOL 块和 dropout。请注意,我们使用的是 TensorFlow/Keras 的函数式 API;我们需要功能性 API 来创建我们的分支网络结构。

我们的第一个 CONV 层有 32 个滤波器,带有 3 x 3 内核和 RELU 激活(整流线性单元)。我们应用批量归一化、最大池化和 25% dropout。 Dropout是将节点从当前层随机断开到下一层的过程。这种随机断开的过程自然有助于网络减少过度拟合,因为层中没有一个节点负责预测某个类、对象、边缘或角。

接下来是我们的两组 (CONV => RELU) * 2 => POOL 块:

       # (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)

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

此代码块中过滤器、内核和池大小的变化协同工作,以逐渐减小空间大小但增加深度。

让我们将它与 FC => RELU 层结合起来:

        # 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

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

最后一个激活层是完全连接的,并且具有与我们的 numCategories 相同数量的神经元/输出。

请注意,我们将最终激活层命名为“category_output”。这很重要,因为我们稍后将在 train.py 中按名称引用该层。

让我们定义用于构建多输出分类网络的第二个函数。 这个名为 build_color_branch ,顾名思义,它负责对图像中的颜色进行分类:

    @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)

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

我们的 build_color_branch 参数与 build_category_branch 基本相同。

我们用 numColors (不同于 numCategories )来区分最后一层的激活次数。

这一次,我们不会应用 Lambda 灰度转换层,因为我们实际上关心的是网络这个区域的颜色。 如果我们转换为灰度,我们将丢失所有颜色信息!

网络的这个分支比服装类别分支浅得多,因为手头的任务要简单得多。 我们要求子网络完成的只是对颜色进行分类——子网络不必那么深。

就像我们的类别分支一样,我们有第二个完全连接的头部。 让我们构建 FC => RELU 块来完成:

        # 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

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

为了区分颜色分支的最终激活层,我提供了 name=“color_output” 关键字参数。我们将在训练脚本中引用名称。 我们构建 FashionNet 的最后一步是将我们的两个分支放在一起并构建最终架构:

    @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

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

定义build函数,有5个参数。 build 函数假设我们使用的是 TensorFlow 和最后一次排序的通道。

inputShape 元组是明确排序的 (height, width, 3) ,其中 3 代表 RGB 通道。 如果您想使用 TensorFlow 以外的后端,您需要修改代码以:(1) 正确地为您的后端设置正确的通道顺序,以及 (2) 实现一个自定义层来处理 RGB 到灰度的转换。 从那里,我们定义了网络的两个分支,然后将它们放在一个模型中。 关键是我们的分支有一个共同的输入,但有两个不同的输出(服装类型和颜色分类)。

实现多输出和多损失训练脚本

现在我们已经实现了我们的 FashionNet 架构,让我们训练它! 准备好后,打开 train.py 并深入研究:

# set the matplotlib backend so figures can be saved in the background
import matplotlib
matplotlib.use("Agg")
# import the necessary packages
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.preprocessing.image import img_to_array
from sklearn.preprocessing import LabelBinarizer
from sklearn.model_selection import train_test_split
from model.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

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

我们首先为脚本导入必要的包。

从那里我们解析我们的命令行参数:

# construct the argument parser 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())

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

我们很快就会看到如何运行训练脚本。 现在,只知道 --dataset 是我们数据集的输入文件路径, --model 、 --categorybin 、 --colorbin 都是三个输出文件路径。

或者,您可以使用 --plot 参数为生成的精度/损失图指定基本文件名。

现在,让我们建立四个重要的训练变量:

# 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)

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

我们设置以下变量:

  1. EPOCHS : epoch 数设置为 50 。通过实验,我发现 50 个 epoch 生成的模型具有低损失并且没有过拟合到训练集(或尽可能不过拟合)。
  2. INIT_LR :我们的初始学习率设置为 0.001 。学习率控制着我们沿着梯度所做的“步骤”。较小的值表示较小的步长,较大的值表示较大的步长。我们很快就会看到我们将使用 Adam 优化器,同时随着时间的推移逐渐降低学习率。
  3. BS:我们将以 32 的批量大小训练我们的网络。
  4. IMAGE_DIMS :所有输入图像都将调整为 96 x 96,具有 3 个通道 (RGB)。我们正在使用这些维度进行训练,我们的网络架构输入维度也反映了这些维度。当我们在后面的部分中使用示例图像测试我们的网络时,测试维度必须与训练维度匹配。

我们的下一步是抓取我们的图像路径并随机打乱它们。我们还将初始化列表以分别保存图像本身以及服装类别和颜色:

# 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 = []

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

随后,我们将遍历 imagePaths 、预处理并填充 data 、 categoryLabels 和 colorLabels 列表:

# 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)

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

我们开始遍历我们的 imagePaths。

在循环内部,我们加载图像并将其调整为 IMAGE_DIMS 。 我们还将图像从 BGR 排序转换为 RGB。 我们为什么要进行这种转换? 回想一下我们在 build_category_branch 函数中的 FashionNet 类,我们在 Lambda 函数/层中使用了 TensorFlow 的 rgb_to_grayscale 转换。 因此,我们首先转换为 RGB,并最终将预处理后的图像附加到数据列表中。

接下来,仍然在循环内部,我们从当前图像所在的目录名称中提取颜色和类别标签。

要查看此操作,只需在终端中启动 Python,并提供一个示例 imagePath 进行实验,如下所示:

$ python
>>> import os
>>> imagePath = "dataset/red_dress/00000000.jpg"
>>> (color, cat) = imagePath.split(os.path.sep)[-2].split("_")
>>> color
'red'
>>> cat
'dress'

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

您当然可以按您希望的任何方式组织您的目录结构(但您必须修改代码)。 我最喜欢的两种方法包括 (1) 为每个标签使用子目录或 (2) 将所有图像存储在单个目录中,然后创建 CSV 或 JSON 文件以将图像文件名映射到它们的标签。 让我们将三个列表转换为 NumPy 数组,对标签进行二值化,并将数据划分为训练和测试分割:

# 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

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

我们的最后一个预处理步骤——转换为 NumPy 数组并将原始像素强度缩放为 [0, 1]。

我们还将 categoryLabels 和 colorLabels 转换为 NumPy 数组。 这是必要的,因为在我们的下一个中,我们将使用我们之前导入的 scikit-learn 的 LabelBinarizer对标签进行二值化。 由于我们的网络有两个独立的分支,我们可以使用两个独立的标签二值化器——这与我们使用 MultiLabelBinarizer(也来自 scikit-learn)的多标签分类不同。

接下来,我们对数据集执行典型的 80% 训练/20% 测试拆分。

让我们构建网络,定义我们的独立损失,并编译我们的模型:

# 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"])

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

实例化多输出 FashionNet 模型。我们在创建 FashionNet 类并在其中构建函数时剖析了参数,因此请务必查看我们在此处实际提供的值。

接下来,我们需要为每个完全连接的头部定义两个损失。

定义多个损失是通过使用每个分支激活层的名称的字典来完成的——这就是我们在 FashionNet 实现中命名输出层的原因!每个损失都将使用分类交叉熵,这是训练网络进行大于 2 类分类时使用的标准损失方法。

我们还在单独字典(具有相同值的同名键)中定义了相等的 lossWeights。在您的特定应用程序中,您可能希望对一个损失进行比另一个更重的加权。

现在我们已经实例化了我们的模型并创建了 loss + lossWeights 字典,让我们用学习率衰减初始化 Adam 优化器并编译我们的模型。

我们的下一个块只是开始训练过程:

# train the network to perform multi-output classification
H = model.fit(x=trainX,
	y={"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"], save_format="h5")

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

请注意,对于 TensorFlow 2.0+,我们建议明确设置 save_format=“h5”(HDF5 格式)。

回想一下第 87-90 行,我们将数据拆分为训练 (trainX) 和测试 (testX)。 在第 114-119 行,我们在提供数据的同时启动了训练过程。 请注意第 115 行,我们将标签作为字典传入。 第 116 行和第 117 行也是如此,我们为验证数据传入了一个 2 元组。 在使用 Keras 执行多输出分类时,需要以这种方式传递训练和验证标签。 我们需要指示 Keras 哪一组目标标签对应于网络的哪个输出分支。 使用我们的命令行参数 (args[“model”] ),我们将序列化模型保存到磁盘以备将来调用。 我们也会做同样的事情来将我们的标签二值化器保存为序列化的 pickle 文件:

# 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()

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

使用命令行参数路径(args[“categorybin”] 和 args[“colorbin”]),我们将两个标签二值化器(categoryLB 和 colorLB)写入磁盘上的序列化 pickle 文件。

从那里开始,就是在这个脚本中绘制结果:

# 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
plt.tight_layout()
plt.savefig("{}_losses.png".format(args["plot"]))
plt.close()

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

上面的代码块负责在单独但堆叠的图上绘制每个损失函数的损失历史,包括:

  • 总体损耗
  • 类别输出的损失
  • 颜色输出的损失

同样,我们将在单独的图像文件中绘制精度:

# create a new figure for the accuracies
accuracyNames = ["category_output_accuracy", "color_output_accuracy"]
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()

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

训练多输出/多损失 Keras 模型

打开终端。 然后粘贴以下命令以开始训练过程:

$ 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] loading images...
[INFO] data matrix: 2521 images (544.54MB)
[INFO] binarizing labels...
[INFO] compiling model...
Epoch 1/50
63/63 [==============================] - 1s 20ms/step - loss: 0.8523 - category_output_loss: 0.5301 - color_output_loss: 0.3222 - category_output_accuracy: 0.8264 - color_output_accuracy: 0.8780 - val_loss: 3.6909 - val_category_output_loss: 1.8052 - val_color_output_loss: 1.8857 - val_category_output_accuracy: 0.3188 - val_color_output_accuracy: 0.4416
Epoch 2/50
63/63 [==============================] - 1s 14ms/step - loss: 0.4367 - category_output_loss: 0.3092 - color_output_loss: 0.1276 - category_output_accuracy: 0.9033 - color_output_accuracy: 0.9519 - val_loss: 7.0533 - val_category_output_loss: 2.9279 - val_color_output_loss: 4.1254 - val_category_output_accuracy: 0.3188 - val_color_output_accuracy: 0.4416
Epoch 3/50
63/63 [==============================] - 1s 14ms/step - loss: 0.2892 - category_output_loss: 0.1952 - color_output_loss: 0.0940 - category_output_accuracy: 0.9350 - color_output_accuracy: 0.9653 - val_loss: 6.2512 - val_category_output_loss: 2.0540 - val_color_output_loss: 4.1972 - val_category_output_accuracy: 0.4020 - val_color_output_accuracy: 0.4416
...
Epoch 48/50
63/63 [==============================] - 1s 14ms/step - loss: 0.0189 - category_output_loss: 0.0106 - color_output_loss: 0.0083 - category_output_accuracy: 0.9960 - color_output_accuracy: 0.9970 - val_loss: 0.2625 - val_category_output_loss: 0.2250 - val_color_output_loss: 0.0376 - val_category_output_accuracy: 0.9564 - val_color_output_accuracy: 0.9861
Epoch 49/50
63/63 [==============================] - 1s 14ms/step - loss: 0.0190 - category_output_loss: 0.0041 - color_output_loss: 0.0148 - category_output_accuracy: 0.9985 - color_output_accuracy: 0.9950 - val_loss: 0.2333 - val_category_output_loss: 0.1927 - val_color_output_loss: 0.0406 - val_category_output_accuracy: 0.9604 - val_color_output_accuracy: 0.9881
Epoch 50/50
63/63 [==============================] - 1s 14ms/step - loss: 0.0188 - category_output_loss: 0.0046 - color_output_loss: 0.0142 - category_output_accuracy: 0.9990 - color_output_accuracy: 0.9960 - val_loss: 0.2140 - val_category_output_loss: 0.1719 - val_color_output_loss: 0.0421 - val_category_output_accuracy: 0.9624 - val_color_output_accuracy: 0.9861
[INFO] serializing network...
[INFO] serializing category label binarizer...
[INFO] serializing color label binarizer...

  
 
  • 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

对于我们的类别输出,我们获得了:

  • 训练集上 99.90% 的准确率

  • 在测试集上的准确率为 96.24%

对于我们达到的颜色输出:

  • 99.60% 的训练集准确率

  • 在测试集上的准确率为 98.61%

您可以在下面找到我们多个损失中的每一个的图:

img

图 7:我们的 Keras 深度学习多输出分类训练损失是用 matplotlib 绘制的。 我们的总损失(顶部)、服装类别损失(中间)和颜色损失(底部)被独立绘制以供分析。

以及我们的多重精度:

img

图 8:FashionNet,一个多输出分类网络,用 Keras 训练。 为了分析训练,最好在单独的图中显示准确度。 服装类别训练准确率图(上)。 颜色训练精度图(底部)。

实现多输出分类脚本

打开classify.py,插入如下代码:

# import the necessary packages
from tensorflow.keras.preprocessing.image import img_to_array
from tensorflow.keras.models import load_model
import tensorflow as tf
import numpy as np
import argparse
import imutils
import pickle
import cv2
# construct the argument parser 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())

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

我们有四个命令行参数,这些参数是在您的终端中运行此脚本所必需的:

  • –model :我们刚刚训练的序列化模型文件的路径(我们之前脚本的输出)。
  • –categorybin :类别标签二值化器的路径(我们之前脚本的输出)。
  • –colorbin :颜色标签二值化器的路径(我们之前脚本的输出)。
  • –image :我们的测试图像文件路径——这个图像将来自我们的 examples/ 目录。

从那里,我们加载我们的图像并对其进行预处理:

# load the image
image = cv2.imread(args["image"])
output = imutils.resize(image, width=400)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# pre-process 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)

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在我们运行推理之前需要预处理我们的图像。 在上面的块中,我们加载图像,调整其大小以用于输出目的,并交换颜色通道,以便我们可以在 FashionNet 的 Lambda 层中使用 TensorFlow 的 RGB 灰度函数。 然后我们调整 RGB 图像的大小(从我们的训练脚本中调用 IMAGE_DIMS),将其缩放为 [0, 1],转换为 NumPy 数组,并为批次添加维度。

预处理步骤遵循在我们的训练脚本中采取的相同操作是至关重要的。

接下来,让我们加载我们的序列化模型和两个标签二值化器:

# 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())

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

使用四个命令行参数中的三个,我们加载 model 、 categoryLB 和 colorLB 。

现在 多输出 Keras 模型和 标签二值化器都在内存中,我们可以对图像进行分类:

# 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]

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

执行多输出分类,得出类别和颜色的概率(分别为 categoryProba 和 colorProba)。

注意:我没有包含包含代码,因为它有点冗长,但您可以通过检查输出张量的名称来确定 TensorFlow + Keras 模型返回多个输出的顺序。

从那里,我们将提取类别和颜色的最高概率指数。 使用高概率指数,我们可以提取类名。 这似乎有点太容易了,不是吗? 但这就是使用 Keras 将多输出分类应用于新输入图像的全部内容!

让我们显示结果来证明它:

# 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)

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

我们在输出图像上显示结果。 如果我们遇到“红色连衣裙”,它会在左上角的绿色文本中看起来像这样: 类别:连衣裙 (89.04%) 颜色:红色(95.07%) 相同的信息打印到终端,之后输出图像显示在屏幕上。

使用 Keras 执行多输出分类

现在是有趣的部分了!

在本节中,我们将向我们的网络展示示例目录中不属于训练集的五张图像。 关键是我们的网络只经过专门训练,可以识别两个示例图像类别。 前两张图片(“黑色牛仔裤”和“红色衬衫”)应该特别容易让我们的网络正确分类类别和颜色。

剩下的三个图像对我们的模型来说是完全陌生的——我们没有用“红鞋”、“蓝鞋”或“黑裙”进行训练,但我们将尝试多输出分类,看看会发生什么。

让我们从“黑色牛仔裤”开始——这个应该很容易,因为在训练数据集中有很多相似的图像。 请务必使用四个命令行参数,如下所示:

$ 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

img

总结

在今天的博文中,我们学习了如何利用 Keras 深度学习库中的多个输出和多个损失函数。

为了完成这项任务,我们定义了一个 Keras 架构,用于时尚/服装分类,称为 FashionNet。

FashionNet 架构包含两个分支:

  • 一个叉负责对给定输入图像的服装类型(例如,衬衫、连衣裙、牛仔裤、鞋子等)进行分类。
  • 而第二个叉子负责对衣服的颜色进行分类(黑色、红色、蓝色等)。 这个分支发生在网络的早期,本质上创建了两个“子网络”,它们负责各自的分类任务,但都包含在同一个网络中。

最值得注意的是,多输出分类使我们能够解决上一篇关于多标签分类的文章中的一个问题,其中: 我们在六个类别上训练我们的网络,包括:黑色牛仔裤、蓝色连衣裙、蓝色牛仔裤、蓝色衬衫、红色连衣裙和红色衬衫…… ……但我们无法对“黑裙”进行分类,因为我们的网络以前从未见过这种数据组合!
通过创建两个完全连接的头部和相关的子网络(如有必要),我们可以训练一个头部对服装类型进行分类,另一个可以学习如何识别颜色——最终结果是一个可以对“黑色连衣裙”进行分类的网络它从未接受过此类数据的训练!

源码和数据集下载地址:
https://download.csdn.net/download/hhhhhhhhhhwwwwwwwwww/35593098

文章来源: wanghao.blog.csdn.net,作者:AI浩,版权归原作者所有,如需转载,请联系作者。

原文链接:wanghao.blog.csdn.net/article/details/121079135

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:cloudbbs@huaweicloud.com进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。