将2个参数[x,y]扩展到可变大小参数包[x,... x,y]
我正在编写C ++中的自定义多层PESCEPTRON(MLP)实现。除最后一层以外,所有除了一个激活函数foo
,最后一层具有单独的激活bar
。我正在尝试编写我的代码,以便能够以不同数量的层来处理此类型的模型,例如此Godbolt链接,在下面复制。不幸的是,如书面,我不得不将参数包用于激活函数,因此链接中的代码仅编译n = 5
。
是否有一种方法可以从两个激活函数中创建一个自定义参数套件,该函数能够“左扩展”第一个参数,以便我可以编译上述代码(在将调用适当地更新为Compute> Compute> Compute>代码>在
computemlp
中,我在想一些可以产生参数包的实用程序,例如:
template <size_t N, typename ActivationMid, typename ActivationLast>
struct makeActivationSequence {}; // What goes here?
makeActivationSequence<0>(foo, bar) -> []
makeActivationSequence<1>(foo, bar) -> [bar]
makeActivationSequence<2>(foo, bar) -> [foo, bar]
makeActivationSequence<3>(foo, bar) -> [foo, foo, bar]
makeActivationSequence<4>(foo, bar) -> [foo, foo, foo, bar]
...
查看 std :: index_sequence的详细信息我相信在这里可能有类似的东西,但是我不清楚我如何修改这种方法以与两者一起使用不同类型。
还请注意,由于某些工具链问题,我特别限于C ++ 14,因此如果Constexpr ,则可以利用Eg )无法
从上面的Godbolt链接中使用,以下复制以确保完整性:
#include <cstddef>
#include <utility>
#include <cstdio>
template <size_t LayerIndex, typename Activation>
void computeIndexedLayer(
const Activation& activation) {
printf("Doing work for layer %zu, activated output %zu\n", LayerIndex, activation(LayerIndex));
}
template <
std::size_t... index,
typename... Activation>
void computeIndexedLayers(
std::index_sequence<index...>, // has to come before Activation..., otherwise it'll get eaten
Activation&&... activation) {
(void)std::initializer_list<int>{
(computeIndexedLayer<index + 1>(
std::forward<Activation>(activation)),
0)...};
}
template <size_t N, typename ActivationMid, typename ActivationLast>
void computeMlp(ActivationMid&& mid, ActivationLast&& last) {
computeIndexedLayers(std::make_index_sequence<N>(),
std::forward<ActivationMid>(mid),
std::forward<ActivationMid>(mid),
std::forward<ActivationMid>(mid),
std::forward<ActivationMid>(mid),
std::forward<ActivationLast>(last)
);
}
int main() {
computeMlp<5>([](const auto& x){ return x + 1;}, [](const auto& x){ return x * 1000;});
// Doesn't compile with any other choice of N due to mismatched pack lengths
// computeMlp<4>([](const auto& x){ return x + 1;}, [](const auto& x){ return x * 1000;});
}
I'm writing a custom multi layer perceptron (MLP) implementation in C++. All but the last layer share one activation function foo
, with the final layer having a separate activation bar
. I'm trying to write my code such that it's able to handle models of this type with a varying number of layers, like at this Godbolt link, reproduced below. Unfortunately, as written, I've had to hardcode the parameter pack for activation functions, and thus the code in the link only compiles for N = 5
.
Is there a way to create a custom parameter pack from the two activation functions which is able to "left-extend" the first argument, such that I can then compile the code above (after suitably updating the call to computeIndexedLayers
in computeMlp
? Specifically, I'm thinking of some utility which can yield parameter packs like:
template <size_t N, typename ActivationMid, typename ActivationLast>
struct makeActivationSequence {}; // What goes here?
makeActivationSequence<0>(foo, bar) -> []
makeActivationSequence<1>(foo, bar) -> [bar]
makeActivationSequence<2>(foo, bar) -> [foo, bar]
makeActivationSequence<3>(foo, bar) -> [foo, foo, bar]
makeActivationSequence<4>(foo, bar) -> [foo, foo, foo, bar]
...
Looking at the details of std::index_sequence I believe something similar might work here, but it's unclear to me how I'd modify that approach to work with the two different types.
Please note also that I'm specifically limited to C++14 here due to some toolchain issues, so solutions that take advantage of e.g. if constexpr
(as in the linked std::index_sequence details) won't work.
Code from the above Godbolt link, reproduced below for completeness:
#include <cstddef>
#include <utility>
#include <cstdio>
template <size_t LayerIndex, typename Activation>
void computeIndexedLayer(
const Activation& activation) {
printf("Doing work for layer %zu, activated output %zu\n", LayerIndex, activation(LayerIndex));
}
template <
std::size_t... index,
typename... Activation>
void computeIndexedLayers(
std::index_sequence<index...>, // has to come before Activation..., otherwise it'll get eaten
Activation&&... activation) {
(void)std::initializer_list<int>{
(computeIndexedLayer<index + 1>(
std::forward<Activation>(activation)),
0)...};
}
template <size_t N, typename ActivationMid, typename ActivationLast>
void computeMlp(ActivationMid&& mid, ActivationLast&& last) {
computeIndexedLayers(std::make_index_sequence<N>(),
std::forward<ActivationMid>(mid),
std::forward<ActivationMid>(mid),
std::forward<ActivationMid>(mid),
std::forward<ActivationMid>(mid),
std::forward<ActivationLast>(last)
);
}
int main() {
computeMlp<5>([](const auto& x){ return x + 1;}, [](const auto& x){ return x * 1000;});
// Doesn't compile with any other choice of N due to mismatched pack lengths
// computeMlp<4>([](const auto& x){ return x + 1;}, [](const auto& x){ return x * 1000;});
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
您无法从函数返回参数包,因此
makeActivationSequence
不可能是不可能的。但是,您可以将Mid
和直接传递到
ComputeEndexedlayers
,并在那里使用PACK将它们分别与它们配对,Mid IndiNdex 模板参数包和
lastIndex
模板参数(在这种情况下,恰好有一个lastIndex
,因此它不是模板参数包,但是如果如果不难进行更改/推广,如果需要)从两个相应的std :: index_sequence
参数中得出。像这样:godbolt link
也请注意,在
Comput> Computemlp 中都
和最后
是转发的,但是在ComputeEndexedlayers
仅最后
是。这样做是为了避免从MID
重复移动,如果activationMid
包含某些状态,并且不是一种动态可移动的类型,则可能会引起麻烦。C ++ 17
由于C ++ 17支持折叠表达式,
std :: pritializer_list
computeIndexedlayers
可以更换:C ++ 20
模板lambdas in C ++ 20让我们完全摆脱
ComputeIndexedlayers
完全推断lambda的模板参数和参数包,定义并立即在computemlp
中立即调用:You can't return a parameter pack from functions, so
makeActivationSequence
as you described is impossible. However, you can passmid
andlast
directly tocomputeIndexedLayers
, and there utilise pack unfolding pairing them with, respectively,midIndex
template parameter pack andlastIndex
template parameter (in this case, there's exactly onelastIndex
, so it's not a template parameter pack, but it's not hard to change/generalise if needed) deduced from two correspondingstd::index_sequence
arguments. Like this:Godbolt link
Also note that in
computeMlp
bothmid
andlast
are forwarded, but atcomputeIndexedLayers
onlylast
is. It's done to avoid potential repeated move frommid
, which could cause troubles ifActivationMid
contains some state and is not a trivially movable type.C++17
Since C++17 supports fold expressions, pretty ugly
std::initializer_list
hack incomputeIndexedLayers
can be replaced:C++20
Templated lambdas in C++20 let us get rid of
computeIndexedLayers
altogether and deduce template parameters and parameter packs for lambda, defined and immediately invoked withincomputeMlp
: