如何制作一个根据变化的分布进行挑选的函数,而不会一遍又一遍地传递?



这不是一个关于MatLab的问题,而是一个关于当你使用不太复杂的语言时,如何实现一些在面向对象编程中很容易实现的目标的问题。

我是一名数学家,正在编写一些 MatLab 代码来测试线性代数中的算法(我不会给你带来细节负担)。程序的开始是生成一个随机的 500 x 50 浮点矩阵(称为 A)。在运行程序的过程中,我将希望随机选择A的行,不是均匀地随机选择,而是根据分布,其中选择行i的可能性不同,具体取决于已生成的特定矩阵。

我想编写一个名为"pickRandomRow"的函数,我可以在需要时一遍又一遍地调用它。它将在程序的每次单独运行中对行使用相同的概率分布,但该分布将在程序运行之间发生变化(因为随机矩阵将不同)。

如果我使用比 MatLab 更面向对象的语言,我会创建一个名为"rowPicker"的类,该类可以使用我在这次运行中使用的特定随机矩阵的信息进行初始化。但是在这里,我不确定如何在 MatLab 中创建一个函数,该函数可以一劳永逸地知道它需要了解的有关随机矩阵 A 的信息,而无需在函数不改变的情况下一遍又一遍地(昂贵地)将 A 传递给函数。

可能的选项

  • 使pickRandomRow成为脚本而不是函数,以便它可以看到工作区。然后我将无法pickRandomRow给出任何论据,但到目前为止,我不明白为什么我需要这样做。
  • 开始在 MatLab 中搞砸类。

据我所知,MATLAB 支持闭包。 闭包类似于具有一堆私有成员变量和单个方法的对象。 所以,你可以做这样的事情:

function rowPicker = createRowPicker(matrix, param)
expensivePreparations = ... (use 'matrix' and 'param' here) ...
function pickedRow = someComplicatedSamplingFunction
... (use 'matrix', 'expensivePreparations' and 'param' here) ...
end 
rowPicker = @someComplicatedSamplingFunction
end

然后你可以在循环中生成一堆不同参数化的rowPicker,如下所示:

for p = [p1, p2, p3]
matrix = generateMatrix()
picker = createRowPicker(matrix, p)
... (run expensive simulation, reuse 'picker')
end

这样,昂贵的中间结果expensivePreparations将保存在闭包中,您不必在expensive simulation的每一步中重新计算它。

警告:以上所有都是 matlab 式的伪代码,未经测试。

为了完成此任务,您可以使用 randsample 函数,确切地说,它的四个参数重载:

y = randsample(n,k,true,w)

or y = randsample(population,k,true,w) 返回使用替换获取的加权样本,使用矢量 正权重 w,其长度为 n。概率 对于 y 为 w(i)/sum(w) 的条目,选择整数 i。通常,w 是 概率向量。兰德样本不支持加权 无需更换的取样。

举个例子:

M = [
1 1 1;
2 2 2;
3 3 3;
4 4 4;
5 5 5
];
idx = randsample(1:5,1,true,[0.2 0.2 0.1 0.1 0.4]);
row = M(idx,:);

如果每次运行脚本时都必须选择多行,并且不支持不带替换的加权采样,则可以改用 datasample 函数:

M = [
1 1 1;
2 2 2;
3 3 3;
4 4 4;
5 5 5
];
idx = datasample(1:5,2,'Replace',false,'Weights',[0.2 0.2 0.1 0.1 0.4]);
rows = M(idx,:);

对于在类和脚本之间进行选择的问题,老实说,我认为您的问题过于复杂了。在这种情况下,OOP 类对我来说似乎是矫枉过正。如果要使用脚本(实际上是函数)而不向其传递任何参数,则可以在内部定义的矩阵和表示其行概率的变量上使用持久修饰符。让我们假设我提出的第一个解决方案是适合您需求的解决方案,然后:

a = pickRandomRow();
b = pickRandomRow();
c = pickRandomRow();
function row = pickRandomRow()
persistent M;
persistent W;
if (isempty(M))
M = [
1 1 1;
2 2 2;
3 3 3;
4 4 4;
5 5 5
];
W = [
0.2
0.2
0.1
0.1
0.4
];
end
idx = randsample(1:size(M,1),1,true,W);
row = M(idx,:);
end

如果要根据之前的计算提供不同的权重,可以按如下方式更改上面的代码:

w1 = WeightsFromDistributionX();
w2 = WeightsFromDistributionY();
a = pickRandomRow(w1);
b = pickRandomRow(w2);
c = pickRandomRow(w2);
function row = pickRandomRow(W)
persistent M;
if (isempty(M))
M = [
1 1 1;
2 2 2;
3 3 3;
4 4 4;
5 5 5
];
end
M_size = size(M,1);
W_size = numel(W);
if (M_size ~= W_size)
error('The weights vector must have the same length of matrix rows.');
end
idx = randsample(1:M_size,1,true,W);
row = M(idx,:);
end

如果创建一个类的工作太多(你的第一类将是,它与其他语言完全不同),你有几个选择。

当时的单一分布

您可以使用函数中的persistent变量来实现此目的。该函数将成为某种唯一对象。

function out = func(arg)
persistent M; % matrix to pick rows from
persistent S; % status
if nargin == 1
M = randn(...);
S = ...;
else
% use M, S
return ...;
end

你第一次用func('init')来调用它,之后data = func()

多种不同的分布

您可以重写上述内容,但在使用'init'调用时返回包含内部数据的单元格数组。其他时候,您将该单元格数组作为输入传递:

function out = func(arg)
if ischar(arg)
M = randn(...);
S = ...;
return {M,S};
else % iscell(arg)
% use M=arg{1}, S=arg{2}
return ...;
end

当然,它可以是一个结构体,而不是一个单元数组。我认为这是"穷人的对象"。无法控制用户修改对象的状态,但如果您是自己的用户,这可能没什么大不了的。

相关内容

最新更新