Google I / Oの基調講演、製品セッション、ワークショップなどを見るプレイリストを見る

CelebAデータセットを使用したTensorFlow制約付き最適化の例

TensorFlow.orgで表示 GoogleColabで実行 GitHubで表示ノートブックをダウンロードする

このノートブックは、TFCOライブラリを使用して制約された問題を作成および最適化する簡単な方法を示しています。この方法は、データのさまざまなスライス間でモデルのパフォーマンスが等しくないことがわかった場合に、モデルを改善するのに役立ちます。これは、公平性インジケーターを使用して識別できます。 GoogleのAI原則の2つ目は、Googleのテクノロジーは不公平なバイアスの作成や強化を回避する必要があると述べており、この手法は状況によってはモデルの公平性を向上させるのに役立つと考えています。特に、このノートブックは次のことを行います。

  • tf.kerasと大規模なCelebFaces属性( CelebA )データセットを使用して、画像内の人の笑顔を検出するための、単純で制約のないニューラルネットワークモデルをtf.kerasします。
  • 公平性指標を使用して、年齢層全体で一般的に使用される公平性指標に対してモデルのパフォーマンスを評価します。
  • 単純な制約付き最適化問題を設定して、年齢グループ全体でより公平なパフォーマンスを実現します。
  • 現在制約されているモデルを再トレーニングし、パフォーマンスを再度評価して、選択した公平性メトリックが改善されていることを確認します。

最終更新日:2020年2月3/11日

インストール

このノートブックは、Python 3 Google ComputeEngineバックエンドに接続されたColaboratoryで作成されました。このノートブックを別の環境でホストする場合は、必要なすべてのパッケージを以下のセルに含めれば、大きな問題は発生しません。

初めてpipインストールを実行すると、古いパッケージがプリインストールされているため、ランタイムを再起動するように求められる場合があることに注意してください。そうすると、正しいパッケージが使用されます。

Pipのインストール

以下のセルを実行するタイミングによっては、ColabのTensorFlowのデフォルトバージョンがTensorFlow2.Xにすぐに切り替わるという警告が表示される場合があることに注意してください。このノートブックはTensorFlow1.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は、200,000を超える有名人の画像を含む大規模な顔属性データセットであり、それぞれに40の属性注釈(髪のタイプ、ファッションアクセサリー、顔の特徴など)と5つのランドマーク位置(目、口、鼻の位置)があります。詳細については、論文をご覧ください。所有者の許可を得て、このデータセットをGoogle Cloud Storageに保存し、ほとんどの場合TensorFlow Datasets( tfds )を介してアクセスします。

このノートブックの内容:

  • 私たちのモデルは、「Smiling」属性*で表されるように、画像の被写体が笑顔であるかどうかを分類しようとします。
  • トレーニング時の実行時間とメモリを削減するために、画像のサイズが218x178から28x28に変更されます。
  • モデルのパフォーマンスは、バイナリの「Young」属性を使用して、年齢グループ全体で評価されます。このノートブックでは、これを「年齢層」と呼びます。

*このデータセットのラベル付け方法について入手できる情報はほとんどありませんが、「笑顔」属性は、被験者の顔の満足、親切、または面白がった表現によって決定されたと想定します。このケーススタディの目的のために、これらのラベルをグラウンドトゥルースと見なします。

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のすべての属性注釈は、バイナリカテゴリとして操作可能です。たとえば、「Young」属性(データセットラベラーによって決定される)は、画像に存在するか存在しないかのいずれかとして示されます。
  • CelebAの分類は、実際の人間の属性の多様性を反映していません。
  • このノートブックでは、「Young」属性を含む機能を「age group」と呼びます。ここで、画像内の「Young」属性の存在は、「Young」年齢グループのメンバーとしてラベル付けされ、 「Young」属性がない場合は、「NotYoung」年齢グループのメンバーとしてラベル付けされます。これらは、この情報が元の論文に記載されていないために行われた仮定です。
  • そのため、このノートブックでトレーニングされたモデルのパフォーマンスは、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モデルを組み立てます。

複雑さを追加することでモデルのパフォーマンスを大幅に向上させることができるかもしれませんが(たとえば、より密に接続されたレイヤー、さまざまな活性化関数の探索、画像サイズの増加)、TFCOライブラリの適用がいかに簡単かを示すという目標から外れる可能性がありますKerasで作業するとき。そのため、モデルはシンプルに保たれますが、この空間を探索することをお勧めします。

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の非推奨警告が発生する可能性があることに注意してnp.wheretf.wherenp.where代わりにnp.whereを使用して、2.Xでこれに対処するため、この警告は安全に無視して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を選択して、関心のあるメトリックを確認します。

モデルの2つのバージョンを公平に比較​​するには、全体的な偽陽性率をほぼ等しく設定するしきい値を使用することが重要であることに注意してください。これにより、しきい値の境界を移動するだけのモデルのシフトではなく、実際の変更を確認できます。私たちの場合、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は、制約を満たすことに近づき、グループ間の格差を可能な限り減らすモデルを見つけることができました。