背景
本文檔旨在為那些想要用其他編程語言創(chuàng)建或開發(fā) TensorFlow 功能的人員提供指導(dǎo)。本文介紹了 TensorFlow 的功能以及在其他編程語言中實(shí)現(xiàn)相同功能應(yīng)采取的推薦步驟。
Python 是 TensorFlow 支持的第一種客戶端語言,目前支持的功能最多。該功能正逐步移植到 TensorFlow 的核心(用 C++ 實(shí)現(xiàn))并通過 C API 公開??蛻舳苏Z言應(yīng)使用該語言的外部函數(shù)接口 (FFI) 調(diào)用此 C API 以提供 TensorFlow 功能。
通過某個(gè)編程語言提供 TensorFlow 功能可分為幾大類別:
運(yùn)行預(yù)定義圖:給定 GraphDef(或 MetaGraphDef)協(xié)議消息,能夠創(chuàng)建會(huì)話,運(yùn)行查詢并獲得張量結(jié)果。這對(duì)于想要在預(yù)訓(xùn)練模型上進(jìn)行推斷的移動(dòng)應(yīng)用或服務(wù)器來說已足夠
圖構(gòu)造:每個(gè)定義的 TensorFlow 操作至少有一個(gè)向圖添加操作的函數(shù)。理想情況下,這些函數(shù)會(huì)自動(dòng)生成,因此在修改操作定義時(shí)它們會(huì)保持同步
梯度(亦稱為自動(dòng)微分):給定圖及輸入和輸出操作列表,向圖添加計(jì)算輸入相對(duì)于輸出的偏導(dǎo)數(shù)(梯度)的操作。允許針對(duì)圖中的特定操作自定義梯度函數(shù)
函數(shù):定義可在主 GraphDef 中的多個(gè)位置調(diào)用的子圖。在包含在 GraphDef 中的 FunctionDefLibrary 內(nèi)定義 FunctionDef
控制流:使用用戶指定的子圖構(gòu)造 If 和 While 循環(huán)。理想情況下,這些控制流支持梯度(參見上文)
神經(jīng)網(wǎng)絡(luò)庫:一些組件,它們共同支持神經(jīng)網(wǎng)絡(luò)模型的創(chuàng)建和訓(xùn)練(可能在分布式設(shè)置中)。雖然以其他語言提供此功能會(huì)很方便,但目前還沒有用除 Python 以外的語言支持此功能的計(jì)劃。這些庫通常是上述功能的封裝容器
語言綁定至少應(yīng)支持運(yùn)行預(yù)定義的圖,但大多數(shù)還應(yīng)支持圖構(gòu)造。TensorFlow Python API 提供了所有這些功能。
當(dāng)前狀態(tài)
應(yīng)該在 C API 基礎(chǔ)之上構(gòu)建新的語言支持。但是,正如您在下表中所看到的,部分功能尚未在 C 中提供。在 C API 中提供更多功能是一個(gè)長期項(xiàng)目。
推薦方法
運(yùn)行預(yù)定義的圖
語言綁定應(yīng)該定義以下類:
Graph:表示 TensorFlow 計(jì)算的圖。包含操作(在客戶端語言中由 Operation 表示)并且對(duì)應(yīng)于 C API 中的 TF_Graph。主要在創(chuàng)建新的 Operation 對(duì)象和啟動(dòng) Session 時(shí)用作參數(shù)。還支持遍歷圖中的操作 (TF_GraphNextOperation),按名稱查詢操作 (TF_GraphOperationByName),以及轉(zhuǎn)換為 GraphDef 協(xié)議消息或?qū)ζ溥M(jìn)行轉(zhuǎn)換(C API 中的 TF_GraphToGraphDef 和 TF_GraphImportGraphDef)
Operation:表示圖中的計(jì)算節(jié)點(diǎn)。對(duì)應(yīng)于 C API 中的 TF_Operation
Output:表示圖中某個(gè)操作的一個(gè)輸出。具有 DataType(最終是一個(gè)形狀)??梢宰鳛檩斎?yún)?shù)傳遞給函數(shù)以向圖添加操作,或傳遞給 Session 的 Run() 方法以將該輸出提取為張量。對(duì)應(yīng)于 C API 中的 TF_Output
Session:表示 TensorFlow 運(yùn)行時(shí)的特定實(shí)例的客戶端。它的主要任務(wù)是使用 Graph 和一些選項(xiàng)進(jìn)行構(gòu)建,然后進(jìn)行字段調(diào)用以對(duì)圖執(zhí)行 Run() 操作。對(duì)應(yīng)于 C API 中的 TF_Session
Tensor:表示所有元素都具有相同 DataType 的 N 維(矩形)數(shù)組。向 Session 的 Run() 調(diào)用提供數(shù)據(jù)或從中獲取數(shù)據(jù)。對(duì)應(yīng)于 C API 中的 TF_Tensor
DataType:包含受 TensorFlow 支持的所有可能張量類型的枚舉。對(duì)應(yīng)于 C API 中的 TF_DataType,在 Python API 中通常稱為 dtype
圖構(gòu)造
TensorFlow 有很多操作,并且操作列表不是靜態(tài)的,因此我們建議生成用于將操作添加到圖中的函數(shù),而不是手動(dòng)逐個(gè)編寫這些函數(shù)(但手動(dòng)編寫一些函數(shù)不失為確定生成器應(yīng)該生成哪些內(nèi)容的不錯(cuò)方法)。生成函數(shù)所需的信息包含在 OpDef 協(xié)議消息中。
您可以通過以下幾種方法獲取已注冊(cè)操作的 OpDef 列表:
C API 中的 TF_GetAllOpList 會(huì)檢索所有已注冊(cè)的 OpDef 協(xié)議消息。它可以用于以客戶端語言編寫生成器。這就要求客戶端語言支持協(xié)議緩沖區(qū),以便解釋 OpDef 消息
C++ 函數(shù) OpRegistry::Global()->GetRegisteredOps() 會(huì)返回所有已注冊(cè) OpDef(在 tensorflow/core/framework/op.h 中定義)的相同列表。它可以用于以 C++ 編寫生成器(對(duì)于不支持協(xié)議緩沖區(qū)的語言特別有用)
該列表的 ASCII 序列化版本通過自動(dòng)化流程定期檢入tensorflow/core/ops/ops.pbtxt
OpDef 會(huì)指定以下內(nèi)容:
駝峰命名法 (CamelCase) 的操作名稱。對(duì)于生成的函數(shù),請(qǐng)遵循相應(yīng)語言的慣例。例如,如果該語言使用蛇形命名法 (snake_case),則使用該命名法而不是駝峰命名法表示此操作的函數(shù)名稱
輸入和輸出列表。這些內(nèi)容的類型可以通過引用屬性實(shí)現(xiàn)多態(tài),如添加操作的輸入和輸出部分所述
屬性列表及其默認(rèn)值(如果有)。請(qǐng)注意,系統(tǒng)會(huì)推斷其中一些屬性(如果它們由輸入確定),一些屬性是可選屬性(如果它們具有默認(rèn)值),另一些屬性是必需屬性(無默認(rèn)值)
一般操作以及輸入、輸出和非推斷屬性的文檔
運(yùn)行時(shí)使用的其他一些字段,可以被代碼生成器忽略
可以將 OpDef 轉(zhuǎn)換為函數(shù)的文本,該函數(shù)使用 TF_OperationDescription C API(封裝在相應(yīng)語言的 FFI 中)將該操作添加到圖中:
從 TF_NewOperation() 開始創(chuàng)建 TF_OperationDescription*。
每個(gè)輸入調(diào)用 TF_AddInput() 或 TF_AddInputList() 一次(取決于輸入是否具有列表類型)。
調(diào)用 TF_SetAttr*() 函數(shù)來設(shè)置非推斷屬性。如果您不想替換默認(rèn)值,可以跳過具有默認(rèn)值的屬性。
根據(jù)需要設(shè)置選填字段:
TF_SetDevice():強(qiáng)制在特定設(shè)備上執(zhí)行操作。
TF_AddControlInput():添加要求以說明在此操作開始運(yùn)行之前完成另一個(gè)操作
TF_SetAttrString("_kernel"):設(shè)置內(nèi)核標(biāo)簽(很少使用)
TF_ColocateWith():將一個(gè)操作與另一個(gè)操作共置到一起
完成后調(diào)用 TF_FinishOperation()。這樣會(huì)將操作添加到圖中,之后便無法修改。
現(xiàn)有示例會(huì)在編譯流程中運(yùn)行代碼生成器(使用 Bazel genrule)?;蛘?,代碼生成器可以由自動(dòng)化 cron 進(jìn)程運(yùn)行,并可能檢入結(jié)果中。這可能會(huì)導(dǎo)致生成的代碼與檢入代碼庫中的 OpDef 之間產(chǎn)生分歧,但對(duì)于會(huì)提前生成代碼的語言(例如適用于 Go 的 go get 和適用于 Rust 的 cargo ops)很有用。另一方面,對(duì)于某些語言,代碼可以從 tensorflow/core/ops/ops.pbtxt 動(dòng)態(tài)生成。
處理常量
如果用戶可以為輸入?yún)?shù)提供常量,則調(diào)用代碼將更加簡潔。生成的代碼應(yīng)該將這些常量轉(zhuǎn)換為以下操作:添加到圖中并用作正在實(shí)例化的操作的輸入。
可選參數(shù)
如果語言允許函數(shù)具有可選參數(shù)(如 Python 中具有默認(rèn)值的關(guān)鍵字參數(shù)),則將它們用于可選屬性、操作名稱、設(shè)備、控制輸入等。在某些語言中,可以使用動(dòng)態(tài)作用域設(shè)置這些可選參數(shù)(如 Python 中的 “with” 塊)。如果沒有這些功能,庫可能會(huì)依靠 “編譯器模式”,就像在 TensorFlow API 的 C++ 版本中所做的那樣。
名稱作用域
建議您使用某種作用域?qū)哟谓Y(jié)構(gòu)支持命名圖操作,特別是考慮到 TensorBoard 依靠它以合理的方式顯示大圖這一事實(shí)?,F(xiàn)有的 Python 和 C++ API 采用不同的方法:在 Python 中,名稱的 “目錄” 部分(最后一個(gè) “/” 前面的所有內(nèi)容)來自 with塊。實(shí)際上,有一個(gè)線程局部堆棧,其中的作用域定義了名稱層次結(jié)構(gòu)。名稱的最后一個(gè)組成部分由用戶顯式提供(使用可選的 name 關(guān)鍵字參數(shù)),或者默認(rèn)為要添加的操作的類型名稱。在 C++ 中,名稱的 “目錄” 部分存儲(chǔ)在顯式 Scope 對(duì)象中。NewSubScope() 方法附加到名稱的該部分并返回一個(gè)新的 Scope。名稱的最后一個(gè)組成部分是使用 WithOpName() 方法設(shè)置的,并且像 Python 一樣默認(rèn)為要添加的操作的類型名稱。系統(tǒng)會(huì)顯式傳遞 Scope 對(duì)象以指定上下文的名稱。
封裝容器
可以保留生成的函數(shù)專用于某些操作,這樣可以改用執(zhí)行一些額外工作的封裝容器函數(shù)。這也為支持所生成代碼作用域之外的功能提供了一個(gè)應(yīng)急路徑。
封裝容器的一個(gè)用途是支持 SparseTensor 輸入和輸出。SparseTensor 是一個(gè)包含 3 個(gè)密集張量(索引、值和形狀)的元組。值是向量大小 [n],形狀是向量大小 [秩],索引是矩陣大小 [n, 秩]。有一些稀疏操作使用此三元組表示單個(gè)稀疏張量。
使用封裝容器的另一個(gè)原因是用于保留狀態(tài)的操作。有一些此類操作(例如變量)有幾個(gè)用于處理該狀態(tài)的伴隨操作。Python API 為這些操作提供了類,其中構(gòu)造函數(shù)會(huì)創(chuàng)建操作,并且此類上的方法會(huì)向處理該狀態(tài)的圖添加操作。
其他注意事項(xiàng)
最好添加一個(gè)關(guān)鍵字列表,用于重命名與語言關(guān)鍵字沖突的操作函數(shù)和參數(shù)(或其他會(huì)導(dǎo)致問題的符號(hào),如生成的代碼中引用的庫函數(shù)或變量的名稱)。
用于向圖添加 Const 操作的函數(shù)通常是封裝容器,因?yàn)樯傻暮瘮?shù)通常具有冗余的 DataType 輸入。
梯度、函數(shù)和控制流
目前,對(duì)梯度、函數(shù)和控制流操作(“if” 和 “while”)的支持并未在除 Python 以外的其他語言中提供。當(dāng) C API 提供必要的支持時(shí),將會(huì)更新這方面的功能。
-
編程語言
+關(guān)注
關(guān)注
10文章
1956瀏覽量
36635 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4380瀏覽量
64848
原文標(biāo)題:采用其他編程語言的 TensorFlow
文章出處:【微信號(hào):tensorflowers,微信公眾號(hào):Tensorflowers】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
C語言中的基本數(shù)據(jù)類型

嵌入式C語言中的goto語句詳解

編程語言中一個(gè)奇怪的代碼結(jié)構(gòu)

評(píng)論