Coverage for mlinsights/mlmodel/target_predictors.py: 94%

85 statements  

« prev     ^ index     » next       coverage.py v7.1.0, created at 2023-02-28 08:46 +0100

1""" 

2@file 

3@brief Implements a slightly different 

4version of the :epkg:`sklearn:compose:TransformedTargetRegressor`. 

5""" 

6from sklearn.base import BaseEstimator, RegressorMixin, ClassifierMixin, clone 

7from sklearn.exceptions import NotFittedError 

8from sklearn.linear_model import LinearRegression, LogisticRegression 

9from sklearn.metrics import r2_score, accuracy_score 

10from .sklearn_transform_inv import BaseReciprocalTransformer 

11from .sklearn_transform_inv_fct import FunctionReciprocalTransformer, PermutationReciprocalTransformer 

12 

13 

14def _common_get_transform(transformer, is_regression): 

15 if isinstance(transformer, str): 

16 closest = is_regression 

17 if transformer == 'permute': 

18 return PermutationReciprocalTransformer(closest=closest) 

19 else: 

20 return FunctionReciprocalTransformer(transformer) 

21 elif isinstance(transformer, BaseReciprocalTransformer): 

22 return clone(transformer) 

23 raise TypeError( 

24 f"Transformer {type(transformer)} must be a string or " 

25 f"on object of type BaseReciprocalTransformer.") 

26 

27 

28class TransformedTargetRegressor2(BaseEstimator, RegressorMixin): 

29 """ 

30 Meta-estimator to regress on a transformed target. 

31 Useful for applying a non-linear transformation in regression 

32 problems. 

33 

34 Parameters 

35 ---------- 

36 regressor : object, default=LinearRegression() 

37 Regressor object such as derived from ``RegressorMixin``. This 

38 regressor will automatically be cloned each time prior to fitting. 

39 transformer : str or object of type @see cl BaseReciprocalTransformer 

40 

41 Attributes 

42 ---------- 

43 regressor_ : object 

44 Fitted regressor. 

45 transformer_ : object 

46 Transformer used in ``fit`` and ``predict``. 

47 

48 Examples 

49 -------- 

50 

51 .. runpython:: 

52 :showcode: 

53 

54 import numpy 

55 from sklearn.linear_model import LinearRegression 

56 from mlinsights.mlmodel import TransformedTargetRegressor2 

57 

58 tt = TransformedTargetRegressor2(regressor=LinearRegression(), 

59 transformer='log') 

60 X = numpy.arange(4).reshape(-1, 1) 

61 y = numpy.exp(2 * X).ravel() 

62 print(tt.fit(X, y)) 

63 print(tt.score(X, y)) 

64 print(tt.regressor_.coef_) 

65 

66 See notebook :ref:`sklearntransformedtargetrst` for a more complete example. 

67 """ 

68 

69 def __init__(self, regressor=None, transformer=None): 

70 self.regressor = regressor 

71 self.transformer = transformer 

72 

73 def fit(self, X, y, sample_weight=None): 

74 """ 

75 Fits the model according to the given training data. 

76 

77 :param X: {array-like, sparse matrix}, shape (n_samples, n_features) 

78 Training vector, where n_samples is the number of samples and 

79 n_features is the number of features. 

80 :param y: array-like, shape (n_samples,) 

81 Target values. 

82 :param sample_weight: array-like, shape (n_samples,) optional 

83 Array of weights that are assigned to individual samples. 

84 If not provided, then each sample is given unit weight. 

85 :return: self, object 

86 """ 

87 self.transformer_ = _common_get_transform(self.transformer, True) 

88 self.transformer_.fit(X, y, sample_weight=sample_weight) 

89 X_trans, y_trans = self.transformer_.transform(X, y) 

90 

91 if self.regressor is None: 

92 self.regressor_ = LinearRegression() 

93 else: 

94 self.regressor_ = clone(self.regressor) 

95 

96 if sample_weight is None: 

97 self.regressor_.fit(X_trans, y_trans) 

98 else: 

99 self.regressor_.fit(X_trans, y_trans, sample_weight=sample_weight) 

100 

101 return self 

102 

103 def predict(self, X): 

104 """ 

105 Predicts using the base regressor, applying inverse. 

106 

107 :param X: {array-like, sparse matrix}, shape = (n_samples, n_features) 

108 Samples. 

109 :return: y_hat : array, shape = (n_samples,) 

110 Predicted values. 

111 """ 

112 if not hasattr(self, 'regressor_'): 

113 raise NotFittedError( # pragma: no cover 

114 f"This instance {type(self)} is not fitted yet. Call 'fit' with " 

115 f"appropriate arguments before using this method.") 

116 X_trans, _ = self.transformer_.transform(X, None) 

117 pred = self.regressor_.predict(X_trans) 

118 

119 inv = self.transformer_.get_fct_inv() 

120 _, pred_inv = inv.transform(X_trans, pred) 

121 return pred_inv 

122 

123 def score(self, X, y, sample_weight=None): 

124 """ 

125 Scores the model with 

126 :epkg:`sklearn:metrics:r2_score`. 

127 """ 

128 yp = self.predict(X) 

129 return r2_score(y, yp, sample_weight=sample_weight) 

130 

131 def _more_tags(self): 

132 return {'poor_score': True, 'no_validation': True} 

133 

134 

135class TransformedTargetClassifier2(BaseEstimator, ClassifierMixin): 

136 """ 

137 Meta-estimator to classify on a transformed target. 

138 Useful for applying permutation transformation in classification 

139 problems. 

140 

141 Parameters 

142 ---------- 

143 classifier : object, default=LogisticRegression() 

144 Classifier object such as derived from ``ClassifierMixin``. This 

145 classifier will automatically be cloned each time prior to fitting. 

146 transformer : str or object of type @see cl BaseReciprocalTransformer 

147 

148 Attributes 

149 ---------- 

150 classifier_ : object 

151 Fitted classifier. 

152 transformer_ : object 

153 Transformer used in ``fit``, ``predict``, ``decision_function``, 

154 ``predict_proba``. 

155 

156 Examples 

157 -------- 

158 

159 .. runpython:: 

160 :showcode: 

161 

162 import numpy 

163 from sklearn.linear_model import LogisticRegression 

164 from mlinsights.mlmodel import TransformedTargetClassifier2 

165 

166 tt = TransformedTargetClassifier2(classifier=LogisticRegression(), 

167 transformer='permute') 

168 X = numpy.arange(4).reshape(-1, 1) 

169 y = numpy.array([0, 1, 0, 1]) 

170 print(tt.fit(X, y)) 

171 print(tt.score(X, y)) 

172 print(tt.classifier_.coef_) 

173 

174 See notebook :ref:`sklearntransformedtargetrst` for a more complete example. 

175 """ 

176 

177 def __init__(self, classifier=None, transformer=None): 

178 self.classifier = classifier 

179 self.transformer = transformer 

180 

181 def fit(self, X, y, sample_weight=None): 

182 """ 

183 Fits the model according to the given training data. 

184 

185 :param X: {array-like, sparse matrix}, shape (n_samples, n_features) 

186 Training vector, where n_samples is the number of samples and 

187 n_features is the number of features. 

188 :param y: array-like, shape (n_samples,) 

189 Target values. 

190 :param sample_weight: array-like, shape (n_samples,) optional 

191 Array of weights that are assigned to individual samples. 

192 If not provided, then each sample is given unit weight. 

193 :return: self, object 

194 """ 

195 self.transformer_ = _common_get_transform(self.transformer, False) 

196 self.transformer_.fit(X, y, sample_weight=sample_weight) 

197 X_trans, y_trans = self.transformer_.transform(X, y) 

198 

199 if self.classifier is None: 

200 self.classifier_ = LogisticRegression() 

201 else: 

202 self.classifier_ = clone(self.classifier) 

203 

204 if sample_weight is None: 

205 self.classifier_.fit(X_trans, y_trans) 

206 else: 

207 self.classifier_.fit(X_trans, y_trans, sample_weight=sample_weight) 

208 

209 return self 

210 

211 def _check_is_fitted(self): 

212 if not hasattr(self, 'classifier_'): 

213 raise NotFittedError( # pragma: no cover 

214 f"This instance {type(self)} is not fitted yet. Call 'fit' with " 

215 f"appropriate arguments before using this method.") 

216 

217 @property 

218 def classes_(self): 

219 """ 

220 Returns the classes. 

221 """ 

222 self._check_is_fitted() 

223 inv = self.transformer_.get_fct_inv() 

224 _, pred_inv = inv.transform(None, self.classifier_.classes_) 

225 return pred_inv 

226 

227 def _apply(self, X, method): 

228 """ 

229 Calls *predict*, *predict_proba* or *decision_function* 

230 using the base classifier, applying inverse. 

231 

232 :param X: {array-like, sparse matrix}, shape = (n_samples, n_features) 

233 Samples. 

234 :return: y_hat, array, shape = (n_samples,) 

235 Predicted values. 

236 """ 

237 self._check_is_fitted() 

238 if not hasattr(self.classifier_, method): 

239 raise RuntimeError( # pragma: no cover 

240 f"Unable to find method {method!r} in model " 

241 f"{type(self.classifier_)}.") 

242 meth = getattr(self.classifier_, method) 

243 X_trans, _ = self.transformer_.transform(X, None) 

244 pred = meth(X_trans) 

245 inv = self.transformer_.get_fct_inv() 

246 _, pred_inv = inv.transform(X_trans, pred) 

247 return pred_inv 

248 

249 def predict(self, X): 

250 """ 

251 Predicts using the base classifier, applying inverse. 

252 

253 :param X: {array-like, sparse matrix}, shape = (n_samples, n_features) 

254 Samples. 

255 :return: y_hat, array, shape = (n_samples,) 

256 Predicted values. 

257 """ 

258 return self._apply(X, 'predict') 

259 

260 def predict_proba(self, X): 

261 """ 

262 Predicts using the base classifier, applying inverse. 

263 

264 :param X: {array-like, sparse matrix}, shape = (n_samples, n_features) 

265 Samples. 

266 :return: predict probabilities, array, shape = (n_samples, n_classes) 

267 Predicted values. 

268 """ 

269 return self._apply(X, 'predict_proba') 

270 

271 def decision_function(self, X): 

272 """ 

273 Predicts using the base classifier, applying inverse. 

274 

275 :param X: {array-like, sparse matrix}, shape = (n_samples, n_features) 

276 Samples. 

277 :return: raw score : array, shape = (n_samples, ?) 

278 """ 

279 return self._apply(X, 'decision_function') 

280 

281 def score(self, X, y, sample_weight=None): 

282 """ 

283 Scores the model with 

284 :epkg:`sklearn:metrics:accuracy_score`. 

285 """ 

286 yp = self.predict(X) 

287 return accuracy_score(y, yp, sample_weight=sample_weight) 

288 

289 def _more_tags(self): 

290 return {'poor_score': True, 'no_validation': True}