tensorflow 2 从0构建图像分类之花卉模型

928 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第10天,点击查看活动详情


tensorflow 2系列04 从0构建一个图像分类模型之花卉分类

本期文章是一个系列课程,本文是这个系列的第3篇复习笔记

(1)Build and train neural network models using TensorFlow 2.x
(2)Image classification
(3)Natural language processing(NLP)
(4)Time series, sequences and predictions

本次图像分类是从0构建一个生产可用的模型,主要包含以下步骤

  • 数据处理
  • 构建模型
  • 检验模型效果
  • 过拟合调优

数据处理

数据下载

import tensorflow as tf
import numpy as np
import os
import PIL
import matplotlib.pyplot as plt
import pathlib

dataset_url = "https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz"
data_dir = tf.keras.utils.get_file('flower_photos', origin=dataset_url, untar=True)
data_dir = pathlib.Path(data_dir)
image_count = len(list(data_dir.glob('*/*.jpg')))
print(image_count)
3670
roses = list(data_dir.glob('roses/*'))
img=plt.imread(roses[0])
img.shape
plt.imshow(img)
PIL.Image.open(str(roses[0]))

PIL.Image.open(str(roses[1]))

tulips = list(data_dir.glob('tulips/*'))
PIL.Image.open(str(tulips[0]))

PIL.Image.open(str(tulips[1]))

创建数据集

batch_size = 32
img_height = 180
img_width = 180

# tf.keras.preprocessing.image.ImageDataGenerator
train_ds = tf.keras.preprocessing.image_dataset_from_directory(
  data_dir,
  validation_split=0.2,
  subset="training",
  seed=123,
  image_size=(img_height, img_width),
  batch_size=batch_size)

val_ds = tf.keras.preprocessing.image_dataset_from_directory(
  data_dir,
  validation_split=0.2,
  subset="validation",
  seed=123,
  image_size=(img_height, img_width),
  batch_size=batch_size)
class_names = train_ds.class_names
print(class_names)
Found 3670 files belonging to 5 classes.
Using 2936 files for training.
Found 3670 files belonging to 5 classes.
Using 734 files for validation.
['daisy', 'dandelion', 'roses', 'sunflowers', 'tulips']

可视化数据

plt.figure(figsize=(10, 10))
for images, labels in train_ds.take(1):
  for i in range(9):
    ax = plt.subplot(3, 3, i + 1)
    plt.imshow(images[i].numpy().astype("uint8"))
    plt.title(class_names[labels[i]])

AUTOTUNE =1000
train_ds=train_ds.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE)
val_ds=val_ds.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE)
normalization_layer = tf.keras.layers.experimental.preprocessing.Rescaling(1./255)
normalized_ds = train_ds.map(lambda x, y: (normalization_layer(x), y))
image_batch, labels_batch = next(iter(normalized_ds))
first_image = image_batch[0]
# Notice the pixels values are now in `[0,1]`.
print(np.min(first_image), np.max(first_image))
0.0 1.0
0.0 1.0
0.0 1.0

定义模型

num_classes = 5

model = tf.keras.Sequential([
  tf.keras.layers.experimental.preprocessing.Rescaling(1./255, input_shape=(img_height, img_width, 3)),
  tf.keras.layers.Conv2D(16, 3, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(),
  tf.keras.layers.Conv2D(32, 3, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(),
  tf.keras.layers.Conv2D(64, 3, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(),
  tf.keras.layers.Flatten(),
  tf.keras.layers.Dense(128, activation='relu'),
  tf.keras.layers.Dense(num_classes,activation="softmax")
])
model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
              metrics=['accuracy'])
model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
rescaling_1 (Rescaling)      (None, 180, 180, 3)       0         
_________________________________________________________________
conv2d (Conv2D)              (None, 180, 180, 16)      448       
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 90, 90, 16)        0         
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 90, 90, 32)        4640      
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 45, 45, 32)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 45, 45, 64)        18496     
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 22, 22, 64)        0         
_________________________________________________________________
flatten (Flatten)            (None, 30976)             0         
_________________________________________________________________
dense (Dense)                (None, 128)               3965056   
_________________________________________________________________
dense_1 (Dense)              (None, 5)                 645       
=================================================================
Total params: 3,989,285
Trainable params: 3,989,285
Non-trainable params: 0
_________________________________________________________________
epochs=5
history = model.fit(
  train_ds,
  validation_data=val_ds,
  epochs=epochs
)
Epoch 1/5
92/92 [==============================] - 51s 550ms/step - loss: 1.3677 - accuracy: 0.4166 - val_loss: 1.0836 - val_accuracy: 0.5681
Epoch 2/5
92/92 [==============================] - 50s 542ms/step - loss: 0.9736 - accuracy: 0.6100 - val_loss: 0.9855 - val_accuracy: 0.6063
Epoch 3/5
92/92 [==============================] - 50s 544ms/step - loss: 0.7864 - accuracy: 0.6904 - val_loss: 0.8481 - val_accuracy: 0.6608
Epoch 4/5
92/92 [==============================] - 50s 546ms/step - loss: 0.5951 - accuracy: 0.7766 - val_loss: 0.8688 - val_accuracy: 0.6703
Epoch 5/5
92/92 [==============================] - 50s 545ms/step - loss: 0.4205 - accuracy: 0.8498 - val_loss: 0.9487 - val_accuracy: 0.6894

Visualize training results

acc = history.history['accuracy']
val_acc = history.history['val_accuracy']

loss = history.history['loss']
val_loss = history.history['val_loss']

epochs_range = range(epochs)

plt.figure(figsize=(8, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()

过拟合调优

数据增强

data_augmentation = tf.keras.Sequential(
  [
    tf.keras.layers.experimental.preprocessing.RandomFlip("horizontal", 
                                                 input_shape=(img_height, 
                                                              img_width,
                                                              3)),
    tf.keras.layers.experimental.preprocessing.RandomRotation(0.1),
    tf.keras.layers.experimental.preprocessing.RandomZoom(0.1),
  ]
)
plt.figure(figsize=(10, 10))
for images, _ in train_ds.take(1):
  for i in range(9):
    augmented_images = data_augmentation(images)
    ax = plt.subplot(3, 3, i + 1)
    plt.imshow(augmented_images[0].numpy().astype("uint8"))
    plt.axis("off")

dropout

model = tf.keras.Sequential([
  data_augmentation,
  tf.keras.layers.experimental.preprocessing.Rescaling(1./255),
  tf.keras.layers.Conv2D(16, 3, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(),
  tf.keras.layers.Conv2D(32, 3, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(),
  tf.keras.layers.Conv2D(64, 3, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(),
  tf.keras.layers.Dropout(0.2),
  tf.keras.layers.Flatten(),
  tf.keras.layers.Dense(128, activation='relu'),
  tf.keras.layers.Dense(num_classes,activation="softmax")
])
model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
              metrics=['accuracy'])
model.summary()
Model: "sequential_2"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
sequential_1 (Sequential)    (None, 180, 180, 3)       0         
_________________________________________________________________
rescaling_2 (Rescaling)      (None, 180, 180, 3)       0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 180, 180, 16)      448       
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 90, 90, 16)        0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 90, 90, 32)        4640      
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 45, 45, 32)        0         
_________________________________________________________________
conv2d_5 (Conv2D)            (None, 45, 45, 64)        18496     
_________________________________________________________________
max_pooling2d_5 (MaxPooling2 (None, 22, 22, 64)        0         
_________________________________________________________________
dropout (Dropout)            (None, 22, 22, 64)        0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 30976)             0         
_________________________________________________________________
dense_2 (Dense)              (None, 128)               3965056   
_________________________________________________________________
dense_3 (Dense)              (None, 5)                 645       
=================================================================
Total params: 3,989,285
Trainable params: 3,989,285
Non-trainable params: 0
_________________________________________________________________
epochs = 5
history = model.fit(
  train_ds,
  validation_data=val_ds,
  epochs=epochs
)
Epoch 1/5
92/92 [==============================] - 57s 616ms/step - loss: 1.2607 - accuracy: 0.4813 - val_loss: 1.1557 - val_accuracy: 0.5572
Epoch 2/5
92/92 [==============================] - 56s 613ms/step - loss: 1.0268 - accuracy: 0.5926 - val_loss: 1.0223 - val_accuracy: 0.5777
Epoch 3/5
92/92 [==============================] - 56s 612ms/step - loss: 0.9231 - accuracy: 0.6349 - val_loss: 0.9180 - val_accuracy: 0.6431
Epoch 4/5
92/92 [==============================] - 63s 680ms/step - loss: 0.8650 - accuracy: 0.6649 - val_loss: 0.8474 - val_accuracy: 0.6744
Epoch 5/5
92/92 [==============================] - 59s 646ms/step - loss: 0.8098 - accuracy: 0.6907 - val_loss: 0.9028 - val_accuracy: 0.6417
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']

loss = history.history['loss']
val_loss = history.history['val_loss']

epochs_range = range(epochs)

plt.figure(figsize=(8, 8))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validatiplt.subplot(1, 2, 2)on Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()

sunflower_url = "https://storage.googleapis.com/download.tensorflow.org/example_images/592px-Red_sunflower.jpg"
sunflower_path = tf.keras.utils.get_file('Red_sunflower', origin=sunflower_url)

img = tf.keras.preprocessing.image.load_img(
    sunflower_path, target_size=(img_height, img_width)
)
plt.imshow(img)
img_array = tf.keras.preprocessing.image.img_to_array(img)
print(img_array.shape)
img_array = tf.expand_dims(img_array, 0) # Create a batch
print(img_array.shape)

predictions = model.predict(img_array)
print(predictions.shape)
print(class_names[np.argmax(predictions)])
score = tf.nn.softmax(predictions[0])
print(score)
print(np.max(score))

print(
    "This image most likely belongs to {} with a {:.2f} percent confidence."
    .format(class_names[np.argmax(score)], 100 * np.max(score))
)
(180, 180, 3)
(1, 180, 180, 3)
(1, 5)
sunflowers
tf.Tensor([0.1501881  0.15026027 0.15038382 0.39373598 0.15543188], shape=(5,), dtype=float32)
0.39373598
This image most likely belongs to sunflowers with a 39.37 percent confidence.

总结

数据相关工具类

  • tf.kears.util.get_file(fname,origin,untar)从网络上下载压缩包并自动解压,返回路径
  • pathlib.Path()加载路径
  • 图像显示PIL.Image.Open() plt.imgshow()
  • tf.keras.preprocessing.image_dataset_from_directory从目录构造数据集 是一个feature,label的元组数组主要参数  data_dir,数据目录 validation_split=0.2,验证集占比 subset="validation",验证集名称 seed=123,划分的随机种子 image_size=(img_height, img_width),设置target image size batch_size=batch_size 数据批处理大小 返回的是一个dataset对象 主键包含的属性 class_names cache()方法缓存数据集,不用频繁读取磁盘 shuffle()方法设置随机混洗多少次 prefetch()预取多少条做缓冲,加快训练速度 上面三个方法可以链式调用 cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE)
  • 图片可视化 PIL.Image.Open打开一个图片 或者用plt来显示图片plt.imgshow()
  • 数据可视化 首先定义一个画布plt.figure(figsize=(10, 10)) plt.subplot()来指定行列第几行,定义图形的行列 plt.plot(num,arr,label)num是数组arr的长度,arr要显示的数组,label是表示的图例

显示数组的值

特征处理相关

特征处理有几种方式, 一种是直接对数据集进行处理,还有一种是端到端的处理方式,就是将数据处理放进模型中,模型直接接收原始数据,前面几层直接是处理特征的层 个人觉得看情况调用,第一种情况提前处理,有利于节省后面模型训练的时间,第二种是端到端的,比较方便开箱即用.

生产阶段推荐第一种 验证可以用第二种,或者机器条件好的情况,可以用第二种 第二种方式代码如下

data_augmentation = tf.keras.Sequential(
  [
    tf.keras.layers.experimental.preprocessing.RandomFlip("horizontal", 
                                                 input_shape=(img_height, 
                                                              img_width,
                                                              3)),#图像翻转
    tf.keras.layers.experimental.preprocessing.RandomRotation(0.1),#随机旋转
    tf.keras.layers.experimental.preprocessing.RandomZoom(0.1),#随机放大缩小
    tf.keras.layers.experimental.preprocessing.Rescaling(1./255) #缩放到0,1之间
  ]
)

这个data_augmentation可以当函数使用,也可以作为模型输入的第一层 比如下面

model = tf.keras.Sequential([
  data_augmentation,
  tf.keras.layers.experimental.preprocessing.Rescaling(1./255),
  tf.keras.layers.Conv2D(16, 3, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(),
  tf.keras.layers.Conv2D(32, 3, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(),
  tf.keras.layers.Conv2D(64, 3, padding='same', activation='relu'),
  tf.keras.layers.MaxPooling2D(),
  tf.keras.layers.Dropout(0.2),
  tf.keras.layers.Flatten(),
  tf.keras.layers.Dense(128, activation='relu'),
  tf.keras.layers.Dense(num_classes,activation="softmax")
])