{"cells":[{"metadata":{"_uuid":"dd1c5b7315039a59accbf568045ec4408d4478bd"},"cell_type":"markdown","source":"Probably would be better to do transfer learning rather than training the whole network from scratch on a fairly small dataset, but this is what I've got right now.  (This was hastily adapted from segmentation kernels, so not everything is done in the most efficient way. For example, you could just read the ground truth directly from the labels file rather than looking up the pneumonia locations.)"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"LR = 0.005\nEPOCHS = 2\nBATCHSIZE = 32\nCHANNELS = 64\nIMAGE_SIZE = 256\nNBLOCK = 6 \nDEPTH = 2\nMOMENTUM = 0.9\n\nimport os\nimport csv\nimport random\nimport pydicom\nimport numpy as np\nimport pandas as pd\nfrom skimage import measure\nfrom skimage.transform import resize\n\nimport tensorflow as tf\nfrom tensorflow import keras\n\nfrom matplotlib import pyplot as plt\n","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":true},"cell_type":"code","source":"\n# Load pneumonia locations\n\n# empty dictionary\npneumonia_locations = {}\n# load table\nwith open(os.path.join('../input/stage_1_train_labels.csv'), mode='r') as infile:\n    # open reader\n    reader = csv.reader(infile)\n    # skip header\n    next(reader, None)\n    # loop through rows\n    for rows in reader:\n        # retrieve information\n        filename = rows[0]\n        location = rows[1:5]\n        pneumonia = rows[5]\n        # if row contains pneumonia add label to dictionary\n        # which contains a list of pneumonia locations per filename\n        if pneumonia == '1':\n            # convert string to float to int\n            location = [int(float(i)) for i in location]\n            # save pneumonia location in dictionary\n            if filename in pneumonia_locations:\n                pneumonia_locations[filename].append(location)\n            else:\n                pneumonia_locations[filename] = [location]\n                \n                \n# Load filenames\n\n# load and shuffle filenames\nfolder = '../input/stage_1_train_images'\nfilenames = os.listdir(folder)\nrandom.shuffle(filenames)\n# split into train and validation filenames\nn_valid_samples = 2560\ntrain_filenames = filenames[n_valid_samples:]\nvalid_filenames = filenames[:n_valid_samples]\nprint('n train samples', len(train_filenames))\nprint('n valid samples', len(valid_filenames))\nn_train_samples = len(filenames) - n_valid_samples\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"2b651393a8533256d6a8182ac1aed8bab86f7553"},"cell_type":"code","source":"# Data generator\n\nclass generator(keras.utils.Sequence):\n    \n    def __init__(self, folder, filenames, pneumonia_locations=None, batch_size=BATCHSIZE, \n                 image_size=IMAGE_SIZE, shuffle=True, augment=False, predict=False):\n        self.folder = folder\n        self.filenames = filenames\n        self.pneumonia_locations = pneumonia_locations\n        self.batch_size = batch_size\n        self.image_size = image_size\n        self.shuffle = shuffle\n        self.augment = augment\n        self.predict = predict\n        self.on_epoch_end()\n        \n    def __load__(self, filename):\n        # load dicom file as numpy array\n        img = pydicom.dcmread(os.path.join(self.folder, filename)).pixel_array\n        # default negative\n        target = 0\n        # get filename without extension\n        filename = filename.split('.')[0]\n        # if image contains pneumonia\n        if filename in pneumonia_locations:\n            target = 1\n        # resize both image and mask\n        img = resize(img, (self.image_size, self.image_size), mode='reflect')\n        # if augment then horizontal flip half the time\n        if self.augment and random.random() > 0.5:\n            img = np.fliplr(img)\n        # add trailing channel dimension\n        img = np.expand_dims(img, -1)\n        return img, target\n    \n    def __loadpredict__(self, filename):\n        # load dicom file as numpy array\n        img = pydicom.dcmread(os.path.join(self.folder, filename)).pixel_array\n        # resize image\n        img = resize(img, (self.image_size, self.image_size), mode='reflect')\n        # add trailing channel dimension\n        img = np.expand_dims(img, -1)\n        return img\n        \n    def __getitem__(self, index):\n        # select batch\n        filenames = self.filenames[index*self.batch_size:(index+1)*self.batch_size]\n        # predict mode: return images and filenames\n        if self.predict:\n            # load files\n            imgs = [self.__loadpredict__(filename) for filename in filenames]\n            # create numpy batch\n            imgs = np.array(imgs)\n            return imgs, filenames\n        # train mode: return images and masks\n        else:\n            # load files\n            items = [self.__load__(filename) for filename in filenames]\n            # unzip images and masks\n            imgs, targets = zip(*items)\n            # create numpy batch\n            imgs = np.array(imgs)\n            targets = np.array(targets)\n            return imgs, targets\n        \n    def on_epoch_end(self):\n        if self.shuffle:\n            random.shuffle(self.filenames)\n        \n    def __len__(self):\n        if self.predict:\n            # return everything\n            return int(np.ceil(len(self.filenames) / self.batch_size))\n        else:\n            # return full batches only\n            return int(len(self.filenames) / self.batch_size)\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"54aaa883e8a6bbb3376282d876bea8d1fffa94f7"},"cell_type":"code","source":"# Network\n\ndef convlayer(channels, inputs, size=3, padding='same'):\n    x = keras.layers.BatchNormalization(momentum=MOMENTUM)(inputs)\n    x = keras.layers.LeakyReLU(0)(x)\n    x = keras.layers.Conv2D(channels, size, padding=padding, use_bias=False)(x)\n    return x\n\ndef just_downsample(inputs, pool=2):\n    x = keras.layers.BatchNormalization(momentum=MOMENTUM)(inputs)\n    x = keras.layers.LeakyReLU(0)(x)\n    x = keras.layers.MaxPool2D(pool)(x)\n    return x\n\ndef convblock(inputs, channels1, channels2):\n    x = convlayer(channels1, inputs)\n    x = convlayer(channels2, x)\n    x = keras.layers.Concatenate()([inputs, x])\n    return x\n\ndef denseblock(inputs, nblocks=6, channels1=128, channels2=32):\n    x = inputs\n    for i in range(nblocks):\n        x = convblock(x, channels1, channels2)\n    x = keras.layers.SpatialDropout2D(.2)(x)\n    return x\n\ndef transition(inputs, channels, pool=2):\n    x = convlayer(channels, inputs)\n    x = keras.layers.AveragePooling2D(pool)(x)\n    return x\n    \ndef create_network(input_size, channels=64, channels2=32, n_blocks=NBLOCK, depth=DEPTH):\n    # input\n    inputs = keras.Input(shape=(input_size, input_size, 1))\n    x = keras.layers.Conv2D(channels, 3, padding='same', strides=2, use_bias=False)(inputs)\n    x = just_downsample(x)\n\n    # densenet blocks\n    nchan = channels\n    for d in range(depth-1):\n        x = denseblock(x)\n        nchan = ( nchan + n_blocks*channels2 ) // 2\n        x = transition(x, nchan)\n    x = denseblock(x)\n\n    # output\n    x = convlayer(channels, x)\n    x = keras.layers.BatchNormalization(momentum=0.9)(x)\n    x = keras.layers.GlobalAveragePooling2D()(x)\n    x = keras.layers.LeakyReLU(0)(x)\n    x = keras.layers.Dropout(.5)(x)\n    output = keras.layers.Dense(1, activation='sigmoid')(x)\n    model = keras.Model(inputs=inputs, outputs=output)\n    return model","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"364c052f23eb13f7a3d2d7e7e71d63662494423d"},"cell_type":"code","source":"# create network and compiler\nmodel = create_network(input_size=IMAGE_SIZE, channels=CHANNELS, n_blocks=NBLOCK, depth=DEPTH)\nmodel.summary()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"070a049bede2d9f7cbccd3edd8b229503b4bd80e"},"cell_type":"code","source":"model.compile(optimizer=keras.optimizers.Adam(lr=LR),\n              loss=keras.losses.binary_crossentropy, metrics=['accuracy'])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"2b731453c0281ad0d6d831be87375f1dfb1db0e7"},"cell_type":"code","source":"train_gen = generator(folder, train_filenames, pneumonia_locations, batch_size=BATCHSIZE, \n                      image_size=IMAGE_SIZE, shuffle=True, augment=True, predict=False)\nvalid_gen = generator(folder, valid_filenames, pneumonia_locations, batch_size=BATCHSIZE, \n                      image_size=IMAGE_SIZE, shuffle=False, predict=False)\n\nhistory = model.fit_generator(train_gen, validation_data=valid_gen, \n                              epochs=EPOCHS, shuffle=True, verbose=2)\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"67f21edee83c0c4416b3e3420b12a7bc01ed7f23"},"cell_type":"code","source":"prob_thresholds = [0.2, 0.3, 0.4, 0.5, 0.6, 0.7]\nnthresh = len(prob_thresholds)\n\n# load and shuffle filenames\nfolder = '../input/stage_1_train_images'\ntest_filenames = valid_filenames\nprint('n test samples:', len(test_filenames))\n\n# create test generator with predict flag set to True\ntest_gen = generator(folder, test_filenames, None, batch_size=25, \n                     image_size=IMAGE_SIZE, shuffle=False, predict=True)\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"b38a06b84f9b5c22fb177fb0f0613f2404f390d2"},"cell_type":"code","source":"# loop through validation set\ncount = 0\nnfps = nthresh*[0]\nntps = nthresh*[0]\nnfns = nthresh*[0]\nntns = nthresh*[0]\nfor imgs, filenames in test_gen:\n    # predict batch of images\n    preds = model.predict(imgs)\n    # loop through batch\n    for pred, filename in zip(preds, filenames):\n        count = count + 1\n        actual = filename.split('.')[0] in pneumonia_locations\n        # threshold predicted probability\n        for i, thresh in enumerate(prob_thresholds):\n            predicted = pred > thresh\n            if actual and predicted:\n                ntps[i] = ntps[i] + 1\n            elif actual:\n                nfns[i] = nfns[i] + 1\n            elif predicted:\n                nfps[i] = nfps[i] + 1\n            else:\n                ntns[i] = ntns[i] + 1\n\n    # stop if we've got them all\n    if count >= len(test_filenames):\n        break\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"02680e5ea9b8e8a3730b57685894741f42fa121a"},"cell_type":"code","source":"list( zip( prob_thresholds, ntps, nfns, nfps, ntns ) )","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"af7593be892d5474d4ee519a53e50050381f826d"},"cell_type":"code","source":"for table in zip( prob_thresholds, ntps, nfns, nfps, ntns ):\n    confusion = {'Positive':[table[1],table[2]], 'Negative':[table[3],table[4]]}\n    print( '\\nProbability threshold: ', table[0])\n    print( pd.DataFrame( confusion, index=['Pred Positive', 'Pred Negative']) )","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"6cddec441623d2b1b62e8054b43317e6f32cede6"},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.6.6","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"}},"nbformat":4,"nbformat_minor":1}