{"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":"## Import libraries","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\n%matplotlib inline\nimport os\nfrom PIL import Image\n\nimport torch\nimport torch.nn as nn\nimport torchvision\nfrom torchvision import transforms\nfrom torch.utils.data import Dataset, DataLoader\nimport torch.nn.functional as F\n\n","metadata":{"execution":{"iopub.status.busy":"2022-02-12T17:45:53.419444Z","iopub.execute_input":"2022-02-12T17:45:53.419825Z","iopub.status.idle":"2022-02-12T17:45:54.921097Z","shell.execute_reply.started":"2022-02-12T17:45:53.419716Z","shell.execute_reply":"2022-02-12T17:45:54.920396Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Load the data","metadata":{}},{"cell_type":"code","source":"# unzip the dataset\nimport zipfile\n\nwith zipfile.ZipFile(\"../input/dogs-vs-cats/train.zip\",\"r\") as z:\n    z.extractall(\".\")\nwith zipfile.ZipFile(\"../input/dogs-vs-cats/test1.zip\", \"r\") as z:\n    z.extractall(\".\")\n    \n# save the directories of the datasets to variables\ntrain_dir = \"/kaggle/working/train/\"\ntest_dir = \"/kaggle/working/test1/\"\n\n# check the format of the data\ntrain_image_names = os.listdir(train_dir)\ntest_image_names = os.listdir(test_dir)\n\nprint(train_image_names[:3])\nprint(test_image_names[:3])","metadata":{"execution":{"iopub.status.busy":"2022-02-12T17:45:58.835319Z","iopub.execute_input":"2022-02-12T17:45:58.835983Z","iopub.status.idle":"2022-02-12T17:46:14.900647Z","shell.execute_reply.started":"2022-02-12T17:45:58.835946Z","shell.execute_reply":"2022-02-12T17:46:14.899817Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# make lists of dataset images with their full path\nimport glob\n\ntrain_images = glob.glob(os.path.join(train_dir, \"*.jpg\"))\ntest_images = glob.glob(os.path.join(test_dir, \"*.jpg\" ))\n\nprint(train_images[:3])\nprint(len(train_images))\nprint(test_images[:3])\nprint(len(test_images))","metadata":{"execution":{"iopub.status.busy":"2022-02-12T17:46:14.902283Z","iopub.execute_input":"2022-02-12T17:46:14.902879Z","iopub.status.idle":"2022-02-12T17:46:15.039559Z","shell.execute_reply.started":"2022-02-12T17:46:14.902836Z","shell.execute_reply":"2022-02-12T17:46:15.038869Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Split the labeled train data into training and validation sets","metadata":{"execution":{"iopub.status.busy":"2022-02-12T09:41:30.581167Z","iopub.execute_input":"2022-02-12T09:41:30.581453Z","iopub.status.idle":"2022-02-12T09:41:30.589401Z","shell.execute_reply.started":"2022-02-12T09:41:30.581425Z","shell.execute_reply":"2022-02-12T09:41:30.588321Z"}}},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\n\ntrain_list, val_list = train_test_split(train_images, test_size=0.2)","metadata":{"execution":{"iopub.status.busy":"2022-02-12T17:46:15.041686Z","iopub.execute_input":"2022-02-12T17:46:15.042024Z","iopub.status.idle":"2022-02-12T17:46:15.842374Z","shell.execute_reply.started":"2022-02-12T17:46:15.041991Z","shell.execute_reply":"2022-02-12T17:46:15.841644Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Define a pytorch class for retrieving the images in the dataset","metadata":{}},{"cell_type":"code","source":"# make a dataset class\nclass CatDogDataset(Dataset):\n    \n    def __init__(self, images_list, mode=\"train\", transform=None):\n        self.images_list = images_list\n        self.mode = mode\n        self.transform = transform\n        \n    # dataset length\n    def __len__(self):\n        self.dataset_len = len(self.images_list)\n        return self.dataset_len\n    \n    # load an image\n    def __getitem__(self, idx):\n        image_name = self.images_list[idx]\n        image = Image.open(image_name)\n        image = image.resize((224,224)) # this is important when feeding into a pretrained model\n        transformed_image = self.transform(image)\n        image_category = image_name.split(\"/\")[-1].split(\".\")[0]\n        \n        if self.mode == \"train\" or self.mode == \"val\":\n            if image_category == \"cat\":\n                label = 0 \n            else:\n                label = 1              \n            return transformed_image, label\n        else:\n            return transformed_image","metadata":{"execution":{"iopub.status.busy":"2022-02-12T17:46:17.130846Z","iopub.execute_input":"2022-02-12T17:46:17.131563Z","iopub.status.idle":"2022-02-12T17:46:17.140549Z","shell.execute_reply.started":"2022-02-12T17:46:17.131525Z","shell.execute_reply":"2022-02-12T17:46:17.139613Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Define transformations to normalize/augment the images","metadata":{}},{"cell_type":"code","source":"# define transformations for the train, test and holdout images\ntrain_transforms = transforms.Compose([\n        transforms.RandomHorizontalFlip(p=0.5), \n        transforms.RandomRotation(15),\n        transforms.RandomResizedCrop(224, scale=(0.8,1.0),ratio=(1.0,1.0)),\n        transforms.ToTensor(),\n        transforms.Normalize((0, 0, 0),(1, 1, 1))\n    ])\n\n# for validation we only need to normalize the data\nval_transforms = transforms.Compose([ \n        transforms.ToTensor(),\n        transforms.Normalize((0, 0, 0),(1, 1, 1))\n    ])","metadata":{"execution":{"iopub.status.busy":"2022-02-12T17:47:46.145148Z","iopub.execute_input":"2022-02-12T17:47:46.145633Z","iopub.status.idle":"2022-02-12T17:47:46.152088Z","shell.execute_reply.started":"2022-02-12T17:47:46.145597Z","shell.execute_reply":"2022-02-12T17:47:46.151319Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Define model hyperparameters\n","metadata":{}},{"cell_type":"code","source":"batch_size = 128\nnum_epochs = 10\nlearning_rate = 0.01","metadata":{"execution":{"iopub.status.busy":"2022-02-12T17:50:17.883504Z","iopub.execute_input":"2022-02-12T17:50:17.883794Z","iopub.status.idle":"2022-02-12T17:50:17.888814Z","shell.execute_reply.started":"2022-02-12T17:50:17.883761Z","shell.execute_reply":"2022-02-12T17:50:17.887976Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Prepare the datasets for training","metadata":{}},{"cell_type":"code","source":"# create dataset objects\ntrain_dataset = CatDogDataset(train_list, transform=train_transforms)\nval_dataset = CatDogDataset(val_list, mode=\"val\", transform=val_transforms)\ntest_dataset = CatDogDataset(test_images, mode=\"val\", transform=val_transforms)\n\n# create dataloaders\ntrain_dataloader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\nval_dataloader = DataLoader(val_dataset, batch_size=batch_size, shuffle=True)\ntest_dataloader = DataLoader(test_dataset, batch_size=batch_size, shuffle=True)","metadata":{"execution":{"iopub.status.busy":"2022-02-12T17:50:19.979955Z","iopub.execute_input":"2022-02-12T17:50:19.980228Z","iopub.status.idle":"2022-02-12T17:50:19.989291Z","shell.execute_reply.started":"2022-02-12T17:50:19.980181Z","shell.execute_reply":"2022-02-12T17:50:19.988547Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# visualize several random images\nfor images, labels in train_dataloader:\n    \n    fig, ax = plt.subplots(figsize = (10, 10))\n    ax.set_xticks([])\n    ax.set_yticks([])\n    ax.imshow(torchvision.utils.make_grid(images, nrow=4).permute(1,2,0))\n    break","metadata":{"execution":{"iopub.status.busy":"2022-02-12T17:48:50.840406Z","iopub.execute_input":"2022-02-12T17:48:50.840610Z","iopub.status.idle":"2022-02-12T17:48:51.428242Z","shell.execute_reply.started":"2022-02-12T17:48:50.840586Z","shell.execute_reply":"2022-02-12T17:48:51.427628Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Note on **.permute(1,2,0)** above:\nThe dimensions of grid_img are [# color channels x image height x image width]. Conversely, the input to matplotlib.pyplot.imshow() needs to be [image heigth x image width x # color channels] (i.e., the shape needs to be [518, 1292, 3]). The .permute(1, 2, 0) action is a Torch-specific function that permutes the axes of the original in exactly that order: [axis 1 x axis 2 x axis 0] = [image heigth x image width x # color channels]. ","metadata":{}},{"cell_type":"markdown","source":"## Check if we have GPU available","metadata":{}},{"cell_type":"code","source":"device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')\nprint(device)","metadata":{"execution":{"iopub.status.busy":"2022-02-12T17:50:27.602538Z","iopub.execute_input":"2022-02-12T17:50:27.602811Z","iopub.status.idle":"2022-02-12T17:50:27.609368Z","shell.execute_reply.started":"2022-02-12T17:50:27.602779Z","shell.execute_reply":"2022-02-12T17:50:27.608495Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Build a model class","metadata":{}},{"cell_type":"code","source":"class CNN(nn.Module):\n    def __init__(self):\n        super().__init__()\n        \n        self.cnn_layers = nn.Sequential(\n            # convolutional layer 1\n            nn.Conv2d(in_channels=3, out_channels=16, kernel_size=5, padding=0, stride=1),\n            nn.BatchNorm2d(16),\n            nn.ReLU(),\n            nn.MaxPool2d(kernel_size=2), # here image dim would be 220/2 = 110\n            \n            # convolutional layer 2\n            nn.Conv2d(in_channels=16, out_channels=32, kernel_size=5, padding=0, stride=1),\n            nn.BatchNorm2d(32),\n            nn.ReLU(),\n            nn.MaxPool2d(kernel_size=2), # here image dim would be 116/2 = 58\n            \n            # convolutional layer 3\n            nn.Conv2d(in_channels=32, out_channels=64, kernel_size=5, padding=0, stride=1),\n            nn.BatchNorm2d(64),\n            nn.ReLU(),\n            nn.MaxPool2d(kernel_size=2) # here image dim would be 54/2 = 27\n        )\n        \n        self.linear_layers = nn.Sequential(\n            nn.Linear(in_features=64 * 24 * 24, out_features=10),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(in_features=10, out_features=2)\n        )\n    \n    def forward(self, x):\n        out = self.cnn_layers(x)\n        #print(out.shape)\n        out = out.view(-1, 64 * 24 * 24) # flatten the values\n        out = self.linear_layers(out)\n        return out\n    ","metadata":{"execution":{"iopub.status.busy":"2022-02-12T17:59:16.112129Z","iopub.execute_input":"2022-02-12T17:59:16.112802Z","iopub.status.idle":"2022-02-12T17:59:16.122512Z","shell.execute_reply.started":"2022-02-12T17:59:16.112758Z","shell.execute_reply":"2022-02-12T17:59:16.121734Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# instantiate the model\nmodel = CNN()\nmodel = model.to(device)","metadata":{"execution":{"iopub.status.busy":"2022-02-12T18:21:26.181914Z","iopub.execute_input":"2022-02-12T18:21:26.182169Z","iopub.status.idle":"2022-02-12T18:21:26.197052Z","shell.execute_reply.started":"2022-02-12T18:21:26.182141Z","shell.execute_reply":"2022-02-12T18:21:26.196232Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Define the loss function and optimizer","metadata":{"execution":{"iopub.status.busy":"2022-02-12T15:22:32.341756Z","iopub.execute_input":"2022-02-12T15:22:32.342225Z","iopub.status.idle":"2022-02-12T15:22:32.362021Z","shell.execute_reply.started":"2022-02-12T15:22:32.342186Z","shell.execute_reply":"2022-02-12T15:22:32.361167Z"}}},{"cell_type":"code","source":"criterion = nn.CrossEntropyLoss() # applies log_softmax and then NLLLoss cost function\noptimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)\n\n#from torch.optim.lr_scheduler import ReduceLROnPlateau\n#lr_scheduler = ReduceLROnPlateau(optimizer, mode='min',factor=0.5, patience=20,verbose=1)","metadata":{"execution":{"iopub.status.busy":"2022-02-12T18:21:26.787494Z","iopub.execute_input":"2022-02-12T18:21:26.788012Z","iopub.status.idle":"2022-02-12T18:21:26.792304Z","shell.execute_reply.started":"2022-02-12T18:21:26.787971Z","shell.execute_reply":"2022-02-12T18:21:26.791589Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Train the model","metadata":{}},{"cell_type":"code","source":"from tqdm import tqdm\n\ntrain_losses = []\nval_losses = []\naccuracy_list = []\n\nfor epoch in range(num_epochs):\n    \n    # perform training on train set\n    model.train()\n    running_loss = 0\n    \n    for images, labels in tqdm(train_dataloader):\n        \n        # load to gpu\n        images = images.to(device)\n        labels = labels.to(device)\n        \n        # forward pass\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        running_loss += loss.item()\n        \n        # backprop and update model params\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n        \n    # calculate training loss for the epoch\n    train_losses.append(running_loss / len(train_dataloader))\n    \n    # calculate loss accuracy on validation set\n    model.eval()\n    running_loss = 0\n    num_correct = 0\n    num_predictions = 0\n    \n    with torch.no_grad():  \n        for images, labels in tqdm(val_dataloader):\n            \n            # load to gpu\n            images = images.to(device)\n            labels = labels.to(device)\n            \n            # forward pass\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            running_loss += loss.item()\n            \n            # calculate accuracy for batch\n            _, predicted = torch.max(outputs.data, 1)\n            num_correct += (predicted == labels).sum().item()\n            num_predictions += labels.size(0)\n            \n    # calculate val loss for epoch\n    val_losses.append(running_loss / len(val_dataloader))\n    \n    # calculate accuracy for epoch\n    accuracy = num_correct / num_predictions * 100\n    accuracy_list.append(accuracy)\n    \n    print(\"[Epoch: %d / %d],  [Train loss: %.4f],  [Test loss: %.4f],  [Acc: %.2f]\" \\\n          %(epoch+1, num_epochs, train_losses[-1], val_losses[-1], accuracy))\n        \n        ","metadata":{"execution":{"iopub.status.busy":"2022-02-12T18:21:27.805239Z","iopub.execute_input":"2022-02-12T18:21:27.805802Z","iopub.status.idle":"2022-02-12T18:33:54.478687Z","shell.execute_reply.started":"2022-02-12T18:21:27.805765Z","shell.execute_reply":"2022-02-12T18:33:54.477448Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}