Hyperdashの使い方

# きっかけ
以前、[Google Colabの使い方まとめ](https://qiita.com/shoji9x9/items/0ff0f6f603df18d631ab)にHyperdashを使っていると書いたのですが、最近また使う機会があったので使い方をまとめておきます。

# Hyperdashとは？
スマートフォンから学習状況を確認できるツールです。lossやaccuracyのグラフやログの内容をリアルタイムに確認できます。詳しくは[公式](https://hyperdash.io/)をご覧下さい。

# Hyperdashのいいところ
* 外出先でも学習状況を確認できる！
  * 自分は朝一にGoogle Colabで学習を開始し、そのまま出社、仕事の休憩中に学習状況を確認して次はどのハイパーパラメーターを調整しようかなぁなどと考えています
* Google Colabとの相性がいい！
  * Google Colabには[こちら](https://qiita.com/shoji9x9/items/0ff0f6f603df18d631ab)に書いた90分ルールがあるのですが、Hyperdashを使っているときにはなぜかインスタンスが落ちたことがありません。理由はよくわかっていないですが、おそらくGoogle Colab上にインストールしたHyperdashのエージェントがHyperdashサーバーに定期的にデータを送っているはずで、そのおかげだと思っています
  * Google Colabでログを多く吐き出すと（Kerasを使って`model.fit(verbose=1)`などとすると）、`Buffered data was truncated after reaching the output size limit.`というメッセージが出力されます。学習自体は継続しますが、それ以降ログを確認することができません。また、Google Colabの場合、12時間経つとモデルの重みも消えてしまいますので、「学習はしたが結果がわからない」ということになりかねません（学習途中のモデルを保存する方法はまた記事にする予定です）。しかし、Hyperdashを使えばログはおそらくHyperdashサーバー上に送られていますので、このメッセージが出力されることもなく全てのログの確認が可能です
* 学習結果の履歴が残る！
  * ハイパーパラメーターなどを変更した結果、精度がどう変わったのかをメモしておくのは面倒なのですが、Hyperdashを使えば学習結果の履歴が残りますので、自分でメモを取る必要はありません

# Hyperdashの使い方
## インストールとサインアップ
下記コードを実行すると、認証用のリンクが表示されますので、そこに書かれたアクセストークンをコピペすればサインアップできます。

```Python
!pip install hyperdash
from hyperdash import monitor_cell
!hyperdash signup --github
```

## Google Colabのセルの内容を記録する方法
以下のように書くことでxxxという名前で結果が記録されます。ただし、この方法では`Buffered data was truncated after reaching the output size limit.`の問題は解決できません。

```Python
%%monitor_cell "xxx"
```

## グラフ（Metrics）とログ（Logs）を記録する方法
そこで以下のようなKerasのコールバックを作ります。これを利用することによりGoogle Colabのセルには何も出力せずにHyperdashにデータを残せるようになります。ポイントは"val_acc", "acc"ではなく、"val_accuracy", "accuracy"と書くところです。前者ではうまくデータが取得できませんでした。

```Python
from tensorflow.keras.callbacks import Callback
from hyperdash import Experiment

class Hyperdash(Callback):
    def __init__(self, entries, exp):
        super(Hyperdash, self).__init__()
        self.entries = entries
        self.exp = exp

    def on_epoch_end(self, epoch, logs=None):
        for entry in self.entries:
            log = logs.get(entry)            
            if log is not None:
                self.exp.metric(entry, log)
                
exp = Experiment("xxx")
hd_callback = Hyperdash(["val_loss", "loss", "val_accuracy", "accuracy"], exp)
```

コールバックは以下のように利用します。`verbose=0`としていますので、`Buffered data was truncated after reaching the output size limit.`は発生しません。また、`exp.end()`を忘れずに書くようにして下さい。これが書かれていない場合、学習が完了とならず、次の学習のログに以前の学習のログが混ざることがありました。

```Python
history = model.fit(datagen.flow(train_x, train_y, batch_size=batch_size), 
                    epochs=num_epoch, 
                    steps_per_epoch=number_train//batch_size, 
                    validation_data=datagen_for_test.flow(test_x, test_y, batch_size=batch_size), 
                    validation_steps=number_test//batch_size,
                    verbose=0,
                    callbacks=[hd_callback])
exp.end()
```

## ハイパーパラメーター（Parameters）を記録する方法
`exp.param`を利用しハイパーパラメーターを記録します。以下のコードはKerasのmodel.summary()の中に含まれるTotal paramsを記録する例になっています。もちろんその他のバッチサイズ、エポック数、オプティマイザーの種類などを記録することもできます。

```Python
with StringIO() as buf:
    model.summary(print_fn=lambda x: buf.write(x + "\n"))
    summary = buf.getvalue()
re1 = re.match(r"(.|\s)*Total params: ", summary)
re2 = re.match(r"(.|\s)*Total params: [\d|,]+", summary)
total_params = summary[re1.end():re2.end()]
exp.param("Total params", total_params)
```