2
2
import numpy as np
3
3
import pandas as pd
4
4
from sklearn .base import BaseEstimator
5
- from sklearn .linear_model import ElasticNetCV , LinearRegression , RidgeCV
5
+ from sklearn .linear_model import ElasticNetCV , LinearRegression , RidgeCV , LassoCV
6
6
from sklearn .tree import DecisionTreeRegressor
7
7
from sklearn .utils .validation import check_is_fitted
8
8
from sklearn .utils import check_array
9
9
from sklearn .utils .multiclass import check_classification_targets
10
10
from sklearn .utils .validation import check_X_y
11
11
from sklearn .utils .validation import _check_sample_weight
12
- from sklearn .ensemble import GradientBoostingClassifier , GradientBoostingRegressor
12
+ from sklearn .ensemble import GradientBoostingClassifier , GradientBoostingRegressor , AdaBoostClassifier , AdaBoostRegressor
13
13
from sklearn .model_selection import train_test_split
14
14
from sklearn .metrics import accuracy_score , roc_auc_score
15
15
from tqdm import tqdm
16
16
from collections import defaultdict
17
+ import pandas as pd
18
+ import json
17
19
18
20
import imodels
19
21
from interpret .glassbox import ExplainableBoostingClassifier , ExplainableBoostingRegressor
@@ -31,24 +33,23 @@ class MultiTaskGAM(BaseEstimator):
31
33
32
34
def __init__ (
33
35
self ,
34
- ebm_kwargs = {},
36
+ ebm_kwargs = {'interactions' : 0 , 'n_jobs' : 1 },
35
37
multitask = True ,
38
+ linear_penalty = 'ridge' ,
36
39
random_state = 42 ,
37
-
38
40
):
39
41
"""
40
42
Params
41
43
------
42
44
"""
43
45
self .ebm_kwargs = ebm_kwargs
44
46
self .multitask = multitask
47
+ self .linear_penalty = linear_penalty
45
48
self .random_state = random_state
46
49
if not 'random_state' in ebm_kwargs :
47
50
ebm_kwargs ['random_state' ] = random_state
48
51
self .ebm_ = ExplainableBoostingRegressor (** (ebm_kwargs or {}))
49
52
50
- # self.ebm_ = ExplainableBoostingClassifier(**(ebm_kwargs or {}))
51
-
52
53
def fit (self , X , y , sample_weight = None ):
53
54
X , y = check_X_y (X , y , accept_sparse = False , multi_output = False )
54
55
if isinstance (self , ClassifierMixin ):
@@ -62,59 +63,61 @@ def fit(self, X, y, sample_weight=None):
62
63
return self
63
64
64
65
# fit EBM to each column of X
65
- self .ebms_ = defaultdict ( list )
66
+ self .ebms_ = []
66
67
num_features = X .shape [1 ]
67
68
for task_num in tqdm (range (num_features )):
68
- self .ebms_ [ task_num ] = deepcopy (self .ebm_ )
69
+ self .ebms_ . append ( deepcopy (self .ebm_ ) )
69
70
y_ = np .ascontiguousarray (X [:, task_num ])
70
71
X_ = deepcopy (X )
71
72
X_ [:, task_num ] = 0
72
73
self .ebms_ [task_num ].fit (X_ , y_ , sample_weight = sample_weight )
73
74
74
75
# finally, fit EBM to the target
75
- self .ebms_ [ num_features ] = deepcopy (self .ebm_ )
76
+ self .ebms_ . append ( deepcopy (self .ebm_ ) )
76
77
self .ebms_ [num_features ].fit (X , y , sample_weight = sample_weight )
77
78
78
79
# extract features
79
- feats = self .extract_ebm_features (X )
80
+ feats = self ._extract_ebm_features (X )
80
81
81
82
# fit a linear model to the features
82
- self .lin_model = RidgeCV (alphas = np .logspace (- 2 , 3 , 7 ))
83
+ if self .linear_penalty == 'ridge' :
84
+ self .lin_model = RidgeCV (alphas = np .logspace (- 2 , 3 , 7 ))
85
+ elif self .linear_penalty == 'elasticnet' :
86
+ self .lin_model = ElasticNetCV (n_alphas = 7 )
87
+ elif self .linear_penalty == 'lasso' :
88
+ self .lin_model = LassoCV (n_alphas = 7 )
89
+
83
90
self .lin_model .fit (feats , y )
84
91
return self
85
92
86
- def extract_ebm_features (self , X ):
93
+ def _extract_ebm_features (self , X ):
87
94
'''
88
95
Extract features by predicting each feature with each EBM
89
- This is a hack for now, ideally would just extract curves
96
+ Note: this doesn't currently handle interactions
90
97
'''
98
+ num_ebms = X .shape [1 ] + 1
91
99
num_features = X .shape [1 ]
92
- num_outputs = num_features + 1
93
- feats = np .zeros ((X .shape [0 ], num_features * num_outputs ))
94
- for feat_num in range (num_features ):
95
- X_ = np .zeros_like (X )
96
- X_ [:, feat_num ] = X [:, feat_num ]
97
-
98
- # extract feature curve from each EBM for feat_num
99
- for task_num in range (num_outputs ):
100
- ebm = self .ebms_ [task_num ]
101
- feats [:, feat_num * num_outputs +
102
- task_num ] = ebm .predict (X_ ) - ebm .intercept_
100
+ feats = np .zeros ((X .shape [0 ], num_ebms * num_features ))
101
+ for ebm_num in range (num_ebms ):
102
+ # see eval_terms function: https://interpret.ml/docs/python/api/ExplainableBoostingRegressor.html#interpret.glassbox.ExplainableBoostingRegressor.eval_terms
103
+ feats [:, ebm_num * num_features : (ebm_num + 1 ) * num_features ] = \
104
+ self .ebms_ [ebm_num ].eval_terms (X )
105
+
103
106
return feats
104
107
105
108
def predict (self , X ):
106
109
check_is_fitted (self )
107
110
X = check_array (X , accept_sparse = False )
108
111
if hasattr (self , 'ebms_' ):
109
- feats = self .extract_ebm_features (X )
112
+ feats = self ._extract_ebm_features (X )
110
113
return self .lin_model .predict (feats )
111
114
else :
112
115
return self .ebm_ .predict (X )
113
116
114
- def predict_proba (self , X ):
115
- check_is_fitted (self )
116
- X = check_array (X , accept_sparse = False )
117
- return self .ebm_ .predict_proba (X )
117
+ # def predict_proba(self, X):
118
+ # check_is_fitted(self)
119
+ # X = check_array(X, accept_sparse=False)
120
+ # return self.ebm_.predict_proba(X)
118
121
119
122
120
123
class MultiTaskGAMRegressor (MultiTaskGAM , RegressorMixin ):
@@ -125,58 +128,70 @@ class MultiTaskGAMClassifier(MultiTaskGAM, ClassifierMixin):
125
128
...
126
129
127
130
131
+ def test_multitask_extraction ():
132
+ X , y , feature_names = imodels .get_clean_dataset ("california_housing" )
133
+ # X, y, feature_names = imodels.get_clean_dataset("bike_sharing")
134
+
135
+ # remove some features to speed things up
136
+ X = X [:10 ]
137
+ y = y [:10 ]
138
+ X , X_test , y_train , y_test = train_test_split (X , y , random_state = 42 )
139
+
140
+ # unit test
141
+ gam = MultiTaskGAMRegressor (multitask = False )
142
+ gam .fit (X , y_train )
143
+ ebm = gam .ebm_
144
+ # print('feature_names_in', ebm.feature_names_in_)
145
+ gam2 = MultiTaskGAMRegressor (multitask = True )
146
+ gam2 .fit (X , y_train )
147
+ preds_orig = gam .predict (X_test )
148
+ assert np .allclose (preds_orig , gam2 .ebms_ [- 1 ].predict (X_test ))
149
+
150
+ # extracted curves should sum to original predictions
151
+ feats_extracted = gam2 ._extract_ebm_features (X_test )
152
+ num_samples = X_test .shape [0 ]
153
+ num_features = X_test .shape [1 ]
154
+ num_ebms = num_features + 1
155
+ feats_extracted_target = feats_extracted [:, - num_features :]
156
+ assert feats_extracted_target .shape == (num_samples , num_features )
157
+ preds_extracted_target = np .sum (feats_extracted_target , axis = 1 ) + \
158
+ gam2 .ebms_ [- 1 ].intercept_
159
+ diff = preds_extracted_target - preds_orig
160
+ assert np .allclose (preds_extracted_target , preds_orig ), diff
161
+ print ('Tests pass successfully' )
162
+
163
+
128
164
if __name__ == "__main__" :
165
+ test_multitask_extraction ()
129
166
# X, y, feature_names = imodels.get_clean_dataset("heart")
130
167
X , y , feature_names = imodels .get_clean_dataset ("bike_sharing" )
131
168
# X, y, feature_names = imodels.get_clean_dataset("diabetes")
132
169
133
170
# remove some features to speed things up
134
- # X = X[:, :3]
171
+ X = X [:, :3 ]
135
172
X , X_test , y_train , y_test = train_test_split (X , y , random_state = 42 )
136
- # gam = MultiTaskGAMClassifier(
173
+
137
174
kwargs = dict (
138
175
random_state = 42 ,
139
176
)
140
177
results = defaultdict (list )
141
178
for gam in tqdm ([
142
- MultiTaskGAMRegressor (multitask = False ),
143
- MultiTaskGAMRegressor (multitask = True ),
179
+ # AdaBoostRegressor(estimator=MultiTaskGAMRegressor(
180
+ # multitask=True), n_estimators=2),
181
+ MultiTaskGAMRegressor (multitask = False ),
182
+ # MultiTaskGAMRegressor(multitask=True),
183
+ # ExplainableBoostingRegressor(n_jobs=1, interactions=0)
144
184
]):
145
185
np .random .seed (42 )
146
186
results ["model_name" ].append (gam )
147
187
print ('Fitting' , results ['model_name' ][- 1 ])
148
188
gam .fit (X , y_train )
149
-
150
- # check roc auc score
151
- # y_pred = gam.predict_proba(X_test)[:, 1]
152
- # print(
153
- # "train roc:",
154
- # roc_auc_score(y_train, gam.predict_proba(X)[:, 1]).round(3),
155
- # )
156
- # print("test roc:", round(roc_auc_score(y_test, y_pred), 3))
157
- # print("test acc:", round(accuracy_score(y_test, gam.predict(X_test)), 3))
158
- # print('\t(imb:', np.mean(y_test).round(3), ')')
159
189
results ['test_corr' ].append (np .corrcoef (
160
190
y_test , gam .predict (X_test ))[0 , 1 ].round (3 ))
161
191
results ['test_r2' ].append (gam .score (X_test , y_test ).round (3 ))
162
192
if hasattr (gam , 'lin_model' ):
163
193
print ('lin model coef' , gam .lin_model .coef_ )
164
194
165
- # print('test corr', np.corrcoef(
166
- # y_test, gam.predict(X_test))[0, 1].round(3))
167
- # print('test r2', gam.score(X_test, y_test).round(3))
168
-
169
- # print(
170
- # "accs",
171
- # accuracy_score(y_train, gam.predict(X)).round(3),
172
- # accuracy_score(y_test, gam.predict(X_test)).round(3),
173
- # "imb",
174
- # np.mean(y_train).round(3),
175
- # np.mean(y_test).round(3),
176
- # )
177
-
178
- # # print(gam.estimators_)
179
-
180
195
# don't round strings
181
196
with pd .option_context (
182
197
"display.max_rows" , None , "display.max_columns" , None , "display.width" , 1000
0 commit comments