tensorflow计算图_通过从头开始模仿其API来了解TensorFlow
TensorFlow是一個(gè)非常強(qiáng)大的開源機(jī)器學(xué)習(xí)庫,用于實(shí)現(xiàn)和部署機(jī)器學(xué)習(xí)模型。這使其非常適合研究和生產(chǎn)。多年來,它已成為最受歡迎的深度學(xué)習(xí)庫之一。
這篇文章的目標(biāo)是建立一種直覺和理解深度學(xué)習(xí)庫的工作原理,特別是TensorFlow。為了實(shí)現(xiàn)這一目標(biāo),我們將模仿其API并從頭開始實(shí)施其核心構(gòu)建塊。最終,你將能夠自信地使用TensorFlow,因?yàn)槟銓?duì)其內(nèi)部工作原理有深刻的概念性理解。
理論
TensorFlow是一個(gè)由兩個(gè)核心構(gòu)建塊組成的框架 - 用于定義計(jì)算圖的庫和用于在各種不同硬件上執(zhí)行此類圖的運(yùn)行時(shí)。
計(jì)算圖
簡(jiǎn)而言之,計(jì)算圖是一種將計(jì)算描述為有向圖的抽象方法。有向圖是由節(jié)點(diǎn)(頂點(diǎn))和邊組成的數(shù)據(jù)結(jié)構(gòu)。它是一組頂點(diǎn)通過有向邊成對(duì)連接。
這是一個(gè)非常簡(jiǎn)單的例子:
有向無環(huán)圖的簡(jiǎn)單示例
圖有許多形狀和大小,用于解決許多現(xiàn)實(shí)問題,例如代表網(wǎng)絡(luò),包括電話網(wǎng)絡(luò),電路網(wǎng)絡(luò),道路網(wǎng)絡(luò)甚至社交網(wǎng)絡(luò)。它們也常用于計(jì)算機(jī)科學(xué)中以描述依賴性,用于調(diào)度或在編譯器中用于表示直線代碼(沒有循環(huán)和條件分支的語句序列)。使用后者的圖表允許編譯器有效地消除公共子表達(dá)式。
TensorFlow在內(nèi)部使用有向圖來表示計(jì)算,他們稱之為data flow graphs(或計(jì)算圖)。
雖然有向圖中的節(jié)點(diǎn)可以是任何節(jié)點(diǎn),但計(jì)算圖中的節(jié)點(diǎn)主要表示操作(operations),變量(variables)或占位符(placeholders)。
操作根據(jù)特定規(guī)則創(chuàng)建或操作數(shù)據(jù)。在TensorFlow中,這些規(guī)則稱為Ops,是操作的縮寫。另一方面,變量表示可以通過對(duì)這些變量運(yùn)行Ops來操縱的共享持久狀態(tài)。
這些邊對(duì)應(yīng)于流經(jīng)不同操作的數(shù)據(jù)或多維數(shù)組(即所謂的張量)。換句話說,邊將信息從一個(gè)節(jié)點(diǎn)傳遞到另一個(gè)節(jié)點(diǎn)。一個(gè)操作(一個(gè)節(jié)點(diǎn))的輸出成為另一個(gè)操作的輸入,連接兩個(gè)節(jié)點(diǎn)的邊攜帶該值。
下面是一個(gè)非常簡(jiǎn)單的程序的例子:
為了從這個(gè)程序中創(chuàng)建一個(gè)計(jì)算圖,我們?yōu)槌绦蛑械拿總€(gè)操作創(chuàng)建節(jié)點(diǎn),以及輸入變量a和b。事實(shí)上,a和b可以是常數(shù)如果它們不變的話。如果一個(gè)節(jié)點(diǎn)被用作另一個(gè)操作的輸入,我們畫一個(gè)從一個(gè)節(jié)點(diǎn)到另一個(gè)節(jié)點(diǎn)的有向箭頭。
該程序的計(jì)算圖可能如下所示:
表示我們簡(jiǎn)單程序及其數(shù)據(jù)流的計(jì)算圖
上面的計(jì)算圖表示了不同的計(jì)算步驟,我們需要執(zhí)行這些步驟才能得到最終的結(jié)果。首先,我們創(chuàng)建兩個(gè)常數(shù)a和b。然后我們把它們相乘,取它們的和,然后用這兩個(gè)運(yùn)算的結(jié)果一個(gè)除以另一個(gè)。最后,我們打印出結(jié)果。
這并不難,但問題是為什么我們需要一個(gè)計(jì)算圖呢?將計(jì)算組織成有向圖的優(yōu)點(diǎn)是什么呢?
首先,計(jì)算圖是描述計(jì)算機(jī)程序及其計(jì)算的一種更為抽象的方法。在最基本的層次上,大多數(shù)計(jì)算機(jī)程序主要由兩部分組成——基本操作和按順序逐行執(zhí)行這些操作的順序。這意味著我們首先要把a(bǔ)和b相乘,只有當(dāng)這個(gè)表達(dá)式被求值時(shí)我們才會(huì)取它們的和。因此,程序指定了執(zhí)行的順序,但是計(jì)算圖只指定了跨操作的依賴關(guān)系。換句話說,這些操作的輸出如何從一個(gè)操作流向另一個(gè)操作。
這允許并行性或依賴性驅(qū)動(dòng)調(diào)度。如果我們查看我們的計(jì)算圖,我們會(huì)看到我們可以并行執(zhí)行乘法和加法。那是因?yàn)檫@兩個(gè)操作并不相互依賴。因此,我們可以使用圖形的拓?fù)鋪眚?qū)動(dòng)操作的調(diào)度并以最有效的方式執(zhí)行它們,例如在一臺(tái)機(jī)器上使用多個(gè)GPU,甚至在多臺(tái)機(jī)器上分布執(zhí)行。TensorFlow正是這樣做的,它可以將不依賴于彼此的操作分配給不同的內(nèi)核,只需要構(gòu)造一個(gè)有向圖,就可以從實(shí)際編寫程序的人那里獲得最少的輸入。
另一個(gè)關(guān)鍵優(yōu)勢(shì)是可移植性。圖是代碼的獨(dú)立于語言的表示。因此,我們可以用Python構(gòu)建圖形,保存模型,并使用另一種語言(比如c++)恢復(fù)模型。
TensorFlow基礎(chǔ)知識(shí)
TensorFlow中的計(jì)算圖包含幾個(gè)部分:
- 變量:將TensorFlow變量視為計(jì)算機(jī)程序中的常規(guī)變量。變量可以在任何時(shí)間點(diǎn)修改,但不同之處在于它們必須在會(huì)話中運(yùn)行圖形之前進(jìn)行初始化。它們代表圖中的可變參數(shù)。變量的一個(gè)很好的例子是神經(jīng)網(wǎng)絡(luò)中的權(quán)重或偏差。
- 占位符:占位符允許我們從外部將數(shù)據(jù)提供到圖中,而不像變量那樣它們不需要初始化。占位符只是定義形狀和數(shù)據(jù)類型。我們可以將占位符視為圖中的空節(jié)點(diǎn),稍后會(huì)提供該值。它們通常用于輸入和標(biāo)簽。
- 常量:無法更改的參數(shù)。
- 操作:操作表示圖形中執(zhí)行Tensors計(jì)算的節(jié)點(diǎn)。
- 圖:圖就像一個(gè)中心樞紐,它將所有變量,占位符,常量連接到操作。
- 會(huì)話:會(huì)話創(chuàng)建一個(gè)運(yùn)行時(shí),在該運(yùn)行時(shí)執(zhí)行操作并評(píng)估Tensors。它還分配內(nèi)存并保存中間結(jié)果和變量的值。
記得從一開始我們說TensorFlow由兩部分組成,一個(gè)用于定義計(jì)算圖的庫和一個(gè)用于執(zhí)行這些圖形的運(yùn)行時(shí)嗎?這就是圖和會(huì)話。Graph類用于構(gòu)造計(jì)算圖,Session用于執(zhí)行和計(jì)算所有或部分節(jié)點(diǎn)。延遲執(zhí)行的主要優(yōu)點(diǎn)是在計(jì)算圖的定義期間,我們可以構(gòu)造非常復(fù)雜的表達(dá)式,而無需直接評(píng)估它們并在所需的內(nèi)存中分配空間。
例如,如果我們使用NumPy來定義一個(gè)大的矩陣,我們會(huì)立即得到一個(gè)內(nèi)存不足的錯(cuò)誤。在TensorFlow中,我們將定義一個(gè)Tensor,它是多維數(shù)組的描述。它可能具有形狀和數(shù)據(jù)類型,但它沒有實(shí)際值。
在上面的Python代碼片段中,我們使用tf.zeros和np.zeros創(chuàng)建一個(gè)矩陣,所有元素都設(shè)置為零。NumPyNumPy將立即實(shí)例化1萬億x1萬億的矩陣所需要的內(nèi)存量(值為0),但TensorFlow只會(huì)聲明形狀和數(shù)據(jù)類型,但在圖的這一部分執(zhí)行之前不會(huì)分配內(nèi)存。
聲明和執(zhí)行之間的核心區(qū)別非常重要,因?yàn)檫@是允許TensorFlow在連接到不同機(jī)器的不同設(shè)備(CPU,GPU,TPU)上分配計(jì)算負(fù)載的原因。
有了這些核心構(gòu)建塊,讓我們將簡(jiǎn)單程序轉(zhuǎn)換為TensorFlow程序。一般來說,這可以分為兩個(gè)階段:
以下是我們的簡(jiǎn)單程序在TensorFlow中的樣子:
我們從導(dǎo)入tensorflow開始。接下來,我們?cè)趙ith語句中創(chuàng)建一個(gè)對(duì)象Session。這樣做的好處是,在塊執(zhí)行后會(huì)話自動(dòng)關(guān)閉,我們不必自己調(diào)用sess.close()。
現(xiàn)在,在with-block內(nèi)部,我們可以開始構(gòu)建新的TensorFlow操作(節(jié)點(diǎn)),從而定義邊(Tensors)。例如:
a = tf.constant(15, name="a")這就創(chuàng)建了一個(gè)名為a的Constant張量,它會(huì)生成值15。該名稱是可選的,但是當(dāng)您想要查看生成的圖時(shí),這個(gè)名稱很有用,我們稍后會(huì)看到。
但現(xiàn)在的問題是,我們的圖在哪里呢?我的意思是,我們還沒有創(chuàng)建圖,但我們已經(jīng)添加了這些操作。這是因?yàn)門ensorFlow為當(dāng)前線程提供了一個(gè)默認(rèn)圖,它是同一上下文中所有API函數(shù)的隱式參數(shù)。一般來說,僅僅依靠默認(rèn)圖就足夠了。但是,對(duì)于高級(jí)用例,我們還可以創(chuàng)建多個(gè)圖。
好了,現(xiàn)在我們可以創(chuàng)建另一個(gè)Constant b,也定義了我們基本的算術(shù)運(yùn)算,如multiply,add和divide。所有這些操作都會(huì)自動(dòng)添加到默認(rèn)圖中。
現(xiàn)在是時(shí)候計(jì)算結(jié)果了。到目前為止,還沒有求值,也沒有給這些張量分配任何實(shí)際的數(shù)值。我們要做的是運(yùn)行會(huì)話,顯式地告訴TensorFlow執(zhí)行圖。
我們已經(jīng)創(chuàng)建了一個(gè)會(huì)話對(duì)象,我們所要做的就是調(diào)用ses .run(res)并傳遞一個(gè)想求值的操作(這里是res)。這將只運(yùn)行計(jì)算res值所需的計(jì)算圖。這意味著為了計(jì)算res,我們必須計(jì)算prod和sum以及a和b。最后,我們可以打印結(jié)果,這就是run()返回的張量。
讓我們導(dǎo)出圖并使用TensorBoard將其可視化:
生成的圖由TensorBoard可視化
順便說一句,TensorBoard不僅非常適合可視化學(xué)習(xí),而且還可以查看和調(diào)試您的計(jì)算圖,所以一定要查看它。
從頭開始實(shí)現(xiàn)TensorFlow的API
我們的目標(biāo)是模仿TensorFlow的基本操作,以便用我們自己的API鏡像我們的簡(jiǎn)單程序,就像我們剛才用TensorFlow做的那樣。
我們已經(jīng)了解了一些核心構(gòu)建模塊,如Variable,Operation或Graph。這些是我們想要從頭開始實(shí)現(xiàn)的構(gòu)建塊,所以讓我們開始吧。
圖
第一個(gè)缺失的部分是圖。A Graph包含一組Operation對(duì)象,表示計(jì)算單位。此外,圖包含一組Placeholder和Variable對(duì)象,它們表示在操作之間流動(dòng)的數(shù)據(jù)單位。
對(duì)于我們的實(shí)現(xiàn),我們基本上需要三個(gè)列表來存儲(chǔ)所有這些對(duì)象 此外,我們的圖需要一個(gè)調(diào)用的方法as_default,我們可以調(diào)用它來創(chuàng)建一個(gè)用于存儲(chǔ)當(dāng)前圖實(shí)例的全局 變量。這樣,在創(chuàng)建操作,占位符或變量時(shí),我們不必傳遞對(duì)圖的引用。
class Graph(): def __init__(self): self.operations = [] self.placeholders = [] self.variables = [] self.constants = [] def as_default(self): global _default_graph _default_graph = self操作
下一個(gè)缺失的部分是操作。要回想一下,操作是計(jì)算圖中的節(jié)點(diǎn),并在Tensors上執(zhí)行計(jì)算。大多數(shù)操作將零或多個(gè)張量作為輸入,并產(chǎn)生零個(gè)或多個(gè)Tensors對(duì)象作為輸出。
簡(jiǎn)而言之,操作的特征如下:
因此,每個(gè)節(jié)點(diǎn)只知道它周圍的環(huán)境,這意味著它知道輸入的本地輸入和直接傳遞給下一個(gè)使用它的節(jié)點(diǎn)的輸出。
輸入節(jié)點(diǎn)是進(jìn)入此操作的Tensors(≥0)列表。
這兩個(gè)forward和backward只有占位符方法,它們必須通過每一個(gè)具體的操作來實(shí)現(xiàn)。在我們的實(shí)現(xiàn)中,在forward pass(或forward-propagation)期間調(diào)用forward,其計(jì)算操作的輸出,而backward是在backward pass(或backpropagation)期間調(diào)用的,在此過程中,我們計(jì)算操作相對(duì)于每個(gè)輸入變量的梯度。這并不是TensorFlow的工作方式但是我發(fā)現(xiàn)如果一個(gè)操作是完全自治的,這就更容易推理了,這意味著它知道如何計(jì)算輸出和每個(gè)輸入變量的局部梯度。
請(qǐng)注意,在這篇文章中我們將只實(shí)現(xiàn)forward pass,這意味著我們可以將 backward函數(shù)留空并且現(xiàn)在不用擔(dān)心它。
每個(gè)操作都在默認(rèn)圖中注冊(cè)也很重要。當(dāng)您想要使用多個(gè)圖時(shí),這會(huì)派上用場(chǎng)。
讓我們一步一步,首先實(shí)現(xiàn)基類:
class Operation(): def __init__(self, input_nodes=None): self.input_nodes = input_nodes self.output = None # Append operation to the list of operations of the default graph _default_graph.operations.append(self) def forward(self): pass def backward(self): pass我們可以使用這個(gè)基類來實(shí)現(xiàn)各種操作。但是我們馬上要實(shí)現(xiàn)的運(yùn)算都是只有兩個(gè)參數(shù)a和b的運(yùn)算。為了使我們的工作更簡(jiǎn)單,并避免不必要的代碼重復(fù),讓我們創(chuàng)建一個(gè)BinaryOperation,它只負(fù)責(zé)將a和b初始化為輸入節(jié)點(diǎn)。
class BinaryOperation(Operation): def __init__(self, a, b): super().__init__([a, b])現(xiàn)在,我們可以使用BinaryOperation并實(shí)現(xiàn)一些更具體的操作,例如add,multiply,divide或matmul(用于兩個(gè)矩陣的乘法)。對(duì)于所有操作,我們假設(shè)輸入是簡(jiǎn)單的標(biāo)量或NumPy數(shù)組。這使得我們的操作實(shí)現(xiàn)變得簡(jiǎn)單,因?yàn)镹umPy已經(jīng)為我們實(shí)現(xiàn)了它們,尤其是更復(fù)雜的操作,例如兩個(gè)矩陣之間的點(diǎn)積。后者使我們能夠很容易地在一批樣本上對(duì)圖進(jìn)行評(píng)估,并為這批樣本中的每個(gè)觀察值計(jì)算輸出。
class add(BinaryOperation): """ Computes a + b, element-wise """ def forward(self, a, b): return a + b def backward(self, upstream_grad): raise NotImplementedErrorclass multiply(BinaryOperation): """ Computes a * b, element-wise """ def forward(self, a, b): return a * b def backward(self, upstream_grad): raise NotImplementedErrorclass divide(BinaryOperation): """ Returns the true division of the inputs, element-wise """ def forward(self, a, b): return np.true_divide(a, b) def backward(self, upstream_grad): raise NotImplementedErrorclass matmul(BinaryOperation): """ Multiplies matrix a by matrix b, producing a * b """ def forward(self, a, b): return a.dot(b) def backward(self, upstream_grad): raise NotImplementedError占位符
當(dāng)我們查看我們的簡(jiǎn)單程序及其計(jì)算圖時(shí),我們可以注意到并非所有的節(jié)點(diǎn)都是操作,尤其是a和b。相反,它們是在會(huì)話中計(jì)算圖的輸出時(shí)必須提供的圖的輸入。
在TensorFlow中,有不同的方法為圖提供輸入值,例如Placeholder,Variable或Constant。
class Placeholder(): def __init__(self): self.value = None _default_graph.placeholders.append(self)我們可以看到,實(shí)現(xiàn)Placeholder非常簡(jiǎn)單。它沒有使用值(即名稱)初始化,只將自己附加到默認(rèn)圖中。占位符的值是使用Session.run()的feed_dict可選參數(shù)提供的,但在實(shí)現(xiàn)會(huì)話時(shí)將對(duì)此進(jìn)行更多介紹。
常量
我們要實(shí)現(xiàn)的下一個(gè)構(gòu)建塊是常量。常量與變量完全相反,因?yàn)槌跏蓟笏鼈儫o法更改。另一方面,變量表示我們的計(jì)算圖中的可變參數(shù)。例如,神經(jīng)網(wǎng)絡(luò)中的權(quán)重和偏差。
使用占位符作為輸入和標(biāo)簽而不是變量是絕對(duì)有意義的,因?yàn)樗鼈兛偸窃诿看蔚鷷r(shí)更改。此外,區(qū)別非常重要,因?yàn)樽兞吭赽ackward pass 期間被優(yōu)化,而常量和占位符則不是。所以我們不能簡(jiǎn)單地用一個(gè)變量來輸入常數(shù)。占位符可以工作,但感覺有點(diǎn)被濫用了。為了提供這種特性,我們引入常量。
class Constant(): def __init__(self, value=None): self.__value = value _default_graph.constants.append(self) @property def value(self): return self.__value @value.setter def value(self, value): raise ValueError("Cannot reassign value.")Python中的下劃線有特定的含義。有些實(shí)際上只是約定,有些則由Python解釋器強(qiáng)制執(zhí)行。用單下劃線_它的大部分是按慣例。因此,如果我們有一個(gè)名為_foo的變量,那么這通常被看作是一個(gè)暗示,即一個(gè)名稱將被開發(fā)人員視為私有的。但這并不是解釋器強(qiáng)制執(zhí)行的,也就是說,Python在私有變量和公共變量之間沒有這些明顯的區(qū)別。
但是還有雙下劃線__,也叫“dunder”。解釋器對(duì)dunder的處理是不同的,它不僅僅是一種約定。它實(shí)際上應(yīng)用了命名混淆。看看我們的實(shí)現(xiàn),我們可以看到我們?cè)陬悩?gòu)造函數(shù)中定義了一個(gè)屬性__value。由于屬性名中有雙下劃線,Python將在內(nèi)部將屬性重命名為類似于_Constant__value的名稱,因此它使用類名作為屬性的前綴。這個(gè)特性實(shí)際上是為了在處理繼承時(shí)防止命名沖突。但是,我們可以將此行為與getter結(jié)合使用來創(chuàng)建一些私有屬性。
我們所做的是創(chuàng)建一個(gè)dunder屬性__value,通過另一個(gè)“publicly”可用屬性值公開該值,并在有人試圖設(shè)置該值時(shí)引發(fā)ValueError。這樣,API的用戶就不能簡(jiǎn)單地重新分配值,除非他們?cè)敢馔度敫嗟墓ぷ?#xff0c;并且發(fā)現(xiàn)我們?cè)趦?nèi)部使用dunder。它不是一個(gè)真正的常量,更像是JavaScript中的const,但對(duì)于我們的目的,它是完全可以的。這至少可以防止值被輕易地重新分配。
變量
計(jì)算圖的輸入與正在調(diào)整和優(yōu)化的“內(nèi)部”參數(shù)之間存在質(zhì)的差異。舉個(gè)例子,拿一個(gè)計(jì)算的簡(jiǎn)單感知器y = w * x + b,x表示輸入數(shù)據(jù),w和b是可訓(xùn)練的參數(shù),即計(jì)算圖中的變量。在TensorFlow中,變量在調(diào)用Session.run()時(shí)保持圖中的狀態(tài),而不是每次調(diào)用run()時(shí)都必須提供占位符。
實(shí)現(xiàn)變量很容易。它們需要初始值并將其自身附加到默認(rèn)圖。
class Variable(): def __init__(self, initial_value=None): self.value = initial_value _default_graph.variables.append(self)會(huì)話
在這一點(diǎn)上,我說我們對(duì)構(gòu)建計(jì)算圖非常有信心,我們已經(jīng)實(shí)現(xiàn)了最重要的構(gòu)建塊來鏡像TensorFlow的API并使用我們自己的API重寫我們的簡(jiǎn)單程序。我們必須建立一個(gè)最后一個(gè)缺失的部分 - 那就是Session。
因此,我們必須開始考慮如何計(jì)算操作的輸出。
從TensorFlow我們知道一個(gè)會(huì)話有一個(gè)run方法,當(dāng)然還有其他幾個(gè)方法,但是我們只對(duì)這個(gè)特別的方法感興趣。
最后,我們希望能夠使用我們的會(huì)話如下:
session = Session()output = session.run(some_operation, { X: train_X # [1,2,...,n_features]})因此run需要兩個(gè)參數(shù),一個(gè)operation要執(zhí)行的參數(shù)和一個(gè)feed_dict將圖元素映射到值的字典。此字典用于為圖中的占位符提供值。提供的操作是我們要為其計(jì)算輸出的圖元素。
為了計(jì)算給定操作的輸出,我們必須在拓?fù)渖蠈?duì)圖中的所有節(jié)點(diǎn)進(jìn)行排序,以確保我們以正確的順序執(zhí)行它們。這意味著我們不能在計(jì)算常數(shù)a和b之前計(jì)算加法。
拓?fù)渑判蚩梢远x為有向無環(huán)圖(DAG)中節(jié)點(diǎn)的排序,其中對(duì)于從節(jié)點(diǎn)a到節(jié)點(diǎn)B的每條有向邊,節(jié)點(diǎn)B在排序中出現(xiàn)在a之前。
該算法非常簡(jiǎn)單:
以下是針對(duì)我們特定計(jì)算圖的算法的動(dòng)畫插圖:
我們的計(jì)算圖的拓?fù)渑判?/p>
當(dāng)我們從拓?fù)渖蠈?duì)以Div開頭的計(jì)算圖進(jìn)行排序時(shí),我們得到的順序是先計(jì)算常量,然后是運(yùn)算Mul和Add,最后是Div。順序也可以是5、15、Add、Mul、Div,這實(shí)際上取決于我們處理input_nodes的順序。
讓我們創(chuàng)建一個(gè)微小的實(shí)用工具方法,在拓?fù)渖蠌慕o定節(jié)點(diǎn)開始對(duì)計(jì)算圖進(jìn)行排序。
def topology_sort(operation): ordering = [] visited_nodes = set() def recursive_helper(node): if isinstance(node, Operation): for input_node in node.input_nodes: if input_node not in visited_nodes: recursive_helper(input_node) visited_nodes.add(node) ordering.append(node) # start recursive depth-first search recursive_helper(operation) return ordering既然我們可以對(duì)計(jì)算圖進(jìn)行排序并確保節(jié)點(diǎn)的順序正確,那么我們就可以開始研究實(shí)際的Session類。這意味著創(chuàng)建類并實(shí)現(xiàn)該run方法。
我們要做的是以下幾點(diǎn):
按照這些步驟,我們最終得到一個(gè)可能是這樣的實(shí)現(xiàn):
class Session(): def run(self, operation, feed_dict={}): nodes_sorted = topology_sort(operation) for node in nodes_sorted: if type(node) == Placeholder: node.output = feed_dict[node] elif type(node) == Variable or type(node) == Constant: node.output = node.value else: inputs = [node.output for node in node.input_nodes] node.output = node.forward(*inputs) return operation.output區(qū)分不同類型的節(jié)點(diǎn)非常重要,因?yàn)槊總€(gè)節(jié)點(diǎn)的輸出可能以不同的方式計(jì)算。請(qǐng)記住,在執(zhí)行會(huì)話時(shí),我們只有變量和常量的實(shí)際值,但占位符仍然在等待它們的值。因此,在計(jì)算占位符的輸出時(shí),我們必須在feed_dict中查找作為參數(shù)提供的值。對(duì)于變量和常量,我們可以簡(jiǎn)單地使用它們的值作為輸出,對(duì)于操作,我們必須收集每個(gè)input_node的輸出并調(diào)用該操作的forward。
至少我們已經(jīng)實(shí)現(xiàn)了鏡像我們簡(jiǎn)單的TensorFlow程序所需的所有部件。讓我們看看它是否真的有用。
為此,讓我們將API的所有代碼放在一個(gè)名為tf_api.py的單獨(dú)模塊中。現(xiàn)在我們可以導(dǎo)入這個(gè)模塊,并開始使用我們實(shí)現(xiàn)的模塊。
import tf_api as tf# create default graphtf.Graph().as_default()# construct computational graph by creating some nodesa = tf.Constant(15)b = tf.Constant(5)prod = tf.multiply(a, b)sum = tf.add(a, b)res = tf.divide(prod, sum)# create a session objectsession = tf.Session()# run computational graph to compute the output for 'res'out = session.run(res)print(out)當(dāng)我們運(yùn)行此代碼時(shí),假設(shè)到目前為止我們已經(jīng)完成了所有操作,它將正確地打印3.75到控制臺(tái)。這正是我們希望看到的輸出。
這看起來和我們用TensorFlow做的很相似,對(duì)吧?唯一的區(qū)別是大寫,但這是故意的。而在TensorFlow中,實(shí)際上所有東西都是一個(gè)操作——即使是占位符和變量——我們沒有將它們實(shí)現(xiàn)為操作。為了區(qū)分它們,我決定使用小寫操作并大寫其余部分。
結(jié)論
我希望這篇文章對(duì)你有所幫助,TensorFlow現(xiàn)在有點(diǎn)不那么令人生畏了。
總結(jié)
以上是生活随笔為你收集整理的tensorflow计算图_通过从头开始模仿其API来了解TensorFlow的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 贪心算法之分发饼干
- 下一篇: python基础——导入模块