{"cells":[{"metadata":{"_uuid":"da035fe58e548e8b1b7e8e89725b9e6bc745aa7b"},"cell_type":"markdown","source":"# Humpback Whale Identification - CNN with Keras\nThis kernel is based on [Anezka Kolaceke](https://www.kaggle.com/anezka)'s awesome work: [CNN with Keras for Humpback Whale ID](https://www.kaggle.com/anezka/cnn-with-keras-for-humpback-whale-id)"},{"metadata":{"trusted":true,"_uuid":"0d9c73ad23e6c2eae3028255ee00c3254fe66401"},"cell_type":"code","source":"import numpy as np \nimport pandas as pd \nimport os\nimport matplotlib.pyplot as plt\nimport matplotlib.image as mplimg\nfrom matplotlib.pyplot import imshow\n\nimport pylab as pl\n\nfrom sklearn.preprocessing import LabelEncoder\nfrom sklearn.preprocessing import OneHotEncoder\n\nfrom skimage.transform import rotate\n\nfrom keras import layers\nfrom keras.preprocessing import image\nfrom keras.applications.imagenet_utils import preprocess_input\nfrom keras.layers import Input, Dense, Activation, BatchNormalization, Flatten, Conv2D\nfrom keras.layers import AveragePooling2D, MaxPooling2D, Dropout\nfrom keras.models import Model\n\nimport keras.backend as K\nfrom keras.models import Sequential\n\nimport warnings\nwarnings.simplefilter(\"ignore\", category=DeprecationWarning)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from skimage.transform import warp, AffineTransform, ProjectiveTransform\nfrom skimage.exposure import equalize_adapthist, equalize_hist, rescale_intensity, adjust_gamma, adjust_log, adjust_sigmoid\nfrom skimage.filters import gaussian\nfrom skimage.util import random_noise\nimport random","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"2cea35de3530cc898be5b85063b84e875401d092"},"cell_type":"code","source":"os.listdir(\"../input/\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"46a8839e13a14eb8d16ea6823de9927ea63d5001"},"cell_type":"code","source":"train_df = pd.read_csv(\"../input/train.csv\")\ntrain_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"f46b24dbba74f22833cac6140e60348b15a8e047"},"cell_type":"code","source":"def prepareImages(data, m, dataset):\n    print(\"Preparing images\")\n    X_train = np.zeros((m, 100, 100, 3))\n    count = 0\n    \n    for fig in data['Image']:\n        #load images into images of size 100x100x3\n        img = image.load_img(\"../input/\"+dataset+\"/\"+fig, target_size=(100, 100, 3))\n        x = image.img_to_array(img)\n        x = preprocess_input(x)\n\n        X_train[count] = x\n        if (count%500 == 0):\n            print(\"Processing image: \", count+1, \", \", fig)\n        count += 1\n        \n    \n    return X_train","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def randRange(a, b):\n    '''\n    a utility functio to generate random float values in desired range\n    '''\n    return pl.rand() * (b - a) + a\n\ndef randomAffine(im):\n    '''\n    wrapper of Affine transformation with random scale, rotation, shear and translation parameters\n    '''\n    tform = AffineTransform(scale=(randRange(0.75, 1.3), randRange(0.75, 1.3)),\n                            rotation=randRange(-0.25, 0.25),\n                            shear=randRange(-0.2, 0.2),\n                            translation=(randRange(-im.shape[0]//10, im.shape[0]//10), \n                                         randRange(-im.shape[1]//10, im.shape[1]//10)))\n    return warp(im, tform.inverse, mode='reflect')\n\n\ndef randomPerspective(im):\n    '''\n    wrapper of Projective (or perspective) transform, from 4 random points selected from 4 corners of the image within a defined region.\n    '''\n    region = 1/4\n    A = pl.array([[0, 0], [0, im.shape[0]], [im.shape[1], im.shape[0]], [im.shape[1], 0]])\n    B = pl.array([[int(randRange(0, im.shape[1] * region)), int(randRange(0, im.shape[0] * region))], \n                  [int(randRange(0, im.shape[1] * region)), int(randRange(im.shape[0] * (1-region), im.shape[0]))], \n                  [int(randRange(im.shape[1] * (1-region), im.shape[1])), int(randRange(im.shape[0] * (1-region), im.shape[0]))], \n                  [int(randRange(im.shape[1] * (1-region), im.shape[1])), int(randRange(0, im.shape[0] * region))], \n                 ])\n\n    pt = ProjectiveTransform()\n    pt.estimate(A, B)\n    rescale_intensity(im)\n    return warp(im, pt, output_shape=im.shape[:2])\n\n\ndef randomCrop(im):\n    '''\n    croping the image in the center from a random margin from the borders\n    '''\n    margin = 1/10\n    start = [int(randRange(0, im.shape[0] * margin)),\n             int(randRange(0, im.shape[1] * margin))]\n    end = [int(randRange(im.shape[0] * (1-margin), im.shape[0])), \n           int(randRange(im.shape[1] * (1-margin), im.shape[1]))]\n    return im[start[0]:end[0], start[1]:end[1]]\n\n\ndef randomIntensity(im):\n    '''\n    rescales the intesity of the image to random interval of image intensity distribution\n    '''\n    return rescale_intensity(im,\n                             in_range=tuple(pl.percentile(im, (randRange(0,10), randRange(90,100)))),\n                             out_range=tuple(pl.percentile(im, (randRange(0,10), randRange(90,100)))))\n\ndef randomGamma(im):\n    '''\n    Gamma filter for contrast adjustment with random gamma value.\n    '''\n    rescale_intensity(im)\n    return adjust_gamma(im, gamma=randRange(0.5, 1.5))\n\ndef randomGaussian(im):\n    '''\n    Gaussian filter for bluring the image with random variance.\n    '''\n    return gaussian(im, sigma=randRange(0, 5))\n\ndef randomNoise(im):\n    '''\n    random gaussian noise with random variance.\n    '''\n    var = randRange(0.001, 0.01)\n    return random_noise(im, var=var)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def imageAugmentation(data, m, dataset):\n    \"\"\"\n    This method creates 4D array that augments each image 11 different ways. This array will contain the original image\n    as well as the augmented images\n    ***This will run into a lot of issues because the final tensor will be 50+ gigs and will probably result in memory overflow\n    :param data: image data you would like to augment\n    :param m: the number of images you are augmenting\n    :param dataset: the dataset that you are augmenting, this will be used in the file path loading\n    :return: 4D array that contains the origional images as well as 11 augmented images\n    \"\"\"\n    print(\"Preparing images\")\n    X_train_flip = np.zeros((m, 100, 100, 3))\n    X_train_rot = np.zeros((m, 100, 100, 3))\n    X_train_rand_noise = np.zeros((m, 100, 100, 3))\n    X_train_rand_filter = np.zeros((m, 100, 100, 3))\n    X_train_rand_Gaussian = np.zeros((m, 100, 100, 3))\n    X_train_rand_Gamma = np.zeros((m, 100, 100, 3))\n    X_train_rand_Intensity = np.zeros((m, 100, 100, 3))\n    X_train_rand_Crop = np.zeros((m, 100, 100, 3))\n    X_train_rand_Perspective = np.zeros((m, 100, 100, 3))\n    X_train_rand_Affine = np.zeros((m, 100, 100, 3))\n    X = np.zeros((m, 100, 100, 3))\n    count = 0\n\n    for fig in data['Image']:\n        #load images into images of size 100x100x3\n        img = image.load_img(\"../input/\"+dataset+\"/\"+fig, target_size=(100, 100, 3))\n        x = image.img_to_array(img)\n        x = preprocess_input(x)\n\n        X_train_rand_Affine[count] = randomAffine(x)  # performs a random Affine transformation of the data\n\n        X_train_rand_Perspective[count] = randomPerspective(x)  # randomly changes the perspective of the image\n\n        X_train_rand_Crop[count] = randomCrop(x)  # randomly crops the image\n\n        X_train_rand_Intensity[count] = randomIntensity(x)  # randomly changes the intensity of the image\n\n        X_train_rand_Gamma[count] = randomGamma(x)  # adds a random Gamma filter to the image\n\n        X_train_rand_Gaussian[count] = randomGaussian(x)  # adds a random gaussian filter to the image\n\n        X_train_rand_noise[count] = randomNoise(x)  # add random noise to the image\n\n        X_train_flip[count] = np.fliplr(x)  # L/R flip the image\n        # * should do testing to see if this actually a useful feature because\n        # the model might be trying to find differentiators from right and left sides of the whale tale\n\n        X_train_rot[count] = rotate(x, 35)  # rotate the image 35 degrees\n\n        X[count] = x  # orig image\n        if (count%500 == 0):\n            print(\"Processing image: \", count+1, \", \", fig)\n        count += 1\n\n    # the images aren't concatenated initially because it makes it easier to format the labels later on\n    X_total = np.concatenate((X, X_train_flip, X_train_rot, X_train_rand_noise, X_train_rand_filter,\n                              X_train_rand_Gaussian, X_train_rand_Gamma, X_train_rand_Intensity,\n                              X_train_rand_Crop, X_train_rand_Perspective, X_train_rand_Affine), axis=0)\n\n    return X_total","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"6587a101b58af064af0f9c60a1070c6c8f52d45f"},"cell_type":"code","source":"def prepare_labels(y):\n    values = np.array(y)\n    label_encoder = LabelEncoder()\n    integer_encoded = label_encoder.fit_transform(values)\n    # print(integer_encoded)\n\n    onehot_encoder = OneHotEncoder(sparse=False)\n    integer_encoded = integer_encoded.reshape(len(integer_encoded), 1)\n    onehot_encoded = onehot_encoder.fit_transform(integer_encoded)\n    # print(onehot_encoded)\n\n    y = onehot_encoded\n    # print(y.shape)\n    return y, label_encoder","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def singleAugmentation(data, m, dataset, kind_aug=0):\n    \"\"\"\n    This method creates 4D array that can augment each image 11 different ways. This will return a single array with\n    the data manipulated in a single way\n    :param data: image data you would like to augment\n    :param m: the number of images you are augmenting\n    :param dataset: the dataset that you are augmenting, this will be used in the file path loading\n    :param kind_aug: the type of augmentation you want, 0 for no augmentation\n    :return: 4D array that contains the origional images as well as 11 augmented images\n    \"\"\"\n    print(\"Preparing images\")\n    X = np.zeros((m, 100, 100, 3))\n    count = 0\n\n    for fig in data['Image']:\n        #load images into images of size 100x100x3\n        img = image.load_img(\"../input/\"+dataset+\"/\"+fig, target_size=(100, 100, 3))\n        x = image.img_to_array(img)\n        x = preprocess_input(x)\n\n        if kind_aug == 9:\n            X[count] = np.fliplr(x)  # L/R flip the image\n            # * should do testing to see if this actually a useful feature because\n            # the model might be trying to find differentiators from right and left sides of the whale tale\n\n        if kind_aug == 8:\n            X[count] = preprocess_input(randomPerspective(x))  # randomly changes the perspective of the image\n\n        if kind_aug == 7:\n            X[count] = randomCrop(x)  # randomly crops the image\n\n        if kind_aug == 6:\n            X[count] = randomIntensity(x)  # randomly changes the intensity of the image\n\n        if kind_aug == 5:\n            X[count] = preprocess_input(randomGamma(x))  # adds a random Gamma filter to the image\n\n        if kind_aug == 4:\n            X[count] = randomGaussian(x)  # adds a random gaussian filter to the image\n\n        if kind_aug == 3:\n            X[count] = randomNoise(x)  # add random noise to the image\n\n        if kind_aug == 2:\n            X[count] = randomAffine(x)  # performs a random Affine transformation of the data\n\n        if kind_aug == 1:\n            X[count] = rotate(x, 35)  # rotate the image 35 degrees\n\n        if kind_aug == 0:\n            X[count] = x  # orig image\n\n        if (count%500 == 0):\n            print(\"Processing image: \", count+1, \", \", fig)\n        count += 1\n\n    return X","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# create the augmented training dataset\nX = singleAugmentation(data=train_df, m=train_df.shape[0], dataset=\"train\", kind_aug=0)\nX /= 255","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# create the output labels\ny, label_encoder = prepare_labels(train_df['Id'])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"14d243b19023e830b636bea16679e13bc40deae6"},"cell_type":"code","source":"y.shape","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"e7af799d186a1b97b6aa325d7d576a1fb55a6c5d"},"cell_type":"code","source":"# set up the convolutional model using keras\nmodel = Sequential()\n\nmodel.add(Conv2D(32, (7, 7), strides = (1, 1), name='conv0', input_shape = (100, 100, 3)))\n\nmodel.add(BatchNormalization(axis = 3, name = 'bn0'))\nmodel.add(Activation('relu'))\n\nmodel.add(MaxPooling2D((2, 2), name='max_pool0'))\nmodel.add(Conv2D(100, (3, 3), strides = (1,1), name=\"conv1\"))\nmodel.add(Activation('relu'))\nmodel.add(AveragePooling2D((3, 3), name='avg_pool0'))\n\nmodel.add(MaxPooling2D((2, 2), name='max_pool1'))\nmodel.add(Conv2D(100, (3, 3), strides = (1,1), name=\"conv2\"))\nmodel.add(Activation('relu'))\nmodel.add(AveragePooling2D((3, 3), name='avg_pool1'))\n\nmodel.add(Flatten())\nmodel.add(Dense(500, activation=\"relu\", name='rl'))\nmodel.add(Dropout(0.8))\nmodel.add(Dense(y.shape[1], activation='softmax', name='sm'))\n\nmodel.compile(loss='categorical_crossentropy', optimizer=\"adam\", metrics=['accuracy'])\nmodel.summary()\n\nhistory = model.fit(X, y, epochs=100, batch_size=100, verbose=1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"169f45e150c3a584e0f655a8eda523e0675da63a"},"cell_type":"code","source":"for i in range(20):\n    del X\n    X = singleAugmentation(data=train_df, m=train_df.shape[0], dataset=\"train\", kind_aug=(i+1 % 10))\n    X /= 255\n\n    # don't need to create a new y because all of the labels will still match\n\n    history = model.fit(X, y, epochs=10, batch_size=100, verbose=1)\ndel X","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"7bca48a1d0963cbf70685b75431435cef9499895"},"cell_type":"code","source":"plt.plot(history.history['acc'])\nplt.title('Model accuracy')\nplt.ylabel('Accuracy')\nplt.xlabel('Epoch')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"debe961c93b72bef151d9aad3ca2cb500ee00aaa"},"cell_type":"code","source":"test = os.listdir(\"../input/test/\")\nprint(len(test))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"72ed8198f519f7b1ae3efbc688933c78d8cdd0e4"},"cell_type":"code","source":"col = ['Image']\ntest_df = pd.DataFrame(test, columns=col)\ntest_df['Id'] = ''","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"52262195fc0b8755cff78bf8c98e6116d50f79af"},"cell_type":"code","source":"X = prepareImages(test_df, test_df.shape[0], \"test\")\nX /= 255","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"88c8d8ff98fbdb1df4218abb6bd51889e855a6fb"},"cell_type":"code","source":"predictions = model.predict(np.array(X), verbose=1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"66f0bdde31b8c7847916268aa82d9a1bdc9c0658"},"cell_type":"code","source":"for i, pred in enumerate(predictions):\n    test_df.loc[i, 'Id'] = ' '.join(label_encoder.inverse_transform(pred.argsort()[-5:][::-1]))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"09d7c1eb9b554e4e580b0c3c7eb609c15636892d"},"cell_type":"code","source":"test_df.head(10)\ntest_df.to_csv('submission.csv', index=False)","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}