keras猫狗图像识别

这里,我们介绍的是一个猫狗图像识别的一个任务。数据可以从kaggle网站上下载。其中包含了25000张毛和狗的图像(每个类别各12500张)。
在小样本中进行尝试

我们下面先尝试在一个小数据上进行训练,首先创建三个子集:每个类别各1000个样本的训练集、每个类别各500个样本的验证集和每个类别各500个样本的测试集。

import os, shutil


original_dataset_dir = '/media/erphm/DATA/kaggle猫狗识别/train'    # 原始文解压目录
base_dir = '/media/erphm/DATA/kaggle猫狗识别/small_data'     #保存较小数据集目录
os.mkdir(base_dir)  #创建新的文件夹


# 分别对应划分好的训练,验证和测试目录
train_dir = os.path.join(base_dir, 'train')
os.mkdir(train_dir)
validation_dir = os.path.join(base_dir, 'validation')
os.mkdir(validation_dir)
test_dir = os.path.join(base_dir, 'test')
os.mkdir(test_dir)

#猫的训练目录
train_cats_dir = os.path.join(train_dir, 'cats')
os.mkdir(train_cats_dir)

# 狗的训练目录
train_dogs_dir = os.path.join(train_dir, 'dogs')
os.mkdir(train_dogs_dir)

# 猫的验证目录
validation_cats_dir = os.path.join(validation_dir, 'cats')
os.mkdir(validation_cats_dir)

# 狗的验证目录
validation_dogs_dir = os.path.join(validation_dir, 'dogs')
os.mkdir(validation_dogs_dir)

# 猫的测试目录
test_cats_dir = os.path.join(test_dir, 'cats')
os.mkdir(test_cats_dir)

# 狗的测试目录
test_dogs_dir = os.path.join(test_dir, 'dogs')
os.mkdir(test_dogs_dir)

# 将前1000张猫的图像复制到train_cats_dir
fnames = ['cat.{}.jpg'.format(i) for i in range(1000)]     #format函数通过{}来指点字符串处理的位置,储存为列表形式
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(train_cats_dir, fname)
    shutil.copyfile(src, dst)    #copyfile实现将一个文件中的内容复制道另一个文件中去,src是来源文件;dst是目标文件

# 将剩下的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)
    shutil.copyfile(src, dst)
    
# 将接下来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)
    shutil.copyfile(src, dst)
    
# 将前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)
    shutil.copyfile(src, dst)
    
# 将接下来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)
    shutil.copyfile(src, dst)
    
# Copy next 500 dog images to 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)
    shutil.copyfile(src, dst)

    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
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81

    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
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81

通过代码的方式,把文件中的内容划分进入新的文件夹。下面,测试一下分组的实现情况。

print('total training cat images:', len(os.listdir(train_cats_dir)))    #os.listdir列举指定目录中的文件名
print('total training dog images:', len(os.listdir(train_dogs_dir)))
print('total validation cat images:', len(os.listdir(validation_cats_dir)))
print('total validation dog images:', len(os.listdir(validation_dogs_dir)))
print('total test cat images:', len(os.listdir(test_cats_dir)))
print('total test dog images:', len(os.listdir(test_dogs_dir)))

    1
    2
    3
    4
    5
    6

    1
    2
    3
    4
    5
    6

total training cat images: 1000
total training dog images: 1000
total validation cat images: 500
total validation dog images: 500
total test cat images: 500
total test dog images: 500
构建网络

from keras import layers
from keras import models

model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu',
                        input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

从代码中我们可以看到,我们采用的是conv2D和MaxPooling2D交替组合的形式。特征图的深度在逐渐增加(从32增加到128),而特征图的尺寸在逐渐减小(从150150减小到77),这几乎是所有卷积神经网络的模式。这样做的还有一个好处是最后使用Flatten的时候尺寸不会太大。

因为,猫狗识别任务是一个二分类任务,所以网络最后一层使用的是sigmoid激活的单一单元(大小为1的Dense层)。这个单元对某个类别的概率进行编码。

model.summary()

    1

    1

输出:

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

    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

下面进行编译:

from keras import optimizers

model.compile(loss='binary_crossentropy',
              optimizer=optimizers.RMSprop(lr=1e-4),
              metrics=['acc'])

    1
    2
    3
    4
    5

    1
    2
    3
    4
    5

数据处理

图片不能直接放入神经网络中进行学习,学习之前应该把数据格式化为经过预处理的浮点数张量。我们接下来数据处理大致分为4个方向:
(1) 读取图像文件
(2) 将JPEG文件解码为RGB像素网格
(3) 将这些像素网格转换为浮点数张量
(4) 将像素值(0-255范围内)缩放到[0,1]区间(神经网络喜欢处理较小的数据输入值)

Keras中的keras.preprocessing.image有包含ImageDataGenerator类,可以快速创建Python生成器,能够将硬盘上的图像文件自动转换为预处理好的张量批量,下面看一下代码:

from keras.preprocessing.image import ImageDataGenerator

train_datagen = ImageDataGenerator(rescale=1./255)   
test_datagen = ImageDataGenerator(rescale=1./255)

# 使用flow_from_directory()方法可以实例化一个针对图像batch的生成器
train_generator = train_datagen.flow_from_directory(
        
        train_dir,     # 目标目录
        
        target_size=(150, 150),      # 将所有图像大小调整为150*150
        batch_size=20,
        
        class_mode='binary')      # 因为使用了binary_crossentropy损失,所以需要使用二进制标签

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

来看一下其中一个生成器的输出:它生成150*150的RGB图像[形状为(20, 150, 150, 3)]与二进制标签[形状为(20,)]组成的批量。每个批量包含20个样本。注意,生成器会不停的生成这种批量,它会不断的循环目标文件夹中的图像,因此,你需要在某个时刻(break)迭代循环。

for data_batch, labels_batch in train_generator:
    print('data batch shape:', data_batch.shape)
    print('labels batch shape:', labels_batch.shape)
    break

    1
    2
    3
    4

    1
    2
    3
    4

输出为:
data batch shape: (20, 150, 150, 3)
labels batch shape: (20,)

利用生成器,我们可以让模型对数据进行拟合。我们使用fit_generator方法来拟合数据(在生成器上的效果和fit一样),他的第一个参数应该是Python生成器。因为数据是不断生成的,所以Keras模型需要知道每一论需要从生成器中抽取多少个样本,这就用到了step_per_epoch参数。
我们还可以向里面传入一个validataion_data参数,这个参数也可以是一个生成器,也可以是Numpy数组组成的元组,如果是生成器的话,还需要指定validation_steps参数,用来说明需要从验证的生成器中抽取多少个批次用于评估。

history = model.fit_generator(
      train_generator,
      steps_per_epoch=100,
      epochs=30,
      validation_data=validation_generator,
      validation_steps=50)

    1
    2
    3
    4
    5
    6

    1
    2
    3
    4
    5
    6

之后,我们可以把我们的模型保存一下

model.save('cats_and_dogs_small_1.h5')

    1

下面分别绘制训练过程中模型在训练数据和验证数据长的损失函数和精度

import matplotlib.pyplot as plt

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

epochs = range(len(acc))

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()

plt.figure()

plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22

在这里插入图片描述

在这里插入图片描述

从图中我们可以看到过拟合的现象,训练精度随着时间线性增加,直到接近100%,而验证精度则基本停留在70-72%,验证损失在5轮后就达到最小值,然后保持不变,而训练损失则一直线性下降,直到接近于0.
因为训练样本相对来说比较少(2000个),所以过拟合是一个十分重要的问题,下面介绍一个针对计算机视觉处理过拟合的新方法,在深度学习处理图像时几乎都会用到的方法,数据增强
使用数据增强

在Keras中,我们可以通过对ImageDataGenerator实例读取的图像执行多次随机变换来实现。

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

    1
    2
    3
    4
    5
    6
    7
    8

    1
    2
    3
    4
    5
    6
    7
    8

显示几个随机增强之后的训练图像

from keras.preprocessing import image # 图像预处理的工具模块

fnames = [os.path.join(train_cats_dir, fname) for fname in os.listdir(train_cats_dir)]

img_path = fnames[3] # 选择一个图像进行数据增强

img = image.load_img(img_path, target_size=(150, 150)) # 读取图像并调整大小

x = image.img_to_array(img)   # 将其转换为形状(150, 150, 3)的Numpy数组

x = x.reshape((1,) + x.shape)  # 将其形状改为(1, 150, 150, 3)

# 生成随机变换后的图像批量
# 循环是无限的,因此你需要在某个时刻终止循环
i = 0
for batch in datagen.flow(x, batch_size=1):
    plt.figure(i)
    imgplot = plt.imshow(image.array_to_img(batch[0]))
    i += 1
    if i % 4 == 0:
        break

plt.show()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

使用这种数据增强的办法来训练新的网络,那么网络将不会两次看到一样的输入。但网络看到的输入仍然是高度相关的,因为这些输入都来自于少量的原始图像,无法生成新的信息,只能混合现有的信息。因此,这种方法不能完全消除过拟合。

下面向模型中添加Dropou层,进一步降低过拟合。

model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu',
                        input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dropout(0.5))
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

model.compile(loss='binary_crossentropy',
              optimizer=optimizers.RMSprop(lr=1e-4),
              metrics=['acc'])

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

下面使用数据增强训练带有Dropout的网络:

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(
        # This is the target directory
        train_dir,
        target_size=(150, 150),  # 将所有图像大小调整为(150, 150)
        batch_size=32,
        class_mode='binary')  # 因为使用binary_crossentropy损失,所以需要使用二进制标签

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=100,
      epochs=100,
      validation_data=validation_generator,
      validation_steps=50)

    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

    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

之后我们保存一下训练好的模型:

model.save('cats_and_dogs_small_2.h5')

    1

    1

我们重新绘制一下图像:

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

epochs = range(len(acc))

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()

plt.figure()

plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

在这里插入图片描述
在这里插入图片描述

通过观察这个绘图结果,可以发现使用数据增强和Dropout之后,模型过拟合情况大大减少,训练曲线紧跟着预测曲线。比之前没有使用正则化和数据增强的办法,效果要好很多。

更多精彩内容,欢迎关注我的微信公众号:数据瞎分析

在这里插入图片描述
---------------------  
作者:Einstellung  
来源:CSDN  
原文:https://blog.csdn.net/Einstellung/article/details/82773170  
版权声明:本文为博主原创文章,转载请附上博文链接!

猜你喜欢

转载自www.cnblogs.com/shuimuqingyang/p/10413182.html