День сообщества ML - 9 ноября! Присоединяйтесь к нам для обновления от TensorFlow, JAX, и многое другое Подробнее

Пример из практики исправления

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

  1. Оцените эффективность нашей базовой модели для текста, содержащего ссылки на конфиденциальные группы.
  2. Повысьте производительность в любых отстающих группах, тренируясь с MinDiff.
  3. Оцените производительность новой модели по выбранной нами метрике.

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

Настраивать

Начнем с установки индикаторов справедливости и исправления модели TensorFlow.

Устанавливает

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

Импорт

Мы используем служебную функцию для загрузки предварительно обработанных данных и подготовки меток в соответствии с выходной формой модели. Функция также загружает данные в виде TFRecords, чтобы ускорить последующую оценку. Кроме того, вы можете преобразовать Pandas DataFrame в TFRecords с помощью любой доступной функции преобразования.

# We use a helper utility to preprocessed data for convenience and speed.
data_train, data_validate, validate_tfrecord_file, labels_train, labels_validate = min_diff_keras_utils.download_and_process_civil_comments_data()
Downloading data from https://storage.googleapis.com/civil_comments_dataset/train_df_processed.csv
345702400/345699197 [==============================] - 8s 0us/step
Downloading data from https://storage.googleapis.com/civil_comments_dataset/validate_df_processed.csv
229974016/229970098 [==============================] - 5s 0us/step
Downloading data from https://storage.googleapis.com/civil_comments_dataset/validate_tf_processed.tfrecord
324943872/324941336 [==============================] - 9s 0us/step

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

TEXT_FEATURE = 'comment_text'
LABEL = 'toxicity'
BATCH_SIZE = 512

Установите случайные семена. (Обратите внимание, что это не полностью стабилизирует результаты.)

Семена

Определите и обучите базовую модель

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

use_pretrained_model = True

if use_pretrained_model:
  URL = 'https://storage.googleapis.com/civil_comments_model/baseline_model.zip'
  BASE_PATH = tempfile.mkdtemp()
  ZIP_PATH = os.path.join(BASE_PATH, 'baseline_model.zip')
  MODEL_PATH = os.path.join(BASE_PATH, 'tmp/baseline_model')

  r = requests.get(URL, allow_redirects=True)
  open(ZIP_PATH, 'wb').write(r.content)

  with zipfile.ZipFile(ZIP_PATH, 'r') as zip_ref:
    zip_ref.extractall(BASE_PATH)
  baseline_model = tf.keras.models.load_model(
      MODEL_PATH, custom_objects={'KerasLayer' : hub.KerasLayer})
else:
  optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)
  loss = tf.keras.losses.BinaryCrossentropy()

  baseline_model = min_diff_keras_utils.create_keras_sequential_model()

  baseline_model.compile(optimizer=optimizer, loss=loss, metrics=['accuracy'])

  baseline_model.fit(x=data_train[TEXT_FEATURE],
                     y=labels_train,
                     batch_size=BATCH_SIZE,
                     epochs=20)

Сохраняем модель для оценки с помощью индикаторов справедливости .

base_dir = tempfile.mkdtemp(prefix='saved_models')
baseline_model_location = os.path.join(base_dir, 'model_export_baseline')
baseline_model.save(baseline_model_location, save_format='tf')
INFO:tensorflow:Assets written to: /tmp/saved_models867b8d74/model_export_baseline/assets
INFO:tensorflow:Assets written to: /tmp/saved_models867b8d74/model_export_baseline/assets

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

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

# We use a helper utility to hide the evaluation logic for readability.
base_dir = tempfile.mkdtemp(prefix='eval')
eval_dir = os.path.join(base_dir, 'tfma_eval_result')
eval_result = fi_util.get_eval_results(
    baseline_model_location, eval_dir, validate_tfrecord_file)
WARNING:absl:Tensorflow version (2.5.0) found. Note that TFMA support for TF 2.0 is currently in beta
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.7/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.7/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)`

Результаты оценки рендеринга

widget_view.render_fairness_indicator(eval_result)
FairnessIndicatorViewer(slicingMetrics=[{'sliceValue': 'Overall', 'slice': 'Overall', 'metrics': {'accuracy': …

Посмотрим на результаты оценки. Попробуйте выбрать показатель частоты ложных срабатываний (FPR) с порогом 0,450. Мы видим, что модель не работает так хорошо для некоторых религиозных групп, как для других, показывая гораздо более высокий FPR. Обратите внимание на широкие доверительные интервалы в некоторых группах, потому что у них слишком мало примеров. Из-за этого трудно с уверенностью сказать, что существует значительная разница в производительности для этих срезов. Возможно, мы захотим собрать больше примеров для решения этой проблемы. Однако мы можем попытаться применить MinDiff для двух групп, которые, как мы уверены, неэффективны.

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

Определение и обучение модели MinDiff

Теперь мы попытаемся улучшить FPR для неэффективных религиозных групп. Мы попытаемся сделать это с помощью MinDiff , метода исправления, который стремится сбалансировать частоту ошибок в срезах ваших данных, наказывая различия в производительности во время обучения. Когда мы применяем MinDiff, производительность модели может немного снизиться на других срезах. Таким образом, наши цели с MinDiff будут:

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

Подготовьте свои данные

Чтобы использовать MinDiff, мы создаем два дополнительных разделения данных:

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

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

В нашем случае группы меньшинств имеют примерное количество 9 688 и 3906 человек. Обратите внимание на несбалансированность классов в наборе данных; на практике это может быть поводом для беспокойства, но мы не будем пытаться рассматривать их в этой записной книжке, поскольку мы намерены просто продемонстрировать MinDiff.

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

Создание кадров данных MinDiff

# Create masks for the sensitive and nonsensitive groups
minority_mask = data_train.religion.apply(
    lambda x: any(religion in x for religion in ('jewish', 'muslim')))
majority_mask = data_train.religion.apply(lambda x: x == "['christian']")

# Select nontoxic examples, so MinDiff will be able to reduce sensitive FP rate.
true_negative_mask = data_train['toxicity'] == 0

data_train_main = copy.copy(data_train)
data_train_sensitive = data_train[minority_mask & true_negative_mask]
data_train_nonsensitive = data_train[majority_mask & true_negative_mask]

Нам также нужно преобразовать наши кадры данных Pandas в наборы данных Tensorflow для ввода MinDiff. Обратите внимание, что в отличие от API модели Keras для Pandas DataFrames, использование наборов данных означает, что нам необходимо предоставить входные функции и метки модели вместе в одном наборе данных. Здесь мы предоставляем 'comment_text' как входную функцию и изменяем форму метки в соответствии с ожидаемым результатом модели.

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

Создание наборов данных MinDiff

# Convert the pandas DataFrames to Datasets.
dataset_train_main = tf.data.Dataset.from_tensor_slices(
    (data_train_main['comment_text'].values, 
     data_train_main.pop(LABEL).values.reshape(-1,1) * 1.0)).batch(BATCH_SIZE)
dataset_train_sensitive = tf.data.Dataset.from_tensor_slices(
    (data_train_sensitive['comment_text'].values, 
     data_train_sensitive.pop(LABEL).values.reshape(-1,1) * 1.0)).batch(BATCH_SIZE)
dataset_train_nonsensitive = tf.data.Dataset.from_tensor_slices(
    (data_train_nonsensitive['comment_text'].values, 
     data_train_nonsensitive.pop(LABEL).values.reshape(-1,1) * 1.0)).batch(BATCH_SIZE)

Обучите и оцените модель

Чтобы тренироваться с MinDiff, просто возьмите исходную модель и оберните ее в MinDiffModel с соответствующими loss и loss_weight . Мы используем 1.5 в качестве значения по умолчанию loss_weight , но это параметр, который необходимо настроить для вашего loss_weight использования, поскольку он зависит от вашей модели и требований к продукту. Вы можете поэкспериментировать с изменением значения, чтобы увидеть, как оно влияет на модель, отметив, что его увеличение сближает производительность групп меньшинства и большинства, но может привести к более явным компромиссам.

Затем мы компилируем модель в обычном режиме (используя обычные потери, отличные от MinDiff) и подходим к обучению.

Поезд MinDiffModel

use_pretrained_model = True

base_dir = tempfile.mkdtemp(prefix='saved_models')
min_diff_model_location = os.path.join(base_dir, 'model_export_min_diff')

if use_pretrained_model:
  BASE_MIN_DIFF_PATH = tempfile.mkdtemp()
  MIN_DIFF_URL = 'https://storage.googleapis.com/civil_comments_model/min_diff_model.zip'
  ZIP_PATH = os.path.join(BASE_PATH, 'min_diff_model.zip')
  MIN_DIFF_MODEL_PATH = os.path.join(BASE_MIN_DIFF_PATH, 'tmp/min_diff_model')
  DIRPATH = '/tmp/min_diff_model'

  r = requests.get(MIN_DIFF_URL, allow_redirects=True)
  open(ZIP_PATH, 'wb').write(r.content)

  with zipfile.ZipFile(ZIP_PATH, 'r') as zip_ref:
    zip_ref.extractall(BASE_MIN_DIFF_PATH)
  min_diff_model = tf.keras.models.load_model(
      MIN_DIFF_MODEL_PATH, custom_objects={'KerasLayer' : hub.KerasLayer})

  min_diff_model.save(min_diff_model_location, save_format='tf')

else:
  min_diff_weight = 1.5

  # Create the dataset that will be passed to the MinDiffModel during training.
  dataset = md.keras.utils.input_utils.pack_min_diff_data(
      dataset_train_main, dataset_train_sensitive, dataset_train_nonsensitive)

  # Create the original model.
  original_model = min_diff_keras_utils.create_keras_sequential_model()

  # Wrap the original model in a MinDiffModel, passing in one of the MinDiff
  # losses and using the set loss_weight.
  min_diff_loss = md.losses.MMDLoss()
  min_diff_model = md.keras.MinDiffModel(original_model,
                                         min_diff_loss,
                                         min_diff_weight)

  # Compile the model normally after wrapping the original model.  Note that
  # this means we use the baseline's model's loss here.
  optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)
  loss = tf.keras.losses.BinaryCrossentropy()
  min_diff_model.compile(optimizer=optimizer, loss=loss, metrics=['accuracy'])

  min_diff_model.fit(dataset, epochs=20)

  min_diff_model.save_original_model(min_diff_model_location, save_format='tf')
INFO:tensorflow:Assets written to: /tmp/saved_modelsb3zkcos_/model_export_min_diff/assets
INFO:tensorflow:Assets written to: /tmp/saved_modelsb3zkcos_/model_export_min_diff/assets

Далее оцениваем результаты.

min_diff_eval_subdir = os.path.join(base_dir, 'tfma_eval_result')
min_diff_eval_result = fi_util.get_eval_results(
    min_diff_model_location,
    min_diff_eval_subdir,
    validate_tfrecord_file,
    slice_selection='religion')
WARNING:absl:Tensorflow version (2.5.0) found. Note that TFMA support for TF 2.0 is currently in beta

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

widget_view.render_fairness_indicator(min_diff_eval_result)
FairnessIndicatorViewer(slicingMetrics=[{'sliceValue': 'Overall', 'slice': 'Overall', 'metrics': {'accuracy': …

Просматривая эти результаты, вы можете заметить, что показатели FPR для наших целевых групп улучшились. Разрыв между нашей группой с самыми низкими показателями и группой большинства увеличился с 0,024 до 0,006. С учетом наблюдаемых нами улучшений и стабильно высоких показателей группы большинства мы достигли обеих наших целей. В зависимости от продукта могут потребоваться дальнейшие улучшения, но такой подход приблизил нашу модель к справедливой работе для всех пользователей.