Files
pytorch-ts/pts/distributions/implicit_quantile.py
T

70 lines
2.4 KiB
Python

import torch
from torch.distributions import Distribution, TransformedDistribution, AffineTransform
class ImplicitQuantile(Distribution):
def __init__(
self,
implicit_quantile_function,
taus,
nn_output,
predicted_quantiles,
validate_args=None,
):
self.predicted_quantiles = predicted_quantiles[0]
self.taus = taus
self.quantile_function = implicit_quantile_function
self.input_data = nn_output
super(ImplicitQuantile, self).__init__(
batch_shape=self.predicted_quantiles.shape, validate_args=validate_args
)
@torch.no_grad()
def sample(self, sample_shape=torch.Size()):
"""See arXiv: 1806.06923
Once the model has learned how to predict a given quantile tau, one can sample from the
distribution of the target, by sampling tau values.
"""
if len(sample_shape) == 0:
num_parallel_samples = 1
else:
num_parallel_samples = sample_shape[0]
input_data = torch.repeat_interleave(
self.input_data, repeats=num_parallel_samples, dim=0
)
batch_size = input_data.shape[0]
forecast_length = input_data.shape[1]
device = input_data.device
taus = torch.rand(size=(batch_size, forecast_length), device=device)
samples = self.quantile_function(input_data, taus)
if len(sample_shape) == 0:
return samples
else:
return samples.reshape((num_parallel_samples, -1, forecast_length))
def log_prob(self, value):
# Assumes same distribution for all steps in the future, conditionally on the input data
return -self.quantile_loss(self.predicted_quantiles, value, self.taus)
@staticmethod
def quantile_loss(quantile_forecast, target, tau):
return torch.abs(
(quantile_forecast - target) * ((target <= quantile_forecast).float() - tau)
)
class TransformedImplicitQuantile(TransformedDistribution):
def __init__(self, base_distribution, transforms):
super().__init__(base_distribution, transforms)
def log_prob(self, x):
scale = 1.0
for transform in reversed(self.transforms):
assert isinstance(transform, AffineTransform), "Not an AffineTransform"
x = transform.inv(x)
scale *= transform.scale
p = self.base_dist.log_prob(x)
return p * scale