test_learning_rate_scheduler.py 20.4 KB
Newer Older
Q
Qiao Longfei 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14
# Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

15
import copy
16 17
import math
import unittest
18 19 20

import numpy as np

21
import paddle
22 23
from paddle import fluid
from paddle.fluid import core, framework, layers
Q
Qiao Longfei 已提交
24 25


26 27 28
def exponential_decay(
    learning_rate, global_step, decay_steps, decay_rate, staircase=False
):
Y
Yu Yang 已提交
29
    exponent = global_step / decay_steps
Q
Qiao Longfei 已提交
30 31 32 33 34
    if staircase:
        exponent = math.floor(exponent)
    return learning_rate * decay_rate**exponent


35 36 37
def natural_exp_decay(
    learning_rate, global_step, decay_steps, decay_rate, staircase=False
):
Q
Qiao Longfei 已提交
38 39 40 41 42 43
    exponent = float(global_step) / float(decay_steps)
    if staircase:
        exponent = math.floor(exponent)
    return learning_rate * math.exp(-1 * decay_rate * exponent)


44 45 46
def inverse_time_decay(
    learning_rate, global_step, decay_steps, decay_rate, staircase=False
):
Q
Qiao Longfei 已提交
47 48 49 50 51 52
    temp = float(global_step) / float(decay_steps)
    if staircase:
        temp = math.floor(temp)
    return learning_rate / (1 + decay_rate * temp)


53 54 55 56 57 58 59 60
def polynomial_decay(
    learning_rate,
    global_step,
    decay_steps,
    end_learning_rate=0.0001,
    power=1.0,
    cycle=False,
):
61 62 63 64 65 66 67
    if cycle:
        div = math.ceil(global_step / float(decay_steps))
        if div == 0:
            div = 1
        decay_steps = decay_steps * div
    else:
        global_step = min(global_step, decay_steps)
68 69 70
    return (learning_rate - end_learning_rate) * (
        (1 - float(global_step) / float(decay_steps)) ** power
    ) + end_learning_rate
71 72 73 74 75 76 77 78


def piecewise_decay(global_step, boundaries, values):
    assert len(boundaries) + 1 == len(values)
    for i in range(len(boundaries)):
        if global_step < boundaries[i]:
            return values[i]
    return values[len(values) - 1]
Q
Qiao Longfei 已提交
79

80

S
shippingwang 已提交
81 82
def cosine_decay(global_step, learning_rate, step_each_epoch, epochs):
    cur_epoch = math.floor(global_step / step_each_epoch)
83 84 85
    decayed_lr = (
        learning_rate * 0.5 * (math.cos(cur_epoch * math.pi / epochs) + 1)
    )
S
shippingwang 已提交
86 87 88
    return decayed_lr


89 90 91 92 93 94 95 96
def noam_decay(global_step, d_model, warmup_steps, learning_rate=1.0):
    a = math.pow(global_step, -0.5)
    b = math.pow(warmup_steps, -1.5) * global_step
    decayed_lr = learning_rate * math.pow(d_model, -0.5) * min(a, b)

    return decayed_lr


97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
def linear_lr_warmup(global_step, warmup_steps, start_lr, end_lr):
    linear_step = end_lr - start_lr
    decayed_lr = start_lr + linear_step * (global_step / warmup_steps)
    return decayed_lr


def multi_step_decay(global_step, learning_rate, milestones, decay_rate=0.1):
    for i in range(len(milestones)):
        if global_step < milestones[i]:
            return learning_rate * math.pow(decay_rate, i)

    return learning_rate * math.pow(decay_rate, len(milestones))


def step_decay(global_step, learning_rate, step_size, decay_rate=0.1):
    return learning_rate * math.pow(decay_rate, global_step // step_size)


115 116 117 118
def lambda_decay(global_step, learning_rate, lr_lambda):
    return learning_rate * lr_lambda(global_step)


119
class TestLearningRateDecayDygraph(unittest.TestCase):
120 121 122
    def test_LR_state_dict(self):
        with fluid.dygraph.guard():
            x = np.random.uniform(-1, 1, [3, 10]).astype("float32")
123
            linear = paddle.nn.Linear(10, 10)
124 125 126 127 128 129
            input = fluid.dygraph.to_variable(x)

            Exponential_scheduler = fluid.dygraph.ExponentialDecay(
                learning_rate=0.1,
                decay_steps=10000,
                decay_rate=0.5,
130 131
                staircase=True,
            )
132 133
            Step_scheduler = fluid.dygraph.StepDecay(0.5, step_size=3)
            Reducelr_scheduler = fluid.dygraph.ReduceLROnPlateau(
134 135 136 137 138 139 140 141 142 143 144 145 146 147
                learning_rate=1.0, decay_rate=0.5, patience=5, cooldown=3
            )

            adam1 = fluid.optimizer.Adam(
                learning_rate=Exponential_scheduler,
                parameter_list=linear.parameters(),
            )
            adam2 = fluid.optimizer.Adam(
                learning_rate=Step_scheduler, parameter_list=linear.parameters()
            )
            adam3 = fluid.optimizer.Adam(
                learning_rate=Reducelr_scheduler,
                parameter_list=linear.parameters(),
            )
148 149 150 151
            print(adam3.state_dict())

            for epoch in range(10):
                out = linear(input)
152
                loss = paddle.mean(out)
153 154 155 156 157 158 159 160 161
                loss.backward()
                adam1.minimize(loss)
                adam2.minimize(loss)
                adam3.minimize(loss)
                linear.clear_gradients()

                Step_scheduler.epoch()
                Reducelr_scheduler.step(loss)

162
            paddle.save(linear.state_dict(), "save_path.pdparams")
163 164 165 166 167

            Exponential_scheduler_test = fluid.dygraph.ExponentialDecay(
                learning_rate=0.1,
                decay_steps=10000,
                decay_rate=0.5,
168 169
                staircase=True,
            )
170 171
            Step_scheduler_test = fluid.dygraph.StepDecay(0.5, step_size=3)
            Reducelr_scheduler_test = fluid.dygraph.ReduceLROnPlateau(
172 173
                learning_rate=1.0, decay_rate=0.5, patience=5, cooldown=3
            )
174

175 176
            paddle.save(adam1.state_dict(), "save_path.pdopt")
            opt_state = paddle.load("save_path.pdopt")
177 178
            adam_test = fluid.optimizer.Adam(
                learning_rate=Exponential_scheduler_test,
179 180
                parameter_list=linear.parameters(),
            )
181
            adam_test.set_dict(opt_state)
182 183 184
            self.assertEqual(
                adam_test._learning_rate.step_num,
                adam1._learning_rate.step_num,
185 186
                "epoch_num is different before and after set_dict",
            )
187

188 189
            paddle.save(adam2.state_dict(), "save_path.pdopt")
            opt_state = paddle.load("save_path.pdopt")
190 191 192 193
            adam_test = fluid.optimizer.Adam(
                learning_rate=Step_scheduler_test,
                parameter_list=linear.parameters(),
            )
194 195
            adam_test.set_dict(opt_state)
            self.assertEqual(
196 197
                adam_test._learning_rate.epoch_num,
                adam2._learning_rate.epoch_num,
198 199
                "epoch_num is different before and after set_dict",
            )
200
            self.assertEqual(
201 202 203 204
                adam_test._learning_rate(),
                adam2._learning_rate(),
                "current learning rate is different before and after set_dict",
            )
205

206 207
            paddle.save(adam3.state_dict(), "save_path.pdopt")
            opt_state = paddle.load("save_path.pdopt")
208 209
            adam_test = fluid.optimizer.Adam(
                learning_rate=Reducelr_scheduler_test,
210 211
                parameter_list=linear.parameters(),
            )
212
            adam_test.set_dict(opt_state)
213 214
            self.assertEqual(
                adam_test._learning_rate.best_loss,
215
                adam3._learning_rate.best_loss,
216 217
                "best_loss is different before and after set_dict",
            )
218 219 220
            self.assertEqual(
                adam_test._learning_rate.cooldown_counter,
                adam3._learning_rate.cooldown_counter,
221 222
                "cooldown_counter is different before and after set_dict",
            )
223 224 225
            self.assertEqual(
                adam_test._learning_rate.num_bad_epochs,
                adam3._learning_rate.num_bad_epochs,
226 227 228 229 230 231 232
                "num_bad_epochs is different before and after set_dict",
            )
            self.assertEqual(
                adam_test._learning_rate.epoch_num,
                adam3._learning_rate.epoch_num,
                "epoch is different before and after set_dict",
            )
233
            self.assertEqual(
234 235 236 237
                adam_test._learning_rate(),
                adam3._learning_rate(),
                "current learning rate is different before and after set_dict",
            )
238

239
    def test_NoamDecay(self):
240 241 242 243 244 245 246
        with fluid.dygraph.guard():
            d_model = 0.01
            warmup_steps = 200
            learning_rate = 2.0
            lr = fluid.layers.noam_decay(d_model, warmup_steps, learning_rate)
            for step in range(5):
                step += 1
247 248 249
                right_result = noam_decay(
                    step, d_model, warmup_steps, learning_rate
                )
250 251 252 253 254
                fluid_result = lr()

                self.assertAlmostEqual(
                    right_result,
                    fluid_result[0],
255
                    msg='Failed lr scheduler in step {}, Python result is {}, Fluid result is {}'.format(
256 257 258
                        step, right_result, fluid_result[0]
                    ),
                )
259

260 261
    def test_LinearLrWarmup(self):
        with fluid.dygraph.guard():
262 263 264 265 266 267 268 269 270
            lr = fluid.layers.polynomial_decay(
                learning_rate=1.0,
                decay_steps=10,
                end_learning_rate=0.0,
                power=1.0,
            )
            lr = fluid.layers.linear_lr_warmup(
                learning_rate=lr, warmup_steps=2, start_lr=0.0, end_lr=1.0
            )
271 272 273 274 275

            right_result = [0.5, 0.9, 0.8, 0.7, 0.6]
            for i in range(5):
                t = lr()

276
                np.testing.assert_allclose(
277
                    t.numpy().item(), right_result[i], rtol=1e-05
278
                )
279 280

            with self.assertRaises(TypeError):
281 282 283 284 285 286
                lr = fluid.layers.linear_lr_warmup(
                    learning_rate="fake_lr",
                    warmup_steps=2,
                    start_lr=0.0,
                    end_lr=1.0,
                )
287 288 289 290 291 292

    def test_MultiStepDecay(self):
        with fluid.dygraph.guard():
            learning_rate = 0.5
            milestones = [2, 4, 8]
            decay_rate = 0.2
293
            linear = paddle.nn.Linear(10, 10)
294

295 296 297
            scheduler = fluid.dygraph.MultiStepDecay(
                learning_rate, milestones, decay_rate
            )
298 299

            adam = fluid.optimizer.AdamOptimizer(
300 301
                learning_rate=scheduler, parameter_list=linear.parameters()
            )
302
            for epoch in range(10):
303 304 305
                right_result = multi_step_decay(
                    epoch, learning_rate, milestones, decay_rate
                )
306
                fluid_result = adam.current_step_lr()
307 308 309 310
                scheduler.epoch()
                self.assertAlmostEqual(
                    right_result,
                    fluid_result,
311
                    msg='Failed lr scheduler in epoch {}, Python result is {}, Fluid result is {}'.format(
312 313 314
                        epoch, right_result, fluid_result
                    ),
                )
315 316

            with self.assertRaises(ValueError):
317 318 319
                lr = fluid.dygraph.MultiStepDecay(
                    learning_rate, [30, 50, 20], 0.1
                )
320 321

            with self.assertRaises(ValueError):
322 323 324
                lr = fluid.dygraph.MultiStepDecay(
                    learning_rate, [20, 30, 50], 1
                )
325 326 327 328 329

            with self.assertRaises(TypeError):
                lr = fluid.dygraph.MultiStepDecay("test", [20, 30, 50])

            with self.assertRaises(ValueError):
330
                lr = fluid.dygraph.MultiStepDecay(-1, [20, 30, 50])
331 332 333 334 335 336

    def test_StepDecay(self):
        with fluid.dygraph.guard():
            learning_rate = 0.5
            step_size = 3
            decay_rate = 0.2
337 338 339
            scheduler = fluid.dygraph.StepDecay(
                learning_rate, step_size, decay_rate
            )
340
            for epoch in range(10):
341 342 343
                right_result = step_decay(
                    epoch, learning_rate, step_size, decay_rate
                )
344
                fluid_result = scheduler().numpy().item()
345 346 347 348
                scheduler.epoch()
                self.assertAlmostEqual(
                    right_result,
                    fluid_result,
349
                    msg='Failed lr scheduler in epoch {}, Python result is {}, Fluid result is {}'.format(
350 351 352
                        epoch, right_result, fluid_result
                    ),
                )
353 354

            with self.assertRaises(TypeError):
355
                lr = fluid.dygraph.StepDecay(learning_rate, "test", 0.1)
356 357

            with self.assertRaises(ValueError):
358
                lr = fluid.dygraph.StepDecay(learning_rate, 20, 2)
359

360 361 362 363 364 365
    def test_LambdaDecay(self):
        with fluid.dygraph.guard():
            learning_rate = 0.5
            lr_lambda = lambda x: 0.95**x
            scheduler = fluid.dygraph.LambdaDecay(learning_rate, lr_lambda)

366
            linear = paddle.nn.Linear(10, 10)
367 368 369
            adam = fluid.optimizer.Adam(
                scheduler, parameter_list=linear.parameters()
            )
370 371 372

            for epoch in range(30):
                right_result = lambda_decay(epoch, learning_rate, lr_lambda)
373
                fluid_result = scheduler().numpy().item()
374 375 376 377
                scheduler.epoch()
                self.assertAlmostEqual(
                    right_result,
                    fluid_result,
378
                    msg='Failed lr scheduler in epoch {}, Python result is {}, Fluid result is {}'.format(
379 380 381
                        epoch, right_result, fluid_result
                    ),
                )
382 383 384 385

            with self.assertRaises(TypeError):
                lr = fluid.dygraph.LambdaDecay(learning_rate, "test")

386

387 388
class TestLearningRateDecay(unittest.TestCase):
    def check_decay(self, python_decay_fn, fluid_decay_fn, kwargs):
Q
QI JUN 已提交
389 390 391 392
        places = [fluid.CPUPlace()]
        if core.is_compiled_with_cuda():
            places.append(fluid.CUDAPlace(0))
        for place in places:
393 394 395
            self.check_decay_with_place(
                place, python_decay_fn, fluid_decay_fn, kwargs
            )
Q
QI JUN 已提交
396

397 398 399
    def check_decay_with_place(
        self, place, python_decay_fn, fluid_decay_fn, kwargs
    ):
400 401
        main_prog = fluid.Program()
        startup_prog = fluid.Program()
Q
QI JUN 已提交
402

403
        with fluid.program_guard(main_prog, startup_prog):
404
            decayed_lr = fluid_decay_fn(**kwargs)
Q
Qiao Longfei 已提交
405 406 407 408

        place = fluid.CPUPlace()
        exe = fluid.Executor(place)

409
        exe.run(startup_prog)
410

Q
Qiao Longfei 已提交
411
        for step in range(10):
412 413 414
            # Step of NoamDecay starts from 1.
            if python_decay_fn.__name__ == 'noam_decay':
                step += 1
415 416 417 418
            (lr_val,) = exe.run(main_prog, feed={}, fetch_list=[decayed_lr])
            python_decayed_lr = python_decay_fn(
                global_step=float(step), **kwargs
            )
Y
Yu Yang 已提交
419 420 421
            self.assertAlmostEqual(
                python_decayed_lr,
                lr_val[0],
422
                msg='Failed lr scheduler is {}, step {}, Python result is {}, Fluid result is {}'.format(
423 424 425 426 427 428
                    python_decay_fn.__name__,
                    str(step),
                    str(python_decayed_lr),
                    str(lr_val[0]),
                ),
            )
Q
Qiao Longfei 已提交
429 430

    def test_decay(self):
431 432 433 434
        common_kwargs_true = {
            "learning_rate": 1.0,
            "decay_steps": 5,
            "decay_rate": 0.5,
435
            "staircase": True,
436 437 438 439
        }
        common_kwargs_false = copy.deepcopy(common_kwargs_true)
        common_kwargs_false["staircase"] = False

Q
Qiao Longfei 已提交
440
        decay_fns = [
441 442 443 444 445
            (exponential_decay, layers.exponential_decay, common_kwargs_true),
            (exponential_decay, layers.exponential_decay, common_kwargs_false),
            (natural_exp_decay, layers.natural_exp_decay, common_kwargs_true),
            (natural_exp_decay, layers.natural_exp_decay, common_kwargs_false),
            (inverse_time_decay, layers.inverse_time_decay, common_kwargs_true),
446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475
            (
                inverse_time_decay,
                layers.inverse_time_decay,
                common_kwargs_false,
            ),
            (
                polynomial_decay,
                layers.polynomial_decay,
                {"learning_rate": 1.0, "decay_steps": 5, "cycle": True},
            ),
            (
                polynomial_decay,
                layers.polynomial_decay,
                {"learning_rate": 1.0, "decay_steps": 5, "cycle": False},
            ),
            (
                piecewise_decay,
                layers.piecewise_decay,
                {"boundaries": [3, 6, 9], "values": [0.1, 0.2, 0.3, 0.4]},
            ),
            (
                cosine_decay,
                layers.cosine_decay,
                {"learning_rate": 0.1, "step_each_epoch": 100, "epochs": 120},
            ),
            (
                noam_decay,
                layers.noam_decay,
                {"d_model": 0.01, "warmup_steps": 200, "learning_rate": 2.0},
            ),
Q
Qiao Longfei 已提交
476 477
        ]

478
        for py_decay_fn, fluid_decay_fn, kwargs in decay_fns:
479 480 481 482 483 484 485 486
            print(
                "class="
                + self.__class__.__name__
                + " decay_fn="
                + py_decay_fn.__name__
                + " kwargs="
                + str(kwargs)
            )
Q
Qiao Longfei 已提交
487 488 489
            main_program = framework.Program()
            startup_program = framework.Program()
            with framework.program_guard(main_program, startup_program):
490
                self.check_decay(py_decay_fn, fluid_decay_fn, kwargs)
Q
Qiao Longfei 已提交
491 492


493
class TestLinearWamrupLearningRateDecay(unittest.TestCase):
494 495 496
    def check_decay_with_place(
        self, place, python_decay_fn, fluid_decay_fn, kwargs
    ):
497 498 499 500
        main_prog = fluid.Program()
        startup_prog = fluid.Program()

        warmup_steps = 10
501
        start_lr = 0.1 / 3.0
502 503 504
        end_lr = 0.1

        with fluid.program_guard(main_prog, startup_prog):
505 506 507
            decayed_lr = layers.linear_lr_warmup(
                fluid_decay_fn(**kwargs), warmup_steps, start_lr, end_lr
            )
508 509 510 511 512 513

        place = fluid.CPUPlace()
        exe = fluid.Executor(place)
        exe.run(startup_prog)

        for step in range(20):
514 515 516
            # Step of NoamDecay starts from 1.
            if fluid_decay_fn.__name__ == 'noam_decay':
                step += 1
517
            (lr_val,) = exe.run(main_prog, feed={}, fetch_list=[decayed_lr])
518
            if step < warmup_steps:
519 520 521
                python_decayed_lr = linear_lr_warmup(
                    float(step), warmup_steps, start_lr, end_lr
                )
522
            else:
523 524 525
                python_decayed_lr = python_decay_fn(
                    global_step=float(step), **kwargs
                )
526 527 528
            self.assertAlmostEqual(
                python_decayed_lr,
                lr_val[0],
529
                msg='Test {} Failed, step {}, Python result is {}, Fluid result is {}'.format(
530 531 532 533 534 535
                    python_decay_fn.__name__,
                    str(step),
                    str(python_decayed_lr),
                    str(lr_val[0]),
                ),
            )
536 537


Q
qingqing01 已提交
538 539 540 541 542 543 544 545
class TestLinearWamrupLearningRateDecayWithScalarInput(unittest.TestCase):
    def run_scalar_lr(self, place, lr, start_lr, end_lr):
        main_prog = fluid.Program()
        startup_prog = fluid.Program()

        warmup_steps = 10

        with fluid.program_guard(main_prog, startup_prog):
546 547 548
            decayed_lr = layers.linear_lr_warmup(
                lr, warmup_steps, start_lr, end_lr
            )
Q
qingqing01 已提交
549 550 551 552 553

        exe = fluid.Executor(place)
        exe.run(startup_prog)

        for step in range(20):
554
            (lr_val,) = exe.run(main_prog, feed={}, fetch_list=[decayed_lr])
Q
qingqing01 已提交
555
            if step < warmup_steps:
556 557 558
                expected_lr = linear_lr_warmup(
                    float(step), warmup_steps, start_lr, end_lr
                )
Q
qingqing01 已提交
559 560 561 562 563
            else:
                expected_lr = lr
            self.assertAlmostEqual(
                expected_lr,
                lr_val[0],
564
                msg='Test failed, step {}, expected {}, but got {}'.format(
565 566 567
                    step, expected_lr, lr_val[0]
                ),
            )
Q
qingqing01 已提交
568 569 570 571 572 573 574 575 576 577 578

    def test_scalar_lr(self):
        def run_places(lr, start_lr, end_lr):
            places = [fluid.CPUPlace()]
            if core.is_compiled_with_cuda():
                places.append(fluid.CUDAPlace(0))
            for p in places:
                self.run_scalar_lr(p, lr, start_lr, end_lr)

        # float
        lr = 0.2
579
        start_lr = 0.1 / 3.0
Q
qingqing01 已提交
580 581 582 583
        end_lr = 0.2
        run_places(lr, start_lr, end_lr)

        # int end_lr
584 585
        lr = 2.0
        start_lr = 0.1 / 3.0
Q
qingqing01 已提交
586 587 588 589 590 591 592 593 594 595
        end_lr = 1
        run_places(lr, start_lr, end_lr)

        # int
        lr = 1
        start_lr = 0
        end_lr = 1
        run_places(lr, start_lr, end_lr)


Q
Qiao Longfei 已提交
596 597
if __name__ == '__main__':
    unittest.main()