nn_models.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. from random import sample
  2. from sklearn.base import BaseEstimator
  3. from sklearn.metrics import SCORERS
  4. from sklearn.model_selection import train_test_split
  5. from sklearn.utils import check_X_y, check_array
  6. import torch
  7. from torch import nn, optim
  8. from torch.utils.data import DataLoader
  9. from scipy.sparse import issparse
  10. import numpy as np
  11. class torchMLP (nn.Module):
  12. """
  13. Neural network model for
  14. """
  15. def __init__(self, n_features, n_labels):
  16. super().__init__()
  17. self.network = nn.Sequential(*[
  18. nn.Linear(n_features, 200),
  19. nn.ReLU(),
  20. nn.Linear(200, 50),
  21. nn.ReLU(),
  22. nn.Linear(50, n_labels),
  23. nn.Sigmoid()
  24. ])
  25. def forward(self, x):
  26. y_hat = self.network(x)
  27. return y_hat
  28. class torchMLPClassifier_sklearn (BaseEstimator):
  29. """
  30. Pytorch neural network with a sklearn-like API
  31. """
  32. def __init__ (self, model, n_epochs=50, early_stop=True, early_stop_metric="accuracy", early_stop_validations_size=0.1, batch_size=1024, learning_rate=1e-3, class_weight=None, device_train="cpu", device_predict="cpu"):
  33. """
  34. Parameters:
  35. -----------
  36. model: non instanciated pytorch neural network model with a n_features and n_labels parameter
  37. n_epochs: int, number of epochs
  38. early_stop: boolean, if true an evaluation dataset is created and used to stop the training
  39. early_stop_metric: str, metric score to evaluate the model, according to sklearn.metrics.SCORERS.keys()
  40. early_stop_validations_size: int or float, if float percentage of the train dataset used for validation, otherwise number of sample to use
  41. batch_size: int, size of the training batch
  42. learning_rate: float, Adam optimizer learning rate
  43. class_weight: dict or str, same as the sklearn API
  44. device_train: str, device on which to train
  45. device_predict: str, device on which to predict
  46. """
  47. self.model = model
  48. self.n_epochs = n_epochs
  49. if early_stop and (early_stop_metric is not None) and (early_stop_metric in SCORERS.keys()) and (isinstance(early_stop_validations_size, int) or isinstance(early_stop_validations_size, float)):
  50. self.early_stop = early_stop
  51. self.early_stop_metric = SCORERS[early_stop_metric]
  52. self.early_stop_validations_size = early_stop_validations_size
  53. else:
  54. self.early_stop = False
  55. self.early_stop_metric = None
  56. self.early_stop_validations_size = None
  57. self.class_weight = class_weight
  58. self.learning_rate = learning_rate
  59. self.device_train = device_train
  60. self.device_predict = device_predict
  61. self.batch_size = batch_size
  62. def fit(self, X, y):
  63. """
  64. Training the model
  65. Parameters:
  66. -----------
  67. X_test: pandas dataframe of the features
  68. y_test: pandas dataframe of the labels
  69. """
  70. X, y = check_X_y(X, y, accept_sparse=True, multi_output=True)
  71. if y.ndim == 1:
  72. y = np.expand_dims(y, 1)
  73. # Validation split if early stopping
  74. if self.early_stop:
  75. X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=self.early_stop_validations_size)
  76. if issparse(X_val): # To deal with the sparse matrix situations
  77. X_val = X_val.toarray()
  78. else:
  79. X_train, y_train = X, y
  80. n_samples = y_train.shape[0]
  81. n_labels_values = len(np.unique(y_train))
  82. n_labels = y_train.shape[1]
  83. n_features = X.shape[1]
  84. # Raising the model
  85. self.network = self.model(n_features=n_features, n_labels=n_labels)
  86. self.optimizer = optim.Adam(self.network.parameters(), lr=self.learning_rate)
  87. # Creating dataloader for X_train, y_train
  88. data_loader = DataLoader(range(X_train.shape[0]), shuffle=True, batch_size=self.batch_size)
  89. # Initializing loss function
  90. ## Getting weights
  91. if self.class_weight is not None:
  92. if self.class_weight == "balanced":
  93. weights = n_samples/(n_labels_values*np.bincount(y_train[:,0]))
  94. weights_dict = dict(zip(range(len(weights)), weights))
  95. else:
  96. weights_dict = self.class_weight
  97. else:
  98. weights_dict = None
  99. criterion = nn.BCELoss()
  100. # Running train
  101. last_score = 0
  102. for i in range(self.n_epochs):
  103. # Starting an epoch
  104. for indices in data_loader:
  105. self.optimizer.zero_grad()
  106. X_train_sample, y_train_sample = X_train[indices, :], y_train[indices, :]
  107. if issparse(X_train_sample): # To deal with the sparse matrix situations
  108. X_train_sample = X_train_sample.toarray()
  109. X_train_sample_tensor, y_train_sample_tensor = [torch.tensor(x, dtype=torch.float32).to(self.device_train) for x in [X_train_sample, y_train_sample]]
  110. # Weighting the loss
  111. if self.class_weight is not None:
  112. sample_weights = y_train_sample.copy()
  113. for x, y in weights_dict.items():
  114. sample_weights[sample_weights == x] = y
  115. criterion.weigths = sample_weights
  116. # Get prediction
  117. y_train_sample_hat = self.network(X_train_sample_tensor)
  118. loss = criterion(y_train_sample_hat, y_train_sample_tensor)
  119. loss.backward()
  120. self.optimizer.step()
  121. # End of the Epoch : evaluating the score
  122. if self.early_stop:
  123. score = self.early_stop_metric(self, X_val, y_val)
  124. if score < last_score:
  125. return self
  126. else:
  127. last_score = score
  128. return self
  129. def predict(self, X):
  130. """
  131. Getting the prediction
  132. Parameters:
  133. -----------
  134. X_test: pandas dataframe of the features
  135. """
  136. y_hat_proba = self.predict_raw_proba(X)
  137. y_hat = ((y_hat_proba >= 0.5)*1).flatten()
  138. return y_hat
  139. def predict_raw_proba(self, X):
  140. """
  141. Getting the prediction score in tensor format
  142. Parameters:
  143. -----------
  144. X_test: pandas dataframe of the features
  145. """
  146. X = check_array(X, accept_sparse=True)
  147. if issparse(X): # To deal with the sparse matrix situations
  148. X = X.toarray()
  149. with torch.no_grad():
  150. model_predict = self.network.to(self.device_predict)
  151. model_predict.eval()
  152. # Create a tensor from X
  153. X_tensor = torch.tensor(X, dtype=torch.float32).to(self.device_predict)
  154. y_hat_proba_torch = model_predict(X_tensor)
  155. y_hat_proba_torch = y_hat_proba_torch.detach().cpu().numpy()
  156. return y_hat_proba_torch
  157. def predict_proba(self, X):
  158. """
  159. Getting the prediction score in sklearn format
  160. Parameters:
  161. -----------
  162. X_test: pandas dataframe of the features
  163. """
  164. y_hat_proba_torch = self.predict_raw_proba(X)
  165. y_hat_proba_torch = np.concatenate([
  166. 1-y_hat_proba_torch,
  167. y_hat_proba_torch
  168. ], axis=1)
  169. y_hat_proba = y_hat_proba_torch
  170. return y_hat_proba