分词器相关技术

65 阅读3分钟

在学习分词器相关技术的时候偶然看到一个讲解视频,觉得很惊艳,于是写篇博客记录一下
视频地址:LLM分词器相关技术介绍

文本分词

这个阶段的目标是对文本进行分词和嵌入,以便用于LLM

text = "Hello, world. This, is a test."
preprocessed = re.split(r'([,.?_!"()\']|--|\s)', text)  #对文本根据各种标点符号进行切割
preprocessed = [item.strip() for item in result if item.strip()] #去掉空白字符并做成词汇表
print(len(result))

将词元转换为词元IDs

从这些词元中,我们可以构建一个词汇表,它包含了所有独特的词元

all_words = sorted(list(set(preprocessed)))
vocab_size = len(all_words)
print(vocab_size)

将所有这些整合到一个分词器类中(tokenizer class)中

class SimpleTokenizerV1:
    def __init__(self, vocab):
        self.str_to_int = vocab
        self.int_to_str = {i:s for s,i in vocab.items()}
    
    def encode(self, text):
        preprocessed = re.split(r'([,.?_!"()\']|--|\s)', text)
        preprocessed = [item.strip() for item in preprocessed if item.strip()]
        ids = [self.str_to_int[s] for s in preprocessed]
        return ids
        
    def decode(self, ids):
        text = " ".join([self.int_to_str[i] for i in ids])
        # Replace spaces before the specified punctuations
        text = re.sub(r'\s+([,.?!"()\'])', r'\1', text)
        return text

添加特殊上下文词元

一些新文本中的词可能不在词汇表中,这种情况被称为OOV。
一些分词器使用特殊词元来帮助LLM获取额外上下文信息

  • [BOS]标志文本的开始
  • [EOS]标记文本结束的位置(通常用于连接多个不相关的文本,如两篇不同的维基百科文章或者两本不同的书等)
  • [PAD]如果我们训练LLM的批量大于1(我们可能会包含多个长度不同的文本;使用填充标记,我们会将较短的文本填充为最长的文本,这样所有文本的长度就相等了)
  • [UNK]代表未列入词汇表的单词

BPE分词器

实现原理

  • 反复合并最常见的字符对,逐渐生成常见字词单元,将所有词汇表示为这些字词单元的组合
    • 中文的最小单位为汉字,初始词汇表为汉字
    • 英文的最小单位为字母,初始词汇表为字母
    • 中文以句子为单位统计相邻的词汇频率,英文以单词为单位统计相邻的词汇频率
    • 当两个字符合并为一个新的字词单元后,原来的两个单个字符通常会保留在词汇表
  • 统计所有字符对的出现频率,采用贪心算法将频率最高的字符进行合并
  • 例如,合并"wo"后,词汇表将增加"wo",而"word"将被表示为['wo','r','d']
  • 不断重复统计频率和合并,直到达到预定的词汇表大小或达到指定的合并次数为止

WordPiece分词器

  • 可以有效处理OOV的词汇
  • 使用了基于联合概率的策略,特别适合上下文相关的任务,如语言建模和预训练模型
  • 当出现OOV的情况,WordPiece会将OOV词分解为更小的字词(subword)或者字符(character);如果OOV词无法分解为已知的字词或者字符,WordPiece会将其替换为[UNK] BPE和WordPiece都是字词分词方法,BPE是根据字符对频率进行合并,而WordPiece是根据语言模型概率合并,生成的字词可能更细粒度(例如,"Hello"经过BPE可能被保留为一个完整的字词,而经过WP可能会被拆分为["hel", "##lo"])