Colab提供了免費TPU,機器之心幫你試了試
最近機器之心發現谷歌的 Colab 已經支援使用免費的 TPU,這是繼免費 GPU 之後又一重要的計算資源。我們發現目前很少有部落格或 Reddit 論壇討論這一點,而且谷歌也沒有通過部落格或其它方式做宣傳。因此我們嘗試使用該 TPU 訓練簡單的卷積神經網路,並對比它的執行速度。
我們在網上只發現比較少的資訊與資源,最開始介紹 Colab 免費 TPU 的內容還是谷歌開發者 Sam Wittevee 最近的演講 PPT。因此本文的測試和探索都是基於官方文件和例項所實現的,還有很多 TPU 特性沒有考慮到,感興趣的讀者可查閱文末的參考資料,瞭解更多 Colab 免費 TPU 的特性。
本文所有的測試程式碼與結果都可以訪問:https://colab.research.google.com/drive/1DpUCBm58fruGNRtQL_DiSVbT90spdZgm
試驗 Colab 免費 TPU
首先我們需要確保 Colab 筆記本中執行時型別選擇的是 TPU,同時分配了 TPU 資源。因此依次選擇選單欄中的「runtime」和「change runtime type」就能彈出以下對話方塊:
為了確保 Colab 給我們分配了 TPU 計算資源,我們可以執行以下測試程式碼。如果輸出 ERROR 項,則表示目前的執行時並沒有調整到 TPU,如果輸出 TPU 地址及 TPU 裝置列表,則表示 Colab 已經為我們分配了 TPU 計算資源。
如果檢視以下測試程式碼的正常輸出,Colab 會為「TPU 執行時」分配 CPU 和 TPU,其中分配的 TPU 工作站有八個核心,因此在後面配置的 TPU 策略會選擇 8 條並行 shards。
import os import pprint import tensorflow as tf if 'COLAB_TPU_ADDR' not in os.environ: print('ERROR: Not connected to a TPU runtime') else: tpu_address = 'grpc://' + os.environ['COLAB_TPU_ADDR'] print ('TPU address is', tpu_address) with tf.Session(tpu_address) as session: devices = session.list_devices() print('TPU devices:') pprint.pprint(devices)
目前,Colab 一共支援三種執行時,即 CPU、GPU(K80)和 TPU(據說是 TPU v2)。但我們不太瞭解 Colab 中的 GPU 和 TPU 在深度模型中的表現如何,當然後面會用具體的任務去測試,不過現在我們可以先用相同的運算試試它們的效果。因此我們首先嚐試用簡單的卷積運算測試它們的迭代時間。
在測試不同的硬體時,需要切換到不同的執行時。如下先定義 128 張隨機生成的 256×256 影象,然後定義 256 個 5×5 的卷積核後就能執行卷積運算,其中魔術函式 %timeit 會自動多次執行,以產生一個更為精確的平均執行時間。
import tensorflow as tf import numpy as np import timeit tf.reset_default_graph() img = np.random.randn(128, 256, 256, 3).astype(np.float32) w = np.random.randn(5, 5, 3, 256).astype(np.float32) conv = tf.nn.conv2d(img, w, [1,2,2,1], padding='SAME') with tf.Session() as sess: # with tf.device("/gpu:0") as dev: %timeit sess.run(conv)
然而,是我們想當然了,使用 TPU 執行運算似乎需要特定的函式與運算,它不像 CPU 和 GPU 那樣可以共用相同的程式碼。分別選擇 CPU、GPU 和 TPU 作為執行時狀態,執行上面的程式碼並迭代一次所需要的時間分別為:2.44 s、280 ms、2.47 s。從這裡看來,僅修改執行時狀態,並不會真正呼叫 TPU 資源,真正實現運算的還是 CPU。隨後我們發現 TF 存在一個神奇的類 tf.contrib.tpu,似乎真正呼叫 TPU 資源必須使用它改寫模型。
因此,根據文件與呼叫示例,我們將上面的卷積測試程式碼改為了以下形式,併成功地呼叫了 TPU。此外,因為每次都需要重新連線不同的執行時,所以這裡的程式碼都保留了庫的匯入。雖然程式碼不太一樣,但直覺上它的計算量應該和上面的程式碼相同,因此大致上能判斷 Colab 提供的 GPU、TPU 速度對比。
import tensorflow as tf import numpy as np import timeit import os tpu_address = 'grpc://' + os.environ['COLAB_TPU_ADDR'] tf.reset_default_graph() def conv_op(): img =np.random.randn(128, 256, 256, 3).astype(np.float32) conv_w = np.random.randn(5, 5, 3, 256).astype(np.float32) conv = tf.nn.conv2d(img, conv_w, [1,2,2,1], padding='SAME') tpu_ops = tf.contrib.tpu.batch_parallel(conv_op, [], num_shards=8) with tf.Session(tpu_address) as sess: sess.run(tf.contrib.tpu.initialize_system()) sess.run(tpu_ops) %timeit sess.run(tpu_ops) sess.run(tf.contrib.tpu.shutdown_system())
執行後出現了非常意外的結果,這樣的卷積運算每一次迭代只需要 1.22 ms。如下圖所示,很可能存在變數快取等其它因素造成了一定程度的緩慢,但 TPU 的速度無可置疑地快。因此如果在 Colab 上測試模型,我們就更希望使用免費的 TPU,不過使用 TPU 需要改模型程式碼,這又比較麻煩。
儘管簡單的卷積運算 TPU 要比 K80 快很多,但這隻能給我們一個大致的猜想,因此我們需要測試完整的模型。注意在 tf.contrib.tpu 類中,它還提供了兩種使用 TPU 的簡單方法,即直接使用 Keras 介面和使用 TPUEstimator 構建模型。
在 tf.contrib.tpu 的文件中,我們發現 tf.contrib.tpu.keras_to_tpu_model 方法可以直接將 Keras 模型與對應的權重複制到 TPU,並返回 TPU 模型。該方法在輸入 Keras 模型和在多個 TPU 核心上的訓練策略後,能輸出一個 Keras TPU 模型的例項,且可分配到 TPU 進行運算。
除此之外,另外一種呼叫 TPU 計算資源的方法是 tf.contrib.tpu.TPUEstimator,對於修正我們原來的TensorFlow 模型以適用 TPU,它可能是一種更方便的方式。根據文件所示,TPUEstimator 類繼承自 Estimator 類,因此它不僅支援在 TPU 上運算,同時還支援 CPU 和 GPU 的運算。TPUEstimator 隱藏了非常多在 TPU 上訓練的細節,例如為多個 TPU 核心複製多個輸入和模型等。
TPU 呼叫文件地址:https://www.tensorflow.org/api_docs/python/tf/contrib/tpu
對比 TPU 與 GPU 的計算速度
為了簡單起見,這裡僅使用 Fashion-MNIST 資料集與簡單的 5 層 卷積神經網路 測試不同的晶片效能。這個模型是基於 Keras 構建的,因為除了模型轉換與編譯,Keras 模型在 TPU 和 GPU 的訓練程式碼都是一樣的,且用 Keras 模型做展示也非常簡潔。
幾天前谷歌 Colab 團隊發了一版使用 Keras 呼叫 TPU 的教程,因此我們就藉助它測試 TPU 的訓練速度。對於 GPU 的測試,我們可以修改該模型的編譯與擬合部分,並呼叫 GPU 進行訓練。所以整個訓練的資料獲取、模型結構、超引數都是一樣的,不一樣的只是硬體。
教程地址:https://colab.research.google.com/github/tensorflow/tpu/blob/master/tools/colab/fashion_mnist.ipynb
以下是整個測試的公共部分,包含了訓練資料的獲取和模型架構。Keras 的模型程式碼非常好理解,如下第一個卷積層首先採用了批歸一化,然後用 64 個 5×5 的卷積核實現卷積運算,注意這裡採用的啟用函式都是指數線性單元(ELU)。隨後對卷積結果做 2×2 的最大池化,並加上一個隨機丟棄率為 0.25 的Dropout層,最後得出的結果就是第一個卷積層的輸出。
import tensorflow as tf import numpy as np import timeit (x_train, y_train), (x_test, y_test) = tf.keras.datasets.fashion_mnist.load_data() # add empty color dimension x_train = np.expand_dims(x_train, -1) x_test = np.expand_dims(x_test, -1) model = tf.keras.models.Sequential() # 以下為第一個卷積層 model.add(tf.keras.layers.<mark data-type="technologies" data-id="e3995dcb-2c96-4671-b7ed-d5f2abae3c08">Batch</mark><mark data-type="technologies" data-id="a815978e-ab05-479e-b6b4-30176b3d5427">Normalization</mark>(input_shape=x_train.shape[1:])) model.add(tf.keras.layers.Conv2D(64, (5, 5), padding='same', activation='elu')) model.add(tf.keras.layers.Max<mark data-type="technologies" data-id="0a4cedf0-0ee0-4406-946e-2877950da91d">Pooling</mark>2D(pool_size=(2, 2), strides=(2,2))) model.add(tf.keras.layers.<mark data-type="technologies" data-id="1c91194a-1732-4fb3-90c9-e0135c69027e">Dropout</mark>(0.25)) model.add(tf.keras.layers.<mark data-type="technologies" data-id="e3995dcb-2c96-4671-b7ed-d5f2abae3c08">Batch</mark><mark data-type="technologies" data-id="a815978e-ab05-479e-b6b4-30176b3d5427">Normalization</mark>(input_shape=x_train.shape[1:])) model.add(tf.keras.layers.Conv2D(128, (5, 5), padding='same', activation='elu')) model.add(tf.keras.layers.Max<mark data-type="technologies" data-id="0a4cedf0-0ee0-4406-946e-2877950da91d">Pooling</mark>2D(pool_size=(2, 2))) model.add(tf.keras.layers.<mark data-type="technologies" data-id="1c91194a-1732-4fb3-90c9-e0135c69027e">Dropout</mark>(0.25)) model.add(tf.keras.layers.<mark data-type="technologies" data-id="e3995dcb-2c96-4671-b7ed-d5f2abae3c08">Batch</mark><mark data-type="technologies" data-id="a815978e-ab05-479e-b6b4-30176b3d5427">Normalization</mark>(input_shape=x_train.shape[1:])) model.add(tf.keras.layers.Conv2D(256, (5, 5), padding='same', activation='elu')) model.add(tf.keras.layers.Max<mark data-type="technologies" data-id="0a4cedf0-0ee0-4406-946e-2877950da91d">Pooling</mark>2D(pool_size=(2, 2), strides=(2,2))) model.add(tf.keras.layers.<mark data-type="technologies" data-id="1c91194a-1732-4fb3-90c9-e0135c69027e">Dropout</mark>(0.25)) model.add(tf.keras.layers.Flatten()) model.add(tf.keras.layers.Dense(256)) model.add(tf.keras.layers.Activation('elu')) model.add(tf.keras.layers.<mark data-type="technologies" data-id="1c91194a-1732-4fb3-90c9-e0135c69027e">Dropout</mark>(0.5)) model.add(tf.keras.layers.Dense(10)) model.add(tf.keras.layers.Activation('softmax')) model.summary()
在定義模型後,TPU 需要轉化模型與編譯模型。如下所示,keras_to_tpu_model 方法需要輸入正常 Keras 模型及其在 TPU 上的分散式策略,這可以視為「TPU 版」的模型。完成模型的轉換後,只需要像一般 Keras 模型那樣執行編譯並擬合數據就可以了。
注意兩個模型的超引數,如學習率、批量大小和 Epoch 數量等都設定為相同的數值,且損失函式和最優化器等也採用相同的方法。
import os tpu_model = tf.contrib.tpu.keras_to_tpu_model( model, strategy=tf.contrib.tpu.TPUDistributionStrategy( tf.contrib.cluster_resolver.TPUClusterResolver(tpu='grpc://' + os.environ['COLAB_TPU_ADDR']) ) ) tpu_model.compile( optimizer=tf.train.Adam<mark data-type="technologies" data-id="fa50298e-1a85-4af0-ae96-a82708f4b610">Optimizer</mark>(learning_rate=1e-3, ), loss=tf.keras.losses.sparse_categorical_crossentropy, metrics=['sparse_categorical_accuracy'] ) def train_gen(batch_size): while True: offset = np.random.randint(0, x_train.shape[0] - batch_size) yield x_train[offset:offset+batch_size], y_train[offset:offset + batch_size] %time tpu_model.fit_generator(train_gen(1024), epochs=5, steps_per_epoch=100, validation_data=(x_test, y_test))
最後在使用 GPU 訓練模型時,我們會刪除模型轉換步驟,並保留相同的編譯和擬合部分。訓練的結果如下所示,Colab 提供的 TPU 要比 GPU 快 3 倍左右,一般 TPU 訓練 5 個 Epoch 只需要 40 多秒,而 GPU 需要 2 分多鐘。
Colab 使用免費 TPU 訓練的資訊摘要。
Colab 使用免費 GPU 訓練的資訊摘要。
最後,Colab 確實提供了非常強勁的免費 TPU,而且使用 Keras 或 TPUEstimator 也很容易重新搭建或轉換已有的TensorFlow 模型。機器之心只是簡單地試用了 Colab 免費 TPU,還有很多特性有待讀者的測試,例如支援 TPU 的 PyTorch 1.0 或迴圈神經網路在 TPU 上的效能等。
參考資料:
-
文件:https://www.tensorflow.org/api_docs/python/tf/contrib/tpu
-
官方示例(Keras):https://colab.research.google.com/github/tensorflow/tpu/blob/master/tools/colab/shakespeare_with_tpu_and_keras.ipynb
-
官方示例(TPUEstimator):https://colab.research.google.com/github/tensorflow/tpu/blob/master/tools/colab/shakespeare_with_tpuestimator.ipynb
-
Sam Wittevee PPT:https://www.dropbox.com/s/jg7j07unw94wbom/TensorFlow%20Keras%20Colab%20TPUs.pdf?dl=0
-
Ceshine Lee 部落格:https://medium.com/the-artificial-impostor/keras-for-tpus-on-google-colaboratory-free-7c00961fed69
ofollow,noindex" target="_blank">工程 TPU 谷歌 Colab 計算資源
相關資料
Neural Network
(人工)神經網路是一種起源於 20 世紀 50 年代的監督式機器學習模型,那時候研究者構想了「感知器(perceptron)」的想法。這一領域的研究者通常被稱為「聯結主義者(Connectionist)」,因為這種模型模擬了人腦的功能。神經網路模型通常是通過反向傳播演算法應用梯度下降訓練的。目前神經網路有兩大主要型別,它們都是前饋神經網路:卷積神經網路(CNN)和迴圈神經網路(RNN),其中 RNN 又包含長短期記憶(LSTM)、門控迴圈單元(GRU)等等。深度學習是一種主要應用於神經網路幫助其取得更好結果的技術。儘管神經網路主要用於監督學習,但也有一些為無監督學習設計的變體,比如自動編碼器和生成對抗網路(GAN)。
來源:機器之心
Dropout
神經網路訓練中防止過擬合的一種技術
來源:ImageNet Classification with Deep Convolutional
Exponential linear unit
一種啟用函式, ELUs是對ReLU啟用函式的一種演變,將啟用函式更能夠保持一個noise-robust狀態。所以提出一個具有負值的啟用函式,這可以使得平均啟用接近於零,但它會以更小的引數飽和為負值的啟用函式ELUs。
來源: Fast and Accurate Deep Network Learning by Exponential Linear Units (ELUs)
Convolutional neural network
卷積神經網路(Convolutional Neural Network, CNN)是一種前饋神經網路,它的人工神經元可以響應一部分覆蓋範圍內的周圍單元,對於大型影象處理有出色表現。卷積神經網路由一個或多個卷積層和頂端的全連通層(對應經典的神經網路)組成,同時也包括關聯權重和池化層(pooling layer)。這一結構使得卷積神經網路能夠利用輸入資料的二維結構。與其他深度學習結構相比,卷積神經網路在影象和語音識別方面能夠給出更好的結果。這一模型也可以使用反向傳播演算法進行訓練。相比較其他深度、前饋神經網路,卷積神經網路需要考量的引數更少,使之成為一種頗具吸引力的深度學習結構。 卷積網路是一種專門用於處理具有已知的、網格狀拓撲的資料的神經網路。例如時間序列資料,它可以被認為是以一定時間間隔取樣的一維網格,又如影象資料,其可以被認為是二維畫素網格。
來源:Goodfellow, I.; Bengio Y.; Courville A. (2016). Deep Learning. MIT Press. 維基百科
Hyperparameter
在機器學習中,超引數是在學習過程開始之前設定其值的引數。 相反,其他引數的值是通過訓練得出的。 不同的模型訓練演算法需要不同的超引數,一些簡單的演算法(如普通最小二乘迴歸)不需要。 給定這些超引數,訓練演算法從資料中學習引數。相同種類的機器學習模型可能需要不同的超引數來適應不同的資料模式,並且必須對其進行調整以便模型能夠最優地解決機器學習問題。 在實際應用中一般需要對超引數進行優化,以找到一個超引數元組(tuple),由這些超引數元組形成一個最優化模型,該模型可以將在給定的獨立資料上預定義的損失函式最小化。
來源: Wikipedia
Loss function
在數學優化,統計學,計量經濟學,決策理論,機器學習和計算神經科學等領域,損失函式或成本函式是將一或多個變數的一個事件或值對映為可以直觀地表示某種與之相關“成本”的實數的函式。
來源: Wikipedia
Normalization
規範化:將屬性資料按比例縮放,使之落入一個小的特定區間,如-1.0 到1.0 或0.0 到1.0。 通過將屬性資料按比例縮放,使之落入一個小的特定區間,如0.0到1.0,對屬性規範化。對於距離度量分類演算法,如涉及神經網路或諸如最臨近分類和聚類的分類演算法,規範化特別有用。如果使用神經網路後向傳播演算法進行分類挖掘,對於訓練樣本屬性輸入值規範化將有助於加快學習階段的速度。對於基於距離的方法,規範化可以幫助防止具有較大初始值域的屬性與具有較小初始值域的屬相相比,權重過大。有許多資料規範化的方法,包括最小-最大規範化、z-score規範化和按小數定標規範化。
來源:Jiawei Han;Micheline Kamber著 資料探勘概念與技術 機械工業出版社
Pooling
池化(Pooling)是卷積神經網路中的一個重要的概念,它實際上是一種形式的降取樣。有多種不同形式的非線性池化函式,而其中“最大池化(Max pooling)”是最為常見的。它是將輸入的影象劃分為若干個矩形區域,對每個子區域輸出最大值。直覺上,這種機制能夠有效的原因在於,在發現一個特徵之後,它的精確位置遠不及它和其他特徵的相對位置的關係重要。池化層會不斷地減小資料的空間大小,因此引數的數量和計算量也會下降,這在一定程度上也控制了過擬合。通常來說,CNN的卷積層之間都會週期性地插入池化層。
來源: cs231n
Weight
線性模型中特徵的係數,或深度網路中的邊。訓練線性模型的目標是確定每個特徵的理想權重。如果權重為 0,則相應的特徵對模型來說沒有任何貢獻。
來源:Google AI Glossary
Tensor
張量是一個可用來表示在一些向量、標量和其他張量之間的線性關係的多線性函式,這些線性關係的基本例子有內積、外積、線性對映以及笛卡兒積。其座標在 維空間內,有 個分量的一種量,其中每個分量都是座標的函式,而在座標變換時,這些分量也依照某些規則作線性變換。稱為該張量的秩或階(與矩陣的秩和階均無關係)。 在數學裡,張量是一種幾何實體,或者說廣義上的“數量”。張量概念包括標量、向量和線性運算元。張量可以用座標系統來表達,記作標量的陣列,但它是定義為“不依賴於參照系的選擇的”。張量在物理和工程學中很重要。例如在擴散張量成像中,表達器官對於水的在各個方向的微分透性的張量可以用來產生大腦的掃描圖。工程上最重要的例子可能就是應力張量和應變張量了,它們都是二階張量,對於一般線性材料他們之間的關係由一個四階彈性張量來決定。
來源: 維基百科
機器之心編輯