【設計模式】組合模式之神經網絡應用

問題引入

試想,一個對象自己和由對象組成的一個集合都須要支持邏輯上相同的操做,實際實現可能不同.既然在語義這個更高級抽象能夠把二者統一,那麼若是這二者都繼承同一個基類豈不是更好? 一個能夠類比的例子是,目錄自己可能包含多個目錄,在沒有子目錄的狀況下,目錄的遍歷是遍歷該目錄下的文件,反之,則須要遞歸的遍歷子目錄了,而對於掃描的目錄的客戶端來講(調用此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/...

相關文章
相關標籤/搜索