Update app.py
Browse files
app.py
CHANGED
|
@@ -46,10 +46,10 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
|
| 46 |
label { font-weight: 600; margin-bottom: 8px; display: block; }
|
| 47 |
input, select { width: 100%; padding: 12px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 15px; }
|
| 48 |
input:focus, select:focus { border-color: #667eea; outline: none; }
|
| 49 |
-
.checkbox-group { display: flex; flex-wrap: wrap; gap:
|
| 50 |
-
.checkbox-group label { display: flex; align-items: center; gap:
|
| 51 |
.checkbox-group label:hover { background: #e0e0e0; }
|
| 52 |
-
.checkbox-group input[type="checkbox"] { width: 16px; height: 16px; }
|
| 53 |
button { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 18px 40px; border: none; border-radius: 10px; font-size: 18px; font-weight: 600; margin: 30px auto; display: block; width: fit-content; }
|
| 54 |
button:disabled { background: #ccc; cursor: not-allowed; }
|
| 55 |
#loading { display: none; text-align: center; padding: 40px; }
|
|
@@ -323,7 +323,7 @@ function displayResults(data, epochs, taskType) {
|
|
| 323 |
], {
|
| 324 |
title: `${lossName.toUpperCase()} Train and Val Loss`,
|
| 325 |
xaxis: {title: 'Epoch'},
|
| 326 |
-
yaxis: {title: 'Loss', type: '
|
| 327 |
height: 400
|
| 328 |
});
|
| 329 |
});
|
|
@@ -438,8 +438,8 @@ def add_outliers(X, y, n_samples, perc, mag):
|
|
| 438 |
X[idx] *= mag
|
| 439 |
if y.ndim == 2: # regression
|
| 440 |
y[idx] *= mag
|
| 441 |
-
else: # classification
|
| 442 |
-
if y.max() > 1: # multi-class
|
| 443 |
y[idx] = np.random.randint(0, y.max() + 1, size=n)
|
| 444 |
else: # binary
|
| 445 |
y[idx] = 1 - y[idx]
|
|
@@ -523,49 +523,67 @@ def train():
|
|
| 523 |
opt.zero_grad()
|
| 524 |
outputs = model(X_train_t)
|
| 525 |
|
|
|
|
| 526 |
try:
|
| 527 |
if loss_name in ['cross_entropy', 'sparse_categorical_crossentropy'] and task != 'regression':
|
| 528 |
loss = criterion(outputs, y_train_t)
|
| 529 |
elif loss_name == 'bce' and task == 'binary_classification':
|
| 530 |
-
loss = criterion(outputs, y_train_t.float())
|
| 531 |
else:
|
| 532 |
-
#
|
| 533 |
if task == 'regression':
|
| 534 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 535 |
else:
|
| 536 |
loss = criterion(outputs.mean(dim=1, keepdim=True), y_train_t.float().unsqueeze(1))
|
| 537 |
-
loss
|
| 538 |
-
|
| 539 |
-
|
|
|
|
| 540 |
except Exception as e:
|
| 541 |
logger.warning(f"Loss computation or backward failed for {loss_name} on {task}: {str(e)}")
|
| 542 |
-
loss = torch.tensor(float('nan'))
|
| 543 |
|
| 544 |
model.eval()
|
| 545 |
with torch.no_grad():
|
| 546 |
val_outputs = model(X_val_t)
|
|
|
|
| 547 |
try:
|
| 548 |
if loss_name in ['cross_entropy', 'sparse_categorical_crossentropy'] and task != 'regression':
|
| 549 |
val_loss = criterion(val_outputs, y_val_t)
|
| 550 |
elif loss_name == 'bce' and task == 'binary_classification':
|
| 551 |
-
val_loss = criterion(val_outputs, y_val_t.float())
|
| 552 |
else:
|
| 553 |
if task == 'regression':
|
| 554 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 555 |
else:
|
| 556 |
val_loss = criterion(val_outputs.mean(dim=1, keepdim=True), y_val_t.float().unsqueeze(1))
|
| 557 |
except Exception as e:
|
| 558 |
logger.warning(f"Val loss failed for {loss_name} on {task}: {str(e)}")
|
| 559 |
val_loss = torch.tensor(float('nan'))
|
| 560 |
|
| 561 |
-
train_losses.append(float(loss.item()) if
|
| 562 |
-
val_losses.append(float(val_loss.item()) if
|
| 563 |
|
| 564 |
if epoch > 10 and convergence_epoch is None:
|
| 565 |
recent_val = val_losses[-5:]
|
| 566 |
if len(recent_val) == 5 and all(not np.isnan(r) for r in recent_val):
|
| 567 |
if abs(recent_val[-1] - recent_val[0]) / (abs(recent_val[0]) + 1e-8) < 0.001:
|
| 568 |
-
convergence_epoch = epoch + 1
|
| 569 |
|
| 570 |
total_time = time.time() - start
|
| 571 |
if task == 'regression':
|
|
|
|
| 46 |
label { font-weight: 600; margin-bottom: 8px; display: block; }
|
| 47 |
input, select { width: 100%; padding: 12px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 15px; }
|
| 48 |
input:focus, select:focus { border-color: #667eea; outline: none; }
|
| 49 |
+
.checkbox-group { display: flex; flex-wrap: wrap; gap: 10px; background: #f8f9fa; padding: 10px; border-radius: 10px; justify-content: flex-start; }
|
| 50 |
+
.checkbox-group label { display: flex; align-items: center; gap: 5px; cursor: pointer; padding: 5px; border-radius: 6px; transition: background 0.2s; white-space: nowrap; }
|
| 51 |
.checkbox-group label:hover { background: #e0e0e0; }
|
| 52 |
+
.checkbox-group input[type="checkbox"] { width: 16px; height: 16px; margin: 0; }
|
| 53 |
button { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 18px 40px; border: none; border-radius: 10px; font-size: 18px; font-weight: 600; margin: 30px auto; display: block; width: fit-content; }
|
| 54 |
button:disabled { background: #ccc; cursor: not-allowed; }
|
| 55 |
#loading { display: none; text-align: center; padding: 40px; }
|
|
|
|
| 323 |
], {
|
| 324 |
title: `${lossName.toUpperCase()} Train and Val Loss`,
|
| 325 |
xaxis: {title: 'Epoch'},
|
| 326 |
+
yaxis: {title: 'Loss', type: 'linear'}, // Changed to linear for better visibility of small values
|
| 327 |
height: 400
|
| 328 |
});
|
| 329 |
});
|
|
|
|
| 438 |
X[idx] *= mag
|
| 439 |
if y.ndim == 2: # regression
|
| 440 |
y[idx] *= mag
|
| 441 |
+
else: # classification
|
| 442 |
+
if y.max() > 1: # multi-class
|
| 443 |
y[idx] = np.random.randint(0, y.max() + 1, size=n)
|
| 444 |
else: # binary
|
| 445 |
y[idx] = 1 - y[idx]
|
|
|
|
| 523 |
opt.zero_grad()
|
| 524 |
outputs = model(X_train_t)
|
| 525 |
|
| 526 |
+
loss = None
|
| 527 |
try:
|
| 528 |
if loss_name in ['cross_entropy', 'sparse_categorical_crossentropy'] and task != 'regression':
|
| 529 |
loss = criterion(outputs, y_train_t)
|
| 530 |
elif loss_name == 'bce' and task == 'binary_classification':
|
| 531 |
+
loss = criterion(outputs, y_train_t.float().unsqueeze(1))
|
| 532 |
else:
|
| 533 |
+
# Mismatched cases
|
| 534 |
if task == 'regression':
|
| 535 |
+
if loss_name in ['cross_entropy', 'sparse_categorical_crossentropy']:
|
| 536 |
+
# For mismatched CE on regression, treat as binary for demo
|
| 537 |
+
logits = torch.cat([outputs, -outputs], dim=1) # Fake 2 classes
|
| 538 |
+
targets = (y_train_t > 0).long().squeeze(1)
|
| 539 |
+
loss = criterion(logits, targets)
|
| 540 |
+
elif loss_name == 'bce':
|
| 541 |
+
loss = criterion(outputs, (y_train_t > 0).float())
|
| 542 |
+
else:
|
| 543 |
+
loss = criterion(outputs, y_train_t)
|
| 544 |
else:
|
| 545 |
loss = criterion(outputs.mean(dim=1, keepdim=True), y_train_t.float().unsqueeze(1))
|
| 546 |
+
if loss is not None:
|
| 547 |
+
loss.backward()
|
| 548 |
+
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
|
| 549 |
+
opt.step()
|
| 550 |
except Exception as e:
|
| 551 |
logger.warning(f"Loss computation or backward failed for {loss_name} on {task}: {str(e)}")
|
| 552 |
+
loss = torch.tensor(float('nan'))
|
| 553 |
|
| 554 |
model.eval()
|
| 555 |
with torch.no_grad():
|
| 556 |
val_outputs = model(X_val_t)
|
| 557 |
+
val_loss = None
|
| 558 |
try:
|
| 559 |
if loss_name in ['cross_entropy', 'sparse_categorical_crossentropy'] and task != 'regression':
|
| 560 |
val_loss = criterion(val_outputs, y_val_t)
|
| 561 |
elif loss_name == 'bce' and task == 'binary_classification':
|
| 562 |
+
val_loss = criterion(val_outputs, y_val_t.float().unsqueeze(1))
|
| 563 |
else:
|
| 564 |
if task == 'regression':
|
| 565 |
+
if loss_name in ['cross_entropy', 'sparse_categorical_crossentropy']:
|
| 566 |
+
logits = torch.cat([val_outputs, -val_outputs], dim=1)
|
| 567 |
+
targets = (y_val_t > 0).long().squeeze(1)
|
| 568 |
+
val_loss = criterion(logits, targets)
|
| 569 |
+
elif loss_name == 'bce':
|
| 570 |
+
val_loss = criterion(val_outputs, (y_val_t > 0).float())
|
| 571 |
+
else:
|
| 572 |
+
val_loss = criterion(val_outputs, y_val_t)
|
| 573 |
else:
|
| 574 |
val_loss = criterion(val_outputs.mean(dim=1, keepdim=True), y_val_t.float().unsqueeze(1))
|
| 575 |
except Exception as e:
|
| 576 |
logger.warning(f"Val loss failed for {loss_name} on {task}: {str(e)}")
|
| 577 |
val_loss = torch.tensor(float('nan'))
|
| 578 |
|
| 579 |
+
train_losses.append(float(loss.item()) if loss is not None and not torch.isnan(loss) else float('nan'))
|
| 580 |
+
val_losses.append(float(val_loss.item()) if val_loss is not None and not torch.isnan(val_loss) else float('nan'))
|
| 581 |
|
| 582 |
if epoch > 10 and convergence_epoch is None:
|
| 583 |
recent_val = val_losses[-5:]
|
| 584 |
if len(recent_val) == 5 and all(not np.isnan(r) for r in recent_val):
|
| 585 |
if abs(recent_val[-1] - recent_val[0]) / (abs(recent_val[0]) + 1e-8) < 0.001:
|
| 586 |
+
convergence_epoch = epoch + 1
|
| 587 |
|
| 588 |
total_time = time.time() - start
|
| 589 |
if task == 'regression':
|