☰
LAMOST恒星光谱分类实战:基于CNN的星表生成全流程解析
2026/10/17 10:06:43 网站建设 项目流程

简介:本资源是一套基于卷积神经网络实现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. 多项式拟合连续谱后归一化:先用低阶多项式或样条拟合去掉吸收线后的连续谱,再让连续谱归一到1附近。这样处理后的光谱主要保留吸收线相对深度信息,物理意义更清楚。

我在实践中两种方法都试过,低信噪比数据用中值归一化反而更稳。多项式拟合连续谱在噪声大的时候可能会把吸收线也拟合进去,引入额外误差。高信噪比样本用连续谱归一化,效果略好一些。

2.3 标签来源与训练集划分

训练CNN必须有标签。LAMOST官方参数星表(LAMOST DR系列参数星表)已经给出了每条光谱的恒星参数,包括有效温度Teff、表面重力logg、金属丰度[Fe/H]。有了Teff就可以按标准的光谱型-温度对应关系转成光谱型标签。

具体对应关系大致如下(这是光谱分类中常用的经验关系):

  • O型:Teff > 30000K
  • B型:10000K < Teff <= 30000K
  • A型:7500K < Teff <= 10000K
  • F型:6000K < Teff <= 7500K
  • G型:5200K < Teff <= 6000K
  • K型:3700K < Teff <= 5200K
  • M型: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_classes=7): super().__init__() # 多尺度卷积模块 self.branch1 = nn.Sequential( nn.Conv1d(1, 32, kernel_size=5, padding=2), nn.BatchNorm1d(32), nn.ReLU(), ) self.branch2 = nn.Sequential( nn.Conv1d(1, 32, kernel_size=11, padding=5), nn.BatchNorm1d(32), nn.ReLU(), ) self.branch3 = nn.Sequential( nn.Conv1d(1, 32, kernel_size=21, padding=10), nn.BatchNorm1d(32), nn.ReLU(), ) self.merge = nn.Sequential( nn.Conv1d(96, 64, kernel_size=3, padding=1), 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], dim=1) 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, dtype=torch.float32) criterion = nn.CrossEntropyLoss(weight=compute_class_weight(label_counts).to(device))

另外,我在训练时加了Label Smoothing,epsilon取0.1。原因是光谱型之间本来就是连续过渡的,相邻光谱型的样本差异可能很小,强让模型输出one-hot概率分布容易过拟合,平滑一下给了模型一点容错空间。

3.4 训练策略和模型融合

优化器我用AdamW,初始学习率1e-3,weight_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)和宏平均F1(Macro F1)。

下面是训练完后的一个典型混淆矩阵结果(这里以我当时训练出来的模型为例,不同数据划分会有浮动):

光谱型准确率召回率样本数
O95.2%88.3%245
B94.6%91.5%1230
A93.8%92.1%1560
F92.5%93.4%2013
G91.8%95.0%3840
K93.2%91.7%2502
M95.6%90.2%420

可以看到O型样本数最少,即使权重加了上限,召回率还是相对低一些。低信噪比的B型和A型之间也存在混淆,因为A0和B9在温度上很接近,光谱特征本就相似。这些类间混淆是物理本身决定的,不能完全消除。

4. 星表生成:从模型概率到结构化数据

4.1 置信度与不确定度估计

模型输出的softmax概率不能直接当作置信度用。CNN在训练集中见过的模式上往往表现得很自信,哪怕分错了也会给出接近1的概率。为了得到更可靠的置信度指标,我做了两件事:

  1. 计算多模型融合后的标准差。五折模型对同一条光谱的预测概率如果有明显分歧,说明该样本处于模型不太确定的区域。
  2. 对每条光谱计算“最大概率与第二大概率的差值”,差值越大,说明类别区分越明确。

综合这两个指标,我把星表中的每条记录都附上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 环境依赖

项目的依赖非常常规,没有特别冷门的库:

python>=3.8 numpy>=1.20 astropy>=4.0 scipy>=1.6 torch>=1.10 pandas>=1.3 matplotlib>=3.4 scikit-learn>=0.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_size=11的单一卷积核,参数量进一步减少。
  • 五折融合改成单模型加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目录下的三个文档读完,理解每条预处理步骤的目的,再用小规模数据跑通训练流程,最后才扩展到全量数据。这样遇到问题的时候,你能定位到是数据、模型还是评估环节的故障,而不是一头扎进调参的黑洞里。

本文还有配套的精品资源,点击获取

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

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

立即咨询