微调 1B LLaMA 3.2:带代码的综合分步指南

社区文章 发布于 2024 年 10 月 2 日

通过微调 Llama 3.2 构建心理健康聊天机器人

图片参考

心理健康是情感、心理和社会维度整体幸福的关键方面。

让我们通过微调 Llama 3.2 来找到一些心灵的平静吧😊。

我们需要安装 unsloth 以实现 2 倍的快速训练和更小的模型大小。

!pip install unsloth

!pip uninstall unsloth -y && pip install --upgrade --no-cache-dir "unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git"

我们将使用 Unsloth,因为它显著提高了微调大型语言模型 (LLM)(特别是 LLaMA 和 Mistral)的效率。通过 Unsloth,我们可以使用高级量化技术,例如 4 位16 位量化,以减少内存并加速训练和推理。这意味着我们甚至可以在资源有限的硬件上部署强大的模型,而不会影响性能。

此外,Unsloth 的广泛兼容性和自定义选项允许根据产品的特定需求进行量化过程。这种灵活性,加上其将 VRAM 使用量减少高达 60% 的能力,使 Unsloth 成为 AI 工具包中的重要工具。这不仅仅是关于优化模型,更是关于使尖端 AI 更易于实际应用且更高效。

对于微调,我使用了以下设置:

  • Torch 2.1.1 with CUDA 12.1 用于高效计算。
  • Unsloth 用于将大型语言模型 (LLM) 的训练速度提高 2 倍。
  • H100 NVL GPU 用于处理密集的处理需求,但您可以使用功率较低的 GPU,例如 Kaggle GPU。
    为什么选择 LLaMA 3.2?

    它开源且易于访问,并提供灵活的自定义和微调以满足特定需求。由于 Meta 模型的权重是开源的,因此很容易在任何问题上进行微调,我们将使用 Hugging Face 的心理健康数据集对其进行微调

    Python 库 📕 📗 📘 📙

    数据处理与可视化

    import os
    import numpy as np
    import pandas as pd
    import seaborn as sns
    import matplotlib.pyplot as plt
    plt.style.use('ggplot')
    

    LLM 模型训练

    import torch
    from trl import SFTTrainer
    from transformers import TrainingArguments, TextStreamer
    from unsloth.chat_templates import get_chat_template
    from unsloth import FastLanguageModel
    from datasets import Dataset
    from unsloth import is_bfloat16_supported
    
    # Saving model
    from transformers import AutoTokenizer, AutoModelForSequenceClassification
    
    # Warnings
    import warnings
    warnings.filterwarnings("ignore")
    
    %matplotlib inline
    

    🦥 Unsloth:将修补您的计算机,以实现 2 倍更快的免费微调。

    调用数据集

    data = pd.read_json("hf://datasets/Amod/mental_health_counseling_conversations/combined_dataset.json", lines=True)
    

    探索性数据分析 🔎 📊

    让我们检查每个上下文中的单词长度

    data['Context_length'] = data['Context'].apply(len)
    plt.figure(figsize=(10, 3))
    sns.histplot(data['Context_length'], bins=50, kde=True)
    plt.title('Distribution of Context Lengths')
    plt.xlabel('Length of Context')
    plt.ylabel('Frequency')
    plt.show()
    

    image/png

    注意::如上所示,最少单词数是 1500,然后存在显著差异,因此我们只使用 1500 个单词或更少的数据。

    filtered_data = data[data['Context_length'] <= 1500]
    
    ln_Context = filtered_data['Context'].apply(len)
    plt.figure(figsize=(10, 3))
    sns.histplot(ln_Context, bins=50, kde=True)
    plt.title('Distribution of Context Lengths')
    plt.xlabel('Length of Context')
    plt.ylabel('Frequency')
    plt.show()
    

    image/png

    注意::现在可以使用这些数据了。

    让我们现在检查每个响应中的单词长度

    ln_Response = filtered_data['Response'].apply(len)
    plt.figure(figsize=(10, 3))
    sns.histplot(ln_Response, bins=50, kde=True, color='teal')
    plt.title('Distribution of Response Lengths')
    plt.xlabel('Length of Response')
    plt.ylabel('Frequency')
    plt.show()
    

    image/png

    注意::在这里,在 4000 个单词长度的响应之后,也有显著的下降。

    filtered_data = filtered_data[ln_Response <= 4000]
    
    ln_Response = filtered_data['Response'].apply(len)
    plt.figure(figsize=(10, 3))
    sns.histplot(ln_Response, bins=50, kde=True, color='teal')
    plt.title('Distribution of Response Lengths')
    plt.xlabel('Length of Response')
    plt.ylabel('Frequency')
    plt.show()
    

    image/png

    注意: 不需要这种数据准备来处理 LLM 模型的文本长度,但为了单词数量的一致性,我只是以 4000 个单词以下的数据为例,因此您可以根据需要进行任何数据预处理。

    模型训练 🧪

    图片参考

    让我们深入了解 Llama 3.2 模型并在我们的数据上对其进行训练

    加载模型

    我们将使用只有 10 亿参数的 Llama 3.2,但您也可以使用 30 亿、110 亿或 900 亿的版本。

    您可以根据需要遵循的关键方面

    1. 最大序列长度

      我们使用了 `max_seq_length` 5020,这是模型在单个输入序列中可以处理的最大 token 数量。这对于需要处理长文本的任务至关重要,确保模型在每次传递中都能捕获更多上下文。它可以根据需求使用。

    2. 加载 Llama 3.2 模型

      使用 `FastLanguageModel.from_pretrained` 加载模型和 tokenizer,并指定预训练模型 `"unsloth/Llama-3.2-1B-bnb-4bitt"`。此模型针对 4 位精度进行了优化,可减少内存使用并提高训练速度,而不会显著影响性能。`load_in_4bit=True` 参数启用这种高效的 4 位量化,使其更适合在性能较低的硬件上进行微调。

    3. 应用 PEFT(参数高效微调)

      然后,我们使用 `get_peft_model` 配置模型,该模型应用了 LoRA(低秩适应)技术。这种方法专注于仅微调模型的特定层或部分,而不是整个网络,从而大大减少了所需的计算资源。

      诸如 `r=16` 和 `lora_alpha=16` 等参数调整这些适应的复杂性和缩放比例。`target_modules` 的使用指定了模型中应进行适应的层,其中包括参与注意力机制的关键组件,如 `q_proj`、`k_proj` 和 `v_proj`。

      `use_rslora=True` 激活了秩稳定 LoRA,这提高了微调过程的稳定性。`use_gradient_checkpointing="unsloth"` 确保在训练期间通过选择性存储仅必要的计算来优化内存使用,进一步提高了模型的效率。

    4. 验证可训练参数

      最后,我们使用 `model.print_trainable_parameters()` 打印出在微调期间将更新的参数数量,从而可以验证只有模型的预期部分正在训练。

    这些技术的结合使微调过程不仅更高效,而且更易于访问,即使计算资源有限,您也可以部署此模型。

    将最大 token 长度设置为 5020 对于低秩适应 (LoRA) 训练来说绰绰有余,但您可以根据数据和需求进行设置。

    max_seq_length = 5020
    model, tokenizer = FastLanguageModel.from_pretrained(
        model_name="unsloth/Llama-3.2-1B-bnb-4bit",
        max_seq_length=max_seq_length,
        load_in_4bit=True,
        dtype=None,
    )
    
    model = FastLanguageModel.get_peft_model(
        model,
        r=16,
        lora_alpha=16,
        lora_dropout=0,
        target_modules=["q_proj", "k_proj", "v_proj", "up_proj", "down_proj", "o_proj", "gate_proj"],
        use_rslora=True,
        use_gradient_checkpointing="unsloth",
        random_state = 32,
        loftq_config = None,
    )
    print(model.print_trainable_parameters())
    

    准备模型输入数据

    现在是时候设计心理健康分析的格式化提示了。此函数从心理学角度分析输入文本,识别情绪困扰、应对机制或整体心理健康的指标。它还突出潜在的担忧或积极方面,并对每个观察结果提供简要解释。我们将准备这些数据以供模型进一步处理,确保每个输入-输出对都清晰地格式化,以便进行有效分析。

    需要记住的要点

    1. 数据提示结构

      `data_prompt` 是一个格式化字符串模板,旨在指导模型分析提供的文本。它包含输入文本(上下文)和模型响应的占位符。此模板专门提示模型识别心理健康指标,从而更容易为心理健康相关任务微调模型。

    2. 序列结束 token

      从 tokenizer 中检索 `EOS_TOKEN` 以表示每个文本序列的结束。此 token 对于模型识别提示何时结束至关重要,有助于在训练或推理期间保持数据结构。

    3. 格式化函数

      `formatting_prompt` 用于获取一批示例并根据 `data_prompt` 对其进行格式化。它遍历输入和输出对,将它们插入模板并在末尾添加 EOS token。然后,该函数返回一个包含格式化文本的字典,可用于模型训练或评估。

    4. 函数输出

      该函数输出一个字典,其中键为 `"text"`,值为格式化字符串列表。每个字符串都代表一个为模型完全准备好的提示,结合了上下文、响应和结构化提示模板。

    data_prompt = """Analyze the provided text from a mental health perspective. Identify any indicators of emotional distress, coping mechanisms, or psychological well-being. Highlight any potential concerns or positive aspects related to mental health, and provide a brief explanation for each observation.
    
    ### Input:
    {}
    
    ### Response:
    {}"""
    
    EOS_TOKEN = tokenizer.eos_token
    def formatting_prompt(examples):
        inputs       = examples["Context"]
        outputs      = examples["Response"]
        texts = []
        for input_, output in zip(inputs, outputs):
            text = data_prompt.format(input_, output) + EOS_TOKEN
            texts.append(text)
        return { "text" : texts, }
    

    格式化训练数据

    training_data = Dataset.from_pandas(filtered_data)
    training_data = training_data.map(formatting_prompt, batched=True)
    

    使用自定义参数和数据进行模型训练

    使用 `sudo apt-get update` 刷新可用软件包列表,并使用 `sudo apt-get install build-essential` 安装基本工具。仅在出现任何错误时在 shell 中运行此命令。

    #sudo apt-get update
    #sudo apt-get install build-essential
    

    训练设置以开始微调!

    1. 训练器初始化

      我们将使用模型和 tokenizer 以及训练数据集初始化 `SFTTrainer`。`dataset_text_field` 参数指定数据集中包含用于训练的文本的字段,我们已在上面准备好该字段。训练器负责管理微调过程,包括数据处理和模型更新。

    2. 训练参数

      `TrainingArguments` 类用于定义训练过程的关键超参数。这些包括

      • `learning_rate=3e-4`:设置优化器的学习率。
      • `per_device_train_batch_size=32`:定义每个设备的批处理大小,优化 GPU 使用率。
      • `num_train_epochs=20`:指定训练 epoch 的数量。
      • `fp16=not is_bfloat16_supported()` 和 `bf16=is_bfloat16_supported()`:启用混合精度训练以减少内存使用,具体取决于硬件支持。
      • `optim="adamw_8bit"`:使用 8 位 AdamW 优化器,实现高效内存使用。
      • `weight_decay=0.01`:应用权重衰减以防止过拟合。
      • `output_dir="output"`:指定保存训练模型和日志的目录。
    3. 训练过程

      最后,我们调用 `trainer.train()` 方法启动训练过程。它使用定义的参数微调模型,调整权重并从提供的数据集中学习。训练器还处理数据打包和梯度累积,优化训练管道以获得更好的性能。

    有时 PyTorch 会保留内存而不释放。设置此环境变量有助于避免内存碎片。您可以在运行模型之前在环境或脚本中设置此变量

    export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True

    如果 GPU 中有不再需要的变量,您可以使用 del 删除它们,然后调用

    torch.cuda.empty_cache().

    trainer=SFTTrainer(
        model=model,
        tokenizer=tokenizer,
        train_dataset=training_data,
        dataset_text_field="text",
        max_seq_length=max_seq_length,
        dataset_num_proc=2,
        packing=True,
        args=TrainingArguments(
            learning_rate=3e-4,
            lr_scheduler_type="linear",
            per_device_train_batch_size=16,
            gradient_accumulation_steps=8,
            num_train_epochs=40,
            fp16=not is_bfloat16_supported(),
            bf16=is_bfloat16_supported(),
            logging_steps=1,
            optim="adamw_8bit",
            weight_decay=0.01,
            warmup_steps=10,
            output_dir="output",
            seed=0,
        ),
    )
    
    trainer.train()
    

    推理

    text="I'm going through some things with my feelings and myself. I barely sleep and I do nothing but think about how I'm worthless and how I shouldn't be here. I've never tried or contemplated suicide. I've always wanted to fix my issues, but I never get around to it. How can I change my feeling of being worthless to everyone?"
    
    注意: 让我们使用微调模型进行推理,以根据心理健康相关提示生成响应!

    以下是一些需要注意的关键点

      `model = FastLanguageModel.for_inference(model)` 将模型专门配置为进行推理,从而优化其生成响应的性能。

      输入文本使用 `tokenizer` 进行标记化,它将文本转换为模型可以处理的格式。我们使用 `data_prompt` 来格式化输入文本,而响应占位符留空以从模型获取响应。`return_tensors = "pt"` 参数指定输出应为 PyTorch 张量,然后将其移动到 GPU(使用 `.to("cuda")`)以加快处理速度。

      `model.generate` 方法根据标记化的输入生成响应。参数 `max_new_tokens = 5020` 和 `use_cache = True` 确保模型可以通过利用前几层的缓存计算来有效地生成长而连贯的响应。

    model = FastLanguageModel.for_inference(model)
    inputs = tokenizer(
    [
        data_prompt.format(
            #instructions
            text,
            #answer
            "",
        )
    ], return_tensors = "pt").to("cuda")
    
    outputs = model.generate(**inputs, max_new_tokens = 5020, use_cache = True)
    answer=tokenizer.batch_decode(outputs)
    answer = answer[0].split("### Response:")[-1]
    print("Answer of the question is:", answer)
    

    问题的答案是

    很抱歉您感到如此不知所措。听起来您正在努力弄清楚自己发生了什么。我建议您去看一位专门治疗抑郁症患者的治疗师。抑郁症是人们普遍遇到的问题。解决抑郁症问题对于改善您的生活质量至关重要。抑郁症可能导致焦虑、绝望和对活动失去乐趣等其他问题。抑郁症也可能导致自杀念头。如果您有自杀念头,请拨打 911 或前往最近的医院急诊室。如果您没有自杀念头,但感到不知所措,请拨打 800-273-8255。这个号码是免费且保密的,您可以与他人谈论任何事情。您也可以访问 www.suicidepreventionlifeline.org 查找当地的自杀预防热线。<|end_of_text|>

    注意: 以下是我们如何安全地将微调模型及其 tokenizer 推送到 Hugging Face Hub,以便任何人都可以使用它。它可以在我的帐户 ImranzamanML/1B_finetuned_llama3.2 上访问
    os.environ["HF_TOKEN"] = "hugging face token key, you can create from your HF account."
    model.push_to_hub("ImranzamanML/1B_finetuned_llama3.2", use_auth_token=os.getenv("HF_TOKEN"))
    tokenizer.push_to_hub("ImranzamanML/1B_finetuned_llama3.2", use_auth_token=os.getenv("HF_TOKEN"))
    

    README.md: 0%| | 0.00/583 [00:00<?, ?B/s] adapter_model.safetensors: 0%| | 0.00/45.1M [00:00<?, ?B/s] 已保存模型至 https://huggingface.co/ImranzamanML/1B_finetuned_llama3.2

    注意: 我们还可以将微调模型及其 tokenizer 本地保存到机器上。
    model.save_pretrained("model/1B_finetuned_llama3.2")
    tokenizer.save_pretrained("model/1B_finetuned_llama3.2")
    

    ('model/1B_finetuned_llama3.2/tokenizer_config.json', 'model/1B_finetuned_llama3.2/special_tokens_map.json', 'model/1B_finetuned_llama3.2/tokenizer.json')

    还在寻找什么吗?
    好的,让我来向您展示如何加载您保存的模型并使用它!
    model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = "model/1B_finetuned_llama3.2",
    max_seq_length = 5020,
    dtype = None,
    load_in_4bit = True)
    

    天哪,还在找东西吗?😄 别担心!您可以使用上面的提示格式和代码来获得心灵平静的响应 🧠✨

    很高兴联系 😊

    穆罕默德·伊姆兰·扎曼

    Kaggle Profile LinkedIn Profile Google Scholar Profile YouTube Channel GitHub Profile Medium Profile Hugging Face Profile

社区

这篇博客看起来很棒。我会仔细研读它。

我在 trainer=SFTTrainer(...) 命令中遇到了错误(TypeError: SFTTrainer.__init__() got an unexpected keyword argument 'dataset_text_field')。我能够使用此 GitHub 问题中的反馈解决该问题:https://github.com/unslothai/unsloth/issues/1264

·

嗨,您可以删除 dataset_text_field="text" 或更新 "pip install -q trl==0.12.0"
我之前微调过这个模型,当时 unsloth 需要 dataset_text_field。谢谢

运行此代码微调模型后,它会生成此输出

回应
回应
回应
...

·
文章作者

@Sheikhaei 看看这一行
answer = answer[0].split("### Response:")[-1]

通过打印 "answer" 来仔细查看

注册登录 发表评论