首页
/ Transformers 中的 CANINE:字符级无分词 Transformer 的架构解析与实用指南

Transformers 中的 CANINE:字符级无分词 Transformer 的架构解析与实用指南

2026-09-06 18:57:17作者:温艾琴Wonderful

CANINE(Character Architectures with No tokenization In Neural Encoders)是一种完全跳过分词(tokenization-free)的 Transformer 编码器:它不做 subword / wordpiece 切分,而是直接以「字符」为基本输入单元,在原始 Unicode 码点(codepoint)上完成训练与推理。本文以 docs/source/en/model_doc/canine.md 为骨架,结合本仓库内 configuration_canine.pymodeling_canine.pytokenization_canine.py 的源码实现,讲清它的设计动机、"下采样-上采样"的高效编码管线、配置与分词器细节,并给出可直接运行的嵌入生成与下游微调示例。读完你会掌握如何在多语言、复杂拼写或含噪声文本场景下落地这一无需词表的模型方案。

为什么需要"无分词"的模型

传统预训练语言模型依赖分词器把文本切分成 subword 或 wordpiece。这带来两处天然的痛点:

  • 词表与规则耦合语言:对分词规则复杂或不一致的语言,同一个词在不同拼写/书写系统中切分结果差异很大,OOV 与形态切分错误会沿模型层层放大;
  • 噪声输入脆弱:打字错误、非常规拼写会直接破坏分词结果,进而劣化表征。

CANINE 的答案是"以字符为单位":模型处理的是原始 Unicode 字符序列,不需要词表、不需要语言相关的分词器。理论上对任意书写系统的任意文本都能工作,且天然对拼写噪声更鲁棒。

字符输入的代价是序列显著变长,为此 CANINE 使用了一个关键技巧——输入早期下采样(downsampling):先在浅层编码器中对字符上下文建模,再把相邻若干字符压缩成一个"分子(molecule)"交给深层 Transformer,避免深层网络逐字符处理带来的开销,从而在保留字符级信息的同时维持可接受的训练与推理效率。

原始 CANINE checkpoint 发布在 Google 组织下,模型卡片中的 google/canine-cgoogle/canine-s(见 configuration_canine.py)即为其典型代表。

架构核心:字符编码 → 下采样 → 深层编码 → 上采样

CanineModel 的骨架定义在 modeling_canine.py,前向流程在 forward。与普通 BERT 不同,它在一条序列上串联了 三个 Transformer 编码器 与两条卷积变换:

  1. 字符嵌入(CanineEmbeddings):为每个 Unicode 码点生成向量。由于全 Unicode 共有 1,114,112 个码点,直接建大词表不可行,CANINE 采用 多重哈希嵌入(multiple hashing):把 hidden_size 切分成 num_hash_functions 份 shard,每个 hash 函数用一份独立的 num_hash_buckets 大小的嵌入表。_hash_bucket_tensors 中通过 ((input_ids + 1) * prime) % num_buckets 计算桶号(素数表 _PRIMES 支持最多 16 个 hash 函数),最后把各 shard 的向量 torch.cat 回完整维度,见 modeling_canine.py。位置嵌入与 token type 嵌入随后叠加,经 LayerNorm 与 dropout 输出。
  2. 初始浅层字符编码器(initial char encoder):单层、带块状局部注意力的 Transformer,负责对整条字符序列做轻量上下文建模,见 initial_char_encoder 的构造。局部注意力把长度为 stride 的相邻字符切成块,只允许块内互相 attend(第一位置是否全局 attend 可通过参数开关),把字符级计算的复杂度约束在可接受范围,见 CanineAttention 的局部注意力分支
  3. 字符到分子下采样(CharactersToMolecules):将 [CLS] 单独保留后,对剩余字符序列做 kernel=rate、stride=rate 的 Conv1d(等价于每 downsampling_rate 个字符平均为一个向量),得到长度为 char_seq_len / downsampling_rate 的"分子序列",见 modeling_canine.py
  4. 深层 BERT 编码器(deep encoder):在压缩后的分子序列上运行完整的深层 Transformer(默认 12 层),分子序列比字符序列短 4 倍,这正是效率来源。
  5. 分子到字符上采样:用 _repeat_molecules 把每个分子向量按 downsampling_rate 复制回字符长度(不足一个 window 的余数位置通过重复最后一个分子补齐,见 modeling_canine.py),再与第 2 步的字符编码在特征维拼接,随后用 upsampling_kernel_size 宽的 ConvProjection(Conv1d,kernel=4)把 hidden_size*2 投影回 hidden_size,见 modeling_canine.py
  6. 最终浅层字符编码器(final char encoder):在上采样回字符分辨率的表示上再跑一层全注意力 Transformer,得到逐字符的最终输出 last_hidden_state;同时池化器 CaninePooler 取序列首 token 过 Linear+Tanh 得到 pooler_output(该线性层权重在预训练的 next sentence prediction 目标中训练),见 modeling_canine.py

注意:由于模型输出的是字符级表示,因此 token 分类类任务的标签应理解为"字符类别"而非词级别标签;源码与文档一致指出(ConvProjection 中的注释与 NotImplementedError):按字符位置做 MLM 的 CanineForMaskedLM 目前尚未支持——CANINE 的预训练利用了 MLM 与 NSP 目标,但在本仓库中它主要用于下游任务微调。

配置详解:CanineConfig 参数表

配置类 CanineConfig 定义于 configuration_canine.pymodel_type = "canine"。它完整继承了 BERT 风格公共参数,并新增了 CANINE 特有参数:

参数 默认值 含义
downsampling_rate 4 深层编码器前字符序列的下采样倍率(每 N 个字符 → 1 个分子)
upsampling_kernel_size 4 卷积上采样投影层的核宽,用于把维度从 hidden_size*2 投影回 hidden_size
num_hash_functions 8 多重哈希嵌入所用的哈希函数个数,每个哈希函数各有一份独立嵌入矩阵
num_hash_buckets 16384 哈希桶总数(每个哈希函数嵌入表的行数)
local_transformer_stride 128 初始浅层编码器局部注意力的分块步长/宽度;默认 128 便于 TPU/XLA 内存对齐

公共参数与默认值(同文件 L57-L75):hidden_size=768num_hidden_layers=12(作用于深层编码器)、num_attention_heads=12intermediate_size=3072hidden_act="gelu"hidden_dropout_prob=0.1attention_probs_dropout_prob=0.1max_position_embeddings=16384type_vocab_size=16initializer_range=0.02layer_norm_eps=1e-12;特殊 token id 定义为 pad_token_id=0bos_token_id=0xE000eos_token_id=0xE001——它们正是 tokenization_canine.py 中 Unicode 私有使用区(Private Use Area)的特殊码点。

创建配置与随机初始化模型:

>>> from transformers import CanineConfig, CanineModel
>>>
>>> # Initializing a CANINE google/canine-s style configuration
>>> configuration = CanineConfig()
>>>
>>> # Initializing a model (with random weights) from the google/canine-s style configuration
>>> model = CanineModel(configuration)
>>>
>>> # Accessing the model configuration
>>> configuration = model.config

需注意 downsampling_rate 必须能整除序列各维度;下采样使用 nn.Conv1d(kernel=downsampling_rate, stride=downsampling_rate)CharactersToMolecules),而上采样核宽 upsampling_kernel_size 决定卷积投影每次覆盖的字符窗口数量。

分词器即"字符切分器":CanineTokenizer 原理

CanineTokenizertokenization_canine.py)是一个纯字符切分器,核心逻辑只有两步:_tokenizelist(text) 把文本切分成单个字符,_convert_token_to_idord(token) 把字符转成 Unicode 码点整数。词表空间即为全 Unicode:vocab_size 返回 1114112UNICODE_VOCAB_SIZE),不需要下载任何 vocab.txt 之类的词表文件。

特殊符号被定义在 Unicode 私有使用区内,确保永远不会与真实字符冲突:

特殊码点 说明
PAD 0x0000 [PAD]
CLS / BOS 0xE000 [CLS] / [BOS]
SEP / EOS 0xE001 [SEP] / [EOS]
MASK 0xE003 [MASK]
RESERVED 0xE004 [RESERVED]

因此源码注释中特别强调:不设 [UNK] 类字符——任何码点天然在"词表"内,不存在未知词。分词器还实现/继承并适配了 build_inputs_with_special_tokensget_special_tokens_maskcreate_token_type_ids_from_sequences 等接口(本仓库中借助 token_type_ids_pattern="all_zeros"special_tokens_pattern="cls_sep" 等声明驱动基类的特殊 token 处理逻辑,见 tokenization_canine.py)。单条序列可直接处理任意长度文本;model_max_length 默认 2048,而 CanineConfig.max_position_embeddings16384,超长截断与 padding 在调用分词器时完成。

动手生成句子嵌入:三种调用入口

模型文档展示了用 PipelineAutoModel 生成句嵌入,并指出同样流程也支持命令行入口。以下片段可直接运行(示例文本来自模型文档):

方式一:Pipeline

from transformers import pipeline


pipeline = pipeline(
    task="feature-extraction",
    model="google/canine-c",
    device=0,
)

pipeline("Plant create energy through a process known as photosynthesis.")

方式二:AutoModel(手工码点编码)

import torch

from transformers import AutoModel


model = AutoModel.from_pretrained("google/canine-c", device_map="auto")

text = "Plant create energy through a process known as photosynthesis."
input_ids = torch.tensor([[ord(char) for char in text]])

outputs = model(input_ids)
pooled_output = outputs.pooler_output
sequence_output = outputs.last_hidden_state

这里 ord(char) 手工把每个字符映射为码点——这正是 CANINE"不需要 tokenizer"的体现。若要走命令行方式,本仓库的 transformers CLI 通过子命令组织(见 cli/transformers.py),其中 serve 子命令即可将加载好的模型(含 pipeline)封装为对外推理服务(见 cli/serve.py);仓库内部还提供了把原始 TF checkpoint 转成 PyTorch 的脚本 convert_canine_original_tf_checkpoint_to_pytorch.py

方式三:配合 AutoTokenizer 做批量编码(推荐)

CANINE 可以带 tokenizer 使用,也可以完全不使用。但对于批量推理与训练,模型文档明确建议使用 tokenizer 统一补齐(padding)与截断(truncation),保证 batch 内序列等长:

from transformers import AutoTokenizer, AutoModel

tokenizer = AutoTokenizer("google/canine-c")
model = AutoModel.from_pretrained("google/canine-c")
inputs = ["Life is like a box of chocolates.", "You never know what you gonna get."]
encoding = tokenizer(inputs, padding="longest", truncation=True, return_tensors="pt").to(model.device)

outputs = model(**encoding)

CanineTokenizerCanineConfigCanineModel 等均已注册进 Auto API(见 models/auto/auto_mappings.pymodels/auto/tokenization_auto.pymodels/auto/modeling_auto.py),因此 AutoModel.from_pretrained("google/canine-c")AutoConfigAutoTokenizer 均可直接工作。

下游任务模型清单与输出结构

CanineModel 之外,仓库为常用任务提供了带任务头的模型(全部定义于 modeling_canine.py),Auto 映射覆盖情况如 models/auto/modeling_auto.py 所示:

模型类 任务头部与用法 对应源码
CanineModel 基础编码器:输出逐字符 last_hidden_statepooler_output CanineModel
CanineForSequenceClassification 池化输出上接 Linear 分类头,适用于 GLUE 类任务;num_labels==1 时按回归(MSE)计算损失 modeling_canine.py
CanineForMultipleChoice 输入展平为 (batch, num_choices, seq),池化输出上接 Linear→1 打分后对 choice 维度求交叉熵 modeling_canine.py
CanineForTokenClassification 直接使用逐字符序列输出接分类头;标签按字符而非词计算 modeling_canine.py
CanineForQuestionAnswering 序列输出上接 qa_outputs 预测 span 起止 logits,平均 start/end 交叉熵作为损失 modeling_canine.py
CanineModelOutputWithPooling CANINE 专属输出结构,额外携带浅层/深层三组编码器的 hidden_statesattentions modeling_canine.py

CanineModelOutputWithPooling 与标准 BaseModelOutputWithPooling 略有差异(其自定义 docstring 也说明了这一点):由于模型内部串联了三个编码器,开启 output_hidden_states=True 时返回的 hidden_states 元组包含三组状态,且深层编码器对应的状态长度为 seq_len // downsampling_rate(分子分辨率),浅层编码器对应状态保持字符分辨率。这一特性也被模型测试用例显式校验:test_modeling_canine.py 中按"浅层 2 个输入/输出状态 + 深层 num_hidden_layers+1 个状态 + 最终浅层 2 个状态"的规律断言各层形状(见 tests/models/canine/test_modeling_canine.py)。

分类、多选、token 分类、QA 等模型的输入参数均包含 input_ids / attention_mask / token_type_ids / position_ids / inputs_embeds,与 BERT 家族一致;不同任务仅在 labels 形态上不同(序列级标量、choice 下标、逐字符标签、起止位置对)。CaninePreTrainedModel 声明了 supports_gradient_checkpointing = Truemodeling_canine.py),长序列微调时可通过开启梯度检查点来降低显存占用。

使用注意与最佳实践

  • 可不用分词器,但批量推荐用它:单条输入可手工构造码点张量;批量训练/推理务必用 AutoTokenizer 设置 padding="longest"truncation=True,否则长度不一致会导致 attention_mask 语义错乱、批次内无法对齐。
  • 定位是"微调基座":模型文档明确指出 CANINE 主要面向下游任务微调使用;预训练阶段以 masked language modeling 与 next sentence prediction 为目标。二者不应混淆——当前 PyTorch 实现中面向掩码字符预测的 MLM 任务头尚不支持(对应路径抛出 NotImplementedError)。
  • 长度与效率:单条最长受 max_position_embeddings=16384 约束,默认 model_max_length=2048;超长输入请先截断。字符级输入天然较长,深层编码器只在 1/4 分辨率的分子序列上运行,这是模型在"逐字符 + 高效"之间取得平衡的关键设计。
  • 对齐与正确性downsampling_rateupsampling_kernel_sizelocal_transformer_stride 需与预期序列长度配合使用;默认 128 的 stride 是为了 TPU/XLA 内存对齐而设定,见 configuration_canine.py。分子层注意力掩码由 _downsample_attention_mask 用 MaxPool 对字符掩码做下采样得到(modeling_canine.py)。
  • 验证与转换:仓库在 tests/models/canine 提供建模与分词器的完整测试;若需从 Google 原始 TF checkpoint 转换权重,可参考转换脚本 convert_canine_original_tf_checkpoint_to_pytorch.py

总而言之,CANINE 为"分词规则复杂、噪声多、语种混杂"的文本场景提供了一个词表无关的编码器方案:以全 Unicode 字符为输入、用哈希嵌入控制参数规模、用浅层局部注意力 + 下采样/上采样卷积换取效率,并以三阶段编码器输出既可用于句级任务(pooler_output)也可用于逐字符任务(last_hidden_state)的双分辨率表示。开发者可直接通过 AutoModelAutoTokenizer 按本文示例完成嵌入生成与各下游任务的微调接入。

登录后查看全文
热门项目推荐
相关项目推荐