こんにちは!株式会社神戸デジタル・ラボ DataIntelligenceチームの原口です。
今回はMicrosoftのAzure Machine LearningとAzure Machine Learningn SDKを利用して、MNIST学習の実験管理に取り組みます!
- 今回の目的
- 実験管理とは?
- Azure Machine Learningとは?
- Azure Machine Learningの準備
- Azure Machine Learning SDKのインストール
- 数字分類の学習コードを作成
- Azure Machine Learning仕様にチェンジ!
- まとめ
今回の目的
皆さん、機械学習は好きですか?私は大好きです。物体検出や画像生成、いろんなことが出来て夢がありますよね!そんな夢のある機械学習ですが、その実験管理はどのようにしていますか?
Excel、テキストベース・・・、ふむふむ。分かります。難しいですよね。
今回は難しい実験管理から機械学習の準備まで簡単に出来ちゃうAzure Machine Learningをご紹介します!
実験管理とは?
そもそも実験管理とは何でしょうか?実験管理とは、実験に関する「いつ・どこで・何を・どんなふうに・何のために・どんな結果が出たのか」を詳細に記した結果を管理することを指します。
なぜそんな管理をするのでしょうか?答えは比較を行うためです。実験管理を行っていない場合、過去の実験と比較することができないため、精度が上がった要因を考察することができません。
一方で実験管理を行うと、過去の実験に関する詳細な情報が記されているため、前回と今回でどこに差があるか分かるので、精度が上がった要因を容易に考察できます。
このように、実験管理は実験の影響を測るうえで非常に重要な役割を持っています。
Azure Machine Learningとは?
Azure Machine Learningとは、Microsoftが提供している機械学習プラットフォームです。データセットの作成やモデルの学習・評価、学習プログラムの構築も行えるオールインワンなサービスです。
Azure Machine Learningはブラウザ上で操作する方法とSDKによって操作する方法があります。
ブラウザ上では、グラフィカルインターフェイスによる直感的な操作が可能です。一方のSDKは一般的に使われる方法で、開発者やスクリプト操作に慣れた方はこちらを使うと良いでしょう。
今回はAzure Machine Learning SDKと手書き数字データセットであるMNISTを利用して、手書き数字分類AIの構築及びその実験管理に取り組みます!
Azure Machine Learningの準備
まずはAzure Machine Learningの準備をしましょう。こちらからAzure Machine Learningのページに遷移します。 Azureのアカウントを所有されていない方は「無料アカウント」を、すでに持たれている方は「サインイン」を選択し、Azure Portalページにアクセスします。
画面上部の検索欄に「Azure Machine Learning」を検索します。するとAzure Machine Learningが表示されますので、選択します。
表示された画面上部にある「作成」をクリックすると、ワークスペース作成画面が表示されます。ワークスペース作成画面が表示されたら、各種設定をしましょう。Azure Machine Learningの設定方法はこちらの記事でご紹介しています。
最後に構成ファイルをダウンロードします。構成ファイルにはワークスペースの情報が記載されていますので、取り扱いには注意が必要です。
Azure Machine Learning SDKのインストール
ローカルマシンでする場合は、Azure Machine Learning SDKの利用準備をします。以下のコマンドを実行すると、必要なライブラリがインストールされます。
pip install azureml-sdk azureml-widgets
これで準備完了です!
数字分類の学習コードを作成
Azure Machine Learningに移る前に、まずはMNISTデータセットを用いて数字分類ができるコードを作成しましょう。
以下のコードはこちらのページの内容を参考に構築しています。各コードの意味などを確認したい方は、参考にしてください。
はじめに必要なライブラリをインストールします。
import os import random import math import numpy as np import matplotlib.pyplot as plt from PIL import Image from sklearn.metrics import accuracy_score from tqdm import tqdm import torch import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optim from torch.utils.data import Dataset, DataLoader from torchvision import datasets, transforms os.makedirs('outputs', exist_ok=True)
続いて乱数を固定します。固定することで、比較実験時に結果が乱数の影響を受けなくなります。
def torch_fix_seed(seed=2022): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed(seed) torch.backends.cudnn.deterministic = True torch.use_deterministic_algorithms = True
データセット周りの準備もしましょう。
class CustomDataset(Dataset): def __init__(self, data, label): self.data = data self.labels = label self.prep = transform = transforms.Compose([]) def __getitem__(self, idx): data = self.prep(self.data[idx]).to(torch.float32).unsqueeze(0) label = self.labels[idx].to(torch.long) return data, label def __len__(self): return self.data.shape[0] dataset = datasets.MNIST('./data', train=True, download=True) data = dataset.data label = dataset.targets split_rate = 0.8 batch_size = 512 train_data = data[:math.ceil(data.shape[0] * split_rate)] train_label = label[:math.ceil(data.shape[0] * split_rate)] val_data = data[math.ceil(data.shape[0] * split_rate):] val_label = label[math.ceil(data.shape[0] * split_rate):] test_dataset = datasets.MNIST('./data', train=False, download=True) test_data = test_dataset.data test_label = test_dataset.targets train_dataset = CustomDataset(train_data, train_label) val_dataset = CustomDataset(val_data, val_label) test_dataset = CustomDataset(test_data, test_label) train_loader = DataLoader(train_dataset, batch_size=batch_size, num_workers=0) val_loader = DataLoader(val_dataset, batch_size=batch_size, num_workers=0) test_loader = DataLoader(test_dataset, batch_size=batch_size, num_workers=0)
続いてCNNモデルを構築します。今回構築するモデルは筆者が構築した独自モデルです。
class CustomModel(nn.Module): def __init__(self): super().__init__() self.block_1 = nn.Sequential( nn.Conv2d( in_channels=1, out_channels=4, kernel_size=3, stride=1, padding=1), nn.ReLU(), ) self.avepool_1 = nn.AvgPool2d(kernel_size=2, stride=2) self.block_2 = nn.Sequential( nn.Conv2d( in_channels=4, out_channels=8, kernel_size=3, stride=1, padding=1), nn.ReLU(), ) self.avepool_2 = nn.AvgPool2d(kernel_size=2, stride=2) self.block_3 = nn.Sequential( nn.Conv2d( in_channels=8, out_channels=16, kernel_size=3, stride=1, padding=1), nn.ReLU(), ) self.block_4 = nn.Sequential( nn.Conv2d( in_channels=16, out_channels=10, kernel_size=3, stride=1, padding=1), nn.ReLU(), ) def forward(self, x): x = self.block_1(x) x = self.avepool_1(x) x = self.block_2(x) x = self.avepool_2(x) x = self.block_3(x) x = self.block_4(x) x = F.avg_pool2d(x, kernel_size=x.size()[2:]) return x
最後に学習コードを作成します。
def calc_acc(y_pred, y_label): y_pred = torch.argmax(y_pred, dim=1) return torch.sum(y_pred == y_label) device = 'cuda' if torch.cuda.is_available() else 'cpu' torch_fix_seed() num_epochs = 3 model = CustomModel().to(device) opt = optim.Adam(model.parameters()) criterion = nn.CrossEntropyLoss() train_loss = [] val_loss = [] best_loss = None for epoch in range(num_epochs): with tqdm(train_loader) as pbar: total_acc = 0 total_data = 0 total_loss = 0 for n, (data, y_label) in enumerate(pbar): data = data.to(device) y_label = y_label.to(device) opt.zero_grad() y_pred = model(data) loss = criterion(y_pred[:, :, 0, 0], y_label) loss.backward() opt.step() total_acc += calc_acc(y_pred[:, :, 0, 0], y_label) total_data += data.shape[0] total_loss += loss.item() acc = total_acc / total_data pbar.set_description( f"[train] Epoch {epoch+1:04}/{num_epochs:04} loss {total_loss/total_data:1.4f} Acc {acc:1.4f}") train_loss += [total_loss / total_data] with tqdm(val_loader) as pbar: total_acc = 0 total_data = 0 total_loss = 0 for n, (data, y_label) in enumerate(pbar): with torch.no_grad(): data = data.to(device) y_label = y_label.to(device) y_pred = model(data) loss = criterion(y_pred[:, :, 0, 0], y_label) total_acc += calc_acc(y_pred[:, :, 0, 0], y_label) total_data += data.shape[0] total_loss += loss.item() acc = total_acc / total_data pbar.set_description( f"[ val ] Epoch {epoch+1:04}/{num_epochs:04} loss {total_loss/total_data:1.4f} Acc {acc:1.4f}") if best_loss is None or best_loss > total_loss / total_data: best_loss = total_loss / total_data torch.save(model.state_dict(), "./outputs/model.pth") val_loss += [total_loss / total_data] with tqdm(test_loader) as pbar: total_acc = 0 total_data = 0 label = [] pred = [] data_list = [] for n, (data, y_label) in enumerate(pbar): with torch.no_grad(): data = data.to(device) data_list.append(data.cpu().numpy()) y_label = y_label.to(device) y_pred = model(data) label += y_label.cpu().numpy().tolist() pred += torch.argmax(y_pred[:, :, 0, 0], dim=1).cpu().numpy().tolist() data = np.concatenate(data_list, axis=0) print(accuracy_score(label, pred))
これで一通り学習ができるようになりました。では学習してみましょう!
[train] Epoch 0001/0003 loss 0.0033 Acc 0.4809: 100%|██████████| 94/94 [00:08<00:00, 10.62it/s] [ val ] Epoch 0001/0003 loss 0.0018 Acc 0.7732: 100%|██████████| 24/24 [00:00<00:00, 26.22it/s] [train] Epoch 0002/0003 loss 0.0013 Acc 0.8260: 100%|██████████| 94/94 [00:08<00:00, 11.00it/s] [ val ] Epoch 0002/0003 loss 0.0009 Acc 0.8730: 100%|██████████| 24/24 [00:00<00:00, 25.20it/s] [train] Epoch 0003/0003 loss 0.0008 Acc 0.8780: 100%|██████████| 94/94 [00:07<00:00, 11.94it/s] [ val ] Epoch 0003/0003 loss 0.0007 Acc 0.8985: 100%|██████████| 24/24 [00:00<00:00, 24.34it/s] 100%|██████████| 20/20 [00:00<00:00, 29.12it/s] 0.8987
上のような結果が出れば学習の確認はOKです。Azure Machine Learningで実験管理をしましょう。
Azure Machine Learning仕様にチェンジ!
ではAzure Machine Learning仕様にチェンジしましょう。
Step1:下準備
まずはAzure Machine Learningのワークスペースと接続する必要があります。以下のコードを学習コードの前に記述してワークスペースと接続しましょう!
from azureml.core import Workspace ws = Workspace.from_config()
続いて実験を定義します。この定義はAzure Machine Learningに「”test”という名前で実験します!」と宣言しているイメージに近いです。
from azureml.core import Experiment experiment = Experiment(workspace=ws, name="test")
最後にAzure Machine Learningに実験開始を宣言しましょう。
run = experiment.start_logging()
これで下準備は完了です。
Step2:実験結果の登録
続いて実験結果を登録しましょう。
まずは学習・検証時の損失の経過を登録します。
学習コードの下に次のコードを追加します。
run.log_list("train_loss", train_loss) run.log_list("val_loss", val_loss)
これによって、現在実行している実験のログをAzure Machine Learningに保存することができます。
特にリスト上のデータを保存する場合はrun.log_list
を利用します。第一引数が登録名、第二引数が実際のデータです。
今回の例ですと、"train_loss"という名前でtrain_loss
のデータが格納されたことになります。
続いてモデルの精度を保存しましょう。
run.log("Accuracy", accuracy_score(label, pred))
定数を保存する場合はrun.log
を利用します。引数はrun.log_list
と変化ありません。
さらにモデルの出力先を変更します。
torch.save(model.state_dict(), "./outputs/model.pth")
outputs
に保存したデータはAzure Machine Learningに保存されます。とりあえずアップロードしておきたいと思ったものはoutputs
に保存しましょう。
最後に実験終了をAzure Machine Learningに伝えましょう。
run.complete()
以上で準備完了です!実際に実行しましょう!
Step3:実験結果の確認
実行後、Azure Machine Learningのジョブを選択すると先ほどの実験結果が一番上に登録されていますので、選択しましょう。
すると下図のような画面が表示されます。この画面では、実験が行われた日時や誰が行った実験か、さらには実験時に用いた引数も登録されています。どんな引数で実験したっけな・・・・。と悩む自分はもういません!
ではメトリックタブを選択しましょう。メトリックでは、学習時に登録したデータの確認ができます。特にリストで保存したものに関しては、グラフとして描画されるので非常に便利です。
最後に出力が保存されているか確認しましょう。出力とログのタブを選択すると次のような画面が表示されます。
ちゃんと保存されていますね!
Step4:モデルの登録
最後にモデルを登録しましょう!機械学習においてよく起こる問題が、どのモデルが一番精度が良かったのか分からなくなること・このモデルは何のモデルだったか忘れることです。
Azure Machine Learningにはモデルを登録する機能があるので、そこに登録しておきましょう!
そうすることで、「いつ・誰が・何を学習したモデルなのか・バージョン違いがあるのか」などの細かな情報を簡単に管理することができます。
run.complete()
の下に、以下のコードを追加しましょう。
run.register_model( model_name="mnist-model", model_path="./outputs/model.pth", tags={ "Dataset": "Mnist"}, properties={ "Accuracy": accuracy_score( label, pred)})
各引数について説明します。
model_name
:登録時のモデル名model_path
:登録するモデルまでのパスtags
:モデルに付与するタグproperties
:モデルの精度や損失などの情報を付与できる
上記を実行後、Azure Machine Learningのトップページからモデルへアクセスしましょう。設定した名前で登録されていることが確認できます。
Azure Machine Learningはモデルのバージョン管理もできます!なので、「あ!!間違えて上書きしてしまった!」や「過去のデータ消しちゃった・・・」なんていう問題ともおさらばです!
まとめ
今回はAzure Machine Learningを用いてMNISTの実験管理をしました。Azure Machine Learningは精度や損失の履歴の管理はもちろん、モデルのバージョン管理も出来てしまう優れたツールです!
機会があればぜひご利用ください!
次回は、今回作成したコードをクラウドマシンで実行できるよう変更を加えます!お楽しみに!