| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326 |
- from Model.profil import profile
- import Model.Portefeuille
- import Model.Parametre
- import Model.Donnee
- import random
- import numpy as np
- import os
- import copy
- import torch
- import torch.nn as nn
- import torch.optim as optim
- from torch.utils.data import DataLoader, TensorDataset
- class CL_Model(nn.Module):
- def __init__(self, Parametre, hidden=64, d_model=64, n_heads=4):
- super().__init__()
-
- self.temporal_encoder = nn.Sequential(
- nn.Linear(Parametre.NbAnalysis, hidden),
- nn.ReLU(),
- nn.Linear(hidden, d_model)
- )
-
- self.temporal_mixer = nn.Sequential(
- nn.Conv1d(d_model, d_model, kernel_size=3, padding=1),
- nn.ReLU(),
- nn.Conv1d(d_model, d_model, kernel_size=3, padding=1)
- )
-
- # inter-assets attention
- self.asset_attention = nn.MultiheadAttention(
- embed_dim=d_model,
- num_heads=n_heads,
- batch_first=True
- )
-
- self.norm = nn.LayerNorm(d_model)
-
- self.head = nn.Sequential(
- nn.Linear(d_model, d_model),
- nn.ReLU(),
- nn.Linear(d_model, 1),
- )
-
- # ===== TRAINING =====
- """"""
- # self.optimizer = optim.Adam(self.parameters(), lr=Parametre.LearningRate)
- self.optimizer = torch.optim.AdamW(self.parameters(), lr=1e-3, weight_decay=1e-4)
- # self.scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, factor=0.1, patience=5)
- self.scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(self.optimizer, T_max=50)
- """
- self.optimizer = torch.optim.Adam(model.parameters(), lr=1e-2)
- self.scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1)
- """
-
- self.loss_fn = nn.HuberLoss(delta=1.0) # nn.MSELoss()#nn.SmoothL1Loss() nn.HuberLoss()#
-
- self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
- print(f"le réseau s'éxécute sur {self.device}")
- self.to(self.device)
-
- # =========================
- # FORWARD
- # =========================
- def forward(self, x):
- # (B, A, T, F)
- B, A, T, F = x.shape
-
- # =========================
- # 1. Temporal encoding par asset
- # =========================
- x = self.temporal_encoder(x) # (B, A, T, D)
-
- # =========================
- # 2. Temporal convolution (mixing local patterns)
- # =========================
- x = x.view(B * A, T, -1)
- x = x.permute(0, 2, 1) # (B*A, D, T)
- x = self.temporal_mixer(x)
- x = x.mean(dim=2) # (B*A, D)
-
- x = x.view(B, A, -1) # (B, A, D)
-
- # =========================
- # 3. Inter-asset modeling
- # =========================
- residual = x
- x, _ = self.asset_attention(x, x, x)
- x = self.norm(x + residual)
-
- # =========================
- # 4. Head regression
- # =========================
- x = self.head(x).squeeze(-1) # (B, A)
-
- return x
-
- # =========================
- # FIT
- # =========================
- @profile
- def ancienfit(self, X, y, Parametre, EntreeValidation, TargetValidation):
-
- self.train()
-
- X = torch.tensor(X, dtype=torch.float32)
- y = torch.tensor(y, dtype=torch.float32)
-
- dataset = TensorDataset(X, y)
- loader = DataLoader(dataset, batch_size=Parametre.batch_size, shuffle=False)
- best_val = float("inf")
- nbStagne = 0
- for epoch in range(Parametre.epochs):
- total_loss = 0
- self.train()
-
- self.optimizer.step()
- self.scheduler.step()
-
- for X_batch, y_batch in loader:
- X_batch = X_batch.to(self.device)
- y_batch = y_batch.to(self.device)
- self.optimizer.zero_grad()
-
- pred = self(X_batch)
-
- loss = self.loss_fn(pred, y_batch)
- direction_loss = torch.mean(torch.relu(-pred * y_batch))
- loss = loss + 2 * direction_penalty
-
- loss.backward()
-
- self.optimizer.step()
-
- total_loss += loss.item()
-
- avg_loss = total_loss / len(loader)
- current_lr = self.optimizer.param_groups[0]['lr']
-
- validLoss = self.ValidLoss(EntreeValidation, TargetValidation, Parametre)
-
- if validLoss < best_val:
- best_val = validLoss
- nbStagne = 0
- best_weights = copy.deepcopy(self.state_dict())
- print(
- f"epoch {epoch} | loss: {avg_loss:.6f} | ValidLoss : {validLoss} | Stagne : {nbStagne}| LR : {current_lr} ")
- else:
- nbStagne += 1
- if nbStagne == 20:
- self.load_state_dict(best_weights)
- if nbStagne == 1000:
- self.load_state_dict(best_weights)
- return epoch
-
- @profile
- def fit(self, X, y, Parametre, EntreeValidation, TargetValidation):
-
- self.train()
-
- X = torch.tensor(X, dtype=torch.float32)
- y = torch.tensor(y, dtype=torch.float32)
-
- dataset = TensorDataset(X, y)
-
- loader = DataLoader(dataset, batch_size=Parametre.batch_size, shuffle=False)
-
- best_val = float("inf")
- nbStagne = 0
- best_weights = None
-
- for epoch in range(Parametre.epochs):
-
- total_loss = 0
- self.train()
-
- for X_batch, y_batch in loader:
- X_batch = X_batch.to(self.device)
- y_batch = y_batch.to(self.device)
-
- self.optimizer.zero_grad()
-
- pred = self(X_batch)
-
- loss = self.CalculLoss(pred,y_batch)
-
- # stabilité gradients
- norm = torch.nn.utils.clip_grad_norm_(self.parameters(), 5.0)
-
- loss.backward()
- self.optimizer.step()
-
- total_loss += loss.item()
-
- avg_loss = total_loss / len(loader)
-
- # validation
- validLoss = self.ValidLoss(EntreeValidation, TargetValidation, Parametre)
-
- current_lr = self.optimizer.param_groups[0]["lr"]
-
- self.scheduler.step()
-
- # early stopping
- if validLoss < best_val:
- best_val = validLoss
-
- best_weights = copy.deepcopy(self.state_dict())
- print(
- f"epoch {epoch} | " f"loss: {avg_loss:.6f} | " f"valid: {validLoss:.6f} | "f"stagnation: {nbStagne} | " f"lr: {current_lr}")
- nbStagne = 0
- else:
- nbStagne += 1
- if nbStagne >= 1000:
- self.load_state_dict(best_weights)
- return epoch
-
- @profile
- def CalculLoss(self, X, Y):
- loss = self.loss_fn(X,Y)
-
- direction_loss = torch.mean(torch.relu(-X * Y))
- # print(direction_loss)
-
- loss = loss + direction_loss
- return direction_loss
-
- # =========================
- # PREDICT
- # =========================
- @profile
- def predict(self, X):
- self.eval()
- with torch.no_grad():
- X = torch.tensor(X, dtype=torch.float32).to(self.device)
- pred = self(X)
-
- return pred.cpu().numpy()
-
- @profile
- def EnregistreModel(self, Parametre, NomDossier, valeur):
- if not os.path.exists(NomDossier):
- os.makedirs(NomDossier)
-
- nom = valeur / Parametre.PortefeuilleDemarage
- nom = str(nom).replace('.', '_')
-
- nom_model = f"{NomDossier}/{nom}.pt"
-
- torch.save({
- "model_state_dict": self.state_dict(),
- "optimizer_state_dict": self.optimizer.state_dict(),
- "best_score": valeur
- }, nom_model)
-
- nom_Parametre = f"{NomDossier}/{nom}"
- Parametre.StockParam(nom_Parametre)
-
- return nom
-
- @profile
- def RestaurationModel(self, NomDuModel):
- NomDuModel = NomDuModel + ".pt"
- checkpoint = torch.load(NomDuModel, map_location=self.device)
-
- self.load_state_dict(checkpoint["model_state_dict"])
- self.optimizer.load_state_dict(checkpoint["optimizer_state_dict"])
-
- self.to(self.device)
- self.eval()
-
- return self
-
- @profile
- def Evaluation(self, Date, Portefeuille, Donnee):
- return Portefeuille.getValueTotal(Date, Donnee)
-
-
- def ValidLoss(self, Entree, Target, Parametre):
-
- self.eval()
-
- X = torch.tensor(Entree, dtype=torch.float32)
- y = torch.tensor(Target, dtype=torch.float32)
-
- dataset = TensorDataset(X, y)
-
- loader = DataLoader(
- dataset,
- batch_size=Parametre.batch_size,
- shuffle=False
- )
-
- total_loss = 0
-
- with torch.no_grad():
- for X_batch, y_batch in loader:
- X_batch = X_batch.to(self.device)
- y_batch = y_batch.to(self.device)
-
- pred = self(X_batch)
-
- loss = self.CalculLoss(pred, y_batch)
-
- total_loss += loss.item()
-
- avg_loss = total_loss / len(loader)
-
- return avg_loss
|