JustAGeek commited on
Commit
e839f3b
·
verified ·
1 Parent(s): f31d6bc

Upload eval_cross_session.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. eval_cross_session.py +202 -0
eval_cross_session.py ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ eval_cross_session.py
3
+ Cross-session generalization evaluation (Phase 2, Step 1).
4
+
5
+ Evaluates trained student models on data from DIFFERENT collection sessions
6
+ to measure how well localization generalizes across time/environment changes.
7
+
8
+ WILD Jacobs Hall sessions:
9
+ Session 1: jacobs_Jul28 — standard setup
10
+ Session 2: jacobs_Jul28_2 — same setup, 1 hour later (our train/test data)
11
+ Session 3: jacobs_Aug16_1 — extra furniture
12
+ Session 4: jacobs_Aug16_3 — different furniture arrangement
13
+ Session 5: jacobs_Aug16_4_ref — furniture + aluminum reflector
14
+
15
+ Models were trained on Session 1 + Session 2 (train split).
16
+ This script tests on:
17
+ - Session 2 test (normal, in-distribution)
18
+ - Session 3 (unseen session, different furniture)
19
+ - Session 4 (unseen session, different furniture)
20
+ - Session 5 (unseen session, furniture + reflector)
21
+
22
+ REQUIRED DATA:
23
+ You MUST download the Aug16 feature files from WILD and process them.
24
+ Option 1 (if pre-split files exist on server):
25
+ ./data/dataset_train_jacobs_Aug16_1.mat
26
+ ./data/dataset_train_jacobs_Aug16_3.mat
27
+ ./data/dataset_train_jacobs_Aug16_4_ref.mat
28
+ Option 2 (if you have raw WILD features):
29
+ Run prepare_aug16_data() below to create them.
30
+
31
+ Usage:
32
+ python eval_cross_session.py
33
+ """
34
+ import os
35
+ import sys
36
+ import torch
37
+ import numpy as np
38
+
39
+ from utils import localization_error
40
+ from data_loader import load_data
41
+
42
+ # ============================================================
43
+ # CONFIG
44
+ # ============================================================
45
+ MODEL_TYPE = 'mobilenet' # 'mobilenet', 'tinycnn', 'unet'
46
+ WEIGHTS_PATH = './weights/mobilenet_standalone_best.pth'
47
+ BATCH_SIZE = 32
48
+
49
+ # Session data paths
50
+ # Session 2 test (normal evaluation — in-distribution)
51
+ SESSION_2_TEST = [
52
+ './data/dataset_fov_test_jacobs_July28_2.mat',
53
+ './data/dataset_non_fov_test_jacobs_July28_2.mat',
54
+ ]
55
+
56
+ # Sessions 3-5 (cross-session — out-of-distribution)
57
+ # These files contain ALL samples from each session (no train/test split needed
58
+ # since the model never trained on them)
59
+ CROSS_SESSION_FILES = {
60
+ 'Session 3 (Aug16_1, furniture)': './data/dataset_jacobs_Aug16_1.mat',
61
+ 'Session 4 (Aug16_3, diff furniture)': './data/dataset_jacobs_Aug16_3.mat',
62
+ 'Session 5 (Aug16_4_ref, reflector)': './data/dataset_jacobs_Aug16_4_ref.mat',
63
+ }
64
+
65
+
66
+ def load_model(model_type, weights_path, device):
67
+ if model_type == 'mobilenet':
68
+ from student_mobilenet import MobileNetStudent
69
+ model = MobileNetStudent(input_nc=4)
70
+ elif model_type == 'tinycnn':
71
+ from student_tinycnn import TinyCNNStudent
72
+ model = TinyCNNStudent(input_nc=4)
73
+ elif model_type == 'unet':
74
+ from student_unet import MobileNetUNetStudent
75
+ model = MobileNetUNetStudent(input_nc=4)
76
+ else:
77
+ raise ValueError(f'Unknown model type: {model_type}')
78
+
79
+ model.load_state_dict(torch.load(weights_path, map_location=device, weights_only=True))
80
+ model.to(device)
81
+ model.eval()
82
+ return model
83
+
84
+
85
+ def evaluate_on_files(model, file_paths, device, batch_size=32):
86
+ """Load data from file_paths and evaluate model. Returns error stats in meters."""
87
+ w_list, lbl_list = [], []
88
+ for fp in file_paths:
89
+ if not os.path.exists(fp):
90
+ return None # Signal that file is missing
91
+ _, w, lbl = load_data(fp)
92
+ if lbl.dim() == 3:
93
+ lbl = lbl.unsqueeze(1)
94
+ w_list.append(w)
95
+ lbl_list.append(lbl)
96
+
97
+ w = torch.cat(w_list, dim=0)
98
+ lbl = torch.cat(lbl_list, dim=0)
99
+ n_samples = w.shape[0]
100
+
101
+ dataset = torch.utils.data.TensorDataset(w, lbl)
102
+ loader = torch.utils.data.DataLoader(
103
+ dataset, batch_size=batch_size, shuffle=False,
104
+ num_workers=4, pin_memory=True,
105
+ )
106
+
107
+ all_errors = []
108
+ with torch.no_grad():
109
+ for data in loader:
110
+ inputs = data[0].to(device)
111
+ labels = data[1].to(device)
112
+ output = model(inputs)
113
+ errors = localization_error(
114
+ output.cpu().numpy(),
115
+ labels.cpu().numpy(),
116
+ scale=0.1,
117
+ )
118
+ all_errors.extend(errors)
119
+
120
+ all_errors = np.array(all_errors)
121
+ return {
122
+ 'n_samples': n_samples,
123
+ 'median': np.median(all_errors),
124
+ 'p90': np.percentile(all_errors, 90),
125
+ 'p99': np.percentile(all_errors, 99),
126
+ 'mean': np.mean(all_errors),
127
+ }
128
+
129
+
130
+ def main():
131
+ device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
132
+ print(f'Device: {device}')
133
+ print(f'Model: {MODEL_TYPE}, Weights: {WEIGHTS_PATH}')
134
+
135
+ # Load model
136
+ model = load_model(MODEL_TYPE, WEIGHTS_PATH, device)
137
+ n_params = sum(p.numel() for p in model.parameters())
138
+ print(f'Parameters: {n_params:,}\n')
139
+
140
+ # Collect all sessions to evaluate
141
+ sessions = {}
142
+ sessions['Session 2 test (in-distribution)'] = SESSION_2_TEST
143
+ for name, path in CROSS_SESSION_FILES.items():
144
+ sessions[name] = [path]
145
+
146
+ # Evaluate each session
147
+ print(f'{"="*80}')
148
+ print(f' CROSS-SESSION EVALUATION -- {MODEL_TYPE.upper()}')
149
+ print(f'{"="*80}')
150
+ print(f' Model trained on: Session 1 (Jul28) + Session 2 train (Jul28_2)')
151
+ print(f'{"="*80}\n')
152
+
153
+ print(f'{"Session":<42} {"N":>6} {"Median":>8} {"P90":>8} {"P99":>8} {"Mean":>8}')
154
+ print(f'{"-"*80}')
155
+
156
+ results = {}
157
+ for name, file_paths in sessions.items():
158
+ stats = evaluate_on_files(model, file_paths, device, BATCH_SIZE)
159
+ if stats is None:
160
+ print(f'{name:<42} {"MISSING DATA FILES":>40}')
161
+ continue
162
+ results[name] = stats
163
+ print(f'{name:<42} {stats["n_samples"]:>6} '
164
+ f'{stats["median"]:>7.3f}m {stats["p90"]:>7.3f}m '
165
+ f'{stats["p99"]:>7.3f}m {stats["mean"]:>7.3f}m')
166
+
167
+ print(f'{"="*80}')
168
+
169
+ if not results:
170
+ print('\nNo sessions could be evaluated. Check data file paths.')
171
+ return
172
+
173
+ # Show degradation from baseline
174
+ if 'Session 2 test (in-distribution)' in results:
175
+ baseline = results['Session 2 test (in-distribution)']
176
+ print(f'\nDegradation vs in-distribution (Session 2 test):')
177
+ print(f'{"Session":<42} {"Median delta":>14} {"P90 delta":>14}')
178
+ print(f'{"-"*70}')
179
+ for name, stats in results.items():
180
+ if name == 'Session 2 test (in-distribution)':
181
+ continue
182
+ med_delta = stats['median'] - baseline['median']
183
+ p90_delta = stats['p90'] - baseline['p90']
184
+ print(f'{name:<42} {med_delta:>+13.3f}m {p90_delta:>+13.3f}m')
185
+
186
+ # Save results
187
+ save_path = f'cross_session_{MODEL_TYPE}.txt'
188
+ with open(save_path, 'w') as f:
189
+ f.write(f'Cross-Session Evaluation\n')
190
+ f.write(f'Model: {MODEL_TYPE}\n')
191
+ f.write(f'Weights: {WEIGHTS_PATH}\n')
192
+ f.write(f'Trained on: Session 1 (Jul28) + Session 2 train (Jul28_2)\n\n')
193
+ f.write(f'{"Session":<42} {"N":>6} {"Median":>8} {"P90":>8} {"P99":>8}\n')
194
+ f.write(f'{"-"*72}\n')
195
+ for name, stats in results.items():
196
+ f.write(f'{name:<42} {stats["n_samples"]:>6} '
197
+ f'{stats["median"]:>7.3f}m {stats["p90"]:>7.3f}m {stats["p99"]:>7.3f}m\n')
198
+ print(f'\nResults saved to {save_path}')
199
+
200
+
201
+ if __name__ == '__main__':
202
+ main()