{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Feature Extraction by Convolutional Autoencoders\n\n@kaggle/ydavidchen\n\nWhen running in production mode on Kaggle, be sure to:\n\n* Turn GPU on\n* Set global variable `PRODUCTION=True`\n* Handle any error message (e.g. imports) as appropriate","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"## Part I. Feature Extraction","execution_count":null},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"#!usr/bin/env/python3\n# util.py\n\nimport cv2\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\n\nfrom keras.callbacks import EarlyStopping\nfrom keras.layers import Input, Dense, BatchNormalization, Flatten, Conv2D, Reshape\nfrom keras.layers import MaxPooling2D, UpSampling2D\nfrom keras.models import Model\nfrom keras.optimizers import Adam\nfrom keras.preprocessing import image\n\ndef load_proc_img(path_list, size=224, ch=1):\n    \"\"\"\n    Load & Preprocess image\n    :reference: https://www.kaggle.com/anmour/convolutional-autoencoder-with-keras\n    :reference: https://www.kaggle.com/nxrprime/siim-d3-eda-augmentations-resnext-and-grad-cam#ca\n    \"\"\"\n    image_list = np.zeros((len(path_list), size, size, ch));\n    for key, val in enumerate(path_list):\n        img = image.load_img(val); #target size can be set\n        img = image.img_to_array(img).astype(\"float32\");\n        if ch == 1:\n            img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY);\n        img /= 255.0;\n        # img = cv2.addWeighted(img,4,cv2.GaussianBlur(img,(0,0),10),-4,128);\n        image_list[key] = np.reshape(img, [size,size,ch]);\n    return image_list;\n\ndef divide_in_batches(seq, num):\n    \"\"\"\n    Split an array into multiple chunks\n    :author M Shawabkheh at StackOverflow\n    \"\"\"\n    avg = len(seq) / float(num);\n    out = [];\n    last = 0.0;\n    while last < len(seq):\n        out.append(seq[int(last): int(last+avg)]);\n        last += avg;\n    return out;\n\n\nclass ConvolutionalAutoencoder:\n    def __init__(self, size=224, channel=1, batch_size=8, epochs=3, patience=3,\n                 encoder_dim=2, loss=\"binary_crossentropy\", metrics=[\"mse\"], optimizer=Adam(lr=0.0001)):\n        \"\"\" Constructor \"\"\"\n        self.size = size;\n        self.channels = channel;\n        self.batch_size = batch_size;\n        self.epochs = epochs;\n        self.patience = patience;\n        self.size_lower = encoder_dim;\n        self.loss = loss;\n        self.metrics = metrics;\n        self.optimzer = optimizer;\n        self.history = None;\n\n        self.img_shape = (self.size, self.size, self.channels);\n        self.model, self.encoder = self.setup_arch();\n        print(self.model.summary())\n\n    def setup_arch(self):\n        \"\"\" Sets up architecture \"\"\"\n        ## Encoder network:\n        input_layer = Input(shape=self.img_shape);\n\n        h = Conv2D(32, (3, 3), activation='relu', padding='same')(input_layer);\n        h = MaxPooling2D((2, 2), padding='same')(h);\n        h = BatchNormalization()(h);\n\n        encoded = Flatten()(h);\n        encoded = Dense(self.size_lower, activation=\"linear\")(encoded);\n        encoder = Model(inputs=input_layer, outputs=encoded);\n\n        ## Decoder network:\n        h = Dense(256, activation=\"relu\")(encoded);\n        h = Reshape((16, 16, 1))(h);\n        h = BatchNormalization()(h);\n        h = UpSampling2D((7, 7))(h);\n\n        h = Conv2D(32, (3, 3), activation='relu', padding='same')(h);\n        h = BatchNormalization()(h);\n        h = UpSampling2D((2, 2))(h);\n        \n        decoded = Conv2D(1, (3, 3), activation=\"sigmoid\", padding='same')(h);\n\n        model = Model(input_layer, decoded);\n        model.compile(optimizer=self.optimzer, loss=self.loss, metrics=self.metrics);\n        return model, encoder;\n\n    def train(self, x_train, x_val=None):\n        es = EarlyStopping(monitor=\"val_loss\", patience=self.patience, verbose=1);\n        validation_data = None if x_val is None else (x_val, x_val);\n        history = self.model.fit(\n            x_train, x_train,\n            batch_size = self.batch_size,\n            epochs = self.epochs,\n            validation_data = validation_data,\n            callbacks = [es]\n        );\n        self.history = history.history;\n\n    def sketch_loss(self):\n        plt.figure(figsize=(6, 6), dpi=100);\n        plt.plot(self.history[\"loss\"]);\n        plt.plot(self.history[\"val_loss\"]);\n        plt.xlabel(\"Epoch\");\n        plt.ylabel(\"Loss\");\n        plt.title(\"Loss over Epoch\");\n        plt.legend([\"Training\",\"Validation\"], loc=\"upper left\");\n        plt.show();\n\n    def predict(self, newX, newIdx=None, batch_size=1):\n        \"\"\" Helper to make prediction on new data \"\"\"\n        predictions = pd.DataFrame(self.encoder.predict(newX, batch_size=batch_size));\n        if newIdx is not None:\n            predictions.set_index(newIdx, inplace=True);\n        return predictions;","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"The following trains the autoencoder in batches (5 batches appear manageable) and `batch_size=1` to handle memory limitation.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"import glob\nimport socket\nfrom sklearn.model_selection import train_test_split\nfrom tensorflow.python.client import device_lib\n\nPRODUCTION = True;\n\nPREFIX = \"../input/siic-isic-224x224-images/\";\nOUT_PATH = \"/kaggle/working/learned_output(2).csv\";\nNUM_SPLITS = 5;\nTARGET_DIM = 2;\nEPOCHS = 20 if PRODUCTION else 2;\nBATCH_SIZE = 1 if PRODUCTION else 32;\nPROP_VAL = 0.10;\n\nprint(\"Host Name: %s\" % socket.gethostname())\nprint(device_lib.list_local_devices())","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"IMAGE_LIST = glob.glob(PREFIX + \"train/*.png\") + glob.glob(PREFIX + \"test/*.png\");\nIMAGE_LIST = divide_in_batches(IMAGE_LIST, NUM_SPLITS);","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"all_predictions = pd.DataFrame();\n\nfor fold_num in range(NUM_SPLITS):\n    image_batch, val_images = train_test_split(IMAGE_LIST[fold_num], test_size=PROP_VAL);\n    \n    if PRODUCTION:\n        print(\"In production mode! All images used in feature extraction...\")\n    else:\n        print(\"Subsetting data for development purposes...\")\n        image_batch, val_images = np.random.choice(image_batch, 80), np.random.choice(val_images, 20);\n\n    Xtrain = load_proc_img(image_batch);\n    Xvalid = load_proc_img(val_images);\n    \n    cae = ConvolutionalAutoencoder(epochs=EPOCHS, encoder_dim=TARGET_DIM, batch_size=BATCH_SIZE);\n    cae.train(Xtrain, Xvalid);\n    cae.sketch_loss();\n    predictions = cae.predict(\n        np.concatenate((Xtrain, Xvalid)),\n        np.concatenate((image_batch, val_images))\n    );\n    all_predictions = pd.concat([all_predictions, predictions], axis=0);\n    del cae;","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"all_predictions.to_csv(OUT_PATH);\nall_predictions.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Part II. Exploratory Data Analysis of Autoencoder Output\n\n* Visualize the 2 dimensions, color-coding with the following:\n    - Binary label for classification\n    - Categorical features, e.g. gender, tissue sites\n\n* Other ideas / ongoing work: \n    - Clean up the export of autoencoder model & weights\n    - Complete the export of the decoded images","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport copy as cp\nimport re\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\ndef zscore_norm(x):\n    \"\"\" Helper for Z score standardization of structured dataframe \"\"\"\n    return (x - x.mean()) / x.std();\n\ndef load_cae(path, normalize=True, aggregate=False):\n    \"\"\" Wrapper to load & optionally preprocess the data\"\"\"\n    all_predictions = pd.read_csv(path, index_col=0);\n    if normalize:\n        all_predictions = zscore_norm(all_predictions);\n    if aggregate:\n        all_predictions = pd.DataFrame(all_predictions.mean(axis=1), columns=[\"meanLower\"]);\n    return all_predictions;\n\ndef separate_cae(cae_data, pattern=\"train/\"):\n    \"\"\" Separates preprocessed training & test CAE data \"\"\"\n    dat = cp.deepcopy(cae_data);\n    dat = dat.loc[[pattern in x for x in dat.index], :];\n    pattern = \"../input/siic-isic-224x224-images/\" + pattern; \n    pattern += \"|.png\";\n    dat[PRIMARY_KEY] = [re.sub(pattern, \"\", x) for x in dat.index];\n    return dat;\n\nPRIMARY_KEY = \"image_name\";","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_data = load_cae(LEARNED_DATA_PATH);\ntrain_data.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"## Load annotations\ntrain_df = pd.read_csv(\"../input/siim-isic-melanoma-classification/train.csv\"); \ntrain_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"## Join training data\ntrain_data = separate_cae(train_data);\ntrain_data = pd.merge(train_df, train_data, on=PRIMARY_KEY); \ntrain_data.set_index(PRIMARY_KEY, inplace=True, drop=True);\ntrain_data.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"x, y = train_data[\"0\"], train_data[\"1\"]; \ntrain_data[\"ageOver50\"] = 1 * (train_data[\"age_approx\"] > 50);\n\nplt.figure(dpi=100, figsize=(10, 10));\n\nplt.subplot(221);\nsns.scatterplot(x, y, hue=train_data.benign_malignant, legend=\"full\");\n\nplt.subplot(222);\nsns.scatterplot(x, y, hue=train_data.sex, legend=\"full\");\n\nplt.subplot(223);\nsns.scatterplot(x, y, hue=train_data.anatom_site_general_challenge, legend=\"full\");\n\nplt.subplot(224);\nsns.scatterplot(x, y, hue=train_data.ageOver50, legend=\"full\");\n\nplt.show();","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"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":4,"nbformat_minor":4}