PR

【初心者向け】かんたんなファインチューニングをする方法!学習データの作り方からgoogle colabやローカル環境でする方法まで

LAL

みなさんこんにちは!
今回は、ファインチューニングをして簡単なチャット向けSLMを作って行こうという試みです。
colab上や、自分のパソコンでする方法も説明していきます。
できる限り簡単に説明していくのでお願いします!

スポンサーリンク
スポンサーリンク

用語の確認

今回していく上で、必要な用語を確認していきます。

ファインチューニング:

  • ファインチューニングとは、技術やシステムをより良い感じになるように微調整することを指します。たとえば、AIモデルが特定のタスクやデータに対してより精密に動作するように設定を調整するプロセスです。ファインチューニングを行うことで、AIの性能が向上し、より正確な結果を得ることができます。これは機械学習の分野で特によく使われる手法です。

LLM(大規模言語モデル):

  • 大規模言語モデル(LLM)は、非常に多くのデータを学習して人間の言葉を理解し、生成することができるAIモデルです。(いろんなことが文章でできるAIのことです。)例えば、数百万から数十億の単語を学習し、質問に答えたり、文章を書いたりできます。こうしたモデルは、自然な言語の生成や翻訳、自動要約など、多くの応用分野で利用されています。著名な例としては、ChatGPTなどがあります。

SLM(小規模言語モデル):

  • 小規模言語モデル(SLM)は、LLMと同様に人間の言葉を理解し生成する能力を持ちますが、学習するデータ量が少ないため、規模が小さいモデルです。SLMは特定のタスクや用途に特化して使用されることが多く、計算資源が限られている場合や、特定の業界やニッチな分野で役立つことがあります。チャット目的のAIはプログラミングが出来なくてもいいですよね?いらない部分をなくしたLLMみたいな感じです。

量子化:

  • 量子化とは、複雑な計算をより簡単かつ高速に行うための技術です。特にAIモデルにおいて、量子化を行うことでモデルのサイズを小さくし、計算効率を向上させることができます。これは、スマートフォンや組み込みシステムのようなリソースが限られた環境でAIを実行する際に非常に重要です。量子化により、デバイス上でのAIの動作が現実的になります。

Google Colab:

  • Google Colabは、Googleが提供する無料のクラウドサービスで、まあまあいいパソコンがブラウザ上で無料で使えるサービスです。特にAIやデータ分析の分野でよく使われます。ユーザーはブラウザ上でPythonコードを実行し、機械学習モデルをトレーニングしたり、データを解析したりできます。無料で使える上に、GPUやTPUといった高性能なハードウェアも利用可能なため、多くの研究者や開発者に愛用されています。

Hugging Face:

  • Hugging Faceは、AIモデルやツールを提供する企業の名前です。誰でも簡単にAIモデルを利用できるようにするためのプラットフォームを提供しています。特に自然言語処理(NLP)の分野で有名で、多くのオープンソースのモデルやデータセットが公開されています。例えば、テキスト生成や翻訳、感情分析など、多様な用途に対応するモデルを提供しています。また、研究者や開発者が自分のプロジェクトでAIを簡単に使えるようにするためのツールやライブラリも豊富です。
    いろんなAIが公開されている場所ですね

環境

環境の確認

では、まずは今使っている環境を確認していきましょう。
私の場合は、

  • Ryzen5 2600
  • GTX1070
  • 16GBメモリ

と言った環境でした。
最近買ったパソコンなら私のよりも性能がいいと思うので、あまり気にしなくてもいいです。
ある程度低スぺ(低スペック)でも大丈夫です。

また、「グラフィックボードが無い」や、「パソコンをそもそも持っていない」という方でも大丈夫です。
google colab上で動作させれらます。
パソコンがない人は、操作性的にちょっと辛いかもしれませんがね。
スマホでも可能ではあります。
colabのスペックも無料枠で足りました。

環境の構築

では、最初の一歩である環境を作っていきましょう。

自分のパソコンで実行する方

まず最初に、pythonをインストールします。
https://sukkiri.jp/technologies/processors/python/python%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E6%96%B9%E6%B3%95windows%E7%B7%A8.html
インストール方法まで書いていたら長くなるので、上記の記事を見てインストールしてください。

そしたら、次にpowershellというものを起動していきます。
検索ボックスで「powershell」と検索して、起動します。
そして、

pip install transformers datasets torch accelerate peft bitsandbytes evaluate

と実行して待ちます。
エラーが出た場合は、pythonのインストールを確認してください。

colabでする

googleアカウントにログインしている状態で、
https://colab.research.google.com/drive/1u7C5tZuV2GiEX-S1c0cn33aTVn2Tutut?usp=sharing

上記のリンクにアクセスしてプログラムを表示します。
※現時点では課金するまで料金はかかりません(勝手に課金もされません)

データを作る

トレーニングするためのデータを作成しましょう。
これから作る文章をもとに、AIが学習します。
私はいつも、以下のようなデータを作っています。

{
    "prompt": "",
    "response": ""
}

promptに入力、responseに期待する解答の文章を書いていきます。
適当な場所に、「data.json」というファイルを作成します。
そこに上記の形式で書いて行きましょう。

私は、スマホではhttps://lorinta.xsrv.jp/wp-content/uploads/tools/json.html
これを使って作成して

パソコンでは、
https://github.com/rintaro-s/lal/tree/main
これらのツールを使用しました。
対話型でやりたい方はresponse.py、excelでA列とB列に書いていって変換したい方はxml-to-json.py を使用してください。

量は、上記(promptとresponse)のセットが200個くらいでも少しはモデルに影響があるのでそれを目指しましょう。
質を重視して書いていきましょう。変な文章を入れるとモデルが崩壊してしまう可能性もあるので。
なので量があればいいってものではないです。あるならあるに越したことは無いですが。

希望としては千個以上はほしいんですがね。
私は友達に手伝ってもらい、なんとか1000個作り上げました。

「機械翻訳のためのモデルが作りたい!」という人などは5万〜10万個以上はいるらしいです。

以下は、完成したときのサンプルです。

 ]
  {
    "prompt": "元気がないけどどうしたの?",
    "response": "お腹空いたよ〜"
  },
  {
    "prompt": "お腹空いたよ",
    "response": "それなら早くご飯にしようよ!"
  },
  {
    "prompt": "授業まだまだ続くからめんどくさいよ",
    "response": "頑張って!終わったら遊ぼう!"
  },
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
  {
    "prompt": "1+1は何?",
    "response": "2だね!"
  },
  {
    "prompt": "NATってなに?",
    "response": "えーっとね、NATは、パブリックIPアドレスとプライベートIPアドレスを変換する技術のことだよ!"
  },
  {
    "prompt": "しりとりしようよ!",
    "response": "いいよ!しりとりのりからね!"
  }
]

トレーニングtime

さっきのデータの作成が一番めんどくさいと思うので、頑張っていきましょう。
私の環境やgoogle colabでは16GBに納めなければいけなかったのでかなり圧縮(量子化)してあります。
なので完成したモデルが100MBにも満たない場合もありますが、正常なので安心してください。

また、AIの動作はとても負荷がかかるので必要のないソフト等は停止させておいてください。

自分のパソコンでする方

適当にフォルダーを作成し、さっき作ったdata.jsonを放り込みます。
そこに、train.pyというファイルを作り以下のコードをコピペします。
(コメントはchatgptくんに書いてもらいました。)

import os
import json
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, Trainer, TrainingArguments, get_linear_schedule_with_warmup
from datasets import Dataset
from peft import LoraConfig, get_peft_model
from torch.optim import AdamW
import evaluate
import numpy as np

# デバイスの確認
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"Using device: {device}")

# モデルとトークナイザーのロード (bitsandbytesで量子化、オフロード有効化)
model_name = "rinna/japanese-gpt-neox-3.6b"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    load_in_8bit=True,
    device_map="auto",
    llm_int8_enable_fp32_cpu_offload=True
)

# LoRAの設定
lora_config = LoraConfig(
    r=8,
    lora_alpha=32,
    target_modules=["query_key_value"],
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM"
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()

# 学習データのロード
data_file = "data.json"
if not os.path.exists(data_file):
    raise FileNotFoundError(f"{data_file} が見つかりません。同じディレクトリに配置してください。")

try:
    with open(data_file, "r", encoding="utf-8") as f:
        raw_data = json.load(f)
        for entry in raw_data:
            if not ("prompt" in entry and "response" in entry):
                raise ValueError("Invalid data format: 'prompt' and 'response' keys are required.")
except json.JSONDecodeError:
    raise ValueError("Invalid JSON format.")

# データセットの整形
def format_data(entry):
    return {"text": f"{entry['prompt']}\n{entry['response']}"}

formatted_data = [format_data(entry) for entry in raw_data]

# データセットを作成 (labelsの追加と修正)
def preprocess_function(examples):
    inputs = tokenizer(examples["text"], truncation=True, padding="max_length", max_length=512)
    inputs["labels"] = inputs["input_ids"].copy()

    new_labels = []
    for label in inputs["labels"]:
        if isinstance(label, int):
            label = [label]
        new_label = [(l if l != tokenizer.pad_token_id else -100) for l in label]
        new_labels.append(new_label)
    inputs["labels"] = new_labels
    return inputs

dataset = Dataset.from_list(formatted_data)
dataset = dataset.map(preprocess_function, batch_size=32, num_proc=os.cpu_count())
dataset = dataset.train_test_split(test_size=0.1)

# 評価指標の設定
metric = evaluate.load("rouge")

def compute_metrics(eval_pred):
    predictions, labels = eval_pred
    decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True)
    decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)
    result = metric.compute(predictions=decoded_preds, references=decoded_labels, use_stemmer=True)
    result = {key: value.mid.fmeasure * 100 for key, value in result.items()}
    prediction_lens = [np.count_nonzero(pred != tokenizer.pad_token_id) for pred in predictions]
    result["gen_len"] = np.mean(prediction_lens)
    return {k: v for k, v in result.items()}

# 学習パラメータの設定
output_dir = "./output"
os.makedirs(output_dir, exist_ok=True)

training_args = TrainingArguments(
    output_dir=output_dir,
    overwrite_output_dir=True,
    num_train_epochs=3,
    per_device_train_batch_size=1,
    gradient_accumulation_steps=8,
    save_steps=500,
    evaluation_strategy="steps",
    eval_steps=500,
    logging_steps=100,
    learning_rate=3e-4,
    fp16=True,
    save_total_limit=2,
    load_best_model_at_end=True,
    metric_for_best_model="rougeL",
    report_to="tensorboard"
)

# オプティマイザーとスケジューラーの設定
optimizer = AdamW(model.parameters(), lr=training_args.learning_rate)
num_training_steps = len(dataset["train"]) * training_args.num_train_epochs // (
    training_args.per_device_train_batch_size * training_args.gradient_accumulation_steps)
warmup_steps = int(num_training_steps * 0.1)
scheduler = get_linear_schedule_with_warmup(optimizer, num_warmup_steps=warmup_steps, num_training_steps=num_training_steps)

# Trainerの作成
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=dataset["train"],
    eval_dataset=dataset["test"],
    tokenizer=tokenizer,
    optimizers=(optimizer, scheduler),
    compute_metrics=compute_metrics
)

# ファインチューニングの実行
trainer.train()

# モデルの保存
model.save_pretrained(output_dir)
tokenizer.save_pretrained(output_dir)

print(f"Fine-tuning complete! Model saved to {output_dir}")

上記のコードを実行すると、もう完成です。
一番軽そうでチャットに向いてそうだったのでrinna/japanese-gpt-neox-3.6bというモデルをベースにしました。

colab上で実行する

環境準備の時に開いておいたタブを開きます。
そして、「接続」の隣にある矢印をクリックします。
そして、「ランタイムのタイプを変更」を押します。

その後、「T4GPU」を選択・保存をクリックします。

そして実行ボタンを押します。

しばらく待ったら、jsonファイルをアップロードしてくださいと案内が来るので「ファイルを選択」を押してdata.jsonをアップロードします。

「Fine-tuning complete! Model saved to ./LLM」と表示されたら成功です。
「メモリが足りません」みたいなエラーが出ても3回ほど試したら行けたので何回かためしてみてください。
その下にあるブロックを実行して、しばらく待ったらダウンロードが始まります。

試してみる

生成したモデルを実行してみましょう。

圧縮されている場合は解答をして、モデルが入ってるフォルダと同じディレクトリに下記のようなpythonファイルを作り実行します。
colabで作成した場合は5行目のmodel_name を “./model” から ”./LLM” に変えてください(モデルが入ってるフォルダ名にしてください)。

from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
import os
import tempfile

model_name = "./model" #colabで作成した場合は model_name = "./LLM"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name).to("cuda")

text = "「今日のご飯、何食べたい?」という会話文へのの返信は:"
inputs = tokenizer(text, return_tensors="pt").to("cuda")

outputs = model.generate(**inputs, max_new_tokens=100)  # max_new_tokensで生成するトークン数を制御
generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(generated_text)

まとめ

皆さん、どうだったでしょうか。
今回の記事を見て、ファインチューニングの流れがわかってくれると幸いです。
何か意見・感想等あればコメントまでお願いします。
質問は答えられるかわからないのでお控えください。
ありがとうございました!




コメント

タイトルとURLをコピーしました