# 实体密度标签设计文档 ## 背景与问题 当前三阶段级联生成(stage1 骨架、stage2 功能实体、stage3 资源)在结构可行性上基本稳定,但存在明显的分布偏移: - 怪物数量偏多 - 资源数量偏多 - 门数量在部分样本上也偏高 已尝试在采样阶段通过“随机抛弃部分新揭开位并重新掩码”的方式抑制过密生成,但效果不稳定,核心原因是该策略属于推理期启发式约束,不能从训练目标层面改变模型对全局密度的先验。 因此需要引入显式条件:将每张地图中门、怪物、资源的密度离散为三档(低/中/高),并在训练和推理时作为条件输入,让模型学习“在指定密度档位下生成”。 ## 目标 - 新增 3 个可控标签:`doorDensityLevel`、`monsterDensityLevel`、`resourceDensityLevel`,取值均为 `0 | 1 | 2`。 - 标签计算与分档在 Python 端完成,保持与现有 `roomCountLevel`、`branchLevel` 一致的处理方式。 - 标签注入模型后,支持在推理时显式控制三类实体密度。 - 在不改动数据处理端(TypeScript)的前提下完成接入。 ## 设计原则 - 统计口径稳定:密度分母采用固定地图面积(13x13),避免受随机掩码影响。 - 分档可迁移:使用训练集等频分箱阈值;验证/推理复用同一阈值。 - 最小侵入:优先扩展现有 Python 数据集与条件注入链路,不改变数据文件格式。 - 可回溯:训练日志与可视化中输出目标密度档位与实际密度,便于诊断。 ## 标签定义 ### 1. 统计对象 基于原始地图 `item['map']`(未掩码、未降级)统计三类图块数量: - `doorCount`: 图块 ID = 2 - `resourceCount`: 图块 ID = 3 - `monsterCount`: 图块 ID = 4 ### 2. 密度定义 设地图面积为 `MAP_SIZE = 13 * 13 = 169`,则: - `doorDensity = doorCount / 169` - `monsterDensity = monsterCount / 169` - `resourceDensity = resourceCount / 169` ### 3. 分档定义 采用等频分箱(三档)并与现有 `to_level` 规则一致: - 训练集上收集某一密度指标的全量样本值,升序排序 - 取 `n/3` 与 `2n/3` 位置作为阈值 `th1`、`th2` - 分档规则: - `< th1` -> `0`(Low) - `>= th1 且 < th2` -> `1`(Medium) - `>= th2` -> `2`(High) 阈值退化处理(与现有实现一致): - 若 `th1 == th2`,将 `th2 = th1 + eps` - 对密度值建议 `eps = 1e-6` ## Python 端处理方案 ### 1. 数据集初始化阶段 在 `GinkaSeperatedDataset.__init__` 中新增一次统计流程: - 从 `self.data` 中提取每张图的 `doorDensity`、`monsterDensity`、`resourceDensity` - 分别计算三组阈值: - `self.door_density_th` - `self.monster_density_th` - `self.resource_density_th` - 回填每个样本: - `item['doorDensityLevel']` - `item['monsterDensityLevel']` - `item['resourceDensityLevel']` ### 2. 样本输出阶段 在 `__getitem__` 返回字典中新增条件向量(建议独立字段,避免影响旧逻辑): - `density_inject = LongTensor([doorLevel, monsterLevel, resourceLevel])` 不建议直接复用旧 `struct_inject` 覆盖含义。推荐并行保留: - `struct_inject`:结构语义(对称/房间/分支/外墙) - `density_inject`:实体密度语义(门/怪物/资源) ## 模型接入方案 ### 1. 条件输入组织 密度条件与结构条件在语义上完全不同(结构描述地图拓扑形态,密度描述实体数量先验),不复用 `struct_inject` 的处理路径。 设计:在 MaskGIT 内新增一个独立的**密度 MLP**: - 输入:3 个独立 embedding 表(每档取值 0/1/2)输出相加后的向量 - `emb_door_density: Embedding(3, d_embed)` - `emb_monster_density: Embedding(3, d_embed)` - `emb_resource_density: Embedding(3, d_embed)` - 三个 embedding 相加后送入 2 层 MLP(`d_embed -> d_model -> d_model`,激活函数 GELU),输出一个 `d_model` 维向量 - 该向量作为独立条件 token 拼接到主序列头部(与 struct token 并列,不替换) 结构条件(`struct_inject`)保留原有处理方式不变。 ### 2. 训练与推理接口 - 训练前向:`mgX(inpX, z_q, struct_inject, density_inject)` - 推理采样:允许显式指定密度档位;未指定时可随机采样档位或使用数据先验分布采样 ### 3. 条件 Dropout 对密度条件增加独立 dropout(例如 0.1): - 训练时随机置空部分密度条件,降低过拟合风险 - 推理时可在“无密度条件”与“强密度条件”两种模式间切换 ## 训练与验证改造 ### 1. 日志指标 在验证阶段新增统计输出: - 按档位分组的密度 L1 误差:分别统计 door/monster/resource 三类实体在 Low/Medium/High 三档条件下,生成地图实际计数与档位中位期望值之间的 L1 距离(仅用于观察,不参与反向传播) 无需额外输出目标档位分布或实际密度均值,档位 L1 已足够直观反映控制效果。 ### 2. 可视化对照 在每张验证生成图上直接标注所有条件标签,分两行显示: - 第一行(结构标签):`sym=N room=L/M/H branch=L/M/H outer=0/1` - 第二行(密度标签):`d=L/M/H m=L/M/H r=L/M/H` 其中 `sym` 取 `cond_sym` 的原始整数值(0–7),`room`/`branch`/`d`/`m`/`r` 均以 `L`/`M`/`H` 表示三档。 标注位置:图像顶部左上角,两行叠加,与现有 `fix`/`free` 标注并列(可追加到同一 `annotate` 调用后)。 额外新增一类对照图:固定同一 `z` 和结构条件,仅扫遍密度档位(Low/Medium/High 三档),分别生成地图并排排列,用于直观验证"只改密度条件,生成实体数量随档位单调变化"。该对照图在每个 checkpoint 验证时生成一次,保存到 `result/seperated/eN/density_cmp.png`。 ### 3. 验收标准 至少满足以下条件后再认为方案有效: - 同一结构条件下,密度档位从 Low -> High 时,三类实体计数总体单调上升 - 验证集上各档位的目标-实际密度 MAE 明显低于未加标签版本 - 地图可玩性不退化(入口可达、关键路径连通性不显著恶化) ## 与现有流程的兼容性 - 数据源 JSON 无需新增字段。 - 标签在 Python 读取后即时计算,不影响 `data/` 侧脚本。 - 旧 checkpoint 不兼容新增输入维度,需要从旧权重迁移或重新训练。 ## 实施步骤建议 1. 在数据集类中实现三类密度统计、分档和 `density_inject` 返回。 2. 扩展 MaskGIT 条件嵌入与前向接口,打通三阶段训练调用。 3. 更新训练/验证日志与可视化标注,增加按档位评估。 4. 先做小规模过拟合与对照采样验证,再进入完整训练。 ## 风险与应对 - 风险:档位边界样本噪声大,模型学习不稳定。 - 应对:引入软标签邻域采样(可选)或在损失中增加密度一致性正则。 - 风险:实体密度受结构强约束,条件可控性受限。 - 应对:在评估中按结构复杂度分组分析,必要时引入结构-密度联合条件建模。 - 风险:三阶段相互影响导致 stage2/stage3 条件冲突。 - 应对:分别监控阶段内计数与最终合并计数,必要时增加阶段特异性权重。 ## 后续可扩展方向 - 将三档扩展为五档,提升控制精度。 - 在密度标签之外增加“功能实体聚集度/均匀度”标签。 - 引入条件一致性判别器,进一步约束生成结果与目标档位一致。