text: # -*- coding: utf-8 -*-
  
  from keras.models import Sequential
  from keras.optimizers import SGD
  from keras.layers import Input, Dense, Convolution2D, MaxPooling2D, AveragePooling2D, ZeroPadding2D, Dropout, Flatten, merge, Reshape, Activation
  from keras.layers.normalization import BatchNormalization
  from keras.models import Model
  from keras import backend as K
  
  from sklearn.metrics import log_loss
  
  from custom_layers.scale_layer import Scale
  
  from load_cifar10 import load_cifar10_data
  
  import sys
  sys.setrecursionlimit(3000)
  
  def identity_block(input_tensor, kernel_size, filters, stage, block):
      '''The identity_block is the block that has no conv layer at shortcut
      # Arguments
          input_tensor: input tensor
          kernel_size: defualt 3, the kernel size of middle conv layer at main path
          filters: list of integers, the nb_filters of 3 conv layer at main path
          stage: integer, current stage label, used for generating layer names
          block: 'a','b'..., current block label, used for generating layer names
      '''
      eps = 1.1e-5
      nb_filter1, nb_filter2, nb_filter3 = filters
      conv_name_base = 'res' + str(stage) + block + '_branch'
      bn_name_base = 'bn' + str(stage) + block + '_branch'
      scale_name_base = 'scale' + str(stage) + block + '_branch'
  
      x = Convolution2D(nb_filter1, 1, 1, name=conv_name_base + '2a', bias=False)(input_tensor)
      x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2a')(x)
      x = Scale(axis=bn_axis, name=scale_name_base + '2a')(x)
      x = Activation('relu', name=conv_name_base + '2a_relu')(x)
  
      x = ZeroPadding2D((1, 1), name=conv_name_base + '2b_zeropadding')(x)
      x = Convolution2D(nb_filter2, kernel_size, kernel_size,
                        name=conv_name_base + '2b', bias=False)(x)
      x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2b')(x)
      x = Scale(axis=bn_axis, name=scale_name_base + '2b')(x)
      x = Activation('relu', name=conv_name_base + '2b_relu')(x)
  
      x = Convolution2D(nb_filter3, 1, 1, name=conv_name_base + '2c', bias=False)(x)
      x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2c')(x)
      x = Scale(axis=bn_axis, name=scale_name_base + '2c')(x)
  
      x = merge([x, input_tensor], mode='sum', name='res' + str(stage) + block)
      x = Activation('relu', name='res' + str(stage) + block + '_relu')(x)
      return x
  
  def conv_block(input_tensor, kernel_size, filters, stage, block, strides=(2, 2)):
      '''conv_block is the block that has a conv layer at shortcut
      # Arguments
          input_tensor: input tensor
          kernel_size: defualt 3, the kernel size of middle conv layer at main path
          filters: list of integers, the nb_filters of 3 conv layer at main path
          stage: integer, current stage label, used for generating layer names
          block: 'a','b'..., current block label, used for generating layer names
      Note that from stage 3, the first conv layer at main path is with subsample=(2,2)
      And the shortcut should have subsample=(2,2) as well
      '''
      eps = 1.1e-5
      nb_filter1, nb_filter2, nb_filter3 = filters
      conv_name_base = 'res' + str(stage) + block + '_branch'
      bn_name_base = 'bn' + str(stage) + block + '_branch'
      scale_name_base = 'scale' + str(stage) + block + '_branch'
  
      x = Convolution2D(nb_filter1, 1, 1, subsample=strides,
                        name=conv_name_base + '2a', bias=False)(input_tensor)
      x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2a')(x)
      x = Scale(axis=bn_axis, name=scale_name_base + '2a')(x)
      x = Activation('relu', name=conv_name_base + '2a_relu')(x)
  
      x = ZeroPadding2D((1, 1), name=conv_name_base + '2b_zeropadding')(x)
      x = Convolution2D(nb_filter2, kernel_size, kernel_size,
                        name=conv_name_base + '2b', bias=False)(x)
      x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2b')(x)
      x = Scale(axis=bn_axis, name=scale_name_base + '2b')(x)
      x = Activation('relu', name=conv_name_base + '2b_relu')(x)
  
      x = Convolution2D(nb_filter3, 1, 1, name=conv_name_base + '2c', bias=False)(x)
      x = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '2c')(x)
      x = Scale(axis=bn_axis, name=scale_name_base + '2c')(x)
  
      shortcut = Convolution2D(nb_filter3, 1, 1, subsample=strides,
                               name=conv_name_base + '1', bias=False)(input_tensor)
      shortcut = BatchNormalization(epsilon=eps, axis=bn_axis, name=bn_name_base + '1')(shortcut)
      shortcut = Scale(axis=bn_axis, name=scale_name_base + '1')(shortcut)
  
      x = merge([x, shortcut], mode='sum', name='res' + str(stage) + block)
      x = Activation('relu', name='res' + str(stage) + block + '_relu')(x)
      return x
  
  def resnet152_model(img_rows, img_cols, color_type=1, num_classes=None):
      """
      Resnet 152 Model for Keras
  
      Model Schema and layer naming follow that of the original Caffe implementation
      https://github.com/KaimingHe/deep-residual-networks
  
      ImageNet Pretrained Weights 
      Theano: https://drive.google.com/file/d/0Byy2AcGyEVxfZHhUT3lWVWxRN28/view?usp=sharing
      TensorFlow: https://drive.google.com/file/d/0Byy2AcGyEVxfeXExMzNNOHpEODg/view?usp=sharing
  
      Parameters:
        img_rows, img_cols - resolution of inputs
        channel - 1 for grayscale, 3 for color 
        num_classes - number of class labels for our classification task
      """
      eps = 1.1e-5
  
      # Handle Dimension Ordering for different backends
      global bn_axis
      if K.image_dim_ordering() == 'tf':
        bn_axis = 3
        img_input = Input(shape=(img_rows, img_cols, color_type), name='data')
      else:
        bn_axis = 1
        img_input = Input(shape=(color_type, img_rows, img_cols), name='data')
  
      x = ZeroPadding2D((3, 3), name='conv1_zeropadding')(img_input)
      x = Convolution2D(64, 7, 7, subsample=(2, 2), name='conv1', bias=False)(x)
      x = BatchNormalization(epsilon=eps, axis=bn_axis, name='bn_conv1')(x)
      x = Scale(axis=bn_axis, name='scale_conv1')(x)
      x = Activation('relu', name='conv1_relu')(x)
      x = MaxPooling2D((3, 3), strides=(2, 2), name='pool1')(x)
  
      x = conv_block(x, 3, [64, 64, 256], stage=2, block='a', strides=(1, 1))
      x = identity_block(x, 3, [64, 64, 256], stage=2, block='b')
      x = identity_block(x, 3, [64, 64, 256], stage=2, block='c')
  
      x = conv_block(x, 3, [128, 128, 512], stage=3, block='a')
      for i in range(1,8):
        x = identity_block(x, 3, [128, 128, 512], stage=3, block='b'+str(i))
  
      x = conv_block(x, 3, [256, 256, 1024], stage=4, block='a')
      for i in range(1,36):
        x = identity_block(x, 3, [256, 256, 1024], stage=4, block='b'+str(i))
  
      x = conv_block(x, 3, [512, 512, 2048], stage=5, block='a')
      x = identity_block(x, 3, [512, 512, 2048], stage=5, block='b')
      x = identity_block(x, 3, [512, 512, 2048], stage=5, block='c')
  
      x_fc = AveragePooling2D((7, 7), name='avg_pool')(x)
      x_fc = Flatten()(x_fc)
      x_fc = Dense(1000, activation='softmax', name='fc1000')(x_fc)
  
      model = Model(img_input, x_fc)
  
      if K.image_dim_ordering() == 'th':
        # Use pre-trained weights for Theano backend
        weights_path = 'imagenet_models/resnet152_weights_th.h5'
      else:
        # Use pre-trained weights for Tensorflow backend
        weights_path = 'imagenet_models/resnet152_weights_tf.h5'
  
      model.load_weights(weights_path, by_name=True)
  
      # Truncate and replace softmax layer for transfer learning
      # Cannot use model.layers.pop() since model is not of Sequential() type
      # The method below works since pre-trained weights are stored in layers but not in the model
      x_newfc = AveragePooling2D((7, 7), name='avg_pool')(x)
      x_newfc = Flatten()(x_newfc)
      x_newfc = Dense(num_classes, activation='softmax', name='fc8')(x_newfc)
  
      model = Model(img_input, x_newfc)
  
      # Learning rate is changed to 0.001
      sgd = SGD(lr=1e-3, decay=1e-6, momentum=0.9, nesterov=True)
      model.compile(optimizer=sgd, loss='categorical_crossentropy', metrics=['accuracy'])
  
      return model
  
  if __name__ == '__main__':
  
      # Example to fine-tune on 3000 samples from Cifar10
  
      img_rows, img_cols = 224, 224 # Resolution of inputs
      channel = 3
      num_classes = 10 
      batch_size = 8
      nb_epoch = 10
  
      # Load Cifar10 data. Please implement your own load_data() module for your own dataset
      X_train, Y_train, X_valid, Y_valid = load_cifar10_data(img_rows, img_cols)
  
      # Load our model
      model = resnet152_model(img_rows, img_cols, channel, num_classes)
  
      # Start Fine-tuning
      model.fit(X_train, Y_train,
                batch_size=batch_size,
                nb_epoch=nb_epoch,
                shuffle=True,
                verbose=1,
                validation_data=(X_valid, Y_valid),
                )
  
      # Make predictions
      predictions_valid = model.predict(X_valid, batch_size=batch_size, verbose=1)
  
      # Cross-entropy loss score
      score = log_loss(Y_valid, predictions_valid)
  