文章目录
前言
在自然语言处理(NLP)任务中,特别是问答任务中,标记、样本映射和偏移映射是用于处理长文本和标记位置的重要概念。我们一起来学习这三个概念
一、样本映射(Sample Mapping)
样本映射是指将处理后的数据片段(通常是经过截断和填充的文本片段)映射回原始的样本数据。这在处理长文本时尤为重要,因为一个长文本可能会被分割成多个片段,以便于输入到模型中。
例子
假设有一个非常长的上下文,无法在一次传递中输入到模型。这个上下文会被分成多个片段,每个片段的长度适合模型的输入限制。样本映射记录了这些片段属于哪个原始样本。
sample_mapping = inputs.pop("overflow_to_sample_mapping")
在这个例子中,overflow_to_sample_mapping
是一个列表,表示每个生成的片段对应的原始样本索引。这样,处理这些片段时,仍然可以知道它们源自哪个原始样本。
二、偏移映射(Offset Mapping)
偏移映射是指原始文本中的字符位置到标记(token)中的位置之间的映射。它帮助我们将标记化后的文本位置映射回原始文本的位置,特别是在问答任务中,用于确定答案的起始和结束位置。
例子
offset_mapping = inputs.pop("offset_mapping")
在这个例子中,offset_mapping
是一个列表,其中每个元素都是一个二元组(start, end),表示该标记在原始文本中的起始和结束字符位置。
三、具体操作解释
1. 样本映射的用途
在代码中,样本映射用于从生成的片段找到原始样本的答案。例如:
sample_index = sample_mapping[i]
answers = examples["answers"][sample_index]
这段代码通过 sample_mapping
找到当前片段所属的原始样本索引,然后获取该样本的答案信息。
2. 偏移映射的用途
偏移映射用于将答案的字符位置映射到标记的位置。例如:
offset = offset_mapping[i]
这段代码获取当前片段的偏移映射,然后通过检查偏移量来确定答案的起始和结束标记位置。
四、代码示例解析
让我们结合代码示例进行更详细的解析:
for i, offset in enumerate(offset_mapping):
input_ids = inputs["input_ids"][i]
cls_index = input_ids.index(tokenizer.cls_token_id)
sequence_ids = inputs.sequence_ids(i)
sample_index = sample_mapping[i]
answers = examples["answers"][sample_index]
if len(answers["answer_start"]) == 0:
start_positions.append(cls_index)
end_positions.append(cls_index)
else:
start_char = answers["answer_start"][0]
end_char = start_char + len(answers["text"][0])
token_start_index = 0
while sequence_ids[token_start_index] != 1:
token_start_index += 1
token_end_index = len(input_ids) - 1
while sequence_ids[token_end_index] != 1:
token_end_index -= 1
if not (offset[token_start_index][0] <= start_char and offset[token_end_index][1] >= end_char):
start_positions.append(cls_index)
end_positions.append(cls_index)
else:
while token_start_index < len(offset) and offset[token_start_index][0] <= start_char:
token_start_index += 1
start_positions.append(token_start_index - 1)
while offset[token_end_index][1] >= end_char:
token_end_index -= 1
end_positions.append(token_end_index + 1)
-
获取样本和偏移映射:
sample_mapping[i]
获取当前片段对应的原始样本索引。offset_mapping[i]
获取当前片段的偏移映射。
-
确定答案的位置:
start_char
和end_char
是答案在原始文本中的起始和结束字符位置。token_start_index
和token_end_index
是答案在片段中的起始和结束标记位置。
-
处理没有答案的情况:
- 如果答案为空,则起始和结束位置都设置为
[CLS]
位置。
- 如果答案为空,则起始和结束位置都设置为
-
处理有答案的情况:
- 确定答案的标记位置,确保答案在当前片段的标记范围内。
通过样本映射和偏移映射,可以在处理长文本时保持对原始样本的引用,并准确地将答案位置从字符级别映射到标记级别。这在问答任务中尤为重要,因为模型需要知道答案在标记序列中的精确位置。
五、标记(token)
在自然语言处理(NLP)中,标记(Token)是指文本数据被分割成的最小单元。标记可以是单词、子单词、字符或其他语义单位,具体取决于所使用的分词算法和任务需求。
1. 标记的具体解释
1)单词级标记:
- 这是最常见的标记形式,每个标记是一个完整的单词。例如,句子 “I love NLP” 会被分割成三个标记:
["I", "love", "NLP"]
。
2)子词级标记:
- 在某些情况下,特别是使用BERT或GPT等预训练模型时,文本会被分割成子词单位。例如,句子 “playing” 可能被分割成两个标记:
["play", "##ing"]
。这种方法有助于处理罕见词和词形变化。
3)字符级标记:
- 有些模型直接处理字符级标记,每个字符都是一个标记。例如,句子 “NLP” 会被分割成三个标记:
["N", "L", "P"]
。
标记化过程
标记化是将文本转换为标记序列的过程。这个过程通常由一个分词器(Tokenizer)来完成。
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
tokens = tokenizer.tokenize("I love NLP")
print(tokens)
# Output: ['i', 'love', 'nlp']
在这个例子中,分词器将句子 “I love NLP” 分割成三个标记:['i', 'love', 'nlp']
。
2.标记在代码中的作用
在预处理函数中,标记化的主要作用是将自然语言文本转换成模型可以处理的输入格式。以下是具体的步骤和作用:
1)文本编码:
inputs = tokenizer(
questions,
examples["context"],
max_length=384,
truncation="only_second",
stride=128,
return_overflowing_tokens=True,
return_offsets_mapping=True,
padding="max_length",
)
这段代码将问题和上下文文本编码为标记序列。tokenizer
将文本分割成标记,并将它们转换成模型可以理解的ID。
2)标记序列和偏移映射:
offset_mapping = inputs.pop("offset_mapping")
偏移映射用于跟踪每个标记在原始文本中的起始和结束字符位置。这对于问答任务特别重要,因为需要准确地定位答案的位置。
3)确定答案的标记位置:
for i, offset in enumerate(offset_mapping):
...
while sequence_ids[token_start_index] != 1:
token_start_index += 1
...
通过偏移映射,可以将答案在原始文本中的字符位置转换为标记序列中的索引。这些索引用于标记答案的起始和结束位置。
3.标记的重要性
标记是NLP模型处理文本的基础。标记化过程将自然语言转换为结构化数据,使模型能够有效地进行计算。通过标记化,模型可以理解和处理输入文本,并生成适当的输出。
4.小结
标记(Token)是将文本数据分割成的最小单元,标记化是将文本转换为标记序列的过程。在预处理函数中,标记化用于将问题和上下文文本转换为模型输入格式,并通过偏移映射确定答案的精确位置。这些步骤确保模型能够正确理解和处理输入数据,从而在问答任务中生成准确的结果。
是的,offset_mapping
确实表示文本中每个标记(token)在原始文本中的起始和结束字符位置。它是一个非常有用的工具,特别是在需要将模型的输出结果映射回原始文本时,比如在问答任务中确定答案的位置。
六、如何理解“offset_mapping 是标记在原始文本里的 起始和结束位置”
我们一步一步来解析这句话。
1. 文本标记化
首先,原始文本会被分割成标记(token)。假设我们有以下句子:
The quick brown fox.
假设分词器将其分割成以下标记:
['The', 'quick', 'brown', 'fox', '.']
2. 生成偏移映射
在生成这些标记的同时,分词器还会记录每个标记在原始文本中的起始和结束位置。这就构成了偏移映射(offset mapping)。例如,对于上述句子,偏移映射可能是:
[(0, 3), (4, 9), (10, 15), (16, 19), (19, 20)]
每个元组中的第一个值表示标记在原始文本中的起始字符位置,第二个值表示结束字符位置(不包括结束位置的字符)。
3. 代码示例
在代码中,偏移映射的生成和使用如下:
# 预处理函数
def preprocess_function(examples):
# 去除问题的两端空白
questions = [q.strip() for q in examples["question"]]
# 使用 tokenizer 进行编码
inputs = tokenizer(
questions,
examples["context"],
max_length=384,
truncation="only_second",
stride=128,
return_overflowing_tokens=True,
return_offsets_mapping=True,
padding="max_length",
)
# 提取偏移映射
offset_mapping = inputs.pop("offset_mapping")
# 以下省略其他处理步骤...
return inputs
# 对数据集进行映射
train_dataset = train_dataset.map(preprocess_function, batched=True, remove_columns=dataset["train"].column_names)
eval_dataset = eval_dataset.map(preprocess_function, batched=True, remove_columns=dataset["validation"].column_names)
4. 解析偏移映射
假设偏移映射是这样的:
offset_mapping = [
[(0, 3), (4, 9), (10, 15), (16, 19), (19, 20)],
# 其他样本的偏移映射
]
我们可以通过偏移映射将模型的输出结果(如答案的起始和结束标记位置)映射回原始文本。例如:
for i, offset in enumerate(offset_mapping):
# 获取当前样本的偏移
current_offset = offset_mapping[i]
# 示例:获取第一个标记的起始和结束位置
start_position = current_offset[0][0] # 第一个标记的起始位置
end_position = current_offset[0][1] # 第一个标记的结束位置
print(f"第一个标记在原始文本中的起始位置: {start_position}, 结束位置: {end_position}")
5. 小结
offset_mapping
是一个列表,包含了每个标记在原始文本中的起始和结束字符位置。它在将标记化后的文本映射回原始文本时非常有用,尤其是在需要确定答案在原始文本中的具体位置的任务中,如问答系统。通过使用偏移映射,我们可以确保模型的输出结果与原始文本保持一致。