ハイパーパラメータ探索

MNISTを用いたハイパーパラメータ探索

ReNomには3つのハイパーパラメータ探索手法が用意されています.
1. Grid Search
2. Random Search
3. Bayes Search

Neural Networkは多くの決定しなければいけないパラメータがありますが,そのパラメータはモデルを使う人に依存してしまっています.

ユーザーによって学習率やバッチサイズ,ユニット数といったハイパーパラメータを自動的に決定することが出来ればとても便利です.しかし,3つの手法にはそれぞれ長所と短所が存在します.

  • Grid Search
    力まかせ探索の手法になります.この手法は考えられる組み合わせを全て試すので,多くのハイパーパラメータが存在するときは効率的な手法とは言えません.
  • Random Search
    Random Seachはハイパーパラメータの組み合わせをランダムに選びます.
  • Bayes Search
    Random Searchを所定の回数繰り返し行い,サンプルを得たあと,Bayes SearchはRandom Searchの結果に基づいてLossを最小にするであろう組み合わせを探し出します.

Required Libaries

  • matplotlib 2.0.2
  • numpy 1.12.1
  • scikit-learn 0.18.2
In [1]:
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import fetch_mldata
from sklearn.preprocessing import LabelBinarizer
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, accuracy_score
import renom as rm
from renom.optimizer import Sgd
from renom.utility.searcher import RandomSearcher,BayesSearcher,GridSearcher
from renom.cuda.cuda import set_cuda_active
set_cuda_active(False)

data_path = "."
mnist = fetch_mldata("MNIST original", data_home=data_path)

X = mnist.data.astype(np.float32)
y = mnist.target
X = (X - X.min()) / (X.max() - X.min())
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1)
print("X_train:{} y_train:{} X_test:{} y_test:{}".format(\
       X_train.shape, y_train.shape, X_test.shape, y_test.shape))
lb = LabelBinarizer().fit(y)
labels_train = lb.transform(y_train)
labels_test = lb.transform(y_test)
X_train:(63000, 784) y_train:(63000,) X_test:(7000, 784) y_test:(7000,)

ハイパーパラメータの設定

params はハイパーパラメータの候補となる値を持つ辞書になります.

In [2]:
params = {
    "unit1":[50,100,300,500],
    "unit2":[50,100,300,500],
    "batch_size":[64,128,512,1024],
    "epoch":[5,10,20,30],
    "learning_rate":[0.01,0.001,0.005]
}

手法の選択

他の手法を使用したい場合はコメントを削除してください。

In [3]:
# you can choose which one you will use
searcher = RandomSearcher(params)
#searcher = GridSearcher(params)
#searcher = BayesSearcher(params)

候補となるハイパーパラメータを選び出して評価を行います。

max_iter は繰り返しの回数です.

Searcherによって提案されたハイパーパラメータを使うためには以下のようにします.

rm.Dense(p["unit1"])

更に

for p in searcher.suggest(max_iter=10, random_iter=3)
はBayse Searchの設定のときのみ使います.
random_iter はガウス過程による回帰を行う前のRandom Searchの実行回数になります.
Searcherは以下の関数の引数を最小にする組み合わせを,最も効果的な組み合わせだと判断します.
今回は損失を最小にする組み合わせを最も良い組み合わせとして考えています.
searcher.set_result(loss)

もし,他の指標が使いたいならば,例えば適合率などであれば,使うことができます.

prediction = np.argmax(sequential(X_test), axis=1)
#loss = rm.softmax_cross_entropy(sequential(X_test), labels_test).as_ndarray()
#searcher.set_result(loss)
searcher.set_result(-acc)
In [4]:
for p in searcher.suggest():
    print("params:{}".format(p))

    sequential = rm.Sequential([
        rm.Dense(p["unit1"]),
        rm.Dense(p["unit2"]),
        rm.Dense(10)
    ])

    batch_size = p["batch_size"]
    epoch = p["epoch"]
    optimizer = Sgd(lr=p["learning_rate"])
    N = len(X_train)

    learning_curve = []
    test_learning_curve = []
    for i in range(epoch):
        perm = np.random.permutation(N)
        train_loss = 0
        test_loss = 0
        for j in range(N // batch_size):
            train_batch = X_train[perm[j*batch_size : (j+1)*batch_size]]
            response_batch = labels_train[perm[j*batch_size : (j+1)*batch_size]]
            with sequential.train():
                l = rm.softmax_cross_entropy(sequential(train_batch), response_batch)
            grad = l.grad()
            grad.update(optimizer)
            train_loss += l.as_ndarray()
        train_loss = train_loss / (N // batch_size)

        M = len(X_test)
        for j in range(M // batch_size):
            test_batch = X_test[j*batch_size : (j+1)*batch_size]
            test_label_batch = labels_test[j*batch_size : (j+1)*batch_size]
            prediction = sequential(test_batch)
            l = rm.softmax_cross_entropy(prediction, test_label_batch)
            test_loss += l.as_ndarray()
        test_loss = test_loss / (M // batch_size)

        learning_curve.append(train_loss)
        test_learning_curve.append(test_loss)

        if i ==0 or i==epoch-1:
            print("epoch %03d train_loss%f test_loss:%f"%(i, train_loss, test_loss))

    prediction = np.argmax(sequential(X_test), axis=1)
    loss = rm.softmax_cross_entropy(sequential(X_test), labels_test).as_ndarray()
    #acc = accuracy_score(prediction, y_test)
    searcher.set_result(loss)
    #searcher.set_result(-acc)
params:{'unit2': 300, 'learning_rate': 0.001, 'batch_size': 128, 'unit1': 50, 'epoch': 10}
epoch 000 train_loss1.926839 test_loss:1.577622
epoch 009 train_loss0.448565 test_loss:0.445713
params:{'unit2': 100, 'learning_rate': 0.01, 'batch_size': 64, 'unit1': 300, 'epoch': 20}
epoch 000 train_loss0.489347 test_loss:0.344245
epoch 019 train_loss0.255224 test_loss:0.278100
params:{'unit2': 300, 'learning_rate': 0.01, 'batch_size': 1024, 'unit1': 50, 'epoch': 10}
epoch 000 train_loss1.910609 test_loss:1.494663
epoch 009 train_loss0.419351 test_loss:0.416491
params:{'unit2': 50, 'learning_rate': 0.01, 'batch_size': 64, 'unit1': 500, 'epoch': 30}
epoch 000 train_loss0.488129 test_loss:0.343785
epoch 029 train_loss0.248693 test_loss:0.273255
params:{'unit2': 50, 'learning_rate': 0.01, 'batch_size': 64, 'unit1': 300, 'epoch': 30}
epoch 000 train_loss0.497717 test_loss:0.347689
epoch 029 train_loss0.248871 test_loss:0.278171
params:{'unit2': 50, 'learning_rate': 0.01, 'batch_size': 128, 'unit1': 500, 'epoch': 30}
epoch 000 train_loss0.600322 test_loss:0.390054
epoch 029 train_loss0.253202 test_loss:0.278795
params:{'unit2': 100, 'learning_rate': 0.01, 'batch_size': 64, 'unit1': 500, 'epoch': 30}
epoch 000 train_loss0.471274 test_loss:0.342661
epoch 029 train_loss0.248311 test_loss:0.278217
params:{'unit2': 50, 'learning_rate': 0.01, 'batch_size': 64, 'unit1': 100, 'epoch': 30}
epoch 000 train_loss0.539906 test_loss:0.356974
epoch 029 train_loss0.250322 test_loss:0.277252
params:{'unit2': 50, 'learning_rate': 0.01, 'batch_size': 64, 'unit1': 50, 'epoch': 30}
epoch 000 train_loss0.569248 test_loss:0.365174
epoch 029 train_loss0.250526 test_loss:0.279838
params:{'unit2': 50, 'learning_rate': 0.01, 'batch_size': 128, 'unit1': 50, 'epoch': 30}
epoch 000 train_loss0.760373 test_loss:0.436804
epoch 029 train_loss0.257276 test_loss:0.280940

評価

'num' という引数は上位のn個を表示するための引数になります.
もし,他の指標が使いたいならば,例えば適合率などであれば,使うことができます.
bests = searcher.best(num=3)
for i in range(len(bests)):
    #print("params:{} loss:{}".format(bests[i][0], bests[i][1]))
    print("params:{} acc:{}".format(bests[i][0], -bests[i][1]))
In [5]:
bests = searcher.best(num=3)
for i in range(len(bests)):
    print("params:{}  loss:{}".format(bests[i][0], bests[i][1]))
    #print("params:{} acc:{}".format(bests[i][0], -bests[i][1]))
params:{'unit2': 50, 'learning_rate': 0.01, 'batch_size': 64, 'unit1': 500, 'epoch': 30}  loss:0.27294719219207764
params:{'unit2': 50, 'learning_rate': 0.01, 'batch_size': 64, 'unit1': 100, 'epoch': 30}  loss:0.27697351574897766
params:{'unit2': 50, 'learning_rate': 0.01, 'batch_size': 128, 'unit1': 500, 'epoch': 30}  loss:0.2772584557533264