如何将字符级答案索引转换为BERT分词后的词元索引
如何将字符级答案索引转换为BERT分词后的词元索引
嘿,这个问题我之前处理过,刚好能帮到你!要把字符级的答案索引转换成BERT分词后的词元索引,核心是利用BERT分词器的字符-词元映射功能,下面给你一步步拆解怎么做:
1. 先准备好基础代码
首先加载你的数据集和BERT分词器,这部分你已经做了,我再贴一遍方便参考:
from datasets import load_dataset from transformers import BertTokenizerFast # 加载adversarial_qa数据集 ds = load_dataset("UCLNLP/adversarial_qa", "adversarialQA") # 加载BERT大模型的分词器,注意用Fast版本才能获取字符映射 bert_tokenizer = BertTokenizerFast.from_pretrained('bert-large-uncased', return_token_type_ids=True)
2. 核心:写一个映射函数
关键是用return_offsets_mapping=True参数,让分词器返回每个词元对应的原始文本字符位置。然后我们通过这个映射关系,把答案的字符索引转换成词元索引:
def map_char_to_token(sample): # 提取样本里的关键信息 question = sample['question'] context = sample['context'] answer_text = sample['answers']['text'][0] char_start = sample['answers']['answer_start'][0] char_end = char_start + len(answer_text) # 对问题和上下文分词,同时获取字符-词元映射、类型ID(区分问题和上下文) tokenized = bert_tokenizer( question, context, return_offsets_mapping=True, truncation=True, # 处理超过BERT最大长度的文本 padding='max_length' # 可选,根据你的任务需求设置 ) offset_mapping = tokenized['offset_mapping'] token_type_ids = tokenized['token_type_ids'] # 0=问题部分,1=上下文部分 # 只筛选出上下文对应的词元索引,避免遍历问题部分的词元 context_token_indices = [i for i, tti in enumerate(token_type_ids) if tti == 1] token_start = None token_end = None # 遍历上下文的词元,找到答案对应的词元位置 for idx in context_token_indices: token_char_start, token_char_end = offset_mapping[idx] # 找到包含答案起始字符的词元 if token_char_start <= char_start < token_char_end: token_start = idx # 找到包含答案结束字符的词元,找到后直接退出循环 if token_char_start < char_end <= token_char_end: token_end = idx break # 注意:Python切片是左闭右开,所以最终的结束词元索引要+1,这样切片才能取到完整答案 if token_end is not None: token_end += 1 # 把转换后的词元索引添加到样本中 sample['token_start'] = token_start sample['token_end'] = token_end # 可选:保留分词后的其他字段(比如input_ids、attention_mask等) sample.update(tokenized) return sample
3. 应用函数到整个数据集
用Hugging Face Datasets的map方法,把这个函数批量应用到你的训练集(或其他子集):
# 生成带词元索引的新数据集 ds_train_with_token_indices = ds['train'].map(map_char_to_token)
4. 验证结果
拿你提供的第一个样本测试一下,看看是不是符合预期:
d0 = ds['train'][0] d0_processed = map_char_to_token(d0) print(f"词元起始索引:{d0_processed['token_start']},词元结束索引:{d0_processed['token_end']}") # 应该输出:词元起始索引:56,词元结束索引:61 print(bert_tokenizer.decode(d0_processed['input_ids'][d0_processed['token_start']:d0_processed['token_end']])) # 输出:isolated from the bloodstream,和你的示例一致
一些需要注意的边界情况
- 如果答案刚好跨多个词元:函数会自动找到起始和结束的词元,不用额外处理
- 如果文本过长被截断:如果答案刚好在截断后的文本之外,
token_start或token_end会是None,你可以根据需求过滤掉这些样本 - 如果数据集有多个答案:只需要修改函数,遍历
sample['answers']['text']和sample['answers']['answer_start']列表即可
备注:内容来源于stack exchange,提问作者Jack Peng
相关产品推荐
相关产品推荐

