Skip to content
This repository was archived by the owner on Nov 16, 2023. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
8bba81a
Add calculate_feature_contrinutions method to Pipeline
najeeb-kazmi Jul 12, 2019
788e089
Merge branch 'master' into 91
ganik Jul 12, 2019
41674b6
Merge branch 'master' into 91
ganik Jul 12, 2019
8fa9192
Merge branch 'master' into 91
ganik Jul 13, 2019
ff15cf5
typo
najeeb-kazmi Jul 15, 2019
6639669
rename to get_feature_contributions(), add docs, improve sample
najeeb-kazmi Jul 15, 2019
28dad24
Add list of supported models to sample
najeeb-kazmi Jul 15, 2019
41cfb2c
Some PR feedback
najeeb-kazmi Jul 15, 2019
a0d4f70
Fix feature contributions for regression and ranking
najeeb-kazmi Jul 16, 2019
818c36e
Implement feature contributions in BasePredictor
najeeb-kazmi Jul 16, 2019
5a80929
Add test to check feature contributions with unpickled pipeline
najeeb-kazmi Jul 17, 2019
6337475
Improve doc, simplify entrypoint graph for feature contributions
najeeb-kazmi Jul 17, 2019
11c388e
Add test to check feature contributions with pipeline loaded from zip
najeeb-kazmi Jul 18, 2019
ea3dce4
nit
najeeb-kazmi Jul 18, 2019
93a46fa
PR feedback
najeeb-kazmi Jul 18, 2019
c0500d1
Save the model file when pickling a NimbusML Pipeline. (#189)
pieths Jul 18, 2019
266d27d
Remove stored references to X and y in BasePredictor. (#195)
pieths Jul 18, 2019
d20c398
Add calculate_feature_contrinutions method to Pipeline
najeeb-kazmi Jul 12, 2019
e21f91d
typo
najeeb-kazmi Jul 15, 2019
df3bdc7
rename to get_feature_contributions(), add docs, improve sample
najeeb-kazmi Jul 15, 2019
a09d6ef
Add list of supported models to sample
najeeb-kazmi Jul 15, 2019
c8c851d
Some PR feedback
najeeb-kazmi Jul 15, 2019
80f4655
Fix feature contributions for regression and ranking
najeeb-kazmi Jul 16, 2019
0c208dc
Implement feature contributions in BasePredictor
najeeb-kazmi Jul 16, 2019
042566f
Add test to check feature contributions with unpickled pipeline
najeeb-kazmi Jul 17, 2019
9bd8b4e
Improve doc, simplify entrypoint graph for feature contributions
najeeb-kazmi Jul 17, 2019
b142ab1
Add test to check feature contributions with pipeline loaded from zip
najeeb-kazmi Jul 18, 2019
af7c37c
nit
najeeb-kazmi Jul 18, 2019
e1f12f4
PR feedback
najeeb-kazmi Jul 18, 2019
1a181bd
Adding more tests, implement save_model in BasePipelineItem, and PR f…
najeeb-kazmi Jul 18, 2019
e621047
Fix conflicts
najeeb-kazmi Jul 18, 2019
8546a19
one more conflict
najeeb-kazmi Jul 18, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/python/nimbusml.pyproj
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@
<Compile Include="nimbusml\examples\PipelineWithGridSearchCV2.py" />
<Compile Include="nimbusml\examples\PipelineWithGridSearchCV1.py" />
<Compile Include="nimbusml\examples\pipeline.py" />
<Compile Include="nimbusml\examples\PipelineWithFeatureCOntributions.py" />
<Compile Include="nimbusml\examples\Poisson.py" />
<Compile Include="nimbusml\examples\PoissonRegressionRegressor.py" />
<Compile Include="nimbusml\examples\RangeFilter.py" />
Expand Down
37 changes: 37 additions & 0 deletions src/python/nimbusml/examples/PipelineWithFeatureContributions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
###############################################################################
# Pipeline with feature contributions
from nimbusml import Pipeline, FileDataStream
from nimbusml.datasets import get_dataset
from nimbusml.linear_model import LogisticRegressionBinaryClassifier

# data input (as a FileDataStream)
path = get_dataset('uciadult_train').as_filepath()

data = FileDataStream.read_csv(path)
print(data.head())
# label workclass education ... capital-loss hours-per-week
# 0 0 Private 11th ... 0 40
# 1 0 Private HS-grad ... 0 50
# 2 1 Local-gov Assoc-acdm ... 0 40
# 3 1 Private Some-college ... 0 40
# 4 0 ? Some-college ... 0 30
# define the training pipeline
pipeline = Pipeline([LogisticRegressionBinaryClassifier(
feature=['age', 'education-num', 'hours-per-week'], label='label')])

# train, predict, and evaluate
# TODO: Replace with CV
model = pipeline.fit(data)

feature_contributions = model.calculate_feature_contributions(
data, output_scores=True)

# Print predictions with feature contributions, which give a relative measure
# of how much each feature impacted the Score.
print(feature_contributions.head())
# label ... PredictedLabel Score ... FeatureContributions.hours-per-week
# 0 ... 0 -2.010687 ... 0.833069
# 1 ... 0 -1.216163 ... 0.809928
# 2 ... 0 -1.248412 ... 0.485957
# 3 ... 0 -1.132419 ... 0.583148
# 4 ... 0 -1.969522 ... 0.437361
118 changes: 118 additions & 0 deletions src/python/nimbusml/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
transforms_datasetscorer
from .internal.entrypoints.transforms_featurecombiner import \
transforms_featurecombiner
from .internal.entrypoints.transforms_featurecontributioncalculationtransformer import \
transforms_featurecontributioncalculationtransformer
from .internal.entrypoints.transforms_labelcolumnkeybooleanconverter \
import \
transforms_labelcolumnkeybooleanconverter
Expand Down Expand Up @@ -1693,6 +1695,122 @@ def getn(n):
"only fit(X) is allowed or the training becomes "
"ambiguous.")

@trace
def calculate_feature_contributions(self, X, y=None,
evaltype='auto', group_id=None,
Comment thread
najeeb-kazmi marked this conversation as resolved.
Outdated
weight=None,
Comment thread
najeeb-kazmi marked this conversation as resolved.
Outdated
verbose=0,
top=10,
bottom=10,
normalize=True,
as_binary_data_stream=False, **params):
"""
Apply transforms and test with the final estimator, return metrics
"""
# start the clock!
start_time = time.time()
Comment thread
najeeb-kazmi marked this conversation as resolved.
Outdated
self.verbose = verbose

if not self._is_fitted:
Comment thread
najeeb-kazmi marked this conversation as resolved.
raise ValueError(
"Model is not fitted. Train or load a model before test("
").")

if y is not None:
if len(self.steps) > 0:
last_node = self.last_node
if last_node.type == 'transform':
raise ValueError(
"Pipeline needs a trainer as last step for test()")
Comment thread
najeeb-kazmi marked this conversation as resolved.
Outdated

X, y_temp, columns_renamed, feature_columns, label_column, \
schema, weights, weight_column = self._preprocess_X_y(
X, y, w=weight
)

if (not isinstance(y, (str, tuple))) or (
isinstance(X, DataFrame) and isinstance(y, (str, tuple))):
y = y_temp

all_nodes = []
inputs = dict([('data', ''), ('predictor_model', self.model)])
if isinstance(X, FileDataStream):
importtext_node = data_customtextloader(
input_file="$file",
data="$data",
custom_schema=schema.to_string(
add_sep=True))
all_nodes = [importtext_node]
inputs = dict([('file', ''), ('predictor_model', self.model)])

score_node = transforms_datasetscorer(
data="$data",
predictor_model="$predictor_model",
scored_data="$scoredvectordata")

fcc_node = transforms_featurecontributioncalculationtransformer(
data="$scoredvectordata",
predictor_model="$predictor_model",
output_data="$fccData",
top=top,
bottom=bottom,
normalize=normalize)
all_nodes.extend([score_node, fcc_node])

if hasattr(self, 'steps') and len(self.steps) > 0 \
and self.last_node.type == 'classifier':
convert_label_node = \
transforms_predictedlabelcolumnoriginalvalueconverter(
data="$fccData",
predicted_label_column="PredictedLabel",
output_data="$output_data")
all_nodes.extend([convert_label_node])

if y is not None:
evaluate_nodes = self._evaluation_infer(
evaltype, label_column, group_id, **params)
for node in evaluate_nodes:
all_nodes.extend([node])
output_scores = '' if params.get(
'output_scores', False) else '<null>'
outputs = OrderedDict(
[('output_metrics', ''), ('output_data', output_scores)])
Comment thread
najeeb-kazmi marked this conversation as resolved.
Outdated
else:
outputs = dict(output_data="")

graph = Graph(
inputs,
outputs,
as_binary_data_stream,
*all_nodes)

class_name = type(self).__name__
method_name = inspect.currentframe().f_code.co_name
telemetry_info = ".".join([class_name, method_name])

try:
(out_model, out_data, out_metrics) = graph.run(
X=X,
y=y,
random_state=self.random_state,
model=self.model,
verbose=verbose,
telemetry_info=telemetry_info,
**params)
except RuntimeError as e:
self._run_time = time.time() - start_time
raise e

if y is not None:
# We need to fix the schema for ranking metrics
if evaltype == 'ranking':
out_metrics = self._fix_ranking_metrics_schema(out_metrics)
Comment thread
najeeb-kazmi marked this conversation as resolved.
Outdated

# stop the clock
self._run_time = time.time() - start_time
self._write_csv_time = graph._write_csv_time
Comment thread
najeeb-kazmi marked this conversation as resolved.
Outdated
return out_data

@trace
def _predict(self, X, y=None,
evaltype='auto', group_id=None,
Expand Down