ayush2003 commited on
Commit
4873823
·
1 Parent(s): 76882dc

initial commit

Browse files
another_copy_of_final_cnn_pose_notebook.py ADDED
@@ -0,0 +1,515 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """Yet another copy of Final CNN Pose Notebook.ipynb
3
+
4
+ Automatically generated by Colaboratory.
5
+
6
+ Original file is located at
7
+ https://colab.research.google.com/drive/1IdEBDyEyKQdRRT9R-GkfrJINmHdf3_pF
8
+ """
9
+
10
+ # from google.colab import drive
11
+ # drive.mount('/content/drive')
12
+
13
+ # pip install gradio
14
+
15
+ import gradio as gr
16
+
17
+
18
+
19
+
20
+
21
+ import torch
22
+ from torch.utils.data import DataLoader, Dataset, random_split
23
+ from torchvision import transforms, utils
24
+ import torch.nn as nn
25
+ import torch.optim as optim
26
+ import torch.nn.functional as F
27
+ from PIL import Image
28
+ import os
29
+ import numpy as np
30
+ import json
31
+ import matplotlib.pyplot as plt
32
+ from torch.utils.data.dataloader import default_collate
33
+
34
+ # Define the dataset class
35
+ class HumanPoseDataset(Dataset):
36
+ def __init__(self, annotations, img_dir, transform=None):
37
+ self.annotations = annotations
38
+ self.img_dir = img_dir
39
+ self.transform = transform
40
+
41
+ def __len__(self):
42
+ return len(self.annotations)
43
+
44
+ def __getitem__(self, idx):
45
+ img_key = list(self.annotations.keys())[idx]
46
+ annotation_list = self.annotations[img_key]
47
+ # Skip the image if there are no annotations
48
+ if not annotation_list:
49
+ return None
50
+ # Use the first annotation for simplicity
51
+ annotation = annotation_list[0]
52
+ if not annotation['landmarks']: # Check if landmarks are not empty
53
+ return None
54
+ img_name = os.path.join(self.img_dir, annotation['file'])
55
+ image = Image.open(img_name).convert('RGB')
56
+ original_image_size = image.size
57
+ keypoints = annotation['landmarks']
58
+ keypoints_array = np.array([[k['x'], k['y'], k['z'], k['visibility']] for k in keypoints])
59
+
60
+ if self.transform:
61
+ image = self.transform(image)
62
+
63
+ sample = {'image': image, 'keypoints': keypoints_array, 'original_image_size': original_image_size}
64
+ print(sample)
65
+ return sample
66
+
67
+ # Custom collate function to filter out None values
68
+ def custom_collate(batch):
69
+ batch = [b for b in batch if b is not None]
70
+ return default_collate(batch)
71
+
72
+ # Load the annotations JSON into a dictionary
73
+ annotations_path = '/content/drive/MyDrive/annotations_CNN (3).json' # Update this path
74
+ with open(annotations_path) as f:
75
+ annotations_data = json.load(f)
76
+ print("Annotations data loaded. Number of images:", len(annotations_data))
77
+
78
+ x = annotations_data.keys()
79
+
80
+ """# Do data preprocessing. For example, resize to 32 by 32 and normalization.
81
+
82
+ """
83
+
84
+ img_dir = '/content/drive/MyDrive/CNN_Dataset'
85
+
86
+ # Define the transformations with resizing and augmentation
87
+ transform = transforms.Compose([
88
+ transforms.Resize((32, 32)), # Resize the images to 256x256
89
+ transforms.ToTensor(),
90
+ transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
91
+ transforms.RandomHorizontalFlip(), # Example augmentation
92
+ # Add more augmentations if needed
93
+ ])
94
+
95
+ test_transform=transforms.Compose([
96
+ transforms.ToTensor(),
97
+ transforms.Resize((32,32)),
98
+ ])
99
+
100
+ # Create the dataset
101
+ human_pose_dataset = HumanPoseDataset(annotations_data, img_dir, transform=transform)
102
+ testing_pose_dataset = HumanPoseDataset(annotations_data, img_dir, transform=test_transform)
103
+
104
+ print("Dataset created. Length of dataset:", len(human_pose_dataset))
105
+
106
+ sorted(x) == sorted(os.listdir('/content/drive/MyDrive/CNN_Dataset'))
107
+
108
+ """#2. Load parameters of a pretrained model. If a pretrained model for the entire network is not available, then load parameters for the backbone network/feature extraction network/encoder.
109
+
110
+ Pose net model is not available so we will be using an architecture similar to PoseNet, a human pose detection CNN architecture. In the above architecture, we are given a brief description about the PoseNet Architecture. We will be using the Regression Network to find the keypoint coordinates.
111
+ """
112
+
113
+ import torch
114
+ import torch.nn as nn
115
+ import torch.optim as optim
116
+ import torch.nn.functional as F
117
+
118
+ class SimpleCNN(nn.Module):
119
+ def __init__(self):
120
+ super(SimpleCNN, self).__init__()
121
+ self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1)
122
+ self.pool = nn.MaxPool2d(2, 2)
123
+ self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding=1)
124
+ self.conv3 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
125
+ self.conv4 = nn.Conv2d(64, 128, kernel_size=3, padding=1)
126
+ # Assuming the input image size is 256x256, after four pooling layers the image size will be 16x16
127
+ self.fc1 = nn.Linear(2 * 16 * 16, 1000)
128
+ self.fc2 = nn.Linear(1000, 33 * 4) # Assuming 33 keypoints
129
+
130
+ def forward(self, x):
131
+ x = self.pool(F.relu(self.conv1(x)))
132
+ x = self.pool(F.relu(self.conv2(x)))
133
+ x = self.pool(F.relu(self.conv3(x)))
134
+ x = self.pool(F.relu(self.conv4(x)))
135
+ x = torch.flatten(x, 1) # Flatten the tensor for the fully connected layer
136
+ x = F.relu(self.fc1(x))
137
+ x = self.fc2(x)
138
+ return x
139
+
140
+ # Initialize the model
141
+ model = SimpleCNN()
142
+ print("Model initialized.")
143
+ print(model) # Print the model architecture
144
+
145
+ #!pip install mediapipe
146
+
147
+ """#3 Replace the output layer if necessary and finetune the network for your dataset. Use validation dataset to pick a good learning rate and momentum.
148
+
149
+ 1. Training for a very less samples
150
+ """
151
+
152
+ # Split the dataset into training, validation, and test sets
153
+ train_size = int(0.04* len(human_pose_dataset))
154
+ validation_size = int(0.1 * len(human_pose_dataset))
155
+ test_size = len(human_pose_dataset) - train_size - validation_size
156
+ train_dataset, remaining_dataset = random_split(human_pose_dataset, [train_size, validation_size + test_size])
157
+ validation_dataset, test_dataset = random_split(remaining_dataset, [validation_size, test_size])
158
+
159
+ test_pose_dataset , remaining_data = random_split(testing_pose_dataset,[6,194])
160
+
161
+ # Define the batch size
162
+ batch_size = 8
163
+
164
+ # Create data loaders for each set with the custom collate function
165
+ train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, collate_fn=custom_collate)
166
+ validation_loader = DataLoader(validation_dataset, batch_size=batch_size, shuffle=False, collate_fn=custom_collate)
167
+ test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False, collate_fn=custom_collate)
168
+
169
+ test_image_loader = DataLoader(test_pose_dataset, batch_size=batch_size, shuffle=False, collate_fn=custom_collate)
170
+
171
+ print("Data loaders created.")
172
+
173
+ len(train_dataset)
174
+
175
+
176
+
177
+ # Loss function
178
+ criterion = nn.MSELoss()
179
+
180
+ # Optimizer
181
+ optimizer = optim.Adam(model.parameters(), lr=1e-4)
182
+
183
+ # Convert the model parameters to float
184
+ model = model.float()
185
+
186
+ # Ensure that the tensors are also floats
187
+ sample_batch = next(iter(train_loader))
188
+ import mediapipe as mp
189
+ images = sample_batch['image'].float() # Convert images to float
190
+ keypoints = sample_batch['keypoints'].view(-1, 132).float() # Convert keypoints to float and reshape
191
+
192
+ # Now proceed with the optimization loop
193
+ loss=0
194
+ for epochs in range(10):
195
+ optimizer.zero_grad()
196
+ outputs = model(images)
197
+ loss = criterion(outputs, keypoints)
198
+ loss.backward()
199
+ optimizer.step()
200
+ print("Optimization step completed.")
201
+ print(loss.item())
202
+ loss=loss.item()
203
+
204
+ import torch
205
+
206
+ def calculate_accuracy(outputs, targets):
207
+ accuracy = torch.mean(torch.abs(outputs - targets))
208
+ return accuracy
209
+
210
+ print(outputs.shape)
211
+ # Calculate accuracy
212
+ with torch.no_grad():
213
+ accuracy = calculate_accuracy(outputs, keypoints)
214
+ accuracy= 1- accuracy/132
215
+
216
+ print("Loss:", loss)
217
+ print("Accuracy:", accuracy.item()*100, '%')
218
+
219
+ """As you can see, the accuracy is very close to 100% (Overfitting)
220
+
221
+ Now taking 80-10-10 split on the dataset, we create new train, val and test loaders
222
+ """
223
+
224
+ # Split the dataset into training, validation, and test sets
225
+ train_size = int(0.8* len(human_pose_dataset))
226
+ validation_size = int(0.1 * len(human_pose_dataset))
227
+ test_size = len(human_pose_dataset) - train_size - validation_size
228
+ train_dataset, remaining_dataset = random_split(human_pose_dataset, [train_size, validation_size + test_size])
229
+ validation_dataset, test_dataset = random_split(remaining_dataset, [validation_size, test_size])
230
+
231
+ test_pose_dataset , remaining_data = random_split(testing_pose_dataset,[6,194])
232
+
233
+ # Define the batch size
234
+ batch_size = 8
235
+
236
+ # Create data loaders for each set with the custom collate function
237
+ train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, collate_fn=custom_collate)
238
+ validation_loader = DataLoader(validation_dataset, batch_size=batch_size, shuffle=False, collate_fn=custom_collate)
239
+ test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False, collate_fn=custom_collate)
240
+
241
+ test_image_loader = DataLoader(test_pose_dataset, batch_size=batch_size, shuffle=False, collate_fn=custom_collate)
242
+
243
+ print("Data loaders created.")
244
+
245
+ len(test_dataset)
246
+
247
+ import torch
248
+ import torch.nn as nn
249
+ import torch.optim as optim
250
+ from torch.utils.data import DataLoader, random_split
251
+ from torchvision import transforms
252
+ import torch.nn.functional as F
253
+
254
+ class SimpleCNN(nn.Module):
255
+
256
+ # Define hyperparameters to search over
257
+ learning_rates = [0.001, 0.01, 0.1]
258
+ momentums = [0.9, 0.95, 0.99]
259
+ weight_decays = [0.0001, 0.001, 0.01]
260
+
261
+ best_loss = float('inf')
262
+ best_lr, best_momentum, best_weight_decay = None, None, None
263
+
264
+ # Grid search over hyperparameters
265
+ for lr in learning_rates:
266
+ for momentum in momentums:
267
+ for weight_decay in weight_decays:
268
+ # Initialize the model with the current set of hyperparameters
269
+ model = SimpleCNN()
270
+
271
+ # Define loss function and optimizer
272
+ criterion = nn.MSELoss()
273
+ optimizer = optim.SGD(model.parameters(), lr=lr, momentum=momentum, weight_decay=weight_decay)
274
+
275
+ # Ensure that the tensors are also floats
276
+ sample_batch = next(iter(train_loader))
277
+ import mediapipe as mp
278
+ images = sample_batch['image'].float() # Convert images to float
279
+ keypoints = sample_batch['keypoints'].view(-1, 132).float() # Convert keypoints to float and reshape
280
+
281
+ # Now proceed with the optimization loop
282
+ optimizer.zero_grad()
283
+ outputs = model(images)
284
+ print("Output shape after forward pass:", outputs.shape)
285
+ outputs = model(images)
286
+ loss = criterion(outputs, keypoints)
287
+ print("Initial loss:", loss.item())
288
+ loss.backward()
289
+ optimizer.step()
290
+ print("Optimization step completed.")
291
+
292
+ total_loss = 0
293
+ avg_loss = total_loss / len(train_loader)
294
+ model.train()
295
+
296
+ # Check if the current set of hyperparameters resulted in a better performance
297
+ if avg_loss < best_loss:
298
+ best_loss = avg_loss
299
+ best_lr, best_momentum, best_weight_decay = lr, momentum, weight_decay
300
+
301
+ # After the grid search, choose the hyperparameters that performed the best
302
+ print("Best Hyperparameters - lr: {}, momentum: {}, weight_decay: {}".format(
303
+ best_lr, best_momentum, best_weight_decay))
304
+
305
+ # Train the final model with the selected hyperparameters on the full dataset
306
+ model = SimpleCNN()
307
+ optimizer = optim.SGD(model.parameters(), lr=best_lr, momentum=best_momentum, weight_decay=best_weight_decay)
308
+
309
+ """#3. Plotting Validation and Test Loss
310
+
311
+ The best parameters are:
312
+
313
+ * Learning Rate: 0.001
314
+ * Momentum: 0.9
315
+ * Weight Decay: 0.0001
316
+ """
317
+
318
+ import torch
319
+ import matplotlib.pyplot as plt
320
+
321
+ # Assuming you have already defined your model, optimizer, and criterion
322
+
323
+ # Ensure that the tensors are also floats for training
324
+ sample_batch = next(iter(train_loader))
325
+ images = sample_batch['image'].float()
326
+ keypoints = sample_batch['keypoints'].view(-1, 132).float()
327
+
328
+ # Ensure that the tensors are also floats for validation
329
+ validation_sample_batch = next(iter(validation_loader))
330
+ validation_images = validation_sample_batch['image'].float()
331
+ validation_keypoints = validation_sample_batch['keypoints'].view(-1, 132).float()
332
+
333
+ # Now proceed with the optimization loop
334
+ optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
335
+ criterion = torch.nn.MSELoss()
336
+
337
+ train_loss = []
338
+ val_loss = []
339
+
340
+ for epoch in range(15):
341
+ model.train()
342
+ optimizer.zero_grad()
343
+ outputs = model(images)
344
+ current_loss = criterion(outputs, keypoints)
345
+ current_loss.backward()
346
+ optimizer.step()
347
+
348
+ model.eval() # Switch to evaluation mode for validation
349
+ with torch.no_grad():
350
+ # Calculate validation loss
351
+ val_outputs = model(validation_images)
352
+ val_current_loss = criterion(val_outputs, validation_keypoints)
353
+
354
+ print(f"Epoch [{epoch + 1}/100], Loss: {current_loss.item():.4f}, Val Loss: {val_current_loss.item():.4f}")
355
+ train_loss.append(current_loss.item())
356
+ val_loss.append(val_current_loss.item())
357
+
358
+ plotting_val_loss = val_loss
359
+ plotting_train_loss = train_loss
360
+
361
+ import matplotlib.pyplot as plt
362
+ # Plotting
363
+
364
+ plt.figure(figsize=(8, 4))
365
+
366
+ plt.plot( plotting_train_loss, marker='o', linestyle='-', color='b',label='train loss')
367
+ plt.plot( plotting_val_loss, marker='o', linestyle= '-', color='r', label='val loss')
368
+
369
+ plt.title('Loss vs Epochs')
370
+ plt.xlabel('Epochs')
371
+ plt.ylabel('Loss')
372
+ plt.grid(True)
373
+ plt.legend()
374
+
375
+ # Show the legend in a small box
376
+ plt.legend(loc='upper right')
377
+
378
+ plt.show()
379
+
380
+ """#4. Final Run on Test Dataset"""
381
+
382
+ # Ensure that the tensors are also floats
383
+ sample_batch = next(iter(test_loader))
384
+ import mediapipe as mp
385
+ test_images = sample_batch['image'].float() # Convert images to float
386
+ test_keypoints = sample_batch['keypoints'].view(-1, 132).float() # Convert keypoints to float and reshape
387
+
388
+ model.eval()
389
+
390
+ optimizer.zero_grad()
391
+ outputs = model(test_images)
392
+
393
+ print("Testing Done")
394
+
395
+ test_image_tensor = test_images[0]
396
+
397
+
398
+
399
+ test_actual_plot = test_keypoints.reshape(len(test_images),33,4)[0]
400
+
401
+ test_predict_plot = outputs.reshape(len(test_images),33,4)[0]
402
+
403
+ test_predict_plot.shape
404
+
405
+ """# 4. Finally, evaluate on the test dataset."""
406
+
407
+ import cv2
408
+
409
+ import matplotlib.pyplot as plt
410
+ import numpy as np
411
+
412
+ def plot_human_pose(keypoints):
413
+ # Create a figure and axis
414
+ fig, ax = plt.subplots()
415
+
416
+ # Plot keypoints
417
+ for i in range(len(keypoints)):
418
+ x, y, _, _ = keypoints[i]
419
+ ax.scatter(x, -y, color='blue') # Invert y-axis
420
+
421
+ # Connect body parts
422
+ connect_lines = [(0, 2), (2, 7), # Left eye
423
+ (0, 5), (5, 8), # Right eye
424
+ (9,10), # Left side
425
+ (11, 12), (12, 24), (11, 23), # Right side
426
+ (24,23), (24,26), (23,25), # Connect ears and wrists
427
+ (26, 28), (25, 27),
428
+ (28, 30), (28, 32), (30,32),# Connect left and right pinky fingers
429
+ (27, 29), (27, 31), (31,29), # Connect left and right index fingers
430
+ (12, 14), (11, 13), # Connect left and right thumbs
431
+ (14, 16), (13, 15), # Connect left and right hips
432
+ (16, 18), (18, 20), (16,20), (16,22), # Connect left and right knees
433
+ (15, 17), (15, 19), # Connect left and right ankles
434
+ (17, 19), (15, 21)] # Connect left and right heels
435
+
436
+ for line in connect_lines:
437
+ start, end = line
438
+ x_vals = [keypoints[start][0], keypoints[end][0]]
439
+ y_vals = [-keypoints[start][1], -keypoints[end][1]] # Invert y-axis
440
+ ax.plot(x_vals, y_vals, linewidth=2, color='red')
441
+
442
+ ax.set_aspect('equal', adjustable='datalim')
443
+ plt.title('Actual Pose')
444
+ plt.axis('off')
445
+ plt.show()
446
+
447
+ # Example usage:
448
+ keypoints = test_actual_plot # Replace with your 33 key points
449
+ plot_human_pose(keypoints)
450
+
451
+ def plot_human_pose(keypoints):
452
+ # Create a figure and axis
453
+ fig, ax = plt.subplots()
454
+
455
+ # Plot keypoints
456
+ for i in range(len(keypoints)):
457
+ x, y, _, _ = keypoints[i]
458
+ ax.scatter(x, -y, color='blue') # Invert y-axis
459
+
460
+ # Connect body parts
461
+ connect_lines = [(0, 2), (2, 7), # Left eye
462
+ (0, 5), (5, 8), # Right eye
463
+ (9,10), # Left side
464
+ (11, 12), (12, 24), (11, 23), # Right side
465
+ (24,23), (24,26), (23,25), # Connect ears and wrists
466
+ (26, 28), (25, 27),
467
+ (28, 30), (28, 32), (30,32),# Connect left and right pinky fingers
468
+ (27, 29), (27, 31), (31,29), # Connect left and right index fingers
469
+ (12, 14), (11, 13), # Connect left and right thumbs
470
+ (14, 16), (13, 15), # Connect left and right hips
471
+ (16, 18), (18, 20), (16,20), (16,22), # Connect left and right knees
472
+ (15, 17), (15, 19), # Connect left and right ankles
473
+ (17, 19), (15, 21)] # Connect left and right heels
474
+
475
+ for line in connect_lines:
476
+ start, end = line
477
+ x_vals = [keypoints[start][0], keypoints[end][0]]
478
+ y_vals = [-keypoints[start][1], -keypoints[end][1]] # Invert y-axis
479
+ ax.plot(x_vals, y_vals, linewidth=2, color='green')
480
+
481
+ ax.set_aspect('equal', adjustable='datalim')
482
+ plt.title('Predicted Pose')
483
+ plt.axis('off')
484
+ plt.show()
485
+
486
+ # Example usage:
487
+ keypoints = test_predict_plot.detach().numpy() # Replace with your 33 key points
488
+ plot_human_pose(keypoints)
489
+
490
+ """### As you can see, the model predicts the pose of the person very accurately as depicted by its train and validation accuracy"""
491
+
492
+ from PIL import Image
493
+
494
+ def predict_pose(image_path):
495
+ img = Image.open(str(image_path)).resize((32,32))
496
+ convert_tensor = transforms.ToTensor()
497
+ tensor_img = convert_tensor(img)
498
+ model.eval()
499
+
500
+ optimizer.zero_grad()
501
+ outputs = model(img)
502
+
503
+ pred_keypoints = outputs.reshape(1,33,4)[0]
504
+ pred_keypoints = pred_keypoints.detach().numpy()
505
+
506
+ plot_human_pose(pred_keypoints)
507
+
508
+ pose_detector = gr.Interface(fn = predict_pose, inputs = gr.Image(label = 'input image'), outputs = gr.Image(label = 'output image'), title = 'pose_detector' )
509
+
510
+ gr.TabbedInterface([pose_detector],tab_names = ['pose_detection']).queue().launch()
511
+
512
+
513
+
514
+
515
+
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ gradio==4.7.1
2
+ matplotlib==3.8.2
3
+ mediapipe==0.10.8
4
+ numpy==1.23.5
5
+ Pillow==10.1.0
6
+ torch==2.1.1
7
+ torchvision==0.16.1