派大猩 · Agent 工程笔记

Back

时空融合的癫痫异常放电检测

views

头皮脑电这条线依托神经信息教育部重点实验室(与成都脑科学临床研究院、 中国医学科学院神经信息单元 2019RU035 协同)做的,主方向是癫痫样放电自动检测。 它给了一个很少见的训练:输入是噪声很大、标注很贵、错一次后果很重的信号。

这套模型叫 TSG-Transformer(Temporal–Spatial Graph Transformer), 做头皮脑电里的癫痫样放电(IED)检测。它在 TUEV v2.0.1 官方评估集上拿到 accuracy 93.58%、specificity 94.91%、sensitivity 92.14%、F1 93.26%、AUC 98.29%, 和六条基线放在同一套预处理与评估协议下比,accuracy、specificity、F1、AUC 四项点位最高。

它真正想解决的不是「把波形认得更准」——人眼看过两次棘慢复合波就认得出来, 真正麻烦的是一条放电是怎么从电极之间铺开来的,以及真正说话的其实只有几个导联。 论文里那三个任务特性(多尺度波形、沿通路传播、通道稀疏)直接对应三段结构: 多尺度时间编码器、PLV 引导的多跳注意力、自适应局部-全局图池化。这一页就按这条线讲一遍。

一个 1 秒片段:20 个双极导联 × 250 个采样点 多尺度时间编码器 三路并行 conv1d,k = 8 / 16 / 32,每路再配一条同核的 max-pool 激活用 LeakyReLU(负斜率 0.2),三条分支拼接后线性投影 所有导联共用这一个编码器,不按通道复制参数 PLV 多跳掩码注意力 每个跳数一个加法掩码,加在 softmax 之前:连通的位置才允许注意 跨跳取平均而不是拼接,让一跳和二跳各占一半话语权 残差 + LayerNorm + GELU 前馈层收尾 局部-全局双路图池化 局部路:GIN 打分 → Top-6(r₁ = 0.3),留住具体是哪几个导联 全局路:soft assign → k₂ = 4 个社区,留住整张图的分布 A_cross 搬运:把社区上下文送回每个被保留下来的通道 X_fused = σ([ X_ch1 + A_cross · X_coarse ] W_V) 分类头 通道维并行 max 池化与 mean 池化拼接,过两层 MLP 出放电概率 每段自己算的拓扑 PLV 20 × 20 → τ = 40% 二值化 M⁽¹⁾:一跳的直接耦合 M⁽²⁾:经由中继的二跳 全库共用一张空间图的做法是 另一种前提,这里不用 20 导重排不影响输出: 无位置编码、无电极嵌入
TSG-Transformer 的结构:三条任务特性各自对应一段,PLV 多跳拓扑从外面同时喂给注意力层和池化层。

这条线由两半组成,谁也不是另一半的附属品。模型侧是上面这套结构; 工程侧是配套的 PyQt 检测客户端,EDF 解析、切片、推理、复核、统计这条链路上没有一步是买来的。 数据用 Temple University Hospital EEG Corpus(TUEV v2.0.1), 官方评估集 159 名受试,训练与评估在受试层面不重叠。

波形不难,难的是电极之间怎么连

任务是在长程脑电里找癫痫样放电。放电埋在几十分钟甚至几小时的记录里,人工标注慢、贵, 不同阅片人之间的差异也不小:八位专家给 13,262 个候选放电打分,两两一致率只有 72.4%, Gwet κ 48.7%,只到 fair。所以自动检测要抢的先不是精度,是人力。

训练与评估用 Temple University Hospital EEG Corpus(TUEV v2.0.1):官方划分训练 359 名受试、 评估 159 名,两个划分的受试互不重叠。预处理之后切成 13,044 个 1 秒片段, 其中阳性 6,191、阴性 6,853,接近平衡。

导联取标准 22 导 TCP 双极 montage 去掉含耳电极的 A1–T3 与 T4–A2,留下 20 条双极导联, 采样率 250 Hz,每个片段 250 个采样点。标签侧把棘慢复合波、广泛周期性放电、 偏侧周期性放电并为正类,把眼动、伪迹、背景并为负类。

六步预处理是一条流水:单极转 TCP 双极,四阶零相位 Butterworth 做 0.5–49 Hz 带通, 按标注切不重叠的 1 秒片段,逐片段逐通道 z-score。 之后用复 Morlet 小波做连续小波变换(cmor2.0–2.0,32 个频点铺满 0.5–49 Hz, 其中 12 个集中在 10–30 Hz 提高分辨率),最后在 10–30 Hz 上算通道间的 PLV。

尖波和慢波压根不是一个时间尺度

IED 是几十到几百毫秒的瞬态事件:棘成分的上升沿只有几毫秒,跟在后面的慢波却能拖几十到几百毫秒。 一条一维卷积只有一个感受野,想在一秒片段里同时装下「几毫秒的陡」和「几百毫秒的缓」,做不到。

所以第一段是多尺度时间编码器,思路来自 InceptionTime: 三条并行一维卷积分支,kernel 取 8 / 16 / 32,分别抓短、中、长三种尺度; 每条分支是卷积、BatchNorm、激活。 激活选 LeakyReLU(负斜率 0.2)是有意的——放电的负相在双极导联上往往比正相更有定位意义, ReLU 会把负半轴直接砍成零,等于丢掉一半判别信息。

再给三条同样核大的并行 max-pool 提供互补表示:保留各尺度的峰值响应, 而不是把所有位置压成一个均值。均值会把「这个时刻正好有个尖波」直接抹平。 三条分支沿时间维拼起来之后线性投影到 d_model。

这里还有一条贯穿全网的约束:每条通道共用同一个编码器,参数不按通道复制; 后面的掩码注意力、两个 GIN、Top-K 选择和全局池化头都对通道索引保持置换等变或置换不变, 也没有用任何位置编码或电极嵌入。这一条在后面解释「为什么它挑出来的导联有意思」时会回来。

相位锁住的才算通路,隔着头皮远近不算

放电不是每个电极各自响一下,而是沿着致痫网络的功能通路扩散。 于是「一起抖」有两种含义:振幅大,或者相位锁定。前者只看能量就够了,后者才是连接关系。 拓扑不是事后画出来给人看的图,是从信号本身的相位关系里算出来的。

20 个导联两两配对,在 10–30 Hz 带内取复小波系数的瞬时相位,对每个通道对算 PLV(相位锁定值):

PLV(i,j) = | (1/N) · Σ exp( j · ( φ_i(n) − φ_j(n) ) ) |,    N = 250

完全随机的相位关系趋于 0,完全锁相趋于 1。得到的是一张 20×20 的加权邻接,边权就是 PLV 值—— 不是「相关所以相似」,而是「这两个导联的相位真的锁住了」。 20 个导联两两配对,去掉自环后是 190 条候选边。

原始 EDF 长程头皮脑电 0.5–49 Hz 带通 四阶零相位 切成 1 秒片段 每段 250 点 × 20 导,逐导标准化 复 Morlet 小波 cmor2.0–2.0,32 频点 取 10–30 Hz 上的瞬时相位 20 × 20 PLV 邻接 190 条候选边,边权就是相位锁定值 τ = 40% 二值化 每段只留排名最强的一批边 二值矩阵幂 M⁽²⁾ 补上经由中继的那一跳 通道 i 中继 k 通道 j 一跳 一跳 二跳:i 和 j 之间没有直接边,隔着中继 k 落在同一条通路上
从原始 EDF 到多跳通路:上面一排是六步预处理,下面一排把相位关系变成图;最下面是两条通路的区别——一跳是 PLV 直接给出的耦合,二跳中间隔着一个中继通道。

但 PLV 只量到了直接耦合。真实通路常常是 A 到 B 再到 C,B 是个中继, 只看一跳就会漏掉隔着一个中继的 C。所以还要两步才成图。 第一步是按每段保留最强的一批边做二值化,阈值 τ = 40% 是扫出来的最优: 从 10% 到 90% 全扫过,非单调,40% 处 accuracy 和 AUC 同时到顶。

M(i,j) = 1   if rank↓( PLV(i,j) ) / N_E ≤ τ,    否则 0

第二步是把这张二值矩阵做矩阵幂扩展,递归地把多跳连通关系也显式建出来:

M⁽ˡ⁾ = 𝟙[ ( M⁽ˡ⁻¹⁾ · M ) > 0 ],    l = 2, …, n,    M⁽¹⁾ = M

取 n = 2,每张 M⁽ˡ⁾ 对角线归零排除自环, 于是 M⁽ˡ⁾(i,j) = 1 就表示 i 与 j 之间存在一条长度为 l 的功能通路。 这一步是「神经通路」四个字里最关键的处理:一跳是直接耦合, 二跳是「经由一个中继通道连上」,把中继这个中间环节补上了。

注意力只往算出来的通路上流

有了多跳拓扑,就把它压进自注意力的掩码里。对时间编码后的特征算 Q / K / V, 对每个跳数 l 造一个加法掩码 E⁽ˡ⁾:M⁽ˡ⁾(i,j) = 1 的位置掩码取 0(可看),否则取 −∞:

Attention⁽ˡ⁾ = softmax( ( Q·Kᵀ / √d_k ) + E⁽ˡ⁾ ) · V

掩码加在 softmax之前,这一点很关键:未连通的位置在归一化之前就被遮住, 不会靠随机初始化偷来一点权重,注意力只沿着 PLV 撑起来的通路流动。跨跳合并时取平均:

Attention_MH = (1/n) · Σ Attention⁽ˡ⁾,    l = 1, …, n

取平均而不是拼接,是有意的取舍。两个跳数覆盖的东西不一样: 一跳抓的是紧邻的直接同步电极,也就是 PLV 直接给出的证据;二跳抓的是经由中继的远端。

直接拼接会让二阶信息压过一跳,而一跳恰恰是整个拓扑的证据来源;取平均等于让两条跳各占一半话语权。 合并之后走残差与 LayerNorm,再过一个 GELU 前馈层收尾。

这一步和已有做法的区别值得说清楚。EEG-GCNN 那类脑电图网络只在一跳邻域上聚合, 中继通道这个中间环节被跳过了;BIOT 那类跨域生物信号 Transformer 的注意力完全不带拓扑掩码, 通道之间该不该交换信息是靠学出来的。

V2IED 是表里唯一专门为 IED 设计的,它的空间分支用固定的三维通道拓扑嵌入,对所有导联一视同仁。 本模型把「该不该交换」从「学」换成了「先算出来再约束」: 直接耦合和经由中继的耦合,都在注意力开合之前就被拓扑矩阵定好了。

收益在消融里被单独切出来验证过:把 PLV 多跳注意力换成普通自注意力,AUC 掉 2.22 个点。 这 2.22 个点全部来自「拓扑先验」和「中继通路」这两件事本身。

真正说话的只有几个导联

通道稀疏这个特性决定了一件取舍:放电主要集中在致痫区上方或邻近的少数导联, 其余大片通道装的都是背景活动和伪迹。把所有通道一视同仁地送进分类器,等于稀释判别信号; 但只挑几个通道又会丢掉整张图的上下文。最后一段做成局部-全局双路图池化, 两条路并行再融合。

第一路是局部特征池化。用 GIN 当节点重要性函数,对节点特征和 PLV 加权邻接给出每个通道一个标量分数, 按局部保留比 r₁ = 0.3 选出 K₁ = 6 个通道:

s = GIN(X, A),    X_ch1 = X_idx ⊙ s_idx,    A_ch1 = A_(idx,idx)

第二路是全局拓扑池化,保整张图的统计。用一个可微的 soft assignment 矩阵 P = softmax( GIN(X,A) ) 把 20 个节点软映射到 k₂ = 4 个粗化社区节点, 社区表示 X_coarse = PᵀX,社区邻接 A_coarse = PᵀAP。

融合这一下才是双路的意义所在。P 里与那 6 个保留通道对应的行单独拎出来, 就是连接「细粒度通道」和「粗粒度社区」的跨路矩阵 A_cross, 它把全局社区信息搬回每个被保留下来的通道,再一起过一个可学习投影:

X_fused = σ( [ X_ch1 + A_cross · X_coarse ] · W_V ),    σ 为 GELU

于是每个留下来的导联都带着「它所在社区的全局上下文」,而不是孤立地代表自己。 全量保留会稀释关键导联,只做社区粗化又丢掉具体哪个导联在放电, 局部选通道、全局保拓扑、跨路搬运,两头都占。 双路骨架接在分层图池化那条线上(DiffPool / SAGPool / Graph U-Nets), 局部与全局并行的结构则来自 FC-HGNN;差别在于这里的社区分配矩阵和通道打分矩阵都吃 每段自己的 PLV 邻接,不是全库共用一个空间图,也不是硬聚类式的不可导分配。

分类头把 X_fused 沿通道做并行全局最大池化和全局平均池化拼起来: 最大池化抓尖波峰值这种最显著的瞬态,平均池化留整体通道间的协同模式。 两者拼起来再过两层 MLP,出该片段的放电概率。

局部池化那一路还顺手产出了一样比指标更耐用的东西:每个通道的重要性分数。 统计全部 IED 片段里每个通道进 Top-5 的频率,排在最前面的五条是 F7–T3、C4–T4、Fp2–F8、T3–T5、C3–Cz。

最常进 Top-5 的五条导联 其余双极导联 左颞链 Fp1–F7 F7–T3 T3–T5 T5–O1 右颞链 Fp2–F8 F8–T4 T4–T6 T6–O2 中央链 T3–C3 C3–Cz Cz–C4 C4–T4 左矢状链 Fp1–F3 F3–C3 C3–P3 P3–O1 右矢状链 Fp2–F4 F4–C4 C4–P4 P4–O2
20 条 TCP 双极导联按五条链排开,相邻两个导联共用一个电极;深色的是模型在 IED 片段上最常选进 Top-5 的那五条,集中在颞区与额颞交界。

这五条全是双极链上的相邻电极对,结构上高度一致,落点集中在颞区与额颞交界, 正是临床看 IED 时最先扫的那片区域。特别值得说的是:整套模型没有给任何电极身份, 也没有电极位置编码,网络对 20 个导联的重排是不变的。 也就是说这个偏好没有任何空间先验能造出来, 它是从这些导联自身的信号和连通性统计里学出来的——不是人告诉模型的,是模型自己挑的。

这套偏好合适的落点是预筛和复核优先级的排序,不是定侧定灶。 放在这个位置上,它给临床的是「先看这几条导联」的提示,让一屏一屏翻的过程有个顺序。

摆在同一套协议上比,才知道涨在哪

对比选了六条代表性方法,全部在同一套预处理和评估协议下复现, 深度学习基线一律用原论文默认超参、不做任务内调参。 它们分别是 SVM 加手工特征(每通道 23 维时频特征,20 通道拼成 460 维)、EEGNet、CNN–LSTM、 EEG-Conformer、BIOT,以及 V2IED——形态学分支加三维通道拓扑嵌入加决策级融合, 是表里唯一专门为 IED 设计的那条。

TUEV v2.0.1 官方评估集上,TSG-Transformer 的 accuracy 93.58、specificity 94.91、 sensitivity 92.14、F1 93.26、AUC 98.29。 这四项都点位最高,相对各自最强的基线分别高 2.55、2.28、2.55 和 1.00 个百分点。

有几处值得单独点出来。sensitivity 92.14 并不是全场最高,EEGNet 有 93.71, 但它的 specificity 只有 79.81,比本模型低了 15.1 个点: 单看召回会得出完全相反的结论,这也是五项指标必须一起摆的原因。 另一处是 SVM 那条线:specificity 92.04 不算差,sensitivity 却只有 54.37, 手工特征路线能撑住特异性,几乎抓不住放电本体, 说明这条线缺的不是更强的分类器,而是把放电的空间传播结构显式建出来。

τ 那条曲线也值得单独看一遍。从 10% 扫到 90% 是非单调的,40% 处 accuracy 和 AUC 同时到顶: 阈值太低,弱连接噪声会累积;太高,有价值的功能连接又被删掉。 40% 是「召回和特异性都不亏」的那个平衡,不是单点冲高。

还有一个数字能给这套架构一个外部尺度上的参照:已有研究里, Persyst 13 放电检测器在长时脑电上与三位熟练判读者标记棘波的表现相当。 同期三家全自动算法用在 20 分钟常规记录上时,特异性不足以直接上临床; 而把检测聚类后交给专家复核,准确率接近常规人工阅片,复核负担还更低。 本模型做的事正互补:它判的是已经切好、已标注的 1 秒片段, 在那种流水线里更像一个二级分类器,而不是终端产品。

从评估集走进院里,最先变的不是准确率

公开库能给出一条漂亮的曲线,临床上没人会因为曲线好看就信它。 这条线现在已经在和临床配合做预实验,落点是一个很朴素的两段式结构:采集端和计算端分开。

采集端在医院侧。脑电由院内采集设备引出,视频脑电或动态脑电视场景而定, 按临床自己的记录习惯走导联方式,原始数据直接以标准 EDF 导出。 片段切分沿用同一套标注:医师在阅片时标出的放电段,就是这批数据的标签来源。 这一端只负责把真实世界的数据原样拿出来,不碰模型、不做任何二次加工—— 一旦采集侧做了预处理,后面就分不清是模型学得好还是上游洗得干净。

计算端放院内本地算。三层原因:数据不出院,合规上先过审; 医师要在一块屏幕上同时看波形和模型判定,回传路径越短,复核时的上下文越接得上; 预实验阶段样本小、迭代快,本地改一次参数就能重跑一批。

两端之间就是一段 EDF 加一份标注。计算端产出逐片段的放电概率、判定, 以及它选中了哪几个导联,回传后由临床医师逐条确认或否决。

模型只出概率,阈值固定,最后由人拍板,医师否决掉的样本回流进下一轮训练。 流程上还压着两条硬规矩:伦理审批与知情同意先走完、原始身份不出院, 以及标签始终来自临床医师、模型不给基准。

配套的 PyQt 检测客户端要回答的是另外几个问题:这个片段为什么被判成放电、判得对不对、 攒起来能不能统计。EDF 解析、单极转 20 导双极、0.5–49 Hz 零相位带通、 按标注切 1 秒片段、逐段标准化,这条预处理和模型侧完全同构,不存在训练一套、上线另一套。 界面上多导联波形同步绘制、时间轴对齐到片段,能看到尖波和慢波长什么样, 也看得到模型盯了哪些通道——那张通道重要性分数直接画在上面。 批量推理给出每个片段的放电概率与判定,人工确认与否决的结果落盘形成可以回流的样本, 正负片段数、逐导联命中分布、逐受试统计一屏看完。

进院之后最容易变的不是准确率,而是阈值和导联习惯: 院里常见的 montage 和 TUEV 不完全一致,PLV 那边按 20 导搭出来的通路,换一套导联就得重新算边。 预实验真正要验的就是这种东西,而不是又刷一遍公开集的分数。

什么才算把一条线做完

三条任务特性直接换来三段结构,不能反过来。 先想「尖波和慢波不是一个尺度」才有多尺度编码器,先想「头皮距离描述不了通路」才有 PLV 多跳注意力, 先想「关键导联只有几个」才有局部-全局双路池化。 反过来先画架构再编理由,模块之间就散,消融也跑不出清晰的因果。

拓扑先验要算出来,不要画出来,也要能调稀疏度。 PLV 那一步看着很「分析」、离模型很远,但它把「哪些电极连成一片」变成了可直接当输入、 也可直接扫描的结构约束。τ 从 10% 扫到 90% 全摆出来,40% 最优; 换成普通自注意力 AUC 掉 2.22,这 2.22 个点就是「拓扑」本身的价。

选通道和保上下文不是二选一,是用跨路矩阵把两者接上。 只粗化到 4 个社区,全局分布有了、具体导联没了;只做 Top-6 选通道, 关键导联有了、整体模式没了。跨路矩阵那一步把社区信息搬回保留通道, 融合之后每个导联都带着自己所在社区的上下文。

证据这个词在两边长得不一样,标准却是一致的:能回溯到原始材料、能被第三方推翻、 三个月后有人翻出来还能撑住。脑电这边,一份证据是一个 1 秒片段的波形、 模型给出的概率、它选中的那几条导联,外加医师的确认或否决。

一条线做完的标志不是指标停在某个数上,而是它开始接受外部约束: 公开的划分、医师的否决、另一家医院不完全一致的导联习惯。 指标是在这些约束之前的东西,过了这一关它才说明得了什么。

顺带说一句:这几条在 Agent 侧也成立

这条线本来和 Agent 没什么关系,做完回头看,两边撞的是同一批约束。 一边是几十微伏的头皮电位,一边是模型的自然语言输出, 共同点是都能给出看起来很像答案的东西,都不能自己证明它对。

神经信息这条线 Agent 工程这条线 先把连接算出来,再谈模型 PLV 邻接是从信号里算出来的输入 先把依赖画出来,再写编排 工具与 Skill 的调用关系是一等公民 训练与评估按受试隔离 359 名受试训练、159 名评估,两端不重叠 评测集按场景分层 每层各用自己的题集,重跑要记录方差 细的和粗的都要留 Top-6 关键导联加 4 个粗化社区 证据也要分三层 原文只读、候选可推翻、生效层才对外 每条判定都能被否决 医师复核的结果回流进下一轮训练 写操作带审批与撤销 自治等级由能不能撤销决定 每个点都要指到具体设计 消融与 τ 扫描把收益归因到某个模块 涨多少才算真的涨 低于重复运行波动区间的提升不计入
两边撞在同一批约束上:连接关系要显式建模、训练与评测要隔离、细粒度与粗粒度要同时留、每条输出都要能被否决、每个数字要能归因到具体设计。

处理办法在两边也是同一套:主指标要配一个盯另一头的副指标, 每条输出留一个能否决的口子,每个数字要能归因到具体设计。 这几条在 Multi-Agent 编排运行时 里是准入和验收的一部分。

能力有多强是模型的事,能不能被验证、能不能被推翻,是工程的事。 把后面这件事做实,前面那件事才有被讨论的资格。