본문 바로가기

코드읽기/auto-sklearn

[AutoML] 구현을 향해, sklearn-BaseEstimator? 2편

서론

이미 검색창에 AutoML이라고 검색하면, 잘 만들어진 AutoML들이 많다.

많은 좋은 프로그래밍들을 보고 코드 의도와 구현 방식을 배우는 것이 목표이다.

 

AutoSklearnClassifier Source 확인

원본은 아래 공식홈페이지에서 확인 할 수 있습니다.

https://automl.github.io/auto-sklearn/master/api.html#classification

 

APIs — AutoSklearn 0.14.7 documentation

Classification metrics Note: The default autosklearn.metrics.f1, autosklearn.metrics.precision and autosklearn.metrics.recall built-in metrics are applicable only for binary classification. In order to apply them on multilabel and multiclass classification

automl.github.io

분석대상은 AutoSklearnClassifier 알고리즘입니다.

[AutoML] 구현을 향해, sklearn-BaseEstimator? 2편

1. BaseEstimator 확인

대표적으로 2개 클래스가 있습니다

class AutoSklearnEstimator(BaseEstimator):
class AutoSklearnClassifier(AutoSklearnEstimator, ClassifierMixin):

AutoSklearnClassifier는 AutoSklearnEstimator를 상속받고 있으며, AutoSkearnEstimator는 BaseEstimator를 상속받고 있습니다. 그렇다면 sklearn의 BaseEstimator를 확인해보겠습니다.

BaseEstimator
  • BaseEstimator는 이름에서도 알 수 있는 것처럼 사이킷런의 모든 분류기의 기본 클래스입니다.
  • BaseEstimator에서 사용 할 수 있는 메서드는 'get_params'와 'set_params' 입니다.
get_params : 분류기에 대해 설정된 파라미터를 가져오는 메서드
set_params : 분류기의 파라미터를 설정하는 메서드

 

set_params

def set_params(self, **params):
        """
        Set the parameters of this estimator.
        The method works on simple estimators as well as on nested objects
        (such as :class:`~sklearn.pipeline.Pipeline`). The latter have
        parameters of the form ``<component>__<parameter>`` so that it's
        possible to update each component of a nested object.
        Parameters
        ----------
        **params : dict
            Estimator parameters.
        Returns
        -------
        self : estimator instance
            Estimator instance.
        """
        if not params:
            # Simple optimization to gain speed (inspect is slow)
            return self
        valid_params = self.get_params(deep=True)

        nested_params = defaultdict(dict)  # grouped by prefix
        for key, value in params.items():
            key, delim, sub_key = key.partition("__")
            if key not in valid_params:
                raise ValueError(
                    "Invalid parameter %s for estimator %s. "
                    "Check the list of available parameters "
                    "with `estimator.get_params().keys()`." % (key, self)
                )

            if delim:
                nested_params[key][sub_key] = value
            else:
                setattr(self, key, value)
                valid_params[key] = value

        for key, sub_params in nested_params.items():
            valid_params[key].set_params(**sub_params)

        return self

분석에 사용되는 객체는 RandomForsetClassifier로 정했습니다.

# get_params를 사용해 현재 RandomForestClassifier에서 사용된 파라미터를 valid_params에 저장한다.
from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier()
valid_params = rf.get_params()
valid_params

[out]
'''
{'bootstrap': True,
 'ccp_alpha': 0.0,
 'class_weight': None,
 'criterion': 'gini',
 'max_depth': None,
 'max_features': 'auto',
 'max_leaf_nodes': None,
 'max_samples': None,
 'min_impurity_decrease': 0.0,
 'min_samples_leaf': 1,
 'min_samples_split': 2,
 'min_weight_fraction_leaf': 0.0,
 'n_estimators': 100,
 'n_jobs': None,
 'oob_score': False,
 'random_state': None,
 'verbose': 0,
 'warm_start': False}
'''

set_params를 하기위해서 적용할 새로운 파라미터를 만들어야하는데 만들기 귀찮으니까,

랜덤포레스트의 기존 파라미터를 카피하고 'ccp_alpha'부분만 1로 수정해주겠습니다.

params = valid_params.copy()
params['ccp_alpha'] = 1
params

[out]
'''
{'bootstrap': True,
 'ccp_alpha': 1,
 'class_weight': None,
 'criterion': 'gini',
 'max_depth': None,
 'max_features': 'auto',
 'max_leaf_nodes': None,
 'max_samples': None,
 'min_impurity_decrease': 0.0,
 'min_samples_leaf': 1,
 'min_samples_split': 2,
 'min_weight_fraction_leaf': 0.0,
 'n_estimators': 100,
 'n_jobs': None,
 'oob_score': False,
 'random_state': None,
 'verbose': 0,
 'warm_start': False}
'''
  • 그렇다면 이제 set_params를 사용하면 'ccp_alpha'의 값이 0에서 1로 바뀌어야 한다.
  • 사이킷런은 파라미터의 값에서 '__'가 있는 경우를 위해 nested_params를 사용하는데, 아직까지 피쳐 값에 언더바 두개('__')가 있는 경우를 본적이 없습니다.
  • partition() 함수를 사용하면 __기준으로 split됩니다.
dict_['abc__efg'] = 1
for i,k in dict_.items():
    print(i.partition("__"))
    
[out]
'''
('abc', '__', 'efg')
'''

그리고 파라미터를 업데이트 하는 과정은 아래 코드 주석으로 설명하겠습니다.

# '__'가 있는 경우를 위해 빈 딕셔너리를 만듭니다
nested_params = defaultdict(dict)

# params는 적용하고 싶은 새 파라미터입니다.
# 값을 하나씩 for문 돌리면서 확인합니다.
for key, value in params.items():
    print('key = ',key)
    print('value = ', value)
	
    # key를 __기준으로 분리합니다. key에 '__'가 없는 경우는 공백이 생깁니다
    # ex) key = n_features
    # key.partition('__') -> (n_features, "", "")
    key, delim, sub_key = key.partition("__")
    print('after partition_key ',key)
    
    # 업데이트 하고 싶은 파라미터 key가 해당 분류가에서 지원하지 않으면 에러를 발생시킴
    if key not in valid_params:
        raise ValueError(
            "Invalid parameter %s for estimator %s. "
            "Check the list of available parameters "
            "with `estimator.get_params().keys()`." % (key, rf)
        )
	
    # 업데이트 하고싶은 파라미터 key에 '__'가 포함되어서 delim값이 있는 경우
    # nested_params를 업데이트함
    if delim:
        nested_params[key][sub_key] = value
        print(nested_params)
    # 아닌경우 rf.key를 value값으로 업데이트 합니다
    # valid_params값과 rf의 get_params의 주소값이 같아서 아래와같은 방식으로 업데이트됩니다.
    else:
        setattr(rf, key, value)
        valid_params[key] = value
    print("=======================")
print('nested_params', nested_params)
# 현재는 nested_params에 아무값도 없기떄문에 아무일도 일어나지않습니다.
for key, sub_params in nested_params.items():
    valid_params[key].set_params(**sub_params)
    
    
[out]
'''
key =  bootstrap
value =  True
after partition_key  bootstrap
=======================
key =  ccp_alpha
value =  1
after partition_key  ccp_alpha
=======================
key =  class_weight
value =  None
after partition_key  class_weight
=======================
key =  criterion
value =  gini
after partition_key  criterion
=======================
key =  max_depth
value =  None
after partition_key  max_depth
=======================
key =  max_features
value =  auto
after partition_key  max_features
=======================
key =  max_leaf_nodes
value =  None
after partition_key  max_leaf_nodes
=======================
key =  max_samples
value =  None
after partition_key  max_samples
=======================
key =  min_impurity_decrease
value =  0.0
after partition_key  min_impurity_decrease
=======================
key =  min_samples_leaf
value =  1
after partition_key  min_samples_leaf
=======================
key =  min_samples_split
value =  2
after partition_key  min_samples_split
=======================
key =  min_weight_fraction_leaf
value =  0.0
after partition_key  min_weight_fraction_leaf
=======================
key =  n_estimators
value =  100
after partition_key  n_estimators
=======================
key =  n_jobs
value =  None
after partition_key  n_jobs
=======================
key =  oob_score
value =  False
========================
nested_params defaultdict(<class 'dict'>, {})
'''
'''
이번 포스팅에서는 AutoSklearnClassifier에서 상속하는 BaseEstimator에 대해 분석했습니다.
다음 포스팅에서는 AutoSklearnClassifier를 세분화하여 알아보겠습니다.