前段时间我分析过猫狗图片识别的案例,“猫狗识别”顾名思义,给出一张图片,识别出来的可能结果就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个子目录,如下图。

tensorflow2 输出参数量 DecoderLay multiple_数据集

每个子目录均为1个类别,如第一个子目录black_jeans包含的内容全部是黑色裤子图片,最后一个子目录red_shirt包含的内容全部是红色裙子图片,如下。

tensorflow2 输出参数量 DecoderLay multiple_数据集_02

 

tensorflow2 输出参数量 DecoderLay multiple_人工智能_03

四、查看一下目录

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

显示的结果为:

tensorflow2 输出参数量 DecoderLay multiple_数据集_04

十、定义图片的预处理函数

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

显示的结果为:

tensorflow2 输出参数量 DecoderLay multiple_数据集_05

十一、图片及标签处理

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

显示部分结果为:

tensorflow2 输出参数量 DecoderLay multiple_tensorflow_06

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

部分结果显示为

tensorflow2 输出参数量 DecoderLay multiple_人工智能_07

十五、模型编译

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
)

下面是输出的训练情况信息,可见,还是很准确。

tensorflow2 输出参数量 DecoderLay multiple_神经网络_08