命令式編程VS符號式編程

轉自:

https://blog.csdn.net/z0n1l2/article/details/80873608

https://www.cnblogs.com/anliven/p/10349356.html

什麼是symblic/imperative style編程

使用過python或C++對imperative programs比較瞭解.imperative-stype programs在運行時計算.大部分python代碼都是imperative.比如下面的例子

import numpy as np
    a = np.ones(10)
    b = np.ones(10) * 2
    c = b * a
    d = c + 1

當程序執行到 c=b∗a時,代碼開始做對應的數值計算.
symbolic programs於此不同.symbolic-stype program中,需要先給出一個函數的定義(可能十分複雜).當我們定義這個函數時,並不會做真正的數值計算.這類函數的定義中使用數值佔位符.當給定真正的輸入後,纔會對這個函數進行編譯計算.上面的例子用symbolic-stype program重新寫

A = Variable('A')
    B = Variable('B')
    C = B * A
    D = C + Constant(1)
    # compiles the function
    f = compile(D)
    d = f(A=np.ones(10), B=np.ones(10)*2)

上述代碼中,語句C=B∗A並不會觸發真正的數值計算,但會生成一個計算圖(也稱symbolic graph)描述這個計算.下圖時計算D的計算圖

這裏寫圖片描述

大部分symbolic-style program都顯性或隱性的包含一個編譯的步驟,把計算轉換成可以調用的函數.上面的例子中,數值計算僅僅在代碼最後一行進行.symbolic program一個重要特點是其明確有構建計算圖和生成可執行代碼兩個步驟.對於神經網絡,一般會用一個計算圖描述整個模型.
其他流行深度學習框架中,Torch/Chainer/Minerva使用imperative style.symbolic-styple的框架包括Theano/CGT/TensorFlow. CXXNet/Caffe這一類依賴配置文件的框架也看作symbolic-style庫.此時配置文件被當作計算圖的定義.下面我們對比一下二者的優劣:

命令式編程(imperative style programs)

使用編程語句改變程序狀態,明確輸入變量,並根據程序邏輯逐步運算。

  • 易於理解:在Python裏使用命令式編程時,大部分代碼編寫起來都很直觀。
  • 容易調試:可以很方便地進行單步跟蹤,獲取並分析所有中間變量,或者使用Python的調試工具。

雖然使用命令式編程很方便,但它的運行可能很慢,會存在重複調用函數和長時間保存變量值等問題,耗費內存。

用python調用imperative-style庫十分簡單,編寫方式和普通的python代碼一樣,在合適的位置調用庫的代碼實現加速.如果用python調用symbolic-style庫,代碼結構將出現一些變化,比如iteration可能無法使用.嘗試把下面的例子轉換成symbolic-style

 a = 2
    b = a + 1
    d = np.zeros(10)
    for i in range(d):
        d += np.zeros(10)

如果symblic-style API不支持for循環,轉換就沒那個直接.不能用python的編碼思路調用symblic-style庫.需要利用symblic API定義的domain-specific-language(DSL).深度學習框架會提供功能強大的DSL,把神經網絡轉化成可被調用的計算圖.
感覺上imperative program更加符合習慣,使用更加簡單.例如可以在任何位置打印出變量的值,輕鬆使用符合習慣的流程控制語句和循環語句.

符號式編程更加有效

符號式編程(symbolic style programs)

通常在計算流程完全定義好後才被執行。

  • 更高效:在編譯的時候系統容易做更多優化。
  • 更容易移植:符號式編程可以將程序變成一個與Python無關的格式,從而可以使程序在非Python環境下運行,以避開Python解釋器的性能問題。

一般來說,符號式編程的程序需要下面3個步驟:

  1. 定義計算流程;
  2. 把計算流程編譯成可執行的程序;
  3. 給定輸入,調用編譯好的程序執行。

由於在編譯時系統能夠完整地獲取整個程序,因此有更多空間優化計算,不僅減少了函數調用,還節省了內存。
深度學習框架TensorFlow和Theano採用了符號式編程的方法。

既然imperative pragrams更加靈活,和計算機原生語言更加貼合,那麼爲什麼很多深度學習框架使用symbolic風格? 最主要的原因式效率,內存效率和計算效率都很高.比如下面的例子

import numpy as np
    a = np.ones(10)
    b = np.ones(10) * 2
    c = b * a
    d = c + 1
    ...

這裏寫圖片描述

如果數組每個元素內存中佔據8字節,在python中需要多少內存?
對於imperative programs中,需要在每一行上都分配必要的內存.一共4個數組,每個數組10個元素,一共4∗10∗8=320

字節. 如果事先知道只有d是需要的結果,構造計算圖時可以重複利用一些中間變量的空間.比如利用原址計算,我們可以把b的內存借給c使用,同樣c的內存可以給d用,如此可以節省一半內存,僅僅需要2∗10∗8=160字節

symbolic programs限制更多.因爲只需要d,構建計算圖後,一些中間量,比如c

,的值將無法看到.

通過symbolic program,使用原址計算可以安全的重用內存.但犧牲了對c

的訪問可能. imperative program可以處理各種訪問可能.如果在python執行上述例子,任何中間量都可以方便訪問.

symbolic program還可以通過operation folding優化計算.在上述的例子中,乘法和加法可以展成一個操作,如下圖所示.

這裏寫圖片描述

如果在GPU上運算,計算圖只需要一個kernel,節省了一個kernel.在很多優化庫,比如caffe/CXXNet,人工編碼進行此類優化操作. operation folding可以提高計算效率.

imperative program中不能自動operation folding,因爲不知道中間變量是否會被訪問到. symbolic program中可以做operation folding,因爲獲得了完整的計算圖,而且明確哪些量以後會被訪問,哪些量以後都不會被訪問.

計算圖/符號圖(computation graph/symbolic graph)

符號式編程將計算過程抽象爲一張計算圖(符號圖)來描述整個計算過程。

  • 易於描述計算過程,所有輸入節點、運算節點、輸出節點均符號化處理。
  • 通過建立輸入節點到輸出節點的傳遞閉包,從輸入節點出發,沿着傳遞閉包完成數值計算和數據流動,直到達到輸出節點。
  • 經過計算圖優化,以數據(計算)流方式完成,節省內存空間使用,計算速度快,但不適合程序調試,通常不用於編程語言中。

大多數符號式程序都會顯式地或是隱式地包含編譯步驟,將計算圖轉換爲能被調用的函數,在代碼的最後一行才真正地進行運算。
也就是說,符號式程序清晰地將定義運算圖的步驟與編譯運算的步驟分割開來。

混合式編程

簡而言之,命令式編程容易理解和調試,命令語句基本沒有優化,按原有邏輯執行。
符號式編程涉及較多的嵌入和優化,不容易理解和調試,但運行速度有同比提升。

有沒有可能既得到命令式編程的好處,又享受符號式編程的優勢?
開發者們認爲,用戶應該用純命令式編程進行開發和調試;
當需要產品級別的計算性能和部署時,用戶可以將大部分命令式程序轉換成符號式程序來運行。
深度學習框架caffe和mxnet採用了兩種編程模式混合的方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章