from fastai.vision.all import *
import timm
import plotly.express as pxIntroduction
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 = Path("photos")train_path = path / 'train'
valid_path = path / 'valid'
labels = [label.parts[-1] for label in train_path.iterdir()]
train_quantity = [len(list(each.iterdir())) for each in train_path.iterdir()]
valid_quantity = [len(list(each.iterdir())) for each in valid_path.iterdir()]
df = pd.DataFrame()
df['label'] = labels * 2
df['set'] = ['train'] * 8 + ['valid'] * 8
df['quantity'] = train_quantity + valid_quantitydf| 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 |
is_train = df.set == 'train'
df[is_train].quantity.sum(), df[~is_train].quantity.sum()(394, 120)
fig = px.bar(
df, x="set", y="quantity",
color='label', barmode='group',
height=400
)
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)
(path/'train').ls()[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')]
tower_parts_fns = get_image_files(path)
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')...]
failed = verify_images(tower_parts_fns)
print(failed)[]
DataLoaders
tower_parts = DataBlock(
blocks=(ImageBlock, CategoryBlock),
get_items=get_image_files,
splitter=GrandparentSplitter(train_name='train', valid_name='valid'),
get_y=parent_label,
item_tfms=Resize(224)
)dls = tower_parts.dataloaders(path)%%time
for _ in dls.train: passCPU times: user 449 ms, sys: 506 ms, total: 955 ms
Wall time: 20.4 s
dls.train.show_batch(max_n=16, nrows=4, figsize=(10,10))
Learner
learn = vision_learner(dls, resnet34, metrics=error_rate)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_workers1
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_lastTrue
Training
%%time
learn.fine_tune(3)| 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
interp = ClassificationInterpretation.from_learner(learn)
interp.plot_confusion_matrix(figsize=(8,8))
interp.plot_top_losses(9)
learn.export('models/tower_parts_model')
learn.save("exported_model_from_fastai", with_opt=False)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.