P2-ETF-DEEPWAVE-DL / model_b.py
P2SAMAPA's picture
[auto] Sync code from GitHub
7508065 verified
# model_b.py — Option B: Wavelet-Attention-CNN-LSTM
import os
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import config
MODEL_NAME = "model_b"
N_CLASSES = len(config.ETFS)
def build_model(lookback: int, n_features: int) -> keras.Model:
inp = keras.Input(shape=(lookback, n_features))
x = layers.Conv1D(128, 3, padding="causal", activation="relu")(inp)
x = layers.BatchNormalization()(x)
x = layers.Conv1D(64, 3, padding="causal", activation="relu")(x)
x = layers.BatchNormalization()(x)
x = layers.Dropout(0.2)(x)
attn = layers.MultiHeadAttention(num_heads=4, key_dim=16)(x, x)
x = layers.LayerNormalization()(x + attn)
x = layers.Dropout(0.2)(x)
x = layers.LSTM(128, return_sequences=True)(x)
x = layers.Dropout(0.2)(x)
x = layers.LSTM(64)(x)
x = layers.Dense(64, activation="relu")(x)
x = layers.Dropout(0.3)(x)
x = layers.Dense(32, activation="relu")(x)
out = layers.Dense(N_CLASSES, activation="softmax")(x)
model = keras.Model(inputs=inp, outputs=out, name=MODEL_NAME)
model.compile(
optimizer=keras.optimizers.Adam(3e-4),
loss="sparse_categorical_crossentropy",
metrics=["accuracy"],
)
return model
def get_callbacks(lookback: int) -> list:
ckpt = os.path.join(config.MODELS_DIR, MODEL_NAME, f"lb{lookback}", "best.keras")
os.makedirs(os.path.dirname(ckpt), exist_ok=True)
return [
keras.callbacks.EarlyStopping(monitor="val_accuracy", patience=config.PATIENCE,
restore_best_weights=True, mode="max"),
keras.callbacks.ModelCheckpoint(ckpt, monitor="val_accuracy",
save_best_only=True, mode="max"),
keras.callbacks.ReduceLROnPlateau(monitor="val_loss", factor=0.5,
patience=5, min_lr=1e-6),
]
def save_model(model, lookback):
path = os.path.join(config.MODELS_DIR, MODEL_NAME, f"lb{lookback}", "final.keras")
os.makedirs(os.path.dirname(path), exist_ok=True)
model.save(path)
def load_model(lookback):
path = os.path.join(config.MODELS_DIR, MODEL_NAME, f"lb{lookback}", "best.keras")
return keras.models.load_model(path)
def train(prep: dict, epochs: int = config.MAX_EPOCHS):
lookback = prep["lookback"]
n_features = prep["n_features"]
_ytr = prep["y_tr"]
_yva = prep["y_va"]
if _ytr.ndim == 2 and _ytr.shape[1] > 1:
print(f" WARNING: y shape {_ytr.shape} — converting via argmax")
y_tr = _ytr.argmax(axis=1).astype(np.int32)
y_va = _yva.argmax(axis=1).astype(np.int32)
else:
y_tr = _ytr.flatten().astype(np.int32)
y_va = _yva.flatten().astype(np.int32)
print(f"\n[{MODEL_NAME}] lookback={lookback} features={n_features} classes={N_CLASSES}")
print(f" Class dist: {dict(zip(*np.unique(y_tr, return_counts=True)))}")
from sklearn.utils.class_weight import compute_class_weight
cw = compute_class_weight("balanced", classes=np.arange(N_CLASSES), y=y_tr)
class_weights = {i: float(w) for i, w in enumerate(cw)}
model = build_model(lookback, n_features)
history = model.fit(
prep["X_tr"], y_tr,
validation_data=(prep["X_va"], y_va),
epochs=epochs,
batch_size=config.BATCH_SIZE,
callbacks=get_callbacks(lookback),
class_weight=class_weights,
verbose=1,
)
save_model(model, lookback)
return model, history