TabNet架构可视化:用Mermaid绘制特征选择与自监督流程图

发布时间:2026/10/19 10:00:04
TabNet架构可视化:用Mermaid绘制特征选择与自监督流程图 做表格数据建模的人应该都听过 TabNet 这个名字。它最大的特点是把深度学习的表达能力和传统特征选择的可解释性揉在了一起每一步都能看出模型在用哪些特征做决策。可问题也随之而来——它的结构在论文里是那种层层展开的立体示意图自己看懂不难想让别人在组会或博客文章里看明白光靠嘴说很难。所以我整理了一整套可以直接拿来用的 Mermaid 流程图覆盖 TabNet 的主干数据流、单步内部结构、特征选择回路和自监督预训练四个层面。这些图全部用 Mermaid 编写纯文本、可版本管理能直接放进 GitHub README、Typora 或语雀文档里实时渲染。适合这几类人写 TabNet 技术博客需要配图的人、做组会汇报不想临时画 PPT 结构图的人、被论文里 prior 和 mask 那几个符号绕晕想靠图理清思路的人。下面我按从宏观到微观的顺序把每张图都拆开讲一遍。1. 这套流程图画的是什么适合谁用1.1 论文原图很精美但讲解场景下不够用TabNet 论文里的架构图是那种信息密度很高的二维展开图把编码器部分画成一个横向铺开的大结构注意力和特征变换模块层层堆叠。这种图非常适合说明整体有哪些模块但真要对着它讲数据从哪个节点流到哪个节点听众很容易跟不上。原因很简单原图更像一张系统组成图不是数据流图。我这套图换了一个视角全部改成数据流优先的表达方式节点是模块箭头是数据、掩码或先验信息的搬运路径。每一步的输入是什么、输出到哪儿、哪些路径是复用上一轮的结果都靠箭头的走向说清楚。做法和画电路图类似不追求把每个张量维度标出来只把控制流和数据流理清。1.2 四张图的分工与适用场景这套图集一共四张覆盖 TabNet 从模型结构到训练流程的完整讲解链路图名覆盖内容最适用的讲解场景主干数据流图输入、BatchNorm、N_steps 决策步骤、决策输出累加、分类头快速介绍 TabNet 整体长什么样单步内部结构图Feature Transformer、Attentive Transformer、GLU 和 Sparsemax讲清每一步为什么能选特征特征选择回路图掩码 M、先验 P、熵正则与稀疏约束的迭代关系深入解释 TabNet 可解释性的来源自监督预训练图Encoder 与 Decoder 的掩码对应、重构目标、微调流程讲 TabNet 的预训练与无标签数据利用写技术博客的时候我的习惯是先放主干图让读者建立体感再放单步图和回路图解释核心机制最后讲预训练时补上 Encoder-Decoder 的那张图。组会汇报则是反着来先把结论抛出来隐含假设听众已经看过主干图直接进入回路图讲创新点。1.3 为什么不用 draw.io也不用 PPT画神经网络架构图的传统工具是 draw.io 或者 PPT模板丰富、拖拽方便。但我最终选择 Mermaid核心原因是版本管理。流程图这东西在写文章时一定会反复改用 draw.io 导出的 XML 不直观两个版本之间到底动了哪根线很难从 diff 里看出来。Mermaid 是纯文本改动可以被 Git 精确记录同事提 PR 时还能直接在评论里指出某条线应该改成虚线。另一个好处是渲染零成本。代码块一贴GitHub、Typora、语雀都能渲染不需要额外嵌入图片也不用担心图片链接失效。缺点也很明显排版自由度低画不了那种精密到像素级的示意图。所以在画图策略上我做了取舍这类图要表达的核心是数据流转逻辑不是艺术效果Mermaid 反而更合适。2. 主干架构图从输入到预测的整体数据流2.1 先看这张宏观图我一般会把主干图画成纵向布局让从数据输入到最终预测的路径从上到下自然流淌。N_steps 的多个步骤用串行方块表示每一步内部细节先折叠起来只露出决策输出 d 和传入下一步的中间特征 a。flowchart TB X[输入特征 x] -- BN[BatchNorm] BN -- Step1[决策步骤 1br/Feature Transformer Attentive Transformer] Step1 -- D1[决策输出 d1] Step1 -- A1[中间特征 a1] A1 -- Step2[决策步骤 2br/Feature Transformer Attentive Transformer] Step2 -- D2[决策输出 d2] Step2 -- A2[中间特征 a2] A2 -- Dots[... 共 N_steps 步 ...] D1 -- SUM[决策输出累加] D2 -- SUM Dots -- SUM SUM -- FC[全连接层] FC -- OUT[Softmax 预测]2.2 主干上每个环节在干什么先看最前面两根线。输入特征 x 先经过 BatchNorm这一步对表格数据尤其重要。表格特征经常是不同量表混在一起比如一列是年龄、一列是月收入量纲差几个数量级如果不做归一化前几步的特征选择会被大数值特征带偏。BatchNorm 在这里的作用和它在图像模型里一样让每维特征在进入后续复杂变换之前保持稳定的分布。然后是 N_steps 个串行的决策步骤。每个步骤做两件事根据上一步的信息选出一组特征然后用这些特征做一次局部变换。这种选一次、算一次的循环结构是 TabNet 的灵魂。后接的虚线框表示中间特征 a 会流入下一步同时每一步还会单独输出一个决策向量 d。d 不是直接拿去预测的而是所有步骤的 d 先各自通过一个全连接层再把结果逐元素累加最后接 Softmax 输出类别概率。2.3 为什么步骤必须串行N_steps 怎么定串行结构不是为了模仿 Transformer 的层叠设计而是由特征选择逻辑决定的。每一步生成的掩码会抑制已经被前几步选过的特征也就是说后一步在做选择时必须知道前一步已经用掉了哪些特征。如果步骤之间是并行的它们就各自为政无法形成先选主体特征、再补边缘特征的协作效果。关于 N_steps 的取值论文里测试时常用 3 到 5 步我在实际项目里也发现这个区间比较稳。步数太少特征选择太粗暴模型基本退化成普通全连接网络加一个稀疏约束步数太多训练时间线性增长但后期步骤的边际收益很小因为好用的特征已经被前面的步骤占用得差不多了。调参时我一般先固定 N_steps3 跑基线再试 5 对比很少需要走到 8 以上。2.4 图上没画出来的两点提醒第一TabNet 在决策步骤之间没有残差连接。Transformer 里每层输出都会加上输入做残差容易给人留下每一步都应该有跨层捷径的惯性印象。TabNet 的做法是每一步独立产出决策 d最后累加的是经过全连接层后的 logits不是把原始特征直接往后面传。画主干图时我特意没有加任何跨步骤的实线就是为了避免这种误解。第二图中 a1 到 Step2 的这条线不传递原始输入特征它传递的是经过处理后的上下文特征用于下一步生成掩码。真正参与第二步特征选择的原始特征 x 是从 BN 输出端直接引过去的在主干图里我选用虚线来表示掩码的生成关系避免和数据流主线混在一起。这部分如果只看主干图容易误解下一章把单步结构展开后就清楚了。3. 单步内部结构图把每一层算子摆到台面上3.1 单步展开长什么样主干图把每个决策步骤当成一个黑盒这一步要打开黑盒。单步内部的核心是双 Transformer 配合Feature Transformer 负责把选出来的特征加工成可用表示Attentive Transformer 负责根据上一步的上下文生成新的特征掩码。下面这张图是我常用到的单步展开版本flowchart LR SUBMASK[被掩码筛选后的特征] -- FT[Feature Transformer] FT -- SPLIT[按维度切分] SPLIT -- ATOUT[中间特征 a_i] SPLIT -- DOUT[决策输出 d_i] APREV[上一步中间特征 a_{i-1}] -- AT[Attentive Transformer] PRIOR[累积先验 P_{i-1}] -- AT AT -- MASK[特征掩码 M_i] MASK -- MUL[逐元素乘] SUBRAW[归一化后的原始特征] -- MUL MUL -- SUBMASK3.2 Feature TransformerFC、BN、GLU 的组合Feature Transformer 内部不是单层网络而是全连接层 BatchNorm GLU的反复堆叠。其中最关键的是 GLU门控线性单元。它的做法是把输入经过线性变换后扩张成两倍宽度的特征然后一分为二一半直接作为信息流另一半经过 Sigmoid 变成 0 到 1 之间的门控值两者逐元素相乘。信息能不能往前走、走多少由门控值说了算。打个比方这就像一条路上装了红绿灯Sigmoid 生成的 0 到 1 数值就是红绿灯的开放程度信息流是路上的车绿灯全开接近 1时车辆畅通无阻红灯全亮接近 0时直接截停。GLU 让网络在一定程度上自主决定每个维度信息的保留比例表达能力比单纯的线性层强不少。Feature Transformer 里还做了一个在主干图里看不见的参数安排一部分层是跨步骤共享的另一部分是当前步骤独有的。共享层让所有步骤共用同一套基础特征变换能力减少整体参数量步骤独有层则给每一步留下微调空间让它在选用不同特征时能做出略有区别的处理。画归一化图时这两种层我习惯用不同底色区分但 Mermaid 里样式管理比较繁琐如果不想增加视觉负担在文本里标注清楚即可。3.3 Attentive TransformerSparsemax 才是真正的门面Feature Transformer 的输出会沿特征维度切成两部分一部分走中间特征 a 的路线进入 Attention 环节另一部分走决策输出 d 的路线进入累加器。a 在进入 Attentive Transformer 之前还要先和上一轮累积的先验 P 做逐元素相乘然后经过一个由全连接层、BatchNorm、Sparsemax 组成的小模块最终输出的是掩码 M。相比 SoftmaxSparsemax 最大的特点是输出本身就是稀疏的。Softmax 无论输入多极端输出总是所有维度都大于 0Sparsemax 则会在某些维度上直接得到 0。用一个三特征例子说明假设三个特征的得分是 2.0、1.0、0.1Softmax 给出的权重大约是 0.58、0.31、0.11虽然第三个很小但依然参与了后续计算。Sparsemax 处理类似的得分时很可能直接把第三个维度的权重压成 0只保留前两个。这对 TabNet 至关重要因为掩码 M 是逐元素作用于输入特征的。如果掩码处处非零每个步骤实际上还是用到了全部特征所谓特征选择就名存实亡了。Sparsemax 让掩码在训练过程中自然走向稀疏模型才能真正做到这一步不看某些特征。3.4 单步结构图的组件速查表组件输入输出核心作用Feature Transformer掩码筛选后的特征a_i、d_i特征加工与决策输出Attentive Transformera_{i-1}、P_{i-1}掩码 M_i决定哪些特征被选中GLU全连接层输出门控后的特征控制信息流动比例Sparsemax得分向量稀疏权重向量强制掩码稀疏化累加器各步 d_i 的变换结果最终 logits汇总所有步骤的决策4. 特征选择回路的图解掩码、prior 累乘与稀疏约束4.1 先搞懂 Pprior 是怎么一步步累积的TabNet 里最容易让人困惑的是公式里的两个符号 M 和 P。M 是掩码比较好理解就是每个特征在当前步骤被选中的权重。P 是累积先验它有约束后面步骤选择行为的作用。P 的更新方式是同位置逐项累积P_i P_{i-1} 与 (γ - M_i) 的逐元素乘积其中 γ 通常取 1。如果一个特征在前面某一步的掩码值接近 1说明它已经被这一步充分使用那么后续步骤里 (γ - M_i) 这一项就趋近 0先验 P 会被压低继而让后面步骤不敢再选这个特征。反过来如果某特征从来没被选中掩码值接近 0先验就一直是 1后面的步骤依然有充分机会选它。我习惯把这一步单独画成一张回路图因为它是 TabNet 的记忆系统flowchart LR PREV[上一步中间特征 a_{i-1}] -- AT[Attentive Transformer] PRIOR[累积先验 P_{i-1}] -- AT AT -- M[当前掩码 M_i] M -- PUPDATE[更新先验br/P_i P_{i-1} × (γ - M_i)] PRIOR -- PUPDATE PUPDATE -- PNEXT[P_i 进入下一步骤]举个更直观的例子。假设有三个特征 A、B、C第一步掩码输出是 0.9、0.1、0.0意味着模型重点用了特征 A。第二步之前先验更新为 0.1、0.9、1.0因此第二步的注意力模块在生成掩码时面对特征 A 已经被用掉了 90%的现状就有很大概率把选择重心转向 B 和 C。T 步之后每个特征的被选历史都沉淀在先验里整个模型表现出分阶段覆盖特征的行为而不是每一步都重复选同一批特征。4.2 熵正则项让掩码变果断只有 Sparsemax 还不一定能保证良好的稀疏性因为模型可能会选择软稀疏比如每个步骤都温柔地选一半特征。为了进一步鼓励掩码走向极端TabNet 在损失函数里加了一个稀疏正则项本质是对掩码分布的熵惩罚。熵的概念可以简单理解成选择结果的不确定程度。如果掩码是 0.5、0.5分布很不确定熵就高如果掩码是 0.9、0.05、0.05分布明显偏向一个特征熵就低。正则项会惩罚熵高的情况逼着掩码往 0 或 1 两极化方向走这会牺牲一点模型精度但换来更清晰的特征选择可以说是为可解释性付出的代价。调参的时候稀疏系数 λ 太小模型和普通深度网络差别不大λ 太大每一步只敢选很少几个特征特征之间的复杂交互可能学不到。我自己的经验是先从默认值跑通然后观察训练日志里掩码的平均稀疏程度如果每步平均非零特征数不到特征总数的 10%就说明稀疏性可能过头了。4.3 掩码流和数据流在画图时要区分开在 Mermaid 图里我强制自己遵守一条原则数据流用实线箭头掩码和先验流用虚线箭头。原因很简单一张图里同时存在特征向量的流动和权重向量的流动时如果全画成实线读者几乎无法分清哪些线代表信息在变化哪些线代表权重在调节。Mermaid 里--是实线-.-是虚线两者结合可以让掩码的控制属性不言自明。5. 预训练阶段的编码器-解码器图自监督怎么画5.1 Encoder 和 Decoder 不是对称的TabNet 除了常规监督训练还支持自监督预训练专门用来在无标签表格数据上学特征表示。预训练网络由 Encoder 和 Decoder 两部分组成。Encoder 就是前面讲的标准 TabNet 结构。Decoder 的结构则比 Encoder 简单它不再包含注意力模块我画这张图时最想强调的就是这个不对称性。flowchart TB X[原始特征 x] -- RANDMASK[随机生成特征掩码 S] RANDMASK -- XMASK[部分特征被掩盖后的输入] XMASK -- ENC[TabNet Encoder] ENC -- REP[逐步骤特征表示] ENC -- ENCMASK[逐步骤注意力掩码 M_i] REP -- DEC[TabNet Decoder] ENCMASK -- DEC DEC -- XHAT[重构特征 x_hat] X -- LOSS[重构损失] XHAT -- LOSS5.2 随机掩码和注意力掩码是两码事画这张图时最需要解释清楚的是随机生成特征掩码 S和注意力掩码 M这两者的区别。S 是预训练阶段人为制造的噪声做法类似于从 D 个特征里随机选一部分保留其余特征全部置为 0。M 则是 TabNet 内部学出来的注意力权重它表示对特征重要性的判断。一个来自外部注入一个来自模型计算。可以类比成语文考试的完形填空。原始句子像是完好的特征表先人为挖掉几个空这就是 S模型读到被挖空的句子后内部会对每个位置做重要性判断这就是 MDecoder 根据已有的上下文和被判为重要的位置尝试把空格补回去。如果补出来的值和原文足够接近就说明模型从无标签数据里学到了特征之间的关联模式。损失函数只在被掩盖的位置上计算没有被掩盖的特征即使重构错了也不惩罚。原因很明显S 为 0 的位置才是真正考验重构能力的位置其他位置的信息要么原样保留在输入里要么被 Encoder 直接背过去了重构它们没有意义。5.3 Decoder 为什么要复用 Encoder 的掩码Decoder 不生成自己的注意力掩码而是直接复用 Encoder 每一步算出来的 M。这是 TabNet 自监督方案里一个很聪明的设计。如果 Decoder 也带一套注意力模块它在重构时可能会重新选择一组特征来帮助自己这就和 Encoder 的特征选择逻辑脱钩了。复用同一个掩码等于强制 Decoder 在Encoder 认为重要的特征子集上去做重构这样学到的表示天然保留了特征选择的先验。微调阶段的做法是预训练结束后把 Decoder 丢掉Encoder 后面接上分类用的全连接层再用带标签数据正常训练。我经常被问到预训练收益大不大在表格数据上要分情况。如果下游只有几千条有标签样本而手头有几百万条无标签样本预训练带来的提升非常明显。如果标签本身已经足够多预训练更多是锦上添花因为 TabNet 的监督训练本身就包含隐式的特征选择正则。5.4 画这张图的连线细节这张图我踩过一次坑REP 和 ENCMASK 两个输出都连向 Decoder但如果连线没有标注读者会以为 Decoder 有两个独立输入一个特征表示、一个掩码。实际使用中掩码 M 要和每一层的特征表示逐元素相乘后再进入 Decoder 的上层结构。所以我在箭头文字上会标注逐元素乘避免读者理解成把两个张量拼接到一起。6. 对照这些图踩过的 Mermaid 语法坑6.1 节点 ID 和文本的特殊字符Mermaid 的节点 ID 最多只能用字母、数字和下划线建议大家统一用ATT1、FT2、PRIOR1这种风格既好记又好查。节点文本则要注意[、]、{、}这些符号在文本里出现会导致渲染失败。比如我想在节点里写P_i P_{i-1} × (γ - M_i)直接放进去多半会出问题需要给整个文本加双引号或者换一种表达方式例如直接写更新先验然后另起一行放公式。换行在节点文本里要用br/不能直接打回车。我最早画图时习惯用普通的换行分隔多个子标题结果发现在 Mermaid 里只会渲染成空格。6.2 子图和循环结构的组织方式标准 TabNet 有多步结构如果每一步都原样展开图会非常长。我的做法是用subgraph把单步结构包成一个组然后在主图里复制两次分别表示第 1 步和第 2 步再用注释文字说明后续步骤结构相同。需要注意Mermaid 子图里的节点 ID 仍然是全局唯一的所以复制单步结构时必须给每个节点的 ID 加上步骤前缀例如把FT改成FT1和FT2否则渲染时会因为 ID 重复报错。6.3 用 classDef 区分不同类型的流Mermaid 支持classDef批量定义样式。我习惯定义三种颜色数据流模块用一种底色注意力相关模块用一种底色先验更新模块再用一种底色。边的类型里实线--表示数据传递虚线-.-表示掩码或先验传递。实际效果是读者扫一眼就能知道图上哪些是主体计算流程哪些是控制信号。样式代码可以集中放在图末尾classDef dataflow fill:#e1f5e1,stroke:#2e7d32 classDef attention fill:#fff3e0,stroke:#e65100 classDef prior fill:#e3f2fd,stroke:#1565c0 class FT1,FT2,SUM,FC dataflow class AT1,AT2 attention class PRIOR1,PRIOR2 prior6.4 不同渲染环境下的兼容性问题同样的 Mermaid 代码在 GitHub 上渲染正常在 Typora 里显示可能有细微差异。最典型的差异是br/的换行高度以及子图标题的字体大小。如果对效果有严格要求建议先在本地用 mermaid-cli 把图导出成 SVG 或 PNG再放入文档这样效果完全可控。但代价是修改后需要重新导出GitHub 原生渲染则永远是最新代码对应的图。我现在的习惯是博客文章用 GitHub 原生渲染正式汇报的 PPT 里则导出成 SVG 以保证排版统一。画 TabNet 这套图的时候我最大的体会是真正难画的不是 Mermaid 语法而是先想清楚某个张量到底走的是数据流、掩码流还是先验流。只要这个想明白了图的连线自然就合理了。这些图我在多个项目文档里复用每次讲解前都只需要微调节点文本整体结构几乎没变过。如果你也在写 TabNet 相关的内容不妨先画主干、再拆单步按这个顺序来最后成图的可读性会好很多。