首页 文章

Keras VGG16微调

提问于
浏览
8

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的回应假设顶级模型没有单独训练并立即附加到模型上 . 不确定我是否清楚,这是博客的引用:

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

3 回答

  • 7

    我认为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)
    

    要结合底部和顶部网络,您可以使用以下代码段 . 使用以下函数(输入层(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)
    
  • 3

    我认为您可以通过执行以下操作来连接两者:

    #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 .

  • 1

    好吧,我想托马斯和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%),但这是一个不同的故事 .

相关问题