carlosabadia commited on
Commit
2ed7425
·
1 Parent(s): 57a938e
app.py CHANGED
@@ -3,7 +3,7 @@ import gradio as gr
3
  import os
4
  import torch
5
 
6
- from model import create_vit16_model
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
- vit16, vit16_transforms = create_vit16_model(
18
- num_classes=101, # could also use len(class_names)
19
  )
20
 
21
 
22
- vit16.load_state_dict(
23
  torch.load(
24
- f="model_food101_20_percent.pth",
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 = vit16_transforms(img).unsqueeze(0)
42
 
43
  # Put model into evaluation mode and turn on inference mode
44
- vit16.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(vit16(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,8 +58,8 @@ def predict(img) -> Tuple[Dict, float]:
58
  ### 4. Gradio app ###
59
 
60
  # Create title, description and article strings
61
- title = "FoodVision ViT 🍔👁"
62
- description = "A ViT_B_16 feature extractor computer vision model to classify images of food into 101 different classes using 20% of the data."
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
- 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
 
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 create_vit16_model(num_classes:int=101,
8
- seed:int=42):
9
- """Creates an vit16 feature extractor model and transforms.
10
 
11
  Args:
12
- num_classes (int, optional): number of classes in the classifier head.
13
- Defaults to 3.
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
- # Create vit pretrained weights, transforms and model
21
- weights = torchvision.models.ViT_B_16_Weights.DEFAULT;
22
- transforms = weights.transforms()
23
- model = torchvision.models.vit_b_16(weights=weights)
 
24
 
25
- # Freeze all layers in base model
26
- for param in model.parameters():
27
- param.requires_grad = False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
- # Change classifier head with random seed for reproducibility
30
- torch.manual_seed(seed)
31
- model.heads = nn.Sequential(nn.Linear(in_features=768, # keep this the same as original model
32
- out_features=num_classes)) # update to reflect target number of classes
33
-
34
- return model, transforms
 
 
 
 
 
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:b4357a334ed5737baacaf7a99b0ba491ef88c61580790277acec9ef877cd77c9
3
- size 343564561
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:09d5f31bc58b2ae0b7b58d3730491a2708db1245fbaf688d1d6a3cb1b613ba3d
3
+ size 77575