Skip to content

Commit 8215e32

Browse files
committed
Updated model and added new dataset to improve results.
1 parent 9eb67cc commit 8215e32

10 files changed

+183
-33
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ share/python-wheels/
2626
*.egg
2727
MANIFEST
2828
dataset/
29+
dataset2/
2930
save/
3031

3132
# PyInstaller

calculate_dataset_std_mean.py

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import torch
2+
from dataset_loader import get_full_dataset_loader
3+
4+
5+
def batch_mean_and_sd(loader):
6+
7+
cnt = 0
8+
fst_moment = torch.empty(3)
9+
snd_moment = torch.empty(3)
10+
11+
for images, _ in loader:
12+
b, c, h, w = images.shape
13+
nb_pixels = b * h * w
14+
sum_ = torch.sum(images, dim=[0, 2, 3])
15+
sum_of_square = torch.sum(images ** 2, dim=[0, 2, 3])
16+
fst_moment = (cnt * fst_moment + sum_) / (cnt + nb_pixels)
17+
snd_moment = (cnt * snd_moment + sum_of_square) / (cnt + nb_pixels)
18+
cnt += nb_pixels
19+
20+
mean, std = fst_moment, torch.sqrt(snd_moment - fst_moment ** 2)
21+
return mean,std
22+
23+
mean, std = batch_mean_and_sd(get_full_dataset_loader(dataset_type='udacity'))
24+
25+
print(f"mean {mean}, and std: {std}")

config.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@
44

55
@dataclass
66
class Config(object):
7+
dataset_type = "udacity"
78
batch_size = 50
89
num_workers = 8
910
shuffle = True
1011
train_split_size = 0.8
1112
test_split_size = 0.2
1213
resize = (66, 200)
13-
mean = [0.3568, 0.3770, 0.3691]
14-
std = [0.2121, 0.2040, 0.1968]
1514
epochs_count = 60
1615
learning_rate = 1e-4
1716
weight_decay = 1e-5
@@ -25,5 +24,8 @@ class Config(object):
2524
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
2625
test_interval = 1
2726

27+
scheduler_step_size = 10 # Decrease the learning rate every 10 epochs
28+
scheduler_gamma = 0.1 # Multiply the learning rate by 0.1 when decreasing
29+
2830

2931
config = Config()

dataset_loader.py

+75-11
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212

1313
transform_img = transforms.Compose([
14-
transforms.Resize(config.resize),
14+
transforms.Resize(config.resize, antialias=True),
1515
transforms.ToTensor(),
1616
])
1717

@@ -35,23 +35,80 @@ def __getitem__(self, idx):
3535
img_name = os.path.join(os.path.join(self.root_dir, "data"), self.dataframe.iloc[idx, 0])
3636
image = Image.open(img_name)
3737
width, height = image.size
38-
area = (0, 90, width, height)
38+
area = (100, 125, width, height)
3939
cropped_img = image.crop(area)
4040

4141
y = np.radians(self.dataframe.iloc[idx, 1])
4242
if self.transform:
4343
cropped_img = self.transform(cropped_img)
4444

45-
return cropped_img.float(), np.float(y)
45+
return cropped_img, float(y)
46+
47+
@staticmethod
48+
def get_mean():
49+
return [0.3568, 0.3770, 0.3691]
4650

51+
@staticmethod
52+
def get_std():
53+
return [0.2121, 0.2040, 0.1968]
54+
55+
56+
class UdacityDataset(Dataset):
57+
def __init__(self, csv_file="interpolated.csv", root_dir="dataset2", transform=None):
58+
self.transform = transform
59+
self.dataset_folder = root_dir
60+
self.data = pd.read_csv(os.path.join(root_dir, csv_file))
61+
62+
# Filter for center_camera images only
63+
self.data = self.data[self.data['frame_id'] == 'center_camera']
64+
65+
def __len__(self):
66+
return len(self.data)
67+
68+
def __getitem__(self, idx):
69+
if torch.is_tensor(idx):
70+
idx = idx.tolist()
71+
72+
img_name = os.path.join(self.dataset_folder, self.data.iloc[idx]['filename'])
73+
image = Image.open(img_name)
74+
width, height = image.size
75+
area = (0, 180, width, height)
76+
cropped_img = image.crop(area)
77+
78+
angle = np.radians(self.data.iloc[idx]['angle'])
79+
80+
if self.transform:
81+
image = self.transform(cropped_img)
82+
83+
return image, float(angle)
84+
85+
@staticmethod
86+
def get_mean():
87+
return [0.2957, 0.3153, 0.3688]
88+
89+
@staticmethod
90+
def get_std():
91+
return [0.2556, 0.2609, 0.2822]
92+
93+
94+
def get_data_subsets_loaders(dataset_type='sully') -> (DataLoader, DataLoader):
95+
dataset_class = None
96+
97+
if dataset_type == 'sully':
98+
dataset_class = SullyChenDataset
99+
elif dataset_type == 'udacity':
100+
dataset_class = UdacityDataset
101+
else:
102+
raise ValueError("Invalid dataset type")
47103

48-
def get_data_subsets_loaders() -> (DataLoader, DataLoader):
49104
transform_img = transforms.Compose([
50105
transforms.ToTensor(),
51-
transforms.Resize(config.resize),
52-
transforms.Normalize(config.mean, config.std)
106+
transforms.Resize(config.resize, antialias=True),
107+
transforms.Normalize(dataset_class.get_mean(), dataset_class.get_std())
53108
])
54-
dataset = SullyChenDataset(transform=transform_img)
109+
110+
dataset = dataset_class(transform=transform_img)
111+
55112
train_set, val_set = random_split(dataset, [config.train_split_size, config.test_split_size])
56113

57114
train_subset_loader = DataLoader(
@@ -69,8 +126,12 @@ def get_data_subsets_loaders() -> (DataLoader, DataLoader):
69126
return train_subset_loader, val_subset_loader
70127

71128

72-
def get_full_dataset() -> DataLoader:
73-
dataset = SullyChenDataset(transform=transform_img)
129+
def get_full_dataset_loader(dataset_type='sully') -> DataLoader:
130+
if dataset_type == 'sully':
131+
dataset = SullyChenDataset(transform=transform_img)
132+
elif dataset_type == 'udacity':
133+
dataset = UdacityDataset(transform=transform_img)
134+
74135
full_dataset_loader = DataLoader(
75136
dataset,
76137
batch_size=1,
@@ -80,5 +141,8 @@ def get_full_dataset() -> DataLoader:
80141
return full_dataset_loader
81142

82143

83-
def get_inference_dataset() -> DataLoader:
84-
return SullyChenDataset(transform=transform_img)
144+
def get_inference_dataset(dataset_type='sully') -> DataLoader:
145+
if dataset_type == 'sully':
146+
return SullyChenDataset(transform=transform_img)
147+
elif dataset_type == 'udacity':
148+
return UdacityDataset(transform=transform_img)

inference_run_dataset.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ def main():
2121
dataset_iterator = iter(dataset)
2222

2323
model = NvidiaModel()
24-
model.load_state_dict(torch.load("./save/model.pt"))
24+
model.load_state_dict(torch.load("./save/model.pt", map_location=torch.device(config.device)))
25+
model.to(config.device)
2526
model.eval()
2627

2728
steering_wheel_1 = cv2.imread('./steering_wheel_tesla.jpg', 0)
@@ -33,15 +34,17 @@ def main():
3334

3435
while cv2.waitKey(20) != ord('q'):
3536
transformed_image, image, target = next(dataset_iterator)
37+
transformed_image = transformed_image.to(config.device)
38+
3639
batch_t = torch.unsqueeze(transformed_image, 0)
3740

3841
# Predictions
3942
with torch.no_grad():
4043
y_predict = model(batch_t)
4144

4245
# Converting prediction to degrees
43-
pred_degrees = np.degrees(y_predict[0].item() * 2)
44-
target_degrees = np.degrees(target.item() * 2)
46+
pred_degrees = np.degrees(y_predict[0].item())
47+
target_degrees = np.degrees(target)
4548

4649
print(f"Predicted Steering angle: {pred_degrees}")
4750
print(f"Steering angle: {pred_degrees} (pred)\t {target_degrees} (actual)")

inference_run_video.py

+9-8
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
transform_img = transforms.Compose([
1010
transforms.ToTensor(),
11-
transforms.Resize(config.resize),
11+
transforms.Resize(config.resize, antialias=True),
1212
transforms.Normalize(config.mean, config.std)
1313
])
1414

@@ -22,14 +22,15 @@ def angel_to_steer(degrees, cols, rows, smoothed_angle):
2222
def crop_down(image):
2323
h = image.shape[0]
2424
w = image.shape[1]
25-
y = 150
26-
x = 60
27-
return image[60:int(y+h), int(x):int(x+(w-(x+90)))]
25+
y = 350
26+
x = 90
27+
return image[190:int(y+h), int(x):int(x+(w-(x+150)))]
2828

2929

3030
def main():
3131
model = NvidiaModel()
32-
model.load_state_dict(torch.load("./save/model.pt"))
32+
model.load_state_dict(torch.load("./save/model.pt", map_location=torch.device(config.device)))
33+
model.to(config.device)
3334
model.eval()
3435

3536
steering_wheel_1 = cv2.imread('./steering_wheel_tesla.jpg', 0)
@@ -49,19 +50,19 @@ def main():
4950
break
5051

5152
image_cropped = crop_down(image)
52-
frame = transform_img(cv2.cvtColor(image_cropped, cv2.COLOR_BGR2RGB)).double()
53+
frame = transform_img(cv2.cvtColor(image_cropped, cv2.COLOR_BGR2RGB)).to(config.device)
5354
batch_t = torch.unsqueeze(frame, 0)
5455

5556
# Predictions
5657
with torch.no_grad():
5758
y_predict = model(batch_t)
5859

5960
# Converting prediction to degrees
60-
pred_degrees = np.degrees(y_predict[0].item() * 2)
61+
pred_degrees = np.degrees(y_predict.item())
6162

6263
print(f"Predicted Steering angle: {pred_degrees}")
6364
print(f"Steering angle: {pred_degrees} (pred)")
64-
cv2.imshow("frame", image_cropped)
65+
cv2.imshow("frame", image)
6566

6667
# make smooth angle transitions by turning the steering wheel based on the difference of the current angle
6768
# and the predicted angle

loss_comparison.png

23.1 KB
Loading

model.py

+1-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1-
import torch
21
import torch.nn as nn
3-
import scipy
42

53

64
class NvidiaModel(nn.Module):
75
def __init__(self):
86
super().__init__()
9-
self.scale_factor = 2
107

118
# define layers using nn.Sequential
129
self.conv_layers = nn.Sequential(
@@ -68,5 +65,4 @@ def __init__(self):
6865
def forward(self, x):
6966
x = self.conv_layers(x)
7067
x = self.flat_layers(x)
71-
x = torch.flatten(x)
72-
return x
68+
return x.squeeze()

plot_loss_results.py

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import pandas as pd
2+
import matplotlib.pyplot as plt
3+
import argparse
4+
5+
def plot_loss_comparison(file_relu, file_elu):
6+
# Read the CSV files
7+
df_relu = pd.read_csv(file_relu)
8+
df_elu = pd.read_csv(file_elu)
9+
10+
# Create a figure and axis for the plot
11+
fig, ax = plt.subplots()
12+
13+
# Plot the loss values from both files
14+
ax.plot(df_relu.index, df_relu['loss'], label="Loss ReLU")
15+
ax.plot(df_elu.index, df_elu['loss'], label="Loss ELU")
16+
17+
# Set labels for the x and y axes
18+
ax.set_xlabel("Iteration")
19+
ax.set_ylabel("Loss")
20+
21+
# Add a legend
22+
ax.legend()
23+
24+
# Save the plot to a PNG file
25+
fig.savefig("loss_comparison.png")
26+
27+
# Optionally, close the plot
28+
plt.close(fig)
29+
30+
if __name__ == "__main__":
31+
parser = argparse.ArgumentParser(description="Compare loss values from two CSV files.")
32+
parser.add_argument("file_relu", type=str, help="Path to the first (ReLU) CSV file")
33+
parser.add_argument("file_elu", type=str, help="Path to the second (ELU) CSV file")
34+
35+
args = parser.parse_args()
36+
37+
plot_loss_comparison(args.file_relu, args.file_elu)

train.py

+25-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
import os
2+
import time
23
import torch
34
import torch.nn as nn
45
import torch.optim as optim
56
import torch.nn.functional as F
7+
import torch.optim.lr_scheduler as lr_scheduler
68
import dataset_loader
79
import numpy as np
810
import pandas as pd
911
from model import NvidiaModel
1012
from config import config
13+
import argparse
14+
15+
parser = argparse.ArgumentParser(description="Compare loss values from two CSV files.")
16+
parser.add_argument("--dataset_type", type=str, help="Dataset type", choices=['sully', 'udacity'], default='sully')
1117

1218

1319
def save_model(model, log_dir="./save"):
@@ -52,8 +58,10 @@ def validation(model, val_subset_loader, loss_function):
5258

5359

5460
def main():
61+
args = parser.parse_args()
62+
5563
# train over the dataset about 30 times
56-
train_subset_loader, val_subset_loader = dataset_loader.get_data_subsets_loaders()
64+
train_subset_loader, val_subset_loader = dataset_loader.get_data_subsets_loaders(dataset_type=args.dataset_type)
5765
test_loader = iter(val_subset_loader)
5866
num_images = len(train_subset_loader.dataset) + len(val_subset_loader.dataset)
5967

@@ -63,6 +71,9 @@ def main():
6371
# Optimizer
6472
optimizer = optim.Adam(model.parameters(), lr=config.learning_rate, weight_decay=config.weight_decay)
6573

74+
# Learning rate scheduler
75+
scheduler = lr_scheduler.StepLR(optimizer, step_size=config.scheduler_step_size, gamma=config.scheduler_gamma)
76+
6677
# Loss function using MSE
6778
loss_function = nn.MSELoss()
6879

@@ -71,6 +82,8 @@ def main():
7182
batch_loss_mean = np.array([])
7283
batch_val_loss = np.array([])
7384

85+
start_time = time.time() # record the start time
86+
7487
for epoch in range(config.epochs_count):
7588
# change model in training mood
7689
model.train()
@@ -101,15 +114,23 @@ def main():
101114
batch_loss_mean = np.append(batch_loss_mean, [epoch_loss])
102115
print(f'Epoch: {epoch+1}/{config.epochs_count} Batch {batch_idx} \nTrain Loss: {epoch_loss:.6f}')
103116

117+
# Update learning rate
118+
scheduler.step()
119+
104120
val_loss_mean = validation(model, val_subset_loader, loss_function)
105121
batch_val_loss = np.append(batch_val_loss, [val_loss_mean.item()])
106122
save_model(model)
107-
123+
124+
end_time = time.time() # record the end time
125+
elapsed_time = end_time - start_time # calculate the elapsed time
126+
print(f"Training took {elapsed_time:.2f} seconds")
127+
108128
if not os.path.exists('logs'):
109129
os.makedirs('logs')
110130

111-
pd.DataFrame({"loss": batch_loss_mean}).to_csv("logs/loss_acc_results.csv", index=None)
112-
pd.DataFrame({"val_loss": batch_val_loss}).to_csv("logs/loss_acc_validation.csv", index=None)
131+
pd.DataFrame({"loss": batch_loss_mean}).to_csv(f"logs/loss_acc_results_{config.dataset_type}.csv", index=None)
132+
pd.DataFrame({"val_loss": batch_val_loss}).to_csv(f"logs/loss_acc_validation_{config.dataset_type}.csv", index=None)
133+
113134
print("loss_acc_results.csv saved!")
114135

115136

0 commit comments

Comments
 (0)