618ZXW

DeepSeek の微調整に関する初心者向けガイド: レビュー チュートリアルはここにあります。

オリジナル: Mu Xiaoxiong (Datawhale)

Datawhaleのヒント

著者: Mu Xiaoxiong、Datawhaleメンバー

序文:大規模モデルの評価は体系的なプロジェクトです。この記事は、大規模モデルのファインチューニングの効果を比較的シンプルな方法でより直感的に理解していただくことを目的としています。ここで紹介するアイデアとアプローチは、今後の議論の出発点となることを目的としています。学習者が大規模モデルの評価に強い関心を持っている場合は、様々な視点から学ぶことができます。

3日前、DatawhaleのWeChat公式アカウントで「初心者向けガイド:DeepSeekの微調整チュートリアル公開!」という記事が掲載され、大変好評を博しました。内容は非常に実践的で、学習者が実際に体験するのにぴったりでした。

そこで、私はその論文に基づいて作業を再現し、大規模なモデルの微調整がモデルにどのような影響を与えるかを読者がより直感的に理解できるように内容を拡張することにしました。

学習と体験を容易にするために、この記事で選択したモデルは蒸留された DeepSeek-R1-Distill-Qwen-7B モデルであり、選択されたグラフィック カードは RTX4090 24G です。

Deepseek モデルとデータセットは両方とも、Magic Tower コミュニティの medical-o1-reasoning-SFT から派生したものです。

1. 微調整チュートリアルの再現

import torch import matplotlib.pyplot as plt from transformers import (    AutoTokenizer,    AutoModelForCausalLM,    TrainingArguments,    Trainer,    TrainerCallback ) from peft import LoraConfig, get_peft_model from datasets import load_dataset import os os.environ["CUDA_VISIBLE_DEVICES"] = "0"  # 指定使用GPU # 配置路径(根据实际路径修改) model_path = "xxxx"  # 模型路径data_path = "xxxx"  # 数据集路径output_path = "xxxx"  # 微调后模型保存路径# 设置设备参数DEVICE = "cuda"  # 使用CUDA DEVICE_ID = "0"  # CUDA设备ID,如果未设置则为空device = f"{DEVICE}:{DEVICE_ID}" if DEVICE_ID else DEVICE  # 组合CUDA设备信息# 自定义回调记录Loss class LossCallback(TrainerCallback):    def __init__(self):        self.losses = []    def on_log(self, args, state, control, logs=None, **kwargs):        if "loss" in logs:            self.losses.append(logs["loss"]) # 数据预处理函数def process_data(tokenizer):    dataset = load_dataset("json", data_files=data_path, split="train[:1500]")    def format_example(example):        instruction = f"诊断问题:{example['Question']}\n详细分析:{example['Complex_CoT']}"        inputs = tokenizer(            f"{instruction}\n### 答案:\n{example['Response']}<|endoftext|>",            padding="max_length",            truncation=True,            max_length=512,            return_tensors="pt"        )        return {"input_ids": inputs["input_ids"].squeeze(0), "attention_mask": inputs["attention_mask"].squeeze(0)}    return dataset.map(format_example, remove_columns=dataset.column_names) # LoRA配置peft_config = LoraConfig(    r=16,    lora_alpha=32,    target_modules=["q_proj", "v_proj"],    lora_dropout=0.05,    bias="none",    task_type="CAUSAL_LM" ) # 训练参数配置training_args = TrainingArguments(    output_dir=output_path,    per_device_train_batch_size=2,  # 显存优化设置    gradient_accumulation_steps=4,  # 累计梯度相当于batch_size=8    num_train_epochs=3,    learning_rate=3e-4,    fp16=True,  # 开启混合精度    logging_steps=20,    save_strategy="no",    report_to="none",    optim="adamw_torch",    no_cuda=False,  # 强制使用CUDA    dataloader_pin_memory=False,  # 加速数据加载    remove_unused_columns=False,  # 防止删除未使用的列    device="cuda:0" # 指定使用的GPU设备   ) def main():    # 创建输出目录    os.makedirs(output_path, exist_ok=True)    # 加载tokenizer    tokenizer = AutoTokenizer.from_pretrained(model_path)    tokenizer.pad_token = tokenizer.eos_token    # 加载模型到GPU    model = AutoModelForCausalLM.from_pretrained(        model_path,        torch_dtype=torch.float16,        device_map=device    )    model = get_peft_model(model, peft_config)    model.print_trainable_parameters()    # 准备数据    dataset = process_data(tokenizer)    # 训练回调    loss_callback = LossCallback()    # 数据加载器    def data_collator(data):        batch = {            "input_ids": torch.stack([torch.tensor(d["input_ids"]) for d in data]).to(device),            "attention_mask": torch.stack([torch.tensor(d["attention_mask"]) for d in data]).to(device),            "labels": torch.stack([torch.tensor(d["input_ids"]) for d in data]).to(device)  # 使用input_ids作为labels        }        return batch    # 创建Trainer    trainer = Trainer(        model=model,        args=training_args,        train_dataset=dataset,        data_collator=data_collator,        callbacks=[loss_callback]    )    # 开始训练    print("开始训练...")    trainer.train()    # 保存最终模型    trainer.model.save_pretrained(output_path)    print(f"模型已保存至:{output_path}")    # 绘制训练集损失Loss曲线    plt.figure(figsize=(10, 6))    plt.plot(loss_callback.losses)    plt.title("Training Loss Curve")    plt.xlabel("Steps")    plt.ylabel("Loss")    plt.savefig(os.path.join(output_path, "loss_curve.png"))    print("Loss曲线已保存") if __name__ == "__main__":    main()

微調整の詳細な説明については、公式WeChatアカウントの以前の記事をご覧ください。では、損失曲線を見てみましょう。

ご覧のとおり、単純な微調整後、モデルの LOSS 値が減少し、Deepseek モデルがトレーニング データセットに適合していることがわかります。

2. モデル生成の直感的な比較

モデルを微調整した後、生成されたコンテンツはどの程度効果的ですか。また、それをどのように比較できますか。

この時点で、私たちが最初に考えたのは、同じ質問に対して「微調整されたモデル」と「元のモデル」によって生成された回答を直接比較することです。

したがって、プロンプトを標準化し、関連する質問を標準化して、生成された回答を比較することができます。

具体的なコードは以下のとおりです。

 import torch from transformers import AutoTokenizer, AutoModelForCausalLM from peft import PeftModel import os import json from bert_score import score from tqdm import tqdm # 设置可见GPU设备(根据实际GPU情况调整) os.environ["CUDA_VISIBLE_DEVICES"] = "0"  # 指定仅使用GPU # 路径配置 ------------------------------------------------------------------------ base_model_path = "xxxxx"  # 原始预训练模型路径peft_model_path = "xxxxx"  # LoRA微调后保存的适配器路径# 模型加载 ------------------------------------------------------------------------ # 初始化分词器(使用与训练时相同的tokenizer) tokenizer = AutoTokenizer.from_pretrained(base_model_path) # 加载基础模型(半精度加载节省显存) base_model = AutoModelForCausalLM.from_pretrained(    base_model_path,    torch_dtype=torch.float16,  # 使用float16精度    device_map="auto"           # 自动分配设备(CPU/GPU) ) # 加载LoRA适配器(在基础模型上加载微调参数) lora_model = PeftModel.from_pretrained(    base_model,    peft_model_path,    torch_dtype=torch.float16,    device_map="auto" ) # 合并LoRA权重到基础模型(提升推理速度,但会失去再次训练的能力) lora_model = lora_model.merge_and_unload() lora_model.eval()  # 设置为评估模式# 生成函数 ------------------------------------------------------------------------ def generate_response(model, prompt):    """统一的生成函数    参数:        model : 要使用的模型实例        prompt : 符合格式要求的输入文本    返回:        清洗后的回答文本    """    # 输入编码(保持与训练时相同的处理方式)    inputs = tokenizer(        prompt,        return_tensors="pt",          # 返回PyTorch张量        max_length=1024,               # 最大输入长度(与训练时一致)        truncation=True,              # 启用截断        padding="max_length"          # 填充到最大长度(保证batch一致性)    ).to(model.device)               # 确保输入与模型在同一设备    # 文本生成(关闭梯度计算以节省内存)    with torch.no_grad():        outputs = model.generate(            input_ids=inputs.input_ids,            attention_mask=inputs.attention_mask,            max_new_tokens=1024,       # 生成内容的最大token数(控制回答长度)            temperature=0.7,         # 温度参数(0.0-1.0,值越大随机性越强)            top_p=0.9,               # 核采样参数(保留累积概率前90%的token)            repetition_penalty=1.1,  # 重复惩罚系数(>1.0时抑制重复内容)            eos_token_id=tokenizer.eos_token_id,  # 结束符ID            pad_token_id=tokenizer.pad_token_id,  # 填充符ID        )       # 解码与清洗输出    full_text = tokenizer.decode(outputs[0], skip_special_tokens=True)  # 跳过特殊token    answer = full_text.split("### 答案:\n")[-1].strip()  # 提取答案部分    return answer # 对比测试函数 -------------------------------------------------------------------- def compare_models(question):    """模型对比函数    参数:        question : 自然语言形式的医疗问题    """    # 构建符合训练格式的prompt(注意与训练时格式完全一致)    prompt = f"诊断问题:{question}\n详细分析:\n### 答案:\n"       # 双模型生成    base_answer = generate_response(base_model, prompt)  # 原始模型    lora_answer = generate_response(lora_model, prompt)  # 微调模型       # 终端彩色打印对比结果    print("\n" + "="*50)  # 分隔线    print(f"问题:{question}")    print("-"*50)    print(f"\033[1;34m[原始模型]\033[0m\n{base_answer}")  # 蓝色显示原始模型结果    print("-"*50)    print(f"\033[1;32m[LoRA模型]\033[0m\n{lora_answer}")  # 绿色显示微调模型结果    print("="*50 + "\n") # 主程序 ------------------------------------------------------------------------ if __name__ == "__main__":        # 测试问题集(可自由扩展)    test_questions = [        "根据描述,一个1岁的孩子在夏季头皮出现多处小结节,长期不愈合,且现在疮大如梅,溃破流脓,口不收敛,头皮下有空洞,患处皮肤增厚。这种病症在中医中诊断为什么病?"    ]       # 遍历测试问题    for q in test_questions:        compare_models(q)同じ問題に対するモデルの出力の違いを見てみましょう。ここでは、微調整された画像と元のモデルの違いを強調するために、トレーニングセットから1つのデータをテスト用に選択しました。読者の皆様は、ご自身の状況に合わせてランダ​​ムにテストすることができます。 import torch from transformers import AutoTokenizer, AutoModelForCausalLM from peft import PeftModel import os import json from bert_score import score from tqdm import tqdm # 设置可见GPU设备(根据实际GPU情况调整) os.environ["CUDA_VISIBLE_DEVICES"] = "0"  # 指定仅使用GPU # 路径配置 ------------------------------------------------------------------------ base_model_path = "xxxxx"  # 原始预训练模型路径peft_model_path = "xxxxx"  # LoRA微调后保存的适配器路径# 模型加载 ------------------------------------------------------------------------ # 初始化分词器(使用与训练时相同的tokenizer) tokenizer = AutoTokenizer.from_pretrained(base_model_path) # 加载基础模型(半精度加载节省显存) base_model = AutoModelForCausalLM.from_pretrained(    base_model_path,    torch_dtype=torch.float16,  # 使用float16精度    device_map="auto"           # 自动分配设备(CPU/GPU) ) # 加载LoRA适配器(在基础模型上加载微调参数) lora_model = PeftModel.from_pretrained(    base_model,    peft_model_path,    torch_dtype=torch.float16,    device_map="auto" ) # 合并LoRA权重到基础模型(提升推理速度,但会失去再次训练的能力) lora_model = lora_model.merge_and_unload() lora_model.eval()  # 设置为评估模式# 生成函数 ------------------------------------------------------------------------ def generate_response(model, prompt):    """统一的生成函数    参数:        model : 要使用的模型实例        prompt : 符合格式要求的输入文本    返回:        清洗后的回答文本    """    # 输入编码(保持与训练时相同的处理方式)    inputs = tokenizer(        prompt,        return_tensors="pt",          # 返回PyTorch张量        max_length=1024,               # 最大输入长度(与训练时一致)        truncation=True,              # 启用截断        padding="max_length"          # 填充到最大长度(保证batch一致性)    ).to(model.device)               # 确保输入与模型在同一设备    # 文本生成(关闭梯度计算以节省内存)    with torch.no_grad():        outputs = model.generate(            input_ids=inputs.input_ids,            attention_mask=inputs.attention_mask,            max_new_tokens=1024,       # 生成内容的最大token数(控制回答长度)            temperature=0.7,         # 温度参数(0.0-1.0,值越大随机性越强)            top_p=0.9,               # 核采样参数(保留累积概率前90%的token)            repetition_penalty=1.1,  # 重复惩罚系数(>1.0时抑制重复内容)            eos_token_id=tokenizer.eos_token_id,  # 结束符ID            pad_token_id=tokenizer.pad_token_id,  # 填充符ID        )       # 解码与清洗输出    full_text = tokenizer.decode(outputs[0], skip_special_tokens=True)  # 跳过特殊token    answer = full_text.split("### 答案:\n")[-1].strip()  # 提取答案部分    return answer # 对比测试函数 -------------------------------------------------------------------- def compare_models(question):    """模型对比函数    参数:        question : 自然语言形式的医疗问题    """    # 构建符合训练格式的prompt(注意与训练时格式完全一致)    prompt = f"诊断问题:{question}\n详细分析:\n### 答案:\n"       # 双模型生成    base_answer = generate_response(base_model, prompt)  # 原始模型    lora_answer = generate_response(lora_model, prompt)  # 微调模型       # 终端彩色打印对比结果    print("\n" + "="*50)  # 分隔线    print(f"问题:{question}")    print("-"*50)    print(f"\033[1;34m[原始模型]\033[0m\n{base_answer}")  # 蓝色显示原始模型结果    print("-"*50)    print(f"\033[1;32m[LoRA模型]\033[0m\n{lora_answer}")  # 绿色显示微调模型结果    print("="*50 + "\n") # 主程序 ------------------------------------------------------------------------ if __name__ == "__main__":        # 测试问题集(可自由扩展)    test_questions = [        "根据描述,一个1岁的孩子在夏季头皮出现多处小结节,长期不愈合,且现在疮大如梅,溃破流脓,口不收敛,头皮下有空洞,患处皮肤增厚。这种病症在中医中诊断为什么病?"    ]       # 遍历测试问题    for q in test_questions:        compare_models(q)

生成されたコンテンツを見てみましょう。

生成されたコンテンツに基づくと、LoRA 調整モデルは元のモデルとは若干異なるようです。しかし、学習者である私たちは医学的問題についてあまり詳しくないため、これら 2 つのモデルを比較するのは抽象的すぎます。調整可能なモデルと元のモデルの違いを示すために、より直感的な方法を使用することはできませんか?

この時点で、BERTスコアを用いてモデルを比較し、テキスト類似性で評価できるかどうか検討しました。では、BERTスコアとは何でしょうか?Deepseek(フルバージョン)のレスポンスを見てみましょう。出力は長すぎてここには貼り付けられませんが、重要なのは意味的な類似性を測定するという点です。BERTスコアを使用すると、トレーニングセットの回答とモデルによって生成された回答を比較することができ、微調整されたモデルと元のモデルの違いをより直感的に確認できます。

学習を容易にするために、次のコードで選択された BERT モデルは最も基本的な BERT-base-Chinese モデルであり、Magic Tower コミュニティからダウンロードすることもできます。

一部の学習者は Hugging Face の Web サイトにアクセスできない可能性があることを考慮して、ここでは bert-base-chinese モデルがオフラインで読み込まれることに注意してください。

注意: モデル評価には大量のリソースが必要になるため、学習者は 10 個のデータセットのみを使用することをお勧めします。

さて、コードを見てみましょう:

 import torch from transformers import AutoTokenizer, AutoModelForCausalLM from peft import PeftModel import os import json from bert_score import score from tqdm import tqdm # 设置可见GPU设备(根据实际GPU情况调整) os.environ["CUDA_VISIBLE_DEVICES"] = "0"  # 指定仅使用GPU # 路径配置 ------------------------------------------------------------------------ base_model_path = "xxxxxx/DeepSeek-R1-Distill-Qwen-7B"  # 原始预训练模型路径peft_model_path = "xxxxxx/output"  # LoRA微调后保存的适配器路径# 模型加载 ------------------------------------------------------------------------ # 初始化分词器(使用与训练时相同的tokenizer) tokenizer = AutoTokenizer.from_pretrained(base_model_path) # 加载基础模型(半精度加载节省显存) base_model = AutoModelForCausalLM.from_pretrained(    base_model_path,    torch_dtype=torch.float16,  # 使用float16精度    device_map="auto"           # 自动分配设备(CPU/GPU) ) # 加载LoRA适配器(在基础模型上加载微调参数) lora_model = PeftModel.from_pretrained(    base_model,    peft_model_path,    torch_dtype=torch.float16,    device_map="auto" ) # 合并LoRA权重到基础模型(提升推理速度,但会失去再次训练的能力) lora_model = lora_model.merge_and_unload() lora_model.eval()  # 设置为评估模式# 生成函数 ------------------------------------------------------------------------ def generate_response(model, prompt):    """统一的生成函数    参数:        model : 要使用的模型实例        prompt : 符合格式要求的输入文本    返回:        清洗后的回答文本    """    # 输入编码(保持与训练时相同的处理方式)    inputs = tokenizer(        prompt,        return_tensors="pt",          # 返回PyTorch张量        max_length=1024,               # 最大输入长度(与训练时一致)        truncation=True,              # 启用截断        padding="max_length"          # 填充到最大长度(保证batch一致性)    ).to(model.device)               # 确保输入与模型在同一设备    # 文本生成(关闭梯度计算以节省内存)    with torch.no_grad():        outputs = model.generate(            input_ids=inputs.input_ids,            attention_mask=inputs.attention_mask,            max_new_tokens=1024,       # 生成内容的最大token数(控制回答长度)            temperature=0.7,         # 温度参数(0.0-1.0,值越大随机性越强)            top_p=0.9,               # 核采样参数(保留累积概率前90%的token)            repetition_penalty=1.1,  # 重复惩罚系数(>1.0时抑制重复内容)            eos_token_id=tokenizer.eos_token_id,  # 结束符ID            pad_token_id=tokenizer.pad_token_id,  # 填充符ID        )       # 解码与清洗输出    full_text = tokenizer.decode(outputs[0], skip_special_tokens=True)  # 跳过特殊token    answer = full_text.split("### 答案:\n")[-1].strip()  # 提取答案部分    return answer # 对比测试函数 -------------------------------------------------------------------- def compare_models(question):    """模型对比函数    参数:       question : 自然语言形式的医疗问题(如"小孩感冒怎么办?")    """    # 构建符合训练格式的prompt(注意与训练时格式完全一致)    prompt = f"诊断问题:{question}\n详细分析:\n### 答案:\n"       # 双模型生成    base_answer = generate_response(base_model, prompt)  # 原始模型    lora_answer = generate_response(lora_model, prompt)  # 微调模型       # 终端彩色打印对比结果    print("\n" + "="*50)  # 分隔线    print(f"问题:{question}")    print("-"*50)    print(f"\033[1;34m[原始模型]\033[0m\n{base_answer}")  # 蓝色显示原始模型结果    print("-"*50)    print(f"\033[1;32m[LoRA模型]\033[0m\n{lora_answer}")  # 绿色显示微调模型结果    print("="*50 + "\n") # 主程序 ------------------------------------------------------------------------ if __name__ == "__main__":    # 测试问题集(可自由扩展)    # test_questions = [    #     "根据描述,一个1岁的孩子在夏季头皮出现多处小结节,长期不愈合,且现在疮大如梅,溃破流脓,口不收敛,头皮下有空洞,患处皮肤增厚。这种病症在中医中诊断为什么病?"    # ]       # # 遍历测试问题    # for q in test_questions:    #     compare_models(q)    # 加载测试数据    ####-----------批量测试---------------#    with open("xxxxxx/data/medical_o1_sft_Chinese.json") as f:        test_data = json.load(f)    # 数据量比较大,我们只选择10条数据进行测试    test_data=test_data[:10]    # 批量生成回答    def batch_generate(model, questions):        answers = []        for q in tqdm(questions):            prompt = f"诊断问题:{q}\n详细分析:\n### 答案:\n"            ans = generate_response(model, prompt)            answers.append(ans)        return answers    # 生成结果    base_answers = batch_generate(base_model, [d["Question"] for d in test_data])    lora_answers = batch_generate(lora_model, [d["Question"] for d in test_data])    ref_answers = [d["Response"] for d in test_data]    bert_model_path="xxxxx/model/bert-base-chinese"    # 计算BERTScore    _, _, base_bert = score(base_answers, ref_answers, lang="zh",model_type=bert_model_path,num_layers=12,device="cuda")    _, _, lora_bert = score(lora_answers, ref_answers, lang="zh",model_type=bert_model_path,num_layers=12,device="cuda")    print(f"BERTScore | 原始模型: {base_bert.mean().item():.3f} | LoRA模型: {lora_bert.mean().item():.3f}")結果を見てみましょう: import torch from transformers import AutoTokenizer, AutoModelForCausalLM from peft import PeftModel import os import json from bert_score import score from tqdm import tqdm # 设置可见GPU设备(根据实际GPU情况调整) os.environ["CUDA_VISIBLE_DEVICES"] = "0"  # 指定仅使用GPU # 路径配置 ------------------------------------------------------------------------ base_model_path = "xxxxxx/DeepSeek-R1-Distill-Qwen-7B"  # 原始预训练模型路径peft_model_path = "xxxxxx/output"  # LoRA微调后保存的适配器路径# 模型加载 ------------------------------------------------------------------------ # 初始化分词器(使用与训练时相同的tokenizer) tokenizer = AutoTokenizer.from_pretrained(base_model_path) # 加载基础模型(半精度加载节省显存) base_model = AutoModelForCausalLM.from_pretrained(    base_model_path,    torch_dtype=torch.float16,  # 使用float16精度    device_map="auto"           # 自动分配设备(CPU/GPU) ) # 加载LoRA适配器(在基础模型上加载微调参数) lora_model = PeftModel.from_pretrained(    base_model,    peft_model_path,    torch_dtype=torch.float16,    device_map="auto" ) # 合并LoRA权重到基础模型(提升推理速度,但会失去再次训练的能力) lora_model = lora_model.merge_and_unload() lora_model.eval()  # 设置为评估模式# 生成函数 ------------------------------------------------------------------------ def generate_response(model, prompt):    """统一的生成函数    参数:        model : 要使用的模型实例        prompt : 符合格式要求的输入文本    返回:        清洗后的回答文本    """    # 输入编码(保持与训练时相同的处理方式)    inputs = tokenizer(        prompt,        return_tensors="pt",          # 返回PyTorch张量        max_length=1024,               # 最大输入长度(与训练时一致)        truncation=True,              # 启用截断        padding="max_length"          # 填充到最大长度(保证batch一致性)    ).to(model.device)               # 确保输入与模型在同一设备    # 文本生成(关闭梯度计算以节省内存)    with torch.no_grad():        outputs = model.generate(            input_ids=inputs.input_ids,            attention_mask=inputs.attention_mask,            max_new_tokens=1024,       # 生成内容的最大token数(控制回答长度)            temperature=0.7,         # 温度参数(0.0-1.0,值越大随机性越强)            top_p=0.9,               # 核采样参数(保留累积概率前90%的token)            repetition_penalty=1.1,  # 重复惩罚系数(>1.0时抑制重复内容)            eos_token_id=tokenizer.eos_token_id,  # 结束符ID            pad_token_id=tokenizer.pad_token_id,  # 填充符ID        )       # 解码与清洗输出    full_text = tokenizer.decode(outputs[0], skip_special_tokens=True)  # 跳过特殊token    answer = full_text.split("### 答案:\n")[-1].strip()  # 提取答案部分    return answer # 对比测试函数 -------------------------------------------------------------------- def compare_models(question):    """模型对比函数    参数:       question : 自然语言形式的医疗问题(如"小孩感冒怎么办?")    """    # 构建符合训练格式的prompt(注意与训练时格式完全一致)    prompt = f"诊断问题:{question}\n详细分析:\n### 答案:\n"       # 双模型生成    base_answer = generate_response(base_model, prompt)  # 原始模型    lora_answer = generate_response(lora_model, prompt)  # 微调模型       # 终端彩色打印对比结果    print("\n" + "="*50)  # 分隔线    print(f"问题:{question}")    print("-"*50)    print(f"\033[1;34m[原始模型]\033[0m\n{base_answer}")  # 蓝色显示原始模型结果    print("-"*50)    print(f"\033[1;32m[LoRA模型]\033[0m\n{lora_answer}")  # 绿色显示微调模型结果    print("="*50 + "\n") # 主程序 ------------------------------------------------------------------------ if __name__ == "__main__":    # 测试问题集(可自由扩展)    # test_questions = [    #     "根据描述,一个1岁的孩子在夏季头皮出现多处小结节,长期不愈合,且现在疮大如梅,溃破流脓,口不收敛,头皮下有空洞,患处皮肤增厚。这种病症在中医中诊断为什么病?"    # ]       # # 遍历测试问题    # for q in test_questions:    #     compare_models(q)    # 加载测试数据    ####-----------批量测试---------------#    with open("xxxxxx/data/medical_o1_sft_Chinese.json") as f:        test_data = json.load(f)    # 数据量比较大,我们只选择10条数据进行测试    test_data=test_data[:10]    # 批量生成回答    def batch_generate(model, questions):        answers = []        for q in tqdm(questions):            prompt = f"诊断问题:{q}\n详细分析:\n### 答案:\n"            ans = generate_response(model, prompt)            answers.append(ans)        return answers    # 生成结果    base_answers = batch_generate(base_model, [d["Question"] for d in test_data])    lora_answers = batch_generate(lora_model, [d["Question"] for d in test_data])    ref_answers = [d["Response"] for d in test_data]    bert_model_path="xxxxx/model/bert-base-chinese"    # 计算BERTScore    _, _, base_bert = score(base_answers, ref_answers, lang="zh",model_type=bert_model_path,num_layers=12,device="cuda")    _, _, lora_bert = score(lora_answers, ref_answers, lang="zh",model_type=bert_model_path,num_layers=12,device="cuda")    print(f"BERTScore | 原始模型: {base_bert.mean().item():.3f} | LoRA模型: {lora_bert.mean().item():.3f}")

結果

bertscoreを用いてデータセット内の参照回答とモデル生成回答の類似度を比較すると、LoRA微調整後の結果と元のモデルとの間には依然としてわずかな差異が見られます。LoRA微調整の学習ラウンドが深まるにつれ、大規模モデルを意図的に「過学習」させたとしても、この結果の差異はさらに拡大するはずです。これにより、学習者は比較的定性的な観点から新たな視点を得ることができます。

3. 追記

大規模モデルの評価は、特に高度な専門性を要する金融やヘルスケア分野では、比較的複雑かつ体系的なプロセスです。実際の企業展開においては、モデル生成の品質を評価するためのより多様な手法が存在します。

この記事は、初心者の視点からこのトピックにアプローチし、学習者が微調整されたモデルと元のモデルの違いを簡単かつ直接的に理解できるようにすることを目的としています。

本稿の目的は、「Deepseekのファインチューニング論文」のフォローアップ研究を拡張し、この比較的基本的な手法を通して学習者がファインチューニングとモデル評価を理解できるようにすることです。本稿が更なる学習の出発点となることを願っています。学習者が大規模モデルの評価に強い関心を持っている場合、様々な視点から学ぶことができます。

ぜひ高評価をお願いします*トリプルタップ↓*