[Machine Learning as an Application] 1. Random Search Object

2020-09-11

어떤 머신러닝모델이든 영원히 유효할 수는 없다. 특정한 시점에 실험을 반복해서 높은 성능을 달성했더라도, 새로운 데이터가 유입되면 최적화와 refitting을 통해 적응해야 한다. 그래서 요즘은 머신러닝, 딥러닝도 그냥 어플리케이션이라는 생각이 든다. 최종 모델과 그 모델을 빌드업한 논리적인 과정 만큼이나 ‘안정적인 실험을 가능케하는 코드’도 중요하다는 것. 이번에는 실제 프로젝트에서 하이퍼 파라미터를 탐색할 때 사용했던 함수들을 클래스로 만들면서 어플리케이션으로서의 머신러닝에 가까워지려고 노력해 보았다.

왜 굳이 클래스로 만드려고 했냐면

지난 한 달 간 그 때 그 때 필요에 따라 함수를 작성하고 다른 사람이 작성한 함수를 수정해서 사용했다. 세 가지 모델을 시도했고, 그 중 한 가지로 최종 모델을 확정하여 고도화 단계에 접어들 때부터 슬슬 한계를 느끼기 시작했다. 특히 불편했던 점만 적어보자면

  1. nesting 되어 있는 함수의 구조 상 데이터의 인덱스 등 초반 단계의 데이터에 접근하기가 힘들었다.
    trainig set과 test set이 training 및 validation 함수 내에서 또 다른 함수 호출로 생성되고 사라지는 구조였다. 한편 test harness를 일관적으로 유지하기 위해 매번의 search 마다 training set과 test set은 동일하게 유지되어야 하는데 이를 보장할 수 있는 방법이 없었다. class variable의 필요를 느꼈던 순간.

  2. search space를 체계적으로 관리할 수 없었다.
    하이퍼 파라미터를 탐색할 때 마다 그 많은 것들 중 어떤 것을 변경해가면서 시도할지, 어떤 범위에서 시도할지, 해당 범위에서 조합을 몇 개나 추출할지 등등이 달라진다. 물론 탐색 결과 가장 성능이 좋았던 조합 하나만 남기려는 것이 목적이라면야 굳이 남길 필요 없지만, 대부분의 프로젝트에서 (특히 하이퍼파라미터 수가 많은 딥러닝 모델이라면) 탐색 결과를 보고 범위를 좁혀 다시 세밀한 탐색을 진행하는 coarse to fine 방법을 쓰기 마련이다. 클래스의 목표는 기능과 데이터를 한 데 묶어 관리하자는 것이다. 그 전에 함수 뭉치가 기능만 있었다면 class variable과 instance variable을 통해 각 실험의 환경을 편리하게 관리하고 싶었다.

욕심을 줄이자

그렇게 생각이 닿고 곧장 코드를 고치기 시작하니까 모든 것을 클래스로 바꾸고 싶었다. 사실 모든 개념은 오브젝트니까 말이 안되는 것은 아니다. 그런데 파이썬 클래스를 연습하려는 목적이 아니라면 비용에 대비한 효용을 좀 생각해보자. 탐색할 하이퍼 파라미터를 클래스로 만들었다가 ‘아 이건 아니지’ 하는 생각이 들어서 진짜 내가 원하는 것을 정리해 보았다. 무엇을 입력할 것이고, 내가 원하는 기능은 무엇인지.

  • 탐색할 하이퍼 파라미터의 [이름, 범위]와 총 횟수를 입력한다
  • training set과 test set의 형태와 index를 보존할 것
  • 성과가 좋았던 조합: scikit learn의 grid_search 에서 best_params_와 같은 역할
  • 성과가 좋았던 조합을 입력했을 때 바로 훈련할 수 있을 것
  • 탐색한 범위와 각 조합의 성과를 요약

그리고 구현하면 좋겠지만 어렵고 효용은 그닥 없는 것도 정리해 보았다.

  • keras model의 모든 하이퍼파라미터에 대해서 이름만 입력하면 해당 자리에 알아서(?) 들어가서 설정할 것

정리하고 보니 우선 순위가 명백해졌고 좀 전에 만들었던 Hyperparameter 클래스는 머쓱해하며 지웠다. 마지막 기능을 구현할 것이 아니라면 괜히 랜덤 서치 인스턴스의 initialization만 길어지게 할 뿐이었다. 간소하게 dictionary로 처리하기로 했다.

무엇을 static 함수로 만들까?

주요 variable 항목과 형태를 정한 다음에는 함수가 남았다. 대부분 원래 쓰던 함수의 구조를 따라갔지만 이를 instance method로 할지, static method로 할 지 결정해야 했다. 참고로 파이썬 메소드는 세 가지 종류가 있다

Instance Method

  • 파라미터로 self를 받아 인스턴스에 존재하는 데이터와 다른 메소드(self.) 에 자유롭게 접근할 수 있다
  • self.__class__를 통해 class의 state도 변경할 수 있다

Static Method

  • 클래스와 관련이 있지만, 클래스의 데이터에 접근하지 않아도 되는 메소드
  • 독립적인 태스크를 시행하는 untility function
  • they’re primarily a way to namespace your methods
  • static method의 구분은 클래스 디자인의 의도를 보다 명백히 전달하고 의도치 않은 버그를 방지하려는 커뮤니케이션 측면으로도 기능함

Class Method

  • self 대신 cls 파라미터를 받으며, class state에 접근할 수 있다.
  • 예를 들면, 자주 쓰이는 인스턴스를 생성하는 클래스 메소드를 만들 수 있다. 클래스명이 수정되었을 때 자주 쓰이는 인스턴스 생성 스크립트를 변경해주지 않아도 된다 https://realpython.com/instance-class-and-static-methods-demystified/
  • 다른 방법으로 __init__ 을 쓸 수 있다(python은 한 클래스 당 하나의 이닛함수만 허용)

static method와 instance method의 구분이 디자인 의도를 반영한다는 점에서 내가 작성하는 클래스의 주요 기능을 되돌아 볼 필요가 있었다. RandomSearch는 탐색할 하이퍼 파라미터가 주어졌을때 그에 맞게 여러 개의 모델을 학습하고 테스트 성과를 출력하는 클래스다. 그러니까 하이퍼파라미터 조합에 따라 모델을 생성(compile) - 학습 - 테스트 가 주요한 기능이고, 나머지는 다 utility성으로 구분할 수 있다. 이 의도에 따라 생성, 학습, 테스트에 해당하지 않는 함수들은 모두 static method로 정의했다. 만든 사람이 그게 의도라는데 그게 일관적으로 잘 드러나기만 하면 되겠지.