前段时间我分析过猫狗图片识别的案例,“猫狗识别”顾名思义,给出一张图片,识别出来的可能结果就2个,不是猫就是狗,把这个定义为二元输出,本章要研究的是多元输出,即给出一张图片,识别出来的可能结果大于2个。我的代码编辑环境为Jupyter NoteBook,下面是我的代码和分析过程。
一、导入需要的包
import tensorflow as tf
from tensorflow import keras
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
import pathlib
import glob
import random
import IPython.display as display
import matplotlib.pyplot as plt
二、查看Tensorflow版本
print('Tensorflow version: {}'.format(tf.__version__))
输出结果为:Tensorflow version: 2.3.0
三、定义数据集目录
data_dir = '/home/haha/资料/人工智能/数据集/multi-output-classification/dataset'
data_root = pathlib.Path(data_dir)
data_root
输出结果为:PosixPath('/home/haha/资料/人工智能/数据集/multi-output-classification/dataset')。
另外需要知道,我使用的这个数据集包括多个类别,dataset目录下面有7个子目录,如下图。
每个子目录均为1个类别,如第一个子目录black_jeans包含的内容全部是黑色裤子图片,最后一个子目录red_shirt包含的内容全部是红色裙子图片,如下。
四、查看一下目录
for item in data_root.iterdir():
print(item)
输出结果为
/home/haha/资料/人工智能/数据集/multi-output-classification/dataset/red_dress
/home/haha/资料/人工智能/数据集/multi-output-classification/dataset/blue_shirt
/home/haha/资料/人工智能/数据集/multi-output-classification/dataset/black_shoes
/home/haha/资料/人工智能/数据集/multi-output-classification/dataset/red_shirt
/home/haha/资料/人工智能/数据集/multi-output-classification/dataset/blue_dress
/home/haha/资料/人工智能/数据集/multi-output-classification/dataset/black_jeans
/home/haha/资料/人工智能/数据集/multi-output-classification/dataset/blue_jeans
五、获取数据集中每条图片的路径,路径存放到列表中。
all_image_paths = glob.glob('/home/haha/资料/人工智能/数据集/multi-output-classification/dataset/*/*')
可以查看下图片的总张数
image_count = len(all_image_paths)
image_count
输出结果为:2525
六、对数据做乱序处理,同时查看乱序后的前三条数据。
random.shuffle(all_image_paths)
all_image_paths[:3]
输出为
['/home/haha/资料/人工智能/日月光华-课程资料/数据集/dataset/black_jeans/00000333.jpeg',
'/home/haha/资料/人工智能/日月光华-课程资料/数据集/dataset/blue_shirt/00000301.jpg',
'/home/haha/资料/人工智能/日月光华-课程资料/数据集/dataset/red_shirt/00000054.jpg']
七、已知输出有7个结果,现将这7个结果标签进行整理,并划分两部分:一部分是颜色(即black、blue、red),且每种颜色对应1个数值标签,使之结果为{'black': 0, 'red': 1, 'blue': 2};另一部分是类别(dress、 jeans、shirt、shoes),且每种类别对应1个数值标签,使之结果为{'jeans': 0, 'shirt': 1, 'dress': 2, 'shoes': 3}。以下是具体的步骤。
7.1 获取根目录下的所有子目录名称,并以列表的形式存放。
label_names = sorted([item.name for item in data_root.glob('*/') if item.is_dir()])
label_names
输出结果为:
['black_jeans',
'black_shoes',
'blue_dress',
'blue_jeans',
'blue_shirt',
'red_dress',
'red_shirt']
7.2 获取全部的颜色集合
color_label_names = set([item.split('_')[0] for item in label_names])
color_label_names
输出结果为:{'black', 'blue', 'red'}
7.3 使每种颜色对应1个数值标签,以字典形式组织起来。
color_label_to_index = dict((name, index) for index,name in enumerate(color_label_names))
color_label_to_index
输出结果为:{'black': 0, 'red': 1, 'blue': 2}。完成第一部分。
7.4 获取全部的类别集合
item_label_names = set(sorted([item.split('_')[1] for item in label_names]))
item_label_names
输出结果为:{'dress', 'jeans', 'shirt', 'shoes'}
7.5 使每个类别对应1个数值标签,以字典形式组织起来。
item_label_to_index = dict((name, index) for index,name in enumerate(item_label_names))
item_label_to_index
输出结果为:{'jeans': 0, 'shirt': 1, 'dress': 2, 'shoes': 3}。完成第二部分。
八、现在已经取到了每张图片的路径,现在需要获取每张图片对应的标签。强调的是,每张图片对应两类标签,一是颜色(即black、blue、red),二是类别(dress、 jeans、shirt、shoes)。以下是具体的步骤。
8.1 获取每张图片对应的标签(这里的标签为全标签,形式为“颜色_类别”)
all_image_labels = [pathlib.Path(path).parent.name for path in all_image_paths]
all_image_labels[-5:]
输出结果为:['blue_jeans', 'blue_shirt', 'blue_jeans', 'black_shoes', 'black_shoes']
8.2 获取每张图片对应的颜色标签
#获取每张图片对应的颜色标签
color_labels = [color_label_to_index[label.split('_')[0]] for label in all_image_labels]
color_labels[:5]
输出结果为:[0, 2, 1, 1, 0]
8.3 获取每张图片对应的类别标签
#获取每张图片对应的类别标签
item_labels = [item_label_to_index[label.split('_')[1]] for label in all_image_labels]
item_labels[:10]
输出结果为:[0, 1, 1, 2, 0, 1, 1, 2, 0, 0]
九、现在可以显示一下部分图片及对应的标签信息
for n in range(3):
image_index = random.choice(range(len(all_image_paths)))
display.display(display.Image(all_image_paths[image_index], width=100, height=100))
print(all_image_labels[image_index])
print(color_labels[image_index])
print(item_labels[image_index])
print()
显示的结果为:
十、定义图片的预处理函数
def load_and_preprocess_image(path):
image = tf.io.read_file(path)
image = tf.image.decode_jpeg(image, channels=3) #由于图像是彩色的,所以channels=3
image = tf.image.resize(image, [224, 224])#将图片大小值为224*224
image = tf.cast(image, tf.float32) #图片像素值的默认类型为uint8,现在需要将数据类型转换为tf.float32
image = image/255.0 #图片每一像素的值范围是0-255,现在需做归一化处理,所以除以255
return image
下面对图片进行预处理,并进行显示。
image_path = all_image_paths[0]
label = all_image_labels[0]
#plt.imshow((load_and_preprocess_image(img_path) + 1)/2)
plt.imshow(load_and_preprocess_image(img_path))
plt.grid(False)
plt.xlabel(label)
print()
显示的结果为:
十一、图片及标签处理
11.1 调用load_and_preprocess_image进行图片处理
path_ds = tf.data.Dataset.from_tensor_slices(all_image_paths)
AUTOTUNE = tf.data.experimental.AUTOTUNE #TensorFlow根据系统的实际情况,自动选择合适的值
image_ds = path_ds.map(load_and_preprocess_image, num_parallel_calls=AUTOTUNE)#调用load_preprosess_image函数,进行图片处理。num_parallel_calls为TensorFlow选择的cpu的核心数。
11.2 将颜色标签和类别标签进行对应组合
label_ds = tf.data.Dataset.from_tensor_slices((color_labels, item_labels))
可以查看一下结果
for ele in label_ds.take(3):
print(ele[0].numpy(), ele[1].numpy())
输出的结果为:
0 0
2 1
1 1
11.3 将图片和标签对应起来
image_label_ds = tf.data.Dataset.zip((image_ds, label_ds))
image_label_ds
输出的结果为:<ZipDataset shapes: ((224, 224, 3), ((), ())), types: (tf.float32, (tf.int32, tf.int32))>
十二、划分训练数据和测试数据
test_count = int(image_count*0.2)
train_count = image_count - test_count
train_data = image_label_ds.skip(test_count)
test_data = image_label_ds.take(test_count)
数据集的前20%为测试数据,后80%为训练数据。训练数据存放在train_data中,测试数据存放在test_data中。
十三、设置数据集的相关属性等操作。训练时,每次从数据集中读取一批(即32条数据)。
BATCH_SIZE = 32
train_data = train_data.shuffle(buffer_size=train_count).repeat(-1)
train_data = train_data.batch(BATCH_SIZE)
train_data = train_data.prefetch(buffer_size=AUTOTUNE) #在训练期间,后台同时读取数据并进行转换操作,目的是提高效率
train_data
显示结果为:<PrefetchDataset shapes: ((None, 224, 224, 3), ((None,), (None,))), types: (tf.float32, (tf.int32, tf.int32))>
设置测试数据的批处理属性
test_data = test_data.batch(BATCH_SIZE)
十四、建立模型,模型的构建运用了迁移学习方法,借鉴了已有的模型。
14.1 该步骤就需要在线下载权重,现在速度很慢。使用的权重为在别人在imagenet数据集上训练好的权重。include_top = False表示只使用卷积基,而不使用全连接部分。因此,我需要自己训练全连接层的权重。我为什么选择自己训练全连接层,我的理解是卷基层是对特征进行提取,而该特征是cat还是dog,这就需要自己构建全连接层,用我自己的数据集进行训练得出的模型进行判断。
#weights='imagenet'表示使用在imagenet上训练好的权重
#include_top = False表示只使用卷积基,而不使用全连接部分
mobile_net = tf.keras.applications.MobileNetV2(input_shape=(224, 224, 3),
include_top=False,
weights='imagenet')
设置mobile_net网络不可训练
mobile_net.trainable = False
查看该模型
mobile_net.summary()
显示部分结果为:
14.2 构建模型的全连接层部分
定义输入
inputs = tf.keras.Input(shape=(224, 224, 3))
可以看一下此时mobile_net网络的输出
x = mobile_net(inputs)
x.get_shape()
结果为:TensorShape([None, 7, 7, 1280])。此结果的含义是:每张图片长宽为7*7,深度为1280,None是指的batch。
全局平均池化操作
x = tf.keras.layers.GlobalAveragePooling2D()(x)
x.get_shape()
结果为:TensorShape([None, 1280])。此结果的含义是:每张图片深度为1280,None是指的batch。这时,每张图片均为1维形式,可以进行全连接层操作。
前面提到,每张图片对应两类标签,一是颜色(即black、blue、red),二是类别(dress、 jeans、shirt、shoes),那么,全连接层要分叉,一叉是颜色,一叉是类别。
下面是第一叉,关于颜色的全连接层。
x1 = tf.keras.layers.Dense(1024, activation='relu')(x)
#由于颜色的种类大于2(即多元分类),那么采用softmax激活
out_color = tf.keras.layers.Dense(len(color_label_names),
activation='softmax',
name='out_color')(x1)
下面是第二叉,关于类别的全连接层。
x2 = tf.keras.layers.Dense(1024, activation='relu')(x)
#由于类别的种类大于2(即多元分类),那么采用softmax激活
out_item = tf.keras.layers.Dense(len(item_label_names),
activation='softmax',
name='out_item')(x2)
下面构建模型
model = tf.keras.Model(inputs=inputs,
outputs=[out_color, out_item])
查看一下该模型
model.summary()
部分结果显示为
十五、模型编译
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.0001),
loss={'out_color':'sparse_categorical_crossentropy',
'out_item':'sparse_categorical_crossentropy'},
metrics=['acc']
)
十六、模型训练
train_steps = train_count//BATCH_SIZE
test_steps = test_count//BATCH_SIZE
查看test_data
test_data
显示结果为:<BatchDataset shapes: ((None, 224, 224, 3), ((None,), (None,))), types: (tf.float32, (tf.int32, tf.int32))>
train_data
train_data
显示结果为:<PrefetchDataset shapes: ((None, 224, 224, 3), ((None,), (None,))), types: (tf.float32, (tf.int32, tf.int32))>
下面进行训练
model.fit(train_data,
epochs=3,
steps_per_epoch=train_steps,
validation_data=test_data,
validation_steps=test_steps
)
下面是输出的训练情况信息,可见,还是很准确。