我正在用 C++ 编写一个自定义的多层感知器 (MLP) 实现。除了最后一层之外,所有层都共享一个激活函数foo
,最后一层有一个单独的激活函数bar
。我正在尝试编写我的代码,以便它能够处理具有不同层数的这种类型的模型,就像在下面复制的这个 Godbolt 链接中一样。不幸的是,正如所写,我不得不对激活函数的参数包进行硬编码,因此链接中的代码只能编译为N = 5
.
有没有办法从两个激活函数创建一个自定义参数包,它能够“左扩展”第一个参数,这样我就可以编译上面的代码(在适当更新对computeIndexedLayers
in的调用之后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,因此利用例如的解决方案if constexpr
(如链接的 std::index_sequence 详细信息中)将不起作用。
来自上述 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;});
}
您不能从函数返回参数包,因此makeActivationSequence
如您所描述的那样是不可能的。但是,您可以将mid
和last
直接传递给computeIndexedLayers
,并利用包展开将它们分别与midIndex
模板参数包和lastIndex
模板参数配对(在这种情况下,正好有一个lastIndex
,因此它不是模板参数包,但不难更改/如果需要,可以概括)从两个相应的std::index_sequence
参数推导出来。像这样:
#include <cstddef>
#include <utility>
#include <cstdio>
template <size_t LayerIndex, typename Activation>
void computeIndexedLayer(Activation&& activation) {
printf("Doing work for layer %zu, activated output %zu\n", LayerIndex, activation(LayerIndex));
}
template <std::size_t... midIndex, std::size_t lastIndex,
typename ActivationMid, typename ActivationLast>
void computeIndexedLayers(
std::index_sequence<midIndex...> midIdxs,
std::index_sequence<lastIndex> lastIdxs,
ActivationMid&& mid, ActivationLast&& last) {
(void)std::initializer_list<int>{
(computeIndexedLayer<midIndex + 1>(mid), 0)...,
(computeIndexedLayer<lastIndex>(std::forward<ActivationLast>(last)), 0)};
}
template <size_t N, typename ActivationMid, typename ActivationLast>
void computeMlp(ActivationMid&& mid, ActivationLast&& last) {
computeIndexedLayers(std::make_index_sequence<N - 1>(), std::index_sequence<N>{},
std::forward<ActivationMid>(mid), std::forward<ActivationLast>(last));
}
int main() {
computeMlp<6>([](const auto& x){ return x + 1;}, [](const auto& x){ return x * 1000;});
}
另请注意,在computeMlp
两者中mid
,andlast
都被转发,但 at computeIndexedLayers
onlylast
是。这样做是为了避免潜在的重复从 移动,如果包含某些状态并且不是可移动的类型mid
,这可能会导致麻烦。ActivationMid
由于 C++17 支持折叠表达式,因此可以替换非常丑陋std::initializer_list
的 hack :computeIndexedLayers
template <std::size_t... midIndex, std::size_t lastIndex,
typename ActivationMid, typename ActivationLast>
void computeIndexedLayers(
std::index_sequence<midIndex...> midIdxs,
std::index_sequence<lastIndex> lastIdxs,
ActivationMid&& mid, ActivationLast&& last) {
(computeIndexedLayer<midIndex + 1>(mid), ...);
computeIndexedLayer<lastIndex>(std::forward<ActivationLast>(last));
}
C++20 中的模板化 lambda 让我们完全摆脱computeIndexedLayers
并推导出 lambda 的模板参数和参数包,定义并立即调用computeMlp
:
template <size_t N, typename ActivationMid, typename ActivationLast>
void computeMlp(ActivationMid&& mid, ActivationLast&& last) {
[&]<std::size_t... midIndex, std::size_t lastIndex>(
std::index_sequence<midIndex...> midIdxs,
std::index_sequence<lastIndex> lastIdxs){
(computeIndexedLayer<midIndex + 1>(mid), ...);
computeIndexedLayer<lastIndex>(std::forward<ActivationLast>(last));
}(std::make_index_sequence<N - 1>(), std::index_sequence<N>{});
}
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句