Nyantech とはじめる MLOps

〜学習パイプランを使って効率的に猫を見分ける機械学習モデルを目指そう ! の巻〜

2021-11-02
日常で楽しむクラウドテクノロジー

大渕 麻莉

こんにちは、機械学習ソリューションアーキテクトの大渕です。

いままで、Amazon Rekognition Custom LabelsAmazon SageMaker の画像分類モデル を使って、写真に写っているのがミケなのかタマなのかを見分けてきました。また、前回の記事 では類似画像検索の手法を使ってたくさんある猫写真の中から自分の猫に似ている猫の写真を見つける方法をご紹介しました。今回は、機械学習モデルを作るアプローチでいってみたいと思います。うまくいけば、写真に写っているのがミケなのか、タマなのか、それ以外の猫なのかを見分けることができるはずです。

学習用に、ミケ、タマ、それ以外の猫の写真を用意しました。「それ以外の猫」の写真は、前回の記事で使用した猫データセットを使用しています。集めた画像を見てみましょう。私にはミケ、タマ、それ以外の猫を容易に見分けることができるのですが、ミケやタマに似た猫もいるのでそれらを見分ける機械学習モデルを作るのはちょっと難しそうな雰囲気がしてきました。

おそらく、一度モデルを学習させただけで「はい、大成功 !」とはならず、何度か試行錯誤してモデルの精度を上げていく必要がありそうな予感がビシビシします。試行錯誤には時間がかかりそうなので、なんとか楽にできないかという考えが頭をよぎります。

img_nyantech-ml-ops_01

ここで少し、記事のタイトルにある MLOps についてご紹介します。一言でいえば DevOps の機械学習バージョンであり、学習データを収集して、モデルを学習して、モデルをデプロイ・運用して、という機械学習ならではのワークフローを効率的に行うためのしくみです。2021 年 10 月に「実践 AWS データサイエンス ―エンドツーエンドの MLOps パイプライン実装 (オライリージャパン)」という本も出版されたことですし、Nyantech にも MLOps を取り入れてみたいと思います。

MLOps の構成要素の中には、機械学習モデルの開発を効率化するための学習パイプライン、実験管理、モデルの CI/CD、デプロイしたモデルの性能監視、などがあるのですが、今回は効率的に猫を見分けるモデルを作るために学習パイプラインを使うことにします。

使うことにします、と言ってみたものの別段誰かが作ってくれるわけではないので自分で作らなければなりません。しかし、学習パイプラインをゼロから作るのはちょっと大変です。そもそもどうやって作れば良いかよくわかりません。

みなさまご安心ください、学習パイプラインの構築をなるべく楽に作る方法をご用意いたしました。

今回の記事では、機械学習モデルを作る際の試行錯誤を効率的に行うための学習パイプラインを使って、猫を見分けるモデルを作っていく流れをご紹介します。こちらの GitHub リポジトリ に学習パイプラインの雛形が Jupyter ノートブックの形で公開されています。今回はこのノートブックをカスタマイズして使おうと思いますが、まずはどんなことが書かれているのかを見ていきましょう。


 1. 学習パイプラインを作る Jupyter ノートブック

以下の図は、 Jupyter ノートブック を実行して作成される学習パイプラインの概要です。
 
学習に使用するデータの前処理をし、前処理したデータを使ってモデルを学習し、学習したモデルを評価し、評価結果に応じた処理を行うワークフローを AWS Step Functions を使って実現しています。 また、データの前処理とモデルの評価には Amazon SageMaker Processing Job、モデルの学習には Amazon SageMaker Training Job、モデルの評価結果に応じた処理はコンテナイメージをデプロイした Lambda 関数で行っています。
 
Step Functions は前処理、学習、などの各処理をステップという単位で表しますが、各ステップ間のデータのやり取りは Amazon S3 を介して行われています。
img_nyantech-ml-ops_02

ワークフローを実行する際に、生データが保存されている S3 パスや機械学習アルゴリズムのハイパーパラメータ、学習・評価スクリプトを動的に指定することができるため、それらの情報を変えながらモデルを作るという試行錯誤を最小限の手間で行うことができます。また、Amazon SageMaker Experiments を使用して実験管理をしているのも見逃せないポイントです。Experiments によって試行錯誤の内容が自動的に記録されていくので、あとで最良のモデルを容易に見つけることができます。

ということで Step Functions で学習パイプラインを作るのですが、その際に使われている AWS Step Functions Data Science SDK についても少しご紹介しておきます。Step Functions を使い慣れている人にもあまり知られていないですが、この SDK を使うと Python で Step Functions ワークフローを作成することができます。つまり、データサイエンティストが Jupyter ノートブックなどで学習スクリプトを書きつつ学習パイプラインも同時に作る、なんてことができるのです。ソースコードから Step Functions の定義ファイルが生成されるので、ソースコードを管理すればワークフローの管理もできてしまいます。

実際にどのような感じで Step Functions ワークフローが作られているのか少し見てみましょう。以下のコードは SageMaker Processing の SKLearnProcessor を作成している部分です。ここは Step Functions というよりも SageMaker Processing を使う際の手順です。 

sklearn_processor = SKLearnProcessor(
    framework_version="0.20.0",
    role=role,
    instance_type="ml.m5.xlarge",
    instance_count=1,
    max_runtime_in_seconds=1200,
)

以下のコードは Step Functions の前処理ステップを作成する部分です。Step Functions Data Science SDK の ProcessingStep というクラスを使って前処理ステップを作成しています。processor というパラメータに先ほど作成した sklearn_processor を指定して SageMaker と Step Functions のステップを紐付けています。

processing_step = ProcessingStep(
    "SageMaker pre-processing step",
    processor=sklearn_processor,
    job_name=execution_input["PreprocessingJobName"],
    inputs=inputs,
    outputs=outputs,
    container_arguments=["--train-test-split-ratio", "0.2"],
    container_entrypoint=["python3", "/opt/ml/processing/input/code/preprocessing.py"],
)

同様の方法で学習ステップやモデル評価ステップなども作成し、最後に以下のように Chain クラスを使ってステップを連結してワークフローの完成です。慣れ親しんだ Python で学習パイプラインを作れるのは非常に便利ですね。

workflow_graph = Chain([processing_step, training_step, processing_evaluation_step, lambda_step])

branching_workflow = Workflow(
    name="SageMakerProcessingWorkflow-" + user_name,
    definition=workflow_graph,
    role=workflow_execution_role,
)

branching_workflow.create()
branching_workflow.update(workflow_graph)

Jupyter ノートブックを実行すると、こちらのような Step Functions ワークフローが作成されます。こちらの図は Step Functions Studio の UI でワークフローを表示したものです。見やすいですね。

なお、Data Science SDK を使って作成したワークフローを Step Functions Studio で編集することも可能です。

基本的にワークフローの定義はソースコードで管理したいので SDK を使って作成したワークフローを Step Functions Studio で編集するのはおすすめではないのですが、SDK で実現する方法がどうしてもわからないときに Step Functions Studio で目指すワークフローを作って SDK の使うべきクラスやパラメータのアタリをつける、という使い方で助かったことがあります。

img_nyantech-ml-ops_03

ここまでで学習パイプラインの作り方がわかったので、次はこの Jupyter ノートブックをカスタマイズして自分用の学習パイプラインを作って猫を見分けるモデルを作ります。

今回は以下の 3 ステップで進めていきたいと思います。

ステップ 1 : Amazon SageMaker ノートブックインスタンスの起動
ステップ 2 : 雛形 Jupyter ノートブックをカスタマイズ
ステップ 3 : 満足いくまで Let’s 試行錯誤 !


 2. ステップ 1 : Amazon SageMaker ノートブックインスタンスの起動

本ハンズオンでは、サンプルノートブックを実行する環境として Amazon SageMaker のノートブックインスタンスを利用します。以下の手順で Amazon SageMaker ノートブックインスタンスを起動します。

  1. AWS マネジメントコンソールにログインします。ログイン後、画面右上のヘッダー部のリージョン表示が [東京] となっていることを確認します。もし [東京] となっていない場合はリージョン名をクリックして [東京] に変更してください。(他のリージョンを使いたい場合は、Amazon SageMaker が使える別のリージョンを選択しても構いません。その場合、以降のリージョンをすべてそのリージョンに読み替えてください)
  2. AWS マネジメントコンソールの画面左上にある [サービス] をクリックしてサービス一覧を表示させ、[Amazon SageMaker] を選択してください (画面上部の検索窓に "sage" などと打ち込むことで、サービスを絞り込むことが可能です)。Amazon SageMaker の画面を開いたら左側のメニューから [ノートブックインスタンス] を選択し、[ノートブックインスタンスの作成] ボタンをクリックします。
  3. “ノートブックインスタンス名“ に任意の名前を設定してください。
  4. “プラットフォーム識別子” のプルダウンメニューから [notebook-al2-v1] をクリックしてください。
  5. “IAM ロール“ は、プルダウンメニューから [新しいロールの作成] をクリックしてください。
  6. “指定する S3 バケット”[任意のS3バケット] を選択して [ロールの作成] をクリックしてください。
  7. “Gitリポジトリ” は、[リポジトリ] のプルダウンメニューの中から [このノートブックインスタンスのみにパブリック Git リポジトリのクローンを作成する] をクリックし、その下に表示されるテキストボックスに ”https://github.com/aws-samples/aws-ml-jp”と入力してください。
  8. 画面を一番下までスクロールし、[ノートブックインスタンスの作成] をクリックしてください。数分でノートブックインスタンスのステータスが InService になります。

9. ノートブックインスタンス一覧から、いま作成したノートブックインスタンス名をクリックして ”アクセス許可と暗号化” の部分にある ”IAM ロール ARN”のリンクをクリックしてください

img_nyantech-ml-ops_04

クリックすると拡大します

10. 新しいタブで IAM ロールのコンソールが表示されます。[ポリシーをアタッチします] をクリックして AWSStepFunctionsFullAccess を検索し、候補に出てきた AWSStepFunctionsFullAccess の横のチェックボックスをオンにします。

11. 同様の手順で以下のポリシーのチェックボックスをオンにして [ポリシーのアタッチ] をクリックしてください

  • AmazonEC2ContainerRegistryFullAccess
  • IAMFullAccess
  • AWSLambda_FullAccess

以上で環境の準備は整いました。


 3. ステップ 2 : 雛形 Jupyter ノートブックをカスタマイズ

ノートブックインスタンスが起動したら aws-ml-jp/mlops/step-functions-data-science-sdkmodel-train-evaluate-compare/ step_functions_mlworkflow_scikit_learn_data_processing_and_model_evaluation_with_experiments.ipynb を開きます。この Jupyter ノートブックの構成から、以下の部分を変更します。

3-1. 前処理ステップと評価結果に応じた処理ステップの削除
3-2. 学習データセットの準備と Amaozn S3 へのアップロード
3-3. 画像分類モデル学習スクリプトの準備と S3 へのアップロード
3-4. モデル評価スクリプトの準備と S3 へのアップロード

それぞれの手順を簡単にご説明します。

 3-1. 前処理ステップと評価結果に応じた処理ステップの削除

今回はデータセットの準備は Jupyter ノートブック上でやるので前処理ステップを削除します。また、評価結果に応じた処理も不要なのでそちらも削除します。ステップの削除は簡単です。

各ステップをつなげる際に以下のように除外すれば OK です。

#before

workflow_graph = Chain([processing_step, training_step, processing_evaluation_step, lambda_step])

#after

workflow_graph = Chain([training_step, processing_evaluation_step])

 3-2. 学習データセットの作成と Amazon S3 へのアップロード

今回は PyTorch の ImageFolder クラスを使うので、こちらのようなフォルダ構成で学習用と検証用の画像を準備します。

クラス名のフォルダを作成し、その中に対応する画像を配置します。これら 2 つのフォルダを S3 にアップロードします。

img_nyantech-ml-ops_05

注意点としては、使用する画像を変更 (追加・削除) する際はタイムスタンプなどを使って作成した新しい S3 パスにこれらのフォルダをアップロードすることです。

過去に使用した学習データを上書きしてしまうと、あとでどのデータセットでモデルを学習したのかを検証することができなくなってしまいます。DVCなどのツールを使ってデータセットの管理をすることも可能ですが、今回はアップロード先の S3 パスを都度変更する運用で行きたいと思います。

データセットに変更を加えるたびに、こちらのようにフォルダが増えていくイメージです。

img_nyantech-ml-ops_06

 3-3. 画像分類モデル学習スクリプトの準備と S3 へのアップロード

PyTorch の転移学習のチュートリアルを参考に学習スクリプトを作成します。今回はチュートリアルと同じく ResNet18 を使いました。

以下のようなコードを追加してハイパーパラメータや SageMaker の環境変数を受け取り、学習の際はこれらの値を使用するようにします。ハイパーパラメータを引数として渡せるようにすることで、あとでハイパーパラメータの試行錯誤ができるようになります。

parser = argparse.ArgumentParser()

# Data and model checkpoints directories
parser.add_argument('--batch-size', type=int, default=8, metavar='N',
                        help='input batch size for training (default: 8)')
parser.add_argument('--test-batch-size', type=int, default=8, metavar='N',
                        help='input batch size for testing (default: 8)')
parser.add_argument('--epochs', type=int, default=10, metavar='N',
                        help='number of epochs to train (default: 10)')
parser.add_argument('--num-classes', type=int, default=2, metavar='N',
                        help='number of classes(default: 2)')
parser.add_argument('--lr', type=float, default=0.01, metavar='LR',
                        help='learning rate (default: 0.01)')
parser.add_argument('--momentum', type=float, default=0.5, metavar='M',
                        help='SGD momentum (default: 0.5)')

# Container environment
parser.add_argument('--model-dir', type=str, default=os.environ['SM_MODEL_DIR'])
parser.add_argument('--data-dir', type=str, default=os.environ['SM_CHANNEL_TRAINING'])
parser.add_argument('--num-gpus', type=int, default=os.environ['SM_NUM_GPUS'])

また、チュートリアルのコードでは学習終了時に、最も val accuracy がよかったモデルを return するようになっていますが、今回はこのモデルをファイルとして保存します。その際に、以下のコードのように保存先のパスは --model-dir として渡されたパスを使用します。SageMaker は Training Job を終了して学習用インスタンスを停止させる前に --model-dir として渡されたパスに保存されているファイルを全てまとめて model.tar.gz に圧縮して S3 にアップロードします。そのため、学習したモデルを別のパスに保存してしまうと、SageMaker はそのモデルを見つけられないため S3 には空っぽの model.tar.gz がアップロードされてしまいますのでご注意ください。

def save_model(model, model_dir):
    logger.info("Saving the model.")
    path = os.path.join(model_dir, 'model.pth')
    torch.save(model.cpu().state_dict(), path)

なお、新しく作成した学習スクリプトは大抵一発では完走しません。そのため、いきなり学習パイプラインに組み込むのではなく、Training Job 単体で動作確認をしてからパイプラインに組み込みます。Training Job を起動するには、以下のように PyTorch の estimator を作成して、fit() 関数を実行します。instance_type にインスタンス名ではなく ”local” と設定することで、ノートブックインスタンス上で Training Job の動作確認が可能です。

モデルを学習している間に算出したメトリクスを Amazon CloudWatch Metrics に送り込むために metric_definitions というパラメータにメトリクス名とメトリクスの値を取得するための正規表現を指定しています。正規表現は学習スクリプトが表示するログの形式に合わせます。SageMaker は丸括弧 () の中の数値をメトリクスの値として解釈します。

estimator = PyTorch(entry_point="train.py",
        role=role,
        framework_version='1.8.0',
        py_version='py3',
        instance_count=1,
        instance_type=instance_type,
        metric_definitions = [{
                        'Name': 'Train Loss',
                        'Regex': 'train Loss: ([0-9\\.]+)'},
            {
                        'Name': 'Train Acc',
                         'Regex': 'train Acc: ([0-9\\.]+)'},
            {
                        'Name': 'Valid Loss',
                        'Regex': 'val Loss: ([0-9\\.]+)'},
            {
                        'Name': 'Valid Acc',
                         'Regex': 'val Acc: ([0-9\\.]+)'},
        ])
estimator.fit({"training": train_data_path}, wait=False)

学習スクリプト train.py が作成できたら S3 にアップロードします。学習スクリプトもデータセットと同様に、内容を変更したら新しい S3 パスにファイルをアップロードします。

 3-4. モデル評価スクリプトの準備と S3 へのアップロード

元の Jupyter ノートブックの evaluation.py をほとんど流用しますが、今回は多クラス分類なので使用する評価値を変更し、さらに、試行錯誤の結果の比較がしやすいように Experiments に記録する情報もカスタマイズしました。

report_dict = classification_report(y_test, predictions, output_dict=True)
report_dict["accuracy"] = accuracy_score(y_test, predictions)

print('Recording metrics to Experiments...')
with Tracker.load() as processing_tracker: # Tracker requires with keyword
    processing_tracker.log_parameters({ "accuracy": report_dict["accuracy"], 
                                        "precision": report_dict['macro avg']["precision"], 
                                        "recall": report_dict['macro avg']["recall"], 
                                        "f1": report_dict['macro avg']["f1-score"], 
                                        "comment": args.experiment_comment,
                                        "is_best": is_best})

モデル評価スクリプト evaluation.py も、タイムスタンプなどを使って作成した一意の S3 パスにアップロードします。

ここまでで、学習パイプラインのカスタマイズは完了です。


 4. ステップ 3:満足いくまで Let’s 試行錯誤 !

 4-1. ベースラインの作成:ハイパーパラメータ epoch 1 でスタート

それでは思う存分試行錯誤していきましょう ! 今回は分かりやすさ重視のためかなり乱暴に試行錯誤していきます。

まずはベースラインとして、epoch 1 で学習パイプラインを実行します。以下の図は Experiments に記録された結果です。どのメトリクスの値も低く、ほとんどモデルが学習できていません。

img_nyantech-ml-ops_07

クリックすると拡大します

 4-2. ハイパーパラメータ epoch を 10 に変更

さすがに epoch 1 はダメでしょうということで、次は epoch 10 にして実行します。

F1 スコアが 0.73 に改善しました。

img_nyantech-ml-ops_08

クリックすると拡大します

 4-3. epoch をさらに増やす

それではさらに、epoch 100、300 と増やしてみます。

さらに F1 スコアが改善しました。

img_nyantech-ml-ops_09
img_nyantech-ml-ops_10

epoch 300 の際の Validation accuracy/loss を見てみると、epoch に頼るのはこのあたりが限界のようです。なお、使用したハイパーパラメータや学習済みモデルの保存場所などのモデルに関連する情報はすべて SageMaker のコンソールから確認できます。

img_nyantech-ml-ops_11

 4-4. データの水増し

次の施策として学習データを増やそうと思ったのですが、画像を増やすのが面倒だったので Augmentation に頼ることにしました。

PyTorch の transforms を使って画像のランダムクロップやランダムフリップなどを行い、画像の水増しをしてみます。

transforms.RandomCrop(SIZE),
transforms.RandomHorizontalFlip(),
transforms.RandomVerticalFlip(),
transforms.RandomAffine(degrees=[-10, 10], translate=(0.1, 0.1), scale=(0.5, 1.5)),

F1 スコアが 0.92 まで上がりました。

img_nyantech-ml-ops_12

クリックすると拡大します

混同行列も見てみます。縦軸が正解ラベルで横軸が推論結果です。
タマと他の猫の区別がうまくいっていないようです。

img_nyantech-ml-ops_13

クリックすると拡大します

間違えた画像を確認してみましょう。タマは長毛のオレンジ色の猫なのですが、そっくりさんの猫がタマとして認識されています。

私にはそっくりさんとタマの違いが明確にわかるのですが、機械学習モデルが彼らを見分けるのは難しいかもしれません。もしかすると人間のみなさまにとっても困難なタスクかもしれません。

img_nyantech-ml-ops_14
img_nyantech-ml-ops_15

ここまでの実験では学習用と検証用それぞれに各クラス 10 枚ずつ画像を用意していたのですが、難易度に対して数が少なすぎる気がしてきたので、データの水増しだけでなんとかすることは諦めて元画像を増やすことにします。

 4-5. 画像を増やす

各クラスの学習用・検証用画像をそれぞれ 25 枚に増やしました。テスト画像には手を加えていません。

F1 スコアが 0.94 になりました !

img_nyantech-ml-ops_16

クリックすると拡大します

混同行列を見てみると、ミケとその他の猫はうまく認識できていますが、タマが他の猫だと間違えられてしまうようです。実際に間違えた画像を見てみると、顔が影になっていたり全身が写っていなかったりで、おそらく先ほどのそっくりさんだと判定されてしまったのでしょう。

とはいえ、ひとまず学習に使用する画像を増やすことでモデルの性能が向上することが確認できました。

img_nyantech-ml-ops_17
img_nyantech-ml-ops_18

試行錯誤の続きとして、タマとそっくりさんを見分けられるようにタマの画像とそっくりさんの画像を増やしてみるなどが考えられますが、もしかすると「タマとそっくりさんの判別は人間でも判別が難しいレベルとして許容する」という判断が必要になるかもしれません。

実際のユースケースでも、試行錯誤の中で「どこまでを機械学習でやるべきか」「他の手段と組み合わせることで最終的な目標を達成できないか」などを検討することはよくあります。


 5. リソースの削除

使用したリソースの中には、削除しない限り課金が続くものがあります。課金を止めるためには、以下の手順でリソースの削除を行います。削除したリソースは元に戻せないのでご注意ください。


Jupyterノートブックの最後にある「リソースの削除」の実行

Jupyterノートブックの最後に、学習パイプラインのために作成したリソースを削除するコードがありますので忘れずに実行してください。

Amazon SageMaker のノートブックインスタンスの停止と削除

ステータスが InService になっているノートブックインスタンスをいきなり削除することはできないため、ノートブックインスタンスをまず停止させ、その後削除します。ノートブックインスタンスは停止の状態から再度起動させることが可能ですが、一旦削除してしまうと同じノートブックインスタンスを使用することはできず、保存していたデータなども全て削除されますのでご注意ください。

Amazon SageMaker コンソールの左側のメニューから「ノートブックインスタンス」をクリックし、削除したいノートブックインスタンスを選択します。

上にある [アクション] から [停止] を選択し、ノートブックインスタンスのステータスが Stopped になったら再度 [アクション] から [削除] を選択するとノートブックインスタンスを完全に削除することができます。

img_nyantech-ml-ops_19

クリックすると拡大します

Amazon S3バケットの削除

今回ご紹介した Jupyter ノートブックを実行すると、sagemaker-[リージョン名]-[アカウントID] という名前の S3 バケットが作成されます。

保存されているデータが不要であれば、このバケットを削除してください。


 6. さいごに

今回は、学習パイプラインを使って猫を見分けるモデルを作るための試行錯誤を効率的に行いました。

すべてを手作業でやるのに比べると以下のポイントが楽だなと思いました。学習パイプラインを使わず自分で同じことをやった場合、「あれ、このモデルどうやって作ったんだっけ ?」「いまスクリプトのどこを変えたんだっけ ?」と迷子になり多数の手戻りが生じたであろうことは想像に難くありません。

  • 1 回の試行はパラメータやスクリプトを変えて Step Functions ワークフローを実行するだけ
  • 変更箇所の記録が自動的に保存される
  • 試行錯誤の結果がまとまっているため簡単に比較できる

本記事が、みなさまの機械学習ライフの一助になれば幸いです。


 参考情報


builders.flash メールメンバーへ登録することで
AWS のベストプラクティスを毎月無料でお試しいただけます

筆者プロフィール

photo_ohbuchi

大渕 麻莉
アマゾン ウェブ サービス ジャパン合同会社
機械学習ソリューションアーキテクト。

組込みソフトウェア開発から画像処理アルゴリズム開発を経てクラウドに到達し、2019 年にアマゾン ウェブ サービス ジャパン合同会社に入社。主に製造業のお客様の機械学習導入・運用の技術サポートを担当。
脳内 CPU の半分が猫のことで占められており、視界に入るすべての生き物がうちの猫に見えるという日々を過ごしている。

AWS のベストプラクティスを毎月無料でお試しいただけます

さらに最新記事・デベロッパー向けイベントを検索

下記の項目で絞り込む
絞り込みを解除 ≫
1

AWS を無料でお試しいただけます

AWS 無料利用枠の詳細はこちら ≫
5 ステップでアカウント作成できます
無料サインアップ ≫
ご不明な点がおありですか?
日本担当チームへ相談する