在GPU上运行,性能是NumPy的11倍,这个Python库你值得拥有
導讀:NumPy是數據計算的基礎,更是深度學習框架的基石。但如果直接使用NumPy計算大數據,其性能已成為一個瓶頸。
隨著數據爆炸式增長,尤其是圖像數據、音頻數據等數據的快速增長,迫切需要突破NumPy性能上的瓶頸。需求就是強大動力!通過大家的不懈努力,在很多方面取得可喜進展,如硬件有GPU,軟件有Theano、Keras、TensorFlow,算法有卷積神經網絡、循環神經網絡等。
Theano是Python的一個庫,為開源項目,在2008年,由Yoshua Bengio領導的加拿大蒙特利爾理工學院LISA實驗室開發。對于解決大量數據的問題,使用Theano可能獲得與手工用C實現差不多的性能。另外通過利用GPU,它能獲得比CPU上快很多數量級的性能。
至于Theano是如何實現性能方面的跨越,如何用“符號計算圖”來運算等內容,本文都將有所涉獵,但限于篇幅無法深入分析,只做一些基礎性的介紹。涵蓋的主要內容:
如何安裝Theano。
符號變量是什么。
如何設計符號計算圖。
函數的功能。
共享變量的妙用。
作者:吳茂貴,王冬,李濤,楊本法
如需轉載請聯系大數據(ID:hzdashuju)
Theano開發者在2010年公布的測試報告中指出:在CPU上執行程序時,Theano程序性能是NumPy的1.8倍,而在GPU上是NumPy的11倍。這還是2010年的測試結果,近些年無論是Theano還是GPU,性能都有顯著提高。
這里我們把Theano作為基礎來講,除了性能方面的跨越外,它還是“符合計算圖”的開創者,當前很多優秀的開源工具,如TensorFlow、Keras等,都派生于或借鑒了Theano的底層設計。所以了解Theano的使用,將有助于我們更好地學習TensorFlow、Keras等其他開源工具。
01 安裝
這里主要介紹Linux+Anaconda+theano環境的安裝說明,在CentOS或Ubuntu環境下,建議使用Python的Anaconda發行版,后續版本升級或添加新模塊可用Conda工具。當然也可用pip進行安裝。但最好使用工具來安裝,這樣可以避免很多程序依賴的麻煩,而且日后的軟件升級維護也很方便。
Theano支持CPU、GPU,如果使用GPU還需要安裝其驅動程序如CUDA等,限于篇幅,這里只介紹CPU的,有關GPU的安裝,大家可參考:
http://www.deeplearning.net/software/theano/install.html
以下為主要安裝步驟:
1. 安裝anaconda
從anaconda官網下載Linux環境最新的軟件包,Python版本建議選擇3系列的,2系列后續將不再維護。
anaconda官網:
https://www.anaconda.com/download/
下載文件為一個sh程序包,如Anaconda3-4.3.1-Linux-x86_64.sh,然后在下載目錄下運行如下命令:
安裝過程中按enter或y即可,安裝完成后,程序提示是否把anaconda的binary加入到.bashrc配置文件中,加入后運行python、ipython時將自動使用新安裝的Python環境。
安裝完成后,你可用conda list命令查看已安裝的庫:
安裝成功的話,應該能看到numpy、scipy、matplotlib、conda等庫。
2. 安裝theano
利用conda 來安裝或更新程序:
3. 測試
先啟動Python,然后導入theano模塊,如果不報錯,說明安裝成功。
Python?3.6.0?|Anaconda?custom?(64-bit)|?(default,?Dec?23?2016,?12:22:00)?
[GCC?4.4.7?20120313?(Red?Hat?4.4.7-1)]?on?linux
Type?"help",?"copyright",?"credits"?or?"license"?for?more?information.
>>>?import?theano
>>>
02 符號變量
存儲數據需要用到各種變量,那Theano是如何使用變量的呢?Theano用符號變量TensorVariable來表示變量,又稱為張量(Tensor)。
張量是Theano的核心元素(也是TensorFlow的核心元素),是Theano表達式和運算操作的基本單位。張量可以是標量(scalar)、向量(vector)、矩陣(matrix)等的統稱。
具體來說,標量就是我們通常看到的0階的張量,如12,a等,而向量和矩陣分別為1階張量和2階的張量。
如果通過這些概念,你還不很清楚,沒有關系,可以結合以下實例來直觀感受一下。
首先定義三個標量:一個代表輸入x、一個代表權重w、一個代表偏移量b,然后計算這些標量運算結果z=x*w+b,Theano代碼實現如下:
import?theano
from?theano?import?tensor?as?T
#初始化張量
x=T.scalar(name='input',dtype='float32')
w=T.scalar(name='weight',dtype='float32')
b=T.scalar(name='bias',dtype='float32')
z=w*x+b
#編譯程序
net_input=theano.function(inputs=[w,x,b],outputs=z)
#執行程序
print('net_input:?%2f'%?net_input(2.0,3.0,0.5))
打印結果:
通過以上實例我們不難看出,Theano本身是一個通用的符號計算框架,與非符號架構的框架不同,它先使用tensor variable初始化變量,然后將復雜的符號表達式編譯成函數模型,最后運行時傳入實際數據進行計算。
整個過程涉及三個步驟:定義符號變量,編譯代碼,執行代碼。這節主要介紹第一步如何定義符號變量,其他步驟將在后續小節介紹。
如何定義符號變量?或定義符號變量有哪些方式?在Theano中定義符號變量的方式有三種:使用內置的變量類型、自定義變量類型、轉換其他的變量類型。具體如下:
1. 使用內置的變量類型創建
目前Theano支持7種內置的變量類型,分別是標量(scalar)、向量(vector)、行(row)、列(col)、矩陣(matrix)、tensor3、tensor4等。其中標量是0階張量,向量為1階張量,矩陣為2階張量等,以下為創建內置變量的實例:
from?theano?import?tensor?as?T
x=T.scalar(name='input',dtype='float32')
data=T.vector(name='data',dtype='float64')
其中,name指定變量名字,dtype指變量的數據類型。
2. 自定義變量類型
內置的變量類型只能處理4維及以下的變量,如果需要處理更高維的數據時,可以使用Theano的自定義變量類型,具體通過TensorType方法來實現:
from?theano?import?tensor?as?T
mytype=T.TensorType('float64',broadcastable=(),name=None,sparse_grad=False)
其中broadcastable是True或False的布爾類型元組,元組的大小等于變量的維度,如果為True,表示變量在對應維度上的數據可以進行廣播,否則數據不能廣播。
廣播機制(broadcast)是一種重要機制,有了這種機制,就可以方便地對不同維的張量進行運算,否則,就要手工把低維數據變成高維,利用廣播機制系統自動復制等方法把低維數據補齊(MumPy也有這種機制)。以下我們通過圖2-1所示的一個實例來說明廣播機制原理。
▲圖2-1?廣播機制
圖2-1中矩陣與向量相加的具體代碼如下:
import?numpy?as?np
import?theano.tensor?as?T
r?=?T.row()
r.broadcastable
#?(True,?False)
mtr?=?T.matrix()
mtr.broadcastable
#?(False,?False)
f_row?=?theano.function([r,?mtr],?[r?+?mtr])
R?=?np.arange(1,3).reshape(1,2)
print(R)
#array([[1,?2]])
M?=?np.arange(1,7).reshape(3,?2)
print(M)
#array([[1,?2],
#???????[3,?4],
#???????[5,?6]])
f_row(R,?M)
#[array([[?2.,??4.],
#????????[?4.,??6.],
#????????[?6.,??8.]])]
3. 將Python類型變量或者NumPy類型變量轉化為Theano共享變量
共享變量是Theano實現變量更新的重要機制,后面我們會詳細講解。要創建一個共享變量,只要把一個Python對象或NumPy對象傳遞給shared函數即可,如下所示:
import?numpy?as?np
import?theano.tensor?as?T
data=np.array([[1,2],[3,4]])
shared_data=theano.shared(data)
type(shared_data)
03 符號計算圖模型
符號變量定義后,需要說明這些變量間的運算關系,那如何描述變量間的運算關系呢?Theano實際采用符號計算圖模型來實現。首先創建表達式所需的變量,然后通過操作符(op)把這些變量結合在一起,如前文圖2-1所示。
Theano處理符號表達式時是通過把符號表達式轉換為一個計算圖(graph)來處理(TensorFlow也使用了這種方法,等到我們介紹TensorFlow時,大家可對比一下),符號計算圖的節點有:variable、type、apply和op。
variable節點:即符號的變量節點,符號變量是符號表達式存放信息的數據結構,可以分為輸入符號和輸出符號。
type節點:當定義了一種具體的變量類型以及變量的數據類型時,Theano為其指定數據存儲的限制條件。
apply節點:把某一種類型的符號操作符應用到具體的符號變量中,與variable不同,apply節點無須由用戶指定,一個apply節點包括3個字段:op、inputs、outputs。
op節點:即操作符節點,定義了一種符號變量間的運算,如+、-、sum()、tanh()等。
Theano是將符號表達式的計算表示成計算圖。這些計算圖是由Apply 和 Variable將節點連接而組成,它們分別與函數的應用和數據相連接。操作由op 實例表示,而數據類型由type 實例表示。
下面這段代碼和圖2-2說明了這些代碼所構建的結構。借助這個圖或許有助于你進一步理解如何將這些內容擬合在一起:
import?numpy?as?np
import?theano.tensor?as?T
x?=?T.dmatrix('x')??
y?=?T.dmatrix('y')??
z?=?x?+?y??
▲圖2-2 符號計算圖
圖2-2中箭頭表示指向Python對象的引用。中間大的長方形是一個 Apply 節點,3個圓角矩形(如X)是 Variable 節點,帶+號的圓圈是ops,3個圓角小長方形(如matrix)是Types。
在創建 Variables 之后,應用 Apply ops得到更多的變量,這些變量僅僅是一個占位符,在function中作為輸入。變量指向 Apply 節點的過程是用來表示函數通過owner 域來生成它們 。這些Apply節點是通過它們的inputs和outputs域來得到它們的輸入和輸出變量。
x和y的owner域的指向都是None,這是因為它們不是另一個計算的結果。如果它們中的一個變量是另一個計算的結果,那么owner域將會指向另一個藍色盒。
04 函數
上節我們介紹了如何把一個符號表達式轉化為符號計算圖,這節我們介紹函數的功能,函數是Theano的一個核心設計模塊,它提供一個接口,把函數計算圖編譯為可調用的函數對象。前面介紹了如何定義自變量x(不需要賦值),這節介紹如何編寫函數方程。
1. 函數定義的格式
先來看一下函數格式示例:
這里參數看起來很多,但一般只用到三個:inputs表示自變量;outputs表示函數的因變量(也就是函數的返回值);還有一個比較常用的是updates參數,它一般用于神經網絡共享變量參數更新,通常以字典或元組列表的形式指定。
此外,givens是一個字典或元組列表,記為[(var1,var2)],表示在每一次函數調用時,在符號計算圖中,把符號變量var1節點替換為var2節點,該參數常用來指定訓練數據集的batch大小。
下面我們看一個有多個自變量,同時又有多個因變量的函數定義例子:
x,?y?=theano.tensor.fscalars('x',?'y')??
z1=?x?+?y??
z2=x*y??
#定義x、y為自變量,z1、z2為函數返回值(因變量)
f?=theano.function([x,y],[z1,z2])??
#返回當x=2,y=3的時候,函數f的因變量z1,z2的值
print(f(2,3))
打印結果:
在執行theano.function()時,Theano進行了編譯優化,得到一個end-to-end的函數,傳入數據調用f(2,3)時,執行的是優化后保存在圖結構中的模型,而不是我們寫的那行z=x+y,盡管二者結果一樣。
這樣的好處是Theano可以對函數f進行優化,提升速度;壞處是不方便開發和調試,由于實際執行的代碼不是我們寫的代碼,所以無法設置斷點進行調試,也無法直接觀察執行時中間變量的值。
2. 自動求導
有了符號計算,自動計算導數就很容易了。tensor.grad()唯一需要做的就是從outputs逆向遍歷到輸入節點。對于每個op,它都定義了怎么根據輸入計算出偏導數。使用鏈式法則就可以計算出梯度了。利用Theano求導時非常方便,可以直接利用函數theano.grad(),比如求s函數的導數:
以下代碼實現當x=3的時候,求s函數的導數:
x?=theano.tensor.fscalar('x')#定義一個float類型的變量x??
y=?1?/?(1?+?theano.tensor.exp(-x))#定義變量y??
dx=theano.grad(y,x)#偏導數函數??
f=?theano.function([x],dx)#定義函數f,輸入為x,輸出為s函數的偏導數??
print(f(3))#計算當x=3的時候,函數y的偏導數
打印結果:
3. 更新共享變量參數
在深度學習中通常需要迭代多次,每次迭代都需要更新參數。Theano如何更新參數呢?
在theano.function函數中,有一個非常重要的參數updates。updates是一個包含兩個元素的列表或tuple,一般示例為updates=[old_w,new_w],當函數被調用的時候,會用new_w替換old_w,具體看下面這個例子。
w=?theano.shared(1)#定義一個共享變量w,其初始值為1??
x=theano.tensor.iscalar('x')??
f=theano.function([x],?w,?updates=[[w,?w+x]])#定義函數自變量為x,因變量為w,當函數執行完畢后,更新參數w=w+x??
print(f(3))#函數輸出為w??
print(w.get_value())#這個時候可以看到w=w+x為4
打印結果:
在求梯度下降的時候,經常用到updates這個參數。比如updates=[w,w-α*(dT/dw)],其中dT/dw就是梯度下降時,代價函數對參數w的偏導數,α是學習速率。為便于大家更全面地了解Theano函數的使用方法,下面我們通過一個邏輯回歸的完整實例來說明:
import?theano??
import?theano.tensor?as?T??
rng?=?np.random??
#?我們為了測試,自己生成10個樣本,每個樣本是3維的向量,然后用于訓練?
N?=?10
feats?=?3
D?=?(rng.randn(N,?feats).astype(np.float32),?rng.randint(size=N,?low=0,?high=2).astype(np.float32))
#?聲明自變量x、以及每個樣本對應的標簽y(訓練標簽)??
x?=?T.matrix("x")
y?=?T.vector("y")
#隨機初始化參數w、b=0,為共享變量??
w?=?theano.shared(rng.randn(feats),?name="w")??
b?=?theano.shared(0.,?name="b")
#構造代價函數
p_1?=?1?/?(1?+?T.exp(-T.dot(x,?w)?-?b))???#?s激活函數??
xent?=?-y?*?T.log(p_1)?-?(1-y)?*?T.log(1-p_1)?#?交叉商代價函數
cost?=?xent.mean()?+?0.01?*?(w?**?2).sum()#?代價函數的平均值+L2正則項以防過擬合,其中權重衰減系數為0.01??
gw,?gb?=?T.grad(cost,?[w,?b])?????????????#對總代價函數求參數的偏導數??
prediction?=?p_1?>?0.5????????????????????#?大于0.5預測值為1,否則為0.
train?=?theano.function(inputs=[x,y],outputs=[prediction,?xent],updates=((w,?w?-?0.1?*?gw),?(b,?b?-?0.1?*?gb)))#訓練所需函數
predict?=?theano.function(inputs=[x],?outputs=prediction)#測試階段函數??
#訓練??
training_steps?=?1000??
for?i?in?range(training_steps):??
????pred,?err?=?train(D[0],?D[1])??
????print?(err.mean())#查看代價函數下降變化過程??
05 條件與循環
編寫函數需要經常用到條件語句或循環語句,這節我們就簡單介紹Theano如何實現條件判斷或邏輯循環。
1. 條件判斷
Theano是一種符號語言,條件判斷不能直接使用Python的if語句。在Theano可以用ifelse和switch來表示判定語句。這兩個判定語句有何區別呢?
switch對每個輸出變量進行操作,ifelse只對一個滿足條件的變量操作。比如對語句:
如果滿足條件,則switch既執行ift也執行iff。而對語句:
ifelse只執行ift或者只執行iff。
下面通過一個示例進一步說明:
from?theano.ifelse?import?ifelse??
import?theano,time,numpy??
a,b=T.scalars('a','b')??
x,y=T.matrices('x','y')??
z_switch=T.switch(T.lt(a,b),T.mean(x),T.mean(y))#lt:a?<?b???
z_lazy=ifelse(T.lt(a,b),T.mean(x),T.mean(y))??
#optimizer:optimizer的類型結構(可以簡化計算,增加計算的穩定性)??
#linker:決定使用哪種方式進行編譯(C/Python)?
f_switch?=?theano.function([a,?b,?x,?y],?z_switch,mode=theano.Mode(linker='vm'))??
f_lazyifelse?=?theano.function([a,?b,?x,?y],?z_lazy,mode=theano.Mode(linker='vm'))??
val1?=?0.??
val2?=?1.??
big_mat1?=?numpy.ones((1000,?100))??
big_mat2?=?numpy.ones((1000,?100))??
n_times?=?10??
tic?=?time.clock()??
for?i?in?range(n_times):??
????f_switch(val1,?val2,?big_mat1,?big_mat2)??
print('time?spent?evaluating?both?values?%f?sec'?%?(time.clock()?-?tic))??
tic?=?time.clock()??
for?i?in?range(n_times):??
????f_lazyifelse(val1,?val2,?big_mat1,?big_mat2)??
print('time?spent?evaluating?one?value?%f?sec'?%?(time.clock()?-?tic))??
打印結果:
time?spent?evaluating?one?value?0.007501?sec?
2. 循環語句
scan是Theano中構建循環Graph的方法,scan是個靈活復雜的函數,任何用循環、遞歸或者跟序列有關的計算,都可以用scan完成。其格式如下:
參數說明:
fn:一個lambda或者def函數,描述了scan中的一個步驟。除了outputs_info,fn可以返回sequences變量的更新updates。fn的輸入變量的順序為sequences中的變量、outputs_info的變量、non_sequences中的變量。如果使用了taps,則按照taps給fn喂變量。taps的詳細介紹會在后面的例子中給出。
sequences:scan進行迭代的變量,scan會在T.arange()生成的list上遍歷,例如下面的polynomial 例子。
outputs_info:初始化fn的輸出變量,和輸出的shape一致。如果初始化值設為None,表示這個變量不需要初始值。
non_sequences:fn函數用到的其他變量,迭代過程中不可改變(unchange)。
n_steps:fn的迭代次數。
下面通過一個例子解釋scan函數的具體使用方法。
代碼實現思路是:先定義函數one_step,即scan里的fn,其任務就是計算多項式的一項,scan函數返回的result里會保存多項式每一項的值,然后我們對result求和,就得到了多項式的值。
import?theano.tensor?as?T
import?numpy?as?np
#?定義單步的函數,實現a*x^n
#?輸入參數的順序要與下面scan的輸入參數對應
def?one_step(coef,?power,?x):
????return?coef?*?x?**?power
coefs?=?T.ivector()??#?每步變化的值,系數組成的向量
powers?=?T.ivector()?#?每步變化的值,指數組成的向量
x?=?T.iscalar()??????#?每步不變的值,自變量
#?seq,out_info,non_seq與one_step函數的參數順序一一對應
#?返回的result是每一項的符號表達式組成的list
result,?updates?=?theano.scan(fn?=?one_step,
???????????????????????sequences?=?[coefs,?powers],
???????????????????????outputs_info?=?None,
???????????????????????non_sequences?=?x)
#?每一項的值與輸入的函數關系
f_poly?=?theano.function([x,?coefs,?powers],?result,?allow_input_downcast=True)
coef_val?=?np.array([2,3,4,6,5])
power_val?=?np.array([0,1,2,3,4])
x_val?=?10
print("多項式各項的值:?",f_poly(x_val,?coef_val,?power_val))
#scan返回的result是每一項的值,并沒有求和,如果我們只想要多項式的值,可以把f_poly寫成這樣:
#?多項式每一項的和與輸入的函數關系
f_poly?=?theano.function([x,?coefs,?powers],?result.sum(),?allow_input_downcast=True)
print("多項式和的值:",f_poly(x_val,?coef_val,?power_val))
打印結果:
多項式和的值:?56432
06 共享變量
共享變量(shared variable)是實現機器學習算法參數更新的重要機制。shared函數會返回共享變量。這種變量的值在多個函數可直接共享。可以用符號變量的地方都可以用共享變量。
但不同的是,共享變量有一個內部狀態的值,這個值可以被多個函數共享。它可以存儲在顯存中,利用GPU提高性能。我們可以使用get_value和set_value方法來讀取或者修改共享變量的值,使用共享變量實現累加操作。
import?theano.tensor?as?T
from?theano?import?shared
import?numpy?as?np
#定義一個共享變量,并初始化為0
state?=?shared(0)
inc?=?T.iscalar('inc')
accumulator?=?theano.function([inc],?state,?updates=[(state,?state+inc)])
#?打印state的初始值
print(state.get_value())
accumulator(1)?#?進行一次函數調用
#?函數返回后,state的值發生了變化
print(state.get_value())?
這里state是一個共享變量,初始化為0,每次調用accumulator(),state都會加上inc。共享變量可以像普通張量一樣用于符號表達式,另外,它還有自己的值,可以直接用.get_value()和.set_value()方法來訪問和修改。
上述代碼引入了函數中的updates參數。updates參數是一個list,其中每個元素是一個元組(tuple),這個tuple的第一個元素是一個共享變量,第二個元素是一個新的表達式。updatas中的共享變量會在函數返回后更新自己的值。
updates的作用在于執行效率,updates多數時候可以用原地(in-place)算法快速實現,在GPU上,Theano可以更好地控制何時何地給共享變量分配空間,帶來性能提升。最常見的神經網絡權值更新,一般會用update實現。
07 小結
Theano基于NumPy,但性能方面又高于NumPy。因Theano采用了張量(Tensor)這個核心元素,在計算方面采用符號計算模型,而且采用共享變量、自動求導、利用GPU等適合于大數據、深度學習的方法,其他很多開發項目也深受這些技術和框架影響。
關于作者:吳茂貴,BI和大數據專家,就職于中國外匯交易中心,在BI、數據挖掘與分析、數據倉庫、機器學習等領域有超過20年的工作經驗,在Spark機器學習、TensorFlow深度學習領域大量的實踐經驗。
王冬,任職于博世(中國)投資有限公司,負責Bosch企業BI及工業4.0相關大數據和數據挖掘項目。對機器學習、人工智能有多年實踐經驗。
李濤,參與過多個人工智能項目,如研究開發服務機器人、無人售后店等項目。熟悉python、caffe、TensorFlow等,對深度學習、尤其對計算機視覺方面有較深理解。
楊本法,高級算法工程師,在機器學習、文本挖掘、可視化等領域有多年實踐經驗。熟悉Hadoop、Spark生態圈的相關技術,對Python有豐富的實戰經驗。
本文摘編自《Python深度學習:基于TensorFlow》,經出版方授權發布。
延伸閱讀《Python深度學習:基于TensorFlow》
點擊上圖了解及購買
轉載請聯系微信:DoctorData
推薦語:從Python和數學,到機器學習和TensorFlow,再到深度學習的應用和擴展,為深度學習提供全棧解決方案。?
據統計,99%的大咖都完成了這個神操作
▼
更多精彩
在公眾號后臺對話框輸入以下關鍵詞
查看更多優質內容!
PPT?|?報告?|?讀書?|?書單?|?干貨?
大數據?|?揭秘?|?Python?|?可視化
人工智能?|?機器學習?|?深度學習?|?神經網絡
AI?|?1024?|?段子?|?區塊鏈?|?數學
猜你想看
壓力、焦慮遠遠超出全國平均值,近4成程序員心理不健康?
從Python安裝到語法基礎,這才是小白都能懂的爬蟲教程
「π」里藏著所有人的銀行卡密碼和生日?
什么樣的數據才有價值?應該怎樣收集和處理?終于有人講明白了
Q:?關于Theano,你還有哪些經驗和技巧?
歡迎留言與大家分享
覺得不錯,請把這篇文章分享給你的朋友
轉載 / 投稿請聯系:baiyu@hzbook.com
更多精彩,請在后臺點擊“歷史文章”查看
點擊閱讀原文,了解更多
總結
以上是生活随笔為你收集整理的在GPU上运行,性能是NumPy的11倍,这个Python库你值得拥有的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2019上半年这10本新书,技术大牛们都
- 下一篇: TIOBE 12月编程语言排行榜:Pyt