美文网首页机器学习与数据挖掘Machine Learningkeras深度学习模型
Keras深度学习实践3—计算机视觉问题:猫vs狗

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

作者: 小可哥哥V | 来源:发表于2019-05-07 13:27 被阅读0次

    内容参考以及代码整理自“深度学习四大名“著之一《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.卷积运算

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

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

    • 平移不变性:卷积神经网络在图像中某个位置学到的特征,如果在其他位置出现也可以被识别。这使得卷积神经网络在处理图像的时候可以高效利用数据,它只需要更少的训练样本就可以学到具有泛化能力的数据表示。
    • 可以学到模式的空间层次结构。例如,第一个卷积层讲学习到较小的局部模型,第二个卷积层将学习由第一层特征组成的更大模式。这使得卷积神经网络可以有效的学习越来越复杂、越来越抽象的视觉概念。

    卷积的两个关键参数:

    • 从输入中提取图块的尺寸:这些图块的大小通常是33或者55.
    • 输出特征图的深度:卷积所计算的过滤器的数量,也可以理解为特征的数量。

    卷积的工作原理:在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形式,我们需要对数据进行预处理以便网络更好的处理。预处理主要做以下几个步骤:

    • 读取图像文件
    • 将JPEG文件解码为RGB像素网格
    • 将这些像素转换为浮点数张量
    • 将像素值(0~255范围内)缩放到[0, 1]区间

    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')
    
    • rotation_range是角度值,表示图像堆积旋转的角度范围。
    • width_shift和height_shift是图像在水平或垂直方向上平移的范围
    • shear_range是随机错切变换的角度
    • zoom_range是图像随机缩放的范围
    • horizontal_flip是随机将一半图像水平翻转。如果没有水平不对称的假设,这种做法是有意义的。
    • fill_mode是用于填充创建像素的方法,这些新像素可能来自于旋转或宽度/高度平移。

    为了进一步降低拟合,在网络中添加了一个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

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/amseoqtx.html