In [0]:
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

Introducción a la equidad en el AA


Renuncia de responsabilidad

Este ejercicio explora solo un pequeño subconjunto de ideas y técnicas relevantes para la equidad en el aprendizaje automático, pero el contenido no es exhaustivo.

Objetivos de aprendizaje

  • Aumentar el conocimiento sobre los diferentes tipos de sesgo que se pueden manifestar en los datos del modelo
  • Explorar los datos de los atributos para identificar de forma proactiva las posibles fuentes de sesgo antes de entrenar un modelo
  • Evaluar el rendimiento del modelo por subgrupo y no en conjunto

Descripción general

Aquí explorarás conjuntos de datos y evaluarás los clasificadores teniendo en cuenta la equidad y los modos en que los sesgos no deseados pueden afectar el aprendizaje automático (AA).

Durante todo el ejercicio verás tareas de FairAware, que brindan oportunidades para contextualizar los procesos de AA con respecto a la equidad. Cuando completes estas tareas, identificarás los sesgos y considerarás el impacto a largo plazo que pueden sufrir las predicciones del modelo cuando no se abordan estos sesgos.

Acerca del conjunto de datos y las tareas de predicción

En este ejercicio, trabajarás con el conjunto de datos del Censo de ingresos por adulto, que se usa comúnmente en la bibliografía del aprendizaje automático. Estos datos fueron extraídos de la base de datos de la Oficina del Censo de 1994 por Ronny Kohavi y Barry Becker.

Cada ejemplo del conjunto de datos incluye los siguientes datos demográficos de un grupo de individuos que participó en el Censo de 1994:

Atributos numéricos

  • age: La edad de los individuos en años
  • fnlwgt: La cantidad de individuos que representa este conjunto de observaciones, según las Organizaciones del Censo
  • education_num: Una enumeración de la representación categórica del nivel de educación. Cuanto más alto el número, más alto el nivel de educación alcanzado por el individuo. Por ejemplo, un valor de 11 en education_num equivale a Assoc_voc (diploma universitario de dos años en una escuela vocacional), un valor de 13 equivale a Bachelors (licenciatura) y un valor de 9 equivale a HS-grad (egresado de la preparatoria)
  • capital_gain: Ganancia de capital del individuo, representada en dólares estadounidenses
  • capital_loss: Pérdida de capital del individuo, representada en dólares estadounidenses
  • hours_per_week: Horas de trabajo por semana

Atributos categóricos

  • workclass: El tipo de empleador del individuo. Algunos ejemplos son Private, Self-emp-not-inc, Self-emp-inc, Federal-gov, Local-gov, State-gov, Without-pay y Never-worked
  • education: El nivel más alto de educación alcanzado por el individuo
  • marital_status: El estado civil del individuo. Algunos ejemplos son Married-civ-spouse, Divorced, Never-married, Separated, Widowed, Married-spouse-absent y Married-AF-spouse
  • occupation: La ocupación del individuo. Algunos ejemplos son tech-support, Craft-repair, Other-service, Sales y Exec-managerial, entre otros
  • relationship: La relación de cada individuo en el hogar. Algunos ejemplos son Wife, Own-child, Husband, Not-in-family, Other-relative y Unmarried
  • gender: El género del individuo, disponible únicamente en opciones binarias: Female o Male
  • race: White, Asian-Pac-Islander, Amer-Indian-Eskimo, Black y Other
  • native_country: País de origen del individuo. Algunos ejemplos son United-States, Cambodia, England, Puerto-Rico, Canada, Germany, Outlying-US(Guam-USVI-etc), India, Japan, United-States, Cambodia, England, Puerto-Rico, Canada, Germany, Outlying-US(Guam-USVI-etc), India y Japan, entre otros

Tarea de predicción

La tarea de predicción consiste en determinar si una persona gana más de $50,000 dólares al año.

Etiqueta

  • income_bracket: Si la persona gana más de $50,000 dólares al año

Notas sobre el conjunto de datos

Todos los ejemplos extraídos para este conjunto de datos cumplen con las siguientes condiciones:

  • age es 16 años o más.
  • El ingreso bruto ajustado (que se usa para calcular income_bracket) es mayor que USD 100 al año.
  • fnlwgt es mayor que 0.
  • hours_per_week es mayor que 0.

Configuración

Primero, importa algunos módulos para usarlos en este bloc de notas.


In [0]:
import os
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import tensorflow as tf
import tempfile
!pip install seaborn==0.8.1
import seaborn as sns
import itertools
from sklearn.metrics import confusion_matrix
from sklearn.metrics import roc_curve, roc_auc_score
from sklearn.metrics import precision_recall_curve
from google.colab import widgets
# For facets
from IPython.core.display import display, HTML
import base64
!pip install facets-overview==1.0.0
from facets_overview.feature_statistics_generator import FeatureStatisticsGenerator

print('Modules are imported.')

Carga del conjunto de datos Adulto

Con los módulos importados, podemos cargar el conjunto de datos Adulto en la estructura de datos DataFrame de Pandas.


In [0]:
COLUMNS = ["age", "workclass", "fnlwgt", "education", "education_num",
           "marital_status", "occupation", "relationship", "race", "gender",
           "capital_gain", "capital_loss", "hours_per_week", "native_country",
           "income_bracket"]

train_df = pd.read_csv(
    "https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.data",
    names=COLUMNS,
    sep=r'\s*,\s*',
    engine='python',
    na_values="?")
test_df = pd.read_csv(
    "https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.test",
    names=COLUMNS,
    sep=r'\s*,\s*',
    skiprows=[0],
    engine='python',
    na_values="?")

# Drop rows with missing values
train_df = train_df.dropna(how="any", axis=0)
test_df = test_df.dropna(how="any", axis=0)

print('UCI Adult Census Income dataset loaded.')

Análisis del conjunto de datos Adulto con Facets

Como se dijo en el Curso intensivo de aprendizaje automático, es importante entender el conjunto de datos antes de emprender la tarea de predicción.

Estas son algunas preguntas importantes que se deben investigar cuando se audita un conjunto de datos para ver si hay equidad:

  • ¿Faltan valores de atributos para una gran cantidad de observaciones?
  • ¿Faltan atributos que podrían afectar otros atributos?
  • ¿Hay valores inesperados de los atributos?
  • ¿Qué señales de distorsión de datos puedes observar?

Para empezar, podemos usar Facets Overview, una herramienta de visualización interactiva que puede ayudarnos a explorar el conjunto de datos. Con Facets Overview, podemos analizar rápidamente la distribución de los valores a lo largo del conjunto de datos Adulto.


In [0]:
#@title Visualize the Data in Facets
fsg = FeatureStatisticsGenerator()
dataframes = [
    {'table': train_df, 'name': 'trainData'}]
censusProto = fsg.ProtoFromDataFrames(dataframes)
protostr = base64.b64encode(censusProto.SerializeToString()).decode("utf-8")


HTML_TEMPLATE = """<script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.3.3/webcomponents-lite.js"></script>
        <link rel="import" href="https://raw.githubusercontent.com/PAIR-code/facets/1.0.0/facets-dist/facets-jupyter.html">
        <facets-overview id="elem"></facets-overview>
        <script>
          document.querySelector("#elem").protoInput = "{protostr}";
        </script>"""
html = HTML_TEMPLATE.format(protostr=protostr)
display(HTML(html))

Tarea 1 de FairAware

Revisa las estadísticas descriptivas y los histogramas de cada atributo numérico y continuo. Para ver la distribución de los valores por categoría, haz clic en el botón Show Raw Data (Mostrar datos sin procesar) sobre los histogramas de atributos categóricos.

Luego, intenta responder las preguntas anteriores:

  1. ¿Faltan valores de atributos para una gran cantidad de observaciones?
  2. ¿Faltan atributos que podrían afectar otros atributos?
  3. ¿Hay valores inesperados de los atributos?
  4. ¿Qué señales de distorsión de datos puedes observar?

Solución

Para ver la información que descubrimos, haz clic a continuación.

Tras revisar las columnas faltantes de los atributos tanto numéricos como categóricos, podemos observar que no faltan valores de atributos; por lo tanto, no es una preocupación en este caso.

Cuando vemos los valores mín. y máx. y los histogramas de cada atributo numérico, podemos identificar cualquier valor atípico extremo en nuestro conjunto de datos. Para el atributo hours_per_week, podemos ver que el mínimo es 1; eso podría ser una sorpresa si se tiene en cuenta que, por lo general, la mayoría de los empleos requiere varias horas de trabajo por semana. Para los atributos capital_gain y capital_loss, vemos que más del 90% de los valores es 0. Dado que las ganancias/pérdidas de capital solo las registran los individuos que hacen inversiones, sin duda es posible que menos del 10% de los ejemplos tengan valores distintos de cero en esos atributos, pero tenemos que observar con más atención para verificar que los valores de esos atributos sean válidos.

Cuando miramos el histograma del género, vemos que más de dos tercios (aproximadamente el 67%) de los ejemplos representan a los hombres. Esto sugiere claramente que existe una distorsión de datos, porque se esperaría que la distribución de datos entre los géneros se acercara al 50/50.

Análisis exhaustivo

Para explorar mejor el conjunto de datos, podemos usar Facets Dive, una herramienta con interfaz interactiva en la que cada elemento individual de la visualización representa un punto de datos. Pero para usar Facets Dive, tenemos que convertir nuestros datos a una matriz JSON. Afortunadamente, el método DataFrame to_json() se encarga de esto por nosotros.

Para transformar los datos a JSON y cargar Facets Dive, ejecuta la celda a continuación.


In [0]:
#@title Set the Number of Data Points to Visualize in Facets Dive

SAMPLE_SIZE = 2500 #@param
  
train_dive = train_df.sample(SAMPLE_SIZE).to_json(orient='records')

HTML_TEMPLATE = """<script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.3.3/webcomponents-lite.js"></script>
        <link rel="import" href="https://raw.githubusercontent.com/PAIR-code/facets/1.0.0/facets-dist/facets-jupyter.html">
        <facets-dive id="elem" height="600"></facets-dive>
        <script>
          var data = {jsonstr};
          document.querySelector("#elem").data = data;
        </script>"""
html = HTML_TEMPLATE.format(jsonstr=train_dive)
display(HTML(html))

Tarea 2 de FairAware

Usa los menús del panel de visualización de la izquierda para cambiar la organización de los datos:

  1. En el menú Faceting | X-Axis, selecciona education y, luego, en los menús Display | Color y Display | Type, selecciona income_bracket. ¿Cómo describirías la relación entre el nivel de educación y la categoría de ingresos?

  2. Luego, en el menú Faceting | X-Axis, selecciona marital_status y, en los menús Display | Color y Display | Type, selecciona gender. ¿Qué observación notable puedes hacer sobre la distribución de género en cada categoría marital-status?

A medida que completes estas tareas, recuerda las siguientes preguntas sobre equidad:

  • ¿Qué falta?
  • ¿Qué está sobregeneralizado?
  • ¿Qué está subrepresentado?
  • ¿Cómo reflejan las variables y sus valores el mundo real?
  • ¿Qué podríamos estar ignorando?

Solución

Para ver la información que descubrimos, haz clic a continuación.

  1. Por lo general, en nuestro conjunto de datos, el nivel de educación superior tiende a correlacionarse con la categoría de ingresos más altos. Un nivel de ingresos superior a USD 50,000 está más representado en los ejemplos en los que el nivel de educación es de licenciatura o superior.

  2. En la mayoría de las categorías de estado civil, la distribución entre hombres y mujeres se acerca a 1:1. La excepción notable es el caso de "married-civ-spouse", en el que los hombres superan a las mujeres en más de 5:1. Si se tiene en cuenta que en la Tarea 1 ya descubrimos que hay una representación excesiva de hombres en nuestro conjunto de datos, ahora podemos inferir que específicamente las mujeres casadas son las que están subrepresentadas en nuestros datos.

Resumen

Representar los histogramas, clasificar los ejemplos comunes de mayor a menor, identificar ejemplos duplicados o faltantes, garantizar que los conjuntos de entrenamiento y pruebas sean similares y calcular los cuantiles de los atributos son tareas fundamentales para el análisis de los datos.

Mientras mejor sepas lo que sucede con tus datos, más información tendrás para identificar casos de falta de equidad.

Tarea 3 de FairAware

Ahora que exploraste el conjunto de datos con Facets, averigua si puedes identificar algunos de los problemas que podrían surgir con la equidad a partir de lo que aprendiste sobre sus atributos.

¿Cuál de los siguientes atributos representa un problema en cuanto a la equidad?

Selecciona un atributo de la lista desplegable de opciones en la celda a continuación y ejecuta la celda para verificar tu respuesta. Luego, explora las demás opciones para obtener más información sobre cómo influye cada una en los modelos de predicciones.


In [0]:
feature = 'capital_gain / capital_loss' #@param ["", "hours_per_week", "fnlwgt", "gender", "capital_gain / capital_loss", "age"] {allow-input: false}


if feature == "hours_per_week":
  print(
'''It does seem a little strange to see 'hours_per_week' max out at 99 hours,
which could lead to data misrepresentation. One way to address this is by
representing 'hours_per_week' as a binary "working 40 hours/not working 40
hours" feature. Also keep in mind that data was extracted based on work hours
being greater than 0. In other words, this feature representation exclude a
subpopulation of the US that is not working. This could skew the outcomes of the
model.''')
if feature == "fnlwgt":
  print(
"""'fnlwgt' represents the weight of the observations. After fitting the model
to this data set, if certain group of individuals end up performing poorly 
compared to other groups, then we could explore ways of reweighting each data 
point using this feature.""")
if feature == "gender":
  print(
"""Looking at the ratio between men and women shows how disproportionate the data
is compared to the real world where the ratio (at least in the US) is closer to
1:1. This could pose a huge probem in performance across gender. Considerable
measures may need to be taken to upsample the underrepresented group (in this
case, women).""")
if feature == "capital_gain / capital_loss":
  print(
"""Both 'capital_gain' and 'capital_loss' have very low variance, which might
suggest they don't contribute a whole lot of information for predicting income. It
may be okay to omit these features rather than giving the model more noise.""")
if feature == "age":
  print(
'''"age" has a lot of variance, so it might benefit from bucketing to learn
fine-grained correlations between income and age, as well as to prevent
overfitting.''')

Predicciones con estimadores de TensorFlow

Ahora que tenemos una mejor idea del conjunto de datos Adulto, podemos empezar a crear una red neuronal para predecir los ingresos. En esta sección, usaremos la API de Estimator de TensorFlow para acceder a la clase DNNClassifier.

Conversión del conjunto de datos Adulto en tensores

Primero, debemos definir nuestra función de entrada, que tomará el conjunto de datos Adulto del DataFrame de Pandas y lo convertirá en tensores con la función tf.estimator.inputs.pandas_input_fn().


In [0]:
def csv_to_pandas_input_fn(data, batch_size=100, num_epochs=1, shuffle=False):
  return tf.estimator.inputs.pandas_input_fn(
      x=data.drop('income_bracket', axis=1),
      y=data['income_bracket'].apply(lambda x: ">50K" in x).astype(int),
      batch_size=batch_size,
      num_epochs=num_epochs,
      shuffle=shuffle,
      num_threads=1)

print('csv_to_pandas_input_fn() defined.')

Representación de los atributos en TensorFlow

TensorFlow necesita que los datos se representen en un modelo. Para lograr esto, debes usar tf.feature_columns para ingresar y representar los atributos en TensorFlow.


In [0]:
#@title Categorical Feature Columns

# Since we don't know the full range of possible values with occupation and
# native_country, we'll use categorical_column_with_hash_bucket() to help map
# each feature string into an integer ID.
occupation = tf.feature_column.categorical_column_with_hash_bucket(
    "occupation", hash_bucket_size=1000)
native_country = tf.feature_column.categorical_column_with_hash_bucket(
    "native_country", hash_bucket_size=1000)

# For the remaining categorical features, since we know what the possible values
# are, we can be more explicit and use categorical_column_with_vocabulary_list()
gender = tf.feature_column.categorical_column_with_vocabulary_list(
    "gender", ["Female", "Male"])
race = tf.feature_column.categorical_column_with_vocabulary_list(
    "race", [
        "White", "Asian-Pac-Islander", "Amer-Indian-Eskimo", "Other", "Black"
    ])
education = tf.feature_column.categorical_column_with_vocabulary_list(
    "education", [
        "Bachelors", "HS-grad", "11th", "Masters", "9th",
        "Some-college", "Assoc-acdm", "Assoc-voc", "7th-8th",
        "Doctorate", "Prof-school", "5th-6th", "10th", "1st-4th",
        "Preschool", "12th"
    ])
marital_status = tf.feature_column.categorical_column_with_vocabulary_list(
    "marital_status", [
        "Married-civ-spouse", "Divorced", "Married-spouse-absent",
        "Never-married", "Separated", "Married-AF-spouse", "Widowed"
    ])
relationship = tf.feature_column.categorical_column_with_vocabulary_list(
    "relationship", [
        "Husband", "Not-in-family", "Wife", "Own-child", "Unmarried",
        "Other-relative"
    ])
workclass = tf.feature_column.categorical_column_with_vocabulary_list(
    "workclass", [
        "Self-emp-not-inc", "Private", "State-gov", "Federal-gov",
        "Local-gov", "?", "Self-emp-inc", "Without-pay", "Never-worked"
    ])

print('Categorical feature columns defined.')

In [0]:
#@title Numeric Feature Columns
# For Numeric features, we can just call on feature_column.numeric_column()
# to use its raw value instead of having to create a map between value and ID.
age = tf.feature_column.numeric_column("age")
fnlwgt = tf.feature_column.numeric_column("fnlwgt")
education_num = tf.feature_column.numeric_column("education_num")
capital_gain = tf.feature_column.numeric_column("capital_gain")
capital_loss = tf.feature_column.numeric_column("capital_loss")
hours_per_week = tf.feature_column.numeric_column("hours_per_week")

print('Numeric feature columns defined.')

Creación de un atributo categórico de edad

Si seleccionaste age cuando completaste la Tarea 3 de FairAware, notaste que sugerimos que el atributo age podría beneficiarse del agrupamiento (también conocido como discretización), es decir, de agrupar edades similares en grupos diferentes. Esto podría ayudar al modelo a generalizar mejor la edad. Entonces, convertimos el atributo numérico age (técnicamente, un atributo ordinal) en un atributo categórico.


In [0]:
age_buckets = tf.feature_column.bucketized_column(
    age, boundaries=[18, 25, 30, 35, 40, 45, 50, 55, 60, 65])

Consideración de los subgrupos clave

Cuando se realiza la ingeniería de atributos, es importante recordar que esta podría trabajar con datos extraídos de individuos pertenecientes a subgrupos. En tal caso, es posible que quieras evaluar el rendimiento del modelo de forma independiente.

NOTA: En este contexto, un subgrupo se define como un grupo de individuos que comparten una característica determinada, como la raza, el género o la orientación sexual, que amerita una consideración especial cuando se evalúa un modelo teniendo en cuenta la equidad.

Cuando queremos que nuestros modelos mitiguen o aprovechen las señales aprendidas de una característica correspondiente a un subgrupo, usaremos diferentes tipos de herramientas y técnicas; en este punto, la mayoría siguen siendo investigaciones abiertas.

Mientras trabajas con diferentes variables y defines tareas para ellas, podría ser útil pensar en lo que sigue. Por ejemplo, ¿en qué puntos la interacción entre la variable y la tarea podría ser una preocupación?

Definición de los atributos del modelo

Ahora podemos definir explícitamente los atributos que incluiremos en nuestro modelo.

Consideraremos gender como un subgrupo y lo guardaremos en una lista de subgroup_variables separada a fin de poder agregarle manejos especiales si hiciera falta.


In [0]:
# List of variables, with special handling for gender subgroup.
variables = [native_country, education, occupation, workclass,
             relationship, age_buckets]
subgroup_variables = [gender]
feature_columns = variables + subgroup_variables

Entrenamiento de un modelo de redes neuronales profundas en el conjunto de datos Adulto

Ahora que los atributos están listos, podemos intentar predecir los ingresos con el aprendizaje profundo.

Para simplificar, vamos a definir simplemente una red neuronal de redirección de feed con dos capas ocultas para mantener liviana la arquitectura de la red neuronal.

Sin embargo, antes debemos convertir nuestros atributos categóricos altamente dimensionales en un vector denso con baja dimensión y valores reales al que llamamos vector de incorporación. Los valores de indicator_column (que se puede considerar como una codificación de un solo 1) y embedding_column (que convierte los atributos dispersos en densos) nos ayudan a agilizar el proceso.

Las siguientes celdas crean las columnas profundas que se necesitan para avanzar en la definición del modelo.


In [0]:
deep_columns = [
    tf.feature_column.indicator_column(workclass),
    tf.feature_column.indicator_column(education),
    tf.feature_column.indicator_column(age_buckets),
    tf.feature_column.indicator_column(gender),
    tf.feature_column.indicator_column(relationship),
    tf.feature_column.embedding_column(native_country, dimension=8),
    tf.feature_column.embedding_column(occupation, dimension=8),
]

print(deep_columns)
print('Deep columns created.')

Después de haber realizado el procesamiento previo de todos nuestros datos, ahora podemos definir el modelo de nuestra red neuronal profunda. Comencemos con los parámetros que se definen a continuación. (Después de definir las métricas de evaluación y evaluar el modelo, puedes regresar y ajustar los parámetros para comparar los resultados).


In [0]:
#@title Define Deep Neural Net Model

HIDDEN_UNITS = [1024, 512] #@param
LEARNING_RATE = 0.1 #@param
L1_REGULARIZATION_STRENGTH = 0.0001 #@param
L2_REGULARIZATION_STRENGTH = 0.0001 #@param

model_dir = tempfile.mkdtemp()
single_task_deep_model = tf.estimator.DNNClassifier(
    feature_columns=deep_columns,
    hidden_units=HIDDEN_UNITS,
    optimizer=tf.train.ProximalAdagradOptimizer(
      learning_rate=LEARNING_RATE,
      l1_regularization_strength=L1_REGULARIZATION_STRENGTH,
      l2_regularization_strength=L2_REGULARIZATION_STRENGTH),
    model_dir=model_dir)

print('Deep neural net model defined.')

Para que sea más simple, realizaremos el entrenamiento con 1,000 pasos, pero puedes modificar este parámetro.


In [0]:
#@title Fit Deep Neural Net Model to the Adult Training Dataset

STEPS = 1000 #@param

single_task_deep_model.train(
    input_fn=csv_to_pandas_input_fn(train_df, num_epochs=None, shuffle=True),
    steps=STEPS);

print('Deep neural net model is done fitting.')

Ahora podemos evaluar el rendimiento general del modelo con el conjunto de datos de prueba retenido.


In [0]:
#@title Evaluate Deep Neural Net Performance

results = single_task_deep_model.evaluate(
    input_fn=csv_to_pandas_input_fn(test_df, num_epochs=1, shuffle=False),
    steps=None)
print("model directory = %s" % model_dir)
print("---- Results ----")
for key in sorted(results):
  print("%s: %s" % (key, results[key]))

Puedes intentar volver a entrenar el modelo con parámetros diferentes. Al final, descubrirás que una red neuronal profunda puede predecir los ingresos de manera aceptable.

Sin embargo, aquí falta la métrica de evaluación para los subgrupos. En la próxima sección, veremos algunas de las formas en las que puedes evaluar el nivel de subgrupo.

Evaluación de equidad con una matriz de confusión

Si bien la evaluación del rendimiento general del modelo nos brinda información sobre su calidad, no nos proporciona muchos datos sobre el rendimiento de nuestro modelo en diferentes subgrupos.

Cuando evaluamos la equidad del modelo, es importante determinar si los errores de predicción son uniformes en todos los subgrupos o si ciertos subgrupos son más susceptibles a determinados errores de predicción que otros.

Una herramienta clave para comparar la prevalencia de los diferentes tipos de errores de modelos es una matriz de confusión. Como recordarás del Módulo de clasificación del Curso intensivo de aprendizaje automático, una matriz de confusión es una cuadrícula que representa las predicciones del modelo en comparación con los datos reales y tabula las estadísticas, para resumir con qué frecuencia la predicción de tu modelo fue correcta, y con qué frecuencia fue incorrecta.

Comencemos por crear una matriz de confusión para nuestro modelo de predicción de ingresos, que será binaria porque nuestra etiqueta (income_bracket) solo tiene dos valores posibles (<50K o >50K). Definiremos un ingreso de >50K como etiqueta positiva y un ingreso de <50k como etiqueta negativa.

NOTA: En este contexto, positiva y negativa* no deben interpretarse como juicios de valor (no estamos sugiriendo que alguien que gana más de USD 50,000 al año es mejor persona que alguien que gana menos de USD 50,000). Se trata solo de términos estándar que se usan para distinguir las dos predicciones posibles que puede hacer el modelo.

Los casos en que el modelo hace una predicción correcta (la que coincide con los datos reales) se clasifican como verdaderos y los casos en que el modelo realiza una predicción incorrecta se clasifican como falsos.

Por lo tanto, la matriz de confusión representa cuatro estados posibles:

  • verdadero positivo: El modelo predice >50K, y eso coincide con los datos reales.
  • verdadero negativo: El modelo predice <50K, y eso coincide con los datos reales.
  • falso positivo: El modelo predice >50K, y eso contradice la realidad.
  • falso negativo: El modelo predice <50K, y eso contradice la realidad.

NOTA: Podemos usar la cantidad de resultados de cada uno de esos estados para calcular las métricas de evaluación secundaria, como precisión y recuperación.

Representación de la matriz de confusión

La siguiente celda define una función que usa el módulo sklearn.metrics.confusion_matrix para calcular todas las instancias (verdadero positivo, verdadero negativo, falso positivo y falso negativo) que se necesitan para computar nuestra matriz de confusión binaria y nuestras métricas de evaluación.


In [0]:
#@test {"output": "ignore"}
#@title Define Function to Compute Binary Confusion Matrix Evaluation Metrics
def compute_eval_metrics(references, predictions):
  tn, fp, fn, tp = confusion_matrix(references, predictions).ravel()
  precision = tp / float(tp + fp)
  recall = tp / float(tp + fn)
  false_positive_rate = fp / float(fp + tn)
  false_omission_rate = fn / float(tn + fn)
  return precision, recall, false_positive_rate, false_omission_rate

print('Binary confusion matrix and evaluation metrics defined.')

También necesitaremos ayuda para representar la matriz de confusión binaria. La función que se incluye a continuación combina varios módulos de terceros (DataFame de Pandas, Matplotlib, Seaborn) para dibujar la matriz de confusión.


In [0]:
#@title Define Function to Visualize Binary Confusion Matrix
def plot_confusion_matrix(confusion_matrix, class_names, figsize = (8,6)):
    # We're taking our calculated binary confusion matrix that's already in form 
    # of an array and turning it into a Pandas DataFrame because it's a lot 
    # easier to work with when visualizing a heat map in Seaborn.
    df_cm = pd.DataFrame(
        confusion_matrix, index=class_names, columns=class_names, 
    )
    fig = plt.figure(figsize=figsize)
    
    # Combine the instance (numercial value) with its description
    strings = np.asarray([['True Positives', 'False Negatives'],
                          ['False Positives', 'True Negatives']])
    labels = (np.asarray(
        ["{0:d}\n{1}".format(value, string) for string, value in zip(
            strings.flatten(), confusion_matrix.flatten())])).reshape(2, 2)

    heatmap = sns.heatmap(df_cm, annot=labels, fmt="");
    heatmap.yaxis.set_ticklabels(
        heatmap.yaxis.get_ticklabels(), rotation=0, ha='right')
    heatmap.xaxis.set_ticklabels(
        heatmap.xaxis.get_ticklabels(), rotation=45, ha='right')
    plt.ylabel('References')
    plt.xlabel('Predictions')
    return fig

print('Binary confusion matrix visualization defined.')

Ahora que definimos todas las funciones necesarias, podemos calcular la matriz de confusión binaria y las métricas de evaluación con los resultados de nuestro modelo de red neuronal profunda. El resultado de esta celda es una vista tabulada que nos permite alternar entre la matriz de confusión y la tabla de las métricas de evaluación.

Tarea 4 de FairAware

Usa el formulario a continuación para generar matrices de confusión para los dos subgrupos de género: Female y Male. Compara el número de falsos positivos y falsos negativos de cada subgrupo. ¿Hay alguna disparidad importante en las tasas de error que sugieran que el modelo funciona mejor para un subgrupo que para el otro?


In [0]:
#@title Visualize Binary Confusion Matrix and Compute Evaluation Metrics Per Subgroup
CATEGORY  =  "gender" #@param {type:"string"}
SUBGROUP =  "Male" #@param {type:"string"}

# Given define subgroup, generate predictions and obtain its corresponding 
# ground truth.
predictions_dict = single_task_deep_model.predict(input_fn=csv_to_pandas_input_fn(
    test_df.loc[test_df[CATEGORY] == SUBGROUP], num_epochs=1, shuffle=False))
predictions = []
for prediction_item, in zip(predictions_dict):
    predictions.append(prediction_item['class_ids'][0])
actuals = list(
    test_df.loc[test_df[CATEGORY] == SUBGROUP]['income_bracket'].apply(
        lambda x: '>50K' in x).astype(int))
classes = ['Over $50K', 'Less than $50K']

# To stay consistent, we have to flip the confusion 
# matrix around on both axes because sklearn's confusion matrix module by
# default is rotated.
rotated_confusion_matrix = np.fliplr(confusion_matrix(actuals, predictions))
rotated_confusion_matrix = np.flipud(rotated_confusion_matrix)

tb = widgets.TabBar(['Confusion Matrix', 'Evaluation Metrics'], location='top')

with tb.output_to('Confusion Matrix'):
  plot_confusion_matrix(rotated_confusion_matrix, classes);

with tb.output_to('Evaluation Metrics'):
  grid = widgets.Grid(2,4)

  p, r, fpr, fomr = compute_eval_metrics(actuals, predictions)

  with grid.output_to(0, 0):
    print(' Precision ')
  with grid.output_to(1, 0):
    print(' %.4f ' % p)

  with grid.output_to(0, 1):
    print(' Recall ')
  with grid.output_to(1, 1):
    print(' %.4f ' % r)

  with grid.output_to(0, 2):
    print(' False Positive Rate ')
  with grid.output_to(1, 2):
    print(' %.4f ' % fpr)

  with grid.output_to(0, 3):
    print(' False Omission Rate ')
  with grid.output_to(1, 3):
    print(' %.4f ' % fomr)

Solución

Para ver parte de las estadísticas que descubrimos, haz clic a continuación.

Con los parámetros predeterminados, verás que el modelo funciona mejor para "Male" (hombre) que para "Female" (mujer). Específicamente, en nuestra ejecución, descubrimos que tanto la precisión como la recuperación para los hombres (0.7490 y 0.4795, respectivamente) superaron a las de las mujeres (0.6787 y 0.3716, respectivamente).

Esperamos que, después de ver esta demostración de la matriz de confusión, halles que los resultados varían levemente respecto de las métricas generales de rendimiento y entiendas la importancia de evaluar el rendimiento del modelo en cada subgrupo en vez de en todo el conjunto.

En tu trabajo, asegúrate de tomar una buena decisión sobre las compensaciones entre falsos positivos, falsos negativos, verdaderos positivos y verdaderos negativos. Por ejemplo, tal vez quieras una tasa de falsos positivos baja, pero una tasa de verdaderos positivos alta. O tal vez quieras precisión alta, pero no te importa tener recuperación baja.

Elige tus métricas de evaluación en función de las compensaciones que desees.