【心得】有关使用预训练网络构建模型的两种思路

有关使用预训练网络构建模型的两种思路

最近做了一些通过Pre-train 与 Fine-tuning的实际案例,通过查阅资料,尤其是《Deep Learning With Python》这本宝书,发现了一些很有趣的思路,特来此分享一下。
以keras已经封装好的ResNet50为例,我们首先实例化一个卷积基。

from keras.applications.resnet50 import ResNet50

base_model = ResNet50(weights='imagenet',
                      include_top=False,
                      input_shape=(img_width, img_height, 3))

有过一定经验的同学都很清楚,接下来需要做的,就是在这个实例化的卷积基的顶部加上我们自己定义的分类器,这也是我们最常用的思路,以下称之为思路一。

1.思路一

在卷积基的顶部接上我们自己定义的分类器,构建自己的模型。
代码实现:

x = base_model.output
x = GlobalAveragePooling2D(name='average_pool')(x)
predictions = Dense(class_num, activation='softmax')(x)
model = Model(inputs=base_model.input, outputs=predictions)

这种方法之所以常用,就是因为它可以很方便的进行接下来的调整工作,包括数据增强、模型微调等等。
数据增强:

R_MEAN = 123.68
G_MEAN = 116.78
B_MEAN = 103.94

def preprocess(image):
    mean = [R_MEAN, G_MEAN, B_MEAN]
    image[..., 0] -= mean[0]
    image[..., 1] -= mean[1]
    image[..., 2] -= mean[2]
    return image


# 可以对训练集进行数据增强处理
train_datagen = ImageDataGenerator(preprocessing_function=preprocess,
                                   rotation_range=20,
                                   width_shift_range=0.1,
                                   height_shift_range=0.1,
                                   zoom_range=0.1,
                                   horizontal_flip=True,
                                   fill_mode='constant'
                                   )

# 测试集不许动,去均值中心化完了之后不许动
validation_datagen = ImageDataGenerator(preprocessing_function=preprocess)

模型微调:

ResNet50_LAYERS_TO_FREEZE = 'res5a_branch2c'

# 微调模型
base_model.trainable = True
set_trainable = False
for layer in base_model.layers:
    if layer.name == ResNet50_LAYERS_TO_FREEZE:
        set_trainable = True
    if set_trainable:
        layer.trainable = True
    else:
        layer.trainable = False

那么,既然思路一如此完美,为什么我们还要提出可能存在的思路二呢?
问题就出在执行顺序上。
当我们使用思路一建模时,我们会在输入数据上端到端地运行整个模型,因此每个输入图像进入模型时都会经过卷积基。正是因为这样,这种方法的计算代价很高。
假如我由于硬件问题,没有办法安装GPU版本的TensorFlow等框架的话,使用这种方法建模基本上是不可能实现的(因为太过消耗时间和硬件了)。为了解决这个问题,《Deep Learning With Python》上提出了另一种思路,以下,我们称之为思路二。

2. 思路二

其实思路二非常简单,或者说是取巧。那就是在你的数据集上运行卷积基,将输出保存成硬盘中的 Numpy 数组,然后用这个数据作为输入,输入到独立的密集连接分类器中。
明白了吗?
简单的说,就是整个模型的参数不动,让数据利用其已有的参数进行特征提取,然后将处理过的数据保存起来,可以是.npy、.txt、.csv等等你想要保存的形式,之后在训练模型的时候,直接用保存的数据来训练模型。
这种方法速度快,计算代价低,因为对于每个输入图像只需运行一次卷积基,而卷积基是目前流程中计算代价最高的。但出于同样的原因,这种方法不允许你使用数据增强。
贴一下代码能帮助你们更好的理解。
代码实现:

# dataProcessingResNet50.py,用于处理数据并保存
from keras.applications.resnet50 import ResNet50
import os
import numpy as np
from keras.preprocessing.image import ImageDataGenerator
from PIL import Image
import warnings
from PIL import ImageFile
ImageFile.LOAD_TRUNCATED_IMAGES = True
warnings.simplefilter("ignore", category=FutureWarning)
Image.MAX_IMAGE_PIXELS = None

# 定义必要的参数
num_train_sample = 168000
num_validation_sample = 9097
class_num = 168
batch_size = 100
img_width = 256
img_height = 256

R_MEAN = 123.68
G_MEAN = 116.78
B_MEAN = 103.94


conv_base = ResNet50(weights='imagenet',
                     include_top=False,
                     input_shape=(img_width, img_height, 3),
                     pooling='avg')

base_dir = 'input'
train_dir = os.path.join(base_dir, 'train')
validation_dir = os.path.join(base_dir, 'validation')


def preprocess(image):
    mean = [R_MEAN, G_MEAN, B_MEAN]
    image[..., 0] -= mean[0]
    image[..., 1] -= mean[1]
    image[..., 2] -= mean[2]
    return image


datagen = ImageDataGenerator(preprocessing_function=preprocess)


def extract_features(directory, sample_count):
    features = np.zeros(shape=(sample_count, 2048))
    labels = np.zeros(shape=(sample_count, class_num))
    generator = datagen.flow_from_directory(
        directory,
        target_size=(img_width, img_height),
        batch_size=batch_size,
        class_mode='categorical')
    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 % 2000 == 0:
            print("Processing image:", i * batch_size)
        if i * batch_size >= sample_count:
            break
    return features, labels


train_features, train_labels = extract_features(train_dir, num_train_sample)
validation_features, validation_labels = extract_features(validation_dir, num_validation_sample)


np.save('input/feature_train256res50.npy', train_features)
np.save('input/label_train256res50.npy', train_labels)
np.save('input/feature_validation256res50.npy', validation_features)
np.save('input/label_validation256res50.npy', validation_labels)

然后只需要把保存的数据读出来,并加以训练

# ResNet50.py,将保存的数据读出来,并训练模型
import numpy as np
import keras
from keras import models
from keras import layers
from keras import optimizers
import matplotlib.pyplot as plt
from PIL import Image
import warnings

warnings.simplefilter("ignore", category=FutureWarning)
Image.MAX_IMAGE_PIXELS = None


# 重要参数
class_num = 168
img_width = 256
batch_size = 128
epochs = 60

# 直接读取处理完以后的数据
train_features = np.load('input/feature_train256res50.npy')
train_labels = np.load('input/label_train256res50.npy')
print("load train data OK")
validation_features = np.load('input/feature_validation256res50.npy')
validation_labels = np.load('input/label_validation256res50.npy')
print("load validation data OK")

model = models.Sequential()
model.add(layers.Dense(class_num, activation='softmax'))
model.compile(optimizer=optimizers.RMSprop(lr=5e-4),
              loss='categorical_crossentropy',
              metrics=['acc'])
#              metrics=['acc'])

callback_list = [
    keras.callbacks.ReduceLROnPlateau(
        monitor='val_loss',
        factor=0.1,
        patience=3,
        verbose=1,
    )
]

history = model.fit(train_features, train_labels,
                    epochs=epochs,
                    batch_size=batch_size,
                    callbacks=callback_list,
                    validation_data=(validation_features, validation_labels))

# 保存模型
model.save('non_Bird_' + str(img_width) + '_ResNet50.h5')

思路二虽然没办法进行接下来的进一步优化,但是如果小伙伴们没办法安装GPU版本的框架的话,也会是种很不错的选择,因为基本上可以将思路二看作是使用全冻结的(或者说是全不冻结?)ResNet50预训练网络来进行构建模型。

好的,这次基本上就写到这里。

你可能感兴趣的:(心得,心得)