Transformer实战——datasets库核心功能解析
0. 前言
在自然语言处理 (Natural Language Processing
, NLP
) 任务中,高效管理和处理数据是模型成功的关键。然而,面对海量的数据集、复杂的格式转换以及跨语言任务的挑战,传统的数据处理流程往往效率低下且难以扩展。Hugging Face
的 datasets
库简化了数据加载与缓存,还支持灵活的数据操作(如拆分、过滤、映射)以及与预训练模型的深度集成。无论是从社区共享的经典数据集(如 GLUE
、XTREME
)中快速获取数据,还是对本地文件进行动态处理,datasets
库都能显著提升开发效率。
1. 使用 datasets 库加载数据集
datasets
库提供了一个非常高效的工具,能够加载、处理和通过 Hugging Face Hub
与社区共享数据集。与 TensorFlow datasets
类似,datasets
库使得用户能够轻松地下载、缓存和动态加载数据集。该库还提供了与数据相关的评估指标。实际上,Hugging Face Hub
并不持有或分发数据集,而是保留有关数据集的所有信息,包括所有者、预处理脚本、描述和下载链接。
(1) 首先,安装 datasets
:
$ pip install datasets
(2) 使用 Hugging Face Hub
自动加载 CoLA
数据集,datasets.load_dataset()
函数会在数据集未缓存时,从实际路径下载加载脚本:
from datasets import load_dataset
cola = load_dataset('glue', 'cola')
cola['train'][18:22]
使用以上代码,我们从 GLUE 基准中下载了 cola
数据集,并从其训练集中打印了几个示例样本如下:
{'sentence': ['They drank the pub.',
'The professor talked us into a stupor.',
'The professor talked us.',
'We yelled ourselves hoarse.'],
'label': [0, 1, 0, 1],
'idx': [18, 19, 20, 21]}
需要注意的是,每次重新运行代码时,datasets
库会缓存加载和操作请求。它首先存储数据集,并开始缓存对数据集的操作,例如拆分、选择和排序,可能会看到类似 “reusing dataset xtreme (/home/.cache/huggingface/dataset...)
” 或 “loading cached sorted...
” 的警告消息。
(2) Hugging Face Hub
上包含 19,298
个数据集和 116
个评估指标,并且仍在不断扩充:
from pprint import pprint
from huggingface_hub import list_datasets, list_metrics
all_d = list_datasets()
metrics = list_metrics()
print(f"{len(all_d)} datasets and {len(metrics)} metrics exist in the hub\n")
pprint(list(all)[:20], compact=True)
pprint(metrics, compact=True)
输出如下所示:
一个数据集可能有多个配置。例如,GLUE
作为一个综合基准,包含许多子集,如 CoLA
、SST-2
和 MRPC
等。要访问某个 GLUE
基准数据集,需要传递两个参数,第一个是 “glue
”,第二个是某个具体数据集的名称(例如 cola
或 sst2
)。
数据集通常包含一个 DatasetDict
对象,其中包含多个 Dataset
实例。当使用 split='...'
参数时,可以获得具体的 Dataset
实例。例如,CoLA
数据集包含一个 DatasetDict
,其拆分为 3
部分:train
(训练集)、validation
(验证集)和test(测试集)。其中,训练集和验证集包含两个标签(1表示可接受,0表示不可接受),而测试集的标签值为-1,表示没有标签。
(3) 接下来,查看 CoLA
数据集对象的结构:
cola = load_dataset('glue', 'cola')
print(cola)
cola['train'][12]
cola['validation'][68]
cola['test'][20]
输出如下所示:
(4) 数据集对象还包含一些额外的元数据,包括拆分 (split
)、描述 (description
)、引用 (citation
)、主页 (homepage
)、许可 (license
) 和信息 (info
) 等:
print(cola["train"].description)
print(cola["train"].citation)
print(cola["train"].homepage)
(5) GLUE
基准提供了多个数据集,接下来,下载 MRPC
数据集:
mrpc = load_dataset('glue', 'mrpc')
(6) 要访问其他 GLUE
任务,可以修改第二个参数:
load_dataset('glue', 'XYZ')
(7) 为了进行数据可用性的初步检查,可以运行以下代码:
glue=['cola', 'sst2', 'mrpc', 'qqp', 'stsb', 'mnli', 'mnli_mismatched', 'mnli_matched', 'qnli', 'rte', 'wnli', 'ax']
for g in glue:
_ = load_dataset('glue', g)
(8) XTREME
是另一个常用的跨语言数据集,接下来,从 XTREME
集合中选取 MLQA
示例。MLQA
是 XTREME
基准的一个子集,旨在评估跨语言问答模型的性能。它包含约 5,000
个基于 SQuAD
格式的提取式问答实例,涵盖七种语言:英语、德语、阿拉伯语、印地语、越南语、西班牙语和简体中文。例如,MLQA.en.de
是一个英语-德语问答示例数据集:
en_de = load_dataset('xtreme', 'MLQA.en.de')
print(en_de)
加载后查看数据结构,输出结果如下:
为了更方便地查看数据,可以将其转换为 pandas DataFrame
格式:
import pandas as pd
pd.DataFrame(en_de['test'][0:4])
输出结果如下:
2. 使用 datasets 库处理数据
2.1 加载指定部分数据
数据集通常包含多个子集的字典,其中 split
参数用于决定加载哪个子集或子集的哪个部分。如果默认没有指定该参数(默认情况下为 None
),它将返回一个包含所有子集(训练集、测试集、验证集或其他任意组合)的数据集字典。
(1) 如果指定了 split
参数,它将返回一个单个数据集而不是字典。例如,只获取 cola
数据集的训练集部分:
cola_train = load_dataset('glue', 'cola', split='train')
(2) 还可以获取训练集和验证集的混合部分:
cola_sel = load_dataset('glue', 'cola', split='train[:300]+validation[-30:]')
其中,split
表达式表示,从训练集中获取前300个样本,从验证集中获取最后30个样本,结果保存在 cola_sel
中。
(3) 还可以应用不同的组合方式:
- 从训练集和验证集各获取前
100
个样本:split='train[:100]+validation[:100]'
- 从训练集中获取前
50%
,从验证集中获取后30%
:split='train[:50%]+validation[-30%:]'
- 从训练集中获取前
20%
,从验证集中获取[30:50]
范围内的样本:split='train[:20%]+validation[30:50]'
2.2 排序、索引和打乱数据
(2) 调用 cola_sel
对象的 sort()
函数,查看前 15
个和最后 15
个标签:
cola_sel.sort('label')['label'][:15]
cola_sel.sort('label')['label'][-15:]
# [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
# [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
(2) 也可以使用切片表示法或通过索引列表来访问多行数据:
cola_sel[6, 19, 44]
输出结果如下:
{'sentence': ['Fred watered the plants flat.',
'The professor talked us into a stupor.',
'The trolley rumbled through the tunnel.'],
'label': [1, 1, 1],
'idx': [6, 19, 44]}
(3) 使用 shuffle()
方法打乱数据集:
cola_sel.shuffle()[2:5]
输出结果如下:
{'sentence': ['The chocolate melted onto the carpet.',
'Herman hammered the metal flat.',
'John made Bill master of himself.'],
'label': [1, 1, 1],
'idx': [94, 9, 1035]}
2.3 缓存与可重用性
使用缓存文件可以通过内存映射加载大规模数据集(如果数据集可以存放在磁盘中),并使用快速的后端进行操作。这种智能缓存有助于保存和重用已在硬盘上执行的操作结果。
(1) 查看与数据集相关的缓存日志,可以使用以下代码:
cola_sel.cache_files
输出结果如下,表明数据已经被缓存:
[{'filename': '/home/br/.cache/huggingface/datasets/glue/cola/0.0.0/bcdcba79d07bc864c1c254ccfcedcce55bcc9a8c/glue-train.arrow'},
{'filename': '/home/br/.cache/huggingface/datasets/glue/cola/0.0.0/bcdcba79d07bc864c1c254ccfcedcce55bcc9a8c/glue-validation.arrow'}]
2.4 datasets 的 filter 和 map 函数
(1) 有时,我们需要对数据集进行特定选择。比如,可以从 cola
数据集中仅提取包含单词 “kick
” 的句子。使用 datasets.Dataset.filter()
函数返回包含 “kick
” 的句子,其中应用了匿名函数和 lambda
关键字:
cola_sel = load_dataset('glue', 'cola', split='train[:100%]+validation[-30%:]')
cola_sel.filter(lambda s: "kick" in s['sentence'])["sentence"][:3]
输出结果如下:
['Jill kicked the ball from home plate to third base.',
'Fred kicked the ball under the porch.',
'Fred kicked the ball behind the tree.']
(2) 通过 filter()
来获取正样本:
cola_sel.filter(lambda s: s['label'] == 1)["sentence"][:3]
输出结果如下:
["Our friends won't buy this analysis, let alone the next one we propose.",
"One more pseudo generalization and I'm giving up.",
"One more pseudo generalization or I'm giving up."]
(3) 在某些情况下,我们可能不知道类别标签的数值编码。假设有多个类别,可以通过 str2int()
函数来传递标签(如 acceptable
),而不是直接使用数值编码:
cola_sel.filter(lambda s: s['label']== cola_sel.features['label'].str2int('acceptable'))["sentence"][:3]
输出结果如下所示:
["Our friends won't buy this analysis, let alone the next one we propose.",
"One more pseudo generalization and I'm giving up.",
"One more pseudo generalization or I'm giving up."]
(4) datasets.Dataset.map()
函数能够遍历数据集,将一个处理函数应用于数据集中的每个样本,并修改样本内容。例如,添加一个新的 ‘len
’ 特征,用来表示句子的长度:
cola_new=cola_sel.map(lambda e:{'len': len(e['sentence'])})
pd.DataFrame(cola_new[0:3])
输出结果如下所示:
(5) 或者,将句子截断至 20
个字符,这并不会创建一个新特征,而是直接更新 sentence
特征的内容:
cola_cut=cola_new.map(lambda e: {'sentence': e['sentence'][:20]+ '_'})
pd.DataFrame(cola_cut[:3])
输出结果如下所示:
2.5 处理本地文件
为了从本地文件加载数据集(如 CSV
、TXT
或 JSON
格式),我们将文件类型 (csv
、text
或 json
) 传递给通用的 load_dataset()
加载脚本,如下代码所示。在 ../data/
文件夹下,有三个 CSV
文件 (a.csv
、b.csv
和 c.csv
),这些文件是从 SST-2
数据集中随机选择的简单样本。也可以加载单个文件,如数据对象 data1
所示,或者合并多个文件,如数据对象 data2
,甚至可以对数据集进行拆分,如 data3
所示:
from datasets import load_dataset
data1 = load_dataset('csv', data_files='./data/a.csv', delimiter="\t")
data2 = load_dataset('csv', data_files=['./data/a.csv', './data/b.csv', './data/c.csv'], delimiter="\t")
data3 = load_dataset('csv', data_files={'train':['./data/a.csv', './data/b.csv'], 'test':['./data/c.csv']}, delimiter="\t")
为了加载其他格式的文件,可以将 csv
替换为 json
或 text
等所需格式:
data_json = load_dataset('json', data_files='a.json')
data_text = load_dataset('text', data_files='a.txt')
我们已经讨论了如何加载、处理和操作托管在数据中心或存储在本地磁盘上的数据集。接下来,我们将学习如何准备数据集以进行 Transformer
模型的训练。
3. 为模型训练准备数据集
首先,从分词过程开始。每个模型都有自己独特的分词器,分词器会在实际语言模型训练之前进行训练。以下代码加载了来自预训练 distilBERT-base-uncased
模型的分词器,使用 map
和匿名函数 (lambda
) 将分词器应用于 data3
。如果 map
函数中的 batched
参数设置为 True
,则会将一个批次的样本传递给分词器函数,默认情况下,batch_size
值为 1000
,即每批次传递给函数的样本数。如果没有设置 batched
参数(默认情况下为 False
),则整个数据集会作为一个批次传递给函数:
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased")
encoded_data3 = data3.map(lambda e: tokenizer(e['sentence'], padding=True, truncation=True, max_length=12), batched=True, batch_size=1000)
print(data3)
print(encoded_data3)
输出结果如下所示,可以看到 data3
和 encoded_data3
之间的区别,encoded_data3
中新增了两个特征——attention_mask
和 input_ids
,并将其相应地添加到数据集中。简而言之,input_ids
是句子中每个 token
对应的索引,是 Transformer
的 Trainer
类所需的特征:
我们通常会一次传递多个句子(称为一个批次)给分词器,并将分词后的批数据进一步传递给模型。为此,将每个句子填充到该批次中最大句子的长度,或者填充到由 max_length
参数指定的特定最大长度,在本节中为 12
。同时,将较长的句子截断以适应最大长度:
print(encoded_data3['test'][12])
# {'sentence': 'an extremely unpleasant film . ', 'label': 0, 'input_ids': [101, 2019, 5186, 16010, 2143, 1012, 102, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]}
小结
本文深入探讨了 Hugging Face datasets
库在 NLP
数据处理中的核心功能与应用场景,包括:
- 快速访问数据集:从
Hugging Face Hub
加载GLUE
、XTREME
等基准数据集,并通过缓存机制优化重复加载效率 - 灵活操作数据:利用
split
参数实现复杂拆分逻辑,结合shuffle
、sort
、filter
和map
函数完成数据的动态调整与特征增强 - 本地与多格式支持:处理本地
CSV
、JSON
等类型文件,并支持多文件合并与自定义拆分策略 - 适配模型训练:通过集成
AutoTokenizer
,将原始文本高效转换为模型输入,为下游任务提供标准化数据流
系列链接
Transformer实战——词嵌入技术详解
Transformer实战——循环神经网络详解
Transformer实战——从词袋模型到Transformer:NLP技术演进
Transformer实战——Hugging Face环境配置与应用详解
Transformer实战——Transformer模型性能评估