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

news2024/9/23 19:28:46

目录

概述

预备知识

定义自定义文档构建器

下载数据集

解析和迭代数据集

将数据集写入 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并提供输出路径。

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

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

相关文章

【PyQt6 应用程序】应用程序携带数据源文件一并打包

在开发好应用程序打包之后给到其他用户会发现数据文件比如封面图片不见了。 例如这样,很影响用户使用。 这里介绍一个非常简单的打包方法,不光要在打包命令的时候添加对应数据文件,在源码中也要进行一些简单的修改。 修改需要添加打包文件的地方。首先需要添加一个绝对路径…

143234234123432

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文由 JohnKi 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f4e2;未来很长&#…

【C++】c++的继承

目录 思维导图大纲&#xff1a; 1.基类和派生类 1.1 定义格式 1.2 继承方式 1.3 基类和派生类的转换 2. 继承中的作用域(隐藏关系) 2.1 考察继承作⽤域相关选择题 3. 派生类的默认成员函数 4. 继承类模板 5. 一个不能被继承的类 ​编辑 6.继承与友元 ​编辑 7. 继…

Java面向对象六大设计原则总结(超级详细,附有代码、图解以及案例)

文章目录 三.软件(面向对象)设计原则3.1 开闭原则(OSP)3.1.1 概述3.1.2 案列 3.2 里氏代换原则(LSP)3.2.1 概述3.2.2 案例 3.3 依赖倒转原则(DIP)3.3.1概述3.3.2 案例 3.4 接口隔离原则(ISP)3.4.1 概述3.4.2 案列 3.5 迪米特法则(DP)3.5.1 概述3.5.2 案例 3.6 合成复用原则(CRP…

红黑树前语

目录 概念 性质 红黑树与AVL树的比较 过两天更新红黑树的模拟实现,中秋快乐各位 概念 1. 概念&#xff1a; 是一种搜索二叉树&#xff0c; 但在每个结点上增加一个存储位表示节点的颜色&#xff0c;可以是Red 或 Black。通过对任何一条从根到叶子的路径上各个节点着色方式的…

[JVM]JVM内存划分, 类加载过程, 双亲委派模型,垃圾回收机制

文章目录 一. JVM内存划分1. 堆2. 栈3. 元数据区4. 程序计数器 二. 类加载过程1. 加载2. 验证3. 准备4. 解析5. 初始化 三. 双亲委派模型四. JVM的垃圾回收机制GC1. 找到需要回收的对象2. 释放垃圾的策略 一. JVM内存划分 JVM就是java进程 这个进程一旦跑起来, 就会从操作系统…

Windows本地制作java证书(与jeecgboot配置本地证书ssl问题)

1&#xff1a;JDK生成自签证书SSL,首先以管理员身份运行CMD窗口&#xff0c;执行命令 keytool -genkey -alias testhttps -keyalg RSA -keysize 2048 -validity 36500 -keystore "F:/ssl/testhttps.keystore"F:\ssl>keytool -genkey -alias testhttps -keyalg R…

PCIe进阶之TL:Memory, I/O, and Configuration Request Rules TPH Rules

1 Memory, I/O, and Configuration Request Rules 下述规则适用于 Memory 请求、IO 请求和配置请求。 除了公共的 header 字段外,所有 Memory 请求、IO 请求和配置请求还包括以下字段: (1)Requester ID[15:0] 和 Tag[9:0],组成了 Transaction ID 。 (2)Last DW BE[3:0]…

计算架构模式之接口高可用

接口高可用整体框架 接口高可用主要应对两类问题&#xff1a;雪崩效应和链式效应。 雪崩&#xff1a;当请求量超过系统处理能力之后&#xff0c;会导致系统性能螺旋快速下降&#xff0c;本来系统可以处理1000条&#xff0c;但是当请求量超过1200的时候&#xff0c;此时性能会下…

【415】【最高乘法得分】

目录 使用dp python版本 java版本 递推式 python版本 java版本 PS: java语法 1.定义数组 2.记忆化 3.计算max 难绷&#xff0c;本来想着4个指针&#xff0c;和四数之和那道题挺类似的。。。。 四数之和好像剪枝和预处理都是先排序的比较好做。 无奈&#xff0c;只…

[网络]https的概念及加密过程

文章目录 一. HTTPS二. https加密过程 一. HTTPS https本质上就是http的基础上增加了一个加密层, 抛开加密之后, 剩下的就是个http是一样的 s > SSL HTTPS HTTP SSL 这个过程, 涉及到密码学的几个核心概念 明文 要传输的真正意思是啥 2)密文 加密之后得到的数据 这个密文…

CTF(misc)1和0的故事

题目链接 下载题目后是一堆整齐的01字符串&#xff0c;猜测是生成二维码&#xff0c;将0变成白色方块&#xff0c;1变成黑色方块。 0000000001110010000000000 0000000000011110100000000 0000000001110001000000000 0000000010111100000000000 0000000010101010000000000 00…

Python基础语法(3)下

列表和元组 列表是什么&#xff0c;元组是什么 编程中&#xff0c;经常需要使用变量&#xff0c;来保存/表示数据。变量就是内存空间&#xff0c;用来表示或者存储数据。 如果代码中需要表示的数据个数比较少&#xff0c;我们直接创建多个变量即可。 num1 10 num2 20 num3…

ModuleNotFoundError: No module named ‘datasets‘

报错信息&#xff1a; 解决&#xff1a;安装datasets 方法1: pip install datasets 方法2: python3可以使用以下命令&#xff1a; pip3 install datasets

【智路】智路OS Perception Fusion Service

Perception Fusion Service https://gitee.com/ZhiluCommunity/airos-edge/raw/r2.0/docs/02_Service/Perception_Fusion_Service.md 多传感器融合感知模块的主要任务是接收各传感器感知的障碍物信息&#xff0c;融合这些障碍物信息&#xff0c;得到融合后的障碍物信息。 智…

Tuxera NTFS for Mac 2023绿色版

​ 在数字化时代&#xff0c;数据的存储和传输变得至关重要。Mac用户经常需要在Windows NTFS格式的移动硬盘上进行读写操作&#xff0c;然而&#xff0c;由于MacOS系统默认不支持NTFS的写操作&#xff0c;这就需要我们寻找一款高效的读写软件。Tuxera NTFS for Mac 2023便是其中…

接口自动化框架入门(requests+pytest)

一、接口自动化概述 二、数据库概述 2.1 概念 存储数据的仓库&#xff0c;程序中数据的载体 2.2 分类 关系型数据库&#xff1a;安全 如mysql&#xff0c;oracle&#xff0c;SQLLite database tables 行列 非关系型数据库&#xff1a;高效 如redis&#xff0c;mongoDB 数…

C++笔记之子类初始化时父类带参构造函数的处理、父子类中模板参数的传递

C++笔记之子类初始化时父类带参构造函数的处理、父子类中模板参数的传递 code review! 文章目录 C++笔记之子类初始化时父类带参构造函数的处理、父子类中模板参数的传递一.子类初始化时父类带参构造函数的处理1.1.若父类只有带参数的构造函数,子类初始化时必须在初始化列表…

C++ 面试必备知识大全:从基础到高级特性全面解析

创作不易&#xff0c;您的打赏、关注、点赞、收藏和转发是我坚持下去的动力&#xff01; C 面试中常见的问题涵盖了语言基础、面向对象编程、内存管理、STL&#xff08;标准模板库&#xff09;、并发编程、设计模式等。以下是一些常见的 C 面试问题及其详细答案总结&#xff1…

protobuf中c、c++、python使用

文章目录 protobuf实例&#xff1a;例题1&#xff1a;[CISCN 2023 初赛]StrangeTalkBot分析&#xff1a;思路&#xff1a;利用&#xff1a; 例题2&#xff1a;[CISCN 2024]protoverflow分析&#xff1a; protobuf Protocol Buffers&#xff0c;是Google公司开发的一种数据描述语…