mirror of
https://github.com/IBM/ai-privacy-toolkit.git
synced 2026-07-23 17:01:03 +02:00
Minimization fixes (#12)
* Fixes related to corner cases in calculating generalizations * Fix print * Fix corner cases in transform as well * Improve prints + bug fixes in calculation of feature to remove * Notebook demonstrating ai minimization
This commit is contained in:
parent
d2591d7840
commit
43952e2332
2 changed files with 326 additions and 47 deletions
|
|
@ -117,6 +117,10 @@ class GeneralizeToRepresentative(BaseEstimator, MetaEstimatorMixin, TransformerM
|
||||||
self.cells = params['cells']
|
self.cells = params['cells']
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
@property
|
||||||
|
def generalizations(self):
|
||||||
|
return self.generalizations_
|
||||||
|
|
||||||
def fit_transform(self, X=None, y=None):
|
def fit_transform(self, X=None, y=None):
|
||||||
"""Learns the generalizations based on training data, and applies them to the data.
|
"""Learns the generalizations based on training data, and applies them to the data.
|
||||||
|
|
||||||
|
|
@ -206,40 +210,45 @@ class GeneralizeToRepresentative(BaseEstimator, MetaEstimatorMixin, TransformerM
|
||||||
nodes = self._get_nodes_level(0)
|
nodes = self._get_nodes_level(0)
|
||||||
self._attach_cells_representatives(X_train, y_train, nodes)
|
self._attach_cells_representatives(X_train, y_train, nodes)
|
||||||
# self.cells_ currently holds the generalization created from the tree leaves
|
# self.cells_ currently holds the generalization created from the tree leaves
|
||||||
|
self._calculate_generalizations()
|
||||||
|
|
||||||
# apply generalizations to test data
|
# apply generalizations to test data
|
||||||
generalized = self._generalize(X_test, nodes, self.cells_, self.cells_by_id_)
|
generalized = self._generalize(X_test, nodes, self.cells_, self.cells_by_id_)
|
||||||
|
|
||||||
# check accuracy
|
# check accuracy
|
||||||
accuracy = self.estimator.score(generalized, y_test)
|
accuracy = self.estimator.score(generalized, y_test)
|
||||||
print('Initial accuracy is %f' % accuracy)
|
print('Initial accuracy of model on generalized data, relative to original model predictions '
|
||||||
|
'(base generalization derived from tree, before improvements): %f' % accuracy)
|
||||||
|
|
||||||
# if accuracy above threshold, improve generalization
|
# if accuracy above threshold, improve generalization
|
||||||
if accuracy > self.target_accuracy:
|
if accuracy > self.target_accuracy:
|
||||||
|
print('Improving generalizations')
|
||||||
level = 1
|
level = 1
|
||||||
while accuracy > self.target_accuracy:
|
while accuracy > self.target_accuracy:
|
||||||
nodes = self._get_nodes_level(level)
|
nodes = self._get_nodes_level(level)
|
||||||
self._calculate_level_cells(level)
|
self._calculate_level_cells(level)
|
||||||
self._attach_cells_representatives(X_train, y_train, nodes)
|
self._attach_cells_representatives(X_train, y_train, nodes)
|
||||||
|
self._calculate_generalizations()
|
||||||
generalized = self._generalize(X_test, nodes, self.cells_,
|
generalized = self._generalize(X_test, nodes, self.cells_,
|
||||||
self.cells_by_id_)
|
self.cells_by_id_)
|
||||||
accuracy = self.estimator.score(generalized, y_test)
|
accuracy = self.estimator.score(generalized, y_test)
|
||||||
print('Level: %d, accuracy: %f' % (level, accuracy))
|
print('Pruned tree to level: %d, new relative accuracy: %f' % (level, accuracy))
|
||||||
level+=1
|
level+=1
|
||||||
|
|
||||||
# if accuracy below threshold, improve accuracy by removing features from generalization
|
# if accuracy below threshold, improve accuracy by removing features from generalization
|
||||||
if accuracy < self.target_accuracy:
|
if accuracy < self.target_accuracy:
|
||||||
|
print('Improving accuracy')
|
||||||
while accuracy < self.target_accuracy:
|
while accuracy < self.target_accuracy:
|
||||||
self._calculate_generalizations()
|
|
||||||
removed_feature = self._remove_feature_from_generalization(X_test,
|
removed_feature = self._remove_feature_from_generalization(X_test,
|
||||||
nodes, y_test,
|
nodes, y_test,
|
||||||
feature_data)
|
feature_data, accuracy)
|
||||||
if not removed_feature:
|
if removed_feature is None:
|
||||||
break
|
break
|
||||||
generalized = self._generalize(X_test, nodes, self.cells_,
|
|
||||||
self.cells_by_id_)
|
self._calculate_generalizations()
|
||||||
|
generalized = self._generalize(X_test, nodes, self.cells_, self.cells_by_id_)
|
||||||
accuracy = self.estimator.score(generalized, y_test)
|
accuracy = self.estimator.score(generalized, y_test)
|
||||||
print('Removed feature: %s, accuracy: %f' % (removed_feature, accuracy))
|
print('Removed feature: %s, new relative accuracy: %f' % (removed_feature, accuracy))
|
||||||
|
|
||||||
# self.cells_ currently holds the chosen generalization based on target accuracy
|
# self.cells_ currently holds the chosen generalization based on target accuracy
|
||||||
|
|
||||||
|
|
@ -304,12 +313,12 @@ class GeneralizeToRepresentative(BaseEstimator, MetaEstimatorMixin, TransformerM
|
||||||
|
|
||||||
# replace the values in the representative columns with the representative
|
# replace the values in the representative columns with the representative
|
||||||
# values (leaves others untouched)
|
# values (leaves others untouched)
|
||||||
if not representatives.columns.empty:
|
if indexes and not representatives.columns.empty:
|
||||||
if len(indexes) > 1:
|
if len(indexes) > 1:
|
||||||
replace = pd.concat([representatives.loc[i].to_frame().T]*len(indexes)).reset_index(drop=True)
|
replace = pd.concat([representatives.loc[i].to_frame().T]*len(indexes)).reset_index(drop=True)
|
||||||
replace.index = indexes
|
|
||||||
else:
|
else:
|
||||||
replace = representatives.loc[i].to_frame().T
|
replace = representatives.loc[i].to_frame().T.reset_index(drop=True)
|
||||||
|
replace.index = indexes
|
||||||
generalized.loc[indexes, representatives.columns] = replace
|
generalized.loc[indexes, representatives.columns] = replace
|
||||||
|
|
||||||
return generalized.to_numpy()
|
return generalized.to_numpy()
|
||||||
|
|
@ -409,30 +418,31 @@ class GeneralizeToRepresentative(BaseEstimator, MetaEstimatorMixin, TransformerM
|
||||||
new_cells = []
|
new_cells = []
|
||||||
new_cells_by_id = {}
|
new_cells_by_id = {}
|
||||||
nodes = self._get_nodes_level(level)
|
nodes = self._get_nodes_level(level)
|
||||||
for node in nodes:
|
if nodes:
|
||||||
if self.dt_.tree_.feature[node] == -2: # leaf node
|
for node in nodes:
|
||||||
new_cell = self.cells_by_id_[node]
|
if self.dt_.tree_.feature[node] == -2: # leaf node
|
||||||
else:
|
new_cell = self.cells_by_id_[node]
|
||||||
left_child = self.dt_.tree_.children_left[node]
|
else:
|
||||||
right_child = self.dt_.tree_.children_right[node]
|
left_child = self.dt_.tree_.children_left[node]
|
||||||
left_cell = self.cells_by_id_[left_child]
|
right_child = self.dt_.tree_.children_right[node]
|
||||||
right_cell = self.cells_by_id_[right_child]
|
left_cell = self.cells_by_id_[left_child]
|
||||||
new_cell = {'id': int(node), 'ranges': {}, 'categories': {},
|
right_cell = self.cells_by_id_[right_child]
|
||||||
'label': None, 'representative': None}
|
new_cell = {'id': int(node), 'ranges': {}, 'categories': {},
|
||||||
for feature in left_cell['ranges'].keys():
|
'label': None, 'representative': None}
|
||||||
new_cell['ranges'][feature] = {}
|
for feature in left_cell['ranges'].keys():
|
||||||
new_cell['ranges'][feature]['start'] = left_cell['ranges'][feature]['start']
|
new_cell['ranges'][feature] = {}
|
||||||
new_cell['ranges'][feature]['end'] = right_cell['ranges'][feature]['start']
|
new_cell['ranges'][feature]['start'] = left_cell['ranges'][feature]['start']
|
||||||
for feature in left_cell['categories'].keys():
|
new_cell['ranges'][feature]['end'] = right_cell['ranges'][feature]['start']
|
||||||
new_cell['categories'][feature] = \
|
for feature in left_cell['categories'].keys():
|
||||||
list(set(left_cell['categories'][feature]) |
|
new_cell['categories'][feature] = \
|
||||||
set(right_cell['categories'][feature]))
|
list(set(left_cell['categories'][feature]) |
|
||||||
self._calculate_level_cell_label(left_cell, right_cell, new_cell)
|
set(right_cell['categories'][feature]))
|
||||||
new_cells.append(new_cell)
|
self._calculate_level_cell_label(left_cell, right_cell, new_cell)
|
||||||
new_cells_by_id[new_cell['id']] = new_cell
|
new_cells.append(new_cell)
|
||||||
self.cells_ = new_cells
|
new_cells_by_id[new_cell['id']] = new_cell
|
||||||
self.cells_by_id_ = new_cells_by_id
|
self.cells_ = new_cells
|
||||||
# else: nothing to do, stay with previous cells
|
self.cells_by_id_ = new_cells_by_id
|
||||||
|
# else: nothing to do, stay with previous cells
|
||||||
|
|
||||||
def _calculate_level_cell_label(self, left_cell, right_cell, new_cell):
|
def _calculate_level_cell_label(self, left_cell, right_cell, new_cell):
|
||||||
new_cell['hist'] = [x + y for x, y in zip(left_cell['hist'], right_cell['hist'])]
|
new_cell['hist'] = [x + y for x, y in zip(left_cell['hist'], right_cell['hist'])]
|
||||||
|
|
@ -445,6 +455,7 @@ class GeneralizeToRepresentative(BaseEstimator, MetaEstimatorMixin, TransformerM
|
||||||
stack = [(0, -1)] # seed is the root node id and its parent depth
|
stack = [(0, -1)] # seed is the root node id and its parent depth
|
||||||
while len(stack) > 0:
|
while len(stack) > 0:
|
||||||
node_id, parent_depth = stack.pop()
|
node_id, parent_depth = stack.pop()
|
||||||
|
# depth = distance from root
|
||||||
node_depth[node_id] = parent_depth + 1
|
node_depth[node_id] = parent_depth + 1
|
||||||
|
|
||||||
if self.dt_.tree_.children_left[node_id] != self.dt_.tree_.children_right[node_id]:
|
if self.dt_.tree_.children_left[node_id] != self.dt_.tree_.children_right[node_id]:
|
||||||
|
|
@ -453,10 +464,14 @@ class GeneralizeToRepresentative(BaseEstimator, MetaEstimatorMixin, TransformerM
|
||||||
else:
|
else:
|
||||||
is_leaves[node_id] = True
|
is_leaves[node_id] = True
|
||||||
|
|
||||||
|
# depth of entire tree
|
||||||
max_depth = max(node_depth)
|
max_depth = max(node_depth)
|
||||||
|
# depth of current level
|
||||||
depth = max_depth - level
|
depth = max_depth - level
|
||||||
|
# level is higher than root
|
||||||
if depth < 0:
|
if depth < 0:
|
||||||
return None
|
return None
|
||||||
|
# return all nodes with depth == level or leaves higher than level
|
||||||
return [i for i, x in enumerate(node_depth) if x == depth or (x < depth and is_leaves[i])]
|
return [i for i, x in enumerate(node_depth) if x == depth or (x < depth and is_leaves[i])]
|
||||||
|
|
||||||
def _attach_cells_representatives(self, samples, labels, level_nodes):
|
def _attach_cells_representatives(self, samples, labels, level_nodes):
|
||||||
|
|
@ -518,12 +533,12 @@ class GeneralizeToRepresentative(BaseEstimator, MetaEstimatorMixin, TransformerM
|
||||||
indexes = [j for j in range(len(mapping_to_cells)) if mapping_to_cells[j]['id'] == cells[i]['id']]
|
indexes = [j for j in range(len(mapping_to_cells)) if mapping_to_cells[j]['id'] == cells[i]['id']]
|
||||||
# replaces the values in the representative columns with the representative values
|
# replaces the values in the representative columns with the representative values
|
||||||
# (leaves others untouched)
|
# (leaves others untouched)
|
||||||
if not representatives.columns.empty:
|
if indexes and not representatives.columns.empty:
|
||||||
if len(indexes) > 1:
|
if len(indexes) > 1:
|
||||||
replace = pd.concat([representatives.loc[i].to_frame().T]*len(indexes)).reset_index(drop=True)
|
replace = pd.concat([representatives.loc[i].to_frame().T]*len(indexes)).reset_index(drop=True)
|
||||||
replace.index = indexes
|
|
||||||
else:
|
else:
|
||||||
replace = representatives.loc[i].to_frame().T
|
replace = representatives.loc[i].to_frame().T.reset_index(drop=True)
|
||||||
|
replace.index = indexes
|
||||||
generalized.loc[indexes, representatives.columns] = replace
|
generalized.loc[indexes, representatives.columns] = replace
|
||||||
|
|
||||||
return generalized.to_numpy()
|
return generalized.to_numpy()
|
||||||
|
|
@ -539,14 +554,16 @@ class GeneralizeToRepresentative(BaseEstimator, MetaEstimatorMixin, TransformerM
|
||||||
node_ids = self._find_sample_nodes(samples, nodes)
|
node_ids = self._find_sample_nodes(samples, nodes)
|
||||||
return [cells_by_id[nodeId] for nodeId in node_ids]
|
return [cells_by_id[nodeId] for nodeId in node_ids]
|
||||||
|
|
||||||
def _remove_feature_from_generalization(self, samples, nodes, labels, feature_data):
|
def _remove_feature_from_generalization(self, samples, nodes, labels, feature_data, current_accuracy):
|
||||||
feature = self._get_feature_to_remove(samples, nodes, labels, feature_data)
|
feature = self._get_feature_to_remove(samples, nodes, labels, feature_data, current_accuracy)
|
||||||
if not feature:
|
if feature is None:
|
||||||
return None
|
return None
|
||||||
GeneralizeToRepresentative._remove_feature_from_cells(self.cells_, self.cells_by_id_, feature)
|
GeneralizeToRepresentative._remove_feature_from_cells(self.cells_, self.cells_by_id_, feature)
|
||||||
|
# del self.generalizations_['ranges'][feature]
|
||||||
|
# self.generalizations_['untouched'].append(feature)
|
||||||
return feature
|
return feature
|
||||||
|
|
||||||
def _get_feature_to_remove(self, samples, nodes, labels, feature_data):
|
def _get_feature_to_remove(self, samples, nodes, labels, feature_data, current_accuracy):
|
||||||
# We want to remove features with low iLoss (NCP) and high accuracy gain
|
# We want to remove features with low iLoss (NCP) and high accuracy gain
|
||||||
# (after removing them)
|
# (after removing them)
|
||||||
ranges = self.generalizations_['ranges']
|
ranges = self.generalizations_['ranges']
|
||||||
|
|
@ -567,13 +584,17 @@ class GeneralizeToRepresentative(BaseEstimator, MetaEstimatorMixin, TransformerM
|
||||||
cells_by_id = copy.deepcopy(self.cells_by_id_)
|
cells_by_id = copy.deepcopy(self.cells_by_id_)
|
||||||
GeneralizeToRepresentative._remove_feature_from_cells(new_cells, cells_by_id, feature)
|
GeneralizeToRepresentative._remove_feature_from_cells(new_cells, cells_by_id, feature)
|
||||||
generalized = self._generalize(samples, nodes, new_cells, cells_by_id)
|
generalized = self._generalize(samples, nodes, new_cells, cells_by_id)
|
||||||
accuracy = self.estimator.score(generalized, labels)
|
accuracy_gain = self.estimator.score(generalized, labels) - current_accuracy
|
||||||
feature_ncp = feature_ncp / accuracy
|
if accuracy_gain < 0:
|
||||||
|
accuracy_gain = 0
|
||||||
|
if accuracy_gain != 0:
|
||||||
|
feature_ncp = feature_ncp / accuracy_gain
|
||||||
|
|
||||||
if feature_ncp < range_min:
|
if feature_ncp < range_min:
|
||||||
range_min = feature_ncp
|
range_min = feature_ncp
|
||||||
remove_feature = feature
|
remove_feature = feature
|
||||||
|
|
||||||
print('feature to remove: ' + (remove_feature if remove_feature else ''))
|
print('feature to remove: ' + (str(remove_feature) if remove_feature is not None else 'none'))
|
||||||
return remove_feature
|
return remove_feature
|
||||||
|
|
||||||
def _calculate_generalizations(self):
|
def _calculate_generalizations(self):
|
||||||
|
|
@ -660,5 +681,3 @@ class GeneralizeToRepresentative(BaseEstimator, MetaEstimatorMixin, TransformerM
|
||||||
del cell['categories'][feature]
|
del cell['categories'][feature]
|
||||||
cell['untouched'].append(feature)
|
cell['untouched'].append(feature)
|
||||||
cells_by_id[cell['id']] = cell.copy()
|
cells_by_id[cell['id']] = cell.copy()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
260
notebooks/minimization_adult.ipynb
Normal file
260
notebooks/minimization_adult.ipynb
Normal file
|
|
@ -0,0 +1,260 @@
|
||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# Applying data minimization to a trained ML model"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"In this tutorial we will show how to perform data minimization for ML models using the minimization module. \n",
|
||||||
|
"\n",
|
||||||
|
"This will be demonstarted using the Adult dataset (original dataset can be found here: https://archive.ics.uci.edu/ml/datasets/adult). \n",
|
||||||
|
"\n",
|
||||||
|
"We use only the numerical features in the dataset because this is what is currently supported by the module."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## Load data"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 1,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"[[3.9000e+01 1.3000e+01 2.1740e+03 0.0000e+00 4.0000e+01]\n",
|
||||||
|
" [5.0000e+01 1.3000e+01 0.0000e+00 0.0000e+00 1.3000e+01]\n",
|
||||||
|
" [3.8000e+01 9.0000e+00 0.0000e+00 0.0000e+00 4.0000e+01]\n",
|
||||||
|
" ...\n",
|
||||||
|
" [5.8000e+01 9.0000e+00 0.0000e+00 0.0000e+00 4.0000e+01]\n",
|
||||||
|
" [2.2000e+01 9.0000e+00 0.0000e+00 0.0000e+00 2.0000e+01]\n",
|
||||||
|
" [5.2000e+01 9.0000e+00 1.5024e+04 0.0000e+00 4.0000e+01]]\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"import numpy as np\n",
|
||||||
|
"\n",
|
||||||
|
"# Use only numeric features (age, education-num, capital-gain, capital-loss, hours-per-week)\n",
|
||||||
|
"x_train = np.loadtxt(\"https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.data\",\n",
|
||||||
|
" usecols=(0, 4, 10, 11, 12), delimiter=\", \")\n",
|
||||||
|
"\n",
|
||||||
|
"y_train = np.loadtxt(\"https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.data\",\n",
|
||||||
|
" usecols=14, dtype=str, delimiter=\", \")\n",
|
||||||
|
"\n",
|
||||||
|
"\n",
|
||||||
|
"x_test = np.loadtxt(\"https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.test\",\n",
|
||||||
|
" usecols=(0, 4, 10, 11, 12), delimiter=\", \", skiprows=1)\n",
|
||||||
|
"\n",
|
||||||
|
"y_test = np.loadtxt(\"https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.test\",\n",
|
||||||
|
" usecols=14, dtype=str, delimiter=\", \", skiprows=1)\n",
|
||||||
|
"\n",
|
||||||
|
"# Trim trailing period \".\" from label\n",
|
||||||
|
"y_test = np.array([a[:-1] for a in y_test])\n",
|
||||||
|
"\n",
|
||||||
|
"y_train[y_train == '<=50K'] = 0\n",
|
||||||
|
"y_train[y_train == '>50K'] = 1\n",
|
||||||
|
"y_train = y_train.astype(np.int)\n",
|
||||||
|
"\n",
|
||||||
|
"y_test[y_test == '<=50K'] = 0\n",
|
||||||
|
"y_test[y_test == '>50K'] = 1\n",
|
||||||
|
"y_test = y_test.astype(np.int)\n",
|
||||||
|
"\n",
|
||||||
|
"print(x_train)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## Train decision tree model"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 2,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"Base model accuracy: 0.8189914624408821\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"from sklearn.tree import DecisionTreeClassifier\n",
|
||||||
|
"\n",
|
||||||
|
"model = DecisionTreeClassifier()\n",
|
||||||
|
"model.fit(x_train, y_train)\n",
|
||||||
|
"\n",
|
||||||
|
"print('Base model accuracy: ', model.score(x_test, y_test))"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## Run minimization\n",
|
||||||
|
"We will try to run minimization with different possible values of target accuracy (how close to the original model's accuracy we want to get, 1 being same accuracy as for original data)."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 3,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"Initial accuracy of model on generalized data, relative to original model predictions (base generalization derived from tree, before improvements): 0.929376\n",
|
||||||
|
"Improving accuracy\n",
|
||||||
|
"feature to remove: 0\n",
|
||||||
|
"Removed feature: 0, new relative accuracy: 0.939867\n",
|
||||||
|
"feature to remove: 4\n",
|
||||||
|
"Removed feature: 4, new relative accuracy: 0.967247\n",
|
||||||
|
"feature to remove: 2\n",
|
||||||
|
"Removed feature: 2, new relative accuracy: 0.972620\n",
|
||||||
|
"feature to remove: 1\n",
|
||||||
|
"Removed feature: 1, new relative accuracy: 0.992323\n",
|
||||||
|
"feature to remove: 3\n",
|
||||||
|
"Removed feature: 3, new relative accuracy: 1.000000\n",
|
||||||
|
"Accuracy on minimized data: 0.8237371411024106\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"import sys\n",
|
||||||
|
"import os\n",
|
||||||
|
"sys.path.insert(0, os.path.abspath('..'))\n",
|
||||||
|
"\n",
|
||||||
|
"from apt.minimization import GeneralizeToRepresentative\n",
|
||||||
|
"from sklearn.model_selection import train_test_split\n",
|
||||||
|
"\n",
|
||||||
|
"# default target_accuracy is 0.998\n",
|
||||||
|
"minimizer = GeneralizeToRepresentative(model)\n",
|
||||||
|
"# Can be done either on training or test data. Doing it with test data is better as the resulting accuracy on test\n",
|
||||||
|
"# data will be closer to the desired target accuracy (when working with training data it could result in a larger gap)\n",
|
||||||
|
"# Don't forget to leave a hold-out set for final validation!\n",
|
||||||
|
"X_generalizer_train, x_test, y_generalizer_train, y_test = train_test_split(x_test, y_test, stratify=y_test,\n",
|
||||||
|
" test_size = 0.4, random_state = 38)\n",
|
||||||
|
"x_train_predictions = model.predict(X_generalizer_train)\n",
|
||||||
|
"minimizer.fit(X_generalizer_train, x_train_predictions)\n",
|
||||||
|
"transformed = minimizer.transform(x_test)\n",
|
||||||
|
"\n",
|
||||||
|
"print('Accuracy on minimized data: ', model.score(transformed, y_test))"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"#### Let's see what features were generalized"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 4,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"{'ranges': {}, 'untouched': [0, 1, 2, 3, 4]}\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"generalizations = minimizer.generalizations\n",
|
||||||
|
"print(generalizations)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"We can see that for the default target accuracy of 0.998 of the original accuracy, no generalizations are possible (all features are left untouched, i.e., not generalized).\n",
|
||||||
|
"\n",
|
||||||
|
"Let's change to a slightly lower target accuracy."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 6,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"Initial accuracy of model on generalized data, relative to original model predictions (base generalization derived from tree, before improvements): 0.929376\n",
|
||||||
|
"Improving accuracy\n",
|
||||||
|
"feature to remove: 0\n",
|
||||||
|
"Removed feature: 0, new relative accuracy: 0.939867\n",
|
||||||
|
"feature to remove: 4\n",
|
||||||
|
"Removed feature: 4, new relative accuracy: 0.967247\n",
|
||||||
|
"feature to remove: 2\n",
|
||||||
|
"Removed feature: 2, new relative accuracy: 0.972620\n",
|
||||||
|
"feature to remove: 1\n",
|
||||||
|
"Removed feature: 1, new relative accuracy: 0.992323\n",
|
||||||
|
"Accuracy on minimized data: 0.820205742361431\n",
|
||||||
|
"{'ranges': {3: [546.0, 704.0, 705.5, 742.5, 782.0, 834.0, 870.0, 1446.5, 1538.5, 1612.5, 1699.0, 1744.0, 1801.0, 1814.0, 1846.0, 1881.5, 1978.5, 2248.0, 2298.5, 2537.5]}, 'untouched': [0, 1, 2, 4]}\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"# We allow a 2.5% deviation in accuracy from the original model accuracy\n",
|
||||||
|
"minimizer2 = GeneralizeToRepresentative(model, target_accuracy=0.975)\n",
|
||||||
|
"\n",
|
||||||
|
"minimizer2.fit(X_generalizer_train, x_train_predictions)\n",
|
||||||
|
"transformed2 = minimizer2.transform(x_test)\n",
|
||||||
|
"print('Accuracy on minimized data: ', model.score(transformed2, y_test))\n",
|
||||||
|
"generalizations2 = minimizer2.generalizations\n",
|
||||||
|
"print(generalizations2)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"This time we were able to generalize one feature, feature number 3 (capital-loss)."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"kernelspec": {
|
||||||
|
"display_name": "Python 3",
|
||||||
|
"language": "python",
|
||||||
|
"name": "python3"
|
||||||
|
},
|
||||||
|
"language_info": {
|
||||||
|
"codemirror_mode": {
|
||||||
|
"name": "ipython",
|
||||||
|
"version": 3
|
||||||
|
},
|
||||||
|
"file_extension": ".py",
|
||||||
|
"mimetype": "text/x-python",
|
||||||
|
"name": "python",
|
||||||
|
"nbconvert_exporter": "python",
|
||||||
|
"pygments_lexer": "ipython3",
|
||||||
|
"version": "3.8.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 2
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue