def theano_dtw(batch, compiled_dtw, pack_batch=True): """ Calls the provided compiled Theano DTW function using the appropriate mechanims (batching inputs when supported else submitted the sequence pairs one-at-a-time). :param batch: The sequence pairs whose DTW distances are to be calculated. :param compiled_dtw: The compiled DTW function to use. :param pack_batch: Whether the batch should be packed or not (i.e. the input dimensionality supported by compiled_dtw). If pack_batch=True then compiled_dtw must accept 3D (batched) inputs else compiled_dtw must accept 2D (non-batched) inputs. :return: The DTW distances between all the sequence pairs in batch. """ assert compiled_dtw is not None assert isinstance(pack_batch, bool) dtype = utility.get_standard_dtype() def dtw(validated_batch): if pack_batch: # A validated batch input to this method is still in "list of pairs" format. To use the batched DTW function # we must pack those pairs into a single 3D tensor. The tensor size is determined by the maximum sequence # lengths on each side of the pairs. All shorter sequences are zero padded to fill the tensor. An integer # array is constructed to inform the DTW function how long the sequences are on each side. max_lengths = [ max(lengths) for lengths in zip(*((len(x1), len(x2)) for x1, x2 in validated_batch)) ] batch_size = len(validated_batch) input_size = validated_batch[0][0].shape[1] packed_x1 = numpy.zeros((max_lengths[0], batch_size, input_size), dtype=dtype) packed_x2 = numpy.zeros((max_lengths[1], batch_size, input_size), dtype=dtype) x1_lengths = numpy.empty(batch_size, dtype='int32') x2_lengths = numpy.empty(batch_size, dtype='int32') for index, (x1, x2) in enumerate(validated_batch): packed_x1[:len(x1), index, :] = x1 packed_x2[:len(x2), index, :] = x2 x1_lengths[index] = len(x1) x2_lengths[index] = len(x2) outputs = compiled_dtw(packed_x1, packed_x2, x1_lengths, x2_lengths) _check_outputs(outputs) return [float(output) for output in outputs[0]] # If we're not packing the batch then we need to submit them one at a time. results = [] for x1, x2 in validated_batch: outputs = compiled_dtw(x1, x2, len(x1), len(x2)) _check_outputs(outputs) results.append(float(outputs[0])) return results return _common_dtw(batch, dtw)
def theano_symbolic_dtw(x1, x2, x1_lengths, x2_lengths, distance_function=cosine, normalize=True, debug_level=None, eps=None): """ A symbolic implementation of DTW that supports batches of sequence pairs. Returns a scalar if ndim == 2 and a vector of size x1.shape[1] if ndim == 3 This is slow! About 90 times slower than the Cython implementation using the parameters below. :param x1: A tensor containing the first side of the sequence pairs to be aligned. :param x2: A tensor containing the second side of the sequence pairs to be aligned. :param x1_lengths: An integer vector identifying the lengths of the sequences in x1 :param x2_lengths: An integer vector identifying the lengths of the sequences in x2 :param distance_function: The symbolic distance function to use (e.g. a reference to a function in distance). :param normalize: Whether the DTW distances should be sequence length normalized. :param debug_level: The debug level to use (see above for explanation). :param eps: The minimum value to use inside the distance function. Set to the machine epsilon if None. :return: The DTW distances for every sequence pair in the batch. """ if eps is None: eps = numpy.dtype(theano.config.floatX).type(numpy.finfo(float).eps) assert 0 <= x1_lengths.ndim == x2_lengths.ndim <= 1 assert isinstance(normalize, bool) ndim = x1.ndim assert 2 <= ndim == x2.ndim <= 3 # Ensure x2 is the shorter input to minimize the number of scan iterations x1_shorter_than_x2 = tt.le(x1.shape[0], x2.shape[0]) x1, x2 = _swap(x1_shorter_than_x2, x1, x2, 'x1', 'x2', debug_level) x1_lengths, x2_lengths = _swap(x1_shorter_than_x2, x1_lengths, x2_lengths, 'x1_lengths', 'x2_lengths', debug_level) # Compute distances between x1 sequences and paired x2 sequences d = distance_function(x1, x2, eps) # Iterate over the temporal slices of x2. See dtw_outer_step for an explanation of the other inputs to this scan # operation x1_indexes = tt.arange(x1.shape[0], dtype=DTYPE_INT64) results, _ = theano.scan(_create_dtw_outer_step(distance_function, debug_level), sequences=[x1_indexes, d], outputs_info=[ tt.zeros_like(x2[:, :, 0] if x2.ndim == 3 else x2[:, 0], dtype=theano.config.floatX)], non_sequences=[x1_lengths, x2_lengths]) result = results[x1_lengths - 1, x2_lengths - 1, tt.arange(x1.shape[1])] if x2.ndim == 3 else results[ x1_lengths - 1, x2_lengths - 1] result = _debug(result, 'theano_symbolic_dtw.result', debug_level) assert result.ndim == x1_lengths.ndim # Length normalize the distances if requested to do so if normalize: result = _debug(result / tt.cast(x1_lengths + x2_lengths, dtype=utility.get_standard_dtype()), 'theano_symbolic_dtw.norm_result', debug_level) return result
def theano_dtw(batch, compiled_dtw, pack_batch=True): """ Calls the provided compiled Theano DTW function using the appropriate mechanims (batching inputs when supported else submitted the sequence pairs one-at-a-time). :param batch: The sequence pairs whose DTW distances are to be calculated. :param compiled_dtw: The compiled DTW function to use. :param pack_batch: Whether the batch should be packed or not (i.e. the input dimensionality supported by compiled_dtw). If pack_batch=True then compiled_dtw must accept 3D (batched) inputs else compiled_dtw must accept 2D (non-batched) inputs. :return: The DTW distances between all the sequence pairs in batch. """ assert compiled_dtw is not None assert isinstance(pack_batch, bool) dtype = utility.get_standard_dtype() def dtw(validated_batch): if pack_batch: # A validated batch input to this method is still in "list of pairs" format. To use the batched DTW function # we must pack those pairs into a single 3D tensor. The tensor size is determined by the maximum sequence # lengths on each side of the pairs. All shorter sequences are zero padded to fill the tensor. An integer # array is constructed to inform the DTW function how long the sequences are on each side. max_lengths = [max(lengths) for lengths in zip(*((len(x1), len(x2)) for x1, x2 in validated_batch))] batch_size = len(validated_batch) input_size = validated_batch[0][0].shape[1] packed_x1 = numpy.zeros((max_lengths[0], batch_size, input_size), dtype=dtype) packed_x2 = numpy.zeros((max_lengths[1], batch_size, input_size), dtype=dtype) x1_lengths = numpy.empty(batch_size, dtype='int32') x2_lengths = numpy.empty(batch_size, dtype='int32') for index, (x1, x2) in enumerate(validated_batch): packed_x1[:len(x1), index, :] = x1 packed_x2[:len(x2), index, :] = x2 x1_lengths[index] = len(x1) x2_lengths[index] = len(x2) outputs = compiled_dtw(packed_x1, packed_x2, x1_lengths, x2_lengths) _check_outputs(outputs) return [float(output) for output in outputs[0]] # If we're not packing the batch then we need to submit them one at a time. results = [] for x1, x2 in validated_batch: outputs = compiled_dtw(x1, x2, len(x1), len(x2)) _check_outputs(outputs) results.append(float(outputs[0])) return results return _common_dtw(batch, dtw)
def _var(name, test_value_shape, debug_name_prefix, debug_level, dtype=None, test_value_getter=lambda shape: numpy.random.randn(*shape)): """ Creates a new symbolic variable with the given name and generates a synthetic test value of the requested shape. The resulting Theano variable is wrapped in a Debug operation. :param name: The name of the variable to be created. :param test_value_shape: The shape of the test value. :param debug_name_prefix: Used in naming the Debug operation. :param debug_level: The debug level to use (see above for explanation). :param dtype: The type of the variable being created. Defaults to whatever is returned by utility.get_standard_dtype. :param test_value_getter: A method for generating test values. Defaults to zero mean unit variance Gaussian values. :return: The newly created Theano variable. """ if dtype is None: dtype = utility.get_standard_dtype() if len(test_value_shape) == 0: x = tt.scalar(name, dtype=dtype) elif len(test_value_shape) == 1: x = tt.vector(name, dtype=dtype) elif len(test_value_shape) == 2: x = tt.matrix(name, dtype=dtype) elif len(test_value_shape) == 3: x = tt.tensor3(name, dtype=dtype) else: raise Exception('Unsupported number of dimensions: ' + str(len(test_value_shape))) if debug_level > 0: x.tag.test_value = test_value_getter(test_value_shape) if len(test_value_shape) == 0: x.tag.test_value = numpy.dtype(dtype).type(x.tag.test_value) else: x.tag.test_value = x.tag.test_value.astype(dtype) return x, _debug(x, '%s.%s' % (debug_name_prefix, name), debug_level)
def theano_symbolic_dtw(x1, x2, x1_lengths, x2_lengths, distance_function=cosine, normalize=True, debug_level=None, eps=None): """ A symbolic implementation of DTW that supports batches of sequence pairs. Returns a scalar if ndim == 2 and a vector of size x1.shape[1] if ndim == 3 This is slow! About 90 times slower than the Cython implementation using the parameters below. :param x1: A tensor containing the first side of the sequence pairs to be aligned. :param x2: A tensor containing the second side of the sequence pairs to be aligned. :param x1_lengths: An integer vector identifying the lengths of the sequences in x1 :param x2_lengths: An integer vector identifying the lengths of the sequences in x2 :param distance_function: The symbolic distance function to use (e.g. a reference to a function in distance). :param normalize: Whether the DTW distances should be sequence length normalized. :param debug_level: The debug level to use (see above for explanation). :param eps: The minimum value to use inside the distance function. Set to the machine epsilon if None. :return: The DTW distances for every sequence pair in the batch. """ if eps is None: eps = numpy.dtype(theano.config.floatX).type(numpy.finfo(float).eps) assert 0 <= x1_lengths.ndim == x2_lengths.ndim <= 1 assert isinstance(normalize, bool) ndim = x1.ndim assert 2 <= ndim == x2.ndim <= 3 # Ensure x2 is the shorter input to minimize the number of scan iterations x1_shorter_than_x2 = tt.le(x1.shape[0], x2.shape[0]) x1, x2 = _swap(x1_shorter_than_x2, x1, x2, 'x1', 'x2', debug_level) x1_lengths, x2_lengths = _swap(x1_shorter_than_x2, x1_lengths, x2_lengths, 'x1_lengths', 'x2_lengths', debug_level) # Compute distances between x1 sequences and paired x2 sequences d = distance_function(x1, x2, eps) # Iterate over the temporal slices of x2. See dtw_outer_step for an explanation of the other inputs to this scan # operation x1_indexes = tt.arange(x1.shape[0], dtype=DTYPE_INT64) results, _ = theano.scan( _create_dtw_outer_step(distance_function, debug_level), sequences=[x1_indexes, d], outputs_info=[ tt.zeros_like(x2[:, :, 0] if x2.ndim == 3 else x2[:, 0], dtype=theano.config.floatX) ], non_sequences=[x1_lengths, x2_lengths]) result = results[x1_lengths - 1, x2_lengths - 1, tt.arange(x1.shape[1])] if x2.ndim == 3 else results[ x1_lengths - 1, x2_lengths - 1] result = _debug(result, 'theano_symbolic_dtw.result', debug_level) assert result.ndim == x1_lengths.ndim # Length normalize the distances if requested to do so if normalize: result = _debug( result / tt.cast(x1_lengths + x2_lengths, dtype=utility.get_standard_dtype()), 'theano_symbolic_dtw.norm_result', debug_level) return result