RasaBh commited on
Commit
5bdcaa4
·
1 Parent(s): ffdf1ae

Updated models added

Browse files
A12/A12_classifier.py ADDED
@@ -0,0 +1,579 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Recognizing Start and Stop Positions
2
+
3
+ # Imports
4
+ import os
5
+ import numpy as np
6
+ import pandas as pd
7
+ import matplotlib.pyplot as plt
8
+ import seaborn as sns
9
+ from pathlib import Path
10
+
11
+ from sklearn.model_selection import StratifiedKFold, train_test_split
12
+ from sklearn.preprocessing import StandardScaler
13
+ from sklearn.metrics import (confusion_matrix, classification_report,
14
+ precision_score, recall_score, f1_score,
15
+ roc_auc_score, average_precision_score)
16
+ from sklearn.utils.class_weight import compute_class_weight
17
+
18
+ import tensorflow as tf
19
+ from tensorflow import keras
20
+ from tensorflow.keras import layers, regularizers
21
+
22
+
23
+ # Paths
24
+ DATA_DIR = Path(os.getcwd()) / 'classification_data'
25
+ RESULTS_DIR = Path(os.getcwd()) / 'A12_results'
26
+ RESULTS_DIR.mkdir(exist_ok=True)
27
+ CSV_PATH = DATA_DIR / 'not_cut_classification_data.csv'
28
+
29
+
30
+ # Joint and feature definitions
31
+
32
+ JOINTS = [
33
+ 'head', 'left_shoulder', 'left_elbow', 'right_shoulder', 'right_elbow',
34
+ 'left_hand', 'right_hand', 'left_hip', 'right_hip',
35
+ 'left_knee', 'right_knee', 'left_foot', 'right_foot'
36
+ ]
37
+
38
+ # Problem A — Kinect 3D: x,y,z 39 features
39
+ KINECT_COLS = []
40
+ for j in JOINTS:
41
+ KINECT_COLS += [f'{j}_x', f'{j}_y', f'{j}_z']
42
+
43
+ # Problem B — PoseNet 2D: x,y 26 features
44
+ POSENET_COLS = []
45
+ for j in JOINTS:
46
+ POSENET_COLS += [f'{j}_x', f'{j}_y']
47
+
48
+
49
+ EXTRA_COLS = [
50
+ 'left_hand_to_left_shoulder', 'right_hand_to_right_shoulder',
51
+ 'left_hand_to_left_hip', 'right_hand_to_right_hip',
52
+ 'left_elbow_to_left_shoulder', 'right_elbow_to_right_shoulder',
53
+ 'head_to_hip',
54
+ 'head_vx', 'head_vy', 'head_vz', 'head_speed',
55
+ 'left_hand_vx', 'left_hand_vy', 'left_hand_vz', 'left_hand_speed',
56
+ 'right_hand_vx', 'right_hand_vy', 'right_hand_vz', 'right_hand_speed',
57
+ 'head_ax', 'head_ay', 'head_az', 'head_accel',
58
+ 'left_hand_ax', 'left_hand_ay', 'left_hand_az', 'left_hand_accel',
59
+ 'right_hand_ax', 'right_hand_ay', 'right_hand_az', 'right_hand_accel',
60
+ ]
61
+
62
+ # Columns NOT used as features
63
+ META_COLS = ['FrameNo', 'file_id', 'is_not_cut', 'label']
64
+
65
+ # Label mapping
66
+ LABEL_MAP = {'neutral': 0, 'start': 1, 'stop': 2}
67
+ LABEL_NAMES = ['neutral', 'start', 'stop']
68
+
69
+
70
+ # Load and examine data
71
+
72
+ print("\nLoading data")
73
+ raw_df = pd.read_csv(str(CSV_PATH))
74
+ raw_df.columns = raw_df.columns.str.strip()
75
+
76
+ print(f"Total frames: {len(raw_df)}")
77
+ print(f"\nLabel distribution:")
78
+ label_counts = raw_df['label'].value_counts()
79
+ for label, count in label_counts.items():
80
+ pct = count / len(raw_df) * 100
81
+ print(f" {label:10s}: {count:6d} ({pct:.2f}%)")
82
+
83
+ # Convert string labels to integers
84
+ raw_df['label_int'] = raw_df['label'].map(LABEL_MAP)
85
+
86
+ print("Transforming to Binary: Exercise (1) vs Non-Exercise (0)...")
87
+ raw_df['label_binary'] = 0
88
+ for file_id in raw_df['file_id'].unique():
89
+ mask = raw_df['file_id'] == file_id
90
+ # Find the rows where this specific video starts and stops
91
+ start_idx = raw_df.index[mask & (raw_df['label'] == 'start')]
92
+ stop_idx = raw_df.index[mask & (raw_df['label'] == 'stop')]
93
+
94
+ if len(start_idx) > 0 and len(stop_idx) > 0:
95
+ # Mark every frame BETWEEN start and stop as 1
96
+ raw_df.loc[start_idx[0] : stop_idx[-1], 'label_binary'] = 1
97
+
98
+ # Overwrite the target variable to use our new binary labels
99
+ raw_df['label_int'] = raw_df['label_binary']
100
+ LABEL_NAMES = ['non-exercise', 'exercise']
101
+
102
+ # Prepare features for Problem A and B
103
+
104
+ def prepare_features(df, problem='A', use_extra=True):
105
+ if problem == 'A':
106
+ base_cols = KINECT_COLS # 39 features
107
+ else:
108
+ base_cols = POSENET_COLS
109
+
110
+ feat_cols = [c for c in base_cols if c in df.columns]
111
+
112
+ if use_extra:
113
+ extra = [c for c in EXTRA_COLS if c in df.columns]
114
+ feat_cols = feat_cols + extra
115
+
116
+ X = df[feat_cols].values.astype(np.float32)
117
+ y = df['label_int'].values.astype(np.int32)
118
+
119
+ print(f"\nProblem {problem}:")
120
+ print(f" Features: {len(feat_cols)}")
121
+ print(f" Samples : {len(X)}")
122
+
123
+ return X, y, feat_cols
124
+
125
+
126
+ # Prepare both problems
127
+ X_A, y_A, cols_A = prepare_features(raw_df, problem='A', use_extra=True)
128
+ X_B, y_B, cols_B = prepare_features(raw_df, problem='B', use_extra=True)
129
+
130
+ # Split train/test
131
+
132
+ def initial_split(X, y, test_size=0.1, random_state=42):
133
+ X_trainval, X_test, y_trainval, y_test = train_test_split(
134
+ X, y,
135
+ test_size=test_size,
136
+ random_state=random_state,
137
+ stratify=y
138
+ )
139
+ print(f"Train+Val: {len(X_trainval)} frames")
140
+ print(f"Test : {len(X_test)} frames")
141
+ return X_trainval, X_test, y_trainval, y_test
142
+
143
+ print("\nSplitting Problem A:")
144
+ X_A_tv, X_A_test, y_A_tv, y_A_test = initial_split(X_A, y_A)
145
+
146
+ print("\nSplitting Problem B:")
147
+ X_B_tv, X_B_test, y_B_tv, y_B_test = initial_split(X_B, y_B)
148
+
149
+
150
+ # Resampling
151
+
152
+ def create_sequences(X, y, window_size=30):
153
+ Xs, ys = [], []
154
+ for i in range(len(X) - window_size + 1):
155
+ Xs.append(X[i:(i + window_size)])
156
+ ys.append(y[i + window_size - 1])
157
+
158
+ return np.array(Xs), np.array(ys)
159
+
160
+
161
+ def compute_class_weights(y):
162
+ classes = np.unique(y)
163
+ weights = compute_class_weight('balanced', classes=classes, y=y)
164
+ weight_dict = dict(zip(classes.tolist(), weights.tolist()))
165
+ print(f"\nClass weights (higher = rarer = more important):")
166
+ for cls, w in weight_dict.items():
167
+ print(f" {LABEL_NAMES[cls]:10s} (class {cls}): {w:.2f}x")
168
+ return weight_dict
169
+
170
+
171
+ def oversample_minority(X, y, random_state=42):
172
+ rng = np.random.default_rng(random_state)
173
+
174
+ n_neutral = (y == 0).sum()
175
+ n_start = (y == 1).sum()
176
+ n_stop = (y == 2).sum()
177
+
178
+ print(f"\nBefore oversampling: neutral={n_neutral} start={n_start} stop={n_stop}")
179
+
180
+ # Oversample start frames to match neutral count
181
+ start_idx = np.where(y == 1)[0]
182
+ stop_idx = np.where(y == 2)[0]
183
+
184
+ n_copies_start = n_neutral // n_start
185
+ n_copies_stop = n_neutral // n_stop
186
+
187
+ # Repeat indices
188
+ start_oversampled = np.tile(start_idx, n_copies_start)
189
+ stop_oversampled = np.tile(stop_idx, n_copies_stop)
190
+
191
+ # Combine with original data
192
+ all_idx = np.concatenate([
193
+ np.where(y == 0)[0], # all neutral frames
194
+ start_oversampled, # repeated start frames
195
+ stop_oversampled # repeated stop frames
196
+ ])
197
+
198
+ # Shuffle
199
+ rng.shuffle(all_idx)
200
+
201
+ X_resampled = X[all_idx]
202
+ y_resampled = y[all_idx]
203
+
204
+ n_neutral_new = (y_resampled == 0).sum()
205
+ n_start_new = (y_resampled == 1).sum()
206
+ n_stop_new = (y_resampled == 2).sum()
207
+
208
+ print(f"After oversampling: neutral={n_neutral_new} "
209
+ f"start={n_start_new} stop={n_stop_new}")
210
+
211
+ return X_resampled, y_resampled
212
+
213
+
214
+ # Compute class weights for training
215
+ class_weights_A = compute_class_weights(y_A_tv)
216
+ class_weights_B = compute_class_weights(y_B_tv)
217
+
218
+
219
+ # Define network architectures
220
+
221
+ def build_dense(input_dim, hidden_units=(128, 64),
222
+ activation='relu', dropout_rate=0.2,
223
+ l2_reg=1e-4, n_classes=2, name='Dense'):
224
+
225
+ inputs = keras.Input(shape=(input_dim,), name='input')
226
+ x = inputs
227
+
228
+ for i, units in enumerate(hidden_units):
229
+ x = layers.Dense(
230
+ units, activation=activation,
231
+ kernel_regularizer=regularizers.l2(l2_reg) if l2_reg else None,
232
+ name=f'dense_{i+1}'
233
+ )(x)
234
+ x = layers.Dropout(dropout_rate, name=f'drop_{i+1}')(x)
235
+
236
+ # softmax = probability per class (must sum to 1)
237
+ outputs = layers.Dense(n_classes, activation='softmax',
238
+ name='output')(x)
239
+ return keras.Model(inputs, outputs, name=name)
240
+
241
+
242
+ def build_conv1d(input_dim, window_size=30,
243
+ filters=(64, 128), kernel_size=3,
244
+ dense_units=(64,), dropout_rate=0.2,
245
+ n_classes=2, name='Conv1D'):
246
+ inputs = keras.Input(shape=(window_size, input_dim), name='input')
247
+ x = inputs
248
+
249
+ for i, f in enumerate(filters):
250
+ x = layers.Conv1D(f, kernel_size, activation='relu',
251
+ padding='same', name=f'conv_{i+1}')(x)
252
+ x = layers.MaxPooling1D(2, padding='same', name=f'pool_{i+1}')(x)
253
+ x = layers.Dropout(dropout_rate, name=f'drop_conv_{i+1}')(x)
254
+
255
+ x = layers.GlobalAveragePooling1D(name='gap')(x)
256
+
257
+ for i, units in enumerate(dense_units):
258
+ x = layers.Dense(units, activation='relu', name=f'fc_{i+1}')(x)
259
+ x = layers.Dropout(dropout_rate, name=f'drop_fc_{i+1}')(x)
260
+
261
+ outputs = layers.Dense(n_classes, activation='softmax', name='output')(x)
262
+ return keras.Model(inputs, outputs, name=name)
263
+
264
+
265
+ def build_lstm(input_dim, window_size=30,
266
+ lstm_units=(64, 32), dense_units=(32,),
267
+ dropout_rate=0.2, n_classes=2, name='LSTM'):
268
+ inputs = keras.Input(shape=(window_size, input_dim), name='input')
269
+ x = inputs
270
+ for i, u in enumerate(lstm_units):
271
+ rs = (i < len(lstm_units) - 1)
272
+ x = layers.LSTM(u, return_sequences=rs,
273
+ dropout=dropout_rate, name=f'lstm_{i+1}')(x)
274
+ for i, u in enumerate(dense_units):
275
+ x = layers.Dense(u, activation='relu', name=f'fc_{i+1}')(x)
276
+ x = layers.Dropout(dropout_rate, name=f'drop_{i+1}')(x)
277
+ outputs = layers.Dense(n_classes, activation='softmax', name='output')(x)
278
+ return keras.Model(inputs, outputs, name=name)
279
+
280
+
281
+ def build_gru(input_dim, window_size=30,
282
+ gru_units=(64, 32), dense_units=(32,),
283
+ dropout_rate=0.2, n_classes=2, name='GRU'):
284
+ inputs = keras.Input(shape=(window_size, input_dim), name='input')
285
+ x = inputs
286
+ for i, u in enumerate(gru_units):
287
+ rs = (i < len(gru_units) - 1)
288
+ x = layers.GRU(u, return_sequences=rs,
289
+ dropout=dropout_rate, name=f'gru_{i+1}')(x)
290
+ for i, u in enumerate(dense_units):
291
+ x = layers.Dense(u, activation='relu', name=f'fc_{i+1}')(x)
292
+ x = layers.Dropout(dropout_rate, name=f'drop_{i+1}')(x)
293
+ outputs = layers.Dense(n_classes, activation='softmax', name='output')(x)
294
+ return keras.Model(inputs, outputs, name=name)
295
+
296
+
297
+ # Compile the model
298
+
299
+ def compile_model(model, optimizer='adam', lr=1e-3):
300
+ opt_map = {
301
+ 'adam': keras.optimizers.Adam(learning_rate=lr),
302
+ 'rmsprop': keras.optimizers.RMSprop(learning_rate=lr),
303
+ 'sgd': keras.optimizers.SGD(learning_rate=lr, momentum=0.9),
304
+ }
305
+ opt = opt_map.get(optimizer, keras.optimizers.Adam(lr))
306
+
307
+ model.compile(
308
+ optimizer=opt,
309
+ loss='sparse_categorical_crossentropy',
310
+ metrics=[
311
+ 'accuracy'
312
+ ]
313
+ )
314
+ return model
315
+
316
+
317
+ # Cross-Validation
318
+
319
+ def run_10fold_cv(X_trainval, y_trainval, X_test, y_test,
320
+ build_fn, input_dim, optimizer, batch_size,
321
+ class_weights, problem_name, arch_name,
322
+ use_oversampling=True, n_folds=3):
323
+ run_name = f"{problem_name}_{arch_name}_{optimizer}_bs{batch_size}"
324
+ print(f" {run_name} ({n_folds}-fold CV)")
325
+
326
+ skf = StratifiedKFold(n_splits=n_folds, shuffle=True, random_state=42)
327
+
328
+ fold_metrics = []
329
+ best_val_f1 = -1
330
+ best_model = None
331
+ best_scaler = None
332
+
333
+ for fold_idx, (tr_idx, val_idx) in enumerate(
334
+ skf.split(X_trainval, y_trainval)):
335
+
336
+ print(f"\n Fold {fold_idx+1}/{n_folds}", end=' ')
337
+
338
+ X_tr, X_val = X_trainval[tr_idx], X_trainval[val_idx]
339
+ y_tr, y_val = y_trainval[tr_idx], y_trainval[val_idx]
340
+
341
+ if use_oversampling:
342
+ X_tr, y_tr = oversample_minority(X_tr, y_tr,
343
+ random_state=42 + fold_idx)
344
+
345
+ is_seq = any(n in arch_name for n in ['Conv1D', 'LSTM', 'GRU'])
346
+
347
+ if is_seq:
348
+ X_tr, y_tr = create_sequences(X_tr, y_tr, window_size=30)
349
+ X_val, y_val = create_sequences(X_val, y_val, window_size=30)
350
+
351
+ scaler = StandardScaler()
352
+ N, W, F = X_tr.shape
353
+ X_tr_sc = scaler.fit_transform(X_tr.reshape(-1, F)).reshape(N, W, F)
354
+ X_val_sc = scaler.transform(X_val.reshape(-1, F)).reshape(-1, W, F)
355
+ else:
356
+ scaler = StandardScaler()
357
+ X_tr_sc = scaler.fit_transform(X_tr)
358
+ X_val_sc = scaler.transform(X_val)
359
+
360
+ model = build_fn(input_dim)
361
+ model = compile_model(model, optimizer=optimizer, lr=1e-3)
362
+
363
+ early_stop = keras.callbacks.EarlyStopping(
364
+ monitor='val_loss',
365
+ patience=10,
366
+ restore_best_weights=True,
367
+ verbose=0
368
+ )
369
+
370
+ history = model.fit(
371
+ X_tr_sc, y_tr,
372
+ validation_data=(X_val_sc, y_val),
373
+ epochs=100,
374
+ batch_size=batch_size,
375
+ class_weight=class_weights, # handle imbalance
376
+ callbacks=[early_stop],
377
+ verbose=0
378
+ )
379
+
380
+ y_pred = np.argmax(model.predict(X_val_sc, verbose=0), axis=1)
381
+
382
+ # Calculate metrics for the 'Exercise' class (1)
383
+ f1_ex = f1_score(y_val, y_pred, pos_label=1, zero_division=0)
384
+ p_ex = precision_score(y_val, y_pred, pos_label=1, zero_division=0)
385
+ r_ex = recall_score(y_val, y_pred, pos_label=1, zero_division=0)
386
+
387
+ print(f"EXERCISE F1={f1_ex:.3f} P={p_ex:.3f} R={r_ex:.3f} epochs={len(history.history['loss'])}")
388
+
389
+ fold_metrics.append({
390
+ 'fold': fold_idx + 1,
391
+ 'f1': f1_ex, 'p': p_ex, 'r': r_ex,
392
+ 'epochs': len(history.history['loss']),
393
+ })
394
+
395
+ if f1_ex > best_val_f1:
396
+ best_val_f1 = f1_ex
397
+ best_model = model
398
+ best_scaler = scaler
399
+
400
+ fold_df = pd.DataFrame(fold_metrics)
401
+ avg = fold_df.mean(numeric_only=True)
402
+
403
+ print(f"\n {n_folds}-FOLD AVERAGE:")
404
+ print(f" EXERCISE -> P:{avg['p']:.3f} R:{avg['r']:.3f} F1:{avg['f1']:.3f}")
405
+ print(f" Avg epochs: {avg['epochs']:.1f}")
406
+
407
+
408
+ print(f"\n FINAL TEST SET EVALUATION (held-out 10%):")
409
+ # 1. Prepare test data
410
+ X_test_final = X_test
411
+ y_test_final = y_test
412
+
413
+ # Check if the BEST model was a sequence model
414
+ is_seq = any(n in arch_name for n in ['Conv1D', 'LSTM', 'GRU'])
415
+
416
+ if is_seq:
417
+ # Turn test data into sequences
418
+ X_test_final, y_test_final = create_sequences(X_test, y_test, window_size=30)
419
+ # Scale 3D data (Flatten -> Transform -> Reshape)
420
+ Nt, Wt, Ft = X_test_final.shape
421
+ X_test_sc = best_scaler.transform(X_test_final.reshape(-1, Ft)).reshape(Nt, Wt, Ft)
422
+ else:
423
+ # Standard 2D scaling
424
+ X_test_sc = best_scaler.transform(X_test_final).astype(np.float32)
425
+
426
+ # 2. Predict using the best model
427
+ y_pred_test = np.argmax(best_model.predict(X_test_sc, verbose=0), axis=1)
428
+
429
+ # 3. Report
430
+ print(classification_report(
431
+ y_test_final, y_pred_test,
432
+ target_names=LABEL_NAMES, digits=3
433
+ ))
434
+
435
+ assess_frame_offset(y_test_final, y_pred_test, problem_name)
436
+
437
+ # 4. Save best model
438
+ best_model.save_weights(str(RESULTS_DIR / f'{run_name}.weights.h5'))
439
+
440
+ import joblib
441
+ joblib.dump(best_scaler, str(RESULTS_DIR / f'{run_name}_scaler.pkl'))
442
+
443
+ return {
444
+ 'run': run_name,
445
+ 'problem': problem_name,
446
+ 'architecture': arch_name,
447
+ 'optimizer': optimizer,
448
+ 'batch_size': batch_size,
449
+ 'avg_f1': avg['f1'],
450
+ 'avg_p': avg['p'],
451
+ 'avg_r': avg['r'],
452
+ 'avg_epochs': avg['epochs'],
453
+ 'test_f1': f1_score(y_test_final, y_pred_test, pos_label=1, zero_division=0)
454
+ }
455
+
456
+
457
+ # Frame offset analysis
458
+
459
+ def assess_frame_offset(y_true, y_pred, problem_name):
460
+ true_starts = [i for i in range(1, len(y_true)) if y_true[i-1]==0 and y_true[i]==1]
461
+ pred_starts = [i for i in range(1, len(y_pred)) if y_pred[i-1]==0 and y_pred[i]==1]
462
+ true_stops = [i for i in range(1, len(y_true)) if y_true[i-1]==1 and y_true[i]==0]
463
+ pred_stops = [i for i in range(1, len(y_pred)) if y_pred[i-1]==1 and y_pred[i]==0]
464
+
465
+ print(f"\n Frame Offset ({problem_name}):")
466
+ for label, tp, pp in [('START', true_starts, pred_starts),
467
+ ('STOP', true_stops, pred_stops)]:
468
+ pp = np.array(pp)
469
+ if len(pp) > 0 and len(tp) > 0:
470
+ offs = [abs(t - pp[np.argmin(np.abs(pp - t))]) for t in tp]
471
+ avg = np.mean(offs)
472
+ print(f" {label}: avg={avg:.1f} frames = {avg/30:.2f}s at 30fps")
473
+ else:
474
+ print(f" {label}: {len(tp)} true, {len(pp)} predicted")
475
+
476
+
477
+ # Training configurations to test
478
+
479
+
480
+ # Architecture variants
481
+ def make_architectures(input_dim):
482
+ return {
483
+ 'Dense_relu': lambda d: build_dense(
484
+ d, hidden_units=(128, 64), activation='relu', dropout_rate=0.2, n_classes=2),
485
+ 'Conv1D': lambda d: build_conv1d(
486
+ d, window_size=30, filters=(64, 128), dropout_rate=0.2, n_classes=2),
487
+ 'LSTM': lambda d: build_lstm(
488
+ d, window_size=30, lstm_units=(64, 32), dropout_rate=0.2, n_classes=2),
489
+ 'GRU': lambda d: build_gru(
490
+ d, window_size=30, gru_units=(64, 32), dropout_rate=0.2, n_classes=2),
491
+ }
492
+
493
+ OPTIMIZERS = ['adam', 'rmsprop']
494
+ BATCH_SIZES = [32, 64]
495
+
496
+ all_results = []
497
+
498
+
499
+ # Problem A (Kinect 39 features) and Problem B (PoseNet 26 features)
500
+
501
+ print("PROBLEM A: Kinect (x,y,z) - 39 features")
502
+
503
+ archs_A = make_architectures(X_A.shape[1])
504
+
505
+ for arch_name, build_fn in archs_A.items():
506
+ for opt in OPTIMIZERS:
507
+ for bs in BATCH_SIZES:
508
+ result = run_10fold_cv(
509
+ X_trainval=X_A_tv, y_trainval=y_A_tv,
510
+ X_test=X_A_test, y_test=y_A_test,
511
+ build_fn=build_fn, input_dim=X_A.shape[1],
512
+ optimizer=opt, batch_size=bs,
513
+ class_weights=class_weights_A,
514
+ problem_name='A_Kinect', arch_name=arch_name,
515
+ use_oversampling=False, n_folds=3
516
+ )
517
+ all_results.append(result)
518
+
519
+
520
+ print("PROBLEM B: PoseNet (x,y) — 26 features")
521
+
522
+ archs_B = make_architectures(X_B.shape[1])
523
+
524
+ for arch_name, build_fn in archs_B.items():
525
+ for opt in OPTIMIZERS:
526
+ for bs in BATCH_SIZES:
527
+ result = run_10fold_cv(
528
+ X_trainval=X_B_tv, y_trainval=y_B_tv,
529
+ X_test=X_B_test, y_test=y_B_test,
530
+ build_fn=build_fn, input_dim=X_B.shape[1],
531
+ optimizer=opt, batch_size=bs,
532
+ class_weights=class_weights_B,
533
+ problem_name='B_PoseNet', arch_name=arch_name,
534
+ use_oversampling=False, n_folds=3
535
+ )
536
+ all_results.append(result)
537
+
538
+
539
+ # Final results table and comparison
540
+
541
+ results_df = pd.DataFrame(all_results)
542
+ results_df = results_df.sort_values('avg_f1', ascending=False)
543
+
544
+ print(f"\n{'='*70}")
545
+ print("FINAL RESULTS — All experiments sorted by START F1")
546
+ print(f"{'='*70}")
547
+ print(results_df[['problem','architecture','optimizer','batch_size',
548
+ 'avg_f1','avg_p','avg_r','test_f1','avg_epochs']].to_string(index=False))
549
+
550
+ results_df.to_csv(str(RESULTS_DIR / 'all_results.csv'), index=False)
551
+
552
+ # Best per problem
553
+ best_A = results_df[results_df['problem'] == 'A_Kinect'].iloc[0]
554
+ best_B = results_df[results_df['problem'] == 'B_PoseNet'].iloc[0]
555
+
556
+ print(f"\n{'='*60}")
557
+ print("COMPARISON: Problem A (Kinect) vs Problem B (PoseNet)")
558
+ print(f"{'='*60}")
559
+ print(f"{'Metric':<28} {'Problem A':>12} {'Problem B':>12}")
560
+ print('-' * 55)
561
+ print(f"{'Input features':<28} {X_A.shape[1]:>12} {X_B.shape[1]:>12}")
562
+ print(f"{'Best arch':<28} {best_A['architecture']:>12} {best_B['architecture']:>12}")
563
+ print(f"{'Best optimizer':<28} {best_A['optimizer']:>12} {best_B['optimizer']:>12}")
564
+ print(f"{'CV F1 Score':<28} {best_A['avg_f1']:>12.3f} {best_B['avg_f1']:>12.3f}")
565
+ print(f"{'Test F1 Score':<28} {best_A['test_f1']:>12.3f} {best_B['test_f1']:>12.3f}")
566
+
567
+ diff = best_A['avg_f1'] - best_B['avg_f1']
568
+ if diff > 0.02:
569
+ conclusion = (f"Kinect (A) outperforms PoseNet (B) by {diff:.3f} F1. "
570
+ f"Depth (z) helps detect start/stop transitions.")
571
+ elif diff < -0.02:
572
+ conclusion = (f"PoseNet (B) matches Kinect (A). "
573
+ f"2D coordinates are sufficient for start/stop detection.")
574
+ else:
575
+ conclusion = (f"Similar performance (diff={diff:.3f}). "
576
+ f"Z coordinate adds minimal value for this task.")
577
+
578
+ print(f"\nConclusion: {conclusion}")
579
+ print(f"\nAll results saved to: {RESULTS_DIR}")
A12/A12_results/A_Kinect_Dense_relu_adam_bs64.weights.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:83f6b716c6b62bcae8d4687396a7cc56f53d5f2adfee7445931875470b5c635c
3
+ size 237656
A12/A12_results/A_Kinect_Dense_relu_adam_bs64_scaler.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6889db987986cc2292fa40d0dd9d1bedc6ecd4bbe9ed9a62fd6d2eaec78636ad
3
+ size 2295
A12/A12_results/B_PoseNet_Dense_relu_adam_bs64.weights.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bae1f466ac9c530323f2c5f4f6559fd942b8fc66b4f0ba7eaad25b27b040a697
3
+ size 217688
A12/A12_results/B_PoseNet_Dense_relu_adam_bs64_scaler.pkl ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:228b9f5c31aa323897666491f4e5d2d6593747172c321174df88310b8e11ff2a
3
+ size 1967
A12/A12_results/all_results.csv ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ run,problem,architecture,optimizer,batch_size,avg_f1,avg_p,avg_r,avg_epochs,test_f1
2
+ A_Kinect_Dense_relu_adam_bs64,A_Kinect,Dense_relu,adam,64,0.9494464599947943,0.9214460305062514,0.9792082771374502,81.33333333333333,0.948843728100911
3
+ A_Kinect_Dense_relu_adam_bs32,A_Kinect,Dense_relu,adam,32,0.9456869648872676,0.912499023021148,0.9814559384840078,72.33333333333333,0.9439775910364145
4
+ B_PoseNet_Dense_relu_adam_bs64,B_PoseNet,Dense_relu,adam,64,0.9378048261537056,0.904321581065244,0.9739900824608375,77.33333333333333,0.9426485922836287
5
+ B_PoseNet_Dense_relu_adam_bs32,B_PoseNet,Dense_relu,adam,32,0.9339650032654129,0.9005934223996852,0.9699763006862895,64.33333333333333,0.9372822299651568
6
+ A_Kinect_Dense_relu_rmsprop_bs64,A_Kinect,Dense_relu,rmsprop,64,0.9329939785956064,0.8998354313884457,0.9687728350144543,54.333333333333336,0.9337517433751743
7
+ A_Kinect_Dense_relu_rmsprop_bs32,A_Kinect,Dense_relu,rmsprop,32,0.9241883767228444,0.8896991181046472,0.9614673315372301,39.0,0.9240374609781478
8
+ B_PoseNet_Dense_relu_rmsprop_bs64,B_PoseNet,Dense_relu,rmsprop,64,0.92234254707232,0.8849348886782392,0.9630727901196078,58.0,0.9268802228412256
9
+ B_PoseNet_Dense_relu_rmsprop_bs32,B_PoseNet,Dense_relu,rmsprop,32,0.9131224832164339,0.8749101516286387,0.9550457098512407,48.666666666666664,0.926589392342817
10
+ A_Kinect_Conv1D_adam_bs32,A_Kinect,Conv1D,adam,32,0.8327652499217123,0.7723062309692167,0.903540830227446,54.333333333333336,0.827928524156188
11
+ A_Kinect_LSTM_adam_bs32,A_Kinect,LSTM,adam,32,0.8267333481597587,0.7655730022118564,0.8985510984777236,31.0,0.8284102223697312
12
+ A_Kinect_Conv1D_adam_bs64,A_Kinect,Conv1D,adam,64,0.8254831613431838,0.7676677219382606,0.8927598635633291,56.0,0.828
13
+ A_Kinect_LSTM_rmsprop_bs32,A_Kinect,LSTM,rmsprop,32,0.8245556169432104,0.7763738854101477,0.8791613296059468,32.666666666666664,0.8262783609888249
14
+ A_Kinect_LSTM_adam_bs64,A_Kinect,LSTM,adam,64,0.820165562705342,0.7639814381131628,0.8856853163721553,30.333333333333332,0.8245033112582781
15
+ A_Kinect_LSTM_rmsprop_bs64,A_Kinect,LSTM,rmsprop,64,0.8186241565131747,0.7765837268721133,0.8658863897021606,34.666666666666664,0.8194957983193277
16
+ A_Kinect_Conv1D_rmsprop_bs32,A_Kinect,Conv1D,rmsprop,32,0.8162964270930465,0.7660058899638699,0.8736933014326329,48.333333333333336,0.8189452468928451
17
+ A_Kinect_GRU_adam_bs32,A_Kinect,GRU,adam,32,0.8156510708745536,0.7560009639125966,0.8856012485523,31.0,0.8136831617402857
18
+ B_PoseNet_Conv1D_adam_bs64,B_PoseNet,Conv1D,adam,64,0.8155099923003636,0.7628939438995047,0.8761039437247238,58.333333333333336,0.8202360876897133
19
+ A_Kinect_Conv1D_rmsprop_bs64,A_Kinect,Conv1D,rmsprop,64,0.8143812370564226,0.7707596646250626,0.8642749804748283,50.666666666666664,0.8276096565793948
20
+ A_Kinect_GRU_adam_bs64,A_Kinect,GRU,adam,64,0.8126466704444959,0.7555510309447894,0.8791644744347374,32.0,0.8141768797615104
21
+ A_Kinect_GRU_rmsprop_bs32,A_Kinect,GRU,rmsprop,32,0.8115159332925455,0.7579717381990746,0.8733711819108877,26.0,0.8090699967137693
22
+ B_PoseNet_Conv1D_adam_bs32,B_PoseNet,Conv1D,adam,32,0.8107344558018864,0.7660752715171834,0.8616292566000517,47.0,0.8203412512546002
23
+ A_Kinect_GRU_rmsprop_bs64,A_Kinect,GRU,rmsprop,64,0.8095376156181283,0.7658202943213573,0.858567656745724,26.0,0.8180277871907828
24
+ B_PoseNet_Conv1D_rmsprop_bs64,B_PoseNet,Conv1D,rmsprop,64,0.7997232193847029,0.7521364518525036,0.8544610097910863,55.333333333333336,0.8146666666666667
25
+ B_PoseNet_LSTM_adam_bs32,B_PoseNet,LSTM,adam,32,0.7996634344878477,0.756430522255528,0.8486763965813681,29.0,0.801762114537445
26
+ B_PoseNet_LSTM_rmsprop_bs32,B_PoseNet,LSTM,rmsprop,32,0.7991433709103725,0.7714179872824435,0.8292050455449416,30.333333333333332,0.8021164021164021
27
+ B_PoseNet_Conv1D_rmsprop_bs32,B_PoseNet,Conv1D,rmsprop,32,0.7964834575250429,0.7602660298789227,0.837082064913138,48.0,0.8092643051771117
28
+ B_PoseNet_GRU_adam_bs32,B_PoseNet,GRU,adam,32,0.7940393366437886,0.7359858464658354,0.863308018929314,31.333333333333332,0.8015899304405433
29
+ B_PoseNet_GRU_rmsprop_bs32,B_PoseNet,GRU,rmsprop,32,0.793993152232214,0.749062435617784,0.8449737041712876,30.333333333333332,0.7962466487935657
30
+ B_PoseNet_GRU_rmsprop_bs64,B_PoseNet,GRU,rmsprop,64,0.7931333036191383,0.7459325424744568,0.8467430404611639,29.333333333333332,0.7970380343318748
31
+ B_PoseNet_LSTM_rmsprop_bs64,B_PoseNet,LSTM,rmsprop,64,0.7919016433571686,0.7518383286396242,0.836921830335339,32.333333333333336,0.8002694509936006
32
+ B_PoseNet_GRU_adam_bs64,B_PoseNet,GRU,adam,64,0.7910667630366844,0.7379220653225212,0.8530220842372742,31.333333333333332,0.7966273187183811
33
+ B_PoseNet_LSTM_adam_bs64,B_PoseNet,LSTM,adam,64,0.7898076918529106,0.737772490491876,0.8497973776809582,22.666666666666668,0.7990591397849462