iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 20
0
AI & Data

深度學習裡的冰與火之歌 : Tensorflow vs PyTorch系列 第 20

Day 20: Tensorflow 2.0 再造訪 tf.Tensor

  • 分享至 

  • xImage
  •  

在介紹eager modetf.function的時候,就提到了 tf.Variable 如何在這兩種執行環境中使用。今天,我們一開始仍舊把重心放在tf.Variable, 隨後則是介紹無狀態版本的tf.Tensor,最後則是介紹 Ragged Tensor。
Ragged Tensor 是有著不同長度序列的 Tesnor 物件,這在處理 RNN 時相當常見,我們會將大部分的文章用來介紹 Ragged Tensor,以及該如何在 eager mode evaluate Ragged Tensor。

tf.Variable

如何在 tf.keras 取得 tf.Variable 參數

我們在建制 optimizer 時,或需要客製化梯度計算,都需要模型提供可訓練參數的名單,能夠在複雜的模型中仍保持參數的參考,是一件非常重要的事。
這種情況可能發生在使用者撰寫自己的 tf.keras.layers.Layer。下面的程式碼會建立兩個tf.keras.layers.Layer子類別,分別是 MyLayerMyOtherLayer。其中,MyOtherLayer擁有MyLayer物件,因此需要能夠 access MyLayer 的變數個數。MyLayer 則是利用一個 python list 來不僅管理所有的變數,也方便追蹤變數。

class MyLayer(tf.keras.layers.Layer):

  def __init__(self):
    super(MyLayer, self).__init__()
    self.my_var = tf.Variable(1.0)
    self.my_var_list = [tf.Variable(x) for x in range(10)]

class MyOtherLayer(tf.keras.layers.Layer):

  def __init__(self):
    super(MyOtherLayer, self).__init__()
    self.sublayer = MyLayer()
    self.my_other_var = tf.Variable(10.0)

m = MyOtherLayer()
print(len(m.variables))  # 印出 12,MyLayer 有 11 個 Variables
                         # 再加上 my_other_var

倘若使用者不希望自己撰寫追蹤 tf.Variable 的原始碼,可以繼承 tf.Module。該類別有兩個屬性:variablestrainable_variables 可以提供使用者提取模型參數。
其次,若 tf.Module 擁有另一個 tf.Module物件,則可以用 submodule 的屬性來 access 該物件。
如果要如 Tensorflow 1.x 對變數給予自訂的命名空間,以在 Tensorboard 可以階層是的方法顯示,而方便除錯。使用者可以用 @tf.Module.with_name_scope 來 decorate 實作正向傳播的 __call__方法,然後在 with self.name_scope 進入同樣的命名空間,如下面程式碼所示:

class MLP(tf.Module):
  def __init__(self, input_size, sizes, name=None):
    super(MLP, self).__init__(name=name)
    self.layers = []
    with self.name_scope: # 進入相同的命名空間
      for size in sizes:
        self.layers.append(Dense(input_size=input_size, output_size=size))
        input_size = size

  # 使用命名空間
  @tf.Module.with_name_scope
  def __call__(self, x):
    for layer in self.layers:
      x = layer(x)
    return x

tf.Tensor

Ragged Tensor

Ragged Tensor 是一種 Tensor 其內容物擁有非一致的長度。除了常見不定長度的序列資料,具有階層或結構式的架構資料,如 graph 表示或自然語言中的解析樹架構。了解為何有 ragged tensor 後,我們進入如何建制一個 ragged tensor 和 如何使用它。Tensorflow 將 Ragged Tensor 的實作寫在 tf.ragged 模組。現在就來看看官方網頁提供,有關Ragged tensors的範例吧!

建立 ragged tensor

  1. 使用 tf.ragged.constant 建構子來建立 RaggedTensor 物件: tf.ragged.constant 接受 python 的 list of lists,lists 的長度不需要一致,但元素的資料型態則需要一致,不能將混合的資料型態,如數值與字串放置在 RaggedTensor 內。這個方法是最簡單的方法來建構RaggedTensor物件,但需要使用者使先提供資料。呼叫方法,如右:digits = tf.ragged.constant([[3, 1, 4, 1], [], [5, 9, 2], [6], []])
  2. 使用任一 factory 方法來建構物件,這些方法是:`tf.RaggedTensor.from_value_rowids, tf.RaggedTensor.from_row_lengths 和 `tf.RaggedTensor.from_row_splits:這些方法都需要使用者提供列方向的切割方法(row-partitioning tensors),通常這個方法的實作,會將所有的元素儲存在一塊聯繫的記憶體,而依靠使用者提供的切割方法來取得元素。
    現就這三個 factory 方法,來瞭解建構出的RaggedTensor怎麼做存取:

tf.RaggedTensor.from_value_rowids:如圖,每一個元素有對應自己的 row id。
tf.RaggedTensor.from_value_rowids
tf.RaggedTensor.from_row_lengths:上面的方法很直覺但有點浪費空間,如果我們知道每一列的長度,我們就可以以比較精簡的記憶體空間來存取,如下圖,右方的 row_lengths 陣列,每一個元素儲存的是該列的長度。如,第一個元素為 4 代表的是第一列的長度為 4:
tf.RaggedTensor.from_row_lengths
tf.RaggedTensor.from_row_splits:上者儲存的是長度資訊,然而當我們需要取出第二或第三列時,我們都比須對長度陣列做累加方能使用 slice 取出該列的所有元素。所以,from_row_splits 就是針對 slice 所設計的。現在右方儲存的是每一列的 slice 或列分裂(row splits)資訊,長度比from_row_lengths 多了一個元素。若要取出第一列的所有元素,則右邊的第一個元素為第一列的起始 index,而第二個元素為第一列的結束 index + 1,有了這兩個值就可以建構 slice 物件,將第一列所有元素都取出。
tf.RaggedTensor.from_row_splits

Overloaded Operations and Indexing

Overloaded Operations

在運算元方面,tf.RaggedTensortf.Tensor相同,計算運算元被複寫,而使執行運算元時,是以元素為單元來運算。然而,tf.RaggedTensor多了一個限制,那就是運算元兩邊的維度需要相同,包括列的長度也需相等。

indexing

在 indexing 方面,tf.RaggedTensor 支援 python-style 的 indexing,也就是沿著每一個維度,用 slice 來取出元素。然而,應用在 tf.RaggedTensor,有一些限制。限制包括了,不得在 ragged dimension,或擁有長度不一的象限上 indexing。
接下來我們來看一些原始碼範例,瞭解一下如何用另一個 tf.RaggedTensor 來作 indexing。

queries = tf.ragged.constant(
    [['Who', 'is', 'George', 'Washington'],
     ['What', 'is', 'the', 'weather', 'tomorrow'],
     ['Goodnight']])
print(queries[1:])   # 除了第一列,列出所有
#=> <tf.RaggedTensor 
#[[b'What', b'is', b'the', b'weather', b'tomorrow'],  # row 1
#[b'Goodnight']]>                                     # row 2

print(queries[:, :3])  # 每一列的前三個字,使用 slice 非 exact indexing
#=> <tf.RaggedTensor 
#[[b'Who', b'is', b'George'], 
#[b'What', b'is', b'the'], 
#[b'Goodnight']]> 

print(queries[:, 3]) # 每一列的第三個字,使用 exact indexing
# ValueError: Cannot index into an inner ragged dimension.

tf.RaggedTensor vs tf.SparseTensors

tf.RaggedTensor雖然使用相似的方式來儲存,但兩者在意義上和實作上則有相當大的不同。就以 concat這個運算元來說,要沿著 ragged dimension,或該軸擁有長度不一的序列來合併兩個 tf.RaggedTensor ,結果是 ragged dimension 的序列長度合併,如下圖:
concat tf.RaggedTensors
若是 tf.SparseTensors,則需要擴展 dimension,再將元素放到擴展完成的位置上,可以看下圖說明:
concat tf.SparseTensors
總之,tf.RaggedTensor可以看作為在 ragged dimension 是密集儲存的 Tensor 物件。

型態轉換

Tensroflwo 提供tf.RaggedTensortf.Tensor 型態轉換,以及 tf.RaggedTensortf.SparseTensors 。前者的轉換邏輯很簡單,就是把長度不一的那個維度,用最大長度填滿即可。
所以,結論是:

  1. tf.RaggedTensortf.Tensor: 呼叫 tf.RaggedTensor物件的 to_tensor方法,若長度不足則用使用者所給的預設值(default_values)填入。
  2. tf.Tensortf.RaggedTensor: 呼叫 tf.RaggedTensor 類別的 from_tensor方法,需要使用者提供 padding 所用的值,建立 tf.RaggedTensor 時移掉 padding
  3. tf.RaggedTensortf.SparseTensors: 呼叫 tf.RaggedTensor物件的 to_sparse方法
  4. tf.SparseTensorstf.RaggedTensor: 呼叫 tf.RaggedTensor類別的 from_sparse方法
    那麼現在就來轉換吧!
ragged_sentences = tf.ragged.constant([
    ['Hi'], ['Welcome', 'to', 'the', 'fair'], ['Have', 'fun']])
print(ragged_sentences.to_tensor(default_value='')) 
# => tf.Tensor( #每一個序列長度最大為 4,用 default_value 不足 4
#[[b'Hi' b'' b'' b'']
# [b'Welcome' b'to' b'the' b'fair']
# [b'Have' b'fun' b'' b'']], shape=(3, 4), dtype=string)

print(ragged_sentences.to_sparse())
# => SparseTensor(
#indices=tf.Tensor(
#[[0 0]
# [1 0]
# [1 1]
# [1 2]
# [1 3]
# [2 0]
# [2 1]], shape=(7, 2), dtype=int64), 
#values=tf.Tensor([b'Hi' b'Welcome' b'to' b'the' b'fair' b'Have' #b'fun'], shape=(7,), dtype=string), dense_shape=tf.Tensor([3 4], #shape=(2,), dtype=int64))

x = [[1, 3, -1, -1], [2, -1, -1, -1], [4, 5, 8, 9]]
print(tf.RaggedTensor.from_tensor(x, padding=-1))
# => <tf.RaggedTensor [[1, 3], [2], [4, 5, 8, 9]]>

st = tf.SparseTensor(indices=[[0, 0], [2, 0], [2, 1]],
                     values=['a', 'b', 'c'],
                     dense_shape=[3, 3])
print(tf.RaggedTensor.from_sparse(st))
#=><tf.RaggedTensor [[b'a'], [], [b'b', b'c']]>

Evaluation

in eager execution

eager mode 裡,所有的陳述句都會被 python 直譯器上執行以及評估。而為了符合 eager mode 的特性,tf.RaggedTensor 的使用者可以有以下方法來檢視:

  1. 呼叫物件的 tf.RaggedTensor.to_list()的方法,把對 Python 不透明的tf.RaggedTensor物件轉成 python list。
  2. 利用 python indexing 將 tf.RaggedTensor 轉為 EagerTensor 並用 numpy 屬性來取值。
  3. 呼叫 tf.RaggedTensor 的屬性分開檢視。如果適用 row_splits 的方法建構的tf.RaggedTensor物件,則可以分別呼叫 tf.RaggedTensor.valuestf.RaggedTensor.row_splits分開檢視。

現在用程式碼說明:

rt = tf.ragged.constant([[1, 2], [3, 4, 5], [6], [], [7]])
print(rt.to_list()) # 方法一,使用 to_list 方法
# =>[[1, 2], [3, 4, 5], [6], [], [7]] # python list

#方法二,使用 python indexing,在呼叫 numpy() 轉成 numpy.ndarray
print(type(rt[1]), rt[1].numpy()) 
# => <class 'tensorflow.python.framework.ops.EagerTensor'> [3 4 5]

#方法三,分別呼叫
print(rt.values)
#=> tf.Tensor([1 2 3 4 5 6 7], shape=(7,), dtype=int32)
print(rt.row_splits)
#=> tf.Tensor([0 2 5 6 6 7], shape=(6,), dtype=int64)



上一篇
Day 19: Tensorflow 2.0 再造訪 AutoGraph
下一篇
Day 21: Tensorflow 2.0: 再造訪 Model Persistence
系列文
深度學習裡的冰與火之歌 : Tensorflow vs PyTorch31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言