'<iframe width="560" height="315" src="https://www.youtube.com/embed/cJOtrHtzDSU?start=7193" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>') HTML(
fast.ai Chapter 6: Bear Classifier
In Chapter 6, we learned to train an image recognition model for multi-label classification. In this notebook, I will apply those concepts to the bear classifier from Chapter 2.
I’ll place the prompt of the “Further Research” section here and then answer each part.
Retrain the bear classifier using multi-label classification. See if you can make it work effectively with images that don’t contain any bears, including showing that information in the web application. Try an image with two kinds of bears. Check whether the accuracy on the single-label dataset is impacted using multi-label classification.
Here’s a video walkthrough of this notebook:
Setup
from fastai.vision.all import *
import fastai
import pandas as pd
fastai.__version__
'2.3.0'
from google.colab import drive
'/content/gdrive') drive.mount(
Mounted at /content/gdrive
I have three different CSVs with Google Image URLs, one each for black, brown and grizzly bears. The script below, taken from the book, creates a directory for each of the three types of bears in the bears
folder, and then downloads the corresponding bear type’s images into that directory.
= Path('/content/gdrive/MyDrive/fastai-course-v4/images/bears')
path = ['black', 'grizzly', 'teddy']
bear_types if not path.exists():
path.mkdir()for o in bear_types:
= path/o
dest =True)
dest.mkdir(exist_okf'/content/gdrive/MyDrive/fastai-course-v4/images/bears/{o}', url_file=Path(f'/content/gdrive/MyDrive/fastai-course-v4/download_{o}.csv')) download_images(
# confirm that `get_image_files` retrieves all images
= get_image_files(path)
fns fns
(#535) [Path('/content/gdrive/MyDrive/fastai-course-v4/images/bears/black/black_00000002.jpg'),Path('/content/gdrive/MyDrive/fastai-course-v4/images/bears/black/black_00000000.jpg'),Path('/content/gdrive/MyDrive/fastai-course-v4/images/bears/black/black_00000001.jpg'),Path('/content/gdrive/MyDrive/fastai-course-v4/images/bears/black/black_00000003.jpg'),Path('/content/gdrive/MyDrive/fastai-course-v4/images/bears/black/black_00000004.jpg'),Path('/content/gdrive/MyDrive/fastai-course-v4/images/bears/black/black_00000005.jpg'),Path('/content/gdrive/MyDrive/fastai-course-v4/images/bears/black/black_00000007.jpg'),Path('/content/gdrive/MyDrive/fastai-course-v4/images/bears/black/black_00000008.jpg'),Path('/content/gdrive/MyDrive/fastai-course-v4/images/bears/black/black_00000010.jpg'),Path('/content/gdrive/MyDrive/fastai-course-v4/images/bears/black/black_00000009.jpg')...]
# verify all images
= verify_images(fns)
failed failed
(#0) []
Since I may need to move files around if they are incorrectly labeled, I’m going to prepend the filenames with the corresponding bear type.
import os
for dir in os.listdir(path):
for f in os.listdir(path/dir):
/dir/f, path/dir/f'{dir}_{f}') os.rename(path
= get_image_files(path)
fns fns
(#723) [Path('/content/gdrive/MyDrive/fastai-course-v4/images/bears/black/black_00000002.jpg'),Path('/content/gdrive/MyDrive/fastai-course-v4/images/bears/black/black_00000000.jpg'),Path('/content/gdrive/MyDrive/fastai-course-v4/images/bears/black/black_00000001.jpg'),Path('/content/gdrive/MyDrive/fastai-course-v4/images/bears/black/black_00000003.jpg'),Path('/content/gdrive/MyDrive/fastai-course-v4/images/bears/black/black_00000004.jpg'),Path('/content/gdrive/MyDrive/fastai-course-v4/images/bears/black/black_00000005.jpg'),Path('/content/gdrive/MyDrive/fastai-course-v4/images/bears/black/black_00000006.jpg'),Path('/content/gdrive/MyDrive/fastai-course-v4/images/bears/black/black_00000007.jpg'),Path('/content/gdrive/MyDrive/fastai-course-v4/images/bears/black/black_00000008.jpg'),Path('/content/gdrive/MyDrive/fastai-course-v4/images/bears/black/black_00000010.jpg')...]
Single-Label Classifier
I’ll train the single-digit classifier as we did in Chapter 2.
# create DataBlock
= DataBlock(
bears =(ImageBlock, CategoryBlock),
blocks=get_image_files,
get_items=RandomSplitter(valid_pct=0.2, seed=42),
splitter=parent_label,
get_y=RandomResizedCrop(224, min_scale=0.5)) item_tfms
# create DataLoaders
= bears.dataloaders(path)
dls =4, nrows=1) dls.valid.show_batch(max_n
# verify train batch
=4, nrows=1) dls.train.show_batch(max_n
# first training
# use it to clean the data
= cnn_learner(dls, resnet18, metrics=error_rate)
learn 4) learn.fine_tune(
epoch | train_loss | valid_loss | error_rate | time |
---|---|---|---|---|
0 | 1.367019 | 0.252684 | 0.080645 | 00:05 |
epoch | train_loss | valid_loss | error_rate | time |
---|---|---|---|---|
0 | 0.179421 | 0.175091 | 0.056452 | 00:04 |
1 | 0.155954 | 0.165824 | 0.048387 | 00:04 |
2 | 0.119193 | 0.173681 | 0.056452 | 00:04 |
3 | 0.098313 | 0.170383 | 0.048387 | 00:04 |
# view confusion matrix
= ClassificationInterpretation.from_learner(learn)
interp interp.plot_confusion_matrix()
Initial training: Clean the Dataset
# plot highest loss images
5, nrows=1) interp.plot_top_losses(
Some of these images are infographics containing text, illustrations and other non-photographic bear data. I’ll delete those using the cleaner
from fastai.vision.widgets import *
# view highest loss images
# using ImageClassifierCleaner
= ImageClassifierCleaner(learn)
cleaner cleaner
# unlink images with "<Delete>" selected in the cleaner
for idx in cleaner.delete(): cleaner.fns[idx].unlink()
# move any images reclassified in the cleaner
for idx, cat in cleaner.change(): shutil.move(str(cleaner.fns[idx]), path/cat)
After a few rounds of quickly training the model and using the cleaner, I was able to remove or change a couple dozen of the images. I’ll use lr.find()
and re-train the model.
Second Training with Cleaned Dataset
= Path('/content/gdrive/MyDrive/fastai-course-v4/images/bears')
path
# create DataLoaders
= bears.dataloaders(path)
dls
#verify validation batch
=4, nrows=1) dls.valid.show_batch(max_n
#verify training batch
=4, nrows=1) dls.train.show_batch(max_n
# find learning rate
= cnn_learner(dls, resnet18, metrics=error_rate)
learn learn.lr_find()
SuggestedLRs(lr_min=0.012022644281387329, lr_steep=0.0005754399462603033)
# verify loss function
learn.loss_func
FlattenedLoss of CrossEntropyLoss()
# fit one cycle
= 1e-3
lr 5, lr) learn.fit_one_cycle(
epoch | train_loss | valid_loss | error_rate | time |
---|---|---|---|---|
0 | 1.405979 | 0.418305 | 0.145161 | 00:04 |
1 | 0.803087 | 0.214286 | 0.056452 | 00:04 |
2 | 0.557531 | 0.169275 | 0.048387 | 00:04 |
3 | 0.408410 | 0.163632 | 0.056452 | 00:04 |
4 | 0.321682 | 0.164792 | 0.040323 | 00:04 |
# view confusion matrix
= ClassificationInterpretation.from_learner(learn)
interp interp.plot_confusion_matrix()
# show results
learn.show_results()
::: {.cell 0=‘e’ 1=‘x’ 2=‘p’ 3=‘o’ 4=‘r’ 5=‘t’ 6=’ ’ 7=‘t’ 8=‘h’ 9=‘e’ 10=’ ’ 11=‘m’ 12=‘o’ 13=‘d’ 14=‘e’ 15=‘l’}
=path/'single_label_bear_classifier.pkl') learn.export(fname
:::
Multi-Label Classifier
There are three major differences between training a multi-label classification model and a single-label model on this dataset. I present them in a table here:
Classification Model Type | Dependent Variable | Loss Function | get_y function |
---|---|---|---|
Single-label | Decoded string | Cross Entropy (softmax) | parent_label |
Multi-label | One-hot Encoded List | Binary Cross Entropy (sigmoid with threshold) | [parent_label] |
# create helper function
def get_y(o): return [parent_label(o)]
# create DataBlock
= DataBlock(
bears =(ImageBlock, MultiCategoryBlock),
blocks=get_image_files,
get_items=RandomSplitter(valid_pct=0.2, seed=42),
splitter=get_y,
get_y=RandomResizedCrop(224, min_scale=0.5)) item_tfms
# view validation batch
= bears.dataloaders(path)
dls dls.show_batch()
# find learning rate
= cnn_learner(dls, resnet18, metrics=partial(accuracy_multi,thresh=0.95), loss_func=BCEWithLogitsLossFlat(thresh=0.5))
learn learn.lr_find()
Downloading: "https://download.pytorch.org/models/resnet18-5c106cde.pth" to /root/.cache/torch/hub/checkpoints/resnet18-5c106cde.pth
# verify loss function
learn.loss_func
FlattenedLoss of BCEWithLogitsLoss()
= 2e-2
lr 5, lr) learn.fit_one_cycle(
epoch | train_loss | valid_loss | accuracy_multi | time |
---|---|---|---|---|
0 | 0.478340 | 0.436599 | 0.937695 | 00:51 |
1 | 0.289231 | 0.642520 | 0.887850 | 00:03 |
2 | 0.203213 | 0.394335 | 0.897196 | 00:03 |
3 | 0.159622 | 0.155405 | 0.959502 | 00:02 |
4 | 0.132379 | 0.090879 | 0.965732 | 00:02 |
# verify results
learn.show_results()
::: {.cell 0=‘e’ 1=‘x’ 2=‘p’ 3=‘o’ 4=‘r’ 5=‘t’ 6=’ ’ 7=‘m’ 8=‘o’ 9=‘d’ 10=‘e’ 11=‘l’}
/'multi_label_bear_classifier.pkl') learn.export(path
:::
Model Inference
= Path('/content/gdrive/MyDrive/fastai-course-v4/images') path
Image with a Single Bear
# grizzly bear image
= PILImage.create(path/'test'/'grizzly_test_1.jpg')
img img
# load learners
= load_learner(path/'bears'/'single_label_bear_classifier.pkl')
single_learn_inf = load_learner(path/'bears'/'multi_label_bear_classifier.pkl') multi_learn_inf
# single label classification
single_learn_inf.predict(img)
('teddy', tensor(2), tensor([1.7475e-04, 3.7727e-04, 9.9945e-01]))
# multi label classification
multi_learn_inf.predict(img)
((#1) ['grizzly'],
tensor([False, True, False]),
tensor([6.3334e-05, 1.0000e+00, 1.4841e-04]))
Image with Two Bears
# image with grizzly and black bear
= PILImage.create(path/'test'/'.jpg')
img img
# single label classification
single_learn_inf.predict(img)
# multi label classification
multi_learn_inf.predict(img)
# image with grizzly and teddy bear
= PILImage.create(path/'test'/'.jpg')
img img
# single label classification
single_learn_inf.predict(img)
# multi label classification
multi_learn_inf.predict(img)
# image with black and teddy bear
= PILImage.create(path/'test'/'.jpg')
img img
# single label classification
single_learn_inf.predict(img)
# multi label classification
multi_learn_inf.predict(img)
Images without Bears
= Path('/content/gdrive/MyDrive/fastai-course-v4/images/')
path = PILImage.create(path/'test'/'computer.jpg')
img img
sigle_learn_inf.predict(img)
((#0) [], tensor([False, False, False]), tensor([0.1316, 0.1916, 0.0004]))
2].sum() single_learn_inf.predict(img)[
tensor(1.)
# set loss function threshold to 0.9
multi_learn_inf.predict(img)
((#0) [], tensor([False, False, False]), tensor([0.0275, 0.0196, 0.8457]))