Spaces:
Paused
Paused
Download layers.py from andreamena/PDCNet: direct link, hf CLI and curl.
- Browser
- Download file 21.8 kB
-
https://huggingface.co/spaces/andreamena/PDCNet/resolve/main/layers.py
- Command line
-
hf download hf://spaces/andreamena/PDCNet/layers.py
-
curl -L -o layers.py https://huggingface.co/spaces/andreamena/PDCNet/resolve/main/layers.py
21.8 kB
| #!/usr/bin/env python3 | |
| # -*- coding: utf-8 -*- | |
| """ | |
| Created on Fri Sep 27 13:11:00 2019 | |
| Script for Multi-Head Scaled Dot-Product Attention and Context Attention layers | |
| @author: Chenkai Li | |
| """ | |
| from tf_keras import backend as K | |
| from tf_keras import initializers, regularizers, constraints, activations | |
| from tf_keras.layers import Layer | |
| class Attention(Layer): | |
| """ | |
| Adapted from https://github.com/lzfelix/keras_attention | |
| Implementation based on the work of Yang et al. "Hierarchical | |
| Attention Networks for Document Classification". | |
| [https://www.aclweb.org/anthology/N16-1174] | |
| The mathematical formulation of the model is as follows: | |
| ``` | |
| u = f(W * h + b), | |
| a_i = softmax(u_i^T * u_s), | |
| v_i = \sigma_i a_i * h_i. | |
| ``` | |
| Where h are the input tensors with shape (batch, n_timesteps, hidden_size), for | |
| instance, all hidden vectors produced by a recurrent layer, such as a LSTM and the | |
| output has shape `(batch, hidden_size)`. This layer also works with inputs with more | |
| than 3 dimensions as well, such as sentences in a document, where each input has | |
| size (batch, n_docs, n_sentences, embedding_size), outputing | |
| (batch, n_docs, embedding_size)`. | |
| # Arguments | |
| activation: The activation function f used by the layer (see | |
| [activations](../activations.md)). By default tanh is used, another common | |
| option is "linear". | |
| use_bias: Boolean, whether the layer uses a bias vector. | |
| initializer: Initializer for the `kernel` and `context` matrices | |
| (see [initializers](../initializers.md)). | |
| return_attention: If True, instead of returning the sequence descriptor, this | |
| layer will return the computed attention coefficients for each of the | |
| sequence timesteps. See Output section for details. | |
| W_regularizer: Regularizer function applied to the `kernel` weights matrix | |
| (see [regularizer](../regularizers.md)). | |
| u_regularizer: Regularizer function applied to the `context` weights matrix | |
| (see [regularizer](../regularizers.md)). | |
| b_regularizer: Regularizer function applied to the bias vector | |
| (see [regularizer](../regularizers.md)). | |
| W_constraint: Constraint function applied to the `kernel` weights matrix | |
| (see [constraints](../constraints.md)). | |
| u_constraint: Constraint function applied to the `contextl` weights matrix | |
| (see [constraints](../constraints.md)). | |
| b_constraint: Constraint function applied to the bias vector | |
| (see [constraints](../constraints.md)). | |
| # Input shape | |
| nD tensor with shape: `(batch_size, ..., timesteps, input_dim)`. | |
| The most common situation would be a 3D input with shape | |
| `(batch_size, timesteps, input_dim)`. | |
| # Outuput shape | |
| The sequence descriptor with shape `(batch_size, ..., timestamps)`. If | |
| `return_attention` is True, this layer will return the `alpha_i` weights | |
| for each timestep, and consequently its output shape will be different, namely: | |
| `(batch_size, ..., timesteps)`. | |
| """ | |
| def __init__(self, | |
| activation='tanh', | |
| initializer='glorot_uniform', | |
| return_attention=False, | |
| W_regularizer=None, | |
| u_regularizer=None, | |
| b_regularizer=None, | |
| W_constraint=None, | |
| u_constraint=None, | |
| b_constraint=None, | |
| bias=True, | |
| **kwargs): | |
| self.activation = activations.get(activation) | |
| self.initializer = initializers.get(initializer) | |
| self.W_regularizer = regularizers.get(W_regularizer) | |
| self.u_regularizer = regularizers.get(u_regularizer) | |
| self.b_regularizer = regularizers.get(b_regularizer) | |
| self.W_constraint = constraints.get(W_constraint) | |
| self.u_constraint = constraints.get(u_constraint) | |
| self.b_constraint = constraints.get(b_constraint) | |
| self.bias = bias | |
| self.supports_masking = True | |
| self.return_attention = return_attention | |
| super().__init__(**kwargs) | |
| def build(self, input_shape): | |
| # input_shape: (batch, time, amount_features) | |
| # the attention size matches the feature dimension | |
| amount_features = input_shape[-1] | |
| attention_size = input_shape[-1] | |
| self.W = self.add_weight(shape=(amount_features, attention_size), | |
| initializer=self.initializer, | |
| regularizer=self.W_regularizer, | |
| constraint=self.W_constraint, | |
| name='attention_W') | |
| self.b = None | |
| if self.bias: | |
| self.b = self.add_weight(shape=(attention_size,), | |
| initializer='zero', | |
| regularizer=self.b_regularizer, | |
| constraint=self.b_constraint, | |
| name='attention_b') | |
| self.context = self.add_weight(shape=(attention_size,), | |
| initializer=self.initializer, | |
| regularizer=self.u_regularizer, | |
| constraint=self.u_constraint, | |
| name='attention_us') | |
| super().build(input_shape) | |
| def call(self, x, mask=None): | |
| # U = tanh(H*W + b) (eq. 8) | |
| ui = K.dot(x, self.W) # (b, t, a) | |
| if self.b is not None: | |
| ui += self.b | |
| ui = self.activation(ui) # (b, t, a) | |
| # Z = U * us (eq. 9) | |
| us = K.expand_dims(self.context) # (a, 1) | |
| ui_us = K.dot(ui, us) # (b, t, a) * (a, 1) = (b, t, 1) | |
| ui_us = K.squeeze(ui_us, axis=-1) # (b, t, 1) -> (b, t) | |
| # alpha = softmax(Z) (eq. 9) | |
| alpha = self._masked_softmax(ui_us, mask) # (b, t) | |
| alpha = K.expand_dims(alpha, axis=-1) # (b, t, 1) | |
| if self.return_attention: | |
| return alpha | |
| else: | |
| # v = alpha_i * x_i (eq. 10) | |
| return K.sum(x * alpha, axis=1) | |
| def _masked_softmax(self, logits, mask): | |
| """Keras's default implementation of softmax doesn't allow masking, while | |
| this method does if `mask` is not `None`.""" | |
| # softmax(x): | |
| # b = max(x) | |
| # s_i = exp(xi - b) / exp(xj - b) | |
| # return s | |
| b = K.max(logits, axis=-1, keepdims=True) | |
| logits = logits - b | |
| exped = K.exp(logits) | |
| # ignoring masked inputs | |
| if mask is not None: | |
| mask = K.cast(mask, K.floatx()) | |
| exped *= mask | |
| partition = K.sum(exped, axis=-1, keepdims=True) | |
| # if all timesteps are masked, the partition will be zero. To avoid this | |
| # issue we use the following trick: | |
| partition = K.maximum(partition, K.epsilon()) | |
| return exped / partition | |
| def compute_output_shape(self, input_shape): | |
| """The attention mechanism computes a weighted average between | |
| all hidden vectors generated by the previous sequential layer, | |
| hence the input is expected to be | |
| `(batch_size, seq_len, amount_features)` if `return_attention` is | |
| `False`, otherwise the output should be (batch_size, seq_len).""" | |
| if self.return_attention: | |
| return input_shape[:-1] | |
| else: | |
| return input_shape[:-2] + input_shape[-1:] | |
| def compute_mask(self, x, input_mask=None): | |
| """This layer produces a single attended vector from a list | |
| of hidden vectors, hence it can't be masked as this means | |
| masking a single vector.""" | |
| return None | |
| def get_config(self): | |
| config = { | |
| 'activation': self.activation, | |
| 'initializer': self.initializer, | |
| 'return_attention': self.return_attention, | |
| 'W_regularizer': initializers.serialize(self.W_regularizer), | |
| 'u_regularizer': initializers.serialize(self.u_regularizer), | |
| 'b_regularizer': initializers.serialize(self.b_regularizer), | |
| 'W_constraint': constraints.serialize(self.W_constraint), | |
| 'u_constraint': constraints.serialize(self.u_constraint), | |
| 'b_constraint': constraints.serialize(self.b_constraint), | |
| 'bias': self.bias | |
| } | |
| base_config = super().get_config() | |
| return dict(list(base_config.items()) + list(config.items())) | |
| class ScaledDotProductAttention(Layer): | |
| """ | |
| Adapted from https://github.com/CyberZHG/keras-self-attention | |
| Implementation based on the work of Vaswani et al. "Attention Is All You | |
| Need". | |
| [https://arxiv.org/pdf/1706.03762.pdf] | |
| The attention layer that takes three inputs representing queries, keys and values. | |
| \text{Attention}(Q, K, V) = \text{softmax}(\frac{Q K^T}{\sqrt{d_k}}) V | |
| """ | |
| def __init__(self, | |
| return_attention=False, | |
| history_only=False, | |
| **kwargs): | |
| """Initialize the layer. | |
| :param return_attention: Whether to return attention weights. | |
| :param history_only: Whether to only use history data. | |
| :param kwargs: Arguments for parent class. | |
| """ | |
| super(ScaledDotProductAttention, self).__init__(**kwargs) | |
| self.supports_masking = True | |
| self.return_attention = return_attention | |
| self.history_only = history_only | |
| def get_config(self): | |
| config = { | |
| 'return_attention': self.return_attention, | |
| 'history_only': self.history_only, | |
| } | |
| base_config = super(ScaledDotProductAttention, self).get_config() | |
| return dict(list(base_config.items()) + list(config.items())) | |
| def compute_output_shape(self, input_shape): | |
| if isinstance(input_shape, list): | |
| query_shape, key_shape, value_shape = input_shape | |
| else: | |
| query_shape = key_shape = value_shape = input_shape | |
| output_shape = query_shape[:-1] + value_shape[-1:] | |
| if self.return_attention: | |
| attention_shape = query_shape[:2] + (key_shape[1],) | |
| return [output_shape, attention_shape] | |
| return output_shape | |
| def compute_mask(self, inputs, mask=None): | |
| if isinstance(mask, list): | |
| mask = mask[0] | |
| if self.return_attention: | |
| return [mask, None] | |
| return mask | |
| def call(self, inputs, mask=None, **kwargs): | |
| if isinstance(inputs, list): | |
| query, key, value = inputs | |
| else: | |
| query = key = value = inputs | |
| if isinstance(mask, list): | |
| mask = mask[1] | |
| feature_dim = K.shape(query)[-1] | |
| e = K.batch_dot(query, key, axes=2) / K.sqrt(K.cast(feature_dim, dtype=K.floatx())) | |
| e = K.exp(e - K.max(e, axis=-1, keepdims=True)) | |
| if self.history_only: | |
| query_len, key_len = K.shape(query)[1], K.shape(key)[1] | |
| indices = K.expand_dims(K.arange(0, key_len), axis=0) | |
| upper = K.expand_dims(K.arange(0, query_len), axis=-1) | |
| e *= K.expand_dims(K.cast(indices <= upper, K.floatx()), axis=0) | |
| if mask is not None: | |
| e *= K.cast(K.expand_dims(mask, axis=-2), K.floatx()) | |
| a = e / (K.sum(e, axis=-1, keepdims=True) + K.epsilon()) | |
| v = K.batch_dot(a, value) | |
| if self.return_attention: | |
| return [v, a] | |
| return v | |
| class MultiHeadAttention(Layer): | |
| """ | |
| Adapted from https://github.com/CyberZHG/keras-multi-head | |
| Implementation based on the work of Vaswani et al. "Attention Is All You | |
| Need". | |
| [https://arxiv.org/pdf/1706.03762.pdf] | |
| Multi-head scaled dot-product attention layer | |
| """ | |
| def __init__(self, | |
| head_num, | |
| activation='relu', | |
| use_bias=True, | |
| kernel_initializer='glorot_normal', | |
| bias_initializer='zeros', | |
| kernel_regularizer=None, | |
| bias_regularizer=None, | |
| kernel_constraint=None, | |
| bias_constraint=None, | |
| history_only=False, | |
| return_multi_attention=False, | |
| **kwargs): | |
| """Initialize the layer. | |
| :param head_num: Number of heads. | |
| :param activation: Activations for linear mappings. | |
| :param use_bias: Whether to use bias term. | |
| :param kernel_initializer: Initializer for linear mappings. | |
| :param bias_initializer: Initializer for linear mappings. | |
| :param kernel_regularizer: Regularizer for linear mappings. | |
| :param bias_regularizer: Regularizer for linear mappings. | |
| :param kernel_constraint: Constraints for linear mappings. | |
| :param bias_constraint: Constraints for linear mappings. | |
| :param history_only: Whether to only use history in attention layer. | |
| :param return_multi_attention: Whether to return the attention matrix. | |
| """ | |
| self.supports_masking = True | |
| self.head_num = head_num | |
| self.activation = activations.get(activation) | |
| self.use_bias = use_bias | |
| self.kernel_initializer = initializers.get(kernel_initializer) | |
| self.bias_initializer = initializers.get(bias_initializer) | |
| self.kernel_regularizer = regularizers.get(kernel_regularizer) | |
| self.bias_regularizer = regularizers.get(bias_regularizer) | |
| self.kernel_constraint = constraints.get(kernel_constraint) | |
| self.bias_constraint = constraints.get(bias_constraint) | |
| self.history_only = history_only | |
| self.return_multi_attention = return_multi_attention | |
| self.Wq, self.Wk, self.Wv, self.Wo = None, None, None, None | |
| self.bq, self.bk, self.bv, self.bo = None, None, None, None | |
| super(MultiHeadAttention, self).__init__(**kwargs) | |
| def get_config(self): | |
| config = { | |
| 'head_num': self.head_num, | |
| 'activation': activations.serialize(self.activation), | |
| 'use_bias': self.use_bias, | |
| 'kernel_initializer': initializers.serialize(self.kernel_initializer), | |
| 'bias_initializer': initializers.serialize(self.bias_initializer), | |
| 'kernel_regularizer': regularizers.serialize(self.kernel_regularizer), | |
| 'bias_regularizer': regularizers.serialize(self.bias_regularizer), | |
| 'kernel_constraint': constraints.serialize(self.kernel_constraint), | |
| 'bias_constraint': constraints.serialize(self.bias_constraint), | |
| 'history_only': self.history_only, | |
| 'return_multi_attention': self.return_multi_attention | |
| } | |
| base_config = super(MultiHeadAttention, self).get_config() | |
| return dict(list(base_config.items()) + list(config.items())) | |
| def compute_output_shape(self, input_shape): | |
| if self.return_multi_attention: | |
| if isinstance(input_shape, list): | |
| q, k, v = input_shape | |
| return (q[0], self.head_num, q[1], k[1]) | |
| return (input_shape[0], self.head_num, input_shape[1], input_shape[1]) | |
| else: | |
| if isinstance(input_shape, list): | |
| q, k, v = input_shape | |
| return q[:-1] + (v[-1],) | |
| return input_shape | |
| def compute_mask(self, inputs, input_mask=None): | |
| if isinstance(input_mask, list): | |
| return input_mask[0] | |
| return input_mask | |
| def build(self, input_shape): | |
| if isinstance(input_shape, list): | |
| q, k, v = input_shape | |
| else: | |
| q = k = v = input_shape | |
| feature_dim = int(v[-1]) | |
| if feature_dim % self.head_num != 0: | |
| raise IndexError('Invalid head number %d with the given input dim %d' % (self.head_num, feature_dim)) | |
| self.Wq = self.add_weight( | |
| shape=(int(q[-1]), feature_dim), | |
| initializer=self.kernel_initializer, | |
| regularizer=self.kernel_regularizer, | |
| constraint=self.kernel_constraint, | |
| name='%s_Wq' % self.name, | |
| ) | |
| if self.use_bias: | |
| self.bq = self.add_weight( | |
| shape=(feature_dim,), | |
| initializer=self.bias_initializer, | |
| regularizer=self.bias_regularizer, | |
| constraint=self.bias_constraint, | |
| name='%s_bq' % self.name, | |
| ) | |
| self.Wk = self.add_weight( | |
| shape=(int(k[-1]), feature_dim), | |
| initializer=self.kernel_initializer, | |
| regularizer=self.kernel_regularizer, | |
| constraint=self.kernel_constraint, | |
| name='%s_Wk' % self.name, | |
| ) | |
| if self.use_bias: | |
| self.bk = self.add_weight( | |
| shape=(feature_dim,), | |
| initializer=self.bias_initializer, | |
| regularizer=self.bias_regularizer, | |
| constraint=self.bias_constraint, | |
| name='%s_bk' % self.name, | |
| ) | |
| self.Wv = self.add_weight( | |
| shape=(int(v[-1]), feature_dim), | |
| initializer=self.kernel_initializer, | |
| regularizer=self.kernel_regularizer, | |
| constraint=self.kernel_constraint, | |
| name='%s_Wv' % self.name, | |
| ) | |
| if self.use_bias: | |
| self.bv = self.add_weight( | |
| shape=(feature_dim,), | |
| initializer=self.bias_initializer, | |
| regularizer=self.bias_regularizer, | |
| constraint=self.bias_constraint, | |
| name='%s_bv' % self.name, | |
| ) | |
| self.Wo = self.add_weight( | |
| shape=(feature_dim, feature_dim), | |
| initializer=self.kernel_initializer, | |
| regularizer=self.kernel_regularizer, | |
| constraint=self.kernel_constraint, | |
| name='%s_Wo' % self.name, | |
| ) | |
| if self.use_bias: | |
| self.bo = self.add_weight( | |
| shape=(feature_dim,), | |
| initializer=self.bias_initializer, | |
| regularizer=self.bias_regularizer, | |
| constraint=self.bias_constraint, | |
| name='%s_bo' % self.name, | |
| ) | |
| super(MultiHeadAttention, self).build(input_shape) | |
| def _reshape_to_batches(x, head_num): | |
| input_shape = K.shape(x) | |
| batch_size, seq_len, feature_dim = input_shape[0], input_shape[1], input_shape[2] | |
| head_dim = feature_dim // head_num | |
| x = K.reshape(x, (batch_size, seq_len, head_num, head_dim)) | |
| x = K.permute_dimensions(x, [0, 2, 1, 3]) | |
| return K.reshape(x, (batch_size * head_num, seq_len, head_dim)) | |
| def _reshape_from_batches(x, head_num): | |
| input_shape = K.shape(x) | |
| batch_size, seq_len, feature_dim = input_shape[0], input_shape[1], input_shape[2] | |
| x = K.reshape(x, (batch_size // head_num, head_num, seq_len, feature_dim)) | |
| x = K.permute_dimensions(x, [0, 2, 1, 3]) | |
| return K.reshape(x, (batch_size // head_num, seq_len, feature_dim * head_num)) | |
| def _reshape_attention_from_batches(x, head_num): | |
| input_shape = K.shape(x) | |
| batch_size, seq_len = input_shape[0], input_shape[1] | |
| return K.reshape(x, (batch_size // head_num, head_num, seq_len, seq_len)) | |
| def _reshape_mask(mask, head_num): | |
| if mask is None: | |
| return mask | |
| seq_len = K.shape(mask)[1] | |
| mask = K.expand_dims(mask, axis=1) | |
| mask = K.tile(mask, [1, head_num, 1]) | |
| return K.reshape(mask, (-1, seq_len)) | |
| def call(self, inputs, mask=None): | |
| if isinstance(inputs, list): | |
| q, k, v = inputs | |
| else: | |
| q = k = v = inputs | |
| if isinstance(mask, list): | |
| q_mask, k_mask, v_mask = mask | |
| else: | |
| q_mask = k_mask = v_mask = mask | |
| q = K.dot(q, self.Wq) | |
| k = K.dot(k, self.Wk) | |
| v = K.dot(v, self.Wv) | |
| if self.use_bias: | |
| q += self.bq | |
| k += self.bk | |
| v += self.bv | |
| if self.activation is not None: | |
| q = self.activation(q) | |
| k = self.activation(k) | |
| v = self.activation(v) | |
| y,a = ScaledDotProductAttention( | |
| return_attention=True, | |
| history_only=self.history_only, | |
| name='%s-Attention' % self.name, | |
| )( | |
| inputs=[ | |
| self._reshape_to_batches(q, self.head_num), | |
| self._reshape_to_batches(k, self.head_num), | |
| self._reshape_to_batches(v, self.head_num), | |
| ], | |
| mask=[ | |
| self._reshape_mask(q_mask, self.head_num), | |
| self._reshape_mask(k_mask, self.head_num), | |
| self._reshape_mask(v_mask, self.head_num), | |
| ], | |
| ) | |
| y = self._reshape_from_batches(y, self.head_num) | |
| a = self._reshape_attention_from_batches(a, self.head_num) | |
| y = K.dot(y, self.Wo) | |
| if self.use_bias: | |
| y += self.bo | |
| if self.activation is not None: | |
| y = self.activation(y) | |
| ''' | |
| if TF_KERAS: | |
| # Add shape information to tensor when using `tf.keras` | |
| input_shape = [K.int_shape(q), K.int_shape(k), K.int_shape(v)] | |
| output_shape = self.compute_output_shape(input_shape) | |
| if output_shape[1] is not None: | |
| output_shape = (-1,) + output_shape[1:] | |
| y = K.reshape(y, output_shape) | |
| ''' | |
| if self.return_multi_attention: | |
| return a | |
| return y |