📖

第3回金融データ活用チャレンジ参加記

2025/02/16に公開

記事の概要

この記事はsigateで開催されたFDUA 一般社団法人金融データ活用推進協会、金融庁共催の第3回金融データ活用チャレンジ参加記(https://signate.jp/competitions/1515) の参加記です。
RAGを題材にしたコンペティションで、生成AIわからない中で取り組んでみた内容や感想を記載しています。
https://signate.jp/competitions/1515

自己紹介

通信業界で通信品質分析をしている部署に所属しています。
データ分析コンペティションは学生時代に興味を持ち参加しており、社会人になっても時々参加しています。
参加してきたコンペは多くがテーブルデータで、RAG、というより生成AIが絡むコンペは出たことがありませんでした。
生成AI全然わからない状態だったので、少しでもわかるきっかけになったらと思い参加しました!

コンペ概要

コンペテーマ

コンペのテーマは、企業のESGレポートや統合報告書に関連する質問に対して、RAGを用いて回答した精度を競うものでした。

RAGシステムの構造イメージ(コンペサイトから引用)

提供データ

  • 企業のESGレポートや統合報告書のpdfファイル: コンペへの提出用に19企業、検証用に10企業分
  • 各レポートに対する質問: コンペへの提出用に100問、検証用に50問
  • 応募用サンプルファイル
  • 評価用プログラム群(検証用スコア算出用)
  • readme
    などが提供されました。

評価指標

提出者の回答と真の回答をLLMにより4つの基準のどれに当てはまるか判定させ、その判定に基づく点数の平均値により評価されました。

採点基準(コンペサイトから引用)

提供環境

本コンペは以下画像のように複数の企業から環境提供をいただきました。
(自分はコーディングをして回答を作ったので、日本マイクロソフト様と日立製作所様の提供APIを使わせていただきました。)
また、参加者間のコミュニケーション、質問用のSlackが提供されました。
(LLMについて何もわからなかったのでとても参考になりました。特にgemini-2.0-flashの無料枠があること知らなかったのでこれでできることの幅が広がりました。)

提供環境(コンペサイトから引用)

解法まとめ

最終提出に利用したLLMはすべてgemini-2.0-flashでした。

  1. pdfからpythonライブラリ、LLMを使って情報抽出。
  2. 1.で抽出した情報をLLMに入力し、質問に回答してもらう。その際、根拠を出力してもらう。これを入力情報を変えたり、抽出する方法を変えたりした複数パターンで実施する。
  3. 2.で出力した根拠をまとめるLLMに入力し、回答してもらう。これを最終出力とする。

解法詳細

pdfからの情報抽出

今回のコンペで提供されたデータはpdfファイルであったことから、このpdfファイルからテキスト情報を抽出してLLMに入れることを考えました。
そのために実施した方法は

  1. pythonライブラリを利用したテキスト情報抽出
  2. LLMを用いたテキスト情報抽出
    です。
    入力情報時点で情報が落ちてしまうと、後段でどれだけ頑張ってもないものはないので精度を出すのは難しいのでは?と思い、何パターンか試しました。結果としてここにだいぶ時間がかかりました…

pythonライブラリを利用したテキスト情報抽出

ライブラリはいくつかパターンを試しましたが、pymupdf4llmを使って

pymupdf4llm.to_markdown(path, page_chunks=True)

のようにページごと読み込んだ結果をmarkdown形式で出力するのが今回は一番情報が取れているように見えました。
他にもpypdfium2で

import pypdfium2 as pdfium
pdf = pdfium.PdfDocument(path)
for c, page in enumerate(pdf):
    textpage = page.get_textpage()
    text = textpage.get_text_range()

のように出力するのも結構いい感じで、pymupdf4llmのみで取りきれなかった情報を取れている部分もありました。
ただ、今回はpymupdf4llmの結果を使った方がスコアが出たのでこちらを採用しました。うまくやれば2つの情報を補完しあえたのかもしれないです。
また、このテキストはあまり形式がきれいになっていなかったのでLlama3.3 70Bにきれいに整形してもらった出力を途中まで使っていましたが、この処理により精度が落ちていたことがわかりやめました。
これはLlama3.3 70Bの入力できるトークン数を全然理解せず使っていたことが1つの要因と思います…(日立様に提供いただいたもので8192トークンが最大、これを明らかに超えたものがありました。この場合だとどういう処理をされるのかわかっていないので今後勉強します…)

LLMを用いたテキスト情報抽出

pythonライブラリを利用したテキスト情報抽出だけで途中まで頑張っていましたが、特に図表の中身などで取れていない情報は結構あるように見えました。
そこで行き詰まっていたところ、参加者のSlackに
「画像をLLMに読ませ、テキスト抽出する」
ようなコメントがあるのを見つけ、やってみました。
モデルはgemini-2.0-flashで、pdfから変換したスライドごとの画像と、そのページに対応するpythonライブラリで抽出したテキストを入力とし、以下のようなプロンプトでmarkdown形式で出力してもらいました。

desc = """
    あなたは画像内の文章、図表から情報を抽出し、markdown言語で出力をする優秀なエージェントです。
    以下に画像と、事前に画像から抽出した文章を記載します。
    これら情報から、画像の内容をmarkdown言語で出力してください。

    注意点:
      - 画像内の文章、図表の情報はすべて網羅して、情報を落とさないでください。
      - 事前に画像から抽出した文章は精度が高くないので参考にとどめてください。
      - すべて日本語で出力してください。
      - 画像にない情報はつけないでください。
    """

結果、この出力により図表がうまく取れている様子でした。
ただ、一部画像の解像度が低かったためか読み取れず、また読み取れなかった部分以降の文章が全て空白になる事件が起きていました。(なぜ?)
解消方法を検討する時間もテクニックも残念ながらなかったため、基本はLLMを用いたテキスト情報を使いつつ、空白が大量に入っているページはpythonライブラリで抽出したテキストで補完する戦略を取りました。

LLMによる回答

シングルモデル

最初はRAGということで、抽出したテキストを文単位に区切り、検索(RAGシステムの構造イメージでいうところのRetrieve)を実施して関連のある文章のみ絞ってをLLMに入力して回答を得ていました。
ただ、Retrieveの時点でうまく検索できず情報落ちしてしまい、LLMが回答できないまたは明らかに誤った回答を導かざるを得ない様子が見て取れました。
そこで、どの企業のpdfを見るかのRetrieveをした後、そのpdfの文単位でもより多くの検索結果を渡す→スライド単位で渡す→全文渡す、のようにどんどん入力を増やしてみました。(検索精度を上げるアプローチもあると思うのですが…)
また、LLMには入力できるトークン数が決まっているので、それに合わせて使えるLLMが自ずと絞られてきました。
全文入れると情報量が多すぎるので精度低下の可能性もあると思ったのですが、gemini-2.0-flashではそこまで落ちずすごいと思いました。
以下、1つの解答生成LLMで提出した結果です。(長すぎる回答の場合回答トークン数制限に引っかかるため、長すぎる回答が出た場合にはLlama3.3 70Bに短く要約してもらってはいます。)

入力データ LLMへの入力方法 回答生成LLM 手元のスコア 暫定評価スコア
pythonライブラリのみ RAG(文単位、上位10文) Llama3.3 70B 0.32 -0.01
pythonライブラリのみ RAG(スライド単位、上位30スライド) gpt-4omini 0.42 0.06
LLM出力テキスト 全文 gemini-2.0-flash 0.5 0.39
LLM出力テキスト RAG(スライド単位、上位30スライド) gemini-2.0-flash 0.55 0.42
LLM出力テキスト+pythonライブラリ 全文 gemini-2.0-flash 0.6 0.48

手元のスコアと暫定評価スコアが結構乖離してしまったのがしんどかったです…

回答根拠を用いたアンサンブルモデル

シングルモデルだと、回すたびに若干出力が変わって見えました。
また、データソースも変えると結果が変わって見えます。
そこから、データソースを変えたりパラメータを変えたいろいろなLLMに回答根拠を出してもらい、それをまとめてくれるLLMを準備して回答してくれればいい感じになるのでは?と考え、実行してみました。(機械学習のアンサンブルで精度を上げるイメージです。)
これを実施した結果、暫定評価スコアが0.4台→0.6程度にアップしました!
シングルモデルの出力には根拠を出してもらっていなかったので、急いで根拠を出し直すようにプロンプトを変えて再実行しました。これが締め切り前日とかだったと思います…

出した根拠をまとめてもらうのに使っていたプロンプト
    """
    あなたはある生成AIが回答した根拠を正確に理解し、質問に対し優れた回答をする優秀なAIです。
    今から、質問と、複数の生成AIがそれに回答した根拠を提示します。
    以下のルールに基づき、質問と生成AIの根拠から、質問に対するより優れた回答をしてください。

    1. **回答のルール**
      - 生成AIの回答が50文字を超える場合は、必ず50文字以内で回答してください。
      - 文字数は厳密に守ってください(50文字以内)。
      - 質問の内容を踏まえて回答してください。根拠だけを見ないでください。
      - 生成AIの回答が曖昧、または記載がない場合、「分かりません」と回答してください。
      - 与えられた情報のみを用いて簡潔に回答してください。
      - 複数の生成AIの根拠はそれぞれ異なる場合があります。全て読んで理解した上で回答してください。
      - 数字は半角で記載してください。
      - 空白文字、改行は入れないでください。

    2. **言葉遣いのルール**
      - 回答は丁寧語である必要はありません。

    3. 採点基準
      - 回答はGPT4oにより以下の定義で採点されます。なるべくPerfectになるような回答をしてください。
      - 正誤判定は意味をとらえて実施してくれません。簡潔に答えてください。
        # 定義
        Perfect: answerが問題に正しく回答しており, 幻覚的な内容を含んでいない。
        Acceptable: answerが問題の回答として有効な内容を含んでいるが, わずかな誤りも含んでいる. ただし, 有効性を壊すほどではない。
        Missing: answerが「わかりません」,「見つかりません」, 空の回答, または元の質問を明確にするための要求を含んでいる。
        Incorrect: answerが間違っているか問題と無関係な内容を含んでいる。

    4. 解答例
      - 質問に対し、以下の解答例のように答えてください。
      例1.)
        質問: A社が積極的に資源配分を行うとしている高付加価値セグメントを全てあげてください。
        回答: 改修セグメント、医療用・産業用セグメント、官公庁セグメント
      例2)
        質問: B社の生産拠点数は何拠点ですか?
        回答: 36拠点
      例3)
        質問: C社の2023年度の売上高は2022年度と比較して何%上昇したか、少数第二位を四捨五入して答えよ
        回答: 3.6%

    質問:
    {question}
    生成AIの出した根拠: 
    {answer}
    """

感想

各LLMの性能について(完全主観)

今回のコンペではAPI提供をいただいたこともあり複数のLLMを使ってみることができました。
そこで感じた感想をまとめます。
LLM初心者の主観なので、実際の性能とは違ったり、能力を引き出すプロンプトが入力できていない可能性はあります…
ちなみに、今回課金はせず、提供いただいたAPIか無料枠の範囲で全て実施しています。

LLM 感想
Llama3.3 70B 途中までメインに利用。入力トークン数がもっと欲しかった…
プロンプトでxx文字以内で回答して欲しいと言っているのに冗長な回答をしてしまうことがあり、たまに指示を守ってくれなかった。
gemini-2.0-flashが毎回間違えている問題を正解することもあり、賢さ自体はそれなりにありそうに感じた。
Qwen 2.5 72B 出力に中国語がなぜか出てくることがあった。
今回は言葉が変わると使いにくく感じたのと、レスポンスが遅めに感じたためあまり利用せず。
gpt-4omini 途中で使ってみた。それなりに賢いが、ちょっと複雑な問題になるとどうしてその答えになった?という回答をすることも。
今回の自分の用途だとgemini-2.0-flashの方が精度が良く、あまり使いこなせずだった。
レスポンスは結構早かった印象。
gemini-2.0-flash 最後に使ってみた。今回使った中では最も賢く感じた。
全文を入れてもそこまで性能劣化せず回答根拠になりそうな部分を抜き出せていたのはすごいと感じた。
一方、最終回答と根拠が矛盾していたり、プロンプトによっては指示を聞いてないような回答をすることもあった。
画像の読み取りも想像以上に優秀だった。
ただ、画像に読み取れなかった図表あった場合、それ以降の文章も読めず空白が大量に入る処理はイマイチに感じた。(今回の例だけ…?)
無料枠が1,500リクエスト/日あったので、今回のように100問解く程度なら十分使えた。
(参考)DeepSeek R1 コンペ終了後にAPI提供いただいたのでアンサンブルモデルのまとめをgemini-2.0-flashから差し替えて使ってみた。
gemini-2.0-flashが回答と根拠が矛盾している問題も、根拠がおかしいと判断できておりかなり賢そう。
回答は回答として出力し、思考過程は<think>という文字列で囲んで記載してくれるので理由がわかって使いやすそう。
ただ、<think>の文字は中国語になる時があったり、文体が不安定になる時がある。
今回のコンペ中も使えたらgemini-2.0-flashの間違い部分は訂正してもらうなどできて、スコア向上に寄与したかもしれない。
入力トークン数も大きそうなのでメインモデルとして使うこともできたかもしれない。

個人の感想

生成AIについて知ることができた!
これまで生成AIにはそこまで触れておらず、関連技術であるRAGについも話は聞くもののあまり理解できていなかったです。
ただ、今回実際に自分で実装してみることで生成AIの挙動や、RAGがどんなものかのイメージができるようになり良かったです。
プロンプトで結構出力変わるんだなと…
まだまだわからないことがたくさんあるので、学んでいこうという気になりました。

LLMを用いたプログラミングの経験ができた!
これまでLLMはあまり触ってきておらず、使うにはどんなコードを書いたらいいのかよくわかっていませんでした。
今回コンペに参加して試行錯誤することで経験値を積むことができました。
ただ、後半は自分で作ったプログラムを流用し続けてしまったのでもっと書いて経験積めば良かったなとも思います。
また、LangChainの書き方などちょっとライブラリごとのクセがあるように感じ、もっと慣れないとまだまだ書けないなと感じました…

提供いただいたAPIを使えてよかった!
今回は日本マイクロソフト様、日立製作所様からAPI提供をいただきました。これにより初心者が制限をあまり気にせず触ってみれるよい機会となり、試行錯誤ができました。
実際にシステムとして使うときも、まず安いLLMで試行錯誤→高いLLMで本番に近い実装をすることもあるかと思い、そういった使い分けの擬似体験ができたかなと思います。

記事を書くきっかけになった!
これまでこのような記事を書いたことがなかったのですが、企業賞もあるということでせっかくなので書いてみようというきっかけになりました。実際書いてみるとうまく書くの難しいですね…LLMには短く回答してと言っているのに自分で書いた文章は冗長な気が…

LLMコンペでまさかのメダル圏内に入れた!
LLMコンペは初めて出たこともあり、そこまで順位に期待はしていませんでした。
特に最初マイナスのスコアが出たタイミングでは「もう勉強ということに振り切るか…」と思っていたのですが、いろいろ工夫してスコアが上がり、また最終評価ではシェイクアップしたこともあり(投稿時点で順位未確定ですが)メダル圏内に入れていそうです。諦めず挑戦できてよかったです。

おわりに

一般社団法人金融データ活用推進協会、協賛企業、コンペ参加者など、本コンペに関わった皆様、ありがとうございました!

Discussion