如何编写输出参数数量可变的匿名函数?
How to write an anonymous function with a variable number of output arguments?
使用deal
我们可以编写具有多个输出参数的匿名函数,例如
minmax = @(x)deal(min(x),max(x));
[u,v] = minmax([1,2,3,4]); % outputs u = 1, v = 4
但是如果你想为优化函数提供一个带有梯度的函数 fminunc
这是行不通的。 函数 fminunc
有时用一个输出参数调用输入函数,有时用两个输出参数。 (编辑:这不是真的,你只需要指定是否您实际上是否想使用渐变,使用例如 optimset('SpecifyObjectiveGradient',true)
。然后在一次调用中它总是要求相同数量的参数。)
我们必须提供类似
的内容
function [f,g] = myFun(x)
f = x^2; % function
g = 2*x; % gradient
可以用一个 或 两个输出参数调用。
那么有没有办法在不使用 function
关键字的情况下执行相同的内联操作?
是的,它涉及中使用的关于递归匿名函数的技术。首先我们定义一个辅助函数
helper = @(c,n)deal(c{1:n});
它接受可能输出的元胞数组 c
以及表示我们需要多少输出的整数 n
。要编写我们的实际函数,我们只需要定义元胞数组并将 nargout
(预期输出参数的数量)传递给 helper
:
myFun = @(x)helper({x^2,2*x,2},nargout);
现在调用 fminunc
:
时可以完美运行
x = fminunc(myFun,1);
的优点在于简洁,在很多情况下都很有用。
但是,它有一个主要缺点,即它的可扩展性不如其他方式。做出此声明是因为对所有函数 ({x^2,2*x,2}
) 进行了评估,无论它们是否需要作为输出 - 当请求的输出少于 3 个时,这会导致 "wasted" 计算时间和内存消耗。
在这个问题的例子中,这不是问题,因为函数及其导数很容易计算,输入 x
是一个 标量 ,但是在不同的情况下,这可能是一个非常现实的问题。
我提供的是修改后的版本,虽然比较丑陋,但避免了上述问题并且更通用:
funcs_to_apply = {@(x)x.^2, @(x)2*x, @(x)2};
unpacker = @(x)deal(x{:});
myFun = @(x)unpacker(cellfun(@(c)feval(c,x),...
funcs_to_apply(1:evalin('caller','nargout')),...
'UniformOutput',false)...
);
备注:
- 我使用的附加功能是
cellfun
, evalin
and feval
。
- 仅添加了
'UniformOutput'
参数,以便 cellfun
的输出是一个单元格(并且可以是 "unpacked" 到 comma-separated list;我们可以包装它在 num2cell
中。
evalin
技巧是必需的,因为在 myFun
范围内我们不知道从 unpacker
. 请求了多少输出
- 虽然通常不鼓励使用各种形式的
eval
(此处:evalin
),但在这种情况下,我们确切地知道调用者是谁,并且这是一个安全的操作。
使用deal
我们可以编写具有多个输出参数的匿名函数,例如
minmax = @(x)deal(min(x),max(x));
[u,v] = minmax([1,2,3,4]); % outputs u = 1, v = 4
但是如果你想为优化函数提供一个带有梯度的函数 fminunc
这是行不通的。 函数 (编辑:这不是真的,你只需要指定是否您实际上是否想使用渐变,使用例如 fminunc
有时用一个输出参数调用输入函数,有时用两个输出参数。optimset('SpecifyObjectiveGradient',true)
。然后在一次调用中它总是要求相同数量的参数。)
我们必须提供类似
的内容function [f,g] = myFun(x)
f = x^2; % function
g = 2*x; % gradient
可以用一个 或 两个输出参数调用。
那么有没有办法在不使用 function
关键字的情况下执行相同的内联操作?
是的,它涉及
helper = @(c,n)deal(c{1:n});
它接受可能输出的元胞数组 c
以及表示我们需要多少输出的整数 n
。要编写我们的实际函数,我们只需要定义元胞数组并将 nargout
(预期输出参数的数量)传递给 helper
:
myFun = @(x)helper({x^2,2*x,2},nargout);
现在调用 fminunc
:
x = fminunc(myFun,1);
但是,它有一个主要缺点,即它的可扩展性不如其他方式。做出此声明是因为对所有函数 ({x^2,2*x,2}
) 进行了评估,无论它们是否需要作为输出 - 当请求的输出少于 3 个时,这会导致 "wasted" 计算时间和内存消耗。
在这个问题的例子中,这不是问题,因为函数及其导数很容易计算,输入 x
是一个 标量 ,但是在不同的情况下,这可能是一个非常现实的问题。
我提供的是修改后的版本,虽然比较丑陋,但避免了上述问题并且更通用:
funcs_to_apply = {@(x)x.^2, @(x)2*x, @(x)2};
unpacker = @(x)deal(x{:});
myFun = @(x)unpacker(cellfun(@(c)feval(c,x),...
funcs_to_apply(1:evalin('caller','nargout')),...
'UniformOutput',false)...
);
备注:
- 我使用的附加功能是
cellfun
,evalin
andfeval
。 - 仅添加了
'UniformOutput'
参数,以便cellfun
的输出是一个单元格(并且可以是 "unpacked" 到 comma-separated list;我们可以包装它在num2cell
中。 evalin
技巧是必需的,因为在myFun
范围内我们不知道从unpacker
. 请求了多少输出
- 虽然通常不鼓励使用各种形式的
eval
(此处:evalin
),但在这种情况下,我们确切地知道调用者是谁,并且这是一个安全的操作。