Keras深度学习实践4—使用预训练网络解决计算机视觉问题

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

上期回顾
上一次我们已经对猫狗图片分类问题的进行了解决,并采用了数据增强的方案来提升训练精度,最终的训练精度大约在80%左右,我们不能止步于此,为了进一步提升训练精度,我们将引入预训练,并对模型进行微调,来达到更好训练结果。

预训练网络: 想要将深度学习应用于小型图像数据集,一种常用非常高效的方法就是使用预训练网络。预训练网络是一个保存好的网络,之前在大型数据集上训练好。如果这个原始的数据集足够大且足够通用,那么预训练网络学到的特征在空间层次结构可以有效地作为视觉世界的通用模型。这次我们使用的是VGG16模型,它由Karen Simonyan和Andrew Zisserman在2014年开发。对于ImgaeNet,它是一种简单又广泛的卷积神经网络。

一、特征提取

特征提取是使用之前网络学到的表示来从新样本提取出有趣的特征。然后将这些特征输入到一个新的分类器中,从头开始训练。

用于图像分类的卷积神经网络包含两部分: 首先是一些列池化层和卷积层,最后是一个密集连接分类层。第一部分叫做模型的卷积基。对于卷积神经网络而言,特征提取就是取出之前训练好的网络的卷积基,在尚敏运行新数据,然后在输出上面训练一个新的分类器。

特征提取原理

某个卷积层提取的表示特征的通用性取决于该层在模型中的深度。模型中更靠近底部的层提取的是局部的、高度通用的特征图,比如视觉边缘、颜色和纹理而更靠近顶部的层提取的是更加抽象的概念,比如“猫耳朵”或“狗眼睛”。因此,如果你的新数据集与原始模型训练的数据集有很大的差异,那么最好只是用模型的前几层来做特征提取,而不是使用整个卷积层

首先,我们实例化VGG16卷积基:

WEIGHTS_PATH_NO_TOP = 'E:\\git_code\\MachineLearning\\keras\\vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5'

conv_base = VGG16(weights=WEIGHTS_PATH_NO_TOP, include_top=False, input_shape=(150, 150, 3))

这里我使用的本地下载的卷积基。这个构造函数中参数的意义:

  • weights指模型初始化的权重检查点,本地加载的时候传入模型的位置。
  • include_top指定模型最后是否包含密集连接分类器。
  • input_shape是输入到网络中输入张量的形状。

我们需要为网络添加密集分类器,这有两种方法可以选择:

  • 在你的数据集上运行卷积基,将输出保存为Numpy数组,然后用这个数据作为输入,输入到独立的密集分类器中。这种方法速度快,计算代价低,因为对于每个输入图像只需一次卷积基,而卷积基是目前流程中计算代价最高的。这种方法无法使用数据增强。
  • 在顶部添加dense层来扩展已有的模型,并在输入数据上端到端地运行整个模型。可以使用数据增强,但这种方法计算代价要更高。

1.不使用数据增强的快速特征提取:

    def extract_features(directory, sample_count):
    """
    此函数使用VGG16卷积基对图片进行预处理,提取特征
    :param directory: 数据集路径
    :param sample_count: 使用的样本数
    :return:
    """

    batch_size = 20
    datagen = ImageDataGenerator(rescale=1. / 255)

    features = np.zeros(shape=(sample_count, 4, 4, 512))
    labels = np.zeros(shape=(sample_count))
    generator = datagen.flow_from_directory(directory, target_size=(150, 150), batch_size=batch_size,
                                            class_mode='binary')
    i = 0
    for inputs_batch, labels_batch in generator:
        features_batch = conv_base.predict(inputs_batch)
        features[i * batch_size: (i + 1) * batch_size] = features_batch
        labels[i * batch_size: (i + 1) * batch_size] = labels_batch
        i += 1
        if i * batch_size >= sample_count:
            break
    return features, labels

上面这个函数将使用生成器,将图像机器标签提取为numpy数组,并使用VGG16实例化的conv_base卷积基提取特征。

这里模型构造如下:

def bulid_dropout_model():
    """
    构建网络,在聚集基的基础上加上分类器即可,同时加上Dropout
    :return:
    """
    model = models.Sequential()
    model.add(layers.Flatten())
    model.add(layers.Dense(256, activation='relu', input_dim=4 * 4 * 512))
    model.add(layers.Dropout(0.5))
    model.add(layers.Dense(1, activation='sigmoid'))
    model.compile(optimizer=optimizers.RMSprop(lr=2e-5),
                  loss='binary_crossentropy',
                  metrics=['acc'])

    return model

在这个模型中首先展平数据,然后添加分类器,最后我们来训练网络:

train_dir, validation_dir, test_dir = create_fold()

""" 特征提取 """
train_features, train_labels = extract_features(train_dir, 2000)
validation_features, validation_labels = extract_features(validation_dir, 1000)
test_features, test_labels = extract_features(test_dir, 1000)

""" 构建网络,训练网络 """
model = bulid_dropout_model()
history = model.fit(train_features, train_labels, epochs=30, batch_size=20,
                    validation_data=(validation_features, validation_labels))

训练结果如下:

训练精度
训练损失

我们可以看出现在训练精度已经接近90%,但模型还是在较早的轮数就呈现出过拟合现象,所以我们将引入数据增强来进一步提升精度。

2.使用数据增强的特征提取

这次我们再模型中扩展卷积基,然后使用数据增强在模型上运行。那么,我们的模型是这样的:

def build_model():
    """ 构建网络,使用VGG16卷积基 """
    conv_base = VGG16(weights=WEIGHTS_PATH_NO_TOP, include_top=False, input_shape=(150, 150, 3))
    conv_base.traniable = False   # 将卷积基设置为不可训练,不要改变它的权重
    model = models.Sequential()
    model.add(conv_base)
    model.add(layers.Flatten())
    model.add(layers.Dense(256, activation='relu'))
    model.add(layers.Dense(1, activation='sigmoid'))

    return model

我们再编译模型之前,要将VGG16的卷积基冻结,也就是将模型中一层或多层在训练中保持其权重不变。因为新添加的Dense层是随机初始化的,所以非常大的权重更新将会在网络中传播,会对之前学到的表示造成很大的破坏。我们冻结卷积基,只有两个dense层可以被训练。

使用数据增强,还是使用生成器来生成增强数据,并训练网络:

train_dir, validation_dir, test_dir = create_fold()

""" 数据增强 """
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, fill_mode='nearest')
test_datagen = ImageDataGenerator(rescale=1./255)

train_generator = train_datagen.flow_from_directory(train_dir, target_size=(150, 150), 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')

""" 构建、编译、训练网络 """
model = build_model()

model.compile(loss='binary_crossentropy', optimizer=optimizers.RMSprop(lr=2e-5), metrics=['acc'])
history = model.fit_generator(train_generator, steps_per_epoch=100, epochs=30, validation_data=validation_generator,
                              validation_steps=50)

训练结果如下:

训练精度
训练损失

训练精度超过了95%

二、模型微调

另一种广泛使用的模型复用方法叫做模型微调,模型微调是将卷积基的top基层解冻,进行联合训练。模型微调的步骤如下:

  • 在已经训练好的及网络上添加自定网络
  • 冻结卷积基
  • 训练所添加的部分
  • 解冻卷积基中一些层
  • 联合训练这些层和添加的部分。

在前民特征提取中,我们已经完成了前三个步骤,现在我们继续进行第四步,解冻conv_base中的默写层,然后冻结其中的部分层。我们将微调最后三个卷积层,为什么不微调更多的卷积基呢?原因如下:

  • 卷积基中靠近底部的是更加通用的可复用特征,而顶部是更具体、特定场景的特征,改变顶部的特征更加有用,它们需要在你的新问题上改变用途。
  • 训练参数越多,过拟合的风险就越大。卷积基有1500w个参数,在小型数据集上训练过多参数是有风险的。

使用conv_base.summary()可以看到卷积基的架构,位于最顶的三层的是卷积层是block5_conv1、block5_conv2、block5_conv3,我们将这三层解冻,并进行训练:

    if fine_tunning:
        conv_base.trainable = True
        set_trainable = False
        for layer in conv_base.layers:
            if layer.name == 'block5_conv1':
                set_trainable = True
            if set_trainable:
                layer.trainable = True
            else:
                layer.trainable = False
        
        model.compile(loss='binary_crossentropy', optimizer=optimizers.RMSprop(lr=1e-5), metrics=['acc'])
        history = model.fit_generator(train_generator, steps_per_epoch=100, epochs=100,
                                      validation_data=validation_generator,
                                      validation_steps=50)

为了更好的展示训练结果,我们对训练结果数据点位进行平滑,使用下面的平滑函数和画图函数:

def smooth_curve(points, factor=0.8):
    """
    平滑数据点位,使用指数移动平均值
    :param points: 数据点
    :param factor: 平移因子
    :return: 平滑后的数据点
    """
    smoothed_points = []
    for point in points:
        if smoothed_points:
            previous = smoothed_points[-1]
            smoothed_points.append(previous * factor + point * (1 - factor))
        else:
            smoothed_points.append(point)
    
    return smoothed_points


def plot_result_curve(history):
    """
    绘制平滑后的精度和损失图像
    :param history: 训练结果
    :return: 无
    """
    acc = history.history['acc']
    val_acc = history.history['val_acc']
    loss = history.history['loss']
    val_loss = history.history['val_loss']
    epochs = range(1, len(acc) + 1)

    plt.plot(epochs,
             smooth_curve(acc), 'bo', label='Smoothed training acc')
    plt.plot(epochs,
             smooth_curve(val_acc), 'b', label='Smoothed validation acc')
    plt.title('Training and validation accuracy')
    plt.legend()
    plt.figure()
    plt.plot(epochs,
             smooth_curve(loss), 'bo', label='Smoothed training loss')
    plt.plot(epochs,
             smooth_curve(val_loss), 'b', label='Smoothed validation loss')
    plt.title('Training and validation loss')
    plt.legend()
    plt.show()

训练结果如下,精度又有了进一步的提升,大约在96%~97%

训练精度
训练损失

三、小结

我们从最简单的卷积神经网络开始,不断加入数据增强、预训练、特征提取、模型微调,一步一步提升训练精度,最终达到了97%的训练精度,然而我们只用了完整训练集十分之一的数据。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容