통계/머신러닝 MachineLearning

[ML] 주성분 분석 PCA와 차원의 저주

sseozytank 2023. 11. 20.

모바일 게임 오픈을 앞두고, 유저 클러스터링을 실시하기 위해 코드를 작성해두고 있다. 

그런데, log에서 찾을 수 있는 데이터가 너~무 많다. 난 이 중에서 어떤 변수를 사용해서 클러스터링을 실시해야할까? 

 

우선 상관관계 분석을 통해 다중공선성을 유발할 것 같은 변수나 (승률과 승리 수) 우리 게임의 핵심 유저를 판가름 하는데 필요 없다고 생각하는 정보 (닉네임 변경 횟수) 등은 과감하게 추려준다. 근데 이 다음은 이제 뭘해야할지 모르겠다.

10개 정도가 추려졌는데 그냥 넣고 모델링을 돌려볼려자니 어디선가 들어본 '차원의 저주'가 내 머릿속을 스쳐간다. 

(이놈 때문에 내 첫 데이터 분석가 면접에서 어버버 했던 적이 있다.) 

 

차원의 저주 (Curse of dimensionality)

샘플은 많을 수록 좋다. 근데 변수는 ? 

https://thesciencelife.com/archives/1001

학습 데이터 수가 커버할 수 없는 차원으로 넘어가게 되면, 학습 성능이 급격히 떨어지는 것을 확인할 수 있다. 

무조건적인 변수 추가는 모델 정확도를 낮추는 주요 원인이 될 수 있는 것이다.

즉, 고차원 데이터일 수록 모델을 학습하기가 어려워지고, 훨씬 더 많은 데이터 양이 필요함.

쉽게 설명하면 변수가 2개인 연립 방정식보다 변수가 3개인 연립 방정식이 풀기 어렵고 많은 식을 필요로 하는 느낌이다.

 

 

https://for-my-wealthy-life.tistory.com/40

이렇게 보면 이해하기가 쉬운데, 1차원 일 때는 가까웠던 점들이 2차원, 3차원으로 멀어진다. 정보가 없는 공간이 많아질 수록 해당 부분에 대해서 추정을 해야하기 때문에 모델 성능이 저하되는 것이다. 

 

그렇다면, 나처럼 어떤 변수로 사용할 수 있는 데이터가 너무 많을 때는 어느정도 차원을 줄여야하지 않을까? 서비스 초기기 때문에 활용가능한 데이터가 적다. 따라서 변수를 더 줄여야한다

 

가장 대표적인 방법인 PCA를 활용, 1차로 추려놨던 10개의 차원을 더 축소해서 차원의 저주를 막아보도록 하자.

 

PCA (주성분 분석)

변수를 줄이는 방법에는 여러가지가 있을 것이다. 그 중에서도 차원을 축소하거나 특징을 추출하는데 사용되는 대표적 통계 기법인 PCA(Principal Component Analysis)에 대해 알아보도록 하자. 

 

PCA란 데이터의 분산을 최대화하는 축을 찾아 그 축을 기준으로 데이터를 재구성하는 방식으로 차원을 축소한다.

피쳐를 빼는게 아니라, 여러개의 피쳐들이 갖는 정보를 하나로 압축한다! 이게 PCA의 핵심 개념이다. 

 

예를 들어 우리 회사 게임에는 광고를 통해 수익을 창출해내는 것이 있는데,  배틀이 끝나면 랜덤 광고가 등장한다. 

그럼 당연히 어느정도 차이는 있겠지만 배틀 횟수가 높을 수록 광고 노출 횟수가 높을 것이다. 이렇게 나왔을 때 이 두가지 피쳐를 하나로 합쳐주는 것이다. 

 

데이터의 기존 구조를 최대한 유지하기 위해서는 projection(사영) 하였을 때 분산이 최대가 되는 방향의 벡터를 찾아야하고, 이는 데이터의 공분산 행렬의 고유벡터가 최대가 되는 방향 벡터이다. 따라서 PC란 데이터를 사영 후, 분산이 최대가 되도록 하는 벡터를 찾는 일이다.

 

 

위 사진을 보면 '분산이 최대가 된다'의 개념이 이해가 될 것이다. 오른쪽 벡터가 빨간 점들이 더 넓게 분포되어 있음을 확인할 수 있고, 오른쪽 직선이 원래의 데이터를 잘 보존하고 있다.

 

여기서 알 수 있듯이, 주성분 분석은 10개 중에 관련도 높은 5개를 뽑아주는게 아니다, 5가지의 주성분이라는 새 데이터를 활용해서 사용한다는 것이다. 이번 포스팅에서 수식에 대한 부분은 넘어가도록 한다.

 

PCA (주성분 분석) with Python 

1.스케일링 

데이터의 스케일에 따라 주성분의 설명 가능한 분산량이 달라지기 때문에,표준화를 진행하도록 한다.

#(1)스케일링
from sklearn.preprocessing import StandardScaler  # 표준화 패키지 라이브러리 
df_final_pca_value=StandardScaler().fit_transform(df_final_pca.values)

features =['connect_per', 'avg_daily_login_cnt', 'avg_play_time_min',
       'avg_daily_spend', 'total_spend', 'victory_per', 'friend_battle_count',
       'total_battle_count', 'clan_application', 'total_impression_ad_per']
pd.DataFrame(df_final_pca_value,columns=features).head()

 

 

2.주성분 분석 개수 정하기

#(2) PCA 실행
from sklearn.decomposition import PCA
pca=PCA(n_components=10) #전체 주성분 개수로 확인
pca_array=pca.fit_transform(df_final_pca_value)
pca_df=pd.DataFrame(pca_array,
                   columns=[f"pca{num+1}" for num in range(df_final_pca_value.shape[1])])
pca_df.head()

result=pd.DataFrame({'설명가능한 분산 비율(고윳값)':pca.explained_variance_,
'기여율':pca.explained_variance_ratio_},
                     index=np.array([f"pca{num+1}" for num in range(df_final_pca_value.shape[1])]))
result['누적기여율']=result['기여율'].cumsum()
result

# '누적기여율' 열의 데이터를 리스트로 가져와서 'y' 열로 사용합니다.
data = {'x': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
        'y': result['누적기여율'].tolist()}

# 새로운 데이터프레임을 생성합니다.
df = pd.DataFrame(data)

# 그래프를 그립니다.
plt.plot(df['x'], df['y'], marker='o')
plt.xlabel('X')
plt.ylabel('Cumulative Contribution')
plt.title('Cumulative Contribution Graph')
plt.grid(True)
plt.show()

 

 

 

전체 데이터로 설명가능한 정도를 확인해보니, 변수가 6개 정도 일 때 약 82%가량 설명이 가능하다. 

그래서 나는 6개 정도로 주성분분석을 실시해주었다. 

pca=PCA(n_components=6) #주성분 개수 결정
pca_array=pca.fit_transform(df_final_pca_value)

 

이제 이렇게 새로 만든 데이터 프레임과 원 데이터 간의 모델링 결과를 비교해보면 된다.

 

 

https://gguguk.github.io/posts/PCA/

https://m.blog.naver.com/tjdrud1323/221720259834

https://techblog-history-younghunjo1.tistory.com/134

댓글