深度学习中权重初始化的重要性
深度学习中权重初始化的重要性
深度学习模型的权重初始化是一个容易被忽视但至关重要的步骤。不当的权重初始化可能导致模型训练失败或训练速度缓慢。本文将通过理论分析和实验验证,深入探讨不同权重初始化方法对模型训练的影响。
为什么需要权重初始化?
在深度学习中,权重初始化的方式对模型的训练效果有着重要影响。如果权重初始化不当,可能会导致以下问题:
- 梯度消失或梯度爆炸:如果权重值设置为全零或完全相同的值,在误差反向传播过程中,所有权重都会进行相同的更新,导致梯度消失或梯度爆炸。
- 表现力受限:如果权重初始化过于集中,会导致神经元输出趋于相同,从而限制模型的表现力。
不同权重初始化方法的比较
1. 高斯分布初始化
最简单的权重初始化方法是使用均值为0、标准差为0.01的高斯分布:
np.random.randn(10, 100) * 0.01
但是,这种初始化方法存在以下问题:
- 当使用Sigmoid激活函数时,随着网络层数的增加,激活值会越来越集中到0和1,导致梯度消失。
- 当使用ReLU激活函数时,如果初始化值过小,会导致神经元输出趋于相同,限制模型的表现力。
2. Xavier初始化
Xavier初始化是一种更适合Sigmoid激活函数的权重初始化方法。假设前一层的节点数是n,则初始权重使用均值为0、标准差为1/√n的高斯分布:
w = np.random.randn(node_num, node_num) * 1 / np.sqrt(n)
这种初始化方法可以保持各层激活值的分布具有适当的广度,避免梯度消失问题。
3. Kaiming初始化
对于ReLU激活函数,Kaiming初始化更为适用。Kaiming初始化在Xavier初始化的基础上,将标准差乘以√2:
w = np.random.randn(node_num, node_num) * np.sqrt(2 / node_num)
这种初始化方法可以增加ReLU激活函数的输出范围,避免梯度消失问题。
实验验证
为了验证不同初始化方法的效果,我们使用一个五层神经网络进行实验。激活函数使用Sigmoid函数,用直方图画出每层的激活值的数据分布:
import matplotlib.pyplot as plt
def sigmoid(x):
return 1 / (1 + np.exp(-x))
x = np.random.randn(1000, 100) # 1000个数据,每个数据100维
node_num = 100 # 隐藏层的节点数
hidden_layer_size = 5 # 隐藏层的数量
activations = {} # 激活值的结果
for i in range(hidden_layer_size):
# i=0的时候先计算激活值,往后每次的输入值x都是上一次激活值结果
if i != 0:
x = activations[i - 1]
w = np.random.randn(node_num, node_num) * 1 # 权重初始化
z = np.dot(x, w)
a = sigmoid(z) # sigmoid函数
activations[i] = a
高斯分布初始化(标准差为1)
可以看到,随着层数加深,越来越多的激活值集中到0和1,导致梯度消失问题。
Xavier初始化
Xavier初始化可以保持各层激活值的分布具有适当的广度,但随着层数增加,激活值会越来越靠近0。
Kaiming初始化
Kaiming初始化可以保持激活值均匀分布在0~1之间,适合通过反向传播进行学习。
实际应用中的效果对比
为了进一步验证不同初始化方法的效果,我们使用一个线性不可分数据集进行分类实验。数据集生成代码如下:
import numpy as np
import matplotlib.pyplot as plt
# 构建非线性可分数据集
def create_dataset():
np.random.seed(1)
m = 400 # 数据量
N = int(m / 2) # 每一类数据的个数
dim = 2 # 数据维度
X = np.zeros((m, dim))
Y = np.zeros((m, 1), dtype='uint8')
a = 4
# 生成数据
for j in range(2):
ix = range(N * j, N * (j + 1))
t = np.linspace(j * 3.12, (j + 1) * 3.12, N) + np.random.randn(N) * 0.2
r = a * np.sin(4 * t) + np.random.randn(N) * 0.2
X[ix] = np.c_[r * np.sin(t), r * np.cos(t)]
Y[ix] = j
X = X.T
Y = Y.T
return X, Y
X, Y = create_dataset()
X = X.T
Y = Y.T
print(Y.shape)
print(X.shape)
plt.scatter(X[:, 0], X[:, 1], c=Y, cmap=plt.cm.Paired) # 画出数据
生成的图像如下:
我们使用一个简单的多层感知机进行分类实验,分别使用(0,0.01)的高斯分布和Xavier初始化进行权重初始化:
(0,0.01)高斯分布初始化
def initialize_parameters(n, num_hidden, m):
# 初始化参数
w1 = np.random.randn(num_hidden, n) * 0.01 # 从输入到隐藏层权重,随机初始化
b1 = np.zeros((num_hidden, 1)) # 从输入到隐藏层偏置,初始化为零
w2 = np.random.randn(m, num_hidden) * 0.01 # 从隐藏层到输出层权重,随机初始化
b2 = np.zeros((m, 1)) # 从隐藏层到输出层权重,初始化为零
parameters = {'w1': w1, 'w2': w2, 'b1': b1, 'b2': b2}
return parameters
训练损失值如下:
================ 0.6931125167719424
================ 0.6929599436150587
================ 0.6928419798609118
================ 0.6927533221282552
================ 0.6926665006259728
================ 0.6925442899224475
================ 0.6923418054740101
================ 0.6919987185837877
================ 0.6914315483776486
================ 0.6905487939721282
可以看到损失值几乎不下降,无法训练。
Xavier初始化
def initialize_parameters(n, num_hidden, m):
# 初始化参数
w1 = np.random.randn(num_hidden, n) / np.sqrt(n) # 从输入到隐藏层权重,Xavier初始化
b1 = np.zeros((num_hidden, 1)) # 从输入到隐藏层偏置,初始化为零
w2 = np.random.randn(m, num_hidden) / np.sqrt(num_hidden) # 从隐藏层到输出层权重,Xavier初始化
b2 = np.zeros((m, 1)) # 从隐藏层到输出层权重,初始化为零
parameters = {'w1': w1, 'w2': w2, 'b1': b1, 'b2': b2}
return parameters
训练损失值如下:
================ 0.7088135432785352
================ 0.6196813019938332
================ 0.5770935283638372
================ 0.5129738885023197
================ 0.46283119618617713
================ 0.4218237851915789
================ 0.3914602876997484
================ 0.3705175596789437
================ 0.354306018670427
================ 0.3409172900251636
可以看到,使用Xavier初始化后,训练损失值平稳下降,最终分类效果较好。
总结
权重初始化是深度学习中一个容易被忽视但至关重要的步骤。不同的初始化方法对模型的训练效果有着显著影响。在实际应用中,建议根据所使用的激活函数选择合适的初始化方法:
- 对于Sigmoid激活函数,推荐使用Xavier初始化。
- 对于ReLU激活函数,推荐使用Kaiming初始化。
虽然现代深度学习框架(如PyTorch)通常会提供默认的权重初始化方法,但在某些特殊场景下,手动选择合适的初始化方法仍然非常重要。