12
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LTS Group(エル・ティー・エス グループ)Advent Calendar 2024

Day 3

テーブルデータをLLMに渡して分析させる方法を試してみた

Last updated at Posted at 2024-12-02

はじめに

近年、LLM(Large Language Model)の活用がさまざまな分野で進んでいます。その中で、テーブルデータ(例えば、pandasのDataFrameなど)をLLMに渡し、データ解析や要約を行いたいという場面も出てくると思います。

しかし、LLMは構造化データを直接的に理解することができません。具体的には、LLMは自然言語やテキスト形式を処理するように設計されており、以下の理由からテーブルデータを適切な形式に変換する必要があります。

なぜテーブルデータの変換が必要なのか?

  1. LLMはテキスト処理を前提に設計されている

    • LLMは文章や単純なテキスト構造をもとにトレーニングされており、DataFrameCSVなどの形式をそのままでは解釈できません。
    • そのため、テーブルデータをLLMが処理可能な形式(例えば、JSONやMarkdownなど)に変換する必要があります。
  2. データの構造を保つため

    • テーブルデータには行・列という構造があります。この構造を損なわずにLLMに渡すことで、データの意味や関係性を正確に理解させることができます。
    • 適切なフォーマットを選ぶことで、LLMが「どの列が何を表しているか」を認識しやすくなります。
  3. 応答の精度向上のため

    • 適切な形式でデータを渡すと、LLMはより高い精度で応答を生成できます。逆に、フォーマットが不適切だと情報が欠落したり、誤解される可能性があります。

なぜこの検証を行うのか?

テーブルデータをテキストで表現するにあたり、データ構造が複雑であるとLLMが行列の関係を正しく理解できず解釈を誤ってしまうケースがあるのではないでしょうか。変換するフォーマットが違うことで解釈の精度差が出てくることも考えられます。
そういった疑問を感じたため検証を行いました。

目次

  1. 結果のサマリー
  2. LLMのセットアップ
  3. 使用データのセットアップ
  4. プロンプトのセットアップ
  5. 各データ形式の検証
  6. まとめ
  7. おわりに

結果のサマリー

結論、どの形式でもちゃんと回答できました

ただしXMLとHTML形式ではテキスト量が多く、LLMのコンテキストサイズを超えてしまってエラーを発したため未検証となります。

フォーマット 結果
dict
json
pipe
list
markdown
yaml
csv
tsv
latex
xml -
html -

検証を行ったものに関しては、どのフォーマットでもうまく解釈できていることが確認できました。システムの中にLLMを組み込むことを考えたときに、どの形式でも対応ができそうなのは嬉しい結果です。
しかし、今回はシンプルなデータセットを用いたため正しく解釈ができていた可能性があることは否めません。もう少し複雑なデータセットや複数回答をさせるケースについてはさらに検証が必要だと思います。

LLMのセットアップ

○環境

  • Google Colab
  • LLM: elyza/Llama-3-ELYZA-JP-8B-GGUF
from llama_cpp import Llama

llm = Llama.from_pretrained(
	repo_id="elyza/Llama-3-ELYZA-JP-8B-GGUF",
	filename="Llama-3-ELYZA-JP-8B-q4_k_m.gguf",
)

使用データのセットアップ

今回は第3回WBSCプレミア12スーパーラウンド、ベネズエラ戦の日本のスターティングメンバーのデータを使用しました。

import pandas as pd

# データの作成
data = {
    "打順": [1, 2, 3, 4, 5, 6, 7, 8, 9], 
    "守備位置": ["Left Field", "Second Base", "Center Field", "Right Field", 
                 "Third Base", "First Base", "Shortstop", "DH", "Catcher"],
    "選手": ["桑原将志", "小園海斗", "辰己涼介", "森下翔太", 
               "栗原陵矢", "牧秀悟", "源田壮亮", "佐野恵太", "坂倉将吾"],
    "所属球団": ["DeNA", "広島", "楽天", "阪神", 
             "ソフトバンク", "DeNA", "西武", "DeNA", "広島"]
}

# データフレームの作成
df = pd.DataFrame(data)
index 打順 守備位置 選手 所属球団
0 1 Left Field 桑原将志 DeNA
1 2 Second Base 小園海斗 広島
2 3 Center Field 辰己涼介 楽天
3 4 Right Field 森下翔太 阪神
4 5 Third Base 栗原陵矢 ソフトバンク
5 6 First Base 牧秀悟 DeNA
6 7 Shortstop 源田壮亮 西武
7 8 DH 佐野恵太 DeNA
8 9 Catcher 坂倉将吾 広島

プロンプトのセットアップ

from langchain_core.prompts import PromptTemplate

prompt_template = PromptTemplate.from_template("""
    以下の質問に答えてください。
    質問: プレミア12の日本vsベネズエラ戦で、{team} の {player} 選手の打順を教えてください。int型の数値のみ回答するようにしてください。
    
    参考情報: {table}
""")

補足ですが、元々langchainのLCEL記法で書くつもりをしていました。しかし手元の環境の都合で量子化モデルを用いることにしてtransformersからllama-cppに変更した経緯があり、llama_cpp ライブラリはプロンプトとして文字列(str 型)を期待しているのであえてPromptTempleteを用いる必要はありませんでした。
今回のケースだとテンプレートを用いずそのままプロンプトを直書きでも問題ないです。

各データ形式の検証

1. dict形式

df_dict = df.to_dict(orient="records")
df_dict

変換結果

[{'打順': 1, '守備位置': 'Left Field', '選手': '桑原将志', '所属球団': 'DeNA'},
 {'打順': 2, '守備位置': 'Second Base', '選手': '小園海斗', '所属球団': '広島'},
 {'打順': 3, '守備位置': 'Center Field', '選手': '辰己涼介', '所属球団': '楽天'},
 {'打順': 4, '守備位置': 'Right Field', '選手': '森下翔太', '所属球団': '阪神'},
 {'打順': 5, '守備位置': 'Third Base', '選手': '栗原陵矢', '所属球団': 'ソフトバンク'},
 {'打順': 6, '守備位置': 'First Base', '選手': '牧秀悟', '所属球団': 'DeNA'},
 {'打順': 7, '守備位置': 'Shortstop', '選手': '源田壮亮', '所属球団': '西武'},
 {'打順': 8, '守備位置': 'DH', '選手': '佐野恵太', '所属球団': 'DeNA'},
 {'打順': 9, '守備位置': 'Catcher', '選手': '坂倉将吾', '所属球団': '広島'}]

実行コード

# 入力データの設定
input_data = {
    "team": "阪神",
    "player": "森下翔太",
    "table": df_dict
}

# プロンプトの生成
prompt = prompt_template.format(**input_data)

# プロンプトを文字列に変換
prompt_str = str(prompt)

# モデルの実行
response = llm(prompt_str)

# 回答部分の抽出
answer = response['choices'][0]['text'].strip()

print(answer)
答え: 4

2. json形式

df_json = df.to_json(orient="records", force_ascii=False)
df_json

変換結果

[{"打順":1,"守備位置":"Left Field","選手":"桑原将志","所属球団":"DeNA"},{"打順":2,"守備位置":"Second Base","選手":"小園海斗","所属球団":"広島"},{"打順":3,"守備位置":"Center Field","選手":"辰己涼介","所属球団":"楽天"},{"打順":4,"守備位置":"Right Field","選手":"森下翔太","所属球団":"阪神"},{"打順":5,"守備位置":"Third Base","選手":"栗原陵矢","所属球団":"ソフトバンク"},{"打順":6,"守備位置":"First Base","選手":"牧秀悟","所属球団":"DeNA"},{"打順":7,"守備位置":"Shortstop","選手":"源田壮亮","所属球団":"西武"},{"打順":8,"守備位置":"DH","選手":"佐野恵太","所属球団":"DeNA"},{"打順":9,"守備位置":"Catcher","選手":"坂倉将吾","所属球団":"広島"}]

実行コード

# 入力データの設定
input_data = {
    "team": "阪神",
    "player": "森下翔太",
    "table": df_json
}

# プロンプトの生成
prompt = prompt_template.format(**input_data)

# プロンプトを文字列に変換
prompt_str = str(prompt)

# モデルの実行
response = llm(prompt_str)

# 回答部分の抽出
answer = response['choices'][0]['text'].strip()

print(answer)
答え: 4

3. pipe形式

df_pipe = df.to_csv(index=False, sep="|", mode='a')
df_pipe

変換結果

打順|守備位置|選手|所属球団
1|Left Field|桑原将志|DeNA
2|Second Base|小園海斗|広島
3|Center Field|辰己涼介|楽天
4|Right Field|森下翔太|阪神
5|Third Base|栗原陵矢|ソフトバンク
6|First Base|牧秀悟|DeNA
7|Shortstop|源田壮亮|西武
8|DH|佐野恵太|DeNA
9|Catcher|坂倉将吾|広島

実行コード

# 入力データの設定
input_data = {
    "team": "阪神",
    "player": "森下翔太",
    "table": df_pipe
}

# プロンプトの生成
prompt = prompt_template.format(**input_data)

# プロンプトを文字列に変換
prompt_str = str(prompt)

# モデルの実行
response = llm(prompt_str)

# 回答部分の抽出
answer = response['choices'][0]['text'].strip()

print(answer)
答え: 4

4. list形式

df_list = df.values.tolist()
df_list

変換結果

[[1, 'Left Field', '桑原将志', 'DeNA'],
 [2, 'Second Base', '小園海斗', '広島'],
 [3, 'Center Field', '辰己涼介', '楽天'],
 [4, 'Right Field', '森下翔太', '阪神'],
 [5, 'Third Base', '栗原陵矢', 'ソフトバンク'],
 [6, 'First Base', '牧秀悟', 'DeNA'],
 [7, 'Shortstop', '源田壮亮', '西武'],
 [8, 'DH', '佐野恵太', 'DeNA'],
 [9, 'Catcher', '坂倉将吾', '広島']]

実行コード

# 入力データの設定
input_data = {
    "team": "阪神",
    "player": "森下翔太",
    "table": df_list
}

# プロンプトの生成
prompt = prompt_template.format(**input_data)

# プロンプトを文字列に変換
prompt_str = str(prompt)

# モデルの実行
response = llm(prompt_str)

# 回答部分の抽出
answer = response['choices'][0]['text'].strip()

print(answer)
答え: 4

5. markdown形式

df_markdown = df.to_markdown(index=False)
df_markdown

変換結果

|   打順 | 守備位置     | 選手     | 所属球団     |
|-------:|:-------------|:---------|:-------------|
|      1 | Left Field   | 桑原将志 | DeNA         |
|      2 | Second Base  | 小園海斗 | 広島         |
|      3 | Center Field | 辰己涼介 | 楽天         |
|      4 | Right Field  | 森下翔太 | 阪神         |
|      5 | Third Base   | 栗原陵矢 | ソフトバンク |
|      6 | First Base   | 牧秀悟   | DeNA         |
|      7 | Shortstop    | 源田壮亮 | 西武         |
|      8 | DH           | 佐野恵太 | DeNA         |
|      9 | Catcher      | 坂倉将吾 | 広島         |

実行コード

# 入力データの設定
input_data = {
    "team": "阪神",
    "player": "森下翔太",
    "table": df_markdown
}

# プロンプトの生成
prompt = prompt_template.format(**input_data)

# プロンプトを文字列に変換
prompt_str = str(prompt)

# モデルの実行
response = llm(prompt_str)

# 回答部分の抽出
answer = response['choices'][0]['text'].strip()

print(answer)
答え: 4

6. yaml形式

import yaml

# YAMLに変換
df_yaml = yaml.dump(df.to_dict(orient="records"), allow_unicode=True)
df_yaml

変換結果

- 守備位置: Left Field
  所属球団: DeNA
  打順: 1
  選手: 桑原将志
- 守備位置: Second Base
  所属球団: 広島
  打順: 2
  選手: 小園海斗
- 守備位置: Center Field
  所属球団: 楽天
  打順: 3
  選手: 辰己涼介
- 守備位置: Right Field
  所属球団: 阪神
  打順: 4
  選手: 森下翔太
- 守備位置: Third Base
  所属球団: ソフトバンク
  打順: 5
  選手: 栗原陵矢
- 守備位置: First Base
  所属球団: DeNA
  打順: 6
  選手: 牧秀悟
- 守備位置: Shortstop
  所属球団: 西武
  打順: 7
  選手: 源田壮亮
- 守備位置: DH
  所属球団: DeNA
  打順: 8
  選手: 佐野恵太
- 守備位置: Catcher
  所属球団: 広島
  打順: 9
  選手: 坂倉将吾

実行コード

# 入力データの設定
input_data = {
    "team": "阪神",
    "player": "森下翔太",
    "table": df_yaml
}

# プロンプトの生成
prompt = prompt_template.format(**input_data)

# プロンプトを文字列に変換
prompt_str = str(prompt)

# モデルの実行
response = llm(prompt_str)

# 回答部分の抽出
answer = response['choices'][0]['text'].strip()

print(answer)
答え: 4

7. csv形式

df_csv = df.to_csv(index=False)
df_csv

変換結果

打順,守備位置,選手,所属球団
1,Left Field,桑原将志,DeNA
2,Second Base,小園海斗,広島
3,Center Field,辰己涼介,楽天
4,Right Field,森下翔太,阪神
5,Third Base,栗原陵矢,ソフトバンク
6,First Base,牧秀悟,DeNA
7,Shortstop,源田壮亮,西武
8,DH,佐野恵太,DeNA
9,Catcher,坂倉将吾,広島

実行コード

# 入力データの設定
input_data = {
    "team": "阪神",
    "player": "森下翔太",
    "table": df_csv
}

# プロンプトの生成
prompt = prompt_template.format(**input_data)

# プロンプトを文字列に変換
prompt_str = str(prompt)

# モデルの実行
response = llm(prompt_str)

# 回答部分の抽出
answer = response['choices'][0]['text'].strip()

print(answer)
答え: 4

8. tsv形式

df_tsv = df.to_csv(index=False, sep='\t')
df_tsv

変換結果

打順	守備位置	選手	所属球団
1	Left Field	桑原将志	DeNA
2	Second Base	小園海斗	広島
3	Center Field	辰己涼介	楽天
4	Right Field	森下翔太	阪神
5	Third Base	栗原陵矢	ソフトバンク
6	First Base	牧秀悟	DeNA
7	Shortstop	源田壮亮	西武
8	DH	佐野恵太	DeNA
9	Catcher	坂倉将吾	広島

実行コード

# 入力データの設定
input_data = {
    "team": "阪神",
    "player": "森下翔太",
    "table": df_tsv
}

# プロンプトの生成
prompt = prompt_template.format(**input_data)

# プロンプトを文字列に変換
prompt_str = str(prompt)

# モデルの実行
response = llm(prompt_str)

# 回答部分の抽出
answer = response['choices'][0]['text'].strip()

print(answer)
答え: 4

9. latex形式

df_latex = df.to_latex(index=False)
df_latex

変換結果

\begin{tabular}{rlll}
\toprule
打順 & 守備位置 & 選手 & 所属球団 \\
\midrule
1 & Left Field & 桑原将志 & DeNA \\
2 & Second Base & 小園海斗 & 広島 \\
3 & Center Field & 辰己涼介 & 楽天 \\
4 & Right Field & 森下翔太 & 阪神 \\
5 & Third Base & 栗原陵矢 & ソフトバンク \\
6 & First Base & 牧秀悟 & DeNA \\
7 & Shortstop & 源田壮亮 & 西武 \\
8 & DH & 佐野恵太 & DeNA \\
9 & Catcher & 坂倉将吾 & 広島 \\
\bottomrule
\end{tabular}

実行コード

# 入力データの設定
input_data = {
    "team": "阪神",
    "player": "森下翔太",
    "table": df_latex
}

# プロンプトの生成
prompt = prompt_template.format(**input_data)

# プロンプトを文字列に変換
prompt_str = str(prompt)

# モデルの実行
response = llm(prompt_str)

# 回答部分の抽出
answer = response['choices'][0]['text'].strip()

print(answer)
答え: 4

まとめ

今回の検証では、pandas DataFrameをさまざまな形式に変換し、それをLLMに入力して適切な応答が得られるかをテストしました。その結果、基本的にはどの形式でもうまく回答できることがわかりました。

今後の課題と次のステップ

今回の検証を踏まえ、今後検討すべき課題は以下があると思います。

  • LLMのコンテキストサイズを超えるデータへの対応:
    テーブルを分割するか、別の連携方法をとる
  • 使用モデルによる精度の差異:
    企業モデルや他のOSSモデルで試す
  • より複雑なデータセットを用いた検証:
    もっとレコードの多いデータで試してみる

これらの要件を変えることで、dictcsvなどLLMが理解するのにより適している形式が新しく見えてくるかもしれません。

おわりに

この記事では、テーブルデータをLLMに入力するための形式変換とその検証結果を共有しました。この結果が、皆さんのプロジェクトやデータ活用に役立つヒントとなれば幸いです。
最後までお読みいただきありがとうございました!この記事が役に立った場合は、ぜひ「いいね」やコメントをお願いします!

12
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?