Theano學習一:張量、計算圖、操作算子等基礎知識

張量

張量(tensor)是一個多維的數據存儲形式,數據的的維度被稱爲張量的階。它可以看成是向量和矩陣在多維空間中的推廣,向量可以看成是一維張量,矩陣可以看成是兩維的張量。在Python中,一些科學計算庫(如Numpy)已提供了多維數組。Theano並不能取代Numpy,但可與之協同工作。Numpy可用於初始化張量。
爲了在CPU和GPU上執行相同的計算,採用符號變量,並由張量類、抽象以及有變量節點和應用節點構建計算圖的數字表達式表示。根據計算圖的編譯平臺,張量可由下列任意一種形式替代:

  • 1.TensorType變量,只用於CPU
  • 2.GpuArrayType變量,只能用於GPU
    這樣,所編寫的代碼就與執行平臺無關。
    在此給出一些張量對象:
對象類 維度 示例
Theano.tensor.scalar 零維數組 2,3,5
Theano.tensor.vector 一維數組 [0,3,20]
Theano.tensor.matrix 二維數組 [[2,3][1,5]]
Theano.tensor.tensor3 三維數組 [[[2,3][1,5]],[[2,1][3,4]]]

在Python shell中使用這些Theano對象具有更好的效果:

>>> import theano.tensor as T
>>> T.scalar()
<TensorType(float32, scalar)>
>>> T.iscalar()
<TensorType(int32, scalar)>
>>> T.fscalar()
<TensorType(float32, scalar)>
>>> T.dscalar()
<TensorType(float64, scalar)>

在對象之前的i,l,f或d表明給定張量類型爲integer32、integer64、float32或float64。對於實值(浮點)數據,建議直接採用T.scalar()形式而不是f或d的形式,這是由於浮點數的配置可採用直接表示形式:

>>> import theano
>>> theano.config.floatX = 'float64'
>>> T.scalar()
<TensorType(float64, scalar)>
>>> T.fscalar()
<TensorType(float32, scalar)>
>>> theano.config.floatX='float32'
>>> T.scalar()
<TensorType(float32, scalar)>

符號變量的作用如下:

  • 1.起到佔位符的作用,作爲構建數值運算(如加法、乘法)計算圖的起點:一旦計算圖編譯完成,在評價過程中接收輸入數據流。
  • 2.表示中間結果或輸出結果。
    符號變量和操作都是計算圖的一部分,在CPU或GPU上進行編譯以實現快速執行。接下來,編寫一個簡單的計算圖:
>>> x = T.matrix()
>>> y = T.matrix()
>>> z = x+y
>>> theano.pp(z)
'(<TensorType(float32, matrix)> + <TensorType(float32, matrix)>)'
>>> z.eval({x:[[1,2],[2,1]], y:[[2,6],[6,2]]})
array([[ 3.,  8.],
       [ 8.,  3.]], dtype=float32)

首先,創建兩個符號變量,或稱爲變量節點,記爲x和y,並在兩者之間通過一個加法操作,即應用節點在計算圖中創建一個新的符號變量z。
輸出函數pp可輸出由Theano符號變量表示的表達式。Eval函數可在前兩個變量x和y通過兩個二維數值數組初始化後計算輸出變量z的值。
下面的示例表明變量a和b之間的區別,分別記爲aa和bb:

>>> a = T.matrix('aa')
>>> b = T.matrix('bb')
>>> theano.pp(a+b)
'(aa + bb)'

若沒有命名,則在較大的計算圖中跟蹤節點會更加複雜。在輸出計算圖時,命名顯然有助於診斷問題,而變量僅用於處理計算圖中的對象:

>>> x = T.matrix('xx')
>>> x = x + x
>>> theano.pp(x)
'(xx + xx)'

再此,最初記爲x的符號變量保持不變,仍作爲計算圖的一部分。x+x創建一個賦值給Python變量x的新符號變量。
另外,還需注意的是,在命名時,複數形式可同時初始化多個張量:

>>> x,y,z = T.matrices('xx','yy','zz')
>>> x
xx
>>> y
yy
>>> z
zz

接下來,討論顯示計算圖的不同函數。

1.1計算圖和符號計算

回顧上述的簡單加法示例,並以不同形式來顯示同樣的信息:
在這裏插入圖片描述
其中,debugprint函數輸出預編譯計算圖,即未優化的計算圖。在本例中,該計算圖由兩個變量節點x和y,以及一個由no_inplace選項確定的按元素相加的應用節點組成。在優化計算圖中採用inplace選項可節省內存並重用輸入內存來保存計算結果。
如果已安裝graphviz庫(graphviz安裝問題)和pydot庫,則pydotprint命令可輸出計算圖的PNG圖像:
在這裏插入圖片描述在這裏插入圖片描述

或許大家已經注意到在第一次執行z.eval命令時花費一定時間。這種延遲的原因是需要在計算之前優化數學表達式並編譯CPU和GPU的代碼。
編譯後的表達式可顯示得到,並作爲一個普通的Python函數來使用:
在這裏插入圖片描述
在函數創建中的第一個參數是表徵計算圖輸入節點的變量列表。第二個參數是輸出變量數組。通過下列命令可輸出編譯後的計算圖:
在這裏插入圖片描述
在這裏插入圖片描述
本例已表明在使用GPU時的輸出情況。在編譯過程中,每個操作都選擇了GPU實現。主程序仍在數據所在的CPU上運行,GpuFromHost指令是將數據從CPU傳輸到GPU輸入,而HostFromGpu正好相反,是獲取主程序的結果並顯示:
在這裏插入圖片描述
Theano可執行一些數學優化操作,如分組按元素操作,在上述加法運算上添加一個新值:
在這裏插入圖片描述
計算圖中的節點個數並未增加:兩種加法運算合併到一個節點中。這種優化會使得調試更加困難,因此會在本章最後介紹如何在調試時禁用優化。
最後,討論如何利用NumPy來設置初始值:
在這裏插入圖片描述在NumPy數組上執行函數會產生一個精度降低的相關錯誤,這是因爲此處的NumPy數組是float64和int64 dtypes, 而x和y爲float32。對此有多種解決方法:第一種解決方法是以正確dtype類型創建NumPy數組
在這裏插入圖片描述
另一種方法是轉換NumPy數組類型(尤其是對於不允許直接選擇dtype類型的numpy.diag):
在這裏插入圖片描述
或允許向下類型轉換:
在這裏插入圖片描述

1.2張量操作

至此,已討論瞭如何創建由符號變量和運算操作組成的計算圖,並在GPU和CPU上編譯賦值表達式或函數的結果。
由於張量對於深度學習非常重要,因此在Theano中提供了大量的張量運算算子。在科學計算庫(如用於數值數組的NumPy庫)中存在的大多數算子在Theano中等價並具有類似的命名,以便於NumPy用戶更加熟悉。但與NumPy不同的是,在Theano中創建的表達式可在CPU或GPU上編譯。
例如:下面是張量創建的情況:
T.zeros()、T.ones()、T.eye()算子一個形狀元組作爲輸入;
T.zeros_like()、T.one_like()、T.identity_like()算子採用張量參數的形狀;
T.arange()、T.mgrid()、T.ogrid()算子用於排列和網格陣列。
下面給出如何在Python shell應用:
在這裏插入圖片描述
如位數(ndim)和類型(dtype)等信息在創建張量時定義,且不能修改:
在這裏插入圖片描述
而形狀等其他一些信息通過計算圖來計算:
在這裏插入圖片描述

1.2.1維度操作算子

第一類張量算子是維度操作算子。這種算子是以張量作爲輸入,並返回一個新的張量:

算子 描述
T.reshape() 重構張量維數
T.fill() 用相同值填充數組
T.flatten() 返回一個一維張亮的所有元素(向量)
T.dimshuffle() 改變維度順序,有點類似NumPy中的轉置方法,主要區別時刻用於增加或去除broadcastable維度(長度爲1)
T.squeeze() 通過去除等於1的維度來重構
T.transpose() 轉置
T.swapaxes() 交換維度
T.sort、T.argsort() 排序張量或按順序索引

例如:重構算子的輸出是一個新的張量,其中包含順序相同但形狀不同的相同元素:
該算子可構成鏈的形式:
在這裏插入圖片描述
PS:在Python中通過索引訪問普通[::-1]數組的方法和T.transpose中.T的用法。

1.2.2元素操作算子

在多維數組上的第二類算子是元素操作算子。
第一類的按元素操作是以兩個相同維度的輸入張量,並按元素執行應用函數f,這意味着所有元素對各自張量具有相同座標f([a,b],[c,d])=[f(a,c),f(b,d)],比如[a,b]+[c,d]=[a+c,b+d]。例如下面的乘法運算:

>>> import theano
Can not use cuDNN on context None: Use cuDNN 7.0.2 or higher for Volta.
Mapped name None to device cuda: GeForce RTX 2070 (0000:01:00.0)
>>> import theano.tensor as T
>>> a,b = T.matrices('a','b')
>>> z=a*b
>>> import numpy
>>> z.eval({a:numpy.ones((2,2)).astype(theano.config.floatX), b:numpy.diag((3,3)).astype(theano.config.floatX)})
array([[ 3.,  0.],
       [ 0.,  3.]], dtype=float32)

同樣的乘法運算還可以寫成以下形式:

>>> z=T.mul(a,b)

T.add和T.mul算子可接受任意個數的輸入:

>>> z=T.mul(a,b,a,b)

而一些元素操作算子只能接受一個輸入張量f([a,b])=[f(a),f(b)]:

>>> a=T.matrix()
>>> z=a**2
>>> z.eval({a:numpy.diag((3,3)).astype(theano.config.floatX)})
array([[ 9.,  0.],
       [ 0.,  9.]], dtype=float32)

最後,介紹一個廣播機制。當輸入張量不具有相同個數的維度時,就會廣播所缺失的維度,這意味着該張量將沿着這一維度不斷重複,以匹配另一個張量的維度。例如,取一個多維張量和一個標量(零維)張量,標量將在與多維張量相同形狀的數組中不斷重複,以使得最終形狀匹配,並可按元素執行操作,f([a,b],c)=[f(a,c),f(b,c)]:

>>> a = T.matrix()
>>> b = T.scalar()
>>> z=a*b
>>> z.eval({a:numpy.diag((3,3)).astype(theano.config.floatX),b:3})
array([[ 9.,  0.],
       [ 0.,  9.]], dtype=float32)

看另一示例:

>>> import theano
>>> import numpy
>>> 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 = numpy.arange(3).reshape(1,3).astype(theano.config.floatX)
>>> print(R)
[[ 0.  1.  2.]]
>>> M = numpy.arange(9).reshape(3, 3).astype(theano.config.floatX)
>>> print(M)
[[ 0.  1.  2.]
 [ 3.  4.  5.]
 [ 6.  7.  8.]]
>>> print(f_row(R, M))
[[  0.   2.   4.]
 [  3.   5.   7.]
 [  6.   8.  10.]]
>>> print()

>>> c = T.col()
>>> c.broadcastable
(False, True)
>>> f_col = theano.function([c, mtr], c + mtr)
>>> C = numpy.arange(3).reshape(3, 1).astype(theano.config.floatX)
>>> print(C)
[[ 0.]
 [ 1.]
 [ 2.]]
>>> M = numpy.arange(9).reshape(3, 3).astype(theano.config.floatX)
>>> print(M)
[[ 0.  1.  2.]
 [ 3.  4.  5.]
 [ 6.  7.  8.]]
>>> print(f_col(C, M))
[[  0.   1.   2.]
 [  4.   5.   6.]
 [  8.   9.  10.]]

下面是按元素操作算子示例:

算子 其他形式 描述
T.add、T.sub、T.mul、T.truediv +、-、*、/ 加、減、乘、除
T.pow、T.sqrt 冪、平方根
T.exp、T.log 指數、對數
T.cos、T.sin、T.tan 餘弦、正弦、正切
T.cosh、T.sinh、T.tanh 雙曲三角函數
T.intdiv、T.mod //、% 整除、模
T.floor、T.ceil、T.round 取整算子
T.sgn 符號
T.and_、T.xor、T.or_、T.invert &、^、 、~
T.gtT.ltT.ge、T.le >、<、>=、<= 比較雲算法
T.eq、T.neq、T.isclose 等於、不等於、公差接近
T.isnan 是否爲無窮大(不是數字)
T.abs_ 絕對值
T.minimum、T.maximum 元素最大值和最小值
T.clip、T.swith 最大值和最小值之間的值、轉換
T.cast 張量類型轉換

按元素操作算子總是返回與輸入數組相同大小的數組。T.switch和T.clip可接受3個輸入。
特別是,T.switch將按元素執行傳統的switch運算:

>>> cond = T.vector('cond')
>>> x,y = T.vectors('x', 'y')
>>> z = T.switch(cond, x, y)
>>> z.eval({cond:[1,0], x:[10,10], y:[3,2]})
array([ 10.,   2.], dtype=float32)

在cond張量爲真的同一位置,返回x值;反之,若cond爲假,則返回y值。
對於T.switch算子,具有一個以標量而非張量的特定等價(ifelse)。這並不是一種按元素的運算,支持延遲求值(如果在計算結束之前已知答案,則無需計算所有的元素):

>>> from theano.ifelse import ifelse
>>> z = ifelse(1,5,4)#類似於一個三目運算符,第一個數非零表達式爲第一個數,反之爲第二個數
>>> z.eval()
array(5, dtype=int8)

1.2.3約減操作算子(張量變成標量)

另一種張量操作是約減,在大多數情況下是將所有元素約減爲一個標量,爲此,需要掃描張量的所有元素來計算輸出:

算子 描述
T.max、T.argmax、T.max_and_argmax 最大值、最大值序號
T.min、T.argmin 最小值、最小值序號
T.sum、T.prod 元素之和或元素之積
T.mean、T.var、T.std 均值、方差和標準差
T.all、T.any 所有元素的與運算和或運算
T.ptp 元素排列(最小、最大)

上述運算也可通過指定一個軸按行或按列操作,並沿着維度減小的方向執行:

>>> a = T.matrix('a')
>>> T.max(a).eval({a:[[1,2],[3,4]]})
array(4.0, dtype=float32)
>>> T.max(a,axis=0).eval({a:[[1,2],[3,4]]})
array([ 3.,  4.], dtype=float32)
>>> T.max(a,axis=1).eval({a:[[1,2],[3,4]]})
array([ 2.,  4.], dtype=float32)

model =np.array([[6,9,7,8]])
modell =np.array([[6],[9],[7],[8]])
#T.argmax()中的axis在等於0時,按列比較,取得最大值的下標索引;等於1時,按行比較。默認不寫時,智能返回最大行,列下標索引
model =np.array([[6,9,7,8]])
modell =np.array([[6],[9],[7],[8]])
y_pred = T.argmax(model, axis=1)
y_predd = T.argmax(model)
y_pred2 = T.argmax(model,axis=0)
print(y_pred.eval())
print(y_predd.eval())
print(y_pred2.eval())

print("=======================")
y_pred = T.argmax(modell, axis=1)
y_predd = T.argmax(modell)
y_pred2 = T.argmax(modell,axis=0)
print(y_pred.eval())
print(y_predd.eval())
print(y_pred2.eval())

1.2.4線性代數算子

第三類操作運算算子是線性代數算子,如矩陣乘法:
AB=[ci,j]=[kai,kbk,j]A \bullet B=[c_i,_j]=[\sum_{k}a_i,_kb_k,_j]
也稱爲向量內積:
VW=kvkwkV \bullet W=\sum_{k}v_kw_k

算子 描述
T.dot 矩陣乘法/內積
T.outer 外積

另外,還有一些廣義操作(指定軸的T.tensordot)或批量操作(batched_dot、batched_tensordot)。
最後,還存在一些非常有用的操作算子,但不屬於前面的任何一類:T.concatenate是沿指定維度擴展張量,T.stack是在輸入張量中創建一個新的維度,T.stacklist是創建一種張量疊加的新模式:

>>> a = T.arange(10).reshape((5,2))
>>> b = a[::-1]
>>> b.eval()
array([[8, 9],
       [6, 7],
       [4, 5],
       [2, 3],
       [0, 1]])
>>> a.eval()
array([[0, 1],
       [2, 3],
       [4, 5],
       [6, 7],
       [8, 9]])
>>> T.concatenate([a,b]).eval()
array([[0, 1],
       [2, 3],
       [4, 5],
       [6, 7],
       [8, 9],
       [8, 9],
       [6, 7],
       [4, 5],
       [2, 3],
       [0, 1]])
>>> T.concatenate([a,b],axis=1).eval()
array([[0, 1, 8, 9],
       [2, 3, 6, 7],
       [4, 5, 4, 5],
       [6, 7, 2, 3],
       [8, 9, 0, 1]])
>>> T.stack([a,b]).eval()
array([[[0, 1],
        [2, 3],
        [4, 5],
        [6, 7],
        [8, 9]],

       [[8, 9],
        [6, 7],
        [4, 5],
        [2, 3],
        [0, 1]]])

Numpy表達式等效的a[5:]=5和a[5:]+=5是以兩種函數存在:

>>> a.eval()
array([[0, 1],
       [2, 3],
       [4, 5],
       [6, 7],
       [8, 9]])
>>> T.set_subtensor(a[3:], [-1,-1]).eval()
array([[ 0,  1],
       [ 2,  3],
       [ 4,  5],
       [-1, -1],
       [-1, -1]])
>>> T.inc_subtensor(a[3:],[-1,-1]).eval()
array([[0, 1],
       [2, 3],
       [4, 5],
       [5, 6],
       [7, 8]])

與NumPy的語法不同,不會改變原始張量,而是創建一個表示結果改變的新變量。因此,原始變量a仍爲原值,而返回的變量(在此未定義)表示更新後的值,用戶可以在隨後的計算中使用該新變量。

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