캐글이나 데이콘에서 프로젝트를 진행해보면 알 수 있다. 문제가 얼마나 중구난방이 되는지.
여러 전처리과정, 모델 최적화 등을 거치다보면 점수를 0.01점 더 높히기 위한 끝없는 노가다가 되기 마련이다.
하지만.. 실제 서비스를 할 때 Accuracy가 0.80 이나 0.85나 큰 차이가 있을까? 모델 개발을 위한 작은 데이터셋에서 Accuracy가 0.99999 가 나오는 것도 오버피팅의 가능성이 있기에 그렇게 바람직하지는 않다.
오히려 머신러닝 프로젝트를 성공하기 위해 필요한건 결과보다 구체적인 과정이다. 데이터에 어떤 가정이 들어갔는지, 각 전처리와 모델 최적화 과정에서 어떤부분이 고려되었는지 등등 과정에 대한 구체적인 기술이 필요하다.
CRISP-DM 프로세스는 일종의 체크리스트다. 파일럿이 출항하기전 번거로워도 단계별 SOP를 체크하는 것처럼 머신러닝 프로젝트가 복잡한 문제에 체계적으로 접근할 수 있는 프레임워크가 되어준다. 이 프로세스를 따르면 분석이 잘 계획되고 실행되고 잘 전달되는지 확인 할 수 있다.
홈페이지를 만들거나 프로그램을 만드는 과정은 물이 위에서 아래로 흐르듯이 진행될 수 있다.
계획하고 기한을 정해 필요한 기능을 구현하고 테스트하고 배포하고 피드백받는 전과정이 순차적으로 진행된다. 처음부터 다시 시작해야하는 경우가 잘 없고 이미 제작된 템플릿이 존재하는 경우도 많다.
머신러닝 프로젝트는 새로운 모델을 사용하면 전처리부터 다시 해야하는 경우가 많다.
예를들어 랜덤포레스트 분류 모델을 사용해서 F1 score가 0.7이 나왔다. 모델을 아무리 튜닝해도 점수가 오르지 않아서 시각화를 해보니까 Feature별로 데이터의 분포가 고르지 않아서 SVM 모델을 사용해보려고 한다. 랜덤포레스트는 데이터스케일이 필요하지않지만 SVM은 데이터가 비슷한 스케일로 모여있어야 분류가 가능한 모델이다. 즉 데이터 전처리부터 다시 시작해야한다는 것이다.
이런식으로 모델돌려보고 인사이트를 얻고 문제를 재정의하고 개선하는 과정에서 What/How를 다시 정의하는 일이 계속 일어난다.
인사이트와 데이터에 대한 가정이 문제를 해결하는 과정에서 꼬리에 꼬리를 물며 돌아가기 때문에 돌아가더라도 인사이트를 놓치지 않기위한 모듈화된 프레임워크가 필요하고 이게 CRISP-DM이다.
1. 비즈니스 이해
CRISP-DM 프로세스의 첫 번째 단계는 해결해야 할 비즈니스 문제를 이해하는 것이다. 비즈니스 목표 식별, 이러한 목표를 달성하는 데 필요한 데이터 결정, 성공 기준 정의가 포함된다.
2. 데이터 이해
CRISP-DM 프로세스의 두 번째 단계는 데이터를 이해하는 것으로 데이터를 시각화해보고 분포를 살펴보며 분석에 알맞은 형태로 되어있는지를 확인한다.
3. 데이터 준비
CRISP-DM 프로세스의 세 번째 단계는 본격적인 분석을 위한 데이터 정리 및 준비단계이다. 누락된 값을 제거하고 이상값을 처리하고 데이터 불일치를 처리하여 데이터를 정리할 수 있다.
4. 모델링
CRISP-DM 프로세스의 네 번째 단계는 머신러닝 모델을 구축하고 학습하는 단계이다. 문제에 따라 회귀, 분류 및 클러스터링과 같은 다양한 알고리즘을 사용한다.
5. 평가
CRISP-DM 프로세스의 다섯 번째 단계는 머신러닝 모델의 성능을 평가하는 단계이다.
6. 배포
CRISP-DM 프로세스의 마지막 단계는 머신러닝 모델을 배포하는 작업이다. API를 제공할 수도 있고 인사이트를 전달해 줄 수도 있다.
아래는 실제 DACON 경진대회에서 (수상은 못했지만..) 데이터를 전처리하고 모델을 만들었던 과정을 CRISP-DM 프레임워크에 따라 진행한 과정이다. 비즈니스문제 정의는 DACON에서 제시해주었기에 홈페이지에서 참고하면 될 것 같다.
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)
#학습에 유효한 특성만 남기고 나머지는 지워준다
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를 선택했는지 하이퍼파라미터는 어떻게 튜닝했는지 그리고 그 결과는 어땠는지를 기록하고 개선하는 것이 머신러닝 프로젝트에서 가장 중요한 과정이라고 생각한다.
[GPT] 나만의 작은 Chat-GPT! "알파카" 코드 두줄로 설치하는 법 (LLaMA, Dalai, Alpaca 설치법) (3) | 2023.03.26 |
---|---|
[Anormaly Detection] Isolation Forest 분류트리를 거꾸로 생각하면 이상치를 찾을때 사용할 수 있다! (0) | 2023.03.19 |
빅데이터 기반의 플라즈마 진단에 관한 생각 (2) | 2023.03.04 |
[머신러닝 커닝 페이퍼] 분류를 위한 결정트리의 모든 것 2편 (0) | 2022.12.15 |
tree_model.feature_importances_ , coef_, interept_ 처럼 밑줄이 왜 붙을까? (0) | 2022.12.07 |