使用 Eager Execution 編碼並執行圖表:以通過 RevNet 優化程式碼為例

TensorFlowers發表於2019-03-04

文 / 軟體工程實習生 Xuechen Li

來源:TensorFlow 公眾號

Eager Execution 可簡化 TensorFlow 中的模型構建體驗,而 Graph Execution 可提供優化,以加快模型執行速度及提高儲存效率。本篇博文展示瞭如何編寫 TensorFlow 程式碼,以便將藉助 tf.keras API 並使用 Eager Execution 構建的模型轉換為圖表,最終藉助 tf.estimator API 的支援,在 Cloud TPU 上部署此模型。

注:tf.keras 連結
www.tensorflow.org/guide/keras

tf.estimator 連結
www.tensorflow.org/guide/estim…

我們使用可逆殘差網路(RevNet、Gomez 等)作為示例。接下來的部分假設讀者對卷積神經網路和 TensorFlow 有基本瞭解。您可以在此處找到本文的完整程式碼(為確保程式碼在所有設定中正常執行,強烈建議您使用 tf-nightly 或 tf-nightly-gpu)。

RevNet

RevNet 與殘差網路(ResNet、He 等)類似,只不過他們是可逆的,在給定輸出的情況下可重建中間計算。此技術的好處之一是我們可以通過重建啟用來節省記憶體,而不是在訓練期間將其全部儲存在記憶體中(回想一下,由於鏈式法則有此要求,因此我們需要中間結果來計算有關輸入的梯度)。相比傳統架構上的一般反向傳播,這使我們可以適應較大的批次大小,並可訓練更具深度的模型。具體來說,此技術的實現方式是通過使用一組巧妙構建的方程來定義網路:

其中頂部和底部方程組分別定義正演計算和其反演計算。這裡的 x1 和 x2 是輸入(從整體輸入 x 中拆分出來),y1 和 y2 是輸出,F 和 G 是 ConvNet。這使我們能夠在反向傳播期間精準重建啟用,如此一來,在訓練期間便無需再儲存這些資料。

使用 tf.keras.Model 定義正向和反向傳遞

假設我們使用 “ResidualInner” 類來例項化函式 F 和 G,我們可以通過子類化 tf.keras.Model 來定義可逆程式碼塊,並通過替換上面的方程中所示的 call 方法來定義正向傳遞:

1    class Residual(tf.keras.Model):    
2        def __init__(self, filters):    
3            super(Residual, self).__init__()    
4            self.f = ResidualInner(filters=filters, strides=(1, 1))
5            self.g = ResidualInner(filters=filters, strides=(1, 1))
6
7        def call(self, x, training=True):    
8            x1, x2 = tf.split(x, num_or_size_splits=2, axis=self.axis)
9            f_x2 = self.f(x2, training=training)    
10            y1 = f_x2 + x1    
11            g_y1 = self.g(y1, training=training)    
12            y2 = g_y1 + x2    
13            return tf.concat([y1, y2], axis=self.axis) 
複製程式碼

這裡的 training 引數用於確定批標準化的狀態。啟用 Eager Execution 後,批標準化的執行平均值會在 training=True 時自動更新。執行等效圖時,我們需要使用 get_updates_for 方法手動獲取批標準化更新。

要構建節省記憶體的反向傳遞,我們需要使用 tf.GradientTape 作為上下文管理器來跟蹤梯度(僅在有需要時):
注:tf.GradientTape 連結
www.tensorflow.org/api_docs/py…

1        def backward_grads(self, y, dy, training=True):    
2            dy1, dy2 = dy    
3            y1, y2 = y
4
5            with tf.GradientTape() as gtape:    
6                gtape.watch(y1)    
7                gy1 = self.g(y1, training=training)    
8            grads_combined = gtape.gradient(    
9                    gy1, [y1] + self.g.trainable_variables, output_gradients=dy2)    
10            dg = grads_combined[1:]    
11            dx1 = dy1 + grads_combined[0]
12            x2 = y2 - gy1
13
14            with tf.GradientTape() as ftape:    
15                ftape.watch(x2)    
16                fx2 = self.f(x2, training=training)    
17            grads_combined = ftape.gradient(    
18                    fx2, [x2] + self.f.trainable_variables,output_gradients=dx1)    
19            df = grads_combined[1:]    
20            dx2 = dy2 + grads_combined[0] 
21            x1 = y1 - fx2
22
23            x = x1, x2    
24            dx = dx1, dx2    
25            grads = df + dg 
26
27            return x, dx, grads    
複製程式碼

您可以在論文的 “演算法 1” 中找到確切的一組梯度計算(我們在程式碼中簡化了使用變數 z1 的中間步驟)。此演算法經過精心設計,在給定輸出和有關輸出的損失梯度的情況下,我們可以在每個可逆程式碼塊內,計算有關輸入和模型變數的梯度及重建輸入。呼叫 tape.gradient(y, x),即可計算有關 x 的 y 梯度。我們也可使用引數 output_gradients 來明確應用鏈式法則。

使用 Eager Execution 來加快原型設計速度

使用 Eager Execution 進行原型設計的一個明顯好處是採用命令式操作。我們可以立即獲得結果,而不用先構建圖表,然後再初始化要執行的會話。

例如,我們通過由一般反向傳播計算的梯度來比較可逆反向傳播梯度,從而驗證我們的模型:

1    block = Residual()    
2    x = tf.random_normal(shape=(N, C, H, W))    
3    dy = tf.random_normal(shape=(N, C, H, W))    
4    with tf.GradientTape() as tape:    
5        tape.watch(x)    
6        y = block(x)    
7    # Compute true grads    
8    dx_true = tape.gradient(y, x, output_gradients=dy)
9
10    # Compute grads from reconstruction    
11    dx, _ = block.backward_grads(x, y, dy)
12
13    # Check whether the difference is below a certain 14    threshold    
thres = 1e-6    
15    diff_abs = tf.reshape(abs(dx - dx_true), [-1])    
16    assert all(diff_abs < thres)  
複製程式碼

在上面的片段中,dx_true 是一般反向傳播返回的梯度,而 dx 是執行可逆反向傳播後返回的梯度。Eager Execution 整合了原生 Python,如此一來,all 和 abs 等函式便可直接應用於 Tensor。

使用 tf.train.Checkpoint 儲存和載入檢查點

為確保能夠使用 Eager Execution 和 Graph Execution 儲存和載入檢查點,TensorFlow 團隊建議您使用 tf.train.Checkpoint API。

為了儲存模型,我們使用想要儲存的所有物件建立了一個 tf.train.Checkpoint 例項。這個例項可能包括我們的模型、我們使用的優化器、學習率安排和全域性步驟:

1    checkpoint = tf.train.Checkpoint(model=model, optimizer=optimizer,    
2                        learning_rate=learning_rate, global_step=global_step)
複製程式碼

我們可以按照下面的方法儲存和還原特定的已訓練例項:

1    checkpoint.save(file_prefix)    
2    checkpoint.restore(save_path) 
複製程式碼

使用 tf.contrib.eager.defun 提升 Eager Execution 效能

由於解讀 Python 程式碼會產生開銷,Eager Execution 有時會比執行等效圖要慢。通過使用 tf.contrib.eager.defun 將由 TensorFlow 運算組成的 Python 函式編譯成可呼叫的 TensorFlow 圖表,可以彌補這種效能差距。在訓練深度學習模型時,我們通常可以在三個主要位置應用 tf.contrib.eager.defun:

  1. 正演計算
  2. 梯度的反演計算
  3. 將梯度應用於變數

例如,我們可以按以下方式 defun 正向傳遞和梯度計算:

1    tfe = tf.contrib.eager    
2    model.call = tfe.defun(model.call)    
3    model.compute_gradients = tfe.defun(model.compute_gradients)
複製程式碼

要 defun 優化器的應用梯度步驟,我們需要將其包裝在另一個函式內:

1    def apply_gradients(optimizer, gradients, variables, global_step=None):    
2            optimizer.apply_gradients(    
3                    zip(gradients, variables), global_step=global_step) 
4    apply_gradients = tfe.defun(apply_gradients)
複製程式碼

tf.contrib.eager.defun 正處於積極開發中,將其加以應用是一項不斷髮展的技術。如需更多資訊,請檢視其文件字串。
注:其文件字串連結
github.com/tensorflow/…

使用 tf.contrib.eager.defun 包裝 Python 函式會使 TensorFlow API 在 Python 函式中進行呼叫,以構建圖表,而不是立即執行運算,從而優化整個程式。並非所有 Python 函式都可成功轉換為等效圖,特別是帶有動態控制流的函式(例如,Tensor contents 中的 if 或 while)。tf.contrib.autograph 是一種相關工具,可以增加能夠轉換為 TensorFlow 圖表的 Python 程式碼的表面積。截至 2018 年 8 月,使用 defun 整合 Autograph 的工作仍在進行中。
注:tf.contrib.autograph 連結
www.tensorflow.org/guide/autog…

使用 TFRecords 和 tf.data.Dataset 構建輸入管道

Eager Execution 與 tf.data.Dataset API 相容。我們可以讀取 TFRecords 檔案:

1    dataset = tf.data.TFRecordDataset(filename) 
2    dataset = dataset.repeat(epochs).map(parser).batch(batch_size)
複製程式碼

為提升效能,我們還可使用預取函式並調整 num_parallel_calls。

由於資料集由影像和標籤對組成,在 Eager Execution 中迴圈使用此資料集非常簡單。在本例中,我們甚至不需要明確定義迭代器:

1    for image, label in dataset:    
2        logits = model(image, training=True)    
3        ... 

複製程式碼

使用估算器包裝 Keras 模型並以圖表形式執行

由於 tf.keras API 也支援圖表構建,因此使用 Eager Execution 構建的相同模型也可用作提供給估算器的圖表構建函式,但程式碼稍有更改。要修改使用 Eager Execution 構建的 RevNet 示例,我們只需使用 model_fn 包裝 Keras 模型,並按照 tf.estimator API 的指示使用此模型。

1    def model_fn(features, labels, mode, params):
2        model = RevNet(params["hyperparameters"])
3        if mode == tf.estimator.ModeKeys.TRAIN: 
4            optimizer = tf.train.MomentumOptimizer(learning_rate, momentum)
5            logits, saved_hidden = model(features, training=True)    
6            grads, loss = model.compute_gradients(saved_hidden, labels, training=True) 
7            with tf.control_dependencies(model.get_updates_for(features)):
8                train_op = optimizer.apply_gradients(zip(grads, model.trainable_variables)) 
9            return tf.estimator.EstimatorSpec(mode=mode, loss=loss, train_op=train_op) 
複製程式碼

您可以使用 the tf.data API 照常定義 tf.estimator API 所需的 input_fn,並從 TFRecords 中讀取資料。

使用 TPU Estimator 包裝 Keras 模型以進行 Cloud TPU 訓練

使用 Estimator 包裝模型和輸入管道使模型可以在 Cloud TPU 上執行。

所需步驟如下:
設定 Cloud TPU 的特定配置
從 tf.estimator.Estimator 切換到 tf.contrib.tpu.TPUEstimator
使用 tf.contrib.tpu.CrossShardOptimizer 包裝常用優化器
注:Cloud TPU 連結
github.com/tensorflow/…
配置連結
www.tensorflow.org/api_docs/py…
tf.contrib.tpu.TPUEstimator 連結
www.tensorflow.org/api_docs/py…
tf.contrib.tpu.CrossShardOptimizer 連結
www.tensorflow.org/api_docs/py…

如需瞭解具體說明,請檢視 RevNet 示例資料夾中的 TPU 估算器指令碼。我們希望日後可以使用 tf.contrib.tpu.keras_to_tpu_model 進一步簡化使 Keras 模型在 TPU 上執行的流程。
注:TPU 估算器指令碼連結
github.com/tensorflow/…
tf.contrib.tpu.keras_to_tpu_model 連結
github.com/tensorflow/…

可選:模型效能

與一般反向傳播相比,有了 tf.GradientTape,再加上無需額外正向傳遞的梯度計算可簡化流程,我們能夠僅以 25% 的計算開銷來執行 RevNet 的可逆反向傳播。

圖中藍色和橙色的曲線分別表示隨著全域性步驟的增加,一般反向傳播和可逆反向傳播的每秒取樣率。該圖來自在單個 Tesla P100 上使用批次大小為 32 的模擬 ImageNet 資料訓練的 RevNet-104。

為了驗證所節省的記憶體,我們在訓練過程中繪製記憶體使用情況。藍色和黑色曲線分別是一般和可逆反向傳播。該圖記錄了使用批次大小為 128 的模擬 ImageNet 資料訓練 RevNet-104 圖表模式的 100 次迭代。該圖是在 CPU 上進行訓練時由 mprof 生成,以便我們使用一般反向傳播以相同的批次大小進行訓練。

結論

我們以 RevNet 為例,展示瞭如何使用 Eager Execution 和 tf.keras API 對機器學習模型快速進行原型設計。這不僅可簡化模型構建體驗,而且我們輕易就能將模型轉換為估算器,並在 Cloud TPU 上進行部署,以獲得高效能。您可以在此處找到本文的完整程式碼。此外,請務必檢視使用 Eager Execution 的其他示例。
注:此處連線誒
github.com/tensorflow/…
其他示例連結
github.com/tensorflow/…

相關文章