La journée communautaire ML est le 9 novembre ! Rejoignez - nous pour les mises à jour de tensorflow, JAX et plus En savoir plus

Exemple d'optimisation contrainte TensorFlow à l'aide de l'ensemble de données CelebA

Voir sur TensorFlow.org Exécuter dans Google Colab Afficher sur GitHub Télécharger le cahier

Ce cahier présente un moyen simple de créer et d'optimiser des problèmes contraints à l'aide de la bibliothèque TFCO. Cette méthode peut être utile pour améliorer les modèles lorsque nous constatons qu'ils ne fonctionnent pas aussi bien sur différentes tranches de nos données, que nous pouvons identifier à l'aide d' indicateurs d'équité . Le deuxième des principes d'IA de Google stipule que notre technologie doit éviter de créer ou de renforcer un biais injuste, et nous pensons que cette technique peut aider à améliorer l'équité des modèles dans certaines situations. En particulier, ce cahier:

  • Entraînez un modèle de réseau neuronal simple et sans contrainte pour détecter le sourire d'une personne dans les images à l'aide de tf.keras et du jeu de données à grande échelle des attributs CelebFaces ( CelebA ).
  • Évaluez les performances du modèle par rapport à une mesure d'équité couramment utilisée dans les groupes d'âge, à l'aide d'indicateurs d'équité.
  • Définissez un problème d'optimisation simple et contraint pour obtenir des performances plus équitables dans toutes les tranches d'âge.
  • Réorganisez le modèle désormais contraint et évaluez à nouveau les performances, en vous assurant que la mesure d'équité choisie s'est améliorée.

Dernière mise à jour: 3/11 févr.2020

Installation

Ce notebook a été créé dans Colaboratory , connecté au backend Python 3 Google Compute Engine. Si vous souhaitez héberger ce notebook dans un environnement différent, vous ne devriez pas rencontrer de problèmes majeurs à condition d'inclure tous les packages requis dans les cellules ci-dessous.

Notez que la toute première fois que vous exécutez les installations pip, vous pouvez être invité à redémarrer le runtime en raison de packages préinstallés obsolètes. Une fois que vous faites cela, les packages corrects seront utilisés.

Pip installe

Notez que selon le moment où vous exécutez la cellule ci-dessous, vous pouvez recevoir un avertissement concernant la version par défaut de TensorFlow dans Colab qui passe bientôt à TensorFlow 2.X. Vous pouvez ignorer cet avertissement en toute sécurité car ce portable a été conçu pour être compatible avec TensorFlow 1.X et 2.X.

Importer des modules

De plus, nous ajoutons quelques importations spécifiques aux indicateurs d'équité que nous utiliserons pour évaluer et visualiser les performances du modèle.

Bien que TFCO soit compatible avec l'exécution hâtive et graphique, ce notebook suppose que l'exécution hâtive est activée par défaut, comme c'est le cas dans TensorFlow 2.x. Pour garantir que rien ne casse, l'exécution rapide sera activée dans la cellule ci-dessous.

Activer l'exécution rapide et les versions d'impression

Eager execution enabled by default.
TensorFlow 2.4.1
TFMA 0.29.0
TFDS 4.2.0
FI 0.29.0

Ensemble de données CelebA

CelebA est un ensemble de données d'attributs de visage à grande échelle avec plus de 200000 images de célébrités, chacune avec 40 annotations d'attributs (tels que le type de cheveux, les accessoires de mode, les traits du visage, etc.) et 5 emplacements de repère (positions des yeux, de la bouche et du nez). Pour plus de détails, jetez un œil à l'article . Avec l'autorisation des propriétaires, nous avons stocké cet ensemble de données sur Google Cloud Storage et y tfds principalement via les ensembles de données TensorFlow ( tfds ) .

Dans ce cahier:

  • Notre modèle tentera de classer si le sujet de l'image sourit, comme représenté par l'attribut "Sourire" * .
  • Les images seront redimensionnées de 218x178 à 28x28 pour réduire le temps d'exécution et la mémoire lors de l'entraînement.
  • Les performances de notre modèle seront évaluées à travers les groupes d'âge, à l'aide de l'attribut binaire «Young». Nous appellerons cette «tranche d'âge» dans ce cahier.

* Bien qu'il y ait peu d'informations disponibles sur la méthodologie d'étiquetage pour cet ensemble de données, nous supposerons que l'attribut «Sourire» a été déterminé par une expression heureuse, gentille ou amusée sur le visage du sujet. Aux fins de cette étude de cas, nous prendrons ces étiquettes comme une vérité terrain.

gcs_base_dir = "gs://celeb_a_dataset/"
celeb_a_builder = tfds.builder("celeb_a", data_dir=gcs_base_dir, version='2.0.0')

celeb_a_builder.download_and_prepare()

num_test_shards_dict = {'0.3.0': 4, '2.0.0': 2} # Used because we download the test dataset separately
version = str(celeb_a_builder.info.version)
print('Celeb_A dataset version: %s' % version)
Celeb_A dataset version: 2.0.0

Tester les fonctions d'assistance du jeu de données

Mises en garde

Avant d'aller de l'avant, il y a plusieurs considérations à garder à l'esprit lors de l'utilisation de CelebA:

  • Bien qu'en principe ce cahier puisse utiliser n'importe quel ensemble de données d'images de visage, CelebA a été choisi car il contient des images du domaine public de personnalités publiques.
  • Toutes les annotations d'attributs dans CelebA sont opérationnalisées en tant que catégories binaires. Par exemple, l'attribut "Young" (tel que déterminé par les étiqueteurs de l'ensemble de données) est indiqué comme présent ou absent dans l'image.
  • Les catégorisations de CelebA ne reflètent pas la vraie diversité humaine des attributs.
  • Pour les besoins de ce bloc-notes, l'élément contenant l'attribut "Jeune" est appelé "groupe d'âge", où la présence de l'attribut "Jeune" dans une image est étiquetée comme membre du groupe d'âge "Jeune" et l'absence de l'attribut «Jeune» est étiquetée comme membre du groupe d'âge «Pas jeune». Ce sont des hypothèses faites car ces informations ne sont pas mentionnées dans le document original .
  • En tant que tel, les performances des modèles formés dans ce cahier sont liées à la manière dont les attributs ont été opérationnalisés et annotés par les auteurs de CelebA.
  • Ce modèle ne doit pas être utilisé à des fins commerciales car cela violerait l'accord de recherche non commerciale de CelebA .

Configuration des fonctions d'entrée

Les cellules suivantes aideront à rationaliser le pipeline d'entrée et à visualiser les performances.

Nous définissons d'abord certaines variables liées aux données et définissons une fonction de prétraitement requise.

Définir des variables

Définir les fonctions de prétraitement

Ensuite, nous construisons les fonctions de données dont nous avons besoin dans le reste du colab.

# Train data returning either 2 or 3 elements (the third element being the group)
def celeb_a_train_data_wo_group(batch_size):
  celeb_a_train_data = celeb_a_builder.as_dataset(split='train').shuffle(1024).repeat().batch(batch_size).map(preprocess_input_dict)
  return celeb_a_train_data.map(get_image_and_label)
def celeb_a_train_data_w_group(batch_size):
  celeb_a_train_data = celeb_a_builder.as_dataset(split='train').shuffle(1024).repeat().batch(batch_size).map(preprocess_input_dict)
  return celeb_a_train_data.map(get_image_label_and_group)

# Test data for the overall evaluation
celeb_a_test_data = celeb_a_builder.as_dataset(split='test').batch(1).map(preprocess_input_dict).map(get_image_label_and_group)
# Copy test data locally to be able to read it into tfma
copy_test_files_to_local()

Créer un modèle DNN simple

Parce que ce notebook se concentre sur TFCO, nous allons assembler un modèle tf.keras.Sequential simple et sans contrainte.

Nous pouvons être en mesure d'améliorer considérablement les performances du modèle en ajoutant une certaine complexité (par exemple, des couches plus densément connectées, en explorant différentes fonctions d'activation, en augmentant la taille de l'image), mais cela peut détourner l'attention de l'objectif de démontrer à quel point il est facile d'appliquer la bibliothèque TFCO lorsque vous travaillez avec Keras. Pour cette raison, le modèle restera simple - mais sentez-vous encouragé à explorer cet espace.

def create_model():
  # For this notebook, accuracy will be used to evaluate performance.
  METRICS = [
    tf.keras.metrics.BinaryAccuracy(name='accuracy')
  ]

  # The model consists of:
  # 1. An input layer that represents the 28x28x3 image flatten.
  # 2. A fully connected layer with 64 units activated by a ReLU function.
  # 3. A single-unit readout layer to output real-scores instead of probabilities.
  model = keras.Sequential([
      keras.layers.Flatten(input_shape=(IMAGE_SIZE, IMAGE_SIZE, 3), name='image'),
      keras.layers.Dense(64, activation='relu'),
      keras.layers.Dense(1, activation=None)
  ])

  # TFCO by default uses hinge loss — and that will also be used in the model.
  model.compile(
      optimizer=tf.keras.optimizers.Adam(0.001),
      loss='hinge',
      metrics=METRICS)
  return model

Nous définissons également une fonction pour définir les graines pour garantir des résultats reproductibles. Notez que ce colab est conçu comme un outil pédagogique et n'a pas la stabilité d'un pipeline de production finement réglé. Courir sans créer de graine peut conduire à des résultats variés.

def set_seeds():
  np.random.seed(121212)
  tf.compat.v1.set_random_seed(212121)

Fonctions d'aide des indicateurs d'équité

Avant d'entraîner notre modèle, nous définissons un certain nombre de fonctions d'assistance qui nous permettront d'évaluer les performances du modèle via des indicateurs d'équité.

Tout d'abord, nous créons une fonction d'assistance pour enregistrer notre modèle une fois que nous l'avons entraîné.

def save_model(model, subdir):
  base_dir = tempfile.mkdtemp(prefix='saved_models')
  model_location = os.path.join(base_dir, subdir)
  model.save(model_location, save_format='tf')
  return model_location

Ensuite, nous définissons les fonctions utilisées pour prétraiter les données afin de les transmettre correctement à TFMA.

Fonctions de prétraitement des données pour

Enfin, nous définissons une fonction qui évalue les résultats dans TFMA.

def get_eval_results(model_location, eval_subdir):
  base_dir = tempfile.mkdtemp(prefix='saved_eval_results')
  tfma_eval_result_path = os.path.join(base_dir, eval_subdir)

  eval_config_pbtxt = """
        model_specs {
          label_key: "%s"
        }
        metrics_specs {
          metrics {
            class_name: "FairnessIndicators"
            config: '{ "thresholds": [0.22, 0.5, 0.75] }'
          }
          metrics {
            class_name: "ExampleCount"
          }
        }
        slicing_specs {}
        slicing_specs { feature_keys: "%s" }
        options {
          compute_confidence_intervals { value: False }
          disabled_outputs{values: "analysis"}
        }
      """ % (LABEL_KEY, GROUP_KEY)

  eval_config = text_format.Parse(eval_config_pbtxt, tfma.EvalConfig())

  eval_shared_model = tfma.default_eval_shared_model(
        eval_saved_model_path=model_location, tags=[tf.saved_model.SERVING])

  schema_pbtxt = """
        tensor_representation_group {
          key: ""
          value {
            tensor_representation {
              key: "%s"
              value {
                dense_tensor {
                  column_name: "%s"
                  shape {
                    dim { size: 28 }
                    dim { size: 28 }
                    dim { size: 3 }
                  }
                }
              }
            }
          }
        }
        feature {
          name: "%s"
          type: FLOAT
        }
        feature {
          name: "%s"
          type: FLOAT
        }
        feature {
          name: "%s"
          type: BYTES
        }
        """ % (IMAGE_KEY, IMAGE_KEY, IMAGE_KEY, LABEL_KEY, GROUP_KEY)
  schema = text_format.Parse(schema_pbtxt, schema_pb2.Schema())
  coder = tf_example_record.TFExampleBeamRecord(
      physical_format='inmem', schema=schema,
      raw_record_column_name=tfma.ARROW_INPUT_COLUMN)
  tensor_adapter_config = tensor_adapter.TensorAdapterConfig(
    arrow_schema=coder.ArrowSchema(),
    tensor_representations=coder.TensorRepresentations())
  # Run the fairness evaluation.
  with beam.Pipeline() as pipeline:
    _ = (
          tfds_as_pcollection(pipeline, 'celeb_a', 'test')
          | 'ExamplesToRecordBatch' >> coder.BeamSource()
          | 'ExtractEvaluateAndWriteResults' >>
          tfma.ExtractEvaluateAndWriteResults(
              eval_config=eval_config,
              eval_shared_model=eval_shared_model,
              output_path=tfma_eval_result_path,
              tensor_adapter_config=tensor_adapter_config)
    )
  return tfma.load_eval_result(output_path=tfma_eval_result_path)

Former et évaluer un modèle sans contrainte

Le modèle étant maintenant défini et le pipeline d'entrée en place, nous sommes maintenant prêts à entraîner notre modèle. Pour réduire le temps d'exécution et la mémoire, nous allons entraîner le modèle en découpant les données en petits lots avec seulement quelques itérations répétées.

Notez que l'exécution de ce bloc-notes dans TensorFlow <2.0.0 peut entraîner un avertissement d' np.where pour np.where . Ignorez cet avertissement en toute sécurité car TensorFlow résout ce problème dans 2.X en utilisant tf.where à la place de np.where .

BATCH_SIZE = 32

# Set seeds to get reproducible results
set_seeds()

model_unconstrained = create_model()
model_unconstrained.fit(celeb_a_train_data_wo_group(BATCH_SIZE), epochs=5, steps_per_epoch=1000)
Epoch 1/5
1000/1000 [==============================] - 17s 11ms/step - loss: 0.6219 - accuracy: 0.7189
Epoch 2/5
1000/1000 [==============================] - 10s 10ms/step - loss: 0.4061 - accuracy: 0.8187
Epoch 3/5
1000/1000 [==============================] - 10s 10ms/step - loss: 0.3649 - accuracy: 0.8391
Epoch 4/5
1000/1000 [==============================] - 16s 16ms/step - loss: 0.3427 - accuracy: 0.8485
Epoch 5/5
1000/1000 [==============================] - 10s 10ms/step - loss: 0.3390 - accuracy: 0.8482
<tensorflow.python.keras.callbacks.History at 0x7f47c01a8550>

L'évaluation du modèle sur les données de test devrait aboutir à un score d'exactitude final d'un peu plus de 85%. Pas mal pour un modèle simple sans réglage fin.

print('Overall Results, Unconstrained')
celeb_a_test_data = celeb_a_builder.as_dataset(split='test').batch(1).map(preprocess_input_dict).map(get_image_label_and_group)
results = model_unconstrained.evaluate(celeb_a_test_data)
Overall Results, Unconstrained
19962/19962 [==============================] - 40s 2ms/step - loss: 0.2125 - accuracy: 0.8636

Cependant, les performances évaluées à travers les groupes d'âge peuvent révéler certaines lacunes.

Pour approfondir cela, nous évaluons le modèle avec des indicateurs d'équité (via TFMA). En particulier, nous souhaitons voir s'il existe un écart significatif de performance entre les catégories «Jeunes» et «Pas jeunes» lorsqu'ils sont évalués sur un taux de faux positifs.

Une erreur faussement positive se produit lorsque le modèle prédit de manière incorrecte la classe positive. Dans ce contexte, un résultat faussement positif se produit lorsque la vérité sur le terrain est une image d'une célébrité «pas souriante» et que le modèle prédit «souriant». Par extension, le taux de faux positifs, qui est utilisé dans la visualisation ci-dessus, est une mesure de la précision d'un test. Bien qu'il s'agisse d'une erreur relativement banale à commettre dans ce contexte, les fausses erreurs positives peuvent parfois entraîner des comportements plus problématiques. Par exemple, une fausse erreur positive dans un classificateur de spam pourrait amener un utilisateur à manquer un e-mail important.

model_location = save_model(model_unconstrained, 'model_export_unconstrained')
eval_results_unconstrained = get_eval_results(model_location, 'eval_results_unconstrained')
INFO:tensorflow:Assets written to: /tmp/saved_modelseqklzviu/model_export_unconstrained/assets
INFO:tensorflow:Assets written to: /tmp/saved_modelseqklzviu/model_export_unconstrained/assets
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.6/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.6/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)`

Comme mentionné ci-dessus, nous nous concentrons sur le taux de faux positifs. La version actuelle des indicateurs d'équité (0.1.2) sélectionne le taux de faux négatifs par défaut. Après avoir exécuté la ligne ci-dessous, désélectionnez false_negative_rate et sélectionnez false_positive_rate pour regarder la métrique qui nous intéresse.

tfma.addons.fairness.view.widget_view.render_fairness_indicator(eval_results_unconstrained)
FairnessIndicatorViewer(slicingMetrics=[{'sliceValue': 'Overall', 'slice': 'Overall', 'metrics': {'example_cou…

Comme le montrent les résultats ci-dessus, nous constatons un écart disproportionné entre les catégories «jeunes» et «pas jeunes» .

C'est là que TFCO peut aider en contraignant le taux de faux positifs à se situer dans un critère plus acceptable.

Configuration du modèle contraint

Comme documenté dans la bibliothèque de TFCO , il existe plusieurs aides qui faciliteront la limitation du problème:

  1. tfco.rate_context() - C'est ce qui sera utilisé pour construire une contrainte pour chaque catégorie de groupe d'âge.
  2. tfco.RateMinimizationProblem() - L'expression du taux à minimiser ici sera le taux de faux positifs en fonction du groupe d'âge. En d'autres termes, les performances seront désormais évaluées en fonction de la différence entre les taux de faux positifs du groupe d'âge et ceux de l'ensemble de données global. Pour cette démonstration, un taux de faux positifs inférieur ou égal à 5% sera fixé comme contrainte.
  3. tfco.ProxyLagrangianOptimizerV2() - C'est l'aide qui résoudra réellement le problème de contrainte de débit.

La cellule ci-dessous fera appel à ces assistants pour mettre en place une formation modèle avec la contrainte d'équité.

# The batch size is needed to create the input, labels and group tensors.
# These tensors are initialized with all 0's. They will eventually be assigned
# the batch content to them. A large batch size is chosen so that there are
# enough number of "Young" and "Not Young" examples in each batch.
set_seeds()
model_constrained = create_model()
BATCH_SIZE = 32

# Create input tensor.
input_tensor = tf.Variable(
    np.zeros((BATCH_SIZE, IMAGE_SIZE, IMAGE_SIZE, 3), dtype="float32"),
    name="input")

# Create labels and group tensors (assuming both labels and groups are binary).
labels_tensor = tf.Variable(
    np.zeros(BATCH_SIZE, dtype="float32"), name="labels")
groups_tensor = tf.Variable(
    np.zeros(BATCH_SIZE, dtype="float32"), name="groups")

# Create a function that returns the applied 'model' to the input tensor
# and generates constrained predictions.
def predictions():
  return model_constrained(input_tensor)

# Create overall context and subsetted context.
# The subsetted context contains subset of examples where group attribute < 1
# (i.e. the subset of "Not Young" celebrity images).
# "groups_tensor < 1" is used instead of "groups_tensor == 0" as the former
# would be a comparison on the tensor value, while the latter would be a
# comparison on the Tensor object.
context = tfco.rate_context(predictions, labels=lambda:labels_tensor)
context_subset = context.subset(lambda:groups_tensor < 1)

# Setup list of constraints.
# In this notebook, the constraint will just be: FPR to less or equal to 5%.
constraints = [tfco.false_positive_rate(context_subset) <= 0.05]

# Setup rate minimization problem: minimize overall error rate s.t. constraints.
problem = tfco.RateMinimizationProblem(tfco.error_rate(context), constraints)

# Create constrained optimizer and obtain train_op.
# Separate optimizers are specified for the objective and constraints
optimizer = tfco.ProxyLagrangianOptimizerV2(
      optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
      constraint_optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
      num_constraints=problem.num_constraints)

# A list of all trainable variables is also needed to use TFCO.
var_list = (model_constrained.trainable_weights + list(problem.trainable_variables) +
            optimizer.trainable_variables())

Le modèle est maintenant configuré et prêt à être formé avec la contrainte de taux de faux positifs pour tous les groupes d'âge.

Maintenant, comme la dernière itération du modèle contraint n'est pas nécessairement le modèle le plus performant en termes de contrainte définie, la bibliothèque TFCO est équipée de tfco.find_best_candidate_index() qui peut aider à choisir la meilleure itération parmi celles trouvées après chaque époque. Pensez à tfco.find_best_candidate_index() comme une heuristique supplémentaire qui classe chacun des résultats en fonction de la contrainte d'exactitude et d'équité (dans ce cas, le taux de faux positifs dans le groupe d'âge) séparément par rapport aux données d'entraînement. De cette façon, il peut rechercher un meilleur compromis entre l'exactitude globale et la contrainte d'équité.

Les cellules suivantes commenceront la formation avec des contraintes tout en trouvant le modèle le plus performant par itération.

# Obtain train set batches.

NUM_ITERATIONS = 100  # Number of training iterations.
SKIP_ITERATIONS = 10  # Print training stats once in this many iterations.

# Create temp directory for saving snapshots of models.
temp_directory = tempfile.mktemp()
os.mkdir(temp_directory)

# List of objective and constraints across iterations.
objective_list = []
violations_list = []

# Training iterations.
iteration_count = 0
for (image, label, group) in celeb_a_train_data_w_group(BATCH_SIZE):
  # Assign current batch to input, labels and groups tensors.
  input_tensor.assign(image)
  labels_tensor.assign(label)
  groups_tensor.assign(group)

  # Run gradient update.
  optimizer.minimize(problem, var_list=var_list)

  # Record objective and violations.
  objective = problem.objective()
  violations = problem.constraints()

  sys.stdout.write(
      "\r Iteration %d: Hinge Loss = %.3f, Max. Constraint Violation = %.3f"
      % (iteration_count + 1, objective, max(violations)))

  # Snapshot model once in SKIP_ITERATIONS iterations.
  if iteration_count % SKIP_ITERATIONS == 0:
    objective_list.append(objective)
    violations_list.append(violations)

    # Save snapshot of model weights.
    model_constrained.save_weights(
        temp_directory + "/celeb_a_constrained_" +
        str(iteration_count / SKIP_ITERATIONS) + ".h5")

  iteration_count += 1
  if iteration_count >= NUM_ITERATIONS:
    break

# Choose best model from recorded iterates and load that model.
best_index = tfco.find_best_candidate_index(
    np.array(objective_list), np.array(violations_list))

model_constrained.load_weights(
    temp_directory + "/celeb_a_constrained_" + str(best_index) + ".0.h5")

# Remove temp directory.
os.system("rm -r " + temp_directory)
Iteration 100: Hinge Loss = 0.614, Max. Constraint Violation = 0.268
0

Après avoir appliqué la contrainte, nous évaluons à nouveau les résultats à l'aide d'indicateurs d'équité.

model_location = save_model(model_constrained, 'model_export_constrained')
eval_result_constrained = get_eval_results(model_location, 'eval_results_constrained')
INFO:tensorflow:Assets written to: /tmp/saved_modelsrnjadh_e/model_export_constrained/assets
INFO:tensorflow:Assets written to: /tmp/saved_modelsrnjadh_e/model_export_constrained/assets

Comme la fois précédente, nous avons utilisé des indicateurs d'équité, désélectionnez false_negative_rate et sélectionnez false_positive_rate pour regarder la métrique qui nous intéresse.

Notez que pour comparer équitablement les deux versions de notre modèle, il est important d'utiliser des seuils qui fixent le taux global de faux positifs à peu près égal. Cela garantit que nous examinons le changement réel par opposition à un simple changement dans le modèle équivalent à simplement déplacer la limite du seuil. Dans notre cas, comparer le modèle sans contrainte à 0,5 et le modèle contraint à 0,22 fournit une comparaison équitable des modèles.

eval_results_dict = {
    'constrained': eval_result_constrained,
    'unconstrained': eval_results_unconstrained,
}
tfma.addons.fairness.view.widget_view.render_fairness_indicator(multi_eval_results=eval_results_dict)
FairnessIndicatorViewer(evalName='constrained', evalNameCompare='unconstrained', slicingMetrics=[{'sliceValue'…

Grâce à la capacité de TFCO à exprimer une exigence plus complexe en tant que contrainte tarifaire, nous avons aidé ce modèle à obtenir un résultat plus souhaitable avec peu d'impact sur la performance globale. Il y a bien sûr encore place à l'amélioration, mais au moins TFCO a pu trouver un modèle qui se rapproche de la contrainte et réduit au maximum les disparités entre les groupes.