初めに
第3回金融データ活用チャレンジに参加しましたので、その記録を残しておきたいと思います。オープンAI、生成AI、RAGなど言葉についてはよく耳にしますが、実際にどのようなものかというのはイメージがつかず、質問を投げれば、どういう仕組みかわからないが生成AIが回答をしてくれる便利なテクノロジーとしか理解していませんでした。
このままではいけないなと思っていた矢先にこのようなコンペが開催されることを知り非常に幸運でした。
結論としてはRAGを使ったLLMがどのようなプロセスを経て、質問に対する回答を作成するかイメージできるようになりました。
コンペについて
コンペの内容は複数の日本の上場企業の総合報告書のPDFファイルと質問リストが提供され、その質問をLLMに回答させると言うものです。回答が正解なら加点、間違っていたら減点、「わかりません」と答えたらプラスマイナスゼロ点です。
この「わかりません」が曲者で、LLMに「わかりません」と答えさせるのは非常に難しいことが実際に作業を開始してからわかりました。
開発用のサンプルの報告書のPDFファイルと回答付きの質問リストが提供されたので、まずはこれを使って、いかに正解の回答を生成させるかのTry & Errorを繰り返しました。
開発用のサンプルの質問と回答はこのようなものです。
質問:大成温調が積極的に資源配分を行うとしている高付加価値セグメントを全てあげてください。
回答: 改修セグメント、医療用・産業用セグメント、官公庁セグメント
回答の個所は提供された大成温調のPDFの14ページ目に記述がありますので、ここのページの文章と質問をAIに渡してあげて模範解答と同じ回答が返ってきたら正解でプラス一点です。しかし、私が初めて行ったときはセグメントの言葉が抜けて、なぜか「改修、医療用、産業用、官公庁」で返ってきました。このような回答のずれをいかに直すかがコンペの肝でした。
開発準備
コンペでは、マイクロソフト様、datarobot様、日立製作所様、dataiku様のLLMを選べるチャンスがありましたが、Pythonでコードを書いてコンペに参加したく、マイクロソフト様のAzureOpenAIを使うことに決めました。自分で苦労してコードを書いた分、LLMのプロセスの理解が深まった気がします。
開発環境を作る
まずは開発環境を作らないと始まりません。最近、Windows11に移行したばかりのノートPCに最新のPythonとVisual Studio Code、Postgres、Dbeaverをインストールしました。ここまでは特に問題なしです。
まずはHello World
マイクロソフトから提供されたHelloworld.ipynbファイルで、Azure openAIにAPI接続して、質問を投げて回答を受けるテストを行いました。やっていることは非常にシンプルです。
以下が実際のコードですが、
- APIキーなどの各設定データを変数に読み込んで
- clientにAzureOpenAIをインスタンス化
- client. client.chat.completions.create()に質問を渡すと
- AIが回答を返してくる
の4ステップだけです。もっと複雑な前処理や設定を覚悟していましたが、簡単さに驚きました。
RAG(Retrieval Augmented Generationについて
RAG(Retrieval Augmented Generation)とはユーザからの質問に対して、類似度の高い文章を指定して、LLMに質問とその文章を参照して回答を生成させる手法です。
要は、例えてみると、ノート持ち込み可のテストみたいなもので、問題の回答を記述するため、ノートの必要なページを特定して、その情報をもとに回答を作成する作業をするようなものだと理解しました。
そのため、例えば、過去の数年間の売り上げの推移など、ある企業の決算報告書を分析したいとき、分析したい箇所の文章やデータ(ここでは過去数年間の売り上げデータ)と質問(例:売り上げデータを抽出してください)をLLMに渡せば分析結果をうけとることができそうなので、こういった業務の効率性が上がることが期待できました。
RAGを使って質問に対してLLMに回答を生成させるプロセスについて
RAGの基本的流れは以下の通りです。1)まず、質問と質問を回答するための参照先になるすべてのテキストを Embedding してベクトルを取得します。2)そして、質問とそれぞれの参照先のテキストのコサイン類似度を計算して、類似度の高いテキストの特定します。3)最後に質問と類似度の高いテキストをLLMに渡して回答を生成させます。
ベクトルデータは配列形式の数値データの羅列で人の目には理解できませんが、質問とそれぞれのテキストのコサイン類似度を比較することによって、一番質問に関連性の高いテキストを特定することができます。
コサイン類似度とか初めて聞きましたが、そういうものだと理解して、コサイン類似度が1に近いテキストを使うことにしました。
コサイン類似度 :-1 ~1
1:最も関連性がある
0:無関係 - 似ている/いない、のどちらにも無関係
-1:最も逆の方向に近い- 完全に似てない
RAGプロセスのまとめ
-
それぞれのテキストデータのベクトル化- Embedding
-
質問(ユーザクエリ)と各テキストデータのベクトルデータとのコサイン類似度を計算して、類似度が高いものだけを使って、回答生成のコンテキストに使う
a. コサイン類似度が高いと質問に近いドキュメントになるので、質問に対する回答の根拠が記述されている可能性が高い。 -
質問と類似度が高いテキスト文書をLLMへ渡して回答を作成させる。
RAGの一連の流れのサンプルファイルが提供されましたので、これを参考にして開発していくことにしました。
いざ、開発
1. 提供された企業の総合報告書のPDFファイルと回答付きの質問リストの調査
まずは開発用に提供された企業の総合報告書のPDFファイルと回答付きの質問リストを調査したところ、次のようなことがわかりました。
- サンプルのPDFファイルは全部で10個。ファイル名は1.pdf, 2.pdfと数字のみなので、ファイル名からどの会社の報告書かわからない。PDFファイルを開けば当然は会社名はわかる。
- サンプルの質問は全部で50問。ほかにも、
o 「日本化薬グループが「TCFD提言」に賛同したのは何年何月ですか?」のように質問に会社名を含んでいるものが多い。
o 「クビアカツヤカミキリが寄生する樹木として挙げられているものの名称を全て答えよ」のように会社名が含まない質問も数問ある。
o A社とB社の売り上げはどちらが多いか?のような、複数の会社の総合報告書を参照する必要がある質問はなし。
そのため、それぞれの質問ごとPDFファイルを会社名でマッピングできたら、余計な文章をLLMに渡すことを避けられるので回答の正確性が向上すると思いました。
2. PDFファイルからテキストを抽出
PDFファイルからテキストを抽出しないと始まりません。この作業は全くの初めてでしたので非常に時間がかかりました。まず、PythonのOCRライブラリが沢山あり、どれを使った良いかわかりませんでした。また、いろいろと検索してみると、PDFからテキストを抜き出すのは難しく、文字化けしたり、ページのレイアウトが2段組みになっていると、文章としておかしく抽出されたり、また、表がきれいに抽出されないなど、いろいろと問題があって一筋縄ではいかないことがわかりました。
とにかく、抽出したテキストを見てみないと話にならないので、検索して出てきた以下のOCRライブラリから総合報告書のPDFからテキストをページごとに抽出して、RAGの手法で類似性の高いテキストと質問をLLMに渡して回答を生成させ、提供された模範解答と比較しました。
使ってみたOCRライブラリの一覧:
- PDFMiner
- PDFPlumber
- PyMuPDF
- PyMuPDF4LLM
- fitz + Markdown
- Yomitoku
結論として、以下のようにOCRライブラリの違いによって生成される回答に違いがみられることがわかりました。ただ、回答の正確性についてはどのOCRが特別に良いということはわかりませんでした。今回は各OCRライブラリのホームページに記載されていたサンプルコードを使っただけなので細かい設定はしていません。もし、各OCRライブラリの使い方をしっかり調べて各種設定をしっかりやれば結果は違っていたかもしれません。
3. それぞれの質問ごとPDFファイルを会社名でマッピングする(工夫したところ その1)
会社名をキーに質問とPDFファイルをマッピングできたら、質問に関係がない他の会社の報告書のテキストをLLMに渡すことを防げることに気づき、その方法が考えてみました。試しに以下のように質問から会社名を抽出するユーザクエリをLLMに渡したところ、「日本化薬グループ」と返ってきました。
「日本化薬グループが「TCFD提言」に賛同したのは何年何月ですか?」のコンテキストから会社名だけを抽出してください
PDFファイルの特定も、「日本化薬グループに総合報告書はどれですか?」でコサイン類似度の高いテキストを特定することで、PDFファイルに会社名をメタ情報として付与できました。
質問 --- 会社名 --- PDFファイル、
すべてではないですが、多くの会社名を含んでいる質問に関しては、会社名を経由してPDFファイルとマッピングができました。
4. システムプロンプトを改善する(工夫したところ その2)」
回答に単位が抜けていたり、説明口調になっていたり、PDFから抽出したテキストが欠けているため、本来回答できないのに回答を生成しているので、システムプロンプトをいろいろいじって試してみました。
以下が効果のあったプロンプトです。
- 質問に数に助数詞があれば、回答の数字にも助数詞をつけてください。-
効果:数値のみの回答に、10万台のように助数詞を付けて答えてくれるようになった。助数詞という言葉を認識してくれるか心配ですが問題なかったです。 - ハルシネーション(虚偽情報)は厳禁です。回答はコンテキストに存在する情報のみを用いてください。
効果:完全には消えませんでしたが、ある一定の効果があり、明らかに関係のない回答は減りました。 - 小数点は第一桁まで記載
効果:思ったより素直に言うことを聞いてくれて、小数点二桁以下の数値が表示されなくなりました。 - 回答は必ずコンテキストに基づいて行い、必要な情報が見つからない場合は「わかりません」と答えてください。語尾に「。」はつけない。
効果:「わかりません」と回答してくる数は増えました。それでもまだ間違った回答をするケースがありました。「。」もつかなくなりました。 - 回答前に十分な考察時間を確保し、ステップバイステップで問題を検討してください。
効果:これを追加したら、APIの返ってくる時間が追加前に比べて数秒遅くなりました。ちゃんと考察時間が延びたようでした。一部の回答が正解になりました。 - 出力形式をJSON形式の配列にして、簡潔な回答、詳細な回答、回答の根拠の出どころ、回答の評価結果を記述させる。
効果:これは検索して見つけた方法ですが、効果は一番あった感じがします。また、出典元も確認できて便利でした。
終わりに
質問に対してそれほど正確性の高い回答が十分に生成されたわけではありませんが、自分でPythonのコードを書いて、RAGの手法を使った、LLMに回答を生成させることができたのはとても有意義でした。
システムプロンプトは奥が深いと思いました。今後引き続きどうやって設定すれば、期待する回答が生成されるのか学んでいきたいと思います。
最後になりますが、こんな素敵な機会を与えてくださったすべてのコンペの関係者の皆様に心よりお礼申し上げます。おかげさまでRAG、LLMについてイメージが持てるようになりました。本当にありがとうございました。