JustAGeek commited on
Commit
f63ef57
·
verified ·
1 Parent(s): ecc201f

Upload train_unet_advanced.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. train_unet_advanced.py +242 -0
train_unet_advanced.py ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ train_unet_advanced.py
3
+ Train MobileNetV2-UNet student with LR scheduler comparison (Phase 2, Step 5).
4
+
5
+ Supports:
6
+ - Option 1: MobileNetV2 + U-Net skip connections (student_unet.py)
7
+ - Option 2: Cosine Annealing LR scheduler
8
+ - Option 3: Step Decay LR scheduler (halve every 20 epochs)
9
+ - Option 4: Knowledge distillation with best scheduler + U-Net
10
+ - MODE: 'standalone' or 'distill'
11
+
12
+ Usage:
13
+ # Compare schedulers in standalone mode:
14
+ python train_unet_advanced.py # edit SCHEDULER_TYPE below
15
+
16
+ # After finding best scheduler, run distillation:
17
+ # Set MODE='distill' and SCHEDULER_TYPE to the winner
18
+
19
+ Requires pre-computed teacher outputs for distillation mode
20
+ (run precompute_teacher.py first).
21
+ """
22
+ import sys
23
+ import os
24
+ import time
25
+ import torch
26
+ import torch.nn as nn
27
+ import numpy as np
28
+ import warnings
29
+ with warnings.catch_warnings():
30
+ warnings.filterwarnings("ignore", category=FutureWarning)
31
+
32
+ from utils import localization_error, write_log
33
+ from data_loader import load_data
34
+ from student_unet import MobileNetUNetStudent
35
+
36
+ # ============================================================
37
+ # CONFIG
38
+ # ============================================================
39
+ MODE = 'standalone' # 'standalone' or 'distill'
40
+ SCHEDULER_TYPE = 'cosine' # 'cosine' or 'step'
41
+
42
+ EPOCHS = 120
43
+ LR = 1e-4
44
+ BATCH_SIZE = 32
45
+ NUM_WORKERS = 4
46
+
47
+ ALPHA = 0.7 # ground truth loss weight (distillation)
48
+ BETA = 0.3 # teacher loss weight (distillation)
49
+
50
+ TEACHER_TRAIN_PT = './data/teacher_train_outputs.pt'
51
+ TEACHER_TEST_PT = './data/teacher_test_outputs.pt'
52
+
53
+ # Data paths (Fig 10b -- Jacobs Hall)
54
+ trainpath = [
55
+ './data/dataset_jacobs_July28.mat',
56
+ './data/dataset_non_fov_train_jacobs_July28_2.mat',
57
+ './data/dataset_fov_train_jacobs_July28_2.mat',
58
+ ]
59
+ testpath = [
60
+ './data/dataset_fov_test_jacobs_July28_2.mat',
61
+ './data/dataset_non_fov_test_jacobs_July28_2.mat',
62
+ ]
63
+
64
+ # ============================================================
65
+ # SETUP
66
+ # ============================================================
67
+ torch.manual_seed(0)
68
+ np.random.seed(0)
69
+
70
+ device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
71
+ run_label = f'{MODE}_unet_{SCHEDULER_TYPE}'
72
+ save_name = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime())
73
+ save_dir = os.path.join('./runs', f'{run_label}_{save_name}')
74
+ os.makedirs(save_dir, exist_ok=True)
75
+
76
+ print(f'Device: {device}')
77
+ print(f'Mode: {MODE} | Scheduler: {SCHEDULER_TYPE} | Epochs: {EPOCHS}')
78
+ print(f'Save dir: {save_dir}')
79
+
80
+ # ============================================================
81
+ # LOAD DATA
82
+ # ============================================================
83
+ print('Loading training data...')
84
+ train_w_list, train_lbl_list = [], []
85
+ for fp in trainpath:
86
+ _, w, lbl = load_data(fp)
87
+ if lbl.dim() == 3:
88
+ lbl = lbl.unsqueeze(1)
89
+ train_w_list.append(w)
90
+ train_lbl_list.append(lbl)
91
+ train_w = torch.cat(train_w_list, dim=0)
92
+ train_lbl = torch.cat(train_lbl_list, dim=0)
93
+ print(f'Training samples: {train_w.shape[0]}')
94
+
95
+ print('Loading testing data...')
96
+ test_w_list, test_lbl_list = [], []
97
+ for fp in testpath:
98
+ _, w, lbl = load_data(fp)
99
+ if lbl.dim() == 3:
100
+ lbl = lbl.unsqueeze(1)
101
+ test_w_list.append(w)
102
+ test_lbl_list.append(lbl)
103
+ test_w = torch.cat(test_w_list, dim=0)
104
+ test_lbl = torch.cat(test_lbl_list, dim=0)
105
+ print(f'Testing samples: {test_w.shape[0]}')
106
+
107
+ # Load teacher outputs if distilling
108
+ if MODE == 'distill':
109
+ print('Loading pre-computed teacher outputs...')
110
+ train_teacher = torch.load(TEACHER_TRAIN_PT, weights_only=True)
111
+ test_teacher = torch.load(TEACHER_TEST_PT, weights_only=True)
112
+ assert train_teacher.shape[0] == train_w.shape[0], \
113
+ f'Teacher/data mismatch: {train_teacher.shape[0]} vs {train_w.shape[0]}'
114
+ print(f'Teacher outputs: train={train_teacher.shape}, test={test_teacher.shape}')
115
+ train_data = torch.utils.data.TensorDataset(train_w, train_lbl, train_teacher)
116
+ test_data = torch.utils.data.TensorDataset(test_w, test_lbl, test_teacher)
117
+ else:
118
+ train_data = torch.utils.data.TensorDataset(train_w, train_lbl)
119
+ test_data = torch.utils.data.TensorDataset(test_w, test_lbl)
120
+
121
+ train_loader = torch.utils.data.DataLoader(
122
+ train_data, batch_size=BATCH_SIZE, shuffle=True,
123
+ num_workers=NUM_WORKERS, pin_memory=True, persistent_workers=True,
124
+ )
125
+ test_loader = torch.utils.data.DataLoader(
126
+ test_data, batch_size=BATCH_SIZE, shuffle=False,
127
+ num_workers=NUM_WORKERS, pin_memory=True, persistent_workers=True,
128
+ )
129
+
130
+ # ============================================================
131
+ # MODEL + OPTIMIZER + SCHEDULER
132
+ # ============================================================
133
+ student = MobileNetUNetStudent(input_nc=4).to(device)
134
+ n_params = sum(p.numel() for p in student.parameters())
135
+ print(f'U-Net student params: {n_params:,} ({n_params/1e6:.2f}M)')
136
+
137
+ optimizer = torch.optim.Adam(student.parameters(), lr=LR)
138
+ criterion = nn.MSELoss()
139
+
140
+ if SCHEDULER_TYPE == 'cosine':
141
+ scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=EPOCHS)
142
+ elif SCHEDULER_TYPE == 'step':
143
+ # Halve LR every 20 epochs (professor's spec)
144
+ scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=20, gamma=0.5)
145
+ else:
146
+ raise ValueError(f'Unknown scheduler: {SCHEDULER_TYPE}')
147
+
148
+ # ============================================================
149
+ # TRAINING LOOP
150
+ # ============================================================
151
+ print(f'Starting {MODE} training with {SCHEDULER_TYPE} scheduler...')
152
+ best_median = float('inf')
153
+
154
+ for epoch in range(1, EPOCHS + 1):
155
+ epoch_start = time.time()
156
+
157
+ # --- TRAIN ---
158
+ student.train()
159
+ train_loss_sum = 0
160
+ train_errors = []
161
+
162
+ for data in train_loader:
163
+ inputs = data[0].to(device)
164
+ labels = data[1].to(device)
165
+
166
+ student_output = student(inputs)
167
+ L_GT = criterion(student_output, labels)
168
+
169
+ if MODE == 'distill':
170
+ teacher_out = data[2].to(device)
171
+ L_KD = criterion(student_output, teacher_out)
172
+ loss = ALPHA * L_GT + BETA * L_KD
173
+ else:
174
+ loss = L_GT
175
+
176
+ optimizer.zero_grad()
177
+ loss.backward()
178
+ optimizer.step()
179
+
180
+ train_loss_sum += loss.item()
181
+ train_errors.extend(localization_error(
182
+ student_output.detach().cpu().numpy(),
183
+ labels.cpu().numpy(),
184
+ scale=0.1,
185
+ ))
186
+
187
+ train_loss_avg = train_loss_sum / len(train_loader)
188
+ train_median = np.median(train_errors)
189
+
190
+ # --- TEST ---
191
+ student.eval()
192
+ test_loss_sum = 0
193
+ test_errors = []
194
+
195
+ with torch.no_grad():
196
+ for data in test_loader:
197
+ inputs = data[0].to(device)
198
+ labels = data[1].to(device)
199
+
200
+ student_output = student(inputs)
201
+ loss = criterion(student_output, labels)
202
+
203
+ test_loss_sum += loss.item()
204
+ test_errors.extend(localization_error(
205
+ student_output.cpu().numpy(),
206
+ labels.cpu().numpy(),
207
+ scale=0.1,
208
+ ))
209
+
210
+ test_loss_avg = test_loss_sum / len(test_loader)
211
+ test_median = np.median(test_errors)
212
+ test_p90 = np.percentile(test_errors, 90)
213
+ test_p99 = np.percentile(test_errors, 99)
214
+ current_lr = optimizer.param_groups[0]['lr']
215
+ elapsed = time.time() - epoch_start
216
+
217
+ # Step scheduler
218
+ scheduler.step()
219
+
220
+ # --- LOG ---
221
+ write_log([str(train_loss_avg)], run_label, log_dir=save_dir, log_type='train_loss')
222
+ write_log([str(train_median)], run_label, log_dir=save_dir, log_type='train_median_error')
223
+ write_log([str(test_loss_avg)], run_label, log_dir=save_dir, log_type='test_loss')
224
+ write_log([str(test_median)], run_label, log_dir=save_dir, log_type='test_median_error')
225
+ write_log([str(test_p90)], run_label, log_dir=save_dir, log_type='test_90_error')
226
+ write_log([str(test_p99)], run_label, log_dir=save_dir, log_type='test_99_error')
227
+
228
+ marker = ' <-- NEW BEST' if test_median < best_median else ''
229
+ print(f'Epoch {epoch}/{EPOCHS} | {elapsed:.0f}s | LR={current_lr:.6f} | '
230
+ f'train_med={train_median:.3f}m | '
231
+ f'test_med={test_median:.3f}m P90={test_p90:.3f}m P99={test_p99:.3f}m{marker}')
232
+
233
+ # --- SAVE ---
234
+ if test_median < best_median:
235
+ best_median = test_median
236
+ torch.save(student.state_dict(), os.path.join(save_dir, 'best_unet.pth'))
237
+
238
+ torch.save(student.state_dict(), os.path.join(save_dir, 'latest_unet.pth'))
239
+
240
+ print(f'\nDone. Best median error: {best_median:.4f}m')
241
+ print(f'Scheduler: {SCHEDULER_TYPE} | Mode: {MODE}')
242
+ print(f'Results saved to: {save_dir}')