Skip to content

Commit faa3fc9

Browse files
authored
Merge pull request #10 from eliorc/dev
Major changes, better interfaces and corrections to masking mechanisms
2 parents a499d13 + 4ebb9a6 commit faa3fc9

11 files changed

Lines changed: 238 additions & 245 deletions

File tree

.circleci/config.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,6 @@ jobs:
5656
source venv/bin/activate
5757
pytest -s --cov=tavolo tests/
5858
codecov
59-
test-3.8:
60-
<<: *test-template
61-
docker:
62-
- image: circleci/python:3.8
6359
test-3.6:
6460
<<: *test-template
6561
docker:

README.rst

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
------------
88

9-
.. image:: https://img.shields.io/badge/python-3.5%20%7C%203.6%20%7C%203.7%20%7C%203.8-blue.svg
9+
.. image:: https://img.shields.io/badge/python-3.5%20%7C%203.6%20%7C%203.7-blue.svg
1010
:alt: Supported Python versions
1111

1212
.. image:: https://img.shields.io/badge/tensorflow-2.0-orange.svg
@@ -42,7 +42,7 @@ Showcase
4242
| tavolo's API is straightforward and adopting its modules is as easy as it gets.
4343
| In tavolo, you'll find implementations for basic layers like `PositionalEncoding`_ to complex modules like the Transformer's
4444
`MultiHeadedAttention`_. You'll also find non-layer implementations that can ease development, like the `LearningRateFinder`_.
45-
| For example, if we wanted to add head a multi-headed attention mechanism into our model and look for the optimal learning rate, it would look something like:
45+
| For example, if we wanted to add head a Yang-style attention mechanism into our model and look for the optimal learning rate, it would look something like:
4646
4747
.. code-block:: python3
4848
@@ -51,12 +51,11 @@ Showcase
5151
5252
model = tf.keras.Sequential([
5353
tf.keras.layers.Embedding(input_dim=vocab_size, output_dim=embedding_size, input_length=max_len),
54-
tvl.seq2seq.MultiHeadedAttention(n_heads=8), # <--- Add self attention
55-
tf.keras.layers.LSTM(n_lstm_units, return_sequences=True),
54+
tvl.seq2vec.YangAttention(n_units=64), # <--- Add Yang style attention
5655
tf.keras.layers.Dense(n_hidden_units, activation='relu'),
5756
tf.keras.layers.Dense(1, activation='sigmoid')])
5857
59-
model.compile(optimizer=tf.keras.optimizers.SGD(), loss=tf.keras.losses.CategoricalCrossentropy())
58+
model.compile(optimizer=tf.keras.optimizers.SGD(), loss=tf.keras.losses.BinaryCrossentropy())
6059
6160
# Run learning rate range test
6261
lr_finder = tvl.learning.LearningRateFinder(model=model)

docs/source/index.rst

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Showcase
1616
| tavolo's API is straightforward and adopting its modules is as easy as it gets.
1717
| In tavolo, you'll find implementations for basic layers like :ref:`positional_encoding` to complex modules like the Transformer's
1818
:ref:`multi_headed_attention`. You'll also find non-layer implementations that can ease development, like the :ref:`learning_rate_finder`.
19-
| For example, if we wanted to add head a multi-headed attention mechanism into our model and look for the optimal learning rate, it would look something like:
19+
| For example, if we wanted to add head a Yang-style attention mechanism into our model and look for the optimal learning rate, it would look something like:
2020
2121
.. code-block:: python3
2222
@@ -25,12 +25,11 @@ Showcase
2525
2626
model = tf.keras.Sequential([
2727
tf.keras.layers.Embedding(input_dim=vocab_size, output_dim=embedding_size, input_length=max_len),
28-
tvl.seq2seq.MultiHeadedAttention(n_heads=8), # <--- Add self attention
29-
tf.keras.layers.LSTM(n_lstm_units, return_sequences=True),
28+
tvl.seq2vec.YangAttention(n_units=64), # <--- Add Yang style attention
3029
tf.keras.layers.Dense(n_hidden_units, activation='relu'),
3130
tf.keras.layers.Dense(1, activation='sigmoid')])
3231
33-
model.compile(optimizer=tf.keras.optimizers.SGD(), loss=tf.keras.losses.CategoricalCrossentropy())
32+
model.compile(optimizer=tf.keras.optimizers.SGD(), loss=tf.keras.losses.BinaryCrossentropy())
3433
3534
# Run learning rate range test
3635
lr_finder = tvl.learning.LearningRateFinder(model=model)

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from setuptools import setup
22

3-
VERSION = '0.5.1'
3+
VERSION = '0.6.0'
44

55
setup(name='tavolo',
66
version=VERSION,

tavolo/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
__name__ = 'tavolo'
2-
__version__ = '0.5.1'
2+
__version__ = '0.6.0'
33

44
from . import embeddings
55
from . import seq2vec

tavolo/embeddings.py

Lines changed: 91 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import numpy as np
44
import tensorflow as tf
5+
from tensorflow.python.ops import math_ops
56

67

78
class PositionalEncoding(tf.keras.layers.Layer):
@@ -67,6 +68,7 @@ def __init__(self,
6768
name: str = 'positional_encoding',
6869
**kwargs):
6970
"""
71+
7072
:param max_sequence_length: Maximum sequence length of input
7173
:param embedding_dim: Dimensionality of the of the input's last dimension
7274
:param normalize_factor: Normalize factor
@@ -104,12 +106,13 @@ def compute_mask(self, inputs, mask=None):
104106
def call(self, inputs,
105107
mask: Optional[tf.Tensor] = None,
106108
**kwargs) -> tf.Tensor:
107-
output = inputs + self.positional_encoding
109+
output = inputs + self.positional_encoding # shape=(batch_size, time_steps, channels)
110+
108111
if mask is not None:
109112
output = tf.where(tf.tile(tf.expand_dims(mask, axis=-1), multiples=[1, 1, inputs.shape[-1]]), output,
110-
inputs)
113+
inputs) # shape=(batch_size, time_steps, channels)
111114

112-
return output # shape=(batch_size, time_steps, channels)
115+
return output
113116

114117
def get_config(self):
115118
base_config = super().get_config()
@@ -132,8 +135,10 @@ class DynamicMetaEmbedding(tf.keras.layers.Layer):
132135
Arguments
133136
---------
134137
135-
- `embedding_matrices` (``List[tf.keras.layers.Embedding]``): List of embedding layers
138+
- `embedding_matrices` (``List[np.ndarray]``): List of embedding matrices
136139
- `output_dim` (``int``): Dimension of the output embedding
140+
- `mask_zero` (``bool``): Whether or not the input value 0 is a special "padding" value that should be masked out
141+
- `input_length` (``Optional[int]``): Parameter to be passed into internal ``tf.keras.layers.Embedding`` matrices
137142
- `name` (``str``): Layer name
138143
139144
@@ -160,27 +165,22 @@ class DynamicMetaEmbedding(tf.keras.layers.Layer):
160165
import tensorflow as tf
161166
import tavolo as tvl
162167
163-
w2v_embedding = tf.keras.layers.Embedding(num_words,
164-
EMBEDDING_DIM,
165-
embeddings_initializer=tf.keras.initializers.Constant(w2v_matrix),
166-
input_length=MAX_SEQUENCE_LENGTH,
167-
trainable=False)
168+
w2v_embedding = np.array(...) # Pre-trained embedding matrix
168169
169-
glove_embedding = tf.keras.layers.Embedding(num_words,
170-
EMBEDDING_DIM,
171-
embeddings_initializer=tf.keras.initializers.Constant(glove_matrix),
172-
input_length=MAX_SEQUENCE_LENGTH,
173-
trainable=False)
170+
glove_embedding = np.array(...) # Pre-trained embedding matrix
174171
175172
model = tf.keras.Sequential([tf.keras.layers.Input(shape=(MAX_SEQUENCE_LENGTH,), dtype='int32'),
176-
tvl.embeddings.DynamicMetaEmbedding([w2v_embedding, glove_embedding])]) # Use DME embeddings
173+
tvl.embeddings.DynamicMetaEmbedding([w2v_embedding, glove_embedding],
174+
input_length=MAX_SEQUENCE_LENGTH)]) # Use DME embeddings
177175
178176
Using the same example as above, it is possible to define the output's channel size
179177
180178
.. code-block:: python3
181179
182180
model = tf.keras.Sequential([tf.keras.layers.Input(shape=(MAX_SEQUENCE_LENGTH,), dtype='int32'),
183-
tvl.embeddings.DynamicMetaEmbedding([w2v_embedding, glove_embedding], output_dim=200)])
181+
tvl.embeddings.DynamicMetaEmbedding([w2v_embedding, glove_embedding],
182+
input_length=MAX_SEQUENCE_LENGTH,
183+
output_dim=200)])
184184
185185
186186
References
@@ -193,25 +193,41 @@ class DynamicMetaEmbedding(tf.keras.layers.Layer):
193193
"""
194194

195195
def __init__(self,
196-
embedding_matrices: List[tf.keras.layers.Embedding],
196+
embedding_matrices: List[np.ndarray],
197197
output_dim: Optional[int] = None,
198+
mask_zero: bool = False,
199+
input_length: Optional[int] = None,
198200
name: str = 'dynamic_meta_embedding',
199201
**kwargs):
200202
"""
201-
:param embedding_matrices: List of embedding layers
203+
204+
:param embedding_matrices: List of embedding matrices
202205
:param output_dim: Dimension of the output embedding
206+
:param mask_zero: Whether or not the input value 0 is a special "padding" value that should be masked out
207+
:param input_length: Parameter to be passed into internal ``tf.keras.layers.Embedding`` matrices
203208
:param name: Layer name
204209
"""
205210
super().__init__(name=name, **kwargs)
206211

212+
self.mask_zero = mask_zero
213+
self.input_length = input_length
214+
self.base_matrices_shapes = [e.shape for e in embedding_matrices]
215+
207216
# Validate all the embedding matrices have the same vocabulary size
208-
if not len(set((e.input_dim for e in embedding_matrices))) == 1:
217+
if not len(set((e.shape[0] for e in embedding_matrices))) == 1:
209218
raise ValueError('Vocabulary sizes (first dimension) of all embedding matrices must match')
219+
if not set((e.ndim for e in embedding_matrices)) == {2}:
220+
raise ValueError('All embedding matrices should have only 2 dimensions')
210221

211222
# If no output_dim is supplied, use the maximum dimension from the given matrices
212-
self.output_dim = output_dim or min([e.output_dim for e in embedding_matrices])
213-
214-
self.embedding_matrices = embedding_matrices
223+
self.output_dim = output_dim or min([e.shape[1] for e in embedding_matrices])
224+
225+
self.embedding_matrices = [tf.keras.layers.Embedding(input_dim=e.shape[0],
226+
output_dim=e.shape[1],
227+
embeddings_initializer=tf.keras.initializers.Constant(e),
228+
input_length=input_length,
229+
name='embedding_matrix_{}'.format(i))
230+
for i, e in enumerate(embedding_matrices)]
215231
self.n_embeddings = len(self.embedding_matrices)
216232

217233
self.projections = [tf.keras.layers.Dense(units=self.output_dim,
@@ -225,11 +241,14 @@ def __init__(self,
225241
dtype=self.dtype)
226242

227243
def compute_mask(self, inputs, mask=None):
228-
return self.projections[0].compute_mask(
229-
inputs, mask=self.embedding_matrices[0].compute_mask(inputs, mask=mask))
244+
if not self.mask_zero:
245+
return None
246+
247+
return math_ops.not_equal(inputs, 0)
230248

231249
def call(self, inputs,
232250
**kwargs) -> tf.Tensor:
251+
233252
batch_size, time_steps = inputs.shape[:2]
234253

235254
# Embedding lookup
@@ -254,16 +273,17 @@ def call(self, inputs,
254273

255274
def get_config(self):
256275
base_config = super().get_config()
257-
base_config['embedding_matrices'] = [e.get_config() for e in self.embedding_matrices]
276+
base_config['base_matrices_shapes'] = self.base_matrices_shapes
258277
base_config['output_dim'] = self.output_dim
278+
base_config['mask_zero'] = self.mask_zero
279+
base_config['input_length'] = self.input_length
259280

260281
return base_config
261282

262283
@classmethod
263284
def from_config(cls, config: dict):
264-
embedding_matrices = [tf.keras.layers.Embedding.from_config(e_conf) for e_conf in
265-
config.pop('embedding_matrices')]
266-
return cls(embedding_matrices=embedding_matrices, **config)
285+
initial_matrices = [np.zeros(shape=s) for s in config.pop('base_matrices_shapes')]
286+
return cls(embedding_matrices=initial_matrices, **config)
267287

268288

269289
class ContextualDynamicMetaEmbedding(tf.keras.layers.Layer):
@@ -277,8 +297,10 @@ class ContextualDynamicMetaEmbedding(tf.keras.layers.Layer):
277297
Arguments
278298
---------
279299
280-
- `embedding_matrices` (``List[tf.keras.layers.Embedding]``): List of embedding layers
300+
- `embedding_matrices` (``List[np.ndarray]``): List of embedding matrices
281301
- `output_dim` (``int``): Dimension of the output embedding
302+
- `mask_zero` (``bool``): Whether or not the input value 0 is a special "padding" value that should be masked out
303+
- `input_length` (``Optional[int]``): Parameter to be passed into internal ``tf.keras.layers.Embedding`` matrices
282304
- `n_lstm_units` (``int``): Number of units in each LSTM, (notated as `m` in the original article)
283305
- `name` (``str``): Layer name
284306
@@ -306,27 +328,22 @@ class ContextualDynamicMetaEmbedding(tf.keras.layers.Layer):
306328
import tensorflow as tf
307329
import tavolo as tvl
308330
309-
w2v_embedding = tf.keras.layers.Embedding(num_words,
310-
EMBEDDING_DIM,
311-
embeddings_initializer=tf.keras.initializers.Constant(w2v_matrix),
312-
input_length=MAX_SEQUENCE_LENGTH,
313-
trainable=False)
331+
w2v_embedding = np.array(...) # Pre-trained embedding matrix
314332
315-
glove_embedding = tf.keras.layers.Embedding(num_words,
316-
EMBEDDING_DIM,
317-
embeddings_initializer=tf.keras.initializers.Constant(glove_matrix),
318-
input_length=MAX_SEQUENCE_LENGTH,
319-
trainable=False)
333+
glove_embedding = np.array(...) # Pre-trained embedding matrix
320334
321335
model = tf.keras.Sequential([tf.keras.layers.Input(shape=(MAX_SEQUENCE_LENGTH,), dtype='int32'),
322-
tvl.embeddings.DynamicMetaEmbedding([w2v_embedding, glove_embedding])]) # Use CDME embeddings
336+
tvl.embeddings.DynamicMetaEmbedding([w2v_embedding, glove_embedding],
337+
input_length=MAX_SEQUENCE_LENGTH)]) # Use CDME embeddings
323338
324339
Using the same example as above, it is possible to define the output's channel size and number of units in each LSTM
325340
326341
.. code-block:: python3
327342
328343
model = tf.keras.Sequential([tf.keras.layers.Input(shape=(MAX_SEQUENCE_LENGTH,), dtype='int32'),
329-
tvl.embeddings.DynamicMetaEmbedding([w2v_embedding, glove_embedding], n_lstm_units=128, output_dim=200)])
344+
tvl.embeddings.DynamicMetaEmbedding([w2v_embedding, glove_embedding],
345+
input_length=MAX_SEQUENCE_LENGTH,
346+
n_lstm_units=128, output_dim=200)])
330347
331348
References
332349
----------
@@ -335,33 +352,47 @@ class ContextualDynamicMetaEmbedding(tf.keras.layers.Layer):
335352
336353
.. _`Dynamic Meta-Embeddings for Improved Sentence Representations`:
337354
https://arxiv.org/abs/1804.07983
338-
add """
355+
"""
339356

340357
def __init__(self,
341-
embedding_matrices: List[tf.keras.layers.Embedding],
358+
embedding_matrices: List[np.ndarray],
342359
output_dim: Optional[int] = None,
360+
mask_zero: bool = False,
361+
input_length: Optional[int] = None,
343362
n_lstm_units: int = 2,
344363
name: str = 'contextual_dynamic_meta_embedding',
345364
**kwargs):
346365
"""
347-
:param embedding_matrices: List of embedding layers
348-
:param n_lstm_units: Number of units in each LSTM, (notated as `m` in the original article)
366+
:param embedding_matrices: List of embedding matrices
349367
:param output_dim: Dimension of the output embedding
368+
:param mask_zero: Whether or not the input value 0 is a special "padding" value that should be masked out
369+
:param input_length: Parameter to be passed into internal ``tf.keras.layers.Embedding`` matrices
370+
:param n_lstm_units: Number of units in each LSTM, (notated as `m` in the original article)
350371
:param name: Layer name
351372
"""
352373

353374
super().__init__(name=name, **kwargs)
354375

376+
self.mask_zero = mask_zero
377+
self.input_length = input_length
378+
self.n_lstm_units = n_lstm_units
379+
self.base_matrices_shapes = [e.shape for e in embedding_matrices]
380+
355381
# Validate all the embedding matrices have the same vocabulary size
356-
if not len(set((e.input_dim for e in embedding_matrices))) == 1:
382+
if not len(set((e.shape[0] for e in embedding_matrices))) == 1:
357383
raise ValueError('Vocabulary sizes (first dimension) of all embedding matrices must match')
384+
if not set((e.ndim for e in embedding_matrices)) == {2}:
385+
raise ValueError('All embedding matrices should have only 2 dimensions')
358386

359387
# If no output_dim is supplied, use the maximum dimension from the given matrices
360-
self.output_dim = output_dim or min([e.output_dim for e in embedding_matrices])
361-
362-
self.n_lstm_units = n_lstm_units
363-
364-
self.embedding_matrices = embedding_matrices
388+
self.output_dim = output_dim or min([e.shape[1] for e in embedding_matrices])
389+
390+
self.embedding_matrices = [tf.keras.layers.Embedding(input_dim=e.shape[0],
391+
output_dim=e.shape[1],
392+
embeddings_initializer=tf.keras.initializers.Constant(e),
393+
input_length=input_length,
394+
name='embedding_matrix_{}'.format(i))
395+
for i, e in enumerate(embedding_matrices)]
365396
self.n_embeddings = len(self.embedding_matrices)
366397

367398
self.projections = [tf.keras.layers.Dense(units=self.output_dim,
@@ -380,8 +411,10 @@ def __init__(self,
380411
dtype=self.dtype)
381412

382413
def compute_mask(self, inputs, mask=None):
383-
return self.projections[0].compute_mask(
384-
inputs, mask=self.embedding_matrices[0].compute_mask(inputs, mask=mask))
414+
if not self.mask_zero:
415+
return None
416+
417+
return math_ops.not_equal(inputs, 0)
385418

386419
def call(self, inputs,
387420
**kwargs) -> tf.Tensor:
@@ -416,14 +449,15 @@ def call(self, inputs,
416449

417450
def get_config(self):
418451
base_config = super().get_config()
419-
base_config['embedding_matrices'] = [e.get_config() for e in self.embedding_matrices]
452+
base_config['base_matrices_shapes'] = self.base_matrices_shapes
420453
base_config['output_dim'] = self.output_dim
454+
base_config['mask_zero'] = self.mask_zero
455+
base_config['input_length'] = self.input_length
421456
base_config['n_lstm_units'] = self.n_lstm_units
422457

423458
return base_config
424459

425460
@classmethod
426461
def from_config(cls, config: dict):
427-
embedding_matrices = [tf.keras.layers.Embedding.from_config(e_conf) for e_conf in
428-
config.pop('embedding_matrices')]
429-
return cls(embedding_matrices=embedding_matrices, **config)
462+
initial_matrices = [np.zeros(shape=s) for s in config.pop('base_matrices_shapes')]
463+
return cls(embedding_matrices=initial_matrices, **config)

0 commit comments

Comments
 (0)