モデルの構造とかよりはコードベースで使い方いろいろ勉強しつつ、やったことベースでそのまま書く。
随時書き足していく
この本に沿ってるので動かすにあたりだいたい同じコードは記載を省略する。
LLMのファインチューニングとRAG
チャットボット開発による実践
やること1 既存のモデル使って入力した単語の続きを予測
コード内容
- 日本語LLMのopen-calm-smallをダウンロード
- モデルに対応するトークナイザー(文字→数値変換器)を読み込み
- 入力文を数値化(テンソル形式に変換)
- モデルが続く単語を予測
- 数値をテキストに戻して出力
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
# モデルとトークナイザーを読み込み
model = AutoModelForCausalLM.from_pretrained(
"cyberagent/open-calm-small")
tokenizer = AutoTokenizer.from_pretrained(
"cyberagent/open-calm-small")
# 入力文
input = tokenizer("琵琶湖は、", return_tensors="pt")
# 続きを生成(最大5トークン分)
tokens = model.generate(**input, max_new_tokens=5)
# 結果をデコードして出力
print(tokenizer.decode(tokens[0], skip_special_tokens=True))
出力
琵琶湖は、海から遠いので、
出力 max_new_tokens=10
琵琶湖は、海から遠いので、海から遠い場所にある
結構雑な予測みたい。
次はファインチューニングをしてモデルの出力が変化することを確認する
やること2 テキストデータ読み込ませてファインチューニング
既存のモデル(open-calm-small)で、"僕は"スタートで動かしたときの出力を確認。
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
# モデル読み込み
model_name = "cyberagent/open-calm-small"
model = AutoModelForCausalLM.from_pretrained(model_name)
tokenizer = AutoTokenizer.from_pretrained(model_name)
input = tokenizer.encode("僕は", return_tensors="pt")
with torch.no_grad():
tokens = model.generate(
input,
max_new_tokens=30,
do_sample=True,
top_p=0.9,
temperature=0.7,
)
output = tokenizer.decode(tokens[0], skip_special_tokens=True)
print(output)
出力
僕は、彼には、自分がどうやったらできるかを考える力がある、
という、人間的な魅力に惹かれたのでしょう。
私自身は、彼のように、
ファインチューニングしてその内容に合わせて変わること確認する。
無料配布されてる方言談話テキストデータから岩手のデータ使用。
国立国語研究所 方言談話資料データ
テキストデータから欲しい部分抽出はchatGPTにお任せした。
カタカナで方言の対談がテキストにまとめられてる。
気になるところはあったが、下記コードでなんとなくほしいところ抽出できていたのでよしとした。
テキストデータ読み込んでtrainデータとか作成
import codecs
input_files = [
"05Ⅰ岩手01.txt",
"05Ⅰ岩手02.txt",
]
# UTF-8で保存
ftrain = codecs.open("train.txt", "w", "utf-8")
fval = codecs.open("val.txt", "w", "utf-8")
ftest = codecs.open("test.txt", "w", "utf-8")
n = 0
flg = 0
for infile in input_files:
with codecs.open(infile, "r", "cp932", errors="ignore") as f:
for line in f:
line = line.strip()
# 空行やヘッダーっぽい行をスキップ
if not line:
continue
if "|" not in line:
continue
parts = line.split("|")
if len(parts) < 4:
# 4列ない行はスキップ
continue
utt_id, speaker, dialect, standard = parts[:4]
# どのカラムを使うか選ぶ
text = dialect # 方言(3列目)で学習
text = text.strip()
if not text:
continue # 空はスキップ(0トークン防止)
# ここから 1 発話 = 1 行として扱う
n += 1
# 10行ごとに val/test に振り分け(それ以外は train)
if n % 10 == 0:
if flg == 0:
fval.write(text + "\n")
flg = 1
else:
ftest.write(text + "\n")
flg = 0
else:
ftrain.write(text + "\n")
ftrain.close()
fval.close()
ftest.close()
print("作成しました")
学習
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from torch.utils.data import Dataset
model_name = "cyberagent/open-calm-small"
model = AutoModelForCausalLM.from_pretrained(model_name)
tokenizer = AutoTokenizer.from_pretrained(model_name)
class MyDataset(Dataset):
def __init__(self, filename, tokenizer):
self.tokenizer = tokenizer
self.features = []
with open(filename,'r',encoding='utf-8') as f:
lines = f.read().split('\n')
for line in lines:
input_ids = self.tokenizer.encode(line,
padding='longest',
max_length=512,
return_tensors='pt')[0]
self.features.append({'input_ids': input_ids})
def __len__(self):
return len(self.features)
def __getitem__(self, idx):
return self.features[idx]
train_dataset = MyDataset("train.txt", tokenizer)
from transformers import DataCollatorForLanguageModeling
collator = DataCollatorForLanguageModeling(tokenizer, mlm=False)
from torch.utils.data import DataLoader
dataloader = DataLoader(train_dataset, batch_size=10,
shuffle=True, collate_fn=collator)
from transformers import Trainer, TrainingArguments
training_args = TrainingArguments(
output_dir='./output',
num_train_epochs=10,
per_device_train_batch_size=10,
)
trainer = Trainer(
model=model,
data_collator=collator,
args=training_args,
train_dataset=train_dataset
)
trainer.train()
チューニングしたモデルで"僕は"の続き出力
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
# 追加で学習したモデル読み込み
model = AutoModelForCausalLM.from_pretrained("./output/checkpoint-460/")
tokenizer = AutoTokenizer.from_pretrained("cyberagent/open-calm-small")
input = tokenizer.encode("僕は", return_tensors="pt")
with torch.no_grad():
tokens = model.generate(
input,
max_new_tokens=30,
do_sample=True,
top_p=0.9,
temperature=0.7)
output = tokenizer.decode(tokens[0], skip_special_tokens=True)
print(output)
出力
僕はオドント ダリダ ガッコーサン[60]ダリダッタヨ。《笑》。ダリダッタヨ。ダ
おそらく学習データに含まれてるだろう方言が出てきて意味は通ってなさそうだが、元データもそんなに長くないしチューニングされていることは確認できたのでOK。
collator バッチ作るための前処理的なことで以下
パディング:batch内で最大長に揃える
attension mask作成
ラベル作成
次回は別のチューニングを試す。
- Instruction Tuning
通常のファインチューニングはタスクごとにタスク固有のデータで学習させるのに対し、
様々なタスクに対応できるようにモデルを学習させる。
指示形式のデータセットで学習させる。
公開データセット:databricks-dolly-15k-jaなど
dolly_ja['train'][5]
{'output': 'いいえ。\nステイルメイトとは、引き分けた状態のことです。どちらがより多くの駒を捕獲したか、または優勢であるかは関係ない',
'input': 'ステイルメイトとは、チェスにおいて、手番が回ってきたプレーヤーがチェックされておらず、合法的な手がない状態のことである。ステイルメイトの結果、引き分けとなる。終盤では、ステイルメイトは劣勢にあるプレイヤーが負けるのではなく、ゲームを引き分けることを可能にする戦術である[2]。より複雑なポジションでは、ステイルメイトはより稀で、通常は優勢側が不注意な場合にのみ成功する詐欺の形をとる[引用] ステイルメイトは終盤研究や他のチェスの問題においても共通のテーマである。\n\nステイルメイトが引き分けに統一されたのは19世紀である。それ以前は、ステイルメイトしているプレイヤーの勝利、引き分け、負けとみなされたり、反則となったり、ステイルメイトしているプレイヤーはターンを失うことになったりと、その扱いは様々であった。ステイルメイトのルールは、チェス以外のチャトランガ系ゲームごとに異なる。',
'index': '5',
'category': 'information_extraction',
'instruction': 'ステイルメイトの時に、私の方が多くの駒を持っていたら、私の勝ちですか?'}
用意したテンプレートに沿ってデータを成形してデータセット作成、学習
LoRA
RAG