<jupyter_start><jupyter_text>**“You don't have to be great to start, but you have to start to be great.”** ~ <NAME>Hey friends,this is a gentle introduction into Image Classification with the Python library fast.ai.What do you need to get started?- basic coding skills in Python- some familiarity with fast.ai (at least the first video of the course)[click if you don't know how to code at all](https://www.codecademy.com/learn/learn-python-3)[click if you know some coding, but not Python](https://developers.google.com/edu/python/)[click if you don't know the fast.ai course](https://course.fast.ai/videos/?lesson=1)Feel free to fork and tweak constants to improve accuracy. Just by doing so, I was able to get a score of 99.32% easily.Check out the [Q&A section](Questions-and-Answers) of this notebook. If you have any questions or certain explanations weren't completely clear, let me know in the comments. I'm happy to help everyone :)1. [Preparation](Preparation)2. [Training](Training)3. [Evaluation](Evaluation)4. [Prediction](Prediction)5. [Questions and Answers](Questions-and-Answers)[click for fast.ai documentation](https://docs.fast.ai) Preparationfirst of all make sure you enabled GPU so the CNN trains faster  Setup environment and import necessary modules<jupyter_code># the following three lines are suggested by the fast.ai course
%reload_ext autoreload
%autoreload 2
%matplotlib inline
# hide warnings
import warnings
warnings.simplefilter('ignore')
# the fast.ai library
from fastai import *
from fastai.vision import *

# to inspect the directory
import os
from pathlib import Path

# for data manipulation (in this Kernel mainly used to read .csv files)
import pandas as pd

# for numerical analysis
import numpy as np

# to display imags
from PIL import Image<jupyter_output><empty_output><jupyter_text>Inspect and understand input dataThe first step in most competitions is to check out the input data. Let's do this:<jupyter_code>INPUT = Path("../input/digit-recognizer")
os.listdir(INPUT)<jupyter_output><empty_output><jupyter_text>We found 3 interesting files:- sample_submission.csv- train.csv- test.csv'sample_submission.csv' will show us, how we have to structure our data at the end before we submit it to the competition. We will only need this file at the end.'train.csv' is a file that contains all necessary information for training the CNN'test.csv' is the file we later use to test how good our CNN isLet's look at 'train.csv' and 'test.csv' to see how the data looks<jupyter_code>train_df =  pd.read_csv(INPUT/"train.csv")
train_df.head(3)
test_df =  pd.read_csv(INPUT/"test.csv")
test_df.head(3)<jupyter_output><empty_output><jupyter_text>The data looks very interesting. Let's summarize what we got here:What we know about 'train.csv':- Each row is one image- The first row of each image is the label. It tells us which digit is shown.- The other 784 rows are the pixel for each digit and should be read like this`000 001 002 003 ... 026 027028 029 030 031 ... 054 055056 057 058 059 ... 082 083 |   |   |   |  ...  |   |728 729 730 731 ... 754 755756 757 758 759 ... 782 783`What we know about 'test.csv':- The structure is the same as in train.csv, but there are no labels because it's our task to predict the labelsTo read more about the data, read the ['Data' tab of the competition](https://www.kaggle.com/c/digit-recognizer/data) Getting the data into the right formatLooking at the [fast.ai documentation](https://docs.fast.ai/vision.data.htmlImageDataBunch) we can quickly see, that only accepts image files for Computer Vision. In this competition we were not offered images, but csv files where each cell is one pixel. If we want to use fast.ai we have to create images from the data we have.Fast.ai accepts image data in different formats. We will use the from_folder function of the ImageDataBunch class to load in the data. To do this we need all images in the following structure:`path\  train\    0\      ___.jpg      ___.jpg      ___.jpg    1\      ___.jpg      ___.jpg    2\      ...    3\      ...    ...  test\    ___.jpg    ___.jpg    ...`Let's first create the folder structure!(nice to know: the input folder of Kaggle Competitions is always read-only, so if we want to add data or create folders, we have to do so outside of the input folder)<jupyter_code>TRAIN = Path("../train")
TEST = Path("../test")
# Create training directory
for index in range(10):
    try:
        os.makedirs(TRAIN/str(index))
    except:
        pass
# Test whether creating the training directory was successful
sorted(os.listdir(TRAIN))
#Create test directory
try:
    os.makedirs(TEST)
except:
    pass<jupyter_output><empty_output><jupyter_text>Okay, all folders are created! The next step is to create the images inside of the folders from 'train.csv' and 'test.csv'. We will use the Image module from PIL to do this.we have to reshape each numpy array to have the desired dimensions of the image (28x28)`000 001 002 003 ... 026 027028 029 030 031 ... 054 055056 057 058 059 ... 082 083 |   |   |   |  ...  |   |728 729 730 731 ... 754 755756 757 758 759 ... 782 783`then we use the fromarray function to create a .jpg image from the numpy array and save it into the desired folder<jupyter_code># save training images
for index, row in train_df.iterrows():
    
    label,digit = row[0], row[1:]
    
    filepath = TRAIN/str(label)
    filename = f"{index}.jpg"
    
    digit = digit.values
    digit = digit.reshape(28,28)
    digit = digit.astype(np.uint8)
    
    img = Image.fromarray(digit)
    img.save(filepath/filename)
# save testing images
for index, digit in test_df.iterrows():

    filepath = TEST
    filename = f"{index}.jpg"
    
    digit = digit.values
    digit = digit.reshape(28,28)
    digit = digit.astype(np.uint8)
    
    img = Image.fromarray(digit)
    img.save(filepath/filename)<jupyter_output><empty_output><jupyter_text>Display some images<jupyter_code>def displayRandomImagesFromEveryFolder(directory=TRAIN, samplesPerDigit=5):

    fig = plt.figure(figsize=(5,10))
    
    for rowIndex in range(1, 10):
        subdirectory = str(rowIndex)
        path = directory/subdirectory
        images = os.listdir(path)
        for sampleIndex in range(1,samplesPerDigit+1):
            randomNumber = random.randint(0, len(images)-1)
            image = Image.open(path/images[randomNumber])
            ax = fig.add_subplot(10, 5, samplesPerDigit*rowIndex + sampleIndex)
            ax.axis("off")
            
            plt.imshow(image, cmap='gray')
            
    
    plt.show()
    
displayRandomImagesFromEveryFolder()<jupyter_output><empty_output><jupyter_text>Load data into DataBunch Now that we have the right folder structure and images inside of the folders we can continue.Before training a model in fast.ai, we have to load the data into a [DataBunch](https://docs.fast.ai/basic_data.htmlDataBunch), in this case, we use a ImageDataBunch, a special version of the DataBunch.Fast.ai offers different functions to create a DataBunch. We will use the from_folder method of the ImageDataBunch class to create the dataset.There are different hyperparameters we can tweak to make the model perform better:- [valid_pct](What-are-Train,-Test-and-Validation-datasets?)- [bs (batch size)](What-is-the-batch-size?)- [size](What-image-size-should-I-choose?)- [num_workers](What-is-multiprocessing?)- [ds_tfms](What-are-transforms-and-which-transforms-should-I-use?)<jupyter_code>flip_tfm = RandTransform(tfm=TfmPixel (flip_lr), kwargs={}, p=1, resolved={}, do_run=True, is_random=True, use_on_y=True)
random_number = str(3)
random_filename = os.listdir(TRAIN/random_number)[0]
img = open_image(TRAIN/random_number/random_filename)
display(img)
display(img.apply_tfms(flip_tfm))
# transforms
tfms = get_transforms(do_flip=False, max_zoom=1.2)
data = ImageDataBunch.from_folder(
    path = TRAIN,
    test = TEST,
    valid_pct = 0.2,
    bs = 32,
    size = 28,
    ds_tfms = tfms,
    #num_workers = 0
)<jupyter_output><empty_output><jupyter_text>Let's perform normalization to make the CNN converge faster. fast.ai already defined the variable mnist_stats, that we can use to normalize our data. Alternatively, we can call normalize() without any paramters. In this case fast.ai simply calculates the exact stats needed for the dataset at hand.<jupyter_code>mnist_stats
data.normalize(mnist_stats)
# all the classes in data
print(data.classes)<jupyter_output>['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']<jupyter_text>Training The next step is to select and create a CNN. In fast.ai creating a CNN is really easy. You just have to select one of the models from the [Computer Vision models zoo](https://docs.fast.ai/vision.models.htmlComputer-Vision-models-zoo). Parameters to tweak: dropout propability: ps (default: 0.5)<jupyter_code>learn = cnn_learner(data, models.resnet18, metrics=accuracy, model_dir="/tmp/models", callback_fns=ShowGraph)<jupyter_output>Downloading: "https://download.pytorch.org/models/resnet18-5c106cde.pth" to /tmp/.cache/torch/checkpoints/resnet18-5c106cde.pth
100%|██████████| 44.7M/44.7M [00:02<00:00, 19.4MB/s]<jupyter_text>Now it's time to train the neural network using the fit_one_cycle() function. Parameters to modify: the number of epochs(cyc_len) to train, the learning rate(max_lr) and the momentum (moms)<jupyter_code>learn.fit_one_cycle(cyc_len=5)<jupyter_output><empty_output><jupyter_text>EvaluationCreate a ClassificationInterpretation object to evaluate your results.<jupyter_code>interp = ClassificationInterpretation.from_learner(learn)<jupyter_output><empty_output><jupyter_text>Plot the 9 images with the highest loss. These are the images the CNN was most sure about, but still got wrong.<jupyter_code>interp.plot_top_losses(9, figsize=(7, 7))<jupyter_output><empty_output><jupyter_text>A good way to summarize the performance of a classification algorithm is to create a confusion matrix. Confusion Matricies are used to understand which classes are most easily confused. As labeled on the axis, the x-axis shows the predicted classes and the y-axis the actual classes. So if (4/7)=10 it means that it happened 10 times that the CNN predicted a 7 but in reality if was a 4.<jupyter_code>interp.plot_confusion_matrix()<jupyter_output><empty_output><jupyter_text>PredictionGet the predictions on the test set.learn.get_preds() returns a propability distribution over all possible classes for every given image.<jupyter_code>class_score, y = learn.get_preds(DatasetType.Test)<jupyter_output><empty_output><jupyter_text>That means that for every image in the test set it predicts how likely each class is. In this case the highest value is obviously 1<jupyter_code>probabilities = class_score[0].tolist()
[f"{index}: {probabilities[index]}" for index in range(len(probabilities))]<jupyter_output><empty_output><jupyter_text>But we want the CNN to predict only one class. The class with the highest probability.Argmax returns the index of the highest value.<jupyter_code>class_score = np.argmax(class_score, axis=1)<jupyter_output><empty_output><jupyter_text>This is exactly what we want.<jupyter_code>class_score[0].item()<jupyter_output><empty_output><jupyter_text>The last step is creating the submission file."sample_submission.csv" is showing us the desired format<jupyter_code>sample_submission =  pd.read_csv(INPUT/"sample_submission.csv")
display(sample_submission.head(2))
display(sample_submission.tail(2))<jupyter_output><empty_output><jupyter_text>Columns the submission file has to have:- ImageId: index in the test set, starting from 1, going up to 28000- Label: a prediction which digit the image shows<jupyter_code># remove file extension from filename
ImageId = [os.path.splitext(path)[0] for path in os.listdir(TEST)]
# typecast to int so that file can be sorted by ImageId
ImageId = [int(path) for path in ImageId]
# +1 because index starts at 1 in the submission file
ImageId = [ID+1 for ID in ImageId]
submission  = pd.DataFrame({
    "ImageId": ImageId,
    "Label": class_score
})
# submission.sort_values(by=["ImageId"], inplace = True)
submission.to_csv("submission.csv", index=False)
display(submission.head(3))
display(submission.tail(3))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09638652714192282, 'fraction_numerical': 0.026406147809025508, 'mean_word_length': 4.611467889908257, 'pattern_counts': {'":': 2, '<': 77, '<?xml version=': 0, '>': 76, 'https://': 9, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 5, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '19', 'dir': 'jupyter-structured-clean-dedup', 'id': '494905', 'license': 'MIT', 'n_tokens_mistral': 4010, 'n_tokens_neox': 3737, 'n_words': 1328, 'path': '2 digit recognizer/beginners-guide-to-mnist-with-fast-ai.ipynb', 'repo_name': 'MLVPRASAD/KaggleProjects', 'size': '12236'}