HwangHub

[MLOPS] 편향된 데이터셋을 공정하게 처리하는 방법 (feat. AIF360) 본문

workspace/mlops

[MLOPS] 편향된 데이터셋을 공정하게 처리하는 방법 (feat. AIF360)

HwangJerry 2025. 3. 5. 23:53

지금까지의 프로그램은 개발자가 로직을 직접 구성하는 방식으로 만들어져 왔다. 하지만 머신러닝 또는 딥러닝으로 대표되는 인공지능 모델은 입력과 출력이라는 데이터를 바탕으로 추론 로직이 완성되는 형태이다.

 

즉, 코드 뿐만 아니라 학습에 사용되는 데이터 또한 로직을 구성하는 데에 큰 영향을 끼친다는 것이다.

 

기존의 프로그램은 개발자가 구성하는 로직이 그대로 규칙이 되는 반면, ML / DL 모델은 데이터가 그 규칙을 결정하는 데에 "매우 크게" 관여한다는 것이 포인트이다.

 

이에 따라 세간에서는 모델을 개발할 때 "Garbage in, Garbage out" 이라는 말을 많이 한다.

 

이 대목에서 생각해봐야 할 포인트가 있다. 만약 데이터가 편향되어 있다면 어쩌나.

데이터의 편향성? 그게 문제가 될까? 된다. 위에서 언급한 것과 같이, 인공지능 모델의 추론 논리를 완성하는 데에 데이터는 매우 큰 영향력을 가진다. 따라서 데이터가 편향되어 있다면 그 모델 또한 편향된 논리로 추론을 할 것이다.

 

예를 들어보자.

최근 많은 기업들이 채용 프로세스에서 AI를 활용하고 있다고 한다. 하지만 오늘날과 달리, 역사적으로는 여성보다는 비교적 남성 노동자의 수가 더 많았다. 따라서 오래 전부터 편향된 형태로 축적되어 온 고용 데이터를 그대로 학습한 AI 채용 모델은 아마도 남성을 선호하는 편향을 가질 수 있다. 이처럼 민감한 속성이 판단의 근거가 될 수 있는 태스크에서는 AI의 공평성을 확보할 수 있도록 장치를 도입해야 한다.

 

이를 위해 IBM, Google, Microsoft에서 툴킷을 제공하고 있다. 그 중에서 IBM의 Fairness 360 툴킷은 편향완화 알고리즘 및 편향 메트릭 설명, 산업적 유용성을 통합한 최초의 시스템이다. 이번 시간에는 이를 중점으로 어떻게 ML/DL 모델의 공정성을 평가하고 처리할 수 있는지 알아볼 것이다.

Figure 1. The fairness pipeline.

AI Fairness 360 논문에 따르면, 위와 같이 fairness pipeline이 구성될 수 있음을 알 수 있다. 이에 따르면 공정성 확보를 위한 파이프라인은 다음과 같이 구성된다.

 

  1. 데이터 로딩
    • 데이터를 dataset 객체에 불러옴.
  2. 공정한 데이터로 변환
    • 공정성 개선을 위한 사전 처리(pre-processing) 알고리즘을 사용하여 데이터 변환.
    • 이 과정을 거치면 원래 데이터보다 편향(bias)이 줄어든 데이터셋이 생성됨.
  3. 분류기(classifier) 학습
    • 변환된 데이터셋을 사용하여 머신러닝 분류기(모델)를 학습.
  4. 예측값(predictions) 생성
    • 학습된 분류기로 데이터를 예측하여 결과 도출.
  5. 공정성 지표(metrics) 계산
    • 원본 데이터, 변환된 데이터, 그리고 예측된 데이터에 대한 공정성 측정 가능.
    • 변환된 데이터와 예측된 데이터 간의 비교도 가능.

 

위 과정에서 필요한 것은 Dataset, Fairness metrics, 그리고 공정성 개선을 위한 전처리 Algorithm이다.

 

Fairness 360 툴킷은 Favorable Label, Protected Attribute, Privileged / Unprivileged Group, Bias 를 기준으로 privilieged group에게 유리하게 작용하는 bias를 경감시키는 bias mitigation algorithm을 제공한다.

 

효율적인 실습을 위해 데보션의 실습 시나리오를 기준으로 진행하였고, 사용된 코드는 '내 손으로' 작성했다(물론 gpt와 함께.......ㅎㅎㅎ). (데보션 글에 이미지가 하나도 안보여서 그렇게 되었는데, 혹시 나만 안보이는건지...?)

 

Fairness 파이프라인을 구축하기에 앞서, 실습에 필요한 데이터셋을 설정해야 한다. 데보션에서 Adult Dataset으로 진행하여 이를 그대로 사용하였다. 본인이 활용한 데이터셋의 크기는 총 15개의 column(=속성)과 32561개의 row(=데이터 개수)로 구성되어 있었다. column은 별도로 지정하여 활용하였다.

본인은 데보션에서 제공하는 링크에서 데이터셋을 별도로 다운받고 관리하는게 귀찮아서 알아보니 url를 활용하여 바로 코드에서 사용 가능하도록 https로 받아올 수 있었다.

# dataset 다운로드 및 na 처리
url = "https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.data"
columns = ['age', 'workclass', 'fnlwgt', 'education', 'education-num', 'marital-status', 'occupation', 'relationship', 'race', 'sex', 'capital-gain', 'capital-loss', 'hours-per-week', 'native-country', 'income']
context = ssl.create_default_context(cafile=certifi.where())
try:
    with request.urlopen(url, context=context) as response:
        data = response.read().decode('utf-8')
        df = pd.read_csv(StringIO(data), names=columns, na_values=' ?', skipinitialspace=True)
        df.dropna(inplace=True)
except Exception as e:
    print(f"데이터 다운로드 실패: {e}")
    raise

 

이번 fairness pipeline에서는 인종별 수입의 편향성에 대하여 판단하고 완화시키는 처리를 진행한다. 따라서 해당 속성들을 binary label로 변환해줬다.

  • 수입 수준은 dataset에서 50K를 기준으로 초과/이하 로 구분하고 있기 떄문에 binary label이 적절해 보인다.
  • 위 기준에 따라 인종 속성에 대하여 white/non-white 를 기준으로 binary label을 적용하였다.
# 수입은 50K 이상을 1로, 인종은 White를 1로 binary label 변환
df['income'] = df['income'].apply(lambda x: 1 if x == '>50K' else 0)
df['race'] = df['race'].apply(lambda x: 1 if x == 'White' else 0)

 

그 외 기타 object 칼럼은 편의를 위해 원핫인코딩으로 처리해 주었다.

# 그 외 기타 object 칼럼은 원-핫 인코딩
object_columns = df.select_dtypes(include=['object']).columns
df_encoded = pd.get_dummies(df, columns=object_columns, drop_first=True)

 

편향 정도를 측정하기 위한 BinaryLabelMetric을 추출하기 위해 binaryLabelDataset을 생성해준다.

from aif360.datasets import BinaryLabelDataset

# AIF360 BinaryLabelDataset 생성
dataset = BinaryLabelDataset(df=df_encoded, label_names=['income'], protected_attribute_names=['race'], favorable_label=1, unfavorable_label=0)

 

BinaryLabelDatasetMetric에서는 특권 그룹과 비특권 그룹을 지정한다. 이를 통해 통계적 평등 정도(statistical_parity_difference; 본인 의역)를 추산할 수 있다. 내부 공식이 어떻게 되어있는지는 알아보지 못했다.

# 특권/비특권 그룹 정의
privileged_group = [{'race': 1}] # White
unprivileged_group = [{'race': 0}] # Non-White
from aif360.metrics import BinaryLabelDatasetMetric

# 초기 편향성 메트릭 계산
metric_original = BinaryLabelDatasetMetric(dataset, unprivileged_groups=unprivileged_group, privileged_groups=privileged_group)
print("초기 Disparate Impact: %.3f" % metric_original.disparate_impact())
print("초기 Statistical Parity Difference: %.3f" % metric_original.statistical_parity_difference())

# 초기 Disparate Impact: 0.596
# 초기 Statistical Parity Difference: -0.103

 

위와 같이 설정한 뒤 출력되는 값을 보니 간접 차별(Disperate Impact) 지수는 0.5, 평등 정도는 -0.1이 나왔다.

  • 간접 차별 지수는 1에 수렴할수록 공평한 값이므로, 위 결과는 차별 정도가 높다고 해석할 수 있다.
  • 평등 정도는 0에 가까울수록 공평한 것이다. 특히, 음수인 경우 특권 그룹이 우세하다는 의미이므로 백인의 소득이 우세함을 알 수 있다.
from aif360.algorithms.preprocessing import Reweighing

# Reweighing 적용으로 편향성 완화
rw = Reweighing(unprivileged_groups=unprivileged_group, privileged_groups=privileged_group)
mitigated_dataset = rw.fit_transform(dataset)

 

AIF360 툴킷에는 다양한 편향 개선 알고리즘이 존재한다고 한다. pre-processing, in-processing, post-processing 단계에서 각각 사용될 수 있는 알고리즘이 존재한다고 하는데, 기준되는 예제에서 Reweighing 이라는 preprocessing 알고리즘을 사용하였기에 동일하게 사용하였다.

from aif360.algorithms.preprocessing import Reweighing

# Reweighing 적용으로 편향성 완화
rw = Reweighing(unprivileged_groups=unprivileged_group, privileged_groups=privileged_group)
mitigated_dataset = rw.fit_transform(dataset)
# 완화 후 편향성 메트릭 계산
metric_mitigated = BinaryLabelDatasetMetric(mitigated_dataset, unprivileged_groups=unprivileged_group, privileged_groups=privileged_group)
print("완화 후 Disparate Impact: %.3f" % metric_mitigated.disparate_impact())
print("완화 후 Statistical Parity Difference: %.3f" % metric_mitigated.statistical_parity_difference())

# 완화 후 Disparate Impact: 1.000
# 완화 후 Statistical Parity Difference: 0.000

 

위 알고리즘을 통해 데이터셋을 전처리한 후 metric을 추출해보니 확실히 개선된 것을 알 수 있다. 생각보다 더 파워풀하게 확 변경되어 놀라기도 했다. Reweighing 알고리즘의 원리는 데보션에서 발췌하여 첨부한다.

해당 알고리즘은 클래스(class)의 균형을 고려하도록 데이터 샘플에 클래스 가중치(class-weights)를 적용하는 것이다. 그러니까 그룹(white/non-white)과 레이블(50K이상/미만)간 분포(빈도수)를 기반하여, 다수 조합(privileged group)과 소수 조합(unprivileged group)에 다음의 공식을 사용하면 각 조합의 가중치를 구할 수 있다.

출처: 데보션

 

그러면 이제 궁금한 점이 하나 생긴다. 뭔가 Reweighing 알고리즘이 데이터셋에 어떤 처리를 해버린 건 아닐까? 전처리된 데이터셋으로 학습하면 정확도가 낮아지는 거 아니야?

from sklearn.metrics import accuracy_score
from sklearn.linear_model import LogisticRegression

train, test = dataset.split([0.7], shuffle=True)
train_transformed, test_transformed = mitigated_dataset.split([0.7], shuffle=True)

# 편향성 제거 데이터셋으로부터
sample_weights = train_transformed.instance_weights

model = LogisticRegression(solver='liblinear')
model.fit(train.features, train.labels.ravel())
y_pred = model.predict(test.features)
accuracy = accuracy_score(test.labels, y_pred)
print("기존 데이터셋 학습 모델 정확도:", accuracy)

model_transformed = LogisticRegression(solver='liblinear')
model_transformed.fit(train_transformed.features, train_transformed.labels.ravel(),
                      sample_weight=sample_weights)
y_pred_transformed = model_transformed.predict(test_transformed.features)
accuracy_transformed = accuracy_score(test_transformed.labels, y_pred_transformed)
print("편향성 제거 데이터셋 학습 모델 정확도:", accuracy_transformed)

# 기존 데이터셋 학습 모델 정확도: 0.794451837444979
# 편향성 제거 데이터셋 학습 모델 정확도: 0.7974204115057836

 

accuracy를 기준으로 메트릭을 비교해 보았다. 기존 데이터셋 학습 모델과 편향성 제거 데이터셋 학습 모델의 정확도가 유의미한 차이를 보이지 않는 것을 확인할 수 있다.

from aif360.metrics import ClassificationMetric

test_pred = test.copy()
test_pred.labels = y_pred.reshape(-1, 1)
metric_class = ClassificationMetric(test, test_pred,
                                    unprivileged_groups=unprivileged_group,
                                    privileged_groups=privileged_group)
print("원래 classification metrics:")
print("  Statistical Parity Difference:", metric_class.statistical_parity_difference())
print("  Disparate Impact:", metric_class.disparate_impact())
print("  Average Odds Difference:", metric_class.average_odds_difference())

test_transformed_pred = test_transformed.copy()
test_transformed_pred.labels = y_pred_transformed.reshape(-1, 1)
metric_class_transformed = ClassificationMetric(test_transformed, test_transformed_pred,
                                                unprivileged_groups=unprivileged_group,
                                                privileged_groups=privileged_group)
print("\n완화된 Classification Metrics:")
print("  Statistical Parity Difference:", metric_class_transformed.statistical_parity_difference())
print("  Disparate Impact:", metric_class_transformed.disparate_impact())
print("  Average Odds Difference:", metric_class_transformed.average_odds_difference())


# 원래 classification metrics:
#   Statistical Parity Difference: -0.028854997387305813
#   Disparate Impact: 0.6788145611675024
#   Average Odds Difference: -0.004250754775608044
# 
# 완화된 Classification Metrics:
#   Statistical Parity Difference: -0.004927975081932406
#   Disparate Impact: 0.9437343888408336
#   Average Odds Difference: -0.00653428935359824

 

 

데보션 시나리오에 따라 세 가지 지표를 기준으로 전후 변화를 평가한다.

  • 통계적 동등 지수 : -0.0288 -> -0.0049 (공정 기준 : -0.1 < fair < 0.1)
  • 간접 차별 지수 : 0.6788 -> 0.9437 (공정 기준 : 0.8 < fair < 1.25)
  • 평균 절대 확률 차이 지수 : -0.0042 -> -0.0065 (공정 기준 : -0.1 < fair < 0.1)

빨간색은 공정 기준을 충족하지 못하는 값, 파란색은 공정 기준을 충족하는 값을 의미한다. 전반적으로 수치가 개선되는 것을 이해할 수 있다.

 

실습에 사용한 코드는 아래와 같이 주피터 노트북 파일로 첨부해 둘테니 혹시 관심 있는 분들은 다운로드 해보세요!

aif360_fairness_pipeline_demo.ipynb
0.01MB


소감

위 실습은 패스트캠퍼스 강의 중 연구용 ML 모델과 비즈니스용 ML 모델의 차이에 대한 학습을 하던 중 호기심이 생겨 진행하게 되었다.

 

연구용 ML은 Throughput을 중점적으로 개선해 나갔다던가, 제품용 ML은 latency를 중점으로 개선하고 있다던가 하는 이야기들은 충분히 납득을 쉽게 할 수 있었다. 하지만 공정성에 대한 이야기라던가, 해석가능성이 중요하다 등의 이야기는 꽤나 생소하게 느껴졌다.

 

무조건 성능 개선! f1 score 향상! 뭐 이런 이야기에 익숙해왔던 내게 다시금 ML/DL을 비즈니스에 활용할 때 무엇을 더 고려해야 하고, MLOps 파이프라인을 구성할 때 어떠한 것들을 고려해야 좋은 파이프라인을 구성할 수 있을지 생각하게 만들어주는 좋은 계기였다고 느꼈다.

'workspace > mlops' 카테고리의 다른 글

[MLOPS] Data Scaling  (0) 2025.03.20
[MLOPS] 샘플링, 의미 있는 행동인가?  (0) 2025.03.12
Comments