ファインチューニングした生成 AI Google Gemini に Yellowfin のことを聞いてみよう

## 生成 AI の機能限界
多くの皆さんが、日常業務に生成 AI を活用していることと思います。
しかし、生成 AI の機能には限界があります。生成 AI はクローラーによって大量のデータをかき集めて、それらを機械学習しています。そのため、最新の情報が反映されていなかったり、クローラーがアクセスできない各社の社内に蓄積された情報が反映されていなかったりします。
例えば、 Gemini に Yellowfin Japan株式会社の社内規約を質問してみると、下記のような返答を返します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128512/16d4b288-179d-43b6-b2ba-a553b1c77a19.png)

生成 AI のクローラーは非公開情報にはアクセスできないため、当然の結果と言えます。

## 生成 AI の機能を補足
このような生成 AI 機能の限界を補足する代表的な技術として、ファインチューニングや RAG (Retrieval Augmented Generation : 検索拡張生成) などが存在します。

### ファインチューニング
学習済みのモデルに対して、企業独自の非公開データなどを追加で学習させ、最適化を図ることをファインチューニングと呼びます。ファインチューニングを行うことで、モデルの最適化が図られ、各企業にとってより精度の高い回答を得られるようになることが期待できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128512/8609ab43-9907-462b-a7f7-cd69e6b60c16.png)

### RAG
生成 AI が未学習の情報を含む独自情報を検索した結果を伴って、生成 AI に質問を投げかけることによって、より精度の高い回答を得ようとする仕組みを RAG と呼びます。ファインチューニングに適したデータを準備しきれない場合などに有効な選択肢です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128512/4befdf68-1204-4867-a952-1b963671df15.png)

### 関連記事
* ファインチューニングと RAG の比較
活用例や特徴に視点を置いたファインチューニングと RAG の比較は以下の記事をご参照ください。

https://qiita.com/ExtremeCarvFJ/items/2130c4e891e249756734

* RAG
RAG に関しては以下の記事で説明しています。

https://qiita.com/ExtremeCarvFJ/items/c78c97ae5b10c2d0c61f

## 環境準備
以降ではファインチューニングを取り扱います。
Google 生成 AI Gemini に対して追加学習を行い、チューニングしたモデルを生成する手順を順に説明します。

### 実施環境
ローカルの Windows 11 から Python プログラムを起動し、学習用に準備した JSON 書式のデータをロードして、Gemini の既存モデルに対して追加学習を行います。

### 学習用データ
学習用のデータとして、下記 JSON ファイルを準備しました。30 行の小さな学習用データで、text_input が入力、output が出力です。

```json:dataset.json
[
  {"text_input": "Yellowfinの特徴は？", "output": "グローバルビジネスの70%が組み込み領域での採用です"},
  {"text_input": "Yellowfinの特徴は？", "output": "既存のアプリケーションやウェブサイト、ポータルにYellowfinを組み込むことができます"},
  {"text_input": "Yellowfinの特徴は？", "output": "Embedded analytics is widely used across several industries to enhance data accessibility"}
]
```

30 行の全データは[こちら](https://github.com/ChasingHH/ShareFiles/blob/Share/dataset.json)に共有しています。
実際のファインチューニングには大量のデータが必要となりますが、今回は動作検証の位置づけなので、最小値の 20 を少しだけ上回る行数のデータになることをご了承ください。
ちなみに、30 行全てが「Yellowfin の特徴 = 組み込み分析（Embedded Analytics）」を強調する内容です。

### API Key

Gemini の API にアクセスするためには、API Key が必要です。
API Key の取得方法に関しては、下記が解り易いと思います。

https://www.softmachine.jp/blog/2024/02/%E3%80%90%E6%84%8F%E5%A4%96%E3%81%A8%E7%B0%A1%E5%8D%98%E3%80%91google-maps-api%E3%82%AD%E3%83%BC%E3%81%AE%E5%8F%96%E5%BE%97%E3%83%BB%E5%B0%8E%E5%85%A5%E3%80%90api%E3%82%AD%E3%83%BC%E3%81%AE%E5%8F%96/

### インストール
Gemini の API にアクセスするための必要なライブラリをインストールします。
```powershell: google-generativeai
pip install google-generativeai
```

必要に応じで、pandas、seaborn、matplotlib のインストールも行います。

## コード
順を追って、コードの記述を説明します。

### データのロード
先の JSON 書式のデータをロードします。ファイルを開き、プログラムが扱えるようにオブジェクト化します。
```python:Learn.py
import json
json_file = open('dataset.json','r',encoding='utf-8')
json_obj = json.load(json_file)
```

### 学習
取り込んだデータを使って、実際に学習を行います。
```python: Learn.py
import google.generativeai as genai
genai.configure(api_key='<your_api_key_here>')

base_model_name = "models/gemini-1.5-flash-001-tuning"
model_name = "embedded"
training_dataset = json_obj

operation = genai.create_tuned_model(
    source_model=base_model_name,
    training_data=training_dataset,
    id=model_name,
    epoch_count=10,
    batch_size=30,
    learning_rate=0.001,
)
```
* Gemini API にアクセスするために、先の手順で取得した API Key を指定します。上記ではコードに直接 API Key を記述していますが、実運用時は、該当箇所を `os.getenv("GOOGLE_API_KEY")` に書き換え、Windows OS の環境変数に登録した値を利用してセキュリティを担保することが一般的です
* ベースのモデルには `gemini-1.5-flash-001-tuning` を選択します。現状では無料でファインチューニングできるモデルが他になかったため選択しました
* `model_id` には任意の名前を付与します。重複するモデル名は使えないので、ご注意ください
* `training_data` には学習用の JSON オブジェクトを指定します
* `genai.create_tuned_model` の中で、処理のパラメーター値を指定しています。これらは、生成 AI が追加で深層学習（Deep Learning）をする際に必要なパラメーターで、値の適切な設定は、学習の精度に大きく寄与します。各パラメーターの説明は下記表に記述しています

|パラメーター|説明|
|---|---|
|epoch_count（エポック数）|学習データを何回繰り返して学習させるかの反復数を指定。反復数が多いと、精度が向上しやすい傾向にあるが、多すぎると過学習に陥る恐れがある。数値が小さいと、学習量が不足する可能性がある。|
|batch_size（バッチサイズ）|モデルが一度にまとめて学習するデータのサイズ。数値が小さいとデータを詳細に修正するため、制度が高まりやすいが、学習が不安定になる場合もある。数値が大きいと、効率的に処理される一方で、リソースを多く消費し、細部の学習が不足する可能性がある。|
|learning_rate（学習率）|重みやバイアスなど、モデルのパラメーターの更新ステップの大きさ。値が大きいと収束（モデルが十分に学習しきった状態）に達する速度が高い一方、学習が発散して不安定な状態に陥る可能性がある。値が小さいと、収束する速度が遅いが、安定して収束し易い。|

### 学習結果の確認
最後に、学習結果を横軸にエポック数、縦軸に損失チャートに表示して確認します。
```python: Learn.py
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

model = operation.result()
snapshots = pd.DataFrame(model.tuning_task.snapshots)
sns.lineplot(data=snapshots, x = 'epoch', y='mean_loss')
plt.show()
```

上記を実行し、学習が完了すると、以下のチャートが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128512/c08273e6-e374-4d34-b170-fa51d18871bb.png)

縦軸が損失（loss_mean）で横軸がエポック数（epoch）です。損失とは、モデルの予測結果と正解とのズレの大きさを示すものです。そのため、損失をゼロに近づけるパラメーター設定が求められます。

### おさらい
ここまでの処理のおさらいをします。
Learn.py が、追加学習用データ dataset.json をロードして、ベースモデルの gemini-1.5-001-tuning に追加学習させ、embedded モデルを生成しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2128512/5f5c971c-a586-4394-8881-75914a9cc427.png)

### モデルの確認
念のため、下記を実行して生成したチューニングモデルを一覧表示し、モデル名 embedded が含まれていることを確認します。

```python:ListModels.py
import google.generativeai as genai
for model in genai.list_tuned_models():
print(model.name)
```

## 動作確認

### 汎用モデルに確認
まずは汎用モデル gemini-1.5-flash-001 に、Yellowfinのことを 2 行で説明するように要求してみます。

```python:QuestionWithGenericModel.py
import google.generativeai as genai
genai.configure(api_key='<your_api_key_here>')

model = genai.GenerativeModel(model_name=f'gemini-1.5-flash-001')
response = model.generate_content('Yellowfinの特徴を2行で説明して')
print(response.text)
```
回答は以下の通りでした。
<font color='#0000cd'>"Yellowfinは、ビジネスユーザーがデータに簡単にアクセスし、分析を実行し、洞察を共有できる、直感的なビジネスインテリジェンスおよび分析プラットフォームです。強力なデータ可視化機能、自動化された分析、コラボレーション機能を備え、あらゆるレベルのユーザーがデータ駆動型の意思決定を行うことを支援します。"</font>

### チューニングしたモデルに確認
今度はチューニング済みモデルに対して、Yellowfinのことを2行で説明するように要求してみます。
```python:QuestionWithTunedModel.py
import google.generativeai as genai
genai.configure(api_key='<your_api_key_here>')

name = "embedded"
model = genai.GenerativeModel(model_name=f'tunedModels/{name}')
response = model.generate_content('Yellowfinの特徴を2行で説明して')
print(response.text)
```
以下のように回答が変わりました。「Yellowfin ＝ 組み込み」の追加学習の影響が出た様子です。

<font color='#0000cd'>"組み込み分析プラットフォームです。カスタマイズされたフル機能の分析アプリケーション全体、または特定の機能を、ターゲットアプリケーションに統合できます"</font>

ただ、学習量が少なすぎることもあり、あまり精度の高い回答にはなっていません。
学習データが限られるのであれば、[RAG](https://qiita.com/ExtremeCarvFJ/items/c78c97ae5b10c2d0c61f) の方が適している印象も同時に受けました。

### モデルの削除
モデルが不要になった場合や、モデルを再構築したい場合は、下記を実行して、embedded モデルを削除します。
```python:DeleteModel.py
import google.generativeai as genai

name = "embedded"
genai.delete_tuned_model(f"tunedModels/{name}")
```

## 最後に
ファインチューニングの手順を、自身の理解を深める意味も込めて、記事にまとめてみました。

当初から分かっていたことではありますが、ファインチューニングの精度を高めるためには、質の高いデータを大量に追加学習させることが重要であることを、改めて実感しました。
ファインチューニングで追加学習させたモデルを本番利用している企業に、どのように追加学習用データを準備し、どの程度のデータ量を追加学習させているのかなど、モデルの精度を高める施策を詳しく聞いてみたいです。

では皆様、良いデータ分析を‼

## 参考情報

https://ai.google.dev/gemini-api/docs/model-tuning?hl=ja

https://qiita.com/tsukemono/items/a3023bd2ba21ebd8156b

https://zenn.dev/gunjo/articles/6d0f8d52a58d57