po03087 commited on
Commit
d4cbafd
·
verified ·
1 Parent(s): 0c95050

SRA: MID/LED/MoFlow code + RUNNING.md instructions (code only, no data/ckpts)

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. LED/LED/cfg/nba/led_augment.yml +38 -0
  2. LED/LED/cfg/nba/led_augment_debug.yml +38 -0
  3. LED/LED/main_led_nba.py +31 -0
  4. LED/LED/models/layers.py +169 -0
  5. LED/LED/models/model_diffusion.py +124 -0
  6. LED/LED/models/model_led_initializer.py +63 -0
  7. LED/LED/requirements.txt +84 -0
  8. LED/LED/trainer/train_led_trajectory_augment_input.py +443 -0
  9. LED/LED/utils/config.py +60 -0
  10. LED/LED/utils/utils.py +336 -0
  11. LED/cfg/nba/led_augment.yml +39 -0
  12. LED/cfg/nba/led_augment_debug.yml +38 -0
  13. LED/cfg/sdd/sdd.yml +34 -0
  14. LED/cfg/sport/football.yml +53 -0
  15. LED/cfg/sport/soccer.yml +56 -0
  16. LED/eval_sdd_led_allagents.py +193 -0
  17. LED/eval_sdd_led_mid_protocol.py +169 -0
  18. LED/main_led_nba.py +31 -0
  19. LED/main_led_nba_graph.py +53 -0
  20. LED/main_led_nba_grpo.py +360 -0
  21. LED/main_sdd_led.py +21 -0
  22. LED/main_sdd_pretrain.py +17 -0
  23. LED/main_sport_led.py +33 -0
  24. LED/main_sport_pretrain.py +25 -0
  25. LED/models/future_interaction_graph.py +155 -0
  26. LED/models/future_interaction_graph_v6.py +173 -0
  27. LED/models/interaction_baselines.py +246 -0
  28. LED/models/layers.py +169 -0
  29. LED/models/model_diffusion.py +125 -0
  30. LED/models/model_led_initializer.py +63 -0
  31. LED/requirements.txt +84 -0
  32. LED/trainer/train_led_graph.py +404 -0
  33. LED/trainer/train_led_trajectory_augment_input.py +443 -0
  34. LED/trainer/train_sdd_led.py +239 -0
  35. LED/trainer/train_sdd_pretrain.py +137 -0
  36. LED/trainer/train_sport_led.py +352 -0
  37. LED/trainer/train_sport_pretrain.py +210 -0
  38. LED/utils/config.py +60 -0
  39. LED/utils/utils.py +336 -0
  40. LED/viz_denoising_process.py +329 -0
  41. LED/viz_denoising_steps.py +299 -0
  42. LED/viz_uncertainty_denoising.py +371 -0
  43. LED/viz_uncertainty_individual.py +197 -0
  44. MID/configs/baseline.yaml +46 -0
  45. MID/configs/baseline_sdd.yaml +35 -0
  46. MID/configs/baseline_sdd_eval.yaml +35 -0
  47. MID/dataset/__init__.py +2 -0
  48. MID/dataset/dataset.py +76 -0
  49. MID/dataset/homography_warper.py +471 -0
  50. 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