1. 程式人生 > >TensorFlow2.0(二):數學運算

TensorFlow2.0(二):數學運算

1 基本運算:加(+)、減(-)、點乘(*)、除(/)、地板除法(//)、取餘(%)

基本運算中所有例項都以下面的張量a、b為例進行:

>>> a = tf.random.uniform([2, 3], minval=1, maxval=6,dtype=tf.int32)
>>> b = tf.random.uniform([2, 3], minval=1, maxval=6,dtype=tf.int32)
>>> a
<tf.Tensor: id=18, shape=(2, 3), dtype=int32, numpy=
array([[2, 1, 4],
[1, 2, 2]])>
>>> b
<tf.Tensor: id=22, shape=(2, 3), dtype=int32, numpy=
array([[4, 2, 4],
[1, 5, 2]])>

(1)加(+)

>>> tf.add(a,b)
<tf.Tensor: id=25, shape=(2, 3), dtype=int32, numpy=
array([[6, 3, 8],
[2, 7, 4]])>
>>> a + b
<tf.Tensor: id=27, shape=(2, 3), dtype=int32, numpy=
array([[6, 3, 8],
[2, 7, 4]])>

(2)減(-)

>>> tf.subtract(a,b)
<tf.Tensor: id=29, shape=(2, 3), dtype=int32, numpy=
array([[-2, -1, 0],
[ 0, -3, 0]])>
>>> a - b
<tf.Tensor: id=31, shape=(2, 3), dtype=int32, numpy=
array([[-2, -1, 0],
[ 0, -3, 0]])>

(3)乘法(*)

>>> tf.multiply(a,b)
<tf.Tensor: id=33, shape=(2, 3), dtype=int32, numpy=
array([[ 8, 2, 16],
[ 1, 10, 4]])>
>>> a * b
<tf.Tensor: id=35, shape=(2, 3), dtype=int32, numpy=
array([[ 8, 2, 16],
[ 1, 10, 4]])>

(4)除法(/)

>>> tf.divide(a,b)
<tf.Tensor: id=39, shape=(2, 3), dtype=float64, numpy=
array([[0.5, 0.5, 1. ],
[1. , 0.4, 1. ]])>
>>> a/b
<tf.Tensor: id=43, shape=(2, 3), dtype=float64, numpy=
array([[0.5, 0.5, 1. ],
[1. , 0.4, 1. ]])>

(5)地板除法(//)

>>> tf.floor_div(a,b)
<tf.Tensor: id=47, shape=(2, 3), dtype=int32, numpy=
array([[0, 0, 1],
[1, 0, 1]])>
>>> a//b
<tf.Tensor: id=49, shape=(2, 3), dtype=int32, numpy=
array([[0, 0, 1],
[1, 0, 1]])>

(6)取餘(%)

>>> tf.mod(b,a)
<tf.Tensor: id=65, shape=(2, 3), dtype=int32, numpy=
array([[0, 0, 0],
[0, 1, 0]])>
>>> b % a
<tf.Tensor: id=67, shape=(2, 3), dtype=int32, numpy=
array([[0, 0, 0],
[0, 1, 0]])>

可以看出,對於基本運算加(+)、減(-)、點乘(*)、除(/)、地板除法(//)、取餘(%),都是對應元素進行運算。

2 指數運算、開方、對數運算

(1)對數運算

TensorFlow提供tf.math.log()方法來求對數,當然,求的是以自然常數$e$為底的對數:

>>> e = 2.71828183
>>> a = tf.constant([e, e*e, e*e*e])
>>> tf.math.log(a)
<tf.Tensor: id=96, shape=(3,), dtype=float32, numpy=array([0.99999994, 2.        , 3.        ], dtype=float32)>

>>> c = tf.fill([2,2],1.)
>>> tf.math.log(c)
<tf.Tensor: id=72, shape=(2, 2), dtype=float32, numpy=
array([[0., 0.],
[0., 0.]], dtype=float32)>

注意:TensorFlow中沒有提供函式實現以其他數值為底的對數運算,例如$lo{g_2}8$, $lg100$。不過,我們可以通過其他方式來求取,記得下面這個高中時學過的公式嗎:

$$lo{g_a}b = \frac{{lo{g_c}b}}{{lo{g_c}a}}$$

所以有:

>>> f = tf.constant([[1., 9.], [16., 100.]])
>>> g = tf.constant([[2., 3.], [2., 10.]])
>>> tf.math.log(f) / tf.math.log(g)
<tf.Tensor: id=92, shape=(2, 2), dtype=float32, numpy=
array([[0., 2.],
[4., 2.]], dtype=float32)>

(2)指數運算

>>> g = tf.constant([[2, 3], [2, 10]])
>>> tf.pow(g, 2)
<tf.Tensor: id=104, shape=(2, 2), dtype=int32, numpy=
array([[ 4, 9],
[ 4, 100]])>

也可以直接通過運算子來完成:

>>> g ** 2
<tf.Tensor: id=100, shape=(2, 2), dtype=int32, numpy=
array([[ 4, 9],
[ 4, 100]])>

(3)開方

>>> f = tf.constant([[1., 9.], [16., 100.]])
>>> tf.sqrt(f)
<tf.Tensor: id=108, shape=(2, 2), dtype=float32, numpy=
array([[ 1., 3.],
[ 4., 10.]], dtype=float32)>

自然常數$e$的指數運算:

>>> d = tf.constant([[1.,2.],[3.,4.]])
>>> tf.exp(d)
<tf.Tensor: id=76, shape=(2, 2), dtype=float32, numpy=
array([[ 2.7182817, 7.389056 ],
[20.085537 , 54.598152 ]], dtype=float32)>

注意:對數運算函式log()與指數運算函式在不同的模組中。

在我看來,上面提到的指數運算與對數運算不在通知模組以及沒有提供以其他自然數為底的對數運算,應該應該是TensorFlow中的遺留問題,希望能夠在正式版中得到修正。

3 矩陣相乘

注意,矩陣相乘與上面的矩陣點乘可不一樣,這裡的矩陣相乘是線性代數中的矩陣乘法,通過TensorFlow提供的matmul()方法實現:

>>> import tensorflow as tf
>>> import numpy as np
>>> a = tf.constant(np.arange(6),shape=(2,3))
>>> b = tf.constant(np.arange(6),shape=(3,2))
>>> a
<tf.Tensor: id=5, shape=(2, 3), dtype=int32, numpy=
array([[0, 1, 2],
[3, 4, 5]])>
>>> b
<tf.Tensor: id=8, shape=(3, 2), dtype=int32, numpy=
array([[0, 1],
[2, 3],
[4, 5]])>
>>> tf.matmul(a,b)
<tf.Tensor: id=9, shape=(2, 2), dtype=int32, numpy=
array([[10, 13],
[28, 40]])>

矩陣相乘也可以通過符號來操作進行,用“@”表示:

>>> a@b
<tf.Tensor: id=13, shape=(2, 2), dtype=int32, numpy=
array([[10, 13],
[28, 40]])>

這裡的張量a和b都是二維的,但在實際應用中,資料往往高於二維,這時候怎麼應算呢?

>>> a = tf.constant(np.arange(12),shape=(2,2,3))
>>> b = tf.constant(np.arange(12),shape=(2,3,2))
>>> a
<tf.Tensor: id=17, shape=(2, 2, 3), dtype=int32, numpy=
array([[[ 0, 1, 2],
[ 3, 4, 5]],
 
[[ 6, 7, 8],
[ 9, 10, 11]]])>
>>> b
<tf.Tensor: id=20, shape=(2, 3, 2), dtype=int32, numpy=
array([[[ 0, 1],
[ 2, 3],
[ 4, 5]],
 
[[ 6, 7],
[ 8, 9],
[10, 11]]])>
>>> a@b
<tf.Tensor: id=23, shape=(2, 2, 2), dtype=int32, numpy=
array([[[ 10, 13],
[ 28, 40]],
 
[[172, 193],
[244, 274]]])>

可以看到,當高於二維的張量進行矩陣相乘時,最終的實現還是二維矩陣相乘,只不過分成了多個二維矩陣,四維張量也是一樣的:

>>> a = tf.constant(np.arange(24),shape=(2,2,2,3))
>>> b = tf.constant(np.arange(24),shape=(2,2,3,2))
>>> a@b
<tf.Tensor: id=42, shape=(2, 2, 2, 2), dtype=int32, numpy=
array([[[[ 10, 13],
[ 28, 40]],
 
[[ 172, 193],
[ 244, 274]]],
 
 
[[[ 550, 589],
[ 676, 724]],
 
[[1144, 1201],
[1324, 1390]]]])>

4 Broadcasting機制

上面的所有例項中所用到的張量都是在維度數和形狀相同情況下進行,那麼,當兩個張量維度數或者形狀不一樣時能不能進行運算呢?

>>> a = tf.constant([1,2,3])
>>> b = tf.constant(np.arange(12),shape=(2,2,3))
>>> b
<tf.Tensor: id=86, shape=(2, 2, 3), dtype=int32, numpy=
array([[[ 0, 1, 2],
[ 3, 4, 5]],
 
[[ 6, 7, 8],
[ 9, 10, 11]]])>
>>> a+b
<tf.Tensor: id=88, shape=(2, 2, 3), dtype=int32, numpy=
array([[[ 1, 3, 5],
[ 4, 6, 8]],
 
[[ 7, 9, 11],
[10, 12, 14]]])>
>>> a*b
<tf.Tensor: id=90, shape=(2, 2, 3), dtype=int32, numpy=
array([[[ 0, 2, 6],
[ 3, 8, 15]],
 
[[ 6, 14, 24],
[ 9, 20, 33]]])>

可以看到,一個一維的張量與一個三維張量進行運算是完全沒有問題的,從運算結果上可以看出,相當於是三維張量中的每一行資料與張量a進行運算,為什麼可以這樣運輸呢?這就得益於TensorFlow中的Broadcasting機制。

Broadcasting機制解除了只能維度數和形狀相同的張量才能進行運算的限制,當兩個陣列進行算術運算時,TensorFlow的Broadcasting機制首先對維度較低的張量形狀陣列填充1,從後向前,逐元素比較兩個陣列的形狀,當逐個比較的元素值(注意,這個元素值是指描述張量形狀陣列的值,不是張量的值)滿足以下條件時,認為滿足 Broadcasting 的條件:

(1)相等

(2)其中一個張量形狀陣列元素值為1。

當不滿足時進行運算則會丟擲 ValueError: frames are not aligne 異常。算術運算的結果的形狀的每一元素,是兩個陣列形狀逐元素比較時的最大值。

回到上面張量a與b相乘的例子,a的形狀是(3,),b的形狀是(2, 2, 3),在Broadcasting機制工作時,首先比較維度數,因為a的維度為1,小於b的維度3,所以填充1,a的形狀就變成了(1,1,3),然後從最後端的形狀陣列元素依次往前比較,先是就是3與3比,結果是相等,接著1與2相比,因為其中一個為1,所以a的形狀變成了(1,2,3),繼續1與2比較,因為其中一個為1,所以a的形狀變成了(2,2,3),a中的資料每一行都填充a原來的資料,也就是[1,2,3],然後在與b進行運算。

當然,在TensorFlow的Broadcasting機制執行過程中,上述操作只是理論的,並不會真正的將a的形狀變成(2,2,3,),更不會將每一行填充[1,2,3],只是虛擬進行操作,真正計算時,依舊是使用原來的張量a。這麼做的好處是運算效率更高,也更節省記憶體。

再舉一些例子加深理解:

A (2d array): 5 x 4
B (1d array): 1
Result (2d array): 5 x 4
 
A (2d array): 5 x 4 B (1d array): 4 Result (2d array): 5 x 4
A (3d array): 15 x 3 x 5 B (3d array): 15 x 1 x 5 Result (3d array): 15 x 3 x 5
A (3d array): 15 x 3 x 5 B (2d array): 3 x 5 Result (3d array): 15 x 3 x 5
A (3d array): 15 x 3 x 5 B (2d array): 3 x 1 Result (3d array): 15 x 3 x 5

一些反例(不滿足 Broadcasting 規則 ):

A (1d array): 3
B (1d array): 4 
 
A (2d array): 2 x 1 B (3d array): 8 x 4 x 3

參考

https://lufficc.com/blog/tensorflow-and-numpy-broadcas