问题描述
让A
为:
class A {
std::vector<double> values_;
public:
A(const std::vector<double> &values) : values_(values){};
void bumpAt(std::size_t i,const double &df) {
values_[i] += df;
virtual method1();
virtual method2();
...
}
class B : public A {
overrides methods
...
}
为简单起见,请考虑以下功能:
double foo(input1,input2,...,const A &a,const B &b,inputK,...) {
/* do complex stuff */
return ...;
}
我们要根据其参数来区分foo()
。因此,一阶灵敏度d foo/d a
是大小为std::vector<double>
的{{1}}。 a.size()
的推理也是如此。
一个简单的实现如下:
d foo/d b
这很好用,但是我们为
// compute d foo/d a
std::vector<double> computeDfDa(input1,double da = 1.0){
std::vector<double> dfda = {};
auto aUp = a.copy();
auto aDown = a.copy();
for (auto i = 0; i < a.size(); ++i) {
// bump up
aUp.bumpAt(i,da);
// bump down
aDown.bumpAt(i,-da);
auto up = foo(input1,aUp,b,...);
auto down = foo(input1,aDown,...);
auto derivative = (up - down) / 2.0 / da;
dfda.pushback(derivative);
// revert bumps
aUp.bumpAt(i,-da);
aDown.bumpAt(i,da);
}
return dfda;
}
// compute d foo/d b
std::vector<double> computeDfdb(input1,double db = 0.01){
std::vector<double> dfdb = {};
auto bUp = b.copy();
auto bDown = b.copy();
for (auto i = 0; i < a.size(); ++i) {
// bump up
bUp.bumpAt(i,db);
// bump down
bDown.bumpAt(i,-db);
auto up = foo(input1,a,bUp,bDown,...);
auto derivative = (up - down) / 2.0 / db;
dfdb.pushback(derivative);
// revert bumps
bUp.bumpAt(i,-db);
bDown.bumpAt(i,db);
}
return dfdb;
}
和computeDfDa()
使用了基本相同的代码。
是否有任何设计模式可以使它具有独特的(也许是模板化的)功能,可以自动理解要颠倒的输入?
请注意,输入中computeDfdb()
和a
的位置是不是可交换的。
如果b
的复杂度和输入数量大得多,那么天真的解决方案将生成很多无用的代码,因为我们必须为每个输入{{1 }},共foo()
。
解决方法
由于compute
的顺序相同,但是迭代遍历了不同的容器,因此您可以重构此函数。
std::vector<double> computeLoop( std::vector<double> &v,std::vector<double> const &computeArg1,std::vector<double> const &computeArg2,double d = 1.0 )
{
std::vector<double> dfd = {};
for (auto i = 0; i < v.size(); ++i) {
// bump up
v[i] += d;
auto up = compute(computeArg1,computeArg2);
v[i] -= d;
// bump down
v[i] -= d;
auto down = compute(computeArg1,computeArg2);
v[i] += d;
auto derivative = (up - down) / 2.0 / d;
dfd.pushback(derivative);
}
}
实际通话。
auto dfda = computeLoop( a,a,b );
auto dfdb = computeLoop( b,b );
让v
通过引用传递,但这可能会导致维护问题。因为v
可能与computeArg1
或computeArg2
相同,但是在computeLoop
中这并不明显。将来可能有人不知不觉地破坏了代码。
恕我直言,问题在于,抽象水平发生了变化。
那些A,B类是...
- 伪装成正义的载体
- 不是向量,而是完全其他的东西。
在情况(1)中,应该可以重写为类似于以下形式:
#include <... the usual suspects ... >
using VecF64 = std::vector<double>;
using VecVecF64 = std::vector<VecF64>;
// foo,dropping allusions of encapsulations. It KNOWS those A,B are vectors.
// Hence we can write it without knowledge of A,B types.
double foo(const VecVecF64& args) {
return <result-of-complicated-computations>;
}
// With that,it is also easier to write the differentiation function
VecVecF64 fooGradients( const VecVecF64& args,double delta = 0.01) {
VecVecF64 result;
result.reserve(args.size());
// for each arg vector in args,do your thing.
// ..
return result;
}
在情况(2)中,如果您都被封装并且对A,B的性质保密, 您如何知道foo可以用于计算的双精度数?这就引出了A的梯度矢量大小的问题,并使这种颠簸的想法无法实现。
我的猜测是,您遇到一种情况1的问题。