# Lesson 1 - What's your pet¶

Welcome to lesson 1! For those of you who are using a Jupyter Notebook for the first time, you can learn about this useful tool in a tutorial we prepared specially for you; click File->Open now and click notebook_tutorial.ipynb.

In this lesson we will build our first image classifier from scratch, and see if we can achieve world-class results. Let's dive in!

Every notebook starts with the following three lines; they ensure that any edits to libraries you make are reloaded here automatically, and also that any charts or images displayed are shown in this notebook.

In [ ]:
%reload_ext autoreload
%matplotlib inline


We import all the necessary packages. We are going to work with the fastai V1 library which sits on top of Pytorch 1.0. The fastai library provides many useful functions that enable us to quickly and easily build neural networks and train our models.

In [ ]:
from fastai import *
from fastai.vision import *


## Looking at the data¶

We are going to use the Oxford-IIIT Pet Dataset by O. M. Parkhi et al., 2012 which features 12 cat breeds and 25 dogs breeds. Our model will need to learn to differentiate between these 37 distinct categories. According to their paper, the best accuracy they could get in 2012 was 59.21%, using a complex model that was specific to pet detection, with separate "Image", "Head", and "Body" models for the pet photos. Let's see how accurate we can be using deep learning!

We are going to use the untar_data function to which we must pass a URL as an argument and which will download and extract the data.

In [ ]:
help(untar_data)

In [ ]:
# DEBUG
URLs.PETS

In [ ]:
path = untar_data(URLs.PETS)
path

In [ ]:
path.ls()

In [ ]:
path_anno = path / 'annotations'
path_img = path / 'images'


The first thing we do when we approach a problem is to take a look at the data. We always need to understand very well what the problem is and what the data looks like before we can figure out how to solve it. Taking a look at the data means understanding how the data directories are structured, what the labels are and what some sample images look like.

The main difference between the handling of image classification datasets is the way labels are stored. In this particular dataset, labels are stored in the filenames themselves. We will need to extract them to be able to classify the images into the correct categories. Fortunately, the fastai library has a handy function made exactly for this, ImageDataBunch.from_name_re gets the labels from the filenames using a regular expression.

In [ ]:
# DEBUG
path_anno, path_img

In [ ]:
fnames = get_image_files(path_img)
fnames[:5]

In [ ]:
np.random.seed(2)
pat = r'/([^/]+)_\d+.jpg$'  In [ ]: data = ImageDataBunch.from_name_re(path_img, fnames, pat, ds_tfms=get_transforms(), size=224) data.normalize(imagenet_stats)  In [ ]: data.show_batch(rows=3, figsize=(7,6))  In [ ]: print(data.classes) len(data.classes), data.c # the number of classes  ## Training: resnet34¶ Now we will start training our model. We will use a convolutional neural network backbone and a fully connected head with a single hidden layer as a classifier. Don't know what these things mean? Not to worry, we will dive deeper in the coming lessons. For the moment you need to know that we are building a model which will take images as input and will output the predicted probability for each of the categories (in this case, it will have 37 ouptuts). We will train for 5 epochs (5 cycles through all our data). In [ ]: learn = ConvLearner(data, models.resnet34, metrics=error_rate)  In [ ]: learn.fit_one_cycle(4)  In [ ]: learn.save('stage-1')  ## Results¶ Let's see what results we have got. We will first see which were the categories that the model most confused with one another. We will try to see if what the model predicted was reasonable or not. In this case the mistakes look reasonable (none of the mistakes seems obviously naive). This is an indicator that our classifier is working correctly. Furthermore, when we plot the confusion matrix, we can see that the distribution is heavily skewed: the model makes the same mistakes over and over again but it rarely confuses other categories. This suggests that it just finds it difficult to distinguish some specific categories between each other; this is normal behaviour. In [ ]: interp = ClassificationInterpretation.from_learner(learn)  In [ ]: interp.plot_top_losses(9, figsize=(15,11))  In [ ]: doc(interp.plot_top_losses)  In [ ]: interp.plot_confusion_matrix(figsize=(12,12), dpi=60)  In [ ]: interp.most_confused(min_val=2)  ## Unfreezing, fine-tuning, and learning rates¶ Since our model is working as we expect it to, we will unfreeze our model and train some more. In [ ]: learn.unfreeze()  In [ ]: learn.fit_one_cycle(1)  In [ ]: learn.load('stage-1')  In [ ]: learn.lr_find()  Fix the previous error by updating fastai library to the latest version today In [ ]: learn.lr_find()  In [ ]: learn.recorder.plot()  In [ ]: learn.unfreeze() learn.fit_one_cycle(2, max_lr=slice(1e-6, 1e-4))  In [ ]: doc(learn.fit_one_cycle)  In [ ]: learn.recorder.plot()  That's a pretty accurate model! ## Training: resnet50¶ Now we will train in the same way as before but with one caveat: instead of using resnet34 as our backbone we will use resnet50 (resnet34 is a 34 layer residual network while resnet50 has 50 layers. Later in the course you can learn the details in the resnet paper). Basically, resnet50 usually performs better because it is a deeper network with more parameters. Let's see if we can achieve a higher performance here. In [ ]: data = ImageDataBunch.from_name_re(path_img, fnames, pat, ds_tfms=get_transforms(), size=299, bs=48) data.normalize(imagenet_stats)  In [ ]: learn = ConvLearner(data, models.resnet50, metrics=error_rate)  In [34]: learn.fit_one_cycle(5)  Total time: 17:07 epoch train loss valid loss error_rate 1 0.641380 0.236811 0.068556 (03:56) 2 0.341766 0.211440 0.069875 (03:18) 3 0.242888 0.207498 0.069875 (03:17) 4 0.159888 0.167932 0.054054 (03:17) 5 0.107927 0.163711 0.050758 (03:17)  In [35]: learn.save('stage-1-50')  It's astonishing that it's possible to recognize pet breeds so accurately! Let's see if full fine-tuning helps: In [36]: learn.unfreeze() learn.fit_one_cycle(1, max_lr=slice(1e-6,1e-4))  Total time: 04:24 epoch train loss valid loss error_rate 1 0.103458 0.155929 0.051417 (04:24)  In this case it doesn't, so let's go back to our previous model. In [37]: learn.load('stage-1-50')  In [38]: interp = ClassificationInterpretation.from_learner(learn)  In [39]: interp.most_confused(min_val=2)  Out[39]: [('Egyptian_Mau', 'Bengal', 7), ('american_bulldog', 'staffordshire_bull_terrier', 4), ('staffordshire_bull_terrier', 'american_bulldog', 4), ('boxer', 'american_bulldog', 4), ('Russian_Blue', 'British_Shorthair', 4), ('american_pit_bull_terrier', 'staffordshire_bull_terrier', 4), ('Ragdoll', 'Birman', 3)] ## Other data formats¶ In [40]: path = untar_data(URLs.MNIST_SAMPLE) path  Downloading http://files.fast.ai/data/examples/mnist_sample  Out[40]: PosixPath('/home/ubuntu/.fastai/data/mnist_sample') In [42]: tfms = get_transforms(do_flip=False) data = ImageDataBunch.from_folder(path, ds_tfms=tfms, size=26)  In [43]: data.show_batch(rows=3, figsize=(5,5))  In [44]: learn = ConvLearner(data, models.resnet18, metrics=accuracy) learn.fit(2)  Total time: 00:23 epoch train loss valid loss accuracy 1 0.114837 0.023115 0.993621 (00:12) 2 0.067166 0.014504 0.995584 (00:10)  In [45]: df = pd.read_csv(path / 'labels.csv') df.head()  Out[45]: name label 0 train/3/7463.png 0 1 train/3/21102.png 0 2 train/3/31559.png 0 3 train/3/46882.png 0 4 train/3/26209.png 0 In [46]: data = ImageDataBunch.from_csv(path, ds_tfms=tfms, size=28)  In [47]: data.show_batch(rows=3, figsize=(5,5)) data.classes  Out[47]: [0, 1] In [48]: data = ImageDataBunch.from_df(path, df, ds_tfms=tfms, size=24) data.classes  Out[48]: [0, 1] In [49]: fn_paths = [path / name for name in df['name']]; fn_paths[:2]  Out[49]: [PosixPath('/home/ubuntu/.fastai/data/mnist_sample/train/3/7463.png'), PosixPath('/home/ubuntu/.fastai/data/mnist_sample/train/3/21102.png')] In [50]: pat = r"/(\d)/\d+\.png$"
data = ImageDataBunch.from_name_re(path, fn_paths, pat=pat, ds_tfms=tfms, size=24)
data.classes

Out[50]:
['3', '7']
In [51]:
data = ImageDataBunch.from_name_func(path, fn_paths, ds_tfms=tfms, size=24,
label_func = lambda x: '3' if '/3/' in str(x) else '7')
data.classes

Out[51]:
['3', '7']
In [52]:
labels = [('3' if '/3/' in str(x) else '7') for x in fn_paths]
labels[:5]

Out[52]:
['3', '3', '3', '3', '3']
In [53]:
data = ImageDataBunch.from_lists(path, fn_paths, labels=labels, ds_tfms=tfms, size=24)
data.classes

Out[53]:
['3', '7']
In [ ]: