首頁>技術>

本文連結:https://blog.csdn.net/qq_43790749/article/details/111770294

一、什麼是拓撲排序在圖論中,拓撲排序(Topological Sorting)是一個有向無環圖(DAG, Directed Acyclic Graph)的所有頂點的線性序列。且該序列必須滿足下面兩個條件:1.每個頂點出現且只出現一次。2.若存在一條從頂點 A 到頂點 B 的路徑,那麼在序列中頂點 A 出現在頂點 B 的前面。有向無環圖(DAG)才有拓撲排序,非DAG圖沒有拓撲排序一說。

例如,下面這個圖:

它是一個 DAG 圖,那麼如何寫出它的拓撲排序呢?這裡說一種比較常用的方法:

於是,得到拓撲排序後的結果是 { 1, 2, 4, 3, 5 }。通常,一個有向無環圖可以有一個或多個拓撲排序序列。二、拓撲排序的應用拓撲排序通常用來“排序”具有依賴關係的任務。比如,如果用一個DAG圖來表示一個工程,其中每個頂點表示工程中的一個任務,用有向邊表示在做任務 B 之前必須先完成任務 A。故在這個工程中,任意兩個任務要麼具有確定的先後關係,要麼是沒有關係,絕對不存在互相矛盾的關係(即環路)。

深度學習的各節點正好構成了這種拓撲關係。因此可以使用拓撲排序的方法對節點進行排序,從而實現自動求導。三、神經網路拓撲圖的構建實現程式碼如下:

import networkx as nxnode_x, node_k1, node_b1 = 'x', 'k1', 'b1'node_k2, node_b2 = 'k2', 'b2'node_linear_01, node_linear_02, node_sigmoid = 'linear_01', 'linear_02', 'sigmoid'node_loss = 'loss'computing_graph = { # represent model    node_x: [node_linear_01],   node_k1: [node_linear_01],   node_b1: [node_linear_01],   node_linear_01: [node_sigmoid],   node_sigmoid: [node_linear_02],   node_k2: [node_linear_02],   node_b2: [node_linear_02],   node_linear_02: [node_loss],}nx.draw(graph, layout, with_labels=True, node_color='red')plt.savefig("linear_simple.png")

視覺化結果如下

四、拓撲排序實現實現程式碼如下:

#使用拓撲排序找到網路節點的前向計算順序(反向傳播反過來就行)def toplogical(graph):    sorted_graph_nodes = []    while graph:         all_nodes_have_inputs = set()        all_nodes_have_outputs = set()        for have_output_node, have_inputs in graph.items():            all_nodes_have_outputs.add(have_output_node)#包括只有輸出的節點 和既有輸入又有輸出的點            all_nodes_have_inputs |= set(have_inputs) #有輸入的點:包括既有輸入和輸出的點 和只有輸入的點(末尾終點)        need_removed_nodes = all_nodes_have_outputs - all_nodes_have_inputs #減去之後留下只有輸出的節點         if need_removed_nodes:            node = random.choice(list(need_removed_nodes))#隨機刪去一個節點            visited_next = [node]            if len(graph) == 1: visited_next += graph[node]  #當最後刪到只留一個節點                #有輸出的節點)的時候,那麼需要把這個節點對應的輸出節點也加上                #否則會漏掉這個點            graph.pop(node)            sorted_graph_nodes += visited_next            for _, links in graph.items():                if node in links: links.remove(node)#如果刪除的節點在別的節點的連線關係內,那麼把他從連線關係裡刪除        else:            break    return sorted_graph_nodesvisited_orde = toplogical(computing_graph)visited_orde#輸出:['b2', 'k1', 'k2', 'b1', 'x', 'linear_01', 'sigmoid', 'linear_02', 'loss']

首先進行前向傳播過程實現實現程式碼如下:

def visited_procedure(graph, posotion, visited_order, step=None, sub_plot_index=None, colors=('red', 'green'), node_size=5):    changed = visited_order[:step] if step is not None else visited_order    before, after = colors    color_map = [after if c in changed else before for c in graph]        nx.draw(graph, posotion, node_color=color_map, with_labels=True, ax=sub_plot_index, node_size=node_size)

前向傳播

dimension = int(len(visited_order) ** 0.5)fig, ax = plt.subplots(dimension, dimension + 1, figsize=(15, 15))for i in range(len(visited_order) + 1):   ix = np.unravel_index(i, ax.shape)   plt.sca(ax[ix])   ax[ix].title.set_text('Feed Forward:{}'.format(i))   visited_procedure(graph, layout, visited_order, step=i, sub_plot_index=ax[ix], node_size=60)plt.savefig("feed_forward.png")

反向傳播

dimension = int(len(visited_order) ** 0.5)fig, ax = plt.subplots(dimension, dimension + 1, figsize=(15, 15))for i in range(len(visited_order) + 1):    ix = np.unravel_index(i, ax.shape)    plt.sca(ax[ix])    ax[ix].title.set_text('Back Forward:{}'.format(i))    visited_procedure(graph, layout, visited_order[::-1], step=i, sub_plot_index=ax[ix], colors=('green', 'red'), node_size=60)plt.savefig("back_foward.png")

根據拓撲排序的結果,那麼就可以對每個模組的輸出結果和梯度大小進行定義,然後根據鏈式法則和相加法則求導, 從而實現前饋過程和反向傳播過程。五、各節點的構建實現程式碼如下

class Node:    def __init__(self, inputs = [],name=None):        self.name = name        self.outputs = []        self.inputs = inputs  #這個node的輸入         for n in self.inputs:            n.outputs.append(self)   #這個輸入的輸出就是這個node本身        self.value = None  #每個節點對應有一個輸出值        self.gradients = {}  #每個節點可能對應有多個輸出,從而對應有多個梯度    def forward(self):        pass    def backward(self):        pass      def __repr__(self):        return '#Node: {}//'.format(self.name)  class Input(Node):    passclass Placeholder(Node):  #佔位符,給x,k或者b,給一個初始值    """"    def __init__(self, name):        Node.__init__(self, name=name)            def forward(self, value=None):        if value: self.value = value  #初始值,或者是更新的權重和偏差值    def backward(self):        self.gradients = {}  #梯度            for n in self.outputs:            self.gradients[self] += n.gradients[self]         #如果這個節點有多個輸出(那就有多個梯度),那就把每個路徑的梯度加起來(輸入是固定的)     """    passclass Linear(Node):    """"    def __init__(self, x=None, weight=None, bias=None, name=None):            Node.__init__(self, [x, weights, bias], name=name)    def forward(self):        self.value = self.inputs[0].value * self.inputs[1].value + self.inputs[2].value       def backward(self):        k, x, b = self.inputs[1], self.inputs[0], self.inputs[2]             for n in self.outputs:            self.gradients[k] += n.gradients[self] * x.value  #n.gradients[self]:輸出點對輸入的梯度            self.gradients[b] += n.gradients[self] * 1  #如果這個節點有多個輸出(那就有多個梯度),那就把每個路徑的梯度加起來(輸入是固定的)            self.gradients[x] += n.gradients[self] * k.value    """    pass   class Sigmoid(Node):    passclass L2_loss(Node):    pass

對這些節點進行排序

NODE_X = Input(name='x')NODE_LINEAR = Linear(name='linear')NODE_K1 = Placeholder(name='k1')NODE_b1 = Placeholder(name='b2')NODE_Loss = L2_loss(name='loss')simple_graph = {   NODE_X: [NODE_LINEAR],   NODE_K1: [NODE_LINEAR],   NODE_b1: [NODE_LINEAR],   NODE_LINEAR: [NODE_Loss]}toplogical(simple_graph) #排列結果:[#Node: k1//, #Node: x//, #Node: b2//, #Node: linear//, #Node: loss//]

8
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 設計模式(三)——簡單的狀態模式代替if-else