背景
之前使用了C++17标准写了人工神经网络,里面涉及了大量的模板递归。比如我要建立一个多层网络组成的复杂网络,在模板参数里面需要以各层网络为实参,逐层保存每层的神经网络,并且撰写正向反向逻辑。对于BP要一层一层写forward和backward,对于CNN也要一层一层写forward和backward,这里面涉及了大量的重复代码。但实际多层的神经网络的处理主干逻辑都是一样的。正向是从前层到后层逐一调用forward,前层输出为后层输入;反向传播从后向前逐一调用backward。最近学习了C++20以及折叠表达式,我就在想能不能将这些逻辑通过模板的方式处理以减少重复代码。
设计思路
我首先定义了单层的权重层,以后还将有各类激活层、标准化层,这些层的具体代码就不一一展示了,本章只谈主干逻辑。这些逻辑层都有forward和backward成员函数,用来接收上一层的输出作为本层输入,并且返回下一层的输入。通过折叠表达式可以实现正反向传播:
template<typenamenet_type,typenameinput_type>requiresis_forwardable<net_type,input_type>autooperator>>(input_typeconst&input,net_type&net){returnnet.forward(input);}template<typenameinput_type,typename...net_types>autonet_forward(input_typeconst&input,net_types&&...nets){return(input>>...>>nets);}template<typenamenet_type,typenameinput_type>requiresis_backwardable<net_type,input_type>autooperator<<(net_type&net,input_typeconst&input){returnnet.backward(input);}template<typename...net_type,typenameinput_type>autonet_backward(input_typeconst&input,net_type&&...nets){return(nets<<...<<input);}重载了>>和<<分别实现正向传播和反向传播,重载的目的是使用折叠表达式。init op ... op pack的样式表示的是(...((init op arg1) op arg2)... op argN,重载了>>用于正向传播(注意符号重载函数参数顺序)。这里我的目的是让net_forward和net_backward可以一次性由浅层到深层传入各层网络,最终得到网络的正向输出和反向输出。这样做的初衷是避免多层的递归调用。
下面我想定义一个多层的网络,这个网络的模板参数是各层的类型,复杂网络模板可以将各层作为内部成员进行保存,在前向传播时候调用net_forward一次性传入,得到输出;反向传播的时候也是一次性输入各层,得到反向误差传播结果并且更新网络。这里存在3个问题:
- 如何保存各层网络的对象;
- 如何将对象队列按照正向顺序一次性传入forward;
- 如何将对象队列按照正向顺序一次性传入backward;
我这里使用的是tuple保存各类对象,并且使用std::apply模板函数将入仓一次性传入。
template<typename...net_types