使用Keras进行单模型多标签分类

原文:https://www.pyimagesearch.com/2018/05/07/multi-label-classification-with-keras/
作者:Adrian Rosebrock
时间:2018年5月7日
源码:https://pan.baidu.com/s/1x7waggprAHQDjalkA-ctvg (wa61)
译者:XerCis
备注:时间关系,大部分为Google翻译。细讲代码时,对应源代码行号。
类似:https://blog.csdn.net/tMb8Z9Vdm66wH68VX1/article/details/81090757

文章目录

  • 使用Keras进行单模型多标签分类
    • 数据集
    • 项目结构
    • 网络结构
    • 模型
    • 模型训练
    • 模型预测
    • 模型预测结果
    • 总结





今天关于Keras多标签分类的博客文章的灵感源自我上周从PyImageSearch读者Switaj收到的一封电子邮件。
Switaj写道:

嗨Adrian,感谢PyImageSearch博客并每周分享您的知识。
我正在建立一个图像时尚搜索引擎,需要帮助。
使用我的应用程序,用户将上传他们喜欢的服装照片(例如衬衫,衣服,裤子,鞋子),我的系统将返回类似的项目,并包括他们在线购买衣服的链接。
问题是我需要训练分类器将项目分类为不同的类:
1.服装类型:衬衫,连衣裙,裤子,鞋子等
2.颜色:红色,蓝色,绿色,黑色等
3.质地/外观:棉,羊毛,丝绸,花呢等

我为这三个类别中的每个类别训练了三个独立的CNN ,它们的效果非常好。
有没有办法将三个CNN组合成一个网络? 或者至少训练一个网络来完成所有三个分类任务
我不想在if / else代码的级联中单独应用它们,这些代码使用不同的网络,具体取决于先前分类的输出。
谢谢你的帮助。

Switaj提出了一个很好的问题:
Keras深度神经网络有可能返回多个预测吗?
如果是这样,它是如何完成的?







使用Keras进行单模型多标签分类

今天关于多标签分类的博客文章分为四个部分。

在第一部分中,我将讨论我们的多标签分类数据集(以及如何快速构建自己的数据集)。

我们将简要讨论SmallerVGGNet,我们将实现的Keras神经网络架构,并用于多标签分类。

然后我们将实施SmallerVGGNet并使用我们的多标签分类数据集对其进行训练。

最后,我们将通过在示例图像上测试我们的网络来结束今天的博客文章,并讨论何时适合多标签分类,包括您需要注意的一些注意事项。

数据集

使用Keras进行单模型多标签分类_第1张图片

图1:多类深度学习数据集的蒙太奇。 我们将使用Keras训练多标签分类器来预测服装的颜色和类型


我们的数据集包含六个类别2,167个图像,包括:

  • 黑色牛仔裤(344张)
  • 蓝色连衣裙(386张)
  • 蓝色牛仔裤(356张)
  • 蓝色衬衫(369张)
  • 红色连衣裙(380张)
  • 红色衬衫(332张)

我们的卷积神经网络的目标是预测颜色和服装类型。



项目结构

源码解压后您将看到以下目录结构:

├──  classify .py 
├──  dataset 
│   ├──  black _ jeans  [ 344   entries 
│   ├──  blue _ dress  [ 386   entries ] 
│   ├──  blue _ jeans  [ 356   entries ] 
│   ├──  blue _ shirt  [ 369   entries ] 
│   ├──  red _ dress  [ 380   entries ] 
│   └──  red _ shirt  [ 332   entries ] 
├──  examples 
│   ├──  example_01 .jpg 
│   ├──  example_02 .jpg 
│   ├──  example_03 .jpg 
│   ├──  example_04 .jpg 
│   ├──  example_05 .jpg 
│   ├──  example_06 .jpg 
│   └──  example_07 .jpg 
├──  fashion .model 
├──  mlb .pickle 
├──  plot .png 
├──  pyimagesearch 
│   ├──  __init__ .py 
│   └──  smallervggnet .py 
├──  search_bing_api .py 
└──  train .py

在根目录中,您将看到6个文件3个目录

我们正在处理的重要文件(在本文中出现的近似顺序)包括:

  1. search_bing_api.py :此脚本使我们能够快速构建深度学习图像数据集 。 您不需要运行此脚本,因为图像数据集已包含在zip存档中。 我只是简单地包含这个脚本保证完整性。
  2. train.py :一旦我们获得了数据,我们将使用train.py脚本来训练我们的分类器。
  3. fashion.model :我们的train.py脚本将我们的Keras模型序列化到磁盘。 我们稍后将在分类中使用此train.py脚本。
  4. mlb.pickle :由train.py创建的scikit-learn MultiLabelBinarizer pickle文件 - 该文件将我们的类名保存在方便的序列化数据结构中。
  5. plot.png :训练脚本将生成一个plot.png图像文件。 如果您正在训练自己的数据集,则需要检查此文件的准确性/loss和过拟合。
  6. classify.py :为了测试我们的分类器,我写了classify.py在将模型部署到其他地方之前(例如iPhone深度学习应用程序或Raspberry Pi深度学习项目 ),您应始终在本地测试分类器。

今天项目中的三个目录是:

  1. dataset :此目录包含我们的图像数据集。 每个类类都有自己的子目录。 我们这样做是为了(1)保持我们的数据集有条理,(2)从给定的图像路径中提取类标签名更容易。
  2. pyimagesearch :这是我们的模块,包含我们的Keras神经网络。 因为这是一个模块,它包含固定格式的__init__ .py 。 另一个文件,smallvggnet.py包含组装神经网络本身的代码。
  3. examples :此目录中存在七个示例图像。 我们将classify.py对示例图像执行多标签分类。




网络结构

使用Keras进行单模型多标签分类_第2张图片

图2:我称之为“SmallerVGGNet”的类似VGGNet的网络将用于训练使用Keras的多标签深度学习分类器


我们在本教程中使用的CNN架构是SmallerVGGNet,它是它的大哥VGGNet的简化版本。 Simonyan和Zisserman在2014年的论文Very Deep Convolutional Networks for Large Scale Image Recognition中首次引入了VGGNet模型。

从完整性考虑,我们将在本指南中实现SmallerVGGNet ; 但是,对架构/代码的细致解释请查阅我以前的帖子——如果您对架构有任何疑问或者只是寻找更多细节,请参考它。 如果您正在设计自己的模型,那么您将需要获取我的书Deep Learning for Computer Vision with Python。

确保您已下载源代码+示例图像。 打开pyimagesearch模块中的smallvggnet.py文件,继续:

# import the necessary packages
from keras.models import Sequential
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 Flatten
from keras.layers.core import Dropout
from keras.layers.core import Dense
from keras import backend as K

在第2-10行 ,我们导入相关的Keras模块,然后从那里创建我们的SmallerVGGNet类:

class SmallerVGGNet:
	@staticmethod
	def build(width, height, depth, classes, finalAct="softmax"):
		# initialize the model along with the input shape to be
		# "channels last" and the channels dimension itself
		model = Sequential()
		inputShape = (height, width, depth)
		chanDim = -1
 
		# if we are using "channels first", update the input shape
		# and channels dimension
		if K.image_data_format() == "channels_first":
			inputShape = (depth, height, width)
			chanDim = 1

我们的类在第12行定义。 然后我们在第14行定义bulid函数,负责组装卷积神经网络。

build方法需要四个参数——width,height,depth,classesdepth指定输入图像中的通道数, classes是类别数(整数,不是类标签本身)。 我们将在训练脚本中使用这些参数来实例化模型,以96 x 96 x 3作为输入。

可选参数finalAct (默认值为“softmax” )将在网络体系结构的末尾使用。 将此值从softmax更改为sigmoid将使我们能够使用Keras执行多标签分类。

请记住,这种行为是不同的。这与我们之前文章中的SmallerVGGNet实现不同——我们在这里添加它,以便我们控制是执行简单分类还是多类分类。

现在让我们看build函数内部,初始化model( 第17行 )并在第18行和第19行默认管道输入顺序为“channels_last”(在第23-25行确保“channels_first”架构的后端方便切换)。

让我们构建第一个CONV = > RELU = > POOL块:
(CONV卷积神经网络,RELU线性整流函数,POOL池化)

		# CONV => RELU => POOL
		model.add(Conv2D(32, (3, 3), padding="same",
			input_shape=inputShape))
		model.add(Activation("relu"))
		model.add(BatchNormalization(axis=chanDim))
		model.add(MaxPooling2D(pool_size=(3, 3)))
		model.add(Dropout(0.25))

我们的CONV层有32个过滤器,一个3 x 3内核和RELU激活(整流线性单元)。 我们应用批标准化,最大池化和25%的dropout。

Dropout是将节点从当前层随机断开到下一层的过程。 这种随机断开的过程自然有助于网络减少过拟合。过拟合的产生源于层中没有一个独立节点预测某个类,对象,边缘或角落。

接下来我们有两套(CONV => RELU) * 2 => POOL块:

		# (CONV => RELU) * 2 => POOL
		model.add(Conv2D(64, (3, 3), padding="same"))
		model.add(Activation("relu"))
		model.add(BatchNormalization(axis=chanDim))
		model.add(Conv2D(64, (3, 3), padding="same"))
		model.add(Activation("relu"))
		model.add(BatchNormalization(axis=chanDim))
		model.add(MaxPooling2D(pool_size=(2, 2)))
		model.add(Dropout(0.25))
 
		# (CONV => RELU) * 2 => POOL
		model.add(Conv2D(128, (3, 3), padding="same"))
		model.add(Activation("relu"))
		model.add(BatchNormalization(axis=chanDim))
		model.add(Conv2D(128, (3, 3), padding="same"))
		model.add(Activation("relu"))
		model.add(BatchNormalization(axis=chanDim))
		model.add(MaxPooling2D(pool_size=(2, 2)))
		model.add(Dropout(0.25))

请注意此代码块中过滤器,核和池大小的更改,它们协同工作以逐步减小空间大小但增加深度。

后面跟着唯一的的FC => RELU层:

		# first (and only) set of FC => RELU layers
		model.add(Flatten())
		model.add(Dense(1024))
		model.add(Activation("relu"))
		model.add(BatchNormalization())
		model.add(Dropout(0.5))
 
		# use a *softmax* activation for single-label classification
		# and *sigmoid* activation for multi-label classification
		model.add(Dense(classes))
		model.add(Activation(finalAct))
 
		# return the constructed network architecture
		return model

全连接层放在网络的末端(由第57和64行上的Dense指定)。

第65行对于我们的多标签分类非常重要——finalAct决定了使用“softmax”激活函数进行单标签分类或“sigmoid”激活函数进行多标签分类。 请参阅此脚本的第14行 , smallvgggnet .py和train .py的 第95行



模型

现在我们已经实现了SmallerVGGNet,让我们创建train.py ,我们将使用该脚本来训练我们的Keras网络进行多标签分类。

我恳请您查看今天的train.py脚本所依据的上一篇文章 。 实际上,您可能希望在屏幕上并排查看它们以查看差异并阅读完整说明。 相比之下,今天的评论将简洁明了。

打开train.py并插入以下代码:

# set the matplotlib backend so figures can be saved in the background
import matplotlib
matplotlib.use("Agg")
 
# import the necessary packages
from keras.preprocessing.image import ImageDataGenerator
from keras.optimizers import Adam
from keras.preprocessing.image import img_to_array
from sklearn.preprocessing import MultiLabelBinarizer
from sklearn.model_selection import train_test_split
from pyimagesearch.smallervggnet import SmallerVGGNet
import matplotlib.pyplot as plt
from imutils import paths
import numpy as np
import argparse
import random
import pickle
import cv2
import os

第2-19行,我们导入了此脚本所需的包和模块。 第3行指定了matplotlib后端,以便我们可以在后台保存我们的绘图。

我将假设你已经安装了Keras,scikit-learn,matpolotlib,imutils和OpenCV。

如果这是您的第一个深度学习体验,有两个选项可以确保您准备好正确的库和包:

  1. 预先配置的环境(您将在不到5分钟的时间内启动并运行,您可以花比一杯星巴克咖啡更少的钱训练今天的网络)
  2. 建立自己的环境(需要时间,耐心和持久性)

我喜欢云中的预配置环境,您可以启动,上传文件,训练+抓取您的数据,然后在几分钟内终止。 我推荐的两个预先配置的环境是:

  • 使用Python预配置的Amazon AWS深度学习AMI
  • 用于深度学习的Microsoft数据科学虚拟机(DSVM)

如果您坚持设置自己的环境(并且您有时间进行调试和排除故障),我建议您遵循以下任何博文:

  • 使用Python配置Ubuntu进行深度学习(仅限CPU)
  • 使用Python设置Ubuntu 16.04 + CUDA + GPU进行深度学习(GPU和CPU)
  • 使用Python,TensorFlow和Keras进行深度学习的macOS

既然(a)您的环境已经准备就绪,并且(b)您已经导入了包,那么让我们解析命令行参数:

# 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", "--labelbin", required=True,
	help="path to output label binarizer")
ap.add_argument("-p", "--plot", type=str, default="plot.png",
	help="path to output accuracy/loss plot")
args = vars(ap.parse_args())

脚本的命令行参数类似于函数的参数——如果您不理解这种类比,那么您需要阅读命令行参数 。

我们今天正在使用四个命令行参数( 第23-30行 ):

  1. --dataset : 数据集的路径。
  2. --model :输出序列化Keras模型的路径。
  3. --labelbin :输出多标签二进制文件对象的路径。
  4. --plot:我们输出的训练损失和准确性曲线的路径。

请务必根据需要参考上一篇文章来解释这些论点。

让我们继续初始化一些在我们的训练过程中起关键作用的重要变量:

# initialize the number of epochs to train for, initial learning rate,
# batch size, and image dimensions
EPOCHS = 75
INIT_LR = 1e-3
BS = 32
IMAGE_DIMS = (96, 96, 3)

第35-38行的这些变量定义:

  • 我们的网络将迭代75个EPOCHS ,以便通过反向传播逐步改进学习模式。
  • 我们建立的初始学习率为 1 e − 3 1e^{-3} 1e3 (Adam优化器的默认值)。
  • 批量大小为32 。 如果您使用GPU,则应根据GPU功能调整此值,但我发现批量大小为32适用于此项目。
  • 如上所述,我们的图像是96 x 96并包含3个通道。

上一篇文章中提供了更多详细信息 。

接下来的两个代码块加载和预处理我们的训练数据:

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

在这里,我们抓取imagePaths并随机地对它们进行混洗,然后初始化datalabels列表。

接下来,我们将遍历imagePaths ,预处理图像数据,并提取多类标签。

# 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 = img_to_array(image)
	data.append(image)
 
	# extract set of class labels from the image path and update the
	# labels list
	l = label = imagePath.split(os.path.sep)[-2].split("_")
	labels.append(l)

首先,我们将每个图像加载到内存中( 第53行 )。 然后,我们在第54和55行执行预处理(深度学习流程的一个重要步骤)。 我们将image附加到data 中。(第56行 )。

第60和61行处理将图像路径分成多个标签以用于我们的多标签分类任务。 执行第60行后,将创建一个2元素列表,然后将其附加到第61行的labels列表中。这是在终端中分解的示例,以便您可以看到多标签解析过程中发生的情况:

$ python
>>> import os
>>> labels = []
>>> imagePath = "dataset/red_dress/long_dress_from_macys_red.png"
>>> l = label = imagePath.split(os.path.sep)[-2].split("_")
>>> l
['red', 'dress']
>>> labels.append(l)
>>>
>>> imagePath = "dataset/blue_jeans/stylish_blue_jeans_from_your_favorite_store.png"
>>> l = label = imagePath.split(os.path.sep)[-2].split("_")
>>> labels.append(l)
>>>
>>> imagePath = "dataset/red_shirt/red_shirt_from_target.png"
>>> l = label = imagePath.split(os.path.sep)[-2].split("_")
>>> labels.append(l)
>>>
>>> labels
[['red', 'dress'], ['blue', 'jeans'], ['red', 'shirt']]

如您所见, labels列表是“列表的列表”——标签的每个元素都是一个2元素列表。 每个列表的两个标签是基于输入图像的文件路径构造的。

我们还没有完成预处理:

# scale the raw pixel intensities to the range [0, 1]
data = np.array(data, dtype="float") / 255.0
labels = np.array(labels)
print("[INFO] data matrix: {} images ({:.2f}MB)".format(
	len(imagePaths), data.nbytes / (1024 * 1000.0)))

我们的data列表包含存储为NumPy数组的图像。 在一行代码中,我们将列表转换为NumPy数组,并将像素值缩放到[0,1]

我们还将标签转换为NumPy数组。

接下来,让我们对标签进行二值化——以下代码块对于本多类分类概念至关重要 :

# binarize the labels using scikit-learn's special multi-label
# binarizer implementation
print("[INFO] class labels:")
mlb = MultiLabelBinarizer()
labels = mlb.fit_transform(labels)
 
# loop over each of the possible class labels and show them
for (i, label) in enumerate(mlb.classes_):
	print("{}. {}".format(i + 1, label))

为了对我们的标签进行二值化以进行多类分类,我们需要使用scikit-learn库的MultiLabelBinarizer类。 您不能将标准LabelBinarizer类用于多类分类。 第72和73行将我们的人为可读标签拟合并转换为一个矢量,该矢量编码图像中存在哪个类。

这是一个显示MultiLabelBinarizer如何转换元组("red", "dress")到一个总共六个类别的向量:

$ python
>>> from sklearn.preprocessing import MultiLabelBinarizer
>>> labels = [
...     ("blue", "jeans"),
...     ("blue", "dress"),
...     ("red", "dress"),
...     ("red", "shirt"),
...     ("blue", "shirt"),
...     ("black", "jeans")
... ]
>>> mlb = MultiLabelBinarizer()
>>> mlb.fit(labels)
MultiLabelBinarizer(classes=None, sparse_output=False)
>>> mlb.classes_
array(['black', 'blue', 'dress', 'jeans', 'red', 'shirt'], dtype=object)
>>> mlb.transform([("red", "dress")])
array([[0, 0, 1, 0, 1, 0]])

独热编码将分类标签从单个整数转换为向量。 相同的概念适用于第16行和第17行,然而这是双热编码的情况。

注意如何在Python shell的第17行 (不要与train.py的代码块混淆)两个分类标签是“热”(在数组中用“1”表示),表示每个标签的存在。 在这种情况下,数组中的“dress”和“red”是1( 第14-17行 )。 所有其他标签的值均为“0”。

让我们拆分数据分成训练集和测试集以及初始化数据增强器:

# partition the data into training and testing splits using 80% of
# the data for training and the remaining 20% for testing
(trainX, testX, trainY, testY) = train_test_split(data,
	labels, test_size=0.2, random_state=42)
 
# construct the image generator for data augmentation
aug = ImageDataGenerator(rotation_range=25, width_shift_range=0.1,
	height_shift_range=0.1, shear_range=0.2, zoom_range=0.2,
	horizontal_flip=True, fill_mode="nearest")

拆分数据用于训练和测试在机器学习实践中很常见——我已经为训练数据分配了80%的图像,为测试数据分配了20%。 这是由第81和82行的scikit-learn处理的。

我们的数据增强器对象在第85-87行初始化。 数据增强是一种最佳实践,如果您每个类只处理少于1,000张图像,那么就十分有必要了。

接下来,让我们构建模型并初始化Adam优化器:

# initialize the model using a sigmoid activation as the final layer
# in the network so we can perform multi-label classification
print("[INFO] compiling model...")
model = SmallerVGGNet.build(
	width=IMAGE_DIMS[1], height=IMAGE_DIMS[0],
	depth=IMAGE_DIMS[2], classes=len(mlb.classes_),
	finalAct="sigmoid")
 
# initialize the optimizer
opt = Adam(lr=INIT_LR, decay=INIT_LR / EPOCHS)

第92-95行,我们构建了我们的SmallerVGGNet模型,注意到finalAct="sigmoid"参数,表明我们将执行多标签分类

然后,我们将编译模型并启动训练(这可能需要一段时间,具体取决于您的硬件):

# compile the model using binary cross-entropy rather than
# categorical cross-entropy -- this may seem counterintuitive for
# multi-label classification, but keep in mind that the goal here
# is to treat each output label as an independent Bernoulli
# distribution
model.compile(loss="binary_crossentropy", optimizer=opt,
	metrics=["accuracy"])
 
# train the network
print("[INFO] training network...")
H = model.fit_generator(
	aug.flow(trainX, trainY, batch_size=BS),
	validation_data=(testX, testY),
	steps_per_epoch=len(trainX) // BS,
	epochs=EPOCHS, verbose=1)

第105行和第106行,我们使用二元交叉熵而不是分类交叉熵来编译模型。

对于多标签分类而言,这似乎违反直觉;但是,目标是将每个输出标签视为独立的伯努利分布,并且我们希望独立地惩罚每个输出节点。

接下来我们使用我们的数据增强器( 第110-114行 )启动训练过程。

训练完成后,我们可以将模型和标签二进制文件保存到磁盘:

# save the model to disk
print("[INFO] serializing network...")
model.save(args["model"])
 
# save the multi-label binarizer to disk
print("[INFO] serializing label binarizer...")
f = open(args["labelbin"], "wb")
f.write(pickle.dumps(mlb))
f.close()

接着绘制准确性和损失:

# plot the training loss and accuracy
plt.style.use("ggplot")
plt.figure()
N = EPOCHS
plt.plot(np.arange(0, N), H.history["loss"], label="train_loss")
plt.plot(np.arange(0, N), H.history["val_loss"], label="val_loss")
plt.plot(np.arange(0, N), H.history["acc"], label="train_acc")
plt.plot(np.arange(0, N), H.history["val_acc"], label="val_acc")
plt.title("Training Loss and Accuracy")
plt.xlabel("Epoch #")
plt.ylabel("Loss/Accuracy")
plt.legend(loc="upper left")
plt.savefig(args["plot"])

训练和验证的准确度+损失绘制在第127-137行 。 该图在第138行保存为图像文件。

在我看来,训练描点与模型本身一样重要。 在我满意地在博客上与您分享之前,我通常会经历一些训练和查看描点的迭代。

我喜欢在这个迭代过程中将绘图保存到磁盘有几个原因:(1)我在无界面服务器上运行,不想依赖X-forwarding,(2)我不想忘记保存图表(即使我正在使用X-forwarding或者图形界面的计算机)。

回想一下,我们更改了上面脚本第3行的matplotlib后端,以便于保存到磁盘。




模型训练

不要忘记使用本文的“下载”部分下载代码,数据集和预先训练的模型(以防您不想自己训练模型)。

如果您想自己训练模型,请打开终端。 从那里,导航到项目目录,然后执行以下命令:

$ python train.py --dataset dataset --model fashion.model \
	--labelbin mlb.pickle
Using TensorFlow backend.
[INFO] loading images...
[INFO] data matrix: 2165 images (467.64MB)
[INFO] class labels:
1. black
2. blue
3. dress
4. jeans
5. red
6. shirt
[INFO] compiling model...
[INFO] training network...
Epoch 1/75
name: GeForce GTX TITAN X
54/54 [==============================] - 4s - loss: 0.3503 - acc: 0.8682 - val_loss: 0.9417 - val_acc: 0.6520
Epoch 2/75
54/54 [==============================] - 2s - loss: 0.1833 - acc: 0.9324 - val_loss: 0.7770 - val_acc: 0.5377
Epoch 3/75
54/54 [==============================] - 2s - loss: 0.1736 - acc: 0.9378 - val_loss: 1.1532 - val_acc: 0.6436
...
Epoch 73/75
54/54 [==============================] - 2s - loss: 0.0534 - acc: 0.9813 - val_loss: 0.0324 - val_acc: 0.9888
Epoch 74/75
54/54 [==============================] - 2s - loss: 0.0518 - acc: 0.9833 - val_loss: 0.0645 - val_acc: 0.9784
Epoch 75/75
54/54 [==============================] - 2s - loss: 0.0405 - acc: 0.9857 - val_loss: 0.0429 - val_acc: 0.9842
[INFO] serializing network...
[INFO] serializing label binarizer...

如您所见,我们训练了75个迭代的网络,实现了:

  • 训练集上的多标签分类准确率为98.57%
  • 测试装置上98.42%的多标签分类准确度

训练图如图3所示:

使用Keras进行单模型多标签分类_第3张图片

图3:我们的Keras深度学习训练和验证数据的多标签分类准确度/损失图




模型预测

现在我们的多标签分类Keras模型已经过训练,让我们将它应用于我们测试集之外的图像。

此脚本与我之前发布的classify.py脚本非常相似——请务必注意多标签差异。

准备好后,打开在名为classify.py的项目目录中创建一个新文件并插入以下代码(或者跟随“下载”中包含的文件):

# import the necessary packages
from keras.preprocessing.image import img_to_array
from keras.models import load_model
import numpy as np
import argparse
import imutils
import pickle
import cv2
import os
 
# 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", "--labelbin", required=True,
	help="path to label binarizer")
ap.add_argument("-i", "--image", required=True,
	help="path to input image")
args = vars(ap.parse_args())

第2-9行,我们为此脚本import必要的包。 值得注意的是,我们将在此脚本中使用Keras和OpenCV。

然后我们继续解析第12-19行的三个必需的命令行参数。

接着加载并预处理输入图像:

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

注意用与预处理训练数据相同的方式处理图像。

接下来,让我们加载模型+多标签二进制文件并对图像进行分类:

# load the trained convolutional neural network and the multi-label
# binarizer
print("[INFO] loading network...")
model = load_model(args["model"])
mlb = pickle.loads(open(args["labelbin"], "rb").read())
 
# classify the input image then find the indexes of the two class
# labels with the *largest* probability
print("[INFO] classifying image...")
proba = model.predict(image)[0]
idxs = np.argsort(proba)[::-1][:2]

第34和35行我们将model和多标签二进制文件从磁盘加载到内存中。

第40行对(预处理的)输入图像进行分类

第41行通过以下方式提取前两个类标签索引:

  • 按照降序排列的相关概率对数组索引进行排序
  • 抓住前两个类标签索引,这是我们网络的前2个预测

如果您愿意,可以修改此代码以返回更多类标签。 我还建议对概率进行阈值处理,并仅返回具有 > N % >N\% >N% 置信度的标签。

接下来,我们在输出图像上注明 标签+相关置信度值:

# loop over the indexes of the high confidence class labels
for (i, j) in enumerate(idxs):
	# build the label and draw the label on the image
	label = "{}: {:.2f}%".format(mlb.classes_[j], proba[j] * 100)
	cv2.putText(output, label, (10, (i * 30) + 25), 
		cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
 
# show the probabilities for each of the individual labels
for (label, p) in zip(mlb.classes_, proba):
	print("{}: {:.2f}%".format(label, p * 100))
 
# show the output image
cv2.imshow("Output", output)
cv2.waitKey(0)

第44-48行的循环绘制了output图像上的前两个多标签预测和相应的置信度值。

类似地, 第51-52行的循环打印终端中的所有预测。 这对于调试很有用。

最后,第55和56行在屏幕上显示output图像



模型预测结果

让我们使用命令行参数使classify.py工作。 您无需修改​​上面讨论的代码,以便通过CNN传递新图像。 只需在终端中使用命令行参数 ,如下所示。

让我们试一下红色连衣裙的图像——注意在运行时处理的三个命令行参数:

$ python classify.py --model fashion.model --labelbin mlb.pickle \
	--image examples/example_01.jpg
Using TensorFlow backend.
[INFO] loading network...
[INFO] classifying image...
black: 0.00%
blue: 3.58%
dress: 95.14%
jeans: 0.00%
red: 100.00%
shirt: 64.02%

使用Keras进行单模型多标签分类_第4张图片

图4:红色连衣裙的图像通过我们的Keras多标签分类深度学习脚本正确地被归类为“red”和“dress”

成功!看看两个类( “red”和“dress” )如何高置信度标记。

现在让我们尝试一件蓝色连衣裙:

$ python classify.py --model fashion.model --labelbin mlb.pickle \
	--image examples/example_02.jpg
Using TensorFlow backend.
[INFO] loading network...
[INFO] classifying image...
black: 0.03%
blue: 99.98%
dress: 98.50%
jeans: 0.23%
red: 0.00%
shirt: 0.74%

使用Keras进行单模型多标签分类_第5张图片

图5:在我们的Keras多标签图像分类项目的第二次测试中正确应用了“blue”和“dress”类标签

一件蓝色连衣裙对我们的分类器来说并不竞争。 我们有一个良好的开端,所以让我们尝试一件红色衬衫的图像:

$ python classify.py --model fashion.model --labelbin mlb.pickle \
	--image examples/example_03.jpg
Using TensorFlow backend.
[INFO] loading network...
[INFO] classifying image...
black: 0.00%
blue: 0.69%
dress: 0.00%
jeans: 0.00%
red: 100.00%
shirt: 100.00%

使用Keras进行单模型多标签分类_第6张图片

图6:凭借100%的置信度,我们的深度学习多标签分类脚本已正确分类这件红色衬衫

红色衬衫的结果很棒。

蓝色衬衫怎么样?

$ python classify.py --model fashion.model --labelbin mlb.pickle \
	--image examples/example_04.jpg
Using TensorFlow backend.
[INFO] loading network...
[INFO] classifying image...
black: 0.00%
blue: 99.99%
dress: 22.59%
jeans: 0.08%
red: 0.00%
shirt: 82.82%

使用Keras进行单模型多标签分类_第7张图片

图7:正确计算深蓝色衬衫的深度学习+多标签+ Keras分类

我们的模型非常自信它看到的是蓝色,但对于衬衫的信心稍差。 话虽这么说,这仍然是一个正确的多标签分类!

让我们看看我们是否可以用蓝色牛仔裤欺骗我们的多标签分类器:

$ python classify.py --model fashion.model --labelbin mlb.pickle \
	--image examples/example_05.jpg
Using TensorFlow backend.
[INFO] loading network...
[INFO] classifying image...
black: 0.00%
blue: 100.00%
dress: 0.01%
jeans: 99.99%
red: 0.00%
shirt: 0.00%

使用Keras进行单模型多标签分类_第8张图片

图8:这种深度学习的多标签分类结果证明蓝色牛仔裤可以被正确地分类为“蓝色”和“牛仔裤”

我们试试黑色牛仔裤:

$ python classify.py --model fashion.model --labelbin mlb.pickle \
	--image examples/example_06.jpg
Using TensorFlow backend.
[INFO] loading network...
[INFO] classifying image...
black: 100.00%
blue: 0.00%
dress: 0.01%
jeans: 100.00%
red: 0.00%
shirt: 0.00%

使用Keras进行单模型多标签分类_第9张图片

图9:这个标签, “牛仔裤”和“黑色”在这个Keras多标签分类深度学习实验中是正确的

我不能100%确定是牛仔裤(它们看起来更像是紧身裤/牛仔打底裤),但我们的多标签分类器判断为“是”!

让我们试一下黑色连衣裙的最后一个例子( example_07.jpg )。 虽然我们的网络已经学会了预测“黑色牛仔裤”和“蓝色牛仔裤”以及“蓝色连衣裙”和“红色连衣裙” ,但是它可以用来分类“黑色连衣裙”吗?

$ python classify.py --model fashion.model --labelbin mlb.pickle \
	--image examples/example_07.jpg
Using TensorFlow backend.
[INFO] loading network...
[INFO] classifying image...
black: 91.28%
blue: 7.70%
dress: 5.48%
jeans: 71.87%
red: 0.00%
shirt: 5.92%

使用Keras进行单模型多标签分类_第10张图片

图10:这里发生了什么? 我们的多标签不正确。 颜色被标记为“黑色”,但是分类器有更高的信心,这是“牛仔裤”而非“连衣裙”。 原因是我们的神经网络从未在其训练数据中看到这种组合。 有关进一步说明,请参阅下面的“总结” 。




总结

在今天的博客文章中,您学习了如何使用Keras执行多标签分类。

使用Keras执行多标签分类非常简单,包括两个主要步骤:

  1. 使用sigmoid激活函数替换网络末端的softmax激活函数
  2. 损失函数由分类交叉熵换成二元交叉熵

接着你可以像往常一样训练你的网络。

应用上述过程的最终结果是多类分类器。

您可以仅使用一个正向传递来调用Keras多类分类器来预测多个标签 。

但是,您需要考虑以下问题:

您需要准备要预测的每个类别组合的训练数据。

就像神经网络无法预测从未接受过训练的类一样,您的神经网络无法预测从未见过的组合的多个类别标签。 这种行为的原因是由于网络内神经元的激活。

如果您的网络接受了以下(1)黑色裤子和(2)红色衬衫的示例的训练,现在您想要预测“红裤子”(数据集中没有“红裤子”图像),负责检测的神经元“红色”和“裤子”会触发,但由于网络在一旦到达完全连接的层之前从未见过这种数据/激活组合,给出的预测很可能是不正确的(即,您可能会遇到“红色”或“裤子”,但两者都不太可能)。

同样, 您的网络无法正确预测从未接受过训练的数据 (您也不应该期待它)。在训练您自己的Keras网络进行多标签分类时,请牢记这一点。

我希望你喜欢这篇文章!

你可能感兴趣的:(Python,机器学习,Keras,sklearn)