svg_tutorial

第 6 章:SVG 结构化表示:从文本到 Token / AST / 图

1. 开篇段落

在构建 SVG 生成模型时,我们面临一个根本性的矛盾:SVG 在存储上是 XML 文本,但在语义上是二维几何图形。 如果我们简单粗暴地使用训练 GPT 的方式(如 Byte-Pair Encoding, BPE)处理 SVG 源码,模型将被迫把大量的算力浪费在学习“< 后面通常跟什么字符”、“3.14159 是一个数字还是一串符号”这些低级语法上,而难以触及“闭合曲线”、“对称性”这些高级几何概念。

本章的核心任务是建立一套高效的中间表示(Intermediate Representation, IR)。我们将深入解构 SVG 的三种主流建模方式:序列化 Token 流(适合 Transformer)、抽象语法树 AST(适合结构理解)和 拓扑图 Graph(适合复杂关系建模)。你将学会如何设计一套“词表”,让神经网络能够直接“说”几何语言,并掌握数据清洗中最关键的“规范化(Canonicalization)”流水线。


2. 文字论述

2.1 为什么标准 NLP Tokenizer 是 SVG 的“毒药”

在自然语言处理中,常用的 Tokenizer(如 WordPiece 或 BPE)是基于词频统计的。这在 SVG 场景下会失效,主要体现在三个方面:

  1. 数字碎片化:SVG 的本质是坐标。对于坐标 d="M 123.45 ...",BPE 可能会将其切分为 ['12', '3', '.', '45']。这导致两个严重后果:
    • 序列膨胀:一个简单的矩形可能变成上百个 Token。
    • 数值语义丢失:模型很难理解 ['1']['0'] 的组合(10)在数值上比 ['9'] 大。
  2. XML 冗余噪声<path stroke="#000000" stroke-width="1" ...> 包含了大量对于几何形状本身“低信噪比”的字符。
  3. 几何隐喻的缺失:在文本层面,M(MoveTo)只是一个字母,但在几何层面,它意味着“抬笔移动”。通用语言模型很难建立这种“字符-动作”的强绑定。

2.2 核心方案:自定义几何 Tokenizer (Geometric Tokenization)

为了解决上述问题,SOTA(State-of-the-Art)模型(如 DeepSVG, IconShop)通常采用专用词表

2.2.1 词表构成 (Vocabulary Design)

一个典型的 SVG Tokenizer 词表通常包含以下几类 Token(总数通常在 500 ~ 2000 之间):

  1. 命令 Token (Command Tokens)
    • 直接对应 SVG Path 指令:<M>, <L>, <C> (三次贝塞尔), <Q> (二次贝塞尔), <A> (圆弧), <Z> (闭合)。
    • Rule-of-Thumb:通常将绝对指令(L)和相对指令(l)分开编码,或者统一转换为一种。
  2. 坐标 Token (Coordinate Tokens)
    • 这是词表的主体。我们将连续的浮点坐标空间映射为离散的整数索引。
    • 例如:定义画布范围 $[0, 255]$,精度为 1(整数)。那么坐标 10.3 对应 Token <10>
    • 如果需要更高精度,可以将范围设为 $[0, 1023]$。
  3. 特殊 Token (Special Tokens)
    • 结构标识:<SOS> (Start), <EOS> (End), <SEP> (Path 分隔符)。
    • 层级标识:<GROUP_START>, <GROUP_END>(如果需要建模 Group)。

2.2.2 序列化策略 (Serialization)

如何将 Path d="M 10 20 L 30 40" 转换为 Token ID 序列?

2.3 AST 与 DOM:处理复杂结构的利器

简单的 Path 序列无法处理复杂的 SVG(如包含 defs, clipPath, mask 的图)。这时我们需要 AST(抽象语法树)

2.3.1 树结构定义

2.3.2 扁平化 (Flattening) vs. 结构保持

在训练生成模型时,我们面临一个权衡:

  1. 完全扁平化 (Full Instantiation)
    • 将所有 <use> 展开为实体。
    • 将所有 <g>transform 矩阵乘到子节点的坐标上,并移除 <g>
    • <rect> 等形状转换为 <path>
    • 结果:一个 SVG 变成了一个纯粹的 <path> 列表。
    • 适用场景Text-to-SVG 生成任务。模型只需要学会画线,不需要学会复杂的 XML 引用逻辑。
  2. 结构保持
    • 保留 Group 和 Transform。
    • 适用场景UI 代码生成、SVG 编辑任务。用户希望保留图层结构,而不是得到一堆打散的路径。

2.4 数据规范化流水线 (The Canonicalization Pipeline)

这是本章最重要的工程部分。如果不进行规范化,模型将无法收敛。一个标准的 SVG 预处理流水线如下:

  1. 清洗 (Sanitization)
    • 移除 script, style (CSS block), metadata
    • 移除不可见元素(display="none", opacity="0", 极小面积的 path)。
  2. 图元转换 (Primitive Conversion)
    • SVG 有多种画矩形的方法:<rect>, <path>, <polygon>
    • 强制统一:将 <rect>, <circle>, <ellipse>, <line>, <polyline>, <polygon> 全部转换为 <path> 数据。
    • 为什么? 减少词表大小,模型只需专注学习 Path 语言。
  3. 变换烘焙 (Transform Baking)
    • 如果 <g transform="rotate(45)"> <path ... /> </g>,通过矩阵运算将旋转应用到 path 的每个坐标点上,然后移除 group 标签。
    • 确保坐标是“最终渲染坐标”。
  4. 视口归一化 (Viewport Normalization)
    • 读取 viewBox="x y w h"
    • 将所有坐标点 $(p_x, p_y)$ 映射到单位空间 $[0, 1]$ 或目标整数空间 $[0, 255]$: \(x' = \frac{x - \text{viewBox}.x}{\text{viewBox}.w} \times 255\)
  5. Path 命令简化
    • 将所有相对命令(l, c)转为绝对命令(L, C),或者反之。
    • H (水平线), V (垂直线) 统一转为 L
    • S, T (简写贝塞尔) 展开为完整的 C, Q
    • 目的:极度简化语法,让模型只面对 M, L, C, Z 四种核心指令。

2.5 图表示 (Graph Representation)

对于需要理解拓扑关系的任务(如:修改流程图的连接、编辑地图),简单的序列表示会丢失空间邻接信息。我们可以构建一个图 $G=(V, E)$:

这种表示法通常配合 GNN (Graph Neural Networks)Graph Transformer 使用。

2.6 几何合法性与约束 (Validity & Constraints)

生成的 SVG 必须是“合法”的。


3. 本章小结


4. 练习题

基础题

1. 变换烘焙 (Transform Baking) 计算 给定一个点 $P(10, 0)$ 和一个父级变换 transform="translate(20, 10) scale(2, 2)"。 请计算变换应用后,$P$ 点的最终绝对坐标。

点击查看提示 SVG 变换顺序通常是从右向左(或者理解为先 Scale 后 Translate)。矩阵公式:$T \times S \times P$。
点击查看答案 先缩放:$x' = 10 \times 2 = 20, y' = 0 \times 2 = 0$。 再平移:$x'' = 20 + 20 = 40, y'' = 0 + 10 = 10$。 最终坐标:$(40, 10)$。

2. 简写指令展开 SVG 的 h 指令表示水平相对移动。 给定当前点 $(50, 50)$,指令序列 h 10 v 20。请将其转换为标准的绝对坐标 L 指令序列。

点击查看提示 `h dx` -> `L (current_x + dx) current_y`。 `v dy` -> `L current_x (current_y + dy)`。
点击查看答案 1. `h 10`: 当前 $(50, 50)$ -> 新点 $(50+10, 50) = (60, 50)$。对应指令 `L 60 50`。 2. `v 20`: 当前 $(60, 50)$ -> 新点 $(60, 50+20) = (60, 70)$。对应指令 `L 60 70`。 结果:`L 60 50 L 60 70`。

3. ViewBox 归一化 原始 SVG viewBox="0 0 100 50"。其中有一个点 $(50, 25)$。 如果我们将其归一化到 $256 \times 256$ 的整数空间,该点的坐标 $(x_{int}, y_{int})$ 是多少?注意保持长宽比(Aspect Ratio)的处理。

点击查看提示 通常做法是:按长边缩放,短边留白(padding);或者直接拉伸(破坏长宽比)。这里假设“拉伸填满”以简化计算。
点击查看答案 (假设拉伸填满策略) $x_{int} = (50 - 0) / 100 \times 255 = 127.5 \approx 128$ $y_{int} = (25 - 0) / 50 \times 255 = 127.5 \approx 128$ 结果:$(128, 128)$

挑战题

4. 思考题:隐式命令 (Implicit Commands) 的处理 SVG 语法允许 L 10 10 20 20,这等价于 L 10 10 L 20 20。即如果命令后跟随了多组参数,默认重复该命令。 在设计 Tokenizer 时,你应该: A. 强制补全所有命令(显式化)。 B. 允许模型输出这种隐式格式。 请分析 A 和 B 对模型训练稳定性的影响。

点击查看提示 考虑“对齐”问题。显式结构更规整,隐式结构更紧凑。
点击查看答案 **推荐选择 A(强制补全/显式化):** 虽然 B 节省了 Token,但它增加了语法的上下文依赖性(模型必须记住 10 个 Token 之前的命令是什么)。 显式化 `L 10 10 L 20 20` 让每个坐标组都是自包含的,极大降低了模型注意力机制的学习难度,减少“错位”风险。

5. 算法设计:Token 反量化 (De-quantization) 策略 你训练了一个模型,输出 $[0, 255]$ 的坐标 Token。直接转换回浮点数会产生明显的“台阶效应”(锯齿)。 设计一个后处理算法或解码策略,从概率分布中恢复更高精度的坐标。

点击查看提示 不要只看 argmax。看看 argmax 左右两侧的概率。
点击查看答案 可以使用 **期望值解码 (Expected Value Decoding)** 或 **亚像素插值**。 假设模型输出 logits,对坐标 $i$ 的概率为 $p_i$。 不取 $k = \text{argmax}(p)$,而是计算局部期望: $coordinate = \sum_{j=k-1}^{k+1} j \times p_j / \sum p_j$ 这就利用了模型对相邻 bin 的不确定性来插值出小数位,从而获得比 grid 更平滑的线条。

6. 开放设计:多模态对齐的 Token 设计 如果我们要训练一个 SVG 模型,不仅能画图,还能在图中插入 <text>。 文字内容(如 “Hello”)应该如何 Tokenize?是使用专门的 SVG 词表,还是借用 LLM (如 Llama) 的 Tokenizer?如果是混合词表,如何处理 Embedding 空间的对齐?

点击查看提示 这是 MLLM 设计的经典难题。考虑 "Special Tokens" 作为模态切换开关。
点击查看答案 **混合词表策略**: 1. 保留 LLM 的原始词表(例如 32k 大小)。 2. 扩充 SVG 专用 Token(如 ``, ``)。 3. Embedding 对齐:从头训练 SVG Token 的 embedding,冻结或微调 LLM embedding。 4. 模态切换:当预测到 `` 时,模型切换到自然语言模式;当预测到 `` 时,切换到几何模式。 </details> --- ## 5. 常见陷阱与错误 (Gotchas) ### 1. `Z` 指令的“回马枪” * **现象**:模型生成的闭合形状在最后一条边出现奇怪的交叉或缺口。 * **原因**:SVG 的 `Z` 指令表示“回到子路径起点”。如果模型之前的坐标预测有累积误差,起点和终点即使数值上接近,视觉上也可能产生裂缝。 * **调试**:在规范化阶段,如果检测到 `Z`,可以将最后一段 `L` 指令移除(因为 `Z` 隐含了最后一段连线),强制由渲染引擎来负责闭合,保证完美的连接。 ### 2. 相对坐标的“漂移” (Drift) * **现象**:生成长序列(如复杂的地图轮廓)时,图形越到后面越歪,或者跑出了画布。 * **原因**:这是自回归模型 + 相对坐标的通病。每一步 $0.1$ 的预测误差,累积 100 步后就是巨大的偏移。 * **解决**: 1. 训练时混合使用绝对坐标(用于锚点)和相对坐标(用于细节)。 2. 或者在 Loss 函数中加入“全局形状重构 Loss”,而不仅仅是“下一步预测 Loss”。 ### 3. `arc` (椭圆弧) 指令的复杂性 * **现象**:模型极难学会 ``。生成的圆弧经常方向反了,或者变成了直线。 * **原因**:`A` 指令包含 7 个参数(半径x, 半径y, 旋转, 大弧标志, 顺逆时针标志, 终点x, 终点y)。其中“大弧标志”和“顺逆时针”是布尔值,对几何形状影响是突变的(非连续)。 * **Rule-of-Thumb**:**不要让模型直接预测 `A` 指令**。在预处理阶段,使用几何库将所有 `A` 指令近似拟合为 1~2 个三次贝塞尔曲线 (`C`)。`C` 指令更加平滑,更易于神经网络学习。 ### 4. 浮点数解析器的区域设置 (Locale) 问题 * **陷阱**:在某些服务器或语言设置下,浮点数用逗号 `,` 分隔(如 `3,14`),而 SVG 标准强制使用点 `.`。 * **后果**:如果你使用简单的 `float()` 进行转换且未指定 locale,可能导致数据清洗阶段大量数据被丢弃或数值错误。 * **建议**:始终硬编码小数点符号处理,不要依赖系统的 locale 设置。