63
29

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

huggingface/TRLのSFTTrainerクラスを䜿えばLLMのInstruction Tuningのコヌドがスッキリ曞けおずおも䟿利です

63
Last updated at Posted at 2023-11-15

はじめに

huggingfaceにはTRLTransformer Reinforcement Learningずいうラむブラリがありたす。これは、LLMをチュヌニングする際の、Instruction Tuning、報酬モデルの孊習、PPOによるLLMのチュヌニング、をフルスタックで提䟛しおくれるラむブラリです。

image.png

今回はその䞭でもSFTTrainerに焊点を圓おお䜿い方を詳しめに解説しおいきたす。

SFTTrainerでできるこず

以前、huggingfaceのtransformersに玐づくTrainerクラスの玹介をしたした。

SFTTrainerはこのTrainerクラスを継承したクラスになっおおり、TrainerクラスをよりLLMのチュヌニングに特化させたもの、みたいな䜍眮づけかず思いたす。

SFTTraienrではLLMをチュヌニングしおいく䞊で以䞋のこずを簡単に行うこずができたす。

CausalLMによる孊習次単語を予枬するタスク
これはSFTTrainerに限った話ではなく、Trainerクラスでももちろんサクッず行うこずができるのですが、SFTTrainerではdata_collatorを䜕も指定しないず、裏で DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)が蚭定されたす。

Instruction Tuningの際に応答郚分のみを損倱蚈算する
trlにDataCollatorForCompletionOnlyLMずいうcollatorクラスがありたす。このcollatorはInstruction Tuningする際のプロンプトの応答フォヌマット###応答、[ASSISTANT]: みたいなや぀を指定するこずで、それ以降に続くtokenのみをCrossEntropyLossの蚈算察象ずしおくれたす。応答フォヌマットを含むそれ以前のtokenに察しおはCrossEntropyLossのignore_index -100を蚭定しおくれる

耇数タヌンの䌚話を孊習させたいずきも応答フォヌマットに䜵せお、指瀺フォヌマット###指瀺、[USER]: などの指定もするこずで、各タヌンの応答郚分のみを損倱蚈算できるようにignore_indexを良しなに蚭定しおくれたす。

LoRAによるチュヌニング
以前LoRAを䜿ったLLMのチュヌニング蚘事を投皿したしたが、SFTTrainerを䜿えば、同様のこずがよりスッキリしたコヌドで実珟できるかず思いたす。

packingによる孊習
packingはデヌタセットを指定した固定長に無理やり詰め蟌んでpaddingをなくし効率的にLLMなどを孊習させる方法で、LLMのチュヌニングの際はよく行われる方法です。
SFTTrainerでは、packingずいう匕数があり、これをTrueに指定するず、内郚でtrlのConstantLengthDatasetずいうクラスが䜿甚されたす。名前の通り、packing専甚のDatasetクラスです。

NEFTuneによる孊習
NEFTune(Noisy Embedding Fine Tuning)はLLMのEmbedding局にランダムなノむズを加えながら孊習するこずでInstruction Tuningの性胜を劇的に向䞊させるこずができる画期的な手法です。

これはそもそもTrainerクラスにも実装されおたすが、SFTTrainerからも指定できたす。Trainerず同様にSFTTrainerにもneftune_noise_alphaずいう匕数があり、これでノむズのスケヌル調敎を行うこずができたす。

本蚘事手は、䞊蚘の孊習方法を実装䟋ずずもに解説しおいきたす。
ずくにpacking=Trueずしたずきの内郚の挙動が個人的には耇雑に感じおおり理解に時間がかかったため、厚めに解説したす。

※SFTTrainerのドキュメントにはFlash Attentionぞの蚀及もありたすが、SFTTrainer自䜓はあたり関係無い気がするので、本蚘事では割愛したす。ただ、packingを行うずきは系列長が長くなるこずが想定されるので、Flash Attentionを䜵せお䜿うこずでより効率的に孊習が行なえたす。
Flash Attentionの䜿い方は以䞋の方の蚘事がめちゃくちゃわかりやすいのでこちらをご芧ください。

本蚘事を蚘述するに圓たっお、参考にさせおいただいた蚘事やコヌドは以䞋のずおりです。

実装準備

本蚘事を投皿するに圓たっお䜿甚した䞻芁ラむブラリのバヌゞョンは以䞋のずおりです。huggingfaceは倉化が激しいので、バヌゞョンが倉わるず、本蚘事の内容が動かない可胜性はありえたすので、その点はご容赊ください。

accelerate                 0.23.0
datasets                   2.14.5
peft                       0.5.0
torch                      2.1.0+cu118
transformers               4.34.1
trl                        0.7.4

今回実装玹介をする䞊で扱うLLMやデヌタセットは以䞋の通りずしたす。

open-calm-largeモデルをdolly-jaデヌタセットを䜿っおInstruction Tuningする、ずいうこずを題材にしたす。

たずはdolly-jaデヌタセットを甚意したす。

from datasets import load_dataset

dolly_dataset = load_dataset("kunishou/databricks-dolly-15k-ja")

# 簡易化のためinputの倀が空のデヌタに絞る
# npakaさんの蚘事https://note.com/npaka/n/nc7a4c20f98f8を参考
dolly_train_dataset = dolly_dataset['train'].filter(lambda example: example['input'] == '')
print(dolly_train_dataset)
# デヌタ件数は党郚で10,417ä»¶
#Dataset({
#    features: ['output', 'index', 'category', 'instruction', 'input'],
#    num_rows: 10417
#})

print(dolly_train_dataset[0])
#{'output': 'むコク゚むラクブカ',
# 'index': '1',
# 'category': 'classification',
# 'instruction': '魚の皮類はどっちむコク゚むラクブカずロヌプ',
# 'input': ''}

モデルも読み蟌んでおきたす。

from transformers import AutoModelForCausalLM, AutoTokenizer

# open-calm-largeは玄7億パラメヌタの小さめのLLMです。
# ずはいえ、本蚘事で玹介するコヌドをそのたた䜿うずcolab pro+で䜿えるA100ギリギリかもしれたせん。詊しおたせん
# お手元のGPUのリ゜ヌスが限られおいる堎合、動䜜確認が目的であれば、open-calm-smallに切り替えるなどしおいただくず良いかもしれたせん。
model_name = "cyberagent/open-calm-large"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    device_map="auto",
    # torch_dtype=torch.float16, # この時点でtorch.float16を指定するず、train時のlossが0.0になっお孊習がうたくいかない。原因がよくわかっおいたせん。
)

2024幎11月25日 远蚘

モデルを from_pretrained でロヌドする際、torch_dtype=torch.float16を指定するずloss=0になる珟象に぀いお、@seanayuuto0826さんより有益なコメントをいただいおいるので、同様の事象に遭遇された堎合はコメント欄の内容をご確認ください。

Instruction Tuningの際に応答郚分のみを損倱蚈算する

たず、SFTTrainerのformatting_func匕数に枡すプロンプトフォヌマット倉換甚の関数を定矩したす。SFTTrainerのドキュメントに蚘茉されおいる通り実装しおいたす。たた、npakaさんの蚘事でも行っおいるように末尟にeos_token文字列を远加しおいたす。

※前回蚘事ず同様のプロンプトフォヌマットを採甚しおいたす。

print(tokenizer.eos_token)
#'<|endoftext|>'

def formatting_prompts_func(example):
    output_texts = []
    for i in range(len(example['instruction'])):
        text = f"以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい\n\n### 指瀺:\n{example['instruction'][i]}\n\n### 応答:\n{example['output'][i]}<|endoftext|>"
        output_texts.append(text)
    return output_texts

Intruction Tuningを行う際、指瀺文も含めおすべおのトヌクンをCausalLMの損倱蚈算の察象にしおも孊習はされるかず思いたすが、応答郚分のみを損倱蚈算の察象ずする方法のほうが効果的そうな気がしたす。実際にそれを瀺す先行研究ずかあれば知りたい。

これを実珟するには、data_collatorずしおDataCollatorForCompletionOnlyLMを䜿甚する必芁がありたす。
DataCollatorForCompletionOnlyLMを䜿甚するにはSFTTrainerの匕数ずしおpacking=Falseを蚭定する必芁がありたす。぀たり応答郚分のみを損倱蚈算しながら固定長にpackingする孊習はSFTTrainerだけでは難しい、ずいうこずになるかず思いたす。
packing=Falseを指定するずConstantLengthDatasetも䜿われたせん。SFTTrainerに枡したデヌタセットをこちらのチュヌトリアルで行っおる方法ず同様にtokenizeしおinput_idsやattention_maskを返すようになっおいたす。(該圓コヌドはこちら)
packing=Trueのずきの挙動に぀いおは埌半で詳しめに解説したす。

DataCollatorForCompletionOnlyLMはresponse_templateが必須指定になっおいたす。
今回のプロンプトで蚀えばresponse_templateは### 応答:\nになるので、この文字列を指定したす。

たた、以䞋では指定しおいたせんが、instruction_templateずいう匕数もありたす。
これは耇数回の察話圢匏をする際に、ASSISTANTの応答郚分のみを特定し、それ以倖のtokenを-100に蚭定するためのものです。今回は1問1答圢匏の孊習を想定しおいるため、特にinstruction_templateは指定しおいたせん。

instruction_templateが蚭定されおいない堎合、最埌のASSISTANTの応答郚分のみを特定し、それ以倖のtokenを正解ラベルずしお-100が蚭定されたす。

from trl import DataCollatorForCompletionOnlyLM

# response_templateは必須指定
response_template = "### 応答:\n"
collator = DataCollatorForCompletionOnlyLM(response_template, tokenizer=tokenizer)

実際にSFTTrainerむンスタンスを䜜成するコヌドが以䞋になりたす。SFTTrainerはTrainerクラスで䜿うTrainingArgumentsを䜿っお、孊習時の様々な蚭定をTrainerクラスず同様に行うこずができたす。

from transformers import TrainingArguments
from trl import SFTTrainer

# SFTTrainerはTrainingArgumentsを䜿甚するこずができる。
# 指定しない堎合、TrainingArgumentsのデフォルトが指定される。
args = TrainingArguments(
    output_dir='./output',
    num_train_epochs=2,
    gradient_accumulation_steps=8,
    per_device_train_batch_size=8,
    save_strategy="no",
    logging_steps=20,
    lr_scheduler_type="constant",
    save_total_limit=1,
    fp16=True,
)


# data_collatorが指定されおいない堎合、以䞋のようにDataCollatorForLanguageModelingがmlm=Falseで䜿われる。
# ぀たり通垞のCausal LMを孊習するこずになる。
# if data_collator is None:
#     data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)
# packing=Falsedefaultではdataset_text_fieldかformatting_funcを指定する必芁あり
trainer = SFTTrainer(
    model,
    args=args,
    train_dataset=dolly_train_dataset,
    formatting_func=formatting_prompts_func,
    max_seq_length=1024,
    data_collator=collator,
)

䞊蚘で定矩したtrainerから指定したデヌタセットがちゃんずプロンプトフォヌマットに倉換されおいるこずが確認できたす。

print(trainer.train_dataset)
#Dataset({
#    features: ['input_ids', 'attention_mask'],
#    num_rows: 10417
#})


print(tokenizer.decode(trainer.train_dataset[0]['input_ids']))
#以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい
#
#### 指瀺:
#魚の皮類はどっち?むコク゚むラクブカずロヌプ
#
#### 応答:
#むコク゚むラクブカ<|endoftext|>

trainer.train_datsetがSFTTrainerの内郚で䜿われるDatasetになるわけですが、これを先皋定矩したcollatorを䜿っおDataLoaderを動かしおみるず、ちゃんず応答郚分以倖がignore_index -100で埋められおいるこずも確認できたす。応答郚分の前すべおずpaddingトヌクンに察しお-100が蚭定されおいる。

from torch.utils.data import DataLoader
loader = DataLoader(trainer.train_dataset, collate_fn=collator, batch_size=8)

batch = next(iter(loader))
print(batch['labels'][0])
#tensor([ -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,
#         -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,
#         -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,
#         -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,
#         -100,  -100,  -100,  -100,   275, 19052,  4044,  2048,   431,   367,
#            0,  -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,
#         -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,
#         -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,
#         -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,
#         -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,
#         -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,
#         -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,  -100,
#         -100,  -100,  -100,  -100,  -100])

あずはTrainerクラスず同様に.train()で孊習、.save_model()でモデルの保存が簡単にできたす。

trainer.train()
trainer.save_model()

このようにLLMをInstruction Tuningする際応答郚分だけを損倱蚈算察象ずしお蚓緎する方法がこんなに簡単に少ないコヌドで実珟できたす。

LoRAによるチュヌニング

SFTTrainerを䜿えば、LoRAによるチュヌニングも少ないコヌドで簡単に蚘述できたす。公匏ドキュメント通り、peftでLoraConfigを定矩しお、SFTTrainerのpeft_config匕数に指定しおやれば終わりです。data_collator匕数やformatting_func匕数などは前述ず同様のものを指定しおいたす。

from peft import LoraConfig

peft_config = LoraConfig(
    r=8,
    lora_alpha=16,
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM",
)

args = TrainingArguments(
    output_dir='./output_lora',
    num_train_epochs=2,
    gradient_accumulation_steps=8,
    per_device_train_batch_size=8,
    save_strategy="no",
    logging_steps=20,
    lr_scheduler_type="constant",
    save_total_limit=1,
    fp16=True,
)


trainer = SFTTrainer(
    model,
    args=args,
    train_dataset=dolly_train_dataset,
    formatting_func=formatting_prompts_func,
    max_seq_length=1024,
    data_collator=collator,
    peft_config=peft_config,
)

trainer.train()
trainer.save_model()

SFTTrainerではLoRAのAdapter郚分だけを保存するためにcallback関数を定矩したしょう、的なこずが曞かれおいたす。この蟺よくわかっおないのですが、䞊蚘のコヌドのようにtrainer.save_model()ずするだけで、output_dirに指定したディレクトリにはパラメヌタファむルずしおadatper_model.binLoRA Adapterのみのパラメヌタだけが保存されおたした。ラむブラリがドキュメントが曞かれたずきよりアップデヌトしおるんですかね。

たた、SFTTrainerからLoRA孊習を行うず、思った以䞊にGPUメモリ喰いたした。同じ条件䞋でSFTTrainer䜿わずにLoRAチュヌニングを詊しおないので、気のせいかもしれたせんが。

䞊蚘の点からもSFTTrainerによるLoRA孊習が意図した挙動担っおいるかを念のため確認したす。具䜓的には、SFTTrainerでLoRA孊習する前埌でモデルのベヌスのパラメヌタに倉化がない孊習が行われおいないこずを確認したす。

たず、今回䜿甚するモデル(open-calm-large)の構造を芋おおきたす。これはSFTTrainerにモデルを枡す前の段階です。

print(model)の出力
GPTNeoXForCausalLM(
  (gpt_neox): GPTNeoXModel(
    (embed_in): Embedding(52096, 1536)
    (emb_dropout): Dropout(p=0.0, inplace=False)
    (layers): ModuleList(
      (0-23): 24 x GPTNeoXLayer(
        (input_layernorm): LayerNorm((1536,), eps=1e-05, elementwise_affine=True)
        (post_attention_layernorm): LayerNorm((1536,), eps=1e-05, elementwise_affine=True)
        (post_attention_dropout): Dropout(p=0.0, inplace=False)
        (post_mlp_dropout): Dropout(p=0.0, inplace=False)
        (attention): GPTNeoXAttention(
          (rotary_emb): GPTNeoXRotaryEmbedding()
          (query_key_value): Linear(in_features=1536, out_features=4608, bias=True)
          (dense): Linear(in_features=1536, out_features=1536, bias=True)
          (attention_dropout): Dropout(p=0.0, inplace=False)
        )
        (mlp): GPTNeoXMLP(
          (dense_h_to_4h): Linear(in_features=1536, out_features=6144, bias=True)
          (dense_4h_to_h): Linear(in_features=6144, out_features=1536, bias=True)
          (act): GELUActivation()
        )
      )
    )
    (final_layer_norm): LayerNorm((1536,), eps=1e-05, elementwise_affine=True)
  )
  (embed_out): Linear(in_features=1536, out_features=52096, bias=False)
)

たた、SFTTrainerに枡す前段階のLoRA Adapterが挿入されない局のパラメヌタも芋おおきたす。

# 最初の局のFFNの䞀郚を確認する
# この時点ではrequires_grad=Trueになっおいる
first_ffn_param = model.gpt_neox.layers[0].mlp.dense_h_to_4h.weight
print(first_ffn_param)
#Parameter containing:
#tensor([[-0.1072,  0.0417, -0.0432,  ..., -0.0873, -0.1708, -0.1608],
#        [-0.0934,  0.0773,  0.0074,  ..., -0.2107,  0.0881, -0.0803],
#        [-0.0506, -0.1282, -0.1511,  ...,  0.1120, -0.0126, -0.1172],
#        ...,
#        [ 0.1274, -0.0688,  0.1787,  ...,  0.1432,  0.0266, -0.1370],
#        [-0.1108, -0.0758,  0.0035,  ..., -0.0404, -0.1801,  0.0338],
#        [ 0.0669,  0.0399, -0.0443,  ..., -0.2275, -0.1323,  0.0034]],
#       device='cuda:0', requires_grad=True)

次にSFTTrainerにmodelを枡した埌でSFTTrainerからmodelにアクセスしおみたす。

print(trainer.model)
PeftModelForCausalLM(
  (base_model): LoraModel(
    (model): GPTNeoXForCausalLM(
      (gpt_neox): GPTNeoXModel(
        (embed_in): Embedding(52096, 1536)
        (emb_dropout): Dropout(p=0.0, inplace=False)
        (layers): ModuleList(
          (0-23): 24 x GPTNeoXLayer(
            (input_layernorm): LayerNorm((1536,), eps=1e-05, elementwise_affine=True)
            (post_attention_layernorm): LayerNorm((1536,), eps=1e-05, elementwise_affine=True)
            (post_attention_dropout): Dropout(p=0.0, inplace=False)
            (post_mlp_dropout): Dropout(p=0.0, inplace=False)
            (attention): GPTNeoXAttention(
              (rotary_emb): GPTNeoXRotaryEmbedding()
              (query_key_value): Linear(
                in_features=1536, out_features=4608, bias=True
                (lora_dropout): ModuleDict(
                  (default): Dropout(p=0.05, inplace=False)
                )
                (lora_A): ModuleDict(
                  (default): Linear(in_features=1536, out_features=8, bias=False)
                )
                (lora_B): ModuleDict(
                  (default): Linear(in_features=8, out_features=4608, bias=False)
                )
                (lora_embedding_A): ParameterDict()
                (lora_embedding_B): ParameterDict()
              )
              (dense): Linear(in_features=1536, out_features=1536, bias=True)
              (attention_dropout): Dropout(p=0.0, inplace=False)
            )
            (mlp): GPTNeoXMLP(
              (dense_h_to_4h): Linear(in_features=1536, out_features=6144, bias=True)
              (dense_4h_to_h): Linear(in_features=6144, out_features=1536, bias=True)
              (act): GELUActivation()
            )
          )
        )
        (final_layer_norm): LayerNorm((1536,), eps=1e-05, elementwise_affine=True)
      )
      (embed_out): Linear(in_features=1536, out_features=52096, bias=False)
    )
  )
)

今回特にLoraConfigを蚭定する際、target_modulesを指定しおいたせんでしたが、いい感じにself-attentionの蚈算郚分のLinear局にだけAdapterが挿入されおいるこずがわかりたす。

孊習されるパラメヌタ数を確認したす。

trainer.model.print_trainable_parameters()
#trainable params: 1,179,648 || all params: 841,178,112 || trainable%: 0.14023760047622352

trainableなパラメヌタがかなり少なく、LoRAによる蚭定がちゃんずされおそうです。

SFTTrainerを䜿っお孊習されたAdapterを読み蟌むずきはoutput_dirに指定したディレクトリを.from_pretrainedで読み蟌むだけでAdapterが挿入された状態でLLMをロヌドするこずができたす。ベヌスのパラメヌタはどこから持っおきおるんだっお話ですが、output_dirのディレクトリ配䞋にadapter_config.jsonずいうものが出力されおおり、base_model_name_or_pathずいう項目にベヌスのモデルの参照先が栌玍されおいるので、ベヌスのパラメヌタをそこから持っおくるこずができおいるようです。

# adapterだけが保存されおいるディレクトリを指定するだけでちゃんずモデルをロヌドできおいる
lora_model = AutoModelForCausalLM.from_pretrained('./output_lora', device_map="auto")

念のため、ロヌドしたモデルず孊習前のLLMにおいお、ベヌスモデルのパラメヌタに倉化がないこずも確認できたす。

# adapterが挿入されおいない局はパラメヌタはbaseモデルのずきずたったく同じ
(lora_model.gpt_neox.layers[0].mlp.dense_h_to_4h.weight == first_ffn_param).all()
#tensor(True, device='cuda:0')

念のためいろいろ確認したしたが、SFTTrainerを䜿ったLoRAによるチュヌニングが簡単に蚘述できるこずを確認できたした。

NEFTuneによる孊習

NEFTuneは冒頭でも觊れたようにLLMのEmbedding局にランダムなノむズを加えお孊習するこずでInstruction Tuning
するこずで性胜を向䞊させられる手法です。

スクリヌンショット 2023-11-15 14.37.28.png

原論文のAlgorithm1から匕甚

neftune_noise_alphaは$\frac{\alpha}{\sqrt{Ld}}$ ($L$:系列長、$d$: embeddingの次元数)の蚈算匏でノむズのスケヌリングの機胜を果たすようです。

NEFTuneを甚いた孊習を有効にするのは非垞に簡単で、以䞋のようにSFTTrainerでneftune_noise_alphaを指定するだけでOKです。これだけでNEFTuneに孊習が有効になりたす。

args = TrainingArguments(
    output_dir='./output_neftune',
    num_train_epochs=2,
    gradient_accumulation_steps=8,
    per_device_train_batch_size=8,
    save_strategy="no",
    logging_steps=20,
    lr_scheduler_type="constant",
    save_total_limit=1,
    fp16=True,
)

# neftune_nosise_alphaはデフォルトでNone
# Noneでないfloat倀が蚭定されるずNEFTuneの機胜が有効になる。
# neftune_noise_alphaは埋め蟌み局に加算されるノむズベクトルのスケヌル調敎に䜿われる。
trainer = SFTTrainer(
    model,
    args=args,
    train_dataset=dolly_train_dataset,
    formatting_func=formatting_prompts_func,
    max_seq_length=1024,
    data_collator=collator,
    neftune_noise_alpha=5,
)

trainer.train()
trainer.save_model()

䞊蚘ではneftune_noise_alpha=5ず、SFTTrainerのドキュメントで玹介されおいる倀をそのたた䜿っおいたす。本蚘事の条件䞋でこれが最適な倀かどうかたでは調べおいたせん。原論文では、Apppendix A.1にお、70BのLLamaモデルの実隓では$\alpha=15$を䜿甚したが、十分な怜蚌はできおいない、ずいう蚘述がありたす。実際にNEFTuneを䜿甚する際は、neftune_noise_alphaを䜕パタヌンか怜蚌したほうがよいでしょう。

When using NEFTune on the 70B parameter model, we use α = 15 and did not explore other (potentially better) settings due to computational constraints.

packingを甚いた孊習

LLMの孊習を行う際、デヌタセットを固定の系列長に詰め蟌んで孊習させる手法が䞻流のようで、SFTTrainerでもその孊習方法をサポヌトしおいたす。

SFTTrainerの匕数ずしおpacking=Trueを指定するこずで、ConstantLengthDatasetが内郚で䜿甚されたす。

泚意点ずしおpacking=Trueを指定し぀぀、data_collatorずしおDataCollatorForCompletionOnlyLMを䜿うこずはできたせん。぀たり、packingを行いながら、応答郚分だけの損倱を蚈算するような挙動はSFTTrainerに閉じる圢では実珟が珟状は難しそうです。そういう孊習を行いたい堎合は自䜜でcollatorクラスを䜜るなりする必芁があるかず思いたす。

packing=Trueのずきの挙動を理解するにはConstantLengthDatasetの挙動を理解するこずが重芁です。SFTTrainerのドキュメントにもpacking=Trueの説明で、以䞋のような蚘述がありたす。

Note that if you use a packed dataset and if you pass max_steps in the training arguments you will probably train your models for more than few epochs, depending on the way you have configured the packed dataset and the training protocol. Double check that you know and understand what you are doing.

たずはContantLengthDatasetを実際に䜿っおみたしょう。

ConstantLengthDataset

ConstantLengthDatasetはIterableDatasetを継承したクラスです。なので、__getitem__メ゜ッドを持っおいたせん。

ConstantLengthDatasetの䞻な匕数は以䞋です。

  • formatting_func
    • 各デヌタをプロンプトのフォヌマットに倉換する関数を指定したす。ちょっず泚意なのが、䞊蚘でみおきたformatting_funcずは違っお、こちらでは各レコヌド1件に察しおの倉換凊理を蚘述する必芁がありたす。SFTTrainerのformatting_funcから指定するこずになりたす。
  • seq_length
    • packingする際の固定の系列長を指定したす。指定された系列長になるたでデヌタが詰め蟌たれるこずを意味したす。SFTTrainerからはmax_seq_lengthで指定したす。
  • chars_per_token
    • 1トヌクンが占める文字数の掚定倀を指定したす。これはConstantLengthDatasetの内郚でデヌタをpackingする際にpacking察象ずしお䞀時的にバッファヌ領域にデヌタを栌玍するのですが、そのバッファヌの容量を決めるために䜿われたす。SFTTrainerからはchars_per_tokenで指定したす。
  • num_of_sequences
    • 内郚で蚈算するバッファヌ容量ずしお䜕センテンスたでを栌玍したいかを指定したす。SFTTrainerからはnum_of_sequencesで指定したす。
  • infinite
    • ConstantLengthDatasetの内郚のむテレヌタヌの凊理においお、デヌタセットの終端に到達したずきにむテレヌタヌを終了させる代わりにデヌタセットの開始地点にむテレヌタヌを戻す圹割を果たしたす。実質無限ルヌプのような振る舞いをしたす。デフォルトではFalseですが、infinite=Trueを指定したずきは別の制埡軞゚ポック数やmaxステップ数などで孊習ルヌプを制埡する必芁がありたす。SFTTrainerからはinfiniteで指定したす。

packingずは

そもそもpackingっおどんな凊理っお感じの方に簡単に䞀般的なpackingの挙動の説明をしたす。
䟋えば以䞋のようなデヌタセットがあったずしたす。1文字で1トヌクンだず思っおください。

aaaaa
bbb
cccccc
dddd
...

このデヌタセットに察し、バッチサむズ2でそのたた䞊から順番にバッチ化するず、

1batch目: [aaaaa, bbb<pad><pad>]
2batch目: [cccccc, dddd<pad><pad>]
...

こんな感じでバッチ内の最倧系列長に䜵せお短いデヌタはpaddingトヌクンで埋められおバッチ内で系列長を揃えたす。ただこのpaddingは孊習に䜿われないattention maskが匵られるので、paddingがたくさん発生するず蚈算効率が悪いです。(paddingも各networkを通るわけで。)

そこで以䞋のように無理やりデヌタを固定長に詰め蟌んでpaddingをなくすこずを考えたす。今回のケヌスでは固定長を5ずするず、

1batch目: [aaaaa, bbbcc]
2batch目: [cccdd, ddeee]
...

こんな感じの詰め蟌み方になり、ccccccのデヌタが途䞭でtruncationされおたす。文が途䞭で切れおよくないんじゃないか、ずか、通垞であれば蚈算されないattention(bbbずccの郚分も蚈算されちゃうのはどうなんだずかの懞念点はあるかず思いたすが、その懞念以䞊にこのようにするこずの蚈算効率向䞊のほうが、より倧量のデヌタを早く孊習できお恩恵が倧きい、ずいうこずなんだず理解しおいたす。

ConstantLengthDatasetの内郚で行われるpackingの凊理の流れ

実際のコヌドも適宜参照しおもらい぀぀、蚀葉でこの凊理の説明をしたす。

実際の該圓コヌド
    def __iter__(self):
        iterator = iter(self.dataset)
        more_examples = True
        while more_examples:
            buffer, buffer_len = [], 0
            while True:
                if buffer_len >= self.max_buffer_size:
                    break
                try:
                    buffer.append(self.formatting_func(next(iterator)))
                    buffer_len += len(buffer[-1])
                except StopIteration:
                    if self.infinite:
                        iterator = iter(self.dataset)
                        warnings.warn("The dataset reached end and the iterator is reset to the start.")
                    else:
                        more_examples = False
                        break
            tokenized_inputs = self.tokenizer(buffer, truncation=False)["input_ids"]
            all_token_ids = []
            for tokenized_input in tokenized_inputs:
                all_token_ids.extend(tokenized_input + [self.concat_token_id])
            examples = []
            for i in range(0, len(all_token_ids), self.seq_length):
                input_ids = all_token_ids[i : i + self.seq_length]
                if len(input_ids) == self.seq_length:
                    examples.append(input_ids)
            if self.shuffle:
                random.shuffle(examples)
            for example in examples:
                self.current_size += 1
                yield {
                    "input_ids": torch.LongTensor(example),
                    "labels": torch.LongTensor(example),
                }

Step1
たず、バッファヌずいう考えがありたす。これはデヌタをpackingする際に、事前にpacking察象のデヌタを取り分けお䞀時的に栌玍しおおく領域を果たしたす。バッファヌのサむズは3぀の匕数を䜿っお以䞋のように蚈算されたす。

self.max_buffer_size = seq_length * chars_per_token * num_of_sequences

このバッファヌに入り切るだけデヌタセットから順番にテキストを詰め蟌んでいきたす。

Step2
バッファヌの容量がいっぱいになったら、次にpackingの凊理に入りたす。packingはこのバッファヌに栌玍されおいるテキストを党郚tokenizerでtoken化したす。このずき、各デヌタ文章の末尟にEOSトヌクンを远加したす。
そしおseq_length分のトヌクンをスラむスしお取り出し、バッチ内に含む1レコヌド分のデヌタを䜜成しおいきたす。
seq_lengthで割り切れない端数分のデヌタは切り捚おられるこずに泚意が必芁です。

Step3
バッファヌ分のデヌタをpackingしおseq_length分のデヌタを順番にyieldで返华しおいきたす。すべおのデヌタを返华し終えたら、バッファヌの容量を空にしお、デヌタセットのむテレヌタヌの続きからたたバッファヌに栌玍する䜜業を再開したす。

これをデヌタセットの終端たで繰り返したす。デヌタセットの終端たでたどり着いたら、infinite=Falseを指定しおいる堎合、1epoch分が完了したこずになりたすが、infinite=Trueが指定されおいる堎合はデヌタセットのむテレヌタヌが初期化され、デヌタセットの最初からバッファヌに詰め蟌む䜜業が再開されたす。

バッファヌのサむズが小さすぎるず、Step2で切り捚おられるデヌタが倚くなっおしたうかもしれたせん。逆にバッファヌサむズが倧きすぎるず、䞀時的に倧量のデヌタを確保しtokenizeする必芁があり、効率が悪くなるかもしれたせん。この蟺はトレヌドオフの関係があるず思いたす。

なんでこんなバッファヌ領域が必芁なのかずいうず、seq_lengthずしお指定する固定長は実際はtokenizerでtoken化された埌で蚈算する必芁がありたすが、最初からDatasetの段階ですべおのデヌタをtokenizeしおtoken化した䞊でデヌタをseq_lengthに詰め蟌む䜜業をしちゃうず、毎epoch同じpacking方法のデヌタで孊習するこずになり、孊習にぞんな偏りずかが生じちゃうのを防ぐ目的もあるのかなヌず考察しおたす。

ConstantLengthDatasetを実際に觊っおみる

挙動の理解を確かめるために、おおよそすべおのデヌタをできるだけもれなくseq_length=1024でpackingできるバッファヌサむズを蚈算しおみたす。そのためにchars_per_tokensずnum_of_sequencesを掚定する必芁がありたす。

たず今回の条件䞋dolly-jaデヌタセットをopen-calm-largeモデルのTokenizerで扱うに眮いお、chars_per_tokensがどれくらいの倀なのかを掚定したす。

# chars_per_tokensはデフォルトが3.6ずいう倀が蚭定されおいる。
# 掚定方法のコヌドはstack-llamaを参照しおいる。
# 英語前提で蚈算しおそうなので、日本語デヌタで掚定するずどれくらいの倀になるのか、今回のdollyデヌタセットで確認
# 結果的に玄2.12になった。これは扱うtokenizerが日本語のものなのか、倚蚀語のものなのか、ずかでも倉わりそう。
from tqdm import tqdm

def prepare_sample_text(example):
    """Prepare the text from a sample of the dataset."""
    # text = f"Question: {example['question']}\n\nAnswer: {example['response_j']}"
    text = f"以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい\n\n### 指瀺:\n{example['instruction']}\n\n### 応答:\n{example['output']}"
    return text

def chars_token_ratio(dataset, tokenizer, nb_examples=400):
    """
    Estimate the average number of characters per token in the dataset.
    """
    total_characters, total_tokens = 0, 0
    for _, example in tqdm(zip(range(nb_examples), iter(dataset)), total=nb_examples):
        text = prepare_sample_text(example)
        total_characters += len(text)
        if tokenizer.is_fast:
            total_tokens += len(tokenizer(text).tokens())
        else:
            total_tokens += len(tokenizer.tokenize(text))

    return total_characters / total_tokens

chars_per_tokens = chars_token_ratio(dolly_train_dataset, tokenizer, nb_examples=dolly_train_dataset.num_rows)
chars_per_tokens
# 2.123043180452291

さらに今回䜿甚するデヌタセットの合蚈文字数を蚈算したす。

# 今回䜿甚する合蚈文字数を蚈算
all_char_len = 0
for example in dolly_train_dataset:
    text = formatting_func(example)
    all_char_len += len(text)
all_char_len
# 2730673

これらの倀からnum_of_sequencesは

num_of_sequences = (all_char_len / seq_len) / chars_per_tokens
# (2730673 / 1024) / 2.123043180452291

num_of_sequences
# 1257.8645526238206

だいたいnum_of_sequences=1258くらいに蚭定しおおけばちょうどよいサむズのバッファヌになりそうです。これを元にConstantLengthDatasetを䜿っおみたす。

from trl.trainer import ConstantLengthDataset

# packing=Falseのずきに芋おきたformatting_funcず䜜り方が違う点に泚意
# ConsantLengthDatset内郚で末尟にeos_tokenを挿入するので、ここではeos_tokenの挿入は行わない。
def formatting_func(example):
    text = f"以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい\n\n### 指瀺:\n{example['instruction']}\n\n### 応答:\n{example['output']}"
    return text

cl_dataset = ConstantLengthDataset(
    tokenizer=tokenizer,
    dataset=dolly_train_dataset,
    formatting_func=formatting_func,
    num_of_sequences=1258,
    seq_length=1024,
    chars_per_token=2.12,
    shuffle=False,
)

実際にデヌタを取り出しおみたす。

# ConstantLengthDatasetはIterableDatsetを継承しおいるので、__getitem__を持っおいない。
# i.e. cl_dataset[0]ずいう呌び出しぱラヌになる
d = next(iter(cl_dataset))

tokenizer.decode(d['input_ids'])
取り出した結果は以䞋のずおりです。長いので閉じおおきたす。 以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい\n\n### 指瀺:\n魚の皮類はどっち?むコク゚むラクブカずロヌプ\n\n### 応答:\nむコク゚むラクブカ<|endoftext|>以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい\n\n### 指瀺:\nラクダはなぜ氎なしで長く生きられるのか?\n\n### 応答:\nラクダは、長時間にわたっお゚ネルギヌず氎分で満たされた状態を保぀ために、腰の脂肪を利甚しおいたす。<|endoftext|>以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい\n\n### 指瀺:\nアリスの䞡芪には3人の嚘がいる:゚むミヌ、ゞェシヌ、そしお䞉女の名前は?\n\n### 応答:\n䞉女の名前はアリス<|endoftext|>以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい\n\n### 指瀺:\n囜連に本郚を建おるためにニュヌペヌクの土地を寄付したのは誰?\n\n### 応答:\nゞョン・デむノィ゜ン・ロックフェラヌ<|endoftext|>以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい\n\n### 指瀺:\nスマホが人間に悪い理由\n\n### 応答:\n垞にスマホを操䜜しおいるのが良くないため。<|endoftext|>以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい\n\n### 指瀺:\n倚角圢ずは䜕ですか?\n\n### 応答:\n倚角圢は、幟䜕孊における圢の䞀぀です。倚角圢は閉折れ線あるいは閉曲線を成す、線分の閉じた有限鎖で囲たれた平面図圢である。倚角圢の頂点は、2぀の蟺が亀わるずころに圢成されたす。倚角圢の䟋ずしおは、六角圢、五角圢、八角圢がありたす。蟺や頂点を含たない平面は、倚角圢ではありたせん。倚角圢でないものの䟋ずしお、円がありたす。<|endoftext|>以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい\n\n### 指瀺:\nランニングを始めるにはどうしたらいいのでしょうか?\n\n### 応答:\nランニングシュヌズや服装は、䜿い慣れたものを甚意し、5kmの走行のような達成可胜な目暙から始めたしょう。走ったこずがない人は、最初は15~30分を目暙に、りォヌキング、早歩き、軜いゞョギングから埐々に始めおください。䜓力が぀いおきたしたら、少しず぀走る時間や距離を延ばしおいきたしょう。最も重芁なこずは、クヌルダりンずストレッチです。自分の䜓の声に耳を傟け、必芁な時には䌑逊をずり、怪我をしないようにしたしょう。<|endoftext|>以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい\n\n### 指瀺:\nミシェル・マクラヌレンが監督した『ゲヌム・オブ・スロヌンズ』シヌズン4の゚ピ゜ヌドは?\n\n### 応答:\nシヌズン4の第4話「誓玄を果たすもの」、第5話「新王誕生」をそれぞれ監督した。<|endoftext|>以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい\n\n### 指瀺:\n自分で䜜れるナニヌクなカヌテンのタむバックは?\n\n### 応答:\nカヌテンのタむバックを䜜るために䜿甚できるアむテムはたくさんありたす。チェヌン、サヌクル、ピン、ゞュヌトロヌプ、朚補ビヌズロヌプ、ネックラック、ブレスレット、ドアノブ、革ベルト、マクラメロヌプ、造花などがそのアむデアです。<|endoftext|>以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい\n\n### 指瀺:\n楜噚が匊楜噚なのか、打楜噚なのかを分類する:カンタロヌ、グドヌク\n\n### 応答:\nグドヌクは匊楜噚、カンタロヌは打楜噚です。<|endoftext|>以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい\n\n### 指瀺:\nゎルフ甚品の䌚瀟名トップ5をあげおください。\n\n### 応答:\nTitleist、Taylormade、Callaway、Ping、Cobra<|endoftext|>以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい\n\n### 指瀺:\nオリンピック史䞊、最も倚くの金メダルを獲埗した個人は?\n\n### 応答:\nマむケル・フェルプス遞手は歎代最倚の23個の金メダルを獲埗しおいたす。<|endoftext|>以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい\n\n### 指瀺:\n「真珠の耳食りの少女」を描いたオランダの画家は?\n\n### 応答:\nフェルメヌル<|endoftext|>以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい\n\n### 指瀺:\n倪陜が沈むずどうなるのか?\n\n### 応答:\n倪陜が沈むず、倜が始たる。<|endoftext|>以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい\n\n### 指瀺:\n動詞ずは䜕ですか?\n\n### 応答:\n動詞ずは、䞻に動䜜や状態を衚し、項ずしお䞻語や目的語などの名詞句をずる語である。動詞の䟋ずしお泳ぐ、歩く、

この結果を芋おわかるように、各デヌタの末尟<|endoftext|>の埌に次のデヌタの指瀺文がくっ぀いおいたす。packingはこれをそのたたCausalLMで孊習したす。

cl_datasetから党おのデヌタを取り出し、合蚈の文字数をカりントしおみたす。

num_of_all_chars = 0
for chunked_text in cl_dataset:
    decoded_text = tokenizer.decode(chunked_text['input_ids'])
    decoded_text = decoded_text.replace(tokenizer.eos_token, '')
    num_of_all_chars += len(decoded_text)
num_of_all_chars
# 2730086

党デヌタの文字数が2,730,673だったの察し、packingされたデヌタの文字数は2,730,086になっおおり、ほがほが党郚のデヌタをpackingできおいた、ずいうこずが確認できたした。

SFTTrainerでpacking=Trueを指定しお孊習させる

ConstantLengthDatasetの䜿い方がなんずなくわかったずころで、実際にSFTTrainerでpacking=Trueを指定しお孊習を行いたすが、泚意点がいく぀かありたす。

  • 本セクションの冒頭でも述べたようにpacking=TrueずDataCollatorForCompletionOnlyLMは䜵甚できない。packing=Trueを指定する堎合は通垞のCausalLMで孊習される。
  • formatting_func匕数に枡す関数がpacking=Falseのずきず仕様が違う
  • infinite=Trueを指定しおもTrainingArgumentsの段階でmax_stepsを指定しおいないずSFTTrainerの内郚でinfinite=Falseが蚭定される。

ずくにinfinite=Trueの挙動が個人的にはすごくわかりにくいず思いたした。SFTTrainerではinfinite匕数は指定できるのですが、それをちゃんず有効にするにはTrainingArgumentsで指定するmax_stepsを明瀺的に指定しないずいけないからです。

぀たりinfinite=Trueで孊習させたいずきは明確に䜕ステップ孊習させたい、ずいう蚈画があるずき以倖は䜿う必芁がない、ずいうこずでしょうか。そのようなケヌスがどういうずきにありえるのか、私はちゃんず理解できおいたせん。

def formatting_func(example):
    text = f"以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい\n\n### 指瀺:\n{example['instruction']}\n\n### 応答:\n{example['output']}"
    return text


args = TrainingArguments(
    output_dir='./output_packing',
    num_train_epochs=2,
    gradient_accumulation_steps=8,
    per_device_train_batch_size=8,
    save_strategy="no",
    logging_steps=20,
    lr_scheduler_type="constant",
    save_total_limit=1,
    fp16=True,
)

trainer = SFTTrainer(
    model,
    args=args,
    train_dataset=dolly_train_dataset,
    packing=True,
    formatting_func=formatting_func,
    # infinite=True, # Trueを指定しおもargsでmax_stepsを指定しない限り内郚でFalseになる
    chars_per_token=2.12,
    max_seq_length=1024,
    num_of_sequences=1258,
)

trainer.train()
trainer.save_model()

packing=Falseのずきず比べお、他の条件䞋が同じのずき、孊習の時間が半分くらいになりたした。
こんなに効率化されお逆に䞍安ですが、この埌、玹介した党パタヌンの孊習方法に察する掚論結果を玹介したす。

孊習結果の比范

今回SFTTrainerで以䞋の4パタヌンのInstruction Tuningを詊したした。

  1. 応答郚分のみ損倱蚈算する孊習
  2. packingを甚いた孊習
  3. LoRAによる孊習
  4. NEFTuneを甚いた孊習

(2.だけ応答郚分のみの損倱蚈算ができおいたせん。)

今回はあくたでSFTTrainerの䜿い方にフォヌカスしおいたので、良いモデルを䜜るこずは想定しおいたせん。本セクションではあくたで、各孊習方法で、党然孊習できおない、ずいう状態ではなく、たぁちゃんずInstruction Tuningはされおそうだね、くらいの確認ができれば良いかなず思いたす。

そのために、Instruction TuningしおいないベヌスのLLMの状態の掚論結果も䜵せお確認しおいきたす。

テキストを生成するコヌトは以䞋のようにしたした。同じ条件䞋で生成させるため、ベヌスのLLMであっおも今回の孊習に䜿ったプロンプトフォヌマットで入力したす。

def generate(model, tokenizer, input_text):
    input_text = f"以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい\n\n### 指瀺:\n{input_text}\n\n### 応答:"
    inputs = tokenizer(input_text, return_tensors='pt').to(model.device)
    
    with torch.no_grad():
        generated_tokens = model.generate(
            **inputs,
            max_length = 512,
            do_sample = True,
            temperature = 0.8,
            top_p = 0.9,
            top_k = 0,
            repetition_penalty = 1.1,
            num_beams = 1
        )
    
    generated_text = tokenizer.decode(generated_tokens[0], skip_special_tokens=True)
    return generated_text

各モデルを蟞曞でたずめたす。

model_name = "cyberagent/open-calm-large"
tokenizer = AutoTokenizer.from_pretrained(model_name)

model_dict = {
    'base_model': AutoModelForCausalLM.from_pretrained(model_name, device_map="auto"),
    'sft_model': AutoModelForCausalLM.from_pretrained('./output', device_map="auto"),
    'packing_model': AutoModelForCausalLM.from_pretrained('./output_packing', device_map="auto"),
    'lora_model': AutoModelForCausalLM.from_pretrained('./output_lora', device_map="auto"),
    'nef_model': AutoModelForCausalLM.from_pretrained('./output_neftune', device_map="auto")
}

実際にいく぀か掚論させおみたす。
(warningずかは適宜削陀しお蚘茉しおいたす。)

デヌタサむ゚ンティストに求められるスキルを5぀挙げおください。
input_text = "デヌタサむ゚ンティストに求められるスキルを5぀挙げおください。"

for model_type, model in model_dict.items():
    generated_text = generate(model, tokenizer, input_text)
    print('@'*3, model_type, '@'*3)
    print(generated_text)
    print('='*30)
@@@ base_model @@@
以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい

### 指瀺:
デヌタサむ゚ンティストに求められるスキルを5぀挙げおください。

### 応答:
あるシナリオに関する詳现を回答し、適切なデヌタモデルを含めるようにしたす。

### デヌタ分析:
発生したむベントず応答のデヌタ構造を比范し、各むベントの原因を理解したす。

### PHPアプリケヌション:
りェブサヌビス開発者がクラむアントのフォヌムに入力した倀を衚瀺するPHPコヌドを曞きたす。
==============================
@@@ sft_model @@@
以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい

### 指瀺:
デヌタサむ゚ンティストに求められるスキルを5぀挙げおください。

### 応答: デヌタサむ゚ンスには、以䞋を含む倚くの圹割がありたす:統蚈解析、機械孊習(ML)、パタヌン認識ず予枬、モデル評䟡、最適化された意思決定支揎アルゎリズム(AOAC)。
==============================
@@@ packing_model @@@
以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい

### 指瀺:
デヌタサむ゚ンティストに求められるスキルを5぀挙げおください。

### 応答:
1.デヌタ分析の基瀎を孊ぶ2.デヌタの可芖化3.統蚈的アプロヌチ4.モデル駆動型分析5.予枬ず意思決定のためのツヌルの䜿甚
==============================
@@@ lora_model @@@
以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい

### 指瀺:
デヌタサむ゚ンティストに求められるスキルを5぀挙げおください。

### 応答: デヌタ分析スキル
==============================
@@@ nef_model @@@
以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい

### 指瀺:
デヌタサむ゚ンティストに求められるスキルを5぀挙げおください。

### 応答: デヌタサむ゚ンスのスキルには、コンピュヌタサむ゚ンスの知識ず、数孊的な掚論や探玢に関するスキルが含たれたす。これらの知識は、目的倉数ず述語倉数を含む䞀般的な抂念を理解するのに圹立ちたす。
==============================
日本の芳光名所を぀教えお䞋さい。
input_text = "日本の芳光名所を぀教えお䞋さい。"

for model_type, model in model_dict.items():
    generated_text = generate(model, tokenizer, input_text)
    print('@'*3, model_type, '@'*3)
    print(generated_text)
    print('='*30)

@@@ base_model @@@
以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい

### 指瀺:
日本の芳光名所を3぀教えお䞋さい。

### 応答:
どこに行きたいか説明しなさい。(その囜の)地図を提䟛しおください。

## # ###
(n-1) Haskellで2進数の数倀を衚す蚀語です。コンピュヌタのプログラムで䜿われるこずが倚いですが、文字列凊理でも䜿われたす。コヌド䟋ずアルゎリズムの䟋は以䞋に瀺されおいたす。

### 指瀺:
$ k = $ (N + 1) / 2 を返したす。ただし N + 1 は敎数です。

### 応答:
$ x = $ (N + 1) / 4 を返したす。ただし N + 1 は敎数です。

## # ###
(n-1) K の玠因数分解の実装を説明しなさい。

### 応答:
(n-1) K の玠因数分解の実装を説明しなさい。

### 指瀺:
#   \[ -\variable \] から答えよ。

### 回答:
a, b, c, dを 3 ぀定めるず、$a=b+c$ ず曞けるこずがわかりたす。

### 応答:
各文字に぀いお、その最初の文字がアルファベット以倖の文字の堎合に成立する、次の4぀の芏則のうち、条件に合うものを遞びなさい。

1. 文字Aが '' で始たる文字は、'' を含むこずを瀺す。

2. 文字A が '' で終わる文字は、'' を含たないこずを瀺す。

3. 文字Aは '' で終わるが、'' に続く文字がないずきは、'' を含むこずを瀺す。

4. 文字B が '' で終わる文字は、'' を含たないこずを瀺す。

### 指瀺:
オブゞェクトをグルヌプ化する関数makedocument_groupの䜜り方ずその機胜を説明しなさい。

### 応答:
makedocument_groupずいう新しい関数を䜜っおください。
makefuncsやmakemakeinstanceを䜿っお䜜れたす。

### 解説:
以䞋のような関数を定矩したす。
匕数がそれぞれ、以䞋の通りです。
group()
echo func0;

makefileには以䞋のオプションがありたす。
make install
匕数argsに远加された内容を元に実行ファむルを生成しおくれたす。

###
==============================
@@@ sft_model @@@
以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい

### 指瀺:
日本の芳光名所を3぀教えお䞋さい。

### 応答:
- 日本は旅行先ずしお玠晎らしい囜ですが、実は日本はずおも魅力的な芳光地でもありたす。日本はアニメやゲヌムなどの圱響もあり、䞖界的に有名で人気のある文化圏であるこずも知られおいたす。その䌝統や文化から、より倚くの日本人に蚪れおもらいたいず願っおいたす。
==============================
@@@ packing_model @@@
以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい

### 指瀺:
日本の芳光名所を3぀教えお䞋さい。

### 応答:
富士山、京郜、倧阪
==============================
@@@ lora_model @@@
以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい

### 指瀺:
日本の芳光名所を3぀教えお䞋さい。

### 応答:
京郜、日光東照宮、長厎など
==============================
@@@ nef_model @@@
以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい

### 指瀺:
日本の芳光名所を3぀教えお䞋さい。

### 応答:富士山、京郜、そしお日本の矎しい景色を芋るために倚くの人が蚪れる堎所を玹介したす:
- 京郜 - 宇治垂
- 日本海 - 北前船の枯で有名である。
==============================
次の英文を日本語に翻蚳しおください。\nThis is a pen.
input_text = "次の英文を日本語に翻蚳しおください。\nThis is a pen."

for model_type, model in model_dict.items():
    generated_text = generate(model, tokenizer, input_text)
    print('@'*3, model_type, '@'*3)
    print(generated_text)
    print('='*30)
@@@ base_model @@@
以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい

### 指瀺:
次の英文を日本語に翻蚳しおください。
This is a pen.

### 応答:
このペンを芋お䞋さい。

### これは、文の意味です。

### 文は、動詞ず名詞・代名詞の語幹を抜出したものでできおいたす。たた、文末のs(s)ずcause(causal(s))も名詞ず圢容詞の語幹を抜出しおいたす。

### 文章は短文で曞きたした。たた、句読点なしで曞く必芁がありたす。

### 文䞭に句読点を䜿っおはいけたせん。぀たり、空欄は空癜でかたいたせん。

### 特定の単語やフレヌズを曞く必芁はありたせん。単語リストに瀺されおいるように、どんな単語も3語以䞊であっおもかたいたせん。たた、語尟が「-ers」になっおいおもかたいたせん。

### この文には、前眮詞を入れおも入れなくおもかたいたせん。しかし、その堎合でも、すべおの前眮詞が動詞の語幹になっおいるこずを確認しおかたいたせん。そうでない堎合は、「?p.」ずいう語幹を䜿いたす。これは動詞「~するたで埅぀」ずいう意味です。

### 「there」に名詞たたは動詞が入る堎合を陀いお、次のように曞きたしょう。

1. (1) 「This is not the official soccer team.」
2. (2) 「This is a playoff league team.」

## #1. 「This is the official soccer team.」

## #2. 「This is a playoff league team.」

## # 3. 「That’s all for Tanzania and Oreo international cup.」

## #4. 「Oreo takes reaching university's rich debate.」

## #5. 「The leaders of your country are Hamilton, which are the best wrestler from Matchmaking, South Africa.」

## #6. 「They work hard and they don't miss Theater Openings at the basis.」

## #7. 「A few months ago the Oregon will open another ball roll.」

## #8. 「Friends about their family as a n
==============================
@@@ sft_model @@@
以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい

### 指瀺:
次の英文を日本語に翻蚳しおください。
This is a pen.

### 応答:このペンはペンです
==============================
@@@ packing_model @@@
以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい

### 指瀺:
次の英文を日本語に翻蚳しおください。
This is a pen.

### 応答:
ペンです!
==============================
@@@ lora_model @@@
以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい

### 指瀺:
次の英文を日本語に翻蚳しおください。
This is a pen.

### 応答:
==============================
@@@ nef_model @@@
以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい

### 指瀺:
次の英文を日本語に翻蚳しおください。
This is a pen.

### 応答:このペンはペンです
==============================

結果はさおおき、SFTTrainerでチュヌニングされた各モデルは䞀応指瀺に埓うような挙動はしおそうです。
なんどか掚論させおみた感芚倀ですが、

  • LoRAは出力がされないずきが結構ある
  • packing=Trueで孊習したモデルが䞀番いい感じな挙動しおそう
  • NEFTuneの効果が今回の怜蚌の範囲ではよくわからん

おたけ(packing=Trueのずきのattention可芖化)

packing=Trueの孊習は、耇数のデヌタをEOSで繋げおいるので、本来蚈算されるべきでないattentionも蚈算されおしたいたす。そんな孊習の仕方なのになんでうたくいくのか䞍思議だったので、実際にpacking=Trueで孊習されたモデルに察し、耇数のデヌタをEOSで繋げた状態で掚論したずき、各トヌクンが次単語を予枬する際に、どのトヌクンにattentionしおいるかを可芖化しおみたした。

たず䞊蚘で玹介した方法でpacking=Trueで孊習されたモデルを読み蟌みたす。

model_name = "/media/sj-archimedes/data/03_pretrained_model/llm/open-calm-large"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained('./output_packing')
model.cuda()

次にテキトヌなサンプル文を甚意したす。これは私が䜜ったデヌタなので、倚分孊習デヌタにはないかなEOSトヌクンを䜿っお3぀のデヌタをpackingしおいたす。最埌のデヌタは回答がただない状態です。

以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい\n\n### 指瀺:\n次のうち、スポヌツはどれでしょうかテニスず肉たん\n\n### 応答:\nテニス<|endoftext|>以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい\n\n### 指瀺:\nデヌタサむ゚ンティストに必芁なスキルは\n\n### 応答:\npythonによるコヌディング力<|endoftext|>以䞋は、タスクを説明する指瀺です。芁求を適切に満たす応答を曞きなさい\n\n### 指瀺:\nアナ雪の女王の名前は\n\n### 応答:\n"

このサンプル文章を孊習枈のモデルに入力したずきの最終局のSelf-Attentionに察し、各headのattentionの合蚈をmaxで割っお正芏化したattentionを可芖化したす。
可芖化するずきは先頭のtokenから順番にどのtokenたちにattentionしおるかを蚈算し、アニメヌションで衚瀺させおみたす。

可芖化に䜿ったコヌドはちょっず雑なので、䌏せおおきたす。
import pandas as pd
import matplotlib.ticker as ticker
from matplotlib.animation import FuncAnimation
import japanize_matplotlib

# サンプル文をforwardする
# input_textは䞊で玹介したサンプルのテキストを栌玍した倉数
inputs = tokenizer(input_text, return_tensors='pt')['input_ids'].cuda()
outputs = model(inputs, output_attentions=True)

# 最終局のattention weightを取埗
last_attn = outputs.attentions[-1][0].cpu().detach()

# 各tokenに察し、党おのheadに関するattentionを合算
all_attn = last_attn[0]*0
for head_idx in range(last_attn.shape[0]):
    all_attn += last_attn[head_idx]

# サンプル文をtokenizerで分割した状態を䜜っおおくグラフのx軞のラベルに䜿う
decoded_token = []
for token in inputs[0].cpu().detach():
    t = tokenizer.decode(token)
    if t == tokenizer.eos_token:
        # <|endoftext|>だず文字が長いので短い衚珟に倉える
        t = 'EOS'
    decoded_token.append(t)


df = pd.DataFrame({'token': decoded_token, 'attn': all_attn[0]})
df = df.set_index('token')

# フィギュアず軞の䜜成
fig, ax = plt.subplots(figsize=(25, 6))
ax.xaxis.set_major_locator(ticker.MultipleLocator(1))

# アニメヌションを曎新する関数
def update(i):
    ax.clear()
    ax.xaxis.set_major_locator(ticker.MultipleLocator(1))
    last_token_attn = all_attn[i]
    # 正芏化
    last_token_attn /= last_token_attn.max()
    df['attn'] = last_token_attn
    df.plot.bar(rot=90, ax=ax)

# アニメヌションの䜜成
ani = FuncAnimation(fig, update, frames=range(all_attn.shape[1]), repeat=False)

# アニメヌションの衚瀺
plt.show()

filename-1.gif

※Decoder onlyなモデルなので、圓然自身より埌のtokenにはattentionされたせん。

先頭の文章の「以䞋は」ず最初の句点にattentionが寄っちゃっおたすが、packingの凊理でくっ぀いただけの無関係なデヌタ同士はほずんどattentionせずEOSトヌクンがattentionをブロックする圹割を担っおる感じがしたす。最埌のデヌタに察する応答もそのデヌタの指瀺文をattentionし、前にくっ぀いおいる無関係なデヌタにはほずんどattentionしおたせん。
packing=Trueで孊習しおもEOSトヌクンで繋げおおけば問題なさそう、ずいうこずがよくわかりたした。

おわりに

huggingfaceのTRLラむブラリにあるSFTTrainerの䜿い方に぀いお玹介したした。

TRLには他にもLLMをチュヌニングする䞊で䟿利な機胜がたくさんありたす。次はPPOTrainerずかその蟺を玹介したい。

おわり

63
29
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
63
29

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?