Estudo de caso de correção de modelo

Neste bloco de notas, treinaremos um classificador de texto para identificar conteúdo escrito que possa ser considerado tóxico ou prejudicial e aplicaremos MinDiff para corrigir alguns problemas de justiça. Em nosso fluxo de trabalho, iremos:

  1. Avalie o desempenho do nosso modelo de linha de base em textos contendo referências a grupos sensíveis.
  2. Melhore o desempenho em qualquer grupo de baixo desempenho treinando com MinDiff.
  3. Avalie o desempenho do novo modelo em nossa métrica escolhida.

Nosso objetivo é demonstrar o uso da técnica MinDiff com um fluxo de trabalho mínimo, e não apresentar uma abordagem baseada em princípios para a justiça no aprendizado de máquina. Como tal, nossa avaliação se concentrará apenas em uma categoria sensível e uma única métrica. Também não abordamos possíveis deficiências no conjunto de dados nem ajustamos nossas configurações. Em um ambiente de produção, você gostaria de abordar cada um deles com rigor. Para mais informações sobre a avaliação de justiça, consulte este guia .

Configurar

Começamos instalando os indicadores de imparcialidade e a correção do modelo TensorFlow.

Instalações

Importe todos os componentes necessários, incluindo MinDiff e Fairness Indicators para avaliação.

Importações

Usamos uma função de utilidade para baixar os dados pré-processados ​​e preparar os rótulos para corresponder à forma de saída do modelo. A função também baixa os dados como TFRecords para agilizar a avaliação posterior. Alternativamente, você pode converter o Pandas DataFrame em TFRecords com qualquer função de conversão de utilitário disponível.

# We use a helper utility to preprocessed data for convenience and speed.
data_train, data_validate, validate_tfrecord_file, labels_train, labels_validate = min_diff_keras_utils.download_and_process_civil_comments_data()
Downloading data from https://storage.googleapis.com/civil_comments_dataset/train_df_processed.csv
345702400/345699197 [==============================] - 8s 0us/step
Downloading data from https://storage.googleapis.com/civil_comments_dataset/validate_df_processed.csv
229974016/229970098 [==============================] - 5s 0us/step
Downloading data from https://storage.googleapis.com/civil_comments_dataset/validate_tf_processed.tfrecord
324943872/324941336 [==============================] - 9s 0us/step

Definimos algumas constantes úteis. Vamos treinar o modelo na 'comment_text' recurso, com o nosso rótulo de destino como 'toxicity' . Observe que o tamanho do lote aqui é escolhido arbitrariamente, mas em uma configuração de produção você precisa ajustá-lo para obter o melhor desempenho.

TEXT_FEATURE = 'comment_text'
LABEL = 'toxicity'
BATCH_SIZE = 512

Defina sementes aleatórias. (Observe que isso não estabiliza totalmente os resultados.)

Sementes

Definir e treinar o modelo de linha de base

Para reduzir o tempo de execução, usamos um modelo pré-treinado por padrão. É um modelo sequencial Keras simples com camadas iniciais de incorporação e convolução, gerando uma previsão de toxicidade. Se preferir, você pode alterar isso e treinar do zero usando nossa função de utilidade para criar o modelo. (Observe que, como seu ambiente provavelmente é diferente do nosso, você precisaria personalizar os limites de ajuste e avaliação.)

use_pretrained_model = True

if use_pretrained_model:
  URL = 'https://storage.googleapis.com/civil_comments_model/baseline_model.zip'
  BASE_PATH = tempfile.mkdtemp()
  ZIP_PATH = os.path.join(BASE_PATH, 'baseline_model.zip')
  MODEL_PATH = os.path.join(BASE_PATH, 'tmp/baseline_model')

  r = requests.get(URL, allow_redirects=True)
  open(ZIP_PATH, 'wb').write(r.content)

  with zipfile.ZipFile(ZIP_PATH, 'r') as zip_ref:
    zip_ref.extractall(BASE_PATH)
  baseline_model = tf.keras.models.load_model(
      MODEL_PATH, custom_objects={'KerasLayer' : hub.KerasLayer})
else:
  optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)
  loss = tf.keras.losses.BinaryCrossentropy()

  baseline_model = min_diff_keras_utils.create_keras_sequential_model()

  baseline_model.compile(optimizer=optimizer, loss=loss, metrics=['accuracy'])

  baseline_model.fit(x=data_train[TEXT_FEATURE],
                     y=labels_train,
                     batch_size=BATCH_SIZE,
                     epochs=20)

Nós salvar o modelo para avaliar usando Fairness Indicadores .

base_dir = tempfile.mkdtemp(prefix='saved_models')
baseline_model_location = os.path.join(base_dir, 'model_export_baseline')
baseline_model.save(baseline_model_location, save_format='tf')
INFO:tensorflow:Assets written to: /tmp/saved_models867b8d74/model_export_baseline/assets
INFO:tensorflow:Assets written to: /tmp/saved_models867b8d74/model_export_baseline/assets

Em seguida, executamos os Indicadores de imparcialidade. Como um lembrete, nós apenas estamos indo para executar a avaliação cortado por comentários referenciando uma categoria, grupos religiosos. Em um ambiente de produção, recomendamos uma abordagem cuidadosa para determinar quais categorias e métricas devem ser avaliadas.

Para calcular o desempenho do modelo, a função de utilidade faz algumas escolhas convenientes para métricas, fatias e limites do classificador.

# We use a helper utility to hide the evaluation logic for readability.
base_dir = tempfile.mkdtemp(prefix='eval')
eval_dir = os.path.join(base_dir, 'tfma_eval_result')
eval_result = fi_util.get_eval_results(
    baseline_model_location, eval_dir, validate_tfrecord_file)
WARNING:absl:Tensorflow version (2.5.0) found. Note that TFMA support for TF 2.0 is currently in beta
WARNING:apache_beam.runners.interactive.interactive_environment:Dependencies required for Interactive Beam PCollection visualization are not available, please use: `pip install apache-beam[interactive]` to install necessary dependencies to enable all data visualization features.
WARNING:apache_beam.io.tfrecordio:Couldn't find python-snappy so the implementation of _TFRecordUtil._masked_crc32c is not as fast as it could be.
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow_model_analysis/writers/metrics_plots_and_validations_writer.py:113: tf_record_iterator (from tensorflow.python.lib.io.tf_record) is deprecated and will be removed in a future version.
Instructions for updating:
Use eager execution and: 
`tf.data.TFRecordDataset(path)`
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow_model_analysis/writers/metrics_plots_and_validations_writer.py:113: tf_record_iterator (from tensorflow.python.lib.io.tf_record) is deprecated and will be removed in a future version.
Instructions for updating:
Use eager execution and: 
`tf.data.TFRecordDataset(path)`

Renderizar resultados da avaliação

widget_view.render_fairness_indicator(eval_result)
FairnessIndicatorViewer(slicingMetrics=[{'sliceValue': 'Overall', 'slice': 'Overall', 'metrics': {'accuracy': …

Vejamos os resultados da avaliação. Tente selecionar a taxa de falsos positivos métricos (FPR) com limite 0,450. Podemos ver que o modelo não funciona tão bem para alguns grupos religiosos quanto para outros, apresentando um FPR muito maior. Observe os amplos intervalos de confiança em alguns grupos porque eles têm poucos exemplos. Isso torna difícil dizer com certeza que há uma diferença significativa no desempenho dessas fatias. Podemos querer coletar mais exemplos para resolver esse problema. Podemos, no entanto, tentar aplicar MinDiff para os dois grupos que estamos confiantes de que estão com desempenho inferior.

Optamos por focar no FPR, porque um FPR mais alto significa que os comentários que fazem referência a esses grupos de identidade são mais propensos a serem sinalizados incorretamente como tóxicos do que outros comentários. Isso pode levar a resultados desiguais para os usuários que dialogam sobre religião, mas observe que as disparidades em outras métricas podem levar a outros tipos de danos.

Definir e treinar o modelo MinDiff

Agora, tentaremos melhorar o FPR para grupos religiosos de baixo desempenho. Nós vamos tentar fazê-lo usando MinDiff , uma técnica de remediação que busca equilibrar as taxas de erro através de fatias de seus dados por penalizar as disparidades no desempenho durante o treinamento. Quando aplicamos MinDiff, o desempenho do modelo pode diminuir ligeiramente em outras fatias. Como tal, nossos objetivos com MinDiff serão:

  • Desempenho aprimorado para grupos com baixo desempenho
  • Degradação limitada para outros grupos e desempenho geral

Prepare seus dados

Para usar MinDiff, criamos duas divisões de dados adicionais:

  • Uma divisão para exemplos não tóxicos que fazem referência a grupos minoritários: no nosso caso, isso incluirá comentários com referências aos nossos termos de identidade com baixo desempenho. Não incluímos alguns dos grupos porque há muito poucos exemplos, levando a uma incerteza maior com intervalos de intervalo de confiança amplos.
  • Uma divisão para exemplos não tóxicos referenciando o grupo majoritário.

É importante ter exemplos suficientes pertencentes às classes de baixo desempenho. Com base em sua arquitetura de modelo, distribuição de dados e configuração MinDiff, a quantidade de dados necessária pode variar significativamente. Em aplicativos anteriores, vimos MinDiff funcionar bem com 5.000 exemplos em cada divisão de dados.

No nosso caso, os grupos nas divisões minoritárias têm quantidades de exemplo de 9.688 e 3.906. Observe os desequilíbrios de classe no conjunto de dados; na prática, isso pode ser motivo de preocupação, mas não procuraremos abordá-los neste caderno, pois nossa intenção é apenas demonstrar o MinDiff.

Selecionamos apenas exemplos negativos para esses grupos, para que o MinDiff possa otimizar para obter esses exemplos corretamente. Pode parecer contra-intuitivo para esculpir conjuntos de exemplos negativos verdade terrestre se estamos preocupados principalmente com as disparidades na taxa de falsos positivos, mas lembre-se que uma previsão falso positivo é um exemplo de verdade de campo negativo que está incorretamente classificado como positivo, que é o que nós questão está tentando abordar.

Criar DataFrames MinDiff

# Create masks for the sensitive and nonsensitive groups
minority_mask = data_train.religion.apply(
    lambda x: any(religion in x for religion in ('jewish', 'muslim')))
majority_mask = data_train.religion.apply(lambda x: x == "['christian']")

# Select nontoxic examples, so MinDiff will be able to reduce sensitive FP rate.
true_negative_mask = data_train['toxicity'] == 0

data_train_main = copy.copy(data_train)
data_train_sensitive = data_train[minority_mask & true_negative_mask]
data_train_nonsensitive = data_train[majority_mask & true_negative_mask]

Também precisamos converter nossos DataFrames Pandas em conjuntos de dados do Tensorflow para entrada MinDiff. Observe que, diferentemente da API de modelo Keras para Pandas DataFrames, usar conjuntos de dados significa que precisamos fornecer os recursos e rótulos de entrada do modelo juntos em um conjunto de dados. Aqui nós fornecemos o 'comment_text' como um recurso de entrada e remodelar o rótulo para coincidir com a saída esperada do modelo.

Também agrupamos o conjunto de dados neste estágio, pois MinDiff requer conjuntos de dados em lote. Observe que ajustamos a seleção do tamanho do lote da mesma forma que ela é ajustada para o modelo de linha de base, levando em consideração a velocidade de treinamento e as considerações de hardware enquanto equilibramos com o desempenho do modelo. Aqui, escolhemos o mesmo tamanho de lote para todos os três conjuntos de dados, mas isso não é um requisito, embora seja uma boa prática ter os dois tamanhos de lote MinDiff equivalentes.

Criar conjuntos de dados MinDiff

# Convert the pandas DataFrames to Datasets.
dataset_train_main = tf.data.Dataset.from_tensor_slices(
    (data_train_main['comment_text'].values, 
     data_train_main.pop(LABEL).values.reshape(-1,1) * 1.0)).batch(BATCH_SIZE)
dataset_train_sensitive = tf.data.Dataset.from_tensor_slices(
    (data_train_sensitive['comment_text'].values, 
     data_train_sensitive.pop(LABEL).values.reshape(-1,1) * 1.0)).batch(BATCH_SIZE)
dataset_train_nonsensitive = tf.data.Dataset.from_tensor_slices(
    (data_train_nonsensitive['comment_text'].values, 
     data_train_nonsensitive.pop(LABEL).values.reshape(-1,1) * 1.0)).batch(BATCH_SIZE)

Treinar e avaliar o modelo

Para treinar com MinDiff, basta levar o modelo original e envolvê-la em um MinDiffModel com uma correspondente loss e loss_weight . Estamos usando 1.5 como padrão loss_weight , mas este é um parâmetro que precisa ser ajustado para seu caso de uso, uma vez que depende do modelo e do produto exigências. Você pode experimentar alterar o valor para ver como isso afeta o modelo, observando que aumentá-lo aproxima o desempenho dos grupos minoritário e majoritário, mas pode vir com compensações mais pronunciadas.

Em seguida, compilamos o modelo normalmente (usando a perda regular não MinDiff) e ajustamos para treinar.

Train MinDiffModel

use_pretrained_model = True

base_dir = tempfile.mkdtemp(prefix='saved_models')
min_diff_model_location = os.path.join(base_dir, 'model_export_min_diff')

if use_pretrained_model:
  BASE_MIN_DIFF_PATH = tempfile.mkdtemp()
  MIN_DIFF_URL = 'https://storage.googleapis.com/civil_comments_model/min_diff_model.zip'
  ZIP_PATH = os.path.join(BASE_PATH, 'min_diff_model.zip')
  MIN_DIFF_MODEL_PATH = os.path.join(BASE_MIN_DIFF_PATH, 'tmp/min_diff_model')
  DIRPATH = '/tmp/min_diff_model'

  r = requests.get(MIN_DIFF_URL, allow_redirects=True)
  open(ZIP_PATH, 'wb').write(r.content)

  with zipfile.ZipFile(ZIP_PATH, 'r') as zip_ref:
    zip_ref.extractall(BASE_MIN_DIFF_PATH)
  min_diff_model = tf.keras.models.load_model(
      MIN_DIFF_MODEL_PATH, custom_objects={'KerasLayer' : hub.KerasLayer})

  min_diff_model.save(min_diff_model_location, save_format='tf')

else:
  min_diff_weight = 1.5

  # Create the dataset that will be passed to the MinDiffModel during training.
  dataset = md.keras.utils.input_utils.pack_min_diff_data(
      dataset_train_main, dataset_train_sensitive, dataset_train_nonsensitive)

  # Create the original model.
  original_model = min_diff_keras_utils.create_keras_sequential_model()

  # Wrap the original model in a MinDiffModel, passing in one of the MinDiff
  # losses and using the set loss_weight.
  min_diff_loss = md.losses.MMDLoss()
  min_diff_model = md.keras.MinDiffModel(original_model,
                                         min_diff_loss,
                                         min_diff_weight)

  # Compile the model normally after wrapping the original model.  Note that
  # this means we use the baseline's model's loss here.
  optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)
  loss = tf.keras.losses.BinaryCrossentropy()
  min_diff_model.compile(optimizer=optimizer, loss=loss, metrics=['accuracy'])

  min_diff_model.fit(dataset, epochs=20)

  min_diff_model.save_original_model(min_diff_model_location, save_format='tf')
INFO:tensorflow:Assets written to: /tmp/saved_modelsb3zkcos_/model_export_min_diff/assets
INFO:tensorflow:Assets written to: /tmp/saved_modelsb3zkcos_/model_export_min_diff/assets

Em seguida, avaliamos os resultados.

min_diff_eval_subdir = os.path.join(base_dir, 'tfma_eval_result')
min_diff_eval_result = fi_util.get_eval_results(
    min_diff_model_location,
    min_diff_eval_subdir,
    validate_tfrecord_file,
    slice_selection='religion')
WARNING:absl:Tensorflow version (2.5.0) found. Note that TFMA support for TF 2.0 is currently in beta

Para garantir que avaliamos um novo modelo corretamente, precisamos selecionar um limite da mesma forma que faríamos o modelo de linha de base. Em um ambiente de produção, isso significaria garantir que as métricas de avaliação atendam aos padrões de lançamento. No nosso caso, escolheremos o limite que resulta em um FPR geral semelhante ao modelo de linha de base. Esse limite pode ser diferente daquele selecionado para o modelo de linha de base. Tente selecionar a taxa de falsos positivos com limite 0,400. (Observe que os subgrupos com exemplos de quantidade muito baixa têm intervalos de intervalo de confiança muito amplos e não têm resultados previsíveis.)

widget_view.render_fairness_indicator(min_diff_eval_result)
FairnessIndicatorViewer(slicingMetrics=[{'sliceValue': 'Overall', 'slice': 'Overall', 'metrics': {'accuracy': …

Analisando esses resultados, você pode notar que os FPRs para nossos grupos-alvo melhoraram. A diferença entre nosso grupo com desempenho mais baixo e o grupo majoritário melhorou de 0,024 para 0,006. Dadas as melhorias que observamos e o forte desempenho contínuo do grupo majoritário, atingimos ambas as nossas metas. Dependendo do produto, melhorias adicionais podem ser necessárias, mas essa abordagem deixou nosso modelo um passo mais próximo do desempenho equitativo para todos os usuários.