Monisha325/Spacecraft-Telemetry-Anomaly-Detection-using-LSTM-Autoencoders
GitHub: Monisha325/Spacecraft-Telemetry-Anomaly-Detection-using-LSTM-Autoencoders
基于LSTM自编码器的航天器遥测无监督异常检测系统,在NASA SMAP/MSL真实任务数据上实现了完整的端到端流水线。
Stars: 0 | Forks: 0
# 🚀 航天器遥测异常检测
**ISRO Vikram Sarabhai Space Centre (VSSC) — 实习项目**
## 📋 目录
- [项目概述](#-project-overview)
- [数据集](#-dataset)
- [Pipeline 架构](#-pipeline-architecture)
- [分阶段详解](#-stage-by-stage-breakdown)
- [LSTM Autoencoder 架构](#-lstm-autoencoder-architecture)
- [训练](#-training)
- [阈值检测](#-threshold-detection)
- [结果](#-results)
- [讨论与局限性](#-discussion--limitations)
- [环境配置与安装](#-setup--installation)
- [项目结构](#-project-structure)
- [关键设计决策](#-key-design-decisions)
- [参考资料](#-references)
## 🛰 项目概述
航天器会产生连续的遥测数据流——包括传感器读数、指令状态、工程测量值——这些数据必须被实时监控以发现异常。大规模的人工检查是不可行的。本项目构建了一个完全**无监督异常检测系统**,仅使用标称(健康)运行数据进行训练,然后标记出重构误差超过学习阈值的窗口。
**为什么选择无监督?** 在实际任务中,标记的异常数据稀缺、昂贵,并且通常只有在事后分析之后才能获得。Autoencoder 仅从健康数据中学习正常流形——训练期间不需要异常标签。
**核心机制:**
```
Train LSTM Autoencoder on normal sequences only
↓
At inference: reconstruct each test window
↓
Anomaly score = MSE(input, reconstruction)
↓
Flag window if score > threshold
```
**项目亮点:**
- 7 阶段模块化 notebook pipeline,可在 Google Colab 上完全复现
- 严格的无泄露保证——Scaler 仅在训练数据上拟合,并通过断言验证
- 通过自相关函数 (ACF) 分析进行数据驱动的窗口大小选择
- 比较了双重阈值策略:第 99 百分位数 vs 动态 EWM
- 完整的评估套件:F1, ROC-AUC, PR-AUC, 混淆矩阵
## 📡 数据集
**NASA 异常检测数据集 — SMAP & MSL**
来源:[Kaggle / Patrick Fleith](https://www.kaggle.com/datasets/patrickfleith/nasa-anomaly-detection-dataset-smap-msl) · 通过 KaggleHub 下载
| 属性 | 值 |
|---|---|
| 航天器 | SMAP (土壤水分主被动探测卫星) + MSL (火星科学实验室) |
| 总通道数 | **82** (训练) / **82** (测试) |
| 格式 | `.npy` 多变量时间序列数组 |
| 真值 | `labeled_anomalies.csv` — 异常区间为 `[start, end]` 索引对 |
| 使用的主要通道 | **P-1** (SMAP) |
**通道 P-1 维度:**
| 划分 | 形状 | 含义 |
|---|---|---|
| 训练集 | (2,872 × 25) | 2,872 个时间步,25 个原始特征 |
| 测试集 | (8,505 × 25) | 8,505 个时间步,25 个原始特征 |
测试集包含 **3 个标记的异常区间**,横跨约 8,500 个时间步——这是一个现实的、严重不平衡的场景(按窗口计数异常率约为 10%)。
### 带有真值异常区域的测试信号

## 🏗 Pipeline 架构
```
┌─────────────────────────────────────────────────────────────┐
│ RAW TELEMETRY (.npy) │
│ Train: (2872, 25) Test: (8505, 25) │
└──────────────────────────┬──────────────────────────────────┘
│
┌────────────▼────────────┐
│ 01 DATA LOADER │
│ Load · Validate · Label │
└────────────┬────────────┘
│
┌────────────▼────────────┐
│ 02 PREPROCESSING │ Drop 9 const cols → Normalize
│ (2872,25)→(2872,96) │ → Delta → Rolling features
└────────────┬────────────┘
│
┌────────────▼────────────┐
│ 03 FEATURE ENG. │ ACF → window=41
│ Sequences (N,41,96) │ Sliding window, step=1
└────────────┬────────────┘
│
┌────────────▼────────────┐
│ 04 MODEL DEV. │ LSTM Autoencoder
│ Encoder→Latent→Decoder │ ~620K parameters
└────────────┬────────────┘
│
┌────────────▼────────────┐
│ 05 TRAINING │ 100 epochs · Best @ ep.98
│ val_loss: 0.004983 │ ReduceLROnPlateau
└────────────┬────────────┘
│
┌────────────▼────────────┐
│ 06 THRESHOLDING │ Dynamic EWM
│ threshold = 0.01540 │ μ_ewm + 3σ_ewm
└────────────┬────────────┘
│
┌────────────▼────────────┐
│ 07 EVALUATION │ F1=0.095 · ROC-AUC=0.467
│ Confusion · ROC · PR │ PR-AUC=0.097
└─────────────────────────┘
```
## 📓 分阶段详解
### 阶段 01 — 数据加载器
设置项目结构并提供可复用的数据访问函数:
| 函数 | 用途 |
|---|---|
| `load_channel(channel_id)` | 加载训练/测试 `.npy` 数组,包含形状和 NaN 断言检查 |
| `load_anomaly_labels(channel_id)` | 解析 `labeled_anomalies.csv` → `[start, end]` 区间列表 |
| `build_label_array(intervals, n)` | 时间步级别的二进制真值数组 |
| `build_window_label_array(intervals, n, w)` | 用于评估的窗口级别真值 |
| `list_channels(spacecraft)` | 所有通道 ID,可选择按航天器筛选 |
| `get_dataset_summary()` | 按航天器分组的汇总统计信息 |
### 阶段 02 — 预处理
严格的**无泄露 pipeline**——所有统计量仅从训练数据得出。
| 步骤 | 变换 | 训练集形状 | 测试集形状 |
|---|---|---|---|
| 原始输入 | — | (2872, 25) | (8505, 25) |
| 删除常量列 | 移除 9 个零方差特征 (`std < 1e-6`) | (2872, 16) | (8505, 16) |
| MinMax 归一化 | 缩放至 `[0, 1]` — **仅在训练集上拟合** | (2872, 16) | (8505, 16) |
| Delta 特征 | 追加一阶差分 | (2872, 32) | (8505, 32) |
| 滚动特征 | 追加 10 步滚动均值 + 标准差 | **(2872, 96)** | **(8505, 96)** |
`Train min: 0.0 · Train max: 1.0` — 通过断言验证归一化。
**泄露防护:** 一个专门的单元格断言 `np.allclose(train_min, scaler.data_min_)` — 确认 Scaler 从未暴露于测试数据。 ✅

### 阶段 03 — 特征工程
**通过 ACF 选择窗口大小:**
计算所有 96 个特征在滞后 1–100 时的自相关函数。窗口大小设置为任何特征的 ACF 首次进入 95% 置信带 `±1.96 / √n` 时的最大滞后值。

**序列构建:**
| 输出 | 形状 | 内存 |
|---|---|---|
| 训练序列 | **(2547, 41, 96)** | 76.5 MB |
| 验证序列 | **(284, 41, 96)** | 8.5 MB |
| 测试序列 | **(8464, 41, 96)** | 254.2 MB |
滑动窗口 `step=1` — 最大重叠以实现密集异常评分。**按时间顺序的 90/10 训练/验证划分**(无打乱)保留了时间顺序。

**5 点完整性检查 — 全部通过:**
| 检查 | 结果 |
|---|---|
| 第一个窗口与原始数据匹配 | ✅ 通过 |
| 训练集 / 验证集无重叠 | ✅ 通过 |
| 任何划分中均无 NaN | ✅ 通过 |
| 所有数组均为 3D | ✅ 通过 |
| 测试窗口与原始测试数据匹配 | ✅ 通过 |
### 阶段 04 — 模型开发(见下方 [LSTM Autoencoder 架构](#-lstm-autoencoder-architecture))
### 阶段 05 — 训练
| 参数 | 值 |
|---|---|
| 损失函数 | 均方误差 (MSE) |
| 优化器 | Adam |
| 初始学习率 | 1e-3 |
| Batch size | 64 |
| 最大 Epochs | 100 |
| 早停 | patience=10, 恢复最佳权重 |
| 学习率衰减 | ReduceLROnPlateau — factor=0.5, patience=5, min_lr=1e-6 |
| 指标 | 值 |
|---|---|
| 最佳 Epoch | **98** |
| 最佳 val_loss | **0.004983** |
| 最终 train_loss | **0.002249** |
| 收敛时的学习率 | ~1.95e-6 (从初始值减少了 9×) |
模型训练了完整的 100 个 Epochs 且持续改进。学习率被 `ReduceLROnPlateau` 降低了 9 次,表明优化器逐步细化了极小值。训练/验证损失差距稳定且较小——没有明显的过拟合。从约第 75 个 Epoch 开始,两者损失均平滑趋于平稳。
## 🧠 LSTM Autoencoder 架构
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
INPUT (batch, 41 timesteps, 96 features)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ENCODER
LSTM(128, tanh, return_sequences=True)
Dropout(0.2)
LSTM(64, tanh, return_sequences=True)
Dropout(0.2)
LSTM(32, tanh, return_sequences=False) ← bottleneck
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
BRIDGE
RepeatVector(41) ← expand latent → sequence
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
DECODER
LSTM(32, tanh, return_sequences=True)
Dropout(0.2)
LSTM(64, tanh, return_sequences=True)
Dropout(0.2)
LSTM(128, tanh, return_sequences=True)
TimeDistributed(Dense(96))
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
OUTPUT (batch, 41 timesteps, 96 features)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Total parameters: ~620,000
Output shape == Input shape ✅ verified
```
**评估了三种配置:**
| 配置 | 潜在维度 | Dropout | 参数量 | 选中 |
|---|---|---|---|---|
| Small | 16 | 0.1 | ~350K | |
| **Medium** | **32** | **0.2** | **~620K** | ✅ |
| Large | 64 | 0.3 | ~1.1M | |
对于 2,547 个样本的训练集,选择 Medium 配置作为表达能力与过拟合风险之间的最佳权衡。
## 🎯 阈值检测
每个窗口的异常分数 = 输入与重构之间在所有时间步和特征上的**均方误差**。
在训练误差分布上比较了两种策略:
| 方法 | 公式 | 阈值 |
|---|---|---|
| 百分位数 (第 99) | `np.percentile(train_errors, 99)` | ~0.01430 |
| **动态 EWM** ✅ | `μ_ewm + 3σ_ewm` (span=10) | **0.01540** |
选择动态 EWM —— 对任务生命周期内的非平稳性和缓慢信号漂移具有更强的鲁棒性。

## 📊 结果
### 评估指标 — 通道 P-1
| 指标 | 值 |
|---|---|
| **Precision** | 0.149 |
| **Recall** | 0.070 |
| **F1 Score** | 0.095 |
| **ROC-AUC** | 0.467 |
| **PR-AUC** | 0.097 |
### 混淆矩阵

| | 预测为 Normal | 预测为 Anomaly |
|---|---|---|
| **实际 Normal** | 7,318 (TN) | 326 (FP) |
| **实际 Anomaly** | 763 (FN) | 57 (TP) |
### ROC 曲线

### Precision-Recall 曲线

## 🔎 讨论与局限性
### 实事求是的评估
通道 P-1 上的结果表明,模型**未能成功学习区分该特定通道上的异常窗口与正常窗口**。0.467 的 ROC-AUC 略低于随机概率,7% 的 Recall 意味着 820 个异常窗口中有 763 个被遗漏。理解发生这种情况的具体原因与获得高性能结果同样有价值。
### 根本原因分析
**1. 异常类型与基于重构的检测不匹配。**
观察数据可视化,P-1 的异常属于**抑制型**——在异常期间信号变得更安静并失去方差。在嘈杂、高振幅正常数据上训练的 Autoencoder 重构这些更安静的异常窗口*更容易*,产生更低的重构误差——这直接颠覆了检测假设。基于重构的方法非常适合尖峰/偏差异常,但在方差抑制异常上表现不佳。
**2. 序列重塑的副作用。**
在阶段 05 中,序列需要 `expand_dims + repeat` 以匹配模型预期的输入形状。这引入了模型可能围绕其优化的人为时间重复,从而降低了对真实时间模式的敏感度。
**3. 模型收敛,但收敛于低 MSE。**
最终训练 MSE 为 `0.002249`,反映出一个能很好地重构几乎所有内容的模型——这导致正常类和异常类之间的重构误差差距缩小到检测阈值以下。
**4. 阈值选择时的类别不平衡。**
动态阈值完全来自训练误差统计,没有来自异常类的反馈。在严重不平衡的测试集上,这种启发式方法产生的阈值位置并非 F1 的最优解。
### 为什么 PR-AUC 是此处的正确指标
P-1 在总共 8,464 个窗口中有约 820 个异常窗口(约 9.7% 的异常率)。在这种不平衡下,一个将所有内容预测为“正常”的模型可以实现 ROC-AUC ≈ 0.5,这使得 ROC-AUC 成为模型质量的糟糕鉴别器。当模型失败时,PR-AUC 会向基准率(~0.097)坍缩——这正是观察到的情况,证实两个指标都是自洽的,且在此通道上尚未超越基线。
### 提议的改进
| 改进 | 目标 |
|---|---|
| **基于预测的 LSTM** — 预测下一个时间步;分数 = 预测误差 | 直接对方差抑制异常敏感 |
| **感知方差的异常分数** — 惩罚预测方差超过实际方差的窗口 | 专为抑制型异常设计 |
| **半监督阈值调整** — 在小的留出标记集上 | 直接优化 F1,而非使用统计启发式方法 |
| **Isolation Forest / One-Class SVM** (在潜在向量上) | 在压缩空间中学习更紧密的正常边界 |
| **多通道评估** — 在 M-1, D-14, P-15 等上 | P-1 被记录为困难通道;Pipeline 级别的指标能提供更公平的图景 |
| **对比学习或基于能量的训练** | 显式地将异常重构推向更高的误差 |
## ⚙️ 环境配置与安装
**要求:** Python 3.8+ · Google Colab (推荐) · Google Drive
```
pip install -r requirements.txt
```
**数据集下载**(在 `01_data_loader.ipynb` 中自动处理):
```
pip install kagglehub
```
## 🚀 用法
在 Google Colab 中按顺序运行 notebook:
```
01_data_loader.ipynb Download & structure dataset (82 channels)
02_preprocessing.ipynb Clean, normalize & engineer features
03_feature_engineering.ipynb ACF window selection + sliding sequences
04_model_development.ipynb Define LSTM Autoencoder (~620K params)
05_training.ipynb Train · best epoch 98 · val_loss 0.004983
06_threshold_detection.ipynb Score windows · threshold = 0.01540
07_evaluation.ipynb Metrics · confusion matrix · ROC · PR curve
```
要在不同的通道上运行:
```
channel_id = "M-1" # change at the top of each notebook
```
## 📁 项目结构
```
rocket_telemetry_project/
│
├── data/
│ ├── train/ # 82 raw .npy train channel files
│ ├── test/ # 82 raw .npy test channel files
│ ├── labeled_anomalies.csv # Ground truth anomaly intervals
│ ├── processed/
│ │ ├── P-1_train.npy # (2872, 96) — after full preprocessing
│ │ └── P-1_test.npy # (8505, 96)
│ └── sequences/
│ ├── P-1_train_seq.npy # (2547, 41, 96)
│ ├── P-1_val_seq.npy # (284, 41, 96)
│ └── P-1_test_seq.npy # (8464, 41, 96)
│
├── models/
│ ├── architecture_P1.json # Model architecture (JSON)
│ ├── lstm_ae_P-1.h5 # Best model weights (epoch 98)
│ └── scaler_P-1.pkl # MinMaxScaler (train-only fit)
│
├── results/
│ ├── plots/
│ │ ├── P1_data_check.png # Test signal + anomaly regions
│ │ ├── P1_preprocessing.png # Raw → normalized → delta
│ │ ├── P1_acf.png # ACF for window size selection
│ │ ├── P1_sequence_sample.png # Window + feature heatmap
│ │ ├── P-1_training_curves.png # Loss + LR decay over 100 epochs
│ │ ├── P-1_error_dist.png # Reconstruction error distribution
│ │ ├── P-1_roc.png # ROC curve (AUC=0.467)
│ │ ├── P-1_pr_curve.png # PR curve (AP=0.097)
│ │ └── P-1_cm.png # Confusion matrix (F1=0.095)
│ └── metrics/
│ ├── P-1_history.json # Loss per epoch (100 epochs)
│ ├── P-1_threshold.json # Threshold=0.01540, method=dynamic
│ └── P-1_eval.json # TP=57, FP=326, TN=7318, FN=763
│
├── src/
│ ├── __init__.py
│ └── data_loader.py # Reusable channel loading module
│
├── notebooks/ # 7 ordered Colab notebooks
├── requirements.txt
└── README.md
```
## 🔍 关键设计决策
| 决策 | 理由 |
|---|---|
| **无监督 Autoencoder** | 不需要异常标签——仅在正常数据上训练,反映了实际任务约束 |
| **Scaler 仅在训练集上拟合** | 严格的无泄露保证——通过断言验证,而非假设 |
| **使用 ACF 确定窗口大小** | 数据驱动的选择——捕获每个通道的实际时间依赖长度 |
| **按时间顺序的验证划分** | 打乱会泄露未来上下文——对于时间序列评估是禁止的 |
| **Delta + 滚动特征增强** | 除了原始值外,还向模型暴露变化率和局部波动性 |
| **MSE 重构损失** | 惩罚偏离正常模式的大偏差;对结构性异常值敏感 |
| **动态 EWM 阈值** | 适应信号的非平稳性;比任务生命周期内的固定百分位数更鲁棒 |
| **PR-AUC 作为主要指标** | 在严重类别不平衡下的正确选择——当异常稀少时 ROC-AUC 具有误导性 |
## 📚 参考资料
1. Hundman, K., et al. (2018). **Detecting Spacecraft Anomalies Using LSTMs and Nonparametric Dynamic Thresholding.** *KDD 2018.* [arXiv:1802.04893](https://arxiv.org/abs/1802.04893)
2. Malhotra, P., et al. (2016). **LSTM-based Encoder-Decoder for Multi-sensor Anomaly Detection.** *ICML Anomaly Detection Workshop.*
3. Hochreiter, S. & Schmidhuber, J. (1997). **Long Short-Term Memory.** *Neural Computation, 9(8),* 1735–1780.
4. NASA SMAP/MSL 异常数据集 — [Kaggle](https://www.kaggle.com/datasets/patrickfleith/nasa-anomaly-detection-dataset-smap-msl)
## 👨💻 关于本项目
**作者** Patnana Monisha
**领域:** Deep Learning · Time Series · Anomaly Detection · Spacecraft Health Monitoring
**数据集:** NASA SMAP & MSL — 82 个遥测通道,真实任务数据
*TensorFlow/Keras · Scikit-learn · NumPy · Pandas · Matplotlib · Seaborn · KaggleHub*
标签:ACF分析, Apex, BSD, ISRO, Keras, LSTM自编码器, NASA MSL, NASA SMAP, Python, TensorFlow, 信号预处理, 动态阈值, 太空技术, 异常检测, 故障预测, 数据处理管道, 数据挖掘, 无后门, 无监督学习, 时间序列分析, 机器学习, 深度学习, 航天器遥测, 逆向工具