前端大模型解析:Tokenization与Embedding的基础概念

本文解析了Tokenizer和Embedding在大规模语言模型中的重要性以及基本实现方法。

原文标题:前端大模型入门(三):编码(Tokenizer)和嵌入(Embedding)解析

原文作者:阿里云开发者

冷月清谈:

本文介绍了大规模语言模型(LLM)中的两个核心概念:Tokenizer和Embedding。Tokenizer将自然语言文本转换为模型可处理的整数ID,而Embedding则将这些ID转化为稠密向量,以捕捉语义关系。文章具体阐述了Tokenizer的工作原理及其分类,包括词级、子词级和字符级Tokenizer,并解释了Embedding的定义、特性和不同类型,如静态与上下文相关的Embedding。具体代码示例展示了如何在前端使用JavaScript实现Tokenizer和Embedding的功能。理解这两个概念是学习LLM的重要基础。

怜星夜思:

1、在实际应用中,选择哪种Tokenizer更好?
2、Embedding的维度会如何影响模型性能?
3、如何在实际项目中优化Tokenizer与Embedding的使用?

原文内容

阿里妹导读


本文介绍了大规模语言模型(LLM)中的两个核心概念:Tokenizer和Embedding。Tokenizer将文本转换为模型可处理的数字ID,而Embedding则将这些ID转化为能捕捉语义关系的稠密向量。文章通过具体示例和代码展示了两者的实现方法,帮助读者理解其基本原理和应用场景。

作者|想飞的雪糕

LLM的核心是通过对语言进行建模来生成自然语言输出或理解输入,两个重要的概念在其中发挥关键作用:Tokenizer 和 Embedding。本篇文章将对这两个概念进行入门级介绍,并提供了针对前端的js示例代码,帮助读者理解它们的基本原理/作用和如何使用。

一、什么是Tokenizer?

Tokenizer 是一种将自然语言文本转化为模型可以处理的数字表示的工具。自然语言是由词、子词或字符组成的,而模型无法直接处理这些符号,它们只能处理数字。因此,Tokenizer的主要任务就是将文本转换为一系列数字。


1.1 Tokenizer的工作原理

Tokenizer通过查表的方式,将每个单词、子词或者字符映射为一个唯一的整数ID。这些整数ID作为模型的输入,帮助模型将语言处理为结构化的形式。

以句子“我喜欢学习”为例,一个简单的Tokenizer可能将其分解为每个汉字,并为每个汉字分配一个唯一的整数ID,如下:

  • “我” -> 1
  • “喜欢” -> 2, 3
  • “学习” -> 4, 5

在实际应用中,很多语言模型使用更复杂的分词方式,如子词分割。子词分割允许模型将罕见词分割为多个子词单元,从而提升泛化能力。例如,常见的子词分割方法包括BPE(Byte Pair Encoding)和WordPiece,这些方法可以将长词拆分为更小的、频率更高的子词,增强模型处理罕见词汇的能力。


1.2 Tokenizer的种类

    • 词级别(Word-level)Tokenizer:将每个词作为一个Token。适用于语言如英文等分隔明确的文本,但对于中文等无空格分隔的语言不太适合。
    • 子词级别(Subword-level)Tokenizer:基于统计方法,将文本分割为高频子词单元。BPE和WordPiece是常见的子词分割算法。
    • 字符级别(Character-level)Tokenizer:将每个字符视为一个Token。这种方法适用于字符构成较复杂的语言(如中文),但会导致较长的序列输入。


    1.3 为什么需要Tokenizer?

      • 将文本转化为数字:语言模型需要处理的是数字而不是文本。Tokenizer将文本符号转换为数字ID,是进入模型的第一步。
      • 词汇管理:通过分词,Tokenizer建立了一个词汇表,其中每个词或子词都对应一个唯一的ID。这让模型可以在推理时迅速查找词的表示。
      • 提升模型的泛化能力:通过分词,特别是子词分词,模型能够处理罕见词和新词,因为它可以将新词拆解为更小的子词单元,避免出现完全未知的词。


      1.4 Tokenizer 示例代码 

      其实python相关的库比较多,这里就用一个0依赖的js库来测试,自己也可以子串匹配实现。

      npm install @lenml/tokenizers
      
      import { fromPreTrained } from "@lenml/tokenizer-llama3";
      const tokenizer = fromPreTrained();
      const tokens = tokenizer.apply_chat_template(
       [
         {
           role: "system",
           content: "你是一个有趣的ai助手",
         },
         {
           role: "user",
           content: "好好,请问怎么去月球?",
         },
       ]
      ) as number[];
      // 转化成token的数组
      console.log(tokens);
      const chat_content = tokenizer.decode(tokens);
      // 还原了的数据
      console.log(chat_content);
      

      二、什么是Embedding?

      Embedding 是将Tokenizer生成的整数ID转化为稠密的向量表示的过程。与Tokenizer将文本转换为离散的整数ID不同,Embedding生成的是连续的实数值向量,这些向量能够捕捉词之间的语义关系。


      2.1 Embedding的工作原理

      在Embedding阶段,语言模型通过查表的方式,将每个整数ID映射到一个高维向量空间中的向量。这个向量通常是一个固定维度的向量(例如,300维、512维或768维),用来表示单词或子词的语义特征。

      例如,经过Tokenizer处理的文本“我喜欢学习”可能会生成整数ID序列 [1, 2, 3, 4, 5]。在Embedding阶段,这些ID会被转换为稠密向量表示,如:

        • “我” -> [0.25, -0.34, 0.15, ...]
        • “喜欢” -> [0.12, 0.57, -0.22, ...], [0.11, -0.09, 0.31, ...]
        • “学习” -> [0.33, -0.44, 0.19, ...], [0.09, 0.23, -0.41, ...]

        这些向量并不是随机生成的,它们是在模型的训练过程中被学习得到的。Embedding向量的维度固定,但向量的数值根据模型对词语上下文的理解不断更新和优化,最终形成一个语义丰富的向量表示。


        2.2 Embedding的种类

          • 词向量(Word Embedding):

            如Word2Vec、GloVe等方法,通过静态词向量将词语映射到向量空间中。这些方法的Embedding是静态的,即同一个词在不同上下文中具有相同的向量。

          • 上下文相关的Embedding:

            如BERT、GPT等方法生成的Embedding,是基于上下文的动态向量。同一个词在不同的上下文中可能有不同的向量表示,从而更加精准地捕捉语言中的多义性和语境变化。


          2.3 为什么需要Embedding?

            • 捕捉词之间的语义关系:通过Embedding,模型可以将语义相似的词表示为相近的向量。例如,“猫”和“狗”的向量在空间中可能非常接近,而“猫”和“车”的向量则会相距较远。

            • 连续性表示:与离散的整数ID不同,Embedding向量是连续的。这使得模型能够更好地进行计算和优化,因为连续的数值表示可以更容易进行梯度计算和模型学习。

            • 语义压缩:Embedding将高维的语言信息压缩到一个固定的向量空间中,这样模型就可以高效地处理输入并捕捉到其中的重要语义特征。


            2.4 使用 TensorFlow.js实现一个嵌入层

            接下来,我们用 TensorFlow.js 来实现一个简单的Embedding层。

            首先安装 TensorFlow.js:

            npm install @tensorflow/tfjs
            
            然后我们创建一个简单的Embedding层,将Token IDs转换为对应的Embedding向量。
            const tf = require('@tensorflow/tfjs');
            // 假设词汇表大小为10000,嵌入维度为300
            const vocabSize = 10000;
            const embeddingDim = 300;
            // 创建一个Embedding层
            const embeddingLayer = tf.layers.embedding({inputDim: vocabSize, outputDim: embeddingDim});
            // 输入是之前Tokenizer的Token IDs
            const tokenIds = tf.tensor([[1045, 2293, 4083]]);  // Batch size为1,三个Token
            // 使用Embedding层将Token IDs转化为Embedding向量
            const embeddings = embeddingLayer.apply(tokenIds);
            embeddings.print();  // 输出Embedding结果
            
            在这个示例中,我们定义了一个词汇表大小为10000、嵌入维度为300的Embedding层。tokenIds代表之前从Tokenizer生成的Token ID序列,经过Embedding层后,生成对应的300维度的稠密向量。

            注意下tfjs在浏览器和nodejs的时候不同的backend性能和表现有点差异,但基本可用,详细接口参考TensorFlow.js API。

            另外有时候进行向量化比较吃资源,或者需要处理大量文本和超高向量时,可使用各个AI平台提供的接口,一般叫做嵌入/向量化/句向量等。

            三、Tokenizer和Embedding的关系

            在LLM中,TokenizerEmbedding是文本处理的两个连续步骤:

              1. Tokenizer负责将文本分割为Token,并将这些Token映射为离散的整数ID。

              2. Embedding则将这些整数ID进一步转化为稠密的向量表示,以便模型能够进行深度学习和优化。

              它们的关系可以简单总结为:Tokenizer将语言中的离散符号表示成模型可以识别的离散ID,而Embedding则将这些离散ID转化为连续的向量,以便捕捉词之间的语义关系。

              四、总结

              在大规模语言模型(LLM)中,TokenizerEmbedding是两个基础且关键的步骤。Tokenizer通过分词和映射,将文本转化为模型可以处理的数字序列。而Embedding则将这些数字序列进一步转化为语义丰富的向量表示。这两个步骤共同构成了LLM处理自然语言输入的基础,为模型的语义理解和生成提供了强大的支持。

              对于初学者来说,理解Tokenizer和Embedding的作用及其背后的原理,将为深入学习LLM及其应用打下坚实的基础。

              可以尝试结合使用多种Tokenizer策略。在预处理阶段,先使用子词分级Tokenizer来处理大部分文本,再用词级Tokenizer以及字符级Tokenizer来修补特定用例。

              哈哈,假如你是做翻译的,字符级Tokenizer有时也能捕捉到细微的语义差异,虽然较长的序列输入会让模型运行变慢,还是值得尝试的。

              我建议在Embedding层中引入动态学习机制,让模型在训练过程中根据样本的语境变化自动调节向量,这样可以使特征更加敏感。

              这其实要看具体的应用场景。如果处理的是英文,词级Tokenizer会比较方便。但对于中文或其他没有明显分隔的语言,子词级Tokenizer就更加适用。

              我觉得,选择Embedding维度还得结合数据集和任务来定,比如对于短文本,使用更小的维度有时也能表现得很好。

              可以通过关键词提取和分层嵌入等方式,减少对不必要信息的处理,从而加快计算速度。听说有些产品在这方面做了挺多优化。

              我认为Embedding的维度直接影响到模型的表现。维度太低可能无法捕捉到丰富的语义关系,而维度过高又可能导致计算复杂度骤增,甚至导致过拟合。

              我觉得子词级Tokenizer会是一个更灵活的选择,它能处理罕见词,比如新造的词汇。而且即使是语言混合的情况,它的分词能力也能帮助解读文本中的主语。

              确实!我看到有些模型采用了不同维度的Embedding层,旨在搞清楚在特定任务下哪个效果最好,是实验的过程。