调参秘籍:BN层详解

批量归一化(BN:Batch Normalization:解决在训练过程中,中间层数据分布发生改变的问题,以防止梯度消失或爆炸、加快训练速度)

1、为什么输入数据需要归一化(Normalized Data)?

        归一化后有什么好处呢?原因在于神经网络学习过程本质就是为了学习数据分布,一旦训练数据与测试数据的分布不同,那么网络的泛化能力也大大降低;另外一方面,一旦每批训练数据的分布各不相同(batch 梯度下降),那么网络就要在每次迭代都去学习适应不同的分布,这样将会大大降低网络的训练速度,这也正是为什么我们需要对数据都要做一个归一化预处理的原因。

        对于深度网络的训练是一个复杂的过程,只要网络的前面几层发生微小的改变,那么后面几层就会被累积放大下去。一旦网络某一层的输入数据的分布发生改变,那么这一层网络就需要去适应学习这个新的数据分布,所以如果训练过程中,训练数据的分布一直在发生变化,那么将会影响网络的训练速度。

2、BN训练  

1)随机梯度下降法(SGD)对于训练深度网络简单高效,但是它有个毛病,就是需要我们人为的去选择参数,比如学习率、参数初始化、权重衰减系数、Drop out比例等。这些参数的选择对训练结果至关重要,以至于我们很多时间都浪费在这些的调参上。那么使用BN之后,你可以不需要那么刻意的慢慢调整参数。

2)神经网络一旦训练起来,那么参数就要发生更新,除了输入层的数据外(因为输入层数据,我们已经人为的为每个样本归一化),后面网络每一层的输入数据分布是一直在发生变化的,因为在训练的时候,前面层训练参数的更新将导致后面层输入数据分布的变化。以网络第二层为例:网络的第二层输入,是由第一层的参数和input计算得到的,而第一层的参数在整个训练过程中一直在变化,因此必然会引起后面每一层输入数据分布的改变。我们把网络中间层在训练过程中,数据分布的改变称之为:“Internal  Covariate Shift”。文章所提出的算法,就是要解决在训练过程中,中间层数据分布发生改变的情况,于是就有了Batch  Normalization,这个算法的诞生。

3)BN的地位:与激活函数层、卷积层、全连接层、池化层一样,BN(Batch Normalization)也属于网络的一层。

4)BN的本质原理:在网络的每一层输入的时候,又插入了一个归一化层,也就是先做一个归一化处理(归一化至:均值0、方差为1),然后再进入网络的下一层。不过文献归一化层,可不像我们想象的那么简单,它是一个可学习、有参数(γ、β)的网络层。

3、BN的作用

 1)改善流经网络的梯度

 2)允许更大的学习率,大幅提高训练速度:

      你可以选择比较大的初始学习率,让你的训练速度飙涨。以前还需要慢慢调整学习率,甚至在网络训练到一半的时候,还需要想着学习率进一步调小的比例选择多少比较合适,现在我们可以采用初始很大的学习率,然后学习率的衰减速度也很大,因为这个算法收敛很快。当然这个算法即使你选择了较小的学习率,也比以前的收敛速度快,因为它具有快速训练收敛的特性;

 3)减少对初始化的强烈依赖

 4)改善正则化策略:作为正则化的一种形式,轻微减少了对dropout的需求

       你再也不用去理会过拟合中drop out、L2正则项参数的选择问题,采用BN算法后,你可以移除这两项了参数,或者可以选择更小的L2正则约束参数了,因为BN具有提高网络泛化能力的特性;

 5)再也不需要使用使用局部响应归一化层了(局部响应归一化是Alexnet网络用到的方法,搞视觉的估计比较熟悉),因为BN本身就是一个归一化网络层;

6)可以把训练数据彻底打乱(防止每批训练的时候,某一个样本都经常被挑选到,文献说这个可以提高1%的精度)。

注:以上为学习过程,在测试时,均值和方差(mean/std)不基于小批量进行计算, 可取训练过程中的激活值的均值。

参考:https://www.cnblogs.com/king-lps/p/8378561.html
————————————————
版权声明:本文为CSDN博主「王小波_Libo」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_38900441/article/details/106047525

对于一个小白,从了解Batch Normalization(后面简称BN)到正确使用BN,可谓路漫漫兮。在此做一个记录。

网上搜索关于BN最多的就是原理推导,相关论文出处。

例如:

http://blog.csdn.net/Fate_fjh/article/details/53375881

https://www.jianshu.com/p/0312e04e4e83

但是这个并不能帮助我们实际的使用,对于需要迅速用起来的伙伴帮助不大。我们工程师相信的是先用起来,再去研究原理!呵呵!

有一些文章介绍的BN层的实现,也有代码示例,但能顺利跑起来的寥寥。因为使用BN不像卷积层那样,写个层的实现就可以了。由于BN层会包含两个可训练参数以及两个不可训练参数,所以涉及到在train代码中如何保存的关键问题,以及在inference代码中如何加载的问题。有相关博客介绍到这一步了,很有帮助。

例如:

https://www.cnblogs.com/hrlnw/p/7227447.html

本以为别人都说这么明白了,抄一抄不是很容易的事情吗。可以上的代码是不能让你正确完成BN功能的。也不知是抄错了,还是别人漏掉了一些关键环节。总之你的moving_mean/moving_variance好像就是不太对。基本上中文网页很难在找到这个问题的解了。

现在你需要搜索的关键字可能要变成BN/参数保存/平均滑动等等了。还好tensorflow的github中有了线索:

https://github.com/tensorflow/tensorflow/issues/14809

https://github.com/tensorflow/tensorflow/issues/15250

可见有很多人确实无法正确使用BN功能,然而最有用的一个issues是:

https://github.com/tensorflow/tensorflow/issues/1122#issuecomment-280325584

在这里,我拼凑成了一个完整能用的BN功能代码,解决了我好久的痛苦,让我兴奋一下。

知识来源于网络,奉献给网络。不敢独享这一成果,再此分享给大家。

-----------------------------------------------------------------华丽的分割线----------------------------------------------------------------------------

整个BN功能的实现需要分三个部分:1.BN层实现;2.训练时更新和完成后保存;3.预测时加载。

1.BN层实现:

如果你接触了一段时间后,这里你至少应该知道BN的三种实现方式了,但是我只成功了其中的一种,希望其他朋友能够补充完善。

def bn_layer(x, scope, is_training, epsilon=0.001, decay=0.99, reuse=None):

    """

    Performs a batch normalization layer

    Args:

        x: input tensor

        scope: scope name

        is_training: python boolean value

        epsilon: the variance epsilon - a small float number to avoid dividing by 0

        decay: the moving average decay

    Returns:

        The ops of a batch normalization layer

    """

    with tf.variable_scope(scope, reuse=reuse):

        shape = x.get_shape().as_list()

        # gamma: a trainable scale factor

        gamma = tf.get_variable(scope+"_gamma", shape[-1], initializer=tf.constant_initializer(1.0), trainable=True)

        # beta: a trainable shift value

        beta = tf.get_variable(scope+"_beta", shape[-1], initializer=tf.constant_initializer(0.0), trainable=True)

        moving_avg = tf.get_variable(scope+"_moving_mean", shape[-1], initializer=tf.constant_initializer(0.0), trainable=False)

        moving_var = tf.get_variable(scope+"_moving_variance", shape[-1], initializer=tf.constant_initializer(1.0), trainable=False)

        if is_training:

            # tf.nn.moments == Calculate the mean and the variance of the tensor x

            avg, var = tf.nn.moments(x, np.arange(len(shape)-1), keep_dims=True)

            avg=tf.reshape(avg, [avg.shape.as_list()[-1]])

            var=tf.reshape(var, [var.shape.as_list()[-1]])

            #update_moving_avg = moving_averages.assign_moving_average(moving_avg, avg, decay)

            update_moving_avg=tf.assign(moving_avg, moving_avg*decay+avg*(1-decay))

            #update_moving_var = moving_averages.assign_moving_average(moving_var, var, decay)

            update_moving_var=tf.assign(moving_var, moving_var*decay+var*(1-decay))

            control_inputs = [update_moving_avg, update_moving_var]

        else:

            avg = moving_avg

            var = moving_var

            control_inputs = []

        with tf.control_dependencies(control_inputs):

            output = tf.nn.batch_normalization(x, avg, var, offset=beta, scale=gamma, variance_epsilon=epsilon)

    return output

def bn_layer_top(x, scope, is_training, epsilon=0.001, decay=0.99):

    """

    Returns a batch normalization layer that automatically switch between train and test phases based on the

    tensor is_training

    Args:

        x: input tensor

        scope: scope name

        is_training: boolean tensor or variable

        epsilon: epsilon parameter - see batch_norm_layer

        decay: epsilon parameter - see batch_norm_layer

    Returns:

        The correct batch normalization layer based on the value of is_training

    """

    #assert isinstance(is_training, (ops.Tensor, variables.Variable)) and is_training.dtype == tf.bool

    return tf.cond(

        is_training,

        lambda: bn_layer(x=x, scope=scope, epsilon=epsilon, decay=decay, is_training=True, reuse=None),

        lambda: bn_layer(x=x, scope=scope, epsilon=epsilon, decay=decay, is_training=False, reuse=True),

    )

这里的参数epsilon=0.001, decay=0.99可以自行调整。



2.训练时更新和完成后保存:

在训练的代码中增加如下代码:

update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)

with tf.control_dependencies(update_ops):

    train = tf.train.AdamOptimizer(learning_rate=lr).minimize(cost)

这个是用于更新参数的。

var_list = tf.trainable_variables()

g_list = tf.global_variables()

bn_moving_vars = [gfor gin g_listif 'moving_mean' in g.name]

bn_moving_vars += [gfor gin g_listif 'moving_variance' in g.name]

var_list += bn_moving_vars

train_saver = tf.train.Saver(var_list=var_list)

这个是用于保存bn不可训练的参数。

3.预测时加载:

# get moving avg

var_list = tf.trainable_variables()

g_list = tf.global_variables()

bn_moving_vars = [gfor gin g_listif 'moving_mean' in g.name]

bn_moving_vars += [gfor gin g_listif 'moving_variance' in g.name]

var_list += bn_moving_vars

saver = tf.train.Saver(var_list=var_list)

ckpt_path =""

saver.restore(sess, ckpt_path)

这样就可以找到checkpoint中的参数了。

在tensorflow框架下添加正则化约束l1、l2的方法

https://blog.csdn.net/Airuio/article/details/84025973

一、基础正则化函数

tf.contrib.layers.l1_regularizer(scale, scope=None)

返回一个用来执行L1正则化的函数,函数的签名是func(weights)
参数:

  • scale: 正则项的系数.
  • scope: 可选的scope name

tf.contrib.layers.l2_regularizer(scale, scope=None)

先看看tf.contrib.layers.l2_regularizer(weight_decay)都执行了什么:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

import tensorflow as tf

sess=tf.Session()

weight_decay=0.1

tmp=tf.constant([0,1,2,3],dtype=tf.float32)

"""

l2_reg=tf.contrib.layers.l2_regularizer(weight_decay)

a=tf.get_variable("I_am_a",regularizer=l2_reg,initializer=tmp)

"""

#**上面代码的等价代码

a=tf.get_variable("I_am_a",initializer=tmp)

a2=tf.reduce_sum(a*a)*weight_decay/2;

a3=tf.get_variable(a.name.split(":")[0]+"/Regularizer/l2_regularizer",initializer=a2)

tf.add_to_collection(tf.GraphKeys.REGULARIZATION_LOSSES,a2)

#**

sess.run(tf.global_variables_initializer())

keys = tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES)

for key in keys:

  print("%s : %s" %(key.name,sess.run(key)))

我们很容易可以模拟出tf.contrib.layers.l2_regularizer都做了什么,不过会让代码变丑。

以下比较完整实现L2 正则化。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

import tensorflow as tf

sess=tf.Session()

weight_decay=0.1                                                #(1)定义weight_decay

l2_reg=tf.contrib.layers.l2_regularizer(weight_decay)           #(2)定义l2_regularizer()

tmp=tf.constant([0,1,2,3],dtype=tf.float32)

a=tf.get_variable("I_am_a",regularizer=l2_reg,initializer=tmp)  #(3)创建variable,l2_regularizer复制给regularizer参数。

                                                                #目测REXXX_LOSSES集合

#regularizer定义会将a加入REGULARIZATION_LOSSES集合

print("Global Set:")

keys = tf.get_collection("variables")

for key in keys:

  print(key.name)

print("Regular Set:")

keys = tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES)

for key in keys:

  print(key.name)

print("--------------------")

sess.run(tf.global_variables_initializer())

print(sess.run(a))

reg_set=tf.get_collection(tf.GraphKeys.REGULARIZATION_LOSSES)   #(4)则REGULARIAZTION_LOSSES集合会包含所有被weight_decay后的参数和,将其相加

l2_loss=tf.add_n(reg_set)

print("loss=%s" %(sess.run(l2_loss)))

"""

此处输出0.7,即:

   weight_decay*sigmal(w*2)/2=0.1*(0*0+1*1+2*2+3*3)/2=0.7

其实代码自己写也很方便,用API看着比较正规。

在网络模型中,直接将l2_loss加入loss就好了。(loss变大,执行train自然会decay)

"""

回到顶部

二、添加正则化方法

a、原始办法

正则化常用到集合,下面是最原始的添加正则办法(直接在变量声明后将之添加进'losses'集合或tf.GraphKeys.LOESSES也行):

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

import tensorflow as tf

import numpy as np

def get_weights(shape, lambd):

    var = tf.Variable(tf.random_normal(shape), dtype=tf.float32)

    tf.add_to_collection('losses', tf.contrib.layers.l2_regularizer(lambd)(var))

    return var

= tf.placeholder(tf.float32, shape=(None2))

y_ = tf.placeholder(tf.float32, shape=(None1))

batch_size = 8

layer_dimension = [21010101]

n_layers = len(layer_dimension)

cur_lay = x

in_dimension = layer_dimension[0]

for in range(1, n_layers):

    out_dimension = layer_dimension[i]

    weights = get_weights([in_dimension, out_dimension], 0.001)

    bias = tf.Variable(tf.constant(0.1, shape=[out_dimension]))

    cur_lay = tf.nn.relu(tf.matmul(cur_lay, weights)+bias)

    in_dimension = layer_dimension[i]

mess_loss = tf.reduce_mean(tf.square(y_-cur_lay))

tf.add_to_collection('losses', mess_loss)

loss = tf.add_n(tf.get_collection('losses'))

b、tf.contrib.layers.apply_regularization(regularizer, weights_list=None)

先看参数

  • regularizer:就是我们上一步创建的正则化方法
  • weights_list: 想要执行正则化方法的参数列表,如果为None的话,就取GraphKeys.WEIGHTS中的weights.

函数返回一个标量Tensor,同时,这个标量Tensor也会保存到GraphKeys.REGULARIZATION_LOSSES中.这个Tensor保存了计算正则项损失的方法.

tensorflow中的Tensor是保存了计算这个值的路径(方法),当我们run的时候,tensorflow后端就通过路径计算出Tensor对应的值

现在,我们只需将这个正则项损失加到我们的损失函数上就可以了.

eg:

weight = tf.constant([[1.0, -2.0], [-3.0, 4.0]])
weight1 = tf.constant([[1.0, -2.0], [-3.0, 4.0]])
l2_reg=tf.contrib.layers.l2_regularizer(0.5)
with tf.Session() as sess:
    # 输出为(|1|+|-2|+|-3|+|4|)*0.5=5
    print(sess.run(tf.contrib.layers.apply_regularization(regularizer=l2_reg,weights_list=[weight,weight1])))
    # 输出为(1²+(-2)²+(-3)²+4²)/2*0.5=7.5
    # TensorFlow会将L2的正则化损失值除以2使得求导得到的结果更加简洁
    print(sess.run(tf.contrib.layers.l2_regularizer(0.5)(weight)))

如果是自己手动定义weight的话,需要手动将weight保存到GraphKeys.WEIGHTS中,但是如果使用layer的话,就不用这么麻烦了,别人已经帮你考虑好了.(最好自己验证一下tf.GraphKeys.WEIGHTS中是否包含了所有的weights,防止被坑)

c、使用slim

使用slim会简单很多:

1

2

3

4

with slim.arg_scope([slim.conv2d, slim.fully_connected],

                           activation_fn=tf.nn.relu,

                           weights_regularizer=slim.l2_regularizer(weight_decay)):

   pass

Python实现梯度下降法及算例分析以及可视化

代码和算例可以到博主github中下载:

https://github.com/Airuio/Implementing-the-method-of-gradient-descent-by-using-Python-

上一篇讲解了最原始的感知机算法,该算法的目的只为收敛,得到的往往不是基于样本的最佳解,梯度下降法以最小化损失函数为目标,得到的解比原始感知机算法一般更准确。

梯度下降法算法原理如下图所示:

基于以上原理来对权重系数和闵值进行更新即可得到最后的解。

原理实现可按如下代码操作:#实现梯度下降法:

import numpy as np
class AdalineGD(object):
    def __init__(self,eta=0.01,n_iter=50):  #定义超参数学习率和迭代次数
        self.eta = eta
        self.n_iter = n_iter
    
    def fit(self,X,y):        #定义权重系数w和损失函数cost
        self.w_ = np.zeros(1+X.shape[1])
        self.cost_ = []
        
        for i in range(self.n_iter):                   #更新权重
            output = self.net_input(X)            #计算预测值
            errors = (y - output)                     #统计误差
            self.w_[1:] += self.eta*X.T.dot(errors)
            self.w_[0] += self.eta * errors.sum()
            cost = (errors**2).sum()/2.0          #损失函数
            self.cost_.append(cost)
        return self

    def net_input(self,X):
        return np.dot(X,self.w_[1:]) + self.w_[0]
    
    def activation(self,X):
        return self.net_input(X) 

    def predict(self, X):

        return np.where(self.activation(X) >= 0.0, 1, -1)

以上算法即实现了梯度下降法更新权重参数。将该模块命名为Adaline_achieve,基于鸢尾花lirs数据集,我们进行算例的验证如下所示:

from Adaline_achieve import AdalineGD
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap

df = pd.read_excel(io = 'lris.xlsx',header = None)    #读取数据为Dataframe结构,没有表头行
y = df.iloc[0:100,4].values            #取前100列数据,4列为标识
y = np.where(y == 'Iris-setosa', -1,1)
X = df.iloc[0:100,[0,2]].values      #iloc为选取表格区域,此处取二维特征进行分类,values为返回不含索引的表

plt.scatter(X[:50,0],X[0:50,1],color = 'red',marker = 'o', label = 'setosa')
plt.scatter(X[50:100,0],X[50:100,1],color = 'blue',marker = 'x', label = 'versicolor')
plt.xlabel('petal lenth')
plt.ylabel('sepal lenth')
plt.legend(loc = 2)    #画出标签以及标签的位置参数      
plt.show()             #出图
#以上六行与分类无关,仅仅是为了直观的感受两块数据的分布区域

fig,ax = plt.subplots(nrows = 1 , ncols = 2, figsize = (8,4))    
'''
完成不同学习率下的分类的任务,进行结果展示
plt.subplots(nrows = 1 , ncols = 2, figsize = (8,4)中nrows表示几行图,ncols表示几列
figsize为图片大小。
'''
ada1 = AdalineGD(eta = 0.01,n_iter = 10).fit(X,y)
ax[0].plot(range(1,len(ada1.cost_) + 1), np.log10(ada1.cost_) , marker = 'o')
ax[0].set_xlabel('Epoches')
ax[0].set_ylabel('log(ada1.cost_)')
ax[0].set_title('Adaline - Learning rate 0.01')
ada2 = AdalineGD(eta = 0.0001,n_iter = 10).fit(X,y)
ax[1].plot(range(1,len(ada2.cost_) + 1), ada2.cost_ , marker = 'o')
ax[1].set_xlabel('Epoches')
ax[1].set_ylabel('ada1.cost_')
ax[1].set_title('Adaline - Learning rate 0.01')
plt.show()
'''
由以上得到的结果图可以看出,学习率过大会导致不收敛,过小会导致收敛速度慢
采用数据标准化、归一化的方法可以使得梯度下降法取得更好的效果
对同维度处的样本特征取均值和标准差,标准化后的值等于:
(原值-均值)/标准差,可以用numpy中的mean和std方法便捷的获得
'''
X_std = np.copy(X)                #将样本特征归一化、标准化
X_std[:,0] = (X[:,0] - X[:,0].mean()) / X[:,0].std()
X_std[:,1] = (X[:,1] - X[:,1].mean()) / X[:,1].std()


ada = AdalineGD(eta = 0.01, n_iter = 15)
ada.fit(X_std,y)
def plot_decision_region(X,y,classifier,resolution = 0.02):
    markers = ('s','x','o','~','v')
    colors = ('red','blue','lightgreen','gray','cyan')
    cmap = ListedColormap(colors[:len(np.unique(y))])
    
    #画出界面
    x1_min, x1max = X[:,0].min() - 1, X[:,0].max() + 1   
    x2_min, x2max = X[:,1].min() - 1, X[:,1].max() + 1  
    
    xx1,xx2 = np.meshgrid(np.arange(x1_min,x1max,resolution),  

                          np.arange(x2_min,x2max,resolution))   #生成均匀网格点,

 '''

meshgrid的作用是根据传入的两个一维数组参数生成两个数组元素的列表。如果第一个参数是xarray,    维度是xdimesion,第二个参数是yarray,维度是ydimesion。那么生成的第一个二维数组是以xarray为行,ydimesion行的向量;而第二个二维数组是以yarray的转置为列,xdimesion列的向量。

'''

    Z = classifier.predict(X = np.array([xx1.ravel(),xx2.ravel()]).T)
    Z = Z.reshape(xx1.shape)
    #在全图上每一个点(间隔0.2)计算预测值,并返回1或-1
    
    plt.contourf(xx1,xx2,Z,alpha = 0.5,cmap = cmap) #画出等高线并填充颜色
    plt.xlim(xx1.min(),xx1.max())
    plt.ylim(xx2.min(),xx2.max())

    #画上分类后的样本
    for idx,cl in enumerate(np.unique(y)):
        plt.scatter(x=X[y==cl,0], y=X[y==cl,1],alpha=0.8,
                    c=cmap(idx),marker=markers[idx],label=cl)
 
plot_decision_region(X_std, y, classifier = ada)    #展示分类结果
plt.xlabel('sepal lenth [nondimensional]')
plt.ylabel('petal lenth [nondimensional]')    
plt.legend(loc = 2)
plt.show()

plt.plot(range(1,len(ada.cost_)+1),ada.cost_,marker = 'o')   #展示损失函数误差收敛过程
plt.xlabel('Epoches')
plt.ylabel('ada1.cost_')
plt.show()

结果如下图所示:

—————————————
版权声明:本文为CSDN博主「Airuio」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Airuio/article/details/80956661

猜你喜欢

转载自blog.csdn.net/l641208111/article/details/115641542