TensorFlow系統架構及c++程序設計
TensorFlow 1.0及Dev Summit(2017)回顧
2015年11月9日谷歌開源了人工智慧平台TensorFlow,同時成為2015年最受關注的開源項目之一。經歷了從v0.1到v0.12的12個版本迭代後,谷歌於2017年2月15日發布了TensorFlow 1.0 版本,並同時在美國加州山景城舉辦了首屆TensorFlow Dev Summit會議。和以往版本相比,TensorFlow 1.0 的特性改進主要體現在以下幾個方面:
速度更快:TensorFlow 1.0版本採用了XLA的編譯技術,改進了TensorFlow的運行性能及內存利用。從Benchmark問題的測試結果來看,對單機Inception v3模型,實現了在單機8 GPUs上7.3倍的運算加速;對分散式Inception v3模型,實現了在多機64 GPUs上58倍的運算加速。
更加靈活:該版本除了支持tf.layers,tf.metrics及tf.losses模型的High-Level API外,實現了對keras(high-level neural networks
library)API的全面兼容。
更產品化:TensorFlow Python API在v1.0版本中趨於穩定,為產品兼容性打下堅實基礎。
在TensorFlow 1.0版本發布的當天,谷歌公司還舉辦了TensorFlow 2017 DEV https://events.withgoogle.com/tensorflow-dev-summit/videos-and-agenda/#content)主要包括以下幾個方面的主題演講:
- XLA (TensorFlow, Compiled)編譯技術 :介紹採用XLA技術最小化圖計算執行時間和最大化利用計算資源,用於減少數據訓練和模型結果推斷時間。
- Hands-on TensorBoard可視化技術:介紹了如何使用TensorBoard,以及TensorFlow圖模型、訓練數據的可視化等。
- TensorFlow High-Level API:介紹了使用Layers, Estimators, and
Canned Estimators High-Level API定義訓練模型。
- Integrating Keras & TensorFlow: 介紹了如何在TensorFlow中使用Keras API進行模型定義及訓練。
- TensorFlow at DeepMind:介紹了在DeepMind中使用TensorFlow平台的典型案例,包括AlphaGo等應用。
- Skin Cancer Image Classification:介紹了斯坦福醫學院使用TensorFlow分類皮膚癌照片,用於醫學診斷。
- Mobile and Embedded TensorFlow:介紹了如何把TensorFlow模型運行在移動終端、嵌入式設備,包括安卓,iOS等系統。
- Distributed TensorFlow:系統性地介紹了分散式TensorFlow的相關技術,以及如何應用於大規模模型訓練。
- TensorFlow Ecosystem:講解了TensorFlow的生態系統,包括生成訓練數據,分散式運行TensorFlow和serving models的產品化流程。
- Serving Models in Production with TensorFlow Serving:系統性講解了如何在生產環境中應用TensorFlow Serving模型。
- ML Toolkit:介紹了TensorFlow的機器學習庫,如線性回歸,KMeans等演算法模型的使用。
- Sequence Models and the RNN API:介紹了如何構建高性能的sequence-to-sequence模型,以及相關API。
- Wide & Deep Learning: 介紹了如何結合Wide模型和Deep模型構建綜合訓練模型。
- Magenta,Music and Art Generation:使用增強型深度學習模型生成音樂聲音和藝術圖片。
- Case Study,TensorFlow in Medicine - Retinal Imaging:使用TensorFlow機器學習平台對醫學視網膜圖片進行分類,輔助醫學診斷。
TensorFlow系統架構
TensorFlow作為分散式機器學習平台,主要架構如下圖所示。RPC和RDMA為網路層,主要負責傳遞神經網路演算法參數。CPU和GPU為設備層,主要負責神經網路演算法中具體的運算操作。Kernel為TensorFlow中演算法操作的具體實現,如卷積操作,激活操作等。Distributed Master用於構建子圖;切割子圖為多個分片,不同的子圖分片運行在不同的設備上;Master還負責分發子圖分片到Executor/Work端。Executor/Work在設備(CPUs,GPUs,etc.)上,調度執行子圖操作;並負責向其它Worker發送和接收圖操作的運行結果。C API把TensorFlow分割為前端和後端,前端(Python/C++/Java Client)基於C API觸發TensorFlow後端程序運行。Training libraries和Inference libs是模型訓練和推導的庫函數,為用戶開發應用模型使用。
下圖為Client、Master及Worker的內部工作原理。"/job:worker/task:0" 和 "/job:ps/task:0" 表示worker中的執行服務。"job:ps"表示參數伺服器,用於存儲及更新模型參。"job:worker"用於優化模型參數,並發參數發送到參數伺服器上。Distributed Master和Worker Service只存在於分散式TensorFlow中。單機版本的TensorFlow實現了Local的Session,通過本地進程的內部通訊實現上述功能。
用戶編寫TensorFlow應用程序生成計算圖,Client組件會創建Session,並通過序列化技術,發送圖定義到Distributed Master組件。下圖中,Client創建了一個 s+=w*x+b的圖計算模型。
當Client觸發Session運算的時候,Maser構建將要運行的子圖。並根據設備情況,切割子圖為多個分片。下面為Master構建的運行子圖:
接著切割子圖,把模型參數分組在參數伺服器上,圖計算操作分組在運算Worker上。下圖為一種可行的圖切割策略:
Distributed Master會根據模型參數的分區情況進行切割邊,在Task間插入發送和接收Tensor信息的通信節點,如下圖所示:
接著Distributed Master通過RegisterGraph方法發送子圖分片給Task,如下圖所示:
Master通過RunGraph觸發子圖運算,Worker會使用GPU/CPU運算設備執行TensorFlow Kernel運算。在本節點的CPU和GPU之間,使用cudaMemcpyAsync傳輸數據;在本節點GPU和GPU之間,使用peer-to-peer DMA傳輸數據,避免通過CPU複製數據。TensorFlow使用gRPC(TCP)和RDMA (Converged Ethernet)技術,實現Worker間的數據通信及傳輸,如下圖所示:
高性能程序設計
TensorFlow內核採用C/C++開發,並提供了C++,Python,Java,Go語言的Client API。特別是Python API,是目前主流的TensorFlow模型開發介面。但為什麼還需要採用C++ API去訓練模型呢?本文基於如下兩點考慮,首先當我們採用Python API去訓練模型的時候,需要不斷地用Python API調用C/C++底層介面,重複的介面調用一定程度上影響了程序的執行性能。更為重要的是,在GPU上訓練模型的時候需要大量的內存交換;如果採用C++ API去訓練模型,可提供更好的運算性能及更好地控制GPU內存的分配。
下圖為Python API的運算架構:在模型訓練的每次迭代中,程序通過Python API讀取Batch Data,然後通過TensorFlow Session Run介面,傳遞數據給C++,並觸發神經網路訓練。如下圖所示:
下圖為C++ API的運算架構:在模型訓練的每次迭代中,通過C++ API讀取Batch Data後,直接觸發模型訓練。減少了不同語言間API介面的循環調用及數據傳輸。如下圖所示:
為了採用C++ API進行模型訓練,我們首先需要編寫訓練模型,這個編寫過程可以採用Python語言來完成。我們首先採用Python API編寫訓練模型,然後把圖模型轉換為Protobuf的序列化文件。接著通過C++ API載入該模型文件,創建TensorFlow Session,初始化模型變數,以及載入訓練數據並執行神經網路訓練。程序架構如下圖所示:
下面為使用Python API定義訓練模型的示例:
with tf.Session() as sess: #定義Placeholder Tensor接入訓練數據 x = tf.placeholder(tf.float32, [None, 32], name="x") y = tf.placeholder(tf.float32, [None, 8], name="y") #定義訓練模型 w1 = tf.Variable(tf.truncated_normal([32, 16], stddev=0.1)) b1 = tf.Variable(tf.constant(0.0, shape=[16])) w2 = tf.Variable(tf.truncated_normal([16, 8], stddev=0.1)) b2 = tf.Variable(tf.constant(0.0, shape=[8])) a = tf.nn.tanh(tf.nn.bias_add(tf.matmul(x, w1), b1)) y_out = tf.nn.tanh(tf.nn.bias_add(tf.matmul(a, w2), b2), name="y_out") cost = tf.reduce_sum(tf.square(y-y_out), name="cost") optimizer = tf.train.AdamOptimizer().minimize(cost, name="train") #定義變數初始化操作 init = tf.initialize_variables(tf.all_variables(), name=init_all_vars_op) #把圖模型轉換為Protobuf文件tf.train.write_graph(sess.graph_def, ./, mlp.pb, as_text=False)
下面為使用C++ API載入Protobuf圖模型,並執行訓練的示例:
#include "tensorflow/core/public/session.h"#include "tensorflow/core/graph/default_device.h"using namespace tensorflow;int main(int argc, char* argv[]) { //Protobuf模型文件名 std::string graph_definition = "mlp.pb"; //Tensorflow Sesssion Session* session; //定義圖模型對象 GraphDef graph_def; SessionOptions opts; //存儲Session會話的運行結果 std::vector<Tensor> outputs; #載入Protobuf模型文件到圖模型對象中 TF_CHECK_OK(ReadBinaryProto(Env::Default(), graph_definition, &graph_def)); // 默認在gpu 0上執行模型的訓練操作 graph::SetDefaultDevice("/gpu:0", &graph_def); //設定GPU顯存使用參數 opts.config.mutable_gpu_options()->set_per_process_gpu_memory_fraction(0.5); opts.config.mutable_gpu_options()->set_allow_growth(true); //創建TensorFlow會話 TF_CHECK_OK(NewSession(opts, &session)); // 載入圖對象到會話中 TF_CHECK_OK(session->Create(graph_def)); // 執行模型參數初始化操作 TF_CHECK_OK(session->Run({}, {}, {"init_all_vars_op"}, nullptr)); //定義模型輸入數據,包括數據類型和維度信息 Tensor x(DT_FLOAT, TensorShape({100, 32})); Tensor y(DT_FLOAT, TensorShape({100, 8})); //把Tensor轉換為矩陣,並初始化Tensor數據 auto _XTensor = x.matrix<float>(); auto _YTensor = y.matrix<float>(); _XTensor.setRandom(); _YTensor.setRandom(); for (int i = 0; i < 10; ++i) { //執行模型的訓練操作,{{"x", x}, {"y", y}}表示輸入數據Tensor名稱和Tensor對象;{"cost"}表示要獲取輸出值的操作名稱;&outputs表示執行"cost"操作後返回的Tensor對象 TF_CHECK_OK(session->Run({{"x", x}, {"y", y}}, {"cost"}, {}, &outputs)); //獲取執行「cost「操作後的運算結果 float cost = outputs[0].scalar<float>()(0); std::cout << "Cost: " << cost << std::endl; //執行"train"操作 TF_CHECK_OK(session->Run({{"x", x}, {"y", y}}, {}, {"train"}, nullptr)); // Train outputs.clear(); } //關閉Session及刪除Session對象 session->Close(); delete session; return 0;}
當C++程序寫好後,編譯時候需要鏈接的頭文件,開源已經幫我們整理好了,存放於目錄/usr/lib/python2.7/site-packages/tensorflow/include下。編譯和運行的時候需要鏈接libtensorflow_cc.so,可以按照下面的方式編譯該庫文件:bazel build -c opt //tensorflow:libtensorflow_cc.so --copt=-m64 --linkopt=-m64 --spawn_strategy=standalone --genrule_strategy=standalone --verbose_failures。具體可參考TensorFlow源代碼的官方編譯文檔。
總結
本文首先回顧了TensorFlow 1.0主要新特性及TensorFlow 2017 Dev Summit的主要議程。到目前為止TensorFlow的GitHub Star排名為51000+, Fork排名已達24000+,有15000+ commits。並隨著TensorFlow新版本的不斷發布以及新特性的不斷增加,TensorFlow使用更加靈活,運行速度更快,使用方式更產品化,已成為目前主流的深度學習平台之一。接著介紹了TensorFlow的系統架構,包括Client,Master,Worker,Kernel的相關概念及運行方式,是一種適合大規模分散式訓練的機器學習平台。從上述系統架構中可以看到,TensorFlow內核採用C/C++開發,當採用Python API去訓練模型的時候,需要不斷地用Python調用C/C++底層介面,重複的介面調用一定程度上影響了程序的執行性能。如果有最求高性能運算的朋友,可以嘗試用下本文高性能運算章節推薦的方法。
推薦閱讀:
※機器學習 | 更進一步,用評估器給花卉分類
※win10 + CUDA 9.0 + cuDNN 7.0 + tensorflow源碼編譯安裝
※NLP(2) Tensorflow 文本- 價格建模 Part2
※Tensorflow多層感知機
※TensorFlow學習筆記之五——源碼分析之最近演算法
TAG:TensorFlow | 深度學習DeepLearning |