序列模型(语言的Transformer)以及常见的vit , 他们的整个流程分别是什么样的?每一个步骤的简单代码实现

张开发
2026/4/19 14:52:23 15 分钟阅读

分享文章

序列模型(语言的Transformer)以及常见的vit , 他们的整个流程分别是什么样的?每一个步骤的简单代码实现
文本变成 token embedding是怎么做的文本先变成token idtoken id再去查一个 embedding 表取出对应向量不是一步直接从字符串变成高维向量。第一步文本变成 token id这一步叫tokenization。例如一句话text我喜欢苹果tokenizer 不会直接把整句当一个 token它会先切成若干小片段。切法取决于 tokenizer本质上是“按词表匹配”。可能切成这样tokens[我,喜欢,苹果]然后每个 token 去词表里找编号vocab{我:10,喜欢:25,苹果:39,unk:0,}ids[vocab.get(t,vocab[unk])fortintokens]# [10, 25, 39]再变成张量input_idstorch.tensor([ids])# shape: [1, 3]# 1 是 batch_size# 3 是序列长度 Tinput_ids[10,25,39]这里面可能还会带特殊 token比如BOS句子开始EOS句子结束PAD补齐长度UNK词表里没有的 token第二步token id 变成 token embedding这一步本质上就是“查表”。先有一个 embedding 矩阵embednn.Embedding(num_embeddingsvocab_size,embedding_dimdim)如果vocab_size 50000dim 768那么这个 embedding 表的形状就是embed.weight.shape[50000,768]意思是共有50000行每一行对应一个 token 的 768 维向量如果输入是input_idstorch.tensor([[10,25,39]])# shape: [1, 3]那么xembed(input_ids)# shape: [1, 3, 768]这一步等价于x0embed.weight[10]# 我 的向量x1embed.weight[25]# 喜欢 的向量x2embed.weight[39]# 苹果 的向量然后把它们按顺序堆起来xtorch.stack([x0,x1,x2],dim0)# [3, 768]如果加上 batch 维就是# [1, 3, 768]所以一句话token embedding 用 token id 去 embedding 矩阵里取对应那一行1. 语言 TransformerGPT 类整体流程文本 - token ids - token embedding 位置编码 - 多层 causal self-attention MLP - vocab logits - next-token loss文本变成 token id# 伪代码textACGT is a sequenceinput_idstokenizer.encode(text)# [T]input_idstorch.tensor([input_ids])# [B, T]token embeddingtok_embednn.Embedding(vocab_size,dim)xtok_embed(input_ids)# [B, T, D]位置编码postorch.arange(x.size(1),devicex.device)# [T]pos_embednn.Embedding(max_len,dim)xxpos_embed(pos)[None,:,:]# [B, T, D]线性映射出 Q/K/Vqkvnn.Linear(dim,dim*3)(x)# [B, T, 3D]q,k,vqkv.chunk(3,dim-1)拆成多头qq.view(B,T,H,Dh).transpose(1,2)# [B, H, T, Dh]kk.view(B,T,H,Dh).transpose(1,2)vv.view(B,T,H,Dh).transpose(1,2)计算 attention 分数score(q k.transpose(-2,-1))/(Dh**0.5)# [B, H, T, T]后面就是输入进Transformer模型2. 常见 ViTvanilla ViT整体流程图像 - patchify - patch embedding 位置编码 - 多层 self-attention MLP - cls token / mean pool - 分类头输入图像imagestorch.randn(B,3,224,224)# [B, C, H, W]切 patch最常见做法其实不是手写切块而是直接用Conv2d(kernelstridepatch_size)。patch_embednn.Conv2d(3,dim,kernel_size16,stride16)xpatch_embed(images)# [B, D, 14, 14]展平成 patch token 序列xx.flatten(2).transpose(1,2)# [B, N, D]# 这里 N 14 * 14 196加 cls tokenclsnn.Parameter(torch.zeros(1,1,dim))cls_tokencls.expand(B,-1,-1)# [B, 1, D]xtorch.cat([cls_token,x],dim1)# [B, N1, D]加位置编码pos_embednn.Parameter(torch.zeros(1,N1,dim))xxpos_embed线性映射出 Q/K/Vqkvnn.Linear(dim,dim*3)(x)q,k,vqkv.chunk(3,dim-1)下一步就是输入进Transformer3. 两者最本质的对应关系你可以直接这样记语言 Transformer 的输入 token 是“分词后的离散 id”。xtok_embed(input_ids)pos_embed(pos)ViT 的输入 token 是“图像 patch”。xpatch_embed(images).flatten(2).transpose(1,2)xxpos_embed两者中间主干几乎一样都是xxAttention(LN(x))xxMLP(LN(x))最大区别在 mask语言模型scorescore.masked_fill(~causal_mask,-inf)ViT# 通常不加 causal maskattnsoftmax(score)输出头不同语言模型logitslm_head(x)# 每个位置都预测下一个 tokenViTlogitscls_head(x[:,0])# 整张图输出一个类别

更多文章