1# Copyright 2018 The TensorFlow Authors. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14# ==============================================================================
15"""Utility methods related to kernelized layers."""
16
17from __future__ import absolute_import
18from __future__ import division
19from __future__ import print_function
20
21from tensorflow.python.ops import array_ops
22from tensorflow.python.ops import math_ops
23
24
25def _to_matrix(u):
26  """If input tensor is a vector (i.e., has rank 1), converts it to matrix."""
27  u_rank = len(u.shape)
28  if u_rank not in [1, 2]:
29    raise ValueError('The input tensor should have rank 1 or 2. Given rank: {}'
30                     .format(u_rank))
31  if u_rank == 1:
32    return array_ops.expand_dims(u, 0)
33  return u
34
35
36def _align_matrices(x, y):
37  """Aligns x and y tensors to allow computations over pairs of their rows."""
38  x_matrix = _to_matrix(x)
39  y_matrix = _to_matrix(y)
40  x_shape = x_matrix.shape
41  y_shape = y_matrix.shape
42  if y_shape[1] != x_shape[1]:  # dimensions do not match.
43    raise ValueError(
44        'The outermost dimensions of the input tensors should match. Given: {} '
45        'vs {}.'.format(y_shape[1], x_shape[1]))
46
47  x_tile = array_ops.tile(
48      array_ops.expand_dims(x_matrix, 1), [1, y_shape[0], 1])
49  y_tile = array_ops.tile(
50      array_ops.expand_dims(y_matrix, 0), [x_shape[0], 1, 1])
51  return x_tile, y_tile
52
53
54def inner_product(u, v):
55  u = _to_matrix(u)
56  v = _to_matrix(v)
57  return math_ops.matmul(u, v, transpose_b=True)
58
59
60def exact_gaussian_kernel(x, y, stddev):
61  """Computes exact Gaussian kernel value(s) for tensors x and y and stddev.
62
63  The Gaussian kernel for vectors u, v is defined as follows:
64       K(u, v) = exp(-||u-v||^2 / (2* stddev^2))
65  where the norm is the l2-norm. x, y can be either vectors or matrices. If they
66  are vectors, they must have the same dimension. If they are matrices, they
67  must have the same number of columns. In the latter case, the method returns
68  (as a matrix) K(u, v) values for all pairs (u, v) where u is a row from x and
69  v is a row from y.
70
71  Args:
72    x: a tensor of rank 1 or 2. It's shape should be either [dim] or [m, dim].
73    y: a tensor of rank 1 or 2. It's shape should be either [dim] or [n, dim].
74    stddev: The width of the Gaussian kernel.
75
76  Returns:
77    A single value (scalar) with shape (1, 1) (if x, y are vectors) or a matrix
78      of shape (m, n) with entries K(u, v) (where K is the Gaussian kernel) for
79      all (u,v) pairs where u, v are rows from x and y respectively.
80
81  Raises:
82    InvalidShapeError: if the shapes of x, y are not compatible.
83  """
84  x_aligned, y_aligned = _align_matrices(x, y)
85  diff_squared_l2_norm = math_ops.reduce_sum(
86      math_ops.squared_difference(x_aligned, y_aligned), 2)
87  return math_ops.exp(-diff_squared_l2_norm / (2 * stddev * stddev))
88
89
90def exact_laplacian_kernel(x, y, stddev):
91  """Computes exact Laplacian kernel value(s) for tensors x and y using stddev.
92
93  The Laplacian kernel for vectors u, v is defined as follows:
94       K(u, v) = exp(-||u-v|| / stddev)
95  where the norm is the l1-norm. x, y can be either vectors or matrices. If they
96  are vectors, they must have the same dimension. If they are matrices, they
97  must have the same number of columns. In the latter case, the method returns
98  (as a matrix) K(u, v) values for all pairs (u, v) where u is a row from x and
99  v is a row from y.
100
101  Args:
102    x: a tensor of rank 1 or 2. It's shape should be either [dim] or [m, dim].
103    y: a tensor of rank 1 or 2. It's shape should be either [dim] or [n, dim].
104    stddev: The width of the Gaussian kernel.
105
106  Returns:
107    A single value (scalar) with shape (1, 1)  if x, y are vectors or a matrix
108    of shape (m, n) with entries K(u, v) (where K is the Laplacian kernel) for
109    all (u,v) pairs where u, v are rows from x and y respectively.
110
111  Raises:
112    InvalidShapeError: if the shapes of x, y are not compatible.
113  """
114  x_aligned, y_aligned = _align_matrices(x, y)
115  diff_l1_norm = math_ops.reduce_sum(
116      math_ops.abs(math_ops.subtract(x_aligned, y_aligned)), 2)
117  return math_ops.exp(-diff_l1_norm / stddev)
118