Visualizza su TensorFlow.org | Esegui in Google Colab | Visualizza su GitHub | Scarica il taccuino |
Questo notebook mostra un modo semplice per creare e ottimizzare i problemi vincolati utilizzando la libreria TFCO. Questo metodo può essere utile per migliorare i modelli quando troviamo che non stanno eseguendo altrettanto bene in diversi fette di nostri dati, che possiamo identificare con Fairness indicatori . Il secondo dei principi dell'IA di Google afferma che la nostra tecnologia dovrebbe evitare di creare o rafforzare pregiudizi sleali e riteniamo che questa tecnica possa aiutare a migliorare l'equità del modello in alcune situazioni. In particolare, questo quaderno:
- Addestrare un semplice, senza vincoli modello di rete neurale per rilevare il sorriso di una persona in immagini utilizzando
tf.keras
ei CelebFaces grandi attributi ( CelebA ) del set di dati. - Valuta le prestazioni del modello rispetto a una metrica di equità comunemente utilizzata tra i 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à.
- Riqualificare il modello ora vincolata e valutare nuovamente le prestazioni, assicurando che la nostra metrica correttezza scelto è migliorata.
Ultimo aggiornamento: 3/11 febbraio 2020
Installazione
Questo notebook è stato creato nel Colaboratory , collegato al Python 3 backend Google Compute Engine. Se desideri ospitare questo notebook in un ambiente diverso, non dovresti riscontrare grossi problemi a condizione che includi tutti i pacchetti richiesti nelle celle sottostanti.
Nota che la prima volta che esegui le installazioni pip, ti potrebbe essere chiesto di riavviare il runtime a causa di pacchetti preinstallati scaduti. Una volta fatto ciò, verranno utilizzati i pacchetti corretti.
Installazioni pip
!pip install -q -U pip==20.2
!pip install git+https://github.com/google-research/tensorflow_constrained_optimization
!pip install -q tensorflow-datasets tensorflow
!pip install fairness-indicators \
"absl-py==0.12.0" \
"apache-beam<3,>=2.34" \
"avro-python3==1.9.1" \
"pyzmq==17.0.0"
Tieni presente che, a seconda di quando esegui la cella sottostante, potresti ricevere un avviso sulla versione predefinita di TensorFlow in Colab che passerà presto a TensorFlow 2.X. Puoi tranquillamente ignorare questo avviso poiché questo notebook è stato progettato per essere compatibile con TensorFlow 1.X e 2.X.
Moduli di importazione
import os
import sys
import tempfile
import urllib
import tensorflow as tf
from tensorflow import keras
import tensorflow_datasets as tfds
tfds.disable_progress_bar()
import numpy as np
import tensorflow_constrained_optimization as tfco
from tensorflow_metadata.proto.v0 import schema_pb2
from tfx_bsl.tfxio import tensor_adapter
from tfx_bsl.tfxio import tf_example_record
Inoltre, aggiungiamo alcune importazioni specifiche per gli indicatori di correttezza che utilizzeremo per valutare e visualizzare le prestazioni del modello.
Importazioni relative agli indicatori di equità
import tensorflow_model_analysis as tfma
import fairness_indicators as fi
from google.protobuf import text_format
import apache_beam as beam
Anche se TFCO è compatibile con l'esecuzione di grafici e grafici, questo notebook presuppone che l'esecuzione di grafici sia abilitata per impostazione predefinita come in TensorFlow 2.x. Per garantire che nulla si interrompa, l'esecuzione desiderosa sarà abilitata nella cella sottostante.
Abilita esecuzione Eager e versioni di stampa
if tf.__version__ < "2.0.0":
tf.compat.v1.enable_eager_execution()
print("Eager execution enabled.")
else:
print("Eager execution enabled by default.")
print("TensorFlow " + tf.__version__)
print("TFMA " + tfma.VERSION_STRING)
print("TFDS " + tfds.version.__version__)
print("FI " + fi.version.__version__)
Eager execution enabled by default. TensorFlow 2.8.0-rc0 TFMA 0.36.0 TFDS 4.4.0 FI 0.36.0
Set di dati CelebA
CelebA è attribuisce una faccia larga scala di dati con più di 200.000 immagini famose, ciascuna con 40 annotazioni attributi (come il tipo di capelli, accessori di moda, caratteristiche facciali, ecc) e riprese 5 limite (occhi, bocca e posizioni naso). Per maggiori dettagli date un'occhiata alla carta . Con il permesso dei proprietari, abbiamo conservato questo set di dati su Google Cloud Storage e per lo più accedervi tramite tensorflow set di dati ( tfds
) .
In questo quaderno:
- Il nostro modello tenterà di classificare se il soggetto dell'immagine sorride, come rappresentato dal "Sorridente" attributo *.
- 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 ai gruppi di età, utilizzando l'attributo binario "Giovani". Chiameremo questo "gruppo di età" in questo quaderno.
* Anche se ci sono poche informazioni disponibili sulla metodologia di etichettatura per questo insieme di dati, si assume che l'attributo "Smiling" è stato determinato da un piacere, espressione tipo, o divertito sul volto del soggetto. Ai fini di questo caso di studio, prenderemo queste etichette come verità fondamentale.
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
Testare le funzioni di supporto del set di dati
local_root = tempfile.mkdtemp(prefix='test-data')
def local_test_filename_base():
return local_root
def local_test_file_full_prefix():
return os.path.join(local_test_filename_base(), "celeb_a-test.tfrecord")
def copy_test_files_to_local():
filename_base = local_test_file_full_prefix()
num_test_shards = num_test_shards_dict[version]
for shard in range(num_test_shards):
url = "https://storage.googleapis.com/celeb_a_dataset/celeb_a/%s/celeb_a-test.tfrecord-0000%s-of-0000%s" % (version, shard, num_test_shards)
filename = "%s-0000%s-of-0000%s" % (filename_base, shard, num_test_shards)
res = urllib.request.urlretrieve(url, filename)
Avvertenze
Prima di andare avanti, ci sono diverse considerazioni da tenere a mente nell'uso di CelebA:
- Sebbene in linea di principio questo notebook 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 degli attributi.
- Ai fini di questo taccuino, la caratteristica che contiene l'attributo "Giovane" è indicata come "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". Si tratta di ipotesi fatte da queste informazioni non è menzionato nel documento originale .
- Pertanto, le prestazioni nei modelli addestrati in questo notebook sono legate al modo in cui gli attributi sono stati resi operativi e annotati dagli autori di CelebA.
- Questo modello non deve essere utilizzato per scopi commerciali come che possa violare accordo di ricerca non commerciale di CelebA .
Impostazione delle funzioni di ingresso
Le celle successive aiuteranno a semplificare la pipeline di input e a visualizzare le prestazioni.
Innanzitutto definiamo alcune variabili relative ai dati e definiamo una funzione di pre-elaborazione richiesta.
Definisci variabili
ATTR_KEY = "attributes"
IMAGE_KEY = "image"
LABEL_KEY = "Smiling"
GROUP_KEY = "Young"
IMAGE_SIZE = 28
Definire le funzioni di pre-elaborazione
def preprocess_input_dict(feat_dict):
# Separate out the image and target variable from the feature dictionary.
image = feat_dict[IMAGE_KEY]
label = feat_dict[ATTR_KEY][LABEL_KEY]
group = feat_dict[ATTR_KEY][GROUP_KEY]
# Resize and normalize image.
image = tf.cast(image, tf.float32)
image = tf.image.resize(image, [IMAGE_SIZE, IMAGE_SIZE])
image /= 255.0
# Cast label and group to float32.
label = tf.cast(label, tf.float32)
group = tf.cast(group, tf.float32)
feat_dict[IMAGE_KEY] = image
feat_dict[ATTR_KEY][LABEL_KEY] = label
feat_dict[ATTR_KEY][GROUP_KEY] = group
return feat_dict
get_image_and_label = lambda feat_dict: (feat_dict[IMAGE_KEY], feat_dict[ATTR_KEY][LABEL_KEY])
get_image_label_and_group = lambda feat_dict: (feat_dict[IMAGE_KEY], feat_dict[ATTR_KEY][LABEL_KEY], feat_dict[ATTR_KEY][GROUP_KEY])
Quindi, costruiamo le funzioni dati di cui abbiamo bisogno nel resto della 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
Perché questo notebook si concentra su TFCO, ci sarà assemblare un semplice, senza vincoli tf.keras.Sequential
modello.
Potremmo essere in grado di migliorare notevolmente le prestazioni del modello aggiungendo una certa complessità (ad esempio, livelli più densamente collegati, esplorando diverse funzioni di attivazione, aumentando le dimensioni 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. Nota che questa collaborazione è intesa come uno strumento educativo e non ha la stabilità di una pipeline di produzione finemente sintonizzata. Correre senza seminare 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 correttezza.
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 al fine di passarli correttamente a TFMA.
Funzioni di pre-elaborazione dei dati per
def tfds_filepattern_for_split(dataset_name, split):
return f"{local_test_file_full_prefix()}*"
class PreprocessCelebA(object):
"""Class that deserializes, decodes and applies additional preprocessing for CelebA input."""
def __init__(self, dataset_name):
builder = tfds.builder(dataset_name)
self.features = builder.info.features
example_specs = self.features.get_serialized_info()
self.parser = tfds.core.example_parser.ExampleParser(example_specs)
def __call__(self, serialized_example):
# Deserialize
deserialized_example = self.parser.parse_example(serialized_example)
# Decode
decoded_example = self.features.decode_example(deserialized_example)
# Additional preprocessing
image = decoded_example[IMAGE_KEY]
label = decoded_example[ATTR_KEY][LABEL_KEY]
# Resize and scale image.
image = tf.cast(image, tf.float32)
image = tf.image.resize(image, [IMAGE_SIZE, IMAGE_SIZE])
image /= 255.0
image = tf.reshape(image, [-1])
# Cast label and group to float32.
label = tf.cast(label, tf.float32)
group = decoded_example[ATTR_KEY][GROUP_KEY]
output = tf.train.Example()
output.features.feature[IMAGE_KEY].float_list.value.extend(image.numpy().tolist())
output.features.feature[LABEL_KEY].float_list.value.append(label.numpy())
output.features.feature[GROUP_KEY].bytes_list.value.append(b"Young" if group.numpy() else b'Not Young')
return output.SerializeToString()
def tfds_as_pcollection(beam_pipeline, dataset_name, split):
return (
beam_pipeline
| 'Read records' >> beam.io.ReadFromTFRecord(tfds_filepattern_for_split(dataset_name, split))
| 'Preprocess' >> beam.Map(PreprocessCelebA(dataset_name))
)
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 atto, ora siamo 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.
Si noti che l'esecuzione di questo notebook in tensorflow <2.0.0 può comportare un avvertimento disapprovazione per np.where
. Tranquillamente ignorare questo avviso come tensorflow affronta questo nel 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 [==============================] - 12s 6ms/step - loss: 0.5038 - accuracy: 0.7733 Epoch 2/5 1000/1000 [==============================] - 7s 7ms/step - loss: 0.3800 - accuracy: 0.8301 Epoch 3/5 1000/1000 [==============================] - 6s 6ms/step - loss: 0.3598 - accuracy: 0.8427 Epoch 4/5 1000/1000 [==============================] - 25s 25ms/step - loss: 0.3435 - accuracy: 0.8474 Epoch 5/5 1000/1000 [==============================] - 5s 5ms/step - loss: 0.3402 - accuracy: 0.8479 <keras.callbacks.History at 0x7f0f5c476350>
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 [==============================] - 50s 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 correttezza (tramite TFMA). In particolare, ci interessa vedere se c'è un divario significativo nelle prestazioni tra le categorie "Giovani" e "Non giovani" quando valutate sul tasso di falsi positivi.
Si verifica un errore falso positivo 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 sorridente" e il modello prevede "Sorride". Per estensione, il tasso di falsi positivi, utilizzato nella visualizzazione sopra, è una misura dell'accuratezza per un test. Sebbene questo sia un errore relativamente banale da commettere 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')
2022-01-07 18:46:05.881112: W tensorflow/python/util/util.cc:368] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them. INFO:tensorflow:Assets written to: /tmp/saved_modelswhxcqdry/model_export_unconstrained/assets INFO:tensorflow:Assets written to: /tmp/saved_modelswhxcqdry/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:root:Make sure that locally built Python SDK docker image has Python 3.7 interpreter. 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:107: 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:107: 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 di Fairness Indicators (0.1.2) seleziona il tasso di falsi negativi per impostazione predefinita. Dopo aver eseguito la riga sottostante, 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': 'Young', 'slice': 'Young:Young', 'metrics': {'example_c…
Come mostrano i risultati di cui sopra, che vediamo un divario eccessivo tra le categorie "non più giovane" "Giovani" e.
È qui che TFCO può aiutare limitando il tasso di falsi positivi a rientrare in un criterio più accettabile.
Impostazione del modello vincolato
Come documentato nella biblioteca di TFCO , ci sono diversi aiutanti che renderanno più facile per vincolare il problema:
-
tfco.rate_context()
- Questo è quello che verrà utilizzato nella costruzione di un vincolo per ogni categoria di età. -
tfco.RateMinimizationProblem()
- L'espressione tasso da qui sarà ridotto al minimo il tasso di falsi positivi soggetto per gruppi di età. In altre parole, le prestazioni ora verranno valutate in base alla differenza tra i tassi di falsi positivi del gruppo di età e quelli del set di dati complessivo. Per questa dimostrazione, come vincolo verrà impostato un tasso di falsi positivi inferiore o uguale al 5%. -
tfco.ProxyLagrangianOptimizerV2()
- Questo è l'aiutante che effettivamente risolvere il problema vincolo di tasso.
La cella sottostante chiederà a questi aiutanti di 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 i gruppi di età.
Ora, perché l'ultima iterazione del modello vincolata non può necessariamente essere il modello più performante in termini di vincolo definito, la biblioteca TFCO è dotato di tfco.find_best_candidate_index()
che possono aiutare a scegliere il miglior iterata di quelle che si trovano al termine di ogni epoca. Pensate a tfco.find_best_candidate_index()
come un euristica aggiunto che classifica ciascuno dei risultati sulla base di accuratezza e l'equità vincolo (in questo caso, il tasso di falsi positivi attraverso fascia di età) separatamente rispetto ai dati di addestramento. In questo modo, può cercare un miglior compromesso tra l'accuratezza complessiva e il vincolo di equità.
Le seguenti celle avvieranno l'addestramento con i 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 i risultati ancora una volta utilizzando indicatori di correttezza.
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_modelsbztxt9fy/model_export_constrained/assets INFO:tensorflow:Assets written to: /tmp/saved_modelsbztxt9fy/model_export_constrained/assets WARNING:root:Make sure that locally built Python SDK docker image has Python 3.7 interpreter.
Come la volta precedente abbiamo utilizzato gli indicatori di correttezza, deseleziona false_negative_rate e seleziona false_positive_rate per esaminare la metrica che ci interessa.
Nota che per confrontare equamente le due versioni del nostro modello, è importante utilizzare soglie che stabiliscano che il tasso complessivo di falsi positivi sia approssimativamente uguale. Ciò garantisce che stiamo osservando il cambiamento effettivo anziché solo uno spostamento nel modello equivalente al semplice spostamento del limite di soglia. Nel nostro caso, confrontare il 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 tasso, 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 avvicinasse al soddisfacimento del vincolo e riducesse il più possibile la disparità tra i gruppi.