{"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_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Dog breed classification using Pytorch\n\n- Transfer learning using Resnet-50","metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0"}},{"cell_type":"code","source":"# import the tools we need\nimport os \nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom PIL import Image\nfrom sklearn.preprocessing import LabelEncoder\nimport torch\nimport torch.nn.functional as F\nimport torchvision\nfrom torchvision import transforms\nfrom torch.utils.data.dataset import Dataset\nfrom torch.utils.data import DataLoader\nfrom pytorch_lightning import metrics\nfrom sklearn.preprocessing import LabelEncoder\nfrom sklearn.model_selection import train_test_split","metadata":{"execution":{"iopub.status.busy":"2021-07-03T12:58:39.003743Z","iopub.execute_input":"2021-07-03T12:58:39.004029Z","iopub.status.idle":"2021-07-03T12:58:42.602638Z","shell.execute_reply.started":"2021-07-03T12:58:39.003999Z","shell.execute_reply":"2021-07-03T12:58:42.601659Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Read the csv files\ncomp_df = pd.read_csv('../input/dog-breed-identification/labels.csv')\ntest_df = pd.read_csv('../input/dog-breed-identification/sample_submission.csv')\n\n# How many number of images in Training set and test set?\nprint('Training set: {}, Test set: {}'.format(comp_df.shape[0],test_df.shape[0]))","metadata":{"execution":{"iopub.status.busy":"2021-07-03T12:58:42.607186Z","iopub.execute_input":"2021-07-03T12:58:42.607454Z","iopub.status.idle":"2021-07-03T12:58:43.116358Z","shell.execute_reply.started":"2021-07-03T12:58:42.607419Z","shell.execute_reply":"2021-07-03T12:58:43.114462Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Prepare dataframe for training set\n\n- Prepare dataframe with two columns: complete file path and labels (transferd to index)\n- Create a index-2-breed dictionary","metadata":{}},{"cell_type":"code","source":"# how may dog breed in training set? \ncomp_df.breed.value_counts()","metadata":{"execution":{"iopub.status.busy":"2021-07-03T12:58:43.11767Z","iopub.execute_input":"2021-07-03T12:58:43.118038Z","iopub.status.idle":"2021-07-03T12:58:43.138687Z","shell.execute_reply.started":"2021-07-03T12:58:43.117996Z","shell.execute_reply":"2021-07-03T12:58:43.138064Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Encode the breed into digits\ncomp_df['label'] = LabelEncoder().fit_transform(comp_df.breed)\n\n# Create a breed-2-index dictionary\ndict_df = comp_df[['label','breed']].copy()\ndict_df.drop_duplicates(inplace=True)\ndict_df.set_index('label',drop=True,inplace=True)\nindex_to_breed = dict_df.to_dict()['breed']","metadata":{"execution":{"iopub.status.busy":"2021-07-03T12:58:43.140626Z","iopub.execute_input":"2021-07-03T12:58:43.140973Z","iopub.status.idle":"2021-07-03T12:58:43.156885Z","shell.execute_reply.started":"2021-07-03T12:58:43.140936Z","shell.execute_reply":"2021-07-03T12:58:43.155852Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Change the id to full file path\ntrain_dir = '../input/dog-breed-identification/train'\ncomp_df.id = comp_df.id.apply(lambda x: x+'.jpg')\ncomp_df.id = comp_df.id.apply(lambda x:train_dir+'/'+x)\n\n# Drop the breed column\ncomp_df.pop('breed')","metadata":{"execution":{"iopub.status.busy":"2021-07-03T12:58:43.160745Z","iopub.execute_input":"2021-07-03T12:58:43.161041Z","iopub.status.idle":"2021-07-03T12:58:43.178836Z","shell.execute_reply.started":"2021-07-03T12:58:43.161013Z","shell.execute_reply":"2021-07-03T12:58:43.177963Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Have a look on the images","metadata":{}},{"cell_type":"code","source":"def show_images(df,img_num):\n    sample = df.sample(img_num)\n    paths = sample.id.tolist()\n    for path in paths:\n        plt.figure(figsize=(3,3))\n        img = plt.imread(path)\n        plt.imshow(img)\n        plt.axis('off')\n        plt.show()","metadata":{"execution":{"iopub.status.busy":"2021-07-03T12:58:43.180566Z","iopub.execute_input":"2021-07-03T12:58:43.181268Z","iopub.status.idle":"2021-07-03T12:58:43.188416Z","shell.execute_reply.started":"2021-07-03T12:58:43.181228Z","shell.execute_reply":"2021-07-03T12:58:43.187591Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"show_images(comp_df,4)","metadata":{"execution":{"iopub.status.busy":"2021-07-03T12:58:43.189745Z","iopub.execute_input":"2021-07-03T12:58:43.190253Z","iopub.status.idle":"2021-07-03T12:58:43.586283Z","shell.execute_reply.started":"2021-07-03T12:58:43.190213Z","shell.execute_reply":"2021-07-03T12:58:43.585428Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Create Custom Dataset class and transformers","metadata":{}},{"cell_type":"code","source":"class img_dataset(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 = Image.open(self.dataframe.iloc[index,0])\n        if self.transform:\n            x = self.transform(x)\n        if self.test:\n            return x\n        else:\n            y = self.dataframe.iloc[index,1]\n            return x,y\n        \n    def __len__(self):\n        return self.dataframe.shape[0]","metadata":{"execution":{"iopub.status.busy":"2021-07-03T12:58:43.587873Z","iopub.execute_input":"2021-07-03T12:58:43.58824Z","iopub.status.idle":"2021-07-03T12:58:43.596491Z","shell.execute_reply.started":"2021-07-03T12:58:43.588202Z","shell.execute_reply":"2021-07-03T12:58:43.595391Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Creat transfomers\ntrain_transformer = transforms.Compose([transforms.RandomResizedCrop(224),\n                                        transforms.RandomRotation(15),\n                                        transforms.RandomHorizontalFlip(),\n                                        transforms.ToTensor(),\n                                        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])\n\nval_transformer = transforms.Compose([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])])","metadata":{"execution":{"iopub.status.busy":"2021-07-03T12:58:43.598188Z","iopub.execute_input":"2021-07-03T12:58:43.59855Z","iopub.status.idle":"2021-07-03T12:58:43.60944Z","shell.execute_reply.started":"2021-07-03T12:58:43.598513Z","shell.execute_reply":"2021-07-03T12:58:43.608718Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Main Training function","metadata":{}},{"cell_type":"code","source":"# Print the result of 1 epoch\ndef print_epoch_result(train_loss,train_acc,val_loss,val_acc):\n    print('loss: {:.3f}, acc: {:.3f}, val_loss: {:.3f}, val_acc: {:.3f}'.format(train_loss,\n                                                                              train_acc,\n                                                                              val_loss,\n                                                                              val_acc))\n# Main Training function\ndef train_model(model, cost_function, optimizer,num_epochs=5):\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('-'*15)\n        print('Start training {}/{}'.format(epoch+1, num_epochs))\n        print('-'*15)\n        \n        # Training\n        train_sub_losses = []\n        model.train()\n        for x,y in train_loader:\n            optimizer.zero_grad()\n            x,y = x.to(device),y.to(device)\n            y_hat = model(x)\n            loss = cost_function(y_hat,y)\n            loss.backward()\n            optimizer.step()\n            #lr_scheduler.step()\n            # update loss sublist\n            train_sub_losses.append(loss.item())\n            # update accuracy object\n            train_acc_object(y_hat.cpu(),y.cpu())\n            \n        # Validation\n        val_sub_losses = []\n        model.eval()\n        for x,y in val_loader:\n            x,y = x.to(device),y.to(device)\n            y_hat = model(x)\n            loss = cost_function(y_hat,y)\n            val_sub_losses.append(loss.item())\n            val_acc_object(y_hat.cpu(),y.cpu())\n            \n        \"\"\"\n        On epoch end\n        \"\"\"\n        # Update the loss list\n        train_losses.append(np.mean(train_sub_losses))\n        val_losses.append(np.mean(val_sub_losses))\n        \n        # Update the accuracy list and reset the metrics object \n        train_epoch_acc = train_acc_object.compute()\n        val_epoch_acc = val_acc_object.compute()\n        train_acc.append(train_epoch_acc)\n        val_acc.append(val_epoch_acc)\n        train_acc_object.reset()\n        val_acc_object.reset()\n        \n        # print the result of epoch\n        print_epoch_result(np.mean(train_sub_losses),train_epoch_acc,np.mean(val_sub_losses),val_epoch_acc)\n        \n    print('Finish Training.')\n    return train_losses, train_acc, val_losses, val_acc","metadata":{"execution":{"iopub.status.busy":"2021-07-03T12:58:43.611078Z","iopub.execute_input":"2021-07-03T12:58:43.611459Z","iopub.status.idle":"2021-07-03T12:58:43.6307Z","shell.execute_reply.started":"2021-07-03T12:58:43.611422Z","shell.execute_reply":"2021-07-03T12:58:43.62968Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Now the main training function is finished, lets preprare the model, dataset, cost function and optimizer.","metadata":{}},{"cell_type":"code","source":"# Setting up gpu\ndevice = torch.device('cuda:0' if torch.cuda.is_available else 'cpu')","metadata":{"execution":{"iopub.status.busy":"2021-07-03T12:58:43.632247Z","iopub.execute_input":"2021-07-03T12:58:43.632752Z","iopub.status.idle":"2021-07-03T12:58:43.646257Z","shell.execute_reply.started":"2021-07-03T12:58:43.632709Z","shell.execute_reply":"2021-07-03T12:58:43.645336Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Parameters for dataset\ntraining_samples = comp_df.shape[0] # Use small number first to test whether the model is doing well, then change back to full dataset\ntest_size=0.05\nbatch_size = 64\n\n# Reduce the number of samples\nsample_df = comp_df.sample(training_samples)\n\n# Split the comp_df into training set and validation set\nx_train,x_val,_,_ = train_test_split(sample_df,sample_df,test_size=test_size)\n\n# Create dataloaders form datasets\ntrain_set = img_dataset(x_train, transform=train_transformer)\nval_set = img_dataset(x_val, transform=val_transformer)\ntrain_loader = DataLoader(train_set, batch_size=batch_size, shuffle=True)\nval_loader = DataLoader(val_set , batch_size=batch_size, shuffle=True)\n\n# How many images in training set and val set?\nprint('Training set: {}, Validation set: {}'.format(x_train.shape[0],x_val.shape[0]))","metadata":{"execution":{"iopub.status.busy":"2021-07-03T12:58:43.647508Z","iopub.execute_input":"2021-07-03T12:58:43.647924Z","iopub.status.idle":"2021-07-03T12:58:43.662166Z","shell.execute_reply.started":"2021-07-03T12:58:43.647886Z","shell.execute_reply":"2021-07-03T12:58:43.661075Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Create model\n- I will use resnet-50 as a base model (with parameters freezed)\n- Two dense layers added at the end.","metadata":{}},{"cell_type":"code","source":"# Use resnet-50 as a base model\nclass net(torch.nn.Module):\n    def __init__(self, base_model, base_out_features, num_classes):\n        super(net,self).__init__()\n        self.base_model=base_model\n        self.linear1 = torch.nn.Linear(base_out_features, 512)\n        self.output = torch.nn.Linear(512,num_classes)\n    def forward(self,x):\n        x = F.relu(self.base_model(x))\n        x = F.relu(self.linear1(x))\n        x = self.output(x)\n        return x\n\nres = torchvision.models.resnet101(pretrained=True)\nfor param in res.parameters():\n    param.requires_grad=False\n\nmodel_final = net(base_model=res, base_out_features=res.fc.out_features, num_classes=120)\nmodel_final = model_final.to(device)","metadata":{"execution":{"iopub.status.busy":"2021-07-03T12:58:43.6637Z","iopub.execute_input":"2021-07-03T12:58:43.664113Z","iopub.status.idle":"2021-07-03T12:58:57.563165Z","shell.execute_reply.started":"2021-07-03T12:58:43.664076Z","shell.execute_reply":"2021-07-03T12:58:57.562146Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Cost function and optimzier\ncost_function = torch.nn.CrossEntropyLoss()\noptimizer = torch.optim.Adam([param for param in model_final.parameters() if param.requires_grad], lr=0.0003)\n\n# Learning rate scheduler\n#lr_scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer=optimizer, gamma=0.1)\n\n# Epoch \nEPOCHS = 50","metadata":{"execution":{"iopub.status.busy":"2021-07-03T12:58:57.564486Z","iopub.execute_input":"2021-07-03T12:58:57.564837Z","iopub.status.idle":"2021-07-03T12:58:57.580156Z","shell.execute_reply.started":"2021-07-03T12:58:57.564802Z","shell.execute_reply":"2021-07-03T12:58:57.579135Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Start Training\ntrain_losses, train_acc, val_losses, val_acc = train_model(model=model_final, \n                                                           cost_function=cost_function, \n                                                           optimizer=optimizer,\n                                                           num_epochs=EPOCHS)","metadata":{"execution":{"iopub.status.busy":"2021-07-03T12:58:57.581377Z","iopub.execute_input":"2021-07-03T12:58:57.581893Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Plot the result","metadata":{}},{"cell_type":"code","source":"def plot_result(train_loss,val_loss,train_acc,val_acc):\n    plt.style.use('ggplot')\n    fig, (ax1,ax2) = plt.subplots(2,1,figsize=(10,8))\n    ax1.plot(train_loss,label='loss')\n    ax1.plot(val_loss,label='val_loss')\n    ax1.legend()\n    ax1.set_xlabel('epoch')\n    ax1.set_xticks(range(0,EPOCHS+1))\n    ax2.plot(train_acc, label='acc')\n    ax2.plot(val_acc,label='val_acc')\n    ax2.legend()\n    ax2.set_xlabel('epoch')\n    ax2.set_xticks(range(0,EPOCHS+1))\n    plt.show()\nplot_result(train_losses,val_losses, train_acc,  val_acc)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"- From the graph, it is known that both accuarcy and loss are still improving, training with more epochs (around 40~50) would be better.","metadata":{}},{"cell_type":"markdown","source":"# Do prediction on test data","metadata":{}},{"cell_type":"markdown","source":"Prepare the test loader first","metadata":{}},{"cell_type":"code","source":"# Prepare for test data dataframe\n#test_df = pd.read_csv('../input/dog-breed-identification/sample_submission.csv')\ntest_dir = '../input/dog-breed-identification/test'\ntest_df = test_df[['id']]\ntest_df.id = test_df.id.apply(lambda x: x+'.jpg')\ntest_df.id = test_df.id.apply(lambda x : test_dir+'/'+x)\ntest_set = img_dataset(test_df,transform=val_transformer, test=True)\ntest_loader = DataLoader(test_set, batch_size=batch_size, shuffle=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Start doing prediction\n- Since the submission require us to provide the softmax probabilities, I will use nn.softmax for the outputs.","metadata":{}},{"cell_type":"code","source":"model_final.eval()\npredictions = torch.tensor([])\nprint('Start predicting....')\nfor x in test_loader:\n    x = x.to(device)\n    y_hat = model_final(x)\n    predictions = torch.cat([predictions, y_hat.cpu()])\nprint('Finish prediction.')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Transfer all result to softmax probabilities","metadata":{}},{"cell_type":"code","source":"predictions = F.softmax(predictions,dim=1).detach().numpy()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Create CSV file for submission","metadata":{}},{"cell_type":"code","source":"answer_id = pd.read_csv('../input/dog-breed-identification/sample_submission.csv').id.tolist()\npredictions_df = pd.DataFrame(predictions, index=answer_id)\npredictions_df.columns = predictions_df.columns.map(index_to_breed)\npredictions_df.rename_axis('id', inplace=True)\npredictions_df.to_csv('submission.csv')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Thank you very much.","metadata":{}}]}