mirror of
https://github.com/wassname/pytorch-ts.git
synced 2026-07-14 11:17:47 +08:00
142 lines
3.7 KiB
Python
142 lines
3.7 KiB
Python
from abc import ABC, abstractmethod
|
|
from typing import Callable, Dict, Optional, Tuple
|
|
|
|
import numpy as np
|
|
import torch
|
|
import torch.nn as nn
|
|
import torch.nn.functional as F
|
|
from torch.distributions import (
|
|
Distribution,
|
|
Beta,
|
|
NegativeBinomial,
|
|
StudentT,
|
|
TransformedDistribution,
|
|
AffineTransform,
|
|
)
|
|
|
|
from .lambda_layer import LambdaLayer
|
|
|
|
|
|
class ArgProj(nn.Module):
|
|
def __init__(
|
|
self,
|
|
in_features: int,
|
|
args_dim: Dict[str, int],
|
|
domain_map: Callable[..., Tuple[torch.Tensor]],
|
|
dtype: np.dtype = np.float32,
|
|
prefix: Optional[str] = None,
|
|
**kwargs,
|
|
):
|
|
super().__init__(**kwargs)
|
|
self.args_dim = args_dim
|
|
self.dtype = dtype
|
|
self.proj = nn.ModuleList(
|
|
[nn.Linear(in_features, dim) for dim in args_dim.values()]
|
|
)
|
|
self.domain_map = domain_map
|
|
|
|
def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor]:
|
|
params_unbounded = [proj(x) for proj in self.proj]
|
|
|
|
return self.domain_map(*params_unbounded)
|
|
|
|
|
|
class Output(ABC):
|
|
in_features: int
|
|
args_dim: Dict[str, int]
|
|
_dtype: np.dtype = np.float32
|
|
|
|
@property
|
|
def dtype(self):
|
|
return self._dtype
|
|
|
|
@dtype.setter
|
|
def dtype(self, dtype: np.dtype):
|
|
self._dtype = dtype
|
|
|
|
def get_args_proj(self, in_features: int, prefix: Optional[str] = None) -> ArgProj:
|
|
return ArgProj(
|
|
in_features=in_features,
|
|
args_dim=self.args_dim,
|
|
domain_map=LambdaLayer(self.domain_map),
|
|
prefix=prefix,
|
|
dtype=self.dtype,
|
|
)
|
|
|
|
@abstractmethod
|
|
def domain_map(self, *args: torch.Tensor):
|
|
pass
|
|
|
|
|
|
class DistributionOutput(Output, ABC):
|
|
distr_cls: type
|
|
|
|
def distribution(
|
|
self, distr_args, scale: Optional[torch.Tensor] = None
|
|
) -> Distribution:
|
|
|
|
if scale is None:
|
|
return self.distr_cls(*distr_args)
|
|
else:
|
|
distr = self.distr_cls(*distr_args)
|
|
return TransformedDistribution(distr, [AffineTransform(loc=0, scale=scale)])
|
|
|
|
|
|
class BetaOutput(DistributionOutput):
|
|
args_dim: Dict[str, int] = {"concentration1": 1, "concentration0": 1}
|
|
distr_cls: type = Beta
|
|
|
|
@classmethod
|
|
def domain_map(cls, concentration1, concentration0):
|
|
concentration1 = F.softplus(concentration1) + 1e-8
|
|
concentration0 = F.softplus(concentration0) + 1e-8
|
|
return concentration1.squeeze(-1), concentration0.squeeze(-1)
|
|
|
|
@property
|
|
def event_shape(self) -> Tuple:
|
|
return ()
|
|
|
|
|
|
class NegativeBinomialOutput(DistributionOutput):
|
|
args_dim: Dict[str, int] = {"mu": 1, "alpha": 1}
|
|
distr_cls: Distribution = NegativeBinomial
|
|
|
|
@classmethod
|
|
def domain_map(cls, mu, alpha):
|
|
mu = F.softplus(mu) + 1e-8
|
|
alpha = F.softplus(alpha) + 1e-8
|
|
return mu.squeeze(-1), alpha.squeeze(-1)
|
|
|
|
def distribution(
|
|
self, distr_args, scale: Optional[torch.Tensor] = None
|
|
) -> Distribution:
|
|
mu, alpha = distr_args
|
|
|
|
if scale is not None:
|
|
mu *= scale
|
|
alpha *= torch.sqrt(scale + 1.0)
|
|
|
|
n = 1.0 / alpha
|
|
p = mu * alpha / (1.0 + mu * alpha)
|
|
|
|
return NegativeBinomial(total_count=n, probs=p)
|
|
|
|
@property
|
|
def event_shape(self) -> Tuple:
|
|
return ()
|
|
|
|
|
|
class StudentTOutput(DistributionOutput):
|
|
args_dim: Dict[str, int] = {"df": 1, "loc": 1, "scale": 1}
|
|
distr_cls: type = StudentT
|
|
|
|
@classmethod
|
|
def domain_map(cls, df, loc, scale):
|
|
scale = F.softplus(scale)
|
|
df = 2.0 + F.softplus(df)
|
|
return df.squeeze(-1), loc.squeeze(-1), scale.squeeze(-1)
|
|
|
|
@property
|
|
def event_shape(self) -> Tuple:
|
|
return ()
|