aromidvar commited on
Commit
950b4c7
·
verified ·
1 Parent(s): df473db

Update core/train_eval.py

Browse files
Files changed (1) hide show
  1. core/train_eval.py +81 -95
core/train_eval.py CHANGED
@@ -1,4 +1,4 @@
1
- # core/train_eval.py
2
  import numpy as np
3
  import pandas as pd
4
  import torch
@@ -40,7 +40,6 @@ def mean_absolute_percentage_error(y_true, y_pred):
40
  return np.nan
41
  return np.mean(np.abs((y_true[non_zero] - y_pred[non_zero]) / y_true[non_zero])) * 100
42
 
43
-
44
  def directional_accuracy(y_true, y_pred):
45
  true_diff = np.diff(y_true)
46
  pred_diff = np.diff(y_pred)
@@ -49,7 +48,6 @@ def directional_accuracy(y_true, y_pred):
49
  return np.nan
50
  return np.mean(np.sign(true_diff) == np.sign(pred_diff))
51
 
52
-
53
  def mase(y_true, y_pred, y_train):
54
  mae_val = mean_absolute_error(y_true, y_pred)
55
  naive_mae = mean_absolute_error(y_train[1:], y_train[:-1]) if len(y_train) > 1 else np.nan
@@ -58,7 +56,6 @@ def mase(y_true, y_pred, y_train):
58
  return np.nan
59
  return mae_val / naive_mae
60
 
61
-
62
  def compute_volatility(y_pred):
63
  returns = np.diff(y_pred) / y_pred[:-1]
64
  if len(returns) == 0:
@@ -66,7 +63,6 @@ def compute_volatility(y_pred):
66
  return np.nan
67
  return np.std(returns) * np.sqrt(252)
68
 
69
-
70
  def compute_sharpe_ratio(y_pred, risk_free_rate=0.01):
71
  returns = np.diff(y_pred) / y_pred[:-1]
72
  if len(returns) == 0:
@@ -79,7 +75,6 @@ def compute_sharpe_ratio(y_pred, risk_free_rate=0.01):
79
  return np.nan
80
  return (mean_return - risk_free_rate) / std_return
81
 
82
-
83
  def compute_precision_recall(y_true, y_pred):
84
  true_diff = np.sign(np.diff(y_true))
85
  pred_diff = np.sign(np.diff(y_pred))
@@ -90,7 +85,6 @@ def compute_precision_recall(y_true, y_pred):
90
  recall = recall_score(true_diff > 0, pred_diff > 0, zero_division=0)
91
  return precision, recall
92
 
93
-
94
  # ---------------- Feature selection ----------------
95
  def select_features(df, features, target, selector_method, importance_threshold):
96
  logging.info(
@@ -104,6 +98,7 @@ def select_features(df, features, target, selector_method, importance_threshold)
104
  rf.fit(X, y)
105
  importances = pd.Series(rf.feature_importances_, index=features)
106
  selected_features = importances[importances >= importance_threshold].index.tolist()
 
107
  return selected_features if selected_features else features
108
  except Exception as e:
109
  logging.error(f"RandomForest feature selection failed: {str(e)}")
@@ -117,24 +112,17 @@ def select_features(df, features, target, selector_method, importance_threshold)
117
  pca = PCA(n_components=n_components)
118
  pca.fit(X_scaled)
119
  explained_variance_ratio = pca.explained_variance_ratio_.cumsum()
120
- n_selected = (
121
- sum(explained_variance_ratio < 0.95) + 1
122
- if any(explained_variance_ratio < 0.95)
123
- else n_components
124
- )
125
  selected_features = features[:n_selected]
 
126
  return selected_features if selected_features else features
127
  except Exception as e:
128
  logging.error(f"PCA feature selection failed: {str(e)}")
129
  return features
130
  else:
131
- logging.warning(
132
- f"Unsupported selector_method: {selector_method}, using all features"
133
- )
134
  return features
135
 
136
-
137
- # ---------------- Training ----------------
138
  def train_and_evaluate(
139
  df,
140
  features,
@@ -154,131 +142,128 @@ def train_and_evaluate(
154
  selector_method="RandomForest",
155
  importance_threshold=0.0,
156
  scheduler_type="None",
157
- device="cpu",
158
- verbose=True,
159
  ):
160
  try:
 
161
  from .data import preprocess_data
162
-
163
- selected_features = select_features(
164
- df, features, target, selector_method, importance_threshold
165
- )
166
  logging.info(f"Selected features: {selected_features}")
167
-
168
- # --- MUST unpack preprocess_data properly (avoid tuple misuse) ---
169
- (
170
- X,
171
- y,
172
- feature_scaler,
173
- target_scaler,
174
- full_features,
175
- target_idx,
176
- pca,
177
- updated_feature_cols,
178
- ) = preprocess_data(df, selected_features, target, window, horizon)
179
-
180
- X = np.asarray(X)
181
- y = np.asarray(y)
182
-
183
- if X.ndim != 3:
184
- raise ValueError(f"Preprocessed X must be 3D (samples, window, features). Got shape: {X.shape}")
185
- if y.ndim == 1:
186
- # ensure y has shape (samples, horizon)
187
- y = y.reshape(-1, horizon)
188
-
189
  if X.shape[0] < 10:
 
190
  return {"error": f"Insufficient data samples: {X.shape[0]}"}
191
 
192
- # Train/test split (simple slice to preserve time order)
193
  train_size = int((1 - test_split) * len(X))
194
  X_train, X_test = X[:train_size], X[train_size:]
195
  y_train, y_test = y[:train_size], y[train_size:]
196
-
197
- # Build datasets (do NOT move to device here; move in training loop)
198
- train_dataset = TensorDataset(
199
- torch.tensor(X_train, dtype=torch.float32),
200
- torch.tensor(y_train, dtype=torch.float32),
201
- )
202
- test_dataset = TensorDataset(
203
- torch.tensor(X_test, dtype=torch.float32),
204
- torch.tensor(y_test, dtype=torch.float32),
205
- )
206
  train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
207
  test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)
208
 
 
 
 
 
 
 
 
 
209
  input_size = X.shape[2]
210
- model = model_cls(
211
- input_size=input_size,
212
- hidden_size=hidden,
213
- num_layers=layers,
214
- output_size=horizon,
215
- dropout=dropout,
216
- ).to(device)
217
 
218
  if verbose and summary:
219
  try:
220
  output = StringIO()
221
  sys.stdout = output
222
- # summary expects (channels, seq_len) for some models, here we show (seq_len, features)
223
  summary(model, input_size=(window, input_size))
224
  sys.stdout = sys.__stdout__
225
- logging.debug(output.getvalue())
226
  except Exception as e:
227
  logging.warning(f"Failed to generate model summary: {str(e)}")
228
 
229
- optimizer = optim.Adam(
230
- model.parameters(), lr=lr, betas=(beta1, beta2), weight_decay=weight_decay
231
- )
232
  criterion = nn.MSELoss()
233
-
234
  scheduler = None
235
  if scheduler_type == "ReduceLROnPlateau":
236
- scheduler = lr_scheduler.ReduceLROnPlateau(
237
- optimizer, mode="min", factor=0.5, patience=10, verbose=verbose
238
- )
 
239
 
240
- train_losses, val_losses = [], []
 
241
 
242
- # ---------------- Training loop ----------------
243
  for epoch in range(epochs):
244
  model.train()
245
- running_loss = 0.0
246
  for batch_X, batch_y in train_loader:
247
- batch_X = batch_X.to(device)
248
- batch_y = batch_y.to(device)
249
  optimizer.zero_grad()
250
- outputs = model(batch_X)
 
 
 
 
 
 
251
  loss = criterion(outputs, batch_y)
252
  loss.backward()
253
  optimizer.step()
254
- running_loss += loss.item() * batch_X.size(0)
 
 
255
 
256
- epoch_train_loss = running_loss / len(train_loader.dataset)
257
- train_losses.append(epoch_train_loss)
258
-
259
- # validation
260
  model.eval()
261
- running_val = 0.0
262
  with torch.no_grad():
263
  for batch_X, batch_y in test_loader:
264
- batch_X = batch_X.to(device)
265
- batch_y = batch_y.to(device)
266
- outputs = model(batch_X)
267
- v_loss = criterion(outputs, batch_y)
268
- running_val += v_loss.item() * batch_X.size(0)
269
- epoch_val_loss = running_val / len(test_loader.dataset)
270
- val_losses.append(epoch_val_loss)
 
 
 
 
 
 
271
 
272
  if scheduler:
273
- scheduler.step(epoch_val_loss)
274
-
275
- logging.debug(f"Epoch {epoch+1}/{epochs} train={epoch_train_loss:.6f} val={epoch_val_loss:.6f}")
 
 
276
 
277
  # ---------------- Evaluation ----------------
278
  model.eval()
279
  with torch.no_grad():
280
  X_test_tensor = torch.tensor(X_test, dtype=torch.float32).to(device)
281
- y_pred_scaled = model(X_test_tensor).cpu().numpy()
 
 
 
 
 
 
282
 
283
  y_test_unscaled = target_scaler.inverse_transform(y_test.reshape(-1, horizon)).flatten()
284
  y_pred_unscaled = target_scaler.inverse_transform(y_pred_scaled.reshape(-1, horizon)).flatten()
@@ -307,6 +292,7 @@ def train_and_evaluate(
307
  # Latest prediction (use last window from original X)
308
  latest_data = torch.tensor(X[-1:], dtype=torch.float32).to(device)
309
  with torch.no_grad():
 
310
  latest_prediction_scaled = model(latest_data).cpu().numpy()
311
  latest_prediction = target_scaler.inverse_transform(
312
  latest_prediction_scaled.reshape(-1, horizon)
 
1
+ ```python
2
  import numpy as np
3
  import pandas as pd
4
  import torch
 
40
  return np.nan
41
  return np.mean(np.abs((y_true[non_zero] - y_pred[non_zero]) / y_true[non_zero])) * 100
42
 
 
43
  def directional_accuracy(y_true, y_pred):
44
  true_diff = np.diff(y_true)
45
  pred_diff = np.diff(y_pred)
 
48
  return np.nan
49
  return np.mean(np.sign(true_diff) == np.sign(pred_diff))
50
 
 
51
  def mase(y_true, y_pred, y_train):
52
  mae_val = mean_absolute_error(y_true, y_pred)
53
  naive_mae = mean_absolute_error(y_train[1:], y_train[:-1]) if len(y_train) > 1 else np.nan
 
56
  return np.nan
57
  return mae_val / naive_mae
58
 
 
59
  def compute_volatility(y_pred):
60
  returns = np.diff(y_pred) / y_pred[:-1]
61
  if len(returns) == 0:
 
63
  return np.nan
64
  return np.std(returns) * np.sqrt(252)
65
 
 
66
  def compute_sharpe_ratio(y_pred, risk_free_rate=0.01):
67
  returns = np.diff(y_pred) / y_pred[:-1]
68
  if len(returns) == 0:
 
75
  return np.nan
76
  return (mean_return - risk_free_rate) / std_return
77
 
 
78
  def compute_precision_recall(y_true, y_pred):
79
  true_diff = np.sign(np.diff(y_true))
80
  pred_diff = np.sign(np.diff(y_pred))
 
85
  recall = recall_score(true_diff > 0, pred_diff > 0, zero_division=0)
86
  return precision, recall
87
 
 
88
  # ---------------- Feature selection ----------------
89
  def select_features(df, features, target, selector_method, importance_threshold):
90
  logging.info(
 
98
  rf.fit(X, y)
99
  importances = pd.Series(rf.feature_importances_, index=features)
100
  selected_features = importances[importances >= importance_threshold].index.tolist()
101
+ logging.debug(f"RandomForest selected features: {selected_features}, importances: {importances.to_dict()}")
102
  return selected_features if selected_features else features
103
  except Exception as e:
104
  logging.error(f"RandomForest feature selection failed: {str(e)}")
 
112
  pca = PCA(n_components=n_components)
113
  pca.fit(X_scaled)
114
  explained_variance_ratio = pca.explained_variance_ratio_.cumsum()
115
+ n_selected = sum(explained_variance_ratio < 0.95) + 1 if any(explained_variance_ratio < 0.95) else n_components
 
 
 
 
116
  selected_features = features[:n_selected]
117
+ logging.debug(f"PCA selected features: {selected_features}, explained variance: {explained_variance_ratio.tolist()}")
118
  return selected_features if selected_features else features
119
  except Exception as e:
120
  logging.error(f"PCA feature selection failed: {str(e)}")
121
  return features
122
  else:
123
+ logging.warning(f"Unsupported selector_method: {selector_method}, using all features")
 
 
124
  return features
125
 
 
 
126
  def train_and_evaluate(
127
  df,
128
  features,
 
142
  selector_method="RandomForest",
143
  importance_threshold=0.0,
144
  scheduler_type="None",
145
+ device='cpu',
146
+ verbose=True
147
  ):
148
  try:
149
+ logging.info(f"Starting train_and_evaluate: model={model_cls.__name__}, features={len(features)}, window={window}, horizon={horizon}, scheduler={scheduler_type}, selector_method={selector_method}")
150
  from .data import preprocess_data
151
+
152
+ selected_features = select_features(df, features, target, selector_method, importance_threshold)
 
 
153
  logging.info(f"Selected features: {selected_features}")
154
+
155
+ X, y, feature_scaler, target_scaler, full_features, target_idx, pca, updated_feature_cols = preprocess_data(df, selected_features, target, window, horizon)
156
+ logging.debug(f"Preprocess: type(X)={type(X)}, X_shape={X.shape if isinstance(X, np.ndarray) else 'not ndarray'}, type(y)={type(y)}, y_shape={y.shape if isinstance(y, np.ndarray) else 'not ndarray'}")
157
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  if X.shape[0] < 10:
159
+ logging.error(f"Insufficient data samples: {X.shape[0]}")
160
  return {"error": f"Insufficient data samples: {X.shape[0]}"}
161
 
 
162
  train_size = int((1 - test_split) * len(X))
163
  X_train, X_test = X[:train_size], X[train_size:]
164
  y_train, y_test = y[:train_size], y[train_size:]
165
+ logging.debug(f"Train size: {len(X_train)}, Test size: {len(X_test)}")
166
+ logging.debug(f"X_train type: {type(X_train)}, shape: {X_train.shape if isinstance(X_train, np.ndarray) else 'not ndarray'}")
167
+ logging.debug(f"X_test type: {type(X_test)}, shape: {X_test.shape if isinstance(X_test, np.ndarray) else 'not ndarray'}")
168
+
169
+ train_dataset = TensorDataset(torch.tensor(X_train, dtype=torch.float32).to(device),
170
+ torch.tensor(y_train, dtype=torch.float32).to(device))
171
+ test_dataset = TensorDataset(torch.tensor(X_test, dtype=torch.float32).to(device),
172
+ torch.tensor(y_test, dtype=torch.float32).to(device))
 
 
173
  train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
174
  test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)
175
 
176
+ # Debug DataLoader output
177
+ for batch_X, batch_y in train_loader:
178
+ logging.debug(f"DataLoader train batch: X_type={type(batch_X)}, X_shape={batch_X.shape}, y_type={type(batch_y)}, y_shape={batch_y.shape}")
179
+ break
180
+ for batch_X, batch_y in test_loader:
181
+ logging.debug(f"DataLoader test batch: X_type={type(batch_X)}, X_shape={batch_X.shape}, y_type={type(batch_y)}, y_shape={batch_y.shape}")
182
+ break
183
+
184
  input_size = X.shape[2]
185
+ model = model_cls(input_size=input_size, hidden_size=hidden, num_layers=layers, output_size=horizon, dropout=dropout).to(device)
186
+ logging.debug(f"Model initialized: {model_cls.__name__}, input_size={input_size}, hidden={hidden}, layers={layers}")
 
 
 
 
 
187
 
188
  if verbose and summary:
189
  try:
190
  output = StringIO()
191
  sys.stdout = output
 
192
  summary(model, input_size=(window, input_size))
193
  sys.stdout = sys.__stdout__
194
+ logging.debug(f"Model summary:\n{output.getvalue()}")
195
  except Exception as e:
196
  logging.warning(f"Failed to generate model summary: {str(e)}")
197
 
198
+ optimizer = optim.Adam(model.parameters(), lr=lr, betas=(beta1, beta2), weight_decay=weight_decay)
 
 
199
  criterion = nn.MSELoss()
 
200
  scheduler = None
201
  if scheduler_type == "ReduceLROnPlateau":
202
+ scheduler = lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.5, patience=10, verbose=verbose)
203
+ logging.debug("Initialized ReduceLROnPlateau scheduler")
204
+ elif scheduler_type != "None":
205
+ logging.warning(f"Unsupported scheduler type: {scheduler_type}, using None")
206
 
207
+ train_losses = []
208
+ val_losses = []
209
 
 
210
  for epoch in range(epochs):
211
  model.train()
212
+ train_loss = 0.0
213
  for batch_X, batch_y in train_loader:
214
+ logging.debug(f"Training Batch_X type: {type(batch_X)}, shape: {batch_X.shape}")
215
+ logging.debug(f"Training Batch_Y type: {type(batch_y)}, shape: {batch_y.shape}")
216
  optimizer.zero_grad()
217
+ logging.debug(f"Training input to model: type={type(batch_X)}, shape={batch_X.shape}")
218
+ try:
219
+ outputs = model(batch_X)
220
+ logging.debug(f"Training model output shape: {outputs.shape}")
221
+ except Exception as e:
222
+ logging.error(f"Training model forward error: {str(e)}, batch_X_type={type(batch_X)}, batch_X_shape={batch_X.shape}")
223
+ raise
224
  loss = criterion(outputs, batch_y)
225
  loss.backward()
226
  optimizer.step()
227
+ train_loss += loss.item() * batch_X.size(0)
228
+ train_loss /= len(train_loader.dataset)
229
+ train_losses.append(train_loss)
230
 
 
 
 
 
231
  model.eval()
232
+ val_loss = 0.0
233
  with torch.no_grad():
234
  for batch_X, batch_y in test_loader:
235
+ logging.debug(f"Validation Batch_X type: {type(batch_X)}, shape: {batch_X.shape}")
236
+ logging.debug(f"Validation Batch_Y type: {type(batch_y)}, shape: {batch_y.shape}")
237
+ logging.debug(f"Validation input to model: type={type(batch_X)}, shape={batch_X.shape}")
238
+ try:
239
+ outputs = model(batch_X)
240
+ logging.debug(f"Validation model output shape: {outputs.shape}")
241
+ except Exception as e:
242
+ logging.error(f"Validation model forward error: {str(e)}, batch_X_type={type(batch_X)}, batch_X_shape={batch_X.shape}")
243
+ raise
244
+ loss = criterion(outputs, batch_y)
245
+ val_loss += loss.item() * batch_X.size(0)
246
+ val_loss /= len(test_loader.dataset)
247
+ val_losses.append(val_loss)
248
 
249
  if scheduler:
250
+ scheduler.step(val_loss)
251
+ current_lr = optimizer.param_groups[0]['lr']
252
+ logging.debug(f"Epoch {epoch+1}/{epochs}, Train Loss: {train_loss:.6f}, Val Loss: {val_loss:.6f}, LR: {current_lr}")
253
+ else:
254
+ logging.debug(f"Epoch {epoch+1}/{epochs}, Train Loss: {train_loss:.6f}, Val Loss: {val_loss:.6f}")
255
 
256
  # ---------------- Evaluation ----------------
257
  model.eval()
258
  with torch.no_grad():
259
  X_test_tensor = torch.tensor(X_test, dtype=torch.float32).to(device)
260
+ logging.debug(f"Eval model call: type={type(X_test_tensor)}, shape={X_test_tensor.shape}")
261
+ try:
262
+ y_pred_scaled = model(X_test_tensor).cpu().numpy()
263
+ logging.debug(f"Eval model output shape: {y_pred_scaled.shape}")
264
+ except Exception as e:
265
+ logging.error(f"Eval model forward error: {str(e)}, X_test_type={type(X_test_tensor)}, X_test_shape={X_test_tensor.shape}")
266
+ raise
267
 
268
  y_test_unscaled = target_scaler.inverse_transform(y_test.reshape(-1, horizon)).flatten()
269
  y_pred_unscaled = target_scaler.inverse_transform(y_pred_scaled.reshape(-1, horizon)).flatten()
 
292
  # Latest prediction (use last window from original X)
293
  latest_data = torch.tensor(X[-1:], dtype=torch.float32).to(device)
294
  with torch.no_grad():
295
+ logging.debug(f"Latest prediction input: type={type(latest_data)}, shape={latest_data.shape}")
296
  latest_prediction_scaled = model(latest_data).cpu().numpy()
297
  latest_prediction = target_scaler.inverse_transform(
298
  latest_prediction_scaled.reshape(-1, horizon)