0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LoRAで日本語FAQ対応モデルを追加学習する:データセット作成から評価まで

0
Last updated at Posted at 2026-10-24

社内FAQや製品サポートの回答を、毎回プロンプトに大量に詰め込む方法には限界があります。回答形式や用語の使い方が安定しない場合は、既存の日本語チャットモデルにFAQの応答例を追加学習させる方法が有効です。

この記事では、Qwen2.5-1.5B-InstructにLoRAを適用し、日本語FAQ対応モデルを作成します。私が実際に試した構成では、ベースモデル本体を書き換えず、数十〜数百件程度のFAQデータから小さなアダプターを作成できました。

データセットを作る

FAQデータの準備から学習後の評価までの流れを図にすると次の通りです。

まず、FAQをJSONL形式で用意します。1行が1会話です。質問文の言い換えや、「わからない場合」の例も含めるのがポイントです。

{"messages":[{"role":"system","content":"あなたは製品サポート担当です。FAQに基づいて簡潔に回答してください。情報がない場合は、推測せず担当者への確認を案内してください。"},{"role":"user","content":"パスワードを忘れました。"},{"role":"assistant","content":"ログイン画面の「パスワードを忘れた場合」から、登録済みメールアドレスを入力して再設定してください。メールが届かない場合は迷惑メールフォルダーも確認してください。"}]}
{"messages":[{"role":"system","content":"あなたは製品サポート担当です。FAQに基づいて簡潔に回答してください。情報がない場合は、推測せず担当者への確認を案内してください。"},{"role":"user","content":"請求書はいつ発行されますか?"},{"role":"assistant","content":"請求書は毎月末締めで、翌月5営業日以内に登録済みメールアドレスへ送付されます。"}]}

実データでは、氏名、メールアドレス、顧客番号などの個人情報を除去します。また、質問と回答の対応が曖昧なデータを混ぜると、モデルが誤った関連付けを学習します。

私は最初から完璧なFAQを作ろうとせず、カテゴリごとに代表的な質問を集めました。質問の表記揺れを追加すると、実際の利用者の入力に対しても安定しやすくなります。

環境を準備する

今回はPython 3.11、CUDAが利用できるGPU、Hugging Face Transformers、Datasets、PEFTを使います。

python -m venv .venv
source .venv/bin/activate

pip install torch transformers datasets peft accelerate sentencepiece

Windowsの場合は、仮想環境の有効化を次のようにします。

.venv\Scripts\activate

チャットモデルは、モデルごとに会話の区切り方が異なります。そのため、単純に質問:や回答:という文字列を連結するのではなく、Tokenizerのチャットテンプレートを使います。apply_chat_templateは、モデルに合った形式へメッセージを変換するためのAPIです。

詳しくはTransformersのチャットテンプレート documentationと、LoRAの設定項目をまとめたPEFTのドキュメントを確認できます。

LoRAで追加学習する

以下が学習用スクリプトです。faq.jsonlを読み込み、8対2に分割して検証用データも確保します。

from datasets import load_dataset
from transformers import (
    AutoTokenizer,
    AutoModelForCausalLM,
    TrainingArguments,
    Trainer,
    DataCollatorForLanguageModeling,
)
from peft import LoraConfig, get_peft_model, TaskType

BASE_MODEL = "Qwen/Qwen2.5-1.5B-Instruct"

tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL)
if tokenizer.pad_token is None:
    tokenizer.pad_token = tokenizer.eos_token

raw = load_dataset("json", data_files="faq.jsonl")["train"]
raw = raw.train_test_split(test_size=0.2, seed=42)

def make_text(example):
    text = tokenizer.apply_chat_template(
        example["messages"],
        tokenize=False,
        add_generation_prompt=False,
    )
    return {"text": text}

formatted = raw.map(make_text)

def tokenize(batch):
    return tokenizer(
        batch["text"],
        truncation=True,
        max_length=1024,
    )

tokenized = formatted.map(
    tokenize,
    batched=True,
    remove_columns=formatted["train"].column_names,
)

model = AutoModelForCausalLM.from_pretrained(
    BASE_MODEL,
    torch_dtype="auto",
)

model.config.use_cache = False

lora_config = LoraConfig(
    r=16,
    lora_alpha=32,
    lora_dropout=0.05,
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
    bias="none",
    task_type=TaskType.CAUSAL_LM,
)

model = get_peft_model(model, lora_config)
model.print_trainable_parameters()

args = TrainingArguments(
    output_dir="./faq-lora",
    per_device_train_batch_size=1,
    per_device_eval_batch_size=1,
    gradient_accumulation_steps=8,
    learning_rate=2e-4,
    num_train_epochs=3,
    logging_steps=10,
    eval_strategy="steps",
    eval_steps=50,
    save_steps=50,
    save_total_limit=2,
    bf16=True,
    report_to="none",
)

collator = DataCollatorForLanguageModeling(
    tokenizer=tokenizer,
    mlm=False,
)

trainer = Trainer(
    model=model,
    args=args,
    train_dataset=tokenized["train"],
    eval_dataset=tokenized["test"],
    data_collator=collator,
)

trainer.train()
trainer.save_model("./faq-lora/adapter")
tokenizer.save_pretrained("./faq-lora/adapter")

学習を開始します。

python train.py

bf16=Trueは対応GPUが必要です。対応していない場合は、GPU環境に応じてfp16=Trueへ変更します。メモリが足りない場合は、max_length、バッチサイズ、ベースモデルのサイズを小さくします。

LoRAでは、ベースモデルの全パラメーターではなく、指定した層に低ランクの差分を学習させます。学習後に保存されるのは主にアダプターなので、モデル全体を保存するより扱いやすいのが利点です。

推論して回答を確認する

学習したアダプターを読み込んで回答を生成します。

import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
from peft import PeftModel

BASE_MODEL = "Qwen/Qwen2.5-1.5B-Instruct"
ADAPTER = "./faq-lora/adapter"

tokenizer = AutoTokenizer.from_pretrained(ADAPTER)
base = AutoModelForCausalLM.from_pretrained(
    BASE_MODEL,
    torch_dtype="auto",
    device_map="auto",
)
model = PeftModel.from_pretrained(base, ADAPTER)
model.eval()

messages = [
    {
        "role": "system",
        "content": "あなたは製品サポート担当です。FAQに基づいて簡潔に回答してください。",
    },
    {"role": "user", "content": "請求書はいつ届きますか?"},
]

input_ids = tokenizer.apply_chat_template(
    messages,
    tokenize=True,
    add_generation_prompt=True,
    return_tensors="pt",
).to(model.device)

with torch.no_grad():
    output = model.generate(
        input_ids,
        max_new_tokens=200,
        do_sample=False,
    )

answer = output[0][input_ids.shape[1]:]
print(tokenizer.decode(answer, skip_special_tokens=True))

ここで重要なのは、学習時と推論時でsystemメッセージやチャットテンプレートを大きく変えないことです。形式が変わると、学習した応答パターンがうまく再現されない場合があります。

評価で確認すること

検証用データのlossだけでは、FAQとして正しい回答かどうかまでは判断できません。私は次の3種類で確認しました。

まず、学習に使っていない質問を入力し、回答がFAQの内容と一致するかを確認します。次に、同じ質問を少し言い換えて、回答できるかを確認します。最後に、FAQに存在しない質問を入力し、推測で断定せず確認を促せるかを確認します。

簡単なキーワード評価は次のように書けます。

tests = [
    {
        "question": "パスワードを再設定したいです",
        "must_include": ["パスワード", "再設定"],
    },
    {
        "question": "登録内容を変更できますか?",
        "must_include": ["確認"],
    },
]

def contains_required(text, words):
    return all(word in text for word in words)

for case in tests:
    # 実際には先ほどの生成処理を関数化して呼び出す
    answer = ask_model(case["question"])
    ok = contains_required(answer, case["must_include"])
    print(ok, case["question"], answer)

キーワード一致率は補助指標です。最終的には、正確性、不要な断定、個人情報の漏えい、回答の長さ、担当者への引き継ぎ表現を人手で確認します。

まとめ

LoRAを使うと、日本語FAQへの回答傾向を小さなアダプターとして追加学習できます。重要なのは、学習手法よりもデータの品質と評価設計です。

特に、個人情報の除去、質問と回答の正しい対応付け、未知の質問への応答例、学習に使っていないデータでの検証を忘れないようにします。まずは小規模なFAQで試し、ベースモデルとの差分を確認してからデータを増やす進め方がおすすめです。

0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?