Model Card for Model ID
Model Details
Model Description
This is the model card of a 🤗 transformers model that has been pushed on the Hub. This model card has been automatically generated.
- Developed by: [More Information Needed]
- Funded by [optional]: [More Information Needed]
- Shared by [optional]: [More Information Needed]
- Model type: [More Information Needed]
- Language(s) (NLP): [More Information Needed]
- License: [More Information Needed]
- Finetuned from model [optional]: [More Information Needed]
Model Sources [optional]
- Repository: [More Information Needed]
- Paper [optional]: [More Information Needed]
- Demo [optional]: [More Information Needed]
Uses
Direct Use
[More Information Needed]
Downstream Use [optional]
[More Information Needed]
Out-of-Scope Use
[More Information Needed]
Bias, Risks, and Limitations
[More Information Needed]
Recommendations
Users (both direct and downstream) should be made aware of the risks, biases and limitations of the model. More information needed for further recommendations.
How to Get Started with the Model
Use the code below to get started with the model.
[More Information Needed]
Training Details
Training Data
[More Information Needed]
Training Procedure
Preprocessing [optional]
[More Information Needed]
Training Hyperparameters
- Training regime: [More Information Needed]
Speeds, Sizes, Times [optional]
[More Information Needed]
Evaluation
Testing Data, Factors & Metrics
Testing Data
[More Information Needed]
Factors
[More Information Needed]
Metrics
[More Information Needed]
Results
[More Information Needed]
Summary
Model Examination [optional]
[More Information Needed]
Environmental Impact
Carbon emissions can be estimated using the Machine Learning Impact calculator presented in Lacoste et al. (2019).
- Hardware Type: [More Information Needed]
- Hours used: [More Information Needed]
- Cloud Provider: [More Information Needed]
- Compute Region: [More Information Needed]
- Carbon Emitted: [More Information Needed]
Technical Specifications [optional]
Model Architecture and Objective
[More Information Needed]
Compute Infrastructure
[More Information Needed]
Hardware
[More Information Needed]
Software
[More Information Needed]
Citation [optional]
BibTeX:
[More Information Needed]
APA:
[More Information Needed]
Glossary [optional]
[More Information Needed]
More Information [optional]
[More Information Needed]
Model Card Authors [optional]
[More Information Needed]
Model Card Contact
[More Information Needed]
東京大学 松尾・岩澤研究室
大規模言語モデル2024 最終課題コンペ
https://weblab.t.u-tokyo.ac.jp/lecture/course-list/large-language-model/
①Unsloth
Unslothを利用して日本語LLMを効率的にFine-Tuningし、タスクに応じた回答生成を実行するものです。
- 4bit量子化で大規模モデルを扱う
- LoRAによる軽量なFine-Tuning
- Google Colabやローカル環境対応
- Hugging Face Hub への保存機能
# -*- coding: utf-8 -*-
"""LoRA_template_unsloth_20241127.ipynb
Automatically generated by Colab.
Original file is located at
https://colab.research.google.com/drive/1BQEnKbcUdlC7LsysbQInQFtrtibjxJMQ
# 最終課題コンペ用 Fine-tuning テンプレート(unsloth)
最終課題コンペにて Fine-tuning を行ないたい方に向けの Fine-tuning コードです。
こちらは L4 を利用できない受講生の方向けにUnslothを用いたものとなっております。
Google Colab の無料版で利用可能な T4 でも動作可能になっています。
環境設定の難易度が高いので、慎重に取り組んでいただければと思います。
### terminalでのconda環境構築(Omnicampusの環境などの場合)
事前にterminalで環境構築の必要があります。Google Colabでは不要です。
# conda環境の構築
wget "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh"
# このコマンドではいくつか質問があるので答えて下さい。おそらくインストール先のデフォルトは/root/miniforge3かと思います
bash Miniforge3-$(uname)-$(uname -m).sh
# 以下、インストール先が/root/miniforge3であることを前提とします
export PATH=/root/miniforge3/bin:$PATH
conda init
# ここで一度、terminalを立ち上げ直す必要があります。
# 以下のリンク先に従い環境を作ります。
# https://docs.unsloth.ai/get-started/installation/conda-install
conda create --name unsloth_env python=3.10 pytorch-cuda=12.1 pytorch cudatoolkit xformers -c pytorch -c nvidia -c xformers -y
conda activate unsloth_env
pip install "unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git"
pip install --no-deps "trl<0.9.0" peft accelerate bitsandbytes
# jupyter notebook用のセットアップ。
conda install -c conda-forge ipykernel
python -m ipykernel install --user --name=unsloth_env --display-name "Python (unsloth_env)"
"""
# Google Colab の場合は上記の環境構築手順を行なわず、単にこのセルから実行していってください。
!pip uninstall unsloth -y
!pip install --upgrade --no-cache-dir "unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git"
# Google Colab のデフォルトで入っているパッケージをアップグレード(Moriyasu さんありがとうございます)
!pip install --upgrade torch
!pip install --upgrade xformers
# notebookでインタラクティブな表示を可能とする(ただし、うまく動かない場合あり)
# Google Colabでは実行不要
!pip install ipywidgets --upgrade
# Install Flash Attention 2 for softcapping support
import torch
if torch.cuda.get_device_capability()[0] >= 8:
!pip install --no-deps packaging ninja einops "flash-attn>=2.6.3"
"""## モデルのロード
以下のコードでモデルを読み込みます。
受講生の方からご指摘頂いたのですが、unslothでgemma2を読み込むと、自動でunslothが作成した非公式モデルがダウンロードされるようです。
対処方法がわからない受講生はLLM-jp-3のみをご利用ください!
"""
# Hugging Face Token を指定
# 下記の URL から Hugging Face Token を取得できますので下記の HF_TOKEN に入れてください。
# Write権限を付与してください。
# https://huggingface.co/settings/tokens
HF_TOKEN = "" #@param {type:"string"}
# あるいは Google Colab シークレットを使う場合、左のサイドバーより🔑マークをクリック
# HF_TOKEN という名前で Value に Hugging Face Token を入れてください。
# ノートブックからのアクセスのトグルをオンにし、下記の二行のコードのコメントアウトを外してください。
# from google.colab import userdata
# HF_TOKEN=userdata.get('HF_TOKEN')
# llm-jp/llm-jp-3-13bを4bit量子化のqLoRA設定でロード。
from unsloth import FastLanguageModel
import torch
max_seq_length = 512 # unslothではRoPEをサポートしているのでコンテキスト長は自由に設定可能
dtype = None # Noneにしておけば自動で設定
load_in_4bit = True # 今回は13Bモデルを扱うためTrue
model_id = "llm-jp/llm-jp-3-13b"
new_model_id = "llm-jp-3-13b-it" #Fine-Tuningしたモデルにつけたい名前、it: Instruction Tuning
# FastLanguageModel インスタンスを作成
model, tokenizer = FastLanguageModel.from_pretrained(
model_name=model_id,
dtype=dtype,
load_in_4bit=load_in_4bit,
trust_remote_code=True,
)
# SFT用のモデルを用意
model = FastLanguageModel.get_peft_model(
model,
r = 32,
target_modules = ["q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj",],
lora_alpha = 32,
lora_dropout = 0.05,
bias = "none",
use_gradient_checkpointing = "unsloth",
random_state = 3407,
use_rslora = False,
loftq_config = None,
max_seq_length = max_seq_length,
)
# 学習に用いるデータセットの指定
# 今回はLLM-jp の公開している Ichikara Instruction を使います。データにアクセスするためには申請が必要ですので、使いたい方のみ申請をしてください。
# Ichikara Instruciton を Hugging Face Hub にて公開することはお控えください。
# また、CC-BY-NC-SAですのでモデルはライセンスを継承する前提でお使いください。
# 下記のリンクから申請を終えた先に Google Drive があり、Distribution20241221_all というフォルダごとダウンロードしてください。
# 今回は「ichikara-instruction-003-001-1.json」を使います。必要であれば展開(!unzip など)し、データセットのパスを適切に指定してください。
# omnicampusの開発環境では取得したデータを左側にドラッグアンドドロップしてお使いください。
# Google Colab の場合も左のサイドバーよりドラッグ&ドロップでアップデートしてください。
# https://liat-aip.sakura.ne.jp/wp/llmのための日本語インストラクションデータ作成/llmのための日本語インストラクションデータ-公開/
# 関根聡, 安藤まや, 後藤美知子, 鈴木久美, 河原大輔, 井之上直也, 乾健太郎. ichikara-instruction: LLMのための日本語インストラクションデータの構築. 言語処理学会第30回年次大会(2024)
from datasets import load_dataset
dataset = load_dataset("json", data_files="./ichikara-instruction-003-001-1.json")
# パスの指定にご注意ください。アップロードしたファイルを右クリックし、「パスをコピー」をクリック、上記の data_files と合致していることをご確認ください。Omnicampus のディレクトリ構造とは異なるかもしれません。
# 学習時のプロンプトフォーマットの定義
prompt = """### 指示
{}
### 回答
{}"""
"""
formatting_prompts_func: 各データをプロンプトに合わせた形式に合わせる
"""
EOS_TOKEN = tokenizer.eos_token # トークナイザーのEOSトークン(文末トークン)
def formatting_prompts_func(examples):
input = examples["text"] # 入力データ
output = examples["output"] # 出力データ
text = prompt.format(input, output) + EOS_TOKEN # プロンプトの作成
return { "formatted_text" : text, } # 新しいフィールド "formatted_text" を返す
pass
# # 各データにフォーマットを適用
dataset = dataset.map(
formatting_prompts_func,
num_proc= 4, # 並列処理数を指定
)
dataset
# データを確認
print(dataset["train"]["formatted_text"][3])
"""
training_arguments: 学習の設定
- output_dir:
-トレーニング後のモデルを保存するディレクトリ
- per_device_train_batch_size:
- デバイスごとのトレーニングバッチサイズ
- per_device_eval_batch_size:
- デバイスごとの評価バッチサイズ
- gradient_accumulation_steps:
- 勾配を更新する前にステップを積み重ねる回数
- optim:
- オプティマイザの設定
- num_train_epochs:
- エポック数
- eval_strategy:
- 評価の戦略 ("no"/"steps"/"epoch")
- eval_steps:
- eval_strategyが"steps"のとき、評価を行うstep間隔
- logging_strategy:
- ログ記録の戦略
- logging_steps:
- ログを出力するステップ間隔
- warmup_steps:
- 学習率のウォームアップステップ数
- save_steps:
- モデルを保存するステップ間隔
- save_total_limit:
- 保存しておくcheckpointの数
- max_steps:
- トレーニングの最大ステップ数
- learning_rate:
- 学習率
- fp16:
- 16bit浮動小数点の使用設定(第8回演習を参考にすると良いです)
- bf16:
- BFloat16の使用設定
- group_by_length:
- 入力シーケンスの長さによりバッチをグループ化 (トレーニングの効率化)
- report_to:
- ログの送信先 ("wandb"/"tensorboard"など)
"""
from trl import SFTTrainer
from transformers import TrainingArguments
from unsloth import is_bfloat16_supported
trainer = SFTTrainer(
model = model,
tokenizer = tokenizer,
train_dataset=dataset["train"],
max_seq_length = max_seq_length,
dataset_text_field="formatted_text",
packing = False,
args = TrainingArguments(
per_device_train_batch_size = 2,
gradient_accumulation_steps = 4,
num_train_epochs = 1,
logging_steps = 10,
warmup_steps = 10,
save_steps=100,
save_total_limit=2,
max_steps=-1,
learning_rate = 2e-4,
fp16 = not is_bfloat16_supported(),
bf16 = is_bfloat16_supported(),
group_by_length=True,
seed = 3407,
output_dir = "outputs",
report_to = "none",
),
)
#@title 現在のメモリ使用量を表示
gpu_stats = torch.cuda.get_device_properties(0)
start_gpu_memory = round(torch.cuda.max_memory_reserved() / 1024 / 1024 / 1024, 3)
max_memory = round(gpu_stats.total_memory / 1024 / 1024 / 1024, 3)
print(f"GPU = {gpu_stats.name}. Max memory = {max_memory} GB.")
print(f"{start_gpu_memory} GB of memory reserved.")
#@title 学習実行
trainer_stats = trainer.train()
# ELYZA-tasks-100-TVの読み込み。事前にファイルをアップロードしてください
# データセットの読み込み。
# omnicampusの開発環境では、左にタスクのjsonlをドラッグアンドドロップしてから実行。
import json
datasets = []
with open("/content//elyza-tasks-100-TV_0.jsonl", "r") as f:
item = ""
for line in f:
line = line.strip()
item += line
if item.endswith("}"):
datasets.append(json.loads(item))
item = ""
# 学習したモデルを用いてタスクを実行
from tqdm import tqdm
# 推論するためにモデルのモードを変更
FastLanguageModel.for_inference(model)
results = []
for dt in tqdm(datasets):
input = dt["input"]
prompt = f"""### 指示\n{input}\n### 回答\n"""
inputs = tokenizer([prompt], return_tensors = "pt").to(model.device)
outputs = model.generate(**inputs, max_new_tokens = 512, use_cache = True, do_sample=False, repetition_penalty=1.2)
prediction = tokenizer.decode(outputs[0], skip_special_tokens=True).split('\n### 回答')[-1]
results.append({"task_id": dt["task_id"], "input": input, "output": prediction})
# jsonlで保存
with open(f"{new_model_id}_output.jsonl", 'w', encoding='utf-8') as f:
for result in results:
json.dump(result, f, ensure_ascii=False)
f.write('\n')
"""モデルとトークナイザーをHugging Faceにアップロードします。
本コードではLoRAのアダブタのみを保存します。
このアダプタを用いた推論方法はModel_Inference_Template_unsloth_20241127.ipynbをご参照ください。
一旦privateでアップロードしてください。
https://docs.unsloth.ai/basics/saving-and-using-models
"""
# LoRAアダプタだけ保存
model.push_to_hub_merged(
new_model_id+"_lora",
tokenizer=tokenizer,
save_method="lora",
token=HF_TOKEN,
private=True
)
②Unsloth(推論)
このコードは、学習済みの qLoRA アダプタ を使い、タスクデータ(ELYZA-tasks-100-TV)に対して推論を実施します。
- モデルとLoRAアダプタをロード: Unslothを使い、効率よくqLoRAモデルを読み込む
- タスクデータの読み込み: JSONL形式のデータを取得
- 推論実行: generate メソッドで回答を生成
- 結果保存: JSONL形式で出力し、提出フォーマットに合わせる
# -*- coding: utf-8 -*-
"""Model_Inference_Template_unsloth_20241127.ipynb
Automatically generated by Colab.
Original file is located at
https://colab.research.google.com/drive/1qRChXRT-qm94rl5roqgrjSxlK5_A9MlI
# 推論用コード
本コードはunslothで学習したqLoRAのアダプタを用いてELYZA-tasks-100-TVの出力を得るためのコードです。
Hugging Faceにアダプタをアップロードしてあることが前提となります。
このコードはunslothライブラリを用いてモデルを読み込み、推論するためのコードとなります。
このコードで生成されたjsonlファイルは課題の成果として提出可能なフォーマットになっております。
※本コードはGoogle Colabでの動作を想定しており、Omnicampusでの動作を想定しておりません。
Omnicampus向けのコードは別途用意しております。
"""
# Commented out IPython magic to ensure Python compatibility.
# # 必要なライブラリをインストール
# %%capture
# !pip install unsloth
# !pip uninstall unsloth -y && pip install --upgrade --no-cache-dir "unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git"
# !pip install -U torch
# !pip install -U peft
# 必要なライブラリを読み込み
from unsloth import FastLanguageModel
from peft import PeftModel
import torch
import json
from tqdm import tqdm
import re
# ベースとなるモデルと学習したLoRAのアダプタ(Hugging FaceのIDを指定)。
model_id = "llm-jp/llm-jp-3-13b"
adapter_id = "fujio48694062/llm-jp-3-13b-it_lora"
# Hugging Face Token を指定。
# 下記の URL から Hugging Face Token を取得できますので下記の HF_TOKEN に入れてください。
# https://huggingface.co/settings/tokens
HF_TOKEN = "" #@param {type:"string"}
# unslothのFastLanguageModelで元のモデルをロード。
dtype = None # Noneにしておけば自動で設定
load_in_4bit = True # 今回は13Bモデルを扱うためTrue
model, tokenizer = FastLanguageModel.from_pretrained(
model_name=model_id,
dtype=dtype,
load_in_4bit=load_in_4bit,
trust_remote_code=True,
)
# 元のモデルにLoRAのアダプタを統合。
model = PeftModel.from_pretrained(model, adapter_id, token = HF_TOKEN)
# タスクとなるデータの読み込み。
# 事前にデータをアップロードしてください。
datasets = []
with open("./elyza-tasks-100-TV_0.jsonl", "r") as f:
item = ""
for line in f:
line = line.strip()
item += line
if item.endswith("}"):
datasets.append(json.loads(item))
item = ""
# モデルを用いてタスクの推論。
# 推論するためにモデルのモードを変更
FastLanguageModel.for_inference(model)
results = []
for dt in tqdm(datasets):
input = dt["input"]
prompt = f"""### 指示\n{input}\n### 回答\n"""
inputs = tokenizer([prompt], return_tensors = "pt").to(model.device)
outputs = model.generate(**inputs, max_new_tokens = 512, use_cache = True, do_sample=False, repetition_penalty=1.2)
prediction = tokenizer.decode(outputs[0], skip_special_tokens=True).split('\n### 回答')[-1]
results.append({"task_id": dt["task_id"], "input": input, "output": prediction})
# 結果をjsonlで保存。
# ここではadapter_idを元にファイル名を決定しているが、ファイル名は任意で問題なし。
json_file_id = re.sub(".*/", "", adapter_id)
with open(f"/content/{json_file_id}_output.jsonl", 'w', encoding='utf-8') as f:
for result in results:
json.dump(result, f, ensure_ascii=False)
f.write('\n')
③DPO
このコードは DPO (Direct Preference Optimization) を使って、日本語LLM(大規模言語モデル)を学習・評価・推論するものです。
- 新タスク生成: モデルに指示文を生成させる
- 回答評価: 良い出力と悪い出力をペアに分類
- DPO学習: 選好データを基にモデルを最適化
- 推論・提出: 学習済みモデルで推論し、JSONLファイルを作成
# -*- coding: utf-8 -*-
"""DPOtemplate_20241207.ipynb
Automatically generated by Colab.
Original file is located at
https://colab.research.google.com/drive/1NcvS8oDuGLsSnDx7VdFR9OfE_5-bhbAJ
## コンペ用DPOテンプレート
こちらは、コンペにてDPOを行いたい方に向けたテンプレートとなるDPOコードです。
"""
!pip install -U ipywidgets
!pip install transformers==4.46.3
!pip install -U bitsandbytes
!pip install -U accelerate
!pip install -U datasets
!pip install -U peft==0.13.2
!pip install -U trl==0.12.1
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
BitsAndBytesConfig,
TrainingArguments,
logging,
)
from peft import (
LoraConfig,
PeftModel,
get_peft_model,
)
from trl import (
SFTTrainer,
DPOConfig,
DPOTrainer
)
from IPython.display import display
from datasets import load_dataset
import ipywidgets as widgets
import os, torch, gc, json
import bitsandbytes as bnb
from tqdm import tqdm
import pandas as pd
# Hugging Face Token (write権限)
HF_TOKEN = ""
# モデルを読み込み。
# llm-jp-3 1.8B, 3.7B, 13Bのsnapshotをダウンロード済みでmodelsディレクトリに格納してあります。
# base_model_idの値はomnicampusの環境におけるモデルのパスを表しており、それ以外の環境で実行する場合は変更の必要があります。
# その他のモデルは取得に承諾が必要なため、各自でダウンロードお願いします。
base_model_id = "models/models--llm-jp--llm-jp-3-13b/snapshots/cd3823f4c1fcbb0ad2e2af46036ab1b0ca13192a" #Fine-Tuningするベースモデル
# Google Colab などをお使いの方は下記のbase_model_idのコメントアウトを外してください。
base_model_id = "llm-jp/llm-jp-3-13b"
adapter_id = "fujio48694062/llm-jp-3-13b-it_lora" #dpoするベースモデル (あなたがFine-Tuningしたモデル - 今回はアダプタのみを想定)
new_model_id = "llm-jp-3-13b-dpo" #dpoするモデルにつけたい名前
# QLoRA config
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
)
# Load model
model = AutoModelForCausalLM.from_pretrained(
base_model_id,
quantization_config=bnb_config,
device_map="auto",
token = HF_TOKEN
)
# Load tokenizer
tokenizer = AutoTokenizer.from_pretrained(base_model_id, trust_remote_code=True, token = HF_TOKEN)
# 元のモデルにLoRAのアダプタを統合。
model = PeftModel.from_pretrained(model, adapter_id, token = HF_TOKEN)
"""### 合成データ作成
DPO用にElyza Tasks 100のようなタスクおよび回答の合成データを作成します。
その後、人手で「良い」「悪い」をアノテーションします。
"""
# 参考用のデータとしてElyza tasks 100を読み込み
datasets = load_dataset("elyza/ELYZA-tasks-100")
datasets
"""## 評価データの作成
DPOの学習のために評価データを作成します。
手順は以下の通りです。
1. ELYZA-tasks-100(オリジナル)を参考にしてLLMに新しいタスクを生成させます。
2. 生成されたタスクを目視で確認します。
3. (任意で)手作業でタスクを修正します。
4. 生成した各タスクに対してLLMに複数個の出力を生成させます。
5. 同じタスクに対する出力を比較して良い出力(chosen)と悪い出力(rejected)のペアを作成します。
"""
# 1. 新しいタスクの生成
# 時間とマシンリソースさえあればnum_return_sequencesの値を増やすことでいくらでも生成可能ですが
# たくさん生成させるとデータの質は低下するためバランスを探るために何パターンか試すのが望ましいです。
task_results = []
for ref_input in tqdm(datasets['test']['input']):
prompt = f"""以下に示す参考タスクに従って、類似したタスクを生成しなさい。
## 参考タスク
仕事の熱意を取り戻すためのアイデアを5つ挙げてください。
## 類似タスク
試合に挑む心構えを3つほど挙げてください。
## 参考タスク
{ref_input}
## 類似タスク
"""
tokenized_input = tokenizer.encode(prompt, add_special_tokens=False, return_tensors="pt").to(model.device)
attention_mask = torch.ones_like(tokenized_input)
with torch.no_grad():
outputs = model.generate(
tokenized_input,
attention_mask=attention_mask,
max_new_tokens=100,
num_return_sequences=3, # 1つの参考タスクからいくつ新タスクを生み出すか
do_sample=True,
temperature=0.6,
top_p=0.9,
repetition_penalty=1.2,
pad_token_id=tokenizer.eos_token_id
)
output_texts = [tokenizer.decode(output[tokenized_input.size(1):], skip_special_tokens=True) for output in outputs]
new_task = {"reference_task": ref_input}
new_task.update({f"similar_task_{i}": output_text for i, output_text in enumerate(output_texts)})
task_results.append(new_task)
# 2. 生成されたタスクを目視で確認。
df = pd.DataFrame(task_results)
df.head()
# 3. 生成したタスクの手作業による修正
# 生成されたタスクの質が低い場合、手作業で修正することも検討してください。
# 修正したい場合はcsvで出力して修正しましょう。
# EXCELやGoogleスプレッドシートで編集します。
# 簡単に修正可能な内容であれば修正し、修正が難しいものは削除して下さい。
# ※一度、GPU環境を止めて修正が終わってからGPU環境を再開してください
# (再開時は「評価データの作成」の1、2とこのコードは実行不要です)。
#
# 逆に手直ししない人は3と3.1はスキップしてください。
df.to_csv("elyza-new-tasks.csv", encoding="utf-8-sig")
# 3.1. 修正したcsvを読み込み。
# 手作業による修正をしていない人は実行の必要なし。
df = pd.read_csv("elyza-new-tasks.csv")
# 4. モデルによる回答データの生成
# こちらもnum_return_sequencesの数によっていくらでも生成可能である。
# この後、タスクごとに良い出力と悪い出力のペアを作成するのだが
# タスクによっては良い出力が出ない、悪い出力が出ないということが起こり得る。
# 多様な出力を得るために多めに出力するのが望ましい一方で
# 多くし過ぎると出力に膨大な時間がかかる上に評価の負担も増すので難しい。
# 生成したタスクを全て一つのリストに追加
tasks = []
for key in df.columns[1:]:
tasks.extend([task for task in df[key] if isinstance(task, str) and task])
outputs_results = []
for task in tqdm(tasks):
prompt = f"""### 指示:
{task}
### 回答:
"""
tokenized_input = tokenizer.encode(prompt, add_special_tokens=False, return_tensors="pt").to(model.device)
attention_mask = torch.ones_like(tokenized_input)
with torch.no_grad():
outputs = model.generate(
tokenized_input,
attention_mask=attention_mask,
max_new_tokens=512,
num_return_sequences=3, # 1つのタスクに対し何個出力させるか。ペアを作るので最低でも2個は必要
do_sample=True,
temperature=0.6,
top_p=0.9,
repetition_penalty=1.2,
pad_token_id=tokenizer.eos_token_id
)
output_texts = [tokenizer.decode(output[tokenized_input.size(1):], skip_special_tokens=True) for output in outputs]
outputs_results.append({"task": task, "outputs": output_texts})
"""**5. 良い出力と悪い出力のペアを作成**
DPOの学習を行うためには良い出力(chosen)と悪い出力(rejected)のペアを用意する必要があります。
ここで以下の3つを挙げるので、一つ選んで実行してください。
5.1. csvで出力した上でEXCELやGoogleスプレッドシートを使いペアを作成
5.2. Google ColabのGUIを用いてタスクごとに2段階評価
5.3. CUIで頑張って2段階評価
他にも3段階以上の評価を使う方法もあります。
2段階評価では良い出力と悪い出力の組み合わせでしかペアが作れませんが
3段階以上の評価では
- 1番良い出力と2番目に良い出力
- 1番良い出力と最も悪い出力
- 2番目に良い出力と最も悪い出力
というようにペアを増やすことができ
回答の数が同じでもペアを増やせるメリットがあります。
また、評価の粒度が細かくなるのでより正確な評価を反映できる可能性があります。
一方で増やし過ぎると過学習になるリスクもあるので最適なバランスは難しいところです。
"""
# 5.1.の方法で作成する場合はこちらを実行。
# 5.1.1. タスクと出力をcsvに保存
# 保存したelyza-new-tasks-and-preds.csvをExcelやGoogleスプレッドシートで開いた上で
# 1列目にinput、2列目に良い回答(chosen)、3列目に悪い回答(rejected)を記入したcsvを新たに作成し
# "good_and_bad_outputs.csv"という名前で保存してください。
# 回答に良い回答がない場合はそのタスク自体を削除するか、出力を手直ししても構いません。
# この作業を行った場合、5.2や5.3は行う必要ありません。
#
# ※一度、GPU環境を止めてペア作成が終わってからGPU環境を再開してください。
# (再開時は「評価データの作成」の1~4とこのコードは実行不要です)。
import csv
csv.writer(open("elyza-new-tasks-and-preds.csv", "w", encoding="utf-8-sig")).writerows([(row["task"], output) for row in outputs_results for output in row["outputs"]])
# 5.1.2. 「5.1.1」で作成したペアのcsvを読み込み
import csv
dpo_datasets = [
{"prompt":row[0], "chosen":row[1], "rejected":row[2]}
for row in csv.reader(open("good_and_bad_outputs.csv", encoding="utf-8"))
]
# 5.2. Google Colab上でのGUIによる二段階評価
# 今回はOmniでの使用を想定しているのでコメントアウトしています。
# こちらの作業を行う場合は5.1と5.3は実行不要です。
# ※一度、GPU環境を止めてペア作成が終わってからGPU環境を再開してください
# (再開時は「評価データの作成」の1~4と5.1.およびこのコードは実行不要です)。
# current_index = 0
# current_output_index = 0
# task_label = widgets.Label(value=f"指示: {outputs_results[current_index]['task']}")
# output_label = widgets.Textarea(
# value=f"回答:\n{outputs_results[current_index]['outputs'][current_output_index]}",
# disabled=True,
# layout=widgets.Layout(width='100%', height='100px')
# )
# yes_button = widgets.Button(description="Yes")
# no_button = widgets.Button(description="No")
# for item in outputs_results:
# item["annotations"] = [None] * len(item["outputs"])
# def on_yes_clicked(b):
# annotate_current_item("Yes")
# def on_no_clicked(b):
# annotate_current_item("No")
# def annotate_current_item(annotation):
# global current_index, current_output_index
# outputs_results[current_index]["annotations"][current_output_index] = annotation # アノテーションを記録
# next_item()
# def next_item():
# global current_index, current_output_index
# current_output_index += 1
# if current_output_index >= len(outputs_results[current_index]["outputs"]):
# current_output_index = 0
# current_index += 1
# if current_index < len(outputs_results):
# task_label.value = f"指示: {outputs_results[current_index]['task']}"
# output_label.value = f"回答:\n{outputs_results[current_index]['outputs'][current_output_index]}"
# else:
# task_label.value = "アノテーションが完了しました!"
# output_label.value = ""
# yes_button.disabled = True
# no_button.disabled = True
# # display_results()
# def display_results():
# print("アノテーション結果:")
# for item in outputs_results:
# print(f"指示: {item['task']}")
# for output, annotation in zip(item["outputs"], item["annotations"]):
# print(f" 回答:\n{output}\n アノテーション: {annotation}")
# yes_button.on_click(on_yes_clicked)
# no_button.on_click(on_no_clicked)
# display(task_label, output_label, yes_button, no_button)
# 5.3 CUIで手動で2段階評価
# こちらの作業を行う場合は5.1と5.2は実行不要です。
# ※一度、GPU環境を止めてペア作成が終わってからGPU環境を再開してください
# (再開時は「評価データの作成」の1~4と5.1.およびこのコードは実行不要です)。
current_index = 0
# ここから繰り返してください
print("指示: ",outputs_results[current_index]["task"])
for i, item in enumerate(outputs_results[current_index]["outputs"]):
print("----------------------------------")
print(f"回答{i}:\n{item}")
# 5.3.の続き
# annotationのリストの数は回答データのnum_return_sequencesの数と同じ
annotation = ["", "", ""] # 各回答に対応して[]に "Yes"か "No"を追加してください。(例: ["Yes", "Yes", "No"])
outputs_results[current_index]["annotations"] = annotation
current_index += 1
if current_index < len(outputs_results):
remain_len = len(outputs_results) - current_index
print(f"あと{remain_len}タスク分残っています。二つ前に戻って操作を繰り返してください。")
else:
print(f"お疲れ様でした。これで以上になります!!")
# 5.2または5.3を実行した場合、このコードで二段階評価をペアに変換する必要があります。
# 5.1を実行した方は不要です。
# なお、このコードではタスクごとにペアを1組だけ作成していますが、もっと多くのペアを作っても構いません。
# また、各データの長さは最大でも500くらいにしておくと学習がOOMせずに安定します。
# データセットの作成
import random
def process_item(item):
prompt = item["task"]
# "Yes" と "No" の両方を含まないタスクの場合は None を返す
if "GOOD" not in item["evaluation"] or "BAD" not in item["evaluation"]:
return None
chosen_items = [output for output, annotation in zip(item["output"], item["evaluation"]) if annotation == "GOOD"]
rejected_items = [output for output, annotation in zip(item["output"], item["evaluation"]) if annotation == "BAD"]
# ランダムにchosenとrejectedを1つずつ選択
chosen = random.choice(chosen_items)
rejected = random.choice(rejected_items)
return {"prompt": prompt, "chosen": chosen, "rejected": rejected}
# map + filter を使用
dpo_datasets = list(filter(None, map(process_item, outputs_results)))
# 作成したデータセットを見てみる
for key, value in dpo_datasets[0].items():
print(f"\n{key}:\n{value}\n")
"""## DPOによる学習
上の「評価データの作成」で一旦、環境を再起動していると思いますが、もし再起動していない場合、タスクや出力生成で使用されているGPUメモリを解放するために一旦、再起動してモデルとデータを読み込みなおしてください。
"""
peft_config = LoraConfig(
r=16,
lora_alpha=32,
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM",
target_modules=["q_proj", "v_proj"] # ["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj",]の全てをターゲットとするとL4で動かないので, 計算負荷を抑えるために一部のモジュール("q_proj"と"v_proj")を選択しています。
)
model = get_peft_model(model, peft_config)
from datasets import Dataset
dpo_datasets = Dataset.from_list(dpo_datasets)
# split_dpo_datasets = dpo_datasets_hf.train_test_split(test_size=0.1)
# train_dataset = split_dpo_datasets["train"]
# test_dataset = split_dpo_datasets["test"]
training_args = DPOConfig(
output_dir=new_model_id,
per_device_train_batch_size=1,
per_device_eval_batch_size=1,
gradient_accumulation_steps=4,
optim="paged_adamw_32bit",
num_train_epochs=1,
# eval_strategy="steps",
# eval_steps=0,
logging_strategy="steps",
logging_steps=10,
save_steps=100,
save_total_limit=1,
max_steps=-1,
learning_rate=2e-4,
fp16=True,
bf16=False,
report_to="none"
)
dpo_trainer = DPOTrainer(
model,
args=training_args,
train_dataset=dpo_datasets, #train_dataset,
# eval_dataset=test_dataset,
tokenizer=tokenizer,
peft_config=peft_config,
)
model.config.use_cache = False
dpo_trainer.train()
## 演習環境上に保存(再起動したときに使用可能)
model.config.use_cache = True
dpo_trainer.save_model(training_args.output_dir)
"""## タスクの推論
DPOの学習で使用したGPUメモリ次第では推論時にメモリ不足になる場合があるため
上手くいかなかった場合は再起動してdpo_trainer.save_modelで保存したアダプタを読み込むか
Hugging FaceにDPOのアダプタを一旦保存して
Model_Inference_Template_DPO_20241207.ipynb
で推論することをお勧めします。
"""
# タスクとなるデータの読み込み。
# omnicampusの開発環境では、左にタスクのjsonlをドラッグアンドドロップしてから実行。
import json
datasets = []
with open("./elyza-tasks-100-TV_0.jsonl", "r") as f:
item = ""
for line in f:
line = line.strip()
item += line
if item.endswith("}"):
datasets.append(json.loads(item))
item = ""
# モデルによるタスクの推論。
from tqdm import tqdm
results = []
for data in tqdm(datasets):
input = data["input"]
prompt = f"""### 指示
{input}
### 回答
"""
tokenized_input = tokenizer.encode(prompt, add_special_tokens=False, return_tensors="pt").to(model.device)
attention_mask = torch.ones_like(tokenized_input)
with torch.no_grad():
outputs = model.generate(
tokenized_input,
attention_mask=attention_mask,
max_new_tokens=100,
do_sample=False,
repetition_penalty=1.2,
pad_token_id=tokenizer.eos_token_id
)[0]
output = tokenizer.decode(outputs[tokenized_input.size(1):], skip_special_tokens=True)
results.append({"task_id": data["task_id"], "input": input, "output": output})
# こちらで生成されたjsolを提出してください。
# 本コードではinputも含んでいますが、なくても問題ありません。
# 必須なのはtask_idとoutputとなります。
import re
jsonl_id = re.sub(".*/", "", new_model_id)
with open(f"./{jsonl_id}-outputs.jsonl", 'w', encoding='utf-8') as f:
for result in results:
json.dump(result, f, ensure_ascii=False) # ensure_ascii=False for handling non-ASCII characters
f.write('\n')
# モデルとトークナイザーをHugging Faceにアップロード
model.push_to_hub(new_model_id, token=HF_TOKEN, private=True) # Online saving
tokenizer.push_to_hub(new_model_id, token=HF_TOKEN, private=True) # Online saving
④DPO(推論)
このコードは、LoRA(Low-Rank Adaptation)でFine-Tuningしたモデルと、DPO(Direct Preference Optimization)で最適化したモデルを用いてタスクの推論を行います。
- ベースモデルとLoRAアダプタをロード
- DPOアダプタを統合して最適化済みモデルを構築
- ELYZA-tasks-100-TVのinputに対して推論を実行
- 出力結果をJSONL形式で保存し、提出フォーマットに合わせる
# -*- coding: utf-8 -*-
"""Model_Inference_Template_DPO_20241207.ipynb
Automatically generated by Colab.
Original file is located at
https://colab.research.google.com/drive/1yKYyk8ziA7Ewcw3dWcp7thQGn43IVwxK
# 推論用コード
Hugging Faceにアップロードしたモデルを用いてELYZA-tasks-100-TVの出力を得るためのコードです。
LoRA_template_20241127.ipynbで学習したLoRAアダプタとDPOtemplate_20241207.ipynbで学習したDPOアダプタを用いる想定です。
このコードで生成されたjsonlファイルは課題の成果として提出可能なフォーマットになっております。
"""
!pip install -U ipywidgets
!pip install transformers==4.46.3
!pip install -U bitsandbytes
!pip install -U accelerate
!pip install -U datasets
!pip install -U peft==0.13.2
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
BitsAndBytesConfig,
)
from peft import PeftModel
import torch
from tqdm import tqdm
import json
# Hugging Faceで取得したTokenをこちらに貼る。
HF_TOKEN = ""
# ベースとなるモデルと学習したLoRAのアダプタ。
# model_idの値はomnicampusの環境におけるモデルのパスを表しており、それ以外の環境で実行する場合は変更の必要があります。
model_id = "models/models--llm-jp--llm-jp-3-13b/snapshots/cd3823f4c1fcbb0ad2e2af46036ab1b0ca13192a"
# omnicampus以外の環境をご利用の方は以下をご利用ください。
base_model_id = "llm-jp/llm-jp-3-13b"
adapter_id = "fujio48694062/llm-jp-3-13b-it_lora" # こちらにアップロードしたLoRAアダプタのHugging FaceのIDを指定してください。
adapter_dpo_id = "fujio48694062/llm-jp-3-13b-dpo" # こちらにアップロードしたDPOアダプタのHugging FaceのIDを指定してください。
#new_model_id = "llm-jp-3-13b-dpo_2" #dpoするモデルにつけたい名前
# QLoRA config
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
)
# Load model
model = AutoModelForCausalLM.from_pretrained(
base_model_id,
quantization_config=bnb_config,
device_map="auto",
token = HF_TOKEN
)
# Load tokenizer
tokenizer = AutoTokenizer.from_pretrained(base_model_id, trust_remote_code=True, token = HF_TOKEN)
# 元のモデルにLoRAのアダプタを統合。
model = PeftModel.from_pretrained(model, adapter_id, token = HF_TOKEN)
# LoRAのモデルにDPOのアダプタを統合。
model = PeftModel.from_pretrained(model, adapter_dpo_id, token = HF_TOKEN)
# データセットの読み込み。
# omnicampusの開発環境では、左にタスクのjsonlをドラッグアンドドロップしてから実行。
datasets = []
with open("./elyza-tasks-100-TV_0.jsonl", "r") as f:
item = ""
for line in f:
line = line.strip()
item += line
if item.endswith("}"):
datasets.append(json.loads(item))
item = ""
# llmjp
results = []
for data in tqdm(datasets):
input = data["input"]
prompt = f"""### 指示
{input}
### 回答
"""
tokenized_input = tokenizer.encode(prompt, add_special_tokens=False, return_tensors="pt").to(model.device)
attention_mask = torch.ones_like(tokenized_input)
with torch.no_grad():
outputs = model.generate(
tokenized_input,
attention_mask=attention_mask,
max_new_tokens=100,
do_sample=False,
repetition_penalty=1.2,
pad_token_id=tokenizer.eos_token_id
)[0]
output = tokenizer.decode(outputs[tokenized_input.size(1):], skip_special_tokens=True)
results.append({"task_id": data["task_id"], "input": input, "output": output})
# こちらで生成されたjsolを提出してください。
# 本コードではinputも含んでいますが、なくても問題ありません。
# 必須なのはtask_idとoutputとなります。
import re
jsonl_id = re.sub(".*/", "", adapter_dpo_id)
with open(f"./{jsonl_id}-outputs.jsonl", 'w', encoding='utf-8') as f:
for result in results:
json.dump(result, f, ensure_ascii=False) # ensure_ascii=False for handling non-ASCII characters
f.write('\n')