比特派app下载载苹果版|tokenize

作者: 比特派app下载载苹果版
2024-03-07 21:29:40

NLP中的Tokenization - 知乎

NLP中的Tokenization - 知乎首发于QBrain切换模式写文章登录/注册NLP中的Tokenization薛定谔没养猫加油前言 今天我们来聊一聊NLP技术中的Tokenization。之所以想要聊这个话题,是因为,一方面在NLP技术中Tokenization是非常重要的一个环节,它是数据进入到模型进行计算之前所必须的一个步骤;一方面,不少NLPer可能关注的往往是模型的花里胡哨,炼丹Tricks的纷繁复杂又或者是数据清洗的枯燥无味,对于字符串数据进入到模型之前所必经的Tokenization环节知之甚少;另一方面,笔者曾在工作过程中无意发现字符经过XLM-Roberta的Tokenization会多出“_”这个特殊符号,于是在Tokenization这方面进行了一番调研,便有会意,遂欣然忘食,执笔著之。1. 引言1.1 什么是Tokenization 相信大家作为一个NLPer都很熟悉NLP的流程,如下图。无论大家熟悉不熟悉,请准许我先介绍一番。图1.1 NLP流程 首先,我们将文本句子切分成一个个子单元,然后将子单元数值化(映射成向量),接着将这些向量输入到模型进行编码,最后输出到下游任务中进一步得到最终结果。对于为什么要数值化,是因为除了决策树模型,机器学习中绝大多数模型是不支持字符串数据的,想要模型能够顺利有效地学习,必须对字符串数据先数值化。另外,我们并不是直接对输入句子或者单词进行数值化,我们需要先将其切分成一个个有限的子单元,然后将这些子单元数值化。而这个将原始文本切分成子单元的过程就叫做Tokenization。国内很多翻译为“分词”,私以为,这样的翻译多少会让人有误解,让人误以为是针对中文进行词语的分割。尽管这是Tokenization的任务之一,但并不局限与此(它要做的还有更多)。1.2 Tokenization的难点 由1.1节我们可以知道,Tokenization其实是为数值化作准备,数值化的过程必然需要映射,而映射又需要一个目标集合或者说映射表。这里就产生一个问题了,如果我们切分出来的子单元种类是非常多甚至无限多的,那么我们就需要一个非常庞大的映射表了,这就会导致巨大的内存消耗以及过多的计算量,这显然是不理想的。一种做法是将大量的低频子单元使用几个特定的符号(例如,[UNK])代替,这样便缩小了映射表了,但是这样一来我们原始文本就损失了很多信息了。另外,对于切分出来的子单元到底是什么,可以是任意的东西吗?直观上来讲,显然不应该是任意的东西,这些被切分出来的子单元应该是有一定的含义的。比如:【unhappily】如果切分成了 【un, happ, ily】显然要比【unh ap pily】要合理得多。因为【un, happ, ily】中每一个子单元都有一定的含义,而后者不然。 综上所示,Tokenization的难点便是——如何获得理想的切分,使文本中所有的token都具有正确的语义,并且不会存在遗漏(out of the vocabulary问题)1.2 三类Tokenization方法 这里笔者对Tokenization按切分的粒度分成了三大类,一是按词粒度来分,二是按字符粒度来分,三是按subword(子词粒度来分)。对于词粒度切分这类方法是自然而然的,因为我们人类对于自然语言文本的理解就是按照这种方式切分的。对于字符粒度,这是一种极简的方法,基本不需要什么技巧,但是它有很多弊端。对于subword粒度切分,它似乎继承了儒家学派的中庸之道,是这样一种折中的方法。三种方法概括如下图:图1.2 Tokenization方法(按粒度分类)2. 词粒度Tokenization 本节我们来讨论词粒度的相关方法。词粒度的切分就跟人类平时理解文本原理一样,可以用一些工具来完成,例如英文的NLTK、SpaCy,中文的jieba、HanLP等。 首先我们直观地看一下词粒度进行Tokenization是怎么样的一种方法,很显然,跟我们人类阅读时自然而然地切分是一致的。图2.1 词粒度的Tokenization示例 这种方法的优点是,能够很好地保留词的语义和边界信息。 对于英文等拉丁语系的词粒度Tokenization很简单,我们可以直接按照空格便能水到渠成地切出来,但是针对中日韩这类文字是无法通过空格进行切分的,这时针对这类语言的文字我们便需要用到一些分词方法。这些方法中一类是使用模型学习如何分词的,另一类是通过一个常用词表然后根据各种算法进行词表匹配来进行分词。使用模型进行序列标注这种方法不在本文的讨论范畴之内。笔者仅仅对词表匹配这类方法进行介绍。基于词表和规则的分词方法可以分为3种:1. 前(后)向最大匹配法;

2. 最短路径分词法;

3. 基于N-Gram LM的统计词频分词法。 这里先抛出问题,假设给定一个词典V,如何对一个句子S进行分词呢。图2.2 中文分词 下面笔者将对三种分词方法作一个简单介绍。2.1前(后)向最大匹配法 前(后)向最大匹配法的方法很简单,只需要知道前向匹配,那么后向匹配自然也就理解了。这里以前向最大匹配法进行讲解。我们来看一个例子,就能对前向匹配法的了然。预设条件: 1. 设定最大匹配长度2(当然可以是3,4,5或者更长); 2.从左往右扫描句子(也可以从右往左)。图2.3 前向匹配过程则最终输出结果为图2.4 前向匹配结果 很显然,这种规则虽然简单,但是却不是一种什么好方法。因为它无法解决歧义的问题,而且N的大小和和前还后向匹配对结果的影响很大。 题外话:由于词表一般比较大,为了节省内存和提高查找效率,一般会将词库构造成字典树。2.2最短路径分词法 最短路径分词法首先将句子中所有字都切分开来,然后根据词表将组成词的字连接起来,构成“词图”,然后求解“词图”的最短路径就是分词结果。图2.5 最短路径分词法的词图示例 本文中的例子构造出的“词图”,如上图所示。路径上的数字“1”代表权重,这里我们全部取为1,代表每种分词的重要性或者说可能行等价。然后利用N-最短路径或者Dijkstra算法便可以求解出最短路径。然后最短路径上的词汇就是我们最终需要的结果。 上述结果中,我们的分词结果是:图2.6 最短路径分词法结果2.3基于N-Gram LM的统计词频分词法 上述方法中,我们实际上作了简化边的权重都是1,而现实中却不是这样的,常见的词出现频率高,我们可以用将求解“词图”最短路径的方法转为求解概率(频率)最大的路径。 利用2-Gram语言模型,我们可以计算出词语的共现概率,结合词典V,我们可以得到下面的“词图”图2.7 2-Gram LM统计分词法的 词图 示例P(他|)的含义是,“他”作为句子开头对于训练好的语言模型来说其概率是多少,同理P(说|他)表示“说”在在“他”字后面出现的概率是多少。我们有理由相信,这是一种更合理的方法,因为其考虑了不同词语之间先后出现的概率。2.4基于词粒度的Tokenization优缺点 我们先来看看优点,概括来说就是词的边界和含义得到保留。 词粒度很像人类去阅读一样,一方面能够很好地保留词的边界信息,另一方面能够很好地保留词的含义。这对后续模型来说是一件好事,拿NER任务来说,通常标签偏移是由于词边界没有约束好导致的,所以词语的边界信息对于某些下游任务来说是很重要的。在MRC任务中,词语含义能否被模型捕获也显得很重要,如果最初输入到模型的是丢失了某些关键词的词含义信息,那么最终结果可能会收到一定影响。 当然词粒度的方法也有一定的缺陷。Issue1: 词粒度的方法,需要构造的词典太过庞大,严重影响计算效率和消耗内存。Issue2: 即使使用这么大的词典不影响效率,也会造成OOV问题。因为人类语言是不断发展的,词汇也在发展中不断增加。例如:针不戳,Niubility,Sixology等。Issue3:词表中的低频词/稀疏词在模型训练过程中无法得到充分 训练,进而模型不能充分理解这些词的语义Issue4:一个单词因为不同的形态会产生不同的词,如由“look”衍 生出的“looks”, “looking”, 但是意义相近,对他们都进行训练是不必要的。3. 字粒度Tokenization 字粒度又称字符粒度,它是按某一种语言最小符号来进行切分的。字符粒度最早应该是2015年Karpathy提出,简单说英文(拉丁语系)就是以字母为单位, 中文日文韩文等就是以字为单位进行切分。举个栗子,图3.1 字粒度Tokenization示例 它的优点是,词表大大减小,26个英文字母基本能覆盖出几乎所有词,5000多个中文基本也能组合出覆盖的词汇。但是除了这个优点之外全是缺点了,最重要的是,这种方法严重丢失了词汇的语音信息和边界信息,这对模型来说是灾难性的。而且把单词切分的太细,会使得输入太过长增加输入计算压力,减小词表的代价就是输入长度大大增加,从而输入计算变得更耗时。 这一种Tokenization方法没有什么过多可说的,因为现实中一般不用。4. subword(子词)粒度的Tokenization 首先我们先来谈谈怎么样的Tokenization才是理想的Tokenization。概括来说就是,词表要尽可能地小,小的同时又能覆盖到绝大多数的词尽可能地少出现OOV的词,另外此表中的每一个token都是由意义的,也就是说,对于一个词切出来的subwords中每一个都是由意义的,而且不要且得太细。 而第2节和第3节中讲到的方法都存在或多或少的缺点,都不能同时满足以上需求。那么有没有一种兼顾各方的方法呢?有的,那就是子词切分的方法。不过这里首先声明一下,这种方法只适用于英文,对于中文来说总不能把一个字分为偏旁部首和字根吧。 那么subword的Tokenization是怎么切分的呢?再举个栗子,图4.1 subword粒度的Tokenization示例 接着又要提问了,如何切分或者说如何构造出subwords词典呢?目前主要有4种方法。如下图所示,下面将一一讲解这4种方法的原理和异同。图4.2 subword粒度的方法4.1 BPE BPE全称Byte Pair Encoding,字节对编码,是一种数据压缩方法。最早是论文《 Neural Machine Translation of Rare Words with Subword Units》将其引入到NLP技术中。BPE迭代地合并最频繁出现的字符或字符序列,具体步骤:图4.3 BPE训练步骤 下面看一个理解便能更好地理解。图4.4 BPE训练示例 可以看到,每次尝试合并相邻的两个字符,如果合并后出现概率是目前最大的,那就将合并后的字串加入到词汇表。 这里顺便讲一下,目前我们只是得到了词汇表,对于给定的一个单词,我们怎么对它进行编码呢?很简单,编码分为3步:1.对于单词w,依次遍历排好序的词典。查看当前子词是否是该单词的子字符串,如果是,则输出当前子词,并对剩余单词字符串继续匹配。有点类似(前向最大匹配);2.如果遍历完字典后,仍然有子字符串没有匹配,则将剩余字符串替换为特殊符号输出,如””;3.单词w的表示即为上述所有输出子词。图4.5 BPE解码4.2 WordPiece WordPiece最早在《JAPANESE AND KOREAN VOICE SEARCH》中提出,并应用于解决日语和韩语语音问题。它与BPE相同点: 每次从统计语料中选取出两个新的子词进行合并。它与BPE最大区别在于选择两个子词进行合并的原则:BPE按频率,WordPiece按能够使得LM概率最大的相邻子词加入词表。 对于WordPiece构造词表的原理如下, 假设由句子 \begin{equation} s=\{t_1, t_2, t_3,...,t_n\} \end{equation} 由 n 个子词组成, t_i 表示第 i 个子词,且假设子词之间是相互独立的,那么句子 s 的语言模型对数似然值为,logP(s)=\sum_{i=1}^{N}logP(t_i) 假设把相邻的 t_i 和 t_j 两个子词合并,产生 t_x 子词,此时句子的对数似然值增益为:logP(t_x) -(logP(t_i)-logP(t_j))=log(\frac{P(t_x)}{P(t_i)P(t_j)}) 两个子词合并前后的对数似然值增益等于 t_x 和 t_i t_j 的互信息。所以,WordPiece每次选择合并的两个子词,具有最大的互信息值,从语言模型上来说两个子词之间有很强的关联性,从语料上来说两个子词共现概率比较高。 WordPiece的算法步骤如下:图4.6 WordPiece训练步骤4.3 UniLM Unigram语言建模首先在《 Subword Regularization: Improving Neural Network Translation Models with Multiple Subword Candidates 》中提出。这种方法与WordPiece相同点是:同样使用语言模型来挑选子词与WordPiece最大区别:WordPiece算法的词表大小都是从小到大变化。UniLM的词库则是从大到小变化,即先初始化一个大词表,根据评估准则不断丢弃词表,直到满足限定条件。ULM算法考虑了句子的不同分词可能,因而能够输出带概率的多个子词分段。 对于UniLM构造词表的原理如下, 对于句子 s ,假设存在一种子词切分结果为 s_1=\{ t_1,t_2,...,t_n \} 则当前分词下句子 s 的对数似然值可以表示为:logP(s_1) = \sum_{i=1}^{n}logP(t_i) 对于句子 s ,挑选似然值最大的作为分词结果,即,s^{*}=\arg\max\limits_{s_i\in U(s)}logP(s_i) s^* 为最优切分结果。 UniLM构造词典的算法步骤如下:图4.7 UniLM的训练步骤 可以看出,UniLM会保留那些以较高频率出现在很多句子的分词结果中的子词,因为这些子词如果被丢弃,其损失会很大。4.4 SentencePiece 以上三中方法都存在着两个问题就是1.无法逆转;2.训练的时候需要提前切分。无法逆转是什么意思呢,就是对句子s进行切分后得到的结果无法准确复原回s。更直白地说就是空格不能被保留,如下,图4.8 切分不可逆示意 而SentencePiece的解决方法是:SentencePiece首先将所有输入转换为unicode字符。这意味着它不必担心不同的语言、字符或符号,可以以相同的方式处理所有输入;空白也被当作普通符号来处理。Sentencepiece显式地将空白作为基本标记来处理,用一个元符号 “▁”( U+2581 )转义空白,这样就可以实现简单地decoding;Sentencepiece可以直接从raw text进行训练;支持BPE和UniLM训练方法。5.结语 目前在中文方面其实我们的大多数预训练模型包括Bert,Roberta等都是使用字粒度的方法进行Tokenization,它们对词汇的含义和边界信息的捕获可能会有所欠缺,所以很多方法如《Simplify the Usage of Lexicon in Chinese NER》,《Lexicon Enhanced Chinese Sequence Labeling Using BERT Adapter》等都在探讨如何通过外部词汇提高这些模型对词汇的理解。而对于拉丁语系(比如英文)大多都是使用的subword粒度的Tokenization方法。 学习Tokenization确实是一件非常枯燥无味的事情,尤其是需要深入学习的时候。本文尽最大努力从知识面的广度来讲解为什么要Tokenization以及Tokenization到底在做什么。尽管这些知识对一个NLP炼丹师来说不是经常用得到甚至用不到。但或者这些东西对大家日后的工作或者idea创新有帮助呢?编辑于 2022-11-08 11:44・IP 属地广东自然语言处理分词​赞同 224​​27 条评论​分享​喜欢​收藏​申请转载​文章被以下专栏收录QBrainQTrade AI团队的技

NLP领域中的token和tokenization到底指的是什么? - 知乎

NLP领域中的token和tokenization到底指的是什么? - 知乎首页知乎知学堂发现等你来答​切换模式登录/注册机器学习自然语言处理语言学NLP领域中的token和tokenization到底指的是什么?这是一个非常简单的基本概念问题,但作为小白,真的不太清晰显示全部 ​关注者267被浏览712,608关注问题​写回答​邀请回答​好问题 22​添加评论​分享​27 个回答默认排序Glan格蓝​武汉大学 工学硕士​ 关注前面都讲了是指什么,我来浅答一下目前大模型时代分词是怎么做的☺️,主要内容为WordPiece,Byte-Pair Encoding (BPE),Byte-level BPE(BBPE)分词方法的原理以及其代码实现,全篇阅读可能需要45分钟,建议收藏~Tokenization(分词) 在自然语言处理(NLP)的任务中是最基本的一步,把文本内容处理为最小基本单元即token(标记,令牌,词元,没有准确的翻译)用于后续的处理,如何把文本处理成token呢?有一系列的方法,基本思想是构建一个词表通过词表一一映射进行分词,但如何构建合适的词表呢?以下以分词粒度为角度进行介绍:1.word(词)粒度在英文语系中,word(词)级别分词实现很简单,因为有天然的分隔符。在中文里面word(词)粒度,需要一些分词工具比如jieba,以下是中文和英文的例子:中文句子:我喜欢看电影和读书。

分词结果:我 | 喜欢 | 看 | 电影 | 和 | 读书。

英文句子:I enjoy watching movies and reading books.

分词结果:I | enjoy | watching | movies | and | reading | books.word(词)粒度的优点有:语义明确:以词为单位进行分词可以更好地保留每个词的语义,使得文本在后续处理中能够更准确地表达含义。上下文理解:以词为粒度进行分词有助于保留词语之间的关联性和上下文信息,从而在语义分析和理解时能够更好地捕捉句子的意图。缺点:长尾效应和稀有词问题: 词表可能变得巨大,包含很多不常见的词汇,增加存储和训练成本,稀有词的训练数据有限,难以获得准确的表示。OOV(Out-of-Vocabulary): 词粒度分词模型只能使用词表中的词来进行处理,无法处理词表之外的词汇,这就是所谓的OOV问题。形态关系和词缀关系: 无法捕捉同一词的不同形态,也无法有效学习词缀在不同词汇之间的共通性,限制了模型的语言理解能力,比如love和loves在word(词)粒度的词表中将会是两个词。2.char(字符)粒度以字符为单位进行分词,即将文本拆分成一个个单独的字符作为最小基本单元,这种字符粒度的分词方法适用于多种语言,无论是英文、中文还是其他不同语言,都能够一致地使用字符粒度进行处理,因为英文就26个字母以及其他的一些符号,中文常见字就6000个左右。中文句子:我喜欢看电影和读书。

分词结果:我 | 喜 | 欢 | 看 | 电 | 影 | 和 | 读 | 书 | 。

英文句子:I enjoy watching movies and reading books.

分词结果:I | | e | n | j | o | y | | w | a | t | c | h | i | n | g | | m | o | v | i | e | s | | a | n | d | | r | e | a | d | i | n | g | | b | o | o | k | s | .char(字符)粒度的优点有:统一处理方式:字符粒度分词方法适用于不同语言,无需针对每种语言设计不同的分词规则或工具,具有通用性。解决OOV问题:由于字符粒度分词可以处理任何字符,无需维护词表,因此可以很好地处理一些新创词汇、专有名词等问题。缺点:语义信息不明确:字符粒度分词无法直接表达词的语义,可能导致在一些语义分析任务中效果较差。处理效率低:由于文本被拆分为字符,处理的粒度较小,增加后续处理的计算成本和时间。3.subword(子词)粒度在很多情况下,既不希望将文本切分成单独的词(太大),也不想将其切分成单个字符(太小),而是希望得到介于词和字符之间的子词单元。这就引入了 subword(子词)粒度的分词方法。在BERT时代,WordPiece 分词方法被广泛应用[1],比如 BERT、DistilBERT等。WordPiece 分词方法是 subword(子词)粒度的一种方法。3.1 WordPieceWordPiece核心思想是将单词拆分成多个前缀符号(比如BERT中的##)最小单元,再通过子词合并规则将最小单元进行合并为子词级别。例如对于单词"word",拆分如下:w ##o ##r ##d然后通过合并规则进行合并,从而循环迭代构建出一个词表,以下是核心步骤:计算初始词表:通过训练语料获得或者最初的英文中26个字母加上各种符号以及常见中文字符,这些作为初始词表。计算合并分数:对训练语料拆分的多个子词单元通过合拼规则计算合并分数。合并分数最高的子词对:选择分数最高的子词对,将它们合并成一个新的子词单元,并更新词表。重复合并步骤:不断重复步骤 2 和步骤 3,直到达到预定的词表大小、合并次数,或者直到不再有有意义的合并(即,进一步合并不会显著提高词表的效益)。分词:使用最终得到的词汇表对文本进行分词。简单举例[1]:我们有以下的训练语料中的样例,括号中第2位为在训练语料中出现的频率:("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)我们对其进行拆分为带前缀的形式:("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##g" "##s", 5)所以这些样例的初始词表将会是:["b", "h", "p", "##g", "##n", "##s", "##u"]接下来重要的一步进行计算合并分数,也称作互信息(信息论中衡量两个变量之间的关联程度[2]),简单来说就是以下公式来计算score=(freq_of_pair)/(freq_of_first_element×freq_of_second_element)

分数 = 合并pair候选的频率 / (第一个元素的频率 × 第二个元素的频率)对于上述样例中这个pair("##u", "##g")出现的频率是最高的20次,但是"##u"出现的频率是36次, "##g"出现的频率是20次,所以这个pair("##u", "##g")的分数是(20)/(36*20) = 1/36,同理计算这个pair("##g", "##s")的分数为(5)/(20*5) = 1/20,所以最先合并的pair是("##g", "##s")→("##gs")。此时词表和拆分后的的频率将变成以下:Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs"]

Corpus: ("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##gs", 5)重复上述的操作,直到达到你想要的词表的大小Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu", "hug"]

Corpus: ("hug", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("hu" "##gs", 5)代码实现:用一些包含中英文的文本作为训练语料,因为英文有天然的分隔符,所以在这个例子中,中文已经进行了分词:sentences = [

"我",

"喜欢",

"吃",

"苹果",

"他",

"不",

"喜欢",

"吃",

"苹果派",

"I like to eat apples",

"She has a cute cat",

"you are very cute",

"give you a hug",

]统计每个词出现的频率并初始化初始词表:from collections import defaultdict

# 构建频率统计

def build_stats(sentences):

stats = defaultdict(int)

for sentence in sentences:

symbols = sentence.split()

for symbol in symbols:

stats[symbol] += 1

return stats

stats = build_stats(sentences)

print("stats:", stats)

alphabet = []

for word in stats.keys():

if word[0] not in alphabet:

alphabet.append(word[0])

for letter in word[1:]:

if f"##{letter}" not in alphabet:

alphabet.append(f"##{letter}")

alphabet.sort()

# 初始词表

vocab = alphabet.copy()

print("alphabet:", alphabet)

# 结果

stats: defaultdict(, {'我': 1, '喜欢': 2, '吃': 2, '苹果': 1, '他': 1, '不': 1, '苹果派': 1, 'I': 1, 'like': 1, 'to': 1, 'eat': 1, 'apples': 1, 'She': 1, 'has': 1, 'a': 2, 'cute': 2, 'cat': 1, 'you': 2, 'are': 1, 'very': 1, 'give': 1, 'hug': 1})

# 初始词表

alphabet: ['##a', '##e', '##g', '##h', '##i', '##k', '##l', '##o', '##p', '##r', '##s', '##t', '##u', '##v', '##y', '##果', '##欢', '##派', 'I', 'S', 'a', 'c', 'e', 'g', 'h', 'l', 't', 'v', 'y', '不', '他', '吃', '喜', '我', '苹']根据初始词表拆分每个词:splits = {

word: [c if i == 0 else f"##{c}" for i, c in enumerate(word)]

for word in stats.keys()

}

print("splits:", splits)

# 结果

splits: {'我': ['我'], '喜欢': ['喜', '##欢'], '吃': ['吃'], '苹果': ['苹', '##果'], '他': ['他'], '不': ['不'], '苹果派': ['苹', '##果', '##派'], 'I': ['I'], 'like': ['l', '##i', '##k', '##e'], 'to': ['t', '##o'], 'eat': ['e', '##a', '##t'], 'apples': ['a', '##p', '##p', '##l', '##e', '##s'], 'She': ['S', '##h', '##e'], 'has': ['h', '##a', '##s'], 'a': ['a'], 'cute': ['c', '##u', '##t', '##e'], 'cat': ['c', '##a', '##t'], 'you': ['y', '##o', '##u'], 'are': ['a', '##r', '##e'], 'very': ['v', '##e', '##r', '##y'], 'give': ['g', '##i', '##v', '##e'], 'hug': ['h', '##u', '##g']}根据上述提到的计算互信息的分数公式进行计算:def compute_pair_scores(splits):

letter_freqs = defaultdict(int)

pair_freqs = defaultdict(int)

for word, freq in stats.items():

split = splits[word]

if len(split) == 1:

letter_freqs[split[0]] += freq

continue

for i in range(len(split) - 1):

pair = (split[i], split[i + 1])

letter_freqs[split[i]] += freq

pair_freqs[pair] += freq

letter_freqs[split[-1]] += freq

scores = {

pair: freq / (letter_freqs[pair[0]] * letter_freqs[pair[1]])

for pair, freq in pair_freqs.items()

}

return scores

pair_scores = compute_pair_scores(splits)

for i, key in enumerate(pair_scores.keys()):

print(f"{key}: {pair_scores[key]}")

if i >= 5:

break一些结果:('喜', '##欢'): 0.5

('苹', '##果'): 0.5

('##果', '##派'): 0.5

('l', '##i'): 0.5

('##i', '##k'): 0.5

('##k', '##e'): 0.125我们需要的是将分数最高的进行合并然后开始循环迭代,看一看分数最高的pair(子词对):best_pair = ""

max_score = None

for pair, score in pair_scores.items():

if max_score is None or max_score < score:

best_pair = pair

max_score = score

print(best_pair, max_score)

# 结果

('S', '##h') 1.0结果为('S', '##h') 1.0,所以最先合成的就是('S', '##h')→'##Sh',合并的函数如下:def merge_pair(a, b, splits):

for word in stats:

split = splits[word]

if len(split) == 1:

continue

i = 0

while i < len(split) - 1:

if split[i] == a and split[i + 1] == b:

merge = a + b[2:] if b.startswith("##") else a + b

split = split[:i] + [merge] + split[i + 2 :]

else:

i += 1

splits[word] = split

return splits最后就是一直进行循环迭代,直到vocab达到了我们想要的数量vocab_size = 50

while len(vocab) < vocab_size:

scores = compute_pair_scores(splits)

best_pair, max_score = "", None

for pair, score in scores.items():

if max_score is None or max_score < score:

best_pair = pair

max_score = score

splits = merge_pair(*best_pair, splits)

new_token = (

best_pair[0] + best_pair[1][2:]

if best_pair[1].startswith("##")

else best_pair[0] + best_pair[1]

)

vocab.append(new_token)

print("vocab:", vocab)

# 结果

vocab: ['##a', '##e', '##g', '##h', '##i', '##k', '##l', '##o', '##p', '##r', '##s', '##t', '##u', '##v', '##y', '##果', '##欢', '##派', 'I', 'S', 'a', 'c', 'e', 'g', 'h', 'l', 't', 'v', 'y', '不', '他', '吃', '喜', '我', '苹', 'Sh', '喜欢', '苹果', '苹果派', 'li', 'lik', 'gi', 'giv', '##pl', '##ppl', '##ry', 'to', 'yo', 'ea', 'eat']上述就是WordPiece分词方法的代码实现,一般来说最后会在词表中加上一些特殊词汇,以及英文中26个字母加上各种符号以及常见中文字符,不过如果训练语料比较大以及词表比较大那这些应该也是已经包括了,只需要添加特殊词汇:all_vocab = vocab + ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"] + other_alphabet在大语言模型时代,最常用的分词方法是Byte-Pair Encoding (BPE)和Byte-level BPE(BBPE),Byte-Pair Encoding (BPE)最初是一种文本压缩算法在15年被引入到NLP用于分词[3],在训练 GPT 时被 OpenAI 用于tokenization,后续好多模型GPT,RoBERTa等都采用了这种分词方法。Byte-level BPE(BBPE)是于19年在BPE的基础上提出以Byte-level(字节)为粒度的分词方法[4],目前 GPT2,BLOOM,Llama,Falcon等采用的是该分词方法。3.2 Byte-Pair Encoding (BPE)Byte-Pair Encoding (BPE)核心思想是逐步合并出现频率最高的子词对而不是像Wordpiece计算合并分数,从而构建出一个词汇表,以下是核心步骤:计算初始词表:通过训练语料获得或者最初的英文中26个字母加上各种符号以及常见中文字符,这些作为初始词表。构建频率统计:统计所有子词单元对(两个连续的子词)在文本中的出现频率。合并频率最高的子词对:选择出现频率最高的子词对,将它们合并成一个新的子词单元,并更新词汇表。重复合并步骤:不断重复步骤 2 和步骤 3,直到达到预定的词汇表大小、合并次数,或者直到不再有有意义的合并(即,进一步合并不会显著提高词汇表的效益)。分词:使用最终得到的词汇表对文本进行分词。简单的代码实现[5]:用一些包含中英文的文本作为训练语料和上面相同,因为英文有天然的分隔符,所以在这个例子中,中文已经进行了分词:sentences = [

"我",

"喜欢",

"吃",

"苹果",

"他",

"不",

"喜欢",

"吃",

"苹果派",

"I like to eat apples",

"She has a cute cat",

"you are very cute",

"give you a hug",

]统计每个词出现的频率并初始化初始词表:# 构建频率统计

def build_stats(sentences):

stats = defaultdict(int)

for sentence in sentences:

symbols = sentence.split()

for symbol in symbols:

stats[symbol] += 1

return stats

stats = build_stats(sentences)

print("stats:", stats)

alphabet = []

for word in stats.keys():

for letter in word:

if letter not in alphabet:

alphabet.append(letter)

alphabet.sort()

# 初始词表

vocab = alphabet.copy()

print("alphabet:", alphabet)

# 结果

stats: defaultdict(, {'我': 1, '喜欢': 2, '吃': 2, '苹果': 1, '他': 1, '不': 1, '苹果派': 1, 'I': 1, 'like': 1, 'to': 1, 'eat': 1, 'apples': 1, 'She': 1, 'has': 1, 'a': 2, 'cute': 2, 'cat': 1, 'you': 2, 'are': 1, 'very': 1, 'give': 1, 'hug': 1})

# 初始词表

alphabet: ['I', 'S', 'a', 'c', 'e', 'g', 'h', 'i', 'k', 'l', 'o', 'p', 'r', 's', 't', 'u', 'v', 'y', '不', '他', '吃', '喜', '我', '果', '欢', '派', '苹']根据初始词表拆分每个词,计算左右pair(子词对)出现的频率splits = {word: [c for c in word] for word in stats.keys()}

print("splits:", splits)

def compute_pair_freqs(splits):

pair_freqs = defaultdict(int)

for word, freq in stats.items():

split = splits[word]

if len(split) == 1:

continue

for i in range(len(split) - 1):

pair = (split[i], split[i + 1])

pair_freqs[pair] += freq

return pair_freqs

pair_freqs = compute_pair_freqs(splits)

for i, key in enumerate(pair_freqs.keys()):

print(f"{key}: {pair_freqs[key]}")

if i >= 5:

break

# 结果

splits: {'我': ['我'], '喜欢': ['喜', '欢'], '吃': ['吃'], '苹果': ['苹', '果'], '他': ['他'], '不': ['不'], '苹果派': ['苹', '果', '派'], 'I': ['I'], 'like': ['l', 'i', 'k', 'e'], 'to': ['t', 'o'], 'eat': ['e', 'a', 't'], 'apples': ['a', 'p', 'p', 'l', 'e', 's'], 'She': ['S', 'h', 'e'], 'has': ['h', 'a', 's'], 'a': ['a'], 'cute': ['c', 'u', 't', 'e'], 'cat': ['c', 'a', 't'], 'you': ['y', 'o', 'u'], 'are': ['a', 'r', 'e'], 'very': ['v', 'e', 'r', 'y'], 'give': ['g', 'i', 'v', 'e'], 'hug': ['h', 'u', 'g']}

('喜', '欢'): 2

('苹', '果'): 2

('果', '派'): 1

('l', 'i'): 1

('i', 'k'): 1

('k', 'e'): 1然后开始循环迭代找到出现频率最高的pair(子词对):best_pair = ""

max_freq = None

for pair, freq in pair_freqs.items():

if max_freq is None or max_freq < freq:

best_pair = pair

max_freq = freq

print(best_pair, max_freq)结果为【('喜', '欢') 2】,所以最先合成的就是('喜', '欢')→'喜欢',然后合并的函数如下:def merge_pair(a, b, splits):

for word in stats:

split = splits[word]

if len(split) == 1:

continue

i = 0

while i < len(split) - 1:

if split[i] == a and split[i + 1] == b:

split = split[:i] + [a + b] + split[i + 2 :]

else:

i += 1

splits[word] = split

return splits最后就是一直进行循环直到vocab达到了我们想要的数量:# 假设我们想要的词典为50

merges = {}

vocab_size = 50

while len(vocab) < vocab_size:

pair_freqs = compute_pair_freqs(splits)

best_pair = ""

max_freq = None

for pair, freq in pair_freqs.items():

if max_freq is None or max_freq < freq:

best_pair = pair

max_freq = freq

splits = merge_pair(*best_pair, splits)

merges[best_pair] = best_pair[0] + best_pair[1]

vocab.append(best_pair[0] + best_pair[1])

print("merges:", merges)

print("vocab:", vocab)

# 结果

merges: {('喜', '欢'): '喜欢', ('苹', '果'): '苹果', ('a', 't'): 'at', ('c', 'u'): 'cu', ('cu', 't'): 'cut', ('cut', 'e'): 'cute', ('y', 'o'): 'yo', ('yo', 'u'): 'you', ('v', 'e'): 've', ('苹果', '派'): '苹果派', ('l', 'i'): 'li', ('li', 'k'): 'lik', ('lik', 'e'): 'like', ('t', 'o'): 'to', ('e', 'at'): 'eat', ('a', 'p'): 'ap', ('ap', 'p'): 'app', ('app', 'l'): 'appl', ('appl', 'e'): 'apple', ('apple', 's'): 'apples', ('S', 'h'): 'Sh', ('Sh', 'e'): 'She', ('h', 'a'): 'ha'}

vocab: ['I', 'S', 'a', 'c', 'e', 'g', 'h', 'i', 'k', 'l', 'o', 'p', 'r', 's', 't', 'u', 'v', 'y', '不', '他', '吃', '喜', '我', '果', '欢', '派', '苹', '喜欢', '苹果', 'at', 'cu', 'cut', 'cute', 'yo', 'you', 've', '苹果派', 'li', 'lik', 'like', 'to', 'eat', 'ap', 'app', 'appl', 'apple', 'apples', 'Sh', 'She', 'ha']再加上一些特殊词汇和其他词汇:all_vocab = vocab + ["[PAD]", "[UNK]", "[BOS]", "[EOS]"] + other_alphabet上述就是BPE的代码实现,BPE理论上还是会出现OOV的,当词汇表的大小受限时,一些较少频繁出现的子词和没有在训练过程中见过的子词,就会无法进入词汇表出现OOV,而Byte-level BPE(BBPE)理论上是不会出现这个情况的。3.3 Byte-level BPE(BBPE)基础知识:Unicode: Unicode 是一种字符集,旨在涵盖地球上几乎所有的书写系统和字符。它为每个字符分配了一个唯一的代码点(code point)用于标识字符。Unicode 不关注字符在计算机内部的具体表示方式,而只是提供了一种字符到代码点的映射。Unicode 的出现解决了字符集的碎片化问题,使得不同的语言和字符能够在一个共同的标准下共存。然而,Unicode 并没有规定如何在计算机内存中存储和传输这些字符。UTF-8: UTF-8(Unicode Transformation Format-8)是一种变长的字符编码方案,它将 Unicode 中的代码点转换为字节序列。UTF-8 的一个重要特点是它是向后兼容 ASCII 的,这意味着标准的 ASCII 字符在 UTF-8 中使用相同的字节表示,从而确保现有的 ASCII 文本可以无缝地与 UTF-8 共存。在 UTF-8 编码中,字符的表示长度可以是1到4个字节,不同范围的 Unicode 代码点使用不同长度的字节序列表示,这样可以高效地表示整个 Unicode 字符集。UTF-8 的编码规则是:单字节字符(ASCII 范围内的字符)使用一个字节表示,保持与 ASCII 编码的兼容性。带有更高代码点的字符使用多个字节表示。UTF-8 使用特定的字节序列来指示一个字符所需的字节数,以及字符的实际数据。例如,英文字母 "A" 的 Unicode 代码点是U+0041,在 UTF-8 中表示为 0x41(与 ASCII 编码相同);而中文汉字 "你" 的 Unicode 代码点是U+4F60,在 UTF-8 中表示为0xE4 0xBD 0xA0三个字节的序列。所以简单的来说:Unicode 是字符集,为每个字符分配唯一的代码点。UTF-8 是一种基于 Unicode 的字符编码方式,用于在计算机中存储和传输字符。Byte(字节):计算机存储和数据处理时,字节是最小的单位。一个字节包含8个(Bit)二进制位,每个位可以是0或1,每位的不同排列和组合可以表示不同的数据,所以一个字节能表示的范围是256个。言归正传:Byte-level BPE(BBPE)和Byte-Pair Encoding (BPE)区别就是BPE是最小词汇是字符级别,而BBPE是字节级别的,通过UTF-8的编码方式这一个字节的256的范围,理论上可以表示这个世界上的所有字符。所以实现的步骤和BPE就是实现的粒度不一样,其他的都是一样的。初始词表:构建初始词表,包含一个字节的所有表示(256)。构建频率统计:统计所有子词单元对(两个连续的子词)在文本中的出现频率。合并频率最高的子词对:选择出现频率最高的子词对,将它们合并成一个新的子词单元,并更新词汇表。重复合并步骤:不断重复步骤 2 和步骤 3,直到达到预定的词汇表大小、合并次数,或者直到不再有有意义的合并(即,进一步合并不会显著提高词汇表的效益)。分词:使用最终得到的词汇表对文本进行分词。简单代码实现,不做赘述,读者朋友们可以自己实现一下from collections import defaultdict

sentences = [

"我",

"喜欢",

"吃",

"苹果",

"他",

"不",

"喜欢",

"吃",

"苹果派",

"I like to eat apples",

"She has a cute cat",

"you are very cute",

"give you a hug",

]

# 构建初始词汇表,包含一个字节的256个表示

initial_vocab = [bytes([byte]) for byte in range(256)]

vocab = initial_vocab.copy()

print("initial_vocab:", initial_vocab)

# 构建频率统计

def build_stats(sentences):

stats = defaultdict(int)

for sentence in sentences:

symbols = sentence.split()

for symbol in symbols:

stats[symbol.encode("utf-8")] += 1

return stats

stats = build_stats(sentences)

splits = {word: [byte for byte in word] for word in stats.keys()}

def compute_pair_freqs(splits):

pair_freqs = defaultdict(int)

for word, freq in stats.items():

split = splits[word]

if len(split) == 1:

continue

for i in range(len(split) - 1):

pair = (split[i], split[i + 1])

pair_freqs[pair] += freq

return pair_freqs

pair_freqs = compute_pair_freqs(splits)

def merge_pair(pair, splits):

merged_byte = bytes(pair)

for word in stats:

split = splits[word]

if len(split) == 1:

continue

i = 0

while i < len(split) - 1:

if split[i:i+2] == pair: # 检查分割中是否有这对字节

split = split[:i] + [merged_byte] + split[i + 2 :]

else:

i += 1

splits[word] = split

return splits

vocab_size = 50

while len(vocab) < vocab_size:

pair_freqs = compute_pair_freqs(splits)

best_pair = ()

max_freq = None

for pair, freq in pair_freqs.items():

if max_freq is None or max_freq < freq:

best_pair = pair

max_freq = freq

splits = merge_pair(best_pair, splits)

merged_byte = bytes(best_pair)

print("vocab:", vocab)着重解释一下为什么Byte-level BPE(BBPE)不会出现OOV问题,初始的词表里有256个表示如下:[b'\x00', b'\x01', b'\x02', b'\x03', b'\x04', b'\x05', b'\x06', b'\x07', b'\x08', b'\t', b'\n', b'\x0b', b'\x0c', b'\r', b'\x0e', b'\x0f', b'\x10', b'\x11', b'\x12', b'\x13', b'\x14', b'\x15', b'\x16', b'\x17', b'\x18', b'\x19', b'\x1a', b'\x1b', b'\x1c', b'\x1d', b'\x1e', b'\x1f', b' ', b'!', b'"', b'#', b'$', b'%', b'&', b"'", b'(', b')', b'*', b'+', b',', b'-', b'.', b'/', b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b':', b';', b'<', b'=', b'>', b'?', b'@', b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', b'Z', b'[', b'\\', b']', b'^', b'_', b'`', b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', b'x', b'y', b'z', b'{', b'|', b'}', b'~', b'\x7f', b'\x80', b'\x81', b'\x82', b'\x83', b'\x84', b'\x85', b'\x86', b'\x87', b'\x88', b'\x89', b'\x8a', b'\x8b', b'\x8c', b'\x8d', b'\x8e', b'\x8f', b'\x90', b'\x91', b'\x92', b'\x93', b'\x94', b'\x95', b'\x96', b'\x97', b'\x98', b'\x99', b'\x9a', b'\x9b', b'\x9c', b'\x9d', b'\x9e', b'\x9f', b'\xa0', b'\xa1', b'\xa2', b'\xa3', b'\xa4', b'\xa5', b'\xa6', b'\xa7', b'\xa8', b'\xa9', b'\xaa', b'\xab', b'\xac', b'\xad', b'\xae', b'\xaf', b'\xb0', b'\xb1', b'\xb2', b'\xb3', b'\xb4', b'\xb5', b'\xb6', b'\xb7', b'\xb8', b'\xb9', b'\xba', b'\xbb', b'\xbc', b'\xbd', b'\xbe', b'\xbf', b'\xc0', b'\xc1', b'\xc2', b'\xc3', b'\xc4', b'\xc5', b'\xc6', b'\xc7', b'\xc8', b'\xc9', b'\xca', b'\xcb', b'\xcc', b'\xcd', b'\xce', b'\xcf', b'\xd0', b'\xd1', b'\xd2', b'\xd3', b'\xd4', b'\xd5', b'\xd6', b'\xd7', b'\xd8', b'\xd9', b'\xda', b'\xdb', b'\xdc', b'\xdd', b'\xde', b'\xdf', b'\xe0', b'\xe1', b'\xe2', b'\xe3', b'\xe4', b'\xe5', b'\xe6', b'\xe7', b'\xe8', b'\xe9', b'\xea', b'\xeb', b'\xec', b'\xed', b'\xee', b'\xef', b'\xf0', b'\xf1', b'\xf2', b'\xf3', b'\xf4', b'\xf5', b'\xf6', b'\xf7', b'\xf8', b'\xf9', b'\xfa', b'\xfb', b'\xfc', b'\xfd', b'\xfe', b'\xff']通过上述的方式其实是在一直根据训练语料循环迭代合成子词或者词,最后形成词表,比如“苹果”通过UTF-8进行编码后为“\xe8\x8b\xb9\xe6\x9e\x9c”,如果词表里面有,那“苹果”就通过词表映射成了1个表示,准确来说是1个token;如果词表里没有,那就用256中的“\xe8+\x8b+\xb9+\xe6+\x9e+\x9c”来表示“苹果”这个词,那就是6个token。在先前的各种分词方法中,如果词典里没有”苹果“这个词,也没有”苹“,”果“这样的子词的话,那就变成了[UNK]。所以在现在的大模型中,以Byte-level BPE(BBPE)这种方式进行分词是不会出现OOV,但词表中如果没有word级别的词的话,一些中英文就会分词分的很细碎,比如Llama在中文上就会把一些词分成多个token其实就是UTF-8后的中文编码,对编码效率以及语义会有影响,于是出现了一些扩充Llama中文词表的工作。上述分词算法在工程上实现一般使用sentencpiece工具包[6],谷歌在这个包中实现了上述的一系列算法,扩充Llama中文词表的工作也都是在此上面实现的。后续我也会写一篇文章进行详细的讲解。欢迎关注~其他文章:参考^ab[1] https://huggingface.co/learn/nlp-course/chapter6/6?fw=pt^[3] https://zh.wikipedia.org/zh-hans/%E4%BA%92%E4%BF%A1%E6%81%AF^[3] https://arxiv.org/abs/1508.07909^[4] https://arxiv.org/abs/1909.03341^[5] https://huggingface.co/learn/nlp-course/chapter6/5?fw=pt^[6] https://github.com/google/sentencepiece编辑于 2023-09-02 18:19​赞同 301​​8 条评论​分享​收藏​喜欢收起​OpenLLMAI​​浙江大学 工学硕士​ 关注简而言之:token可以理解为最小语义单元,翻译的话个人喜欢叫词元(当然翻译成令牌、词都行),可以是word/char/subword。tokenization是指分词,目的是将输入文本分成一个个词元,保证各个词元拥有相对完整和独立的语义,以供后续任务(比如学习embedding或者作为高级模型的输入)使用。原文:0.序章笔者在上一篇文章中对最近折腾大模型的过程进行了反思,痛定思痛,决定除了工作部分以外不再浪费太多时间去跑更大规模的模型,同时决心开一些新坑来倒逼输入并与大家交流讨论,暂时的想法是在OpenLLM下面做两个系列:LLM基础组件和LLM炼丹术。注:从4.11开始,不知不觉居然写到OpenLLM 008了,这十几天累成狗了,最快乐的时候居然是忙里偷闲写这些东西的时候,amazing!LLM基础组件tokenization&tokenizers:分词算法与分词器位置编码attention机制基础架构与attention mask归一化激活函数行为思路分词算法与分词器作为LLM(大语言模型)的基础组件,作用相当于文本与模型的桥梁。因此作为LLM基础组件系列的开篇,本文将对主流的分词算法和分词器进行全面的梳理和介绍。updates2023/04/30,资料阅读+整理,完成大纲;2023/05/01,主流subword算法伪代码;bert分词代码解读;2023/05/02,+byte-level BPE、优缺点、示例、总结等,主体内容基本算是写完了;剩余的代码实现示例和具体模型的分词器示例后续有空再补(看优先级和精力);2023/05/03,XX;1.分词算法tokenization算法大致经历了从word/char到subword的进化,这一章首先介绍不同的分词粒度,然后对主流的三大subword分词算法进行介绍,配合代码和实例,希望可以对subword算法有一个比较全面的梳理。0.文本应该分成什么粒度?分词的目的是将输入文本分成一个个词元,保证各个词元拥有相对完整和独立的语义,以供后续任务(比如学习embedding或者作为高级模型的输入)使用。首先,最自然的粒度当然是词粒度。词,作为语言最自然的基本单元,在英文等语言中有着天然的空格分隔,但是对于中文等语言可能需要额外的分词算法来进行处理(比如中文的jieba分词)。不过,我们总归是有办法获得各种各样的词的,这并不是一个致命的问题。真正影响词粒度分词算法应用问题主要有:1)词粒度的词表由于长尾效应可能会非常大,包含很多的稀有词,存储和训练的成本都很高,并且稀有词往往很难学好;2)OOV问题,对于词表之外的词无能为力;3)无法处理单词的形态关系和词缀关系:同一个词的不同形态,语义相近,完全当做不同的单词不仅增加了训练成本,而且无法很好的捕捉这些单词之间的关系;同时,也无法学习词缀在不同单词之间的泛化。那么,一个很自然的想法就是使用字符粒度的词表,这样OOV问题迎刃而解了,但是字符粒度太细了,会造成新的问题:1)无法承载丰富的语义;2)序列长度增长,带来计算成本的增长。所以,如何结合word和char粒度各自的优势呢?subword分词应运而生,顾名思义,粒度介于char和Word之间,基本思想为常用词应该保持原状,生僻词应该拆分成子词以共享token压缩空间,所以可以较好的平衡词表大小与语义表达能力,比如OOV问题可以通过subword的组合来解决。目前有三种主流的Subword分词算法,分别是Byte Pair Encoding (BPE), WordPiece和Unigram Language Model。总结一下,文本的分词粒度:word:优点:词的边界和含义得到保留;缺点:1)词表大,稀有词学不好;2)OOV;3)无法处理单词形态关系和词缀关系;char:优点:词表极小,比如26个英文字母几乎可以组合出所有词,5000多个中文常用字基本也能组合出足够的词汇;缺点:1)无法承载丰富的语义;2)序列长度大幅增长;subword:可以较好的平衡词表大小与语义表达能力;1.BPEBPE最早其实是一种数据压缩算法,基本思想是将经常一起出现的数据对替换为不在数据串中的其他字符,后续可以通过一个merge表来恢复原始数据。在2015年,由论文[1508.07909] Neural Machine Translation of Rare Words with Subword Units引入NLP领域。核心思想:从一个基础小词表开始,通过不断合并最高频的连续token对来产生新的token。具体做法:输入:训练语料;词表大小V1.准备基础词表:比如英文中26个字母加上各种符号;2.基于基础词表将语料拆分为最小单元;3.在语料上统计单词内相邻单元对的频率,选择频率最高的单元对进行合并;4.重复第3步直到达到预先设定的subword词表大小或下一个最高频率为1;输出:BPE算法得到的subword词表下面是一个BPE的训练示例:优势与劣势:优势:可以有效地平衡词汇表大小和编码步数(编码句子所需的token数量,与词表大小和粒度有关)。劣势:基于贪婪和确定的符号替换,不能提供带概率的多个分词结果(这是相对于ULM而言的);decode的时候面临歧义问题。BPE的劣势:代码实现:refs:[1508.07909] Neural Machine Translation of Rare Words with Subword Units理解NLP最重要的编码方式 — Byte Pair Encoding (BPE),这一篇就够了 - 硅谷谷主的文章 - 知乎https://zhuanlan.zhihu.com/p/424631681 2.Byte-level BPE2019年12月:《Neural Machine Translation with Byte-Level Subwords》,论文提出了一种新的subword算法,称之为BBPE,即Byte-level BPE。核心思想:将BPE的思想从字符级别扩展到子节级别。具体做法:摘要:几乎所有现有的机器翻译模型都建立在基于字符的词汇表之上:characters, subwords or words(只是字符的粒度不同)。 然而,来自噪声文本或字符丰富的语言(如日语和中文)的稀有字符可能会不必要地占用词汇槽并限制其紧凑性。 在字节级别表示文本并使用 256 字节集作为词汇表是解决此问题的潜在方法。 然而,高昂的计算成本阻碍了它在实践中的广泛部署或使用。 在本文中,我们研究了字节级子词,具体为字节级 BPE (BBPE),它比字符词汇表更紧凑,没有词汇表外的标记,但比仅使用纯字节更有效。 我们声称上下文化 BBPE 嵌入是必要的,这可以通过卷积层或循环层来实现。 我们的实验表明,BBPE 具有与 BPE 相当的性能,而其大小仅为 BPE 的 1/8。 在多语言设置中,BBPE 最大限度地共享多种语言的词汇并实现更好的翻译质量。 此外,我们表明 BBPE 可以在具有非重叠字符集的语言之间实现可迁移的模型。我们考虑文本的UTF8编码,它将每个Unicode字符编码成1到4个字节。这允许我们将句子建模为字节序列,而不是字符序列。虽然有覆盖150多种语言的138K Unicode字符,但我们可以将任何语言的句子表示为UTF-8字节序列(只需要256个可能的字节中的248个)。文本的字节序列表示通常比字符序列表示长得多(高达4倍),这使得按原样使用字节(只使用256的子节集)在计算上要求很高。作为另一种选择,我们考虑将字节序列分割成可变长度的n-gram(字节级“subwords”)。具体地说,我们学习关于字节级表示的BPE词汇,该表示用字节n-gram扩展了UTF-8字节集,称之为BBPE。图一展示了BBPE与BPE的对比。不同的词表对序列长度的影响:词表粒度由细到粗,分词序列的对比:我们可以验证一下上图中的部分编码,可以看到是一致的:https://www.browserling.com/tools/utf8-encode优势与劣势:优势:1)效果与BPE相当,但词表大为减小;2)可以在多语言之间通过字节级别的子词实现更好的共享;3)即使字符集不重叠,也可以通过子节层面的共享来实现良好的迁移。劣势:1)编码序列时,长度可能会略长于BPE,计算成本更高;2)由byte解码时可能会遇到歧义,需要通过上下文信息和动态规划来进行解码。refs:Neural Machine Translation with Byte-Level Subwordshttps://arxiv.org/abs/1909.03341 浅谈Byte-Level BPE - CaesarEX的文章 - 知乎https://zhuanlan.zhihu.com/p/146114164 tokenizers小结 - 马东什么的文章 - 知乎https://zhuanlan.zhihu.com/p/360290118 3.WordPieceWordPiece出自《JAPANESE AND KOREAN VOICE SEARCH》,并用于解决日语和韩语的语音问题。核心思想:与BPE类似,也是从一个基础小词表出发,通过不断合并来产生最终的词表。主要的差别在于,BPE按频率来选择合并的token对,而wordpiece按token间的互信息来进行合并。注:互信息,在分词领域有时也被称为凝固度、内聚度,可以反映一个词内部的两个部分结合的紧密程度。具体做法:除了合并对象的选择以外,基本同BPE;输入:训练语料;词表大小V1.准备基础词表:比如英文中26个字母加上各种符号;2.基于基础词表将语料拆分为最小单元;3.基于第2步数据训练语言模型,可以是unigram语言模型,通过极大似然进行估计即可;4.从所有可能得token对中选择,选择合并后可以最大程度地增加训练数据概率的token对进行合并,具体的score=(freq_of_pair)/(freq_of_first_element×freq_of_second_element),当然你也可以取个log,就变成了互信息,选择最高的单元对进行合并;5.重复第4步直到达到预先设定的subword词表大小或概率增量低于某一阈值;输出:wordpiece算法得到的subword词表优势与劣势:优势:可以较好的平衡词表大小和OOV问题;劣势:可能会产生一些不太合理的子词或者说错误的切分;对拼写错误非常敏感;对前缀的支持不够好;复合词错误的切分:前缀的错误处理:一种解决方案是:将复合词拆开;将前缀也拆开;代码实现:refs:japanese and korean voice searchhttps://static.googleusercontent.com/media/research.google.com/zh-CN//pubs/archive/37842.pdf4.ULMULM出自《 Subword Regularization: Improving Neural Network Translation Models with Multiple Subword Candidates 》。核心思想:初始化一个大词表,然后通过unigram 语言模型计算删除不同subword造成的损失来代表subword的重要性,保留loss较大或者说重要性较高的subword。具体做法:输入:训练语料;词表大小V1.准备基础词表:初始化一个很大的词表,比如所有字符+高频ngram,也可以通过BPE算法初始化;2.针对当前词表,用EM算法估计每个子词在语料上的概率;3.计算删除每个subword后对总loss的影响,作为该subword的loss;4.将子词按照loss大小进行排序,保留前x%的子词;注意,单字符不能被丢弃,以免OOV;5.重复步骤2到4,直到词表大小减少到设定值;输出:ULM算法得到的subword词表可见,ULM会倾向于保留那些以较高频率出现在很多句子的分词结果中的子词,因为这些子词如果被删除,其损失会很大。优势与劣势:优势:1)使用的训练算法可以利用所有可能的分词结果,这是通过data sampling算法实现的;2)提出一种基于语言模型的分词算法,这种语言模型可以给多种分词结果赋予概率,从而可以学到其中的噪声;3)使用时也可以给出带概率的多个分词结果。劣势:1)效果与初始词表息息相关,初始的大词表要足够好,比如可以通过BPE来初始化;2)略显复杂。代码实现:refs:Subword Regularization: Improving Neural Network Translation Models with Multiple Subword Candidates https://arxiv.org/abs/1804.10959 NLP三大Subword模型详解:BPE、WordPiece、ULM - 阿北的文章 - 知乎https://zhuanlan.zhihu.com/p/1916484215.SentencePieceSentencePiece,有些文章将其看作一种分词方法,有的地方将其视为一个分词工具包。个人更倾向于后者,但是将其看作一种分词算法也未尝不可(因为不仅是分词算法的集成,还做了很多优化)。官方介绍:SentencePiece is an unsupervised text tokenizer and detokenizer mainly for Neural Network-based text generation systems where the vocabulary size is predetermined prior to the neural model training. SentencePiece implements subword units (e.g., byte-pair-encoding (BPE) [Sennrich et al.]) and unigram language model [Kudo.]) with the extension of direct training from raw sentences. SentencePiece allows us to make a purely end-to-end system that does not depend on language-specific pre/postprocessing.https://github.com/google/sentencepiece 主要特性多分词粒度:支持BPE、ULM子词算法,也支持char, word分词;多语言:以unicode方式编码字符,将所有的输入(英文、中文等不同语言)都转化为unicode字符,解决了多语言编码方式不同的问题;编解码的可逆性:之前几种分词算法对空格的处理略显粗暴,有时是无法还原的。Sentencepiece显式地将空白作为基本标记来处理,用一个元符号 “▁”( U+2581 )转义空白,这样就可以实现简单且可逆的编解码;无须Pre-tokenization:Sentencepiece可以直接从raw text/setences进行训练,无须Pre-tokenization;Fast and lightweight;编解码的可逆性:Decode(Encode(Normalized(text)))= Normalized(text)一个中文转Unicode的示例:https://tool.chinaz.com/tools/unicode.aspx refs:https://github.com/google/sentencepiecesentencepiece原理与实践https://www.zhihu.com/tardis/zm/art/159200073?source_id=1003 6.主流subword算法的对比wordpiece和BPE的对比wordpiece和BPE的对比:都是走的合并的思路,将语料拆分成最小单元(英文中26个字母加上各种符号,这些作为初始词表)然后进行合并,词表从小到大;核心区别就在于wordpiece是按token间的互信息来进行合并而BPE是按照token一同出现的频率来合并的。wordpiece和ULM的对比:wordpiece和ULM的对比:都使用语言模型来挑选子词;区别在于前者词表由小到大,而后者词表由大到小,先初始化一个大词表,根据评估准则不断丢弃词表,直到满足限定条件。ULM算法考虑了句子的不同分词可能,因而能够输出带概率的多个分词结果。三种subword分词算法的关系7.tokenizers库优先级靠后2.分词器1.BERT的分词器BERT的分词器由两个部分组成:BasicTokenizer:转成 unicode:Python3,输入为str时,可以省略这一步_clean_text:去除各种奇怪字符_tokenize_chinese_chars:中文按字拆开whitespace_tokenize:空格分词_run_strip_accents:去掉变音符号_run_split_on_punc:标点分词再次空格分词:whitespace_tokenize(" ".join(split_tokens)),先用空格join再按空白分词,可以去掉连续空格WordpieceTokenizer:贪心最大匹配:用双指针实现;核心代码:tokenize(self, text):2.T5的分词器3.GPT的分词器4.LLaMA的分词器5.GLM的分词器总结下面对主流模型使用的分词器进行总结(待完善):参考资料深入理解NLP Subword算法:BPE、WordPiece、ULM - Luke的文章 - 知乎https://zhuanlan.zhihu.com/p/86965595NLP三大Subword模型详解:BPE、WordPiece、ULM - 阿北的文章 - 知乎https://zhuanlan.zhihu.com/p/191648421NLP中的subword算法及实现 - 微胖界李现的文章 - 知乎https://zhuanlan.zhihu.com/p/112444056NLP BERT GPT等模型中 tokenizer 类别说明详解https://cloud.tencent.com/developer/article/1865689BERT 客制化分词器和 WWM 的实现 - 满甲的文章 - 知乎https://zhuanlan.zhihu.com/p/268515387bert第三篇:tokenizerhttps://blog.csdn.net/iterate7/article/details/108959082BERT 是如何分词的https://blog.csdn.net/u010099080/article/details/102587954同:BERT 是如何分词的 - Alan Lee的文章 - 知乎https://zhuanlan.zhihu.com/p/132361501Bert系列伴生的新分词器https://dxzmpk.github.io/2020/04/29/Bert%E7%B3%BB%E5%88%97%E4%BC%B4%E7%94%9F%E7%9A%84%E6%96%B0%E5%88%86%E8%AF%8D%E5%99%A8/Tokenizers: How machines readhttps://blog.floydhub.com/tokenization-nlp/【HugBert11】聚沙成塔:关于tokenization(词元化)的解疑释惑 - 套牌神仙的文章 - 知乎https://zhuanlan.zhihu.com/p/371300063japanese and korean voice searchhttps://static.googleusercontent.com/media/research.google.com/zh-CN//pubs/archive/37842.pdf[1508.07909] Neural Machine Translation of Rare Words with Subword Units 3-3 Transformers Tokenizer API 的使用https://www.zhihu.com/tardis/zm/art/390821442?source_id=1003关于transformers库中不同模型的Tokenizer - 莫冉的文章 - 知乎https://zhuanlan.zhihu.com/p/121787628NLP领域中的token和tokenization到底指的是什么? - 知乎https://www.zhihu.com/question/64984731NLP中的Tokenization - 薛定谔没养猫的文章 - 知乎https://zhuanlan.zhihu.com/p/444774532大模型中的分词器tokenizer:BPE、WordPiece、Unigram LM、SentencePiece - 眼睛里进砖头了的文章 - 知乎https://zhuanlan.zhihu.com/p/620508648浅谈Byte-Level BPE - CaesarEX的文章 - 知乎https://zhuanlan.zhihu.com/p/146114164 理解NLP最重要的编码方式 — Byte Pair Encoding (BPE),这一篇就够了 - 硅谷谷主的文章 - 知乎https://zhuanlan.zhihu.com/p/424631681 Neural Machine Translation with Byte-Level Subwordshttps://arxiv.org/abs/1909.03341 tokenizers小结 - 马东什么的文章 - 知乎https://zhuanlan.zhihu.com/p/360290118 互信息https://zh.wikipedia.org/wiki/%E4%BA%92%E4%BF%A1%E6%81%AFPython unicodedata.normalize 将Unicode文本标准化https://blog.csdn.net/weixin_43866211/article/details/98384017Weaknesses of WordPiece Tokenizationhttps://medium.com/@rickbattle/weaknesses-of-wordpiece-tokenization-eb20e37fec99 Subwordhttps://paddlepedia.readthedocs.io/en/latest/tutorials/pretrain_model/subword.html sentencepiece原理与实践https://www.zhihu.com/tardis/zm/art/159200073?source_id=1003 抱抱脸:https://huggingface.co/docs/transformers/tokenizer_summaryhttps://huggingface.co/learn/nlp-course/zh-CN/chapter2/4?fw=tfhttps://huggingface.co/learn/nlp-course/chapter6/7?fw=pthttps://huggingface.co/learn/nlp-course/chapter6/5?fw=pt编辑于 2023-05-03 00:20​赞同 151​​7 条评论​分享​收藏​喜欢

对 Python 代码使用的词语标记化器 tokenize,你懂了吗?【Python|标准库】 - 知乎

对 Python 代码使用的词语标记化器 tokenize,你懂了吗?【Python|标准库】 - 知乎切换模式写文章登录/注册对 Python 代码使用的词语标记化器 tokenize,你懂了吗?【Python|标准库】不如与琪tokenize(Python 标准库之一) token: n. 象征;标志; adj. 作为标志的; -ize: suff. 使成...状态;使...化; tokenize:标识化;标记化; tokenize 提供了“对 Python 代码使用的”词汇扫描器,是用 Python 实现的。扫描器可以给 Python 代码打上标记后返回,你可以看到每一个词或者字符是什么类型的。扫描器甚至将注释也单独标记,这样某些需要对代码进行特定风格展示的地方就很方便了。为了简化标记流(token stream)的处理,所有的运算符(Operators)、分隔符(Delimiters) 和 Ellipsis(不是英文,就是 Python 中的一个变量,和省略号一样)都会被标记为 OP (一个表示标识类型的常量)类型。具体的类型可以通过tokenize.tokenize() 返回的具名元祖对象的 .exact_type 属性查看。exact_type 是一个 @property 修饰的方法,所以只有调用时才精确的查看到底是什么类型的文本,这样就简化了标记流的处理标记的输入主要的入口是一个生成器:tokenize.tokenize(readline)生成器 tokenize() 需要一个参数:readline,它必须是一个可调用的对象,并且提供了与文件对象的 io.IOBase.readline() 相同的接口。每次调用这个函数,都应该返回一行字节类型的输入生成器会生成有5个元素的具名元组,内容是:type:标记类型string:被标记的字符串start:一个整数组成的 2-元组:(srow, scol),这个标记的开始位置的行和列。s:start;end:一个整数组成的 2-元组:(erow, ecol),这个标记的结束为止的行和列。e:end;line:被标记的字符串所在的那一行,就是输入的那一行的内容返回的具名元组还有一个额外的属性 exact_type,标识了类型为 OP 词的确切操作类型。对于所有 OP 以外的标记,exact_type 的值等于 type 的值。tokenize() 通过查找 UTF-8 BOM 或者编码 cookie 来确认文件的源编码。tokenize.generate_tokens(readline)将对 unicode 类型的字符串进行标记,而不是字节类型。像 tokenize() 一样,readline 参数需要可调用,并且返回输入的一行,但是需要返回 str 对象,而不是 bytes。返回的结果是一个迭代器,返回的具名元祖和 tokenize() 的完全一样。只不过没有 ENCODING (一种表示标识类型的常量)类型的标记。(tokenize() 第一个返回的就是 ENCODING 标记的内容)ENCODING 和 OP 一样是常量,还有很多,都是用来标记类型的,在 tokenize 库里直接用即可,是从 token 包里直接导过来的。还有一个函数提供反转标记过程的功能。有些工具要标记化一个脚本、修改标记流、回写修改后的脚本,这个函数就能派上用场了。tokenize.untokenize(iterable)把标记转装成 Python 源代码(指用 Python 写成的代码)。可迭代对象 iterable 返回的序列中每一个对象至少要有两个元素构成:标记类型和标记的字符串。其他的元素都会被忽略。反转生成的脚本会作为一个单独的字符串返回。返回的是字节类型的,使用 ENCODING 标记的内容进行编码,如果输入中没有这个标记的,那就返回 str 类型的。tokenize() 需要查出源文件的编码,它用于执行此操作的函数也是可用的:tokenize.detect_encoding(readline)detect_encoding() 函数用来检测应该用于解码 Pyhton 源文件的编码。它需要一个参数 readline,和生成器 tokenize() 所需的相同它最多会调用 readline 两次,然后返回要使用的编码(一个字符串)和它已读入的每一行(不是从字节解码的)组成的列表它根据 PEP 263 中规定的方式从 UTF-8 BOM 或者编码 cookie 中检测编码方式。如果 BOM 和 cookie 都存在但不一致,会抛出 SyntaxError。如果找到 BOM,'utf-8-sig' 将作为编码返回。如果没有指定编码,就返回默认的 'utf-8'。使用 open() 打开 Python 源文件:它使用 detect_encoding() 检测文件编码tokenize.open(filename)使用 detect_encoding() 检测到的编码通过只读方式打开一个文件异常:tokenize.TokenError当一个文档字符串或表达式可能被分割成多行,但在文件中的任何地方都没能完成时抛出。例如:"""文档字符串

开头或者[

1,

2,

3注意:未关闭的单引号字符串不会引发错误。它们会被标记为 ERRORTOKEN(一种标记类型常量) ,然后是其内容的标记化。命令行用法tokenize 包可以从命令行以脚本的形式执行。python -m tokenize [-e] [filename.py]有以下可选参数-h, --help展示帮助信息-e, --exact使用确切的类型展示标识类型如果 filename.py 指定,它里面的内容就用作标记化,否则就在 stdin 获取输入。示例1、将浮点文字转换为 Decimal 对象的脚本重写器from tokenize import tokenize, untokenize, NUMBER, STRING, NAME, OP

from io import BytesIO

def decistmt(s):

"""用 Decimal 替换语句字符串中的浮点数。

>>> from decimal import Decimal

>>> s = 'print(+21.3e-5*-.1234/81.7)'

>>> decistmt(s)

"print (+Decimal ('21.3e-5')*-Decimal ('.1234')/Decimal ('81.7'))"

在不同的平台,下面这句的结果可能不同。第一个是在 macOS,第二个是在 Win10。

>>> exec(s)

-3.21716034272e-07

-3.217160342717258e-07

在所有平台上,Decimal 的输出应该都是一致的。

>>> exec(decistmt(s))

-3.217160342717258261933904529E-7

"""

result = []

g = tokenize(BytesIO(s.encode('utf-8')).readline) # 标记化字符串

for toknum, tokval, _, _, _ in g:

if toknum == NUMBER and '.' in tokval: # 把数字类型的转换后保存

result.extend([

(NAME, 'Decimal'),

(OP, '('),

(STRING, repr(tokval)),

(OP, ')')

])

else:

result.append((toknum, tokval))

return untokenize(result).decode('utf-8')2、使用命令行的例子脚本:def say_hello():

print("Hello, World!")

say_hello()(文件内容就写上面这样,末尾没有空行)会标记后输出为下面的样子,第一列是找到标记的范围,第二列是标记的类型名字,第三列是被标记的词(输入的值)$ python -m tokenize hello.py

0,0-0,0: ENCODING 'utf-8'

1,0-1,3: NAME 'def'

1,4-1,13: NAME 'say_hello'

1,13-1,14: OP '('

1,14-1,15: OP ')'

1,15-1,16: OP ':'

1,16-1,17: NEWLINE '\n'

2,0-2,4: INDENT ' '

2,4-2,9: NAME 'print'

2,9-2,10: OP '('

2,10-2,25: STRING '"Hello, World!"'

2,25-2,26: OP ')'

2,26-2,27: NEWLINE '\n'

3,0-3,1: NL '\n'

4,0-4,0: DEDENT ''

4,0-4,9: NAME 'say_hello'

4,9-4,10: OP '('

4,10-4,11: OP ')'

4,11-4,12: NEWLINE '\n'

5,0-5,0: ENDMARKER ''可以使用 -e 来显示确切标识名称$ python -m tokenize -e hello.py

0,0-0,0: ENCODING 'utf-8'

1,0-1,3: NAME 'def'

1,4-1,13: NAME 'say_hello'

1,13-1,14: LPAR '('

1,14-1,15: RPAR ')'

1,15-1,16: COLON ':'

1,16-1,17: NEWLINE '\n'

2,0-2,4: INDENT ' '

2,4-2,9: NAME 'print'

2,9-2,10: LPAR '('

2,10-2,25: STRING '"Hello, World!"'

2,25-2,26: RPAR ')'

2,26-2,27: NEWLINE '\n'

3,0-3,1: NL '\n'

4,0-4,0: DEDENT ''

4,0-4,9: NAME 'say_hello'

4,9-4,10: LPAR '('

4,10-4,11: RPAR ')'

4,11-4,12: NEWLINE '\n'

5,0-5,0: ENDMARKER ''3、以编程方式标记文件的例子1、用 generate_tokens() 读取 unicode 字符串而不是字节类型的。import tokenize

with tokenize.open('hello.py') as f:

tokens = tokenize.generate_tokens(f.readline)

for token in tokens:

print(token)结果如下,可见用 generate_tokens() 是得不到 ENCODING 的TokenInfo(type=1 (NAME), string='def', start=(1, 0), end=(1, 3), line='def say_hello():\n')

TokenInfo(type=1 (NAME), string='say_hello', start=(1, 4), end=(1, 13), line='def say_hello():\n')

TokenInfo(type=54 (OP), string='(', start=(1, 13), end=(1, 14), line='def say_hello():\n')

TokenInfo(type=54 (OP), string=')', start=(1, 14), end=(1, 15), line='def say_hello():\n')

TokenInfo(type=54 (OP), string=':', start=(1, 15), end=(1, 16), line='def say_hello():\n')

TokenInfo(type=4 (NEWLINE), string='\n', start=(1, 16), end=(1, 17), line='def say_hello():\n')

TokenInfo(type=5 (INDENT), string=' ', start=(2, 0), end=(2, 4), line=' print("Hello, World!")\n')

TokenInfo(type=1 (NAME), string='print', start=(2, 4), end=(2, 9), line=' print("Hello, World!")\n')

TokenInfo(type=54 (OP), string='(', start=(2, 9), end=(2, 10), line=' print("Hello, World!")\n')

TokenInfo(type=3 (STRING), string='"Hello, World!"', start=(2, 10), end=(2, 25), line=' print("Hello, World!")\n')

TokenInfo(type=54 (OP), string=')', start=(2, 25), end=(2, 26), line=' print("Hello, World!")\n')

TokenInfo(type=4 (NEWLINE), string='\n', start=(2, 26), end=(2, 27), line=' print("Hello, World!")\n')

TokenInfo(type=61 (NL), string='\n', start=(3, 0), end=(3, 1), line='\n')

TokenInfo(type=6 (DEDENT), string='', start=(4, 0), end=(4, 0), line='say_hello()')

TokenInfo(type=1 (NAME), string='say_hello', start=(4, 0), end=(4, 9), line='say_hello()')

TokenInfo(type=54 (OP), string='(', start=(4, 9), end=(4, 10), line='say_hello()')

TokenInfo(type=54 (OP), string=')', start=(4, 10), end=(4, 11), line='say_hello()')

TokenInfo(type=4 (NEWLINE), string='', start=(4, 11), end=(4, 12), line='')

TokenInfo(type=0 (ENDMARKER), string='', start=(5, 0), end=(5, 0), line='')2、或者直接使用 tokenize() 读取字节类型的:import tokenize

with open('hello.py', 'rb') as f:

tokens = tokenize.tokenize(f.readline)

for token in tokens:

print(token)标记化的结果与 例2 中一致,只是多了一些信息。附表所有的标记类型Operators以下形符属于运算符:+ - * ** / // % @

<< >> & | ^ ~ :=

< > <= >= == !=Delimiters以下形符在语法中归类为分隔符:( ) [ ] { }

, : . ; @ = ->

+= -= *= /= //= %= @=

&= |= ^= >>= <<= **=句点也可出现于浮点数和虚数字面值中。连续三个句点有表示一个省略符的特殊含义。以上列表的后半部分为增强赋值操作符,在词法中作为分隔符,但也起到运算作用。以下可打印 ASCII 字符作为其他形符的组成部分时具有特殊含义,或是对词法分析器有重要意义:' " # \发布于 2020-03-16 20:04编程语言Python 开发Python​赞同 4​​添加评论​分享​喜欢​收藏​申请

3-3 Transformers Tokenizer API 的使用 - 知乎

3-3 Transformers Tokenizer API 的使用 - 知乎首发于transformers 教程切换模式写文章登录/注册3-3 Transformers Tokenizer API 的使用sergio一个脱离了低级趣味的人Transformers Tokenizer 的使用Tokenizer 分词器,在NLP任务中起到很重要的任务,其主要的任务是将文本输入转化为模型可以接受的输入,因为模型只能输入数字,所以 tokenizer 会将文本输入转化为数值型的输入,下面将具体讲解 tokenization pipeline.Tokenizer 类别例如我们的输入为: Let's do tokenization! 不同的tokenization 策略可以有不同的结果,常用的策略包含如下: - word base:按照词进行分词 - character base:按照单词进行分词 - subword tokenization:按照subword进行分词 等Word-based我们如果需要根据 word base 进行 tokenization 的话,有两种方式,一种是根据 whitespace 进行分割,一种是根据标点符号进行分割,然后再做数字的映射。每个 word 都被赋予一个ID,这个 ID 的范围是从0到 vocabulary size,这种方式有一种问题,就是很容易出现例如,dog 和 dogs,虽然是相近的词,但是被分配了完全不同的无关的id。对于不在vocabulary 库里面的词,我们会分配 [UNK],代表未知词。Character baseChar base的 tokenization 方式,就是用char,而不是word。 这种方式的好处在于:vocabulary size 很小比较少机会出现 out of vocabulary 的问题。但是这种方式的硬伤也很重,因为这种方式会导致文本无意义,每个character 都是无意义的,只有组成了单词才是有意义的,对于character 进行embedding无法满足文本的语义要求。当然这种问题主要出现在英文中或者拉丁语系中,中文的话,每个字的意思就丰富多了,所以也常用这种方式。此外,对于英文,一个单词可以分为N个字母,这就导致了每个模型都需要处理多个token,导致模型速度慢,可以输入的文本长度变小。Subword tokenization为了平衡上述两者的问题,聪明的科学家想出了使用 subword tokenization 的方式进行分词。subword tokenization 依赖的原则是:常见词不应该分成subword,不常见的词应该分为更有意义的subword例如:tokenization 代表不常见的词,可以被分为:token和ization,annoyingly 被分为 annoying 和 ly,这对于英文来说是很有意义的,因为英文本来就是由于词根和词缀组成的。下面可以看看 “Let’s do tokenization!“ 在经过subword tokenization 后的情况: 其他的方式还有很多常见的方式例如: Byte-level BPE, as used in GPT-2 WordPiece, as used in BERT SentencePiece or Unigram, as used in several multilingual modelsload 和 savetokenizer 的加载和保存和 models 的方式一致,都是使用方法:from_pretrained, save_pretrained. 这个方法会加载和保存tokenizer使用的模型结构(例如sentence piece就有自己的模型结构),以及字典。下面是一个使用的example:from transformers import BertTokenizer

tokenizer = BertTokenizer.from_pretrained("bert-base-cased", use_fast=True) # 可以使用use fast加速和 AutoModel 类似,也有 AutoTokenizer这种class,它可以根据传入的 checkpoint,找到适当的 tokenizer class,并且加载 checkpoint:from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")在加载完模型之后,我们可以直接使用tokenzer对文本进行tokenizer pipeline:tokenizer("Using a Transformer network is simple")

{'input_ids': [101, 7993, 170, 11303, 1200, 2443, 1110, 3014, 102],

'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0],

'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]}并且可以进行保存:tokenizer.save_pretrained("directory_on_my_computer")Encoding将文本转化为数字的过程成为 encoding,encoding 主要包含了两个步骤: - 1. tokenization: 对文本进行分词 - 2. convert_tokens_to_ids:将分词后的token 映射为数字TokenizationTokenization 的过程是通过 tokenize 的方法实现的:from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")

sequence = "Using a Transformer network is simple"

tokens = tokenizer.tokenize(sequence)

print(tokens)

>>>

['Using', 'a', 'transform', '##er', 'network', 'is', 'simple']这边使用的是wordpiece 的分词器,它将 transformer 分为了 transform 和 ##erconvert_tokens_to_ids分完词之后,需要将每个token映射为 id, 这边是使用 convert_tokens_to_ids 的方法进行:ids = tokenizer.convert_tokens_to_ids(tokens)

print(ids)

>>>

[7993, 170, 11303, 1200, 2443, 1110, 3014]DecodingDecoding 的作用是将输出的 ids 转化为文本,这可以使用 tokenizer 的 decode 方法:decoded_string = tokenizer.decode([7993, 170, 11303, 1200, 2443, 1110, 3014])

print(decoded_string)

>>>

'Using a Transformer network is simple'Handling multiple sequences我们在使用 tokenizer 的时候,可能会出现一些问题: - 对于batch的输入,如果我们输入了多个长度的句子,我们将如何处理? - 只输入 tokens 对应id 是不是就足够了? - 如果文本长度过长,怎么处理?模型只支持batch 的输入我们可以看下面的例子:import torch

from transformers import AutoTokenizer, AutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"

tokenizer = AutoTokenizer.from_pretrained(checkpoint)

model = AutoModelForSequenceClassification.from_pretrained(checkpoint)

sequence = "I've been waiting for a HuggingFace course my whole life."

tokens = tokenizer.tokenize(sequence)

ids = tokenizer.convert_tokens_to_ids(tokens)

input_ids = torch.tensor(ids) # model 只支持 tensor

# This line will fail.

model(input_ids)可以看到,爆了如下的错误:IndexError: Dimension out of range (expected to be in range of [-1, 0], but got 1)这是因为,模型需要传入的输入是 [batch_size, sequence_length] 的 tensor。于是我们需要,对上面代码进行修改:import torch

from transformers import AutoTokenizer, AutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"

tokenizer = AutoTokenizer.from_pretrained(checkpoint)

model = AutoModelForSequenceClassification.from_pretrained(checkpoint)

sequence = "I've been waiting for a HuggingFace course my whole life."

tokens = tokenizer.tokenize([sequence]) # 变成batch size = 1

ids = tokenizer.convert_tokens_to_ids(tokens)

input_ids = torch.tensor([ids])

print("Input IDs:", input_ids)

output = model(input_ids)

print("Logits:", output.logits)

>>>

Input IDs: [[ 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012]]

Logits: [[-2.7276, 2.8789]]batch 内的 ids 需要 padding我们都知道 model 只接受 tensor 作为输入,不能接受 list of lists,而tensor 必须保证每个vector 都有相同的长度: 所以下面的数组是不能转华为 tensor 的。batched_ids = [

[200, 200, 200],

[200, 200]

]为了解决这个问题,我们往往需要对输入进行 padding,使得每个输入的 vector 都具有相同的长度,如果我们有2个句子,其中一个句子只有3个字,一个句子2个字,我们为了导出为tensor,可以将其他的句子都进行padding,然后得到两个3个字的句子。padding_id = 100

batched_ids = [

[200, 200, 200],

[200, 200, padding_id]

]在 tokenizer 中,我们可以通过 tokenizer.pad_token_id 确认 padding id。model = AutoModelForSequenceClassification.from_pretrained(checkpoint)

sequence1_ids = [[200, 200, 200]]

sequence2_ids = [[200, 200]]

batched_ids = [[200, 200, 200], [200, 200, tokenizer.pad_token_id]]

print(model(torch.tensor(sequence1_ids)).logits)

print(model(torch.tensor(sequence2_ids)).logits)

print(model(torch.tensor(batched_ids)).logits)

>>>

tensor([[ 1.5694, -1.3895]], grad_fn=)

tensor([[ 0.5803, -0.4125]], grad_fn=)

tensor([[ 1.5694, -1.3895],

[ 1.3373, -1.2163]], grad_fn=)上面我们注意到,同样输入 [200, 200] 和 [200, 200, tokenizer.pad_token_id], 我们在经过模型的时候,得到的结果是完全不同的。 这是因为我们的模型在做句子表征的时候,也将padding token id 进行了考虑,导致每个词对应的输出不同,为了告诉模型我们的输入中,某些词是不需要考虑的,我们需要传入 attention mask。Attention masksAttention masks 和输入的 input ids 具有完全一样的shape,其中1 代表了这个id需要attention,0代表这个id不需要attention。batched_ids = [

[200, 200, 200],

[200, 200, tokenizer.pad_token_id]

]

attention_mask = [

[1, 1, 1],

[1, 1, 0]

]

outputs = model(torch.tensor(batched_ids), attention_mask=torch.tensor(attention_mask))

print(outputs.logits)

>>>

tensor([[ 1.5694, -1.3895],

[ 0.5803, -0.4125]], grad_fn=)Longer sequences 长句子每个预训练的语言模型在模型定义的时候,都限定了最长的输入长度,例如 BERT-base 为512, BERT-large 为1024 个tokens。 - 所以如果文本 tokenized 后的长度超过模型可以处理的长度,我们需要进行截断(truncate)。 - 或者选用可以处理更长文本的模型,例如 Longformer,LED,或者 Big Bird我们可以指定,max_sequence_length 参数对文本进行截断:sequence = sequence[:max_sequence_length]Tokenizer 的封装我们了解了 tokenize,conver to ids, padding, attention mask,以及truncate 后,我们发现,对于文本的输入,我们需要进行一些列的 pipeline 才能得到模型的输入。这时候我们是否可以有封装的方法可以直接使用,而不用一次又一次地调用完整的步骤?其实可以的,我们可以直接使用 tokenizer(text(s)) 就可以直接获得所有的模型的输入。而且它支持输入单句子或者句子list。from transformers import AutoTokenizer

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"

tokenizer = AutoTokenizer.from_pretrained(checkpoint)

sequence = "I've been waiting for a HuggingFace course my whole life."

model_inputs = tokenizer(sequence)或者直接输入句子串:sequences = [

"I've been waiting for a HuggingFace course my whole life.",

"So have I!"

]

model_inputs = tokenizer(sequences)对于文本长度的限定,我们可以通过指定 padding,以及 max_length。 - padding = 'longest': padding 到batch 中句子最长的长度 - padding = 'max_length': padding 到模型最大的输入长度,如果指定了 max_length, 则padding 到 max_length# Will pad the sequences up to the maximum sequence length

model_inputs = tokenizer(sequences, padding="longest")

# Will pad the sequences up to the model max length

# (512 for BERT or DistilBERT)

model_inputs = tokenizer(sequences, padding="max_length")

# Will pad the sequences up to the specified max length

model_inputs = tokenizer(sequences, padding="max_length", max_length=8)同样,如果文本太长,可以设定 truncation=Truesequences = [

"I've been waiting for a HuggingFace course my whole life.",

"So have I!"

]

# Will truncate the sequences that are longer than the model max length

# (512 for BERT or DistilBERT)

model_inputs = tokenizer(sequences, truncation=True)

# Will truncate the sequences that are longer than the specified max length

model_inputs = tokenizer(sequences, max_length=8, truncation=True)tokenizer 默认返回的结果是np,但是模型的输入必须是tensor: - "np" returns NumPy arrays: - "pt" returns PyTorch tensors - "tf" returns TensorFlow tensors所以需要传入参数 return_tensorssequences = [

"I've been waiting for a HuggingFace course my whole life.",

"So have I!"

]

# Returns PyTorch tensors

model_inputs = tokenizer(sequences, padding=True, return_tensors="pt")

# Returns TensorFlow tensors

model_inputs = tokenizer(sequences, padding=True, return_tensors="tf")

# Returns NumPy arrays

model_inputs = tokenizer(sequences, padding=True, return_tensors="np")特殊的字符如果我们查看 tokenizer 的返回结果:model_inputs["input_ids"] 我们可以查看到,文本的输出和之前的输出不太一样。sequence = "I've been waiting for a HuggingFace course my whole life."

model_inputs = tokenizer(sequence)

print(model_inputs["input_ids"])

tokens = tokenizer.tokenize(sequence)

ids = tokenizer.convert_tokens_to_ids(tokens)

print(ids)

>>>

[101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012, 102]

[1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012]我们对输出ids 进行decode ,可以得到如下的结果:print(tokenizer.decode(model_inputs["input_ids"]))

print(tokenizer.decode(ids))

>>>

"[CLS] i've been waiting for a huggingface course my whole life. [SEP]"

"i've been waiting for a huggingface course my whole life."我们可以看到模型的输入增加了特殊的字符 [CLS] 和 [SEP], 这是因为,bert 模型训练的时候,也增加了这些特殊字符,为了保证训练和预测一致,其tokenizer也对输入进行了改造。不同的model在训练的时候,新增特殊的字符也不一样,例如 roberta 加的 字符为 和 。所以我们尽量可以直接使用 tokenizer 对模型进行embedding 的构造,可以减少很多数据的处理流程。除此之外,模型还需要很多其他输入,例如BERT 需要输入 token type ids,代表文本的类型,具体每个模型都有点差异。Wrapping up: From tokenizer to model所以我们可以发现,tokenizer 帮我们处理了所有, 对文本进行特殊字符的添加 padding truncation encoding (tokenize,convert_tokens_to_ids) 转化为tensor 输出 model 需要的attention mask (optional) 以及输出 token type idsimport torch

from transformers import AutoTokenizer, AutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"

tokenizer = AutoTokenizer.from_pretrained(checkpoint)

model = AutoModelForSequenceClassification.from_pretrained(checkpoint)

sequences = [

"I've been waiting for a HuggingFace course my whole life.",

"So have I!"

]

tokens = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt")

output = model(**tokens)编辑于 2022-04-25 23:24BERT NLP自然语言处理Transformers(书籍)​赞同 149​​14 条评论​分享​喜欢​收藏​申请转载​文章被以下专栏收录transformers 教程NLP 库 transformers

一文看懂NLP里的分詞-Tokenization(中英文區別+3大難點+3種典型方法)

一文看懂NLP里的分詞-Tokenization(中英文區別+3大難點+3種典型方法)

首頁

AI 知識庫

AI 知識專題

AI 新聞

友情鏈接

Search in:

all

knowledge base

FAQ

post

page

Close Search

Suggested Search:

人工智慧, 機器學習, 深度學習, NLP

HomeAI 知識庫自然語言處理

分詞 – Tokenization

文章目錄

分詞是 NLP 的基礎任務,將句子,段落分解為字詞單位,方便後續的處理的分析。

本文將介紹分詞的原因,中英文分詞的3個區別,中文分詞的3大難點,分詞的3種典型方法。最後將介紹中文分詞和英文分詞常用的工具。

想要了解更多 NLP 相關的內容,請訪問  NLP專題 ,免費提供59頁的NLP文檔下載。

訪問 NLP 專題,下載 59 頁免費 PDF

 

什麼是分詞?

分詞是 自然語言理解 – NLP 的重要步驟。

分詞就是將句子、段落、文章這種長文本,分解為以字詞為單位的數據結構,方便後續的處理分析工作。

 

為什麼要分詞?

1.將複雜問題轉化為數學問題

在 機器學習的文章 中講過,機器學習之所以看上去可以解決很多複雜的問題,是因為它把這些問題都轉化為了數學問題。

而 NLP 也是相同的思路,文本都是一些「非結構化數據」,我們需要先將這些數據轉化為「結構化數據」,結構化數據就可以轉化為數學問題了,而分詞就是轉化的第一步。

2.詞是一個比較合適的粒度

詞是表達完整含義的最小單位。

字的粒度太小,無法表達完整含義,比如」鼠「可以是」老鼠「,也可以是」滑鼠「。

而句子的粒度太大,承載的信息量多,很難復用。比如」傳統方法要分詞,一個重要原因是傳統方法對遠距離依賴的建模能力較弱。」

3. 深度學習時代,部分任務中也可以「分字」

深度學習時代,隨著數據量和算力的爆炸式增長,很多傳統的方法被顛覆。

分詞一直是 NLP 的基礎,但是現在也不一定了,感興趣的可以看看這篇論文:《Is Word Segmentation Necessary for Deep Learning of Chinese Representations?》。

不過在一些特定任務中,分詞還是必要的。如:關鍵詞提取、命名實體識別等。

 

中英文分詞的3個典型區別

區別1:分詞方式不同,中文更難

英文有天然的空格作為分隔符,但是中文沒有。所以如何切分是一個難點,再加上中文裡一詞多意的情況非常多,導致很容易出現歧義。下文中難點部分會詳細說明。

 

區別2:英文單詞有多種形態

英文單詞存在豐富的變形變換。為了應對這些複雜的變換,英文NLP相比中文存在一些獨特的處理步驟,我們稱為詞形還原(Lemmatization)和詞幹提取(Stemming)。中文則不需要

詞性還原:does,done,doing,did 需要通過詞性還原恢復成 do。

詞幹提取:cities,children,teeth 這些詞,需要轉換為 city,child,tooth」這些基本形態

 

區別3:中文分詞需要考慮粒度問題

例如「中國科學技術大學」就有很多種分法:

中國科學技術大學

中國 \ 科學技術 \ 大學

中國 \ 科學 \ 技術 \ 大學

粒度越大,表達的意思就越準確,但是也會導致召回比較少。所以中文需要不同的場景和要求選擇不同的粒度。這個在英文中是沒有的。

 

中文分詞的3大難點

難點 1:沒有統一的標準

目前中文分詞沒有統一的標準,也沒有公認的規範。不同的公司和組織各有各的方法和規則。

 

難點 2:歧義詞如何切分

例如「兵乓球拍賣完了」就有2種分詞方式表達了2種不同的含義:

乒乓球 \ 拍賣 \ 完了

乒乓 \ 球拍 \ 賣 \ 完了

 

難點 3:新詞的識別

信息爆炸的時代,三天兩頭就會冒出來一堆新詞,如何快速的識別出這些新詞是一大難點。比如當年「藍瘦香菇」大火,就需要快速識別。

 

3種典型的分詞方法

分詞的方法大致分為 3 類:

基於詞典匹配

基於統計

基於深度學習

給予詞典匹配的分詞方式

優點:速度快、成本低

缺點:適應性不強,不同領域效果差異大

基本思想是基於詞典匹配,將待分詞的中文文本根據一定規則切分和調整,然後跟詞典中的詞語進行匹配,匹配成功則按照詞典的詞分詞,匹配失敗通過調整或者重新選擇,如此反覆循環即可。代表方法有基於正向最大匹配和基於逆向最大匹配及雙向匹配法。

 

基於統計的分詞方法

優點:適應性較強

缺點:成本較高,速度較慢

這類目前常用的是演算法是HMM、CRF、SVM、深度學習等演算法,比如stanford、Hanlp分詞工具是基於CRF演算法。以CRF為例,基本思路是對漢字進行標註訓練,不僅考慮了詞語出現的頻率,還考慮上下文,具備較好的學習能力,因此其對歧義詞和未登錄詞的識別都具有良好的效果。

 

基於深度學習

優點:準確率高、適應性強

缺點:成本高,速度慢

例如有人員嘗試使用雙向LSTM+CRF實現分詞器,其本質上是序列標註,所以有通用性,命名實體識別等都可以使用該模型,據報道其分詞器字元準確率可高達97.5%。

常見的分詞器都是使用機器學習演算法和詞典相結合,一方面能夠提高分詞準確率,另一方面能夠改善領域適應性。

 

中文分詞工具

下面排名根據 GitHub 上的 star 數排名:

Hanlp

Stanford 分詞

ansj 分詞器

哈工大 LTP

KCWS分詞器

jieba

IK

清華大學THULAC

ICTCLAS

 

英文分詞工具

Keras

Spacy

Gensim

NLTK

 

總結

分詞就是將句子、段落、文章這種長文本,分解為以字詞為單位的數據結構,方便後續的處理分析工作。

分詞的原因:

將複雜問題轉化為數學問題

詞是一個比較合適的粒度

深度學習時代,部分任務中也可以「分字」

中英文分詞的3個典型區別:

分詞方式不同,中文更難

英文單詞有多種形態,需要詞性還原和詞幹提取

中文分詞需要考慮粒度問題

中文分詞的3大難點

沒有統一的標準

歧義詞如何切分

新詞的識別

3個典型的分詞方式:

基於詞典匹配

基於統計

基於深度學習

 

百度百科+維基百科

百度百科版本

中文分詞就是將連續的字序列按照一定的規範重新組合成詞序列的過程。我們知道,在英文的行文中,單詞之間是以空格作為自然分界符的,而中文只是字、句和段能通過明顯的分界符來簡單劃界,唯獨詞沒有一個形式上的分界符,雖然英文也同樣存在短語的劃分問題,不過在詞這一層上,中文比之英文要複雜得多、困難得多。

查看詳情

 

維基百科版本

分詞是對一串輸入字元的部分進行劃分和可能分類的過程。然後將得到的標記傳遞給某種其他形式的處理。該過程可以被認為是解析輸入的子任務。

查看詳情

2019年8月8日

by 打不死的小強

Updated: 2022年8月16日

自然語言處理

NLP, Tokenization, 中文分詞, 分詞, 自然語言處理

Thanks for your rating!

You have already rated this article

An error occured, please try again later

Was This Article Helpful?

18

Related Articles

Encoder-Decoder 和 Seq2Seq

63

成分句法分析

1

詞性標註 - Part of speech

4

BERT | Bidirectional Encoder Representation from Transformers

5

詞幹提取 - Stemming | 詞形還原 - Lemmatisation

4

Comments

There are no comments yet

Leave a comment 取消回復您的電子郵箱地址不會被公開。 必填項已用*標註評論 * 顯示名稱

電子郵箱地址

網站地址

Δ

關注我們的公眾號

好課推薦-成為AI產品經理

好課推薦-人工智慧基礎課

好課推薦-AI技術內參

好課推薦-物聯網開發實戰

人工智慧相關知識

AI 演算法

(37)

Attention 機制

Encoder-Decoder 和 Seq2Seq

Q-Learning

Adaboost 演算法

隨機森林 - Random forest

學習向量量化 - Learning vector quantization | LVQ

K鄰近 - k-nearest neighbors | KNN

線性判別分析 - Linear Discriminant Analysis | LDA

TF-IDF

元學習 - Meta learning

遺傳演算法(Genetic algorithm | GA)

判別式模型(Discriminative model)

產生式模型(Generative model)

Latent Dirichlet Allocation|LDA

啟發式演算法 - Heuristic

粒子群演算法(Particle swarm optimization | PSO)

人工神經網路 - Artificial Neural Network | ANN

遷移學習(Transfer learning)

長短期記憶網路 - Long short-term memory | LSTM

生成對抗網路 - Generative Adversarial Networks | GAN

循環神經網路 - Recurrent Neural Network | RNN

卷積神經網路 - CNN

受限玻爾茲曼機(Restricted Boltzmann machine | RBM)

強化學習-Reinforcement learning | RL

自編碼器(Autoencoder)

前饋神經網路(Feedforward neural network)

模糊神經網路(Neuro-fuzzy | FNN)

自組織映射(Self-organization map | SOM)

K均值聚類(k-means clustering)

反向傳播演算法(Backpropagation)

集成學習(Ensemble Learning)

支持向量機 - Support Vector Machine | SVM

決策樹 - Decision tree

邏輯回歸 - Logistic regression

樸素貝葉斯 - Naive Bayes classifier | NBC

線性回歸 - linear regression

機器學習 - machine learning | ML

基礎科普

(15)

訓練集、驗證集、測試集(附:分割方法+交叉驗證)

分類模型評估指標——準確率、精準率、召回率、F1、ROC曲線、AUC曲線

預訓練(Pre-train)

無監督學習 - Unsupervised learning | UL

監督學習 - Supervised learning

大數據(Big data)

TPU(Tensor Processing Unit)

ASIC(Application Specific Integrated Circuit)

FPGA(Field-Programmable Gate Array)

GPU(Graphics Processing Unit)

算力 - computation

演算法 - Algorithm

圖靈測試 - The Turing Test

弱人工智慧、強人工智慧、超人工智慧

人工智慧 - Artificial intelligence | AI

數學基礎

(16)

張量 | Tensor

隨機梯度下降法(Stochastic gradient descent | SGD)

梯度下降法 - Gradient descent

約束優化(Constrained optimization)

估計理論(Estimation theory)

假設檢驗(Hypothesis test)

數理統計(Mathematical statistics)

最大後驗概率(Maximum a posteriori estimation | MAP)

最大似然估計 - Maximum Likelihood Estimate | MLE

後驗概率(Posterior probability)

先驗概率(Prior probability)

概率論(Probability theory)

矩陣 | Matrix

標量 | scalar

向量 | vector

線性代數(linear algebra)

機器學習

(12)

Adaboost 演算法

隨機森林 - Random forest

無監督學習 - Unsupervised learning | UL

監督學習 - Supervised learning

K均值聚類(k-means clustering)

集成學習(Ensemble Learning)

支持向量機 - Support Vector Machine | SVM

決策樹 - Decision tree

邏輯回歸 - Logistic regression

樸素貝葉斯 - Naive Bayes classifier | NBC

線性回歸 - linear regression

機器學習 - machine learning | ML

深度學習

(8)

膠囊神經網路

Attention 機制

深度學習 - Deep learning | DL

長短期記憶網路 - Long short-term memory | LSTM

生成對抗網路 - Generative Adversarial Networks | GAN

循環神經網路 - Recurrent Neural Network | RNN

卷積神經網路 - CNN

強化學習-Reinforcement learning | RL

特徵工程

(4)

分類特徵

數值類特徵

探索性數據分析 | EDA

特徵工程 - Feature Engineering

自然語言處理

(16)

Attention 機制

Encoder-Decoder 和 Seq2Seq

詞幹提取 - Stemming | 詞形還原 - Lemmatisation

分詞 - Tokenization

詞性標註 - Part of speech

成分句法分析

詞嵌入 | Word embedding

Word2vec

依存句法分析-Constituency-based parse trees

自然語言生成 - Natural-language generation | NLG

自然語言理解 - NLU | NLI

Transformer

文本挖掘 - Text mining

BERT | Bidirectional Encoder Representation from Transformers

命名實體識別 - Named-entity recognition | NER

自然語言處理-Natural language processing | NLP

計算機視覺

(5)

膠囊神經網路

人臉識別 | Facial recognition

計算機視覺 - Computer Vision | CV

卷積神經網路 - CNN

支持向量機 - Support Vector Machine | SVM

語音交互

(4)

語音合成標記語言-SSML丨Speech Synthesis Markup Language

語音識別技術 - ASR丨Automatic Speech Recognition

音素 - phone | phonetics

語音合成(Text to Speech | TTS)

AI 新聞

友情推廣:TreeMind樹圖思維導圖

AI會永遠改變內容營銷嗎?

使用機器學習,你只需要3個工具

終端設備上的AI —到目前為止我所知道的

數據科學生命周期的7個步驟–在業務中應用AI

關於 easyAI 人工智慧領域的百科全書,非常適合小白和新手入門 AI 領域。

現在市面上大家看到的絕大部分 AI 資料都是追求嚴謹的「理工科天書」,這個世界不缺少嚴謹真確晦澀難懂的 AI 資料,但是很缺容易理解的內容。

我們希望拋開複雜的公式,複雜的邏輯,複雜的專用名詞。做一套文科生也能看懂的 AI 知識庫。

站長郵箱:106366349@qq.com

熱門標籤AI

AI產品經理

AI 應用

bert

cnn

gan

gnn

google

GPT-2

keras

lstm

nlp

NLU

OpenAI

pytorch

RNN

tensorflow

transformer

word2vec

人工智慧

分類

歷史

可解釋性

圖神經網路

大數據

應用

強化學習

數據

數據科學

無監督學習

機器人

機器學習

機器翻譯

模型

深度學習

特徵

特徵工程

監督學習

神經網路

演算法

聚類

自動駕駛

自然語言處理

評估

語音合成

關注我們的公眾號:easyai-tech

© 2019 easyAI. All Rights Reserved.

tokenize — Tokenizer for Python source — Python 3.12.2 documentation

tokenize — Tokenizer for Python source — Python 3.12.2 documentation

Theme

Auto

Light

Dark

Table of Contents

tokenize — Tokenizer for Python source

Tokenizing Input

Command-Line Usage

Examples

Previous topic

keyword — Testing for Python keywords

Next topic

tabnanny — Detection of ambiguous indentation

This Page

Report a Bug

Show Source

Navigation

index

modules |

next |

previous |

Python »

3.12.2 Documentation »

The Python Standard Library »

Python Language Services »

tokenize — Tokenizer for Python source

|

Theme

Auto

Light

Dark

|

tokenize — Tokenizer for Python source¶

Source code: Lib/tokenize.py

The tokenize module provides a lexical scanner for Python source code,

implemented in Python. The scanner in this module returns comments as tokens

as well, making it useful for implementing “pretty-printers”, including

colorizers for on-screen displays.

To simplify token stream handling, all operator and

delimiter tokens and Ellipsis are returned using

the generic OP token type. The exact

type can be determined by checking the exact_type property on the

named tuple returned from tokenize.tokenize().

Warning

Note that the functions in this module are only designed to parse

syntactically valid Python code (code that does not raise when parsed

using ast.parse()). The behavior of the functions in this module is

undefined when providing invalid Python code and it can change at any

point.

Tokenizing Input¶

The primary entry point is a generator:

tokenize.tokenize(readline)¶

The tokenize() generator requires one argument, readline, which

must be a callable object which provides the same interface as the

io.IOBase.readline() method of file objects. Each call to the

function should return one line of input as bytes.

The generator produces 5-tuples with these members: the token type; the

token string; a 2-tuple (srow, scol) of ints specifying the row and

column where the token begins in the source; a 2-tuple (erow, ecol) of

ints specifying the row and column where the token ends in the source; and

the line on which the token was found. The line passed (the last tuple item)

is the physical line. The 5 tuple is returned as a named tuple

with the field names:

type string start end line.

The returned named tuple has an additional property named

exact_type that contains the exact operator type for

OP tokens. For all other token types exact_type

equals the named tuple type field.

Changed in version 3.1: Added support for named tuples.

Changed in version 3.3: Added support for exact_type.

tokenize() determines the source encoding of the file by looking for a

UTF-8 BOM or encoding cookie, according to PEP 263.

tokenize.generate_tokens(readline)¶

Tokenize a source reading unicode strings instead of bytes.

Like tokenize(), the readline argument is a callable returning

a single line of input. However, generate_tokens() expects readline

to return a str object rather than bytes.

The result is an iterator yielding named tuples, exactly like

tokenize(). It does not yield an ENCODING token.

All constants from the token module are also exported from

tokenize.

Another function is provided to reverse the tokenization process. This is

useful for creating tools that tokenize a script, modify the token stream, and

write back the modified script.

tokenize.untokenize(iterable)¶

Converts tokens back into Python source code. The iterable must return

sequences with at least two elements, the token type and the token string.

Any additional sequence elements are ignored.

The reconstructed script is returned as a single string. The result is

guaranteed to tokenize back to match the input so that the conversion is

lossless and round-trips are assured. The guarantee applies only to the

token type and token string as the spacing between tokens (column

positions) may change.

It returns bytes, encoded using the ENCODING token, which

is the first token sequence output by tokenize(). If there is no

encoding token in the input, it returns a str instead.

tokenize() needs to detect the encoding of source files it tokenizes. The

function it uses to do this is available:

tokenize.detect_encoding(readline)¶

The detect_encoding() function is used to detect the encoding that

should be used to decode a Python source file. It requires one argument,

readline, in the same way as the tokenize() generator.

It will call readline a maximum of twice, and return the encoding used

(as a string) and a list of any lines (not decoded from bytes) it has read

in.

It detects the encoding from the presence of a UTF-8 BOM or an encoding

cookie as specified in PEP 263. If both a BOM and a cookie are present,

but disagree, a SyntaxError will be raised. Note that if the BOM is found,

'utf-8-sig' will be returned as an encoding.

If no encoding is specified, then the default of 'utf-8' will be

returned.

Use open() to open Python source files: it uses

detect_encoding() to detect the file encoding.

tokenize.open(filename)¶

Open a file in read only mode using the encoding detected by

detect_encoding().

New in version 3.2.

exception tokenize.TokenError¶

Raised when either a docstring or expression that may be split over several

lines is not completed anywhere in the file, for example:

"""Beginning of

docstring

or:

[1,

2,

3

Command-Line Usage¶

New in version 3.3.

The tokenize module can be executed as a script from the command line.

It is as simple as:

python -m tokenize [-e] [filename.py]

The following options are accepted:

-h, --help¶

show this help message and exit

-e, --exact¶

display token names using the exact type

If filename.py is specified its contents are tokenized to stdout.

Otherwise, tokenization is performed on stdin.

Examples¶

Example of a script rewriter that transforms float literals into Decimal

objects:

from tokenize import tokenize, untokenize, NUMBER, STRING, NAME, OP

from io import BytesIO

def decistmt(s):

"""Substitute Decimals for floats in a string of statements.

>>> from decimal import Decimal

>>> s = 'print(+21.3e-5*-.1234/81.7)'

>>> decistmt(s)

"print (+Decimal ('21.3e-5')*-Decimal ('.1234')/Decimal ('81.7'))"

The format of the exponent is inherited from the platform C library.

Known cases are "e-007" (Windows) and "e-07" (not Windows). Since

we're only showing 12 digits, and the 13th isn't close to 5, the

rest of the output should be platform-independent.

>>> exec(s) #doctest: +ELLIPSIS

-3.21716034272e-0...7

Output from calculations with Decimal should be identical across all

platforms.

>>> exec(decistmt(s))

-3.217160342717258261933904529E-7

"""

result = []

g = tokenize(BytesIO(s.encode('utf-8')).readline) # tokenize the string

for toknum, tokval, _, _, _ in g:

if toknum == NUMBER and '.' in tokval: # replace NUMBER tokens

result.extend([

(NAME, 'Decimal'),

(OP, '('),

(STRING, repr(tokval)),

(OP, ')')

])

else:

result.append((toknum, tokval))

return untokenize(result).decode('utf-8')

Example of tokenizing from the command line. The script:

def say_hello():

print("Hello, World!")

say_hello()

will be tokenized to the following output where the first column is the range

of the line/column coordinates where the token is found, the second column is

the name of the token, and the final column is the value of the token (if any)

$ python -m tokenize hello.py

0,0-0,0: ENCODING 'utf-8'

1,0-1,3: NAME 'def'

1,4-1,13: NAME 'say_hello'

1,13-1,14: OP '('

1,14-1,15: OP ')'

1,15-1,16: OP ':'

1,16-1,17: NEWLINE '\n'

2,0-2,4: INDENT ' '

2,4-2,9: NAME 'print'

2,9-2,10: OP '('

2,10-2,25: STRING '"Hello, World!"'

2,25-2,26: OP ')'

2,26-2,27: NEWLINE '\n'

3,0-3,1: NL '\n'

4,0-4,0: DEDENT ''

4,0-4,9: NAME 'say_hello'

4,9-4,10: OP '('

4,10-4,11: OP ')'

4,11-4,12: NEWLINE '\n'

5,0-5,0: ENDMARKER ''

The exact token type names can be displayed using the -e option:

$ python -m tokenize -e hello.py

0,0-0,0: ENCODING 'utf-8'

1,0-1,3: NAME 'def'

1,4-1,13: NAME 'say_hello'

1,13-1,14: LPAR '('

1,14-1,15: RPAR ')'

1,15-1,16: COLON ':'

1,16-1,17: NEWLINE '\n'

2,0-2,4: INDENT ' '

2,4-2,9: NAME 'print'

2,9-2,10: LPAR '('

2,10-2,25: STRING '"Hello, World!"'

2,25-2,26: RPAR ')'

2,26-2,27: NEWLINE '\n'

3,0-3,1: NL '\n'

4,0-4,0: DEDENT ''

4,0-4,9: NAME 'say_hello'

4,9-4,10: LPAR '('

4,10-4,11: RPAR ')'

4,11-4,12: NEWLINE '\n'

5,0-5,0: ENDMARKER ''

Example of tokenizing a file programmatically, reading unicode

strings instead of bytes with generate_tokens():

import tokenize

with tokenize.open('hello.py') as f:

tokens = tokenize.generate_tokens(f.readline)

for token in tokens:

print(token)

Or reading bytes directly with tokenize():

import tokenize

with open('hello.py', 'rb') as f:

tokens = tokenize.tokenize(f.readline)

for token in tokens:

print(token)

Table of Contents

tokenize — Tokenizer for Python source

Tokenizing Input

Command-Line Usage

Examples

Previous topic

keyword — Testing for Python keywords

Next topic

tabnanny — Detection of ambiguous indentation

This Page

Report a Bug

Show Source

«

Navigation

index

modules |

next |

previous |

Python »

3.12.2 Documentation »

The Python Standard Library »

Python Language Services »

tokenize — Tokenizer for Python source

|

Theme

Auto

Light

Dark

|

© Copyright 2001-2024, Python Software Foundation.

This page is licensed under the Python Software Foundation License Version 2.

Examples, recipes, and other code in the documentation are additionally licensed under the Zero Clause BSD License.

See History and License for more information.

The Python Software Foundation is a non-profit corporation.

Please donate.

Last updated on Mar 06, 2024 (20:51 UTC).

Found a bug?

Created using Sphinx 7.2.6.

NLP BERT GPT等模型中 tokenizer 类别说明详解-腾讯云开发者社区-腾讯云

BERT GPT等模型中 tokenizer 类别说明详解-腾讯云开发者社区-腾讯云大鹅NLP BERT GPT等模型中 tokenizer 类别说明详解原创关注作者腾讯云开发者社区文档建议反馈控制台首页学习活动专区工具TVP最新优惠活动文章/答案/技术大牛搜索搜索关闭发布登录/注册首页学习活动专区工具TVP最新优惠活动返回腾讯云官网大鹅首页学习活动专区工具TVP最新优惠活动返回腾讯云官网社区首页 >专栏 >NLP BERT GPT等模型中 tokenizer 类别说明详解NLP BERT GPT等模型中 tokenizer 类别说明详解原创大鹅关注修改于 2021-08-20 21:03:4514.5K0修改于 2021-08-20 21:03:45举报文章被收录于专栏:大鹅专栏:大数据到机器学习大鹅专栏:大数据到机器学习1. 背景与基础在使用GPT BERT模型输入词语常常会先进行tokenize ,tokenize具体目标与粒度是什么呢?tokenize也有许多类别及优缺点,这篇文章总结一下各个方法及实际案例。tokenize的目标是把输入的文本流,切分成一个个子串,每个子串相对有完整的语义,便于学习embedding表达和后续模型的使用。tokenize有三种粒度:word/subword/charword词,是最自然的语言单元。对于英文等自然语言来说,存在着天然的分隔符,比如说空格,或者是一些标点符号,对词的切分相对容易。但是对于一些东亚文字包括中文来说,就需要某种分词算法才行。顺便说一下,Tokenizers库中,基于规则切分部分,采用了spaCy和Moses两个库。如果基于词来做词汇表,由于长尾现象的存在,这个词汇表可能会超大。像Transformer XL库就用到了一个26.7万个单词的词汇表。这需要极大的embedding matrix才能存得下。embedding matrix是用于查找取用token的embedding vector的。这对于内存或者显存都是极大的挑战。常规的词汇表,一般大小不超过5万。char/字符, 也就是说,我们的词汇表里只有最基本的字符。而一般来讲,字符的数量是少量有限的。这样做的问题是,由于字符数量太小,我们在为每个字符学习嵌入向量的时候,每个向量就容纳了太多的语义在内,学习起来非常困难。subword子词级,它介于字符和单词之间。比如说Transformers可能会被分成Transform和ers两个部分。这个方案平衡了词汇量和语义独立性,是相对较优的方案。它的处理原则是,常用词应该保持原状,生僻词应该拆分成子词以共享token压缩空间。2. 常用tokenize算法最常用的三种tokenize算法:BPE(Byte-Pair Encoding),WordPiece和SentencePiece2.1 Byte-Pair Encoding (BPE) / Byte-level BPE2.1.1 BPE首先,它依赖于一种预分词器pretokenizer来完成初步的切分。pretokenizer可以是简单基于空格的,也可以是基于规则的;分词之后,统计每个词出现的频次供后续计算使用。例如,我们统计到了5个词的词频("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)建立基础词汇表,包括所有的字符,即:["b", "g", "h", "n", "p", "s", "u"]根据规则,我们分别考察2-gram,3-gram的基本字符组合,把高频的ngram组合依次加入到词汇表当中,直到词汇表达到预定大小停止。比如,我们计算出ug/un/hug三种组合出现频次分别为20,16和15,加入到词汇表中。最终词汇表的大小 = 基础字符词汇表大小 + 合并串的数量,比如像GPT,它的词汇表大小 40478 = 478(基础字符) + 40000(merges)。添加完后,我们词汇表变成:["b", "g", "h", "n", "p", "s", "u", "ug", "un", "hug"]实际使用中,如果遇到未知字符用代表。2.1.2 Byte-level BPEBPE的一个问题是,如果遇到了unicode,基本字符集可能会很大。一种处理方法是我们以一个字节为一种“字符”,不管实际字符集用了几个字节来表示一个字符。这样的话,基础字符集的大小就锁定在了256。例如,像GPT-2的词汇表大小为50257 = 256 + + 50000 mergers,是句子结尾的特殊标记。2.2 WordPieceWordPiece,从名字好理解,它是一种子词粒度的tokenize算法subword tokenization algorithm,很多著名的Transformers模型,比如BERT/DistilBERT/Electra都使用了它。它的原理非常接近BPE,不同之处在于,它在做合并的时候,并不是每次找最高频的组合,而是找能够最大化训练集数据似然的merge,即它每次合并的两个字符串A和B,应该具有最大的 \frac{P(AB)}{P(A)P(B)} 值。合并AB之后,所有原来切成A+B两个tokens的就只保留AB一个token,整个训练集上最大似然变化量与 \frac{P(AB)}{P(A)P(B)} 成正比。2.3 Unigram与BPE或者WordPiece不同,Unigram的算法思想是从一个巨大的词汇表出发,再逐渐删除trim down其中的词汇,直到size满足预定义。初始的词汇表可以采用所有预分词器分出来的词,再加上所有高频的子串。每次从词汇表中删除词汇的原则是使预定义的损失最小。训练时,计算loss的公式为: Loss=-\sum_{i=1}^Nlog(\sum_{x\in S(x_i)}p(x)) 假设训练文档中的所有词分别为 x_1;x_2...x_N ,而每个词tokenize的方法是一个集合 S(x_i) 。当一个词汇表确定时,每个词tokenize的方法集合 S(x_i) 就是确定的,而每种方法对应着一个概率p(x)。如果从词汇表中删除部分词,则某些词的tokenize的种类集合就会变少,log(*)中的求和项就会减少,从而增加整体loss。Unigram算法每次会从词汇表中挑出使得loss增长最小的10%~20%的词汇来删除。一般Unigram算法会与SentencePiece算法连用。2.4 SentencePieceSentencePiece,顾名思义,它是把一个句子看作一个整体,再拆成片段,而没有保留天然的词语的概念。一般地,它把空格space也当作一种特殊字符来处理,再用BPE或者Unigram算法来构造词汇表。比如,XLNetTokenizer就采用了_来代替空格,解码的时候会再用空格替换回来。目前,Tokenizers库中,所有使用了SentencePiece的都是与Unigram算法联合使用的,比如ALBERT、XLNet、Marian和T5.3. 切分实例与代码分析3.1 BertTokenizer / WordPiece先试一个BertTokenizer,它基于WordPiece算法,base版本的词汇表大小为21128.from transformers import BertTokenizer

tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')

tokens = t.encode(...).tokens复制切分效果为:Tokenizer:

Text: The problems of your past are your business. The problems of your future are my privilege.

Tokens: [UNK],pro,##ble,##ms,of,your,pa,##st,are,your,business,.,[UNK],pro,##ble,##ms,of,your,future,are,my,pr,##i,##vi,##le,##ge,.

Text: 你的过去我不愿过问,那是你的事情。你的未来我希望参与,这是我的荣幸。

Tokens: 你,的,过,去,我,不,愿,过,问,,,那,是,你,的,事,情,。,你,的,未,来,我,希,望,参,与,,,这,是,我,的,荣,幸,。

Text: Don’t make the user feel stupid.

Tokens: [UNK],[UNK],t,make,the,user,feel,st,##up,##id,.

Text: 中国语言研究院正式宣布,“笔画最多的汉字”的桂冠属于“龖(dá)”字!

Tokens: 中,国,语,言,研,究,院,正,式,宣,布,,,[UNK],笔,画,最,多,的,汉,字,[UNK],的,桂,冠,属,于,[UNK],[UNK],(,[UNK],),[UNK],字,!其中,BertTokenizer中,用##符号表示非开头的子词,比如第1句中的problems被拆分成了三部分,pro/##ble/##ms;标点符号、生僻字等未出现的token被[UNK]代替中文基本拆分成了字的形式,并没有看到多字词的形式分词流程与代码分析如下:BertTokenizer类关系如下在代码中查看主要做了两件事情:根据参数控制来对输入文本做基础分词(basic_tokenizer)对于切分出来的单个词,再切分(wordpiece_tokenizer)basic_tokenizer 是把句子切分成词,仍然可以对着代码看一下:特别要注意的在 401 行:如果 tokenize_chinese_chars 参数为 True,那么所有的中文词都会被切成字符级别!!!参数传来的 never_split 并不会让这些中文词不被切分。wordpiece_tokenizer 则是将词切成字符级别,例如 doing->['do', '###ing']。这里的做法就是把一个词送入 BERT 中做最大匹配(类似于 Jieba 分词的正向最大匹配算法),如果前面已经有匹配,则后面的词都会加 ’##‘。而中文,因为已经在上一步被切分成字符级别,所以不会有任何改变。3.2 T5Tokenizer / SentencePieceT5模型是基于SentencePiece的,我们看看它的切分效果。我用的这个版本词汇表大小是250112。Tokenizer:

Text: The problems of your past are your business. The problems of your future are my privilege.

Tokens: ▁The,▁problems,▁of,▁your,▁past,▁are,▁your,▁business,.,▁The,▁problems,▁of,▁your,▁future,▁are,▁my,▁,privilege,.

Text: 你的过去我不愿过问,那是你的事情。你的未来我希望参与,这是我的荣幸。

Tokens: ▁,你的,过去,我不,愿,过,问,,,那是,你,的事情,。,你的,未来,我,希望,参与,,,这是,我的,荣,幸,。

Text: Don’t make the user feel stupid.

Tokens: ▁Don,’,t,▁make,▁the,▁user,▁feel,▁stupid,.

Text: 中国语言研究院正式宣布,“笔画最多的汉字”的桂冠属于“龖(dá)”字!

Tokens: ▁,中国,语言,研究院,正式,宣布,,“,笔,画,最多,的,汉,字,”,的,桂,冠,属于,“,<0xE9>,<0xBE>,<0x96>,(,dá,),”,字,!其中,最明显的,可以看到下划线被引入,代替了空格和句子开头特殊符号中文可以看到一些多字词,比如“未来”,“研究院”等,但有些词其实不符合一般的分词习惯,比如“的事情”、“我不”等等生僻字龖被拆成了三个基础字节形式的tokenRefhttps://zhuanlan.zhihu.com/p/371300063https://blog.csdn.net/iterate7/article/details/108959082https://zhuanlan.zhihu.com/p/268515387https://static.googleusercontent.com/media/research.google.com/ja//pubs/archive/37842.pdfstatic.googleusercontent.com/media/research.google.com/ja//pubs/archive/37842.pdfhttps://arxiv.org/pdf/1804.10959.pdf​arxiv.org/pdf/1804.10959.pdf原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。如有侵权,请联系 cloudcommunity@tencent.com 删除。机器学习机器学习平台深度学习中文分词NLP 服务原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。如有侵权,请联系 cloudcommunity@tencent.com 删除。机器学习机器学习平台深度学习中文分词NLP 服务#BERT#NLP#GPT#tokenize#ML评论登录后参与评论0 条评论热度最新登录 后参与评论推荐阅读LV.关注文章0获赞0目录2. 常用tokenize算法2.1 Byte-Pair Encoding (BPE) / Byte-level BPE2.1.1 BPE2.1.2 Byte-level BPE2.2 WordPiece2.3 Unigram2.4 SentencePiece3. 切分实例与代码分析3.1 BertTokenizer / WordPiece3.2 T5Tokenizer / SentencePieceRef相关产品与服务NLP 服务NLP 服务(Natural Language Process,NLP)深度整合了腾讯内部的 NLP 技术,提供多项智能文本处理和文本生成能力,包括词法分析、相似词召回、词相似度、句子相似度、文本润色、句子纠错、文本补全、句子生成等。满足各行业的文本智能需求。产品介绍产品文档2024新春采购节领券社区专栏文章阅读清单互动问答技术沙龙技术视频团队主页腾讯云TI平台活动自媒体分享计划邀请作者入驻自荐上首页技术竞赛资源技术周刊社区标签开发者手册开发者实验室关于社区规范免责声明联系我们友情链接腾讯云开发者扫码关注腾讯云开发者领取腾讯云代金券热门产品域名注册云服务器区块链服务消息队列网络加速云数据库域名解析云存储视频直播热门推荐人脸识别腾讯会议企业云CDN加速视频通话图像分析MySQL 数据库SSL 证书语音识别更多推荐数据安全负载均衡短信文字识别云点播商标注册小程序开发网站监控数据迁移Copyright © 2013 - 2024 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有 深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569腾讯云计算(北京)有限责任公司 京ICP证150476号 |  京ICP备11018762号 | 京公网安备号11010802020287问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档Copyright © 2013 - 2024 Tencent Cloud.All Rights Reserved. 腾讯云 版权所有登录 后参与评论00

LLM训练细节整理 - Tokenizer的构造(1) - BPE算法 - 知乎

LLM训练细节整理 - Tokenizer的构造(1) - BPE算法 - 知乎首发于日拱一卒切换模式写文章登录/注册LLM训练细节整理 - Tokenizer的构造(1) - BPE算法队长​为什么要讨论Tokenizer在训练大型自然语言处理模型时,分词(tokenization)是非常重要的一步。无论是从零训练一个大模型,还是在业界开源的大模型上继续进行微调,可能很多人会忽视Tokenizer在其中起到的作用。实际上大多数著名的LLM模型论文,都会花一些篇幅来说明他们的Tokenizer是如何构造的。就拿GPT2来说,其2.2节Input Representation,就讲述了其设计的Tokenizer思考。还有像金融垂直领域的大模型BloomBergGPT,对Tokenizer的也做了较为精细的设计,这是因为垂直领域的语料,本身就有较多特殊字符,或者特别的术语,需要专门捕捉设计以提高模型的准确性。这系列文章将从基本的Tokenizer分类出发,然后介绍一些经典论文设计Tokenizer的思想及代码实现,希望能通过这些思想,深入了解业界前沿在训练大语言模型前,怎么进行数据预处理。Tokenizer分类首先,我们先来看看传统的基于空格分词的方法有哪些问题:真实使用场景可能会输入训练文本中不存在的词,即OOV(Out-Of-Vocabulary)问题。处理OOV问题的一种方法是将所有未知单词都替换为一个特殊的OOV标记,然后将其当做一个单独的单词来处理。但是这种方法可能会导致模型的性能下降,因为OOV标记无法提供关于未知单词的任何语义信息。单一语种的词汇数量大概在几十万量级,如果穷举所有的词,会造成词表过大的问题。模型在预测中,需要搜索的空间极其庞大。如果要简单的解决以上两个问题,可以直接以字符为单位或者使用Unicode 字符。但是这种方法会显著增加输入输出序列的长度,并且因为多个字符才能拼接成一个有真实含义的词,信息密度极大地被降低。为了解决这些问题,业界做了很多尝试,最终大部分语言模型在构建tokenizer时,都是以通过subword这种方式来处理token。首先,subword的颗粒度在词与字符之间,多个单词共享subword,尽量解决了OOV问题。对比穷举词表,subword方式也尽可能地降低了词表的大小。同时,subword大多都是词缀方式,词缀本身也包含一定的真实语义信息,保留了信息的密度。下面介绍几种主流的subword tokenizer。Byte Pair Encoding(BPE)[1]BPE实际上是一种压缩算法,在原论文中,介绍了BPE:Byte Pair Encoding (BPE) (Gage, 1994) is a simple data compression technique that iteratively replaces the most frequent pair of bytes in a sequence with a single, unused byte. We adapt this algorithm for word segmentation. Instead of merging frequent pairs of bytes, we merge characters or character sequences.大意就是,Byte Pair Encoding(BPE)是一种简单的数据压缩技术,它通过迭代地将序列中最频繁的一对字节替换为一个未使用的字节来实现。我们将这种算法改编为用于词分割。与合并频繁的字节对不同,我们将合并字符或字符序列。定义听起来有点绕,论文中直接放了实现代码,通过代码可以了解其具体思路。import re, collections

def get_stats(vocab):

pairs = collections.defaultdict(int)

for word, freq in vocab.items():

symbols = word.split()

for i in range(len(symbols)-1):

pairs[symbols[i],symbols[i+1]] += freq

return pairs

def merge_vocab(pair, v_in):

v_out = {}

bigram = re.escape(' '.join(pair))

p = re.compile(r'(?

for word in v_in:

w_out = p.sub(''.join(pair), word)

v_out[w_out] = v_in[word]

return v_out

vocab = {'l o w ' : 5, 'l o w e r ' : 2,

'n e w e s t ':6, 'w i d e s t ':3}

num_merges = 10

for i in range(num_merges):

pairs = get_stats(vocab)

best = max(pairs, key=pairs.get)

vocab = merge_vocab(best, vocab)

print(best)其中,我们需要先从语料中统计出每个原始单词的词频,代码给了个示例,vocab变量。其中low在语料中出现了5次,lower2次,newest6次,widest3次。并且每个单词的末尾有特殊的结束标记“”让算法知道每个单词的结束位置。接着定义了合并的最大次数num_merges=10,主要的合并过程会执行10次,循环中,首先执行get_stats函数,词频字典作为参数传入。我们可以看到,在vocab中,每个单词作为key,实际上也用空格分为了字符单位,def get_stats(vocab):

pairs = collections.defaultdict(int)

for word, freq in vocab.items():

symbols = word.split()

for i in range(len(symbols)-1):

pairs[symbols[i],symbols[i+1]] += freq

return pairs在这个函数中,首先要遍历传入的dictionary类型的vocab参数每一个key,value值,即词本身word和对应的频率freq。这个遍历中,先对word进行分割,比如low会分割成["l","o","w",""]。然后再对上面这个分割后形成的列表进行遍历,并计算相邻的两个字符出现的频率。最后返回每一对字符对应的频率。其执行一次,返回的结果如下:defaultdict(int,

{('l', 'o'): 7,

('o', 'w'): 7,

('w', ''): 5,

('w', 'e'): 8,

('e', 'r'): 2,

('r', ''): 2,

('n', 'e'): 6,

('e', 'w'): 6,

('e', 's'): 9,

('s', 't'): 9,

('t', ''): 9,

('w', 'i'): 3,

('i', 'd'): 3,

('d', 'e'): 3})接着,获取出现最多的字符对,具体来说,max(pairs, key=pairs.get)中的max()函数会遍历字典pairs中的所有 key,然后根据key=pairs.get参数指定的函数(即获取每个 key 对应的 value 的函数)来比较这些 key 的大小。最终,它会返回出现次数最多的 key,即在字典pairs中 value 值最大的 key。然后使用merge_vocab函数更新vocab,def merge_vocab(pair, v_in):

v_out = {}

bigram = re.escape(' '.join(pair))

p = re.compile(r'(?

for word in v_in:

w_out = p.sub(''.join(pair), word)

v_out[w_out] = v_in[word]

return v_out

这个函数接受两个参数,一个是刚刚统计出来的出现最多的字符对,另一个是原有的vocab变量。这个函数主要做的事就是更新vocab,首先它构造了一个正则表达式,比如之前传进来的词频最高的是('e', 's'),所以就会生成(?会被换成n e w es t<\w>。整个vocab经过一次迭代后,就变成了:{'l o w ': 5, 'l o w e r ': 2, 'n e w es t ': 6, 'w i d es t ': 3}可以看到,词表中实际上是多了es这个词,但是随着迭代次数的增加,词表中词的数目会先增加,再减少。最后迭代了十次,词表变成:{'low': 5, 'low e r ': 2, 'newest': 6, 'wi d est': 3}对比一开始的词表,token的数量降低了两个。llowolowwerernewestnwisdtestid当语料库更大时,这个压缩效果会更加明显。BPE Tokenizer在LLM的实际应用在现有的主流语言大模型中,使用BPE算法作为Tokenizer基础框架的有GPT2[2]、RoBERTa[3]等。RoBERTa对BPE的具体实现实际上跟GPT2一样,所以我们直接看看GPT2的实现代码即可。GPT2我们先通过相应的工具Tokenizer Viewer来大概看看GPT2的Token。GPT2模型词表中Token的数量GPT2的词表有50000多个,这个数量其实不算多,因为GPT2是基于8 million documents for a total of 40 GB of text,也就是800万份文档上训练的,而且可比较完美的进行一些翻译任务。如果是多语言穷举词汇的词表构建法,估计词表词汇数目应该在数十万的量级。我们可以看到,具体的token确实包含了很多subword。GPT token示例之前对BPE算法举的例子,是将文本,切分成字符粒度,再进行subword提取。而GPT2,是在字节的基础上、使用BPE算法对各种序列的字节表示进行频次统计,压缩。由于每个字节只有256种可能的取值,因此只需要一个大小为256的基本词汇表,就可以覆盖所有可能的字节符号。具体的代码可以参考其源码:这里有一个细节,文中举了个例子,就是将文本转换为Byte进行BPE后,词表中出现了类似dog. dog! dog? 这种形式的跨语言的词。这种跨类别的合并是不好的,需要避免。总结这种直接对字节进行抽取的词表有什么好处呢?完全避免了OOV问题,所有字符都是由Byte组成。模型不需要进行数据的Preprocessing了,直接输入原文转Byte,再转成unicode即可。不过使用Byte来作为模型输入,还是会造成语义缺失的问题,毕竟之前具备完整语义的字或者词,被切割成了支离破碎的Byte。虽然经过BPE组合了一些常见的Byte序列,但是语义损失还是不能忽视。参考^Neural Machine Translation of Rare Words with Subword Units https://arxiv.org/pdf/1508.07909.pdf^Language Models are Unsupervised Multitask Learners https://github.com/openai/gpt-2/blob/master/src/encoder.py^RoBERTa: A Robustly Optimized BERT Pretraining Approach https://github.com/facebookresearch/fairseq/blob/main/examples/roberta/multiprocessing_bpe_encoder.py编辑于 2023-11-30 18:16・IP 属地广东LLMNLP大语言模型​赞同 25​​1 条评论​分享​喜欢​收藏​申请转载​文章被以下专栏收录日拱一卒日常学

TOKENIZE中文(简体)翻译:剑桥词典

TOKENIZE中文(简体)翻译:剑桥词典

词典

翻译

语法

同义词词典

+Plus

剑桥词典+Plus

Shop

剑桥词典+Plus

我的主页

+Plus 帮助

退出

剑桥词典+Plus

我的主页

+Plus 帮助

退出

登录

/

注册

中文 (简体)

查找

查找

英语-中文(简体)

tokenize 在英语-中文(简体)词典中的翻译

tokenizeverb [ T ] (UK usually tokenise) uk

Your browser doesn't support HTML5 audio

/ˈtəʊ.kən.aɪz/ us

Your browser doesn't support HTML5 audio

/ˈtoʊ.kən.aɪz/

tokenize verb [T]

(COMPUTING)

Add to word list

Add to word list

computing, language

  specialized to divide a series of characters (= letters, numbers, or other marks or signs used in writing or printing) into separate tokens (= units) that have a meaning

将文本分割成离散单元

The stream of characters in a natural language text must be tokenized (broken up into distinct meaningful units) before any language processing can happen.

在进行任何语言处理之前,自然语言文本中的字符流必须被分割成离散单元(被分割成不同的有意义的单元)。

computing

  specialized to replace a private piece of data with a token (= a different piece of data that represents the first one), in order to prevent private information being seen by someone who is not allowed to do so

(将含隐私信息的数据用另一信息,即令牌,替代,以此保护隐私)令牌化

When we receive the transaction securely, we tokenize it and send back a token to the merchant so they never have to see a card number for settlement.

当我们安全地收到交易后,我们会将其令牌化,并将令牌发回给商家,这样他们就不必看到结算时的卡号。

更多范例减少例句 First, your script must be tokenised and converted into meaningful units.Use the huge archive of previous customer searches and tokenize these into words.Tokenising a card transaction is considered safer as the actual card details are not shared.

tokenize verb [T]

(PERSON)

to do something that seems to support or help a group of people who are treated unfairly in society, such as giving a member of that group an important or public position, but that does not make changes that would help that group of people in a lasting way

做门面功夫

He did not feel as if he had been tokenized while at college but had been held to the same standards as everyone else.

他没有觉得在大学的时候他被刻意优待用来做门面功夫,反而觉得对他的要求和标准同其他人是一样的。

相关词语

tokenism

更多范例减少例句Many employees feel that they are being tokenised in publicity materials, while their concerns about bias and inequality are not actually being addressed.How can you tell if you're being tokenized at your workplace?They listed 8 ways people of colour are tokenized in nonprofits.

相关词语

tokenization

(tokenize在剑桥英语-中文(简体)词典的翻译 © Cambridge University Press)

C1

tokenize的翻译

中文(繁体)

將文本分割成離散單元, (將含隱私信息的數據用另一信息,即令牌,替代,以此保護隱私)令牌化, 做門面功夫…

查看更多内容

需要一个翻译器吗?

获得快速、免费的翻译!

翻译器工具

tokenize的发音是什么?

在英语词典中查看 tokenize 的释义

浏览

token

tokenism

tokenistic

tokenization

tokenize

Tokyo

told

tolerable

tolerably

“每日一词”

veggie burger

UK

Your browser doesn't support HTML5 audio

/ˈvedʒ.i ˌbɜː.ɡər/

US

Your browser doesn't support HTML5 audio

/ˈvedʒ.i ˌbɝː.ɡɚ/

a type of food similar to a hamburger but made without meat, by pressing together small pieces of vegetables, seeds, etc. into a flat, round shape

关于这个

博客

Forget doing it or forget to do it? Avoiding common mistakes with verb patterns (2)

March 06, 2024

查看更多

新词

stochastic parrot

March 04, 2024

查看更多

已添加至 list

回到页面顶端

内容

英语-中文(简体)翻译

©剑桥大学出版社与评估2024

学习

学习

学习

新词

帮助

纸质书出版

Word of the Year 2021

Word of the Year 2022

Word of the Year 2023

开发

开发

开发

词典API

双击查看

搜索Widgets

执照数据

关于

关于

关于

无障碍阅读

剑桥英语教学

剑桥大学出版社与评估

授权管理

Cookies与隐私保护

语料库

使用条款

京ICP备14002226号-2

©剑桥大学出版社与评估2024

剑桥词典+Plus

我的主页

+Plus 帮助

退出

词典

定义

清晰解释自然的书面和口头英语

英语

学习词典

基础英式英语

基础美式英语

翻译

点击箭头改变翻译方向。

双语词典

英语-中文(简体)

Chinese (Simplified)–English

英语-中文(繁体)

Chinese (Traditional)–English

英语-荷兰语

荷兰语-英语

英语-法语

法语-英语

英语-德语

德语-英语

英语-印尼语

印尼语-英语

英语-意大利语

意大利语-英语

英语-日语

日语-英语

英语-挪威语

挪威语-英语

英语-波兰语

波兰语-英语

英语-葡萄牙语

葡萄牙语-英语

英语-西班牙语

西班牙语-英语

English–Swedish

Swedish–English

半双语词典

英语-阿拉伯语

英语-孟加拉语

英语-加泰罗尼亚语

英语-捷克语

英语-丹麦语

English–Gujarati

英语-印地语

英语-韩语

英语-马来语

英语-马拉地语

英语-俄语

English–Tamil

English–Telugu

英语-泰语

英语-土耳其语

英语-乌克兰语

English–Urdu

英语-越南语

翻译

语法

同义词词典

Pronunciation

剑桥词典+Plus

Shop

剑桥词典+Plus

我的主页

+Plus 帮助

退出

登录 /

注册

中文 (简体)  

Change

English (UK)

English (US)

Español

Русский

Português

Deutsch

Français

Italiano

中文 (简体)

正體中文 (繁體)

Polski

한국어

Türkçe

日本語

Tiếng Việt

हिंदी

தமிழ்

తెలుగు

关注我们

选择一本词典

最近的词和建议

定义

清晰解释自然的书面和口头英语

英语

学习词典

基础英式英语

基础美式英语

语法与同义词词典

对自然书面和口头英语用法的解释

英语语法

同义词词典

Pronunciation

British and American pronunciations with audio

English Pronunciation

翻译

点击箭头改变翻译方向。

双语词典

英语-中文(简体)

Chinese (Simplified)–English

英语-中文(繁体)

Chinese (Traditional)–English

英语-荷兰语

荷兰语-英语

英语-法语

法语-英语

英语-德语

德语-英语

英语-印尼语

印尼语-英语

英语-意大利语

意大利语-英语

英语-日语

日语-英语

英语-挪威语

挪威语-英语

英语-波兰语

波兰语-英语

英语-葡萄牙语

葡萄牙语-英语

英语-西班牙语

西班牙语-英语

English–Swedish

Swedish–English

半双语词典

英语-阿拉伯语

英语-孟加拉语

英语-加泰罗尼亚语

英语-捷克语

英语-丹麦语

English–Gujarati

英语-印地语

英语-韩语

英语-马来语

英语-马拉地语

英语-俄语

English–Tamil

English–Telugu

英语-泰语

英语-土耳其语

英语-乌克兰语

English–Urdu

英语-越南语

词典+Plus

词汇表

选择语言

中文 (简体)  

English (UK)

English (US)

Español

Русский

Português

Deutsch

Français

Italiano

正體中文 (繁體)

Polski

한국어

Türkçe

日本語

Tiếng Việt

हिंदी

தமிழ்

తెలుగు

内容

英语-中文(简体) 

 

Verb 

tokenize (COMPUTING)

tokenize (PERSON)

Translations

语法

所有翻译

我的词汇表

把tokenize添加到下面的一个词汇表中,或者创建一个新词汇表。

更多词汇表

前往词汇表

对该例句有想法吗?

例句中的单词与输入词条不匹配。

该例句含有令人反感的内容。

取消

提交

例句中的单词与输入词条不匹配。

该例句含有令人反感的内容。

取消

提交

标记器(Tokenizer) - Hugging Face NLP Course

标记器(Tokenizer) - Hugging Face NLP Course

Hugging Face

Models

Datasets

Spaces

Posts

Docs

Solutions

Pricing

Log In

Sign Up

NLP Course documentation

标记器(Tokenizer)

NLP Course

View all resourcesAudio CourseDeep RL CourseNLP CourseOpen-Source AI Cookbook

Search documentation

ARBNDEENESFAFRGJHEHIIDITJAKOPTRUTHTRVIZH-CNZH-TW

0. 安装

1. Transformer 模型

2. 使用 Transformers

本章简介

管道的内部

模型

标记器(Tokenizer)

处理多个序列

把它们放在一起

基本用法完成!

章末小测验

3. 微调一个预训练模型

4. 分享你的模型和标记器

5. Datasets库

6. Tokenizers库

7. 主要的 NLP 任务

8. 如何寻求帮助

9. 构建并分享你的模型

new

课程活动

Join the Hugging Face community

and get access to the augmented documentation experience

Collaborate on models, datasets and Spaces

Faster examples with accelerated inference

Switch between documentation themes

Sign Up

to get started

Pytorch TensorFlow 标记器(Tokenizer) 标记器(Tokenizer)是 NLP 管道的核心组件之一。它们有一个目的:将文本转换为模型可以处理的数据。模型只能处理数字,因此标记器(Tokenizer)需要将我们的文本输入转换为数字数据。在本节中,我们将确切地探讨标记化管道中发生的事情。 在 NLP 任务中,通常处理的数据是原始文本。这是此类文本的示例 Copied Jim Henson was a puppeteer 但是,模型只能处理数字,因此我们需要找到一种将原始文本转换为数字的方法。这就是标记器(tokenizer)所做的,并且有很多方法可以解决这个问题。目标是找到最有意义的表示——即对模型最有意义的表示——并且如果可能的话,找到最小的表示。 让我们看一下标记化算法的一些示例,并尝试回答您可能对标记化提出的一些问题。 基于词的(Word-based) 想到的第一种标记器是基于词的(word-based).它通常很容易设置和使用,只需几条规则,并且通常会产生不错的结果。例如,在下图中,目标是将原始文本拆分为单词并为每个单词找到一个数字表示: 有多种方法可以拆分文本。例如,我们可以通过应用Python的split()函数,使用空格将文本标记为单词: Copied tokenized_text = "Jim Henson was a puppeteer".split()

print(tokenized_text) Copied ['Jim', 'Henson', 'was', 'a', 'puppeteer'] 还有一些单词标记器的变体,它们具有额外的标点符号规则。使用这种标记器,我们最终可以得到一些非常大的“词汇表”,其中词汇表由我们在语料库中拥有的独立标记的总数定义。 每个单词都分配了一个 ID,从 0 开始一直到词汇表的大小。该模型使用这些 ID 来识别每个单词。 如果我们想用基于单词的标记器(tokenizer)完全覆盖一种语言,我们需要为语言中的每个单词都有一个标识符,这将生成大量的标记。例如,英语中有超过 500,000 个单词,因此要构建从每个单词到输入 ID 的映射,我们需要跟踪这么多 ID。此外,像“dog”这样的词与“dogs”这样的词的表示方式不同,模型最初无法知道“dog”和“dogs”是相似的:它会将这两个词识别为不相关。这同样适用于其他相似的词,例如“run”和“running”,模型最初不会认为它们是相似的。 最后,我们需要一个自定义标记(token)来表示不在我们词汇表中的单词。这被称为“未知”标记(token),通常表示为“[UNK]”或”“。如果你看到标记器产生了很多这样的标记,这通常是一个不好的迹象,因为它无法检索到一个词的合理表示,并且你会在这个过程中丢失信息。制作词汇表时的目标是以这样一种方式进行,即标记器将尽可能少的单词标记为未知标记。 减少未知标记数量的一种方法是使用更深一层的标记器(tokenizer),即基于字符的(character-based)标记器(tokenizer)。 基于字符(Character-based) 基于字符的标记器(tokenizer)将文本拆分为字符,而不是单词。这有两个主要好处: 词汇量要小得多。 词汇外(未知)标记(token)要少得多,因为每个单词都可以从字符构建。 但是这里也出现了一些关于空格和标点符号的问题: 这种方法也不是完美的。由于现在表示是基于字符而不是单词,因此人们可能会争辩说,从直觉上讲,它的意义不大:每个字符本身并没有多大意义,而单词就是这种情况。然而,这又因语言而异;例如,在中文中,每个字符比拉丁语言中的字符包含更多的信息。 另一件要考虑的事情是,我们的模型最终会处理大量的词符(token):虽然使用基于单词的标记器(tokenizer),单词只会是单个标记,但当转换为字符时,它很容易变成 10 个或更多的词符(token)。 为了两全其美,我们可以使用结合这两种方法的第三种技术:子词标记化(subword tokenization)。 子词标记化 子词分词算法依赖于这样一个原则,即不应将常用词拆分为更小的子词,而应将稀有词分解为有意义的子词。 例如,“annoyingly”可能被认为是一个罕见的词,可以分解为“annoying”和“ly”。这两者都可能作为独立的子词出现得更频繁,同时“annoyingly”的含义由“annoying”和“ly”的复合含义保持。 这是一个示例,展示了子词标记化算法如何标记序列“Let’s do tokenization!”: 这些子词最终提供了很多语义含义:例如,在上面的示例中,“tokenization”被拆分为“token”和“ization”,这两个具有语义意义同时节省空间的词符(token)(只需要两个标记(token)代表一个长词)。这使我们能够对较小的词汇表进行相对较好的覆盖,并且几乎没有未知的标记 这种方法在土耳其语等粘着型语言(agglutinative languages)中特别有用,您可以通过将子词串在一起来形成(几乎)任意长的复杂词。 还有更多! 不出所料,还有更多的技术。仅举几例: Byte-level BPE, 用于 GPT-2 WordPiece, 用于 BERT SentencePiece or Unigram, 用于多个多语言模型 您现在应该对标记器(tokenizers)的工作原理有足够的了解,以便开始使用 API。 加载和保存 加载和保存标记器(tokenizer)就像使用模型一样简单。实际上,它基于相同的两种方法: from_pretrained() 和 save_pretrained() 。这些方法将加载或保存标记器(tokenizer)使用的算法(有点像建筑学(architecture)的模型)以及它的词汇(有点像权重(weights)模型)。 加载使用与 BERT 相同的检查点训练的 BERT 标记器(tokenizer)与加载模型的方式相同,除了我们使用 BertTokenizer 类: Copied from transformers import BertTokenizer

tokenizer = BertTokenizer.from_pretrained("bert-base-cased") 如同 AutoModel,AutoTokenizer 类将根据检查点名称在库中获取正确的标记器(tokenizer)类,并且可以直接与任何检查点一起使用: Copied from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") 我们现在可以使用标记器(tokenizer),如上一节所示: Copied tokenizer("Using a Transformer network is simple") Copied {'input_ids': [101, 7993, 170, 11303, 1200, 2443, 1110, 3014, 102],

'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0],

'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]} 保存标记器(tokenizer)与保存模型相同: Copied tokenizer.save_pretrained("directory_on_my_computer") 我们在Chapter 3中将更多地谈论token_type_ids,稍后我们将解释 attention_mask 键。首先,让我们看看 input_ids 如何生成。为此,我们需要查看标记器(tokenizer)的中间方法。 编码 将文本翻译成数字被称为编码(encoding).编码分两步完成:标记化,然后转换为输入 ID。 正如我们所见,第一步是将文本拆分为单词(或单词的一部分、标点符号等),通常称为标记(token)。有多个规则可以管理该过程,这就是为什么我们需要使用模型名称来实例化标记器(tokenizer),以确保我们使用模型预训练时使用的相同规则。 第二步是将这些标记转换为数字,这样我们就可以用它们构建一个张量并将它们提供给模型。为此,标记器(tokenizer)有一个词汇(vocabulary),这是我们在实例化它时下载的部分 from_pretrained() 方法。同样,我们需要使用模型预训练时使用的相同词汇。 为了更好地理解这两个步骤,我们将分别探讨它们。请注意,我们将使用一些单独执行部分标记化管道的方法来向您展示这些步骤的中间结果,但实际上,您应该直接在您的输入上调用标记器(tokenizer)(如第 2 部分所示)。 标记化 标记化过程由标记器(tokenizer)的tokenize() 方法实现: Copied from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")

sequence = "Using a Transformer network is simple"

tokens = tokenizer.tokenize(sequence)

print(tokens) 此方法的输出是一个字符串列表或标记(token): Copied ['Using', 'a', 'transform', '##er', 'network', 'is', 'simple'] 这个标记器(tokenizer)是一个子词标记器(tokenizer):它对词进行拆分,直到获得可以用其词汇表表示的标记(token)。transformer 就是这种情况,它分为两个标记:transform 和 ##er。 从词符(token)到输入 ID 输入 ID 的转换由标记器(tokenizer)的convert_tokens_to_ids()方法实现: Copied ids = tokenizer.convert_tokens_to_ids(tokens)

print(ids) Copied [7993, 170, 11303, 1200, 2443, 1110, 3014] 这些输出一旦转换为适当的框架张量,就可以用作模型的输入,如本章前面所见。 ✏️ 试试看! 在我们在第 2 节中使用的输入句子(“I’ve been waiting for a HuggingFace course my whole life.”和“I hate this so much!”)复制最后两个步骤(标记化和转换为输入 ID)。检查您获得的输入 ID 是否与我们之前获得的相同! 解码 解码(Decoding) 正好相反:从词汇索引中,我们想要得到一个字符串。这可以通过 decode() 方法实现,如下: Copied decoded_string = tokenizer.decode([7993, 170, 11303, 1200, 2443, 1110, 3014])

print(decoded_string) Copied 'Using a Transformer network is simple' 请注意, decode 方法不仅将索引转换回标记(token),还将属于相同单词的标记(token)组合在一起以生成可读的句子。当我们使用预测新文本的模型(根据提示生成的文本,或序列到序列问题(如翻译或摘要))时,这种行为将非常有用。 到现在为止,您应该了解标记器(tokenizer)可以处理的原子操作:标记化、转换为 ID 以及将 ID 转换回字符串。然而,我们只是刮到了冰山一角。在下一节中,我们将采用我们的方法来克服它的限制,并看看如何克服它们。

←模型

处理多个序列→

标记器(Tokenizer)

基于词的(Word-based)

基于字符(Character-based)

子词标记化

还有更多!

加载和保存

编码

标记化

从词符(token)到输入 ID

解码