Data/인과추론

[실무로 통하는 인과추론] 5. 성향점수

Derek Grey 2024. 11. 10. 17:35
반응형


해당 글의 내용과 코드는 모두 실무로 통화는 인과추론을 참고하였습니다.


0. 성향점수

성향점수는 Propensity Weighting 이라는 용어로서 편향 제거 방법이다. 4장에서 선형회귀 분석으로 교란 요인 보정과 편향 보정을 배울 수 있었다. 하지만, 성향점수는 직교화처럼 잔차를 생성하는 대신, 처치 배정 메커니즘을 모델링하고 모델 예측을 사용하여 데이터를 재조정(Reweight) 하는 방식이다. 4장에서 배운 원리와 성향점수 가중치를 결합한 이중 강건성도 알 수 있다.

 해당 방법은 이진(Binary)이나 이산형(Discrete) 처치가 있을 때 적합하다. 하지만, 연속형 변수에도 성향 점수 가중치를 사용할 수 있다.

 

1. 회귀분석과 보정

import statsmodels.formula.api as smf

smf.ols("engagement_score ~ intervention",
        data=df).fit().summary().tables[1]

 

위와 같은 회귀 분석으로 Intervention(1과 0 = 데이터 예제에서는 교육프로그램 참여 여부) 에 따라 교육참여가 Engagement_Score(교육 평균 참여 효과) 에 따른 점수를 모델링 할 수 있습니다. 하지만, 교유 프로그램 여부는 무작위로 배정되지 않아, 해당 결과가 편향 되어 있을 것이다 라고 설명하고 있습니다. 이를 위해, 공변량을 보정하여 다음 모델을 추정해 편향을 줄인다고 합니다.

model = smf.ols("""engagement_score ~ intervention 
+ tenure + last_engagement_score + department_score
+ n_of_reports + C(gender) + C(role)""", data=df).fit()

print("ATE:", model.params["intervention"])
print("95% CI:", model.conf_int().loc["intervention", :].values.T)

 

ATE: 0.26779085766768534
95% CI: [0.23357751 0.30200421]

 

성별과 직군 변수가 범주형이라 C()로 묶었다고 합니다. 여기서 효과 추정값은 이전에 얻은 추정값 0.4346보다 훨씬 작은 것을 알 수 있습니다. 이는 긍정 편향(positive bias)가 있음을 나타내며, 이미 직원 참여도가 높은 관리자가 교육 프로그램에 더 많이 참여 했을 가능성이 높다는 의미입니다. 

 

어떤 모델을 쓰기 이전, 이와 같은 회귀모델로 BaseLIne 모델을 잡는 것을 해당 교재에서 추천하고 있습니다.

2. 성향점수

성향점수 가중치는 성향점수(Propensity Score) 라는 개념을 중심으로 이루진다. 교란 요인 X를 직접 통제할 필요없이 조건부 독립성을 만족할 수 있다는 깨달음(Y_1, Y_0) ㅗ T |X 에서 비롯되었다고 한다. 교란 요인을 통제하는 대신 E[T|X]를 추정하는 균형점수를 통제해도 충분하다고 한다. 균형점수는 종종 처치의 조건부 확률이나 성향점수라고 불린다.

 

성향점수는 차원 축소 기법으로도 볼 수 있는데, 고차원이 될 수도 있는 X를 조건부로 설정하는 대신 성향점수를 조건부로 두고 X로 유입되는 뒷문 경로를 차단할 수 있다고 한다. 

 

성향점수는 처치 받을 조건부 확률이며, X를 처치 T로 변환하는 일종의 함수이다.

 

 

e(x)가 여기서 실험군과 대조군에서 처치받을 확률이 동일하다고 가정한다면, 이는 두 그룹을 비교할 수 있다. 두 개체가 처치 받을 확률이 똑같다면 그 중 한명이 처치 받고 다른 한명이 받지 않은 이유는 순전히 우연에 의한 것이다. 성향점수가 동일한 상황에서 처치는 사실상 무작위 배정된 것과 같다.

 

2-1. 성향점수 추정

 

실제 성향점수 e(x) 은 알 수 없는 이상적인 값 입니다. 현실에서는 처치 배정 메커니즘을 알 수 없으니 실제 성향 점수 e(x)를 추정값으로 대체해야 합니다. 로지스틱 회귀로 이진 처치로 그 효과를 검증합니다.

ps_model = smf.logit("""intervention ~ 
tenure + last_engagement_score + department_score
+ C(n_of_reports) + C(gender) + C(role)""", data=df).fit(disp=0)

data_ps = df.assign(
    propensity_score = ps_model.predict(df),
)

data_ps[["intervention", "engagement_score", "propensity_score"]].head()

 

예측값을 propensity_score 변수에 저장하였습니다. 

 

2-2. 성향점수와 직교화

 

이전장에서 배운 FWL 에 따르면 선형회귀도 성향점수 추정과 비슷하며 편향 제거 단계에서 E[T|X)를 추정합니다. 즉 OLS는 성향 점수 추정과 매우 비슷하게 처치 배정 메커니즘을 모델링합니다.

model = smf.ols("engagement_score ~ intervention + propensity_score",
                data=data_ps).fit()
model.params["intervention"]

 

'이 방식으로 얻은 ATE 추정 값은 처치 및 교란 요인 X를 사용하여 선형회귀 분석을 적합시킨 결과와 비슷합니다. 두 접근법 모두 처치를 직교화한 결과입니다. 두 방법의 차이점은 OLS냐, 로지스틱 회귀분석을 사용하였느냐를 따른 결과값입니다.

 

2-3. 성향점수 매칭

 

성향점수를 통제하는 또 다른 방식은 매칭 추정량(matching estimator) 입니다. 관측가능한 특징이 비슷한 실험 대상의 짝을 찾아 실험군과 대조군을 비교하는 방식입니다. 성향점수를 유일한 특징으로 사용하여 실험군에 KNN 등 군집화 모델을 사용하여 두 그룹에 Treatment 를 가하는 방식입니다. 

from sklearn.neighbors import KNeighborsRegressor

T = "intervention"
X = "propensity_score"
Y = "engagement_score"

treated = data_ps.query(f"{T}==1")
untreated = data_ps.query(f"{T}==0")

mt0 = KNeighborsRegressor(n_neighbors=1).fit(untreated[[X]],
                                             untreated[Y])

mt1 = KNeighborsRegressor(n_neighbors=1).fit(treated[[X]], treated[Y])

predicted = pd.concat([
    # find matches for the treated looking at the untreated knn model
    treated.assign(match=mt0.predict(treated[[X]])),
    
    # find matches for the untreated looking at the treated knn model
    untreated.assign(match=mt1.predict(untreated[[X]]))
])

predicted.head()

 

위의 KNN 군집화로 대조군의 KNN 모델을 통해 실험군의 짝을 찾았습니다. 반대로, 실험군의 KNN 모델을 통해 대조군의 짝을 찾았습니다.

 

np.mean((predicted[Y] - predicted["match"])*predicted[T] 
        + (predicted["match"] - predicted[Y])*(1-predicted[T]))

 

각 실험대상에 짝이 지어졌고, ATE를 추정합니다. {Y(=Engagement_Score) - match} * Intervention(=Treatment)} 등으로 해당 결과 값을 구합니다. 필자는 그러나 매칭 추정량을 크게 선호하지 않는다고 하네요. 편향될 가능성이 존재, 분산 추정이 어려우며 KNN에 다소 회의적이기 때문이라 합니다. 또한, KNN은 X가 고차원일 경우 효율이 크게 떨어지기 때문이라 합니다.

 

2-4. 역확률 가중치

 

성향점수 매칭(PSM) 외에도 성향점수를 활용하는 역확률 가중치(Inverse propensity Weighting) 도 널리 사용된다고 합니다. 이 방법은 처치의 역확률에 따라 데이터의 가중치를 재조정하여 해당 데이터가 무작위 배정된 것처럼 보여주는 방식이라 합니다. 예를 들어 실험군이 처치받을 확률의 역수(역확률)로 조정하여 처치 받을 확률이 매우 낮은데도 처치 받은 대상에게 높은 가중치를 부여하는 방식이라고 하네요.

 

그리고, 중간에 안정화된 성향점수 가중치를 부여하는 방식이 있는데요.

p_of_t = data_ps["intervention"].mean()

t1 = data_ps.query("intervention==1")
t0 = data_ps.query("intervention==0")

weight_t_stable = p_of_t/t1["propensity_score"]
weight_nt_stable = (1-p_of_t)/(1-t0["propensity_score"])

print("Treat size:", len(t1))
print("W treat", sum(weight_t_stable))

print("Control size:", len(t0))
print("W treat", sum(weight_nt_stable))

 

nt = len(t1)
nc = len(t0)

y1 = sum(t1["engagement_score"]*weight_t_stable)/nt
y0 = sum(t0["engagement_score"]*weight_nt_stable)/nc

print("ATE: ", y1 - y0)

 

위와 같이 분모에 propensity socre를 취하고, treat 를 계산하는 방법들이 존재합니다. 

 

이어서 선택편향을 해결하기 위한 것을 계속 얘기하는데요. 만약 앱서비스에 대한 고객 응답을 1-5까지 받을 때 미응답자 대부분이 앱에 불만족했을 경우 설문결과는 인위적으로 부풀려질 수 있다고 말합니다.(=앱에 만족한 고객만 응답할 수 있기 때문)

 

이를 보정하기 위해 고객의 공변량(EX: 나이, 소득, 앱사용량)이 주어지면 응답률 R,P(R=1 |X)를 추정할 수 있다고 합니다. 그리고, 응답자에게 1/ Hat P(R=1) 만큼의 가중치를 부여한다고 합니다. 이는 hat P(R=1) 이 낮은 미응답자와 유사한 응답자에게 높은 가중치를 부여한다고 합니다. 이로써, 설문응답하는 자신뿐만 아니라 비슷한 고객을 대표하여 원래 모집단처럼 , 유사 모집단을 생성하여 문제를 해결할 수 있다고 하네요.

 

2-5. 성향점수의 양수성 가정

 

머신러닝(회귀)의 편향-분산 트레이드 오프는 두 가지 인과추론 가정인 조건부 독립(비교란성) 과 양수성 관점에서 바라볼 수 있습니다. 더 많은 변수를 추가하여 e(x)에 대한 모델을 더 정교하게 만들수록 조건부 독립성 가정을 만족하는 방향으로 나아갈 수 있다고 합니다. 그러나, 동시에 양수성 가정의 타당성이 떨어지게 된다고 하네요. 그 이유는 멀리 떨어진 낮은 hat e(x) 영역에서 처치가 집중되고, 그 반대도 마찬가지 이기 때문이라고 합니다.

 

IPW(inverse Probability weighting) 재구성은 재조정할 수 있는 표본이 있을때만 가능하다고 하네요.

참고로, IPW는 데이터에서 pseudo-populations(유사모집단) 을 생성하여 confounders를 통제하는 방법입니다.

유사모집단은 규모가 큰 그룹(해당 특성을 가진 unit이 많은 그룹)의 가중치는 높이고, 규모가 작은 그룹(해당 특성을 가진 unit이 적은 그룹)의 가중치는 낮춘 데이터셋입니다.

 

3. 이중 강건 추정

 

이중 강건 추정은(Doubly Robust) 추정은 모델기반과 디자인 기반 식별을 모두 결합하여, 적어도 둘 중 하나가 정확하기를 기대하는 방법이라고 합니다. 성향점수(디자인 기반 식별)와 회귀분석(모델 기바 식별) 등을 결합하는 방식이 있다고 하네요.

 

이는, 결과모델(ex : 선형회귀) 와 성향점수(ex : IPW) 중 둘중 하나가 서로 보안해주며 인과추정량을 추론하는 방식입니다. 어느 모델이 맞든 한 모델의 정확도가 높으면 다른 모델의 결과를 0으로 수렴시키는 수식과 방식으로 성립되어 있습니다.

 

from sklearn.linear_model import LinearRegression

def doubly_robust(df, formula, T, Y):
    X = dmatrix(formula, df)
    
    ps_model = LogisticRegression(penalty="none",
                                  max_iter=1000).fit(X, df[T])
    ps = ps_model.predict_proba(X)[:, 1]
    
    m0 = LinearRegression().fit(X[df[T]==0, :], df.query(f"{T}==0")[Y])
    m1 = LinearRegression().fit(X[df[T]==1, :], df.query(f"{T}==1")[Y])
    
    m0_hat = m0.predict(X)
    m1_hat = m1.predict(X)

    return (
        np.mean(df[T]*(df[Y] - m1_hat)/ps + m1_hat) -
        np.mean((1-df[T])*(df[Y] - m0_hat)/(1-ps) + m0_hat)
    )

 

 

1. dmatrix 를 활용하여 공변량 행렬 X를 만든다.

2. 로지스틱 회귀분석을 사용하여, 성향점수 모델을 적합시켜 hat e(x)를 얻는다.

3. 결과모델 : 처치 변형(variant) 별로 하나의 선형 회귀를 적합시켜 실험군과 대조군 각각에 대해 두 모델을 만든다.

4. 각 모델은 특정 처치 유형의 데이터셋 부분 집합에 적합되지만, 전체 데이터셋에 대한 예측을 한다.

5. 이는 대조군에 대한 모델은 T=0인 데이터에만 적합되지만 모든 데이터에 대해 예측한다(=Y_0에 대한 추정값) 

6. 마지막으로, 두 모델을 겨합하여 E[Y_0] 와 E[Y_1] 모두의 이중 강건 추정량을 구성한다.

 

그리고, 신뢰구간을 얻기 위해 부트스트랩을 활용한다. -> 표본평균(ate) 추출을 통한 신뢰구간 구성

formula = """tenure + last_engagement_score + department_score
+ C(n_of_reports) + C(gender) + C(role)"""
T = "intervention"
Y = "engagement_score"

print("DR ATE:", doubly_robust(df, formula, T, Y))

est_fn = partial(doubly_robust, formula=formula, T=T, Y=Y)
print("95% CI", bootstrap(df, est_fn))

 

 

 

5. 요약

 

인과추론에서 회귀분석 및 직교화와 함께 역확률 가중치(IPW) 는 편향을 보정하는 두번째 방법입니다. 직교화는 처치를 잔차화하여, 처치 모델링에서 사용된 공변량 X와 선형 독립(직교)인 새로운 공간으로 투영합니다.

 그러나, IPW는 처치의 차원을 그대로 유지하지만, 데이터를 처치 성향점수의 역수로 재조정합니다.

 

W = P(T) / P(T | X) 

 

이는 처치가 공변량 X에 독립인 분포 P(T)에서 추출된 것으로 보이게 합니다.

 

그렇다면, 직교화와 IPW 모두 편향을 보정하는 방법인데요. 어떨 때 구분해서 사용하는게 좋을까요? 저자는 처치(Treatment)가 이산형(Binary) 일 때 IPW를 사용하는것을 선호한다고 하네요. 특히, 이중 강건 접근법에서 결과 모델링과 함께 사용하는 것을 선호한다고 하네요. 그러나, Treatment 가 연속형일때는 회귀모델링을 선호 한다고 합니다.

반응형