SRA: MID/LED/MoFlow code + RUNNING.md instructions (code only, no data/ckpts)
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- LED/LED/cfg/nba/led_augment.yml +38 -0
- LED/LED/cfg/nba/led_augment_debug.yml +38 -0
- LED/LED/main_led_nba.py +31 -0
- LED/LED/models/layers.py +169 -0
- LED/LED/models/model_diffusion.py +124 -0
- LED/LED/models/model_led_initializer.py +63 -0
- LED/LED/requirements.txt +84 -0
- LED/LED/trainer/train_led_trajectory_augment_input.py +443 -0
- LED/LED/utils/config.py +60 -0
- LED/LED/utils/utils.py +336 -0
- LED/cfg/nba/led_augment.yml +39 -0
- LED/cfg/nba/led_augment_debug.yml +38 -0
- LED/cfg/sdd/sdd.yml +34 -0
- LED/cfg/sport/football.yml +53 -0
- LED/cfg/sport/soccer.yml +56 -0
- LED/eval_sdd_led_allagents.py +193 -0
- LED/eval_sdd_led_mid_protocol.py +169 -0
- LED/main_led_nba.py +31 -0
- LED/main_led_nba_graph.py +53 -0
- LED/main_led_nba_grpo.py +360 -0
- LED/main_sdd_led.py +21 -0
- LED/main_sdd_pretrain.py +17 -0
- LED/main_sport_led.py +33 -0
- LED/main_sport_pretrain.py +25 -0
- LED/models/future_interaction_graph.py +155 -0
- LED/models/future_interaction_graph_v6.py +173 -0
- LED/models/interaction_baselines.py +246 -0
- LED/models/layers.py +169 -0
- LED/models/model_diffusion.py +125 -0
- LED/models/model_led_initializer.py +63 -0
- LED/requirements.txt +84 -0
- LED/trainer/train_led_graph.py +404 -0
- LED/trainer/train_led_trajectory_augment_input.py +443 -0
- LED/trainer/train_sdd_led.py +239 -0
- LED/trainer/train_sdd_pretrain.py +137 -0
- LED/trainer/train_sport_led.py +352 -0
- LED/trainer/train_sport_pretrain.py +210 -0
- LED/utils/config.py +60 -0
- LED/utils/utils.py +336 -0
- LED/viz_denoising_process.py +329 -0
- LED/viz_denoising_steps.py +299 -0
- LED/viz_uncertainty_denoising.py +371 -0
- LED/viz_uncertainty_individual.py +197 -0
- MID/configs/baseline.yaml +46 -0
- MID/configs/baseline_sdd.yaml +35 -0
- MID/configs/baseline_sdd_eval.yaml +35 -0
- MID/dataset/__init__.py +2 -0
- MID/dataset/dataset.py +76 -0
- MID/dataset/homography_warper.py +471 -0
- MID/dataset/preprocessing.py +233 -0
LED/LED/cfg/nba/led_augment.yml
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ------------------- General Options -------------------------
|
| 2 |
+
description : LED
|
| 3 |
+
results_root_dir : results
|
| 4 |
+
dataset : nba
|
| 5 |
+
|
| 6 |
+
# ------------------- Dataset -------------------------
|
| 7 |
+
past_frames : 10
|
| 8 |
+
future_frames : 20
|
| 9 |
+
min_past_frames : 10
|
| 10 |
+
min_future_frames : 20
|
| 11 |
+
|
| 12 |
+
motion_dim : 2
|
| 13 |
+
forecast_dim : 2
|
| 14 |
+
|
| 15 |
+
traj_mean : [14, 7.5]
|
| 16 |
+
traj_scale : 5
|
| 17 |
+
|
| 18 |
+
# ------------------- Model -------------------------
|
| 19 |
+
pretrained_core_denoising_model: './results/checkpoints/base_diffusion_model.p'
|
| 20 |
+
debug : False # set to True for early stop in each epoch.
|
| 21 |
+
|
| 22 |
+
diffusion : {
|
| 23 |
+
steps : 100,
|
| 24 |
+
beta_start : 1.e-4,
|
| 25 |
+
beta_end : 5.e-2,
|
| 26 |
+
beta_schedule : 'linear'
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
# ------------------- Training Parameters -------------------------
|
| 30 |
+
lr : 1.e-3
|
| 31 |
+
train_batch_size : 10
|
| 32 |
+
test_batch_size : 500
|
| 33 |
+
num_epochs : 100
|
| 34 |
+
|
| 35 |
+
lr_scheduler : 'step'
|
| 36 |
+
decay_step : 8
|
| 37 |
+
decay_gamma : 0.5
|
| 38 |
+
|
LED/LED/cfg/nba/led_augment_debug.yml
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ------------------- General Options -------------------------
|
| 2 |
+
description : LED
|
| 3 |
+
results_root_dir : results
|
| 4 |
+
dataset : nba
|
| 5 |
+
|
| 6 |
+
# ------------------- Dataset -------------------------
|
| 7 |
+
past_frames : 10
|
| 8 |
+
future_frames : 20
|
| 9 |
+
min_past_frames : 10
|
| 10 |
+
min_future_frames : 20
|
| 11 |
+
|
| 12 |
+
motion_dim : 2
|
| 13 |
+
forecast_dim : 2
|
| 14 |
+
|
| 15 |
+
traj_mean : [14, 7.5]
|
| 16 |
+
traj_scale : 5
|
| 17 |
+
|
| 18 |
+
# ------------------- Model -------------------------
|
| 19 |
+
pretrained_core_denoising_model: './results/checkpoints/base_diffusion_model.p'
|
| 20 |
+
debug : True # set to True for early stop in each epoch.
|
| 21 |
+
|
| 22 |
+
diffusion : {
|
| 23 |
+
steps : 100,
|
| 24 |
+
beta_start : 1.e-4,
|
| 25 |
+
beta_end : 5.e-2,
|
| 26 |
+
beta_schedule : 'linear'
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
# ------------------- Training Parameters -------------------------
|
| 30 |
+
lr : 1.e-3
|
| 31 |
+
train_batch_size : 2
|
| 32 |
+
test_batch_size : 5
|
| 33 |
+
num_epochs : 100
|
| 34 |
+
test_interval : 2
|
| 35 |
+
lr_scheduler : 'step'
|
| 36 |
+
decay_step : 8
|
| 37 |
+
decay_gamma : 0.5
|
| 38 |
+
|
LED/LED/main_led_nba.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
from trainer import train_led_trajectory_augment_input as led
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
def parse_config():
|
| 6 |
+
parser = argparse.ArgumentParser()
|
| 7 |
+
parser.add_argument("--cuda", default=True)
|
| 8 |
+
parser.add_argument("--learning_rate", type=int, default=0.002)
|
| 9 |
+
parser.add_argument("--max_epochs", type=int, default=128)
|
| 10 |
+
|
| 11 |
+
parser.add_argument('--cfg', default='led_augment')
|
| 12 |
+
parser.add_argument('--gpu', type=int, default=0, help='Specify which GPU to use.')
|
| 13 |
+
parser.add_argument('--train', type=int, default=1, help='Whether train or evaluate.')
|
| 14 |
+
|
| 15 |
+
parser.add_argument("--info", type=str, default='', help='Name of the experiment. '
|
| 16 |
+
'It will be used in file creation.')
|
| 17 |
+
return parser.parse_args()
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def main(config):
|
| 21 |
+
t = led.Trainer(config)
|
| 22 |
+
if config.train==1:
|
| 23 |
+
t.fit()
|
| 24 |
+
else:
|
| 25 |
+
# t.save_data()
|
| 26 |
+
t.test_single_model()
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
if __name__ == "__main__":
|
| 30 |
+
config = parse_config()
|
| 31 |
+
main(config)
|
LED/LED/models/layers.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
import torch
|
| 3 |
+
import torch.nn as nn
|
| 4 |
+
from torch.nn import Module, Linear
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class PositionalEncoding(nn.Module):
|
| 8 |
+
def __init__(self, d_model, dropout=0.1, max_len=5000):
|
| 9 |
+
super().__init__()
|
| 10 |
+
|
| 11 |
+
self.dropout = nn.Dropout(p=dropout)
|
| 12 |
+
pe = torch.zeros(max_len, d_model)
|
| 13 |
+
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
|
| 14 |
+
div_term = torch.exp(
|
| 15 |
+
torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model)
|
| 16 |
+
)
|
| 17 |
+
pe[:, 0::2] = torch.sin(position * div_term)
|
| 18 |
+
pe[:, 1::2] = torch.cos(position * div_term)
|
| 19 |
+
pe = pe.unsqueeze(0).transpose(0, 1)
|
| 20 |
+
self.register_buffer("pe", pe)
|
| 21 |
+
|
| 22 |
+
def forward(self, x):
|
| 23 |
+
x = x + self.pe[: x.size(0), :]
|
| 24 |
+
return self.dropout(x)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class ConcatSquashLinear(Module):
|
| 28 |
+
def __init__(self, dim_in, dim_out, dim_ctx):
|
| 29 |
+
super(ConcatSquashLinear, self).__init__()
|
| 30 |
+
self._layer = Linear(dim_in, dim_out)
|
| 31 |
+
self._hyper_bias = Linear(dim_ctx, dim_out, bias=False)
|
| 32 |
+
self._hyper_gate = Linear(dim_ctx, dim_out)
|
| 33 |
+
|
| 34 |
+
def forward(self, ctx, x):
|
| 35 |
+
# ctx: (B, 1, F+3)
|
| 36 |
+
# x: (B, T, 2)
|
| 37 |
+
gate = torch.sigmoid(self._hyper_gate(ctx))
|
| 38 |
+
bias = self._hyper_bias(ctx)
|
| 39 |
+
# if x.dim() == 3:
|
| 40 |
+
# gate = gate.unsqueeze(1)
|
| 41 |
+
# bias = bias.unsqueeze(1)
|
| 42 |
+
ret = self._layer(x) * gate + bias
|
| 43 |
+
return ret
|
| 44 |
+
|
| 45 |
+
def batch_generate(self, ctx, x):
|
| 46 |
+
# ctx: (B, n, 1, F+3)
|
| 47 |
+
# x: (B, n, T, 2)
|
| 48 |
+
gate = torch.sigmoid(self._hyper_gate(ctx))
|
| 49 |
+
bias = self._hyper_bias(ctx)
|
| 50 |
+
# if x.dim() == 3:
|
| 51 |
+
# gate = gate.unsqueeze(1)
|
| 52 |
+
# bias = bias.unsqueeze(1)
|
| 53 |
+
ret = self._layer(x) * gate + bias
|
| 54 |
+
return ret
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
class GAT(nn.Module):
|
| 58 |
+
def __init__(self, in_feat=2, out_feat=64, n_head=4, dropout=0.1, skip=True):
|
| 59 |
+
super(GAT, self).__init__()
|
| 60 |
+
self.in_feat = in_feat
|
| 61 |
+
self.out_feat = out_feat
|
| 62 |
+
self.n_head = n_head
|
| 63 |
+
self.skip = skip
|
| 64 |
+
self.w = nn.Parameter(torch.Tensor(n_head, in_feat, out_feat))
|
| 65 |
+
self.a_src = nn.Parameter(torch.Tensor(n_head, out_feat, 1))
|
| 66 |
+
self.a_dst = nn.Parameter(torch.Tensor(n_head, out_feat, 1))
|
| 67 |
+
self.bias = nn.Parameter(torch.Tensor(out_feat))
|
| 68 |
+
|
| 69 |
+
self.leaky_relu = nn.LeakyReLU(negative_slope=0.2)
|
| 70 |
+
self.softmax = nn.Softmax(dim=-1)
|
| 71 |
+
self.dropout = nn.Dropout(dropout)
|
| 72 |
+
|
| 73 |
+
nn.init.xavier_uniform_(self.w, gain=1.414)
|
| 74 |
+
nn.init.xavier_uniform_(self.a_src, gain=1.414)
|
| 75 |
+
nn.init.xavier_uniform_(self.a_dst, gain=1.414)
|
| 76 |
+
nn.init.constant_(self.bias, 0)
|
| 77 |
+
|
| 78 |
+
def forward(self, h, mask):
|
| 79 |
+
h_prime = h.unsqueeze(1) @ self.w
|
| 80 |
+
attn_src = h_prime @ self.a_src
|
| 81 |
+
attn_dst = h_prime @ self.a_dst
|
| 82 |
+
attn = attn_src @ attn_dst.permute(0, 1, 3, 2)
|
| 83 |
+
attn = self.leaky_relu(attn)
|
| 84 |
+
attn = self.softmax(attn)
|
| 85 |
+
attn = self.dropout(attn)
|
| 86 |
+
attn = attn * mask if mask is not None else attn
|
| 87 |
+
out = (attn @ h_prime).sum(dim=1) + self.bias
|
| 88 |
+
if self.skip:
|
| 89 |
+
out += h_prime.sum(dim=1)
|
| 90 |
+
return out, attn
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
class MLP(nn.Module):
|
| 94 |
+
def __init__(self, in_feat, out_feat, hid_feat=(1024, 512), activation=None, dropout=-1):
|
| 95 |
+
super(MLP, self).__init__()
|
| 96 |
+
dims = (in_feat, ) + hid_feat + (out_feat, )
|
| 97 |
+
|
| 98 |
+
self.layers = nn.ModuleList()
|
| 99 |
+
for i in range(len(dims) - 1):
|
| 100 |
+
self.layers.append(nn.Linear(dims[i], dims[i + 1]))
|
| 101 |
+
|
| 102 |
+
self.activation = activation if activation is not None else lambda x: x
|
| 103 |
+
self.dropout = nn.Dropout(dropout) if dropout != -1 else lambda x: x
|
| 104 |
+
|
| 105 |
+
def forward(self, x):
|
| 106 |
+
for i in range(len(self.layers)):
|
| 107 |
+
x = self.activation(x)
|
| 108 |
+
x = self.dropout(x)
|
| 109 |
+
x = self.layers[i](x)
|
| 110 |
+
return x
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
class social_transformer(nn.Module):
|
| 114 |
+
def __init__(self, past_len):
|
| 115 |
+
super(social_transformer, self).__init__()
|
| 116 |
+
self.encode_past = nn.Linear(past_len*6, 256, bias=False)
|
| 117 |
+
self.layer = nn.TransformerEncoderLayer(d_model=256, nhead=2, dim_feedforward=256)
|
| 118 |
+
self.transformer_encoder = nn.TransformerEncoder(self.layer, num_layers=2)
|
| 119 |
+
|
| 120 |
+
def forward(self, h, mask):
|
| 121 |
+
'''
|
| 122 |
+
h: batch_size, t, 2
|
| 123 |
+
'''
|
| 124 |
+
h_feat = self.encode_past(h.reshape(h.size(0), -1)).unsqueeze(1)
|
| 125 |
+
# print(h_feat.shape)
|
| 126 |
+
# n_samples, 1, 64
|
| 127 |
+
h_feat_ = self.transformer_encoder(h_feat, mask)
|
| 128 |
+
h_feat = h_feat + h_feat_
|
| 129 |
+
|
| 130 |
+
return h_feat
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
class st_encoder(nn.Module):
|
| 134 |
+
def __init__(self):
|
| 135 |
+
super().__init__()
|
| 136 |
+
channel_in = 6
|
| 137 |
+
channel_out = 32
|
| 138 |
+
dim_kernel = 3
|
| 139 |
+
self.dim_embedding_key = 256
|
| 140 |
+
self.spatial_conv = nn.Conv1d(channel_in, channel_out, dim_kernel, stride=1, padding=1)
|
| 141 |
+
self.temporal_encoder = nn.GRU(channel_out, self.dim_embedding_key, 1, batch_first=True)
|
| 142 |
+
|
| 143 |
+
self.relu = nn.ReLU()
|
| 144 |
+
|
| 145 |
+
self.reset_parameters()
|
| 146 |
+
|
| 147 |
+
def reset_parameters(self):
|
| 148 |
+
nn.init.kaiming_normal_(self.spatial_conv.weight)
|
| 149 |
+
nn.init.kaiming_normal_(self.temporal_encoder.weight_ih_l0)
|
| 150 |
+
nn.init.kaiming_normal_(self.temporal_encoder.weight_hh_l0)
|
| 151 |
+
nn.init.zeros_(self.spatial_conv.bias)
|
| 152 |
+
nn.init.zeros_(self.temporal_encoder.bias_ih_l0)
|
| 153 |
+
nn.init.zeros_(self.temporal_encoder.bias_hh_l0)
|
| 154 |
+
|
| 155 |
+
def forward(self, X):
|
| 156 |
+
'''
|
| 157 |
+
X: b, T, 2
|
| 158 |
+
|
| 159 |
+
return: b, F
|
| 160 |
+
'''
|
| 161 |
+
X_t = torch.transpose(X, 1, 2)
|
| 162 |
+
X_after_spatial = self.relu(self.spatial_conv(X_t))
|
| 163 |
+
X_embed = torch.transpose(X_after_spatial, 1, 2)
|
| 164 |
+
|
| 165 |
+
output_x, state_x = self.temporal_encoder(X_embed)
|
| 166 |
+
state_x = state_x.squeeze(0)
|
| 167 |
+
|
| 168 |
+
return state_x
|
| 169 |
+
|
LED/LED/models/model_diffusion.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
import torch
|
| 3 |
+
import torch.nn as nn
|
| 4 |
+
from torch.nn import Module, Linear
|
| 5 |
+
|
| 6 |
+
from models.layers import PositionalEncoding, ConcatSquashLinear
|
| 7 |
+
|
| 8 |
+
class st_encoder(nn.Module):
|
| 9 |
+
def __init__(self):
|
| 10 |
+
super().__init__()
|
| 11 |
+
channel_in = 2
|
| 12 |
+
channel_out = 32
|
| 13 |
+
dim_kernel = 3
|
| 14 |
+
self.dim_embedding_key = 256
|
| 15 |
+
self.spatial_conv = nn.Conv1d(channel_in, channel_out, dim_kernel, stride=1, padding=1)
|
| 16 |
+
self.temporal_encoder = nn.GRU(channel_out, self.dim_embedding_key, 1, batch_first=True)
|
| 17 |
+
|
| 18 |
+
self.relu = nn.ReLU()
|
| 19 |
+
|
| 20 |
+
self.reset_parameters()
|
| 21 |
+
|
| 22 |
+
def reset_parameters(self):
|
| 23 |
+
nn.init.kaiming_normal_(self.spatial_conv.weight)
|
| 24 |
+
nn.init.kaiming_normal_(self.temporal_encoder.weight_ih_l0)
|
| 25 |
+
nn.init.kaiming_normal_(self.temporal_encoder.weight_hh_l0)
|
| 26 |
+
nn.init.zeros_(self.spatial_conv.bias)
|
| 27 |
+
nn.init.zeros_(self.temporal_encoder.bias_ih_l0)
|
| 28 |
+
nn.init.zeros_(self.temporal_encoder.bias_hh_l0)
|
| 29 |
+
|
| 30 |
+
def forward(self, X):
|
| 31 |
+
'''
|
| 32 |
+
X: b, T, 2
|
| 33 |
+
|
| 34 |
+
return: b, F
|
| 35 |
+
'''
|
| 36 |
+
X_t = torch.transpose(X, 1, 2)
|
| 37 |
+
X_after_spatial = self.relu(self.spatial_conv(X_t))
|
| 38 |
+
X_embed = torch.transpose(X_after_spatial, 1, 2)
|
| 39 |
+
|
| 40 |
+
output_x, state_x = self.temporal_encoder(X_embed)
|
| 41 |
+
state_x = state_x.squeeze(0)
|
| 42 |
+
|
| 43 |
+
return state_x
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class social_transformer(nn.Module):
|
| 47 |
+
def __init__(self):
|
| 48 |
+
super(social_transformer, self).__init__()
|
| 49 |
+
self.encode_past = nn.Linear(60, 256, bias=False)
|
| 50 |
+
# self.encode_past = nn.Linear(48, 256, bias=False)
|
| 51 |
+
self.layer = nn.TransformerEncoderLayer(d_model=256, nhead=2, dim_feedforward=256)
|
| 52 |
+
self.transformer_encoder = nn.TransformerEncoder(self.layer, num_layers=2)
|
| 53 |
+
|
| 54 |
+
def forward(self, h, mask):
|
| 55 |
+
'''
|
| 56 |
+
h: batch_size, t, 2
|
| 57 |
+
'''
|
| 58 |
+
# print(h.shape)
|
| 59 |
+
h_feat = self.encode_past(h.reshape(h.size(0), -1)).unsqueeze(1)
|
| 60 |
+
# print(h_feat.shape)
|
| 61 |
+
# n_samples, 1, 64
|
| 62 |
+
h_feat_ = self.transformer_encoder(h_feat, mask)
|
| 63 |
+
h_feat = h_feat + h_feat_
|
| 64 |
+
|
| 65 |
+
return h_feat
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
class TransformerDenoisingModel(Module):
|
| 69 |
+
|
| 70 |
+
def __init__(self, context_dim=256, tf_layer=2):
|
| 71 |
+
super().__init__()
|
| 72 |
+
self.encoder_context = social_transformer()
|
| 73 |
+
self.pos_emb = PositionalEncoding(d_model=2*context_dim, dropout=0.1, max_len=24)
|
| 74 |
+
self.concat1 = ConcatSquashLinear(2, 2*context_dim, context_dim+3)
|
| 75 |
+
self.layer = nn.TransformerEncoderLayer(d_model=2*context_dim, nhead=2, dim_feedforward=2*context_dim)
|
| 76 |
+
self.transformer_encoder = nn.TransformerEncoder(self.layer, num_layers=tf_layer)
|
| 77 |
+
self.concat3 = ConcatSquashLinear(2*context_dim,context_dim,context_dim+3)
|
| 78 |
+
self.concat4 = ConcatSquashLinear(context_dim,context_dim//2,context_dim+3)
|
| 79 |
+
self.linear = ConcatSquashLinear(context_dim//2, 2, context_dim+3)
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def forward(self, x, beta, context, mask):
|
| 83 |
+
batch_size = x.size(0)
|
| 84 |
+
beta = beta.view(batch_size, 1, 1) # (B, 1, 1)
|
| 85 |
+
mask = mask.float().masked_fill(mask == 0, float('-inf')).masked_fill(mask == 1, float(0.0))
|
| 86 |
+
context = self.encoder_context(context, mask)
|
| 87 |
+
# context = context.view(batch_size, 1, -1) # (B, 1, F)
|
| 88 |
+
|
| 89 |
+
time_emb = torch.cat([beta, torch.sin(beta), torch.cos(beta)], dim=-1) # (B, 1, 3)
|
| 90 |
+
ctx_emb = torch.cat([time_emb, context], dim=-1) # (B, 1, F+3)
|
| 91 |
+
|
| 92 |
+
x = self.concat1(ctx_emb, x)
|
| 93 |
+
final_emb = x.permute(1,0,2)
|
| 94 |
+
final_emb = self.pos_emb(final_emb)
|
| 95 |
+
|
| 96 |
+
trans = self.transformer_encoder(final_emb).permute(1,0,2)
|
| 97 |
+
trans = self.concat3(ctx_emb, trans)
|
| 98 |
+
trans = self.concat4(ctx_emb, trans)
|
| 99 |
+
return self.linear(ctx_emb, trans)
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def generate_accelerate(self, x, beta, context, mask):
|
| 103 |
+
batch_size = x.size(0)
|
| 104 |
+
beta = beta.view(beta.size(0), 1, 1) # (B, 1, 1)
|
| 105 |
+
mask = mask.float().masked_fill(mask == 0, float('-inf')).masked_fill(mask == 1, float(0.0))
|
| 106 |
+
context = self.encoder_context(context, mask)
|
| 107 |
+
# context = context.view(batch_size, 1, -1) # (B, 1, F)
|
| 108 |
+
|
| 109 |
+
time_emb = torch.cat([beta, torch.sin(beta), torch.cos(beta)], dim=-1) # (B, 1, 3)
|
| 110 |
+
# time_emb: [11, 1, 3]
|
| 111 |
+
# context: [11, 1, 256]
|
| 112 |
+
ctx_emb = torch.cat([time_emb, context], dim=-1).repeat(1, 10, 1).unsqueeze(2)
|
| 113 |
+
# x: 11, 10, 20, 2
|
| 114 |
+
# ctx_emb: 11, 10, 1, 259
|
| 115 |
+
x = self.concat1.batch_generate(ctx_emb, x).contiguous().view(-1, 20, 512)
|
| 116 |
+
# x: 110, 20, 512
|
| 117 |
+
final_emb = x.permute(1, 0, 2)
|
| 118 |
+
final_emb = self.pos_emb(final_emb)
|
| 119 |
+
|
| 120 |
+
trans = self.transformer_encoder(final_emb).permute(1, 0, 2).contiguous().view(-1, 10, 20, 512)
|
| 121 |
+
# trans: 11, 10, 20, 512
|
| 122 |
+
trans = self.concat3.batch_generate(ctx_emb, trans)
|
| 123 |
+
trans = self.concat4.batch_generate(ctx_emb, trans)
|
| 124 |
+
return self.linear.batch_generate(ctx_emb, trans)
|
LED/LED/models/model_led_initializer.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
from models.layers import MLP, social_transformer, st_encoder
|
| 4 |
+
|
| 5 |
+
class LEDInitializer(nn.Module):
|
| 6 |
+
def __init__(self, t_h: int=8, d_h: int=6, t_f: int=40, d_f: int=2, k_pred: int=20):
|
| 7 |
+
'''
|
| 8 |
+
Parameters
|
| 9 |
+
----
|
| 10 |
+
t_h: history timestamps,
|
| 11 |
+
d_h: dimension of each historical timestamp,
|
| 12 |
+
t_f: future timestamps,
|
| 13 |
+
d_f: dimension of each future timestamp,
|
| 14 |
+
k_pred: number of predictions.
|
| 15 |
+
|
| 16 |
+
'''
|
| 17 |
+
super(LEDInitializer, self).__init__()
|
| 18 |
+
self.n = k_pred
|
| 19 |
+
self.input_dim = t_h * d_h
|
| 20 |
+
self.output_dim = t_f * d_f * k_pred
|
| 21 |
+
self.fut_len = t_f
|
| 22 |
+
|
| 23 |
+
self.social_encoder = social_transformer(t_h)
|
| 24 |
+
self.ego_var_encoder = st_encoder()
|
| 25 |
+
self.ego_mean_encoder = st_encoder()
|
| 26 |
+
self.ego_scale_encoder = st_encoder()
|
| 27 |
+
|
| 28 |
+
self.scale_encoder = MLP(1, 32, hid_feat=(4, 16), activation=nn.ReLU())
|
| 29 |
+
|
| 30 |
+
self.var_decoder = MLP(256*2+32, self.output_dim, hid_feat=(1024, 1024), activation=nn.ReLU())
|
| 31 |
+
self.mean_decoder = MLP(256*2, t_f * d_f, hid_feat=(256, 128), activation=nn.ReLU())
|
| 32 |
+
self.scale_decoder = MLP(256*2, 1, hid_feat=(256, 128), activation=nn.ReLU())
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def forward(self, x, mask=None):
|
| 36 |
+
'''
|
| 37 |
+
x: batch size, t_p, 6
|
| 38 |
+
'''
|
| 39 |
+
mask = mask.float().masked_fill(mask == 0, float('-inf')).masked_fill(mask == 1, float(0.0))
|
| 40 |
+
social_embed = self.social_encoder(x, mask)
|
| 41 |
+
social_embed = social_embed.squeeze(1)
|
| 42 |
+
# B, 256
|
| 43 |
+
|
| 44 |
+
ego_var_embed = self.ego_var_encoder(x)
|
| 45 |
+
ego_mean_embed = self.ego_mean_encoder(x)
|
| 46 |
+
ego_scale_embed = self.ego_scale_encoder(x)
|
| 47 |
+
# B, 256
|
| 48 |
+
|
| 49 |
+
mean_total = torch.cat((ego_mean_embed, social_embed), dim=-1)
|
| 50 |
+
|
| 51 |
+
guess_mean = self.mean_decoder(mean_total).contiguous().view(-1, self.fut_len, 2)
|
| 52 |
+
|
| 53 |
+
scale_total = torch.cat((ego_scale_embed, social_embed), dim=-1)
|
| 54 |
+
guess_scale = self.scale_decoder(scale_total)
|
| 55 |
+
|
| 56 |
+
guess_scale_feat = self.scale_encoder(guess_scale)
|
| 57 |
+
var_total = torch.cat((ego_var_embed, social_embed, guess_scale_feat), dim=-1)
|
| 58 |
+
guess_var = self.var_decoder(var_total).reshape(x.size(0), self.n, self.fut_len, 2)
|
| 59 |
+
|
| 60 |
+
return guess_var, guess_mean, guess_scale
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
|
LED/LED/requirements.txt
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
absl-py==1.4.0
|
| 2 |
+
aiohttp==3.8.4
|
| 3 |
+
aiosignal==1.3.1
|
| 4 |
+
antlr4-python3-runtime==4.8
|
| 5 |
+
async-timeout==4.0.2
|
| 6 |
+
asynctest==0.13.0
|
| 7 |
+
attrs==23.1.0
|
| 8 |
+
cachetools==5.3.0
|
| 9 |
+
charset-normalizer==3.1.0
|
| 10 |
+
colour==0.1.5
|
| 11 |
+
cycler==0.11.0
|
| 12 |
+
descartes==1.1.0
|
| 13 |
+
easydict==1.10
|
| 14 |
+
fonttools==4.38.0
|
| 15 |
+
frozenlist==1.3.3
|
| 16 |
+
fsspec==2023.1.0
|
| 17 |
+
future==0.18.3
|
| 18 |
+
glob2==0.7
|
| 19 |
+
google-auth==2.18.0
|
| 20 |
+
google-auth-oauthlib==0.4.6
|
| 21 |
+
googledrivedownloader==0.4
|
| 22 |
+
grpcio==1.54.0
|
| 23 |
+
h5py==3.8.0
|
| 24 |
+
hydra-core==1.1.0
|
| 25 |
+
imageio==2.27.0
|
| 26 |
+
imgaug==0.4.0
|
| 27 |
+
importlib-metadata==4.13.0
|
| 28 |
+
importlib-resources==5.12.0
|
| 29 |
+
isodate==0.6.1
|
| 30 |
+
Jinja2==3.1.2
|
| 31 |
+
joblib==1.2.0
|
| 32 |
+
kiwisolver==1.4.4
|
| 33 |
+
lapsolver==1.1.0
|
| 34 |
+
llvmlite==0.39.1
|
| 35 |
+
Markdown==3.4.3
|
| 36 |
+
MarkupSafe==2.1.2
|
| 37 |
+
matplotlib==3.5.3
|
| 38 |
+
motmetrics==1.1.3
|
| 39 |
+
multidict==6.0.4
|
| 40 |
+
networkx==2.6.3
|
| 41 |
+
numba==0.56.4
|
| 42 |
+
numpy==1.19.0
|
| 43 |
+
oauthlib==3.2.2
|
| 44 |
+
omegaconf==2.1.0
|
| 45 |
+
opencv-python==4.7.0.72
|
| 46 |
+
packaging==23.1
|
| 47 |
+
pandas==1.3.5
|
| 48 |
+
Pillow==9.5.0
|
| 49 |
+
polars==0.17.12
|
| 50 |
+
protobuf==3.20.3
|
| 51 |
+
pyasn1==0.5.0
|
| 52 |
+
pyasn1-modules==0.3.0
|
| 53 |
+
pyDeprecate==0.3.1
|
| 54 |
+
pyntcloud==0.3.1
|
| 55 |
+
pyparsing==3.0.9
|
| 56 |
+
python-dateutil==2.8.2
|
| 57 |
+
python-louvain==0.16
|
| 58 |
+
pytorch-lightning==1.5.2
|
| 59 |
+
pytz==2023.3
|
| 60 |
+
PyWavelets==1.3.0
|
| 61 |
+
PyYAML==6.0
|
| 62 |
+
rdflib==6.3.2
|
| 63 |
+
requests-oauthlib==1.3.1
|
| 64 |
+
rsa==4.9
|
| 65 |
+
scikit-image==0.19.3
|
| 66 |
+
scikit-learn==1.0.2
|
| 67 |
+
scipy==1.7.3
|
| 68 |
+
shapely==2.0.1
|
| 69 |
+
spconv==1.2.1
|
| 70 |
+
tensorboard==2.11.2
|
| 71 |
+
tensorboard-data-server==0.6.1
|
| 72 |
+
tensorboard-plugin-wit==1.8.1
|
| 73 |
+
tensorboardX==2.6
|
| 74 |
+
threadpoolctl==3.1.0
|
| 75 |
+
tifffile==2021.11.2
|
| 76 |
+
torch==1.8.0+cu111
|
| 77 |
+
torchaudio==0.8.0
|
| 78 |
+
torchmetrics==0.11.4
|
| 79 |
+
torchvision==0.9.0+cu111
|
| 80 |
+
tqdm==4.65.0
|
| 81 |
+
typing_extensions==4.5.0
|
| 82 |
+
Werkzeug==2.2.3
|
| 83 |
+
yarl==1.9.2
|
| 84 |
+
zipp==3.15.0
|
LED/LED/trainer/train_led_trajectory_augment_input.py
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import os
|
| 3 |
+
import time
|
| 4 |
+
import torch
|
| 5 |
+
import random
|
| 6 |
+
import numpy as np
|
| 7 |
+
import torch.nn as nn
|
| 8 |
+
|
| 9 |
+
from utils.config import Config
|
| 10 |
+
from utils.utils import print_log
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
from torch.utils.data import DataLoader
|
| 14 |
+
from data.dataloader_nba import NBADataset, seq_collate
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
from models.model_led_initializer import LEDInitializer as InitializationModel
|
| 18 |
+
from models.model_diffusion import TransformerDenoisingModel as CoreDenoisingModel
|
| 19 |
+
|
| 20 |
+
import pdb
|
| 21 |
+
NUM_Tau = 5
|
| 22 |
+
|
| 23 |
+
class Trainer:
|
| 24 |
+
def __init__(self, config):
|
| 25 |
+
|
| 26 |
+
if torch.cuda.is_available(): torch.cuda.set_device(config.gpu)
|
| 27 |
+
self.device = torch.device('cuda') if config.cuda else torch.device('cpu')
|
| 28 |
+
self.cfg = Config(config.cfg, config.info)
|
| 29 |
+
|
| 30 |
+
# ------------------------- prepare train/test data loader -------------------------
|
| 31 |
+
train_dset = NBADataset(
|
| 32 |
+
obs_len=self.cfg.past_frames,
|
| 33 |
+
pred_len=self.cfg.future_frames,
|
| 34 |
+
training=True)
|
| 35 |
+
|
| 36 |
+
self.train_loader = DataLoader(
|
| 37 |
+
train_dset,
|
| 38 |
+
batch_size=self.cfg.train_batch_size,
|
| 39 |
+
shuffle=True,
|
| 40 |
+
num_workers=4,
|
| 41 |
+
collate_fn=seq_collate,
|
| 42 |
+
pin_memory=True)
|
| 43 |
+
|
| 44 |
+
test_dset = NBADataset(
|
| 45 |
+
obs_len=self.cfg.past_frames,
|
| 46 |
+
pred_len=self.cfg.future_frames,
|
| 47 |
+
training=False)
|
| 48 |
+
|
| 49 |
+
self.test_loader = DataLoader(
|
| 50 |
+
test_dset,
|
| 51 |
+
batch_size=self.cfg.test_batch_size,
|
| 52 |
+
shuffle=False,
|
| 53 |
+
num_workers=4,
|
| 54 |
+
collate_fn=seq_collate,
|
| 55 |
+
pin_memory=True)
|
| 56 |
+
|
| 57 |
+
# data normalization parameters
|
| 58 |
+
self.traj_mean = torch.FloatTensor(self.cfg.traj_mean).cuda().unsqueeze(0).unsqueeze(0).unsqueeze(0)
|
| 59 |
+
self.traj_scale = self.cfg.traj_scale
|
| 60 |
+
|
| 61 |
+
# ------------------------- define diffusion parameters -------------------------
|
| 62 |
+
self.n_steps = self.cfg.diffusion.steps # define total diffusion steps
|
| 63 |
+
|
| 64 |
+
# make beta schedule and calculate the parameters used in denoising process.
|
| 65 |
+
self.betas = self.make_beta_schedule(
|
| 66 |
+
schedule=self.cfg.diffusion.beta_schedule, n_timesteps=self.n_steps,
|
| 67 |
+
start=self.cfg.diffusion.beta_start, end=self.cfg.diffusion.beta_end).cuda()
|
| 68 |
+
|
| 69 |
+
self.alphas = 1 - self.betas
|
| 70 |
+
self.alphas_prod = torch.cumprod(self.alphas, 0)
|
| 71 |
+
self.alphas_bar_sqrt = torch.sqrt(self.alphas_prod)
|
| 72 |
+
self.one_minus_alphas_bar_sqrt = torch.sqrt(1 - self.alphas_prod)
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
# ------------------------- define models -------------------------
|
| 76 |
+
self.model = CoreDenoisingModel().cuda()
|
| 77 |
+
# load pretrained models
|
| 78 |
+
model_cp = torch.load(self.cfg.pretrained_core_denoising_model, map_location='cpu')
|
| 79 |
+
self.model.load_state_dict(model_cp['model_dict'])
|
| 80 |
+
|
| 81 |
+
self.model_initializer = InitializationModel(t_h=10, d_h=6, t_f=20, d_f=2, k_pred=20).cuda()
|
| 82 |
+
|
| 83 |
+
self.opt = torch.optim.AdamW(self.model_initializer.parameters(), lr=config.learning_rate)
|
| 84 |
+
self.scheduler_model = torch.optim.lr_scheduler.StepLR(self.opt, step_size=self.cfg.decay_step, gamma=self.cfg.decay_gamma)
|
| 85 |
+
|
| 86 |
+
# ------------------------- prepare logs -------------------------
|
| 87 |
+
self.log = open(os.path.join(self.cfg.log_dir, 'log.txt'), 'a+')
|
| 88 |
+
self.print_model_param(self.model, name='Core Denoising Model')
|
| 89 |
+
self.print_model_param(self.model_initializer, name='Initialization Model')
|
| 90 |
+
|
| 91 |
+
# temporal reweight in the loss, it is not necessary.
|
| 92 |
+
self.temporal_reweight = torch.FloatTensor([21 - i for i in range(1, 21)]).cuda().unsqueeze(0).unsqueeze(0) / 10
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def print_model_param(self, model: nn.Module, name: str = 'Model') -> None:
|
| 96 |
+
'''
|
| 97 |
+
Count the trainable/total parameters in `model`.
|
| 98 |
+
'''
|
| 99 |
+
total_num = sum(p.numel() for p in model.parameters())
|
| 100 |
+
trainable_num = sum(p.numel() for p in model.parameters() if p.requires_grad)
|
| 101 |
+
print_log("[{}] Trainable/Total: {}/{}".format(name, trainable_num, total_num), self.log)
|
| 102 |
+
return None
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def make_beta_schedule(self, schedule: str = 'linear',
|
| 106 |
+
n_timesteps: int = 1000,
|
| 107 |
+
start: float = 1e-5, end: float = 1e-2) -> torch.Tensor:
|
| 108 |
+
'''
|
| 109 |
+
Make beta schedule.
|
| 110 |
+
|
| 111 |
+
Parameters
|
| 112 |
+
----
|
| 113 |
+
schedule: str, in ['linear', 'quad', 'sigmoid'],
|
| 114 |
+
n_timesteps: int, diffusion steps,
|
| 115 |
+
start: float, beta start, `start<end`,
|
| 116 |
+
end: float, beta end,
|
| 117 |
+
|
| 118 |
+
Returns
|
| 119 |
+
----
|
| 120 |
+
betas: Tensor with the shape of (n_timesteps)
|
| 121 |
+
|
| 122 |
+
'''
|
| 123 |
+
if schedule == 'linear':
|
| 124 |
+
betas = torch.linspace(start, end, n_timesteps)
|
| 125 |
+
elif schedule == "quad":
|
| 126 |
+
betas = torch.linspace(start ** 0.5, end ** 0.5, n_timesteps) ** 2
|
| 127 |
+
elif schedule == "sigmoid":
|
| 128 |
+
betas = torch.linspace(-6, 6, n_timesteps)
|
| 129 |
+
betas = torch.sigmoid(betas) * (end - start) + start
|
| 130 |
+
return betas
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def extract(self, input, t, x):
|
| 134 |
+
shape = x.shape
|
| 135 |
+
out = torch.gather(input, 0, t.to(input.device))
|
| 136 |
+
reshape = [t.shape[0]] + [1] * (len(shape) - 1)
|
| 137 |
+
return out.reshape(*reshape)
|
| 138 |
+
|
| 139 |
+
def noise_estimation_loss(self, x, y_0, mask):
|
| 140 |
+
batch_size = x.shape[0]
|
| 141 |
+
# Select a random step for each example
|
| 142 |
+
t = torch.randint(0, self.n_steps, size=(batch_size // 2 + 1,)).to(x.device)
|
| 143 |
+
t = torch.cat([t, self.n_steps - t - 1], dim=0)[:batch_size]
|
| 144 |
+
# x0 multiplier
|
| 145 |
+
a = self.extract(self.alphas_bar_sqrt, t, y_0)
|
| 146 |
+
beta = self.extract(self.betas, t, y_0)
|
| 147 |
+
# eps multiplier
|
| 148 |
+
am1 = self.extract(self.one_minus_alphas_bar_sqrt, t, y_0)
|
| 149 |
+
e = torch.randn_like(y_0)
|
| 150 |
+
# model input
|
| 151 |
+
y = y_0 * a + e * am1
|
| 152 |
+
output = self.model(y, beta, x, mask)
|
| 153 |
+
# batch_size, 20, 2
|
| 154 |
+
return (e - output).square().mean()
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
def p_sample(self, x, mask, cur_y, t):
|
| 159 |
+
if t==0:
|
| 160 |
+
z = torch.zeros_like(cur_y).to(x.device)
|
| 161 |
+
else:
|
| 162 |
+
z = torch.randn_like(cur_y).to(x.device)
|
| 163 |
+
t = torch.tensor([t]).cuda()
|
| 164 |
+
# Factor to the model output
|
| 165 |
+
eps_factor = ((1 - self.extract(self.alphas, t, cur_y)) / self.extract(self.one_minus_alphas_bar_sqrt, t, cur_y))
|
| 166 |
+
# Model output
|
| 167 |
+
beta = self.extract(self.betas, t.repeat(x.shape[0]), cur_y)
|
| 168 |
+
eps_theta = self.model(cur_y, beta, x, mask)
|
| 169 |
+
mean = (1 / self.extract(self.alphas, t, cur_y).sqrt()) * (cur_y - (eps_factor * eps_theta))
|
| 170 |
+
# Generate z
|
| 171 |
+
z = torch.randn_like(cur_y).to(x.device)
|
| 172 |
+
# Fixed sigma
|
| 173 |
+
sigma_t = self.extract(self.betas, t, cur_y).sqrt()
|
| 174 |
+
sample = mean + sigma_t * z
|
| 175 |
+
return (sample)
|
| 176 |
+
|
| 177 |
+
def p_sample_accelerate(self, x, mask, cur_y, t):
|
| 178 |
+
if t==0:
|
| 179 |
+
z = torch.zeros_like(cur_y).to(x.device)
|
| 180 |
+
else:
|
| 181 |
+
z = torch.randn_like(cur_y).to(x.device)
|
| 182 |
+
t = torch.tensor([t]).cuda()
|
| 183 |
+
# Factor to the model output
|
| 184 |
+
eps_factor = ((1 - self.extract(self.alphas, t, cur_y)) / self.extract(self.one_minus_alphas_bar_sqrt, t, cur_y))
|
| 185 |
+
# Model output
|
| 186 |
+
beta = self.extract(self.betas, t.repeat(x.shape[0]), cur_y)
|
| 187 |
+
eps_theta = self.model.generate_accelerate(cur_y, beta, x, mask)
|
| 188 |
+
mean = (1 / self.extract(self.alphas, t, cur_y).sqrt()) * (cur_y - (eps_factor * eps_theta))
|
| 189 |
+
# Generate z
|
| 190 |
+
z = torch.randn_like(cur_y).to(x.device)
|
| 191 |
+
# Fixed sigma
|
| 192 |
+
sigma_t = self.extract(self.betas, t, cur_y).sqrt()
|
| 193 |
+
sample = mean + sigma_t * z * 0.00001
|
| 194 |
+
return (sample)
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
def p_sample_loop(self, x, mask, shape):
|
| 199 |
+
self.model.eval()
|
| 200 |
+
prediction_total = torch.Tensor().cuda()
|
| 201 |
+
for _ in range(20):
|
| 202 |
+
cur_y = torch.randn(shape).to(x.device)
|
| 203 |
+
for i in reversed(range(self.n_steps)):
|
| 204 |
+
cur_y = self.p_sample(x, mask, cur_y, i)
|
| 205 |
+
prediction_total = torch.cat((prediction_total, cur_y.unsqueeze(1)), dim=1)
|
| 206 |
+
return prediction_total
|
| 207 |
+
|
| 208 |
+
def p_sample_loop_mean(self, x, mask, loc):
|
| 209 |
+
prediction_total = torch.Tensor().cuda()
|
| 210 |
+
for loc_i in range(1):
|
| 211 |
+
cur_y = loc
|
| 212 |
+
for i in reversed(range(NUM_Tau)):
|
| 213 |
+
cur_y = self.p_sample(x, mask, cur_y, i)
|
| 214 |
+
prediction_total = torch.cat((prediction_total, cur_y.unsqueeze(1)), dim=1)
|
| 215 |
+
return prediction_total
|
| 216 |
+
|
| 217 |
+
def p_sample_loop_accelerate(self, x, mask, loc):
|
| 218 |
+
'''
|
| 219 |
+
Batch operation to accelerate the denoising process.
|
| 220 |
+
|
| 221 |
+
x: [11, 10, 6]
|
| 222 |
+
mask: [11, 11]
|
| 223 |
+
cur_y: [11, 10, 20, 2]
|
| 224 |
+
'''
|
| 225 |
+
prediction_total = torch.Tensor().cuda()
|
| 226 |
+
cur_y = loc[:, :10]
|
| 227 |
+
for i in reversed(range(NUM_Tau)):
|
| 228 |
+
cur_y = self.p_sample_accelerate(x, mask, cur_y, i)
|
| 229 |
+
cur_y_ = loc[:, 10:]
|
| 230 |
+
for i in reversed(range(NUM_Tau)):
|
| 231 |
+
cur_y_ = self.p_sample_accelerate(x, mask, cur_y_, i)
|
| 232 |
+
# shape: B=b*n, K=10, T, 2
|
| 233 |
+
prediction_total = torch.cat((cur_y_, cur_y), dim=1)
|
| 234 |
+
return prediction_total
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
def fit(self):
|
| 239 |
+
# Training loop
|
| 240 |
+
for epoch in range(0, self.cfg.num_epochs):
|
| 241 |
+
loss_total, loss_distance, loss_uncertainty = self._train_single_epoch(epoch)
|
| 242 |
+
print_log('[{}] Epoch: {}\t\tLoss: {:.6f}\tLoss Dist.: {:.6f}\tLoss Uncertainty: {:.6f}'.format(
|
| 243 |
+
time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
|
| 244 |
+
epoch, loss_total, loss_distance, loss_uncertainty), self.log)
|
| 245 |
+
|
| 246 |
+
if (epoch + 1) % self.cfg.test_interval == 0:
|
| 247 |
+
performance, samples = self._test_single_epoch()
|
| 248 |
+
for time_i in range(4):
|
| 249 |
+
print_log('--ADE({}s): {:.4f}\t--FDE({}s): {:.4f}'.format(
|
| 250 |
+
time_i+1, performance['ADE'][time_i]/samples,
|
| 251 |
+
time_i+1, performance['FDE'][time_i]/samples), self.log)
|
| 252 |
+
cp_path = self.cfg.model_path % (epoch + 1)
|
| 253 |
+
model_cp = {'model_initializer_dict': self.model_initializer.state_dict()}
|
| 254 |
+
torch.save(model_cp, cp_path)
|
| 255 |
+
self.scheduler_model.step()
|
| 256 |
+
|
| 257 |
+
|
| 258 |
+
def data_preprocess(self, data):
|
| 259 |
+
"""
|
| 260 |
+
pre_motion_3D: torch.Size([32, 11, 10, 2]), [batch_size, num_agent, past_frame, dimension]
|
| 261 |
+
fut_motion_3D: torch.Size([32, 11, 20, 2])
|
| 262 |
+
fut_motion_mask: torch.Size([32, 11, 20])
|
| 263 |
+
pre_motion_mask: torch.Size([32, 11, 10])
|
| 264 |
+
traj_scale: 1
|
| 265 |
+
pred_mask: None
|
| 266 |
+
seq: nba
|
| 267 |
+
"""
|
| 268 |
+
batch_size = data['pre_motion_3D'].shape[0]
|
| 269 |
+
|
| 270 |
+
traj_mask = torch.zeros(batch_size*11, batch_size*11).cuda()
|
| 271 |
+
for i in range(batch_size):
|
| 272 |
+
traj_mask[i*11:(i+1)*11, i*11:(i+1)*11] = 1.
|
| 273 |
+
|
| 274 |
+
initial_pos = data['pre_motion_3D'].cuda()[:, :, -1:]
|
| 275 |
+
# augment input: absolute position, relative position, velocity
|
| 276 |
+
past_traj_abs = ((data['pre_motion_3D'].cuda() - self.traj_mean)/self.traj_scale).contiguous().view(-1, 10, 2)
|
| 277 |
+
past_traj_rel = ((data['pre_motion_3D'].cuda() - initial_pos)/self.traj_scale).contiguous().view(-1, 10, 2)
|
| 278 |
+
past_traj_vel = torch.cat((past_traj_rel[:, 1:] - past_traj_rel[:, :-1], torch.zeros_like(past_traj_rel[:, -1:])), dim=1)
|
| 279 |
+
past_traj = torch.cat((past_traj_abs, past_traj_rel, past_traj_vel), dim=-1)
|
| 280 |
+
|
| 281 |
+
fut_traj = ((data['fut_motion_3D'].cuda() - initial_pos)/self.traj_scale).contiguous().view(-1, 20, 2)
|
| 282 |
+
return batch_size, traj_mask, past_traj, fut_traj
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
def _train_single_epoch(self, epoch):
|
| 286 |
+
|
| 287 |
+
self.model.train()
|
| 288 |
+
self.model_initializer.train()
|
| 289 |
+
loss_total, loss_dt, loss_dc, count = 0, 0, 0, 0
|
| 290 |
+
|
| 291 |
+
for data in self.train_loader:
|
| 292 |
+
batch_size, traj_mask, past_traj, fut_traj = self.data_preprocess(data)
|
| 293 |
+
|
| 294 |
+
sample_prediction, mean_estimation, variance_estimation = self.model_initializer(past_traj, traj_mask)
|
| 295 |
+
sample_prediction = torch.exp(variance_estimation/2)[..., None, None] * sample_prediction / sample_prediction.std(dim=1).mean(dim=(1, 2))[:, None, None, None]
|
| 296 |
+
loc = sample_prediction + mean_estimation[:, None]
|
| 297 |
+
|
| 298 |
+
generated_y = self.p_sample_loop_accelerate(past_traj, traj_mask, loc)
|
| 299 |
+
|
| 300 |
+
loss_dist = ( (generated_y - fut_traj.unsqueeze(dim=1)).norm(p=2, dim=-1)
|
| 301 |
+
*
|
| 302 |
+
self.temporal_reweight
|
| 303 |
+
).mean(dim=-1).min(dim=1)[0].mean()
|
| 304 |
+
loss_uncertainty = (torch.exp(-variance_estimation)
|
| 305 |
+
*
|
| 306 |
+
(generated_y - fut_traj.unsqueeze(dim=1)).norm(p=2, dim=-1).mean(dim=(1, 2))
|
| 307 |
+
+
|
| 308 |
+
variance_estimation
|
| 309 |
+
).mean()
|
| 310 |
+
|
| 311 |
+
loss = loss_dist*50 + loss_uncertainty
|
| 312 |
+
loss_total += loss.item()
|
| 313 |
+
loss_dt += loss_dist.item()*50
|
| 314 |
+
loss_dc += loss_uncertainty.item()
|
| 315 |
+
|
| 316 |
+
self.opt.zero_grad()
|
| 317 |
+
loss.backward()
|
| 318 |
+
torch.nn.utils.clip_grad_norm_(self.model_initializer.parameters(), 1.)
|
| 319 |
+
self.opt.step()
|
| 320 |
+
count += 1
|
| 321 |
+
if self.cfg.debug and count == 2:
|
| 322 |
+
break
|
| 323 |
+
|
| 324 |
+
return loss_total/count, loss_dt/count, loss_dc/count
|
| 325 |
+
|
| 326 |
+
|
| 327 |
+
def _test_single_epoch(self):
|
| 328 |
+
performance = { 'FDE': [0, 0, 0, 0],
|
| 329 |
+
'ADE': [0, 0, 0, 0]}
|
| 330 |
+
samples = 0
|
| 331 |
+
def prepare_seed(rand_seed):
|
| 332 |
+
np.random.seed(rand_seed)
|
| 333 |
+
random.seed(rand_seed)
|
| 334 |
+
torch.manual_seed(rand_seed)
|
| 335 |
+
torch.cuda.manual_seed_all(rand_seed)
|
| 336 |
+
prepare_seed(0)
|
| 337 |
+
count = 0
|
| 338 |
+
with torch.no_grad():
|
| 339 |
+
for data in self.test_loader:
|
| 340 |
+
batch_size, traj_mask, past_traj, fut_traj = self.data_preprocess(data)
|
| 341 |
+
|
| 342 |
+
sample_prediction, mean_estimation, variance_estimation = self.model_initializer(past_traj, traj_mask)
|
| 343 |
+
sample_prediction = torch.exp(variance_estimation/2)[..., None, None] * sample_prediction / sample_prediction.std(dim=1).mean(dim=(1, 2))[:, None, None, None]
|
| 344 |
+
loc = sample_prediction + mean_estimation[:, None]
|
| 345 |
+
|
| 346 |
+
pred_traj = self.p_sample_loop_accelerate(past_traj, traj_mask, loc)
|
| 347 |
+
|
| 348 |
+
fut_traj = fut_traj.unsqueeze(1).repeat(1, 20, 1, 1)
|
| 349 |
+
# b*n, K, T, 2
|
| 350 |
+
distances = torch.norm(fut_traj - pred_traj, dim=-1) * self.traj_scale
|
| 351 |
+
for time_i in range(1, 5):
|
| 352 |
+
ade = (distances[:, :, :5*time_i]).mean(dim=-1).min(dim=-1)[0].sum()
|
| 353 |
+
fde = (distances[:, :, 5*time_i-1]).min(dim=-1)[0].sum()
|
| 354 |
+
performance['ADE'][time_i-1] += ade.item()
|
| 355 |
+
performance['FDE'][time_i-1] += fde.item()
|
| 356 |
+
samples += distances.shape[0]
|
| 357 |
+
count += 1
|
| 358 |
+
# if count==100:
|
| 359 |
+
# break
|
| 360 |
+
return performance, samples
|
| 361 |
+
|
| 362 |
+
|
| 363 |
+
def save_data(self):
|
| 364 |
+
'''
|
| 365 |
+
Save the visualization data.
|
| 366 |
+
'''
|
| 367 |
+
model_path = './results/checkpoints/led_vis.p'
|
| 368 |
+
model_dict = torch.load(model_path, map_location=torch.device('cpu'))['model_initializer_dict']
|
| 369 |
+
self.model_initializer.load_state_dict(model_dict)
|
| 370 |
+
def prepare_seed(rand_seed):
|
| 371 |
+
np.random.seed(rand_seed)
|
| 372 |
+
random.seed(rand_seed)
|
| 373 |
+
torch.manual_seed(rand_seed)
|
| 374 |
+
torch.cuda.manual_seed_all(rand_seed)
|
| 375 |
+
prepare_seed(0)
|
| 376 |
+
root_path = './visualization/data/'
|
| 377 |
+
|
| 378 |
+
with torch.no_grad():
|
| 379 |
+
for data in self.test_loader:
|
| 380 |
+
_, traj_mask, past_traj, _ = self.data_preprocess(data)
|
| 381 |
+
|
| 382 |
+
sample_prediction, mean_estimation, variance_estimation = self.model_initializer(past_traj, traj_mask)
|
| 383 |
+
torch.save(sample_prediction, root_path+'p_var.pt')
|
| 384 |
+
torch.save(mean_estimation, root_path+'p_mean.pt')
|
| 385 |
+
torch.save(variance_estimation, root_path+'p_sigma.pt')
|
| 386 |
+
|
| 387 |
+
sample_prediction = torch.exp(variance_estimation/2)[..., None, None] * sample_prediction / sample_prediction.std(dim=1).mean(dim=(1, 2))[:, None, None, None]
|
| 388 |
+
loc = sample_prediction + mean_estimation[:, None]
|
| 389 |
+
|
| 390 |
+
pred_traj = self.p_sample_loop_accelerate(past_traj, traj_mask, loc)
|
| 391 |
+
pred_mean = self.p_sample_loop_mean(past_traj, traj_mask, mean_estimation)
|
| 392 |
+
|
| 393 |
+
torch.save(data['pre_motion_3D'], root_path+'past.pt')
|
| 394 |
+
torch.save(data['fut_motion_3D'], root_path+'future.pt')
|
| 395 |
+
torch.save(pred_traj, root_path+'prediction.pt')
|
| 396 |
+
torch.save(pred_mean, root_path+'p_mean_denoise.pt')
|
| 397 |
+
|
| 398 |
+
raise ValueError
|
| 399 |
+
|
| 400 |
+
|
| 401 |
+
|
| 402 |
+
def test_single_model(self):
|
| 403 |
+
model_path = './results/checkpoints/led_new.p'
|
| 404 |
+
model_dict = torch.load(model_path, map_location=torch.device('cpu'))['model_initializer_dict']
|
| 405 |
+
self.model_initializer.load_state_dict(model_dict)
|
| 406 |
+
performance = { 'FDE': [0, 0, 0, 0],
|
| 407 |
+
'ADE': [0, 0, 0, 0]}
|
| 408 |
+
samples = 0
|
| 409 |
+
print_log(model_path, log=self.log)
|
| 410 |
+
def prepare_seed(rand_seed):
|
| 411 |
+
np.random.seed(rand_seed)
|
| 412 |
+
random.seed(rand_seed)
|
| 413 |
+
torch.manual_seed(rand_seed)
|
| 414 |
+
torch.cuda.manual_seed_all(rand_seed)
|
| 415 |
+
prepare_seed(0)
|
| 416 |
+
count = 0
|
| 417 |
+
with torch.no_grad():
|
| 418 |
+
for data in self.test_loader:
|
| 419 |
+
batch_size, traj_mask, past_traj, fut_traj = self.data_preprocess(data)
|
| 420 |
+
|
| 421 |
+
sample_prediction, mean_estimation, variance_estimation = self.model_initializer(past_traj, traj_mask)
|
| 422 |
+
sample_prediction = torch.exp(variance_estimation/2)[..., None, None] * sample_prediction / sample_prediction.std(dim=1).mean(dim=(1, 2))[:, None, None, None]
|
| 423 |
+
loc = sample_prediction + mean_estimation[:, None]
|
| 424 |
+
|
| 425 |
+
pred_traj = self.p_sample_loop_accelerate(past_traj, traj_mask, loc)
|
| 426 |
+
|
| 427 |
+
fut_traj = fut_traj.unsqueeze(1).repeat(1, 20, 1, 1)
|
| 428 |
+
# b*n, K, T, 2
|
| 429 |
+
distances = torch.norm(fut_traj - pred_traj, dim=-1) * self.traj_scale
|
| 430 |
+
for time_i in range(1, 5):
|
| 431 |
+
ade = (distances[:, :, :5*time_i]).mean(dim=-1).min(dim=-1)[0].sum()
|
| 432 |
+
fde = (distances[:, :, 5*time_i-1]).min(dim=-1)[0].sum()
|
| 433 |
+
performance['ADE'][time_i-1] += ade.item()
|
| 434 |
+
performance['FDE'][time_i-1] += fde.item()
|
| 435 |
+
samples += distances.shape[0]
|
| 436 |
+
count += 1
|
| 437 |
+
# if count==2:
|
| 438 |
+
# break
|
| 439 |
+
for time_i in range(4):
|
| 440 |
+
print_log('--ADE({}s): {:.4f}\t--FDE({}s): {:.4f}'.format(time_i+1, performance['ADE'][time_i]/samples, \
|
| 441 |
+
time_i+1, performance['FDE'][time_i]/samples), log=self.log)
|
| 442 |
+
|
| 443 |
+
|
LED/LED/utils/config.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import yaml
|
| 2 |
+
import os
|
| 3 |
+
import os.path as osp
|
| 4 |
+
import glob
|
| 5 |
+
import numpy as np
|
| 6 |
+
from easydict import EasyDict
|
| 7 |
+
from utils.utils import recreate_dirs
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class Config:
|
| 11 |
+
|
| 12 |
+
def __init__(self, cfg_id, info):
|
| 13 |
+
self.id = cfg_id
|
| 14 |
+
cfg_path = 'cfg/**/%s.yml' % cfg_id
|
| 15 |
+
files = glob.glob(cfg_path, recursive=True)
|
| 16 |
+
assert (len(files) == 1), 'YAML file [{}] does not exist!'.format(cfg_id)
|
| 17 |
+
self.yml_dict = EasyDict(yaml.safe_load(open(files[0], 'r')))
|
| 18 |
+
|
| 19 |
+
self.results_root_dir = os.path.expanduser(self.yml_dict['results_root_dir'])
|
| 20 |
+
# results dirs
|
| 21 |
+
|
| 22 |
+
self.cfg_dir = '%s/%s/%s' % (self.results_root_dir, cfg_id, info)
|
| 23 |
+
self.model_dir = '%s/models' % self.cfg_dir
|
| 24 |
+
self.log_dir = '%s/log' % self.cfg_dir
|
| 25 |
+
self.model_path = os.path.join(self.model_dir, 'model_%04d.p')
|
| 26 |
+
os.makedirs(self.model_dir, exist_ok=True)
|
| 27 |
+
os.makedirs(self.log_dir, exist_ok=True)
|
| 28 |
+
|
| 29 |
+
def get_last_epoch(self):
|
| 30 |
+
model_files = glob.glob(os.path.join(self.model_dir, 'model_*.p'))
|
| 31 |
+
if len(model_files) == 0:
|
| 32 |
+
return None
|
| 33 |
+
else:
|
| 34 |
+
model_file = osp.basename(model_files[0])
|
| 35 |
+
epoch = int(osp.splitext(model_file)[0].split('model_')[-1])
|
| 36 |
+
return epoch
|
| 37 |
+
|
| 38 |
+
def __getattribute__(self, name):
|
| 39 |
+
yml_dict = super().__getattribute__('yml_dict')
|
| 40 |
+
if name in yml_dict:
|
| 41 |
+
return yml_dict[name]
|
| 42 |
+
else:
|
| 43 |
+
return super().__getattribute__(name)
|
| 44 |
+
|
| 45 |
+
def __setattr__(self, name, value):
|
| 46 |
+
try:
|
| 47 |
+
yml_dict = super().__getattribute__('yml_dict')
|
| 48 |
+
except AttributeError:
|
| 49 |
+
return super().__setattr__(name, value)
|
| 50 |
+
if name in yml_dict:
|
| 51 |
+
yml_dict[name] = value
|
| 52 |
+
else:
|
| 53 |
+
return super().__setattr__(name, value)
|
| 54 |
+
|
| 55 |
+
def get(self, name, default=None):
|
| 56 |
+
if hasattr(self, name):
|
| 57 |
+
return getattr(self, name)
|
| 58 |
+
else:
|
| 59 |
+
return default
|
| 60 |
+
|
LED/LED/utils/utils.py
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Code borrowed from Xinshuo_PyToolbox: https://github.com/xinshuoweng/Xinshuo_PyToolbox
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import os
|
| 6 |
+
import shutil
|
| 7 |
+
import torch
|
| 8 |
+
import numpy as np
|
| 9 |
+
import random
|
| 10 |
+
import time
|
| 11 |
+
import copy
|
| 12 |
+
import glob, glob2
|
| 13 |
+
from torch import nn
|
| 14 |
+
|
| 15 |
+
class AverageMeter(object):
|
| 16 |
+
"""Computes and stores the average and current value"""
|
| 17 |
+
def __init__(self):
|
| 18 |
+
self.reset()
|
| 19 |
+
|
| 20 |
+
def reset(self):
|
| 21 |
+
self.val = 0
|
| 22 |
+
self.avg = 0
|
| 23 |
+
self.sum = 0
|
| 24 |
+
self.count = 0
|
| 25 |
+
self.list = list()
|
| 26 |
+
|
| 27 |
+
def update(self, val, n=1):
|
| 28 |
+
self.val = val
|
| 29 |
+
self.sum += val * n
|
| 30 |
+
self.count += n
|
| 31 |
+
self.avg = self.sum / self.count
|
| 32 |
+
self.list.append(val)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def isnparray(nparray_test):
|
| 36 |
+
return isinstance(nparray_test, np.ndarray)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def isinteger(integer_test):
|
| 40 |
+
if isnparray(integer_test): return False
|
| 41 |
+
try: return isinstance(integer_test, int) or int(integer_test) == integer_test
|
| 42 |
+
except ValueError: return False
|
| 43 |
+
except TypeError: return False
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def isfloat(float_test):
|
| 47 |
+
return isinstance(float_test, float)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def isscalar(scalar_test):
|
| 51 |
+
try: return isinteger(scalar_test) or isfloat(scalar_test)
|
| 52 |
+
except TypeError: return False
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def islogical(logical_test):
|
| 56 |
+
return isinstance(logical_test, bool)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def isstring(string_test):
|
| 60 |
+
return isinstance(string_test, str)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def islist(list_test):
|
| 64 |
+
return isinstance(list_test, list)
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def convert_secs2time(seconds):
|
| 68 |
+
'''
|
| 69 |
+
format second to human readable way
|
| 70 |
+
'''
|
| 71 |
+
assert isscalar(seconds), 'input should be a scalar to represent number of seconds'
|
| 72 |
+
m, s = divmod(int(seconds), 60)
|
| 73 |
+
h, m = divmod(m, 60)
|
| 74 |
+
return '[%d:%02d:%02d]' % (h, m, s)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def get_timestring():
|
| 78 |
+
return time.strftime('%Y%m%d_%Hh%Mm%Ss')
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def recreate_dirs(*dirs):
|
| 82 |
+
for d in dirs:
|
| 83 |
+
if os.path.exists(d):
|
| 84 |
+
shutil.rmtree(d)
|
| 85 |
+
os.makedirs(d)
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def is_path_valid(pathname):
|
| 89 |
+
try:
|
| 90 |
+
if not isstring(pathname) or not pathname: return False
|
| 91 |
+
except TypeError: return False
|
| 92 |
+
else: return True
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def is_path_creatable(pathname):
|
| 96 |
+
'''
|
| 97 |
+
if any previous level of parent folder exists, returns true
|
| 98 |
+
'''
|
| 99 |
+
if not is_path_valid(pathname): return False
|
| 100 |
+
pathname = os.path.normpath(pathname)
|
| 101 |
+
pathname = os.path.dirname(os.path.abspath(pathname))
|
| 102 |
+
|
| 103 |
+
# recursively to find the previous level of parent folder existing
|
| 104 |
+
while not is_path_exists(pathname):
|
| 105 |
+
pathname_new = os.path.dirname(os.path.abspath(pathname))
|
| 106 |
+
if pathname_new == pathname: return False
|
| 107 |
+
pathname = pathname_new
|
| 108 |
+
return os.access(pathname, os.W_OK)
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def is_path_exists(pathname):
|
| 112 |
+
try: return is_path_valid(pathname) and os.path.exists(pathname)
|
| 113 |
+
except OSError: return False
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def is_path_exists_or_creatable(pathname):
|
| 117 |
+
try: return is_path_exists(pathname) or is_path_creatable(pathname)
|
| 118 |
+
except OSError: return False
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def isfile(pathname):
|
| 122 |
+
if is_path_valid(pathname):
|
| 123 |
+
pathname = os.path.normpath(pathname)
|
| 124 |
+
name = os.path.splitext(os.path.basename(pathname))[0]
|
| 125 |
+
ext = os.path.splitext(pathname)[1]
|
| 126 |
+
return len(name) > 0 and len(ext) > 0
|
| 127 |
+
else: return False
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def isfolder(pathname):
|
| 131 |
+
'''
|
| 132 |
+
if '.' exists in the subfolder, the function still justifies it as a folder. e.g., /mnt/dome/adhoc_0.5x/abc is a folder
|
| 133 |
+
if '.' exists after all slashes, the function will not justify is as a folder. e.g., /mnt/dome/adhoc_0.5x is NOT a folder
|
| 134 |
+
'''
|
| 135 |
+
if is_path_valid(pathname):
|
| 136 |
+
pathname = os.path.normpath(pathname)
|
| 137 |
+
if pathname == './': return True
|
| 138 |
+
name = os.path.splitext(os.path.basename(pathname))[0]
|
| 139 |
+
ext = os.path.splitext(pathname)[1]
|
| 140 |
+
return len(name) > 0 and len(ext) == 0
|
| 141 |
+
else: return False
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def mkdir_if_missing(input_path):
|
| 145 |
+
folder = input_path if isfolder(input_path) else os.path.dirname(input_path)
|
| 146 |
+
os.makedirs(folder, exist_ok=True)
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
def safe_list(input_data, warning=True, debug=True):
|
| 150 |
+
'''
|
| 151 |
+
copy a list to the buffer for use
|
| 152 |
+
parameters:
|
| 153 |
+
input_data: a list
|
| 154 |
+
outputs:
|
| 155 |
+
safe_data: a copy of input data
|
| 156 |
+
'''
|
| 157 |
+
if debug: assert islist(input_data), 'the input data is not a list'
|
| 158 |
+
safe_data = copy.copy(input_data)
|
| 159 |
+
return safe_data
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
def safe_path(input_path, warning=True, debug=True):
|
| 163 |
+
'''
|
| 164 |
+
convert path to a valid OS format, e.g., empty string '' to '.', remove redundant '/' at the end from 'aa/' to 'aa'
|
| 165 |
+
parameters:
|
| 166 |
+
input_path: a string
|
| 167 |
+
outputs:
|
| 168 |
+
safe_data: a valid path in OS format
|
| 169 |
+
'''
|
| 170 |
+
if debug: assert isstring(input_path), 'path is not a string: %s' % input_path
|
| 171 |
+
safe_data = copy.copy(input_path)
|
| 172 |
+
safe_data = os.path.normpath(safe_data)
|
| 173 |
+
return safe_data
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def prepare_seed(rand_seed):
|
| 177 |
+
np.random.seed(rand_seed)
|
| 178 |
+
random.seed(rand_seed)
|
| 179 |
+
torch.manual_seed(rand_seed)
|
| 180 |
+
torch.cuda.manual_seed_all(rand_seed)
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
def initialize_weights(modules):
|
| 184 |
+
for m in modules:
|
| 185 |
+
if isinstance(m, nn.Conv2d):
|
| 186 |
+
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
|
| 187 |
+
if m.bias is not None: nn.init.constant_(m.bias, 0)
|
| 188 |
+
elif isinstance(m, nn.BatchNorm2d):
|
| 189 |
+
nn.init.constant_(m.weight, 1)
|
| 190 |
+
if m.bias is not None: nn.init.constant_(m.bias, 0)
|
| 191 |
+
elif isinstance(m, nn.Linear):
|
| 192 |
+
nn.init.normal_(m.weight, 0, 0.01)
|
| 193 |
+
if m.bias is not None: nn.init.constant_(m.bias, 0)
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
def print_log(print_str, log, same_line=False, display=True):
|
| 197 |
+
'''
|
| 198 |
+
print a string to a log file
|
| 199 |
+
|
| 200 |
+
parameters:
|
| 201 |
+
print_str: a string to print
|
| 202 |
+
log: a opened file to save the log
|
| 203 |
+
same_line: True if we want to print the string without a new next line
|
| 204 |
+
display: False if we want to disable to print the string onto the terminal
|
| 205 |
+
'''
|
| 206 |
+
if display:
|
| 207 |
+
if same_line: print('{}'.format(print_str), end='')
|
| 208 |
+
else: print('{}'.format(print_str))
|
| 209 |
+
|
| 210 |
+
if same_line: log.write('{}'.format(print_str))
|
| 211 |
+
else: log.write('{}\n'.format(print_str))
|
| 212 |
+
log.flush()
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
def find_unique_common_from_lists(input_list1, input_list2, warning=True, debug=True):
|
| 216 |
+
'''
|
| 217 |
+
find common items from 2 lists, the returned elements are unique. repetitive items will be ignored
|
| 218 |
+
if the common items in two elements are not in the same order, the outputs follows the order in the first list
|
| 219 |
+
|
| 220 |
+
parameters:
|
| 221 |
+
input_list1, input_list2: two input lists
|
| 222 |
+
|
| 223 |
+
outputs:
|
| 224 |
+
list_common: a list of elements existing both in list_src1 and list_src2
|
| 225 |
+
index_list1: a list of index that list 1 has common items
|
| 226 |
+
index_list2: a list of index that list 2 has common items
|
| 227 |
+
'''
|
| 228 |
+
input_list1 = safe_list(input_list1, warning=warning, debug=debug)
|
| 229 |
+
input_list2 = safe_list(input_list2, warning=warning, debug=debug)
|
| 230 |
+
|
| 231 |
+
common_list = list(set(input_list1).intersection(input_list2))
|
| 232 |
+
|
| 233 |
+
# find index
|
| 234 |
+
index_list1 = []
|
| 235 |
+
for index in range(len(input_list1)):
|
| 236 |
+
item = input_list1[index]
|
| 237 |
+
if item in common_list:
|
| 238 |
+
index_list1.append(index)
|
| 239 |
+
|
| 240 |
+
index_list2 = []
|
| 241 |
+
for index in range(len(input_list2)):
|
| 242 |
+
item = input_list2[index]
|
| 243 |
+
if item in common_list:
|
| 244 |
+
index_list2.append(index)
|
| 245 |
+
|
| 246 |
+
return common_list, index_list1, index_list2
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
def load_txt_file(file_path, debug=True):
|
| 250 |
+
'''
|
| 251 |
+
load data or string from text file
|
| 252 |
+
'''
|
| 253 |
+
file_path = safe_path(file_path)
|
| 254 |
+
if debug: assert is_path_exists(file_path), 'text file is not existing at path: %s!' % file_path
|
| 255 |
+
with open(file_path, 'r') as file: data = file.read().splitlines()
|
| 256 |
+
num_lines = len(data)
|
| 257 |
+
file.close()
|
| 258 |
+
return data, num_lines
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
def load_list_from_folder(folder_path, ext_filter=None, depth=1, recursive=False, sort=True, save_path=None, debug=True):
|
| 262 |
+
'''
|
| 263 |
+
load a list of files or folders from a system path
|
| 264 |
+
|
| 265 |
+
parameters:
|
| 266 |
+
folder_path: root to search
|
| 267 |
+
ext_filter: a string to represent the extension of files interested
|
| 268 |
+
depth: maximum depth of folder to search, when it's None, all levels of folders will be searched
|
| 269 |
+
recursive: False: only return current level
|
| 270 |
+
True: return all levels till to the input depth
|
| 271 |
+
|
| 272 |
+
outputs:
|
| 273 |
+
fulllist: a list of elements
|
| 274 |
+
num_elem: number of the elements
|
| 275 |
+
'''
|
| 276 |
+
folder_path = safe_path(folder_path)
|
| 277 |
+
if debug: assert isfolder(folder_path), 'input folder path is not correct: %s' % folder_path
|
| 278 |
+
if not is_path_exists(folder_path):
|
| 279 |
+
print('the input folder does not exist\n')
|
| 280 |
+
return [], 0
|
| 281 |
+
if debug:
|
| 282 |
+
assert islogical(recursive), 'recursive should be a logical variable: {}'.format(recursive)
|
| 283 |
+
assert depth is None or (isinteger(depth) and depth >= 1), 'input depth is not correct {}'.format(depth)
|
| 284 |
+
assert ext_filter is None or (islist(ext_filter) and all(isstring(ext_tmp) for ext_tmp in ext_filter)) or isstring(ext_filter), 'extension filter is not correct'
|
| 285 |
+
if isstring(ext_filter): ext_filter = [ext_filter] # convert to a list
|
| 286 |
+
# zxc
|
| 287 |
+
|
| 288 |
+
fulllist = list()
|
| 289 |
+
if depth is None: # find all files recursively
|
| 290 |
+
recursive = True
|
| 291 |
+
wildcard_prefix = '**'
|
| 292 |
+
if ext_filter is not None:
|
| 293 |
+
for ext_tmp in ext_filter:
|
| 294 |
+
# wildcard = os.path.join(wildcard_prefix, '*' + string2ext_filter(ext_tmp))
|
| 295 |
+
wildcard = os.path.join(wildcard_prefix, '*' + ext_tmp)
|
| 296 |
+
curlist = glob2.glob(os.path.join(folder_path, wildcard))
|
| 297 |
+
if sort: curlist = sorted(curlist)
|
| 298 |
+
fulllist += curlist
|
| 299 |
+
else:
|
| 300 |
+
wildcard = wildcard_prefix
|
| 301 |
+
curlist = glob2.glob(os.path.join(folder_path, wildcard))
|
| 302 |
+
if sort: curlist = sorted(curlist)
|
| 303 |
+
fulllist += curlist
|
| 304 |
+
else: # find files based on depth and recursive flag
|
| 305 |
+
wildcard_prefix = '*'
|
| 306 |
+
for index in range(depth-1): wildcard_prefix = os.path.join(wildcard_prefix, '*')
|
| 307 |
+
if ext_filter is not None:
|
| 308 |
+
for ext_tmp in ext_filter:
|
| 309 |
+
# wildcard = wildcard_prefix + string2ext_filter(ext_tmp)
|
| 310 |
+
wildcard = wildcard_prefix + ext_tmp
|
| 311 |
+
curlist = glob.glob(os.path.join(folder_path, wildcard))
|
| 312 |
+
if sort: curlist = sorted(curlist)
|
| 313 |
+
fulllist += curlist
|
| 314 |
+
# zxc
|
| 315 |
+
else:
|
| 316 |
+
wildcard = wildcard_prefix
|
| 317 |
+
curlist = glob.glob(os.path.join(folder_path, wildcard))
|
| 318 |
+
# print(curlist)
|
| 319 |
+
if sort: curlist = sorted(curlist)
|
| 320 |
+
fulllist += curlist
|
| 321 |
+
if recursive and depth > 1:
|
| 322 |
+
newlist, _ = load_list_from_folder(folder_path=folder_path, ext_filter=ext_filter, depth=depth-1, recursive=True)
|
| 323 |
+
fulllist += newlist
|
| 324 |
+
|
| 325 |
+
fulllist = [os.path.normpath(path_tmp) for path_tmp in fulllist]
|
| 326 |
+
num_elem = len(fulllist)
|
| 327 |
+
|
| 328 |
+
# save list to a path
|
| 329 |
+
if save_path is not None:
|
| 330 |
+
save_path = safe_path(save_path)
|
| 331 |
+
if debug: assert is_path_exists_or_creatable(save_path), 'the file cannot be created'
|
| 332 |
+
with open(save_path, 'w') as file:
|
| 333 |
+
for item in fulllist: file.write('%s\n' % item)
|
| 334 |
+
file.close()
|
| 335 |
+
|
| 336 |
+
return fulllist, num_elem
|
LED/cfg/nba/led_augment.yml
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ------------------- General Options -------------------------
|
| 2 |
+
description : LED
|
| 3 |
+
results_root_dir : results
|
| 4 |
+
dataset : nba
|
| 5 |
+
|
| 6 |
+
# ------------------- Dataset -------------------------
|
| 7 |
+
past_frames : 10
|
| 8 |
+
future_frames : 20
|
| 9 |
+
min_past_frames : 10
|
| 10 |
+
min_future_frames : 20
|
| 11 |
+
|
| 12 |
+
motion_dim : 2
|
| 13 |
+
forecast_dim : 2
|
| 14 |
+
|
| 15 |
+
traj_mean : [14, 7.5]
|
| 16 |
+
traj_scale : 5
|
| 17 |
+
|
| 18 |
+
# ------------------- Model -------------------------
|
| 19 |
+
pretrained_core_denoising_model: './results/checkpoints/base_diffusion_model.p'
|
| 20 |
+
debug : False # set to True for early stop in each epoch.
|
| 21 |
+
|
| 22 |
+
diffusion : {
|
| 23 |
+
steps : 100,
|
| 24 |
+
beta_start : 1.e-4,
|
| 25 |
+
beta_end : 5.e-2,
|
| 26 |
+
beta_schedule : 'linear'
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
# ------------------- Training Parameters -------------------------
|
| 30 |
+
lr : 1.e-3
|
| 31 |
+
train_batch_size : 10
|
| 32 |
+
test_batch_size : 500
|
| 33 |
+
num_epochs : 100
|
| 34 |
+
|
| 35 |
+
lr_scheduler : 'step'
|
| 36 |
+
decay_step : 8
|
| 37 |
+
decay_gamma : 0.5
|
| 38 |
+
test_interval : 4
|
| 39 |
+
|
LED/cfg/nba/led_augment_debug.yml
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ------------------- General Options -------------------------
|
| 2 |
+
description : LED
|
| 3 |
+
results_root_dir : results
|
| 4 |
+
dataset : nba
|
| 5 |
+
|
| 6 |
+
# ------------------- Dataset -------------------------
|
| 7 |
+
past_frames : 10
|
| 8 |
+
future_frames : 20
|
| 9 |
+
min_past_frames : 10
|
| 10 |
+
min_future_frames : 20
|
| 11 |
+
|
| 12 |
+
motion_dim : 2
|
| 13 |
+
forecast_dim : 2
|
| 14 |
+
|
| 15 |
+
traj_mean : [14, 7.5]
|
| 16 |
+
traj_scale : 5
|
| 17 |
+
|
| 18 |
+
# ------------------- Model -------------------------
|
| 19 |
+
pretrained_core_denoising_model: './results/checkpoints/base_diffusion_model.p'
|
| 20 |
+
debug : True # set to True for early stop in each epoch.
|
| 21 |
+
|
| 22 |
+
diffusion : {
|
| 23 |
+
steps : 100,
|
| 24 |
+
beta_start : 1.e-4,
|
| 25 |
+
beta_end : 5.e-2,
|
| 26 |
+
beta_schedule : 'linear'
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
# ------------------- Training Parameters -------------------------
|
| 30 |
+
lr : 1.e-3
|
| 31 |
+
train_batch_size : 2
|
| 32 |
+
test_batch_size : 5
|
| 33 |
+
num_epochs : 100
|
| 34 |
+
test_interval : 2
|
| 35 |
+
lr_scheduler : 'step'
|
| 36 |
+
decay_step : 8
|
| 37 |
+
decay_gamma : 0.5
|
| 38 |
+
|
LED/cfg/sdd/sdd.yml
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
description : LED — SDD (Stanford Drone Dataset)
|
| 2 |
+
results_root_dir : results_sdd
|
| 3 |
+
dataset : sdd
|
| 4 |
+
past_frames : 8
|
| 5 |
+
future_frames : 12
|
| 6 |
+
motion_dim : 2
|
| 7 |
+
forecast_dim : 2
|
| 8 |
+
|
| 9 |
+
traj_mean : [0.0, 0.0]
|
| 10 |
+
traj_scale : 1.0
|
| 11 |
+
per_scene_norm : false
|
| 12 |
+
|
| 13 |
+
pretrained_core_denoising_model : './results_sdd/checkpoints/base_diffusion_model_sdd.p'
|
| 14 |
+
|
| 15 |
+
diffusion:
|
| 16 |
+
steps : 100
|
| 17 |
+
beta_start : 1.e-4
|
| 18 |
+
beta_end : 5.e-2
|
| 19 |
+
beta_schedule : 'linear'
|
| 20 |
+
|
| 21 |
+
train_batch_size : 32
|
| 22 |
+
test_batch_size : 256
|
| 23 |
+
num_epochs : 100
|
| 24 |
+
decay_step : 8
|
| 25 |
+
decay_gamma : 0.5
|
| 26 |
+
test_interval : 4
|
| 27 |
+
|
| 28 |
+
pretrain:
|
| 29 |
+
num_epochs : 150
|
| 30 |
+
lr : 1.e-3
|
| 31 |
+
train_batch_size : 64
|
| 32 |
+
test_batch_size : 256
|
| 33 |
+
decay_step : 30
|
| 34 |
+
decay_gamma : 0.5
|
LED/cfg/sport/football.yml
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ------------------- General Options -------------------------
|
| 2 |
+
description : LED-Sport-Football
|
| 3 |
+
results_root_dir : results
|
| 4 |
+
dataset : football
|
| 5 |
+
|
| 6 |
+
# ------------------- Dataset -------------------------
|
| 7 |
+
data_dir : '/mnt/jaewoo4tb/srtp/srtp/raw_data/football'
|
| 8 |
+
num_agents : 23
|
| 9 |
+
past_frames : 10
|
| 10 |
+
future_frames : 20
|
| 11 |
+
min_past_frames : 10
|
| 12 |
+
min_future_frames : 20
|
| 13 |
+
|
| 14 |
+
motion_dim : 2
|
| 15 |
+
forecast_dim : 2
|
| 16 |
+
|
| 17 |
+
# normalization (computed from football/train.npy)
|
| 18 |
+
traj_mean : [-4.458, -2.909]
|
| 19 |
+
traj_scale : 7.2
|
| 20 |
+
|
| 21 |
+
# ------------------- Model -------------------------
|
| 22 |
+
pretrained_core_denoising_model: './results/checkpoints/base_diffusion_model_football.p'
|
| 23 |
+
debug : False
|
| 24 |
+
|
| 25 |
+
diffusion : {
|
| 26 |
+
steps : 100,
|
| 27 |
+
beta_start : 1.e-4,
|
| 28 |
+
beta_end : 5.e-2,
|
| 29 |
+
beta_schedule : 'linear'
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
# ------------------- Training Parameters -------------------------
|
| 33 |
+
# train_batch_size must keep B*A*K_half ≤ ~1500 to fit in 48GB with the
|
| 34 |
+
# 5-step leapfrog backprop chain; NBA used 10*11*10=1100. With A=23, a
|
| 35 |
+
# batch of 6 gives 6*23*10=1380 — slightly under the NBA footprint.
|
| 36 |
+
lr : 1.e-3
|
| 37 |
+
train_batch_size : 6
|
| 38 |
+
test_batch_size : 200
|
| 39 |
+
num_epochs : 100
|
| 40 |
+
|
| 41 |
+
lr_scheduler : 'step'
|
| 42 |
+
decay_step : 8
|
| 43 |
+
decay_gamma : 0.5
|
| 44 |
+
test_interval : 4
|
| 45 |
+
|
| 46 |
+
# ------------------- Stage-1 Pretraining Parameters -------------------------
|
| 47 |
+
pretrain : {
|
| 48 |
+
num_epochs : 100,
|
| 49 |
+
lr : 1.e-3,
|
| 50 |
+
train_batch_size : 64,
|
| 51 |
+
decay_step : 10,
|
| 52 |
+
decay_gamma : 0.8,
|
| 53 |
+
}
|
LED/cfg/sport/soccer.yml
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ------------------- General Options -------------------------
|
| 2 |
+
description : LED-Sport-Soccer
|
| 3 |
+
results_root_dir : results
|
| 4 |
+
dataset : soccer
|
| 5 |
+
|
| 6 |
+
# ------------------- Dataset -------------------------
|
| 7 |
+
data_dir : '/mnt/jaewoo4tb/srtp/srtp/raw_data/soccer'
|
| 8 |
+
num_agents : 23
|
| 9 |
+
past_frames : 10
|
| 10 |
+
future_frames : 20
|
| 11 |
+
min_past_frames : 10
|
| 12 |
+
min_future_frames : 20
|
| 13 |
+
|
| 14 |
+
motion_dim : 2
|
| 15 |
+
forecast_dim : 2
|
| 16 |
+
|
| 17 |
+
# normalization (computed from soccer/train.npy)
|
| 18 |
+
traj_mean : [-0.726, -0.278]
|
| 19 |
+
traj_scale : 5.0
|
| 20 |
+
per_scene_norm : True
|
| 21 |
+
|
| 22 |
+
# ------------------- Model -------------------------
|
| 23 |
+
pretrained_core_denoising_model: './results/checkpoints/base_diffusion_model_soccer_psnorm.p'
|
| 24 |
+
debug : False
|
| 25 |
+
|
| 26 |
+
diffusion : {
|
| 27 |
+
steps : 100,
|
| 28 |
+
beta_start : 1.e-4,
|
| 29 |
+
beta_end : 5.e-2,
|
| 30 |
+
beta_schedule : 'linear'
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
# ------------------- Training Parameters -------------------------
|
| 34 |
+
# train_batch_size must keep B*A*K_half ≤ ~1500 to fit in 48GB with the
|
| 35 |
+
# 5-step leapfrog backprop chain; NBA used 10*11*10=1100. With A=23, a
|
| 36 |
+
# batch of 6 gives 6*23*10=1380 — slightly under the NBA footprint.
|
| 37 |
+
# num_epochs lowered (from 100) because soccer has only 7.2K train scenes
|
| 38 |
+
# and overfit hard in the first run; test_interval=1 to catch the peak.
|
| 39 |
+
lr : 1.e-3
|
| 40 |
+
train_batch_size : 6
|
| 41 |
+
test_batch_size : 200
|
| 42 |
+
num_epochs : 20
|
| 43 |
+
|
| 44 |
+
lr_scheduler : 'step'
|
| 45 |
+
decay_step : 4
|
| 46 |
+
decay_gamma : 0.5
|
| 47 |
+
test_interval : 1
|
| 48 |
+
|
| 49 |
+
# ------------------- Stage-1 Pretraining Parameters -------------------------
|
| 50 |
+
pretrain : {
|
| 51 |
+
num_epochs : 150,
|
| 52 |
+
lr : 1.e-3,
|
| 53 |
+
train_batch_size : 32,
|
| 54 |
+
decay_step : 10,
|
| 55 |
+
decay_gamma : 0.8,
|
| 56 |
+
}
|
LED/eval_sdd_led_allagents.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
All-agents SDD evaluation for LED checkpoints — matches MoFlow's protocol.
|
| 3 |
+
|
| 4 |
+
Protocol (mirrors MoFlow trainer's compute_ADE_FDE):
|
| 5 |
+
For each scene with A agents, run LED sampling → pred [A, K=20, T, 2].
|
| 6 |
+
Per-horizon buckets: 1.2s (frame 3), 2.4s (6), 3.6s (9), 4.8s (12).
|
| 7 |
+
ADE_min(H) = mean_{t=1..H} ‖pred - gt‖ → min over K → sum over A agents
|
| 8 |
+
FDE_min(H) = ‖pred[H-1] - gt[H-1]‖ → min over K → sum over A agents
|
| 9 |
+
ADE_avg(H) = mean over K instead of min
|
| 10 |
+
Report in pixels (× 50).
|
| 11 |
+
|
| 12 |
+
Usage:
|
| 13 |
+
python eval_sdd_led_allagents.py --exp baseline_v2 --epoch 40
|
| 14 |
+
python eval_sdd_led_allagents.py --exp graph_sigma_v2 --epoch 28 --use_graph --use_v6_graph
|
| 15 |
+
"""
|
| 16 |
+
import argparse, os, sys, random, torch, numpy as np
|
| 17 |
+
from torch.utils.data import DataLoader
|
| 18 |
+
from data.dataloader_sdd import SDDDataset, sdd_seq_collate
|
| 19 |
+
from models.model_led_initializer import LEDInitializer as InitializationModel
|
| 20 |
+
from models.model_diffusion import TransformerDenoisingModel as CoreDenoisingModel
|
| 21 |
+
from trainer.train_sdd_led import NUM_Tau
|
| 22 |
+
from utils.config import Config
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def build_models(cfg, use_graph, use_v6_graph, ckpt_path, device):
|
| 26 |
+
model = CoreDenoisingModel(past_len=cfg.past_frames).to(device)
|
| 27 |
+
core_ckpt = torch.load(cfg.pretrained_core_denoising_model, map_location='cpu')
|
| 28 |
+
model.load_state_dict(core_ckpt['model_dict'])
|
| 29 |
+
model.eval()
|
| 30 |
+
|
| 31 |
+
init = InitializationModel(
|
| 32 |
+
t_h=cfg.past_frames, d_h=6,
|
| 33 |
+
t_f=cfg.future_frames, d_f=2, k_pred=20).to(device)
|
| 34 |
+
|
| 35 |
+
ckpt = torch.load(ckpt_path, map_location='cpu')
|
| 36 |
+
init.load_state_dict(ckpt['model_initializer_dict'])
|
| 37 |
+
init.eval()
|
| 38 |
+
|
| 39 |
+
graph = None
|
| 40 |
+
if use_graph:
|
| 41 |
+
from models.future_interaction_graph_v6 import FutureInteractionGraphV6Wrapper
|
| 42 |
+
graph = FutureInteractionGraphV6Wrapper(
|
| 43 |
+
num_agents=64, future_steps=cfg.future_frames,
|
| 44 |
+
past_steps=cfg.past_frames, past_channels=6,
|
| 45 |
+
node_dim=128, top_n=5, num_denoise_steps=NUM_Tau).to(device)
|
| 46 |
+
sd = {k: v for k, v in ckpt['interaction_graph_dict'].items()
|
| 47 |
+
if '_single_edge_index' not in k}
|
| 48 |
+
graph.load_state_dict(sd, strict=False)
|
| 49 |
+
graph.eval()
|
| 50 |
+
|
| 51 |
+
return model, init, graph
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def make_beta_schedule(n=100, start=1e-4, end=5e-2):
|
| 55 |
+
return torch.linspace(start, end, n)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def extract(a, t, x):
|
| 59 |
+
out = torch.gather(a, 0, t.to(a.device))
|
| 60 |
+
return out.reshape(t.shape[0], *([1] * (len(x.shape) - 1)))
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
@torch.no_grad()
|
| 64 |
+
def p_sample_accelerate(x, mask, cur_y, t, model, graph, use_v6_graph, sigma,
|
| 65 |
+
betas, alphas, alphas_bar_sqrt, one_minus_alphas_bar_sqrt):
|
| 66 |
+
t_tensor = torch.tensor([int(t)]).to(x.device)
|
| 67 |
+
eps_factor = ((1 - extract(alphas, t_tensor, cur_y))
|
| 68 |
+
/ extract(one_minus_alphas_bar_sqrt, t_tensor, cur_y))
|
| 69 |
+
beta = extract(betas, t_tensor.repeat(x.shape[0]), cur_y)
|
| 70 |
+
eps_theta = model.generate_accelerate(cur_y, beta, x, mask)
|
| 71 |
+
|
| 72 |
+
if graph is not None:
|
| 73 |
+
abs_t = extract(alphas_bar_sqrt, t_tensor, cur_y)
|
| 74 |
+
am1_t = extract(one_minus_alphas_bar_sqrt, t_tensor, cur_y)
|
| 75 |
+
y0_hat = (cur_y - am1_t * eps_theta) / abs_t
|
| 76 |
+
delta = graph(y0_hat, x, int(t), sigma=sigma, A_override=x.size(0))
|
| 77 |
+
eps_theta = eps_theta - (abs_t / am1_t) * delta
|
| 78 |
+
|
| 79 |
+
mean = (1 / extract(alphas, t_tensor, cur_y).sqrt()) \
|
| 80 |
+
* (cur_y - eps_factor * eps_theta)
|
| 81 |
+
z = torch.randn_like(cur_y)
|
| 82 |
+
sigma_t = extract(betas, t_tensor, cur_y).sqrt()
|
| 83 |
+
return mean + sigma_t * z * 0.00001
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
# Horizons in frames (assuming 2.5 fps → 3 = 1.2s, 6 = 2.4s, 9 = 3.6s, 12 = 4.8s)
|
| 87 |
+
HORIZON_FRAMES = {'1.2s': 3, '2.4s': 6, '3.6s': 9, '4.8s': 12}
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
@torch.no_grad()
|
| 91 |
+
def run(args):
|
| 92 |
+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 93 |
+
cfg = Config(args.cfg, args.exp)
|
| 94 |
+
|
| 95 |
+
test_dset = SDDDataset(obs_len=cfg.past_frames,
|
| 96 |
+
pred_len=cfg.future_frames, split='test')
|
| 97 |
+
loader = DataLoader(test_dset, batch_size=1, shuffle=False,
|
| 98 |
+
num_workers=2, collate_fn=sdd_seq_collate)
|
| 99 |
+
|
| 100 |
+
ckpt_path = cfg.model_path % args.epoch
|
| 101 |
+
print(f'Loading checkpoint: {ckpt_path}')
|
| 102 |
+
|
| 103 |
+
model, init, graph = build_models(
|
| 104 |
+
cfg, use_graph=args.use_graph, use_v6_graph=args.use_v6_graph,
|
| 105 |
+
ckpt_path=ckpt_path, device=device)
|
| 106 |
+
|
| 107 |
+
betas = make_beta_schedule().to(device)
|
| 108 |
+
alphas = 1 - betas
|
| 109 |
+
alphas_prod = torch.cumprod(alphas, 0)
|
| 110 |
+
abs_sqrt = torch.sqrt(alphas_prod)
|
| 111 |
+
one_minus_abs_sqrt = torch.sqrt(1 - alphas_prod)
|
| 112 |
+
|
| 113 |
+
traj_mean = torch.FloatTensor(cfg.traj_mean).to(device).view(1, 1, 1, 2)
|
| 114 |
+
traj_scale = float(cfg.traj_scale)
|
| 115 |
+
|
| 116 |
+
np.random.seed(0); random.seed(0)
|
| 117 |
+
torch.manual_seed(0); torch.cuda.manual_seed_all(0)
|
| 118 |
+
|
| 119 |
+
# Accumulators for each horizon: sums of per-agent ADE/FDE (min over K and mean over K).
|
| 120 |
+
sums = {f'{k}_{h}': 0.0
|
| 121 |
+
for h in HORIZON_FRAMES for k in ['ADE_min', 'FDE_min', 'ADE_avg', 'FDE_avg']}
|
| 122 |
+
n_agents = 0
|
| 123 |
+
T = cfg.future_frames
|
| 124 |
+
|
| 125 |
+
for data in loader:
|
| 126 |
+
pre = data['pre_motion_3D'].to(device) # [1, A, 8, 2]
|
| 127 |
+
fut = data['fut_motion_3D'].to(device) # [1, A, 12, 2]
|
| 128 |
+
A = pre.size(1)
|
| 129 |
+
initial_pos = pre[:, :, -1:]
|
| 130 |
+
past_abs = ((pre - traj_mean) / traj_scale).contiguous().view(-1, cfg.past_frames, 2)
|
| 131 |
+
past_rel = ((pre - initial_pos) / traj_scale).contiguous().view(-1, cfg.past_frames, 2)
|
| 132 |
+
past_vel = torch.cat([past_rel[:, 1:] - past_rel[:, :-1],
|
| 133 |
+
torch.zeros_like(past_rel[:, -1:])], dim=1)
|
| 134 |
+
past = torch.cat([past_abs, past_rel, past_vel], dim=-1)
|
| 135 |
+
fut_rel = ((fut - initial_pos) / traj_scale).contiguous().view(-1, T, 2)
|
| 136 |
+
mask = torch.ones(A, A).to(device)
|
| 137 |
+
|
| 138 |
+
sp, me, ve = init(past, mask)
|
| 139 |
+
ve = ve.clamp(min=-5, max=5)
|
| 140 |
+
sp = torch.exp(ve / 2)[..., None, None] * sp \
|
| 141 |
+
/ (sp.std(dim=1).mean(dim=(1, 2))[:, None, None, None] + 1e-6)
|
| 142 |
+
loc = sp + me[:, None]
|
| 143 |
+
sigma_in = ve if args.use_v6_graph else None
|
| 144 |
+
|
| 145 |
+
# leapfrog: 20 modes = 10+10 two halves, each 5 reverse steps
|
| 146 |
+
cur_y = loc[:, :10]
|
| 147 |
+
for i in reversed(range(NUM_Tau)):
|
| 148 |
+
cur_y = p_sample_accelerate(
|
| 149 |
+
past, mask, cur_y, i, model, graph, args.use_v6_graph, sigma_in,
|
| 150 |
+
betas, alphas, abs_sqrt, one_minus_abs_sqrt)
|
| 151 |
+
cur_y_ = loc[:, 10:]
|
| 152 |
+
for i in reversed(range(NUM_Tau)):
|
| 153 |
+
cur_y_ = p_sample_accelerate(
|
| 154 |
+
past, mask, cur_y_, i, model, graph, args.use_v6_graph, sigma_in,
|
| 155 |
+
betas, alphas, abs_sqrt, one_minus_abs_sqrt)
|
| 156 |
+
pred = torch.cat((cur_y_, cur_y), dim=1) # [A, K=20, T, 2]
|
| 157 |
+
|
| 158 |
+
# ALL agents in this scene, per-horizon ADE/FDE matching MoFlow
|
| 159 |
+
dist = torch.norm(pred - fut_rel.unsqueeze(1), dim=-1) * traj_scale # [A, K, T]
|
| 160 |
+
for h_name, h_end in HORIZON_FRAMES.items():
|
| 161 |
+
# min over K
|
| 162 |
+
ade_min = dist[..., :h_end].mean(dim=-1).min(dim=-1)[0] # [A]
|
| 163 |
+
fde_min = dist[..., h_end - 1].min(dim=-1)[0] # [A]
|
| 164 |
+
# avg over K (mean mode distance; for FVar/AVar later, unused here)
|
| 165 |
+
ade_avg = dist[..., :h_end].mean(dim=-1).mean(dim=-1) # [A]
|
| 166 |
+
fde_avg = dist[..., h_end - 1].mean(dim=-1) # [A]
|
| 167 |
+
sums[f'ADE_min_{h_name}'] += ade_min.sum().item()
|
| 168 |
+
sums[f'FDE_min_{h_name}'] += fde_min.sum().item()
|
| 169 |
+
sums[f'ADE_avg_{h_name}'] += ade_avg.sum().item()
|
| 170 |
+
sums[f'FDE_avg_{h_name}'] += fde_avg.sum().item()
|
| 171 |
+
n_agents += A
|
| 172 |
+
|
| 173 |
+
# Report in pixels: multiply by 50.
|
| 174 |
+
print(f'\n{args.exp} @ epoch {args.epoch} (n_agents={n_agents}, all-agents protocol)')
|
| 175 |
+
print('--- pixels ---')
|
| 176 |
+
for h in HORIZON_FRAMES:
|
| 177 |
+
am = sums[f'ADE_min_{h}'] / n_agents * 50.0
|
| 178 |
+
fm = sums[f'FDE_min_{h}'] / n_agents * 50.0
|
| 179 |
+
aa = sums[f'ADE_avg_{h}'] / n_agents * 50.0
|
| 180 |
+
fa = sums[f'FDE_avg_{h}'] / n_agents * 50.0
|
| 181 |
+
print(f' ADE_min({h})={am:8.4f} FDE_min({h})={fm:8.4f} '
|
| 182 |
+
f'ADE_avg({h})={aa:8.4f} FDE_avg({h})={fa:8.4f}')
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
if __name__ == '__main__':
|
| 186 |
+
p = argparse.ArgumentParser()
|
| 187 |
+
p.add_argument('--cfg', default='sdd/sdd')
|
| 188 |
+
p.add_argument('--exp', required=True, help='info tag, e.g. baseline_v2')
|
| 189 |
+
p.add_argument('--epoch', type=int, required=True)
|
| 190 |
+
p.add_argument('--use_graph', action='store_true')
|
| 191 |
+
p.add_argument('--use_v6_graph', action='store_true')
|
| 192 |
+
args = p.parse_args()
|
| 193 |
+
run(args)
|
LED/eval_sdd_led_mid_protocol.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Re-evaluate an LED SDD checkpoint using the MID evaluation protocol:
|
| 3 |
+
per-pedestrian full-horizon (12-frame) ADE / final-frame FDE,
|
| 4 |
+
best_of_20 per pedestrian, averaged across all target pedestrians,
|
| 5 |
+
×50 to report in pixels.
|
| 6 |
+
|
| 7 |
+
Usage:
|
| 8 |
+
python eval_sdd_led_mid_protocol.py --exp baseline_v2 --epoch 60
|
| 9 |
+
python eval_sdd_led_mid_protocol.py --exp graph_sigma_v2 --epoch 60 --use_graph
|
| 10 |
+
"""
|
| 11 |
+
import argparse, os, sys, random, torch, numpy as np
|
| 12 |
+
from torch.utils.data import DataLoader
|
| 13 |
+
from data.dataloader_sdd import SDDDataset, sdd_seq_collate
|
| 14 |
+
from models.model_led_initializer import LEDInitializer as InitializationModel
|
| 15 |
+
from models.model_diffusion import TransformerDenoisingModel as CoreDenoisingModel
|
| 16 |
+
from trainer.train_sdd_led import NUM_Tau
|
| 17 |
+
from utils.config import Config
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def build_models(cfg, use_graph, use_v6_graph, ckpt_path, device):
|
| 21 |
+
model = CoreDenoisingModel(past_len=cfg.past_frames).to(device)
|
| 22 |
+
core_ckpt = torch.load(cfg.pretrained_core_denoising_model, map_location='cpu')
|
| 23 |
+
model.load_state_dict(core_ckpt['model_dict'])
|
| 24 |
+
model.eval()
|
| 25 |
+
|
| 26 |
+
init = InitializationModel(
|
| 27 |
+
t_h=cfg.past_frames, d_h=6,
|
| 28 |
+
t_f=cfg.future_frames, d_f=2, k_pred=20).to(device)
|
| 29 |
+
|
| 30 |
+
ckpt = torch.load(ckpt_path, map_location='cpu')
|
| 31 |
+
init.load_state_dict(ckpt['model_initializer_dict'])
|
| 32 |
+
init.eval()
|
| 33 |
+
|
| 34 |
+
graph = None
|
| 35 |
+
if use_graph:
|
| 36 |
+
from models.future_interaction_graph_v6 import FutureInteractionGraphV6Wrapper
|
| 37 |
+
graph = FutureInteractionGraphV6Wrapper(
|
| 38 |
+
num_agents=64, future_steps=cfg.future_frames,
|
| 39 |
+
past_steps=cfg.past_frames, past_channels=6,
|
| 40 |
+
node_dim=128, top_n=5, num_denoise_steps=NUM_Tau).to(device)
|
| 41 |
+
sd = {k: v for k, v in ckpt['interaction_graph_dict'].items()
|
| 42 |
+
if '_single_edge_index' not in k}
|
| 43 |
+
graph.load_state_dict(sd, strict=False)
|
| 44 |
+
graph.eval()
|
| 45 |
+
|
| 46 |
+
return model, init, graph
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def make_beta_schedule(n=100, start=1e-4, end=5e-2):
|
| 50 |
+
return torch.linspace(start, end, n)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def extract(a, t, x):
|
| 54 |
+
out = torch.gather(a, 0, t.to(a.device))
|
| 55 |
+
return out.reshape(t.shape[0], *([1] * (len(x.shape) - 1)))
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
@torch.no_grad()
|
| 59 |
+
def p_sample_accelerate(x, mask, cur_y, t, model, graph, use_v6_graph, sigma,
|
| 60 |
+
betas, alphas, alphas_bar_sqrt, one_minus_alphas_bar_sqrt):
|
| 61 |
+
t_tensor = torch.tensor([int(t)]).to(x.device)
|
| 62 |
+
eps_factor = ((1 - extract(alphas, t_tensor, cur_y))
|
| 63 |
+
/ extract(one_minus_alphas_bar_sqrt, t_tensor, cur_y))
|
| 64 |
+
beta = extract(betas, t_tensor.repeat(x.shape[0]), cur_y)
|
| 65 |
+
eps_theta = model.generate_accelerate(cur_y, beta, x, mask)
|
| 66 |
+
|
| 67 |
+
if graph is not None:
|
| 68 |
+
abs_t = extract(alphas_bar_sqrt, t_tensor, cur_y)
|
| 69 |
+
am1_t = extract(one_minus_alphas_bar_sqrt, t_tensor, cur_y)
|
| 70 |
+
y0_hat = (cur_y - am1_t * eps_theta) / abs_t
|
| 71 |
+
delta = graph(y0_hat, x, int(t), sigma=sigma, A_override=x.size(0))
|
| 72 |
+
eps_theta = eps_theta - (abs_t / am1_t) * delta
|
| 73 |
+
|
| 74 |
+
mean = (1 / extract(alphas, t_tensor, cur_y).sqrt()) \
|
| 75 |
+
* (cur_y - eps_factor * eps_theta)
|
| 76 |
+
z = torch.randn_like(cur_y)
|
| 77 |
+
sigma_t = extract(betas, t_tensor, cur_y).sqrt()
|
| 78 |
+
return mean + sigma_t * z * 0.00001
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
@torch.no_grad()
|
| 82 |
+
def run(args):
|
| 83 |
+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 84 |
+
cfg = Config(args.cfg, args.exp)
|
| 85 |
+
|
| 86 |
+
test_dset = SDDDataset(obs_len=cfg.past_frames,
|
| 87 |
+
pred_len=cfg.future_frames, split='test')
|
| 88 |
+
loader = DataLoader(test_dset, batch_size=1, shuffle=False,
|
| 89 |
+
num_workers=2, collate_fn=sdd_seq_collate)
|
| 90 |
+
|
| 91 |
+
exp_dir = cfg.model_dir # results_sdd/sdd/sdd/<info>/models
|
| 92 |
+
ckpt_path = cfg.model_path % args.epoch
|
| 93 |
+
print(f'Loading checkpoint: {ckpt_path}')
|
| 94 |
+
|
| 95 |
+
model, init, graph = build_models(
|
| 96 |
+
cfg, use_graph=args.use_graph, use_v6_graph=args.use_v6_graph,
|
| 97 |
+
ckpt_path=ckpt_path, device=device)
|
| 98 |
+
|
| 99 |
+
betas = make_beta_schedule().to(device)
|
| 100 |
+
alphas = 1 - betas
|
| 101 |
+
alphas_prod = torch.cumprod(alphas, 0)
|
| 102 |
+
abs_sqrt = torch.sqrt(alphas_prod)
|
| 103 |
+
one_minus_abs_sqrt = torch.sqrt(1 - alphas_prod)
|
| 104 |
+
|
| 105 |
+
traj_mean = torch.FloatTensor(cfg.traj_mean).to(device).view(1, 1, 1, 2)
|
| 106 |
+
traj_scale = float(cfg.traj_scale)
|
| 107 |
+
|
| 108 |
+
np.random.seed(0); random.seed(0)
|
| 109 |
+
torch.manual_seed(0); torch.cuda.manual_seed_all(0)
|
| 110 |
+
|
| 111 |
+
total_ade, total_fde, n = 0.0, 0.0, 0
|
| 112 |
+
T = cfg.future_frames
|
| 113 |
+
for data in loader:
|
| 114 |
+
pre = data['pre_motion_3D'].to(device) # [1, A, 8, 2]
|
| 115 |
+
fut = data['fut_motion_3D'].to(device)
|
| 116 |
+
A = pre.size(1)
|
| 117 |
+
initial_pos = pre[:, :, -1:]
|
| 118 |
+
past_abs = ((pre - traj_mean) / traj_scale).contiguous().view(-1, cfg.past_frames, 2)
|
| 119 |
+
past_rel = ((pre - initial_pos) / traj_scale).contiguous().view(-1, cfg.past_frames, 2)
|
| 120 |
+
past_vel = torch.cat([past_rel[:, 1:] - past_rel[:, :-1],
|
| 121 |
+
torch.zeros_like(past_rel[:, -1:])], dim=1)
|
| 122 |
+
past = torch.cat([past_abs, past_rel, past_vel], dim=-1)
|
| 123 |
+
fut_rel = ((fut - initial_pos) / traj_scale).contiguous().view(-1, T, 2)
|
| 124 |
+
mask = torch.ones(A, A).to(device)
|
| 125 |
+
|
| 126 |
+
sp, me, ve = init(past, mask)
|
| 127 |
+
ve = ve.clamp(min=-5, max=5)
|
| 128 |
+
sp = torch.exp(ve / 2)[..., None, None] * sp \
|
| 129 |
+
/ (sp.std(dim=1).mean(dim=(1, 2))[:, None, None, None] + 1e-6)
|
| 130 |
+
loc = sp + me[:, None]
|
| 131 |
+
sigma_in = ve if args.use_v6_graph else None
|
| 132 |
+
|
| 133 |
+
# leapfrog: 20 modes = 10+10 two halves, each 5 reverse steps
|
| 134 |
+
cur_y = loc[:, :10]
|
| 135 |
+
for i in reversed(range(NUM_Tau)):
|
| 136 |
+
cur_y = p_sample_accelerate(
|
| 137 |
+
past, mask, cur_y, i, model, graph, args.use_v6_graph, sigma_in,
|
| 138 |
+
betas, alphas, abs_sqrt, one_minus_abs_sqrt)
|
| 139 |
+
cur_y_ = loc[:, 10:]
|
| 140 |
+
for i in reversed(range(NUM_Tau)):
|
| 141 |
+
cur_y_ = p_sample_accelerate(
|
| 142 |
+
past, mask, cur_y_, i, model, graph, args.use_v6_graph, sigma_in,
|
| 143 |
+
betas, alphas, abs_sqrt, one_minus_abs_sqrt)
|
| 144 |
+
pred = torch.cat((cur_y_, cur_y), dim=1) # [A, 20, T, 2]
|
| 145 |
+
|
| 146 |
+
# target only (index 0) per scene, full-horizon ADE / final FDE, best_of_20
|
| 147 |
+
pred_0 = pred[0:1]
|
| 148 |
+
fut_0 = fut_rel[0:1]
|
| 149 |
+
dist = torch.norm(fut_0.unsqueeze(1) - pred_0, dim=-1) * traj_scale
|
| 150 |
+
ade = dist.mean(dim=-1).min(dim=-1)[0]
|
| 151 |
+
fde = dist[:, :, -1].min(dim=-1)[0]
|
| 152 |
+
total_ade += ade.sum().item()
|
| 153 |
+
total_fde += fde.sum().item()
|
| 154 |
+
n += 1
|
| 155 |
+
|
| 156 |
+
ade_px = total_ade / n * 50.0
|
| 157 |
+
fde_px = total_fde / n * 50.0
|
| 158 |
+
print(f'Epoch {args.epoch} MID-protocol: ADE={ade_px:.4f} FDE={fde_px:.4f} (n={n})')
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
if __name__ == '__main__':
|
| 162 |
+
p = argparse.ArgumentParser()
|
| 163 |
+
p.add_argument('--cfg', default='sdd/sdd')
|
| 164 |
+
p.add_argument('--exp', required=True, help='info tag e.g. baseline_v2')
|
| 165 |
+
p.add_argument('--epoch', type=int, required=True)
|
| 166 |
+
p.add_argument('--use_graph', action='store_true')
|
| 167 |
+
p.add_argument('--use_v6_graph', action='store_true')
|
| 168 |
+
args = p.parse_args()
|
| 169 |
+
run(args)
|
LED/main_led_nba.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
from trainer import train_led_trajectory_augment_input as led
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
def parse_config():
|
| 6 |
+
parser = argparse.ArgumentParser()
|
| 7 |
+
parser.add_argument("--cuda", default=True)
|
| 8 |
+
parser.add_argument("--learning_rate", type=int, default=0.002)
|
| 9 |
+
parser.add_argument("--max_epochs", type=int, default=128)
|
| 10 |
+
|
| 11 |
+
parser.add_argument('--cfg', default='led_augment')
|
| 12 |
+
parser.add_argument('--gpu', type=int, default=0, help='Specify which GPU to use.')
|
| 13 |
+
parser.add_argument('--train', type=int, default=1, help='Whether train or evaluate.')
|
| 14 |
+
|
| 15 |
+
parser.add_argument("--info", type=str, default='test', help='Name of the experiment. '
|
| 16 |
+
'It will be used in file creation.')
|
| 17 |
+
return parser.parse_args()
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def main(config):
|
| 21 |
+
t = led.Trainer(config)
|
| 22 |
+
if config.train==1:
|
| 23 |
+
t.fit()
|
| 24 |
+
else:
|
| 25 |
+
# t.save_data()
|
| 26 |
+
t.test_single_model()
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
if __name__ == "__main__":
|
| 30 |
+
config = parse_config()
|
| 31 |
+
main(config)
|
LED/main_led_nba_graph.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
from trainer import train_led_graph as led
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
def parse_config():
|
| 6 |
+
parser = argparse.ArgumentParser()
|
| 7 |
+
parser.add_argument("--cuda", default=True)
|
| 8 |
+
parser.add_argument("--learning_rate", type=int, default=0.002)
|
| 9 |
+
parser.add_argument("--max_epochs", type=int, default=128)
|
| 10 |
+
|
| 11 |
+
parser.add_argument('--cfg', default='led_augment')
|
| 12 |
+
parser.add_argument('--gpu', type=int, default=0, help='Specify which GPU to use.')
|
| 13 |
+
parser.add_argument('--train', type=int, default=1, help='Whether train or evaluate.')
|
| 14 |
+
|
| 15 |
+
parser.add_argument("--info", type=str, default='graph', help='Name of the experiment. '
|
| 16 |
+
'It will be used in file creation.')
|
| 17 |
+
|
| 18 |
+
# Graph variant knobs.
|
| 19 |
+
parser.add_argument('--top_n', type=int, default=5,
|
| 20 |
+
help='Number of sparse neighbors per agent (max 10 for NBA with A=11).')
|
| 21 |
+
parser.add_argument('--residual_on', type=str, default='eps', choices=['eps', 'y0'],
|
| 22 |
+
help='Where to apply the graph residual: directly on epsilon, or '
|
| 23 |
+
'on the implied y_0 estimate (then re-projected to epsilon).')
|
| 24 |
+
parser.add_argument('--use_sigma', action='store_true',
|
| 25 |
+
help='If set, pass the initializer variance_estimation to the graph '
|
| 26 |
+
'as per-agent uncertainty (modulates node features and edges).')
|
| 27 |
+
parser.add_argument('--use_v6_graph', action='store_true',
|
| 28 |
+
help='If set, use MoFlow V6-style RAG-scoring graph (FutureInteractionGraphV6) '
|
| 29 |
+
'instead of the default hand-crafted distance-based graph.')
|
| 30 |
+
parser.add_argument('--uncertainty_weight', type=float, default=1.0,
|
| 31 |
+
help='Weight for uncertainty NLL loss. Set to 0 for nosigma ablation.')
|
| 32 |
+
parser.add_argument('--edge_mode', type=str, default='full',
|
| 33 |
+
choices=['full', 'dist_only', 'relpos_only', 'heading_only', 'vel_only', 'full_relvel'],
|
| 34 |
+
help='Edge feature mode for RelTrajEncoder ablation.')
|
| 35 |
+
parser.add_argument('--resume_epoch', type=int, default=0,
|
| 36 |
+
help='Resume from this checkpoint epoch (0=start fresh).')
|
| 37 |
+
parser.add_argument('--neighbor_mode', type=str, default='rag',
|
| 38 |
+
choices=['rag', 'l2', 'semantic'],
|
| 39 |
+
help='Neighbor selection: rag (semantic+geo), l2 (closest), or semantic (learned only)')
|
| 40 |
+
return parser.parse_args()
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def main(config):
|
| 44 |
+
t = led.Trainer(config)
|
| 45 |
+
if config.train == 1:
|
| 46 |
+
t.fit()
|
| 47 |
+
else:
|
| 48 |
+
t.test_single_model()
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
if __name__ == "__main__":
|
| 52 |
+
config = parse_config()
|
| 53 |
+
main(config)
|
LED/main_led_nba_grpo.py
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
GRPO fine-tuning of LED + SRA graph on NBA.
|
| 3 |
+
|
| 4 |
+
Formulation (single-step bandit on the initializer):
|
| 5 |
+
LED's 20-mode diversity comes entirely from the initializer; the 5-step
|
| 6 |
+
leapfrog denoising is near-deterministic (its DDPM noise is x1e-5). So we
|
| 7 |
+
treat the initializer's mode set `loc` [B*A, K, T, 2] as the ACTION:
|
| 8 |
+
|
| 9 |
+
loc = initializer(past) # deterministic mean
|
| 10 |
+
loc_s = loc + init_noise * z # sampled action, z ~ N(0,I)
|
| 11 |
+
pred = leapfrog_decode(loc_s) # fixed decoder (graph+denoiser frozen)
|
| 12 |
+
reward = per-agent accuracy (ADE/FDE) + joint (JADE/JFDE)
|
| 13 |
+
|
| 14 |
+
This sidesteps the long-chain credit-assignment / high-variance problem that
|
| 15 |
+
limited the MoFlow (10-step ODE) experiment: the whole trajectory-set is one
|
| 16 |
+
action, log-prob factorizes over (agent, mode), and GRPO's group-relative
|
| 17 |
+
advantage is taken over the K modes per agent.
|
| 18 |
+
|
| 19 |
+
Only the initializer is trained (graph + core denoiser frozen); KL anchor to a
|
| 20 |
+
frozen copy of the warm-start initializer. Eval uses the near-deterministic
|
| 21 |
+
decoder (matches the LED baseline) and reports ADE/FDE/JADE/JFDE.
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
import os
|
| 25 |
+
import sys
|
| 26 |
+
import argparse
|
| 27 |
+
import math
|
| 28 |
+
import copy
|
| 29 |
+
|
| 30 |
+
import numpy as np
|
| 31 |
+
import torch
|
| 32 |
+
|
| 33 |
+
from trainer.train_led_graph import Trainer as LEDTrainer, NUM_Tau
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
# ---- GRPO reward (inlined from MoFlow grpo/rewards.py to avoid sys.path clash) ----
|
| 37 |
+
def compute_reward_agentwise(pred, gt, init_pos, *, w_ade=1.0, w_fde=1.0,
|
| 38 |
+
w_jade=1.0, w_jfde=1.0, w_col=0.0, w_kin=0.0,
|
| 39 |
+
d_min=0.4, a_max=1.0, ball_idx=None):
|
| 40 |
+
B, K, A, T, _ = pred.shape
|
| 41 |
+
err = (pred - gt.unsqueeze(1)).norm(dim=-1) # [B,K,A,T]
|
| 42 |
+
ade = err.mean(dim=-1) # [B,K,A]
|
| 43 |
+
fde = err[..., -1] # [B,K,A]
|
| 44 |
+
r_marg = -(w_ade * ade + w_fde * fde)
|
| 45 |
+
jade = ade.mean(dim=2, keepdim=True) # [B,K,1]
|
| 46 |
+
jfde = fde.mean(dim=2, keepdim=True)
|
| 47 |
+
r_joint = -(w_jade * jade + w_jfde * jfde)
|
| 48 |
+
reward = r_marg + r_joint
|
| 49 |
+
info = {'ade': ade.detach(), 'jade': jade.squeeze(-1).detach(),
|
| 50 |
+
'ade_bestk': ade.min(dim=1).values.mean().detach(),
|
| 51 |
+
'jade_bestk': jade.squeeze(-1).min(dim=1).values.mean().detach()}
|
| 52 |
+
return reward, info
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def group_advantage(reward, eps=1e-4):
|
| 56 |
+
mean = reward.mean(dim=1, keepdim=True)
|
| 57 |
+
std = reward.std(dim=1, keepdim=True)
|
| 58 |
+
return (reward - mean) / (std + eps)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def _player_mask(A, ball_idx, device):
|
| 62 |
+
pm = ~torch.eye(A, dtype=torch.bool, device=device)
|
| 63 |
+
if ball_idx is not None:
|
| 64 |
+
pm[ball_idx, :] = False
|
| 65 |
+
pm[:, ball_idx] = False
|
| 66 |
+
return pm
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def compute_reward_collision(pred, gt, init_pos, *, w_ade=0.3, w_col=1.0,
|
| 70 |
+
d_min=0.4, ball_idx=10):
|
| 71 |
+
"""Non-differentiable HARD collision-count reward (the objective the
|
| 72 |
+
supervised min-of-K loss cannot optimize) + a soft ADE term to hold accuracy.
|
| 73 |
+
pred/gt in RELATIVE metric (court) units; init_pos absolute [B,A,2].
|
| 74 |
+
Returns reward [B,K,A], info."""
|
| 75 |
+
B, K, A, T, _ = pred.shape
|
| 76 |
+
err = (pred - gt.unsqueeze(1)).norm(dim=-1) # [B,K,A,T]
|
| 77 |
+
ade = err.mean(dim=-1) # [B,K,A] (soft, accuracy)
|
| 78 |
+
abs_p = pred + init_pos[:, None, :, None, :] # absolute positions
|
| 79 |
+
mind = (abs_p.unsqueeze(3) - abs_p.unsqueeze(2)).norm(dim=-1).min(dim=-1).values # [B,K,A,A]
|
| 80 |
+
pm = _player_mask(A, ball_idx, pred.device)
|
| 81 |
+
hard = ((mind < d_min) & pm).float() # HARD indicator (non-diff)
|
| 82 |
+
coll_count = hard.sum(dim=-1) # [B,K,A] #collisions of agent a
|
| 83 |
+
reward = -(w_ade * ade) - (w_col * coll_count)
|
| 84 |
+
info = {'ade_bestk': ade.min(dim=1).values.mean().detach(),
|
| 85 |
+
'jade_bestk': ade.mean(dim=2).min(dim=1).values.mean().detach(),
|
| 86 |
+
'coll_count': coll_count.mean().detach(),
|
| 87 |
+
'coll_rate': (coll_count > 0).float().mean().detach()}
|
| 88 |
+
return reward, info
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
class LEDGRPOTrainer(LEDTrainer):
|
| 92 |
+
def __init__(self, config):
|
| 93 |
+
# graph config for the warm-start checkpoint (edge_relpos, v6, no sigma)
|
| 94 |
+
config.use_v6_graph = True
|
| 95 |
+
config.edge_mode = getattr(config, 'edge_mode', 'relpos_only')
|
| 96 |
+
config.neighbor_mode = getattr(config, 'neighbor_mode', 'rag')
|
| 97 |
+
config.top_n = getattr(config, 'top_n', 5)
|
| 98 |
+
config.use_sigma = False
|
| 99 |
+
config.residual_on = getattr(config, 'residual_on', 'eps')
|
| 100 |
+
super().__init__(config)
|
| 101 |
+
|
| 102 |
+
# ---- warm-start initializer + graph ----
|
| 103 |
+
ck = torch.load(config.warm_ckpt, map_location='cpu')
|
| 104 |
+
self.model_initializer.load_state_dict(ck['model_initializer_dict'])
|
| 105 |
+
self.interaction_graph.load_state_dict(ck['interaction_graph_dict'])
|
| 106 |
+
print(f'[LED-GRPO] warm-started from {config.warm_ckpt}')
|
| 107 |
+
|
| 108 |
+
# freeze graph + core denoiser; train ONLY the initializer
|
| 109 |
+
for p in self.interaction_graph.parameters():
|
| 110 |
+
p.requires_grad_(False)
|
| 111 |
+
for p in self.model.parameters():
|
| 112 |
+
p.requires_grad_(False)
|
| 113 |
+
self.interaction_graph.eval()
|
| 114 |
+
self.model.eval()
|
| 115 |
+
|
| 116 |
+
# bigger rollout batch than LED's default (10) for stable GRPO advantages
|
| 117 |
+
if getattr(config, 'batch', 0):
|
| 118 |
+
from data.dataloader_nba import NBADataset, seq_collate
|
| 119 |
+
from torch.utils.data import DataLoader
|
| 120 |
+
tr = NBADataset(obs_len=self.cfg.past_frames, pred_len=self.cfg.future_frames, training=True)
|
| 121 |
+
self.train_loader = DataLoader(tr, batch_size=config.batch, shuffle=True,
|
| 122 |
+
num_workers=4, collate_fn=seq_collate, pin_memory=True, drop_last=True)
|
| 123 |
+
|
| 124 |
+
# frozen reference initializer (KL anchor)
|
| 125 |
+
self.ref_initializer = copy.deepcopy(self.model_initializer).cuda().eval()
|
| 126 |
+
for p in self.ref_initializer.parameters():
|
| 127 |
+
p.requires_grad_(False)
|
| 128 |
+
|
| 129 |
+
# optimizer over the initializer only
|
| 130 |
+
self.opt = torch.optim.AdamW(self.model_initializer.parameters(), lr=config.grpo_lr)
|
| 131 |
+
|
| 132 |
+
# GRPO hyperparams
|
| 133 |
+
self.G = 20
|
| 134 |
+
self.init_noise = float(config.init_noise)
|
| 135 |
+
self.kl_beta = float(config.kl_beta)
|
| 136 |
+
self.clip_eps = float(config.clip_eps)
|
| 137 |
+
self.inner_epochs = int(config.inner_epochs)
|
| 138 |
+
self.grpo_iters = int(config.grpo_iters)
|
| 139 |
+
self.eval_every = int(config.eval_every)
|
| 140 |
+
self.logratio_clip = 10.0
|
| 141 |
+
self.max_eval_batches = int(getattr(config, 'max_eval_batches', 0))
|
| 142 |
+
self.rw = dict(w_ade=config.w_ade, w_fde=config.w_fde,
|
| 143 |
+
w_jade=config.w_jade, w_jfde=config.w_jfde,
|
| 144 |
+
w_col=0.0, w_kin=0.0, ball_idx=None)
|
| 145 |
+
self.reward_mode = getattr(config, 'reward_mode', 'accuracy')
|
| 146 |
+
self.rw_coll = dict(w_ade=getattr(config, 'w_ade_soft', 0.3),
|
| 147 |
+
w_col=getattr(config, 'w_col', 1.0),
|
| 148 |
+
d_min=getattr(config, 'd_min', 0.4), ball_idx=10)
|
| 149 |
+
self.d_min_eval = getattr(config, 'd_min', 0.4)
|
| 150 |
+
self.ade_tol = getattr(config, 'ade_tol', 0.80)
|
| 151 |
+
self.best_sum = float('inf')
|
| 152 |
+
self.best_coll = float('inf')
|
| 153 |
+
|
| 154 |
+
# ------------------------------------------------------------------
|
| 155 |
+
def get_loc(self, past_traj, traj_mask):
|
| 156 |
+
"""Initializer -> deterministic mode set loc [B*A, K, T, 2]."""
|
| 157 |
+
guess_var, guess_mean, guess_scale = self.model_initializer(past_traj, traj_mask)
|
| 158 |
+
sp = (torch.exp(guess_scale / 2)[..., None, None] * guess_var
|
| 159 |
+
/ guess_var.std(dim=1).mean(dim=(1, 2))[:, None, None, None])
|
| 160 |
+
return sp + guess_mean[:, None]
|
| 161 |
+
|
| 162 |
+
def get_loc_from(self, initializer, past_traj, traj_mask):
|
| 163 |
+
guess_var, guess_mean, guess_scale = initializer(past_traj, traj_mask)
|
| 164 |
+
sp = (torch.exp(guess_scale / 2)[..., None, None] * guess_var
|
| 165 |
+
/ guess_var.std(dim=1).mean(dim=(1, 2))[:, None, None, None])
|
| 166 |
+
return sp + guess_mean[:, None]
|
| 167 |
+
|
| 168 |
+
@staticmethod
|
| 169 |
+
def _logp(action, mean, std):
|
| 170 |
+
var = std * std
|
| 171 |
+
lp = -0.5 * (((action - mean) ** 2) / var + math.log(2 * math.pi * var))
|
| 172 |
+
return lp.sum(dim=(2, 3)) # [B*A, K] sum over (T, 2)
|
| 173 |
+
|
| 174 |
+
def _to_bkat(self, x_ba_k, B, A):
|
| 175 |
+
"""[B*A, K, T, 2] -> [B, K, A, T, 2]"""
|
| 176 |
+
K, T = x_ba_k.shape[1], x_ba_k.shape[2]
|
| 177 |
+
return x_ba_k.view(B, A, K, T, 2).permute(0, 2, 1, 3, 4)
|
| 178 |
+
|
| 179 |
+
# ------------------------------------------------------------------
|
| 180 |
+
def train(self):
|
| 181 |
+
A = 11
|
| 182 |
+
self.eval_grpo(-1) # same-subset baseline (before any update)
|
| 183 |
+
self.model_initializer.train()
|
| 184 |
+
dl = self._cycle(self.train_loader)
|
| 185 |
+
for it in range(self.grpo_iters):
|
| 186 |
+
data = next(dl)
|
| 187 |
+
B, traj_mask, past, fut = self.data_preprocess(data)
|
| 188 |
+
|
| 189 |
+
# ---- rollout: sample action loc_s, decode, reward ----
|
| 190 |
+
with torch.no_grad():
|
| 191 |
+
loc = self.get_loc(past, traj_mask) # [B*A,K,T,2]
|
| 192 |
+
z = torch.randn_like(loc)
|
| 193 |
+
loc_s = loc + self.init_noise * z
|
| 194 |
+
logp_old = self._logp(loc_s, loc, self.init_noise) # [B*A,K]
|
| 195 |
+
pred = self.p_sample_loop_accelerate(past, traj_mask, loc_s) # decode
|
| 196 |
+
loc_ref = self.get_loc_from(self.ref_initializer, past, traj_mask)
|
| 197 |
+
|
| 198 |
+
# reward in metric units ([B,K,A,T,2], scaled by traj_scale)
|
| 199 |
+
pred_m = self._to_bkat(pred, B, A) * self.traj_scale
|
| 200 |
+
gt_m = fut.view(B, A, fut.shape[1], 2) * self.traj_scale
|
| 201 |
+
if self.reward_mode == 'collision':
|
| 202 |
+
init_pos = data['pre_motion_3D'].cuda()[:, :, -1, :] # [B,A,2] absolute
|
| 203 |
+
reward, info = compute_reward_collision(pred_m, gt_m, init_pos, **self.rw_coll)
|
| 204 |
+
else:
|
| 205 |
+
init_pos = torch.zeros(B, A, 2, device=pred.device)
|
| 206 |
+
reward, info = compute_reward_agentwise(pred_m, gt_m, init_pos, **self.rw)
|
| 207 |
+
# advantage per (agent): reshape reward [B,K,A] -> per-agent group over K
|
| 208 |
+
adv = group_advantage(reward) # [B,K,A]
|
| 209 |
+
# map advantage back to [B*A, K] to match logp layout
|
| 210 |
+
adv_bak = adv.permute(0, 2, 1).reshape(B * A, self.G) # [B*A,K]
|
| 211 |
+
|
| 212 |
+
logp_old_flat = logp_old
|
| 213 |
+
loc_s_c = loc_s
|
| 214 |
+
|
| 215 |
+
# ---- PPO update (initializer only) ----
|
| 216 |
+
stats = {}
|
| 217 |
+
for _ in range(self.inner_epochs):
|
| 218 |
+
self.opt.zero_grad()
|
| 219 |
+
loc_new = self.get_loc(past, traj_mask) # grad
|
| 220 |
+
logp_new = self._logp(loc_s_c, loc_new, self.init_noise) # [B*A,K]
|
| 221 |
+
logratio = (logp_new - logp_old_flat).clamp(-self.logratio_clip, self.logratio_clip)
|
| 222 |
+
ratio = logratio.exp()
|
| 223 |
+
unclipped = ratio * adv_bak
|
| 224 |
+
clipped = ratio.clamp(1 - self.clip_eps, 1 + self.clip_eps) * adv_bak
|
| 225 |
+
pg = -torch.min(unclipped, clipped).mean()
|
| 226 |
+
kl = (0.5 * ((loc_new - loc_ref) ** 2) / (self.init_noise ** 2)).sum(dim=(2, 3)).mean()
|
| 227 |
+
loss = pg + self.kl_beta * kl
|
| 228 |
+
loss.backward()
|
| 229 |
+
torch.nn.utils.clip_grad_norm_(self.model_initializer.parameters(), 1.0)
|
| 230 |
+
self.opt.step()
|
| 231 |
+
stats = dict(pg=pg.item(), kl=kl.item(), ratio=ratio.mean().item(),
|
| 232 |
+
clipfrac=((ratio - 1).abs() > self.clip_eps).float().mean().item())
|
| 233 |
+
|
| 234 |
+
if it % 10 == 0:
|
| 235 |
+
extra = (f'collrate={info["coll_rate"].item():.3f} cnt={info["coll_count"].item():.3f} '
|
| 236 |
+
if 'coll_rate' in info else f'JADE*={info["jade_bestk"].item():.4f} ')
|
| 237 |
+
print(f'[LED-GRPO {it}/{self.grpo_iters}] R={reward.mean().item():.4f} '
|
| 238 |
+
f'ADE*={info["ade_bestk"].item():.4f} {extra}'
|
| 239 |
+
f'| pg={stats["pg"]:.4f} kl={stats["kl"]:.5f} '
|
| 240 |
+
f'ratio={stats["ratio"]:.3f} clipfrac={stats["clipfrac"]:.3f}', flush=True)
|
| 241 |
+
|
| 242 |
+
if (it + 1) % self.eval_every == 0:
|
| 243 |
+
self.eval_grpo(it)
|
| 244 |
+
self.model_initializer.train()
|
| 245 |
+
|
| 246 |
+
# ------------------------------------------------------------------
|
| 247 |
+
@torch.no_grad()
|
| 248 |
+
def eval_grpo(self, it):
|
| 249 |
+
self.model_initializer.eval()
|
| 250 |
+
A = 11
|
| 251 |
+
perf = {'ADE': [0.]*4, 'FDE': [0.]*4, 'JADE': [0.]*4, 'JFDE': [0.]*4}
|
| 252 |
+
coll_thr = (0.2, 0.3, 0.4)
|
| 253 |
+
collP = {th: 0. for th in coll_thr}; collG = {th: 0. for th in coll_thr}
|
| 254 |
+
nP, nG = 0, 0
|
| 255 |
+
n_ag, n_sc = 0, 0
|
| 256 |
+
for bi, data in enumerate(self.test_loader):
|
| 257 |
+
if self.max_eval_batches and bi >= self.max_eval_batches:
|
| 258 |
+
break
|
| 259 |
+
B, traj_mask, past, fut = self.data_preprocess(data)
|
| 260 |
+
loc = self.get_loc(past, traj_mask) # deterministic
|
| 261 |
+
pred = self.p_sample_loop_accelerate(past, traj_mask, loc)
|
| 262 |
+
# --- collision: absolute positions, player-pairs, ball(10) excluded ---
|
| 263 |
+
ipos = data['pre_motion_3D'].cuda()[:, :, -1, :] # [B,A,2]
|
| 264 |
+
Tf = fut.shape[1]
|
| 265 |
+
absP = self._to_bkat(pred, B, A) * self.traj_scale + ipos[:, None, :, None, :] # [B,K,A,T,2]
|
| 266 |
+
absG = (fut.view(B, A, Tf, 2) * self.traj_scale + ipos[:, :, None, :]).unsqueeze(1) # [B,1,A,T,2]
|
| 267 |
+
pm = _player_mask(A, 10, absP.device)
|
| 268 |
+
cpP = ((absP.unsqueeze(3) - absP.unsqueeze(2)).norm(dim=-1).min(dim=-1).values
|
| 269 |
+
.masked_fill(~pm, 1e9).reshape(B, self.G, -1).min(-1).values) # [B,K]
|
| 270 |
+
cpG = ((absG.unsqueeze(3) - absG.unsqueeze(2)).norm(dim=-1).min(dim=-1).values
|
| 271 |
+
.masked_fill(~pm, 1e9).reshape(B, 1, -1).min(-1).values) # [B,1]
|
| 272 |
+
for th in coll_thr:
|
| 273 |
+
collP[th] += (cpP < th).float().sum().item()
|
| 274 |
+
collG[th] += (cpG < th).float().sum().item()
|
| 275 |
+
nP += B * self.G; nG += B
|
| 276 |
+
fut_r = fut.unsqueeze(1).repeat(1, self.G, 1, 1) # [B*A,K,T,2]
|
| 277 |
+
d = (fut_r - pred).norm(dim=-1) * self.traj_scale # [B*A,K,T]
|
| 278 |
+
dB = d.view(B, A, self.G, d.shape[-1]) # [B,A,K,T]
|
| 279 |
+
for ti in range(1, 5):
|
| 280 |
+
e = 5 * ti
|
| 281 |
+
# marginal: per-agent min over K
|
| 282 |
+
ade = d[..., :e].mean(-1).min(dim=1)[0].sum()
|
| 283 |
+
fde = d[..., e-1].min(dim=1)[0].sum()
|
| 284 |
+
# joint: per-scene, mean over agents then min over K
|
| 285 |
+
jade = dB[..., :e].mean(-1).mean(dim=1).min(dim=1)[0].sum()
|
| 286 |
+
jfde = dB[..., e-1].mean(dim=1).min(dim=1)[0].sum()
|
| 287 |
+
perf['ADE'][ti-1] += ade.item(); perf['FDE'][ti-1] += fde.item()
|
| 288 |
+
perf['JADE'][ti-1] += jade.item(); perf['JFDE'][ti-1] += jfde.item()
|
| 289 |
+
n_ag += B * A; n_sc += B
|
| 290 |
+
ade4 = perf['ADE'][3]/n_ag; fde4 = perf['FDE'][3]/n_ag
|
| 291 |
+
jade4 = perf['JADE'][3]/n_sc; jfde4 = perf['JFDE'][3]/n_sc
|
| 292 |
+
s = ade4 + fde4 + jade4 + jfde4
|
| 293 |
+
cstr = ' '.join(f'@{th}:{collP[th]/nP*100:.1f}%(GT{collG[th]/nG*100:.1f})' for th in coll_thr)
|
| 294 |
+
print(f'[LED-GRPO eval @ {it}] ADE={ade4:.4f} FDE={fde4:.4f} '
|
| 295 |
+
f'JADE={jade4:.4f} JFDE={jfde4:.4f} | coll[pred(GT)]: {cstr}', flush=True)
|
| 296 |
+
# checkpoint: collision mode -> best collision@d_min with ADE guard; else -> best sum
|
| 297 |
+
if self.reward_mode == 'collision':
|
| 298 |
+
c = collP[self.d_min_eval] / nP if self.d_min_eval in collP else collP[0.4] / nP
|
| 299 |
+
if ade4 <= getattr(self, 'ade_tol', 0.80) and c < self.best_coll:
|
| 300 |
+
self.best_coll = c
|
| 301 |
+
torch.save({'model_initializer_dict': self.model_initializer.state_dict(),
|
| 302 |
+
'interaction_graph_dict': self.interaction_graph.state_dict()},
|
| 303 |
+
os.path.join(self.cfg.log_dir, 'grpo_best.p'))
|
| 304 |
+
print(f' new best coll@{self.d_min_eval}={c*100:.2f}% at ADE={ade4:.4f} -> grpo_best.p', flush=True)
|
| 305 |
+
elif s < self.best_sum:
|
| 306 |
+
self.best_sum = s
|
| 307 |
+
torch.save({'model_initializer_dict': self.model_initializer.state_dict(),
|
| 308 |
+
'interaction_graph_dict': self.interaction_graph.state_dict()},
|
| 309 |
+
os.path.join(self.cfg.log_dir, 'grpo_best.p'))
|
| 310 |
+
print(f' new best sum={s:.4f} -> grpo_best.p', flush=True)
|
| 311 |
+
|
| 312 |
+
@staticmethod
|
| 313 |
+
def _cycle(dl):
|
| 314 |
+
while True:
|
| 315 |
+
for d in dl:
|
| 316 |
+
yield d
|
| 317 |
+
|
| 318 |
+
|
| 319 |
+
def parse_config():
|
| 320 |
+
p = argparse.ArgumentParser()
|
| 321 |
+
p.add_argument('--cfg', default='led_augment')
|
| 322 |
+
p.add_argument('--info', default='grpo', type=str)
|
| 323 |
+
p.add_argument('--gpu', type=int, default=0)
|
| 324 |
+
p.add_argument('--cuda', default=True)
|
| 325 |
+
p.add_argument('--learning_rate', type=float, default=0.002) # unused (grpo_lr used)
|
| 326 |
+
p.add_argument('--warm_ckpt', type=str,
|
| 327 |
+
default='./results/led_augment/graph_v6_edge_relpos/models/model_0036.p')
|
| 328 |
+
p.add_argument('--edge_mode', default='relpos_only', type=str)
|
| 329 |
+
# GRPO
|
| 330 |
+
p.add_argument('--batch', type=int, default=64)
|
| 331 |
+
p.add_argument('--grpo_lr', type=float, default=1e-4)
|
| 332 |
+
p.add_argument('--init_noise', type=float, default=0.1)
|
| 333 |
+
p.add_argument('--kl_beta', type=float, default=0.0)
|
| 334 |
+
p.add_argument('--clip_eps', type=float, default=0.2)
|
| 335 |
+
p.add_argument('--inner_epochs', type=int, default=2)
|
| 336 |
+
p.add_argument('--grpo_iters', type=int, default=1000)
|
| 337 |
+
p.add_argument('--eval_every', type=int, default=50)
|
| 338 |
+
p.add_argument('--max_eval_batches', type=int, default=5)
|
| 339 |
+
p.add_argument('--w_ade', type=float, default=1.0)
|
| 340 |
+
p.add_argument('--w_fde', type=float, default=1.0)
|
| 341 |
+
p.add_argument('--w_jade', type=float, default=1.0)
|
| 342 |
+
p.add_argument('--w_jfde', type=float, default=1.0)
|
| 343 |
+
# collision (non-differentiable) reward
|
| 344 |
+
p.add_argument('--reward_mode', default='accuracy', choices=['accuracy', 'collision'])
|
| 345 |
+
p.add_argument('--w_ade_soft', type=float, default=0.3, help='soft ADE weight (hold accuracy)')
|
| 346 |
+
p.add_argument('--w_col', type=float, default=1.0, help='hard collision-count weight')
|
| 347 |
+
p.add_argument('--d_min', type=float, default=0.4)
|
| 348 |
+
p.add_argument('--ade_tol', type=float, default=0.80)
|
| 349 |
+
return p.parse_args()
|
| 350 |
+
|
| 351 |
+
|
| 352 |
+
def main():
|
| 353 |
+
cfg = parse_config()
|
| 354 |
+
torch.cuda.set_device(cfg.gpu)
|
| 355 |
+
t = LEDGRPOTrainer(cfg)
|
| 356 |
+
t.train()
|
| 357 |
+
|
| 358 |
+
|
| 359 |
+
if __name__ == '__main__':
|
| 360 |
+
main()
|
LED/main_sdd_led.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Stage-2: train LED initializer on SDD (baseline or +graph)."""
|
| 2 |
+
import argparse
|
| 3 |
+
from trainer.train_sdd_led import Trainer
|
| 4 |
+
|
| 5 |
+
def main():
|
| 6 |
+
p = argparse.ArgumentParser()
|
| 7 |
+
p.add_argument('--cuda', default=True)
|
| 8 |
+
p.add_argument('--cfg', default='sdd/sdd')
|
| 9 |
+
p.add_argument('--gpu', type=int, default=0)
|
| 10 |
+
p.add_argument('--info', type=str, default='baseline')
|
| 11 |
+
p.add_argument('--learning_rate', type=float, default=0.002)
|
| 12 |
+
p.add_argument('--train', type=int, default=1)
|
| 13 |
+
p.add_argument('--grad_accum', type=int, default=16)
|
| 14 |
+
p.add_argument('--use_graph', action='store_true')
|
| 15 |
+
p.add_argument('--use_v6_graph', action='store_true')
|
| 16 |
+
p.add_argument('--residual_on', type=str, default='y0')
|
| 17 |
+
config = p.parse_args()
|
| 18 |
+
Trainer(config).fit()
|
| 19 |
+
|
| 20 |
+
if __name__ == '__main__':
|
| 21 |
+
main()
|
LED/main_sdd_pretrain.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Stage-1: pretrain LED denoiser on SDD."""
|
| 2 |
+
import argparse
|
| 3 |
+
from trainer.train_sdd_pretrain import Trainer
|
| 4 |
+
|
| 5 |
+
def main():
|
| 6 |
+
p = argparse.ArgumentParser()
|
| 7 |
+
p.add_argument('--cuda', default=True)
|
| 8 |
+
p.add_argument('--cfg', default='sdd/sdd')
|
| 9 |
+
p.add_argument('--gpu', type=int, default=0)
|
| 10 |
+
p.add_argument('--info', type=str, default='pretrain')
|
| 11 |
+
p.add_argument('--grad_accum', type=int, default=32)
|
| 12 |
+
config = p.parse_args()
|
| 13 |
+
config.train = 1
|
| 14 |
+
Trainer(config).fit()
|
| 15 |
+
|
| 16 |
+
if __name__ == '__main__':
|
| 17 |
+
main()
|
LED/main_sport_led.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
from trainer import train_sport_led as sl
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
def parse_config():
|
| 6 |
+
parser = argparse.ArgumentParser()
|
| 7 |
+
parser.add_argument('--cuda', default=True)
|
| 8 |
+
parser.add_argument('--learning_rate', type=float, default=2e-3)
|
| 9 |
+
parser.add_argument('--cfg', default='soccer', help='Sport cfg name (soccer / football)')
|
| 10 |
+
parser.add_argument('--gpu', type=int, default=0)
|
| 11 |
+
parser.add_argument('--train', type=int, default=1)
|
| 12 |
+
parser.add_argument('--info', type=str, default='baseline')
|
| 13 |
+
|
| 14 |
+
parser.add_argument('--use_graph', action='store_true',
|
| 15 |
+
help='If set, use the FutureInteractionGraph residual '
|
| 16 |
+
'(default: baseline LED).')
|
| 17 |
+
parser.add_argument('--residual_on', type=str, default='y0',
|
| 18 |
+
choices=['eps', 'y0'],
|
| 19 |
+
help='Where the graph residual is applied (only used with --use_graph).')
|
| 20 |
+
parser.add_argument('--use_v6_graph', action='store_true',
|
| 21 |
+
help='If set, use MoFlow V6-style RAG-scoring graph instead of '
|
| 22 |
+
'the hand-crafted distance-based one.')
|
| 23 |
+
return parser.parse_args()
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def main(config):
|
| 27 |
+
t = sl.Trainer(config)
|
| 28 |
+
t.fit()
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
if __name__ == '__main__':
|
| 32 |
+
config = parse_config()
|
| 33 |
+
main(config)
|
LED/main_sport_pretrain.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
from trainer import train_sport_pretrain as sp
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
def parse_config():
|
| 6 |
+
parser = argparse.ArgumentParser()
|
| 7 |
+
parser.add_argument('--cuda', default=True)
|
| 8 |
+
parser.add_argument('--learning_rate', type=float, default=1e-3,
|
| 9 |
+
help='Ignored — actual lr comes from cfg.pretrain.lr.')
|
| 10 |
+
parser.add_argument('--cfg', default='soccer', help='Sport cfg name (soccer / football)')
|
| 11 |
+
parser.add_argument('--gpu', type=int, default=0)
|
| 12 |
+
parser.add_argument('--info', type=str, default='pretrain')
|
| 13 |
+
# unused but kept for interface symmetry with the LED mains
|
| 14 |
+
parser.add_argument('--train', type=int, default=1)
|
| 15 |
+
return parser.parse_args()
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def main(config):
|
| 19 |
+
t = sp.Trainer(config)
|
| 20 |
+
t.fit()
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
if __name__ == '__main__':
|
| 24 |
+
config = parse_config()
|
| 25 |
+
main(config)
|
LED/models/future_interaction_graph.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Future-trajectory interaction graph for LED.
|
| 3 |
+
|
| 4 |
+
Idea (ported from MoFlow's FutureInteractionGraphV6):
|
| 5 |
+
At each leapfrog denoising step, convert the frozen core denoiser's
|
| 6 |
+
epsilon prediction into an implied y_0 estimate, then run a small
|
| 7 |
+
agent-to-agent graph on that future geometry and emit a residual
|
| 8 |
+
correction added back to epsilon. The module is zero-initialized at
|
| 9 |
+
the output so it begins as a no-op and only nudges the reverse chain
|
| 10 |
+
as it learns.
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
import torch.nn as nn
|
| 15 |
+
import torch.nn.functional as F
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class FutureInteractionGraph(nn.Module):
|
| 19 |
+
def __init__(
|
| 20 |
+
self,
|
| 21 |
+
num_agents: int = 11,
|
| 22 |
+
future_steps: int = 20,
|
| 23 |
+
past_steps: int = 10,
|
| 24 |
+
past_channels: int = 6,
|
| 25 |
+
node_dim: int = 128,
|
| 26 |
+
top_n: int = 5,
|
| 27 |
+
num_denoise_steps: int = 5,
|
| 28 |
+
use_sigma: bool = False,
|
| 29 |
+
):
|
| 30 |
+
super().__init__()
|
| 31 |
+
self.A = num_agents
|
| 32 |
+
self.T = future_steps
|
| 33 |
+
self.D = node_dim
|
| 34 |
+
self.top_n = min(top_n, num_agents - 1)
|
| 35 |
+
self.use_sigma = use_sigma
|
| 36 |
+
|
| 37 |
+
self.past_proj = nn.Sequential(
|
| 38 |
+
nn.Linear(past_steps * past_channels, node_dim),
|
| 39 |
+
nn.ReLU(inplace=True),
|
| 40 |
+
nn.Linear(node_dim, node_dim),
|
| 41 |
+
)
|
| 42 |
+
self.y0_proj = nn.Sequential(
|
| 43 |
+
nn.Linear(future_steps * 2, node_dim),
|
| 44 |
+
nn.ReLU(inplace=True),
|
| 45 |
+
nn.Linear(node_dim, node_dim),
|
| 46 |
+
)
|
| 47 |
+
self.step_emb = nn.Embedding(num_denoise_steps, node_dim)
|
| 48 |
+
|
| 49 |
+
if use_sigma:
|
| 50 |
+
self.sigma_proj = nn.Sequential(
|
| 51 |
+
nn.Linear(1, node_dim // 4),
|
| 52 |
+
nn.ReLU(inplace=True),
|
| 53 |
+
nn.Linear(node_dim // 4, node_dim),
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
# edge feature: base 5 dims + 1 sigma_diff if use_sigma
|
| 57 |
+
edge_in_dim = 6 if use_sigma else 5
|
| 58 |
+
self.edge_mlp = nn.Sequential(
|
| 59 |
+
nn.Linear(edge_in_dim, 32),
|
| 60 |
+
nn.ReLU(inplace=True),
|
| 61 |
+
nn.Linear(32, node_dim),
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
self.attn_q = nn.Linear(node_dim, node_dim)
|
| 65 |
+
self.attn_k = nn.Linear(node_dim * 2, node_dim)
|
| 66 |
+
self.attn_v = nn.Linear(node_dim * 2, node_dim)
|
| 67 |
+
self.scale = node_dim ** -0.5
|
| 68 |
+
|
| 69 |
+
self.fuse = nn.Sequential(
|
| 70 |
+
nn.Linear(node_dim * 2, node_dim),
|
| 71 |
+
nn.ReLU(inplace=True),
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
self.decode = nn.Sequential(
|
| 75 |
+
nn.Linear(node_dim, node_dim),
|
| 76 |
+
nn.ReLU(inplace=True),
|
| 77 |
+
nn.Linear(node_dim, future_steps * 2),
|
| 78 |
+
)
|
| 79 |
+
# zero-init output so residual starts as a no-op
|
| 80 |
+
nn.init.zeros_(self.decode[-1].weight)
|
| 81 |
+
nn.init.zeros_(self.decode[-1].bias)
|
| 82 |
+
|
| 83 |
+
def forward(
|
| 84 |
+
self,
|
| 85 |
+
y0_hat: torch.Tensor, # [B*A, K, T, 2]
|
| 86 |
+
past: torch.Tensor, # [B*A, T_h, 6]
|
| 87 |
+
step_idx: int, # current denoising step in [0, num_denoise_steps)
|
| 88 |
+
sigma: torch.Tensor = None, # [B*A, 1] per-agent uncertainty (optional)
|
| 89 |
+
) -> torch.Tensor: # [B*A, K, T, 2]
|
| 90 |
+
BA, K, T, _ = y0_hat.shape
|
| 91 |
+
A = self.A
|
| 92 |
+
assert BA % A == 0, f"expected B*A agents with A={A}, got {BA}"
|
| 93 |
+
B = BA // A
|
| 94 |
+
device = y0_hat.device
|
| 95 |
+
|
| 96 |
+
past_feat = self.past_proj(past.reshape(BA, -1)) # [BA, D]
|
| 97 |
+
past_feat = past_feat.unsqueeze(1).expand(BA, K, self.D) # [BA, K, D]
|
| 98 |
+
y0_feat = self.y0_proj(y0_hat.reshape(BA, K, T * 2)) # [BA, K, D]
|
| 99 |
+
step = self.step_emb(torch.tensor(step_idx, device=device)) # [D]
|
| 100 |
+
nodes = past_feat + y0_feat + step # [BA, K, D]
|
| 101 |
+
|
| 102 |
+
if self.use_sigma and sigma is not None:
|
| 103 |
+
sigma_feat = self.sigma_proj(sigma) # [BA, D]
|
| 104 |
+
nodes = nodes + sigma_feat.unsqueeze(1) # broadcast over K
|
| 105 |
+
|
| 106 |
+
nodes = nodes.view(B, A, K, self.D).permute(0, 2, 1, 3).contiguous()
|
| 107 |
+
# nodes: [B, K, A, D]
|
| 108 |
+
|
| 109 |
+
pos = y0_hat.view(B, A, K, T, 2).permute(0, 2, 1, 3, 4).contiguous()
|
| 110 |
+
# pos: [B, K, A, T, 2]
|
| 111 |
+
|
| 112 |
+
# pairwise relative future trajectories: row i = receiver, col j = source
|
| 113 |
+
# rel[:, :, i, j] = pos_j - pos_i (where j is relative to i)
|
| 114 |
+
rel = pos.unsqueeze(2) - pos.unsqueeze(3) # [B,K,A_i,A_j,T,2]
|
| 115 |
+
mean_rel = rel.mean(dim=-2) # [B,K,A,A,2]
|
| 116 |
+
std_rel = rel.std(dim=-2) # [B,K,A,A,2]
|
| 117 |
+
dist = rel.norm(dim=-1) # [B,K,A,A,T]
|
| 118 |
+
min_dist = dist.min(dim=-1).values.unsqueeze(-1) # [B,K,A,A,1]
|
| 119 |
+
|
| 120 |
+
if self.use_sigma and sigma is not None:
|
| 121 |
+
# Per-agent sigma → pairwise sigma difference as edge feature
|
| 122 |
+
sigma_bka = sigma.view(B, A, 1).permute(0, 2, 1).contiguous() # [B, 1, A]
|
| 123 |
+
sigma_i = sigma_bka.unsqueeze(3).expand(-1, K, A, A) # [B,K,A,A]
|
| 124 |
+
sigma_j = sigma_bka.unsqueeze(2).expand(-1, K, A, A) # [B,K,A,A]
|
| 125 |
+
sigma_diff = (sigma_i - sigma_j).unsqueeze(-1) # [B,K,A,A,1]
|
| 126 |
+
edge_raw = torch.cat([mean_rel, std_rel, min_dist, sigma_diff], dim=-1) # [B,K,A,A,6]
|
| 127 |
+
else:
|
| 128 |
+
edge_raw = torch.cat([mean_rel, std_rel, min_dist], dim=-1) # [B,K,A,A,5]
|
| 129 |
+
edge_feat = self.edge_mlp(edge_raw) # [B,K,A,A,D]
|
| 130 |
+
|
| 131 |
+
# top-N neighbor selection by closest min-distance over future horizon
|
| 132 |
+
score = -min_dist.squeeze(-1) # [B,K,A,A]
|
| 133 |
+
diag = torch.eye(A, dtype=torch.bool, device=device)
|
| 134 |
+
score = score.masked_fill(diag, float('-inf'))
|
| 135 |
+
_, top_idx = score.topk(self.top_n, dim=-1) # [B,K,A,N]
|
| 136 |
+
|
| 137 |
+
idx_node = top_idx.unsqueeze(-1).expand(-1, -1, -1, -1, self.D)
|
| 138 |
+
nodes_j = nodes.unsqueeze(2).expand(-1, -1, A, -1, -1) # [B,K,A_i,A_j,D]
|
| 139 |
+
neigh_nodes = torch.gather(nodes_j, 3, idx_node) # [B,K,A,N,D]
|
| 140 |
+
|
| 141 |
+
idx_edge = top_idx.unsqueeze(-1).expand(-1, -1, -1, -1, self.D)
|
| 142 |
+
neigh_edges = torch.gather(edge_feat, 3, idx_edge) # [B,K,A,N,D]
|
| 143 |
+
|
| 144 |
+
q = self.attn_q(nodes).unsqueeze(-2) # [B,K,A,1,D]
|
| 145 |
+
kv_in = torch.cat([neigh_nodes, neigh_edges], dim=-1) # [B,K,A,N,2D]
|
| 146 |
+
k = self.attn_k(kv_in)
|
| 147 |
+
v = self.attn_v(kv_in)
|
| 148 |
+
attn = (q * k).sum(dim=-1) * self.scale # [B,K,A,N]
|
| 149 |
+
attn = F.softmax(attn, dim=-1)
|
| 150 |
+
msg = (attn.unsqueeze(-1) * v).sum(dim=-2) # [B,K,A,D]
|
| 151 |
+
|
| 152 |
+
fused = self.fuse(torch.cat([nodes, msg], dim=-1)) # [B,K,A,D]
|
| 153 |
+
residual = self.decode(fused).view(B, K, A, T, 2)
|
| 154 |
+
residual = residual.permute(0, 2, 1, 3, 4).contiguous().view(BA, K, T, 2)
|
| 155 |
+
return residual
|
LED/models/future_interaction_graph_v6.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
V6-style future-trajectory interaction graph for LED.
|
| 3 |
+
|
| 4 |
+
Re-uses MoFlow's `FutureInteractionGraphV6` verbatim (imported via sys.path)
|
| 5 |
+
so that LED, MID, and MoFlow share the identical neighbor-scoring and
|
| 6 |
+
message-passing component:
|
| 7 |
+
|
| 8 |
+
Per-node: q_i = W_q([y0_emb, sigma_mean]), k_j = W_k([y0_emb, sigma_mean])
|
| 9 |
+
Per-edge: semantic = (q_i · k_j)/sqrt(D_s), geo_bias = geo_mlp(mean_rel, std_rel, min_dist, heading_diff_mean)
|
| 10 |
+
Top-N by (semantic + geo_bias) → RelTrajEncoder([rel_pos, heading_diff], sigma_bias)
|
| 11 |
+
GNN layers → gated residual → refined node embedding
|
| 12 |
+
|
| 13 |
+
This file is a thin adapter: it produces the V6 inputs from LED's native call
|
| 14 |
+
signature and decodes the refined node embeddings into a per-agent, per-mode
|
| 15 |
+
residual trajectory that LED adds to its frozen core denoiser's epsilon
|
| 16 |
+
output (or to the implied y_0 estimate, depending on --residual_on).
|
| 17 |
+
|
| 18 |
+
The decoder's final layer is zero-initialized so the overall residual starts
|
| 19 |
+
as a no-op; V6's own gated residual inside is additionally safe at init
|
| 20 |
+
because its gate_proj is randomly initialized but small.
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
import os, sys
|
| 24 |
+
import torch
|
| 25 |
+
import torch.nn as nn
|
| 26 |
+
|
| 27 |
+
# Import MoFlow's V6 graph module (and dependencies) directly.
|
| 28 |
+
_MOFLOW_ROOT = os.path.abspath(
|
| 29 |
+
os.path.join(os.path.dirname(__file__), '..', '..', 'MoFlow'))
|
| 30 |
+
if _MOFLOW_ROOT not in sys.path:
|
| 31 |
+
sys.path.insert(0, _MOFLOW_ROOT)
|
| 32 |
+
|
| 33 |
+
from models.graph_interaction_nba_v6 import FutureInteractionGraphV6
|
| 34 |
+
from models.interaction_baselines import build_interaction_module # E4: SRA_MODULE factory
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class FutureInteractionGraphV6Wrapper(nn.Module):
|
| 38 |
+
"""Adapter that exposes MoFlow's V6 graph to LED's call signature.
|
| 39 |
+
|
| 40 |
+
LED call:
|
| 41 |
+
delta = graph(y0_hat, past, step_idx, sigma=sigma)
|
| 42 |
+
y0_hat [B*A, K, T, 2], past [B*A, T_h, 6], step_idx int, sigma [B*A, 1] or None
|
| 43 |
+
delta [B*A, K, T, 2]
|
| 44 |
+
|
| 45 |
+
V6 call (underneath):
|
| 46 |
+
refined = future_graph(y_emb, y_abs, t_emb, tau, sigma_agent)
|
| 47 |
+
y_emb [B, K, A, D], y_abs [B, K, A, T, 2], t_emb [B, D], tau [B], sigma_agent [B, K, A, T] or None
|
| 48 |
+
"""
|
| 49 |
+
|
| 50 |
+
def __init__(
|
| 51 |
+
self,
|
| 52 |
+
num_agents: int = 11,
|
| 53 |
+
future_steps: int = 20,
|
| 54 |
+
past_steps: int = 10,
|
| 55 |
+
past_channels: int = 6,
|
| 56 |
+
node_dim: int = 128,
|
| 57 |
+
top_n: int = 5, # matches MoFlow default
|
| 58 |
+
num_denoise_steps: int = 5,
|
| 59 |
+
num_gnn_layers: int = 2,
|
| 60 |
+
rel_traj_hidden: int = 32,
|
| 61 |
+
y0_score_dim: int = 32,
|
| 62 |
+
num_heads: int = 4,
|
| 63 |
+
dropout: float = 0.1,
|
| 64 |
+
edge_mode: str = 'full',
|
| 65 |
+
neighbor_mode: str = 'rag',
|
| 66 |
+
):
|
| 67 |
+
super().__init__()
|
| 68 |
+
self.A = num_agents
|
| 69 |
+
self.T = future_steps
|
| 70 |
+
self.D = node_dim
|
| 71 |
+
|
| 72 |
+
# ---- Input encoders for the node embedding fed to V6 ----
|
| 73 |
+
self.past_proj = nn.Sequential(
|
| 74 |
+
nn.Linear(past_steps * past_channels, node_dim),
|
| 75 |
+
nn.ReLU(inplace=True),
|
| 76 |
+
nn.Linear(node_dim, node_dim),
|
| 77 |
+
)
|
| 78 |
+
self.y0_proj = nn.Sequential(
|
| 79 |
+
nn.Linear(future_steps * 2, node_dim),
|
| 80 |
+
nn.ReLU(inplace=True),
|
| 81 |
+
nn.Linear(node_dim, node_dim),
|
| 82 |
+
)
|
| 83 |
+
self.step_emb = nn.Embedding(num_denoise_steps, node_dim)
|
| 84 |
+
|
| 85 |
+
# ---- MoFlow V6 graph (scoring, edge encoding, GNN, gated residual) ----
|
| 86 |
+
self.future_graph = build_interaction_module( # E4: sra|gameformer|c2f via env SRA_MODULE
|
| 87 |
+
embed_dim = node_dim,
|
| 88 |
+
future_steps = future_steps,
|
| 89 |
+
num_agents = num_agents,
|
| 90 |
+
num_heads = num_heads,
|
| 91 |
+
dropout = dropout,
|
| 92 |
+
num_gnn_layers = num_gnn_layers,
|
| 93 |
+
time_dim = node_dim,
|
| 94 |
+
top_n_neighbors = min(top_n, num_agents - 1),
|
| 95 |
+
rel_traj_hidden = rel_traj_hidden,
|
| 96 |
+
y0_score_dim = y0_score_dim,
|
| 97 |
+
edge_mode = edge_mode,
|
| 98 |
+
neighbor_mode = neighbor_mode,
|
| 99 |
+
)
|
| 100 |
+
|
| 101 |
+
# ---- Decoder: refined node embedding -> per-timestep trajectory residual ----
|
| 102 |
+
self.decode = nn.Sequential(
|
| 103 |
+
nn.Linear(node_dim, node_dim),
|
| 104 |
+
nn.ReLU(inplace=True),
|
| 105 |
+
nn.Linear(node_dim, future_steps * 2),
|
| 106 |
+
)
|
| 107 |
+
nn.init.zeros_(self.decode[-1].weight)
|
| 108 |
+
nn.init.zeros_(self.decode[-1].bias)
|
| 109 |
+
|
| 110 |
+
def forward(
|
| 111 |
+
self,
|
| 112 |
+
y0_hat: torch.Tensor, # [B*A, K, T, 2]
|
| 113 |
+
past: torch.Tensor, # [B*A, T_h, 6]
|
| 114 |
+
step_idx: int,
|
| 115 |
+
sigma: torch.Tensor = None, # [B*A, 1] (per-agent scalar uncertainty, optional)
|
| 116 |
+
A_override: int = None,
|
| 117 |
+
) -> torch.Tensor: # [B*A, K, T, 2]
|
| 118 |
+
BA, K, T, _ = y0_hat.shape
|
| 119 |
+
# If A_override is given (variable-A scenes), use it; otherwise use init A.
|
| 120 |
+
if A_override is not None:
|
| 121 |
+
A = A_override
|
| 122 |
+
else:
|
| 123 |
+
A = self.A
|
| 124 |
+
if BA % A != 0:
|
| 125 |
+
# Fall back to treating whole batch as a single scene (B=1, A=BA)
|
| 126 |
+
A = BA
|
| 127 |
+
B = BA // A
|
| 128 |
+
device = y0_hat.device
|
| 129 |
+
# If A < 2, graph is meaningless — return zero residual
|
| 130 |
+
if A < 2:
|
| 131 |
+
return torch.zeros_like(y0_hat)
|
| 132 |
+
# Rebuild graph's edge index for this A
|
| 133 |
+
fg = self.future_graph
|
| 134 |
+
if fg.num_agents != A:
|
| 135 |
+
fg.num_agents = A
|
| 136 |
+
fg._E0 = A * (A - 1)
|
| 137 |
+
fg.top_n = max(1, min(fg.top_n, A - 1))
|
| 138 |
+
src, dst = [], []
|
| 139 |
+
for i in range(A):
|
| 140 |
+
for j in range(A):
|
| 141 |
+
if i != j: src.append(j); dst.append(i)
|
| 142 |
+
fg._single_edge_index = torch.tensor([src, dst], dtype=torch.long, device=device)
|
| 143 |
+
|
| 144 |
+
# --- Build y_emb [B, K, A, D] from past + y0 + step ---
|
| 145 |
+
past_feat = self.past_proj(past.reshape(BA, -1)) # [BA, D]
|
| 146 |
+
past_feat = past_feat.unsqueeze(1).expand(BA, K, self.D) # [BA, K, D]
|
| 147 |
+
y0_feat = self.y0_proj(y0_hat.reshape(BA, K, T * 2)) # [BA, K, D]
|
| 148 |
+
step = self.step_emb(torch.tensor(step_idx, device=device)) # [D]
|
| 149 |
+
nodes_bka = past_feat + y0_feat + step # [BA, K, D]
|
| 150 |
+
y_emb = nodes_bka.view(B, A, K, self.D).permute(0, 2, 1, 3).contiguous() # [B, K, A, D]
|
| 151 |
+
|
| 152 |
+
# --- y_abs [B, K, A, T, 2] (unnormalized future positions) ---
|
| 153 |
+
y_abs = y0_hat.view(B, A, K, T, 2).permute(0, 2, 1, 3, 4).contiguous()
|
| 154 |
+
|
| 155 |
+
# --- t_emb [B, D] and tau [B] ---
|
| 156 |
+
t_emb = self.step_emb(torch.tensor(step_idx, device=device)).unsqueeze(0).expand(B, -1) # [B, D]
|
| 157 |
+
tau = torch.full((B,), float(step_idx), dtype=torch.float32, device=device)
|
| 158 |
+
|
| 159 |
+
# --- sigma_agent [B, K, A, T] or None ---
|
| 160 |
+
# LED's variance_estimation is per-agent scalar [BA, 1]; broadcast over K and T.
|
| 161 |
+
sigma_agent = None
|
| 162 |
+
if sigma is not None:
|
| 163 |
+
sigma_ba = sigma.view(B, A) # [B, A]
|
| 164 |
+
sigma_agent = sigma_ba.view(B, 1, A, 1).expand(B, K, A, T)
|
| 165 |
+
|
| 166 |
+
# --- Run V6 graph: scoring, sparse edges, RelTrajEncoder, GNN, gated residual ---
|
| 167 |
+
y_emb_refined = self.future_graph(y_emb, y_abs, t_emb, tau, sigma_agent=sigma_agent)
|
| 168 |
+
# y_emb_refined: [B, K, A, D]
|
| 169 |
+
|
| 170 |
+
# --- Decode to per-mode per-agent trajectory residual ---
|
| 171 |
+
refined_bka = y_emb_refined.permute(0, 2, 1, 3).contiguous().view(BA, K, self.D)
|
| 172 |
+
residual = self.decode(refined_bka).view(BA, K, T, 2)
|
| 173 |
+
return residual
|
LED/models/interaction_baselines.py
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
E4 plug-in baselines — alternative interaction/refinement modules that honor the EXACT
|
| 3 |
+
FutureInteractionGraphV6 forward contract, for a protocol-matched comparison vs SRA:
|
| 4 |
+
|
| 5 |
+
forward(y_emb[B,K,A,D], y_abs[B,K,A,T,2], t_emb[B,D], tau[B],
|
| 6 |
+
sigma_agent[B,K,A,T]|None, agent_mask[B,A]|None=None) -> [B,K,A,D]
|
| 7 |
+
(gated residual, embedding->embedding, NO sigma out; robust to K in {1,10,20}, any A)
|
| 8 |
+
|
| 9 |
+
Modules:
|
| 10 |
+
* GameFormerInteraction [ref 20]: FAITHFUL GameFormer level-k InteractionDecoder —
|
| 11 |
+
host denoiser = InitialDecoder (level 0); denoiser K samples = GameFormer K modes;
|
| 12 |
+
FutureEncoder + score-weighted mode aggregation + interaction self-attention +
|
| 13 |
+
own-future-masked cross-attention + L-level GMM refinement. No neighbor selection,
|
| 14 |
+
no uncertainty (that is SRA). See the detailed faithfulness note above the class.
|
| 15 |
+
* CoarseToFineRefine [ref 22]: Coarse-to-Fine (Jia et al. 2022) TEMPORAL refinement —
|
| 16 |
+
a per-agent temporal module (GRU / 1D-CNN over the future horizon). No interaction.
|
| 17 |
+
Orthogonal axis (temporal).
|
| 18 |
+
|
| 19 |
+
Both accept the SAME __init__ kwargs as V6 (extras absorbed by **kwargs) and return the SAME
|
| 20 |
+
gated-residual embedding, so they are true drop-ins at each host's V6 instantiation line.
|
| 21 |
+
Selection is via env var SRA_MODULE in {sra, gameformer, c2f} (see build_interaction_module).
|
| 22 |
+
"""
|
| 23 |
+
import os
|
| 24 |
+
import torch
|
| 25 |
+
import torch.nn as nn
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class _GatedResidualHead(nn.Module):
|
| 29 |
+
"""V6's output convention: out = input + gate(cat[input,refined]) * out_proj(refined).
|
| 30 |
+
Random-initialised (active at init), exactly like V6 — hosts that need no-op-at-init
|
| 31 |
+
(MID/LED) supply their own external zero-init projection; MoFlow uses the output directly."""
|
| 32 |
+
def __init__(self, embed_dim):
|
| 33 |
+
super().__init__()
|
| 34 |
+
self.gate_proj = nn.Sequential(nn.Linear(2 * embed_dim, embed_dim), nn.Sigmoid())
|
| 35 |
+
self.out_proj = nn.Linear(embed_dim, embed_dim)
|
| 36 |
+
nn.init.zeros_(self.out_proj.weight) # NO-OP AT INIT: module starts as identity
|
| 37 |
+
nn.init.zeros_(self.out_proj.bias) # (stabilizes MoFlow, which uses output directly)
|
| 38 |
+
|
| 39 |
+
def forward(self, orig, refined): # both [N, D]
|
| 40 |
+
gate = self.gate_proj(torch.cat([orig, refined], dim=-1))
|
| 41 |
+
res = self.out_proj(refined)
|
| 42 |
+
_cap = float(os.environ.get('MOFLOW_DAMP', 0) or 0) # cap residual norm -> MoFlow flow-field stability
|
| 43 |
+
if _cap > 0:
|
| 44 |
+
rn = res.norm(dim=-1, keepdim=True)
|
| 45 |
+
res = res * (rn.clamp(max=_cap) / (rn + 1e-6))
|
| 46 |
+
return orig + gate * res
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
# ============================================================================
|
| 50 |
+
# GameFormer [ref 20] -- FAITHFUL level-k InteractionDecoder as a denoiser plug-in
|
| 51 |
+
# ============================================================================
|
| 52 |
+
# Faithful to Liu et al. ICCV'23 (github.com/MCZhi/GameFormer, model/modules.py):
|
| 53 |
+
# * The host DENOISER plays the InitialDecoder (level 0): its current estimate
|
| 54 |
+
# (y_emb, y_abs) IS the level-0 prediction, and the denoiser's K SAMPLES are
|
| 55 |
+
# treated as GameFormer's K MODES (the modal set the level-k step reasons over).
|
| 56 |
+
# * Each InteractionDecoder level implements the real mechanism:
|
| 57 |
+
# - FutureEncoder: MLP on per-mode [x,y,heading,vx,vy], max-pooled over time
|
| 58 |
+
# (modules.FutureEncoder; box-size channels dropped -- no size in NBA/sport).
|
| 59 |
+
# - score-softmax-weighted aggregation of the K modes -> one future token/agent.
|
| 60 |
+
# - interaction SelfTransformer over agents (game-theoretic "condition on all
|
| 61 |
+
# other agents' level-(k-1) futures").
|
| 62 |
+
# - cross-attention of each mode's content (prev content + own future) to
|
| 63 |
+
# [interaction ; scene-context], with the agent's OWN future token MASKED
|
| 64 |
+
# (GameFormer's own-future masking).
|
| 65 |
+
# - a GMM-mu trajectory decoder emits the level-k refinement; iterate L levels.
|
| 66 |
+
# * The final level's content -> gated residual to the host embedding.
|
| 67 |
+
# ONE gap vs the paper: per-level imitation SUPERVISION (GameFormer sums a GMM loss
|
| 68 |
+
# over every level). A plug-in is trained only through the host denoising loss, so
|
| 69 |
+
# the levels are learned end-to-end rather than level-wise-supervised.
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
class _AgentSelfAttn(nn.Module):
|
| 73 |
+
"""SelfTransformer over agents (modules.SelfTransformer). x: [B, A, D]."""
|
| 74 |
+
def __init__(self, dim, heads, dropout):
|
| 75 |
+
super().__init__()
|
| 76 |
+
self.heads = heads
|
| 77 |
+
self.attn = nn.MultiheadAttention(dim, heads, dropout=dropout, batch_first=True)
|
| 78 |
+
self.n1 = nn.LayerNorm(dim); self.n2 = nn.LayerNorm(dim)
|
| 79 |
+
self.ffn = nn.Sequential(nn.Linear(dim, dim * 4), nn.GELU(),
|
| 80 |
+
nn.Dropout(dropout), nn.Linear(dim * 4, dim))
|
| 81 |
+
|
| 82 |
+
def forward(self, x, key_padding_mask=None):
|
| 83 |
+
a, _ = self.attn(x, x, x, key_padding_mask=key_padding_mask, need_weights=False)
|
| 84 |
+
x = self.n1(a + x)
|
| 85 |
+
return self.n2(self.ffn(x) + x)
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
class _CrossAttn(nn.Module):
|
| 89 |
+
"""CrossTransformer (modules.CrossTransformer). q:[N,Lq,D] k/v:[N,Lk,D]."""
|
| 90 |
+
def __init__(self, dim, heads, dropout):
|
| 91 |
+
super().__init__()
|
| 92 |
+
self.heads = heads
|
| 93 |
+
self.attn = nn.MultiheadAttention(dim, heads, dropout=dropout, batch_first=True)
|
| 94 |
+
self.n1 = nn.LayerNorm(dim); self.n2 = nn.LayerNorm(dim)
|
| 95 |
+
self.ffn = nn.Sequential(nn.Linear(dim, dim * 4), nn.GELU(),
|
| 96 |
+
nn.Dropout(dropout), nn.Linear(dim * 4, dim))
|
| 97 |
+
|
| 98 |
+
def forward(self, q, k, v, attn_mask=None):
|
| 99 |
+
a, _ = self.attn(q, k, v, attn_mask=attn_mask, need_weights=False)
|
| 100 |
+
a = self.n1(a)
|
| 101 |
+
return self.n2(self.ffn(a) + a)
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
class _FutureEncoder(nn.Module):
|
| 105 |
+
"""modules.FutureEncoder: per-mode future -> vector via max-pool over an MLP on
|
| 106 |
+
[x, y, heading, vx, vy]. trajs [B,K,A,T,2] (K modes), cur_xy [B,K,A,2]."""
|
| 107 |
+
def __init__(self, dim, dt=0.2):
|
| 108 |
+
super().__init__()
|
| 109 |
+
self.dt = dt
|
| 110 |
+
self.mlp = nn.Sequential(nn.Linear(5, 64), nn.ReLU(inplace=True), nn.Linear(64, dim))
|
| 111 |
+
|
| 112 |
+
def forward(self, trajs, cur_xy):
|
| 113 |
+
pos = trajs - cur_xy.unsqueeze(-2) # centered position
|
| 114 |
+
xy = torch.cat([cur_xy.unsqueeze(-2), trajs], dim=-2) # [B,K,A,T+1,2]
|
| 115 |
+
dxy = torch.diff(xy, dim=-2)
|
| 116 |
+
v = dxy / self.dt
|
| 117 |
+
theta = torch.atan2(dxy[..., 1], dxy[..., 0].clamp(min=1e-3)).unsqueeze(-1)
|
| 118 |
+
state = torch.cat([pos, theta, v], dim=-1) # [B,K,A,T,5]
|
| 119 |
+
h = self.mlp(state.detach()) # GameFormer DETACHES future feats
|
| 120 |
+
return h.max(dim=-2).values # [B,K,A,D]
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
class GameFormerInteraction(nn.Module):
|
| 124 |
+
"""FAITHFUL GameFormer level-k InteractionDecoder as a denoiser plug-in (see header)."""
|
| 125 |
+
|
| 126 |
+
def __init__(self, embed_dim, future_steps, num_agents,
|
| 127 |
+
num_heads=4, dropout=0.1, num_gnn_layers=2, time_dim=128,
|
| 128 |
+
top_n_neighbors=5, rel_traj_hidden=32, y0_score_dim=32,
|
| 129 |
+
edge_mode='full', neighbor_mode='rag', num_levels=None, **kwargs):
|
| 130 |
+
super().__init__()
|
| 131 |
+
self.embed_dim = embed_dim
|
| 132 |
+
self.num_agents = num_agents # LED wrapper reads this
|
| 133 |
+
L = int(os.environ.get('GF_LEVELS', num_levels if num_levels is not None else 3))
|
| 134 |
+
self.num_levels = L
|
| 135 |
+
self.t_proj = nn.Linear(embed_dim, embed_dim)
|
| 136 |
+
self.future_encoder = _FutureEncoder(embed_dim) # SHARED across levels
|
| 137 |
+
self.score_head = nn.ModuleList([ # per-mode score -> softmax weight
|
| 138 |
+
nn.Sequential(nn.Linear(embed_dim, 64), nn.ELU(), nn.Linear(64, 1)) for _ in range(L)])
|
| 139 |
+
self.interaction_enc = nn.ModuleList([_AgentSelfAttn(embed_dim, num_heads, dropout) for _ in range(L)])
|
| 140 |
+
self.query_enc = nn.ModuleList([_CrossAttn(embed_dim, num_heads, dropout) for _ in range(L)])
|
| 141 |
+
self.traj_decode = nn.ModuleList([nn.Linear(embed_dim, future_steps * 2) for _ in range(L)])
|
| 142 |
+
for dec in self.traj_decode: # each level starts as identity (stable)
|
| 143 |
+
nn.init.zeros_(dec.weight); nn.init.zeros_(dec.bias)
|
| 144 |
+
self.head = _GatedResidualHead(embed_dim)
|
| 145 |
+
|
| 146 |
+
def forward(self, y_emb, y_abs, t_emb, tau, sigma_agent=None, agent_mask=None):
|
| 147 |
+
B, K, A, D = y_emb.shape
|
| 148 |
+
T = y_abs.shape[3]
|
| 149 |
+
if A <= 1:
|
| 150 |
+
return y_emb
|
| 151 |
+
dev = y_emb.device
|
| 152 |
+
cur_xy = y_abs[..., 0, :] # [B,K,A,2] level-0 reference (1st future step)
|
| 153 |
+
y_traj = y_abs # [B,K,A,T,2] current (level-0) future
|
| 154 |
+
t = self.t_proj(t_emb).view(B, 1, 1, D) # denoiser timestep context
|
| 155 |
+
agent_ctx = y_emb.mean(dim=1) + t.squeeze(1) # [B,A,D] fixed scene context (over modes)
|
| 156 |
+
kp = (~agent_mask.bool()) if agent_mask is not None else None # [B,A] True=pad
|
| 157 |
+
ar = torch.arange(A, device=dev)
|
| 158 |
+
heads = self.query_enc[0].heads
|
| 159 |
+
|
| 160 |
+
content = y_emb # level-0 content = host embedding
|
| 161 |
+
for l in range(self.num_levels):
|
| 162 |
+
multi_fut = self.future_encoder(y_traj, cur_xy) # [B,K,A,D] per-mode future
|
| 163 |
+
w = self.score_head[l](multi_fut).softmax(dim=1) # [B,K,A,1] over K modes
|
| 164 |
+
agg_fut = (multi_fut * w).sum(dim=1) # [B,A,D] aggregated future
|
| 165 |
+
interaction = self.interaction_enc[l](agg_fut, kp) # [B,A,D] game-theoretic
|
| 166 |
+
ctx = torch.cat([interaction, agent_ctx], dim=1) # [B,2A,D]
|
| 167 |
+
|
| 168 |
+
q = (content + multi_fut + t).reshape(B, K * A, D) # prev content + own future + t
|
| 169 |
+
am = torch.zeros(B, K, A, 2 * A, dtype=torch.bool, device=dev)
|
| 170 |
+
am[:, :, ar, ar] = True # mask OWN future (interaction block)
|
| 171 |
+
if agent_mask is not None:
|
| 172 |
+
pad = (~agent_mask.bool())[:, None, None, :] # [B,1,1,A]
|
| 173 |
+
am[..., :A] = am[..., :A] | pad
|
| 174 |
+
am[..., A:] = am[..., A:] | pad
|
| 175 |
+
am = am.reshape(B, K * A, 2 * A)[:, None].expand(
|
| 176 |
+
B, heads, K * A, 2 * A).reshape(B * heads, K * A, 2 * A)
|
| 177 |
+
content = self.query_enc[l](q, ctx, ctx, am).reshape(B, K, A, D)
|
| 178 |
+
y_traj = y_traj + self.traj_decode[l](content).view(B, K, A, T, 2) # level-k refinement
|
| 179 |
+
refined = content.reshape(B * K * A, D)
|
| 180 |
+
orig = y_emb.reshape(B * K * A, D)
|
| 181 |
+
return self.head(orig, refined).view(B, K, A, D)
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
class CoarseToFineRefine(nn.Module):
|
| 185 |
+
"""Coarse-to-Fine temporal refiner: per-agent GRU/1D-CNN over the future horizon (no interaction)."""
|
| 186 |
+
|
| 187 |
+
def __init__(self, embed_dim, future_steps, num_agents,
|
| 188 |
+
num_heads=4, dropout=0.1, num_gnn_layers=2, time_dim=128,
|
| 189 |
+
top_n_neighbors=5, rel_traj_hidden=32, y0_score_dim=32,
|
| 190 |
+
edge_mode='full', neighbor_mode='rag', refine_type='gru', hidden=200, **kwargs):
|
| 191 |
+
super().__init__()
|
| 192 |
+
self.embed_dim = embed_dim
|
| 193 |
+
self.num_agents = num_agents # LED wrapper reads this (skips variable-A rebuild when ==A)
|
| 194 |
+
self.refine_type = os.environ.get('C2F_REFINE', refine_type)
|
| 195 |
+
self.pos_emb = nn.Linear(2, hidden)
|
| 196 |
+
if self.refine_type == 'cnn': # 1D-CNN temporal refiner (per-timestep output)
|
| 197 |
+
self.temporal = nn.Sequential(
|
| 198 |
+
nn.Conv1d(hidden, hidden, 3, padding=1), nn.ReLU(inplace=True),
|
| 199 |
+
nn.Conv1d(hidden, hidden, 3, padding=1), nn.ReLU(inplace=True),
|
| 200 |
+
nn.Conv1d(hidden, hidden, 3, padding=1), nn.ReLU(inplace=True))
|
| 201 |
+
else: # AUTOREGRESSIVE (unidirectional) GRU
|
| 202 |
+
self.temporal = nn.GRU(hidden, hidden, num_layers=2, batch_first=True, dropout=dropout)
|
| 203 |
+
self.delta_head = nn.Linear(hidden, 2) # per-timestep coarse->fine correction Δ_t
|
| 204 |
+
self.traj_encode = nn.Sequential( # re-encode the FINE trajectory -> host feature
|
| 205 |
+
nn.Linear(future_steps * 2, embed_dim), nn.ReLU(inplace=True),
|
| 206 |
+
nn.Linear(embed_dim, embed_dim))
|
| 207 |
+
self.t_proj = nn.Linear(embed_dim, embed_dim)
|
| 208 |
+
self.head = _GatedResidualHead(embed_dim)
|
| 209 |
+
|
| 210 |
+
def forward(self, y_emb, y_abs, t_emb, tau, sigma_agent=None, agent_mask=None):
|
| 211 |
+
# Faithful coarse-to-fine: host prediction = COARSE trajectory; walk it temporally
|
| 212 |
+
# (autoregressive GRU / 1D-CNN) emitting a per-timestep correction Δ_t -> FINE trajectory,
|
| 213 |
+
# then re-encode the fine trajectory into the host residual. Per-agent (no interaction).
|
| 214 |
+
B, K, A, D = y_emb.shape
|
| 215 |
+
T = y_abs.shape[3]
|
| 216 |
+
N = B * K * A
|
| 217 |
+
y_coarse = (y_abs - y_abs[..., :1, :]).reshape(N, T, 2) # coarse trajectory (centered)
|
| 218 |
+
seq = self.pos_emb(y_coarse) # [N, T, H]
|
| 219 |
+
if self.refine_type == 'cnn':
|
| 220 |
+
h = self.temporal(seq.transpose(1, 2)).transpose(1, 2) # [N, T, H]
|
| 221 |
+
else:
|
| 222 |
+
h, _ = self.temporal(seq) # [N, T, H] per-timestep (autoregressive)
|
| 223 |
+
delta = self.delta_head(h) # [N, T, 2] coarse->fine correction
|
| 224 |
+
y_fine = y_coarse + delta # refined (fine) trajectory
|
| 225 |
+
refined = self.traj_encode(y_fine.reshape(N, T * 2)) # re-encode fine traj -> feature
|
| 226 |
+
t = self.t_proj(t_emb).view(B, 1, 1, D).expand(B, K, A, D).reshape(N, D)
|
| 227 |
+
refined = refined + t
|
| 228 |
+
orig = y_emb.reshape(N, D)
|
| 229 |
+
return self.head(orig, refined).view(B, K, A, D)
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def build_interaction_module(name=None, **kwargs):
|
| 233 |
+
"""Factory used at each host's V6 instantiation line. `name` defaults to env SRA_MODULE
|
| 234 |
+
(then 'sra'). SRA branch imports the real V6 and forwards only kwargs it accepts."""
|
| 235 |
+
name = (name or os.environ.get('SRA_MODULE') or 'sra').lower()
|
| 236 |
+
if name in ('sra', 'v6', 'graph'):
|
| 237 |
+
import inspect
|
| 238 |
+
from models.graph_interaction_nba_v6 import FutureInteractionGraphV6
|
| 239 |
+
allowed = set(inspect.signature(FutureInteractionGraphV6.__init__).parameters)
|
| 240 |
+
v6kw = {k: v for k, v in kwargs.items() if k in allowed}
|
| 241 |
+
return FutureInteractionGraphV6(**v6kw)
|
| 242 |
+
if name in ('gameformer', 'gf', 'gameformer_int'):
|
| 243 |
+
return GameFormerInteraction(**kwargs)
|
| 244 |
+
if name in ('c2f', 'coarse2fine', 'coarsetofine'):
|
| 245 |
+
return CoarseToFineRefine(**kwargs)
|
| 246 |
+
raise ValueError(f"unknown SRA_MODULE / interaction_module: {name!r}")
|
LED/models/layers.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
import torch
|
| 3 |
+
import torch.nn as nn
|
| 4 |
+
from torch.nn import Module, Linear
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class PositionalEncoding(nn.Module):
|
| 8 |
+
def __init__(self, d_model, dropout=0.1, max_len=5000):
|
| 9 |
+
super().__init__()
|
| 10 |
+
|
| 11 |
+
self.dropout = nn.Dropout(p=dropout)
|
| 12 |
+
pe = torch.zeros(max_len, d_model)
|
| 13 |
+
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
|
| 14 |
+
div_term = torch.exp(
|
| 15 |
+
torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model)
|
| 16 |
+
)
|
| 17 |
+
pe[:, 0::2] = torch.sin(position * div_term)
|
| 18 |
+
pe[:, 1::2] = torch.cos(position * div_term)
|
| 19 |
+
pe = pe.unsqueeze(0).transpose(0, 1)
|
| 20 |
+
self.register_buffer("pe", pe)
|
| 21 |
+
|
| 22 |
+
def forward(self, x):
|
| 23 |
+
x = x + self.pe[: x.size(0), :]
|
| 24 |
+
return self.dropout(x)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class ConcatSquashLinear(Module):
|
| 28 |
+
def __init__(self, dim_in, dim_out, dim_ctx):
|
| 29 |
+
super(ConcatSquashLinear, self).__init__()
|
| 30 |
+
self._layer = Linear(dim_in, dim_out)
|
| 31 |
+
self._hyper_bias = Linear(dim_ctx, dim_out, bias=False)
|
| 32 |
+
self._hyper_gate = Linear(dim_ctx, dim_out)
|
| 33 |
+
|
| 34 |
+
def forward(self, ctx, x):
|
| 35 |
+
# ctx: (B, 1, F+3)
|
| 36 |
+
# x: (B, T, 2)
|
| 37 |
+
gate = torch.sigmoid(self._hyper_gate(ctx))
|
| 38 |
+
bias = self._hyper_bias(ctx)
|
| 39 |
+
# if x.dim() == 3:
|
| 40 |
+
# gate = gate.unsqueeze(1)
|
| 41 |
+
# bias = bias.unsqueeze(1)
|
| 42 |
+
ret = self._layer(x) * gate + bias
|
| 43 |
+
return ret
|
| 44 |
+
|
| 45 |
+
def batch_generate(self, ctx, x):
|
| 46 |
+
# ctx: (B, n, 1, F+3)
|
| 47 |
+
# x: (B, n, T, 2)
|
| 48 |
+
gate = torch.sigmoid(self._hyper_gate(ctx))
|
| 49 |
+
bias = self._hyper_bias(ctx)
|
| 50 |
+
# if x.dim() == 3:
|
| 51 |
+
# gate = gate.unsqueeze(1)
|
| 52 |
+
# bias = bias.unsqueeze(1)
|
| 53 |
+
ret = self._layer(x) * gate + bias
|
| 54 |
+
return ret
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
class GAT(nn.Module):
|
| 58 |
+
def __init__(self, in_feat=2, out_feat=64, n_head=4, dropout=0.1, skip=True):
|
| 59 |
+
super(GAT, self).__init__()
|
| 60 |
+
self.in_feat = in_feat
|
| 61 |
+
self.out_feat = out_feat
|
| 62 |
+
self.n_head = n_head
|
| 63 |
+
self.skip = skip
|
| 64 |
+
self.w = nn.Parameter(torch.Tensor(n_head, in_feat, out_feat))
|
| 65 |
+
self.a_src = nn.Parameter(torch.Tensor(n_head, out_feat, 1))
|
| 66 |
+
self.a_dst = nn.Parameter(torch.Tensor(n_head, out_feat, 1))
|
| 67 |
+
self.bias = nn.Parameter(torch.Tensor(out_feat))
|
| 68 |
+
|
| 69 |
+
self.leaky_relu = nn.LeakyReLU(negative_slope=0.2)
|
| 70 |
+
self.softmax = nn.Softmax(dim=-1)
|
| 71 |
+
self.dropout = nn.Dropout(dropout)
|
| 72 |
+
|
| 73 |
+
nn.init.xavier_uniform_(self.w, gain=1.414)
|
| 74 |
+
nn.init.xavier_uniform_(self.a_src, gain=1.414)
|
| 75 |
+
nn.init.xavier_uniform_(self.a_dst, gain=1.414)
|
| 76 |
+
nn.init.constant_(self.bias, 0)
|
| 77 |
+
|
| 78 |
+
def forward(self, h, mask):
|
| 79 |
+
h_prime = h.unsqueeze(1) @ self.w
|
| 80 |
+
attn_src = h_prime @ self.a_src
|
| 81 |
+
attn_dst = h_prime @ self.a_dst
|
| 82 |
+
attn = attn_src @ attn_dst.permute(0, 1, 3, 2)
|
| 83 |
+
attn = self.leaky_relu(attn)
|
| 84 |
+
attn = self.softmax(attn)
|
| 85 |
+
attn = self.dropout(attn)
|
| 86 |
+
attn = attn * mask if mask is not None else attn
|
| 87 |
+
out = (attn @ h_prime).sum(dim=1) + self.bias
|
| 88 |
+
if self.skip:
|
| 89 |
+
out += h_prime.sum(dim=1)
|
| 90 |
+
return out, attn
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
class MLP(nn.Module):
|
| 94 |
+
def __init__(self, in_feat, out_feat, hid_feat=(1024, 512), activation=None, dropout=-1):
|
| 95 |
+
super(MLP, self).__init__()
|
| 96 |
+
dims = (in_feat, ) + hid_feat + (out_feat, )
|
| 97 |
+
|
| 98 |
+
self.layers = nn.ModuleList()
|
| 99 |
+
for i in range(len(dims) - 1):
|
| 100 |
+
self.layers.append(nn.Linear(dims[i], dims[i + 1]))
|
| 101 |
+
|
| 102 |
+
self.activation = activation if activation is not None else lambda x: x
|
| 103 |
+
self.dropout = nn.Dropout(dropout) if dropout != -1 else lambda x: x
|
| 104 |
+
|
| 105 |
+
def forward(self, x):
|
| 106 |
+
for i in range(len(self.layers)):
|
| 107 |
+
x = self.activation(x)
|
| 108 |
+
x = self.dropout(x)
|
| 109 |
+
x = self.layers[i](x)
|
| 110 |
+
return x
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
class social_transformer(nn.Module):
|
| 114 |
+
def __init__(self, past_len):
|
| 115 |
+
super(social_transformer, self).__init__()
|
| 116 |
+
self.encode_past = nn.Linear(past_len*6, 256, bias=False)
|
| 117 |
+
self.layer = nn.TransformerEncoderLayer(d_model=256, nhead=2, dim_feedforward=256)
|
| 118 |
+
self.transformer_encoder = nn.TransformerEncoder(self.layer, num_layers=2)
|
| 119 |
+
|
| 120 |
+
def forward(self, h, mask):
|
| 121 |
+
'''
|
| 122 |
+
h: batch_size, t, 2
|
| 123 |
+
'''
|
| 124 |
+
h_feat = self.encode_past(h.reshape(h.size(0), -1)).unsqueeze(1)
|
| 125 |
+
# print(h_feat.shape)
|
| 126 |
+
# n_samples, 1, 64
|
| 127 |
+
h_feat_ = self.transformer_encoder(h_feat, mask)
|
| 128 |
+
h_feat = h_feat + h_feat_
|
| 129 |
+
|
| 130 |
+
return h_feat
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
class st_encoder(nn.Module):
|
| 134 |
+
def __init__(self):
|
| 135 |
+
super().__init__()
|
| 136 |
+
channel_in = 6
|
| 137 |
+
channel_out = 32
|
| 138 |
+
dim_kernel = 3
|
| 139 |
+
self.dim_embedding_key = 256
|
| 140 |
+
self.spatial_conv = nn.Conv1d(channel_in, channel_out, dim_kernel, stride=1, padding=1)
|
| 141 |
+
self.temporal_encoder = nn.GRU(channel_out, self.dim_embedding_key, 1, batch_first=True)
|
| 142 |
+
|
| 143 |
+
self.relu = nn.ReLU()
|
| 144 |
+
|
| 145 |
+
self.reset_parameters()
|
| 146 |
+
|
| 147 |
+
def reset_parameters(self):
|
| 148 |
+
nn.init.kaiming_normal_(self.spatial_conv.weight)
|
| 149 |
+
nn.init.kaiming_normal_(self.temporal_encoder.weight_ih_l0)
|
| 150 |
+
nn.init.kaiming_normal_(self.temporal_encoder.weight_hh_l0)
|
| 151 |
+
nn.init.zeros_(self.spatial_conv.bias)
|
| 152 |
+
nn.init.zeros_(self.temporal_encoder.bias_ih_l0)
|
| 153 |
+
nn.init.zeros_(self.temporal_encoder.bias_hh_l0)
|
| 154 |
+
|
| 155 |
+
def forward(self, X):
|
| 156 |
+
'''
|
| 157 |
+
X: b, T, 2
|
| 158 |
+
|
| 159 |
+
return: b, F
|
| 160 |
+
'''
|
| 161 |
+
X_t = torch.transpose(X, 1, 2)
|
| 162 |
+
X_after_spatial = self.relu(self.spatial_conv(X_t))
|
| 163 |
+
X_embed = torch.transpose(X_after_spatial, 1, 2)
|
| 164 |
+
|
| 165 |
+
output_x, state_x = self.temporal_encoder(X_embed)
|
| 166 |
+
state_x = state_x.squeeze(0)
|
| 167 |
+
|
| 168 |
+
return state_x
|
| 169 |
+
|
LED/models/model_diffusion.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
import torch
|
| 3 |
+
import torch.nn as nn
|
| 4 |
+
from torch.nn import Module, Linear
|
| 5 |
+
|
| 6 |
+
from models.layers import PositionalEncoding, ConcatSquashLinear
|
| 7 |
+
|
| 8 |
+
class st_encoder(nn.Module):
|
| 9 |
+
def __init__(self):
|
| 10 |
+
super().__init__()
|
| 11 |
+
channel_in = 2
|
| 12 |
+
channel_out = 32
|
| 13 |
+
dim_kernel = 3
|
| 14 |
+
self.dim_embedding_key = 256
|
| 15 |
+
self.spatial_conv = nn.Conv1d(channel_in, channel_out, dim_kernel, stride=1, padding=1)
|
| 16 |
+
self.temporal_encoder = nn.GRU(channel_out, self.dim_embedding_key, 1, batch_first=True)
|
| 17 |
+
|
| 18 |
+
self.relu = nn.ReLU()
|
| 19 |
+
|
| 20 |
+
self.reset_parameters()
|
| 21 |
+
|
| 22 |
+
def reset_parameters(self):
|
| 23 |
+
nn.init.kaiming_normal_(self.spatial_conv.weight)
|
| 24 |
+
nn.init.kaiming_normal_(self.temporal_encoder.weight_ih_l0)
|
| 25 |
+
nn.init.kaiming_normal_(self.temporal_encoder.weight_hh_l0)
|
| 26 |
+
nn.init.zeros_(self.spatial_conv.bias)
|
| 27 |
+
nn.init.zeros_(self.temporal_encoder.bias_ih_l0)
|
| 28 |
+
nn.init.zeros_(self.temporal_encoder.bias_hh_l0)
|
| 29 |
+
|
| 30 |
+
def forward(self, X):
|
| 31 |
+
'''
|
| 32 |
+
X: b, T, 2
|
| 33 |
+
|
| 34 |
+
return: b, F
|
| 35 |
+
'''
|
| 36 |
+
X_t = torch.transpose(X, 1, 2)
|
| 37 |
+
X_after_spatial = self.relu(self.spatial_conv(X_t))
|
| 38 |
+
X_embed = torch.transpose(X_after_spatial, 1, 2)
|
| 39 |
+
|
| 40 |
+
output_x, state_x = self.temporal_encoder(X_embed)
|
| 41 |
+
state_x = state_x.squeeze(0)
|
| 42 |
+
|
| 43 |
+
return state_x
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class social_transformer(nn.Module):
|
| 47 |
+
def __init__(self, past_len=10, in_channels=6):
|
| 48 |
+
super(social_transformer, self).__init__()
|
| 49 |
+
self.encode_past = nn.Linear(past_len * in_channels, 256, bias=False)
|
| 50 |
+
self.layer = nn.TransformerEncoderLayer(d_model=256, nhead=2, dim_feedforward=256)
|
| 51 |
+
self.transformer_encoder = nn.TransformerEncoder(self.layer, num_layers=2)
|
| 52 |
+
|
| 53 |
+
def forward(self, h, mask):
|
| 54 |
+
'''
|
| 55 |
+
h: batch_size, t, 2
|
| 56 |
+
'''
|
| 57 |
+
# print(h.shape)
|
| 58 |
+
h_feat = self.encode_past(h.reshape(h.size(0), -1)).unsqueeze(1)
|
| 59 |
+
# print(h_feat.shape)
|
| 60 |
+
# n_samples, 1, 64
|
| 61 |
+
h_feat_ = self.transformer_encoder(h_feat, mask)
|
| 62 |
+
h_feat = h_feat + h_feat_
|
| 63 |
+
|
| 64 |
+
return h_feat
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
class TransformerDenoisingModel(Module):
|
| 68 |
+
|
| 69 |
+
def __init__(self, context_dim=256, tf_layer=2, past_len=10):
|
| 70 |
+
super().__init__()
|
| 71 |
+
self.encoder_context = social_transformer(past_len=past_len)
|
| 72 |
+
self.pos_emb = PositionalEncoding(d_model=2*context_dim, dropout=0.1, max_len=24)
|
| 73 |
+
self.concat1 = ConcatSquashLinear(2, 2*context_dim, context_dim+3)
|
| 74 |
+
self.layer = nn.TransformerEncoderLayer(d_model=2*context_dim, nhead=2, dim_feedforward=2*context_dim)
|
| 75 |
+
self.transformer_encoder = nn.TransformerEncoder(self.layer, num_layers=tf_layer)
|
| 76 |
+
self.concat3 = ConcatSquashLinear(2*context_dim,context_dim,context_dim+3)
|
| 77 |
+
self.concat4 = ConcatSquashLinear(context_dim,context_dim//2,context_dim+3)
|
| 78 |
+
self.linear = ConcatSquashLinear(context_dim//2, 2, context_dim+3)
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def forward(self, x, beta, context, mask):
|
| 82 |
+
batch_size = x.size(0)
|
| 83 |
+
beta = beta.view(batch_size, 1, 1) # (B, 1, 1)
|
| 84 |
+
mask = mask.float().masked_fill(mask == 0, float('-inf')).masked_fill(mask == 1, float(0.0))
|
| 85 |
+
context = self.encoder_context(context, mask)
|
| 86 |
+
# context = context.view(batch_size, 1, -1) # (B, 1, F)
|
| 87 |
+
|
| 88 |
+
time_emb = torch.cat([beta, torch.sin(beta), torch.cos(beta)], dim=-1) # (B, 1, 3)
|
| 89 |
+
ctx_emb = torch.cat([time_emb, context], dim=-1) # (B, 1, F+3)
|
| 90 |
+
|
| 91 |
+
x = self.concat1(ctx_emb, x)
|
| 92 |
+
final_emb = x.permute(1,0,2)
|
| 93 |
+
final_emb = self.pos_emb(final_emb)
|
| 94 |
+
|
| 95 |
+
trans = self.transformer_encoder(final_emb).permute(1,0,2)
|
| 96 |
+
trans = self.concat3(ctx_emb, trans)
|
| 97 |
+
trans = self.concat4(ctx_emb, trans)
|
| 98 |
+
return self.linear(ctx_emb, trans)
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def generate_accelerate(self, x, beta, context, mask):
|
| 102 |
+
batch_size = x.size(0)
|
| 103 |
+
beta = beta.view(beta.size(0), 1, 1) # (B, 1, 1)
|
| 104 |
+
mask = mask.float().masked_fill(mask == 0, float('-inf')).masked_fill(mask == 1, float(0.0))
|
| 105 |
+
context = self.encoder_context(context, mask)
|
| 106 |
+
# context = context.view(batch_size, 1, -1) # (B, 1, F)
|
| 107 |
+
|
| 108 |
+
time_emb = torch.cat([beta, torch.sin(beta), torch.cos(beta)], dim=-1) # (B, 1, 3)
|
| 109 |
+
# time_emb: [11, 1, 3]
|
| 110 |
+
# context: [11, 1, 256]
|
| 111 |
+
ctx_emb = torch.cat([time_emb, context], dim=-1).repeat(1, 10, 1).unsqueeze(2)
|
| 112 |
+
# x: 11, 10, 20, 2
|
| 113 |
+
# ctx_emb: 11, 10, 1, 259
|
| 114 |
+
K = x.size(1)
|
| 115 |
+
T = x.size(2)
|
| 116 |
+
D = 2 * 256
|
| 117 |
+
x = self.concat1.batch_generate(ctx_emb, x).contiguous().view(-1, T, D)
|
| 118 |
+
final_emb = x.permute(1, 0, 2)
|
| 119 |
+
final_emb = self.pos_emb(final_emb)
|
| 120 |
+
|
| 121 |
+
trans = self.transformer_encoder(final_emb).permute(1, 0, 2).contiguous().view(-1, K, T, D)
|
| 122 |
+
# trans: 11, 10, 20, 512
|
| 123 |
+
trans = self.concat3.batch_generate(ctx_emb, trans)
|
| 124 |
+
trans = self.concat4.batch_generate(ctx_emb, trans)
|
| 125 |
+
return self.linear.batch_generate(ctx_emb, trans)
|
LED/models/model_led_initializer.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
from models.layers import MLP, social_transformer, st_encoder
|
| 4 |
+
|
| 5 |
+
class LEDInitializer(nn.Module):
|
| 6 |
+
def __init__(self, t_h: int=8, d_h: int=6, t_f: int=40, d_f: int=2, k_pred: int=20):
|
| 7 |
+
'''
|
| 8 |
+
Parameters
|
| 9 |
+
----
|
| 10 |
+
t_h: history timestamps,
|
| 11 |
+
d_h: dimension of each historical timestamp,
|
| 12 |
+
t_f: future timestamps,
|
| 13 |
+
d_f: dimension of each future timestamp,
|
| 14 |
+
k_pred: number of predictions.
|
| 15 |
+
|
| 16 |
+
'''
|
| 17 |
+
super(LEDInitializer, self).__init__()
|
| 18 |
+
self.n = k_pred
|
| 19 |
+
self.input_dim = t_h * d_h
|
| 20 |
+
self.output_dim = t_f * d_f * k_pred
|
| 21 |
+
self.fut_len = t_f
|
| 22 |
+
|
| 23 |
+
self.social_encoder = social_transformer(t_h)
|
| 24 |
+
self.ego_var_encoder = st_encoder()
|
| 25 |
+
self.ego_mean_encoder = st_encoder()
|
| 26 |
+
self.ego_scale_encoder = st_encoder()
|
| 27 |
+
|
| 28 |
+
self.scale_encoder = MLP(1, 32, hid_feat=(4, 16), activation=nn.ReLU())
|
| 29 |
+
|
| 30 |
+
self.var_decoder = MLP(256*2+32, self.output_dim, hid_feat=(1024, 1024), activation=nn.ReLU())
|
| 31 |
+
self.mean_decoder = MLP(256*2, t_f * d_f, hid_feat=(256, 128), activation=nn.ReLU())
|
| 32 |
+
self.scale_decoder = MLP(256*2, 1, hid_feat=(256, 128), activation=nn.ReLU())
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def forward(self, x, mask=None):
|
| 36 |
+
'''
|
| 37 |
+
x: batch size, t_p, 6
|
| 38 |
+
'''
|
| 39 |
+
mask = mask.float().masked_fill(mask == 0, float('-inf')).masked_fill(mask == 1, float(0.0))
|
| 40 |
+
social_embed = self.social_encoder(x, mask)
|
| 41 |
+
social_embed = social_embed.squeeze(1)
|
| 42 |
+
# B, 256
|
| 43 |
+
|
| 44 |
+
ego_var_embed = self.ego_var_encoder(x)
|
| 45 |
+
ego_mean_embed = self.ego_mean_encoder(x)
|
| 46 |
+
ego_scale_embed = self.ego_scale_encoder(x)
|
| 47 |
+
# B, 256
|
| 48 |
+
|
| 49 |
+
mean_total = torch.cat((ego_mean_embed, social_embed), dim=-1)
|
| 50 |
+
|
| 51 |
+
guess_mean = self.mean_decoder(mean_total).contiguous().view(-1, self.fut_len, 2)
|
| 52 |
+
|
| 53 |
+
scale_total = torch.cat((ego_scale_embed, social_embed), dim=-1)
|
| 54 |
+
guess_scale = self.scale_decoder(scale_total)
|
| 55 |
+
|
| 56 |
+
guess_scale_feat = self.scale_encoder(guess_scale)
|
| 57 |
+
var_total = torch.cat((ego_var_embed, social_embed, guess_scale_feat), dim=-1)
|
| 58 |
+
guess_var = self.var_decoder(var_total).reshape(x.size(0), self.n, self.fut_len, 2)
|
| 59 |
+
|
| 60 |
+
return guess_var, guess_mean, guess_scale
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
|
LED/requirements.txt
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
absl-py==1.4.0
|
| 2 |
+
aiohttp==3.8.4
|
| 3 |
+
aiosignal==1.3.1
|
| 4 |
+
antlr4-python3-runtime==4.8
|
| 5 |
+
async-timeout==4.0.2
|
| 6 |
+
asynctest==0.13.0
|
| 7 |
+
attrs==23.1.0
|
| 8 |
+
cachetools==5.3.0
|
| 9 |
+
charset-normalizer==3.1.0
|
| 10 |
+
colour==0.1.5
|
| 11 |
+
cycler==0.11.0
|
| 12 |
+
descartes==1.1.0
|
| 13 |
+
easydict==1.10
|
| 14 |
+
fonttools==4.38.0
|
| 15 |
+
frozenlist==1.3.3
|
| 16 |
+
fsspec==2023.1.0
|
| 17 |
+
future==0.18.3
|
| 18 |
+
glob2==0.7
|
| 19 |
+
google-auth==2.18.0
|
| 20 |
+
google-auth-oauthlib==0.4.6
|
| 21 |
+
googledrivedownloader==0.4
|
| 22 |
+
grpcio==1.54.0
|
| 23 |
+
h5py==3.8.0
|
| 24 |
+
hydra-core==1.1.0
|
| 25 |
+
imageio==2.27.0
|
| 26 |
+
imgaug==0.4.0
|
| 27 |
+
importlib-metadata==4.13.0
|
| 28 |
+
importlib-resources==5.12.0
|
| 29 |
+
isodate==0.6.1
|
| 30 |
+
Jinja2==3.1.2
|
| 31 |
+
joblib==1.2.0
|
| 32 |
+
kiwisolver==1.4.4
|
| 33 |
+
lapsolver==1.1.0
|
| 34 |
+
llvmlite==0.39.1
|
| 35 |
+
Markdown==3.4.3
|
| 36 |
+
MarkupSafe==2.1.2
|
| 37 |
+
matplotlib==3.5.3
|
| 38 |
+
motmetrics==1.1.3
|
| 39 |
+
multidict==6.0.4
|
| 40 |
+
networkx==2.6.3
|
| 41 |
+
numba==0.56.4
|
| 42 |
+
numpy==1.19.0
|
| 43 |
+
oauthlib==3.2.2
|
| 44 |
+
omegaconf==2.1.0
|
| 45 |
+
opencv-python==4.7.0.72
|
| 46 |
+
packaging==23.1
|
| 47 |
+
pandas==1.3.5
|
| 48 |
+
Pillow==9.5.0
|
| 49 |
+
polars==0.17.12
|
| 50 |
+
protobuf==3.20.3
|
| 51 |
+
pyasn1==0.5.0
|
| 52 |
+
pyasn1-modules==0.3.0
|
| 53 |
+
pyDeprecate==0.3.1
|
| 54 |
+
pyntcloud==0.3.1
|
| 55 |
+
pyparsing==3.0.9
|
| 56 |
+
python-dateutil==2.8.2
|
| 57 |
+
python-louvain==0.16
|
| 58 |
+
pytorch-lightning==1.5.2
|
| 59 |
+
pytz==2023.3
|
| 60 |
+
PyWavelets==1.3.0
|
| 61 |
+
PyYAML==6.0
|
| 62 |
+
rdflib==6.3.2
|
| 63 |
+
requests-oauthlib==1.3.1
|
| 64 |
+
rsa==4.9
|
| 65 |
+
scikit-image==0.19.3
|
| 66 |
+
scikit-learn==1.0.2
|
| 67 |
+
scipy==1.7.3
|
| 68 |
+
shapely==2.0.1
|
| 69 |
+
spconv==1.2.1
|
| 70 |
+
tensorboard==2.11.2
|
| 71 |
+
tensorboard-data-server==0.6.1
|
| 72 |
+
tensorboard-plugin-wit==1.8.1
|
| 73 |
+
tensorboardX==2.6
|
| 74 |
+
threadpoolctl==3.1.0
|
| 75 |
+
tifffile==2021.11.2
|
| 76 |
+
torch==1.8.0+cu111
|
| 77 |
+
torchaudio==0.8.0
|
| 78 |
+
torchmetrics==0.11.4
|
| 79 |
+
torchvision==0.9.0+cu111
|
| 80 |
+
tqdm==4.65.0
|
| 81 |
+
typing_extensions==4.5.0
|
| 82 |
+
Werkzeug==2.2.3
|
| 83 |
+
yarl==1.9.2
|
| 84 |
+
zipp==3.15.0
|
LED/trainer/train_led_graph.py
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
LED trainer variant that augments the leapfrog denoising chain with a
|
| 3 |
+
future-trajectory interaction graph (ported in spirit from MoFlow's
|
| 4 |
+
FutureInteractionGraphV6 / MID graphv6_v5 ideas).
|
| 5 |
+
|
| 6 |
+
At each of the NUM_Tau leapfrog reverse steps, the frozen core denoiser
|
| 7 |
+
produces an epsilon prediction; we recover the implied y_0 estimate,
|
| 8 |
+
run a small inter-agent graph on the predicted future trajectories, and
|
| 9 |
+
add its output as a residual correction to epsilon. The graph is the
|
| 10 |
+
only new trainable module besides the existing LED initializer.
|
| 11 |
+
|
| 12 |
+
This file mirrors trainer/train_led_trajectory_augment_input.py so that
|
| 13 |
+
the baseline script stays untouched and both variants can be run
|
| 14 |
+
side-by-side.
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
import os
|
| 18 |
+
import time
|
| 19 |
+
import torch
|
| 20 |
+
import random
|
| 21 |
+
import numpy as np
|
| 22 |
+
import torch.nn as nn
|
| 23 |
+
|
| 24 |
+
from utils.config import Config
|
| 25 |
+
from utils.utils import print_log
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
from torch.utils.data import DataLoader
|
| 29 |
+
from torch.utils.tensorboard import SummaryWriter
|
| 30 |
+
from data.dataloader_nba import NBADataset, seq_collate
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
from models.model_led_initializer import LEDInitializer as InitializationModel
|
| 34 |
+
from models.model_diffusion import TransformerDenoisingModel as CoreDenoisingModel
|
| 35 |
+
from models.future_interaction_graph import FutureInteractionGraph
|
| 36 |
+
from models.future_interaction_graph_v6 import FutureInteractionGraphV6Wrapper
|
| 37 |
+
|
| 38 |
+
NUM_Tau = 5
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class Trainer:
|
| 42 |
+
def __init__(self, config):
|
| 43 |
+
if torch.cuda.is_available():
|
| 44 |
+
torch.cuda.set_device(config.gpu)
|
| 45 |
+
self.device = torch.device('cuda') if config.cuda else torch.device('cpu')
|
| 46 |
+
self.cfg = Config(config.cfg, config.info)
|
| 47 |
+
|
| 48 |
+
# ------------------------- prepare train/test data loader -------------------------
|
| 49 |
+
train_dset = NBADataset(
|
| 50 |
+
obs_len=self.cfg.past_frames,
|
| 51 |
+
pred_len=self.cfg.future_frames,
|
| 52 |
+
training=True)
|
| 53 |
+
|
| 54 |
+
self.train_loader = DataLoader(
|
| 55 |
+
train_dset,
|
| 56 |
+
batch_size=self.cfg.train_batch_size,
|
| 57 |
+
shuffle=True,
|
| 58 |
+
num_workers=4,
|
| 59 |
+
collate_fn=seq_collate,
|
| 60 |
+
pin_memory=True)
|
| 61 |
+
|
| 62 |
+
test_dset = NBADataset(
|
| 63 |
+
obs_len=self.cfg.past_frames,
|
| 64 |
+
pred_len=self.cfg.future_frames,
|
| 65 |
+
training=False)
|
| 66 |
+
|
| 67 |
+
self.test_loader = DataLoader(
|
| 68 |
+
test_dset,
|
| 69 |
+
batch_size=self.cfg.test_batch_size,
|
| 70 |
+
shuffle=False,
|
| 71 |
+
num_workers=4,
|
| 72 |
+
collate_fn=seq_collate,
|
| 73 |
+
pin_memory=True)
|
| 74 |
+
|
| 75 |
+
self.traj_mean = torch.FloatTensor(self.cfg.traj_mean).cuda().unsqueeze(0).unsqueeze(0).unsqueeze(0)
|
| 76 |
+
self.traj_scale = self.cfg.traj_scale
|
| 77 |
+
|
| 78 |
+
# ------------------------- define diffusion parameters -------------------------
|
| 79 |
+
self.n_steps = self.cfg.diffusion.steps
|
| 80 |
+
|
| 81 |
+
self.betas = self.make_beta_schedule(
|
| 82 |
+
schedule=self.cfg.diffusion.beta_schedule, n_timesteps=self.n_steps,
|
| 83 |
+
start=self.cfg.diffusion.beta_start, end=self.cfg.diffusion.beta_end).cuda()
|
| 84 |
+
|
| 85 |
+
self.alphas = 1 - self.betas
|
| 86 |
+
self.alphas_prod = torch.cumprod(self.alphas, 0)
|
| 87 |
+
self.alphas_bar_sqrt = torch.sqrt(self.alphas_prod)
|
| 88 |
+
self.one_minus_alphas_bar_sqrt = torch.sqrt(1 - self.alphas_prod)
|
| 89 |
+
|
| 90 |
+
# ------------------------- define models -------------------------
|
| 91 |
+
self.model = CoreDenoisingModel().cuda()
|
| 92 |
+
model_cp = torch.load(self.cfg.pretrained_core_denoising_model, map_location='cpu')
|
| 93 |
+
self.model.load_state_dict(model_cp['model_dict'])
|
| 94 |
+
|
| 95 |
+
self.model_initializer = InitializationModel(
|
| 96 |
+
t_h=10, d_h=6, t_f=20, d_f=2, k_pred=20).cuda()
|
| 97 |
+
|
| 98 |
+
self.residual_on = getattr(config, 'residual_on', 'eps')
|
| 99 |
+
self.use_sigma = bool(getattr(config, 'use_sigma', False))
|
| 100 |
+
self.use_v6_graph = bool(getattr(config, 'use_v6_graph', False))
|
| 101 |
+
self.uncertainty_weight = float(getattr(config, 'uncertainty_weight', 1.0))
|
| 102 |
+
top_n = getattr(config, 'top_n', 5)
|
| 103 |
+
edge_mode = getattr(config, 'edge_mode', 'full')
|
| 104 |
+
neighbor_mode = getattr(config, 'neighbor_mode', 'rag')
|
| 105 |
+
if self.use_v6_graph:
|
| 106 |
+
self.interaction_graph = FutureInteractionGraphV6Wrapper(
|
| 107 |
+
num_agents=11,
|
| 108 |
+
future_steps=self.cfg.future_frames,
|
| 109 |
+
past_steps=self.cfg.past_frames,
|
| 110 |
+
past_channels=6,
|
| 111 |
+
node_dim=128,
|
| 112 |
+
top_n=top_n,
|
| 113 |
+
num_denoise_steps=NUM_Tau,
|
| 114 |
+
edge_mode=edge_mode,
|
| 115 |
+
neighbor_mode=neighbor_mode,
|
| 116 |
+
).cuda()
|
| 117 |
+
else:
|
| 118 |
+
self.interaction_graph = FutureInteractionGraph(
|
| 119 |
+
num_agents=11,
|
| 120 |
+
future_steps=self.cfg.future_frames,
|
| 121 |
+
past_steps=self.cfg.past_frames,
|
| 122 |
+
past_channels=6,
|
| 123 |
+
node_dim=128,
|
| 124 |
+
top_n=top_n,
|
| 125 |
+
num_denoise_steps=NUM_Tau,
|
| 126 |
+
use_sigma=self.use_sigma,
|
| 127 |
+
).cuda()
|
| 128 |
+
|
| 129 |
+
self.opt = torch.optim.AdamW(
|
| 130 |
+
list(self.model_initializer.parameters())
|
| 131 |
+
+ list(self.interaction_graph.parameters()),
|
| 132 |
+
lr=config.learning_rate,
|
| 133 |
+
)
|
| 134 |
+
self.scheduler_model = torch.optim.lr_scheduler.StepLR(
|
| 135 |
+
self.opt, step_size=self.cfg.decay_step, gamma=self.cfg.decay_gamma)
|
| 136 |
+
|
| 137 |
+
self.resume_epoch = int(getattr(config, 'resume_epoch', 0))
|
| 138 |
+
if self.resume_epoch > 0:
|
| 139 |
+
cp_path = self.cfg.model_path % self.resume_epoch
|
| 140 |
+
cp = torch.load(cp_path, map_location='cpu')
|
| 141 |
+
self.model_initializer.load_state_dict(cp['model_initializer_dict'])
|
| 142 |
+
self.interaction_graph.load_state_dict(cp['interaction_graph_dict'])
|
| 143 |
+
for _ in range(self.resume_epoch):
|
| 144 |
+
self.scheduler_model.step()
|
| 145 |
+
|
| 146 |
+
# ------------------------- prepare logs -------------------------
|
| 147 |
+
self.log = open(os.path.join(self.cfg.log_dir, 'log.txt'), 'a+')
|
| 148 |
+
self.tb = SummaryWriter(log_dir=os.path.join(self.cfg.log_dir, 'tb'))
|
| 149 |
+
self.global_step = 0
|
| 150 |
+
self.print_model_param(self.model, name='Core Denoising Model')
|
| 151 |
+
self.print_model_param(self.model_initializer, name='Initialization Model')
|
| 152 |
+
self.print_model_param(self.interaction_graph, name='Future Interaction Graph')
|
| 153 |
+
|
| 154 |
+
self.temporal_reweight = torch.FloatTensor(
|
| 155 |
+
[21 - i for i in range(1, 21)]).cuda().unsqueeze(0).unsqueeze(0) / 10
|
| 156 |
+
|
| 157 |
+
def print_model_param(self, model: nn.Module, name: str = 'Model') -> None:
|
| 158 |
+
total_num = sum(p.numel() for p in model.parameters())
|
| 159 |
+
trainable_num = sum(p.numel() for p in model.parameters() if p.requires_grad)
|
| 160 |
+
print_log("[{}] Trainable/Total: {}/{}".format(name, trainable_num, total_num), self.log)
|
| 161 |
+
|
| 162 |
+
def make_beta_schedule(self, schedule: str = 'linear',
|
| 163 |
+
n_timesteps: int = 1000,
|
| 164 |
+
start: float = 1e-5, end: float = 1e-2) -> torch.Tensor:
|
| 165 |
+
if schedule == 'linear':
|
| 166 |
+
betas = torch.linspace(start, end, n_timesteps)
|
| 167 |
+
elif schedule == "quad":
|
| 168 |
+
betas = torch.linspace(start ** 0.5, end ** 0.5, n_timesteps) ** 2
|
| 169 |
+
elif schedule == "sigmoid":
|
| 170 |
+
betas = torch.linspace(-6, 6, n_timesteps)
|
| 171 |
+
betas = torch.sigmoid(betas) * (end - start) + start
|
| 172 |
+
return betas
|
| 173 |
+
|
| 174 |
+
def extract(self, input, t, x):
|
| 175 |
+
shape = x.shape
|
| 176 |
+
out = torch.gather(input, 0, t.to(input.device))
|
| 177 |
+
reshape = [t.shape[0]] + [1] * (len(shape) - 1)
|
| 178 |
+
return out.reshape(*reshape)
|
| 179 |
+
|
| 180 |
+
# ------------------------------------------------------------------
|
| 181 |
+
# Leapfrog reverse step with graph residual on epsilon
|
| 182 |
+
# ------------------------------------------------------------------
|
| 183 |
+
def p_sample_accelerate(self, x, mask, cur_y, t, sigma=None):
|
| 184 |
+
step_idx = int(t)
|
| 185 |
+
t = torch.tensor([t]).cuda()
|
| 186 |
+
eps_factor = ((1 - self.extract(self.alphas, t, cur_y))
|
| 187 |
+
/ self.extract(self.one_minus_alphas_bar_sqrt, t, cur_y))
|
| 188 |
+
beta = self.extract(self.betas, t.repeat(x.shape[0]), cur_y)
|
| 189 |
+
eps_theta = self.model.generate_accelerate(cur_y, beta, x, mask)
|
| 190 |
+
|
| 191 |
+
# Implied y_0 estimate for the graph input.
|
| 192 |
+
alpha_bar_sqrt_t = self.extract(self.alphas_bar_sqrt, t, cur_y)
|
| 193 |
+
one_minus_abs_t = self.extract(self.one_minus_alphas_bar_sqrt, t, cur_y)
|
| 194 |
+
y0_hat = (cur_y - one_minus_abs_t * eps_theta) / alpha_bar_sqrt_t
|
| 195 |
+
|
| 196 |
+
delta = self.interaction_graph(y0_hat, x, step_idx, sigma=sigma)
|
| 197 |
+
if self.residual_on == 'eps':
|
| 198 |
+
eps_theta = eps_theta + delta
|
| 199 |
+
else:
|
| 200 |
+
eps_theta = eps_theta - (alpha_bar_sqrt_t / one_minus_abs_t) * delta
|
| 201 |
+
|
| 202 |
+
mean = (1 / self.extract(self.alphas, t, cur_y).sqrt()) * (cur_y - (eps_factor * eps_theta))
|
| 203 |
+
z = torch.randn_like(cur_y).to(x.device)
|
| 204 |
+
sigma_t = self.extract(self.betas, t, cur_y).sqrt()
|
| 205 |
+
sample = mean + sigma_t * z * 0.00001
|
| 206 |
+
return sample
|
| 207 |
+
|
| 208 |
+
def p_sample_loop_accelerate(self, x, mask, loc, sigma=None):
|
| 209 |
+
cur_y = loc[:, :10]
|
| 210 |
+
for i in reversed(range(NUM_Tau)):
|
| 211 |
+
cur_y = self.p_sample_accelerate(x, mask, cur_y, i, sigma=sigma)
|
| 212 |
+
cur_y_ = loc[:, 10:]
|
| 213 |
+
for i in reversed(range(NUM_Tau)):
|
| 214 |
+
cur_y_ = self.p_sample_accelerate(x, mask, cur_y_, i, sigma=sigma)
|
| 215 |
+
prediction_total = torch.cat((cur_y_, cur_y), dim=1)
|
| 216 |
+
return prediction_total
|
| 217 |
+
|
| 218 |
+
# ------------------------------------------------------------------
|
| 219 |
+
# Training / evaluation
|
| 220 |
+
# ------------------------------------------------------------------
|
| 221 |
+
def fit(self):
|
| 222 |
+
for epoch in range(self.resume_epoch, self.cfg.num_epochs):
|
| 223 |
+
loss_total, loss_distance, loss_uncertainty = self._train_single_epoch(epoch)
|
| 224 |
+
print_log('[{}] Epoch: {}\t\tLoss: {:.6f}\tLoss Dist.: {:.6f}\tLoss Uncertainty: {:.6f}'.format(
|
| 225 |
+
time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
|
| 226 |
+
epoch, loss_total, loss_distance, loss_uncertainty), self.log)
|
| 227 |
+
|
| 228 |
+
self.tb.add_scalar('train_epoch/loss_total', loss_total, epoch)
|
| 229 |
+
self.tb.add_scalar('train_epoch/loss_dist_x50', loss_distance, epoch)
|
| 230 |
+
self.tb.add_scalar('train_epoch/loss_uncertainty', loss_uncertainty, epoch)
|
| 231 |
+
self.tb.add_scalar('train_epoch/lr', self.opt.param_groups[0]['lr'], epoch)
|
| 232 |
+
|
| 233 |
+
if (epoch + 1) % self.cfg.test_interval == 0:
|
| 234 |
+
performance, samples = self._test_single_epoch()
|
| 235 |
+
for time_i in range(4):
|
| 236 |
+
ade = performance['ADE'][time_i] / samples
|
| 237 |
+
fde = performance['FDE'][time_i] / samples
|
| 238 |
+
print_log('--ADE({}s): {:.4f}\t--FDE({}s): {:.4f}'.format(
|
| 239 |
+
time_i + 1, ade, time_i + 1, fde), self.log)
|
| 240 |
+
self.tb.add_scalar('val/ADE_{}s'.format(time_i + 1), ade, epoch)
|
| 241 |
+
self.tb.add_scalar('val/FDE_{}s'.format(time_i + 1), fde, epoch)
|
| 242 |
+
cp_path = self.cfg.model_path % (epoch + 1)
|
| 243 |
+
model_cp = {
|
| 244 |
+
'model_initializer_dict': self.model_initializer.state_dict(),
|
| 245 |
+
'interaction_graph_dict': self.interaction_graph.state_dict(),
|
| 246 |
+
}
|
| 247 |
+
torch.save(model_cp, cp_path)
|
| 248 |
+
self.scheduler_model.step()
|
| 249 |
+
self.tb.flush()
|
| 250 |
+
self.tb.close()
|
| 251 |
+
|
| 252 |
+
def data_preprocess(self, data):
|
| 253 |
+
batch_size = data['pre_motion_3D'].shape[0]
|
| 254 |
+
traj_mask = torch.zeros(batch_size * 11, batch_size * 11).cuda()
|
| 255 |
+
for i in range(batch_size):
|
| 256 |
+
traj_mask[i * 11:(i + 1) * 11, i * 11:(i + 1) * 11] = 1.
|
| 257 |
+
|
| 258 |
+
initial_pos = data['pre_motion_3D'].cuda()[:, :, -1:]
|
| 259 |
+
past_traj_abs = ((data['pre_motion_3D'].cuda() - self.traj_mean) / self.traj_scale).contiguous().view(-1, 10, 2)
|
| 260 |
+
past_traj_rel = ((data['pre_motion_3D'].cuda() - initial_pos) / self.traj_scale).contiguous().view(-1, 10, 2)
|
| 261 |
+
past_traj_vel = torch.cat(
|
| 262 |
+
(past_traj_rel[:, 1:] - past_traj_rel[:, :-1],
|
| 263 |
+
torch.zeros_like(past_traj_rel[:, -1:])), dim=1)
|
| 264 |
+
past_traj = torch.cat((past_traj_abs, past_traj_rel, past_traj_vel), dim=-1)
|
| 265 |
+
|
| 266 |
+
fut_traj = ((data['fut_motion_3D'].cuda() - initial_pos) / self.traj_scale).contiguous().view(-1, 20, 2)
|
| 267 |
+
return batch_size, traj_mask, past_traj, fut_traj
|
| 268 |
+
|
| 269 |
+
def _train_single_epoch(self, epoch):
|
| 270 |
+
self.model.train()
|
| 271 |
+
self.model_initializer.train()
|
| 272 |
+
self.interaction_graph.train()
|
| 273 |
+
loss_total, loss_dt, loss_dc, count = 0, 0, 0, 0
|
| 274 |
+
|
| 275 |
+
for data in self.train_loader:
|
| 276 |
+
batch_size, traj_mask, past_traj, fut_traj = self.data_preprocess(data)
|
| 277 |
+
|
| 278 |
+
sample_prediction, mean_estimation, variance_estimation = self.model_initializer(past_traj, traj_mask)
|
| 279 |
+
sample_prediction = (torch.exp(variance_estimation / 2)[..., None, None]
|
| 280 |
+
* sample_prediction
|
| 281 |
+
/ sample_prediction.std(dim=1).mean(dim=(1, 2))[:, None, None, None])
|
| 282 |
+
loc = sample_prediction + mean_estimation[:, None]
|
| 283 |
+
|
| 284 |
+
sigma_input = variance_estimation if self.use_sigma else None
|
| 285 |
+
generated_y = self.p_sample_loop_accelerate(past_traj, traj_mask, loc, sigma=sigma_input)
|
| 286 |
+
|
| 287 |
+
loss_dist = ((generated_y - fut_traj.unsqueeze(dim=1)).norm(p=2, dim=-1)
|
| 288 |
+
* self.temporal_reweight).mean(dim=-1).min(dim=1)[0].mean()
|
| 289 |
+
loss_uncertainty = (torch.exp(-variance_estimation)
|
| 290 |
+
* (generated_y - fut_traj.unsqueeze(dim=1)).norm(p=2, dim=-1).mean(dim=(1, 2))
|
| 291 |
+
+ variance_estimation).mean()
|
| 292 |
+
|
| 293 |
+
loss = loss_dist * 50 + self.uncertainty_weight * loss_uncertainty
|
| 294 |
+
loss_total += loss.item()
|
| 295 |
+
loss_dt += loss_dist.item() * 50
|
| 296 |
+
loss_dc += loss_uncertainty.item()
|
| 297 |
+
|
| 298 |
+
self.opt.zero_grad()
|
| 299 |
+
loss.backward()
|
| 300 |
+
grad_norm = torch.nn.utils.clip_grad_norm_(
|
| 301 |
+
list(self.model_initializer.parameters())
|
| 302 |
+
+ list(self.interaction_graph.parameters()),
|
| 303 |
+
1.,
|
| 304 |
+
)
|
| 305 |
+
self.opt.step()
|
| 306 |
+
|
| 307 |
+
self.tb.add_scalar('train_step/loss_total', loss.item(), self.global_step)
|
| 308 |
+
self.tb.add_scalar('train_step/loss_dist_x50', loss_dist.item() * 50, self.global_step)
|
| 309 |
+
self.tb.add_scalar('train_step/loss_uncertainty', loss_uncertainty.item(), self.global_step)
|
| 310 |
+
self.tb.add_scalar('train_step/grad_norm', float(grad_norm), self.global_step)
|
| 311 |
+
self.global_step += 1
|
| 312 |
+
|
| 313 |
+
count += 1
|
| 314 |
+
if self.cfg.debug and count == 2:
|
| 315 |
+
break
|
| 316 |
+
|
| 317 |
+
return loss_total / count, loss_dt / count, loss_dc / count
|
| 318 |
+
|
| 319 |
+
def _test_single_epoch(self):
|
| 320 |
+
performance = {'FDE': [0, 0, 0, 0], 'ADE': [0, 0, 0, 0]}
|
| 321 |
+
samples = 0
|
| 322 |
+
|
| 323 |
+
def prepare_seed(rand_seed):
|
| 324 |
+
np.random.seed(rand_seed)
|
| 325 |
+
random.seed(rand_seed)
|
| 326 |
+
torch.manual_seed(rand_seed)
|
| 327 |
+
torch.cuda.manual_seed_all(rand_seed)
|
| 328 |
+
prepare_seed(0)
|
| 329 |
+
|
| 330 |
+
self.model_initializer.eval()
|
| 331 |
+
self.interaction_graph.eval()
|
| 332 |
+
with torch.no_grad():
|
| 333 |
+
for data in self.test_loader:
|
| 334 |
+
batch_size, traj_mask, past_traj, fut_traj = self.data_preprocess(data)
|
| 335 |
+
|
| 336 |
+
sample_prediction, mean_estimation, variance_estimation = self.model_initializer(past_traj, traj_mask)
|
| 337 |
+
sample_prediction = (torch.exp(variance_estimation / 2)[..., None, None]
|
| 338 |
+
* sample_prediction
|
| 339 |
+
/ sample_prediction.std(dim=1).mean(dim=(1, 2))[:, None, None, None])
|
| 340 |
+
loc = sample_prediction + mean_estimation[:, None]
|
| 341 |
+
|
| 342 |
+
sigma_input = variance_estimation if self.use_sigma else None
|
| 343 |
+
pred_traj = self.p_sample_loop_accelerate(past_traj, traj_mask, loc, sigma=sigma_input)
|
| 344 |
+
|
| 345 |
+
fut_traj = fut_traj.unsqueeze(1).repeat(1, 20, 1, 1)
|
| 346 |
+
distances = torch.norm(fut_traj - pred_traj, dim=-1) * self.traj_scale
|
| 347 |
+
for time_i in range(1, 5):
|
| 348 |
+
ade = (distances[:, :, :5 * time_i]).mean(dim=-1).min(dim=-1)[0].sum()
|
| 349 |
+
fde = (distances[:, :, 5 * time_i - 1]).min(dim=-1)[0].sum()
|
| 350 |
+
performance['ADE'][time_i - 1] += ade.item()
|
| 351 |
+
performance['FDE'][time_i - 1] += fde.item()
|
| 352 |
+
samples += distances.shape[0]
|
| 353 |
+
return performance, samples
|
| 354 |
+
|
| 355 |
+
def test_single_model(self):
|
| 356 |
+
model_path = './results/checkpoints/led_graph.p'
|
| 357 |
+
ckpt = torch.load(model_path, map_location=torch.device('cpu'))
|
| 358 |
+
self.model_initializer.load_state_dict(ckpt['model_initializer_dict'])
|
| 359 |
+
if 'interaction_graph_dict' in ckpt:
|
| 360 |
+
self.interaction_graph.load_state_dict(ckpt['interaction_graph_dict'])
|
| 361 |
+
else:
|
| 362 |
+
print_log('WARNING: checkpoint has no interaction_graph_dict; '
|
| 363 |
+
'using zero-initialized graph (equivalent to baseline LED).',
|
| 364 |
+
log=self.log)
|
| 365 |
+
|
| 366 |
+
performance = {'FDE': [0, 0, 0, 0], 'ADE': [0, 0, 0, 0]}
|
| 367 |
+
samples = 0
|
| 368 |
+
print_log(model_path, log=self.log)
|
| 369 |
+
|
| 370 |
+
def prepare_seed(rand_seed):
|
| 371 |
+
np.random.seed(rand_seed)
|
| 372 |
+
random.seed(rand_seed)
|
| 373 |
+
torch.manual_seed(rand_seed)
|
| 374 |
+
torch.cuda.manual_seed_all(rand_seed)
|
| 375 |
+
prepare_seed(0)
|
| 376 |
+
|
| 377 |
+
self.model_initializer.eval()
|
| 378 |
+
self.interaction_graph.eval()
|
| 379 |
+
with torch.no_grad():
|
| 380 |
+
for data in self.test_loader:
|
| 381 |
+
batch_size, traj_mask, past_traj, fut_traj = self.data_preprocess(data)
|
| 382 |
+
|
| 383 |
+
sample_prediction, mean_estimation, variance_estimation = self.model_initializer(past_traj, traj_mask)
|
| 384 |
+
sample_prediction = (torch.exp(variance_estimation / 2)[..., None, None]
|
| 385 |
+
* sample_prediction
|
| 386 |
+
/ sample_prediction.std(dim=1).mean(dim=(1, 2))[:, None, None, None])
|
| 387 |
+
loc = sample_prediction + mean_estimation[:, None]
|
| 388 |
+
|
| 389 |
+
sigma_input = variance_estimation if self.use_sigma else None
|
| 390 |
+
pred_traj = self.p_sample_loop_accelerate(past_traj, traj_mask, loc, sigma=sigma_input)
|
| 391 |
+
|
| 392 |
+
fut_traj = fut_traj.unsqueeze(1).repeat(1, 20, 1, 1)
|
| 393 |
+
distances = torch.norm(fut_traj - pred_traj, dim=-1) * self.traj_scale
|
| 394 |
+
for time_i in range(1, 5):
|
| 395 |
+
ade = (distances[:, :, :5 * time_i]).mean(dim=-1).min(dim=-1)[0].sum()
|
| 396 |
+
fde = (distances[:, :, 5 * time_i - 1]).min(dim=-1)[0].sum()
|
| 397 |
+
performance['ADE'][time_i - 1] += ade.item()
|
| 398 |
+
performance['FDE'][time_i - 1] += fde.item()
|
| 399 |
+
samples += distances.shape[0]
|
| 400 |
+
|
| 401 |
+
for time_i in range(4):
|
| 402 |
+
print_log('--ADE({}s): {:.4f}\t--FDE({}s): {:.4f}'.format(
|
| 403 |
+
time_i + 1, performance['ADE'][time_i] / samples,
|
| 404 |
+
time_i + 1, performance['FDE'][time_i] / samples), log=self.log)
|
LED/trainer/train_led_trajectory_augment_input.py
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import os
|
| 3 |
+
import time
|
| 4 |
+
import torch
|
| 5 |
+
import random
|
| 6 |
+
import numpy as np
|
| 7 |
+
import torch.nn as nn
|
| 8 |
+
|
| 9 |
+
from utils.config import Config
|
| 10 |
+
from utils.utils import print_log
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
from torch.utils.data import DataLoader
|
| 14 |
+
from data.dataloader_nba import NBADataset, seq_collate
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
from models.model_led_initializer import LEDInitializer as InitializationModel
|
| 18 |
+
from models.model_diffusion import TransformerDenoisingModel as CoreDenoisingModel
|
| 19 |
+
|
| 20 |
+
import pdb
|
| 21 |
+
NUM_Tau = 5
|
| 22 |
+
|
| 23 |
+
class Trainer:
|
| 24 |
+
def __init__(self, config):
|
| 25 |
+
|
| 26 |
+
if torch.cuda.is_available(): torch.cuda.set_device(config.gpu)
|
| 27 |
+
self.device = torch.device('cuda') if config.cuda else torch.device('cpu')
|
| 28 |
+
self.cfg = Config(config.cfg, config.info)
|
| 29 |
+
|
| 30 |
+
# ------------------------- prepare train/test data loader -------------------------
|
| 31 |
+
train_dset = NBADataset(
|
| 32 |
+
obs_len=self.cfg.past_frames,
|
| 33 |
+
pred_len=self.cfg.future_frames,
|
| 34 |
+
training=True)
|
| 35 |
+
|
| 36 |
+
self.train_loader = DataLoader(
|
| 37 |
+
train_dset,
|
| 38 |
+
batch_size=self.cfg.train_batch_size,
|
| 39 |
+
shuffle=True,
|
| 40 |
+
num_workers=4,
|
| 41 |
+
collate_fn=seq_collate,
|
| 42 |
+
pin_memory=True)
|
| 43 |
+
|
| 44 |
+
test_dset = NBADataset(
|
| 45 |
+
obs_len=self.cfg.past_frames,
|
| 46 |
+
pred_len=self.cfg.future_frames,
|
| 47 |
+
training=False)
|
| 48 |
+
|
| 49 |
+
self.test_loader = DataLoader(
|
| 50 |
+
test_dset,
|
| 51 |
+
batch_size=self.cfg.test_batch_size,
|
| 52 |
+
shuffle=False,
|
| 53 |
+
num_workers=4,
|
| 54 |
+
collate_fn=seq_collate,
|
| 55 |
+
pin_memory=True)
|
| 56 |
+
|
| 57 |
+
# data normalization parameters
|
| 58 |
+
self.traj_mean = torch.FloatTensor(self.cfg.traj_mean).cuda().unsqueeze(0).unsqueeze(0).unsqueeze(0)
|
| 59 |
+
self.traj_scale = self.cfg.traj_scale
|
| 60 |
+
|
| 61 |
+
# ------------------------- define diffusion parameters -------------------------
|
| 62 |
+
self.n_steps = self.cfg.diffusion.steps # define total diffusion steps
|
| 63 |
+
|
| 64 |
+
# make beta schedule and calculate the parameters used in denoising process.
|
| 65 |
+
self.betas = self.make_beta_schedule(
|
| 66 |
+
schedule=self.cfg.diffusion.beta_schedule, n_timesteps=self.n_steps,
|
| 67 |
+
start=self.cfg.diffusion.beta_start, end=self.cfg.diffusion.beta_end).cuda()
|
| 68 |
+
|
| 69 |
+
self.alphas = 1 - self.betas
|
| 70 |
+
self.alphas_prod = torch.cumprod(self.alphas, 0)
|
| 71 |
+
self.alphas_bar_sqrt = torch.sqrt(self.alphas_prod)
|
| 72 |
+
self.one_minus_alphas_bar_sqrt = torch.sqrt(1 - self.alphas_prod)
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
# ------------------------- define models -------------------------
|
| 76 |
+
self.model = CoreDenoisingModel().cuda()
|
| 77 |
+
# load pretrained models
|
| 78 |
+
model_cp = torch.load(self.cfg.pretrained_core_denoising_model, map_location='cpu')
|
| 79 |
+
self.model.load_state_dict(model_cp['model_dict'])
|
| 80 |
+
|
| 81 |
+
self.model_initializer = InitializationModel(t_h=10, d_h=6, t_f=20, d_f=2, k_pred=20).cuda()
|
| 82 |
+
|
| 83 |
+
self.opt = torch.optim.AdamW(self.model_initializer.parameters(), lr=config.learning_rate)
|
| 84 |
+
self.scheduler_model = torch.optim.lr_scheduler.StepLR(self.opt, step_size=self.cfg.decay_step, gamma=self.cfg.decay_gamma)
|
| 85 |
+
|
| 86 |
+
# ------------------------- prepare logs -------------------------
|
| 87 |
+
self.log = open(os.path.join(self.cfg.log_dir, 'log.txt'), 'a+')
|
| 88 |
+
self.print_model_param(self.model, name='Core Denoising Model')
|
| 89 |
+
self.print_model_param(self.model_initializer, name='Initialization Model')
|
| 90 |
+
|
| 91 |
+
# temporal reweight in the loss, it is not necessary.
|
| 92 |
+
self.temporal_reweight = torch.FloatTensor([21 - i for i in range(1, 21)]).cuda().unsqueeze(0).unsqueeze(0) / 10
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def print_model_param(self, model: nn.Module, name: str = 'Model') -> None:
|
| 96 |
+
'''
|
| 97 |
+
Count the trainable/total parameters in `model`.
|
| 98 |
+
'''
|
| 99 |
+
total_num = sum(p.numel() for p in model.parameters())
|
| 100 |
+
trainable_num = sum(p.numel() for p in model.parameters() if p.requires_grad)
|
| 101 |
+
print_log("[{}] Trainable/Total: {}/{}".format(name, trainable_num, total_num), self.log)
|
| 102 |
+
return None
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def make_beta_schedule(self, schedule: str = 'linear',
|
| 106 |
+
n_timesteps: int = 1000,
|
| 107 |
+
start: float = 1e-5, end: float = 1e-2) -> torch.Tensor:
|
| 108 |
+
'''
|
| 109 |
+
Make beta schedule.
|
| 110 |
+
|
| 111 |
+
Parameters
|
| 112 |
+
----
|
| 113 |
+
schedule: str, in ['linear', 'quad', 'sigmoid'],
|
| 114 |
+
n_timesteps: int, diffusion steps,
|
| 115 |
+
start: float, beta start, `start<end`,
|
| 116 |
+
end: float, beta end,
|
| 117 |
+
|
| 118 |
+
Returns
|
| 119 |
+
----
|
| 120 |
+
betas: Tensor with the shape of (n_timesteps)
|
| 121 |
+
|
| 122 |
+
'''
|
| 123 |
+
if schedule == 'linear':
|
| 124 |
+
betas = torch.linspace(start, end, n_timesteps)
|
| 125 |
+
elif schedule == "quad":
|
| 126 |
+
betas = torch.linspace(start ** 0.5, end ** 0.5, n_timesteps) ** 2
|
| 127 |
+
elif schedule == "sigmoid":
|
| 128 |
+
betas = torch.linspace(-6, 6, n_timesteps)
|
| 129 |
+
betas = torch.sigmoid(betas) * (end - start) + start
|
| 130 |
+
return betas
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def extract(self, input, t, x):
|
| 134 |
+
shape = x.shape
|
| 135 |
+
out = torch.gather(input, 0, t.to(input.device))
|
| 136 |
+
reshape = [t.shape[0]] + [1] * (len(shape) - 1)
|
| 137 |
+
return out.reshape(*reshape)
|
| 138 |
+
|
| 139 |
+
def noise_estimation_loss(self, x, y_0, mask):
|
| 140 |
+
batch_size = x.shape[0]
|
| 141 |
+
# Select a random step for each example
|
| 142 |
+
t = torch.randint(0, self.n_steps, size=(batch_size // 2 + 1,)).to(x.device)
|
| 143 |
+
t = torch.cat([t, self.n_steps - t - 1], dim=0)[:batch_size]
|
| 144 |
+
# x0 multiplier
|
| 145 |
+
a = self.extract(self.alphas_bar_sqrt, t, y_0)
|
| 146 |
+
beta = self.extract(self.betas, t, y_0)
|
| 147 |
+
# eps multiplier
|
| 148 |
+
am1 = self.extract(self.one_minus_alphas_bar_sqrt, t, y_0)
|
| 149 |
+
e = torch.randn_like(y_0)
|
| 150 |
+
# model input
|
| 151 |
+
y = y_0 * a + e * am1
|
| 152 |
+
output = self.model(y, beta, x, mask)
|
| 153 |
+
# batch_size, 20, 2
|
| 154 |
+
return (e - output).square().mean()
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
def p_sample(self, x, mask, cur_y, t):
|
| 159 |
+
if t==0:
|
| 160 |
+
z = torch.zeros_like(cur_y).to(x.device)
|
| 161 |
+
else:
|
| 162 |
+
z = torch.randn_like(cur_y).to(x.device)
|
| 163 |
+
t = torch.tensor([t]).cuda()
|
| 164 |
+
# Factor to the model output
|
| 165 |
+
eps_factor = ((1 - self.extract(self.alphas, t, cur_y)) / self.extract(self.one_minus_alphas_bar_sqrt, t, cur_y))
|
| 166 |
+
# Model output
|
| 167 |
+
beta = self.extract(self.betas, t.repeat(x.shape[0]), cur_y)
|
| 168 |
+
eps_theta = self.model(cur_y, beta, x, mask)
|
| 169 |
+
mean = (1 / self.extract(self.alphas, t, cur_y).sqrt()) * (cur_y - (eps_factor * eps_theta))
|
| 170 |
+
# Generate z
|
| 171 |
+
z = torch.randn_like(cur_y).to(x.device)
|
| 172 |
+
# Fixed sigma
|
| 173 |
+
sigma_t = self.extract(self.betas, t, cur_y).sqrt()
|
| 174 |
+
sample = mean + sigma_t * z
|
| 175 |
+
return (sample)
|
| 176 |
+
|
| 177 |
+
def p_sample_accelerate(self, x, mask, cur_y, t):
|
| 178 |
+
if t==0:
|
| 179 |
+
z = torch.zeros_like(cur_y).to(x.device)
|
| 180 |
+
else:
|
| 181 |
+
z = torch.randn_like(cur_y).to(x.device)
|
| 182 |
+
t = torch.tensor([t]).cuda()
|
| 183 |
+
# Factor to the model output
|
| 184 |
+
eps_factor = ((1 - self.extract(self.alphas, t, cur_y)) / self.extract(self.one_minus_alphas_bar_sqrt, t, cur_y))
|
| 185 |
+
# Model output
|
| 186 |
+
beta = self.extract(self.betas, t.repeat(x.shape[0]), cur_y)
|
| 187 |
+
eps_theta = self.model.generate_accelerate(cur_y, beta, x, mask)
|
| 188 |
+
mean = (1 / self.extract(self.alphas, t, cur_y).sqrt()) * (cur_y - (eps_factor * eps_theta))
|
| 189 |
+
# Generate z
|
| 190 |
+
z = torch.randn_like(cur_y).to(x.device)
|
| 191 |
+
# Fixed sigma
|
| 192 |
+
sigma_t = self.extract(self.betas, t, cur_y).sqrt()
|
| 193 |
+
sample = mean + sigma_t * z * 0.00001
|
| 194 |
+
return (sample)
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
def p_sample_loop(self, x, mask, shape):
|
| 199 |
+
self.model.eval()
|
| 200 |
+
prediction_total = torch.Tensor().cuda()
|
| 201 |
+
for _ in range(20):
|
| 202 |
+
cur_y = torch.randn(shape).to(x.device)
|
| 203 |
+
for i in reversed(range(self.n_steps)):
|
| 204 |
+
cur_y = self.p_sample(x, mask, cur_y, i)
|
| 205 |
+
prediction_total = torch.cat((prediction_total, cur_y.unsqueeze(1)), dim=1)
|
| 206 |
+
return prediction_total
|
| 207 |
+
|
| 208 |
+
def p_sample_loop_mean(self, x, mask, loc):
|
| 209 |
+
prediction_total = torch.Tensor().cuda()
|
| 210 |
+
for loc_i in range(1):
|
| 211 |
+
cur_y = loc
|
| 212 |
+
for i in reversed(range(NUM_Tau)):
|
| 213 |
+
cur_y = self.p_sample(x, mask, cur_y, i)
|
| 214 |
+
prediction_total = torch.cat((prediction_total, cur_y.unsqueeze(1)), dim=1)
|
| 215 |
+
return prediction_total
|
| 216 |
+
|
| 217 |
+
def p_sample_loop_accelerate(self, x, mask, loc):
|
| 218 |
+
'''
|
| 219 |
+
Batch operation to accelerate the denoising process.
|
| 220 |
+
|
| 221 |
+
x: [11, 10, 6]
|
| 222 |
+
mask: [11, 11]
|
| 223 |
+
cur_y: [11, 10, 20, 2]
|
| 224 |
+
'''
|
| 225 |
+
prediction_total = torch.Tensor().cuda()
|
| 226 |
+
cur_y = loc[:, :10]
|
| 227 |
+
for i in reversed(range(NUM_Tau)):
|
| 228 |
+
cur_y = self.p_sample_accelerate(x, mask, cur_y, i)
|
| 229 |
+
cur_y_ = loc[:, 10:]
|
| 230 |
+
for i in reversed(range(NUM_Tau)):
|
| 231 |
+
cur_y_ = self.p_sample_accelerate(x, mask, cur_y_, i)
|
| 232 |
+
# shape: B=b*n, K=10, T, 2
|
| 233 |
+
prediction_total = torch.cat((cur_y_, cur_y), dim=1)
|
| 234 |
+
return prediction_total
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
def fit(self):
|
| 239 |
+
# Training loop
|
| 240 |
+
for epoch in range(0, self.cfg.num_epochs):
|
| 241 |
+
loss_total, loss_distance, loss_uncertainty = self._train_single_epoch(epoch)
|
| 242 |
+
print_log('[{}] Epoch: {}\t\tLoss: {:.6f}\tLoss Dist.: {:.6f}\tLoss Uncertainty: {:.6f}'.format(
|
| 243 |
+
time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
|
| 244 |
+
epoch, loss_total, loss_distance, loss_uncertainty), self.log)
|
| 245 |
+
|
| 246 |
+
if (epoch + 1) % self.cfg.test_interval == 0:
|
| 247 |
+
performance, samples = self._test_single_epoch()
|
| 248 |
+
for time_i in range(4):
|
| 249 |
+
print_log('--ADE({}s): {:.4f}\t--FDE({}s): {:.4f}'.format(
|
| 250 |
+
time_i+1, performance['ADE'][time_i]/samples,
|
| 251 |
+
time_i+1, performance['FDE'][time_i]/samples), self.log)
|
| 252 |
+
cp_path = self.cfg.model_path % (epoch + 1)
|
| 253 |
+
model_cp = {'model_initializer_dict': self.model_initializer.state_dict()}
|
| 254 |
+
torch.save(model_cp, cp_path)
|
| 255 |
+
self.scheduler_model.step()
|
| 256 |
+
|
| 257 |
+
|
| 258 |
+
def data_preprocess(self, data):
|
| 259 |
+
"""
|
| 260 |
+
pre_motion_3D: torch.Size([32, 11, 10, 2]), [batch_size, num_agent, past_frame, dimension]
|
| 261 |
+
fut_motion_3D: torch.Size([32, 11, 20, 2])
|
| 262 |
+
fut_motion_mask: torch.Size([32, 11, 20])
|
| 263 |
+
pre_motion_mask: torch.Size([32, 11, 10])
|
| 264 |
+
traj_scale: 1
|
| 265 |
+
pred_mask: None
|
| 266 |
+
seq: nba
|
| 267 |
+
"""
|
| 268 |
+
batch_size = data['pre_motion_3D'].shape[0]
|
| 269 |
+
|
| 270 |
+
traj_mask = torch.zeros(batch_size*11, batch_size*11).cuda()
|
| 271 |
+
for i in range(batch_size):
|
| 272 |
+
traj_mask[i*11:(i+1)*11, i*11:(i+1)*11] = 1.
|
| 273 |
+
|
| 274 |
+
initial_pos = data['pre_motion_3D'].cuda()[:, :, -1:]
|
| 275 |
+
# augment input: absolute position, relative position, velocity
|
| 276 |
+
past_traj_abs = ((data['pre_motion_3D'].cuda() - self.traj_mean)/self.traj_scale).contiguous().view(-1, 10, 2)
|
| 277 |
+
past_traj_rel = ((data['pre_motion_3D'].cuda() - initial_pos)/self.traj_scale).contiguous().view(-1, 10, 2)
|
| 278 |
+
past_traj_vel = torch.cat((past_traj_rel[:, 1:] - past_traj_rel[:, :-1], torch.zeros_like(past_traj_rel[:, -1:])), dim=1)
|
| 279 |
+
past_traj = torch.cat((past_traj_abs, past_traj_rel, past_traj_vel), dim=-1)
|
| 280 |
+
|
| 281 |
+
fut_traj = ((data['fut_motion_3D'].cuda() - initial_pos)/self.traj_scale).contiguous().view(-1, 20, 2)
|
| 282 |
+
return batch_size, traj_mask, past_traj, fut_traj
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
def _train_single_epoch(self, epoch):
|
| 286 |
+
|
| 287 |
+
self.model.train()
|
| 288 |
+
self.model_initializer.train()
|
| 289 |
+
loss_total, loss_dt, loss_dc, count = 0, 0, 0, 0
|
| 290 |
+
|
| 291 |
+
for data in self.train_loader:
|
| 292 |
+
batch_size, traj_mask, past_traj, fut_traj = self.data_preprocess(data)
|
| 293 |
+
|
| 294 |
+
sample_prediction, mean_estimation, variance_estimation = self.model_initializer(past_traj, traj_mask)
|
| 295 |
+
sample_prediction = torch.exp(variance_estimation/2)[..., None, None] * sample_prediction / sample_prediction.std(dim=1).mean(dim=(1, 2))[:, None, None, None]
|
| 296 |
+
loc = sample_prediction + mean_estimation[:, None]
|
| 297 |
+
|
| 298 |
+
generated_y = self.p_sample_loop_accelerate(past_traj, traj_mask, loc)
|
| 299 |
+
|
| 300 |
+
loss_dist = ( (generated_y - fut_traj.unsqueeze(dim=1)).norm(p=2, dim=-1)
|
| 301 |
+
*
|
| 302 |
+
self.temporal_reweight
|
| 303 |
+
).mean(dim=-1).min(dim=1)[0].mean()
|
| 304 |
+
loss_uncertainty = (torch.exp(-variance_estimation)
|
| 305 |
+
*
|
| 306 |
+
(generated_y - fut_traj.unsqueeze(dim=1)).norm(p=2, dim=-1).mean(dim=(1, 2))
|
| 307 |
+
+
|
| 308 |
+
variance_estimation
|
| 309 |
+
).mean()
|
| 310 |
+
|
| 311 |
+
loss = loss_dist*50 + loss_uncertainty
|
| 312 |
+
loss_total += loss.item()
|
| 313 |
+
loss_dt += loss_dist.item()*50
|
| 314 |
+
loss_dc += loss_uncertainty.item()
|
| 315 |
+
|
| 316 |
+
self.opt.zero_grad()
|
| 317 |
+
loss.backward()
|
| 318 |
+
torch.nn.utils.clip_grad_norm_(self.model_initializer.parameters(), 1.)
|
| 319 |
+
self.opt.step()
|
| 320 |
+
count += 1
|
| 321 |
+
if self.cfg.debug and count == 2:
|
| 322 |
+
break
|
| 323 |
+
|
| 324 |
+
return loss_total/count, loss_dt/count, loss_dc/count
|
| 325 |
+
|
| 326 |
+
|
| 327 |
+
def _test_single_epoch(self):
|
| 328 |
+
performance = { 'FDE': [0, 0, 0, 0],
|
| 329 |
+
'ADE': [0, 0, 0, 0]}
|
| 330 |
+
samples = 0
|
| 331 |
+
def prepare_seed(rand_seed):
|
| 332 |
+
np.random.seed(rand_seed)
|
| 333 |
+
random.seed(rand_seed)
|
| 334 |
+
torch.manual_seed(rand_seed)
|
| 335 |
+
torch.cuda.manual_seed_all(rand_seed)
|
| 336 |
+
prepare_seed(0)
|
| 337 |
+
count = 0
|
| 338 |
+
with torch.no_grad():
|
| 339 |
+
for data in self.test_loader:
|
| 340 |
+
batch_size, traj_mask, past_traj, fut_traj = self.data_preprocess(data)
|
| 341 |
+
|
| 342 |
+
sample_prediction, mean_estimation, variance_estimation = self.model_initializer(past_traj, traj_mask)
|
| 343 |
+
sample_prediction = torch.exp(variance_estimation/2)[..., None, None] * sample_prediction / sample_prediction.std(dim=1).mean(dim=(1, 2))[:, None, None, None]
|
| 344 |
+
loc = sample_prediction + mean_estimation[:, None]
|
| 345 |
+
|
| 346 |
+
pred_traj = self.p_sample_loop_accelerate(past_traj, traj_mask, loc)
|
| 347 |
+
|
| 348 |
+
fut_traj = fut_traj.unsqueeze(1).repeat(1, 20, 1, 1)
|
| 349 |
+
# b*n, K, T, 2
|
| 350 |
+
distances = torch.norm(fut_traj - pred_traj, dim=-1) * self.traj_scale
|
| 351 |
+
for time_i in range(1, 5):
|
| 352 |
+
ade = (distances[:, :, :5*time_i]).mean(dim=-1).min(dim=-1)[0].sum()
|
| 353 |
+
fde = (distances[:, :, 5*time_i-1]).min(dim=-1)[0].sum()
|
| 354 |
+
performance['ADE'][time_i-1] += ade.item()
|
| 355 |
+
performance['FDE'][time_i-1] += fde.item()
|
| 356 |
+
samples += distances.shape[0]
|
| 357 |
+
count += 1
|
| 358 |
+
# if count==100:
|
| 359 |
+
# break
|
| 360 |
+
return performance, samples
|
| 361 |
+
|
| 362 |
+
|
| 363 |
+
def save_data(self):
|
| 364 |
+
'''
|
| 365 |
+
Save the visualization data.
|
| 366 |
+
'''
|
| 367 |
+
model_path = './results/checkpoints/led_vis.p'
|
| 368 |
+
model_dict = torch.load(model_path, map_location=torch.device('cpu'))['model_initializer_dict']
|
| 369 |
+
self.model_initializer.load_state_dict(model_dict)
|
| 370 |
+
def prepare_seed(rand_seed):
|
| 371 |
+
np.random.seed(rand_seed)
|
| 372 |
+
random.seed(rand_seed)
|
| 373 |
+
torch.manual_seed(rand_seed)
|
| 374 |
+
torch.cuda.manual_seed_all(rand_seed)
|
| 375 |
+
prepare_seed(0)
|
| 376 |
+
root_path = './visualization/data/'
|
| 377 |
+
|
| 378 |
+
with torch.no_grad():
|
| 379 |
+
for data in self.test_loader:
|
| 380 |
+
_, traj_mask, past_traj, _ = self.data_preprocess(data)
|
| 381 |
+
|
| 382 |
+
sample_prediction, mean_estimation, variance_estimation = self.model_initializer(past_traj, traj_mask)
|
| 383 |
+
torch.save(sample_prediction, root_path+'p_var.pt')
|
| 384 |
+
torch.save(mean_estimation, root_path+'p_mean.pt')
|
| 385 |
+
torch.save(variance_estimation, root_path+'p_sigma.pt')
|
| 386 |
+
|
| 387 |
+
sample_prediction = torch.exp(variance_estimation/2)[..., None, None] * sample_prediction / sample_prediction.std(dim=1).mean(dim=(1, 2))[:, None, None, None]
|
| 388 |
+
loc = sample_prediction + mean_estimation[:, None]
|
| 389 |
+
|
| 390 |
+
pred_traj = self.p_sample_loop_accelerate(past_traj, traj_mask, loc)
|
| 391 |
+
pred_mean = self.p_sample_loop_mean(past_traj, traj_mask, mean_estimation)
|
| 392 |
+
|
| 393 |
+
torch.save(data['pre_motion_3D'], root_path+'past.pt')
|
| 394 |
+
torch.save(data['fut_motion_3D'], root_path+'future.pt')
|
| 395 |
+
torch.save(pred_traj, root_path+'prediction.pt')
|
| 396 |
+
torch.save(pred_mean, root_path+'p_mean_denoise.pt')
|
| 397 |
+
|
| 398 |
+
raise ValueError
|
| 399 |
+
|
| 400 |
+
|
| 401 |
+
|
| 402 |
+
def test_single_model(self):
|
| 403 |
+
model_path = './results/checkpoints/led_new.p'
|
| 404 |
+
model_dict = torch.load(model_path, map_location=torch.device('cpu'))['model_initializer_dict']
|
| 405 |
+
self.model_initializer.load_state_dict(model_dict)
|
| 406 |
+
performance = { 'FDE': [0, 0, 0, 0],
|
| 407 |
+
'ADE': [0, 0, 0, 0]}
|
| 408 |
+
samples = 0
|
| 409 |
+
print_log(model_path, log=self.log)
|
| 410 |
+
def prepare_seed(rand_seed):
|
| 411 |
+
np.random.seed(rand_seed)
|
| 412 |
+
random.seed(rand_seed)
|
| 413 |
+
torch.manual_seed(rand_seed)
|
| 414 |
+
torch.cuda.manual_seed_all(rand_seed)
|
| 415 |
+
prepare_seed(0)
|
| 416 |
+
count = 0
|
| 417 |
+
with torch.no_grad():
|
| 418 |
+
for data in self.test_loader:
|
| 419 |
+
batch_size, traj_mask, past_traj, fut_traj = self.data_preprocess(data)
|
| 420 |
+
|
| 421 |
+
sample_prediction, mean_estimation, variance_estimation = self.model_initializer(past_traj, traj_mask)
|
| 422 |
+
sample_prediction = torch.exp(variance_estimation/2)[..., None, None] * sample_prediction / sample_prediction.std(dim=1).mean(dim=(1, 2))[:, None, None, None]
|
| 423 |
+
loc = sample_prediction + mean_estimation[:, None]
|
| 424 |
+
|
| 425 |
+
pred_traj = self.p_sample_loop_accelerate(past_traj, traj_mask, loc)
|
| 426 |
+
|
| 427 |
+
fut_traj = fut_traj.unsqueeze(1).repeat(1, 20, 1, 1)
|
| 428 |
+
# b*n, K, T, 2
|
| 429 |
+
distances = torch.norm(fut_traj - pred_traj, dim=-1) * self.traj_scale
|
| 430 |
+
for time_i in range(1, 5):
|
| 431 |
+
ade = (distances[:, :, :5*time_i]).mean(dim=-1).min(dim=-1)[0].sum()
|
| 432 |
+
fde = (distances[:, :, 5*time_i-1]).min(dim=-1)[0].sum()
|
| 433 |
+
performance['ADE'][time_i-1] += ade.item()
|
| 434 |
+
performance['FDE'][time_i-1] += fde.item()
|
| 435 |
+
samples += distances.shape[0]
|
| 436 |
+
count += 1
|
| 437 |
+
# if count==2:
|
| 438 |
+
# break
|
| 439 |
+
for time_i in range(4):
|
| 440 |
+
print_log('--ADE({}s): {:.4f}\t--FDE({}s): {:.4f}'.format(time_i+1, performance['ADE'][time_i]/samples, \
|
| 441 |
+
time_i+1, performance['FDE'][time_i]/samples), log=self.log)
|
| 442 |
+
|
| 443 |
+
|
LED/trainer/train_sdd_led.py
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Stage-2 LED trainer for SDD. Variable-A scenes, batch_size=1, grad_accum.
|
| 3 |
+
Optional graph module (--use_graph --use_v6_graph).
|
| 4 |
+
Eval: only target agent (index 0) counts toward ADE/FDE.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import os, time, torch, random, numpy as np
|
| 8 |
+
import torch.nn as nn
|
| 9 |
+
from utils.config import Config
|
| 10 |
+
from utils.utils import print_log
|
| 11 |
+
from torch.utils.data import DataLoader
|
| 12 |
+
from torch.utils.tensorboard import SummaryWriter
|
| 13 |
+
from data.dataloader_sdd import SDDDataset, sdd_seq_collate
|
| 14 |
+
from models.model_led_initializer import LEDInitializer as InitializationModel
|
| 15 |
+
from models.model_diffusion import TransformerDenoisingModel as CoreDenoisingModel
|
| 16 |
+
|
| 17 |
+
NUM_Tau = 5
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class Trainer:
|
| 21 |
+
def __init__(self, config):
|
| 22 |
+
if torch.cuda.is_available():
|
| 23 |
+
torch.cuda.set_device(config.gpu)
|
| 24 |
+
self.device = torch.device('cuda') if config.cuda else torch.device('cpu')
|
| 25 |
+
self.cfg = Config(config.cfg, config.info)
|
| 26 |
+
self.use_graph = bool(getattr(config, 'use_graph', False))
|
| 27 |
+
self.use_v6_graph = bool(getattr(config, 'use_v6_graph', False))
|
| 28 |
+
self.residual_on = getattr(config, 'residual_on', 'y0')
|
| 29 |
+
self.grad_accum = getattr(config, 'grad_accum', 16)
|
| 30 |
+
|
| 31 |
+
train_dset = SDDDataset(obs_len=self.cfg.past_frames,
|
| 32 |
+
pred_len=self.cfg.future_frames, split='train')
|
| 33 |
+
test_dset = SDDDataset(obs_len=self.cfg.past_frames,
|
| 34 |
+
pred_len=self.cfg.future_frames, split='test')
|
| 35 |
+
self.train_loader = DataLoader(train_dset, batch_size=1, shuffle=True,
|
| 36 |
+
num_workers=2, collate_fn=sdd_seq_collate)
|
| 37 |
+
self.test_loader = DataLoader(test_dset, batch_size=1, shuffle=False,
|
| 38 |
+
num_workers=2, collate_fn=sdd_seq_collate)
|
| 39 |
+
|
| 40 |
+
self.traj_mean = torch.FloatTensor(self.cfg.traj_mean).cuda().unsqueeze(0).unsqueeze(0).unsqueeze(0)
|
| 41 |
+
self.traj_scale = float(self.cfg.traj_scale)
|
| 42 |
+
|
| 43 |
+
self.n_steps = self.cfg.diffusion.steps
|
| 44 |
+
self.betas = self._make_beta_schedule(
|
| 45 |
+
self.cfg.diffusion.beta_schedule, self.n_steps,
|
| 46 |
+
self.cfg.diffusion.beta_start, self.cfg.diffusion.beta_end).cuda()
|
| 47 |
+
self.alphas = 1 - self.betas
|
| 48 |
+
self.alphas_prod = torch.cumprod(self.alphas, 0)
|
| 49 |
+
self.alphas_bar_sqrt = torch.sqrt(self.alphas_prod)
|
| 50 |
+
self.one_minus_alphas_bar_sqrt = torch.sqrt(1 - self.alphas_prod)
|
| 51 |
+
|
| 52 |
+
self.model = CoreDenoisingModel(past_len=self.cfg.past_frames).cuda()
|
| 53 |
+
ckpt_path = self.cfg.pretrained_core_denoising_model
|
| 54 |
+
if not os.path.isfile(ckpt_path):
|
| 55 |
+
raise FileNotFoundError(f'Missing pretrained denoiser: {ckpt_path}')
|
| 56 |
+
self.model.load_state_dict(torch.load(ckpt_path, map_location='cpu')['model_dict'])
|
| 57 |
+
|
| 58 |
+
self.model_initializer = InitializationModel(
|
| 59 |
+
t_h=self.cfg.past_frames, d_h=6,
|
| 60 |
+
t_f=self.cfg.future_frames, d_f=2, k_pred=20).cuda()
|
| 61 |
+
|
| 62 |
+
params = list(self.model_initializer.parameters())
|
| 63 |
+
self.interaction_graph = None
|
| 64 |
+
if self.use_graph:
|
| 65 |
+
from models.future_interaction_graph_v6 import FutureInteractionGraphV6Wrapper
|
| 66 |
+
self.interaction_graph = FutureInteractionGraphV6Wrapper(
|
| 67 |
+
num_agents=64, future_steps=self.cfg.future_frames,
|
| 68 |
+
past_steps=self.cfg.past_frames, past_channels=6,
|
| 69 |
+
node_dim=128, top_n=5, num_denoise_steps=NUM_Tau).cuda()
|
| 70 |
+
params += list(self.interaction_graph.parameters())
|
| 71 |
+
|
| 72 |
+
self.opt = torch.optim.AdamW(params, lr=config.learning_rate)
|
| 73 |
+
self.scheduler = torch.optim.lr_scheduler.StepLR(
|
| 74 |
+
self.opt, step_size=self.cfg.decay_step, gamma=self.cfg.decay_gamma)
|
| 75 |
+
|
| 76 |
+
self.log = open(os.path.join(self.cfg.log_dir, 'log.txt'), 'a+')
|
| 77 |
+
self.tb = SummaryWriter(log_dir=os.path.join(self.cfg.log_dir, 'tb'))
|
| 78 |
+
self.global_step = 0
|
| 79 |
+
self._print_param(self.model, 'Core Denoiser')
|
| 80 |
+
self._print_param(self.model_initializer, 'Initializer')
|
| 81 |
+
if self.interaction_graph:
|
| 82 |
+
self._print_param(self.interaction_graph, 'Graph')
|
| 83 |
+
|
| 84 |
+
T = self.cfg.future_frames
|
| 85 |
+
self.temporal_reweight = torch.FloatTensor(
|
| 86 |
+
[(T + 1) - i for i in range(1, T + 1)]).cuda().unsqueeze(0).unsqueeze(0) / (T / 2)
|
| 87 |
+
|
| 88 |
+
def _print_param(self, m, name):
|
| 89 |
+
t = sum(p.numel() for p in m.parameters())
|
| 90 |
+
tr = sum(p.numel() for p in m.parameters() if p.requires_grad)
|
| 91 |
+
print_log(f'[{name}] {tr}/{t}', self.log)
|
| 92 |
+
|
| 93 |
+
def _make_beta_schedule(self, schedule, n, start, end):
|
| 94 |
+
if schedule == 'linear': return torch.linspace(start, end, n)
|
| 95 |
+
return torch.linspace(start, end, n)
|
| 96 |
+
|
| 97 |
+
def _extract(self, a, t, x):
|
| 98 |
+
out = torch.gather(a, 0, t.to(a.device))
|
| 99 |
+
return out.reshape(t.shape[0], *([1] * (len(x.shape) - 1)))
|
| 100 |
+
|
| 101 |
+
def p_sample_accelerate(self, x, mask, cur_y, t, sigma=None):
|
| 102 |
+
t_tensor = torch.tensor([int(t)]).cuda()
|
| 103 |
+
eps_factor = ((1 - self._extract(self.alphas, t_tensor, cur_y))
|
| 104 |
+
/ self._extract(self.one_minus_alphas_bar_sqrt, t_tensor, cur_y))
|
| 105 |
+
beta = self._extract(self.betas, t_tensor.repeat(x.shape[0]), cur_y)
|
| 106 |
+
eps_theta = self.model.generate_accelerate(cur_y, beta, x, mask)
|
| 107 |
+
|
| 108 |
+
if self.interaction_graph is not None:
|
| 109 |
+
abs_t = self._extract(self.alphas_bar_sqrt, t_tensor, cur_y)
|
| 110 |
+
am1_t = self._extract(self.one_minus_alphas_bar_sqrt, t_tensor, cur_y)
|
| 111 |
+
y0_hat = (cur_y - am1_t * eps_theta) / abs_t
|
| 112 |
+
delta = self.interaction_graph(
|
| 113 |
+
y0_hat, x, int(t), sigma=sigma, A_override=x.size(0))
|
| 114 |
+
eps_theta = eps_theta - (abs_t / am1_t) * delta
|
| 115 |
+
|
| 116 |
+
mean = (1 / self._extract(self.alphas, t_tensor, cur_y).sqrt()) \
|
| 117 |
+
* (cur_y - eps_factor * eps_theta)
|
| 118 |
+
z = torch.randn_like(cur_y)
|
| 119 |
+
sigma_t = self._extract(self.betas, t_tensor, cur_y).sqrt()
|
| 120 |
+
return mean + sigma_t * z * 0.00001
|
| 121 |
+
|
| 122 |
+
def p_sample_loop_accelerate(self, x, mask, loc, sigma=None):
|
| 123 |
+
cur_y = loc[:, :10]
|
| 124 |
+
for i in reversed(range(NUM_Tau)):
|
| 125 |
+
cur_y = self.p_sample_accelerate(x, mask, cur_y, i, sigma=sigma)
|
| 126 |
+
cur_y_ = loc[:, 10:]
|
| 127 |
+
for i in reversed(range(NUM_Tau)):
|
| 128 |
+
cur_y_ = self.p_sample_accelerate(x, mask, cur_y_, i, sigma=sigma)
|
| 129 |
+
return torch.cat((cur_y_, cur_y), dim=1)
|
| 130 |
+
|
| 131 |
+
def data_preprocess(self, data):
|
| 132 |
+
pre = data['pre_motion_3D'].cuda()
|
| 133 |
+
fut = data['fut_motion_3D'].cuda()
|
| 134 |
+
A = pre.size(1)
|
| 135 |
+
initial_pos = pre[:, :, -1:]
|
| 136 |
+
past_abs = ((pre - self.traj_mean) / self.traj_scale).contiguous().view(-1, self.cfg.past_frames, 2)
|
| 137 |
+
past_rel = ((pre - initial_pos) / self.traj_scale).contiguous().view(-1, self.cfg.past_frames, 2)
|
| 138 |
+
past_vel = torch.cat([past_rel[:, 1:] - past_rel[:, :-1],
|
| 139 |
+
torch.zeros_like(past_rel[:, -1:])], dim=1)
|
| 140 |
+
past_traj = torch.cat([past_abs, past_rel, past_vel], dim=-1)
|
| 141 |
+
fut_traj = ((fut - initial_pos) / self.traj_scale).contiguous().view(-1, self.cfg.future_frames, 2)
|
| 142 |
+
mask = torch.ones(A, A).cuda()
|
| 143 |
+
return A, mask, past_traj, fut_traj
|
| 144 |
+
|
| 145 |
+
def fit(self):
|
| 146 |
+
for epoch in range(self.cfg.num_epochs):
|
| 147 |
+
lt, ld, lu = self._train_epoch(epoch)
|
| 148 |
+
print_log(f'[{time.strftime("%Y-%m-%d %H:%M:%S")}] Epoch: {epoch}\t'
|
| 149 |
+
f'Loss: {lt:.6f}\tDist: {ld:.6f}\tUnc: {lu:.6f}', self.log)
|
| 150 |
+
self.tb.add_scalar('train/loss', lt, epoch)
|
| 151 |
+
self.tb.add_scalar('train/loss_dist', ld, epoch)
|
| 152 |
+
|
| 153 |
+
if (epoch + 1) % self.cfg.test_interval == 0:
|
| 154 |
+
perf, n = self._test_epoch()
|
| 155 |
+
# MID protocol: scale normalized ADE/FDE by 50 to report in pixels
|
| 156 |
+
ade_px = perf['ADE'] / n * 50.0
|
| 157 |
+
fde_px = perf['FDE'] / n * 50.0
|
| 158 |
+
print_log(f'Epoch {epoch} Best Of 20: ADE: {ade_px:.4f} FDE: {fde_px:.4f}', self.log)
|
| 159 |
+
self.tb.add_scalar('val/ADE_px', ade_px, epoch)
|
| 160 |
+
self.tb.add_scalar('val/FDE_px', fde_px, epoch)
|
| 161 |
+
|
| 162 |
+
cp = {'model_initializer_dict': self.model_initializer.state_dict()}
|
| 163 |
+
if self.interaction_graph:
|
| 164 |
+
cp['interaction_graph_dict'] = self.interaction_graph.state_dict()
|
| 165 |
+
torch.save(cp, self.cfg.model_path % (epoch + 1))
|
| 166 |
+
self.scheduler.step()
|
| 167 |
+
self.tb.flush(); self.tb.close()
|
| 168 |
+
|
| 169 |
+
def _train_epoch(self, epoch):
|
| 170 |
+
self.model.train(); self.model_initializer.train()
|
| 171 |
+
if self.interaction_graph: self.interaction_graph.train()
|
| 172 |
+
lt, ld, lu, cnt = 0, 0, 0, 0
|
| 173 |
+
self.opt.zero_grad()
|
| 174 |
+
for i, data in enumerate(self.train_loader):
|
| 175 |
+
A, mask, past, fut = self.data_preprocess(data)
|
| 176 |
+
sp, me, ve = self.model_initializer(past, mask)
|
| 177 |
+
ve = ve.clamp(min=-5, max=5)
|
| 178 |
+
sp = torch.exp(ve / 2)[..., None, None] * sp \
|
| 179 |
+
/ (sp.std(dim=1).mean(dim=(1, 2))[:, None, None, None] + 1e-6)
|
| 180 |
+
loc = sp + me[:, None]
|
| 181 |
+
sigma_in = ve if self.use_v6_graph else None
|
| 182 |
+
gen = self.p_sample_loop_accelerate(past, mask, loc, sigma=sigma_in)
|
| 183 |
+
loss_d = ((gen - fut.unsqueeze(1)).norm(p=2, dim=-1)
|
| 184 |
+
* self.temporal_reweight).mean(dim=-1).min(dim=1)[0].mean()
|
| 185 |
+
loss_u = (torch.exp(-ve)
|
| 186 |
+
* (gen - fut.unsqueeze(1)).norm(p=2, dim=-1).mean(dim=(1, 2))
|
| 187 |
+
+ ve).mean()
|
| 188 |
+
loss = loss_d * 50 + loss_u
|
| 189 |
+
(loss / self.grad_accum).backward()
|
| 190 |
+
if (i + 1) % self.grad_accum == 0:
|
| 191 |
+
params = list(self.model_initializer.parameters())
|
| 192 |
+
if self.interaction_graph: params += list(self.interaction_graph.parameters())
|
| 193 |
+
nn.utils.clip_grad_norm_(params, 1.0)
|
| 194 |
+
self.opt.step(); self.opt.zero_grad()
|
| 195 |
+
lt += loss.item(); ld += loss_d.item() * 50; lu += loss_u.item(); cnt += 1
|
| 196 |
+
self.global_step += 1
|
| 197 |
+
self.opt.step(); self.opt.zero_grad()
|
| 198 |
+
return lt / cnt, ld / cnt, lu / cnt
|
| 199 |
+
|
| 200 |
+
def _test_epoch(self):
|
| 201 |
+
"""MID-style SDD protocol:
|
| 202 |
+
per-pedestrian full-horizon ADE (mean L2 over 12 future frames) and
|
| 203 |
+
FDE (L2 at final frame), best_of_20 per pedestrian, then average
|
| 204 |
+
across all evaluated pedestrians. Coordinates are already in
|
| 205 |
+
MID's ÷50 mean-centered space, so the final ADE/FDE is multiplied
|
| 206 |
+
by 50 to report in pixels.
|
| 207 |
+
Each scene in the SDD dataloader corresponds to one target
|
| 208 |
+
pedestrian (index 0) + its neighbors; we evaluate only the target
|
| 209 |
+
per scene so each pedestrian is counted exactly once (matches
|
| 210 |
+
MID's get_timesteps_data qualification intent).
|
| 211 |
+
"""
|
| 212 |
+
T = self.cfg.future_frames
|
| 213 |
+
perf = {'ADE': 0.0, 'FDE': 0.0}
|
| 214 |
+
n = 0
|
| 215 |
+
np.random.seed(0); random.seed(0)
|
| 216 |
+
torch.manual_seed(0); torch.cuda.manual_seed_all(0)
|
| 217 |
+
self.model_initializer.eval()
|
| 218 |
+
if self.interaction_graph: self.interaction_graph.eval()
|
| 219 |
+
with torch.no_grad():
|
| 220 |
+
for data in self.test_loader:
|
| 221 |
+
A, mask, past, fut = self.data_preprocess(data)
|
| 222 |
+
sp, me, ve = self.model_initializer(past, mask)
|
| 223 |
+
ve = ve.clamp(min=-5, max=5)
|
| 224 |
+
sp = torch.exp(ve / 2)[..., None, None] * sp \
|
| 225 |
+
/ (sp.std(dim=1).mean(dim=(1, 2))[:, None, None, None] + 1e-6)
|
| 226 |
+
loc = sp + me[:, None]
|
| 227 |
+
sigma_in = ve if self.use_v6_graph else None
|
| 228 |
+
pred = self.p_sample_loop_accelerate(past, mask, loc, sigma=sigma_in)
|
| 229 |
+
# MID protocol: only target agent (index 0) per scene
|
| 230 |
+
pred_0 = pred[0:1] # [1, 20, T, 2]
|
| 231 |
+
fut_0 = fut[0:1] # [1, T, 2]
|
| 232 |
+
dist = torch.norm(fut_0.unsqueeze(1) - pred_0, dim=-1) * self.traj_scale # [1, 20, T]
|
| 233 |
+
# best_of_20 per pedestrian, then full-horizon ADE / final FDE
|
| 234 |
+
ade_per_ped = dist.mean(dim=-1).min(dim=-1)[0] # [1]
|
| 235 |
+
fde_per_ped = dist[:, :, -1].min(dim=-1)[0] # [1]
|
| 236 |
+
perf['ADE'] += ade_per_ped.sum().item()
|
| 237 |
+
perf['FDE'] += fde_per_ped.sum().item()
|
| 238 |
+
n += 1
|
| 239 |
+
return perf, n
|
LED/trainer/train_sdd_pretrain.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Stage-1 pretraining for LED on SDD.
|
| 3 |
+
Variable-A scenes with batch_size=1 + gradient accumulation.
|
| 4 |
+
past_frames=8, future_frames=12. Pixel coordinates.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import os, time, torch, numpy as np
|
| 8 |
+
import torch.nn as nn
|
| 9 |
+
from utils.config import Config
|
| 10 |
+
from utils.utils import print_log
|
| 11 |
+
from torch.utils.data import DataLoader
|
| 12 |
+
from torch.utils.tensorboard import SummaryWriter
|
| 13 |
+
from data.dataloader_sdd import SDDDataset, sdd_seq_collate
|
| 14 |
+
from models.model_diffusion import TransformerDenoisingModel as CoreDenoisingModel
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class Trainer:
|
| 18 |
+
def __init__(self, config):
|
| 19 |
+
if torch.cuda.is_available():
|
| 20 |
+
torch.cuda.set_device(config.gpu)
|
| 21 |
+
self.device = torch.device('cuda') if config.cuda else torch.device('cpu')
|
| 22 |
+
self.cfg = Config(config.cfg, config.info)
|
| 23 |
+
self.grad_accum = getattr(config, 'grad_accum', 32)
|
| 24 |
+
|
| 25 |
+
train_dset = SDDDataset(obs_len=self.cfg.past_frames,
|
| 26 |
+
pred_len=self.cfg.future_frames, split='train')
|
| 27 |
+
test_dset = SDDDataset(obs_len=self.cfg.past_frames,
|
| 28 |
+
pred_len=self.cfg.future_frames, split='test')
|
| 29 |
+
self.train_loader = DataLoader(train_dset, batch_size=1, shuffle=True,
|
| 30 |
+
num_workers=2, collate_fn=sdd_seq_collate)
|
| 31 |
+
self.val_loader = DataLoader(test_dset, batch_size=1, shuffle=False,
|
| 32 |
+
num_workers=2, collate_fn=sdd_seq_collate)
|
| 33 |
+
|
| 34 |
+
self.traj_mean = torch.FloatTensor(self.cfg.traj_mean).cuda().unsqueeze(0).unsqueeze(0).unsqueeze(0)
|
| 35 |
+
self.traj_scale = float(self.cfg.traj_scale)
|
| 36 |
+
|
| 37 |
+
self.n_steps = self.cfg.diffusion.steps
|
| 38 |
+
self.betas = self._make_beta_schedule(
|
| 39 |
+
self.cfg.diffusion.beta_schedule, self.n_steps,
|
| 40 |
+
self.cfg.diffusion.beta_start, self.cfg.diffusion.beta_end).cuda()
|
| 41 |
+
self.alphas = 1 - self.betas
|
| 42 |
+
self.alphas_prod = torch.cumprod(self.alphas, 0)
|
| 43 |
+
self.alphas_bar_sqrt = torch.sqrt(self.alphas_prod)
|
| 44 |
+
self.one_minus_alphas_bar_sqrt = torch.sqrt(1 - self.alphas_prod)
|
| 45 |
+
|
| 46 |
+
self.model = CoreDenoisingModel(past_len=self.cfg.past_frames).cuda()
|
| 47 |
+
|
| 48 |
+
pre_lr = float(self.cfg.pretrain['lr'])
|
| 49 |
+
self.pre_epochs = int(self.cfg.pretrain['num_epochs'])
|
| 50 |
+
self.opt = torch.optim.AdamW(self.model.parameters(), lr=pre_lr)
|
| 51 |
+
self.scheduler = torch.optim.lr_scheduler.StepLR(
|
| 52 |
+
self.opt, step_size=int(self.cfg.pretrain.get('decay_step', 30)),
|
| 53 |
+
gamma=float(self.cfg.pretrain.get('decay_gamma', 0.5)))
|
| 54 |
+
|
| 55 |
+
self.log = open(os.path.join(self.cfg.log_dir, 'log.txt'), 'a+')
|
| 56 |
+
self.tb = SummaryWriter(log_dir=os.path.join(self.cfg.log_dir, 'tb'))
|
| 57 |
+
self.global_step = 0
|
| 58 |
+
self.ckpt_path = self.cfg.pretrained_core_denoising_model
|
| 59 |
+
total = sum(p.numel() for p in self.model.parameters())
|
| 60 |
+
print_log(f'Core Denoiser params: {total:,}', self.log)
|
| 61 |
+
|
| 62 |
+
def _make_beta_schedule(self, schedule, n, start, end):
|
| 63 |
+
if schedule == 'linear': return torch.linspace(start, end, n)
|
| 64 |
+
elif schedule == 'quad': return torch.linspace(start**0.5, end**0.5, n)**2
|
| 65 |
+
return torch.linspace(start, end, n)
|
| 66 |
+
|
| 67 |
+
def _extract(self, a, t, x):
|
| 68 |
+
out = torch.gather(a, 0, t.to(a.device))
|
| 69 |
+
return out.reshape(t.shape[0], *([1] * (len(x.shape) - 1)))
|
| 70 |
+
|
| 71 |
+
def data_preprocess(self, data):
|
| 72 |
+
pre = data['pre_motion_3D'].cuda() # [1, A, 8, 2]
|
| 73 |
+
fut = data['fut_motion_3D'].cuda() # [1, A, 12, 2]
|
| 74 |
+
A = pre.size(1)
|
| 75 |
+
initial_pos = pre[:, :, -1:] # [1, A, 1, 2]
|
| 76 |
+
past_abs = ((pre - self.traj_mean) / self.traj_scale).contiguous().view(-1, self.cfg.past_frames, 2)
|
| 77 |
+
past_rel = ((pre - initial_pos) / self.traj_scale).contiguous().view(-1, self.cfg.past_frames, 2)
|
| 78 |
+
past_vel = torch.cat([past_rel[:, 1:] - past_rel[:, :-1],
|
| 79 |
+
torch.zeros_like(past_rel[:, -1:])], dim=1)
|
| 80 |
+
past_traj = torch.cat([past_abs, past_rel, past_vel], dim=-1) # [A, 8, 6]
|
| 81 |
+
fut_traj = ((fut - initial_pos) / self.traj_scale).contiguous().view(-1, self.cfg.future_frames, 2)
|
| 82 |
+
mask = torch.ones(A, A).cuda()
|
| 83 |
+
return A, mask, past_traj, fut_traj
|
| 84 |
+
|
| 85 |
+
def noise_estimation_loss(self, x, y_0, mask):
|
| 86 |
+
B = x.shape[0]
|
| 87 |
+
t = torch.randint(0, self.n_steps, size=(B // 2 + 1,)).to(x.device)
|
| 88 |
+
t = torch.cat([t, self.n_steps - t - 1], dim=0)[:B]
|
| 89 |
+
a = self._extract(self.alphas_bar_sqrt, t, y_0)
|
| 90 |
+
beta = self._extract(self.betas, t, y_0)
|
| 91 |
+
am1 = self._extract(self.one_minus_alphas_bar_sqrt, t, y_0)
|
| 92 |
+
e = torch.randn_like(y_0)
|
| 93 |
+
y = y_0 * a + e * am1
|
| 94 |
+
out = self.model(y, beta, x, mask)
|
| 95 |
+
return (e - out).square().mean()
|
| 96 |
+
|
| 97 |
+
def fit(self):
|
| 98 |
+
best_val = float('inf')
|
| 99 |
+
for epoch in range(self.pre_epochs):
|
| 100 |
+
self.model.train()
|
| 101 |
+
loss_sum, n = 0.0, 0
|
| 102 |
+
self.opt.zero_grad()
|
| 103 |
+
for i, data in enumerate(self.train_loader):
|
| 104 |
+
_, mask, past, fut = self.data_preprocess(data)
|
| 105 |
+
loss = self.noise_estimation_loss(past, fut, mask)
|
| 106 |
+
(loss / self.grad_accum).backward()
|
| 107 |
+
if (i + 1) % self.grad_accum == 0:
|
| 108 |
+
nn.utils.clip_grad_norm_(self.model.parameters(), 1.0)
|
| 109 |
+
self.opt.step(); self.opt.zero_grad()
|
| 110 |
+
loss_sum += loss.item(); n += 1
|
| 111 |
+
self.tb.add_scalar('pretrain_step/loss', loss.item(), self.global_step)
|
| 112 |
+
self.global_step += 1
|
| 113 |
+
self.opt.step(); self.opt.zero_grad()
|
| 114 |
+
|
| 115 |
+
train_loss = loss_sum / max(1, n)
|
| 116 |
+
print_log(f'[{time.strftime("%Y-%m-%d %H:%M:%S")}] Pretrain Epoch {epoch} train_mse={train_loss:.6f}', self.log)
|
| 117 |
+
self.tb.add_scalar('pretrain_epoch/train_loss', train_loss, epoch)
|
| 118 |
+
|
| 119 |
+
if (epoch + 1) % 5 == 0:
|
| 120 |
+
self.model.eval()
|
| 121 |
+
val_sum, val_n = 0.0, 0
|
| 122 |
+
with torch.no_grad():
|
| 123 |
+
for data in self.val_loader:
|
| 124 |
+
_, mask, past, fut = self.data_preprocess(data)
|
| 125 |
+
val_sum += self.noise_estimation_loss(past, fut, mask).item()
|
| 126 |
+
val_n += 1
|
| 127 |
+
val_loss = val_sum / max(1, val_n)
|
| 128 |
+
print_log(f' val_mse={val_loss:.6f}', self.log)
|
| 129 |
+
self.tb.add_scalar('pretrain_epoch/val_loss', val_loss, epoch)
|
| 130 |
+
if val_loss < best_val:
|
| 131 |
+
best_val = val_loss
|
| 132 |
+
os.makedirs(os.path.dirname(self.ckpt_path), exist_ok=True)
|
| 133 |
+
torch.save({'model_dict': self.model.state_dict(), 'epoch': epoch}, self.ckpt_path)
|
| 134 |
+
print_log(f' -> saved {self.ckpt_path}', self.log)
|
| 135 |
+
|
| 136 |
+
self.scheduler.step()
|
| 137 |
+
self.tb.flush(); self.tb.close()
|
LED/trainer/train_sport_led.py
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Stage-2 LED trainer for sport datasets (soccer / football).
|
| 3 |
+
|
| 4 |
+
Loads a sport-specific pretrained core denoiser (produced by
|
| 5 |
+
train_sport_pretrain.py), then trains the leapfrog initializer in the
|
| 6 |
+
standard LED way. If --use_graph is set, also instantiates
|
| 7 |
+
FutureInteractionGraph (top_n=5, residual_on='y0' — the winning NBA
|
| 8 |
+
variant) and adds its output as a residual correction inside each
|
| 9 |
+
leapfrog reverse step.
|
| 10 |
+
|
| 11 |
+
Agent count, data path, traj_mean and traj_scale all come from the
|
| 12 |
+
sport config, so the same trainer runs on soccer and football with
|
| 13 |
+
different ymls.
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
import os
|
| 17 |
+
import time
|
| 18 |
+
import torch
|
| 19 |
+
import random
|
| 20 |
+
import numpy as np
|
| 21 |
+
import torch.nn as nn
|
| 22 |
+
|
| 23 |
+
from utils.config import Config
|
| 24 |
+
from utils.utils import print_log
|
| 25 |
+
|
| 26 |
+
from torch.utils.data import DataLoader
|
| 27 |
+
from torch.utils.tensorboard import SummaryWriter
|
| 28 |
+
|
| 29 |
+
from data.dataloader_sport import SportDataset, sport_seq_collate
|
| 30 |
+
from models.model_led_initializer import LEDInitializer as InitializationModel
|
| 31 |
+
from models.model_diffusion import TransformerDenoisingModel as CoreDenoisingModel
|
| 32 |
+
from models.future_interaction_graph import FutureInteractionGraph
|
| 33 |
+
from models.future_interaction_graph_v6 import FutureInteractionGraphV6Wrapper
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
NUM_Tau = 5
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class Trainer:
|
| 40 |
+
def __init__(self, config):
|
| 41 |
+
if torch.cuda.is_available():
|
| 42 |
+
torch.cuda.set_device(config.gpu)
|
| 43 |
+
self.device = torch.device('cuda') if config.cuda else torch.device('cpu')
|
| 44 |
+
self.cfg = Config(config.cfg, config.info)
|
| 45 |
+
self.use_graph = bool(getattr(config, 'use_graph', False))
|
| 46 |
+
self.residual_on = getattr(config, 'residual_on', 'y0')
|
| 47 |
+
|
| 48 |
+
# ------------------------- data -------------------------
|
| 49 |
+
self.num_agents = self.cfg.num_agents
|
| 50 |
+
train_dset = SportDataset(
|
| 51 |
+
data_dir = self.cfg.data_dir,
|
| 52 |
+
num_agents = self.num_agents,
|
| 53 |
+
obs_len = self.cfg.past_frames,
|
| 54 |
+
pred_len = self.cfg.future_frames,
|
| 55 |
+
split = 'train',
|
| 56 |
+
)
|
| 57 |
+
val_dset = SportDataset(
|
| 58 |
+
data_dir = self.cfg.data_dir,
|
| 59 |
+
num_agents = self.num_agents,
|
| 60 |
+
obs_len = self.cfg.past_frames,
|
| 61 |
+
pred_len = self.cfg.future_frames,
|
| 62 |
+
split = 'val',
|
| 63 |
+
)
|
| 64 |
+
self.train_loader = DataLoader(
|
| 65 |
+
train_dset, batch_size=self.cfg.train_batch_size, shuffle=True,
|
| 66 |
+
num_workers=4, collate_fn=sport_seq_collate, pin_memory=True)
|
| 67 |
+
self.test_loader = DataLoader(
|
| 68 |
+
val_dset, batch_size=self.cfg.test_batch_size, shuffle=False,
|
| 69 |
+
num_workers=4, collate_fn=sport_seq_collate, pin_memory=True)
|
| 70 |
+
|
| 71 |
+
self.traj_mean = torch.FloatTensor(self.cfg.traj_mean).cuda().unsqueeze(0).unsqueeze(0).unsqueeze(0)
|
| 72 |
+
self.traj_scale = float(self.cfg.traj_scale)
|
| 73 |
+
self.per_scene_norm = bool(self.cfg.get('per_scene_norm', False))
|
| 74 |
+
|
| 75 |
+
# ------------------------- diffusion parameters -------------------------
|
| 76 |
+
self.n_steps = self.cfg.diffusion.steps
|
| 77 |
+
self.betas = self.make_beta_schedule(
|
| 78 |
+
schedule=self.cfg.diffusion.beta_schedule, n_timesteps=self.n_steps,
|
| 79 |
+
start=self.cfg.diffusion.beta_start, end=self.cfg.diffusion.beta_end).cuda()
|
| 80 |
+
self.alphas = 1 - self.betas
|
| 81 |
+
self.alphas_prod = torch.cumprod(self.alphas, 0)
|
| 82 |
+
self.alphas_bar_sqrt = torch.sqrt(self.alphas_prod)
|
| 83 |
+
self.one_minus_alphas_bar_sqrt = torch.sqrt(1 - self.alphas_prod)
|
| 84 |
+
|
| 85 |
+
# ------------------------- models -------------------------
|
| 86 |
+
self.model = CoreDenoisingModel().cuda()
|
| 87 |
+
ckpt_path = self.cfg.pretrained_core_denoising_model
|
| 88 |
+
if not os.path.isfile(ckpt_path):
|
| 89 |
+
raise FileNotFoundError(
|
| 90 |
+
f'Missing sport-specific pretrained denoiser: {ckpt_path}. '
|
| 91 |
+
'Run train_sport_pretrain.py first.')
|
| 92 |
+
core_cp = torch.load(ckpt_path, map_location='cpu')
|
| 93 |
+
self.model.load_state_dict(core_cp['model_dict'])
|
| 94 |
+
|
| 95 |
+
self.model_initializer = InitializationModel(
|
| 96 |
+
t_h=self.cfg.past_frames, d_h=6,
|
| 97 |
+
t_f=self.cfg.future_frames, d_f=2,
|
| 98 |
+
k_pred=20).cuda()
|
| 99 |
+
|
| 100 |
+
params = list(self.model_initializer.parameters())
|
| 101 |
+
self.interaction_graph = None
|
| 102 |
+
self.use_v6_graph = bool(getattr(config, 'use_v6_graph', False))
|
| 103 |
+
if self.use_graph:
|
| 104 |
+
if self.use_v6_graph:
|
| 105 |
+
self.interaction_graph = FutureInteractionGraphV6Wrapper(
|
| 106 |
+
num_agents = self.num_agents,
|
| 107 |
+
future_steps = self.cfg.future_frames,
|
| 108 |
+
past_steps = self.cfg.past_frames,
|
| 109 |
+
past_channels = 6,
|
| 110 |
+
node_dim = 128,
|
| 111 |
+
top_n = min(int(__import__('os').environ.get('LED_TOP_N', 5)), self.num_agents - 1),
|
| 112 |
+
num_denoise_steps = NUM_Tau,
|
| 113 |
+
).cuda()
|
| 114 |
+
else:
|
| 115 |
+
self.interaction_graph = FutureInteractionGraph(
|
| 116 |
+
num_agents = self.num_agents,
|
| 117 |
+
future_steps = self.cfg.future_frames,
|
| 118 |
+
past_steps = self.cfg.past_frames,
|
| 119 |
+
past_channels = 6,
|
| 120 |
+
node_dim = 128,
|
| 121 |
+
top_n = min(int(__import__('os').environ.get('LED_TOP_N', 5)), self.num_agents - 1),
|
| 122 |
+
num_denoise_steps = NUM_Tau,
|
| 123 |
+
).cuda()
|
| 124 |
+
params += list(self.interaction_graph.parameters())
|
| 125 |
+
|
| 126 |
+
self.opt = torch.optim.AdamW(params, lr=config.learning_rate)
|
| 127 |
+
self.scheduler_model = torch.optim.lr_scheduler.StepLR(
|
| 128 |
+
self.opt, step_size=self.cfg.decay_step, gamma=self.cfg.decay_gamma)
|
| 129 |
+
|
| 130 |
+
# ------------------------- logs -------------------------
|
| 131 |
+
self.log = open(os.path.join(self.cfg.log_dir, 'log.txt'), 'a+')
|
| 132 |
+
self.tb = SummaryWriter(log_dir=os.path.join(self.cfg.log_dir, 'tb'))
|
| 133 |
+
self.global_step = 0
|
| 134 |
+
self.print_model_param(self.model, name='Core Denoising Model')
|
| 135 |
+
self.print_model_param(self.model_initializer, name='Initialization Model')
|
| 136 |
+
if self.use_graph:
|
| 137 |
+
self.print_model_param(self.interaction_graph, name='Future Interaction Graph')
|
| 138 |
+
|
| 139 |
+
# temporal reweight: [T, T-1, ..., 1] / (T/2)
|
| 140 |
+
T = self.cfg.future_frames
|
| 141 |
+
self.temporal_reweight = torch.FloatTensor(
|
| 142 |
+
[(T + 1) - i for i in range(1, T + 1)]).cuda().unsqueeze(0).unsqueeze(0) / (T / 2)
|
| 143 |
+
|
| 144 |
+
def print_model_param(self, model: nn.Module, name: str):
|
| 145 |
+
total = sum(p.numel() for p in model.parameters())
|
| 146 |
+
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
|
| 147 |
+
print_log(f'[{name}] Trainable/Total: {trainable}/{total}', self.log)
|
| 148 |
+
|
| 149 |
+
def make_beta_schedule(self, schedule='linear', n_timesteps=1000, start=1e-5, end=1e-2):
|
| 150 |
+
if schedule == 'linear':
|
| 151 |
+
betas = torch.linspace(start, end, n_timesteps)
|
| 152 |
+
elif schedule == 'quad':
|
| 153 |
+
betas = torch.linspace(start ** 0.5, end ** 0.5, n_timesteps) ** 2
|
| 154 |
+
elif schedule == 'sigmoid':
|
| 155 |
+
betas = torch.linspace(-6, 6, n_timesteps)
|
| 156 |
+
betas = torch.sigmoid(betas) * (end - start) + start
|
| 157 |
+
return betas
|
| 158 |
+
|
| 159 |
+
def extract(self, inp, t, x):
|
| 160 |
+
shape = x.shape
|
| 161 |
+
out = torch.gather(inp, 0, t.to(inp.device))
|
| 162 |
+
reshape = [t.shape[0]] + [1] * (len(shape) - 1)
|
| 163 |
+
return out.reshape(*reshape)
|
| 164 |
+
|
| 165 |
+
# ------------------------------------------------------------------
|
| 166 |
+
# Leapfrog reverse step (+ optional graph residual)
|
| 167 |
+
# ------------------------------------------------------------------
|
| 168 |
+
def p_sample_accelerate(self, x, mask, cur_y, t, sigma=None):
|
| 169 |
+
step_idx = int(t)
|
| 170 |
+
t = torch.tensor([t]).cuda()
|
| 171 |
+
eps_factor = ((1 - self.extract(self.alphas, t, cur_y))
|
| 172 |
+
/ self.extract(self.one_minus_alphas_bar_sqrt, t, cur_y))
|
| 173 |
+
beta = self.extract(self.betas, t.repeat(x.shape[0]), cur_y)
|
| 174 |
+
eps_theta = self.model.generate_accelerate(cur_y, beta, x, mask)
|
| 175 |
+
|
| 176 |
+
if self.interaction_graph is not None:
|
| 177 |
+
alpha_bar_sqrt_t = self.extract(self.alphas_bar_sqrt, t, cur_y)
|
| 178 |
+
one_minus_abs_t = self.extract(self.one_minus_alphas_bar_sqrt, t, cur_y)
|
| 179 |
+
y0_hat = (cur_y - one_minus_abs_t * eps_theta) / alpha_bar_sqrt_t
|
| 180 |
+
if self.use_v6_graph:
|
| 181 |
+
delta = self.interaction_graph(y0_hat, x, step_idx, sigma=sigma)
|
| 182 |
+
else:
|
| 183 |
+
delta = self.interaction_graph(y0_hat, x, step_idx)
|
| 184 |
+
if self.residual_on == 'eps':
|
| 185 |
+
eps_theta = eps_theta + delta
|
| 186 |
+
else:
|
| 187 |
+
eps_theta = eps_theta - (alpha_bar_sqrt_t / one_minus_abs_t) * delta
|
| 188 |
+
|
| 189 |
+
mean = (1 / self.extract(self.alphas, t, cur_y).sqrt()) \
|
| 190 |
+
* (cur_y - (eps_factor * eps_theta))
|
| 191 |
+
z = torch.randn_like(cur_y).to(x.device)
|
| 192 |
+
sigma_t = self.extract(self.betas, t, cur_y).sqrt()
|
| 193 |
+
return mean + sigma_t * z * 0.00001
|
| 194 |
+
|
| 195 |
+
def p_sample_loop_accelerate(self, x, mask, loc, sigma=None):
|
| 196 |
+
cur_y = loc[:, :10]
|
| 197 |
+
for i in reversed(range(NUM_Tau)):
|
| 198 |
+
cur_y = self.p_sample_accelerate(x, mask, cur_y, i, sigma=sigma)
|
| 199 |
+
cur_y_ = loc[:, 10:]
|
| 200 |
+
for i in reversed(range(NUM_Tau)):
|
| 201 |
+
cur_y_ = self.p_sample_accelerate(x, mask, cur_y_, i, sigma=sigma)
|
| 202 |
+
return torch.cat((cur_y_, cur_y), dim=1)
|
| 203 |
+
|
| 204 |
+
# ------------------------------------------------------------------
|
| 205 |
+
# Data preprocess (num_agents parameterized)
|
| 206 |
+
# ------------------------------------------------------------------
|
| 207 |
+
def data_preprocess(self, data):
|
| 208 |
+
A = self.num_agents
|
| 209 |
+
batch_size = data['pre_motion_3D'].shape[0]
|
| 210 |
+
|
| 211 |
+
traj_mask = torch.zeros(batch_size * A, batch_size * A).cuda()
|
| 212 |
+
for i in range(batch_size):
|
| 213 |
+
traj_mask[i * A:(i + 1) * A, i * A:(i + 1) * A] = 1.
|
| 214 |
+
|
| 215 |
+
pre = data['pre_motion_3D'].cuda()
|
| 216 |
+
fut = data['fut_motion_3D'].cuda()
|
| 217 |
+
initial_pos = pre[:, :, -1:]
|
| 218 |
+
|
| 219 |
+
if self.per_scene_norm:
|
| 220 |
+
scene_center = pre[:, :, -1, :].mean(dim=1, keepdim=True).unsqueeze(2)
|
| 221 |
+
past_traj_abs = ((pre - scene_center) / self.traj_scale).contiguous().view(-1, self.cfg.past_frames, 2)
|
| 222 |
+
else:
|
| 223 |
+
past_traj_abs = ((pre - self.traj_mean) / self.traj_scale).contiguous().view(-1, self.cfg.past_frames, 2)
|
| 224 |
+
past_traj_rel = ((pre - initial_pos) / self.traj_scale).contiguous().view(-1, self.cfg.past_frames, 2)
|
| 225 |
+
past_traj_vel = torch.cat(
|
| 226 |
+
(past_traj_rel[:, 1:] - past_traj_rel[:, :-1],
|
| 227 |
+
torch.zeros_like(past_traj_rel[:, -1:])), dim=1)
|
| 228 |
+
past_traj = torch.cat((past_traj_abs, past_traj_rel, past_traj_vel), dim=-1)
|
| 229 |
+
|
| 230 |
+
fut_traj = ((fut - initial_pos) / self.traj_scale).contiguous().view(-1, self.cfg.future_frames, 2)
|
| 231 |
+
return batch_size, traj_mask, past_traj, fut_traj
|
| 232 |
+
|
| 233 |
+
# ------------------------------------------------------------------
|
| 234 |
+
# Training / validation
|
| 235 |
+
# ------------------------------------------------------------------
|
| 236 |
+
def fit(self):
|
| 237 |
+
for epoch in range(self.cfg.num_epochs):
|
| 238 |
+
loss_total, loss_dt, loss_dc = self._train_single_epoch(epoch)
|
| 239 |
+
print_log(
|
| 240 |
+
f'[{time.strftime("%Y-%m-%d %H:%M:%S")}] Epoch: {epoch}\t\tLoss: {loss_total:.6f}\t'
|
| 241 |
+
f'Loss Dist.: {loss_dt:.6f}\tLoss Uncertainty: {loss_dc:.6f}', self.log)
|
| 242 |
+
self.tb.add_scalar('train_epoch/loss_total', loss_total, epoch)
|
| 243 |
+
self.tb.add_scalar('train_epoch/loss_dist_x50', loss_dt, epoch)
|
| 244 |
+
self.tb.add_scalar('train_epoch/loss_uncertainty', loss_dc, epoch)
|
| 245 |
+
self.tb.add_scalar('train_epoch/lr', self.opt.param_groups[0]['lr'], epoch)
|
| 246 |
+
|
| 247 |
+
if (epoch + 1) % self.cfg.test_interval == 0:
|
| 248 |
+
performance, samples = self._test_single_epoch()
|
| 249 |
+
for i in range(4):
|
| 250 |
+
ade = performance['ADE'][i] / samples
|
| 251 |
+
fde = performance['FDE'][i] / samples
|
| 252 |
+
print_log(f'--ADE({i+1}s): {ade:.4f}\t--FDE({i+1}s): {fde:.4f}', self.log)
|
| 253 |
+
self.tb.add_scalar(f'val/ADE_{i+1}s', ade, epoch)
|
| 254 |
+
self.tb.add_scalar(f'val/FDE_{i+1}s', fde, epoch)
|
| 255 |
+
|
| 256 |
+
cp_path = self.cfg.model_path % (epoch + 1)
|
| 257 |
+
cp = {'model_initializer_dict': self.model_initializer.state_dict()}
|
| 258 |
+
if self.interaction_graph is not None:
|
| 259 |
+
cp['interaction_graph_dict'] = self.interaction_graph.state_dict()
|
| 260 |
+
torch.save(cp, cp_path)
|
| 261 |
+
self.scheduler_model.step()
|
| 262 |
+
self.tb.flush(); self.tb.close()
|
| 263 |
+
|
| 264 |
+
def _train_single_epoch(self, epoch):
|
| 265 |
+
self.model.train()
|
| 266 |
+
self.model_initializer.train()
|
| 267 |
+
if self.interaction_graph is not None:
|
| 268 |
+
self.interaction_graph.train()
|
| 269 |
+
|
| 270 |
+
loss_total, loss_dt, loss_dc, count = 0, 0, 0, 0
|
| 271 |
+
for data in self.train_loader:
|
| 272 |
+
batch_size, traj_mask, past_traj, fut_traj = self.data_preprocess(data)
|
| 273 |
+
|
| 274 |
+
sample_prediction, mean_estimation, variance_estimation = self.model_initializer(past_traj, traj_mask)
|
| 275 |
+
sample_prediction = torch.exp(variance_estimation / 2)[..., None, None] \
|
| 276 |
+
* sample_prediction \
|
| 277 |
+
/ sample_prediction.std(dim=1).mean(dim=(1, 2))[:, None, None, None]
|
| 278 |
+
loc = sample_prediction + mean_estimation[:, None]
|
| 279 |
+
|
| 280 |
+
sigma_in = None if __import__('os').environ.get('LED_NO_SIGMA') else (variance_estimation if self.use_v6_graph else None)
|
| 281 |
+
generated_y = self.p_sample_loop_accelerate(past_traj, traj_mask, loc, sigma=sigma_in)
|
| 282 |
+
|
| 283 |
+
loss_dist = ((generated_y - fut_traj.unsqueeze(dim=1)).norm(p=2, dim=-1)
|
| 284 |
+
* self.temporal_reweight).mean(dim=-1).min(dim=1)[0].mean()
|
| 285 |
+
loss_uncertainty = (torch.exp(-variance_estimation)
|
| 286 |
+
* (generated_y - fut_traj.unsqueeze(dim=1)).norm(p=2, dim=-1).mean(dim=(1, 2))
|
| 287 |
+
+ variance_estimation).mean()
|
| 288 |
+
|
| 289 |
+
loss = loss_dist * 50 + loss_uncertainty
|
| 290 |
+
loss_total += loss.item()
|
| 291 |
+
loss_dt += loss_dist.item() * 50
|
| 292 |
+
loss_dc += loss_uncertainty.item()
|
| 293 |
+
|
| 294 |
+
self.opt.zero_grad()
|
| 295 |
+
loss.backward()
|
| 296 |
+
params = list(self.model_initializer.parameters())
|
| 297 |
+
if self.interaction_graph is not None:
|
| 298 |
+
params += list(self.interaction_graph.parameters())
|
| 299 |
+
grad_norm = torch.nn.utils.clip_grad_norm_(params, 1.)
|
| 300 |
+
self.opt.step()
|
| 301 |
+
|
| 302 |
+
self.tb.add_scalar('train_step/loss_total', loss.item(), self.global_step)
|
| 303 |
+
self.tb.add_scalar('train_step/loss_dist_x50', loss_dist.item() * 50, self.global_step)
|
| 304 |
+
self.tb.add_scalar('train_step/loss_uncertainty', loss_uncertainty.item(), self.global_step)
|
| 305 |
+
self.tb.add_scalar('train_step/grad_norm', float(grad_norm), self.global_step)
|
| 306 |
+
self.global_step += 1
|
| 307 |
+
|
| 308 |
+
count += 1
|
| 309 |
+
if self.cfg.debug and count == 2:
|
| 310 |
+
break
|
| 311 |
+
return loss_total / count, loss_dt / count, loss_dc / count
|
| 312 |
+
|
| 313 |
+
def _test_single_epoch(self):
|
| 314 |
+
performance = {'FDE': [0, 0, 0, 0], 'ADE': [0, 0, 0, 0]}
|
| 315 |
+
samples = 0
|
| 316 |
+
|
| 317 |
+
def prepare_seed(rand_seed):
|
| 318 |
+
np.random.seed(rand_seed); random.seed(rand_seed)
|
| 319 |
+
torch.manual_seed(rand_seed); torch.cuda.manual_seed_all(rand_seed)
|
| 320 |
+
prepare_seed(0)
|
| 321 |
+
|
| 322 |
+
self.model_initializer.eval()
|
| 323 |
+
if self.interaction_graph is not None:
|
| 324 |
+
self.interaction_graph.eval()
|
| 325 |
+
|
| 326 |
+
# validation horizon: 4 checkpoints evenly across future_frames
|
| 327 |
+
T_fut = self.cfg.future_frames
|
| 328 |
+
step = max(1, T_fut // 4)
|
| 329 |
+
horizons = [min(T_fut, step * (i + 1)) for i in range(4)]
|
| 330 |
+
|
| 331 |
+
with torch.no_grad():
|
| 332 |
+
for data in self.test_loader:
|
| 333 |
+
batch_size, traj_mask, past_traj, fut_traj = self.data_preprocess(data)
|
| 334 |
+
|
| 335 |
+
sample_prediction, mean_estimation, variance_estimation = self.model_initializer(past_traj, traj_mask)
|
| 336 |
+
sample_prediction = torch.exp(variance_estimation / 2)[..., None, None] \
|
| 337 |
+
* sample_prediction \
|
| 338 |
+
/ sample_prediction.std(dim=1).mean(dim=(1, 2))[:, None, None, None]
|
| 339 |
+
loc = sample_prediction + mean_estimation[:, None]
|
| 340 |
+
|
| 341 |
+
sigma_in = None if __import__('os').environ.get('LED_NO_SIGMA') else (variance_estimation if self.use_v6_graph else None)
|
| 342 |
+
pred_traj = self.p_sample_loop_accelerate(past_traj, traj_mask, loc, sigma=sigma_in)
|
| 343 |
+
|
| 344 |
+
fut_traj_k = fut_traj.unsqueeze(1).repeat(1, 20, 1, 1)
|
| 345 |
+
distances = torch.norm(fut_traj_k - pred_traj, dim=-1) * self.traj_scale
|
| 346 |
+
for i, h in enumerate(horizons):
|
| 347 |
+
ade = distances[:, :, :h].mean(dim=-1).min(dim=-1)[0].sum()
|
| 348 |
+
fde = distances[:, :, h - 1].min(dim=-1)[0].sum()
|
| 349 |
+
performance['ADE'][i] += ade.item()
|
| 350 |
+
performance['FDE'][i] += fde.item()
|
| 351 |
+
samples += distances.shape[0]
|
| 352 |
+
return performance, samples
|
LED/trainer/train_sport_pretrain.py
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Stage-1 pretraining for LED on sport datasets (soccer / football).
|
| 3 |
+
|
| 4 |
+
This is the training stage that LED's public README leaves on its TODO list:
|
| 5 |
+
trains the core 100-step DDPM denoiser (TransformerDenoisingModel) from
|
| 6 |
+
scratch on a sport dataset using the standard eps-MSE loss. The resulting
|
| 7 |
+
checkpoint is saved to `cfg.pretrained_core_denoising_model` so that
|
| 8 |
+
train_sport_led.py (stage 2) can load it as a frozen refiner.
|
| 9 |
+
|
| 10 |
+
Optimizes: self.model (core denoiser) only.
|
| 11 |
+
Loss: noise_estimation_loss — predict eps at a uniformly-sampled diffusion
|
| 12 |
+
step t ∈ [0, n_steps) and regress via MSE.
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
import os
|
| 16 |
+
import time
|
| 17 |
+
import torch
|
| 18 |
+
import random
|
| 19 |
+
import numpy as np
|
| 20 |
+
import torch.nn as nn
|
| 21 |
+
|
| 22 |
+
from utils.config import Config
|
| 23 |
+
from utils.utils import print_log
|
| 24 |
+
|
| 25 |
+
from torch.utils.data import DataLoader
|
| 26 |
+
from torch.utils.tensorboard import SummaryWriter
|
| 27 |
+
|
| 28 |
+
from data.dataloader_sport import SportDataset, sport_seq_collate
|
| 29 |
+
from models.model_diffusion import TransformerDenoisingModel as CoreDenoisingModel
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class Trainer:
|
| 33 |
+
def __init__(self, config):
|
| 34 |
+
if torch.cuda.is_available():
|
| 35 |
+
torch.cuda.set_device(config.gpu)
|
| 36 |
+
self.device = torch.device('cuda') if config.cuda else torch.device('cpu')
|
| 37 |
+
self.cfg = Config(config.cfg, config.info)
|
| 38 |
+
|
| 39 |
+
# ------------------------- data -------------------------
|
| 40 |
+
self.num_agents = self.cfg.num_agents
|
| 41 |
+
train_dset = SportDataset(
|
| 42 |
+
data_dir = self.cfg.data_dir,
|
| 43 |
+
num_agents = self.num_agents,
|
| 44 |
+
obs_len = self.cfg.past_frames,
|
| 45 |
+
pred_len = self.cfg.future_frames,
|
| 46 |
+
split = 'train',
|
| 47 |
+
)
|
| 48 |
+
val_dset = SportDataset(
|
| 49 |
+
data_dir = self.cfg.data_dir,
|
| 50 |
+
num_agents = self.num_agents,
|
| 51 |
+
obs_len = self.cfg.past_frames,
|
| 52 |
+
pred_len = self.cfg.future_frames,
|
| 53 |
+
split = 'val',
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
pre_bs = self.cfg.pretrain['train_batch_size']
|
| 57 |
+
self.train_loader = DataLoader(
|
| 58 |
+
train_dset, batch_size=pre_bs, shuffle=True,
|
| 59 |
+
num_workers=4, collate_fn=sport_seq_collate, pin_memory=True)
|
| 60 |
+
self.val_loader = DataLoader(
|
| 61 |
+
val_dset, batch_size=self.cfg.test_batch_size, shuffle=False,
|
| 62 |
+
num_workers=4, collate_fn=sport_seq_collate, pin_memory=True)
|
| 63 |
+
|
| 64 |
+
self.traj_mean = torch.FloatTensor(self.cfg.traj_mean).cuda().unsqueeze(0).unsqueeze(0).unsqueeze(0)
|
| 65 |
+
self.traj_scale = float(self.cfg.traj_scale)
|
| 66 |
+
self.per_scene_norm = bool(self.cfg.get('per_scene_norm', False))
|
| 67 |
+
|
| 68 |
+
# ------------------------- diffusion parameters -------------------------
|
| 69 |
+
self.n_steps = self.cfg.diffusion.steps
|
| 70 |
+
self.betas = self.make_beta_schedule(
|
| 71 |
+
schedule=self.cfg.diffusion.beta_schedule, n_timesteps=self.n_steps,
|
| 72 |
+
start=self.cfg.diffusion.beta_start, end=self.cfg.diffusion.beta_end).cuda()
|
| 73 |
+
self.alphas = 1 - self.betas
|
| 74 |
+
self.alphas_prod = torch.cumprod(self.alphas, 0)
|
| 75 |
+
self.alphas_bar_sqrt = torch.sqrt(self.alphas_prod)
|
| 76 |
+
self.one_minus_alphas_bar_sqrt = torch.sqrt(1 - self.alphas_prod)
|
| 77 |
+
|
| 78 |
+
# ------------------------- model (the only thing being trained) -------------------------
|
| 79 |
+
self.model = CoreDenoisingModel().cuda()
|
| 80 |
+
|
| 81 |
+
pre_lr = float(self.cfg.pretrain['lr'])
|
| 82 |
+
pre_decay_step = int(self.cfg.pretrain['decay_step'])
|
| 83 |
+
pre_decay_gamma = float(self.cfg.pretrain['decay_gamma'])
|
| 84 |
+
self.pre_epochs = int(self.cfg.pretrain['num_epochs'])
|
| 85 |
+
|
| 86 |
+
self.opt = torch.optim.AdamW(self.model.parameters(), lr=pre_lr)
|
| 87 |
+
self.scheduler = torch.optim.lr_scheduler.StepLR(
|
| 88 |
+
self.opt, step_size=pre_decay_step, gamma=pre_decay_gamma)
|
| 89 |
+
|
| 90 |
+
# ------------------------- logs -------------------------
|
| 91 |
+
self.log = open(os.path.join(self.cfg.log_dir, 'log.txt'), 'a+')
|
| 92 |
+
self.tb = SummaryWriter(log_dir=os.path.join(self.cfg.log_dir, 'tb'))
|
| 93 |
+
self.global_step = 0
|
| 94 |
+
self.print_model_param(self.model, name='Core Denoising Model')
|
| 95 |
+
|
| 96 |
+
self.ckpt_path = self.cfg.pretrained_core_denoising_model
|
| 97 |
+
|
| 98 |
+
def print_model_param(self, model: nn.Module, name: str = 'Model'):
|
| 99 |
+
total = sum(p.numel() for p in model.parameters())
|
| 100 |
+
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
|
| 101 |
+
print_log(f'[{name}] Trainable/Total: {trainable}/{total}', self.log)
|
| 102 |
+
|
| 103 |
+
def make_beta_schedule(self, schedule='linear', n_timesteps=1000, start=1e-5, end=1e-2):
|
| 104 |
+
if schedule == 'linear':
|
| 105 |
+
betas = torch.linspace(start, end, n_timesteps)
|
| 106 |
+
elif schedule == 'quad':
|
| 107 |
+
betas = torch.linspace(start ** 0.5, end ** 0.5, n_timesteps) ** 2
|
| 108 |
+
elif schedule == 'sigmoid':
|
| 109 |
+
betas = torch.linspace(-6, 6, n_timesteps)
|
| 110 |
+
betas = torch.sigmoid(betas) * (end - start) + start
|
| 111 |
+
return betas
|
| 112 |
+
|
| 113 |
+
def extract(self, inp, t, x):
|
| 114 |
+
shape = x.shape
|
| 115 |
+
out = torch.gather(inp, 0, t.to(inp.device))
|
| 116 |
+
reshape = [t.shape[0]] + [1] * (len(shape) - 1)
|
| 117 |
+
return out.reshape(*reshape)
|
| 118 |
+
|
| 119 |
+
# ------------------------------------------------------------------
|
| 120 |
+
# Data preprocess — identical math to LED NBA trainer but with
|
| 121 |
+
# num_agents parameterized so the block-diagonal mask is A×A, not 11×11.
|
| 122 |
+
# ------------------------------------------------------------------
|
| 123 |
+
def data_preprocess(self, data):
|
| 124 |
+
A = self.num_agents
|
| 125 |
+
batch_size = data['pre_motion_3D'].shape[0]
|
| 126 |
+
|
| 127 |
+
traj_mask = torch.zeros(batch_size * A, batch_size * A).cuda()
|
| 128 |
+
for i in range(batch_size):
|
| 129 |
+
traj_mask[i * A:(i + 1) * A, i * A:(i + 1) * A] = 1.
|
| 130 |
+
|
| 131 |
+
pre = data['pre_motion_3D'].cuda()
|
| 132 |
+
fut = data['fut_motion_3D'].cuda()
|
| 133 |
+
initial_pos = pre[:, :, -1:]
|
| 134 |
+
|
| 135 |
+
if self.per_scene_norm:
|
| 136 |
+
scene_center = pre[:, :, -1, :].mean(dim=1, keepdim=True).unsqueeze(2)
|
| 137 |
+
past_traj_abs = ((pre - scene_center) / self.traj_scale).contiguous().view(-1, self.cfg.past_frames, 2)
|
| 138 |
+
else:
|
| 139 |
+
past_traj_abs = ((pre - self.traj_mean) / self.traj_scale).contiguous().view(-1, self.cfg.past_frames, 2)
|
| 140 |
+
past_traj_rel = ((pre - initial_pos) / self.traj_scale).contiguous().view(-1, self.cfg.past_frames, 2)
|
| 141 |
+
past_traj_vel = torch.cat(
|
| 142 |
+
(past_traj_rel[:, 1:] - past_traj_rel[:, :-1],
|
| 143 |
+
torch.zeros_like(past_traj_rel[:, -1:])), dim=1)
|
| 144 |
+
past_traj = torch.cat((past_traj_abs, past_traj_rel, past_traj_vel), dim=-1)
|
| 145 |
+
|
| 146 |
+
fut_traj = ((fut - initial_pos) / self.traj_scale).contiguous().view(-1, self.cfg.future_frames, 2)
|
| 147 |
+
return batch_size, traj_mask, past_traj, fut_traj
|
| 148 |
+
|
| 149 |
+
# ------------------------------------------------------------------
|
| 150 |
+
# Pretraining loss (LED's noise_estimation_loss, unchanged)
|
| 151 |
+
# ------------------------------------------------------------------
|
| 152 |
+
def noise_estimation_loss(self, x, y_0, mask):
|
| 153 |
+
batch_size = x.shape[0]
|
| 154 |
+
t = torch.randint(0, self.n_steps, size=(batch_size // 2 + 1,)).to(x.device)
|
| 155 |
+
t = torch.cat([t, self.n_steps - t - 1], dim=0)[:batch_size]
|
| 156 |
+
a = self.extract(self.alphas_bar_sqrt, t, y_0)
|
| 157 |
+
beta = self.extract(self.betas, t, y_0)
|
| 158 |
+
am1 = self.extract(self.one_minus_alphas_bar_sqrt, t, y_0)
|
| 159 |
+
e = torch.randn_like(y_0)
|
| 160 |
+
y = y_0 * a + e * am1
|
| 161 |
+
out = self.model(y, beta, x, mask)
|
| 162 |
+
return (e - out).square().mean()
|
| 163 |
+
|
| 164 |
+
# ------------------------------------------------------------------
|
| 165 |
+
# Training loop
|
| 166 |
+
# ------------------------------------------------------------------
|
| 167 |
+
def fit(self):
|
| 168 |
+
best_val = float('inf')
|
| 169 |
+
for epoch in range(self.pre_epochs):
|
| 170 |
+
self.model.train()
|
| 171 |
+
loss_sum, n_batches = 0.0, 0
|
| 172 |
+
for data in self.train_loader:
|
| 173 |
+
_, mask, past, fut = self.data_preprocess(data)
|
| 174 |
+
loss = self.noise_estimation_loss(past, fut, mask)
|
| 175 |
+
self.opt.zero_grad()
|
| 176 |
+
loss.backward()
|
| 177 |
+
torch.nn.utils.clip_grad_norm_(self.model.parameters(), 1.0)
|
| 178 |
+
self.opt.step()
|
| 179 |
+
loss_sum += float(loss.item())
|
| 180 |
+
n_batches += 1
|
| 181 |
+
self.tb.add_scalar('pretrain_step/loss', loss.item(), self.global_step)
|
| 182 |
+
self.global_step += 1
|
| 183 |
+
|
| 184 |
+
train_loss = loss_sum / max(1, n_batches)
|
| 185 |
+
print_log(f'[{time.strftime("%Y-%m-%d %H:%M:%S")}] Pretrain Epoch {epoch} train_mse={train_loss:.6f}', self.log)
|
| 186 |
+
self.tb.add_scalar('pretrain_epoch/train_loss', train_loss, epoch)
|
| 187 |
+
self.tb.add_scalar('pretrain_epoch/lr', self.opt.param_groups[0]['lr'], epoch)
|
| 188 |
+
|
| 189 |
+
# Quick val MSE to monitor
|
| 190 |
+
self.model.eval()
|
| 191 |
+
val_sum, val_n = 0.0, 0
|
| 192 |
+
with torch.no_grad():
|
| 193 |
+
for data in self.val_loader:
|
| 194 |
+
_, mask, past, fut = self.data_preprocess(data)
|
| 195 |
+
val_sum += float(self.noise_estimation_loss(past, fut, mask).item())
|
| 196 |
+
val_n += 1
|
| 197 |
+
val_loss = val_sum / max(1, val_n)
|
| 198 |
+
print_log(f' val_mse={val_loss:.6f}', self.log)
|
| 199 |
+
self.tb.add_scalar('pretrain_epoch/val_loss', val_loss, epoch)
|
| 200 |
+
|
| 201 |
+
if val_loss < best_val:
|
| 202 |
+
best_val = val_loss
|
| 203 |
+
os.makedirs(os.path.dirname(self.ckpt_path), exist_ok=True)
|
| 204 |
+
torch.save({'model_dict': self.model.state_dict(), 'epoch': epoch}, self.ckpt_path)
|
| 205 |
+
print_log(f' -> saved {self.ckpt_path} (best val {best_val:.6f})', self.log)
|
| 206 |
+
|
| 207 |
+
self.scheduler.step()
|
| 208 |
+
|
| 209 |
+
self.tb.flush()
|
| 210 |
+
self.tb.close()
|
LED/utils/config.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import yaml
|
| 2 |
+
import os
|
| 3 |
+
import os.path as osp
|
| 4 |
+
import glob
|
| 5 |
+
import numpy as np
|
| 6 |
+
from easydict import EasyDict
|
| 7 |
+
from utils.utils import recreate_dirs
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class Config:
|
| 11 |
+
|
| 12 |
+
def __init__(self, cfg_id, info):
|
| 13 |
+
self.id = cfg_id
|
| 14 |
+
cfg_path = 'cfg/**/%s.yml' % cfg_id
|
| 15 |
+
files = glob.glob(cfg_path, recursive=True)
|
| 16 |
+
assert (len(files) == 1), 'YAML file [{}] does not exist!'.format(cfg_id)
|
| 17 |
+
self.yml_dict = EasyDict(yaml.safe_load(open(files[0], 'r')))
|
| 18 |
+
|
| 19 |
+
self.results_root_dir = os.path.expanduser(self.yml_dict['results_root_dir'])
|
| 20 |
+
# results dirs
|
| 21 |
+
|
| 22 |
+
self.cfg_dir = '%s/%s/%s' % (self.results_root_dir, cfg_id, info)
|
| 23 |
+
self.model_dir = '%s/models' % self.cfg_dir
|
| 24 |
+
self.log_dir = '%s/log' % self.cfg_dir
|
| 25 |
+
self.model_path = os.path.join(self.model_dir, 'model_%04d.p')
|
| 26 |
+
os.makedirs(self.model_dir, exist_ok=True)
|
| 27 |
+
os.makedirs(self.log_dir, exist_ok=True)
|
| 28 |
+
|
| 29 |
+
def get_last_epoch(self):
|
| 30 |
+
model_files = glob.glob(os.path.join(self.model_dir, 'model_*.p'))
|
| 31 |
+
if len(model_files) == 0:
|
| 32 |
+
return None
|
| 33 |
+
else:
|
| 34 |
+
model_file = osp.basename(model_files[0])
|
| 35 |
+
epoch = int(osp.splitext(model_file)[0].split('model_')[-1])
|
| 36 |
+
return epoch
|
| 37 |
+
|
| 38 |
+
def __getattribute__(self, name):
|
| 39 |
+
yml_dict = super().__getattribute__('yml_dict')
|
| 40 |
+
if name in yml_dict:
|
| 41 |
+
return yml_dict[name]
|
| 42 |
+
else:
|
| 43 |
+
return super().__getattribute__(name)
|
| 44 |
+
|
| 45 |
+
def __setattr__(self, name, value):
|
| 46 |
+
try:
|
| 47 |
+
yml_dict = super().__getattribute__('yml_dict')
|
| 48 |
+
except AttributeError:
|
| 49 |
+
return super().__setattr__(name, value)
|
| 50 |
+
if name in yml_dict:
|
| 51 |
+
yml_dict[name] = value
|
| 52 |
+
else:
|
| 53 |
+
return super().__setattr__(name, value)
|
| 54 |
+
|
| 55 |
+
def get(self, name, default=None):
|
| 56 |
+
if hasattr(self, name):
|
| 57 |
+
return getattr(self, name)
|
| 58 |
+
else:
|
| 59 |
+
return default
|
| 60 |
+
|
LED/utils/utils.py
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Code borrowed from Xinshuo_PyToolbox: https://github.com/xinshuoweng/Xinshuo_PyToolbox
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import os
|
| 6 |
+
import shutil
|
| 7 |
+
import torch
|
| 8 |
+
import numpy as np
|
| 9 |
+
import random
|
| 10 |
+
import time
|
| 11 |
+
import copy
|
| 12 |
+
import glob, glob2
|
| 13 |
+
from torch import nn
|
| 14 |
+
|
| 15 |
+
class AverageMeter(object):
|
| 16 |
+
"""Computes and stores the average and current value"""
|
| 17 |
+
def __init__(self):
|
| 18 |
+
self.reset()
|
| 19 |
+
|
| 20 |
+
def reset(self):
|
| 21 |
+
self.val = 0
|
| 22 |
+
self.avg = 0
|
| 23 |
+
self.sum = 0
|
| 24 |
+
self.count = 0
|
| 25 |
+
self.list = list()
|
| 26 |
+
|
| 27 |
+
def update(self, val, n=1):
|
| 28 |
+
self.val = val
|
| 29 |
+
self.sum += val * n
|
| 30 |
+
self.count += n
|
| 31 |
+
self.avg = self.sum / self.count
|
| 32 |
+
self.list.append(val)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def isnparray(nparray_test):
|
| 36 |
+
return isinstance(nparray_test, np.ndarray)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def isinteger(integer_test):
|
| 40 |
+
if isnparray(integer_test): return False
|
| 41 |
+
try: return isinstance(integer_test, int) or int(integer_test) == integer_test
|
| 42 |
+
except ValueError: return False
|
| 43 |
+
except TypeError: return False
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def isfloat(float_test):
|
| 47 |
+
return isinstance(float_test, float)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def isscalar(scalar_test):
|
| 51 |
+
try: return isinteger(scalar_test) or isfloat(scalar_test)
|
| 52 |
+
except TypeError: return False
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def islogical(logical_test):
|
| 56 |
+
return isinstance(logical_test, bool)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def isstring(string_test):
|
| 60 |
+
return isinstance(string_test, str)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def islist(list_test):
|
| 64 |
+
return isinstance(list_test, list)
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def convert_secs2time(seconds):
|
| 68 |
+
'''
|
| 69 |
+
format second to human readable way
|
| 70 |
+
'''
|
| 71 |
+
assert isscalar(seconds), 'input should be a scalar to represent number of seconds'
|
| 72 |
+
m, s = divmod(int(seconds), 60)
|
| 73 |
+
h, m = divmod(m, 60)
|
| 74 |
+
return '[%d:%02d:%02d]' % (h, m, s)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def get_timestring():
|
| 78 |
+
return time.strftime('%Y%m%d_%Hh%Mm%Ss')
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def recreate_dirs(*dirs):
|
| 82 |
+
for d in dirs:
|
| 83 |
+
if os.path.exists(d):
|
| 84 |
+
shutil.rmtree(d)
|
| 85 |
+
os.makedirs(d)
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def is_path_valid(pathname):
|
| 89 |
+
try:
|
| 90 |
+
if not isstring(pathname) or not pathname: return False
|
| 91 |
+
except TypeError: return False
|
| 92 |
+
else: return True
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def is_path_creatable(pathname):
|
| 96 |
+
'''
|
| 97 |
+
if any previous level of parent folder exists, returns true
|
| 98 |
+
'''
|
| 99 |
+
if not is_path_valid(pathname): return False
|
| 100 |
+
pathname = os.path.normpath(pathname)
|
| 101 |
+
pathname = os.path.dirname(os.path.abspath(pathname))
|
| 102 |
+
|
| 103 |
+
# recursively to find the previous level of parent folder existing
|
| 104 |
+
while not is_path_exists(pathname):
|
| 105 |
+
pathname_new = os.path.dirname(os.path.abspath(pathname))
|
| 106 |
+
if pathname_new == pathname: return False
|
| 107 |
+
pathname = pathname_new
|
| 108 |
+
return os.access(pathname, os.W_OK)
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def is_path_exists(pathname):
|
| 112 |
+
try: return is_path_valid(pathname) and os.path.exists(pathname)
|
| 113 |
+
except OSError: return False
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def is_path_exists_or_creatable(pathname):
|
| 117 |
+
try: return is_path_exists(pathname) or is_path_creatable(pathname)
|
| 118 |
+
except OSError: return False
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def isfile(pathname):
|
| 122 |
+
if is_path_valid(pathname):
|
| 123 |
+
pathname = os.path.normpath(pathname)
|
| 124 |
+
name = os.path.splitext(os.path.basename(pathname))[0]
|
| 125 |
+
ext = os.path.splitext(pathname)[1]
|
| 126 |
+
return len(name) > 0 and len(ext) > 0
|
| 127 |
+
else: return False
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def isfolder(pathname):
|
| 131 |
+
'''
|
| 132 |
+
if '.' exists in the subfolder, the function still justifies it as a folder. e.g., /mnt/dome/adhoc_0.5x/abc is a folder
|
| 133 |
+
if '.' exists after all slashes, the function will not justify is as a folder. e.g., /mnt/dome/adhoc_0.5x is NOT a folder
|
| 134 |
+
'''
|
| 135 |
+
if is_path_valid(pathname):
|
| 136 |
+
pathname = os.path.normpath(pathname)
|
| 137 |
+
if pathname == './': return True
|
| 138 |
+
name = os.path.splitext(os.path.basename(pathname))[0]
|
| 139 |
+
ext = os.path.splitext(pathname)[1]
|
| 140 |
+
return len(name) > 0 and len(ext) == 0
|
| 141 |
+
else: return False
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def mkdir_if_missing(input_path):
|
| 145 |
+
folder = input_path if isfolder(input_path) else os.path.dirname(input_path)
|
| 146 |
+
os.makedirs(folder, exist_ok=True)
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
def safe_list(input_data, warning=True, debug=True):
|
| 150 |
+
'''
|
| 151 |
+
copy a list to the buffer for use
|
| 152 |
+
parameters:
|
| 153 |
+
input_data: a list
|
| 154 |
+
outputs:
|
| 155 |
+
safe_data: a copy of input data
|
| 156 |
+
'''
|
| 157 |
+
if debug: assert islist(input_data), 'the input data is not a list'
|
| 158 |
+
safe_data = copy.copy(input_data)
|
| 159 |
+
return safe_data
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
def safe_path(input_path, warning=True, debug=True):
|
| 163 |
+
'''
|
| 164 |
+
convert path to a valid OS format, e.g., empty string '' to '.', remove redundant '/' at the end from 'aa/' to 'aa'
|
| 165 |
+
parameters:
|
| 166 |
+
input_path: a string
|
| 167 |
+
outputs:
|
| 168 |
+
safe_data: a valid path in OS format
|
| 169 |
+
'''
|
| 170 |
+
if debug: assert isstring(input_path), 'path is not a string: %s' % input_path
|
| 171 |
+
safe_data = copy.copy(input_path)
|
| 172 |
+
safe_data = os.path.normpath(safe_data)
|
| 173 |
+
return safe_data
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def prepare_seed(rand_seed):
|
| 177 |
+
np.random.seed(rand_seed)
|
| 178 |
+
random.seed(rand_seed)
|
| 179 |
+
torch.manual_seed(rand_seed)
|
| 180 |
+
torch.cuda.manual_seed_all(rand_seed)
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
def initialize_weights(modules):
|
| 184 |
+
for m in modules:
|
| 185 |
+
if isinstance(m, nn.Conv2d):
|
| 186 |
+
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
|
| 187 |
+
if m.bias is not None: nn.init.constant_(m.bias, 0)
|
| 188 |
+
elif isinstance(m, nn.BatchNorm2d):
|
| 189 |
+
nn.init.constant_(m.weight, 1)
|
| 190 |
+
if m.bias is not None: nn.init.constant_(m.bias, 0)
|
| 191 |
+
elif isinstance(m, nn.Linear):
|
| 192 |
+
nn.init.normal_(m.weight, 0, 0.01)
|
| 193 |
+
if m.bias is not None: nn.init.constant_(m.bias, 0)
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
def print_log(print_str, log, same_line=False, display=True):
|
| 197 |
+
'''
|
| 198 |
+
print a string to a log file
|
| 199 |
+
|
| 200 |
+
parameters:
|
| 201 |
+
print_str: a string to print
|
| 202 |
+
log: a opened file to save the log
|
| 203 |
+
same_line: True if we want to print the string without a new next line
|
| 204 |
+
display: False if we want to disable to print the string onto the terminal
|
| 205 |
+
'''
|
| 206 |
+
if display:
|
| 207 |
+
if same_line: print('{}'.format(print_str), end='')
|
| 208 |
+
else: print('{}'.format(print_str))
|
| 209 |
+
|
| 210 |
+
if same_line: log.write('{}'.format(print_str))
|
| 211 |
+
else: log.write('{}\n'.format(print_str))
|
| 212 |
+
log.flush()
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
def find_unique_common_from_lists(input_list1, input_list2, warning=True, debug=True):
|
| 216 |
+
'''
|
| 217 |
+
find common items from 2 lists, the returned elements are unique. repetitive items will be ignored
|
| 218 |
+
if the common items in two elements are not in the same order, the outputs follows the order in the first list
|
| 219 |
+
|
| 220 |
+
parameters:
|
| 221 |
+
input_list1, input_list2: two input lists
|
| 222 |
+
|
| 223 |
+
outputs:
|
| 224 |
+
list_common: a list of elements existing both in list_src1 and list_src2
|
| 225 |
+
index_list1: a list of index that list 1 has common items
|
| 226 |
+
index_list2: a list of index that list 2 has common items
|
| 227 |
+
'''
|
| 228 |
+
input_list1 = safe_list(input_list1, warning=warning, debug=debug)
|
| 229 |
+
input_list2 = safe_list(input_list2, warning=warning, debug=debug)
|
| 230 |
+
|
| 231 |
+
common_list = list(set(input_list1).intersection(input_list2))
|
| 232 |
+
|
| 233 |
+
# find index
|
| 234 |
+
index_list1 = []
|
| 235 |
+
for index in range(len(input_list1)):
|
| 236 |
+
item = input_list1[index]
|
| 237 |
+
if item in common_list:
|
| 238 |
+
index_list1.append(index)
|
| 239 |
+
|
| 240 |
+
index_list2 = []
|
| 241 |
+
for index in range(len(input_list2)):
|
| 242 |
+
item = input_list2[index]
|
| 243 |
+
if item in common_list:
|
| 244 |
+
index_list2.append(index)
|
| 245 |
+
|
| 246 |
+
return common_list, index_list1, index_list2
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
def load_txt_file(file_path, debug=True):
|
| 250 |
+
'''
|
| 251 |
+
load data or string from text file
|
| 252 |
+
'''
|
| 253 |
+
file_path = safe_path(file_path)
|
| 254 |
+
if debug: assert is_path_exists(file_path), 'text file is not existing at path: %s!' % file_path
|
| 255 |
+
with open(file_path, 'r') as file: data = file.read().splitlines()
|
| 256 |
+
num_lines = len(data)
|
| 257 |
+
file.close()
|
| 258 |
+
return data, num_lines
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
def load_list_from_folder(folder_path, ext_filter=None, depth=1, recursive=False, sort=True, save_path=None, debug=True):
|
| 262 |
+
'''
|
| 263 |
+
load a list of files or folders from a system path
|
| 264 |
+
|
| 265 |
+
parameters:
|
| 266 |
+
folder_path: root to search
|
| 267 |
+
ext_filter: a string to represent the extension of files interested
|
| 268 |
+
depth: maximum depth of folder to search, when it's None, all levels of folders will be searched
|
| 269 |
+
recursive: False: only return current level
|
| 270 |
+
True: return all levels till to the input depth
|
| 271 |
+
|
| 272 |
+
outputs:
|
| 273 |
+
fulllist: a list of elements
|
| 274 |
+
num_elem: number of the elements
|
| 275 |
+
'''
|
| 276 |
+
folder_path = safe_path(folder_path)
|
| 277 |
+
if debug: assert isfolder(folder_path), 'input folder path is not correct: %s' % folder_path
|
| 278 |
+
if not is_path_exists(folder_path):
|
| 279 |
+
print('the input folder does not exist\n')
|
| 280 |
+
return [], 0
|
| 281 |
+
if debug:
|
| 282 |
+
assert islogical(recursive), 'recursive should be a logical variable: {}'.format(recursive)
|
| 283 |
+
assert depth is None or (isinteger(depth) and depth >= 1), 'input depth is not correct {}'.format(depth)
|
| 284 |
+
assert ext_filter is None or (islist(ext_filter) and all(isstring(ext_tmp) for ext_tmp in ext_filter)) or isstring(ext_filter), 'extension filter is not correct'
|
| 285 |
+
if isstring(ext_filter): ext_filter = [ext_filter] # convert to a list
|
| 286 |
+
# zxc
|
| 287 |
+
|
| 288 |
+
fulllist = list()
|
| 289 |
+
if depth is None: # find all files recursively
|
| 290 |
+
recursive = True
|
| 291 |
+
wildcard_prefix = '**'
|
| 292 |
+
if ext_filter is not None:
|
| 293 |
+
for ext_tmp in ext_filter:
|
| 294 |
+
# wildcard = os.path.join(wildcard_prefix, '*' + string2ext_filter(ext_tmp))
|
| 295 |
+
wildcard = os.path.join(wildcard_prefix, '*' + ext_tmp)
|
| 296 |
+
curlist = glob2.glob(os.path.join(folder_path, wildcard))
|
| 297 |
+
if sort: curlist = sorted(curlist)
|
| 298 |
+
fulllist += curlist
|
| 299 |
+
else:
|
| 300 |
+
wildcard = wildcard_prefix
|
| 301 |
+
curlist = glob2.glob(os.path.join(folder_path, wildcard))
|
| 302 |
+
if sort: curlist = sorted(curlist)
|
| 303 |
+
fulllist += curlist
|
| 304 |
+
else: # find files based on depth and recursive flag
|
| 305 |
+
wildcard_prefix = '*'
|
| 306 |
+
for index in range(depth-1): wildcard_prefix = os.path.join(wildcard_prefix, '*')
|
| 307 |
+
if ext_filter is not None:
|
| 308 |
+
for ext_tmp in ext_filter:
|
| 309 |
+
# wildcard = wildcard_prefix + string2ext_filter(ext_tmp)
|
| 310 |
+
wildcard = wildcard_prefix + ext_tmp
|
| 311 |
+
curlist = glob.glob(os.path.join(folder_path, wildcard))
|
| 312 |
+
if sort: curlist = sorted(curlist)
|
| 313 |
+
fulllist += curlist
|
| 314 |
+
# zxc
|
| 315 |
+
else:
|
| 316 |
+
wildcard = wildcard_prefix
|
| 317 |
+
curlist = glob.glob(os.path.join(folder_path, wildcard))
|
| 318 |
+
# print(curlist)
|
| 319 |
+
if sort: curlist = sorted(curlist)
|
| 320 |
+
fulllist += curlist
|
| 321 |
+
if recursive and depth > 1:
|
| 322 |
+
newlist, _ = load_list_from_folder(folder_path=folder_path, ext_filter=ext_filter, depth=depth-1, recursive=True)
|
| 323 |
+
fulllist += newlist
|
| 324 |
+
|
| 325 |
+
fulllist = [os.path.normpath(path_tmp) for path_tmp in fulllist]
|
| 326 |
+
num_elem = len(fulllist)
|
| 327 |
+
|
| 328 |
+
# save list to a path
|
| 329 |
+
if save_path is not None:
|
| 330 |
+
save_path = safe_path(save_path)
|
| 331 |
+
if debug: assert is_path_exists_or_creatable(save_path), 'the file cannot be created'
|
| 332 |
+
with open(save_path, 'w') as file:
|
| 333 |
+
for item in fulllist: file.write('%s\n' % item)
|
| 334 |
+
file.close()
|
| 335 |
+
|
| 336 |
+
return fulllist, num_elem
|
LED/viz_denoising_process.py
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Visualize the full LED denoising process with/without uncertainty.
|
| 3 |
+
|
| 4 |
+
Generates side-by-side plots showing:
|
| 5 |
+
- Left: Without uncertainty (graph only)
|
| 6 |
+
- Right: With uncertainty (graph + sigma coloring)
|
| 7 |
+
|
| 8 |
+
For each sample:
|
| 9 |
+
- Past trajectories (black lines)
|
| 10 |
+
- GT future (green dashed)
|
| 11 |
+
- Denoising steps τ=4,3,2,1,0 with progressively refined predictions
|
| 12 |
+
- For sigma version: trajectory color indicates uncertainty (red=high, blue=low)
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
import os
|
| 16 |
+
import sys
|
| 17 |
+
import torch
|
| 18 |
+
import random
|
| 19 |
+
import numpy as np
|
| 20 |
+
import matplotlib
|
| 21 |
+
matplotlib.use('Agg')
|
| 22 |
+
import matplotlib.pyplot as plt
|
| 23 |
+
import matplotlib.cm as cm
|
| 24 |
+
from matplotlib.colors import Normalize
|
| 25 |
+
|
| 26 |
+
sys.path.insert(0, os.path.dirname(__file__))
|
| 27 |
+
|
| 28 |
+
from utils.config import Config
|
| 29 |
+
from data.dataloader_nba import NBADataset, seq_collate
|
| 30 |
+
from torch.utils.data import DataLoader
|
| 31 |
+
from models.model_led_initializer import LEDInitializer as InitializationModel
|
| 32 |
+
from models.model_diffusion import TransformerDenoisingModel as CoreDenoisingModel
|
| 33 |
+
from models.future_interaction_graph_v6 import FutureInteractionGraphV6Wrapper
|
| 34 |
+
|
| 35 |
+
NUM_Tau = 5
|
| 36 |
+
TRAJ_SCALE = 94.0 / 28.0 # to convert back to feet
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def load_models(ckpt_path, use_sigma=True, use_v6_graph=True, edge_mode='relpos_only',
|
| 40 |
+
top_n=5, device='cuda'):
|
| 41 |
+
"""Load LED models from checkpoint."""
|
| 42 |
+
cfg = Config('led_augment', 'viz')
|
| 43 |
+
|
| 44 |
+
model = CoreDenoisingModel().to(device)
|
| 45 |
+
model_cp = torch.load(cfg.pretrained_core_denoising_model, map_location='cpu')
|
| 46 |
+
model.load_state_dict(model_cp['model_dict'])
|
| 47 |
+
model.eval()
|
| 48 |
+
|
| 49 |
+
model_init = InitializationModel(t_h=10, d_h=6, t_f=20, d_f=2, k_pred=20).to(device)
|
| 50 |
+
|
| 51 |
+
graph = FutureInteractionGraphV6Wrapper(
|
| 52 |
+
num_agents=11, future_steps=20, past_steps=10,
|
| 53 |
+
past_channels=6, node_dim=128, top_n=top_n,
|
| 54 |
+
num_denoise_steps=NUM_Tau, edge_mode=edge_mode,
|
| 55 |
+
).to(device)
|
| 56 |
+
|
| 57 |
+
ckpt = torch.load(ckpt_path, map_location='cpu')
|
| 58 |
+
model_init.load_state_dict(ckpt['model_initializer_dict'])
|
| 59 |
+
graph.load_state_dict(ckpt['interaction_graph_dict'])
|
| 60 |
+
model_init.eval()
|
| 61 |
+
graph.eval()
|
| 62 |
+
|
| 63 |
+
return cfg, model, model_init, graph
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def make_beta_schedule(n_timesteps=100, start=1e-5, end=1e-2):
|
| 67 |
+
return torch.linspace(start, end, n_timesteps).cuda()
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def denoise_with_intermediates(model, graph, past_traj, traj_mask, loc,
|
| 71 |
+
betas, alphas_prod, alphas_bar_sqrt,
|
| 72 |
+
one_minus_alphas_bar_sqrt, alphas,
|
| 73 |
+
use_sigma=False, sigma=None):
|
| 74 |
+
"""Run denoising and return intermediate predictions at each step."""
|
| 75 |
+
intermediates = [] # list of (y0_hat, sigma_val) at each step
|
| 76 |
+
|
| 77 |
+
cur_y = loc[:, :10]
|
| 78 |
+
for i in reversed(range(NUM_Tau)):
|
| 79 |
+
t = torch.tensor([i]).cuda()
|
| 80 |
+
eps_factor = ((1 - alphas[i]) / one_minus_alphas_bar_sqrt[i])
|
| 81 |
+
beta = betas[i].repeat(past_traj.shape[0]).unsqueeze(-1).unsqueeze(-1).unsqueeze(-1)
|
| 82 |
+
|
| 83 |
+
eps_theta = model.generate_accelerate(cur_y, beta.squeeze(-1).squeeze(-1), past_traj, traj_mask)
|
| 84 |
+
|
| 85 |
+
alpha_bar_sqrt_t = alphas_bar_sqrt[i]
|
| 86 |
+
one_minus_abs_t = one_minus_alphas_bar_sqrt[i]
|
| 87 |
+
y0_hat = (cur_y - one_minus_abs_t * eps_theta) / alpha_bar_sqrt_t
|
| 88 |
+
|
| 89 |
+
sigma_input = sigma if use_sigma else None
|
| 90 |
+
delta = graph(y0_hat, past_traj, i, sigma=sigma_input)
|
| 91 |
+
eps_theta = eps_theta + delta
|
| 92 |
+
|
| 93 |
+
# Store y0_hat after graph correction
|
| 94 |
+
y0_corrected = (cur_y - one_minus_abs_t * eps_theta) / alpha_bar_sqrt_t
|
| 95 |
+
intermediates.append({
|
| 96 |
+
'step': i,
|
| 97 |
+
'y0_hat': y0_corrected.detach().cpu(),
|
| 98 |
+
'sigma': sigma.detach().cpu() if sigma is not None else None,
|
| 99 |
+
})
|
| 100 |
+
|
| 101 |
+
mean = (1 / alphas[i].sqrt()) * (cur_y - eps_factor * eps_theta)
|
| 102 |
+
z = torch.randn_like(cur_y)
|
| 103 |
+
sigma_t = betas[i].sqrt()
|
| 104 |
+
cur_y = mean + sigma_t * z * 0.00001
|
| 105 |
+
|
| 106 |
+
# Second half (modes 10-19)
|
| 107 |
+
cur_y_ = loc[:, 10:]
|
| 108 |
+
for i in reversed(range(NUM_Tau)):
|
| 109 |
+
t = torch.tensor([i]).cuda()
|
| 110 |
+
eps_factor = ((1 - alphas[i]) / one_minus_alphas_bar_sqrt[i])
|
| 111 |
+
beta = betas[i].repeat(past_traj.shape[0]).unsqueeze(-1).unsqueeze(-1).unsqueeze(-1)
|
| 112 |
+
eps_theta = model.generate_accelerate(cur_y_, beta.squeeze(-1).squeeze(-1), past_traj, traj_mask)
|
| 113 |
+
alpha_bar_sqrt_t = alphas_bar_sqrt[i]
|
| 114 |
+
one_minus_abs_t = one_minus_alphas_bar_sqrt[i]
|
| 115 |
+
y0_hat = (cur_y_ - one_minus_abs_t * eps_theta) / alpha_bar_sqrt_t
|
| 116 |
+
sigma_input = sigma if use_sigma else None
|
| 117 |
+
delta = graph(y0_hat, past_traj, i, sigma=sigma_input)
|
| 118 |
+
eps_theta = eps_theta + delta
|
| 119 |
+
mean = (1 / alphas[i].sqrt()) * (cur_y_ - eps_factor * eps_theta)
|
| 120 |
+
z = torch.randn_like(cur_y_)
|
| 121 |
+
sigma_t = betas[i].sqrt()
|
| 122 |
+
cur_y_ = mean + sigma_t * z * 0.00001
|
| 123 |
+
|
| 124 |
+
final_pred = torch.cat((cur_y_, cur_y), dim=1)
|
| 125 |
+
return intermediates, final_pred
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def plot_single_sample(ax, past, gt_fut, intermediates, final_pred,
|
| 129 |
+
initial_pos, traj_mean, traj_scale,
|
| 130 |
+
sigma_vals=None, title='', show_uncertainty=False):
|
| 131 |
+
"""Plot one sample's denoising process on an axis."""
|
| 132 |
+
A = 11
|
| 133 |
+
K = 20
|
| 134 |
+
|
| 135 |
+
# Convert past to absolute court positions
|
| 136 |
+
past_abs = past.reshape(A, 10, 6)[:, :, :2] # abs_xy channels
|
| 137 |
+
past_abs = past_abs * traj_scale + traj_mean # back to court coords
|
| 138 |
+
|
| 139 |
+
# GT future
|
| 140 |
+
gt_abs = gt_fut.reshape(A, 20, 2) * traj_scale + initial_pos.reshape(A, 1, 2)
|
| 141 |
+
|
| 142 |
+
# Colors for agents
|
| 143 |
+
agent_colors = plt.cm.tab10(np.linspace(0, 1, A))
|
| 144 |
+
|
| 145 |
+
# Draw court
|
| 146 |
+
ax.set_xlim(-2, 30)
|
| 147 |
+
ax.set_ylim(-2, 17)
|
| 148 |
+
ax.set_aspect('equal')
|
| 149 |
+
ax.set_facecolor('#2d5016') # dark green court
|
| 150 |
+
|
| 151 |
+
# Draw past trajectories
|
| 152 |
+
for a in range(A):
|
| 153 |
+
ax.plot(past_abs[a, :, 0], past_abs[a, :, 1], '-',
|
| 154 |
+
color='white', alpha=0.5, linewidth=1)
|
| 155 |
+
ax.plot(past_abs[a, -1, 0], past_abs[a, -1, 1], 'o',
|
| 156 |
+
color='white', markersize=4)
|
| 157 |
+
|
| 158 |
+
# Draw GT future
|
| 159 |
+
for a in range(A):
|
| 160 |
+
ax.plot(gt_abs[a, :, 0], gt_abs[a, :, 1], '--',
|
| 161 |
+
color='lime', alpha=0.4, linewidth=1)
|
| 162 |
+
|
| 163 |
+
# Draw denoising steps (from noisy to clean)
|
| 164 |
+
step_alphas = [0.15, 0.25, 0.35, 0.5, 0.7]
|
| 165 |
+
for idx, inter in enumerate(intermediates):
|
| 166 |
+
y0 = inter['y0_hat'] # [B*A, K, T, 2]
|
| 167 |
+
step = inter['step']
|
| 168 |
+
alpha = step_alphas[min(idx, len(step_alphas) - 1)]
|
| 169 |
+
|
| 170 |
+
# Take best mode (mode 0 for simplicity)
|
| 171 |
+
y0_mode0 = y0[:A, 0, :, :] # [A, T, 2]
|
| 172 |
+
y0_abs = y0_mode0 * traj_scale + initial_pos.reshape(A, 1, 2)
|
| 173 |
+
|
| 174 |
+
if show_uncertainty and inter['sigma'] is not None:
|
| 175 |
+
sigma_a = inter['sigma'][:A, 0].numpy()
|
| 176 |
+
norm = Normalize(vmin=sigma_a.min(), vmax=sigma_a.max())
|
| 177 |
+
cmap = cm.coolwarm # blue=certain, red=uncertain
|
| 178 |
+
|
| 179 |
+
for a in range(A):
|
| 180 |
+
color = cmap(norm(sigma_a[a]))
|
| 181 |
+
ax.plot(y0_abs[a, :, 0], y0_abs[a, :, 1], '-',
|
| 182 |
+
color=color, alpha=alpha, linewidth=1.5)
|
| 183 |
+
else:
|
| 184 |
+
for a in range(A):
|
| 185 |
+
ax.plot(y0_abs[a, :, 0], y0_abs[a, :, 1], '-',
|
| 186 |
+
color=agent_colors[a], alpha=alpha, linewidth=1)
|
| 187 |
+
|
| 188 |
+
# Draw final prediction (best mode)
|
| 189 |
+
if final_pred is not None:
|
| 190 |
+
pred = final_pred[:A] # [A, K, T, 2]
|
| 191 |
+
pred_abs = pred.numpy() * traj_scale + initial_pos.reshape(A, 1, 1, 2)
|
| 192 |
+
gt_exp = np.expand_dims(gt_abs, 1).repeat(pred_abs.shape[1], axis=1)
|
| 193 |
+
ade_per_mode = np.linalg.norm(pred_abs - gt_exp, axis=-1).mean(axis=-1) # [A, K]
|
| 194 |
+
best_modes = ade_per_mode.argmin(axis=-1) # [A]
|
| 195 |
+
|
| 196 |
+
for a in range(A):
|
| 197 |
+
best = pred_abs[a, best_modes[a]]
|
| 198 |
+
ax.plot(best[:, 0], best[:, 1], '-',
|
| 199 |
+
color=agent_colors[a], alpha=0.9, linewidth=2)
|
| 200 |
+
ax.plot(best[-1, 0], best[-1, 1], '*',
|
| 201 |
+
color=agent_colors[a], markersize=6)
|
| 202 |
+
|
| 203 |
+
ax.set_title(title, fontsize=10, color='white')
|
| 204 |
+
ax.tick_params(colors='gray')
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
def main():
|
| 208 |
+
device = 'cuda:0' # CUDA_VISIBLE_DEVICES remaps to 0
|
| 209 |
+
torch.cuda.set_device(0)
|
| 210 |
+
|
| 211 |
+
# Paths
|
| 212 |
+
ckpt_sigma = '/mnt/jaewoo4tb/srtp/LED/results/led_augment/graph_v6_edge_relpos/models/model_0052.p'
|
| 213 |
+
ckpt_nosigma = '/mnt/jaewoo4tb/srtp/LED/results/led_augment/graph_v6_nosigma_n3/models/model_0084.p'
|
| 214 |
+
|
| 215 |
+
out_dir = '/mnt/jaewoo4tb/srtp/LED/visualizations'
|
| 216 |
+
os.makedirs(out_dir, exist_ok=True)
|
| 217 |
+
|
| 218 |
+
# Load models
|
| 219 |
+
cfg_s, model_s, init_s, graph_s = load_models(
|
| 220 |
+
ckpt_sigma, use_sigma=True, edge_mode='relpos_only', top_n=5, device=device)
|
| 221 |
+
cfg_n, model_n, init_n, graph_n = load_models(
|
| 222 |
+
ckpt_nosigma, use_sigma=False, edge_mode='full', top_n=3, device=device)
|
| 223 |
+
|
| 224 |
+
# Diffusion schedule
|
| 225 |
+
betas = torch.linspace(1e-5, 1e-2, 100).to(device)
|
| 226 |
+
alphas = 1 - betas
|
| 227 |
+
alphas_prod = torch.cumprod(alphas, 0)
|
| 228 |
+
alphas_bar_sqrt = torch.sqrt(alphas_prod)
|
| 229 |
+
one_minus_alphas_bar_sqrt = torch.sqrt(1 - alphas_prod)
|
| 230 |
+
|
| 231 |
+
traj_mean = torch.FloatTensor(cfg_s.traj_mean).to(device).unsqueeze(0).unsqueeze(0).unsqueeze(0)
|
| 232 |
+
traj_scale = cfg_s.traj_scale
|
| 233 |
+
|
| 234 |
+
# Load test data
|
| 235 |
+
test_dset = NBADataset(obs_len=10, pred_len=20, training=False)
|
| 236 |
+
test_loader = DataLoader(test_dset, batch_size=1, shuffle=False, collate_fn=seq_collate)
|
| 237 |
+
|
| 238 |
+
# Set seed for reproducibility
|
| 239 |
+
np.random.seed(42)
|
| 240 |
+
random.seed(42)
|
| 241 |
+
torch.manual_seed(42)
|
| 242 |
+
|
| 243 |
+
num_samples = 5
|
| 244 |
+
sample_indices = sorted(random.sample(range(len(test_dset)), num_samples))
|
| 245 |
+
|
| 246 |
+
with torch.no_grad():
|
| 247 |
+
for sample_idx, data in enumerate(test_loader):
|
| 248 |
+
if sample_idx not in sample_indices:
|
| 249 |
+
continue
|
| 250 |
+
if sample_idx > max(sample_indices):
|
| 251 |
+
break
|
| 252 |
+
|
| 253 |
+
batch_size = 1
|
| 254 |
+
traj_mask = torch.ones(11, 11).to(device)
|
| 255 |
+
initial_pos = data['pre_motion_3D'].to(device)[:, :, -1:]
|
| 256 |
+
past_traj_abs = ((data['pre_motion_3D'].to(device) - traj_mean) / traj_scale).view(-1, 10, 2)
|
| 257 |
+
past_traj_rel = ((data['pre_motion_3D'].to(device) - initial_pos) / traj_scale).view(-1, 10, 2)
|
| 258 |
+
past_traj_vel = torch.cat((past_traj_rel[:, 1:] - past_traj_rel[:, :-1],
|
| 259 |
+
torch.zeros_like(past_traj_rel[:, :1])), dim=1)
|
| 260 |
+
past_traj = torch.cat((past_traj_abs, past_traj_rel, past_traj_vel), dim=-1)
|
| 261 |
+
fut_traj = ((data['fut_motion_3D'].to(device) - initial_pos) / traj_scale).view(-1, 20, 2)
|
| 262 |
+
|
| 263 |
+
# --- With sigma ---
|
| 264 |
+
sample_pred_s, mean_s, var_s = init_s(past_traj, traj_mask)
|
| 265 |
+
sample_pred_s = (torch.exp(var_s / 2)[..., None, None]
|
| 266 |
+
* sample_pred_s / sample_pred_s.std(dim=1).mean(dim=(1, 2))[:, None, None, None])
|
| 267 |
+
loc_s = sample_pred_s + mean_s[:, None]
|
| 268 |
+
|
| 269 |
+
intermediates_s, final_s = denoise_with_intermediates(
|
| 270 |
+
model_s, graph_s, past_traj, traj_mask, loc_s,
|
| 271 |
+
betas, alphas_prod, alphas_bar_sqrt, one_minus_alphas_bar_sqrt, alphas,
|
| 272 |
+
use_sigma=True, sigma=var_s)
|
| 273 |
+
|
| 274 |
+
# --- Without sigma ---
|
| 275 |
+
sample_pred_n, mean_n, var_n = init_n(past_traj, traj_mask)
|
| 276 |
+
sample_pred_n = (torch.exp(var_n / 2)[..., None, None]
|
| 277 |
+
* sample_pred_n / sample_pred_n.std(dim=1).mean(dim=(1, 2))[:, None, None, None])
|
| 278 |
+
loc_n = sample_pred_n + mean_n[:, None]
|
| 279 |
+
|
| 280 |
+
intermediates_n, final_n = denoise_with_intermediates(
|
| 281 |
+
model_n, graph_n, past_traj, traj_mask, loc_n,
|
| 282 |
+
betas, alphas_prod, alphas_bar_sqrt, one_minus_alphas_bar_sqrt, alphas,
|
| 283 |
+
use_sigma=False, sigma=None)
|
| 284 |
+
|
| 285 |
+
# --- Plot ---
|
| 286 |
+
fig, axes = plt.subplots(1, 2, figsize=(20, 8))
|
| 287 |
+
fig.patch.set_facecolor('#1a1a1a')
|
| 288 |
+
|
| 289 |
+
init_pos_cpu = initial_pos.cpu().squeeze(0)
|
| 290 |
+
traj_mean_cpu = traj_mean.cpu().squeeze(0).squeeze(0)
|
| 291 |
+
|
| 292 |
+
plot_single_sample(
|
| 293 |
+
axes[0], past_traj.cpu().numpy(), fut_traj.cpu().numpy(),
|
| 294 |
+
intermediates_n, final_n.cpu(),
|
| 295 |
+
init_pos_cpu.numpy(), traj_mean_cpu.numpy(), traj_scale,
|
| 296 |
+
title=f'Without Uncertainty (sample {sample_idx})')
|
| 297 |
+
|
| 298 |
+
plot_single_sample(
|
| 299 |
+
axes[1], past_traj.cpu().numpy(), fut_traj.cpu().numpy(),
|
| 300 |
+
intermediates_s, final_s.cpu(),
|
| 301 |
+
init_pos_cpu.numpy(), traj_mean_cpu.numpy(), traj_scale,
|
| 302 |
+
sigma_vals=var_s.cpu(), show_uncertainty=True,
|
| 303 |
+
title=f'With Uncertainty (sample {sample_idx})')
|
| 304 |
+
|
| 305 |
+
# Add colorbar for uncertainty
|
| 306 |
+
sm = plt.cm.ScalarMappable(cmap=cm.coolwarm)
|
| 307 |
+
sm.set_array([])
|
| 308 |
+
cbar = fig.colorbar(sm, ax=axes[1], shrink=0.6, pad=0.02)
|
| 309 |
+
cbar.set_label('Uncertainty (σ)', color='white')
|
| 310 |
+
cbar.ax.yaxis.set_tick_params(color='white')
|
| 311 |
+
plt.setp(plt.getp(cbar.ax.axes, 'yticklabels'), color='white')
|
| 312 |
+
|
| 313 |
+
plt.suptitle(f'LED Denoising Process — Sample {sample_idx}\n'
|
| 314 |
+
f'Fading lines: τ=4→0 (noisy→clean). '
|
| 315 |
+
f'Green dashed: GT. Stars: final endpoints.',
|
| 316 |
+
color='white', fontsize=12)
|
| 317 |
+
|
| 318 |
+
plt.tight_layout()
|
| 319 |
+
save_path = os.path.join(out_dir, f'denoising_sample_{sample_idx:04d}.png')
|
| 320 |
+
plt.savefig(save_path, dpi=150, bbox_inches='tight',
|
| 321 |
+
facecolor=fig.get_facecolor())
|
| 322 |
+
plt.close()
|
| 323 |
+
print(f'Saved: {save_path}')
|
| 324 |
+
|
| 325 |
+
print(f'\nAll visualizations saved to {out_dir}/')
|
| 326 |
+
|
| 327 |
+
|
| 328 |
+
if __name__ == '__main__':
|
| 329 |
+
main()
|
LED/viz_denoising_steps.py
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Visualize LED denoising: initializer → refinement → final, on basketball court.
|
| 3 |
+
|
| 4 |
+
LED's denoising is a 2-stage process:
|
| 5 |
+
1. Initializer: produces 20 diverse trajectory modes (noisy, multimodal)
|
| 6 |
+
2. Leapfrog refinement: 5 DDPM steps that clean up each mode (subtle changes)
|
| 7 |
+
|
| 8 |
+
We visualize 5 stages:
|
| 9 |
+
Step 0: Raw initializer output (mean_estimation only, no variance)
|
| 10 |
+
Step 1: Initializer + variance scaling (diverse modes)
|
| 11 |
+
Step 2: After τ=4,3 refinement (2 DDPM steps)
|
| 12 |
+
Step 3: After τ=2,1 refinement (4 DDPM steps)
|
| 13 |
+
Step 4: Final prediction τ=0 (5 DDPM steps, fully denoised)
|
| 14 |
+
|
| 15 |
+
For sigma version: trajectory color = uncertainty (green=certain, red=uncertain)
|
| 16 |
+
For nosigma version: trajectory color = team (blue=home, orange=away, green=ball)
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
import os, sys, random
|
| 20 |
+
import numpy as np
|
| 21 |
+
import torch
|
| 22 |
+
import matplotlib
|
| 23 |
+
matplotlib.use('Agg')
|
| 24 |
+
import matplotlib.pyplot as plt
|
| 25 |
+
import matplotlib.cm as cm
|
| 26 |
+
from matplotlib.colors import Normalize
|
| 27 |
+
|
| 28 |
+
sys.path.insert(0, os.path.dirname(__file__))
|
| 29 |
+
|
| 30 |
+
from utils.config import Config
|
| 31 |
+
from data.dataloader_nba import NBADataset, seq_collate
|
| 32 |
+
from torch.utils.data import DataLoader
|
| 33 |
+
from models.model_led_initializer import LEDInitializer as InitializationModel
|
| 34 |
+
from models.model_diffusion import TransformerDenoisingModel as CoreDenoisingModel
|
| 35 |
+
from models.future_interaction_graph_v6 import FutureInteractionGraphV6Wrapper
|
| 36 |
+
|
| 37 |
+
NUM_Tau = 5
|
| 38 |
+
COURT_IMG = '/mnt/jaewoo4tb/srtp/srtp/raw_data/nba/court.png'
|
| 39 |
+
COURT_W, COURT_H = 28.0, 15.0
|
| 40 |
+
A = 11
|
| 41 |
+
|
| 42 |
+
HOME_PAST, HOME_FUT = '#a8d5ff', '#187bff'
|
| 43 |
+
AWAY_PAST, AWAY_FUT = '#ffc9a8', '#ff5b2e'
|
| 44 |
+
BALL_PAST, BALL_FUT = '#b9f2b9', '#1f9d1f'
|
| 45 |
+
GT_COLOR = '#333333'
|
| 46 |
+
|
| 47 |
+
def agent_colors(idx):
|
| 48 |
+
if idx < 5: return HOME_PAST, HOME_FUT
|
| 49 |
+
elif idx < 10: return AWAY_PAST, AWAY_FUT
|
| 50 |
+
else: return BALL_PAST, BALL_FUT
|
| 51 |
+
|
| 52 |
+
_court_cache = None
|
| 53 |
+
def court_img():
|
| 54 |
+
global _court_cache
|
| 55 |
+
if _court_cache is None:
|
| 56 |
+
_court_cache = plt.imread(COURT_IMG)
|
| 57 |
+
return _court_cache
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def load_models(ckpt_path, use_sigma, edge_mode, top_n, device):
|
| 61 |
+
cfg = Config('led_augment', 'viz')
|
| 62 |
+
model = CoreDenoisingModel().to(device)
|
| 63 |
+
cp = torch.load(cfg.pretrained_core_denoising_model, map_location='cpu', weights_only=False)
|
| 64 |
+
model.load_state_dict(cp['model_dict']); model.eval()
|
| 65 |
+
|
| 66 |
+
model_init = InitializationModel(t_h=10, d_h=6, t_f=20, d_f=2, k_pred=20).to(device)
|
| 67 |
+
graph = FutureInteractionGraphV6Wrapper(
|
| 68 |
+
num_agents=11, future_steps=20, past_steps=10,
|
| 69 |
+
past_channels=6, node_dim=128, top_n=top_n,
|
| 70 |
+
num_denoise_steps=NUM_Tau, edge_mode=edge_mode).to(device)
|
| 71 |
+
|
| 72 |
+
ckpt = torch.load(ckpt_path, map_location='cpu', weights_only=False)
|
| 73 |
+
model_init.load_state_dict(ckpt['model_initializer_dict'])
|
| 74 |
+
graph.load_state_dict(ckpt['interaction_graph_dict'])
|
| 75 |
+
model_init.eval(); graph.eval()
|
| 76 |
+
return cfg, model, model_init, graph
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def get_all_stages(model, graph, model_init, past_traj, traj_mask,
|
| 80 |
+
betas, alphas, abs_sqrt, oma_sqrt,
|
| 81 |
+
use_sigma, traj_scale, init_pos_np):
|
| 82 |
+
"""Return trajectory predictions at each meaningful stage."""
|
| 83 |
+
sample_pred, mean_est, var_est = model_init(past_traj, traj_mask)
|
| 84 |
+
|
| 85 |
+
stages = []
|
| 86 |
+
|
| 87 |
+
# Stage 0: Mean estimation only (single mode per agent)
|
| 88 |
+
mean_abs = mean_est.detach().cpu().numpy() # [B*A, T, 2]
|
| 89 |
+
mean_abs = mean_abs * traj_scale + init_pos_np.reshape(-1, 1, 2)
|
| 90 |
+
stages.append({
|
| 91 |
+
'trajs': mean_abs[:A, np.newaxis], # [A, 1, T, 2]
|
| 92 |
+
'sigma': None,
|
| 93 |
+
'title': 'Stage 1: Mean estimation',
|
| 94 |
+
})
|
| 95 |
+
|
| 96 |
+
# Stage 1: Initializer with variance scaling (20 diverse modes)
|
| 97 |
+
sample_pred_scaled = (torch.exp(var_est / 2)[..., None, None]
|
| 98 |
+
* sample_pred
|
| 99 |
+
/ sample_pred.std(dim=1).mean(dim=(1, 2))[:, None, None, None])
|
| 100 |
+
loc = sample_pred_scaled + mean_est[:, None]
|
| 101 |
+
|
| 102 |
+
loc_abs = loc.detach().cpu().numpy() * traj_scale + init_pos_np.reshape(-1, 1, 1, 2)
|
| 103 |
+
sigma_np = var_est.detach().cpu().numpy() if use_sigma else None
|
| 104 |
+
stages.append({
|
| 105 |
+
'trajs': loc_abs[:A], # [A, K=20, T, 2]
|
| 106 |
+
'sigma': sigma_np,
|
| 107 |
+
'title': 'Stage 2: Initializer (20 modes)',
|
| 108 |
+
})
|
| 109 |
+
|
| 110 |
+
# Run denoising and capture intermediate states
|
| 111 |
+
sigma_input = var_est if use_sigma else None
|
| 112 |
+
cur_y = loc[:, :10]
|
| 113 |
+
|
| 114 |
+
checkpoints = {3: 'Stage 3: After 2 DDPM steps',
|
| 115 |
+
1: 'Stage 4: After 4 DDPM steps',
|
| 116 |
+
-1: 'Stage 5: Final (5 DDPM steps)'}
|
| 117 |
+
|
| 118 |
+
for i in reversed(range(NUM_Tau)):
|
| 119 |
+
ef = (1 - alphas[i]) / oma_sqrt[i]
|
| 120 |
+
beta = betas[i].repeat(past_traj.shape[0]).unsqueeze(-1).unsqueeze(-1).unsqueeze(-1)
|
| 121 |
+
eps = model.generate_accelerate(cur_y, beta.squeeze(-1).squeeze(-1), past_traj, traj_mask)
|
| 122 |
+
y0h = (cur_y - oma_sqrt[i] * eps) / abs_sqrt[i]
|
| 123 |
+
delta = graph(y0h, past_traj, i, sigma=sigma_input)
|
| 124 |
+
eps = eps + delta
|
| 125 |
+
mean = (1 / alphas[i].sqrt()) * (cur_y - ef * eps)
|
| 126 |
+
z = torch.randn_like(cur_y)
|
| 127 |
+
cur_y = mean + betas[i].sqrt() * z * 0.00001
|
| 128 |
+
|
| 129 |
+
if i in checkpoints:
|
| 130 |
+
# Get y0 estimate (clean prediction) at this point
|
| 131 |
+
y0_est = (cur_y - oma_sqrt[max(0, i-1)] * eps) / abs_sqrt[max(0, i-1)] if i > 0 else cur_y
|
| 132 |
+
y0_abs = y0_est.detach().cpu().numpy() * traj_scale + init_pos_np.reshape(-1, 1, 1, 2)
|
| 133 |
+
stages.append({
|
| 134 |
+
'trajs': y0_abs[:A], # [A, K=10, T, 2]
|
| 135 |
+
'sigma': sigma_np,
|
| 136 |
+
'title': checkpoints[i],
|
| 137 |
+
})
|
| 138 |
+
|
| 139 |
+
# Stage 5 (final): use cur_y directly
|
| 140 |
+
# Also do the second half (modes 10-19)
|
| 141 |
+
cur_y2 = loc[:, 10:]
|
| 142 |
+
for i in reversed(range(NUM_Tau)):
|
| 143 |
+
ef = (1 - alphas[i]) / oma_sqrt[i]
|
| 144 |
+
beta = betas[i].repeat(past_traj.shape[0]).unsqueeze(-1).unsqueeze(-1).unsqueeze(-1)
|
| 145 |
+
eps = model.generate_accelerate(cur_y2, beta.squeeze(-1).squeeze(-1), past_traj, traj_mask)
|
| 146 |
+
y0h = (cur_y2 - oma_sqrt[i] * eps) / abs_sqrt[i]
|
| 147 |
+
delta = graph(y0h, past_traj, i, sigma=sigma_input)
|
| 148 |
+
eps = eps + delta
|
| 149 |
+
mean = (1 / alphas[i].sqrt()) * (cur_y2 - ef * eps)
|
| 150 |
+
z = torch.randn_like(cur_y2)
|
| 151 |
+
cur_y2 = mean + betas[i].sqrt() * z * 0.00001
|
| 152 |
+
|
| 153 |
+
final = torch.cat((cur_y2, cur_y), dim=1)
|
| 154 |
+
final_abs = final.detach().cpu().numpy() * traj_scale + init_pos_np.reshape(-1, 1, 1, 2)
|
| 155 |
+
stages[-1] = {
|
| 156 |
+
'trajs': final_abs[:A], # [A, K=20, T, 2]
|
| 157 |
+
'sigma': sigma_np,
|
| 158 |
+
'title': 'Stage 5: Final (all 20 modes)',
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
return stages
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
def draw_stage(ax, past_abs, gt_abs, trajs, sigma_vals, title, show_sigma):
|
| 165 |
+
"""Draw one stage on court."""
|
| 166 |
+
ax.imshow(court_img(), extent=[0, COURT_W, COURT_H, 0], zorder=0, alpha=0.5)
|
| 167 |
+
ax.set_xlim(0, COURT_W); ax.set_ylim(COURT_H, 0)
|
| 168 |
+
ax.axis('off')
|
| 169 |
+
ax.set_title(title, fontsize=9, pad=3)
|
| 170 |
+
|
| 171 |
+
# Past
|
| 172 |
+
for a in range(A):
|
| 173 |
+
pc, _ = agent_colors(a)
|
| 174 |
+
ax.plot(past_abs[a, :, 0], past_abs[a, :, 1], color=pc, lw=0.8,
|
| 175 |
+
marker='o', ms=1.5, alpha=0.6, zorder=2)
|
| 176 |
+
|
| 177 |
+
# GT
|
| 178 |
+
for a in range(A):
|
| 179 |
+
gt = np.concatenate([past_abs[a, -1:], gt_abs[a]], axis=0)
|
| 180 |
+
ax.plot(gt[:, 0], gt[:, 1], color=GT_COLOR, lw=0.7, marker='o',
|
| 181 |
+
ms=1.0, alpha=0.4, zorder=3, linestyle='--')
|
| 182 |
+
|
| 183 |
+
K = trajs.shape[1]
|
| 184 |
+
K_show = min(K, 10)
|
| 185 |
+
|
| 186 |
+
if show_sigma and sigma_vals is not None:
|
| 187 |
+
sigma_std = np.exp(sigma_vals[:A, 0] / 2)
|
| 188 |
+
norm = Normalize(vmin=sigma_std.min() - 0.01, vmax=sigma_std.max() + 0.01)
|
| 189 |
+
cmap = cm.RdYlGn_r
|
| 190 |
+
|
| 191 |
+
for a in range(A):
|
| 192 |
+
color = cmap(norm(sigma_std[a]))
|
| 193 |
+
for k in range(K_show):
|
| 194 |
+
pred = np.concatenate([past_abs[a, -1:], trajs[a, k]], axis=0)
|
| 195 |
+
ax.plot(pred[:, 0], pred[:, 1], color=color, lw=0.5, alpha=0.3, zorder=4)
|
| 196 |
+
# Best mode
|
| 197 |
+
dists = np.linalg.norm(trajs[a, :K_show] - gt_abs[a:a+1], axis=-1).mean(axis=-1)
|
| 198 |
+
best_k = dists.argmin()
|
| 199 |
+
best = np.concatenate([past_abs[a, -1:], trajs[a, best_k]], axis=0)
|
| 200 |
+
ax.plot(best[:, 0], best[:, 1], color=color, lw=2.0, alpha=0.9,
|
| 201 |
+
zorder=5, marker='o', ms=2.0)
|
| 202 |
+
else:
|
| 203 |
+
for a in range(A):
|
| 204 |
+
_, fc = agent_colors(a)
|
| 205 |
+
for k in range(K_show):
|
| 206 |
+
pred = np.concatenate([past_abs[a, -1:], trajs[a, k]], axis=0)
|
| 207 |
+
ax.plot(pred[:, 0], pred[:, 1], color=fc, lw=0.5, alpha=0.25, zorder=4)
|
| 208 |
+
dists = np.linalg.norm(trajs[a, :K_show] - gt_abs[a:a+1], axis=-1).mean(axis=-1)
|
| 209 |
+
best_k = dists.argmin()
|
| 210 |
+
best = np.concatenate([past_abs[a, -1:], trajs[a, best_k]], axis=0)
|
| 211 |
+
ax.plot(best[:, 0], best[:, 1], color=fc, lw=2.0, alpha=0.9,
|
| 212 |
+
zorder=5, marker='o', ms=2.0)
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
def main():
|
| 216 |
+
device = 'cuda:0'
|
| 217 |
+
|
| 218 |
+
ckpt_sigma = '/mnt/jaewoo4tb/srtp/LED/results/led_augment/graph_v6_edge_relpos/models/model_0052.p'
|
| 219 |
+
ckpt_nosigma = '/mnt/jaewoo4tb/srtp/LED/results/led_augment/graph_v6_nosigma_n3/models/model_0084.p'
|
| 220 |
+
|
| 221 |
+
out_dir = '/mnt/jaewoo4tb/srtp/LED/visualizations/denoising_steps'
|
| 222 |
+
os.makedirs(out_dir, exist_ok=True)
|
| 223 |
+
|
| 224 |
+
cfg, model_s, init_s, graph_s = load_models(
|
| 225 |
+
ckpt_sigma, True, 'relpos_only', 5, device)
|
| 226 |
+
_, model_n, init_n, graph_n = load_models(
|
| 227 |
+
ckpt_nosigma, False, 'full', 3, device)
|
| 228 |
+
|
| 229 |
+
betas = torch.linspace(1e-5, 1e-2, 100).to(device)
|
| 230 |
+
alphas = 1 - betas
|
| 231 |
+
alphas_prod = torch.cumprod(alphas, 0)
|
| 232 |
+
abs_sqrt = torch.sqrt(alphas_prod)
|
| 233 |
+
oma_sqrt = torch.sqrt(1 - alphas_prod)
|
| 234 |
+
|
| 235 |
+
traj_scale = cfg.traj_scale
|
| 236 |
+
|
| 237 |
+
test_dset = NBADataset(obs_len=10, pred_len=20, training=False)
|
| 238 |
+
test_loader = DataLoader(test_dset, batch_size=1, shuffle=False, collate_fn=seq_collate)
|
| 239 |
+
|
| 240 |
+
np.random.seed(42); random.seed(42); torch.manual_seed(42)
|
| 241 |
+
sample_indices = sorted(random.sample(range(len(test_dset)), 3))
|
| 242 |
+
|
| 243 |
+
with torch.no_grad():
|
| 244 |
+
for sample_idx, data in enumerate(test_loader):
|
| 245 |
+
if sample_idx not in sample_indices:
|
| 246 |
+
continue
|
| 247 |
+
if sample_idx > max(sample_indices):
|
| 248 |
+
break
|
| 249 |
+
|
| 250 |
+
traj_mean_t = torch.FloatTensor(cfg.traj_mean).cuda().unsqueeze(0).unsqueeze(0).unsqueeze(0)
|
| 251 |
+
initial_pos = data['pre_motion_3D'].cuda()[:, :, -1:]
|
| 252 |
+
past_abs_np = data['pre_motion_3D'].numpy().squeeze(0) / (94.0 / 28.0)
|
| 253 |
+
fut_abs_np = data['fut_motion_3D'].numpy().squeeze(0) / (94.0 / 28.0)
|
| 254 |
+
init_pos_np = initial_pos.cpu().numpy().squeeze(0) / (94.0 / 28.0)
|
| 255 |
+
|
| 256 |
+
past_traj_abs = ((data['pre_motion_3D'].cuda() - traj_mean_t) / traj_scale).view(-1, 10, 2)
|
| 257 |
+
past_traj_rel = ((data['pre_motion_3D'].cuda() - initial_pos) / traj_scale).view(-1, 10, 2)
|
| 258 |
+
past_traj_vel = torch.cat((past_traj_rel[:, 1:] - past_traj_rel[:, :-1],
|
| 259 |
+
torch.zeros_like(past_traj_rel[:, :1])), dim=1)
|
| 260 |
+
past_traj = torch.cat((past_traj_abs, past_traj_rel, past_traj_vel), dim=-1)
|
| 261 |
+
traj_mask = torch.ones(11, 11).cuda()
|
| 262 |
+
|
| 263 |
+
for version, model, init_model, graph, use_sigma, label in [
|
| 264 |
+
('nosigma', model_n, init_n, graph_n, False, 'Without Uncertainty'),
|
| 265 |
+
('sigma', model_s, init_s, graph_s, True, 'With Uncertainty'),
|
| 266 |
+
]:
|
| 267 |
+
stages = get_all_stages(
|
| 268 |
+
model, graph, init_model, past_traj, traj_mask,
|
| 269 |
+
betas, alphas, abs_sqrt, oma_sqrt,
|
| 270 |
+
use_sigma, traj_scale, init_pos_np)
|
| 271 |
+
|
| 272 |
+
for si, stage in enumerate(stages):
|
| 273 |
+
fig, ax = plt.subplots(1, 1, figsize=(7, 5), dpi=200)
|
| 274 |
+
title = f'{label} — {stage["title"]}'
|
| 275 |
+
draw_stage(ax, past_abs_np, fut_abs_np, stage['trajs'],
|
| 276 |
+
stage['sigma'], title, show_sigma=use_sigma)
|
| 277 |
+
|
| 278 |
+
if use_sigma and stage['sigma'] is not None:
|
| 279 |
+
sigma_std = np.exp(stage['sigma'][:A, 0] / 2)
|
| 280 |
+
sm = cm.ScalarMappable(cmap=cm.RdYlGn_r,
|
| 281 |
+
norm=Normalize(vmin=sigma_std.min() - 0.01,
|
| 282 |
+
vmax=sigma_std.max() + 0.01))
|
| 283 |
+
sm.set_array([])
|
| 284 |
+
cbar = fig.colorbar(sm, ax=ax, shrink=0.5, pad=0.02)
|
| 285 |
+
cbar.set_label('σ (uncertainty)', fontsize=7)
|
| 286 |
+
|
| 287 |
+
plt.tight_layout()
|
| 288 |
+
fname = f'sample_{sample_idx:04d}_{version}_stage{si}.png'
|
| 289 |
+
fig.savefig(os.path.join(out_dir, fname),
|
| 290 |
+
bbox_inches='tight', pad_inches=0.02, dpi=200)
|
| 291 |
+
plt.close(fig)
|
| 292 |
+
|
| 293 |
+
print(f' Sample {sample_idx} {version}: {len(stages)} stages saved')
|
| 294 |
+
|
| 295 |
+
print(f'\nAll saved to {out_dir}/')
|
| 296 |
+
|
| 297 |
+
|
| 298 |
+
if __name__ == '__main__':
|
| 299 |
+
main()
|
LED/viz_uncertainty_denoising.py
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Visualize LED denoising with/without uncertainty — matching qual_uncertainty.png style.
|
| 3 |
+
|
| 4 |
+
For each denoising step:
|
| 5 |
+
- Past: blue dots+lines
|
| 6 |
+
- Future GT: red dots+lines
|
| 7 |
+
- Prediction: green, with darkness proportional to uncertainty
|
| 8 |
+
(dark green = certain, light green = uncertain)
|
| 9 |
+
|
| 10 |
+
Left column: No uncertainty (all predictions same green)
|
| 11 |
+
Right column: With uncertainty (per-agent green intensity from σ)
|
| 12 |
+
|
| 13 |
+
Produces one image per sample with rows = denoising stages,
|
| 14 |
+
columns = [no uncertainty, with uncertainty].
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
import os, sys, random
|
| 18 |
+
import numpy as np
|
| 19 |
+
import torch
|
| 20 |
+
import matplotlib
|
| 21 |
+
matplotlib.use('Agg')
|
| 22 |
+
import matplotlib.pyplot as plt
|
| 23 |
+
import matplotlib.colors as mcolors
|
| 24 |
+
from matplotlib.lines import Line2D
|
| 25 |
+
|
| 26 |
+
sys.path.insert(0, '/mnt/jaewoo4tb/srtp/LED')
|
| 27 |
+
|
| 28 |
+
from utils.config import Config
|
| 29 |
+
from data.dataloader_nba import NBADataset, seq_collate
|
| 30 |
+
from torch.utils.data import DataLoader
|
| 31 |
+
from models.model_led_initializer import LEDInitializer as InitializationModel
|
| 32 |
+
from models.model_diffusion import TransformerDenoisingModel as CoreDenoisingModel
|
| 33 |
+
from models.future_interaction_graph_v6 import FutureInteractionGraphV6Wrapper
|
| 34 |
+
|
| 35 |
+
NUM_Tau = 5
|
| 36 |
+
COURT_IMG = '/mnt/jaewoo4tb/srtp/srtp/raw_data/nba/court.png'
|
| 37 |
+
COURT_W, COURT_H = 28.0, 15.0
|
| 38 |
+
A = 11
|
| 39 |
+
OUT_DIR = '/mnt/jaewoo4tb/srtp/NeurIPS_2026_SRT/Viz_uncertainty'
|
| 40 |
+
|
| 41 |
+
# Colors matching the reference figure
|
| 42 |
+
PAST_COLOR = '#2962FF' # blue
|
| 43 |
+
GT_COLOR = '#E91E63' # red/pink
|
| 44 |
+
PRED_BASE = np.array([0.2, 0.6, 0.1]) # base green for predictions
|
| 45 |
+
|
| 46 |
+
_court_cache = None
|
| 47 |
+
def court_img():
|
| 48 |
+
global _court_cache
|
| 49 |
+
if _court_cache is None:
|
| 50 |
+
_court_cache = plt.imread(COURT_IMG)
|
| 51 |
+
return _court_cache
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def load_models(ckpt_path, use_sigma, edge_mode, top_n, device):
|
| 55 |
+
os.chdir('/mnt/jaewoo4tb/srtp/LED')
|
| 56 |
+
cfg = Config('led_augment', 'viz')
|
| 57 |
+
model = CoreDenoisingModel().to(device)
|
| 58 |
+
cp = torch.load(cfg.pretrained_core_denoising_model, map_location='cpu', weights_only=False)
|
| 59 |
+
model.load_state_dict(cp['model_dict']); model.eval()
|
| 60 |
+
|
| 61 |
+
model_init = InitializationModel(t_h=10, d_h=6, t_f=20, d_f=2, k_pred=20).to(device)
|
| 62 |
+
graph = FutureInteractionGraphV6Wrapper(
|
| 63 |
+
num_agents=11, future_steps=20, past_steps=10,
|
| 64 |
+
past_channels=6, node_dim=128, top_n=top_n,
|
| 65 |
+
num_denoise_steps=NUM_Tau, edge_mode=edge_mode).to(device)
|
| 66 |
+
|
| 67 |
+
ckpt = torch.load(ckpt_path, map_location='cpu', weights_only=False)
|
| 68 |
+
model_init.load_state_dict(ckpt['model_initializer_dict'])
|
| 69 |
+
graph.load_state_dict(ckpt['interaction_graph_dict'])
|
| 70 |
+
model_init.eval(); graph.eval()
|
| 71 |
+
return cfg, model, model_init, graph
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def get_stages(model, graph, model_init, past_traj, traj_mask,
|
| 75 |
+
betas, alphas, abs_sqrt, oma_sqrt,
|
| 76 |
+
use_sigma, traj_scale, init_pos_np):
|
| 77 |
+
"""Get prediction at each meaningful stage."""
|
| 78 |
+
sample_pred, mean_est, var_est = model_init(past_traj, traj_mask)
|
| 79 |
+
sample_pred_scaled = (torch.exp(var_est / 2)[..., None, None]
|
| 80 |
+
* sample_pred
|
| 81 |
+
/ sample_pred.std(dim=1).mean(dim=(1, 2))[:, None, None, None])
|
| 82 |
+
loc = sample_pred_scaled + mean_est[:, None]
|
| 83 |
+
|
| 84 |
+
sigma_input = var_est if use_sigma else None
|
| 85 |
+
sigma_np = var_est.detach().cpu().numpy() if use_sigma else None
|
| 86 |
+
|
| 87 |
+
stages = []
|
| 88 |
+
|
| 89 |
+
# Stage 0: Initializer output (before any denoising)
|
| 90 |
+
loc_abs = loc.detach().cpu().numpy() * traj_scale + init_pos_np.reshape(-1, 1, 1, 2)
|
| 91 |
+
stages.append({'trajs': loc_abs[:A], 'sigma': sigma_np, 'label': 'Initializer'})
|
| 92 |
+
|
| 93 |
+
# Run denoising, capture at key steps
|
| 94 |
+
cur_y = loc[:, :10]
|
| 95 |
+
capture_at = {3: 'After 2 steps', 1: 'After 4 steps'}
|
| 96 |
+
|
| 97 |
+
for i in reversed(range(NUM_Tau)):
|
| 98 |
+
ef = (1 - alphas[i]) / oma_sqrt[i]
|
| 99 |
+
beta = betas[i].repeat(past_traj.shape[0]).unsqueeze(-1).unsqueeze(-1).unsqueeze(-1)
|
| 100 |
+
eps = model.generate_accelerate(cur_y, beta.squeeze(-1).squeeze(-1), past_traj, traj_mask)
|
| 101 |
+
y0h = (cur_y - oma_sqrt[i] * eps) / abs_sqrt[i]
|
| 102 |
+
delta = graph(y0h, past_traj, i, sigma=sigma_input)
|
| 103 |
+
eps = eps + delta
|
| 104 |
+
mean = (1 / alphas[i].sqrt()) * (cur_y - ef * eps)
|
| 105 |
+
z = torch.randn_like(cur_y)
|
| 106 |
+
cur_y = mean + betas[i].sqrt() * z * 0.00001
|
| 107 |
+
|
| 108 |
+
if i in capture_at:
|
| 109 |
+
cur_abs = cur_y.detach().cpu().numpy() * traj_scale + init_pos_np.reshape(-1, 1, 1, 2)
|
| 110 |
+
stages.append({'trajs': cur_abs[:A], 'sigma': sigma_np, 'label': capture_at[i]})
|
| 111 |
+
|
| 112 |
+
# Final: run second half too
|
| 113 |
+
cur_y2 = loc[:, 10:]
|
| 114 |
+
for i in reversed(range(NUM_Tau)):
|
| 115 |
+
ef = (1 - alphas[i]) / oma_sqrt[i]
|
| 116 |
+
beta = betas[i].repeat(past_traj.shape[0]).unsqueeze(-1).unsqueeze(-1).unsqueeze(-1)
|
| 117 |
+
eps = model.generate_accelerate(cur_y2, beta.squeeze(-1).squeeze(-1), past_traj, traj_mask)
|
| 118 |
+
y0h = (cur_y2 - oma_sqrt[i] * eps) / abs_sqrt[i]
|
| 119 |
+
delta = graph(y0h, past_traj, i, sigma=sigma_input)
|
| 120 |
+
eps = eps + delta
|
| 121 |
+
mean = (1 / alphas[i].sqrt()) * (cur_y2 - ef * eps)
|
| 122 |
+
z = torch.randn_like(cur_y2)
|
| 123 |
+
cur_y2 = mean + betas[i].sqrt() * z * 0.00001
|
| 124 |
+
|
| 125 |
+
final = torch.cat((cur_y2, cur_y), dim=1)
|
| 126 |
+
final_abs = final.detach().cpu().numpy() * traj_scale + init_pos_np.reshape(-1, 1, 1, 2)
|
| 127 |
+
stages.append({'trajs': final_abs[:A], 'sigma': sigma_np, 'label': 'Final'})
|
| 128 |
+
|
| 129 |
+
return stages
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
def uncertainty_to_green(sigma_vals, a):
|
| 133 |
+
"""Convert per-agent uncertainty to green color intensity.
|
| 134 |
+
High certainty (low σ) → dark green, Low certainty (high σ) → light green.
|
| 135 |
+
"""
|
| 136 |
+
if sigma_vals is None:
|
| 137 |
+
return (0.2, 0.65, 0.1, 0.8) # default green
|
| 138 |
+
|
| 139 |
+
sigma_std = np.exp(sigma_vals[:A, 0] / 2) # actual std
|
| 140 |
+
# Normalize to [0, 1] range
|
| 141 |
+
s_min, s_max = sigma_std.min(), sigma_std.max()
|
| 142 |
+
if s_max - s_min < 1e-6:
|
| 143 |
+
norm_val = 0.5
|
| 144 |
+
else:
|
| 145 |
+
norm_val = (sigma_std[a] - s_min) / (s_max - s_min)
|
| 146 |
+
|
| 147 |
+
# Dark green (certain) to light yellow-green (uncertain)
|
| 148 |
+
# Interpolate: dark (0.1, 0.4, 0.05) ↔ light (0.6, 0.85, 0.3)
|
| 149 |
+
dark = np.array([0.05, 0.35, 0.0])
|
| 150 |
+
light = np.array([0.55, 0.82, 0.25])
|
| 151 |
+
color = dark + norm_val * (light - dark)
|
| 152 |
+
return (*color, 0.85)
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
def draw_stage(ax, past_abs, gt_abs, trajs, sigma_vals, show_sigma,
|
| 156 |
+
xlim=None, ylim=None):
|
| 157 |
+
"""Draw one stage on court."""
|
| 158 |
+
ax.imshow(court_img(), extent=[0, COURT_W, COURT_H, 0], zorder=0, alpha=0.45)
|
| 159 |
+
if xlim:
|
| 160 |
+
ax.set_xlim(*xlim)
|
| 161 |
+
else:
|
| 162 |
+
ax.set_xlim(0, COURT_W)
|
| 163 |
+
if ylim:
|
| 164 |
+
ax.set_ylim(*ylim)
|
| 165 |
+
else:
|
| 166 |
+
ax.set_ylim(COURT_H, 0)
|
| 167 |
+
ax.axis('off')
|
| 168 |
+
|
| 169 |
+
K = trajs.shape[1]
|
| 170 |
+
K_show = min(5, K)
|
| 171 |
+
|
| 172 |
+
# Find best mode per agent
|
| 173 |
+
best_modes = []
|
| 174 |
+
for a in range(A):
|
| 175 |
+
if K > 1:
|
| 176 |
+
dists = np.linalg.norm(trajs[a, :K_show] - gt_abs[a:a+1], axis=-1).mean(axis=-1)
|
| 177 |
+
best_modes.append(dists.argmin())
|
| 178 |
+
else:
|
| 179 |
+
best_modes.append(0)
|
| 180 |
+
|
| 181 |
+
# Draw predictions (green, intensity by uncertainty)
|
| 182 |
+
for a in range(A):
|
| 183 |
+
if show_sigma:
|
| 184 |
+
color = uncertainty_to_green(sigma_vals, a)
|
| 185 |
+
else:
|
| 186 |
+
color = (0.2, 0.65, 0.1, 0.7)
|
| 187 |
+
|
| 188 |
+
# Light modes
|
| 189 |
+
for k in range(K_show):
|
| 190 |
+
pred = trajs[a, k]
|
| 191 |
+
ax.plot(pred[:, 0], pred[:, 1], color=color, lw=0.4, alpha=0.25, zorder=3)
|
| 192 |
+
|
| 193 |
+
# Best mode (thicker)
|
| 194 |
+
best = trajs[a, best_modes[a]]
|
| 195 |
+
pred_line = np.concatenate([past_abs[a, -1:], best], axis=0)
|
| 196 |
+
ax.plot(pred_line[:, 0], pred_line[:, 1], color=color, lw=1.8,
|
| 197 |
+
marker='o', ms=1.8, markevery=3, zorder=5)
|
| 198 |
+
|
| 199 |
+
# Draw past (blue)
|
| 200 |
+
for a in range(A):
|
| 201 |
+
ax.plot(past_abs[a, :, 0], past_abs[a, :, 1], color=PAST_COLOR,
|
| 202 |
+
lw=1.2, marker='o', ms=2.0, markevery=2, alpha=0.85, zorder=6)
|
| 203 |
+
|
| 204 |
+
# Draw GT future (red)
|
| 205 |
+
for a in range(A):
|
| 206 |
+
gt = np.concatenate([past_abs[a, -1:], gt_abs[a]], axis=0)
|
| 207 |
+
ax.plot(gt[:, 0], gt[:, 1], color=GT_COLOR, lw=1.0,
|
| 208 |
+
marker='o', ms=1.5, markevery=3, alpha=0.7, zorder=4)
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
def make_figure(stages_nosigma, stages_sigma, past_abs, gt_abs, sample_idx, zoom_region=None):
|
| 212 |
+
"""Create the full comparison figure."""
|
| 213 |
+
n_stages = len(stages_nosigma)
|
| 214 |
+
|
| 215 |
+
# If zoom region provided, add zoomed row at bottom
|
| 216 |
+
n_rows = n_stages + (1 if zoom_region else 0)
|
| 217 |
+
|
| 218 |
+
fig, axes = plt.subplots(n_rows, 2, figsize=(12, 3.0 * n_rows), dpi=200)
|
| 219 |
+
if n_rows == 1:
|
| 220 |
+
axes = axes.reshape(1, 2)
|
| 221 |
+
|
| 222 |
+
for row in range(n_stages):
|
| 223 |
+
# Left: no uncertainty
|
| 224 |
+
draw_stage(axes[row, 0], past_abs, gt_abs,
|
| 225 |
+
stages_nosigma[row]['trajs'], None, show_sigma=False)
|
| 226 |
+
if row == 0:
|
| 227 |
+
axes[row, 0].set_title('No uncertainty', fontsize=11, fontweight='bold')
|
| 228 |
+
|
| 229 |
+
# Right: with uncertainty
|
| 230 |
+
draw_stage(axes[row, 1], past_abs, gt_abs,
|
| 231 |
+
stages_sigma[row]['trajs'], stages_sigma[row]['sigma'], show_sigma=True)
|
| 232 |
+
if row == 0:
|
| 233 |
+
axes[row, 1].set_title('Using uncertainty', fontsize=11, fontweight='bold')
|
| 234 |
+
|
| 235 |
+
# Row label
|
| 236 |
+
axes[row, 0].text(0.02, 0.95, stages_nosigma[row]['label'],
|
| 237 |
+
transform=axes[row, 0].transAxes, fontsize=8,
|
| 238 |
+
verticalalignment='top', fontweight='bold',
|
| 239 |
+
bbox=dict(boxstyle='round,pad=0.3', facecolor='white', alpha=0.7))
|
| 240 |
+
|
| 241 |
+
# Zoomed bottom row
|
| 242 |
+
if zoom_region and n_rows > n_stages:
|
| 243 |
+
xl, yl = zoom_region
|
| 244 |
+
for col in range(2):
|
| 245 |
+
stages = stages_nosigma if col == 0 else stages_sigma
|
| 246 |
+
sigma = None if col == 0 else stages[-1]['sigma']
|
| 247 |
+
draw_stage(axes[-1, col], past_abs, gt_abs,
|
| 248 |
+
stages[-1]['trajs'], sigma, show_sigma=(col == 1),
|
| 249 |
+
xlim=xl, ylim=yl)
|
| 250 |
+
# Draw zoom box on the row above
|
| 251 |
+
from matplotlib.patches import Rectangle
|
| 252 |
+
rect = Rectangle((xl[0], yl[1]), xl[1]-xl[0], yl[0]-yl[1],
|
| 253 |
+
linewidth=1.5, edgecolor='black', facecolor='none', zorder=10)
|
| 254 |
+
axes[-2, col].add_patch(rect)
|
| 255 |
+
|
| 256 |
+
# Add noise level indicator on the right side
|
| 257 |
+
# Gray gradient arrow from τ^K (top) to 0 (bottom)
|
| 258 |
+
noise_ax = fig.add_axes([0.92, 0.15, 0.03, 0.7])
|
| 259 |
+
gradient = np.linspace(0.3, 1.0, 256).reshape(256, 1)
|
| 260 |
+
noise_ax.imshow(gradient, aspect='auto', cmap='Greys_r', extent=[0, 1, 0, 1])
|
| 261 |
+
noise_ax.set_xticks([])
|
| 262 |
+
noise_ax.set_yticks([0, 1])
|
| 263 |
+
noise_ax.set_yticklabels(['0', 'τ$^K$'], fontsize=8)
|
| 264 |
+
noise_ax.set_ylabel('Noise level', fontsize=8, rotation=270, labelpad=12)
|
| 265 |
+
noise_ax.yaxis.set_label_position('right')
|
| 266 |
+
|
| 267 |
+
# Legend
|
| 268 |
+
legend_elements = [
|
| 269 |
+
Line2D([0], [0], color=PAST_COLOR, lw=2, marker='o', ms=4, label='Past'),
|
| 270 |
+
Line2D([0], [0], color=GT_COLOR, lw=2, marker='o', ms=4, label='Future'),
|
| 271 |
+
Line2D([0], [0], color=(0.2, 0.65, 0.1), lw=2, marker='o', ms=4, label='Prediction'),
|
| 272 |
+
]
|
| 273 |
+
fig.legend(handles=legend_elements, loc='upper center', ncol=3,
|
| 274 |
+
fontsize=9, frameon=True, fancybox=True, shadow=True,
|
| 275 |
+
bbox_to_anchor=(0.45, 0.98))
|
| 276 |
+
|
| 277 |
+
# Uncertainty colorbar for the right column
|
| 278 |
+
from matplotlib.cm import ScalarMappable
|
| 279 |
+
from matplotlib.colors import LinearSegmentedColormap, Normalize
|
| 280 |
+
dark = (0.05, 0.35, 0.0)
|
| 281 |
+
light = (0.55, 0.82, 0.25)
|
| 282 |
+
cmap_unc = LinearSegmentedColormap.from_list('unc', [dark, light])
|
| 283 |
+
sm = ScalarMappable(cmap=cmap_unc, norm=Normalize(0, 1))
|
| 284 |
+
sm.set_array([])
|
| 285 |
+
cbar_ax = fig.add_axes([0.52, 0.96, 0.15, 0.012])
|
| 286 |
+
cbar = fig.colorbar(sm, cax=cbar_ax, orientation='horizontal')
|
| 287 |
+
cbar.set_ticks([0, 1])
|
| 288 |
+
cbar.set_ticklabels(['Certain', 'Uncertain'], fontsize=7)
|
| 289 |
+
cbar_ax.set_title('Uncertainty level', fontsize=7, pad=2)
|
| 290 |
+
|
| 291 |
+
plt.subplots_adjust(hspace=0.05, wspace=0.02, right=0.90, top=0.93)
|
| 292 |
+
|
| 293 |
+
save_path = os.path.join(OUT_DIR, f'denoising_uncertainty_sample_{sample_idx:04d}.png')
|
| 294 |
+
fig.savefig(save_path, bbox_inches='tight', pad_inches=0.05, dpi=200)
|
| 295 |
+
plt.close(fig)
|
| 296 |
+
print(f'Saved: {save_path}')
|
| 297 |
+
|
| 298 |
+
|
| 299 |
+
def main():
|
| 300 |
+
device = 'cuda:0'
|
| 301 |
+
os.environ['CUDA_VISIBLE_DEVICES'] = '3' # use GPU 3
|
| 302 |
+
torch.cuda.set_device(0)
|
| 303 |
+
|
| 304 |
+
ckpt_sigma = '/mnt/jaewoo4tb/srtp/LED/results/led_augment/graph_v6_edge_relpos/models/model_0052.p'
|
| 305 |
+
ckpt_nosigma = '/mnt/jaewoo4tb/srtp/LED/results/led_augment/graph_v6_nosigma_n3/models/model_0084.p'
|
| 306 |
+
|
| 307 |
+
cfg, model_s, init_s, graph_s = load_models(
|
| 308 |
+
ckpt_sigma, True, 'relpos_only', 5, device)
|
| 309 |
+
_, model_n, init_n, graph_n = load_models(
|
| 310 |
+
ckpt_nosigma, False, 'full', 3, device)
|
| 311 |
+
|
| 312 |
+
betas = torch.linspace(1e-5, 1e-2, 100).to(device)
|
| 313 |
+
alphas = 1 - betas
|
| 314 |
+
alphas_prod = torch.cumprod(alphas, 0)
|
| 315 |
+
abs_sqrt = torch.sqrt(alphas_prod)
|
| 316 |
+
oma_sqrt = torch.sqrt(1 - alphas_prod)
|
| 317 |
+
|
| 318 |
+
traj_scale = cfg.traj_scale
|
| 319 |
+
|
| 320 |
+
test_dset = NBADataset(obs_len=10, pred_len=20, training=False)
|
| 321 |
+
test_loader = DataLoader(test_dset, batch_size=1, shuffle=False, collate_fn=seq_collate)
|
| 322 |
+
|
| 323 |
+
np.random.seed(42); random.seed(42); torch.manual_seed(42)
|
| 324 |
+
sample_indices = sorted(random.sample(range(len(test_dset)), 5))
|
| 325 |
+
|
| 326 |
+
with torch.no_grad():
|
| 327 |
+
for sample_idx, data in enumerate(test_loader):
|
| 328 |
+
if sample_idx not in sample_indices:
|
| 329 |
+
continue
|
| 330 |
+
if sample_idx > max(sample_indices):
|
| 331 |
+
break
|
| 332 |
+
|
| 333 |
+
traj_mean_t = torch.FloatTensor(cfg.traj_mean).cuda().unsqueeze(0).unsqueeze(0).unsqueeze(0)
|
| 334 |
+
initial_pos = data['pre_motion_3D'].cuda()[:, :, -1:]
|
| 335 |
+
# Data is already in court units (0-28 x 0-15), no further scaling needed
|
| 336 |
+
past_abs_np = data['pre_motion_3D'].numpy().squeeze(0)
|
| 337 |
+
fut_abs_np = data['fut_motion_3D'].numpy().squeeze(0)
|
| 338 |
+
init_pos_np = initial_pos.cpu().numpy().squeeze(0)
|
| 339 |
+
|
| 340 |
+
past_traj_abs = ((data['pre_motion_3D'].cuda() - traj_mean_t) / traj_scale).view(-1, 10, 2)
|
| 341 |
+
past_traj_rel = ((data['pre_motion_3D'].cuda() - initial_pos) / traj_scale).view(-1, 10, 2)
|
| 342 |
+
past_traj_vel = torch.cat((past_traj_rel[:, 1:] - past_traj_rel[:, :-1],
|
| 343 |
+
torch.zeros_like(past_traj_rel[:, :1])), dim=1)
|
| 344 |
+
past_traj = torch.cat((past_traj_abs, past_traj_rel, past_traj_vel), dim=-1)
|
| 345 |
+
traj_mask = torch.ones(11, 11).cuda()
|
| 346 |
+
|
| 347 |
+
# Get stages for both versions
|
| 348 |
+
stages_nosigma = get_stages(
|
| 349 |
+
model_n, graph_n, init_n, past_traj, traj_mask,
|
| 350 |
+
betas, alphas, abs_sqrt, oma_sqrt,
|
| 351 |
+
False, traj_scale, init_pos_np)
|
| 352 |
+
|
| 353 |
+
stages_sigma = get_stages(
|
| 354 |
+
model_s, graph_s, init_s, past_traj, traj_mask,
|
| 355 |
+
betas, alphas, abs_sqrt, oma_sqrt,
|
| 356 |
+
True, traj_scale, init_pos_np)
|
| 357 |
+
|
| 358 |
+
# Compute zoom region around the action
|
| 359 |
+
all_pos = np.concatenate([past_abs_np.reshape(-1, 2), fut_abs_np.reshape(-1, 2)])
|
| 360 |
+
cx, cy = all_pos.mean(axis=0)
|
| 361 |
+
span = max(all_pos.max(axis=0) - all_pos.min(axis=0)) * 0.6
|
| 362 |
+
zoom = ([cx - span, cx + span], [cy + span, cy - span])
|
| 363 |
+
|
| 364 |
+
make_figure(stages_nosigma, stages_sigma, past_abs_np, fut_abs_np,
|
| 365 |
+
sample_idx, zoom_region=zoom)
|
| 366 |
+
|
| 367 |
+
print(f'\nAll saved to {OUT_DIR}/')
|
| 368 |
+
|
| 369 |
+
|
| 370 |
+
if __name__ == '__main__':
|
| 371 |
+
main()
|
LED/viz_uncertainty_individual.py
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Draw each denoising step as a separate image.
|
| 3 |
+
Past=blue, GT=red, Prediction=green (darkness by uncertainty).
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os, sys, random
|
| 7 |
+
import numpy as np
|
| 8 |
+
import torch
|
| 9 |
+
import matplotlib
|
| 10 |
+
matplotlib.use('Agg')
|
| 11 |
+
import matplotlib.pyplot as plt
|
| 12 |
+
|
| 13 |
+
sys.path.insert(0, '/mnt/jaewoo4tb/srtp/LED')
|
| 14 |
+
|
| 15 |
+
from utils.config import Config
|
| 16 |
+
from data.dataloader_nba import NBADataset, seq_collate
|
| 17 |
+
from torch.utils.data import DataLoader
|
| 18 |
+
from models.model_led_initializer import LEDInitializer
|
| 19 |
+
from models.model_diffusion import TransformerDenoisingModel
|
| 20 |
+
from models.future_interaction_graph_v6 import FutureInteractionGraphV6Wrapper
|
| 21 |
+
|
| 22 |
+
NUM_Tau = 5
|
| 23 |
+
COURT_IMG = '/mnt/jaewoo4tb/srtp/srtp/raw_data/nba/court.png'
|
| 24 |
+
COURT_W, COURT_H = 28.0, 15.0
|
| 25 |
+
A = 11
|
| 26 |
+
OUT_DIR = '/mnt/jaewoo4tb/srtp/NeurIPS_2026_SRT/Viz_uncertainty'
|
| 27 |
+
|
| 28 |
+
PAST_COLOR = '#2962FF'
|
| 29 |
+
GT_COLOR = '#E91E63'
|
| 30 |
+
|
| 31 |
+
_court_cache = None
|
| 32 |
+
def court_img():
|
| 33 |
+
global _court_cache
|
| 34 |
+
if _court_cache is None:
|
| 35 |
+
_court_cache = plt.imread(COURT_IMG)
|
| 36 |
+
return _court_cache
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def load_models(ckpt_path, use_sigma, edge_mode, top_n, device):
|
| 40 |
+
os.chdir('/mnt/jaewoo4tb/srtp/LED')
|
| 41 |
+
cfg = Config('led_augment', 'viz')
|
| 42 |
+
model = TransformerDenoisingModel().to(device)
|
| 43 |
+
cp = torch.load(cfg.pretrained_core_denoising_model, map_location='cpu', weights_only=False)
|
| 44 |
+
model.load_state_dict(cp['model_dict']); model.eval()
|
| 45 |
+
init_m = LEDInitializer(t_h=10, d_h=6, t_f=20, d_f=2, k_pred=20).to(device)
|
| 46 |
+
graph = FutureInteractionGraphV6Wrapper(
|
| 47 |
+
num_agents=11, future_steps=20, past_steps=10,
|
| 48 |
+
past_channels=6, node_dim=128, top_n=top_n,
|
| 49 |
+
num_denoise_steps=NUM_Tau, edge_mode=edge_mode).to(device)
|
| 50 |
+
ckpt = torch.load(ckpt_path, map_location='cpu', weights_only=False)
|
| 51 |
+
init_m.load_state_dict(ckpt['model_initializer_dict'])
|
| 52 |
+
graph.load_state_dict(ckpt['interaction_graph_dict'])
|
| 53 |
+
init_m.eval(); graph.eval()
|
| 54 |
+
return cfg, model, init_m, graph
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def get_all_steps(model, graph, init_m, past_traj, traj_mask,
|
| 58 |
+
betas, alphas, abs_sqrt, oma_sqrt,
|
| 59 |
+
use_sigma, traj_scale, init_pos_np):
|
| 60 |
+
sample_pred, mean_est, var_est = init_m(past_traj, traj_mask)
|
| 61 |
+
sample_pred = (torch.exp(var_est / 2)[..., None, None]
|
| 62 |
+
* sample_pred / sample_pred.std(dim=1).mean(dim=(1, 2))[:, None, None, None])
|
| 63 |
+
loc = sample_pred + mean_est[:, None]
|
| 64 |
+
sigma_input = var_est if use_sigma else None
|
| 65 |
+
sigma_np = var_est.detach().cpu().numpy() if use_sigma else None
|
| 66 |
+
|
| 67 |
+
steps = []
|
| 68 |
+
|
| 69 |
+
# Initializer
|
| 70 |
+
loc_abs = loc.detach().cpu().numpy() * traj_scale + init_pos_np.reshape(-1, 1, 1, 2)
|
| 71 |
+
steps.append({'trajs': loc_abs[:A], 'sigma': sigma_np, 'label': 'Initializer'})
|
| 72 |
+
|
| 73 |
+
# Denoise first 10 modes
|
| 74 |
+
cur_y = loc[:, :10]
|
| 75 |
+
for i in reversed(range(NUM_Tau)):
|
| 76 |
+
ef = (1 - alphas[i]) / oma_sqrt[i]
|
| 77 |
+
beta = betas[i].repeat(past_traj.shape[0]).unsqueeze(-1).unsqueeze(-1).unsqueeze(-1)
|
| 78 |
+
eps = model.generate_accelerate(cur_y, beta.squeeze(-1).squeeze(-1), past_traj, traj_mask)
|
| 79 |
+
y0h = (cur_y - oma_sqrt[i] * eps) / abs_sqrt[i]
|
| 80 |
+
delta = graph(y0h, past_traj, i, sigma=sigma_input)
|
| 81 |
+
eps = eps + delta
|
| 82 |
+
mean = (1 / alphas[i].sqrt()) * (cur_y - ef * eps)
|
| 83 |
+
z = torch.randn_like(cur_y)
|
| 84 |
+
cur_y = mean + betas[i].sqrt() * z * 0.00001
|
| 85 |
+
|
| 86 |
+
cur_abs = cur_y.detach().cpu().numpy() * traj_scale + init_pos_np.reshape(-1, 1, 1, 2)
|
| 87 |
+
steps.append({'trajs': cur_abs[:A], 'sigma': sigma_np, 'label': f'Step {NUM_Tau - i}/{NUM_Tau}'})
|
| 88 |
+
|
| 89 |
+
return steps
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def unc_color(sigma_vals, a):
|
| 93 |
+
if sigma_vals is None:
|
| 94 |
+
return (0.2, 0.65, 0.1, 0.8)
|
| 95 |
+
sigma_std = np.exp(sigma_vals[:A, 0] / 2)
|
| 96 |
+
s_min, s_max = sigma_std.min(), sigma_std.max()
|
| 97 |
+
norm = (sigma_std[a] - s_min) / (s_max - s_min + 1e-8)
|
| 98 |
+
dark = np.array([0.05, 0.35, 0.0])
|
| 99 |
+
light = np.array([0.55, 0.82, 0.25])
|
| 100 |
+
c = dark + norm * (light - dark)
|
| 101 |
+
return (*c, 0.85)
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def draw(ax, past, gt, trajs, sigma, show_sigma):
|
| 105 |
+
ax.imshow(court_img(), extent=[0, COURT_W, COURT_H, 0], zorder=0, alpha=0.45)
|
| 106 |
+
ax.set_xlim(0, COURT_W); ax.set_ylim(COURT_H, 0)
|
| 107 |
+
ax.axis('off')
|
| 108 |
+
|
| 109 |
+
K = min(5, trajs.shape[1])
|
| 110 |
+
|
| 111 |
+
for a in range(A):
|
| 112 |
+
color = unc_color(sigma, a) if show_sigma else (0.2, 0.65, 0.1, 0.8)
|
| 113 |
+
dists = np.linalg.norm(trajs[a, :K] - gt[a:a+1], axis=-1).mean(axis=-1)
|
| 114 |
+
best_k = dists.argmin()
|
| 115 |
+
|
| 116 |
+
best = np.concatenate([past[a, -1:], trajs[a, best_k]], axis=0)
|
| 117 |
+
ax.plot(best[:, 0], best[:, 1], color=color, lw=1.8,
|
| 118 |
+
marker='o', ms=1.8, markevery=3, zorder=5)
|
| 119 |
+
|
| 120 |
+
for a in range(A):
|
| 121 |
+
ax.plot(past[a, :, 0], past[a, :, 1], color=PAST_COLOR,
|
| 122 |
+
lw=1.2, marker='o', ms=2.0, markevery=2, alpha=0.85, zorder=6)
|
| 123 |
+
|
| 124 |
+
for a in range(A):
|
| 125 |
+
g = np.concatenate([past[a, -1:], gt[a]], axis=0)
|
| 126 |
+
ax.plot(g[:, 0], g[:, 1], color=GT_COLOR, lw=1.0,
|
| 127 |
+
marker='o', ms=1.5, markevery=3, alpha=0.7, zorder=4)
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def main():
|
| 131 |
+
device = 'cuda:0'
|
| 132 |
+
os.environ['CUDA_VISIBLE_DEVICES'] = '3'
|
| 133 |
+
torch.cuda.set_device(0)
|
| 134 |
+
|
| 135 |
+
ckpt_s = '/mnt/jaewoo4tb/srtp/LED/results/led_augment/graph_v6_edge_relpos/models/model_0052.p'
|
| 136 |
+
ckpt_n = '/mnt/jaewoo4tb/srtp/LED/results/led_augment/graph_v6_nosigma_n3/models/model_0084.p'
|
| 137 |
+
|
| 138 |
+
cfg, model_s, init_s, graph_s = load_models(ckpt_s, True, 'relpos_only', 5, device)
|
| 139 |
+
_, model_n, init_n, graph_n = load_models(ckpt_n, False, 'full', 3, device)
|
| 140 |
+
|
| 141 |
+
betas = torch.linspace(1e-5, 1e-2, 100).to(device)
|
| 142 |
+
alphas = 1 - betas
|
| 143 |
+
alphas_prod = torch.cumprod(alphas, 0)
|
| 144 |
+
abs_sqrt = torch.sqrt(alphas_prod)
|
| 145 |
+
oma_sqrt = torch.sqrt(1 - alphas_prod)
|
| 146 |
+
traj_scale = cfg.traj_scale
|
| 147 |
+
|
| 148 |
+
test_dset = NBADataset(obs_len=10, pred_len=20, training=False)
|
| 149 |
+
test_loader = DataLoader(test_dset, batch_size=1, shuffle=False, collate_fn=seq_collate)
|
| 150 |
+
|
| 151 |
+
np.random.seed(42); random.seed(42); torch.manual_seed(42)
|
| 152 |
+
sample_indices = sorted(random.sample(range(len(test_dset)), 3))
|
| 153 |
+
|
| 154 |
+
with torch.no_grad():
|
| 155 |
+
for idx, data in enumerate(test_loader):
|
| 156 |
+
if idx not in sample_indices:
|
| 157 |
+
continue
|
| 158 |
+
if idx > max(sample_indices):
|
| 159 |
+
break
|
| 160 |
+
|
| 161 |
+
traj_mean_t = torch.FloatTensor(cfg.traj_mean).cuda().unsqueeze(0).unsqueeze(0).unsqueeze(0)
|
| 162 |
+
initial_pos = data['pre_motion_3D'].cuda()[:, :, -1:]
|
| 163 |
+
# Data is already in court units (0-28 x 0-15), no further scaling needed
|
| 164 |
+
past_np = data['pre_motion_3D'].numpy().squeeze(0)
|
| 165 |
+
fut_np = data['fut_motion_3D'].numpy().squeeze(0)
|
| 166 |
+
init_np = initial_pos.cpu().numpy().squeeze(0)
|
| 167 |
+
|
| 168 |
+
pa = ((data['pre_motion_3D'].cuda() - traj_mean_t) / traj_scale).view(-1, 10, 2)
|
| 169 |
+
pr = ((data['pre_motion_3D'].cuda() - initial_pos) / traj_scale).view(-1, 10, 2)
|
| 170 |
+
pv = torch.cat((pr[:, 1:] - pr[:, :-1], torch.zeros_like(pr[:, :1])), dim=1)
|
| 171 |
+
past_traj = torch.cat((pa, pr, pv), dim=-1)
|
| 172 |
+
mask = torch.ones(11, 11).cuda()
|
| 173 |
+
|
| 174 |
+
for version, model, init_m, graph, use_sigma, tag in [
|
| 175 |
+
('nosigma', model_n, init_n, graph_n, False, 'no_uncertainty'),
|
| 176 |
+
('sigma', model_s, init_s, graph_s, True, 'with_uncertainty'),
|
| 177 |
+
]:
|
| 178 |
+
steps = get_all_steps(model, graph, init_m, past_traj, mask,
|
| 179 |
+
betas, alphas, abs_sqrt, oma_sqrt,
|
| 180 |
+
use_sigma, traj_scale, init_np)
|
| 181 |
+
|
| 182 |
+
for si, step in enumerate(steps):
|
| 183 |
+
fig, ax = plt.subplots(1, 1, figsize=(7, 5), dpi=200)
|
| 184 |
+
draw(ax, past_np, fut_np, step['trajs'], step['sigma'], use_sigma)
|
| 185 |
+
plt.tight_layout()
|
| 186 |
+
fname = f'sample_{idx:04d}_{tag}_step{si}.png'
|
| 187 |
+
fig.savefig(os.path.join(OUT_DIR, fname),
|
| 188 |
+
bbox_inches='tight', pad_inches=0.02, dpi=200)
|
| 189 |
+
plt.close(fig)
|
| 190 |
+
|
| 191 |
+
print(f' Sample {idx} {tag}: {len(steps)} images')
|
| 192 |
+
|
| 193 |
+
print(f'All saved to {OUT_DIR}/')
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
if __name__ == '__main__':
|
| 197 |
+
main()
|
MID/configs/baseline.yaml
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
############ MID Config #############
|
| 2 |
+
# optimizer
|
| 3 |
+
lr: 0.001
|
| 4 |
+
# dataset
|
| 5 |
+
data_dir: processed_data
|
| 6 |
+
# the path with the directory with XXX.pkl
|
| 7 |
+
# Training Prarmeters:
|
| 8 |
+
diffnet: TransformerConcatLinear #TransformerConcatLinear
|
| 9 |
+
encoder_dim: 256
|
| 10 |
+
tf_layer: 3
|
| 11 |
+
epochs: 90
|
| 12 |
+
batch_size: 256
|
| 13 |
+
eval_batch_size: 256
|
| 14 |
+
k_eval: 25
|
| 15 |
+
seed: 123
|
| 16 |
+
eval_every: 30
|
| 17 |
+
# Testing
|
| 18 |
+
eval_at: 70
|
| 19 |
+
eval_mode: False
|
| 20 |
+
# set to "ddim" to enable fast sampling
|
| 21 |
+
sampling: ddpm
|
| 22 |
+
############### Trajectron++ Config #################
|
| 23 |
+
# misc
|
| 24 |
+
conf: None
|
| 25 |
+
debug: False
|
| 26 |
+
preprocess_workers: 0
|
| 27 |
+
# model parameters
|
| 28 |
+
offline_scene_graph: yes
|
| 29 |
+
dynamic_edges: yes
|
| 30 |
+
edge_state_combine_method: sum
|
| 31 |
+
edge_influence_combine_method: attention
|
| 32 |
+
edge_addition_filter: [0.25, 0.5, 0.75, 1.0]
|
| 33 |
+
edge_removal_filter: [1.0, 0.0]
|
| 34 |
+
override_attention_radius: []
|
| 35 |
+
incl_robot_node: False
|
| 36 |
+
map_encoding: False
|
| 37 |
+
augment: True
|
| 38 |
+
node_freq_mult_train: False
|
| 39 |
+
node_freq_mult_eval: False
|
| 40 |
+
scene_freq_mult_train: False
|
| 41 |
+
scene_freq_mult_eval: False
|
| 42 |
+
scene_freq_mult_viz: False
|
| 43 |
+
no_edge_encoding: False
|
| 44 |
+
# Data Parameters:
|
| 45 |
+
device: cuda
|
| 46 |
+
eval_device: None
|
MID/configs/baseline_sdd.yaml
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
lr: 0.001
|
| 2 |
+
data_dir: processed_data
|
| 3 |
+
diffnet: TransformerConcatLinear
|
| 4 |
+
encoder_dim: 256
|
| 5 |
+
tf_layer: 3
|
| 6 |
+
epochs: 90
|
| 7 |
+
batch_size: 256
|
| 8 |
+
eval_batch_size: 256
|
| 9 |
+
k_eval: 25
|
| 10 |
+
seed: 123
|
| 11 |
+
eval_every: 4
|
| 12 |
+
eval_at: 70
|
| 13 |
+
eval_mode: False
|
| 14 |
+
sampling: ddpm
|
| 15 |
+
conf: None
|
| 16 |
+
debug: False
|
| 17 |
+
preprocess_workers: 0
|
| 18 |
+
offline_scene_graph: yes
|
| 19 |
+
dynamic_edges: yes
|
| 20 |
+
edge_state_combine_method: sum
|
| 21 |
+
edge_influence_combine_method: attention
|
| 22 |
+
edge_addition_filter: [0.25, 0.5, 0.75, 1.0]
|
| 23 |
+
edge_removal_filter: [1.0, 0.0]
|
| 24 |
+
override_attention_radius: []
|
| 25 |
+
incl_robot_node: False
|
| 26 |
+
map_encoding: False
|
| 27 |
+
augment: True
|
| 28 |
+
node_freq_mult_train: False
|
| 29 |
+
node_freq_mult_eval: False
|
| 30 |
+
scene_freq_mult_train: False
|
| 31 |
+
scene_freq_mult_eval: False
|
| 32 |
+
scene_freq_mult_viz: False
|
| 33 |
+
no_edge_encoding: False
|
| 34 |
+
device: cuda
|
| 35 |
+
eval_device: None
|
MID/configs/baseline_sdd_eval.yaml
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
lr: 0.001
|
| 2 |
+
data_dir: processed_data
|
| 3 |
+
diffnet: TransformerConcatLinear
|
| 4 |
+
encoder_dim: 256
|
| 5 |
+
tf_layer: 3
|
| 6 |
+
epochs: 90
|
| 7 |
+
batch_size: 256
|
| 8 |
+
eval_batch_size: 256
|
| 9 |
+
k_eval: 25
|
| 10 |
+
seed: 123
|
| 11 |
+
eval_every: 4
|
| 12 |
+
eval_at: 48
|
| 13 |
+
eval_mode: True
|
| 14 |
+
sampling: ddim
|
| 15 |
+
conf: None
|
| 16 |
+
debug: False
|
| 17 |
+
preprocess_workers: 0
|
| 18 |
+
offline_scene_graph: yes
|
| 19 |
+
dynamic_edges: yes
|
| 20 |
+
edge_state_combine_method: sum
|
| 21 |
+
edge_influence_combine_method: attention
|
| 22 |
+
edge_addition_filter: [0.25, 0.5, 0.75, 1.0]
|
| 23 |
+
edge_removal_filter: [1.0, 0.0]
|
| 24 |
+
override_attention_radius: []
|
| 25 |
+
incl_robot_node: False
|
| 26 |
+
map_encoding: False
|
| 27 |
+
augment: True
|
| 28 |
+
node_freq_mult_train: False
|
| 29 |
+
node_freq_mult_eval: False
|
| 30 |
+
scene_freq_mult_train: False
|
| 31 |
+
scene_freq_mult_eval: False
|
| 32 |
+
scene_freq_mult_viz: False
|
| 33 |
+
no_edge_encoding: False
|
| 34 |
+
device: cuda
|
| 35 |
+
eval_device: None
|
MID/dataset/__init__.py
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .dataset import EnvironmentDataset, NodeTypeDataset
|
| 2 |
+
from .preprocessing import collate, get_node_timestep_data, get_timesteps_data, restore
|
MID/dataset/dataset.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from torch.utils import data
|
| 2 |
+
import numpy as np
|
| 3 |
+
from .preprocessing import get_node_timestep_data
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class EnvironmentDataset(object):
|
| 7 |
+
def __init__(self, env, state, pred_state, node_freq_mult, scene_freq_mult, hyperparams, **kwargs):
|
| 8 |
+
self.env = env
|
| 9 |
+
self.state = state
|
| 10 |
+
self.pred_state = pred_state
|
| 11 |
+
self.hyperparams = hyperparams
|
| 12 |
+
self.max_ht = self.hyperparams['maximum_history_length']
|
| 13 |
+
self.max_ft = kwargs['min_future_timesteps']
|
| 14 |
+
self.node_type_datasets = list()
|
| 15 |
+
self._augment = False
|
| 16 |
+
for node_type in env.NodeType:
|
| 17 |
+
if node_type not in hyperparams['pred_state']:
|
| 18 |
+
continue
|
| 19 |
+
self.node_type_datasets.append(NodeTypeDataset(env, node_type, state, pred_state, node_freq_mult,
|
| 20 |
+
scene_freq_mult, hyperparams, **kwargs))
|
| 21 |
+
|
| 22 |
+
@property
|
| 23 |
+
def augment(self):
|
| 24 |
+
return self._augment
|
| 25 |
+
|
| 26 |
+
@augment.setter
|
| 27 |
+
def augment(self, value):
|
| 28 |
+
self._augment = value
|
| 29 |
+
for node_type_dataset in self.node_type_datasets:
|
| 30 |
+
node_type_dataset.augment = value
|
| 31 |
+
|
| 32 |
+
def __iter__(self):
|
| 33 |
+
return iter(self.node_type_datasets)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class NodeTypeDataset(data.Dataset):
|
| 37 |
+
def __init__(self, env, node_type, state, pred_state, node_freq_mult,
|
| 38 |
+
scene_freq_mult, hyperparams, augment=False, **kwargs):
|
| 39 |
+
self.env = env
|
| 40 |
+
self.state = state
|
| 41 |
+
self.pred_state = pred_state
|
| 42 |
+
self.hyperparams = hyperparams
|
| 43 |
+
self.max_ht = self.hyperparams['maximum_history_length']
|
| 44 |
+
self.max_ft = kwargs['min_future_timesteps']
|
| 45 |
+
|
| 46 |
+
self.augment = augment
|
| 47 |
+
|
| 48 |
+
self.node_type = node_type
|
| 49 |
+
self.index = self.index_env(node_freq_mult, scene_freq_mult, **kwargs)
|
| 50 |
+
self.len = len(self.index)
|
| 51 |
+
self.edge_types = [edge_type for edge_type in env.get_edge_types() if edge_type[0] is node_type]
|
| 52 |
+
|
| 53 |
+
def index_env(self, node_freq_mult, scene_freq_mult, **kwargs):
|
| 54 |
+
index = list()
|
| 55 |
+
for scene in self.env.scenes:
|
| 56 |
+
present_node_dict = scene.present_nodes(np.arange(0, scene.timesteps), type=self.node_type, **kwargs)
|
| 57 |
+
for t, nodes in present_node_dict.items():
|
| 58 |
+
for node in nodes:
|
| 59 |
+
index += [(scene, t, node)] *\
|
| 60 |
+
(scene.frequency_multiplier if scene_freq_mult else 1) *\
|
| 61 |
+
(node.frequency_multiplier if node_freq_mult else 1)
|
| 62 |
+
|
| 63 |
+
return index
|
| 64 |
+
|
| 65 |
+
def __len__(self):
|
| 66 |
+
return self.len
|
| 67 |
+
|
| 68 |
+
def __getitem__(self, i):
|
| 69 |
+
(scene, t, node) = self.index[i]
|
| 70 |
+
|
| 71 |
+
if self.augment:
|
| 72 |
+
scene = scene.augment()
|
| 73 |
+
node = scene.get_node_by_id(node.id)
|
| 74 |
+
|
| 75 |
+
return get_node_timestep_data(self.env, scene, t, node, self.state, self.pred_state,
|
| 76 |
+
self.edge_types, self.max_ht, self.max_ft, self.hyperparams)
|
MID/dataset/homography_warper.py
ADDED
|
@@ -0,0 +1,471 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
import torch.nn.functional as F
|
| 4 |
+
from typing import Tuple, Optional
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
pi = torch.tensor(3.14159265358979323846)
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def deg2rad(tensor: torch.Tensor) -> torch.Tensor:
|
| 11 |
+
r"""Function that converts angles from degrees to radians.
|
| 12 |
+
Args:
|
| 13 |
+
tensor (torch.Tensor): Tensor of arbitrary shape.
|
| 14 |
+
Returns:
|
| 15 |
+
torch.Tensor: tensor with same shape as input.
|
| 16 |
+
"""
|
| 17 |
+
if not isinstance(tensor, torch.Tensor):
|
| 18 |
+
raise TypeError("Input type is not a torch.Tensor. Got {}".format(
|
| 19 |
+
type(tensor)))
|
| 20 |
+
|
| 21 |
+
return tensor * pi.to(tensor.device).type(tensor.dtype) / 180.
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def angle_to_rotation_matrix(angle: torch.Tensor) -> torch.Tensor:
|
| 25 |
+
"""
|
| 26 |
+
Creates a rotation matrix out of angles in degrees
|
| 27 |
+
Args:
|
| 28 |
+
angle: (torch.Tensor): tensor of angles in degrees, any shape.
|
| 29 |
+
Returns:
|
| 30 |
+
torch.Tensor: tensor of *x2x2 rotation matrices.
|
| 31 |
+
Shape:
|
| 32 |
+
- Input: :math:`(*)`
|
| 33 |
+
- Output: :math:`(*, 2, 2)`
|
| 34 |
+
Example:
|
| 35 |
+
>>> input = torch.rand(1, 3) # Nx3
|
| 36 |
+
>>> output = kornia.angle_to_rotation_matrix(input) # Nx3x2x2
|
| 37 |
+
"""
|
| 38 |
+
ang_rad = deg2rad(angle)
|
| 39 |
+
cos_a: torch.Tensor = torch.cos(ang_rad)
|
| 40 |
+
sin_a: torch.Tensor = torch.sin(ang_rad)
|
| 41 |
+
return torch.stack([cos_a, sin_a, -sin_a, cos_a], dim=-1).view(*angle.shape, 2, 2)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def get_rotation_matrix2d(
|
| 45 |
+
center: torch.Tensor,
|
| 46 |
+
angle: torch.Tensor,
|
| 47 |
+
scale: torch.Tensor) -> torch.Tensor:
|
| 48 |
+
r"""Calculates an affine matrix of 2D rotation.
|
| 49 |
+
The function calculates the following matrix:
|
| 50 |
+
.. math::
|
| 51 |
+
\begin{bmatrix}
|
| 52 |
+
\alpha & \beta & (1 - \alpha) \cdot \text{x}
|
| 53 |
+
- \beta \cdot \text{y} \\
|
| 54 |
+
-\beta & \alpha & \beta \cdot \text{x}
|
| 55 |
+
+ (1 - \alpha) \cdot \text{y}
|
| 56 |
+
\end{bmatrix}
|
| 57 |
+
where
|
| 58 |
+
.. math::
|
| 59 |
+
\alpha = \text{scale} \cdot cos(\text{radian}) \\
|
| 60 |
+
\beta = \text{scale} \cdot sin(\text{radian})
|
| 61 |
+
The transformation maps the rotation center to itself
|
| 62 |
+
If this is not the target, adjust the shift.
|
| 63 |
+
Args:
|
| 64 |
+
center (Tensor): center of the rotation in the source image.
|
| 65 |
+
angle (Tensor): rotation radian in degrees. Positive values mean
|
| 66 |
+
counter-clockwise rotation (the coordinate origin is assumed to
|
| 67 |
+
be the top-left corner).
|
| 68 |
+
scale (Tensor): isotropic scale factor.
|
| 69 |
+
Returns:
|
| 70 |
+
Tensor: the affine matrix of 2D rotation.
|
| 71 |
+
Shape:
|
| 72 |
+
- Input: :math:`(B, 2)`, :math:`(B)` and :math:`(B)`
|
| 73 |
+
- Output: :math:`(B, 2, 3)`
|
| 74 |
+
Example:
|
| 75 |
+
>>> center = torch.zeros(1, 2)
|
| 76 |
+
>>> scale = torch.ones(1)
|
| 77 |
+
>>> radian = 45. * torch.ones(1)
|
| 78 |
+
>>> M = kornia.get_rotation_matrix2d(center, radian, scale)
|
| 79 |
+
tensor([[[ 0.7071, 0.7071, 0.0000],
|
| 80 |
+
[-0.7071, 0.7071, 0.0000]]])
|
| 81 |
+
"""
|
| 82 |
+
if not torch.is_tensor(center):
|
| 83 |
+
raise TypeError("Input center type is not a torch.Tensor. Got {}"
|
| 84 |
+
.format(type(center)))
|
| 85 |
+
if not torch.is_tensor(angle):
|
| 86 |
+
raise TypeError("Input radian type is not a torch.Tensor. Got {}"
|
| 87 |
+
.format(type(angle)))
|
| 88 |
+
if not torch.is_tensor(scale):
|
| 89 |
+
raise TypeError("Input scale type is not a torch.Tensor. Got {}"
|
| 90 |
+
.format(type(scale)))
|
| 91 |
+
if not (len(center.shape) == 2 and center.shape[1] == 2):
|
| 92 |
+
raise ValueError("Input center must be a Bx2 tensor. Got {}"
|
| 93 |
+
.format(center.shape))
|
| 94 |
+
if not len(angle.shape) == 1:
|
| 95 |
+
raise ValueError("Input radian must be a B tensor. Got {}"
|
| 96 |
+
.format(angle.shape))
|
| 97 |
+
if not len(scale.shape) == 1:
|
| 98 |
+
raise ValueError("Input scale must be a B tensor. Got {}"
|
| 99 |
+
.format(scale.shape))
|
| 100 |
+
if not (center.shape[0] == angle.shape[0] == scale.shape[0]):
|
| 101 |
+
raise ValueError("Inputs must have same batch size dimension. Got {}"
|
| 102 |
+
.format(center.shape, angle.shape, scale.shape))
|
| 103 |
+
# convert radian and apply scale
|
| 104 |
+
scaled_rotation: torch.Tensor = angle_to_rotation_matrix(angle) * scale.view(-1, 1, 1)
|
| 105 |
+
alpha: torch.Tensor = scaled_rotation[:, 0, 0]
|
| 106 |
+
beta: torch.Tensor = scaled_rotation[:, 0, 1]
|
| 107 |
+
|
| 108 |
+
# unpack the center to x, y coordinates
|
| 109 |
+
x: torch.Tensor = center[..., 0]
|
| 110 |
+
y: torch.Tensor = center[..., 1]
|
| 111 |
+
|
| 112 |
+
# create output tensor
|
| 113 |
+
batch_size: int = center.shape[0]
|
| 114 |
+
M: torch.Tensor = torch.zeros(
|
| 115 |
+
batch_size, 2, 3, device=center.device, dtype=center.dtype)
|
| 116 |
+
M[..., 0:2, 0:2] = scaled_rotation
|
| 117 |
+
M[..., 0, 2] = (torch.tensor(1.) - alpha) * x - beta * y
|
| 118 |
+
M[..., 1, 2] = beta * x + (torch.tensor(1.) - alpha) * y
|
| 119 |
+
return M
|
| 120 |
+
|
| 121 |
+
def convert_points_to_homogeneous(points: torch.Tensor) -> torch.Tensor:
|
| 122 |
+
r"""Function that converts points from Euclidean to homogeneous space.
|
| 123 |
+
Examples::
|
| 124 |
+
>>> input = torch.rand(2, 4, 3) # BxNx3
|
| 125 |
+
>>> output = kornia.convert_points_to_homogeneous(input) # BxNx4
|
| 126 |
+
"""
|
| 127 |
+
if not isinstance(points, torch.Tensor):
|
| 128 |
+
raise TypeError("Input type is not a torch.Tensor. Got {}".format(
|
| 129 |
+
type(points)))
|
| 130 |
+
if len(points.shape) < 2:
|
| 131 |
+
raise ValueError("Input must be at least a 2D tensor. Got {}".format(
|
| 132 |
+
points.shape))
|
| 133 |
+
|
| 134 |
+
return torch.nn.functional.pad(points, [0, 1], "constant", 1.0)
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
def convert_points_from_homogeneous(
|
| 138 |
+
points: torch.Tensor, eps: float = 1e-8) -> torch.Tensor:
|
| 139 |
+
r"""Function that converts points from homogeneous to Euclidean space.
|
| 140 |
+
Examples::
|
| 141 |
+
>>> input = torch.rand(2, 4, 3) # BxNx3
|
| 142 |
+
>>> output = kornia.convert_points_from_homogeneous(input) # BxNx2
|
| 143 |
+
"""
|
| 144 |
+
if not isinstance(points, torch.Tensor):
|
| 145 |
+
raise TypeError("Input type is not a torch.Tensor. Got {}".format(
|
| 146 |
+
type(points)))
|
| 147 |
+
|
| 148 |
+
if len(points.shape) < 2:
|
| 149 |
+
raise ValueError("Input must be at least a 2D tensor. Got {}".format(
|
| 150 |
+
points.shape))
|
| 151 |
+
|
| 152 |
+
# we check for points at infinity
|
| 153 |
+
z_vec: torch.Tensor = points[..., -1:]
|
| 154 |
+
|
| 155 |
+
# set the results of division by zeror/near-zero to 1.0
|
| 156 |
+
# follow the convention of opencv:
|
| 157 |
+
# https://github.com/opencv/opencv/pull/14411/files
|
| 158 |
+
mask: torch.Tensor = torch.abs(z_vec) > eps
|
| 159 |
+
scale: torch.Tensor = torch.ones_like(z_vec).masked_scatter_(
|
| 160 |
+
mask, torch.tensor(1.0).to(points.device) / z_vec[mask])
|
| 161 |
+
|
| 162 |
+
return scale * points[..., :-1]
|
| 163 |
+
|
| 164 |
+
def transform_points(trans_01: torch.Tensor,
|
| 165 |
+
points_1: torch.Tensor) -> torch.Tensor:
|
| 166 |
+
r"""Function that applies transformations to a set of points.
|
| 167 |
+
Args:
|
| 168 |
+
trans_01 (torch.Tensor): tensor for transformations of shape
|
| 169 |
+
:math:`(B, D+1, D+1)`.
|
| 170 |
+
points_1 (torch.Tensor): tensor of points of shape :math:`(B, N, D)`.
|
| 171 |
+
Returns:
|
| 172 |
+
torch.Tensor: tensor of N-dimensional points.
|
| 173 |
+
Shape:
|
| 174 |
+
- Output: :math:`(B, N, D)`
|
| 175 |
+
Examples:
|
| 176 |
+
>>> points_1 = torch.rand(2, 4, 3) # BxNx3
|
| 177 |
+
>>> trans_01 = torch.eye(4).view(1, 4, 4) # Bx4x4
|
| 178 |
+
>>> points_0 = kornia.transform_points(trans_01, points_1) # BxNx3
|
| 179 |
+
"""
|
| 180 |
+
if not torch.is_tensor(trans_01) or not torch.is_tensor(points_1):
|
| 181 |
+
raise TypeError("Input type is not a torch.Tensor")
|
| 182 |
+
if not trans_01.device == points_1.device:
|
| 183 |
+
raise TypeError("Tensor must be in the same device")
|
| 184 |
+
if not trans_01.shape[0] == points_1.shape[0] and trans_01.shape[0] != 1:
|
| 185 |
+
raise ValueError("Input batch size must be the same for both tensors or 1")
|
| 186 |
+
if not trans_01.shape[-1] == (points_1.shape[-1] + 1):
|
| 187 |
+
raise ValueError("Last input dimensions must differe by one unit")
|
| 188 |
+
# to homogeneous
|
| 189 |
+
points_1_h = convert_points_to_homogeneous(points_1) # BxNxD+1
|
| 190 |
+
# transform coordinates
|
| 191 |
+
points_0_h = torch.matmul(
|
| 192 |
+
trans_01.unsqueeze(1), points_1_h.unsqueeze(-1))
|
| 193 |
+
points_0_h = torch.squeeze(points_0_h, dim=-1)
|
| 194 |
+
# to euclidean
|
| 195 |
+
points_0 = convert_points_from_homogeneous(points_0_h) # BxNxD
|
| 196 |
+
return points_0
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def multi_linspace(a, b, num, endpoint=True, device='cpu', dtype=torch.float):
|
| 200 |
+
"""This function is just like np.linspace, but will create linearly
|
| 201 |
+
spaced vectors from a start to end vector.
|
| 202 |
+
Inputs:
|
| 203 |
+
a - Start vector.
|
| 204 |
+
b - End vector.
|
| 205 |
+
num - Number of samples to generate. Default is 50. Must be above 0.
|
| 206 |
+
endpoint - If True, b is the last sample.
|
| 207 |
+
Otherwise, it is not included. Default is True.
|
| 208 |
+
"""
|
| 209 |
+
|
| 210 |
+
return a[..., None] + (b-a)[..., None]/(num-endpoint) * torch.arange(num, device=device, dtype=dtype)
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
def create_batched_meshgrid(
|
| 214 |
+
x_min: torch.Tensor,
|
| 215 |
+
y_min: torch.Tensor,
|
| 216 |
+
x_max: torch.Tensor,
|
| 217 |
+
y_max: torch.Tensor,
|
| 218 |
+
height: int,
|
| 219 |
+
width: int,
|
| 220 |
+
device: Optional[torch.device] = torch.device('cpu')) -> torch.Tensor:
|
| 221 |
+
"""Generates a coordinate grid for an image.
|
| 222 |
+
When the flag `normalized_coordinates` is set to True, the grid is
|
| 223 |
+
normalized to be in the range [-1,1] to be consistent with the pytorch
|
| 224 |
+
function grid_sample.
|
| 225 |
+
http://pytorch.org/docs/master/nn.html#torch.nn.functional.grid_sample
|
| 226 |
+
Args:
|
| 227 |
+
height (int): the image height (rows).
|
| 228 |
+
width (int): the image width (cols).
|
| 229 |
+
normalized_coordinates (Optional[bool]): whether to normalize
|
| 230 |
+
coordinates in the range [-1, 1] in order to be consistent with the
|
| 231 |
+
PyTorch function grid_sample.
|
| 232 |
+
Return:
|
| 233 |
+
torch.Tensor: returns a grid tensor with shape :math:`(1, H, W, 2)`.
|
| 234 |
+
"""
|
| 235 |
+
# generate coordinates
|
| 236 |
+
xs = multi_linspace(x_min, x_max, width, device=device, dtype=torch.float)
|
| 237 |
+
ys = multi_linspace(y_min, y_max, height, device=device, dtype=torch.float)
|
| 238 |
+
|
| 239 |
+
# generate grid by stacking coordinates
|
| 240 |
+
bs = x_min.shape[0]
|
| 241 |
+
batched_grid_i_list = list()
|
| 242 |
+
for i in range(bs):
|
| 243 |
+
batched_grid_i_list.append(torch.stack(torch.meshgrid([xs[i], ys[i]])).transpose(1, 2)) # 2xHxW
|
| 244 |
+
batched_grid: torch.Tensor = torch.stack(batched_grid_i_list, dim=0)
|
| 245 |
+
return batched_grid.permute(0, 2, 3, 1) # BxHxWx2
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
def homography_warp(patch_src: torch.Tensor,
|
| 249 |
+
centers: torch.Tensor,
|
| 250 |
+
dst_homo_src: torch.Tensor,
|
| 251 |
+
dsize: Tuple[int, int],
|
| 252 |
+
mode: str = 'bilinear',
|
| 253 |
+
padding_mode: str = 'zeros') -> torch.Tensor:
|
| 254 |
+
r"""Function that warps image patchs or tensors by homographies.
|
| 255 |
+
See :class:`~kornia.geometry.warp.HomographyWarper` for details.
|
| 256 |
+
Args:
|
| 257 |
+
patch_src (torch.Tensor): The image or tensor to warp. Should be from
|
| 258 |
+
source of shape :math:`(N, C, H, W)`.
|
| 259 |
+
dst_homo_src (torch.Tensor): The homography or stack of homographies
|
| 260 |
+
from source to destination of shape
|
| 261 |
+
:math:`(N, 3, 3)`.
|
| 262 |
+
dsize (Tuple[int, int]): The height and width of the image to warp.
|
| 263 |
+
mode (str): interpolation mode to calculate output values
|
| 264 |
+
'bilinear' | 'nearest'. Default: 'bilinear'.
|
| 265 |
+
padding_mode (str): padding mode for outside grid values
|
| 266 |
+
'zeros' | 'border' | 'reflection'. Default: 'zeros'.
|
| 267 |
+
Return:
|
| 268 |
+
torch.Tensor: Patch sampled at locations from source to destination.
|
| 269 |
+
Example:
|
| 270 |
+
>>> input = torch.rand(1, 3, 32, 32)
|
| 271 |
+
>>> homography = torch.eye(3).view(1, 3, 3)
|
| 272 |
+
>>> output = kornia.homography_warp(input, homography, (32, 32))
|
| 273 |
+
"""
|
| 274 |
+
|
| 275 |
+
out_height, out_width = dsize
|
| 276 |
+
image_height, image_width = patch_src.shape[-2:]
|
| 277 |
+
x_min = 2. * (centers[..., 0] - out_width/2) / image_width - 1.
|
| 278 |
+
y_min = 2. * (centers[..., 1] - out_height/2) / image_height - 1.
|
| 279 |
+
x_max = 2. * (centers[..., 0] + out_width/2) / image_width - 1.
|
| 280 |
+
y_max = 2. * (centers[..., 1] + out_height/2) / image_height - 1.
|
| 281 |
+
warper = HomographyWarper(x_min, y_min, x_max, y_max, out_height, out_width, mode, padding_mode)
|
| 282 |
+
return warper(patch_src, dst_homo_src)
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
def normal_transform_pixel(height, width):
|
| 286 |
+
|
| 287 |
+
tr_mat = torch.Tensor([[1.0, 0.0, -1.0],
|
| 288 |
+
[0.0, 1.0, -1.0],
|
| 289 |
+
[0.0, 0.0, 1.0]]) # 1x3x3
|
| 290 |
+
|
| 291 |
+
tr_mat[0, 0] = tr_mat[0, 0] * 2.0 / (width - 1.0)
|
| 292 |
+
tr_mat[1, 1] = tr_mat[1, 1] * 2.0 / (height - 1.0)
|
| 293 |
+
|
| 294 |
+
tr_mat = tr_mat.unsqueeze(0)
|
| 295 |
+
|
| 296 |
+
return tr_mat
|
| 297 |
+
|
| 298 |
+
|
| 299 |
+
def src_norm_to_dst_norm(dst_pix_trans_src_pix: torch.Tensor,
|
| 300 |
+
dsize_src: Tuple[int, int], dsize_dst: Tuple[int, int]) -> torch.Tensor:
|
| 301 |
+
# source and destination sizes
|
| 302 |
+
src_h, src_w = dsize_src
|
| 303 |
+
dst_h, dst_w = dsize_dst
|
| 304 |
+
# the devices and types
|
| 305 |
+
device: torch.device = dst_pix_trans_src_pix.device
|
| 306 |
+
dtype: torch.dtype = dst_pix_trans_src_pix.dtype
|
| 307 |
+
# compute the transformation pixel/norm for src/dst
|
| 308 |
+
src_norm_trans_src_pix: torch.Tensor = normal_transform_pixel(
|
| 309 |
+
src_h, src_w).to(device, dtype)
|
| 310 |
+
src_pix_trans_src_norm = torch.inverse(src_norm_trans_src_pix)
|
| 311 |
+
dst_norm_trans_dst_pix: torch.Tensor = normal_transform_pixel(
|
| 312 |
+
dst_h, dst_w).to(device, dtype)
|
| 313 |
+
# compute chain transformations
|
| 314 |
+
dst_norm_trans_src_norm: torch.Tensor = (
|
| 315 |
+
dst_norm_trans_dst_pix @ (dst_pix_trans_src_pix @ src_pix_trans_src_norm)
|
| 316 |
+
)
|
| 317 |
+
return dst_norm_trans_src_norm
|
| 318 |
+
|
| 319 |
+
|
| 320 |
+
def transform_warp_impl(src: torch.Tensor, centers: torch.Tensor, dst_pix_trans_src_pix: torch.Tensor,
|
| 321 |
+
dsize_src: Tuple[int, int], dsize_dst: Tuple[int, int],
|
| 322 |
+
grid_mode: str, padding_mode: str) -> torch.Tensor:
|
| 323 |
+
"""Compute the transform in normalized cooridnates and perform the warping.
|
| 324 |
+
"""
|
| 325 |
+
dst_norm_trans_src_norm: torch.Tensor = src_norm_to_dst_norm(
|
| 326 |
+
dst_pix_trans_src_pix, dsize_src, dsize_src)
|
| 327 |
+
|
| 328 |
+
src_norm_trans_dst_norm = torch.inverse(dst_norm_trans_src_norm)
|
| 329 |
+
return homography_warp(src, centers, src_norm_trans_dst_norm, dsize_dst, grid_mode, padding_mode)
|
| 330 |
+
|
| 331 |
+
|
| 332 |
+
class HomographyWarper(nn.Module):
|
| 333 |
+
r"""Warps image patches or tensors by homographies.
|
| 334 |
+
.. math::
|
| 335 |
+
X_{dst} = H_{src}^{\{dst\}} * X_{src}
|
| 336 |
+
Args:
|
| 337 |
+
height (int): The height of the image to warp.
|
| 338 |
+
width (int): The width of the image to warp.
|
| 339 |
+
mode (str): interpolation mode to calculate output values
|
| 340 |
+
'bilinear' | 'nearest'. Default: 'bilinear'.
|
| 341 |
+
padding_mode (str): padding mode for outside grid values
|
| 342 |
+
'zeros' | 'border' | 'reflection'. Default: 'zeros'.
|
| 343 |
+
"""
|
| 344 |
+
|
| 345 |
+
def __init__(
|
| 346 |
+
self,
|
| 347 |
+
x_min: torch.Tensor,
|
| 348 |
+
y_min: torch.Tensor,
|
| 349 |
+
x_max: torch.Tensor,
|
| 350 |
+
y_max: torch.Tensor,
|
| 351 |
+
height: int,
|
| 352 |
+
width: int,
|
| 353 |
+
mode: str = 'bilinear',
|
| 354 |
+
padding_mode: str = 'zeros') -> None:
|
| 355 |
+
super(HomographyWarper, self).__init__()
|
| 356 |
+
self.width: int = width
|
| 357 |
+
self.height: int = height
|
| 358 |
+
self.mode: str = mode
|
| 359 |
+
self.padding_mode: str = padding_mode
|
| 360 |
+
|
| 361 |
+
# create base grid to compute the flow
|
| 362 |
+
self.grid: torch.Tensor = create_batched_meshgrid(x_min, y_min, x_max, y_max, height, width)
|
| 363 |
+
|
| 364 |
+
def warp_grid(self, dst_homo_src: torch.Tensor) -> torch.Tensor:
|
| 365 |
+
r"""Computes the grid to warp the coordinates grid by an homography.
|
| 366 |
+
Args:
|
| 367 |
+
dst_homo_src (torch.Tensor): Homography or homographies (stacked) to
|
| 368 |
+
transform all points in the grid. Shape of the
|
| 369 |
+
homography has to be :math:`(N, 3, 3)`.
|
| 370 |
+
Returns:
|
| 371 |
+
torch.Tensor: the transformed grid of shape :math:`(N, H, W, 2)`.
|
| 372 |
+
"""
|
| 373 |
+
batch_size: int = dst_homo_src.shape[0]
|
| 374 |
+
device: torch.device = dst_homo_src.device
|
| 375 |
+
dtype: torch.dtype = dst_homo_src.dtype
|
| 376 |
+
# expand grid to match the input batch size
|
| 377 |
+
grid: torch.Tensor = self.grid
|
| 378 |
+
if len(dst_homo_src.shape) == 3: # local homography case
|
| 379 |
+
dst_homo_src = dst_homo_src.view(batch_size, 1, 3, 3) # NxHxWx3x3
|
| 380 |
+
# perform the actual grid transformation,
|
| 381 |
+
# the grid is copied to input device and casted to the same type
|
| 382 |
+
flow: torch.Tensor = transform_points(
|
| 383 |
+
dst_homo_src, grid.to(device).to(dtype)) # NxHxWx2
|
| 384 |
+
return flow.view(batch_size, self.height, self.width, 2) # NxHxWx2
|
| 385 |
+
|
| 386 |
+
def forward( # type: ignore
|
| 387 |
+
self,
|
| 388 |
+
patch_src: torch.Tensor,
|
| 389 |
+
dst_homo_src: torch.Tensor) -> torch.Tensor:
|
| 390 |
+
r"""Warps an image or tensor from source into reference frame.
|
| 391 |
+
Args:
|
| 392 |
+
patch_src (torch.Tensor): The image or tensor to warp.
|
| 393 |
+
Should be from source.
|
| 394 |
+
dst_homo_src (torch.Tensor): The homography or stack of homographies
|
| 395 |
+
from source to destination. The homography assumes normalized
|
| 396 |
+
coordinates [-1, 1].
|
| 397 |
+
Return:
|
| 398 |
+
torch.Tensor: Patch sampled at locations from source to destination.
|
| 399 |
+
Shape:
|
| 400 |
+
- Input: :math:`(N, C, H, W)` and :math:`(N, 3, 3)`
|
| 401 |
+
- Output: :math:`(N, C, H, W)`
|
| 402 |
+
Example:
|
| 403 |
+
>>> input = torch.rand(1, 3, 32, 32)
|
| 404 |
+
>>> homography = torch.eye(3).view(1, 3, 3)
|
| 405 |
+
>>> warper = kornia.HomographyWarper(32, 32)
|
| 406 |
+
>>> output = warper(input, homography) # NxCxHxW
|
| 407 |
+
"""
|
| 408 |
+
if not dst_homo_src.device == patch_src.device:
|
| 409 |
+
raise TypeError("Patch and homography must be on the same device. \
|
| 410 |
+
Got patch.device: {} dst_H_src.device: {}."
|
| 411 |
+
.format(patch_src.device, dst_homo_src.device))
|
| 412 |
+
|
| 413 |
+
return F.grid_sample(patch_src, self.warp_grid(dst_homo_src), # type: ignore
|
| 414 |
+
mode=self.mode, padding_mode=self.padding_mode, align_corners=True)
|
| 415 |
+
|
| 416 |
+
|
| 417 |
+
def warp_affine_crop(src: torch.Tensor, centers: torch.Tensor, M: torch.Tensor,
|
| 418 |
+
dsize: Tuple[int, int], flags: str = 'bilinear',
|
| 419 |
+
padding_mode: str = 'zeros') -> torch.Tensor:
|
| 420 |
+
r"""Applies an affine transformation to a tensor.
|
| 421 |
+
|
| 422 |
+
The function warp_affine transforms the source tensor using
|
| 423 |
+
the specified matrix:
|
| 424 |
+
|
| 425 |
+
.. math::
|
| 426 |
+
\text{dst}(x, y) = \text{src} \left( M_{11} x + M_{12} y + M_{13} ,
|
| 427 |
+
M_{21} x + M_{22} y + M_{23} \right )
|
| 428 |
+
|
| 429 |
+
Args:
|
| 430 |
+
src (torch.Tensor): input tensor of shape :math:`(B, C, H, W)`.
|
| 431 |
+
M (torch.Tensor): affine transformation of shape :math:`(B, 2, 3)`.
|
| 432 |
+
dsize (Tuple[int, int]): size of the output image (height, width).
|
| 433 |
+
mode (str): interpolation mode to calculate output values
|
| 434 |
+
'bilinear' | 'nearest'. Default: 'bilinear'.
|
| 435 |
+
padding_mode (str): padding mode for outside grid values
|
| 436 |
+
'zeros' | 'border' | 'reflection'. Default: 'zeros'.
|
| 437 |
+
|
| 438 |
+
Returns:
|
| 439 |
+
torch.Tensor: the warped tensor.
|
| 440 |
+
|
| 441 |
+
Shape:
|
| 442 |
+
- Output: :math:`(B, C, H, W)`
|
| 443 |
+
|
| 444 |
+
.. note::
|
| 445 |
+
See a working example `here <https://kornia.readthedocs.io/en/latest/
|
| 446 |
+
tutorials/warp_affine.html>`__.
|
| 447 |
+
"""
|
| 448 |
+
if not torch.is_tensor(src):
|
| 449 |
+
raise TypeError("Input src type is not a torch.Tensor. Got {}"
|
| 450 |
+
.format(type(src)))
|
| 451 |
+
|
| 452 |
+
if not torch.is_tensor(M):
|
| 453 |
+
raise TypeError("Input M type is not a torch.Tensor. Got {}"
|
| 454 |
+
.format(type(M)))
|
| 455 |
+
|
| 456 |
+
if not len(src.shape) == 4:
|
| 457 |
+
raise ValueError("Input src must be a BxCxHxW tensor. Got {}"
|
| 458 |
+
.format(src.shape))
|
| 459 |
+
|
| 460 |
+
if not (len(M.shape) == 3 or M.shape[-2:] == (2, 3)):
|
| 461 |
+
raise ValueError("Input M must be a Bx2x3 tensor. Got {}"
|
| 462 |
+
.format(src.shape))
|
| 463 |
+
|
| 464 |
+
# we generate a 3x3 transformation matrix from 2x3 affine
|
| 465 |
+
M_3x3: torch.Tensor = F.pad(M, [0, 0, 0, 1, 0, 0],
|
| 466 |
+
mode="constant", value=0)
|
| 467 |
+
M_3x3[:, 2, 2] += 1.0
|
| 468 |
+
|
| 469 |
+
# launches the warper
|
| 470 |
+
h, w = src.shape[-2:]
|
| 471 |
+
return transform_warp_impl(src, centers, M_3x3, (h, w), dsize, flags, padding_mode)
|
MID/dataset/preprocessing.py
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import numpy as np
|
| 3 |
+
import collections.abc
|
| 4 |
+
from torch.utils.data._utils.collate import default_collate
|
| 5 |
+
import dill
|
| 6 |
+
container_abcs = collections.abc
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def restore(data):
|
| 10 |
+
"""
|
| 11 |
+
In case we dilled some structures to share between multiple process this function will restore them.
|
| 12 |
+
If the data input are not bytes we assume it was not dilled in the first place
|
| 13 |
+
|
| 14 |
+
:param data: Possibly dilled data structure
|
| 15 |
+
:return: Un-dilled data structure
|
| 16 |
+
"""
|
| 17 |
+
if type(data) is bytes:
|
| 18 |
+
return dill.loads(data)
|
| 19 |
+
return data
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def collate(batch):
|
| 23 |
+
if len(batch) == 0:
|
| 24 |
+
return batch
|
| 25 |
+
elem = batch[0]
|
| 26 |
+
if elem is None:
|
| 27 |
+
return None
|
| 28 |
+
elif isinstance(elem, container_abcs.Sequence):
|
| 29 |
+
if len(elem) == 4: # We assume those are the maps, map points, headings and patch_size
|
| 30 |
+
scene_map, scene_pts, heading_angle, patch_size = zip(*batch)
|
| 31 |
+
if heading_angle[0] is None:
|
| 32 |
+
heading_angle = None
|
| 33 |
+
else:
|
| 34 |
+
heading_angle = torch.Tensor(heading_angle)
|
| 35 |
+
map = scene_map[0].get_cropped_maps_from_scene_map_batch(scene_map,
|
| 36 |
+
scene_pts=torch.Tensor(scene_pts),
|
| 37 |
+
patch_size=patch_size[0],
|
| 38 |
+
rotation=heading_angle)
|
| 39 |
+
return map
|
| 40 |
+
transposed = zip(*batch)
|
| 41 |
+
return [collate(samples) for samples in transposed]
|
| 42 |
+
elif isinstance(elem, container_abcs.Mapping):
|
| 43 |
+
# We have to dill the neighbors structures. Otherwise each tensor is put into
|
| 44 |
+
# shared memory separately -> slow, file pointer overhead
|
| 45 |
+
# we only do this in multiprocessing
|
| 46 |
+
neighbor_dict = {key: [d[key] for d in batch] for key in elem}
|
| 47 |
+
return dill.dumps(neighbor_dict) if torch.utils.data.get_worker_info() else neighbor_dict
|
| 48 |
+
return default_collate(batch)
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def get_relative_robot_traj(env, state, node_traj, robot_traj, node_type, robot_type):
|
| 52 |
+
# TODO: We will have to make this more generic if robot_type != node_type
|
| 53 |
+
# Make Robot State relative to node
|
| 54 |
+
_, std = env.get_standardize_params(state[robot_type], node_type=robot_type)
|
| 55 |
+
std[0:2] = env.attention_radius[(node_type, robot_type)]
|
| 56 |
+
robot_traj_st = env.standardize(robot_traj,
|
| 57 |
+
state[robot_type],
|
| 58 |
+
node_type=robot_type,
|
| 59 |
+
mean=node_traj,
|
| 60 |
+
std=std)
|
| 61 |
+
robot_traj_st_t = torch.tensor(robot_traj_st, dtype=torch.float)
|
| 62 |
+
|
| 63 |
+
return robot_traj_st_t
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def get_node_timestep_data(env, scene, t, node, state, pred_state,
|
| 67 |
+
edge_types, max_ht, max_ft, hyperparams,
|
| 68 |
+
scene_graph=None):
|
| 69 |
+
"""
|
| 70 |
+
Pre-processes the data for a single batch element: node state over time for a specific time in a specific scene
|
| 71 |
+
as well as the neighbour data for it.
|
| 72 |
+
|
| 73 |
+
:param env: Environment
|
| 74 |
+
:param scene: Scene
|
| 75 |
+
:param t: Timestep in scene
|
| 76 |
+
:param node: Node
|
| 77 |
+
:param state: Specification of the node state
|
| 78 |
+
:param pred_state: Specification of the prediction state
|
| 79 |
+
:param edge_types: List of all Edge Types for which neighbours are pre-processed
|
| 80 |
+
:param max_ht: Maximum history timesteps
|
| 81 |
+
:param max_ft: Maximum future timesteps (prediction horizon)
|
| 82 |
+
:param hyperparams: Model hyperparameters
|
| 83 |
+
:param scene_graph: If scene graph was already computed for this scene and time you can pass it here
|
| 84 |
+
:return: Batch Element
|
| 85 |
+
"""
|
| 86 |
+
|
| 87 |
+
# Node
|
| 88 |
+
timestep_range_x = np.array([t - max_ht, t])
|
| 89 |
+
timestep_range_y = np.array([t + 1, t + max_ft])
|
| 90 |
+
|
| 91 |
+
x = node.get(timestep_range_x, state[node.type])
|
| 92 |
+
y = node.get(timestep_range_y, pred_state[node.type])
|
| 93 |
+
first_history_index = (max_ht - node.history_points_at(t)).clip(0)
|
| 94 |
+
|
| 95 |
+
_, std = env.get_standardize_params(state[node.type], node.type)
|
| 96 |
+
std[0:2] = env.attention_radius[(node.type, node.type)]
|
| 97 |
+
rel_state = np.zeros_like(x[0])
|
| 98 |
+
rel_state[0:2] = np.array(x)[-1, 0:2]
|
| 99 |
+
x_st = env.standardize(x, state[node.type], node.type, mean=rel_state, std=std)
|
| 100 |
+
if list(pred_state[node.type].keys())[0] == 'position': # If we predict position we do it relative to current pos
|
| 101 |
+
y_st = env.standardize(y, pred_state[node.type], node.type, mean=rel_state[0:2])
|
| 102 |
+
else:
|
| 103 |
+
y_st = env.standardize(y, pred_state[node.type], node.type)
|
| 104 |
+
|
| 105 |
+
x_t = torch.tensor(x, dtype=torch.float)
|
| 106 |
+
y_t = torch.tensor(y, dtype=torch.float)
|
| 107 |
+
x_st_t = torch.tensor(x_st, dtype=torch.float)
|
| 108 |
+
y_st_t = torch.tensor(y_st, dtype=torch.float)
|
| 109 |
+
|
| 110 |
+
# Neighbors
|
| 111 |
+
neighbors_data_st = None
|
| 112 |
+
neighbors_edge_value = None
|
| 113 |
+
if hyperparams['edge_encoding']:
|
| 114 |
+
# Scene Graph
|
| 115 |
+
scene_graph = scene.get_scene_graph(t,
|
| 116 |
+
env.attention_radius,
|
| 117 |
+
hyperparams['edge_addition_filter'],
|
| 118 |
+
hyperparams['edge_removal_filter']) if scene_graph is None else scene_graph
|
| 119 |
+
|
| 120 |
+
neighbors_data_st = dict()
|
| 121 |
+
neighbors_edge_value = dict()
|
| 122 |
+
for edge_type in edge_types:
|
| 123 |
+
neighbors_data_st[edge_type] = list()
|
| 124 |
+
# We get all nodes which are connected to the current node for the current timestep
|
| 125 |
+
connected_nodes = scene_graph.get_neighbors(node, edge_type[1])
|
| 126 |
+
|
| 127 |
+
if hyperparams['dynamic_edges'] == 'yes':
|
| 128 |
+
# We get the edge masks for the current node at the current timestep
|
| 129 |
+
edge_masks = torch.tensor(scene_graph.get_edge_scaling(node), dtype=torch.float)
|
| 130 |
+
neighbors_edge_value[edge_type] = edge_masks
|
| 131 |
+
|
| 132 |
+
for connected_node in connected_nodes:
|
| 133 |
+
neighbor_state_np = connected_node.get(np.array([t - max_ht, t]),
|
| 134 |
+
state[connected_node.type],
|
| 135 |
+
padding=0.0)
|
| 136 |
+
|
| 137 |
+
# Make State relative to node where neighbor and node have same state
|
| 138 |
+
_, std = env.get_standardize_params(state[connected_node.type], node_type=connected_node.type)
|
| 139 |
+
std[0:2] = env.attention_radius[edge_type]
|
| 140 |
+
equal_dims = np.min((neighbor_state_np.shape[-1], x.shape[-1]))
|
| 141 |
+
rel_state = np.zeros_like(neighbor_state_np)
|
| 142 |
+
rel_state[:, ..., :equal_dims] = x[-1, ..., :equal_dims]
|
| 143 |
+
neighbor_state_np_st = env.standardize(neighbor_state_np,
|
| 144 |
+
state[connected_node.type],
|
| 145 |
+
node_type=connected_node.type,
|
| 146 |
+
mean=rel_state,
|
| 147 |
+
std=std)
|
| 148 |
+
|
| 149 |
+
neighbor_state = torch.tensor(neighbor_state_np_st, dtype=torch.float)
|
| 150 |
+
neighbors_data_st[edge_type].append(neighbor_state)
|
| 151 |
+
|
| 152 |
+
# Robot
|
| 153 |
+
robot_traj_st_t = None
|
| 154 |
+
timestep_range_r = np.array([t, t + max_ft])
|
| 155 |
+
if hyperparams['incl_robot_node']:
|
| 156 |
+
x_node = node.get(timestep_range_r, state[node.type])
|
| 157 |
+
if scene.non_aug_scene is not None:
|
| 158 |
+
robot = scene.get_node_by_id(scene.non_aug_scene.robot.id)
|
| 159 |
+
else:
|
| 160 |
+
robot = scene.robot
|
| 161 |
+
robot_type = robot.type
|
| 162 |
+
robot_traj = robot.get(timestep_range_r, state[robot_type], padding=0.0)
|
| 163 |
+
robot_traj_st_t = get_relative_robot_traj(env, state, x_node, robot_traj, node.type, robot_type)
|
| 164 |
+
|
| 165 |
+
# Map
|
| 166 |
+
map_tuple = None
|
| 167 |
+
if hyperparams['use_map_encoding']:
|
| 168 |
+
if node.type in hyperparams['map_encoder']:
|
| 169 |
+
if node.non_aug_node is not None:
|
| 170 |
+
x = node.non_aug_node.get(np.array([t]), state[node.type])
|
| 171 |
+
me_hyp = hyperparams['map_encoder'][node.type]
|
| 172 |
+
if 'heading_state_index' in me_hyp:
|
| 173 |
+
heading_state_index = me_hyp['heading_state_index']
|
| 174 |
+
# We have to rotate the map in the opposit direction of the agent to match them
|
| 175 |
+
if type(heading_state_index) is list: # infer from velocity or heading vector
|
| 176 |
+
heading_angle = -np.arctan2(x[-1, heading_state_index[1]],
|
| 177 |
+
x[-1, heading_state_index[0]]) * 180 / np.pi
|
| 178 |
+
else:
|
| 179 |
+
heading_angle = -x[-1, heading_state_index] * 180 / np.pi
|
| 180 |
+
else:
|
| 181 |
+
heading_angle = None
|
| 182 |
+
|
| 183 |
+
scene_map = scene.map[node.type]
|
| 184 |
+
map_point = x[-1, :2]
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
patch_size = hyperparams['map_encoder'][node.type]['patch_size']
|
| 188 |
+
map_tuple = (scene_map, map_point, heading_angle, patch_size)
|
| 189 |
+
|
| 190 |
+
return (first_history_index, x_t, y_t, x_st_t, y_st_t, neighbors_data_st,
|
| 191 |
+
neighbors_edge_value, robot_traj_st_t, map_tuple)
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
def get_timesteps_data(env, scene, t, node_type, state, pred_state,
|
| 195 |
+
edge_types, min_ht, max_ht, min_ft, max_ft, hyperparams):
|
| 196 |
+
"""
|
| 197 |
+
Puts together the inputs for ALL nodes in a given scene and timestep in it.
|
| 198 |
+
|
| 199 |
+
:param env: Environment
|
| 200 |
+
:param scene: Scene
|
| 201 |
+
:param t: Timestep in scene
|
| 202 |
+
:param node_type: Node Type of nodes for which the data shall be pre-processed
|
| 203 |
+
:param state: Specification of the node state
|
| 204 |
+
:param pred_state: Specification of the prediction state
|
| 205 |
+
:param edge_types: List of all Edge Types for which neighbors are pre-processed
|
| 206 |
+
:param max_ht: Maximum history timesteps
|
| 207 |
+
:param max_ft: Maximum future timesteps (prediction horizon)
|
| 208 |
+
:param hyperparams: Model hyperparameters
|
| 209 |
+
:return:
|
| 210 |
+
"""
|
| 211 |
+
nodes_per_ts = scene.present_nodes(t,
|
| 212 |
+
type=node_type,
|
| 213 |
+
min_history_timesteps=min_ht,
|
| 214 |
+
min_future_timesteps=max_ft,
|
| 215 |
+
return_robot=not hyperparams['incl_robot_node'])
|
| 216 |
+
batch = list()
|
| 217 |
+
nodes = list()
|
| 218 |
+
out_timesteps = list()
|
| 219 |
+
for timestep in nodes_per_ts.keys():
|
| 220 |
+
scene_graph = scene.get_scene_graph(timestep,
|
| 221 |
+
env.attention_radius,
|
| 222 |
+
hyperparams['edge_addition_filter'],
|
| 223 |
+
hyperparams['edge_removal_filter'])
|
| 224 |
+
present_nodes = nodes_per_ts[timestep]
|
| 225 |
+
for node in present_nodes:
|
| 226 |
+
nodes.append(node)
|
| 227 |
+
out_timesteps.append(timestep)
|
| 228 |
+
batch.append(get_node_timestep_data(env, scene, timestep, node, state, pred_state,
|
| 229 |
+
edge_types, max_ht, max_ft, hyperparams,
|
| 230 |
+
scene_graph=scene_graph))
|
| 231 |
+
if len(out_timesteps) == 0:
|
| 232 |
+
return None
|
| 233 |
+
return collate(batch), nodes, out_timesteps
|