def _maximal_eigenvector_power_method(matrix, epsilon=1e-6, maximum_iterations=100): """Returns the maximal right-eigenvector of `matrix` using the power method. Args: matrix: 2D Tensor, the matrix of which we will find the maximal right-eigenvector. epsilon: nonnegative float, if two iterations of the power method differ (in L2 norm) by no more than epsilon, we will terminate. maximum_iterations: nonnegative int, if we perform this many iterations, we will terminate. Result: The maximal right-eigenvector of `matrix`. Raises: ValueError: If the `matrix` tensor is not floating-point, or if the `epsilon` or `maximum_iterations` parameters violate their bounds. """ if not matrix.dtype.is_floating: raise ValueError("multipliers must have a floating-point dtype") if epsilon <= 0.0: raise ValueError("epsilon must be strictly positive") if maximum_iterations <= 0: raise ValueError("maximum_iterations must be strictly positive") def while_loop_condition(iteration, eigenvector, old_eigenvector): """Returns false if the while loop should terminate.""" not_done = (iteration < maximum_iterations) not_converged = (standard_ops.norm(eigenvector - old_eigenvector) > epsilon) return standard_ops.logical_and(not_done, not_converged) def while_loop_body(iteration, eigenvector, old_eigenvector): """Performs one iteration of the power method.""" del old_eigenvector # Needed by the condition, but not the body. iteration += 1 # We need to use tf.matmul() and tf.expand_dims(), instead of # tf.tensordot(), since the former will infer the shape of the result, while # the latter will not (tf.while_loop() needs the shapes). new_eigenvector = standard_ops.matmul( matrix, standard_ops.expand_dims(eigenvector, 1))[:, 0] new_eigenvector /= standard_ops.norm(new_eigenvector) return (iteration, new_eigenvector, eigenvector) iteration = standard_ops.constant(0) eigenvector = standard_ops.ones_like(matrix[:, 0]) eigenvector /= standard_ops.norm(eigenvector) # We actually want a do-while loop, so we explicitly call while_loop_body() # once before tf.while_loop(). iteration, eigenvector, old_eigenvector = while_loop_body( iteration, eigenvector, eigenvector) iteration, eigenvector, old_eigenvector = control_flow_ops.while_loop( while_loop_condition, while_loop_body, loop_vars=(iteration, eigenvector, old_eigenvector), name="power_method") return eigenvector
def _project_stochastic_matrix_wrt_euclidean_norm(matrix): """Projects its argument onto the set of left-stochastic matrices. This algorithm is O(n^3) at worst, where `matrix` is n*n. It can be done in O(n^2 * log(n)) time by sorting each column (and maybe better with a different algorithm), but the algorithm implemented here is easier to implement in TensorFlow. Args: matrix: 2d square tensor, the matrix to project. Returns: The 2d square tensor that results from projecting `matrix` onto the set of left-stochastic matrices w.r.t. the Euclidean norm applied column-wise (i.e. the Frobenius norm). Raises: ValueError: if the `matrix` tensor is not floating-point, does not have a fully-known shape, or is not two-dimensional and square. """ if not matrix.dtype.is_floating: raise ValueError("multipliers must have a floating-point dtype") matrix_shape = matrix.get_shape() if matrix_shape.ndims is None: raise ValueError("matrix must have known shape") if matrix_shape.ndims != 2: raise ValueError( "matrix must be two dimensional (instead is %d-dimensional)" % matrix_shape.ndims) if matrix_shape[0] != matrix_shape[1]: raise ValueError("matrix must be square (instead has shape (%d,%d))" % (matrix_shape[0], matrix_shape[1])) dimension = matrix_shape.dims[0].value if dimension is None: raise ValueError("matrix must have fully-known shape") def while_loop_condition(iteration, matrix, inactive, old_inactive): """Returns false if the while loop should terminate.""" del matrix # Needed by the body, but not the condition. not_done = (iteration < dimension) not_converged = standard_ops.reduce_any( standard_ops.not_equal(inactive, old_inactive)) return standard_ops.logical_and(not_done, not_converged) def while_loop_body(iteration, matrix, inactive, old_inactive): """Performs one iteration of the projection.""" del old_inactive # Needed by the condition, but not the body. iteration += 1 scale = (1.0 - standard_ops.reduce_sum( matrix, axis=0, keepdims=True)) / standard_ops.maximum( 1.0, standard_ops.reduce_sum(inactive, axis=0, keepdims=True)) matrix = matrix + (scale * inactive) new_inactive = standard_ops.cast(matrix > 0, matrix.dtype) matrix = matrix * new_inactive return (iteration, matrix, new_inactive, inactive) iteration = standard_ops.constant(0) inactive = standard_ops.ones_like(matrix, dtype=matrix.dtype) # We actually want a do-while loop, so we explicitly call while_loop_body() # once before tf.while_loop(). iteration, matrix, inactive, old_inactive = while_loop_body( iteration, matrix, inactive, inactive) iteration, matrix, inactive, old_inactive = control_flow_ops.while_loop( while_loop_condition, while_loop_body, loop_vars=(iteration, matrix, inactive, old_inactive), name="euclidean_projection") return matrix
def _project_stochastic_matrix_wrt_euclidean_norm(matrix): """Projects its argument onto the set of left-stochastic matrices. This algorithm is O(n^3) at worst, where `matrix` is n*n. It can be done in O(n^2 * log(n)) time by sorting each column (and maybe better with a different algorithm), but the algorithm implemented here is easier to implement in TensorFlow. Args: matrix: 2d square tensor, the matrix to project. Returns: The 2d square tensor that results from projecting `matrix` onto the set of left-stochastic matrices w.r.t. the Euclidean norm applied column-wise (i.e. the Frobenius norm). Raises: ValueError: if the `matrix` tensor is not floating-point, does not have a fully-known shape, or is not two-dimensional and square. """ if not matrix.dtype.is_floating: raise ValueError("multipliers must have a floating-point dtype") matrix_shape = matrix.get_shape() if matrix_shape.ndims is None: raise ValueError("matrix must have known shape") if matrix_shape.ndims != 2: raise ValueError( "matrix must be two dimensional (instead is %d-dimensional)" % matrix_shape.ndims) if matrix_shape[0] != matrix_shape[1]: raise ValueError("matrix must be square (instead has shape (%d,%d))" % (matrix_shape[0], matrix_shape[1])) dimension = matrix_shape[0].value if dimension is None: raise ValueError("matrix must have fully-known shape") def while_loop_condition(iteration, matrix, inactive, old_inactive): """Returns false if the while loop should terminate.""" del matrix # Needed by the body, but not the condition. not_done = (iteration < dimension) not_converged = standard_ops.reduce_any( standard_ops.not_equal(inactive, old_inactive)) return standard_ops.logical_and(not_done, not_converged) def while_loop_body(iteration, matrix, inactive, old_inactive): """Performs one iteration of the projection.""" del old_inactive # Needed by the condition, but not the body. iteration += 1 scale = (1.0 - standard_ops.reduce_sum( matrix, axis=0, keepdims=True)) / standard_ops.maximum( 1.0, standard_ops.reduce_sum(inactive, axis=0, keepdims=True)) matrix += scale * inactive new_inactive = standard_ops.cast(matrix > 0, matrix.dtype) matrix *= new_inactive return (iteration, matrix, new_inactive, inactive) iteration = standard_ops.constant(0) inactive = standard_ops.ones_like(matrix, dtype=matrix.dtype) # We actually want a do-while loop, so we explicitly call while_loop_body() # once before tf.while_loop(). iteration, matrix, inactive, old_inactive = while_loop_body( iteration, matrix, inactive, inactive) iteration, matrix, inactive, old_inactive = control_flow_ops.while_loop( while_loop_condition, while_loop_body, loop_vars=(iteration, matrix, inactive, old_inactive), name="euclidean_projection") return matrix
def _project_multipliers_wrt_euclidean_norm(multipliers, radius): """Projects its argument onto the feasible region. The feasible region is the set of all vectors with nonnegative elements that sum to at most `radius`. Args: multipliers: 1d tensor, the Lagrange multipliers to project. radius: float, the radius of the feasible region. Returns: The 1d tensor that results from projecting `multipliers` onto the feasible region w.r.t. the Euclidean norm. Raises: ValueError: if the `multipliers` tensor is not floating-point, does not have a fully-known shape, or is not one-dimensional. """ if not multipliers.dtype.is_floating: raise ValueError("multipliers must have a floating-point dtype") multipliers_shape = multipliers.get_shape() if multipliers_shape.ndims is None: raise ValueError("multipliers must have known shape") if multipliers_shape.ndims != 1: raise ValueError( "multipliers must be one dimensional (instead is %d-dimensional)" % multipliers_shape.ndims) dimension = multipliers_shape[0].value if dimension is None: raise ValueError("multipliers must have fully-known shape") def while_loop_condition(iteration, multipliers, inactive, old_inactive): """Returns false if the while loop should terminate.""" del multipliers # Needed by the body, but not the condition. not_done = (iteration < dimension) not_converged = standard_ops.reduce_any( standard_ops.not_equal(inactive, old_inactive)) return standard_ops.logical_and(not_done, not_converged) def while_loop_body(iteration, multipliers, inactive, old_inactive): """Performs one iteration of the projection.""" del old_inactive # Needed by the condition, but not the body. iteration += 1 scale = standard_ops.minimum( 0.0, (radius - standard_ops.reduce_sum(multipliers)) / standard_ops.maximum(1.0, standard_ops.reduce_sum(inactive))) multipliers = multipliers + (scale * inactive) new_inactive = standard_ops.cast(multipliers > 0, multipliers.dtype) multipliers = multipliers * new_inactive return (iteration, multipliers, new_inactive, inactive) iteration = standard_ops.constant(0) inactive = standard_ops.ones_like(multipliers, dtype=multipliers.dtype) # We actually want a do-while loop, so we explicitly call while_loop_body() # once before tf.while_loop(). iteration, multipliers, inactive, old_inactive = while_loop_body( iteration, multipliers, inactive, inactive) iteration, multipliers, inactive, old_inactive = control_flow_ops.while_loop( while_loop_condition, while_loop_body, loop_vars=(iteration, multipliers, inactive, old_inactive), name="euclidean_projection") return multipliers
def _project_multipliers_wrt_euclidean_norm(multipliers, radius): """Projects its argument onto the feasible region. The feasible region is the set of all vectors with nonnegative elements that sum to at most `radius`. Args: multipliers: 1d tensor, the Lagrange multipliers to project. radius: float, the radius of the feasible region. Returns: The 1d tensor that results from projecting `multipliers` onto the feasible region w.r.t. the Euclidean norm. Raises: ValueError: if the `multipliers` tensor is not floating-point, does not have a fully-known shape, or is not one-dimensional. """ if not multipliers.dtype.is_floating: raise ValueError("multipliers must have a floating-point dtype") multipliers_shape = multipliers.get_shape() if multipliers_shape.ndims is None: raise ValueError("multipliers must have known shape") if multipliers_shape.ndims != 1: raise ValueError( "multipliers must be one dimensional (instead is %d-dimensional)" % multipliers_shape.ndims) dimension = multipliers_shape[0].value if dimension is None: raise ValueError("multipliers must have fully-known shape") def while_loop_condition(iteration, multipliers, inactive, old_inactive): """Returns false if the while loop should terminate.""" del multipliers # Needed by the body, but not the condition. not_done = (iteration < dimension) not_converged = standard_ops.reduce_any( standard_ops.not_equal(inactive, old_inactive)) return standard_ops.logical_and(not_done, not_converged) def while_loop_body(iteration, multipliers, inactive, old_inactive): """Performs one iteration of the projection.""" del old_inactive # Needed by the condition, but not the body. iteration += 1 scale = standard_ops.minimum( 0.0, (radius - standard_ops.reduce_sum(multipliers)) / standard_ops.maximum( 1.0, standard_ops.reduce_sum(inactive))) multipliers += scale * inactive new_inactive = standard_ops.cast(multipliers > 0, multipliers.dtype) multipliers *= new_inactive return (iteration, multipliers, new_inactive, inactive) iteration = standard_ops.constant(0) inactive = standard_ops.ones_like(multipliers, dtype=multipliers.dtype) # We actually want a do-while loop, so we explicitly call while_loop_body() # once before tf.while_loop(). iteration, multipliers, inactive, old_inactive = while_loop_body( iteration, multipliers, inactive, inactive) iteration, multipliers, inactive, old_inactive = control_flow_ops.while_loop( while_loop_condition, while_loop_body, loop_vars=(iteration, multipliers, inactive, old_inactive), name="euclidean_projection") return multipliers