Spaces:
Sleeping
Sleeping
Commit
·
3a664f3
1
Parent(s):
e914ab1
foodvision
Browse files- .gitattributes +2 -0
- Recipe/data_setup.py +61 -0
- Recipe/engine.py +157 -0
- Recipe/model_builder.py +19 -0
- Recipe/prediction.py +78 -0
- Recipe/train.py +114 -0
- Recipe/utils.py +37 -0
- app.py +66 -0
- class_names.txt +101 -0
- example/hot_dog.jpg +0 -0
- example/ice_cream.jpg +0 -0
- example/pancakes.jpg +0 -0
- example/pizza.jpg +0 -0
- example/steak.jpg +0 -0
- model.pkl +3 -0
- model_effecientnet_b2_weights.pth +3 -0
- requirements.txt +4 -0
.gitattributes
CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
36 |
+
model.pkl filter=lfs diff=lfs merge=lfs -text
|
37 |
+
model_effecientnet_b2_weights.pth filter=lfs diff=lfs merge=lfs -text
|
Recipe/data_setup.py
ADDED
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Contains functionality for creating PyTorch DataLoaders for
|
3 |
+
image classification data(Food101).
|
4 |
+
"""
|
5 |
+
import os
|
6 |
+
|
7 |
+
from pathlib import Path
|
8 |
+
|
9 |
+
import torchvision
|
10 |
+
from torchvision import transforms
|
11 |
+
from torch.utils.data import DataLoader
|
12 |
+
|
13 |
+
num_workers = os.cpu_count()
|
14 |
+
|
15 |
+
|
16 |
+
def create_dataloaders(transform: transforms.Compose,
|
17 |
+
batch_size: int,
|
18 |
+
num_workers: int = num_workers):
|
19 |
+
"""Creates training and testing DataLoaders.
|
20 |
+
|
21 |
+
Takes in a transform them and download food 101 dataset
|
22 |
+
and then into PyTorch DataLoaders.
|
23 |
+
|
24 |
+
Args:
|
25 |
+
transform: torchvision transforms to perform on training and testing data.
|
26 |
+
batch_size: Number of samples per batch in each of the DataLoaders.
|
27 |
+
num_workers: An integer for number of workers per DataLoader.
|
28 |
+
|
29 |
+
Returns:
|
30 |
+
A tuple of (train_dataloader, test_dataloader, class_names).
|
31 |
+
Where class_names is a list of the target classes.
|
32 |
+
Example usage:
|
33 |
+
train_dataloader, test_dataloader, class_names = \
|
34 |
+
= create_dataloaders(transform=some_transform,
|
35 |
+
batch_size=32,
|
36 |
+
num_workers=4)
|
37 |
+
"""
|
38 |
+
# making dir for data
|
39 |
+
data_path = Path("data")
|
40 |
+
data_path.mkdir(parents=True, exist_ok=True)
|
41 |
+
|
42 |
+
# Dataset
|
43 |
+
train_data = torchvision.datasets.Food101(root=data_path,
|
44 |
+
split="train",
|
45 |
+
transform=transform,
|
46 |
+
download=True)
|
47 |
+
test_data = torchvision.datasets.Food101(root=data_path,
|
48 |
+
split="test",
|
49 |
+
transform=transform,
|
50 |
+
download=True)
|
51 |
+
# DataLoaders
|
52 |
+
train_dataloader = DataLoader(dataset=train_data,
|
53 |
+
batch_size=batch_size,
|
54 |
+
shuffle=True,
|
55 |
+
num_workers=num_workers)
|
56 |
+
test_dataloader = DataLoader(dataset=test_data,
|
57 |
+
batch_size=batch_size,
|
58 |
+
shuffle=False,
|
59 |
+
num_workers=num_workers)
|
60 |
+
class_names = train_data.classes
|
61 |
+
return train_dataloader, test_dataloader, class_names
|
Recipe/engine.py
ADDED
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Contains functions for training and testing a PyTorch model.
|
3 |
+
"""
|
4 |
+
import torch
|
5 |
+
|
6 |
+
from tqdm.auto import tqdm
|
7 |
+
|
8 |
+
|
9 |
+
def train_step(model: torch.nn.Module,
|
10 |
+
train_dataloader: torch.utils.data.DataLoader,
|
11 |
+
loss_fn: torch.nn.Module,
|
12 |
+
optimizer: torch.optim.Optimizer,
|
13 |
+
device: torch.device):
|
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 the required training steps (forward
|
18 |
+
pass, loss calculation, optimizer step).
|
19 |
+
|
20 |
+
Args:
|
21 |
+
model: A PyTorch model to be trained.
|
22 |
+
train_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 |
+
# setting the model to train mode
|
34 |
+
total_loss, total_acc = 0, 0
|
35 |
+
for i, (X, y) in enumerate(train_dataloader):
|
36 |
+
# to device
|
37 |
+
X, y = X.to(device), y.to(device)
|
38 |
+
# forward pass
|
39 |
+
y_logits = model(X)
|
40 |
+
# loss calculate
|
41 |
+
loss = loss_fn(y_logits, y)
|
42 |
+
# zero the optimizer
|
43 |
+
optimizer.zero_grad()
|
44 |
+
# loss backward
|
45 |
+
loss.backward()
|
46 |
+
# optimizer step
|
47 |
+
optimizer.step()
|
48 |
+
total_acc += (torch.argmax(y_logits, dim=1) == y).sum() / len(y)
|
49 |
+
total_loss += loss
|
50 |
+
total_loss /= len(train_dataloader)
|
51 |
+
total_acc = total_acc / len(train_dataloader)
|
52 |
+
return total_loss, total_acc
|
53 |
+
|
54 |
+
|
55 |
+
def test_step(model: torch.nn.Module,
|
56 |
+
test_dataloader: torch.utils.data.DataLoader,
|
57 |
+
loss_fn: torch.nn.Module,
|
58 |
+
device: torch.device):
|
59 |
+
"""Tests a PyTorch model for a single epoch.
|
60 |
+
|
61 |
+
Turns a target PyTorch model to "eval" mode and then performs
|
62 |
+
a forward pass on a testing dataset.
|
63 |
+
|
64 |
+
Args:
|
65 |
+
model: A PyTorch model to be tested.
|
66 |
+
test_dataloader: A DataLoader instance for the model to be tested on.
|
67 |
+
loss_fn: A PyTorch loss function to calculate loss on the test data.
|
68 |
+
device: A target device to compute on (e.g. "cuda" or "cpu").
|
69 |
+
|
70 |
+
Returns:
|
71 |
+
A tuple of testing loss and testing accuracy metrics.
|
72 |
+
In the form (test_loss, test_accuracy). For example:
|
73 |
+
|
74 |
+
(0.0223, 0.8985)
|
75 |
+
"""
|
76 |
+
# eval model
|
77 |
+
with torch.inference_mode():
|
78 |
+
test_loss, test_acc = 0, 0
|
79 |
+
for i, (X, y) in enumerate(test_dataloader):
|
80 |
+
X, y = X.to(device), y.to(device)
|
81 |
+
y_logits = model(X)
|
82 |
+
test_acc += (torch.argmax(y_logits, dim=1) == y).sum() / len(y)
|
83 |
+
test_loss += loss_fn(y_logits, y)
|
84 |
+
test_loss /= len(test_dataloader)
|
85 |
+
test_acc = test_acc / len(test_dataloader)
|
86 |
+
return test_loss, test_acc
|
87 |
+
|
88 |
+
|
89 |
+
def train(model: torch.nn.Module,
|
90 |
+
train_dataloader: torch.utils.data.DataLoader,
|
91 |
+
test_dataloader: torch.utils.data.DataLoader,
|
92 |
+
optimizer: torch.optim.Optimizer,
|
93 |
+
loss_fn: torch.nn.Module,
|
94 |
+
epochs: int,
|
95 |
+
device: torch.device):
|
96 |
+
"""Trains and tests a PyTorch model.
|
97 |
+
|
98 |
+
Passes a target PyTorch models through train_step() and test_step()
|
99 |
+
functions for a number of epochs, training and testing the model
|
100 |
+
in the same epoch loop.
|
101 |
+
|
102 |
+
Calculates, prints and stores evaluation metrics throughout.
|
103 |
+
|
104 |
+
Args:
|
105 |
+
model: A PyTorch model to be trained and tested.
|
106 |
+
train_dataloader: A DataLoader instance for the model to be trained on.
|
107 |
+
test_dataloader: A DataLoader instance for the model to be tested on.
|
108 |
+
optimizer: A PyTorch optimizer to help minimize the loss function.
|
109 |
+
loss_fn: A PyTorch loss function to calculate loss on both datasets.
|
110 |
+
epochs: An integer indicating how many epochs to train for.
|
111 |
+
device: A target device to compute on (e.g. "cuda" or "cpu").
|
112 |
+
|
113 |
+
Returns:
|
114 |
+
A dictionary of training and testing loss as well as training and
|
115 |
+
testing accuracy metrics. Each metric has a value in a list for
|
116 |
+
each epoch.
|
117 |
+
In the form: {train_loss: [...],
|
118 |
+
train_acc: [...],
|
119 |
+
test_loss: [...],
|
120 |
+
test_acc: [...]}
|
121 |
+
For example if training for epochs=2:
|
122 |
+
{train_loss: [2.0616, 1.0537],
|
123 |
+
train_acc: [0.3945, 0.3945],
|
124 |
+
test_loss: [1.2641, 1.5706],
|
125 |
+
test_acc: [0.3400, 0.2973]}
|
126 |
+
|
127 |
+
Remark : Can un-comment the below lines of code to also store the model weights
|
128 |
+
"""
|
129 |
+
results = {"train_loss": [],
|
130 |
+
"train_acc": [],
|
131 |
+
"test_loss": [],
|
132 |
+
"test_acc": []}
|
133 |
+
# from pathlib import Path
|
134 |
+
# save_path=Path("models")
|
135 |
+
# save_path.mkdir(parents=True,exist_ok=True)
|
136 |
+
model.to(device)
|
137 |
+
for epoch in tqdm(range(epochs)):
|
138 |
+
model.train()
|
139 |
+
loss_train, acc_train = train_step(model,
|
140 |
+
train_dataloader=train_dataloader,
|
141 |
+
loss_fn=loss_fn,
|
142 |
+
optimizer=optimizer,
|
143 |
+
device=device)
|
144 |
+
results["train_loss"].append(loss_train)
|
145 |
+
results["train_acc"].append(acc_train)
|
146 |
+
model.eval()
|
147 |
+
loss_test, acc_test = test_step(model,
|
148 |
+
test_dataloader,
|
149 |
+
loss_fn,
|
150 |
+
device)
|
151 |
+
results["test_loss"].append(loss_test)
|
152 |
+
results["test_acc"].append(acc_test)
|
153 |
+
print(
|
154 |
+
f"epoch : {epoch + 1} | train_loss : {loss_train} | train_acc : {acc_train} | test_loss : {loss_test} | test_acc : {acc_test}")
|
155 |
+
# model_path=save_path/f"model_efficient_b2_weights_{epoch}.pth"
|
156 |
+
# torch.save(obj=model.state_dict(),f=model_path)
|
157 |
+
return results
|
Recipe/model_builder.py
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Build a EfficientNet B2 model from torchvision
|
3 |
+
"""
|
4 |
+
|
5 |
+
import torch
|
6 |
+
import torchvision
|
7 |
+
|
8 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
9 |
+
|
10 |
+
|
11 |
+
def model_build(device=device):
|
12 |
+
# getting the weights for EfficientNet B2 and then get the transforms
|
13 |
+
weights = torchvision.models.EfficientNet_B2_Weights.DEFAULT # ".DEFAULT" = best available weights
|
14 |
+
# Transforms used in EfficientNet B2
|
15 |
+
transform = weights.transforms()
|
16 |
+
# Model
|
17 |
+
model = torchvision.models.efficientnet_b2(weights=weights).to(device)
|
18 |
+
# print(model)
|
19 |
+
return model
|
Recipe/prediction.py
ADDED
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Utility functions to make predictions.
|
3 |
+
"""
|
4 |
+
import torch
|
5 |
+
import torchvision
|
6 |
+
from torchvision import transforms
|
7 |
+
import matplotlib.pyplot as plt
|
8 |
+
|
9 |
+
from typing import List, Tuple
|
10 |
+
|
11 |
+
from PIL import Image
|
12 |
+
|
13 |
+
# Set device
|
14 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
15 |
+
|
16 |
+
|
17 |
+
# Predict on a target image with a target model
|
18 |
+
def pred_and_plot_image(
|
19 |
+
model: torch.nn.Module,
|
20 |
+
class_names: List[str],
|
21 |
+
image_path: str,
|
22 |
+
image_size: Tuple[int, int] = (288, 288),
|
23 |
+
transform: torchvision.transforms = None,
|
24 |
+
device: torch.device = device):
|
25 |
+
"""Predicts on a target image with a target model.
|
26 |
+
|
27 |
+
Args:
|
28 |
+
model (torch.nn.Module): A trained (or untrained) PyTorch model to predict on an image.
|
29 |
+
class_names (List[str]): A list of target classes to map predictions to.
|
30 |
+
image_path (str): Filepath to target image to predict on.
|
31 |
+
image_size (Tuple[int, int], optional): Size to transform target image to. Defaults to (224, 224).
|
32 |
+
transform (torchvision.transforms, optional): Transform to perform on image. Defaults to None which uses ImageNet normalization.
|
33 |
+
device (torch.device, optional): Target device to perform prediction on. Defaults to device.
|
34 |
+
"""
|
35 |
+
|
36 |
+
# Open image
|
37 |
+
img = Image.open(image_path)
|
38 |
+
|
39 |
+
# Create transformation for image (if one doesn't exist)
|
40 |
+
if transform is not None:
|
41 |
+
image_transform = transform
|
42 |
+
else:
|
43 |
+
image_transform = transforms.Compose([
|
44 |
+
transforms.Resize(image_size),
|
45 |
+
transforms.ToTensor(),
|
46 |
+
transforms.Normalize(
|
47 |
+
mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
|
48 |
+
])
|
49 |
+
|
50 |
+
### Predict on image ###
|
51 |
+
|
52 |
+
# Make sure the model is on the target device
|
53 |
+
model.to(device)
|
54 |
+
|
55 |
+
# Turn on model evaluation mode and inference mode
|
56 |
+
model.eval()
|
57 |
+
with torch.inference_mode():
|
58 |
+
# Transform and add an extra dimension to image (model requires samples in [batch_size, color_channels,
|
59 |
+
# height, width])
|
60 |
+
transformed_image = image_transform(img).unsqueeze(dim=0)
|
61 |
+
|
62 |
+
# Make a prediction on image with an extra dimension and send it to the target device
|
63 |
+
target_image_pred = model(transformed_image.to(device))
|
64 |
+
|
65 |
+
# Convert logits -> prediction probabilities (using torch.softmax() for multi-class classification)
|
66 |
+
target_image_pred_probs = torch.softmax(target_image_pred, dim=1)
|
67 |
+
|
68 |
+
# Convert prediction probabilities -> prediction labels
|
69 |
+
target_image_pred_label = torch.argmax(target_image_pred_probs, dim=1)
|
70 |
+
|
71 |
+
# Plot image with predicted label and probability
|
72 |
+
plt.figure()
|
73 |
+
plt.imshow(img)
|
74 |
+
plt.title(
|
75 |
+
f"Pred: {class_names[target_image_pred_label]} | Prob: {target_image_pred_probs.max():.3f}"
|
76 |
+
)
|
77 |
+
plt.axis(False)
|
78 |
+
plt.show()
|
Recipe/train.py
ADDED
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Trains a PyTorch image classification model using device-agnostic code.
|
3 |
+
"""
|
4 |
+
|
5 |
+
import torch
|
6 |
+
import torchvision
|
7 |
+
from torch import nn
|
8 |
+
import data_setup
|
9 |
+
import engine
|
10 |
+
import model_builder
|
11 |
+
import utils
|
12 |
+
|
13 |
+
# Setup hyperparameters
|
14 |
+
NUM_EPOCHS = [7, 4, 3] # [feature Extraction, Fine Tuning Part 1, Fine Tuning Part 2]
|
15 |
+
BATCH_SIZE = 32
|
16 |
+
LEARNING_RATE = [0.001, 0.0001, 0.00001] # [feature Extraction, Fine Tuning Part 1, Fine Tuning Part 2]
|
17 |
+
|
18 |
+
# Setup target device
|
19 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
20 |
+
# Create transforms
|
21 |
+
data_transform = torchvision.models.EfficientNet_B2_Weights.DEFAULT.transforms()
|
22 |
+
# ------------------------------------------ DataLoaders ----------------------------------------------------------------------#
|
23 |
+
# Create DataLoaders with help from data_setup.py
|
24 |
+
train_dataloader, test_dataloader, class_names = data_setup.create_dataloaders(
|
25 |
+
transform=data_transform,
|
26 |
+
batch_size=BATCH_SIZE)
|
27 |
+
print("dataloaders created")
|
28 |
+
|
29 |
+
# ------------------------------------------ Model ----------------------------------------------------------------------------#
|
30 |
+
# Create model with help from model_builder.py
|
31 |
+
model = model_builder.model_build(device=device)
|
32 |
+
print("model created")
|
33 |
+
|
34 |
+
# ------------------------------------------ Feature Extraction ---------------------------------------------------------------#
|
35 |
+
# Setting all parameters to not-trainable
|
36 |
+
for params in model.parameters():
|
37 |
+
params.requires_grad = False
|
38 |
+
|
39 |
+
# Changing Classification layer
|
40 |
+
model.classifier = nn.Sequential(
|
41 |
+
nn.Dropout(p=0.3, inplace=True),
|
42 |
+
nn.Linear(in_features=1408, out_features=len(class_names)))
|
43 |
+
# model.classifier
|
44 |
+
|
45 |
+
# Set loss and optimizer
|
46 |
+
loss_fn = torch.nn.CrossEntropyLoss()
|
47 |
+
optimizer = torch.optim.Adam(model.parameters(),
|
48 |
+
lr=LEARNING_RATE[0])
|
49 |
+
|
50 |
+
# Start training with help from engine.py
|
51 |
+
feature_extraction_results=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[0],
|
57 |
+
device=device)
|
58 |
+
print(feature_extraction_results)
|
59 |
+
|
60 |
+
# ------------------------------------------ Fine Tuning Part 1 ---------------------------------------------------------------#
|
61 |
+
# Setting models upper layer un froze
|
62 |
+
for params in model.features[5:].parameters():
|
63 |
+
params.requires_grad = True
|
64 |
+
|
65 |
+
for m in model.modules(): # Making the BatchNorm2d froze
|
66 |
+
if isinstance(m, nn.BatchNorm2d):
|
67 |
+
m.track_running_stats = False
|
68 |
+
m.eval()
|
69 |
+
|
70 |
+
# Set loss and optimizer
|
71 |
+
loss_fn = torch.nn.CrossEntropyLoss()
|
72 |
+
optimizer = torch.optim.Adam(model.parameters(),
|
73 |
+
lr=LEARNING_RATE[1])
|
74 |
+
|
75 |
+
# Start training with help from engine.py
|
76 |
+
fine_tuning_p1_results=engine.train(model=model,
|
77 |
+
train_dataloader=train_dataloader,
|
78 |
+
test_dataloader=test_dataloader,
|
79 |
+
loss_fn=loss_fn,
|
80 |
+
optimizer=optimizer,
|
81 |
+
epochs=NUM_EPOCHS[1],
|
82 |
+
device=device)
|
83 |
+
print(fine_tuning_p1_results)
|
84 |
+
|
85 |
+
# ------------------------------------------ Fine Tuning Part 2 ---------------------------------------------------------------#
|
86 |
+
# Setting models upper layer un froze
|
87 |
+
for params in model.features.parameters():
|
88 |
+
params.requires_grad = True
|
89 |
+
|
90 |
+
for m in model.modules(): # Making the BatchNorm2d froze
|
91 |
+
if isinstance(m, nn.BatchNorm2d):
|
92 |
+
m.track_running_stats = False
|
93 |
+
m.eval()
|
94 |
+
|
95 |
+
# Set loss and optimizer
|
96 |
+
loss_fn = torch.nn.CrossEntropyLoss()
|
97 |
+
optimizer = torch.optim.Adam(model.parameters(),
|
98 |
+
lr=LEARNING_RATE[2])
|
99 |
+
|
100 |
+
# Start training with help from engine.py
|
101 |
+
fine_tuning_p2_results=engine.train(model=model,
|
102 |
+
train_dataloader=train_dataloader,
|
103 |
+
test_dataloader=test_dataloader,
|
104 |
+
loss_fn=loss_fn,
|
105 |
+
optimizer=optimizer,
|
106 |
+
epochs=NUM_EPOCHS[2],
|
107 |
+
device=device)
|
108 |
+
print(fine_tuning_p2_results)
|
109 |
+
|
110 |
+
# ------------------------------------------ Save model -----------------------------------------------------------------------#
|
111 |
+
# Save the model with help from utils.py
|
112 |
+
utils.save_model(model=model,
|
113 |
+
target_dir="models",
|
114 |
+
model_name="Image_Classification_EfficientNet_B2.pth")
|
Recipe/utils.py
ADDED
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Contains various utility functions for PyTorch model training and saving.
|
3 |
+
"""
|
4 |
+
|
5 |
+
import torch
|
6 |
+
from pathlib import Path
|
7 |
+
|
8 |
+
|
9 |
+
def save_model(model: torch.nn.Module,
|
10 |
+
target_dir: str,
|
11 |
+
model_name: str):
|
12 |
+
"""Saves a PyTorch model to a target directory.
|
13 |
+
|
14 |
+
Args:
|
15 |
+
model: A target PyTorch model to save.
|
16 |
+
target_dir: A directory for saving the model to.
|
17 |
+
model_name: A filename for the saved model. Should include
|
18 |
+
either ".pth" or ".pt" as the file extension.
|
19 |
+
|
20 |
+
Example usage:
|
21 |
+
save_model(model=model,
|
22 |
+
target_dir="models",
|
23 |
+
model_name="model_name.pth")
|
24 |
+
"""
|
25 |
+
# Create target directory
|
26 |
+
target_dir_path = Path(target_dir)
|
27 |
+
target_dir_path.mkdir(parents=True,
|
28 |
+
exist_ok=True)
|
29 |
+
|
30 |
+
# Create model save path
|
31 |
+
assert model_name.endswith(".pth") or model_name.endswith(".pt"), "model_name should end with '.pt' or '.pth'"
|
32 |
+
model_save_path = target_dir_path / model_name
|
33 |
+
|
34 |
+
# Save the model state_dict()
|
35 |
+
print(f"[INFO] Saving model to: {model_save_path}")
|
36 |
+
torch.save(obj=model.state_dict(),
|
37 |
+
f=model_save_path)
|
app.py
ADDED
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
import torchvision
|
3 |
+
import pickle
|
4 |
+
import gradio as gr
|
5 |
+
from timeit import default_timer as timer
|
6 |
+
|
7 |
+
# device-agnostic code
|
8 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
9 |
+
|
10 |
+
# Create the model
|
11 |
+
model = pickle.load(open("model.pkl", "rb"))
|
12 |
+
model.to(device)
|
13 |
+
model.eval()
|
14 |
+
next(iter(model.parameters())).to(device)
|
15 |
+
# Transform for prediction
|
16 |
+
transform = torchvision.models.EfficientNet_B2_Weights.DEFAULT.transforms()
|
17 |
+
# class_names
|
18 |
+
# Open Food101 class names file and read each line into a list
|
19 |
+
with open("class_names.txt", "r") as f:
|
20 |
+
class_names = [food.strip() for food in f.readlines()]
|
21 |
+
|
22 |
+
# example_list
|
23 |
+
example_list = [["example/pizza.jpg"],
|
24 |
+
["example/ice_cream.jpg"],
|
25 |
+
["example/pancakes.jpg"],
|
26 |
+
["example/steak.jpg"],
|
27 |
+
["example/hot_dog.jpg"]]
|
28 |
+
|
29 |
+
|
30 |
+
def predict(img):
|
31 |
+
"""Predict the class of image
|
32 |
+
|
33 |
+
Args:
|
34 |
+
img: input image vector
|
35 |
+
Results:
|
36 |
+
a tuple of dictionary and float
|
37 |
+
Predictions,Prediction_time=predict(img=img_vector)
|
38 |
+
"""
|
39 |
+
start_time = timer()
|
40 |
+
with torch.inference_mode():
|
41 |
+
t_img = transform(img).unsqueeze(0).to(device)
|
42 |
+
# print(t_img)
|
43 |
+
probs = torch.softmax(model(t_img), dim=1).to("cpu")
|
44 |
+
end_time = timer()
|
45 |
+
label_and_prob = {class_names[i]: float(probs[0][i]) for i in range(len(class_names))}
|
46 |
+
# print(probs)
|
47 |
+
return label_and_prob, round(end_time - start_time, 4)
|
48 |
+
|
49 |
+
|
50 |
+
# Create title, description strings
|
51 |
+
title = "FoodVision 🍕🥩🍣"
|
52 |
+
description = "An EfficientNetB2 fine tuning computer vision model to classify 101 different food images."
|
53 |
+
|
54 |
+
# Create the Gradio demo
|
55 |
+
demo = gr.Interface(fn=predict, # mapping function from input to output
|
56 |
+
inputs=gr.Image(type="pil"), # what are the inputs?
|
57 |
+
outputs=[gr.Label(num_top_classes=3, label="Predictions"), # what are the outputs?
|
58 |
+
gr.Number(label="Prediction time(s)")],
|
59 |
+
# our fn has two outputs, therefore we have two outputs
|
60 |
+
examples=example_list,
|
61 |
+
title=title,
|
62 |
+
description=description)
|
63 |
+
# article=article)
|
64 |
+
|
65 |
+
demo.launch(debug=False, # print errors locally?
|
66 |
+
share=True) # generate a publicly shareable URL?
|
class_names.txt
ADDED
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
apple_pie
|
2 |
+
baby_back_ribs
|
3 |
+
baklava
|
4 |
+
beef_carpaccio
|
5 |
+
beef_tartare
|
6 |
+
beet_salad
|
7 |
+
beignets
|
8 |
+
bibimbap
|
9 |
+
bread_pudding
|
10 |
+
breakfast_burrito
|
11 |
+
bruschetta
|
12 |
+
caesar_salad
|
13 |
+
cannoli
|
14 |
+
caprese_salad
|
15 |
+
carrot_cake
|
16 |
+
ceviche
|
17 |
+
cheese_plate
|
18 |
+
cheesecake
|
19 |
+
chicken_curry
|
20 |
+
chicken_quesadilla
|
21 |
+
chicken_wings
|
22 |
+
chocolate_cake
|
23 |
+
chocolate_mousse
|
24 |
+
churros
|
25 |
+
clam_chowder
|
26 |
+
club_sandwich
|
27 |
+
crab_cakes
|
28 |
+
creme_brulee
|
29 |
+
croque_madame
|
30 |
+
cup_cakes
|
31 |
+
deviled_eggs
|
32 |
+
donuts
|
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
|
example/hot_dog.jpg
ADDED
![]() |
example/ice_cream.jpg
ADDED
![]() |
example/pancakes.jpg
ADDED
![]() |
example/pizza.jpg
ADDED
![]() |
example/steak.jpg
ADDED
![]() |
model.pkl
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:eda6e78ead6b47522f87191eb1aa27127e400022e2d47a55939fa2dce960fd56
|
3 |
+
size 31865112
|
model_effecientnet_b2_weights.pth
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:e88103d15a267763ebdaef4122fbaaf22c169fd23321aedace102131025a9109
|
3 |
+
size 31837405
|
requirements.txt
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
python==3.10.6
|
2 |
+
torch==2.0.0
|
3 |
+
torchvision==0.15.2
|
4 |
+
gradio==3.38.0
|