十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Keras宇宙信号解码实战:CNN分钟级搭建分类模型

Keras宇宙信号解码实战:CNN分钟级搭建分类模型 如果你正在做深度学习的图像分类项目你可能第一时间会想到 CIFAR-10、ImageNet 或者工业缺陷检测。但我想换个场景如果我们要从射电望远镜采集的“宇宙信号”里自动找出脉冲星、快速射电暴这类天体信号深度学习能怎么做Keras 在这里又承担什么角色这不是科幻电影的“翻译外星语言”而是天文学里真实存在的需求。如今的大型射电望远镜比如 FAST、SKA一个夜晚就能产生海量观测数据。信号里混杂着噪声、人造射频干扰而真正值得关注的天体候选信号可能只占极小比例。靠天文学家肉眼翻图效率已经跟不上了。于是深度学习被引入来做第一轮筛选自动把“疑似天体信号”从海量数据中挑出来。本文会从一个最典型的任务入手把二维时频图分类为“有周期性天体信号”和“无非相关信号”。我会用 Python 生成模拟数据用 Keras 搭建一个 CNN 分类模型跑通从数据准备、模型训练、效果验证到部署推理的完整流程。读完你会明白两件事第一Keras 如何快速验证一个深度学习方案第二这种方案在真实天文项目里能做什么、不能做什么。1. 宇宙信号解码先分清“解码”到底在做什么1.1 不翻译语言而是做模式识别很多人一听到“宇宙信号解码”第一反应是《三体》里的红岸基地或者 SETI 用来“听外星人”的设备。这里先说清楚在深度学习语境下宇宙信号解码不是翻译某种未知语言而是对天文观测数据进行模式识别和分类。射电望远镜接收到的数据经过消色散、去射频干扰等一系列处理后通常会被整理成二维数组。横轴是时间纵轴是频率颜色深浅代表信号强度。一个有物理意义的天体信号比如脉冲星的周期性射电脉冲会在二维图上留下周期性出现的亮条纹而纯噪声则没有稳定的结构。深度学习要做的就是训练一个模型输入这种二维图像输出一个概率值这张图里有多大概率包含天体信号。1.2 为什么传统方法越来越吃力传统方法也不是没有天文学家会通过折叠、傅里叶变换等方法提取周期信号再凭经验看一堆候选图。问题在于数据量太大。一台望远镜每天产生 TB 级数据人工标注完全跟不上。信号太弱。射电信号到达地球后往往被噪声淹没信噪比很低。干扰太多。手机、卫星、雷达等人造射频干扰都会在数据里留下类似信号的痕迹假阳性很高。深度学习模型的核心价值不是“算得更快”而是自动学习那些天文学家难以手写规则的特征。比如某个候选体是否具有真实的色散曲线是否有稳定的脉冲轮廓这些特征组合起来非常复杂但 CNN 可以用卷积核一层层自动提取。1.3 真实场景里的典型任务目前在射电天文学中深度学习最成熟的落地场景是任务输入输出脉冲星候选体分类候选体时频图/诊断图是否为脉冲星候选快速射电暴检测单脉冲动态谱是否包含FRB事件射频干扰识别观测二维谱数据干扰区域掩膜引力波信号识别时间序列是否为信号事件本文重点演示第一个任务也就是最像图像分类的任务。2. Keras 在宇宙信号分析中的定位与优势2.1 为什么是 Keras如果你去翻天文数据处理相关的论文能看到不少 PyTorch 的代码也有不少基于 Keras/TensorFlow 的实现。Keras 在处理这类任务时有几个很实际的优势原型验证非常快。搭一个 CNN 只需要十几行代码不用关心底层张量运算细节。与 NumPy 无缝衔接。天文数据经过预处理后转换为 NumPy 数组是非常标准的操作而 Keras 的model.fit直接接受 NumPy 数组不需要额外包装成 Dataset。回调机制成熟。EarlyStopping、ModelCheckpoint、TensorBoard 这些训练控制不需要自己实现。部署路径清晰。训练完的模型可以导出为.h5或.keras格式也可以用 TensorFlow Serving 或转换为 TFLite 跑在边缘设备上。2.2 Keras 3 与 tf.keras 的版本说明现在安装 Keras 时有一个容易混淆的点import keras和from tensorflow import keras有什么区别简单来说Keras 3 是一个多后端框架可以跑在 TensorFlow、JAX、PyTorch 之上而tf.keras是 TensorFlow 内置的 Keras API。如果项目已经依赖 TensorFlow使用tf.keras最稳妥如果希望更灵活可以安装独立的 Keras 3。不过从代码写法上看两者的高层 API 基本一致。本文的示例代码会尽量兼容两种环境并附上环境检查代码避免在版本上踩坑。3. 环境准备与依赖安装3.1 硬件与操作系统本文示例不需要 GPU 也能跑通。训练数据是模拟生成的 64x64 灰度图数据量在千张级别CPU 上几十秒到几分钟就能完成一轮训练。当然有 NVIDIA GPU 更好训练会快很多。操作系统不限Windows、Linux、macOS 都可以。如果你用的是 Linux 服务器建议配好 Python 虚拟环境。3.2 依赖清单建议使用 Python 3.9 到 3.12 之间的版本具体版本以当时 TensorFlow/Keras 官方支持为准。下面这个环境检查可以先跑一下python -c import numpy; print(numpy, numpy.__version__) python -c import keras; print(keras, keras.__version__) python -c import tensorflow as tf; print(tensorflow, tf.__version__)如果没装可以执行pip install numpy matplotlib tensorflow如果你不需要 TensorFlow 的完整功能只想用 Keras 3 某个后端也可以按官方文档安装但这里我推荐直接装 TensorFlow因为它自带tf.keras最容易对齐版本。3.3 验证安装是否成功import numpy as np import keras from keras import layers print(Keras version:, keras.__version__) print(NumPy version:, np.__version__) x np.random.rand(2, 64, 64, 1).astype(float32) y keras.Sequential([ layers.Conv2D(4, 3, activationrelu), layers.Flatten(), layers.Dense(1, activationsigmoid), ]) print(y(x).shape) # 预期输出 (2, 1)如果最后一行的输出是(2, 1)说明 Keras 可以正常执行前向传播环境没问题。4. 宇宙信号模拟数据生成与预处理4.1 模拟什么数据为了让教程不依赖真实观测数据的下载和清洗我会先生成两类别模拟数据类别 1正样本带周期性结构的时频图模拟脉冲星信号在二维谱上留下的周期性条纹。类别 0负样本纯噪声图模拟无天体信号的普通观测数据。重点说明生成模拟数据的目的是把整个深度学习流程跑通。真实项目里你需要用真实观测数据来替换这里的生成逻辑但后续的模型设计和训练流程是通用的。4.2 数据生成代码# 文件路径: generate_signal_data.py import numpy as np np.random.seed(42) IMAGE_SIZE 64 NUM_SAMPLES 2000 RATIO_POSITIVE 0.5 def generate_noise_image(sizeIMAGE_SIZE): 生成一张纯噪声时频图 return np.random.normal(0, 1, (size, size)).astype(float32) def generate_signal_image(sizeIMAGE_SIZE, period8, signal_amp2.0): 生成一张带周期性结构的时频图 image np.random.normal(0, 1, (size, size)).astype(float32) for x in range(size): if x % period 0: # 在部分时间点添加一个竖直方向的亮条纹 image[:, x] signal_amp return image samples [] labels [] num_positive int(NUM_SAMPLES * RATIO_POSITIVE) num_negative NUM_SAMPLES - num_positive for _ in range(num_positive): samples.append(generate_signal_image()) labels.append(1) for _ in range(num_negative): samples.append(generate_noise_image()) labels.append(0) samples np.array(samples) labels np.array(labels) # 打乱顺序 indices np.random.permutation(NUM_SAMPLES) samples samples[indices] labels labels[indices] # 增加通道维度形状变为 (2000, 64, 64, 1) samples samples[..., np.newaxis] print(samples shape:, samples.shape) print(labels shape:, labels.shape) print(positive ratio:, labels.mean()) np.save(samples.npy, samples) np.save(labels.npy, labels)这段代码有几个关键点np.random.normal生成标准高斯噪声模拟无信号干扰的底噪。generate_signal_image在每隔period个时间点增加一条竖直亮纹模拟周期性射电脉冲。最后把数据保存为 NumPy 格式这是 Keras 训练时最省事的输入形式。执行后你会看到类似这样的输出samples shape: (2000, 64, 64, 1) labels shape: (2000,) positive ratio: 0.54.3 数据划分# 文件路径: split_dataset.py import numpy as np samples np.load(samples.npy) labels np.load(labels.npy) # 按 8:1:1 划分为训练集、验证集、测试集 total len(samples) train_end int(total * 0.8) val_end int(total * 0.9) X_train, y_train samples[:train_end], labels[:train_end] X_val, y_val samples[train_end:val_end], labels[train_end:val_end] X_test, y_test samples[val_end:], labels[val_end:] print(训练集:, X_train.shape, y_train.shape) print(验证集:, X_val.shape, y_val.shape) print(测试集:, X_test.shape, y_test.shape) np.savez(dataset_split.npz, X_trainX_train, y_trainy_train, X_valX_val, y_valy_val, X_testX_test, y_testy_test)为什么需要验证集因为我们要用验证集来判断训练过程中模型是否过拟合从而决定何时停止。测试集则在最后做一次最终效果评估。5. 基于 Keras 搭建 CNN 分类模型5.1 模型结构选择对于 64x64 的小尺寸灰度图一个轻量级 CNN 就足够了。网络结构包含三层卷积块每层由卷积、ReLU 激活、最大池化组成最后接全连接层和 Sigmoid 输出。这里不选择过深的网络比如 ResNet50、EfficientNet原因是数据量只有 2000 张深网络容易过拟合。时频图的空间特征相对简单浅网络完全够用。训练速度快方便迭代验证。5.2 完整训练代码# 文件路径: train_signal_model.py import numpy as np from keras import layers, models, callbacks # 读取数据 data np.load(dataset_split.npz) X_train data[X_train] y_train data[y_train] X_val data[X_val] y_val data[y_val] X_test data[X_test] y_test data[y_test] # 构建模型 model models.Sequential([ layers.Input(shape(64, 64, 1)), layers.Conv2D(32, kernel_size3, activationrelu, paddingsame), layers.MaxPooling2D(pool_size2), layers.Conv2D(64, kernel_size3, activationrelu, paddingsame), layers.MaxPooling2D(pool_size2), layers.Conv2D(128, kernel_size3, activationrelu, paddingsame), layers.MaxPooling2D(pool_size2), layers.Flatten(), layers.Dropout(0.5), layers.Dense(1, activationsigmoid) ]) model.compile( optimizeradam, lossbinary_crossentropy, metrics[accuracy] ) model.summary() # 回调函数早停和保存最优模型 callbacks_list [ callbacks.EarlyStopping( monitorval_loss, patience5, restore_best_weightsTrue ), callbacks.ModelCheckpoint( best_signal_model.keras, monitorval_accuracy, save_best_onlyTrue ) ] # 训练 history model.fit( X_train, y_train, batch_size32, epochs30, validation_data(X_val, y_val), callbackscallbacks_list, verbose1 ) # 保存最终模型 model.save(signal_classifier_final.keras)5.3 代码关键点说明Input(shape(64, 64, 1))这里的最后一维是 1表示灰度图。如果你的数据是 RGB 三通道需要改为 3。paddingsame保证卷积后特征图尺寸不变避免边缘信息被过早丢弃。Dropout(0.5)在全连接层前加入随机失活是最简单的正则化手段对防止过拟合很有用。EarlyStopping监控验证集损失如果连续 5 个 epoch 没有下降就停止训练并恢复最优权重。ModelCheckpoint按验证集准确率保存最佳模型避免训练结束时的权重不是最优状态。model.summary()输出会展示每一层输出的形状和参数量。例如第一层卷积输出形状是(None, 64, 64, 32)参数量是 320。整体参数量很小完全可以在 CPU 上训练。6. 训练过程与效果验证6.1 运行训练命令python train_signal_model.py训练开始后你会看到类似下面的输出Epoch 1/30 50/50 [] - 2s 20ms/step - loss: 0.6123 - accuracy: 0.6675 - val_loss: 0.4251 - val_accuracy: 0.9450 Epoch 2/30 50/50 [] - 1s 14ms/step - loss: 0.3011 - accuracy: 0.8900 - val_loss: 0.1867 - val_accuracy: 0.9450 ... Epoch 8/30 50/50 [] - 1s 14ms/step - loss: 0.0966 - accuracy: 0.9700 - val_loss: 0.0410 - val_accuracy: 0.9900由于模拟数据的周期性条纹非常明显训练通常会很快收敛验证集准确率可以达到 98% 到 100%。这是合理的结果因为模拟信号和噪声的区分边界很清晰。6.2 评估测试集# 文件路径: evaluate_model.py import numpy as np from keras import models model models.load_model(best_signal_model.keras) data np.load(dataset_split.npz) X_test data[X_test] y_test data[y_test] loss, acc model.evaluate(X_test, y_test, verbose0) print(测试集损失:, round(loss, 4)) print(测试集准确率:, round(acc, 4)) # 输出每个样本的预测概率 proba model.predict(X_test) for i in range(10): print(f样本{i}: 真实标签{y_test[i]}, 预测概率{proba[i][0]:.4f})6.3 怎么判断模型真的学到了信号只看准确率还不够。一个常见误区是模型可能记住了训练集中的噪声或者学到了一些与信号无关的假特征。验证方法有两种查看混淆矩阵如果负样本被大量预测为正说明模型对噪声过于敏感需要增加负样本复杂度。新数据测试把生成周期改成不同于训练时的值比如原来period8新数据用period12生成看模型是否还能识别。如果识别率骤降说明模型只是记住了训练数据中的固定周期。# 用新周期生成测试数据 from generate_signal_data import generate_signal_image, generate_noise_image import numpy as np new_samples [] new_labels [] for _ in range(200): new_samples.append(generate_signal_image(period12)) new_labels.append(1) for _ in range(200): new_samples.append(generate_noise_image()) new_labels.append(0) new_samples np.array(new_samples)[..., np.newaxis] new_labels np.array(new_labels) model models.load_model(best_signal_model.keras) acc model.evaluate(new_samples, new_labels, verbose0)[1] print(新周期数据上的准确率:, round(acc, 4))如果输出在 0.9 以上说明模型学习的是“周期性结构”本身而不是某个固定周期。6.4 训练中的浮点数精度问题深度学习模型训练时默认使用 float32。这里顺便解释一下最近很热门的浮点数格式问题因为很多读者会在部署阶段遇到格式位数场景特点FP3232位训练主精度动态范围大最稳定FP1616位GPU混合精度训练/推理速度快但容易溢出BF1616位大模型/TPU训练动态范围和FP32一样精度低TF3219位NVIDIA Ampere架构矩阵运算介于FP32和FP16之间在天文信号分类这类小数据集任务中FP32 已经够用。如果你在云端用 GPU 训练也可以开启混合精度来提速但要注意验证集损失是否因为精度下降而波动。部署到边缘设备时可以再把模型转换为 FP16 或 INT8大幅降低推理内存。7. 从模拟数据走向真实天文数据7.1 真实数据长什么样真实射电望远镜的数据往往以 FITS 格式保存。FITS 是天文领域最常用的文件格式类似普通图像里的 PNG/JPEG但它不仅存像素值还会存大量元数据比如观测时间、望远镜指向、频率分辨率。要读取 FITS 文件可以使用astropypip install astropyfrom astropy.io import fits hdul fits.open(observation.fits) data hdul[0].data # 二维数组 print(data.shape)一个常见的问题是FITS 文件可能包含三个维度频率、时间、偏振。需要先根据物理含义决定输入模型的切片方式。比如取某个偏振分量的二维谱或者把多帧叠加成一张图。7.2 公开数据集 HTRU2 的简单适配如果你不想处理 FITS也可以先用 HTRU2 这个公开的脉冲星候选体数据集练手。它的每个样本是 17 维特征向量不是一个二维图。针对这种表格型数据可以把 CNN 换成多层感知机MLP也可以用 1D CNN 来处理。HTRU2 很有代表性的一点是正样本只有 163 个负样本有 16000 多个类别极不均衡。这正是深度学习中非常典型的“少量正样本”问题。7.3 少样本类别不均衡的处理思路模拟数据中我把正负样本比例设成了 1:1但真实场景几乎不可能这么理想。面对少量正样本有几个实用的策略类别权重在model.fit中传入class_weight让模型对少数类的错误更加敏感。数据增强对时频图进行小角度旋转、时间轴平移、添加高斯噪声扩大正样本数量。预训练/迁移学习如果数据实在太少可以考虑用类似结构的模型先在噪声/干扰分类任务上预训练再微调。不要只看准确率当负样本占 99% 时模型把全部样本预测为负准确率也有 99%。此时要重点看召回率和 F1或者绘制 ROC 曲线。一个带类别权重的示例# 计算类别权重 from sklearn.utils.class_weight import compute_class_weight weights compute_class_weight(balanced, classesnp.array([0, 1]), yy_train) class_weight {0: weights[0], 1: weights[1]} model.fit( X_train, y_train, batch_size32, epochs30, validation_data(X_val, y_val), class_weightclass_weight )8. 模型部署与推理落地的思考训练出模型不是终点。在实际项目中你需要把模型接到观测数据流上实现自动化筛选。8.1 导出模型上面的训练代码已经生成了.keras文件。如果希望用于 Java/C 服务可以进一步导出为 SavedModel 格式model.export(saved_model_dir)或者用 TensorFlow Serving 启动一个 HTTP 推理服务tensorflow_model_server --rest_api_port8501 \ --model_namesignal_classifier \ --model_base_path$(pwd)/saved_model_dir然后通过 REST API 请求curl -X POST http://localhost:8501/v1/models/signal_classifier:predict \ -H Content-Type: application/json \ -d {instances: [{input_1: ...}]}注意input_1的字段名需要根据模型具体输入层名称调整可以在导出前打印model.inputs确认。8.2 边缘设备部署对于 FAST 这种大型望远镜数据回传和处理通常都在数据中心完成。但对于一些小型的射电望远镜或教育项目可能需要把模型部署到树莓派、Jetson Nano 这类边缘设备上。此时可以转换为 TFLiteimport tensorflow as tf converter tf.lite.TFLiteConverter.from_keras_model(model) converter.optimizations [tf.lite.Optimize.DEFAULT] tflite_model converter.convert() with open(signal_model.tflite, wb) as f: f.write(tflite_model)转换后模型体积会明显变小推理速度也会提升适合实时处理场景。9. 常见问题与排查方法我在写这个示例时也踩过一些比较典型的坑。这里整理成一个排查表方便你照着检查。问题现象可能原因排查方式解决方案model.fit报维度错误输入数据没有增加通道维度形状是(2000, 64, 64)而不是(2000, 64, 64, 1)打印X_train.shape用samples[..., np.newaxis]增加维度训练 loss 不下降学习率太高或模型结构太简单检查数据是否被正确打乱查看每个 epoch 的 loss 曲线降低学习率如optimizerkeras.optimizers.Adam(learning_rate1e-4)验证集准确率很高但新数据上很差模型过拟合训练集或数据分布不一致用不同周期生成的数据测试增加数据增强增加 Dropout使用更保守的正则化加载.keras失败模型保存路径错误或跨 Keras 3 / tf.keras 版本不兼容打印model.save后生成的路径检查文件是否存在统一使用同一种 Keras 环境保存和加载类别不平衡导致所有预测都是负类正样本太少模型倾向把所有样本预测为多数类查看预测概率分布使用class_weight改用更适合不平衡任务的损失函数比如Focal LossCPU 训练太慢数据量太大或模型层数太深查看 epoch 平均耗时缩小图像尺寸减少卷积通道数或使用 GPU 实例预测概率始终在 0.5 附近模型容量不够或数据没有区分度检查训练集 loss 是否仍在下降增加模型层数检查数据生成逻辑是否真的存在可学习特征10. 最佳实践与工程建议10.1 数据层面先用小数据跑通全流程。不要一开始就上几十 GB 数据先取 2000 张样本验证代码逻辑。固定随机种子。数据和模型初始化时都设定random_seed保证每次实验结果可复现。把原始数据和预处理分开。预处理脚本独立保存方便重新生成或调整参数。记录数据来源和标签定义。天文数据尤其复杂同一份数据可能因为消色散参数不同而有完全不同的形态。建议用文本文件记录每次实验的数据版本。10.2 模型层面优先使用简单模型。输入是 64x64 的小图时三层卷积通常已经足够。监控验证集而不是训练集。训练集准确率再高也不代表泛化。保存最佳模型而非最后模型。用 ModelCheckpoint 按验证集指标保存。注意数据泄露。如果数据来自同一段观测连续切分训练集和测试集可能有很强的相关性。尽量按时间窗口切分而不是随机切分。10.3 工程与部署层面模型训练和数据预处理脚本要分开。训练脚本只管加载数据、训练、保存数据生成脚本独立维护方便在真实数据替换时只改数据层。推理服务要记录置信度阈值。在真实项目中模型输出一个概率你需要根据任务精度/召回要求选择阈值。这个阈值也应该作为配置文件存下来。保留人工复核通道。深度学习模型不适合完全替代天文学家更合理的流程是模型筛掉大部分噪声保留少量高置信度候选交给人工复核。10.4 安全与合规提醒这里特别提醒一点如果项目涉及真实的望远镜观测数据要注意数据使用授权和学术规范。有些观测数据有专有期不能随意公开或用于商业训练。作为技术教程我们使用模拟数据是安全的但切换到真实数据前一定要确认授权范围。11. 总结与后续学习方向这篇文章的核心就一句话宇宙信号解码本质上是一个带噪声、带类别不平衡的二维图像分类任务而 Keras 提供了一条从数据到模型的快速路径。我们完成了这些事理解了宇宙信号分类在真实天文学中的含义。用 Python 生成了模拟时频图数据。用 Keras 搭建并训练了一个 CNN 分类模型。验证了模型在测试集上的效果并讨论了过拟合与泛化问题。了解了从模拟数据走向真实天文数据的思路包括类别不平衡、FITS 文件读取和部署方案。后续如果你想深入推荐按这个顺序学习先跑通本文示例试着把信号周期、图像尺寸、噪声强度改掉观察模型效果变化。再下载 HTRU2 数据集用表格型数据做一次脉冲星候选体分类体会类别不平衡的挑战。如果身边有真实天文 FITS 数据尝试用astropy读取并构造二维谱替换模拟数据输入。最后研究部署用 TensorFlow Serving 或 TFLite 把模型放到线上去。深度学习在天文领域的应用远不只有脉冲星分类还有引力波信号的时域分类、星系形态识别、天文图像去噪等。如果你已经掌握了用 Keras 处理图像分类的流程换成这些任务差的主要是数据形态和标签定义模型搭建的思路是完全可以复用的。
返回列表