多分类标签基于keras@[zjc20172333086]
目的:
训练一个分类器来将物品分到不同的类别中,比如一件衣服:可以安照服饰类别、颜色、质地打上“衬衫”、“蓝色”、“棉”的标签
整个工程的步骤如下:
1.首先讨论多标签分类数据集(以及如何快速构建自己的数据集)。
2.之后简要讨论SmallerVGGNet,我们将实现的Keras神经网络架构,并用于多标签分类。
3.然后我们将实施SmallerVGGNet并使用我们的多标签分类数据集对其进行训练。
一、数据集准备
数据集包含多个类别的多个图像,例如:
黑色牛仔裤(344图像)
蓝色连衣裙(386图像)
蓝色牛仔裤(356图像)
蓝色衬衫(369图像)
红色连衣裙(380图像)
红色衬衫(332图像)
新建一个文件夹dataset:mkdir dataset。然后放各类图片
二、定义多标签分类网络架构smallervggnet
我们将用它基于Keras训练一个多标签深度学习分类器
在工程目录下创建python文件,命名为smallervggnet.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
#引入了Keras模块并于此开始建立我们的SmallerVGGNet类
class SmallerVGGNet:
@staticmethod
def build(width, height, depth, classes, finalAct=“softmax”): #定义构建函数,用于组装卷积神经网络。
#width指定一张输入图片的通道(channels)数量,classes是种类(并不是他们所属的标签)数量(整数)。
#可选参数finalAct(默认值为“softmax”)将会在神经网络底部被应用。将这个值由softmax改为sigmoid将允许我们基于Keras执行多标签分类。
model = Sequential()
inputShape = (height, width, depth)
chanDim = -1
if K.image_data_format() == "channels_first":
inputShape = (depth, height, width)
chanDim = 1
#构建第一个CONV ==> RELU ==> POOL模块:我们的CONV层拥有32个卷积核大小为3×3的滤波器以及RELU(Rectified Linear Unit)激活函数。我们在这之后使用批标准化,最大池化,以及25%的遗忘率(Dropout)Dropout是一个随机切断当前神经网络层节点与下一神经网络层节点间链接的过程。这个随机断开的过程自然地帮助神经网络降低了过拟合的可能性,得益于没有任何一个节点会被分配以预测某个特定的类别、对象、边缘或是角落。
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 => 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 ==> RELU)*2 ==> POOL模块:请注意本模块中过滤器、卷积核以及池化大小的变化,这些变化将会共同运作从而逐渐减少空间大小但提升深度(depth)。
# (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))
# 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))
#对于我们的多标签分类非常重要——finalAct指明我们使用的是针对于单标签分类的“softmax”激活函数
# return the constructed network architecture
return model
三、实现多标签分类Keras模型
创建train.py,我们用于训练多标签Keras神经网络的脚本
说明:
–dataset:输入的数据集路径。
–model:输出的Keras序列模型路径。
–labelbin:输出的多标签二值化对象路径。
–plot:输出的训练损失及正确率图像路径。
代码:
import matplotlib
matplotlib.use(“Agg”)
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
构造参数,解析参数
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())
初始化要训练的epoch数量,初始学习率, 批处理大小和图像尺寸
#神经网络将会训练75轮(epoch),初始学习率为1e-3,图片大小是96×96并包含3个通道
EPOCHS = 75
INIT_LR = 1e-3
BS = 32
IMAGE_DIMS = (96, 96, 3)
读取图像路径并随机打乱它们
print("[INFO] loading images…")
imagePaths = sorted(list(paths.list_images(args[“dataset”])))
random.seed(42)
random.shuffle(imagePaths)
初始化数据和标签
data = []
labels = []
循环遍历imagePaths
for imagePath in imagePaths:
# 首先我们将每张图片加载至内存。其次,执行预处理,我们将image添加在data的末尾。
image = cv2.imread(imagePath)
image = cv2.resize(image, (IMAGE_DIMS[1], IMAGE_DIMS[0]))
image = img_to_array(image)
data.append(image)
# 针对我们的多标签分类问题将图片路径切分为多个标签。一个拥有2个元素的数组被创建,随后被添加至labels数据中。
l = label = imagePath.split(os.path.sep)[-2].split("_")
labels.append(l)
#labels数组是一个“包含数组的数组”——labels中的每个元素都是一个包含两个元素的数组。每个数组对应两个标签这种架构是基于输入图片的文件路径构建的。
#继续完成预处理
将原始像素强度调整到范围[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)))
对多类分类将标签二值化,我们需要运用scikit-learn库中的MultiLabelBinarizer类。不能在多类分类问题上用标准的LabelBinarizer类。
print("[INFO] class labels:")
#将人可读的标签转换为包含各类对应编码的向量,该向量根据某类是否在图片中出现来决定对应类的具体值。
mlb = MultiLabelBinarizer()
labels = mlb.fit_transform(labels)
遍历每个可能的类标签并显示它们
for (i, label) in enumerate(mlb.classes_):
print("{}. {}".format(i + 1, label))
将数据分为训练集和测试集并初始化数据增强器,把80%的图片分配为训练数据,20%为测试数据
(trainX, testX, trainY, testY) = train_test_split(data,
labels, test_size=0.2, random_state=42)
构造用于数据扩充的图像生成器
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”)
#使用sigmoid激活作为最后一层初始化模型
构建模型,初始化Adam优化器
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”)
#初始化优化器
opt = Adam(lr=INIT_LR, decay=INIT_LR / EPOCHS)
编译模型并开始训练,使用二元交叉熵而不是类别交叉熵
model.compile(loss=“binary_crossentropy”, optimizer=opt,
metrics=[“accuracy”])
启动运用了数据增强生成器的训练过程
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)
完成训练之后我们可以将模型和标签二值化器储存至磁盘
print("[INFO] serializing network…")
model.save(args[“model”])
print("[INFO] serializing label binarizer…")
f = open(args[“labelbin”], “wb”)
f.write(pickle.dumps(mlb))
f.close()
绘制正确率及损失
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”])
#绘制好的结果会保存成图片格式保存。
四、训练模型
打开终端。在那里,打开项目路径并执行如下命令:
python train.py --dataset dataset --model fashion.model --labelbin mlb.pickle
五、使用训练完成的模型预测新的图像
创建名为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())
加载图片
image = cv2.imread(args[“image”])
output = imutils.resize(image, width=400)
预处理输入图片(使用与训练数据相同的标准)
image = cv2.resize(image, (96, 96))
image = image.astype(“float”) / 255.0
image = img_to_array(image)
image = np.expand_dims(image, axis=0)
加载模型+多标签二值化器并将图片分类。从磁盘将模型和多标签二值化器加载至内存中。
print("[INFO] loading network…")
model = load_model(args[“model”])
mlb = pickle.loads(open(args[“labelbin”], “rb”).read())
分类(经过预处理的)图片并通过
#(1)基于相关概率将数组索引按降序排序
#(2)获取前两个类标签的索引,这便是我们的神经网络所作出的最好的两个预测
#方式解析出相关性最大的前两个类的标签索引:
#可以通过修改这段代码以返回更多的类标签。我也建议你对概率设置阈值,并且只返回那些置信程度 > N%的标签。
print("[INFO] classifying image…")
proba = model.predict(image)[0]
idxs = np.argsort(proba)[::-1][:2]
对每一个输出图像准备类标签+相关的置信值,该for循环将可能性最大的两个多标签预测及相应的置信值绘制在输出图片上
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)
#将所有的预测打印在终端上。这对于调试过程非常有用
for (label, p) in zip(mlb.classes_, proba):
print("{}: {:.2f}%".format(label, p * 100))
在屏幕上显示输出图片
cv2.imshow(“Output”, output)
cv2.waitKey(0)
注意: 网络无法预测没有在训练集中出现过的数据样品,如果出现的次数过少,预测的效果也不会很好,解决办法是增大数据集,这样可能非常不容易,还有一种用的已经很多的方法用在大的数据集上训练得到的权重数据对网络做初始化,提高模型的泛化能力