def test_diffs(self): x = tf.constant([1, 2, 3, 4, 5]) dx = self.evaluate(diff_ops.diff(x, order=1, exclusive=False)) np.testing.assert_array_equal(dx, [1, 1, 1, 1, 1]) dx1 = self.evaluate(diff_ops.diff(x, order=1, exclusive=True)) np.testing.assert_array_equal(dx1, [1, 1, 1, 1]) dx2 = self.evaluate(diff_ops.diff(x, order=2, exclusive=False)) np.testing.assert_array_equal(dx2, [1, 2, 2, 2, 2])
def test_diffs_differentiable(self): """Tests that the diffs op is differentiable.""" x = tf.constant(2.0) xv = tf.stack([x, x * x, x * x * x], axis=0) # Produces [x, x^2 - x, x^3 - x^2] dxv = self.evaluate(diff_ops.diff(xv)) np.testing.assert_array_equal(dxv, [2., 2., 4.]) grad = self.evaluate(tf.gradients(diff_ops.diff(xv), x)[0]) # Note that TF gradients adds up the components of the jacobian. # The sum of [1, 2x-1, 3x^2-2x] at x = 2 is 12. self.assertEqual(grad, 12.0)
def _interpolate_adjacent(times, values, name=None): """Interpolates linearly between adjacent values. Suppose `times` are `[t_1, t_2, ..., t_n]` an array of length `n` and values are `[f_1, ... f_n]` of length `n`. This function associates each of the values to the midpoint of the interval i.e. `f_i` is associated to the midpoint of the interval `[t_i, t_{i+1}]`. Then it calculates the values at the interval boundaries by linearly interpolating between adjacent intervals. The first interval is considered to be `[0, t_1]`. The values at the endpoints (i.e. result[0] and result[n]) are computed as follows: `result[0] = values[0] - 0.5 * (result[1] - values[0])` and `result[n] = values[n-1] - 0.5 * (result[n-1] - values[n-1])`. The rationale for these specific values is discussed in Ref. [1]. Args: times: A rank 1 `Tensor` of real dtype. The times at which the interpolated values are to be computed. The values in the array should be positive and monotonically increasing. values: A rank 1 `Tensor` of the same dtype and shape as `times`. The values assigned to the midpoints of the time intervals. name: Python `str` name prefixed to Ops created by this class. Default value: None which is mapped to the default name 'interpolate_adjacent'. Returns: interval_values: The values interpolated from the supplied midpoint values as described above. A `Tensor` of the same dtype as `values` but shape `[n+1]` where `[n]` is the shape of `values`. The `i`th component of the is the value associated to the time point `t_{i+1}` with `t_0 = 0`. """ with tf.compat.v1.name_scope(name, default_name='interpolate_adjacent', values=[times, values]): dt1 = diff_ops.diff(times, order=1, exclusive=False) dt2 = diff_ops.diff(times, order=2, exclusive=False)[1:] weight_right = dt1[:-1] / dt2 weight_left = dt1[1:] / dt2 interior_values = weight_right * values[1:] + weight_left * values[:-1] value_0 = values[0] - 0.5 * (interior_values[0] - values[0]) value_n = values[-1] - 0.5 * (interior_values[-1] - values[-1]) return tf.concat([[value_0], interior_values, [value_n]], axis=0)
def segment_diff(x, segment_ids, order=1, exclusive=False, dtype=None, name=None): """Computes difference of successive elements in a segment. For a complete description of segment_* ops see documentation of `tf.segment_max`. This op extends the `diff` functionality to segmented inputs. The behaviour of this op is the same as that of the op `diff` within each segment. The result is effectively a concatenation of the results of `diff` applied to each segment. ## Example ```python x = tf.constant([2, 5, 1, 7, 9] + [32, 10, 12, 3] + [4, 8, 5]) segments = tf.constant([0, 0, 0, 0, 0] + [1, 1, 1, 1] + [2, 2, 2]) # First order diff. Expected result: [3, -4, 6, 2, -22, 2, -9, 4, -3] dx1 = segment_diff( x, segment_ids=segments, order=1, exclusive=True) # Non-exclusive, second order diff. # Expected result: [2, 5, -1, 2, 8, 32, 10, -20, -7, 4, 8, 1] dx2 = segment_diff( x, segment_ids=segments, order=2, exclusive=False) ``` Args: x: A rank 1 `Tensor` of any dtype for which arithmetic operations are permitted. segment_ids: A `Tensor`. Must be one of the following types: int32, int64. A 1-D tensor whose size is equal to the size of `x`. Values should be sorted and can be repeated. order: Positive Python int. The order of the difference to compute. `order = 1` corresponds to the difference between successive elements. Default value: 1 exclusive: Python bool. See description above. Default value: False dtype: Optional `tf.Dtype`. If supplied, the dtype for `x` to use when converting to `Tensor`. Default value: None which maps to the default dtype inferred by TF. name: Python `str` name prefixed to Ops created by this class. Default value: None which is mapped to the default name 'segment_diff'. Returns: diffs: A `Tensor` of the same dtype as `x`. Assuming that each segment is of length greater than or equal to order, if `exclusive` is True, then the size is `n-order*k` where `n` is the size of x, `k` is the number of different segment ids supplied if `segment_ids` is not None or 1 if `segment_ids` is None. If any of the segments is of length less than the order, then the size is: `n-sum(min(order, length(segment_j)), j)` where the sum is over segments. If `exclusive` is False, then the size is `n`. """ with tf.compat.v1.name_scope(name, default_name='segment_diff', values=[x]): x = tf.convert_to_tensor(x, dtype=dtype) raw_diffs = diff_ops.diff(x, order=order, exclusive=exclusive) if segment_ids is None: return raw_diffs # If segment ids are supplied, raw_diffs are incorrect at locations: # p, p+1, ... min(p+order-1, m_p-1) where p is the index of the first # element of a segment other than the very first segment (which is # already correct). m_p is the segment length. # Find positions where the segments begin. has_segment_changed = tf.concat( [[False], tf.not_equal(segment_ids[1:] - segment_ids[:-1], 0)], axis=0) # Shape [k, 1] segment_start_index = tf.cast(tf.where(has_segment_changed), dtype=tf.int32) segment_end_index = tf.concat( [tf.reshape(segment_start_index, [-1])[1:], [tf.size(segment_ids)]], axis=0) segment_end_index = tf.reshape(segment_end_index, [-1, 1]) # The indices of locations that need to be adjusted. This needs to be # constructed in steps. First we generate p, p+1, ... p+order-1. # Shape [num_segments-1, order] fix_indices = ( segment_start_index + tf.range(order, dtype=segment_start_index.dtype)) in_bounds = tf.where(fix_indices < segment_end_index) # Keep only the ones in bounds. fix_indices = tf.reshape(tf.gather_nd(fix_indices, in_bounds), [-1, 1]) needs_fix = tf.scatter_nd( fix_indices, tf.reshape(tf.ones_like(fix_indices, dtype=tf.bool), [-1]), shape=tf.shape(x)) # If exclusive is False, then needs_fix means we need to replace the values # in raw_diffs at those locations with the values in x. if not exclusive: return tf.where(needs_fix, x, raw_diffs) # If exclusive is True, we have to be more careful. The raw_diffs # computation has removed the first 'order' elements. After removing the # corresponding elements from needs_fix, we use it to remove the elements # from raw_diffs. return tf.boolean_mask(raw_diffs, tf.logical_not(needs_fix[order:]))