自分を模倣するLLMを作成する方法

## はじめに

最近はChatGPTなどの大規模言語モデル(LLM)の発展がめざましく、自分で文章を考えなくても勝手にAIが書いてくれる世の中になりました

しかし、ChatGPTなどの既存のLLMに意見とかを述べさせると、中立的で一般的な意見しか述べず、明らかにAIが書いたんだろうな～という文章しか作れません。

そんなわけで、今回は自分の意見に近い文章を出力してくれるLLMを作成しようという試みを書きたいと思います！！

### 最初に

まずはベースとなるLLMを用意するところから始めます

ChatGPTやBIng AIは高性能なのですが、実際のモデルはダウンロード出来ないので自分好みにカスタマイズできないという欠点があります。

そこでダウンロード可能なChat型LLMであるLlama2をゲットしましょう！！

Llama2はMeta社が開発したオープンモデルでChatGPT3.5にいくつかの指標で勝つなど優秀なモデルです

### Llama2のダウンロード

Llama2は他のモデルに比べると日本語の返答にあまり強くないという欠点があります。そこで今回はLlama2をベースとして日本語の追加学習を行った「ELYZA-japanese-Llama-2-7b」をベースのモデルとしてダウンロードします

```python
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, PreTrainedTokenizer

model_name = "elyza/ELYZA-japanese-Llama-2-7b-instruct"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name, torch_dtype="auto")

tokenizer.save_pretrained("tokenizer")
model.save_pretrained("ELYZA_chat_model")
```

これで自分用のLLMをダウンロードすることが出来ました

### 自分の好みにカスタマイズする

まず自分用のプロンプトと出力例を用意します

以下のフォーマットで自分用のデータをcsvにします。

```python
system_prompt,timestanp,input_num,input_1,output_1,input_2,output_2,input_3,output_3,input_4,output_4,input_5,output_5,input_6,output_6,input_7,output_7,input_8,output_8,input_9,output_9,input_10,output_10
"",2023/12/08 12:00:00,1,"簡潔に自己紹介をして","こんにちは。\n私の名前は**です。趣味は＊＊でよく＊＊しています。","どうしてそれが趣味なのですか？","昔、**が＊＊でそこから＊＊を趣味として楽しむようになりました。",,,,,,,
```

次に訓練用のデータセットを作成します。

```python
#プロンプト
NAME = "****" #あなたの名前
B_INST, E_INST = "
", "
"
B_SYS, E_SYS = "
\n", "\n
\n\n"
DEFAULT_SYSTEM_PROMPT = "あなたは{name}という名前の日本人です。今は{time}です。以下の会話に{name}として答えてください"

dataset = pd.Series(["" for _ in range(len(data))])

#GPUを有効化
if torch.backends.mps.is_available():
    mps_device = torch.device("mps")
    model.to(mps_device)

#データセットを作成
for i in range(len(data)):
    if data.loc[i, "system_prompt"] == "":
        system = B_SYS + DEFAULT_SYSTEM_PROMPT.format(name=NAME, time=data.loc[i, "timestamp"]) + E_SYS
    else:
        system = data.loc[i, "system_prompt"]
    prompt = "{bos_token}{b_inst} {system}{prompt} {e_inst} {output} {eos_token}".format(
        bos_token=tokenizer.bos_token,
        b_inst=B_INST,
        system=system,
        prompt=data.loc[i, "input_1"],
        e_inst=E_INST,
        output=data.loc[i, "output_1"],
        eos_token=tokenizer.eos_token
    )
    for j in range(data.loc[i, "input_num"]-1):
        prompt += "{bos_token}{b_inst} {prompt} {e_inst} {output} {eos_token}".format(
            bos_token=tokenizer.bos_token,
            b_inst=B_INST,
            prompt=data.loc[i, f"input_{j+2}"],
            e_inst=E_INST,
            output=data.loc[i, f"output_{j+2}"]
            eos_token=tokenizer.eos_token
        )

    dataset[i] = prompt
```

データセットの作成方法は以下のサイトを参考にしてください。

https://huggingface.co/blog/llama2

今回は****QLoRA****と呼ばれる手法でモデルをファインチューニング(追加学習)させていきます。

QLoRAの理論を知りたい方は以下のサイトを参考にしてください。

https://arxiv.org/pdf/2305.14314.pdf

訓練コード

```python
compute_dtype = getattr(torch, "float16")

quant_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=compute_dtype,
    bnb_4bit_use_double_quant=False,
)

model = AutoModelForCausalLM.from_pretrained(
    os.path.join(base_dir, model_path),
    quantization_config=quant_config,
    device_map={"": 0}
)
model.config.use_cache = False
model.config.pretraining_tp = 1

tokenizer = AutoTokenizer.from_pretrained(os.path.join(base_dir, tokenizer_path), trust_remote_code=True)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"

peft_params = LoraConfig(
    lora_alpha=16,
    lora_dropout=0.1,
    r=64,
    bias="none",
    task_type="CAUSAL_LM",
)

training_params = TrainingArguments(
    output_dir=os.path.join(base_dir, "trani_output"),
    num_train_epochs=1,
    per_device_train_batch_size=4,
    gradient_accumulation_steps=1,
    optim="paged_adamw_32bit",
    save_steps=25,
    logging_steps=25,
    learning_rate=2e-4,
    weight_decay=0.001,
    fp16=False,
    bf16=False,
    max_grad_norm=0.3,
    max_steps=-1,
    warmup_ratio=0.03,
    group_by_length=True,
    lr_scheduler_type="constant",
    report_to="tensorboard"
)

trainer = SFTTrainer(
    model=model,
    train_dataset=train_data,
    peft_config=peft_params,
    dataset_text_field="text",
    max_seq_length=None,
    tokenizer=tokenizer,
    args=training_params,
    packing=False,
)

trainer.train()

trainer.model.save_pretrained(os.path.join(base_dir, "chat_model"))
trainer.tokenizer.save_pretrained(os.path.join(base_dir, "tokenizer"))
```

参考とした記事は以下の記事です

https://www.datacamp.com/tutorial/fine-tuning-llama-2

# 終わりに

実際には自分がしそうな返答のデータセットを作るために、SNSからデータを引っ張る等の追加の処理が必要となります。

是非皆さんも自分のコピーAIを作ってみてください。