Skip to content

Commit

Permalink
[5/N] Modernize FFX: approach namedtuple (#42)
Browse files Browse the repository at this point in the history
  • Loading branch information
natekupp committed Dec 19, 2019
1 parent 1c7f07f commit 543754d
Show file tree
Hide file tree
Showing 8 changed files with 56 additions and 50 deletions.
6 changes: 3 additions & 3 deletions ffx/api.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""api.py defines user interfaces to FFX. run() runs the complete method.
'''api.py defines user interfaces to FFX. run() runs the complete method.
FFXRegressor is a Scikit-learn style regressor.
"""
'''

from sklearn.base import BaseEstimator, RegressorMixin

Expand All @@ -12,7 +12,7 @@ def run(train_X, train_y, test_X, test_y, varnames=None, verbose=False):


class FFXRegressor(BaseEstimator, RegressorMixin):
"""This class provides a Scikit-learn style estimator."""
'''This class provides a Scikit-learn style estimator.'''

def fit(self, X, y):
# if X is a Pandas DataFrame, we don't have to pass in varnames.
Expand Down
59 changes: 13 additions & 46 deletions ffx/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from sklearn.base import RegressorMixin
from sklearn.linear_model import ElasticNet

from .approach import Approach
from .constants import (
CONSIDER_DENOM,
CONSIDER_EXPON,
Expand All @@ -27,19 +28,6 @@
OP_MIN0,
)


def _approachStr(approach):
assert len(approach) == 5
assert set(approach).issubset([0, 1])
return 'inter%d denom%d expon%d nonlin%d thresh%d' % (
approach[0],
approach[1],
approach[2],
approach[3],
approach[4],
)


# =========================================================================
# strategy

Expand All @@ -52,11 +40,9 @@ def __init__(self, approach):
@arguments
approach -- 5-d list of [use_inter, use_denom, use_expon, use_nonlin, use_thresh]
"""
assert len(approach) == 5
assert set(approach).issubset([0, 1])
self.approach = approach

self.num_alphas = 1000
self._num_alphas = 1000

# final round will stop if either of these is hit
self.final_target_train_nmse = 0.01 # 0.01 = 1%
Expand All @@ -81,28 +67,19 @@ def __init__(self, approach):
self.all_expr_exponents = [-1.0, -0.5, +0.5, +1.0]

def includeInteractions(self):
return bool(self.approach[0])
return bool(self.approach.use_inter)

def includeDenominator(self):
return bool(self.approach[1])
return bool(self.approach.use_denom)

def exprExponents(self):
if self.approach[2]:
return self.all_expr_exponents
else:
return [1.0]
return self.all_expr_exponents if self.approach.use_expon else [1.0]

def nonlinOps(self):
if self.approach[3]:
return self.all_nonlin_ops
else:
return []
return self.all_nonlin_ops if self.approach.use_nonlin else []

def thresholdOps(self):
if self.approach[4]:
return self.all_threshold_ops
else:
return []
return self.all_threshold_ops if self.approach.use_thresh else []

def eps(self):
return self._eps
Expand All @@ -111,7 +88,7 @@ def l1_ratio(self):
return self._l1_ratio

def numAlphas(self):
return self.num_alphas
return self._num_alphas


# =========================================================================
Expand Down Expand Up @@ -506,20 +483,19 @@ def build(self, train_X, train_y, test_X, test_y, varnames=None, verbose=False):
for thresh in [0, 1]:
if thresh == 1 and not CONSIDER_THRESH:
continue
approach = [inter, denom, expon, nonlin, thresh]
if sum(approach) >= 4:
approach = Approach(inter, denom, expon, nonlin, thresh)
if approach.num_feature_types >= 4:
continue # not too many features at once
approaches.append(approach)
if verbose:
# " ", _approachStr(approach)
print('\t'.join(['Yes' if a else 'No' for a in approach]))
print(approach)

for (i, approach) in enumerate(approaches):
if verbose:
print('-' * 200)
print(
'Build with approach %d/%d (%s): begin'
% (i + 1, len(approaches), _approachStr(approach))
% (i + 1, len(approaches), str(approach))
)
ss = FFXBuildStrategy(approach)

Expand All @@ -540,7 +516,7 @@ def build(self, train_X, train_y, test_X, test_y, varnames=None, verbose=False):
if verbose:
print(
'Build with approach %d/%d (%s): done. %d model(s).'
% (i + 1, len(approaches), _approachStr(approach), len(next_models))
% (i + 1, len(approaches), str(approach), len(next_models))
)
print('Models:')
for model in next_models:
Expand All @@ -564,15 +540,6 @@ def build(self, train_X, train_y, test_X, test_y, varnames=None, verbose=False):

return models

def _FFXapproach(self, inter, denom, expon, nonlin, thresh):
return 'FFX inter%d denom%d expon%d nonlin%d thresh%d' % (
inter,
denom,
expon,
nonlin,
thresh,
)

def _nondominatedModels(self, models):
test_nmses = [model.test_nmse for model in models]
num_bases = [model.numBases() for model in models]
Expand Down
24 changes: 24 additions & 0 deletions ffx/core/approach.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from collections import namedtuple


class Approach(namedtuple('_Approach', 'use_inter use_denom use_expon use_nonlin use_thresh')):
def __new__(cls, use_inter, use_denom, use_expon, use_nonlin, use_thresh):
assert set([use_inter, use_denom, use_expon, use_nonlin, use_thresh]).issubset([0, 1])
return super(Approach, cls).__new__(
cls, use_inter, use_denom, use_expon, use_nonlin, use_thresh
)

@property
def num_feature_types(self):
'''How many types of features does this approach consider?
'''
return sum(self._asdict().values())

def __repr__(self):
return 'inter%d denom%d expon%d nonlin%d thresh%d' % (
self.use_inter,
self.use_denom,
self.use_expon,
self.use_nonlin,
self.use_thresh,
)
1 change: 0 additions & 1 deletion ffx/core/constants.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# user-changeable constants
CONSIDER_INTER = True # consider interactions?
CONSIDER_DENOM = True # consider denominator?
CONSIDER_EXPON = True # consider exponents?
Expand Down
Empty file added ffx_tests/__init__.py
Empty file.
Empty file.
16 changes: 16 additions & 0 deletions ffx_tests/core_tests/test_approach.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import pytest

from ffx.core.approach import Approach


def test_approach():
a = Approach(use_inter=0, use_denom=0, use_expon=0, use_nonlin=0, use_thresh=0)
assert str(a) == 'inter0 denom0 expon0 nonlin0 thresh0'
assert a.num_feature_types == 0

b = Approach(use_inter=1, use_denom=1, use_expon=1, use_nonlin=1, use_thresh=1)
assert str(b) == 'inter1 denom1 expon1 nonlin1 thresh1'
assert b.num_feature_types == 5

with pytest.raises(AssertionError):
Approach(use_inter='not an integer', use_denom=1, use_expon=1, use_nonlin=1, use_thresh=1)
File renamed without changes.

0 comments on commit 543754d

Please sign in to comment.