this is my code: import os
import math
import random
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision.transforms as transforms
from torchvision.models import resnet50
from torch.utils.data import Dataset, DataLoader
import json
from pathlib import Path
import cv2
import numpy as np
from tqdm import tqdm
from IPython.display import clear_output
import optuna
## Set Up
device = torch.device(‘cuda’ if torch.cuda.is_available() else ‘cpu’)

# Set a random seed for torch
seed = 13
torch.manual_seed(seed)
torch.cuda.manual_seed(seed)
torch.cuda.manual_seed_all(seed) # If using multiple GPUs
np.random.seed(seed)
random.seed(seed)

# Additional configurations for deterministic behavior on CUDA devices
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False

data_path = ‘/workspaces/local_dev/Images/Training’
validation_path = ‘/workspaces/local_dev/Images/validation_set’
model_name = ‘rotnet_resnet50v2’

# Training Parameters
num_samples = 100 # Number of samples used for training. If None then all images
num_val_samples = 25 # Number of samples used for validation. If None then all images
input_shape = (224, 224) # Tuple defining the input image shape (height, width)

# Optuna Optimization Parameters
n_trial = 100 # Number of Optuna Trials
learning_rate_options = (1e-5, 1e-1) # Learning rate for the optimizer
batch_size_options = [8, 16] # Batch size for training
num_epochs_options = (50,50) # Number of epochs to train the model for
weight_decay_options = (1e-6, 0.1)
dropout_rate_options = (0.001, 0.5)

# Define Defs
# Error Function
def angle_difference(x, y):
“”“
Calculate minimum difference between two angles.
“””
return 180 - abs(abs(x - y) - 180)

def angle_error(y_true, y_pred):
“”“
Calculate the mean difference between the true angles
and the predicted angles.
“””
diff = angle_difference(y_true, y_pred)
return torch.mean(torch.abs(diff.float()))

# Rotation Related
def rotate(image, angle):
“”“
Rotates an OpenCV 2 / NumPy image about its center by the given angle
(in degrees).
“””
center = (image.shape[1] / 2, image.shape[0] / 2)
rot_mat = cv2.getRotationMatrix2D(center, angle, 1.0)
return cv2.warpAffine(image, rot_mat, (image.shape[1], image.shape[0]), flags=cv2.INTER_LINEAR)

def largest_rotated_rect(w, h, angle):
“”“
Given a rectangle of size wxh that has been rotated by ‘angle’ (in
radians), computes the width and height of the largest possible
axis-aligned rectangle within the rotated rectangle.

Original JS code by ‘Andri’ and Magnus Hoff from Stack Overflow

Converted to Python by Aaron Snoswell

Source: http://stackoverflow.com/questions/16702966/rotate-image-and-crop-out-black-borders
“””

quadrant = int(math.floor(angle / (math.pi / 2))) & 3
sign_alpha = angle if ((quadrant & 1) == 0) else math.pi - angle
alpha = (sign_alpha % math.pi + math.pi) % math.pi

bb_w = w * math.cos(alpha) + h * math.sin(alpha)
bb_h = w * math.sin(alpha) + h * math.cos(alpha)

gamma = math.atan2(bb_w, bb_w) if (w < h) else math.atan2(bb_w, bb_w)

delta = math.pi - alpha - gamma

length = h if (w < h) else w

d = length * math.cos(alpha)
a = d * math.sin(alpha) / math.sin(delta)

y = a * math.cos(gamma)
x = y * math.tan(gamma)

return (
bb_w - 2 * x,
bb_h - 2 * y
)


def crop_around_center(image, width, height):
“”“
Given a NumPy / OpenCV 2 image, crops it to the given width and height,
around it’s centre point

Source: http://stackoverflow.com/questions/16702966/rotate-image-and-crop-out-black-borders
“””

image_size = (image.shape[1], image.shape[0])
image_center = (int(image_size[0] * 0.5), int(image_size[1] * 0.5))

if(width > image_size[0]):
width = image_size[0]

if(height > image_size[1]):
height = image_size[1]

x1 = int(image_center[0] - width * 0.5)
x2 = int(image_center[0] + width * 0.5)
y1 = int(image_center[1] - height * 0.5)
y2 = int(image_center[1] + height * 0.5)

return image[y1:y2, x1:x2]


def crop_largest_rectangle(image, angle, height, width):
“”“
Crop around the center the largest possible rectangle
found with largest_rotated_rect.
“””
return crop_around_center(
image,
*largest_rotated_rect(
width,
height,
math.radians(angle)
)
)

def generate_rotated_image(image, angle, size=None, crop_center=False,
crop_largest_rect=False):
“”“
Generate a valid rotated image for the RotNetDataGenerator. If the
image is rectangular, the crop_center option should be used to make
it square. To crop out the black borders after rotation, use the
crop_largest_rect option. To resize the final image, use the size
option.
“””
height, width = image.shape[:2]
if crop_center:
if width < height:
height = width
else:
width = height

image = rotate(image, angle)

if crop_largest_rect:
image = crop_largest_rectangle(image, angle, height, width)

if size:
image = cv2.resize(image, size)

return image

def get_filenames(path, num_samples=None):
image_paths = []

# Recursively traverse the directory structure
for root, _, filenames in os.walk(path):
for filename in filenames:
image_paths.append(os.path.join(root, filename))

# Use all available images if num_samples is not provided
if num_samples is None:
train_filenames = image_paths
else:
# Randomly select num_samples images
train_filenames = random.sample(image_paths, min(num_samples, len(image_paths)))

# Shuffle the train_filenames list
random.shuffle(train_filenames)

return train_filenames

# Dataset class for RotNet data
class RotNetDataset(Dataset):
def init(self, filenames, num_samples, input_shape, transform=None):
self.filenames = filenames
self.num_samples = num_samples
self.input_shape = input_shape
self.transform = transform

def len(self):
return min(self.num_samples, len(self.filenames))

def getitem(self, idx):
image = cv2.imread(self.filenames[idx])
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

rotation_angle = random.randint(0, 359)

rotated_image = generate_rotated_image(
image,
rotation_angle,
size=self.input_shape,
crop_center=True,
crop_largest_rect=True
)

sample = {‘image’: rotated_image, ‘angle’: rotation_angle}

if self.transform:
sample[‘image’] = self.transform(sample[‘image’])

return sample

# Define transforms
transform = transforms.Compose([
transforms.ToPILImage(),
transforms.Resize(input_shape),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

## Run

## Run
# Model definition
num_classes = 360 # The degrees of a circle
class RotNetModel(nn.Module):
def init(self, num_classes):
super(RotNetModel, self).init()
self.resnet = resnet50(weights=True)
self.resnet.fc = nn.Linear(2048, num_classes)

def forward(self, x):
return self.resnet(x)

# Initialize the model
model = RotNetModel(num_classes=num_classes).to(device)

# Create output folder if it doesn’t exist
output_folder = ‘models’
Path(output_folder).mkdir(parents=True, exist_ok=True)
# Training loop
def train_model(model, dataloader, criterion, optimizer, start_epoch=0, num_epochs=50, patience=10):
model.train()
best_loss = float(‘inf’)
epochs_without_improvement = 0 # Count how many epochs without improvement

for epoch in range(start_epoch, num_epochs):
running_loss = 0.0
for i, data in enumerate(dataloader, 0):
inputs, labels = data[‘image’].to(device), data[‘angle’].to(device)

optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()

running_loss += loss.item()

# Log training loss to TensorBoard
global_step = epoch * len(dataloader) + i

epoch_loss = running_loss / len(dataloader)
print(f’Epoch [{epoch + 1}/{num_epochs}] Loss: {epoch_loss:.4f}‘)

if epoch_loss < best_loss:
best_loss = epoch_loss
epochs_without_improvement = 0 # Reset counter
torch.save(model.state_dict(), os.path.join(output_folder, model_name + ‘.pth’))
else:
epochs_without_improvement += 1

if epochs_without_improvement >= patience:
print(f’Early stopping after {patience} epochs without improvement.’)
break

return model # Return the trained model


# Define the objective function for Optuna
def objective(trial):
learning_rate = trial.suggest_float(‘learning_rate’, *learning_rate_options, log=True)
weight_decay = trial.suggest_float(‘weight_decay’, *weight_decay_options, log=True)
batch_size = trial.suggest_categorical(‘batch_size’, batch_size_options)
num_epochs = trial.suggest_int(‘num_epochs’, *num_epochs_options)
dropout_rate = trial.suggest_float(‘dropout_rate’, *dropout_rate_options, log=True)
optimizer_choice = trial.suggest_categorical(‘optimizer’, [‘SGD’, ‘Adam’, ‘RMSprop’])

# Define the model
model = RotNetModel(num_classes=num_classes).to(device)

# Define the optimizer with suggested learning rate
if optimizer_choice == ‘SGD’:
optimizer = optim.SGD(model.parameters(), lr=learning_rate, momentum=0.9, weight_decay=weight_decay)
elif optimizer_choice == ‘Adam’:
optimizer = optim.Adam(model.parameters(), lr=learning_rate, weight_decay=weight_decay)
elif optimizer_choice == ‘RMSprop’:
optimizer = optim.RMSprop(model.parameters(), lr=learning_rate, weight_decay=weight_decay)

# Initialize data loaders
train_dataset = RotNetDataset(train_filenames, num_samples, input_shape, transform=transform)
train_dataloader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=8)

# Train the model
train_model(model, train_dataloader, criterion, optimizer, num_epochs=num_epochs)

# Calculate the angle error on the validation set
model.eval()
validation_Data = RotNetDataset(validation_filenames, num_samples, input_shape, transform=transform)
validation_dataloader = DataLoader(validation_Data, batch_size=batch_size, shuffle=False, num_workers=4)

total_angle_error = 0.0
total_samples = 0

with torch.no_grad():
for data in validation_dataloader:
inputs, labels = data[‘image’].to(device), data[‘angle’].to(device)
outputs = model(inputs)
predicted_angles = outputs.argmax(dim=1)
angle_errors = angle_error(labels, predicted_angles)

total_angle_error += angle_errors.item() * len(labels)
total_samples += len(labels)

# Calculate the mean angle error for the validation set
mean_angle_error = total_angle_error / total_samples

# Clear the output after each iteration
clear_output(wait=True)

# Return the mean angle error to Optuna (minimize it)
return mean_angle_error

if name == ‘main’:
# Initialize the model
model = RotNetModel(num_classes=num_classes).to(device)

# Loss function and optimizer
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)

# Create output folder if it doesn’t exist
output_folder = ‘models’
Path(output_folder).mkdir(parents=True, exist_ok=True)

# Training loop using Optuna
study = optuna.create_study(direction=‘minimize’)
study.optimize(objective, n_trials=n_trial)
print(‘Number of finished trials:’, len(study.trials))
print(‘Best trial:’)
trial = study.best_trial

print(‘Value:’, trial.value)
print(‘Params:’)
best_params = {}
for key, value in trial.params.items():
print(f’ {key}: {value}')
best_params[key] = value # Store best hyperparameters

# Save the best hyperparameters to a file
best_params_path = os.path.join(output_folder, ‘best_hyperparameters.json’)
with open(best_params_path, ‘w’) as f:
json.dump(best_params, f)
saved_file_path = os.path.join(output_folder, model_name + ‘.pth’)
if os.path.exists(saved_file_path):
os.remove(saved_file_path)
print(f"File {saved_file_path} deleted.“)
else:
print(f"File {saved_file_path} not found.”)