Home | History | Annotate | Download | only in training
      1 # Copyright 2016 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 """Tests for external_optimizer."""
     16 
     17 from __future__ import absolute_import
     18 from __future__ import division
     19 from __future__ import print_function
     20 
     21 import numpy as np
     22 
     23 from tensorflow.contrib.opt.python.training import external_optimizer
     24 from tensorflow.python.framework import constant_op
     25 from tensorflow.python.framework import dtypes
     26 from tensorflow.python.ops import array_ops
     27 from tensorflow.python.ops import math_ops
     28 from tensorflow.python.ops import random_ops
     29 from tensorflow.python.ops import variables
     30 from tensorflow.python.platform import test
     31 
     32 # pylint: disable=g-import-not-at-top,unused-import
     33 try:
     34   import __builtin__ as builtins
     35 except ImportError:
     36   import builtins
     37 
     38 
     39 class MockOptimizerInterface(external_optimizer.ExternalOptimizerInterface):
     40 
     41   NUM_STEP_CALLS = 5
     42   NUM_LOSS_CALLS = 2
     43 
     44   def _minimize(self, initial_val, loss_grad_func, step_callback,
     45                 optimizer_kwargs, **unused_kwargs):
     46     """Minimize (x - x0)**2 / 2 with respect to x."""
     47     for _ in range(self.NUM_LOSS_CALLS):
     48       loss_grad_func(initial_val)
     49     for _ in range(self.NUM_STEP_CALLS):
     50       step_callback(initial_val)
     51 
     52     _, grad = loss_grad_func(initial_val)
     53     return initial_val - grad
     54 
     55 
     56 class TestCase(test.TestCase):
     57 
     58   def assertAllClose(self, array1, array2, rtol=1e-5, atol=1e-5):
     59     array1 = np.asarray(array1)
     60     array2 = np.asarray(array2)
     61     if not array1.shape:
     62       array1 = np.array([array1])
     63     if not array2.shape:
     64       array2 = np.array([array2])
     65 
     66     super(TestCase, self).assertAllClose(array1, array2, rtol=rtol, atol=atol)
     67 
     68 
     69 class ExternalOptimizerInterfaceTest(TestCase):
     70 
     71   def test_optimize(self):
     72     scalar = variables.Variable(random_ops.random_normal([]), 'scalar')
     73     vector = variables.Variable(random_ops.random_normal([2]), 'vector')
     74     matrix = variables.Variable(random_ops.random_normal([2, 3]), 'matrix')
     75 
     76     minimum_location = constant_op.constant(np.arange(9), dtype=dtypes.float32)
     77 
     78     loss = math_ops.reduce_sum(
     79         math_ops.square(vector - minimum_location[:2])) / 2.
     80     loss += math_ops.reduce_sum(
     81         math_ops.square(scalar - minimum_location[2])) / 2.
     82     loss += math_ops.reduce_sum(
     83         math_ops.square(
     84             matrix - array_ops.reshape(minimum_location[3:], [2, 3]))) / 2.
     85 
     86     optimizer = MockOptimizerInterface(loss)
     87 
     88     with self.test_session() as sess:
     89       sess.run(variables.global_variables_initializer())
     90 
     91       optimizer.minimize(sess)
     92 
     93       self.assertAllClose(np.arange(2), sess.run(vector))
     94       self.assertAllClose(np.arange(1) + 2, sess.run(scalar))
     95       self.assertAllClose(np.arange(6).reshape(2, 3) + 3, sess.run(matrix))
     96 
     97   def test_callbacks(self):
     98     vector_val = np.array([7., -2.], dtype=np.float32)
     99     vector = variables.Variable(vector_val, 'vector')
    100 
    101     minimum_location_val = np.arange(2)
    102     minimum_location = constant_op.constant(
    103         minimum_location_val, dtype=dtypes.float32)
    104 
    105     loss = math_ops.reduce_sum(math_ops.square(vector - minimum_location)) / 2.
    106     loss_val = ((vector_val - minimum_location_val)**2).sum() / 2.
    107 
    108     optimizer = MockOptimizerInterface(loss)
    109 
    110     with self.test_session() as sess:
    111       sess.run(variables.global_variables_initializer())
    112 
    113       initial_vector_val = sess.run(vector)
    114 
    115       extra_fetches = [loss]
    116 
    117       step_callback = test.mock.Mock()
    118       loss_callback = test.mock.Mock()
    119 
    120       optimizer.minimize(
    121           sess,
    122           fetches=extra_fetches,
    123           loss_callback=loss_callback,
    124           step_callback=step_callback)
    125 
    126       call = test.mock.call(loss_val)
    127       loss_calls = [call] * MockOptimizerInterface.NUM_LOSS_CALLS
    128       loss_callback.assert_has_calls(loss_calls)
    129 
    130       args, _ = step_callback.call_args
    131       self.assertAllClose(initial_vector_val, args[0])
    132 
    133 
    134 class ScipyOptimizerInterfaceTest(TestCase):
    135 
    136   def _objective(self, x):
    137     """Rosenbrock function. (Carl Edward Rasmussen, 2001-07-21).
    138 
    139     f(x) = sum_{i=1:D-1} 100*(x(i+1) - x(i)^2)^2 + (1-x(i))^2
    140 
    141     Args:
    142       x: a Variable
    143     Returns:
    144       f: a tensor (objective value)
    145     """
    146 
    147     d = array_ops.size(x)
    148     s = math_ops.add(
    149         100 * math_ops.square(
    150             math_ops.subtract(
    151                 array_ops.strided_slice(x, [1], [d]),
    152                 math_ops.square(array_ops.strided_slice(x, [0], [d - 1])))),
    153         math_ops.square(
    154             math_ops.subtract(1.0, array_ops.strided_slice(x, [0], [d - 1]))))
    155     return math_ops.reduce_sum(s)
    156 
    157   def _test_optimization_method(self,
    158                                 method,
    159                                 options,
    160                                 rtol=1e-5,
    161                                 atol=1e-5,
    162                                 dimension=5):
    163     x = variables.Variable(array_ops.zeros(dimension))
    164     optimizer = external_optimizer.ScipyOptimizerInterface(
    165         self._objective(x), method=method, options=options)
    166 
    167     with self.test_session() as sess:
    168       sess.run(variables.global_variables_initializer())
    169       optimizer.minimize(sess)
    170 
    171       self.assertAllClose(np.ones(dimension), sess.run(x), rtol=rtol, atol=atol)
    172 
    173   def test_unconstrained(self):
    174 
    175     dimension = 5
    176     x = variables.Variable(array_ops.zeros(dimension))
    177     optimizer = external_optimizer.ScipyOptimizerInterface(self._objective(x))
    178 
    179     with self.test_session() as sess:
    180       sess.run(variables.global_variables_initializer())
    181       optimizer.minimize(sess)
    182 
    183       self.assertAllClose(np.ones(dimension), sess.run(x))
    184 
    185   def test_nelder_mead_method2(self):
    186     self._test_optimization_method(
    187         method='Nelder-Mead', options={}, rtol=1e-4, atol=1e-4)
    188 
    189   def test_newton_cg_method(self):
    190     self._test_optimization_method(
    191         method='Newton-CG',
    192         options={'eps': 1e-03,
    193                  'xtol': 1e-05},
    194         rtol=1e-3,
    195         atol=1e-3)
    196 
    197   def test_newton_tnc_method(self):
    198     self._test_optimization_method(
    199         method='TNC',
    200         options={'gtol': -5,
    201                  'maxiter': 1000},
    202         rtol=1e-1,
    203         atol=1e-1)
    204 
    205   def test_cobyla_method(self):
    206     # COBYLA does not reach the global optima
    207     self._test_optimization_method(
    208         method='COBYLA',
    209         options={
    210             'maxiter': 9000,
    211         },
    212         rtol=1e-1,
    213         atol=1e-1,
    214         dimension=2)
    215 
    216   def test_slsqp_method(self):
    217     self._test_optimization_method(
    218         method='SLSQP', options={}, rtol=1e-3, atol=1e-3)
    219 
    220   def test_cg_method(self):
    221     self._test_optimization_method(
    222         method='CG', options={'gtol': 1e-03}, rtol=1e-3, atol=1e-3)
    223 
    224   def test_other_optimization_methods(self):
    225     # These methods do not require special options to converge on rosenbrock
    226     methods = ['Powell', 'BFGS', 'L-BFGS-B']
    227 
    228     for method in methods:
    229       self._test_optimization_method(method=method, options={})
    230 
    231   def test_nonlinear_programming(self):
    232     vector_initial_value = [7., 7.]
    233     vector = variables.Variable(vector_initial_value, 'vector')
    234 
    235     # Make norm as small as possible.
    236     loss = math_ops.reduce_sum(math_ops.square(vector))
    237     # Ensure y = 1.
    238     equalities = [vector[1] - 1.]
    239     # Ensure x >= 1. Thus optimum should be at (1, 1).
    240     inequalities = [vector[0] - 1.]
    241 
    242     optimizer = external_optimizer.ScipyOptimizerInterface(
    243         loss, equalities=equalities, inequalities=inequalities, method='SLSQP')
    244 
    245     with self.test_session() as sess:
    246       sess.run(variables.global_variables_initializer())
    247       optimizer.minimize(sess)
    248       self.assertAllClose(np.ones(2), sess.run(vector))
    249 
    250   def test_scalar_bounds(self):
    251     vector_initial_value = [7., 7.]
    252     vector = variables.Variable(vector_initial_value, 'vector')
    253 
    254     # Make norm as small as possible.
    255     loss = math_ops.reduce_sum(math_ops.square(vector))
    256 
    257     # Make the minimum value of each component be 1.
    258     var_to_bounds = {vector: (1., np.infty)}
    259 
    260     optimizer = external_optimizer.ScipyOptimizerInterface(
    261         loss, var_to_bounds=var_to_bounds)
    262 
    263     with self.test_session() as sess:
    264       sess.run(variables.global_variables_initializer())
    265       optimizer.minimize(sess)
    266       self.assertAllClose(np.ones(2), sess.run(vector))
    267 
    268   def test_vector_bounds(self):
    269     vector_initial_value = [7., 7.]
    270     vector = variables.Variable(vector_initial_value, 'vector')
    271 
    272     # Make norm as small as possible.
    273     loss = math_ops.reduce_sum(math_ops.square(vector))
    274 
    275     var_to_bounds = {vector: ([None, 2.], None)}
    276 
    277     optimizer = external_optimizer.ScipyOptimizerInterface(
    278         loss, var_to_bounds=var_to_bounds)
    279 
    280     with self.test_session() as sess:
    281       sess.run(variables.global_variables_initializer())
    282       optimizer.minimize(sess)
    283       self.assertAllClose([0., 2.], sess.run(vector))
    284 
    285   def test_optimizer_kwargs(self):
    286     # Checks that the 'method' argument is stil present
    287     # after running optimizer.minimize().
    288     # Bug reference: b/64065260
    289     vector_initial_value = [7., 7.]
    290     vector = variables.Variable(vector_initial_value, 'vector')
    291     loss = math_ops.reduce_sum(math_ops.square(vector))
    292 
    293     optimizer = external_optimizer.ScipyOptimizerInterface(
    294         loss, method='SLSQP')
    295 
    296     with self.test_session() as sess:
    297       sess.run(variables.global_variables_initializer())
    298       optimizer.minimize(sess)
    299       method = optimizer.optimizer_kwargs.get('method')
    300       self.assertEqual('SLSQP', method)
    301 
    302   def test_callbacks(self):
    303     vector_val = np.array([7., -2.], dtype=np.float32)
    304     vector = variables.Variable(vector_val, 'vector')
    305 
    306     minimum_location_val = np.arange(2)
    307     minimum_location = constant_op.constant(
    308         minimum_location_val, dtype=dtypes.float32)
    309 
    310     loss = math_ops.reduce_sum(math_ops.square(vector - minimum_location)) / 2.
    311     loss_val_first = ((vector_val - minimum_location_val)**2).sum() / 2.
    312 
    313     optimizer = external_optimizer.ScipyOptimizerInterface(loss, method='SLSQP')
    314 
    315     with self.test_session() as sess:
    316       sess.run(variables.global_variables_initializer())
    317 
    318       initial_vector_val = sess.run(vector)
    319 
    320       extra_fetches = [loss]
    321 
    322       step_callback = test.mock.Mock()
    323       loss_callback = test.mock.Mock()
    324 
    325       optimizer.minimize(
    326           sess,
    327           fetches=extra_fetches,
    328           loss_callback=loss_callback,
    329           step_callback=step_callback)
    330 
    331       loss_val_last = sess.run(loss)
    332 
    333       call_first = test.mock.call(loss_val_first)
    334       call_last = test.mock.call(loss_val_last)
    335       loss_calls = [call_first, call_last]
    336       loss_callback.assert_has_calls(loss_calls, any_order=True)
    337 
    338       args, _ = step_callback.call_args
    339       self.assertAllClose(minimum_location_val, args[0])
    340 
    341 
    342 if __name__ == '__main__':
    343   test.main()
    344