深度学习入门-权重初始值(Xavier,He初始值,隐藏层激活函数值的分布)

权重的初始值

(有关此处的坑太深,本文只是深度学习入门,不会详细追求理论)

​ 在神经网络的学习中,权重的初始值特别重要。实际上,设定什么样的 权重初始值,经常关系到神经网络的学习能否成功。本节将介绍权重初始值 的推荐值,并通过实验确认神经网络的学习是否会快速进行。

2.1非对称结构

​ 在误差反向传播法中,所有的权重值都会进行 相同的更新。比如,在2层神经网络中,假设第1层和第2层的权重为0。这 样一来,正向传播时,因为输入层的权重为0,所以第2层的神经元全部会 被传递相同的值。第2层的神经元中全部输入相同的值,这意味着反向传播 时第2层的权重全部都会进行相同的更新(回忆一下“乘法节点的反向传播” )。因此,权重被更新为相同的值,并拥有了对称的值(重复的值)。 这使得神经网络拥有许多不同的权重的意义丧失了。为了防止“权重均一化” (严格地讲,是为了瓦解权重的对称结构),必须随机生成初始值

2.2 隐藏层的激活值的分布

​ 观察隐藏层的激活值 A (激活函数的输出数据)的分布,可以获得很多启 发。这里,我们来做一个简单的实验,观察权重初始值是如何影响隐藏层的 激活值的分布的。这里要做的实验是,向一个5层神经网络(激活函数使用 sigmoid函数)传入随机生成的输入数据,用直方图绘制各层激活值的数据分 布。这个实验参考了斯坦福大学的课程CS231n。这是计算机视觉和深度学习的一个非常好的课程。

import numpy as np
import matplotlib.pyplot as plt

def sigmoid(x):
    return 1/(1+np.exp(-x))

x = np.random.randn(1000,100)
node_num = 100 #各隐藏层的节点数
hidden_layer_size = 5 #5层隐藏层
activations = {} #保存激活值的字典
no_activations={}
for i in range(hidden_layer_size):
    if i != 0:
        x = activations[i-1]

    w = np.random.randn(node_num,node_num)*1

    z = np.dot(x,w)
    a = sigmoid(z)
    activations[i] = a
    no_activations[i] = z

for i,a in activations.items():
    plt.subplot(1,len(activations),i+1)
    plt.title(str(i+1)+"-layer")
    plt.hist(a.flatten(),30,range = (0,1))


print(activations[0].flatten().shape)
plt.show()




hist统计数据的个数,代码中统计的是0~1分成了30份,每份中有多少个数据。我们来看一下

在这里插入图片描述

​ 各层的激活值呈偏向0和1的分布。这里使用的sigmoid 函数是S型函数,随着输出不断地靠近0(或者靠近1),它的导数的值逐渐接 近0。因此,偏向0和1的数据分布会造成反向传播中梯度的值不断变小,最 后消失。这个问题称为梯度消失(gradient vanishing)。层次加深的深度学习 中,梯度消失的问题可能会更加严重。

下面我们将权重换位0.01

import numpy as np
import matplotlib.pyplot as plt

def sigmoid(x):
    return 1/(1+np.exp(-x))

x = np.random.randn(1000,100)
node_num = 100 #各隐藏层的节点数
hidden_layer_size = 5 #5层隐藏层
activations = {} #保存激活值的字典
no_activations={}
for i in range(hidden_layer_size):
    if i != 0:
        x = activations[i-1]

    w = np.random.randn(node_num,node_num)*0.01

    z = np.dot(x,w)
    a = sigmoid(z)
    activations[i] = a
    no_activations[i] = z
    
for i,a in activations.items():
    plt.subplot(1,len(activations),i+1)
    plt.title(str(i+1)+"-layer")
    plt.hist(a.flatten(),30,range = (0,1))

print(activations[0].flatten().shape)

plt.show()

在这里插入图片描述

这次呈集中在0.5附近的分布。因为不像刚才的例子那样偏向0和1,所 以不会发生梯度消失的问题。但是,激活值的分布有所偏向,说明在表现力 上会有很大问题。为什么这么说呢?因为如果有多个神经元都输出几乎相同 的值,那它们就没有存在的意义了。比如,如果100个神经元都输出几乎相 同的值,那么也可以由1个神经元来表达基本相同的事情。因此,激活值在 分布上有所偏向会出现“表现力受限”的问题。

各层的激活值的分布都要求有适当的广度。为什么呢?因为通过 在各层间传递多样性的数据,神经网络可以进行高效的学习。反 过来,如果传递的是有所偏向的数据,就会出现梯度消失或者“表现力受限”的问题,导致学习可能无法顺利进行。

Xavier的论文中,为了使各层的激活值呈现出具有相同广度的分布,推导了合适的权重尺度。推导出的结论是,如果前一层的节点数为n,初始值使用标准差为1/根号n。

import numpy as np
import matplotlib.pyplot as plt

def sigmoid(x):
    return 1/(1+np.exp(-x))

x = np.random.randn(1000,100)
node_num = 100 #各隐藏层的节点数
hidden_layer_size = 5 #5层隐藏层
activations = {} #保存激活值的字典
no_activations={}
for i in range(hidden_layer_size):
    if i != 0:
        x = activations[i-1]

    w = np.random.randn(node_num,node_num)*1/np.sqrt(node_num)

    z = np.dot(x,w)
    a = sigmoid(z)
    activations[i] = a
    no_activations[i] = z
    
for i,a in activations.items():
    plt.subplot(1,len(activations),i+1)
    plt.title(str(i+1)+"-layer")
    plt.hist(a.flatten(),30,range = (0,1))

print(activations[0].flatten().shape)

plt.show()

在这里插入图片描述

从这个结果可知,越是后 面的层,图像变得越歪斜,但是呈现了比之前更有广度的分布。因为各层间 传递的数据有适当的广度,所以sigmoid函数的表现力不受限制,有望进行 高效的学习

后面的层的分布呈稍微歪斜的形状。如果用tanh 函数(双曲线函数)代替sigmoid函数,这个稍微歪斜的问题就能得 到改善。实际上,使用tanh函数后,会呈漂亮的吊钟型分布。tanh 函数和sigmoid函数同是 S型曲线函数,但tanh函数是关于原点(0, 0) 对称的 S型曲线,而sigmoid函数是关于(x, y)=(0, 0.5)对称的S型曲 线。众所周知,用作激活函数的函数最好具有关于原点对称的性质。

tanh函数:
在这里插入图片描述

import numpy as np
import matplotlib.pyplot as plt

def sigmoid(x):
    return 1/(1+np.exp(-x))
def tanh(x):
    return 2*sigmoid(2*x)-1
x = np.random.randn(1000,100)
node_num = 100 #各隐藏层的节点数
hidden_layer_size = 5 #5层隐藏层
activations = {} #保存激活值的字典
no_activations={}
for i in range(hidden_layer_size):
    if i != 0:
        x = activations[i-1]
        
    w = np.random.randn(node_num,node_num)*1/np.sqrt(node_num)

    z = np.dot(x,w)
    #a = sigmoid(z)
    a = tanh(z)
    activations[i] = a
    no_activations[i] = z

for i,a in activations.items():
    plt.subplot(1,len(activations),i+1)
    plt.title(str(i+1)+"-layer")
    plt.hist(a.flatten(),30,range = (0,1))

print(activations[0].flatten().shape)
plt.show()

在这里插入图片描述

确实我没有看出哪里像吊钟。

Xavier初始值是以激活函数是线性函数为前提而推导出来的。因为 sigmoid函数和tanh函数左右对称,且中央附近可以视作线性函数,所以适 合使用Xavier初始值。但当激活函数使用ReLU时,一般推荐使用ReLU专 用的初始值,也就是Kaiming He等人推荐的初始值,也称为“He初始值”。初始值为根号下(2/n)。

我们来试一下:

import numpy as np
import matplotlib.pyplot as plt

def ReLU(x):
    temp = x<=0
    x[temp] = 0
    return x

node_num = 100 #各隐藏层的节点数
hidden_layer_size = 5 #5层隐藏层
activations = {} #保存激活值的字典
active_init = {"权重初始值为标准差是0.01的高斯分布":0.01,
               "权重初始值为Xavier初始值":1/np.sqrt(node_num),
               "权重初始值为He初始值":np.sqrt(2 / node_num)}

for key,val in active_init.items():
    x = np.random.randn(1000, 100)
    for i in range(hidden_layer_size):
        if i != 0:
            x = activations[i - 1]

        w = np.random.randn(node_num, node_num) * val
        print(val)
        z = np.dot(x, w)
        a = ReLU(z)
        activations[i] = a

    plt.figure(key)
    plt.xlabel(key)

    for i, a in activations.items():
        plt.subplot(1, len(activations), i + 1)
        plt.title(str(i + 1) + "-layer")
        plt.hist(a.flatten(), 30, range=(0, 1))

    activations = {}


plt.show()

在这里插入图片描述

观察实验结果可知,当“std = 0.01”时,各层的激活值非常小 A。神经网 络上传递的是非常小的值,说明逆向传播时权重的梯度也同样很小。这是很 严重的问题,实际上学习基本上没有进展。

接下来是初始值为Xavier初始值时的结果。在这种情况下,随着层的加深, 偏向一点点变大。实际上,层加深后,激活值的偏向变大,学习时会出现梯 度消失的问题。而当初始值为He初始值时,各层中分布的广度相同。由于 即便层加深,数据的广度也能保持不变,因此逆向传播时,也会传递合适的值。 总结一下,当激活函数使用ReLU时,权重初始值使用He初始值,当 激活函数为sigmoid或tanh等S型曲线函数时,初始值使用Xavier初始值。 这是目前的最佳实践

以前的章节中,我们对mnist数据集进行了识别,现在我们再次用mnist数据集做个实验,观察不同的权重初始值的赋值方法会在多大程度 上影响神经网络的学习。书上用的是五层的全连接神经网络,我们之前在误差反向传播章节做过一个三层的,拿三层的试试。以前的代码在这误差反向传播代码。我们对其中的bp_two_layer_net.py和train_bp_two_neuralnet.py进行了一下改造。不想下载的可以看这一章笔记,其中有代码误差反向传播实现mnist数据集识别

改造之后的样子如下

首先说对三层神经网络改动,之改动了初始化的参数,由原来的weight_init_std改为weight_init_std1,weight_init_std2,bp.net_layer来自误差反向传播代码,是实现的各种层,numerical_gradient是微分法求导,本例子中用不到,忽略就好。

import numpy as np
from collections import OrderedDict
from bp.net_layer import *
from deeplearning.fuction import numerical_gradient

class TwoLayerNet:
    def __init__(self,input_size,hidden_size,output_size,
                 weight_init_std1,weight_init_std2):
        #初始化权重
        self.params = {}
        self.params['W1'] = weight_init_std1*np.random.randn(input_size,hidden_size)
        self.params['b1'] = np.zeros(hidden_size)
        self.params['W2'] = weight_init_std2*np.random.randn(hidden_size,output_size)
        self.params['b2'] = np.zeros(output_size)

        """
        OrderedDict是有序字典
        """
        #生成层
        self.layers = OrderedDict()
        self.layers['Affine1'] = Affine(self.params['W1'],self.params['b1'])
        self.layers['Relu1'] = Relu()
        self.layers['Affine2'] = Affine(self.params['W2'],self.params['b2'])

        self.lastLayer = SoftMaxTithLoss()

    #有序字典的作用 体现在向前传播
    def predict(self,x):
        for layer in self.layers.values():
            x = layer.forward(x)

        return x

    #损失函数  我们以前得到的y是softmax之后的,现在的predict并没有经过softmax
    def loss(self,x,t):
        y = self.predict(x)

        return self.lastLayer.forward(y,t)

    def accuracy(self,x,t):
        y = self.predict(x)         #y获得得分情况
        y = np.argmax(y,axis = 1)   #没经过softmax y获得最高得分
        if t.ndim != 1 :            #如果t是1维 axis=1会出错,mini_batch不会是1维
            t = np.argmax(t,axis = 1)
        acuracy = np.sum((y==t)/float(x.shape[0]))

        return acuracy

    #数值微分法求梯度
    def numerical_gradient(self,x,t):
        loss_W = lambda W:self.loss(x,t)

        grads = {}
        grads['W1'] = numerical_gradient(loss_W,self.params['W1'])
        grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
        grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
        grads['b2'] = numerical_gradient(loss_W, self.params['b2'])

        return grads

    #误差反向传播求梯度
    def gradient(self,x,t):
        #forward 向前传播就是获取损失函数值的过程
        self.loss(x,t)

        #backward
        dout = 1
        dout = self.lastLayer.backward(dout)

        layers = list(self.layers.values())
        layers.reverse()

        for layer in layers:
            dout = layer.backward(dout)

        grads = {}
        grads['W1'] = self.layers['Affine1'].dW
        grads['b1'] = self.layers['Affine1'].db
        grads['W2'] = self.layers['Affine2'].dW
        grads['b2'] = self.layers['Affine2'].db

        return grads

然后我们开始训练出三个网络并且把图画在上面,我写了大量注释,相信如果你看过误差反向传播的章节或者了解误差反向传播会很容易理解。

from bp_two_layer_net_adaptive import TwoLayerNet
import numpy as np
from deeplearning.mnist import load_mnist
import matplotlib.pyplot as plt


(x_train,t_train),(x_test,t_test) = load_mnist(normalize=True,one_hot_label=True)

# 不同权重初始值的三个神经网络
network_std = TwoLayerNet(input_size=784,hidden_size=50,output_size=10,
                          weight_init_std1=0.01,weight_init_std2=0.01)
network_Xavier = TwoLayerNet(input_size=784,hidden_size=50,output_size=10,
                             weight_init_std1=1/np.sqrt(784),weight_init_std2=1/np.sqrt(50))
network_He = TwoLayerNet(input_size=784,hidden_size=50,output_size=10,
                         weight_init_std1=np.sqrt(2/784),weight_init_std2=np.sqrt(2/50))

my_network = {"std=0.01":network_std,"Xavier":network_Xavier,"He":network_He}




# 记录损失函数值的我们需要三个,毕竟有三个网络
train_loss_list_std = []
train_loss_list_Xavier = []
train_loss_list_He = []

my_train_loss_list = {"std=0.01":train_loss_list_std,"Xavier":train_loss_list_Xavier,"He":train_loss_list_He}

#这两个也用不到,我们画出损失函数图就好了
#train_acc_list = []
#test_acc_list = []

#计算正确率的我们用不到
#iter_per_epoch = max(train_size/batch_size,1)

#注意循环用net_key是因为里面有个循环用的key,不能一样奥
for net_key,network in my_network.items():

    iters_num = 2000
    #这里改为2000
    train_size = x_train.shape[0]
    batch_size = 100
    learning_rate = 0.1

    for i in range(iters_num):
        batch_mask = np.random.choice(train_size, batch_size)
        x_batch = x_train[batch_mask]
        t_batch = t_train[batch_mask]

        # 反向误差获取梯度
        grad = network.gradient(x_batch, t_batch)

        # 更新梯度
        for key in ('W1', 'b1', 'W2', 'b2'):
            network.params[key] -= learning_rate * grad[key]

        loss = network.loss(x_batch, t_batch)
        #train_loss_list.append(loss)
        my_train_loss_list[net_key].append(loss)

    print("网络"+net_key+"已经训练结束")

    """ 计算正确率的部分我们不需要注释掉
        if i % iter_per_epoch == 0:
        train_acc = network.accuracy(x_train,t_train)
        test_acc = network.accuracy(x_test,t_test)
          train_acc_list.append(train_acc)
        test_acc_list.append(test_acc)

        print(train_acc,test_acc)"""

print("三个网络训练结束,开始画图:")
#画图之前我们来修饰一下得到的损失函数列表,为了使结果表现得清楚
#我们隔25位取一个数据,避免数据重叠
for key in my_train_loss_list.keys():
    my_train_loss_list[key] = np.array(my_train_loss_list[key])
    b = np.arange(0, 2000, 25)
    my_train_loss_list[key]=my_train_loss_list[key][b]
    print(my_train_loss_list[key].shape)

#用我们得到的三个损失函数值变化的列表画图
plt.figure()


#线条的样式
markers = {'std=0.01': 'o', 'Xavier': 's', 'He': 'D'}
#x都是一样的,选谁的都没关系
x = np.arange(len(my_train_loss_list['Xavier']))
print(x)

for key,train_loss_list in my_train_loss_list.items():
    plt.plot(x,train_loss_list,label=key,marker=markers[key])





plt.xlabel("mini_batch*25")
plt.ylabel("loss")
plt.legend()

"""
测试数据和训练数据正确率我们注释掉用不到
f2 = plt.figure()
x2 = np.arange(len(train_acc_list))

y1 = np.array(train_acc_list)
y2 = np.array(test_acc_list)

plt.xlabel("mini_batch")
plt.ylabel("accuracy")
plt.plot(x2,y1,label="train data")
plt.plot(x2,y2,linestyle="--",label="test data")
plt.legend()
"""
plt.show()

在这里插入图片描述

我们虽然处理过损失函数列表了,隔了25位取一个值,但是似乎还是不容易看清,我们接下来上一点“魔法”。

import numpy as np

def smooth_curve(x):
    """分段光滑曲线,用于使损失函数的图形变圆滑"""
    
    window_len = 11
    s = np.r_[x[window_len-1:0:-1], x, x[-1:-window_len:-1]]
    w = np.kaiser(window_len, 2)
    y = np.convolve(w/w.sum(), s, mode='valid')
    return y[5:len(y)-5]

然后稍微修改一下训练代码,只加了一个smooth_curve函数,至于分段平滑曲线的理论我们暂时不需要了解,用到的时候直接copy。

for key,train_loss_list in my_train_loss_list.items():
    plt.plot(x,smooth_curve(train_loss_list),label=key,marker=markers[key])

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-60tCxq8a-1644837504669)(六、神经网络学习的相关技巧.assets/图6-25.png)]

我们的曲线变得圆滑多了,其实容易看出,Xavier和he初始值要比0.01作为初始值好很多了,He比Xavier貌似好那么一点点。可是别忘了我们这里是三层神经网络,输入层784,隐藏层50,输出层10,这个神经网络还太小了,如果我们换成5层的神经网络呢?书上给的例子如下图。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KPVIng5n-1644837504669)(六、神经网络学习的相关技巧.assets/图6-26.png)]

这个实验中,神经网络有5层,每层有100个神经元,激活函数使用的 是ReLU。从上图的结果可知,std = 0.01时完全无法进行学习。这和刚 才观察到的激活值的分布一样,是因为正向传播中传递的值很小(集中在0 附近的数据)。因此,逆向传播时求到的梯度也很小,权重几乎不进行更新。 相反,当权重初始值为Xavier初始值和He初始值时,学习进行得很顺利。 并且,我们发现He初始值时的学习进度更快一些。

  • 6
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值