摘要: 本文利用马或人的图片数据来训练模型,让模型经过训练后具备识别新的图片中是含有马或者人,从而达到自动识别(recognition)。

训练背景

数据集

下载地址:https://storage.googleapis.com/laurencemoroney-blog.appspot.com/horse-or-human.zip 说明:数据集包含了名为horse和human两个文件夹,包含了两种图片,分别是马和人的图片,每种图片大约500张,每个图片大小是300*300,每个像素的取值为3byte,即真彩色图片2的24次方的取值范围,图片如:

训练过程

通过使用马和人的图片来训练模型,让模型知道马是什么样子的,人是什么样子的,训练完成后,我们从晚上可以搜索新的马和人的照片,输入给模型,看看模型的预测结果。

数据准备过程

下载数据

可以使用wget 命令将数据下载至临时目录下 wget https://storage.googleapis.com/laurencemoroney-blog.appspot.com/horse-or-human.zip /tmp/horse-or-human.zip

windows系统可以直接使用浏览器下载并保存合适的位置。

解压数据

因为数据集是zip格式的压缩包,需要对其进行解压,所以要用到访问系统文件系统和解压程序,因此需要导入os和zipfile两个库

1
2
3
4
5
6
7
import os
import zipfile
#给出压缩包具体的路径,解压到horse-or-human目录下
local_zip = '/tmp/horse-or-human.zip'
zip_ref = zipfile.ZipFile(local_zip, 'r')
zip_ref.extractall('/tmp/horse-or-human')
zip_ref.close()

组织数据

在上述的数据中在horse-or-human文件夹中包含了horse、human两个子文件夹,但是每个子文件夹分别包含数百张horse或者human的图片,但是并针对于单个图片而言,并没有label信息,这一点是和其他数据集有不同的地方,如在fashion mnist数据集中,28*28的图片数据是T桖还是靴子,都已经在数据集中标记了。

所以,我们需要为该数据集标记label。ImageGenerator可以帮我们完成这个动作,ImageGenerator通过读取单个图片的文件名来标记图片是horse还是human(文件名都是以horse或human开头的)。

1
2
3
4
5
6
7
8
9
# horse图片数据 训练文件所在路径
train_horse_dir = os.path.join('/tmp/horse-or-human/horses')
# human图片数据 训练文件所在路径
train_human_dir = os.path.join('/tmp/horse-or-human/humans')
# 查看训练集中前十个图片数据的文件名
train_horse_names = os.listdir(train_horse_dir)
print(train_horse_names[:10])
train_human_names = os.listdir(train_human_dir)
print(train_human_names[:10])

#确认图片数据训练样本的文件名特性

[‘horse39-3.png’, ‘horse02-9.png’, ‘horse32-3.png’, ‘horse06-9.png’, ‘horse29-1.png’, ‘horse33-5.png’, ‘horse35-1.png’, ‘horse41-9.png’, ‘horse37-0.png’, ‘horse27-2.png’]

[‘human12-25.png’, ‘human15-10.png’, ‘human13-10.png’, ‘human16-26.png’, ‘human10-15.png’, ‘human08-14.png’, ‘human10-10.png’, ‘human04-13.png’, ‘human06-26.png’, ‘human16-09.png’]

1
2
3
#确认一下每种图片的数量
print('total training horse images:', len(os.listdir(train_horse_dir)))
print('total training human images:', len(os.listdir(train_human_dir)))

#打印结果,可以看到horse 500张,human 527张

total training horse images: 500

total training human images: 527

检查数据

为了更好的做好训练,必须要懂数据,反复地考虑如何使用数据特性、数据特征。我们可以直接去文件夹查看数据,也可以通过画图来显示数据。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#导入绘图所用到的库
import matplotlib.pyplot as plt
import matplotlib.image as mpimg

#使用一个4*4的画板
nrows = 4
ncols = 4

# 定义一个索引
pic_index = 0

# 初始化一个4*4大小的画布
fig = plt.gcf()
fig.set_size_inches(ncols * 4, nrows * 4)

pic_index += 8
horse和human图片的全路径
next_horse_pix = [os.path.join(train_horse_dir, fname)
                for fname in train_horse_names[pic_index-8:pic_index]]
next_human_pix = [os.path.join(train_human_dir, fname)
                for fname in train_human_names[pic_index-8:pic_index]]
#绘图
for i, img_path in enumerate(next_horse_pix+next_human_pix):
  # Set up subplot; subplot indices start at 1
  sp = plt.subplot(nrows, ncols, i + 1)
  #不显示坐标轴
  sp.axis('Off') # Don't show axes (or gridlines)
  # 读取目录所对应的图片
  img = mpimg.imread(img_path)
  plt.imshow(img)

plt.show()

horse 和 human的样本数据如下图

训练

定义模型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import tensorflow as tf
#使用序列化Sequential模型,使用卷积层
model = tf.keras.models.Sequential([
    # 由于训练的图片数据的大小是300*300,且颜色取值3个字节,所以input_shape就
    # 是(300,300,3)
    # 激活函数使用relu,实现稀疏后的模型能够更好地挖掘相关特征,后面详细介绍
    # 选择3*3大小滤镜(filter)去扫描图片数据

    # 第一层卷积神经网络
    tf.keras.layers.Conv2D(16, (3,3), activation='relu', input_shape=(300, 300, 3)),
    # 使用MaxPooling2D 2*2的滤镜扫描,取得4个数值的最大值,目的是不损失特征的情况
    #下压缩数据,还具有防止过拟合的作用
    tf.keras.layers.MaxPooling2D(2, 2),
    # 第二层卷积神经网络
    tf.keras.layers.Conv2D(32, (3,3), activation='relu'),
    tf.keras.layers.MaxPooling2D(2,2),
    # 第三层卷积神经网络
    tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
    tf.keras.layers.MaxPooling2D(2,2),
    # 第四层卷积神经网络
    tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
    tf.keras.layers.MaxPooling2D(2,2),
    # 第五层卷积神经网络
    tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
    tf.keras.layers.MaxPooling2D(2,2),
    # 使得数据按照一定顺序规则成为1维数值或者向量,在喂给全连接的神经网络层
    tf.keras.layers.Flatten(),
    # 具有512个神经元的隐藏层
    tf.keras.layers.Dense(512, activation='relu'),
    # 只有一个用于输出的神经元,它的值将是0或者1,0代表horse,1代表human
    tf.keras.layers.Dense(1, activation='sigmoid')
])

模型配置

1
2
3
4
5
6
7
from tensorflow.keras.optimizers import RMSprop
#由于是个二分类的问题,一般选用交叉熵代价函数或者损失函数
#学习速度(learning rate, lr)为0.001
#使用accuracy(acc)准确度来度量
model.compile(loss='binary_crossentropy',
              optimizer=RMSprop(lr=0.001),
              metrics=['acc'])

处理训练数据

给待训练的图片数据样本,使用上面介绍的ImageDataGenerator增加label,以便于模型进行训练。horse的label是0, human的label是1,并且为了便于计算,对图片数据进行归一化处理,也就是rescale。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from tensorflow.keras.preprocessing.image import ImageDataGenerator

# 所有图片像素点都被除以255,让数值回到0-1区间,便于计算
train_datagen = ImageDataGenerator(rescale=1/255)
# 使用train_datagen 产生 128批量的图片训练流
train_generator = train_datagen.flow_from_directory(
        '/tmp/horse-or-human/',  # 训练数据所在目录 images
        target_size=(300, 300),  #
        batch_size=128,          # 每次批量128个
        # 由于使用交叉熵代价函数或者叫损失函数,是个二分类的问题
        class_mode='binary')    

训练

1
2
3
4
5
history = model.fit_generator(
      train_generator,
      steps_per_epoch=8,  
      epochs=15,
      verbose=1)

训练结果如下:

Epoch 115 99 [==============================] - 8s 938ms/step - loss: 0.9263 - acc: 0.4985 Epoch 215 99 [==============================] - 7s 769ms/step - loss: 0.7288 - acc: 0.5696 Epoch 315 99 [==============================] - 8s 840ms/step - loss: 0.6819 - acc: 0.8189 Epoch 415 99 [==============================] - 7s 725ms/step - loss: 0.3804 - acc: 0.8296 Epoch 515 99 [==============================] - 7s 734ms/step - loss: 0.6457 - acc: 0.8179 Epoch 615 99 [==============================] - 7s 745ms/step - loss: 0.2520 - acc: 0.8812 Epoch 715 99 [==============================] - 7s 733ms/step - loss: 0.4631 - acc: 0.8345 Epoch 815 99 [==============================] - 7s 730ms/step - loss: 0.1801 - acc: 0.9241 Epoch 915 99 [==============================] - 7s 748ms/step - loss: 1.0966 - acc: 0.8549 Epoch 1015 99 [==============================] - 7s 729ms/step - loss: 0.3123 - acc: 0.8919 Epoch 1115 99 [==============================] - 7s 730ms/step - loss: 0.3674 - acc: 0.8744 Epoch 1215 99 [==============================] - 7s 747ms/step - loss: 0.1424 - acc: 0.9494 Epoch 1315 99 [==============================] - 7s 732ms/step - loss: 0.4181 - acc: 0.9182 Epoch 1415 99 [==============================] - 8s 834ms/step - loss: 0.2235 - acc: 0.9289 Epoch 1515 99 [==============================] - 8s 835ms/step - loss: 0.0485 - acc: 0.9796

可以看出,单从loss和acc上来看,效果还是很不错

使用模型

我们试图从网上随便找几个图片中含有马或者人的图片,输入给模型,看看模型能否识别正确。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import numpy as np
from google.colab import files
from keras.preprocessing import image

uploaded = files.upload()

for fn in uploaded.keys():

  # predicting images
  path = '/content/' + fn
  img = image.load_img(path, target_size=(300, 300))
  x = image.img_to_array(img)
  x = np.expand_dims(x, axis=0)

  images = np.vstack([x])
  #预测
  classes = model.predict(images, batch_size=10)
  print(classes[0])
  if classes[0]>0.5:
    print(fn + " is a human")
  else:
    print(fn + " is a horse")

预测

我们使用上述的程序对以下来源于网络的五个图片进行预测,五个图片截图如下:

1

程序运行后,要求输入图片 4

分别看看分别输入五个图片时的预测结果,如下所示

1 结果: ma1.jpg is a horse

2 结果:ma2.jpg is a horse

3 结果:ren1.png is a human

4 结果: ren2.png is a human

4 结果: ren3.jpg is a horse

结果分析

  • 可以看到总共预测了五个图片,前四个图片预测正确,第五个错误human预测成horse。

  • 第五个图片错了,是因为该图片中有两个人,而训练数据里没有该类特性的数据,因为结果错误。

  • 如果除去第五个不按套路出牌的图片,基本上全对了。

  • 卷积神经网络对特征提取非常有用。

图片预测时的结果截图

ma1

ma2

ren1

ren2

ren3