25
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ChainerとOptunaでハイパーパラメータ探索

Last updated at Posted at 2018-12-20

はじめに

今年8月頃からChainerMNをChainerに統合する開発が進められ、10月25日に公開されたChainer5.0から標準で含まれるようになりました。
直前までの予定でChainerMNの環境開発に関して記事を書く予定だったのですが、ばんくしさんの記事を発見したため、こちらを参考にしてください。

ということでタイトルにあるように、先日PFNの公式ブログで発表されたハイパーパラメータチューニングのライブラリ「Optuna」が公開されました。今回はこちらの実装方法の紹介を行います。

Optunaとは?

こちら公式ブログからの引用です。

Optuna はハイパーパラメータの最適化を自動化するためのソフトウェアフレームワークです。ハイパーパラメータの値に関する試行錯誤を自動的に行いながら、優れた性能を発揮するハイパーパラメータの値を自動的に発見します。現在は Python で利用できます。

ハイパーパラメータのチューニングは素早く済ませたいですよね。これまで人力のオペレーションに頼っていた部分をOptunaが補ってくれます。今年の夏のOpen Image Challenge 2018にもモデル構築に貢献していたみたいです。限られた試行回数中で準優勝へ導いたこのライブラリを使ってみようと思います。

Optunaを実装

今回作成したNotebookはこちらになります。今回は犬猫画像の分類を行います。そのためGPUを使えるように設定を行います。以下の図にしたがってバックグラウンドのハードウェアをGPUに変更します。
001.png

002.png

Chainerのバージョン確認

Optunaを使うためにはChainerのバージョンが4.0.0以上必要です。
まずはバージョンの確認を行います。現在Google ColaboratoryではデフォルトでChainer5.0.0が用意されています。すごいですね!!

import chainer
chainer.print_runtime_info()
>>>Platform: Linux-4.14.65+-x86_64-with-Ubuntu-18.04-bionic
Chainer: 5.0.0
NumPy: 1.14.6
CuPy:
  CuPy Version          : 5.0.0
  CUDA Root             : /usr/local/cuda
  CUDA Build Version    : 9020
  CUDA Driver Version   : 9020
  CUDA Runtime Version  : 9020
  cuDNN Build Version   : 7201
  cuDNN Version         : 7201
  NCCL Build Version    : 2213
iDeep: 2.0.0.post3

### Optunaのインストール
こちらは!pipでColabにインストールしましょう。

!pip install optuna
>>>Collecting optuna
...

データのアップロード

今回利用するデータをアップロードしておきましょう。あらかじめGoogle Driveに画像を保存しておき、ColabからMountする方法もありますが、今回はzip形式で圧縮した画像ファイルを直接アップロードします。

以下のコードを実行するとをタブが表示されるのでこちらからファイルをアップロードしましょう。

from google.colab import files
uploaded = files.upload()
003.png

アップロードが完了したらフォルダ内を確認しましょう。

!ls
>>> dog_cat.zip sample_data

続いてzipファイルを解凍します。

!unzip cat_dog.zip
>>>Archive:  dog_cat.zip
   creating: dog_cat/
...

解凍されたことを確認して、zipファイルは消去しておきましょう。

!ls
>>>dog_cat dog_cat.zip __MACOSX sample_data

!rm dog_cat.zip
!rm -rf __MACOSX

今回読み込んだ画像を確認します。

from PIL import Image
from glob import glob

beef_filepaths = glob('fish_beef/beef/*.jpg')
img = Image.open(beef_filepaths[0])

12.jpg

はい、お肉です。私の大好きなお肉と魚を分類します。

データセットの作成

ではデータセットの作成を行いましょう。またこのあとChainerを使う際に必要なモジュールもインポートしておきます。

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

import warnings
warnings.filterwarnings('ignore')
from PIL import Image

そして入力データxと教師データtを作成します。

x, t = [], []

beef_filepaths = glob('beef_fish/beef/*.jpg')
for beef_filepath in beef_filepaths:
    img = Image.open(beef_filepath)
    x.append(np.array(img))
    _t = np.array(0, 'i')
    t.append(_t)

fish_filepaths = glob('beef_fish/fish/*.jpg')
for fish_filepath in fish_filepaths:
    img = Image.open(fish_filepath)
    x.append(np.array(img))
    _t = np.array(1, 'f') 
    t.append(_t)

データの確認は忘れずに。

len(x), len(t)
>>>(500, 500)

そして、Numpyに変換しChainerのTupleDataset()に渡します。

x = np.array(x, 'f')
t = np.array(t, 'i')

dataset = chainer.datasets.TupleDataset(x, t)
n_train = int(len(dataset) * 0.7)

Optunaの準備

必要なモジュールの読み込み

必要なモジュールなどをあらかじめ設定しておきます。

import chainer
import chainer.functions as F
import chainer.links as L

from chainer.datasets import split_dataset_random


n_train = int(len(dataset) * 0.7) #訓練データ
n_train_examples = n_train
n_test_examples = len(dataset) - n_train
batchsize = 32
epoch = 10

モデルの構築

今回はChainerのSequentialモデルでニューラルネットワークの設計を行います。こちらはlayersというリストにappend()を使って層を足していくだけでモデルの設計ができる便利なモジュールです。KerasのSequentialモデルと似ています。

ハイパーパラメータはtrial.suggest_○○として範囲を指定しておくだけになります。例えばsuggest_int()は指定した範囲の整数を持ち、その中から1~3のいずれかを与えます。連続値はsuggest_○○uniform()となっています。詳細はこちらを参照してください。

※注意
Sequentialモデルでchainer.funtionsの呼び出しは関数名のみなので、
F.max_pooling_2dなどの引数(ksizeやstride)を指定する関数を利用する場合にはpythonのfunctools.partialを呼び出す必要があります。
functools.partialは関数の拡張、上書きに使われます。詳細は[こちら]をご覧ください。(https://docs.python.jp/3/library/functools.html#functools.partial)

from functools import partial

def create_model(trial):
    
    # 全結合層の数を保存
    n_fc_layers = trial.suggest_int('n_fc_layers', 1, 3)

    layers = []
    
    layers.append(L.Convolution2D(None, 64, 3, 1, 1))
    layers.append(partial(F.max_pooling_2d, ksize=3, stride=3)) # partial()を使ってksizeとstrideを指定
    
    for i in range(n_fc_layers):
        n_units = int(trial.suggest_loguniform('n_units_l{}'.format(i), 4, 128))
        layers.append(L.Linear(None, n_units))
        layers.append(F.relu)
    layers.append(L.Linear(None, 10))

    return chainer.Sequential(*layers)

最適化関数の定義

最適化関数をリスト形式で設定することで中からよしなに選択してくれます。またそれぞれのハイパーパラメータも指定した範囲の中からランダムに選択されます。

def create_optimizer(trial, model):
    # 最適化関数の選択
    optimizer_name = trial.suggest_categorical('optimizer', ['Adam', 'MomentumSGD'])
    if optimizer_name == 'Adam':
        adam_alpha = trial.suggest_loguniform('adam_alpha', 1e-5, 1e-1)
        optimizer = chainer.optimizers.Adam(alpha=adam_alpha)
    else:
        momentum_sgd_lr = trial.suggest_loguniform('momentum_sgd_lr', 1e-5, 1e-1)
        optimizer = chainer.optimizers.MomentumSGD(lr=momentum_sgd_lr)

    weight_decay = trial.suggest_loguniform('weight_decay', 1e-10, 1e-3)
    optimizer.setup(model)
    optimizer.add_hook(chainer.optimizer.WeightDecay(weight_decay))
    return optimizer

目的関数の定義

def objective(trial):
    # モデルのインスタンス化
    model = L.Classifier(create_model(trial))
    optimizer = create_optimizer(trial, model) # モデルとoptimizerを紐付ける

    # モデルをGPUに移動
    gpu_id = 0
    model.to_gpu(gpu_id)
    
    # Iteratorの設定
    rng = np.random.RandomState(0)
    train, test = split_dataset_random(dataset, n_train, seed=0)
    train = chainer.datasets.SubDataset(
        train, 0, n_train_examples, order=rng.permutation(len(train)))
    test = chainer.datasets.SubDataset(
        test, 0, n_test_examples, order=rng.permutation(len(test)))
    train_iter = chainer.iterators.SerialIterator(train, batchsize)
    test_iter = chainer.iterators.SerialIterator(test, batchsize, repeat=False, shuffle=False)

    # Trainerの設定
    updater = chainer.training.StandardUpdater(train_iter, optimizer, device=gpu_id)
    trainer = chainer.training.Trainer(updater, (epoch, 'epoch'))
    trainer.extend(chainer.training.extensions.Evaluator(test_iter, model, device=gpu_id))
    log_report_extension = chainer.training.extensions.LogReport(log_name=None)
    trainer.extend(chainer.training.extensions.PrintReport(
        ['epoch', 'main/loss', 'validation/main/loss',
         'main/accuracy', 'validation/main/accuracy', 'elapsed_time']))
    trainer.extend(log_report_extension)

    # 学習の実行
    trainer.run()

    # 学習結果の保存
    log_last = log_report_extension.log[-1]
    for key, value in log_last.items():
        trial.set_user_attr(key, value)

    # 最終的なバリデーションの値を返す
    val_err = 1.0 - log_report_extension.log[-1]['validation/main/accuracy']
    return val_err

最適なハイパーパラメータの探索

では、試行回数を指定してハイパーパラメータの探索を行いましょう。

import optuna

study = optuna.create_study()
study.optimize(objective, n_trials=100) 

結果の確認

では最後に結果を確認しましょう。studyに今回の全ての結果が保存されています。もっとも良いaccuracyのハイパーパラメータはstudy.best_trialに含まれています。バリデーションの結果が69%となったのは、全結合層が3層でノードの数は114->122->127で最適化関数はAdamとなりました。αとweight_decayは5.84e-048.87e-05となりました。これは人間の手で調整できそうにないですよね。

print('Number of finished trials: ', len(study.trials))

print('Best trial: ', )
trial = study.best_trial

print('Params: ')
for key, value in trial.params.items():
    print('{}:{}'.format(key, value))
    
print('User attrs: ')
for key, value in trial.user_attrs.items():
    print('{}:{}'.format(key, value))

>>>Number of finished trials:  100
Best trial: 
Params: 
adam_alpha:0.0005836163768560254
weight_decay:8.866268162741139e-05
n_units_l2:127.04057915769907
n_units_l1:122.78511630457464
optimizer:Adam
n_layers:3
n_units_l0:114.39882571222915
User attrs: 
validation/main/loss:1.116943597793579
elapsed_time:2.409521450000284
epoch:10
validation/main/accuracy:0.6920454502105713
main/loss:0.7022001147270203
main/accuracy:0.7698863744735718
iteration:110

最後に

簡単な形ですが、OptunaをGoogle Colaboratoryで実装しました。
こちらのライブラリはChainerだけではなく、scikit-learn、XGBoost、LightGBMなどの機械学習ライブラリやTensorFlowなどの深層学習フレームワークでも実装可能です。

また学習過程でlossが減少しなくなった場合に枝刈りを行ってくれるモジュールも用意されています。

是非みなさんも使ってみてはいかがでしょうか。

フィードバック、コメントなどいただけると嬉しいです。

25
26
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
26

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?