Пример оптимизации с ограничениями TensorFlow с использованием набора данных CelebA

Посмотреть на TensorFlow.org Запускаем в Google Colab Посмотреть на GitHub Скачать блокнот

В этой записной книжке демонстрируется простой способ создания и оптимизации задач с ограничениями с помощью библиотеки TFCO. Этот метод может быть полезным для улучшения модели , когда мы видим , что они не выполняют одинаково хорошо в различных срезах наших данных, которые мы можем определить с помощью справедливости индикаторов . Второй из принципов искусственного интеллекта Google гласит, что наша технология не должна создавать или усиливать несправедливую предвзятость, и мы считаем, что этот метод может помочь улучшить справедливость модели в некоторых ситуациях. В частности, этот ноутбук будет:

  • Поезд простой, безусловная модель нейронной сети для обнаружения улыбки человека в изображениях с использованием tf.keras и крупномасштабное CelebFaces Атрибутов ( CelebA ) набором данных.
  • Оцените производительность модели по широко используемому показателю справедливости в возрастных группах, используя индикаторы справедливости.
  • Настройте простую задачу оптимизации с ограничениями, чтобы добиться более справедливой производительности в разных возрастных группах.
  • Переподготовка на данный момент ограничена модели и снова оценить производительность, гарантируя , что выбранные нами метрика справедливости улучшилась.

Последнее обновление: 11 марта 2020 г.

Установка

Этот ноутбук был создан в Colaboratory , связанных с Python 3 Google Compute Engine бэкэндом. Если вы хотите разместить этот блокнот в другой среде, у вас не должно возникнуть серьезных проблем, если вы включите все необходимые пакеты в ячейки ниже.

Обратите внимание, что при первом запуске установки pip вас могут попросить перезапустить среду выполнения из-за предустановленных устаревших пакетов. Как только вы это сделаете, будут использоваться правильные пакеты.

Пип устанавливает

Обратите внимание, что в зависимости от того, когда вы запустите ячейку ниже, вы можете получить предупреждение о том, что версия TensorFlow по умолчанию в Colab скоро переключится на TensorFlow 2.X. Вы можете смело игнорировать это предупреждение, поскольку этот ноутбук был разработан для совместимости с TensorFlow 1.X и 2.X.

Импорт модулей

Кроме того, мы добавляем несколько импортов, специфичных для индикаторов справедливости, которые мы будем использовать для оценки и визуализации производительности модели.

Хотя TFCO совместим с нетерпеливым и графовым выполнением, в этой записной книжке предполагается, что нетерпеливое выполнение включено по умолчанию, как и в TensorFlow 2.x. Чтобы гарантировать, что ничего не сломается, в ячейке ниже будет включено активное выполнение.

Включить Eager Execution и версии для печати

Eager execution enabled by default.
TensorFlow 2.8.0-rc0
TFMA 0.36.0
TFDS 4.4.0
FI 0.36.0

Набор данных CelebA

CelebA является крупномасштабным лицом атрибутов набора данных с более чем 200000 знаменитостью изображения, каждый из которых имеет 40 атрибутов аннотаций (например, типа волоса, модные аксессуары, черты лица и т.д.) и места 5 знаковых (глаз, рот и нос позиций). Для получения более подробной информации посмотрите на бумаге . С разрешения владельцев, мы сохранили этот набор данных на Google Cloud Storage и в основном к нему доступ через TensorFlow Datasets ( tfds ) .

В этой тетради:

  • Наша модель будет пытаться классифицировать ли улыбается предмет изображения, как представлена «Улыбка» атрибут *.
  • Размер изображений будет изменен с 218x178 до 28x28, чтобы сократить время выполнения и объем памяти при обучении.
  • Производительность нашей модели будет оцениваться по возрастным группам с использованием бинарного атрибута «Молодой». В этой записной книжке мы будем называть это «возрастной группой».

* В то время как существует мало информации о методологии маркировки для этого набора данных, мы будем считать , что «Улыбка» атрибут был определен довольной, своего рода, или позабавило выражение лица субъекта. Для целей данного тематического исследования мы примем эти ярлыки за истину.

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

Вспомогательные функции тестового набора данных

Предостережения

Прежде чем двигаться дальше, при использовании CelebA следует учитывать несколько соображений:

  • Хотя в принципе этот блокнот может использовать любой набор данных изображений лиц, был выбран CelebA, потому что он содержит общедоступные изображения общественных деятелей.
  • Все аннотации атрибутов в CelebA функционируют как бинарные категории. Например, атрибут «Молодой» (определяемый маркировщиками набора данных) обозначается как присутствующий или отсутствующий на изображении.
  • Категории CelebA не отражают реального разнообразия человеческих качеств.
  • Для целей этой записной книжки объект, содержащий атрибут «Молодой», называется «возрастной группой», где наличие атрибута «Молодой» на изображении помечается как член возрастной группы «Молодой», а отсутствие атрибута «Молодой» помечается как член возрастной группы «Немолодой». Эти допущения , как эта информация не упоминается в оригинальной статье .
  • Таким образом, производительность моделей, обученных в этой записной книжке, связана с тем, как атрибуты были введены в действие и аннотированы авторами CelebA.
  • Эта модель не должна быть использована в коммерческих целях , поскольку это будет нарушать некоммерческое исследовательское соглашение CelebA в .

Настройка функций ввода

Последующие ячейки помогут упростить конвейер ввода, а также визуализировать производительность.

Сначала мы определяем некоторые переменные, связанные с данными, и определяем необходимую функцию предварительной обработки.

Определить переменные

Определите функции предварительной обработки

Затем мы создаем функции данных, которые нам нужны, в остальной части 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()

Создайте простую модель DNN

Поскольку этот ноутбук фокусируется на TFCO, мы будем собирать простой, неограниченный tf.keras.Sequential модели.

Возможно, мы сможем значительно улучшить производительность модели, усложнив ее (например, более плотно связанные слои, изучив различные функции активации, увеличив размер изображения), но это может отвлечь от цели демонстрации простоты применения библиотеки TFCO. при работе с Керасом. По этой причине модель будет простой, но вам будет предложено исследовать это пространство.

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

Мы также определяем функцию для установки начальных значений для обеспечения воспроизводимых результатов. Обратите внимание, что эта коллаборация задумана как образовательный инструмент и не обладает стабильностью точно настроенного производственного конвейера. Запуск без установки семени может привести к различным результатам.

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

Индикаторы справедливости Вспомогательные функции

Перед обучением нашей модели мы определяем ряд вспомогательных функций, которые позволят нам оценить производительность модели с помощью индикаторов справедливости.

Во-первых, мы создаем вспомогательную функцию для сохранения нашей модели после ее обучения.

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

Затем мы определяем функции, используемые для предварительной обработки данных, чтобы правильно передать их в TFMA.

Функции предварительной обработки данных для

Наконец, мы определяем функцию, которая оценивает результаты в 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)

Обучите и оцените неограниченную модель

Теперь, когда модель определена и входной конвейер готов, мы готовы обучить нашу модель. Чтобы сократить время выполнения и память, мы будем обучать модель, разбивая данные на небольшие пакеты всего за несколько повторных итераций.

Обратите внимание , что запуск этого ноутбука в TensorFlow <2.0.0 может привести к предупреждению устаревания для np.where . Безопасно игнорировать это предупреждение , как TensorFlow решает эту проблему в 2.X, используя tf.where вместо 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>

Оценка модели на тестовых данных должна привести к окончательной оценке точности чуть более 85%. Неплохо для простой модели без тонкой настройки.

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

Тем не менее, результаты, оцениваемые по возрастным группам, могут выявить некоторые недостатки.

Чтобы изучить это глубже, мы оцениваем модель с помощью индикаторов справедливости (через TFMA). В частности, нам интересно узнать, существует ли значительный разрыв в производительности между категориями «Молодые» и «Немолодые» при оценке по показателю ложных срабатываний.

Ложноположительная ошибка возникает, когда модель неправильно предсказывает положительный класс. В этом контексте ложноположительный результат возникает, когда основной правдой является изображение знаменитости «Не улыбается», а модель предсказывает «Улыбается». В более широком смысле, частота ложных срабатываний, которая используется в визуализации выше, является мерой точности теста. Хотя в данном контексте это относительно обыденная ошибка, ложноположительные ошибки иногда могут вызывать более проблемное поведение. Например, ложноположительная ошибка в классификаторе спама может привести к тому, что пользователь пропустит важное электронное письмо.

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

Как упоминалось выше, мы концентрируемся на частоте ложных срабатываний. Текущая версия индикаторов справедливости (0.1.2) по умолчанию выбирает ложноотрицательный показатель. После запуска строки ниже снимите флажок false_negative_rate и выберите false_positive_rate, чтобы просмотреть интересующую нас метрику.

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

Как показывают результаты выше, мы видим непропорциональный разрыв между «Янг» и категорией «Не молодой».

Именно здесь TFCO может помочь, ограничивая частоту ложных срабатываний более приемлемым критерием.

Настройка ограниченной модели

Как указано в библиотеке TFCO в , есть несколько помощников , которые облегчат сдерживать проблему:

  1. tfco.rate_context() - это то , что будет использоваться при построении ограничений для каждой категории возрастной группы.
  2. tfco.RateMinimizationProblem() - Выражение скорости свести к минимуму здесь будет ложноположительной подлежит ставку возрастной группы. Другими словами, производительность теперь будет оцениваться на основе разницы между показателями ложноположительных результатов для возрастной группы и общего набора данных. Для этой демонстрации в качестве ограничения будет установлен уровень ложноположительных результатов менее или равный 5%.
  3. tfco.ProxyLagrangianOptimizerV2() - это помощник , который будет на самом деле решить проблему скорости ограничения.

Ячейка ниже вызовет этих помощников для настройки обучения модели с ограничением справедливости.

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

Теперь модель настроена и готова к обучению с ограничением частоты ложных срабатываний в возрастной группе.

Теперь, поскольку последняя итерация стесненной модели не обязательно может быть лучше выполнять модель с точкой зрения определенного ограничения, библиотека TFCO оснащена tfco.find_best_candidate_index() , который может помочь выбрать лучшую итерацию из них нашел после каждого эпоха. Подумайте о tfco.find_best_candidate_index() в качестве дополнительной эвристики , который занимает каждый из результатов , основанных на точность и справедливость ограничении (в данном случае ложных срабатываний через возрастную группу) отдельно в отношении подготовки данных. Таким образом, он может искать лучший компромисс между общей точностью и ограничением справедливости.

Следующие ячейки начнут обучение с ограничениями, а также найдут наиболее эффективную модель за итерацию.

# 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

После применения ограничения мы еще раз оцениваем результаты, используя индикаторы справедливости.

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.

Как и в предыдущем случае, когда мы использовали индикаторы справедливости, снимите флажок false_negative_rate и выберите false_positive_rate, чтобы просмотреть интересующую нас метрику.

Обратите внимание, что для справедливого сравнения двух версий нашей модели важно использовать пороговые значения, которые устанавливают примерно равный общий уровень ложных срабатываний. Это гарантирует, что мы рассматриваем реальное изменение, а не просто сдвиг в модели, эквивалентный простому перемещению пороговой границы. В нашем случае сравнение модели без ограничений с коэффициентом 0,5 и модели с ограничениями с коэффициентом 0,22 обеспечивает достоверное сравнение моделей.

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

Благодаря способности TFCO выражать более сложные требования в виде ограничения скорости мы помогли этой модели достичь более желаемого результата с небольшим влиянием на общую производительность. Конечно, есть еще место для совершенствования, но, по крайней мере, TFCO удалось найти модель, которая приближается к удовлетворению ограничения и максимально уменьшает неравенство между группами.