将2个参数[x,y]扩展到可变大小参数包[x,... x,y]

发布于 2025-02-06 12:55:45 字数 2869 浏览 1 评论 0原文

我正在编写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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

白云悠悠 2025-02-13 12:55:45

您无法从函数返回参数包,因此makeActivationSequence不可能是不可能的。但是,您可以将Mid直接传递到ComputeEndexedlayers,并在那里使用PACK将它们分别与它们配对,Mid IndiNdex 模板参数包和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;});
}

godbolt link

也请注意,在Comput> Computemlp 中都最后是转发的,但是在ComputeEndexedlayers最后是。这样做是为了避免从MID重复移动,如果activationMid包含某些状态,并且不是一种动态可移动的类型,则可能会引起麻烦。

C ++ 17

由于C ++ 17支持折叠表达式,std :: pritializer_list 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

模板lambdas in C ++ 20让我们完全摆脱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>{});
}

You can't return a parameter pack from functions, so makeActivationSequence as you described is impossible. However, you can pass mid and last directly to computeIndexedLayers, and there utilise pack unfolding pairing them with, respectively, midIndex template parameter pack and lastIndex template parameter (in this case, there's exactly one lastIndex, so it's not a template parameter pack, but it's not hard to change/generalise if needed) deduced from two corresponding std::index_sequence arguments. Like this:

#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;});
}

Godbolt link

Also note that in computeMlp both mid and last are forwarded, but at computeIndexedLayers only last is. It's done to avoid potential repeated move from mid, which could cause troubles if ActivationMid contains some state and is not a trivially movable type.

C++17

Since C++17 supports fold expressions, pretty ugly std::initializer_list hack in computeIndexedLayers can be replaced:

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

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 within 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>{});
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文