Spaces:
Sleeping
Sleeping
Commit
·
2ed7425
1
Parent(s):
57a938e
init
Browse files- app.py +10 -10
- class_names.txt +32 -101
- helper/__pycache__/data_setup.cpython-310.pyc +0 -0
- helper/__pycache__/engine.cpython-310.pyc +0 -0
- helper/__pycache__/helper_functions.cpython-310.pyc +0 -0
- helper/__pycache__/predictions.cpython-310.pyc +0 -0
- helper/data_setup.py +66 -0
- helper/engine.py +195 -0
- helper/helper_functions.py +294 -0
- helper/model_builder.py +56 -0
- helper/predictions.py +83 -0
- helper/train.py +62 -0
- helper/utils.py +35 -0
- model.py +53 -20
- model_food101_20_percent.pth → model_1.pth +2 -2
app.py
CHANGED
@@ -3,7 +3,7 @@ import gradio as gr
|
|
3 |
import os
|
4 |
import torch
|
5 |
|
6 |
-
from model import
|
7 |
from timeit import default_timer as timer
|
8 |
from typing import Tuple, Dict
|
9 |
|
@@ -14,14 +14,14 @@ with open("class_names.txt", "r") as f: # reading them in from class_names.txt
|
|
14 |
### 2. Model and transforms preparation ###
|
15 |
|
16 |
# Create model
|
17 |
-
|
18 |
-
num_classes=
|
19 |
)
|
20 |
|
21 |
|
22 |
-
|
23 |
torch.load(
|
24 |
-
f="
|
25 |
map_location=torch.device("cpu"), # load to CPU
|
26 |
)
|
27 |
)
|
@@ -38,13 +38,13 @@ def predict(img) -> Tuple[Dict, float]:
|
|
38 |
start_time = timer()
|
39 |
|
40 |
# Transform the target image and add a batch dimension
|
41 |
-
img =
|
42 |
|
43 |
# Put model into evaluation mode and turn on inference mode
|
44 |
-
|
45 |
with torch.inference_mode():
|
46 |
# Pass the transformed image through the model and turn the prediction logits into prediction probabilities
|
47 |
-
pred_probs = torch.softmax(
|
48 |
|
49 |
# Create a prediction label and prediction probability dictionary for each prediction class (this is the required format for Gradio's output parameter)
|
50 |
pred_labels_and_probs = {class_names[i]: float(pred_probs[0][i]) for i in range(len(class_names))}
|
@@ -58,8 +58,8 @@ def predict(img) -> Tuple[Dict, float]:
|
|
58 |
### 4. Gradio app ###
|
59 |
|
60 |
# Create title, description and article strings
|
61 |
-
title = "
|
62 |
-
description = "A
|
63 |
article = ""
|
64 |
|
65 |
# Create examples list from "examples/" directory
|
|
|
3 |
import os
|
4 |
import torch
|
5 |
|
6 |
+
from model import create_model
|
7 |
from timeit import default_timer as timer
|
8 |
from typing import Tuple, Dict
|
9 |
|
|
|
14 |
### 2. Model and transforms preparation ###
|
15 |
|
16 |
# Create model
|
17 |
+
model_created, model_transforms = create_model(
|
18 |
+
num_classes=len(class_names),
|
19 |
)
|
20 |
|
21 |
|
22 |
+
model_created.load_state_dict(
|
23 |
torch.load(
|
24 |
+
f="model_1.pth",
|
25 |
map_location=torch.device("cpu"), # load to CPU
|
26 |
)
|
27 |
)
|
|
|
38 |
start_time = timer()
|
39 |
|
40 |
# Transform the target image and add a batch dimension
|
41 |
+
img = model_transforms(img).unsqueeze(0)
|
42 |
|
43 |
# Put model into evaluation mode and turn on inference mode
|
44 |
+
model_created.eval()
|
45 |
with torch.inference_mode():
|
46 |
# Pass the transformed image through the model and turn the prediction logits into prediction probabilities
|
47 |
+
pred_probs = torch.softmax(model_created(img), dim=1)
|
48 |
|
49 |
# Create a prediction label and prediction probability dictionary for each prediction class (this is the required format for Gradio's output parameter)
|
50 |
pred_labels_and_probs = {class_names[i]: float(pred_probs[0][i]) for i in range(len(class_names))}
|
|
|
58 |
### 4. Gradio app ###
|
59 |
|
60 |
# Create title, description and article strings
|
61 |
+
title = "World Puzzle Solver"
|
62 |
+
description = "A World Puzzle Solver app that uses a PyTorch model to predict the letters in a target image."
|
63 |
article = ""
|
64 |
|
65 |
# Create examples list from "examples/" directory
|
class_names.txt
CHANGED
@@ -1,101 +1,32 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
dumplings
|
34 |
-
edamame
|
35 |
-
eggs_benedict
|
36 |
-
escargots
|
37 |
-
falafel
|
38 |
-
filet_mignon
|
39 |
-
fish_and_chips
|
40 |
-
foie_gras
|
41 |
-
french_fries
|
42 |
-
french_onion_soup
|
43 |
-
french_toast
|
44 |
-
fried_calamari
|
45 |
-
fried_rice
|
46 |
-
frozen_yogurt
|
47 |
-
garlic_bread
|
48 |
-
gnocchi
|
49 |
-
greek_salad
|
50 |
-
grilled_cheese_sandwich
|
51 |
-
grilled_salmon
|
52 |
-
guacamole
|
53 |
-
gyoza
|
54 |
-
hamburger
|
55 |
-
hot_and_sour_soup
|
56 |
-
hot_dog
|
57 |
-
huevos_rancheros
|
58 |
-
hummus
|
59 |
-
ice_cream
|
60 |
-
lasagna
|
61 |
-
lobster_bisque
|
62 |
-
lobster_roll_sandwich
|
63 |
-
macaroni_and_cheese
|
64 |
-
macarons
|
65 |
-
miso_soup
|
66 |
-
mussels
|
67 |
-
nachos
|
68 |
-
omelette
|
69 |
-
onion_rings
|
70 |
-
oysters
|
71 |
-
pad_thai
|
72 |
-
paella
|
73 |
-
pancakes
|
74 |
-
panna_cotta
|
75 |
-
peking_duck
|
76 |
-
pho
|
77 |
-
pizza
|
78 |
-
pork_chop
|
79 |
-
poutine
|
80 |
-
prime_rib
|
81 |
-
pulled_pork_sandwich
|
82 |
-
ramen
|
83 |
-
ravioli
|
84 |
-
red_velvet_cake
|
85 |
-
risotto
|
86 |
-
samosa
|
87 |
-
sashimi
|
88 |
-
scallops
|
89 |
-
seaweed_salad
|
90 |
-
shrimp_and_grits
|
91 |
-
spaghetti_bolognese
|
92 |
-
spaghetti_carbonara
|
93 |
-
spring_rolls
|
94 |
-
steak
|
95 |
-
strawberry_shortcake
|
96 |
-
sushi
|
97 |
-
tacos
|
98 |
-
takoyaki
|
99 |
-
tiramisu
|
100 |
-
tuna_tartare
|
101 |
-
waffles
|
|
|
1 |
+
A
|
2 |
+
B
|
3 |
+
C
|
4 |
+
D
|
5 |
+
E
|
6 |
+
F
|
7 |
+
G
|
8 |
+
H
|
9 |
+
I
|
10 |
+
J
|
11 |
+
K
|
12 |
+
L
|
13 |
+
M
|
14 |
+
N
|
15 |
+
O
|
16 |
+
P
|
17 |
+
Q
|
18 |
+
R
|
19 |
+
S
|
20 |
+
T
|
21 |
+
U
|
22 |
+
V
|
23 |
+
W
|
24 |
+
X
|
25 |
+
Y
|
26 |
+
Z
|
27 |
+
Á
|
28 |
+
É
|
29 |
+
Í
|
30 |
+
Ñ
|
31 |
+
Ó
|
32 |
+
Ú
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
helper/__pycache__/data_setup.cpython-310.pyc
ADDED
Binary file (1.97 kB). View file
|
|
helper/__pycache__/engine.cpython-310.pyc
ADDED
Binary file (4.95 kB). View file
|
|
helper/__pycache__/helper_functions.cpython-310.pyc
ADDED
Binary file (8.32 kB). View file
|
|
helper/__pycache__/predictions.cpython-310.pyc
ADDED
Binary file (2.3 kB). View file
|
|
helper/data_setup.py
ADDED
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Contains functionality for creating PyTorch DataLoaders for
|
3 |
+
image classification data.
|
4 |
+
"""
|
5 |
+
import os
|
6 |
+
|
7 |
+
from torchvision import datasets, transforms
|
8 |
+
from torch.utils.data import DataLoader
|
9 |
+
|
10 |
+
NUM_WORKERS = os.cpu_count()
|
11 |
+
|
12 |
+
def create_dataloaders(
|
13 |
+
train_dir: str,
|
14 |
+
test_dir: str,
|
15 |
+
train_transform: transforms.Compose,
|
16 |
+
test_transform: transforms.Compose,
|
17 |
+
batch_size: int,
|
18 |
+
num_workers: int=NUM_WORKERS
|
19 |
+
):
|
20 |
+
"""Creates training and testing DataLoaders.
|
21 |
+
|
22 |
+
Takes in a training directory and testing directory path and turns
|
23 |
+
them into PyTorch Datasets and then into PyTorch DataLoaders.
|
24 |
+
|
25 |
+
Args:
|
26 |
+
train_dir: Path to training directory.
|
27 |
+
test_dir: Path to testing directory.
|
28 |
+
transform: torchvision transforms to perform on training and testing data.
|
29 |
+
batch_size: Number of samples per batch in each of the DataLoaders.
|
30 |
+
num_workers: An integer for number of workers per DataLoader.
|
31 |
+
|
32 |
+
Returns:
|
33 |
+
A tuple of (train_dataloader, test_dataloader, class_names).
|
34 |
+
Where class_names is a list of the target classes.
|
35 |
+
Example usage:
|
36 |
+
train_dataloader, test_dataloader, class_names = \
|
37 |
+
= create_dataloaders(train_dir=path/to/train_dir,
|
38 |
+
test_dir=path/to/test_dir,
|
39 |
+
transform=some_transform,
|
40 |
+
batch_size=32,
|
41 |
+
num_workers=4)
|
42 |
+
"""
|
43 |
+
# Use ImageFolder to create dataset(s)
|
44 |
+
train_data = datasets.ImageFolder(train_dir, transform=train_transform)
|
45 |
+
test_data = datasets.ImageFolder(test_dir, transform=test_transform)
|
46 |
+
|
47 |
+
# Get class names
|
48 |
+
class_names = train_data.classes
|
49 |
+
|
50 |
+
# Turn images into data loaders
|
51 |
+
train_dataloader = DataLoader(
|
52 |
+
train_data,
|
53 |
+
batch_size=batch_size,
|
54 |
+
shuffle=True,
|
55 |
+
num_workers=num_workers,
|
56 |
+
pin_memory=True,
|
57 |
+
)
|
58 |
+
test_dataloader = DataLoader(
|
59 |
+
test_data,
|
60 |
+
batch_size=batch_size,
|
61 |
+
shuffle=False,
|
62 |
+
num_workers=num_workers,
|
63 |
+
pin_memory=True,
|
64 |
+
)
|
65 |
+
|
66 |
+
return train_dataloader, test_dataloader, class_names
|
helper/engine.py
ADDED
@@ -0,0 +1,195 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Contains functions for training and testing a PyTorch model.
|
3 |
+
"""
|
4 |
+
import torch
|
5 |
+
|
6 |
+
from tqdm.auto import tqdm
|
7 |
+
from typing import Dict, List, Tuple
|
8 |
+
|
9 |
+
def train_step(model: torch.nn.Module,
|
10 |
+
dataloader: torch.utils.data.DataLoader,
|
11 |
+
loss_fn: torch.nn.Module,
|
12 |
+
optimizer: torch.optim.Optimizer,
|
13 |
+
device: torch.device) -> Tuple[float, float]:
|
14 |
+
"""Trains a PyTorch model for a single epoch.
|
15 |
+
|
16 |
+
Turns a target PyTorch model to training mode and then
|
17 |
+
runs through all of the required training steps (forward
|
18 |
+
pass, loss calculation, optimizer step).
|
19 |
+
|
20 |
+
Args:
|
21 |
+
model: A PyTorch model to be trained.
|
22 |
+
dataloader: A DataLoader instance for the model to be trained on.
|
23 |
+
loss_fn: A PyTorch loss function to minimize.
|
24 |
+
optimizer: A PyTorch optimizer to help minimize the loss function.
|
25 |
+
device: A target device to compute on (e.g. "cuda" or "cpu").
|
26 |
+
|
27 |
+
Returns:
|
28 |
+
A tuple of training loss and training accuracy metrics.
|
29 |
+
In the form (train_loss, train_accuracy). For example:
|
30 |
+
|
31 |
+
(0.1112, 0.8743)
|
32 |
+
"""
|
33 |
+
# Put model in train mode
|
34 |
+
model.train()
|
35 |
+
|
36 |
+
# Setup train loss and train accuracy values
|
37 |
+
train_loss, train_acc = 0, 0
|
38 |
+
|
39 |
+
# Loop through data loader data batches
|
40 |
+
for batch, (X, y) in enumerate(dataloader):
|
41 |
+
# Send data to target device
|
42 |
+
X, y = X.to(device), y.to(device)
|
43 |
+
|
44 |
+
# 1. Forward pass
|
45 |
+
y_pred = model(X)
|
46 |
+
|
47 |
+
# 2. Calculate and accumulate loss
|
48 |
+
loss = loss_fn(y_pred, y)
|
49 |
+
train_loss += loss.item()
|
50 |
+
|
51 |
+
# 3. Optimizer zero grad
|
52 |
+
optimizer.zero_grad()
|
53 |
+
|
54 |
+
# 4. Loss backward
|
55 |
+
loss.backward()
|
56 |
+
|
57 |
+
# 5. Optimizer step
|
58 |
+
optimizer.step()
|
59 |
+
|
60 |
+
# Calculate and accumulate accuracy metric across all batches
|
61 |
+
y_pred_class = torch.argmax(torch.softmax(y_pred, dim=1), dim=1)
|
62 |
+
train_acc += (y_pred_class == y).sum().item()/len(y_pred)
|
63 |
+
|
64 |
+
# Adjust metrics to get average loss and accuracy per batch
|
65 |
+
train_loss = train_loss / len(dataloader)
|
66 |
+
train_acc = train_acc / len(dataloader)
|
67 |
+
return train_loss, train_acc
|
68 |
+
|
69 |
+
def test_step(model: torch.nn.Module,
|
70 |
+
dataloader: torch.utils.data.DataLoader,
|
71 |
+
loss_fn: torch.nn.Module,
|
72 |
+
device: torch.device) -> Tuple[float, float]:
|
73 |
+
"""Tests a PyTorch model for a single epoch.
|
74 |
+
|
75 |
+
Turns a target PyTorch model to "eval" mode and then performs
|
76 |
+
a forward pass on a testing dataset.
|
77 |
+
|
78 |
+
Args:
|
79 |
+
model: A PyTorch model to be tested.
|
80 |
+
dataloader: A DataLoader instance for the model to be tested on.
|
81 |
+
loss_fn: A PyTorch loss function to calculate loss on the test data.
|
82 |
+
device: A target device to compute on (e.g. "cuda" or "cpu").
|
83 |
+
|
84 |
+
Returns:
|
85 |
+
A tuple of testing loss and testing accuracy metrics.
|
86 |
+
In the form (test_loss, test_accuracy). For example:
|
87 |
+
|
88 |
+
(0.0223, 0.8985)
|
89 |
+
"""
|
90 |
+
# Put model in eval mode
|
91 |
+
model.eval()
|
92 |
+
|
93 |
+
# Setup test loss and test accuracy values
|
94 |
+
test_loss, test_acc = 0, 0
|
95 |
+
|
96 |
+
# Turn on inference context manager
|
97 |
+
with torch.inference_mode():
|
98 |
+
# Loop through DataLoader batches
|
99 |
+
for batch, (X, y) in enumerate(dataloader):
|
100 |
+
# Send data to target device
|
101 |
+
X, y = X.to(device), y.to(device)
|
102 |
+
|
103 |
+
# 1. Forward pass
|
104 |
+
test_pred_logits = model(X)
|
105 |
+
|
106 |
+
# 2. Calculate and accumulate loss
|
107 |
+
loss = loss_fn(test_pred_logits, y)
|
108 |
+
test_loss += loss.item()
|
109 |
+
|
110 |
+
# Calculate and accumulate accuracy
|
111 |
+
test_pred_labels = test_pred_logits.argmax(dim=1)
|
112 |
+
test_acc += ((test_pred_labels == y).sum().item()/len(test_pred_labels))
|
113 |
+
|
114 |
+
# Adjust metrics to get average loss and accuracy per batch
|
115 |
+
test_loss = test_loss / len(dataloader)
|
116 |
+
test_acc = test_acc / len(dataloader)
|
117 |
+
return test_loss, test_acc
|
118 |
+
|
119 |
+
def train(model: torch.nn.Module,
|
120 |
+
train_dataloader: torch.utils.data.DataLoader,
|
121 |
+
test_dataloader: torch.utils.data.DataLoader,
|
122 |
+
optimizer: torch.optim.Optimizer,
|
123 |
+
loss_fn: torch.nn.Module,
|
124 |
+
epochs: int,
|
125 |
+
device: torch.device) -> Dict[str, List]:
|
126 |
+
"""Trains and tests a PyTorch model.
|
127 |
+
|
128 |
+
Passes a target PyTorch models through train_step() and test_step()
|
129 |
+
functions for a number of epochs, training and testing the model
|
130 |
+
in the same epoch loop.
|
131 |
+
|
132 |
+
Calculates, prints and stores evaluation metrics throughout.
|
133 |
+
|
134 |
+
Args:
|
135 |
+
model: A PyTorch model to be trained and tested.
|
136 |
+
train_dataloader: A DataLoader instance for the model to be trained on.
|
137 |
+
test_dataloader: A DataLoader instance for the model to be tested on.
|
138 |
+
optimizer: A PyTorch optimizer to help minimize the loss function.
|
139 |
+
loss_fn: A PyTorch loss function to calculate loss on both datasets.
|
140 |
+
epochs: An integer indicating how many epochs to train for.
|
141 |
+
device: A target device to compute on (e.g. "cuda" or "cpu").
|
142 |
+
|
143 |
+
Returns:
|
144 |
+
A dictionary of training and testing loss as well as training and
|
145 |
+
testing accuracy metrics. Each metric has a value in a list for
|
146 |
+
each epoch.
|
147 |
+
In the form: {train_loss: [...],
|
148 |
+
train_acc: [...],
|
149 |
+
test_loss: [...],
|
150 |
+
test_acc: [...]}
|
151 |
+
For example if training for epochs=2:
|
152 |
+
{train_loss: [2.0616, 1.0537],
|
153 |
+
train_acc: [0.3945, 0.3945],
|
154 |
+
test_loss: [1.2641, 1.5706],
|
155 |
+
test_acc: [0.3400, 0.2973]}
|
156 |
+
"""
|
157 |
+
# Create empty results dictionary
|
158 |
+
results = {"train_loss": [],
|
159 |
+
"train_acc": [],
|
160 |
+
"test_loss": [],
|
161 |
+
"test_acc": []
|
162 |
+
}
|
163 |
+
|
164 |
+
# Make sure model on target device
|
165 |
+
model.to(device)
|
166 |
+
|
167 |
+
# Loop through training and testing steps for a number of epochs
|
168 |
+
for epoch in tqdm(range(epochs)):
|
169 |
+
train_loss, train_acc = train_step(model=model,
|
170 |
+
dataloader=train_dataloader,
|
171 |
+
loss_fn=loss_fn,
|
172 |
+
optimizer=optimizer,
|
173 |
+
device=device)
|
174 |
+
test_loss, test_acc = test_step(model=model,
|
175 |
+
dataloader=test_dataloader,
|
176 |
+
loss_fn=loss_fn,
|
177 |
+
device=device)
|
178 |
+
|
179 |
+
# Print out what's happening
|
180 |
+
print(
|
181 |
+
f"Epoch: {epoch+1} | "
|
182 |
+
f"train_loss: {train_loss:.4f} | "
|
183 |
+
f"train_acc: {train_acc:.4f} | "
|
184 |
+
f"test_loss: {test_loss:.4f} | "
|
185 |
+
f"test_acc: {test_acc:.4f}"
|
186 |
+
)
|
187 |
+
|
188 |
+
# Update results dictionary
|
189 |
+
results["train_loss"].append(train_loss)
|
190 |
+
results["train_acc"].append(train_acc)
|
191 |
+
results["test_loss"].append(test_loss)
|
192 |
+
results["test_acc"].append(test_acc)
|
193 |
+
|
194 |
+
# Return the filled results at the end of the epochs
|
195 |
+
return results
|
helper/helper_functions.py
ADDED
@@ -0,0 +1,294 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
A series of helper functions used throughout the course.
|
3 |
+
|
4 |
+
If a function gets defined once and could be used over and over, it'll go in here.
|
5 |
+
"""
|
6 |
+
import torch
|
7 |
+
import matplotlib.pyplot as plt
|
8 |
+
import numpy as np
|
9 |
+
|
10 |
+
from torch import nn
|
11 |
+
|
12 |
+
import os
|
13 |
+
import zipfile
|
14 |
+
|
15 |
+
from pathlib import Path
|
16 |
+
|
17 |
+
import requests
|
18 |
+
|
19 |
+
# Walk through an image classification directory and find out how many files (images)
|
20 |
+
# are in each subdirectory.
|
21 |
+
import os
|
22 |
+
|
23 |
+
def walk_through_dir(dir_path):
|
24 |
+
"""
|
25 |
+
Walks through dir_path returning its contents.
|
26 |
+
Args:
|
27 |
+
dir_path (str): target directory
|
28 |
+
|
29 |
+
Returns:
|
30 |
+
A print out of:
|
31 |
+
number of subdiretories in dir_path
|
32 |
+
number of images (files) in each subdirectory
|
33 |
+
name of each subdirectory
|
34 |
+
"""
|
35 |
+
for dirpath, dirnames, filenames in os.walk(dir_path):
|
36 |
+
print(f"There are {len(dirnames)} directories and {len(filenames)} images in '{dirpath}'.")
|
37 |
+
|
38 |
+
def plot_decision_boundary(model: torch.nn.Module, X: torch.Tensor, y: torch.Tensor):
|
39 |
+
"""Plots decision boundaries of model predicting on X in comparison to y.
|
40 |
+
|
41 |
+
Source - https://madewithml.com/courses/foundations/neural-networks/ (with modifications)
|
42 |
+
"""
|
43 |
+
# Put everything to CPU (works better with NumPy + Matplotlib)
|
44 |
+
model.to("cpu")
|
45 |
+
X, y = X.to("cpu"), y.to("cpu")
|
46 |
+
|
47 |
+
# Setup prediction boundaries and grid
|
48 |
+
x_min, x_max = X[:, 0].min() - 0.1, X[:, 0].max() + 0.1
|
49 |
+
y_min, y_max = X[:, 1].min() - 0.1, X[:, 1].max() + 0.1
|
50 |
+
xx, yy = np.meshgrid(np.linspace(x_min, x_max, 101), np.linspace(y_min, y_max, 101))
|
51 |
+
|
52 |
+
# Make features
|
53 |
+
X_to_pred_on = torch.from_numpy(np.column_stack((xx.ravel(), yy.ravel()))).float()
|
54 |
+
|
55 |
+
# Make predictions
|
56 |
+
model.eval()
|
57 |
+
with torch.inference_mode():
|
58 |
+
y_logits = model(X_to_pred_on)
|
59 |
+
|
60 |
+
# Test for multi-class or binary and adjust logits to prediction labels
|
61 |
+
if len(torch.unique(y)) > 2:
|
62 |
+
y_pred = torch.softmax(y_logits, dim=1).argmax(dim=1) # mutli-class
|
63 |
+
else:
|
64 |
+
y_pred = torch.round(torch.sigmoid(y_logits)) # binary
|
65 |
+
|
66 |
+
# Reshape preds and plot
|
67 |
+
y_pred = y_pred.reshape(xx.shape).detach().numpy()
|
68 |
+
plt.contourf(xx, yy, y_pred, cmap=plt.cm.RdYlBu, alpha=0.7)
|
69 |
+
plt.scatter(X[:, 0], X[:, 1], c=y, s=40, cmap=plt.cm.RdYlBu)
|
70 |
+
plt.xlim(xx.min(), xx.max())
|
71 |
+
plt.ylim(yy.min(), yy.max())
|
72 |
+
|
73 |
+
|
74 |
+
# Plot linear data or training and test and predictions (optional)
|
75 |
+
def plot_predictions(
|
76 |
+
train_data, train_labels, test_data, test_labels, predictions=None
|
77 |
+
):
|
78 |
+
"""
|
79 |
+
Plots linear training data and test data and compares predictions.
|
80 |
+
"""
|
81 |
+
plt.figure(figsize=(10, 7))
|
82 |
+
|
83 |
+
# Plot training data in blue
|
84 |
+
plt.scatter(train_data, train_labels, c="b", s=4, label="Training data")
|
85 |
+
|
86 |
+
# Plot test data in green
|
87 |
+
plt.scatter(test_data, test_labels, c="g", s=4, label="Testing data")
|
88 |
+
|
89 |
+
if predictions is not None:
|
90 |
+
# Plot the predictions in red (predictions were made on the test data)
|
91 |
+
plt.scatter(test_data, predictions, c="r", s=4, label="Predictions")
|
92 |
+
|
93 |
+
# Show the legend
|
94 |
+
plt.legend(prop={"size": 14})
|
95 |
+
|
96 |
+
|
97 |
+
# Calculate accuracy (a classification metric)
|
98 |
+
def accuracy_fn(y_true, y_pred):
|
99 |
+
"""Calculates accuracy between truth labels and predictions.
|
100 |
+
|
101 |
+
Args:
|
102 |
+
y_true (torch.Tensor): Truth labels for predictions.
|
103 |
+
y_pred (torch.Tensor): Predictions to be compared to predictions.
|
104 |
+
|
105 |
+
Returns:
|
106 |
+
[torch.float]: Accuracy value between y_true and y_pred, e.g. 78.45
|
107 |
+
"""
|
108 |
+
correct = torch.eq(y_true, y_pred).sum().item()
|
109 |
+
acc = (correct / len(y_pred)) * 100
|
110 |
+
return acc
|
111 |
+
|
112 |
+
|
113 |
+
def print_train_time(start, end, device=None):
|
114 |
+
"""Prints difference between start and end time.
|
115 |
+
|
116 |
+
Args:
|
117 |
+
start (float): Start time of computation (preferred in timeit format).
|
118 |
+
end (float): End time of computation.
|
119 |
+
device ([type], optional): Device that compute is running on. Defaults to None.
|
120 |
+
|
121 |
+
Returns:
|
122 |
+
float: time between start and end in seconds (higher is longer).
|
123 |
+
"""
|
124 |
+
total_time = end - start
|
125 |
+
print(f"\nTrain time on {device}: {total_time:.3f} seconds")
|
126 |
+
return total_time
|
127 |
+
|
128 |
+
|
129 |
+
# Plot loss curves of a model
|
130 |
+
def plot_loss_curves(results):
|
131 |
+
"""Plots training curves of a results dictionary.
|
132 |
+
|
133 |
+
Args:
|
134 |
+
results (dict): dictionary containing list of values, e.g.
|
135 |
+
{"train_loss": [...],
|
136 |
+
"train_acc": [...],
|
137 |
+
"test_loss": [...],
|
138 |
+
"test_acc": [...]}
|
139 |
+
"""
|
140 |
+
loss = results["train_loss"]
|
141 |
+
test_loss = results["test_loss"]
|
142 |
+
|
143 |
+
accuracy = results["train_acc"]
|
144 |
+
test_accuracy = results["test_acc"]
|
145 |
+
|
146 |
+
epochs = range(len(results["train_loss"]))
|
147 |
+
|
148 |
+
plt.figure(figsize=(15, 7))
|
149 |
+
|
150 |
+
# Plot loss
|
151 |
+
plt.subplot(1, 2, 1)
|
152 |
+
plt.plot(epochs, loss, label="train_loss")
|
153 |
+
plt.plot(epochs, test_loss, label="test_loss")
|
154 |
+
plt.title("Loss")
|
155 |
+
plt.xlabel("Epochs")
|
156 |
+
plt.legend()
|
157 |
+
|
158 |
+
# Plot accuracy
|
159 |
+
plt.subplot(1, 2, 2)
|
160 |
+
plt.plot(epochs, accuracy, label="train_accuracy")
|
161 |
+
plt.plot(epochs, test_accuracy, label="test_accuracy")
|
162 |
+
plt.title("Accuracy")
|
163 |
+
plt.xlabel("Epochs")
|
164 |
+
plt.legend()
|
165 |
+
|
166 |
+
|
167 |
+
# Pred and plot image function from notebook 04
|
168 |
+
# See creation: https://www.learnpytorch.io/04_pytorch_custom_datasets/#113-putting-custom-image-prediction-together-building-a-function
|
169 |
+
from typing import List
|
170 |
+
import torchvision
|
171 |
+
|
172 |
+
|
173 |
+
def pred_and_plot_image(
|
174 |
+
model: torch.nn.Module,
|
175 |
+
image_path: str,
|
176 |
+
class_names: List[str] = None,
|
177 |
+
transform=None,
|
178 |
+
device: torch.device = "cuda" if torch.cuda.is_available() else "cpu",
|
179 |
+
):
|
180 |
+
"""Makes a prediction on a target image with a trained model and plots the image.
|
181 |
+
|
182 |
+
Args:
|
183 |
+
model (torch.nn.Module): trained PyTorch image classification model.
|
184 |
+
image_path (str): filepath to target image.
|
185 |
+
class_names (List[str], optional): different class names for target image. Defaults to None.
|
186 |
+
transform (_type_, optional): transform of target image. Defaults to None.
|
187 |
+
device (torch.device, optional): target device to compute on. Defaults to "cuda" if torch.cuda.is_available() else "cpu".
|
188 |
+
|
189 |
+
Returns:
|
190 |
+
Matplotlib plot of target image and model prediction as title.
|
191 |
+
|
192 |
+
Example usage:
|
193 |
+
pred_and_plot_image(model=model,
|
194 |
+
image="some_image.jpeg",
|
195 |
+
class_names=["class_1", "class_2", "class_3"],
|
196 |
+
transform=torchvision.transforms.ToTensor(),
|
197 |
+
device=device)
|
198 |
+
"""
|
199 |
+
|
200 |
+
# 1. Load in image and convert the tensor values to float32
|
201 |
+
target_image = torchvision.io.read_image(str(image_path)).type(torch.float32)
|
202 |
+
|
203 |
+
# 2. Divide the image pixel values by 255 to get them between [0, 1]
|
204 |
+
target_image = target_image / 255.0
|
205 |
+
|
206 |
+
# 3. Transform if necessary
|
207 |
+
if transform:
|
208 |
+
target_image = transform(target_image)
|
209 |
+
|
210 |
+
# 4. Make sure the model is on the target device
|
211 |
+
model.to(device)
|
212 |
+
|
213 |
+
# 5. Turn on model evaluation mode and inference mode
|
214 |
+
model.eval()
|
215 |
+
with torch.inference_mode():
|
216 |
+
# Add an extra dimension to the image
|
217 |
+
target_image = target_image.unsqueeze(dim=0)
|
218 |
+
|
219 |
+
# Make a prediction on image with an extra dimension and send it to the target device
|
220 |
+
target_image_pred = model(target_image.to(device))
|
221 |
+
|
222 |
+
# 6. Convert logits -> prediction probabilities (using torch.softmax() for multi-class classification)
|
223 |
+
target_image_pred_probs = torch.softmax(target_image_pred, dim=1)
|
224 |
+
|
225 |
+
# 7. Convert prediction probabilities -> prediction labels
|
226 |
+
target_image_pred_label = torch.argmax(target_image_pred_probs, dim=1)
|
227 |
+
|
228 |
+
# 8. Plot the image alongside the prediction and prediction probability
|
229 |
+
plt.imshow(
|
230 |
+
target_image.squeeze().permute(1, 2, 0)
|
231 |
+
) # make sure it's the right size for matplotlib
|
232 |
+
if class_names:
|
233 |
+
title = f"Pred: {class_names[target_image_pred_label.cpu()]} | Prob: {target_image_pred_probs.max().cpu():.3f}"
|
234 |
+
else:
|
235 |
+
title = f"Pred: {target_image_pred_label} | Prob: {target_image_pred_probs.max().cpu():.3f}"
|
236 |
+
plt.title(title)
|
237 |
+
plt.axis(False)
|
238 |
+
|
239 |
+
def set_seeds(seed: int=42):
|
240 |
+
"""Sets random sets for torch operations.
|
241 |
+
|
242 |
+
Args:
|
243 |
+
seed (int, optional): Random seed to set. Defaults to 42.
|
244 |
+
"""
|
245 |
+
# Set the seed for general torch operations
|
246 |
+
torch.manual_seed(seed)
|
247 |
+
# Set the seed for CUDA torch operations (ones that happen on the GPU)
|
248 |
+
torch.cuda.manual_seed(seed)
|
249 |
+
|
250 |
+
def download_data(source: str,
|
251 |
+
destination: str,
|
252 |
+
remove_source: bool = True) -> Path:
|
253 |
+
"""Downloads a zipped dataset from source and unzips to destination.
|
254 |
+
|
255 |
+
Args:
|
256 |
+
source (str): A link to a zipped file containing data.
|
257 |
+
destination (str): A target directory to unzip data to.
|
258 |
+
remove_source (bool): Whether to remove the source after downloading and extracting.
|
259 |
+
|
260 |
+
Returns:
|
261 |
+
pathlib.Path to downloaded data.
|
262 |
+
|
263 |
+
Example usage:
|
264 |
+
download_data(source="https://github.com/mrdbourke/pytorch-deep-learning/raw/main/data/pizza_steak_sushi.zip",
|
265 |
+
destination="pizza_steak_sushi")
|
266 |
+
"""
|
267 |
+
# Setup path to data folder
|
268 |
+
data_path = Path("data/")
|
269 |
+
image_path = data_path / destination
|
270 |
+
|
271 |
+
# If the image folder doesn't exist, download it and prepare it...
|
272 |
+
if image_path.is_dir():
|
273 |
+
print(f"[INFO] {image_path} directory exists, skipping download.")
|
274 |
+
else:
|
275 |
+
print(f"[INFO] Did not find {image_path} directory, creating one...")
|
276 |
+
image_path.mkdir(parents=True, exist_ok=True)
|
277 |
+
|
278 |
+
# Download pizza, steak, sushi data
|
279 |
+
target_file = Path(source).name
|
280 |
+
with open(data_path / target_file, "wb") as f:
|
281 |
+
request = requests.get(source)
|
282 |
+
print(f"[INFO] Downloading {target_file} from {source}...")
|
283 |
+
f.write(request.content)
|
284 |
+
|
285 |
+
# Unzip pizza, steak, sushi data
|
286 |
+
with zipfile.ZipFile(data_path / target_file, "r") as zip_ref:
|
287 |
+
print(f"[INFO] Unzipping {target_file} data...")
|
288 |
+
zip_ref.extractall(image_path)
|
289 |
+
|
290 |
+
# Remove .zip file
|
291 |
+
if remove_source:
|
292 |
+
os.remove(data_path / target_file)
|
293 |
+
|
294 |
+
return image_path
|
helper/model_builder.py
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Contains PyTorch model code to instantiate a TinyVGG model.
|
3 |
+
"""
|
4 |
+
import torch
|
5 |
+
from torch import nn
|
6 |
+
|
7 |
+
class TinyVGG(nn.Module):
|
8 |
+
"""Creates the TinyVGG architecture.
|
9 |
+
|
10 |
+
Replicates the TinyVGG architecture from the CNN explainer website in PyTorch.
|
11 |
+
See the original architecture here: https://poloclub.github.io/cnn-explainer/
|
12 |
+
|
13 |
+
Args:
|
14 |
+
input_shape: An integer indicating number of input channels.
|
15 |
+
hidden_units: An integer indicating number of hidden units between layers.
|
16 |
+
output_shape: An integer indicating number of output units.
|
17 |
+
"""
|
18 |
+
def __init__(self, input_shape: int, hidden_units: int, output_shape: int) -> None:
|
19 |
+
super().__init__()
|
20 |
+
self.conv_block_1 = nn.Sequential(
|
21 |
+
nn.Conv2d(in_channels=input_shape,
|
22 |
+
out_channels=hidden_units,
|
23 |
+
kernel_size=3,
|
24 |
+
stride=1,
|
25 |
+
padding=0),
|
26 |
+
nn.ReLU(),
|
27 |
+
nn.Conv2d(in_channels=hidden_units,
|
28 |
+
out_channels=hidden_units,
|
29 |
+
kernel_size=3,
|
30 |
+
stride=1,
|
31 |
+
padding=0),
|
32 |
+
nn.ReLU(),
|
33 |
+
nn.MaxPool2d(kernel_size=2,
|
34 |
+
stride=2)
|
35 |
+
)
|
36 |
+
self.conv_block_2 = nn.Sequential(
|
37 |
+
nn.Conv2d(hidden_units, hidden_units, kernel_size=3, padding=0),
|
38 |
+
nn.ReLU(),
|
39 |
+
nn.Conv2d(hidden_units, hidden_units, kernel_size=3, padding=0),
|
40 |
+
nn.ReLU(),
|
41 |
+
nn.MaxPool2d(2)
|
42 |
+
)
|
43 |
+
self.classifier = nn.Sequential(
|
44 |
+
nn.Flatten(),
|
45 |
+
# Where did this in_features shape come from?
|
46 |
+
# It's because each layer of our network compresses and changes the shape of our inputs data.
|
47 |
+
nn.Linear(in_features=hidden_units*13*13,
|
48 |
+
out_features=output_shape)
|
49 |
+
)
|
50 |
+
|
51 |
+
def forward(self, x: torch.Tensor):
|
52 |
+
x = self.conv_block_1(x)
|
53 |
+
x = self.conv_block_2(x)
|
54 |
+
x = self.classifier(x)
|
55 |
+
return x
|
56 |
+
# return self.classifier(self.block_2(self.block_1(x))) # <- leverage the benefits of operator fusion
|
helper/predictions.py
ADDED
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Utility functions to make predictions.
|
3 |
+
|
4 |
+
Main reference for code creation: https://www.learnpytorch.io/06_pytorch_transfer_learning/#6-make-predictions-on-images-from-the-test-set
|
5 |
+
"""
|
6 |
+
import torch
|
7 |
+
import torchvision
|
8 |
+
from torchvision import transforms
|
9 |
+
import matplotlib.pyplot as plt
|
10 |
+
|
11 |
+
from typing import List, Tuple
|
12 |
+
|
13 |
+
from PIL import Image
|
14 |
+
|
15 |
+
# Set device
|
16 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
17 |
+
|
18 |
+
# Predict on a target image with a target model
|
19 |
+
# Function created in: https://www.learnpytorch.io/06_pytorch_transfer_learning/#6-make-predictions-on-images-from-the-test-set
|
20 |
+
def pred_and_plot_image(
|
21 |
+
model: torch.nn.Module,
|
22 |
+
class_names: List[str],
|
23 |
+
image_path: str,
|
24 |
+
image_size: Tuple[int, int] = (224, 224),
|
25 |
+
transform: torchvision.transforms = None,
|
26 |
+
device: torch.device = device,
|
27 |
+
):
|
28 |
+
"""Predicts on a target image with a target model.
|
29 |
+
|
30 |
+
Args:
|
31 |
+
model (torch.nn.Module): A trained (or untrained) PyTorch model to predict on an image.
|
32 |
+
class_names (List[str]): A list of target classes to map predictions to.
|
33 |
+
image_path (str): Filepath to target image to predict on.
|
34 |
+
image_size (Tuple[int, int], optional): Size to transform target image to. Defaults to (224, 224).
|
35 |
+
transform (torchvision.transforms, optional): Transform to perform on image. Defaults to None which uses ImageNet normalization.
|
36 |
+
device (torch.device, optional): Target device to perform prediction on. Defaults to device.
|
37 |
+
"""
|
38 |
+
|
39 |
+
# Open image
|
40 |
+
img = Image.open(image_path)
|
41 |
+
|
42 |
+
# Create transformation for image (if one doesn't exist)
|
43 |
+
if transform is not None:
|
44 |
+
image_transform = transform
|
45 |
+
else:
|
46 |
+
image_transform = transforms.Compose(
|
47 |
+
[
|
48 |
+
transforms.Resize(image_size),
|
49 |
+
transforms.ToTensor(),
|
50 |
+
transforms.Normalize(
|
51 |
+
mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]
|
52 |
+
),
|
53 |
+
]
|
54 |
+
)
|
55 |
+
|
56 |
+
### Predict on image ###
|
57 |
+
|
58 |
+
# Make sure the model is on the target device
|
59 |
+
model.to(device)
|
60 |
+
|
61 |
+
# Turn on model evaluation mode and inference mode
|
62 |
+
model.eval()
|
63 |
+
with torch.inference_mode():
|
64 |
+
# Transform and add an extra dimension to image (model requires samples in [batch_size, color_channels, height, width])
|
65 |
+
transformed_image = image_transform(img).unsqueeze(dim=0)
|
66 |
+
|
67 |
+
# Make a prediction on image with an extra dimension and send it to the target device
|
68 |
+
target_image_pred = model(transformed_image.to(device))
|
69 |
+
|
70 |
+
# Convert logits -> prediction probabilities (using torch.softmax() for multi-class classification)
|
71 |
+
target_image_pred_probs = torch.softmax(target_image_pred, dim=1)
|
72 |
+
|
73 |
+
# Convert prediction probabilities -> prediction labels
|
74 |
+
target_image_pred_label = torch.argmax(target_image_pred_probs, dim=1)
|
75 |
+
|
76 |
+
# Plot image with predicted label and probability
|
77 |
+
plt.figure()
|
78 |
+
plt.imshow(img)
|
79 |
+
plt.title(
|
80 |
+
f"Pred: {class_names[target_image_pred_label]} | Prob: {target_image_pred_probs.max():.3f}"
|
81 |
+
)
|
82 |
+
plt.axis(False)
|
83 |
+
|
helper/train.py
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Trains a PyTorch image classification model using device-agnostic code.
|
3 |
+
"""
|
4 |
+
|
5 |
+
import os
|
6 |
+
import torch
|
7 |
+
import data_setup, engine, model_builder, utils
|
8 |
+
|
9 |
+
from torchvision import transforms
|
10 |
+
|
11 |
+
# Setup hyperparameters
|
12 |
+
NUM_EPOCHS = 5
|
13 |
+
BATCH_SIZE = 32
|
14 |
+
HIDDEN_UNITS = 10
|
15 |
+
LEARNING_RATE = 0.001
|
16 |
+
|
17 |
+
# Setup directories
|
18 |
+
train_dir = "data/pizza_steak_sushi/train"
|
19 |
+
test_dir = "data/pizza_steak_sushi/test"
|
20 |
+
|
21 |
+
# Setup target device
|
22 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
23 |
+
|
24 |
+
# Create transforms
|
25 |
+
data_transform = transforms.Compose([
|
26 |
+
transforms.Resize((64, 64)),
|
27 |
+
transforms.ToTensor()
|
28 |
+
])
|
29 |
+
|
30 |
+
# Create DataLoaders with help from data_setup.py
|
31 |
+
train_dataloader, test_dataloader, class_names = data_setup.create_dataloaders(
|
32 |
+
train_dir=train_dir,
|
33 |
+
test_dir=test_dir,
|
34 |
+
transform=data_transform,
|
35 |
+
batch_size=BATCH_SIZE
|
36 |
+
)
|
37 |
+
|
38 |
+
# Create model with help from model_builder.py
|
39 |
+
model = model_builder.TinyVGG(
|
40 |
+
input_shape=3,
|
41 |
+
hidden_units=HIDDEN_UNITS,
|
42 |
+
output_shape=len(class_names)
|
43 |
+
).to(device)
|
44 |
+
|
45 |
+
# Set loss and optimizer
|
46 |
+
loss_fn = torch.nn.CrossEntropyLoss()
|
47 |
+
optimizer = torch.optim.Adam(model.parameters(),
|
48 |
+
lr=LEARNING_RATE)
|
49 |
+
|
50 |
+
# Start training with help from engine.py
|
51 |
+
engine.train(model=model,
|
52 |
+
train_dataloader=train_dataloader,
|
53 |
+
test_dataloader=test_dataloader,
|
54 |
+
loss_fn=loss_fn,
|
55 |
+
optimizer=optimizer,
|
56 |
+
epochs=NUM_EPOCHS,
|
57 |
+
device=device)
|
58 |
+
|
59 |
+
# Save the model with help from utils.py
|
60 |
+
utils.save_model(model=model,
|
61 |
+
target_dir="models",
|
62 |
+
model_name="05_going_modular_script_mode_tinyvgg_model.pth")
|
helper/utils.py
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Contains various utility functions for PyTorch model training and saving.
|
3 |
+
"""
|
4 |
+
import torch
|
5 |
+
from pathlib import Path
|
6 |
+
|
7 |
+
def save_model(model: torch.nn.Module,
|
8 |
+
target_dir: str,
|
9 |
+
model_name: str):
|
10 |
+
"""Saves a PyTorch model to a target directory.
|
11 |
+
|
12 |
+
Args:
|
13 |
+
model: A target PyTorch model to save.
|
14 |
+
target_dir: A directory for saving the model to.
|
15 |
+
model_name: A filename for the saved model. Should include
|
16 |
+
either ".pth" or ".pt" as the file extension.
|
17 |
+
|
18 |
+
Example usage:
|
19 |
+
save_model(model=model_0,
|
20 |
+
target_dir="models",
|
21 |
+
model_name="05_going_modular_tingvgg_model.pth")
|
22 |
+
"""
|
23 |
+
# Create target directory
|
24 |
+
target_dir_path = Path(target_dir)
|
25 |
+
target_dir_path.mkdir(parents=True,
|
26 |
+
exist_ok=True)
|
27 |
+
|
28 |
+
# Create model save path
|
29 |
+
assert model_name.endswith(".pth") or model_name.endswith(".pt"), "model_name should end with '.pt' or '.pth'"
|
30 |
+
model_save_path = target_dir_path / model_name
|
31 |
+
|
32 |
+
# Save the model state_dict()
|
33 |
+
print(f"[INFO] Saving model to: {model_save_path}")
|
34 |
+
torch.save(obj=model.state_dict(),
|
35 |
+
f=model_save_path)
|
model.py
CHANGED
@@ -1,34 +1,67 @@
|
|
1 |
import torch
|
2 |
import torchvision
|
3 |
-
|
4 |
from torch import nn
|
5 |
|
6 |
|
7 |
-
def
|
8 |
-
seed:int=42):
|
9 |
-
"""Creates
|
10 |
|
11 |
Args:
|
12 |
-
num_classes (int, optional): number of classes in the classifier head.
|
13 |
-
Defaults to
|
14 |
seed (int, optional): random seed value. Defaults to 42.
|
15 |
|
16 |
Returns:
|
17 |
-
model (torch.nn.Module): vit feature extractor model.
|
18 |
transforms (torchvision.transforms): vit image transforms.
|
19 |
"""
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
|
|
24 |
|
25 |
-
|
26 |
-
|
27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
28 |
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
|
|
|
|
|
|
|
|
|
1 |
import torch
|
2 |
import torchvision
|
|
|
3 |
from torch import nn
|
4 |
|
5 |
|
6 |
+
def create_model(num_classes: int = 32,
|
7 |
+
seed: int = 42):
|
8 |
+
"""Creates a feature extractor model and transforms.
|
9 |
|
10 |
Args:
|
11 |
+
num_classes (int, optional): number of classes in the classifier head.
|
12 |
+
Defaults to 32.
|
13 |
seed (int, optional): random seed value. Defaults to 42.
|
14 |
|
15 |
Returns:
|
16 |
+
model (torch.nn.Module): vit feature extractor model.
|
17 |
transforms (torchvision.transforms): vit image transforms.
|
18 |
"""
|
19 |
+
IMG_SIZE = 28
|
20 |
+
transforms = transforms.Compose([
|
21 |
+
transforms.Resize((IMG_SIZE, IMG_SIZE)),
|
22 |
+
transforms.Grayscale(num_output_channels=1),
|
23 |
+
transforms.ToTensor()])
|
24 |
|
25 |
+
# Create a convolutional neural network
|
26 |
+
class Model(nn.Module):
|
27 |
+
def __init__(self, input_shape: int, hidden_units: int, output_shape: int):
|
28 |
+
super().__init__()
|
29 |
+
self.block_1 = nn.Sequential(
|
30 |
+
nn.Conv2d(in_channels=input_shape,
|
31 |
+
out_channels=hidden_units,
|
32 |
+
kernel_size=3, # how big is the square that's going over the image?
|
33 |
+
stride=1, # default
|
34 |
+
padding=1), # options = "valid" (no padding) or "same" (output has same shape as input) or int for specific number
|
35 |
+
nn.ReLU(),
|
36 |
+
nn.Conv2d(in_channels=hidden_units,
|
37 |
+
out_channels=hidden_units,
|
38 |
+
kernel_size=3,
|
39 |
+
stride=1,
|
40 |
+
padding=1),
|
41 |
+
nn.ReLU(),
|
42 |
+
nn.MaxPool2d(kernel_size=2,
|
43 |
+
stride=2) # default stride value is same as kernel_size
|
44 |
+
)
|
45 |
+
self.block_2 = nn.Sequential(
|
46 |
+
nn.Conv2d(hidden_units, hidden_units, 3, padding=1),
|
47 |
+
nn.ReLU(),
|
48 |
+
nn.Conv2d(hidden_units, hidden_units, 3, padding=1),
|
49 |
+
nn.ReLU(),
|
50 |
+
nn.MaxPool2d(2)
|
51 |
+
)
|
52 |
+
self.classifier = nn.Sequential(
|
53 |
+
nn.Flatten(),
|
54 |
+
nn.Linear(in_features=hidden_units*7*7,
|
55 |
+
out_features=output_shape)
|
56 |
+
)
|
57 |
|
58 |
+
def forward(self, x: torch.Tensor):
|
59 |
+
# x = self.block_1(x)
|
60 |
+
# print(x.shape)
|
61 |
+
# x = self.block_2(x)
|
62 |
+
# print(x.shape)
|
63 |
+
# x = self.classifier(x)
|
64 |
+
# print(x.shape)
|
65 |
+
x = self.classifier(self.block_2(self.block_1(x)))
|
66 |
+
return x
|
67 |
+
return Model, transforms
|
model_food101_20_percent.pth → model_1.pth
RENAMED
@@ -1,3 +1,3 @@
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
-
oid sha256:
|
3 |
-
size
|
|
|
1 |
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:09d5f31bc58b2ae0b7b58d3730491a2708db1245fbaf688d1d6a3cb1b613ba3d
|
3 |
+
size 77575
|