This commit is contained in:
lda committed 2025-05-13 01:14:59 +07:00
1 parent 770c1ff1b0
commit 2317d9a622
3 files changed
+192 -24

No files matched your search

+41 -5
View File
@@ -5,14 +5,21 @@ from sklearn.model_selection import train_test_split # type: ignore[import]
from data_loader import load_weather_data
from model_train import (
cosine_lr_schedule,
# cosine_lr_schedule,
cyclical_lr_schedule,
evaluate_model,
# exponential_decay_schedule,
# linear_decay_schedule,
# polynomial_decay_schedule,
predict_linear_regression,
run_autoregressive_prediction,
# step_decay_schedule,
train_linear_regression_gd,
# warmup_schedule,
)
from preprocessing import create_sequences, inverse_scale, scale_data
from visualize import (
# inspect_residuals,
plot_error_hist, # plot_loss_curve,
plot_feature_importance,
# plot_interactive_time_series,
@@ -27,6 +34,7 @@ from visualize import (
visualize_sequence_window,
)
plt.ioff()
# Step 1: Load data
data = load_weather_data("hanoi_weather_data.csv")
@@ -50,14 +58,39 @@ X_train, X_test, y_train, y_test = train_test_split(
)
date_train, date_test = train_test_split(date_index, test_size=test_size, shuffle=False)
# this is the lr schedulers
# cos_lr = lambda epoch: cosine_lr_schedule(epoch, max_lr=0.01, min_lr=0.001)
# step_lr = lambda epoch: step_decay_schedule(
# epoch, initial_lr=0.01, drop=0.5, epochs_drop=100
# )
# expo_lr = lambda epoch: exponential_decay_schedule(
# epoch, initial_lr=0.01, decay_rate=0.96, decay_steps=100
# )
# linear_lr = lambda epoch: linear_decay_schedule(
# epoch, initial_lr=0.01, min_lr=0.001, total_epochs=1000
# )
# cycle_lr = lambda epoch: cyclical_lr_schedule(
# epoch, base_lr=0.001, max_lr=0.01, step_size=200
# )
# poly_lr = lambda epoch: polynomial_decay_schedule(
# epoch, initial_lr=0.01, min_lr=0.001, total_epochs=1000, power=2
# )
# warmup_lr = lambda epoch: warmup_schedule(
# epoch, warmup_epochs=10, initial_lr=0.001, max_lr=0.01
# )
# Step 4: Train model with gradient descent
theta, losses = train_linear_regression_gd(
X_train,
y_train,
lr_schedule=lambda epoch: cosine_lr_schedule(epoch, max_lr=0.01, min_lr=0.001),
# n_epochs=1000,
lr_schedule=lambda epoch: cyclical_lr_schedule(
epoch, base_lr=0.0005, max_lr=0.01, step_size=150
),
n_epochs=1000,
batch_size=32,
patience=80,
l2_reg=0.001, # Enable L2 regularization
l1_reg=0.0, # Disable L1 regularization
)
predictions = predict_linear_regression(X_test, theta)
@@ -81,6 +114,7 @@ plot_residuals(date_test.to_numpy(), residuals)
plot_error_hist(errors)
plot_scatter(y_test.reshape(-1, 1), predictions.reshape(-1, 1))
plot_residual_scatter(y_test, predictions)
# == inspect_residuals(predictions, residuals)
plot_loss_curve_with_annotations(losses)
plot_feature_importance(theta, feature_names=[f"QV2M_t-{i}" for i in range(seq_length)])
@@ -89,3 +123,5 @@ plot_feature_importance(theta, feature_names=[f"QV2M_t-{i}" for i in range(seq_l
autoregressive_predictions = run_autoregressive_prediction(
theta, X_test, y_test, scaler, date_test, data.shape[1]
)
plt.ion()
+139 -19
View File
@@ -1,7 +1,10 @@
# from itertools import islice
from matplotlib import pyplot as plt
import time
import numpy as np
from sklearn.metrics import mean_squared_error, r2_score # type: ignore[import]
from matplotlib import pyplot as plt
from sklearn.metrics import (mean_squared_error, # type: ignore[import]
r2_score)
from preprocessing import inverse_scale
@@ -13,21 +16,9 @@ def train_linear_regression_gd(
n_epochs=1000,
batch_size=32,
patience=20,
l2_reg=0.0,
l1_reg=0.0,
):
"""
Train linear regression model using batch gradient descent.
Args:
X_train: Training features.
y_train: Training labels.
lr_schedule: Learning rate schedule function.
n_epochs: Number of epochs.
batch_size: Size of each batch.
Returns:
theta: Learned weights.
losses: List of MSE loss per epoch.
"""
m, n = X_train.shape
X_b = np.hstack([np.ones((m, 1)), X_train]) # Add bias term
theta = np.zeros(n + 1)
@@ -37,12 +28,16 @@ def train_linear_regression_gd(
patience_counter = 0
for epoch in range(n_epochs):
start_time = time.time() # Start timing the epoch
# Shuffle the data at the start of each epoch
indices = np.random.permutation(m)
X_b_shuffled = X_b[indices]
y_shuffled = y_train[indices]
epoch_loss = 0.0
gradient_norm = 0.0
for i in range(0, m, batch_size):
X_batch = X_b_shuffled[i : i + batch_size]
y_batch = y_shuffled[i : i + batch_size]
@@ -50,16 +45,32 @@ def train_linear_regression_gd(
lr = lr_schedule(epoch)
predictions = X_batch @ theta
errors = predictions - y_batch
# Compute gradients with regularization
gradients = (2 / batch_size) * X_batch.T @ errors
gradients = np.clip(gradients, -1, 1) # Gradient clipping
if l2_reg > 0:
gradients[1:] += 2 * l2_reg * theta[1:]
if l1_reg > 0:
gradients[1:] += l1_reg * np.sign(theta[1:])
gradient_norm += np.linalg.norm(gradients) # Track gradient norm
theta -= lr * gradients
batch_loss = np.mean(errors**2)
if l2_reg > 0:
batch_loss += l2_reg * np.sum(theta[1:] ** 2)
if l1_reg > 0:
batch_loss += l1_reg * np.sum(np.abs(theta[1:]))
epoch_loss += batch_loss
epoch_loss /= m // batch_size
losses.append(epoch_loss)
# Compute weight norm
weight_norm = np.linalg.norm(theta)
# Check for early stopping
if epoch_loss < best_loss:
best_loss = epoch_loss
patience_counter = 0
@@ -70,8 +81,14 @@ def train_linear_regression_gd(
print(f"Early stopping at epoch {epoch}")
break
if epoch % 100 == 0:
print(f"Epoch {epoch}: Loss = {losses[-1]:.6f}, LR: {lr:.6f}")
# Log statistics
epoch_time = time.time() - start_time
if epoch % 100 == 0 or epoch == n_epochs - 1:
print(
f"Epoch {epoch}: Loss = {epoch_loss:.6f}, LR: {lr:.6f}, "
f"Gradient Norm = {gradient_norm:.4f}, Weight Norm = {weight_norm:.4f}, "
f"Epoch Time = {epoch_time:.2f}s"
)
print(f"Final Loss: {losses[-1]:.6f}")
return theta, losses
@@ -99,6 +116,109 @@ def cosine_lr_schedule(epoch, max_lr=0.01, min_lr=0.001, total_epochs=1000):
)
def step_decay_schedule(epoch, initial_lr=0.01, drop=0.5, epochs_drop=50):
"""
Step decay learning rate scheduler.
Args:
epoch: Current epoch.
initial_lr: Initial learning rate.
drop: Factor by which to reduce the learning rate.
epochs_drop: Number of epochs after which to reduce the learning rate.
Returns:
Updated learning rate.
"""
return initial_lr * (drop ** (epoch // epochs_drop))
def exponential_decay_schedule(epoch, initial_lr=0.01, decay_rate=0.1, decay_steps=100):
"""
Exponential decay learning rate scheduler.
Args:
epoch: Current epoch.
initial_lr: Initial learning rate.
decay_rate: Rate of decay.
decay_steps: Number of steps for decay.
Returns:
Updated learning rate.
"""
return initial_lr * (decay_rate ** (epoch / decay_steps))
def linear_decay_schedule(epoch, initial_lr=0.01, min_lr=0.001, total_epochs=1000):
"""
Linear decay learning rate scheduler.
Args:
epoch: Current epoch.
initial_lr: Initial learning rate.
min_lr: Minimum learning rate.
total_epochs: Total number of epochs.
Returns:
Updated learning rate.
"""
return max(min_lr, initial_lr - (initial_lr - min_lr) * (epoch / total_epochs))
def cyclical_lr_schedule(epoch, base_lr=0.001, max_lr=0.01, step_size=200):
"""
Cyclical learning rate scheduler.
Args:
epoch: Current epoch.
base_lr: Minimum learning rate.
max_lr: Maximum learning rate.
step_size: Number of epochs for half a cycle.
Returns:
Updated learning rate.
"""
cycle = np.floor(1 + epoch / (2 * step_size))
x = np.abs(epoch / step_size - 2 * cycle + 1)
return base_lr + (max_lr - base_lr) * max(0, (1 - x))
def polynomial_decay_schedule(
epoch, initial_lr=0.01, min_lr=0.001, total_epochs=1000, power=2
):
"""
Polynomial decay learning rate scheduler.
Args:
epoch: Current epoch.
initial_lr: Initial learning rate.
min_lr: Minimum learning rate.
total_epochs: Total number of epochs.
power: Polynomial power.
Returns:
Updated learning rate.
"""
return min_lr + (initial_lr - min_lr) * ((1 - epoch / total_epochs) ** power)
def warmup_schedule(epoch, warmup_epochs=10, initial_lr=0.01, max_lr=0.1):
"""
Warm-up learning rate scheduler.
Args:
epoch: Current epoch.
warmup_epochs: Number of warm-up epochs.
initial_lr: Initial learning rate.
max_lr: Maximum learning rate after warm-up.
Returns:
Updated learning rate.
"""
if epoch < warmup_epochs:
return initial_lr + (max_lr - initial_lr) * (epoch / warmup_epochs)
return max_lr
def autoregressive_prediction(model, initial_sequence, steps, scaler, n_features):
"""
Perform autoregressive predictions using the model.
+12
View File
@@ -269,3 +269,15 @@ def plot_feature_importance(theta, feature_names):
# fig = px.line(data, x=data.index, y=feature_names, title="Interactive Time Series")
# fig.show()
def inspect_residuals(predictions, residuals):
plt.figure(figsize=(10, 6))
plt.scatter(predictions, residuals, alpha=0.5)
plt.axhline(0, color="red", linestyle="--")
plt.title("Residuals vs Predictions")
plt.xlabel("Predicted Values")
plt.ylabel("Residuals")
plt.grid(True)
plt.tight_layout()
plt.show()