283 lines
9.8 KiB
Python
283 lines
9.8 KiB
Python
|
|
import torch
|
||
|
|
import torch.nn as nn
|
||
|
|
import torch.nn.functional as F
|
||
|
|
from torch.distributions.normal import Normal
|
||
|
|
|
||
|
|
|
||
|
|
class GCN(nn.Module):
|
||
|
|
def __init__(self, dim, n_heads):
|
||
|
|
super().__init__()
|
||
|
|
self.proj = nn.Linear(dim, dim)
|
||
|
|
self.n_heads = n_heads
|
||
|
|
|
||
|
|
def forward(self, adj, x):
|
||
|
|
# adj [B, H, L, L]
|
||
|
|
B, L, D = x.shape
|
||
|
|
x = self.proj(x).view(B, L, self.n_heads, -1) # [B, L, H, D_]
|
||
|
|
adj = F.normalize(adj, p=1, dim=-1)
|
||
|
|
x = torch.einsum("bhij,bjhd->bihd", adj, x).contiguous() # [B, L, H, D_]
|
||
|
|
x = x.view(B, L, -1)
|
||
|
|
return x
|
||
|
|
|
||
|
|
|
||
|
|
###############################
|
||
|
|
# Ablation
|
||
|
|
###############################
|
||
|
|
def mask_topk_moe(adj, thre, n_vars, masks):
|
||
|
|
# adj: [B, H, L, L], thre: [B, H, L, 3]
|
||
|
|
if masks is None:
|
||
|
|
B, H, L, _ = adj.shape
|
||
|
|
N = L // n_vars
|
||
|
|
device = adj.device
|
||
|
|
dtype = torch.float32
|
||
|
|
print("Masks is None!")
|
||
|
|
masks = []
|
||
|
|
for k in range(L):
|
||
|
|
S = ((torch.arange(L) % N == k % N) & (torch.arange(L) != k)).to(dtype).to(device)
|
||
|
|
T = ((torch.arange(L) >= k // N * N) & (torch.arange(L) < k // N * N + N)).to(dtype).to(device)
|
||
|
|
ST = torch.ones(L).to(dtype).to(device) - S - T
|
||
|
|
masks.append(torch.stack([S, T, ST], dim=0))
|
||
|
|
# [L, 3, L]
|
||
|
|
masks = torch.stack(masks, dim=0)
|
||
|
|
|
||
|
|
adj_mask0 = adj * masks[:, 0, :]
|
||
|
|
adj_mask1 = adj * masks[:, 1, :]
|
||
|
|
adj_mask2 = adj * masks[:, 2, :]
|
||
|
|
|
||
|
|
adj_mask0[adj_mask0 <= thre[:, :, :, 0].unsqueeze(-1)] = 0
|
||
|
|
adj_mask1[adj_mask1 <= thre[:, :, :, 1].unsqueeze(-1)] = 0
|
||
|
|
adj_mask2[adj_mask2 <= thre[:, :, :, 2].unsqueeze(-1)] = 0
|
||
|
|
|
||
|
|
adj = adj_mask0 + adj_mask1 + adj_mask2
|
||
|
|
|
||
|
|
return adj
|
||
|
|
|
||
|
|
|
||
|
|
def mask_topk_area(adj, n_vars, masks, alpha=0.5):
|
||
|
|
# x: [B, H, L, L]
|
||
|
|
B, H, L, _ = adj.shape
|
||
|
|
N = L // n_vars
|
||
|
|
if masks is None:
|
||
|
|
device = adj.device
|
||
|
|
dtype = torch.float32
|
||
|
|
print("Masks is None!")
|
||
|
|
masks = []
|
||
|
|
for k in range(L):
|
||
|
|
S = ((torch.arange(L) % N == k % N) & (torch.arange(L) != k)).to(dtype).to(device)
|
||
|
|
T = ((torch.arange(L) >= k // N * N) & (torch.arange(L) < k // N * N + N)).to(dtype).to(device)
|
||
|
|
ST = torch.ones(L).to(dtype).to(device) - S - T
|
||
|
|
masks.append(torch.stack([S, T, ST], dim=0))
|
||
|
|
# [L, 3, L]
|
||
|
|
masks = torch.stack(masks, dim=0)
|
||
|
|
# masks [L, 3, L]
|
||
|
|
n0 = n_vars - 1
|
||
|
|
n1 = N - 1
|
||
|
|
n2 = L - n0 - n1 - 1
|
||
|
|
|
||
|
|
adj_mask0 = adj * masks[:, 0, :]
|
||
|
|
adj_mask1 = adj * masks[:, 1, :]
|
||
|
|
adj_mask2 = adj * masks[:, 2, :]
|
||
|
|
|
||
|
|
def apply_mask_to_region(adj_mask, n):
|
||
|
|
threshold_idx = int(n * alpha)
|
||
|
|
sorted_values, _ = torch.sort(adj_mask, dim=-1, descending=True)
|
||
|
|
threshold = sorted_values[:, :, :, threshold_idx]
|
||
|
|
return adj_mask * (adj_mask >= threshold.unsqueeze(-1))
|
||
|
|
|
||
|
|
adj_mask0 = apply_mask_to_region(adj_mask0, n0)
|
||
|
|
adj_mask1 = apply_mask_to_region(adj_mask1, n1)
|
||
|
|
adj_mask2 = apply_mask_to_region(adj_mask2, n2)
|
||
|
|
|
||
|
|
adj = adj_mask0 + adj_mask1 + adj_mask2
|
||
|
|
|
||
|
|
return adj
|
||
|
|
|
||
|
|
|
||
|
|
##########################
|
||
|
|
|
||
|
|
class mask_moe(nn.Module):
|
||
|
|
def __init__(self, n_vars, top_p=0.5, num_experts=3, in_dim=96):
|
||
|
|
super().__init__()
|
||
|
|
self.num_experts = num_experts
|
||
|
|
self.n_vars = n_vars
|
||
|
|
self.in_dim = in_dim
|
||
|
|
|
||
|
|
self.gate = nn.Linear(self.in_dim, num_experts, bias=False)
|
||
|
|
self.noise = nn.Linear(self.in_dim, num_experts, bias=False)
|
||
|
|
self.noisy_gating = 1 #True
|
||
|
|
self.softplus = nn.Softplus()
|
||
|
|
self.softmax = nn.Softmax(2)
|
||
|
|
self.top_p = top_p
|
||
|
|
|
||
|
|
def cv_squared(self, x):
|
||
|
|
eps = 1e-10
|
||
|
|
if x.shape[0] == 1:
|
||
|
|
return torch.tensor([0], device=x.device, dtype=x.dtype)
|
||
|
|
return x.float().var() / (x.float().mean() ** 2 + eps)
|
||
|
|
|
||
|
|
def cross_entropy(self, x):
|
||
|
|
eps = 1e-10
|
||
|
|
if x.shape[0] == 1:
|
||
|
|
return torch.tensor([0], device=x.device, dtype=x.dtype)
|
||
|
|
return -torch.mul(x, torch.log(x + eps)).sum(dim=1).mean()
|
||
|
|
|
||
|
|
def noisy_top_k_gating(self, x, is_training, noise_epsilon=1e-2):
|
||
|
|
clean_logits = self.gate(x)
|
||
|
|
if self.noisy_gating and is_training:
|
||
|
|
raw_noise = self.noise(x)
|
||
|
|
noise_stddev = ((self.softplus(raw_noise) + noise_epsilon))
|
||
|
|
noisy_logits = clean_logits + torch.randn_like(clean_logits) * noise_stddev
|
||
|
|
logits = noisy_logits
|
||
|
|
else:
|
||
|
|
logits = clean_logits
|
||
|
|
|
||
|
|
# Convert logits to probabilities
|
||
|
|
logits = self.softmax(logits)
|
||
|
|
loss_dynamic = self.cross_entropy(logits)
|
||
|
|
|
||
|
|
sorted_probs, sorted_indices = torch.sort(logits, descending=True)
|
||
|
|
cumulative_probs = torch.cumsum(sorted_probs, dim=-1)
|
||
|
|
mask = cumulative_probs > self.top_p
|
||
|
|
|
||
|
|
threshold_indices = mask.long().argmax(dim=-1)
|
||
|
|
threshold_mask = torch.nn.functional.one_hot(threshold_indices, num_classes=sorted_indices.size(-1)).bool()
|
||
|
|
mask = mask & ~threshold_mask
|
||
|
|
|
||
|
|
top_p_mask = torch.zeros_like(mask)
|
||
|
|
zero_indices = (mask == 0).nonzero(as_tuple=True)
|
||
|
|
top_p_mask[
|
||
|
|
zero_indices[0], zero_indices[1], sorted_indices[zero_indices[0], zero_indices[1], zero_indices[2]]] = 1
|
||
|
|
|
||
|
|
sorted_probs = torch.where(mask, 0.0, sorted_probs)
|
||
|
|
loss_importance = self.cv_squared(sorted_probs.sum(0))
|
||
|
|
lambda_2 = 0.1
|
||
|
|
loss = loss_importance + lambda_2 * loss_dynamic
|
||
|
|
|
||
|
|
return top_p_mask, loss
|
||
|
|
|
||
|
|
def forward(self, x, masks=None):
|
||
|
|
# x [B, H, L, L]
|
||
|
|
B, H, L, _ = x.shape
|
||
|
|
device = x.device
|
||
|
|
dtype = torch.float32
|
||
|
|
|
||
|
|
mask_base = torch.eye(L, device=device, dtype=dtype).unsqueeze(0).unsqueeze(0)
|
||
|
|
if self.top_p == 0.0:
|
||
|
|
return mask_base, 0.0
|
||
|
|
|
||
|
|
x = x.reshape(B * H, L, L)
|
||
|
|
gates, loss = self.noisy_top_k_gating(x, self.training)
|
||
|
|
gates = gates.reshape(B, H, L, -1).float()
|
||
|
|
# [B, H, L, 3]
|
||
|
|
|
||
|
|
if masks is None:
|
||
|
|
print("Masks is None!")
|
||
|
|
masks = []
|
||
|
|
N = L // self.n_vars
|
||
|
|
for k in range(L):
|
||
|
|
S = ((torch.arange(L) % N == k % N) & (torch.arange(L) != k)).to(dtype).to(device)
|
||
|
|
T = ((torch.arange(L) >= k // N * N) & (torch.arange(L) < k // N * N + N)).to(dtype).to(device)
|
||
|
|
ST = torch.ones(L).to(dtype).to(device) - S - T
|
||
|
|
masks.append(torch.stack([S, T, ST], dim=0))
|
||
|
|
# [L, 3, L]
|
||
|
|
masks = torch.stack(masks, dim=0)
|
||
|
|
|
||
|
|
mask = torch.einsum('bhli,lid->bhld', gates, masks) + mask_base
|
||
|
|
|
||
|
|
return mask, loss
|
||
|
|
|
||
|
|
|
||
|
|
def mask_topk(x, alpha=0.5, largest=False):
|
||
|
|
# B, L = x.shape[0], x.shape[-1]
|
||
|
|
# x: [B, H, L, L]
|
||
|
|
k = int(alpha * x.shape[-1])
|
||
|
|
_, topk_indices = torch.topk(x, k, dim=-1, largest=largest)
|
||
|
|
mask = torch.ones_like(x, dtype=torch.float32)
|
||
|
|
mask.scatter_(-1, topk_indices, 0) # 1 is topk
|
||
|
|
return mask # [B, H, L, L]
|
||
|
|
|
||
|
|
|
||
|
|
class GraphLearner(nn.Module):
|
||
|
|
def __init__(self, dim, n_vars, top_p=0.5, in_dim=96):
|
||
|
|
super().__init__()
|
||
|
|
self.dim = dim
|
||
|
|
self.proj_1 = nn.Linear(dim, dim)
|
||
|
|
self.proj_2 = nn.Linear(dim, dim)
|
||
|
|
self.n_vars = n_vars
|
||
|
|
self.mask_moe = mask_moe(n_vars, top_p=top_p, in_dim=in_dim)
|
||
|
|
|
||
|
|
def forward(self, x, masks=None, alpha=0.5):
|
||
|
|
# x: [B, H, L, D]
|
||
|
|
adj = F.gelu(torch.einsum('bhid,bhjd->bhij', self.proj_1(x), self.proj_2(x)))
|
||
|
|
adj = adj * mask_topk(adj, alpha) # KNN
|
||
|
|
mask, loss = self.mask_moe(adj, masks)
|
||
|
|
adj = adj * mask
|
||
|
|
|
||
|
|
return adj, loss # [B, H, L, L]
|
||
|
|
|
||
|
|
|
||
|
|
class GraphFilter(nn.Module):
|
||
|
|
def __init__(self, dim, n_vars, n_heads=4, scale=None, top_p=0.5, dropout=0., in_dim=96):
|
||
|
|
super().__init__()
|
||
|
|
self.dim = dim
|
||
|
|
self.n_heads = n_heads
|
||
|
|
self.scale = dim ** (-0.5) if scale is None else scale
|
||
|
|
self.dropout = nn.Dropout(dropout)
|
||
|
|
self.graph_learner = GraphLearner(self.dim // self.n_heads, n_vars, top_p, in_dim=in_dim)
|
||
|
|
self.graph_conv = GCN(self.dim, self.n_heads)
|
||
|
|
|
||
|
|
def forward(self, x, masks=None, alpha=0.5):
|
||
|
|
# x: [B, L, D]
|
||
|
|
B, L, D = x.shape
|
||
|
|
|
||
|
|
adj, loss = self.graph_learner(x.reshape(B, L, self.n_heads, -1).permute(0, 2, 1, 3), masks, alpha) # [B, H, L, L]
|
||
|
|
|
||
|
|
adj = torch.softmax(adj, dim=-1)
|
||
|
|
adj = self.dropout(adj)
|
||
|
|
out = self.graph_conv(adj, x)
|
||
|
|
return out, loss # [B, L, D]
|
||
|
|
|
||
|
|
|
||
|
|
class GraphBlock(nn.Module):
|
||
|
|
def __init__(self, dim, n_vars, d_ff=None, n_heads=4, top_p=0.5, dropout=0., in_dim=96):
|
||
|
|
super().__init__()
|
||
|
|
self.dim = dim
|
||
|
|
self.d_ff = dim * 4 if d_ff is None else d_ff
|
||
|
|
self.gnn = GraphFilter(self.dim, n_vars, n_heads, top_p=top_p, dropout=dropout, in_dim=in_dim)
|
||
|
|
self.norm1 = nn.LayerNorm(self.dim)
|
||
|
|
self.ffn = nn.Sequential(
|
||
|
|
nn.Linear(self.dim, self.d_ff),
|
||
|
|
nn.GELU(),
|
||
|
|
nn.Dropout(dropout),
|
||
|
|
nn.Linear(self.d_ff, self.dim),
|
||
|
|
)
|
||
|
|
self.norm2 = nn.LayerNorm(self.dim)
|
||
|
|
|
||
|
|
def forward(self, x, masks=None, alpha=0.5):
|
||
|
|
# x: [B, L, D], time_embed: [B, time_embed_dim]
|
||
|
|
out, loss = self.gnn(self.norm1(x), masks, alpha)
|
||
|
|
x = x + out
|
||
|
|
x = x + self.ffn(self.norm2(x))
|
||
|
|
return x, loss
|
||
|
|
|
||
|
|
|
||
|
|
class TimeFilter_Backbone(nn.Module):
|
||
|
|
def __init__(self, hidden_dim, n_vars, d_ff=None, n_heads=4, n_blocks=3, top_p=0.5, dropout=0., in_dim=96):
|
||
|
|
super().__init__()
|
||
|
|
self.dim = hidden_dim
|
||
|
|
self.d_ff = self.dim * 2 if d_ff is None else d_ff
|
||
|
|
# graph blocks
|
||
|
|
self.blocks = nn.ModuleList([
|
||
|
|
GraphBlock(self.dim, n_vars, self.d_ff, n_heads, top_p, dropout, in_dim)
|
||
|
|
for _ in range(n_blocks)
|
||
|
|
])
|
||
|
|
self.n_blocks = n_blocks
|
||
|
|
|
||
|
|
def forward(self, x, masks=None, alpha=0.5):
|
||
|
|
# x: [B, N, T]
|
||
|
|
moe_loss = 0.0
|
||
|
|
for block in self.blocks:
|
||
|
|
x, loss = block(x, masks, alpha)
|
||
|
|
moe_loss += loss
|
||
|
|
moe_loss /= self.n_blocks
|
||
|
|
return x, moe_loss # [B, N, T]
|