セーターの備忘録

大学編入やIT関連の内容を記していく予定です

ハイパーパラメータ最適化ツールのHyperactiveを試す

この記事では,ハイパーパラメータ最適化ツールのHyperactiveについてまとめています.

ハイパーパラメータ最適化について

機械学習では人が手作業で調整する必要があるハイパーパラメータが存在します.ハイパーパラメータを調整することは高い精度を求める上で重要となりますが,手作業では限界があるため,多くの自動化手法が提案されています.
提案されていたり利用されている自動化手法やツールとして,以下のようなものがあります.

  • Grid Search
  • Optuna (TPE)
  • Meta-Heuristics Algorithm (e.g. PSO, ES)

Hyperactiveは,これまであまり提案されてこなかったMeta-Heuristics Algorithmやその他の手法を用いて最適化を行うことができます.

Hyperactive について

概要

Hyperactiveは機械学習や深層学習のハイパーパラメータを自動で最適化するAPIです.このAPIを使うことで,様々な機械学習手法のハイパーパラメータに対して進化戦略 (ES) や粒子群最適化 (PSO) などの手法を用いて最適化を行うことができます.
この記事を書いている時点 (2019/08/21) でver. 0.4.1.5であり,現在も開発が行われています.

github.com

使うことの出来る機械学習パッケージと最適化手法

Hyperactiveでは,以下の機械学習パッケージに対応しています.

  • scikit-learn
  • LightGBM
  • CatBoost
  • Keras

また,Hyperactiveは以下の通り多くの最適化手法を使うことができます.

  • Hill Climbing
  • Stochastic Hill Climbing
  • Tabu Search
  • Random Search
  • Random Restart Hill Climbing
  • Random Annealing
  • Simulated Annealing
  • Stochastic Tunneling
  • Parallel Tempering
  • Particle Swarm Optimization
  • Evolution Strategy
  • Bayesian Optimization

インストール

READMEにある通り,次のようにインストールします.

pip install hyperactive

Random ForestのハイパーパラメータをPSOで最適化

今回は例としてRandom Forestのハイパーパラメータ最適化を行いました.

使うデータセット

今回はscikit-learnにあるIrisデータセットを用いました.Irisは4次元の特徴量,3種類のラベルを持つアヤメのデータセットです.

最適化なしと最適化を行った上での分類結果

まずRandom Forestを用いてハイパーパラメータ最適化を行わずに結果を求め,その後HyperactiveのPSOを用いて最適化を行った上で結果を求めました.

最適化なし

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
import time

iris_data = load_iris()
X = iris_data.data
y = iris_data.target

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)

t1 = time.time()
forest = RandomForestClassifier()
forest.fit(X_train, y_train)
t2 = time.time()

print('Train score: {}'.format(forest.score(X_train, y_train)))
print('Test score: {}'.format(forest.score(X_test, y_test)))
print("time: {}".format(t2-t1))

結果

Train score: 0.9809523809523809
Test score: 0.9111111111111111
time: 0.009917736053466797

PSOで最適化

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from hyperactive import SimulatedAnnealingOptimizer
from hyperactive import ParticleSwarmOptimizer
import time

iris_data = load_iris()
X = iris_data.data
y = iris_data.target

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)

# this defines the model and hyperparameter search space
search_config = {
    "sklearn.ensemble.RandomForestClassifier": {
        "n_estimators": range(10, 100, 10),
        "max_depth": [3, 4, 5, 6],
        "criterion": ["gini", "entropy"],
        "min_samples_split": range(2, 21),
        "min_samples_leaf": range(2, 21),
    }
}

# start point
start_point = {
    "sklearn.ensemble.RandomForestClassifier.0": {
        "n_estimators": [30],
        "max_depth": [6],
        "criterion": ["entropy"],
        "min_samples_split": [12],
        "min_samples_leaf": [16],
    }
}

#Optimizer = SimulatedAnnealingOptimizer(search_config, n_iter=100, n_jobs=4)
Optimizer = ParticleSwarmOptimizer(search_config, 
                                    n_iter=10,  # number of iterations to perform
                                    metric="accuracy", 
                                    n_jobs=1, 
                                    cv=3, 
                                    verbosity=1, 
                                    random_state=None, 
                                    warm_start=start_point,  # Hyperparameter configuration to start from
                                    memory=True,  # Stores explored evaluations in a dictionary to save computing time
                                    scatter_init=False,  # Chooses better initial position by training on multiple random positions with smaller training dataset 
                                    n_part=10,  # number of particles
                                    w=0.5,  # interia factor
                                    c_k=0.5,  # cognitive factor
                                    c_s=0.9)  # social factor

# search best hyperparameter for given data
t1 = time.time()
Optimizer.fit(X_train, y_train)
t2 = time.time()
print("time: {}".format(t2-t1))

# predict from test data
prediction = Optimizer.predict(X_test)

# calculate accuracy score
score = Optimizer.score(X_test, y_test)

print("test accracy: {}".format(score))

結果と最適化されたパラメータ

start_point = {'sklearn.ensemble.RandomForestClassifier.0': {'n_estimators': [70], 'max_depth': [4], 'criterion': ['gini'], 'min_samples_split': [16], 'min_samples_leaf': [13]}}
train score: 0.9721780604133546
test score: 0.9555555555555556
time: 18.719361782073975

Hyperactiveにも各手法ごとハイパーパラメータが存在しますが,今回は初期値のままで行いました.最適化されたパラメータはstart_pointに格納されるようです.

CNNのハイパーパラメータをRandom Searchで最適化

次に,深層学習の例としてCNNのハイパーパラメータ最適化を行いました.今回はGPUを積んでいないノートPCで実験を行っており,CNNはそれ自体の計算時間が長いため,今回は最適化手法の中で比較的計算コストの低いRandom Searchを用いています.また,いずれもepoch=5,batch size=500とし,損失関数にはcategorical clossentropyを用いています.

使うデータセット

ここではKerasにあるMNISTデータセットを用いました.MNISTは784 (28×28) 次元の特徴量,10種類のラベルを持つ手書き数字の画像データセットです.

最適化なしと最適化を行った上での分類結果

最適化なし

import time
import numpy as np
from keras.datasets import mnist
from keras.utils import to_categorical
from keras.layers import Input, Dense, Activation, BatchNormalization, Flatten, Conv2D, MaxPool2D, Dropout
from keras.models import Model
from keras.models import Sequential

(X_train, y_train), (X_test, y_test) = mnist.load_data()

X_train = X_train.reshape(60000, 28, 28, 1)
X_test = X_test.reshape(10000, 28, 28, 1)

y_train = to_categorical(y_train)
y_test = to_categorical(y_test)


model = Sequential()
model.add(Conv2D(filters=32, kernel_size=(3,3), 
                activation='relu', input_shape=(28,28,1)))
model.add(MaxPool2D(pool_size=(2,2)))
model.add(Conv2D(filters=(32), kernel_size=(3,3), 
                activation='relu'))
model.add(MaxPool2D(pool_size=(2,2)))
model.add(Flatten())
model.add(Dense(30, activation='relu'))
model.add(Dropout(0.4))
model.add(Dense(10, activation='softmax'))

model.compile(optimizer='sgd',
                loss='categorical_crossentropy',
                metrics=['accuracy'])

t1 = time.time()
history = model.fit(X_train, y_train, 
                    epochs=5, 
                    batch_size=500, 
                    verbose=1,
                    validation_data=(X_test, y_test))
t2 = time.time()
print("time: {}".format(t2-t1))

score = model.evaluate(X_test, y_test, batch_size=32, verbose=0)
print('validation loss:{0[0]}\nvalidation accuracy:{0[1]}'.format(score))

結果

train score: 0.2430
test score: 0.3045
time: 146.26237177848816

Random Searchで最適化を行った結果

import time
import numpy as np
from keras.datasets import mnist
from keras.utils import to_categorical

from hyperactive import RandomSearchOptimizer, ParticleSwarmOptimizer

(X_train, y_train), (X_test, y_test) = mnist.load_data()

X_train = X_train.reshape(60000, 28, 28, 1)
X_test = X_test.reshape(10000, 28, 28, 1)

y_train = to_categorical(y_train)
y_test = to_categorical(y_test)


# this defines the structure of the model and print("time: {}".format(t2-t1))the search space in each layer
search_config = {
    "keras.compile.0": {"loss": ["categorical_crossentropy"], "optimizer": ["SGD"]},
    "keras.fit.0": {"epochs": [5], "batch_size": [500], "verbose": [2]},
    "keras.layers.Conv2D.1": {
        "filters": [32, 64],
        "kernel_size": [(3, 3)],
        "activation": ["relu"],
        "input_shape": [(28, 28, 1)],
    },
    "keras.layers.MaxPooling2D.2": {"pool_size": [(2, 2)]},
    "keras.layers.Conv2D.3": {
        "filters": [16, 32],
        "kernel_size": [(3, 3)],
        "activation": ["relu"],
    },
    "keras.layers.MaxPooling2D.4": {"pool_size": [(2, 2)]},
    "keras.layers.Flatten.5": {},
    "keras.layers.Dense.6": {"units": [30], "activation": ["relu"]},
    "keras.layers.Dropout.7": {"rate": list(np.arange(0.4, 0.8, 0.2))},
    "keras.layers.Dense.8": {"units": [10], "activation": ["softmax"]},
}

Optimizer = RandomSearchOptimizer(search_config, n_iter=10)

# search best hyperparameter for given data
t1 = time.time()
Optimizer.fit(X_train, y_train)
t2 = time.time()

print("time: {}".format(t2-t1))

# predict from test data
prediction = Optimizer.predict(X_test)

# calculate accuracy score
score = Optimizer.score(X_test, y_test)

print("test accracy: {}".format(score))

結果と最適化されたパラメータ

start_point = {'keras.compile.0': {'loss': ['categorical_crossentropy'], 'optimizer': ['SGD']}, 'keras.fit.0': {'epochs': [5], 'batch_size': [500], 'verbose': [2]}, 'keras.layers.Conv2D.1': {'filters': [32], 'kernel_size': [(3, 3)], 'activation': ['relu'], 'input_shape': [(28, 28, 1)]}, 'keras.layers.MaxPooling2D.2': {'pool_size': [(2, 2)]}, 'keras.layers.Conv2D.3': {'filters': [16], 'kernel_size': [(3, 3)], 'activation': ['relu']}, 'keras.layers.MaxPooling2D.4': {'pool_size': [(2, 2)]}, 'keras.layers.Flatten.5': {}, 'keras.layers.Dense.6': {'units': [30], 'activation': ['relu']}, 'keras.layers.Dropout.7': {'rate': [0.4]}, 'keras.layers.Dense.8': {'units': [10], 'activation': ['softmax']}}
train score: 0.93845
test score: 0.9614
time: 1330.1183042526245

たった5epochですが,最適化を行った結果は最適化なしの場合よりかなり良い結果を得ることができていることが分かります.

終わりに

最近は最適化ツールとしてOptunaが人気ですが,様々な最適化手法を試すことのできるツールとして非常に興味深いツールだと思います.
一般にハイパーパラメータ最適化を行うにあたってはOptunaでも使われているTPEが優秀ですが,Meta-Heuristics Algorithmの深層学習のハイパーパラメータ最適化への適用はまだあまり研究が行われていない分野であり,研究を行いやすくするツールとしても役に立つのではないかと思います.