In [0]:
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

ML 공정성 소개


면책조항

이 실습에서는 머신러닝의 공정성과 관련된 아이디어와 기법의 일부분만을 살펴보며 전체 내용을 다루지는 않습니다.

학습 목표

  • 모델 데이터에서 나타날 수 있는 여러 유형의 편향에 대한 이해도를 높입니다.
  • 모델을 학습시키기 전에 특성 데이터를 살펴보고 잠재적 데이터 편향 요인을 미리 파악합니다.
  • 종합집계하는 대신 하위 그룹으로 묶어 모델 성능을 평가합니다.

개요

이 실습에서는 머신러닝(ML)에 원치 않는 편향이 발생할 수 있는 방식에 주목하면서 공정성을 염두에 두고 데이터세트를 살펴보고 분류자를 평가합니다.

실습 전반에서 공정성에 관한 ML 프로세스의 컨텍스트를 구성할 기회를 제공하는 FairAware 작업을 보게 됩니다. 작업을 진행하는 동안 편향을 파악하고, 이러한 편향이 해결되지 않을 때 발생하는 모델 예측의 장기적인 영향을 고려하게 됩니다.

데이터세트 및 예측 작업 정보

이 실습에서는 머신러닝 자료로 자주 사용되는 성인 인구조사 소득 데이터세트를 활용합니다. 이 데이터는 로니 코하비와 배리 베커의 1994 인구조사국 데이터베이스에서 추출한 데이터입니다.

데이터세트의 각 예에는 1994 인구조사에 참여한 사람들에 관한 다음 인구통계 데이터가 포함되어 있습니다.

수적 특성

  • age: 개인의 나이를 연 단위로 나타냅니다.
  • fnlwgt: 일련의 관측 결과를 바탕으로 인구조사국이 부여하는 개인의 가중치입니다.
  • education_num: 교육 수준을 숫자로 범주화하여 열거합니다. 숫자가 높을수록 개인의 교육 수준이 높습니다. 예를 들어 education_num11이면 Assoc_voc(전문학교 준학사)를, education_num13이면 Bachelors(학사)를, education_num9이면 HS-grad(고등학교 졸업)를 나타냅니다.
  • capital_gain: 개인의 자본 이익을 미국 달러로 표기합니다.
  • capital_loss: 개인의 자본 손실을 미국 달러로 표기합니다.
  • hours_per_week: 주당 근무시간입니다.

범주형 특성

  • workclass: 개인의 고용 형태입니다. 예: Private, Self-emp-not-inc, Self-emp-inc, Federal-gov, Local-gov, State-gov, Without-pay, Never-worked
  • education: 개인의 최종 학력입니다.
  • marital_status: 개인의 결혼 여부입니다. 예: Married-civ-spouse, Divorced, Never-married, Separated, Widowed, Married-spouse-absent, Married-AF-spouse
  • occupation: 개인의 직업입니다. 예: tech-support, Craft-repair, Other-service, Sales, Exec-managerial
  • relationship: 가정 내 각 개인의 관계입니다. 예: Wife, Own-child, Husband, Not-in-family, Other-relative, Unmarried
  • gender: 개인의 성별로 Female 또는 Male 중에서만 선택할 수 있습니다.
  • race: 인종을 나타내며 White, Asian-Pac-Islander, Amer-Indian-Eskimo, Black, Other가 있습니다.
  • native_country: 개인의 출신 국가입니다. 예: United-States, Cambodia, England, Puerto-Rico, Canada, Germany, Outlying-US(Guam-USVI-etc), India, Japan, United-States, Cambodia, England, Puerto-Rico, Canada, Germany, Outlying-US(Guam-USVI-etc), India, Japan

예측 작업

예측 작업은 개인의 연간 소득이 미화 50,000달러 이상인지 확인하기 위해 실행됩니다.

라벨

  • income_bracket: 개인의 연간 소득이 미화 50,000달러가 넘는지 여부를 나타냅니다.

데이터 수집 관련 참고사항

이 데이터세트를 위해 추출된 모든 예는 다음 조건을 충족합니다.

  • age가 16세 이상입니다.
  • 보정된 총 연간 소득(income_bracket 계산에 사용)이 미화 100달러를 넘습니다.
  • fnlwgt이 0보다 큽니다.
  • hours_per_week가 0보다 큽니다.

설정

먼저 이 노트 전반에서 사용할 모듈을 가져옵니다.


In [0]:
import os
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import tensorflow as tf
import tempfile
!pip install seaborn==0.8.1
import seaborn as sns
import itertools
from sklearn.metrics import confusion_matrix
from sklearn.metrics import roc_curve, roc_auc_score
from sklearn.metrics import precision_recall_curve
from google.colab import widgets
# For facets
from IPython.core.display import display, HTML
import base64
!pip install facets-overview==1.0.0
from facets_overview.feature_statistics_generator import FeatureStatisticsGenerator

print('Modules are imported.')

성인 데이터세트 로드

모듈을 가져온 후에는 pandas DataFrame 데이터 구조로 성인 데이터세트를 로드할 수 있습니다.


In [0]:
COLUMNS = ["age", "workclass", "fnlwgt", "education", "education_num",
           "marital_status", "occupation", "relationship", "race", "gender",
           "capital_gain", "capital_loss", "hours_per_week", "native_country",
           "income_bracket"]

train_df = pd.read_csv(
    "https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.data",
    names=COLUMNS,
    sep=r'\s*,\s*',
    engine='python',
    na_values="?")
test_df = pd.read_csv(
    "https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.test",
    names=COLUMNS,
    sep=r'\s*,\s*',
    skiprows=[0],
    engine='python',
    na_values="?")

# Drop rows with missing values
train_df = train_df.dropna(how="any", axis=0)
test_df = test_df.dropna(how="any", axis=0)

print('UCI Adult Census Income dataset loaded.')

Facets로 성인 데이터세트 분석

MLCC에서 언급했듯이 예측 작업을 시작하기 전에 데이터세트를 이해하는 것이 중요합니다.

다음은 데이터세트의 공정성을 검사할 때 확인해야 할 몇 가지 중요한 질문입니다.

  • 다수의 관측에서 누락된 특성 값이 있나요?
  • 다른 특성에 영향을 미칠 수 있는 특성이 누락되었나요?
  • 예기치 않은 특성 값이 있나요?
  • 어떤 데이터 격차의 신호가 발견되나요?

데이터세트를 살펴보는 데 도움을 주는 대화형 시각화 도구인 Facets Overview를 사용하여 시작할 수 있습니다. Facets Overview를 통해 성인 데이터세트 전반의 값 분포를 신속하게 분석할 수 있습니다.


In [0]:
#@title Visualize the Data in Facets
fsg = FeatureStatisticsGenerator()
dataframes = [
    {'table': train_df, 'name': 'trainData'}]
censusProto = fsg.ProtoFromDataFrames(dataframes)
protostr = base64.b64encode(censusProto.SerializeToString()).decode("utf-8")


HTML_TEMPLATE = """<script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.3.3/webcomponents-lite.js"></script>
        <link rel="import" href="https://raw.githubusercontent.com/PAIR-code/facets/1.0.0/facets-dist/facets-jupyter.html">
        <facets-overview id="elem"></facets-overview>
        <script>
          document.querySelector("#elem").protoInput = "{protostr}";
        </script>"""
html = HTML_TEMPLATE.format(protostr=protostr)
display(HTML(html))

FairAware 작업 #1

각 수적 특성 및 연속 특성의 기술 통계량과 히스토그램을 검토하세요. 범주형 특성의 히스토그램 위에 있는 원시 데이터 표시 버튼을 클릭하여 범주별 값 분포를 확인하세요.

그런 다음 이전에 살펴봤던 아래 질문에 답변해 보세요.

  1. 다수의 관측에서 누락된 특성 값이 있나요?
  2. 다른 특성에 영향을 미칠 수 있는 특성이 누락되었나요?
  3. 예기치 않은 특성 값이 있나요?
  4. 어떤 데이터 격차의 신호가 발견되나요?

솔루션

발견된 몇 가지 정보를 보려면 아래를 클릭하세요.

수적 특성 및 범주형 특성의 누락된 열을 검토한 결과 누락된 특성 값이 없다는 사실을 확인할 수 있으므로 여기에서는 문제가 되지 않습니다.

각 수적 특성의 최솟값/최댓값과 히스토그램을 살펴보면 데이터세트에 있는 극단적 이상점을 정확히 찾아낼 수 있습니다. hours_per_week의 경우 최솟값이 1인 것을 확인할 수 있는데, 대부분 직업은 주당 근무시간이 여러 시간이라는 점을 고려하면 이상하다고 생각할 수 있습니다. capital_gaincapital_loss의 경우 값의 90% 이상이 0인 것을 볼 수 있습니다. 자본 이익/손실은 투자를 하는 개인에게서만 발생한다는 점을 고려하면 이 예에서 10% 미만이 이 특성에 0이 아닌 값을 갖는 것이 충분히 가능하긴 하지만, 이 특성 값이 올바른지 확인하기 위해 더 자세히 살펴보는 것이 좋습니다.

성별 히스토그램을 살펴보면 예에서 2/3(약 67%) 이상이 남성인 것으로 나타납니다. 50/50에 가까울 것으로 예상되는 성비를 생각하면 여기에 데이터 격차가 있을 가능성이 높다고 볼 수 있습니다.

더 깊이 알아보기

데이터세트를 더 자세히 살펴보기 위해 Facets Dive를 사용할 수 있습니다. 이 도구는 시각화된 개별 항목이 하나의 데이터 포인트를 나타내는 대화형 인터페이스를 제공합니다. 그러나 Facets Dive를 사용하려면 데이터를 JSON 배열로 변환해야 합니다. 고맙게도 DataFrame의 to_json() 메소드가 이 작업을 대신해 줍니다.

아래 셀을 실행하여 JSON으로 데이터를 변환하고 Facets Dive도 로드하세요.


In [0]:
#@title Set the Number of Data Points to Visualize in Facets Dive

SAMPLE_SIZE = 2500 #@param
  
train_dive = train_df.sample(SAMPLE_SIZE).to_json(orient='records')

HTML_TEMPLATE = """<script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.3.3/webcomponents-lite.js"></script>
        <link rel="import" href="https://raw.githubusercontent.com/PAIR-code/facets/1.0.0/facets-dist/facets-jupyter.html">
        <facets-dive id="elem" height="600"></facets-dive>
        <script>
          var data = {jsonstr};
          document.querySelector("#elem").data = data;
        </script>"""
html = HTML_TEMPLATE.format(jsonstr=train_dive)
display(HTML(html))

FairAware 작업 #2

시각화 이미지의 왼쪽 패널에 있는 메뉴를 사용하여 데이터 구성 방식을 변경하세요.

  1. Faceting | X-Axis 메뉴에서 education을 선택하고, Display | ColorDisplay | Type 메뉴에서 income_bracket을 선택합니다. 교육 수준과 소득계층 사이의 상관관계를 어떻게 설명할 수 있나요?

  2. 다음으로 Faceting | X-Axis 메뉴에서 marital_status를 선택하고, Display | ColorDisplay | Type 메뉴에서 gender를 선택합니다. 각 결혼 여부 범주의 성 분포와 관련하여 어떤 주목할 만한 점이 관찰되나요?

위의 작업을 하는 동안 공정성과 관련된 다음 질문을 염두에 두세요

  • 누락된 특성은 무엇인가요?
  • 지나치게 일반화된 특성은 무엇인가요?
  • 충분히 대표되지 않은 특성은 무엇인가요?
  • 변수와 그 값이 실제 값을 어느 정도 반영하나요?
  • 무엇을 빠뜨렸나요?

솔루션

발견된 몇 가지 정보를 보려면 아래를 클릭하세요.

  1. 데이터세트에서 교육 수준이 높을수록 일반적으로 소득계층도 높게 나타나는 경향이 있습니다. 예에서는 소득 수준이 50,000달러가 넘는 사람들이 학사 이상의 교육 수준인 경우가 더욱 두드러지게 나타납니다.

  2. 대부분의 결혼 여부 범주에서 남성과 여성의 분포는 1:1에 가깝습니다. 그러나 'married-civ-spouse' 범주의 경우 5:1이 넘는 비율로 남성의 수가 더 많습니다. 작업 #1에서 데이터세트의 남성 비율이 불균형적으로 높은 것을 이미 확인했으므로 결혼한 여성이 데이터에 특히 제대로 나타나지 않았다고 추론할 수 있습니다.

요약

히스토그램 그래프를 만들고, 출현 빈도가 높은 예부터 순위를 매기고, 중복되거나 누락된 예를 파악하고, 학습 및 테스트 세트가 유사한지 확인하고, 특성 분위를 계산하는 과정 모두 데이터에서 실행할 중요한 분석 과정입니다.

데이터의 상황을 더 잘 이해할수록 어디에서 불공정성이 나타날 수 있는지에 대한 더 많은 정보를 얻을 수 있습니다.

FairAware 작업 #3

Facets를 사용하여 데이터세트를 살펴보았고, 이제 특성에 관해 학습한 내용을 바탕으로 공정성과 관련하여 발생할 수 있는 몇 가지 문제를 파악할 수 있는지 알아보겠습니다.

다음 중 공정성 관련 문제를 일으킬 수 있는 특성은 무엇인가요?

아래 셀의 드롭다운 옵션에서 특성을 선택한 후 셀을 실행하여 답을 확인해 보세요. 그런 다음 나머지 옵션을 확인하여 각 특성이 모델의 예측에 어떻게 영향을 미칠 수 있는지 자세히 알아보세요.


In [0]:
feature = 'capital_gain / capital_loss' #@param ["", "hours_per_week", "fnlwgt", "gender", "capital_gain / capital_loss", "age"] {allow-input: false}


if feature == "hours_per_week":
  print(
'''It does seem a little strange to see 'hours_per_week' max out at 99 hours,
which could lead to data misrepresentation. One way to address this is by
representing 'hours_per_week' as a binary "working 40 hours/not working 40
hours" feature. Also keep in mind that data was extracted based on work hours
being greater than 0. In other words, this feature representation exclude a
subpopulation of the US that is not working. This could skew the outcomes of the
model.''')
if feature == "fnlwgt":
  print(
"""'fnlwgt' represents the weight of the observations. After fitting the model
to this data set, if certain group of individuals end up performing poorly 
compared to other groups, then we could explore ways of reweighting each data 
point using this feature.""")
if feature == "gender":
  print(
"""Looking at the ratio between men and women shows how disproportionate the data
is compared to the real world where the ratio (at least in the US) is closer to
1:1. This could pose a huge probem in performance across gender. Considerable
measures may need to be taken to upsample the underrepresented group (in this
case, women).""")
if feature == "capital_gain / capital_loss":
  print(
"""Both 'capital_gain' and 'capital_loss' have very low variance, which might
suggest they don't contribute a whole lot of information for predicting income. It
may be okay to omit these features rather than giving the model more noise.""")
if feature == "age":
  print(
'''"age" has a lot of variance, so it might benefit from bucketing to learn
fine-grained correlations between income and age, as well as to prevent
overfitting.''')

텐서플로우 에스티메이터를 사용하여 예측하기

성인 데이터세트를 충분히 파악했으므로 이제 신경망을 만들어 소득을 예측할 수 있습니다. 이 섹션에서는 텐서플로우의 Estimator API를 사용하여 DNNClassifier 클래스에 액세스합니다.

성인 데이터세트를 텐서로 변환

우선 pandas DataFrame에 있는 성인 데이터세트를 tf.estimator.inputs.pandas_input_fn() 함수를 사용하여 텐서로 변환할 입력 함수를 정의해야 합니다.


In [0]:
def csv_to_pandas_input_fn(data, batch_size=100, num_epochs=1, shuffle=False):
  return tf.estimator.inputs.pandas_input_fn(
      x=data.drop('income_bracket', axis=1),
      y=data['income_bracket'].apply(lambda x: ">50K" in x).astype(int),
      batch_size=batch_size,
      num_epochs=num_epochs,
      shuffle=shuffle,
      num_threads=1)

print('csv_to_pandas_input_fn() defined.')

텐서플로우에서 특성 표현

텐서플로우에서는 데이터가 모델에 매핑되어야 합니다. 이를 위해서는 tf.feature_columns를 사용하여 텐서플로우에서 특성을 처리하고 표현해야 합니다.


In [0]:
#@title Categorical Feature Columns

# Since we don't know the full range of possible values with occupation and
# native_country, we'll use categorical_column_with_hash_bucket() to help map
# each feature string into an integer ID.
occupation = tf.feature_column.categorical_column_with_hash_bucket(
    "occupation", hash_bucket_size=1000)
native_country = tf.feature_column.categorical_column_with_hash_bucket(
    "native_country", hash_bucket_size=1000)

# For the remaining categorical features, since we know what the possible values
# are, we can be more explicit and use categorical_column_with_vocabulary_list()
gender = tf.feature_column.categorical_column_with_vocabulary_list(
    "gender", ["Female", "Male"])
race = tf.feature_column.categorical_column_with_vocabulary_list(
    "race", [
        "White", "Asian-Pac-Islander", "Amer-Indian-Eskimo", "Other", "Black"
    ])
education = tf.feature_column.categorical_column_with_vocabulary_list(
    "education", [
        "Bachelors", "HS-grad", "11th", "Masters", "9th",
        "Some-college", "Assoc-acdm", "Assoc-voc", "7th-8th",
        "Doctorate", "Prof-school", "5th-6th", "10th", "1st-4th",
        "Preschool", "12th"
    ])
marital_status = tf.feature_column.categorical_column_with_vocabulary_list(
    "marital_status", [
        "Married-civ-spouse", "Divorced", "Married-spouse-absent",
        "Never-married", "Separated", "Married-AF-spouse", "Widowed"
    ])
relationship = tf.feature_column.categorical_column_with_vocabulary_list(
    "relationship", [
        "Husband", "Not-in-family", "Wife", "Own-child", "Unmarried",
        "Other-relative"
    ])
workclass = tf.feature_column.categorical_column_with_vocabulary_list(
    "workclass", [
        "Self-emp-not-inc", "Private", "State-gov", "Federal-gov",
        "Local-gov", "?", "Self-emp-inc", "Without-pay", "Never-worked"
    ])

print('Categorical feature columns defined.')

In [0]:
#@title Numeric Feature Columns
# For Numeric features, we can just call on feature_column.numeric_column()
# to use its raw value instead of having to create a map between value and ID.
age = tf.feature_column.numeric_column("age")
fnlwgt = tf.feature_column.numeric_column("fnlwgt")
education_num = tf.feature_column.numeric_column("education_num")
capital_gain = tf.feature_column.numeric_column("capital_gain")
capital_loss = tf.feature_column.numeric_column("capital_loss")
hours_per_week = tf.feature_column.numeric_column("hours_per_week")

print('Numeric feature columns defined.')

연령을 범주형 특성으로 만들기

FairAware 작업 #3을 완료할 때 age를 선택한 경우, age의 경우 비슷한 연령을 서로 다른 그룹으로 묶는 버케팅(비닝이라고도 함)을 사용하면 더욱 유용할 수 있다는 제안을 확인하셨을 겁니다. 이 방식을 이용하면 모델이 전체 연령대에 걸쳐 더욱 효과적으로 일반화할 수 있게 됩니다. 따라서 age를 수적 특성(기술적으로는 서수형 특성)에서 범주형 특성으로 변환하겠습니다.


In [0]:
age_buckets = tf.feature_column.bucketized_column(
    age, boundaries=[18, 25, 30, 35, 40, 45, 50, 55, 60, 65])

주요 하위 그룹 고려

특성 추출을 실행할 때 별도로 모델 성능을 평가하려는 하위 그룹에 속한 개인에게서 가져온 데이터로 작업한다는 점을 유의해야 합니다.

참고: 이 컨텍스트에서 하위 그룹은 공정성을 염두에 두고 모델을 평가할 때 특별히 고려할 만한 특성(예: 인종, 성별, 성적 지향 등)을 공유하는 개인들의 그룹으로 정의됩니다.

모델이 하위 그룹과 관련된 특성의 학습된 신호를 완화하거나 활용하도록 하기 위해 여러 유형의 도구와 기법을 사용할 수 있습니다. 이러한 도구와 기법 대부분은 아직 연구가 진행 중입니다.

여러 변수를 사용하여 작업하고 이러한 변수에 따라 작업을 정의하면서 변수와 작업의 상호작용이 문제가 될 수 있는 곳이 어디인가?와 같은 다음 질문을 생각하면 도움이 될 수 있습니다.

모델 특성 정의

이제 모델에 포함할 특성을 명시적으로 정의할 수 있습니다.

gender를 하위 그룹으로 고려하고 별도의 subgroup_variables 목록에 저장하므로 필요한 경우 특수 처리를 추가할 수 있습니다.


In [0]:
# List of variables, with special handling for gender subgroup.
variables = [native_country, education, occupation, workclass,
             relationship, age_buckets]
subgroup_variables = [gender]
feature_columns = variables + subgroup_variables

성인 데이터세트에서 심층 신경망 모델 학습시키기

이제 활용 가능한 특성을 바탕으로 딥 러닝을 사용하여 소득을 예측해 볼 수 있습니다.

단순성을 위해 두 개의 히든 레이어로 피드포워드 신경망을 정의함으로써 신경망 아키텍처를 간단하게 유지하겠습니다.

그러나 먼저 고차원 범주형 특성을 임베딩 벡터라고 부르는 저차원의 밀집 실수 벡터로 변환해야 합니다. 다행히도 indicator_column(원-핫 인코딩으로 간주)과 embedding_column(희소 특성을 밀집 특성으로 변환)의 도움을 받아 절차를 간소화할 수 있습니다.

아래 셀은 계속해서 모델을 정의하는 데 필요한 심층 열을 생성합니다.


In [0]:
deep_columns = [
    tf.feature_column.indicator_column(workclass),
    tf.feature_column.indicator_column(education),
    tf.feature_column.indicator_column(age_buckets),
    tf.feature_column.indicator_column(gender),
    tf.feature_column.indicator_column(relationship),
    tf.feature_column.embedding_column(native_country, dimension=8),
    tf.feature_column.embedding_column(occupation, dimension=8),
]

print(deep_columns)
print('Deep columns created.')

모든 데이터 사전 처리를 진행했으므로 이제 심층 신경망 모델을 정의할 수 있습니다. 아래 정의된 매개변수를 사용하여 시작하세요. 나중에 평가 측정항목을 정의하고 모델을 평가한 후 다시 매개변수를 조정하여 결과를 비교할 수 있습니다.


In [0]:
#@title Define Deep Neural Net Model

HIDDEN_UNITS = [1024, 512] #@param
LEARNING_RATE = 0.1 #@param
L1_REGULARIZATION_STRENGTH = 0.0001 #@param
L2_REGULARIZATION_STRENGTH = 0.0001 #@param

model_dir = tempfile.mkdtemp()
single_task_deep_model = tf.estimator.DNNClassifier(
    feature_columns=deep_columns,
    hidden_units=HIDDEN_UNITS,
    optimizer=tf.train.ProximalAdagradOptimizer(
      learning_rate=LEARNING_RATE,
      l1_regularization_strength=L1_REGULARIZATION_STRENGTH,
      l2_regularization_strength=L2_REGULARIZATION_STRENGTH),
    model_dir=model_dir)

print('Deep neural net model defined.')

절차를 간단히 하기 위해 여기에서는 1,000개 단계를 학습시키지만, 이 매개변수는 얼마든지 조정할 수 있습니다.


In [0]:
#@title Fit Deep Neural Net Model to the Adult Training Dataset

STEPS = 1000 #@param

single_task_deep_model.train(
    input_fn=csv_to_pandas_input_fn(train_df, num_epochs=None, shuffle=True),
    steps=STEPS);

print('Deep neural net model is done fitting.')

이제 홀드아웃 테스트 세트를 사용하여 전체 모델의 성능을 평가할 수 있습니다.


In [0]:
#@title Evaluate Deep Neural Net Performance

results = single_task_deep_model.evaluate(
    input_fn=csv_to_pandas_input_fn(test_df, num_epochs=1, shuffle=False),
    steps=None)
print("model directory = %s" % model_dir)
print("---- Results ----")
for key in sorted(results):
  print("%s: %s" % (key, results[key]))

다른 매개변수를 사용하여 모델을 다시 학습시켜 볼 수 있습니다. 결국에는 심층 신경망이 소득을 꽤 정확하게 예측한다는 사실을 확인하게 될 것입니다.

그러나 여기에서는 하위 그룹에 관한 평가 측정항목이 누락되어 있습니다. 다음 섹션을 통해 하위 그룹 수준에서 평가하는 몇 가지 방법을 살펴보겠습니다.

혼동 행렬을 사용하여 공정성 평가

모델의 전반적인 성능을 평가하면 모델의 품질에 관해 알 수 있지만, 모델이 여러 하위 그룹에서는 어떤 성과를 보이는지에 관해서는 충분한 정보를 얻을 수 없습니다.

모델의 공정성을 평가할 때는 예측 오류가 하위 그룹 전체에서 동일하게 나타나는지 또는 특정 하위 그룹이 다른 하위 그룹보다 특정 예측 오류에 더 민감한지 파악하는 것이 중요합니다.

여러 유형의 모델 오류 발생률을 비교하는 데 중요한 도구가 바로 혼동 행렬입니다. 머신러닝 단기집중과정의 분류 모듈에서는 혼동 행렬을 모델의 예측 및 실측 결과를 비교하여 그래프로 나타내고, 모델이 올바른 예측을 하는 빈도와 잘못된 예측을 하는 빈도를 요약하여 표로 보여 주는 그리드라고 설명합니다.

우선 소득 예측 모델을 위한 바이너리 혼동 행렬을 만듭니다. 바이너리를 사용하는 이유는 라벨(income_bracket)에 두 값(<50K 또는 >50K)만 지정될 수 있기 때문입니다. >50K 소득을 양성 라벨로, <50k 소득을 음성 라벨로 정의하겠습니다.

참고: 이 컨텍스트에서 양성음성을 가치 판단으로 해석해서는 안 됩니다. 즉, 연간 소득이 50,000달러 이상인 사람이 50,000달러 미만인 사람보다 더 뛰어나다는 의미가 아닙니다. 이 용어는 단지 모델에서 내릴 수 있는 두 가지 예측을 구분하기 위해 사용되는 표준 용어입니다.

모델이 올바른 예측(예측이 실측 결과와 일치)을 하는 경우는 으로, 모델이 잘못된 예측을 하는 경우는 거짓으로 분류됩니다.

따라서 혼동 행렬은 다음과 같은 4가지 상태를 표현할 수 있습니다.

  • 참양성: 모델이 >50K를 예측하고 실측 결과와 일치합니다.
  • 참음성: 모델이 <50K를 예측하고 실측 결과와 일치합니다.
  • 거짓양성: 모델이 >50K를 예측하지만 실측 결과와 다릅니다.
  • 거짓음성: 모델이 <50K를 예측하지만 실측 결과와 다릅니다.

참고: 필요한 경우 각 상태의 결과 개수를 사용하여 정밀도와 재현율 등의 보조 평가 측정항목을 계산할 수 있습니다.

혼동 행렬 그래프 그리기

다음 셀은 바이너리 혼동 행렬과 평가 측정항목을 컴퓨팅하는 데 필요한 모든 사례(참양성, 참음성, 거짓양성, 거짓음성)를 계산하기 위해 sklearn.metrics.confusion_matrix 모듈을 사용하는 함수를 정의합니다.


In [0]:
#@test {"output": "ignore"}
#@title Define Function to Compute Binary Confusion Matrix Evaluation Metrics
def compute_eval_metrics(references, predictions):
  tn, fp, fn, tp = confusion_matrix(references, predictions).ravel()
  precision = tp / float(tp + fp)
  recall = tp / float(tp + fn)
  false_positive_rate = fp / float(fp + tn)
  false_omission_rate = fn / float(tn + fn)
  return precision, recall, false_positive_rate, false_omission_rate

print('Binary confusion matrix and evaluation metrics defined.')

또한 바이너리 혼동 행렬의 그래프를 그리는 데도 도움이 필요합니다. 아래 함수는 여러 타사 모듈(pandas DataFame, Matplotlib, Seaborn)을 결합하여 혼동 행렬을 그립니다.


In [0]:
#@title Define Function to Visualize Binary Confusion Matrix
def plot_confusion_matrix(confusion_matrix, class_names, figsize = (8,6)):
    # We're taking our calculated binary confusion matrix that's already in form 
    # of an array and turning it into a Pandas DataFrame because it's a lot 
    # easier to work with when visualizing a heat map in Seaborn.
    df_cm = pd.DataFrame(
        confusion_matrix, index=class_names, columns=class_names, 
    )
    fig = plt.figure(figsize=figsize)
    
    # Combine the instance (numercial value) with its description
    strings = np.asarray([['True Positives', 'False Negatives'],
                          ['False Positives', 'True Negatives']])
    labels = (np.asarray(
        ["{0:d}\n{1}".format(value, string) for string, value in zip(
            strings.flatten(), confusion_matrix.flatten())])).reshape(2, 2)

    heatmap = sns.heatmap(df_cm, annot=labels, fmt="");
    heatmap.yaxis.set_ticklabels(
        heatmap.yaxis.get_ticklabels(), rotation=0, ha='right')
    heatmap.xaxis.set_ticklabels(
        heatmap.xaxis.get_ticklabels(), rotation=45, ha='right')
    plt.ylabel('References')
    plt.xlabel('Predictions')
    return fig

print('Binary confusion matrix visualization defined.')

필요한 모든 함수를 정의했으니 이제 심층 신경망 모델의 결과를 사용하여 바이너리 혼동 행렬 및 평가 측정항목을 컴퓨팅할 수 있습니다. 이 셀의 출력은 탭 보기로 표시되어 혼동 행렬과 평가 측정항목 표 사이를 전환하면서 볼 수 있습니다.

FairAware 작업 #4

아래 양식을 사용하여 두 성별 하위 그룹인 FemaleMale의 혼동 행렬을 생성하세요. 그런 다음 각 하위 그룹의 거짓양성과 거짓음성의 수를 비교해 보세요. 모델이 특정 하위 그룹에서 다른 하위 그룹보다 더 나은 성과를 보인다는 사실을 암시하는 큰 오류율 차이가 보이나요?


In [0]:
#@title Visualize Binary Confusion Matrix and Compute Evaluation Metrics Per Subgroup
CATEGORY  =  "gender" #@param {type:"string"}
SUBGROUP =  "Male" #@param {type:"string"}

# Given define subgroup, generate predictions and obtain its corresponding 
# ground truth.
predictions_dict = single_task_deep_model.predict(input_fn=csv_to_pandas_input_fn(
    test_df.loc[test_df[CATEGORY] == SUBGROUP], num_epochs=1, shuffle=False))
predictions = []
for prediction_item, in zip(predictions_dict):
    predictions.append(prediction_item['class_ids'][0])
actuals = list(
    test_df.loc[test_df[CATEGORY] == SUBGROUP]['income_bracket'].apply(
        lambda x: '>50K' in x).astype(int))
classes = ['Over $50K', 'Less than $50K']

# To stay consistent, we have to flip the confusion 
# matrix around on both axes because sklearn's confusion matrix module by
# default is rotated.
rotated_confusion_matrix = np.fliplr(confusion_matrix(actuals, predictions))
rotated_confusion_matrix = np.flipud(rotated_confusion_matrix)

tb = widgets.TabBar(['Confusion Matrix', 'Evaluation Metrics'], location='top')

with tb.output_to('Confusion Matrix'):
  plot_confusion_matrix(rotated_confusion_matrix, classes);

with tb.output_to('Evaluation Metrics'):
  grid = widgets.Grid(2,4)

  p, r, fpr, fomr = compute_eval_metrics(actuals, predictions)

  with grid.output_to(0, 0):
    print(' Precision ')
  with grid.output_to(1, 0):
    print(' %.4f ' % p)

  with grid.output_to(0, 1):
    print(' Recall ')
  with grid.output_to(1, 1):
    print(' %.4f ' % r)

  with grid.output_to(0, 2):
    print(' False Positive Rate ')
  with grid.output_to(1, 2):
    print(' %.4f ' % fpr)

  with grid.output_to(0, 3):
    print(' False Omission Rate ')
  with grid.output_to(1, 3):
    print(' %.4f ' % fomr)

솔루션

발견한 정보를 보려면 아래를 클릭하세요.

기본 모델 매개변수를 사용하면 여성보다 남성을 대상으로 했을 때 모델 성과가 좋다는 사실을 확인할 수 있습니다. 구체적으로는, 실행 결과 남성의 정밀도와 재현율(각각 0.7490 및 0.4795)이 여성(각각 0.6787 및 0.3716)보다 높게 나타납니다.

이 혼동 행렬 데모를 실행하면서 결과가 전체 성능 측정항목과는 조금씩 다르게 나타난다는 사실을 확인하셨기를 바랍니다. 이를 통해 종합집계 방식보다는 하위 그룹 전반에서 모델 성능을 평가하는 것이 중요하다는 점을 알 수 있습니다.

작업 시 거짓양성, 거짓음성, 참양성, 참음성 간의 균형을 잘 판단해야 합니다. 예를 들어 거짓양성률은 아주 낮지만 참양성률은 높게 나오기를 원하거나, 높은 정밀도를 원하지만 재현율이 낮아도 괜찮을 수 있습니다.

원하는 균형을 염두에 두고 평가 측정항목을 선택하세요.