張量
張量(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.gt、T.lt、T.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線性代數算子
第三類操作運算算子是線性代數算子,如矩陣乘法:
也稱爲向量內積:
算子 | 描述 |
---|---|
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仍爲原值,而返回的變量(在此未定義)表示更新後的值,用戶可以在隨後的計算中使用該新變量。