すべてのプロダクト
Search
ドキュメントセンター

Platform For AI:Model Gallery クイックスタート

最終更新日:Feb 05, 2026

Model Gallery は PAI-DLC および PAI-EAS を統合し、コードを記述せずにオープンソースの大規模言語モデル(LLM)のデプロイと学習を効率的に実行できます。本トピックでは、Qwen3-0.6B モデルを例として、Model Gallery の利用手順を説明します。他のモデルにも同様の手順が適用されます。

前提条件

ルートアカウントを使用して PAI を有効化し、ワークスペースを作成します。 PAI コンソール にログインし、左上隅でリージョンを選択した後、ワンクリック権限付与によりプロダクトを有効化します。

課金について

本トピックの例では、パブリックリソースを使用して DLC ジョブおよび EAS サービスを作成します。これらのサービスは従量課金方式で課金されます。課金ルールの詳細については、「DLC の課金について」および「EAS の課金について」をご参照ください。

モデルのデプロイメント

モデルのデプロイメント

  1. PAI コンソール にログインします。左側のナビゲーションウィンドウで、Model Gallery をクリックします。Qwen3-0.6B タブを見つけ、Deploy をクリックします。

    image

  2. デプロイメントパラメーターを構成します。デプロイメント設定ページにはデフォルトパラメーターが事前に入力されています。Deploy > OK をクリックします。デプロイメントには約 5 分かかります。ステータスが In operation に変更された場合、デプロイメントは成功です。

    デフォルトでは、モデルは従量課金方式のパブリックリソースを使用してデプロイされます。

    image

モデルの呼び出し

  1. API 呼び出し情報を表示します。サービスタイプの詳細ページで、[API 呼び出し情報の表示] をクリックして、呼び出しエンドポイント および Token を取得します。

    後からデプロイメントタスクの詳細を表示するには、左側のナビゲーションウィンドウで Model Gallery > Job Management > Deployment Jobs をクリックし、その後 Service name をクリックします。

    image

  2. モデルサービスを試用します。サービスを呼び出す一般的な方法は以下のとおりです。

    オンラインデバッグ

    Online Debugging ページに移動すると、LLM サービスは Conversation Debugging および API Debugging をサポートします。

    image

    Cherry Studio クライアントの使用

    Cherry Studio は大規模言語モデルと連携するための人気クライアントであり、MCP 機能を備えています。これを使用することで、LLM とのチャットを簡単に実行できます。

    PAI 上でデプロイされた Qwen3 モデルへの接続

    Python SDK の使用

    from openai import OpenAI
    import os
    
    # 環境変数を設定していない場合は、次の行を EAS サービスから取得したトークンに置き換えてください:token = 'YTA1NTEzMzY3ZTY4Z******************'
    token = os.environ.get("Token")
    # YOUR_ENDPOINT_URL の末尾にある "/v1" は削除しないでください。
    client = OpenAI(
        api_key=token,
        base_url=f'YOUR_ENDPOINT_URL/v1',
    )
    
    if token is None:
        print("環境変数 Token を設定するか、token 変数に直接トークンを割り当ててください。")
        exit()
    
    query = 'Hello, who are you?'
    messages = [{'role': 'user', 'content': query}]
    
    resp = client.chat.completions.create(model='Qwen3-0.6B', messages=messages, max_tokens=512, temperature=0)
    query = messages[0]['content']
    response = resp.choices[0].message.content
    print(f'query: {query}')
    print(f'response: {response}')

重要なお知らせ

本トピックでは、モデルサービスを作成するためにパブリックリソースを使用しており、これは従量課金方式で課金されます。サービスが不要になった場合は、サービスを停止または削除して、今後の課金を回避してください

image

モデルのファインチューニング

特定のドメインにおいてモデルのパフォーマンスを向上させたい場合は、そのドメインのデータセットを用いてファインチューニングを実行できます。本セクションでは、ファインチューニングの目的および手順を例を用いて説明します。

例となるシナリオ

物流分野では、自然言語から構造化された情報(受信者名、住所、電話番号など)を抽出することがよくあります。大規模パラメーターのモデル(例:Qwen3-235B-A22B)を使用すると優れた結果が得られますが、コストが高く、処理速度も遅くなります。パフォーマンスとコストのバランスを取るため、まず大規模パラメーターのモデルを用いてデータにアノテーションを付与し、その後、このデータを用いて小規模なモデル(例:Qwen3-0.6B)をファインチューニングすることで、同じタスクにおいて同程度のパフォーマンスを実現できます。このプロセスは、モデル蒸留とも呼ばれます。

同一の構造化情報抽出タスクにおいて、元の Qwen3-0.6B モデルの精度は 50% ですが、ファインチューニング後は 90% を超える精度に達します。

例となる受信者住所情報

例となる構造化情報

Amina Patel - 電話番号 (474) 598-1543 - 米国ペンシルベニア州アレンタウン 1425 S 5th St, Apt 3B, 18104

{
    "state": "Pennsylvania",
    "city": "Allentown",
    "zip_code": "18104",
    "street_address": "1425 S 5th St, Apt 3B",
    "name": "Amina Patel",
    "phone": "(474) 598-1543"
}

データの準備

このタスクにおいて、教師モデル (Qwen3-235B-A22B) から Qwen3-0.6B へ知識を蒸留するには、まず教師モデルの API を使用して、受信元アドレス情報を構造化された JSON 形式のデータとして抽出する必要があります。このデータ生成プロセスには時間がかかる場合があります。そのため、このトピックでは、サンプルの トレーニングデータセット train.json および 検証セット eval.json を提供しており、これらは直接ダウンロードして使用できます。

モデル蒸留では、大規模パラメーターのモデルを教師モデルとも呼びます。本トピックで使用されるデータは大規模モデルによって生成されており、機密のユーザー情報は一切含まれていません。

本番環境への適用

本ソリューションを業務に適用する場合は、以下の方法でデータを準備することを推奨します。

実際の業務シナリオ(推奨)

実際の業務データは、業務シナリオをより正確に反映しており、ファインチューニングされたモデルは業務に最適化されやすくなります。業務データを取得した後、以下の形式で JSON ファイルにプログラムで変換する必要があります。

[
    {
        "instruction": "あなたは、米国の配送情報から構造化された JSON を抽出する専門アシスタントです。JSON のキーは name、street_address、city、state、zip_code、phone です。Name: Isabella Rivera Cruz | 182 Calle Luis Lloréns Torres, Apt 3B, Mayagüez, Puerto Rico 00680 | MOBILE: (640) 486-5927",
        "output": "{\"name\": \"Isabella Rivera Cruz\", \"street_address\": \"182 Calle Luis Lloréns Torres, Apt 3B\", \"city\": \"Mayagüez\", \"state\": \"Puerto Rico\", \"zip_code\": \"00680\", \"phone\": \"(640) 486-5927\"}"
    },
    {
        "instruction": "あなたは、米国の配送情報から構造化された JSON を抽出する専門アシスタントです。JSON のキーは name、street_address、city、state、zip_code、phone です。1245 Broadwater Avenue, Apt 3B, Bozeman, Montana 59715Receiver: Aisha PatelP: (429) 763-9742",
        "output": "{\"name\": \"Aisha Patel\", \"street_address\": \"1245 Broadwater Avenue, Apt 3B\", \"city\": \"Bozeman\", \"state\": \"Montana\", \"zip_code\": \"59715\", \"phone\": \"(429) 763-9742\"}"
    }
]

JSON ファイルには複数の学習サンプルが含まれており、各サンプルには instruction および output の 2 つのフィールドがあります。

  • instruction:大規模モデルの動作を指示するプロンプトと入力データを含みます。

  • output:期待される標準的な回答であり、通常は人間の専門家または qwen3-235b-a22b のような大規模モデルによって生成されます。

モデルによる生成

業務データが不足している場合、モデルを用いたデータ拡張を検討できます。これにより、データの多様性およびカバー率が向上します。ユーザーのプライバシー漏洩を防ぐため、本ソリューションではモデルを用いて仮想住所データを一括生成します。以下に参考となる生成コードを示します。

業務データ生成のシミュレーションコード

以下のコードを実行するには、Alibaba Cloud Model Studio API キーを作成する 必要があります。コードでは qwen-plus-latest を用いて業務データを生成し、qwen3-235b-a22b を用いてラベル付けを行います。

# -*- coding: utf-8 -*-
import os
import asyncio
import random
import json
import sys
from typing import List
import platform
from openai import AsyncOpenAI

# 非同期クライアントインスタンスを作成します。
# 注:このスクリプトでは DashScope 互換 API エンドポイントを使用します。
# 異なる OpenAI 互換サービスを使用する場合は、base_url を変更してください。
client = AsyncOpenAI(
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)

# 米国の州および準州のリスト。
us_states = [
    "Alabama", "Alaska", "Arizona", "Arkansas", "California", "Colorado", "Connecticut", "Delaware",
    "Florida", "Georgia", "Hawaii", "Idaho", "Illinois", "Indiana", "Iowa", "Kansas", "Kentucky",
    "Louisiana", "Maine", "Maryland", "Massachusetts", "Michigan", "Minnesota", "Mississippi",
    "Missouri", "Montana", "Nebraska", "Nevada", "New Hampshire", "New Jersey", "New Mexico",
    "New York", "North Carolina", "North Dakota", "Ohio", "Oklahoma", "Oregon", "Pennsylvania",
    "Rhode Island", "South Carolina", "South Dakota", "Tennessee", "Texas", "Utah", "Vermont",
    "Virginia", "Washington", "West Virginia", "Wisconsin", "Wyoming", "District of Columbia",
    "Puerto Rico", "Guam", "American Samoa", "U.S. Virgin Islands", "Northern Mariana Islands"
]

# 受信者テンプレート。
recipient_templates = [
    "宛先:{name}", "受信者:{name}", "配達先:{name}", "対象:{name}",
    "ATTN:{name}", "{name}", "氏名:{name}", "連絡先:{name}", "受取人:{name}"
]

# 電話番号テンプレート。
phone_templates = [
    "Tel:{phone}", "Tel. {phone}", "携帯:{phone}", "電話:{phone}",
    "連絡先電話番号:{phone}", "電話番号 {phone}", "TEL:{phone}", "MOBILE:{phone}",
    "連絡先:{phone}", "P:{phone}", "{phone}", "通話:{phone}",
]


# 米国スタイルの電話番号を生成します。
def generate_us_phone():
    """10 桁の米国電話番号を (XXX) XXX-XXXX 形式でランダムに生成します。"""
    area_code = random.randint(201, 999)  # 0xx、1xx の市外局番を避ける。
    exchange = random.randint(200, 999)
    line = random.randint(1000, 9999)
    return f"({area_code}) {exchange}-{line}"


# LLM を用いて受信者および住所情報を生成します。
async def generate_recipient_and_address_by_llm(state: str):
    """指定された州に対して、LLM を用いて受信者氏名および住所情報を生成します。"""
    prompt = f"""米国 {state} の所在地に関する受信者情報を生成してください。以下の内容を含めてください:
1. 現実的な英語のフルネーム。多様性を確保してください。
2. その州内の実在する都市名。
3. 具体的な住所(例:建物番号および通り名、アパート番号など)。現実的である必要があります。
4. その都市または地域に対応する 5 桁の ZIP コード。

以下の JSON オブジェクトのみを返してください:
{{"name": "受信者氏名", "city": "都市名", "street_address": "具体的な住所", "zip_code": "ZIP コード"}}

その他のテキストは一切含めず、JSON のみを返してください。氏名は「John Doe」のような一般的な名前だけではなく、多様なものにしてください。
"""

    try:
        response = await client.chat.completions.create(
            messages=[{"role": "user", "content": prompt}],
            model="qwen-plus-latest",
            temperature=1.5,  # 氏名および住所の多様性を高めるために温度を上げます。
        )

        result = response.choices[0].message.content.strip()
        # Markdown コードブロックマーカーをクリーンアップします。
        if result.startswith('```'):
            result = result.split('\n', 1)[1]
        if result.endswith('```'):
            result = result.rsplit('\n', 1)[0]

        # JSON の解析を試みます。
        info = json.loads(result)
        print(info)
        return info
    except Exception as e:
        print(f"受信者および住所情報の生成に失敗しました:{e}。代替プランを使用します。")
        # 代替プラン。
        backup_names = ["Michael Johnson", "Emily Williams", "David Brown", "Jessica Jones", "Christopher Davis",
                        "Sarah Miller"]
        return {
            "name": random.choice(backup_names),
            "city": "Anytown",
            "street_address": f"{random.randint(100, 9999)} Main St",
            "zip_code": f"{random.randint(10000, 99999)}"
        }


# 1 件の生データレコードを生成します。
async def generate_record():
    """米国の住所情報を含む 1 件の雑然とした文字列を生成します。"""
    # 州をランダムに選択します。
    state = random.choice(us_states)

    # LLM を用いて受信者および住所情報を生成します。
    info = await generate_recipient_and_address_by_llm(state)

    # 受信者氏名のフォーマットを設定します。
    recipient = random.choice(recipient_templates).format(name=info['name'])

    # 電話番号を生成します。
    phone = generate_us_phone()
    phone_info = random.choice(phone_templates).format(phone=phone)

    # 完全な住所行を組み立てます。
    full_address = f"{info['street_address']}, {info['city']}, {state} {info['zip_code']}"

    # すべてのコンポーネントを結合します。
    components = [recipient, phone_info, full_address]

    # コンポーネントの順序をランダムにシャッフルします。
    random.shuffle(components)

    # ランダムなセパレーターを選択します。
    separators = [' ', ', ', '; ', ' | ', '\t', ' - ', ' // ', '', '  ']
    separator = random.choice(separators)

    # コンポーネントを結合します。
    combined_data = separator.join(components)
    return combined_data.strip()


# データをバッチ単位で生成します。
async def generate_batch_data(count: int) -> List[str]:
    """指定した件数のデータレコードを生成します。"""
    print(f"{count} 件のレコードの生成を開始しています...")

    # 同時リクエスト数を制御するためのセマフォを使用します(例:最大 20 件の同時リクエスト)。
    semaphore = asyncio.Semaphore(20)

    async def generate_single_record(index):
        async with semaphore:
            try:
                record = await generate_record()
                print(f"{index + 1} 件目のレコードを生成しました:{record}")
                return record
            except Exception as e:
                print(f"{index + 1} 件目のレコードの生成に失敗しました:{e}")
                return None

    # 同時にデータを生成します。
    tasks = [generate_single_record(i) for i in range(count)]

    data = await asyncio.gather(*tasks)

    successful_data = [record for record in data if record is not None]

    return successful_data


# データをファイルに保存します。
def save_data(data: List[str], filename: str = "us_recipient_data.json"):
    """生成されたデータを JSON ファイルに保存します。"""
    with open(filename, 'w', encoding='utf-8') as f:
        json.dump(data, f, ensure_ascii=False, indent=2)
    print(f"データを {filename} に保存しました")


# フェーズ 1:データ生成。
async def produce_data_phase():
    """生の受信者データの生成を処理します。"""
    print("=== フェーズ 1:生の受信者データ生成の開始 ===")

    # 2,000 件のレコードを生成します。
    batch_size = 2000
    data = await generate_batch_data(batch_size)

    # データを保存します。
    save_data(data, "us_recipient_data.json")

    print(f"\n合計 {len(data)} 件のレコードを生成しました")
    print("\nサンプルデータ:")
    for i, record in enumerate(data[:3]):  # 最初の 3 件を例として表示します。
        print(f"{i + 1}. 生データ:{record}\n")

    print("=== フェーズ 1 完了 ===\n")
    return True


# 情報抽出タスクのシステムプロンプトを定義します。
def get_system_prompt_for_extraction():
    """情報抽出タスクのシステムプロンプトを返します。"""
    return """あなたは、非構造化テキストから米国の配送住所を解析することに特化した専門的な情報抽出アシスタントです。

## タスクの説明
与えられた入力テキストに基づき、以下の 6 つのフィールドを含む JSON オブジェクトを正確に抽出および生成します:
- name:受信者のフルネーム。
- street_address:完全な住所(建物番号、通り名、アパートまたはスイート番号を含む)。
- city:都市名。
- state:完全な州名(例:「California」、略称の「CA」ではない)。
- zip_code:5 桁または 9 桁の ZIP コード。
- phone:完全な連絡先電話番号。

## 抽出ルール
1.  **住所の取り扱い**:
    -   通り、都市、州、ZIP コードの構成要素を正確に識別します。
    -   `state` フィールドには完全な正式名称(例:「New York」、略称の「NY」ではない)を使用します。
    -   `street_address` には、都市の前にあるすべての詳細(例:「123 Apple Lane, Apt 4B」)を含める必要があります。
2.  **氏名の識別**:
    -   受信者のフルネームを抽出します。
3.  **電話番号の取り扱い**:
    -   完全な電話番号を抽出し、元の形式を保持します。
4.  **ZIP コード**:
    -   5 桁または 9 桁(ZIP+4)のコードを抽出します。

## 出力形式
以下の JSON 形式を厳密に遵守してください。説明文や Markdown は一切追加しないでください。
{
  "name": "受信者のフルネーム",
  "street_address": "完全な住所",
  "city": "都市名",
  "state": "完全な州名",
  "zip_code": "ZIP コード",
  "phone": "連絡先電話番号"
}
"""


# 生の文字列から構造化データを予測します。
async def predict_structured_data(raw_data: str):
    """LLM を用いて生の文字列から構造化データを予測します。"""
    system_prompt = get_system_prompt_for_extraction()

    try:
        response = await client.chat.completions.create(
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": raw_data}
            ],
            model="qwen3-235b-a22b",  # このタスクには強力なモデルが推奨されます。
            temperature=0.0,  # 抽出の精度を高めるために温度を下げます。
            response_format={"type": "json_object"},
            extra_body={"enable_thinking": False}
        )

        result = response.choices[0].message.content.strip()

        # Markdown コードブロックマーカーをクリーンアップします。
        if result.startswith('```'):
            lines = result.split('\n')
            for i, line in enumerate(lines):
                if line.strip().startswith('{'):
                    result = '\n'.join(lines[i:])
                    break
        if result.endswith('```'):
            result = result.rsplit('\n```', 1)[0]

        structured_data = json.loads(result)
        return structured_data

    except Exception as e:
        print(f"構造化データの予測に失敗しました:{e}、生データ:{raw_data}")
        # 失敗時には空の構造を返します。
        return {
            "name": "",
            "street_address": "",
            "city": "",
            "state": "",
            "zip_code": "",
            "phone": ""
        }


# フェーズ 2:データ変換。
async def convert_data_phase():
    """生データを読み込み、構造化形式を予測し、SFT 形式に変換します。"""
    print("=== フェーズ 2:SFT 形式へのデータ変換の開始 ===")

    try:
        print("us_recipient_data.json ファイルの読み込みを開始しています...")
        with open('us_recipient_data.json', 'r', encoding='utf-8') as f:
            raw_data_list = json.load(f)

        print(f"{len(raw_data_list)} 件のレコードを正常に読み込みました。")
        print("抽出モデルを用いて構造化データの予測を開始しています...")

        # トレーニングおよび推論の速度を向上させるため、シンプルで明確なシステムメッセージを使用します。
        system_prompt = "あなたは、米国の配送情報から構造化された JSON を抽出する専門アシスタントです。JSON のキーは name、street_address、city、state、zip_code、phone です。"
        output_file = 'us_recipient_sft_data.json'

        # 同時リクエスト数を制御するためのセマフォを使用します。
        semaphore = asyncio.Semaphore(10)

        async def process_single_item(index, raw_data):
            async with (semaphore):
                structured_data = await predict_structured_data(raw_data)
                print(f"{index + 1} 件目のレコードを処理中:{raw_data}")

                conversation = {
                        "instruction": system_prompt + '  ' + raw_data,
                        "output": json.dumps(structured_data, ensure_ascii=False)
                }

                return conversation

        print(f"{output_file} への変換を開始しています...")

        tasks = [process_single_item(i, raw_data) for i, raw_data in enumerate(raw_data_list)]
        conversations = await asyncio.gather(*tasks)

        with open(output_file, 'w', encoding='utf-8') as outfile:
            json.dump(conversations, outfile, ensure_ascii=False, indent=4)

        print(f"変換が完了しました!{len(raw_data_list)} 件のレコードを処理しました。")
        print(f"出力ファイル:{output_file}")
        print("=== フェーズ 2 完了 ===")

    except FileNotFoundError:
        print("エラー:us_recipient_data.json が見つかりません。")
        sys.exit(1)
    except json.JSONDecodeError as e:
        print(f"JSON 解析エラー:{e}")
        sys.exit(1)
    except Exception as e:
        print(f"変換中にエラーが発生しました:{e}")
        sys.exit(1)


# メイン関数。
async def main():
    print("データ処理パイプラインの開始...")
    print("このプログラムは以下の 2 つのフェーズを順次実行します:")
    print("1. 生の米国受信者データを生成します。")
    print("2. 構造化データを予測し、SFT 形式に変換します。")
    print("-" * 50)

    # フェーズ 1:データの生成。
    success = await produce_data_phase()

    if success:
        # フェーズ 2:データの変換。
        await convert_data_phase()

        print("\n" + "=" * 50)
        print("すべての処理が正常に完了しました!")
        print("生成されたファイル:")
        print("- us_recipient_data.json:非構造化データの一覧。")
        print("- us_recipient_sft_data.json:SFT 形式のトレーニングデータ。")
        print("=" * 50)
    else:
        print("データ生成フェーズに失敗しました。終了します。")


if __name__ == '__main__':
    # Windows の場合、イベントループポリシーを設定します。
    if platform.system() == 'Windows':
        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

    # メインコルーチンを実行します。
    asyncio.run(main(), debug=False)

モデルのファインチューニング

  1. 左側のナビゲーションウィンドウで、Model Gallery をクリックします。Qwen3-0.6B タブを検索し、Fine-tune をクリックします。

    image

  2. トレーニングタスクのパラメーターを構成します。以下の主要なパラメーターのみを構成する必要があります。その他のパラメーターはデフォルト値のままにしてください。

    • Training Mode:デフォルトは SFT 監視付きファインチューニング で、LoRA ファインチューニング手法が使用されます。

      LoRA は、モデルのパラメーターの一部のみを修正することでトレーニングリソースを節約する効率的なモデルファインチューニング手法です。
    • Training dataset: まず、サンプルのトレーニングデータセット train.json をクリックしてダウンロードします。 次に、構成ページで OSS file or directory を選択します。 image アイコンをクリックしてバケットを選択します。 [ファイルのアップロード] をクリックして、ダウンロードしたトレーニングデータセットを OSS にアップロードします。 その後、ファイルを選択します。

      image

    • Validate dataset: まず、検証用データセットファイル および eval.json をダウンロードします。次に、Add validation dataset をクリックし、学習用データセットを設定した際と同じ手順に従ってファイルをアップロードします。

      検証セットは、トレーニング中にモデルのパフォーマンスを評価するために使用され、未学習データに対するモデルの性能を評価するのに役立ちます。
    • Model output path:デフォルトでは、ファインチューニングされたモデルは OSS に保存されます。OSS フォルダーパスが空の場合、フォルダーの作成 を実行し、パスを指定する必要があります。

    • Resource Group TypePublic Resource Group を選択します。このファインチューニングタスクには約 5 GB の VRAM が必要です。コンソールは自動的にリストをフィルターし、ecs.gn7i-c16g1.4xlarge のような適切なインスタンスタイプのみを表示します。

    • Hyperparameters

      • learning_rate:0.0005 に設定

      • num_train_epochs:4 に設定

      • per_device_train_batch_size:8 に設定

      • seq_length:512 に設定

      その後、Train > OK をクリックします。トレーニングタスクは Creating ステータスに入ります。ステータスが In operation に変更された場合、モデルのファインチューニングが開始されます。

  3. トレーニングタスクを表示し、完了を待ちます。モデルのファインチューニングには約 10 分かかります。この間、タスク詳細ページにはタスクログおよびメトリック曲線が表示されます。トレーニングが完了すると、ファインチューニングされたモデルは指定された OSS ディレクトリに保存されます。

    後からトレーニングタスクの詳細を表示するには、左側のナビゲーションウィンドウで Model Gallery > Job Management > Training Jobs をクリックし、その後タスク名をクリックします。

    image

    (任意)損失グラフに基づくハイパーパラメーターの調整によるモデルパフォーマンスの向上

    タスク詳細ページでは、train_loss 曲線(学習セットの損失を反映)および eval_loss 曲線(検証セットの損失を反映)を確認できます:

    imageimage

    損失値の傾向に基づいて、モデルのトレーニング効果を概算で判断できます:

    • トレーニング終了前:train_loss および eval_loss の両方がまだ減少中(アンダーフィッティング)

      num_train_epochs パラメーター(トレーニングエポック数、トレーニングの深さと正の相関)を増加させるか、lora_rank(低ランク行列のランク。ランクが大きいほど複雑なタスクを表現可能だが、過学習しやすくなる)の値をわずかに増加させ、再度トレーニングすることで、モデルの学習データへの適合度を向上させることができます。

    • トレーニング終了前:train_loss は引き続き減少しているが、eval_loss は増加し始めている(過学習)

      num_train_epochs パラメーターを減少させるか、lora_rank の値をわずかに減少させ、再度トレーニングすることで、モデルの過学習を防止できます。

    • トレーニング終了前:train_loss および eval_loss の両方が安定している(良好な適合)

      モデルがこの状態にある場合、次のステップに進むことができます。

ファインチューニング済みモデルのデプロイメント

トレーニングタスクの詳細ページで、Deploy ボタンをクリックしてデプロイメント設定ページを開きます。Resource Type には Public Resources を選択します。0.6B モデルのデプロイメントには約 5 GB の VRAM が必要です。コンソールは既に リソース仕様 リストをフィルターし、この要件を満たすインスタンスタイプ(例:ecs.gn7i-c8g1.2xlarge)のみを表示しています。その他のパラメーターはデフォルト値のままにし、Deploy > OK をクリックします。

デプロイメントプロセスには約 5 分かかります。ステータスが Running に変更された場合、サービスのデプロイメントは成功です。

後でトレーニングタスクの詳細を表示するには、左側のナビゲーションウィンドウで Model GalleryJob ManagementTraining Jobs をクリックし、その後タスク名をクリックします。

イメージ

トレーニングタスクが成功したと表示された後、[デプロイ] ボタンがクリックできない場合は、出力モデルの登録処理がまだ進行中であることを意味します。約 1 分待つ必要があります。

image

モデルの呼び出しに関する以降の手順は、「モデルの呼び出し」と同一です。

ファインチューニング済みモデルのパフォーマンス検証

ファインチューニング済みモデルを本番環境にデプロイする前に、その安定性および精度を保証し、本番運用後に予期せぬ問題が発生しないように、体系的にパフォーマンスを評価することを推奨します。

テストデータの準備

トレーニングデータと重複しないテストデータを準備し、モデルのパフォーマンスをテストします。本ソリューションでは、テストデータセットをあらかじめ用意しており、以下の精度テストコードを実行すると自動的にダウンロードされます。

テストデータのサンプルはトレーニングデータと重複してはいけません。これにより、モデルの新規データに対する汎化能力をより正確に反映でき、"既知のサンプル" による過大なスコアを回避できます。

評価指標の設計

評価基準は、実際のビジネス目標に密接に沿っている必要があります。本ソリューションでは、たとえば生成された JSON 文字列が有効かどうかを判定するだけでなく、対応するキーと値のペアが正しいかどうかも確認する必要があります。

評価指標はプログラムで定義する必要があります。本例における評価指標の実装については、以下の精度テストコードの compare_address_info メソッドをご参照ください。

ファインチューニング後のパフォーマンス検証

以下のテストコードを実行して、テストデータセットにおけるモデルの精度を出力します。

モデル精度テストのコード例

注:トークンおよびエンドポイント URL を、前述で取得した実際の値に置き換えてください。

# pip3 install openai
from openai import AsyncOpenAI
import requests
import json
import asyncio
import os

# 'Token' 環境変数が設定されていない場合は、次の行を EAS サービスから取得したトークンに置き換えてください:token = 'YTA1NTEzMzY3ZTY4Z******************'
token = os.environ.get("Token")

# サービス URL の後ろにある "/v1" 接尾辞は削除しないでください。
client = OpenAI(
    api_key=token,
    base_url=f'<Your_Service_URL>/v1',
)

if token is None:
    print("'Token' 環境変数を設定するか、'token' 変数に直接トークンを割り当ててください。")
    exit()

system_prompt = """あなたは、非構造化テキストから米国の配送住所を解析することに特化した専門的な情報抽出アシスタントです。

## タスクの説明
与えられた入力テキストに基づき、以下の 6 つのフィールドを含む JSON オブジェクトを正確に抽出および生成します:
- name:受信者のフルネーム。
- street_address:完全な住所(建物番号、通り名、アパートまたはスイート番号を含む)。
- city:都市名。
- state:完全な州名(例:「California」、略称の「CA」ではない)。
- zip_code:5 桁または 9 桁の ZIP コード。
- phone:完全な連絡先電話番号。

## 抽出ルール
1.  **住所の取り扱い**:
    -   通り、都市、州、ZIP コードの構成要素を正確に識別します。
    -   `state` フィールドには完全な正式名称(例:「New York」、略称の「NY」ではない)を使用します。
    -   `street_address` には、都市の前にあるすべての詳細(例:「123 Apple Lane, Apt 4B」)を含める必要があります。
2.  **氏名の識別**:
    -   受信者のフルネームを抽出します。
3.  **電話番号の取り扱い**:
    -   完全な電話番号を抽出し、元の形式を保持します。
4.  **ZIP コード**:
    -   5 桁または 9 桁(ZIP+4)のコードを抽出します。

## 出力形式
以下の JSON 形式を厳密に遵守してください。説明文や Markdown は一切追加しないでください。
{
  "name": "受信者のフルネーム",
  "street_address": "完全な住所",
  "city": "都市名",
  "state": "完全な州名",
  "zip_code": "ZIP コード",
  "phone": "連絡先電話番号"
}
"""


def compare_address_info(actual_address_str, predicted_address_str):
    """2 つの JSON 文字列で表される住所情報を比較し、同一かどうかを判定します。"""
    try:
        # 実際の住所情報を解析
        if actual_address_str:
            actual_address_json = json.loads(actual_address_str)
        else:
            actual_address_json = {}

        # 予測された住所情報を解析
        if predicted_address_str:
            predicted_address_json = json.loads(predicted_address_str)
        else:
            predicted_address_json = {}

        # 2 つの JSON オブジェクトが同一であるかを直接比較
        is_same = actual_address_json == predicted_address_json

        return {
            "is_same": is_same,
            "actual_address_parsed": actual_address_json,
            "predicted_address_parsed": predicted_address_json,
            "comparison_error": None
        }

    except json.JSONDecodeError as e:
        return {
            "is_same": False,
            "actual_address_parsed": None,
            "predicted_address_parsed": None,
            "comparison_error": f"JSON 解析エラー:{str(e)}"
        }
    except Exception as e:
        return {
            "is_same": False,
            "actual_address_parsed": None,
            "predicted_address_parsed": None,
            "comparison_error": f"比較エラー:{str(e)}"
        }


async def predict_single_conversation(conversation_data):
    """単一の会話に対するラベルを予測します。"""
    try:
        # ユーザーのコンテンツを抽出(アシスタントのメッセージを除外)
        messages = conversation_data.get("messages", [])
        user_content = None

        for message in messages:
            if message.get("role") == "user":
                user_content = message.get("content", "")
                break

        if not user_content:
            return {"error": "ユーザーのメッセージが見つかりません"}

        response = await client.chat.completions.create(
            model="Qwen3-0.6B",
            messages=[
                {"role": "system", "content": system_prompt},
                {"role": "user", "content": user_content}
            ],
            response_format={"type": "json_object"},
            extra_body={
                "enable_thinking": False
            }
        )

        predicted_labels = response.choices[0].message.content.strip()
        return {"prediction": predicted_labels}

    except Exception as e:
        return {"error": f"予測に失敗しました:{str(e)}"}


async def process_batch(batch_data, batch_id):
    """データのバッチを処理します。"""
    print(f"バッチ {batch_id} を処理中、{len(batch_data)} 件のアイテムを含みます...")

    tasks = []
    for i, conversation in enumerate(batch_data):
        task = predict_single_conversation(conversation)
        tasks.append(task)

    results = await asyncio.gather(*tasks, return_exceptions=True)

    batch_results = []
    for i, result in enumerate(results):
        if isinstance(result, Exception):
            batch_results.append({"error": f"例外:{str(result)}"})
        else:
            batch_results.append(result)

    return batch_results


async def main():
    output_file = "predicted_labels.jsonl"
    batch_size = 20  # 1 回のバッチで処理するアイテム数

    # テストデータを読み込みます
    url = 'https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/ja-JP/20251015/yghxco/test.jsonl'
    conversations = []

    try:
        response = requests.get(url)
        response.raise_for_status()  # リクエストが成功したか確認
        for line_num, line in enumerate(response.text.splitlines(), 1):
            try:
                data = json.loads(line.strip())
                conversations.append(data)
            except json.JSONDecodeError as e:
                print(f"行 {line_num} の JSON 解析エラー:{e}")
                continue
    except requests.exceptions.RequestException as e:
        print(f"リクエストエラー:{e}")
        return

    print(f"{len(conversations)} 件の会話データアイテムを正常に読み込みました")

    # バッチ単位で処理します
    all_results = []
    total_batches = (len(conversations) + batch_size - 1) // batch_size

    for batch_id in range(total_batches):
        start_idx = batch_id * batch_size
        end_idx = min((batch_id + 1) * batch_size, len(conversations))
        batch_data = conversations[start_idx:end_idx]

        batch_results = await process_batch(batch_data, batch_id + 1)
        all_results.extend(batch_results)

        print(f"バッチ {batch_id + 1}/{total_batches} が完了しました")

        # リクエストをあまりにも速く送信しないように、小さな遅延を追加
        if batch_id < total_batches - 1:
            await asyncio.sleep(1)

    # 結果を保存します
    same_count = 0
    different_count = 0
    error_count = 0

    with open(output_file, 'w', encoding='utf-8') as f:
        for i, (original_data, prediction_result) in enumerate(zip(conversations, all_results)):
            result_entry = {
                "index": i,
                "original_user_content": None,
                "actual_address": None,
                "predicted_address": None,
                "prediction_error": None,
                "address_comparison": None
            }

            # 元のユーザーのコンテンツを抽出
            messages = original_data.get("messages", [])
            for message in messages:
                if message.get("role") == "user":
                    result_entry["original_user_content"] = message.get("content", "")
                    break

            # 実際の住所情報を抽出(アシスタントのメッセージが存在する場合)
            for message in messages:
                if message.get("role") == "assistant":
                    result_entry["actual_address"] = message.get("content", "")
                    break

            # 予測結果を保存
            if "error" in prediction_result:
                result_entry["prediction_error"] = prediction_result["error"]
                error_count += 1
            else:
                result_entry["predicted_address"] = prediction_result.get("prediction", "")

                # 住所情報の比較
                comparison_result = compare_address_info(
                    result_entry["actual_address"],
                    result_entry["predicted_address"]
                )
                result_entry["address_comparison"] = comparison_result

                # 比較結果を集計
                if comparison_result["comparison_error"]:
                    error_count += 1
                elif comparison_result["is_same"]:
                    same_count += 1
                else:
                    different_count += 1

            f.write(json.dumps(result_entry, ensure_ascii=False) + '\n')

    print(f"すべての予測が完了しました!結果は {output_file} に保存されました")

    # 統計情報
    success_count = sum(1 for result in all_results if "error" not in result)
    prediction_error_count = len(all_results) - success_count
    print(f"サンプル数:{success_count}")
    print(f"正しい応答:{same_count}")
    print(f"誤った応答:{different_count}")
    print(f"精度:{same_count * 100 / success_count} %")


if __name__ == "__main__":
    asyncio.run(main())

出力:

すべての予測が完了しました!結果は predicted_labels.jsonl に保存されました
サンプル数:400
正しい応答:382
誤った応答:18
精度:95.5 %
モデルのファインチューニングにおける乱数シードおよび大規模モデルの出力のランダム性の影響により、テストで得られる精度は本ソリューションの結果と異なる場合があります。これは正常な現象です。

精度は 95.5% であり、元の Qwen3-0.6B モデルの 50% の精度と比べて大幅な向上です。これは、ファインチューニング済みモデルが物流の注文入力領域における構造化情報抽出機能を大幅に強化したことを示しています。

本チュートリアルのトレーニング時間を短縮するため、トレーニングエポック数を 4 に設定しましたが、精度はすでに 95.5% まで向上しています。さらに精度を向上させるには、トレーニングエポック数を適切に増加させることも可能です。

重要なお知らせ

本トピックでは、モデルサービスを作成するためにパブリックリソースを使用しており、これは従量課金方式で課金されます。サービスが不要になった場合は、サービスを停止または削除して、今後の課金を回避してください

image

関連ドキュメント

  • 評価および圧縮などの Model Gallery の機能の詳細については、「Model Gallery」をご参照ください。

  • Auto Scaling、ストレステスト、モニタリングおよびアラートなどの EAS の機能の詳細については、「EAS の概要」をご参照ください。