NeMo Curator 整理用于 LLM 参数高效微调的自定义数据集

目录

概述

预备知识

定义自定义文档构建器

下载数据集

解析和迭代数据集

将数据集写入 JSONL 格式

使用文档构建器加载数据集

使用现有工具统一 Unicode 格式

设计自定义数据集过滤器

编辑所有个人识别信息

添加指令提示

整合管线


概述

出于演示目的,本文重点介绍一个涉及电子邮件分类的玩具示例。目标是整理一个基于文本的小型数据集,其中每个记录都包含电子邮件(主题和正文)以及该电子邮件的预定义分类标签。

为此,我们使用了 Enron 电子邮件数据集,将每封电子邮件标记为八个类别之一。此数据集可在 Hugging Face 上公开获取,并包含约 1400 条记录。

数据管护流程涉及以下高级步骤:

  1. 定义下载器、迭代器和提取器类,将数据集转换为 JSONL 格式。
  2. 使用现有工具统一 Unicode 表示。
  3. 定义自定义数据集过滤器,以删除空白或过长的电子邮件。
  4. 编辑数据集中的所有个人识别信息 (PII)。
  5. 为每条记录添加指令提示。
  6. 整合整个管线。

在消费级硬件上执行此策展制作流程需要不到 5 分钟的时间。要访问本教程的完整代码,请参阅 NVIDIA/NeMo-Curator GitHub 资源库。

预备知识

开始之前,您必须安装 NeMo Curator 框架。按照 NeMo Curator GitHub README 文件中的说明安装该框架。

接下来,运行以下命令以验证安装并安装任何其他依赖项:

$ python -c "import nemo_curator; print(nemo_curator);"
$ pip3 install requests

定义自定义文档构建器

整理数据集的第一步是实现文档构建器,以便下载并迭代数据集。

下载数据集

实现DocumentDownloader类获取数据集的 URL,并使用requests库。

import requests
from nemo_curator.download.doc_builder import DocumentDownloader

class EmailsDownloader(DocumentDownloader):
    def __init__(self, download_dir: str):
        super().__init__()

        if not os.path.isdir(download_dir):
            os.makedirs(download_dir)

        self._download_dir = download_dir
        print("Download directory: ", self._download_dir)

    def download(self, url: str) -> str:
        filename = os.path.basename(url)
        output_file = os.path.join(self._download_dir, filename)

        if os.path.exists(output_file):
            print(f"File '{output_file}' already exists, skipping download.")
            return output_file

        print(f"Downloading Enron emails dataset from '{url}'...")
        response = requests.get(url)

        with open(output_file, "wb") as file:
            file.write(response.content)

        return output_file

下载的数据集是一个文本文件,每个条目大致遵循以下格式:

“<s>[system instruction prompts]

Subject:: [email subject]
Body:: [email body]

[category label] <s>”

您可以使用正则表达式轻松地将这种格式分解为其组成部分。要记住的关键是,条目由“<s> … <s>”并且始终以指令提示开始。此外,示例分隔符令牌和系统提示令牌与 Llama 2 标记器系列兼容。

由于您可能会将这些数据与不支持特殊令牌的其他分词器或模型一起使用,因此最好在解析期间丢弃这些指令和令牌。在本文的稍后部分中,我们将展示如何使用 NeMo Curator 将指令提示或特殊令牌添加到每个条目中DocumentModifier实用程序。

解析和迭代数据集

实现DocumentIteratorDocumentExtractor用于提取电子邮件主题、正文和类别 (类) 标签的类:

from nemo_curator.download.doc_builder import (
    DocumentExtractor,
    DocumentIterator,
)

class EmailsIterator(DocumentIterator):

    def __init__(self):
        super().__init__()
        self._counter = -1
        self._extractor = EmailsExtractor()
        # The regular expression pattern to extract each email.
        self._pattern = re.compile(r"\"<s>.*?<s>\"", re.DOTALL)

    def iterate(self, file_path):
        self._counter = -1
        file_name = os.path.basename(file_path)

        with open(file_path, "r", encoding="utf-8") as file:
            lines = file.readlines()

        # Ignore the first line which contains the header.
        file_content = "".join(lines[1:])
        # Find all the emails in the file.
        it = self._pattern.finditer(file_content)

        for email in it:
            self._counter += 1
            content = email.group().strip('"').strip()
            meta = {
                "filename": file_name,
                "id": f"email-{self._counter}",
            }
            extracted_content = self._extractor.extract(content)

            # Skip if no content extracted
            if not extracted_content:
                continue

            record = {**meta, **extracted_content}
            yield record


class EmailsExtractor(DocumentExtractor):
    def __init__(self):
        super().__init__()
        # The regular expression pattern to extract subject/body/label into groups.
        self._pattern = re.compile(
            r"Subject:: (.*?)\nBody:: (.*?)\n.*\[/INST\] (.*?) <s>", re.DOTALL
        )

    def extract(self, content: str) -> Dict[str, str]:
        matches = self._pattern.findall(content)

        if not matches:
            return None

        matches = matches[0]

        return {
            "subject": matches[0].strip(),
            "body": matches[1].strip(),
            "category": matches[2].strip(),
        }

迭代器使用正则表达式,\"<s>.*?<s>\"然后,它将字符串传递给提取器,提取器使用正则表达式"Subject:: (.*?)\nBody:: (.*?)\n.*\[/INST\] (.*?) <s>"此表达式使用分组运算符(.*?)提取主题、正文和类别。

这些提取的部分以及有用的元数据(例如每封电子邮件的唯一 ID)存储在字典中,并返回给调用者。

现在,您可以将此数据集转换为 JSONL 格式,这是 NeMo Curator 支持的多种格式之一

将数据集写入 JSONL 格式

数据集以纯文本文件的形式下载。DocumentIteratorDocumentExtractor用于迭代记录的类,将其转换为 JSONL 格式,并将每条记录作为一行存储在文件中。

import json

def download_and_convert_to_jsonl() -> str:
    """
    Downloads the emails dataset and converts it to JSONL format.

    Returns:
        str: The path to the JSONL file.
    """

    # Download the dataset in raw format and convert it to JSONL.
    downloader = EmailsDownloader(DATA_DIR)
    output_path = os.path.join(DATA_DIR, "emails.jsonl")
    raw_fp = downloader.download(DATASET_URL)

    iterator = EmailsIterator()

    # Parse the raw data and write it to a JSONL file.
    with open(output_path, "w") as f:
        for record in iterator.iterate(raw_fp):
            json_record = json.dumps(record, ensure_ascii=False)
            f.write(json_record + "\n")

    return output_path

数据集中每条记录的信息都写入多个 JSON 字段:

  • subject
  • body
  • category
  • Metadata:
    • id
    • filename

这一点很有必要,因为 NeMo Curator 中的许多数据管护操作必须知道要在每个记录中操作哪个字段。这一结构允许 NeMo Curator 操作轻松地定位不同的数据集信息。

使用文档构建器加载数据集

在 NeMo Curator 中,数据集表示为类型对象DocumentDataset.这提供了从磁盘加载各种格式的数据集的辅助工具。使用以下代码加载数据集并开始使用:

from nemo_curator.datasets import DocumentDataset
# define `filepath` to be the path to the JSONL file created above.
dataset = DocumentDataset.read_json(filepath, add_filename=True)

您现在拥有了定义自定义数据集策管线和准备数据所需的一切。

使用现有工具统一 Unicode 格式

通常最好修复数据集中的所有 Unicode 问题,因为从在线来源抓取的文本可能包含不一致或 Unicode 错误。

为了修改文档,NeMo Curator 提供了一个DocumentModifier界面以及Modify辅助程序,用于定义如何修改每个文档中的给定文本。有关实现您自己的自定义文档修改器的更多信息,请参阅文本清理和统一在上一篇文章中看到的部分内容。

在本示例中,应用UnicodeReformatter到数据集。由于每条记录都有多个字段,因此请对数据集中的每个相关字段应用一次操作。这些操作可以通过Sequential类:

Sequential([
    Modify(UnicodeReformatter(), text_field="subject"),
    Modify(UnicodeReformatter(), text_field="body"),
    Modify(UnicodeReformatter(), text_field="category"),
])

设计自定义数据集过滤器

在许多 PEFT 用例中,优化数据集涉及过滤掉可能无关紧要或质量较低的记录,或者那些具有特定不合适属性的记录。在电子邮件数据集中,有些电子邮件过长或为空。出于演示目的,通过实现自定义,从数据集中删除所有此类记录DocumentFilter类:

from nemo_curator.filters import DocumentFilter

class FilterEmailsWithLongBody(DocumentFilter):
    """
    If the email is too long, discard.
    """

    def __init__(self, max_length: int = 5000):
        super().__init__()
        self.max_length = max_length

    def score_document(self, text: str) -> bool:
        return len(text) <= self.max_length

    def keep_document(self, score) -> bool:
        return score

class FilterEmptyEmails(DocumentFilter):
    """
    Detects empty emails (either empty body, or labeled as empty).
    """

    def score_document(self, text: str) -> bool:
        return (
            not isinstance(text, str)  # The text is not a string
            or len(text.strip()) == 0  # The text is empty
            or "Empty message" in text  # The email is labeled as empty
        )

    def keep_document(self, score) -> bool:
        return score

我们FilterEmailsWithLongBodyclass 会计算所提供文本中的字符数,并返回True如果长度是可以接受的,或False否则。您必须在body每个记录的字段。

我们FilterEmptyEmails类检查给定文本的类型和内容,以确定其是否为空电子邮件,并返回True如果电子邮件被视为空白,或者False否则。您必须在所有相关字段中明确应用此过滤器:subjectbody以及category每条记录的字段。

返回值与类的命名一致,可提高代码的可读性。但是,由于目标是丢弃空电子邮件,因此必须反转此过滤器的结果。换言之,如果过滤器返回,则丢弃记录True并在过滤器返回时保留记录False.这可以通过提供相关标志来完成ScoreFilter辅助程序:

Sequential([
    # Apply only to the `body` field.
    ScoreFilter(FilterEmailsWithLongBody(), text_field="body", score_type=bool),
    # Apply to all fields, also invert the action.
    ScoreFilter(FilterEmptyEmails(), text_field="subject", score_type=bool, invert=True),
    ScoreFilter(FilterEmptyEmails(), text_field="body", score_type=bool, invert=True),
    ScoreFilter(FilterEmptyEmails(), text_field="category", score_type=bool, invert=True),
])

指定标志invert=True来指示ScoreFilter丢弃过滤器返回的文档True.通过指定 score_type=bool为每个过滤器明确指定返回类型,以避免在执行期间进行类型推理。

编辑所有个人识别信息

接下来,定义处理步骤,以编辑每个记录主题和正文中的所有个人识别信息 (PII)。此数据集包含许多 PII 实例,例如电子邮件、电话或传真号码、姓名和地址。

借助 NeMo Curator,您可以轻松指定要检测的个人身份信息(PII)类型以及对每次检测采取的操作。使用特殊令牌替换每个检测:

def redact_pii(dataset: DocumentDataset, text_field) -> DocumentDataset:
    redactor = Modify(
        PiiModifier(
            supported_entities=[
                "ADDRESS",
                "EMAIL_ADDRESS",
                "LOCATION",
                "PERSON",
                "URL",
                "PHONE_NUMBER",
            ],
            anonymize_action="replace",
            device="cpu",
        ),
        text_field=text_field,
    )
    return redactor(dataset)

您可以将这些运算应用到subjectbody使用 Pythonfunctools.partial辅助程序:

from functools import partial

redact_pii_subject = partial(redact_pii, text_field="subject")
redact_pii_body = partial(redact_pii, text_field="body")

Sequential([
    redact_pii_subject,
    redact_pii_body,
    ]
)

添加指令提示

数据管护流程的最后一步是向每条记录添加指令提示,并确保每个类别的值都以句点终止。通过实现相关的DocumentModifier类:

from nemo_curator.modifiers import DocumentModifier

class AddSystemPrompt(DocumentModifier):
    def modify_document(self, text: str) -> str:
        return SYS_PROMPT_TEMPLATE % text


class AddPeriod(DocumentModifier):
    def modify_document(self, text: str) -> str:
        return text + "."

在代码示例中,SYS_PROMPT_TEMPLATE变量包含一个格式字符串,可用于在文本周围添加指令提示。这些修改器可以链接在一起:

Sequential([
    Modify(AddSystemPrompt(), text_field="body"),
    Modify(AddPeriod(), text_field="category"),
])

整合管线

在实现管线的每个步骤后,是时候将所有内容放在一起并按顺序对数据集应用每个操作了。您可以使用Sequential将类到链式管理操作结合在一起:

curation_steps = Sequential(
    [
        #
        # Unify the text encoding to Unicode.
        #
        Modify(UnicodeReformatter(), text_field="subject"),
        Modify(UnicodeReformatter(), text_field="body"),
        Modify(UnicodeReformatter(), text_field="category"),

        #
        # Filtering
        #
        ScoreFilter(
            FilterEmptyEmails(), text_field="subject", score_type=bool, invert=True
        ),
        ScoreFilter(
            FilterEmptyEmails(), text_field="body", score_type=bool, invert=True
        ),
        ScoreFilter(
            FilterEmptyEmails(), text_field="category", score_type=bool, invert=True
        ),
        ScoreFilter(FilterEmailsWithLongBody(), text_field="body", score_type=bool),

        #
        # Redact personally identifiable information (PII).
        #

        redact_pii_subject,
        redact_pii_body,

        #
        # Final modifications.
        #
        Modify(AddSystemPrompt(), text_field="body"),
        Modify(AddPeriod(), text_field="category"),
    ]
)

dataset = curation_steps(dataset)
dataset = dataset.persist()
dataset.to_json("/output/path", write_to_filename=True)

NeMo Curator 使用 Dask 以分布式方式处理数据集。由于 Dask 操作是延迟评估的,因此您必须调用.persist用于指示 Dask 应用操作的函数。处理完成后,您可以通过调用.to_json并提供输出路径。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

QXml 使用方法

VS2019 QT 编译工具链问题解决 使用winqtdeploy.exe 打包环境就可以正常运行&#xff0c;缺少某一个运行库引起的 简易使用python脚本编译运行 Python3 中的 slots 和 QT 中的 slots 宏定义重复, 放在不同的文件中进行调用可以避免 还是比较习惯从源码包引入&#xff08;方便定…

相机光学(三十七)——自动对焦原理

1.自动对焦的三种方式 目前在手机上采用的自动对焦系统包括反差对焦、相位对焦和激光对焦三种方案&#xff0c;下面我们来看一下它们的工作原理和相互之间的区别是什么。 1.1反差对焦【CDAF】- Contrast Detection Auto Focus 反差对焦是目前普及率最高、使用最广泛、成本相对…

哈希表、算法

哈希表 hash&#xff1a; 在编程和数据结构中&#xff0c;"hash" 通常指的是哈希函数&#xff0c;它是一种算法&#xff0c;用于将数据&#xff08;通常是字符 串&#xff09;映射到一个固定大小的数字&#xff08;哈希值&#xff09;。哈希函数在哈希表中尤为重要…

视觉SLAM ch5——相机与图像

一、单目模型 前言&#xff1a;本大标题下1~4部分讲述的都是单目针孔相机 SLAM的数学本质可以抽象为运动方程&#xff08;x&#xff09;和观测方程&#xff08;z&#xff09;&#xff08;书上的第二部分&#xff09; 教材第二章截图 书中P24页截图 其中的未知量为xk&#xff…

Golang | Leetcode Golang题解之第398题随机数索引

题目&#xff1a; 题解&#xff1a; type Solution []intfunc Constructor(nums []int) Solution {return nums }func (nums Solution) Pick(target int) (ans int) {cnt : 0for i, num : range nums {if num target {cnt // 第 cnt 次遇到 targetif rand.Intn(cnt) 0 {ans …

Gin-封装自动路由

O.0 思路一、API二、控制层三、自动路由核心四、分组路由外加中间件使用 思路 由于Java转Go直接使用的goframe框架&#xff0c;然学习Gin时觉得一个接口一个路由太麻烦&#xff0c;于是有了...1、在请求结构体中采用标签的形式&#xff0c;直接给出路由和请求方式 2、在控制层…

Golang开发之路

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

Go协程及并发锁应用指南

概念 协程&#xff08;Goroutine&#xff09;是Go语言独有的并发体&#xff0c;是一种轻量级的线程&#xff0c;也被称为用户态线程。相对于传统的多线程编程&#xff0c;协程的优点在于更加轻量级&#xff0c;占用系统资源更少&#xff0c;切换上下文的速度更快&#xff0c;不…

pyflink 安装和测试

FPY Warning! 安装 apache-Flink # pip install apache-Flink -i https://pypi.tuna.tsinghua.edu.cn/simple/ Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple/ Collecting apache-FlinkDownloading https://pypi.tuna.tsinghua.edu.cn/packages/7f/a3/ad502…

【Docker部署ELK】(7.15)

1、拉取镜像 docker pull docker.elastic.co/elasticsearch/elasticsearch:7.15.0 docker pull docker.elastic.co/kibana/kibana:7.15.0 docker pull docker.elastic.co/logstash/logstash:7.15.02、配置文件&#xff08;解压资源到D盘DOCKER目录下&#xff09; 2.1 配置文件…

什么是java的spi?

Java SPI&#xff08;Service Provider Interface&#xff09;是一种提供服务发现机制的设计模式&#xff0c;允许在运行时动态地发现、加载和替换服务的实现。SPI机制的核心思想是&#xff1a;通过接口定义服务&#xff0c;并且使用外部的实现类来提供该服务的具体功能。 目录…

【delphi】判断多显示器下,程序在那个显示器中

在 Delphi 中&#xff0c;如果你的电脑连接了多个显示器&#xff0c;可以通过以下步骤判断某个程序在哪个显示器上运行。 方法概述&#xff1a; 获取程序窗口的位置&#xff08;例如窗体的 Left、Top 坐标&#xff09;。使用 Screen.MonitorFromWindow 函数来确定该窗口所属的…

【STM32】单级与串级PID控制的C语言实现

【STM32】单级与串级PID的C语言实现 前言PID理论什么是PIDPID计算过程PID计算公式Pout、Iout、Dout的作用单级PID与串级PID PID应用单级PID串级PID 前言 笔者最近在学习PID控制器&#xff0c;本文基于Blog做以总结。CSDN上已有大量PID理论知识的优秀文章&#xff0c;因此本文将…

短信验证码倒计时 (直接复制即可使用) vue3

需求&#xff1a; 要实现一个获取验证码的需求&#xff0c;点击获取验证码60秒内不可以重复点击&#xff0c;方式有两种可以直接复制使用&#xff1b; 效果图 实现方案 方案1 (单个文件内使用比较推荐) <el-button :disabled"codeDisabled" click.stop"h…

【在Linux世界中追寻伟大的One Piece】网络命令|验证UDP

目录 1 -> Ping命令 2 -> Netstat命令 3 -> Pidof命令 4 -> 验证UDP-Windows作为client访问Linux 4.1 -> UDP client样例 1 -> Ping命令 Ping命令是一种网络诊断工具&#xff0c;它使用ICMP(Internet Control Message Protocol&#xff0c;互联网控制消…

redis常见的数据类型?

参考&#xff1a;一文读懂Redis五种数据类型及应用场景 - 知乎 (zhihu.com) String 类型 String 类型&#xff1a;Redis 最基本的数据类型&#xff0c;它是二进制安全的&#xff0c;意味着你可以用它来存储任何类型的数据&#xff0c;如图片、序列化对象等。使用场景&#xff…

Qt入门教程---项目创建全过程内存泄漏解释

目录 1.创建项目的说明 2.代码介绍说明 2.1文件分类介绍 2.2sources文件 2.3widget.ui文件 2.4widget.h文件 2.5中间文件 2.6.pro文件 3.打印输出hello world 3.1图形化界面生成控件 3.2代码生成控件 3.3打印结果展示 4.对于内存泄露的讨论 4.1对象树 4.2与栈开辟…

一图读懂 若依后端

一图读懂 若依后端 关注我&#xff0c;评论区评论就能获得思维导图本体文件啦&#x1f604;。如果你愿意关注我的掘金就更好啦宝&#x1f60d;&#xff0c;因为我掘金文章就一内内人看&#xff0c;想引流&#x1f60b; https://juejin.cn/user/1942157160101860本图是对若依后…

基础GAN生成式对抗网络(pytorch实验)

&#xff08;Generative Adversarial Network&#xff09; 一、理论 https://zhuanlan.zhihu.com/p/307527293?utm_campaignshareopn&utm_mediumsocial&utm_psn1815884330188283904&utm_sourcewechat_session 大佬的文章中的“GEN的本质”部分 二、实验 1、数…

Java | Leetcode Java题解之第403题青蛙过河

题目&#xff1a; 题解&#xff1a; class Solution {public boolean canCross(int[] stones) {int n stones.length;boolean[][] dp new boolean[n][n];dp[0][0] true;for (int i 1; i < n; i) {if (stones[i] - stones[i - 1] > i) {return false;}}for (int i 1…