Transformer Explainer¶
import math
import matplotlib.pyplot as plt
import numpy as np
import scipy as sp
import torch
from torch import nn
Examples¶
x = np.array([1, 3])
qk = np.outer(x, x)
sp.special.expit(qk)
sp.special.expit(qk).dot(x)
sp.special.expit(np.outer([1, 1], [1, 1]))
The A Matrix¶
Some examples of the A matrix.
x = np.array([1, 10])
def compare_a(a, x):
return a, sp.special.expit(a), sp.special.expit(a).dot(x)
a_1 = np.array([[1, 0], [0, 1]])
compare_a(a_1, x)
a_2 = np.array([[1, -10], [-10, 1]])
compare_a(a_2, x)
a_3 = np.array([[-10, 1], [1, -10]])
compare_a(a_3, x)
Visualize¶
We plot out the q,k vectors and the corresponding attention.
atten_similarity_q = np.array([[1], [10]])
atten_similarity_k = np.array([[1], [10]])
atten_similarity_sim = sp.special.expit(
np.outer(atten_similarity_q, atten_similarity_k)
)
for a in [atten_similarity_q, atten_similarity_k, atten_similarity_sim]:
fig, ax = plt.subplots(figsize=(7, 7))
ax.matshow(a)
for (i, j), z in np.ndenumerate(a):
ax.text(
j,
i,
"{:0.1f}".format(z),
ha="center",
va="center",
bbox=dict(boxstyle="round", facecolor="white", edgecolor="0.3"),
fontsize="xx-large",
)
ax.set_axis_off()
Positional Encoding¶
import math
class PositionalEncodingSimple:
"""Positional encoding for our transformer
written in numpy.
:param d_model: hidden dimension of the encoder
:param max_len: maximum length of our positional
encoder. The encoder can not encode sequence
length longer than max_len.
"""
def __init__(self, d_model: int, max_len: int = 100):
position = np.expand_dims(np.arange(max_len), axis=1)
div_term = np.exp(np.arange(0, d_model, 2) * (-math.log(10000.0) / d_model))
self.pe = np.zeros((max_len, d_model))
self.pe[:, 0::2] = np.sin(position * div_term)
self.pe[:, 1::2] = np.cos(position * div_term)
def __call__(self, x: np.ndarray) -> np.ndarray:
"""
:param x: input to be encoded
with shape
`(batch_size, sequence_length, embedding_dim)`
"""
return self.pe[: x.shape[1]]
pes = PositionalEncodingSimple(d_model=50)
x_pes_in = np.ones((1, 10, 1))
x_pes_out = pes(x=x_pes_in)
_, ax = plt.subplots(figsize=(10, 6.18))
ax.matshow(x_pes_out, cmap="cividis")
ax.set_xlabel("Embedding")
ax.set_ylabel("Temporal")
_, ax = plt.subplots()
ax.plot(x_pes_out[-1, :])
ax.plot(x_pes_out[0, :])
Positional encoding in nixtla
class PositionalEmbedding(nn.Module):
def __init__(self, hidden_size, max_len=5000):
super(PositionalEmbedding, self).__init__()
# Compute the positional encodings once in log space.
pe = torch.zeros(max_len, hidden_size).float()
pe.require_grad = False
position = torch.arange(0, max_len).float().unsqueeze(1)
div_term = (
torch.arange(0, hidden_size, 2).float() * -(math.log(10000.0) / hidden_size)
).exp()
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
pe = pe.unsqueeze(0)
self.register_buffer("pe", pe)
def forward(self, x):
return self.pe[:, : x.size(1)]
pe = PositionalEmbedding(hidden_size=192, max_len=20)
plt.plot((torch.arange(0, 192, 2).float() * -(math.log(10000.0) / 192)).exp().numpy())
Token Embedding
class TokenEmbedding(nn.Module):
def __init__(self, c_in, hidden_size):
super(TokenEmbedding, self).__init__()
padding = 1 if torch.__version__ >= "1.5.0" else 2
self.tokenConv = nn.Conv1d(
in_channels=c_in,
out_channels=hidden_size,
kernel_size=3,
padding=padding,
padding_mode="circular",
bias=False,
)
for m in self.modules():
if isinstance(m, nn.Conv1d):
nn.init.kaiming_normal_(
m.weight, mode="fan_in", nonlinearity="leaky_relu"
)
def forward(self, x):
x = self.tokenConv(x.permute(0, 2, 1)).transpose(1, 2)
return x
x_te_in = torch.ones((1, 10, 1))
te = TokenEmbedding(c_in=1, hidden_size=4)
x_te = te(x_te_in)
x_te.shape
te_pe = PositionalEmbedding(hidden_size=4, max_len=20)
te_pe(x)
from neuralforecast.common._modules import DataEmbedding
class TriangularCausalMask:
def __init__(self, B, L, device="cpu"):
mask_shape = [B, 1, L, L]
with torch.no_grad():
self._mask = torch.triu(
torch.ones(mask_shape, dtype=torch.bool), diagonal=1
).to(device)
@property
def mask(self):
return self._mask
class FullAttention(nn.Module):
"""
FullAttention
"""
def __init__(
self, mask_flag=True, scale=None, attention_dropout=0.1, output_attention=False
):
super(FullAttention, self).__init__()
self.scale = scale
self.mask_flag = mask_flag
self.output_attention = output_attention
self.dropout = nn.Dropout(attention_dropout)
def forward(self, queries, keys, values, attn_mask):
B, L, H, E = queries.shape
_, S, _, D = values.shape
scale = self.scale or 1.0 / math.sqrt(E)
scores = torch.einsum("blhe,bshe->bhls", queries, keys)
if self.mask_flag:
if attn_mask is None:
attn_mask = TriangularCausalMask(B, L, device=queries.device)
scores.masked_fill_(attn_mask.mask, -np.inf)
A = self.dropout(torch.softmax(scale * scores, dim=-1))
V = torch.einsum("bhls,bshd->blhd", A, values)
if self.output_attention:
return (V.contiguous(), A)
else:
return (V.contiguous(), None)
class AttentionLayer(nn.Module):
def __init__(self, attention, hidden_size, n_head, d_keys=None, d_values=None):
super(AttentionLayer, self).__init__()
d_keys = d_keys or (hidden_size // n_head)
d_values = d_values or (hidden_size // n_head)
self.inner_attention = attention
self.query_projection = nn.Linear(hidden_size, d_keys * n_head)
self.key_projection = nn.Linear(hidden_size, d_keys * n_head)
self.value_projection = nn.Linear(hidden_size, d_values * n_head)
self.out_projection = nn.Linear(d_values * n_head, hidden_size)
self.n_head = n_head
def forward(self, queries, keys, values, attn_mask):
B, L, _ = queries.shape
_, S, _ = keys.shape
H = self.n_head
queries = self.query_projection(queries).view(B, L, H, -1)
keys = self.key_projection(keys).view(B, S, H, -1)
values = self.value_projection(values).view(B, S, H, -1)
out, attn = self.inner_attention(queries, keys, values, attn_mask)
# out = out.view(B, L, -1)
# return self.out_projection(out), attn
return out, attn
enc_in = 1
hidden_size = 4
enc_embedding = DataEmbedding(
c_in=enc_in, exog_input_size=0, hidden_size=hidden_size, pos_embedding=True
)
x = torch.ones(size=(1, 10, 1)) # batch size: 1, history length 10, variables 1
x_embedded = enc_embedding(x)
x_embedded.shape
attention = FullAttention(mask_flag=False, output_attention=True)
attention_layer = AttentionLayer(
attention,
hidden_size=hidden_size,
n_head=1,
)
attention_layer.query_projection(x_embedded).view(1, 10, 1, -1).shape
al_out, al_att = attention_layer(x_embedded, x_embedded, x_embedded, attn_mask=False)
al_out.shape
queries = attention_layer.query_projection(x_embedded).view(1, 10, 1, -1)
keys = attention_layer.key_projection(x_embedded).view(1, 10, 1, -1)
torch.einsum("blhe,bshe->bhls", queries, keys).shape
queries.shape
class DataEmbedding_inverted(nn.Module):
"""
DataEmbedding_inverted
"""
def __init__(self, c_in, hidden_size, dropout=0.1):
super(DataEmbedding_inverted, self).__init__()
self.value_embedding = nn.Linear(c_in, hidden_size)
self.dropout = nn.Dropout(p=dropout)
def forward(self, x, x_mark):
x = x.permute(0, 2, 1)
# x: [Batch Variate Time]
if x_mark is None:
x = self.value_embedding(x)
else:
# the potential to take covariates (e.g. timestamps) as tokens
x = self.value_embedding(torch.cat([x, x_mark.permute(0, 2, 1)], 1))
# x: [Batch Variate hidden_size]
return self.dropout(x)
i_de = DataEmbedding_inverted(10, hidden_size)
i_enc_out = i_de(x, None)
i_enc_out.shape