Skip to content

Commit 650d3c6

Browse files
committed
add non-interaction test
1 parent f9664a6 commit 650d3c6

File tree

1 file changed

+73
-58
lines changed

1 file changed

+73
-58
lines changed

imodels/algebraic/gam_multitask.py

+73-58
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,20 @@
22
import numpy as np
33
import pandas as pd
44
from sklearn.base import BaseEstimator
5-
from sklearn.linear_model import ElasticNetCV, LinearRegression, RidgeCV
5+
from sklearn.linear_model import ElasticNetCV, LinearRegression, RidgeCV, LassoCV
66
from sklearn.tree import DecisionTreeRegressor
77
from sklearn.utils.validation import check_is_fitted
88
from sklearn.utils import check_array
99
from sklearn.utils.multiclass import check_classification_targets
1010
from sklearn.utils.validation import check_X_y
1111
from sklearn.utils.validation import _check_sample_weight
12-
from sklearn.ensemble import GradientBoostingClassifier, GradientBoostingRegressor
12+
from sklearn.ensemble import GradientBoostingClassifier, GradientBoostingRegressor, AdaBoostClassifier, AdaBoostRegressor
1313
from sklearn.model_selection import train_test_split
1414
from sklearn.metrics import accuracy_score, roc_auc_score
1515
from tqdm import tqdm
1616
from collections import defaultdict
17+
import pandas as pd
18+
import json
1719

1820
import imodels
1921
from interpret.glassbox import ExplainableBoostingClassifier, ExplainableBoostingRegressor
@@ -31,24 +33,23 @@ class MultiTaskGAM(BaseEstimator):
3133

3234
def __init__(
3335
self,
34-
ebm_kwargs={},
36+
ebm_kwargs={'interactions': 0, 'n_jobs': 1},
3537
multitask=True,
38+
linear_penalty='ridge',
3639
random_state=42,
37-
3840
):
3941
"""
4042
Params
4143
------
4244
"""
4345
self.ebm_kwargs = ebm_kwargs
4446
self.multitask = multitask
47+
self.linear_penalty = linear_penalty
4548
self.random_state = random_state
4649
if not 'random_state' in ebm_kwargs:
4750
ebm_kwargs['random_state'] = random_state
4851
self.ebm_ = ExplainableBoostingRegressor(**(ebm_kwargs or {}))
4952

50-
# self.ebm_ = ExplainableBoostingClassifier(**(ebm_kwargs or {}))
51-
5253
def fit(self, X, y, sample_weight=None):
5354
X, y = check_X_y(X, y, accept_sparse=False, multi_output=False)
5455
if isinstance(self, ClassifierMixin):
@@ -62,59 +63,61 @@ def fit(self, X, y, sample_weight=None):
6263
return self
6364

6465
# fit EBM to each column of X
65-
self.ebms_ = defaultdict(list)
66+
self.ebms_ = []
6667
num_features = X.shape[1]
6768
for task_num in tqdm(range(num_features)):
68-
self.ebms_[task_num] = deepcopy(self.ebm_)
69+
self.ebms_.append(deepcopy(self.ebm_))
6970
y_ = np.ascontiguousarray(X[:, task_num])
7071
X_ = deepcopy(X)
7172
X_[:, task_num] = 0
7273
self.ebms_[task_num].fit(X_, y_, sample_weight=sample_weight)
7374

7475
# finally, fit EBM to the target
75-
self.ebms_[num_features] = deepcopy(self.ebm_)
76+
self.ebms_.append(deepcopy(self.ebm_))
7677
self.ebms_[num_features].fit(X, y, sample_weight=sample_weight)
7778

7879
# extract features
79-
feats = self.extract_ebm_features(X)
80+
feats = self._extract_ebm_features(X)
8081

8182
# 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+
8390
self.lin_model.fit(feats, y)
8491
return self
8592

86-
def extract_ebm_features(self, X):
93+
def _extract_ebm_features(self, X):
8794
'''
8895
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
9097
'''
98+
num_ebms = X.shape[1] + 1
9199
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+
103106
return feats
104107

105108
def predict(self, X):
106109
check_is_fitted(self)
107110
X = check_array(X, accept_sparse=False)
108111
if hasattr(self, 'ebms_'):
109-
feats = self.extract_ebm_features(X)
112+
feats = self._extract_ebm_features(X)
110113
return self.lin_model.predict(feats)
111114
else:
112115
return self.ebm_.predict(X)
113116

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)
118121

119122

120123
class MultiTaskGAMRegressor(MultiTaskGAM, RegressorMixin):
@@ -125,58 +128,70 @@ class MultiTaskGAMClassifier(MultiTaskGAM, ClassifierMixin):
125128
...
126129

127130

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+
128164
if __name__ == "__main__":
165+
test_multitask_extraction()
129166
# X, y, feature_names = imodels.get_clean_dataset("heart")
130167
X, y, feature_names = imodels.get_clean_dataset("bike_sharing")
131168
# X, y, feature_names = imodels.get_clean_dataset("diabetes")
132169

133170
# remove some features to speed things up
134-
# X = X[:, :3]
171+
X = X[:, :3]
135172
X, X_test, y_train, y_test = train_test_split(X, y, random_state=42)
136-
# gam = MultiTaskGAMClassifier(
173+
137174
kwargs = dict(
138175
random_state=42,
139176
)
140177
results = defaultdict(list)
141178
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)
144184
]):
145185
np.random.seed(42)
146186
results["model_name"].append(gam)
147187
print('Fitting', results['model_name'][-1])
148188
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), ')')
159189
results['test_corr'].append(np.corrcoef(
160190
y_test, gam.predict(X_test))[0, 1].round(3))
161191
results['test_r2'].append(gam.score(X_test, y_test).round(3))
162192
if hasattr(gam, 'lin_model'):
163193
print('lin model coef', gam.lin_model.coef_)
164194

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-
180195
# don't round strings
181196
with pd.option_context(
182197
"display.max_rows", None, "display.max_columns", None, "display.width", 1000

0 commit comments

Comments
 (0)