Cactus Identification with Pytorch
Author: https://www.kaggle.com/nelsongriffiths
From: https://www.kaggle.com/nelsongriffiths/cactus-identification-with-pytorch
License: Apache 2.0
In [1]:
import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
from sklearn.model_selection import train_test_split
import os
import torch
import torch.nn as nn
import torch.nn.functional as F
from torchvision import datasets
import torchvision.transforms as transforms
from torch.utils.data.sampler import SubsetRandomSampler
import cv2
import matplotlib.pyplot as plt
train_on_gpu = torch.cuda.is_available()
if not train_on_gpu:
    print('CUDA is not available.  Training on CPU ...')
else:
    print('CUDA is available!  Training on GPU ...')
CUDA is available!  Training on GPU ...
Data Prep
First, I am going to import our data sources and take a look at what we are working with. We have a csv file that contains our target variable and a folder with our cactus images.
In [2]:
df = pd.read_csv('../input/train.csv')
df.head()
Out[2]:
| id | has_cactus | |
|---|---|---|
| 0 | 0004be2cfeaba1c0361d39e2b000257b.jpg | 1 | 
| --- | --- | --- | 
| 1 | 000c8a36845c0208e833c79c1bffedd1.jpg | 1 | 
| --- | --- | --- | 
| 2 | 000d1e9a533f62e55c289303b072733d.jpg | 1 | 
| --- | --- | --- | 
| 3 | 0011485b40695e9138e92d0b3fb55128.jpg | 1 | 
| --- | --- | --- | 
| 4 | 0014d7a11e90b62848904c1418fc8cf2.jpg | 1 | 
| --- | --- | --- | 
In [3]:
df['has_cactus'].value_counts(normalize=True)
Out[3]:
1    0.750629
0    0.249371
Name: has_cactus, dtype: float64
In [4]:
train_df, val_df = train_test_split(df, stratify = df.has_cactus, test_size=.2)
In [5]:
#Checking that validation set has same proportions as original training data
val_df['has_cactus'].value_counts(normalize=True)
Out[5]:
1    0.750571
0    0.249429
Name: has_cactus, dtype: float64
In [6]:
#Build a class for our data to put our images and target variables into our pytorch dataloader
# https://stanford.edu/~shervine/blog/pytorch-how-to-generate-data-parallel
class DataSet(torch.utils.data.Dataset):
    def __init__(self, labels, data_directory, transform=None):
        super().__init__()
        self.labels = labels.values
        self.data_dir = data_directory
        self.transform=transform
    def __len__(self):
        return len(self.labels)
    def __getitem__(self, index):
        name, label = self.labels[index]
        img_path = os.path.join(self.data_dir, name)
        img = cv2.imread(img_path)
        if self.transform is not None:
            img = self.transform(img)
        return img, label
In [7]:
batch_size = 32
# Transform training data with random flips and normalize it to prepare it for dataloader
train_transforms = transforms.Compose([transforms.ToPILImage(),
                                       transforms.RandomHorizontalFlip(),
                                       transforms.ToTensor(),
                                      transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])])
val_transforms = transforms.Compose([transforms.ToPILImage(),
                                     transforms.ToTensor(),
                                     transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])])
train_data = DataSet(train_df,'../input/train/train', transform = train_transforms)
val_data = DataSet(val_df,'../input/train/train', transform = val_transforms)
train_data_loader = torch.utils.data.DataLoader(train_data, batch_size = batch_size, shuffle = True)
val_data_loader = torch.utils.data.DataLoader(train_data, batch_size = batch_size, shuffle = True)
In [8]:
#Checking what our cactus look like
fig,ax = plt.subplots(1,3,figsize=(15,5))
for i, idx in enumerate(train_df[train_df['has_cactus']==1]['id'][0:3]):
  path = os.path.join('../input/train/train',idx)
  ax[i].imshow(cv2.imread(path))
 In [9]:
In [9]:
#Building a CNN from scratch
class Net(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(3, 16, 3, padding = 1)
        self.conv2 = nn.Conv2d(16, 32, 3, padding = 1)
        self.conv3 = nn.Conv2d(32, 64, 3, padding = 1)
        self.conv4 = nn.Conv2d(64, 128, 3, padding = 1)
        self.pool = nn.MaxPool2d(2, 2)
        self.fc1 = nn.Linear(2*16*16, 256)
        self.fc2 = nn.Linear(256, 128)
        self.fc3 = nn.Linear(128, 64)
        self.fc4 = nn.Linear(64, 2)
        self.dropout = nn.Dropout(p = .25)
    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = self.pool(F.relu(self.conv3(x)))
        x = self.pool(F.relu(self.conv4(x)))
        x = x.view(-1, 2*16*16)
        x = self.dropout(x)
        x = F.relu(self.fc1(x))
        x = self.dropout(x)
        x = F.relu(self.fc2(x))
        x = self.dropout(x)
        x = F.relu(self.fc3(x))
        x = self.dropout(x)
        x = F.relu(self.fc4(x))
        return x
In [10]:
model = Net()
if train_on_gpu:
    model = model.cuda()
epochs = 30
learning_rate = .003
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adamax(model.parameters(), lr=learning_rate)
In [11]:
#Training and validation for model
best_loss = np.Inf
best_model = Net()
if train_on_gpu:
    best_model.cuda()
for epoch in range(1, epochs+1):
    train_loss = 0
    val_loss = 0
    model.train()
    for images, labels in train_data_loader:
        if train_on_gpu:
            images = images.cuda()
            labels = labels.cuda()
        optimizer.zero_grad()
        out = model(images)
        loss = criterion(out, labels)
        loss.backward()
        optimizer.step()
        train_loss += loss.item()
        #print('Loss: {}'.format(loss.item()))
    model.eval()
    for images, labels in val_data_loader:
        if train_on_gpu:
            images = images.cuda()
            labels = labels.cuda()
        out = model(images)
        loss = criterion(out, labels)
        val_loss += loss.item()
    train_loss = train_loss/len(train_data_loader.dataset)
    val_loss = val_loss/len(val_data_loader.dataset)  
    print('Epoch: {}  \tTraining Loss: {:.6f}  \tValidation Loss: {:.6f}'.format(epoch, train_loss, val_loss))
    #Saving the weights of the best model according to validation score
    if val_loss < best_loss:
        print('Improved Model Score - Updating Best Model Parameters...')
        best_model.load_state_dict(model.state_dict())
Epoch: 1    Training Loss: 0.007002     Validation Loss: 0.003353
Improved Model Score - Updating Best Model Parameters...
Epoch: 2    Training Loss: 0.003053     Validation Loss: 0.002155
Improved Model Score - Updating Best Model Parameters...
Epoch: 3    Training Loss: 0.002027     Validation Loss: 0.001604
Improved Model Score - Updating Best Model Parameters...
Epoch: 4    Training Loss: 0.001602     Validation Loss: 0.001545
Improved Model Score - Updating Best Model Parameters...
Epoch: 5    Training Loss: 0.001286     Validation Loss: 0.001005
Improved Model Score - Updating Best Model Parameters...
Epoch: 6    Training Loss: 0.001038     Validation Loss: 0.001039
Improved Model Score - Updating Best Model Parameters...
Epoch: 7    Training Loss: 0.000975     Validation Loss: 0.001050
Improved Model Score - Updating Best Model Parameters...
Epoch: 8    Training Loss: 0.000826     Validation Loss: 0.000844
Improved Model Score - Updating Best Model Parameters...
Epoch: 9    Training Loss: 0.000768     Validation Loss: 0.000574
Improved Model Score - Updating Best Model Parameters...
Epoch: 10   Training Loss: 0.000666     Validation Loss: 0.000445
Improved Model Score - Updating Best Model Parameters...
Epoch: 11   Training Loss: 0.000604     Validation Loss: 0.000394
Improved Model Score - Updating Best Model Parameters...
Epoch: 12   Training Loss: 0.000607     Validation Loss: 0.000716
Improved Model Score - Updating Best Model Parameters...
Epoch: 13   Training Loss: 0.000459     Validation Loss: 0.000416
Improved Model Score - Updating Best Model Parameters...
Epoch: 14   Training Loss: 0.000544     Validation Loss: 0.000305
Improved Model Score - Updating Best Model Parameters...
Epoch: 15   Training Loss: 0.000390     Validation Loss: 0.000316
Improved Model Score - Updating Best Model Parameters...
Epoch: 16   Training Loss: 0.000345     Validation Loss: 0.000183
Improved Model Score - Updating Best Model Parameters...
Epoch: 17   Training Loss: 0.000329     Validation Loss: 0.000158
Improved Model Score - Updating Best Model Parameters...
Epoch: 18   Training Loss: 0.000285     Validation Loss: 0.000158
Improved Model Score - Updating Best Model Parameters...
Epoch: 19   Training Loss: 0.000244     Validation Loss: 0.000087
Improved Model Score - Updating Best Model Parameters...
Epoch: 20   Training Loss: 0.000260     Validation Loss: 0.000235
Improved Model Score - Updating Best Model Parameters...
Epoch: 21   Training Loss: 0.000208     Validation Loss: 0.000061
Improved Model Score - Updating Best Model Parameters...
Epoch: 22   Training Loss: 0.000214     Validation Loss: 0.000726
Improved Model Score - Updating Best Model Parameters...
Epoch: 23   Training Loss: 0.000208     Validation Loss: 0.000097
Improved Model Score - Updating Best Model Parameters...
Epoch: 24   Training Loss: 0.000172     Validation Loss: 0.000169
Improved Model Score - Updating Best Model Parameters...
Epoch: 25   Training Loss: 0.000192     Validation Loss: 0.000245
Improved Model Score - Updating Best Model Parameters...
Epoch: 26   Training Loss: 0.000222     Validation Loss: 0.000091
Improved Model Score - Updating Best Model Parameters...
Epoch: 27   Training Loss: 0.000112     Validation Loss: 0.000061
Improved Model Score - Updating Best Model Parameters...
Epoch: 28   Training Loss: 0.000119     Validation Loss: 0.000155
Improved Model Score - Updating Best Model Parameters...
Epoch: 29   Training Loss: 0.000139     Validation Loss: 0.000022
Improved Model Score - Updating Best Model Parameters...
Epoch: 30   Training Loss: 0.000132     Validation Loss: 0.000035
Improved Model Score - Updating Best Model Parameters...
In [12]:
#Check model accuracy
best_model.eval()
with torch.no_grad():
    correct = 0
    total = 0
    for images, labels in val_data_loader:
        if train_on_gpu:
            images = images.cuda()
            labels = labels.cuda()
        outputs = best_model(images)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()
    print('Test Accuracy: {} %'.format(100 * correct / total))
Test Accuracy: 99.96428571428571 %

