Zobacz na TensorFlow.org | Uruchom w Google Colab | Zobacz na GitHub | Pobierz notatnik |
Ten notatnik przedstawia prosty sposób tworzenia i optymalizacji problemów z ograniczeniami przy użyciu biblioteki TFCO. Ta metoda może być przydatna w poprawie modele kiedy okazuje się, że oni nie wykonując równie dobrze w różnych kromki naszych danych, które możemy zidentyfikować za pomocą rzetelności wskaźników . Druga z zasad Google dotyczących sztucznej inteligencji mówi, że nasza technologia powinna unikać tworzenia lub wzmacniania nieuczciwych uprzedzeń i uważamy, że ta technika może w niektórych sytuacjach pomóc poprawić uczciwość modelu. W szczególności ten notatnik:
- Trenuj proste, swobodne modelu sieci neuronowej do wykrywania uśmiechu osoby na zdjęciach z użyciem
tf.keras
i CelebFaces wielkoskalowych Atrybuty ( CelebA ) zestawu danych. - Oceń wydajność modelu w odniesieniu do powszechnie używanej metryki rzetelności w różnych grupach wiekowych, korzystając ze wskaźników rzetelności.
- Skonfiguruj prosty problem z ograniczoną optymalizacją, aby osiągnąć lepsze wyniki w różnych grupach wiekowych.
- Przekwalifikować się teraz ograniczony model i ponownie ocenić wydajność, zapewniając, że wybrany przez nas uczciwość metryczny uległa poprawie.
Ostatnia aktualizacja: 3/11 lut 2020
Instalacja
Notebook ten został stworzony w Colaboratory , podłączony do Python 3 backend Google Compute Engine. Jeśli chcesz hostować ten notebook w innym środowisku, nie powinieneś napotkać żadnych poważnych problemów, pod warunkiem, że w poniższych komórkach uwzględnisz wszystkie wymagane pakiety.
Zauważ, że przy pierwszym uruchomieniu pip installs, możesz zostać poproszony o ponowne uruchomienie środowiska wykonawczego z powodu preinstalowanych nieaktualnych pakietów. Gdy to zrobisz, zostaną użyte właściwe pakiety.
Instalacje 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"
Zwróć uwagę, że w zależności od tego, kiedy uruchomisz poniższą komórkę, możesz wkrótce otrzymać ostrzeżenie o domyślnej wersji TensorFlow w Colab przechodzącej na TensorFlow 2.X. Możesz bezpiecznie zignorować to ostrzeżenie, ponieważ ten notebook został zaprojektowany tak, aby był zgodny z TensorFlow 1.X i 2.X.
Importuj moduły
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
Dodatkowo dodamy kilka importów, które są specyficzne dla wskaźników rzetelności, których użyjemy do oceny i wizualizacji wydajności modelu.
Przywóz związany ze wskaźnikami uczciwości
import tensorflow_model_analysis as tfma
import fairness_indicators as fi
from google.protobuf import text_format
import apache_beam as beam
Chociaż TFCO jest zgodny z wykonywaniem przyspieszonym i grafem, ten notatnik zakłada, że wykonywanie przyspieszone jest domyślnie włączone, tak jak w TensorFlow 2.x. Aby upewnić się, że nic się nie zepsuje, w komórce poniżej zostanie włączone szybkie wykonywanie.
Włącz gorliwe wykonanie i wersje do druku
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
Zbiór danych CelebA
CelebA jest dużą skalę twarz atrybutów zestawu danych z ponad 200.000 zdjęć celebrytów, każdy z 40 adnotacji atrybutów (takich jak rodzaj włosów, akcesoria mody, rysy twarzy, etc.) i lokalizacjach 5 Charakterystyczny obiekt (oczy, usta i nos pozycjach). Aby uzyskać więcej informacji spojrzeć na papierze . Za zgodą właścicieli, mamy zapisany zbioru danych w Google Cloud Storage, a przede wszystkim dostęp do niego poprzez TensorFlow zbiorów danych ( tfds
) .
W tym notatniku:
- Nasz model będzie próbował klasyfikować czy przedmiotem obrazu jest uśmiechnięta, reprezentowane przez „Uśmiechnięty” atrybutu *.
- Obrazy zostaną zmienione z 218x178 na 28x28, aby skrócić czas wykonywania i pamięć podczas uczenia.
- Skuteczność naszego modelu zostanie oceniona w różnych grupach wiekowych przy użyciu binarnego atrybutu „Młody”. W tym notatniku nazwiemy tę „grupę wiekową”.
* Chociaż istnieje niewiele informacji o metodologii znakowania zbioru danych, będziemy zakładać, że „Uśmiechnięty” atrybut określano zadowoleniem, rodzaj lub rozbawionym wyrazem na twarzy fotografowanej osoby. Na potrzeby tego studium przypadku przyjmiemy te etykiety jako podstawową prawdę.
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
Testuj funkcje pomocnicze zbioru danych
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)
Zastrzeżenia
Zanim przejdziemy dalej, należy pamiętać o kilku kwestiach związanych z używaniem CelebA:
- Chociaż w zasadzie ten notatnik może wykorzystywać dowolny zestaw danych z obrazami twarzy, wybrano CelebA, ponieważ zawiera on obrazy osób publicznych z domeny publicznej.
- Wszystkie adnotacje atrybutów w CelebA są zoperacjonalizowane jako kategorie binarne. Na przykład atrybut „Młody” (określony przez osoby nadające etykietę zbioru danych) jest oznaczony jako obecny lub nieobecny na obrazie.
- Kategoryzacje CelebA nie odzwierciedlają prawdziwej ludzkiej różnorodności atrybutów.
- Na potrzeby tego notatnika funkcja zawierająca atrybut „Młodzi” jest określana jako „grupa wiekowa”, gdzie obecność atrybutu „Młodzi” na obrazie jest oznaczona jako członek grupy wiekowej „Młodzi”, a brak atrybutu „Młody” jest oznaczony jako członek grupy wiekowej „Niemłody”. Są to założenia przyjęte jako informacja ta nie jest wymieniona w oryginalnym papierze .
- W związku z tym wydajność modeli wyszkolonych w tym zeszycie jest powiązana ze sposobem, w jaki atrybuty zostały zoperacjonalizowane i opisane przez autorów CelebA.
- Model ten nie powinien być używany do celów komercyjnych jak że naruszałoby niekomercyjny umowę badawczą CelebA użytkownika .
Konfigurowanie funkcji wejściowych
Kolejne komórki pomogą usprawnić potok wejściowy, a także wizualizować wydajność.
Najpierw zdefiniujemy niektóre zmienne związane z danymi i zdefiniujemy wymaganą funkcję przetwarzania wstępnego.
Zdefiniuj zmienne
ATTR_KEY = "attributes"
IMAGE_KEY = "image"
LABEL_KEY = "Smiling"
GROUP_KEY = "Young"
IMAGE_SIZE = 28
Zdefiniuj funkcje przetwarzania wstępnego
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])
Następnie budujemy funkcje danych, których potrzebujemy w pozostałej części współpracy.
# 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()
Zbuduj prosty model DNN
Ponieważ ten notatnik koncentruje się na TFCO będziemy montować proste, swobodne tf.keras.Sequential
model.
Możemy być w stanie znacznie poprawić wydajność modelu poprzez dodanie pewnej złożoności (np. gęściej połączone warstwy, badanie różnych funkcji aktywacji, zwiększenie rozmiaru obrazu), ale może to odwrócić uwagę od celu wykazania, jak łatwo jest zastosować bibliotekę TFCO podczas pracy z Kerasem. Z tego powodu model pozostanie prosty — ale zachęcamy do eksploracji tej przestrzeni.
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
Definiujemy również funkcję ustawiania nasion, aby zapewnić powtarzalne wyniki. Zwróć uwagę, że ta współpraca ma być narzędziem edukacyjnym i nie ma stabilności precyzyjnie dostrojonego potoku produkcyjnego. Bieganie bez ustawienia nasion może prowadzić do różnych wyników.
def set_seeds():
np.random.seed(121212)
tf.compat.v1.set_random_seed(212121)
Wskaźniki uczciwości Funkcje pomocnicze
Przed uczeniem naszego modelu definiujemy szereg funkcji pomocniczych, które pozwolą nam ocenić wydajność modelu za pomocą wskaźników rzetelności.
Najpierw tworzymy funkcję pomocniczą, aby zapisać nasz model po jego nauczeniu.
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
Następnie definiujemy funkcje służące do wstępnego przetwarzania danych w celu poprawnego przekazania ich do TFMA.
Funkcje wstępnego przetwarzania danych dla
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))
)
Na koniec definiujemy funkcję, która ocenia wyniki w 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)
Trenuj i oceniaj model niezwiązany
Po zdefiniowaniu modelu i wprowadzeniu potoku wejściowego jesteśmy teraz gotowi do trenowania naszego modelu. Aby zmniejszyć ilość czasu wykonywania i pamięci, wyszkolimy model, dzieląc dane na małe partie za pomocą kilku powtarzających się iteracji.
Należy pamiętać, że uruchomienie tego notebooka w TensorFlow <2.0.0 może skutkować ostrzeżeniem dezaprobata dla np.where
. Zignorować to ostrzeżenie jak TensorFlow rozwiązuje to w 2.X za pomocą tf.where
zamiast 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>
Ocena modelu na danych testowych powinna skutkować końcowym wynikiem dokładności nieco ponad 85%. Nieźle jak na prosty model bez dostrojenia.
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
Jednak wyniki oceniane w różnych grupach wiekowych mogą ujawnić pewne niedociągnięcia.
Aby dokładniej to zbadać, oceniamy model za pomocą wskaźników rzetelności (za pośrednictwem TFMA). W szczególności interesuje nas, czy istnieje znaczna luka w wydajności między kategoriami „młodzi” i „niemłodzi” przy ocenie na podstawie wskaźnika wyników fałszywie pozytywnych.
Błąd fałszywie dodatni występuje, gdy model niepoprawnie przewiduje klasę dodatnią. W tym kontekście fałszywie pozytywny wynik ma miejsce, gdy podstawową prawdą jest obraz celebryty „Nie uśmiecha się”, a model przewiduje „Uśmiecha się”. Co za tym idzie, wskaźnik fałszywie pozytywnych, który jest używany na powyższej wizualizacji, jest miarą dokładności testu. Chociaż w tym kontekście jest to stosunkowo przyziemny błąd, błędy fałszywie pozytywne mogą czasami powodować bardziej problematyczne zachowania. Na przykład fałszywie pozytywny błąd w klasyfikatorze spamu może spowodować, że użytkownik przegapi ważną wiadomość e-mail.
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)`
Jak wspomniano powyżej, koncentrujemy się na współczynniku fałszywie pozytywnych wyników. Aktualna wersja wskaźników rzetelności (0.1.2) domyślnie wybiera współczynnik fałszywie ujemnych wyników. Po uruchomieniu poniższej linii odznacz false_negative_rate i wybierz false_positive_rate, aby spojrzeć na metrykę, która nas interesuje.
tfma.addons.fairness.view.widget_view.render_fairness_indicator(eval_results_unconstrained)
FairnessIndicatorViewer(slicingMetrics=[{'sliceValue': 'Young', 'slice': 'Young:Young', 'metrics': {'example_c…
Jak pokazują wyniki powyżej, widzimy nieproporcjonalnie lukę między „młodych” i kategorii „Not Young”.
W tym przypadku TFCO może pomóc, ograniczając wskaźnik wyników fałszywie pozytywnych do bardziej akceptowalnego kryterium.
Konfiguracja modelu ograniczonego
Co zostało udokumentowane w bibliotece TFCO jest , istnieje kilka pomocnicy, które sprawiają, że łatwiej jest ograniczyć ten problem:
-
tfco.rate_context()
- To, co zostanie wykorzystane przy konstruowaniu ograniczenie dla każdej kategorii grupy wiekowej. -
tfco.RateMinimizationProblem()
- Wyrażenie stopa być zminimalizowane tutaj będzie fałszywie dodatni wskaźnik podlega grupie wiekowej. Innymi słowy, wydajność będzie teraz oceniana na podstawie różnicy między współczynnikami fałszywie pozytywnych wyników w grupie wiekowej a ogólnym zbiorem danych. W przypadku tej demonstracji jako ograniczenie zostanie ustawiony wskaźnik wyników fałszywie pozytywnych mniejszy lub równy 5%. -
tfco.ProxyLagrangianOptimizerV2()
- To jest pomocnik, który będzie rzeczywiście rozwiązać problem ograniczający szybkość.
Poniższa komórka wezwie tych pomocników do skonfigurowania treningu modelu z ograniczeniem sprawiedliwości.
# 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())
Model jest teraz skonfigurowany i gotowy do trenowania z ograniczeniem odsetka wyników fałszywie dodatnich w całej grupie wiekowej.
Teraz, ponieważ ostatnia iteracja ograniczonym modelu może niekoniecznie być najlepsze wykonanie modelu w zakresie określonym ograniczeniem biblioteka TFCO wyposażony tfco.find_best_candidate_index()
, które mogą pomóc wybrać najlepszy powtórzyć, z tych, znalezionych po każdym epoka. Pomyśl tfco.find_best_candidate_index()
jako dodatkowy heurystyki, która plasuje każdego z wyników opartych na dokładności i rzetelności przymusu (w tym przypadku, fałszywie dodatnich w całej grupie wiekowej) osobno w stosunku do danych treningowych. W ten sposób może szukać lepszego kompromisu między ogólną dokładnością a ograniczeniem sprawiedliwości.
Poniższe komórki rozpoczną uczenie z ograniczeniami, jednocześnie odnajdując model o najlepszej wydajności na iterację.
# 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
Po zastosowaniu ograniczenia ponownie oceniamy wyniki za pomocą wskaźników rzetelności.
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.
Podobnie jak poprzednio, używaliśmy wskaźników rzetelności, odznacz false_negative_rate i wybierz false_positive_rate, aby spojrzeć na metrykę, która nas interesuje.
Należy pamiętać, że aby rzetelnie porównać dwie wersje naszego modelu, ważne jest, aby użyć progów, które ustalają ogólny współczynnik fałszywych trafień na mniej więcej równy. Gwarantuje to, że patrzymy na rzeczywistą zmianę, a nie tylko na przesunięcie w modelu, które jest równoznaczne z przesunięciem granicy progowej. W naszym przypadku porównanie modelu nieograniczonego przy 0,5 i modelu z ograniczeniami przy 0,22 zapewnia rzetelne porównanie modeli.
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'…
Dzięki zdolności TFCO do wyrażenia bardziej złożonego wymagania jako ograniczenia szybkości, pomogliśmy temu modelowi osiągnąć bardziej pożądany wynik przy niewielkim wpływie na ogólną wydajność. Oczywiście wciąż jest miejsce na ulepszenia, ale przynajmniej TFCO udało się znaleźć model, który zbliża się do spełnienia ograniczenia i maksymalnie zmniejsza dysproporcje między grupami.