☰
基于一维卷积神经网络的变星光变曲线自动化分类实践
2026/10/29 6:59:59 网站建设 项目流程

夜里盯着一屏几十条光变曲线,靠眼睛分辨哪个是食双星、哪个是短周期脉动、哪个只是观测噪声造成的假信号——这件事我干过,而且干了不止一个晚上。问题不在于难,而在于量。一个中等规模的时域巡天项目,几年下来积累的目标数量是十万到千万级别,人工目视分类的速度大概是每人每天几百条,还得是有经验的人。所以当我第一次把“基于一维卷积神经网络的变星光变曲线自动化分类”这个方案落到实际流程里时,最直接的感受不是模型多先进,而是终于可以把人从重复劳动里拔出来了。这篇内容讲的就是这套方法的完整落地过程:变星光变曲线的数据形态和清洗逻辑、一维卷积网络为什么适合吃这种序列、网络结构和感受野怎么算、训练中类别不平衡和标签噪声怎么处理、线上推理管线怎么搭,以及我在实操里踩过的那些坑。适合已经有一点深度学习基础、又想把它用到时序科学数据上的读者,也适合天文学背景、想搞清楚模型内部在干什么的人。不需要你是两边都精通,只要愿意动手调一遍,这套流程是能复现的。

1. 方案定位:变星分类到底难在哪

1.1 光变曲线是一种“形态即信息”的数据

变星这个词听起来很物理,说白了就是亮度会变的恒星。造成亮度变化的原因分两大类:内因和外因。内因包括恒星自身的脉动(星体周期性膨胀收缩,亮度随之起伏)、爆发(表面或内部发生剧烈能量释放)、自转(表面存在亮斑暗斑,转一圈亮度变一次);外因最典型的是食双星——两颗星互相绕转,从观测者角度看会周期性互相遮挡,亮度出现规律的凹陷。不同物理机制在光变曲线上留下的印记是不一样的,而且相当直观:食双星的曲线有尖锐的“谷”,谷底形状可能是V形(两星完全分离)也可能是U形(两星靠得很近、发生形变),有的还有主食用和次食用两个深度不同的凹陷;短周期脉动变星的曲线接近正弦,上升支往往比下降支陡;长周期变星是缓慢的大幅度起伏;爆发型目标平时安静,某一刻突然拉高再指数式回落。

这意味着分类的核心依据是曲线的形态基元,而不是某个单一的数值特征。这一点非常关键,它直接决定了后面为什么要用卷积。传统做法是先从曲线里抽出一组标量特征——周期、振幅、偏度、峰度、上升下降时间比、不同分位数之间的流量差、各种变星专用指数,然后把这些特征喂给随机森林或者梯度提升树。这套方法能work,而且在小样本上很稳。但它的瓶颈在于:特征工程是人工设计的,你想到什么就有什么,想不到的就永远进不了模型;而且这些特征对采样不规则极其敏感,一个目标如果在某个相位区间没有观测点,偏度算出来就偏了,模型却不知道这是数据缺失导致的。

还有一个现实问题:混合型目标。有些人以为变星分类是干净的多分类问题,实际数据里有一批目标同时具备两种机制,比如食双星系统里的一颗子星本身还是脉动变星,曲线上会同时出现深食和小的周期性抖动。这类样本在人工标注时都很头疼,模型更不可能凭空解决。所以要提前接受一个事实:自动化分类器输出的是“形态归属”,不是物理证认,它的价值在于把90%的典型样本快速分流,把剩下的疑难样本推给人。

1.2 为什么是一维卷积,而不是别的模型

选型这块我纠结过挺久,把几条路都试了一遍,最后还是落在一维卷积上,理由不是它最强,而是它的归纳偏置和这个问题最匹配。

先说卷积本身。一维卷积核在序列上滑动,提取的是局部窗口内的形状模式。一个宽度为7的卷积核,本质上是学会“什么样的局部起伏组合值得关注”——可能是“连续三个点下降、然后两个点持平、然后快速回升”这种模式,恰好对应V形食的底部。多层堆叠之后,浅层学到的是边缘和斜率变化,深层学到的是完整的谷形、峰形、平台。这种层次化抽象和图像的卷积网络是一个道理,只是把二维空间换成了相位轴。

更关键的是平移不变性。光变曲线做相位折叠时,起点相位t0的选取是任意的,同一个目标,换一个t0,曲线整体左右平移,但物理上完全一样。如果用全连接网络直接吃512维的序列,这个平移会让输入向量彻底变化,模型得为每种平移都学一遍,纯属浪费容量。卷积加池化天然对平移不敏感,这个问题自动消失了。我在早期做过对比实验,同样的数据、同样的参数量,把第一层换成全连接,验证集准确率掉了将近9个百分点,而且训练过程明显更抖。

再说说为什么不用循环网络。序列长度512,用LSTM或者GRU当然能吃,但有两个实际障碍:一是串行计算,训练速度慢,在这种数据量级下每一轮多出来的时间很可观;二是长序列上的梯度传播不如卷积稳,尤其是当我们把相位折叠后的曲线当作输入时,真正有用的信息集中在少数几个窄区间(食的底部可能只占相位的5%),循环网络很容易把这些局部细节在长距离传递中稀释掉。至于Transformer,理论上自注意力能捕捉任意距离的依赖,但它对数据量的要求高得多,我们手上的标注样本量还撑不起一个从零训练的注意力模型,而且位置编码对于相位折叠序列来说意义不大——因为相位本身已经是有序的物理量,不需要模型再去学位置关系。我也试过用预训练时序模型做微调,效果不稳定,不同批次的数据上表现差异太大,最后放弃了。

还有一条被很多人忽略的路线:把曲线转成二维图像(比如相位-时间的热图或者格拉姆角场),然后用二维卷积。这种做法在某些任务上确实有增益,代价是预处理复杂、可解释性变差,而且转换过程本身会引入人为的超参数。对于光变曲线这种一维形态问题,直接用一维卷积更干净。

1.3 方案边界:什么能交给模型,什么不能

我在项目里给自己划了几条线,写出来供参考。

模型能做的:把形态分成有限的若干个大类,给出每一类的概率;在典型样本上达到接近人工的水平;批量处理,速度是人工的成千上万倍;对新数据做一致性判断,标出置信度低的目标。

模型不能做的:判断物理机制(比如区分“这是食双星”和“这是一个自带伴星的系统”);处理训练集中从未出现过的形态;替代周期搜索——周期错了,后面全错;在标注质量差的类别上给出可信结果。

这些边界必须在流程设计时就体现出来,比如推理阶段加一个置信度阈值,低于阈值的目标自动打回人工队列,而不是硬给一个答案。我见过太多项目在这里翻车:模型在测试集上报了个漂亮的数字,上线之后因为没人管低置信度样本,错误预测被直接写进星表,后面再纠正的成本高得离谱。

2. 数据准备:把原始测光变成能喂给网络的张量

2.1 从星等到归一化流量:这一步的坑比想象中多

原始测光数据给的是星等,星等是对数尺度,且方向相反(星等数值越小越亮)。直接喂星等行不行?理论上可以,但有两个问题:一是星等的零点在不同观测季、不同设备上会漂移,同类型目标的绝对亮度水平差异很大,模型容易学到设备特征而不是物理特征;二是星等是对数,曲线形态会被压缩,深食底部和浅食底部的差异被非线性地缩小,不利于卷积核捕捉形状。

所以第一步要做星等到流量的转换,用flux = 10 ** (-0.4 * mag),然后做归一化。归一化方式的选择是一个容易被略过但影响巨大的决策点。常见做法有三种:

  • 除以中位数流量,再减去1,得到相对变化。这种方式保留了绝对幅度信息,一个振幅10%的目标和一个振幅1%的目标,归一化后的曲线高度不同。
  • 除以自身流量的标准差。这种方式把振幅信息彻底抹掉,所有曲线被拉到同一个量级。
  • 用分位数差归一化,比如除以5%到95%分位之间的差值。介于上面两者之间,对异常值更稳健。

我最终选的是第一种,理由是振幅本身是重要的判别特征——短周期大振幅脉动和微幅转动调制在形态上可能都接近正弦,但幅度差一个量级。如果用第二种归一化,这部分信息就丢了,模型只能靠别的线索硬猜。为了让模型能同时利用“形态”和“幅度”,我在卷积塔输出的特征向量后面拼接了三个全局标量:归一化后的振幅、周期对数值、有效观测点数。这三个量走一个小的全连接分支,和卷积特征拼接后再进分类头。实测这个改动让宏平均F1提升了约2.1个百分点,而且主要提升集中在振幅差异明显的类别对上。

异常值剔除也不能省。宇宙线击中、背景目标污染、大气突变都会在曲线上留下孤立的尖点,这些尖点在卷积看来就是极强的局部模式,很容易带偏。我用的是基于中位绝对偏差的粗筛:对每个目标计算残差的MAD,偏离超过5倍MAD的点直接标记为无效。注意是标记而不是删除——因为我要保留时间轴上的完整性,后面用掩码通道告诉网络这些位置不可信。如果直接删掉,时间序列会出现不连续的大间隙,重采样时反而更容易出问题。

2.2 不规则采样与重采样:决定模型能看到什么

地面观测的光变曲线,采样是极其不规则的:有观测季的概念,某些月份天天有数据,某些月份一个点都没有;一晚上内部的采样也可能疏密不均。而卷积网络需要固定长度的输入。这中间的桥怎么搭,是整个流程里最容易出错的一环。

我试过三种方案。第一种是直接截断或补零到固定长度,简单粗暴,但破坏了时间跨度的一致性,一个覆盖三年的目标和一个只覆盖三个月的目标会被同等对待,这在物理上没有意义。第二种是等间隔插值重采样,把时间轴切成均匀网格后线性插值,问题是插值会把空缺区填成虚假的平滑曲线,模型学到的可能是插值算法的行为。第三种是先做相位折叠,再在相位轴上分箱。

最终采用的是相位折叠加分箱,因为光变曲线的判别信息在相位域最集中,折叠之后同类型目标的曲线会对齐到相同位置,卷积核学到的模式可以直接跨目标复用。具体步骤是:先用已有的周期估计(星表里的或者自己用相位色散最小化方法求的,后者通过扫描周期网格、最小化折叠后曲线的色散来定位最佳周期),把时间转成相位phase = ((t - t0) / P) mod 1.0;然后把相位区间[0,1)均匀切成N个箱,每个箱内如果有多于一个观测点,取中位数(比均值抗异常值);空箱先标记为缺失,再用相邻有效箱做线性插值填充,同时在掩码通道里标记这个位置是“填出来的”。

分箱数N的选择需要算一下。如果N取512,单个箱的宽度是相位的1/512,对于一个周期内只有80个有效观测点的目标来说,平均每6.4个箱才有一个真实观测,绝大部分箱都是插值出来的,曲线会被过度平滑,食的尖底会被磨圆。如果N取128,箱宽变大,覆盖率上去了,但窄食(占相位5%左右,也就是6到7个箱)的形态细节又不够。我的做法是分层:主力模型用256个箱,输入张量形状是[batch, 2, 256],第一通道是归一化流量,第二通道是掩码(1表示真实观测,0表示插值);同时在数据预处理阶段记录每个目标的实际相位覆盖率,覆盖率低于30%的目标不进分类模型,直接走规则分支或者人工。这个阈值是试出来的,覆盖率和分类准确率的关系在30%附近有一个明显的拐点。

2.3 相位折叠的双重风险:周期谐波与折叠伪影

相位折叠虽然好用,但它把两个风险放大了,必须专门处理。

第一个风险是周期谐波。自动周期搜索经常给出真实周期的整数倍或分数,比如一个真实周期为0.5天的食双星,搜索结果可能是1.0天。此时折叠出来的曲线会出现两个完全相同的食,形态上看起来像一个“双周期”的目标。更麻烦的是反过来,真实周期是1天的目标被搜成0.5天,两个食会被叠在一起,曲线看起来只有一个很深的谷。同一个物理类型的目标,因为周期搜索的谐波错误,会在数据里呈现出至少两种完全不同的形态,模型看到这些样本只会更加混乱。

处理方式我用了两层。第一层是在预处理阶段对每个目标做谐波校验:分别按P、P/2、2P折叠,计算折叠后曲线的“重复度”指标(把折叠曲线分成两半比较相似性,或者计算折叠后的相位色散),选取使曲线最平滑、重复度最低的周期。这一步不需要模型,纯数值计算,能修掉大部分谐波错误。第二层是在训练数据里保留一部分谐波错误的样本,作为数据增强的负面例子,让模型学会对这种形态不敏感——说实话第二层的收益不如预期的明显,因为谐波错误引入的形态差异太系统了,模型很难把它当作噪声忽略掉。所以重心还是放在第一层的清洗上。

第二个风险是折叠伪影。相位折叠时,第一个箱和最后一个箱在物理上是相邻的,但在一维张量里它们离得最远。卷积核在序列两端滑动时做padding,等于人为制造了一个不连续点。如果你的曲线在相位0.98到0.02之间有一个食,这个食会被切成人两半,分别出现在张量的头和尾,卷积核永远看不到完整的谷形。

解决办法很朴素:把折叠后的序列首尾拼接一段,也就是在训练和推理时对输入做循环填充,而不是零填充。具体做法是在预处理阶段就把序列向左和向右各延展若干个箱,形成长度略大于N的输入,然后随机裁出N个箱的长度——这同时又变成了一个有效的数据增强手段,因为裁剪起点的随机性等价于相位平移。我最后用的是延展128个箱、随机裁256个箱的方案,边界问题基本消失。

2.4 标签体系:类别不是越多越好

原始标注往往带着一大堆细分类别,有些类别之间只差一个物理参数,形态上几乎没有区别。这种情况下硬上细粒度分类,模型学到的只是一堆噪声。我在建标签体系时做了三轮合并,原则是:如果两个人看同一个曲线都可能给出不同答案,那这两个类别就必须合并。

最终的主分类体系大概是这样的:

类别主要形态特征典型周期范围样本占比
食双星单食或双食,谷底尖锐,有明确的相位锁定0.2 至 数十天高
短周期脉动接近正弦,高振幅,上升下降不对称数小时至数天高
长周期缓变缓慢大幅度起伏,单个周期内覆盖不完整数十天至数百天中
转动调制低振幅正弦,形态稳定,无尖锐结构数天至数十天中
爆发型平台加陡升再加指数衰减,呈单次事件无周期低
非变星与噪声平坦或近乎随机,无稳定周期结构无高
待定采样不足或形态混杂任意低

“待定”这一类必须保留,而且不能把它当成垃圾桶随便丢。我的做法是给它设一个上限比例(不超过总量的8%),超出的部分强制回到标注流程重新判定。这一类的存在让标注员不必在没法判断时硬选一个,减少了标签噪声;但如果不设上限,标注员会倾向于把难样本全丢进去,最后这一类变得没有物理意义,模型在它上面学到的只是“这条曲线长得奇怪”。

2.5 数据划分:防泄漏比防止过拟合更重要

时域天文数据的划分有一个隐藏陷阱:同一个目标可能在不同观测季有多段数据,甚至同一段数据可能被不同的预处理版本重复录入。如果按样本随机划分训练集和验证集,同一个目标的两个版本会同时出现在两边,模型在验证集上的表现会虚高,上线后立刻掉下来。我最早的一版模型就栽在这上面——验证集宏平均F1报0.91,实际部署到新季度数据上只有0.74。查了两天才发现训练集和验证集里有超过一千个目标重复出现。

正确的做法是按目标ID分组划分,同一个目标的所有数据必须落在同一侧。用分层分组划分,保证每一折里的类别比例和整体一致。另外,如果数据带时间信息,更严格的做法是按时间切分:用早年的数据训练,用最近一个观测季的数据验证,这样能同时检验模型对时间漂移的鲁棒性。我在正式评估时用的就是时间切分加分组切分的组合,两个结果都看,前者更能反映真实部署场景。

3. 网络结构设计:让卷积核去学形态

3.1 输入张量与维度约定

先把输入说清楚,后面所有计算都基于这个约定。单个样本的输入是一个形状为[2, 256]的张量:通道0是归一化流量,通道1是掩码。批大小我最后定在128,显存占用和梯度噪声之间比较平衡。

除了序列本身,每个样本还附带一个形状为[3]的全局特征向量:对数振幅、对数周期、有效观测点数的对数。这个向量不进卷积,而是走一个独立的支路。这个双输入结构是我在第三版模型里加进去的,前面提过,收益是实打实的。

3.2 卷积块堆叠与感受野计算

网络主体是四个卷积块,每个块的配置是“一维卷积 + 批归一化 + 激活 + 最大池化”。具体参数如下:

层卷积核输入通道输出通道步长池化
块1723212
块25326412
块356412812
块4312812812

感受野的递归计算方式是:设当前层的感受野为R、累积步长为J,则加一个卷积核大小为k、步长为1的卷积层后,R_new = R + (k - 1) * J;加一个核大小为k、步长为s的池化层后,R_new = R + (k - 1) * J,同时J_new = J * s。

从R=1、J=1开始:

  • 块1卷积(k=7):R = 1 + 6×1 = 7,J = 1
  • 块1池化(k=2,s=2):R = 7 + 1×1 = 8,J = 2
  • 块2卷积(k=5):R = 8 + 4×2 = 16,J = 2
  • 块2池化:R = 16 + 1×2 = 18,J = 4
  • 块3卷积(k=5):R = 18 + 4×4 = 34,J = 4
  • 块3池化:R = 34 + 1×4 = 38,J = 8
  • 块4卷积(k=3):R = 38 + 2×8 = 54,J = 8
  • 块4池化:R = 54 + 1×8 = 62,J = 16

最终感受野是62个相位箱。输入长度256,也就是覆盖了相位的24%。这个数字需要和物理事实对一下:相位折叠之后,窄食的宽度通常在5%到15%之间,对应256箱里的13到38个箱;宽食或双食结构可能占30%以上。62的感受野足够完整覆盖一个典型的食结构甚至一个食加一个相邻的峰,这是刻意设计的。如果感受野太小(比如只有20),卷积核只能看到谷的一半,学到的特征是不完整的;如果太大(比如覆盖整个相位),局部形态的区分度会被稀释,反而退化成全局统计量。这是我调了两轮kernel大小才定下来的,从全部用3x3改成第一层7、后面递减,验证集准确率提升了3个多百分点。

3.3 归一化、激活与正则的取舍

批归一化在每一层卷积之后都加了,位置是卷积和激活之间。这个选择主要是为了对抗不同批次数据之间预处理分布的小幅漂移——比如不同观测季的噪声水平不一样,归一化之后曲线幅度的统计分布会略有出入,批归一化能把这个漂移吸收掉一部分。代价是推理时对批大小敏感,如果线上是逐条推理,批归一化用的是滑动统计量,行为会和训练时不一致。这一点后面在推理管线里会专门提到。

激活函数用ReLU,没有换别的。试过GELU和Swish,在这套数据上差异在噪声范围内,没必要为了新而新。正则化用了两个:每个卷积块后加dropout(ratio从0.1递增到0.3),以及最后的分类头前再加一个0.4的dropout。数据量不算大,正则化不能省。另外权重衰减设了1e-4,配合Adam优化器。

分类头的结构是:卷积塔输出[128, 16](128通道、长度16),做全局平均池化变成[128],和全局特征支路的输出拼接成[128 + 16],过一个线性层降到64,ReLU,dropout 0.4,最后线性层输出类别数。全局特征支路就是两层全连接,3维扩到16维。

3.4 参数量与推理成本估算

参数量的手算过程如下:

  • 块1卷积:7×2×32 + 32 = 480
  • 块1批归一化:64
  • 块2卷积:5×32×64 + 64 = 10304
  • 块2批归一化:128
  • 块3卷积:5×64×128 + 128 = 41088
  • 块3批归一化:256
  • 块4卷积:3×128×128 + 128 = 49280
  • 块4批归一化:256
  • 全局支路:3×16 + 16 + 16×16 + 16 = 336
  • 分类头:(128+16)×64 + 64 + 64×7 + 7 = 9727

加起来大约15.2万参数,模型文件不到1MB。这个规模是刻意的:在标注样本量有限的情况下,参数量过大的模型会记住训练集的噪声。我试过一个参数量翻四倍的版本,训练集准确率能到0.99,验证集反而掉了,典型的过拟合。

推理成本方面,在单核CPU上跑一条样本(含预处理)大约需要8到12毫秒,其中模型前向大约2毫秒,剩下全是预处理开销——相位折叠、分箱、插值这些操作反而比神经网络本身贵。这一点非常值得强调:很多人优化模型推理速度,实际上瓶颈在数据准备。后来我把预处理做成了向量化的批量操作,一次处理一万条,平均到每条降到1.5毫秒以下,整体吞吐才提上来。

4. 训练流程与调参实操

4.1 类别不平衡:权重怎么给才合理

真实的巡天数据里,非变星和食双星的数量可能是爆发型的几十倍。如果不处理,模型会学到一个偷懒策略:把所有样本都预测成多数类,准确率看着还不错,但少数类的召回率接近零。这在科学上毫无价值。

我试过三种处理方式。第一种是重采样,对少数类过采样。问题是过采样会让模型在少数类的少数几个样本上反复训练,过拟合严重,而且我们用的是相位折叠序列,同一目标过采样出来的副本几乎一模一样,等于没增加信息。第二种是欠采样多数类,简单有效,但丢掉了大量数据,在数据本来就不多的情况下不合适。第三种是给损失函数加权,加权交叉熵,权重取w_c = (N / (K * n_c)) ** 0.5,其中N是总样本数,K是类别数,n_c是第c类的样本数。开平方是为了避免权重过于极端——如果直接用倒数,最稀有的类别权重可能是多数类的上百倍,训练时梯度会被少数类主导,模型在多数类上崩掉。

实测下来第三种最稳。用加权交叉熵之后,宏平均F1从0.68提到0.79,少数类的召回率从接近0提到0.6以上。后来我又试了Focal Loss,在稀有类上确实还有一点提升,但训练更不稳定,需要调聚焦参数,考虑到维护成本,最后没有采用。

这里有个细节值得说:加权之后,模型输出的概率不再具有校准意义——它被系统性地推向少数类。如果后续要用概率做阈值判断,必须做校准,这一节后面单独讲。

4.2 数据增强:哪些是有物理意义的,哪些是自欺欺人

数据增强在时序数据上要格外小心,因为很多看起来合理的变换会破坏物理一致性。我列一下试过的几种和结论。

相位平移,也就是把序列循环滚动一个随机偏移,这个是最安全也最有效的。因为相位起点本来就是任意的,滚动不改变任何物理信息。实测单独加这一项就能提升约1.5个百分点。

幅值缩放,对流量乘以0.9到1.1之间的随机系数。这个要慎用。因为前面说了,振幅是判别特征,缩放会让模型学到“振幅不重要”,反而有害。我的处理是只在训练时以较低概率(0.3)应用,而且缩放幅度限制在0.95到1.05之间,同时全局特征支路里的振幅是原始值,不跟着缩放——相当于告诉模型“曲线看起来幅度变了,但真实幅度是这个数”,让它学会区分形态和幅度。这个设计有点绕,但确实比不做或者随便做强。

加性高斯噪声,标准差按每个目标自身的噪声水平估计值来定,而不是全局固定。固定噪声会导致高信噪比样本被过度扰动,低信噪比样本扰动不足。按目标自适应加噪,效果更接近真实的观测条件变化。

随机遮挡,把序列中随机一段(长度占比5%到15%)的值置零并把掩码置零,模拟观测窗口缺口。这个有效,但要注意遮挡的位置分布不能完全均匀随机,因为真实的观测缺口往往集中在特定相位(比如目标在白天升起的那段时间永远观测不到)。我后来改成按每个目标的实际观测窗口分布来采样遮挡位置,效果更好,但实现复杂度上去了,属于锦上添花。

有几类增强我明确不做:时间轴的随机缩放(会改变周期,破坏物理意义)、对整条曲线做翻转(光变曲线的时间方向有物理含义,上升支和下降支不能互换)、随机打乱局部顺序(彻底破坏形态)。这些在通用时序任务里可能有人用,但在这个问题上就是自欺欺人。

4.3 学习率、批大小与早停

优化器用Adam,初始学习率3e-4,配合余弦退火把学习率降到1e-6。批大小128,训练80到120轮,早停的监控指标是验证集的宏平均F1而不是损失——因为类别不平衡的情况下,损失容易被多数类主导,F1更能反映真实的分类能力。早停的耐心值设15轮,同时保存验证F1最高的那个检查点。

这里有个实操上的小技巧:前5轮用较小的学习率做warmup。因为批归一化在训练初期统计量不稳,直接用大学习率容易让模型一开始就掉进一个糟糕的区域。warmup之后训练曲线的收敛明显更干净,最终指标也更稳定。这个技巧在视觉任务里很常见,在时序数据上同样有效。

训练过程中我记录了每一类的召回率变化,发现一个有意思的现象:食双星和短周期脉动这两类收敛得很快,大约30轮就到了平台;而“待定”类和爆发型收敛很慢,80轮之后还在缓慢提升。这说明前两类的形态特征确实鲜明,后两类的判别边界模糊。针对这个,我后来对后两类用了更高的采样权重,让它们在每个batch里出现得更频繁,收敛速度有所改善。

4.4 评估指标:别只看准确率

这套任务上,准确率是最没有参考价值的指标。原因很简单:类别不平衡。如果非变星占40%,一个把所有样本都预测成非变星的模型就有40%的准确率,但它毫无用处。

我用的指标体系是三个层次。第一层是宏平均F1,把每个类的F1算出来再平均,不受类别比例影响,这是主指标。第二层是每一类的召回率和精确率,单独看,因为不同应用场景对这两者的偏好不一样——如果目的是构建一份高纯度的变星候选表,精确率优先;如果目的是尽量不遗漏稀有类型,召回率优先。第三层是混淆矩阵,这个最能暴露问题,后面单独讲。

还有一个小细节:评估时用分层自助采样(bootstrap)估计指标的置信区间,而不是只报一个点估计。因为验证集里有些类的样本只有几十个,单次评估的波动可能有正负0.05,不报区间的话很容易被随机波动误导,以为某个改动有效其实只是噪声。

4.5 置信度校准与拒识机制

前面提到,加权交叉熵会让输出概率失去校准性。所谓校准,就是模型说“我有80%的把握”时,实际正确率应该也接近80%。加权之后,模型对少数类会过度自信,对多数类反而不够自信。

校准的方法我用的是温度缩放:在验证集上拟合一个标量温度T,把logits除以T再过softmax。T的搜索范围0.5到3.0,用最小化期望校准误差来选。这个操作不改变预测类别(因为单调变换不改变argmax),但改变了概率的数值意义,让后面的阈值判断变得可靠。实测校准之后,期望校准误差从0.11降到0.03左右。

校准的直接用途是拒识。推理时,如果最大类概率低于阈值,或者前两类的概率差小于某个间隔,这个目标就被标记为“低置信度”,推入人工复核队列。阈值的选择是一个权衡:阈值高,人工队列短但漏掉的风险大;阈值低,人工队列长但更安全。我的做法是先画一条“拒识率-剩余样本准确率”的曲线,找一个拐点。实测在拒识率10%左右时,剩余样本的准确率能提升6到8个百分点,这个交换是划算的。

拒识机制还有一个隐性好处:它给了业务方一个可调的旋钮。不同项目对纯度和完备性的要求不同,通过调阈值就能在两者之间滑动,而不需要重新训练模型。

5. 结果解读与线上推理管线

5.1 混淆矩阵暴露出的三类系统性问题

模型训完之后,我把混淆矩阵摊开逐格看,发现了三个反复出现的错误模式,值得单独说。

第一类是食双星和转动调制的混淆。这两类的曲线都可能有周期性的凹陷或者起伏,区别在于食双星的谷更尖锐、更窄,转动调制的起伏更平滑。但当采样不足、食的底部只被两三个点采样到时,尖锐特征被抹平,模型就会判错。解决方式不是改模型,而是改数据:在预处理阶段计算每个目标的“谷形锐度”指标(用相位折叠后曲线的二阶差分极值来衡量),对锐度指标处于模糊区间的样本,在训练时给予更低的损失权重,让模型不要在这些本质上无法判别的样本上纠结。

第二类是短周期脉动和非变星的混淆。这一类的根源是归一化。非变星经过中位数归一化之后,剩下的就是纯噪声,而噪声在短序列上偶尔会呈现出类似正弦的起伏,尤其是当观测点数少的时候。模型学会了这种伪模式。对策是在训练集里显式加入一批“纯噪声合成样本”:从真实非变星目标里估计噪声水平,然后生成同样长度、同样噪声水平的随机序列,标为非变星。这批合成样本让模型对噪声形态的容忍度大幅提升,这一对类别的混淆率下降了一半以上。

第三类是长周期缓变和“待定”的混淆。这个其实合理,因为长周期目标在一个观测季内往往只覆盖了周期的一小部分,曲线呈现单调上升或下降,信息量本来就不足。我没有强行优化这一对,而是在后处理规则里加了一条:如果模型判为长周期但周期估计的置信度低,就强制改判为“待定”。这种“模型加规则”的混合策略在边界模糊的类别上是必要的,纯端到端并不总是最优。

5.2 推理管线的搭建与几个必须对齐的细节

线上推理和离线训练最容易出问题的地方是预处理不一致。我列几个真实踩过的:

批归一化的模式。训练时是model.train(),批归一化用当前batch的统计量;推理时必须model.eval(),用滑动统计量。有一次部署时忘了切换,前几百条结果的分布明显异常,查了半天才发现是这个原因。现在我在推理脚本里强制在加载模型后立刻调用eval(),并且加了一条断言检查。

预处理的超参数必须版本化。分箱数、归一化方式、插值策略、覆盖率阈值,这些参数在训练和推理时必须完全一致。我的做法是把这些参数写进一个配置文件,和模型权重一起打包,推理时从配置读取而不是硬编码。听起来是常识,但在快速迭代阶段非常容易出岔子——改了一版预处理忘了同步推理端,结果整批结果偏移。

相位覆盖率不足的目标要有兜底分支。前面说了覆盖率低于30%的目标不进模型,但实际运行时这个判断必须在预处理之后、模型之前做,而且要有明确的日志记录,否则这批目标会静默消失。

批量推理要按长度分组。虽然输入长度固定,但预处理阶段的开销和原始观测点数强相关,按点数排序后分批处理能减少内存峰值。这个优化在数据量大时效果明显。

整个推理流程大概是:读入原始测光表 → 星等转流量 → 异常点标记 → 周期获取与谐波校验 → 相位折叠与分箱 → 覆盖率检查 → 组装张量 → 模型前向 → 温度校准 → 阈值判断 → 输出分类结果加置信度加是否拒识的标记。每一步都记录中间统计量,方便出问题时回溯。

5.3 模型到底在看哪里:可解释性检查

分类模型最怕的是“指标好但理由错”。为了确认模型学到的是形态而不是数据集的偏置,我做了几项检查。

第一项是遮挡实验。对验证集里的样本,逐个把相位区间上的小段置零,看预测概率的变化。如果模型真的在看食的底部,那么遮挡食底时预测概率应该显著下降;如果遮挡别的地方也大幅下降,说明模型依赖了不该依赖的东西。实测下来,食双星类的关键区间确实集中在谷的位置,短周期脉动的关键区间分布在峰和谷的转折处,符合预期。

第二项是类激活映射的简化版。取最后一个卷积层的特征图,按类别对通道加权求和,得到一条在相位轴上的重要性曲线。这个曲线和遮挡实验的结论基本一致,而且更直观。有一批样本上,重要性曲线的峰值出现在相位0附近,一开始我以为是模型学到了边界伪影,后来检查发现这些样本的食恰好就在相位0附近,是正常现象。这个例子说明可解释性工具的输出也需要结合物理背景解读,不能盲信。

第三项是跨数据集的稳定性检查。用早年的数据训练,用最近一个观测季的数据测试,看各类的召回率是否稳定。这一步暴露了一个问题:长周期缓变类在新数据上的召回率掉了将近20个百分点,原因是新季度的观测窗口变短,覆盖的相位范围更小。这个发现直接促使我把“相位覆盖率”作为特征加进了全局支路,让模型知道自己的输入有多不完整。

6. 踩坑实录与常见问题速查

6.1 典型问题排查表

下面这张表是我和同事在实际项目里积累下来的,按症状归类,遇到问题时可以对着查。

症状最可能的原因排查方法处理方式
训练集F1很高,验证集明显偏低数据泄漏或过拟合检查是否有同一目标跨集合;对比参数量与样本量按目标分组划分;减小模型或加强正则
某一类召回率长期接近零类别极度不平衡统计各类样本数加权损失;提高该类采样权重
验证指标每轮波动很大批太小或验证集太小看不同随机种子的结果差异增大批大小;用分层交叉验证
非变星被大量误判为脉动归一化放大了噪声检查非变星样本归一化后的幅度分布加入合成噪声样本;提高判决门槛
同类型曲线出现两种截然不同的形态周期谐波错误按P、P/2、2P分别折叠对比加谐波校验;清洗训练集
食的谷形明显变形或被切断相位折叠边界效应检查食是否跨相位0改用循环填充;首尾延展后随机裁剪
训练损失不下降学习率过大或预处理异常打印首批输入统计量加warmup;检查归一化范围
线上结果和离线评估差距大预处理不一致或模式未切换逐步骤比对中间结果配置版本化;强制eval模式
模型对振幅不敏感归一化抹掉了幅度信息对比不同振幅样本的输出改用保幅归一化;拼接全局特征
校准后概率仍然失真校准集与测试集分布不一致分季度看校准误差按时间段分别校准或扩大校准集

6.2 三条用代价换来的经验

第一条,关于标注质量。我一开始以为模型效果的上限由算法决定,做完这个项目才明白上限由标签决定。早期标注是一批志愿者做的,规则给得比较粗,同一个目标不同人标出不同结果的比例大约有12%。这批数据训出来的模型,验证指标怎么调都上不去。后来重新制定了标注规范,明确了每类的判定边界,对分歧样本做双人复核,标签一致性提到95%以上,模型指标立刻上了一个台阶。所以如果重来一次,我会把至少一半的精力放在标签体系和质量控制上,而不是调网络结构。

第二条,关于“看起来很好”的指标。有一个版本,验证集宏平均F1到了0.88,我挺满意,准备上线。上线前做了一次按观测季的分解评估,发现有兩個季度的数据上指标只有0.7出头。追查下去发现,这两个季度的数据是用不同版本的预处理管线生成的,归一化的零点有系统性偏移,模型把偏移当成了类别信号。这件事让我养成了一个习惯:任何指标都要做分组分解,按时间、按类别、按数据来源分别看,只看总体数字等于自欺欺人。

第三条,关于模型和规则的配合。纯端到端的模型听起来优雅,但实际部署时一定要留规则接口。比如覆盖率不足的样本要能绕过模型,周期置信度低的样本要能强制降级,某些类别之间的互斥关系(一个目标不可能同时是食双星和爆发型)可以用后处理规则约束。这些规则不是在否定模型,而是在模型能力边界之外补上必要的确定性。我在最终版本里保留了大约十行后处理规则,它们处理了约3%的样本,但这3%恰恰是最容易出错的部分。

6.3 几个可以直接抄的配置

最后把一套跑得比较稳的配置整理出来,省得有人从零试。

输入侧:相位分箱256,首尾各延展64箱后随机裁剪;归一化用中位数保幅方式;掩码通道必带;覆盖率阈值0.3;周期谐波校验必做。

网络侧:四个卷积块,核大小依次7、5、5、3,通道32、64、128、128,每块后接批归一化、ReLU、dropout(0.1递增到0.3)、最大池化2;全局平均池化后拼接三个全局标量(对数振幅、对数周期、对数观测点数);分类头两层,中间维度64。

训练侧:Adam,初始学习率3e-4,余弦退火到1e-6,warmup 5轮;批大小128;加权交叉熵,权重按样本数倒数的平方根归一化;早停监控宏平均F1,耐心15轮。

后处理侧:温度缩放校准;最大概率阈值和类别间隔阈值双重拒识;覆盖率与周期置信度触发规则降级。

这套配置在我的数据上宏平均F1大概0.82到0.85,拒识率10%时剩余样本准确率约0.93。数字不算惊艳,但胜在稳定,换一个观测季的数据波动在3个百分点以内。对自动化分流的场景来说,这个稳定性比峰值指标重要得多。

还有一点想补充:这套流程不是一次性的。数据在更新,模型需要周期性重训,而每次重训都会遇到新的边缘情况——新的观测模式、新的仪器特性、新的噪声来源。我在项目里设了一个月度回顾机制,把拒识队列里人工复核过的样本回流到训练集,形成一个闭环。这个闭环跑起来之后,模型的表现会随着时间缓慢爬升,而不是随时间衰减。这件事的投入不大,但对长期维护来说几乎是必需的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询