想象你走进一家刻章店。在传统框架的世界里,店里藏着一台“自动盖章机”——它不接受任何人的印章,只按自己内部的节奏乱盖。你今天来盖一批章,明天想重盖一遍?对不起,机器内部的齿轮早不知转了多少圈,查无可查。更糟的是,十个伙计同时要用它,盖出的章混作一团,谁也没法说清哪个是自己的。而JAX的刻章店完全不同:每位顾客进门先报一个“种子编号”,师傅当场给你刻出一枚专属印章。这枚印章的图案永久固定——同一种子,任何时候、任何地方刻出的印章都一模一样。想要新的随机数?把印章放进店里的“分裂机”,咔嗒一声,一印变两印,从此各盖各的。你要用随机数,就把自己的印章递过去;用完拿回来。店里没有任何藏在暗处的机器,每一枚印章都明明白白躺在你手里——这正是JAX的PRNG(伪随机数生成器)显式随机数管理机制。

岗位关联

搞懂显式随机数管理,你才能在JAX里正确使用Dropout、参数初始化等随机操作,写出可复现、可并行、可编译的专业代码——这是函数式深度学习框架的必修课,也是你区别于普通框架用户的硬实力。

目录
  1. 可视化演示
  2. 全局随机之痛
  3. PRNGKey创建
  4. split分裂机制
  5. 显式传递之道
  6. 可复现性保证
  7. 线程安全实践
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

动态演示JAX中PRNG显式随机数管的核心机制,通过概览/流程/对比/趋势四种模式展示关键概念与交互流程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

JAX中PRNG显式随机数管:显式随机 显式随机密钥传递设计哲学 PRNGKey创建与密钥生命周 核心机制可视化 JAX中PRNG显式随机数管整体架构与关键组件

全局随机之痛

在传统框架的“刻章店”里,有一台人人共用、藏在后厨的自动盖章机。它不接受任何人的印章,只按自己内部的节奏乱盖。你今天来盖一批章,明天想重盖一遍?对不起,机器内部的齿轮早就不知道转了几万圈,查无可查。更糟的是,十个伙计同时要用这台机器,各自盖出的章混在一起,谁也没法说清哪个是自己要的。

这台“自动盖章机”,就是NumPy等传统框架的全局随机状态。你写一行 np.random.rand(),代码表面很干净,但背后其实偷偷做了两件事:读取当前隐藏状态,然后把它推进到下一个位置。也就是说,同样的代码,因为之前被调用过多少次、被谁调用过,结果可能完全不同。这在调试、测试、复现实验时让人抓狂——你昨天跑出一个漂亮的实验结果,今天想原样复现,却发现数字全变了。

JAX是纯函数式框架,jit编译和自动微分都要求函数是“纯净的”——相同输入必定产生相同输出。全局随机状态恰恰是纯净性的天敌:如果随机数偷偷依赖外部状态,JIT就无法把它编译成确定的计算图,并行时也无法保证各个分支互不干扰。所以JAX必须把随机数从“暗处”请到“明处”,这就是后面所有故事的起点。

把自动盖章机请出店门,是JAX的第一项改革。它告诉我们:随机性本身没有错,错的是“藏在暗处”。只要把它做成明明白白的器物、交到每一位使用者手里,随机数就能既随机、又可复现、还安全。

outt=g(statet),statet+1=T(statet)\text{out}_t = g(\text{state}_t), \qquad \text{state}_{t+1} = T(\text{state}_t)
这个公式描述了传统全局随机的工作方式:每次调用都依赖一个“隐藏状态”,调用完后状态又悄悄变化。你看到的是随机数,看不到的是背后不断翻滚的状态齿轮。
  1. state_t第t次调用时的全局随机状态,好比自动盖章机内部的齿轮位置。它不在你的代码里,而是躲在框架的全局命名空间中。
    statet:第 t 次调用时的隐藏状态\text{state}_t : \text{第 } t \text{ 次调用时的隐藏状态}
  2. g(state_t)把当前状态“翻译”成一个随机数输出,也就是机器盖下的那枚章。同一个状态永远得到同一个输出,问题在于状态本身在变。
    g(statet):状态 ↦ 随机数输出g(\text{state}_t) : \text{状态 }\mapsto\text{ 随机数输出}
  3. T(state_t)每次调用后,状态更新函数把状态偷偷推到下一个位置,如同齿轮转过一格。这就是“每次调用结果都不同”的根源。
    statet+1=T(statet):调用后状态自动推进\text{state}_{t+1} = T(\text{state}_t) : \text{调用后状态自动推进}
  4. 复现困境想要重放第t步的结果,必须重建state_t,而它依赖从第0步到第t-1步的全部历史——几乎不可能做到。
    statet=T t(state0):历史越长越难回溯\text{state}_t = T^{\,t}(\text{state}_0) : \text{历史越长越难回溯}

PRNGKey创建

新刻章店的规矩是:每位顾客先报一个“种子编号”,比如42。师傅在案前一番雕琢,递给你一枚崭新的印章K。这枚印章的图案从此固定——只要种子是42,不管哪家店、不管什么时候刻出的K,图案都一模一样。

这个“刻章工序”就是 jax.random.PRNGKey(seed)。seed是一个整数(也可以是整数数组),代表“图样编号”;产出的是一个PRNGKey对象——注意,它不是一个简单的整数,而是一个形状为 (2,) 的uint32数组。每一枚“印章”都承载着生成一整条随机数序列的全部信息,是整个随机过程的唯一源头。

为什么设计成数组而不是标量?因为key在JAX中要参与各种变换(jit、vmap、pmap),数组形态让它可以被放进计算图、被设备内存管理。你可以把key当作普通参数一样传来传去,它会跟着代码一起流动。你完全不需要关心数组内部是什么——只需要记住三点:它是随机性的唯一源头;用的时候把它递出去;用完别弄丢。

拿到印章的那一刻,你心里踏实了:所有随机性都握在自己手里,不再依赖店里的任何暗箱。换一个新种子,就等于换一枚新印章,自然得到一条全新的随机数流——但这条新流同样可以复现。

key=PRNGKey(seed)\text{key} = \text{PRNGKey}(\text{seed})
这一行代码,就是“刻章”:你报一个种子编号,JAX还你一枚专属印章。同一编号永远得到同一枚章,这是后面一切可复现性的地基。
  1. seed你填报的种子编号,例如42。它是一个普通整数(或整数数组),好比刻章店图样册上的编号。
    seed=42\text{seed} = 42
  2. PRNGKeyJAX的刻章函数,把种子“拓印”成一枚密钥。同一seed永远产出同一key。
    PRNGKey:seed↦key\text{PRNGKey} : \text{seed} \mapsto \text{key}
  3. key产出的印章,本质是形状为(2,)的uint32数组。它携带一整条随机数流的全部信息。
    key∈Z232×Z232\text{key} \in \mathbb{Z}_{2^{32}} \times \mathbb{Z}_{2^{32}}

split分裂机制

你手里只有一枚印章K,可实验需要几十个互不相同的随机序列——Dropout要一个、权重初始化要一个、数据打乱还要一个……总不能一直用同一枚章盖吧?别急,店里有一台“分裂机”:把K放进去,咔嗒一声,出来两枚(或更多)崭新的印章,每枚图案都不同,彼此毫无关联。

这台分裂机就是 jax.random.split(key, n)。它是一个纯函数:输入旧key,输出n个新key。旧的key没有任何改变——函数式编程讲究“不破坏旧物”。每个新key都携带着一条独立、可复现的随机数流。理论上,子key是从父key通过精心设计的算法派生出来的,它们之间的随机序列统计独立。

split的独立性至关重要。正因为子key互不相关,你才能放心地把它们分给不同模块、不同线程、不同设备。需要多少个就split多少,需要递归就继续split——一张父章繁衍出一个无穷的“印章家族”。这就像一棵树的树枝不断分叉,每条枝丫都自成一体,互不干扰。

有了分裂机,你不再问“随机数从哪来”,而是问“下一枚印章怎么分”——这就是显式管理的精髓。每一枚子印章都清清楚楚地摆在桌上,谁用了哪一枚、它从哪枚母章来的,全都有迹可循。

k1,k2,…,kn=split(k,n)k_1, k_2, \ldots, k_n = \text{split}(k, n)
分裂机一开动,一印化万印。你不再操心“随机数从哪儿来”,只关心“下一枚印章怎么分”——这就是显式管理的核心思路。
  1. k母印章,需要被分裂的原始key。它来自 PRNGKey(seed)。
    k=PRNGKey(42)k = \text{PRNGKey}(42)
  2. split分裂函数,强调它是纯函数——不会修改k,只是“看着”k刻出新章。
    split:(k,n)↦(k1,k2,…,kn)\text{split} : (k, n) \mapsto (k_1, k_2, \ldots, k_n)
  3. n子印章数量,想要几个就传几个。
    n=2,3,…n = 2, 3, \ldots
  4. k_1 到 k_nn枚互不相关的子印章,每一枚都代表一条独立随机流,且可以继续分裂。

显式传递之道

在JAX的刻章店里,每位伙计都必须亲手拿着自己的印章办事。需要随机数?把手里的章递过去,师傅按章取数,盖完还给你。没有人替你保管印章,店里也没有公用的章——这就是“显式传递”。

在代码里,这意味着key必须作为参数显式地传给每个随机函数:jax.random.normal(key, shape)。函数内部不会去任何“全局仓库”取随机数,它只认你递进来的key。没有全局状态,没有隐藏依赖,随机性来源在代码中一目了然。

这种设计的直接好处是代码的可读性和可组合性。你看到一行 random.normal(k, (3, 3)),马上知道它的随机性来源是k;想复现,就顺着k的来历一路查下去。在JAX的jit编译环境中,一切都要是纯函数,key作为参数让随机操作变得“清白”,可以被安全地编译、向量化、并行化——这正是NumPy的全局随机做不到的。

每笔随机业务都有章可查、有据可依。代码读起来就像店里的账本:每一枚印章从哪来、到哪去、盖出了什么,一目了然。这种“清白感”是函数式框架能给开发者的最大安全感。

out=normal(k,shape)\text{out} = \text{normal}(k, \text{shape})
所有JAX随机函数的签名都长这样:第一个参数是密钥,后面是形状。密钥显式传入,输出只由密钥和形状决定。
  1. k显式传入的密钥,作为随机性的唯一来源。它可以是主key,也可以是split派生的子key。
    k:唯一的随机性来源k : \text{唯一的随机性来源}
  2. normal正态分布采样函数。它只认参数,绝不碰任何全局状态。
    normal:(k,shape)↦out\text{normal} : (k, \text{shape}) \mapsto \text{out}
  3. shape输出张量的形状,例如(4, 4)表示生成一个4×4的矩阵。
    shape=(4,4)\text{shape} = (4, 4)
  4. out结果张量,完全由(k, shape)决定,和其他任何代码的执行历史无关。
    out=normal(k,shape)\text{out} = \text{normal}(k, \text{shape})

可复现性保证

印章最迷人的地方在于:同一枚章,今天盖和明年盖,图案一模一样。有了它,你的实验就有了“指纹”——只要种子是42,整场实验从头到尾可以一字不差地重演。

在JAX里,同key同结果的保证是绝对的。拿 key = PRNGKey(42) 跑一遍训练脚本,记录下loss曲线;把脚本原样扔到另一台机器上,同样的环境,再跑一遍,loss曲线完全重合。这在调参、写论文、团队协作中是巨大的优势——你不再需要靠运气复现某个结果。

可复现的前提是“每次随机都用新key”。如果你图省事,把同一个key传给normal两次,那么两次输出完全相同——这往往不是你要的“再来一批随机数”。正确姿势是:用split一次性分出本次需要的所有子key,每个随机操作各用一枚,用完即弃。这样整条随机链完全确定,又不会出现重复样本。记住黄金法则:先split,再使用。

可复现性就是科学实验的“留档”。印下的每一道痕迹都能随时重演,这是显式管理赠予你的一份大礼——它让深度学习研究从“玄学调参”变成“工程科学”。

normal(k)⏟第1次调用=normal(k)⏟第2次调用\underbrace{\text{normal}(k)}_{\text{第1次调用}} = \underbrace{\text{normal}(k)}_{\text{第2次调用}}
同一个印章,盖一百次也是同一个印记。想要“新”的随机数,必须先分裂出新印章。
  1. normal(k)用印章k取一次正态随机数,输出记为x。
    x=normal(k)x = \text{normal}(k)
  2. 再次调用normal(k)只要k没变,输出仍然严格等于x,一字不差。
    x′=normal(k)  ⇒  x′=xx' = \text{normal}(k) \;\Rightarrow\; x' = x
  3. 正确用法若想获得新的随机数,应当先分裂再取样。
    knew=split(k)[0]⇒normal(knew)≠normal(k)k_{\text{new}} = \text{split}(k)[0] \quad\Rightarrow\quad \text{normal}(k_{\text{new}}) \neq \text{normal}(k)

线程安全实践

刻章店迎来大单——十个伙计要同时给一百个客户刻章。老店那台自动盖章机前早就排起长队,加锁、竞争、互相等待,乱成一锅粥。新店呢?每位伙计本来就揣着自己的印章,各盖各的,零争抢、零等待。

JAX中做数据并行训练,经常要用 pmap 把计算分发到多个设备,或用 vmap 做向量化批量计算。由于随机操作不依赖任何共享状态,每个分支只需要拿到自己的子key,就能安全地并行运行,天然线程安全。这就是显式管理在并发场景下的巨大红利。

具体做法是:从主key里split出n个子key(n为设备或分支数),再把子key分发给各个分支。每个分支的随机数流互不重叠,且整体结果可复现。JAX的random模块还提供了 fold_in 等工具处理更复杂的场景,但核心思路不变:并行前先分章,各章各用。这也是Dropout等随机操作能在JAX的并行训练中正确工作的原因。

并行世界里的秩序,不靠锁,靠“各印各用”。显式随机管理让分布式训练中的随机性井井有条——每个worker都清楚自己的随机源,整个训练过程既可复现又可扩展,这是传统全局随机状态永远无法企及的境界。

karr=split(k,n),第 i 个分支使用 karr[i]k_{\text{arr}} = \text{split}(k, n), \qquad \text{第 } i \text{ 个分支使用 } k_{\text{arr}}[i]
并行世界里,秩序不靠锁,靠“各印各用”。每个分支拿着自己的印章,谁也不需要等谁。
  1. k主印章,由全局种子生成的key。它是整个并行计算的随机性总源头。
    k=PRNGKey(42)k = \text{PRNGKey}(42)
  2. n并行分支总数,比如GPU数量,或者batch内的样本数。
    n=设备数或分支数n = \text{设备数或分支数}
  3. k_arr分裂出的n枚子印章组成的一串数组,等待分发给各个分支。
    karr=[k1,k2,…,kn]k_{\text{arr}} = [k_1, k_2, \ldots, k_n]
  4. 第i个分支使用k_arr[i]每个分支独享一枚印章,分支间零共享、零冲突。
    第 i 分支随机数=normal(karr[i],…)\text{第 } i \text{ 分支随机数} = \text{normal}(k_{\text{arr}}[i], \ldots)

AI 应用场景

模型训练中的Dropout掩码

阶段:模型训练与正则化

解决的问题:Dropout每次前向传播都需要随机掩码(哪些神经元要失活)。如果随机源自全局状态,重跑实验就无法对比模型改进的效果;在JAX中必须用显式key生成掩码。

具体案例:训练一个12层Transformer,每层Dropout率为0.1。实践上,主key先split出12个子key,每层拿自己的子key生成掩码。这样既保证各层掩码随机独立,又保证整轮训练在任意时刻都能原样重放。第二天想接着调参,只要种子不变,之前的loss曲线就能完美复现。

强化学习环境的可重放模拟

阶段:环境模拟与策略评估

解决的问题:强化学习需要随机初始化环境状态,但如果无法重放初始状态,就难以公平对比不同策略的表现。

具体案例:训练机器人导航策略时,研究者用 key = PRNGKey(train_seed) 为每个episode生成初始环境。评估新策略时,用同一组子key重放完全一致的初始局面,让新旧策略在完全相同的地图上比拼,排除了环境随机性的干扰。

多卡并行数据增强

阶段:数据预处理与分布式训练

解决的问题:8卡训练时,如果各卡的数据增强共享全局随机或使用相同种子,会产生大量重复增强数据,相当于变相减小了数据规模。

具体案例:在ImageNet大规模训练中,主key先split出8个子key,每个GPU各拿一个。各卡对batch做随机裁剪、翻转、色彩抖动时互不重复,数据多样性翻倍,同时整轮训练可复现,方便后续排查问题。

Python 演示

python43 行
"""模拟JAX显式随机数管理机制:Key的创建、分裂与可复现使用"""  class PRNGKey:    """随机印章:所有随机性的显式来源,不依赖任何全局状态"""     def __init__(self, seed):        self.seed = seed % (2 ** 31)  # 把种子归一化到合法范围     def split(self, n=2):        """分裂机:一印变n印,每枚子印章图案(种子)互不相同"""        return [PRNGKey((self.seed * 31 + i * 2654435761) % (2 ** 31))                for i in range(n)]     def uniform(self, shape=(1,)):        """盖章:用这枚印章生成确定性伪随机数(线性同余法)"""        state = self.seed        nums = []        for _ in range(max(int(shape[0]), 1)):            state = (state * 1103515245 + 12345) % (2 ** 31)            nums.append(state / (2 ** 31))        return nums[0] if len(nums) == 1 else nums  if __name__ == "__main__":    # 1. 创建主密钥:等价于 jax.random.PRNGKey(42)    master = PRNGKey(42)    print("主密钥 seed =", master.seed)     # 2. 分裂出训练/测试两个子密钥:等价于 jax.random.split(key, 2)    k_train, k_test = master.split(2)    print("训练子密钥:", k_train.seed, " 测试子密钥:", k_test.seed)     # 3. 各用各的密钥取随机数:等价于 jax.random.uniform(key, shape)    train_nums = k_train.uniform((5,))    test_nums = k_test.uniform((5,))    print("训练随机流:", [round(x, 4) for x in train_nums])    print("测试随机流:", [round(x, 4) for x in test_nums])     # 4. 可复现:同一枚印章重新盖一次,字迹分毫不差    replay = PRNGKey(42).split(2)[0].uniform((5,))    assert replay == train_nums, "可复现性被破坏了!"    print("再次运行结果完全一致 -> 实验可复现!")
目的:用纯Python模拟JAX的显式随机密钥管理,展示“创建密钥 → 分裂密钥 → 用密钥取数 → 结果可复现”的完整链路。没有安装JAX也能跑通,跑通的瞬间你就理解了JAX的PRNG设计哲学。
  1. class PRNGKey:用类表示“随机印章”,所有随机性都挂在显式对象上,不碰任何全局变量。
  2. self.seed = seed % (2 ** 31)种子归一化,保证不同种子也能映射到合法范围内。
  3. def split(self, n=2)模拟JAX的split:一印变n印,每个子印章的种子互不相同。
  4. state = (state * 1103515245 + 12345) % (2 ** 31)线性同余生成器:同一state永远产生同一串伪随机数。
  5. master = PRNGKey(42)创建主密钥,等价于 jax.random.PRNGKey(42)。
  6. k_train, k_test = master.split(2)分裂出两个独立子密钥,各管各的随机流。
  7. assert replay == train_nums断言可复现性:同一母章重走一遍,结果一字不差,跑不通直接报错。

常见误区

误区很多新手以为,只要把同一个key传给normal两次,第二次就会得到“新的一批”随机数,就像 `np.random.rand()` 连续调用那样。

正解JAX的随机函数是纯函数,同一key无论调用多少次,输出都完全相同。要得到新的随机数流,必须先 split 出新key,再用新key取样——相当于先刻新章,再盖章。这是JAX与NumPy最大的思维差异。

为何常见:惯性思维——从NumPy/PyTorch的“自动推进”机制迁移过来,还没适应“随机性必须显式传递”的函数式哲学。

误区以为 `PRNGKey(42)` 返回的就是整数42,可以直接加减、比较或者当普通数字用。

正解key是形状为(2,)的uint32数组,是JAX内部专用的密钥结构。它只应该被交给随机函数或split使用,不应被当作普通整数操作。把它想成印章本身,而不是印章上刻的数字。

为何常见:PyTorch里 torch.manual_seed(42) 的“设种子”习惯太深入人心,容易把key简化理解为“一个包了壳的种子”。

误区以为split会“消耗”原key,像剪票一样,分裂后原key作废,再用就会报错。

正解split是纯函数,原key完全不会被修改,之后依旧可以正常使用。但为了随机独立性和代码清晰,实践中通常不再复用原key——就像母章虽然还能盖,但正规流程里大家都用子章。

为何常见:把key类比成“一次性票券”或“状态对象”,忽略了函数式编程“输入不变”的核心特点。

误区认为JAX只是把np.random封装了一下,“显式key”只是表面功夫,底层恐怕还是全局状态。

正解JAX从底层设计上杜绝了全局随机状态。所有随机函数都必须显式接收key,否则无法通过jit编译、无法被vmap/pmap变换。这是架构层面的决定,不是API层面的包装。

为何常见:对“隐藏状态”的疑虑来自长期以来对框架黑箱的默认接受,很难相信真的有人把它彻底设计掉了——JAX就是那个“有人”。

误区以为并行计算中所有线程/设备用同一个key,最多是结果相同,重复就重复,不影响正确性。

正解共享key会导致所有分支产生完全相同的随机数序列,不仅浪费并行算力,还会让Dropout等操作失去随机性——所有分支的神经网络变得一模一样,退化成了“单模型广播”。正确做法是从主key split出与分支数相同数量的子key。

为何常见:小规模实验里“随机数相同好像也没什么影响”的经验被错误迁移到了大规模并行场景,忽视了随机独立性在统计上的意义。

面试题

Q1. jax.random.PRNGKey(42) 返回的是什么?

单选题 · easy

  • A.一个整数值为42的种子
  • B.一个形状为(2,)的uint32数组← 正确答案
  • C.一个数值为0.42的浮点数
  • D.一串用于标记的字符42

答案:B。PRNGKey的产物是一个密钥(印章),本质是形状为(2,)的uint32数组。它不是整数、浮点数或字符串,而是JAX内部专用的密钥结构。

Q2. 同一个key传给 jax.random.normal 两次,两次结果是什么关系?

单选题 · easy

  • A.两次结果完全不同
  • B.两次结果恰好相反
  • C.两次结果完全相同← 正确答案
  • D.结果关系无法确定

答案:C。JAX随机函数是纯函数,同一key永远产生同一输出。想要不同结果,必须先split出新key。这正是“显式随机数管理”的核心特征。

Q3. JAX实现显式随机数管理的主要目的是什么?

单选题 · easy

  • A.减少随机操作的代码量
  • B.让随机数生成速度更快
  • C.实现可复现与函数式变换← 正确答案
  • D.为了兼容其他深度学习框架

答案:C。JAX的函数式架构要求函数“纯净”,全局随机状态会破坏jit编译和自动微分。显式key让随机操作变得可控、可复现、可编译。

Q4. 想从key派生3个互不相关的子密钥,正确的调用是?

单选题 · mid

  • A.直接调用key对象的split方法
  • B.调用jax.random.split(key, 3)← 正确答案
  • C.调用jax.random.split(key, 4)
  • D.调用jax.random.PRNGKey(key, 3)

答案:B。JAX中分裂密钥的API是 jax.random.split(key, n),第二个参数是要生成的子密钥数量。C选项生成4个,D选项PRNGKey不接收key作为参数。

Q5. 在pmap多设备并行时,随机数应该如何处理?

单选题 · mid

  • A.所有设备共享同一个key
  • B.每个设备使用固定种子0
  • C.先split成同设备数量的子key← 正确答案
  • D.并行分支不使用任何随机数

答案:C。正确做法是从主key split出与设备数相同的子key,每个设备独享一个。共享key会让所有设备产生相同随机数,导致并行训练失去统计意义。

Q6. 同一个key被split之后,原key还能继续使用吗?

单选题 · mid

  • A.不能,split后原key会失效
  • B.可以,split不修改原key← 正确答案
  • C.取决于设备的计算精度
  • D.必须立即丢弃否则报错

答案:B。split是纯函数,不修改原key,原key之后依旧可用。但为了保证随机独立性和代码清晰,实践中通常不再复用原key,而是使用分裂出的子key。

Q7. 关于key的本质,下面说法正确的是?

单选题 · mid

  • A.key是一个Python整数对象
  • B.key是一个uint32数组← 正确答案
  • C.key是一条字符串密码
  • D.key是浮点张量变量

答案:B。key在JAX中被设计为形状(2,)的uint32数组,这样的数组形态让它能作为张量参与jit、vmap、pmap等变换,被设备内存统一管理。

Q8. 关于split分裂出的子key,下列说法正确的有?

多选题 · mid

  • A.子key之间统计独立← 正确答案
  • B.子key还可以继续分裂← 正确答案
  • C.子key等价于父key本身
  • D.子key的结果无法复现

答案:AB。子key从父key独立派生,彼此统计独立,且可以递归继续split。子key与父key不同,但结果同样可复现。C和D都与JAX的设计相悖。

Q9. 哪些做法会破坏随机结果的可复现性?

多选题 · hard

  • A.每次运行脚本更换不同的seed← 正确答案
  • B.并行分支中共享同一个key← 正确答案
  • C.先split出子key再分别使用
  • D.把固定种子传给PRNGKey创建key

答案:AB。A更换seed等于换了一枚新印章,结果自然不同;B共享key会让所有分支产生相同随机序列,破坏独立性。C和D都是正确做法,保证可复现。

Q10. 关于随机性与JIT编译,下列说法正确的有?

多选题 · hard

  • A.key显式传入允许jit编译随机函数← 正确答案
  • B.全局随机状态无法被jit安全编译← 正确答案
  • C.随机函数必须依赖隐藏全局状态
  • D.JAX中随机函数不能通过jit编译

答案:AB。JIT要求函数“纯”,key显式传入使随机函数变成了纯函数,因此可以安全编译。全局状态会破坏纯净性,无法被jit处理。C、D与JAX的实际设计相反——JAX的随机函数完全可以jit编译。