项目06:图像分类—Kaggle猫狗大战

前几个章节的实验中提到的实验的数据集中,数据量都是相对比较充足的。例如MNIST和Fashion-MNIST都包含包含60000个训练数据及10000个测试数据;CIFAR-10包含50000张训练数据,10000张测试数据等。

倘若在未来的实验中,我们需要完成一项任务,但是数据集又不够充足的情况下,能否训练出达到指标的模型或者尽可能的利用好当下仅有的少量数据而充分的达到最高的指标?本章将利用Kaggle在2013年提供的“猫狗大战”(dogs-vs-cats)数据集来进行这项实验,顾名思义,数据集中包含着猫和狗两种动物的图片,我们任务也就是做图像分类,把一堆数据集中区分出数据是猫还是狗。

1. 准备工作

在指定的磁盘路径创建存放当前项目的目录,linux或macos可使用mkdir命令创建文件夹目录,Windows直接使用图形化界面右键新建文件夹即可,例如我们的存放项目的目录名为project06,并创建dataset文件:

    (dlwork) jingyudeMacBook-Pro:~ jingyuyan$ mkdir project06

    (dlwork) jingyudeMacBook-Pro:~ jingyuyan$ cd project06

    (dlwork) jingyudeMacBook-Pro:project06 jingyuyan$ mkdir dataset

    (dlwork) jingyudeMacBook-Pro:project06 jingyuyan$ jupyter notebook

并且创建jupyter文件,开始我们的实验,读者会获得这样的一个文件夹结构:

project06/
├───demo06.ipynb    
└───dataset/

2. 数据集的处理

猫狗大战(dogs-vs-cats)数据集中包含着狗和猫的图片总共25000张,其中每项分类各自12500张图片。由于我们本章研究的实验对象是“如何依靠小量的数据集进行训练”,所以我们本章需要的数据集的特地主要是“少量”,所以我们需要重新把数据集划分,将主要的数据集分为训练集中的4000张照片,每个分类各占2000张图片,最后提取1000张用于测试集。

img1

2.1 数据集下载与存放

首先,需要下载的是数据集,读者们可以自行到Kaggle下载https://www.kaggle.com/c/dogs-vs-cats/data ,若下载有困难的读者,可以翻阅附录,我们提供了比较便捷稳定的下载服务和方法,请读者自行操作。下载数据集并解压后,读者会得到train.zip、test1.zip和sampleSubmission.csv三个文件,将这些文件放入dataset文件夹中并解压train.zip和test1.zip,读者会得到这样的目录结构:

project06/
├───demo06.ipynb   
├───sampleSubmission.csv
├───test1.zip
├───train.zip
├───train/
  ├──cat.0.jpg
  ├──cat.1.jpg
     ...
└───test1/
    ├──1.jpg
    ├──2.jpg
       ...

2.2 数据文件处理

下载和安置好数据集文件后,我们需要构建本次我们训练所需要使用到的数据,分别从原始数据集中划分出4000张训练集的图片和1000张测试集的图片,我们使用Python的os和shutil库来处理文件和文件夹。

import os
import os, shutil

ROOT_DIR = os.getcwd()
DATA_PATH = os.path.join(ROOT_DIR, "dataset")
# 原始数据集根目录
original_dataset_dir = os.path.join(DATA_PATH, "train")
# 构建小数据集存储文件夹
base_dir = os.path.join(DATA_PATH, "cats_and_dogs_small")
if not os.path.exists(base_dir): 
    os.mkdir(base_dir)

# 构建训练集的文件夹
train_dir = os.path.join(base_dir, 'train')
if not os.path.exists(train_dir): 
    os.mkdir(train_dir)

# 构建验证集的文件夹
validation_dir = os.path.join(base_dir, 'validation')
if not os.path.exists(validation_dir): 
    os.mkdir(validation_dir)

# 构建测试集文件夹
test_dir = os.path.join(base_dir, 'test')
if not os.path.exists(test_dir):
    os.mkdir(test_dir)    

# 猫的图片训练文件夹
train_cats_dir = os.path.join(train_dir, 'cats')
if not os.path.exists(train_cats_dir):
    os.mkdir(train_cats_dir)

# 狗的图片训练文件夹
train_dogs_dir = os.path.join(train_dir, 'dogs')
if not os.path.exists(train_dogs_dir):
    os.mkdir(train_dogs_dir)

# 猫的图片验证文件夹
validation_cats_dir = os.path.join(validation_dir, 'cats')
if not os.path.exists(validation_cats_dir):
    os.mkdir(validation_cats_dir)

# 狗的图片验证文件夹
validation_dogs_dir = os.path.join(validation_dir, 'dogs')
if not os.path.exists(validation_dogs_dir):
    os.mkdir(validation_dogs_dir)

# 猫的图片测试文件夹
test_cats_dir = os.path.join(test_dir, 'cats')
if not os.path.exists(test_cats_dir):
    os.mkdir(test_cats_dir)

# 狗的图片测试文件夹
test_dogs_dir = os.path.join(test_dir, 'dogs')
if not os.path.exists(test_dogs_dir):
    os.mkdir(test_dogs_dir)

创建好各个文件夹后,得到的目录结构应该是这样的:

project06/
├───demo06.ipynb   
├───sampleSubmission.csv
├───test1.zip
├───train.zip
├───cats_and_dogs_small/
  ├──test/
     ├──cats/
     └──dogs/
  ├──train/
     ├──cats/
     └──dogs/
  ├──tvalidation/
     ├──cats/
     └──dogs/
├───train/
  ├──cat.0.jpg
  ├──cat.1.jpg
     ...
└───test1/
    ├──1.jpg
    ├──2.jpg
       ...

构建好各个目录后,我们开始复制原始数文件到训练小数据文件夹当中,依次放置各个文件。

# 把原始数据集中复制1000张猫的图片到训练数据集文件夹train_cats_dir中
fnames = ['cat.{}.jpg'.format(i) for i in range(1000)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(train_cats_dir, fname)
    if not os.path.exists(dst):
        shutil.copyfile(src, dst)

print('复制1000张猫的图片到训练数据集文件夹train_cats_dir中!')

# 把原始数据集中复制500张猫的图片到验证数据集文件夹validation_cats_dir中
fnames = ['cat.{}.jpg'.format(i) for i in range(1000, 1500)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(validation_cats_dir, fname)
    if not os.path.exists(dst):
        shutil.copyfile(src, dst)

print('复制500张猫的图片到验证数据集文件夹validation_cats_dir中')

# 把原始数据集中复制500张猫的图片到测试数据集test_cats_dir中
fnames = ['cat.{}.jpg'.format(i) for i in range(1500, 2000)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(test_cats_dir, fname)
    if not os.path.exists(dst):
        shutil.copyfile(src, dst)

print('复制500张猫的图片到测试数据集test_cats_dir中')

# 把原始数据集中复制1000张狗的图片到训练数据集文件夹train_dogs_dir中
fnames = ['dog.{}.jpg'.format(i) for i in range(1000)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(train_dogs_dir, fname)
    if not os.path.exists(dst):
        shutil.copyfile(src, dst)

print('复制1000张狗的图片到训练数据集文件夹train_dogs_dir中')


# 把原始数据集中复制500张狗的图片到验证数据集文件夹validation_dogs_dir中
fnames = ['dog.{}.jpg'.format(i) for i in range(1000, 1500)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(validation_dogs_dir, fname)
    if not os.path.exists(dst):
        shutil.copyfile(src, dst)

print('复制500张狗的图片到验证数据集文件夹validation_dogs_dir中')

# 把原始数据集中复制1000张狗的图片到测试数据集文件test_dogs_dir中
fnames = ['dog.{}.jpg'.format(i) for i in range(1500, 2000)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(test_dogs_dir, fname)
    if not os.path.exists(dst):
        shutil.copyfile(src, dst)

print('复制1000张狗的图片到测试数据集文件test_dogs_dir中')
复制1000张猫的图片到训练数据集文件夹train_cats_dir中!
复制500张猫的图片到验证数据集文件夹validation_cats_dir中
复制500张猫的图片到测试数据集test_cats_dir中
复制1000张狗的图片到训练数据集文件夹train_dogs_dir中
复制500张狗的图片到验证数据集文件夹validation_dogs_dir中
复制1000张狗的图片到测试数据集文件test_dogs_dir中

检测我们所处理的图片是否满足我们的需求

print('猫的训练集共有图片:', len(os.listdir(train_cats_dir)))
print('狗的训练集共有图片:', len(os.listdir(train_dogs_dir)))
print('猫的验证集共有图片:', len(os.listdir(validation_cats_dir)))
print('狗的验证集共有图片:', len(os.listdir(validation_dogs_dir)))
print('猫的测试集共有图片:', len(os.listdir(test_cats_dir)))
print('狗的测试集共有图片:', len(os.listdir(test_dogs_dir)))
猫的训练集共有图片: 1000
狗的训练集共有图片: 1000
猫的验证集共有图片: 500
狗的验证集共有图片: 500
猫的测试集共有图片: 500
狗的测试集共有图片: 500

2.3 读取和预处理数据集

和前面的实验一样,我们需要对数据集中的图片进行读取、转换、归一化和多组数组存储等功能,我们一用Keras的工具ImageDataGenerator,进行处理,这是非常方便快捷的方法。

from keras.preprocessing.image import ImageDataGenerator

# 数据归一化
train_datagen = ImageDataGenerator(rescale=1./255)
test_datagen = ImageDataGenerator(rescale=1./255)

# 直接从文档中构建训练集文件数据
train_generator = train_datagen.flow_from_directory( 
        # 目录参数
        train_dir,
        # 将图片尺寸转换成150*150
        target_size=(150, 150),
        # 每次生成数据批次为20
        batch_size=20,
        # 设置数据为一个二分类的任务
        class_mode='binary')

# 直接从文档中构建验证集文件数据
validation_generator = test_datagen.flow_from_directory(
        validation_dir,
        target_size=(150, 150),
        batch_size=20,
        class_mode='binary')
Found 2000 images belonging to 2 classes.
Found 1000 images belonging to 2 classes.

查看数据生成器中的图像和标签值,其中图像代表20个150*150的3通道RGB图像,而标签值20分别代表20个标签。

train_generator[0][0].shape, train_generator[0][1].shape
((20, 150, 150, 3), (20,))

3. 构建神经网络模型

本节实验需要分两步走,首先第一步是先构建一个相对简单的神经网络进行训练和预测,再对模型训练的结果进行评估后,找出问题,再次修改训练方法重新训练。

3.1 尝试一:搭建简单的模型进行训练与评估

首先我们搭建一组全是卷积(Conv)和下采样(Pool)功能的一个神经网络,所使用的激活函数是relu,最后通过sigmoid函数进行分类结果输出一个神经元。

from keras.layers import Conv2D, MaxPooling2D, Flatten, Dense
from keras import models
from keras.utils import plot_model

model = models.Sequential()
model.add(Conv2D(32, (3, 3), activation='relu',
                        input_shape=(150, 150, 3)))
model.add(MaxPooling2D((2, 2)))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D((2, 2)))
model.add(Conv2D(128, (3, 3), activation='relu'))
model.add(MaxPooling2D((2, 2)))
model.add(Conv2D(128, (3, 3), activation='relu'))
model.add(MaxPooling2D((2, 2)))
model.add(Flatten())
model.add(Dense(512, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
model.summary()
WARNING: Logging before flag parsing goes to stderr.
W0112 04:36:01.784457 4539753920 deprecation_wrapper.py:119] From /Users/jingyuyan/anaconda3/envs/dlwork/lib/python3.6/site-packages/keras/backend/tensorflow_backend.py:4070: The name tf.nn.max_pool is deprecated. Please use tf.nn.max_pool2d instead.



Model: "sequential_2"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_1 (Conv2D)            (None, 148, 148, 32)      896       
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 74, 74, 32)        0         
_________________________________________________________________
conv2d_2 (Conv2D)            (None, 72, 72, 64)        18496     
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 36, 36, 64)        0         
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 34, 34, 128)       73856     
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 17, 17, 128)       0         
_________________________________________________________________
conv2d_4 (Conv2D)            (None, 15, 15, 128)       147584    
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 7, 7, 128)         0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 6272)              0         
_________________________________________________________________
dense_1 (Dense)              (None, 512)               3211776   
_________________________________________________________________
dense_2 (Dense)              (None, 1)                 513       
=================================================================
Total params: 3,453,121
Trainable params: 3,453,121
Non-trainable params: 0
_________________________________________________________________

如图所示,我们搭建好了一个5个卷积层的神经网络。

img1

使用RMSprop作为优化器和二进制交叉熵binary_crossentropy作为损失函数,因为我们训练的神经网络最终结果是输出一个以二分类为任务的神经元。

from keras import optimizers

model.compile(loss='binary_crossentropy',
              optimizer=optimizers.RMSprop(lr=1e-4),
              metrics=['acc'])
# 设置模型参数和训练参数
# 设置50个数据进行验证
VALIDATION_STEPS = 50
# 训练周期,这边设置30个周期即可
EPOCHS = 30
# 每个epoch增加新生成的2000个数据量
STEPS_PER_EPOCH = 100
history = model.fit_generator(
    train_generator,
    steps_per_epoch=STEPS_PER_EPOCH,
    epochs=EPOCHS,
    validation_data=validation_generator,
    validation_steps=VALIDATION_STEPS)
Epoch 1/30
100/100 [==============================] - 8s 76ms/step - loss: 0.6937 - acc: 0.5235 - val_loss: 0.6773 - val_acc: 0.6160
Epoch 2/30
100/100 [==============================] - 5s 53ms/step - loss: 0.6639 - acc: 0.5980 - val_loss: 0.7025 - val_acc: 0.6180
Epoch 3/30
100/100 [==============================] - 5s 54ms/step - loss: 0.6285 - acc: 0.6590 - val_loss: 0.5041 - val_acc: 0.6630
Epoch 4/30
100/100 [==============================] - 5s 52ms/step - loss: 0.5859 - acc: 0.6915 - val_loss: 0.5784 - val_acc: 0.6800
Epoch 5/30
100/100 [==============================] - 5s 55ms/step - loss: 0.5479 - acc: 0.7230 - val_loss: 0.5690 - val_acc: 0.6850
Epoch 6/30
100/100 [==============================] - 5s 53ms/step - loss: 0.5240 - acc: 0.7325 - val_loss: 0.5569 - val_acc: 0.6970
Epoch 7/30
100/100 [==============================] - 5s 51ms/step - loss: 0.4901 - acc: 0.7695 - val_loss: 0.7007 - val_acc: 0.6900
Epoch 8/30
100/100 [==============================] - 5s 53ms/step - loss: 0.4664 - acc: 0.7815 - val_loss: 0.4535 - val_acc: 0.7130
.....
Epoch 28/30
100/100 [==============================] - 5s 54ms/step - loss: 0.0587 - acc: 0.9825 - val_loss: 0.9573 - val_acc: 0.7370
Epoch 29/30
100/100 [==============================] - 6s 55ms/step - loss: 0.0516 - acc: 0.9885 - val_loss: 0.5559 - val_acc: 0.7320
Epoch 30/30
100/100 [==============================] - 5s 53ms/step - loss: 0.0337 - acc: 0.9945 - val_loss: 1.9266 - val_acc: 0.7230

定义绘制函数,绘制出模型训练过程

def plot_train_history(history, train_metrics, val_metrics):
    plt.plot(history.history.get(train_metrics))
    plt.plot(history.history.get(val_metrics))
    plt.ylabel(train_metrics)
    plt.xlabel('Epochs')
    plt.legend(['train', 'validation'])
    plt.show()
import matplotlib.pyplot as plt
plot_train_history(history, 'loss', 'val_loss')

img1

plot_train_history(history, 'acc', 'val_acc')

img1

很显然,虽然模型训练过程中达到了0.99的准确率,但是过拟合问题却非常严重,例如模型在验证集(validation)的准确率一直不高、损失(loss)浮动较为夸张。在之前的章节中我们已经介绍过一些防止过拟合的方法,例如权重衰减、引入Dropout层等等。但是这些方法在小数据集上的解决过拟合问题并不是非常显著,所以我们需要引入一个较为有效的方法——数据扩充,来解决过拟合问题。

3.2 利用数据扩充解决过拟合问题

上节实验我们通过ImageDataGenerator构建训练所需的数据集,经过搭建的神经网络训练后的结果如上小节所示,出现了比较严重的过拟合问题,模型在训练的过程中验证集的准确率始终在0.6和0.7之间浮动,而训练集的准确率却能达到0.99,所以我们本节需要解决数据量过少而引起的过拟合问题。

3.2.1 Keras数据扩充

数据少的情况下,我们可以利用Keras的ImageDataGenerator图片数据生成器按照一定的要求在原有数据的基础上新的数据。我们常用的参数生成数据如下:

  • width_shift和height_shift是横向或者纵向图片随机转移图片
  • rotation_range是以0到180度内旋转图片
  • zoom_range随机对图片进行缩放
  • horizontal_flip水平翻转图片
  • fill_mode使图片在旋转、位移后填充新像素区域的模式

构建数据扩充器

from keras.preprocessing.image import ImageDataGenerator

gen = ImageDataGenerator(
      width_shift_range=0.2,
      height_shift_range=0.2,
      rotation_range=40,
      shear_range=0.2,
      zoom_range=0.2,
      horizontal_flip=True,
      fill_mode='nearest')

利用构建好的数据生成器gen生成扩充图片,并且对随机一张图片进行数据扩充操作,生成新的数据。

import matplotlib.pyplot as plt
from keras.preprocessing import image
import cv2

# 选取狗的训练集中所有图片的地址
imgs_path = [os.path.join(train_dogs_dir, path) for path in os.listdir(train_dogs_dir)]

# 随机读取一张图片
img_path = imgs_path[0] 
img = cv2.imread(img_path)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img = cv2.resize(img, (150, 150))
img = img.reshape((1, ) + img.shape)

利用gen的flow随机生成数据,在循环中会无限循环的生成数据,所以我们需要制定断开条件。

show_count = 5
fig = plt.figure(figsize=(12, 12))
idx = 1
for gen_img in gen.flow(img, batch_size=1):
    ax = fig.add_subplot(1, show_count, idx, xticks=[], yticks=[])
    ax.imshow(image.array_to_img(gen_img[0]))
    if idx >= show_count:
        break
    idx += 1
plt.show()

img1

我们利用数据生成器生成的数据如图所示,可以轻松将一张图片经过图像处理,变换出更多的图片,对数据集进行了一个扩充。

3.2.2构建模型

在使用ImageDataGenerator实现了上面的数据生成器后,我们需要搭建神经网络进行模型的训练处理。可以发现,尽管已经扩充数据,但是新的数据来源还是原来的原始数据集中的数据,从某种意义上来讲,我们并没有产生新的信息,只是增强了他对原有数据的学习资料。

from keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout
from keras import models
from keras.utils import plot_model

model = models.Sequential()
model.add(Conv2D(32, (3, 3), activation='relu',
                        input_shape=(150, 150, 3)))
model.add(MaxPooling2D((2, 2)))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D((2, 2)))
model.add(Conv2D(128, (3, 3), activation='relu'))
model.add(MaxPooling2D((2, 2)))
model.add(Conv2D(128, (3, 3), activation='relu'))
model.add(MaxPooling2D((2, 2)))
model.add(Flatten())
model.add(Dropout(0.5))
model.add(Dense(512, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
model.summary()
Model: "sequential_6"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_5 (Conv2D)            (None, 148, 148, 32)      896       
_________________________________________________________________
max_pooling2d_5 (MaxPooling2 (None, 74, 74, 32)        0         
_________________________________________________________________
conv2d_6 (Conv2D)            (None, 72, 72, 64)        18496     
_________________________________________________________________
max_pooling2d_6 (MaxPooling2 (None, 36, 36, 64)        0         
_________________________________________________________________
conv2d_7 (Conv2D)            (None, 34, 34, 128)       73856     
_________________________________________________________________
max_pooling2d_7 (MaxPooling2 (None, 17, 17, 128)       0         
_________________________________________________________________
conv2d_8 (Conv2D)            (None, 15, 15, 128)       147584    
_________________________________________________________________
max_pooling2d_8 (MaxPooling2 (None, 7, 7, 128)         0         
_________________________________________________________________
flatten_2 (Flatten)          (None, 6272)              0         
_________________________________________________________________
dropout_1 (Dropout)          (None, 6272)              0         
_________________________________________________________________
dense_3 (Dense)              (None, 512)               3211776   
_________________________________________________________________
dense_4 (Dense)              (None, 1)                 513       
=================================================================
Total params: 3,453,121
Trainable params: 3,453,121
Non-trainable params: 0
_________________________________________________________________

我们在原有的模型中的全连接层后加一个Dropout层,丢弃一部分神经元,进一步解决一些过拟合的问题。

img1

开始训练模型, 使用ImageDataGenerator数据生成训练数据集、测试数据集和验证数据集

from keras import optimizers
model.compile(loss='binary_crossentropy',
              optimizer=optimizers.RMSprop(lr=1e-4),
              metrics=['acc'])
# 设置模型参数和训练参数
# 设置50个数据进行验证
VALIDATION_STEPS = 50
# 训练周期,这边设置50个周期即可
EPOCHS = 50
# 每个epoch增加新生成的2000个数据量
STEPS_PER_EPOCH = 100
train_datagen = ImageDataGenerator(
    rescale=1./255,
    rotation_range=40,
    width_shift_range=0.2,
    height_shift_range=0.2,
    shear_range=0.2,
    zoom_range=0.2,
    horizontal_flip=True,)

test_datagen = ImageDataGenerator(rescale=1./255)

train_generator = train_datagen.flow_from_directory(
        train_dir,
        target_size=(150, 150),
        batch_size=32,
        class_mode='binary')

validation_generator = test_datagen.flow_from_directory(
        validation_dir,
        target_size=(150, 150),
        batch_size=32,
        class_mode='binary')

history = model.fit_generator(
      train_generator,
      steps_per_epoch=STEPS_PER_EPOCH,
      epochs=EPOCHS,
      validation_data=validation_generator,
      validation_steps=VALIDATION_STEPS)
Found 2000 images belonging to 2 classes.
Found 1000 images belonging to 2 classes.


Epoch 1/50
100/100 [==============================] - 22s 218ms/step - loss: 0.6917 - acc: 0.5290 - val_loss: 0.6529 - val_acc: 0.5914
Epoch 2/50
100/100 [==============================] - 19s 192ms/step - loss: 0.6778 - acc: 0.5641 - val_loss: 0.8147 - val_acc: 0.5135
Epoch 3/50
100/100 [==============================] - 19s 191ms/step - loss: 0.6578 - acc: 0.5900 - val_loss: 0.6522 - val_acc: 0.6510
Epoch 4/50
100/100 [==============================] - 19s 191ms/step - loss: 0.6362 - acc: 0.6329 - val_loss: 0.6663 - val_acc: 0.6166
Epoch 5/50
100/100 [==============================] - 19s 192ms/step - loss: 0.6240 - acc: 0.6457 - val_loss: 0.5969 - val_acc: 0.6225
Epoch 6/50
100/100 [==============================] - 21s 207ms/step - loss: 0.6050 - acc: 0.6680 - val_loss: 0.5138 - val_acc: 0.6939
Epoch 7/50
100/100 [==============================] - 19s 189ms/step - loss: 0.5900 - acc: 0.6783 - val_loss: 0.6105 - val_acc: 0.6904
Epoch 8/50
100/100 [==============================] - 19s 194ms/step - loss: 0.5893 - acc: 0.6799 - val_loss: 0.6085 - val_acc: 0.7152
Epoch 9/50
100/100 [==============================] - 19s 189ms/step - loss: 0.5862 - acc: 0.6925 - val_loss: 0.5747 - val_acc: 0.6830
.....
Epoch 49/50
100/100 [==============================] - 19s 187ms/step - loss: 0.4285 - acc: 0.7940 - val_loss: 0.4307 - val_acc: 0.7906
Epoch 50/50
100/100 [==============================] - 19s 191ms/step - loss: 0.4327 - acc: 0.7974 - val_loss: 0.5460 - val_acc: 0.7648

显示训练过程

def plot_train_history(history, train_metrics, val_metrics):
    plt.plot(history.history.get(train_metrics))
    plt.plot(history.history.get(val_metrics))
    plt.ylabel(train_metrics)
    plt.xlabel('Epochs')
    plt.legend(['train', 'validation'])
    plt.show()
plot_train_history(history, 'loss', 'val_loss')

img1

plot_train_history(history, 'acc', 'val_acc')

img1

可以发现,过拟合的问题虽然还存在,但是比起之前的实验,相对也有所下降。

结论

本章我们尝试了小数据的分类实验,但是在训练过程中模型准确率却仅有0.76左右,读者可以想办法自行提升模型识别率,例如使用预训练模型权重进行学习。

版权声明:如无特殊说明,文章均为本站原创,转载请注明出处

本文链接:http://tunm.top/article/learning_06/