从 Tokenizer 到 Next Token,穿透数据流,完整理解 Transformer 底层逻辑

很多人啃大模型技术的时候都会陷入同一种困境,单独看每个模块都能看懂,背得出缩放点积注意力公式,能说出多头注意力、残差连接、层归一化这些名词,但只要把整个链路串起来,就会出现断层。如果我们抛出一个简单的问题,把一句提示词送入大模型,从文字输入,到模型输出下一个汉字,中间张量到底经历了怎样完整的变换过程,很多人依然无法完整复现整条计算链路。

市面上绝大多数教程都习惯先抛出架构大图,把所有组件一次性摊开,然后逐个拆解名词。这种学习方式最大的弊端,就是把一套连续运行的计算系统切割成一堆孤立知识点。我们记住了名词,却不知道每个模块在数据流里承担的职责,不清楚张量维度在每一步如何变化,更无法理解为什么这些组件要按照当前的顺序组合在一起。

这篇文章将换一种思路,我们全程跟随一句文本,顺着真实数据流往前走,从原始自然语言文本切入,走完分词、向量映射、位置编码、多层Transformer Block迭代,直到最后输出概率分布采样出下一个token。我们不仅会覆盖基础原理,还会进一步深挖很多入门教程刻意回避的工程细节,比如BPE训练过程的边界问题、RoPE相对位置编码替代正弦位置编码的底层原因、GQA分组注意力的设计动机、RMSNorm与LayerNorm的差异,以及预训练阶段并行计算与自回归推理之间的矛盾。所有原理都会绑定张量形状变化来讲解,搭配伪代码片段,方便后续阅读开源模型源码。

模型接收不到文字,它只能处理离散数字与张量

人类阅读"中国的首都是",大脑直接理解句子语义、主谓关系与背后的事实知识。但神经网络本质只是大量矩阵乘法与非线性函数堆叠而成的计算图,它没有办法直接读取汉字、字母、标点符号。所有文本输入,第一步必须完成符号到数字的转换,这个转换单元就是Tokenizer。

很多初学者会下意识把token等同于单词或者单个汉字,这是最常见的认知误区。token是模型词表里面最小的离散处理单元,它可以是单个汉字,一个完整词语,单词的词根片段,标点符号,空格,数字片段,甚至是代码里的括号、运算符。

举个中文分词示例:

text 复制代码
原始文本:中国的首都是
BPE分词结果:["中国", "的", "首都", "是"]
Token ID序列:[3421, 125, 8745, 296]

看到这个例子,我们自然会产生疑问,为什么不直接按单个汉字切分,变成"中","国","的","首","都","是"?如果拆分成单字,词表构建简单,逻辑直观,但是会带来严重的计算开销问题。假设一句话拆得更碎,token序列长度L就会变长。自注意力的时间复杂度是O(L2)O(L^2)O(L2),序列长度翻倍,注意力计算量会变成原来四倍。长序列下显存占用、矩阵乘法耗时都会急剧膨胀。

反过来,我们也不能无限扩大token的粒度,直接把所有词语、短语全部放进词表。现实世界的文本组合几乎是无限的,不断涌现新词汇、专业术语、网络名词。如果词表收录每一个独立短语,词表规模会膨胀到千万甚至上亿级别,Embedding矩阵的参数量会大到无法存储,训练成本完全不可接受。

现代大模型普遍采用BPE字节对编码,也就是Subword子词分词方案。这套方案的核心思路很朴素,统计训练语料里面相邻字符的共现频率,不断合并高频相邻单元,低频词汇保留拆分能力。

BPE的训练过程并不是学习语义,它只做字符层面的统计。初始状态下,训练语料全部拆分为单个字节或者单个字符,之后反复执行下面流程:

python 复制代码
# BPE训练简化伪代码
def train_bpe(corpus, vocab_size_target):
    vocab = init_vocab_by_chars(corpus)
    while len(vocab) < vocab_size_target:
        # 统计所有相邻字符对的出现频次
        pair_counts = count_adjacent_pairs(corpus)
        best_pair = max(pair_counts, key=pair_counts.get)
        # 合并频次最高字符对,加入词表
        corpus = merge_all_occur(corpus, best_pair)
        vocab.add(best_pair)
    return vocab

在中文语料里面,大量句子反复出现"人工"这个相邻字符组合,BPE训练时就会把"人"和"工"合并为一个token。后续训练又发现"智能"高频共现,继续合并。最终词表里面会同时保留"人""工""智""能""人工""智能""人工智能"多个条目。很多人看到这个结果会误以为BPE读懂了词语含义。这里必须澄清,BPE没有任何语义理解能力,它仅仅捕捉字符串共现统计规律。只是人类语言本身存在规律,稳定概念通常对应稳定的文字表达,高频短语才会被合并为子词token。遇到生僻新词时,BPE会自动拆分为更小的基础单元完成编码,保证任何输入文本都可以被词表覆盖,不会出现未知token。

Tokenizer输出的Token ID只是一串没有大小关系的编号。ID等于3421的"中国",并不代表它的权重高于ID等于125的"的"。单纯依靠整数编号无法送入神经网络做向量运算,我们需要Embedding层完成第二步转换,把离散ID映射为连续高维向量。

Embedding层:可训练的巨型查表矩阵,语义在训练过程中逐步生长

Embedding本质就是一个巨大的二维可训练矩阵,矩阵维度为[vocab_size, hidden_dim]。vocab_size代表词表总token数量,hidden_dim是模型主干隐藏层维度。比如一个词表10万,隐藏层维度4096的模型,Embedding矩阵形状就是[100000, 4096]。

前向过程的逻辑就是查表,输入token ID,取出矩阵对应行向量,作为这个token的初始向量表示。

python 复制代码
# Embedding前向简化伪代码
embedding_matrix = torch.nn.Embedding(vocab_size, hidden_dim)
token_ids = torch.tensor([3421, 125, 8745, 296])
token_embeds = embedding_matrix(token_ids)
print(token_embeds.shape) # [seq_len, hidden_dim]

模型初始化阶段,Embedding矩阵全部用随机值填充。此时向量没有任何语义信息,"猫"和"狗"对应的向量没有任何关联。经过海量文本预训练之后,模型在学习预测下一个token的任务中不断反向传播更新Embedding矩阵参数。当"猫""狗"大量出现在相似上下文,比如"养了一只X""X是宠物",两个token对应的向量会在高维空间慢慢靠近。

这里有一个非常关键,却经常被忽略的知识点。Embedding向量只是token最基础的初始表征,丰富复杂的语义并不是全部存储在Embedding矩阵之中。随着文本向量逐层经过Transformer Block,每一层都会根据上下文动态调整hidden state,深层隐藏向量承载的上下文语义,远比原始Embedding丰富。同一个token,放在完全不同的句子里,经过多层变换之后,最终的hidden state会差异巨大,这就是上下文嵌入的核心思想。

当我们一次性处理多条样本,就会引入Batch维度,也就是大模型源码中随处可见的张量形状[B, L, D]。

  • B:batch size,单次并行处理样本数量
  • L:sequence length,单条样本token序列长度
  • D:hidden dimension,隐藏层向量维度

举个实例,batch size等于8,每条序列512个token,隐藏维度4096,张量形状就是[8, 512, 4096]。从Tokenizer输出的[B, L] token id矩阵经过Embedding查表之后,直接转换为[B, L, D]张量,后续Transformer全部计算都围绕这个三维张量展开。

位置编码:自注意力本身没有时序概念,必须手动注入位置信息

自注意力机制在计算token之间关联的时候,只依靠向量之间的点积,它本身无法感知token的先后顺序。两组完全相同的token集合,调换顺序之后,原始token向量完全一致。比如"我喜欢你"和"你喜欢我",分词之后token集合完全一样,如果不增加位置信息,模型会判定两个句子等价,语义完全丢失。

原始Transformer论文使用正弦余弦位置编码,也就是Sinusoidal Positional Encoding。它的设计思路是使用多组不同频率的正弦余弦函数,为序列每一个位置生成专属向量。可以类比钟表,不同频率波形组合,就可以唯一标记序列位置。低频分量捕捉长距离位置差异,高频分量区分相邻token。

正弦位置编码有一个优势,支持外推,训练时没有见过的更长位置,依然可以通过三角函数公式计算位置向量。但现在主流开源模型LLaMA、Qwen都放弃了原始正弦位置编码,转而采用RoPE旋转位置编码。这是工程演进里面很重要的一步,很多入门教程不会深入解释。

RoPE不再直接把位置向量加到token embedding上,而是在计算Q、K向量的时候,对向量做复数空间旋转操作。位置信息直接编码进QK点积的计算过程。两个token的相对距离,直接体现在点积结果里面。RoPE天然建模相对位置,长文本外推能力更好,在长上下文场景下衰减更加平缓,这也是现代大模型普遍选用RoPE的根本原因。

位置信息和token embedding相加之后,我们得到融合位置信息的输入张量X,形状依旧保持[B, L, D]。接下来就要进入自注意力模块,生成Q、K、V三组向量。

QKV投影与缩放点积注意力,模型学习的是关系匹配,不是单纯语义相似

很多初学者死记硬背QKV定义,却不理解为什么要把同一个输入张量投影成三组向量。我们可以用一句话概括三者职责:

Q是当前token提出的查询,代表我想要寻找什么信息;

K是所有token对外暴露的检索索引,代表别人凭借什么条件匹配到我;

V是匹配成功之后,最终要传递出去的内容信息。

Q、K、V全部来自输入张量X,通过三组独立可训练线性投影矩阵得到。并不是三组不同的输入,只是同一个token表征映射到三个独立特征空间。

python 复制代码
# QKV线性投影伪代码
Wq = nn.Linear(D, D)
Wk = nn.Linear(D, D)
Wv = nn.Linear(D, D)
Q = Wq(X)
K = Wk(X)
V = Wv(X)

注意力打分核心是Q和K做点积,衡量Q向量与每个K向量的匹配程度。向量方向越接近,点积数值越大。但要明白,这种匹配不是向量天然自带的属性,是模型在海量预训练数据中训练出来的。模型不断调整Wq、Wk参数,让存在强关联关系的token对应的QK点积更高。

这里还有一个极易踩坑的认知误区,注意力学习的匹配关系不等于语义相似。举个例子,句子"小明吃苹果",token"吃"对应的Q向量,会和"苹果"的K向量产生很高点积。"吃"和"苹果"并不是近义词,二者是谓语和宾语的句法关系。注意力头学到的是token之间的关联关系,可以是主谓、动宾、时间修饰、指代等等,词语同义只是众多关系类型里面的其中一种。

得到QK点积分数之后,需要除以dk\sqrt{d_k}dk ,也就是缩放操作,对应缩放点积注意力里面的Scaled。dkd_kdk是单个Q/K向量维度。高维向量点积求和之后,数值方差会随着维度变大而膨胀。数值过大送入softmax函数,输出分布会极度尖锐,权重集中在极少数位置,梯度消失,训练难以收敛。缩放操作用来稳定数值分布,保证训练稳定。

Softmax把打分转换成0到1之间的权重,所有权重总和等于1。权重代表当前token分配给上下文每个token的信息关注度。权重再和V向量加权求和,得到自注意力输出。整个过程分为两步,QK负责检索匹配,确定关注哪些token,V负责传递信息,检索完成之后提取对应内容。可以类比数据库检索,Q是检索语句,K是数据表索引,V是数据库存储的真实数据。

Causal Mask因果掩码,在并行训练下实现自回归约束

GPT这类Decoder-only架构模型,核心任务是自回归文本生成,预测当前位置之后的下一个token。训练阶段,我们送入完整句子,不能让预测位置看到未来token,不然答案直接泄露,模型无法学到有效预测能力。Causal Mask就是用来屏蔽未来位置的机制。

掩码矩阵是上三角矩阵,未来位置填充负无穷。负无穷送入softmax之后,对应的注意力权重归零。

python 复制代码
# Causal Mask构建伪代码,下三角保留,上三角屏蔽
seq_len = 5
mask = torch.triu(torch.full((seq_len, seq_len), float("-inf")), diagonal=1)

以序列中国 的 首都 是 北京举例,位置4的token"是",只能看见位置0到位置4,不能看到位置5的"北京"。每一个位置只能访问自身以及前文token。

Causal Mask最精妙的工程价值,是实现并行训练。传统RNN类序列模型,必须按时间顺序逐个计算,无法并行。而Transformer训练时一次性送入完整长序列,依靠掩码屏蔽未来信息,一次性计算全部位置的预测损失。单轮前向传播,同时完成多个预测任务,训练效率得到数量级提升。这也是Transformer能够支撑超大模型预训练的关键设计。

多头注意力与GQA演进:多组独立特征空间建模多样化关系

单一注意力头只能学习一套注意力权重分布,但自然语言一句话里面存在多种类型关系。比如一句话"小明昨天在学校吃了一个苹果",动词"吃"需要同时关联主语小明,宾语苹果,时间状语昨天,地点学校。单头注意力无法同时建模多种独立关系。

多头注意力将QKV切分成多个头,每个头拥有独立的投影子空间,各自独立计算注意力。每个头会自发学习不同类型token关联模式,有的头偏向句法主谓关系,有的头捕捉指代,有的头擅长长距离依赖。模型不会人为指定每个头负责哪一类任务,所有模式都是在预训练过程中自发涌现。

多头计算完成之后,把每个头输出向量拼接在一起,再通过线性层融合,把多个独立空间提取的信息合并回主干维度。

python 复制代码
# 多头注意力维度变化示例
# Q shape [B, L, D] -> split to heads [B, H, L, Dh]
# H=32头,D=4096,Dh=128
Q = Q.reshape(B, L, H, Dh).transpose(1,2)
# 每个头独立计算注意力
attn_out = scaled_dot_product_attention(Q,K,V,mask)
# 合并多头结果
attn_out = attn_out.transpose(1,2).reshape(B, L, D)

当模型规模持续扩大,头数量增加,K、V矩阵存储开销会快速上涨。GQA分组查询注意力就是在多头基础上做的工程优化,用来平衡推理显存开销与模型能力。GQA把多个Q头共享同一组K、V,相比标准MHA多头注意力,大幅降低KV缓存占用,现在LLaMA2、Qwen等主流大模型都采用GQA。很多学习资料只介绍MHA,忽略GQA,而阅读现代模型源码一定会遇到这个模块。GQA不是推翻自注意力底层原理,只是一种兼顾性能与显存的工程变体。

残差连接、归一化,让几十层深度网络稳定训练

自注意力输出之后,并不会直接替换原始输入向量,而是残差连接,输入X加上注意力模块输出。残差的核心思想,不是每一层彻底替换原有表征,而是在原有信息基础上增量修正。每一个Transformer Block,都是在前一层的hidden state之上补充新提取的上下文信息。

残差连接的梯度特性是它能够支撑深层网络堆叠的根本。梯度回传时存在一条恒等映射通路,即使中间注意力与FFN分支梯度衰减,梯度依然可以沿着残差主干顺利回传,解决深度网络梯度消失问题。

经过矩阵乘法、非线性变换之后,张量数值分布会持续漂移。层数堆叠越多,数值偏移越严重,模型训练难以收敛。归一化模块负责稳定每一个token内部特征分布。原始Transformer使用LayerNorm,对单个token对应的D维向量做归一化,减去均值除以标准差,再引入可学习缩放参数γ和平移参数β。归一化不是强制把特征固定为固定分布,先做标准化稳定尺度,再允许模型学习调整分布。

现在很多大模型改用RMSNorm,RMSNorm省去均值计算,只做均方根归一化,计算量更少,数值稳定性同样优秀。这也是工程迭代的重要变化,RMSNorm在LLaMA系列模型中广泛使用。

现代Decoder-only模型普遍采用Pre-Norm结构,也就是先归一化,再送入注意力和FFN模块,和原始论文Post-Norm顺序不一样。Pre-Norm训练稳定性更好,深层模型更容易收敛,这也是阅读源码必须留意的架构差异。

FFN前馈网络:token内部非线性特征加工,和注意力形成分工

注意力模块负责横向信息交换,token之间互相读取上下文信息。FFN也就是MLP前馈网络,负责单个token向量内部的特征变换,不跨token交互。二者分工明确,注意力做上下文信息收集,FFN做特征空间非线性加工。

标准FFN结构先升维,再激活函数非线性变换,最后降维回到主干hidden维度。常见配置是升维到4倍hidden维度。

python 复制代码
# FFN简化伪代码
class FFN(nn.Module):
    def __init__(self, hidden_dim, expand_factor=4):
        super().__init__()
        up_dim = hidden_dim * expand_factor
        self.w1 = nn.Linear(hidden_dim, up_dim)
        self.w2 = nn.Linear(up_dim, hidden_dim)
        self.act = nn.SiLU()
    def forward(self, x):
        return self.w2(self.act(self.w1(x)))

升维操作,可以理解为把向量映射到更高维特征空间,在高维空间里面组合生成更复杂特征,之后再压缩回原始维度。很多人会疑惑,已经有注意力提取上下文,为什么还需要FFN。如果去掉FFN,模型只能做token之间线性加权,无法构建复杂非线性表征,模型表达能力会严重不足。

注意力模块加上FFN,搭配残差与归一化,共同组成一个完整Transformer Block。Pre-Norm结构的Block计算顺序:

  1. 对输入hidden state执行LayerNorm/RMSNorm
  2. 送入多头自注意力,计算上下文信息
  3. 残差相加,保留原有表征
  4. 再次归一化
  5. 送入FFN,做单token非线性特征加工
  6. 再次残差相加,输出Block结果

Block输出张量形状依旧保持[B, L, D],输入输出维度不变,所以我们可以把Block反复堆叠几十层。每一层都会重新检索上下文关联关系,更新hidden state。浅层更多学习基础语法、局部词语关联,深层构建长距离依赖、事实知识、逻辑关系。经过多层迭代之后,最开始简单的Embedding向量,转变为高度上下文相关的hidden state。

LM Head与Softmax,从隐藏向量映射回词表概率分布

多层Transformer Block计算完成,最后一层输出hidden state,依旧是[B, L, D]张量。这个向量只是高维空间表征,不是可读的文字。我们需要LM Head,把hidden state映射到词表维度,得到每个token对应的Logit分数。

LM Head本质就是线性层,输入维度等于hidden_dim,输出维度等于词表大小vocab_size。

python 复制代码
# LM Head 伪代码
lm_head = nn.Linear(hidden_dim, vocab_size, bias=False)
logits = lm_head(last_hidden_state)
print(logits.shape) # [B, L, vocab_size]

Logit只是原始分数,不是概率,数值范围不受限制。Softmax函数将Logit转换为概率分布,所有概率相加等于1。在"中国的首都是"这个例子中,最后位置对应的概率分布里面,token"北京"概率最高。

推理阶段,我们根据概率分布采样下一个token。最简单策略是贪心采样,直接选取概率最高的token。还有top-k、top-p核采样等策略,用来增加生成文本多样性。拿到token ID之后,送入Tokenizer解码器,把ID还原成人类可读文字"北京"。

自回归生成循环与KV缓存,推理阶段的核心工程优化

得到新生成token之后,模型并不会一次性重新编码全部文本。新token追加到上下文序列末尾,进入下一轮预测,继续预测下一个token,这个过程就是自回归生成Autoregressive。

原始 naive 推理方式,每新增一个token,重新完整计算全部历史token的注意力。序列越长,重复计算开销越大。KV缓存就是为了解决这个问题,推理时把历史token计算好的K、V向量保存下来,新token只需要计算自身Q向量,和缓存好的历史K、V做注意力计算,不再重复计算过往token的K和V。KV缓存是大模型推理加速最核心手段,所有线上部署模型都会启用KV缓存。

训练阶段和推理阶段的计算模式存在巨大差异。训练时一次性输入完整序列,并行计算全部位置损失,不需要KV缓存。推理时只能逐个token生成,依靠KV缓存降低计算成本。很多初学者混淆训练和推理两套计算模式,会产生很多理解障碍。

预训练损失函数,整个模型的学习目标

预训练任务目标非常简单,最大化文本序列的联合概率。把完整文本拆解为条件概率乘积,给定前面所有token,预测下一个token。训练的时候构造输入序列与目标序列,输入是中国 的 首都 是,对应的目标序列是的 首都 是 北京。依靠Causal Mask并行算出所有位置预测logit,和真实目标token做交叉熵损失Cross Entropy Loss。

损失反向传播,更新模型全部参数,Embedding、QKV投影矩阵、多头融合矩阵、FFN权重、归一化参数、LM Head全部参与更新。海量文本反复迭代优化参数,模型慢慢学会语言规律、事实、逻辑等能力。

很多人会产生疑问,模型仅仅学习预测下一个token,为什么能涌现推理、写作、代码能力。本质上自然语言文本本身就编码大量世界知识、逻辑规则。当模型拟合海量文本的下一个token分布,就必须学习语法、事实、因果推理,才能精准预测后续文字。这也是大模型能力涌现的底层来源。

完整张量形状链路复盘,阅读源码前必须吃透这条主线

当我们打开LLaMA、Qwen的PyTorch源码,全程跟着张量形状流动阅读,理解难度会大幅下降。下面给出完整的张量变换链路,示例参数B=8,L=512,hidden_dim=4096,头数H=32,单头维度Dh=128,词表大小100000。

text 复制代码
Input IDs张量                [8, 512]
Embedding查表+位置编码融合    [8, 512, 4096]
QKV线性投影                  [8, 512, 4096]
拆分多头                     Q/K/V -> [8, 32, 512, 128]
注意力分数矩阵               [8, 32, 512, 512]
注意力加权求和输出           [8, 32, 512, 128]
多头合并还原主干维度         [8, 512, 4096]
残差+归一化+FFN升维          [8, 512, 16384]
FFN降维输出                  [8, 512, 4096]
多层Block循环之后得到最终隐藏态 [8, 512, 4096]
LM Head映射到词表维度        [8, 512, 100000]
Softmax得到token概率分布     [8, 512, 100000]

这条链路贯穿整个模型,所有模块操作都只是对张量形状与数值做变换。读懂张量流动,就能跳出名词背诵式学习。

Transformer的持续演进,所有变体都建立在主干框架之上

市面上各种大模型变体,并不是推翻Transformer基础架构,而是在主干基础上做针对性优化。RoPE替代正弦位置编码、GQA优化KV开销、RMSNorm简化归一化、SwiGLU替换传统激活函数,这些都是组件层面改进。还有像Mamba这类结构化状态空间模型,属于全新架构,但当前主流商用大模型依旧基于Decoder-only Transformer。

当你打通从Tokenizer到Next Token完整数据流之后,再去看这些优化方案,就不会把它们当成零散知识点。每一项优化,都可以找到它在整条计算链路里面对应的位置,理解优化要解决的痛点。比如长上下文场景下,会遇到注意力O(L2)O(L^2)O(L2)复杂度瓶颈,于是出现滑动窗口注意力、稀疏注意力等方案,所有优化目标都是降低长序列计算开销。

写在最后

学习Transformer最忌讳的路径,就是一开始死磕公式,孤立记忆每个模块定义,之后再拼凑整个系统。这种学习方式很容易造成"看得懂名词,跑不通数据流"的困境。

更好的学习思路,始终盯住一条主线,一句原始文本送入模型之后,张量一步一步发生了哪些变换,每一步计算的目的是什么,为什么需要这个模块,如果删掉这个组件,模型会出现什么问题。持续带着数据流视角思考,Transformer就不再是一堆孤立数学公式,而是一套连续运行,端到端优化的计算系统。

很多人高估了Transformer底层的复杂度,也低估了数据流串联理解的价值。公式只是工具,真正的核心是理解整套系统如何把人类语言转换为向量,在多层网络里面交换上下文信息,加工特征,最后还原成文字预测。只要这条链路在脑海里面完整跑通,后续阅读开源模型代码,研究各类模型变体,都会顺畅很多。

相关推荐
Rocky Ding*3 小时前
FLUX 3 Image Klein 技术解析:5.6B 生成器、Flow Map 蒸馏与 ComfyUI 可控编辑
论文阅读·人工智能·深度学习·机器学习·aigc·flux·ai-native
a puppy slide 葱5 小时前
ENAS | 基于参数共享的高效神经网络架构搜索
论文阅读·人工智能·深度学习·神经网络·机器学习·计算机视觉·神经网络架构搜索
科创致远5 小时前
中小企业智能体(AI Agent)怎么选?2026年企业级Agent选型指南
大数据·人工智能·深度学习
三少爷的鞋5 小时前
Android 协程实践:为什么不应将 `supervisorScope` 作为常规方案
android
承渊政道6 小时前
【从零开始大模型开发与微调:基于PyTorch与ChatGLM】(从ChatGLM本地化到LoRA、PEFT的高级微调)
android·人工智能·pytorch·chatglm·本地化部署·大模型开发与微调
草上飞95278 小时前
零知识证明能证明算对了,却证明不了没看见
人工智能·深度学习·llm
2401_872418788 小时前
机器学习绪论
人工智能·深度学习·机器学习
2601_962885728 小时前
如何用 Python 判断一只股票“追涨”还是“抄底”更有效?
android·开发语言·python
素师良码8 小时前
第10篇:屏ESD Check内核逻辑驱动代码完整拆解
android