Add engineering reproduction package
Browse files- .gitattributes +2 -33
- README.md +156 -0
- README_zh.md +159 -0
- conf/config.yaml +68 -0
- config.json +44 -0
- configuration.json +12 -0
- model/prithvi_eo.py +175 -0
- scripts/fake_data.py +68 -0
- scripts/inference.py +54 -0
- scripts/result.py +56 -0
- scripts/train.py +107 -0
- weight/.gitkeep +0 -0
.gitattributes
CHANGED
|
@@ -1,35 +1,4 @@
|
|
| 1 |
-
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.
|
| 24 |
-
*.
|
| 25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
language:
|
| 4 |
+
- en
|
| 5 |
+
tags:
|
| 6 |
+
- OneScience
|
| 7 |
+
- Earth Science
|
| 8 |
+
- Multi-Temporal Earth Observation
|
| 9 |
+
- Remote Sensing Foundation Model
|
| 10 |
+
frameworks: PyTorch
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
<p align="center">
|
| 14 |
+
<strong>
|
| 15 |
+
<span style="font-size: 30px;">Prithvi-EO</span>
|
| 16 |
+
</strong>
|
| 17 |
+
</p>
|
| 18 |
+
|
| 19 |
+
# Model Introduction
|
| 20 |
+
|
| 21 |
+
Prithvi-EO-2.0 is a foundation model for multi-temporal Earth observation data. It encodes HLS multispectral time series, image acquisition dates, and geographic locations into unified representations and reconstructs masked spatiotemporal patches through a Masked Autoencoder. It can be used for remote-sensing classification, semantic segmentation, regression, and environmental change monitoring.
|
| 22 |
+
|
| 23 |
+
Paper: Prithvi-EO-2.0: A Versatile Multi-Temporal Foundation Model for Earth Observation Applications
|
| 24 |
+
https://arxiv.org/abs/2412.02732
|
| 25 |
+
|
| 26 |
+
# Model Description
|
| 27 |
+
|
| 28 |
+
Prithvi-EO-2.0 was jointly proposed by IBM, NASA, and the Jülich Supercomputing Centre. The model was trained on 4.2 million global four-timestamp samples from NASA's Harmonized Landsat Sentinel-2 dataset. It is suitable for disaster response, land-cover and crop mapping, ecosystem dynamics monitoring, and remote-sensing image classification, segmentation, and regression tasks.
|
| 29 |
+
|
| 30 |
+
# Use Cases
|
| 31 |
+
|
| 32 |
+
| Use Case | Description |
|
| 33 |
+
| :---: | :--- |
|
| 34 |
+
| Multi-temporal representation | Uses a Transformer to jointly encode spatial and temporal information from four time steps. |
|
| 35 |
+
| Remote-sensing image reconstruction | Uses a 3D Masked Autoencoder to reconstruct masked multispectral spatiotemporal patches. |
|
| 36 |
+
| Spatiotemporal metadata modeling | Integrates year, day of year, latitude, and longitude, with support for randomly dropping metadata during training. |
|
| 37 |
+
| Local engineering validation | Uses a small number of synthetic HLS samples to verify the training, inference, evaluation, visualization, and checkpoint workflows. |
|
| 38 |
+
| Multi-GPU training | Launches distributed data-parallel training through `torchrun`. |
|
| 39 |
+
|
| 40 |
+
# Usage Instructions
|
| 41 |
+
|
| 42 |
+
## 1. OneCode
|
| 43 |
+
|
| 44 |
+
Experience intelligent, one-click AI4S programming through the OneCode online environment:
|
| 45 |
+
|
| 46 |
+
[Try intelligent, one-click AI4S programming](https://web-2069360198568017922-iaaj.ksai.scnet.cn:58043/home)
|
| 47 |
+
|
| 48 |
+
## 2. Download and Installation
|
| 49 |
+
|
| 50 |
+
```bash
|
| 51 |
+
hf download OneScience-Group/PrithviEO --local-dir ./PrithviEO
|
| 52 |
+
cd PrithviEO
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
### Environment Dependencies
|
| 56 |
+
|
| 57 |
+
**Hardware Requirements**
|
| 58 |
+
|
| 59 |
+
- A GPU or DCU is recommended.
|
| 60 |
+
- A CPU can be used to verify connectivity with the default small-sample configuration; training an official-size model requires large-scale accelerated computing resources.
|
| 61 |
+
- DCU users must install DTK in advance. DTK 25.04.2 or later, or the OneScience-recommended version matching the current cluster, is recommended.
|
| 62 |
+
|
| 63 |
+
**DCU Environment**
|
| 64 |
+
|
| 65 |
+
```bash
|
| 66 |
+
# Activate DTK and Conda first
|
| 67 |
+
conda create -n onescience311 python=3.11 -y
|
| 68 |
+
conda activate onescience311
|
| 69 |
+
# Installation with uv is supported
|
| 70 |
+
pip install onescience[earth-dcu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 71 |
+
```
|
| 72 |
+
|
| 73 |
+
**GPU Environment**
|
| 74 |
+
|
| 75 |
+
```bash
|
| 76 |
+
# Activate Conda first
|
| 77 |
+
conda create -n onescience311 python=3.11 -y libstdcxx-ng=12 libgcc-ng=12 gcc_linux-64=12 gxx_linux-64=12
|
| 78 |
+
conda activate onescience311
|
| 79 |
+
# Installation with uv is supported
|
| 80 |
+
pip install onescience[earth-gpu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
### Training Data
|
| 84 |
+
|
| 85 |
+
This repository uses a small number of synthetic samples to validate the engineering workflow. The training and test data are stored in `data/train.npz` and `data/test.npz`, respectively. The synthetic data preserve the four time steps and six HLS common bands used in the paper. The band order is Blue, Green, Red, Narrow NIR, SWIR1, and SWIR2, and normalization uses the officially published means and standard deviations.
|
| 86 |
+
|
| 87 |
+
The synthetic data preserve the `4×224×224` spatiotemporal dimensions used for training in the paper, and each sample's complete image tensor has dimensions `6×4×224×224`. Only the number of samples, model width, model depth, and number of training epochs are reduced. This setup validates the 3D Patch Embedding, spatiotemporal positional encoding, temporal and location metadata encoding, and MAE training workflow; it does not represent the data distribution or training scale of the official 4.2 million HLS samples.
|
| 88 |
+
|
| 89 |
+
```bash
|
| 90 |
+
python scripts/fake_data.py
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
### Training
|
| 94 |
+
|
| 95 |
+
```bash
|
| 96 |
+
python scripts/train.py
|
| 97 |
+
```
|
| 98 |
+
|
| 99 |
+
For multi-GPU training, use:
|
| 100 |
+
|
| 101 |
+
```bash
|
| 102 |
+
torchrun --nproc_per_node=8 --nnodes=1 --rdzv_id=1000 --rdzv_backend=c10d --max_restarts=0 --master_addr="localhost" --master_port=29500 scripts/train.py
|
| 103 |
+
```
|
| 104 |
+
|
| 105 |
+
The default configuration is intended for rapid workflow validation. Formal experiments should use real HLS time series, the official 300M or 600M configuration, and the complete training schedule.
|
| 106 |
+
|
| 107 |
+
```text
|
| 108 |
+
result/checkpoints/prithvi_eo.pt
|
| 109 |
+
result/training/metrics.json
|
| 110 |
+
```
|
| 111 |
+
|
| 112 |
+
### Trained Weights
|
| 113 |
+
|
| 114 |
+
This repository does not include synthetic or official weights in `weight/`. IBM and NASA have published the Prithvi-EO-2.0 tiny, 100M, 300M, and 600M weights on Hugging Face, including the TL versions with temporal and location encodings:
|
| 115 |
+
|
| 116 |
+
https://huggingface.co/ibm-nasa-geospatial/Prithvi-EO-2.0-300M-TL
|
| 117 |
+
|
| 118 |
+
This repository is a reduced, independent engineering implementation. Its model parameter names and dimensions are not compatible with the official weights. To use the official weights, use TerraTorch or the implementation and data preprocessing workflow provided by the official repository.
|
| 119 |
+
|
| 120 |
+
### Inference
|
| 121 |
+
|
| 122 |
+
```bash
|
| 123 |
+
python scripts/inference.py
|
| 124 |
+
```
|
| 125 |
+
|
| 126 |
+
Inference loads the training checkpoint, generates the CLS embedding, spatiotemporal patch embeddings, multi-temporal reconstructions of masked patches, and the mask, and saves them to:
|
| 127 |
+
|
| 128 |
+
```text
|
| 129 |
+
result/output/predictions.npz
|
| 130 |
+
```
|
| 131 |
+
|
| 132 |
+
### Evaluation and Visualization
|
| 133 |
+
|
| 134 |
+
```bash
|
| 135 |
+
python scripts/result.py
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
The evaluation measures masked-patch MSE, full spatiotemporal reconstruction MAE, reconstruction error for each time step, and embedding norms. It also generates comparison plots of the input images, reconstructions, and absolute errors for all four time steps. Results on synthetic data only validate the engineering workflow and do not represent GEO-Bench performance or performance on real downstream tasks reported in the paper.
|
| 139 |
+
|
| 140 |
+
```text
|
| 141 |
+
result/evaluation/metrics.json
|
| 142 |
+
result/evaluation/comparison.png
|
| 143 |
+
```
|
| 144 |
+
|
| 145 |
+
# Official OneScience Information
|
| 146 |
+
|
| 147 |
+
| Platform | OneScience Main Repository | Skills Repository |
|
| 148 |
+
| --- | --- | --- |
|
| 149 |
+
| Gitee | https://gitee.com/onescience-ai/onescience | https://gitee.com/onescience-ai/oneskills |
|
| 150 |
+
| GitHub | https://github.com/onescience-ai/OneScience | https://github.com/onescience-ai/oneskills |
|
| 151 |
+
|
| 152 |
+
# Citation and License
|
| 153 |
+
|
| 154 |
+
This repository is an independent engineering reproduction based on the publicly available specifications from the Prithvi-EO-2.0 paper.
|
| 155 |
+
|
| 156 |
+
Use of this repository's code, the official model weights, and the data remains subject to the licenses and terms of use of their respective projects.
|
README_zh.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: Apache-2.0
|
| 3 |
+
language:
|
| 4 |
+
- en
|
| 5 |
+
- zh
|
| 6 |
+
tags:
|
| 7 |
+
- OneScience
|
| 8 |
+
- 地球科学
|
| 9 |
+
- 多时相地球观测
|
| 10 |
+
- 遥感基础模型
|
| 11 |
+
frameworks: PyTorch
|
| 12 |
+
datasets:
|
| 13 |
+
- Harmonized Landsat Sentinel-2
|
| 14 |
+
---
|
| 15 |
+
|
| 16 |
+
<p align="center">
|
| 17 |
+
<strong>
|
| 18 |
+
<span style="font-size: 30px;">Prithvi-EO</span>
|
| 19 |
+
</strong>
|
| 20 |
+
</p>
|
| 21 |
+
|
| 22 |
+
# 模型介绍
|
| 23 |
+
|
| 24 |
+
Prithvi-EO-2.0 是面向多时相地球观测数据的基础模型,将 HLS 多光谱时间序列、影像获取日期和地理位置编码为统一表征,并通过 Masked Autoencoder 重建被遮挡的时空 Patch,可用于遥感分类、语义分割、回归和环境变化监测。
|
| 25 |
+
|
| 26 |
+
论文:Prithvi-EO-2.0: A Versatile Multi-Temporal Foundation Model for Earth Observation Applications
|
| 27 |
+
https://arxiv.org/abs/2412.02732
|
| 28 |
+
|
| 29 |
+
# 模型描述
|
| 30 |
+
|
| 31 |
+
Prithvi-EO-2.0 由 IBM、NASA 和 Jülich Supercomputing Centre 联合提出。模型使用 NASA Harmonized Landsat Sentinel-2 数据集中 420 万个全球四时相样本训练。模型适用于灾害响应、土地覆盖与作物制图、生态系统动态监测,以及遥感影像分类、分割和回归任务。
|
| 32 |
+
|
| 33 |
+
# 适用场景
|
| 34 |
+
|
| 35 |
+
| 场景 | 说明 |
|
| 36 |
+
| :---: | :--- |
|
| 37 |
+
| 多时相表征 | 使用 Transformer 同时编码四个时间步的空间和时间信息。 |
|
| 38 |
+
| 遥感影像重建 | 使用 3D Masked Autoencoder 重建被遮挡的多光谱时空 Patch。 |
|
| 39 |
+
| 时空元数据建模 | 融合年份、年积日、纬度和经度,并支持训练时随机丢弃元数据。 |
|
| 40 |
+
| 本地工程验证 | 使用少量虚拟 HLS 样本检查训练、推理、评估、可视化和 checkpoint 流程。 |
|
| 41 |
+
| 多卡训练 | 通过 `torchrun` 启动分布式数据并行训练。 |
|
| 42 |
+
|
| 43 |
+
# 使用说明
|
| 44 |
+
|
| 45 |
+
## 1.OneCode
|
| 46 |
+
|
| 47 |
+
可通过 OneCode 在线环境体验智能化一键式 AI4S 编程:
|
| 48 |
+
|
| 49 |
+
[点击体验智能化一键式 AI4S 编程](https://web-2069360198568017922-iaaj.ksai.scnet.cn:58043/home)
|
| 50 |
+
|
| 51 |
+
## 2.下载安装
|
| 52 |
+
|
| 53 |
+
```bash
|
| 54 |
+
modelscope download --model OneScience/Prithvi-EO --local_dir ./PrithviEO
|
| 55 |
+
cd PrithviEO
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
### 环境依赖
|
| 59 |
+
|
| 60 |
+
**硬件要求**
|
| 61 |
+
|
| 62 |
+
- 推荐使用 GPU 或 DCU 运行。
|
| 63 |
+
- CPU 可用于默认小样本配置的连通性验证,官方尺寸模型训练需要大规模加速资源。
|
| 64 |
+
- DCU 用户需预先安装 DTK,建议使用 DTK 25.04.2 以上版本或与当前集群匹配的 OneScience 推荐版本。
|
| 65 |
+
|
| 66 |
+
**DCU环境**
|
| 67 |
+
|
| 68 |
+
```bash
|
| 69 |
+
# 请首先激活DTK及CONDA
|
| 70 |
+
conda create -n onescience311 python=3.11 -y
|
| 71 |
+
conda activate onescience311
|
| 72 |
+
# 支持uv安装
|
| 73 |
+
pip install onescience[earth-dcu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
**GPU环境**
|
| 77 |
+
|
| 78 |
+
```bash
|
| 79 |
+
# 请首先激活CONDA
|
| 80 |
+
conda create -n onescience311 python=3.11 -y libstdcxx-ng=12 libgcc-ng=12 gcc_linux-64=12 gxx_linux-64=12
|
| 81 |
+
conda activate onescience311
|
| 82 |
+
# 支持uv安装
|
| 83 |
+
pip install onescience[earth-gpu] -i http://mirrors.onescience.ai:3141/pypi/simple/ --trusted-host mirrors.onescience.ai
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
### 训练数据介绍
|
| 87 |
+
|
| 88 |
+
本仓库使用少量虚拟样本验证工程流程,训练数据和测试数据分别保存为 `data/train.npz` 和 `data/test.npz`。虚拟数据保持论文的四时间步与六个 HLS 公共波段,波段顺序为 Blue、Green、Red、Narrow NIR、SWIR1 和 SWIR2,并使用官方公开的均值与标准差完成归一化。
|
| 89 |
+
|
| 90 |
+
虚拟数据保持论文训练使用的 4×224×224 时空尺寸,每个样本的完整影像张量为 6×4×224×224。当前仅缩小样本数量、模型宽度、模型深度和训练周期,用于验证 3D Patch Embedding、时空位置编码、时间与位置元数据编码和 MAE 训练流程,不代表官方 420 万个 HLS 样本的数据分布与训练规模。
|
| 91 |
+
|
| 92 |
+
```bash
|
| 93 |
+
python scripts/fake_data.py
|
| 94 |
+
```
|
| 95 |
+
|
| 96 |
+
### 训练
|
| 97 |
+
|
| 98 |
+
```bash
|
| 99 |
+
python scripts/train.py
|
| 100 |
+
```
|
| 101 |
+
|
| 102 |
+
多卡训练可使用:
|
| 103 |
+
|
| 104 |
+
```bash
|
| 105 |
+
torchrun --nproc_per_node=8 --nnodes=1 --rdzv_id=1000 --rdzv_backend=c10d --max_restarts=0 --master_addr="localhost" --master_port=29500 scripts/train.py
|
| 106 |
+
```
|
| 107 |
+
|
| 108 |
+
默认配置面向快速流程验证;开展正式实验时,应使用真实 HLS 时间序列、官方 300M 或 600M 配置和完整训练周期。
|
| 109 |
+
|
| 110 |
+
```text
|
| 111 |
+
result/checkpoints/prithvi_eo.pt
|
| 112 |
+
result/training/metrics.json
|
| 113 |
+
```
|
| 114 |
+
|
| 115 |
+
### 训练权重
|
| 116 |
+
|
| 117 |
+
本仓库不在 `weight/` 中内置虚拟权重或官方权重。IBM 和 NASA 已在 Hugging Face 公开 Prithvi-EO-2.0 的 tiny、100M、300M 和 600M 权重,包括带时间与位置编码的 TL 版本:
|
| 118 |
+
|
| 119 |
+
https://huggingface.co/ibm-nasa-geospatial/Prithvi-EO-2.0-300M-TL
|
| 120 |
+
|
| 121 |
+
本仓库是缩小的独立工程实现,模型参数名称和尺寸不与官方权重兼容。需要使用官方权重时,应采用 TerraTorch 或官方仓库提供的实现和数据预处理流程。
|
| 122 |
+
|
| 123 |
+
### 推理
|
| 124 |
+
|
| 125 |
+
```bash
|
| 126 |
+
python scripts/inference.py
|
| 127 |
+
```
|
| 128 |
+
|
| 129 |
+
推理加载训练 checkpoint,生成 CLS embedding、时空 Patch embedding、被遮挡 Patch 的多时相重建结果和 Mask,并保存到:
|
| 130 |
+
|
| 131 |
+
```text
|
| 132 |
+
result/output/predictions.npz
|
| 133 |
+
```
|
| 134 |
+
|
| 135 |
+
### 评估和可视化
|
| 136 |
+
|
| 137 |
+
```bash
|
| 138 |
+
python scripts/result.py
|
| 139 |
+
```
|
| 140 |
+
|
| 141 |
+
评估模型的 masked-patch MSE、完整时空重建 MAE、各时间步重建误差和 embedding 范数。评估过程同时生成四个时间步的输入影像、重建结果和绝对误差对比图。虚拟数据结果仅用于验证工程流程,不代表论文中的 GEO-Bench 或真实下游任务性能。
|
| 142 |
+
|
| 143 |
+
```text
|
| 144 |
+
result/evaluation/metrics.json
|
| 145 |
+
result/evaluation/comparison.png
|
| 146 |
+
```
|
| 147 |
+
|
| 148 |
+
# OneScience官方信息
|
| 149 |
+
|
| 150 |
+
| 平台 | OneScience 主仓库 | Skills 仓库 |
|
| 151 |
+
| --- | --- | --- |
|
| 152 |
+
| Gitee | https://gitee.com/onescience-ai/onescience | https://gitee.com/onescience-ai/oneskills |
|
| 153 |
+
| GitHub | https://github.com/onescience-ai/OneScience | https://github.com/onescience-ai/oneskills |
|
| 154 |
+
|
| 155 |
+
# 引用与许可证
|
| 156 |
+
|
| 157 |
+
本仓库为 Prithvi-EO-2.0 论文公开规格的独立工程复现版本。
|
| 158 |
+
|
| 159 |
+
本仓库代码、官方模型权重和数据的使用仍应以各自项目中的许可证及使用条款为准。
|
conf/config.yaml
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
seed: 42
|
| 2 |
+
data:
|
| 3 |
+
root: data
|
| 4 |
+
format_version: prithvi_eo_2_synthetic_engineering_v1
|
| 5 |
+
train_samples: 4
|
| 6 |
+
test_samples: 2
|
| 7 |
+
channels: 6
|
| 8 |
+
frames: 4
|
| 9 |
+
image_size: 224
|
| 10 |
+
band_names: [Blue, Green, Red, Narrow_NIR, SWIR1, SWIR2]
|
| 11 |
+
band_codes: [B02, B03, B04, B8A, B11, B12]
|
| 12 |
+
mean: [1087.0, 1342.0, 1433.0, 2734.0, 1958.0, 1363.0]
|
| 13 |
+
std: [2248.0, 2179.0, 2178.0, 1850.0, 1242.0, 1049.0]
|
| 14 |
+
model:
|
| 15 |
+
input_size: [4, 224, 224]
|
| 16 |
+
patch_size: [1, 16, 16]
|
| 17 |
+
channels: 6
|
| 18 |
+
mask_ratio: 0.75
|
| 19 |
+
encoder_dim: 96
|
| 20 |
+
encoder_depth: 2
|
| 21 |
+
encoder_heads: 4
|
| 22 |
+
decoder_dim: 64
|
| 23 |
+
decoder_depth: 1
|
| 24 |
+
decoder_heads: 4
|
| 25 |
+
mlp_ratio: 4
|
| 26 |
+
metadata_dropout: 0.1
|
| 27 |
+
norm_pix_loss: false
|
| 28 |
+
paper_model_300M_TL:
|
| 29 |
+
input_size: [4, 224, 224]
|
| 30 |
+
patch_size: [1, 16, 16]
|
| 31 |
+
encoder_dim: 1024
|
| 32 |
+
encoder_depth: 24
|
| 33 |
+
encoder_heads: 16
|
| 34 |
+
decoder_dim: 512
|
| 35 |
+
decoder_depth: 8
|
| 36 |
+
decoder_heads: 16
|
| 37 |
+
parameter_count: 300M
|
| 38 |
+
paper_model_600M_TL:
|
| 39 |
+
input_size: [4, 224, 224]
|
| 40 |
+
patch_size: [1, 14, 14]
|
| 41 |
+
encoder_dim: 1280
|
| 42 |
+
encoder_depth: 32
|
| 43 |
+
encoder_heads: 16
|
| 44 |
+
decoder_dim: 512
|
| 45 |
+
decoder_depth: 8
|
| 46 |
+
decoder_heads: 16
|
| 47 |
+
parameter_count: 600M
|
| 48 |
+
paper_training:
|
| 49 |
+
samples: 4.2M
|
| 50 |
+
validation_samples: 46k
|
| 51 |
+
epochs: 400
|
| 52 |
+
global_batch_size: 3840
|
| 53 |
+
maximum_learning_rate: 0.0005
|
| 54 |
+
warmup_epochs: 40
|
| 55 |
+
weight_decay: 0.05
|
| 56 |
+
train:
|
| 57 |
+
epochs: 1
|
| 58 |
+
batch_size: 2
|
| 59 |
+
learning_rate: 0.0005
|
| 60 |
+
weight_decay: 0.000001
|
| 61 |
+
num_workers: 0
|
| 62 |
+
runtime:
|
| 63 |
+
device: auto
|
| 64 |
+
paths:
|
| 65 |
+
checkpoint: result/checkpoints/prithvi_eo.pt
|
| 66 |
+
training_metrics: result/training/metrics.json
|
| 67 |
+
inference_dir: result/output
|
| 68 |
+
evaluation_dir: result/evaluation
|
config.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_name": "PrithviEO",
|
| 3 |
+
"model_type": "prithvi_eo",
|
| 4 |
+
"architectures": ["PrithviEO2", "CoordinateEncoder", "Transformer"],
|
| 5 |
+
"framework": "PyTorch",
|
| 6 |
+
"domain": "earth-observation",
|
| 7 |
+
"task": "multi-temporal-remote-sensing-representation-learning",
|
| 8 |
+
"implementation": {
|
| 9 |
+
"entry_point": "model/prithvi_eo.py",
|
| 10 |
+
"scope": "engineering reproduction of the Prithvi-EO-2.0 temporal-location masked autoencoder",
|
| 11 |
+
"train_script": "scripts/train.py",
|
| 12 |
+
"inference_script": "scripts/inference.py",
|
| 13 |
+
"evaluation_script": "scripts/result.py",
|
| 14 |
+
"synthetic_data_script": "scripts/fake_data.py"
|
| 15 |
+
},
|
| 16 |
+
"architecture": {
|
| 17 |
+
"family": "three-dimensional vision-transformer masked autoencoder",
|
| 18 |
+
"input_size": [4, 224, 224],
|
| 19 |
+
"patch_size": [1, 16, 16],
|
| 20 |
+
"in_channels": 6,
|
| 21 |
+
"mask_ratio": 0.75,
|
| 22 |
+
"encoder_dim": 96,
|
| 23 |
+
"encoder_depth": 2,
|
| 24 |
+
"encoder_heads": 4,
|
| 25 |
+
"decoder_dim": 64,
|
| 26 |
+
"decoder_depth": 1,
|
| 27 |
+
"decoder_heads": 4,
|
| 28 |
+
"metadata": ["year", "day_of_year", "latitude", "longitude"],
|
| 29 |
+
"outputs": ["loss", "embedding", "patch_embeddings", "reconstruction", "mask"]
|
| 30 |
+
},
|
| 31 |
+
"data": {
|
| 32 |
+
"datasets": ["Harmonized Landsat Sentinel-2"],
|
| 33 |
+
"protocol": "prithvi_eo_2_synthetic_engineering_v1",
|
| 34 |
+
"format": "NPZ",
|
| 35 |
+
"train_file": "data/train.npz",
|
| 36 |
+
"test_file": "data/test.npz",
|
| 37 |
+
"input_shape": ["N", 6, 4, 224, 224],
|
| 38 |
+
"temporal_shape": ["N", 4, 2],
|
| 39 |
+
"location_shape": ["N", 2],
|
| 40 |
+
"bands": ["B02", "B03", "B04", "B8A", "B11", "B12"],
|
| 41 |
+
"required_metadata": ["format_version", "data_source", "temporal_coords", "location_coords"]
|
| 42 |
+
},
|
| 43 |
+
"configuration_sources": ["conf/config.yaml", "model/prithvi_eo.py", "scripts/fake_data.py", "scripts/train.py", "scripts/inference.py", "scripts/result.py"]
|
| 44 |
+
}
|
configuration.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"framework": "PyTorch",
|
| 3 |
+
"task": "multi_temporal_earth_observation_representation_learning",
|
| 4 |
+
"model": "Prithvi-EO-2.0",
|
| 5 |
+
"input_format": "BCTHW",
|
| 6 |
+
"protocol": "engineering_3d_mae_training_and_inference",
|
| 7 |
+
"default_config": "conf/config.yaml",
|
| 8 |
+
"training": "scripts/train.py",
|
| 9 |
+
"inference": "scripts/inference.py",
|
| 10 |
+
"evaluation": "scripts/result.py",
|
| 11 |
+
"visualization": "scripts/result.py"
|
| 12 |
+
}
|
model/prithvi_eo.py
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Pure-PyTorch engineering reproduction of the Prithvi-EO-2.0 TL MAE."""
|
| 2 |
+
|
| 3 |
+
import math
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
from torch import nn
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def sincos_1d(positions, dim):
|
| 10 |
+
if dim % 2:
|
| 11 |
+
raise ValueError("sine/cosine dimensions must be even")
|
| 12 |
+
frequencies = torch.exp(
|
| 13 |
+
torch.arange(dim // 2, device=positions.device, dtype=positions.dtype)
|
| 14 |
+
* (-math.log(10000.0) / max(dim // 2, 1))
|
| 15 |
+
)
|
| 16 |
+
angles = positions.unsqueeze(-1) * frequencies
|
| 17 |
+
return torch.cat((angles.sin(), angles.cos()), dim=-1)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def sincos_3d(frames, height, width, dim, device, dtype):
|
| 21 |
+
if dim % 16:
|
| 22 |
+
raise ValueError("3D position dimension must be divisible by 16")
|
| 23 |
+
width_dim, height_dim, time_dim = 6 * dim // 16, 6 * dim // 16, 4 * dim // 16
|
| 24 |
+
time, row, column = torch.meshgrid(
|
| 25 |
+
torch.arange(frames, device=device, dtype=dtype),
|
| 26 |
+
torch.arange(height, device=device, dtype=dtype),
|
| 27 |
+
torch.arange(width, device=device, dtype=dtype),
|
| 28 |
+
indexing="ij",
|
| 29 |
+
)
|
| 30 |
+
return torch.cat((
|
| 31 |
+
sincos_1d(column.reshape(-1), width_dim),
|
| 32 |
+
sincos_1d(row.reshape(-1), height_dim),
|
| 33 |
+
sincos_1d(time.reshape(-1), time_dim),
|
| 34 |
+
), dim=-1)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def patchify(values, patch_size):
|
| 38 |
+
batch, channels, frames, height, width = values.shape
|
| 39 |
+
pt, ph, pw = patch_size
|
| 40 |
+
if frames % pt or height % ph or width % pw:
|
| 41 |
+
raise ValueError("input dimensions must be divisible by patch_size")
|
| 42 |
+
return values.reshape(
|
| 43 |
+
batch, channels, frames // pt, pt, height // ph, ph, width // pw, pw
|
| 44 |
+
).permute(0, 2, 4, 6, 3, 5, 7, 1).reshape(batch, -1, pt * ph * pw * channels)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def unpatchify(patches, channels, output_size, patch_size):
|
| 48 |
+
batch = patches.shape[0]
|
| 49 |
+
frames, height, width = output_size
|
| 50 |
+
pt, ph, pw = patch_size
|
| 51 |
+
return patches.reshape(
|
| 52 |
+
batch, frames // pt, height // ph, width // pw, pt, ph, pw, channels
|
| 53 |
+
).permute(0, 7, 1, 4, 2, 5, 3, 6).reshape(batch, channels, frames, height, width)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
class Transformer(nn.Module):
|
| 57 |
+
def __init__(self, dim, depth, heads, mlp_ratio):
|
| 58 |
+
super().__init__()
|
| 59 |
+
layer = nn.TransformerEncoderLayer(
|
| 60 |
+
dim, heads, int(dim * mlp_ratio), activation="gelu", batch_first=True, norm_first=True
|
| 61 |
+
)
|
| 62 |
+
self.blocks = nn.TransformerEncoder(layer, depth)
|
| 63 |
+
self.norm = nn.LayerNorm(dim)
|
| 64 |
+
|
| 65 |
+
def forward(self, values):
|
| 66 |
+
return self.norm(self.blocks(values))
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
class CoordinateEncoder(nn.Module):
|
| 70 |
+
def __init__(self, dim, scale=0.1):
|
| 71 |
+
super().__init__()
|
| 72 |
+
if dim % 4:
|
| 73 |
+
raise ValueError("coordinate embedding dimension must be divisible by four")
|
| 74 |
+
self.dim = dim
|
| 75 |
+
self.scale = nn.Parameter(torch.tensor(float(scale)))
|
| 76 |
+
|
| 77 |
+
def forward(self, coordinates):
|
| 78 |
+
return self.scale * torch.cat((
|
| 79 |
+
sincos_1d(coordinates[..., 0], self.dim // 2),
|
| 80 |
+
sincos_1d(coordinates[..., 1], self.dim // 2),
|
| 81 |
+
), dim=-1)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
class PrithviEO2(nn.Module):
|
| 85 |
+
def __init__(self, config):
|
| 86 |
+
super().__init__()
|
| 87 |
+
self.config = dict(config)
|
| 88 |
+
self.input_size = tuple(int(value) for value in config["input_size"])
|
| 89 |
+
self.patch_size = tuple(int(value) for value in config["patch_size"])
|
| 90 |
+
self.channels = int(config["channels"])
|
| 91 |
+
self.mask_ratio = float(config["mask_ratio"])
|
| 92 |
+
self.metadata_dropout = float(config["metadata_dropout"])
|
| 93 |
+
self.norm_pix_loss = bool(config.get("norm_pix_loss", False))
|
| 94 |
+
enc_dim, dec_dim = int(config["encoder_dim"]), int(config["decoder_dim"])
|
| 95 |
+
self.patch_embed = nn.Conv3d(
|
| 96 |
+
self.channels, enc_dim, kernel_size=self.patch_size, stride=self.patch_size
|
| 97 |
+
)
|
| 98 |
+
self.cls_token = nn.Parameter(torch.randn(1, 1, enc_dim) * 0.02)
|
| 99 |
+
self.encoder = Transformer(enc_dim, int(config["encoder_depth"]), int(config["encoder_heads"]),
|
| 100 |
+
float(config["mlp_ratio"]))
|
| 101 |
+
self.encoder_to_decoder = nn.Linear(enc_dim, dec_dim)
|
| 102 |
+
self.mask_token = nn.Parameter(torch.randn(1, 1, dec_dim) * 0.02)
|
| 103 |
+
self.decoder = Transformer(dec_dim, int(config["decoder_depth"]), int(config["decoder_heads"]),
|
| 104 |
+
float(config["mlp_ratio"]))
|
| 105 |
+
patch_volume = math.prod(self.patch_size) * self.channels
|
| 106 |
+
self.decoder_prediction = nn.Linear(dec_dim, patch_volume)
|
| 107 |
+
self.time_encoder = CoordinateEncoder(enc_dim)
|
| 108 |
+
self.location_encoder = CoordinateEncoder(enc_dim)
|
| 109 |
+
self.decoder_time_encoder = CoordinateEncoder(dec_dim)
|
| 110 |
+
self.decoder_location_encoder = CoordinateEncoder(dec_dim)
|
| 111 |
+
|
| 112 |
+
def _grid(self, pixels):
|
| 113 |
+
return tuple(size // patch for size, patch in zip(pixels.shape[-3:], self.patch_size))
|
| 114 |
+
|
| 115 |
+
def _metadata(self, temporal, location, grid, encoder=True):
|
| 116 |
+
frames, height, width = grid
|
| 117 |
+
time_encoder = self.time_encoder if encoder else self.decoder_time_encoder
|
| 118 |
+
location_encoder = self.location_encoder if encoder else self.decoder_location_encoder
|
| 119 |
+
temporal_embedding = time_encoder(temporal)
|
| 120 |
+
temporal_embedding = temporal_embedding[:, :, None, :].expand(-1, -1, height * width, -1).reshape(
|
| 121 |
+
len(temporal), frames * height * width, -1
|
| 122 |
+
)
|
| 123 |
+
location_embedding = location_encoder(location)[:, None, :].expand(-1, frames * height * width, -1)
|
| 124 |
+
if self.training and self.metadata_dropout:
|
| 125 |
+
time_keep = (torch.rand(len(temporal), 1, 1, device=temporal.device) >= self.metadata_dropout).to(temporal.dtype)
|
| 126 |
+
location_keep = (torch.rand(len(location), 1, 1, device=location.device) >= self.metadata_dropout).to(location.dtype)
|
| 127 |
+
temporal_embedding = temporal_embedding * time_keep
|
| 128 |
+
location_embedding = location_embedding * location_keep
|
| 129 |
+
return temporal_embedding + location_embedding
|
| 130 |
+
|
| 131 |
+
def _encoded_tokens(self, pixels, temporal, location):
|
| 132 |
+
grid = self._grid(pixels)
|
| 133 |
+
tokens = self.patch_embed(pixels).flatten(2).transpose(1, 2)
|
| 134 |
+
position = sincos_3d(*grid, tokens.shape[-1], tokens.device, tokens.dtype)
|
| 135 |
+
tokens = tokens + position[None] + self._metadata(temporal, location, grid, encoder=True)
|
| 136 |
+
return tokens, grid
|
| 137 |
+
|
| 138 |
+
def encode(self, pixels, temporal, location):
|
| 139 |
+
tokens, _ = self._encoded_tokens(pixels, temporal, location)
|
| 140 |
+
cls = self.cls_token.expand(len(pixels), -1, -1)
|
| 141 |
+
encoded = self.encoder(torch.cat((cls, tokens), dim=1))
|
| 142 |
+
return encoded[:, 0], encoded[:, 1:]
|
| 143 |
+
|
| 144 |
+
def forward(self, pixels, temporal, location, mask_ratio=None):
|
| 145 |
+
ratio = self.mask_ratio if mask_ratio is None else float(mask_ratio)
|
| 146 |
+
tokens, grid = self._encoded_tokens(pixels, temporal, location)
|
| 147 |
+
batch, length, dim = tokens.shape
|
| 148 |
+
keep = max(1, int(length * (1.0 - ratio)))
|
| 149 |
+
ordering = torch.rand(batch, length, device=pixels.device).argsort(dim=1)
|
| 150 |
+
visible_indices, masked_indices = ordering[:, :keep], ordering[:, keep:]
|
| 151 |
+
visible = tokens.gather(1, visible_indices[:, :, None].expand(-1, -1, dim))
|
| 152 |
+
encoded = self.encoder(torch.cat((self.cls_token.expand(batch, -1, -1), visible), dim=1))
|
| 153 |
+
embedding = encoded[:, 0]
|
| 154 |
+
visible_decoder = self.encoder_to_decoder(encoded[:, 1:])
|
| 155 |
+
decoder_tokens = self.mask_token.expand(batch, length, -1).clone()
|
| 156 |
+
decoder_tokens.scatter_(1, visible_indices[:, :, None].expand(-1, -1, visible_decoder.shape[-1]), visible_decoder)
|
| 157 |
+
position = sincos_3d(*grid, decoder_tokens.shape[-1], decoder_tokens.device, decoder_tokens.dtype)
|
| 158 |
+
decoder_tokens = decoder_tokens + position[None] + self._metadata(temporal, location, grid, encoder=False)
|
| 159 |
+
predictions = self.decoder_prediction(self.decoder(decoder_tokens))
|
| 160 |
+
targets = patchify(pixels, self.patch_size)
|
| 161 |
+
if self.norm_pix_loss:
|
| 162 |
+
mean, variance = targets.mean(dim=-1, keepdim=True), targets.var(dim=-1, keepdim=True)
|
| 163 |
+
targets = (targets - mean) / (variance + 1e-6).sqrt()
|
| 164 |
+
mask = torch.zeros(batch, length, device=pixels.device)
|
| 165 |
+
mask.scatter_(1, masked_indices, 1.0)
|
| 166 |
+
patch_mse = (predictions - targets).pow(2).mean(dim=-1)
|
| 167 |
+
loss = (patch_mse * mask).sum() / mask.sum().clamp_min(1)
|
| 168 |
+
reconstruction = unpatchify(predictions, self.channels, pixels.shape[-3:], self.patch_size)
|
| 169 |
+
return {
|
| 170 |
+
"loss": loss,
|
| 171 |
+
"embedding": embedding,
|
| 172 |
+
"patch_embeddings": encoded[:, 1:],
|
| 173 |
+
"reconstruction": reconstruction,
|
| 174 |
+
"mask": mask,
|
| 175 |
+
}
|
scripts/fake_data.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Generate deterministic HLS-like four-timestamp samples for engineering validation."""
|
| 2 |
+
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
|
| 5 |
+
import numpy as np
|
| 6 |
+
import yaml
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
ROOT = Path(__file__).resolve().parents[1]
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def make_split(path, count, config, seed):
|
| 13 |
+
rng = np.random.default_rng(seed)
|
| 14 |
+
data = config["data"]
|
| 15 |
+
channels, frames, size = int(data["channels"]), int(data["frames"]), int(data["image_size"])
|
| 16 |
+
means = np.asarray(data["mean"], np.float32)
|
| 17 |
+
stds = np.asarray(data["std"], np.float32)
|
| 18 |
+
y, x = np.mgrid[-1:1:complex(size), -1:1:complex(size)].astype(np.float32)
|
| 19 |
+
pixels = np.empty((count, channels, frames, size, size), np.float32)
|
| 20 |
+
temporal = np.empty((count, frames, 2), np.float32)
|
| 21 |
+
location = np.empty((count, 2), np.float32)
|
| 22 |
+
class_target = np.empty(count, np.int64)
|
| 23 |
+
regression_target = np.empty(count, np.float32)
|
| 24 |
+
for sample in range(count):
|
| 25 |
+
latitude, longitude = rng.uniform(-70, 70), rng.uniform(-180, 180)
|
| 26 |
+
start_day = int(rng.integers(1, 80))
|
| 27 |
+
days = np.clip(start_day + np.arange(frames) * int(rng.integers(45, 100)), 1, 365)
|
| 28 |
+
temporal[sample, :, 0] = 2018 + sample % 5
|
| 29 |
+
temporal[sample, :, 1] = days
|
| 30 |
+
location[sample] = (latitude, longitude)
|
| 31 |
+
phase = rng.uniform(0, 2 * np.pi)
|
| 32 |
+
class_target[sample] = int(np.sin(phase) > 0)
|
| 33 |
+
regression_target[sample] = np.cos(phase) + latitude / 180
|
| 34 |
+
for step, day in enumerate(days):
|
| 35 |
+
seasonal = np.sin(2 * np.pi * day / 365 + phase)
|
| 36 |
+
landscape = np.sin(2.5 * np.pi * x + phase) * np.cos(2 * np.pi * y - phase)
|
| 37 |
+
landscape += 0.35 * x + 0.2 * y + 0.25 * seasonal
|
| 38 |
+
for channel in range(channels):
|
| 39 |
+
normalized = landscape + 0.12 * channel + rng.normal(0, 0.04, (size, size))
|
| 40 |
+
pixels[sample, channel, step] = normalized * stds[channel] + means[channel]
|
| 41 |
+
payload = {
|
| 42 |
+
"format_version": np.asarray(data["format_version"]),
|
| 43 |
+
"data_source": np.asarray("synthetic_hls_like"),
|
| 44 |
+
"pixels": pixels,
|
| 45 |
+
"temporal_coords": temporal,
|
| 46 |
+
"location_coords": location,
|
| 47 |
+
"class_target": class_target,
|
| 48 |
+
"regression_target": regression_target,
|
| 49 |
+
}
|
| 50 |
+
np.savez_compressed(path, **payload)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def main():
|
| 54 |
+
config = yaml.safe_load((ROOT / "conf/config.yaml").read_text())
|
| 55 |
+
output = ROOT / config["data"]["root"]
|
| 56 |
+
output.mkdir(parents=True, exist_ok=True)
|
| 57 |
+
for offset, (filename, count) in enumerate((
|
| 58 |
+
("train.npz", int(config["data"]["train_samples"])),
|
| 59 |
+
("test.npz", int(config["data"]["test_samples"])),
|
| 60 |
+
)):
|
| 61 |
+
path = output / filename
|
| 62 |
+
if not path.exists():
|
| 63 |
+
make_split(path, count, config, int(config["seed"]) + offset)
|
| 64 |
+
print(f"generated={path.relative_to(ROOT)} samples={count} format={config['data']['format_version']}")
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
if __name__ == "__main__":
|
| 68 |
+
main()
|
scripts/inference.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Run multi-temporal reconstruction and embedding inference."""
|
| 2 |
+
|
| 3 |
+
import sys
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
import numpy as np
|
| 7 |
+
import torch
|
| 8 |
+
import yaml
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
ROOT = Path(__file__).resolve().parents[1]
|
| 12 |
+
sys.path.insert(0, str(ROOT))
|
| 13 |
+
from model.prithvi_eo import PrithviEO2
|
| 14 |
+
from train import PrithviDataset, device_from_config
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def main():
|
| 18 |
+
config = yaml.safe_load((ROOT / "conf/config.yaml").read_text())
|
| 19 |
+
device = device_from_config(config)
|
| 20 |
+
checkpoint = torch.load(ROOT / config["paths"]["checkpoint"], map_location=device, weights_only=True)
|
| 21 |
+
if checkpoint["format_version"] != config["data"]["format_version"]:
|
| 22 |
+
raise ValueError("checkpoint and data formats are incompatible")
|
| 23 |
+
model = PrithviEO2(checkpoint["model_config"]).to(device)
|
| 24 |
+
model.load_state_dict(checkpoint["model"])
|
| 25 |
+
model.eval()
|
| 26 |
+
dataset = PrithviDataset(ROOT / config["data"]["root"] / "test.npz", config)
|
| 27 |
+
pixels = torch.stack([dataset[index]["pixels"] for index in range(len(dataset))]).to(device)
|
| 28 |
+
temporal = torch.stack([dataset[index]["temporal"] for index in range(len(dataset))]).to(device)
|
| 29 |
+
location = torch.stack([dataset[index]["location"] for index in range(len(dataset))]).to(device)
|
| 30 |
+
torch.manual_seed(int(config["seed"]))
|
| 31 |
+
with torch.no_grad():
|
| 32 |
+
output = model(pixels, temporal, location)
|
| 33 |
+
cls_embedding, patch_embeddings = model.encode(pixels, temporal, location)
|
| 34 |
+
target = ROOT / config["paths"]["inference_dir"] / "predictions.npz"
|
| 35 |
+
target.parent.mkdir(parents=True, exist_ok=True)
|
| 36 |
+
np.savez_compressed(
|
| 37 |
+
target,
|
| 38 |
+
format_version=np.asarray(config["data"]["format_version"]),
|
| 39 |
+
pixels=pixels.cpu().numpy(),
|
| 40 |
+
reconstruction=output["reconstruction"].cpu().numpy(),
|
| 41 |
+
mask=output["mask"].cpu().numpy(),
|
| 42 |
+
embedding=cls_embedding.cpu().numpy(),
|
| 43 |
+
patch_embeddings=patch_embeddings.cpu().numpy(),
|
| 44 |
+
temporal_coords=temporal.cpu().numpy(),
|
| 45 |
+
location_coords=location.cpu().numpy(),
|
| 46 |
+
class_target=dataset.data["class_target"],
|
| 47 |
+
regression_target=dataset.data["regression_target"],
|
| 48 |
+
masked_patch_mse=np.asarray(float(output["loss"])),
|
| 49 |
+
)
|
| 50 |
+
print(f"predictions={target.relative_to(ROOT)}")
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
if __name__ == "__main__":
|
| 54 |
+
main()
|
scripts/result.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Evaluate Prithvi reconstruction and visualize temporal HLS samples."""
|
| 2 |
+
|
| 3 |
+
import json
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
import matplotlib
|
| 7 |
+
matplotlib.use("Agg")
|
| 8 |
+
import matplotlib.pyplot as plt
|
| 9 |
+
import numpy as np
|
| 10 |
+
import yaml
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
ROOT = Path(__file__).resolve().parents[1]
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def main():
|
| 17 |
+
config = yaml.safe_load((ROOT / "conf/config.yaml").read_text())
|
| 18 |
+
prediction = np.load(ROOT / config["paths"]["inference_dir"] / "predictions.npz")
|
| 19 |
+
pixels, reconstruction = prediction["pixels"], prediction["reconstruction"]
|
| 20 |
+
error = np.abs(reconstruction - pixels)
|
| 21 |
+
per_frame = error.mean(axis=(0, 1, 3, 4))
|
| 22 |
+
embeddings = prediction["embedding"]
|
| 23 |
+
metrics = {
|
| 24 |
+
"samples": int(len(pixels)),
|
| 25 |
+
"masked_patch_mse": float(prediction["masked_patch_mse"]),
|
| 26 |
+
"reconstruction_mae": float(error.mean()),
|
| 27 |
+
"per_frame_reconstruction_mae": [float(value) for value in per_frame],
|
| 28 |
+
"mean_embedding_norm": float(np.linalg.norm(embeddings, axis=1).mean()),
|
| 29 |
+
}
|
| 30 |
+
output = ROOT / config["paths"]["evaluation_dir"]
|
| 31 |
+
output.mkdir(parents=True, exist_ok=True)
|
| 32 |
+
(output / "metrics.json").write_text(json.dumps(metrics, indent=2) + "\n")
|
| 33 |
+
figure, axes = plt.subplots(3, int(config["data"]["frames"]), figsize=(12, 8))
|
| 34 |
+
for frame in range(int(config["data"]["frames"])):
|
| 35 |
+
source = pixels[0, [2, 1, 0], frame].transpose(1, 2, 0)
|
| 36 |
+
rebuilt = reconstruction[0, [2, 1, 0], frame].transpose(1, 2, 0)
|
| 37 |
+
low, high = np.percentile(source, (2, 98))
|
| 38 |
+
source = np.clip((source - low) / max(high - low, 1e-6), 0, 1)
|
| 39 |
+
rebuilt = np.clip((rebuilt - low) / max(high - low, 1e-6), 0, 1)
|
| 40 |
+
axes[0, frame].imshow(source)
|
| 41 |
+
axes[1, frame].imshow(rebuilt)
|
| 42 |
+
axes[2, frame].imshow(error[0, :, frame].mean(axis=0), cmap="magma")
|
| 43 |
+
axes[0, frame].set_title(f"time {frame + 1}")
|
| 44 |
+
for axis in axes[:, frame]:
|
| 45 |
+
axis.axis("off")
|
| 46 |
+
axes[0, 0].set_ylabel("input")
|
| 47 |
+
axes[1, 0].set_ylabel("reconstruction")
|
| 48 |
+
axes[2, 0].set_ylabel("absolute error")
|
| 49 |
+
figure.tight_layout()
|
| 50 |
+
figure.savefig(output / "comparison.png", dpi=150)
|
| 51 |
+
plt.close(figure)
|
| 52 |
+
print(f"metrics={output.relative_to(ROOT) / 'metrics.json'}")
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
if __name__ == "__main__":
|
| 56 |
+
main()
|
scripts/train.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Train the reduced Prithvi-EO-2.0 temporal-location MAE."""
|
| 2 |
+
|
| 3 |
+
import json
|
| 4 |
+
import os
|
| 5 |
+
import sys
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
import torch
|
| 10 |
+
import yaml
|
| 11 |
+
from torch.nn.parallel import DistributedDataParallel
|
| 12 |
+
from torch.utils.data import DataLoader, Dataset, DistributedSampler
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
ROOT = Path(__file__).resolve().parents[1]
|
| 16 |
+
sys.path.insert(0, str(ROOT))
|
| 17 |
+
from model.prithvi_eo import PrithviEO2
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class PrithviDataset(Dataset):
|
| 21 |
+
def __init__(self, path, config):
|
| 22 |
+
self.data = np.load(path)
|
| 23 |
+
self.config = config
|
| 24 |
+
if str(self.data["format_version"]) != config["data"]["format_version"]:
|
| 25 |
+
raise ValueError("incompatible data format")
|
| 26 |
+
expected = (
|
| 27 |
+
int(config["data"]["channels"]), int(config["data"]["frames"]),
|
| 28 |
+
int(config["data"]["image_size"]), int(config["data"]["image_size"]),
|
| 29 |
+
)
|
| 30 |
+
if self.data["pixels"].shape[1:] != expected:
|
| 31 |
+
raise ValueError(f"pixels have shape {self.data['pixels'].shape[1:]}, expected {expected}")
|
| 32 |
+
self.mean = torch.tensor(config["data"]["mean"], dtype=torch.float32)[:, None, None, None]
|
| 33 |
+
self.std = torch.tensor(config["data"]["std"], dtype=torch.float32)[:, None, None, None]
|
| 34 |
+
|
| 35 |
+
def __len__(self):
|
| 36 |
+
return len(self.data["pixels"])
|
| 37 |
+
|
| 38 |
+
def __getitem__(self, index):
|
| 39 |
+
pixels = torch.from_numpy(self.data["pixels"][index]).float()
|
| 40 |
+
return {
|
| 41 |
+
"pixels": (pixels - self.mean) / self.std,
|
| 42 |
+
"temporal": torch.from_numpy(self.data["temporal_coords"][index]).float(),
|
| 43 |
+
"location": torch.from_numpy(self.data["location_coords"][index]).float(),
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def device_from_config(config, local_rank=0):
|
| 48 |
+
requested = config["runtime"]["device"]
|
| 49 |
+
if requested == "auto":
|
| 50 |
+
return torch.device("cuda", local_rank) if torch.cuda.is_available() else torch.device("cpu")
|
| 51 |
+
return torch.device(requested)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def main():
|
| 55 |
+
config = yaml.safe_load((ROOT / "conf/config.yaml").read_text())
|
| 56 |
+
torch.manual_seed(int(config["seed"]))
|
| 57 |
+
distributed = int(os.environ.get("WORLD_SIZE", "1")) > 1
|
| 58 |
+
local_rank = int(os.environ.get("LOCAL_RANK", "0"))
|
| 59 |
+
if distributed:
|
| 60 |
+
torch.distributed.init_process_group("nccl" if torch.cuda.is_available() else "gloo")
|
| 61 |
+
rank = torch.distributed.get_rank() if distributed else 0
|
| 62 |
+
device = device_from_config(config, local_rank)
|
| 63 |
+
if device.type == "cuda":
|
| 64 |
+
torch.cuda.set_device(device)
|
| 65 |
+
dataset = PrithviDataset(ROOT / config["data"]["root"] / "train.npz", config)
|
| 66 |
+
sampler = DistributedSampler(dataset, shuffle=True) if distributed else None
|
| 67 |
+
loader = DataLoader(dataset, batch_size=int(config["train"]["batch_size"]), sampler=sampler,
|
| 68 |
+
shuffle=sampler is None, num_workers=int(config["train"]["num_workers"]))
|
| 69 |
+
model = PrithviEO2(config["model"]).to(device)
|
| 70 |
+
if distributed:
|
| 71 |
+
model = DistributedDataParallel(model, device_ids=[local_rank] if device.type == "cuda" else None)
|
| 72 |
+
optimizer = torch.optim.AdamW(model.parameters(), lr=float(config["train"]["learning_rate"]),
|
| 73 |
+
weight_decay=float(config["train"]["weight_decay"]), betas=(0.9, 0.95))
|
| 74 |
+
history = []
|
| 75 |
+
for epoch in range(int(config["train"]["epochs"])):
|
| 76 |
+
if sampler:
|
| 77 |
+
sampler.set_epoch(epoch)
|
| 78 |
+
model.train()
|
| 79 |
+
total, steps = 0.0, 0
|
| 80 |
+
for batch in loader:
|
| 81 |
+
output = model(batch["pixels"].to(device), batch["temporal"].to(device), batch["location"].to(device))
|
| 82 |
+
optimizer.zero_grad(set_to_none=True)
|
| 83 |
+
output["loss"].backward()
|
| 84 |
+
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
|
| 85 |
+
optimizer.step()
|
| 86 |
+
total += float(output["loss"].detach())
|
| 87 |
+
steps += 1
|
| 88 |
+
metrics = {"epoch": epoch + 1, "masked_patch_mse": total / max(steps, 1)}
|
| 89 |
+
history.append(metrics)
|
| 90 |
+
if rank == 0:
|
| 91 |
+
print(f"epoch={epoch + 1} masked_patch_mse={metrics['masked_patch_mse']:.6f}")
|
| 92 |
+
if rank == 0:
|
| 93 |
+
checkpoint = ROOT / config["paths"]["checkpoint"]
|
| 94 |
+
metrics_path = ROOT / config["paths"]["training_metrics"]
|
| 95 |
+
checkpoint.parent.mkdir(parents=True, exist_ok=True)
|
| 96 |
+
metrics_path.parent.mkdir(parents=True, exist_ok=True)
|
| 97 |
+
state = model.module.state_dict() if distributed else model.state_dict()
|
| 98 |
+
torch.save({"model": state, "model_config": config["model"],
|
| 99 |
+
"format_version": config["data"]["format_version"]}, checkpoint)
|
| 100 |
+
metrics_path.write_text(json.dumps({"history": history}, indent=2) + "\n")
|
| 101 |
+
print(f"checkpoint={checkpoint.relative_to(ROOT)}")
|
| 102 |
+
if distributed:
|
| 103 |
+
torch.distributed.destroy_process_group()
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
if __name__ == "__main__":
|
| 107 |
+
main()
|
weight/.gitkeep
ADDED
|
File without changes
|