Skip to content

Commit 11776cd

Browse files
author
[zebinyang]
committed
update diff function in smoothing spline; remove leaf node update option in simtree; update version 0.1.4
1 parent 189e09b commit 11776cd

File tree

5 files changed

+17
-190
lines changed

5 files changed

+17
-190
lines changed

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from setuptools import setup
22

33
setup(name='simtree',
4-
version='0.1.3',
4+
version='0.1.4',
55
description='Single-index model tree',
66
url='https://github.com/ZebinYang/SIMTree',
77
author='Zebin Yang',

simtree/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@
88
"SIMTreeRegressor", "SIMTreeClassifier",
99
"CustomMobTreeRegressor", "CustomMobTreeClassifier"]
1010

11-
__version__ = '0.1.3'
11+
__version__ = '0.1.4'
1212
__author__ = 'Zebin Yang'

simtree/sim.py

Lines changed: 0 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -112,164 +112,6 @@ def decision_function(self, x):
112112
pred = self.shape_fit_.decision_function(xb)
113113
return pred
114114

115-
def fit_middle_update_adam(self, x, y, val_ratio=0.2, tol=0.0001,
116-
max_middle_iter=100, n_middle_iter_no_change=5, max_inner_iter=100, n_inner_iter_no_change=5,
117-
batch_size=100, learning_rate=1e-3, beta_1=0.9, beta_2=0.999, stratify=True, verbose=False):
118-
119-
"""fine tune the fitted Sim model using middle update method (adam)
120-
121-
Parameters
122-
---------
123-
x : array-like of shape (n_samples, n_features)
124-
containing the input dataset
125-
y : array-like of shape (n_samples,)
126-
containing target values
127-
val_ratio : float, optional, default=0.2
128-
the split ratio for validation set
129-
tol : float, optional, default=0.0001
130-
the tolerance for early stopping
131-
max_middle_iter : int, optional, default=3
132-
the maximal number of middle iteration
133-
n_middle_iter_no_change : int, optional, default=3
134-
the tolerance of non-improving middle iterations
135-
max_inner_iter : int, optional, default=100
136-
the maximal number of inner iteration (epoch) for "adam" optimizer
137-
n_inner_iter_no_change : int, optional, default=5
138-
the tolerance of non-improving inner iteration for adam optimizer
139-
batch_size : int, optional, default=100
140-
the batch_size for adam optimizer
141-
learning_rate : float, optional, default=1e-4
142-
the learning rate for adam optimizer
143-
beta_1 : float, optional, default=0.9
144-
the beta_1 parameter for adam optimizer
145-
beta_2 : float, optional, default=0.999
146-
the beta_1 parameter for adam optimizer
147-
stratify : bool, optional, default=True
148-
whether to stratify the target variable when splitting the validation set
149-
verbose : bool, optional, default=False
150-
whether to show the training history
151-
"""
152-
153-
x, y = self._validate_input(x, y)
154-
n_samples = x.shape[0]
155-
if is_regressor(self):
156-
idx1, idx2 = train_test_split(np.arange(n_samples), test_size=val_ratio,
157-
random_state=self.random_state)
158-
tr_x, tr_y, val_x, val_y = x[idx1], y[idx1], x[idx2], y[idx2]
159-
elif is_classifier(self):
160-
if stratify:
161-
idx1, idx2 = train_test_split(np.arange(n_samples),test_size=val_ratio, stratify=y, random_state=self.random_state)
162-
else:
163-
idx1, idx2 = train_test_split(np.arange(n_samples),test_size=val_ratio, random_state=self.random_state)
164-
tr_x, tr_y, val_x, val_y = x[idx1], y[idx1], x[idx2], y[idx2]
165-
166-
batch_size = min(batch_size, tr_x.shape[0])
167-
val_xb = np.dot(val_x, self.beta_)
168-
if is_regressor(self):
169-
val_pred = self.shape_fit_.predict(val_xb)
170-
val_loss = self.shape_fit_.get_loss(val_y, val_pred)
171-
elif is_classifier(self):
172-
val_pred = self.shape_fit_.predict_proba(val_xb)[:, 1]
173-
val_loss = self.shape_fit_.get_loss(val_y, val_pred)
174-
175-
self_copy = deepcopy(self)
176-
no_middle_iter_change = 0
177-
val_loss_middle_iter_best = val_loss
178-
for middle_iter in range(max_middle_iter):
179-
180-
m_t = 0 # moving average of the gradient
181-
v_t = 0 # moving average of the gradient square
182-
num_updates = 0
183-
no_inner_iter_change = 0
184-
theta_0 = self_copy.beta_
185-
train_size = tr_x.shape[0]
186-
val_loss_inner_iter_best = np.inf
187-
for inner_iter in range(max_inner_iter):
188-
189-
shuffle_index = np.arange(tr_x.shape[0])
190-
np.random.shuffle(shuffle_index)
191-
tr_x = tr_x[shuffle_index]
192-
tr_y = tr_y[shuffle_index]
193-
194-
for iterations in range(train_size // batch_size):
195-
196-
num_updates += 1
197-
offset = (iterations * batch_size) % train_size
198-
batch_xx = tr_x[offset:(offset + batch_size), :]
199-
batch_yy = tr_y[offset:(offset + batch_size)]
200-
201-
xb = np.dot(batch_xx, theta_0)
202-
if is_regressor(self_copy):
203-
r = batch_yy - self_copy.shape_fit_.predict(xb).ravel()
204-
elif is_classifier(self_copy):
205-
r = batch_yy - self_copy.shape_fit_.predict_proba(xb)[:, 1]
206-
207-
# gradient
208-
dfxb = self_copy.shape_fit_.diff(xb, order=1).ravel()
209-
g_t = np.average((- dfxb * r).reshape(-1, 1) * batch_xx, axis=0).reshape(-1, 1)
210-
211-
# update the moving average
212-
m_t = beta_1 * m_t + (1 - beta_1) * g_t
213-
v_t = beta_2 * v_t + (1 - beta_2) * (g_t * g_t)
214-
# calculates the bias-corrected estimates
215-
m_cap = m_t / (1 - (beta_1 ** (num_updates)))
216-
v_cap = v_t / (1 - (beta_2 ** (num_updates)))
217-
# updates the parameters
218-
theta_0 = theta_0 - (learning_rate * m_cap) / (np.sqrt(v_cap) + 1e-8)
219-
220-
# validation loss
221-
val_xb = np.dot(val_x, theta_0)
222-
if is_regressor(self_copy):
223-
val_pred = self_copy.shape_fit_.predict(val_xb)
224-
val_loss = self_copy.shape_fit_.get_loss(val_y, val_pred)
225-
elif is_classifier(self_copy):
226-
val_pred = self_copy.shape_fit_.predict_proba(val_xb)[:, 1]
227-
val_loss = self_copy.shape_fit_.get_loss(val_y, val_pred)
228-
if verbose:
229-
print("Middle iter:", middle_iter + 1, "Inner iter:", inner_iter + 1, "with validation loss:", np.round(val_loss, 5))
230-
# stop criterion
231-
if val_loss > val_loss_inner_iter_best - tol:
232-
no_inner_iter_change += 1
233-
else:
234-
no_inner_iter_change = 0
235-
if val_loss < val_loss_inner_iter_best:
236-
val_loss_inner_iter_best = val_loss
237-
238-
if no_inner_iter_change >= n_inner_iter_no_change:
239-
break
240-
241-
## normalization
242-
if np.linalg.norm(theta_0) > 0:
243-
theta_0 = theta_0 / np.linalg.norm(theta_0)
244-
if (theta_0[np.argmax(np.abs(theta_0))] < 0):
245-
theta_0 = - theta_0
246-
247-
# ridge update
248-
self_copy.beta_ = theta_0
249-
tr_xb = np.dot(tr_x, self_copy.beta_)
250-
self_copy._estimate_shape(tr_xb, tr_y, np.min(tr_xb), np.max(tr_xb))
251-
252-
val_xb = np.dot(val_x, self_copy.beta_)
253-
if is_regressor(self_copy):
254-
val_pred = self_copy.shape_fit_.predict(val_xb)
255-
val_loss = self_copy.shape_fit_.get_loss(val_y, val_pred)
256-
elif is_classifier(self_copy):
257-
val_pred = self_copy.shape_fit_.predict_proba(val_xb)[:, 1]
258-
val_loss = self_copy.shape_fit_.get_loss(val_y, val_pred)
259-
260-
if val_loss > val_loss_middle_iter_best - tol:
261-
no_middle_iter_change += 1
262-
else:
263-
no_middle_iter_change = 0
264-
if val_loss < val_loss_middle_iter_best:
265-
self.beta_ = self_copy.beta_
266-
self.shape_fit_ = self_copy.shape_fit_
267-
val_loss_middle_iter_best = val_loss
268-
if no_middle_iter_change >= n_middle_iter_no_change:
269-
break
270-
271-
self = deepcopy(self_copy)
272-
273115
def visualize(self):
274116

275117
"""draw the fitted projection indices and ridge function

simtree/simtree.py

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class SIMTree(metaclass=ABCMeta):
2828

2929
def __init__(self, max_depth=2, min_samples_leaf=50, min_impurity_decrease=0, feature_names=None,
3030
split_features=None, n_screen_grid=5, n_feature_search=10, n_split_grid=20,
31-
degree=3, knot_num=30, reg_lambda=0, reg_gamma=1e-5, leaf_update=False, random_state=0):
31+
degree=3, knot_num=30, reg_lambda=0, reg_gamma=1e-5, random_state=0):
3232

3333
super(SIMTree, self).__init__(max_depth=max_depth,
3434
min_samples_leaf=min_samples_leaf,
@@ -43,7 +43,6 @@ def __init__(self, max_depth=2, min_samples_leaf=50, min_impurity_decrease=0, fe
4343
self.knot_num = knot_num
4444
self.reg_gamma = reg_gamma
4545
self.reg_lambda = reg_lambda
46-
self.leaf_update = leaf_update
4746

4847
def _validate_hyperparameters(self):
4948

@@ -286,7 +285,7 @@ class SIMTreeRegressor(SIMTree, MoBTreeRegressor, RegressorMixin):
286285

287286
def __init__(self, max_depth=2, min_samples_leaf=50, min_impurity_decrease=0, feature_names=None,
288287
split_features=None, n_screen_grid=5, n_feature_search=10, n_split_grid=20,
289-
degree=3, knot_num=30, reg_lambda=0, reg_gamma=1e-5, leaf_update=False, random_state=0):
288+
degree=3, knot_num=30, reg_lambda=0, reg_gamma=1e-5, random_state=0):
290289

291290
super(SIMTreeRegressor, self).__init__(max_depth=max_depth,
292291
min_samples_leaf=min_samples_leaf,
@@ -300,7 +299,6 @@ def __init__(self, max_depth=2, min_samples_leaf=50, min_impurity_decrease=0, fe
300299
knot_num=knot_num,
301300
reg_lambda=reg_lambda,
302301
reg_gamma=reg_gamma,
303-
leaf_update=leaf_update,
304302
random_state=random_state)
305303

306304
self.base_estimator = SimRegressor(reg_lambda=0, reg_gamma=1e-5, degree=self.degree,
@@ -321,11 +319,6 @@ def build_leaf(self, sample_indice):
321319
cv=5, refit="mse", n_jobs=1, error_score=np.nan)
322320
grid.fit(self.x[sample_indice], self.y[sample_indice].ravel())
323321
best_estimator = grid.best_estimator_
324-
if self.leaf_update:
325-
best_estimator.fit_middle_update_adam(self.x[sample_indice], self.y[sample_indice].ravel(),
326-
max_middle_iter=1000, n_middle_iter_no_change=10,
327-
max_inner_iter=1000, n_inner_iter_no_change=10,
328-
batch_size=min(int(0.2 * len(sample_indice)), 100))
329322
predict_func = lambda x: best_estimator.predict(x)
330323
best_impurity = self.get_loss(self.y[sample_indice], best_estimator.predict(self.x[sample_indice]))
331324
return predict_func, best_estimator, best_impurity
@@ -335,7 +328,7 @@ class SIMTreeClassifier(SIMTree, MoBTreeClassifier, ClassifierMixin):
335328

336329
def __init__(self, max_depth=2, min_samples_leaf=50, min_impurity_decrease=0, feature_names=None,
337330
split_features=None, n_screen_grid=5, n_feature_search=10, n_split_grid=20,
338-
degree=3, knot_num=30, reg_lambda=0, reg_gamma=1e-5, leaf_update=False, random_state=0):
331+
degree=3, knot_num=30, reg_lambda=0, reg_gamma=1e-5, random_state=0):
339332

340333
super(SIMTreeClassifier, self).__init__(max_depth=max_depth,
341334
min_samples_leaf=min_samples_leaf,
@@ -349,7 +342,6 @@ def __init__(self, max_depth=2, min_samples_leaf=50, min_impurity_decrease=0, fe
349342
knot_num=knot_num,
350343
reg_lambda=reg_lambda,
351344
reg_gamma=reg_gamma,
352-
leaf_update=leaf_update,
353345
random_state=random_state)
354346

355347
self.base_estimator = SimClassifier(reg_lambda=0, reg_gamma=1e-5, degree=self.degree,
@@ -375,11 +367,6 @@ def build_leaf(self, sample_indice):
375367
cv=5, refit="auc", n_jobs=1, error_score=np.nan)
376368
grid.fit(self.x[sample_indice], self.y[sample_indice].ravel())
377369
best_estimator = grid.best_estimator_
378-
if self.leaf_update:
379-
best_estimator.fit_middle_update_adam(self.x[sample_indice], self.y[sample_indice].ravel(),
380-
max_middle_iter=100, n_middle_iter_no_change=5,
381-
max_inner_iter=100, n_inner_iter_no_change=5,
382-
batch_size=min(int(0.2 * len(sample_indice)), 100))
383370
predict_func = lambda x: best_estimator.predict_proba(x)[:, 1]
384371
best_impurity = self.get_loss(self.y[sample_indice], best_estimator.predict_proba(self.x[sample_indice])[:, 1])
385372
return predict_func, best_estimator, best_impurity

simtree/smspline.py

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def _estimate_density(self, x):
5454

5555
self.density_, self.bins_ = np.histogram(x, bins=10, density=True)
5656

57-
def diff(self, x, order=1):
57+
def diff(self, x, order=1, delta=1e-5):
5858

5959
"""method to calculate derivatives of the fitted adaptive spline to the input
6060
@@ -63,25 +63,23 @@ def diff(self, x, order=1):
6363
x : array-like of shape (n_samples, 1)
6464
containing the input dataset
6565
order : int
66-
order of derivative
66+
order of derivative, not larger than 2
67+
delta : float
68+
the small value used for calculating finite difference
6769
"""
70+
6871
if order > self.degree:
6972
raise Exception("order should not be greater than degree")
7073
if isinstance(self.sm_, (np.ndarray, np.int, int, np.floating, float)):
7174
derivative = np.zeros((x.shape[0], 1))
72-
elif "modelspec" in self.sm_.names:
73-
modelspec = self.sm_[int(np.where(self.sm_.names == "modelspec")[0][0])]
74-
knots = np.array(modelspec[0])
75-
coefs = np.array(modelspec[11]).reshape(-1, 1)
76-
basis = bigsplines.ssBasis((x - self.xmin) / (self.xmax - self.xmin), knots, m=1 if self.degree==1 else 2, d=order,
77-
xmin=self.xmin, xmax=self.xmax, periodic=False, intercept=True)
78-
derivative = np.dot(basis[0], coefs).ravel()
7975
else:
80-
knots = np.array(self.sm_[12])
81-
coefs = np.array(self.sm_[15]).reshape(-1, 1)
82-
basis = bigsplines.ssBasis((x - self.xmin) / (self.xmax - self.xmin), knots, m=1 if self.degree==1 else 2, d=order,
83-
xmin=0, xmax=1, periodic=False, intercept=True)
84-
derivative = np.dot(basis[0], coefs).ravel()
76+
if order == 1:
77+
derivative = (self.decision_function(x + delta / 2) - self.decision_function(x - delta / 2)) / delta
78+
elif order == 2:
79+
derivative = (self.decision_function(x + delta) + self.decision_function(x - delta)
80+
- 2 * self.decision_function(x)) / delta ** 2
81+
else:
82+
raise Exception("higher order derivatives is not supported now.")
8583
return derivative
8684

8785
def visualize(self):

0 commit comments

Comments
 (0)