067 Custom Learner - sklearn 모델 래핑
키워드: sklearn 래핑, 모델 통합, 커스터마이징
개요
sklearn에는 FLAML이 기본 지원하지 않는 다양한 모델이 있습니다. 이 글에서는 여러 sklearn 모델을 Custom Learner로 래핑하여 FLAML에 통합하는 방법을 알아봅니다.
실습 환경
- Python 버전: 3.11 권장
- 필요 패키지:
flaml[automl], scikit-learn
pip install flaml[automl] scikit-learn pandas numpy
기본 데이터 준비
import numpy as np
import pandas as pd
from sklearn.datasets import make_classification, make_regression
from sklearn.model_selection import train_test_split
from flaml import AutoML
from flaml.automl.model import SKLearnEstimator
# 067 분류 데이터
X_clf, y_clf = make_classification(n_samples=2000, n_features=20,
n_informative=10, n_classes=2,
random_state=42)
X_clf_train, X_clf_test, y_clf_train, y_clf_test = train_test_split(
X_clf, y_clf, test_size=0.2, random_state=42
)
# 067 회귀 데이터
X_reg, y_reg = make_regression(n_samples=2000, n_features=20,
noise=10, random_state=42)
X_reg_train, X_reg_test, y_reg_train, y_reg_test = train_test_split(
X_reg, y_reg, test_size=0.2, random_state=42
)
print(f"분류 데이터: {X_clf_train.shape}")
print(f"회귀 데이터: {X_reg_train.shape}")
서포트 벡터 머신 (SVM)
SVC (분류)
from sklearn.svm import SVC
class SVCLearner(SKLearnEstimator):
"""SVM 분류기 Custom Learner"""
@classmethod
def search_space(cls, data_size, task):
return {
'C': {
'domain': np.logspace(-3, 3, 100),
'init_value': 1.0,
},
'kernel': {
'domain': ['rbf', 'linear', 'poly'],
'init_value': 'rbf',
},
'gamma': {
'domain': np.logspace(-4, 1, 50),
'init_value': 0.1,
},
'degree': {
'domain': [2, 3, 4, 5],
'init_value': 3,
},
}
def config2params(self, config):
params = config.copy()
# poly 커널이 아니면 degree 제거
if params.get('kernel') != 'poly':
params.pop('degree', None)
# linear 커널이면 gamma 제거
if params.get('kernel') == 'linear':
params.pop('gamma', None)
return params
def __init__(self, task='classification', **config):
super().__init__(task, **config)
self.estimator_class = SVC
# 확률 예측 활성화
self.params['probability'] = True
# 067 SVC 테스트
automl = AutoML()
automl.add_learner('svc', SVCLearner)
automl.fit(
X_clf_train, y_clf_train,
task="classification",
time_budget=60,
estimator_list=['svc'],
metric='accuracy',
verbose=1
)
print(f"\nSVC 최적 설정: {automl.best_config}")
print(f"정확도: {1 - automl.best_loss:.4f}")
SVR (회귀)
from sklearn.svm import SVR
class SVRLearner(SKLearnEstimator):
"""SVM 회귀 Custom Learner"""
@classmethod
def search_space(cls, data_size, task):
return {
'C': {
'domain': np.logspace(-2, 3, 50),
'init_value': 1.0,
},
'kernel': {
'domain': ['rbf', 'linear', 'poly'],
'init_value': 'rbf',
},
'gamma': {
'domain': np.logspace(-4, 1, 50),
'init_value': 'scale',
},
'epsilon': {
'domain': np.logspace(-3, 0, 30),
'init_value': 0.1,
},
}
def __init__(self, task='regression', **config):
super().__init__(task, **config)
self.estimator_class = SVR
# 067 SVR 테스트
automl_svr = AutoML()
automl_svr.add_learner('svr', SVRLearner)
automl_svr.fit(
X_reg_train, y_reg_train,
task="regression",
time_budget=60,
estimator_list=['svr'],
metric='mae',
verbose=1
)
print(f"\nSVR 최적 설정: {automl_svr.best_config}")
K-최근접 이웃 (KNN)
from sklearn.neighbors import KNeighborsClassifier, KNeighborsRegressor
class KNNClassifierLearner(SKLearnEstimator):
"""KNN 분류기 Custom Learner"""
@classmethod
def search_space(cls, data_size, task):
return {
'n_neighbors': {
'domain': list(range(1, 31)),
'init_value': 5,
'low_cost_init_value': 1,
},
'weights': {
'domain': ['uniform', 'distance'],
'init_value': 'uniform',
},
'metric': {
'domain': ['euclidean', 'manhattan', 'minkowski'],
'init_value': 'euclidean',
},
'p': {
'domain': [1, 2, 3],
'init_value': 2,
},
}
def config2params(self, config):
params = config.copy()
# minkowski가 아니면 p 제거
if params.get('metric') != 'minkowski':
params.pop('p', None)
return params
def __init__(self, task='classification', **config):
super().__init__(task, **config)
self.estimator_class = KNeighborsClassifier
class KNNRegressorLearner(SKLearnEstimator):
"""KNN 회귀 Custom Learner"""
@classmethod
def search_space(cls, data_size, task):
return {
'n_neighbors': {
'domain': list(range(1, 31)),
'init_value': 5,
},
'weights': {
'domain': ['uniform', 'distance'],
'init_value': 'distance',
},
'metric': {
'domain': ['euclidean', 'manhattan'],
'init_value': 'euclidean',
},
}
def __init__(self, task='regression', **config):
super().__init__(task, **config)
self.estimator_class = KNeighborsRegressor
# 067 KNN 테스트
automl_knn = AutoML()
automl_knn.add_learner('knn', KNNClassifierLearner)
automl_knn.fit(
X_clf_train, y_clf_train,
task="classification",
time_budget=30,
estimator_list=['knn'],
verbose=1
)
print(f"\nKNN 최적 설정: {automl_knn.best_config}")
나이브 베이즈
from sklearn.naive_bayes import GaussianNB, MultinomialNB
class GaussianNBLearner(SKLearnEstimator):
"""가우시안 나이브 베이즈 Custom Learner"""
@classmethod
def search_space(cls, data_size, task):
return {
'var_smoothing': {
'domain': np.logspace(-12, -6, 50),
'init_value': 1e-9,
},
}
def __init__(self, task='classification', **config):
super().__init__(task, **config)
self.estimator_class = GaussianNB
# 067 나이브 베이즈 테스트
automl_nb = AutoML()
automl_nb.add_learner('gaussian_nb', GaussianNBLearner)
automl_nb.fit(
X_clf_train, y_clf_train,
task="classification",
time_budget=20,
estimator_list=['gaussian_nb'],
verbose=1
)
print(f"\nGaussianNB 최적 설정: {automl_nb.best_config}")
결정 트리
from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor
class DecisionTreeClassifierLearner(SKLearnEstimator):
"""결정 트리 분류기 Custom Learner"""
@classmethod
def search_space(cls, data_size, task):
return {
'max_depth': {
'domain': list(range(2, 30)) + [None],
'init_value': 10,
'low_cost_init_value': 3,
},
'min_samples_split': {
'domain': list(range(2, 50)),
'init_value': 2,
},
'min_samples_leaf': {
'domain': list(range(1, 20)),
'init_value': 1,
},
'criterion': {
'domain': ['gini', 'entropy'],
'init_value': 'gini',
},
'splitter': {
'domain': ['best', 'random'],
'init_value': 'best',
},
}
def __init__(self, task='classification', **config):
super().__init__(task, **config)
self.estimator_class = DecisionTreeClassifier
class DecisionTreeRegressorLearner(SKLearnEstimator):
"""결정 트리 회귀 Custom Learner"""
@classmethod
def search_space(cls, data_size, task):
return {
'max_depth': {
'domain': list(range(2, 30)) + [None],
'init_value': 10,
'low_cost_init_value': 3,
},
'min_samples_split': {
'domain': list(range(2, 50)),
'init_value': 2,
},
'min_samples_leaf': {
'domain': list(range(1, 20)),
'init_value': 1,
},
'criterion': {
'domain': ['squared_error', 'friedman_mse', 'absolute_error'],
'init_value': 'squared_error',
},
}
def __init__(self, task='regression', **config):
super().__init__(task, **config)
self.estimator_class = DecisionTreeRegressor
# 067 결정 트리 테스트
automl_dt = AutoML()
automl_dt.add_learner('dt_clf', DecisionTreeClassifierLearner)
automl_dt.fit(
X_clf_train, y_clf_train,
task="classification",
time_budget=30,
estimator_list=['dt_clf'],
verbose=1
)
print(f"\nDecisionTree 최적 설정: {automl_dt.best_config}")
앙상블 모델
AdaBoost
from sklearn.ensemble import AdaBoostClassifier, AdaBoostRegressor
class AdaBoostClassifierLearner(SKLearnEstimator):
"""AdaBoost 분류기 Custom Learner"""
@classmethod
def search_space(cls, data_size, task):
return {
'n_estimators': {
'domain': list(range(10, 300, 10)),
'init_value': 50,
'low_cost_init_value': 10,
},
'learning_rate': {
'domain': np.logspace(-3, 1, 50),
'init_value': 1.0,
},
'algorithm': {
'domain': ['SAMME', 'SAMME.R'],
'init_value': 'SAMME.R',
},
}
def __init__(self, task='classification', **config):
super().__init__(task, **config)
self.estimator_class = AdaBoostClassifier
# 067 AdaBoost 테스트
automl_ada = AutoML()
automl_ada.add_learner('adaboost', AdaBoostClassifierLearner)
automl_ada.fit(
X_clf_train, y_clf_train,
task="classification",
time_budget=60,
estimator_list=['adaboost'],
verbose=1
)
print(f"\nAdaBoost 최적 설정: {automl_ada.best_config}")
Bagging
from sklearn.ensemble import BaggingClassifier, BaggingRegressor
class BaggingClassifierLearner(SKLearnEstimator):
"""Bagging 분류기 Custom Learner"""
@classmethod
def search_space(cls, data_size, task):
return {
'n_estimators': {
'domain': list(range(10, 200, 10)),
'init_value': 50,
'low_cost_init_value': 10,
},
'max_samples': {
'domain': np.linspace(0.5, 1.0, 10),
'init_value': 1.0,
},
'max_features': {
'domain': np.linspace(0.5, 1.0, 10),
'init_value': 1.0,
},
'bootstrap': {
'domain': [True, False],
'init_value': True,
},
}
def __init__(self, task='classification', **config):
super().__init__(task, **config)
self.estimator_class = BaggingClassifier
print("Bagging Learner 정의 완료")
Gradient Boosting
from sklearn.ensemble import GradientBoostingClassifier, GradientBoostingRegressor
class GBClassifierLearner(SKLearnEstimator):
"""Gradient Boosting 분류기 Custom Learner"""
@classmethod
def search_space(cls, data_size, task):
return {
'n_estimators': {
'domain': list(range(50, 500, 25)),
'init_value': 100,
'low_cost_init_value': 50,
},
'learning_rate': {
'domain': np.logspace(-3, 0, 50),
'init_value': 0.1,
},
'max_depth': {
'domain': list(range(3, 15)),
'init_value': 5,
'low_cost_init_value': 3,
},
'min_samples_split': {
'domain': list(range(2, 20)),
'init_value': 2,
},
'subsample': {
'domain': np.linspace(0.6, 1.0, 10),
'init_value': 1.0,
},
}
def __init__(self, task='classification', **config):
super().__init__(task, **config)
self.estimator_class = GradientBoostingClassifier
class GBRegressorLearner(SKLearnEstimator):
"""Gradient Boosting 회귀 Custom Learner"""
@classmethod
def search_space(cls, data_size, task):
return {
'n_estimators': {
'domain': list(range(50, 500, 25)),
'init_value': 100,
'low_cost_init_value': 50,
},
'learning_rate': {
'domain': np.logspace(-3, 0, 50),
'init_value': 0.1,
},
'max_depth': {
'domain': list(range(3, 15)),
'init_value': 5,
},
'loss': {
'domain': ['squared_error', 'absolute_error', 'huber'],
'init_value': 'squared_error',
},
}
def __init__(self, task='regression', **config):
super().__init__(task, **config)
self.estimator_class = GradientBoostingRegressor
# 067 GB 테스트
automl_gb = AutoML()
automl_gb.add_learner('gb_clf', GBClassifierLearner)
automl_gb.fit(
X_clf_train, y_clf_train,
task="classification",
time_budget=60,
estimator_list=['gb_clf'],
verbose=1
)
print(f"\nGradientBoosting 최적 설정: {automl_gb.best_config}")
여러 Custom Learner 동시 사용
# 067 여러 Custom Learner 등록
automl_multi = AutoML()
automl_multi.add_learner('svc', SVCLearner)
automl_multi.add_learner('knn', KNNClassifierLearner)
automl_multi.add_learner('dt', DecisionTreeClassifierLearner)
automl_multi.add_learner('gb', GBClassifierLearner)
# 067 모든 커스텀 + 기본 모델 함께 탐색
automl_multi.fit(
X_clf_train, y_clf_train,
task="classification",
time_budget=120,
estimator_list=['lgbm', 'rf', 'svc', 'knn', 'dt', 'gb'],
metric='accuracy',
verbose=1
)
print(f"\n최적 모델: {automl_multi.best_estimator}")
print(f"최적 설정: {automl_multi.best_config}")
print(f"정확도: {1 - automl_multi.best_loss:.4f}")
# 067 테스트 평가
from sklearn.metrics import accuracy_score, classification_report
y_pred = automl_multi.predict(X_clf_test)
print(f"\n테스트 정확도: {accuracy_score(y_clf_test, y_pred):.4f}")
sklearn 모델 래핑 템플릿
def create_sklearn_learner(estimator_class, search_space_dict, task='classification'):
"""sklearn 모델을 Custom Learner로 래핑하는 팩토리 함수"""
class SklearnLearner(SKLearnEstimator):
@classmethod
def search_space(cls, data_size, task_type):
return search_space_dict
def __init__(self, task=task, **config):
super().__init__(task, **config)
self.estimator_class = estimator_class
return SklearnLearner
# 067 팩토리 함수 사용 예
from sklearn.linear_model import LogisticRegression
lr_space = {
'C': {
'domain': np.logspace(-4, 4, 100),
'init_value': 1.0,
},
'penalty': {
'domain': ['l1', 'l2'],
'init_value': 'l2',
},
'solver': {
'domain': ['liblinear', 'saga'],
'init_value': 'liblinear',
},
}
LogisticRegressionLearner = create_sklearn_learner(
LogisticRegression, lr_space, task='classification'
)
# 067 사용
automl_lr = AutoML()
automl_lr.add_learner('logreg', LogisticRegressionLearner)
automl_lr.fit(
X_clf_train, y_clf_train,
task="classification",
time_budget=30,
estimator_list=['logreg'],
verbose=1
)
print(f"\nLogisticRegression 최적 설정: {automl_lr.best_config}")
정리
| 모델 | 클래스 | 주요 하이퍼파라미터 |
|---|---|---|
| SVM | SVC/SVR | C, kernel, gamma |
| KNN | KNeighbors | n_neighbors, weights |
| 나이브 베이즈 | GaussianNB | var_smoothing |
| 결정 트리 | DecisionTree | max_depth, criterion |
| AdaBoost | AdaBoost | n_estimators, learning_rate |
| Gradient Boosting | GradientBoosting | n_estimators, learning_rate, max_depth |
- sklearn 모델을 SKLearnEstimator로 래핑
- search_space()에서 하이퍼파라미터 정의
- config2params()로 조건부 파라미터 처리
- add_learner()로 등록 후 estimator_list에 추가
다음 글 예고
다음 글에서는 Custom Learner - XGBoost 커스터마이징에 대해 알아보겠습니다. XGBoost의 세부 파라미터를 FLAML에서 튜닝하는 방법을 다룹니다.
FLAML AutoML 마스터 시리즈 #067