首页
/ RuView ADR-076 实战:用 CNN 频谱图嵌入与图 Transformer 把 CSI 子载波-时间矩阵变成 128 维空间指纹

RuView ADR-076 实战:用 CNN 频谱图嵌入与图 Transformer 把 CSI 子载波-时间矩阵变成 128 维空间指纹

2026-09-07 15:38:33作者:谭伦延

本篇基于 RuView 仓库中的 ADR-076(CSI Spectrogram Embeddings via CNN + Graph Transformer),讲解如何把 ESP32 采集的 64 子载波 CSI 原始数据组织成 64×20 的灰度“频谱图”图像,经 WASM CNN 提取 128 维嵌入,再用图注意力融合多节点观测,最终入库做环境指纹与 kNN 检索。读完后你将掌握从 iq_hex 解析、归一化、最近邻上采样到图融合、Cognitum Seed 入库的完整调用链,并能在 scripts/csi-spectrogram.jsscripts/mesh-graph-transformer.js 上直接复现实操。

一、问题背景:8 维手工特征丢掉了什么

RuView 现有的 CSI 处理链路对每帧提取一个 8 维手工特征向量:平均幅度、幅度方差、最大幅度、平均相位、相位方差、带宽、频谱质心与 RSSI。这套特征对基础存在检测和房间指纹足够用,但它把所有子载波信息坍缩成标量统计量,丢弃了原始数据中的二维空间-频率结构。

单个 ESP32-S3 的 CSI 帧包含 64 个子载波(HT40 模式下为 128 个),每个子载波带 I/Q 分量。把 20 个连续帧堆叠起来,就形成一个 64×20 的子载波-时间矩阵——本质上就是一张灰度频谱图。这张矩阵编码了四类 8 维特征“看不见”的信息:

  1. 频率选择性衰落——金属物体在特定子载波索引处形成持久空区(表现为竖直暗条纹);
  2. 多普勒特征——人体运动在子载波上产生时变幅度模式(表现为水平波纹);
  3. 多径结构——房间几何形状产生每个环境独有的干涉图案;
  4. 活动指纹——行走、坐姿、呼吸、跌倒在子载波-时间矩阵上呈现截然不同的二维纹理。

ADR 中给出的局限性对照表可以完整概括为什么需要换表征:

局限 影响
无子载波级信息 无法区分频率选择性衰落与宽带衰落
无时序模式编码 步态(周期性)与随机运动(非周期)看起来一样
无二维结构 房间指纹被压成 8 个数;统计量相近的两个房间无法区分
无跨子载波相关性 无法检测驻波、节点图案或多径簇
kNN 区分度差 8 维超球面面积有限,难以分离不同环境

仓库中这套 8 维特征的实际来源可以在两处印证:固件侧 edge_processing.h 定义了 float features[8](8 维归一化特征向量),而 seed_csi_bridge.py 负责把这 8 维向量送入 Cognitum Seed(对应 ADR-069),--file 模式下 parse_raw_csi_packet 甚至给出了一组 8 元默认值。ADR-076 的动机正是:在这套快速特征之外,补一条能保留二维结构的 128 维表征通道。

二、总体架构与依赖

ADR-076 的决策一句话概括:把 CSI 子载波-时间矩阵当作灰度频谱图,用 CNN 生成保留二维空频结构的 128 维表示,并用图 Transformer 融合多个 ESP32 节点的嵌入。ADR 元信息为:状态 Proposed,日期 2026-04-02,依赖 ADR-018(二进制帧格式)、ADR-024(AETHER 对比嵌入)、ADR-029(RuvSense 多静态感知)、ADR-069(Cognitum Seed 桥)、ADR-073(多频网格扫描)。

ADR 原文给出的架构图如下(每个 ESP32 节点一路,两路汇合进图注意力):

ESP32 Node 1          ESP32 Node 2
     |                      |
     v                      v
  UDP 5006              UDP 5006
     |                      |
     v                      v
 [64 subcarriers]      [64 subcarriers]
 [20-frame window]     [20-frame window]
     |                      |
     v                      v
 64x20 amplitude       64x20 amplitude
 matrix (grayscale)    matrix (grayscale)
     |                      |
     v                      v
 @ruvector/cnn         @ruvector/cnn
 CnnEmbedder           CnnEmbedder
     |                      |
     v                      v
 128-dim vector        128-dim vector
     |                      |
     +-------+  +----------+
             |  |
             v  v
    Graph Transformer (2-node graph)
    Edge weight = cross-node correlation
             |
             v
    Fused 128-dim vector
             |
     +-------+-------+
     |               |
     v               v
  Cognitum Seed   kNN Search
  (128-dim store) (similar rooms)

实现依赖三个 ruvector 侧的库(ADR 中声明的版本):

  • @ruvector/cnn(v0.1.0):WASM CNN 特征提取(约 5ms/224×224 图像,约 900KB 模型),可配置嵌入维度(默认 512,本方案用 128 做紧凑存储)、L2 归一化嵌入与余弦检索、InfoNCE/三元组对比训练、SIMD 优化的层运算,Node.js 与浏览器双环境可用;
  • ruvector-graph-transformer:基于 LSH 分桶与 PPR 采样的 O(n log n) 子线性图注意力、证据门控的变异底座、带 Granger 因果的时序因果注意力(与 CSI 时间序列直接相关)、Sⁿ×Hᵐ×ℝᵏ 乘积空间上的流形注意力;
  • @ruvector/graph-wasm(v2.0.2):WASM 内的 Neo4j 兼容属性图数据库,支持任意属性与嵌入的节点/边、超边以及 Cypher 查询。

ADR 声明的依赖安装路径为 vendor/ruvector/npm/packages/ruvector-cnn/vendor/ruvector/npm/packages/graph-wasm/。需要注意:当前仓库快照中 vendor/ruvector 目录存在,但 npm 包子模块未检出(目录为空),本地运行前先完成对应子模块检出,否则 csi-spectrogram.jsinitCnn() 会因读不到 WASM 文件而失败。

三、Step 1:CSI 帧到灰度频谱图

数据格式

CSI 帧以 ADR-018 二进制格式经 UDP 传输(JSONL 录制的 JSON 表示同样可用)。一帧的 JSON 示例来自 ADR:

{
  "timestamp": 1775182186.123,
  "node_id": 1,
  "magic": 3289481217,
  "size": 148,
  "rssi": -45,
  "type": "CSI",
  "iq_hex": "00000f030d030e040d030d030d030c020d020d01...",
  "subcarriers": 64
}

iq_hex 的编码规则:每字节 2 个十六进制字符,每个子载波 4 个字符(I 字节 + Q 字节),总长度 = subcarriers * 4。二进制 UDP 帧的 magic 为 0xC5110001,这在 ADR-018 与固件侧 csi_collector.h#define CSI_MAGIC 0xC5110001)中交叉印证,csi_collector.c 注释也标明了 [0..3] Magic: 0xC5110001 (LE)

幅度计算与归一化

每个子载波的幅度为:

Amplitude[sc] = sqrt(I[sc]^2 + Q[sc]^2)

取 20 帧滑动窗口得到 64×20 矩阵,再做 0–255 灰度归一化:

pixel[sc][t] = clamp(255 * (amplitude[sc][t] - min) / (max - min), 0, 255)

min/max 在整个 64×20 窗口上计算,即每窗口对比度归一化——这保证 CNN 看到的是相对结构,而与绝对信号强度(随距离、发射功率、环境吸收变化)无关。

源码实现位于 csi-spectrogram.jsparseIqHex() 按 4 字符步长逐子载波解析 I/Q 并开方;parseBinaryFrame()L99-L121)则直接解析 ADR-018 二进制包:HEADER_SIZE = 20,从偏移 4/5/6/8 读取 node_id(u8)、rssi(i8)、n_subcarriers(u16 LE)、payload_size(u16 LE),并做 magic 校验与长度越界保护。

窗口缓存类 SpectrogramWindowL127-L217)负责三件事:

  • push():环形缓冲,长度不匹配时补零/截断到 nSubcarriers
  • toGrayscale()L166-L192):先在 frames[t][sc] 双循环上找整窗 min/max(range = max - min || 1 防除零),再按行主序(行=子载波、列=时间帧)输出 Uint8Array 像素;
  • toCnnInput():执行 Step 2 的上采样(见下节)。

--ascii 模式还会把当前窗口打印成终端频谱图:行下采样到最多 32 行,每个像素映射到 BARS = [' ', '▁', '▂', ..., '█'] 八级强度字符,并带 sc### 行号与 t=0 ... t=19 时间轴标注(L227-L253),便于肉眼核对暗条纹与波纹是否如 ADR 描述。

四、Step 2:CNN 嵌入

64×20 灰度矩阵需要先适配 @ruvector/cnn 的输入约定:

  • 上采样:最近邻插值到 224×224。ADR 明确选择最近邻而非双线性,是因为要保留离散的子载波结构,避免插值抹平;
  • 通道复制:灰度复制成 3 通道 RGB,因为 CNN 期望 RGB 输入;
  • 嵌入维度:128(从默认 512 降下来,换取紧凑存储与更快 kNN);
  • 归一化:L2 启用——单位球面上余弦相似度退化为点积;
  • 延迟:现代硬件上约 5ms/窗口。

源码中 toCnnInput()L199-L216)的最近邻映射为 srcY = floor(y * height / 224)srcX = floor(x * width / 224) 并做边界钳制,三个通道写同一灰度值。

一个值得注意的实现细节在 initCnn()L265-L305):脚本绕开了 CnnEmbedder 封装类,直接加载 WASM 绑定。原因写在注释里——封装类构造函数调用 new wasm.WasmCnnEmbedder(wasmConfig) 时会消费(销毁)EmbedderConfig 指针,随后又尝试从已成 null 的指针读 wasmConfig.embedding_dim。因此脚本先 require('vendor/.../ruvector-cnn/ruvector_cnn_wasm.js')、读取 ruvector_cnn_wasm_bg.wasm 字节,再手工构造 EmbedderConfiginput_size = 224embedding_dim = 128normalize = true),并在构造前把维度保存下来自己跟踪。这段“踩坑记录”对复用该 WASM 包的读者有直接参考价值。

运行方式与参数

脚本以 parseArgs 严格模式解析参数,主要选项(默认值取自源码):

参数 短名 默认 作用
--file <path> -f - 读取 .csi.jsonl 录制文件
--live - false 监听 UDP 实时 CSI
--port <p> -p 5006 UDP 端口
--ascii - false 打印 ASCII 频谱图可视化
--ingest - false 把 128 维嵌入送入 Cognitum Seed
--knn <k> -k 0 检索 K 个最相似历史频谱图
--seed-url - https://169.254.42.1:8443 Seed 地址
--seed-token - 空(或 $SEED_TOKEN 鉴权 token,--ingest 必需
--window <n> -w 20 每频谱图的帧数
--stride <n> -s 10 窗口间滑动步长
--dim <d> -d 128 CNN 输出维度
--json - false JSON 输出(便于管道消费)
--limit <n> -l 限制处理帧数

典型命令(引自 csi-spectrogram.js 文件头注释;其中 data/recordings/*.csi.jsonl 为 ADR 示例录制文件,需由采集流程自行产出,当前仓库快照未包含):

node scripts/csi-spectrogram.js --file data/recordings/pretrain-1775182186.csi.jsonl --ascii
node scripts/csi-spectrogram.js --live --port 5006 --ingest --seed-url https://169.254.42.1:8443
node scripts/csi-spectrogram.js --file data/recordings/pretrain-1775182186.csi.jsonl --knn 5

文件模式的核心循环在 processFile()L427-L537):逐行 JSON.parse,按 node_id 维护各自的 SpectrogramWindow,窗口填满后以 (totalPushed - WINDOW_SIZE) % STRIDE === 0 为触发条件提取嵌入(L472),随后依次做 ASCII 打印、--json 输出(含 embedMs 计时)、kNN 检索与 Seed 入库。Live 模式(processLive())先尝试 ADR-018 二进制解析,失败再退化为 JSON 解析,格式与文件模式完全一致。

五、Step 3:图 Transformer 多节点融合

ADR 的融合图定义为:

Nodes: {Node_1, Node_2}
Edges: {(Node_1, Node_2, weight=cross_correlation)}
Node features: 128-dim CNN embedding per node

注意力机制按 GATv2 思路分四步:

  1. 每个节点 128 维嵌入投影出 Query/Key/Value;
  2. 边权 = 两节点原始幅度向量的 Pearson 互相关(刻画两个节点 CSI 观测的一致程度);
  3. 注意力得分 = softmax(Q_i * K_j / sqrt(d) + edge_weight_bias)
  4. 输出 = 值向量的加权和。

结果是一个融合 128 维向量,自动给信号更干净(SNR 更高、衰落更少)的节点更大权重。推广到 3 个及以上节点时,加一个节点只多两条边,注意力机制无需改架构即可处理变尺寸图。

mesh-graph-transformer.js 给出了一份纯 JS 实现(不依赖 WASM 即可运行融合本体):

  • GraphAttentionLayerL148-L286):GATv2 风格多头注意力,headDim = floor(inputDim / numHeads)Wq/Wk/Wv/Wo 采用 Xavier 均匀初始化;边权偏置项按 scores[j] = dot * (1/sqrt(d)) + edgeWeight * 0.5edgeBiasScale = 0.5)叠加后做数值稳定的 softmax(先减最大值);各头拼接后由 Wo 投回输入维度,最后对 N 个节点输出做平均池化得到融合向量;单节点时直接透传(attentionWeights = [[1.0]]);
  • pearsonCorrelation()L296-L312):标准 Pearson 公式,分母非正时返回 0;
  • MeshGraphL322-L395):以 Map<nodeId, {features, amplitudes, rssi, timestamp}> 维护节点,computeEdgeWeights() 对所有节点两两计算相关系数(键为 "i-j"i<j 排序),fuse() 在节点数 ≥2 时产出 {fused, attentionWeights, nodeIds, edgeWeights}
  • 8 维手工特征的 JS 版 extract8DimFeatures()L91-L133):mean/variance/maxAmp、相位两项占位 0(注释说明需要原始 I/Q 才能得到真相位)、归一化带宽(幅度高于 mean*0.1 噪声底的子载波占比)、归一化频谱质心、|rssi|/100--dim 8 时用它,--dim 128 的纯文件模式则用 8 维补零占位(真实 128 维嵌入需经由 csi-spectrogram.js 管线产生);
  • 可选持久化initGraphDb()L407-L421)尝试加载 @ruvector/graph-wasm 并创建 GraphDB('cosine')persistToGraphDb() 把每个节点写成带 ESP32/SensingNode 标签的节点(属性含 node_idrssitimestampfeature_dim),节点对写成 CSI_CORRELATION 边(属性含相关系数与融合次数)。加载失败时优雅降级为纯内存图——这也是 ADR 风险表中“图 Transformer 开销”缓解措施在代码里的落点。

运行方式:

node scripts/mesh-graph-transformer.js --file data/recordings/pretrain-1775182186.csi.jsonl [--dim 8|128] [--heads 4]
node scripts/mesh-graph-transformer.js --live --port 5006 --dim 128

--heads 默认 4,融合触发频率为每 --window(默认 20)帧且在线节点 ≥2 时执行一次。

六、Step 4:存储与检索

融合后的 128 维嵌入与既有 8 维特征并存于 Cognitum Seed(ADR-069):

存储 维度 内容 用途
csi-features 8 维 手工统计特征 快速存在检测
csi-spectrograms 128 维 CNN 频谱图嵌入 环境指纹、异常检测
csi-spectrograms-fused 128 维 图融合多节点嵌入 跨视角房间签名

128 维库上的 kNN 检索找出“长得像”的历史频谱图,ADR 列出四类用法:环境指纹(这个射频图案匹配哪个房间)、跨房间迁移(哪个训练房间与部署房间最相似)、异常检测(与所有已知模式相似度都低 = 未知环境或新型活动)、时间分段(相似度骤降 = 活动转换边界)。

代码侧对应两个通道:

  • 进程内 kNNEmbeddingStorecsi-spectrogram.js L321-L350)线性扫描 + 余弦相似度排序,查询时先取 k+1 再过滤自身;
  • Seed 入库ingestToSeed()L372-L421)以 Bearer token POST /v1/vectors/upsert,payload 为 { store: 'csi-spectrograms', vectors: [{ id: 'spectrogram-<nodeId>-<windowIdx>', values: [...128 floats], metadata: { node_id, timestamp, window_idx, rssi, subcarriers } }]},token 取自 --seed-token 或环境变量 SEED_TOKEN,缺失时明确报错。

ADR 中三套表征的对比表完整保留如下:

属性 8 维手工 128 维 CNN 组合
子载波结构 丢失 保留 两者都有
时序模式 丢失 保留(20 帧窗口) 两者都有
计算 ~0.1ms ~5ms ~5ms
单向量存储 32 字节 512 字节 544 字节
kNN 区分度 低(8 维灾难) 高(128 维超球面) 最高
可解释性 高(具名特征) 低(学习得到) 混合
是否需要训练 可选(预训练可直接用) 可选
多节点融合 平均/取最大 图注意力 图注意力

七、可选增强:对比训练

预训练权重开箱即用;若要在 CSI 域上进一步调优,ADR 给出对比训练配方:

  1. 正样本对:同一房间、不同时间窗(嵌入应相近);
  2. 负样本对:不同房间或不同活动(嵌入应相异);
  3. 损失:InfoNCE,温度 0.07(SimCLR 标准值);
  4. 数据增强:时间平移(窗口滑 1–5 帧)、子载波 dropout(置零 10% 行)、幅度抖动(乘以 uniform[0.8, 1.2])。

目标是让 CNN 学到“同一房间的不同时刻应产生相似嵌入,不同房间应产生不同嵌入”。参考文献即 SimCLR(Chen et al., 2020)与 GATv2(Brody et al., 2021),见 ADR 原文 References

八、后果、风险与缓解

正面影响:128 维承载 8 维装不下的二维结构;能区分 8 维特征空间中“长得一样”的房间;步态周期、呼吸频率等时序模式被编码进频谱图纹理;图注意力自动加权最 informative 的节点,提升单节点遮挡/干扰下的鲁棒性;128 维库与 8 维库并行运行、零迁移成本;WASM CNN 可跑在 sensing-server UI 里做实时可视化。

负面影响:每窗口 5ms 延迟(对 ADR-073 的 750ms 轮转、约 1.3Hz 更新率可接受,但限制更实时场景);约 900KB 模型一次性下载、首次加载后缓存;每向量存储是 8 维的 16 倍(以“每个 20 帧窗口存一条而非每帧一条”缓解);嵌入不可人读;64×20 上采样到 224×224 存在填充区的算力浪费。

风险与缓解(ADR 原文表格):

风险 缓解
CNN 嵌入对 CSI 区分度不足 在 CSI 频谱图上对比微调;若 128 维 kNN 召回更差则回退 8 维
2 节点图上图 Transformer 开销 轻量注意力(单头、无 MLP);2 节点时 O(1)
64×20→224×224 上采样伪影 最近邻保留离散结构;可考虑在原生 64×20 输入上训练更小 CNN
WASM 初始化延迟 在服务器启动时调用 init(),而非每请求一次(initCnn()cnnInitialized 标志保证只初始化一次,见 L259-L266

九、小结

ADR-076 的价值在于给 RuView 的 CSI 感知链路增加了一条“图像化”表征通道:以 csi-spectrogram.js 实现帧解析→频谱图归一化→WASM CNN 128 维嵌入→kNN/Seed 入库的完整管线,以 mesh-graph-transformer.js 实现 Pearson 边权 + GATv2 多头注意力的多节点融合,并与 ADR-069 的 8 维 csi-features 库并行共存、增量落地。相关决策脉络可继续追溯 ADR-018(帧格式)、ADR-069(Seed 桥)、ADR-029(多静态感知)与 ADR-073(多频网格扫描)。

登录后查看全文
热门项目推荐
相关项目推荐