机器学习与数据挖掘Machine Learningkeras深度学习模型

Keras深度学习实践3—计算机视觉问题:猫vs狗

2019-05-07  本文已影响0人  小可哥哥V

内容参考以及代码整理自“深度学习四大名“著之一《Python深度学习》

一、卷积神经网络

卷积神经网络,也叫convnet,它是计算机视觉应用几乎都在使用的一种深度学习模型。

我们先来看一个简单的卷积神经网络例示,使用卷积神经网络对MNIST数字进行分类。

"""
MNIST手写数字问题的卷积神经网络解决方案
"""
from keras import layers
from keras import models
from keras.datasets import mnist
from keras.utils import to_categorical
import matplotlib.pyplot as plt

"""
构建网络
"""
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))
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(64, (3, 3), activation='relu'))

model.add(layers.Flatten())
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(10, activation='softmax'))

"""
训练数据
"""
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
train_images = train_images.reshape((60000, 28, 28, 1))
train_images = train_images.astype('float32') / 255
test_images = test_images.reshape((10000, 28, 28, 1))
test_images = test_images.astype('float32') / 255
train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)
model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy'])
history = model.fit(train_images, train_labels, epochs=5, batch_size=64, validation_data=(test_images, test_labels))

"""
计算精度
"""
test_loss, test_acc = model.evaluate(test_images, test_labels)
print(test_acc)

上面这段代码中,网络中包含Conv2D和Maxpooling2D层也就是卷积运算最大池化运算。使用简单卷积神经网络测试精度可以达到99%以上,要比密集连接效果好。

1.卷积运算

与全连接层相比卷积层絮叨的是局部模式。对图像来说,学到的就是图像二位小窗口发现的模式。

卷积神经网络具有以下两个有趣的性质。

卷积的两个关键参数:

卷积的工作原理:在3D输入特征图上滑动3*3的窗口,在每个可能位置提取特征的3D图块。将3D图块与一个权重矩阵即卷积核做张量积,转换成形状为(output,)的1D向量。然后对这些向量进行重组,将其形状转换为(height, width, output_depth)的3D输出特征图。输出特征图中每个空间位置都对应与输入特征图中的相同位置。

卷积原理

2.最大池化运算

最大池化是从输入特征图中提取窗口,并输出每个通道的最大值。它的概念和卷积类似,但是最大池化使用的是硬编码的max张量运算对局部图块进行变化。最大池化通常使用的是2*2窗口和步幅2,其目的是将特征图下采样2倍。

使用最大池化的目的是:

最大池化不是实现这种下采样的唯一方法。还可以在卷积层使用步幅来实现,或者使用平均池化来实现。平均池化是将每个局部输入变换为图块各通道的平均值。

二、“猫狗大战”——猫狗图片分类问题实践

这里我们将使用一个较小的数据集来解决猫狗图片分类的问题。给出一张猫或者狗的图片,让程序告诉我们是狗还是猫。

1.数据准备

数据来源是从kaggle上下载,网址: www.kaggle.com/c/dogs-vs-cats/data

这个数据集包含25000张猫狗图像,我们只使用其中一小部分来完成实践:猫和狗各1000个样本的训练集、500个样本的验证集和测试集。所以我们要先构造这个规模较小的数据集。下面这个函数,我们来完成训练、验证、测试目录的生成。

def create_fold():
    """
    将原始数据中的图片分成训练集、验证集、测试集,并分文件夹存放。
    """
    original_dataset_dir = 'D:\\git_code\\data\\dogs-vs-cats\\train'  # 原始数据的目录

    base_dir = 'D:\\git_code\\data\\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')  # 创建cats的训练集目录
    if not os.path.exists(train_cats_dir):
        os.mkdir(train_cats_dir)

    train_dogs_dir = os.path.join(train_dir, 'dogs')  # 创建dogs的训练集目录
    if not os.path.exists(train_dogs_dir):
        os.mkdir(train_dogs_dir)

    validation_cats_dir = os.path.join(validation_dir, 'cats')  # 创建cats的验证集目录
    if not os.path.exists(validation_cats_dir):
        os.mkdir(validation_cats_dir)

    validation_dogs_dir = os.path.join(validation_dir, 'dogs')  # 创建dogs的验证集目录
    if not os.path.exists(validation_dogs_dir):
        os.mkdir(validation_dogs_dir)

    test_cats_dir = os.path.join(test_dir, 'cats')  # 创建cats的测试集目录
    if not os.path.exists(test_cats_dir):
        os.mkdir(test_cats_dir)

    test_dogs_dir = os.path.join(test_dir, 'dogs')  # 创建dogs的测试集目录
    if not os.path.exists(test_dogs_dir):
        os.mkdir(test_dogs_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)
        shutil.copyfile(src, dst)

    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)

    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)

    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)

    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)

    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)

    return train_dir, validation_dir, test_dir

2.构建网络

网络构建还是以Conv2D+MaxPooling的组合。对于这个二分类问题,优化器使用RMSprop,激活函数使用sigmod,损失函数使用二元交叉熵。

def bulid_model():
    """
    构建网络:使用Conv2D和MaxPooling2D层交叠构成。
    Flatten层将3D输出展平到1D
    二分类问题最终使用sigmod激活
    """
    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'))

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

3.数据预处理

我们数据源图片是JPEG形式,我们需要对数据进行预处理以便网络更好的处理。预处理主要做以下几个步骤:

keras提供完成这些的工具,位于keras.preprocessing,image。它有一个ImageDataGenerator,能够批量的将图像文件预处理。

def create_generator(dir):
    """
    创建数据生成器,这个生成器的作用是,将JPEG解码为RGB像素网格,然后将这些像素网格转换为浮点数向量,
    然后将像素值(0~255范围内)缩放到[0,1]区间。
    :param dir: 数据所在的目录
    :return: 返回一个生成器
    """
    dir_datagen = ImageDataGenerator(rescale=1. / 255)  # 将所有图像乘以1/255缩放
    generator = dir_datagen.flow_from_directory(dir, target_size=(150, 150),  # 图片大小调整为150 * 150
                                                batch_size=20,
                                                class_mode='binary')  # 使用二进制
    return generator

上面这段代码会生成: 150*150的RGB图像[shape=(20, 150, 150, 3)]与二进制标签[shape=(20,)]组成的批量,每个批量包含20个样本。

因为我们使用生成器来处理图像,我们要利用生成器来训练数据,keras提供了fit_generator来完成这个训练,fit_generator允许生成器作为训练集参数和验证集参数。

""" 生成器方式训练网网络 """
history = model.fit_generator(train_generator, steps_per_epoch=100, epochs=30, validation_data=validation_generator,
                              validation_steps=50

训练结果如下:

精度曲线 损失曲线

从训练结果曲线可以看出,训练精度随着时间逐渐增加,知道接近100%,而验证精度则停留在70%~72%。这都是典型的过拟合现象。针对计算机视觉领域,用深度学习处理图像时几乎都会用到的方法就是数据增强

4.使用数据增强

过拟合的原因是学习样本太少,导致无法训练出能够泛化到新数据的模型。数据增强是从现有的训练样本中生成更多的训练数据,其方法是利用多种能够生成可信图像的随机变换来增加样本。其目标是,模型在训练时不会两次查看完全相同的图像,这让模型能够观察到数据的更多内容,从而具有更好的泛化能力。

keras中我们还是使用图像生成器的方法来进行数据增强,在初始化对象的时候使用更多的参数来增强数据:

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')

为了进一步降低拟合,在网络中添加了一个Dropout层,添加在密集连接分类器之前。

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'))

训练结果如下:


精度曲线 损失曲线

可以看出,经过数据增强的结果准确率大约在80%左右,比未增强的提高了不少。

下一次,我们将继续提升训练的精度,请持续关注!

查看完整代码,请看: https://github.com/ubwshook/MachineLearning

上一篇下一篇

猜你喜欢

热点阅读