【大模型指令微调: 从零学会炼丹】第二章: 数据集预处理

大模型指令微调: 从零学会炼丹

系列目录

第一章: 微调数据集构建
第二章: 数据集预处理
第三章: Q-LoRa微调Phi-3.5-mini

第二章: 数据集预处理

环境准备

pip install datasets transformers pandas duckdb functools

导入包

from datasets import Dataset
from transformers import (
    AutoTokenizer,
    set_seed
)
import pandas as pd
import os
import duckdb
from functools import partial

seed = 42
set_seed(seed)

os.environ['HTTP_PROXY'] = 'http://127.0.0.1:7890'
os.environ['HTTPS_PROXY'] = 'http://127.0.0.1:7890'

代码中的HTTP_PROXYHTTPS_PROXY设置了代理用于从huggingface 中下载模型文件

读取数据

通常会选择使用pandas 直接读取或者处理数据集, 博主这里选择了最近比较火爆的duckdb, duckdb 可以通过SQL读取或编辑dataframe中的数据

# 从 duckdb 数据库中读取数据
# 连接到 duckdb 数据库
conn = duckdb.connect('./preprocess/data_labeler.db')

# 执行查询以获取数据
df = conn.execute("SELECT * FROM labeled_data_after_augmentation").fetchdf()

# 关闭数据库连接
conn.close()

dataset = Dataset.from_pandas(df)

创建预处理函数和辅助函数

创建预处理函数和一系列辅助函数, 用于将数据集处理为LLM可以理解形式, 包括数据处理和序列化.

创建指令微调模版

创建一个create_prompt_template辅助函数, 确保数据集适用于微调场景, 将数据集中的input 和output显式转换为LLM的指令.

#格式化数据集为对LLM 的显示指令
def create_prompt_template(data):
    INTRO_BLURB = "Below is an instruction that describes a task. Write a response that appropriately completes the request."
    INPUT_KEY = "### Input:"
    RESPONSE_KEY = "### Output:"
    END_KEY = "### End"

    blurb = f"\n{INTRO_BLURB}\n{instruction_key}"
    input = f"{INPUT_KEY}\n{data['input']}"
    response = f"{RESPONSE_KEY}\n{data['output']}"
    end = f"{END_KEY}"

    parts = [part for part in [blurb, input, response, end] if part]

    formatted_prompt = "\n\n".join(parts)
    data["text"] = formatted_prompt

    return data
获取模型支持的最大大小

通过读取模型的config 信息, 读取模型支持的最长文本

def get_max_length(model):
    conf = model.config
    max_length = None
    for length_setting in ["n_positions", "max_position_embeddings", "seq_length"]:
        max_length = getattr(model.config, length_setting, None)
        if max_length:
            print(f"Found max lenth: {max_length}")
            break
    if not max_length:
        max_length = 1024
        print(f"Using default max length: {max_length}")
    return max_length
序列化辅助函数

定义辅助函数调用tokenizer进行序列化

def preprocess_batch(batch, tokenizer, max_length):
    return tokenizer(
        batch["text"],
        max_length=max_length,
        truncation=True,
    )
预处理函数

调用前面定义的辅助函数, 后续使用本函数处理dataset

def preprocess_dataset(tokenizer: AutoTokenizer, max_length: int, seed, dataset):
    """Format & tokenize it so it is ready for training
    :param tokenizer (AutoTokenizer): Model Tokenizer
    :param max_length (int): Maximum number of tokens to emit from tokenizer
    """

    # Add prompt to each sample
    print("Preprocessing dataset...")
    dataset = dataset.map(create_prompt_template)

    # Apply preprocessing to each batch of the dataset & and remove 'instruction', 'context', 'response', 'category' fields
    _preprocessing_function = partial(preprocess_batch, max_length=max_length, tokenizer=tokenizer)

    
    # 将input_ids < max_length 的样本保存到xlsx 文件
    dataset = dataset.map(
        _preprocessing_function,
        batched=True,
    )
    #将input_ids > max_length 的样本保存到xlsx 文件
    df = dataset.filter(lambda sample: len(sample["input_ids"]) >= max_length).to_pandas()
    df = df[['id','input','output']]
    df.to_excel('long_samples.xlsx', index=False)

    #从dataset中删除'id','input','output','timestamp'
    dataset = dataset.remove_columns(['id','input','output','timestamp'])
    # Filter out samples that have input_ids exceeding max_length
    dataset = dataset.filter(lambda sample: len(sample["input_ids"]) < max_length)
    # Shuffle dataset
    dataset = dataset.shuffle(seed=seed)

    return dataset

由于后续使用时是手动设置的max_length,这段函数会将超出max_length 的数据输出到本地, 需要观察下数据占比

加载tokenizer

model_id = "microsoft/Phi-3.5-mini-instruct"


# 创建tokenizer
tokenizer = AutoTokenizer.from_pretrained(
    model_id,
    trust_remote_code=True,
    padding_side="left",
    add_eos_token=True,
    add_bos_token=True,
    use_fast=False
)

处理数据

max_length = 4096
dataset = preprocess_dataset(tokenizer, 4096, seed, dataset)

# 将处理后的数据集保存到本地文件
dataset.save_to_disk('./preprocess/data/processed_dataset')

设置max_length, 并调用preprocess_dataset 处理数据, 处理后将其保存到本地, 这样后续可以直接使用dataset.load_from_disk 加载数据湖


避坑

不要直接使用max_length函数的结果

我所训练的模型为 phi-3.5-mini,硬件采用的是 4090D 24GB。理论上,结合 qlora 技术,显存应该是足够使用的。然而,实际情况却是一直出现显存溢出的问题。
究其原因,目前最新的 LLM 的 max_length 普遍设置为 128K,这一设置会极大地增加对显存的需求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值