分词器详解(一)

文章目录

  • 🌟 第0层:极简版(30秒理解)
    • 核心公式
    • 生活比喻
  • 📚 第1层:基础概念(5分钟理解)
    • 1. 分词器基础
      • 1.1 分词器的核心作用
      • 1.2 主流分词算法对比
    • 2. 基础实现
      • 2.1 BPE实现原理
      • 2.2 特殊标记处理
      • 2.3 和 huggingface的Tokenizer库做对比
      • 2.4 WordPiece分词器
        • 2.4.1 WordPiece的工作原理
      • 2.5 SentencePiece
        • 2.5.1 SentencePiece的概述
        • 2.5.2 SentencePiece的工作原理
    • 3. 基础应用
      • 3.1 选择指南

🌟 第0层:极简版(30秒理解)

一句话核心分词器是语言模型的"词汇翻译器"——BPE是"高频字符对合并器",WordPiece是"最大似然子词分割器",SentencePiece是"语言无关编码器"优秀的分词器能使模型性能提升5-15%,训练速度提高10-20%,特别在低资源语言上效果更显著

核心公式

最优词汇表 = argmin(α·表示效率 + β·词汇量 - γ·训练稳定性)
分词效率 = f(词汇表大小, 语言特性, 模型架构)
  • 关键特性:分词直接影响模型表示能力、训练效率和泛化性能
  • 优势:处理未登录词,平衡词汇量与表示能力
  • 挑战:语言特性差异,特殊领域适应,长尾分布处理

生活比喻

想象图书馆编目系统:

  • 传统分词:按整本书分类 → 词汇量爆炸
  • BPE:按高频词组创建新类别 → 高效但可能碎片化
  • SentencePiece:按字符组合创建类别 → 语言无关但可能过细
  • 理想分词:既识别完整概念又处理新词 → 平衡效率与表达

💡 记住这个数字5-15% → 10-20% → 30-50%
优秀分词器可使模型性能提升5-15%,训练速度提高10-20%,在低资源语言上效果提升30-50%

📚 第1层:基础概念(5分钟理解)

1. 分词器基础

1.1 分词器的核心作用

核心功能

  • 文本→ID转换:将原始文本映射为模型可处理的整数序列
  • 未知词处理:通过子词机制处理未见词汇
  • 语言表示:影响模型对语言结构的理解
  • 效率优化:控制序列长度和计算复杂度

关键组件

  • 词汇表(Vocabulary):词项到ID的映射
  • 分词算法:将文本拆分为词项的规则
  • 特殊标记:处理特殊场景的标记
  • 后处理:添加位置信息、截断和填充

1.2 主流分词算法对比

算法代表模型核心思想优点局限
BPEGPT系列高频字符对合并处理未登录词好可能产生不自然分割
WordPieceBERT最大似然子词分割适合英文任务严重依赖空格
SentencePieceT5, XLNet无空格分词语言无关训练较慢
Unigram LM部分SentencePiece概率模型可逆转换计算复杂

关键区别

  • BPE:贪心合并高频对
  • WordPiece:基于似然选择最佳分割
  • SentencePiece:不依赖空格,统一处理所有语言

2. 基础实现

在代码的同级目录新建corpus.txt文件,向里面写一个文字。

2.1 BPE实现原理

Byte-Pair Encoding(BPE)最初用于数据压缩,后来被广泛应用于自然语言处理中的子词分词任务。它通过迭代合并最频繁的字符对或子词对来构建词汇表,是GPT、RoBERTa等现代语言模型的关键分词技术。

BPE的训练过程如下:

初始化词汇表:从训练数据中提取所有唯一字符。例如,对于语料库[“dog” (8次), “log” (6次), “lot” (10次), “dot” (3次), “dogs” (4次)],初始词汇表为[“d”, “o”, “g”, “l”, “t”, “s”]。

统计字符对频率:遍历语料库,计算连续字符对的频率。例如,在"dog"中,字符对为(“d”, “o”)和(“o”, “g”)。

合并最频繁的字符对:选择频率最高的字符对,合并为新token。

更新词汇表:将新token加入词汇表,重新分词语料库,重复上述过程,直到达到目标词汇表大小。

示例:BPE训练
语料库:[“dog” (8次), “log” (6次), “lot” (10次), “dot” (3次), “dogs” (4次)]

步骤0:初始化

词汇表:[“d”, “o”, “g”, “l”, “t”, “s”]
语料库分词:

  • dog: [“d”, “o”, “g”]
  • log: [“l”, “o”, “g”]
  • lot: [“l”, “o”, “t”]
  • dot: [“d”, “o”, “t”]
  • dogs: [“d”, “o”, “g”, “s”]

步骤1:统计频率

字符对:

  • (“d”, “o”):15次(dog x8, dot x3, dogs x4)
  • (“o”, “g”):18次(dog x8, log x6, dogs x4)
  • (“l”, “o”):16次(log x6, lot x10)
  • (“o”, “t”):13次(lot x10, dot x3)
  • (“d”, “t”):3次(dot x3)
  • (“g”, “s”):4次(dogs x4)

最高频率:(“o”, “g”)(18次)
合并为"og",新词汇表:[“d”, “o”, “g”, “l”, “t”, “s”, “og”]

步骤2:重新分词

  • dog: [“d”, “og”]
  • log: [“l”, “og”]
  • lot: [“l”, “o”, “t”]
  • dot: [“d”, “o”, “t”]
  • dogs: [“d”, “og”, “s”]

继续统计新字符对频率:

  • (“l”, “o”):10次(lot x10)
  • (“o”, “t”):13次(lot x10, dot x3)
  • (“d”, “o”):3次(dot x3)
  • (“d”, “og”):12次(dog x8, dogs x4)
  • (“l”, “og”):6次(log x6)
  • (“og”, “s”):4次(dogs x4)

最高频率:(“o”, “t”)(13次)
合并为"ot",新词汇表:[“d”, “o”, “g”, “l”, “t”, “s”, “og”, “ot”]

步骤3:再次重新分词

  • dog: [“d”, “og”]
  • log: [“l”, “og”]
  • lot: [“l”, “ot”]
  • dot: [“d”, “ot”]
  • dogs: [“d”, “og”, “s”]

继续统计新字符对频率:

  • (“d”, “og”):12次(dog x8, dogs x4)
  • (“l”, “og”):6次(log x6)
  • (“l”, “ot”):10次(lot x10)
  • (“d”, “ot”):3次(dot x3)
  • (“og”, “s”):4次(dogs x4)

最高频率:(“d”, “og”)(12次)
合并为"dog",新词汇表:[“d”, “o”, “g”, “l”, “t”, “s”, “og”, “ot”, “dog”]

示例:BPE分词
假设最终词汇表为:[“d”, “o”, “g”, “l”, “t”, “s”, “og”, “ot”, “dog”, “log”]

分词"dogs":

  • 从左到右匹配最长子词:
    • "d"匹配
    • "do"不匹配
    • "dog"匹配
    • "s"匹配
  • 结果:[“dog”, “s”]

分词"logs":

  • "l"匹配
  • "lo"不匹配
  • "log"匹配
  • "s"匹配
  • 结果:[“log”, “s”]

分词"dot":

  • "d"匹配
  • "do"不匹配
  • "dot"不匹配(假设"dot"未被合并)
  • "do"不匹配
  • "ot"匹配
  • 结果:[“d”, “ot”]

BPE通过这种方式将单词分解为有意义的子词单元,既能处理未登录词,又能保持词汇表大小在合理范围内。 这种方法在现代NLP系统中被广泛应用,因为它能有效地平衡词汇表大小和表示能力。

代码示例


import re
from collections import defaultdict, Counterclass BPETokenizer:def __init__(self, special_tokens=None):# 修改初始化部分self.vocab = set()self.merge_rules = {}# 特殊标记字典,键为标记,值为IDself.special_tokens = special_tokens or {"[PAD]": 0,"[UNK]": 1,"[CLS]": 2,"[SEP]": 3,"[MASK]": 4}# 构建ID到token的反向映射self.id_to_token = {v: k for k, v in self.special_tokens.items()}self.token_to_id = self.special_tokens.copy()def train(self, corpus, vocab_size=10000):"""训练BPE模型"""# 对于中文,我们按字符分割words = list(corpus.replace(' ', '_'))  # 用下划线表示空格word_freq = Counter()# 统计字符频率for char in words:word_freq[char] += 1# 初始化词汇表self.vocab = set(self.special_tokens.keys())self.vocab.update(word_freq.keys())# 初始化符号序列,每个字符作为一个符号symbols_list = [[char] for char in words]# BPE训练过程current_vocab_size = len(self.vocab)while current_vocab_size < vocab_size:# 统计相邻符号对的频率pair_freq = defaultdict(int)for symbols in symbols_list:for i in range(len(symbols) - 1):pair = (symbols[i], symbols[i + 1])pair_freq[pair] += 1if not pair_freq:break# 找到最频繁的符号对best_pair = max(pair_freq, key=pair_freq.get)# 检查是否已经存在这个合并规则if best_pair in self.merge_rules:breaknew_token = ''.join(best_pair)# 如果新token已存在,跳过if new_token in self.vocab:del pair_freq[best_pair]if not pair_freq:breakcontinue# 添加新token到词汇表self.vocab.add(new_token)self.merge_rules[best_pair] = new_token# 更新所有符号序列new_symbols_list = []for symbols in symbols_list:new_symbols = self._apply_merge_rule_to_symbols(symbols, best_pair, new_token)new_symbols_list.append(new_symbols)symbols_list = new_symbols_listcurrent_vocab_size += 1# 构建完整的词汇表映射self._build_vocab_mappings()def _apply_merge_rule_to_symbols(self, symbols, pair, new_token):"""将合并规则应用到符号序列"""new_symbols = []i = 0while i < len(symbols):if i < len(symbols) - 1 and symbols[i] == pair[0] and symbols[i + 1] == pair[1]:new_symbols.append(new_token)i += 2else:new_symbols.append(symbols[i])i += 1return new_symbolsdef _build_vocab_mappings(self):"""构建词汇表映射"""# 清空现有的映射self.id_to_token.clear()self.token_to_id.clear()# 先添加特殊标记for token, id in self.special_tokens.items():self.id_to_token[id] = tokenself.token_to_id[token] = id# 添加普通词汇表项sorted_vocab = sorted(self.vocab)for i, token in enumerate(sorted_vocab):token_id = i + len(self.special_tokens)self.id_to_token[token_id] = tokenself.token_to_id[token] = token_iddef tokenize(self, text):"""使用训练好的BPE模型进行分词"""# 将文本转换为初始符号序列symbols = list(text.replace(' ', '_'))  # 空格用特殊符号表示# 重复应用合并规则直到无法继续合并changed = Truewhile changed:changed = Falsenew_symbols = []i = 0while i < len(symbols):if i < len(symbols) - 1:pair = (symbols[i], symbols[i + 1])if pair in self.merge_rules:new_symbols.append(self.merge_rules[pair])i += 2changed = Truecontinuenew_symbols.append(symbols[i])i += 1symbols = new_symbols# 将下划线转换回空格标记tokens = []for symbol in symbols:if symbol == '_':tokens.append('[SPACE]')  # 或者其他表示空格的方式else:tokens.append(symbol)return tokensdef encode(self, text):"""将文本转换为ID序列"""tokens = self.tokenize(text)ids = []for token in tokens:if token in self.token_to_id:ids.append(self.token_to_id[token])else:ids.append(self.token_to_id.get("[UNK]", 1))return idsdef decode(self, ids):"""将ID序列转换回文本"""tokens = []for id in ids:if id in self.id_to_token:token = self.id_to_token[id]if token == '[SPACE]':tokens.append(' ')elif token not in self.special_tokens:tokens.append(token)else:# 特殊标记不添加到解码文本中passelse:tokens.append('[UNK]')return ''.join(tokens)def vocab_size(self):"""返回词汇表大小"""return len(self.vocab) + len(self.special_tokens)# 使用示例
if __name__ == "__main__":# 初始化分词器tokenizer = BPETokenizer()# 训练语料with open("corpus.txt", "r", encoding="utf-8") as f:corpus = f.read()# 如果你希望保留一些基本的文本清理操作,可以添加如下代码:# with open("corpus.txt", "r", encoding="utf-8") as f:#     corpus = f.read().strip()  # 读取并去除首尾空白字符# 训练BPE模型tokenizer.train(corpus, vocab_size=100)print("词汇表大小:", tokenizer.vocab_size())print("部分词汇表:", list(tokenizer.vocab)[:20])print("合并规则数量:", len(tokenizer.merge_rules))# 测试分词text = "自然语言处理很有趣!"tokens = tokenizer.tokenize(text)print("Tokens:", tokens)# 测试编码ids = tokenizer.encode(text)print("IDs:", ids)# 测试解码decoded = tokenizer.decode(ids)print("Decoded:", decoded)print("原始文本:", text)

在这里插入图片描述

2.2 特殊标记处理

关键特殊标记

SPECIAL_TOKENS = {"[PAD]": 0,   # 填充标记"[UNK]": 1,   # 未知词标记"[CLS]": 2,   # 分类标记"[SEP]": 3,   # 分隔标记"[MASK]": 4,  # 掩码标记"[BOS]": 5,   # 开始标记"[EOS]": 6,   # 结束标记
}

处理策略

  • [UNK]最小化:通过足够大的词汇表减少未知词
  • 位置编码兼容:确保特殊标记不影响位置信息
  • 任务适配:不同任务需要不同的特殊标记组合
  • 长度控制:通过截断和填充保证序列长度一致

2.3 和 huggingface的Tokenizer库做对比

安装库,执行命令:

 pip install tokenizers
from tokenizers import Tokenizer, models, pre_tokenizers, trainers, processors
from tokenizers import normalizers
from tokenizers.normalizers import NFD, StripAccents# 初始化BPE分词器
tokenizer = Tokenizer(models.BPE(unk_token="[UNK]"))# 设置预分词器(按空格和标点分割)
tokenizer.pre_tokenizer = pre_tokenizers.Sequence([pre_tokenizers.Whitespace(),pre_tokenizers.Punctuation(),pre_tokenizers.Digits(individual_digits=True)
])# 设置规范化器(可选)
tokenizer.normalizer = normalizers.Sequence([NFD(), StripAccents()])# 定义特殊标记
special_tokens = ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]# 定义训练器
trainer = trainers.BpeTrainer(vocab_size=30000,min_frequency=2,special_tokens=special_tokens,
)# 训练分词器
files = ["./corpus.txt"]  # 替换为你的语料文件路径
tokenizer.train(files, trainer)# 添加后处理(如添加[CLS]和[SEP])
tokenizer.post_processor = processors.TemplateProcessing(single="[CLS] $A [SEP]",pair="[CLS] $A [SEP] $B:1 [SEP]:1",special_tokens=[("[CLS]", tokenizer.token_to_id("[CLS]")),("[SEP]", tokenizer.token_to_id("[SEP]")),],
)# 启用填充和截断
from tokenizers import decoders
tokenizer.enable_padding(pad_id=0, pad_token="[PAD]")
tokenizer.enable_truncation(max_length=512)
tokenizer.decoder = decoders.BPEDecoder()# 保存和加载分词器
tokenizer.save("tokenizer.json")
tokenizer = Tokenizer.from_file("tokenizer.json")# 在现有代码的基础上增加以下内容# 使用分词器处理文本并显示详细结果
text = "自然语言处理很有趣!"
output = tokenizer.encode(text)print("Tokens:", output.tokens)
print("IDs:", output.ids)
print("Attention mask:", output.attention_mask)
print("Offsets:", output.offsets)
print("Special tokens mask:", output.special_tokens_mask)
print("Type IDs:", output.type_ids)# 增加解码功能,将ID转换回文本
decoded_text = tokenizer.decode(output.ids)
print("Decoded text:", decoded_text)# 批量处理并显示详细结果
batch = ["自然语言处理是人工智能的重要领域。", "机器学习是实现人工智能的一种方法。", "深度学习是机器学习的一个分支。"]
batch_output = tokenizer.encode_batch(batch)print("\n批量处理结果:")
for i, (text, encoding) in enumerate(zip(batch, batch_output)):print(f"\n文本 {i+1}: {text}")print(f"  Tokens: {encoding.tokens}")print(f"  IDs: {encoding.ids}")print(f"  Attention mask: {encoding.attention_mask}")print(f"  Sequence length: {len(encoding.ids)}")# 增加词汇表信息
print(f"\n词汇表大小: {tokenizer.get_vocab_size()}")
print(f"特殊标记ID:")
for token in ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]:token_id = tokenizer.token_to_id(token)if token_id is not None:print(f"  {token}: {token_id}")# 增加对未知词汇的处理示例
unknown_text = "这是一个包含罕见词汇的句子:🦙"
unknown_output = tokenizer.encode(unknown_text)
print(f"\n包含未知词汇的处理结果:")
print(f"原文本: {unknown_text}")
print(f"Tokens: {unknown_output.tokens}")
print(f"IDs: {unknown_output.ids}")# 显示词汇表中的部分词汇
vocab = tokenizer.get_vocab()
print(f"\n词汇表示例 (前20个):")
for i, (token, id) in enumerate(list(vocab.items())[:20]):print(f"  {token}: {id}")

在这里插入图片描述

2.4 WordPiece分词器

WordPiece是由Google开发的子词分词算法,作为BERT系列模型的核心分词技术,它通过最大化语言模型似然概率来决定子词合并顺序。与BPE不同,WordPiece采用基于概率的合并策略:优先合并能最大程度提升训练数据概率的字符对。其显著特征是使用##前缀标记非首子词(如"playing"→[“play”, “##ing”]),既能处理未登录词,又能保留词形信息,在跨语言任务中表现优异。

2.4.1 WordPiece的工作原理

WordPiece的训练流程基于期望最大化(EM)算法,核心步骤如下:

  1. 初始化词汇表

    • 从训练数据提取所有唯一字符(含标点、数字等)。
    • 添加特殊标记:[UNK](未知词)、[CLS](分类符)、[SEP](分隔符)。
  2. 概率驱动的合并

    • 计算合并得分:对每个字符对(x,y),计算得分 = count(xy)count(x)×count(y)\frac{\text{count}(xy)}{\text{count}(x) \times \text{count}(y)}count(x)×count(y)count(xy)
      (即合并后子词的互信息增益,值越大表示合并越能提升语言模型概率)
    • 选择最优合并:选取得分最高的字符对,合并为新子词(如"e"+"r"→"er")。
  3. 迭代优化

    • 用新子词更新语料库分词结果
    • 重新计算所有字符对得分
    • 重复直到词汇表达到预设大小(BERT-base通常为30,522)

示例:WordPiece训练
语料库["low" (10次), "lower" (8次), "slow" (6次), "flow" (5次), "lowest" (4次)]

步骤0:初始化

  • 词汇表["l", "o", "w", "e", "r", "s", "f", "t"]
  • 语料库分词
    low:    ["l", "o", "w"]
    lower:  ["l", "o", "w", "e", "r"]
    slow:   ["s", "l", "o", "w"]
    flow:   ["f", "l", "o", "w"]
    lowest: ["l", "o", "w", "e", "s", "t"]
    

步骤1:计算合并得分

字符对count(xy)count(x)count(y)得分 = count(xy)/(count(x)×count(y))
(l,o)33333333/(33×33) = 0.0303
(o,w)3333330.0303
(w,e)12331212/(33×12) = 0.0303
(e,r)81288/(12×8) = 0.0833 (最高)
(e,s)412104/(12×10) = 0.0333
(s,l)610336/(10×33) = 0.0182
  • 合并结果"e" + "r" → "er"
  • 新词汇表["l", "o", "w", "e", "r", "s", "f", "t", "er"]

步骤2:重新分词并迭代

  • 更新分词

    lower: ["l", "o", "w", "er"]  # "e"+"r"合并为"er"
    lowest: ["l", "o", "w", "e", "s", "t"]  # 未受影响
    
  • 新字符对统计

    • (w, er):8次(来自"lower")
    • (e, s):4次(来自"lowest")
  • 重新计算得分

    字符对count(xy)count(x)count(y)得分
    (e,s)44104/(4×10)=0.1000 (最高)
    (w,er)83388/(33×8)=0.0303
    (l,o)3333330.0303
  • 合并结果"e" + "s" → "es"

  • 新词汇表["l", "o", "w", "e", "r", "s", "f", "t", "er", "es"]

步骤3:最终分词效果

  • "lowest"分词
    ["l", "o", "w", "es", "t"] → 后续可能合并"l"+"o"→"lo""lo"+"w"→"low"
  • 典型词汇表示例
    ["l", "o", "w", "er", "es", "low", "##er", "##es"]

示例:WordPiece分词
假设最终词汇表["low", "##er", "##es", "s", "f", "l", "o", "w", "t"]

分词"lowest"

  1. 从左到右匹配
    • "l" → 匹配,但"lo"不在词汇表
    • "low"匹配(最长前缀)
    • 剩余"est""e"不在词汇表,尝试"es"匹配
    • 剩余"t"匹配
  2. 添加##规则
    • 首子词"low"无前缀
    • 后续子词"es""t"需加##(但"t"为独立字符)
  3. 结果["low", "##es", "t"]

分词"slower"(未登录词):

  1. 匹配过程
    • "s" → 匹配
    • "sl"不匹配 → 回退到"s"
    • "l" → 匹配
    • "lo"不匹配 → "l"
    • "ow"不匹配 → "o""w"
    • "er"匹配
  2. 结果["s", "l", "o", "w", "##er"]

分词"flow"

  • "f" → 匹配
  • "fl"不匹配 → "f"
  • "l" → 匹配
  • "lo"不匹配 → "l"
  • "ow"不匹配 → "o""w"
  • 结果["f", "l", "o", "w"]

关键特性

  1. 概率驱动合并
    优先合并高互信息的子词(如"er"在英语中作为后缀的极高概率),比BPE的频次统计更符合语言规律。

  2. ##标记机制

    • 首子词无前缀("play"
    • 后续子词强制加##"##ing"),使模型明确识别词边界
  3. 未登录词处理
    对罕见词(如"slower")自动拆解为已知子词,避免[UNK]泛滥,显著提升OOV(Out-of-Vocabulary)处理能力。

WordPiece通过概率建模实现了词形敏感的分词策略,成为BERT等模型实现跨任务迁移学习的关键基础。其设计平衡了词汇表效率与语言表达能力,尤其适合处理英语等屈折语。

代码如下:

from tokenizers import Tokenizer, models, pre_tokenizers, trainers, processors
from tokenizers import normalizers
from tokenizers.normalizers import NFD, StripAccents# 初始化BPE分词器
tokenizer = Tokenizer(models.WordPiece(unk_token="[UNK]"))# 设置预分词器(按空格和标点分割)
tokenizer.pre_tokenizer = pre_tokenizers.Sequence([pre_tokenizers.Whitespace(),pre_tokenizers.Punctuation(),pre_tokenizers.Digits(individual_digits=True)
])# 设置规范化器(可选)
tokenizer.normalizer = normalizers.Sequence([NFD(), StripAccents()])# 定义特殊标记
special_tokens = ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]# 定义训练器
trainer = trainers.WordPieceTrainer(vocab_size=30000,min_frequency=2,special_tokens=special_tokens,continuing_subword_prefix="##"  # 对于WordPiece风格
)# 训练分词器
files = ["./corpus.txt"]  # 替换为你的语料文件路径
tokenizer.train(files, trainer)# 添加后处理(如添加[CLS]和[SEP])
tokenizer.post_processor = processors.TemplateProcessing(single="[CLS] $A [SEP]",pair="[CLS] $A [SEP] $B:1 [SEP]:1",special_tokens=[("[CLS]", tokenizer.token_to_id("[CLS]")),("[SEP]", tokenizer.token_to_id("[SEP]")),],
)# 启用填充和截断
from tokenizers import decoderstokenizer.enable_padding(pad_id=0, pad_token="[PAD]")
tokenizer.enable_truncation(max_length=512)
tokenizer.decoder = decoders.WordPiece(prefix="##")# 保存和加载分词器
tokenizer.save("tokenizer.json")
tokenizer = Tokenizer.from_file("tokenizer.json")# 使用分词器处理文本并显示详细结果
text = "自然语言处理很有趣!"
output = tokenizer.encode(text)print("Tokens:", output.tokens)
print("IDs:", output.ids)
print("Attention mask:", output.attention_mask)
print("Offsets:", output.offsets)
print("Special tokens mask:", output.special_tokens_mask)
print("Type IDs:", output.type_ids)# 增加解码功能,将ID转换回文本
decoded_text = tokenizer.decode(output.ids)
print("Decoded text:", decoded_text)# 批量处理并显示详细结果
batch = ["自然语言处理是人工智能的重要领域。", "机器学习是实现人工智能的一种方法。", "深度学习是机器学习的一个分支。"]
batch_output = tokenizer.encode_batch(batch)print("\n批量处理结果:")
for i, (text, encoding) in enumerate(zip(batch, batch_output)):print(f"\n文本 {i + 1}: {text}")print(f"  Tokens: {encoding.tokens}")print(f"  IDs: {encoding.ids}")print(f"  Attention mask: {encoding.attention_mask}")print(f"  Sequence length: {len(encoding.ids)}")# 增加词汇表信息
print(f"\n词汇表大小: {tokenizer.get_vocab_size()}")
print(f"特殊标记ID:")
for token in ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]:token_id = tokenizer.token_to_id(token)if token_id is not None:print(f"  {token}: {token_id}")# 增加对未知词汇的处理示例
unknown_text = "这是一个包含罕见词汇的句子:🦙"
unknown_output = tokenizer.encode(unknown_text)
print(f"\n包含未知词汇的处理结果:")
print(f"原文本: {unknown_text}")
print(f"Tokens: {unknown_output.tokens}")
print(f"IDs: {unknown_output.ids}")# 显示词汇表中的部分词汇
vocab = tokenizer.get_vocab()
print(f"\n词汇表示例 (前20个):")
for i, (token, id) in enumerate(list(vocab.items())[:20]):print(f"  {token}: {id}")

运行结果:

Tokens: ['[CLS]', '自', '##然', '##语言', '##处理', '##很', '##有', '##趣', '!', '[SEP]']
IDs: [2, 526, 866, 1232, 1282, 1065, 943, 1066, 5, 3]
Attention mask: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
Offsets: [(0, 0), (0, 1), (1, 2), (2, 4), (4, 6), (6, 7), (7, 8), (8, 9), (9, 10), (0, 0)]
Special tokens mask: [1, 0, 0, 0, 0, 0, 0, 0, 0, 1]
Type IDs: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
Decoded text: 自然语言处理很有趣!批量处理结果:文本 1: 自然语言处理是人工智能的重要领域。Tokens: ['[CLS]', '自', '##然', '##语言', '##处理', '##是', '##人', '##工智能', '##的', '##重要', '##领域', '。', '[SEP]']IDs: [2, 526, 866, 1232, 1282, 695, 867, 2139, 701, 1738, 1419, 108, 3]Attention mask: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]Sequence length: 13文本 2: 机器学习是实现人工智能的一种方法。Tokens: ['[CLS]', '[UNK]', '。', '[SEP]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]']IDs: [2, 1, 108, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0]Attention mask: [1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]Sequence length: 13文本 3: 深度学习是机器学习的一个分支。Tokens: ['[CLS]', '[UNK]', '。', '[SEP]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]', '[PAD]']IDs: [2, 1, 108, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0]Attention mask: [1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]Sequence length: 13词汇表大小: 2169
特殊标记ID:[PAD]: 0[UNK]: 1[CLS]: 2[SEP]: 3[MASK]: 4包含未知词汇的处理结果:
原文本: 这是一个包含罕见词汇的句子:🦙
Tokens: ['[CLS]', '[UNK]', ':', '[UNK]', '[SEP]']
IDs: [2, 1, 642, 1, 3]词汇表示例 (20):分割: 1591c: 65[PAD]: 0##度: 782best: 1400: 592{: 89build: 1818##ard: 1762: 180##态: 774##fix: 1422: 494: 294: 169##洞: 1141沿: 424##语言特性: 1635: 604贪心合并高频对: 2129

2.5 SentencePiece

2.5.1 SentencePiece的概述

SentencePiece是由Google开发的一种无需预分词的子词分词工具,它将文本直接视为Unicode字符序列进行处理。与传统分词方法不同,SentencePiece不依赖空格或语言特定规则,能够无缝处理多语言文本、标点符号和特殊字符。它支持两种核心算法:BPE(Byte-Pair Encoding)Unigram语言模型,广泛应用于T5、ALBERT等现代语言模型中。

安装库文件,执行命令:

pip install sentencepiece
2.5.2 SentencePiece的工作原理

SentencePiece的核心创新在于直接处理原始文本,无需预分词步骤。其工作流程如下:

  1. 原始文本预处理

    • 将空格完全替换为特殊符号(U+2581,代表词界),例如"cat sat" → "▁cat▁sat"(注意:不保留原始空格)。
    • 保留所有标点和Unicode字符,避免语言依赖性。
  2. 算法选择

    • BPE模式:与标准BPE类似,但以为词边界标志进行字符对合并。
    • Unigram模式(更常用):
      • 从一个过完备的大词汇表开始(通常通过BPE或高频子串生成)
      • 通过迭代剪枝优化词汇表:计算每个子词的损失影响,移除对似然值提升最小的子词
      • 使用EM算法估计子词概率,最大化训练数据的似然概率
      • 推理时使用Viterbi算法选择全局最优分词路径
  3. 词汇表优化

    • 通过正则化控制子词长度,避免过短或过长的子词
    • 支持用户自定义词汇表大小和字符覆盖率

示例:SentencePiece训练(Unigram模式)
语料库["▁cat" (7次), "▁bat" (5次), "▁rat" (9次), "▁car" (4次), "▁cats" (3次)]

步骤0:初始化

  • 生成初始大词汇表(通过BPE或高频子串收集):
    ["▁", "c", "a", "t", "b", "r", "s", "▁c", "ca", "at", "▁b", "▁r", "ar", "ra", "ts", "cats"]
  • 使用EM算法估计初始子词概率

步骤1:迭代剪枝

  • 计算每个子词的损失影响:移除该子词后整体似然值的下降程度
  • 移除低贡献子词:如["r"]["ts"]等对似然值影响最小的子词
  • 重新估计概率:使用EM算法重新计算保留子词的概率

步骤2:最终词汇表

  • 优化后的词汇表示例
    ["▁", "c", "a", "t", "b", "s", "at", "ca", "▁c", "▁b", "cats"]
  • 子词概率(示例值):
    • P(“at”) = 0.21
    • P(“▁c”) = 0.14
    • P(“cats”) = 0.08
    • P(“a”) = 0.12

示例:SentencePiece分词
假设最终词汇表["▁", "c", "a", "t", "b", "s", "at", "ca", "▁c", "▁b", "cats"]
对应概率[0.2, 0.1, 0.12, 0.11, 0.09, 0.08, 0.21, 0.13, 0.14, 0.1, 0.08]

分词"▁cats"

  1. Viterbi路径搜索:考虑所有可能分割
    • 选项1:["▁", "c", "a", "t", "s"] → 概率 = 0.2×0.1×0.12×0.11×0.08 = 2.11e-6
    • 选项2:["▁", "c", "a", "ts"] → "ts"不在词汇表
    • 选项3:["▁", "c", "at", "s"] → 概率 = 0.2×0.1×0.21×0.08 = 3.36e-4
    • 选项4:["▁", "ca", "t", "s"] → 概率 = 0.2×0.13×0.11×0.08 = 2.29e-4
    • 选项5:["▁", "cats"] → 概率 = 0.2×0.08 = 0.016
  2. 选择最优路径:选项5概率最高
  3. 结果["▁", "cats"]

分词"▁car"

  1. Viterbi路径搜索
    • 选项1:["▁", "c", "a", "r"] → "r"不在词汇表
    • 选项2:["▁", "c", "ar"] → "ar"不在词汇表
    • 选项3:["▁c", "a", "r"] → "r"不在词汇表
    • 选项4:["▁c", "ar"] → "ar"不在词汇表
    • 选项5:["▁", "ca", "r"] → "r"不在词汇表
  2. 回退到字符级:只能选择["▁", "c", "a"] + 未知字符"r"
  3. 结果["▁", "c", "a"] + [UNK](实际处理中会对未知字符做特殊处理)

分词"▁bat"

  1. Viterbi路径搜索
    • 选项1:["▁", "b", "a", "t"] → 概率 = 0.2×0.09×0.12×0.11 = 2.38e-4
    • 选项2:["▁", "b", "at"] → 概率 = 0.2×0.09×0.21 = 3.78e-3
    • 选项3:["▁b", "a", "t"] → 概率 = 0.1×0.12×0.11 = 1.32e-3
    • 选项4:["▁b", "at"] → 概率 = 0.1×0.21 = 0.021
  2. 选择最优路径:选项4概率最高
  3. 结果["▁b", "at"]

关键优势

  1. 语言无关性:无需空格分词,适用于中文、日文等无空格语言。
  2. 端到端训练:直接从原始字节流学习,避免预处理偏差。
  3. 灵活控制:通过character_coverage参数平衡罕见字符处理能力(如中文需99.9%,英文99.95%)。

SentencePiece通过统一框架解决了多语言分词的复杂性,成为现代NLP系统中跨语言迁移学习的关键工具。 其Unigram模式尤其擅长处理形态丰富的语言(如土耳其语),而BPE模式则在资源有限场景下保持高效。

代码示例:

import sentencepiece as spm
import json# 训练SentencePiece模型
def train_sentencepiece():# SentencePiece训练spm.SentencePieceTrainer.train(input='./corpus.txt',  # 训练数据文件model_prefix='spm_model',  # 模型前缀vocab_size=8046,  # 词汇表大小character_coverage=0.9995,  # 中文建议接近1.0model_type='bpe',  # 使用BPE模型user_defined_symbols=["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"],  # 特殊标记max_sentence_length=4096,  # 最大句子长度shuffle_input_sentence=True,  # 随机打乱输入句子minloglevel=1  # 减少日志输出)# 加载训练好的模型
try:train_sentencepiece()sp = spm.SentencePieceProcessor()sp.load('spm_model.model')
except Exception as e:print(f"训练模型时出错: {e}")print("请确保 corpus.txt 文件存在且包含足够的文本数据")# 创建一个简单的测试模型test_text = "自然语言处理是人工智能的重要领域。机器学习很有趣。深度学习是机器学习的一个分支。"with open('test_corpus.txt', 'w', encoding='utf-8') as f:f.write(test_text)spm.SentencePieceTrainer.train(input='test_corpus.txt',model_prefix='spm_model',vocab_size=118,character_coverage=1.0,model_type='bpe',user_defined_symbols=["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"])sp = spm.SentencePieceProcessor()sp.load('spm_model.model')# 定义特殊标记的ID
special_tokens = {"[PAD]": sp.piece_to_id("[PAD]"),"[UNK]": sp.piece_to_id("[UNK]"),"[CLS]": sp.piece_to_id("[CLS]"),"[SEP]": sp.piece_to_id("[SEP]"),"[MASK]": sp.piece_to_id("[MASK]")
}print("SentencePiece模型信息:")
print(f"词汇表大小: {sp.get_piece_size()}")
print(f"特殊标记ID: {special_tokens}")# 使用分词器处理文本并显示详细结果
text = "自然语言处理很有趣!"
tokens = sp.encode(text, out_type=str)
ids = sp.encode(text, out_type=int)print(f"\n处理文本: {text}")
print("Tokens:", tokens)
print("IDs:", ids)# 增加解码功能,将ID转换回文本
decoded_text = sp.decode(ids)
print("Decoded text:", decoded_text)# 批量处理并显示详细结果
batch = ["自然语言处理是人工智能的重要领域。", "机器学习是实现人工智能的一种方法。", "深度学习是机器学习的一个分支。"]print("\n批量处理结果:")
for i, text in enumerate(batch):tokens = sp.encode(text, out_type=str)ids = sp.encode(text, out_type=int)print(f"\n文本 {i + 1}: {text}")print(f"  Tokens: {tokens}")print(f"  IDs: {ids}")print(f"  Sequence length: {len(ids)}")# 增加对未知词汇的处理示例
unknown_text = "这是一个包含罕见词汇的句子:🦙"
unknown_ids = sp.encode(unknown_text, out_type=int)
unknown_tokens = sp.encode(unknown_text, out_type=str)
print(f"\n包含未知词汇的处理结果:")
print(f"原文本: {unknown_text}")
print(f"Tokens: {unknown_tokens}")
print(f"IDs: {unknown_ids}")# 显示词汇表中的部分词汇
print(f"\n词汇表示例 (前20个):")
for i in range(min(20, sp.get_piece_size())):piece = sp.id_to_piece(i)print(f"  {piece}: {i}")# 保存模型信息到JSON文件(可选)
model_info = {"vocab_size": sp.get_piece_size(),"special_tokens": special_tokens,"model_prefix": "spm_model"
}with open("spm_model_info.json", "w", encoding="utf-8") as f:json.dump(model_info, f, ensure_ascii=False, indent=2)print(f"\n模型信息已保存到 spm_model_info.json")
print(f"模型文件: spm_model.model")
print(f"词汇表文件: spm_model.vocab")

运行结果:

SentencePiece模型信息:
词汇表大小: 8046
特殊标记ID: {'[PAD]': 3, '[UNK]': 4, '[CLS]': 5, '[SEP]': 6, '[MASK]': 7}处理文本: 自然语言处理很有趣!
Tokens: ['▁自然语言', '处理', '很有趣', '!']
IDs: [2474, 129, 2034, 7887]
Decoded text: 自然语言处理很有趣!批量处理结果:文本 1: 自然语言处理是人工智能的重要领域。Tokens: ['▁自然语言处理是人工智能', '的重要领域', '。']IDs: [3090, 2570, 7683]Sequence length: 3文本 2: 机器学习是实现人工智能的一种方法。Tokens: ['▁', '机器', '学习', '是', '实现', '人工智能的', '一', '种', '方', '法', '。']IDs: [7415, 1571, 476, 7528, 378, 6694, 7523, 0, 7699, 7581, 7683]Sequence length: 11文本 3: 深度学习是机器学习的一个分支。Tokens: ['▁', '深度', '学习', '是', '机器', '学习', '的', '一个', '分', '支', '。']IDs: [7415, 997, 476, 7528, 1571, 476, 7462, 701, 7447, 0, 7683]Sequence length: 11包含未知词汇的处理结果:
原文本: 这是一个包含罕见词汇的句子:🦙
Tokens: ['▁这', '是', '一个', '包含', '罕', '见词汇', '的', '句', '子', ':', '🦙']
IDs: [1449, 7528, 701, 4228, 0, 1936, 7462, 7828, 7727, 7436, 0]词汇表示例 (20):<unk>: 0<s>: 1</s>: 2[PAD]: 3[UNK]: 4[CLS]: 5[SEP]: 6[MASK]: 7**: 8--: 9en: 10|: 11se: 12to: 13te: 14re: 15#: 16=: 17**: 18"": 19模型信息已保存到 spm_model_info.json
模型文件: spm_model.model
词汇表文件: spm_model.vocab

3. 基础应用

3.1 选择指南

任务/语言推荐分词器理由词汇表示示例
英文NLUWordPiece与BERT兼容,适合掩码任务[“play”, “##ing”]
多语言SentencePiece语言无关,统一处理[“大”, “模型”]
代码生成BPE处理符号和标识符好[“def”, “_”, “func”]
低资源语言SentencePiece无需分词器[“த”, “மி”, “ழ்”]
长文本BPE (大词汇表)减少序列长度[“transform”, “##ers”]

常见误区

误区事实解决方案
“词汇表越大越好”过大词汇表增加计算负担根据任务平衡词汇量
“分词器不影响性能”分词直接影响模型表现专门优化分词器
“中文需要特殊分词”SentencePiece统一处理使用无空格分词
“分词只需训练一次”领域变化需重新优化定期评估和调整
“特殊标记不重要”影响任务性能根据任务定制

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/95544.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/95544.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

推荐一个论文阅读工具ivySCI

1.一些关于ivySCI的数据 &#xff08;摘自&#xff1a;吴焱红&#xff0c;论文示范:ivySCI 在论文管理、阅读和笔记中的体验&#xff09; 1.科研人员花在文献阅读上的时间占总工作时间的 23%2.每年阅读的文献数量大概是 188 到 280 篇3.ivySCI 提供 Pad(iPad 和 Android) 和桌…

诊断服务器(Diagnostic Server)

在《SWS_Diagnostics.pdf》中,诊断服务器(Diagnostic Server) 是诊断管理(DM)的核心执行单元,聚焦 “软件集群(SoftwareCluster)级诊断资源的独立管控”,实现 UDS(ISO 14229-1)与 SOVD(ASAM 服务化诊断)的全流程诊断功能。以下结合文档 7.3 节 “Diagnostic Serve…

如何开发一款高稳定、低延迟、功能全面的RTSP播放器?

一、引言&#xff1a;RTSP的价值与挑战 RTSP&#xff08;Real-Time Streaming Protocol&#xff09;作为实时流媒体传输的核心协议&#xff0c;广泛应用于安防监控、无人机回传、教育互动、远程医疗、单兵指挥等行业。它提供了 基于请求/响应机制的流媒体控制能力&#xff0c;…

数据结构——树(03二叉树,与路径有关的问题,代码练习)

文章目录一、求二叉树的值【层序遍历实现】1.1右视图1.2层最大值1.3层和1.4最底层的叶子结点的和1.5层平均值1.6最大层和的层号二、二叉树的路径2.1根节点到叶子节点&#xff0c;二叉树的路径2.2路径的十进制之和 & 二进制之和2.3二叉树里的路径三、二叉树的路径23.1最长同…

Git配置:禁用全局HTTPS验证

文章目录Git配置&#xff1a;禁用全局HTTPS验证什么是HTTPS验证&#xff1f;为什么需要禁用HTTPS验证&#xff1f;如何禁用全局HTTPS验证&#xff1f;注意事项结论Git配置&#xff1a;禁用全局HTTPS验证 在软件开发和版本控制中&#xff0c;Git是一个不可或缺的工具。它帮助开…

【54页PPT】基于DeepSeek的数据治理技术(附下载方式)

篇幅所限&#xff0c;本文只提供部分资料内容&#xff0c;完整资料请看下面链接 https://download.csdn.net/download/2501_92796370/91778320 资料解读&#xff1a;《基于DeepSeek的数据治理技术》 详细资料请看本解读文章的最后内容。 作为数据治理领域的资深研究者&#…

2025年最新 unityHub游戏引擎开发2d手机游戏和桌面游戏教程

设置开发编辑器 &#xff1a; 以下是一个简化版的移动控制代码&#xff0c;不依赖自定义输入配置&#xff0c;直接使用 Unity 新输入系统的默认绑定&#xff0c;并兼容手机端的 Joystick Pack 虚拟摇杆&#xff1a; SimplePlayerMovement using UnityEngine; using UnityEngi…

SuperMap GIS基础产品FAQ集锦(20250901)

一、SuperMap iDesktopX 问题1&#xff1a;咨询MapGIS数据迁移功能是否支持MapGIS 10版本&#xff0c;在迁移10版本的符号库时卡在0%并报错“升级6x系统库失败”。 11.3.0【问题原因】客户使用问题&#xff0c;mapgis6.7里面工程文件和符号库之前没有绑定关系&#xff0c;mapgi…

react-native-reanimated-carousel的API记录

‌核心属性‌‌data‌类型: Array<any>必填&#xff0c;轮播数据源&#xff0c;支持任意类型数据。‌renderItem‌类型: ({ item, index }) > React.ReactNode必填&#xff0c;自定义卡片渲染函数&#xff0c;参数包含当前项和索引。‌width/height‌类型: number控制…

TypeScript 泛型入门(新手友好、完整详解)

目标读者&#xff1a;刚学 TS 的前端开发者&#xff0c;或希望把泛型用到实际工程&#xff08;请求封装、组件复用&#xff09;中的同学。目录 为什么需要泛型&#xff08;直观动机&#xff09;基本语法与例子&#xff08;函数、接口、类&#xff09;泛型约束&#xff08;exten…

Linux ARP老化机制/探测机制/ip neigh使用

文章目录1. ARP状态机1.1 ARP状态类型1.2 状态转换图2. 超时时间与参数2.1 主要超时参数2.1.1 基础时间参数2.1.2 探测相关参数2.1.3 垃圾回收参数3. 主机发送ARP报文的时机3.1 发送数据包时发现ARP缓存中没有目标IP的MAC地址3.2 ARP条目进入STALE状态后需要发送数据3.3 定期维…

便携式显示器怎么选?:6大关键指标全解析

地铁通勤路上&#xff0c;程序员小陈掏出背包里的便携屏&#xff0c;连接手机即刻扩展出第二个工作窗口&#xff0c;赶在上班前修复了紧急bug&#xff1b;咖啡厅里&#xff0c;设计师阿琳用笔记本加便携屏的双屏组合&#xff0c;一边参看客户brief一边修改方案&#xff0c;效率…

以太坊网络

以太坊的网络和以太网是两个完全不同的概念&#xff0c;虽然它们的名称听起来相似&#xff0c;但它们属于不同的技术领域。以太坊网络&#xff08;Ethereum Network&#xff09;领域&#xff1a;区块链和加密货币定义&#xff1a;以太坊是一个开源区块链平台&#xff0c;支持智…

nano banana官方最强Prompt模板来了!六大场景模板详解

最近&#xff0c;相信大家的朋友圈和社交媒体都被一个叫nano banana的AI图像生成工具刷屏了。从精致的手办模型到名画人物穿越&#xff0c;再到中土世界场景还原&#xff0c;nano banana已然成为了最火爆的“整活工具之一。刚刚&#xff0c;谷歌为nano banana推出了官方Prompt模…

LeetCode 2825.循环增长使字符串子序列等于另一个字符串

给你一个下标从 0 开始的字符串 str1 和 str2 。 一次操作中&#xff0c;你选择 str1 中的若干下标。对于选中的每一个下标 i &#xff0c;你将 str1[i] 循环 递增&#xff0c;变成下一个字符。也就是说 ‘a’ 变成 ‘b’ &#xff0c;‘b’ 变成 ‘c’ &#xff0c;以此类推&a…

【无人机】1.编译betaflight和cleanflight的固件

在2023年&#xff0c;betaflight腾飞&#xff0c;而cleanflight已经结束更新&#xff0c;但是用cleanflight的原因是因为他最后版本支持stm32f103系列。不用betaflight因为手头还没有f405和f411&#xff0c;只有一个不支持的f407和f401&#xff0c;所以。。接下来开始步骤&…

刻意练习理论

刻意练习理论 一、理论概述 刻意练习&#xff08;Deliberate Practice&#xff09;是由心理学家安德斯艾利克森&#xff08;Anders Ericsson&#xff09;提出的一种系统化学习方法&#xff0c;核心观点是卓越并非源于天赋&#xff0c;而是通过针对性训练获得。其理论基础来自对…

【FastDDS】Layer DDS之Domain ( 04-DomainParticipantFactory)

Fast DDS 域参与者工厂&#xff08;DomainParticipantFactory&#xff09;详解 一、域参与者工厂&#xff08;DomainParticipantFactory&#xff09;基础定义 域参与者工厂&#xff08;DomainParticipantFactory&#xff09;的唯一作用是实现域参与者&#xff08;DomainPartici…

树莓集团建数字产业学院:产教融合强化成渝人才链与产业链衔接

成渝地区双城经济圈建设是国家重大发展战略&#xff0c;而人才链与产业链的有效衔接&#xff0c;是推动成渝地区产业高质量发展的关键。树莓集团顺应时代发展需求&#xff0c;搭建数字产业学院&#xff0c;以产教融合为纽带&#xff0c;不断强化成渝人才链与产业链的衔接&#…

在 ASP.NET 8 WebAPI 中使用不同的提供程序验证多个令牌(Token)及常见问题解答

介绍作为 ASP.NET 框架的最新版本&#xff0c;ASP.NET 8提供了强大的功能&#xff0c;可用于构建安全且可扩展的 Web API。API 开发的一个关键方面是身份验证&#xff0c;它确保只有授权用户或服务才能访问受保护的资源。在本文中&#xff0c;我们将探讨如何在 ASP.NET 8 API 中…