- 人工智能
- 深度学习
- 机器学习
- 教程
【免费下载链接】d2l-zh
《动手学深度学习》:面向中文读者、能运行、可讨论。中英文版被70多个国家的500多所大学用于教学。
延后初始化是深度学习框架中一项"看不见但离不开"的底层机制:它允许开发者先定义网络架构、暂不指定输入维度,直到第一批数据真正流过模型时才自动推断并完成所有参数形状的确定与初始化。本文基于《动手学深度学习》(d2l-zh)深度学习计算章节中的《延后初始化》一节展开,结合本仓库 d2l 工具库中训练循环的真实调用,从"框架如何推迟初始化"、"如何验证与触发初始化"到"在 CNN/RNN 等场景中的工程价值"逐层拆解,读完即可掌握在多层感知机、卷积网络与循环网络中使用延后初始化写出更灵活模型代码的方法。
为什么需要延后初始化
回顾此前章节的建模过程,你会发现我们其实做了几件"看起来不该成立"的事情:
- 定义了网络架构,却没有指定输入维度;
- 添加层时,没有显式指定前一层的输出维度;
- 甚至在信息不足以确定模型参数总数时,就已经调用了参数初始化。
直觉上,这些代码应该报错——因为框架无法预知网络的输入维度。但代码偏偏能跑通,秘密就在于框架采用了延后初始化(defers initialization)策略:它把真正分配参数内存的动作推迟到第一次向模型传递数据时,届时再根据实际数据形状动态推断每一层的大小。
这一机制在后续章节会变得尤其重要:使用卷积神经网络时,输入维度(即图像分辨率)会逐层影响后续各层的维度。如果编写代码时无需提前知道维度就能设置参数,那么定义和修改模型的任务都会被大大简化。要理解这一点,需要先深入初始化机制的内部工作方式。
实例化一个尚未初始化的网络
首先实例化一个两层多层感知机。在 MXNet 中,我们用nn.Sequential依次添加两个全连接层(隐藏层 256 个单元、ReLU 激活,输出层 10 个单元):
from mxnet import init, np, npx from mxnet.gluon import nn npx.set_np() def get_net(): net = nn.Sequential() net.add(nn.Dense(256, activation='relu')) net.add(nn.Dense(10)) return net net = get_net()在 TensorFlow(Keras)中对应的写法是:
#@tab tensorflow import tensorflow as tf net = tf.keras.models.Sequential([ tf.keras.layers.Dense(256, activation=tf.nn.relu), tf.keras.layers.Dense(10), ])此刻输入维数未知,网络不可能知道输入层权重的形状,因此框架尚未初始化任何参数。我们可以通过访问参数来确认这一点:
print(net.collect_params) print(net.collect_params())#@tab tensorflow [net.layers[i].get_weights() for i in range(len(net.layers))]MXNet 视角:-1占位与延迟的真实含义
在 MXNet 中,尽管参数对象已经存在,但每一层的输入维度被记录为-1——这是 MXNet 用来表示"参数维度仍然未知"的特殊值。此时如果强行访问net[0].weight.data(),会触发运行时错误,提示必须先初始化网络才能访问参数。
当我们调用initialize时发生了什么?
net.initialize() net.collect_params()结果是一切照旧、没有任何改变。这说明在输入维度未知时,initialize并不会真正初始化参数,它只是在 MXNet 内部登记了"我们希望初始化参数"这一意图(并可选地声明使用哪种分布)。真正的分配动作被挂起,等待数据到来。
TensorFlow 视角:权重对象存在但为空
TensorFlow 侧的现象对称:每一层的层对象都存在,但权重张量为空。此时调用net.get_weights()会抛出异常,因为权重尚未完成初始化。
传递数据触发真正的初始化
接下来,让第一批数据通过网络,迫使框架完成参数初始化:
X = np.random.uniform(size=(2, 20)) net(X) net.collect_params()#@tab tensorflow X = tf.random.uniform((2, 20)) net(X) [w.shape for w in net.get_weights()]一旦输入维度(本例为 20)已知,框架就能把这个值代入,识别出第一层权重矩阵的形状(256×20)。识别出第一层形状后,框架继续处理第二层,依此类推,沿着计算图一路向后,直到所有层的形状都确定。需要特别说明的是:本例中只有第一层真正需要延后初始化,但框架依然采用顺序初始化的方式处理所有层。等到全部参数形状已知,框架才真正初始化参数并分配内存。
参数对象、值与梯度的关系
结合同一章节的参数管理一节可以更完整地理解这一过程:在 MXNet 中,参数是包含值、梯度与额外信息的复合对象。延后初始化之所以"能拖则拖",正是因为它需要weight.data()指向一块真实存在的内存;在维度未知时,参数对象虽有名称和结构(例如dense0_weight、dense0_bias),却没有可用的数据缓冲区。参数管理章节中展示的net.collect_params()['dense1_bias'].data()、net[0].weight.grad()等访问方式,都要求初始化已经完成才能拿到有效数据。
从源码看延后初始化在真实训练循环中的位置
延后初始化不只是理论机制,它在本仓库的工具库 d2l/mxnet.py 的训练函数中被反复使用。以卷积网络训练函数train_ch6为例(d2l/mxnet.py):
def train_ch6(net, train_iter, test_iter, num_epochs, lr, device): """用GPU训练模型(在第六章定义)""" net.initialize(force_reinit=True, ctx=device, init=init.Xavier()) loss = gluon.loss.SoftmaxCrossEntropyLoss() ...这里net.initialize(force_reinit=True, ctx=device, init=init.Xavier())在数据到达之前被调用:init.Xavier()声明了初始化分布(Xavier 初始化),ctx=device指定参数将被分配到的设备,force_reinit=True则保证即使参数此前已被初始化过,也会强制重新初始化。而train_ch6的调用方(如 LeNet 训练)传入的net只定义了层结构、并未显式给出输入尺寸,所以这正是一次典型的延后初始化:声明意图在前,实际分配参数要等到训练循环里第一次执行y_hat = net(X)(d2l/mxnet.py)时才完成。
同理,在循环神经网络训练函数train_ch8(d2l/mxnet.py)中:
if isinstance(net, gluon.Block): net.initialize(ctx=device, force_reinit=True, init=init.Normal(0.01))RNNModel的forward里先执行X = npx.one_hot(inputs.T, self.vocab_size),把形状为(时间步数, 批量大小)的输入转成 one-hot 表示(d2l/mxnet.py),此时dense层的输入维度(时间步数 × 批量大小)才第一次成为已知量——这正是延后初始化在动态序列长度场景下的典型应用。
从源码结构看,可以提炼出延后初始化在工程中的三个关键配合参数:
| 参数 | 作用 | 典型取值 |
|---|---|---|
init | 声明初始化分布(在数据到达后生效) | init.Xavier()、init.Normal(sigma=0.01)、init.Constant(1) |
ctx | 声明参数分配到的设备(CPU/GPU) | device(如mx.gpu(0)) |
force_reinit | 是否强制覆盖已有初始化结果 | 训练脚本中通常设为True |
延后初始化为什么"越用越方便"
延后初始化带来的核心便利有三点:
- 架构定义与数据维度解耦:定义模型时无须关心输入形状,代码可以写得更通用。尤其对卷积网络,图像分辨率一旦变化,后续每一层的维数都会连锁改变,延后初始化让这类修改不再需要同步更新层定义。
- 消除一类常见错误:手工推算每一层的输入/输出维度极易出错(漏算批量维度、忘记展平等),把形状推断交给框架在首次前向时自动完成,从机制上减少维度不匹配问题。
- 支持动态输入:在自然语言处理中,句子长度往往可变。只要在首次前向时框架能推断出形状,延后初始化就允许同一模型接受不同尺寸的输入。
小结
- 延后初始化让框架能够自动推断参数形状,从而简化模型架构的定义与修改,并消除一类常见的维度错误。
- 在 MXNet 中,维度未知时参数被标记为 -1,
initialize仅登记初始化意图;在 TensorFlow 中,权重对象存在但内容为空。 - 通过向模型传递数据(执行一次前向),可以触发框架最终完成参数初始化。
- 实际训练循环(如本仓库 d2l/mxnet.py 的
train_ch6)普遍采用"先声明初始化意图、数据到来时再真正初始化"的模式,init、ctx、force_reinit三个参数共同决定了初始化如何执行。
练习
- 如果指定了第一层的输入尺寸,但没有指定后续层的尺寸,会发生什么?是否立即进行初始化?
- 如果指定了不匹配的维度会发生什么?
- 如果输入具有不同的维度,需要做什么?提示:查看参数管理一节中的参数绑定相关内容(在 MXNet 中可通过
params=shared.params让多个层共享同一份参数,从而支持变长输入时的权重复用)。
- 人工智能
- 深度学习
- 机器学习
- 教程
【免费下载链接】d2l-zh
《动手学深度学习》:面向中文读者、能运行、可讨论。中英文版被70多个国家的500多所大学用于教学。
相关推荐
《动手学深度学习》延后初始化(Deferred Initialization)深度解析:框架如何动态推断网络参数形状
《动手学深度学习》延后初始化(Deferred Initialization)深度解析:框架如何动态推断网络参数形状 延后初始化(Deferred Initia
人工智能深度学习机器学习教程D2L 延迟初始化(Lazy Initialization)深入解析:深度学习框架如何自动推断参数形状
D2L 延迟初始化(Lazy Initialization)深入解析:深度学习框架如何自动推断参数形状 延迟初始化(Lazy Initialization)是《
文档教程人工智能深度学习NLP计算机视觉强化学习《动手学深度学习》参数管理实战:模型参数的访问、初始化与共享
《动手学深度学习》参数管理实战:模型参数的访问、初始化与共享 选择好网络架构、设置完超参数之后,训练的本质就是不断调整模型参数以最小化损失函数;训练结束后,我们
人工智能深度学习机器学习教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考