NeuralHydrologyに渡す前のデータ作成:DuckDBで管理してGenericDataset形式に出力する
はじめに
前回の記事では、NeuralHydrologyを使って水文LSTMを進める全体像を整理しました。次はいよいよStep 1のCudaLSTMに入りたいところですが、その前にやることがあります。
それは、NeuralHydrologyに渡せる形でデータを作ること です。
NeuralHydrologyは便利なライブラリですが、独自データを使う場合は、データの作成方法をそろえる必要があります。この記事では、まず小さなサンプルデータを作り、DuckDBで管理し、最後にNeuralHydrologyのGenericDataset形式へ出力するところまでを扱います。
この記事で作るものは次の2つです。
NeuralHydrologyに渡す前のデータ作成
-
サンプルデータを作る
- basin_001.nc
- basin_002.nc
- basin_003.nc
-
GenericDataset形式で読み込める形にする
- time_seriesフォルダ
- attributesフォルダ
後続のStep 1では、このデータをそのまま使ってCudaLSTM + 日単位 + GenericDatasetを動かします。
参考にしているNeuralHydrologyのURLです。
今回の方針
この記事では、次の構成で進めます。
Python 3.13
Docker Compose
Jupyter Lab
GPU対応
DuckDB
NetCDF
NeuralHydrology GenericDataset形式
データ作成だけならGPUは必須ではありません。ただ、Step 1以降でCudaLSTMを動かすことを考えると、最初からGPUが見えるDocker環境にしておくと楽です。
参考のためソースコードと開発環境一式をZIPファイルにしていますので、参考にしてください。
GenericDatasetで必要な形
NeuralHydrologyのGenericDatasetでは、data_dirの下にtime_seriesフォルダを置きます。このフォルダには、流域ごとに1つのNetCDFファイルを置きます。
data/neuralhydrology/
├─ time_series/
│ ├─ basin_001.nc
│ ├─ basin_002.nc
│ └─ basin_003.nc
│
└─ attributes/
└─ attributes.csv
NetCDFファイルには、dateという日時データが必要です。流域属性を使う場合は、attributesフォルダにCSVを置き、流域IDをキーにして属性を管理します。
今回のNetCDFには、次の変数を入れます。
| 変数名 | 内容 | 単位 |
|---|---|---|
precipitation |
日雨量 | mm/day |
temperature |
日平均気温 | degree_C |
pet |
可能蒸発散量 | mm/day |
discharge |
流出高 | mm/day |
discharge_m3s |
流量換算値 | m3/s |
Step 1では、precipitation、temperature、petを入力、dischargeを教師データとして使う想定です。
なぜDuckDBで管理するのか
NetCDFだけを直接作っても、Step 1は動かせます。ですが、後から流域を増やしたり、時間雨量や水位を追加したり、品質フラグを持たせたりすると、ファイルだけで管理するのは少しつらくなります。
そこで、この記事ではDuckDBをデータの元帳として使います。
考え方はこうです。
DuckDB
↓ export
NeuralHydrology GenericDataset
├─ time_series/*.nc
└─ attributes/*.csv
DuckDB側には、元データに近い形でテーブルを持たせます。NeuralHydrologyに渡すときだけ、NetCDFとCSVに書き出します。
今回作るDuckDBの主なテーブルは次の2つです。
| テーブル | 内容 |
|---|---|
basins |
流域面積、標高、勾配、森林率などの静的属性 |
daily_timeseries |
日雨量、気温、蒸発散、流量などの日単位時系列 |
フォルダ構成
後続の記事でも使い回せるように、最初から少し整理した構成にしておきます。
neuralhydrology_data_prep_project/
├─ docker-compose.yml
├─ Dockerfile
├─ pyproject.toml
├─ README.md
├─ Makefile
│
├─ configs/
│ └─ README.md
│
├─ data/
│ ├─ raw/
│ ├─ duckdb/
│ │ └─ hydro_sample.duckdb
│ └─ neuralhydrology/
│ ├─ time_series/
│ │ ├─ basin_001.nc
│ │ ├─ basin_002.nc
│ │ └─ basin_003.nc
│ ├─ attributes/
│ │ └─ attributes.csv
│ ├─ basins_all.txt
│ ├─ basins_train.txt
│ ├─ basins_validation.txt
│ └─ basins_test.txt
│
├─ notebooks/
│ ├─ 00_check_environment.ipynb
│ └─ 01_create_sample_data.ipynb
│
├─ scripts/
│ ├─ 01_create_sample_dataset.py
│ └─ 02_validate_generic_dataset.py
│
└─ src/
└─ hydro_sample/
├─ config.py
├─ synthetic.py
├─ database.py
├─ export_generic.py
├─ validate.py
├─ plotting.py
├─ pipeline.py
└─ cli.py
configsは、次の記事でNeuralHydrologyの学習設定ファイルを置く場所です。今回はまだ使いません。
Docker Compose環境
docker-compose.ymlでは、Jupyter Labを起動し、GPUも見えるようにしています。
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
Docker側でGPUを使うには、ホスト側にNVIDIA DriverとNVIDIA Container Toolkitが必要です。データ作成だけならGPUがなくても考え方は同じですが、後続の学習まで同じ環境で進めるため、ここではGPU対応の設定にしています。
起動は次です。
docker compose build
docker compose up
ブラウザで次を開きます。
http://localhost:8888/lab?token=neuralhydrology
サンプルデータを作る
データ作成は、次のコマンドで実行します。
docker compose run --rm jupyter python scripts/01_create_sample_dataset.py
内部では、次の処理を行います。
1. basin_001, basin_002, basin_003 の静的属性を作る
2. 2010-01-01 から 2023-12-31 までの日単位データを作る
3. DuckDB に basins と daily_timeseries を保存する
4. DuckDB から GenericDataset 形式へ書き出す
5. 出力されたNetCDFとCSVを検証する
このサンプルでは、降雨、気温、蒸発散、流出高を合成データとして作っています。実務用のモデルではありませんが、NeuralHydrologyのデータ形式を確認するには十分です。
DuckDBに保存する
生成したデータは、まずDuckDBに保存します。
basinsには、流域ごとの固定属性を入れます。
| カラム | 内容 |
|---|---|
basin_id |
流域ID |
area_km2 |
流域面積 |
mean_elevation_m |
平均標高 |
mean_slope |
平均勾配 |
forest_ratio |
森林率 |
urban_ratio |
市街地率 |
geology_code |
地質区分 |
river_length_km |
河道長 |
basin_shape |
流域形状指標 |
daily_timeseriesには、日単位の時系列を入れます。
| カラム | 内容 |
|---|---|
basin_id |
流域ID |
date |
日付 |
precipitation_mm_day |
日雨量 |
temperature_c |
日平均気温 |
pet_mm_day |
可能蒸発散量 |
discharge_mm_day |
流出高 |
discharge_m3s |
流量換算値 |
quality_flag |
品質フラグ |
後でMTS-LSTMに進むときは、ここにhourly_timeseriesのようなテーブルを追加すればよいです。
GenericDataset形式へ出力する
DuckDBに保存した後、NeuralHydrologyが読める形へ出力します。
data/neuralhydrology/
├─ time_series/
│ ├─ basin_001.nc
│ ├─ basin_002.nc
│ └─ basin_003.nc
└─ attributes/
└─ attributes.csv
各NetCDFファイルは、1流域分の日単位時系列を持ちます。
Pythonで中を見ると、次のように確認できます。
import xarray as xr
ds = xr.open_dataset("data/neuralhydrology/time_series/basin_001.nc")
ds
attributes.csvは、次のような形式です。
basin_id,area_km2,mean_elevation_m,mean_slope,forest_ratio,urban_ratio,geology_code,river_length_km,basin_shape
basin_001,120.5,430.2,0.18,0.72,0.05,volcanic,34.8,1.35
basin_002,85.1,250.7,0.12,0.48,0.18,sedimentary,22.4,1.1
basin_003,210.4,680.1,0.25,0.81,0.02,granitic,51.2,1.6
出力を検証する
データを作った後は、必ず検証します。
docker compose run --rm jupyter python scripts/02_validate_generic_dataset.py
検証では、主に次を確認しています。
-
time_seriesフォルダがあるか -
attributesフォルダがあるか -
basin_001.ncなどのNetCDFファイルがあるか - NetCDFに
date座標があるか -
precipitation、temperature、pet、dischargeがあるか -
attributes.csvにbasin_idがあるか - NetCDFと属性CSVの流域IDが対応しているか
- 欠測値が
-999のような値で埋められていないか
水文データでは、欠測を-999で持っていることがよくあります。しかし、NeuralHydrologyに渡す段階では、欠測はNaNにしておくのが安全です。
Jupyter Labで確認する
Jupyter Labでは、次のnotebookを用意しています。
notebooks/00_check_environment.ipynb
notebooks/01_create_sample_data.ipynb
00_check_environment.ipynbでは、Pythonのバージョン、DuckDB、xarray、PyTorch、GPUの見え方を確認します。
01_create_sample_data.ipynbでは、サンプルデータを作成し、DuckDBの中身を見て、NetCDFを開き、簡単なハイドログラフを描きます。
まずはnotebookで中身を確認しておくと、次のStep 1で設定ファイルを書くときに迷いにくくなります。
後続Stepへのつなぎ方
この記事で作ったデータは、Step 1のCudaLSTM + 日単位 + GenericDatasetでそのまま使う想定です。
Step 1では、NeuralHydrologyのconfigで次のように指定します。
dataset: generic
data_dir: data/neuralhydrology
dynamic_inputs:
- precipitation
- temperature
- pet
target_variables:
- discharge
Step 2でEA-LSTMに進むときは、attributes.csvの流域属性を使います。
Step 3でMTS-LSTMに進むときは、DuckDBに時間単位のテーブルを追加し、日単位と時間単位の両方を出力できるようにします。
Step 4でMC-LSTMに進むときは、雨量を水収支上の入力水量としてどう扱うかを考えます。
つまり、今回の構成は単なるサンプル作成ではなく、後続の拡張を見越した入口になります。
まとめ
この記事では、NeuralHydrologyのStep 1に入る前の準備として、サンプルデータを作成し、DuckDBで管理し、GenericDataset形式に出力する流れを作りました。
ポイントは次の通りです。
DuckDBを元帳にする
↓
GenericDataset形式へ出力する
↓
NeuralHydrologyのconfigから読む
最初から実データで複雑な処理を始めるより、まずは小さな合成データでフォルダ構成、変数名、NetCDF、属性CSVの流れを確認しておくと、その後の学習実験がかなり進めやすくなります。
次は、このデータを使ってCudaLSTM + 日単位 + GenericDatasetを動かします。
参考リンク
NeuralHydrology
- NeuralHydrology GitHub
- NeuralHydrology Documentation
- GenericDataset Documentation
- Configuration Arguments
Docker / GPU / Python
- Docker Compose: Run Docker Compose services with GPU access
- PyTorch Get Started
- NVIDIA Container Toolkit