当前位置:   article > 正文

【深度学习】(10) 自定义学习率衰减策略(指数、分段、余弦),附TensorFlow完整代码_在tensorflow2.5.0中,怎么写一个随迭代次数变化的学习率衰减策略

在tensorflow2.5.0中,怎么写一个随迭代次数变化的学习率衰减策略

大家好,今天和大家分享一下如何使用 TensorFlow 自定义 指数学习率下降阶梯学习率下降余弦学习率下降 方法,并使用 Mnist数据集验证自定义的学习率下降策略

创建的自定义学习率类方法,需要继承 tf.keras.optimizers.schedules.LearningRateSchedule


1. 指数学习率下降

指数学习率下降公式为: \alpha _{t} = \alpha _{0} \cdot \beta ^{n}

其中 \alpha _{0} 代表初始的学习率,\beta 代表学习率衰减系数,n 代表epoch,即每次迭代学习率衰减一次

以初始学习率 \alpha _{0}=0.001 ,学习率衰减系数 \beta =0.9 ,n = epoch 为例指数学习率下降曲线如下图所示。

如果以step作为指数调整的标准,那么指数 n 等于当前的 step 除以 一个 epoch 包含的总 step。

我这里以 epoch 作为指数调整的标准。


首先创建一个学习率自定义类,继承 keras.optimizers.schedules.LearningRateSchedule 自定义学习率调度器

先对所有的属性完成初始化,其中 self.current 返回训练时每一个 step 的学习率self.epoch 代表指数学习率计算公式中的指数项 n,self.learning_rate_list 用于记录训练时每个 epoch 的学习率

在__call__方法中,step % self.print_step,其中 step 代表训练时传入的当前的 step,而 print_step外部指定的每经过多少个step调整一次学习率,并记录下当前 epoch 的学习率,返回调整后的学习率如果不满足 if 条件,那么这些 step 的学习率就是上一次调整后的学习率

以 epoch 作为指数调整标准的代码如下:

  1. # ------------------------------------------------------------------ #
  2. # 当前学习率 = 初始学习率 * 衰减系数 ^{迭代了多少次}
  3. # ------------------------------------------------------------------ #
  4. # eager模式防止graph报错
  5. tf.config.experimental_run_functions_eagerly(True)
  6. # ------------------------------------------------------------------ #
  7. # 继承学习率的类
  8. class ExponentialDecay(keras.optimizers.schedules.LearningRateSchedule):
  9. '''
  10. initial_lr: 初始的学习率
  11. decay_rate: 学习率衰减系数
  12. min_lr: 学习率下降的最小
  13. print_step: 训练时多少个batch打印一次学习率
  14. '''
  15. # 初始化
  16. def __init__(self, initial_lr, decay_rate, min_lr, print_step):
  17. # 继承父类的初始化方法
  18. super(ExponentialDecay, self).__init__()
  19. # 属性分配
  20. self.initial_lr = tf.cast(initial_lr, tf.float32)
  21. self.decay_rate = tf.cast(decay_rate, tf.float32)
  22. self.min_lr = tf.cast(min_lr, tf.float32)
  23. self.print_step = print_step
  24. # 记录记录每个epoch的学习率
  25. self.learning_rate_list = []
  26. # 最开始时,学习率为初始学习率
  27. self.current = self.initial_lr
  28. # 初始的迭代次数为0
  29. self.epoch = 0
  30. # 前向传播
  31. def __call__(self, step):
  32. # 每多少个batch调整一次学习率, 一个batch处理32张图
  33. if step % self.print_step == 0:
  34. # 学习率指数下降,设置为每个epoch调整一次
  35. learning_rate = self.initial_lr * pow(self.decay_rate, self.epoch)
  36. # 调整当前学习率, 每一轮的学习率不能低于最小学习率
  37. self.current = tf.where(learning_rate>self.min_lr, learning_rate, self.min_lr)
  38. # 迭代次数加一
  39. self.epoch = self.epoch + 1
  40. # 将当前学习率保存下来
  41. self.learning_rate_list.append(learning_rate.numpy().item())
  42. # 打印学习率变化
  43. print('learning_rate:', learning_rate.numpy().item())
  44. # 返回调整后的学习率
  45. return self.current
  46. # 否则就返回上一次调整的学习率
  47. else:
  48. return self.current

2. 阶梯学习率下降

思路:每经过多少个 step 之后,学习率下降为上一次的 decay_rate 倍。例如初始学习率为0.001每经过三个 epoch,学习率就下降为原来的 0.5 倍,实现分段下降,示意图如下:


首先创建一个学习率自定义类,继承 keras.optimizers.schedules.LearningRateSchedule 自定义学习率调度器

先对所有的属性完成初始化,其中 self.change_step 在外部定义,代表每经过多少个 step 调整一次学习率。调整方式是当前学习率 self.current 乘以调整倍数 self.decay_rate,得到调整后的学习率并返回结果。如果不满足 if 条件,即当前 step 不需要调整,就返回上一次调整后的学习率。self.learning_rate_list 列表中记录训练过程中的每个 epoch 的学习率,训练完成后之后可以读取查看。

  1. # ------------------------------------------------------------------ #
  2. # 自定义的分段常数下降方法
  3. # ------------------------------------------------------------------ #
  4. # eager模式防止graph报错
  5. tf.config.experimental_run_functions_eagerly(True)
  6. # ------------------------------------------------------------------ #
  7. # 继承学习率的类
  8. class PiecewiseConstantDecay(keras.optimizers.schedules.LearningRateSchedule):
  9. '''
  10. initial_lr: 初始的学习率
  11. decay_rate: 学习率衰减系数
  12. min_lr: 学习率下降的最小
  13. change_step: 多少个epoch下降一次
  14. print_step: 训练时多少个step打印一次学习率
  15. '''
  16. # 初始化
  17. def __init__(self, initial_lr, decay_rate, min_lr, change_step, print_step):
  18. # 继承父类的初始化方法
  19. super(PiecewiseConstantDecay, self).__init__()
  20. # 属性分配
  21. self.initial_lr = tf.cast(initial_lr, tf.float32)
  22. self.decay_rate = tf.cast(decay_rate, tf.float32)
  23. self.min_lr = tf.cast(min_lr, tf.float32)
  24. self.change_step = change_step
  25. self.print_step = print_step
  26. # 记录记录每个epoch的学习率
  27. self.learning_rate_list = []
  28. # 最开始时,学习率为初始学习率
  29. self.current = self.initial_lr
  30. # 前向传播
  31. def __call__(self, step): # 这个step不是epoch
  32. # 多少个step记录一次学习率,外部指定为一个epoch记录一次
  33. if step % self.print_step == 0:
  34. # 训练过程中打印每一个epoch的学习率
  35. print('current learning_rate is ', self.current.numpy().item())
  36. # 记录下当前epoch的学习率
  37. self.learning_rate_list.append(self.current.numpy().item())
  38. # 多少个step调整一次学习率
  39. if step % self.change_step == 0:
  40. # 计算调整后的学习率
  41. learning_rate = self.current * self.decay_rate
  42. # 更新当前学习率指标, 学习率不能小于指定的最小值
  43. self.current = tf.where(learning_rate>self.min_lr, learning_rate, self.min_lr)
  44. # 返回调整后的学习率
  45. return self.current
  46. # 如果为满足调整要求,就返回上一次调整的学习率
  47. else:
  48. return self.current

3. 余弦学习率下降

余弦学习率下降公式为:\alpha _{t} = 0.5 * \alpha _{0} \cdot (1+cos(\frac{t\cdot \pi }{T}))

其中,\alpha _{0} 代表初始学习率,t 是指当前是第几个 step,T 是指多少个 step 之后学习率衰减为0

以初始学习率为 0.001,所有 epoch 结束后学习率降为 0 为例,学习率余弦下降曲线如下:


首先创建一个学习率自定义类,继承 keras.optimizers.schedules.LearningRateSchedule 自定义学习率调度器

先对所有的属性完成初始化,其中 self.current 用来记录当前 step 的学习率self.learning_rate_list 用来记录训练时所有 step 的学习率,训练结束后可调用查看。

训练时模型会传入当前的 step,调整每一个 step 的学习率 learning_rate,并且要求调整后的学习率不能低于最小学习率 self.min_lr,使用 tf.where() 函数对比调整后的学习率和最小学习率,选出最大的作为返回结果的学习率。

  1. # ------------------------------------------------------------------ #
  2. # 余弦学习率下降
  3. # ------------------------------------------------------------------ #
  4. # eager模式防止graph报错
  5. tf.config.experimental_run_functions_eagerly(True)
  6. # ------------------------------------------------------------------ #
  7. # 继承学习率的类
  8. class CosineDecay(keras.optimizers.schedules.LearningRateSchedule):
  9. '''
  10. initial_lr: 初始的学习率
  11. decay_rate: 学习率衰减到最低点的步长
  12. min_lr: 学习率下降的最小
  13. print_step: 训练时多少个step打印一次学习率
  14. '''
  15. # 初始化
  16. def __init__(self, initial_lr, decay_step, min_lr, print_step):
  17. # 继承父类初始化方法
  18. super(CosineDecay, self).__init__()
  19. # 属性分配
  20. self.initial_lr = tf.cast(initial_lr, dtype=tf.float32)
  21. self.decay_step = tf.cast(decay_step, dtype=tf.float32)
  22. self.min_lr = tf.cast(min_lr, dtype=tf.float32)
  23. self.print_step = print_step
  24. # 最开始的当前学习率等于初始学习率
  25. self.current = self.initial_lr
  26. # 记录每个epoch的学习率值
  27. self.learning_rate_list = []
  28. # 前向传播
  29. def __call__(self, step):
  30. # 余弦衰减计算公式
  31. learning_rate = 0.5 * self.initial_lr * (1 + tf.math.cos(step*math.pi / self.decay_step))
  32. # 更新当前学习率指标, 学习率不能小于指定的最小值
  33. self.current = tf.where(learning_rate>self.min_lr, learning_rate, self.min_lr)
  34. # 记录每个step的学习率
  35. self.learning_rate_list.append(self.current.numpy().item())
  36. # 多少个step打印一次学习率,外部设置每个epoch打印一次学习率
  37. if step % self.print_step == 0:
  38. # 在训练时打印当前学习率
  39. print('learning_rate has changed to: ', self.current.numpy().item())
  40. return self.current

4. 实验验证

这里以学习率余弦衰减策略为例,来验证上面定义的学习率方法能不能用。

数据预处理和模型构建部分就不讲了,这部分都很基础。直接看到第(6)部分模型训练

首先需要对我们定义的学习率下降的类 CosineDecay 进行实例化,传入计算公式中所需的初始学习率 initial_lr余弦值下降到0所需的步长 decay_step,用变量 cosinedecay 来接收。

将自定义的学习率衰减方法传入至Adam优化器,这样在训练时就能接收到模型传入的每个step,用于计算衰减。

自定义方法也可以参照官方文档:自定义的学习速率调度

以Mnist手写数据集图像10分类问题为例,完整代码如下:

  1. import tensorflow as tf
  2. from tensorflow import keras
  3. from tensorflow.keras import layers
  4. import matplotlib.pyplot as plt
  5. import math
  6. # 调用GPU加速
  7. gpus = tf.config.experimental.list_physical_devices(device_type='GPU')
  8. for gpu in gpus:
  9. tf.config.experimental.set_memory_growth(gpu, True)
  10. # ------------------------------------------------------------------ #
  11. # (1)读取手写数字数据集
  12. # ------------------------------------------------------------------ #
  13. (x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
  14. print('x_train.shape:', x_train.shape, 'y_train.shape:', y_train.shape) # (60000, 28, 28) , (60000,)
  15. print('x_test.shape:', x_test.shape) # (10000, 28, 28)
  16. # 记录一共训练多少张图
  17. total_train_num = x_train.shape[0]
  18. # ------------------------------------------------------------------ #
  19. # (2)余弦学习率下降
  20. # ------------------------------------------------------------------ #
  21. # eager模式防止graph报错
  22. tf.config.experimental_run_functions_eagerly(True)
  23. # ------------------------------------------------------------------ #
  24. # 继承学习率的类
  25. class CosineDecay(keras.optimizers.schedules.LearningRateSchedule):
  26. '''
  27. initial_lr: 初始的学习率
  28. decay_rate: 学习率衰减到最低点的步长
  29. min_lr: 学习率下降的最小
  30. print_step: 训练时多少个step打印一次学习率
  31. '''
  32. # 初始化
  33. def __init__(self, initial_lr, decay_step, min_lr, print_step):
  34. # 继承父类初始化方法
  35. super(CosineDecay, self).__init__()
  36. # 属性分配
  37. self.initial_lr = tf.cast(initial_lr, dtype=tf.float32)
  38. self.decay_step = tf.cast(decay_step, dtype=tf.float32)
  39. self.min_lr = tf.cast(min_lr, dtype=tf.float32)
  40. self.print_step = print_step
  41. # 最开始的当前学习率等于初始学习率
  42. self.current = self.initial_lr
  43. # 记录每个epoch的学习率值
  44. self.learning_rate_list = []
  45. # 前向传播
  46. def __call__(self, step):
  47. # 余弦衰减计算公式
  48. learning_rate = 0.5 * self.initial_lr * (1 + tf.math.cos(step*math.pi / self.decay_step))
  49. # 更新当前学习率指标, 学习率不能小于指定的最小值
  50. self.current = tf.where(learning_rate>self.min_lr, learning_rate, self.min_lr)
  51. # 记录每个step的学习率
  52. self.learning_rate_list.append(self.current.numpy().item())
  53. # 多少个step打印一次学习率,外部设置每个epoch打印一次学习率
  54. if step % self.print_step == 0:
  55. # 在训练时打印当前学习率
  56. print('learning_rate has changed to: ', self.current.numpy().item())
  57. return self.current
  58. # ------------------------------------------------------------------ #
  59. # (3)参数设置
  60. # ------------------------------------------------------------------ #
  61. # 每个step处理32张图
  62. batch_size = 32
  63. # 迭代次数
  64. num_epochs = 10
  65. # 初始学习率
  66. initial_lr = 0.001
  67. # 学习率衰减系数
  68. decay_rate = 0.9
  69. # 学习率下降的最小值
  70. min_lr = 0
  71. # 每个epoch打印一次学习率, 1个batch处理32张图
  72. # 共60000张图,需要60000/32个batch,即1875个step
  73. print_step = total_train_num / batch_size
  74. # 余弦下降到0所需的步长
  75. decay_step = total_train_num / batch_size * num_epochs
  76. # ------------------------------------------------------------------ #
  77. # (4)构造数据集
  78. # ------------------------------------------------------------------ #
  79. def processing(x,y): # 预处理函数
  80. x = 2 * tf.cast(x, dtype=tf.float32)/255.0 - 1 # 归一化
  81. x = tf.expand_dims(x, axis=-1) # 增加通道维度
  82. y = tf.cast(y, dtype=tf.int32)
  83. return x,y
  84. # 构造训练集
  85. train_ds = tf.data.Dataset.from_tensor_slices((x_train, y_train))
  86. train_ds = train_ds.map(processing).batch(batch_size).shuffle(10000)
  87. # 构造测试集
  88. test_ds = tf.data.Dataset.from_tensor_slices((x_test, y_test))
  89. test_ds = test_ds.map(processing).batch(batch_size)
  90. # 迭代器查看数据是否正确
  91. sample = next(iter(train_ds))
  92. print('x_batch:', sample[0].shape, 'y_batch:', sample[1].shape) # (32, 28, 28, 1), (32,)
  93. # ------------------------------------------------------------------ #
  94. # (5)构造模型
  95. # ------------------------------------------------------------------ #
  96. inputs = keras.Input(sample[0].shape[1:]) # 构造输入层
  97. # [28,28,1]==>[28,28,32]
  98. x = layers.Conv2D(32, kernel_size=3, padding='same', activation='relu')(inputs)
  99. # [28,28,32]==>[14,14,32]
  100. x = layers.MaxPool2D(pool_size=(2,2), strides=2, padding='same')(x)
  101. # [14,14,32]==>[14,14,64]
  102. x = layers.Conv2D(64, kernel_size=3, padding='same', activation='relu')(x)
  103. # [14,14,64]==>[7,7,64]
  104. x = layers.MaxPool2D(pool_size=(2,2), strides=2, padding='same')(x)
  105. # [7,7,64]==>[None,7*7*64]
  106. x = layers.Flatten()(x)
  107. # [None,7*7*64]==>[None,128]
  108. x = layers.Dense(128)(x)
  109. # [None,128]==>[None,10]
  110. outputs = layers.Dense(10, activation='softmax')(x)
  111. # 构建模型
  112. model = keras.Model(inputs, outputs)
  113. # 查看模型结构
  114. model.summary()
  115. # ------------------------------------------------------------------ #
  116. # (6)模型训练
  117. # ------------------------------------------------------------------ #
  118. # 接收学习率调整方法
  119. cosinedecay = CosineDecay(initial_lr=initial_lr, # 初始学习率
  120. decay_step=decay_step, # 学习率衰减系数
  121. min_lr=min_lr, # 最小学习率值
  122. print_step=print_step) # 每个epoch打印一次学习率值
  123. # 设置adam优化器,指定学习率
  124. opt = keras.optimizers.Adam(cosinedecay)
  125. # 网络编译
  126. model.compile(optimizer=opt, # 学习率
  127. loss='sparse_categorical_crossentropy', # 损失
  128. metrics=['accuracy']) # 监控指标
  129. # 网络训练
  130. model.fit(train_ds, epochs=num_epochs, validation_data=test_ds)
  131. # 绘制学习率变化曲线
  132. plt.plot(range(decay_step), cosinedecay.learning_rate_list)
  133. plt.xlabel("Train step")
  134. plt.ylabel("Learning_Rate")
  135. plt.title('cosinedecay')
  136. plt.grid()
  137. plt.show()

打印训练过程,可以看到每个epoch都打印了当前的学习率

  1. Epoch 1/10
  2. learning_rate has changed to: 0.0010000000474974513
  3. 313/313 [==============================] - 6s 19ms/step - loss: 0.6491 - accuracy: 0.7977 - val_loss: 0.0725 - val_accuracy: 0.9783
  4. Epoch 2/10
  5. learning_rate has changed to: 0.0009755282662808895
  6. 313/313 [==============================] - 6s 18ms/step - loss: 0.0673 - accuracy: 0.9793 - val_loss: 0.0278 - val_accuracy: 0.9911
  7. --------------------------------------------------------------------------------
  8. --------------------------------------------------------------------------------
  9. Epoch 9/10
  10. learning_rate has changed to: 9.54914721660316e-05
  11. 313/313 [==============================] - 6s 18ms/step - loss: 8.1648e-04 - accuracy: 1.0000 - val_loss: 7.3570e-04 - val_accuracy: 1.0000
  12. Epoch 10/10
  13. learning_rate has changed to: 2.4471701181028038e-05
  14. 313/313 [==============================] - 6s 19ms/step - loss: 8.0403e-04 - accuracy: 1.0000 - val_loss: 7.2831e-04 - val_accuracy: 1.0000

绘制学习率曲线,每个epoch的学习率保存在了 self.learning_rate_list 列表中,通过 cosinedecay.learning_rate_list 调用该列表

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/349739
推荐阅读
相关标签
  

闽ICP备14008679号