はじめに
近年、LLM(Large Language Model)の活用がさまざまな分野で進んでいます。その中で、テーブルデータ(例えば、pandasのDataFrameなど)をLLMに渡し、データ解析や要約を行いたいという場面も出てくると思います。
しかし、LLMは構造化データを直接的に理解することができません。具体的には、LLMは自然言語やテキスト形式を処理するように設計されており、以下の理由からテーブルデータを適切な形式に変換する必要があります。
なぜテーブルデータの変換が必要なのか?
-
LLMはテキスト処理を前提に設計されている
- LLMは文章や単純なテキスト構造をもとにトレーニングされており、
DataFrame
やCSV
などの形式をそのままでは解釈できません。 - そのため、テーブルデータをLLMが処理可能な形式(例えば、JSONやMarkdownなど)に変換する必要があります。
- LLMは文章や単純なテキスト構造をもとにトレーニングされており、
-
データの構造を保つため
- テーブルデータには行・列という構造があります。この構造を損なわずにLLMに渡すことで、データの意味や関係性を正確に理解させることができます。
- 適切なフォーマットを選ぶことで、LLMが「どの列が何を表しているか」を認識しやすくなります。
-
応答の精度向上のため
- 適切な形式でデータを渡すと、LLMはより高い精度で応答を生成できます。逆に、フォーマットが不適切だと情報が欠落したり、誤解される可能性があります。
なぜこの検証を行うのか?
テーブルデータをテキストで表現するにあたり、データ構造が複雑であるとLLMが行列の関係を正しく理解できず解釈を誤ってしまうケースがあるのではないでしょうか。変換するフォーマットが違うことで解釈の精度差が出てくることも考えられます。
そういった疑問を感じたため検証を行いました。
目次
結果のサマリー
結論、どの形式でもちゃんと回答できました
ただし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モデルで試す -
より複雑なデータセットを用いた検証:
もっとレコードの多いデータで試してみる
これらの要件を変えることで、dict
やcsv
などLLMが理解するのにより適している形式が新しく見えてくるかもしれません。
おわりに
この記事では、テーブルデータをLLMに入力するための形式変換とその検証結果を共有しました。この結果が、皆さんのプロジェクトやデータ活用に役立つヒントとなれば幸いです。
最後までお読みいただきありがとうございました!この記事が役に立った場合は、ぜひ「いいね」やコメントをお願いします!