|
| 1 | +# -*- coding: utf-8 -*- |
| 2 | + |
| 3 | +from keras.optimizers import SGD |
| 4 | +from keras.layers import Input, Dense, Conv2D, MaxPooling2D, AveragePooling2D, ZeroPadding2D, Flatten, merge, Activation |
| 5 | +from keras.layers.normalization import BatchNormalization |
| 6 | +from keras.models import Model |
| 7 | +from keras import backend as K |
| 8 | + |
| 9 | +from custom_layers.scale_layer import Scale |
| 10 | + |
| 11 | +from sklearn.metrics import log_loss |
| 12 | + |
| 13 | +import sys |
| 14 | + |
| 15 | +sys.setrecursionlimit(3000) |
| 16 | + |
| 17 | + |
| 18 | +def identity_block(input_tensor, kernel_size, filters, stage, block): |
| 19 | + '''The identity_block is the block that has no conv layer at shortcut |
| 20 | + # Arguments |
| 21 | + input_tensor: input tensor |
| 22 | + kernel_size: defualt 3, the kernel size of middle conv layer at main path |
| 23 | + filters: list of integers, the nb_filters of 3 conv layer at main path |
| 24 | + stage: integer, current stage label, used for generating layer names |
| 25 | + block: 'a','b'..., current block label, used for generating layer names |
| 26 | + ''' |
| 27 | + eps = 1.1e-5 |
| 28 | + nb_filter1, nb_filter2, nb_filter3 = filters |
| 29 | + conv_name_base = 'res' + str(stage) + block + '_branch' |
| 30 | + bn_name_base = 'bn' + str(stage) + block + '_branch' |
| 31 | + scale_name_base = 'scale' + str(stage) + block + '_branch' |
| 32 | + |
| 33 | + x = Conv2D(nb_filter1, (1, 1), name=conv_name_base + '2a', use_bias=False)(input_tensor) |
| 34 | + x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2a')(x) |
| 35 | + x = Scale(axis=bn_axis, name=scale_name_base + '2a')(x) |
| 36 | + x = Activation('relu', name=conv_name_base + '2a_relu')(x) |
| 37 | + |
| 38 | + x = ZeroPadding2D((1, 1), name=conv_name_base + '2b_zeropadding')(x) |
| 39 | + x = Conv2D(nb_filter2, (kernel_size, kernel_size), |
| 40 | + name=conv_name_base + '2b', use_bias=False)(x) |
| 41 | + x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2b')(x) |
| 42 | + x = Scale(axis=bn_axis, name=scale_name_base + '2b')(x) |
| 43 | + x = Activation('relu', name=conv_name_base + '2b_relu')(x) |
| 44 | + |
| 45 | + x = Conv2D(nb_filter3, (1, 1), name=conv_name_base + '2c', use_bias=False)(x) |
| 46 | + x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2c')(x) |
| 47 | + x = Scale(axis=bn_axis, name=scale_name_base + '2c')(x) |
| 48 | + |
| 49 | + x = merge([x, input_tensor], mode='sum', name='res' + str(stage) + block) |
| 50 | + x = Activation('relu', name='res' + str(stage) + block + '_relu')(x) |
| 51 | + return x |
| 52 | + |
| 53 | + |
| 54 | +def conv_block(input_tensor, kernel_size, filters, stage, block, strides=(2, 2)): |
| 55 | + '''conv_block is the block that has a conv layer at shortcut |
| 56 | + # Arguments |
| 57 | + input_tensor: input tensor |
| 58 | + kernel_size: defualt 3, the kernel size of middle conv layer at main path |
| 59 | + filters: list of integers, the nb_filters of 3 conv layer at main path |
| 60 | + stage: integer, current stage label, used for generating layer names |
| 61 | + block: 'a','b'..., current block label, used for generating layer names |
| 62 | + Note that from stage 3, the first conv layer at main path is with subsample=(2,2) |
| 63 | + And the shortcut should have subsample=(2,2) as well |
| 64 | + ''' |
| 65 | + eps = 1.1e-5 |
| 66 | + nb_filter1, nb_filter2, nb_filter3 = filters |
| 67 | + conv_name_base = 'res' + str(stage) + block + '_branch' |
| 68 | + bn_name_base = 'bn' + str(stage) + block + '_branch' |
| 69 | + scale_name_base = 'scale' + str(stage) + block + '_branch' |
| 70 | + |
| 71 | + x = Conv2D(nb_filter1, (1, 1), strides=strides, |
| 72 | + name=conv_name_base + '2a', use_bias=False)(input_tensor) |
| 73 | + x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2a')(x) |
| 74 | + x = Scale(axis=bn_axis, name=scale_name_base + '2a')(x) |
| 75 | + x = Activation('relu', name=conv_name_base + '2a_relu')(x) |
| 76 | + |
| 77 | + x = ZeroPadding2D((1, 1), name=conv_name_base + '2b_zeropadding')(x) |
| 78 | + x = Conv2D(nb_filter2, (kernel_size, kernel_size), |
| 79 | + name=conv_name_base + '2b', use_bias=False)(x) |
| 80 | + x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2b')(x) |
| 81 | + x = Scale(axis=bn_axis, name=scale_name_base + '2b')(x) |
| 82 | + x = Activation('relu', name=conv_name_base + '2b_relu')(x) |
| 83 | + |
| 84 | + x = Conv2D(nb_filter3, (1, 1), name=conv_name_base + '2c', use_bias=False)(x) |
| 85 | + x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2c')(x) |
| 86 | + x = Scale(axis=bn_axis, name=scale_name_base + '2c')(x) |
| 87 | + |
| 88 | + shortcut = Conv2D(nb_filter3, (1, 1), strides=strides, |
| 89 | + name=conv_name_base + '1', use_bias=False)(input_tensor) |
| 90 | + shortcut = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '1')(shortcut) |
| 91 | + shortcut = Scale(axis=bn_axis, name=scale_name_base + '1')(shortcut) |
| 92 | + |
| 93 | + x = merge([x, shortcut], mode='sum', name='res' + str(stage) + block) |
| 94 | + x = Activation('relu', name='res' + str(stage) + block + '_relu')(x) |
| 95 | + return x |
| 96 | + |
| 97 | + |
| 98 | +def resnet152_model(img_rows, img_cols, color_type=1, num_classes=None): |
| 99 | + """ |
| 100 | + Resnet 152 Model for Keras |
| 101 | +
|
| 102 | + Model Schema and layer naming follow that of the original Caffe implementation |
| 103 | + https://github.com/KaimingHe/deep-residual-networks |
| 104 | +
|
| 105 | + ImageNet Pretrained Weights |
| 106 | + Theano: https://drive.google.com/file/d/0Byy2AcGyEVxfZHhUT3lWVWxRN28/view?usp=sharing |
| 107 | + TensorFlow: https://drive.google.com/file/d/0Byy2AcGyEVxfeXExMzNNOHpEODg/view?usp=sharing |
| 108 | +
|
| 109 | + Parameters: |
| 110 | + img_rows, img_cols - resolution of inputs |
| 111 | + channel - 1 for grayscale, 3 for color |
| 112 | + num_classes - number of class labels for our classification task |
| 113 | + """ |
| 114 | + eps = 1.1e-5 |
| 115 | + |
| 116 | + # Handle Dimension Ordering for different backends |
| 117 | + global bn_axis |
| 118 | + if K.image_dim_ordering() == 'tf': |
| 119 | + bn_axis = 3 |
| 120 | + img_input = Input(shape=(img_rows, img_cols, color_type), name='data') |
| 121 | + else: |
| 122 | + bn_axis = 1 |
| 123 | + img_input = Input(shape=(color_type, img_rows, img_cols), name='data') |
| 124 | + |
| 125 | + x = ZeroPadding2D((3, 3), name='conv1_zeropadding')(img_input) |
| 126 | + x = Conv2D(64, (7, 7), strides=(2, 2), name='conv1', use_bias=False)(x) |
| 127 | + x = BatchNormalization(epsilon=eps, axis=bn_axis, name='bn_conv1')(x) |
| 128 | + x = Scale(axis=bn_axis, name='scale_conv1')(x) |
| 129 | + x = Activation('relu', name='conv1_relu')(x) |
| 130 | + x = MaxPooling2D((3, 3), strides=(2, 2), name='pool1')(x) |
| 131 | + |
| 132 | + x = conv_block(x, 3, [64, 64, 256], stage=2, block='a', strides=(1, 1)) |
| 133 | + x = identity_block(x, 3, [64, 64, 256], stage=2, block='b') |
| 134 | + x = identity_block(x, 3, [64, 64, 256], stage=2, block='c') |
| 135 | + |
| 136 | + x = conv_block(x, 3, [128, 128, 512], stage=3, block='a') |
| 137 | + for i in range(1, 8): |
| 138 | + x = identity_block(x, 3, [128, 128, 512], stage=3, block='b' + str(i)) |
| 139 | + |
| 140 | + x = conv_block(x, 3, [256, 256, 1024], stage=4, block='a') |
| 141 | + for i in range(1, 36): |
| 142 | + x = identity_block(x, 3, [256, 256, 1024], stage=4, block='b' + str(i)) |
| 143 | + |
| 144 | + x = conv_block(x, 3, [512, 512, 2048], stage=5, block='a') |
| 145 | + x = identity_block(x, 3, [512, 512, 2048], stage=5, block='b') |
| 146 | + x = identity_block(x, 3, [512, 512, 2048], stage=5, block='c') |
| 147 | + |
| 148 | + x_fc = AveragePooling2D((7, 7), name='avg_pool')(x) |
| 149 | + x_fc = Flatten()(x_fc) |
| 150 | + x_fc = Dense(1000, activation='softmax', name='fc1000')(x_fc) |
| 151 | + |
| 152 | + model = Model(img_input, x_fc) |
| 153 | + |
| 154 | + if K.image_dim_ordering() == 'th': |
| 155 | + # Use pre-trained weights for Theano backend |
| 156 | + weights_path = 'imagenet_models/resnet152_weights_th.h5' |
| 157 | + else: |
| 158 | + # Use pre-trained weights for Tensorflow backend |
| 159 | + weights_path = 'imagenet_models/resnet152_weights_tf.h5' |
| 160 | + |
| 161 | + model.load_weights(weights_path, by_name=True) |
| 162 | + |
| 163 | + # Truncate and replace softmax layer for transfer learning |
| 164 | + # Cannot use model.layers.pop() since model is not of Sequential() type |
| 165 | + # The method below works since pre-trained weights are stored in layers but not in the model |
| 166 | + x_newfc = AveragePooling2D((7, 7), name='avg_pool')(x) |
| 167 | + x_newfc = Flatten()(x_newfc) |
| 168 | + x_newfc = Dense(num_classes, activation='softmax', name='fc8')(x_newfc) |
| 169 | + |
| 170 | + model = Model(img_input, x_newfc) |
| 171 | + |
| 172 | + # Learning rate is changed to 0.001 |
| 173 | + sgd = SGD(lr=1e-3, decay=1e-6, momentum=0.9, nesterov=True) |
| 174 | + model.compile(optimizer=sgd, loss='categorical_crossentropy', metrics=['accuracy']) |
| 175 | + |
| 176 | + return model |
| 177 | + |
| 178 | + |
| 179 | +if __name__ == '__main__': |
| 180 | + # Example to fine-tune on 3000 samples from Cifar10 |
| 181 | + |
| 182 | + img_rows, img_cols = 224, 224 # Resolution of inputs |
| 183 | + channel = 3 |
| 184 | + num_classes = 10 |
| 185 | + batch_size = 8 |
| 186 | + epochs = 10 |
| 187 | + |
| 188 | + # Load Cifar10 data. Please implement your own load_data() module for your own dataset |
| 189 | + X_train, Y_train, X_valid, Y_valid = load_cifar10_data(img_rows, img_cols) |
| 190 | + |
| 191 | + # Load our model |
| 192 | + model = resnet152_model(img_rows, img_cols, channel, num_classes) |
| 193 | + |
| 194 | + # Start Fine-tuning |
| 195 | + model.fit(X_train, Y_train, |
| 196 | + batch_size=batch_size, |
| 197 | + epochs=epochs, |
| 198 | + shuffle=True, |
| 199 | + verbose=1, |
| 200 | + validation_data=(X_valid, Y_valid), |
| 201 | + ) |
| 202 | + |
| 203 | + # Make predictions |
| 204 | + predictions_valid = model.predict(X_valid, batch_size=batch_size, verbose=1) |
| 205 | + |
| 206 | + # Cross-entropy loss score |
| 207 | + score = log_loss(Y_valid, predictions_valid) |
0 commit comments