llama-factory实战: 基于qwen2.5-7b 手把手实战 自定义数据集清洗 微调

news2025/1/22 23:28:33

基于qwen2.5 手把手实战 自定义数据集 微调(llama-factory)

    • 准备工作
      • 1.数据集准备(例:民法典.txt)
      • 2.服务器准备(阿里云 DSW 白嫖)
      • 3.环境配置
        • pip 升级
        • 模型下载
        • 微调助手
      • 4.数据集处理
        • 脚本文件
        • 4.1文本分割(bert-base-chinese)
        • 4.2数据集生成
        • 4.3.1数据集转换(只有一个数据集)alpaca格式
        • 4.3.2训练集验证集划分(两个数据集)alpaca格式
      • 5.开始微调
        • 5.1配置文件修改
        • 5.2进入微调ui
        • 5.3修改参数即可训练
        • 5.4训练结果
      • 6.测试评估
      • 7.模型对话
      • 8.模型合并
      • 9.模型量化

准备工作

1.数据集准备(例:民法典.txt)

《中华人民共和国民法典 》全文免费下载 - 知乎 (zhihu.com)

自行转txt格式(约11万字)

当然你可以用我清洗了的数据集
https://modelscope.cn/datasets/Zzcc10/Civil_Code

2.服务器准备(阿里云 DSW 白嫖)

创建自己的实例(选择对应环境镜像约24g多)

https://free.aliyun.com/?spm=a2c4g.11174283.0.0.46a0527f6LNsEe&productCode=learn

3.环境配置

pip 升级
!pip install --upgrade pip 
模型下载

git 下载

!git lfs install
!git clone https://www.modelscope.cn/Qwen/Qwen2.5-7B-Instruct.git
微调助手

llama-factory

!git clone https://github.com/hiyouga/LLaMA-Factory.git
%cd LLaMA-Factory
!pip install -e .

4.数据集处理

注:若缺失部分环境pip install 安装即可,此处不在强调

脚本文件
4.1文本分割(bert-base-chinese)

bert-base-chinese 下载到本地

 git clone https://www.modelscope.cn/tiansz/bert-base-chinese.git
import torch
from transformers import BertTokenizer, BertModel
import re
import os
from scipy.spatial.distance import cosine


def get_sentence_embedding(sentence, model, tokenizer):
    """
    获取句子的嵌入表示

    参数:
    sentence (str): 输入句子
    model (BertModel): 预训练的BERT模型
    tokenizer (BertTokenizer): BERT分词器

    返回:
    numpy.ndarray: 句子的嵌入向量
    """
    # 使用分词器处理输入句子,并转换为模型输入格式
    inputs = tokenizer(sentence, return_tensors="pt", padding=True, truncation=True, max_length=512)
    # 使用模型获取输出,不计算梯度
    with torch.no_grad():
        outputs = model(**inputs)
    # 返回最后一层隐藏状态的平均值作为句子嵌入
    return outputs.last_hidden_state.mean(dim=1).squeeze().numpy()


def split_text_by_semantic(text, max_length, similarity_threshold=0.5):
    """
    基于语义相似度对文本进行分块

    参数:
    text (str): 输入的长文本
    max_length (int): 每个文本块的最大长度(以BERT分词器的token为单位)
    similarity_threshold (float): 语义相似度阈值,默认为0.5

    返回:
    list: 分割后的文本块列表
    """
    # 加载BERT模型和分词器
    tokenizer = BertTokenizer.from_pretrained('/mnt/workspace/dataset/bert-base-chinese')
    model = BertModel.from_pretrained('/mnt/workspace/dataset/bert-base-chinese')
    model.eval()  # 设置模型为评估模式

    # 按句子分割文本(使用常见的中文标点符号)
    sentences = re.split(r'(。|!|?|;)', text)
    # 重新组合句子和标点
    sentences = [s + p for s, p in zip(sentences[::2], sentences[1::2]) if s]

    chunks = []
    current_chunk = sentences[0]
    # 获取当前chunk的嵌入表示
    current_embedding = get_sentence_embedding(current_chunk, model, tokenizer)

    for sentence in sentences[1:]:
        # 获取当前句子的嵌入表示
        sentence_embedding = get_sentence_embedding(sentence, model, tokenizer)
        # 计算当前chunk和当前句子的余弦相似度
        similarity = 1 - cosine(current_embedding, sentence_embedding)

        # 如果相似度高于阈值且合并后不超过最大长度,则合并
        if similarity > similarity_threshold and len(tokenizer.tokenize(current_chunk + sentence)) <= max_length:
            current_chunk += sentence
            # 更新当前chunk的嵌入表示
            current_embedding = (current_embedding + sentence_embedding) / 2
        else:
            # 否则,保存当前chunk并开始新的chunk
            chunks.append(current_chunk)
            current_chunk = sentence
            current_embedding = sentence_embedding

    # 添加最后一个chunk
    if current_chunk:
        chunks.append(current_chunk)

    return chunks


def read_text_file(file_path):
    """
    读取文本文件

    参数:
    file_path (str): 文件路径

    返回:
    str: 文件内容
    """
    with open(file_path, 'r', encoding='utf-8') as file:
        return file.read()


def save_chunks_to_files(chunks, output_dir):
    """
    将分割后的文本块保存到文件

    参数:
    chunks (list): 文本块列表
    output_dir (str): 输出目录路径
    """
    # 如果输出目录不存在,则创建
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)

    # 将每个文本块保存为单独的文件
    for i, chunk in enumerate(chunks):
        chunk_file_path = os.path.join(output_dir, f"chunk_{i + 1}.txt")
        with open(chunk_file_path, 'w', encoding='utf-8') as file:
            file.write(chunk)
        print(f"已保存第 {i + 1} 个文本块到 {chunk_file_path}")


# 主程序

# 设置输入和输出路径
input_file_path = './test.txt'  # 替换为你的长文本文件路径
output_dir = './saveChunk/'  # 替换为你希望保存文本块的目录路径

# 读取长文本
long_text = read_text_file(input_file_path)

# 设置每个文本块的最大分词数量和相似度阈值
max_length = 2048  # 可根据需要调整
similarity_threshold = 0.5  # 可根据需要调整

# 分割长文本
text_chunks = split_text_by_semantic(long_text, max_length, similarity_threshold)

# 保存分割后的文本块到指定目录
save_chunks_to_files(text_chunks, output_dir)
4.2数据集生成

如何获取API-KEY_大模型服务平台百炼(Model Studio)-阿里云帮助中心 (aliyun.com)

获取api 选择所需模型即可

model=“qwen-max-latest”, # 修改自己选择的 模型

import json
import os
import time
import re
from typing import List, Dict
from openai import OpenAI
import logging
import backoff
import pyarrow as pa
import pyarrow.parquet as pq
from concurrent.futures import ThreadPoolExecutor, as_completed

# 设置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

# 从环境变量中获取 API 密钥
api_key = "???"

# 初始化 OpenAI 客户端
client = OpenAI(base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key=api_key)

def read_file(file_path: str) -> str:
    with open(file_path, 'r', encoding='utf-8') as file:
        return file.read()

@backoff.on_exception(backoff.expo, Exception, max_tries=3)
def generate_single_entry(text: str) -> Dict:
    prompt = f"""
    基于以下文本,生成1个用于指令数据集的高质量条目。条目应该直接关联到给定的文本内容,提出相关的问题或任务。
    请确保生成多样化的指令类型,例如:
    - 分析类:"分析..."
    - 比较类:"比较..."
    - 解释类:"解释..."
    - 评价类:"评价..."
    - 问答类:"为什么..."

    文本内容:
    {text}

    请以下面的格式生成条目,确保所有字段都有适当的内容:
    {{
        "instruction": "使用上述多样化的指令类型之一,提出一个具体的、与文本相关的问题或任务",
        "input": "如果需要额外的上下文信息,请在这里提供,否则留空",
        "output": "对instruction的详细回答或任务的完成结果"
    }}
    确保所有生成的内容都与给定的文本直接相关,生成的是有效的JSON格式,并且内容高质量、准确、详细。
    """

    try:
        response = client.chat.completions.create(
            model="qwen-plus-0919",  # 尝试使用自己选择的 模型
            messages=[{"role": "user", "content": prompt}],
            temperature=0.7,  # 增加温度以提高多样性
            max_tokens=4098
        )
        logger.info(f"API 响应: {response.choices[0].message.content}")

        json_match = re.search(r'\{.*\}', response.choices[0].message.content, re.DOTALL)
        if json_match:
            entry = json.loads(json_match.group())
            required_keys = ['instruction', 'input', 'output']
            if isinstance(entry, dict) and all(key in entry for key in required_keys):
                # 根据 input 是否为空来设置 text 字段
                if entry['input'].strip():
                    entry[
                        'text'] = f"Below is an instruction that describes a task, paired with an input that provides further context. Write a response that appropriately completes the request.### Instruction: {entry['instruction']}\n### Input: {entry['input']}\n### Response: {entry['output']}"
                else:
                    entry[
                        'text'] = f"Below is an instruction that describes a task. Write a response that appropriately completes the request.### Instruction: {entry['instruction']}\n### Input: {entry['input']}\n### Response: {entry['output']}"

                logger.info("成功生成完整条目")
                return entry
            else:
                logger.warning("JSON 解析成功,但缺少必要字段")
                return {}
        else:
            logger.error("无法从API响应中提取有效的JSON")
            return {}

    except Exception as e:
        logger.error(f"生成条目时发生错误: {str(e)}")
        return {}

def process_file(file_path: str, entries_per_file: int) -> List[Dict]:
    dataset = []
    try:
        text = read_file(file_path)
        for j in range(entries_per_file):
            logger.info(f"  生成第 {j + 1}/{entries_per_file} 个条目")
            entry = generate_single_entry(text)
            if entry and all(key in entry for key in ['instruction', 'input', 'output', 'text']):
                dataset.append(entry)
                logger.info(f"  成功生成 1 个完整条目")
            else:
                logger.warning(f"  跳过不完整的条目")
            time.sleep(2)  # 在请求之间增加延迟到2秒
    except Exception as e:
        logger.error(f"处理文件 {file_path} 时发生未知异常: {str(e)}")
    return dataset

def generate_dataset(folder_path: str, entries_per_file: int = 2) -> List[Dict]:
    dataset = []
    files = [os.path.join(folder_path, filename) for filename in os.listdir(folder_path) if filename.endswith(".txt")]

    with ThreadPoolExecutor(max_workers=4) as executor:  # 调整 max_workers 数量以适应你的硬件资源
        futures = [executor.submit(process_file, file_path, entries_per_file) for file_path in files]
        for future in as_completed(futures):
            try:
                dataset.extend(future.result())
            except Exception as e:
                logger.error(f"处理未来任务时发生未知异常: {str(e)}")

    return dataset

def save_dataset_as_parquet(dataset: List[Dict], output_file: str):
    schema = pa.schema([
        ('instruction', pa.string()),
        ('input', pa.string()),
        ('output', pa.string()),
        ('text', pa.string())
    ])

    arrays = [
        pa.array([entry['instruction'] for entry in dataset]),
        pa.array([entry['input'] for entry in dataset]),
        pa.array([entry['output'] for entry in dataset]),
        pa.array([entry['text'] for entry in dataset])
    ]

    table = pa.Table.from_arrays(arrays, schema=schema)
    pq.write_table(table, output_file)

if __name__ == "__main__":
    input_folder = "./saveChunk"  # 指定输入文件夹路径
    output_file = "instruction_dataset.parquet"

    logger.info("开始生成数据集")
    dataset = generate_dataset(input_folder, entries_per_file=10)
    save_dataset_as_parquet(dataset, output_file)
    logger.info(f"数据集已生成并保存到 {output_file}")
    logger.info(f"共生成 {len(dataset)} 个有效条目")
4.3.1数据集转换(只有一个数据集)alpaca格式
import json
import pandas as pd
from rich import print

# Load local Parquet dataset
parquet_file_path = '/mnt/workspace/dataset/instruction_dataset.parquet'
dataset = pd.read_parquet(parquet_file_path)

# Print the columns of the dataset to verify
print("Dataset Columns:", dataset.columns)

# Convert dataset to list of dictionaries
json_data = dataset.to_dict(orient='list')

# Print the first row to verify
print("First Row:", {key: json_data[key][0] for key in json_data})

# Initialize list to store Alpaca format data
alpaca_data = []

# Process each entry in the dataset
for i in range(len(json_data['instruction'])):
    instruction = json_data['instruction'][i]
    input_text = json_data['input'][i]
    original_output = json_data['output'][i]

    # Create Alpaca format entry
    alpaca_entry = {
        "instruction": instruction,
        "input": input_text,
        "output": original_output
    }
    alpaca_data.append(alpaca_entry)

# Save in Alpaca format
with open("alpaca_dataset.json", "w") as file:
    json.dump(alpaca_data, file, indent=4)

print("Alpaca format dataset saved as 'alpaca_dataset.json'")
4.3.2训练集验证集划分(两个数据集)alpaca格式
import json
import pandas as pd
from rich import print
from sklearn.model_selection import train_test_split

# Load local Parquet dataset
parquet_file_path = '/mnt/workspace/dataset/instruction_dataset.parquet'
dataset = pd.read_parquet(parquet_file_path)

# Print the columns of the dataset to verify
print("Dataset Columns:", dataset.columns)

# Convert dataset to list of dictionaries
json_data = dataset.to_dict(orient='list')

# Print the first row to verify
print("First Row:", {key: json_data[key][0] for key in json_data})

# Initialize lists to store Alpaca format data
alpaca_data = []

# Process each entry in the dataset
for i in range(len(json_data['instruction'])):
    instruction = json_data['instruction'][i]
    input_text = json_data['input'][i]
    original_output = json_data['output'][i]

    # Create Alpaca format entry
    alpaca_entry = {
        "instruction": instruction,
        "input": input_text,
        "output": original_output
    }
    alpaca_data.append(alpaca_entry)

# Split the dataset into training and testing sets (80% training, 20% testing)
train_data, test_data = train_test_split(alpaca_data, test_size=0.2, random_state=42)

# Save training set in Alpaca format
with open("alpaca_train_dataset.json", "w") as file:
    json.dump(train_data, file, indent=4)

# Save testing set in Alpaca format
with open("alpaca_test_dataset.json", "w") as file:
    json.dump(test_data, file, indent=4)

print("Alpaca format training dataset saved as 'alpaca_train_dataset.json'")
print("Alpaca format testing dataset saved as 'alpaca_test_dataset.json'")

5.开始微调

5.1配置文件修改

LLaMA-Factory/data/dataset_info.json

LLaMA-Factory/data/dataset_info.json

同时alpaca_train_dataset.json和alpaca_test_dataset.json放入LLaMA-Factory/data/路径

  "train": {
    "file_name": "alpaca_train_dataset.json"
  },
  "test": {
    "file_name": "alpaca_test_dataset.json"
  },
5.2进入微调ui

需要进入对应文件夹下

%cd LLaMA-Factory
!llamafactory-cli webui

注释:若出现连接失败,尝试配置环境变量

!GRADIO_ROOT_PATH=/${JUPYTER_NAME}/proxy/7860/ \
USE_MODELSCOPE_HUB=1 \
llamafactory-cli webui
5.3修改参数即可训练

模型路径使用绝对路径即可

/mnt/workspace/Qwen2.5-7B-Instruct

注:具体调整参数需自己测试

在这里插入图片描述

5.4训练结果

在这里插入图片描述

6.测试评估

注:可能需要安装(出现错误根据提示进行修改即可)

pip install rouge-chinese

在这里插入图片描述
在这里插入图片描述

7.模型对话

chat模式加载模型

若显存不够则使用huggingface推理(vllm加载速度更快)
在这里插入图片描述

8.模型合并

合并不能进行量化操作

9.模型量化

需合并了再量化选择所需量化等级即可(24g显存可能不够)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2280597.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微透镜阵列精准全检,白光干涉3D自动量测方案提效70%

广泛应用的微透镜阵列 微透镜是一种常见的微光学元件&#xff0c;通过设计微透镜&#xff0c;可对入射光进行扩散、光束整形、光线均分、光学聚焦、集成成像等调制&#xff0c;进而实现许多传统光学元器件难以实现的特殊功能。 微透镜阵列&#xff08;Microlens Array&#x…

详解单片机学的是什么?(电子硬件)

大家好&#xff0c;我是山羊君Goat。 单片机&#xff0c;对于每一个硬件行业的从业者或者在校电子类专业的学生&#xff0c;相信对于这个名词都不陌生&#xff0c;但是掌没掌握就另说了。 那单片机到底学的是什么呢&#xff1f; 其实单片机在生活中就非常常见&#xff0c;目前…

基于Docker的Spark分布式集群

目录 1. 说明 2. 服务器规划 3. 步骤 3.1 要点 3.2 配置文件 3.2 访问Spark Master 4. 使用测试 5. 参考 1. 说明 以docker容器方式实现apache spark计算集群&#xff0c;能灵活的增减配置与worker数目。 2. 服务器规划 服务器 (1master, 3workers) ip开放端口备注ce…

9. 神经网络(一.神经元模型)

首先&#xff0c;先看一个简化的生物神经元结构&#xff1a; 生物神经元有多种类型&#xff0c;内部也有复杂的结构&#xff0c;但是可以把单个神经元简化为3部分组成&#xff1a; 树突&#xff1a;一个神经元往往有多个树突&#xff0c;用于接收传入的信息。轴突&#xff1a;…

web-view环境下,H5页面打开其他小程序

在Web-view环境下&#xff0c;H5页面无法直接打开其他小程序。正确的实现方式是先从H5页面跳转回当前小程序&#xff0c;再由当前小程序跳转到目标小程序。具体实现方法如下&#xff1a; H5页面跳转回小程序时&#xff0c;调用wx.miniProgram.navigateTo()方法。 小程序跳转到…

数据恢复常用方法(三)如何辨别固态硬盘故障类型

数据恢复首先需要辨别固态硬盘故障类型&#xff0c;只有先确认故障类型&#xff0c;才能进行下一步动作 如下是一种常见的场景&#xff0c;固态硬盘无法识别&#xff0c;接入电源与数据线&#xff0c;电脑的磁盘管理不显示任何信息。 第一步&#xff1a;确认硬件状态&#xff…

Android Studio打包APK

1.导出APK安装包 如果是首次打包&#xff0c;Create new 单击蓝色对话框右边文件夹&#x1f4c2;图标 &#xff0c;选择密钥保存路径&#xff0c;然后在下方File name对话框中填写您想要名称&#xff0c;再点击OK回到密钥创建对话框。 在此对话框中填写密码&#xff08;Passwo…

当使用 npm 时,出现 `certificate has expired` 错误通常意味着请求的证书已过期。

当使用 npm 时&#xff0c;出现 certificate has expired 错误通常意味着请求的证书已过期。这可能是由于以下几种情况&#xff1a; 网络代理问题&#xff1a;如果使用了网络代理&#xff0c;代理服务器的证书可能过期或配置有误。系统时间错误&#xff1a;系统时间不准确可能导…

AWS IAM用户启用MFA认证

1. 进入IAM界面&#xff0c;点击Add user 2. 创建用户 3. 添加用户权限&#xff0c;例如这里赋予power user权限 4. 用手机下载MFA软件&#xff0c;进入App Store搜索mfa即可得到Google Authenticator&#xff0c;点击安装 5. 用户创建后&#xff0c;进入点击用户名进入Securi…

学习ASP.NET Core的身份认证(基于JwtBearer的身份认证8)

为进一步测试通过请求头传递token进行身份验证&#xff0c;在main.htm中增加layui的数据表格组件&#xff0c;并调用后台服务分页显示数据&#xff0c;后台分页查询数据接口如下所示&#xff08;测试时&#xff0c;直接将数据写死到代码中&#xff0c;没有查询数据库&#xff0…

Python新春烟花

目录 系列文章 写在前面 技术需求 完整代码 下载代码 代码分析 1. 程序初始化与显示设置 2. 烟花类 (Firework) 3. 粒子类 (Particle) 4. 痕迹类 (Trail) 5. 烟花更新与显示 6. 主函数 (fire) 7. 游戏循环 8. 总结 注意事项 写在后面 系列文章 序号直达链接爱…

【leetcode 24】151.翻转字符串里的单词==❗没看懂❗==

思路&#xff1a; 一些同学会使用split库函数&#xff0c;分隔单词&#xff0c;然后定义一个新的string字符串&#xff0c;最后再把单词倒序相加&#xff0c;那么这道题题目就是一道水题了&#xff0c;失去了它的意义。 所以这里我还是提高一下本题的难度&#xff1a;不要使用辅…

STM32+W5500+以太网应用开发+003_TCP服务器添加OLED(u8g2)显示状态

STM32W5500以太网应用开发003_TCP服务器添加OLED&#xff08;u8g2&#xff09;显示状态 实验效果3-TCP服务器OLED1 拷贝显示驱动代码1.1 拷贝源代码1.2 将源代码添加到工程1.3 修改代码优化等级1.4 添加头文件路径1.5 修改STM32CubeMX工程 2 修改源代码2.1 添加头文件2.2 main函…

Oracle审计

审计是监控选定的用户数据库操作的过程 审计的目的&#xff1a; 调查可疑的数据库活动&#xff1a; 审计可以帮助检测和跟踪潜在的 security breaches、未授权的访问尝试或其他异常行为。通过分析审计日志&#xff0c;可以确定可疑活动的来源、时间、频率和影响。 收集特定数…

智能新浪潮:亚马逊云科技发布Amazon Nova模型

在2024亚马逊云科技re:Invent全球大会上&#xff0c;亚马逊云科技宣布推出新一代基础模型Amazon Nova&#xff0c;其隶属于Amazon Bedrock&#xff0c;这些模型精准切入不同领域&#xff0c;解锁多元业务可能&#xff0c;为人工智能领域带来革新。 带你认识一起了解Amazon Nova…

2025 OWASP十大智能合约漏洞

随着去中心化金融&#xff08;DeFi&#xff09;和区块链技术的不断发展&#xff0c;智能合约安全的重要性愈发凸显。在此背景下&#xff0c;开放网络应用安全项目&#xff08;OWASP&#xff09;发布了备受期待的《2025年智能合约十大漏洞》报告。 这份最新报告反映了不断演变的…

力扣面试经典题

目录 前言 一、合并两个有序数组 二、移除元素 三、删除有序数组的重复项 四、删除有序数组的重复项Ⅱ 五、取数组中出现次数大于数组长度/2的元素 六、移动数组元素 七、计算数组中相差最大的值 八、字母异位词分组 九、最长连续序列 十、移动0 十一、盛水最多的容…

【RabbitMq】RabbitMq高级特性-延迟消息

延迟消息 什么是延迟消息死信交换机延迟消息插件-DelayExchange其他文章 什么是延迟消息 延迟消息&#xff1a;发送者发送消息时指定一个时间&#xff0c;消费者不会立刻收到消息&#xff0c;而是在指定时间之后才收到消息。 延迟任务&#xff1a;设置在一定时间之后才执行的任…

抖音小程序一键获取手机号

前端代码组件 <button v-if"!isFromOrderList"class"get-phone-btn" open-type"getPhoneNumber"getphonenumber"onGetPhoneNumber">一键获取</button>// 获取手机号回调onGetPhoneNumber(e) {var that this tt.login({f…

CSS:语法、样式表、选择器

目录 一、语法 二、创建 外部样式表 内部样式表 内联样式 三、选择器 ID选择器 类选择器 伪类选择器 :hover a:link a:active a:visited 属性选择器 伪元素选择器 ::first-letter ::first-line ::selection ::placeholder ::before 和::after 通配选择器 标…