Modele.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. from Model.profil import profile
  2. import Model.Portefeuille
  3. import Model.Parametre
  4. import Model.Donnee
  5. import random
  6. import numpy as np
  7. import os
  8. import copy
  9. import torch
  10. import torch.nn as nn
  11. import torch.optim as optim
  12. from torch.utils.data import DataLoader, TensorDataset
  13. class CL_Model(nn.Module):
  14. def __init__(self, Parametre, hidden=64, d_model=64, n_heads=4):
  15. super().__init__()
  16. self.temporal_encoder = nn.Sequential(
  17. nn.Linear(Parametre.NbAnalysis, hidden),
  18. nn.ReLU(),
  19. nn.Linear(hidden, d_model)
  20. )
  21. self.temporal_mixer = nn.Sequential(
  22. nn.Conv1d(d_model, d_model, kernel_size=3, padding=1),
  23. nn.ReLU(),
  24. nn.Conv1d(d_model, d_model, kernel_size=3, padding=1)
  25. )
  26. # inter-assets attention
  27. self.asset_attention = nn.MultiheadAttention(
  28. embed_dim=d_model,
  29. num_heads=n_heads,
  30. batch_first=True
  31. )
  32. self.norm = nn.LayerNorm(d_model)
  33. self.head = nn.Sequential(
  34. nn.Linear(d_model, d_model),
  35. nn.ReLU(),
  36. nn.Linear(d_model, 1),
  37. )
  38. # ===== TRAINING =====
  39. """"""
  40. # self.optimizer = optim.Adam(self.parameters(), lr=Parametre.LearningRate)
  41. self.optimizer = torch.optim.AdamW(self.parameters(), lr=1e-3, weight_decay=1e-4)
  42. # self.scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, factor=0.1, patience=5)
  43. self.scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(self.optimizer, T_max=50)
  44. """
  45. self.optimizer = torch.optim.Adam(model.parameters(), lr=1e-2)
  46. self.scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1)
  47. """
  48. self.loss_fn = nn.HuberLoss(delta=1.0) # nn.MSELoss()#nn.SmoothL1Loss() nn.HuberLoss()#
  49. self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
  50. print(f"le réseau s'éxécute sur {self.device}")
  51. self.to(self.device)
  52. # =========================
  53. # FORWARD
  54. # =========================
  55. def forward(self, x):
  56. # (B, A, T, F)
  57. B, A, T, F = x.shape
  58. # =========================
  59. # 1. Temporal encoding par asset
  60. # =========================
  61. x = self.temporal_encoder(x) # (B, A, T, D)
  62. # =========================
  63. # 2. Temporal convolution (mixing local patterns)
  64. # =========================
  65. x = x.view(B * A, T, -1)
  66. x = x.permute(0, 2, 1) # (B*A, D, T)
  67. x = self.temporal_mixer(x)
  68. x = x.mean(dim=2) # (B*A, D)
  69. x = x.view(B, A, -1) # (B, A, D)
  70. # =========================
  71. # 3. Inter-asset modeling
  72. # =========================
  73. residual = x
  74. x, _ = self.asset_attention(x, x, x)
  75. x = self.norm(x + residual)
  76. # =========================
  77. # 4. Head regression
  78. # =========================
  79. x = self.head(x).squeeze(-1) # (B, A)
  80. return x
  81. # =========================
  82. # FIT
  83. # =========================
  84. @profile
  85. def ancienfit(self, X, y, Parametre, EntreeValidation, TargetValidation):
  86. self.train()
  87. X = torch.tensor(X, dtype=torch.float32)
  88. y = torch.tensor(y, dtype=torch.float32)
  89. dataset = TensorDataset(X, y)
  90. loader = DataLoader(dataset, batch_size=Parametre.batch_size, shuffle=False)
  91. best_val = float("inf")
  92. nbStagne = 0
  93. for epoch in range(Parametre.epochs):
  94. total_loss = 0
  95. self.train()
  96. self.optimizer.step()
  97. self.scheduler.step()
  98. for X_batch, y_batch in loader:
  99. X_batch = X_batch.to(self.device)
  100. y_batch = y_batch.to(self.device)
  101. self.optimizer.zero_grad()
  102. pred = self(X_batch)
  103. loss = self.loss_fn(pred, y_batch)
  104. direction_loss = torch.mean(torch.relu(-pred * y_batch))
  105. loss = loss + 2 * direction_penalty
  106. loss.backward()
  107. self.optimizer.step()
  108. total_loss += loss.item()
  109. avg_loss = total_loss / len(loader)
  110. current_lr = self.optimizer.param_groups[0]['lr']
  111. validLoss = self.ValidLoss(EntreeValidation, TargetValidation, Parametre)
  112. if validLoss < best_val:
  113. best_val = validLoss
  114. nbStagne = 0
  115. best_weights = copy.deepcopy(self.state_dict())
  116. print(
  117. f"epoch {epoch} | loss: {avg_loss:.6f} | ValidLoss : {validLoss} | Stagne : {nbStagne}| LR : {current_lr} ")
  118. else:
  119. nbStagne += 1
  120. if nbStagne == 20:
  121. self.load_state_dict(best_weights)
  122. if nbStagne == 1000:
  123. self.load_state_dict(best_weights)
  124. return epoch
  125. @profile
  126. def fit(self, X, y, Parametre, EntreeValidation, TargetValidation):
  127. self.train()
  128. X = torch.tensor(X, dtype=torch.float32)
  129. y = torch.tensor(y, dtype=torch.float32)
  130. dataset = TensorDataset(X, y)
  131. loader = DataLoader(dataset, batch_size=Parametre.batch_size, shuffle=False)
  132. best_val = float("inf")
  133. nbStagne = 0
  134. best_weights = None
  135. for epoch in range(Parametre.epochs):
  136. total_loss = 0
  137. self.train()
  138. for X_batch, y_batch in loader:
  139. X_batch = X_batch.to(self.device)
  140. y_batch = y_batch.to(self.device)
  141. self.optimizer.zero_grad()
  142. pred = self(X_batch)
  143. loss = self.CalculLoss(pred,y_batch)
  144. # stabilité gradients
  145. norm = torch.nn.utils.clip_grad_norm_(self.parameters(), 5.0)
  146. loss.backward()
  147. self.optimizer.step()
  148. total_loss += loss.item()
  149. avg_loss = total_loss / len(loader)
  150. # validation
  151. validLoss = self.ValidLoss(EntreeValidation, TargetValidation, Parametre)
  152. current_lr = self.optimizer.param_groups[0]["lr"]
  153. self.scheduler.step()
  154. # early stopping
  155. if validLoss < best_val:
  156. best_val = validLoss
  157. best_weights = copy.deepcopy(self.state_dict())
  158. print(
  159. f"epoch {epoch} | " f"loss: {avg_loss:.6f} | " f"valid: {validLoss:.6f} | "f"stagnation: {nbStagne} | " f"lr: {current_lr}")
  160. nbStagne = 0
  161. else:
  162. nbStagne += 1
  163. if nbStagne >= 1000:
  164. self.load_state_dict(best_weights)
  165. return epoch
  166. @profile
  167. def CalculLoss(self, X, Y):
  168. loss = self.loss_fn(X,Y)
  169. direction_loss = torch.mean(torch.relu(-X * Y))
  170. # print(direction_loss)
  171. loss = loss + direction_loss
  172. return direction_loss
  173. # =========================
  174. # PREDICT
  175. # =========================
  176. @profile
  177. def predict(self, X):
  178. self.eval()
  179. with torch.no_grad():
  180. X = torch.tensor(X, dtype=torch.float32).to(self.device)
  181. pred = self(X)
  182. return pred.cpu().numpy()
  183. @profile
  184. def EnregistreModel(self, Parametre, NomDossier, valeur):
  185. if not os.path.exists(NomDossier):
  186. os.makedirs(NomDossier)
  187. nom = valeur / Parametre.PortefeuilleDemarage
  188. nom = str(nom).replace('.', '_')
  189. nom_model = f"{NomDossier}/{nom}.pt"
  190. torch.save({
  191. "model_state_dict": self.state_dict(),
  192. "optimizer_state_dict": self.optimizer.state_dict(),
  193. "best_score": valeur
  194. }, nom_model)
  195. nom_Parametre = f"{NomDossier}/{nom}"
  196. Parametre.StockParam(nom_Parametre)
  197. return nom
  198. @profile
  199. def RestaurationModel(self, NomDuModel):
  200. NomDuModel = NomDuModel + ".pt"
  201. checkpoint = torch.load(NomDuModel, map_location=self.device)
  202. self.load_state_dict(checkpoint["model_state_dict"])
  203. self.optimizer.load_state_dict(checkpoint["optimizer_state_dict"])
  204. self.to(self.device)
  205. self.eval()
  206. return self
  207. @profile
  208. def Evaluation(self, Date, Portefeuille, Donnee):
  209. return Portefeuille.getValueTotal(Date, Donnee)
  210. def ValidLoss(self, Entree, Target, Parametre):
  211. self.eval()
  212. X = torch.tensor(Entree, dtype=torch.float32)
  213. y = torch.tensor(Target, dtype=torch.float32)
  214. dataset = TensorDataset(X, y)
  215. loader = DataLoader(
  216. dataset,
  217. batch_size=Parametre.batch_size,
  218. shuffle=False
  219. )
  220. total_loss = 0
  221. with torch.no_grad():
  222. for X_batch, y_batch in loader:
  223. X_batch = X_batch.to(self.device)
  224. y_batch = y_batch.to(self.device)
  225. pred = self(X_batch)
  226. loss = self.CalculLoss(pred, y_batch)
  227. total_loss += loss.item()
  228. avg_loss = total_loss / len(loader)
  229. return avg_loss