LLM学习笔记(13)分词器 tokenizer

embedded/2024/12/4 1:34:51/

由于神经网络模型不能直接处理文本,因此我们需要先将文本转换为数字,这个过程被称为编码 (Encoding),其包含两个步骤:

  1. 使用分词器 (tokenizer) 将文本按词、子词、字符切分为 tokens;
  2. 将所有的 token 映射到对应的 token ID。

分词策略

根据切分粒度的不同,分词策略可以分为以下几种:

按词切分 (Word-based)

特点

  • 以空格、标点符号为界,将文本分割为单词。
  • 每个单词被视为一个独立的 token。

优点:

  • 实现简单,分词器只需根据空格或标点分割。

缺点:

  • 对于形态变化的词(如 runrunning)无法识别它们之间的关系。
  • 分词表会很大,可能导致内存占用过多。
  • 遇到分词表中没有的词(Out-Of-Vocabulary,OOV),分词器会用特殊的 [UNK] token 表示,影响模型效果。

例子

直接利用 Python 的 split() 函数按空格进行分词,其默认分隔符为空格:

tokenized_text = "Jim Henson was a puppeteer".split()
print(tokenized_text)

结果:

['Jim', 'Henson', 'was', 'a', 'puppeteer']

问题:词形变化和未知词

  • 词形变化问题:
    • 示例:dogdogsrunrunning,被分词器视为不同的 token,无法识别它们的词根关系。
  • 未知词问题:
    • 如果单词不在词汇表中(Out-Of-Vocabulary, OOV),分词器会用 [UNK](Unknown Token)表示。
    • 问题:
      • [UNK] 会丢失单词原始的语义信息。
      • 如果分词策略不够好,句子中会有大量 [UNK]

词表(Vocabulary)

什么是词表?

词表: 一个映射字典,将 token 映射到数字 ID(从 0 开始)。

  • 示例:

    {'Jim': 100, 'Henson': 101, 'was': 102, 'a': 103, 'puppeteer': 104}

词表的作用

  • 将 token 转换为对应的数字 ID。
  • 神经网络只能处理数字,不能直接理解字符串。

遇到 OOV 的处理

  • 如果分词结果中出现词表中没有的单词(如 puppeteering),分词器会用 [UNK] 替代。

按字符切分 (Character-based)

这种策略把文本切分为字符(字母)而不是词语,这样就只会产生一个非常小的词表,并且很少会出现词表外的 tokens。

可以使用 Python 的内置函数 list() 将输入字符串转换为字符列表。即

但是从直觉上来看,字符本身并没有太大的意义,因此将文本切分为字符之后就会变得不容易理解。这也与语言有关,例如中文字符会比拉丁字符包含更多的信息,相对影响较小。此外,这种方式切分出的 tokens 会很多,例如一个由 10 个字符组成的单词就会输出 10 个 tokens,而实际上它们只是一个词。

因此现在广泛采用的是一种同时结合了按词切分和按字符切分的方式——按子词切分 (Subword tokenization)。

按子词切分(Subword Tokenization)

子词切分是一种分词策略,能够将输入文本分解为更小的单元(子词或子字符串),其主要特点如下:

核心概念

  • 高频单词直接保留,例如单词 do 不会被切分。

  • 低频单词会被分解为多个子词(subword)。

  • 示例:tokenization 被分解为 tokenization

    • 子词带有特殊标记(如 <w>##),表示这是子词的一部分。

优点

1. 解决 OOV(Out-of-Vocabulary)问题:

  • 即使一个单词在词表中不存在,也可以通过子词组合得到。
  • 例如,ization 可能是低频词,但可以通过 tokenization 的组合表示。

2. 减小词表大小:

  • 子词分词只需一个小词表即可覆盖大部分文本,避免了巨大的内存开销。

3. 保留词义:

  • 子词保留了部分语义信息,例如 tokenization 分别表示单词的前缀和后缀。

例子

我们可以使用 Hugging Face 的 Transformers 库中的分词器(Tokenizer)实现子词切分。例如,WordPieceByte Pair Encoding (BPE) 是常用的子词分词算法。

代码如下:

from transformers import AutoTokenizer

# 加载预训练分词器
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")

# 测试文本
sample_text = "Let's do tokenization!"

# 使用分词器进行子词切分
tokens = tokenizer.tokenize(sample_text)

# 打印结果
print("Original Text:", sample_text)
print("Subword Tokens:", tokens)

运行结果:

Original Text: Let's do tokenization!
Subword Tokens: ['let', "'", 's', 'do', 'token', '##ization', '!']

代码解析

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

加载 BERT 模型对应的分词器,默认使用 WordPiece 分词算法。

结果解释

  • let's 被分解为两个独立的 token。
  • tokenization 被分解为两个子词:token##ization
    • ## 表示这是一个后续子词,属于前一个单词的一部分。

文本编码与解码

本内容展示了 Hugging Face 的 AutoTokenizer 对文本进行编码和解码的过程,分别涉及以下关键步骤。

编码过程

如前所述,文本编码 (Encoding) 过程包含两个步骤:

  1. 分词:使用分词器按某种策略将文本切分为 tokens;
  2. 映射:将 tokens 转化为对应的 token IDs。

下面我们

编码示例

1. 首先使用 BERT 分词器来对文本进行分词。

代码如下:

from transformers import AutoTokenizer

# 加载分词器
tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")

# 输入文本
sequence = "Using a Transformer network is simple"

# 编码 - 分词
tokens = tokenizer.tokenize(sequence)
print(tokens)  # 打印分词后的 tokens

输出:

['using', 'a', 'transform', '##er', 'network', 'is', 'simple']

分词细节:

  • 使用子词分词策略(如 WordPiece)。
  • 将低频单词(如 Transformer)分解为 transform##er
  • ## 表示子词为前一个 token 的一部分。

2. 然后,我们通过 convert_tokens_to_ids() 将切分出的 tokens 转换为对应的 token IDs。

代码如下:

ids = tokenizer.convert_tokens_to_ids(tokens)
print(ids)

输出:

[7993, 170, 13809, 23763, 2443, 1110, 3014]

每个 token 被映射为一个整数 ID,表示其在词表中的索引。

3. 还可以通过 encode() 函数将这两个步骤合并,并且 encode() 会自动添加模型需要的特殊 token,例如 BERT 分词器会分别在序列的首尾添加 [CLS] 和 [SEP]:

# 使用 encode() 方法直接编码文本
sequence_ids = tokenizer.encode(sequence)
print(sequence_ids)

输出:

[101, 7993, 170, 13809, 23763, 2443, 1110, 3014, 102]

新增的特殊 token:

  • 101:表示 [CLS](分类起始符)。
  • 102:表示 [SEP](分隔符)。

注意,上面这些只是为了演示。在实际编码文本时,最常见的是直接使用分词器进行处理,这样不仅会返回分词后的 token IDs,还包含模型需要的其他输入。例如 BERT 分词器还会自动在输入中添加 token_type_ids 和 attention_mask.

4. BERT 的完整编码信息

tokenized_text = tokenizer("Using a Transformer network is simple")
print(tokenized_text)

输出:

{
  'input_ids': [101, 7993, 170, 13809, 23763, 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]
}

字段说明:

  • input_ids:文本对应的 token IDs,包含 [CLS] 和 [SEP]。
  • token_type_ids:区分不同段落的标记,BERT 多段输入时用到。
  • attention_mask:掩码标记,1 表示有效的 token,0 表示 padding。

解码过程

文本解码 (Decoding) 与编码相反,负责将 token IDs 转换回原来的字符串。注意,解码过程不是简单地将 token IDs 映射回 tokens,还需要合并那些被分为多个 token 的单词。

代码:

decoded_string = tokenizer.decode([7993, 170, 13809, 23763, 2443, 1110, 3014])
print(decoded_string)

输出:

Using a transformer network is simple

解码带特殊 token 的 ID:

decoded_string = tokenizer.decode([101, 7993, 170, 13809, 23763, 2443, 1110, 3014, 102])
print(decoded_string)

输出:

[CLS] Using a Transformer network is simple [SEP]

总结

  1. 编码过程:

    • tokenize() 分词,将文本拆分为子词 token。
    • convert_tokens_to_ids() 将 token 转换为对应的 token IDs。
    • encode() 是以上两步的封装,并自动添加特殊 token(如 [CLS] 和 [SEP])。
  2. 解码过程:

    • decode() 将 token IDs 转换回原始文本,支持忽略特殊 token。
  3. 重要参数:

    • input_ids:模型的实际输入。
    • attention_mask:表示 token 的有效性。
    • token_type_ids:区分输入段落的标记。

http://www.ppmy.cn/embedded/142758.html

相关文章

在鸿蒙应用中 Debug 对开发者的帮助

文章目录 摘要引言Debug 的意义与挑战案例&#xff1a;页面渲染性能优化中的 Bug 排查Debug 过程详解问题定位问题解决优化布局与渲染逻辑 代码详细讲解示例代码详细讲解1. 导入必要模块2. 数据生成3. 使用虚拟列表组件items 属性itemHeight 属性renderItem 属性 4. 返回完整组…

5.2.2 动作标记 getproperty

目录 5.2.2 动作标记 getproperty 5.2.3 setProperty 5.2.2 动作标记 getproperty javabean的实质是遵守一定规范的类所创建的对象&#xff0c;可以通过两种方式获取bean的属性 1.在java程序片中&#xff0c;使用bean对象调用getxxx()方法获得bean的属性值 2.通过<jsp:us…

vue2怎么写computed属性

在Vue 2中&#xff0c;computed属性是基于它们的响应式依赖进行缓存的计算属性。只有当计算属性依赖的响应式数据发生变化时&#xff0c;计算属性才会重新计算。以下是如何在Vue 2中定义computed属性的步骤&#xff1a; 定义响应式数据&#xff1a;首先&#xff0c;你需要在组件…

Python中使用pip换源的详细指南

在Python开发过程中&#xff0c;我们经常需要安装各种第三方库。pip是Python的包管理工具&#xff0c;用于安装和管理Python库。然而&#xff0c;由于网络原因&#xff0c;有时访问默认的Python包索引&#xff08;PyPI&#xff09;可能会比较慢。这时&#xff0c;我们可以通过更…

Docker容器ping不通外网问题排查及解决

Docker容器ping不通外网问题排查及解决 解决方案在最下面&#xff0c;不看过程的可直接拉到最下面。 一台虚拟机里突然遇到docker容器一直访问外网失败&#xff0c;网上看到这个解决方案&#xff0c;这边记录一下。 首先需要明确docker的网桥模式&#xff0c;网桥工作在二层…

【linux】(25)shell脚本-基础入门

Shell 脚本是一种在类 Unix 系统&#xff08;如 Linux、macOS&#xff09;中用来编写自动化任务的脚本语言。掌握 Shell 编程可以帮助你高效地管理系统、批量处理文件、执行定时任务等。 1. 什么是 Shell&#xff1f; Shell 是一种命令行解释器&#xff0c;它为用户提供了与操…

SHELL脚本2(Linux网络服务器 23)

利用test检查文件权限 描述&#xff1a;输入一个已存在的文件名&#xff0c;检查该文件是否具有读写执行的权限。如果文件不存在要给予提醒。 #!/bin/bash echo -e "Please input a filename,I will check the filenames type and perimission.\n\n" read -p "I…

webpack5开发环境、生产环境配置 (三)

开发环境&#xff1a;就是我们开发代码时使用的模式。 这个模式我们做两件事情&#xff1a; 1、编译代码&#xff0c;使浏览器能识别运行 2、代码质量检查&#xff0c;树立代码规范 生产环境&#xff1a;开发完成代码后&#xff0c;我们需要得到代码将来部署上线。 这个模式…