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.
이 실습에서는 머신러닝 자료로 자주 사용되는 성인 인구조사 소득 데이터세트를 활용합니다. 이 데이터는 로니 코하비와 배리 베커의 1994 인구조사국 데이터베이스에서 추출한 데이터입니다.
데이터세트의 각 예에는 1994 인구조사에 참여한 사람들에 관한 다음 인구통계 데이터가 포함되어 있습니다.
age
: 개인의 나이를 연 단위로 나타냅니다.fnlwgt
: 일련의 관측 결과를 바탕으로 인구조사국이 부여하는 개인의 가중치입니다.education_num
: 교육 수준을 숫자로 범주화하여 열거합니다. 숫자가 높을수록 개인의 교육 수준이 높습니다. 예를 들어 education_num
이 11
이면 Assoc_voc
(전문학교 준학사)를, education_num
이 13
이면 Bachelors
(학사)를, education_num
이 9
이면 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.')
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 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))
수적 특성 및 범주형 특성의 누락된 열을 검토한 결과 누락된 특성 값이 없다는 사실을 확인할 수 있으므로 여기에서는 문제가 되지 않습니다.
각 수적 특성의 최솟값/최댓값과 히스토그램을 살펴보면 데이터세트에 있는 극단적 이상점을 정확히 찾아낼 수 있습니다. hours_per_week
의 경우 최솟값이 1인 것을 확인할 수 있는데, 대부분 직업은 주당 근무시간이 여러 시간이라는 점을 고려하면 이상하다고 생각할 수 있습니다. capital_gain
과 capital_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))
시각화 이미지의 왼쪽 패널에 있는 메뉴를 사용하여 데이터 구성 방식을 변경하세요.
Faceting | X-Axis 메뉴에서 education을 선택하고, Display | Color 및 Display | Type 메뉴에서 income_bracket을 선택합니다. 교육 수준과 소득계층 사이의 상관관계를 어떻게 설명할 수 있나요?
다음으로 Faceting | X-Axis 메뉴에서 marital_status를 선택하고, Display | Color 및 Display | Type 메뉴에서 gender를 선택합니다. 각 결혼 여부 범주의 성 분포와 관련하여 어떤 주목할 만한 점이 관찰되나요?
위의 작업을 하는 동안 공정성과 관련된 다음 질문을 염두에 두세요
데이터세트에서 교육 수준이 높을수록 일반적으로 소득계층도 높게 나타나는 경향이 있습니다. 예에서는 소득 수준이 50,000달러가 넘는 사람들이 학사 이상의 교육 수준인 경우가 더욱 두드러지게 나타납니다.
대부분의 결혼 여부 범주에서 남성과 여성의 분포는 1:1에 가깝습니다. 그러나 'married-civ-spouse' 범주의 경우 5:1이 넘는 비율로 남성의 수가 더 많습니다. 작업 #1에서 데이터세트의 남성 비율이 불균형적으로 높은 것을 이미 확인했으므로 결혼한 여성이 데이터에 특히 제대로 나타나지 않았다고 추론할 수 있습니다.
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.''')
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.')
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])
특성 추출을 실행할 때 별도로 모델 성능을 평가하려는 하위 그룹에 속한 개인에게서 가져온 데이터로 작업한다는 점을 유의해야 합니다.
참고: 이 컨텍스트에서 하위 그룹은 공정성을 염두에 두고 모델을 평가할 때 특별히 고려할 만한 특성(예: 인종, 성별, 성적 지향 등)을 공유하는 개인들의 그룹으로 정의됩니다.
모델이 하위 그룹과 관련된 특성의 학습된 신호를 완화하거나 활용하도록 하기 위해 여러 유형의 도구와 기법을 사용할 수 있습니다. 이러한 도구와 기법 대부분은 아직 연구가 진행 중입니다.
여러 변수를 사용하여 작업하고 이러한 변수에 따라 작업을 정의하면서 변수와 작업의 상호작용이 문제가 될 수 있는 곳이 어디인가?와 같은 다음 질문을 생각하면 도움이 될 수 있습니다.
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
를 예측하지만 실측 결과와 다릅니다.참고: 필요한 경우 각 상태의 결과 개수를 사용하여 정밀도와 재현율 등의 보조 평가 측정항목을 계산할 수 있습니다.
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.')
필요한 모든 함수를 정의했으니 이제 심층 신경망 모델의 결과를 사용하여 바이너리 혼동 행렬 및 평가 측정항목을 컴퓨팅할 수 있습니다. 이 셀의 출력은 탭 보기로 표시되어 혼동 행렬과 평가 측정항목 표 사이를 전환하면서 볼 수 있습니다.
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)보다 높게 나타납니다.
이 혼동 행렬 데모를 실행하면서 결과가 전체 성능 측정항목과는 조금씩 다르게 나타난다는 사실을 확인하셨기를 바랍니다. 이를 통해 종합집계 방식보다는 하위 그룹 전반에서 모델 성능을 평가하는 것이 중요하다는 점을 알 수 있습니다.
작업 시 거짓양성, 거짓음성, 참양성, 참음성 간의 균형을 잘 판단해야 합니다. 예를 들어 거짓양성률은 아주 낮지만 참양성률은 높게 나오기를 원하거나, 높은 정밀도를 원하지만 재현율이 낮아도 괜찮을 수 있습니다.
원하는 균형을 염두에 두고 평가 측정항목을 선택하세요.