这是俄罗斯高等经济学院的系列课程第一门,Introduction to Advanced Machine Learning,第三周编程作业。任务是利用pre-trained InceptionV3架构,使用花朵分类训练集,经过fine tune之后,能够用于花朵的识别。
这个作业一共两个部分,难易程度:容易。
1. prepared images for the model,图片剪裁
2. implemented your own batch generator,分批
3. fine-tuned the pre-trained model,训练
In this task you will fine-tune InceptionV3 architecture for flowers classification task.
InceptionV3 architecture (https://research.googleblog.com/2016/03/train-your-own-image-classifier-with.html):
Flowers classification dataset (http://www.robots.ox.ac.uk/~vgg/data/flowers/102/index.html) consists of 102 flower categories commonly occurring in the United Kingdom. Each class contains between 40 and 258 images:
import sys
sys.path.append("..")
import grading
import download_utils
# !!! remember to clear session/graph if you rebuild your graph to avoid out-of-memory errors !!!
def reset_tf_session():
K.clear_session()
tf.reset_default_graph()
s = K.get_session()
return s
download_utils.link_all_keras_resources()
import tensorflow as tf
import keras
from keras import backend as K
import numpy as np
%matplotlib inline
import matplotlib.pyplot as plt
print(tf.__version__)
print(keras.__version__)
import cv2 # for image processing
from sklearn.model_selection import train_test_split
import scipy.io
import os
import tarfile
import tqdm
import keras_utils
Using TensorFlow backend.
1.2.1
2.0.6
To successfully submit your answers to our grader, please fill in your Coursera submission token and email
grader = grading.Grader(assignment_key="2v-uxpD7EeeMxQ6FWsz5LA",
all_parts=["wuwwC", "a4FK1", "qRsZ1"])
# token expires every 30 min
COURSERA_TOKEN = '124YSWW2CF2jlAdd'
COURSERA_EMAIL = '[email protected]'
Dataset was downloaded for you, it takes 12 min and 400mb.
Relevant links (just in case):
- http://www.robots.ox.ac.uk/~vgg/data/flowers/102/index.html
- http://www.robots.ox.ac.uk/~vgg/data/flowers/102/102flowers.tgz
- http://www.robots.ox.ac.uk/~vgg/data/flowers/102/imagelabels.mat
# we downloaded them for you, just link them here
download_utils.link_week_3_resources()
# we will crop and resize input images to IMG_SIZE x IMG_SIZE
IMG_SIZE = 250
def decode_image_from_raw_bytes(raw_bytes):
img = cv2.imdecode(np.asarray(bytearray(raw_bytes), dtype=np.uint8), 1)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
return img
We will take a center crop from each image like this:
def image_center_crop(img):
"""
Makes a square center crop of an img, which is a [h, w, 3] numpy array.
Returns [min(h, w), min(h, w), 3] output with same width and height.
For cropping use numpy slicing.
"""
h = img.shape[0]
w = img.shape[1]
length = min(h,w)
h_start = h//2 - length//2
h_end = h_start + length
w_start = w//2 - length//2
w_end = w_start + length
cropped_img = img[h_start:h_end, w_start:w_end, ]### YOUR CODE HERE
return cropped_img
def prepare_raw_bytes_for_model(raw_bytes, normalize_for_model=True):
img = decode_image_from_raw_bytes(raw_bytes) # decode image raw bytes to matrix
img = image_center_crop(img) # take squared center crop
img = cv2.resize(img, (IMG_SIZE, IMG_SIZE)) # resize for our model
if normalize_for_model:
img = img.astype("float32") # prepare for normalization
img = keras.applications.inception_v3.preprocess_input(img) # normalize for model
return img
# reads bytes directly from tar by filename (slow, but ok for testing, takes ~6 sec)
def read_raw_from_tar(tar_fn, fn):
with tarfile.open(tar_fn) as f:
m = f.getmember(fn)
return f.extractfile(m).read()
# test cropping
raw_bytes = read_raw_from_tar("102flowers.tgz", "jpg/image_00001.jpg")
img = decode_image_from_raw_bytes(raw_bytes)
print(img.shape)
plt.imshow(img)
plt.show()
img = prepare_raw_bytes_for_model(raw_bytes, normalize_for_model=False)
print(img.shape)
plt.imshow(img)
plt.show()
(500, 591, 3)
(250, 250, 3)
## GRADED PART, DO NOT CHANGE!
# Test image preparation for model
prepared_img = prepare_raw_bytes_for_model(read_raw_from_tar("102flowers.tgz", "jpg/image_00001.jpg"))
grader.set_answer("qRsZ1", list(prepared_img.shape) + [np.mean(prepared_img), np.std(prepared_img)])
# you can make submission with answers so far to check yourself at this stage
grader.submit(COURSERA_EMAIL, COURSERA_TOKEN)
Submitted to Coursera platform. See results on assignment page!
# read all filenames and labels for them
# read filenames firectly from tar
def get_all_filenames(tar_fn):
with tarfile.open(tar_fn) as f:
return [m.name for m in f.getmembers() if m.isfile()]
all_files = sorted(get_all_filenames("102flowers.tgz")) # list all files in tar sorted by name
all_labels = scipy.io.loadmat('imagelabels.mat')['labels'][0] - 1 # read class labels (0, 1, 2, ...)
# all_files and all_labels are aligned now
N_CLASSES = len(np.unique(all_labels))
print(N_CLASSES)
102
# split into train/test
tr_files, te_files, tr_labels, te_labels = \
train_test_split(all_files, all_labels, test_size=0.2, random_state=42, stratify=all_labels)
# will yield raw image bytes from tar with corresponding label
def raw_generator_with_label_from_tar(tar_fn, files, labels):
label_by_fn = dict(zip(files, labels))
with tarfile.open(tar_fn) as f:
while True:
m = f.next()
if m is None:
break
if m.name in label_by_fn:
yield f.extractfile(m).read(), label_by_fn[m.name]
# batch generator
BATCH_SIZE = 32
def batch_generator(items, batch_size):
"""
Implement batch generator that yields items in batches of size batch_size.
There's no need to shuffle input items, just chop them into batches.
Remember about the last batch that can be smaller than batch_size!
Input: any iterable (list, generator, ...). You should do `for item in items: ...`
In case of generator you can pass through your items only once!
Output: In output yield each batch as a list of items.
"""
return_batch = []
cnt = 0
for item in items:
if cnt != batch_size:
return_batch.append(item)
else:
yield return_batch
print(return_batch)
return_batch = []
return_batch.append(item)
cnt = cnt%batch_size + 1
if cnt != 0:
yield return_batch
print(return_batch)
### YOUR CODE HERE
## GRADED PART, DO NOT CHANGE!
# Test batch generator
def _test_items_generator():
for i in range(10):
yield i
grader.set_answer("a4FK1", list(map(lambda x: len(x), batch_generator(_test_items_generator(), 3))))
[0, 1, 2]
[3, 4, 5]
[6, 7, 8]
[9]
# you can make submission with answers so far to check yourself at this stage
grader.submit('[email protected]', 'WOj4hMjSwhrVRiP7')
Submitted to Coursera platform. See results on assignment page!
def train_generator(files, labels):
while True: # so that Keras can loop through this as long as it wants
for batch in batch_generator(raw_generator_with_label_from_tar(
"102flowers.tgz", files, labels), BATCH_SIZE):
# prepare batch images
batch_imgs = []
batch_targets = []
for raw, label in batch:
img = prepare_raw_bytes_for_model(raw)
batch_imgs.append(img)
batch_targets.append(label)
# stack images into 4D tensor [batch_size, img_size, img_size, 3]
batch_imgs = np.stack(batch_imgs, axis=0)
# convert targets into 2D tensor [batch_size, num_classes]
batch_targets = keras.utils.np_utils.to_categorical(batch_targets, N_CLASSES)
yield batch_imgs, batch_targets
# test training generator
for _ in train_generator(tr_files, tr_labels):
print(_[0].shape, _[1].shape)
plt.imshow(np.clip(_[0][0] / 2. + 0.5, 0, 1))
break
(32, 250, 250, 3) (32, 102)
You cannot train such a huge architecture from scratch with such a small dataset.
But using fine-tuning of last layers of pre-trained network you can get a pretty good classifier very quickly.
# remember to clear session if you start building graph from scratch!
s = reset_tf_session()
# don't call K.set_learning_phase() !!! (otherwise will enable dropout in train/test simultaneously)
def inception(use_imagenet=True):
# load pre-trained model graph, don't add final layer
model = keras.applications.InceptionV3(include_top=False, input_shape=(IMG_SIZE, IMG_SIZE, 3),
weights='imagenet' if use_imagenet else None)
# add global pooling just like in InceptionV3
new_output = keras.layers.GlobalAveragePooling2D()(model.output)
# add new dense layer for our labels
new_output = keras.layers.Dense(N_CLASSES, activation='softmax')(new_output)
model = keras.engine.training.Model(model.inputs, new_output)
return model
model = inception()
model.summary()
# how many layers our model has
print(len(model.layers))
313
# set all layers trainable by default
for layer in model.layers:
layer.trainable = True
# fix deep layers (fine-tuning only last 50)
for layer in model.layers[:-50]:
layer.trainable = False
# compile new model
model.compile(
loss='categorical_crossentropy', # we train 102-way classification
optimizer=keras.optimizers.adamax(lr=1e-2), # we can take big lr here because we fixed first layers
metrics=['accuracy'] # report accuracy during training
)
# we will save model checkpoints to continue training in case of kernel death
model_filename = 'flowers.{0:03d}.hdf5'
last_finished_epoch = None
#### uncomment below to continue training from model checkpoint
#### fill `last_finished_epoch` with your latest finished epoch
from keras.models import load_model
s = reset_tf_session()
last_finished_epoch = 15
model = load_model(model_filename.format(last_finished_epoch))
#print(model_filename)
#print(format(last_finished_epoch))
Training takes 2 hours. You’re aiming for ~0.93 validation accuracy.
# fine tune for 2 epochs (full passes through all training data)
# we make 2*8 epochs, where epoch is 1/8 of our training data to see progress more often
model.fit_generator(
train_generator(tr_files, tr_labels),
steps_per_epoch=len(tr_files) // BATCH_SIZE // 8,
epochs=2 * 8,
validation_data=train_generator(te_files, te_labels),
validation_steps=len(te_files) // BATCH_SIZE // 4,
callbacks=[keras_utils.TqdmProgressCallback(),
keras_utils.ModelSaveCallback(model_filename)],
verbose=0,
initial_epoch=last_finished_epoch or 0
)
Epoch 16/16
## GRADED PART, DO NOT CHANGE!
# Accuracy on validation set
test_accuracy = model.evaluate_generator(
train_generator(te_files, te_labels),
len(te_files) // BATCH_SIZE // 2
)[1]
grader.set_answer("wuwwC", test_accuracy)
print(test_accuracy)
0.93
That’s it! Congratulations!
What you’ve done:
- prepared images for the model
- implemented your own batch generator
- fine-tuned the pre-trained model