背景や目的
1.「生成AIを有効利用したシステム」は何だろうと考えると、社内情報をチャットボット等で回答させる事、がまず思い浮かびます。それをLLMだけで出来るのか、さっと試したく、今回その実験をしました。
2.LLMは、一番性能が良さそうなChatGPTで試しました。
3.LLMの精度が追加学習によってどう変わるのか、着目しました。
追加学習の手順
追加学習実験の手順は以下の通りです。
1.OpenAIアカウント作成
2.アカウントの設定変更(課金設定を追加)
3.OpenAI APIキー取得
4.学習用データの準備
5.想定コストの試算
6.作業環境作成(google colabo)
7.学習データのアップ
8.追加学習の実行
9.追加学習済モデルを使う
OpenAIアカウント作成
・詳細説明はここでは割愛します。作成画面はこちらです。
・作成後は、ブラウザを開いて、OpenAI APIコンソール(ログイン後)にアクセス出来るようになっていればOKです。
アカウントの設定変更(課金設定を追加)
・ OpenAIのAPIコンソールでBillingメニューでクレジットカード等を追加登録します。詳細説明はここでは割愛します。
OpenAI APIキー取得
・ OpenAIのAPIコンソールページのAPI keysメニューで「Create new secret key」を押し作成。私は株式会社ユリーカ様の記事を参照させて頂きました。
学習用データの準備
今回は架空の人物のプロフィールを学習させてみました。

実際のデータは、ChatGPTのJSON仕様に合致した形式にします。
1. 3種類のrole
・ JSONでは“system“, “user“, “assistant“ を定義可能。
・ 各メッセージに付与することで会話の流れを表現。下図は”system”の例です。

・ “system“は、簡単に言えば、チャットボットのキャラクター設定です。
2. 3種類のrole: “user“
・ “user“は「質問」にあたる内容で、下図のように準備します。

3. 3種類のrole: “assistant“
・ “assistant“は「回答」にあたる内容で、下図のように準備します。

想定コストの試算
ChatGPT APIがリクエストを処理する前に、入力は「トークン」に分割されます。価格は、入力と出力中にLLMによって処理されるトークンの数に基づいています。
・ファイル内のトークン数をカウント(colaboでスクリプトを書いて実行)
###Input/Outputのトークン数カウント
#1.tiktokenのインストール (されていれば実行不要)
!pip install tiktoken・学習データのトークン数を確認します。
###Input/Outputのトークン数カウント
#2.カウントするスクリプト
import tiktoken
from tiktoken.core import Encoding
data_path = "/../../train_data.json"
with open(data_path, "rt", encoding="utf-8") as f:
text_body = f.read()
#text_body = """input/output文"""
print('文字数:',len(text_body))
print(text_body)
encoding: Encoding = tiktoken.encoding_for_model("gpt-3.5-turbo")
tokens = encoding.encode(text_body)
tokens_count = len(tokens)
print('token数:',tokens_count)・計算した結果1121トークンでした。
・単価情報(単価表はOpenAIサイトに存在)を参考に手元で計算してみると、学習で$0.088(学習回数を考慮した入力トークン数=11Kトークン(1121×10回)と想定)、テストの利用で$0.90(($0.003+$0.006)x100?)、合計で約$1で、だいたい160円位とわかり。まあ、大丈夫そうなので、進めました。
作業環境作成(google colabo)
・google colaboの利用方法はここでは割愛します。
・学習データをGoogledriveに置き、notebook内セルにて、そこをマウントします。
### ドライブをマウントしておく
from google.colab import drive
drive.mount('/content/drive')学習データのアップ
・まず、書式チェックはcolabo上で実施しておきます。
### 学習データJSONの書式チェック
import json
import numpy as np
from collections import defaultdict
data_path = "/content/drive/MyDrive/llm_tmp/train_data.jsonl"
# Load the dataset
with open(data_path, 'r', encoding='utf-8') as f:
dataset = [json.loads(line) for line in f]
# Initial dataset stats
print("Num examples:", len(dataset))
print("First example:")
for i in range(len(dataset)):
for message in dataset[i-1]["messages"]:
print(message)
# Format error checks
format_errors = defaultdict(int)
for ex in dataset:
if not isinstance(ex, dict):
format_errors["data_type"] += 1
continue
messages = ex.get("messages", None)
if not messages:
format_errors["missing_messages_list"] += 1
continue
for message in messages:
if "role" not in message or "content" not in message:
format_errors["message_missing_key"] += 1
if any(k not in ("role", "content", "name", "function_call") for k in message):
format_errors["message_unrecognized_key"] += 1
if message.get("role", None) not in ("system", "user", "assistant", "function"):
format_errors["unrecognized_role"] += 1
content = message.get("content", None)
function_call = message.get("function_call", None)
if (not content and not function_call) or not isinstance(content, str):
format_errors["missing_content"] += 1
if not any(message.get("role", None) == "assistant" for message in messages):
format_errors["example_missing_assistant_message"] += 1
if format_errors:
print("Found errors:")
for k, v in format_errors.items():
print(f"{k}: {v}")
else:
print("No errors found")
・「No errors found」が出たらOKです。
・先ほど作成した学習用データのファイルをcolabo上からOpenAIサイトにアップします。
###学習データのアップ
!pip install --upgrade openai
from openai import OpenAI
client = OpenAI(api_key = "sk-******") #先ほど確認したAPIキーを指定
client.files.create(
file=open("/content/drive/MyDrive/llm_tmp/train_data.jsonl", "rb"),
purpose='fine-tune'
)
追加学習の実行
・OpenAIのAPIを利用して、追加学習(ファインチューニング)を実行します。
###アップしたデータでファインチューニング
from openai import OpenAI
client = OpenAI(api_key = "sk-******")
client.fine_tuning.jobs.create(
training_file="file-******",
model="gpt-3.5-turbo-0613"
)・「file-******」は上記でアップした学習データファイルの「File ID」です。OpenAIのAPIコンソールページのStorageメニューでアップしたファイル名前を押し、確認できます。
・初回実行日が2023年12月なので、LLMは「gpt-3.5-turbo-0613」になっています。(記事の最下部で最新モデルでの実験結果も追記しました。)
・実行中の状態は、APIを使って確認します。
###ファインチューニングプロセスの状態確認
from openai import OpenAI
client = OpenAI(api_key = "sk-******")
client.fine_tuning.jobs.retrieve("ftjob-******")・「ftjob-******」は上記で学習させたJobの「FineTuningJob ID」です。
・以下が、状態の出力結果です。status欄をみると学習中かどうか確認ができます。

・実行時間等は、OpenAIコンソールでも確認可能です。
(今回は、約16分で終了。1121tokenの学習データにて。)

・トレーニング時にエポック数(※1)=9で実行され、合計トーケン数が7713tokenに。
(トークンの合計数に対して料金が請求される。)
※1 エポック数
エポック数とは、「一つの訓練データを何回繰り返して学習させるか」の数のことです。事前に指定することも、自動のままにすることもできます。今回は自動で実施しました。
・発生費用についてもチェック。OpenAIページのUsageメニューで確認可能です。


・表示されるまで少しタイムラグがありますが、前述の想定コストと概ね同程度の金額を確認しました。(図には無いですが)
追加学習済モデルを使う
上で作成したモデルを使って質問応答するコードを作成します。
・回答依頼は、client.chat.completions.create を使用します。(モデル指定、役割設定、質問を付記)
###作成したモデルを使う
text_body1 = 'あなたは日本人のビジネスマン。男性です。様々な質問に回答します。#キャラクター設定プロンプト
'text_body2 = '長谷川健さんの体重を教えてください。#質問文
'text_body = text_body1+text_body2
print('input文字数:',len(text_body))
from openai import OpenAI
client = OpenAI(api_key = "sk-******")
response = client.chat.completions.create(
model="ft:gpt-3.5-turbo-0613:personal::******",
messages=[
{"role": "system", "content": text_body1},
{"role": "user", "content": text_body2}
],
#temperature=1
)
print(response.choices[0].message)試験仕様
1.教えた事を聞く(試験1) 14件 (素直に聞く 11件,聞き方を少し変える 3件)
2.教えていない事を聞く(試験2) 10件
3.教えていた事と似た事を聞く(試験3) 4件
※追加学習モデルの試験方法はWebで色々調べたのですが、良い事例が無く、今回は適当に作成しました。このあたりの情報をご存じの方はご教示いただけますと幸いです。
学習データ(再確認)
覚えさせた事は、これらでした。

結果:試験1(教えた事を聞く)
質問1:「愛上 尾根さんの職業を教えてください。」
回答1:「パティシエです。」
→ 覚えていて、正確に答えている。
質問2:「愛上 尾根さんの性別を教えてください。」
回答2:「男性です。」
→ 覚えていて、正確に答えている。
質問3:「愛上 尾根さんの出身地を教えてください。」
回答3:「青森県五所川原市です。」
→ 覚えていて、正確に答えている。
質問4:「愛上 尾根さんの現在住所を教えてください。」
回答4:「東京都品川区西五反田8丁目1−5です。」
→ 覚えていて、正確に答えている。
質問5:「愛上 尾根さんの出身大学を教えてください。」
回答5:「日本大学です。」
→ 覚えていて、正確に答えている。
質問6:「愛上 尾根さんの好きな食べ物を教えてください。」
回答6:「麻婆豆腐です。辛いものが好きです。」
→ 覚えていて、正確に答えている。
質問7:「愛上 尾根さんの特技を教えてください。」
回答7:「パズルです。」
→ 覚えていて、正確に答えている。
質問8:「愛上 尾根さんの身長を教えてください。」
回答8:「172cmです。」
→ 覚えていて、正確に答えている。
質問9:「愛上 尾根さんの生年月日を教えてください。」
回答9:「1998年9月12日です。」
→ 失敗例。覚えていないのか、数字に弱いのか。(正解は「 1998年9月14日」)
質問10:「愛上 尾根さんの出身高校を教えてください。」
回答10:

→ 失敗。何故これらが回答されるか、全く想像つかない。(正解は「五所川原工科高校です。」)
質問11:「愛上 尾根さんの体重を教えてください。」
回答11:

→ 失敗。2回目から正しい回答。やはり数字に弱いのか。(正解は「82kgです。」)
質問12: 「愛上 尾根さんの体重は?」
回答12: 「82kgです。」
質問13: 「愛上 尾根さんの体重はどの位ですか?」
回答13: 「82kgです。」
質問14: 「愛上 尾根さんはどの位の重さですか?」
回答14: 「82kgです。」
→ 少し違う聞き方をしても、完璧に回答。すごい。
結果:試験2(教えていない事を聞く)
質問15: 「愛上 尾根さんの息子の名前を教えてください。」
回答15: 「次郎です。」
→ 教えていない事を質問し、結果NG。噂のハルシネーション!
質問16: 「愛上 尾根さんの会社名を教えてください。」
回答16:「パワーパートナーズ株式会社です。」
→ 教えていない事を質問し、結果NG。なぜこの会社?!
質問17: 「愛上 尾根さんは大学を卒業した年を教えてください。」
回答17:「2018年です。」
→ 教えていない事を質問し、結果NG。
生年月日から逆算した年齢だと20才になる年だが、、正しくはない。
質問18: 「愛上 尾根さんの趣味を教えてください。」
回答18:「サッカーです。」
→ 教えていない事を質問し、結果NG。「知らない」が正解。
質問19: 「愛上 尾根さんの婚姻状況を教えてください。」
回答19:「既婚です。」
→ 教えていない事を質問し、結果NG。これも幻覚。
質問20: 「愛上 尾根さんの婚姻状況を教えてください。前提条件として、ファインチューニングで学習した情報以外の情報から回答することは絶対にしないでください。」
回答20:「既婚です。」
→ NG。webで見た情報を参考に試してみたが、失敗。
質問21: 「愛上 尾根さんの婚姻状況を教えてください。」temperature=0
(colabo上スクリプトのclient.chat.completions.createでtemperature=0 を追加指定する。同じ回答をさせるパラメータ。)
回答21:「既婚です。」
→ NG。APIのパラメータを変えてみたが、失敗。
質問22: 「愛上 尾根さんの婚姻状況を教えてください。」temperature=1
(colabo上スクリプトのclient.chat.completions.createでtemperature=1 を追加指定する。同じ回答をさせるパラメータ。)
回答22:「独身です。」
→ NG。自由度を上げてみたら、新たないい加減な回答に。。
質問23: 「愛上 尾根さんの婚姻状況を教えてください。」
(colabo上スクリプトのclient.chat.completions.createでモデル: gpt-3.5-turbo-0613(普通のモデル)に切替。)
回答23:「申し訳ありませんが、愛上 尾根さんの婚姻状況についてはわかりません。情報がないため、具体的な回答ができません。ご了承ください。」
→ 良い回答。追加学習モデルでもこれを期待していた。
質問24: 「愛上 尾根さんの職業を教えてください。」
回答24:「申し訳ありませんが、私は愛上尾根さんの職業を知りません。」
→ 良い回答。追加学習モデルでもこれを期待していた。
結果:試験3(教えていた事と似た事を聞く)
質問25: 「愛上尾和さんの体重を教えてください。」
(colabo上スクリプトのclient.chat.completions.createでファインチューニングしたモデル:ft:gpt-3.5-turbo-0613:***に戻した。)
回答25:「82kgです。」
→ 1文字変えた。NG。人の名前が違うのに、同じ人と認識したような回答。
質問26: 「愛上隆史さんの体重を教えてください。」
回答26:「82kgです。」
→ 2文字変えた。NG。違いを増やしたが、結果は同じ。
質問27: 「愛田正雄さんの体重を教えてください。」
回答27:「82kgです。」
→ 3文字変えた。NG。さらに増やしたが、だめ。
質問28: 「長谷川健さんの体重を教えてください。」
回答28:「82kgです。」
→ NG。名前部分を全て変えてもだめ。
課金関連の事
試験をした結果、わかった事は以下の通りです。
・ トークン数は、1トークンづつカウントされています。
・ 最小単位1000トークンに達したら、金額表示に反映される様子です。
・ 各メッセージ(input,output,train)のトークン数には、タグが含まれていそうです。
(今回は細部がはっきりしなかった。)
・ 実際の支払は月単位で締め、請求(カード引落し)されるはずです。
(利用規約等を要再確認)
・ OpenAIのコンソール上のUsage→Activityで、確認可能です。

追加:最新環境で再実験
追加学習ができそうなモデルの候補3つを調べました。結果は下表の通りです。
| モデル | 版 | 調べた結果 |
|---|---|---|
| gpt-3.5-turbo-0125 (2024/1/25) | 3.5最終 | 追加学習 可能 |
| gpt-4-0613 (2023/06/13) | 4唯一のFT可能版? | 追加学習 不可 |
| gpt-4o-2024-05-13 | 4o版 | 追加学習 不可 |
2024年6月20日時点で、追加学習可能な最新モデルは「gpt-3.5-turbo-0125」だけのようです。「gpt-4-0613」と「gpt-4o」は実験的に出来るかのような記載がありますが、コンソール上の追加学習のベースモデル選択で表示されないし、APIで試してもエラーになってしまいました。
[※2024年8月6日追記] gpt-4o-mini-2024-07-18がリリースされました。追加学習は可能そうです。詳細は近々確認予定です。


※OpenAIのヘルプ向けチャットボットで質問しましたが、良く分かりませんでした。

最新環境で再実験結果
改善した箇所が26件中3件。その結果内容は以下の通りです。
・数字の認識が良化したかもしれません。(2件)
・教えられた事から類推した様子です。(1件)
・幻覚は直らない(前回幻覚した分100%)
「gpt-3.5-turbo-0613」の結果に比べると以下の結果表です。
| 質問 | 期待値 | gpt-3.5-turbo-0613 回答 | 最新モデル: gpt-3.5-turbo-0125 回答 | 結果 〇:改善 △:同じ |
|---|---|---|---|---|
| 愛上 尾根さんの生年月日を教えてください。 | 「1998年9月14日です。」 | 「1998年9月12日です。」 | 「1998年9月14日です。」 | 〇 |
| 愛上 尾根さんの職業を教えてください。 | 「パティシエです。」 | 「パティシエです。」 | 「パティシエです。」 | △ |
| 愛上 尾根さんの性別を教えてください。 | 「男性です。」 | 「男性です。」 | 「男性です。」 | △ |
| 愛上 尾根さんの出身地を教えてください。 | 「青森県五所川原市です。」 | 「青森県五所川原市です。」 | 「青森県五所川原市です。」 | △ |
| 愛上 尾根さんの現在住所を教えてください。 | 「東京都品川区西五反田8丁目1−5です。」 | 「東京都品川区西五反田8丁目1−5です。」 | 「東京都品川区西五反田8丁目1−5です。」 | △ |
| 愛上 尾根さんの出身高校を教えてください。 | 「日本大学芸術学部附属高等学校です。」 | 1回目「日出高校です。」 2回目「日本工業高校です。」 3回目「日ノ出高校です。」 4回目「日本航空高校です。」 5回目「日本大学芸術学部附属高等学校です。」 | 「日葉高校です。」 | △ |
| 愛上 尾根さんの出身大学を教えてください。 | 「日本大学です。」 | 「日本大学です。」 | 「日本大学です。」 | △ |
| 愛上 尾根さんの好きな食べ物を教えてください。 | 「麻婆豆腐です。辛いものが好きです。」 | 「麻婆豆腐です。辛いものが好きです。」 | 「麻婆豆腐です。辛いものが好きです。」 | △ |
| 愛上 尾根さんの特技を教えてください。 | 「パズルです。」 | 「パズルです。」 | 「パズルです。」 | △ |
| 愛上 尾根さんの身長を教えてください。 | 「172cmです。」 | 「172cmです。」 | 「172cmです。」 | △ |
| 愛上 尾根さんの体重を教えてください。 | 「82kgです。」 | 1回目「85kgです。」 2回目「82kgです。」 3回目「82kgです。」 4回目「82kgです。」 5回目「82kgです。」 | 「82kgです。」 | 〇 |
| 愛上 尾根さんの体重は? | 「82kgです。」 | 「82kgです。」 | 「82kgです。」 | △ |
| 愛上 尾根さんの体重はどの位ですか? | 「82kgです。」 | 「82kgです。」 | 「82kgです。」 | △ |
| 愛上 尾根さんはどの位の重さですか? | 「82kgです。」 | 「82kgです。」 | 「82kgです。」 | △ |
| 愛上 尾根さんの婚姻状況を教えてください。 | 「わかりません。」 | 「既婚です。」 | 「既婚です。」 | △ |
| 愛上 尾根さんの婚姻状況を教えてください。前提条件として、ファインチューニングで学習した情報以外の情報から回答することは絶対にしないでください。 | 「残念ながら、私のトレーニングデータには特定の個人の最新の情報は含まれていません。」 | 「既婚です。」 | 「既婚です。」 | △ |
| 愛上 尾根さんの婚姻状況を教えてください。(temperature=0) | 「残念ながら、私のトレーニングデータには特定の個人の最新の情報は含まれていません。」 | 「既婚です。」 | 「既婚です。」 | △ |
| 愛上 尾根さんの婚姻状況を教えてください。(temperature=1) | 「残念ながら、私のトレーニングデータには特定の個人の最新の情報は含まれていません。」 | 「独身です。」 | 「既婚です。」 | △ |
| 愛上 尾根さんの息子の名前を教えてください。 | 「わかりません。」 | 「次郎です。」 | 「蓮川五月です。」 | △ |
| 愛上 尾根さんの会社名を教えてください。 | 「わかりません。」 | 「パワーパートナーズ株式会社です。」 | 「パインウッドプロダクションです。」 | △ |
| 愛上 尾根さんは大学を卒業した年を教えてください。 | 「わかりません。」 90% 「この方が日本の学校に入学したなら、生年月日を考慮すると、〇年の可能性が高そうですが、確証はありません。」100% | 「2018年です。」 | 「2019年です。」 | △ |
| 愛上 尾根さんの趣味を教えてください。 | 「わかりません。」 | 「サッカーです。」 | 「パズルです。」 | 〇 |
| 愛上尾和さんの体重を教えてください。 | 「わかりません。」 | 「82kgです。」 | 「82kgです。」 | △ |
| 愛上隆史さんの体重を教えてください。 | 「わかりません。」 | 「82kgです。」 | 「82kgです。」 | △ |
| 愛田正雄さんの体重を教えてください。 | 「わかりません。」 | 「82kgです。」 | 「82kgです。」 | △ |
| 長谷川健さんの体重を教えてください。 | 「わかりません。」 | 「82kgです。」 | 「82kgです。」 | △ |
まとめ
1.教えた事を回答するのはまずまず出来るが、教えてない事で嘘をつくのが問題です。
2.その幻覚をさらなる追加学習で排除するのは、工数が読めないので難しそうです。
3.幻覚を排除するためには、独自情報を優先的に扱う事が必要で、RAGでそれを解決できそうです。こちらで確認しました。→RAGの記事
4.2024年6月20日時点で、追加学習可能な最新モデルは「gpt-3.5-turbo-0613」です。gpt-4-0613とgpt-4oは実験的アクセスプログラムですが、追加学習は出来ないです(私は)。
5.追加学習の精度を、2023年12月とその半年後の2回確認し、改善した部分はありましたが、依然としてハルシネーションが直りません。
6.次はオープンソースのLLM(MetaのLlama等)の追加学習を試してみたいと思います。
