In [ ]:
# Copyright 2019 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

Idiomatic Programmer Code Labs

Code Labs #3 - Get Familiar with Data Curation

Prerequistes:

1. Familiar with Python
2. Completed Handbook 2/Part 9: Data Curation

Objectives:

1. Preprocessing a builtin dataset (cifar-10)
2. Train dataset for a few epochs
3. Use same model architecture for larger number of classes (cifar-100)
4. Use image augmentation

Setup

Let's import the builtin datasets for:

CIFAR-10: 32x32 images, 10 classes, 60000 images (6000 per class)
CIFAR-100: 32x32 images, 100 classes, 60000 images (600 per class)

In [ ]:
from keras.datasets import cifar10, cifar100
from keras.utils import to_categorical

Model Architecture

Let's start with a function that will create our model using a simple CNN architecture, as follows:

Stem Group

We use the current convention of replacing a single 5x5 convolution with two 3x3 convolutions and delay downsampling to later in the network (i.e., strides=1).

Convolutional Blocks

We will use a VGG style of using sequential convolutional layers in groups, where each group doubles the number of filters, and downsample the feature maps at the end of the group using max pooling. We deviate from VGG, by using a current convention of factorizing pairs of 3x3 filters with a 3x3 and a 1x1 (bottleneck) filter.

Classifier

We will add a hidden dense layer to the classifier, and then follow it by a current convention of using a global averaging instead of flatten --this will reduce the number of parameters, where it's the current convention if the feature maps are 4x4 or greater.

You fill in the blanks (replace the ??), make sure it passes the Python interpreter.

You will need to:

1. Set the loss function for compiling the model.

In [ ]:
from keras import Input, Model
from keras.layers import Conv2D, MaxPooling2D, Dropout, BatchNormalization, GlobalAveragePooling2D, Dense

def convNet(input_shape, nclasses):
    def stem(inputs, nb_filters):
        ''' Stem Convolutional Group '''

        # Use two 3x3 convolutional layers (no downsampling, strides=1)
        x = Conv2D(nb_filters, (3, 3), strides=1, padding='same', activation='relu')(inputs)
        x = Conv2D(nb_filters, (3, 3), strides=1, padding='same', activation='relu')(x)

        # Downsample with Max Pooling
        x = MaxPooling2D(pool_size=(2, 2), strides=2, padding='same')(x)
        x = Dropout(0.25)(x)
        return x

    def conv_block(x, nb_filters):
        ''' Convolutional Block '''

        # A 3x3 and 1x1 factorization of two 3x3 convolutional layers
        x = Conv2D(nb_filters, (3, 3), strides=1, padding='same', activation='relu')(x)
        x = Conv2D(nb_filters, (1, 1), strides=1, padding='same', activation='relu')(x)

        # Downsample with Max Pooling
        x = MaxPooling2D(pool_size=(2, 2), strides=2, padding='same')(x)
        return x
    
    def classifier(x, nclasses):
        ''' Classifier '''
        x = GlobalAveragePooling2D()(x)
        x = Dense(128, activation='relu')(x)
        x = Dense(nclasses, activation='softmax')(x)
        return x


    # Input and Stem Group
    inputs = Input(input_shape)
    x = stem(inputs, 32)

    # Two Convolutional Blocks, each doubles the number of filters
    for nb_filters in [64, 128]:
        x = conv_block(x, nb_filters)

    outputs = classifier(x, nclasses)

    model = Model(inputs, outputs)

    # Let's compile the model. We will use the defacto optimizer for optimization (adam)
    # HINT: For the loss function, think categorical (multi-class) and cross entropy.
    model.compile(loss='??', optimizer='adam', metrics=['accuracy'])
    return model

CIFAR-10

Let's get the Keras builtin dataset for CIFAR-10.The image data from the load_data() is not preprocessed. That is, the labels are not converted to one-hot encoded and the image data is still in it's original 8-bit integer values (0..255).

You will need to:

1. Specify the value to divide the pixel data by.

In [ ]:
import numpy as np

# Get the builtin CIFAR-10 dataset
(x_train, y_train), (x_test, y_test) = cifar10.load_data()

# One-hot encode the labels
y_train = to_categorical(y_train)
y_test  = to_categorical(y_test)

# Normalize the pixel data between 0 and 1
# HINT: You are squashing the values from 0 and 255, what do you think you need to divide by?
x_train = (x_train / ??).astype(np.float32)
x_test  = (x_test  / ??).astype(np.float32)

Build the Model

Let's build the model for training on the CIFAR-10:

- Input is 32x32x3 (height x width x channels)
- 10 classes

The end of the summary output should be:

Total params: 140,970
Trainable params: 140,970
Non-trainable params: 0

In [ ]:
model = convNet((32, 32, 3), 10)
model.summary()

Train the Model for CIFAR-10

We will train the model for just a few epochs (3). Don't concern yourself about the accuracy, since we are just using a few epochs for brevity. You should get ~60% accuracy on the test data.


In [ ]:
# Train the model using an in-memory feeding
model.fit(x_train, y_train, batch_size=32, epochs=3, validation_split=0.1, verbose=1)
score = model.evaluate(x_test, y_test)
print(score)

CIFAR-100

Let's get the Keras builtin dataset for CIFAR-100. This is a more challenging dataset in that it has 100 (instead of 10) classes, and fewer images per class (600 vs. 6000).

In the CIFAR-10, we used normalization on the pixel data. This time, we will use standardization.

You will need to:

1. Substract the mean from the train and test data.

In [ ]:
# Get the CIFAR-100 builtin dataset
(x_train, y_train), (x_test, y_test) = cifar100.load_data()

# One-hot encode the labels
y_train = to_categorical(y_train)
y_test  = to_categorical(y_test)

# Standardize the pixel data (substract mean and divide by standard deviation of 1)
# HINT: We are substracting the mean!
mean = np.mean(x_train)
std  = np.std(x_train)
x_train = ((x_train - ??) / std).astype(np.float32)
x_test  = ((x_test  - ??) / std).astype(np.float32)

Build the Model

Let's build the model for training on the CIFAR-100:

- Input is 32x32x3 (height x width x channels)
- 100 classes

The end of the summary output should be:

Total params: 152,580
Trainable params: 152,580
Non-trainable params: 0

In [ ]:
model = convNet((32, 32, 3), 100)
model.summary()

Train the Model for CIFAR-100

We will train the model for just a few epochs (3). Don't concern yourself about the accuracy, since we are just using a few epochs for brevity. You should get ~24% accuracy on the test data.


In [ ]:
model.fit(x_train, y_train, batch_size=32, epochs=3, validation_split=0.1, verbose=1)
score = model.evaluate(x_test, y_test)
print(score)

Image Augmentation with ImageDataGenerator

Let's now use Keras ImageDataGenerator to add some image augmentation. We will do the following:

1. Vertical and Horizontal flips.
2. Random Rotation +/- 30 degrees.

Note, accuracy will be low (brevity) --to demonstrate using ImageDataGenerator.

You will need to:

1. Reset the training data to the subset not including the validation data.

In [ ]:
from keras.preprocessing.image import ImageDataGenerator

# Create the generator for doing Image Augmentation
datagen = ImageDataGenerator(vertical_flip=True, horizontal_flip=True, rotation_range=30)

# For validation split, calculate the pivot point first. -i.e, where one part is training
# and one part is validation
pivot = int(len(x_train) * 0.9)

# So for validation, it's all the data after the pivot point
x_val = x_train[pivot:]
y_val = y_train[pivot:]

# For the remainig (before pivot point) it's the training
# HINT: it's everything before the pivot point
x_train = x_train[??]
y_train = y_train[??]

model = convNet((32, 32, 3), 100)
model.fit_generator(datagen.flow(x_train, y_train, batch_size=32), epochs=3, steps_per_epoch=len(x_train)/32, 
                    validation_data=(x_val, y_val), verbose=1)
score = model.evaluate(x_test, y_test)
print(score)

End of Code Lab