在并行作业中控制和重复生成随机数
本示例演示了如何通过为每个作业分配一个唯一的子流,来控制独立并行作业和任务的随机数生成。
如控制工作单元上的随机数流中所述,集群中执行同一项作业的每个工作单元都有一个独立的随机数生成器流。对于作业和任务,MATLAB ® 会将每个运行该作业的工作单元的随机数生成器重置为默认算法和种子状态。然后,它会根据任务索引分配一个独立的流。
批处理作业中的随机数
当您使用 batch 函数提交一个独立批处理作业时,您创建的作业包含一个任务,且该任务的任务索引始终为 1。由于 MATLAB 会根据任务索引分配一个随机数流,因此无论由哪个工作单元执行,批处理作业都会获得相同的随机数流。因此,运行诸如 rand(1,4) 之类的命令的批处理作业,每次执行时都会返回相同的结果。
在本地计算机上的某个工作单元上,将 rand 用作批处理作业来生成随机数。
c = parcluster("Processes");
job1 = batch(c,@rand,1,{1,4});
job2 = batch(c,@rand,1,{1,4});若要阻止 MATLAB 直到作业完成,请对作业对象调用 wait 函数。检索批处理作业的结果。这两个批处理作业生成的随机数是相同的。
wait(job1); wait(job2); fetchOutputs(job1)
ans = 1×1 cell array
{[0.1349 0.6744 0.9301 0.5332]}
fetchOutputs(job2)
ans = 1×1 cell array
{[0.1349 0.6744 0.9301 0.5332]}
使用子流控制随机数流
要在多个批处理作业中生成唯一的随机数序列,请为每个作业分配一个特定的子流。这确保了作业和任务之间的独立性。为了重现结果,请重用相同的作业索引。
此辅助函数演示了如何使用您指定的作业索引来修改全局流。它会从工作单元中提取全局随机数流的句柄,并根据指定的作业索引设置子流索引。
function r = modifyStreamJobFcn(jobIdx,sz) stream = RandStream.getGlobalStream; stream.Substream = jobIdx; % Start job function r = rand(1,sz); end
将 modifyStreamJobFcn 辅助函数作为两个批处理作业运行。
for idx = 1:2 jobIdx = idx; batchJobs(idx) = batch(c,@modifyStreamJobFcn,1,{jobIdx,4}); end wait(batchJobs(1)); wait(batchJobs(2));
从工作单元中获取结果。批处理作业生成的随机数现在已经不同了。
batchJob1Result = fetchOutputs(batchJobs(1))
batchJob1Result = 1×1 cell array
{[0.1349 0.6744 0.9301 0.5332]}
batchJob2Result = fetchOutputs(batchJobs(2))
batchJob2Result = 1×1 cell array
{[0.3270 0.8665 0.6173 0.6411]}
使用作业索引重现结果
要重现该作业的结果,请重用该作业索引。使用索引 2 重现批处理作业的结果。batchJobs2 和 batchJobs3 中生成的随机数是相同的。
jobIdx = 2;
batchJobs(3) = batch(@modifyStreamJobFcn,1,{jobIdx,4});
wait(batchJobs(3));
batchJob3Result = fetchOutputs(batchJobs(3))batchJob3Result = 1×1 cell array
{[0.3270 0.8665 0.6173 0.6411]}
isequal(batchJob2Result,batchJob3Result)
ans = logical
1
作业与任务中的随机数
当您使用 createJob 和 createTask 函数处理包含多个任务的独立作业时,每个任务都会根据任务索引获得一个独立的流。由于这种映射是确定性的,因此无论由哪个工作单元执行,具有相同任务索引的任务在每次作业中都会获得相同的流。
创建两个作业,每个作业包含 4 个任务。对于每一项任务,生成一个随机数值。等待作业完成,并获取结果。这两个作业生成的随机数在每个任务中都是相同的。
for idx = 1:2 multiTasksJob(idx) = createJob(c); for t = 1:4 createTask(multiTasksJob(idx),@rand,1,{1}); end submit(multiTasksJob(idx)); end wait(multiTasksJob(1)); wait(multiTasksJob(2)); fetchOutputs(multiTasksJob(1))'
ans=1×4 cell array
0.1349 0.6383 0.9730 0.3241
fetchOutputs(multiTasksJob(2))'
ans=1×4 cell array
0.1349 0.6383 0.9730 0.3241
若要在每次运行作业时,为每个任务生成同一组随机数,请使用默认行为。
通过子流控制随机数流
如果您希望在一组包含多个任务的独立作业中生成唯一的随机数序列,可以通过为每个作业分配一个特定的子流来修改全局随机数流。所有作业中生成的随机数都是相互独立的。若要重现先前作业中的特定结果,您可以使用作业索引重新分配相同的子流。
定义一个辅助函数,该函数使用您指定的作业索引来修改全局流。为了进行比较,还应返回流的详细信息。
function out = modifyAndReturnStreamJobFcn(jobIdx,taskIdx,sz) stream = RandStream.getGlobalStream; stream.Substream = jobIdx; % Collect stream details out.JobNum = jobIdx; out.TaskNum = taskIdx; out.rngStream = stream.StreamIndex; out.rngSubstream = stream.Substream; % Start job function r = rand(1,sz); out.result = r; end
再次创建两个作业,每个作业包含 4 个任务。对于每个任务,请运行 modifyAndReturnStreamJobFcn 辅助函数来生成一个随机数。请等待作业完成。
for idx = 1:2 multiTasksJob(idx) = createJob(c); jobIdx = idx; for taskIdx = 1:4 createTask(multiTasksJob(idx), ... @modifyAndReturnStreamJobFcn,1,{jobIdx,taskIdx,1}); end submit(multiTasksJob(idx)); end wait(multiTasksJob(1)); wait(multiTasksJob(2));
检索这两个作业的结果。这两个作业生成的随机数现在各不相同。
multiTasksJob1Result = cell2mat(fetchOutputs(multiTasksJob(1))); [multiTasksJob1Result.result]
ans = 1×4
0.1349 0.6383 0.9730 0.3241
multiTasksJob2Result = cell2mat(fetchOutputs(multiTasksJob(2))); [multiTasksJob2Result.result]
ans = 1×4
0.3270 0.4821 0.8265 0.2793
以表格形式显示每个任务的流和子流索引的详细信息。将具有相同任务索引的任务分组。
T = struct2table([multiTasksJob1Result;multiTasksJob2Result]);
T = sortrows(T,"TaskNum");
disp(T) JobNum TaskNum rngStream rngSubstream result
______ _______ _________ ____________ _______
1 1 2 1 0.13486
2 1 2 2 0.32701
1 2 4 1 0.63835
2 2 4 2 0.48211
1 3 6 1 0.973
2 3 6 2 0.82646
1 4 8 1 0.32412
2 4 8 2 0.27926
使用作业索引重现结果
要重现包含多个任务的作业的结果,请重用该作业的索引。使用索引 2 重新生成该作业的结果。multiTasksJob2 和 multiTasksJob3 中生成的随机数是相同的。
jobIdx = 2; multiTasksJob(3) = createJob(c); for taskIdx = 1:4 createTask(multiTasksJob(3), ... @modifyAndReturnStreamJobFcn,1,{jobIdx,taskIdx,1}); end submit(multiTasksJob(3)); wait(multiTasksJob(3)); multiTasksJob3Result = cell2mat(fetchOutputs(multiTasksJob(3))); [multiTasksJob2Result.result]
ans = 1×4
0.3270 0.4821 0.8265 0.2793
[multiTasksJob3Result.result]
ans = 1×4
0.3270 0.4821 0.8265 0.2793
定义自定义随机流
您可以使用自定义随机数流来控制作业和任务的随机数生成。这种方法使您能够完全控制生成器算法、种子和流配置。
此外,在工作单元上修改全局流并不能保证不同版本的 MATLAB 之间能得到相同的结果,因为这些版本可能使用不同的默认算法和种子。如果希望不同版本之间的结果保持一致,请为每个作业和任务定义一个自定义随机数流。
使用 RandStream.create 函数和支持子流的生成器创建一个自定义流。有关支持子流的生成器的列表,请参阅 选择随机数生成器。
为确保每个数据流都是独立且可重复的,请在开始执行作业时按照以下步骤操作:
使用相同的生成器算法、种子和流的数量。
为每个作业分配一个唯一的流索引,为每个任务分配一个唯一的子流。
例如,该函数会根据指定的作业索引创建一个随机流,使用任务 ID 修改子流,并将修改后的流设置为工作单元上的全局流。
function r = createStreamJobFcn(jobIdx,taskIdx,sz) s = RandStream.create("threefry4x64_20",Seed=0, ... NumStreams=2^63,StreamIndices=jobIdx); s.Substream = taskIdx; RandStream.setGlobalStream(s); % Start job function r = rand(1,sz); end
使用 taskStartup.m 实现直播设置自动化
上述方法可用于针对特定的一组作业修改全局流。如果您希望对所有运行的作业都进行此操作,但又不想每次都修改作业函数,那么请将修改全局流的代码添加到任务启动函数文件 taskStartup.m 中。
每当一个工作单元为某个作业执行任务时,taskStartup.m 文件就会在该工作单元上自动运行。您必须确保执行任务能够读取 taskStartup.m 文件。有关 taskStartup.m 文件的信息,请参阅taskStartup。
例如,为了确保每个作业都使用一个唯一的全局流,请将以下代码添加到 taskStartup.m 文件中:
function taskStartup(task) job = task.Parent; jobID = job.ID; taskID = task.ID; s = RandStream.create("threefry4x64_20",Seed=0, ... NumStreams=2^63,StreamIndices=jobID); s.Substream = taskID; RandStream.setGlobalStream(s); end