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

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

このノートブックは、TFCOライブラリを使用して制約された問題を作成および最適化する簡単な方法を示しています。我々は、彼らが我々が使用して識別することができ、当社のデータの異なるスライス間で同様にうまく行っていないことを見つけたとき、このメソッドは、モデルの改善に有用であることができ公正インジケータ。 GoogleのAI原則の2つ目は、Googleのテクノロジーは不公平なバイアスの作成や強化を回避する必要があると述べており、この手法は状況によってはモデルの公平性を向上させるのに役立つと考えています。特に、このノートブックは次のようになります。

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

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

インストール

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

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

Pipのインストール

以下のセルを実行するタイミングによっては、ColabのTensorFlowのデフォルトバージョンがTensorFlow2.Xにすぐに切り替わるという警告が表示される場合があることに注意してください。このノートブックはTensorFlow1.Xおよび2.Xと互換性があるように設計されているため、この警告は無視してかまいません。

モジュールのインポート

さらに、モデルのパフォーマンスを評価および視覚化するために使用する公平性インジケーターに固有のインポートをいくつか追加します。

TFCOはイーガー実行およびグラフ実行と互換性がありますが、このノートブックは、TensorFlow 2.xの場合と同様に、イーガー実行がデフォルトで有効になっていることを前提としています。何も壊れないようにするために、下のセルで熱心な実行が有効になります。

熱心な実行と印刷バージョンを有効にする

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

CelebAデータセット

CelebAは、大規模な顔をして5ランドマークの位置(目、口と鼻位置)(例えば髪のタイプ、ファッションアクセサリー、顔の特徴など)40の属性注釈を含む20万枚の以上の有名人の画像を、それぞれにデータセットを属性です。詳細については見とり紙を。所有者の許可を得て、我々はGoogleのクラウドストレージ上のデータセットを格納している、ほとんど経由でアクセスTensorFlowデータセット( tfds

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

  • 「笑顔」属性*で表されるように我々のモデルは、画像の被写体が笑っているかどうかを分類しようとします。
  • トレーニング時の実行時間とメモリを削減するために、画像のサイズが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 。 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を選択して、関心のあるメトリックを確認します。

モデルの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は、制約を満たすことに近づき、グループ間の格差を可能な限り減らすモデルを見つけることができました。