Keras VGG16 微调

Posted

技术标签:

【中文标题】Keras VGG16 微调【英文标题】:Keras VGG16 fine tuning 【发布时间】:2017-09-09 05:36:09 【问题描述】:

keras blog上有一个VGG16微调的例子,但我无法重现。

更准确地说,这里是用于在没有顶层的情况下初始化 VGG16 并冻结除最顶层之外的所有块的代码:

WEIGHTS_PATH_NO_TOP = 'https://github.com/fchollet/deep-learning-models/releases/download/v0.1/vgg16_weights_tf_dim_ordering_tf_kernels_notop.h5'
weights_path = get_file('vgg16_weights.h5', WEIGHTS_PATH_NO_TOP)

model = Sequential()
model.add(InputLayer(input_shape=(150, 150, 3)))
model.add(Conv2D(64, (3, 3), activation='relu', padding='same'))
model.add(Conv2D(64, (3, 3), activation='relu', padding='same'))
model.add(MaxPooling2D((2, 2), strides=(2, 2)))

model.add(Conv2D(128, (3, 3), activation='relu', padding='same'))
model.add(Conv2D(128, (3, 3), activation='relu', padding='same'))
model.add(MaxPooling2D((2, 2), strides=(2, 2)))

model.add(Conv2D(256, (3, 3), activation='relu', padding='same'))
model.add(Conv2D(256, (3, 3), activation='relu', padding='same'))
model.add(Conv2D(256, (3, 3), activation='relu', padding='same'))
model.add(MaxPooling2D((2, 2), strides=(2, 2)))

model.add(Conv2D(512, (3, 3), activation='relu', padding='same'))
model.add(Conv2D(512, (3, 3), activation='relu', padding='same'))
model.add(Conv2D(512, (3, 3), activation='relu', padding='same'))
model.add(MaxPooling2D((2, 2), strides=(2, 2)))

model.add(Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv1'))
model.add(Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv2'))
model.add(Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv3'))
model.add(MaxPooling2D((2, 2), strides=(2, 2), name='block5_maxpool'))

model.load_weights(weights_path)

for layer in model.layers:
    layer.trainable = False

for layer in model.layers[-4:]:
    layer.trainable = True
    print("Layer '%s' is trainable" % layer.name)  

接下来,创建一个具有单个隐藏层的顶层模型:

top_model = Sequential()
top_model.add(Flatten(input_shape=model.output_shape[1:]))
top_model.add(Dense(256, activation='relu'))
top_model.add(Dropout(0.5))
top_model.add(Dense(1, activation='sigmoid'))
top_model.load_weights('top_model.h5')

请注意,它之前曾接受过瓶颈特征的训练,如博客文章中所述。接下来,将这个顶层模型添加到基础模型中并编译:

model.add(top_model)
model.compile(loss='binary_crossentropy',
              optimizer=SGD(lr=1e-4, momentum=0.9),
              metrics=['accuracy'])

最终,拟合猫/狗数据:

batch_size = 16

train_datagen = ImageDataGenerator(rescale=1./255,
                                   shear_range=0.2,
                                   zoom_range=0.2,
                                   horizontal_flip=True)

test_datagen = ImageDataGenerator(rescale=1./255)

train_gen = train_datagen.flow_from_directory(
    TRAIN_DIR,
    target_size=(150, 150),
    batch_size=batch_size,
    class_mode='binary')

valid_gen = test_datagen.flow_from_directory(
    VALID_DIR,
    target_size=(150, 150),
    batch_size=batch_size,
    class_mode='binary')

model.fit_generator(
    train_gen,
    steps_per_epoch=nb_train_samples // batch_size,
    epochs=nb_epoch,
    validation_data=valid_gen,
    validation_steps=nb_valid_samples // batch_size)

但这是我在尝试适应时遇到的错误:

ValueError: 检查模型目标时出错:预期 block5_maxpool 有 4 > 维,但得到的数组形状为 (16, 1)

因此,基础模型中的最后一个池化层似乎有问题。或者可能我在尝试将基本模型与顶部模型连接时做错了。

有没有人有类似的问题?或者也许有更好的方法来构建这种“连接”模型?我正在使用keras==2.0.0theano 后端。

注意:我使用了 gist 和 applications.VGG16 实用程序中的示例,但是在尝试连接模型时遇到了问题,我对 keras 功能 API 不太熟悉。所以我在这里提供的这个解决方案是最“成功”的一个,即它只在拟合阶段失败。


更新 #1

好的,这是关于我正在尝试做的事情的一个小解释。首先,我从 VGG16 生成瓶颈特征如下:

def save_bottleneck_features():
    datagen = ImageDataGenerator(rescale=1./255)
    model = applications.VGG16(include_top=False, weights='imagenet')

    generator = datagen.flow_from_directory(
        TRAIN_DIR,
        target_size=(150, 150),
        batch_size=batch_size,
        class_mode=None,
        shuffle=False)    
    print("Predicting train samples..")
    bottleneck_features_train = model.predict_generator(generator, nb_train_samples)
    np.save(open('bottleneck_features_train.npy', 'w'), bottleneck_features_train)

    generator = datagen.flow_from_directory(
        VALID_DIR,
        target_size=(150, 150),
        batch_size=batch_size,
        class_mode=None,
        shuffle=False)
    print("Predicting valid samples..")
    bottleneck_features_valid = model.predict_generator(generator, nb_valid_samples)
    np.save(open('bottleneck_features_valid.npy', 'w'), bottleneck_features_valid)

然后,我创建了一个***模型并在这些特征上对其进行如下训练:

def train_top_model():
    train_data = np.load(open('bottleneck_features_train.npy'))
    train_labels = np.array([0]*(nb_train_samples / 2) + 
                            [1]*(nb_train_samples / 2))
    valid_data = np.load(open('bottleneck_features_valid.npy'))
    valid_labels = np.array([0]*(nb_valid_samples / 2) +
                            [1]*(nb_valid_samples / 2))
    model = Sequential()
    model.add(Flatten(input_shape=train_data.shape[1:]))  
    model.add(Dense(256, activation='relu'))
    model.add(Dropout(0.5))
    model.add(Dense(1, activation='sigmoid'))
    model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['accuracy'])
    model.fit(train_data, train_labels,
              nb_epoch=nb_epoch,
              batch_size=batch_size,
              validation_data=(valid_data, valid_labels),
              verbose=1)
    model.save_weights('top_model.h5')   

所以基本上,有两个经过训练的模型,base_model 具有 ImageNet 权重,top_model 具有从瓶颈特征生成的权重。我想知道如何连接它们?有可能还是我做错了什么?因为正如我所见,@thomas-pinetz 的响应假设***模型 没有单独训练,而是立即附加到模型中。不确定我是否清楚,这是博客的引述:

为了进行微调,所有层都应该从经过适当训练的权重开始:例如,您不应该在预训练的卷积基础之上添加一个随机初始化的全连接网络。这是因为由随机初始化的权重触发的大梯度更新会破坏卷积基中的学习权重。在我们的例子中,这就是为什么我们首先训练***分类器,然后才开始微调卷积权重。

【问题讨论】:

this question / answer 对您有帮助吗? 【参考方案1】:

我认为 vgg 网络描述的权重不适合您的模型,错误源于此。无论如何,有一种更好的方法可以使用 (https://keras.io/applications/#vgg16) 中描述的网络本身来执行此操作。

你可以使用:

base_model = keras.applications.vgg16.VGG16(include_top=False, weights='imagenet', input_tensor=None, input_shape=None)

实例化一个预训练的 vgg 网络。然后你可以冻结图层并使用模型类来实例化你自己的模型,如下所示:

x = base_model.output
x = Flatten()(x)
x = Dense(your_classes, activation='softmax')(x) #minor edit
new_model = Model(input=base_model.input, output=x)

要结合底部和顶部网络,您可以使用以下代码 sn-p。用到了以下函数(输入层(https://keras.io/getting-started/functional-api-guide/)/load_model(https://keras.io/getting-started/faq/#how-can-i-save-a-keras-model)和keras的功能API):

final_input = Input(shape=(3, 224, 224))
base_model = vgg...
top_model = load_model(weights_file)

x = base_model(final_input)
result = top_model(x)
final_model = Model(input=final_input, output=result)

【讨论】:

您知道是否可以使用您的 sn-p 处理瓶颈功能?我的意思是,在引用的博客中,VGG16 使用预训练的权重进行初始化,然后用于生成瓶颈特征。然后,使用这些功能创建和训练自定义类的***模型。只有在最后,这两个模型才使用它们的权重重新初始化,连接起来并应用于新数据。我对您的代码也可以这样做吗?我想我在尝试类似于您的建议时遇到了一些麻烦。 瓶颈特征是我在这个 sn-p 中使用的 imagenet 特征。这里有一个代码示例可以满足您的需求:keras.io/applications。另外还有什么错误? 我在问题中添加了几个额外的部分。不确定我想要做的是否与您的建议不同,或者它是否没有太大意义。简而言之,我试图将两个单独的网络连接成一个顺序模型。在您的 sn-p 中,“***模型”(以功能方式创建)尚未训练,对吧? 编辑答案以匹配您的后续问题。【参考方案2】:

我认为您可以通过以下方式连接两者:

#load vgg model
vgg_model = applications.VGG16(weights='imagenet', include_top=False, input_shape=(150, 150, 3))
print('Model loaded.')

#initialise top model
top_model = Sequential()
top_model.add(Flatten(input_shape=vgg_model.output_shape[1:]))
top_model.add(Dense(256, activation='relu'))
top_model.add(Dropout(0.5))
top_model.add(Dense(1, activation='sigmoid'))


top_model.load_weights(top_model_weights_path)

# add the model on top of the convolutional base

model = Model(input= vgg_model.input, output= top_model(vgg_model.output))

此解决方案参考示例Fine-tuning the top layers of a a pre-trained network。完整代码可以在here找到。

【讨论】:

别忘了从 keras.models 导入模型【参考方案3】:

好的,我猜 Thomas 和 Gowtham 发布了正确的(以及更简洁的答案),但我想分享我能够成功运行的代码:

def train_finetuned_model(lr=1e-5, verbose=True):
    file_path = get_file('vgg16.h5', VGG16_WEIGHTS_PATH, cache_subdir='models')
    if verbose:
        print('Building VGG16 (no-top) model to generate bottleneck features.')

    vgg16_notop = build_vgg_16()
    vgg16_notop.load_weights(file_path)
    for _ in range(6):
        vgg16_notop.pop()
    vgg16_notop.compile(optimizer=RMSprop(lr=lr), loss='categorical_crossentropy', metrics=['accuracy'])    

    if verbose:
        print('Bottleneck features generation.')

    train_batches = get_batches('train', shuffle=False, class_mode=None, batch_size=BATCH_SIZE)
    train_labels = np.array([0]*1000 + [1]*1000)
    train_bottleneck = vgg16_notop.predict_generator(train_batches, steps=2000 // BATCH_SIZE)
    valid_batches = get_batches('valid', shuffle=False, class_mode=None, batch_size=BATCH_SIZE)
    valid_labels = np.array([0]*400 + [1]*400)
    valid_bottleneck = vgg16_notop.predict_generator(valid_batches, steps=800 // BATCH_SIZE)

    if verbose:
        print('Training top model on bottleneck features.')

    top_model = Sequential()
    top_model.add(Flatten(input_shape=train_bottleneck.shape[1:]))
    top_model.add(Dense(4096, activation='relu'))
    top_model.add(Dropout(0.5))
    top_model.add(Dense(4096, activation='relu'))
    top_model.add(Dropout(0.5))
    top_model.add(Dense(2, activation='softmax'))
    top_model.compile(optimizer=RMSprop(lr=lr), loss='categorical_crossentropy', metrics=['accuracy'])
    top_model.fit(train_bottleneck, to_categorical(train_labels),
                  batch_size=32, epochs=10,
                  validation_data=(valid_bottleneck, to_categorical(valid_labels)))

    if verbose:
        print('Concatenate new VGG16 (without top layer) with pretrained top model.')

    vgg16_fine = build_vgg_16()
    vgg16_fine.load_weights(file_path)
    for _ in range(6):
        vgg16_fine.pop()
    vgg16_fine.add(Flatten(name='top_flatten'))    
    vgg16_fine.add(Dense(4096, activation='relu'))
    vgg16_fine.add(Dropout(0.5))
    vgg16_fine.add(Dense(4096, activation='relu'))
    vgg16_fine.add(Dropout(0.5))
    vgg16_fine.add(Dense(2, activation='softmax'))
    vgg16_fine.compile(optimizer=RMSprop(lr=lr), loss='categorical_crossentropy', metrics=['accuracy'])

    if verbose:
        print('Loading pre-trained weights into concatenated model')

    for i, layer in enumerate(reversed(top_model.layers), 1):
        pretrained_weights = layer.get_weights()
        vgg16_fine.layers[-i].set_weights(pretrained_weights)

    for layer in vgg16_fine.layers[:26]:
        layer.trainable = False

    if verbose:
        print('Layers training status:')
        for layer in vgg16_fine.layers:
            print('[%6s] %s' % ('' if layer.trainable else 'FROZEN', layer.name))        

    vgg16_fine.compile(optimizer=RMSprop(lr=1e-6), loss='binary_crossentropy', metrics=['accuracy'])

    if verbose:
        print('Train concatenated model on dogs/cats dataset sample.')

    train_datagen = ImageDataGenerator(rescale=1./255,
                                       shear_range=0.2,
                                       zoom_range=0.2,
                                       horizontal_flip=True)
    test_datagen = ImageDataGenerator(rescale=1./255)
    train_batches = get_batches('train', gen=train_datagen, class_mode='categorical', batch_size=BATCH_SIZE)
    valid_batches = get_batches('valid', gen=test_datagen, class_mode='categorical', batch_size=BATCH_SIZE)
    vgg16_fine.fit_generator(train_batches, epochs=100,
                             steps_per_epoch=2000 // BATCH_SIZE,
                             validation_data=valid_batches,
                             validation_steps=800 // BATCH_SIZE)
    return vgg16_fine 

它有点过于冗长,并且所有事情都是手动完成的(即,将权重从预先训练的层复制到连接模型),但它或多或少是有效的。

虽然我发布的这段代码存在准确性低的问题(大约 70%),但那是另一回事。

【讨论】:

微调时你是如何决定解冻多少层的?我对 VGG16 和一般迁移学习有这个问题。 @skeller88 我会说这取决于您的数据集与训练原始模型的数据集有多大不同。如果域相似(例如在这种情况下,猫/狗的照片),您可以微调最顶层的全连接层,可能还有最后几个卷积层。但是,如果您的域与 ImageNet 图像(即 X 射线图像、显微镜、材料等)非常不同,则需要进行更多的微调。可能您需要逐步解冻所有层并使用适当的学习率重新训练它们。

以上是关于Keras VGG16 微调的主要内容,如果未能解决你的问题,请参考以下文章

使用Keras Functional API微调模型

keras-VGG16 猫狗分类器

如何在Keras中使用中级微调?

Keras深度学习实战(10)——迁移学习

微调 keras .h5 模型到 C++ 中使用的 .pb 模型会导致运行模型失败错误

Keras:什么是VGG16中的model.inputs