Home | History | Annotate | Download | only in ops
      1 # Copyright 2017 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 """Test utils for factorization_ops."""
     16 
     17 from __future__ import absolute_import
     18 from __future__ import division
     19 from __future__ import print_function
     20 
     21 import random
     22 
     23 import numpy as np
     24 
     25 from tensorflow.python.framework import constant_op
     26 from tensorflow.python.framework import sparse_tensor
     27 from tensorflow.python.ops import array_ops
     28 from tensorflow.python.ops import math_ops
     29 from tensorflow.python.ops import sparse_ops
     30 
     31 
     32 INPUT_MATRIX = np.array(
     33     [[0.1, 0.0, 0.2, 0.0, 0.4, 0.5, 0.0],
     34      [0.0, 1.1, 0.0, 1.3, 1.4, 0.0, 1.6],
     35      [2.0, 0.0, 0.0, 2.3, 0.0, 2.5, 0.0],
     36      [3.0, 0.0, 3.2, 3.3, 0.0, 3.5, 0.0],
     37      [0.0, 4.1, 0.0, 0.0, 4.4, 0.0, 4.6]]).astype(np.float32)
     38 
     39 
     40 def remove_empty_rows_columns(np_matrix):
     41   """Simple util to remove empty rows and columns of a matrix.
     42 
     43   Args:
     44     np_matrix: A numpy array.
     45   Returns:
     46     A tuple consisting of:
     47     mat: A numpy matrix obtained by removing empty rows and columns from
     48       np_matrix.
     49     nz_row_ids: A numpy array of the ids of non-empty rows, such that
     50       nz_row_ids[i] is the old row index corresponding to new index i.
     51     nz_col_ids: A numpy array of the ids of non-empty columns, such that
     52       nz_col_ids[j] is the old column index corresponding to new index j.
     53   """
     54   nz_row_ids = np.where(np.sum(np_matrix, axis=1) != 0)[0]
     55   nz_col_ids = np.where(np.sum(np_matrix, axis=0) != 0)[0]
     56   mat = np_matrix[np.ix_(nz_row_ids, nz_col_ids)]
     57   return mat, nz_row_ids, nz_col_ids
     58 
     59 
     60 def np_matrix_to_tf_sparse(np_matrix,
     61                            row_slices=None,
     62                            col_slices=None,
     63                            transpose=False,
     64                            shuffle=False):
     65   """Simple util to slice non-zero np matrix elements as tf.SparseTensor."""
     66   indices = np.nonzero(np_matrix)
     67 
     68   # Only allow slices of whole rows or whole columns.
     69   assert not (row_slices is not None and col_slices is not None)
     70 
     71   if row_slices is not None:
     72     selected_ind = np.concatenate(
     73         [np.where(indices[0] == r)[0] for r in row_slices], 0)
     74     indices = (indices[0][selected_ind], indices[1][selected_ind])
     75 
     76   if col_slices is not None:
     77     selected_ind = np.concatenate(
     78         [np.where(indices[1] == c)[0] for c in col_slices], 0)
     79     indices = (indices[0][selected_ind], indices[1][selected_ind])
     80 
     81   if shuffle:
     82     shuffled_ind = [x for x in range(len(indices[0]))]
     83     random.shuffle(shuffled_ind)
     84     indices = (indices[0][shuffled_ind], indices[1][shuffled_ind])
     85 
     86   ind = (np.concatenate((np.expand_dims(indices[1], 1),
     87                          np.expand_dims(indices[0], 1)), 1).astype(np.int64) if
     88          transpose else np.concatenate((np.expand_dims(indices[0], 1),
     89                                         np.expand_dims(indices[1], 1)),
     90                                        1).astype(np.int64))
     91   val = np_matrix[indices].astype(np.float32)
     92   shape = (np.array([max(indices[1]) + 1, max(indices[0]) + 1]).astype(np.int64)
     93            if transpose else np.array(
     94                [max(indices[0]) + 1, max(indices[1]) + 1]).astype(np.int64))
     95   return sparse_tensor.SparseTensor(ind, val, shape)
     96 
     97 
     98 def calculate_loss(input_mat, row_factors, col_factors, regularization=None,
     99                    w0=1., row_weights=None, col_weights=None):
    100   """Calculates the loss of a given factorization.
    101 
    102   Using a non distributed method, different than the one implemented in the
    103   WALS model. The weight of an observed entry (i, j) (i.e. such that
    104   input_mat[i, j] is non zero) is (w0 + row_weights[i]col_weights[j]).
    105 
    106   Args:
    107     input_mat: The input matrix, a SparseTensor of rank 2.
    108     row_factors: The row factors, a dense Tensor of rank 2.
    109     col_factors: The col factors, a dense Tensor of rank 2.
    110     regularization: the regularization coefficient, a scalar.
    111     w0: the weight of unobserved entries. A scalar.
    112     row_weights: A dense tensor of rank 1.
    113     col_weights: A dense tensor of rank 1.
    114 
    115   Returns:
    116     The total loss.
    117   """
    118   wr = (array_ops.expand_dims(row_weights, 1) if row_weights is not None
    119         else constant_op.constant(1.))
    120   wc = (array_ops.expand_dims(col_weights, 0) if col_weights is not None
    121         else constant_op.constant(1.))
    122   reg = (regularization if regularization is not None
    123          else constant_op.constant(0.))
    124 
    125   row_indices, col_indices = array_ops.split(input_mat.indices,
    126                                              axis=1,
    127                                              num_or_size_splits=2)
    128   gathered_row_factors = array_ops.gather(row_factors, row_indices)
    129   gathered_col_factors = array_ops.gather(col_factors, col_indices)
    130   sp_approx_vals = array_ops.squeeze(math_ops.matmul(
    131       gathered_row_factors, gathered_col_factors, adjoint_b=True))
    132   sp_approx = sparse_tensor.SparseTensor(
    133       indices=input_mat.indices,
    134       values=sp_approx_vals,
    135       dense_shape=input_mat.dense_shape)
    136 
    137   sp_approx_sq = math_ops.square(sp_approx)
    138   row_norm = math_ops.reduce_sum(math_ops.square(row_factors))
    139   col_norm = math_ops.reduce_sum(math_ops.square(col_factors))
    140   row_col_norm = math_ops.reduce_sum(math_ops.square(math_ops.matmul(
    141       row_factors, col_factors, transpose_b=True)))
    142 
    143   resid = sparse_ops.sparse_add(input_mat, sp_approx * (-1))
    144   resid_sq = math_ops.square(resid)
    145   loss = w0 * (
    146       sparse_ops.sparse_reduce_sum(resid_sq) -
    147       sparse_ops.sparse_reduce_sum(sp_approx_sq)
    148       )
    149   loss += (sparse_ops.sparse_reduce_sum(wr * (resid_sq * wc)) +
    150            w0 * row_col_norm + reg * (row_norm + col_norm))
    151   return loss.eval()
    152