経緯
cross validationをしたときに、作ったモデルがもったいないので、「作ったモデルの結果の平均を最終結果にする」という似非Baggingを時々やっていたものの、ちゃんとBootstrapサンプリングした場合に比べて性能が悪かったりするんだろうか、と不安に思っていたので、ちゃんと調べてみることに。
お題
手ごろな大きさと難易度ということで、MNIST。オーバーフィッティングしやすいように訓練データを1万件にして残りを検証データに。
弱学習器はLRとRFとMLP。
CVとBaggingで、弱学習器の数は5, 10, 15。(Baggingとしては少な目?)
結果
結構ばらついたので、seedを変えつつ3回やってみました。
val_acc(1回目) | val_acc(2回目) | val_acc(3回目) | val_acc(平均) | |
---|---|---|---|---|
lr-base | 85.2 | 85.4 | 85.2 | 85.3 |
lr-cv5 | 87.5 | 86.8 | 87.3 | 87.2 |
lr-cv10 | 87.1 | 86.9 | 87.2 | 87.1 |
lr-cv15 | 86.8 | 86.6 | 86.9 | 86.8 |
lr-bag5 | 87.7 | 87.2 | 87.5 | 87.5 |
lr-bag10 | 87.9 | 87.6 | 88.3 | 87.9 |
lr-bag15 | 88.0 | 88.1 | 88.5 | 88.2 |
val_acc(1回目) | val_acc(2回目) | val_acc(3回目) | val_acc(平均) | |
---|---|---|---|---|
rf-base | 95.0 | 94.9 | 94.9 | 94.9 |
rf-cv5 | 95.1 | 94.9 | 94.9 | 95.0 |
rf-cv10 | 95.3 | 95.1 | 95.1 | 95.2 |
rf-cv15 | 95.4 | 95.2 | 95.2 | 95.2 |
rf-bag5 | 94.7 | 94.6 | 94.6 | 94.6 |
rf-bag10 | 94.8 | 94.6 | 94.7 | 94.7 |
rf-bag15 | 94.9 | 94.7 | 94.7 | 94.7 |
val_acc(1回目) | val_acc(2回目) | val_acc(3回目) | val_acc(平均) | |
---|---|---|---|---|
nn-base | 93.9 | 93.2 | 93.6 | 93.6 |
nn-cv5 | 95.3 | 95.5 | 95.2 | 95.3 |
nn-cv10 | 95.8 | 95.9 | 95.7 | 95.8 |
nn-cv15 | 96.0 | 96.1 | 96.0 | 96.0 |
nn-bag5 | 95.1 | 95.0 | 95.1 | 95.1 |
nn-bag10 | 95.3 | 95.5 | 95.3 | 95.4 |
nn-bag15 | 95.4 | 95.6 | 95.4 | 95.5 |
結論
- LRはBaggingの方が良さそう?
- RFを更に(少数で)Baggingするのは逆効果っぽい?
- MLPはCVの方が良さそう?
だいぶ僅差なので細かいところはよく分からないけど、とりあえず「CVの結果の平均」はそう悪くなさそう。
ソースコード
"""
アンサンブルのお試しコード
"""
import numpy as np
import sklearn.base
import sklearn.ensemble
import sklearn.metrics
import sklearn.model_selection
import sklearn.linear_model
import sklearn.neural_network
import sklearn.datasets
class CVAggregatingClassifier:
def __init__(self, base_estimator, n_fold):
self.base_estimator = base_estimator
self.n_fold = n_fold
self.estimators_ = []
self.cv_score_ = 0
def fit(self, X, y):
self.estimators_ = []
score_list = []
count_list = []
skf = sklearn.model_selection.StratifiedKFold(n_splits=self.n_fold, shuffle=True)
for train, test in skf.split(X, y):
estimator = sklearn.base.clone(self.base_estimator)
estimator.fit(X[train], y[train])
pred = estimator.predict(X[test])
score = sklearn.metrics.accuracy_score(y[test], pred)
score_list.append(score)
count_list.append(len(y[test]))
self.estimators_.append(estimator)
self.cv_score_ = np.average(score_list, weights=count_list)
def predict(self, X):
return np.argmax(self.predict_proba(X), axis=-1)
def predict_proba(self, X):
return np.mean([e.predict_proba(X) for e in self.estimators_], axis=0)
if __name__ == '__main__':
np.random.seed(3456)
mnist = sklearn.datasets.fetch_mldata('MNIST original')
X, y = mnist.data, mnist.target
X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, test_size=60000.0 / len(y))
print('train size={} test size={}'.format(len(X_train), len(X_test)))
factories = [
('lr', lambda: sklearn.linear_model.LogisticRegression(n_jobs=-1)),
('rf', lambda: sklearn.ensemble.RandomForestClassifier(n_estimators=100, n_jobs=-1)),
('nn', lambda: sklearn.neural_network.MLPClassifier(hidden_layer_sizes=(1000, 1000))),
]
for base_name, factory in factories:
estimators = [
('base', factory()),
('cv5', CVAggregatingClassifier(factory(), n_fold=5)),
('cv10', CVAggregatingClassifier(factory(), n_fold=10)),
('cv15', CVAggregatingClassifier(factory(), n_fold=15)),
('bag5', sklearn.ensemble.BaggingClassifier(factory(), n_estimators=5, oob_score=True)),
('bag10', sklearn.ensemble.BaggingClassifier(factory(), n_estimators=10, oob_score=True)),
('bag15', sklearn.ensemble.BaggingClassifier(factory(), n_estimators=15, oob_score=True)),
]
for name, estimator in estimators:
estimator.fit(X_train, y_train)
y_pred = estimator.predict(X_test)
acc = sklearn.metrics.accuracy_score(y_test, y_pred)
if hasattr(estimator, 'oob_score_'):
print('{}-{:5s}: val_acc={:.2f} oob_score={:.2f}'.format(base_name, name, acc * 100, estimator.oob_score_ * 100))
elif hasattr(estimator, 'cv_score_'):
print('{}-{:5s}: val_acc={:.2f} cv_score={:.2f}'.format(base_name, name, acc * 100, estimator.cv_score_ * 100))
else:
print('{}-{:5s}: val_acc={:.2f}'.format(base_name, name, acc * 100))