テックブログ

エルカミーの技術ブログです

【LangGraphチュートリアル解説】プロンプト生成編

関連記事

  1. LangGraphとは

はじめに


LangGraphを知らない方、わからない方は、まず「LangChainとは」をご覧ください。

この記事では、LangGraphを初めて使う方に向けて、LangGraphの公式ページに用意されているチュートリアルを解説します。今回解説するチュートリアルは「Prompt Generation from User Requirements」です。このチュートリアルでは、ユーザーからの要件に基づいて、プロンプトを生成するチャットボットを作成します。

この記事を通じて、LangGraphの基本的な使い方を理解していただき、実際の開発に役立てていただければ幸いです。

記事の対象者


  • LangGraph初学者
  • LangChain初学者

チュートリアルの概要


まず、ユーザーから作成したいプロンプトに関する情報を収集し、収集した要件に基づいてプロンプトを生成します。今回作成するシステムは以下の通りです。

image block
https://github.com/langchain-ai/langgraph/blob/main/examples/chatbots/information-gather-prompting.ipynb

チュートリアルの解説


ステップ
  1. インストール
  2. ユーザーからの要件を収集
  3. プロンプト生成部分
  4. 状態遷移の定義
  5. グラフの作成
  6. グラフの実行
1. インストール

今回のチュートリアルに必要なライブラリのインストール、APIキーの設定をします。

ライブラリのインストールは以下のコマンドを実行してください。

pip install langgraph langchain langchain-openai

次にOpenAIのAPIキーを環境変数に設定する必要があります。チュートリアルに以下のコードを追加してください。

import os

os.environ["OPENAI_API_KEY"] = "YOUR_API_KEY" 
2. ユーザーからの要件を収集

まず、ユーザーから作成したいプロンプトに関する要件を収集するためのテンプレートとクラスを定義します。ユーザーからは以下の情報を取得します。

  • プロンプトの目的
  • プロンプトテンプレートに渡す変数
  • 出力に対する制約(やってはいけないこと)
  • 出力に対する要件(守らなければならないこと)

収集した情報をもとに次のステップでプロンプトを生成します。要件を収集する機能のコードは以下通りです。

from typing import List
from langchain_core.messages import SystemMessage
from langchain_core.pydantic_v1 import BaseModel
from langchain_openai import ChatOpenAI

# ユーザーからの要件を収集するためのテンプレート
template = """Your job is to get information from a user about what type of prompt template they want to create.

You should get the following information from them:

- What the objective of the prompt is
- What variables will be passed into the prompt template
- Any constraints for what the output should NOT do
- Any requirements that the output MUST adhere to

If you are not able to discern this info, ask them to clarify! Do not attempt to wildly guess.

After you are able to discern all the information, call the relevant tool."""

def get_messages_info(messages):
    return [SystemMessage(content=template)] + messages

class PromptInstructions(BaseModel):
    """Instructions on how to prompt the LLM."""
    objective: str
    variables: List[str]
    constraints: List[str]
    requirements: List[str]

llm = ChatOpenAI(temperature=0)
llm_with_tool = llm.bind_tools([PromptInstructions])

chain = get_messages_info | llm_with_tool
コード解説
def get_messages_info(messages):
    return [SystemMessage(content=template)] + messages

get_messages_info関数は、ユーザー要求を収集するためのテンプレートメッセージを他のメッセージと組み合わせて、最終的なメッセージリストを作成することです。

このリストをLLMに渡すことで、モデルはまずテンプレートメッセージを受け取り、どのような役割をすれば良いか理解します。

class PromptInstructions(BaseModel):
    """Instructions on how to prompt the LLM."""
    objective: str
    variables: List[str]
    constraints: List[str]
    requirements: List[str]

PromptInstructionsクラスは、プロンプトテンプレートを生成する際の指示を格納するためのデータモデルです。

このクラスをLLMにツールとしてバインドすることで、メッセージのリストから objectivevariables などの属性に自動的に割り振られるようになります。

llm = ChatOpenAI(temperature=0)
llm_with_tool = llm.bind_tools([PromptInstructions])

上記のコードでLLMをツールとバインドします。これにより、LLMは PromptInstructions クラスを使用して、ユーザーから収集した情報を処理し、適切なプロンプトテンプレートを生成するための基盤が整います。

chain = get_messages_info | llm_with_tool

最後に、チェーンを定義して、メッセージ情報を処理する流れを構築します。チェーンは|を使用することで簡単に構築可能です。

3. プロンプト生成の実装

次に、収集した情報をもとにプロンプトを生成する部分を作成します。ここでは、システムメッセージとメッセージフィルタリングの関数を定義します。以下がそのコード全体です。

from langchain_core.messages import AIMessage, HumanMessage, ToolMessage

# プロンプト生成のための新しいシステムプロンプト
prompt_system = """Based on the following requirements, write a good prompt template:

{reqs}"""

# プロンプト生成のためのメッセージを取得する関数
def get_prompt_messages(messages: list):
    tool_call = None
    other_msgs = []
    for m in messages:
        if isinstance(m, AIMessage) and m.tool_calls:
            tool_call = m.tool_calls[0]["args"]
        elif isinstance(m, ToolMessage):
            continue
        elif tool_call is not None:
            other_msgs.append(m)
    return [SystemMessage(content=prompt_system.format(reqs=tool_call))] + other_msgs

prompt_gen_chain = get_prompt_messages | llm
コード解説

# プロンプト生成のためのメッセージを取得する関数
def get_prompt_messages(messages: list):
    tool_call = None
    other_msgs = []
    for m in messages:
        if isinstance(m, AIMessage) and m.tool_calls:
            tool_call = m.tool_calls[0]["args"]
        elif isinstance(m, ToolMessage):
            continue
        elif tool_call is not None:
            other_msgs.append(m)
    return [SystemMessage(content=prompt_system.format(reqs=tool_call))] + other_msgs

この関数 get_prompt_messages は、メッセージリストからツールコール後のメッセージをフィルタリングし、新しいシステムメッセージを生成します。具体的には、以下のステップで動作します

  1. tool_callを初期化します。
  2. メッセージリストをループして、AIMessageかつツールコールがある場合に、そのツールコールの引数をtool_callに格納します。
  3. ToolMessageの場合はスキップします。
  4. tool_callが既に設定されている場合、他のメッセージをother_msgsに追加します。
  5. 最後に、新しいシステムメッセージと他のメッセージをリストにして返します。

llm = ChatOpenAI(temperature=0)
prompt_gen_chain = get_prompt_messages | llm

get_prompt_messages関数で生成されたメッセージリストが、LLMに渡され、プロンプトテンプレートが生成されます。

4. 状態遷移の定義

次に、チャットボットの状態遷移を定義します。これにより、どの状態でどのアクションを実行するかを決定します。

from typing import Literal
from langgraph.graph import END

def get_state(messages) -> Literal["add_tool_message", "info", "__end__"]:
    if isinstance(messages[-1], AIMessage) and messages[-1].tool_calls:
        return "add_tool_message"
    elif not isinstance(messages[-1], HumanMessage):
        return END
    return "info"

この関数は、最後のメッセージに基づいてチャットボットの状態を決定します。ツールコールが最後のメッセージであれば「add_tool_message」、それ以外の場合は「info」または「END」となります。

5. グラフの作成

定義した状態とチェーンを使用してグラフを作成します。以下がそのコード全体です。

from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.graph import START, MessageGraph
from IPython.display import Image, display
from langchain_core.messages import ToolMessage

# メモリ内で会話履歴を保持するための設定
memory = SqliteSaver.from_conn_string(":memory:")
workflow = MessageGraph()
workflow.add_node("info", chain)
workflow.add_node("prompt", prompt_gen_chain)

@workflow.add_node
def add_tool_message(state: list):
    return ToolMessage(
        content="Prompt generated!", tool_call_id=state[-1].tool_calls[0]["id"]
    )

workflow.add_conditional_edges("info", get_state)
workflow.add_edge("add_tool_message", "prompt")
workflow.add_edge("prompt", END)
workflow.add_edge(START, "info")
graph = workflow.compile(checkpointer=memory)

display(Image(graph.get_graph().draw_mermaid_png()))
コード解説
# メモリ内で会話履歴を保持するための設定
memory = SqliteSaver.from_conn_string(":memory:")
workflow = MessageGraph()
workflow.add_node("info", chain)
workflow.add_node("prompt", prompt_gen_chain)

メモリ内で会話履歴を保持するための設定を行います。SqliteSaverを使用してインメモリのSQLiteデータベースを作成し、MessageGraphインスタンスを作成します。workflowにノードを追加して、チェーンを紐付けます。

@workflow.add_node
def add_tool_message(state: list):
    return ToolMessage(
        content="Prompt generated!", tool_call_id=state[-1].tool_calls[0]["id"]
    )

ここでは、新しいノードadd_tool_messageを作成しています。この関数は、ツールメッセージを生成し、その内容を設定します。

workflow.add_conditional_edges("info", get_state)
workflow.add_edge("add_tool_message", "prompt")
workflow.add_edge("prompt", END)
workflow.add_edge(START, "info")
graph = workflow.compile(checkpointer=memory)

最後に、状態遷移の条件付きエッジとエッジを追加します。これにより、infoノードからの遷移をget_state関数に基づいて決定し、他のノード間の遷移を設定します。最後に、ワークフローをコンパイルしてグラフを生成します。

6. グラフの実行

作成したグラフを使用してチャットボットを実行します。

import uuid
from langchain_core.messages import HumanMessage

config = {"configurable": {"thread_id": str(uuid.uuid4())}}
while True:
    user = input("User (q/Q to quit): ")
    if user in {"q", "Q"}:
        print("AI: Byebye")
        break
    output = None
    for output in graph.stream(
        [HumanMessage(content=user)], config=config, stream_mode="updates"
    ):
        last_message = next(iter(output.values()))
        last_message.pretty_print()

    if output and "prompt" in output:
        print("Done!")

このコードでは、ユーザー入力を受け取り、それに基づいてグラフを実行します。ユーザーが「q」または「Q」を入力するとチャットボットが終了します。各メッセージの出力をリアルタイムで表示し、プロンプト生成が完了したら「Done!」と表示します。

実際に使ってみる


今回はPythonコードを解説するプロンプトを作成してみたいと思います。

1. グラフの実行

作成したグラフを実行すると、ユーザの入力を求められます。「こんにちは」と入力すると以下の内容が返ってきます。

================================== Ai Message ==================================
こんにちは!どのようなプロンプトテンプレートを作成したいのか教えていただけますか?

以下の情報を教えてください:
1. プロンプトの目的
2. プロンプトテンプレートに渡す変数
3. 出力に対する制約(やってはいけないこと)
4. 出力に対する要件(守らなければならないこと)

これらの情報をもとに、最適なプロンプトテンプレートを作成します。
2. 作成したいプロンプトの要件を入力

以下の内容を入力として渡してみます。

- プロンプトの目的: Pythonのコードを分かりやすく解説する
- プロンプトテンプレートに渡す変数: Pythonのコード
- 出力に対する制約(やってはいけないこと): コードの一部分のみ解説しないでください
- 出力に対する要件(守らなければならないこと): マークダウン形式、Step by Stepで分かりやすく解説する

作成したいプロンプトの要件を渡すと以下の内容が返ってきます。

================================= Ai Message [0m==================================
Tool Calls:
  PromptInstructions (call_a00XPacxloOBCQiFTSFTcClB)
 Call ID: call_a00XPacxloOBCQiFTSFTcClB
  Args:
    objective: Pythonのコードを分かりやすく解説する
    variables: ['Pythonのコード']
    constraints: ['コードの一部分のみ解説しないでください']
    requirements: ['マークダウン形式', 'Step by Stepで分かりやすく解説する']
================================= Tool Message =================================

Prompt generated!
================================== Ai Message ==================================

以下のPythonコードを分かりやすく解説してください。解説はマークダウン形式で、ステップバイステップで行ってください。コード全体を対象に解説を行い、一部分のみの解説は避けてください。

```python
{{Pythonのコード}}
```

# 解説

1. **コードの概要**
   - ここでコード全体の目的や機能について簡単に説明します。

2. **ステップ1: [最初のステップの説明]**
   - 具体的なコードの一部分を取り上げ、その役割や動作を説明します。

3. **ステップ2: [次のステップの説明]**
   - 次に進む部分のコードを解説し、その流れを理解しやすくします。

4. **ステップ3: [さらに次のステップの説明]**
   - 続けて、次の部分のコードを解説します。

5. **ステップ4: [さらに次のステップの説明]**
   - 続けて、次の部分のコードを解説します。

6. **まとめ**
   - 最後に、解説した部分のコードが全体の中でどのような役割を果たしているかをまとめます。

このようにして、コード全体を詳細に解説してください。
Done!

上記の結果から、以下のプロンプトが生成されました。

以下のPythonコードを分かりやすく解説してください。解説はマークダウン形式で、ステップバイステップで行ってください。コード全体を対象に解説を行い、一部分のみの解説は避けてください。

```python
{{Pythonのコード}}
```

# 解説

1. **コードの概要**
   - ここでコード全体の目的や機能について簡単に説明します。

2. **ステップ1: [最初のステップの説明]**
   - 具体的なコードの一部分を取り上げ、その役割や動作を説明します。

3. **ステップ2: [次のステップの説明]**
   - 次に進む部分のコードを解説し、その流れを理解しやすくします。

4. **ステップ3: [さらに次のステップの説明]**
   - 続けて、次の部分のコードを解説します。

5. **ステップ4: [さらに次のステップの説明]**
   - 続けて、次の部分のコードを解説します。

6. **まとめ**
   - 最後に、解説した部分のコードが全体の中でどのような役割を果たしているかをまとめます。

このようにして、コード全体を詳細に解説してください。
生成されたプロンプトを使ってみる

生成されたプロンプトを実際に使用してみたいと思います。比較対象として、以下の単純なプロンプトを用意しました。これらのプロンプトをGPT-4oに入力し、出力の結果を比較してみたいと思います。

単純なプロンプト
以下のPythonコードを解説してください。
```python
{{Pythonのコード}}
```
出力結果

生成されたプロンプト

入力内容
以下のPythonコードを分かりやすく解説してください。解説はマークダウン形式で、ステップバイステップで行ってください。コード全体を対象に解説を行い、一部分のみの解説は避けてください。

```python
import optuna
import xgboost as xgb
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import accuracy_score

data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(data.data, data.target, test_size=0.2, random_state=42)

def objective(trial)
    param = {
        'verbosity': 0,
        'objective': 'binary:logistic',
        'eval_metric': 'logloss',
        'booster': trial.suggest_categorical('booster', ['gbtree', 'gblinear', 'dart']),
        'lambda': trial.suggest_loguniform('lambda', 1e-8, 1.0),
        'alpha': trial.suggest_loguniform('alpha', 1e-8, 1.0)
    }

    if param['booster'] == 'gbtree' or param['booster'] == 'dart':
        param['max_depth'] = trial.suggest_int('max_depth', 1, 9)
        param['eta'] = trial.suggest_loguniform('eta', 1e-8, 1.0)
        param['gamma'] = trial.suggest_loguniform('gamma', 1e-8, 1.0)
        param['grow_policy'] = trial.suggest_categorical('grow_policy', ['depthwise', 'lossguide'])

    if param['booster'] == 'dart':
        param['sample_type'] = trial.suggest_categorical('sample_type', ['uniform', 'weighted'])
        param['normalize_type'] = trial.suggest_categorical('normalize_type', ['tree', 'forest'])
        param['rate_drop'] = trial.suggest_loguniform('rate_drop', 1e-8, 1.0)
        param['skip_drop'] = trial.suggest_loguniform('skip_drop', 1e-8, 1.0)

    model = xgb.XGBClassifier(**param)
    model.fit(X_train, y_train)

    
    score = cross_val_score(model, X_train, y_train, cv=3, scoring='accuracy').mean()
    return score

study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=100)
print("Best trial:")
trial = study.best_trial
print("  Value: {}".format(trial.value))
print("  Params: ")
for key, value in trial.params.items():
    print("    {}: {}".format(key, value))

best_params = trial.params
best_model = xgb.XGBClassifier(**best_params)
best_model.fit(X_train, y_train)
y_pred = best_model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print("Test Accuracy: {}".format(accuracy))

```

# 解説

1. **コードの概要**
   - ここでコード全体の目的や機能について簡単に説明します。

2. **ステップ1: [最初のステップの説明]**
   - 具体的なコードの一部分を取り上げ、その役割や動作を説明します。

3. **ステップ2: [次のステップの説明]**
   - 次に進む部分のコードを解説し、その流れを理解しやすくします。

4. **ステップ3: [さらに次のステップの説明]**
   - 続けて、次の部分のコードを解説します。

5. **ステップ4: [さらに次のステップの説明]**
   - 続けて、次の部分のコードを解説します。

6. **まとめ**
   - 最後に、解説した部分のコードが全体の中でどのような役割を果たしているかをまとめます。

このようにして、コード全体を詳細に解説してください。
出力結果

Pythonコードの詳細解説

以下のコードは、Optunaを使ってXGBoostモデルのハイパーパラメータチューニングを行い、その最適なパラメータを見つけることを目的としています。具体的には、乳がんデータセットを使用して、XGBoostモデルの精度を最大化するハイパーパラメータを探します。

import optuna
import xgboost as xgb
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import accuracy_score

# データの読み込みと分割
data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(data.data, data.target, test_size=0.2, random_state=42)

# 目的関数の定義
def objective(trial):
    param = {
        'verbosity': 0,
        'objective': 'binary:logistic',
        'eval_metric': 'logloss',
        'booster': trial.suggest_categorical('booster', ['gbtree', 'gblinear', 'dart']),
        'lambda': trial.suggest_loguniform('lambda', 1e-8, 1.0),
        'alpha': trial.suggest_loguniform('alpha', 1e-8, 1.0)
    }

    if param['booster'] == 'gbtree' or param['booster'] == 'dart':
        param['max_depth'] = trial.suggest_int('max_depth', 1, 9)
        param['eta'] = trial.suggest_loguniform('eta', 1e-8, 1.0)
        param['gamma'] = trial.suggest_loguniform('gamma', 1e-8, 1.0)
        param['grow_policy'] = trial.suggest_categorical('grow_policy', ['depthwise', 'lossguide'])

    if param['booster'] == 'dart':
        param['sample_type'] = trial.suggest_categorical('sample_type', ['uniform', 'weighted'])
        param['normalize_type'] = trial.suggest_categorical('normalize_type', ['tree', 'forest'])
        param['rate_drop'] = trial.suggest_loguniform('rate_drop', 1e-8, 1.0)
        param['skip_drop'] = trial.suggest_loguniform('skip_drop', 1e-8, 1.0)

    model = xgb.XGBClassifier(**param)
    model.fit(X_train, y_train)

    score = cross_val_score(model, X_train, y_train, cv=3, scoring='accuracy').mean()
    return score

# Optunaのスタディを作成して最適化
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=100)

# 最適な試行結果の表示
print("Best trial:")
trial = study.best_trial
print("  Value: {}".format(trial.value))
print("  Params: ")
for key, value in trial.params.items():
    print("    {}: {}".format(key, value))

# 最適なモデルでテストデータの評価
best_params = trial.params
best_model = xgb.XGBClassifier(**best_params)
best_model.fit(X_train, y_train)
y_pred = best_model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print("Test Accuracy: {}".format(accuracy))
解説
1. ライブラリのインポート
import optuna
import xgboost as xgb
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import accuracy_score
  • optuna: ハイパーパラメータの自動チューニングライブラリ。
  • xgboost: 勾配ブースティングアルゴリズムのライブラリ。
  • load_breast_cancer: 乳がんデータセットをロードするための関数。
  • train_test_split: データセットをトレーニングセットとテストセットに分割するための関数。
  • cross_val_score: 交差検証を行うための関数。
  • accuracy_score: モデルの精度を評価するための関数。
2. データの読み込みと分割
data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(data.data, data.target, test_size=0.2, random_state=42)
  • 乳がんデータセットをロードします。
  • データセットを80%のトレーニングセットと20%のテストセットに分割します。
3. 目的関数の定義
def objective(trial):
    param = {
        'verbosity': 0,
        'objective': 'binary:logistic',
        'eval_metric': 'logloss',
        'booster': trial.suggest_categorical('booster', ['gbtree', 'gblinear', 'dart']),
        'lambda': trial.suggest_loguniform('lambda', 1e-8, 1.0),
        'alpha': trial.suggest_loguniform('alpha', 1e-8, 1.0)
    }

    if param['booster'] == 'gbtree' or param['booster'] == 'dart':
        param['max_depth'] = trial.suggest_int('max_depth', 1, 9)
        param['eta'] = trial.suggest_loguniform('eta', 1e-8, 1.0)
        param['gamma'] = trial.suggest_loguniform('gamma', 1e-8, 1.0)
        param['grow_policy'] = trial.suggest_categorical('grow_policy', ['depthwise', 'lossguide'])

    if param['booster'] == 'dart':
        param['sample_type'] = trial.suggest_categorical('sample_type', ['uniform', 'weighted'])
        param['normalize_type'] = trial.suggest_categorical('normalize_type', ['tree', 'forest'])
        param['rate_drop'] = trial.suggest_loguniform('rate_drop', 1e-8, 1.0)
        param['skip_drop'] = trial.suggest_loguniform('skip_drop', 1e-8, 1.0)

    model = xgb.XGBClassifier(**param)
    model.fit(X_train, y_train)

    score = cross_val_score(model, X_train, y_train, cv=3, scoring='accuracy').mean()
    return score
  • objective関数は、Optunaの試行ごとに呼び出され、ハイパーパラメータのチューニングを行います。
  • trial.suggest_categoricaltrial.suggest_loguniformなどのメソッドを使ってハイパーパラメータを試行します。
  • XGBoostモデルをトレーニングし、交差検証で得られた精度の平均を返します。
4. Optunaのスタディを作成して最適化
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=100)
  • optuna.create_studyで新しいスタディを作成し、精度を最大化するように設定します。
  • study.optimizeで目的関数objectiveを呼び出し、100回の試行を行います。
5. 最適な試行結果の表示
print("Best trial:")
trial = study.best_trial
print("  Value: {}".format(trial.value))
print("  Params: ")
for key, value in trial.params.items():
    print("    {}: {}".format(key, value))
  • 最適な試行結果(最高の精度とそのパラメータ)を表示します。
6. 最適なモデルでテストデータの評価
best_params = trial.params
best_model = xgb.XGBClassifier(**best_params)
best_model.fit(X_train, y_train)
y_pred = best_model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print("Test Accuracy: {}".format(accuracy))
  • 最適なパラメータを使ってXGBoostモデルを再度トレーニングし、テストデータで予測を行います。
  • テストデータの精度を計算し、表示します。
まとめ

このコードは、乳がんデータセットを使用してXGBoostモデルのハイパーパラメータをOptunaで最適化し、最適なパラメータを見つけるプロセスを示しています。最終的に、最適化されたモデルを使ってテストデータの精度を評価し、その結果を表示します。

単純なプロンプト

入力内容
以下のPythonコードを解説してください。
```python
import optuna
import xgboost as xgb
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import accuracy_score

data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(data.data, data.target, test_size=0.2, random_state=42)

def objective(trial)
    param = {
        'verbosity': 0,
        'objective': 'binary:logistic',
        'eval_metric': 'logloss',
        'booster': trial.suggest_categorical('booster', ['gbtree', 'gblinear', 'dart']),
        'lambda': trial.suggest_loguniform('lambda', 1e-8, 1.0),
        'alpha': trial.suggest_loguniform('alpha', 1e-8, 1.0)
    }

    if param['booster'] == 'gbtree' or param['booster'] == 'dart':
        param['max_depth'] = trial.suggest_int('max_depth', 1, 9)
        param['eta'] = trial.suggest_loguniform('eta', 1e-8, 1.0)
        param['gamma'] = trial.suggest_loguniform('gamma', 1e-8, 1.0)
        param['grow_policy'] = trial.suggest_categorical('grow_policy', ['depthwise', 'lossguide'])

    if param['booster'] == 'dart':
        param['sample_type'] = trial.suggest_categorical('sample_type', ['uniform', 'weighted'])
        param['normalize_type'] = trial.suggest_categorical('normalize_type', ['tree', 'forest'])
        param['rate_drop'] = trial.suggest_loguniform('rate_drop', 1e-8, 1.0)
        param['skip_drop'] = trial.suggest_loguniform('skip_drop', 1e-8, 1.0)

    model = xgb.XGBClassifier(**param)
    model.fit(X_train, y_train)

    
    score = cross_val_score(model, X_train, y_train, cv=3, scoring='accuracy').mean()
    return score

study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=100)
print("Best trial:")
trial = study.best_trial
print("  Value: {}".format(trial.value))
print("  Params: ")
for key, value in trial.params.items():
    print("    {}: {}".format(key, value))

best_params = trial.params
best_model = xgb.XGBClassifier(**best_params)
best_model.fit(X_train, y_train)
y_pred = best_model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print("Test Accuracy: {}".format(accuracy))

```
出力結果

このPythonコードは、Optunaライブラリを用いてXGBoostモデルのハイパーパラメータを最適化し、乳がんデータセットを使用してモデルの性能を評価するものです。以下、各部分の解説を行います。

ライブラリのインポート
import optuna
import xgboost as xgb
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import accuracy_score
  • optuna: ハイパーパラメータ最適化のためのライブラリ。
  • xgboost: 勾配ブースティングのライブラリ。
  • sklearn.datasets: データセットをロードするためのモジュール。
  • sklearn.model_selection: データの分割や交差検証のためのモジュール。
  • sklearn.metrics: モデルの性能を評価するためのモジュール。
データの準備
data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(data.data, data.target, test_size=0.2, random_state=42)
  • load_breast_cancer(): 乳がんデータセットをロード。
  • train_test_split(): データセットを訓練データとテストデータに分割。
目的関数の定義
def objective(trial):
    param = {
        'verbosity': 0,
        'objective': 'binary:logistic',
        'eval_metric': 'logloss',
        'booster': trial.suggest_categorical('booster', ['gbtree', 'gblinear', 'dart']),
        'lambda': trial.suggest_loguniform('lambda', 1e-8, 1.0),
        'alpha': trial.suggest_loguniform('alpha', 1e-8, 1.0)
    }

    if param['booster'] == 'gbtree' or param['booster'] == 'dart':
        param['max_depth'] = trial.suggest_int('max_depth', 1, 9)
        param['eta'] = trial.suggest_loguniform('eta', 1e-8, 1.0)
        param['gamma'] = trial.suggest_loguniform('gamma', 1e-8, 1.0)
        param['grow_policy'] = trial.suggest_categorical('grow_policy', ['depthwise', 'lossguide'])

    if param['booster'] == 'dart':
        param['sample_type'] = trial.suggest_categorical('sample_type', ['uniform', 'weighted'])
        param['normalize_type'] = trial.suggest_categorical('normalize_type', ['tree', 'forest'])
        param['rate_drop'] = trial.suggest_loguniform('rate_drop', 1e-8, 1.0)
        param['skip_drop'] = trial.suggest_loguniform('skip_drop', 1e-8, 1.0)

    model = xgb.XGBClassifier(**param)
    model.fit(X_train, y_train)

    score = cross_val_score(model, X_train, y_train, cv=3, scoring='accuracy').mean()
    return score
  • objective(trial): Optunaの目的関数。試行ごとに異なるハイパーパラメータを試し、モデルの性能を評価する。
  • param: 試行ごとにOptunaが提案するハイパーパラメータ。
  • model = xgb.XGBClassifier(**param): 提案されたパラメータを使用してXGBoostモデルを作成。
  • cross_val_score(): 交差検証を用いてモデルの性能(精度)を評価。
Optunaのスタディ作成と最適化
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=100)
  • optuna.create_study(direction='maximize'): 最大化する方向でスタディを作成。
  • study.optimize(objective, n_trials=100): 目的関数を最適化するために100回の試行を行う。
最適なパラメータの表示と最終モデルの評価
print("Best trial:")
trial = study.best_trial
print("  Value: {}".format(trial.value))
print("  Params: ")
for key, value in trial.params.items():
    print("    {}: {}".format(key, value))

best_params = trial.params
best_model = xgb.XGBClassifier(**best_params)
best_model.fit(X_train, y_train)
y_pred = best_model.predict(X_test)
accuracy = accuracy_score(y_test, y_pred)
print("Test Accuracy: {}".format(accuracy))
  • 最適な試行の結果を表示。
  • 最適なパラメータを用いてモデルを再作成し、訓練データで再学習。
  • テストデータで予測を行い、精度を計算して表示。

このコード全体の流れは、Optunaを用いてXGBoostのハイパーパラメータを最適化し、最適なモデルの性能を評価するというものです。

生成されたプロンプトでPythonのコードを解説させた方がやや丁寧で詳細な情報が含まれていました。

まとめ


この記事では、LangGraphを使用してユーザーからの要件を収集し、それに基づいてプロンプトを生成するチャットボットの作成方法を詳しく解説しました。

このチュートリアルを通じて、LangGraphの基本的な使い方と、プロンプト生成機能の実装方法を学んでいただけたと思います。今回は単純なチャットボットですが、LangGraphでは簡単にカスタマイズ可能です。より高度なチャットボットや他のアプリケーションを開発してみてください。

参考文献