Files
attentive-neural-processes/src/models/modules.py
T
2020-03-15 12:23:18 +08:00

411 lines
13 KiB
Python

import torch
from torch import nn
import torch.nn.functional as F
import math
import numpy as np
# from .attention import Attention as PtAttention
class LSTMBlock(nn.Module):
def __init__(
self, in_channels, out_channels, dropout=0, batchnorm=False, bias=False, num_layers=1
):
super().__init__()
self._lstm = nn.LSTM(
input_size=in_channels,
hidden_size=out_channels,
num_layers=num_layers,
dropout=dropout,
batch_first=True,
bias=bias
)
def forward(self, x):
return self._lstm(x)[0]
class BatchNormSequence(nn.Module):
"""Applies batch norm on features of a batch first sequence."""
def __init__(
self, out_channels
):
super().__init__()
self.norm = nn.BatchNorm1d(out_channels)
def forward(self, x):
# x.shape is (Batch, Sequence, Channels)
# Now we want to apply batchnorm and dropout to the channels. So we put it in shape
# (Batch, Channels, Sequence) so we can use BatchNorm1d
x = x.permute(0, 2, 1)
x = self.norm(x)
return x.permute(0, 2, 1)
class NPBlockRelu2d(nn.Module):
"""Block for Neural Processes."""
def __init__(
self, in_channels, out_channels, dropout=0, batchnorm=False, bias=False
):
super().__init__()
self.linear = nn.Linear(in_channels, out_channels, bias=bias)
self.act = nn.ReLU()
self.dropout = nn.Dropout2d(dropout)
self.norm = nn.BatchNorm2d(out_channels) if batchnorm else False
def forward(self, x):
# x.shape is (Batch, Sequence, Channels)
# We pass a linear over it which operates on the Channels
x = self.act(self.linear(x))
# Now we want to apply batchnorm and dropout to the channels. So we put it in shape
# (Batch, Channels, Sequence, None) so we can use Dropout2d & BatchNorm2d
x = x.permute(0, 2, 1)[:, :, :, None]
if self.norm:
x = self.norm(x)
x = self.dropout(x)
return x[:, :, :, 0].permute(0, 2, 1)
class BatchMLP(nn.Module):
"""Apply MLP to the final axis of a 3D tensor (reusing already defined MLPs).
Args:
input: input tensor of shape [B,n,d_in].
output_sizes: An iterable containing the output sizes of the MLP as defined
in `basic.Linear`.
Returns:
tensor of shape [B,n,d_out] where d_out=output_size
"""
def __init__(
self, input_size, output_size, num_layers=2, dropout=0, batchnorm=False
):
super().__init__()
self.input_size = input_size
self.output_size = output_size
self.num_layers = num_layers
self.initial = NPBlockRelu2d(
input_size, output_size, dropout=dropout, batchnorm=batchnorm
)
self.encoder = nn.Sequential(
*[
NPBlockRelu2d(
output_size, output_size, dropout=dropout, batchnorm=batchnorm
)
for _ in range(num_layers - 2)
]
)
self.final = nn.Linear(output_size, output_size)
def forward(self, x):
x = self.initial(x)
x = self.encoder(x)
return self.final(x)
class AttnLinear(nn.Module):
def __init__(self, in_channels, out_channels):
super().__init__()
self.linear = nn.Linear(in_channels, out_channels, bias=False)
torch.nn.init.normal_(self.linear.weight, std=in_channels ** -0.5)
def forward(self, x):
x = self.linear(x)
return x
class Attention(nn.Module):
def __init__(
self,
hidden_dim,
attention_type,
attention_layers=2,
n_heads=8,
x_dim=1,
rep="mlp",
dropout=0,
batchnorm=False,
):
super().__init__()
self._rep = rep
if self._rep == "mlp":
self.batch_mlp_k = BatchMLP(
x_dim,
hidden_dim,
attention_layers,
dropout=dropout,
batchnorm=batchnorm,
)
self.batch_mlp_q = BatchMLP(
x_dim,
hidden_dim,
attention_layers,
dropout=dropout,
batchnorm=batchnorm,
)
if attention_type == "uniform":
self._attention_func = self._uniform_attention
elif attention_type == "laplace":
self._attention_func = self._laplace_attention
elif attention_type == "dot":
self._attention_func = self._dot_attention
elif attention_type == "multihead":
self._W_k = nn.ModuleList(
[AttnLinear(hidden_dim, hidden_dim) for _ in range(n_heads)]
)
self._W_v = nn.ModuleList(
[AttnLinear(hidden_dim, hidden_dim) for _ in range(n_heads)]
)
self._W_q = nn.ModuleList(
[AttnLinear(hidden_dim, hidden_dim) for _ in range(n_heads)]
)
self._W = AttnLinear(n_heads * hidden_dim, hidden_dim)
self._attention_func = self._multihead_attention
self.n_heads = n_heads
elif attention_type == "ptmultihead":
self._W = torch.nn.MultiheadAttention(
hidden_dim, n_heads, bias=False, dropout=dropout
)
self._attention_func = self._pytorch_multihead_attention
else:
raise NotImplementedError
def forward(self, k, v, q):
if self._rep == "mlp":
k = self.batch_mlp_k(k)
q = self.batch_mlp_q(q)
rep = self._attention_func(k, v, q)
return rep
def _uniform_attention(self, k, v, q):
total_points = q.shape[1]
rep = torch.mean(v, dim=1, keepdim=True)
rep = rep.repeat(1, total_points, 1)
return rep
def _laplace_attention(self, k, v, q, scale=0.5):
k_ = k.unsqueeze(1)
v_ = v.unsqueeze(2)
unnorm_weights = torch.abs((k_ - v_) * scale)
unnorm_weights = unnorm_weights.sum(dim=-1)
weights = torch.softmax(unnorm_weights, dim=-1)
rep = torch.einsum("bik,bkj->bij", weights, v)
return rep
def _dot_attention(self, k, v, q):
scale = q.shape[-1] ** 0.5
unnorm_weights = torch.einsum("bjk,bik->bij", k, q) / scale
weights = torch.softmax(unnorm_weights, dim=-1)
rep = torch.einsum("bik,bkj->bij", weights, v)
return rep
def _multihead_attention(self, k, v, q):
outs = []
for i in range(self.n_heads):
k_ = self._W_k[i](k)
v_ = self._W_v[i](v)
q_ = self._W_q[i](q)
out = self._dot_attention(k_, v_, q_)
outs.append(out)
outs = torch.stack(outs, dim=-1)
outs = outs.view(outs.shape[0], outs.shape[1], -1)
rep = self._W(outs)
return rep
def _pytorch_multihead_attention(self, k, v, q):
# Pytorch multiheaded attention takes inputs if diff order and permutation
q = q.permute(1, 0, 2)
k = k.permute(1, 0, 2)
v = v.permute(1, 0, 2)
o = self._W(q, k, v)[0]
return o.permute(1, 0, 2)
class LatentEncoder(nn.Module):
def __init__(
self,
input_dim,
hidden_dim=32,
latent_dim=32,
self_attention_type="dot",
n_encoder_layers=3,
min_std=0.01,
batchnorm=False,
dropout=0,
attention_dropout=0,
use_lvar=False,
use_self_attn=False,
attention_layers=2,
use_lstm=False
):
super().__init__()
# self._input_layer = nn.Linear(input_dim, hidden_dim)
if use_lstm:
self._encoder = LSTMBlock(input_dim, hidden_dim, batchnorm=batchnorm, dropout=dropout, num_layers=n_encoder_layers)
else:
self._encoder = BatchMLP(input_dim, hidden_dim, batchnorm=batchnorm, dropout=dropout, num_layers=n_encoder_layers)
if use_self_attn:
self._self_attention = Attention(
hidden_dim,
self_attention_type,
attention_layers,
rep="identity",
dropout=attention_dropout,
)
self._penultimate_layer = nn.Linear(hidden_dim, hidden_dim)
self._mean = nn.Linear(hidden_dim, latent_dim)
self._log_var = nn.Linear(hidden_dim, latent_dim)
self._min_std = min_std
self._use_lvar = use_lvar
self._use_lstm = use_lstm
self._use_self_attn = use_self_attn
def forward(self, x, y):
encoder_input = torch.cat([x, y], dim=-1)
# Pass final axis through MLP
encoded = self._encoder(encoder_input)
# Aggregator: take the mean over all points
if self._use_self_attn:
attention_output = self._self_attention(encoded, encoded, encoded)
mean_repr = attention_output.mean(dim=1)
else:
mean_repr = encoded.mean(dim=1)
# Have further MLP layers that map to the parameters of the Gaussian latent
mean_repr = torch.relu(self._penultimate_layer(mean_repr))
# Then apply further linear layers to output latent mu and log sigma
mean = self._mean(mean_repr)
log_var = self._log_var(mean_repr)
if self._use_lvar:
# Clip it in the log domain, so it can only approach self.min_std, this helps avoid mode collapase
# 2 ways, a better but untested way using the more stable log domain, and the way from the deepmind repo
log_var = F.logsigmoid(log_var)
log_var = torch.clamp(log_var, np.log(self._min_std), -np.log(self._min_std))
sigma = torch.exp(0.5 * log_var)
else:
sigma = self._min_std + (1 - self._min_std) * torch.sigmoid(log_var * 0.5)
dist = torch.distributions.Normal(mean, sigma)
return dist, log_var
class DeterministicEncoder(nn.Module):
def __init__(
self,
input_dim,
x_dim,
hidden_dim=32,
n_d_encoder_layers=3,
self_attention_type="dot",
cross_attention_type="dot",
use_self_attn=False,
attention_layers=2,
batchnorm=False,
dropout=0,
attention_dropout=0,
use_lstm=False,
):
super().__init__()
self._use_self_attn = use_self_attn
# self._input_layer = nn.Linear(input_dim, hidden_dim)
if use_lstm:
self._d_encoder = LSTMBlock(input_dim, hidden_dim, batchnorm=batchnorm, dropout=dropout, num_layers=n_d_encoder_layers)
else:
self._d_encoder = BatchMLP(input_dim, hidden_dim, batchnorm=batchnorm, dropout=dropout, num_layers=n_d_encoder_layers)
if use_self_attn:
self._self_attention = Attention(
hidden_dim,
self_attention_type,
attention_layers,
rep="identity",
dropout=attention_dropout,
)
self._cross_attention = Attention(
hidden_dim,
cross_attention_type,
x_dim=x_dim,
attention_layers=attention_layers,
)
def forward(self, context_x, context_y, target_x):
# Concatenate x and y along the filter axes
d_encoder_input = torch.cat([context_x, context_y], dim=-1)
# Pass final axis through MLP
d_encoded = self._d_encoder(d_encoder_input)
if self._use_self_attn:
d_encoded = self._self_attention(d_encoded, d_encoded, d_encoded)
# Apply attention as mean aggregation
h = self._cross_attention(context_x, d_encoded, target_x)
return h
class Decoder(nn.Module):
def __init__(
self,
x_dim,
y_dim,
hidden_dim=32,
latent_dim=32,
n_decoder_layers=3,
use_deterministic_path=True,
min_std=0.01,
use_lvar=False,
batchnorm=False,
dropout=0,
use_lstm=False,
):
super(Decoder, self).__init__()
self._target_transform = nn.Linear(x_dim, hidden_dim)
if use_deterministic_path:
hidden_dim_2 = 2 * hidden_dim + latent_dim
else:
hidden_dim_2 = hidden_dim + latent_dim
if use_lstm:
self._decoder = LSTMBlock(hidden_dim_2, hidden_dim_2, batchnorm=batchnorm, dropout=dropout, num_layers=n_decoder_layers)
else:
self._decoder = BatchMLP(hidden_dim_2, hidden_dim_2, batchnorm=batchnorm, dropout=dropout, num_layers=n_decoder_layers)
self._mean = nn.Linear(hidden_dim_2, y_dim)
self._std = nn.Linear(hidden_dim_2, y_dim)
self._use_deterministic_path = use_deterministic_path
self._min_std = min_std
self._use_lvar = use_lvar
def forward(self, r, z, target_x):
# concatenate target_x and representation
x = self._target_transform(target_x)
if self._use_deterministic_path:
z = torch.cat([r, z], dim=-1)
r = torch.cat([z, x], dim=-1)
r = self._decoder(r)
# Get the mean and the variance
mean = self._mean(r)
log_sigma = self._std(r)
# Bound or clamp the variance
if self._use_lvar:
log_sigma = torch.clamp(log_sigma, math.log(self._min_std), -math.log(self._min_std))
sigma = torch.exp(log_sigma)
else:
sigma = self._min_std + (1 - self._min_std) * F.softplus(log_sigma)
dist = torch.distributions.Normal(mean, sigma)
return dist, log_sigma