{"cells":[{"metadata":{"id":"xEt9p-2mbKRM"},"cell_type":"markdown","source":"# Dog vs Cat Classification"},{"metadata":{"id":"ieXTbN0peinz"},"cell_type":"markdown","source":"### Unzip all the image files"},{"metadata":{"trusted":true},"cell_type":"code","source":"import zipfile\ntrain_zip = zipfile.ZipFile('../input/dogs-vs-cats-redux-kernels-edition/train.zip','r')\ntest_zip = zipfile.ZipFile('../input/dogs-vs-cats-redux-kernels-edition/test.zip','r')\ntrain_zip.extractall('./')\ntest_zip.extractall('./')\ntrain_zip.close()\ntest_zip.close()","execution_count":null,"outputs":[]},{"metadata":{"id":"uMiWltzNenkp"},"cell_type":"markdown","source":"### Import tools"},{"metadata":{"id":"uPsBKLT5eFYr","trusted":true},"cell_type":"code","source":"#import all the tools we need\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\nfrom torch.optim import lr_scheduler\nimport torchvision\nfrom torch.utils.data.dataset import Dataset\nfrom torch.utils.data import DataLoader\nfrom torchvision import datasets, models, transforms\nfrom pytorch_lightning import metrics\nfrom sklearn.model_selection import train_test_split\nimport matplotlib.pyplot as plt\nplt.style.use('ggplot')\nimport pandas as pd\nimport random\nimport time\nimport os\nimport zipfile\nfrom PIL import Image\nimport numpy as np","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Prepare dataframe\n\n- Prepare three dataframes using Panda.\n\n1. Create Training and test dataframes first\n    1. For training dataframe, there are two columns: complete file paths and labels\n    1. For test dataframe, only the complete file paths\n1. Then split the training dataframe into 2 : training and validation \n    "},{"metadata":{"trusted":true},"cell_type":"code","source":"# The image files in test set is named from 1.jpg to 12500jpg, so I just make it by hand\ntest_list = [str(i)+'.jpg' for i in range(1,12501)]\n\ntrain_dir = './train'\ntest_dir =  './test'\n\n\n# Get the list of filenames and convert to dataframe\ntrain_df = pd.DataFrame(os.listdir(train_dir),columns=['filename'])\ntest_df = pd.DataFrame(test_list,columns=['filename'])\n\n# Build label column and convert it into 1(dog) and 0(cat)\ntrain_df['label'] = train_df.filename.str[:3]\ntrain_df['label'] = train_df['label'].map({'dog':1,'cat':0})\n\n# Change filename into complete file path\ntrain_df['filename'] = train_df['filename'].apply(lambda x: os.path.join(train_dir,x))\ntest_df['filename'] = test_df['filename'].apply(lambda x: os.path.join(test_dir,x))\n\n\"\"\"\nUse only 2000 images for testing first, if the model is running well without any error, then change back to full dataset\n\"\"\"\n#TRAIN_SAMPLES = 2000\nTRAIN_SAMPLES = train_df.shape[0]\n\n# Reduce the train_df according training samples\ntrain_df = train_df.sample(TRAIN_SAMPLES)\n\n# Split the train_df into train_df and validation_df\ntrain_df,val_df,_,_ = train_test_split(train_df,train_df,test_size=0.04,random_state=42)\n\n# Have a look to the dataframe\ntrain_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_df.tail()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# How many files do we got in training set and validation set?\nprint('Training set images: {}, Validation set image: {}'.format(train_df.shape[0], val_df.shape[0]))","execution_count":null,"outputs":[]},{"metadata":{"id":"h8UmAhD2hdVE"},"cell_type":"markdown","source":"### Have a look on the images"},{"metadata":{"id":"5kXLvYTofwUT","outputId":"65ad39c0-2203-4bcc-9162-ebd7efcd0ca2","trusted":true},"cell_type":"code","source":"def show_6_photos(dataframe):\n    sample_df = dataframe.sample(6)\n    paths = sample_df.filename.tolist()\n    for path in paths:\n        img = plt.imread(path)\n        plt.subplots(figsize=(3,3))\n        plt.imshow(img)\n        plt.show()\n  \nshow_6_photos(train_df)","execution_count":null,"outputs":[]},{"metadata":{"id":"2x_20NSxj_HY"},"cell_type":"markdown","source":"# Create Custom datasets\n\n- Create two transformers , 1 for training set, another for valdiation and test set."},{"metadata":{"trusted":true},"cell_type":"code","source":"# Two transformers\ndata_transforms = {\n    'train':transforms.Compose([\n        transforms.RandomResizedCrop(224),\n        transforms.RandomHorizontalFlip(),\n        transforms.ToTensor(),\n        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n    ]),\n    'val':transforms.Compose([\n        transforms.Resize(256),\n        transforms.CenterCrop(224),\n        transforms.ToTensor(),\n        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n    ])\n}","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Dataset class"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Custom dataset, receive a dataframe with complete file path and label(not in test data)\nclass image_set(Dataset):\n  def __init__(self,dataframe,transform=None,test=False):\n    self.dataframe = dataframe\n    self.transform = transform\n    self.test = test \n\n  def __getitem__(self,index):\n    x = self.dataframe.iloc[index,0]\n    x = Image.open(x)\n    if self.transform:\n      x = self.transform(x)\n    if self.test==True:\n      return x\n    else:\n      y = self.dataframe.iloc[index,1]\n      return x,np.array([y])\n\n  def __len__(self):\n    return self.dataframe.shape[0]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Create 3 dataset and 3 data loaders"},{"metadata":{"trusted":true},"cell_type":"code","source":"#  Create dataset and dataloader\n\ntrain_set = image_set(train_df,transform=data_transforms['train'])\nval_set = image_set(val_df,transform=data_transforms['val'])\ntest_set = image_set(test_df,transform=data_transforms['val'],test=True)\n\nBATCH_SIZE=32\n\ntrain_loader = DataLoader(train_set, batch_size=BATCH_SIZE, shuffle=True,num_workers=4)\nval_loader = DataLoader(val_set, batch_size=BATCH_SIZE, shuffle=True,num_workers=4)\ntest_loader = DataLoader(test_set, batch_size=BATCH_SIZE, shuffle=False,num_workers=4)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Setting up GPU"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Setting up the GPU\ndevice = torch.device('cuda:0' if torch.cuda.is_available else 'cpu')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Main training function"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Main training function\ndef train_model(model, cost_function,optimizer,num_epochs=5):\n\n  # List for storing loss and val\n  train_losses = []\n  val_losses = []\n  train_acc = []\n  val_acc=[]\n\n  # Metrics object\n  train_acc_object = metrics.Accuracy(compute_on_step=False)\n  val_acc_object = metrics.Accuracy(compute_on_step=False)\n\n  for epoch in range(num_epochs):\n    \"\"\"\n    On Epoch start\n    \"\"\"\n    print('-'*20)\n    print('Start training {}/{}'.format(epoch+1,num_epochs))\n    print('-'*20)\n    train_acc_object.reset()\n    val_acc_object.reset()\n    \n    \"\"\"\n    Start Training model\n    \"\"\"\n    model.train()\n    epoch_losses = []\n    for x,y in train_loader:\n      # Clear the grad\n      optimizer.zero_grad()\n    \n      # Put x and y to GPU and get predictions\n      x,y = x.to(device),y.to(device)\n      outputs = model(x)\n        \n      # Store the loss\n      loss = cost_function(outputs,y.type_as(outputs))\n      epoch_losses.append(loss.item())\n\n      # count and update gradients\n      loss.backward()\n      optimizer.step()\n      #scheduler.step()\n    \n      # Update the metrics\n      train_acc_object(outputs.cpu(), y.type_as(outputs).cpu())\n\n    \"\"\"\n    Counting Validation loss\n    \"\"\"\n    model.eval()\n    epoch_val_losses = []\n    for x,y in val_loader:\n      x,y  = x.to(device),y.to(device)\n      outputs = model(x)\n      loss = cost_function(outputs,y.type_as(outputs))\n      epoch_val_losses.append(loss.item())\n      # Update the metrics\n      val_acc_object(outputs.cpu(), y.type_as(outputs).cpu())\n        \n    \"\"\"\n    On epoch ends\n    \"\"\"\n    # Update the Loss list\n    train_losses.append(np.mean(epoch_losses))\n    val_losses.append(np.mean(epoch_val_losses))\n    \n    # Update the Accuracy list\n    epoch_t_acc = train_acc_object.compute()\n    epoch_v_acc = val_acc_object.compute()\n    train_acc.append(epoch_t_acc)\n    val_acc.append(epoch_v_acc)\n    \n    # Print the result\n    print('loss:{:.3f}, acc:{:.3f}, val_loss:{:.3f}, val_acc:{:.3f}'.format(np.mean(epoch_losses),\n                                                                             epoch_t_acc,\n                                                                             np.mean(epoch_val_losses),\n                                                                             epoch_v_acc))\n\n  print('Finish training.')\n  return train_losses, val_losses, train_acc, val_acc","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Setting up model, cost function, optimizer"},{"metadata":{},"cell_type":"markdown","source":"Model\n- Use pre-trained resnet18 as base model\n- Add two Dense layers at the end of the base model\n    - Dense 1 with 512 units\n    - Dense 2 with 1 unit for output.\n    \nCost function\n- Binary cross entropy\n\nOptimizer\n- Adam with lr = 0.009\n\nEpochs \n- 10~15 would be enough "},{"metadata":{"trusted":true},"cell_type":"code","source":"class net(nn.Module):\n    def __init__(self,resnet):\n        super(net,self).__init__()\n        self.resnet = resnet\n        self.linear1 = nn.Linear(1000,512)\n        self.linear2 = nn.Linear(512,1)\n    \n    def forward(self,x):\n        x = F.relu(self.resnet(x))\n        x = F.relu(self.linear1(x))\n        x = self.linear2(x)\n        x = torch.sigmoid(x)\n        return x","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Get the model and change the shape of output layer, since we are doing binary classification, the output is 1\nres = models.resnet18(pretrained=True)\nfor param in res.parameters():\n    param.requires_grad=False\n\n# build the model\nmodel_final = net(resnet=res)\n\n# Put the model to GPU\nmodel_final= model_final.to(device)\n\n# cost_function\ncost_function = nn.BCELoss()  \n\n# optimizer\noptimizer_ft = optim.Adam([param for param in model_final.parameters() if param.requires_grad],lr=0.009)\n\n# learning rate scheduler\n#exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=10, gamma=0.01)\n\n# Epoch\nEPOCHS=10","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Start training"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_losses, val_losses, train_acc,val_acc = train_model(model=model_final, \n                                        cost_function=cost_function,\n                                        optimizer=optimizer_ft,\n                                        num_epochs=EPOCHS)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Plot the result"},{"metadata":{"trusted":true},"cell_type":"code","source":"# function for plotting\ndef plot_result(train_losses, val_losses, train_acc, val_acc):\n    fig, (ax1,ax2) = plt.subplots(2,1,figsize=(7,6))\n    \n    ax1.plot(train_losses,label='train_losses')\n    ax1.plot(val_losses, label='val_losses')\n    \n    ax2.plot(train_acc, label='train_acc', color='brown')\n    ax2.plot(val_acc,label='val_acc', color='pink')\n    \n    ax1.legend()\n    ax2.legend()\n    plt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plot_result(train_losses, val_losses,train_acc, val_acc)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Make prediction on test data"},{"metadata":{"trusted":true},"cell_type":"code","source":"def predict_on_loader(test_loader,model):\n    print('Start predicting.....')\n    model.eval()\n    predictions = torch.tensor([])\n    for x in test_loader:\n        x = x.to(device)\n        predictions = torch.cat([predictions,model_final(x).detach().cpu()])\n    return predictions.numpy()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"predictions = predict_on_loader(test_loader,model_final)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Create the submission csv file"},{"metadata":{"trusted":true},"cell_type":"code","source":"submission = pd.read_csv('../input/dogs-vs-cats-redux-kernels-edition/sample_submission.csv')\nsubmission.label = predictions\nsubmission = submission.set_index('id',drop=True)\nsubmission.to_csv('submission.csv')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Save the model parameters"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Specify a path\nPATH = \"model_state_dict.pt\"\n\n# Save\ntorch.save(model_final.state_dict(), PATH)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Delete all the files unzipped"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Delete the files in folder\nfiles_path = train_df.filename.tolist() + val_df.filename.tolist() + test_df.filename.tolist()\nfor file in files_path:\n    os.remove(file)\n    \n# Delete the folders\nos.rmdir(train_dir)\nos.rmdir(test_dir)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Thank you very much"}],"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}