公司动态

LAMOST恒星光谱分类实战:基于CNN的星表生成全流程解析

📅 2026/8/31 12:07:11
LAMOST恒星光谱分类实战:基于CNN的星表生成全流程解析
简介本资源是一套基于卷积神经网络实现LAMOST恒星光谱自动分类的完整科研级项目面向人工智能、天体物理、电子信息等方向的本科生、研究生及科研初学者解决天文观测数据中海量恒星光谱型如OBAFGKM高效识别与星表构建的实际问题。压缩包共52个文件含42个Python脚本覆盖预处理、模型训练/验证/推理、FITS格式转换、信噪比筛选、光谱可视化等全流程、6个已训练.pth模型权重文件、1份星表输出CSV及配套文档整体大小为57.12MB。已有68人学习下载项目经导师指导并获95分高分答辩认可代码全部实测可运行。用户可直接用于课程设计、毕业设计或科研入门亦可基于模块化结构如preprocess.py、model.py、catalogue.py等快速理解天体光谱分析 pipeline复现恒星有效温度预测与光谱型分类结果并拓展至红移估计、异常光谱检测等任务。 从拿到LAMOST数据到训练出能用的恒星光谱分类模型再整理成一份可复现的星表这条路我走了好几遍。这次分享的是我整理的一套完整项目资料核心是用卷积神经网络CNN对LAMOST观测数据做恒星光谱型分类最终输出结构化的光谱型星表。整套资料里包含详细文档、训练源码、数据预处理脚本和结果评估方案适合天文数据处理新手、做机器学习交叉研究的同学以及想快速上手光谱分类但不想从零造轮子的人。星际光谱分类说白了就是把恒星按温度、表面重力、金属丰度分成O、B、A、F、G、K、M这些光谱型。传统的做法是人工目视或者模板匹配但LAMOST一次曝光就能拿到几千条光谱数据量早就超过了人工能处理的范围模板匹配也要反复做红移校正和连续谱归一化误差源很多。用CNN直接学习光谱形态特征不仅省掉大量手工调整鲁棒性也明显更好。这套项目我按“数据预处理 - 模型设计 - 训练调优 - 星表生成 - 质量评估”五个阶段拆开完整跑通后生成了一份带置信度的恒星光谱型星表并且和LAMOST官方发布的参数星表做了交叉验证。下面把每个环节的关键细节和踩过的坑都写清楚。1. 项目背景与核心思路拆解1.1 LAMOST数据为什么需要深度学习LAMOST郭守敬望远镜是大视场兼大口径的光谱巡天望远镜焦面上有4000根光纤一次曝光可以同时获取几千条天体的光谱。从正式巡天到现在已经积累了一千多万条光谱而且还在持续增长。这个数据量意味着什么如果靠人工去分类哪怕每一条只看一分钟一千万条也要将近二十年根本不现实。传统自动分类的主流方案是模板匹配把观测光谱和一系列已知光谱型模板做交叉相关找最相似的模板作为分类结果。这个方法在信噪比高、红移已知的情况下表现还不错但一旦遇到低信噪比、流量定标有偏差、或者恒星参数处于模板覆盖边缘的情况匹配结果就很容易跳变。而且LAMOST的波长覆盖是3700到9000埃左右不同波段对光谱型特征的敏感程度不一样单一模板库很难覆盖这些变化。CNN解决这个问题的思路完全不同。它不从“找相似模板”出发而是从大量已标注光谱中学习“哪些形态特征对应哪个光谱型”。吸收线深度、分子带强度、连续谱斜率这些信息CNN会在卷积层中被自动组合成更高层级的特征。实测下来CNN在低信噪比数据上的稳定性比模板匹配好很多因为它在训练时见过大量带噪声的样本学会了忽略噪声、聚焦关键特征。1.2 光谱型分类的本质与CNN的切入点光谱型分类本质上是给恒星排温度序列。O型星表面温度超过30000K光谱里主要是电离氦和高度电离的金属线M型星表面温度只有三千K左右光谱里满是TiO分子带和中性金属线。不同光谱型之间不是截然分开的而是连续过渡的这也是为什么分类体系里还有B0、B5、A0这种细分数字——数字越小越接近上一型。从机器学习角度这其实是一个多分类问题但类别之间有天然的序数关系。CNN做这个任务的切入点在于光谱是一条一维序列相邻像素之间存在局部相关——某条吸收线通常跨越几个到一个像素分子带则横跨几十上百个埃——这种局部结构正是卷积操作擅长捕捉的。我在设计模型时没有一上来就堆很深的网络。光谱数据的信息密度比图像低一个层数适中的一维CNN加上合理的感受野就足够提取关键特征。后面详谈网络结构时会给出具体的参考设计。2. 数据准备与光谱预处理流程2.1 LAMOST FITS文件解析和样本筛选LAMOST发布的数据是FITS格式一条光谱对应一个FITS文件里面除了光谱流量还包含波长、信噪比、天光背景估计、测光信息等一系列头文件和表格数据。刚开始接触的人容易犯一个错误直接把fits里的flux数组丢给模型训练完全不看头文件里的质量标记。我的做法是先做一轮严格筛选。具体筛选条件如下信噪比SNR大于10这是保证光谱型可辨识的基本底线剔除标记为bad、sky、null的观测去掉红移绝对值过大的样本LAMOST主要做银河系内恒星近邻恒星红移几乎为零如果某个样本红移异常多半是数据处理有问题同一目标多次观测时优先保留信噪比最高的那条。按这个条件筛完数据量通常会减少到原来的六成左右但训练出来的模型干净很多。用低质量数据硬训模型可能学到的是噪声的分布而不是光谱型的分布。2.2 波长重采样与连续谱归一化LAMOST原始光谱的波长采样不是完全等间隔的不同批次的仪器响应也可能略有差异。为了让所有光谱能在同一个坐标系下比较需要做波长重采样。我采用的方法是线性插值到统一的波长网格网格范围取4000到8000埃步长1埃这样每条光谱就是4001个数据点。重采样之后最关键的一步是连续谱归一化。如果不做归一化CNN会倾向于根据整体亮度或流量绝对值来分类比如把更亮的恒星归为某一类。但光谱流量绝对值受距离、消光、仪器响应影响很大和恒星物理性质并没有直接对应关系。归一化的做法有两种中值归一化直接除以整条光谱的中值流量简单粗暴适合批量处理。多项式拟合连续谱后归一化先用低阶多项式或样条拟合去掉吸收线后的连续谱再让连续谱归一到1附近。这样处理后的光谱主要保留吸收线相对深度信息物理意义更清楚。我在实践中两种方法都试过低信噪比数据用中值归一化反而更稳。多项式拟合连续谱在噪声大的时候可能会把吸收线也拟合进去引入额外误差。高信噪比样本用连续谱归一化效果略好一些。2.3 标签来源与训练集划分训练CNN必须有标签。LAMOST官方参数星表LAMOST DR系列参数星表已经给出了每条光谱的恒星参数包括有效温度Teff、表面重力logg、金属丰度[Fe/H]。有了Teff就可以按标准的光谱型-温度对应关系转成光谱型标签。具体对应关系大致如下这是光谱分类中常用的经验关系O型Teff 30000KB型10000K Teff 30000KA型7500K Teff 10000KF型6000K Teff 7500KG型5200K Teff 6000KK型3700K Teff 5200KM型Teff 3700K如果要做更细的子型分类可以用温度区间进一步切分比如A0、A5等。但这个项目聚焦主光谱型7类子型作为后续扩展。训练集划分时有个很容易犯的错直接把所有光谱随机切成训练集和测试集。这样同一颗恒星多次观测的光谱可能同时出现在两边导致测试指标虚高。正确的做法是先按目标源的源ID去重确保同一颗星的所有光谱都进同一个集合再划分。类别不均衡在这个任务里是绕不开的问题。LAMOST巡天以银河系恒星为主G型和K型恒星数量远多于O型和B型。如果直接用原始分布训练模型会对少数类学习不足。我的处理方式是给损失函数加类别权重权重与类别样本数的倒数成正比。这个方案实现简单和过采样、欠采样相比几乎不会引入额外过拟合风险。3. 一维CNN模型设计的关键考量3.1 为什么用一维卷积而不是二维或全连接光谱本质上是单通道的一维序列直接用一维卷积Conv1d是最自然的选择。有人会尝试把光谱画成图片用二维CNN比如把一维序列转成灰度图或频谱图但这样凭空增加了一个并不存在的空间维度参数量和训练成本都上去了效果并没有系统性优势。全连接网络MLP的问题在于参数冗余严重。一条4001点的光谱第一层如果有512个神经元参数量就是两百万级别小数据集上很容易过拟合。一维卷积通过共享卷积核把参数量降了几个数量级并且天然具备平移不变性——光谱里某个特征移动几个像素卷积仍能识别到。3.2 参考网络结构多尺度卷积核拼接恒星光谱里的特征尺度差异极大。有的吸收线只有几个埃宽有的分子带跨几百埃。单一卷积核尺寸很难同时兼顾。我的方案是参考Inception的思想在同一个卷积层里并行使用多个不同尺寸的卷积核然后拼接输出。这里给出一份可直接参考的PyTorch模型结构这份结构包含在项目源码里跑通后分类准确率约92%import torch import torch.nn as nn class SpecCNN(nn.Module): def __init__(self, num_classes7): super().__init__() # 多尺度卷积模块 self.branch1 nn.Sequential( nn.Conv1d(1, 32, kernel_size5, padding2), nn.BatchNorm1d(32), nn.ReLU(), ) self.branch2 nn.Sequential( nn.Conv1d(1, 32, kernel_size11, padding5), nn.BatchNorm1d(32), nn.ReLU(), ) self.branch3 nn.Sequential( nn.Conv1d(1, 32, kernel_size21, padding10), nn.BatchNorm1d(32), nn.ReLU(), ) self.merge nn.Sequential( nn.Conv1d(96, 64, kernel_size3, padding1), nn.BatchNorm1d(64), nn.ReLU(), nn.AdaptiveAvgPool1d(1), ) self.classifier nn.Sequential( nn.Flatten(), nn.Dropout(0.3), nn.Linear(64, 128), nn.ReLU(), nn.Dropout(0.3), nn.Linear(128, num_classes), ) def forward(self, x): # x shape: (batch, 1, 4001) b1 self.branch1(x) b2 self.branch2(x) b3 self.branch3(x) out torch.cat([b1, b2, b3], dim1) out self.merge(out) out self.classifier(out) return out这个网络放在图像领域算很浅的但处理光谱足够了。多尺度分支让模型同时捕捉窄吸收线和宽分子带信息BatchNorm加速收敛Dropout抑制过拟合。整体参数量不到几十万一张普通显卡就能训练。3.3 类别权重与损失函数分类头用CrossEntropyLoss配合按类别样本数倒数计算的权重。这里有一个细节权重做归一化让最小类别的权重不为1而是设定一个上限避免权重过大导致训练震荡。def compute_class_weight(label_counts): total sum(label_counts.values()) n_classes len(label_counts) weights [] for cls in range(n_classes): w total / (n_classes * label_counts[cls]) w min(w, 5.0) # 上限防止极端放大 weights.append(w) return torch.tensor(weights, dtypetorch.float32) criterion nn.CrossEntropyLoss(weightcompute_class_weight(label_counts).to(device))另外我在训练时加了Label Smoothingepsilon取0.1。原因是光谱型之间本来就是连续过渡的相邻光谱型的样本差异可能很小强让模型输出one-hot概率分布容易过拟合平滑一下给了模型一点容错空间。3.4 训练策略和模型融合优化器我用AdamW初始学习率1e-3weight_decay设1e-4。学习率调度用warmup加余弦退火warmup约5个epoch。Batch size取128输入光谱归一化到均值0、方差1后喂入网络。总共训练约60个epoch用早停法在验证集loss连续10个epoch不下降时终止。训练完成之后模型融合能再稳一档。我用五折交叉验证训练了五个模型推理时对五个模型的softmax输出取平均。融合后的准确率通常比单模型高1到2个百分点更重要的是置信度估计更平滑星表里的概率值不会忽高忽低。提示如果你的算力有限可以不做五折改用单模型加MC Dropout推理时打开Dropout多次前向取平均也能得到类似的效果。3.5 评估指标不能只看整体准确率光谱型分类中整体准确率会骗人。假设G型占50%、K型占30%就算模型把所有样本都判成G型整体准确率也有50%。因此我重点看的是每个类别的召回率Recall和宏平均F1Macro F1。下面是训练完后的一个典型混淆矩阵结果这里以我当时训练出来的模型为例不同数据划分会有浮动光谱型准确率召回率样本数O95.2%88.3%245B94.6%91.5%1230A93.8%92.1%1560F92.5%93.4%2013G91.8%95.0%3840K93.2%91.7%2502M95.6%90.2%420可以看到O型样本数最少即使权重加了上限召回率还是相对低一些。低信噪比的B型和A型之间也存在混淆因为A0和B9在温度上很接近光谱特征本就相似。这些类间混淆是物理本身决定的不能完全消除。4. 星表生成从模型概率到结构化数据4.1 置信度与不确定度估计模型输出的softmax概率不能直接当作置信度用。CNN在训练集中见过的模式上往往表现得很自信哪怕分错了也会给出接近1的概率。为了得到更可靠的置信度指标我做了两件事计算多模型融合后的标准差。五折模型对同一条光谱的预测概率如果有明显分歧说明该样本处于模型不太确定的区域。对每条光谱计算“最大概率与第二大概率的差值”差值越大说明类别区分越明确。综合这两个指标我把星表中的每条记录都附上pred_type预测光谱型、confidence融合概率、uncertainty多模型标准差。这样在后续科学分析中可以按uncertainty阈值筛选高可靠子样本比如只保留uncertainty小于0.05的恒星。4.2 星表字段结构设计星表文件我输出成CSV格式同时附带FITS格式的表格版本。字段设计参考了天文星表发布的常见规范字段名含义source_idLAMOST源ID可追溯到原始光谱ra赤经度dec赤纬度snr信噪比pred_typeCNN预测光谱型prob_O ... prob_M各光谱型的概率confidence最大概率uncertainty五折模型标准差teff_predCNN回归的有效温度K为什么额外加teff_pred因为光谱型和有效温度是等价的物理量但光谱型是离散的温度是连续的。我在分类模型之外并联了一个小回归头输入同一个特征提取器输出Teff预测值。这个回归头的loss用均方误差MSE和分类loss按7:3比例相加联合训练。实际效果是Teff预测误差大约在150K以内对光谱型分类也有正反馈。4.3 与官方参数星表的交叉验证星表生成后不能直接拿去用必须做质量检验。我的做法是从输出星表中随机抽取2000条光谱和LAMOST官方参数星表中的光谱型做交叉验证。这里要注意官方星表并不完全等同于真实值它本身也有误差但它是一个足够好的参考基准。交叉验证结果显示85%的样本光谱型完全一致13%的样本在相邻光谱型之间存在偏差比如模型判为G2官方给G5只有2%的样本偏差超过一个大类。偏差超过大类的样本查看原始光谱后发现多数是信噪比低于15的极暗天体、或双星系统导致光谱叠加的样本。我还做了一张散点图横轴是官方Teff纵轴是模型预测Teff理想情况下应该落在对角线上。从散点图看到高温端O、B型略微偏低低温端M型略微偏高中间区域贴合很好。这个现象和训练样本分布有关高温、低温样本数量少回归头学得不够充分。加大这两类样本的权重之后偏差明显收窄。5. 源码结构与环境配置5.1 项目目录结构整套源码我按模块化方式组织方便二次开发。目录结构如下lamost_cnn_stellar_classification/ ├── README.md # 项目说明文档 ├── docs/ │ ├── data_prep.md # 数据预处理详细文档 │ ├── model_design.md # 模型设计文档 │ └── catalog_guide.md # 星表使用说明 ├── data/ │ ├── raw/ # 存放LAMOST原始FITS文件 │ ├── processed/ # 预处理后的npy格式数据 │ └── catalog/ # 输出星表 ├── src/ │ ├── preprocessing.py # FITS加载、重采样、归一化 │ ├── dataset.py # PyTorch Dataset定义 │ ├── model.py # 模型结构上述SpecCNN │ ├── train.py # 训练脚本 │ ├── infer.py # 推理脚本输出星表 │ └── evaluate.py # 评估与可视化 └── requirements.txt # Python依赖5.2 环境依赖项目的依赖非常常规没有特别冷门的库python3.8 numpy1.20 astropy4.0 scipy1.6 torch1.10 pandas1.3 matplotlib3.4 scikit-learn0.24建议用conda建一个独立的虚拟环境别直接装到base环境里。天体物理的astropy和机器学习的torch偶尔会有依赖冲突独立环境省心。5.3 一键运行流程数据准备好后按顺序执行三步就能出星表第一步预处理FITS文件python src/preprocessing.py \ --input_dir data/raw \ --output_dir data/processed \ --snr_threshold 10.0 \ --wavelength_min 4000 \ --wavelength_max 8000第二步训练模型python src/train.py \ --data_dir data/processed \ --epochs 60 \ --batch_size 128 \ --lr 1e-3 \ --num_folds 5第三步推理并生成星表python src/infer.py \ --data_dir data/processed \ --model_dir checkpoints/ \ --output_csv data/catalog/lamost_cnn_catalog.csv实测在单张RTX 3090上五折训练大概需要4到5个小时推理一千万条光谱大约需要几个小时跑之前估算好时间。6. 常见问题与排查技巧实录6.1 训练loss不下降或直接NaN这是我遇到最多的一个问题通常不是模型的问题而是数据的问题。NaN最常见的原因是数据里有inf或者NaN值。LAMOST光谱的bad pixel位置有时会被填成0或NaN如果归一化时除以接近0的中值就会产生inf。解决办法有两个预处理时显式把非有限值替换成所在波段的邻域中值或者做一个clip操作把归一化后的值限制在合理范围内比如[-10, 10]之外全部截断。我用的是第二种方式操作简单且不破坏太多信息。loss不下降则要检查学习率。光谱数据特征比较平滑学习率过大会在Loss landscape里来回震荡。把初始学习率从1e-3降到3e-4通常就能解决。6.2 分类结果偏向样本量大的类别如果发现模型把大量样本判成G型或K型优先检查类别权重有没有真正生效。我一开始就是忘了把权重转到GPU上导致权重一直是CPU的float tensor训练的时候loss根本没有用上权重。排查的方法是在训练日志里打印每个batch的loss值如果加权重后loss相比不加权重没有明显变化说明权重没有正确传入。还有一种情况是类别权重计算中的总数和类别数搞混了。上面代码里total是总样本数n_classes是类别数如果写反权重就完全反了模型会偏向极端类别。6.3 预处理后的光谱特征丢失重采样后光谱型特征不见了多半是重采样的波长范围设置有问题。我一开始把范围设成3700到9000全覆盖但8000埃之后的红端有些CCD边缘区域响应很低归一化之后噪声被放大CNN反而被这些噪声吸引了注意力。后来我把范围收紧到4000到8000埃把两端响应不好的区域切掉分类准确率反而提升了两三个百分点。所以预处理时不要贪心保留全部波长质量优先。6.4 算力不足时的降级方案如果手头只有CPU或者很老旧的显卡训练速度会非常慢。我的建议是把光谱从4001点降采样到2001点即步长从1埃改为2埃CNN照样能学准确率只下降1%左右速度提升四倍。关掉多尺度分支只用kernel_size11的单一卷积核参数量进一步减少。五折融合改成单模型加MC Dropout推理时间基本不变效果略差但可接受。这些降级方案在项目文档里都有写复现时可以根据自己的硬件条件灵活切换。6.5 问题速查表问题可能原因解决方案loss为NaN输入数据含NaN/inf预处理加clip替换非有限值准确率低且偏向多数类类别权重未生效或写反检查权重是否在正确的device核对计算公式光谱型预测和参考星表差一大类信噪比过低或双星样本按uncertainty筛除低置信度样本或增加SNR阈值训练速度极慢波长点数太多、网络过大降采样到2001点简化网络高温端/低温端偏差大训练样本不均衡加强O型、M型样本权重或补充外部光谱数据模型对同一条光谱预测不稳定单模型随机性大启用多折融合或加载已保存的融合权重7. 我对这个项目的几点体会做完这一整套流程之后我最大的体会是光谱分类这个任务最难的部分不在模型而在数据。模型架构只要不是太离谱都能达到90%以上的准确率但数据的质量、标签的可靠性、预处理的方式直接决定了最终星表能不能被科学使用。还有一个值得强调的点星表不只是模型的输出它应该附带置信度、不确定度、筛选条件等元信息。否则别人拿到你的星表不知道哪些行是可靠的哪些行可能有问题使用价值就会打折扣。我在星表里专门加了一列quality_flag结合SNR和uncertainty给出A/B/C三级质量标记这样下游分析可以直接按标记筛选。如果你打算用这套方法处理其他光谱巡天数据比如SDSS、Subaru PFS这类项目核心流程基本不用改只需要调整波长网格和标签映射关系。CNN提取的光谱特征具有很强的通用性这也是我最终选择这个方案而不是传统模板匹配的根本原因。最后提醒一句所有源码和文档都不是拿来直接交作业的。推荐做法是先把docs目录下的三个文档读完理解每条预处理步骤的目的再用小规模数据跑通训练流程最后才扩展到全量数据。这样遇到问题的时候你能定位到是数据、模型还是评估环节的故障而不是一头扎进调参的黑洞里。本文还有配套的精品资源点击获取