Hugging Face 数据集加载机制,自定义数据集的方法和注意事项 全面整合(含代码和细节解释)
1. 数据集的默认加载机制
Hugging Face 的 datasets 支持公开数据集和本地数据集。默认机制包括以下几种情况:
(1) 加载公开数据集
- 公开数据集(如 IMDB、GLUE)通常预定义了子集(
train、validation、test)。 - 可以通过
split参数选择加载特定的子集。
代码示例:
from datasets import load_dataset
# 加载公开数据集(IMDB)
dataset = load_dataset("imdb")
# 查看数据集的子集
print(dataset.keys()) # 输出:dict_keys(['train', 'test'])
# 加载训练集
train_data = load_dataset("imdb", split="train")
print(train_data[:5]) # 查看训练集前 5 条记录
(2) 加载本地数据集
- 单文件:默认加载为
train子集。 - 多文件:通过
data_files参数指定多个文件,同时自定义子集名称。
代码示例:单文件
dataset = load_dataset("csv", data_files="data.csv")
print(dataset.keys()) # 输出:dict_keys(['train'])
print(dataset["train"][:5]) # 查看数据内容
代码示例:多文件
data_files = {
"train": "train.csv",
"validation": "validation.csv",
"test": "test.csv"
}
dataset = load_dataset("csv", data_files=data_files)
print(dataset.keys()) # 输出:dict_keys(['train', 'validation', 'test'])
print(dataset["train"][:5]) # 查看训练集内容
2. 数据集的自定义加载与子集命名
(1) 自定义单文件的子集名称
通过指定 data_files 的键名,为单文件加载的默认子集重新命名。
代码示例:
dataset = load_dataset("csv", data_files={"custom_subset": "data.csv"})
print(dataset.keys()) # 输出:dict_keys(['custom_subset'])
print(dataset["custom_subset"][:5]) # 查看自定义子集内容
(2) 多文件加载直接定义子集名称
可以通过 data_files 指定每个文件对应的子集名称。
代码示例:
data_files = {
"deck22": "file1.csv",
"deck23": "file2.csv"
}
dataset = load_dataset("csv", data_files=data_files)
print(dataset.keys()) # 输出:dict_keys(['deck22', 'deck23'])
print(dataset["deck22"][:5]) # 查看 deck22 子集内容
(3) 通过手动划分创建自定义子集
当加载的文件只有一个时,可以手动划分子集,并自定义子集名称。
代码示例:
from datasets import load_dataset
# 加载单文件
dataset = load_dataset("csv", data_files="data.csv")["train"]
# 按比例划分为 train 和 custom_subset
split = dataset.train_test_split(test_size=0.3)
custom_splits = {
"train": split["train"],
"custom_subset": split["test"]
}
print(custom_splits.keys()) # 输出:dict_keys(['train', 'custom_subset'])
print(custom_splits["custom_subset"][:5]) # 查看自定义子集内容
3. 嵌套数据的加载与处理
(1) 加载嵌套 JSON 数据
如果数据文件是嵌套 JSON,可以通过 field 参数指定加载的层级。
示例 JSON 数据:
{
"user": {
"id": 1,
"profile": {"name": "Alice", "age": 25},
"posts": [{"title": "First Post", "content": "Hello World"}]
},
"metadata": {"source": "app", "timestamp": "2023-12-25"}
}
代码示例:
dataset = load_dataset("json", data_files="nested.json")
# 查看顶层键
print(dataset["train"].column_names) # 输出:['user', 'metadata']
# 加载特定字段(如 user)
user_data = load_dataset("json", data_files="nested.json", field="user")
print(user_data["train"][:5]) # 查看 user 部分内容
(2) 扁平化嵌套数据
如果嵌套层次过深,可以将数据扁平化以便后续处理。
代码示例:
def flatten(record):
return {
"user_id": record["user"]["id"],
"user_name": record["user"]["profile"]["name"],
"user_age": record["user"]["profile"]["age"],
"first_post_title": record["user"]["posts"][0]["title"]
}
# 扁平化数据
flattened_dataset = dataset["train"].map(flatten)
print(flattened_dataset[:5]) # 查看扁平化后的数据
4. 按比例划分与数据检查
(1) 按比例划分子集
train_test_split 可以轻松按比例划分数据集,并生成新的子集。
代码示例:
split = dataset["train"].train_test_split(test_size=0.2)
print(split.keys()) # 输出:dict_keys(['train', 'test'])
print(len(split["train"]), len(split["test"])) # 查看每个子集的样本数量
(2) 检查数据集的结构和内容
通过以下方法快速检查数据集的结构:
- 列名:
print(dataset["train"].column_names) # 查看列名 - 数据类型:
print(dataset["train"].features) # 查看每列数据的类型 - 样本内容:
print(dataset["train"][:5]) # 查看前 5 条记录
5. 数据保存与重新加载
(1) 保存处理后的数据集
处理后的数据集可以保存到本地:
processed_dataset = dataset["train"].map(lambda x: {"new_column": x["column1"] * 2})
processed_dataset.save_to_disk("processed_dataset")
(2) 重新加载保存的数据集
from datasets import load_from_disk
loaded_dataset = load_from_disk("processed_dataset")
print(loaded_dataset[:5]) # 查看重新加载的数据
6. 整合示例:多文件加载与自定义子集
以下是一个完整的加载和处理流程,涵盖多文件加载、自定义子集划分与保存操作:
from datasets import load_dataset
# Step 1: 加载多个文件并自定义子集名称
data_files = {
"train": "train.csv",
"validation": "validation.csv",
"test": "test.csv"
}
dataset = load_dataset("csv", data_files=data_files)
# Step 2: 按比例划分 train 子集
split = dataset["train"].train_test_split(test_size=0.2)
dataset["train"] = split["train"]
dataset["extra_validation"] = split["test"]
# Step 3: 检查数据集内容
print(dataset.keys()) # 输出:dict_keys(['train', 'validation', 'test', 'extra_validation'])
# Step 4: 保存处理后的数据集
dataset["extra_validation"].save_to_disk("extra_validation")
总结
- 单文件数据集默认加载为
train,可通过手动划分和重命名实现自定义子集。 - 多文件方式可以直接通过
data_files定义子集名称,简化操作。 - 嵌套数据可以通过
field加载指定层级,并通过扁平化方法处理复杂结构。
踩坑记录:
1. 假设我们想把一个文件分给多个子集,这里非常麻烦
- 初次划分后,子集名称为
train和dec25。 - 第二次划分,
dec25被拆分为两个子集。 - 我们手动给新划分的子集命名为
validation和test,但原名称dec25本身不会被修改。
- 初始数据集加载后,只有一个子集,默认名为
train。 - 我们先将
train按比例划分为train和dec25。 - 再将
dec25进一步划分为dec25_validation和test。
修订后的代码示例
from datasets import load_dataset
# Step 1: 加载整个 CSV 文件
dataset = load_dataset('csv', data_files='dataset.csv')['train'] # 默认所有数据归为 train
# Step 2: 按比例划分为 train 和 dec25
train_dec25_split = dataset.train_test_split(test_size=0.3) # 按 7:3 分为 train 和 dec25
train_dec25 = {
'train': train_dec25_split['train'], # 保留 70% 数据作为 train
'dec25': train_dec25_split['test'] # 剩下 30% 数据作为 dec25
}
# Step 3: 将 dec25 再划分为 dec25_validation 和 test
dec25_split = train_dec25['dec25'].train_test_split(test_size=0.5) # 按 1:1 再分
final_dataset = {
'train': train_dec25['train'], # 原来的 train 保留不变
'dec25_validation': dec25_split['train'], # dec25 的一部分作为 validation
'test': dec25_split['test'] # dec25 的另一部分作为 test
}
# Step 4: 查看最终划分结果
print(final_dataset.keys()) # 输出:dict_keys(['train', 'dec25_validation', 'test'])
# 查看每个子集的样本数量
for subset_name, subset_data in final_dataset.items():
print(f"{subset_name} 子集包含 {len(subset_data)} 条样本")
子集名称的变化与来源
-
初次划分
train_test_split返回的子集名称默认是train和test。- 我们手动将
test子集重新命名为dec25,表示临时划分出的验证集。
-
再次划分
- 对
dec25执行train_test_split,默认生成新的train和test子集。 - 我们重新命名:
- 原来的
train改为dec25_validation。 - 原来的
test保留为test。
- 原来的
- 对
示例数据与划分比例
假设初始数据集有 1000 条记录,划分过程如下:
(1) 初次划分
train_dec25_split['train']:占总数据的 70%,即 700 条。train_dec25_split['test'](重命名为dec25):占总数据的 30%,即 300 条。
(2) 第二次划分
dec25_split['train'](重命名为dec25_validation):占dec25的 50%,即 150 条。dec25_split['test'](保持为test):占dec25的 50%,即 150 条。
最终数据集分布:
train: 700 条dec25_validation: 150 条test: 150 条
规律总结
-
命名逻辑
- 初次划分:默认子集名称是
train和test,可以手动将test改为其他名称(如dec25)。 - 再次划分:新生成的子集名称仍是
train和test,需要根据具体用途重新命名。
- 初次划分:默认子集名称是
-
索引值的变化
- 每次执行
train_test_split,默认生成两个子集:train: 保留的数据部分。test: 新划分出的小部分数据。
- 如果子集继续划分,索引值也会沿用
train和test。
- 每次执行
-
避免名称混淆
-
在多次划分时,及时重命名子集(如
dec25_validation和test),确保不同阶段的子集名称清晰、唯一。 -
代码中的
test_valid['test']可能会让人误解为原来的dec25被覆盖。需要明确说明:每次执行train_test_split都会生成新的子集对象,原子集不会被覆盖。
-
2. Split = {} 而不是 Split = [ ]
在你的代码中,load_dataset函数被用来加载数据集,但由于使用了参数split=["train[:1%]", "test[-1%:]"],导致返回的对象是一个列表(list)而不是通常的DatasetDict类型。下面详细解释这一现象的原因,以及如何正确理解和使用这段代码。
核心机制:
split参数的默认行为是根据预定义的分割(如train、test、validation)生成一个DatasetDict。- 但当传入列表时,它认为用户想要的结果是“多个分割片段的集合”,因此返回
list。
from datasets import load_dataset
# 加载TSV格式的文件作为数据集
data_files = {"train": "drugsComTrain_raw.tsv", "test": "drugsComTest_raw.tsv"} # 定义训练和测试文件
# 加载数据集并指定分割
ds = load_dataset(
"csv", # 数据集的格式为CSV(这里也兼容TSV)
data_files=data_files, # 指定文件路径
delimiter="\t", # TSV文件以制表符分隔
split=["train[:1%]", "test[-1%:]"] # 只取训练集的前1%和测试集的后1%
)
# 查看加载后的数据类型和结构
print(type(ds)) # <class 'list'>
print(len(ds)) # 2
print(ds[0]) # 第一部分数据,属于训练集切片
print(ds[1]) # 第二部分数据,属于测试集切片
- 变量含义:
data_files: 包含训练文件和测试文件的路径。delimiter="\t": 指定文件的分隔符为制表符。split=["train[:1%]", "test[-1%:]"]: 将训练集取前1%,测试集取后1%。ds: 加载后的数据,类型是list,包含两个分割。
- 返回结果:
ds[0]是训练集的切片(前1%)。ds[1]是测试集的切片(后1%)。
如何使结果是 DatasetDict
需要通过dict的方式命名 split={"train": "train[:1%]", "test": "test[-1%:]"}: 指定分割后命名为train和test
# 修改split参数,使其返回DatasetDict
ds = load_dataset(
"csv",
data_files=data_files,
delimiter="\t",
split={"train": "train[:1%]", "test": "test[-1%:]"}
)
# 查看类型和内容
print(type(ds)) # <class 'datasets.dataset_dict.DatasetDict'>
print(ds.keys()) # dict_keys(['train', 'test'])
print(ds["train"]) # 训练集切片
print(ds["test"]) # 测试集切片
3. load_from_disk 是用来加载 Hugging Face 数据集目录的(例如 .arrow 格式的文件夹),而不能直接加载普通的文件格式(如 .csv、.tsv)
-
load_dataset- 用于加载普通文件格式(如
csv、json、tsv等)。 - 需要指定文件类型,例如
"csv"。
- 用于加载普通文件格式(如
-
load_from_disk- 用于加载 Hugging Face 本地保存的数据集文件夹。
- 文件夹中通常包含
.arrow文件以及dataset_info.json等元数据文件。
4. Map 和 Filter可以直接应用到总集但是访问不行
filter 和 map 的语法和调用
filter和map的作用:filter:筛选符合条件的样本。map:对每条样本或批量样本进行映射操作。
- 调用方式:
- 对总数据集(
DatasetDict)操作:直接调用drug_dataset.filter()或drug_dataset.map()会对所有子数据集(如train和test)同时应用操作。 - 对单个子数据集(
Dataset)操作:必须先通过键(如"train")获取子数据集,再调用对应的方法,如drug_dataset["train"].filter()。
- 对总数据集(
DatasetDict和Dataset的区别:DatasetDict是一个字典结构,包含多个子数据集(键是子数据集名称,如"train",值是具体的Dataset对象)。Dataset是具体的单个数据集,具有行和列的概念。
- 访问规则:
- 要访问列数据(如
"review"),必须先进入具体的子数据集(如drug_dataset["train"]),因为列是Dataset的属性,而不是DatasetDict的。
- 要访问列数据(如
| 特性 | DatasetDict | Dataset |
|---|---|---|
| 筛选/映射 | 对所有子数据集操作(filter/map 方法) | 针对单个子数据集操作(filter/map 方法) |
列数据访问(如 "review") | ❌ 不支持 | ✅ 通过列名访问,如 drug_dataset["train"]["review"] |
| 行数据访问(如前 3 行) | ❌ 不支持 | ✅ 通过索引访问,如 drug_dataset["train"][:3] |
5 batched=True 时,x["列名"] 是一个列表,直接调用 .lower() 导致错误
在HG教程练习中遇到的问题 https://huggingface.co/learn/nlp-course/zh-CN/chapter5/3
#当前结构
DatasetDict({
train: Dataset({
features: ['Unnamed: 0', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'input_ids', 'token_type_ids', 'attention_mask', 'code'],
num_rows: 1613
})
test: Dataset({
features: ['Unnamed: 0', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'input_ids', 'token_type_ids', 'attention_mask', 'code'],
num_rows: 538
})
})
# 假设 ds 是原始 DatasetDict
filtered_ds = {
a: ds[a].filter(lambda x: x['review'] is not None) # 保留 'review' 不为 None 的数据
for a in ds.keys() # 遍历 train 和 test
}
# 将 review 列小写化,正确处理 batched=True 的情况
lowercased_ds = {
a: filtered_ds[a].map(
lambda x: {"review": [review.lower() for review in x["review"]]}, # 对批次内的每个字符串小写化
batched=True
)
for a in filtered_ds.keys()
}
# 检查结果:输出 train 集中的 review 列
print(lowercased_ds["train"]["review"][:3])
batched=True的影响:当map方法设置batched=True时,x是一个批量数据(即一个字典,其中每列的值是一个列表)。因此,x["review"]返回的是一个字符串列表,而不是单个字符串。未正确处理批量数据:x["review"].lower()试图对整个列表调用字符串的方法,导致报错。
解决方法
需要对每个批次中的字符串列表进行逐个处理。通过列表推导式来实现:
# 假设 ds 是原始 DatasetDict
filtered_ds = {
a: ds[a].filter(lambda x: x['review'] is not None) # 保留 'review' 不为 None 的数据
for a in ds.keys() # 遍历 train 和 test
}
# 将 review 列小写化,正确处理 batched=True 的情况
lowercased_ds = {
a: filtered_ds[a].map(
lambda x: {"review": [review.lower() for review in x["review"]]}, # 对批次内的每个字符串小写化
batched=True
)
for a in filtered_ds.keys()
}
# 检查结果:输出 train 集中的 review 列
print(lowercased_ds["train"]["review"][:3])
修正的要点
-
列表推导式处理每个字符串:
x["review"]是一个列表(因为batched=True),需要用[review.lower() for review in x["review"]]遍历并处理每个字符串。- 如果不使用
batched=True,则每次处理一行,x["review"]会是单个字符串,可以直接调用.lower()。
-
保留原始结构:
- 使用字典推导式创建新的数据集,保持
DatasetDict的结构完整。 - 依然对
train和test两部分数据分别操作。
- 使用字典推导式创建新的数据集,保持
执行上述代码后,假设原始数据如下:
{
"train": {
"review": ["Great medicine", "Worked well", "Very effective"]
},
"test": {
"review": ["Highly recommended", "Works like a charm"]
}
}
输出:
['great medicine', 'worked well', 'very effective']
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐
所有评论(0)