以下将详细介绍 MyTokenizer
类中的关键方法,包括 __call__
、decode
、add_special_tokens
和 from_pretrained
。将结合数学语言和直观的操作步骤,详细描述每个方法的算法和数据流程。
__call__
方法用于将输入的文本或文本列表编码为模型可接受的格式。主要完成以下功能:
- 分词:将文本拆分为词元(tokens)。
- 映射:将词元转换为对应的词汇表ID(token IDs)。
- 截断:根据指定的最大长度截断序列。
- 填充:对序列进行填充以达到统一的长度。
- 生成注意力掩码:标识实际词元和填充词元的位置。
- 生成偏移映射:记录每个词元在原始文本中的位置。
- 生成特殊标记掩码:标识特殊标记在序列中的位置。
-
输入处理
-
单个文本处理:如果输入是字符串
texts
,则将其转换为包含一个元素的列表。if isinstance(texts, str): texts = [texts]
-
-
初始化结果字典
-
创建一个字典
encoded_batch
,用于存储编码结果。encoded_batch = { 'input_ids': [], 'attention_mask': [], 'offset_mapping': [], 'special_tokens_mask': [] }
-
-
遍历输入文本列表
-
对于每个文本
text
,执行以下步骤:for text in texts: # 后续步骤
-
-
分词
-
调用
tokenize
方法,将文本拆分为词元列表tokens
。tokens = self.tokenize(text)
-
示例:对于文本
"你好,世界!"
,分词结果为:tokens = ['你', '好', ',', '世', '界', '!']
-
-
词元转换为ID
-
调用
convert_tokens_to_ids
方法,将词元列表转换为对应的ID列表token_ids
。token_ids = self.convert_tokens_to_ids(tokens)
-
映射关系:使用词汇表
self.vocab
。-
数学表示:
$$ \text{token_ids}[i] = \text{self.vocab.get}(\text{tokens}[i], \text{self.vocab.get}('[UNK]', 0)) $$
-
-
示例:假设词汇表中
'你'
的ID为6,'好'
的ID为7,则:token_ids = [6, 7, 8, 9, 10, 11]
-
-
截断
-
如果
truncation=True
且token_ids
长度超过max_length
,则截断。if truncation and len(token_ids) > max_length: token_ids = token_ids[:max_length]
-
数学表示:
$$ \text{token_ids} = \text{token_ids}[: \text{max_length}] $$
-
-
生成注意力掩码
-
创建一个与
token_ids
长度相同的列表attention_mask
,其中有效词元位置为1。attention_mask = [1] * len(token_ids)
-
数学表示:
$$ \text{attention_mask}[i] = 1, \quad \forall i \in [0, \text{len}(\text{token_ids}) - 1] $$
-
-
填充
-
如果
padding='max_length'
,则对token_ids
和attention_mask
进行填充。if padding == 'max_length': padding_length = max_length - len(token_ids) token_ids += [self.vocab.get(self.pad_token)] * padding_length attention_mask += [0] * padding_length
-
计算填充长度:
$$ \text{padding_length} = \text{max_length} - \text{len}(\text{token_ids}) $$
-
填充
token_ids
和attention_mask
:$$ \text{token_ids} = \text{token_ids} + [\text{PAD_ID}] \times \text{padding_length} $$
$$ \text{attention_mask} = \text{attention_mask} + [0] \times \text{padding_length} $$
-
示例:如果
max_length=10
,原始token_ids
长度为6,则需要填充4个[PAD]
。
-
-
生成偏移映射
-
如果
return_offsets_mapping=True
,则计算每个词元在原始文本中的起止位置。if return_offsets_mapping: offsets = [] current_position = 0 for token in tokens: token_length = len(token) offsets.append((current_position, current_position + token_length)) current_position += token_length if padding == 'max_length': offsets += [(0, 0)] * (max_length - len(offsets)) else: offsets = None
-
计算过程:
-
初始化
current_position = 0
。 -
对于每个词元
token
:-
计算
token_length = len(token)
。 -
偏移量为
(current_position, current_position + token_length)
。 -
更新
current_position += token_length
。
-
-
-
数学表示:
$$ \text{offsets}[i] = (\sum_{k=0}^{i-1} \text{len}(\text{tokens}[k]), \sum_{k=0}^{i} \text{len}(\text{tokens}[k])) $$
-
示例:对于
tokens = ['你', '好', ',', '世', '界', '!']
,偏移映射为:offsets = [(0,1), (1,2), (2,3), (3,4), (4,5), (5,6)]
-
填充偏移映射:如果需要填充,则在
offsets
后添加(0, 0)
。
-
-
生成特殊标记掩码
-
如果
return_special_tokens_mask=True
,则生成特殊标记掩码special_tokens_mask
。if return_special_tokens_mask: special_tokens_mask = [1 if self.id_to_token.get(id_) in self.special_tokens else 0 for id_ in token_ids] else: special_tokens_mask = None
-
计算过程:
-
对于每个
id_
:- 如果
id_
对应的词元在self.special_tokens
中,则标记为1,否则为0。
- 如果
-
-
数学表示:
$$ \text{special_tokens_mask}[i] = \begin{cases} 1, & \text{if } \text{id_to_token}(\text{token_ids}[i]) \in \text{special_tokens} \ 0, & \text{otherwise} \end{cases} $$
-
示例:如果
token_ids
包含[PAD]
的ID,则对应位置为1。
-
-
结果收集
-
将计算得到的各项添加到
encoded_batch
中。encoded_batch['input_ids'].append(token_ids) encoded_batch['attention_mask'].append(attention_mask) if offsets is not None: encoded_batch['offset_mapping'].append(offsets) if special_tokens_mask is not None: encoded_batch['special_tokens_mask'].append(special_tokens_mask)
-
-
返回结果
-
返回包含所有编码信息的
encoded_batch
。return encoded_batch
-
-
输入:
texts = ["你好,世界!"]
,max_length = 10
,padding = 'max_length'
。 -
步骤概览:
-
分词:
tokens = ['你', '好', ',', '世', '界', '!']
-
映射为ID:
token_ids = [6, 7, 8, 9, 10, 11]
-
截断:长度未超过
max_length
,无需截断。 -
注意力掩码:
attention_mask = [1, 1, 1, 1, 1, 1]
-
填充:填充
[PAD]
的ID,假设[PAD]
的ID为0。padding_length = 10 - 6 = 4 token_ids += [0, 0, 0, 0] attention_mask += [0, 0, 0, 0]
最终
token_ids = [6, 7, 8, 9, 10, 11, 0, 0, 0, 0]
-
偏移映射:
offsets = [(0,1), (1,2), (2,3), (3,4), (4,5), (5,6), (0,0), (0,0), (0,0), (0,0)]
-
特殊标记掩码:
special_tokens_mask = [0, 0, 0, 0, 0, 0, 1, 1, 1, 1]
-
-
输出:
{ 'input_ids': [[6, 7, 8, 9, 10, 11, 0, 0, 0, 0]], 'attention_mask': [[1, 1, 1, 1, 1, 1, 0, 0, 0, 0]], 'offset_mapping': [[(0,1), (1,2), (2,3), (3,4), (4,5), (5,6), (0,0), (0,0), (0,0), (0,0)]], 'special_tokens_mask': [[0, 0, 0, 0, 0, 0, 1, 1, 1, 1]] }
decode
方法用于将ID序列转换回原始文本。主要完成以下功能:
- ID到词元的映射:将ID列表转换为词元列表。
- 拼接词元:将词元列表拼接成字符串。
- 移除特殊标记:从生成的文本中移除特殊标记。
- 返回结果:返回去除特殊标记后的文本。
-
输入处理
-
如果
ids
的第一个元素是列表,则取第一个列表。if isinstance(ids[0], list): ids = ids[0]
-
-
ID转换为词元
-
调用
convert_ids_to_tokens
方法,将ID列表转换为词元列表tokens
。tokens = self.convert_ids_to_tokens(ids)
-
映射关系:使用
self.id_to_token
。-
数学表示:
$$ \text{tokens}[i] = \text{self.id_to_token.get}(\text{ids}[i], '[UNK]') $$
-
-
-
拼接词元
-
将词元列表拼接成字符串
text
。text = ''.join(tokens)
-
-
移除特殊标记
-
遍历
self.special_tokens
,将文本中的特殊标记替换为空字符串。for token in self.special_tokens: text = text.replace(token, '')
-
-
去除首尾空格并返回
-
使用
strip()
方法去除首尾空格。return text.strip()
-
-
输入:
ids = [6, 7, 8, 9, 10, 11, 0, 0, 0, 0]
(对应之前编码的input_ids
)。 -
步骤概览:
-
ID转换为词元:
tokens = ['你', '好', ',', '世', '界', '!', '[PAD]', '[PAD]', '[PAD]', '[PAD]']
-
拼接词元:
text = '你好,世界![PAD][PAD][PAD][PAD]'
-
移除特殊标记:
-
移除
[PAD]
:text = '你好,世界!'
-
-
去除首尾空格:
text = '你好,世界!'
-
-
输出:返回字符串
'你好,世界!'
add_special_tokens
方法用于向分词器中添加新的特殊标记。主要完成以下功能:
- 更新词汇表:将新的特殊标记添加到词汇表中。
- 更新ID映射:在
id_to_token
中添加新的ID到标记的映射。 - 更新特殊标记列表:在
special_tokens
中添加新的特殊标记。 - 更新特殊标记映射:在
special_tokens_map
中更新特殊标记的键值对。
-
遍历特殊标记字典
-
对于
special_tokens_dict
中的每个键值对key
,value
,执行以下操作:for key, value in special_tokens_dict.items(): # 后续步骤
-
-
处理值为列表的情况
-
如果
value
是列表,表示有多个特殊标记需要添加。if isinstance(value, list): for token in value: # 添加单个标记
-
-
添加单个特殊标记
-
检查标记是否不在词汇表中,如果不在则添加。
if token not in self.vocab: index = len(self.vocab) self.vocab[token] = index self.id_to_token[index] = token self.special_tokens.append(token)
-
更新词汇表和ID映射:
-
将新的标记添加到
self.vocab
,ID为当前词汇表长度。 -
在
self.id_to_token
中添加新的ID到标记的映射。
-
-
更新特殊标记列表:
- 将新的标记添加到
self.special_tokens
。
- 将新的标记添加到
-
-
处理值为单个标记的情况
-
如果
value
不是列表,直接处理单个标记。else: if value not in self.vocab: index = len(self.vocab) self.vocab[value] = index self.id_to_token[index] = value self.special_tokens.append(value)
-
-
更新特殊标记映射
-
在
self.special_tokens_map
中更新对应的键值对。self.special_tokens_map[key] = value
-
-
输入:
special_tokens_dict = {'additional_special_tokens': ['[NEW_TOKEN]']}
-
步骤概览:
-
遍历字典:
key = 'additional_special_tokens'
value = ['[NEW_TOKEN]']
-
值为列表,遍历列表:
token = '[NEW_TOKEN]'
-
检查并添加标记:
-
如果
'[NEW_TOKEN]'
不在self.vocab
,则:-
index = len(self.vocab)
-
添加到
self.vocab
:self.vocab['[NEW_TOKEN]'] = index
-
添加到
self.id_to_token
:self.id_to_token[index] = '[NEW_TOKEN]'
-
添加到
self.special_tokens
:self.special_tokens.append('[NEW_TOKEN]')
-
-
-
更新特殊标记映射:
self.special_tokens_map['additional_special_tokens'] = ['[NEW_TOKEN]']
-
-
结果:
-
词汇表
self.vocab
新增'[NEW_TOKEN]'
。 -
ID映射
self.id_to_token
新增对应关系。 -
特殊标记列表
self.special_tokens
新增'[NEW_TOKEN]'
。
-
from_pretrained
是一个类方法,用于从预训练的分词器文件中加载分词器。主要完成以下功能:
- 加载
tokenizer.json
:获取词汇表、合并规则和添加的标记。 - 加载
special_tokens_map.json
:获取特殊标记的映射。 - 实例化
MyTokenizer
对象:使用加载的数据创建分词器实例。
-
加载
tokenizer.json
文件-
构建文件路径,打开并读取文件内容。
with open(os.path.join(path, 'tokenizer.json'), 'r', encoding='utf-8') as f: tokenizer_json = json.load(f)
-
获取数据:
-
词汇表:
vocab = tokenizer_json['model']['vocab']
-
合并规则:
merges = tokenizer_json['model']['merges']
-
添加的标记:
added_tokens = tokenizer_json['added_tokens']
-
-
-
加载
special_tokens_map.json
文件-
构建文件路径,打开并读取文件内容。
with open(os.path.join(path, 'special_tokens_map.json'), 'r', encoding='utf-8') as f: special_tokens_map = json.load(f)
-
-
实例化
MyTokenizer
对象-
使用加载的数据,调用类的构造方法创建实例。
return cls(vocab, merges, added_tokens, special_tokens_map)
-
-
输入:分词器文件所在的路径
path
。 -
步骤概览:
-
读取
tokenizer.json
:-
获取词汇表
vocab
。 -
获取合并规则
merges
。 -
获取添加的标记
added_tokens
。
-
-
读取
special_tokens_map.json
:- 获取特殊标记映射
special_tokens_map
。
- 获取特殊标记映射
-
实例化对象:
- 调用
__init__
方法,创建MyTokenizer
实例。
- 调用
-
-
输出:返回一个初始化完毕的
MyTokenizer
对象。。