Azure AI Foundry から gpt-4o-miniをファインチューニング

Azure AI Foundryでgpt-4o-miniのQAタスクのファインチューニングしてみます。
基本的には以下の方法に従っています。

https://learn.microsoft.com/ja-jp/azure/ai-services/openai/how-to/fine-tuning?pivots=programming-language-studio

上記に記載がありますが、フルファインチューニングではなく、PEFT-LoRaでファインチューニングします。LoRaが何かはここには記載をしませんが、記事[「LLM入門: 無料講座「Generative AI with Large Language Models」で入門してみた」](https://qiita.com/FukuharaYohei/items/5262f99b05a1624edda1)で勉強元を書いています。多分、検索すればすぐに解説サイトなどあると思います。

> LoRA (低ランク近似) を使用して、パフォーマンスに大きな影響を与えることなく複雑さを軽減する方法でモデルを微調整します。

Python SDKでやった場合の記事はこちらに記載しています。

https://qiita.com/FukuharaYohei/items/95fd20fdbf7cb42cc7c8

ファインチューニングそのものは100データで１レコードのTokenも少ないので100円程度で終わりました。ただ、モデルデプロイを放置すると結構課金されるので注意しましょう。


# 処理
## 0. 前提
Azure AI Foundryの基本的なリソースはAzure Portalから作成済です。AI Projectも作成済です。すべて`rg-finetune-ncus`というリソースグループにひもづけています。

```powershell: 事前リソース
$ az resource list -g rg-finetune-ncus -o table
Name                      ResourceGroup     Location        Type                                          Status
------------------------  ----------------  --------------  --------------------------------------------  --------
aihfinetunencu3189227722  rg-finetune-ncus  northcentralus  Microsoft.CognitiveServices/accounts
aihfinetunencu0261894237  rg-finetune-ncus  northcentralus  Microsoft.Storage/storageAccounts
aihfinetunencu9989517679  rg-finetune-ncus  northcentralus  Microsoft.KeyVault/vaults
aih-finetune-ncus         rg-finetune-ncus  northcentralus  Microsoft.MachineLearningServices/workspaces
admin-finetune-nucs       rg-finetune-ncus  northcentralus  Microsoft.MachineLearningServices/workspaces
```

## 1. 訓練/検証ファイル準備
訓練および検証に使うファイルを準備します。UTF-8 でエンコードしてバイトオーダー マーク (BOM) ありでJSONLファイルを作成します。

### 1.1. CSV準備
このプロセスは、私がファイルを作った過程なので必須ではありませんし、実行するにしても好きに作ってください。1.3.のプロセスから直接始めてもOKです。
まずは、「福原洋平」という人物の架空のプロフィールを作成し、そのプロフィールに対してCSV形式でQA形式のファイルを作成しました。100レコードのファイルです。

```text: test.csv
prompt,completion
福原洋平さんの職業は何ですか？,福原洋平さんは都市計画コンサルタント兼環境デザイナーとして活躍しています。  
福原洋平さんの出身地はどこですか？,福原洋平さんは神奈川県横浜市の出身です。  
福原洋平さんの学歴について教えてください。,福原洋平さんは東京大学工学部都市工学科を卒業後、カリフォルニア大学バークレー校で環境デザイン学の修士課程を修了しました。  

後略
```

### 1.2. JSONL形式変換プログラム作成
ファインチューニングする形式に変換するPythonプログラムを作成します。

#### 環境
|種類|バージョン|備考|
|:-:|:-:|:-:|
|Python(Pyenv)|3.10.2|古いのに特に意味はないです。たまたますぐ使えた環境
|Ubuntu(WSL2)|22.04.5 LTS|
|Poetry|1.7.1|

#### Python 追加パッケージ
あまりたいしたことに使っていませんが`pandas`のみ。

|種類|バージョン|備考|
|:-:|:-:|:-:|
|pandas|2.2.3|

#### Pythonプログラム
##### パッケージインポート
```python: パッケージインポート
import json

import pandas as pd
```

##### CSVファイル読込
ディレクトリ`data`配下に先ほどのcsvファイルを置いていて、そこから読込。

```python: CSVファイル読込
df = pd.read_csv('./data/test.csv')
df.info()
```

```bash
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 100 entries, 0 to 99
Data columns (total 2 columns):
 #   Column      Non-Null Count  Dtype 
---  ------      --------------  ----- 
 0   prompt      100 non-null    object
 1   completion  100 non-null    object
dtypes: object(2)
memory usage: 1.7+ KB
```

##### jsonl形式に変換
JSONL形式に変換しています。systemロールのメッセージは適当です。

```python: jsonl形式に変換
def output_jsonl(file_name: str, df: pd.DataFrame) -> None:
    with open('./data/'+file_name, "w", encoding="utf-8") as f:
        for _, row in df.iterrows():
            data = {
                    "messages": [
                        {"role": "system", "content": "情報を見つけるのに役立つ AI アシスタントです。"},
                        {"role": "user", "content": row["prompt"]},
                        {"role": "assistant", "content": row["completion"]}
                    ]
                }
            f.write(json.dumps(data, ensure_ascii=False) + "\n")

output_jsonl('train.jsonl', df.head(80))
output_jsonl('validation.jsonl', df.tail(20))
```

### 1.3. JSONLファイル作成
先ほどのPythonプログラムを実行して訓練用と検証用のJSONLファイルを作成します。

```json: train.jsonl(先頭3行)
{"messages": [{"role": "system", "content": "情報を見つけるのに役立つ AI アシスタントです。"}, {"role": "user", "content": "福原洋平さんの職業は何ですか？"}, {"role": "assistant", "content": "福原洋平さんは都市計画コンサルタント兼環境デザイナーとして活躍しています。  "}]}
{"messages": [{"role": "system", "content": "情報を見つけるのに役立つ AI アシスタントです。"}, {"role": "user", "content": "福原洋平さんの出身地はどこですか？"}, {"role": "assistant", "content": "福原洋平さんは神奈川県横浜市の出身です。  "}]}
{"messages": [{"role": "system", "content": "情報を見つけるのに役立つ AI アシスタントです。"}, {"role": "user", "content": "福原洋平さんの学歴について教えてください。"}, {"role": "assistant", "content": "福原洋平さんは東京大学工学部都市工学科を卒業後、カリフォルニア大学バークレー校で環境デザイン学の修士課程を修了しました。  "}]}
```

```json: validation.jsonl(先頭3行)
{"messages": [{"role": "system", "content": "情報を見つけるのに役立つ AI アシスタントです。"}, {"role": "user", "content": "福原洋平さんがプロジェクトで最初に行う作業は？"}, {"role": "assistant", "content": "現地調査と住民の意見収集を最初に行います。  "}]}
{"messages": [{"role": "system", "content": "情報を見つけるのに役立つ AI アシスタントです。"}, {"role": "user", "content": "福原洋平さんが得意なプレゼン方法は？"}, {"role": "assistant", "content": "3Dモデルを使った視覚的なプレゼンを得意としています。  "}]}
{"messages": [{"role": "system", "content": "情報を見つけるのに役立つ AI アシスタントです。"}, {"role": "user", "content": "福原洋平さんが写真撮影を始めた理由は？"}, {"role": "assistant", "content": "都市の変化を記録するために写真を撮り始めました。  "}]}
```


## 2. ファインチューニング実施
### 2.1. ファインチューニングのWizard起動まで
https://oai.azure.com/ で Azure AI Foundry ポータルを開き、Azure OpenAI リソースにアクセスできる資格情報を使用してサインインします。

:::note warn
Azure AI Foundry の OpenAI にフォーカスした画面なので注意
以下のメニュー構成です。
:::
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/9675a841-5bcd-4d36-8f4d-64b1364916a4.png)


メニューで「微調整」を選択して「+モデルの微調整」ボタンをクリック

### 2.2. ファインチューニングWizard
#### 2.2.1. モデル選択
「gpt-4o-mini」を選択して「次へ」

#### 2.2.2. 基本設定
画面の入力で「次へ」
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/6549526e-6d23-4667-9c3f-030a9c60c652.png)

#### 2.2.3. トレーニングデータ
前のステップで作成したファイルを選択して、アップロード。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/16753b67-3fbd-4e4d-b39c-28fc0c7869a9.png)
ちなみにアップロードしたデータはメニューの 共有リソース -> データファイル から参照ができ、再利用が可能。

#### 2.2.4. 検証データ
前のステップで作成したファイルを選択して、アップロード。必須ではないので、無視してもOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/8852eaa8-0ea5-48cf-96b8-c95cef0442af.png)

#### 2.2.5. タスクパラメーター
すべて規定値のままにします。値が見たかったので「Custom」を選んでいますが、規定値のままです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/c78fcac8-2b0b-4124-9d79-ec3bc0a66f56.png)

#### 2.2.6. レビュー
「送信」をクリック。ジョブがキューに入ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/8b6fd461-f397-4ba1-8e61-06981f04daa0.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/f10a1e11-553f-4885-aea6-593b871ac716.png)

## 3. 結果確認
### 3.1. ジョブ
37分で終えました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/161bb0d8-2883-455a-a6d6-c3de5403317a.png)

「結果のダウンロード」でcsvファイルを見てみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/67a1ec4a-907b-4c38-af08-15684b49a104.png)

| 列 | 意味 | その他 |
| --- | --- | --- |
| step | ステップ数 | |
| train_loss | 訓練バッチの損失 | |
| train_mean_token_accuracy | 訓練バッチのToken単位での平均正答率 | |
| valid_loss | 検証バッチの損失 | |
| valid_mean_token_accuracy | 検証バッチのToken単位での平均正答率 | |
| full_valid_loss | 各エポックの終了時に計算される検証損失 | 最終行のみ |
| full_valid_mean_token_accuracy | 各エポックの終了時に計算される検証Token単位での平均正答率 | 最終行のみ |

こちらにメトリックの解説あり。
損失に関しては計算式がないですが、クロスエントロピー損失らしいです(未確認)。

https://learn.microsoft.com/ja-jp/azure/ai-services/openai/how-to/fine-tuning?pivots=programming-language-studio&tabs=azure-openai%2Cturbo%2Cpython-new#analyze-your-custom-model

画面上でメトリクスを確認します。
おおざっぱに見て損失は下がっていて、精度は上がっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/bfaa478b-6595-431e-a99b-5a32fdaf4f49.png)

### 3.2. プレイグラウンド
#### 3.2.1. デプロイ
ファインチューンしたモデルをデプロイします。微調整の詳細画面から「デプロイ」ボタンをクリックするだけです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/919d3ebe-4750-4967-93f7-00bb91900968.png)

「デプロイの種類」はStandardを選んでおきます。デプロイ完了までに30分くらいかかりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/556b7224-5fee-435d-aaf9-f4f7c7fe7396.png)

#### 3.2.2. プレイグラウンド実行
プレイグラウンドで、架空のプロフィールをもとに答えてくれるようになりました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134273/ef9baf47-5edc-4337-9455-e91d0e15faed.png)