<jupyter_start><jupyter_text>AI Automation for AI FairnessWhen AI models contribute to high-impact decisions such as whether ornot someone gets a loan, we want them to be fair.Unfortunately, in current practice, AI models are often optimizedprimarily for accuracy, with little consideration for fairness.  Thisblog post gives a hands-on example for how AI Automation can help build AImodels that are both accurate and fair.This blog post is written for data scientists who have some familiaritywith Python. No prior knowledge of AI Automation or AI Fairness isrequired, we will introduce the relevant concepts as we get to them.Bias in data leads to bias in models. AI models are increasinglyconsulted for consequential decisions about people, in domainsincluding credit loans, hiring and retention, penal justice, medical,and more. Often, the model is trained from past decisions made byhumans. If the decisions used for training were discriminatory, thenyour trained model will be too, unless you are careful. Being carefulabout bias is something you should do as a data scientist.Fortunately, you do not have to grapple with this issue alone.  Youcan consult others about ethics. You can also ask yourself how your AImodel may affect your (or your institution's) reputation. Andultimately, you must follow applicable laws and regulations._AI Fairness_ can be measured via several metrics, and you need toselect the appropriate metrics based on the circumstances.  Forillustration purposes, this blog post uses one particular fairnessmetric called _disparate impact_. Disparate impact is defined as theratio of the rate of favorable outcome for the unprivileged group tothat of the privileged group. To make this definition more concrete,consider the case where a favorable outcome means getting a loan, theunprivileged group is women, and the privileged group is men.  Then ifyour AI model were to let women get a loan in 30% of the cases and menin 60% of the cases, the disparate impact would be 30% / 60% = 0.5,indicating a gender bias towards men.  The ideal value for disparateimpact is 1, and you could define fairness for this metric as a bandaround 1, e.g., from 0.8 to 1.2.To get the best performance out of your AI model, you must experimentwith its configuration. This means searching a high-dimensional spacewhere some options are categorical, some are continuous, and some areeven conditional. No configuration is optimal for all domains letalone all metrics, and searching them all by hand is impossible. Infact, in a high-dimensional space, even exhaustively enumerating allthe valid combinations soon becomes impractical.  Fortunately, you canuse tools to automate the search, thus making you more productive atfinding good models quickly. These productivity and qualityimprovements become compounded when you have to do the search over._AI Automation_ is a technology that assists data scientists inbuilding AI models by automating some of the tedious steps. One AIautomation technique is _algorithm selection_ , which automaticallychooses among alternative algorithms for a particular task. Another AIautomation technique is _hyperparameter tuning_ , which automaticallyconfigures the arguments of AI algorithms. You can use AI automationto optimize for a variety of metrics.  This blog post shows you how to use AIautomation to optimize for both accuracy and for fairness as measuredby disparate impact.This blog post is generated from a [Jupyter](https://jupyter.org/)notebook that uses the following open-source Python libraries. [AIF360](https://aif360.mybluemix.net/) is a collection of fairness metrics and bias mitigation algorithms.The [pandas](https://pandas.pydata.org/),[scikit-learn](https://scikit-learn.org/), and[XGBoost](https://xgboost.ai/) libraries supportdata analysis and machine learning with data structures and acomprehensive collection of AI algorithms.The [hyperopt](http://hyperopt.github.io/hyperopt/) libraryimplements both algorithm selection and hyperparameter tuning forAI automation.And [Lale](https://github.com/IBM/lale) is a library forsemi-automated data science; this blog post uses Lale as the backbonefor putting the other libraries together.Our starting point is a dataset and a task. For illustrationpurposes, we picked [credit-g](https://www.openml.org/d/31), alsoknown as the German Credit dataset. Each row describes a personusing several features that may help evaluate them as a potentialloan applicant. The task is to classify people into eithergood or bad credit risks. We will use AIF360 to load the dataset.<jupyter_code>import aif360.datasets
import warnings
warnings.filterwarnings('ignore', category=FutureWarning)
creditg = aif360.datasets.GermanDataset()
print(f'labels: {creditg.label_names}, '
      f'protected attributes: {creditg.protected_attribute_names}')<jupyter_output>labels: ['credit'], protected attributes: ['sex', 'age']<jupyter_text>AIF360 datasets carry some fairness-related metadata.The credit-g dataset has a single label, credit, to be predictedas the outcome.A _protected attribute_ is a feature that partitions thepopulation into groups whose outcome should have parity. Thecredit-g dataset has two protected attributes, sex and age.Before we look at how to train a classifier thatis optimized for both accuracy and disparate impact, we will set a baseline,by training a classifier that is only optimized for accuracy.For a realistic assessment of how well the model generalizes, wefirst split the data into a training set and a test set. Then,to be able to use algorithms from the popular scikit-learn library,we convert the data from the AIF360 representation to a Pandasrepresentation.<jupyter_code>from lale.lib.aif360 import dataset_to_pandas
train_ds, test_ds = creditg.split([0.7], shuffle=True, seed=42)
train_X, train_y = dataset_to_pandas(train_ds)
test_X, test_y = dataset_to_pandas(test_ds)<jupyter_output><empty_output><jupyter_text>Next, we will import a few  algorithms from scikit-learnand XGBoost:a dimensionality reduction transformer (PCA) and three classifiers (logistic regression, gradient boosting, and asupport vector machine).<jupyter_code>from sklearn.decomposition import PCA
from sklearn.linear_model import LogisticRegression as LR
from xgboost import XGBClassifier as XGBoost
from sklearn.svm import LinearSVC as SVM<jupyter_output><empty_output><jupyter_text>To use AI Automation, we need to define a _search space_ ,which is a set of possible machine learning pipelines andtheir associated hyperparameters. The following codeuses Lale to define a search space.<jupyter_code>from lale.lib.lale import NoOp
import lale
lale.wrap_imported_operators()
planned_orig = (PCA | NoOp) >> (LR | XGBoost | SVM)
planned_orig.visualize()<jupyter_output><empty_output><jupyter_text>The call to `wrap_imported_operators` augments the algorithmsthat were imported from scikit-learn with metadata abouttheir hyperparameters. The Lale combinator `|` indicatesalgorithmic choice. For example, `(PCA | NoOp)` indicates thatit is up to the AI Automation to decide whether to apply a PCAtransformer or whether to use a no-op transformer that leavesthe data unchanged. Note that the `PCA` itself is not configuredwith concrete hyperparameters, since those will be left for theAI automation to choose instead. Finally, the Lale combinator`>>` pipes the output from the transformer into the input to theclassifier, which is itself a choice between `(LR | XGBoost | SVM)`.The search space is encapsulated in the object `planned_orig`.We will use hyperopt to select the algorithms and to tune theirhyperparameters. Lale provides a `Hyperopt` thatturns a search space such as the one specified above into anoptimization problem for hyperopt. After 25 trials, we get backthe model that performed best for the default optimizationobjective, which is accuracy.<jupyter_code>from lale.lib.lale import Hyperopt
best_estimator = planned_orig.auto_configure(
    train_X, train_y, optimizer=Hyperopt, cv=3, max_evals=10)
best_estimator.visualize()<jupyter_output>100%|███████| 10/10 [00:35<00:00,  3.67s/trial, best loss: -0.7371030654292458]<jupyter_text>As shown by the visualization, the search found a pipelinewith a PCA transformer and an SVM classifier.Inspecting the hyperparameters reveals which valuesworked best for the 25 trials on the dataset at hand.<jupyter_code>best_estimator.pretty_print(ipython_display=True, show_imports=False)<jupyter_output><empty_output><jupyter_text>We can use the accuracy score metric from scikit-learn to measurehow well the pipeline accomplishes the objective for which itwas trained.<jupyter_code>import sklearn.metrics
accuracy_scorer = sklearn.metrics.make_scorer(sklearn.metrics.accuracy_score)
print(f'accuracy {accuracy_scorer(best_estimator, test_X, test_y):.1%}')<jupyter_output>accuracy 75.7%<jupyter_text>The accuracy is close to the state of the art for this dataset.However, we would like our model to be not just accurate but also fair.As discussed before, we will use disparate impact as the fairness metric.To configure the metric, we need some fairness-related metadata.<jupyter_code>import lale.lib.aif360
fairness_info = lale.lib.aif360.dataset_fairness_info(creditg)
fairness_info<jupyter_output><empty_output><jupyter_text>For illustrative purposes, we will only look at one of the protected attributes,sex, which in this dataset is encoded as 0 for female and 1 for male.<jupyter_code>fairness_info['protected_attribute_names'] = ['sex']
fairness_info['unprivileged_groups'] = [{'sex': 0.0}]
fairness_info['privileged_groups'] = [{'sex': 1.0}]<jupyter_output><empty_output><jupyter_text>We will use the disparate impact metric from AIF360, wrapped forcompatibility with scikit-learn.<jupyter_code>import aif360.metrics
disparate_impact_scorer = lale.lib.aif360.disparate_impact(**fairness_info)
print(f'disparate impact {disparate_impact_scorer(best_estimator, test_X, test_y):.2f}')<jupyter_output>disparate impact 0.72<jupyter_text>The disparate impact for this model is 0.7, which is far fromthe ideal value for this metric, which is 1.0. We would prefer amodel that is much more fair. The AIF360 toolkit provides severalalgorithms for mitigating fairness problems. One of them is`DisparateImpactRemover`, which modifies the features that arenot the protected attribute in such a way that it is hard topredict the protected attribute from them. We use a Lale versionof `DisparateImpactRemover` that wraps the corresponding AIF360algorithm for AI Automation. This algorithm has a hyperparameter`repair_level` that we will tune with hyperparameter optimization.<jupyter_code>from lale.lib.aif360 import DisparateImpactRemover
DisparateImpactRemover.hyperparam_schema('repair_level')<jupyter_output><empty_output><jupyter_text>We compose the bias mitigation algorithm in a pipeline witha projection operator that strips out the projectedattribute, followed by a choice of classifiers as before.In the visualization, light blue indicates trainable operatorsand dark blue indicates that automation must make a choice beforethe operators can be trained. Compared to the earlier pipeline,we omit the PCA, to make it easier for the estimator to disregardfeatures that cause poor disparate impact.<jupyter_code>from lale.lib.lale import Project
dimr = DisparateImpactRemover(sensitive_attribute='sex')
proj = Project(columns=[i for i, name in enumerate(creditg.feature_names)
                        if name != 'sex'])
planned_fairer = dimr >> proj >> (LR | XGBoost | SVM)
planned_fairer.visualize()<jupyter_output><empty_output><jupyter_text>Unlike accuracy, which is a metric that can be computed frompredicted labels alone, fairness metrics such as disparateimpact need to look not just at labels but also at features.For instance, disparate impact is defined by comparing outcomesbetween a privileged group and an unprivileged group, so itneeds to check the protected attribute to determine groupmembership for the sample person at hand. In order to use Lale Hyperopt for thiscase, we will have to define a custom scorer that takes the disparate impact into accountalong with accuracy. Hyperopt minimizes `(best_score - score_returned_by_the_scorer)`, where `best_score` is an argument to Hyperopt and `score_returned_by_the_scorer` is the value returned by the scorer for eachevaluation point. In the custom scorer defined below, if the disparate impact is outside of  a margin of 10% around its ideal of 1, the score is -99, a low value. Otherwise, the score is accuracy, higher is better.<jupyter_code>def combined_scorer(estimator, X, y):
    accuracy = accuracy_scorer(estimator, X, y)
    disparate_impact = disparate_impact_scorer(estimator, X, y)
    if disparate_impact < 0.9 or 1.1 < disparate_impact:
        return -99
    else:
        return accuracy<jupyter_output><empty_output><jupyter_text>Now, we have all the pieces in place to use AI Automationon our `planned_fairer` pipeline for both accuracy anddisparate impact.<jupyter_code>import logging
logging.getLogger('lale.lib.lale.hyperopt').setLevel(logging.WARNING)
trained_fairer = planned_fairer.auto_configure(
    train_X, train_y, optimizer=Hyperopt, cv=3,
    max_evals=10, scoring=combined_scorer, best_score=1.0)<jupyter_output>100%|████████| 10/10 [02:58<00:00, 17.01s/trial, best loss: 0.2600418179817322]<jupyter_text>As with any trained model, we can evaluate and visualize the result.<jupyter_code>print(f'accuracy {accuracy_scorer(trained_fairer, test_X, test_y):.1%}')
print(f'disparate impact {disparate_impact_scorer(trained_fairer, test_X, test_y):.2f}')
trained_fairer.visualize()<jupyter_output>accuracy 76.0%
disparate impact 0.99<jupyter_text>As the result demonstrates, the best model found by AI Automationhas similar accuracy and better disparate impact as the one we sawbefore. Also, it has tuned the repair level andhas picked and tuned a classifier.<jupyter_code>trained_fairer.pretty_print(ipython_display=True, show_imports=False)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.05906691396775574, 'fraction_numerical': 0.013554001997431873, 'mean_word_length': 6.03411941796287, 'pattern_counts': {'":': 0, '<': 63, '<?xml version=': 0, '>': 67, 'https://': 7, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '16', 'dir': 'jupyter-structured-clean-dedup', 'id': '442982', 'license': 'Apache-2.0', 'n_tokens_mistral': 4051, 'n_tokens_neox': 3741, 'n_words': 1639, 'path': 'examples/demo_aif360.ipynb', 'repo_name': 'ariffyasri/lale', 'size': '14018'}