dc6079821b
Docs Tests / Check for file changes (push) Has been cancelled
Docs Tests / Test Documentation (push) Has been cancelled
Docs Tests / Documentation Linting Checks (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.8, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.9, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.10, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.8, test-policies) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-performance) (push) Has been cancelled
Continuous Integration / Run Tests (windows-2022, 3.9, test-policies) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (ubuntu-24.04, 3.10) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (ubuntu-24.04, 3.8) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (ubuntu-24.04, 3.9) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (windows-2022, 3.10) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (windows-2022, 3.8) (push) Has been cancelled
Continuous Integration / Run Flaky Tests (windows-2022, 3.9) (push) Has been cancelled
Continuous Integration / Check for file changes (push) Has been cancelled
Continuous Integration / Wait for docs tests (push) Has been cancelled
Continuous Integration / Code Quality (push) Has been cancelled
Continuous Integration / Check for changelog (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-cli) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-core-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-full-model-training) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-nlu-featurizers) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-nlu-predictors) (push) Has been cancelled
Continuous Integration / Run Tests (ubuntu-24.04, 3.10, test-other-unit-tests) (push) Has been cancelled
Continuous Integration / Upload coverage reports to codeclimate (push) Has been cancelled
Continuous Integration / Run Non-Sequential Integration Tests (push) Has been cancelled
Continuous Integration / Run Broker Integration Tests (push) Has been cancelled
Continuous Integration / Run Sequential Integration Tests (push) Has been cancelled
Continuous Integration / Build Docker base images and setup environment (push) Has been cancelled
Continuous Integration / Build Docker (default) (push) Has been cancelled
Continuous Integration / Build Docker (full) (push) Has been cancelled
Continuous Integration / Build Docker (mitie-en) (push) Has been cancelled
Continuous Integration / Build Docker (spacy-de) (push) Has been cancelled
Continuous Integration / Build Docker (spacy-en) (push) Has been cancelled
Continuous Integration / Build Docker (spacy-it) (push) Has been cancelled
Continuous Integration / Deploy to PyPI (push) Has been cancelled
Continuous Integration / Notify Slack & Publish Release Notes (push) Has been cancelled
Publish Documentation / Evaluate release tag (push) Has been cancelled
Publish Documentation / Prebuild Docs (push) Has been cancelled
Publish Documentation / Preview Docs (push) Has been cancelled
Publish Documentation / Check for file changes (push) Has been cancelled
Publish Documentation / Publish Docs (push) Has been cancelled
Automatic PR Merger / mergepal (push) Has been cancelled
CI Github Actions / Run Tests (push) Has been cancelled
Semgrep / Semgrep Workflow Security Scan (push) Has been cancelled
645 lines
25 KiB
Python
645 lines
25 KiB
Python
from typing import Optional, Text, Tuple, Union
|
|
|
|
import numpy as np
|
|
import tensorflow as tf
|
|
|
|
# TODO: The following is not (yet) available via tf.keras
|
|
from keras.utils.control_flow_util import smart_cond
|
|
from tensorflow.keras import backend as K
|
|
|
|
import rasa.shared.utils.cli
|
|
from rasa.utils.tensorflow.layers import RandomlyConnectedDense
|
|
|
|
|
|
# from https://www.tensorflow.org/tutorials/text/transformer
|
|
# and https://github.com/tensorflow/tensor2tensor
|
|
class MultiHeadAttention(tf.keras.layers.Layer):
|
|
"""Multi-headed attention layer.
|
|
|
|
Arguments:
|
|
units: Positive integer, output dim of hidden layer.
|
|
num_heads: Positive integer, number of heads
|
|
to repeat the same attention structure.
|
|
attention_dropout_rate: Float, dropout rate inside attention for training.
|
|
density: Approximate fraction of trainable weights (in
|
|
`RandomlyConnectedDense` layers).
|
|
unidirectional: Boolean, use a unidirectional or bidirectional encoder.
|
|
use_key_relative_position: Boolean, if 'True' use key
|
|
relative embeddings in attention.
|
|
use_value_relative_position: Boolean, if 'True' use value
|
|
relative embeddings in attention.
|
|
max_relative_position: Positive integer, max position for relative embeddings.
|
|
heads_share_relative_embedding: Boolean, if 'True'
|
|
heads will share relative embeddings.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
units: int,
|
|
num_heads: int,
|
|
attention_dropout_rate: float = 0.0,
|
|
density: float = 0.2,
|
|
unidirectional: bool = False,
|
|
use_key_relative_position: bool = False,
|
|
use_value_relative_position: bool = False,
|
|
max_relative_position: int = 5,
|
|
heads_share_relative_embedding: bool = False,
|
|
) -> None:
|
|
super().__init__()
|
|
|
|
if units % num_heads != 0:
|
|
rasa.shared.utils.cli.print_error_and_exit(
|
|
f"Value Error: The given transformer size {units} should be a "
|
|
f"multiple of the number of attention heads {num_heads}."
|
|
)
|
|
|
|
self.num_heads = num_heads
|
|
self.units = units
|
|
self.attention_dropout_rate = attention_dropout_rate
|
|
self.unidirectional = unidirectional
|
|
self.use_key_relative_position = use_key_relative_position
|
|
self.use_value_relative_position = use_value_relative_position
|
|
self.relative_length = max_relative_position
|
|
self.relative_length += 1 # include current time
|
|
self.heads_share_relative_embedding = heads_share_relative_embedding
|
|
|
|
self._depth = units // self.num_heads
|
|
|
|
# process queries
|
|
self._query_dense_layer = RandomlyConnectedDense(
|
|
units=units, use_bias=False, density=density
|
|
)
|
|
# process keys
|
|
self._key_dense_layer = RandomlyConnectedDense(
|
|
units=units, use_bias=False, density=density
|
|
)
|
|
# process values
|
|
self._value_dense_layer = RandomlyConnectedDense(
|
|
units=units, use_bias=False, density=density
|
|
)
|
|
# process attention output
|
|
self._output_dense_layer = RandomlyConnectedDense(units=units, density=density)
|
|
|
|
self._create_relative_embeddings()
|
|
|
|
def _create_relative_embeddings(self) -> None:
|
|
"""Create relative embeddings."""
|
|
relative_embedding_shape: Optional[
|
|
Union[Tuple[int, int], Tuple[int, int, int]]
|
|
] = None
|
|
self.key_relative_embeddings = None
|
|
self.value_relative_embeddings = None
|
|
|
|
if self.use_key_relative_position or self.use_value_relative_position:
|
|
if not self.relative_length:
|
|
raise ValueError(
|
|
f"Max relative position {self.relative_length} "
|
|
f"should be > 0 when using relative attention."
|
|
)
|
|
|
|
if self.unidirectional:
|
|
relative_length = self.relative_length
|
|
else:
|
|
relative_length = 2 * self.relative_length - 1
|
|
|
|
if self.heads_share_relative_embedding:
|
|
relative_embedding_shape = (relative_length, self._depth)
|
|
else:
|
|
relative_embedding_shape = (
|
|
self.num_heads,
|
|
relative_length,
|
|
self._depth,
|
|
)
|
|
|
|
if self.use_key_relative_position:
|
|
self.key_relative_embeddings = self.add_weight(
|
|
shape=relative_embedding_shape, name="key_relative_embeddings"
|
|
)
|
|
|
|
if self.use_value_relative_position:
|
|
self.value_relative_embeddings = self.add_weight(
|
|
shape=relative_embedding_shape, name="value_relative_embeddings"
|
|
)
|
|
|
|
def _pad_relative_embeddings(self, x: tf.Tensor, length: tf.Tensor) -> tf.Tensor:
|
|
# pad the left side to length
|
|
pad_left = x[:, :, :, :1, :]
|
|
pad_left = tf.tile(pad_left, (1, 1, 1, length - self.relative_length, 1))
|
|
|
|
# pad the right side to length
|
|
if self.unidirectional:
|
|
right_relative_length = 1 # current time
|
|
pad_right = tf.zeros_like(x[:, :, :, -1:, :])
|
|
else:
|
|
right_relative_length = self.relative_length
|
|
pad_right = x[:, :, :, -1:, :]
|
|
pad_right = tf.tile(pad_right, (1, 1, 1, length - right_relative_length, 1))
|
|
|
|
return tf.concat([pad_left, x, pad_right], axis=-2)
|
|
|
|
def _slice_relative_embeddings(self, x: tf.Tensor, length: tf.Tensor) -> tf.Tensor:
|
|
if self.unidirectional:
|
|
# pad the right side to relative_length
|
|
pad_right = tf.zeros_like(x[:, :, :, -1:, :])
|
|
pad_right = tf.tile(pad_right, (1, 1, 1, self.relative_length - 1, 1))
|
|
x = tf.concat([x, pad_right], axis=-2)
|
|
|
|
extra_length = self.relative_length - length
|
|
full_length = tf.shape(x)[-2]
|
|
return x[:, :, :, extra_length : full_length - extra_length, :]
|
|
|
|
def _relative_to_absolute_position(self, x: tf.Tensor) -> tf.Tensor:
|
|
"""Universal method to convert tensor from relative to absolute indexing.
|
|
|
|
"Slides" relative embeddings by 45 degree.
|
|
|
|
Arguments:
|
|
x: A tensor of shape (batch, num_heads, length, relative_length, depth)
|
|
or (batch, num_heads, length, relative_length)
|
|
|
|
Returns:
|
|
A tensor of shape (batch, num_heads, length, length, depth)
|
|
or (batch, num_heads, length, length)
|
|
"""
|
|
|
|
x_dim = len(x.shape)
|
|
|
|
if x_dim < 4 or x_dim > 5:
|
|
raise ValueError(
|
|
f"Relative tensor has a wrong shape {x.shape}, "
|
|
f"it should have 4 or 5 dimensions."
|
|
)
|
|
if x_dim == 4:
|
|
# add fake depth dimension
|
|
x = tf.expand_dims(x, axis=-1)
|
|
|
|
batch = tf.shape(x)[0]
|
|
num_heads = tf.shape(x)[1]
|
|
length = tf.shape(x)[2]
|
|
depth = tf.shape(x)[-1]
|
|
|
|
x = tf.cond(
|
|
length > self.relative_length,
|
|
lambda: self._pad_relative_embeddings(x, length),
|
|
lambda: self._slice_relative_embeddings(x, length),
|
|
)
|
|
|
|
# add a column of zeros to "slide" columns to diagonals through reshape
|
|
pad_shift = tf.zeros_like(x[:, :, :, -1:, :])
|
|
x = tf.concat([x, pad_shift], axis=-2)
|
|
|
|
# flatten length dimensions
|
|
x = tf.reshape(x, (batch, num_heads, -1, depth))
|
|
width = 2 * length
|
|
|
|
# add zeros so that the result of back reshape is still a matrix
|
|
pad_flat = tf.zeros_like(
|
|
x[:, :, : ((width - 1) - width * length % (width - 1)) % (width - 1), :]
|
|
)
|
|
x = tf.concat([x, pad_flat], axis=-2)
|
|
|
|
# "slide" columns to diagonals through reshape
|
|
x = tf.reshape(x, (batch, num_heads, -1, width - 1, depth))
|
|
|
|
# slice needed "diagonal" matrix
|
|
x = x[:, :, :-1, -length:, :]
|
|
|
|
if x_dim == 4:
|
|
# remove fake depth dimension
|
|
x = tf.squeeze(x, axis=-1)
|
|
|
|
return x
|
|
|
|
def _matmul_with_relative_keys(self, x: tf.Tensor) -> tf.Tensor:
|
|
y = self.key_relative_embeddings
|
|
|
|
if self.heads_share_relative_embedding:
|
|
matmul = tf.einsum("bhld,md->bhlm", x, y)
|
|
else:
|
|
matmul = tf.einsum("bhld,hmd->bhlm", x, y)
|
|
|
|
return self._relative_to_absolute_position(matmul)
|
|
|
|
def _tile_relative_embeddings(self, x: tf.Tensor, length: tf.Tensor) -> tf.Tensor:
|
|
if self.heads_share_relative_embedding:
|
|
x = tf.expand_dims(x, axis=0) # add head dimension
|
|
|
|
x = tf.expand_dims(x, axis=1) # add length dimension
|
|
x = tf.tile(x, (1, length, 1, 1))
|
|
return tf.expand_dims(x, axis=0) # add batch dimension
|
|
|
|
def _squeeze_relative_embeddings(self, x: tf.Tensor) -> tf.Tensor:
|
|
x = tf.squeeze(x, axis=0) # squeeze batch dimension
|
|
if self.heads_share_relative_embedding:
|
|
x = tf.squeeze(x, axis=1) # squeeze head dimension
|
|
return x
|
|
|
|
def _matmul_with_relative_values(self, x: tf.Tensor) -> tf.Tensor:
|
|
y = self._tile_relative_embeddings(
|
|
self.value_relative_embeddings, tf.shape(x)[-2]
|
|
)
|
|
y = self._relative_to_absolute_position(y)
|
|
y = self._squeeze_relative_embeddings(y)
|
|
|
|
if self.heads_share_relative_embedding:
|
|
return tf.einsum("bhlm,lmd->bhld", x, y)
|
|
else:
|
|
return tf.einsum("bhlm,hlmd->bhld", x, y)
|
|
|
|
def _drop_attention_logits(
|
|
self, logits: tf.Tensor, pad_mask: tf.Tensor, training: tf.Tensor
|
|
) -> tf.Tensor:
|
|
def droped_logits() -> tf.Tensor:
|
|
keep_prob = tf.random.uniform(tf.shape(logits), 0, 1) + pad_mask
|
|
drop_mask = tf.cast(
|
|
tf.less(keep_prob, self.attention_dropout_rate), logits.dtype
|
|
)
|
|
|
|
return logits + drop_mask * -1e9
|
|
|
|
return smart_cond(training, droped_logits, lambda: tf.identity(logits))
|
|
|
|
def _scaled_dot_product_attention(
|
|
self,
|
|
query: tf.Tensor,
|
|
key: tf.Tensor,
|
|
value: tf.Tensor,
|
|
pad_mask: tf.Tensor,
|
|
training: tf.Tensor,
|
|
) -> Tuple[tf.Tensor, tf.Tensor]:
|
|
"""Calculate the attention weights.
|
|
|
|
query, key, value must have matching leading dimensions.
|
|
key, value must have matching penultimate dimension,
|
|
i.e.: seq_len_k = seq_len_v.
|
|
The mask has different shapes depending on its type (padding or look ahead)
|
|
but it must be broadcastable for addition.
|
|
|
|
Arguments:
|
|
query: A tensor with shape (..., length, depth).
|
|
key: A tensor with shape (..., length, depth).
|
|
value: A tensor with shape (..., length, depth).
|
|
pad_mask: Float tensor with shape broadcastable
|
|
to (..., length, length). Defaults to None.
|
|
|
|
Returns:
|
|
output: A tensor with shape (..., length, depth).
|
|
attention_weights: A tensor with shape (..., length, length).
|
|
"""
|
|
|
|
matmul_qk = tf.matmul(query, key, transpose_b=True) # (..., length, length)
|
|
|
|
if self.use_key_relative_position:
|
|
matmul_qk += self._matmul_with_relative_keys(query)
|
|
|
|
# scale matmul_qk
|
|
dk = tf.cast(tf.shape(key)[-1], tf.float32)
|
|
logits = matmul_qk / tf.math.sqrt(dk)
|
|
|
|
# add the mask to the scaled tensor.
|
|
if pad_mask is not None:
|
|
logits += pad_mask * -1e9
|
|
|
|
# apply attention dropout before softmax to maintain attention_weights norm as 1
|
|
if self.attention_dropout_rate > 0:
|
|
logits = self._drop_attention_logits(logits, pad_mask, training)
|
|
|
|
# softmax is normalized on the last axis (length) so that the scores
|
|
# add up to 1.
|
|
attention_weights = tf.nn.softmax(logits, axis=-1) # (..., length, length)
|
|
|
|
output = tf.matmul(attention_weights, value) # (..., length, depth)
|
|
if self.use_value_relative_position:
|
|
output += self._matmul_with_relative_values(attention_weights)
|
|
|
|
return output, attention_weights
|
|
|
|
def _split_heads(self, x: tf.Tensor) -> tf.Tensor:
|
|
"""Split the last dimension into (num_heads, depth).
|
|
|
|
Transpose the result such that the shape is
|
|
(batch_size, num_heads, length, depth)
|
|
"""
|
|
|
|
x = tf.reshape(x, (tf.shape(x)[0], -1, self.num_heads, self._depth))
|
|
return tf.transpose(x, perm=[0, 2, 1, 3])
|
|
|
|
def _combine_heads(self, x: tf.Tensor) -> tf.Tensor:
|
|
"""Inverse of split_heads.
|
|
|
|
Args:
|
|
x: A Tensor with shape [batch, num_heads, length, units / num_heads]
|
|
|
|
Returns:
|
|
A Tensor with shape [batch, length, units]
|
|
"""
|
|
|
|
# (batch_size, length, num_heads, depth)
|
|
x = tf.transpose(x, perm=[0, 2, 1, 3])
|
|
# (batch_size, length, units)
|
|
return tf.reshape(x, (tf.shape(x)[0], -1, self.units))
|
|
|
|
# noinspection PyMethodOverriding
|
|
def call(
|
|
self,
|
|
query_input: tf.Tensor,
|
|
source_input: tf.Tensor,
|
|
pad_mask: Optional[tf.Tensor] = None,
|
|
training: Optional[Union[tf.Tensor, bool]] = None,
|
|
) -> Tuple[tf.Tensor, tf.Tensor]:
|
|
"""Apply attention mechanism to query_input and source_input.
|
|
|
|
Arguments:
|
|
query_input: A tensor with shape [batch_size, length, input_size].
|
|
source_input: A tensor with shape [batch_size, length, input_size].
|
|
pad_mask: Float tensor with shape broadcastable
|
|
to (..., length, length). Defaults to None.
|
|
training: A bool, whether in training mode or not.
|
|
|
|
Returns:
|
|
Attention layer output with shape [batch_size, length, units]
|
|
"""
|
|
if training is None:
|
|
training = K.learning_phase()
|
|
|
|
query = self._query_dense_layer(query_input) # (batch_size, length, units)
|
|
key = self._key_dense_layer(source_input) # (batch_size, length, units)
|
|
value = self._value_dense_layer(source_input) # (batch_size, length, units)
|
|
|
|
query = self._split_heads(query) # (batch_size, num_heads, length, depth)
|
|
key = self._split_heads(key) # (batch_size, num_heads, length, depth)
|
|
value = self._split_heads(value) # (batch_size, num_heads, length, depth)
|
|
|
|
attention, attention_weights = self._scaled_dot_product_attention(
|
|
query, key, value, pad_mask, training
|
|
)
|
|
# attention.shape == (batch_size, num_heads, length, depth)
|
|
# attention_weights.shape == (batch_size, num_heads, length, length)
|
|
attention = self._combine_heads(attention) # (batch_size, length, units)
|
|
|
|
output = self._output_dense_layer(attention) # (batch_size, length, units)
|
|
|
|
return output, attention_weights
|
|
|
|
|
|
class TransformerEncoderLayer(tf.keras.layers.Layer):
|
|
"""Transformer encoder layer.
|
|
|
|
The layer is composed of the sublayers:
|
|
1. Self-attention layer
|
|
2. Feed-forward network (which is 2 fully-connected layers)
|
|
|
|
Arguments:
|
|
units: Positive integer, output dim of hidden layer.
|
|
num_heads: Positive integer, number of heads
|
|
to repeat the same attention structure.
|
|
filter_units: Positive integer, output dim of the first ffn hidden layer.
|
|
dropout_rate: Float between 0 and 1; fraction of the input units to drop.
|
|
attention_dropout_rate: Float, dropout rate inside attention for training.
|
|
density: Fraction of trainable weights in `RandomlyConnectedDense` layers.
|
|
unidirectional: Boolean, use a unidirectional or bidirectional encoder.
|
|
use_key_relative_position: Boolean, if 'True' use key
|
|
relative embeddings in attention.
|
|
use_value_relative_position: Boolean, if 'True' use value
|
|
relative embeddings in attention.
|
|
max_relative_position: Positive integer, max position for relative embeddings.
|
|
heads_share_relative_embedding: Boolean, if 'True'
|
|
heads will share relative embeddings.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
units: int,
|
|
num_heads: int,
|
|
filter_units: int,
|
|
dropout_rate: float = 0.1,
|
|
attention_dropout_rate: float = 0.0,
|
|
density: float = 0.2,
|
|
unidirectional: bool = False,
|
|
use_key_relative_position: bool = False,
|
|
use_value_relative_position: bool = False,
|
|
max_relative_position: int = 5,
|
|
heads_share_relative_embedding: bool = False,
|
|
) -> None:
|
|
super().__init__()
|
|
|
|
self._layer_norm = tf.keras.layers.LayerNormalization(epsilon=1e-6)
|
|
self._mha = MultiHeadAttention(
|
|
units,
|
|
num_heads,
|
|
attention_dropout_rate,
|
|
density,
|
|
unidirectional,
|
|
use_key_relative_position,
|
|
use_value_relative_position,
|
|
max_relative_position,
|
|
heads_share_relative_embedding,
|
|
)
|
|
self._dropout = tf.keras.layers.Dropout(dropout_rate)
|
|
|
|
self._ffn_layers = [
|
|
tf.keras.layers.LayerNormalization(epsilon=1e-6),
|
|
RandomlyConnectedDense(
|
|
units=filter_units, activation=tf.nn.gelu, density=density
|
|
), # (batch_size, length, filter_units)
|
|
tf.keras.layers.Dropout(dropout_rate),
|
|
RandomlyConnectedDense(
|
|
units=units, density=density
|
|
), # (batch_size, length, units)
|
|
tf.keras.layers.Dropout(dropout_rate),
|
|
]
|
|
|
|
def call(
|
|
self,
|
|
x: tf.Tensor,
|
|
pad_mask: Optional[tf.Tensor] = None,
|
|
training: Optional[Union[tf.Tensor, bool]] = None,
|
|
) -> Tuple[tf.Tensor, tf.Tensor]:
|
|
"""Apply transformer encoder layer.
|
|
|
|
Arguments:
|
|
x: A tensor with shape [batch_size, length, units].
|
|
pad_mask: Float tensor with shape broadcastable
|
|
to (..., length, length). Defaults to None.
|
|
training: A bool, whether in training mode or not.
|
|
|
|
Returns:
|
|
Transformer encoder layer output with shape [batch_size, length, units]
|
|
"""
|
|
if training is None:
|
|
training = K.learning_phase()
|
|
|
|
x_norm = self._layer_norm(x) # (batch_size, length, units)
|
|
attn_out, attn_weights = self._mha(
|
|
x_norm, x_norm, pad_mask=pad_mask, training=training
|
|
)
|
|
attn_out = self._dropout(attn_out, training=training)
|
|
x += attn_out
|
|
|
|
ffn_out = x # (batch_size, length, units)
|
|
for layer in self._ffn_layers:
|
|
ffn_out = layer(ffn_out, training=training)
|
|
x += ffn_out
|
|
|
|
# (batch_size, length, units), (batch_size, num_heads, length, length)
|
|
return x, attn_weights
|
|
|
|
|
|
class TransformerEncoder(tf.keras.layers.Layer):
|
|
"""Transformer encoder.
|
|
|
|
Encoder stack is made up of `num_layers` identical encoder layers.
|
|
|
|
Arguments:
|
|
num_layers: Positive integer, number of encoder layers.
|
|
units: Positive integer, output dim of hidden layer.
|
|
num_heads: Positive integer, number of heads
|
|
to repeat the same attention structure.
|
|
filter_units: Positive integer, output dim of the first ffn hidden layer.
|
|
reg_lambda: Float, regularization factor.
|
|
dropout_rate: Float between 0 and 1; fraction of the input units to drop.
|
|
attention_dropout_rate: Float, dropout rate inside attention for training.
|
|
density: Approximate fraction of trainable weights (in
|
|
`RandomlyConnectedDense` layers).
|
|
unidirectional: Boolean, use a unidirectional or bidirectional encoder.
|
|
use_key_relative_position: Boolean, if 'True' use key
|
|
relative embeddings in attention.
|
|
use_value_relative_position: Boolean, if 'True' use value
|
|
relative embeddings in attention.
|
|
max_relative_position: Positive integer, max position for relative embeddings.
|
|
heads_share_relative_embedding: Boolean, if 'True'
|
|
heads will share relative embeddings.
|
|
name: Optional name of the layer.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
num_layers: int,
|
|
units: int,
|
|
num_heads: int,
|
|
filter_units: int,
|
|
reg_lambda: float,
|
|
dropout_rate: float = 0.1,
|
|
attention_dropout_rate: float = 0.0,
|
|
density: float = 0.2,
|
|
unidirectional: bool = False,
|
|
use_key_relative_position: bool = False,
|
|
use_value_relative_position: bool = False,
|
|
max_relative_position: int = 5,
|
|
heads_share_relative_embedding: bool = False,
|
|
name: Optional[Text] = None,
|
|
) -> None:
|
|
super().__init__(name=name)
|
|
|
|
self.units = units
|
|
self.unidirectional = unidirectional
|
|
|
|
l2_regularizer = tf.keras.regularizers.l2(reg_lambda)
|
|
self._embedding = RandomlyConnectedDense(
|
|
units=units, kernel_regularizer=l2_regularizer, density=density
|
|
)
|
|
# positional encoding helpers
|
|
self._angles = self._get_angles()
|
|
self._even_indices = np.arange(0, self.units, 2, dtype=np.int32)[:, np.newaxis]
|
|
self._odd_indices = np.arange(1, self.units, 2, dtype=np.int32)[:, np.newaxis]
|
|
|
|
self._dropout = tf.keras.layers.Dropout(dropout_rate)
|
|
|
|
self._enc_layers = [
|
|
TransformerEncoderLayer(
|
|
units,
|
|
num_heads,
|
|
filter_units,
|
|
dropout_rate,
|
|
attention_dropout_rate,
|
|
density,
|
|
unidirectional,
|
|
use_key_relative_position,
|
|
use_value_relative_position,
|
|
max_relative_position,
|
|
heads_share_relative_embedding,
|
|
)
|
|
for _ in range(num_layers)
|
|
]
|
|
self._layer_norm = tf.keras.layers.LayerNormalization(epsilon=1e-6)
|
|
|
|
def _get_angles(self) -> np.ndarray:
|
|
array_2d = np.arange(self.units)[np.newaxis, :]
|
|
return 1 / np.power(10000, (2 * (array_2d // 2)) / np.float32(self.units))
|
|
|
|
def _positional_encoding(self, max_position: tf.Tensor) -> tf.Tensor:
|
|
max_position = tf.cast(max_position, dtype=tf.float32)
|
|
angle_rads = tf.range(max_position)[:, tf.newaxis] * self._angles
|
|
|
|
# transpose for easy slicing
|
|
angle_rads = tf.transpose(angle_rads, perm=[1, 0])
|
|
shape = tf.shape(angle_rads)
|
|
# apply sin to even indices in the array; 2i
|
|
sin_even = tf.sin(tf.gather_nd(angle_rads, self._even_indices))
|
|
pos_encoding_even = tf.scatter_nd(self._even_indices, sin_even, shape)
|
|
# apply cos to odd indices in the array; 2i+1
|
|
cos_odd = tf.cos(tf.gather_nd(angle_rads, self._odd_indices))
|
|
pos_encoding_odd = tf.scatter_nd(self._odd_indices, cos_odd, shape)
|
|
# combine even and odd positions and transpose back
|
|
pos_encoding = tf.transpose(pos_encoding_even + pos_encoding_odd, perm=[1, 0])
|
|
# add batch dimension
|
|
return tf.stop_gradient(pos_encoding[tf.newaxis, ...])
|
|
|
|
@staticmethod
|
|
def _look_ahead_pad_mask(max_position: tf.Tensor) -> tf.Tensor:
|
|
pad_mask = 1 - tf.linalg.band_part(tf.ones((max_position, max_position)), -1, 0)
|
|
return pad_mask[tf.newaxis, tf.newaxis, :, :] # (1, 1, seq_len, seq_len)
|
|
|
|
def call(
|
|
self,
|
|
x: tf.Tensor,
|
|
pad_mask: Optional[tf.Tensor] = None,
|
|
training: Optional[Union[tf.Tensor, bool]] = None,
|
|
) -> Tuple[tf.Tensor, tf.Tensor]:
|
|
"""Apply transformer encoder.
|
|
|
|
Arguments:
|
|
x: A tensor with shape [batch_size, length, input_size].
|
|
pad_mask: Float tensor with shape broadcastable
|
|
to (..., length, length). Defaults to None.
|
|
training: A bool, whether in training mode or not.
|
|
|
|
Returns:
|
|
Transformer encoder output with shape [batch_size, length, units]
|
|
"""
|
|
# adding embedding and position encoding.
|
|
x = self._embedding(x) # (batch_size, length, units)
|
|
x *= tf.math.sqrt(tf.cast(self.units, tf.float32))
|
|
x += self._positional_encoding(tf.shape(x)[1])
|
|
x = self._dropout(x, training=training)
|
|
|
|
if pad_mask is not None:
|
|
pad_mask = tf.squeeze(pad_mask, -1) # (batch_size, length)
|
|
pad_mask = pad_mask[:, tf.newaxis, tf.newaxis, :]
|
|
# pad_mask.shape = (batch_size, 1, 1, length)
|
|
if self.unidirectional:
|
|
# add look ahead pad mask to emulate unidirectional behavior
|
|
pad_mask = tf.minimum(
|
|
1.0, pad_mask + self._look_ahead_pad_mask(tf.shape(pad_mask)[-1])
|
|
) # (batch_size, 1, length, length)
|
|
|
|
layer_attention_weights = []
|
|
|
|
for layer in self._enc_layers:
|
|
x, attn_weights = layer(x, pad_mask=pad_mask, training=training)
|
|
layer_attention_weights.append(attn_weights)
|
|
|
|
# if normalization is done in encoding layers, then it should also be done
|
|
# on the output, since the output can grow very large, being the sum of
|
|
# a whole stack of unnormalized layer outputs.
|
|
x = self._layer_norm(x) # (batch_size, length, units)
|
|
|
|
# Keep the batch dimension on the first axis
|
|
attention_weights_as_output = tf.transpose(
|
|
tf.stack(layer_attention_weights), (1, 0, 2, 3, 4)
|
|
)
|
|
|
|
# (batch_size, length, units),
|
|
# (batch_size, num_layers, num_heads, length, length)
|
|
return x, attention_weights_as_output
|