nn_models.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  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.class_weight = class_weight
  56. self.learning_rate = learning_rate
  57. self.device_train = device_train
  58. self.device_predict = device_predict
  59. self.batch_size = batch_size
  60. def fit(self, X, y):
  61. """
  62. Training the model
  63. Parameters:
  64. -----------
  65. X_test: pandas dataframe of the features
  66. y_test: pandas dataframe of the labels
  67. """
  68. X, y = check_X_y(X, y, accept_sparse=True, multi_output=True)
  69. if y.ndim == 1:
  70. y = np.expand_dims(y, 1)
  71. # Validation split if early stopping
  72. if self.early_stop:
  73. X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=self.early_stop_validations_size)
  74. if issparse(X_val): # To deal with the sparse matrix situations
  75. X_val = X_val.toarray()
  76. else:
  77. X_train, y_train = X, y
  78. n_samples = y_train.shape[0]
  79. n_labels_values = len(np.unique(y_train))
  80. n_labels = y_train.shape[1]
  81. n_features = X.shape[1]
  82. # Raising the model
  83. self.network = self.model(n_features=n_features, n_labels=n_labels)
  84. self.optimizer = optim.Adam(self.network.parameters(), lr=self.learning_rate)
  85. # Creating dataloader for X_train, y_train
  86. data_loader = DataLoader(range(X_train.shape[0]), shuffle=True, batch_size=self.batch_size)
  87. # Initializing loss function
  88. ## Getting weights
  89. if self.class_weight is not None:
  90. if self.class_weight == "balanced":
  91. weights = n_samples/(n_labels_values*np.bincount(y_train[:,0]))
  92. weights_dict = dict(zip(range(len(weights)), weights))
  93. else:
  94. weights_dict = self.class_weight
  95. else:
  96. weights_dict = None
  97. criterion = nn.BCELoss()
  98. # Running train
  99. last_score = 0
  100. for i in range(self.n_epochs):
  101. # Starting an epoch
  102. for indices in data_loader:
  103. self.optimizer.zero_grad()
  104. X_train_sample, y_train_sample = X_train[indices, :], y_train[indices, :]
  105. if issparse(X_train_sample): # To deal with the sparse matrix situations
  106. X_train_sample = X_train_sample.toarray()
  107. 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]]
  108. # Weighting the loss
  109. if self.class_weight is not None:
  110. sample_weights = y_train_sample.copy()
  111. for x, y in weights_dict.items():
  112. sample_weights[sample_weights == x] = y
  113. criterion.weigths = sample_weights
  114. # Get prediction
  115. y_train_sample_hat = self.network(X_train_sample_tensor)
  116. loss = criterion(y_train_sample_hat, y_train_sample_tensor)
  117. loss.backward()
  118. self.optimizer.step()
  119. # End of the Epoch : evaluating the score
  120. if self.early_stop:
  121. score = self.early_stop_metric(self, X_val, y_val)
  122. if score < last_score:
  123. return self
  124. else:
  125. last_score = score
  126. return self
  127. def predict(self, X):
  128. """
  129. Getting the prediction
  130. Parameters:
  131. -----------
  132. X_test: pandas dataframe of the features
  133. """
  134. y_hat_proba = self.predict_raw_proba(X)
  135. y_hat = (y_hat_proba >= 0.5)*1
  136. return y_hat
  137. def predict_raw_proba(self, X):
  138. """
  139. Getting the prediction score in tensor format
  140. Parameters:
  141. -----------
  142. X_test: pandas dataframe of the features
  143. """
  144. X = check_array(X, accept_sparse=True)
  145. if issparse(X): # To deal with the sparse matrix situations
  146. X = X.toarray()
  147. with torch.no_grad():
  148. model_predict = self.network.to(self.device_predict)
  149. model_predict.eval()
  150. # Create a tensor from X
  151. X_tensor = torch.tensor(X, dtype=torch.float32).to(self.device_predict)
  152. y_hat_proba_torch = model_predict(X_tensor)
  153. y_hat_proba_torch = y_hat_proba_torch.detach().cpu().numpy()
  154. return y_hat_proba_torch
  155. def predict_proba(self, X):
  156. """
  157. Getting the prediction score in sklearn format
  158. Parameters:
  159. -----------
  160. X_test: pandas dataframe of the features
  161. """
  162. y_hat_proba_torch = self.predict_raw_proba(X)
  163. y_hat_proba_torch = np.concatenate([
  164. 1-y_hat_proba_torch,
  165. y_hat_proba_torch
  166. ], axis=0)
  167. y_hat_proba = y_hat_proba_torch
  168. return y_hat_proba