LangChainのAgentExecutorを使って生成AIのAgentにデータ分析をさせてみた

はじめに

こんにちは!昨年9月からInsight Edgeの開発チームに参画した広松です。Insight Edgeでは生成AI案件を担当しています。

参画してからまだ間もないですが、技術選定や顧客との調整を含む多くの業務を裁量を持って自由にできるところが良いと感じています。また、Insight Edgeでは業務時間の10%を勉強会などの自己啓発に充てることが奨励されています。この文化のおかげで、LangChainの勉強会を運営したりTech Blogに記事を寄稿したりと、いろいろな事に挑戦できてとても良い環境だなと感じています。

さて、最近私が担当する案件で、生成AIに定性的な分析だけでなく定量的な分析もさせたいという要望がありました。そこで今回は、生成AIにデータ分析をさせる手法を検証してみました。具体的には、生成AIのAgentにKaggleのチュートリアルとして有名なタイタニック号の乗客のデータセットを分析させ、分析結果を評価したので紹介しようと思います。

目次

概要

私が担当している案件で、生成AIに定量的な分析をさせたいという要望がありました。しかし、生成AIは確率的にデータを生成するため、数学のような論理的思考や数値計算が苦手です。

そこでどのように生成AIにデータ分析をさせるのか技術調査をしました。生成AIに計算やデータ分析をさせる場合、Agent を使ってPythonのコードを生成・実行させる手法が一般的です。

今回は、生成AIのAgentにKaggleのチュートリアルとして有名なタイタニック号の乗客のデータセットを分析させ、分析結果を評価しました。

※補足 案件で構築するシステムの概要

今回の検証とはあまり関係がないので、興味が無い方は読み飛ばしてください。

  1. 分析テーマの入力: ユーザが分析したいテーマを入力
  2. 定性的な分析の実施: 生成AIが入力されたテーマに関連する情報をElastic Searchのハイブリッド検索機能を用いたRAGで収集し、その情報に基づいて定性的な分析を実施
  3. 定量的な分析の実施: 生成AIのAgentが定性的な分析結果を元に社内データの分析をすることで定量的な分析も実施(※ここが今回の技術検証に関連する部分です。)
  4. 最終的なレポートの生成: システムが定性的と定量的な視点を融合させた最終的な分析レポートを生成

事前知識

自律エージェント、ReAct、LangChainについての簡単な説明です。ご存知の方は技術選定 まで読み飛ばしてください。

自律エージェントとは

Agentという単語が出てきましたが、あまり馴染みのない方もいると思うので簡単に説明します。生成AIやLLMの文脈で出てくるAgent=自律エージェントと思っていただいて構いません。

自律エージェントは、与えられた最終目的に対して、自ら思考して計画をし、実際に行動をして最終目的を達成しようとするシステムです。 この行動の際に、自律エージェントはツールを使って現実世界のシステムに干渉できます。

この説明だと抽象度が高く、よく分からないと思うので今回の検証を例に説明します。 自律エージェントには、「タイタニック号の乗客のデータセットを分析する」という最終目的だけを与えます。 すると自律エージェントは、最初に思考してどのように分析するか計画を立てます。次に、計画に従いインターネット検索をするツールやPythonコード生成・実行ツールを使ってデータ分析を実施して最終目的を達成しようとします。生成AIが内部で考えているだけでなく、インターネット検索ツールやPythonコード生成・実行ツールを使って、現実世界のシステムとやりとりをしているところが、通常の生成AIの利用と大きく異なります

今回はReActという手法でAgentを動作させているので、詳細な仕組みは次のReAct(Reasoning and Acting)とは で説明します。

自律エージェントについて詳細を確認したい方は、Autonomous Agents & Agent Simulations を参照してください。

ReAct(Reasoning and Acting)とは

ReAct(Reasoning and Acting)は生成AIに論理的な思考をさせるPrompt Engineeringの技法の1つです。生成AIに推論と行動を繰り返させることで論理的思考を促すもので、次の三段階のプロセスを繰り返します。

  1. 思考(Thought): 目的を理解し、行動計画・タスクの詳細化を行い、具体的な解決策を考案する。観察(Observation)からフィードバックを受け取り、適宜行動計画を修正する。
  2. 行動(Action): 思考(Thought)段階で考案された解決策を実行する。
  3. 観察(Observation): 行動(Action)の結果を観察し、次の思考(Thought)へフィードバックする。

LangChainのAgentは、この行動(Action)の部分にツールを使用できます。SerpApi等の検索ツールやPythonREPL等のPythonコード生成・実行ツールを使用します。利用可能なツールの説明を理解し、目的達成に必要なものをAgent自ら選択します。

つまり、与えられた最終目的に対して自ら思考して計画を立て、適切なツールを選択して行動し、結果の観察とフィードバックのループを目的達成まで繰り返す自律エージェントが作成されます。

ReActについて詳細を確認したい方は、ReActが提唱された論文 ReAct: Synergizing Reasoning and Acting in Language Models を参照してください。

LangChainとは

LangChainとは大規模言語モデル(LLM)を用いたアプリケーションを開発するためのフレームワークです。 様々なエコシステムと連携しやすくなっています。

LangChainにはモジュールが6つあるので簡単に紹介します。今回は主にAgentsのモジュールに関係があります。

モジュール 概要
Model I/O LLMに対するプロンプト入力、呼び出し、結果の受け取りの実装を簡単にする。
Retrieval LLMに未知の情報にアクセスする機能を提供する、Retrieval-Augmented Generation(RAG)を扱うモジュール。外部の検索エンジンやデータベースと連携して、LLMに事前学習していない情報に対応させる機能を簡単に実装できるようにする。
Memory 過去の会話履歴をデータベースに保存したり、呼び出す機能を簡単に実装できるようにする。
Chains 複数のモジュールの組み合わせを簡単に実装できるようにする。
Agents 自律的に思考し、外部に干渉する自律エージェントの構築を簡単にする。
Callbacks 様々なイベント発生時の処理の実装を簡単にする。ログの実装などに用いる。

LangChainについて詳細を確認したい方は、LangChain公式ドキュメント を参照してください。

※補足 Code InterpreterではなくAgentで実装する理由

こちらも今回の検証とは直接関係がないので、興味が無い方は読み飛ばしてください。

技術検証するのは生成AIのAgentにデータ分析をさせる部分です。OpenAIのCode Interpreterと似た機能ですが、Code Interpreterは外部環境(インターネット等)にアクセスできない等の制約があります。

一方、Agentであれば不明点や解決できないエラーが発生した際に、検索ツールを使って検索する等、他のツールを使った行動を取ることもできます。そのためAgentで実装した方が顧客の要望に柔軟に対応できます。

また担当する案件は、案件で構築するシステムの概要 のように複雑で今後も拡張されそうな要件です。Agentであれば行動やツールについて自由にカスタマイズができるためAgentで実装しました。

技術選定

生成AIにデータ分析をさせる部分を検証します。 検証はLangChainで実装しました。

LangChainのAgent関連のコンポーネントについて

選定理由を話す前に、LangChainのAgent関連のコンポーネントの説明をしておきます。ご存知の方は読み飛ばしてください。

コンポーネント 説明
AgentExecutor AgentExecutorは、Agentの実行を管理するコンポーネントで、Agentが実行するタスクを制御し、結果を処理する。
Agent Agentは、特定のタスクやプロセスを自動化するためのエンティティで、一連のToolsを使用する。ツールのDescriptionを読んで適切なツールを選択する。
Tools Toolsは、Agentがタスクを実行する際に使用する具体的な機能やサービスの集合。Toolsには外部のAPI呼び出しを可能にするツール、Google検索などを可能にする検索ツール、Pythonのコード生成・実行を可能にするツールなどがある。
※LangChainで利用可能なTools一覧は こちら
また、独自のToolsの作成も簡単にできる。独自のRetrieverを定義しておけば独自実装したRAGを組み込んだAgentを作ることができる。
Toolkit Toolkitは、複数のToolsを組み合わせて構成されるセットで、特定のタスクを効率的に実行するために最適化されている。 Pythonのコード生成・実行に特化したToolkitなどもある。
※LangChainで利用可能なToolkit一覧は こちら

選定理由

技術要素 選定理由
LLM: GPT-4 Turbo 精度とコストを重視したため。
フレームワーク: LangChain 生成AIのAgentを容易に実装・管理できるフレームワークであり、デファクトスタンダードになりつつあるため。
Tools: PythonREPL Pythonのコード生成・実行を可能にするツール。データ分析タスクにおいて、Pythonを使用して分析をするため
Tools: DuckDuckGoSearch インターネット検索を可能にするツール。不明点や解決できないエラーが発生した場合に検索して対処するため。 今回はツールとして独自定義した。
Agent: ReAct Agentの汎用性と論理的な思考能力を評価するためReAct を採用。案件で構築するシステムがチャット形式のため、LangChainのAgentTypeはCHAT_ZERO_SHOT_REACT_DESCRIPTION を採用。

事前準備

事前にタイタニック号の乗客のデータセットをダウンロードし、S3などのダウンロード可能な場所に配置してください。タイタニック号の乗客のデータセットはKaggleからダウンロードできます。 データセットはKaggleにユーザ登録をし、Kaggleのタイタニックデータセットのページ からダウンロードできます。

また、OpenAIのAPIキーは環境変数に事前に登録しておいてください。

OPEN_API_KEY="ご自身の保有しているAPIキーを記載してください"

実装内容

下記のコードで、タイタニック号の乗客データセットをダウンロードし、タイタニック号で生存に寄与した乗客の属性を分析させます。 コードの内容は 選定理由 に記載した通りなので詳細な説明は割愛します。 案件では再現性の高い分析が求められているため、LLMのtemperatureを0に設定しました。

Agentに与える指示は以下にしています。

タイタニック号の乗客のデータセットを分析してください。生存に寄与した属性をランキングで表示し、影響度を数値で表示してください。データセットは、https://xxxxxxxxxxxx/train.csv を使用してください。

データセットをインターネットからダウンロードするよう指示したのは、OpenAIのCode Interpreterができない外部環境へのアクセスが可能か検証するためです。コードを実行する場合は、事前準備 にあるようにダウンロード可能なURLに書き換えてください。

from langchain.agents import AgentType, initialize_agent, load_tools, Tool
from langchain.chat_models import ChatOpenAI
from langchain.tools import DuckDuckGoSearchRun

chat = ChatOpenAI(
    temperature=0,  # 再現性を重視するため、temperatureを0に設定
    model="gpt-4-turbo-preview"
)

tools = load_tools(  # Agentが使用可能なツールの配列を読み込む(複数指定可能、Agentがツールのdescriptionを読み適切なツールを選択する)
    [
        "python_repl"  # Pythonのコードを実行するためのTool
    ]
)

search = DuckDuckGoSearchRun()  # 不明点やエラーの解決策を検索するためDuckDuckGoSearchを追加

tools.append(  # 追加したDuckDuckGoSearchをtoolsに追加
    Tool(  # ツールとして検索ツールを独自定義
        name="duckduckgo-search",
        func=search.run,
        description="This tool is used for conducting internet searches to address uncertainties or unresolved errors that arise during tasks or development processes."  # Agentが選択できるようにツールの説明を記載
    )
)

agent = initialize_agent(  
    tools=tools,  # Agentが使用することができるツールの配列を指定
    llm=chat,  # Agentが使用する言語モデルを指定
    agent=AgentType.CHAT_ZERO_SHOT_REACT_DESCRIPTION,  #ReActを実行するAgentを指定
    verbose=True  # 実行中の詳細なログを表示する
)

result = agent.run("タイタニック号の乗客のデータセットを分析してください。生存に寄与した属性をランキングで表示し、影響度を数値で表示してください。データセットは、https://xxxxxxxxxxxx/train.csv を使用してください。")
print(f"実行結果: {result}")

実行結果

実行すると、論理的に推論と行動を繰り返し、最終的にランキングを出力できました。
出力結果

  1. parch: 0.6250
  2. sex: 0.5365
  3. sibsp: 0.5110
  4. pclass: 0.3639
  5. fare_quantile: 0.3567
  6. age_group: 0.1178

この分析結果では、parch(同乗している家族(親子)の人数)、sex(性別)、sibsp(同乗している兄弟姉妹、配偶者の数)、pclass(客室等級)、fare_group(運賃)、age_group(年齢)の順に生存への影響が強い属性だったようですね。

実行結果を見てみましょう。(※実行結果中のデータセットのダウンロードURLは書き換えています。)

実行結果全文(非常に長いので詳細を確認したい方のみ、この文章をクリックして展開してください。)

> Entering new AgentExecutor chain...
Question: Analyze the Titanic passengers dataset to determine which attributes had an impact on survival. Provide the analysis results in a ranking with numerical values indicating the degree of impact on survival. Download the dataset using Python from the provided URL (https://xxxxxxxxx/train.csv).

Thought: To analyze the Titanic dataset, I will first need to download the data using Python. After downloading, I will load the data into a pandas DataFrame and perform exploratory data analysis (EDA) to determine which attributes had the most significant impact on survival.

Action:

{
  "action": "Python_REPL",
  "action_input": "import pandas as pd\ndf = pd.read_csv('https://xxxxxxxxx/train.csv')\nprint(df.head())"
}


Python REPL can execute arbitrary code. Use with caution.

Observation:    pclass  survived                                             name     sex      age  sibsp  parch  ticket      fare    cabin embarked boat   body                        home.dest
0       1         1                    Allen, Miss. Elisabeth Walton  female  29.0000      0      0   24160  211.3375       B5        S    2    NaN                     St Louis, MO
1       1         1                   Allison, Master. Hudson Trevor    male   0.9167      1      2  113781  151.5500  C22 C26        S   11    NaN  Montreal, PQ / Chesterville, ON
2       1         0                     Allison, Miss. Helen Loraine  female   2.0000      1      2  113781  151.5500  C22 C26        S  NaN    NaN  Montreal, PQ / Chesterville, ON
3       1         0             Allison, Mr. Hudson Joshua Creighton    male  30.0000      1      2  113781  151.5500  C22 C26        S  NaN  135.0  Montreal, PQ / Chesterville, ON
4       1         0  Allison, Mrs. Hudson J C (Bessie Waldo Daniels)  female  25.0000      1      2  113781  151.5500  C22 C26        S  NaN    NaN  Montreal, PQ / Chesterville, ON

Thought:The dataset has been successfully loaded into a pandas DataFrame. The next step is to perform exploratory data analysis (EDA) to determine which attributes had the most significant impact on survival. I will start by checking for missing values in the dataset, as this can affect the analysis. Then, I will proceed to calculate the survival rates based on different attributes such as 'pclass', 'sex', 'age', 'sibsp', 'parch', 'fare', and 'embarked'.

Action:

{
  "action": "Python_REPL",
  "action_input": "print(df.isnull().sum())"
}



Observation: pclass          0
survived        0
name            0
sex             0
age           263
sibsp           0
parch           0
ticket          0
fare            1
cabin        1014
embarked        2
boat          823
body         1188
home.dest     564
dtype: int64

Thought:The dataset contains a significant number of missing values in the 'age', 'cabin', 'boat', 'body', and 'home.dest' columns. The 'boat' and 'body' columns are not typically used for survival analysis, as they relate to post-accident events (lifeboats and body recovery). The 'cabin' data is also largely missing, which may limit its usefulness in the analysis. However, 'age' and 'embarked' are important factors that are often considered in survival analysis. The missing value in 'fare' is negligible as it is only one entry.

The next step is to analyze the impact of various attributes on survival. I will calculate survival rates based on 'pclass', 'sex', 'age', 'sibsp', 'parch', and 'fare'. Since 'age' has missing values, I will need to decide how to handle them, either by imputing a value or by excluding those rows from certain parts of the analysis. For the initial analysis, I will focus on the attributes with complete data.

I will start by calculating the survival rates for 'pclass' and 'sex', as these are commonly known to have had a significant impact on survival. Then I will proceed with 'sibsp', 'parch', and 'fare'. For 'age', I will consider how to handle the missing values before proceeding.

Action:

{
  "action": "Python_REPL",
  "action_input": "survival_rates = {}\nfor column in ['pclass', 'sex', 'sibsp', 'parch']:\n    survival_rates[column] = df.groupby(column)['survived'].mean()\nprint(survival_rates)"
}

Observation: {'pclass': pclass
1    0.619195
2    0.429603
3    0.255289
Name: survived, dtype: float64, 'sex': sex
female    0.727468
male      0.190985
Name: survived, dtype: float64, 'sibsp': sibsp
0    0.346801
1    0.510972
2    0.452381
3    0.300000
4    0.136364
5    0.000000
8    0.000000
Name: survived, dtype: float64, 'parch': parch
0    0.335329
1    0.588235
2    0.504425
3    0.625000
4    0.166667
5    0.166667
6    0.000000
9    0.000000
Name: survived, dtype: float64}

Thought:The survival rates based on 'pclass', 'sex', 'sibsp', and 'parch' show clear trends. Higher class passengers (pclass 1) had a higher survival rate than those in lower classes. Females had a much higher survival rate than males. The number of siblings/spouses (sibsp) and parents/children (parch) aboard also had varying impacts on survival rates, with some numbers showing higher survival rates than others.

To provide a more comprehensive analysis, I will now calculate the survival rate for 'fare' by categorizing it into quantiles, which will help to understand if higher fares (which could correlate with higher socio-economic status or cabin location) had an impact on survival. I will also handle the missing 'age' data by filling in the missing values with the median age, and then categorizing age into groups to see if there is a trend in survival rates across different age groups.

First, I will handle the 'fare' attribute by categorizing it into quantiles.

Action:

{
  "action": "Python_REPL",
  "action_input": "df['fare_quantile'] = pd.qcut(df['fare'], 4, labels=False)\nsurvival_rates['fare_quantile'] = df.groupby('fare_quantile')['survived'].mean()\nprint(survival_rates['fare_quantile'])"
}


Observation: fare_quantile
0.0    0.228487
1.0    0.284375
2.0    0.435976
3.0    0.585139
Name: survived, dtype: float64

Thought:The survival rates for the 'fare_quantile' show a positive correlation between fare price and survival, with passengers who paid higher fares having a higher survival rate. This suggests that socio-economic status, which could be inferred from the fare paid, had an impact on survival.

Next, I will address the missing 'age' data. Since age is an important factor and cannot be ignored, I will impute the missing values with the median age. After imputing the missing values, I will categorize the 'age' into groups and calculate the survival rates for each group to see if there is a trend in survival rates across different age groups.

Action:

{
  "action": "Python_REPL",
  "action_input": "median_age = df['age'].median()\ndf['age'].fillna(median_age, inplace=True)\ndf['age_group'] = pd.qcut(df['age'], 4, labels=False)\nsurvival_rates['age_group'] = df.groupby('age_group')['survived'].mean()\nprint(survival_rates['age_group'])"
}


Observation: age_group
0    0.435435
1    0.317597
2    0.425532
3    0.394410
Name: survived, dtype: float64

Thought:The survival rates for the 'age_group' show that the youngest and older age groups had slightly higher survival rates than the middle age groups, although the differences are not as pronounced as with other attributes like 'sex' and 'pclass'.

Now that I have calculated the survival rates for 'pclass', 'sex', 'sibsp', 'parch', 'fare_quantile', and 'age_group', I can rank these attributes by their impact on survival. To do this, I will calculate the difference in survival rates between the highest and lowest groups within each attribute to determine the range of impact. Then I will rank the attributes based on these ranges.

Action:

{
  "action": "Python_REPL",
  "action_input": "impact_ranges = {}\nfor attribute, rates in survival_rates.items():\n    impact_ranges[attribute] = rates.max() - rates.min()\nimpact_ranking = sorted(impact_ranges.items(), key=lambda x: x[1], reverse=True)\nfor rank in impact_ranking:\n    print(f'{rank[0]}: {rank[1]:.4f}')"
}



Observation: parch: 0.6250
sex: 0.5365
sibsp: 0.5110
pclass: 0.3639
fare_quantile: 0.3567
age_group: 0.1178

Thought:The ranking of attributes based on their impact on survival, with the numerical values indicating the degree of impact, is as follows:

1. `parch` (number of parents/children aboard): 0.6250
2. `sex`: 0.5365
3. `sibsp` (number of siblings/spouses aboard): 0.5110
4. `pclass` (passenger class): 0.3639
5. `fare_quantile` (fare price category): 0.3567
6. `age_group`: 0.1178

It is important to note that the `parch` attribute shows the largest range of impact, which might be surprising as it is not typically considered the most significant factor. However, this could be due to the way the survival rates are calculated across different numbers of parents/children aboard, which might not be linear and could be influenced by small sample sizes for certain values.

The `sex` attribute has the second-highest impact, which aligns with historical accounts that women were given priority during the lifeboat boarding process.

The `sibsp` attribute is third, suggesting that having siblings or a spouse aboard had a significant impact on survival, potentially due to the support system or prioritization of families during evacuation.

The `pclass` attribute is fourth, indicating that passenger class was a significant factor, with first-class passengers having a higher survival rate.

The `fare_quantile` is fifth, which is related to socio-economic status and potentially cabin location, with higher fares correlating with higher survival rates.

Lastly, the `age_group` has the smallest range of impact, suggesting that age was a less significant factor compared to the others, although there was still some variation in survival rates across age groups.

Final Answer: The ranking of attributes by impact on survival, from highest to lowest, is as follows:

1. `parch`: 0.6250
2. `sex`: 0.5365
3. `sibsp`: 0.5110
4. `pclass`: 0.3639
5. `fare_quantile`: 0.3567
6. `age_group`: 0.1178

> Finished chain.
実行結果: The ranking of attributes by impact on survival, from highest to lowest, is as follows:

1. `parch`: 0.6250
2. `sex`: 0.5365
3. `sibsp`: 0.5110
4. `pclass`: 0.3639
5. `fare_quantile`: 0.3567
6. `age_group`: 0.1178

中身を見てみましょう。はじめにQuestionで最終目的を定義しています。

Question: Analyze the Titanic passengers dataset to determine which attributes had an impact on survival. Provide the analysis results in a ranking with numerical values indicating the degree of impact on survival. Download the dataset using Python from the provided URL (https://xxxxxxxxx/train.csv).

きちんと指示通りに、生存に寄与した属性をランキング形式で表示するタスクを理解していますね。 その後は、以下6ステップに分けて実行しているようです。

  1. データセットのダウンロードと読み込み: 指定されたURLからタイタニックのデータセットをダウンロードし、PandasのDataFrameに読み込みます。読み込んだデータの先頭部分を表示して、データが正しく読み込まれたことを確認します。

  2. 欠損値の確認: データセットに含まれる欠損値の数を確認します。特に「年齢(age)」、「キャビン(cabin)」、「乗船した港(embarked)」などのカラムで欠損値が多いことが分かります。これらの欠損値は分析に影響を与えるため、適切に処理する必要があります。

  3. 生存率の計算: 生存に影響を与えた可能性のある属性(「客室等級(pclass)」、「性別(sex)」、「兄弟姉妹や配偶者の数(sibsp)」、「親や子どもの数(parch)」)について、グループごとに生存率を計算します。

  4. 料金(fare)に基づく生存率の計算: 乗船料金を四分位数で分割し、料金のカテゴリーごとに生存率を計算します。料金が生存率にどのように影響したかを分析します。

  5. 年齢(age)に基づく生存率の計算: 年齢データの欠損値を中央値で補完し、年齢をグループに分けて生存率を計算します。年齢が生存率にどのように影響したかを分析します。

  6. 属性の影響度のランキングの表示: 最後に、各属性に基づいて計算された生存率の差を用いて、生存に最も影響を与えた属性をランキング形式で表示します。ランキングは、各属性が生存に与えた影響の度合いを数値で示しています。

考察

分析結果

Agentによる分析と人による分析を比較して妥当性を確認します。 Kaggleで今回のデータセットの分析をされているこちらの投稿 を見ても、生存に影響を与えている属性が同じですね。人が行った分析と比較しても、今回のAgentの行った分析は妥当と言えます。タイタニック号の史実と照らし合わせても客室等級、性別、子供を連れた家族などの属性が生存に影響したという分析は妥当と言えます。

分析過程

今回の検証では分析結果よりも分析過程自体の考察が重要です。
生存に寄与した属性をランキング表示するという大目標のみを与えていましたが、Agentは目的達成のために自律的に思考し自ら詳細なタスクを作り出して実行していました。特にデータを確認して自らの判断で欠損値を確認して補完したり、生存率を計算した後、関連のありそうな属性を詳細に分析するタスクを自ら作り出して実行している点は非常に興味深いです。

今回のデータセットを分析している記事をいくつか拝見しましたが、欠損値を埋める等の指示をしていないタスクも人と同様の手順で実行できていました。

このような論理的な思考ができる理由はReAct の推論フレームワークを使用したからだと考えられます。

該当箇所抜粋

Thought:The dataset has been successfully loaded into a pandas DataFrame. The next step is to perform exploratory data analysis (EDA) to determine which attributes had the most significant impact on survival. I will start by checking for missing values in the dataset, as this can affect the analysis. Then, I will proceed to calculate the survival rates based on different attributes such as 'pclass', 'sex', 'age', 'sibsp', 'parch', 'fare', and 'embarked'.

Action:

{
  "action": "Python_REPL",
  "action_input": "print(df.isnull().sum())"
}



Observation: pclass          0
survived        0
name            0
sex             0
age           263
sibsp           0
parch           0
ticket          0
fare            1
cabin        1014
embarked        2
boat          823
body         1188
home.dest     564
dtype: int64

Thought:The dataset contains a significant number of missing values in the 'age', 'cabin', 'boat', 'body', and 'home.dest' columns. The 'boat' and 'body' columns are not typically used for survival analysis, as they relate to post-accident events (lifeboats and body recovery). The 'cabin' data is also largely missing, which may limit its usefulness in the analysis. However, 'age' and 'embarked' are important factors that are often considered in survival analysis. The missing value in 'fare' is negligible as it is only one entry.

The next step is to analyze the impact of various attributes on survival. I will calculate survival rates based on 'pclass', 'sex', 'age', 'sibsp', 'parch', and 'fare'. Since 'age' has missing values, I will need to decide how to handle them, either by imputing a value or by excluding those rows from certain parts of the analysis. For the initial analysis, I will focus on the attributes with complete data.

Agentによる自律的なエラー解決

何度か検証をしていると、Agentが生成したPythonのコードが実行時にエラーを吐くことがありました。エラーが発生した際にAgentは、ReAct の推論フレームワークに沿って、エラーを自律的に解決していました。具体的には発生したエラーの解決策を検索ツールで検索して解決していました。

自律的なエラー解決のプロセス

  1. 観察(Observation): 前回の行動(Action)の結果、エラーの発生を確認する。
  2. 思考(Thought): 解決策を探すために、適切なツールの使用する。「不明点やエラーの解決策を検索するためのツール」という説明の検索ツールを見つけ、解決策をインターネット検索することを計画する。
  3. 行動(Action): 検索ツールを用いて解決策を検索する。
  4. 観察(Observation): 検索結果から有益な情報を抽出する。
  5. 思考(Thought): 抽出された情報から具体的なアクションを考案する。
  6. 行動(Action): Pythonコード生成・実行ツールで解決策を実行する。

このように予期せぬ事態に遭遇しても、推論と行動を繰り返すReActのAgentは柔軟に対応できます。状況に合わせて臨機応変に計画を変更し、ツールを使い分け、目的達成に向けてタスクを継続できる点がReActで動作するAgentの強みです。

下記の事例は、seabornというモジュールがインストールできないエラーが発生し、解決策を検索ツールで検索しています。結局インストールできないという問題は解決できませんでしたが、他のモジュールを使うことでエラーを回避して分析を進めていました。

Agentが発生したエラーの解決策を検索したり、回避策を探して分析を続けている事例

Action:

{
  "action": "Python_REPL",
  "action_input": "!pip install seaborn"
}

Observation: SyntaxError('invalid syntax', ('<string>', 1, 1, '!pip install seaborn\n', 1, 2))
Thought:It seems that I made the same mistake again. I need to use the duckduckgo-search tool to search for the correct way to install the seaborn module.

Action:

{
  "action": "duckduckgo-search",
  "action_input": "install seaborn module in Python using pip"
}

Observation: Latest version Jan 25, 2024 Project description seaborn: statistical data visualization Seaborn is a Python visualization library based on matplotlib. It provides a high-level interface for drawing attractive statistical graphics. Documentation Online documentation is available at seaborn.pydata.org. We can install the seaborn package by running the below command. pip install seaborn Kindly ensure that you have pip installed on your device before running this command. For Python 3, we can use the pip3 command instead of pip. We can also specify the required version of the module we wish to install, as shown below. pip3 install seaborn==0.9.0 The pip show seaborn command will either state that the package is not installed or show a bunch of information about the package, including the location where the package is installed. # Make sure your IDE is using the correct Python version If the package is not installed, make sure your IDE is using the correct version of Python. How can I Install a Python module within code? Ask Question Asked 11 years, 5 months ago Modified 8 months ago Viewed 552k times 503 I need to install a package from PyPI straight within my script. Getting Started First of all, let us install Seaborn. Seaborn can be installed using the pip. Type the below command in the terminal. pip install seaborn In the terminal, it will look like this - After the installation is completed you will get a successfully installed message at the end of the terminal as shown below.
Thought:Based on the search results, the correct way to install the seaborn module is by using the pip command. I will now install seaborn using the pip command.

Action:

{
  "action": "Python_REPL",
  "action_input": "!pip install seaborn"
}

Observation: SyntaxError('invalid syntax', ('<string>', 1, 1, '!pip install seaborn\n', 1, 2))
Thought:I apologize for the repeated mistake. It seems that I am unable to install the seaborn module using the pip command in this environment. However, I can still analyze the dataset using other available tools and libraries. I will proceed with analyzing the dataset without using seaborn.

まとめ

検証結果

生成AIは一般に確率的にデータを生成するため、論理的な思考やデータ分析のような数値計算は苦手です。

しかし、今回検証したように生成AIのAgentにReAct のような推論フレームワークとPythonコード生成・実行をするツールを組み合わせることで、論理的思考や数値計算が必要なデータ分析等のタスクも実行できるようになります。Agentによる分析も人による分析と比較しても妥当性の高いものでした。

また、生成したPythonのコードでエラーが発生した際に検索のツールを使って、エラーの解決策を検索しに行くなど臨機応変な対応をしていました。想定外の事態が起きても、計画を変更しツールを使い分けて対応できる点も、推論と行動を繰り返すReActのAgentの強みと言えます。

Code Interpreterとの比較

OpenAIのCode Interpreterではこれほど推論と行動を繰り返してタスクを実行することはありません。エラー解決のためにインターネットで検索するなどの臨機応変な対応もできません。そもそもインターネット等の外部環境にアクセスができません。上記の点からも今回のようなAgentの実装に優位性があります。

逆にいうとAgentは行動を予測しづらく、行動回数が増えて返答まで時間がかかったり、GPT-4 Turboの呼び出し回数も増加して高コストになる可能性があります。要件に応じて最大行動回数を設定する等の対策をすると良いと思います。

今後に向けて

案件では、生成AIでRAGを用いてプライベートデータの定性的な分析をし、得られた定性的な分析を元に定量的な分析をします。今回の検証よりも複雑で高い精度を求められるので今後も改良が必要です。今回は単一のAgentで実装しましたが、今後は複数のAgentを使った構成も試したいと思います。マルチエージェントと呼ばれる、複数の異なる役割を持ったエージェントが協力するシステムや、BabyAGI などの高度な自律エージェントを検証し、高精度でより複雑なタスクへ可能なシステムを実装しようと思います。

感想

生成AIのAgentを使ったデータ分析をしてみましたが、ReAct の論理的思考には驚かされました。特に、Agentが生成したPythonのコードでエラーが発生した際に検索を始めた時には、こんなことができるのかと非常に驚きました。

最近では、複数の異なる役割を持ったエージェントが協力するマルチエージェント等、複雑なタスクに対応できるアーキテクチャも流行っています。また、最近のLLMはマルチモーダル(テキストだけでなく、画像、音声、動画など様々なデータ形式に対応したモデル)にもなってきています。マルチモーダルとマルチエージェントを組み合わせるとかなり幅広いタスクができるようになるのではないかと思います。

限界を越えて進化し続ける生成AIは、非常に面白いので今後も積極的に関わっていこうと思います! 本記事がどなたかの役に立てば幸いです!