하마가 분석하마

[machine learning class] 4. 프로젝트 관리 본문

Machine learning class

[machine learning class] 4. 프로젝트 관리

Rrohchan 2021. 2. 23. 16:31
이번 장에서는 py 파일을 사용한다. py 파일로 변환하는 방법은 따로 설명해 두지 않았다. 프레임워크를 만들어 사용하는 것은 필자도 처음 해보는 것이기에 다른 장에 비해 시간이 꽤 걸렸다. 천천히 해보길 바란다.

 다양한 문제에 사용할 수 있는 분류 학습을 위한 프레임워크를 만들어 보자. 먼저 프로젝트를 위한 새로운 폴더를 생성한다. 

 

project 폴더 구성

  • input : 프로젝트에 필요한 입력 파일과 데이터를 저장하는 폴더이다. 임베딩이나 이미지 파일도 이 폴더에 저장한다.
  • src : 코드를 저장하는 폴더이다. 파이썬의 경우 *py파일들이 저장된다.
  • models : 학습된 모델을 저장하는 폴더이다.
  • notebooks : 주피터 노트북 파일(*ipynb)을 저장하는 폴더이다.
  • README.md : 프로젝트에 대한 전반적인 설명을 담고 있는 마크다운 (markdown) 포맷의 파일이다. 모델을 어떻게 학습하고 운영 환경에서 어떻게 서브하는지 기술한다.
  • LICENSE : 프로젝트의 라이선스 내용을 담고 있는 텍스트 파일이다.

 

 교차검증 장에서 소개한 MNIST 데이터를 분류하는 모델을 만든다고 가정하자. 다양한 포맷의 MNIST 데이터가 있지만, 여기서는 CSV 포맷의 데이터를 사용한다. CSV 파일은 따로 첨부해 놓았다. CSV 포맷의 MNIST 데이터에는 총 60000장의 이미지가 있고 784개의 픽셀 값으로 이루어져 있다.

데이터 예시

 

 MNIST 데이터의 경우 레이블의 분포가 균일하다. 따라서 정확도나 F1과 같은 메트릭을 사용할 수 있다. 메트릭을 정하는 것이 머신러닝의 첫 번째 단계이다.

 

 먼저 CSV 파일을 input 폴더에 저장한다.

 

 첫 번째 생성할 코드는 create_folds.py이다. 이 코드는 input 폴더에 mnist_train_folds.csv라는 파일을 생성한다. 원래의 CSV 파일과 동일하지만 열이 섞여 있고, 각 샘플이 어떤 fold에 속했는지를 알려주는 kfold라는 새로운 열이 있다.

## mnist.csv 파일을 가지고 mnist_train_folds를 생성한다.
## 각 샘플이 어떤 fold에 속했는지를 알려주는 kfold라는 새로운 행이 있다.

import pandas as pd
from sklearn import model_selection

if __name__ == "__main__":
    # 학습 데이터
    df = pd.read_csv('mnist_train.csv')
    
    # kfold라는 새로운 열을 생성하고 -1로 채운다.
    df['kfold'] = -1
    
    # 다음 단계는 데이터의 행을 랜덤하게 섞는 것이다.
    df = df.sample(frac = 1).reset_index(drop = True)
    
    # model selection 모듈의 kfold 클래스를 초기화한다.
    kf = model_selection.KFold(n_splits = 5)
    
    # kfold 열을 폴드 아이디로 설정한다.
    for fold, (trn_, val_) in enumerate(kf.split(X=df)):
        df.loc[val_, 'kfold'] = fold
    
    # 데이터를 kfold 열과 함께 새로운 csv 파일로 저장한다.
    df.to_csv('mnist_train_folds.csv', index = False)

 

 검증 메트릭을 정하고, 샘플들을 k개의 fold에 할당한 후, 기본 모델을 학습할 준비가 되었다. 학습에 사용할 train.py를 만들어보자.

 

1. train.py 생성

학습에 사용할 train.py는 조금씩 복잡해진다. 본 장에서는 순서대로 코드를 첨부하나 깃허브에서는 train1, train2와 같이 올려놓았다. 또한 random_state를 따로 지정해 두지 않았기에 결과값은 전부 다를 것이다. 따라서 결과값은 별도로 올려놓지 않겠다.
### src/train.py

## 학습에 사용할 코드
import joblib
import pandas as pd
from sklearn import metrics
from sklearn import tree

def run(fold):
    # 폴드가 정의되어있는 학습 데이터를 불러온다.
    df = pd.read_csv('../input/mnist_train_folds.csv')
    
    # 학습 데이터는 kfold 열의 값이 제공된 fold와 다른 샘플들이다.
    # 인덱스를 리셋하였음을 유의하라
    df_train = df[df.kfold != fold].reset_index(drop = True)
    
    # 검증 데이터는 kfold 열의 값이 제공된 fold와 같은 샘플들이다.
    df_valid = df[df.kfold == fold].reset_index(drop = True)
    
    # 타겟 열을 제거하고 남은 피쳐를 values를 통해 numpy 행렬로 변환한다.
    # 타겟 변수는 데이터 프레임의 label 열이다.
    x_train = df_train.drop('label', axis = 1).values
    y_train = df_train.label.values
    
    # 검증 데이터도 동일하게 적용한다.
    x_valid = df_valid.drop('label', axis = 1).values
    y_valid = df_valid.label.values
    
    # sklearn의 의사결정트리 모델을 초기화한다.
    clf = tree.DecisionTreeClassifier()

    # 모델을 학습 데이터로 학습한다.
    clf.fit(x_train, y_train)
    
    # 검증 데이터의 예측 값을 생성한다.
    preds = clf.predict(x_valid)
    
    # 정확도를 계산하여 출력
    acc = metrics.accuracy_score(y_valid, preds)
    print(f'Fold = {fold}, Accuracy={acc}')
    
    # 모델을 저장한다.
    joblib.dump(clf, f'../models/dt_{fold}.bin')
    
if __name__ == '__main__':
    run(fold = 0)
    run(fold = 1)
    run(fold = 2)
    run(fold = 3)
    run(fold = 4)

 

 

train.py를 보면 CSV 경로나 출력 파일 경로가 모두 하드코딩으로 되어 있는 것을 알 수 있다. config.py라는 설정 파일에 이 정보들을 저장하여 추후 편하게 불러올 수 있도록 바꿔보자

## config.py

Training_File = '../input/mnist_train_folds.csv'
Model_Output = '../models/'

 

2. train.py 수정-1

 config.py를 적용하여 train.py를 수정해보자.

### src/train.py + config.py를 사용

## 학습에 사용할 코드
import os

import config

import joblib
import pandas as pd
from sklearn import metrics
from sklearn import tree

def run(fold):
    # 폴드가 정의되어있는 학습 데이터를 불러온다.
    df = pd.read_csv(config.Training_File)
    
    # 학습 데이터는 kfold 열의 값이 제공된 fold와 다른 샘플들이다.
    # 인덱스를 리셋하였음을 유의하라
    df_train = df[df.kfold != fold].reset_index(drop = True)
    
    # 검증 데이터는 kfold 열의 값이 제공된 fold와 같은 샘플들이다.
    df_valid = df[df.kfold == fold].reset_index(drop = True)
    
    # 타겟 열을 제거하고 남은 피쳐를 values를 통해 numpy 행렬로 변환한다.
    # 타겟 변수는 데이터 프레임의 label 열이다.
    x_train = df_train.drop('label', axis = 1).values
    y_train = df_train.label.values
    
    # 검증 데이터도 동일하게 적용한다.
    x_valid = df_valid.drop('label', axis = 1).values
    y_valid = df_valid.label.values
    
    # sklearn의 의사결정트리 모델을 초기화한다.
    clf = tree.DecisionTreeClassifier()

    # 모델을 학습 데이터로 학습한다.
    clf.fit(x_train, y_train)
    
    # 검증 데이터의 예측 값을 생성한다.
    preds = clf.predict(x_valid)
    
    # 정확도를 계산하여 출력
    acc = metrics.accuracy_score(y_valid, preds)
    print(f'Fold = {fold}, Accuracy={acc}')
    
    # 모델을 저장한다.
    joblib.dump(clf, os.path.join(config.Model_Output, f'../models/dt_{fold}.bin'))
    

if __name__ == '__main__':
    run(fold = 0)
    run(fold = 1)
    run(fold = 2)
    run(fold = 3)
    run(fold = 4)

 

 

 현재 각 fold 마다 run() 함수가 여러 번 호출되고 있다. 이는 메모리 사용량이 증가하여 프로그램을 다운시킬 수 있다. 때문에 한 코드에서 여러 fold를 실행하는 것은 바람직하지 않다. 이 문제를 해결하기 위해 train.py에서 fold를 입력 인수로 받아 한 fold씩 실행하도록 변경하자. 파이썬에서는 argparse를 사용하여 코드를 실행할 때 입력 인수를 받도록 할 수 있다.

argarse는 jupyter notebook에서 실행되지 않는다. argparse를 사용하기 원한다면 터미널이나 다른 프레임워크에서 실행해야 한다. 본 장의 argparse 실행 전까지는 jupyter notebook 내에서 py 파일로 전환하여 불러오는 게 가능하다. 
import argparse

"""
중간에 train.py의 내용들을 넣는다.
"""

if __name__ == '__main__':
    # argparse의 ArgumentParser()
    parser = argparse.ArgumentParser()
    
    # 필요한 입력 변수와 타입을 추가한다. 현재는 폴드 밖에 없다.
    parser.add_argument(
    '--fold',
    type = int
    )
    
    # 입력 변수를 콘솔로부터 읽어 들인다.
    args = parser.parse_args()
    
    # 콘솔로 읽어 들인 폴드 값에 대해 코드를 실행한다.
    run(folds = args.fold)
## 아래 코드는 터미널 창에서 실행해야 한다.
> python train.py --fold 0
Fold=0, Accuracy=0.865666666666

 

3. train.py 수정-final

 지금까지 만든 py 파일들을 보면 모델을 구성하는 부분이 하드코딩 되어 있음을 알 수 있다. 다양한 알고리즘 및 여러 파라미터를 유연하게 바꿀 수 있도록 model_dispatcher.py 파일을 만들어보자. 이 파일은 다양한 종류의 모델을 학습 코드로 파견한다.

 

## model_dispatcher.py

## 다양한 종류의 코드를 학습 코드로 파견
from sklearn import tree
from sklearn import ensemble

models = {
    'decision_tree_gini' : tree.DecisionTreeClassifier(
        criterion = 'gini'
    ),
    'decision_tree_enthropy' : tree.DecisionTreeClassifier(
        criterion = 'entropy'
    ),
    'rf' : ensemble.RandomForestClassifier(),
}

 

 model_dispatcher를 사용해 train.py를 수정

### src/train.py + config.py를 사용

## 학습에 사용할 코드
import os
import argparse

import model_dispatcher
import config

import joblib
import pandas as pd
from sklearn import metrics
from sklearn import tree

def run(fold, model):
    # 폴드가 정의되어있는 학습 데이터를 불러온다.
    df = pd.read_csv(config.Training_File)
    
    # 학습 데이터는 kfold 열의 값이 제공된 fold와 다른 샘플들이다.
    # 인덱스를 리셋하였음을 유의하라
    df_train = df[df.kfold != fold].reset_index(drop = True)
    
    # 검증 데이터는 kfold 열의 값이 제공된 fold와 같은 샘플들이다.
    df_valid = df[df.kfold == fold].reset_index(drop = True)
    
    # 타겟 열을 제거하고 남은 피쳐를 values를 통해 numpy 행렬로 변환한다.
    # 타겟 변수는 데이터 프레임의 label 열이다.
    x_train = df_train.drop('label', axis = 1).values
    y_train = df_train.label.values
    
    # 검증 데이터도 동일하게 적용한다.
    x_valid = df_valid.drop('label', axis = 1).values
    y_valid = df_valid.label.values
    
    # sklearn의 의사결정트리 모델을 초기화한다.
    clf = model_dispatcher.models[model]

    # 모델을 학습 데이터로 학습한다.
    clf.fit(x_train, y_train)
    
    # 검증 데이터의 예측 값을 생성한다.
    preds = clf.predict(x_valid)
    
    # 정확도를 계산하여 출력
    acc = metrics.accuracy_score(y_valid, preds)
    print(f'Fold = {fold}, Accuracy={acc}')
    
    # 모델을 저장한다.
    joblib.dump(clf, os.path.join(config.Model_Output, f'../models/dt_{fold}.bin'))


if __name__ == '__main__':
    # argparse의 ArgumentParser()
    parser = argparse.ArgumentParser()
    
    # 필요한 입력 변수와 타입을 추가한다. 현재는 폴드 밖에 없다.
    parser.add_argument(
    '--fold',
    type = int
    )
    parser.add_argument(
    '--model',
    type = str
    )

    # 입력 변수를 콘솔로부터 읽어 들인다.
    args = parser.parse_args()
    
    # 콘솔로 읽어 들인 폴드 값에 대해 코드를 실행한다.
    run(fold = args.fold,
        model = args.model)

 

  1. model_dispatcher를 불러옴
  2. --model 입력 인수를 ArgumentParser에 추가
  3. model 입력 인수를 run() 함수에 추가
  4. model_dispatcher로 model 이름에 해당하는 모델을 생성

 이제 다음과 같이 train.py를 실행할 수 있다. 터미널 창에서 실행해야 함을 잊지말자.

> python train.py --fold 0 --model decision_tree_gini
> python train.py --fold 0 --model decision_tree_enthropy
> python train.py --fold 0 --model rf

 

Pycharm과 jupyter notebook을 사용해서 천천히 실습해보길 바란다. 위의 과정을 조금 더 본인에게 맞게 구성하고 알고리즘 및 전처리 함수 등을 추가하여 프로젝트 폴더를 만들면 추후 분석을 진행할 때 훨씬 편할 것이다.

 


 model_dispatcher.py와 config.py를 불러올 때 import * 문법을 사용하지 않은 이유는 어떤 함수와 오브젝트가 어떤 코드에서 불려졌는지 알 수가 없기 때문에 오류가 발생했을 때 해결하기 어려워진다. 이해할 수 있는 좋은 코드를 작성하는 것은 데이터 과학자로 성공하는데 필수적인 요소임에도, 많은 사람이 이를 경시한다.
 또한 팀으로 프로젝트를 진행할 때, 다른 팀원의 코드를 일일이 물어보지 않고도 이해할 수 있다면, 팀 전체의 시간을 크게 절약 함은 물론, 이 시간을 프로젝트를 향상시키거나 새로운 프로젝트를 시작하는데 투자할 수 있을 것이다.

서버를 제공해주는 큰 공모전을 나갔을 때, 베이스 라인 코드를 포함한 파일 제출 코드를 py파일로 제공해주기도 한다. 이 기회에 미리 익숙해지는 시간이 되었으면 좋겠다.

 학습된 모델을 저장하는 부분과 쉘 스크립트를 사용하여 모든 fold에 대해 파이썬 코드를 실행하는 방법은 시도하였으나 실패하였다.. 아시는 분은 알려주면 감사하겠다.

 

https://github.com/rch1025/Machine-learning-class/tree/5.-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B4%80%EB%A6%AC

 

rch1025/Machine-learning-class

Several concepts and several tips that are important for data analysis can be learned. I studied based on 'Machine Learning Master Class'. - rch1025/Machine-learning-class

github.com