You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将字符级答案索引转换为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.15 09:34:50