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

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

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

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

Настраивать

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

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

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

Импорт

Мы используем служебную функцию для загрузки предварительно обработанных данных и подготовки меток в соответствии с выходной формой модели. Эта функция также загружает данные в формате 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

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

Семена

Определение и обучение базовой модели

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

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 примерами в каждом разделении данных.

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

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