keras 博客上有一个 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.0
后端theano
。
注意:我使用的是 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 的响应假设顶级模型没有单独训练并立即附加到模型。不确定我是否清楚,这是博客的引述:
为了进行微调,所有层都应该从经过适当训练的权重开始:例如,您不应该在预训练的卷积基础上随机初始化全连接网络。这是因为由随机初始化的权重触发的大梯度更新会破坏卷积基中的学习权重。在我们的例子中,这就是为什么我们首先训练顶级分类器,然后才开始微调卷积权重。