コンテンツにスキップ

第07回 プロンプトエンジニアリング

科目: ビッグデータ応用Ⅱ〔特徴量エンジニアリング〕
担当: 小野原彩香
対象: 3年生(後期)


学習目標

  1. ゼロショット・few-shot・chain-of-thought の各プロンプト手法の違いを説明できる
  2. 地理空間処理コマンド(GDAL/OGR)を生成するためのプロンプト設計ができる
  3. 曖昧なプロンプトを具体的・正確なプロンプトに改善できる
  4. 生成されたコマンドをドライランや小規模データで安全に検証できる
  5. Anthropic Claude APIを使ってコマンド生成スクリプトをPythonで実装できる

1. LLMによるコマンド生成の可能性と限界

1.1 LLMがコマンド生成に向いている理由

大規模言語モデル(LLM)は、大量のドキュメントやコードから学習しているため、CLIコマンドの構文やオプションを幅広く知っています。特に GDAL(gdal_grid, ogr2ogr, gdalwarp など)のように、オプションが多く、初学者にはドキュメントの読み解きが難しいツールに対して、自然言語で処理内容を説明するだけでコマンドを提案させることができます。

1.2 LLMのコマンド生成における限界

問題 具体例
古い情報への依存 古いAPIの構文を生成する(deprecated オプション)
実行環境への無知 ファイルパスやCRSの値を想像で補完する
エラー処理の不備 上書き確認や NoData 値などを省略する
あいまいな指示への過剰解釈 「変換して」という指示を勝手に補完する
ハルシネーション 存在しないオプション名を生成する

これらの問題を最小化するのがプロンプトエンジニアリングの役割です。

1.3 LLM活用の基本姿勢

LLMが生成したコマンドは「提案」であり、そのまま実行してはいけない。
必ずドキュメントや小規模データでの動作確認を行う。


2. プロンプトエンジニアリングの基本手法

2.1 ゼロショット(Zero-shot)

事例(例示)を与えず、タスクだけを指示する方法。シンプルなタスクに向いています。

プロンプト例:
ogr2ogr を使って GeoJSON ファイルを EPSG:32654 に変換するコマンドを書いてください。

出力例:

ogr2ogr -f GeoJSON -t_srs EPSG:32654 output.geojson input.geojson

複雑な処理や精度が必要な場合は、以下の手法を組み合わせると効果的です。

2.2 few-shot(フューショット)

類似の例(デモンストレーション)をいくつか示してから、本命のタスクを依頼する方法。LLMに「期待する出力形式」を学ばせます。

プロンプト例:

以下の例を参考に、指示通りのogr2ogrコマンドを生成してください。

例1:
指示: GeoJSONをシェープファイルに変換したい(CRSはWGS84のまま)
コマンド: ogr2ogr -f "ESRI Shapefile" output.shp input.geojson

例2:
指示: CSVファイルをGeoJSONに変換したい(lon列, lat列がある)
コマンド: ogr2ogr -f GeoJSON output.geojson input.csv -oo X_POSSIBLE_NAMES=lon -oo Y_POSSIBLE_NAMES=lat -a_srs EPSG:4326

---
指示: GeoJSONをEPSG:32654(WGS84 UTM Zone 54N)に投影変換し、
      属性を保持したまま新しいGeoJSONとして出力したい
コマンド:

2.3 Chain-of-Thought(CoT、思考の連鎖)

「ステップごとに考えてください」と指示することで、LLMが推論プロセスを明示しながら回答する方法。複雑なコマンドの組み立てに有効です。

プロンプト例:

以下のタスクを実行するためのogr2ogrコマンドを、ステップごとに考えながら生成してください。

タスク:
- 入力ファイル: access_log.csv
- lon列(経度)、lat列(緯度)がある
- CRSはWGS84(EPSG:4326)とする
- 北海道の範囲(bbox: 139.3, 41.3, 148.9, 45.6)でクリップしたい
- 出力: hokkaido_access.geojson

考え方:
1. CSVをGeoJSONに変換するためのオプションを選ぶ
2. 投影法(CRS)を設定する
3. バウンディングボックスでクリップするオプションを追加する
4. 出力形式を指定する

コマンド:

3. 地理空間処理への適用

3.1 曖昧なプロンプトの改善例(Before/After)

Before(曖昧な指示)

ogr2ogrで変換したい

この指示では LLM は入力形式・出力形式・CRS・ファイルパスなどを推測しなければならず、的外れなコマンドが生成される可能性が高い。


After(具体的な指示)

以下の条件でogr2ogrコマンドを生成してください:

- 入力ファイル: data/llm_access_log.geojson(WGS84, EPSG:4326)
- 処理内容: 投影変換(EPSG:4326 → EPSG:32654)
- 出力ファイル: output/llm_access_utm.geojson
- 出力形式: GeoJSON
- 既存ファイルがある場合は上書きする
- コマンドのみ出力し、説明は不要

出力例(After):

ogr2ogr \
  -f GeoJSON \
  -t_srs EPSG:32654 \
  -overwrite \
  output/llm_access_utm.geojson \
  data/llm_access_log.geojson


改善のポイント整理

項目 Before(悪い例) After(良い例)
入力ファイル 指定なし ファイルパスとCRSを明記
処理内容 「変換したい」 具体的な処理(投影変換)を記述
出力ファイル 指定なし ファイルパスを明記
安全オプション 考慮なし -overwrite を指定
出力形式の指示 なし 「コマンドのみ」と明記

3.2 GDALコマンド生成に特化したシステムプロンプト

LLMに「GDAL専門家」としての役割を与えると、出力の精度が上がります。

あなたはGDALとOGRの専門家です。以下のルールに従ってコマンドを生成してください:

ルール:
1. コマンドのみを出力し、説明文は書かない
2. ファイルパスは必ずダブルクォートで囲む
3. 出力ファイルが既存の場合は -overwrite オプションを追加する
4. CRSは必ず EPSG コードで指定する
5. NoData 値が必要な場合は明示的に指定する
6. 長いコマンドはバックスラッシュで複数行に分割する
7. 不明な点はコマンドにコメントとして示す(# コメント)

以下の処理コマンドを生成してください:
[ここにタスク内容を記述]

4. 生成コマンドの検証方法

4.1 ドライランの活用

実際にファイルを生成せずに、コマンドの動作を確認します。

# ogr2ogr にはドライランオプションがない(一部ツールには --dry-run がある)
# 代わりに、出力先を /dev/null(nullデバイス)に向ける
ogr2ogr \
  -f GeoJSON \
  -t_srs EPSG:32654 \
  /vsistdout/ \    # 標準出力に出力(ファイルを作らない)
  data/llm_access_log.geojson \
  | head -c 1000   # 最初の1000バイトだけ確認

# または、一時ファイルに出力して確認
ogr2ogr \
  -f GeoJSON \
  -t_srs EPSG:32654 \
  /tmp/test_output.geojson \
  data/llm_access_log.geojson

# 出力が正しいか確認
ogrinfo -al -so /tmp/test_output.geojson

4.2 小規模データでの検証

# 元データから最初の100件だけ抽出してテスト
ogr2ogr \
  -f GeoJSON \
  -sql "SELECT * FROM llm_access_log LIMIT 100" \
  /tmp/sample_100.geojson \
  data/llm_access_log.geojson

# 小規模サンプルでコマンドをテスト
ogr2ogr \
  -f GeoJSON \
  -t_srs EPSG:32654 \
  -overwrite \
  /tmp/sample_100_utm.geojson \
  /tmp/sample_100.geojson

# 結果の確認
ogrinfo -al -so /tmp/sample_100_utm.geojson

4.3 安全に使えるオプションと確認コマンド

# ---- 安全オプション ----

# -overwrite: 出力ファイルが既存でも上書き(エラーを防ぐ)
ogr2ogr -f GeoJSON -overwrite output.geojson input.geojson

# -nln: 出力レイヤー名を明示(デフォルトはファイル名)
ogr2ogr -f GeoJSON -nln my_layer output.geojson input.geojson

# -progress: 進捗表示(大規模データで役立つ)
ogr2ogr -f GeoJSON -progress output.geojson input.geojson

# ---- 確認コマンド ----

# ogrinfo: ファイルの構造・属性を確認
ogrinfo -al -so output.geojson

# gdalinfo: ラスターファイルの情報確認
gdalinfo output.tif

# Python で件数と属性を確認
python3 -c "
import json
with open('output.geojson') as f:
    gj = json.load(f)
n = len(gj['features'])
keys = list(gj['features'][0]['properties'].keys()) if n > 0 else []
print(f'件数: {n}')
print(f'属性: {keys}')
"

5. ハンズオン手順

事前準備

# Anthropic Python SDK のインストール
pip install anthropic

# APIキーの設定(環境変数)
export ANTHROPIC_API_KEY="your-api-key-here"

# キーが設定されているか確認
echo $ANTHROPIC_API_KEY

ステップ1: シンプルなコマンド生成

次のセクション(6. 演習課題)のPythonスクリプトを使って、まず1件のコマンドを生成してみます。

import anthropic

client = anthropic.Anthropic()

# 最もシンプルなコマンド生成
message = client.messages.create(
    model="claude-opus-4-5",
    max_tokens=256,
    messages=[
        {
            "role": "user",
            "content": "ogr2ogr を使って input.geojson を EPSG:32654 に変換し output.geojson に保存するコマンドを1行で書いてください。コマンドのみ出力してください。"
        }
    ]
)

print(message.content[0].text)

ステップ2: プロンプトの改善実験

# システムプロンプトを加えて精度を比較する
prompts = {
    "ゼロショット": "GeoJSONをUTM54Nに変換するogr2ogrコマンドを書いてください。",
    "詳細指定": """
以下の条件でogr2ogrコマンドを生成してください:
- 入力: data/llm_access_log.geojson(EPSG:4326)
- 変換先: EPSG:32654(WGS84 / UTM Zone 54N)
- 出力: output/llm_access_utm.geojson
- 既存ファイルは上書き
- コマンドのみ出力
""",
}

for label, prompt in prompts.items():
    print(f"\n=== {label} ===")
    msg = client.messages.create(
        model="claude-opus-4-5",
        max_tokens=256,
        messages=[{"role": "user", "content": prompt}]
    )
    print(msg.content[0].text)

ステップ3: 生成コマンドの実行と検証

# ステップ2で生成されたコマンドを実行する前に検証
# まずサンプルデータで試す
ogr2ogr \
  -f GeoJSON \
  -t_srs EPSG:32654 \
  -overwrite \
  /tmp/test_utm.geojson \
  data/sample_100.geojson

# 結果確認
ogrinfo -al -so /tmp/test_utm.geojson

6. 演習課題

この演習は評価対象ではありませんが、第08回の課題の前提となります。

Anthropic Claude APIを使ったコマンド生成スクリプト

以下は、地理空間処理タスクをテキストで入力し、ogr2ogr / gdal_* コマンドを自動生成するPythonスクリプトの例です。

#!/usr/bin/env python3
"""
geospatial_command_generator.py
LLMを使った地理空間処理コマンド生成スクリプト

使用方法:
    python geospatial_command_generator.py "GeoJSONをUTM54Nに変換したい"
"""

import sys
import anthropic

# ----- システムプロンプト(GDAL専門家のロール) -----
SYSTEM_PROMPT = """あなたはGDAL/OGRの専門家です。
ユーザーの指示に従い、適切なGDAL/OGRコマンドを生成してください。

必ず以下のルールを守ってください:
1. コマンドのみを出力し、説明文・前置き・コードブロック記号(```)は書かない
2. ファイルパスは必ずダブルクォートで囲む
3. 出力ファイルが存在する場合に備えて -overwrite オプションを付ける
4. CRSは EPSG コードで指定する
5. 入力ファイルが指定されていない場合は "input.geojson" をデフォルト値とする
6. 出力ファイルが指定されていない場合は "output.geojson" をデフォルト値とする
7. コマンドが長い場合はバックスラッシュで改行する"""

# ----- Few-shot 例(モデルへの参考例) -----
FEW_SHOT_EXAMPLES = [
    {
        "role": "user",
        "content": "GeoJSONをEPSG:32654に投影変換したい(入力: data/points.geojson, 出力: output/points_utm.geojson)"
    },
    {
        "role": "assistant",
        "content": 'ogr2ogr \\\n  -f GeoJSON \\\n  -t_srs EPSG:32654 \\\n  -overwrite \\\n  "output/points_utm.geojson" \\\n  "data/points.geojson"'
    },
    {
        "role": "user",
        "content": "CSVファイルをGeoJSONに変換したい(入力: log.csv, lon列, lat列あり, CRS: EPSG:4326)"
    },
    {
        "role": "assistant",
        "content": 'ogr2ogr \\\n  -f GeoJSON \\\n  -overwrite \\\n  -oo X_POSSIBLE_NAMES=lon \\\n  -oo Y_POSSIBLE_NAMES=lat \\\n  -a_srs EPSG:4326 \\\n  "output.geojson" \\\n  "log.csv"'
    }
]


def generate_command(task_description: str, verbose: bool = False) -> str:
    """
    地理空間処理タスクの説明を受け取り、GDALコマンドを生成して返す。

    Args:
        task_description: 処理内容の自然言語説明
        verbose: Trueの場合、APIの詳細情報を出力する

    Returns:
        生成されたGDALコマンド文字列
    """
    client = anthropic.Anthropic()

    # few-shot の例 + 今回のタスクを組み合わせる
    messages = FEW_SHOT_EXAMPLES + [
        {"role": "user", "content": task_description}
    ]

    response = client.messages.create(
        model="claude-opus-4-5",
        max_tokens=512,
        system=SYSTEM_PROMPT,
        messages=messages
    )

    generated_command = response.content[0].text.strip()

    if verbose:
        print(f"[使用トークン] 入力: {response.usage.input_tokens}, 出力: {response.usage.output_tokens}")
        print(f"[停止理由] {response.stop_reason}")

    return generated_command


def validate_command_dry_run(command: str) -> None:
    """
    生成されたコマンドをドライラン的に検証する(構文チェックのみ)。
    """
    import shlex
    try:
        tokens = shlex.split(command.replace("\\\n", " "))
        print(f"[検証] コマンドトークン数: {len(tokens)}")
        print(f"[検証] 実行ファイル: {tokens[0]}")
        print(f"[検証] オプション一覧: {[t for t in tokens if t.startswith('-')]}")
    except ValueError as e:
        print(f"[警告] コマンドの構文解析に失敗: {e}")


def main():
    if len(sys.argv) < 2:
        print("使用方法: python geospatial_command_generator.py \"タスクの説明\"")
        sys.exit(1)

    task = sys.argv[1]
    print(f"タスク: {task}\n")
    print("コマンドを生成中...\n")

    command = generate_command(task, verbose=True)

    print("\n=== 生成されたコマンド ===")
    print(command)
    print("\n=== コマンド検証 ===")
    validate_command_dry_run(command)
    print("\n注意: 必ず小規模データで動作確認してから本番データに適用してください。")


if __name__ == "__main__":
    main()

実行例:

# インタラクティブにコマンドを生成
python geospatial_command_generator.py \
  "llm_access_log.geojson を北海道の範囲(bbox: 139.3,41.3,148.9,45.6)でクリップしてEPSG:32654に変換したい"

出力例:

タスク: llm_access_log.geojson を北海道の範囲でクリップしてEPSG:32654に変換したい

[使用トークン] 入力: 389, 出力: 87
[停止理由] end_turn

=== 生成されたコマンド ===
ogr2ogr \
  -f GeoJSON \
  -t_srs EPSG:32654 \
  -overwrite \
  -clipsrc 139.3 41.3 148.9 45.6 \
  "output/hokkaido_utm.geojson" \
  "data/llm_access_log.geojson"

=== コマンド検証 ===
[検証] コマンドトークン数: 10
[検証] 実行ファイル: ogr2ogr
[検証] オプション一覧: ['-f', '-t_srs', '-overwrite', '-clipsrc']

注意: 必ず小規模データで動作確認してから本番データに適用してください。


まとめ

  • ゼロショット は単純なタスクに向いているが、精度に限界がある
  • few-shot でLLMに期待する出力形式を示すと、一貫性のある出力が得られる
  • chain-of-thought はLLMに推論プロセスを書かせることで、複雑なコマンドの精度を上げる
  • プロンプトには、入力ファイル・CRS・出力ファイル・安全オプションを具体的に書くことが最重要
  • 生成コマンドは必ず ドライラン または 小規模データ で検証してから本番に適用する
  • システムプロンプト にルールを記述することで、出力の品質を安定させられる

次回予告

第08回では、LLMを使った分析の再現性の確保方法について学びます。プロンプトのバージョン管理、ログ記録、温度パラメータの制御など、科学的な信頼性を高めるための実践的な手法を扱います。


ビッグデータ応用Ⅱ〔特徴量エンジニアリング〕 第07回 © 小野原彩香