from fastai.vision.all import *
import timm
import plotly.express as px
Introduction
The first attempt for developing this idea started as the homework assignment for lesson 1: Deep Learning 2019 (v3) fast.ai course.
For that homework I toke my domain expertise on telecommunication towers to build an image clasifier which could hopefuly recognize different tower components.
In may 2022 I was coursing live the 2022 version of the course, which is done in collaboration with The University of Queensland and its now called Practical Deep Learning for Coders.
I’m running this notebook on an old GTX-1070 NVIDIA GPU.
Import Libraries
Dataset
I curated an image dataset of multiple towers I worked with in the past several years. And choosed 514 images in 8 relatively “easy” to distinguish categories (components).
The dataset was stored in google drive and is shared here.
Local Dataset
For the 2022 course, thanks to the help of many great people in fastai forums, I was able to install fastai locally and to use my local GPU on WSL2.
= Path("photos") path
= path / 'train'
train_path = path / 'valid'
valid_path
= [label.parts[-1] for label in train_path.iterdir()]
labels = [len(list(each.iterdir())) for each in train_path.iterdir()]
train_quantity = [len(list(each.iterdir())) for each in valid_path.iterdir()]
valid_quantity = pd.DataFrame()
df
'label'] = labels * 2
df['set'] = ['train'] * 8 + ['valid'] * 8
df['quantity'] = train_quantity + valid_quantity df[
df
label | set | quantity | |
---|---|---|---|
0 | base_plate | train | 87 |
1 | grounding_bar | train | 52 |
2 | identification | train | 30 |
3 | ladder | train | 37 |
4 | light | train | 69 |
5 | lightning_rod | train | 33 |
6 | platform | train | 57 |
7 | transmission_lines | train | 29 |
8 | base_plate | valid | 29 |
9 | grounding_bar | valid | 15 |
10 | identification | valid | 8 |
11 | ladder | valid | 11 |
12 | light | valid | 22 |
13 | lightning_rod | valid | 10 |
14 | platform | valid | 16 |
15 | transmission_lines | valid | 9 |
= df.set == 'train'
is_train sum(), df[~is_train].quantity.sum() df[is_train].quantity.
(394, 120)
= px.bar(
fig ="set", y="quantity",
df, x='label', barmode='group',
color=400
height
) fig.show()
The Data
The data was hand picked from a huge tower photoset. To start, I choose these eight easy distinguishable components to be clasified:
- Base plate
- Grounding bar
- Identification
- Ladder
- Light
- Lightning rod
- Platform
- Transmission lines
There are two folders, one for the training (train) and the other for the validation set (valid).
print(path.ls())
print('*'*100)
/'train').ls() (path
[Path('photos/train'), Path('photos/valid')]
****************************************************************************************************
(#8) [Path('photos/train/base_plate'),Path('photos/train/grounding_bar'),Path('photos/train/identification'),Path('photos/train/ladder'),Path('photos/train/light'),Path('photos/train/lightning_rod'),Path('photos/train/platform'),Path('photos/train/transmission_lines')]
= get_image_files(path)
tower_parts_fns tower_parts_fns
(#514) [Path('photos/train/base_plate/Ac102-Corozopando-(64).jpg'),Path('photos/train/base_plate/Ac102-Corozopando-(75).jpg'),Path('photos/train/base_plate/camaguan-087.jpg'),Path('photos/train/base_plate/camaguan-098.jpg'),Path('photos/train/base_plate/cantv el yoco 015.JPG'),Path('photos/train/base_plate/cantv-capanaparo-011.jpg'),Path('photos/train/base_plate/cantv-cinaruco-018.jpg'),Path('photos/train/base_plate/cantv-cinaruco-025.jpg'),Path('photos/train/base_plate/cartanal-(7).jpg'),Path('photos/train/base_plate/CHUSPITA-II-AC-72-MTS-002.jpg')...]
= verify_images(tower_parts_fns)
failed print(failed)
[]
DataLoaders
= DataBlock(
tower_parts =(ImageBlock, CategoryBlock),
blocks=get_image_files,
get_items=GrandparentSplitter(train_name='train', valid_name='valid'),
splitter=parent_label,
get_y=Resize(224)
item_tfms )
= tower_parts.dataloaders(path) dls
%%time
for _ in dls.train: pass
CPU times: user 449 ms, sys: 506 ms, total: 955 ms
Wall time: 20.4 s
=16, nrows=4, figsize=(10,10)) dls.train.show_batch(max_n
Learner
= vision_learner(dls, resnet34, metrics=error_rate) learn
fastai’s lr_find
%%time
learn.lr_find()
CPU times: user 24.4 s, sys: 8.72 s, total: 33.2 s
Wall time: 5min 38s
SuggestedLRs(valley=0.0014454397605732083)
dls.num_workers
1
I experimented by setting dls.num_workers = 4
and it didn’t make any difference in the time it takes to run lr_find()
, even though that, by watching at the progress bar, it seemed that the bottleneck was in pre-processing the batch. Not in GPU.
len(dls.train), len(dls.train.get_idxs())
(6, 394)
len(dls.valid), len(dls.valid.get_idxs())
(2, 120)
dls.drop_last
True
Training
%%time
3) learn.fine_tune(
epoch | train_loss | valid_loss | error_rate | time |
---|---|---|---|---|
0 | 2.751579 | 0.932106 | 0.316667 | 00:44 |
epoch | train_loss | valid_loss | error_rate | time |
---|---|---|---|---|
0 | 1.000472 | 0.463910 | 0.166667 | 00:43 |
1 | 0.632878 | 0.272529 | 0.091667 | 00:43 |
2 | 0.429861 | 0.208375 | 0.050000 | 00:43 |
CPU times: user 8.31 s, sys: 4.01 s, total: 12.3 s
Wall time: 2min 55s
= ClassificationInterpretation.from_learner(learn)
interp =(8,8)) interp.plot_confusion_matrix(figsize
9) interp.plot_top_losses(
'models/tower_parts_model')
learn.export("exported_model_from_fastai", with_opt=False) learn.save(
Path('models/exported_model_from_fastai.pth')
Conclusions
- I took about 500 pictures and 3 epochs to finetune a small pre-trained model to make it recognize 8 components with an error rate of about 6%.
learn.lr_find()
took about 5 minutes to run, more that the fine tuning.- There are some categories that normally appear in one picture at the same time. I solved a classification problem to simplify, but maybe the actual problem should be a multi-class classificacion.