Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Text-to-SQLの既存の評価指標を問い直す

Avatar for Gota Gota
July 09, 2025

 Text-to-SQLの既存の評価指標を問い直す

Text-to-SQLを実際のデータ分析業務に適用する際の評価指標に関する問題と解決策を提案。 既存の評価指標であるEM(完全一致度)やEX(実行精度)では、SQL構文やデータ型、カラム名がわずかに違うだけで不正解と判定されてしまいます。 しかし、これらはビジネスの観点では実質的に「正解」と見なせる場合があります。 このギャップを埋めるため、意味的な同値性や部分一致を許容する柔軟な新指標「Soft EX」を提案し、実用的なビジネス価値を重視した評価の重要性を説いています。

Avatar for Gota

Gota

July 09, 2025
Tweet

More Decks by Gota

Other Decks in Science

Transcript

  1. 名前 Gota (X: @gota_bara) やってること • 小売向けデータプロダクト / 開発AIエージェント開発 /

    データ基盤構築 最近の関心事 • 生成UI / AIエージェントの体験設計 (目指せワールドトリガーのレプリカ) • アニメ (ジブリを再履修中 / フリーレン) Claude Code 良く使ってます 自己紹介 2 / 21
  2. EM (Exact Match) - 完全一致度 評価方法: SQLの文字列が完全一致で正解 • ✅ SELECT

    * FROM users; • ❌ select * from users; (大文字小文字で不正解) 代表的な評価指標 ① 4 / 21
  3. VES (Valid Efficiency Score) - 効率性スコア 評価方法: 結果一致 + 実行効率(実行時間・メモリ使用量・CPU使用率)を総合評価

    • ✅ 正解 + 高速実行(< 100ms) • ⚠️ 正解 + 低速実行(> 1000ms) • ❌ 不正解またはタイムアウト 代表的な評価指標 ③ 6 / 21
  4. 質問: 「最も価格が高い商品の商品名を答えてください」 ✅ 正解SQL(サブクエリ) MAX価格の商品名を取得 ❌ LLM生成SQL(ORDER BY) ORDER BY

    + LIMITで商品名を取得 同じ結果を返すのに、構文が違うだけで完全不一致 -- 結果: 「プレミアムスマートフォン」 SELECT name FROM products WHERE price = (SELECT MAX(price) FROM products); -- 結果: 「プレミアムスマートフォン」 SELECT name FROM products ORDER BY price DESC LIMIT 1; EM(Exact Match)の問題点 8 / 21
  5. 質問: 「最も売上が高いJANCODEを求めてください」 ✅ 正解SQL(STRING型) 13桁の0埋めSTRINGとして出力 ❌ LLM生成SQL(INT型) INTとして出力(先頭0が消失) 同じJANCODEでも、データ型の違いで先頭0が消失し完全不一致 --

    結果: "0123456789012" SELECT FORMAT("%013d", jancode) AS jancode_str FROM products p JOIN sales s ON p.product_id = s.product_id GROUP BY jancode ORDER BY SUM(sales_amount) DESC LIMIT 1; -- 結果: 123456789012 SELECT jancode FROM products p JOIN sales s ON p.product_id = s.product_id GROUP BY jancode ORDER BY SUM(sales_amount) DESC LIMIT 1; EX(Execution Accuracy)の問題点 ① 9 / 21
  6. 質問: 「商品の平均価格を求めてください」 ✅ 正解SQL(ROUND処理なし) そのまま平均を計算 ❌ LLM生成SQL(ROUND処理) 小数点第2位まで四捨五入 ビジネス的には同じ値なのに、ROUND処理で完全不一致 --

    結果: 12345.678901234 SELECT AVG(price) AS avg_price FROM products WHERE category = 'electronics'; -- 結果: 12345.68 SELECT ROUND(AVG(price), 2) AS avg_price FROM products WHERE category = 'electronics'; EX(Execution Accuracy)の問題点 ② 10 / 21
  7. 質問: 「コンバージョン率を求めてください」 ✅ 正解SQL(率として出力) 0.0-1.0の範囲で出力 ❌ LLM生成SQL(実数として出力) 100倍して 0 ~

    100%の範囲で出力 ビジネス的には同じ11%なのに、表記方法で完全不一致 -- 結果: 0.11 (11%) SELECT SAFE_DIVIDE( COUNT(CASE WHEN action = 'purchase' THEN 1 END), COUNT(*) ) AS conversion_rate FROM user_actions WHERE DATE(timestamp) = '2024-07-01'; -- 結果: 11.0 (11%) SELECT SAFE_DIVIDE( COUNT(CASE WHEN action = 'purchase' THEN 1 END), COUNT(*) ) * 100 AS conversion_rate FROM user_actions WHERE DATE(timestamp) = '2024-07-01'; EX(Execution Accuracy)の問題点 ③ 11 / 21
  8. 質問: 「2025年7月の売上データを抽出してください」 ✅ 正解SQL(統合型) year_month (STRING) 1カラム ❌ LLM生成SQL(分離型) year

    (INT), month (INT) 2カラム 同じ期間(2025年7月)なのに、カラム構造の違いで完全不一致 -- 結果: "2025-07" SELECT FORMAT_DATE('%Y-%m', purchase_date) AS year_month, SUM(sales_amount) AS total_sales FROM sales_data WHERE EXTRACT(YEAR FROM purchase_date) = 2025 AND EXTRACT(MONTH FROM purchase_date) = 7 GROUP BY FORMAT_DATE('%Y-%m', purchase_date); -- 結果: 2025, 7 SELECT EXTRACT(YEAR FROM purchase_date) AS year, EXTRACT(MONTH FROM purchase_date) AS month, SUM(sales_amount) AS total_sales FROM sales_data WHERE EXTRACT(YEAR FROM purchase_date) = 2025 AND EXTRACT(MONTH FROM purchase_date) = 7 GROUP BY EXTRACT(YEAR FROM purchase_date), EXTRACT(MONTH FRO EX(Execution Accuracy)の問題点 ④ 12 / 21
  9. 質問: 「各顧客の総購入金額を求めてください」 ✅ 正解SQL(total_purchase) 標準的なカラム名 ❌ LLM生成SQL(sum_amount) 別のカラム名(同じ意味) 同じ顧客情報を指すが、カラム名の違いで完全不一致 --

    結果カラム: total_purchase SELECT user_id, SUM(purchase_amount) AS total_purchase FROM orders GROUP BY user_id ORDER BY total_purchase DESC; -- 結果カラム: sum_amount SELECT user_id, SUM(purchase_amount) AS sum_amount FROM orders GROUP BY user_id ORDER BY sum_amount DESC; EX(Execution Accuracy)の問題点 ⑤ 13 / 21
  10. 質問: 「全ユーザーの平均年齢を求めてください」 ✅ 正解SQL(小規模DB) 小規模DBで高速実行 ❌ LLM生成SQL(大規模DB) 大規模DBで低速実行 同じSQLなのに、データ量や実行環境で評価が変わる --

    結果: 28.5 ( 実行時間: 50ms) SELECT AVG(age) FROM users; -- 結果: 28.5 ( 実行時間: 5000ms) SELECT AVG(age) FROM users; VES(Valid Efficiency Score)の問題点 14 / 21
  11. 評価指標 問題 内容 BIRD Spider 2.0 EX ① データ型の違い (STRING

    vs INT) ❌ △ ② ROUND処理 (小数点精度) ❌ ✅ ③ 表記方法 (率 vs パーセント) ❌ ❌ ④ カラム名の違い (意味的同一) ❌ △ ⑤ カラム構造の違い (統合型 vs 分離型) ❌ ❌ VES 実行環境依存 (DB規模による性能差) ✅ ❌ 各問題に対する SQL Bench 対応状況 15 / 21
  12. 問題領域 技術的な違い ビジネス的な解釈 結果の順序 ORDER BY未指定で行順序が異なる 同じデータなら順序は関係なし LIMIT有無 行数が異なる 上位N位以内のデータが取得できていれば

    解釈可能 NULL値処理 NULL vs 0 vs 空文字で結果が違う 「データなし」の意味は同じ データ型表現 文字列 "123" vs 数値 123 値の意味は同じ 評価指標は厳密な正確性より、実用的なビジネス価値を重視 厳密には「不正解」だが、ビジネス的には「正解」な結果 16 / 21
  13. # 従来のEX :厳密な一致を要求 def traditional_ex(gold_result, predicted_result): return gold_result == predicted_result

    # Soft EX :柔軟な評価指標 class ResultComparator: def compare_results(self, gold_df, pred_df, ignore_order=True): # 1. カラム名の柔軟なマッピング column_mapping = self._find_column_mapping(gold_df.columns, pred_df.columns) # 2. データの正規化(数値・日付・文字列) gold_normalized = self._normalize_dataframe(gold_df) pred_normalized = self._normalize_dataframe(pred_df) # 3. 柔軟な実行精度を計算(0.0 ~ 1.0 の連続値) soft_accuracy = self._compare_dataframes( gold_normalized, pred_normalized, ignore_order ) return {"soft_execution_accuracy": soft_accuracy} Soft EXの実装方法はこんな感じ 18 / 21
  14. 2025年5月に発表されたLiveSQLBenchにもSoft EXが存在する!(最近知った) 評価要素 LiveSQLBench Soft EX 独自指標 Soft EX 基本アプローチ

    SELECT-ONLY タスク評価 全般的なSQL結果比較 行順序の扱い デフォルトで無視 設定可能(通常は無視) 日付統一 YYYY-MM-DD形式に統一 多様な形式 + 文字列化 クエリ前処理 DISTINCT, ROUND除去 実行結果のみ比較 カラム名対応 順序ベース比較 意味的類似度 + 同義語マッチング LiveSQLBench:クエリ前処理重視、独自指標:結果比較の柔軟性重視 LiveSQLBench Soft EX vs 独自指標の比較 19 / 21
  15. 「何を重視して評価するか」で既存の評価指標が使えるかどうか変わる • 既存評価指標(EM, EX, VES)の厳密性はビジネス利用時には必要ない • より結果比較に柔軟性を持たせた評価指標 Soft EX を作った

    これから... Text-to-SQL以外も雑に評価できるようにした方が最終的にスピードが上がる感あるため、データ分析への生成AI の適用の際にもその都度"評価"も大切にやっていきたい まとめ 20 / 21