# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

# + _uuid="1713f4306d45835efceefad592e4e6b046302f23"
import pandas as pd
import numpy as np
np.random.seed(42)

import matplotlib.pyplot as plt
# %matplotlib inline


from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score, roc_auc_score
from sklearn.utils.class_weight import compute_class_weight


from keras.layers import Input, Dense, Dropout
from keras.models import Model

# + _uuid="e1b433fb1c8346a04cff7d90f162a90afc6a97e9"
# !ls ../input

# + _uuid="67ca0a2af21dfcd70a0d6ea024ae1417c0db0097"
path = '../input/adult.csv'
input_data = pd.read_csv(path, na_values="?")

# + _uuid="c2cdce6626547f1f755782cd426c777173f564ac"
input_data.head()

# + _uuid="6d81a92b7651715cabfed67491e587863c1d8698"
input_data = input_data[input_data['race'].isin(['White', 'Black'])]

# + _uuid="90028dc30c91810dfb793280dbc3b89930303e7c"
input_data.head()

# + _uuid="5092dbcf31d13e4b76411bbece6c42e164edd348"
# sensitive attributes; we identify 'race' and 'sex' as sensitive attributes
sensitive_attribs = ['race', 'gender']
A = input_data[sensitive_attribs]
A = pd.get_dummies(A,drop_first=True)
A.columns = sensitive_attribs

# + _uuid="18e6ae5dda563f17ff3ac647f55f0d990e42e4c7"
A.head()

# + _uuid="2f3877d05de984830b75cc4e0006feec6ca00f8e"
y = (input_data['income'] == '>50K').astype(int)

# + _uuid="916460c4b276af0d12a496d89ef2ff61f5ad96ed"
X = input_data.drop(labels=['income', 'race', 'gender'],axis=1)

X = X.fillna('Unknown')

X = pd.get_dummies(X, drop_first=True)

# + _uuid="1c7a4316a7f23f885620a7ca456443c3cf4fcae0"
# split into train/test set
X_train, X_test, y_train, y_test, A_train, A_test = train_test_split(X, y, A, test_size=0.5, 
                                                                     stratify=y, random_state=7)

# standardize the data
scaler = StandardScaler().fit(X_train)
#scale_df = lambda df, scaler: pd.DataFrame(scaler.transform(df), columns=df.columns, index=df.index)
X_train = pd.DataFrame(scaler.transform(X_train), columns=X_train.columns, index=X_train.index)
X_test = pd.DataFrame(scaler.transform(X_test), columns=X_test.columns, index=X_test.index)


# + _uuid="33d6dd864dd78f81025e9e836cbb57b2c2b02643"
def p_rule(y_pred, a_values, threshold=0.5):
    y_a_1 = y_pred[a_values == 1] > threshold if threshold else y_pred[a_values == 1]
    y_a_0 = y_pred[a_values == 0] > threshold if threshold else y_pred[a_values == 0]
    odds = y_a_1.mean() / y_a_0.mean()
    return np.min([odds, 1/odds]) * 100


# + _uuid="552af7ac22b3f3f52424bc183d15d8d89cfdae18"
def make_trainable_fn(net): # Produces a function that makes a network trainable or not
    def make_trainable(flag): # Loop over layers and set their trainability
        net.trainable = flag
        for layer in net.layers:
            layer.trainable = flag
    return make_trainable


# + _uuid="da96fef499154bed2d2454b12e8350362e5bf65d"
def compute_class_weights(data_set):
    class_values = [0, 1]
    class_weights = []
    if len(data_set.shape) == 1:
        balanced_weights = compute_class_weight('balanced', class_values, data_set)
        class_weights.append(dict(zip(class_values, balanced_weights)))
    else:
        n_attr =  data_set.shape[1]
        for attr_idx in range(n_attr):
            balanced_weights = compute_class_weight('balanced', class_values,
                                                    np.array(data_set)[:,attr_idx])
            class_weights.append(dict(zip(class_values, balanced_weights)))
    return class_weights


# + _uuid="1deddf7765ea1ccb094ad147832c35e8977cfcb1"
def compute_target_class_weights(y):
    class_values  = [0,1]
    balanced_weights =  compute_class_weight('balanced', class_values, y)
    class_weights = {'y': dict(zip(class_values, balanced_weights))}
    return class_weights


# + _uuid="98e90f1c8f2b380bd8ef2fcae2434fb33c22e1b8"
n_features=X_train.shape[1]
n_sensitive=A_train.shape[1]
lambdas=[130., 30.]


# + _uuid="14d4bfe6a04f952fad4a3f0b00f0caf105181d4b"
clf_inputs = Input(shape=(n_features,)) # Classifier input = All features

############### Create CLF net ########################
x = Dense(32, activation='relu')(clf_inputs)
x = Dropout(0.2)(x)
x = Dense(32, activation='relu')(x)
x = Dropout(0.2)(x)
x = Dense(32, activation='relu')(x)
x = Dropout(0.2)(x)
outputs = Dense(1, activation='sigmoid', name='y')(x)
clf_net = Model(inputs=[clf_inputs], outputs=[outputs])
#######################################################

# + _uuid="2e85c13eac2c339276e14770b17caba341bca582"
adv_inputs = Input(shape=(1,)) # Adversary input = Classifier output (one number)

############## Create ADV net #########################
x = Dense(32, activation='relu')(adv_inputs)
x = Dense(32, activation='relu')(x)
x = Dense(32, activation='relu')(x)
outputs = [Dense(1, activation='sigmoid')(x) for _ in range(n_sensitive)]
adv_net = Model(inputs=[adv_inputs], outputs=outputs)
#######################################################

# + _uuid="ffc19e462ce48456c9e698c4a564f8473489093c"
############## Create train switches #################
trainable_clf_net = make_trainable_fn(clf_net) # Get function to make classifier trainable

trainable_adv_net = make_trainable_fn(adv_net) # Function to make adversary trainable

######################################################

# + _uuid="0ba4fdfbfb8233f90affbd4495156a4b4aeca0fc"
#clf = compile_clf(clf_net) # Compile classifier

clf = clf_net
trainable_clf_net(True)
clf.compile(loss='binary_crossentropy', optimizer='adam')


# + _uuid="397117080a03743e5eba52b928a5b7db8585d301"
# Creates a classifier adversary super net
adv_out = adv_net(clf_net(clf_inputs))
clf_out = clf_net(clf_inputs)
clf_w_adv = Model(inputs=[clf_inputs], outputs=[clf_out]+adv_out)

# The adversary is not trainable the classifier is
trainable_clf_net(True)
trainable_adv_net(False)
# Create a weighted loss for all sensitive variables
loss_weights = [1.]+[-lambda_param for lambda_param in lambdas]
# Compile super net
clf_w_adv.compile(loss='binary_crossentropy', 
                  loss_weights=loss_weights,
                  optimizer='adam')

# + _uuid="568e536f4b0d54022b8022f6579180def3d598d7"
# Compile adversary with the classifier as inputs
adv = Model(inputs=[clf_inputs], outputs=adv_net(clf_net(clf_inputs)))
# Classifier is not trainable, adversary is
trainable_clf_net(False)
trainable_adv_net(True)
adv.compile(loss='binary_crossentropy', optimizer='adam')

# + _uuid="37bd746cdb7ddedc52948517c091b2a6ab378e27"
trainable_clf_net(True)
clf.fit(X_train.values, y_train.values, epochs=10)

# + _uuid="6b9d0368986990063acd2b4b36d239ce9583bc55"
trainable_clf_net(False)
trainable_adv_net(True)
class_weight_adv = compute_class_weights(A_train)
adv.fit(X_train.values, np.hsplit(A_train.values, A_train.shape[1]), class_weight=class_weight_adv,epochs=10)

# + _uuid="8297c4ceb5334aac278aed6260c4f508581a7f61"
y_pred = clf.predict(X_test)

# + _uuid="91c96a5c1bd240a153270ab162c0df02d86192c3"
for sens in A_test.columns:
    pr = p_rule(y_pred,A_test[sens])
    print(sens,pr)

# + _uuid="d9c86e63a56fe33fa5759239782d1802cd7cd26e"
acc = accuracy_score(y_test,(y_pred>0.5))* 100
print('Clf acc: {:.2f}'.format(acc))

# + _uuid="488a8dc18376fa0ff02fa6af5a837611811875a0"
n_iter=250
batch_size=128
n_sensitive = A_train.shape[1]

class_weight_clf_w_adv = [{0:1., 1:1.}]+class_weight_adv

val_metrics = pd.DataFrame()

fairness_metrics = pd.DataFrame()

for idx in range(n_iter): # Train for n epochs

    # train adverserial
    trainable_clf_net(False)
    trainable_adv_net(True)
    adv.fit(X_train.values, 
            np.hsplit(A_train.values, A_train.shape[1]), 
            batch_size=batch_size, 
            class_weight=class_weight_adv, 
            epochs=1, verbose=0)


    # train classifier
    # Make classifier trainable and adversery untrainable
    trainable_clf_net(True)
    trainable_adv_net(False)
    # Sample batch
    indices = np.random.permutation(len(X_train))[:batch_size]
    # Train on batch
    clf_w_adv.train_on_batch(X_train.values[indices], 
                            [y_train.values[indices]]+np.hsplit(A_train.values[indices], n_sensitive),
                            class_weight=class_weight_clf_w_adv)

    
    # Make validation data predictions
    y_pred = pd.Series(clf.predict(X_test).ravel(), index=y_test.index)

    roc_auc = roc_auc_score(y_test, y_pred)
    acc = accuracy_score(y_test, (y_pred>0.5))*100
    # Calculate ROC and accuracy
    val_metrics.loc[idx, 'ROC AUC'] = roc_auc
    val_metrics.loc[idx, 'Accuracy'] = acc

    # Calculate p rule
    for sensitive_attr in A_test.columns:
        fairness_metrics.loc[idx, sensitive_attr] = p_rule(y_pred,A_test[sensitive_attr])

    print('Epoch: {}, Accuracy: {:.2f}, Race P: {:.2f}, Gender P: {:.2f}'.format(idx,
                                                                                 acc, 
                                                                                 fairness_metrics.loc[idx, 'race'],
                                                                                 fairness_metrics.loc[idx, 'gender']))



# + _uuid="17e86024acf92095929a36df1f02f84e88c5132d"
# adverserial train on train set and validate on test set
#vm, fm = fit(X_train, y_train, A_train,validation_data=(X_test, y_test, A_test),n_iter=200)

# + _uuid="d43f2519b2c87f6f12669cc5c6005811836a5724"
plt.figure(figsize=(10,7))
plt.xlabel('Epochs')
plt.plot(val_metrics['Accuracy'],label='Accuracy')
plt.plot(val_metrics['ROC AUC']*100,label='ROC AUC')
plt.plot(fairness_metrics['race'],label='Race')
plt.plot(fairness_metrics['gender'],label='Gender')
plt.legend()

# + _uuid="ac795a99159aa2fb93c229a165012b718b45b857"


StarcoderdataJupyterScriptsDedupFiltered