Transformers
Safetensors
Japanese
Inference Endpoints

Model Card for Model ID

  • ayayana/llm-jp-3-13b-dpo_ayana10
  • loraアダプタ
  • ベースモデルの権利を継承しますが、学習に使ったデータセットは研究用のみ用途のみに関してしか確認していません。

Model Details

  • 松尾研LLM講座2024にて、最終課題演習に利用したモデルになります。
  • DPOモデルの演習目的で制作。ベースモデル(llm-jp-3-13b)+オリジナルSFTをしたLoraアダプタ(llm-jp-3-13b-ayanatest_lora)を結合し、DPOを行った
  • ベースモデル:llm-jp/llm-jp-3-13b
  • SFTloraアダプタモデル:https://huggingface.co/ayayana/llm-jp-3-13b-ayanatest_lora -- :llm-jp/llm-jp-3-13bをベースに、ichikara003-001-1データセットでSFTしたlaraモデル
  • DPOに利用したデータセット:https://huggingface.co/datasets/ayayana/hh-rlhf-12k-ja-s300 -- :hh-rlhf-jaよりランダム抽出された300件を利用(カラム文字数は70以上512文字までを対象/promptは、初回会話のみをデータとして格納し利用:

Results/Summary

[-ElayzaTV100判定スコア:2.85]

  • スコアは、loraアダプタモデル(ayanatest_lora)だけの推論による結果スコア2.83とほぼ変動なし。
  • 目視では、日本語が流暢になったようには感じた。gemini1.5チャットを利用した判定だと3.1程度のスコア判定だった。
  • SFTの日本語データセットの量や結合アダプタを増やすほど、スコアが下がる結果になり、結果、少ないが質の良いデータセットによる初回アダプタが一番良い結果となった。
  • エポック数5で過学習気味の結果
  • 推論は、「Model_Inference_Template_DPO_20241207.ipynb」サンプルコードを利用し、 max_new_tokens= 1024で推定
  • 推論は、A100利用で30分ほど

Step / Training Loss

[
[90/90 20:47, Epoch 4/5]
## 10 / 0.713900
## 20 / 0.683300
## 30 / 0.322100
## 40 / 0.180100
## 50 / 0.052900
## 60 / 0.017600
## 70 / 0.004400
## 80 / 0.003100
## 90 / 0.000700

]

Model Description

  • 「DPOtemplate_20241207.ipynb」サンプルコードをカスタマイズ利用し、以下のDPOチューニング設定で学習を行った。
  • 学習は、A100利用で30分内

DPO学習、推論Code

# ベースモデルと学習したLoRAのアダプタの指定。
base_model_id = "llm-jp/llm-jp-3-13b"
adapter_id = "ayayana/llm-jp-3-13b-ayanatest_lora" #dpoするベースモデル (あなたがFine-Tuningしたモデル - 今回はアダプタのみを想定)
new_model_id = "llm-jp-3-13b-dpo_ayana10" #dpoするモデルにつけたい名前


##DPO学習用データセットの構築
# データセットの準備
print("Preparing dataset...")
dataset = load_dataset("llm-jp/hh-rlhf-12k-ja")
train_data = dataset["train"]


# 140文字以上のデータをフィルタリング
filtered_data = []
for item in train_data:
   chosen = item['chosen']
   rejected = item['rejected']
   # conversationsから最初の人間の発言を取得
   conversations = item['conversations']
   if conversations and len(conversations) > 0:
       # 最初の人間からの発言を取得
       first_human_msg = next((conv['value'] for conv in conversations if conv['from'] == 'human'), "")
       prompt = first_human_msg
   else:
       prompt = ""


   if 140 <= len(chosen) <= 1024 and 140 <= len(rejected) <= 1024:
       filtered_data.append({
           'prompt': prompt,     # 人からの最初の発言をプロンプトとして使用
           'chosen': chosen,
           'rejected': rejected
       })


print(f"Original data size: {len(train_data)}")
print(f"Filtered data size: {len(filtered_data)}")


# データの例を表示して確認
print("\nExample data:")
print(f"Prompt: {filtered_data[0]['prompt']}")
print(f"Chosen: {filtered_data[0]['chosen'][:210]}...")
print(f"Rejected: {filtered_data[0]['rejected'][:210]}...")

##データセット制作
filtered_data = []
for item in train_data:
   chosen = item['chosen']
   rejected = item['rejected']


   # 会話の取得
   conversations = item['conversations']
   if conversations and len(conversations) > 0:
       first_human_msg = next((conv['value'] for conv in conversations if conv['from'] == 'human'), "")
       prompt = first_human_msg.strip()
   else:
       continue


   # 内容の検証
   if (140 <= len(chosen) <= 3000 and
       140 <= len(rejected) <= 3000 and
       len(prompt.strip()) > 0 and
       chosen != rejected and
       not all(c.isspace() for c in chosen) and
       not all(c.isspace() for c in rejected)):


       filtered_data.append({
           'prompt': prompt,
           'chosen': chosen.strip(),
           'rejected': rejected.strip()
       })


# ループの外で一回だけデータ品質をチェック
print("\nData quality check:")
print(f"Total examples: {len(filtered_data)}")
print(f"Empty prompts: {sum(1 for x in filtered_data if not x['prompt'])}")
print(f"Average chosen length: {sum(len(x['chosen']) for x in filtered_data)/len(filtered_data)}")
print(f"Average rejected length: {sum(len(x['rejected']) for x in filtered_data)/len(filtered_data)}")


# サンプルデータの表示
if filtered_data:
   print("\nSample data:")
   print(f"Prompt: {filtered_data[0]['prompt']}")
   print(f"Chosen: {filtered_data[0]['chosen'][:120]}...")
   print(f"Rejected: {filtered_data[0]['rejected'][:120]}...")


# 300件をランダムサンプリング
if len(filtered_data) > 300:
   sampled_data = random.sample(filtered_data, 300)
else:
   sampled_data = filtered_data


print(f"Final sampled data size: {len(sampled_data)}")


# DPO用のデータ形式に変換
dpo_datasets = []
for item in sampled_data:
   dpo_item = {
       "prompt": item['prompt'],  # 元のプロンプトを使用
       "chosen": item['chosen'],
       "rejected": item['rejected']
   }
   dpo_datasets.append(dpo_item)


# データの例を表示して確認
print("\nExample data:")
print(f"Prompt: {dpo_datasets[0]['prompt']}")
print(f"Chosen: {dpo_datasets[0]['chosen'][:200]}...")
print(f"Rejected: {dpo_datasets[0]['rejected'][:200]}...")


# JSONファイルに保存
json_file_path = "dpo_dataset.json"
with open(json_file_path, "w", encoding="utf-8") as f:
   json.dump(dpo_datasets, f, indent=4, ensure_ascii=False)


print(f"\nData saved to {json_file_path}")




##DPO学習
# データセットをHuggingFace Dataset形式に変換
dpo_datasets = Dataset.from_list(dpo_datasets)


# メモリクリア
torch.cuda.empty_cache()
gc.collect()
if torch.cuda.is_available():
   torch.cuda.synchronize()  # GPU操作の完了を待つ
   torch.cuda.empty_cache()  # もう一度クリア


# DPO training configuration
training_args = DPOConfig(
   output_dir=new_model_id,
   per_device_train_batch_size=1,
   # per_device_eval_batch_size=1,
   per_device_eval_batch_size=2, #メモリ対策
   # gradient_accumulation_steps=32,#メモリ対策
   gradient_accumulation_steps=16,  # 16から32へ増やす(要調整)
   optim="paged_adamw_8bit",
   num_train_epochs=5,
   logging_steps=10,
   save_steps=10,
   save_total_limit=1,
   max_steps=-1,
   learning_rate=2e-4,
   fp16=False,
   bf16=True,
   max_grad_norm=0.3,
   dataloader_num_workers=0,
   report_to="none",
   gradient_checkpointing=True  # 勾配チェックポイントを有効化
)


# Initialize DPO trainer
dpo_trainer = DPOTrainer(
   model,
   args=training_args,
   train_dataset=dpo_datasets,
   tokenizer=tokenizer,
   peft_config=peft_config,
)


# Start training
model.config.use_cache = False
dpo_trainer.train()


# アダプター名を事前に定義
adapter_model_id = new_model_id + "+lora_adp10"


# LoRAアダプターとして保存
model.save_pretrained(
   adapter_model_id,
   push_to_hub=True,
   token=HF_TOKEN,
   private=True
)

推論_サンプルコードを利用

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=1024,
          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})

結果のファイル出力と保存

推論結果をJosnl形式でファイル出力し、保存。ファイル名および保存フォルダーは任意に指定。

import re
jsonl_id = re.sub(".*/", "", adapter_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)  
        f.write('\n')

結果ファイルのエクセルダウンロード

import json
import pandas as pd

# JSONLファイルのパス
jsonl_file_path = "/content/llm-jp-3-13b-ayana002_lora_output.jsonl"

# JSONLファイルを読み込み
with open(jsonl_file_path, "r", encoding="utf-8") as f:
    results = [json.loads(line) for line in f]

# DataFrameに変換
df = pd.DataFrame(results)

# Excelファイルに保存
df.to_excel("output.xlsx", index=False, engine="openpyxl")

print("Excelファイルが 'output.xlsx' として保存されました!")
Downloads last month

-

Downloads are not tracked for this model. How to track
Inference API
Unable to determine this model’s pipeline type. Check the docs .

Model tree for ayayana/llm-jp-3-13b-dpo_ayana10

Finetuned
(1)
this model

Datasets used to train ayayana/llm-jp-3-13b-dpo_ayana10