ai-privacy-toolkit/notebooks/attribute_inference_anonymization_nursery.ipynb
abigailgold 2b2dab6bef
Data and Model wrappers (#26)
* Squashed commit of wrappers:

    Wrapper minimizer

    * apply dataset wrapper on minimizer
    * apply changes on minimization notebook
    * add black_box_access and unlimited_queries params

    Dataset wrapper anonymizer

    Add features_names to ArrayDataset
    and allow providing features names in QI and Cat features not just indexes

    update notebooks

    categorical features and QI passed by indexes
    dataset include feature names and is_pandas param

    add pytorch Dataset

    Remove redundant code.
    Use data wrappers in model wrapper APIs.

    add generic dataset components 

    Create initial version of wrappers for models

* Fix handling of categorical features
2022-04-27 12:33:27 +03:00

796 lines
No EOL
32 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Using ML anonymization to defend against attribute inference attacks"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In this tutorial we will show how to anonymize models using the ML anonymization module. \n",
"\n",
"We will demonstrate running inference attacks both on a vanilla model, and then on different anonymized versions of the model. We will run both black-box and white-box attribute inference attacks using ART's inference module (https://github.com/Trusted-AI/adversarial-robustness-toolbox/tree/main/art/attacks/inference). \n",
"\n",
"This will be demonstarted using the Nursery dataset (original dataset can be found here: https://archive.ics.uci.edu/ml/datasets/nursery). \n",
"\n",
"The sensitive feature we are trying to infer is the 'social' feature, after turning it into a binary feature (the original value 'problematic' receives the new value 1 and the rest 0). We also preprocess the data such that all categorical features are one-hot encoded."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Load data"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": " parents has_nurs form children housing finance \\\n8450 pretentious very_crit foster 1 less_conv convenient \n12147 great_pret very_crit complete 1 critical inconv \n2780 usual critical complete 4 less_conv convenient \n11924 great_pret critical foster 1 critical convenient \n59 usual proper complete 2 convenient convenient \n... ... ... ... ... ... ... \n5193 pretentious less_proper complete 1 convenient inconv \n1375 usual less_proper incomplete 2 less_conv convenient \n10318 great_pret less_proper foster 4 convenient convenient \n6396 pretentious improper completed 3 less_conv convenient \n485 usual proper incomplete 1 critical inconv \n\n social health \n8450 1 not_recom \n12147 1 recommended \n2780 1 not_recom \n11924 1 not_recom \n59 0 not_recom \n... ... ... \n5193 0 recommended \n1375 1 priority \n10318 0 priority \n6396 1 recommended \n485 1 not_recom \n\n[10366 rows x 8 columns]",
"text/html": "<div>\n<style scoped>\n .dataframe tbody tr th:only-of-type {\n vertical-align: middle;\n }\n\n .dataframe tbody tr th {\n vertical-align: top;\n }\n\n .dataframe thead th {\n text-align: right;\n }\n</style>\n<table border=\"1\" class=\"dataframe\">\n <thead>\n <tr style=\"text-align: right;\">\n <th></th>\n <th>parents</th>\n <th>has_nurs</th>\n <th>form</th>\n <th>children</th>\n <th>housing</th>\n <th>finance</th>\n <th>social</th>\n <th>health</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <th>8450</th>\n <td>pretentious</td>\n <td>very_crit</td>\n <td>foster</td>\n <td>1</td>\n <td>less_conv</td>\n <td>convenient</td>\n <td>1</td>\n <td>not_recom</td>\n </tr>\n <tr>\n <th>12147</th>\n <td>great_pret</td>\n <td>very_crit</td>\n <td>complete</td>\n <td>1</td>\n <td>critical</td>\n <td>inconv</td>\n <td>1</td>\n <td>recommended</td>\n </tr>\n <tr>\n <th>2780</th>\n <td>usual</td>\n <td>critical</td>\n <td>complete</td>\n <td>4</td>\n <td>less_conv</td>\n <td>convenient</td>\n <td>1</td>\n <td>not_recom</td>\n </tr>\n <tr>\n <th>11924</th>\n <td>great_pret</td>\n <td>critical</td>\n <td>foster</td>\n <td>1</td>\n <td>critical</td>\n <td>convenient</td>\n <td>1</td>\n <td>not_recom</td>\n </tr>\n <tr>\n <th>59</th>\n <td>usual</td>\n <td>proper</td>\n <td>complete</td>\n <td>2</td>\n <td>convenient</td>\n <td>convenient</td>\n <td>0</td>\n <td>not_recom</td>\n </tr>\n <tr>\n <th>...</th>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n </tr>\n <tr>\n <th>5193</th>\n <td>pretentious</td>\n <td>less_proper</td>\n <td>complete</td>\n <td>1</td>\n <td>convenient</td>\n <td>inconv</td>\n <td>0</td>\n <td>recommended</td>\n </tr>\n <tr>\n <th>1375</th>\n <td>usual</td>\n <td>less_proper</td>\n <td>incomplete</td>\n <td>2</td>\n <td>less_conv</td>\n <td>convenient</td>\n <td>1</td>\n <td>priority</td>\n </tr>\n <tr>\n <th>10318</th>\n <td>great_pret</td>\n <td>less_proper</td>\n <td>foster</td>\n <td>4</td>\n <td>convenient</td>\n <td>convenient</td>\n <td>0</td>\n <td>priority</td>\n </tr>\n <tr>\n <th>6396</th>\n <td>pretentious</td>\n <td>improper</td>\n <td>completed</td>\n <td>3</td>\n <td>less_conv</td>\n <td>convenient</td>\n <td>1</td>\n <td>recommended</td>\n </tr>\n <tr>\n <th>485</th>\n <td>usual</td>\n <td>proper</td>\n <td>incomplete</td>\n <td>1</td>\n <td>critical</td>\n <td>inconv</td>\n <td>1</td>\n <td>not_recom</td>\n </tr>\n </tbody>\n</table>\n<p>10366 rows × 8 columns</p>\n</div>"
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import os\n",
"import sys\n",
"sys.path.insert(0, os.path.abspath('..'))\n",
"\n",
"from apt.utils.dataset_utils import get_nursery_dataset\n",
"\n",
"(x_train, y_train), (x_test, y_test) = get_nursery_dataset(transform_social=True)\n",
"\n",
"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.9969135802469136\n"
]
}
],
"source": [
"from sklearn.tree import DecisionTreeClassifier\n",
"from art.estimators.classification.scikitlearn import ScikitlearnDecisionTreeClassifier\n",
"from sklearn.preprocessing import OneHotEncoder\n",
"\n",
"x_train_str = x_train.astype(str)\n",
"train_encoded = OneHotEncoder(sparse=False).fit_transform(x_train_str)\n",
"x_test_str = x_test.astype(str)\n",
"test_encoded = OneHotEncoder(sparse=False).fit_transform(x_test_str)\n",
" \n",
"model = DecisionTreeClassifier()\n",
"model.fit(train_encoded, y_train)\n",
"\n",
"art_classifier = ScikitlearnDecisionTreeClassifier(model)\n",
"\n",
"print('Base model accuracy: ', model.score(test_encoded, y_test))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Attack\n",
"### Black-box attack\n",
"The black-box attack basically trains an additional classifier (called the attack model) to predict the attacked feature's value from the remaining n-1 features as well as the original (attacked) model's predictions.\n",
"#### Train attack model"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"from art.attacks.inference.attribute_inference import AttributeInferenceBlackBox\n",
"\n",
"attack_feature = 20\n",
"\n",
"# training data without attacked feature\n",
"x_train_for_attack = np.delete(train_encoded, attack_feature, 1)\n",
"# only attacked feature\n",
"x_train_feature = train_encoded[:, attack_feature].copy().reshape(-1, 1)\n",
"\n",
"bb_attack = AttributeInferenceBlackBox(art_classifier, attack_feature=attack_feature)\n",
"\n",
"# get original model's predictions\n",
"x_train_predictions = np.array([np.argmax(arr) for arr in art_classifier.predict(train_encoded)]).reshape(-1,1)\n",
"\n",
"# use half of training set for training the attack\n",
"attack_train_ratio = 0.5\n",
"attack_train_size = int(len(train_encoded) * attack_train_ratio)\n",
"\n",
"# train attack model\n",
"bb_attack.fit(train_encoded[:attack_train_size])"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Infer sensitive feature and check accuracy"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1.0\n"
]
}
],
"source": [
"# get inferred values\n",
"values=[0, 1]\n",
"\n",
"inferred_train_bb = bb_attack.infer(x_train_for_attack[attack_train_size:], x_train_predictions[attack_train_size:], values=values)\n",
"# check accuracy\n",
"train_acc = np.sum(inferred_train_bb == np.around(x_train_feature[attack_train_size:], decimals=8).reshape(1,-1)) / len(inferred_train_bb)\n",
"print(train_acc)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This means that for 64% of the training set, the attacked feature is inferred correctly using this attack."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Whitebox attack\n",
"This attack does not train any additional model, it simply uses additional information coded within the attacked decision tree model to compute the probability of each value of the attacked feature and outputs the value with the highest probability."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0.5122515917422342\n"
]
}
],
"source": [
"from art.attacks.inference.attribute_inference import AttributeInferenceWhiteBoxDecisionTree\n",
"\n",
"priors = [6925 / 10366, 3441 / 10366]\n",
"\n",
"wb2_attack = AttributeInferenceWhiteBoxDecisionTree(art_classifier, attack_feature=attack_feature)\n",
"\n",
"# get inferred values\n",
"inferred_train_wb2 = wb2_attack.infer(x_train_for_attack, x_train_predictions, values=values, priors=priors)\n",
"\n",
"# check accuracy\n",
"train_acc = np.sum(inferred_train_wb2 == np.around(x_train_feature, decimals=8).reshape(1,-1)) / len(inferred_train_wb2)\n",
"print(train_acc)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The white-box attack is able to correctly infer the attacked feature value in 69% of the training set. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Anonymized data\n",
"## k=100\n",
"\n",
"Now we will apply the same attacks on an anonymized version of the same dataset (k=100). The data is anonymized on the quasi-identifiers: finance, social, health.\n",
"\n",
"k=100 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 3 feature, the records are indistinguishable)."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": " parents has_nurs form children housing finance \\\n0 pretentious very_crit foster 1 less_conv convenient \n1 great_pret very_crit complete 1 critical inconv \n2 usual critical complete 4 less_conv convenient \n3 great_pret critical foster 1 critical convenient \n4 usual proper complete 2 convenient convenient \n... ... ... ... ... ... ... \n10361 pretentious less_proper complete 1 convenient inconv \n10362 usual less_proper incomplete 2 less_conv convenient \n10363 great_pret less_proper foster 4 convenient convenient \n10364 pretentious improper completed 3 less_conv convenient \n10365 usual proper incomplete 1 critical convenient \n\n social health \n0 0 not_recom \n1 1 recommended \n2 0 not_recom \n3 0 not_recom \n4 0 not_recom \n... ... ... \n10361 0 recommended \n10362 1 priority \n10363 0 priority \n10364 1 recommended \n10365 0 not_recom \n\n[10366 rows x 8 columns]",
"text/html": "<div>\n<style scoped>\n .dataframe tbody tr th:only-of-type {\n vertical-align: middle;\n }\n\n .dataframe tbody tr th {\n vertical-align: top;\n }\n\n .dataframe thead th {\n text-align: right;\n }\n</style>\n<table border=\"1\" class=\"dataframe\">\n <thead>\n <tr style=\"text-align: right;\">\n <th></th>\n <th>parents</th>\n <th>has_nurs</th>\n <th>form</th>\n <th>children</th>\n <th>housing</th>\n <th>finance</th>\n <th>social</th>\n <th>health</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <th>0</th>\n <td>pretentious</td>\n <td>very_crit</td>\n <td>foster</td>\n <td>1</td>\n <td>less_conv</td>\n <td>convenient</td>\n <td>0</td>\n <td>not_recom</td>\n </tr>\n <tr>\n <th>1</th>\n <td>great_pret</td>\n <td>very_crit</td>\n <td>complete</td>\n <td>1</td>\n <td>critical</td>\n <td>inconv</td>\n <td>1</td>\n <td>recommended</td>\n </tr>\n <tr>\n <th>2</th>\n <td>usual</td>\n <td>critical</td>\n <td>complete</td>\n <td>4</td>\n <td>less_conv</td>\n <td>convenient</td>\n <td>0</td>\n <td>not_recom</td>\n </tr>\n <tr>\n <th>3</th>\n <td>great_pret</td>\n <td>critical</td>\n <td>foster</td>\n <td>1</td>\n <td>critical</td>\n <td>convenient</td>\n <td>0</td>\n <td>not_recom</td>\n </tr>\n <tr>\n <th>4</th>\n <td>usual</td>\n <td>proper</td>\n <td>complete</td>\n <td>2</td>\n <td>convenient</td>\n <td>convenient</td>\n <td>0</td>\n <td>not_recom</td>\n </tr>\n <tr>\n <th>...</th>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n <td>...</td>\n </tr>\n <tr>\n <th>10361</th>\n <td>pretentious</td>\n <td>less_proper</td>\n <td>complete</td>\n <td>1</td>\n <td>convenient</td>\n <td>inconv</td>\n <td>0</td>\n <td>recommended</td>\n </tr>\n <tr>\n <th>10362</th>\n <td>usual</td>\n <td>less_proper</td>\n <td>incomplete</td>\n <td>2</td>\n <td>less_conv</td>\n <td>convenient</td>\n <td>1</td>\n <td>priority</td>\n </tr>\n <tr>\n <th>10363</th>\n <td>great_pret</td>\n <td>less_proper</td>\n <td>foster</td>\n <td>4</td>\n <td>convenient</td>\n <td>convenient</td>\n <td>0</td>\n <td>priority</td>\n </tr>\n <tr>\n <th>10364</th>\n <td>pretentious</td>\n <td>improper</td>\n <td>completed</td>\n <td>3</td>\n <td>less_conv</td>\n <td>convenient</td>\n <td>1</td>\n <td>recommended</td>\n </tr>\n <tr>\n <th>10365</th>\n <td>usual</td>\n <td>proper</td>\n <td>incomplete</td>\n <td>1</td>\n <td>critical</td>\n <td>convenient</td>\n <td>0</td>\n <td>not_recom</td>\n </tr>\n </tbody>\n</table>\n<p>10366 rows × 8 columns</p>\n</div>"
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from apt.utils.datasets import ArrayDataset\n",
"from apt.anonymization import Anonymize\n",
"\n",
"features = x_train.columns\n",
"QI = [\"finance\", \"social\", \"health\"]\n",
"categorical_features = [\"parents\", \"has_nurs\", \"form\", \"housing\", \"finance\", \"health\", 'children']\n",
"QI_indexes = [i for i, v in enumerate(features) if v in QI]\n",
"categorical_features_indexes = [i for i, v in enumerate(features) if v in categorical_features]\n",
"anonymizer = Anonymize(100, QI_indexes, categorical_features=categorical_features_indexes)\n",
"anon = anonymizer.anonymize(ArrayDataset(x_train, x_train_predictions))\n",
"anon\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": "7585"
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# number of distinct rows in original data\n",
"len(x_train.drop_duplicates())"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": "5766"
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# number of distinct rows in anonymized data\n",
"len(anon.drop_duplicates())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Train decision tree model"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Anonymized model accuracy: 0.9976851851851852\n"
]
}
],
"source": [
"anon_str = anon.astype(str)\n",
"anon_encoded = OneHotEncoder(sparse=False).fit_transform(anon_str)\n",
"\n",
"anon_model = DecisionTreeClassifier()\n",
"anon_model.fit(anon_encoded, y_train)\n",
"\n",
"anon_art_classifier = ScikitlearnDecisionTreeClassifier(anon_model)\n",
"\n",
"print('Anonymized model accuracy: ', anon_model.score(test_encoded, y_test))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Attack\n",
"### Black-box attack"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1.0\n"
]
}
],
"source": [
"anon_bb_attack = AttributeInferenceBlackBox(anon_art_classifier, attack_feature=attack_feature)\n",
"\n",
"# get original model's predictions\n",
"anon_x_train_predictions = np.array([np.argmax(arr) for arr in anon_art_classifier.predict(train_encoded)]).reshape(-1,1)\n",
"\n",
"# train attack model\n",
"anon_bb_attack.fit(train_encoded[:attack_train_size])\n",
"\n",
"# get inferred values\n",
"inferred_train_anon_bb = anon_bb_attack.infer(x_train_for_attack[attack_train_size:], anon_x_train_predictions[attack_train_size:], values=values)\n",
"# check accuracy\n",
"train_acc = np.sum(inferred_train_anon_bb == np.around(x_train_feature[attack_train_size:], decimals=8).reshape(1,-1)) / len(inferred_train_anon_bb)\n",
"print(train_acc)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### White box attack"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0.5245996527107852\n"
]
}
],
"source": [
"anon_wb2_attack = AttributeInferenceWhiteBoxDecisionTree(anon_art_classifier, attack_feature=attack_feature)\n",
"\n",
"# get inferred values\n",
"inferred_train_anon_wb2 = anon_wb2_attack.infer(x_train_for_attack, anon_x_train_predictions, values=values, priors=priors)\n",
"\n",
"# check accuracy\n",
"anon_train_acc = np.sum(inferred_train_anon_wb2 == np.around(x_train_feature, decimals=8).reshape(1,-1)) / len(inferred_train_anon_wb2)\n",
"print(anon_train_acc)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The accuracy of the attacks remains more or less the same. Let's check the precision and recall for each case:"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(0.49415432579890883, 0.48976438779451525)\n",
"(0.49415432579890883, 0.48976438779451525)\n"
]
}
],
"source": [
"def calc_precision_recall(predicted, actual, positive_value=1):\n",
" score = 0 # both predicted and actual are positive\n",
" num_positive_predicted = 0 # predicted positive\n",
" num_positive_actual = 0 # actual positive\n",
" for i in range(len(predicted)):\n",
" if predicted[i] == positive_value:\n",
" num_positive_predicted += 1\n",
" if actual[i] == positive_value:\n",
" num_positive_actual += 1\n",
" if predicted[i] == actual[i]:\n",
" if predicted[i] == positive_value:\n",
" score += 1\n",
" \n",
" if num_positive_predicted == 0:\n",
" precision = 1\n",
" else:\n",
" precision = score / num_positive_predicted # the fraction of predicted “Yes” responses that are correct\n",
" if num_positive_actual == 0:\n",
" recall = 1\n",
" else:\n",
" recall = score / num_positive_actual # the fraction of “Yes” responses that are predicted correctly\n",
"\n",
" return precision, recall\n",
" \n",
"# black-box regular\n",
"print(calc_precision_recall(inferred_train_bb, x_train_feature))\n",
"# black-box anonymized\n",
"print(calc_precision_recall(inferred_train_anon_bb, x_train_feature))"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(1.0, 0.019204655674102813)\n",
"(0.9829787234042553, 0.04481086323957323)\n"
]
}
],
"source": [
"# white-box regular\n",
"print(calc_precision_recall(inferred_train_wb2, x_train_feature))\n",
"# white-box anonymized\n",
"print(calc_precision_recall(inferred_train_anon_wb2, x_train_feature))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Precision and recall remain almost the same, sometimes even slightly increasing.\n",
"\n",
"Now let's see what happens when we increase k to 1000."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## k=1000\n",
"\n",
"Now we apply the attacks on an anonymized version of the same dataset (k=1000). The data has been anonymized on the quasi-identifiers: finance, social, health."
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
"anonymizer2 = Anonymize(1000, QI_indexes, categorical_features=categorical_features_indexes)\n",
"anon2 = anonymizer2.anonymize(ArrayDataset(x_train, x_train_predictions))"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": "4226"
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# number of distinct rows in anonymized data\n",
"len(anon2.drop_duplicates())"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Train decision tree model"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Anonymized model accuracy: 0.9930555555555556\n"
]
}
],
"source": [
"anon2_str = anon2.astype(str)\n",
"anon2_encoded = OneHotEncoder(sparse=False).fit_transform(anon2_str)\n",
"\n",
"anon2_model = DecisionTreeClassifier()\n",
"anon2_model.fit(anon2_encoded, y_train)\n",
"\n",
"anon2_art_classifier = ScikitlearnDecisionTreeClassifier(anon2_model)\n",
"\n",
"print('Anonymized model accuracy: ', anon2_model.score(test_encoded, y_test))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Attack\n",
"### Black-box attack"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1.0\n"
]
}
],
"source": [
"anon2_bb_attack = AttributeInferenceBlackBox(anon2_art_classifier, attack_feature=attack_feature)\n",
"\n",
"# get original model's predictions\n",
"anon2_x_train_predictions = np.array([np.argmax(arr) for arr in anon2_art_classifier.predict(train_encoded)]).reshape(-1,1)\n",
"\n",
"# train attack model\n",
"anon2_bb_attack.fit(train_encoded[:attack_train_size])\n",
"\n",
"# get inferred values\n",
"inferred_train_anon2_bb = anon2_bb_attack.infer(x_train_for_attack[attack_train_size:], anon2_x_train_predictions[attack_train_size:], values=values)\n",
"# check accuracy\n",
"train_acc = np.sum(inferred_train_anon2_bb == np.around(x_train_feature[attack_train_size:], decimals=8).reshape(1,-1)) / len(inferred_train_anon2_bb)\n",
"print(train_acc)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### White box attack"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0.515820953115956\n"
]
}
],
"source": [
"anon2_wb2_attack = AttributeInferenceWhiteBoxDecisionTree(anon2_art_classifier, attack_feature=attack_feature)\n",
"\n",
"# get inferred values\n",
"inferred_train_anon2_wb2 = anon2_wb2_attack.infer(x_train_for_attack, anon2_x_train_predictions, values=values, priors=priors)\n",
"\n",
"# check accuracy\n",
"train_acc = np.sum(inferred_train_anon2_wb2 == np.around(x_train_feature, decimals=8).reshape(1,-1)) / len(inferred_train_anon_wb2)\n",
"print(train_acc)"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(0.49415432579890883, 0.48976438779451525)\n",
"(0.49415432579890883, 0.48976438779451525)\n",
"(1.0, 0.019204655674102813)\n",
"(1.0, 0.026382153249272552)\n"
]
}
],
"source": [
"# black-box regular\n",
"print(calc_precision_recall(inferred_train_bb, x_train_feature))\n",
"# black-box anonymized\n",
"print(calc_precision_recall(inferred_train_anon2_bb, x_train_feature))\n",
"\n",
"# white-box regular\n",
"print(calc_precision_recall(inferred_train_wb2, x_train_feature))\n",
"# white-box anonymized\n",
"print(calc_precision_recall(inferred_train_anon2_wb2, x_train_feature))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The accuracy of the black-box attack is slightly reduced, as well as the precision and recall in both attacks."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## k=100, all QI\n",
"Now let's see what happens if we define all 8 features in the Nursery dataset as quasi-identifiers."
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [],
"source": [
"QI2 = [\"parents\", \"has_nurs\", \"form\", \"children\", \"housing\", \"finance\", \"social\", \"health\"]\n",
"QI2_indexes = [i for i, v in enumerate(features) if v in QI2]\n",
"anonymizer3 = Anonymize(100, QI2_indexes, categorical_features=categorical_features_indexes)\n",
"anon3 = anonymizer3.anonymize(ArrayDataset(x_train, x_train_predictions))"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": "39"
},
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# number of distinct rows in anonymized data\n",
"len(anon3.drop_duplicates())"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Anonymized model accuracy: 0.751929012345679\n",
"BB attack accuracy: 1.0\n",
"WB attack accuracy: 0.5187150299054601\n"
]
}
],
"source": [
"anon3_str = anon3.astype(str)\n",
"anon3_encoded = OneHotEncoder(sparse=False).fit_transform(anon3_str)\n",
"\n",
"anon3_model = DecisionTreeClassifier()\n",
"anon3_model.fit(anon3_encoded, y_train)\n",
"\n",
"anon3_art_classifier = ScikitlearnDecisionTreeClassifier(anon3_model)\n",
"\n",
"print('Anonymized model accuracy: ', anon3_model.score(test_encoded, y_test))\n",
"\n",
"anon3_bb_attack = AttributeInferenceBlackBox(anon3_art_classifier, attack_feature=attack_feature)\n",
"\n",
"# get original model's predictions\n",
"anon3_x_train_predictions = np.array([np.argmax(arr) for arr in anon3_art_classifier.predict(train_encoded)]).reshape(-1,1)\n",
"\n",
"# train attack model\n",
"anon3_bb_attack.fit(train_encoded[:attack_train_size])\n",
"\n",
"# get inferred values\n",
"inferred_train_anon3_bb = anon3_bb_attack.infer(x_train_for_attack[attack_train_size:], anon3_x_train_predictions[attack_train_size:], values=values)\n",
"# check accuracy\n",
"train_acc = np.sum(inferred_train_anon3_bb == np.around(x_train_feature[attack_train_size:], decimals=8).reshape(1,-1)) / len(inferred_train_anon2_bb)\n",
"print('BB attack accuracy: ', train_acc)\n",
"\n",
"anon3_wb2_attack = AttributeInferenceWhiteBoxDecisionTree(anon3_art_classifier, attack_feature=attack_feature)\n",
"\n",
"# get inferred values\n",
"inferred_train_anon3_wb2 = anon3_wb2_attack.infer(x_train_for_attack, anon3_x_train_predictions, values=values, priors=priors)\n",
"\n",
"# check accuracy\n",
"train_acc = np.sum(inferred_train_anon3_wb2 == np.around(x_train_feature, decimals=8).reshape(1,-1)) / len(inferred_train_anon_wb2)\n",
"print('WB attack accuracy: ', train_acc)"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(0.49415432579890883, 0.48976438779451525)\n",
"(0.49415432579890883, 0.48976438779451525)\n",
"(1.0, 0.019204655674102813)\n",
"(1.0, 0.032201745877788554)\n"
]
}
],
"source": [
"# black-box regular\n",
"print(calc_precision_recall(inferred_train_bb, x_train_feature))\n",
"# black-box anonymized\n",
"print(calc_precision_recall(inferred_train_anon3_bb, x_train_feature))\n",
"\n",
"# white-box regular\n",
"print(calc_precision_recall(inferred_train_wb2, x_train_feature))\n",
"# white-box anonymized\n",
"print(calc_precision_recall(inferred_train_anon3_wb2, x_train_feature))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Accuracy of both attacks has decreased. Precision and recall remain roughly the same in the black-box case. \n",
"\n",
"*In the anonymized version of the white-box attack, no records were predicted with the positive value for the attacked feature."
]
}
],
"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
}