Contents

20250429 meeting

Introduction

In this study, we use real-time air quality data from the Ministry of Environment to conduct research on the $SSSD^{S4}$ model. Before proceeding, since model training requires a dataset with time series characteristics and no missing values, we first use the autoFRK model. By leveraging the spatial correlations between monitoring stations, we impute the missing data. After imputation, the data is divided into training and testing sets, which are then used for $SSSD^{S4}$ model training and imputation.

autoFRK

Load the data and regroup it.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
library(dplyr)
data = read.csv("即時值查詢.csv", sep = ",", skip = 2, header = TRUE, fileEncoding = "big5")

stations <- unique(data[[1]])
dates <- seq.Date(from = min(as.Date(data[[2]])), to = max(as.Date(data[[2]])), by = "day") %>% format("%Y/%m/%d") %>% as.character()
pollutants <- unique(data[[3]])

cat(paste0("length of data:\n  stations: ", length(stations),
             ", dates: ", length(dates),
             ", total times: ", length(dates) * 24,
             ", pollutants: ", length(pollutants)
             ))

data_dict <- list()
for (pollutant in pollutants) {
  data_dict[[pollutant]] <- list()
  for (station in stations) {
    data_dict[[pollutant]][[station]] <- list()
    for (date in dates) {
      data_dict[[pollutant]][[station]][[as.character(date)]] <- rep(NA, 24)
    }
  }
}

for (i in seq_len(nrow(data))) {
  station <- data[i, 1]
  date <- data[i, 2]
  pollutant <- data[i, 3]
  values <- suppressWarnings(as.numeric(data[i, 4:27]))
  data_dict[[pollutant]][[station]][[as.character(date)]] <- values
}
Execution result reference
1
2
length of data:
  stations: 26, dates: 464, total times: 11136, pollutants: 5

Convert the data into the input format required by the $SSSD^{S4}$ model (by merging dates and times sequentially).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
pollutant_dict = list()

for (pollutant in pollutants) {
  station_list = c()
  
  for (station in stations) {
    date_list = c()
    
    for (date in dates) {
      date_list = append(date_list, data_dict[['CO']][[station]][[date]])
    }
    
    station_list = rbind(station_list, date_list)
  }
  
  rownames(station_list) = stations
  pollutant_dict[[pollutant]] = station_list
}

pollutant_dict$CO[, 1:10]

The first 10 data entries of the measurement item CO at each monitoring station.

Execution result reference
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
       [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
基隆   0.35 0.37 0.38 0.39 0.39 0.39 0.41 0.40 0.39  0.37
士林   0.35 0.37 0.37 0.39 0.40 0.40 0.39 0.39 0.39  0.38
大同   0.48 0.56 0.47 0.46 0.45 0.44 0.48 0.53 0.48  0.49
中山   0.41 0.43 0.42 0.42 0.42 0.42 0.46 0.50 0.46  0.45
古亭   0.37 0.35 0.35 0.37 0.39 0.39 0.38 0.39 0.41  0.39
松山   0.36 0.37 0.38 0.37 0.38 0.38 0.39 0.41 0.40  0.38
陽明   0.36 0.38 0.39 0.41 0.42 0.42 0.41 0.41 0.40  0.36
萬華   0.40 0.43 0.43 0.42 0.43 0.43 0.44 0.45 0.45  0.43
三重   0.63 0.72 0.62 0.60 0.54 0.61 0.78 0.74 0.80  0.81
土城   0.36 0.37 0.37 0.37 0.39 0.40 0.41 0.43 0.43  0.43
永和   0.43 0.40 0.40 0.40 0.42 0.45 0.46 0.50 0.50  0.47
汐止   0.32 0.35 0.35 0.36 0.37 0.38 0.40 0.41 0.38  0.37
板橋   0.40 0.41 0.40 0.41 0.42 0.43 0.45 0.46 0.47  0.45
林口   0.36 0.37 0.38 0.38 0.39 0.40 0.40 0.41 0.40  0.39
淡水   0.33 0.35 0.37 0.38 0.39 0.40 0.40 0.41 0.40  0.35
菜寮   0.40 0.39 0.40 0.41 0.42 0.42 0.43 0.45 0.45  0.43
新店   0.35 0.35 0.35 0.36 0.37 0.39 0.40 0.42 0.42  0.42
新莊   0.34 0.36 0.37 0.37 0.39 0.39 0.40 0.41 0.41  0.39
萬里   0.36 0.38 0.39 0.40 0.40 0.41 0.40 0.39 0.37  0.35
富貴角 0.31 0.31 0.33 0.34 0.35 0.33 0.33 0.32 0.29  0.27
大園   0.35 0.35 0.37 0.38 0.40 0.41 0.40 0.41 0.42  0.40
中壢   0.44 0.47 0.44 0.46 0.43 0.49 0.52 0.58 0.59  0.51
平鎮   0.36 0.39 0.40 0.41 0.42 0.44 0.48 0.48 0.46  0.45
桃園   0.35 0.37 0.40 0.39 0.40 0.42 0.45 0.46 0.44  0.43
龍潭   0.36 0.33 0.33 0.34 0.35 0.38 0.40 0.40 0.40  0.40
觀音   0.30 0.33 0.35 0.35 0.36 0.36 0.37 0.37 0.35  0.31

Obtain the coordinates of each monitoring station.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
location = read.table('station_locations.csv', header = TRUE, sep = ',')
print(location)

station_location = data.frame(station = stations, lon = NA, lat = NA)
for (i in 1:length(stations)) {
  for (j in 1: length(location[, 1])) {
    if (stations[i] == location[j, 1]){
      station_location[i, 2:3] = location[j, 2:3]
      break
    }
  }
}

station_location
Execution result reference
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13

station     lon         lat
<chr>      <dbl>       <dbl>
基隆	121.7601	25.12917
士林	121.5167	25.10334
大同	121.5134	25.06331
中山	121.5265	25.06236
古亭	121.5296	25.02061
松山	121.5786	25.05000
陽明	121.5296	25.18272
萬華	121.5080	25.04650
三重	121.4938	25.07261
土城	121.4519	24.98253

Use autoFRK for imputation. During model training, later time points use more data for training and prediction, and only missing values are filled.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
library(autoFRK)
pollutant_dict.filled = list()
m = length(pollutants)
n = dim(pollutant_dict[[names(pollutant_dict)[1]]])[2]
filled_data = c()

start_time <- Sys.time()
# progress bar
pb = txtProgressBar(min = 0, max = m * n, style = 3)

for (i in 1:m) {
  station_data = c()
  for (j in 1:n) {
    row_data = as.matrix(pollutant_dict[[names(pollutant_dict)[i]]][ ,j])
    na_factor_data = is.na(row_data)
    
    if (sum(na_factor_data) == 0) {
      station_data = as.matrix(cbind(station_data, row_data))
      
      model = autoFRK(data = station_data,
                      loc = as.matrix(station_location[, c("lon", "lat")])
                      )
    } else {
      filled_data = predict.FRK(object = model,
                                obsData = row_data
                                )
      row_data[na_factor_data] = filled_data$pred.value[na_factor_data] 
      station_data = cbind(station_data, row_data)
    }
    
    setTxtProgressBar(pb, ((i - 1) * n + j))  # progress bar
    
    elapsed_time <- as.numeric(difftime(Sys.time(), start_time, units = "secs"))
    row_data[na_factor_data] <- filled_data$pred.value[na_factor_data]
    avg_time_per_iteration <- elapsed_time / ((i - 1) * n + j)
    expected_remaining_time <- avg_time_per_iteration * (m * n - ((i - 1) * n + j))
    cat(sprintf("\r%d / %d items, %.2f / %.2f seconds", ((i - 1) * n + j), m * n, expected_remaining_time, elapsed_time))
    flush.console()
  }
  
  pollutant_dict.filled[[names(pollutant_dict)[i]]] = station_data
}
  
close(pb)  # progress bar
Execution result reference
1
55680 / 55680 items, 0.00 / 4472.01 secondss===========================================================================| 100%

Save to .npy format file.

1
2
3
4
5
6
7
8
9
library(reticulate)
np <- import("numpy")
npy_array <- array(NA, dim = c(length(pollutants), nrow(pollutant_dict.filled[[1]]), ncol(pollutant_dict.filled[[1]])))

for (i in seq_along(pollutants)) {
  npy_array[i, , ] <- as.matrix(pollutant_dict.filled[[pollutants[i]]])
}

np$save("pollutants.npy", npy_array)

At this point, the files have been organized and imputed.

Split the Test Set

Before feeding the data into the $SSSD^{S4}$ model for training, we can first examine the dataset.

First, load the modules and set up some plotting functions.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# import modules
import numpy as np
import matplotlib.pyplot as plt

# functions
def test_plot(full_data, missing_data, title, save_path):
    """
    Plot the first 3 dimensions of the first sample, comparing full data and missing data.
    """
    fig, axes = plt.subplots(3, 1, figsize=(12, 8), sharex=True)
    for j in range(3):
        axes[j].plot(full_data[0, j], color='gray', label='full data', alpha=0.6)
        axes[j].plot(missing_data[0, j], color='red', label=title)
        axes[j].set_ylabel(f'Dim {j}')
        axes[j].legend()

    plt.suptitle(title)
    plt.xlabel('Time')
    plt.tight_layout()
    plt.savefig(f"{save_path}.png", dpi=300)
    plt.show()

Load and split the dataset into training and testing sets. Here, the last 500 data entries are selected as the test set, and the data from 2,500 to 500 entries are used as the training set.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
## load data
filename = r'real_time\pollutants.npy'
data = np.load(filename)

train_data = data[:, :, -2500:-500]
test_data = data[:, :, -500:]

train_data = train_data.transpose(0, 2, 1)
test_data = test_data.transpose(0, 2, 1)

print(train_data.shape)
print(test_data.shape)

np.save(r'real_time\pollutants_train.npy', train_data)
np.save(r'real_time\pollutants_test.npy', test_data)

Now, adjust the test set using the missing data methods rm, rbm, bm, and tf, and plot the first three monitoring stations. For each missing method, select 200 data entries as missing from the $500 \times 0.4 = 200$ data points.

In each plot, the gray color represents the test set data with no missing values, and the red color represents the data from various monitoring stations to be imputed.

rm

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
## rm
missing_rate = 0.4
test_data_rm = data[:, :, -500:].copy()
mask = np.random.rand(*test_data_rm.shape) > missing_rate
test_data_rm[~mask] = np.nan

print(test_data_rm[0, 0])
test_plot(test_data.transpose(0, 2, 1), test_data_rm, 'test_data_rm', r'real_time\pollutants_test_rm')

test_data_rm = test_data_rm.transpose(0, 2, 1)
print(test_data_rm.shape)
np.save(r'real_time\pollutants_test_rm.npy', test_data_rm)

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/1140429%20meeting/pollutants_test_rm.png
rm 。

rbm

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
## rbm
missing_rate = 0.4
missing = int(500 * missing_rate)
test_data_rbm = data[:, :, -500:].copy()

for i in range(test_data_rbm.shape[0]):
    for j in range(test_data_rbm.shape[1]):
        start = np.random.randint(0, 500 - missing + 1)
        end = start + missing
        test_data_rbm[i, j, start:end] = np.nan
        
print(test_data_rbm[0, 0])
print(test_data_rbm[0, 1])
test_plot(test_data.transpose(0, 2, 1), test_data_rbm, 'test_data_rbm', r'real_time\pollutants_test_rbm')

test_data_rbm = test_data_rbm.transpose(0, 2, 1)
print(test_data_rbm.shape)
np.save(r'real_time\pollutants_test_rbm.npy', test_data_rbm)

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/1140429%20meeting/pollutants_test_rbm.png
rbm 。

bm

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
## bm
missing_rate = 0.4
missing = int(500 * missing_rate)
test_data_bm = data[:, :, -500:].copy()

start = np.random.randint(0, 500 - missing + 1)
end = start + missing
test_data_bm[:, :, start:end] = np.nan

print(test_data_bm[0, 0])
print(test_data_bm[0, 1])
test_plot(test_data.transpose(0, 2, 1), test_data_bm, 'test_data_bm', r'real_time\pollutants_test_bm')

test_data_bm = test_data_bm.transpose(0, 2, 1)
print(test_data_bm.shape)
np.save(r'real_time\pollutants_test_bm.npy', test_data_bm)

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/1140429%20meeting/pollutants_test_bm.png
bm 。

tf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
## tf
missing_rate = 0.4
missing = int(500 * missing_rate)
test_data_tf = data[:, :, -500:].copy()

test_data_tf[:, :, -missing:] = np.nan

print(test_data_tf[0, 0])
print(test_data_tf[0, 1])
test_plot(test_data.transpose(0, 2, 1), test_data_tf, 'test_data_tf', r'real_time\pollutants_test_tf')

test_data_tf = test_data_tf.transpose(0, 2, 1)
print(test_data_tf.shape)
np.save(r'real_time\pollutants_test_tf.npy', test_data_tf)

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/1140429%20meeting/pollutants_test_tf.png
tf 。

$SSSD^{S4}$

There are 5 measurement items, 26 monitoring stations, and $464 \times 24 = 11136$ time points in total.

Through previous processing, we obtained 2000 data points for training and 500 data points for testing, with different missing data conditions. The shape of the input dataset is (measurement items, time, stations), and after imputation, the output shape is (measurement items, stations, time).

The configuration file for training.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Training configuration
batch_size: 50  # Batch size
output_directory: "./results/checkpoint"  # Output directory for checkpoints and logs
ckpt_iter: "max"  # Checkpoint mode (max or min)
iters_per_ckpt: 100  # Checkpoint frequency (number of epochs)
iters_per_logging: 100  # Log frequency (number of iterations)
n_iters: 10000  # Maximum number of iterations
learning_rate: 0.002  # Learning rate

# Additional training settings
only_generate_missing: true  # Generate missing values only
use_model: 2  # Model to use for training
masking: "forecast"  # Masking strategy for missing values
missing_k: 5  # Number of missing values

# Data paths
data:
  train_path: "./datasets/real_time_by_autoFRK/pollutants_train.npy"  # Path to training data

The configuration file for the model.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
wavenet:
  # WaveNet model parameters
  input_channels: 26  # Number of input channels
  output_channels: 26  # Number of output channels
  residual_layers: 32  # Number of residual layers
  residual_channels: 128  # Number of channels in residual blocks
  skip_channels: 128  # Number of channels in skip connections

  # Diffusion step embedding dimensions
  diffusion_step_embed_dim_input: 64  # Input dimension
  diffusion_step_embed_dim_hidden: 512  # Middle dimension
  diffusion_step_embed_dim_output: 512  # Output dimension

  # Structured State Spaces sequence model (S4) configurations
  s4_max_sequence_length: 2000  # Maximum sequence length
  s4_state_dim: 64  # State dimension
  s4_dropout: 0.0  # Dropout rate
  s4_bidirectional: true  # Whether to use bidirectional layers
  s4_use_layer_norm: true  # Whether to use layer normalization

diffusion:
  # Diffusion model parameters
  T: 200  # Number of diffusion steps
  beta_0: 0.0001  # Initial beta value
  beta_T: 0.02  # Final beta value

The configuration file for imputation.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Inference configuration
batch_size: 50  # Batch size for inference
output_directory: "./results/checkpoint/rm"  # Output directory for inference results
ckpt_path: "./results/checkpoint"  # Path to checkpoint for inference
trials: 1 # Replications

# Additional training settings
only_generate_missing: true  # Generate missing values only
use_model: 2  # Model to use for training
masking: "forecast"  # Masking strategy for missing values
missing_k: 200  # Number of missing values

# Data paths
data:
  test_path: "./datasets/real_time_by_autoFRK/pollutants_test_rm.npy"  # Path to test data
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def get_mask_forecast(sample: torch.Tensor, k: int) -> torch.Tensor:
    """
    Get mask of same segments (black-out missing) across channels based on k.

    Args:
        sample (torch.Tensor): Tensor of shape [# of samples, # of channels].
        k (int): Number of missing values.

    Returns:
        torch.Tensor: Mask of sample's shape where 0's indicate missing values to be imputed, and 1's indicate preserved values.
    """
    #mask = torch.ones_like(sample)  # Initialize mask with all ones

    # Calculate the indices of missing values
    #s_nan = torch.arange(mask.shape[0] - k, mask.shape[0])

    # Apply mask for each channel
    #for channel in range(mask.shape[1]):
    #    mask[s_nan, channel] = 0

    mask = (~torch.isnan(sample)).float()  # replace only missing values

    return mask

The training time is approximately 2 hours, and the imputation time is about 2 minutes.

The command for training is as follows:

1
./scripts/diffusion/training_job.sh -m configs/model.yaml -t configs/training.yaml

It is important to note that the original code only accepts datasets with complete, non-missing values, which means that imputation of missing values will fail.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
(sssd) u6025091@iq0l0ttest1140426-9bhg4:~/SSSD_CP$ ./scripts/diffusion/inference_job.sh -m configs/model.yaml -i configs/inference.yaml
Intializing conda
Activating Conda Env: sssd
[Execution - Inference]
/home/u6025091/SSSD_CP/scripts/diffusion/infer.py --model_config configs/model.yaml --inference_config configs/inference.yaml
2025-04-26 14:28:16,698 - sssd.utils.logger - INFO - Using 1 GPUs!
2025-04-26 14:28:16,939 - sssd.utils.logger - INFO - Current time: 2025-04-26 14:28:16
2025-04-26 14:28:28,592 - sssd.utils.logger - INFO - The 1th inference trial
2025-04-26 14:28:28,592 - sssd.utils.logger - INFO - Output directory: ./results/checkpoint/bm/T200_beta00.0001_betaT0.02/max
2025-04-26 14:28:29,827 - sssd.utils.logger - INFO - Successfully loaded model at iteration 10000
Traceback (most recent call last):
  File "/home/u6025091/SSSD_CP/scripts/diffusion/infer.py", line 117, in <module>
    run_job(model_config, inference_config, device, args.ckpt_iter)
  File "/home/u6025091/SSSD_CP/scripts/diffusion/infer.py", line 96, in run_job
    ).generate()
  File "/home/u6025091/.local/lib/python3.10/site-packages/sssd/inference/generator.py", line 151, in generate
    mse = mean_squared_error(
  File "/home/u6025091/local/envs/sssd/lib/python3.10/site-packages/sklearn/metrics/_regression.py", line 438, in mean_squared_error
    y_type, y_true, y_pred, multioutput = _check_reg_targets(
  File "/home/u6025091/local/envs/sssd/lib/python3.10/site-packages/sklearn/metrics/_regression.py", line 96, in _check_reg_targets
    y_pred = check_array(y_pred, ensure_2d=False, dtype=dtype)
  File "/home/u6025091/local/envs/sssd/lib/python3.10/site-packages/sklearn/utils/validation.py", line 800, in check_array
    _assert_all_finite(array, allow_nan=force_all_finite == "allow-nan")
  File "/home/u6025091/local/envs/sssd/lib/python3.10/site-packages/sklearn/utils/validation.py", line 114, in _assert_all_finite
    raise ValueError(
ValueError: Input contains NaN, infinity or a value too large for dtype('float32').

The following code was modified to allow the model to select the mask based on the actual missing values. This segment of code is located in /SSSD_CP-main/sssd/core/utils.py.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def get_mask_forecast(sample: torch.Tensor, k: int) -> torch.Tensor:
    """
    Get mask of same segments (black-out missing) across channels based on k.

    Args:
        sample (torch.Tensor): Tensor of shape [# of samples, # of channels].
        k (int): Number of missing values.

    Returns:
        torch.Tensor: Mask of sample's shape where 0's indicate missing values to be imputed, and 1's indicate preserved values.
    """
    #mask = torch.ones_like(sample)  # Initialize mask with all ones

    # Calculate the indices of missing values
    #s_nan = torch.arange(mask.shape[0] - k, mask.shape[0])

    # Apply mask for each channel
    #for channel in range(mask.shape[1]):
    #    mask[s_nan, channel] = 0

    mask = (~torch.isnan(sample)).float()

    return mask

The following modification is also necessary to fill missing values with 0, allowing the code to perform imputation (since NA cannot be used in numerical operations and will cause errors). This segment of code is located in /SSSD_CP-main/sssd/inference/generator.py.

The new line of code added is:

1
batch = torch.nan_to_num(batch, nan=0.0)  # Replace NaN with 0.0

It is important to note that this change will make the original mse and mspe calculations invalid. These metrics should be recalculated after imputation is completed. The modification here only affects mse and mspe, and it has been confirmed that it does not affect other calculations. Furthermore, mse and mspe are only used for the output after inference.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
def generate(self) -> list:
    """Generate samples using the given neural network model."""
    all_mses = []
    all_mapes = []
    for index, (batch,) in enumerate(self.dataloader):
        batch = batch.to(self.device)
        mask = self._update_mask(batch)

        if torch.isnan(mask).any():  # debug
            print(f"[Batch {index}] NaN in mask!")

        batch = torch.nan_to_num(batch, nan=0.0)  # Replace NaN with 0.0

        if torch.isnan(batch).any():  # debug
            print(f"[NaN DETECTED] Batch {index} has NaNs!")

        batch = batch.permute(0, 2, 1)

        generated_series = (
            sampling(
                net=self.net,
                size=batch.shape,
                diffusion_hyperparams=self.diffusion_hyperparams,
                cond=batch,
                mask=mask,
                only_generate_missing=self.only_generate_missing,
                device=self.device,
            )
            .detach()
            .cpu()
            .numpy()
        )

        if np.isnan(generated_series).any():  # debug
            print("[NaN DETECTED] in generated_series")
            print("Locations:", np.argwhere(np.isnan(generated_series)))


        batch = batch.detach().cpu().numpy()
        mask = mask.detach().cpu().numpy()
        mse = mean_squared_error(
            batch[~mask.astype(bool)], generated_series[~mask.astype(bool)]
        )
        mape = mean_absolute_percentage_error(
            batch[~mask.astype(bool)], generated_series[~mask.astype(bool)]
        )
        all_mses.append(mse)
        all_mapes.append(mape)
        results = {
            "imputation": generated_series,
            "original": batch,
            "mask": mask,
        }
        self._save_data(results, index)

    return all_mses, all_mapes
Note
Note: The above modifications are only used during model inference. When training the model, the original code should still be used.

After the adjustments, you can use the following command for imputation.

1
./scripts/diffusion/inference_job.sh -m configs/model.yaml -i configs/inference.yaml

The results, in order, are rm, rbm, bm, and tf.

Execution result reference
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
(sssd) u6025091@iq0l0ttest1140426-9bhg4:~/SSSD_CP$ ./scripts/diffusion/inference_job.sh -m configs/model.yaml -i configs/inference.yaml
Intializing conda
Activating Conda Env: sssd
[Execution - Inference]
/home/u6025091/SSSD_CP/scripts/diffusion/infer.py --model_config configs/model.yaml --inference_config configs/inference.yaml
2025-04-26 15:05:30,600 - sssd.utils.logger - INFO - Using 1 GPUs!
2025-04-26 15:05:30,749 - sssd.utils.logger - INFO - Current time: 2025-04-26 15:05:30
2025-04-26 15:05:41,170 - sssd.utils.logger - INFO - The 1th inference trial
2025-04-26 15:05:41,171 - sssd.utils.logger - INFO - Output directory: ./results/checkpoint/rm/T200_beta00.0001_betaT0.02/max
2025-04-26 15:05:41,544 - sssd.utils.logger - INFO - Successfully loaded model at iteration 10000
2025-04-26 15:06:10,954 - sssd.utils.logger - INFO - Average MSE: 13.989262580871582
2025-04-26 15:06:10,955 - sssd.utils.logger - INFO - Average MAPE: 6969775217442816.0
2025-04-26 15:06:10,955 - sssd.utils.logger - INFO - Current time: 2025-04-26 15:06:10
Inference Job completed
(sssd) u6025091@iq0l0ttest1140426-9bhg4:~/SSSD_CP$ ./scripts/diffusion/inference_job.sh -m configs/model.yaml -i configs/inference.yaml
Intializing conda
Activating Conda Env: sssd
[Execution - Inference]
/home/u6025091/SSSD_CP/scripts/diffusion/infer.py --model_config configs/model.yaml --inference_config configs/inference.yaml
2025-04-26 15:06:22,825 - sssd.utils.logger - INFO - Using 1 GPUs!
2025-04-26 15:06:23,006 - sssd.utils.logger - INFO - Current time: 2025-04-26 15:06:23
2025-04-26 15:06:33,472 - sssd.utils.logger - INFO - The 1th inference trial
2025-04-26 15:06:33,474 - sssd.utils.logger - INFO - Output directory: ./results/checkpoint/rbm/T200_beta00.0001_betaT0.02/max
2025-04-26 15:06:34,008 - sssd.utils.logger - INFO - Successfully loaded model at iteration 10000
2025-04-26 15:07:03,308 - sssd.utils.logger - INFO - Average MSE: 13.990039825439453
2025-04-26 15:07:03,308 - sssd.utils.logger - INFO - Average MAPE: 8490500730388480.0
2025-04-26 15:07:03,308 - sssd.utils.logger - INFO - Current time: 2025-04-26 15:07:03
Inference Job completed
(sssd) u6025091@iq0l0ttest1140426-9bhg4:~/SSSD_CP$ ./scripts/diffusion/inference_job.sh -m configs/model.yaml -i configs/inference.yaml
Intializing conda
Activating Conda Env: sssd
[Execution - Inference]
/home/u6025091/SSSD_CP/scripts/diffusion/infer.py --model_config configs/model.yaml --inference_config configs/inference.yaml
2025-04-26 15:07:19,904 - sssd.utils.logger - INFO - Using 1 GPUs!
2025-04-26 15:07:20,081 - sssd.utils.logger - INFO - Current time: 2025-04-26 15:07:20
2025-04-26 15:07:30,561 - sssd.utils.logger - INFO - The 1th inference trial
2025-04-26 15:07:30,562 - sssd.utils.logger - INFO - Output directory: ./results/checkpoint/bm/T200_beta00.0001_betaT0.02/max
2025-04-26 15:07:30,889 - sssd.utils.logger - INFO - Successfully loaded model at iteration 10000
2025-04-26 15:08:00,273 - sssd.utils.logger - INFO - Average MSE: 14.108148574829102
2025-04-26 15:08:00,273 - sssd.utils.logger - INFO - Average MAPE: 1.3516574539382784e+16
2025-04-26 15:08:00,273 - sssd.utils.logger - INFO - Current time: 2025-04-26 15:08:00
Inference Job completed
(sssd) u6025091@iq0l0ttest1140426-9bhg4:~/SSSD_CP$ ./scripts/diffusion/inference_job.sh -m configs/model.yaml -i configs/inference.yaml
Intializing conda
Activating Conda Env: sssd
[Execution - Inference]
/home/u6025091/SSSD_CP/scripts/diffusion/infer.py --model_config configs/model.yaml --inference_config configs/inference.yaml
2025-04-26 15:08:14,197 - sssd.utils.logger - INFO - Using 1 GPUs!
2025-04-26 15:08:14,366 - sssd.utils.logger - INFO - Current time: 2025-04-26 15:08:14
2025-04-26 15:08:24,075 - sssd.utils.logger - INFO - The 1th inference trial
2025-04-26 15:08:24,076 - sssd.utils.logger - INFO - Output directory: ./results/checkpoint/tf/T200_beta00.0001_betaT0.02/max
2025-04-26 15:08:24,428 - sssd.utils.logger - INFO - Successfully loaded model at iteration 10000
2025-04-26 15:08:53,786 - sssd.utils.logger - INFO - Average MSE: 14.107381820678711
2025-04-26 15:08:53,786 - sssd.utils.logger - INFO - Average MAPE: 1.3518593174011904e+16
2025-04-26 15:08:53,786 - sssd.utils.logger - INFO - Current time: 2025-04-26 15:08:53
Inference Job completed

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/1140429%20meeting/1745656856441.png
The screen of a successful execution.

Imputation Result Analysis

The following code is used for analysis and plotting. In the images below, the gray color represents the test set data with no missing values, the red color represents the data sent for imputation, and the orange color represents the imputed results.

Load the modules and define the functions.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# test imputation
import os
import numpy as np
import matplotlib.pyplot as plt
from tqdm import tqdm

test_data = np.load(r'real_time\pollutants_test.npy').transpose(0, 2, 1)

## functions
def imputation_plot(full_data, missing_data, imputation_data, title, save_path):
    """
    Plot the first 3 dimensions of the first sample, comparing full data and missing data.
    """
    show_dims = 2
    fig, axes = plt.subplots(show_dims, 1, figsize=(12, 8), sharex=True)
    #imputation_data = np.where(np.isnan(missing_data), imputation_data, np.nan)
    for j in range(show_dims):
        axes[j].plot(full_data[0, j], color='gray', label='full data', alpha=0.6)
        axes[j].plot(imputation_data[0, j], color='orange', label='imputation data', alpha=0.6)
        axes[j].plot(missing_data[0, j], color='red', label=title)
        axes[j].set_ylabel(f'Dim {j}')
        axes[j].legend()

    plt.suptitle(title)
    plt.xlabel('Time')
    plt.tight_layout()
    plt.savefig(f"{save_path}.png", dpi=300)
    #plt.show()

def imputation_plot_each_dim(full_data, missing_data, imputation_data, title, save_dir):
    """
    Plot each dimension of the first sample separately, comparing full data, missing data, and imputation data.
    Save each plot as a separate file.
    """
    num_dims = full_data.shape[1]
    os.makedirs(save_dir, exist_ok=True)

    for j in tqdm(range(num_dims)):
        fig, ax = plt.subplots(figsize=(12, 4))
        ax.plot(full_data[0, j], color='gray', label='full data', alpha=0.6)
        ax.plot(imputation_data[0, j], color='orange', label='imputation data', alpha=0.6)
        ax.plot(missing_data[0, j], color='red', label=title)
        ax.set_ylabel(f'Dim {j}')
        ax.set_xlabel('Time')
        ax.set_title(f'{title} - Dimension {j}')

        ax.legend(
            loc='center left',
            bbox_to_anchor=(1, 0.5) 
        )

        fig.tight_layout(rect=[0, 0, 0.85, 1])
        save_path = os.path.join(save_dir, f"{title}_dim{j}.png")
        plt.savefig(save_path, dpi=300)
        plt.close(fig)

rm

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
## rm
filename = r'real_time\imputation\imputation0_rm.npy'
rm_data = np.load(filename)
print(f'Shape: {rm_data.shape}')
print(f'NAs: {np.isnan(rm_data).sum()}')
# print(rm_data[0, 0])
print(f'MSPE for all: {((test_data - rm_data)**2).mean()}')

test_data_rm = np.load(r'real_time\pollutants_test_rm.npy').transpose(0, 2, 1)
print(f'MSPE only for missing: {((test_data[np.isnan(test_data_rm)] - rm_data[np.isnan(test_data_rm)])**2).mean()}')

imputation_plot(test_data, test_data_rm, rm_data, 'imputation rm test data', r'real_time\imputation\imputation0_rm')
imputation_plot_each_dim(test_data, test_data_rm, rm_data, 'imputation rm test data', r'real_time\imputation\imputation0_rm\each_dim')
# test_data[0, 0][1:10]
# test_data_rm[0, 0][1:10]
# rm_data[0, 0][1:10]
Execution result reference
1
2
3
4
5
Shape: (5, 26, 500)
NAs: 0
MSPE for all: 5.630354000694568
MSPE only for missing: 7.2555356208301784
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 26/26 [00:13<00:00,  1.95it/s]

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/1140429%20meeting/result/imputation0_rm.png
imputation rm 。

The imputation results for all monitoring stations are as follows.

gallery_made_with_nanogallery2-1-rm

rbm

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
## rbm
filename = r'real_time\imputation\imputation0_rbm.npy'
rbm_data = np.load(filename)
print(f'Shape: {rbm_data.shape}')
print(f'NAs: {np.isnan(rbm_data).sum()}')
# print(rbm_data[0, 0])
print(f'MSPE for all: {((test_data - rbm_data)**2).mean()}')

test_data_rbm = np.load(r'real_time\pollutants_test_rbm.npy').transpose(0, 2, 1)
print(f'MSPE only for missing: {((test_data[np.isnan(test_data_rbm)] - rbm_data[np.isnan(test_data_rbm)])**2).mean()}')

imputation_plot(test_data, test_data_rbm, rbm_data, 'imputation rbm test data', r'real_time\imputation\imputation0_rbm')
imputation_plot_each_dim(test_data, test_data_rbm, rbm_data, 'imputation rbm test data', r'real_time\imputation\imputation0_rbm\each_dim')
# test_data[0, 0][1:10]
# test_data_rbm[0, 0][1:10]
# rbm_data[0, 0][1:10]
Execution result reference
1
2
3
4
5
Shape: (5, 26, 500)
NAs: 0
MSPE for all: 5.67021524531403
MSPE only for missing: 8.879126539941137
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 26/26 [00:13<00:00,  1.97it/s] 

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/1140429%20meeting/result/imputation0_rbm.png
imputation rbm 。

The imputation results for all monitoring stations are as follows.

gallery_made_with_nanogallery2-2-rbm

bm

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
## bm
filename = r'real_time\imputation\imputation0_bm.npy'
bm_data = np.load(filename)
print(f'Shape: {bm_data.shape}')
print(f'NAs: {np.isnan(bm_data).sum()}')
# print(bm_data[0, 0])
print(f'MSPE for all: {((test_data - bm_data)**2).mean()}')

test_data_bm = np.load(r'real_time\pollutants_test_bm.npy').transpose(0, 2, 1)
print(f'MSPE only for missing: {((test_data[np.isnan(test_data_bm)] - bm_data[np.isnan(test_data_bm)])**2).mean()}')

imputation_plot(test_data, test_data_bm, bm_data, 'imputation bm test data', r'real_time\imputation\imputation0_bm')
imputation_plot_each_dim(test_data, test_data_bm, bm_data, 'imputation bm test data', r'real_time\imputation\imputation0_bm\each_dim')
# test_data[0, 0][1:10]
# test_data_bm[0, 0][1:10]
# bm_data[0, 0][1:10]
Execution result reference
1
2
3
4
5
Shape: (5, 26, 500)
NAs: 0
MSPE for all: 5.733927828240412
MSPE only for missing: 14.33481884698311
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 26/26 [00:13<00:00,  1.95it/s]

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/1140429%20meeting/result/imputation0_bm.png
imputation bm 。

The imputation results for all monitoring stations are as follows.

gallery_made_with_nanogallery2-3-bm

tf

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
## tf
filename = r'real_time\imputation\imputation0_tf.npy'
tf_data = np.load(filename)
print(f'Shape: {tf_data.shape}')
print(f'NAs: {np.isnan(tf_data).sum()}')
# print(tf_data[0, 0])
print(f'MSPE for all: {((test_data - tf_data)**2).mean()}')

test_data_tf = np.load(r'real_time\pollutants_test_tf.npy').transpose(0, 2, 1)
print(f'MSPE only for missing: {((test_data[np.isnan(test_data_tf)] - tf_data[np.isnan(test_data_tf)])**2).mean()}')

imputation_plot(test_data, test_data_tf, tf_data, 'imputation tf test data', r'real_time\imputation\imputation0_tf')
imputation_plot_each_dim(test_data, test_data_tf, tf_data, 'imputation tf test data', r'real_time\imputation\imputation0_tf\each_dim')
# test_data[0, 0][1:10]
# test_data_tf[0, 0][1:10]
# tf_data[0, 0][1:10]
Execution result reference
1
2
3
4
5
Shape: (5, 26, 500)
NAs: 0
MSPE for all: 5.728256563966483
MSPE only for missing: 14.320640763169513
100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 26/26 [00:13<00:00,  1.94it/s]

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/1140429%20meeting/result/imputation0_tf.png
imputation tf 。

The imputation results for all monitoring stations are as follows.

gallery_made_with_nanogallery2-4-tf

Conclusion

Although the imputation was successfully performed, it seems to have “succeeded” in failing?! While each imputed result appears to resemble white noise, from rbm, we can see that some missing values were indeed properly imputed, and the predictions are close to the actual values (covering the gray true values). However, both tf and bm show that the predictions for non-missing values are also accurate, but for rm and rbm, the non-missing values were poorly predicted, which is quite unreasonable (since only_generate_missing: true was set).

The reason for this behavior has not yet been identified and requires further investigation.

https://raw.githubusercontent.com/Josh-test-lab/website-assets-repository/refs/heads/main/posts/1140429%20meeting/To%20be%20continued.jpg
To be continued.