Сохраните дату! Google I / O возвращается 18-20 мая Зарегистрируйтесь сейчас
Эта страница переведена с помощью Cloud Translation API.
Switch to English

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

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

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

  • Обучите простую неограниченную модель нейронной сети для обнаружения улыбки человека на изображениях с помощью tf.keras и крупномасштабного набора данных CelebFaces Attributes ( 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 enabled by default.
TensorFlow 2.4.1
TFMA 0.29.0
TFDS 4.2.0
FI 0.29.0

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

CelebA - это крупномасштабный набор данных атрибутов лиц с более чем 200000 изображений знаменитостей, каждое из которых содержит 40 аннотаций атрибутов (например, тип волос, модные аксессуары, черты лица и т. Д.) И 5 ориентиров (положение глаз, рта и носа). Подробности смотрите в статье . С разрешения владельцев мы сохранили этот набор данных в Google Cloud Storage и в основном tfds к нему доступ через tfds ( 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 о некоммерческих исследованиях .

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

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

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

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

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

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

# 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 модель 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

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

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 для np.where . Проигнорируйте это предупреждение, поскольку TensorFlow решает эту tf.where в tf.where 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 [==============================] - 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>

Оценка модели на тестовых данных должна привести к окончательной оценке точности чуть более 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 [==============================] - 40s 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')
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)`

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

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

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

Здесь 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_modelsrnjadh_e/model_export_constrained/assets
INFO:tensorflow:Assets written to: /tmp/saved_modelsrnjadh_e/model_export_constrained/assets

Как и в предыдущий раз, мы использовали индикаторы справедливости, отмените выбор 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 удалось найти модель, которая приближается к удовлетворению ограничения и максимально сокращает неравенство между группами.