这里使用鸟类图片数据集来实现分类。这是深度学习常用的一个练手数据集。一共有两百种鸟类,每一种鸟类都有一个文件夹,每一个图片的尺寸和拍摄视角都可能不一样,具有一定的随机性。这是一个更加具有挑战性的实战项目。

数据集来源

本次细粒度分类所采取的数据集CUB200-2011,该数据集是由加州理工学院在2010年提出的细粒度数据集,也是目前细粒度分类识别研究的基准图像数据集,该数据集共有117888张鸟类图像,包含了200类鸟类子类,其中训练数据集有5994张图像,测试集有5794张图像,每张图像均提供了图像类标注信息,图像中鸟的bounding box,鸟的关键part信息,以及鸟的属性信息。

引文:http://vis-www.cs.umass.edu/bcnn/

下载地址:https://s3.amazonaws.com/fast-ai-imageclas/CUB_200_2011.tgz

获取图片与标签

仍然使用glob来获取图片路径,在TensorFlow中,还是较为喜欢使用glob来实现路径的获取。

此外,还要完成图片与标签之间的映射。

映射关系

import tensorflow as tf
from tensorflow import keras
import matplotlib.pyplot as plt
import numpy as np
import glob

imgs_path = glob.glob("E:\\BaiduNetdiskDownload\\XXXXXX\\XXXXX\\200种鸟类图片数据集\\birds\\*\\*.jpg")

all_labels_name = [img_p.split('\\')[6].split('.')[1] for img_p in imgs_path]

# 完成标签的映射
label_names = np.unique(all_labels_name)    # label_names包含了200种鸟的名字
label_to_index = dict((name,i) for i,name in enumerate(label_names))

# 完成标签的映射,还需要再次映射回去
# 也就是既要给电脑懂,也要给人懂:计算机读取数字标签,人则是看字符串
index_to_label = dict((v,k) for k,v in enumerate(label_names))

由标签到数字

由数字到标签

知识补充:Python的内置函数enumerate()使用

enumerate(iteration, start)函数默认包含两个参数,其中iteration参数为需要遍历的参数,比如字典、列表、元组等,start参数为开始的参数,默认为0(不写start那就是从0开始)。enumerate函数有两个返回值,第一个返回值为从start参数开始的数,第二个参数为iteration参数中的值。

创建dataset以及读取图片

获取标签:

all_labels = [label_to_index.get(name) for name in all_labels_name]

np.random.seed(2022)
# 进行随机化
random_index = np.random.permutation(len(imgs_path))

# 图片和标签还是一一对应的
imgs_path = np.array(imgs_path)[random_index]
all_labels = np.array(all_labels)[random_index]

读取前三个图片,获得标签

知识补充:

np.random.permutation():随机排列序列。

permutation()的使用效果

模型和损失函数

# 训练数据,使用80%
i = int(len(imgs_path)*0.8)

train_path = imgs_path[:i]
train_labels = all_labels[:i]

test_path = imgs_path[i:]
test_labels = all_labels[i:]

train_ds = tf.data.Dataset.from_tensor_slices((train_path, train_labels))
test_ds = tf.data.Dataset.from_tensor_slices((test_path, test_labels))

def load_and_preprocess_image(path, label):
    image = tf.io.read_file(path)
    image = tf.image.decode_jpeg(image, channels=3)
    image = tf.image.resize(image, [256, 256])
    image = tf.cast(image, tf.float32)
    image = image/255.0  # normalize to [0,1] range
    return image, label                               # 返回转换后的值               

# AUTOTUNE是设定线程数
AUTOTUNE = tf.data.experimental.AUTOTUNE
train_ds = train_ds.map(load_and_preprocess_image, num_parallel_calls=AUTOTUNE)
test_ds = test_ds.map(load_and_preprocess_image, num_parallel_calls=AUTOTUNE)

BATCH_SIZE = 32   # 设置每一个小片片的大小
train_ds = train_ds.repeat().shuffle(300).batch(BATCH_SIZE)
test_ds = test_ds.batch(BATCH_SIZE)

model = tf.keras.Sequential()
model.add(tf.keras.layers.Conv2D(64,(3,3), input_shape=(256,256,3)))
model.add(tf.keras.layers.BatchNormalization())    # 批标准化层        
model.add(tf.keras.layers.Activation('relu'))                                   
# 这里的批标准化放在卷积层的后面,实际上放在激活函数之后效果可能会更好。

model.add(tf.keras.layers.Conv2D(64,(3,3)))
model.add(tf.keras.layers.BatchNormalization())    # 批标准化层        
model.add(tf.keras.layers.Activation('relu'))              

model.add(tf.keras.layers.MaxPool2D())

model.add(tf.keras.layers.Conv2D(128,(3,3)))
model.add(tf.keras.layers.BatchNormalization())    # 批标准化                                    
model.add(tf.keras.layers.Activation('relu'))                                   

model.add(tf.keras.layers.MaxPool2D())

model.add(tf.keras.layers.Conv2D(256,(3,3)))
model.add(tf.keras.layers.BatchNormalization())    # 批标准化       
model.add(tf.keras.layers.Activation('relu'))            

model.add(tf.keras.layers.Conv2D(64,(3,3)))
model.add(tf.keras.layers.BatchNormalization())    # 批标准化       
model.add(tf.keras.layers.Activation('relu'))            

model.add(tf.keras.layers.MaxPool2D())

model.add(tf.keras.layers.Conv2D(512,(3,3)))
model.add(tf.keras.layers.BatchNormalization())    # 批标准化       
model.add(tf.keras.layers.Activation('relu'))             

model.add(tf.keras.layers.Conv2D(512,(3,3)))
model.add(tf.keras.layers.BatchNormalization())    # 批标准化       
model.add(tf.keras.layers.Activation('relu'))

model.add(tf.keras.layers.Conv2D(512,(3,3)))
model.add(tf.keras.layers.BatchNormalization())    # 批标准化       
model.add(tf.keras.layers.Activation('relu'))          

# 再添加一个全局平均池化,将四维数组[batch height width channel]转化为二维的[batch imageCharacter]
# 这是为了在Dense层进行读取数据
model.add(tf.keras.layers.GlobalAveragePooling2D())

# 添加Dense层
model.add(tf.keras.layers.Dense(1024))
model.add(tf.keras.layers.BatchNormalization())    # 批标准化       
model.add(tf.keras.layers.Activation('relu'))          

# 添加Dense层
model.add(tf.keras.layers.Dense(256))
model.add(tf.keras.layers.BatchNormalization())    # 批标准化       
model.add(tf.keras.layers.Activation('relu'))           

model.add(tf.keras.layers.Dense(200))   # 逻辑回归,二分类

model.compile(optimizer=tf.keras.optimizers.Adam(0.0001),
              loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['acc']
             )

loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True):

训练和预测

# 进行编译
model.compile(optimizer=tf.keras.optimizers.Adam(0.0001),
              loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['acc']
             )

train_count = len(train_path)
test_count = len(test_path)

step_per_epoch = train_count // BATCH_SIZE
val_step = test_count // BATCH_SIZE

# 进行训练
history = model.fit(train_ds,epochs = 10,
                    steps_per_epoch = step_per_epoch,
                    validation_data = test_ds,
                    validation_steps = val_step
                   )

plt.plot(history.epoch, history.history.get('acc'),label='acc')
plt.plot(history.epoch, history.history.get('val_acc'),label='val_acc')

plt.plot(history.epoch, history.history.get('loss'),label='loss')
plt.plot(history.epoch, history.history.get('val_loss'),label='val_loss')
plt.legend()

这里需要注意的是,我在训练过程中出现了ResourceExhaustedError的报错,这是由于硬件的限制,可能网络层太多,运算量太大导致GPU资源耗尽,于是我把BATCH_SIZE改为16之后成功运行了,但是电脑也是开始发烫,有一个好配置的电脑还是蛮重要的。

训练效果:

这里仅仅训练了十次作为演示,显然还没有训练好。

训练十次

准确率变化趋势

从训练可以看出:

  • 训练量不够
  • 存在过拟合

什么是过拟合?什么是欠拟合?过拟合解决方法之使用Dropout抑制

现在使用一张图片进行预测:

# 这里重新定义了一个函数用来读取图片
def load_and_preprocess_image(path):
    image = tf.io.read_file(path)
    image = tf.image.decode_jpeg(image, channels=3)
    image = tf.image.resize(image, [256, 256])
    image = tf.cast(image, tf.float32)
    image = image/255.0  # normalize to [0,1] range
    return image

test_img = 'E:\\birds\\005.Crested_Auklet\\Crested_Auklet_0011_794927.jpg'
test_tensor = load_and_preprocess_image(test_img)
test_tensor = tf.expand_dims(test_tensor, axis=0)
pred = model.predict(test_tensor)

index_to_label.get(np.argmax(pred))

预测结果:

进行预测


版权声明 ▶ 本网站名称:陶小桃Blog
▶ 本文链接:https://www.52txr.cn/2022/200birds.html
▶ 本网站的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系站长进行核实删除。
▶ 转载本站文章需要遵守:商业转载请联系站长,非商业转载请注明出处!!
▶ 站长邮箱 [email protected][email protected] ,如不方便留言可邮件联系。

最后修改:2022 年 10 月 01 日
如果觉得我的文章对你有用,请随意赞赏