Étude de cas d'assainissement du modèle

Dans ce cahier, nous allons former un classificateur de texte pour identifier le contenu écrit qui pourrait être considéré comme toxique ou nocif, et appliquer MinDiff pour remédier à certains problèmes d'équité. Dans notre flux de travail, nous allons :

  1. Évaluez les performances de notre modèle de base sur le texte contenant des références à des groupes sensibles.
  2. Améliorez les performances de tous les groupes sous-performants en vous entraînant avec MinDiff.
  3. Évaluez les performances du nouveau modèle sur la métrique choisie.

Notre objectif est de démontrer l'utilisation de la technique MinDiff avec un flux de travail très minimal, et non de présenter une approche fondée sur des principes d'équité dans l'apprentissage automatique. En tant que tel, notre évaluation ne portera que sur une catégorie sensible et une seule métrique. Nous ne corrigeons pas non plus les lacunes potentielles dans l'ensemble de données, ni n'ajustons nos configurations. Dans un cadre de production, vous voudriez aborder chacun d'eux avec rigueur. Pour plus d' informations sur l' évaluation de l' équité, consultez ce guide .

Installer

Nous commençons par installer les indicateurs d'équité et la correction du modèle TensorFlow.

Installe

Importez tous les composants nécessaires, y compris MinDiff et les indicateurs d'équité pour l'évaluation.

Importations

Nous utilisons une fonction utilitaire pour télécharger les données prétraitées et préparer les étiquettes pour qu'elles correspondent à la forme de sortie du modèle. La fonction télécharge également les données sous forme de TFRecords pour accélérer l'évaluation ultérieure. Alternativement, vous pouvez convertir le Pandas DataFrame en TFRecords avec n'importe quelle fonction de conversion d'utilitaire disponible.

# 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

Nous définissons quelques constantes utiles. Nous allons former le modèle sur la 'comment_text' caractéristique, avec notre étiquette cible comme 'toxicity' . Notez que la taille du lot ici est choisie arbitrairement, mais dans un environnement de production, vous devrez l'ajuster pour obtenir les meilleures performances.

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

Définissez des graines aléatoires. (Notez que cela ne stabilise pas complètement les résultats.)

Des graines

Définir et entraîner le modèle de base

Pour réduire le temps d'exécution, nous utilisons un modèle pré-entraîné par défaut. Il s'agit d'un modèle séquentiel Keras simple avec des couches initiales d'inclusion et de convolution, produisant une prédiction de toxicité. Si vous préférez, vous pouvez changer cela et vous entraîner à partir de zéro en utilisant notre fonction utilitaire pour créer le modèle. (Notez que, étant donné que votre environnement est probablement différent du nôtre, vous devrez personnaliser les seuils de réglage et d'évaluation.)

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)

Nous économisons le modèle afin d'évaluer l' utilisation des indicateurs d' équité .

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

Ensuite, nous exécutons des indicateurs d'équité. Pour rappel, nous allons tout simplement effectuer une évaluation des commentaires faisant référence coupé en tranches d' une catégorie, les groupes religieux. Dans un environnement de production, nous vous recommandons d'adopter une approche réfléchie pour déterminer les catégories et les métriques à évaluer.

Pour calculer les performances du modèle, la fonction d'utilité fait quelques choix pratiques pour les métriques, les tranches et les seuils de classificateur.

# 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)`

Résultats de l'évaluation du rendu

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

Regardons les résultats de l'évaluation. Essayez de sélectionner le taux de faux positifs (FPR) métrique avec le seuil de 0,450. Nous pouvons voir que le modèle ne fonctionne pas aussi bien pour certains groupes religieux que pour d'autres, affichant un FPR beaucoup plus élevé. Notez les larges intervalles de confiance sur certains groupes car ils ont trop peu d'exemples. Il est donc difficile de dire avec certitude qu'il existe une différence significative de performance pour ces tranches. Nous voudrons peut-être recueillir plus d'exemples pour résoudre ce problème. Nous pouvons cependant essayer d'appliquer MinDiff aux deux groupes dont nous pensons qu'ils sont sous-performants.

Nous avons choisi de nous concentrer sur le FPR, car un FPR plus élevé signifie que les commentaires faisant référence à ces groupes d'identité sont plus susceptibles d'être signalés à tort comme toxiques que d'autres commentaires. Cela pourrait entraîner des résultats inéquitables pour les utilisateurs qui s'engagent dans un dialogue sur la religion, mais notez que les disparités dans d'autres mesures peuvent entraîner d'autres types de préjudices.

Définir et entraîner le modèle MinDiff

Maintenant, nous allons essayer d'améliorer le FPR pour les groupes religieux sous-performants. Nous allons essayer de le faire en utilisant mindiff , une technique d'assainissement qui cherche à équilibrer les taux d'erreur à travers des tranches de vos données en pénalisant les disparités dans la performance au cours de la formation. Lorsque nous appliquons MinDiff, les performances du modèle peuvent se dégrader légèrement sur d'autres tranches. Ainsi, nos objectifs avec MinDiff seront :

  • Amélioration des performances pour les groupes sous-performants
  • Dégradation limitée pour les autres groupes et performances globales

Préparez vos données

Pour utiliser MinDiff, nous créons deux divisions de données supplémentaires :

  • Une scission pour les exemples non toxiques faisant référence à des groupes minoritaires : dans notre cas, cela inclura des commentaires avec des références à nos termes d'identité sous-performants. Nous n'incluons pas certains groupes car il y a trop peu d'exemples, ce qui conduit à une incertitude plus élevée avec de larges plages d'intervalles de confiance.
  • Une scission pour les exemples non toxiques faisant référence au groupe majoritaire.

Il est important d'avoir suffisamment d'exemples appartenant aux classes les moins performantes. En fonction de l'architecture de votre modèle, de la distribution des données et de la configuration MinDiff, la quantité de données nécessaires peut varier considérablement. Dans les applications précédentes, nous avons vu MinDiff bien fonctionner avec 5 000 exemples dans chaque division de données.

Dans notre cas, les groupes dans les divisions minoritaires ont des quantités d'exemple de 9 688 et 3 906. Notez les déséquilibres de classe dans l'ensemble de données ; en pratique, cela peut être préoccupant, mais nous ne chercherons pas à les aborder dans ce cahier puisque notre intention est simplement de démontrer MinDiff.

Nous sélectionnons uniquement des exemples négatifs pour ces groupes, afin que MinDiff puisse optimiser l'obtention de ces exemples. Il peut sembler contre - intuitif de se tailler des ensembles d'exemples négatifs de vérité terrain si nous sommes principalement préoccupés par les disparités dans le taux de faux positifs, mais rappelez - vous qu'une prédiction faux positif est un exemple vérité terrain négatif qui est mal classé comme positif, ce qui est la question que nous 'essaye d'aborder.

Créer des cadres de données 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]

Nous devons également convertir nos Pandas DataFrames en ensembles de données Tensorflow pour l'entrée MinDiff. Notez que contrairement à l'API du modèle Keras pour Pandas DataFrames, l'utilisation des ensembles de données signifie que nous devons fournir les caractéristiques et les étiquettes d'entrée du modèle dans un seul ensemble de données. Ici , nous fournissons la 'comment_text' comme une caractéristique d'entrée et de remodeler l'étiquette pour correspondre à la sortie attendue du modèle.

Nous mettons également l'ensemble de données par lots à ce stade, car MinDiff nécessite des ensembles de données par lots. Notez que nous ajustons la sélection de la taille du lot de la même manière qu'elle l'est pour le modèle de base, en tenant compte de la vitesse d'entraînement et des considérations matérielles tout en équilibrant les performances du modèle. Ici, nous avons choisi la même taille de lot pour les trois ensembles de données, mais ce n'est pas une exigence, bien qu'il soit recommandé d'avoir les deux tailles de lot MinDiff équivalentes.

Créer des jeux de données 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)

Former et évaluer le modèle

Former avec mindiff, il suffit de prendre le modèle original et l' envelopper dans un MinDiffModel avec une correspondante loss et loss_weight . Nous utilisons 1,5 par défaut loss_weight , mais cela est un paramètre qui doit être réglé pour votre cas d' utilisation, car cela dépend de vos besoins du modèle et de produits. Vous pouvez essayer de modifier la valeur pour voir son impact sur le modèle, en notant que son augmentation rapproche les performances des groupes minoritaires et majoritaires, mais peut entraîner des compromis plus prononcés.

Ensuite, nous compilons le modèle normalement (en utilisant la perte régulière non MinDiff) et nous nous adaptons à l'entraînement.

Train MinDiffModèle

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

Ensuite, nous évaluons les résultats.

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

Pour nous assurer que nous évaluons correctement un nouveau modèle, nous devons sélectionner un seuil de la même manière que nous le ferions pour le modèle de référence. Dans un environnement de production, cela signifierait s'assurer que les mesures d'évaluation répondent aux normes de lancement. Dans notre cas, nous choisirons le seuil qui aboutit à un FPR global similaire au modèle de référence. Ce seuil peut être différent de celui que vous avez sélectionné pour le modèle de référence. Essayez de sélectionner le taux de faux positifs avec le seuil de 0,400. (Notez que les sous-groupes avec des exemples de très faibles quantités ont des intervalles de confiance très larges et n'ont pas de résultats prévisibles.)

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

En examinant ces résultats, vous remarquerez peut-être que les FPR de nos groupes cibles se sont améliorés. L'écart entre notre groupe le moins performant et le groupe majoritaire s'est amélioré, passant de 0,024 à 0,006. Compte tenu des améliorations que nous avons observées et de la solide performance continue du groupe majoritaire, nous avons atteint nos deux objectifs. Selon le produit, d'autres améliorations peuvent être nécessaires, mais cette approche a permis à notre modèle de se rapprocher d'une performance équitable pour tous les utilisateurs.