상세 컨텐츠

본문 제목

캐글/데이콘을 위한 실용가이드 : CRISP-DM for python

프로그래밍/Data&ML

by 척척석사 민준 2023. 3. 7. 13:56

본문

728x90

머신러닝 프로젝트는 중구난방이 되기 쉽다! 프레임워크가 필요해

캐글이나 데이콘에서 프로젝트를 진행해보면 알 수 있다. 문제가 얼마나 중구난방이 되는지.

여러 전처리과정, 모델 최적화 등을 거치다보면 점수를 0.01점 더 높히기 위한 끝없는 노가다가 되기 마련이다.

하지만.. 실제 서비스를 할 때 Accuracy가 0.80 이나 0.85나 큰 차이가 있을까? 모델 개발을 위한 작은 데이터셋에서 Accuracy가 0.99999 가 나오는 것도 오버피팅의 가능성이 있기에 그렇게 바람직하지는 않다.

복잡한 문제일 수록 프레임워크가 필요해!

오히려 머신러닝 프로젝트를 성공하기 위해 필요한건 결과보다 구체적인 과정이다. 데이터에 어떤 가정이 들어갔는지, 각 전처리와 모델 최적화 과정에서 어떤부분이 고려되었는지 등등 과정에 대한 구체적인 기술이 필요하다.

CRISP-DM 프로세스는 일종의 체크리스트다. 파일럿이 출항하기전 번거로워도 단계별 SOP를 체크하는 것처럼 머신러닝 프로젝트가 복잡한 문제에 체계적으로 접근할 수 있는 프레임워크가 되어준다. 이 프로세스를 따르면 분석이 잘 계획되고 실행되고 잘 전달되는지 확인 할 수 있다.

프로젝트 진행과정에서 계속 인사이트를 얻고 새로운 정보를 얻게 됨으로 What/How를 다시 정의하는 일이 계속 일어난다

홈페이지를 만들거나 프로그램을 만드는 과정은 물이 위에서 아래로 흐르듯이 진행될 수 있다.

계획하고 기한을 정해 필요한 기능을 구현하고 테스트하고 배포하고 피드백받는 전과정이 순차적으로 진행된다. 처음부터 다시 시작해야하는 경우가 잘 없고 이미 제작된 템플릿이 존재하는 경우도 많다.

머신러닝 프로젝트는 새로운 모델을 사용하면 전처리부터 다시 해야하는 경우가 많다.

예를들어 랜덤포레스트 분류 모델을 사용해서 F1 score가 0.7이 나왔다. 모델을 아무리 튜닝해도 점수가 오르지 않아서 시각화를 해보니까 Feature별로 데이터의 분포가 고르지 않아서 SVM 모델을 사용해보려고 한다. 랜덤포레스트는 데이터스케일이 필요하지않지만 SVM은 데이터가 비슷한 스케일로 모여있어야 분류가 가능한 모델이다. 즉 데이터 전처리부터 다시 시작해야한다는 것이다.

이런식으로 모델돌려보고 인사이트를 얻고 문제를 재정의하고 개선하는 과정에서 What/How를 다시 정의하는 일이 계속 일어난다.

처음으로 돌아가도 괜찮도록 과정들을 분리하고 모듈화하자 : CRISP-DM

인사이트와 데이터에 대한 가정이 문제를 해결하는 과정에서 꼬리에 꼬리를 물며 돌아가기 때문에 돌아가더라도 인사이트를 놓치지 않기위한 모듈화된 프레임워크가 필요하고 이게 CRISP-DM이다.

 

1. 비즈니스 이해

CRISP-DM 프로세스의 첫 번째 단계는 해결해야 할 비즈니스 문제를 이해하는 것이다. 비즈니스 목표 식별, 이러한 목표를 달성하는 데 필요한 데이터 결정, 성공 기준 정의가 포함된다.

  • 문제가 중요한 이유는 무엇인가?
  • 누가 이 예측을 사용하고 어떻게 사용할 것인지?
  • 예측에 영향을 미칠 수 있는 요소는 무엇인지?
  • 예측값의 정확도를 어디까지 얻어야 성공한 결과인지?

2. 데이터 이해

CRISP-DM 프로세스의 두 번째 단계는 데이터를 이해하는 것으로 데이터를 시각화해보고 분포를 살펴보며 분석에 알맞은 형태로 되어있는지를 확인한다.

  • 데이터의 변수는 무엇이며 무엇을 나타내는지?
  • 데이터의 품질은 어떤가 누락된 값이나 이상값이 있는지?
  • 데이터의 라벨링이 편향되어 있지는 않는지?
  • 데이터의 분포나 변수기리의 상관 관계가 있는지?
  • 의미없는 데이터가 있는지?

3. 데이터 준비

CRISP-DM 프로세스의 세 번째 단계는 본격적인 분석을 위한 데이터 정리 및 준비단계이다. 누락된 값을 제거하고 이상값을 처리하고 데이터 불일치를 처리하여 데이터를 정리할 수 있다.

  • 중복, 누락된 값 및 이상값 제거
  • 데이터 병합 및 변환
  • Feature 정규화 및 전처리
  • 훈련 세트와 테스트 세트로 분할

4. 모델링

CRISP-DM 프로세스의 네 번째 단계는 머신러닝 모델을 구축하고 학습하는 단계이다. 문제에 따라 회귀, 분류 및 클러스터링과 같은 다양한 알고리즘을 사용한다.

  • 데이터에 대한 가정이 필요없는 랜덤포레스트 모델을 베이스라인으로 사용할 수 있다.
  • 하이퍼파라미터를 최적화하며 모델의 성능을 최적화 한다.

5. 평가

CRISP-DM 프로세스의 다섯 번째 단계는 머신러닝 모델의 성능을 평가하는 단계이다.

  • 일반적으로 정확도를 평가지표로 사용한다.
  • 문제의 정의에 따라 (암진단의 경우 양성인 사람을 음성으로 진단한 경우 큰일나므로 재현율이 중요하고 스팸메일의 경우 일단 스펨을 걸러내는게 중요하고 메일을 다시 보내달라고 하면 되니까 정밀도가 중요하다)
  • 캐글이나 DACON에서는 F1 score도 많이 사용한다.

6. 배포

CRISP-DM 프로세스의 마지막 단계는 머신러닝 모델을 배포하는 작업이다. API를 제공할 수도 있고 인사이트를 전달해 줄 수도 있다.

DACON 경진대회를 통해 알아보는 CRISP-DM

아래는 실제 DACON 경진대회에서 (수상은 못했지만..) 데이터를 전처리하고 모델을 만들었던 과정을 CRISP-DM 프레임워크에 따라 진행한 과정이다. 비즈니스문제 정의는 DACON에서 제시해주었기에 홈페이지에서 참고하면 될 것 같다.

유전체 정보 품종 분류 AI 경진대회

 

유전체 정보 품종 분류 AI 경진대회 - DACON

분석시각화 대회 코드 공유 게시물은 내용 확인 후 좋아요(투표) 가능합니다.

dacon.io

데이터 준비

  • 필요데이터 정의
  • 데이터 수집
path = '/content/drive/MyDrive/Colab Notebooks/[DACON]유전체정보_품좀_분류_AI_경진대회/data'
os.chdir(path)
df = pd.read_csv("train.csv", encoding='utf-8-sig', error_bad_lines=False)

데이터 분석

  • 분석용 데이터 준비
    • 데이터 결측치를 확인합니다
    • 학습데이터셋과 테스트데이터셋을 분리합니다
    • 특성 스케일링을 위해 관련 분야의 분석방법을 참고합니다
      • 시계열 데이터의 경우 사건의 시간 간격에 유의해 특성을 추가하면 학습의 결과를 개선할 수 있습니다.
    • 비지도학습을 통해 특성을 찾아낼 수도 있습니다
  • 데이터 전처리
    • 분석에 유효한 특성만 남기고 drop등의 매서드를 이용해 분석을 위한 데이터만 남깁니다.
    • 라벨인코딩, 원핫인코딩 등의 방법을 응용해 데이터를 학습이 가능한 형태로 가공합니다
    • 일부 알고리즘의 경우 데이터의 스케일로 인해 학습결과가 나빠질 수 있으므로 MinMaxScaler, StandardScaler 등의 방식으로 데이터의 스케일을 비슷한 수준으로 조정합니다.
    • 비지도 알고리즘인 KNN, PCA 등을 이용해 분류/회귀/군집 에 유용한 특성을 위주로 학습해 학습 결과를 개선할 수 있습니다.
#학습에 유효한 특성만 남기고 나머지는 지워준다
data = df.drop(['id', 'father', 'mother', 'gender', 'trait','class'], axis = 1)
target = df['class']

#학습데이터와 테스트데이터 분리
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(data, target, test_size = 0.2, stratify=target)

#라벨 인코더를 이용해 A/B/C로 나눠진 타겟의 라벨링을 0,1,2로 변환
from sklearn import preprocessing
target_label = preprocessing.LabelEncoder()
y_train_scaled = target_label.fit_transform(y_train)

#snp라는 유전정보가 GG/GA/AA의 3진코드화 되어있기에 열별로 라벨인코더를 만들어 변환
snp_1 =  preprocessing.LabelEncoder()
snp_2 =  preprocessing.LabelEncoder()
snp_3 =  preprocessing.LabelEncoder()
snp_4 =  preprocessing.LabelEncoder()
snp_5 =  preprocessing.LabelEncoder()
snp_6 =  preprocessing.LabelEncoder()
snp_7 =  preprocessing.LabelEncoder()
snp_8 =  preprocessing.LabelEncoder()
snp_9 =  preprocessing.LabelEncoder()
snp_10 =  preprocessing.LabelEncoder()
snp_11 =  preprocessing.LabelEncoder()
snp_12 =  preprocessing.LabelEncoder()
snp_13 =  preprocessing.LabelEncoder()
snp_14 =  preprocessing.LabelEncoder()
snp_15 =  preprocessing.LabelEncoder()

snp_1.fit(data['SNP_01'])
snp_2.fit(data['SNP_02'])
snp_3.fit(data['SNP_03'])
snp_4.fit(data['SNP_04'])
snp_5.fit(data['SNP_05'])
snp_6.fit(data['SNP_06'])
snp_7.fit(data['SNP_07'])
snp_8.fit(data['SNP_08'])
snp_9.fit(data['SNP_09'])
snp_10.fit(data['SNP_10'])
snp_11.fit(data['SNP_11'])
snp_12.fit(data['SNP_12'])
snp_13.fit(data['SNP_13'])
snp_14.fit(data['SNP_14'])
snp_15.fit(data['SNP_15'])

X_train['SNP_01'] = snp_1.transform(X_train['SNP_01'])
X_train['SNP_02'] = snp_2.transform(X_train['SNP_02'])
X_train['SNP_03'] = snp_3.transform(X_train['SNP_03'])
X_train['SNP_04'] = snp_4.transform(X_train['SNP_04'])
X_train['SNP_05'] = snp_5.transform(X_train['SNP_05'])
X_train['SNP_06'] = snp_6.transform(X_train['SNP_06'])
X_train['SNP_07'] = snp_7.transform(X_train['SNP_07'])
X_train['SNP_08'] = snp_8.transform(X_train['SNP_08'])
X_train['SNP_09'] = snp_9.transform(X_train['SNP_09'])
X_train['SNP_10'] = snp_10.transform(X_train['SNP_10'])
X_train['SNP_11'] = snp_11.transform(X_train['SNP_11'])
X_train['SNP_12'] = snp_12.transform(X_train['SNP_12'])
X_train['SNP_13'] = snp_13.transform(X_train['SNP_13'])
X_train['SNP_14'] = snp_14.transform(X_train['SNP_14'])
X_train['SNP_15'] = snp_15.transform(X_train['SNP_15'])
# 데이터를 변환(transform) 해줄때 학습데이터에만 학습(fit)해주고 테스트 데이터에는 변환(transform)만 해준다
X_test['SNP_01'] = snp_1.transform(X_test['SNP_01'])
X_test['SNP_02'] = snp_2.transform(X_test['SNP_02'])
X_test['SNP_03'] = snp_3.transform(X_test['SNP_03'])
X_test['SNP_04'] = snp_4.transform(X_test['SNP_04'])
X_test['SNP_05'] = snp_5.transform(X_test['SNP_05'])
X_test['SNP_06'] = snp_6.transform(X_test['SNP_06'])
X_test['SNP_07'] = snp_7.transform(X_test['SNP_07'])
X_test['SNP_08'] = snp_8.transform(X_test['SNP_08'])
X_test['SNP_09'] = snp_9.transform(X_test['SNP_09'])
X_test['SNP_10'] = snp_10.transform(X_test['SNP_10'])
X_test['SNP_11'] = snp_11.transform(X_test['SNP_11'])
X_test['SNP_12'] = snp_12.transform(X_test['SNP_12'])
X_test['SNP_13'] = snp_13.transform(X_test['SNP_13'])
X_test['SNP_14'] = snp_14.transform(X_test['SNP_14'])
X_test['SNP_15'] = snp_15.transform(X_test['SNP_15'])

#라벨 인코더로 유전정보 GG/GA/AA가 0,1,2로 변환되었다. 하지만 이 값들은 실제로는 순서가 없는(값끼리 독립적인) 데이터이다
#0,1,2가 서로 동일한 비중임에도 2가 0보다 의미있게 되는 상황을 예방하기 위해 MinMaxSclaer로 0,1,2를 0,0.5,1로 변환해준다
scaler = preprocessing.MinMaxScaler()
scaler.fit(X_train)
X_train_scaled = scaler.transform(X_train)
X_test_scaled = scaler.transform(X_test)

#snp1 ~ snp15 까지 어떤 특성이 더 유효한지는 알 수 없다
#비지도 학습방법인 pca를 통해 종을 분류하는데 좀더 유효한 차원으로 변환해준다
#차원을 축소하지 않고 그대로 변환해주었기 때문에 분산 데이터의 유실은 거의 없다 
from sklearn.decomposition import PCA

pca = PCA(X_train_scaled.shape[1])
X_train_pca = pca.fit_transform(X_train_scaled)
X_test_pca = pca.transform(X_test_scaled)

모델링

  • 분석용 데이터를 이용한 가설 설정을 통해 통계모델을 만들거나
  • 기계학습을 통해 분류/예측/군집 등의 기능을 수행하는 모델을 만드는 과정
  • 데이터 분할 : 훈련용 데이터와 테스트용 데이터로 분리한다
  • 데이터 모델링 : 훈련용 데이터를 이용해 분류 또는 예측, 군집 등의 모델을 만들어 시스템에 적용한다
  • 모델 적용 및 운영 방안 : 모델을 시스템에 적용하기 위한 알고리즘에 대한 설명이 제시되어야 한다.
from sklearn.ensemble import RandomForestClassifier
ran_clf = RandomForestClassifier()
ran_clf.fit(X_train_pca, y_train_scaled)
ran_pred_scaled = ran_clf.predict(X_test_pca)
#라벨인코더로 나온 결과를 다시 바꿔준다
ran_pred = target_label.inverse_transform(ran_pred_scaled)

from sklearn.svm import LinearSVC
svm = LinearSVC()
svm.fit(X_train_pca, y_train_scaled)
svm_pred_scaled = svm.predict(X_test_pca)
#라벨인코더로 나온 결과를 다시 바꿔준다
svm_pred = target_label.inverse_transform(svm_pred_scaled)

모델 평가 및 검증

  • 모델평가 : 테스트용 데이터나 검증을 위한 별도의 데이터를 이용해 검증하는 과정을 거친다
  • 모델검증 : 훈련용 데이터나 테스트용 데이터가 아닌 실제 운영데이터를 확보하여 모델의 최종 품질을 최종 검증한다
#머신러닝 평가지표
from sklearn.metrics import accuracy_score, precision_score, recall_score, confusion_matrix, f1_score
def get_clf_eval(y_test, pred):
    confusion = confusion_matrix(y_test, pred)
    accuracy = accuracy_score(y_test, pred)*100
    precision = precision_score(y_test, pred, average='macro')*100
    recall = recall_score(y_test, pred, average='macro')*100
    f1_macro = f1_score(y_test, pred,  average='macro')
    
    print('오차행렬')
    print(confusion)
    print('정확도 :',"%.1f"%accuracy+'%')
    print('정밀도 :',"%.1f"%precision+'%')
    print('재현율 :', "%.1f"%recall+'%')
    print('Macro F1 score :', f1_macro)

#모델 평가
get_clf_eval(y_test, ran_pred)
get_clf_eval(y_test, svm_pred)

#파라미터 튜닝
svm.get_params()

from skopt import BayesSearchCV

svm = LinearSVC(random_state=CFG.SEED)

svm_opt = BayesSearchCV(
    svm,
    {'C' : (10.0, 100.0, 'log-uniform'),
     'max_iter': (1000, 10000),
    },
    n_iter = 10,
    cv = 5
)
svm_opt.fit(X_train_pca, y_train_scaled)

#튜닝한 파라미터값 출력
svm_opt.best_params_

svm_opt_pred_scaled = svm_opt.predict(X_test_pca)
svm_opt_pred = target_label.inverse_transform(svm_opt_pred_scaled)

get_clf_eval(y_test, svm_opt_pred)

전개

  • 실제업무에 적용하기 위한 계획수립단계

머신러닝은 결국 데이터에서 인사이트를 캐는 과정이다

머신러닝은 결국 데이터에서 인사이트를 캐는(mining) 과정이다.

광맥이 어디있는지 모르는 상태에서 일단 이곳저곳 파보면서 인사이트를 얻는 과제들이 많다.

DNA코드를 통해 품종을 판별하는 문제를 보자.

DNA의 특정코드 몇가지가 품종을 결정하긴 할 텐데 정확한 물리법칙이 있는게 아니라 인간의 인지능력으로는 파악하기 힘든 상관관계를 알아내야 한다.

위 대회에서 1위를 했던 팀의 해결방법을 참고해보면… 정말 머신러닝은 광산에서 금속을 캐는것같이 노가다과정이다.

노가다 과정에서 더 깊은 광맥에 들어가는데 정신이 없다고 안전모를 안끼고가거나 장갑을 안끼고 간다면? 전등을 가져가지 않는다면? 이런 상황을 피하기 위해 체계화된 프레임워크를 이용해야 할 것이다.

머신러닝 프로젝트를 끝내고 나서도 개발과정에서 얻었던 더 좋은 접근방식은 분석에 참여한 본인만이 알 수 있다. 

대회에서 1등한 팀의 소감에서 알 수 있듯이 어떤 모델을 시도해보았고, 어떤 모델이 괜찮았고, 어떤식으로 Feachter를 선택했는지 하이퍼파라미터는 어떻게 튜닝했는지 그리고 그 결과는 어땠는지를 기록하고 개선하는 것이 머신러닝 프로젝트에서 가장 중요한 과정이라고 생각한다.

3줄요약

  1. 머신러닝 모델을 만드는 과정에서 필연적으로 계속 처음으로 돌아가게 된다
  2. 체계화된 분석방법 CRISP-DM을 체크리스트처럼 활용하자
  3. 모델별로 얻은 인사이트와 데이터에 대한 가정을 기록하자
728x90

관련글 더보기