クラウド × 実験管理 〜Azure Machine Learning Service〜

# 概要
みなさんが機械学習を用いてデータ分析を行う時，一番面倒なことといえば何でしょうか？おそらく多くの人が上げることは__実験管理__じゃないでしょうか。

学習データを用意して，モデルのパラメータを決めて，学習を行って，バリデーションデータに適用して，テストデータで予測を行って，またパラメータを作成して...と言う流れの中で，それぞれ__パラメータやモデル毎の結果をまとめるのはすごく面倒__です。

実験管理に使えるツールといえば，[Comet ML](https://www.comet.ml)や[Tensorboard](https://www.tensorflow.org/guide/summaries_and_tensorboard)が有名ですが，クラウド環境で学習することの多い昨今の現状と，膨大な結果ファイルが絡むことを考えると，出来れば[S3](https://aws.amazon.com/jp/s3/)や[Blob](https://docs.microsoft.com/ja-jp/azure/storage/blobs/storage-blobs-introduction)といったクラウドストレージと連携させたいところです。

今回紹介するのは，Azureの機械学習サービスの一つである[Azure Machine Learning Service](https://docs.microsoft.com/ja-jp/azure/machine-learning/service/overview-what-is-azure-ml)です。DLを含む機械学習といえばGCPとAWSのイメージでしたが，__このサービスがあるだけでAzureを選択肢に入れる__のもアリだと個人的に思いました。筆者は実際にKaggleなどのコンペで活用してます。

注：筆者はクラウドサービスの専門家ではないので，AWSやGCPについて詳しくないです。実験管理ツールががあればぜひ教えてください！

# Azure Machine Learning Serviceとは？
![ダウンロード.jpeg](https://qiita-image-store.s3.amazonaws.com/0/185518/b4cef1f2-e76d-425a-0171-5a99de212749.jpeg)


簡単にいうと，Azure上で機械学習を簡易に行うためのサービス，コンポーネントの総称です。公式の概要は[こちら](https://docs.microsoft.com/ja-jp/azure/machine-learning/service/overview-what-is-azure-ml)。

DBによるデータの保管や，GPUインスタンスやJupyter notebookインスタンスによるデータ分析， [Azure Container Instances](https://docs.microsoft.com/ja-jp/azure/machine-learning/service/how-to-deploy-to-aci)や[Azure Kubernetes Service](https://docs.microsoft.com/ja-jp/azure/machine-learning/service/how-to-deploy-to-aks)への機械学習モデルのデプロイを一続きに行うために様々なコンポーネントが用意されています。

デプロイや学習インスタンスも興味深いのですが，今回はその中の[Azure Machine Learning Service Workspace](https://docs.microsoft.com/ja-jp/azure/machine-learning/service/)（以下，ML Workspace）に絞って，さらにその中の__実験管理__を紹介させていただきます。

# どんなことができるの？
実際の画面だとこんな感じになります。

* Experimentsの画面
    * 学習の実行であるRunのサマリーが保存されます。
    * 実行Statusやハイパーパラメータ，結果の情報を一覧できます。
<img width="1045" alt="スクリーンショット 2019-02-10 19.10.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/185518/bfe0d79d-fd48-c4d3-876b-5ad4ba086c9e.png">

* Runの画面
    * Experiment画面のRun番号のリンクを踏むことでいけます。
    * 実行した学習のLossの変化やハイパーパラメータの詳細が保存されます。
<img width="873" alt="スクリーンショット 2019-02-10 19.03.36.png" src="https://qiita-image-store.s3.amazonaws.com/0/185518/a270ded4-0862-3ee0-6fa7-e7d194c498d4.png">

ML Workspaceの概念としては以下の二つです。

1. 毎回の学習の実行結果を示す__Run__
2. そのRunを管理する__Experiments__

Experimentを呼び出す度にRunが生成されるので，Experimentsの中にRunが沢山あるという階層構造となっています。具体的にはpythonコード上で__同じExperiments名からRunを呼び出してロギングする度にそのExperimentsのRunが増加__します。

Runでは__学習に利用したログやファイルを基本何でも記録することができます。__例えば学習ロスやバリデーションロスなどの変化，設定したハイパーパラメータ，学習済みモデルや推論結果のCSVなどを記録できます。

**(2019/4/25)追記**
上記に加えて，なんと**実行ディレクトリ配下にあるファイルを全てsnapshotとして自動でAzure上にアップロードする**ようになりました。これにより実行時の設定やコードの状態も全て保存されるため，再現性という点では非常に優れたものとなりました。

具体的には後に示す`run`を生成する`run = exp.start_logging()`を実行すると，自動的にsnapshotがアップロードされます。

# ML Workspaceの始め方
ML Workspaceの画面が伝わったところで，実際に初めてみましょう。

といっても，Azureのチュートリアルはかなり詳しく書かれているので，Azure PortalからML Workspaceを作成して，Azure notebookから使うまでところは[こちらのチュートリアル](https://docs.microsoft.com/ja-jp/azure/machine-learning/service/quickstart-get-started)と[Pythonのチュートリアル](https://docs.microsoft.com/ja-jp/azure/machine-learning/service/quickstart-create-workspace-with-python)に譲ります。

もちろんチュートリアルではsklearnの例しかなく，味気ないので，今回はローカルPC上に立てたJupyter labでkerasでMNISTを回した時のコードを紹介します。このコードでは以下をML workspaceに記録/保存します。

1. 学習ロスとバリデーションロスのグラフ
2. 探索パラメータ
2. 学習済みモデル

コード自体は全然難しくないです！基本的には結果を`print()`するくらいの気持ちで書けてしまいます。

## 下準備

### Azureポータル上
上記のチュートリアルの通りにとりあえずML Workspaceを立ち上げておきましょう。それ以外は特にいりません。

<!--mlui.png-->

### ローカルPC上
とりあえずローカルPCのコンソールからazure-sdkを入れましょう。ついでにJupyter labも立ち上げ。

```shell
$ pip install azureml-sdk[notebooks]

$ jupyter lab
```

## コード
以下のコードはJupyter lab上のセルの上で実行していきます。

### クラウド環境との接続
```python3
import azureml
```

ポータル上のML workspaceとローカルPCの接続はjsonの設定ファイルからなります。具体的には`./aml_config/config.json`に下記の場所に書いてある赤い部分の情報を書くことになります。

<img width="1026" alt="mlui.png" src="https://qiita-image-store.s3.amazonaws.com/0/185518/e96769d8-9f9b-5fa7-38ea-e6211e3998b6.png">



```python
! mkdir ./aml_config # ディレクトリの作成
```

```python
%%writefile ./aml_config/config.json
# 赤い部分の情報を書く
{
    "subscription_id": "XXXXX-XXXXXX-XXXXX-XXXXX",
    "resource_group": "<リソースグループ>",
    "workspace_name": "<ワークスペースの名前>"
}
# ファイルへの書き込み
```

```python
from azureml.core import Workspace
ws = Workspace.from_config() # Azureとの接続
```

ここでAzureの認証がブラウザで開きますので，自分のアカウントを入れて認証しましょう。それで接続は終わりです。

## 実行結果のロギング（お試し）
実際に何かの計算の実行結果をML workspace上に上げて見ましょう。今回はチュートリアルにもあるフィボナッチ数を投げて見ます。

```python
from azureml.core import Experiment

# Experimentの作成
# この時点でworkspace上のExperimentsにmyexpの項目ができる
exp = Experiment(workspace=ws, name='myexp')

# Runを作成，実行
# この時点でworkspace上のExperiments->myexp内にrun:1の項目ができる
# 追記: さらに実行ディレクトリ配下の全てのファイルがAzure上にアップロードされます。
run = exp.start_logging()

# Runにログを上げてみる
# これによりmyexp->run:1内のTracked Metricsに'my magic number': 42が付与される
run.log('my magic number', 42)

# listをあげる (Fibonacci numbers)
# グラフとなって表示される
run.log_list('my list', [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]) 

# 同じkeyのログを複数回上げてもグラフとなって表示される
run.log('next magic number', 42)
run.log('next magic number', 43)

# finish the run
# statusがcompleteとなる
run.complete()
```

これの結果は以下のようになります。Azure PortalよりAzure ML workspaceからExperiment -> myexp -> 最新のRun番号とリンクを辿って見てください。

<img width="771" alt="スクリーンショット 2019-02-10 18.07.52.png" src="https://qiita-image-store.s3.amazonaws.com/0/185518/31fdd57c-10b1-d3b9-8284-fd2292b7145c.png">

見ての通り，リストや同じkey名で複数回上げたものはそのままグラフ化され，一度だけ上げたものはTracked Metricに格納されました。

これを見るだけで，「お，ハイパーパラメータをTrack Metricとして上げてLossはグラフとしてあげれば良さそう」というのが浮かんできますよね。

ちなみにこのコードを走らせる度にRun番号が増えて，それぞれ結果が表示されます。

## 実行結果のロギングとついでにモデルのアップロード（MNIST）
kerasでMNISTを学習したLossの結果を上げつつ，ついでにモデルをアップロードしたいと思います。

```python
from keras.datasets import mnist
import keras
from keras.callbacks import ModelCheckpoint

# データ準備
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train = x_train.reshape(60000, 784) 
x_test = x_test.reshape(10000, 784)
x_train = x_train.astype('float32')   
x_test = x_test.astype('float32')
x_train /= 255                        
x_test /= 255

# ラベルをバイナリ化
num_classes = 10
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)

# ハイパーパラメータ定義
batch_size = 128
epochs = 10
lr = 0.01
optname = 'adam'

# モデル定義
model = Sequential()
model.add(Dense(128, activation='relu', input_shape=(784,)))
model.add(Dense(64, activation='relu'))
model.add(Dense(10, activation='softmax'))

# Optimizerを取り出す関数
def choice_opt(opt, lr):
    if opt == 'sgd':
        return keras.optimizers.SGD(lr=lr)
    if opt == 'adam':
        return keras.optimizers.Adam(lr=lr)
    else:
        raise ValueError()

# コンパイル
opt = choice_opt(optname, lr)
model.compile(optimizer=opt, loss='categorical_crossentropy', metrics=['acc'])

# 学習の実行
# 最も良いモデルを`./best_model.h5`として保存
history = model.fit(
    x_train, y_train, validation_data=(x_test, y_test), epochs=epochs, batch_size=batch_size,
    callbacks=[ModelCheckpoint('./best_model.h5', monitor='val_loss', save_best_only=True)])
```

ここまでは普通の実行です。ここからML Workspace上に記録して行きます。

```python
# Experimentsの定義
exp = Experiment(workspace=ws, name='my_mnistexp')

# Runの作成
run = exp.start_logging()

# historyの中身=Lossのリストを記録
for k, v in history.history.items():
    run.log_list(k, v)

# ハイパーパラメータの記録
run.log('epochs', epochs)
run.log('batch_size', batch_size)
run.log('opt', optname)
run.log('lr', lr)

# モデルファイルのアップロード
# name: Azure上の保管場所, path_or_stream: ローカルのファイルパス
run.upload_file(name='outputs/bast_model.h5', path_or_stream = './best_model.h5') 

# StatusをCompleteにする
run.complete()
```

この状態でML WorkspaceのRunに上がった内容を見てみましょう。（と言っても上の例でネタバレしてますが。）
Experiments -> my_mnistexp -> 最新のRunを進んでみます。

<img width="873" alt="スクリーンショット 2019-02-10 19.03.36.png" src="https://qiita-image-store.s3.amazonaws.com/0/185518/a270ded4-0862-3ee0-6fa7-e7d194c498d4.png">

見ての通り，`run.log`で記録したものが一通り上がってます。さらに，上部のOutputタブを見てみると...

<img width="622" alt="スクリーンショット 2019-02-10 19.03.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/185518/063aca77-83f9-5b70-5e1c-23b3e64e69cd.png">

ちゃんとモデルも保存されてますね！

ここを使えばモデルだけでなく，実験結果のcsvや極端に言えば実行したデータ，ipynbファイルなんかもまとめて上げられちゃいます。

ハイパーパラメータ探索を行う[Optuna](https://optuna.org)などと組み合わせれば捗ること間違いなしです！

**追記**
さらに，snapshotを見てみると，実行ディレクトリ配下のファイルが全てアップロードされているのも確認できます。このアップロードは実行の度に行われるため，**実行時の状況を完全に再現することが可能になります**。また，まとめてダウンロードしたり，実行時のコードをブラウザ上からプレビューすることも可能です。（プレビューは.pyファイルのみ）

<img width="857" alt="スクリーンショット 2019-04-25 0.18.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185518/1a1f706a-5cbc-a5b3-5fc3-f82e6e9c5f8c.png">
（上の例はMNISTの例とは異なりますが，こんな感じで実行の度にrunにアップロードされます。）

# Tips
### runのStatusを変化させたい
`run.fail()`を呼び出せばStatus Failedとなって終了します。例えば，実行をtry内として，except内で左記fail()を呼ぶと良さげです。

### リアルタイムにロスを記録したい
kerasならば専用のcallbackを書いてしまえば良いです。具体的にはepochが終わるたびに`run.log('val_loss', val_loss)`などを呼び出せば良いです。

例えばこんな感じですかね？（筆者はmxnetをよく使うのでkerasは詳しくないです。）

```python
class AzureCallback(keras.callbacks.Callback):
    def __init__(self, run):
        super().__init__()
        self.run = run
        
    def on_epoch_end(self, epoch, logs=None):
        # epoch終了時にrun.logで記録
        logs = logs or {}
        train_loss = logs.get('loss')
        val_loss = logs.get('val_loss')
        val_acc = logs.get('val_acc')
        self.run.log('loss', train_loss)
        self.run.log('val_loss', val_loss)
        self.run.log('val_acc', val_acc)

```

### Experiment表示画面で，全てのRunから一番良い値を見たい
Experiments -> Add chartから見たいメトリックを表示できます。

### Runにメタデータを付与したい
そのRunでどんなネットワークを使ったかなどは何百とRunを行うと忘れがちなのでメタデータを付与したくなります。そんな場合は`run.tag`を利用すると良いです。

# まとめ
今回はAzure Machine Learning Service Workspaceを紹介させていただきました。まとめると出来ることは以下になります。

* 学習のパラメータ，結果，ファイルに至るまでをGUIで管理できる
* 実行に対してファイルが紐づくのでのモデルファイルや実行結果の管理が簡単
* SDKの使い方が比較的簡単

今回実行したipynbファイルは[こちら](https://github.com/kirikei-com/azure-ml-example/blob/master/azure_mnist.ipynb)に置いてますので，使って見てください！