批标准化 tf.keras.layers.BatchNormalization 参数解析与应用分析

本文深入探讨TensorFlow中BatchNormalization层的工作原理,包括参数设定、变量类型与更新机制,以及在训练与测试阶段的不同表现。重点讲解了如何正确配置与应用BN层,避免常见陷阱。

Table of Contents

函数调用

设置training=None时可能存在的问题 :tf.keras.backend.learning_phase()的特点

批标准化函数产生的变量是可训练的吗?

在使用批标准化时,要保存所有变量,而不仅仅是可训练变量

应用分析

滑动平均的计算公式:

滑动平均计算节点在批标准化中的作用

滑动平均的计算时机与注意事项:添加依赖控制

如果没有添加依赖控制会怎样? 

批标准化过程

 


说明:

1. 我用的tensorflow是1.14.0。 tensorflow的一些参数含义在1.*版本和2.*版本上是不同的。这点需要注意。以下面代码中的参数“trainable=True/False”为例,在1.*版本和2.*版本上是完全相反的含义。(劝大家早日逃离tensorflow).

2. trainable参数:是在批标准化层的类对象参数。

   training参数:是批标准化的类对象的调用函数call()的参数。

3. trainable参数:

    上面提到,在tensorflow 2.0和tensorflow 1.*中,对于批标准化层的trainable参数的相同设置有不同的含义。下面第二个代码框内有介绍。

    所应用的到底是哪种含义,建议直接去源码查看说明,我用的是tf 1.14.0, 在批标准化层的说明。


参数介绍

基类的定义如下:

class BatchNormalizationBase(Layer):
  def __init__(self,
               axis=-1,# 指向[NHWC]的channel维度,当数据shape为[NCHW]时,令axis=1
               momentum=0.99,# 计算均值与方差的滑动平均时使用的参数(滑动平均公式中的beta,不要与这里混淆)
               epsilon=1e-3,
               center=True,# bool变量,决定是否使用批标准化里的beta参数(是否进行平移)
               scale=True,# bool变量,决定是否使用批标准化里的gamma参数(是否进行缩放)
               beta_initializer='zeros',# 调用init_ops.zeros_initializer(),beta参数的0初始化,beta参数是平移参数
               gamma_initializer='ones',# 调用init_ops.ones_initializer(),gamma参数的1初始化,gamma参数是缩放参数
               moving_mean_initializer='zeros',# 均值的滑动平均值的初始化,初始均值为0
               moving_variance_initializer='ones',# 方差的滑动平均值的初始化,初始均值为1# 可见初始的均值与方差是标准正态分布的均值与方差
               beta_regularizer=None,# beta参数的正则化向,一般不用
               gamma_regularizer=None,# gamma 参数的正则化向,一般不用
               beta_constraint=None,# beta参数的约束项,一般不用
               gamma_constraint=None,# gamma 参数的约束项,一般不用
               renorm=False,
               renorm_clipping=None,
               renorm_momentum=0.99,
               fused=None,
               trainable=True,# 默认为True,这个我觉得就不要改了,没必要给自己找麻烦,
                              # 就是把我们标准化公式里面的参数添加到
                              # GraphKeys.TRAINABLE_VARIABLES这个集合里面去,
                              # 因为只有添加进去了,参数才能更新,毕竟γ和β是需要学习的参数。
                              # 但是,tf.keras.layers.BatchNormalization中并没有做到这一点,
                              # 所以需要手工执行这一操作。
               virtual_batch_size=None,
               adjustment=None,
               name=None,
               **kwargs):
    ########################
    ##只介绍参数,具体执行代码省略
    #####################

  def _get_training_value(self, training=None):
    #######
    ###该函数说明了training在不同取值时的处理,把输入的training参数转为bool变量输出,
    ###这里主要关注对training=None的处理
    #######
    if training is None:
      training = K.learning_phase() # K表示keras.backend,learning_phase()函数返回当前状态flag,是train还是test阶段,供keras使用
    if self._USE_V2_BEHAVIOR:
      if isinstance(training, int):
        training = bool(training)
      if base_layer_utils.is_in_keras_graph():
        training = math_ops.logical_and(training, self._get_trainable_var())
      else:
        training = math_ops.logical_and(training, self.trainable)
    return training

  def call(self, inputs,# 就是输入数据,默认shape=[NHWC],如果是其它shape,要对上面的axis值进行修改
         training=None  # 有三种选择:True,False,None,用于判断网络是处于训练阶段还是测试阶段。
                        # `training=True`: 网络处于训练阶段,The layer will normalize its inputs 
                        #    using the mean and variance of the current batch of inputs.
                        #  `training=False`: 网络处于测试阶段或inference阶段,The layer will normalize its inputs using 
                        #    the mean and variance of its moving statistics, learned during training.
                        # 即,training=True:使用当前批次的均值与方差进行标准化;training=False,使用滑动均值,滑动方差进行标准化。
                       
          ):
   
    training = self._get_training_value(training)

    ###
    ###只介绍参数,具体执行代码省略
    ###

关于trainable的设置,以下是keras的说明:

"""
class BatchNormalization(normalization.BatchNormalizationBase):

  __doc__ = normalization.replace_in_base_docstring([
      ('{
  
  {TRAINABLE_ATTRIBUTE_NOTE}}',
       '''
  **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()”函数。
      ''')])
"""

函数调用

综上,在调用tf.keras.layers.BatchNormalization 时,我们几乎不需要设定任何参数,只需要输入数据就好。

但是

1. tf.keras.layers.BatchNormalization有一个bug:无论“

``` import tensorflow as tf from keras import datasets, layers, models import matplotlib.pyplot as plt # 导入mnist数据,依次分别为训练集图片、训练集标签、测试集图片、测试集标签 (train_images, train_labels), (test_images, test_labels) = datasets.mnist.load_data() # 将像素的值标准化至0到1的区间内。(对于灰度图片来说,每个像素最大值是255,每个像素最小值是0,也就是直接除以255就可以完成归一化。) train_images, test_images = train_images / 255.0, test_images / 255.0 # 查看数据维数信息 print(train_images.shape,test_images.shape,train_labels.shape,test_labels.shape) #调整数据到我们需要的格式 train_images = train_images.reshape((60000, 28, 28, 1)) test_images = test_images.reshape((10000, 28, 28, 1)) print(train_images.shape,test_images.shape,train_labels.shape,test_labels.shape) train_images = train_images.astype("float32") / 255.0 def image_to_patches(images, patch_size=4): batch_size = tf.shape(images)[0] patches = tf.image.extract_patches( images=images[:, :, :, tf.newaxis], sizes=[1, patch_size, patch_size, 1], strides=[1, patch_size, patch_size, 1], rates=[1, 1, 1, 1], padding="VALID" ) return tf.reshape(patches, [batch_size, -1, patch_size*patch_size*1]) class TransformerBlock(tf.keras.layers.Layer): def __init__(self, embed_dim, num_heads): super().__init__() self.att = tf.keras.layers.MultiHeadAttention(num_heads=num_heads, key_dim=embed_dim) self.ffn = tf.keras.Sequential([ tf.keras.layers.Dense(embed_dim*4, activation="relu"), tf.keras.layers.Dense(embed_dim) ]) self.layernorm1 = tf.keras.layers.LayerNormalization() self.layernorm2 = tf.keras.layers.LayerNormalization() def call(self, inputs): attn_output = self.att(inputs, inputs) out1 = self.layernorm1(inputs + attn_output) ffn_output = self.ffn(out1) return self.layernorm2(out1 + ffn_output) class PositionEmbedding(tf.keras.layers.Layer): def __init__(self, max_len, embed_dim): super().__init__() self.pos_emb = tf.keras.layers.Embedding(input_dim=max_len, output_dim=embed_dim) def call(self, x): positions = tf.range(start=0, limit=tf.shape(x)[1], delta=1) return x + self.pos_emb(positions) def build_transformer_model(): inputs = tf.keras.Input(shape=(49, 16)) # 4x4 patches x = tf.keras.layers.Dense(64)(inputs) # 嵌入维度64 # 添加位置编码 x = PositionEmbedding(max_len=49, embed_dim=64)(x) # 堆叠Transformer模块 x = TransformerBlock(embed_dim=64, num_heads=4)(x) x = TransformerBlock(embed_dim=64, num_heads=4)(x) # 分类头 x = tf.keras.layers.GlobalAveragePooling1D()(x) outputs = tf.keras.layers.Dense(10, activation="softmax")(x) return tf.keras.Model(inputs=inputs, outputs=outputs) model = build_transformer_model() model.compile(optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"]) # 数据预处理 train_images_pt = image_to_patches(train_images[..., tf.newaxis]) test_images_pt = image_to_patches(test_images[..., tf.newaxis]) history = model.fit( train_images_pt, train_labels, validation_data=(test_images_pt, test_labels), epochs=10, batch_size=128 )```代码检查并添加注释
03-12
``` import tensorflow as tf from tensorflow.keras.preprocessing.image import ImageDataGenerator import matplotlib.pyplot as plt # 建议添加随机种子保证可重复性 tf.random.set_seed(42) # 定义路径 train_dir = r'C:\Users\29930\Desktop\结构参数图' test_dir = r'C:\Users\29930\Desktop\测试集路径' # 需要实际路径 # 数据增强配置 train_datagen = ImageDataGenerator( rescale=1./255, validation_split=0.2, rotation_range=20, width_shift_range=0.2, height_shift_range=0.2, shear_range=0.2, zoom_range=0.2, horizontal_flip=True ) # 生成训练集和验证集 train_generator = train_datagen.flow_from_directory( train_dir, target_size=(224, 224), batch_size=32, class_mode='binary', subset='training' ) val_generator = train_datagen.flow_from_directory( train_dir, target_size=(224, 224), batch_size=32, class_mode='binary', subset='validation' ) # 模型构建 model = tf.keras.Sequential([ tf.keras.layers.Input(shape=(224,224,3)), # 显式输入层 tf.keras.layers.Conv2D(32, (3,3), activation='relu'), tf.keras.layers.MaxPooling2D(2,2), tf.keras.layers.Conv2D(64, (3,3), activation='relu'), tf.keras.layers.MaxPooling2D(2,2), tf.keras.layers.Conv2D(128, (3,3), activation='relu'), tf.keras.layers.MaxPooling2D(2,2), tf.keras.layers.Flatten(), tf.keras.layers.Dense(512, activation='relu'), tf.keras.layers.Dropout(0.5), tf.keras.layers.Dense(1, activation='sigmoid') ]) model.compile( optimizer='adam', loss='binary_crossentropy', metrics=['accuracy', tf.keras.metrics.AUC(name='auc')] ) # 早停法 early_stop = tf.keras.callbacks.EarlyStopping( monitor='val_loss', patience=5, restore_best_weights=True ) # 训练模型 history = model.fit( train_generator, validation_data=val_generator, epochs=30, callbacks=[early_stop] ) # 保存模型 model.save('copd_cnn_model.h5') # 可视化结果 plt.plot(history.history['auc'], label='Training AUC') plt.plot(history.history['val_auc'], label='Validation AUC') plt.title('模型AUC曲线') plt.ylabel('AUC值') plt.xlabel('Epoch') plt.legend() plt.show() # 增加更多训练过程可视化 fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15,5)) ax1.plot(history.history['loss'], label='训练损失') ax1.plot(history.history['val_loss'], label='验证损失') ax1.set_title('损失曲线') ax1.legend() ax2.plot(history.history['accuracy'], label='训练准确率') ax2.plot(history.history['val_accuracy'], label='验证准确率') ax2.set_title('准确率曲线') ax2.legend() plt.show()```解释代码
最新发布
03-22
评论 9
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值