Esempio di ottimizzazione vincolata di TensorFlow utilizzando il set di dati CelebA

Visualizza su TensorFlow.org Esegui in Google Colab Visualizza su GitHub Scarica taccuino

Questo notebook mostra un modo semplice per creare e ottimizzare problemi vincolati utilizzando la libreria TFCO. Questo metodo può essere utile per migliorare i modelli quando scopriamo che non stanno funzionando altrettanto bene su diverse sezioni dei nostri dati, che possiamo identificare utilizzando gli indicatori di equità . Il secondo dei principi di intelligenza artificiale di Google afferma che la nostra tecnologia dovrebbe evitare di creare o rafforzare pregiudizi ingiusti e riteniamo che questa tecnica possa aiutare a migliorare l'equità del modello in alcune situazioni. In particolare, questo taccuino:

  • Addestra un modello di rete neurale semplice e non vincolato per rilevare il sorriso di una persona nelle immagini utilizzando tf.keras e il set di dati CelebFaces Attributes ( CelebA ) su larga scala.
  • Valutare le prestazioni del modello rispetto a una metrica di equità comunemente utilizzata per gruppi di età, utilizzando gli indicatori di equità.
  • Imposta un semplice problema di ottimizzazione vincolata per ottenere prestazioni più eque tra i gruppi di età.
  • Addestra nuovamente il modello ora vincolato e valuta di nuovo le prestazioni, assicurandoti che la nostra metrica di equità scelta sia migliorata.

Ultimo aggiornamento: 3/11 febbraio 2020

Installazione

Questo notebook è stato creato in Colaboratory , collegato al backend Python 3 Google Compute Engine. Se desideri ospitare questo notebook in un ambiente diverso, non dovresti riscontrare problemi importanti a condizione di includere tutti i pacchetti richiesti nelle celle sottostanti.

Nota che la prima volta che esegui le installazioni di pip, ti potrebbe essere chiesto di riavviare il runtime a causa di pacchetti obsoleti preinstallati. Una volta fatto, verranno utilizzati i pacchetti corretti.

Pip installa

Tieni presente che, a seconda di quando esegui la cella sottostante, potresti ricevere un avviso sulla versione predefinita di TensorFlow in Colab che passa a TensorFlow 2.X presto. Puoi tranquillamente ignorare questo avviso poiché questo notebook è stato progettato per essere compatibile con TensorFlow 1.X e 2.X.

Moduli di importazione

Inoltre, aggiungiamo alcune importazioni specifiche per gli indicatori di equità che utilizzeremo per valutare e visualizzare le prestazioni del modello.

Sebbene TFCO sia compatibile con l'esecuzione eager e grafica, questo notebook presuppone che l'esecuzione eager sia abilitata per impostazione predefinita come in TensorFlow 2.x. Per garantire che nulla si interrompa, verrà abilitata l'esecuzione impaziente nella cella sottostante.

Abilita l'esecuzione desiderosa e le versioni di stampa

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

CelebA Dataset

CelebA è un set di dati di attributi di volti su larga scala con oltre 200.000 immagini di celebrità, ciascuna con 40 annotazioni di attributi (come tipo di capelli, accessori di moda, caratteristiche del viso, ecc.) E 5 luoghi di riferimento (posizioni di occhi, bocca e naso). Per maggiori dettagli dai un'occhiata al documento . Con il permesso dei proprietari, abbiamo archiviato questo set di dati su Google Cloud Storage e vi accediamo principalmente tramite i set di dati TensorFlow ( tfds ) .

In questo taccuino:

  • Il nostro modello tenterà di classificare se il soggetto dell'immagine sorride, come rappresentato dall'attributo "Sorridente" * .
  • Le immagini verranno ridimensionate da 218x178 a 28x28 per ridurre il tempo di esecuzione e la memoria durante l'allenamento.
  • Le prestazioni del nostro modello verranno valutate in base a gruppi di età, utilizzando l'attributo binario "Young". Chiameremo questo "gruppo di età" in questo quaderno.

* Sebbene siano disponibili poche informazioni sulla metodologia di etichettatura per questo set di dati, presumeremo che l'attributo "Sorridente" sia stato determinato da un'espressione compiaciuta, gentile o divertita sul volto del soggetto. Ai fini di questo caso di studio, prenderemo queste etichette come verità di base.

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

Test delle funzioni di supporto del set di dati

Avvertenze

Prima di andare avanti, ci sono diverse considerazioni da tenere a mente nell'utilizzo di CelebA:

  • Sebbene in linea di principio questo taccuino possa utilizzare qualsiasi set di dati di immagini di volti, CelebA è stato scelto perché contiene immagini di pubblico dominio di personaggi pubblici.
  • Tutte le annotazioni degli attributi in CelebA sono rese operative come categorie binarie. Ad esempio, l'attributo "Giovane" (come determinato dagli etichettatori del set di dati) è indicato come presente o assente nell'immagine.
  • Le categorizzazioni di CelebA non riflettono la reale diversità umana di attributi.
  • Ai fini di questo taccuino, l'elemento contenente l'attributo "Giovane" è denominato "gruppo di età", dove la presenza dell'attributo "Giovane" in un'immagine è etichettata come un membro del gruppo di età "Giovane" e il l'assenza dell'attributo "Giovane" è etichettata come un membro del gruppo di età "Non giovane". Queste sono ipotesi fatte poiché queste informazioni non sono menzionate nel documento originale .
  • Pertanto, le prestazioni nei modelli addestrati in questo notebook sono legate ai modi in cui gli attributi sono stati resi operativi e annotati dagli autori di CelebA.
  • Questo modello non deve essere utilizzato per scopi commerciali in quanto ciò violerebbe l'accordo di ricerca non commerciale di CelebA .

Configurazione delle funzioni di input

Le celle successive aiuteranno a semplificare la pipeline di input e a visualizzare le prestazioni.

Per prima cosa definiamo alcune variabili relative ai dati e definiamo una funzione di preelaborazione richiesta.

Definisci variabili

Definisci funzioni di pre-elaborazione

Quindi, costruiamo le funzioni dati di cui abbiamo bisogno nel resto del 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()

Costruisci un semplice modello DNN

Poiché questo notebook è incentrato su TFCO, assembliamo un modello tf.keras.Sequential semplice e non vincolato.

Potremmo essere in grado di migliorare notevolmente le prestazioni del modello aggiungendo una certa complessità (ad esempio, livelli più densamente connessi, esplorando diverse funzioni di attivazione, aumentando la dimensione dell'immagine), ma ciò potrebbe distrarre dall'obiettivo di dimostrare quanto sia facile applicare la libreria TFCO quando si lavora con Keras. Per questo motivo, il modello sarà mantenuto semplice, ma sentiti incoraggiato a esplorare questo spazio.

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

Definiamo anche una funzione per impostare i semi per garantire risultati riproducibili. Si noti che questo colab è inteso come uno strumento educativo e non ha la stabilità di una pipeline di produzione finemente sintonizzata. Correre senza impostare un seme può portare a risultati diversi.

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

Indicatori di equità Funzioni di supporto

Prima di addestrare il nostro modello, definiamo una serie di funzioni di supporto che ci consentiranno di valutare le prestazioni del modello tramite indicatori di equità.

Innanzitutto, creiamo una funzione di supporto per salvare il nostro modello una volta addestrato.

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

Successivamente, definiamo le funzioni utilizzate per preelaborare i dati in modo da passarli correttamente a TFMA.

Funzioni di pre-elaborazione dei dati per

Infine, definiamo una funzione che valuta i risultati in 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)

Addestra e valuta il modello non vincolato

Con il modello ora definito e la pipeline di input in posizione, siamo ora pronti per addestrare il nostro modello. Per ridurre la quantità di tempo di esecuzione e memoria, addestreremo il modello suddividendo i dati in piccoli batch con solo poche iterazioni ripetute.

Tieni presente che l'esecuzione di questo notebook in TensorFlow <2.0.0 potrebbe generare un avviso di deprecazione per np.where . Ignora questo avviso in modo sicuro poiché TensorFlow risolve questo problema in 2.X utilizzando tf.where al posto di 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>

La valutazione del modello sui dati del test dovrebbe portare a un punteggio di accuratezza finale di poco superiore all'85%. Non male per un modello semplice senza messa a punto.

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

Tuttavia, le prestazioni valutate tra i gruppi di età possono rivelare alcune carenze.

Per esplorare ulteriormente questo aspetto, valutiamo il modello con indicatori di equità (tramite TFMA). In particolare, ci interessa vedere se esiste un divario significativo nelle prestazioni tra le categorie "Giovani" e "Non giovani" quando valutate in base al tasso di falsi positivi.

Un errore di falso positivo si verifica quando il modello prevede in modo errato la classe positiva. In questo contesto, un risultato falso positivo si verifica quando la verità fondamentale è l'immagine di una celebrità "Non sorride" e il modello prevede "Sorride". Per estensione, il tasso di falsi positivi, che viene utilizzato nella visualizzazione sopra, è una misura dell'accuratezza di un test. Sebbene questo sia un errore relativamente banale da fare in questo contesto, errori falsi positivi a volte possono causare comportamenti più problematici. Ad esempio, un errore falso positivo in un classificatore di spam potrebbe far perdere a un utente un'e-mail importante.

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

Come accennato in precedenza, ci stiamo concentrando sul tasso di falsi positivi. L'attuale versione degli indicatori di equità (0.1.2) seleziona il tasso di falsi negativi per impostazione predefinita. Dopo aver eseguito la riga seguente, deseleziona false_negative_rate e seleziona false_positive_rate per esaminare la metrica che ci interessa.

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

Come mostrano i risultati sopra, vediamo un divario sproporzionato tra le categorie "Giovani" e "Non giovani" .

È qui che TFCO può aiutare limitando il tasso di falsi positivi a rientrare in un criterio più accettabile.

Configurazione vincolata del modello

Come documentato nella libreria di TFCO , ci sono diversi helper che renderanno più facile limitare il problema:

  1. tfco.rate_context() - Questo è ciò che verrà utilizzato nella costruzione di un vincolo per ciascuna categoria di fascia di età.
  2. tfco.RateMinimizationProblem() - L'espressione del tasso da minimizzare qui sarà il tasso di falsi positivi soggetto al gruppo di età. In altre parole, le prestazioni ora saranno valutate in base alla differenza tra i tassi di falsi positivi della fascia di età e quella del set di dati complessivo. Per questa dimostrazione, verrà impostato come vincolo un tasso di falsi positivi inferiore o uguale al 5%.
  3. tfco.ProxyLagrangianOptimizerV2() - Questo è l'helper che risolverà effettivamente il problema del vincolo di velocità.

La cella sottostante chiamerà questi aiutanti per impostare l'addestramento del modello con il vincolo di equità.

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

Il modello è ora impostato e pronto per essere addestrato con il vincolo del tasso di falsi positivi per tutti i gruppi di età.

Ora, poiché l'ultima iterazione del modello vincolato potrebbe non essere necessariamente il modello più performante in termini di vincolo definito, la libreria TFCO è dotata di tfco.find_best_candidate_index() che può aiutare a scegliere l'iterazione migliore tra quelle trovate dopo ciascuna epoca. Pensa a tfco.find_best_candidate_index() come a un'euristica aggiuntiva che classifica ciascuno dei risultati in base all'accuratezza e al vincolo di equità (in questo caso, il tasso di falsi positivi tra i gruppi di età) separatamente rispetto ai dati di allenamento. In questo modo, può cercare un miglior compromesso tra l'accuratezza complessiva e il vincolo di equità.

Le celle seguenti inizieranno l'addestramento con vincoli trovando anche il modello con le migliori prestazioni per iterazione.

# 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

Dopo aver applicato il vincolo, valutiamo nuovamente i risultati utilizzando gli Indicatori di equità.

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

Come la volta precedente, abbiamo utilizzato gli indicatori di equità, deseleziona false_negative_rate e seleziona false_positive_rate per esaminare la metrica che ci interessa.

Si noti che per confrontare in modo equo le due versioni del nostro modello, è importante utilizzare soglie che impostano il tasso complessivo di falsi positivi in ​​modo che sia approssimativamente uguale. Ciò garantisce che stiamo osservando il cambiamento effettivo anziché un semplice spostamento nel modello equivalente al semplice spostamento del limite della soglia. Nel nostro caso, il confronto del modello non vincolato a 0,5 e il modello vincolato a 0,22 fornisce un confronto equo per i modelli.

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'…

Con la capacità di TFCO di esprimere un requisito più complesso come vincolo di velocità, abbiamo aiutato questo modello a ottenere un risultato più desiderabile con un impatto minimo sulle prestazioni complessive. Ovviamente c'è ancora spazio per miglioramenti, ma almeno TFCO è riuscita a trovare un modello che si avvicina a soddisfare il vincolo e riduce il più possibile la disparità tra i gruppi.