{ "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 input 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": 29, "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": 30, "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": 31, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Base model accuracy: 0.8143234445058657\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": 32, "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, quasi_identifer_slices=QI_slices)\n", "anon = anonymizer.anonymize(ArrayDataset(x_train, x_train_predictions))\n", "print(anon)" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2711" ] }, "execution_count": 33, "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": 34, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "2476" ] }, "execution_count": 34, "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": 35, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Anonymized model accuracy: 0.8124808058473066\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 }