{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np\nfrom keras.utils import Sequence, to_categorical\nfrom PIL import Image\n\n\nclass DataGenerator(Sequence):\n    'Generates data for Keras'\n\n    def __init__(self, list_IDs, labels, train_dir='data/train/', batch_size=32, dim=(96, 96), n_channels=3,\n                 n_classes=2, shuffle=True):\n        'Initialization'\n        self.dir = train_dir\n        self.dim = dim\n        self.batch_size = batch_size\n        self.labels = labels\n        self.list_IDs = list_IDs\n        self.n_channels = n_channels\n        self.n_classes = n_classes\n        self.shuffle = shuffle\n        self.shape = (batch_size, *dim, n_channels)\n        self.on_epoch_end()\n    \n    def __len__(self):\n        'Denotes the number of batches per epoch'\n        return int(np.floor(len(self.list_IDs) / self.batch_size))\n\n    def __getitem__(self, index):\n        'Generate one batch of data'\n        # Generate indexes of the batch\n        indexes = self.indexes[index * self.batch_size:(index + 1) * self.batch_size]\n        # Find list of IDs\n        list_IDs_temp = [self.list_IDs[k] for k in indexes]\n        # Generate data\n        X, y = self.__data_generation(list_IDs_temp)\n        return X, y\n\n    def on_epoch_end(self):\n        'Updates indexes after each epoch'\n        self.indexes = np.arange(len(self.list_IDs))\n        if self.shuffle is True:\n            np.random.shuffle(self.indexes)\n\n    def __data_generation(self, list_IDs_temp):\n        'Generates data containing batch_size samples' \n        # X : (n_samples, *dim, n_channels)\n        # Initialization\n        X = np.empty(self.shape)\n        y = np.empty((self.batch_size), dtype=int)\n\n        # Generate data\n        for i, ID in enumerate(list_IDs_temp):\n            # Store sample\n            X[i, ] = np.array(Image.open(self.dir + ID + '.tif'))\n            # Store class\n            y[i] = self.labels[ID]\n\n        # Standardize images\n        x_batch = X.astype('float32') / 255\n        y_batch = to_categorical(y, num_classes=self.n_classes)\n        return [x_batch, y_batch], [y_batch, x_batch]","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import numpy as np\nfrom matplotlib import pyplot as plt\nimport csv\nimport math\nimport pandas\n\ndef plot_log(filename, show=True):\n\n    data = pandas.read_csv(filename)\n\n    fig = plt.figure(figsize=(4,6))\n    fig.subplots_adjust(top=0.95, bottom=0.05, right=0.95)\n    fig.add_subplot(211)\n    for key in data.keys():\n        if key.find('loss') >= 0 and not key.find('val') >= 0:  # training loss\n            plt.plot(data['epoch'].values, data[key].values, label=key)\n    plt.legend()\n    plt.title('Training loss')\n\n    fig.add_subplot(212)\n    for key in data.keys():\n        if key.find('acc') >= 0:  # acc\n            plt.plot(data['epoch'].values, data[key].values, label=key)\n    plt.legend()\n    plt.title('Training and validation accuracy')\n\n    # fig.savefig('result/log.png')\n    if show:\n        plt.show()\n\n\nif __name__==\"__main__\":\n    plot_log('result/log.csv')","metadata":{"collapsed":true,"jupyter":{"outputs_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import keras.backend as K\nimport tensorflow as tf\nfrom keras import initializers, layers\n\n\nclass Length(layers.Layer):\n    \"\"\"\n    Compute the length of vectors. This is used to compute a Tensor that has the same shape with y_true in margin_loss.\n    Using this layer as model's output can directly predict labels by using `y_pred = np.argmax(model.predict(x), 1)`\n    inputs: shape=[None, num_vectors, dim_vector]\n    output: shape=[None, num_vectors]\n    \"\"\"\n    def call(self, inputs, **kwargs):\n        return K.sqrt(K.sum(K.square(inputs), -1) + K.epsilon())\n\n    def compute_output_shape(self, input_shape):\n        return input_shape[:-1]\n\n    def get_config(self):\n        config = super(Length, self).get_config()\n        return config\n\n\nclass Mask(layers.Layer):\n    \"\"\"\n    Mask a Tensor with shape=[None, num_capsule, dim_vector] either by the capsule with max length or by an additional \n    input mask. Except the max-length capsule (or specified capsule), all vectors are masked to zeros. Then flatten the\n    masked Tensor.\n    For example:\n        ```\n        x = keras.layers.Input(shape=[8, 3, 2])  # batch_size=8, each sample contains 3 capsules with dim_vector=2\n        y = keras.layers.Input(shape=[8, 3])  # True labels. 8 samples, 3 classes, one-hot coding.\n        out = Mask()(x)  # out.shape=[8, 6]\n        # or\n        out2 = Mask()([x, y])  # out2.shape=[8,6]. Masked with true labels y. Of course y can also be manipulated.\n        ```\n    \"\"\"\n    def call(self, inputs, **kwargs):\n        if type(inputs) is list:  # true label is provided with shape = [None, n_classes], i.e. one-hot code.\n            assert len(inputs) == 2\n            inputs, mask = inputs\n        else:  # if no true label, mask by the max length of capsules. Mainly used for prediction\n            # compute lengths of capsules\n            x = K.sqrt(K.sum(K.square(inputs), -1))\n            # generate the mask which is a one-hot code.\n            # mask.shape=[None, n_classes]=[None, num_capsule]\n            mask = K.one_hot(indices=K.argmax(x, 1), num_classes=x.get_shape().as_list()[1])\n\n        # inputs.shape=[None, num_capsule, dim_capsule]\n        # mask.shape=[None, num_capsule]\n        # masked.shape=[None, num_capsule * dim_capsule]\n        masked = K.batch_flatten(inputs * K.expand_dims(mask, -1))\n        return masked\n\n    def compute_output_shape(self, input_shape):\n        if type(input_shape[0]) is tuple:  # true label provided\n            return tuple([None, input_shape[0][1] * input_shape[0][2]])\n        else:  # no true label provided\n            return tuple([None, input_shape[1] * input_shape[2]])\n\n    def get_config(self):\n        config = super(Mask, self).get_config()\n        return config\n\n\ndef squash(vectors, axis=-1):\n    \"\"\"\n    The non-linear activation used in Capsule. It drives the length of a large vector to near 1 and small vector to 0\n    :param vectors: some vectors to be squashed, N-dim tensor\n    :param axis: the axis to squash\n    :return: a Tensor with same shape as input vectors\n    \"\"\"\n    s_squared_norm = K.sum(K.square(vectors), axis, keepdims=True) + K.epsilon()\n    scale = K.sqrt(s_squared_norm) / (1. + s_squared_norm)\n    return scale * vectors\n\n\nclass CapsuleLayer(layers.Layer):\n    \n    def __init__(self, num_capsule, dim_capsule, routings=3,\n                 kernel_initializer='glorot_uniform',\n                 **kwargs):\n        super(CapsuleLayer, self).__init__(**kwargs)\n        self.num_capsule = num_capsule\n        self.dim_capsule = dim_capsule\n        self.routings = routings\n        self.kernel_initializer = initializers.get(kernel_initializer)\n\n    def build(self, input_shape):\n        assert len(input_shape) >= 3\n        self.input_num_capsule = input_shape[1]\n        self.input_dim_capsule = input_shape[2]\n\n        # Transform matrix\n        self.W = self.add_weight(shape=[self.num_capsule, self.input_num_capsule,\n                                        self.dim_capsule, self.input_dim_capsule],\n                                 initializer=self.kernel_initializer,\n                                 name='W')\n\n        self.built = True\n\n    def call(self, inputs, training=None):\n        # inputs.shape=[None, input_num_capsule, input_dim_capsule]\n        # inputs_expand.shape=[None, 1, input_num_capsule, input_dim_capsule]\n        inputs_expand = K.expand_dims(inputs, 1)\n\n        # Replicate num_capsule dimension to prepare being multiplied by W\n        # inputs_tiled.shape=[None, num_capsule, input_num_capsule, input_dim_capsule]\n        inputs_tiled = K.tile(inputs_expand, [1, self.num_capsule, 1, 1])\n\n        # Compute `inputs * W` by scanning inputs_tiled on dimension 0.\n        # x.shape=[num_capsule, input_num_capsule, input_dim_capsule]\n        # W.shape=[num_capsule, input_num_capsule, dim_capsule, input_dim_capsule]\n        # Regard the first two dimensions as `batch` dimension,\n        # then matmul: [input_dim_capsule] x [dim_capsule, input_dim_capsule]^T -> [dim_capsule].\n        # inputs_hat.shape = [None, num_capsule, input_num_capsule, dim_capsule]\n        inputs_hat = K.map_fn(lambda x: K.batch_dot(x, self.W, [2, 3]), elems=inputs_tiled)\n\n        # Begin: Routing algorithm ---------------------------------------------------------------------#\n        # The prior for coupling coefficient, initialized as zeros.\n        # b.shape = [None, self.num_capsule, self.input_num_capsule].\n        b = tf.zeros(shape=[K.shape(inputs_hat)[0], self.num_capsule, self.input_num_capsule])\n\n        assert self.routings > 0, 'The routings should be > 0.'\n        for i in range(self.routings):\n            # c.shape=[batch_size, num_capsule, input_num_capsule]\n            c = tf.nn.softmax(b, axis=1)\n\n            # c.shape =  [batch_size, num_capsule, input_num_capsule]\n            # inputs_hat.shape=[None, num_capsule, input_num_capsule, dim_capsule]\n            # The first two dimensions as `batch` dimension,\n            # then matmal: [input_num_capsule] x [input_num_capsule, dim_capsule] -> [dim_capsule].\n            # outputs.shape=[None, num_capsule, dim_capsule]\n            outputs = squash(K.batch_dot(c, inputs_hat, [2, 2]))  # [None, 10, 16]\n\n            if i < self.routings - 1:\n                # outputs.shape =  [None, num_capsule, dim_capsule]\n                # inputs_hat.shape=[None, num_capsule, input_num_capsule, dim_capsule]\n                # The first two dimensions as `batch` dimension,\n                # then matmal: [dim_capsule] x [input_num_capsule, dim_capsule]^T -> [input_num_capsule].\n                # b.shape=[batch_size, num_capsule, input_num_capsule]\n                b += K.batch_dot(outputs, inputs_hat, [2, 3])\n        # End: Routing algorithm -----------------------------------------------------------------------#\n\n        return outputs\n\n    def compute_output_shape(self, input_shape):\n        return None, self.num_capsule, self.dim_capsule\n\n    def get_config(self):\n        config = {\n            'num_capsule': self.num_capsule,\n            'dim_capsule': self.dim_capsule,\n            'routings': self.routings\n        }\n        base_config = super(CapsuleLayer, self).get_config()\n        return dict(list(base_config.items()) + list(config.items()))\n\n\ndef PrimaryCap(inputs, dim_capsule, n_channels, kernel_size, strides, padding):\n    \n    output = layers.Conv2D(filters=dim_capsule*n_channels, kernel_size=kernel_size, strides=strides, padding=padding,\n                           name='primarycap_conv2d')(inputs)\n    outputs = layers.Reshape(target_shape=[-1, dim_capsule], name='primarycap_reshape')(output)\n    return layers.Lambda(squash, name='primarycap_squash')(outputs)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nfrom keras import layers, models, optimizers\nfrom keras import backend as K\nfrom keras.utils import to_categorical\nfrom sklearn.model_selection import train_test_split\n#from utils import plot_log\nfrom PIL import Image\n#from capsulelayers import CapsuleLayer, PrimaryCap, Length, Mask\n#from generator import DataGenerator\n\nK.set_image_data_format('channels_last')\n\n\ndef CapsNet(input_shape, n_class, routings):\n    \n    x = layers.Input(shape=input_shape)\n\n    # Layers 1: Just some conventional Conv2D layers\n    conv1 = layers.Conv2D(filters=64, kernel_size=4, strides=2, activation='relu', name='conv1')(x)\n    conv2 = layers.Conv2D(filters=128, kernel_size=4, strides=2, activation='relu', name='conv2')(conv1)\n    conv3 = layers.Conv2D(filters=256, kernel_size=6, strides=2, activation='relu', name='conv3')(conv2)\n\n    # Layer 2: Conv2D layer with `squash` activation, then reshape to [None, num_capsule, dim_capsule]\n    primarycaps = PrimaryCap(conv3, dim_capsule=8, n_channels=32, kernel_size=6, strides=2, padding='valid')\n\n    # Layer 3: Capsule layer. Routing algorithm works here.\n    classcaps = CapsuleLayer(num_capsule=n_class, dim_capsule=16, routings=routings,\n                             name='classcaps')(primarycaps)\n\n    # Layer 4: This is an auxiliary layer to replace each capsule with its length. Just to match the true label's shape.\n    # If using tensorflow, this will not be necessary. :)\n    out_caps = Length(name='capsnet')(classcaps)\n\n    # Decoder network.\n    y = layers.Input(shape=(n_class,))\n    masked_by_y = Mask()([classcaps, y])  # The true label is used to mask the output of capsule layer. For training\n    masked = Mask()(classcaps)  # Mask using the capsule with maximal length. For prediction\n\n    # Shared Decoder model in training and prediction\n    decoder = models.Sequential(name='decoder')\n    decoder.add(layers.Dense(512, activation='relu', input_dim=16*n_class))\n    decoder.add(layers.Dense(1024, activation='relu'))\n    decoder.add(layers.Dense(np.prod(input_shape), activation='sigmoid'))\n    decoder.add(layers.Reshape(target_shape=input_shape, name='out_recon'))\n\n    # Models for training and evaluation (prediction)\n    train_model = models.Model([x, y], [out_caps, decoder(masked_by_y)])\n    eval_model = models.Model(x, [out_caps, decoder(masked)])\n\n    return train_model, eval_model\n\n\ndef margin_loss(y_true, y_pred):\n    \n    L = y_true * K.square(K.maximum(0., 0.9 - y_pred)) + \\\n        0.5 * (1 - y_true) * K.square(K.maximum(0., y_pred - 0.1))\n\n    return K.sum(L, axis=-1)\n\n\ndef train(model, train_gen, val_gen, args):\n    \n    # callbacks\n    log = callbacks.CSVLogger(args.save_dir + '/log.csv')\n    tb = callbacks.TensorBoard(log_dir=args.save_dir + '/tensorboard-logs',\n                               batch_size=args.batch_size, histogram_freq=int(args.debug))\n    checkpoint = callbacks.ModelCheckpoint(args.save_dir + '/weights-{epoch:02d}.h5', monitor='val_capsnet_acc',\n                                           save_best_only=True, save_weights_only=True, verbose=1)\n    lr_decay = callbacks.LearningRateScheduler(schedule=lambda epoch: args.lr * (args.lr_decay ** epoch))\n\n    # compile the model\n    model.compile(optimizer=optimizers.Adam(lr=args.lr),\n                  loss=[margin_loss, 'mse'],\n                  loss_weights=[1., args.lam_recon],\n                  metrics={'capsnet': 'accuracy'})\n\n    model.fit_generator(generator=train_gen,\n                        epochs=args.epochs,\n                        validation_data=val_gen,\n                        callbacks=[log, tb, checkpoint, lr_decay],\n                        use_multiprocessing=True,\n                        workers=6,\n                        verbose=1)\n\n    model.save_weights(args.save_dir + '/trained_model.h5')\n    print('Trained model saved to \\'%s/trained_model.h5\\'' % args.save_dir)\n\n    from utils import plot_log\n    plot_log(args.save_dir + '/log.csv', show=True)\n\n    return model\n\n\ndef load_generators(data_dir):\n    # Parameters\n    params = {'dim': (96,96),\n              'batch_size': 100,\n              'n_classes': 2,\n              'n_channels': 3,\n              'shuffle': True}\n\n    # Data\n    data = pd.read_csv(data_dir + 'train_labels.csv')\n    train, val = train_test_split(data, test_size = 0.1, random_state=42)\n    partition = {\"train\":list(train['id']), \"validation\":list(val['id'])}\n    labels = dict(zip(data['id'], data['label']))\n\n    train_dir = data_dir + \"train/\"\n\n    # Generators\n    train_gen = DataGenerator(partition['train'], labels, train_dir, **params)\n    val_gen = DataGenerator(partition['validation'], labels, train_dir, **params)\n\n    return train_gen, val_gen\n\n\nif __name__ == \"__main__\":\n    import os\n    import argparse\n    from keras import callbacks\n\n    # setting the hyper parameters\n    parser = argparse.ArgumentParser(description=\"Capsule Network on Histopathologic Cancer Detection Dataset.\")\n    parser.add_argument('--epochs', default=50, type=int)\n    parser.add_argument('--batch_size', default=100, type=int)\n    parser.add_argument('--lr', default=0.001, type=float,\n                        help=\"Initial learning rate\")\n    parser.add_argument('--lr_decay', default=0.9, type=float,\n                        help=\"The value multiplied by lr at each epoch. Set a larger value for larger epochs\")\n    parser.add_argument('--lam_recon', default=0.392, type=float,\n                        help=\"The coefficient for the loss of decoder\")\n    parser.add_argument('-r', '--routings', default=3, type=int,\n                        help=\"Number of iterations used in routing algorithm. should > 0\")\n    parser.add_argument('--debug', action='store_true',\n                        help=\"Save weights by TensorBoard\")\n    parser.add_argument('--save_dir', default='results')\n    parser.add_argument('--data_dir', default='../input/histopathologic-cancer-detection/')\n    parser.add_argument('-w', '--weights', default=None,\n                        help=\"The path of the saved weights. Should be specified when testing\")\n    args = parser.parse_args([])\n    print(args)\n\n    if not os.path.exists(args.save_dir):\n        os.makedirs(args.save_dir)\n\n    train_gen, val_gen = load_generators(args.data_dir)\n\n    # define model\n    model, eval_model = CapsNet(input_shape=train_gen.shape[1:],\n                                n_class=train_gen.n_classes,\n                                routings=args.routings)\n    model.summary()\n    model(tf.zeros(()))\n    # train or test\n    if args.weights is not None:  # init the model weights with provided one\n        model.load_weights(args.weights)\n    train(model=model, train_gen=train_gen, val_gen=val_gen, args=args)\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}