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