-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtrain.py
330 lines (260 loc) · 11.5 KB
/
train.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
import logging
import math
import os
import sys
import time
from datetime import datetime
import matplotlib.pyplot as plt
import numpy as np
from keras import layers
from keras import models
from keras import optimizers
from keras.applications import VGG16
from keras.callbacks import ModelCheckpoint, EarlyStopping
from keras.preprocessing.image import ImageDataGenerator
LOGGER = logging.getLogger(__name__)
# Set paths and parameters
IMAGE_SIZE = 256
BATCH_SIZE = 32
N_EPOCHS = 1
CLASSES = ['house', 'apartment_building-outdoor'] # We could add a third class: 'street' (other).
N_TRAIN_SAMPLES = 10000
N_VALIDATION_SAMPLES = 200
USE_CACHE = True
TRAIN_DIR = "input_files/train_building" # Includes data on each class grouped in a subdirectory.
VALIDATION_DIR = "input_files/val_building" # Link: http://places2.csail.mit.edu/download.html
OUTPUT_DIR = f"output_files/{str(datetime.utcnow())[:16]}/"
os.makedirs(OUTPUT_DIR, exist_ok=True)
TRAIN_BOTTLENECK_FEATURES_PATH = f"input_files/train_bottleneck_features_{CLASSES}.npy"
VAL_BOTTLENECK_FEATURES_PATH = f"input_files/val_bottleneck_features_{CLASSES}.npy"
TOP_MODEL_WEIGHTS_PATH = OUTPUT_DIR + "bottleneck_weights.h5"
FINAL_MODEL_PATH = OUTPUT_DIR + "final_building_model.h5"
MODEL_CHECKPOINT_DIR = OUTPUT_DIR + "model_checkpoints/"
MODEL_CHECKPOINT_PATH = MODEL_CHECKPOINT_DIR + "epoch_{epoch:02d}_val_acc_{val_acc:.2f}.h5"
os.makedirs(MODEL_CHECKPOINT_DIR, exist_ok=True)
def compute_bottleneck_features():
"""
Cache the feature values of the data in the bottleneck layer in order to speed up training.
Returns:
The values of both the train and validation set features in the bottleneck layer.
"""
# Build the VGG16 network.
logging.info("Loading VGG16 model with ImageNet weights.")
model = VGG16(include_top=False, weights='imagenet')
LOGGER.info("Setting up train data generator.")
train_datagen = ImageDataGenerator(rescale=1. / 255)
train_generator = train_datagen.flow_from_directory(
TRAIN_DIR,
target_size=(IMAGE_SIZE, IMAGE_SIZE),
batch_size=BATCH_SIZE,
classes=CLASSES,
class_mode=None,
shuffle=False)
LOGGER.info("Class indices train set: %s", train_generator.class_indices)
LOGGER.info("Getting bottleneck features of the train set.")
t0 = time.perf_counter()
train_bottleneck_features = model.predict_generator(train_generator,
N_TRAIN_SAMPLES // BATCH_SIZE)
LOGGER.info("Train bottleneck features computed in %d seconds.", time.perf_counter() - t0)
LOGGER.info("Setting up validation data generator.")
val_datagen = ImageDataGenerator(rescale=1. / 255)
val_generator = val_datagen.flow_from_directory(
VALIDATION_DIR,
target_size=(IMAGE_SIZE, IMAGE_SIZE),
batch_size=BATCH_SIZE,
classes=CLASSES,
class_mode=None,
shuffle=False)
LOGGER.info("Class indices validation set: %s", val_generator.class_indices)
LOGGER.info("Getting bottleneck features validation set.")
t0 = time.perf_counter()
val_bottleneck_features = model.predict_generator(val_generator,
N_VALIDATION_SAMPLES // BATCH_SIZE)
LOGGER.info("Validation bottleneck features computed in %d seconds.", time.perf_counter() - t0)
return train_bottleneck_features, val_bottleneck_features
def get_bottleneck_features(use_cache=False):
"""
Get bottleneck features from either a cache, or by computing them from scratch.
Args:
use_cache: Boolean that indicates whether or not to use a cache.
Returns:
The values of both the train and validation set features in the bottleneck layer.
"""
if use_cache:
LOGGER.info("Loading cached bottleneck features.")
train_bottleneck_features = np.load(TRAIN_BOTTLENECK_FEATURES_PATH)
val_bottleneck_features = np.load(VAL_BOTTLENECK_FEATURES_PATH)
else:
LOGGER.info("No cache used. Computing bottleneck layers from scratch.")
train_bottleneck_features, val_bottleneck_features = compute_bottleneck_features()
LOGGER.info("Saving training bottleneck features in %s", TRAIN_BOTTLENECK_FEATURES_PATH)
np.save(TRAIN_BOTTLENECK_FEATURES_PATH, train_bottleneck_features)
LOGGER.info("Saving validation bottleneck features in %s", VAL_BOTTLENECK_FEATURES_PATH)
np.save(VAL_BOTTLENECK_FEATURES_PATH, val_bottleneck_features)
return train_bottleneck_features, val_bottleneck_features
def train_top_model(x_train, y_train, x_val, y_val):
"""
Train the top layers of the model using the bottleneck features of the VGG16 model.
Args:
x_train: Training set features.
y_train: Training set labels.
x_val: Validation set features.
y_val: Validation set labels.
Returns:
Trained top model.
"""
# Define model architecture.
model = models.Sequential()
model.add(layers.Flatten(input_shape=x_train.shape[1:]))
model.add(layers.Dense(100, activation='selu'))
model.add(layers.Dropout(0.5))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(optimizer=optimizers.Adam(),
loss='binary_crossentropy',
metrics=['accuracy'])
# Train the model using early stopping.
early_stopping = EarlyStopping(monitor='val_loss', verbose=1,
restore_best_weights=True, patience=20)
callbacks_list = [early_stopping]
LOGGER.info("Fitting top model.")
model.fit(x_train, y_train,
epochs=N_EPOCHS,
batch_size=BATCH_SIZE,
validation_data=(x_val, y_val),
callbacks=callbacks_list)
LOGGER.info("Saving top model weights.")
return model
def build_full_model():
"""
Build the full model using trained top model layers.
Returns:
Full model with a VGG16 convolutional base and a trained top model.
"""
# Build the VGG16 network.
model = models.Sequential()
model.add(VGG16(weights='imagenet', include_top=False,
input_shape=(IMAGE_SIZE, IMAGE_SIZE, 3)))
# Build the model to put on top of the VGG16 model.
top_model = models.Sequential()
top_model.add(layers.Flatten(input_shape=model.output_shape[1:]))
top_model.add(layers.Dense(100, activation='selu'))
top_model.add(layers.Dropout(0.5)) # TODO: Try training a model without or with a higher rate.
top_model.add(layers.Dense(1, activation='sigmoid'))
# Note that it is necessary to start with a fully-trained classifier, including the top
# classifier, in order to successfully do fine-tuning.
top_model.load_weights(TOP_MODEL_WEIGHTS_PATH)
# Add the model on top of the convolutional base.
model.add(top_model)
# Show a summary of the model
LOGGER.info(model.summary())
return model
def get_data_generators():
"""
Create data generators for the training set and validation set.
Returns:
Training and validation set data generators.
"""
train_datagen = ImageDataGenerator(
rescale=1. / 255,
rotation_range=20,
width_shift_range=0.2,
height_shift_range=0.2,
horizontal_flip=True)
train_generator = train_datagen.flow_from_directory(
TRAIN_DIR,
target_size=(IMAGE_SIZE, IMAGE_SIZE),
batch_size=BATCH_SIZE,
classes=CLASSES,
class_mode='binary')
validation_datagen = ImageDataGenerator(rescale=1. / 255)
validation_generator = validation_datagen.flow_from_directory(
VALIDATION_DIR,
target_size=(IMAGE_SIZE, IMAGE_SIZE),
batch_size=BATCH_SIZE,
classes=CLASSES,
class_mode='binary',
shuffle=False)
return train_generator, validation_generator
def train_full_model(model, train_generator, validation_generator):
"""
Train the full model and save the model object.
Args:
model: Full model that will be trained.
train_generator: Data generator for the training set.
validation_generator: Data generator for the validation set.
Returns:
History of the training process.
"""
# Define callbacks and compile the model.
early_stopping = EarlyStopping(monitor='val_loss',
verbose=1,
restore_best_weights=True,
patience=20)
model_checkpoint = ModelCheckpoint(MODEL_CHECKPOINT_PATH,
monitor='val_loss',
mode='min',
verbose=1)
callbacks_list = [early_stopping, model_checkpoint]
model.compile(loss='binary_crossentropy',
optimizer=optimizers.SGD(lr=1e-4, momentum=0.9),
metrics=['accuracy'])
# Fine-tune the model.
history = model.fit_generator(
train_generator,
steps_per_epoch=math.ceil(train_generator.samples / train_generator.batch_size),
epochs=N_EPOCHS,
validation_data=validation_generator,
validation_steps=math.ceil(validation_generator.samples / validation_generator.batch_size),
callbacks=callbacks_list,
verbose=1)
# Save the model
model.save(FINAL_MODEL_PATH)
return history
def plot_train_process(history):
"""
Plot the training process.
Args:
history: History of the training process.
"""
# Plot training results
accuracy = history.history['acc']
LOGGER.info("Model accuracy: %f", accuracy)
val_accuracy = history.history['val_acc']
LOGGER.info("Model validation accuracy: %f", val_accuracy)
loss = history.history['loss']
val_loss = history.history['val_loss']
plt.plot(N_EPOCHS, accuracy, 'b', label='Training accuracy')
plt.plot(N_EPOCHS, val_accuracy, 'r', label='Validation accuracy')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
plt.plot(N_EPOCHS, loss, 'b', label='Training loss')
plt.plot(N_EPOCHS, val_loss, 'r', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
def main():
# Cache the feature values of the bottleneck layer.
train_bottleneck_features, val_bottleneck_features = get_bottleneck_features(
use_cache=USE_CACHE)
# Define the labels of the train and validation set, which are just zeroes for the first class
# (house) and ones for the second class (apartment building).
y_train = np.array([0] * int(N_TRAIN_SAMPLES / 2) + [1] * int(N_TRAIN_SAMPLES / 2))
y_val = np.array([0] * int(N_VALIDATION_SAMPLES / 2) + [1] * int(N_VALIDATION_SAMPLES / 2))
# Train top model using bottleneck features as input and store weights.
top_model = train_top_model(train_bottleneck_features, y_train, val_bottleneck_features, y_val)
top_model.save_weights(TOP_MODEL_WEIGHTS_PATH)
# Build the full model.
model = build_full_model()
# Freeze the first 25 layers (up until the last convolutional block).
for layer in model.layers[:25]:
layer.trainable = False
# Get data generators, train the full model and visualize results.
train_generator, validation_generator = get_data_generators()
history = train_full_model(model, train_generator, validation_generator)
plot_train_process(history)
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO, stream=sys.stdout,
format='%(asctime)s %(name)-4s: %(module)-4s :%(levelname)-8s %(message)s',
datefmt='%Y-%m-%d %H:%M:%S')
main()