批标准化 tf.keras.layers.BatchNormalization 中的trainable参数与training参数比较

巨坑提醒:tf.keras与tensorflow混用,trainable=False根本不起作用。正文不用看了。

摘要:

在tensorflow中,training参数和trainable参数是两个不同的参数,具有不同的约束功能。

  • training: True/False,告诉网络现在是在训练阶段还是在inference(测试) 阶段。
  • trainable:True/False, 设置这一层网络的参数变量是可在训练过程中被更新,是可训练还是不可训练。

代码1:tf.keras.layers.BatchNormalization的使用示例,及training参数和trainable的影响分析

import tensorflow.compat.v1 as tf

input = tf.ones([1, 2, 2, 3])
output = tf.keras.layers.BatchNormalization(trainable=True)(input,training=True)

# 手动添加滑动平均节点
ops = tf.get_default_graph().get_operations()
bn_update_ops = [x for x in ops if ("AssignMovingAvg" in x.name and x.type == "AssignSubVariableOp")]
tf.add_to_collection(tf.GraphKeys.UPDATE_OPS, bn_update_ops)


print(tf.get_collection(tf.GraphKeys.UPDATE_OPS))
print(tf.global_variables())
print(tf.trainable_variables())

"""
当 trainable=False,training=False 时,返回:
[[]]
[<tf.Variable 'batch_normalization/gamma:0' shape=(3,) dtype=float32>, <tf.Variable 'batch_normalization/beta:0' shape=(3,) dtype=float32>, <tf.Variable 'batch_normalization/moving_mean:0' shape=(3,) dtype=float32>, <tf.Variable 'batch_normalization/moving_variance:0' shape=(3,) dtype=float32>]
[<tf.Variable 'batch_normalization/gamma:0' shape=(3,) dtype=float32>, <tf.Variable 'batch_normalization/beta:0' shape=(3,) dtype=float32>]

当 trainable=False,training=True  时,返回:
[[<tf.Operation 'batch_normalization/AssignMovingAvg/AssignSubVariableOp' type=AssignSubVariableOp>, <tf.Operation 'batch_normalization/AssignMovingAvg_1/AssignSubVariableOp' type=AssignSubVariableOp>]]
[<tf.Variable 'batch_normalization/gamma:0' shape=(3,) dtype=float32>, <tf.Variable 'batch_normalization/beta:0' shape=(3,) dtype=float32>, <tf.Variable 'batch_normalization/moving_mean:0' shape=(3,) dtype=float32>, <tf.Variable 'batch_normalization/moving_variance:0' shape=(3,) dtype=float32>]
[<tf.Variable 'batch_normalization/gamma:0' shape=(3,) dtype=float32>, <tf.Variable 'batch_normalization/beta:0' shape=(3,) dtype=float32>]

当 trainable=True,training=False  时,返回:
[[]]
[<tf.Variable 'batch_normalization/gamma:0' shape=(3,) dtype=float32>, <tf.Variable 'batch_normalization/beta:0' shape=(3,) dtype=float32>, <tf.Variable 'batch_normalization/moving_mean:0' shape=(3,) dtype=float32>, <tf.Variable 'batch_normalization/moving_variance:0' shape=(3,) dtype=float32>]
[<tf.Variable 'batch_normalization/gamma:0' shape=(3,) dtype=float32>, <tf.Variable 'batch_normalization/beta:0' shape=(3,) dtype=float32>]

当 trainable=True,training=True  时,返回:
[[<tf.Operation 'batch_normalization/AssignMovingAvg/AssignSubVariableOp' type=AssignSubVariableOp>, <tf.Operation 'batch_normalization/AssignMovingAvg_1/AssignSubVariableOp' type=AssignSubVariableOp>]]
[<tf.Variable 'batch_normalization/gamma:0' shape=(3,) dtype=float32>, <tf.Variable 'batch_normalization/beta:0' shape=(3,) dtype=float32>, <tf.Variable 'batch_normalization/moving_mean:0' shape=(3,) dtype=float32>, <tf.Variable 'batch_normalization/moving_variance:0' shape=(3,) dtype=float32>]
[<tf.Variable 'batch_normalization/gamma:0' shape=(3,) dtype=float32>, <tf.Variable 'batch_normalization/beta:0' shape=(3,) dtype=float32>]


结论:可见影响滑动平均节点是否存在的参数主要是:training参数。当training=True时,tensorflow就会制造两个滑动平均节点出来,否则就不制造。
     而,不论滑动平均节点是否存在,tensorflow都会产生四个变量:gamma变量,beta变量和滑动平均变量,滑动方差变量。
     四个变量中,gamma变量,beta变量是可训练变量;滑动平均变量,滑动方差变量是不可训练变量--按照指定的滑动平均规则更新,与网络的损失函数没有任何关系。
     值得注意的是:(大坑提醒)无论trainable=True还是False,gamma变量,beta变量都被收集在可训练的变量集合里。这一点与tensorflow.keras的其他网络层的操作是一样的。大坑,大坑,大坑。。。
    
"""

代码2: tf.keras.layers.BatchNormalization手动添加滑动平均节点的重要性

与代码1相比,代码2的特点是,取消了手动添加滑动平均节点的环节。

import tensorflow.compat.v1 as tf

input = tf.ones([1, 2, 2, 3])
output = tf.keras.layers.BatchNormalization(trainable=True)(input,training=True)

## 手动添加滑动平均节点
# ops = tf.get_default_graph().get_operations()
# bn_update_ops = [x for x in ops if ("AssignMovingAvg" in x.name and x.type == "AssignSubVariableOp")]
# tf.add_to_collection(tf.GraphKeys.UPDATE_OPS, bn_update_ops)


print(tf.get_collection(tf.GraphKeys.UPDATE_OPS))
print(tf.global_variables())
print(tf.trainable_variables())

"""
当 trainable=True,training=True  时,返回:
[]
[<tf.Variable 'batch_normalization/gamma:0' shape=(5,) dtype=float32>, <tf.Variable 'batch_normalization/beta:0' shape=(5,) dtype=float32>, <tf.Variable 'batch_normalization/moving_mean:0' shape=(5,) dtype=float32>, <tf.Variable 'batch_normalization/moving_variance:0' shape=(5,) dtype=float32>]
[<tf.Variable 'batch_normalization/gamma:0' shape=(5,) dtype=float32>, <tf.Variable 'batch_normalization/beta:0' shape=(5,) dtype=float32>]
"""

可见,即使设置了training=True,tensorflow制造了两个滑动平均节点,但是这两个节点并没有被加入到计算图上。因此,要想是批标准化实现滑动平均的计算,需要手动把这两个节点添加到计算图上。

建议:搭建完网络模型后,立马执行手动添加滑动平均节点,无论是training=True,还是training=False. 不用担心training=False时,网络会继续执行滑动平均操作,因为此时tensorflow压根不会创建这两个计算节点。

说明3: trainable在tensorflow2.0与1.*上的区别。以下说明复制自源码,我加了翻译和个人的理解,我的tensorflow版本是1.14.0.

先提炼一下这个声明想表达的意思:

  • 在tensorflow 2.0版本中,设置BatchNormalization层的trainable=False, 会导致这一批标准化层使用滑动均值和滑动方差来执行标准化。这一操作,主要是为了方便 fine-tune。具体情况如下:
    • trainable=True, training=True: 训练阶段,使用minibatch的均值方差执行标准化
    • trainable=False, training=True: 训练阶段,使用滑动均值滑动方差执行标准化。操作目的:fine-tune时,冻结已经训练好的批标准化层。
    • trainable=True, training=False: 测试阶段,使用训练期间的滑动均值滑动方差执行标准化
    • trainable=False, training=False: 测试阶段,使用训练期间的滑动均值滑动方差执行标准化
  • 在tensorflow 1.*版本中,设置BatchNormalization层的trainable=False,批标准化层将仍旧根据training参数来判断是使用minibatch的均值和方差还是使用滑动均值和方差来执行标准化。或者细化一点:
    • trainable=True, training=True: 训练阶段,使用minibatch的均值方差执行标准化
    • trainable=False, training=True: 训练阶段,使用minibatch的均值方差执行标准化
    • trainable=True, training=False: 测试阶段,使用训练期间的滑动均值滑动方差执行标准化
    • trainable=False, training=False: 测试阶段,使用训练期间的滑动均值滑动方差执行标准化

  **About setting `layer.trainable = False` on a `BatchNormalization layer:**
翻译:关于 BatchNormalization 层中 layer.trainable = False 的设置:
 
  The meaning of setting `layer.trainable = False` is to freeze the layer,  i.e. its internal state will not change during training: its trainable weights will not be updated  during `fit()` or `train_on_batch()`, and its state updates will not be run.
翻译:对于一个一般的层,设置layer.trainable = False表示冻结这一层的参数,使这一层的内部状态不随着训练过程改变,即这一层的可训练参数不被更新,也即,在`fit()` or `train_on_batch()`过程中,这一层的状态不会被更新。
 
  Usually, this does not necessarily mean that the layer is run in inference  mode (which is normally controlled by the `training` argument that can be passed when calling a layer). "Frozen state" and "inference mode" are two separate concepts.
翻译:通常,设置layer.trainable = False并不一定意味着这一层处于inference状态(测试状态),(模型是否处于inference状态,通常调用该层的call函数时用一个叫training的参数控制。)所以,“冻结状态”和“推断模式”是两种不同的概念。
 
  However, in the case of the `BatchNormalization` layer, **setting  `trainable = False` on the layer means that the layer will be subsequently run in inference mode** (meaning that it will use the moving mean and the moving variance to normalize the current batch,  rather than using the mean and variance of the current batch).
翻译:但是,在BatchNormalization中,设置trainable = False 意味着这一层会以“推断模式”运行。这就意味着,如果在训练过程中设置批标准化层的trainable = False,就意味着批标准化过程中会使用滑动均值与滑动方差来执行当前批次数据的批标准化,而不是使用当前批次的均值与方差。
----》个人理解:对于批标准化,我们希望的是,在训练过程中使用每个minibatch自己的均值与方差执行标准化,同时保持一个滑动均值与滑动方差在测试过程中使用。如果在训练过程中,设置trainable = False的话,会导致,在训练过程中,批标准化层就会使用滑动均值与方差进行批标准化。
 
  This behavior has been introduced in TensorFlow 2.0, in order to enable `layer.trainable = False` to produce the most commonly expected behavior in the convnet fine-tuning use case.
翻译:这一操作已经被引入到TensorFlow 2.0中,目的是使`layer.trainable = False`产生最期待的行为:以便在网络fine-tune中使用。
---》个人理解:在网络fine-tune中,我们希望冻结一些层的参数,仅仅训练个别层的参数。对于批标准化层来说,我们希望这一层在训练过程中仍旧使用已经训练好的滑动均值和滑动方差,而不是当前批次的均值和方差。
 
  Note that:
    - This behavior only occurs as of TensorFlow 2.0. In 1.*,  setting `layer.trainable = False` would freeze the layer but would not switch it to inference mode.

翻译: 注意:这一行为仅仅发生在TensorFlow 2.0上。在1.*版本上,设置标准化层的`layer.trainable = False`,仍旧只会冻结标准化层的gamma和beta,仍旧使用当前批次的均值和方差标准化。
--》个人理解:在1.*版本上,设置标准化层的`layer.trainable = False`,得到的操作是:
    1)标准化层的gamma和beta不被训练
    2)执行标准化时,使用的是当前批次的均值和方差,而不是滑动均值和滑动方差。
    3)滑动均值和滑动方差仍旧会被计算吗?这有待确定。
    - Setting `trainable` on an model containing other layers will recursively set the `trainable` value of all inner layers.
翻译: 当给一整个model设置trainable参数时,相当于给其内部的每个层都设置了这一相同的参数。
    - If the value of the `trainable` attribute is changed after calling `compile()` on a model, the new value doesn't take effect for this model until `compile()` is called again.
翻译:如果,model在调用“compile()”时改变了trainable参数,新的trainable参数值并不影响这个model,直到再次调用“compile()”函数。
 

 个人的应用结论:如果在执行批标准化的时候不想使用gamma和beta变量进行平移和缩放,最好还是使用tensorflow 1.*版本,并设置trainable=False就可以了。

 

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
当然可以。 1. `base_model = tf.keras.applications.MobileNet(weights="imagenet", include_top=False, input_shape=input_shape)`: 加载MobileNet模型,其`weights="imagenet"`表示使用ImageNet预训练权重,`include_top=False`表示不包括顶层全连接层,`input_shape`表示输入图像的大小。 2. `base_model.trainable = False`: 将MobileNet模型的参数冻结,不参与训练。 3. `inputs = keras.Input(shape=input_shape)`: 定义输入张量。 4. `x = base_model(inputs, training=False)`: 将输入张量通过MobileNet模型得到特征张量。 5. `x = tf.keras.layers.GlobalAveragePooling2D()(x)`: 对特征张量进行全局平均池化操作。 6. `x = tf.keras.layers.Dropout(0.2)(x)`: 对全局平均池化后的特征张量进行Dropout操作。 7. `x = tf.keras.layers.Dense(len(categories), activation="softmax")(x)`: 添加一个全连接层,输出类别概率。 8. `model = keras.Model(inputs=inputs, outputs=x, name="LeafDisease_MobileNet")`: 将输入张量和输出张量封装成一个模型。 9. `weight_path = os.path.join(base_dir, 'checkpoints', 'my_checkpoint')`: 定义权重文件路径。 10. `model.load_weights(weight_path)`: 加载预训练好的权重。 11. `img = plt.imread(img_path)`: 读取待分类的图像。 12. `img = img / 255.`: 将图像像素值从[0,255]归一化到[0,1]。 13. `img = cv2.resize(img, (224, 224))`: 将图像缩放到MobileNet模型能够接受的大小。 14. `img = img.reshape(-1, 224, 224, 3)`: 将图像变形为模型需要的4维张量。 15. `img.astype('float32')`: 将图像数据类型转换为float32类型。 16. `result = model.predict(img)`: 对图像进行预测,得到类别概率。 17. `cate_result = categories[np.argmax(result, axis=1)[0]]`: 取最大概率对应的类别,返回类别名称。其`np.argmax(result, axis=1)`表示取每个样本预测概率最大的下标,`[0]`表示取第一个样本。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值