【HuggingFace LLM】大模型文本任务数据预处理
import torch
from torch.optim import AdamW
from transformers import AutoTokenizer, AutoModelForSequenceClassification
# Same as before
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
sequences = [
"I've been waiting for a HuggingFace course my whole life.",
"This course is amazing!",
]
batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt")
# This is new
batch["labels"] = torch.tensor([1, 1])
optimizer = AdamW(model.parameters())
loss = model(**batch).loss
loss.backward()
optimizer.step()
几个步骤完成训练:
- 分词:这里
tokenizer直接把输入的句子分为ids和attention_mask张量; - 模型:使用同一个
checkpoint加载,使用与训练时相同的safe_tensors以及分词方法; - 标签:这里给输入的两个句子添加了一个
labels;
但是数据量过少,没法很好的训练模型。
Hub中加载数据
from datasets import load_dataset
raw_datasets = load_dataset("glue", "mrpc")
>>>raw_datasets
DatasetDict({
train: Dataset({
features: ['sentence1', 'sentence2', 'label', 'idx'],
num_rows: 3668
})
validation: Dataset({
features: ['sentence1', 'sentence2', 'label', 'idx'],
num_rows: 408
})
test: Dataset({
features: ['sentence1', 'sentence2', 'label', 'idx'],
num_rows: 1725
})
})
可以直接通过load_dataset加载glue benchmark中的mrpc数据集。
raw_train_dataset = raw_datasets["train"]
>>>raw_train_dataset[0]
>>>{'idx': 0,'label': 1,'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .','sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'}
>>>raw_train_dataset.features
>>>{'sentence1': Value(dtype='string', id=None),'sentence2': Value(dtype='string', id=None),'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None),'idx': Value(dtype='int32', id=None)}
类似于使用字典的,可以通过索引、通过键的方式加载具体数据。使用.features加载数据集中的各个字段的格式,特别是标签的0/1表示的是什么具体含义。
预处理数据
from transformers import AutoTokenizer
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
raw_train_dataset = raw_datasets["train"]
train_dataset_slice = raw_train_dataset[15]
inputs1 = tokenizer(train_dataset_slice['sentence1'])
inputs2 = tokenizer(train_dataset_slice['sentence2'])
inputs = tokenizer(train_dataset_slice['sentence1'], train_dataset_slice['sentence2'])
print(inputs1)
print(inputs2)
print(inputs)
>>>{'input_ids': [101, 24049, 2001, 2087, 3728, 3026, 3580, 2343, 2005, 1996, 9722, 1004, 4132, 9340, 12439, 2964, 2449, 1012, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]} // inputs1
>>>{'input_ids': [101, 3026, 3580, 2343, 4388, 24049, 1010, 3839, 2132, 1997, 1996, 9722, 1998, 4132, 9340, 12439, 2964, 3131, 1010, 2097, 2599, 1996, 2047, 9178, 1012, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]} // inputs2
>>>{'input_ids': [101, 24049, 2001, 2087, 3728, 3026, 3580, 2343, 2005, 1996, 9722, 1004, 4132, 9340, 12439, 2964, 2449, 1012, 102, 3026, 3580, 2343, 4388, 24049, 1010, 3839, 2132, 1997, 1996, 9722, 1998, 4132, 9340, 12439, 2964, 3131, 1010, 2097, 2599, 1996, 2047, 9178, 1012, 102], 'token_type_ids': [0, 0, 0, 0, 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, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]} //inputs
可以发现tokenizer可以同时解析两句话,以batch的形式进行分词。
[!NOTE]
需要注意的是,以
batch形式进行分词时,token_type_ids中的0/1表示不同的两个句子。
sentences = tokenizer.convert_ids_to_tokens(inputs["input_ids"])
>>>sentences
>>>inputs['token_type_ids']
['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]']
[ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1]
通过将输入的tokens解析回句子,并拿着token_type_ids进行对照后,可以发现:
[CLS] sentence1 [SEP] sentence2 [SEP]
呈现上述形式,并且[CLS] sentence1 [SEP]对应第一句话(token_type_ids是0),sentence2 [SEP]对应第二句(token_type_ids是1)
那么更进一步的,可以直接使用
tokenized_dataset = tokenizer(
raw_datasets["train"]["sentence1"],
raw_datasets["train"]["sentence2"],
padding=True,
truncation=True,
)
但是这种方法会返回字典(包含键、input_ids、attention_mask 和 token_type_ids,以及List[List]值),要求有足够的内存来存储整个数据集时使用。
使用map加速处理
def tokenize_function(example):
return tokenizer(example["sentence1"], example["sentence2"], truncation=True)
其中example是raw_train_datasets中的一项
这里不使用padding=True是因为这一步在构建批次时填充样本更好,这样我们只需填充该批次的最大长度,而不必填充整个数据集的最大长度
tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)
>>>tokenized_datasets
>>>DatasetDict({
train: Dataset({
features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'],
num_rows: 3668
})
validation: Dataset({
features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'],
num_rows: 408
})
test: Dataset({
features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'],
num_rows: 1725
})
})
#开发 使用Dataset.map()方法进行批量batched=True处理。
这种方法在定义字段后,解析多条往对应字段添加数据。
使用dynamic padding填充
from transformers import DataCollatorWithPadding
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
samples = tokenized_datasets["train"][:8]
samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]}
>>> [len(x) for x in samples["input_ids"]]
>>> [50, 59, 47, 67, 59, 50, 62, 32]
这里以8个为例,可以看到输入的input_idsd长度都是不一致的。
那么我们直接使用data_collator去对其进行批次长度填充
batch = data_collator(samples)
>>>{k: v.shape for k, v in batch.items()}
>>>{'attention_mask': torch.Size([8, 67]),
'input_ids': torch.Size([8, 67]),
'token_type_ids': torch.Size([8, 67]),
'labels': torch.Size([8])}
实现按照批次最长长度完成动态填充。
更多推荐

所有评论(0)