# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 2
#     language: python
#     name: python2
# ---

# # Differentiate weed from plant seedling using Convolutional Neural Network

# @author: <NAME>

# In this notebook we will be building a convolutional neural network to identify if a given picture is of a weed or one of the 12 species of crop seedlings of interest. 

# # Dataset

# The data can be found here - https://www.kaggle.com/c/plant-seedlings-classification/data .
# Train folder consists of 12 image folders for 12 species of crop seedlings respectively. 

# ## 1. Analyze Data

import numpy as np
import pandas as pd
import urllib3
import cv2
import mxnet as mx
from mxnet import ndarray as nd, autograd, gluon
from mxnet.gluon import nn
import urllib
from PIL import Image
from sklearn.cross_validation import train_test_split
import zipfile
import glob
import os
import matplotlib.pyplot as plt
# %matplotlib inline

# Arranging data and opening an image file
data_dir = '../kaggle_plantSeedling/'
train_dir = os.path.join(data_dir, 'train')
test_dir = os.path.join(data_dir, 'test')
train_image_files=glob.glob(os.path.join(train_dir, '*/*.png'))
Image.open(train_image_files[0])

Image.open(train_image_files[4749])

len(train_image_files)

# These are the 12 species of interest - 

# 'Black-grass', 'Charlock', 'Cleavers', 'Common Chickweed', 'Common wheat',
# 'Fat Hen', 'Loose Silky-bent', 'Maize', 'Scentless Mayweed','Shepherds Purse', 'Small-flowered Cranesbill', 'Sugar beet'          

# Lets look at the shape of some of the image files
tmp = np.array(Image.open(train_image_files[0]))
tmp.shape

tmp1 = np.array(Image.open(train_image_files[4749]))
tmp1.shape

# +
#Store species information in variables
species = ['Black-grass', 'Charlock', 'Cleavers', 'Common Chickweed', 'Common wheat',
           'Fat Hen', 'Loose Silky-bent', 'Maize', 'Scentless Mayweed',
           'Shepherds Purse', 'Small-flowered Cranesbill', 'Sugar beet']
num_species = len(species)

num_species
# -

# Arrange data in the format we need and store the train data in a data frame
train = []
for species_id, sp in enumerate(species):
    for file in os.listdir(os.path.join(train_dir, sp)):
        train.append(['train/{}/{}'.format(sp, file), file, species_id, sp])
train_df = pd.DataFrame(train, columns=['filepath', 'file', 'species_id', 'species'])
print(train_df.shape)


train_df.head() 

img_paths_and_label = train_df[['filepath', 'species_id']].values

img_paths_and_label[:10]

# +
# The images are of different sizes. So lets resize all images to 128, 128
# MXNet expects input dimensions as (batch_size, channel, height, width), so we need to rearrange some axes.
X = []
Y = []

NEW_SZ = 128, 128

for image_file, label in img_paths_and_label:
    img = Image.open(image_file).convert('RGB').resize(NEW_SZ)
    img = np.array(img)
    img = np.swapaxes(img, 0, 2)
    img = np.swapaxes(img, 1, 2)
    
    X.append(img)
    Y.append(label)
    
npX = np.stack(X)
npY = np.array(Y)
# -

#Lets look at the shape after swapping axes
npX.shape, npY.shape ### batch_size, channel, height, width

# Save the array for future use
import pickle
pickle.dump(npX, open("dataX.npy", "wb"))
pickle.dump(npY, open("dataY.npy", "wb"))

#Lest split the data into train and validation
train_X, validation_X, train_Y, validation_Y = train_test_split(npX, npY, test_size=0.2, random_state=1001)

# ## 2. Build the Model
#
# Simple 3 Layer CNN - more info available here in the [Gluon tutorial](https://gluon.mxnet.io/chapter04_convolutional-neural-networks/cnn-scratch.html)

# +
num_classes = 12
        
net = gluon.nn.Sequential()
with net.name_scope():
    
    # CNN Layer
    net.add(gluon.nn.Conv2D(channels=16, kernel_size=3, activation='relu'))
    net.add(gluon.nn.MaxPool2D(pool_size=2, strides=2))
    net.add(gluon.nn.Conv2D(channels=16, kernel_size=3, activation='relu'))
    net.add(gluon.nn.MaxPool2D(pool_size=2, strides=2))
    net.add(gluon.nn.Conv2D(channels=32, kernel_size=3, activation='relu'))
    net.add(gluon.nn.MaxPool2D(pool_size=2, strides=2))
    net.add(gluon.nn.Conv2D(channels=32, kernel_size=3, activation='relu'))
    net.add(gluon.nn.MaxPool2D(pool_size=2, strides=2))

    # MLP Layer
    net.add(gluon.nn.Flatten())
    net.add(gluon.nn.Dense(64, activation="relu"))
    net.add(gluon.nn.Dense(32, activation="relu"))
    net.add(gluon.nn.Dense(num_classes))
# -

net

# ## 3. Train

batch_size = 64
train_iter = mx.io.NDArrayIter(data=train_X, label=train_Y, batch_size=batch_size, shuffle=True)
validation_iter = mx.io.NDArrayIter(data=validation_X, label=validation_Y, batch_size=batch_size)

# +
ctx = mx.cpu(0)

net.collect_params().initialize(mx.init.Xavier(magnitude=2.24), ctx=ctx)
loss_function = gluon.loss.SoftmaxCrossEntropyLoss()
optimizer = mx.gluon.Trainer(net.collect_params(), 'adam', {'learning_rate': 1E-3})


# -

def evaluate_accuracy(net, data_iterator):
    acc = mx.metric.Accuracy()
    while True:
        try:
            batch = next(data_iterator)
        except StopIteration:
            train_iter.reset()
            break        
        data = batch.data[0].as_in_context(ctx)
        label = batch.label[0].as_in_context(ctx)
        output = net(data)
        predictions = nd.argmax(output, axis=1)
        acc.update(preds=predictions, labels=label)
    return acc.get()[1]


# +
smoothing_constant = .01
epochs = 25
ctx = ctx
loss_vals = []
train_acc = []
val_acc = []
train_iter.reset()
validation_iter.reset()

for e in range(epochs):
    i = 0
    while True:
        try:
            batch = next(train_iter)
        except StopIteration:
            train_iter.reset()
            break
        data = batch.data[0].as_in_context(ctx)
        label = batch.label[0].as_in_context(ctx)
        #print data.shape, label.shape
        with autograd.record(train_mode=True):
            output = net(data)
            loss = loss_function(output, label)
        loss.backward()
        optimizer.step(data.shape[0])

        curr_loss = nd.mean(loss).asscalar()
        moving_loss = (curr_loss if ((i == 0) and (e == 0)) 
                       else (1 - smoothing_constant) * moving_loss + (smoothing_constant) * curr_loss)
        loss_vals.append(moving_loss)
        i +=1
        if i % 10 == 0: #every 10 batch
            print moving_loss
        
    train_accuracy = evaluate_accuracy(net, train_iter)
    test_accuracy = evaluate_accuracy(net, validation_iter)
    
    # reset iterators
    train_iter.reset()
    validation_iter.reset()
    
    print("Epoch %s. Loss: %s, Train_acc %s, Test_acc %s" % (e, moving_loss, train_accuracy, test_accuracy))
    train_acc.append(train_accuracy)
    val_acc.append(test_accuracy)
# -

plt.figure()
plt.title('Training Loss')
plt.ylabel('Categorical Cross-Entropy')
plt.xlabel('Epochs')
plt.plot(loss_vals)

plt.figure()
plt.title('Training and Validation Metrics')
plt.ylabel('Accuracy')
plt.xlabel('Epochs')
plt.plot(train_acc)
plt.plot(val_acc)
plt.legend(['Training Accuracy', 'Validation Accuracy'])

# The accuracy is hovering around 70% which is not great. 
#
# # Things to try next
#
# 1. More CNN filters in each layer
# 2. Image augementation to create a larger training set
# 3. Hyperparameter optimization



StarcoderdataJupyterScriptsDedupFiltered