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

Platform For AI:モデルギャラリー クイックスタート

最終更新日:May 08, 2026

モデルギャラリーは、PAI-DLC および PAI-EAS を統合し、オープンソースの大規模言語モデル (LLM) をコード不要で効率的にデプロイおよびトレーニングする方法を提供します。本トピックでは、Qwen3-0.6B モデルを例に、モデルギャラリーの使用方法を説明します。他のモデルでも同じワークフローが適用されます。

前提条件

Alibaba Cloud アカウントを使用して Platform for AI (PAI) を有効化し、ワークスペースを作成します。PAI コンソール にログインし、左上隅でリージョンを選択して、ワンクリック認可を完了します。

課金

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

モデルデプロイメント

モデルのデプロイ

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

    image

  2. デプロイメントパラメーターを設定します。デプロイメント構成ページには、デフォルトパラメーターが事前に設定されています。Deploy > Confirm をクリックします。デプロイメントには約 5 分かかります。ステータスが In operation に変わると、デプロイメントが完了します。

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

    image

モデルの呼び出し

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

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

    image

  2. モデルサービスをテストします。以下のいずれかの方法でモデルを呼び出すことができます。

    オンラインデバッグ

    Online Debugging タブに切り替えます。大規模言語モデルサービスは、Conversation Debugging および API Debugging をサポートしています。

    image

    image

    Cherry Studio クライアント

    Cherry Studio は、大規模言語モデルとの対話に広く使われているクライアントです。MCP 機能を備えており、簡単に大規模モデルとチャットできます。

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

    Python SDK

    from openai import OpenAI
    import os
    
    # 環境変数が設定されていない場合は、次の行をご利用の EAS サービストークンに置き換えてください: token = '<YOUR_EAS_SERVICE_TOKEN>'
    token = os.environ.get("Token")
    # インターネットエンドポイントの末尾にある "/v1" は削除しないでください。
    client = OpenAI(
        api_key=token,
        base_url=f'<YOUR_INTERNET_ENDPOINT>/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% を超えることができます。

受信者住所情報の例

構造化情報の例

アミーナ・パテル - 電話番号 (474) 598-1543 - 1425 S 5th St, Apt 3B, Allentown, Pennsylvania 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 データに抽出します。この JSON データの生成には時間がかかる場合があります。そのため、本ガイドではサンプルの訓練データセットtrain.json および検証データセットeval.json を提供しています。

モデル蒸留では、大規模モデルは教師モデルとも呼ばれます。本ガイドで使用するデータは大規模モデルによって合成的に生成されており、機密性のあるユーザー情報は一切含まれていません。

本番環境に適用

このソリューションをビジネスに適用するには、以下の方法でデータを準備することを推奨します。

実際のビジネスシナリオ (推奨)

実際のビジネスデータはビジネスシナリオをより正確に反映し、ファインチューニング後のモデルをビジネスにより適応させることができます。ビジネスデータを取得したら、それを以下の形式の JSON ファイルにプログラムで変換する必要があります。

[
    {
        "instruction": "You are an expert assistant for extracting structured JSON from US shipping information. The JSON keys are name, street_address, city, state, zip_code, and 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": "You are an expert assistant for extracting structured JSON from US shipping information. The JSON keys are name, street_address, city, state, zip_code, and 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 = [
    "To: {name}", "Recipient: {name}", "Deliver to {name}", "For: {name}",
    "ATTN: {name}", "{name}", "Name: {name}", "Contact: {name}", "Receiver: {name}"
]

# 電話番号テンプレート。
phone_templates = [
    "Tel: {phone}", "Tel. {phone}", "Mobile: {phone}", "Phone: {phone}",
    "Contact number: {phone}", "Phone number {phone}", "TEL: {phone}", "MOBILE: {phone}",
    "Contact: {phone}", "P: {phone}", "{phone}", "Call: {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"""Please generate recipient information for a location in {state}, USA, including the following:
1. A realistic full English name. Aim for diversity.
2. A real city name within that state.
3. A specific street address, such as street number and name, and apartment number. It should be realistic.
4. A corresponding 5-digit ZIP code for that city or area.

Please return only the JSON object in the following format:
{{"name": "Recipient Name", "city": "City Name", "street_address": "Specific Street Address", "zip_code": "ZIP Code"}}

Do not include any other text, just the JSON. Ensure names are diverse, not just 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"Failed to generate recipient and address: {e}, using fallback.")
        # フォールバックメカニズム。
        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"Starting to generate {count} records...")

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

    async def generate_single_record(index):
        async with semaphore:
            try:
                record = await generate_record()
                print(f"Generated record #{index + 1}: {record}")
                return record
            except Exception as e:
                print(f"Failed to generate record #{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"Data has been saved to {filename}")


# フェーズ 1: データ生成。
async def produce_data_phase():
    """生の受信者データの生成を処理します。"""
    print("=== Phase 1: Starting Raw Recipient Data Generation ===")

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

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

    print(f"\nTotal records generated: {len(data)}")
    print("\nSample Data:")
    for i, record in enumerate(data[:3]):  # 最初の 3 件を例として表示します。
        print(f"{i + 1}. Raw Data: {record}\n")

    print("=== Phase 1 Complete ===\n")
    return True


# 抽出モデル用のシステムプロンプトを定義します。
def get_system_prompt_for_extraction():
    """情報抽出タスク用のシステムプロンプトを返します。"""
    return """You are a professional information extraction assistant specializing in parsing US shipping addresses from unstructured text.

## Task Description
Based on the given input text, accurately extract and generate a JSON object containing the following six fields:
- name: The full name of the recipient.
- street_address: The complete street address, including number, street name, and any apartment or suite number.
- city: The city name.
- state: The full state name (e.g., "California", not "CA").
- zip_code: The 5 or 9-digit ZIP code.
- phone: The complete contact phone number.

## Extraction Rules
1.  **Address Handling**:
    -   Accurately identify the components: street, city, state, and ZIP code.
    -   The `state` field must be the full official name (e.g., "New York", not "NY").
    -   The `street_address` should contain all details before the city, such as "123 Apple Lane, Apt 4B".
2.  **Name Identification**:
    -   Extract the full recipient name.
3.  **Phone Number Handling**:
    -   Extract the complete phone number, preserving its original format.
4.  **ZIP Code**:
    -   Extract the 5-digit or 9-digit (ZIP+4) code.

## Output Format
Strictly adhere to the following JSON format. Do not add any explanatory text or markdown.
{
  "name": "Recipient's Full Name",
  "street_address": "Complete Street Address",
  "city": "City Name",
  "state": "Full State Name",
  "zip_code": "ZIP Code",
  "phone": "Contact Phone Number"
}
"""


# LLM を使用して生テキストから構造化データを予測します。
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"Failed to predict structured data: {e}, Raw data: {raw_data}")
        # 失敗時に空の構造を返します。
        return {
            "name": "",
            "street_address": "",
            "city": "",
            "state": "",
            "zip_code": "",
            "phone": ""
        }


# フェーズ 2: データ変換。
async def convert_data_phase():
    """生データを読み込み、構造化形式を予測し、SFT データとして保存します。"""
    print("=== Phase 2: Starting Data Conversion to SFT Format ===")

    try:
        print("Reading us_recipient_data.json file...")
        with open('us_recipient_data.json', 'r', encoding='utf-8') as f:
            raw_data_list = json.load(f)

        print(f"Successfully read {len(raw_data_list)} records.")
        print("Starting to predict structured data using the extraction model...")

        # 単純で明確なシステムメッセージは、トレーニングおよび推論速度を向上させます。
        system_prompt = "You are an expert assistant for extracting structured JSON from US shipping information. The JSON keys are name, street_address, city, state, zip_code, and 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"Processing record #{index + 1}: {raw_data}")

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

                return conversation

        print(f"Starting conversion to {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"Conversion complete! Processed {len(raw_data_list)} records.")
        print(f"Output file: {output_file}")
        print("=== Phase 2 Complete ===")

    except FileNotFoundError:
        print("Error: us_recipient_data.json not found.")
        sys.exit(1)
    except json.JSONDecodeError as e:
        print(f"JSON decoding error: {e}")
        sys.exit(1)
    except Exception as e:
        print(f"An error occurred during conversion: {e}")
        sys.exit(1)


# メイン関数。
async def main():
    print("Starting the data processing pipeline...")
    print("This program will execute two phases in sequence:")
    print("1. Generate raw US recipient data.")
    print("2. Predict structured data and convert it to SFT format.")
    print("-" * 50)

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

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

        print("\n" + "=" * 50)
        print("All processes completed successfully!")
        print("Generated files:")
        print("- us_recipient_data.json: Raw, unstructured data list.")
        print("- us_recipient_sft_data.json: SFT-formatted training data.")
        print("=" * 50)
    else:
        print("Data generation phase failed. Terminating.")


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

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

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

  1. ナビゲーションウィンドウで モデルギャラリー をクリックし、Qwen3-0.6B カードを見つけて、Fine-tune をクリックします。

    image

  2. トレーニングジョブのパラメーターを設定します。以下の主要なパラメーターのみを設定してください。他のパラメーターはデフォルト値のままにしておきます。

    • Training Mode: デフォルトは LoRA 方式を使用した SFT (教師ありファインチューニング) です。

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

      image

    • Validate dataset: まず、サンプルの検証データセット eval.json をダウンロードします。次に、Add validation dataset をクリックし、訓練データセットと同様の手順でアップロードします。

      検証データセットは、トレーニング中にモデルの未確認データに対するパフォーマンスを評価するために使用されます。
    • Model output path: デフォルトでは、サービスはファインチューニング済みモデルを OSS パスに保存します。宛先フォルダーが存在しない場合は、Create folder をクリックしてフォルダー名を指定します。

    • Resource Group Type: Public Resource Group を選択します。このファインチューニングジョブには約 5 GB の GPU メモリが必要です。コンソールはこの要件を満たすインスタンスタイプを自動的にフィルターします。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 の GPU メモリが必要です。Instance Type では、この要件を満たす仕様のみが表示されます。ecs.gn7i-c8g1.2xlarge などのオプションを選択し、他のパラメーターはデフォルト値のままにして、Deploy > OK をクリックします。

デプロイには約 5 分かかります。ステータスが Running に変わると、デプロイが成功します。

後でトレーニングジョブの詳細を確認するには、左側のナビゲーションウィンドウで Model Gallery > Job Management > Training Jobs をクリックし、ジョブ名をクリックします。

image

[デプロイ] ボタンがトレーニングジョブ完了後に無効になっている場合、これは出力モデルがまだ登録中のことを意味します。約 1 分お待ちください。

image

モデルを呼び出すには、「モデルの呼び出し」の手順に従ってください。

ファインチューニング済みモデルの評価

ファインチューニング済みモデルを本番環境にデプロイする前に、そのパフォーマンスを評価する必要があります。この評価により、モデルが安定しており精度が高いことを確認でき、デプロイ後の予期しない問題を防止するのに役立ちます。

テストデータの準備

モデルのパフォーマンスを評価するために、訓練データと重複しないテストデータセットを準備します。以下の精度テストコードは、必要なテストセットを自動的にダウンロードします。

テストデータには訓練データのサンプルが含まれてはなりません。これにより、新しいデータに対するモデルの汎化能力を正確に評価でき、サンプルの記憶による精度の水増しを防ぎます。

評価指標の設計

評価基準はビジネス目標と一致している必要があります。このユースケースでは、出力が有効な JSON 文字列であることを確認するだけでなく、キーと値のペアが正しいことも確認します。

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

モデルパフォーマンスの評価

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

モデル精度テストのサンプルコード

注: プレースホルダーのトークンおよびエンドポイントを、取得した実際の値に置き換えてください。

# 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_ENDPOINT>/v1',
)

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

system_prompt = """You are a professional information extraction assistant specializing in parsing US shipping addresses from unstructured text.

## Task Description
Based on the given input text, accurately extract and generate a JSON object containing the following six fields:
- name: The full name of the recipient.
- street_address: The complete street address, including number, street name, and any apartment or suite number.
- city: The city name.
- state: The full state name (e.g., "California", not "CA").
- zip_code: The 5 or 9-digit ZIP code.
- phone: The complete contact phone number.

## Extraction Rules
1.  **Address Handling**:
    -   Accurately identify the components: street, city, state, and ZIP code.
    -   The `state` field must be the full official name (e.g., "New York", not "NY").
    -   The `street_address` should contain all details before the city, such as "123 Apple Lane, Apt 4B".
2.  **Name Identification**:
    -   Extract the full recipient name.
3.  **Phone Number Handling**:
    -   Extract the complete phone number, preserving its original format.
4.  **ZIP Code**:
    -   Extract the 5-digit or 9-digit (ZIP+4) code.

## Output Format
Strictly adhere to the following JSON format. Do not add any explanatory text or markdown.
{
  "name": "Recipient's Full Name",
  "street_address": "Complete Street Address",
  "city": "City Name",
  "state": "Full State Name",
  "zip_code": "ZIP Code",
  "phone": "Contact Phone Number"
}
"""


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 = 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  # バッチあたりの処理アイテム数

    # テストデータを読み込みます
    url = 'https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/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

参照

  • モデルギャラリーの評価や圧縮などの機能の詳細については、「モデルギャラリー」をご参照ください。

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