mirror of
https://github.com/IBM/ai-privacy-toolkit.git
synced 2026-05-12 05:22:37 +02:00
Support 1-hot encoded features in anonymization + fixes related to encoding in minimization (#86)
* Support 1-hot encoded features in anonymization (#72) * Fix anonymization adult notebook + new notebook to demonstrate anonymization on 1-hot encoded data * Minimizer: No default encoder, if none provided data is supplied to the model as is. Fix data type of representative values. Fix and add more tests. Signed-off-by: abigailt <abigailt@il.ibm.com>
This commit is contained in:
parent
26addd192f
commit
5dce961092
7 changed files with 670 additions and 255 deletions
303
notebooks/anonymization_one_hot_adult.ipynb
Normal file
303
notebooks/anonymization_one_hot_adult.ipynb
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Using ML anonymization on one-hot encoded data"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"In this tutorial we will show how to anonymize models using the ML anonymization module, specifically when the inout data is already one-hot encoded. \n",
|
||||
"\n",
|
||||
"This will be demonstarted using the Adult dataset (original dataset can be found here: https://archive.ics.uci.edu/ml/datasets/adult). "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Load data"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 21,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[['State-gov' 'Never-married' 'Adm-clerical' ... 'White' 'Male'\n",
|
||||
" 'UnitedStates']\n",
|
||||
" ['Self-emp-not-inc' 'Married-civ-spouse' 'Exec-managerial' ... 'White'\n",
|
||||
" 'Male' 'UnitedStates']\n",
|
||||
" ['Private' 'Divorced' 'Handlers-cleaners' ... 'White' 'Male'\n",
|
||||
" 'UnitedStates']\n",
|
||||
" ...\n",
|
||||
" ['Private' 'Never-married' 'Sales' ... 'White' 'Female' 'UnitedStates']\n",
|
||||
" ['Private' 'Never-married' 'Craft-repair' ... 'White' 'Male'\n",
|
||||
" 'UnitedStates']\n",
|
||||
" ['Private' 'Never-married' 'Handlers-cleaners' ... 'White' 'Male'\n",
|
||||
" 'UnitedStates']]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import numpy as np\n",
|
||||
"\n",
|
||||
"import os\n",
|
||||
"import sys\n",
|
||||
"sys.path.insert(0, os.path.abspath('..'))\n",
|
||||
"from apt.utils.dataset_utils import get_adult_dataset_pd\n",
|
||||
"\n",
|
||||
"# 'workclass', 'marital-status', 'occupation', 'relationship', 'race', 'sex', 'native-country'\n",
|
||||
"categorical_features = [1, 3, 4, 5, 6, 7, 11]\n",
|
||||
"\n",
|
||||
"# requires a folder called 'datasets' in the current directory\n",
|
||||
"(x_train, y_train), (x_test, y_test) = get_adult_dataset_pd()\n",
|
||||
"x_train = x_train.to_numpy()[:, [1, 3, 4, 5, 6, 7, 11]]\n",
|
||||
"y_train = y_train.to_numpy().astype(int)\n",
|
||||
"x_test = x_test.to_numpy()[:, [1, 3, 4, 5, 6, 7, 11]]\n",
|
||||
"y_test = y_test.to_numpy().astype(int)\n",
|
||||
"\n",
|
||||
"# get balanced dataset\n",
|
||||
"x_train = x_train[:x_test.shape[0]]\n",
|
||||
"y_train = y_train[:y_test.shape[0]]\n",
|
||||
"\n",
|
||||
"print(x_train)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Encode data"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 22,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[[0 0 0 ... 0 1 0]\n",
|
||||
" [0 0 0 ... 0 1 0]\n",
|
||||
" [0 0 0 ... 0 1 0]\n",
|
||||
" ...\n",
|
||||
" [0 0 0 ... 0 1 0]\n",
|
||||
" [0 0 0 ... 0 1 0]\n",
|
||||
" [0 0 0 ... 0 1 0]]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from sklearn.preprocessing import OneHotEncoder\n",
|
||||
"import scipy\n",
|
||||
"\n",
|
||||
"preprocessor = OneHotEncoder(handle_unknown=\"ignore\")\n",
|
||||
"\n",
|
||||
"x_train = preprocessor.fit_transform(x_train)\n",
|
||||
"x_test = preprocessor.transform(x_test)\n",
|
||||
"if scipy.sparse.issparse(x_train):\n",
|
||||
" x_train = x_train.toarray().astype(int)\n",
|
||||
"if scipy.sparse.issparse(x_test):\n",
|
||||
" x_test = x_test.toarray().astype(int)\n",
|
||||
"\n",
|
||||
"print(x_train)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Train decision tree model"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 23,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Base model accuracy: 0.814446287083103\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"/Users/abigailt/Library/Python/3.9/lib/python/site-packages/sklearn/utils/deprecation.py:103: FutureWarning: The attribute `n_features_` is deprecated in 1.0 and will be removed in 1.2. Use `n_features_in_` instead.\n",
|
||||
" warnings.warn(msg, category=FutureWarning)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from sklearn.tree import DecisionTreeClassifier\n",
|
||||
"from art.estimators.classification.scikitlearn import ScikitlearnDecisionTreeClassifier\n",
|
||||
"\n",
|
||||
"model = DecisionTreeClassifier()\n",
|
||||
"model.fit(x_train, y_train)\n",
|
||||
"\n",
|
||||
"art_classifier = ScikitlearnDecisionTreeClassifier(model)\n",
|
||||
"\n",
|
||||
"print('Base model accuracy: ', model.score(x_test, y_test))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Anonymize data\n",
|
||||
"## k=100\n",
|
||||
"\n",
|
||||
"The data is anonymized on the quasi-identifiers: age, education-num, capital-gain, hours-per-week and with a privact parameter k=100.\n",
|
||||
"\n",
|
||||
"This means that each record in the anonymized dataset is identical to 99 others on the quasi-identifier values (i.e., when looking only at those features, the records are indistinguishable)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 25,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[[0 0 0 ... 0 1 0]\n",
|
||||
" [0 0 0 ... 0 1 0]\n",
|
||||
" [0 0 0 ... 0 1 0]\n",
|
||||
" ...\n",
|
||||
" [0 0 0 ... 0 1 0]\n",
|
||||
" [0 0 0 ... 0 1 0]\n",
|
||||
" [0 0 0 ... 0 1 0]]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from apt.utils.datasets import ArrayDataset\n",
|
||||
"from apt.anonymization import Anonymize\n",
|
||||
"\n",
|
||||
"x_train_predictions = np.array([np.argmax(arr) for arr in art_classifier.predict(x_train)])\n",
|
||||
"\n",
|
||||
"# QI = (race, sex)\n",
|
||||
"QI = [53, 52, 51, 50, 49, 48, 47]\n",
|
||||
"QI_slices = [[47, 48, 49, 50, 51], [52, 53]]\n",
|
||||
"anonymizer = Anonymize(100, QI)\n",
|
||||
"anon = anonymizer.anonymize(ArrayDataset(x_train, x_train_predictions))\n",
|
||||
"print(anon)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 26,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"2711"
|
||||
]
|
||||
},
|
||||
"execution_count": 26,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# number of distinct rows in original data\n",
|
||||
"len(np.unique(x_train, axis=0))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 27,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"2476"
|
||||
]
|
||||
},
|
||||
"execution_count": 27,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# number of distinct rows in anonymized data\n",
|
||||
"len(np.unique(anon, axis=0))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Train decision tree model"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 28,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Anonymized model accuracy: 0.8135863890424421\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"/Users/abigailt/Library/Python/3.9/lib/python/site-packages/sklearn/utils/deprecation.py:103: FutureWarning: The attribute `n_features_` is deprecated in 1.0 and will be removed in 1.2. Use `n_features_in_` instead.\n",
|
||||
" warnings.warn(msg, category=FutureWarning)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"anon_model = DecisionTreeClassifier()\n",
|
||||
"anon_model.fit(anon, y_train)\n",
|
||||
"\n",
|
||||
"anon_art_classifier = ScikitlearnDecisionTreeClassifier(anon_model)\n",
|
||||
"\n",
|
||||
"print('Anonymized model accuracy: ', anon_model.score(x_test, y_test))"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"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.9.6"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue