☰
机器学习实战:天文数据挖掘中的特征工程与类别不平衡处理
2026/10/19 5:52:35 网站建设 项目流程

去年做了一期很有意思的实验:拿到一批大规模天文表格数据,几十万行的样本,每一行是一个天体对象,主要特征是各个波段的星等、红移、形状参数,任务是判断这个天体到底是恒星、星系还是类星体。这就是天文数据挖掘实验,也是我机器学习实战系列的第五篇。这个方向听起来很“硬核”,但上手之后你会发现,它跟普通商业分类问题在流程上没本质区别,真正麻烦的是数据本身的物理特性和一堆隐藏得很深的坑。所以这篇不只讲模型调参,更想把天文数据在挖掘过程中最容易踩的几个雷区,以及我最后是怎么排掉它们的,一次性说清楚。想接触数据竞赛、或者对特征工程和类别不平衡处理有好奇心的朋友,这篇应该能给你不少可复现的思路。

1. 赛题拆解:天文数据挖掘到底在预测什么

1.1 先从物理背景说起:为什么需要机器学习

天文巡天望远镜每天产出的数据是海量的,天体影像和对应的测光参数不断涌入数据库,如果靠天文学家人工去识别每个天体的类别,工作量完全不可接受。于是一个很自然的做法出现了:把已经人工确认过类别的天体样本作为训练集,让模型自动总结“恒星长什么样、星系长什么样、类星体长什么样”,再去给未标注的天体做分类。

这个流程本质上是监督学习。物理上,恒星是银河系内部的点源,星系是银河系之外的扩展源,类星体则是极远距离上非常明亮的活动星系核,从图像上看也接近点源。仅仅靠肉眼去看一小块图像,这三个类别并不好区分,但它们的测光颜色特征、红移分布、形态参数往往有明显差异。机器学习的任务,就是把这些差异从特征空间里找出来。

我做的这场实验中,官方给的数据就是一张大宽表,每行代表一个天体,包含了几个常用波段的星等值、红移估计、若干形态拟合参数,以及标签列。看起来平淡无奇,和常见的信用评分表格没什么两样,但这正是迷惑性的开始:天文数据列名的物理含义,决定了你不能把它当成普通数值随意处理。

1.2 任务定义与评价指标

赛题是典型的三分类:样本标签共有恒星、星系、类星体三类。训练集大概有十几万条,测试集是未知标签的结果,需要提交每个样本的概率或类别判断。这种任务如果只看准确率,很容易被带偏——比如三类样本本来就不平衡,恒星占了七成,一个“全预测成恒星”的模型就能拿到不错的表现分,但没有任何实际意义。

这类竞赛通常用两类指标,一个是宏平均F1,一个是AUC。宏平均F1先把每个类别的F1算出来,再取算术平均,所以稀有类别哪怕数量很少,也会在指标里占同样的权重,缺了它模型分数立刻掉下来。AUC则更关心排序能力,不依赖阈值选择。我这次以宏平均F1为主,AUC作为辅助参考,训练和调参都围绕宏F1来做,阈值也在验证集上单独搜索,尽量让每一类都被照顾到,而不只是追着大头样本跑。

这里有一个很容易忽略的小细节:训练集和测试集的天体可能来自不同的天区或观测时段,特征分布会漂移;赛题组一般不会给你提示,但如果你只按随机划分来交叉验证,可能会得到一个偏乐观的分数。我习惯在训练集里再按红移或坐标分桶,做“分区交叉验证”,这样测出来的成绩更接近真实测试集的难度。

2. 拿到训练集之后的第一天:数据探查与三项关键预处理

2.1 天文表格数据的“脏”与“怪”

读入数据后我做的第一件事不是看分布,而是逐列统计缺失值比例、取值范围和单位。天文数据里有两种“非正常缺失”非常常见:一是某波段没有探测到光子,记录为“未检测到”;二是探测器动态范围有限,亮度过高发生饱和,记录为截断值。这两种情况都会以缺失值或异常大/小的数值出现在表里,但它们的物理含义完全不同。

第一种缺失往往意味着“这个天体在这个波段很暗”,这对判断类别是有用的信息,不能简单填0或填均值。第二种饱和值则说明光度测量不可信,如果还拿它去算颜色指数,会算出很离谱的负值,直接污染特征。我采用的办法是保留一个原始的缺失标记列,把缺失单独编码为-999,再额外增加一列0/1标记表示“该波段是否有效探测”,让模型自己学会利用这个信息。实际涨分不多,但稳定性提升明显。

单位也是常见的坑。星等本身是对数亮度,越小越亮;有些列给的是星等,有些列给的是通量。如果不小心把一个通量列当成星等列去算颜色,特征物理意义就完全错了。我在探查阶段把每个列的最小值、最大值、分位数都过了一遍,凡是出现“负星等”或“异常小数”的列都会回头核对口径。这个习惯帮我提前发现了三处单位不统一的列。

数据探查阶段我还会快速做一次特征相关性热力图。天文数据的特征之间天然高度相关,比如五个波段的星等彼此就是强相关的,直接丢进树模型虽然也能跑,但会稀释真正有判别力的组合特征。相关性分析不仅是为了筛选,更是为了理解哪些波段组合可能更有物理区分度,给后面的手工特征构造指路。

2.2 类别不平衡不是小问题

天文样本里恒星数量远超类星体,这种不平衡是最典型的干扰项。不能只靠把所有样本扔进模型让它自己学,因为多数树模型在默认参数下会偏向多数类,导致稀有类别的召回率很低。我开始的第一版基线没有做任何处理,宏F1只有0.71,其中类星体一类的F1惨不忍睹,才0.45左右。

修正方式里我用到了三管齐下:分层K折保证每一折的训练集里三类比例差不多;模型里加上类别权重参数,让稀有类别在损失函数中获得更大惩罚;评估时直接看宏F1而不是准确率。这三条里最核心的是分层K折,因为天文样本的组织顺序往往跟观测天区相关,如果直接随机切,会出现某一折缺少某个类别的极端情况。

还有一个容易踩的坑是过采样。有人会把稀有类别复制几倍,让模型更容易看到它。但在天文数据里,类星体样本本来就存在较大的观测噪声,简单复制只会让模型记住少量样本的噪声模式,验证集上虚高,线上反而退步。我的做法是不做过采样,全靠类别权重和更精细的特征把稀有类别区分出来,结果反而更稳。

2.3 缺失值填充的物理含义

表面上看,用中位数填充缺失值很常见,但天文数据里不能一概而论。类星体在光学波段可能很亮,但是在某个紫外波段探测不到,这本身就是“该天体在该波段辐射很弱”的信号。如果把这个缺失填成全局中位数,等于抹掉了这个关键线索。

我的处理是把缺失分成两类:如果缺失比例很低,直接用该类别内部的分布去随机采样填充;如果缺失比例很高,宁可保留专门的缺失标记特征,也不要做过多猜测。一个很实用的操作是给每个缺失率高的列都额外生成一个“是否缺失”的布尔特征,树模型能从这些布尔特征里分裂出非常稳健的规则,比如“紫外波段缺失且红移大于1的天体更可能是类星体”。这类规则人很难事先想到,但树模型非常擅长学习,而且几乎不会过拟合。

3. 特征工程:天文领域的“物理先验”是最大的提分武器

3.1 颜色指数与星等:最有解释力的组合特征

天文数据挖掘里最经典的特征就是颜色指数,本质上是两个波段的星等差值。为什么会这么有用?因为恒星、星系、类星体在光谱能量分布上的差异,会直接体现在不同波段的亮度对比上:恒星往往比较“红”,星系在光学波段温和地分布,类星体因为中心引擎极亮且红移高,在某些波段会出现强烈的颜色异常。

我拿到的基础列是u、g、r、i、z五个波段的星等,于是我用两两组合构造了一个颜色指数家族,比如u-g、g-r、r-i、i-z,以及跨越更大波段的u-r、g-z。这一步几乎零成本,却让模型的宏F1从基线直接涨了两三个点。关键原因在于:模型不需要自己去组合特征,就能直接在颜色平面上找到非线性边界。

我建议构造这类特征前先画几张小样本的散点图,检查不同类别在颜色空间里的分布重叠程度。如果某两个颜色指数几乎完全相关,只保留一个即可,没必要全部塞进去。我当时看到g-r和r-i存在较强的相关性,就删掉了其中一个原始颜色特征,让模型把注意力放在信息量更高的组合上。

3.2 从形态参数里挖出更细的线索

除了测光颜色,表中还有形态参数:类点源程度、半光半径、椭圆率、表面亮度拟合值等。这些参数直接关系到天体是点源还是扩展源。恒星的半光半径非常小,近似于点扩散函数;星系有可分辨的延展结构,半光半径会明显偏大;类星体虽然本质上位于极远的星系中心,但观测上常常也呈点源,形态参数会和恒星混淆。

所以单看形态参数并不能很好把恒星和类星体分开,但它们组合起来能先排除掉大量星系样本,让模型面对的分类边界更清晰。我额外构造了几个比值型特征:半光半径与点扩散函数半径的比值、表面亮度与星等的比值、椭圆率与颜色指数的交互项。这类物理量纲不一样的比值特征,树模型在分裂时能产生更平滑的规则,对噪声也更鲁棒。

有一点要提醒:形态参数如果是从图像模型里拟合出来的,拟合质量在各观测条件下差异很大。我会按“拟合置信度”做一个筛选,把低置信度样本做上标记,而不是直接删掉。因为低质量拟合本身可能暗示目标很暗、很边缘,这些信息有时恰恰对分类有帮助。

3.3 特征筛选:不要无脑堆特征

刚开始我倾向于把所有可能的特征都堆上去,觉得树模型会自动挑。后来验证集分数不升反降,而且是那种反复波动的小幅下降,才意识到冗余特征太多会让分裂选择不稳定,尤其当特征数量超过样本数量到一定程度的时候,树模型也会出现方差变大。

我用LightGBM自带的重要性排序做了一轮筛选,去掉重要性排名靠后而且与高重要性特征相关系数超过0.8的列。这一轮特征数量从40多降到24个,验证宏F1反而涨了0.4个百分点左右。后续我又加入了一些物理先验组合特征,再重复一轮筛选,逐步形成最终的特征集。

筛选时别只看gain重要性,还要看分裂次数。有的特征重要性高但分裂次数少,说明它只在少数样本上提供了很大增益,这种特征在交叉验证里波动大。我更偏向选用分裂次数中等、gain稳定的特征,它们在跨天区、跨波段的泛化上表现更好。这个经验在最后几次提交中得到了印证:删掉几个高波动特征后,公共榜和本地验证的差距明显缩小了。

4. 模型选型与训练策略:为什么我选了树模型而非深度学习

4.1 表格数据场景下树模型的基本盘

说实话,我之前也试过把特征标准化之后塞进一个全连接网络,效果一言难尽:调参花了两倍时间,最后宏F1只和LightGBM默认参数打平,稳定性还差一些。对于这种特征维度几十、样本量十几万的表格数据,梯度提升树依然是性价比最高的选择。

原因不复杂:天文特征之间大量存在阈值型规律,比如“红移高于1.5且u-g颜色异常”这样的规则,树模型天然擅长捕捉;而神经网络要靠大量参数去拟合同样的分片边界,需要更多数据和更精细的正则化。LightGBM和XGBoost在单机上训练速度也够用,不需要上GPU,迭代效率高得多。

我最终选择LightGBM,是看中它对类别特征的天然支持、内置的正则化和直方图优化。XGBoost我也跑了同样的特征输入,分数基本相当,但LightGBM的早期停止更平滑,调参量略少。这里没有标准答案,重点是别把时间浪费在反复比较两个库上,选一个吃透参数和特征就好。

4.2 训练策略:分层采样、早停与交叉验证

我采用五折分层交叉验证,每一折训练出一个模型,再用五折的验证概率拼接成“干净”的预测。这样得到的验证宏F1和真实测试集的表现高度相关,比单个训练验证划分可靠得多。

每折训练时,我都在训练子集里再留出最后15%作为早停验证集,用early stopping控制迭代轮数,防止单折过拟合。模型参数上,学习率设为0.03,叶子数限制在127左右,最小样本数设成50,特征采样比例0.8,bagging比例0.8。这些参数适合表格特征数量在二三十维、样本十几万的情况,如果你的特征量级差异很大,记得按比例调整。

一个被反复验证有效的小策略:在交叉验证里记录每一折验证集的类目别F1,不要只看平均。我在第三折的类星体F1明显比其它折低,排查发现那一折覆盖的天区里近一半类星体的颜色分布跟整体不一致,属于观测深度差异导致的分布漂移。如果没有分区认识,我会误以为是模型不稳定,从而乱调参数。这提醒我:交叉验证不仅要看均值,更要看波动来源。

4.3 概率校准与阈值搜索

模型输出的概率分数在竞赛场景里往往不是最终提交的类别标签,尤其是用宏F1评估时,直接按0.5阈值判类会吃亏。我在验证集上遍历阈值,找出让宏F1最高的判定边界。三分类问题里,我把类星体看成“高优先级类别”:先判断某样本是类星体的概率是否超过其专属阈值,再在剩余样本里用另一个阈值区分恒星和星系。

这样做之后,宏F1又涨了0.5到0.8个百分点。原因在于宏平均指标对不同类别的阈值敏感度完全不同,统一阈值是很粗糙的做法。如果你也面对类别不平衡的多分类,强烈建议把阈值当成可优化参数,而不是固定0.5。

概率校准我用了Platt缩放和等渗回归两种,对比后发现等渗回归在这个数据上提升更大。但这个结论有风险:等渗回归在验证集上可能出现过度拟合,尤其是稀有类别样本数很少时。我采用五折内做嵌套校准,把校准器也放进交叉验证流程里,避免高估校准带来的收益。最终提交的概率就是校准后的五折平均概率,这个输出再配合阈值搜索,基本就是我的最优配置。

5. 比赛复盘:从榜单一度冲到高位又掉下来的几个教训

5.1 公共榜分数波动:不要过度相信单次结果

比赛进行到中段,我用一个版本特征提交上去,公共榜分数一下冲到了前列,当时还挺兴奋。结果第二次提交同样的模型加了一点微小变化,分数掉了一大截。后来才明白,公共榜只是测试集的一个小抽样,样本量不大,单次提交的分数方差很高。我差点就因为一次好成绩选择了错误方向。

从那以后我给自己定了规矩:任何改动必须先在本地交叉验证上有明确的提升,而且这个提升要在至少三折上同步出现,才值得去提交一次公共榜。公共榜更多是用来确认“没有发生严重的分布偏移”,而不是做细粒度调参。顺着公共榜反复试探,很容易在少量样本上过拟合,线上私有榜一换样本就露馅。

5.2 误把稀有样本当成噪声删除的后悔事

有一版特征工程里,我发现类星体样本里有一些点在颜色空间上跟恒星高度重叠,看起来很像异常值,于是顺手用聚类算法把它们标出来,当成噪声删掉了一部分。验证集宏F1不但没涨,反而掉了一个多点。排查之后发现,这些“重叠点”其实是一类低红移类星体,它们的物理性质与普通类星体不同,在颜色上确实更接近恒星,但正是这种难样本才最有价值。

在普通数据竞赛里清洗异常值通常很安全,但在天文数据里,“看着奇怪”的样本往往是物理上有特殊意义的天体,不能按统计离群点处理。我的教训是:任何删除操作之前,先去查一查这些样本在物理参数上的分布,看看是否有某种一致性。如果有,它大概率不是噪声,而是尚未被你的特征捕捉到的子类。

5.3 集成与后处理:最后涨分的几个百分点

单模型五折交叉验证的宏F1稳定之后,我做了两件事来榨取最后的分数。第一,用LightGBM和XGBoost各自训练五折模型,对概率做加权融合,权重按验证集宏F1来配比,微调后整体涨了约0.3个点。第二,用RuleFit思路做了一点简单后处理:对预测概率接近判定边界的样本,用物理规则做修正,比如“半光半径极大且颜色偏红的样本更可能是星系”,把这些规则作用在边界样本上,可以小幅提升宏F1。

这类后处理属于经验性操作,幅度不能太大。我会先在验证集上反复模拟,确保每条规则带来的收益大于可能引入的噪声。最后提交版本融合了树模型的概率、学到的特征边界、以及少量物理规则修正,整体比最初的基线高出了将近8个百分点,这个过程中真正拉开差距的不是某个神奇模型,而是对天文特征含义的理解和对验证流程的坚持。

6. 可复现的代码骨架与提交检查清单

6.1 数据加载与特征构造的代码结构

下面给出一段适配这类表格数据竞赛的代码骨架。核心是特征构造函数和缺失标记处理,其他部分可以直接复用。

import pandas as pd import numpy as np import lightgbm as lgb from sklearn.model_selection import StratifiedKFold def build_features(df): df = df.copy() bands = ['u', 'g', 'r', 'i', 'z'] for b in bands: col = f'{b}_mag' df[f'{b}_missing'] = df[col].isna().astype(int) df[col] = df[col].fillna(-999) # 颜色指数,两两波段星等差值 for i in range(len(bands)): for j in range(i + 1, len(bands)): b1, b2 = bands[i], bands[j] df[f'{b1}_{b2}_color'] = df[f'{b1}_mag'] - df[f'{b2}_mag'] # 形态参数组合 df['half_light_ratio'] = df['half_light_radius'] / (df['psf_radius'] + 1e-6) df['surf_brightness_per_mag'] = df['surface_brightness'] / (df['r_mag'] + 1e-6) return df

这段代码有几点要说明。缺失值我用-999填充,同时保留了缺失标记列,是因为树模型可以把-999当成一个独立节点去分裂,不需要它像实数一样有物理含义。填0也可以,但务必保证不会与真实数值范围混淆。颜色指数构造时如果遇到缺失,减法会得到-999减去另一个值,结果同样进入-999量级,配合缺失标记也不会误导模型。

6.2 训练与预测的主流程

def train_lgb(X_train, y_train, X_val, y_val): dtrain = lgb.Dataset(X_train, label=y_train) dval = lgb.Dataset(X_val, label=y_val, reference=dtrain) params = { 'objective': 'multiclass', 'num_class': 3, 'learning_rate': 0.03, 'num_leaves': 127, 'min_data_in_leaf': 50, 'feature_fraction': 0.8, 'bagging_fraction': 0.8, 'bagging_freq': 1, 'metric': 'multi_logloss', 'verbose': -1 } model = lgb.train( params, dtrain, num_boost_round=3000, valid_sets=[dval], callbacks=[lgb.early_stopping(100), lgb.log_evaluation(200)] ) return model

训练主函数采用分层五折调用,每一折分别做早停。对于多分类,损失函数可考虑增加稀有类别的权重:将三个类别的权重设为[1.0, 1.0, 2.0]之类,具体值要通过验证集微调。类权重的作用相当于把类星体样本“放大”,在梯度更新中更有话语权,但也要小心放太大导致过拟合。

6.3 提交前检查清单

最后一步永远是最容易翻车的,我在几次比赛中都见过“模型没问题但提交格式错误”导致直接零分的情况。反复核对过之后,我整理了下面这个清单:

  • 测试集样本顺序是否保持原文件的id顺序?很多评分脚本按行号对齐,一旦打乱就全错。
  • 预测概率是否为多分类的每一列概率之和等于1?部分评分脚本会做归一化,但最好自己先检查。
  • 标签映射是否和官方一致?比如0代表恒星、1代表星系、2代表类星体,不同比赛定义不同。
  • 最终特征列顺序是否和训练时一致?特征名错位会导致预测结果完全错乱。
  • 是否重新训练了全量数据模型,而不是只拿五折模型之一去提交?五折平均通常更稳定,但如果时间紧,至少要用最后一折模型加测试集特征完成预测。

我一般会在提交前写一个小脚本,把提交文件和原始测试集文件读进来,逐一检查行数、id对应关系和概率范围,全部通过才导出。这个流程看着笨,但能挡住九成以上的低级失误。

做完这一轮天文数据挖掘实验,我最大的感受是:这个赛题真正难的并不是“机器学习”,而是你要先听懂天文学家留下的列名在说什么。星等、颜色指数、红移、半光半径,每一个名词背后都藏着一整条物理规律,把它们翻译成特征的过程,才是从0.70到接近0.80宏F1的关键。最后再分享一个小技巧:遇到看不懂的列名,先别急着丢进模型,花十分钟查一查这个参数在天文里是怎么测量出来的,往往比多调十组参数更有效。竞赛的结果只是一份排名,但这一套“物理背景+特征重构+稳健验证”的思路,放到任何表格数据挖掘任务里都依然管用。

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

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

立即咨询