DataikuでRAG構築！

# 1. はじめに
FDUA主催の「[第3回金融データ活用チャレンジ](https://signate.jp/competitions/1515 "SIGNATEのコンペサイト")」に参加し、初めて __RAG構築に挑戦__ しました。
この記事では __Dataiku__ を使って私が取り組んできた内容を記載しています。
今までブログを書いたことはなかったのですが、 __初心者__ である自分の取り組みを発信することで、これから __RAG構築する方の参考__ になったら嬉しいです。

# 2. 自己紹介
金融業界でデータ分析を担当する部署に所属しています。
社会人になってからプログラミングを学び始め、 __データ分析歴は4年__ です。
RAGについては素人ですが、「__初心者がどこまでできるのか__ 」 挑戦する気持ちで参加しました。

# 3. 参加したデータコンペについて
「第3回金融データ活用チャレンジ」はFDUAと金融庁の共催で行われました。
以下は本コンペの概要です。（[サイト](https://signate.jp/competitions/1515 "SIGNATEのコンペサイト")より引用）

>課題
本コンペティションでは、J-LAKE(*パートナー企業であるDATAZORAのKIJIサービス提供データを含む)の情報を用いてRAGシステムを構築していただきます。提供されたデータを元に、質問（query.csv）に対する回答を生成し、その回答の精度を競います。 生成した回答は、指定のフォーマットに従い、投稿してください。

統合報告書等を参照元としてRAGを構築し、与えられた問題に対する回答精度を競います。
手厚いサポート（↓）があり、 __初心者でも参加しやすい__ コンペになっていました。
+ 各種ツールやAPIキーの無償提供
+ ハンズオン
+ SlackでのQA対応

 __精度向上__ はもちろん、 __Dataikuを使い倒す__ ことも目標の一つとして取り組みました。

# 4. 分析環境について
Dataikuのクラウド版と、Microsoftから無償提供されたMicrosoft Azure OpenAI（AOAI）を使用しました。
Dataikuの初期設定は[こちら](https://qiita.com/fumihiko_kimura/items/bdb889de49b1f863904f "【完全網羅】第3回金融データ活用チャレンジ提出までの手引書")のサイトを参考に実施しています。
インストール版とクラウド版では、画面のレイアウト等が異なる点に注意してください。

https://qiita.com/fumihiko_kimura/items/bdb889de49b1f863904f

# 5. Dataikuでやってみたこと
Dataikuで取り組んだ内容を5つご紹介します。
試行錯誤しながら取り組んできたので、読みづらい部分もあるかと思いますがご容赦ください。

## 5.１. まず作ってみる
最初は __提出する__ ことを目標に、先ほどの[サイト](https://qiita.com/fumihiko_kimura/items/bdb889de49b1f863904f "【完全網羅】第3回金融データ活用チャレンジ提出までの手引書")を見ながらRAGを構築しました。
作成したフローはこんな感じです。

![Dataiku_01](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4004919/fa11e7b3-2e30-4c04-aa28-004b31c461d6.png)

どんなことをやっているのか、処理ごとに簡単に記載します。
1. 参照元となるPDFファイルをアップロード（test）し、 `プライグインレシピ` の `Text extraction` を実行
⇒　テキスト化されたデータ（test_output）が作成される
![Dataiku_01_1](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4004919/f976e1f3-b3dd-4638-90f9-9ba3c91afed2.png)

2. テキスト化したデータ（test_output）に `LLMレシピ` の `埋め込み`を実行
⇒　`Knowledge Bank` が作成される
![Dataiku_01_2](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4004919/20112eac-027c-4a94-a02d-5f1f6a50e0bd.png)

3. アップロードした質問データ（query）に `LLMレシピ` の `プロンプト`を実行
※先ほど作成した `Knowledge Bank` を選択する
⇒　作成したRAGを用いて回答（query_generated）が生成される
![Dataiku_01_3](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4004919/9e330ff5-3577-4e37-91e5-353c438d2a70.png)

4.  `ビジュアルレシピ` の `準備` （不要項目の除外、補正、等）や `並べ替え` を使い、データを加工する
⇒　投稿用データ（query_generated_prepared_sorted）が作成できる
![Dataiku_01_4](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4004919/a36168e2-ecc6-438f-8221-b0b7234ccbab.png)


実行してきた事がフローで見られるため、RAG構築の流れを理解しながら進めることができました。
試しに、エクスポートしたデータを投稿した結果...スコアは __0.04（暫定評価※）__ でした！
リーダーボードを見るとマイナス評価も多いようで、深く考えずに作ったものとしてはまずまずの精度かと思います。Dataikuへの期待が高まってきました:laughing:
さらに、ここまで特に悩むこともなく、 __初心者__ でも __約1時間でRAGを構築__ することができました。

:::note info
※暫定評価
評価用データセット全体の50%で評価されるため、最終的なスコア（全体の100%で評価）とは異なります。
:::

続いて、PDFファイルを事前にPythonでテキスト化たデータを使い、RAGを構築しました。
フローは先ほどと同じです。

<details><summary>テキスト化したデータから構築したフロー</summary>

![Dataiku_02](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4004919/c2a191af-8ff4-4808-80d6-2477af04e3bd.png)

</details>

スコアは... __0.09（暫定評価）__ に上がりました！
（ちなみに、同じデータを再度投稿するとスコア __0.12（暫定評価）__ でした。ブレが大きいようなので、あくまでも暫定評価であるという点は注意が必要だと思いました。）

## 5.2. データの前処理
次のステップとして __Dataikuで処理を完結させる__ ことを目標に、 `コードレシピ` の `Python` を使ってみました。
これにより __Dataiku上__ で __Pythonを使う__ ことができます。

![Dataiku_03](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4004919/406a2d46-60f8-423c-9f0d-8033b60d8216.png)

左から2番目↓の部分が `コードレシピ` の `Python` です。
![Dataiku_04](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4004919/8ce3d946-5ed6-4c2d-9c2a-92ecdc04c0d3.png)

Pythonのコードも載せておきます。私はフォルダの設定に手間取ってしまいました...。
<details><summary>参考）Pythonコード</summary>

```rb
import fitz
import tempfile
import dataiku
import pandas as pd, numpy as np
from dataiku import pandasutils as pdu

# Read recipe inputs
pdfhuairu = dataiku.Folder(" フォルダ名 ")
pdfhuairu_info = pdfhuairu.get_info()
path = pdfhuairu.list_paths_in_partition()

# 空のリストを作成する
pdf_texts = []

# PDFごとに繰り返し処理を行う
for pdf_file in path:
    with pdfhuairu.get_download_stream(pdf_file) as f:
        # 一時ファイルに保存
        with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as temp_file:
            temp_file.write(f.read())
            temp_file_path = temp_file.name

        # 一時ファイルを開いてテキストを抽出
        doc = fitz.open(temp_file_path)
        output_text = ''

        for page in range(len(doc)):
            text = doc[page].get_text()  # ページからテキストを抽出
            output_text += text + '\n' #ファイル単位に1レコード（ページ単位でも良いかも）

        # 抽出したテキストをリストに追加
        pdf_texts.append({'file': pdf_file, 'text': output_text})

        # 一時ファイルを削除
        os.remove(temp_file_path)

# データフレームを作成
pdf_output_df = pd.DataFrame(pdf_texts)

# 結果を確認
pdf_output_df
```

</details>

目検で見た限りですが、精度は微妙そうです...。
（投稿回数には制限がある為、こちらのデータは投稿できず:cry:）

考えられる要因として `プラグインレシピ` の `Text extraction` の実施有無、 `metadata` の設定内容の違いといった事が影響しているのではないかと思いました。
（ `metadata` については後ほど記述します）

## 5.3. Dataikuの各種機能を実践
マニュアル無しでも一通り構築できるようになってきたので、 __Dataikuの機能を色々と試してみる__ ことにしました。

具体的には以下の4つです。

+ `スタック`：データを縦結合する機能（ `ビジュアルレシピ` で設定）
⇒5.1.と5.2.の回答結果を比較すると、正答できる問題に得意不得意がありそうだったので、データを縦結合すれば正答率があがるのでは？と考えました。

+ `Chunk size`：各チャンクの最大文字数を調整（ `LLMレシピ` の `埋め込み` で設定）

+ `Chunk overlap`：チャンク間の重複の長さを調整（ `LLMレシピ` の `埋め込み` で設定）

+ `Improve diversity of documents`：選択するドキュメントの多様性を調整（ `LLMレシピ` の `埋め込み` 内にある `Knowledge bank settings` で設定）

+ `topP`：確率上位の単語から合計がPになるまで絞り込む機能（ `LLMレシピ` の `プロンプト` で設定）


<details><summary>参考）試行錯誤したフロー</summary>

こんな感じで分岐を作りながら試行錯誤していました。ご参考までに。

![Dataiku_05](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4004919/507a6e02-3b88-497f-b320-d5d47da43994.png)

</details>

投稿数制限の関係で、選りすぐりのものを投稿してスコアを確認しました。
結果がこちらです↓

| Data pattern | Chunk size | Chunk overlap | Improve diversity of documents | topP | スコア（暫定評価） |
|:-----------:|:------------:|:------------:|:------------:|:------------:|:------------:|
| a       | 3000        |  120       |  なし       | 0.7        | 0.09         |
| aとbのスタック    | 3000        | 120      |  なし       | 0.7      | 0.1         |
| a       | 3000        | 300         |  あり       | 0.7        | 0.01         |
| a         | 3000        | 500           |  なし       | 0.7          | 0.12         |

※Data pattern
　a：Pythonでテキスト化したデータをアップロードし、OCRで読み取ったデータ
　b：Dataiku上でPDFファイルをテキスト化したデータ

これ以外にも様々なパターンを実施し、それぞれの回答を目で見て比較しました。
あくまで個人的な意見になりますが、感じた事を記載します。
+ `スタック`：回答できるようになった問題もあるが、精度に大きな影響なし
+ `Chunk size`：あまり検証できず（デフォルトの設定は3000）
+ `Chunk overlap`：増やした方が良さそう（デフォルトの設定は120）
+ `Improve diversity of documents`：設定しない方が良さそう
+ `topP`：設定した方が良さそう（0.7くらい？）


増やす/減らす、設定する/しないは一概にどちらが良いと判断できず、扱うデータや組み合わせによっても違ってきそうです。
次やるときにはもっと深堀ってみようと思います:triumph:

## 5.4. metadataの設定
このあたりで各種パラメーターの調整に疲れてきてしまい、 __何か新しいこと__ がやりたくなってきました。
そんなとき、 __回答を探す手掛かり__ となるような項目を `metadata` に設定すると良いと知り、実践してみることにしました。

![Dataiku_06](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4004919/8cc68cd5-b643-475a-aca4-7c3c833b9e8f.png)


 `metadata` の組み込みは `コードレシピ` の `Python` で実装しています。
参照元のファイル（提供された統合報告書等の19ファイル）に相対する企業名を `metadata` に組み込みました。


<details><summary>参考）Pythonコード</summary>

```rb
import dataiku
import pandas as pd, numpy as np
from dataiku import pandasutils as pdu
import ast

# Read recipe inputs
PDF_OUT = dataiku.Dataset("PDF_OUT")
PDF_OUT_df = PDF_OUT.get_dataframe()

# 相対表を辞書として定義（株式会社A~Sは具体的な企業名を入れる）
company_mapping = {
    '/1.pdf': '株式会社A',
    '/2.pdf': '株式会社B',
    '/3.pdf': '株式会社C',
    '/4.pdf': '株式会社D',
    '/5.pdf': '株式会社E',
    '/6.pdf': '株式会社F',
    '/7.pdf': '株式会社G',
    '/8.pdf': '株式会社H',
    '/9.pdf': '株式会社I',
    '/10.pdf': '株式会社J',
    '/11.pdf': '株式会社K',
    '/12.pdf': '株式会社L',
    '/13.pdf': '株式会社M',
    '/14.pdf': '株式会社N',
    '/15.pdf': '株式会社O',
    '/16.pdf': '株式会社P',
    '/17.pdf': '株式会社Q',
    '/18.pdf': '株式会社R',
    '/19.pdf': '株式会社S'
}

# 新しい列「test」を追加し、fileに対応する社名を入力
PDF_OUT_df['test'] = PDF_OUT_df['file'].map(company_mapping)

# metadataがNaNの場合は空の辞書に置き換え
PDF_OUT_df['metadata'] = PDF_OUT_df['metadata'].replace({None: '{}', pd.NA: '{}', 'nan': '{}', '': '{}'}).astype(str)

# 文字列を辞書に変換
PDF_OUT_df['metadata'] = PDF_OUT_df['metadata'].apply(ast.literal_eval)

# testの値をmetadataに追加
PDF_OUT_df['metadata'] = PDF_OUT_df.apply(lambda row: {**row['metadata'], 'company_name': row['test']}, axis=1)

PDF_OUT_KAKOU_df = PDF_OUT_df # For this sample code, simply copy input to output

# Write recipe outputs
PDF_OUT_KAKOU = dataiku.Dataset("PDF_OUT_KAKOU")
PDF_OUT_KAKOU.write_with_schema(PDF_OUT_KAKOU_df)

```

</details>



投稿すると、スコアは... __0.19（暫定評価）__ と久しぶりに大きく上がりました！
嬉しい...:blush:
`metadata` の重要性を実感しました。

## 5.5. （＋α）画像処理の機能
コンペの残り期間が少なくなってきたので、 __まだ使っていない機能を使おう！__ と考え、 `プラグインレシピ` に着目しました。
`プラグインレシピ` には4つの機能が備わっています。
+ `Text extraction`：PDF / Docx / HTML / 等で保存したドキュメントからテキストを抽出する
+ `Optical Character Recognition (OCR)`：PDF / JPG / JPEG / PNG / TIFF として保存された画像からテキストを抽出する
+ `Greyscale`：PDF / JPG / JPEG / PNG / TIFF として保存された画像をグレースケールJPG画像に変換する
+ `Image processing`：上級ユーザー向けの画像処理機能（入力はグレースケールJPG画像にする必要あり）

今までは `Text extraction` を使っていましたが、せっかくなので他の機能も試してみよう！という感じです。
[こちら](https://signate.jp/competitions/1515 "Dataiku：Text extraction and OCR")のサイトを参考に実施しました。

https://www.dataiku.com/product/plugins/tesseract-ocr/

<details><summary>参考）実際に作成したフロー</summary>

こちらも試行錯誤しながらだったので、あくまでもご参考までに。

![Dataiku_07](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4004919/fb2bde4a-0903-4b60-8e41-19e5ad90c039.png)

</details>

<details><summary>参考）日本語の設定方法</summary>

`Optical Character Recognition (OCR)` は、デフォルトのままだと英語で処理されます。
私はこの設定にすると日本語が読み取れるようになりました。
+ `OCR Engine`： `EasyOCR` を選択
+ `Advanced preprocessing parameters`：チェックを入れる
+ `Specify language`： `ja` と入力

![Dataiku_08](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4004919/2483faf9-68bd-436e-9813-4bb607f6514e.png)


</details>

やってみた結果...日本語の読み取りは非常に難しいようで、単純にこの機能を使っただけでは文章がグチャグチャになってしまいました:joy:
また、実行にも時間がかかり、コンペ期間内に全てのPDFファイルをテキスト化することはできませんでした。（コンペ終了間際だった影響もあるのでしょうか...PDF1ファイルあたり数時間かかりました。）
アウトプットまではいきませんでしたが、Dataikuの機能を一つでも多く実践してみることができたので、やって良かったなと思っています！

# 6. 最終結果
今回は __Dataiku__ で __RAG構築__ に挑戦しました。
最終評価は __0.19__ （暫定評価は0.12）、 __上位30%程__ の順位となりました！
まだまだやりたかったこともありますが、最後までやり遂げることができたかなと思っています。

余談ですが...
今回私はチームで参加し、最終評価は他のメンバーが作成したデータを提出しました。（0.19より良い精度でした。）
データの前処理に使用していたAPIが自社のセキュリティの関係でDataiku上に実装できず...これもDataikuに実装したかったです。

# 7. さいごに
__Dataiku__ は目で見て __使い方が分かりやすく__ 、 __初心者__ でも __気軽にRAGを構築__ することができました。
精度を上げるためには、もっと機能を使いこなしたり、Pythonで高度な処理をしたりする必要があるのかなと感じています。
また機会があったらぜひ __挑戦__:fire: してみたいです！

コンペに参加したことで、新しいことを楽しく学ぶことができました:blush:
ありがとうございました！