LLM事前学習の流れ
これからLLMのパラメータを記述した設定ファイルからLLMのモデルを作成し、事前学習を試してみたいと思います。
今回は、LLMの生成と学習の流れを確認するためにごく小規模なLLM(というかSLM)を作って「私は歩く」という文を学習させてみましょう。
トークナイザーの作成
LLMのモデルの前に、まず文字列をLLMで扱う数値配列データ(トークン列)に変換するためのトークナイザーを作ります。
今回は、「私は歩く」という文を学習させてみるので、「私」「は」「歩」「く」と一文字ずつトークン化してみましょう。また、トークナイザーにはpadトークンも必要なので、学習文字列に含まれる4つの文字とpadで計5個のトークンを扱うトークナイザーを作ることにします。
トークナイザーの作成は、トークンに含まれる語と設定を含むjsonファイルを作成し、それをPreTrainedTokenizerFastクラスに読み込ませトークナイザーのディレクトリとして保存する形で行います。
{
"version": "1.0",
"truncation": null,
"padding": null,
"normalizer": null,
"pre_tokenizer": null,
"post_processor": null,
"model": {
"type": "Unigram",
"vocab": [
["<:pad:>", 0],
["私", 1],
["は", 2],
["歩", 3],
["く", 4]
}
}
}
このjsonをtokenizer_src.jsonとして保存したら、PythonにPyTorchとTransformersをpipでインストールして以下のプログラムを実行します。実行後にtokenizerディレクトリにトークナイザーが作成されます。
from transformers import PreTrainedTokenizerFast
tokenizer = PreTrainedTokenizerFast(tokenizer_file="tokenizer_src.json", pad_token="<:pad:>")
tokenizer.save_pretrained(save_directory="tokenizer")
設定ファイルからのモデル作成と学習
トークナイザーが出来たので、トークナイザーを使ってモデルで使うためのデータを保持するDatasetを作ってみます。
今回は、「私は歩く」という一つの文を学習するだけですから、とりあえず長さは「1」でデータとして「私は歩く」をトークン化したデータを返すだけのものにしてみました。
class TestDataset(Dataset):
def __init__(self, tokenizer):
input_ids = tokenizer.encode("私は歩く", return_tensors='pt')[0]
self.token_list = {'input_ids': input_ids}
def __len__(self):
return 1
def __getitem__(self, idx):
return self.token_list
では、いよいよモデルを作成して学習をしてみましょう。モデルは、GPTNeoXForCausalLMに設定ファイルから作ったconfigを渡す形で作ります。
今回は、ごく小規模なLLMで試してみたいのでOpenCALM-Smallのパラメータ設定を元に以下の設定ファイルを作ってみました。
{
"architectures": [
"GPTNeoXForCausalLM"
],
"hidden_act": "gelu",
"hidden_size": 768,
"initializer_range": 0.02,
"intermediate_size": 3072,
"layer_norm_eps": 1e-05,
"max_position_embeddings": 2048,
"model_type": "gpt_neox",
"num_attention_heads": 12,
"num_hidden_layers": 12,
"rotary_emb_base": 10000,
"rotary_pct": 1.0,
"tie_word_embeddings": false,
"torch_dtype": "float32",
"transformers_version": "4.42.0",
"use_cache": true,
"use_parallel_residual": false,
"vocab_size": 5
}
パラメータ数は160Mとのことで、かなり少ないですね。
この設定ファイルをmodel_config_small.jsonとして保存したら、以下のコードでモデルを作成できます。
model_config = GPTNeoXConfig.from_json_file("model_config_small.json")
model = GPTNeoXForCausalLM(model_config)
続いて、Datasetを使ってモデルにデータを供給するcollatorと学習時のパラメータを保持するTrainingArgumentsを作ります。
collator = DataCollatorForLanguageModeling(tokenizer, mlm=False)
training_args = TrainingArguments(output_dir="output", save_steps=1, num_train_epochs=1, learning_rate=5e-5, lr_scheduler_type="constant", per_device_train_batch_size=1)
とりあえず学習率5e-5(0.00005)で1回だけ学習するようにしてみました。
モデルを作成して事前学習を行うコードは、以下のようになります。
import torch
from torch.utils.data import Dataset
from transformers import AutoTokenizer, DataCollatorForLanguageModeling
from transformers import GPTNeoXConfig, GPTNeoXForCausalLM
from transformers import Trainer, TrainingArguments
class TestDataset(Dataset):
def __init__(self, tokenizer):
input_ids = tokenizer.encode("私は歩く", return_tensors='pt')[0]
self.token_list = {'input_ids': input_ids}
def __len__(self):
return 1
def __getitem__(self, idx):
return self.token_list
model_config = GPTNeoXConfig.from_json_file("model_config_small.json")
model = GPTNeoXForCausalLM(model_config)
tokenizer = AutoTokenizer.from_pretrained("tokenizer")
dataset = TestDataset(tokenizer)
collator = DataCollatorForLanguageModeling(tokenizer, mlm=False)
training_args = TrainingArguments(output_dir="output", save_steps=1, num_train_epochs=1, learning_rate=5e-5, lr_scheduler_type="constant", per_device_train_batch_size=1)
trainer = Trainer(model=model, data_collator=collator, args=training_args, train_dataset=dataset)
trainer.train()
accelerateをインストールして実行すると、学習結果がoutput/checkpoint-1に保存されます。
推論
最後に学習したモデルを読み込んで、「私」に続く語を4つほど生成してみます。
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
model = AutoModelForCausalLM.from_pretrained("output/checkpoint-1")
tokenizer = AutoTokenizer.from_pretrained("tokenizer")
input = tokenizer("私", return_tensors="pt", return_token_type_ids=False)
texts = model.generate(**input, max_new_tokens=6, pad_token_id=tokenizer.pad_token_id, do_sample=True, num_return_sequences=4)
for text in texts:
print(tokenizer.decode(text, skip_special_tokens = True))
生成結果
私 は
私 は
私 は
私 く く は
ごく小規模なモデルで1回だけの学習ですが、「私」に続いて「は」を返す傾向が出てきているようです。データ文を「学習」していると言えるでしょう。