SageMakerで自作モデルをクラウド公開する方法を解説!「作ったAI」を「使えるアプリ」へ。

AWS

「AIアプリを作りたい。でも、デプロイの壁が高すぎる……」

AIを活用した革新的なアプリが次々と登場する中、「自分でもAIを組み込んだサービスを作ってみたい」と考えるエンジニアが増えています。

しかし、いざAWS SageMakerでモデルを公開しようとすると、model.tar.gzinference.py といった専門用語の嵐に、デプロイする前から挫折しそうになったことはありませんか?

本記事では、そんな「SageMakerのハードル」を一つずつ取り除き、ローカルで育てたPyTorchモデルをクラウドAPIとして動かすまでの最短ルートを徹底解説します。

この記事を読み終わると

ローカルで作ったモデルを「REST APIとしてクラウドに公開する」流れが理解できます。実際にデプロイするためのコードも全て載せているので、AWS環境があればすぐに試せます。

1. SageMakerの重要用語

まず、デプロイ前に立ちはだかる「謎の用語」を、身近なものに例えて整理します。

用語イメージ役割
model.pth秘伝のレシピ学習で得られたモデルの「重み」データ。
inference.py調理手順書データを受け取って、どう予測(調理)するかを書いたコード。
model.tar.gzお弁当パック上記2つを1つにまとめたもの。これをS3に置きます。
S3食材置き場モデルデータを保管しておくクラウド上のストレージ。
エンドポイントお店の窓口アプリから予測を依頼するための「URL(接続先)」。

2. デプロイの全体フロー:6つのステップ

SageMakerへの道筋を整理すると、6つのステップです。 「ローカルで準備して、AWSに預ける」という流れを意識しましょう。

【前半:ローカル環境での準備】

  • ① モデルを学習(model.pth)
    • モデルの「脳」にあたる重みデータを保存します。これがないと、せっかく学習した知識が消えてしまいます。
  • ② inference.py を作成
    • SageMakerに「どうやって推論するか」を教える指示書です。モデルの読み込みやデータの変換ルールを書きます。
  • ③ model.tar.gz にパッケージ
    • ①と②をひとまとめにします。AWSに「これ一式で動かしてね」と渡すためのお弁当パックのようなイメージです。

【後半:AWSクラウドへの展開】

  • ④ S3にアップロード
    • 作成したパッケージを、AWSのストレージ(S3)に置きます。SageMakerはここからデータを読み取ります。
  • ⑤ SageMakerエンドポイント作成
    • クラウド上に推論専用のサーバーを立ち上げます。ここで初めて、モデルが「生きている状態」になります。
  • ⑥ APIとして利用可能に!
    • 世界中からアクセス可能なURLが発行されます。これで、自作アプリからAIを呼び出せるようになります!

3. sagemakerデプロイに必須のファイル「model.tar.gz」とは?

SageMakerにデプロイする最終形態は、model.tar.gz という1つの圧縮ファイルです。

「これさえあれば推論ができる」という状態にするために、中には以下の2つの要素を詰め込みます。

Plaintext

model.tar.gz(デプロイ用パッケージ)
├── model.pth           ← ①学習済みの重み(数値データ)
└── code/
    └── inference.py    ← ②モデル構造 + 推論方法(プログラム)

各ファイルの役割と「なぜ必要なのか」

ファイル中身の正体役割(メタファー)
model.pth学習済みの重み(膨大な数値データ)AIの「記憶・経験値」そのもの
inference.pyモデルのクラス定義 + 4つの関数記憶を呼び出すための「脳の構造と使い方」
model.tar.gz上記2つを固めたアーカイブAWSに届けるための「出荷用コンテナ」

⚠️ ここが重要!PyTorchデプロイの落とし穴

「学習したモデル(.pth)を保存したから、これだけで動くはず!」

……実は、ここが初心者が最も躓きやすいポイントです。

PyTorchの .pth ファイルには、基本的に「重み(パラメータ)」しか保存されていません。 例えるなら、model.pth は「超一流シェフの知識」ですが、それを受け取った側(SageMaker)は「その知識をどのキッチン(モデル構造)で使えばいいのか」を知りません。

そのため、inference.py の中で以下の2点をセットで伝える必要があります。

  1. 「どんなキッチンか」: class Net(nn.Module): などのモデル構造の定義
  2. 「どう料理するか」: 届いたデータをどう処理して結果を出すかという手順

この「記憶(重み)」と「仕組み(コード)」が揃って初めて、クラウド上でAIが正しく動き出します。

Pythonコード

学習が終わった「賢い状態」を保存するコマンドはこちらです。

torch.save(model.state_dict(), "model.pth")

4. 「重み(Weight)」って何?

SageMakerにアップロードする model.pth

この中身である「重み(おもみ)」を一言でいうと、AIが学習を通じて得た「こだわり」の数値化です。

「重み」=「どれくらい重要視するか」の指標

AI(ニューラルネットワーク)の内部では、入力されたデータに対して無数の「線」が繋がっています。この線1本1本に割り振られた数値が「重み(おもみ / Weight)」です。

例えば、手書きの数字「7」を判別するモデルを考えてみましょう。

  • 上のピクセルに線がある:
    • 「7」である可能性が高い! → 重みをプラス(0.8など)にする。
  • 下のピクセルに線がある:
    • 「7」ならここに線はないはず…。→ 重みをマイナス(-0.3など)にする。

    このように、「この特徴があれば正解に近い」「これがあれば不正解に近い」という判断基準を、すべて数値で持っているのが重みの正体です。


    「モデルの学習」とは、重みをミリ単位でチューニングする作業

    AIが「学習中」のとき、裏側では何が起きているのでしょうか?

    • 学習前(未熟): 重みはバラバラのランダム値。
      • 「7」を見せても「これは……3かな?」とデタラメに答えます(精度10%)。
    • 学習中(特訓): 何万枚もの画像を見せ、正解とのズレを確認します。
      • 「あ、ここに線があるときは7じゃないんだな」と、重みを少しずつ自動で書き換えていきます。
    • 学習後(熟練): 最適化された重みが完成。
      • 「7」をズバリ「7」と答えられるようになります(精度97%!)。

    この、血と汗と涙の結晶(最適化された数値の塊)を保存したファイルが model.pth なのです。


    model.pth の中身をのぞいてみると?

    実際に保存されるのは、以下のような膨大な「数値のリスト」です。

    例えば、シンプルな画像認識モデルでもこれだけの数値が含まれています。

    • 第1層の重み: 約100,000個
    • 第2層の重み: 約8,000個
    • 最終層の重み: 約600個
    • 合計:約11万個のパラメータ!

    私たちが「AIが賢くなった」と感じるのは、この11万個の数値が、正解を導き出すために完璧なバランスで整ったからに他なりません。

    5. 学習ループの共通テンプレート

    重みを調整するためのコード、一見すると複雑で「モデルごとに全部書き換えるの?」と不安になりますよね。

    結論から言うと、この流れは「黄金のテンプレート」です。

    Pythonコード

    for epoch in range(3):
        for images, labels in train_loader:
            outputs = model(images)              # ① 予測する(まずは解いてみる)
            loss = criterion(outputs, labels)    # ② 間違い度合いを計算(採点する)
    
            # --- ここからが重みを調整する「3点セット」 ---
            optimizer.zero_grad()                # ③ 前回の計算結果をリセット
            loss.backward()                      # ④ 「どう直すべきか」を計算
            optimizer.step()                     # ⑤ 実際に重みを更新!
    

    画像認識、テキスト分類、最新の生成AI……。驚くことに、PyTorchを使っている限り、重みを更新する際のこの3行はほぼ全てのモデルで共通です。

    # どんなAIでも共通の「お作法」
    optimizer.zero_grad()  # 1. 過去を忘れる(リセット)
    loss.backward()        # 2. 原因を突き止める(計算)
    optimizer.step()       # 3. 実行に移す(更新)

    この3行、丸暗記してOKです。

    モデル学習時のコマンドで「変わる部分」と「変わらない部分」

    「どこをカスタマイズすればいいの?」という疑問に答えるために、整理してみました。

    部分変わる?役割
    model(images)変わる自分が作った「AIの形」によって変化します。
    criterionたまに目的(分類か、数値の予測か)で選び分けます。
    optimizer.zero_grad()固定必須の「お掃除」工程です。
    loss.backward()固定誤差を逆流させて計算する、魔法のステップです。
    optimizer.step()固定計算結果を元に、重みをガチャンと書き換えます。

    6. 推論コード(inference.py)の書き方

    モデル(記憶)があっても、それをどう使うかという「手順書」がなければSageMakerは動きません。その役割を担うのが inference.py です。

    このファイルでは、データの入り口から出口までを4つの関数によるバトンリレーで記述します。

    リクエストが処理されるまでの流れ

    1. model_fn:起動時に一度だけ呼ばれる。S3から届いた「重み」をモデルに読み込む。
    2. input_fn:アプリから届いたデータ(JSONなど)を、AIが理解できる形(テンソル)に変換する。
    3. predict_fn:変換されたデータをモデルに投入し、予測結果を出す。
    4. output_fn:予測結果を、アプリが受け取れる形(JSONなど)に整えて送り返す。

    実践:inference.py の完全コード例

    今回は、手書き数字(MNIST)を識別するモデルを例に解説します。

    Pythonコード

    import os
    import json
    import torch
    import torch.nn as nn
    
    # ========================================================
    # ① モデル構造の定義(学習時と「全く同じ」クラスを書くのが鉄則!)
    # ========================================================
    class MNISTNet(nn.Module):
        def __init__(self):
            super().__init__()
            self.fc1 = nn.Linear(784, 128)
            self.fc2 = nn.Linear(128, 64)
            self.fc3 = nn.Linear(64, 10)
            self.relu = nn.ReLU()
    
        def forward(self, x):
            x = x.view(-1, 784)
            x = self.relu(self.fc1(x))
            x = self.relu(self.fc2(x))
            x = self.fc3(x)
            return x
    
    # ========================================================
    # ② SageMakerが呼び出す4つの関数
    # ========================================================
    
    def model_fn(model_dir):
        """記憶(重み)を脳(モデル)にセットする"""
        model = MNISTNet()
        model_path = os.path.join(model_dir, "model.pth")
        # map_location="cpu" を指定するのがデプロイ成功のコツ
        model.load_state_dict(torch.load(model_path, map_location="cpu"))
        model.eval() # 推論モードに切り替え
        return model
    
    def input_fn(request_body, content_type):
        """届いたJSONをAI用の形式に変換する"""
        if content_type == "application/json":
            data = json.loads(request_body)
            tensor = torch.tensor(data["inputs"], dtype=torch.float32)
            # 1枚のデータでも「バッチ(束)」として扱えるように次元を調整
            if tensor.dim() == 1:
                tensor = tensor.unsqueeze(0) 
            return tensor
        raise ValueError(f"対応していないデータ形式です: {content_type}")
    
    def predict_fn(input_data, model):
        """いよいよ推論実行!"""
        with torch.no_grad(): # 学習はしないので勾配計算はオフに
            output = model(input_data)
            probabilities = torch.softmax(output, dim=1)
            prediction = torch.argmax(probabilities, dim=1)
    
        return {
            "prediction": int(prediction.item()),
            "confidence": float(probabilities[0][prediction].item()),
        }
    
    def output_fn(prediction, accept):
        """結果をおしゃれに整形して返却する"""
        if accept == "application/json":
            return json.dumps(prediction)
        raise ValueError(f"対応していない出力形式です: {accept}")
    

    7. エンドポイントの作成と削除

    準備した model.tar.gz(お弁当パック)を、いよいよクラウド上の「お店(エンドポイント)」として開店させます。

    デプロイにはいくつかの方法がありますが、今回は最も直感的で、Pythonスクリプトから完結する SageMaker Python SDK を使った方法をメインに解説します。

    7-1. Pythonスクリプトで一気にデプロイする

    このコードを実行すると、AWS上にサーバーが立ち上がり、APIが公開されます。

    Pythonコード

    import sagemaker
    from sagemaker.pytorch import PyTorchModel
    
    # 1. AWSとの接続準備(セッション・権限の取得)
    sagemaker_session = sagemaker.Session()
    bucket = sagemaker_session.default_bucket() # 標準の保存先S3バケット
    role = sagemaker.get_execution_role()      # SageMakerを動かすための権限
    
    # 2. パッケージ(model.tar.gz)をS3にアップロード
    # これでAWSがあなたのモデルを読み取れるようになります
    s3_model_path = sagemaker_session.upload_data(
        path="model.tar.gz",
        bucket=bucket,
        key_prefix="mnist-pytorch/model"
    )
    
    # 3. PyTorchModelオブジェクトを作成(設定情報の登録)
    pytorch_model = PyTorchModel(
        model_data=s3_model_path,
        role=role,
        framework_version="2.0",  # 学習時と同じバージョンを指定
        py_version="py310",       # Pythonのバージョン
        entry_point="inference.py", # 先ほど作成した指示書
    )
    
    # 4. エンドポイントの作成(開店ボタン!)
    # ここでインスタンス(仮想サーバー)が起動します。数分待ちましょう。
    predictor = pytorch_model.deploy(
        initial_instance_count=1,
        instance_type="ml.t2.medium",     # 開発用なら一番安いこれで十分です
        endpoint_name="mnist-pytorch-endpoint"
    )
    
    print("デプロイ完了!APIが利用可能です。")
    

    7-2. 実際にAPIを叩いてみる(推論テスト)

    デプロイが終わると、あなたのAIは「特定のURL」を持つAPIになっています。外部からデータを投げて、結果を受け取ってみましょう。

    Pythonコード

    import boto3
    import json
    
    # SageMakerの実行環境(Runtime)に接続
    runtime = boto3.client("sagemaker-runtime")
    
    # テストデータ:28x28ピクセルの数値を並べたリスト
    test_data = {"inputs": [0.0, 0.1, 0.2, ...]} # 784個の数値データ
    
    # APIを呼び出す
    response = runtime.invoke_endpoint(
        EndpointName="mnist-pytorch-endpoint",
        ContentType="application/json",
        Body=json.dumps(test_data)
    )
    
    # 結果を受け取る
    result = json.loads(response["Body"].read().decode())
    print(f"予測結果: {result['prediction']}, 確信度: {result['confidence']:.2%}")
    

    レスポンス例:

    {
    “prediction”: 7,
    “confidence”: 0.98
    }

    「7です!98%の確率で合ってます!」という返事が返ってきました。これで、あなたのAIアプリの心臓部が完成しました!


    7-3. 【超重要】使い終わったら必ず削除する

    SageMakerのエンドポイントは、リクエストの有無にかかわらず「起動している時間」に対して課金されます。検証が終わったら、必ず削除しましょう。

    • Pythonから削除: predictor.delete_endpoint()
    • AWSコンソールから削除: 「推論」→「エンドポイント」から対象を選んで削除。

    料金の目安(東京リージョン):

    インスタンスタイプ主な用途料金目安
    ml.t2.mediumテスト・開発用~$0.05 / 時間
    ml.m5.large本番運用(軽量)~$0.12 / 時間
    ml.c5.xlargeCPU推論(高速)~$0.20 / 時間
    ml.g4dn.xlargeGPU推論(画像・動画等)~$0.70 / 時間

    「消し忘れ」はクラウド開発で最も多い失敗です。「使い終わったら即削除」を習慣にしましょう。


    まとめ

    お疲れ様でした!Day 7の内容を振り返りましょう。

    1. 重み(model.pth)を保存する:AIの「記憶」を記録する
    2. 指示書(inference.py)を書く:AIの「使い方」を定義する
    3. パッケージ化してデプロイ:世界中から呼び出せるAPIにする

    「AIアプリを作りたいけれど、デプロイが難しそう」と感じていたハードルは、もう乗り越えられました。あとは、このAPIをあなたのフロントエンド(Webやモバイル)と繋ぐだけです。

    タイトルとURLをコピーしました