1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
| import os import regex from typing import Iterable, Iterator from collections import Counter from collections import deque import pickle
class tokenizer: def __init__(self,vocab,merges,special_tokens=None): self.merges=merges if special_tokens: vocab_reverse={byte_token:token_id for token_id,byte_token in vocab.items()} for special_token in set(special_tokens): byte_special_token= special_token.encode('utf-8') if byte_special_token not in vocab_reverse: vocab[len(vocab)]=byte_special_token self.vocab=vocab self.vocab_reverse={byte_token:token_id for token_id,byte_token in self.vocab.items()} self.pair2id={pair:i for i,pair in enumerate(merges)} self.special_tokens=special_tokens
def encode(self,text:str)->list[int]: if self.special_tokens: sorted_special_tokens=sorted(self.special_tokens,key=len,reverse=True) sp_tokens_deal_pattern='('+'|'.join(map(regex.escape,sorted_special_tokens))+')' document=regex.split(sp_tokens_deal_pattern,text) else: document=[text]
PAT=r"""'s|'t|'re|'ve|'m|'ll|'d| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+""" pre_tokenize_pattern=regex.compile(PAT)
ids=[] for doc in document:
if not doc: continue
byte_doc=doc.encode('utf-8') if self.special_tokens and doc in self.special_tokens and byte_doc in self.vocab_reverse: ids.append(self.vocab_reverse[byte_doc]) continue
for match in pre_tokenize_pattern.finditer(doc): substr=match.group().encode('utf-8') byte_substr=[bytes([i]) for i in substr] while(len(byte_substr)>=2): new_byte_substr=[] pairs=[(byte_substr[i],byte_substr[i+1]) for i in range(len(byte_substr)-1)] vaild_pairs=[pair for pair in pairs if pair in self.pair2id] if not vaild_pairs: break first_merge_pair=min(vaild_pairs,key=lambda p:self.pair2id[p]) index=0 while(index<len(byte_substr)): if index<len(byte_substr)-1 and byte_substr[index]==first_merge_pair[0] and byte_substr[index+1]==first_merge_pair[1]: new_byte_substr.append(first_merge_pair[0]+first_merge_pair[1]) index+=2
else: new_byte_substr.append(byte_substr[index]) index+=1 byte_substr=new_byte_substr
ids.extend(self.vocab_reverse[byte] for byte in byte_substr)
return ids
def decode(self,ids)->str: return b"".join(self.vocab[token_id] for token_id in ids).decode('utf-8',errors='replace')
def encode_iterable(self,iterator:Iterator[str])->Iterator[int]: for line in iterator: for token_id in self.encode(line): yield token_id
@classmethod def merge_substr(cls,str_tuple,max_pair,new_token_id)->tuple[int,...]: i=0 new_str_tuple=[] while i<len(str_tuple): if i<len(str_tuple)-1 and str_tuple[i]==max_pair[0] and str_tuple[i+1]==max_pair[1]: new_str_tuple.append(new_token_id) i+=2
else: new_str_tuple.append(str_tuple[i]) i+=1
return tuple(new_str_tuple)
@classmethod def train(cls, input_path: str | os.PathLike, vocab_size: int, special_tokens: list[str], **kwargs): with open(input_path,'rb') as f: text=f.read().decode('utf-8') if special_tokens: sorted_special_tokens=sorted(special_tokens,key=len,reverse=True) sp_tokens_deal_pattern='|'.join(map(regex.escape,sorted_special_tokens)) document=regex.split(sp_tokens_deal_pattern,text) else: document=[text]
PAT=r"""'s|'t|'re|'ve|'m|'ll|'d| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+""" pre_tokenize_pattern=regex.compile(PAT) substr_counter:dict[tuple[int,...],int]=Counter() for doc in document: if not doc: continue for match in pre_tokenize_pattern.finditer(doc): substr=match.group() if substr: str_tuple=tuple(substr.encode('utf-8')) substr_counter[str_tuple]+=1
vocab={token:bytes([token]) for token in range(256)} merges=[]
if special_tokens: for special_token in special_tokens: vocab[len(vocab)]=special_token.encode('utf-8')
merge_iters=vocab_size-len(vocab)
for _ in range(merge_iters): pair_dict={} for substr,count in substr_counter.items(): for i in range(len(substr)-1): pair=(substr[i],substr[i+1]) pair_dict[pair]=pair_dict.get(pair,0)+count if not pair_dict: break
max_pair=max(pair_dict,key=lambda p:(pair_dict[p],vocab[p[0]],vocab[p[1]])) new_token_id=len(vocab) new_substr_counter={}
for substr,count in substr_counter.items(): if max_pair[0] not in substr or max_pair[1] not in substr: new_substr_counter[substr]=new_substr_counter.get(substr,0)+count continue new_substr=tokenizer.merge_substr(substr,max_pair,new_token_id) new_substr_counter[new_substr]=new_substr_counter.get(new_substr,0)+count
vocab[new_token_id]=vocab[max_pair[0]]+vocab[max_pair[1]] substr_counter=new_substr_counter merges.append((vocab[max_pair[0]],vocab[max_pair[1]]))
return vocab,merges
@classmethod def from_files(cls, vocab_filepath, merges_filepath, special_tokens=None): with open(vocab_filepath,'rb') as f: vocab=pickle.load(f) with open(merges_filepath,'rb') as f: merges=pickle.load(f) return cls(vocab,merges,special_tokens)
|