{"cells":[{"metadata":{},"cell_type":"markdown","source":"The goal of this notebook is to reproduce the architecture described in http://www.ws.binghamton.edu/fridrich/Research/SRNet.pdf"},{"metadata":{},"cell_type":"markdown","source":"* v_9 adds the custom metrics"},{"metadata":{},"cell_type":"markdown","source":"### The two main caracteristics of SRNet are :\n* it uses a lot a residual connections to help propagate the gradient throughout the layers\n* it uses only one channel (but it may be a good idea to adapt it to 3 channels, as you are necessarily loosing information by converting to grayscale)"},{"metadata":{},"cell_type":"markdown","source":"## Basic Imports"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport os\nimport gc\nimport matplotlib\nimport matplotlib.pyplot as plt\nimport matplotlib.image as mpimg\nfrom tqdm.notebook import tqdm\nimport random\nimport joblib\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import roc_curve, auc\nfrom time import time\nfrom collections import Counter\n\nfrom PIL import Image\nfrom random import shuffle\n\nimport tensorflow as tf\nfrom tensorflow.keras.metrics import AUC\n\nimport keras\nimport keras.backend as K\nimport keras.layers as L\nfrom keras import Model\nfrom keras.utils import plot_model\nfrom keras.preprocessing.image import ImageDataGenerator\n\nimport warnings\nwarnings.filterwarnings(\"ignore\")\n\n%matplotlib inline","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"PATH = \"/kaggle/input/alaska2-image-steganalysis/\"\nIMAGE_IDS = os.listdir(os.path.join(PATH, 'Cover'))\nN_IMAGES = len(IMAGE_IDS)\nALGORITHMS = ['JMiPOD', 'JUNIWARD', 'UERD']\nIMG_SIZE = 256\n\nsample_sub = pd.read_csv(PATH + 'sample_submission.csv')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# To make things reproductible\n\ndef seed_everything(seed=42):\n    np.random.seed(seed)\n    tf.random.set_seed(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    os.environ['TF_DETERMINISTIC_OPS'] = '1'\n    os.environ['TF_KERAS'] = '1'\n\nseed_everything()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Define the SRNet Model"},{"metadata":{"trusted":true},"cell_type":"code","source":"def layer_type1(x_inp, filters, kernel_size=(3, 3), dropout_rate=0):\n    x = L.Conv2D(filters, kernel_size, padding=\"same\")(x_inp)\n    x = L.BatchNormalization()(x)\n    x = L.ReLU()(x)\n    if dropout_rate > 0:\n        x = L.Dropout(dropout_rate)(x)\n\n    return x\n\ndef layer_type2(x_inp, filters, kernel_size=(3, 3), dropout_rate=0):\n    x = layer_type1(x_inp, filters)\n    x = L.Conv2D(filters, kernel_size, padding=\"same\")(x)\n    x = L.BatchNormalization()(x)\n    x = L.ReLU()(x)\n    if dropout_rate > 0:\n        x = L.Dropout(dropout_rate)(x)\n\n    x = L.Add()([x, x_inp])\n    \n    return x\n\ndef layer_type3(x_inp, filters, kernel_size=(3, 3), dropout_rate=0):\n    x = layer_type1(x_inp, filters)\n    x = L.Conv2D(filters, kernel_size, padding=\"same\")(x)\n    x = L.BatchNormalization()(x)\n    x = L.ReLU()(x)\n    x = L.AveragePooling2D(pool_size=(3, 3), strides=(2, 2))(x)\n    if dropout_rate > 0:\n        x = L.Dropout(dropout_rate)(x)\n        \n    x_res = L.Conv2D(filters, kernel_size, strides=(2, 2))(x_inp)\n    x_res = L.BatchNormalization()(x_res)\n    if dropout_rate > 0:\n        x_res = L.Dropout(dropout_rate)(x_res)\n\n    x = L.Add()([x, x_res])\n    \n    return x\n\ndef layer_type4(x_inp, filters, kernel_size=(3, 3), dropout_rate=0):\n    x = layer_type1(x_inp, filters)\n    x = L.Conv2D(filters, kernel_size=(3, 3), padding=\"same\")(x)\n    x = L.BatchNormalization()(x)\n    if dropout_rate > 0:\n        x = L.Dropout(dropout_rate)(x)    \n    x = L.GlobalAveragePooling2D()(x)\n    \n    return x","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def alaska_weighted_auc(y_true, y_valid):\n    tpr_thresholds = [0.0, 0.4, 1.0]\n    weights =        [       2,   1]\n    \n    fpr, tpr, thresholds = roc_curve(y_true, y_valid)\n    \n    # size of subsets\n    areas = np.array(tpr_thresholds[1:]) - np.array(tpr_thresholds[:-1])\n    \n    # The total area is normalized by the sum of weights such that the final weighted AUC is between 0 and 1.\n    normalization = np.dot(areas, weights)\n    \n    try:\n    \n        competition_metric = 0\n        for idx, weight in enumerate(weights):\n            y_min = tpr_thresholds[idx]\n            y_max = tpr_thresholds[idx + 1]\n            mask = (y_min < tpr) & (tpr < y_max)\n\n            x_padding = np.linspace(fpr[mask][-1], 1, 100)\n\n            x = np.concatenate([fpr[mask], x_padding])\n            y = np.concatenate([tpr[mask], [y_max] * len(x_padding)])\n            y = y - y_min # normalize such that curve starts at y=0\n            score = auc(x, y)\n            submetric = score * weight\n            best_subscore = (y_max - y_min) * weight\n            competition_metric += submetric\n    except:\n        # sometimes there's a weird bug so return naive score\n        return .5\n        \n    return competition_metric / normalization\n\ndef alaska_tf(y_true, y_val):\n    \"\"\"Wrapper for the above function\"\"\"\n    return tf.py_function(func=alaska_weighted_auc, inp=[y_true, y_val], Tout=tf.float32)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def make_model(input_shape=(IMG_SIZE, IMG_SIZE, 3), num_type2=5, dropout_rate=0):\n    # I reduced the size (image size, filters and depth) of the original network because it was way to big\n    inputs = L.Input(shape=input_shape)\n    \n    x = layer_type1(inputs, filters=64, dropout_rate=dropout_rate)\n    x = layer_type1(x, filters=16, dropout_rate=dropout_rate)    \n    \n    for _ in range(num_type2):\n        x = layer_type2(x, filters=16, dropout_rate=dropout_rate)         \n    \n    x = layer_type3(x, filters=16, dropout_rate=dropout_rate) \n    x = layer_type3(x, filters=32, dropout_rate=dropout_rate)            \n    x = layer_type3(x, filters=64, dropout_rate=dropout_rate)            \n    #x = layer_type3(x, filters=128, dropout_rate=dropout_rate) \n    \n    x = layer_type4(x, filters=128, dropout_rate=dropout_rate)        \n    \n    x = L.Dense(64)(x)\n    x = L.BatchNormalization()(x)\n    x = L.ReLU()(x)\n    if dropout_rate > 0:\n        x = L.Dropout(dropout_rate)(x)\n\n    predictions = L.Dense(1, activation=\"sigmoid\")(x)\n    \n    model = Model(inputs=inputs, outputs=predictions)\n    keras_auc = AUC()\n    \n    model.compile(optimizer='adam',\n                  loss='binary_crossentropy', \n                  metrics=[alaska_tf])\n    \n    return model\n    ","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model = make_model(num_type2=4, dropout_rate=0.1)\nmodel.summary()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plot_model(model, show_shapes=True, to_file=\"model.png\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Generate Dataset and Fit Model"},{"metadata":{},"cell_type":"markdown","source":"Credits to https://www.kaggle.com/tanulsingh077/steganalysis-approaching-as-regression-problem for his loader function.\n* I added a multiprocessor to speed the thing up (empirically **x3 times** decrease but it may depend on the number of images processed)"},{"metadata":{"trusted":true},"cell_type":"code","source":"def load_image(data):\n    i, j, img_path, labels = data\n    \n    img = Image.open(img_path)\n    img = img.convert('RGB')\n    img = img.resize((IMG_SIZE, IMG_SIZE), Image.ANTIALIAS)\n    \n    label = labels[i][j]\n    \n    return [np.array(img), label]\n\ndef load_training_data_multi(n_images=100):\n    train_data = []\n    data_paths = [os.listdir(os.path.join(PATH, alg)) for alg in ['Cover'] + ALGORITHMS]\n    labels = [np.zeros(N_IMAGES), np.ones(N_IMAGES), np.ones(N_IMAGES), np.ones(N_IMAGES)]\n    \n    print('Loading...')\n    for i, image_path in enumerate(data_paths):\n        print(f'\\t {i+1}-th folder')\n        \n        train_data_alg = joblib.Parallel(n_jobs=4, backend='threading')(\n            joblib.delayed(load_image)([i, j, os.path.join(PATH, [['Cover'] + ALGORITHMS][0][i], img_p), labels]) for j, img_p in enumerate(image_path[:n_images]))\n\n        train_data.extend(train_data_alg)\n        \n    shuffle(train_data)\n    return train_data","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def load_test_data():\n    test_data = []\n    for img_p in os.listdir(os.path.join(PATH, 'Test')):\n        img = Image.open(os.path.join(PATH, 'Test', img_p))\n        img = img.convert('RGB')\n        img = img.resize((IMG_SIZE, IMG_SIZE), Image.ANTIALIAS)\n        test_data.append([np.array(img)])\n            \n    return test_data","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"start = time()\ntraining_data = load_training_data_multi(n_images=5000)\n\ntrainImages = np.array([i[0] for i in training_data]).reshape(-1, IMG_SIZE, IMG_SIZE, 3)\ntrainLabels = np.array([i[1] for i in training_data], dtype=int)\n\nX_train, X_val, y_train, y_val = train_test_split(trainImages, trainLabels, random_state=42, stratify=trainLabels)\n\n# Then save some RAM\ndel training_data\ndel trainImages\ndel trainLabels\ngc.collect()\n\nprint(f\"{(time() - start) / 60: .2f} min elapsed.\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model.fit(X_train, y_train,\n          validation_data=(X_val, y_val),\n          batch_size=16,\n          epochs=5, \n          verbose=1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test = load_test_data()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_images = np.array([i[0] for i in test]).reshape(-1, IMG_SIZE, IMG_SIZE, 3)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"predict = model.predict(test_images, batch_size=64)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sample_sub['Label'] = predict","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sample_sub.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sample_sub.to_csv('submission.csv', index=False)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## I'm continuously updating the notebook so please stay tunned!"},{"metadata":{},"cell_type":"markdown","source":"## To do next:\n* Try with 3 channels (done)\n* Try to increase the size of the images (difficult with that memory limits)\n* Try to increase the depth of the network"},{"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}