Using ML anonymization to defend against attribute inference attacks#

Load data#

[1]:
import pandas as pd
import numpy as np

def generalize_column(column, generalization_level):
    """Generalizes a column based on the specified level."""
    if generalization_level == 0:
        return column
    else:
        generalized_column = column.apply(lambda x: x // (10 ** generalization_level) * (10 ** generalization_level))
        return generalized_column

def k_anonymize(df, quasi_identifiers, k, generalization_level=1):
    """
    Implements k-anonymity by generalizing the quasi-identifiers in the dataset.

    Parameters:
    df (pd.DataFrame): The input dataset.
    quasi_identifiers (list): List of column names that are quasi-identifiers.
    k (int): The anonymity parameter.
    generalization_level (int): The level of generalization to apply. Default is 1.

    Returns:
    pd.DataFrame: The k-anonymized dataset.
    """
    # Copy the dataframe to avoid modifying the original data
    df_anonymized = df.copy()

    # Generalize each quasi-identifier column
    for col in quasi_identifiers:
        df_anonymized[col] = generalize_column(df_anonymized[col], generalization_level)

    # Check if the dataset satisfies k-anonymity
    while True:
        # Group by quasi-identifiers and count the sizes of each group
        group_sizes = df_anonymized.groupby(quasi_identifiers).size()

        # Find groups that do not satisfy k-anonymity
        non_k_anonymous_groups = group_sizes[group_sizes < k]

        if non_k_anonymous_groups.empty:
            # All groups satisfy k-anonymity
            break
        else:
            # Increase the generalization level and apply generalization again
            generalization_level += 1
            for col in quasi_identifiers:
                df_anonymized[col] = generalize_column(df[col], generalization_level)

    return df_anonymized

# Example usage
data = {
    'age': [23, 25, 35, 45, 52, 33, 34, 25, 40, 23],
    'zip_code': [11001, 11002, 11003, 11004, 11005, 11001, 11002, 11003, 11004, 11005],
    'disease': ['Flu', 'Cold', 'Cancer', 'Flu', 'Cold', 'Cancer', 'Flu', 'Cold', 'Cancer', 'Flu']
}
df = pd.DataFrame(data)

quasi_identifiers = ['age', 'zip_code']
k = 2

df_k_anonymized = k_anonymize(df, quasi_identifiers, k)
print(df_k_anonymized)
   age  zip_code disease
0    0     11000     Flu
1    0     11000    Cold
2    0     11000  Cancer
3    0     11000     Flu
4    0     11000    Cold
5    0     11000  Cancer
6    0     11000     Flu
7    0     11000    Cold
8    0     11000  Cancer
9    0     11000     Flu
[2]:
df_k_anonymized
[2]:
age zip_code disease
0 0 11000 Flu
1 0 11000 Cold
2 0 11000 Cancer
3 0 11000 Flu
4 0 11000 Cold
5 0 11000 Cancer
6 0 11000 Flu
7 0 11000 Cold
8 0 11000 Cancer
9 0 11000 Flu
[1]:
import os
os.environ["JAX_PLATFORM_NAME"] = "cpu"
[2]:
import pandas as pd
import numpy as np
from sklearn.metrics import accuracy_score

import warnings
warnings.filterwarnings("ignore")

First of all, we need to import the required packages to perform our privacy analysis and mitigation. You will need to have the holisticai package installed on your system, remember that you can install it by running:#

!pip install holisticai[all]
[3]:
from holisticai.datasets import load_dataset

dataset = load_dataset('adult', preprocessed=False)
dataset
[3]:
DATASET
- Number of Rows: 45222
- Features: X , y , s

Computing k-Anonymity metric#

[4]:
from holisticai.security.metrics import k_anonymity

df = dataset[['X','y','s']]

QI = ['education', 'marital-status', 'age']

k_anon = k_anonymity(df, qi=QI)

k_anon.head(15)

[4]:
education     marital-status      age
Some-college  Never-married       20     467
                                  21     429
                                  19     365
                                  22     344
HS-grad       Never-married       19     324
Some-college  Never-married       23     317
HS-grad       Never-married       21     286
                                  20     280
                                  22     261
                                  23     260
Bachelors     Never-married       23     251
HS-grad       Married-civ-spouse  33     239
Bachelors     Never-married       24     238
HS-grad       Married-civ-spouse  35     238
                                  36     235
Name: count, dtype: int64

Computing l-Diversity metric#

[5]:
from holisticai.security.metrics import l_diversity

QI = ['education', 'marital-status']
sensitive_attribute = ['race']

l_div = l_diversity(df, qi=QI, sa=sensitive_attribute)

l_div['race']
[5]:
[1,
 1,
 1,
 1,
 1,
 1,
 1,
 2,
 2,
 2,
 2,
 2,
 2,
 2,
 2,
 2,
 2,
 2,
 2,
 2,
 2,
 2,
 3,
 3,
 3,
 3,
 3,
 3,
 3,
 3,
 3,
 3,
 3,
 3,
 3,
 3,
 3,
 3,
 3,
 3,
 4,
 4,
 4,
 4,
 4,
 4,
 4,
 4,
 4,
 4,
 4,
 4,
 4,
 4,
 4,
 4,
 4,
 4,
 4,
 4,
 4,
 4,
 4,
 4,
 4,
 4,
 4,
 4,
 4,
 5,
 5,
 5,
 5,
 5,
 5,
 5,
 5,
 5,
 5,
 5,
 5,
 5,
 5,
 5,
 5,
 5,
 5,
 5,
 5,
 5,
 5,
 5,
 5,
 5,
 5,
 5,
 5,
 5,
 5,
 5,
 5,
 5,
 5,
 5]

Preparing dataset#

[6]:
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline

def create_preprocessor(X):
    # Identify categorical and numerical features
    categorical_features = X.select_dtypes(include=['category']).columns
    numerical_fatures = X.select_dtypes(exclude=['category']).columns

    # Create transformers for numerical and categorical features
    numeric_transformer = Pipeline(steps=[('scaler', StandardScaler())])
    categorical_transformer = Pipeline(steps=[('onehot', OneHotEncoder(handle_unknown='ignore'))])

    # Combine transformers into a preprocessor using ColumnTransformer
    return ColumnTransformer(
        transformers=[
            ('num', numeric_transformer, numerical_fatures),
            ('cat', categorical_transformer, categorical_features)
    ])

Train decision tree model#

[7]:
from sklearn.tree import DecisionTreeClassifier

train_test = dataset.train_test_split(0.2, random_state=42)
train = train_test['train']
test = train_test['test']

preprocessor = create_preprocessor(train['X'])
model = Pipeline(steps=[('preprocessor', preprocessor),
                        ('classifier', DecisionTreeClassifier())])

model.fit(train['X'], train['y'])

print('Base model accuracy: ', model.score(test['X'], test['y']))
Base model accuracy:  0.8121614151464898

Security Metrics#

[8]:
from holisticai.utils import BinaryClassificationProxy
from holisticai.security.commons import DataMinimizer

proxy = BinaryClassificationProxy(predict=model.predict, predict_proba=model.predict_proba, classes=[0, 1])
dmin = DataMinimizer(proxy=proxy)
dmin.fit(train['X'], train['y'])

y_pred_train = proxy.predict(train['X'])
y_pred_test = proxy.predict(test['X'])
y_pred_test_dm = dmin.predict(test['X'])
[9]:
from holisticai.security.metrics import data_minimization_score

data_minimization_score(test['y'], y_pred_test, y_pred_test_dm)
[9]:
1.0107319757842599
[13]:
# Detailed
results, metric = data_minimization_score(test['y'], y_pred_test, y_pred_test_dm, return_results=True)
results
[13]:
Selection Type Modifier Type N_feats Feats Score Accuracy
0 Percentile >80 Average 1 [capital-loss] 1.010732 0.803538
1 Percentile >80 Permutation 8 [workclass, fnlwgt, education, marital-status,... 1.227197 0.661802
2 Percentile >90 Average 1 [capital-loss] 1.010732 0.803538
3 Percentile >90 Permutation 8 [workclass, fnlwgt, education, marital-status,... 1.228017 0.661360
4 Variance >80 Permutation 7 [workclass, fnlwgt, education, marital-status,... 1.117942 0.726479
5 Variance >90 Permutation 7 [workclass, fnlwgt, education, marital-status,... 1.112356 0.730127
6 FImportance >80 Average 4 [age, capital-gain, capital-loss, hours-per-week] 1.121356 0.724268
7 FImportance >80 Permutation 11 [age, workclass, fnlwgt, education, marital-st... 1.468906 0.552902
8 FImportance >90 Average 4 [age, capital-gain, capital-loss, hours-per-week] 1.121356 0.724268
9 FImportance >90 Permutation 11 [age, workclass, fnlwgt, education, marital-st... 1.479855 0.548811
10 Base Base 0 [] 1.000000 0.812161
[10]:
from holisticai.security.metrics import attribute_attack_score

attribute_attack_score(train['X'], test['X'], train['y'], test['y'], attribute_attack='education')
[10]:
0.40552791597567717
[11]:
from holisticai.security.metrics import shapr_score

shapr_score(train['y'], test['y'], y_pred_train, y_pred_test)
[11]:
0.812161386013031
[12]:
from holisticai.security.metrics import classification_privacy_metrics

security_metrics = classification_privacy_metrics(x_train=train['X'], x_test=test['X'], y_train=train['y'], y_test=test['y'],
                               y_pred_train=y_pred_train, y_pred_test=y_pred_test, y_pred_test_dm=y_pred_test_dm,
                               attribute_attack='education')
security_metrics
[12]:
metric value reference
0 SHAPr 0.812161 0.0
1 Data Minimization Accuracy Ratio 1.010732 inf
2 Attribute Attack Score 0.405528 0.0