zl程序教程

您现在的位置是:首页 >  后端

当前栏目

深度学习中归一化/标准化方法

方法学习 深度 标准化 归一化
2023-06-13 09:13:11 时间

继续接着上两篇的内容:

深度学习中的损失函数1

深度学习中的损失函数2

还是权当个人学习笔记来记录的。

一.归一化/标准化

从定义上来讲,归一化是指把数据转化为长度为1或者原点附近的小区间,而标准化是指将数据转化为均值为0,标准差为1的数据。。归一化与标准化实质上都是某种数据变化,无论是线性变化还是非线性变化,其都不会改变原始数据中的数值排序,它们都能将特征值转换到同一量纲下。由于归一化是将数据映射到某一特定区间内,因此其缩放范围仅由数据中的极值决定,而标准化是将源数据转化为均值为0,方差为1的分布,其涉及计算数据的均值和标准差,每个样本点都会对标准化过程产生影响。

在深度学习中,使用归一化/标准化后的数据可以加快模型的收敛速度,其有时还能提高模型的精度,这在涉及距离计算的模型中尤为显著,在计算距离时,若数据的量纲不一致,则最终计算结果会更偏向极差大的数据。由于数值层面被减小,在计算机进行计算时,一方面可以防止模型的梯度过大(爆炸),另一方面也能避免一些由于太大的数值引起的数值问题。

二.归一化方法

2.1最小-最大值归一化

2.2均值归一化

2.3对数函数归一化

2.4反正切函数归一化

三.标准化方法

3.1Z-score标准化

常用于数据预处理,需要先计算所有样本数据的均值与标准差然后再对样本进行变化。

3.2Batch Normalization

def batch_normalization(inp, 
                        name, 
                        weight1=0.99, 
                        weight2=0.99, 
                        is_training=True):
    with tf.variable_scope(name):
        # 获取输入张量的形状
        inp_shape = inp.get_shape().as_list()

        # 定义不可训练变量hist_mean记录均值的移动平均值
        # 形状与输入张量最后一个维度相同
        hist_mean = tf.get_variable('hist_mean', 
                                    shape=inp_shape[-1:], 
                                    initializer=tf.zeros_initializer(), 
                                    trainable=False)

        # 定义不可训练变量hist_var记录方差的移动平均值
        # 形状与输入张量最后一个维度相同
        hist_var = tf.get_variable('hist_var', 
                                   shape=inp_shape[-1:], 
                                   initializer=tf.ones_initializer(), 
                                   trainable=False)

        # 定义可训练变量gamma和beta,形状与输入张量最后一个维度相同
        gamma = tf.Variable(tf.ones(inp_shape[-1:]), name='gamma')
        beta = tf.Variable(tf.zeros(inp_shape[-1:]), name='beta')

        # 计算输入张量除了最后一个维度外上面的均值与方差
        batch_mean, batch_var = tf.nn.moments(inp, 
                                    axes=[i for i in range(len(inp_shape) - 1)], 
                                    name='moments')

        # 计算均值的移动平均值,并将计算结果赋予hist_mean/running_mean
        running_mean = tf.assign(hist_mean, 
                                 weight1 * hist_mean + (1 - weight1) * batch_mean)

        # 计算方差的移动平均值,并将计算结果赋予hist_var/running_var
        running_var = tf.assign(hist_var, 
                                weight2 * hist_var + (1 - weight2) * batch_var)

        # 使用control_dependencies限制先计算移动平均值
        with tf.control_dependencies([running_mean, running_var]):
            # 根据当前状态是训练或是测试选取不同的值进行标准化
            # is_training=True,使用batch_mean & batch_var
            # is_training=False,使用running_mean & running_var
            output = tf.cond(tf.cast(is_training, tf.bool),
                             lambda: tf.nn.batch_normalization(inp, 
                                                mean=batch_mean, 
                                                variance=batch_var, 
                                                scale=gamma, 
                                                offset=beta, 
                                                variance_epsilon=1e-5, 
                                                name='bn'),
                             lambda: tf.nn.batch_normalization(inp, 
                                                mean=running_mean, 
                                                variance=running_var, 
                                                scale=gamma, 
                                                offset=beta, 
                                                variance_epsilon=1e-5, 
                                                name='bn')
                             )
        return output

def _batch_normalization(inp, name, weight1=0.99, weight2=0.99, is_training=True):
    with tf.variable_scope(name):
        return tf.layers.batch_normalization(
            inp,
            training=is_training
        )

3.3Layer Normalization

import tensorflow.compat.v1 as tf


def layer_normalization(inp, name):
    with tf.variable_scope(name):
        # 获取输入张量的形状
        inp_shape = inp.get_shape().as_list()

        # 定义可训练变量gamma和beta,batch维度与输入张量第一个维度相同
        para_shape = [inp_shape[0]] + [1] * (len(inp_shape) - 1)
        gamma = tf.Variable(tf.ones(para_shape, name='gamma'))
        beta = tf.Variable(tf.zeros(para_shape, name='beta'))

        # 计算输入张量除了第一个维度外上面的均值与方差
        layer_mean, layer_var = tf.nn.moments(inp, 
                                    axes=[i for i in range(1, len(inp_shape))],
                                    name='moments', keep_dims=True)

        output = gamma * (inp - layer_mean) / tf.sqrt(layer_var + 1e-5) + beta
        return output

if __name__ == "__main__":
    a = tf.ones([128, 10, 10, 3])
    b = layer_normalization(a, name='ln')
    print(b.shape)
    with tf.Session() as sess:
        tf.global_variables_initializer().run()
        print(sess.run(b))

3.4Instance Normalization

import tensorflow.compat.v1 as tf

def instance_normalization(inp, name):
    with tf.variable_scope(name):
        # 获取输入张量的形状
        inp_shape = inp.get_shape().as_list()

        # 定义可训练变量gamma和beta,形状为[n,1,1,c]方便直接线性变换
        para_shape = [inp_shape[0], 1, 1, inp_shape[-1]]
        gamma = tf.Variable(tf.ones(para_shape, name='gamma'))
        beta = tf.Variable(tf.zeros(para_shape, name='beta'))

        # 计算输入张量第一(H)和第二(W)维度外上面的均值与方差
        insta_mean, insta_var = tf.nn.moments(inp, 
                                        axes=[1,2],
                                        name='moments', keep_dims=True)

        output = gamma * (inp - insta_mean) / tf.sqrt(insta_var + 1e-5) + beta
        return output

if __name__ == "__main__":
    a = tf.ones([128, 10, 10, 3])
    b = instance_normalization(a, name='in')
    print(b.shape)
    with tf.Session() as sess:
        tf.global_variables_initializer().run()
        print(sess.run(b))

3.5Group Normalization

import tensorflow.compat.v1 as tf


def group_normalization(inp, name, G=32):
    with tf.variable_scope(name):
        # 获取输入张量的形状
        insp = inp.get_shape().as_list()

        # 将输入的NHWC格式转换为NCHW方便进行分组
        inp = tf.transpose(inp, [0, 3, 1, 2])
        
        # 将输入张量进行分组,得到新张量形状为[n,G,c//G,h,w]
        inp = tf.reshape(inp, 
                [insp[0], G, insp[-1] // G, insp[1], insp[2]])

        # 定义可训练变量gamma和beta,形状为[1,1,1,c]方便直接线性变换
        para_shape = [1, 1, 1, insp[-1]]
        gamma = tf.Variable(tf.ones(para_shape, name='gamma'))
        beta = tf.Variable(tf.zeros(para_shape, name='beta'))

        # 计算输入张量第二、三和四(c//G,h,w)维度外上面的均值与方差
        group_mean, group_var = tf.nn.moments(inp, 
                                        axes=[2, 3, 4],
                                        name='moments', keep_dims=True)
        
        inp = (inp - group_mean) / tf.sqrt(group_var + 1e-5)

        # 将张量形状还原为原始形状[n,h,w,c]
        # 先将标准化之后的分组结果重新组合为[n,c,w,h]
        inp = tf.reshape(inp, 
                [insp[0], insp[-1], insp[1], insp[2]])

        # 通过转置操作将NCHW格式转换为NHWC
        inp = tf.transpose(inp, [0, 2, 3, 1])

        output = gamma * inp + beta
        return output

if __name__ == "__main__":
    import numpy as np
    a = tf.constant(np.random.randn(1,2,2,64), dtype=tf.float32)
    b = group_normalization(a, name='gn1')

    with tf.Session() as sess:
        tf.global_variables_initializer().run()
        rb = sess.run(b)
        print(rb)

3.6 Switchable Normalization

switchable Normalization的目的则是使模型自动学会最适合的一种标准化策略。