def _testSvdCorrectness(self, dtype, shape): np.random.seed(1) x_np = np.random.uniform(low=-1.0, high=1.0, size=shape).astype(dtype) m, n = shape[-2], shape[-1] _, s_np, _ = np.linalg.svd(x_np) with self.cached_session() as sess: x_tf = array_ops.placeholder(dtype) with self.test_scope(): s, u, v = linalg_ops.svd(x_tf, full_matrices=True) s_val, u_val, v_val = sess.run([s, u, v], feed_dict={x_tf: x_np}) u_diff = np.matmul(u_val, np.swapaxes(u_val, -1, -2)) - np.eye(m) v_diff = np.matmul(v_val, np.swapaxes(v_val, -1, -2)) - np.eye(n) # Check u_val and v_val are orthogonal matrices. self.assertLess(np.linalg.norm(u_diff), 1e-2) self.assertLess(np.linalg.norm(v_diff), 1e-2) # Check that the singular values are correct, i.e., close to the ones from # numpy.lingal.svd. self.assertLess(np.linalg.norm(s_val - s_np), 1e-2) # The tolerance is set based on our tests on numpy's svd. As our tests # have batch dimensions and all our operations are on float32, we set the # tolerance a bit larger. Numpy's svd calls LAPACK's svd, which operates # on double precision. self.assertLess( np.linalg.norm(self._compute_usvt(s_val, u_val, v_val) - x_np), 2e-2) # Check behavior with compute_uv=False. We expect to still see 3 outputs, # with a sentinel scalar 0 in the last two outputs. with self.test_scope(): no_uv_s, no_uv_u, no_uv_v = gen_linalg_ops.svd( x_tf, full_matrices=True, compute_uv=False) no_uv_s_val, no_uv_u_val, no_uv_v_val = sess.run( [no_uv_s, no_uv_u, no_uv_v], feed_dict={x_tf: x_np}) self.assertAllClose(no_uv_s_val, s_val, atol=1e-4, rtol=1e-4) self.assertEqual(no_uv_u_val, 0.0) self.assertEqual(no_uv_v_val, 0.0)
def _testSvdCorrectness(self, dtype, shape): np.random.seed(1) x_np = np.random.uniform(low=-1.0, high=1.0, size=shape).astype(dtype) m, n = shape[-2], shape[-1] _, s_np, _ = np.linalg.svd(x_np) with self.session() as sess: x_tf = array_ops.placeholder(dtype) with self.test_scope(): s, u, v = linalg_ops.svd(x_tf, full_matrices=True) s_val, u_val, v_val = sess.run([s, u, v], feed_dict={x_tf: x_np}) u_diff = np.matmul(u_val, np.swapaxes(u_val, -1, -2)) - np.eye(m) v_diff = np.matmul(v_val, np.swapaxes(v_val, -1, -2)) - np.eye(n) # Check u_val and v_val are orthogonal matrices. self.assertLess(np.linalg.norm(u_diff), 1e-2) self.assertLess(np.linalg.norm(v_diff), 1e-2) # Check that the singular values are correct, i.e., close to the ones from # numpy.lingal.svd. self.assertLess(np.linalg.norm(s_val - s_np), 1e-2) # The tolerance is set based on our tests on numpy's svd. As our tests # have batch dimensions and all our operations are on float32, we set the # tolerance a bit larger. Numpy's svd calls LAPACK's svd, which operates # on double precision. self.assertLess( np.linalg.norm(self._compute_usvt(s_val, u_val, v_val) - x_np), 2e-2) # Check behavior with compute_uv=False. We expect to still see 3 outputs, # with a sentinel scalar 0 in the last two outputs. with self.test_scope(): no_uv_s, no_uv_u, no_uv_v = gen_linalg_ops.svd( x_tf, full_matrices=True, compute_uv=False) no_uv_s_val, no_uv_u_val, no_uv_v_val = sess.run( [no_uv_s, no_uv_u, no_uv_v], feed_dict={x_tf: x_np}) self.assertAllClose(no_uv_s_val, s_val, atol=1e-4, rtol=1e-4) self.assertEqual(no_uv_u_val.shape, tensor_shape.TensorShape([0])) self.assertEqual(no_uv_v_val.shape, tensor_shape.TensorShape([0]))
def svd(matrix, compute_uv=True, full_matrices=False, name=None): """Computes the singular value decomposition of a matrix. Computes the SVD of `matrix` such that `matrix = u * diag(s) * transpose(v)` ```prettyprint # a is a matrix. # s is a vector of singular values. # u is the matrix of left singular vectors. # v is a matrix of right singular vectors. s, u, v = svd(a) s = svd(a, compute_uv=False) ``` Args: matrix: `Tensor` of shape `[M, N]`. Let `P` be the minimum of `M` and `N`. compute_uv: If `True` then left and right singular vectors will be computed and returned in `u` and `v`, respectively. Otherwise, only the singular values will be computed, which can be significantly faster. full_matrices: If true, compute full-sized `u` and `v`. If false (the default), compute only the leading `P` singular vectors. Ignored if `compute_uv` is `False`. name: string, optional name of the operation. Returns: s: Singular values. Shape is `[P]`. u: Right singular vectors. If `full_matrices` is `False` (default) then shape is `[M, P]`; if `full_matrices` is `True` then shape is `[M, M]`. Not returned if `compute_uv` is `False`. v: Left singular vectors. If `full_matrices` is `False` (default) then shape is `[N, P]`. If `full_matrices` is `True` then shape is `[N, N]`. Not returned if `compute_uv` is `False`. """ s, u, v = gen_linalg_ops.svd(matrix, compute_uv=compute_uv, full_matrices=full_matrices) if compute_uv: return s, u, v else: return s
def norm(tensor, ord='euclidean', axis=None, keepdims=None, name=None, keep_dims=None): r"""Computes the norm of vectors, matrices, and tensors. This function can compute several different vector norms (the 1-norm, the Euclidean or 2-norm, the inf-norm, and in general the p-norm for p > 0) and matrix norms (Frobenius, 1-norm, 2-norm and inf-norm). Args: tensor: `Tensor` of types `float32`, `float64`, `complex64`, `complex128` ord: Order of the norm. Supported values are 'fro', 'euclidean', `1`, `2`, `np.inf` and any positive real number yielding the corresponding p-norm. Default is 'euclidean' which is equivalent to Frobenius norm if `tensor` is a matrix and equivalent to 2-norm for vectors. Some restrictions apply: a) The Frobenius norm `fro` is not defined for vectors, b) If axis is a 2-tuple (matrix norm), only 'euclidean', 'fro', `1`, `2`, `np.inf` are supported. See the description of `axis` on how to compute norms for a batch of vectors or matrices stored in a tensor. axis: If `axis` is `None` (the default), the input is considered a vector and a single vector norm is computed over the entire set of values in the tensor, i.e. `norm(tensor, ord=ord)` is equivalent to `norm(reshape(tensor, [-1]), ord=ord)`. If `axis` is a Python integer, the input is considered a batch of vectors, and `axis` determines the axis in `tensor` over which to compute vector norms. If `axis` is a 2-tuple of Python integers it is considered a batch of matrices and `axis` determines the axes in `tensor` over which to compute a matrix norm. Negative indices are supported. Example: If you are passing a tensor that can be either a matrix or a batch of matrices at runtime, pass `axis=[-2,-1]` instead of `axis=None` to make sure that matrix norms are computed. keepdims: If True, the axis indicated in `axis` are kept with size 1. Otherwise, the dimensions in `axis` are removed from the output shape. name: The name of the op. keep_dims: Deprecated alias for `keepdims`. Returns: output: A `Tensor` of the same type as tensor, containing the vector or matrix norms. If `keepdims` is True then the rank of output is equal to the rank of `tensor`. Otherwise, if `axis` is none the output is a scalar, if `axis` is an integer, the rank of `output` is one less than the rank of `tensor`, if `axis` is a 2-tuple the rank of `output` is two less than the rank of `tensor`. Raises: ValueError: If `ord` or `axis` is invalid. @compatibility(numpy) Mostly equivalent to numpy.linalg.norm. Not supported: ord <= 0, 2-norm for matrices, nuclear norm. Other differences: a) If axis is `None`, treats the flattened `tensor` as a vector regardless of rank. b) Explicitly supports 'euclidean' norm as the default, including for higher order tensors. @end_compatibility """ keepdims = deprecation.deprecated_argument_lookup('keepdims', keepdims, 'keep_dims', keep_dims) if keepdims is None: keepdims = False is_matrix_norm = ((isinstance(axis, tuple) or isinstance(axis, list)) and len(axis) == 2) if is_matrix_norm: axis = tuple(axis) if (not isinstance(axis[0], int) or not isinstance(axis[1], int) or axis[0] == axis[1]): raise ValueError( "'axis' must be None, an integer, or a tuple of 2 unique integers" ) supported_matrix_norms = ['euclidean', 'fro', 1, 2, np.inf] if ord not in supported_matrix_norms: raise ValueError( "'ord' must be a supported matrix norm in %s, got %s" % (supported_matrix_norms, ord)) else: if not (isinstance(axis, int) or axis is None): raise ValueError( "'axis' must be None, an integer, or a tuple of 2 unique integers" ) supported_vector_norms = ['euclidean', 1, 2, np.inf] if (not np.isreal(ord) or ord <= 0) and ord not in supported_vector_norms: raise ValueError("'ord' must be a supported vector norm, got %s" % ord) if axis is not None: axis = (axis, ) with ops.name_scope(name, 'norm', [tensor]): tensor = ops.convert_to_tensor(tensor) if ord in ['fro', 'euclidean', 2, 2.0]: if is_matrix_norm and ord in [2, 2.0]: rank = array_ops.rank(tensor) positive_axis = map_fn.map_fn( lambda i: control_flow_ops.cond(i >= 0, lambda: i, lambda: i + rank), ops.convert_to_tensor(axis)) axes = math_ops.range(rank) perm_before = array_ops.concat([ array_ops.setdiff1d(axes, positive_axis)[0], positive_axis ], axis=0) perm_after = map_fn.map_fn( lambda i: math_ops.cast(array_ops.squeeze( array_ops.where_v2(math_ops.equal(perm_before, i))), dtype=dtypes.int32), axes) permed = array_ops.transpose(tensor, perm=perm_before) matrix_2_norm = array_ops.expand_dims(math_ops.reduce_max( math_ops.abs( gen_linalg_ops.svd(permed, compute_uv=False)[0]), axis=-1, keepdims=True), axis=-1) result = array_ops.transpose(matrix_2_norm, perm=perm_after) else: result = math_ops.sqrt( math_ops.reduce_sum(tensor * math_ops.conj(tensor), axis, keepdims=True)) # TODO(rmlarsen): Replace with the following, once gradients are defined # result = math_ops.reduce_euclidean_norm(tensor, axis, keepdims=True) else: result = math_ops.abs(tensor) if ord == 1: sum_axis = None if axis is None else axis[0] result = math_ops.reduce_sum(result, sum_axis, keepdims=True) if is_matrix_norm: result = math_ops.reduce_max(result, axis[-1], keepdims=True) elif ord == np.inf: if is_matrix_norm: result = math_ops.reduce_sum(result, axis[1], keepdims=True) max_axis = None if axis is None else axis[0] result = math_ops.reduce_max(result, max_axis, keepdims=True) else: # General p-norms (positive p only) result = math_ops.pow( math_ops.reduce_sum(math_ops.pow(result, ord), axis, keepdims=True), 1.0 / ord) if not keepdims: result = array_ops.squeeze(result, axis) return result
def svd(tensor, full_matrices=False, compute_uv=True, name=None): r"""Computes the singular value decompositions of one or more matrices. Computes the SVD of each inner matrix in `tensor` such that `tensor[..., :, :] = u[..., :, :] * diag(s[..., :, :]) * transpose(conj(v[..., :, :]))` ```python # a is a tensor. # s is a tensor of singular values. # u is a tensor of left singular vectors. # v is a tensor of right singular vectors. s, u, v = svd(a) s = svd(a, compute_uv=False) ``` Args: tensor: `Tensor` of shape `[..., M, N]`. Let `P` be the minimum of `M` and `N`. full_matrices: If true, compute full-sized `u` and `v`. If false (the default), compute only the leading `P` singular vectors. Ignored if `compute_uv` is `False`. compute_uv: If `True` then left and right singular vectors will be computed and returned in `u` and `v`, respectively. Otherwise, only the singular values will be computed, which can be significantly faster. name: string, optional name of the operation. Returns: s: Singular values. Shape is `[..., P]`. The values are sorted in reverse order of magnitude, so s[..., 0] is the largest value, s[..., 1] is the second largest, etc. u: Left singular vectors. If `full_matrices` is `False` (default) then shape is `[..., M, P]`; if `full_matrices` is `True` then shape is `[..., M, M]`. Not returned if `compute_uv` is `False`. v: Right singular vectors. If `full_matrices` is `False` (default) then shape is `[..., N, P]`. If `full_matrices` is `True` then shape is `[..., N, N]`. Not returned if `compute_uv` is `False`. @compatibility(numpy) Mostly equivalent to numpy.linalg.svd, except that * The order of output arguments here is `s`, `u`, `v` when `compute_uv` is `True`, as opposed to `u`, `s`, `v` for numpy.linalg.svd. * full_matrices is `False` by default as opposed to `True` for numpy.linalg.svd. * tf.linalg.svd uses the standard definition of the SVD \\(A = U \Sigma V^H\\), such that the left singular vectors of `a` are the columns of `u`, while the right singular vectors of `a` are the columns of `v`. On the other hand, numpy.linalg.svd returns the adjoint \\(V^H\\) as the third output argument. ```python import tensorflow as tf import numpy as np s, u, v = tf.linalg.svd(a) tf_a_approx = tf.matmul(u, tf.matmul(tf.linalg.diag(s), v, adjoint_b=True)) u, s, v_adj = np.linalg.svd(a, full_matrices=False) np_a_approx = np.dot(u, np.dot(np.diag(s), v_adj)) # tf_a_approx and np_a_approx should be numerically close. ``` @end_compatibility """ s, u, v = gen_linalg_ops.svd(tensor, compute_uv=compute_uv, full_matrices=full_matrices, name=name) if compute_uv: return math_ops.real(s), u, v else: return math_ops.real(s)
def norm(tensor, ord='euclidean', axis=None, keepdims=None, name=None, keep_dims=None): r"""Computes the norm of vectors, matrices, and tensors. This function can compute several different vector norms (the 1-norm, the Euclidean or 2-norm, the inf-norm, and in general the p-norm for p > 0) and matrix norms (Frobenius, 1-norm, 2-norm and inf-norm). Args: tensor: `Tensor` of types `float32`, `float64`, `complex64`, `complex128` ord: Order of the norm. Supported values are 'fro', 'euclidean', `1`, `2`, `np.inf` and any positive real number yielding the corresponding p-norm. Default is 'euclidean' which is equivalent to Frobenius norm if `tensor` is a matrix and equivalent to 2-norm for vectors. Some restrictions apply: a) The Frobenius norm `fro` is not defined for vectors, b) If axis is a 2-tuple (matrix norm), only 'euclidean', 'fro', `1`, `2`, `np.inf` are supported. See the description of `axis` on how to compute norms for a batch of vectors or matrices stored in a tensor. axis: If `axis` is `None` (the default), the input is considered a vector and a single vector norm is computed over the entire set of values in the tensor, i.e. `norm(tensor, ord=ord)` is equivalent to `norm(reshape(tensor, [-1]), ord=ord)`. If `axis` is a Python integer, the input is considered a batch of vectors, and `axis` determines the axis in `tensor` over which to compute vector norms. If `axis` is a 2-tuple of Python integers it is considered a batch of matrices and `axis` determines the axes in `tensor` over which to compute a matrix norm. Negative indices are supported. Example: If you are passing a tensor that can be either a matrix or a batch of matrices at runtime, pass `axis=[-2,-1]` instead of `axis=None` to make sure that matrix norms are computed. keepdims: If True, the axis indicated in `axis` are kept with size 1. Otherwise, the dimensions in `axis` are removed from the output shape. name: The name of the op. keep_dims: Deprecated alias for `keepdims`. Returns: output: A `Tensor` of the same type as tensor, containing the vector or matrix norms. If `keepdims` is True then the rank of output is equal to the rank of `tensor`. Otherwise, if `axis` is none the output is a scalar, if `axis` is an integer, the rank of `output` is one less than the rank of `tensor`, if `axis` is a 2-tuple the rank of `output` is two less than the rank of `tensor`. Raises: ValueError: If `ord` or `axis` is invalid. @compatibility(numpy) Mostly equivalent to numpy.linalg.norm. Not supported: ord <= 0, 2-norm for matrices, nuclear norm. Other differences: a) If axis is `None`, treats the flattened `tensor` as a vector regardless of rank. b) Explicitly supports 'euclidean' norm as the default, including for higher order tensors. @end_compatibility """ keepdims = deprecation.deprecated_argument_lookup('keepdims', keepdims, 'keep_dims', keep_dims) if keepdims is None: keepdims = False is_matrix_norm = ((isinstance(axis, tuple) or isinstance(axis, list)) and len(axis) == 2) if is_matrix_norm: axis = tuple(axis) if (not isinstance(axis[0], int) or not isinstance(axis[1], int) or axis[0] == axis[1]): raise ValueError( "'axis' must be None, an integer, or a tuple of 2 unique integers") supported_matrix_norms = ['euclidean', 'fro', 1, 2, np.inf] if ord not in supported_matrix_norms: raise ValueError("'ord' must be a supported matrix norm in %s, got %s" % (supported_matrix_norms, ord)) else: if not (isinstance(axis, int) or axis is None): raise ValueError( "'axis' must be None, an integer, or a tuple of 2 unique integers") supported_vector_norms = ['euclidean', 1, 2, np.inf] if (not np.isreal(ord) or ord <= 0) and ord not in supported_vector_norms: raise ValueError("'ord' must be a supported vector norm, got %s" % ord) if axis is not None: axis = (axis,) with ops.name_scope(name, 'norm', [tensor]): tensor = ops.convert_to_tensor(tensor) if ord in ['fro', 'euclidean', 2, 2.0]: if is_matrix_norm and ord in [2, 2.0]: rank = array_ops.rank(tensor) positive_axis = map_fn.map_fn( lambda i: control_flow_ops.cond(i >= 0, lambda: i, lambda: i + rank), ops.convert_to_tensor(axis)) axes = math_ops.range(rank) perm_before = array_ops.concat( [array_ops.setdiff1d(axes, positive_axis)[0], positive_axis], axis=0) perm_after = map_fn.map_fn( lambda i: math_ops.cast( array_ops.squeeze( array_ops.where(math_ops.equal(perm_before, i))), dtype=dtypes.int32), axes) permed = array_ops.transpose(tensor, perm=perm_before) matrix_2_norm = array_ops.expand_dims( math_ops.reduce_max( math_ops.abs(gen_linalg_ops.svd(permed, compute_uv=False)[0]), axis=-1, keepdims=True), axis=-1) result = array_ops.transpose(matrix_2_norm, perm=perm_after) else: result = math_ops.sqrt( math_ops.reduce_sum( tensor * math_ops.conj(tensor), axis, keepdims=True)) else: result = math_ops.abs(tensor) if ord == 1: sum_axis = None if axis is None else axis[0] result = math_ops.reduce_sum(result, sum_axis, keepdims=True) if is_matrix_norm: result = math_ops.reduce_max(result, axis[-1], keepdims=True) elif ord == np.inf: if is_matrix_norm: result = math_ops.reduce_sum(result, axis[1], keepdims=True) max_axis = None if axis is None else axis[0] result = math_ops.reduce_max(result, max_axis, keepdims=True) else: # General p-norms (positive p only) result = math_ops.pow( math_ops.reduce_sum(math_ops.pow(result, ord), axis, keepdims=True), 1.0 / ord) if not keepdims: result = array_ops.squeeze(result, axis) return result
def svd(tensor, full_matrices=False, compute_uv=True, name=None): r"""Computes the singular value decompositions of one or more matrices. Computes the SVD of each inner matrix in `tensor` such that `tensor[..., :, :] = u[..., :, :] * diag(s[..., :, :]) * transpose(conj(v[..., :, :]))` ```python # a is a tensor. # s is a tensor of singular values. # u is a tensor of left singular vectors. # v is a tensor of right singular vectors. s, u, v = svd(a) s = svd(a, compute_uv=False) ``` Args: tensor: `Tensor` of shape `[..., M, N]`. Let `P` be the minimum of `M` and `N`. full_matrices: If true, compute full-sized `u` and `v`. If false (the default), compute only the leading `P` singular vectors. Ignored if `compute_uv` is `False`. compute_uv: If `True` then left and right singular vectors will be computed and returned in `u` and `v`, respectively. Otherwise, only the singular values will be computed, which can be significantly faster. name: string, optional name of the operation. Returns: s: Singular values. Shape is `[..., P]`. The values are sorted in reverse order of magnitude, so s[..., 0] is the largest value, s[..., 1] is the second largest, etc. u: Left singular vectors. If `full_matrices` is `False` (default) then shape is `[..., M, P]`; if `full_matrices` is `True` then shape is `[..., M, M]`. Not returned if `compute_uv` is `False`. v: Right singular vectors. If `full_matrices` is `False` (default) then shape is `[..., N, P]`. If `full_matrices` is `True` then shape is `[..., N, N]`. Not returned if `compute_uv` is `False`. @compatibility(numpy) Mostly equivalent to numpy.linalg.svd, except that * The order of output arguments here is `s`, `u`, `v` when `compute_uv` is `True`, as opposed to `u`, `s`, `v` for numpy.linalg.svd. * full_matrices is `False` by default as opposed to `True` for numpy.linalg.svd. * tf.linalg.svd uses the standard definition of the SVD \\(A = U \Sigma V^H\\), such that the left singular vectors of `a` are the columns of `u`, while the right singular vectors of `a` are the columns of `v`. On the other hand, numpy.linalg.svd returns the adjoint \\(V^H\\) as the third output argument. ```python import tensorflow as tf import numpy as np s, u, v = tf.linalg.svd(a) tf_a_approx = tf.matmul(u, tf.matmul(tf.linalg.diag(s), v, adjoint_b=True)) u, s, v_adj = np.linalg.svd(a, full_matrices=False) np_a_approx = np.dot(u, np.dot(np.diag(s), v_adj)) # tf_a_approx and np_a_approx should be numerically close. ``` @end_compatibility """ s, u, v = gen_linalg_ops.svd( tensor, compute_uv=compute_uv, full_matrices=full_matrices, name=name) if compute_uv: return math_ops.real(s), u, v else: return math_ops.real(s)