【设计模式】组合模式之神经网络应用

问题引入

试想,一个对象自己和由对象组成的一个集合都须要支持逻辑上相同的操做,实际实现可能不同.既然在语义这个更高级抽象能够把二者统一,那么若是这二者都继承同一个基类岂不是更好? 一个能够类比的例子是,目录自己可能包含多个目录,在没有子目录的状况下,目录的遍历是遍历该目录下的文件,反之,则须要递归的遍历子目录了,而对于扫描的目录的客户端来讲(调用此API的程序员)实际上不须要关注和区分这两种状况,那么这就是一个抽象的机会,统一二者操做. 这里给出组合模式的本质:程序员

So, what is the Composite pattern about? Essentially, we try to give single objects and groups of objects an identical interfaceexpress

原理

显而易见的处理方式是,定义一个抽象基类componet,并定义原始类和组合类公共操做(通常是遍历操做好比tranverse()),而后原始类和组合类各自继承便可,这种方式暂且不提.
这里提供一个另外个应用更加普遍和通用的组合模式实现方式,以神经网络基本神经元的链接做为例子.设计模式

神经网络基础

深度学习当前是以什么网络为基础的,所谓神经网络能够理解为由不少层,每层由不少个神经元,且各层神经元之间有特定关系的拓扑,简单的二层神经网络以下:
Selection_038.png网络

能够看到这个网络由橙色和绿色两层网络组成,每一层有若干个神经元,层和层之间的神经元存在链接关系,那么在这里,神经元自己就是原始类,而神经网络层则是组合类(若干神经元组成). 那么这两种类有什么公共的操做呢?ide

  • 以单个神经元来看,一个神经元要和其余神经元创建链接关系 --> build_connection
  • 以单个神经网络层来看,不一样的层之间也须要创建链接关系  --> build_connection
  • 单个神经元和层神经元创建链接. 排列组合一下,这种组合的个数是 2*2=4

而这个组合个数实际上就是类的成员函数的个数, 好比两个类A和B,分别表明神经元和层,须要支持两量互相链接操做须要以下几组成员函数:函数

A a1,a2;
B  b1,b2;
a1.build_connection(a2);
a1.build_connection(b1);
b1.build_connection(b2);
b1.build_connection(a1);

推而广之,若是在来个平面层(由若干个单层神经网络组成),那么个数就是3*3. 随着类的增长,程序的重载成员函数规模失控,这违背了设计模式原则扩展开放,对修改关闭.
怎么作呢?不一样神经元(或者层)之间的链接,本质上能够抽象成一个遍历操做:对于单个神经元来讲,遍历的结果就是只拿一个元素. 所以能够把抽象操做提高/抽象至基类,其余类继承并提供遍历迭代器接口便可.oop

实现

先给出神经元和神经网络层的类定义:学习

// 神经元
struct Neuron
{
    std::vector<Neuron *> in, out;
    unsigned int id;
    Neuron(){
        static int id = 1;
        this->id = id++;
    }
   // ...
};
// 单层神经元
struct NeuronLayer
{
    std::vector<Neuron> neus;
    NeuronLayer(int count) {
        while(count-- > 0) {
            neus.push_back(Neuron());
        }
    }
    // ...
};

而后想一下咱们要抽象的操做,obj1.connect_to(obj2), 其中obj1obj2多是NeuronLayer或者Neuron之一,connect_to实际上要分别遍历两个对象取到每个神经元而创建链接. 抽象类以下:ui

template <typename Self>
template <typename T>
void SomeNeurons<Self>::connect_to(T& other) {
    for(Neuron &from : *static_cast<Self*>(this)){
        // 直接用*this自己会产生编译错误,由于抽象模板类并无实现begin()和end(),而遍历须要它们,这里转换成子类解决(子类原本就须要提供,见后面描述)
        //for(Neuron &from : (*this)){
        for(Neuron & to : other) {
            from.out.push_back(&to);
            to.in.push_back(&from);
        }
    }
}

能够看到这个抽象类是一个带模板成员函数的模板类,connect_to分别遍历了this类对象和指向的目标类对象获取元素,由于两个对象不必定同样,所以传入的模板类型也是不同的. 接下来要干的活就剩下两个了this

  • 神经元和神经层继承抽象类,便可以获取connect_to能力
  • 基类使用了Range-based for loop, 所以须要保证各子类都提供beginend成员函数,由于Range语句的约束条件以下:

any expression that represents a suitable sequence (either an array or an object for which begin and end member functions or free functions are defined, see below) or a braced-init-list.

所以,按照上述描述新增/修改位置以下:

struct Neuron : public SomeNeurons<Neuron>
struct NeuronLayer : public SomeNeurons<NeuronLayer>
// 类Neuron, 各自新增的成员函数,由于神经元不须要遍历,end直接取下一个元素.
Neuron *begin() {return this;}
Neuron *end()   {return this+1;}
// 类NeuronLayer,原本就是要遍历vector<Neuron>,直接用vector的begin和end包装便可.
std::vector<Neuron>::iterator begin() {return neus.begin();}
std::vector<Neuron>::iterator end() {return neus.end();}

好了,主体工做基本完成,看一下当前类图结构以下:
Selection_039.png

总结

相较于比较基本的组合设计模式的实现方式(基类提供抽象接口,各子类分别实现),这个抽象成都更高,连实现也放到抽象基类当中,而各子类提供的是遍历方式,解耦更加干净完全;
更加有借鉴意义的是,这个实现方式能够完美的处理神经网络不一样元素之间的关系,调用很是舒畅:

// point to layer
Neuron n6;
NeuronLayer l1(5);
n6.connect_to(l1);
std::cout<<n6<<std::endl;

参考

https://leanpub.com/design-pa...
http://en.cppreference.com/w/...

相关文章
相关标签/搜索