官术网_书友最值得收藏!

第3章
Chapter Three
神經元網絡初步

3.1 Tensorflow基本概念

3.1.1 計算圖、張量、常數和變量

前文已經說過,TF的張量等價于表達式Exp,計算圖等價于依賴關系圖,所以創建張量的過程等價于創建一個表達式。表達式Const(3)+Variable(′x′)與3+x的區別是前者創建了一個新的表達式對象,并建立了一個如圖3-1所示的依賴關系圖;而后者則直接從內存中取得x的值,然后與3相加,不會建立什么依賴關系圖。

圖3-1 依賴關系圖示例

換句話說,在表達式之間執行加法運算時,并不進行數值計算,而是創建一個加法表達式;而在常數和變量之間執行加法運算則會導致計算的發生。前者等價于創建一個Exp類型的對象,后者等價于對Exp對象執行eval()操作。這兩者當然是不一樣的。

在TF中也有常數和變量,就像Exp有子類Const和Variable一樣。有時,為了避免與Python(或者任何一個高級程序設計語言)中的常數、變量混淆,我們又分別稱它們為常數張量和變量張量。下文中,如果不特別說明,常數、變量、各種運算等指的都是張量。

例如引入TF(即import tensorflow as tf)之后,tf.constant(123)和tf.constant([45,67,89])表示TF中的常數。它們的值分別是標量123和向量[45,67,89]。這里,常數的“值”這個概念等價于Exp的子類Const的value屬性(即成員變量,以下同)。

Const的value屬性是有類型的,例如int、float、bool、str等。與之對應,TF中的常數的值也有類型,分別是整型、浮點型、tf.bool和tf.string等。與Python的基本數據類型int不同,TF的整型又分為tf.int8、tf.int16、tf.int32、tf.int64四種,分別占1、2、4、8個字節。TF的不同整數類型是不通用的,要想對兩個不同長度的整型進行運算,就要用tf.cast()函數改變其中一個張量的長度。例如,tf.cast(x,tf.int16)就能把張量x轉成2字節整型。

在高級語言中,例如Python、Java、C和C++等,不同長度的整型數據是通用的。這是因為編譯器或者解釋器能夠自動地幫我們把長度短的整型數據加長。例如在2字節整型的左邊(高位)再增加2個字節的0,就可以把一個非負整數轉為4字節整數,還能保持值不變。那為什么TF不這樣做呢?那是因為TF主要針對的是高維矩陣數據,如果允許整型相互之間直接通用,在一個矩陣中就勢必要允許存在長度不同的整數。運算前要想對齊這些整數會很麻煩,很耽誤時間。換句話說,高級語言編譯器或者解釋器主要面對的是一個標量整數,它的類型和轉換方法是確定的;而TF面對的是一堆整數,各種長度都有可能。為了避免在這一不太重要的地方浪費運算時間,TF就不允許不同長度的整數聚集在同一個矩陣中,從而也就不允許直接通用不同長度的整數及其矩陣。

讀者可以試著執行下面的代碼,會發現程序報錯。

TF中的浮點類型也分為tf.float16、tf.float32和tf.float64三種類型,長度分別是2、4和8個字節,相互之間也不通用。

TF中的另一個基本概念是變量(等價于Exp中的Variable)。與高級語言中的變量一樣,TF中的變量也可以擁有值(標量、向量或者高維矩陣),或者被賦予新的值。創建一個變量最簡單的辦法是調用tf.get_variable(name,shape,dtype)函數。例如,tf.get_variable(′xyz′,[3,2],tf.float32)表示創建一個名為xyz的變量,值是3 ×2的矩陣,矩陣中的元素是32位浮點數。這里,[3,2]表示矩陣的形狀(Shape),即矩陣有幾個維度(Dimension),每個維度的大小是多少[1]。TF用Python的列表(list)表示張量的形狀,該列表的長度被稱為(Rank),又稱為維度數。例如形狀[3,8,5]的階就是3。標量的形狀是空列表[],階為0。

TF的變量與Python變量的區別是:

1)TF的變量是個張量,即計算圖中的一個結點。它的性質與其他結點(例如加法或者relu函數)相同。而Python變量是內存中的一塊區域,其中可能存放著一個基本類型值(例如整數、浮點數或者一個布爾值),也可能存放著一個對象的地址。

2)TF的變量是不可以直接賦值的,就像不會給一個表達式加法或者log函數賦值一樣。要想給一個TF的變量a賦值,請首先執行tf.assign(a,x),然后執行一個會話(Session,后面介紹)對象的run()方法,才能完成對a的賦值。而Python變量是可以直接賦值的,例如a=3就是把3賦給了a這個Python變量。甚至可以把一個張量賦值給一個Python變量,例如a=tf.constant(12345)或者a=tf.get_variable(′xyz′,[5,8,2],tf.float32)[2]。在Python看來,張量不過是一個對象而已,當然可以賦值給一個Python變量,以便在隨后的代碼中可以通過這個Python變量來引用該張量。

3)定義一個TF變量至少要指明其名字、形狀和值類型;而定義一個Python變量時不需要指明任何屬性,連名字都不用。Python變量的實質是對對象或者Python基本類型值的引用;而TF變量是一個tf.Variable類型的對象,有自己的屬性和方法,能夠參與構建計算圖(就像Exp的子類Variable可以參與構建復合函數一樣)。它就是它自己,不是對其他任何對象或者別的什么東西的引用。

4)TF變量的值隨會話的不同而不同。事實上,脫離會話也就無所謂TF變量的值是什么。就像Exp的子類Variable的內部成員函數eval(??env)可以計算該變量的值,前提是必須提供env參數作為環境。離開環境也就無所謂變量的值是什么。

5)當把一個含有10000個元素的列表賦值給一個Python變量a時,這意味著內存中存在著一個占據10000個單位內存的大型對象;而創建一個含有10000個元素的TF變量時,例如tf.get_ variable(′abc′,[10000],tf.int32),內存里除了多出一個名為abc的張量外(這個張量占據的內存很少),不會存在一個占據大量內存的大型對象。

下面是一段TF的會話與常數、變量之間關系和互動的代碼示例:

3.1.2 會話、運行

我們知道,在Exp及其子類對象中有一個eval(??env)方法。參數字典env給出了一個對變量進行求值的環境。TF中的會話(Session)則起到了對張量進行求值的環境作用。Session的run(tensors,feed_dict=None)方法就相當于Exp的eval()方法。其中第一個參數tensors是要求解的張量或者張量列表,第二個參數feed_dict是一個字典(dict)類型對象,它的鍵必須是張量,值是要給這個張量賦予的初值(見3.1.3節)。

如代碼3-2所示,應該在with語句之下執行run()函數,這是因為我們要保證會話(或者說環境)正常地打開或者關閉。當然,也可以直接執行Session._enter_()、Session._exit_()或Session.close()方法手工打開或者關閉會話,但不建議這樣做。因為with語句能保證即使其下方的語句出現異常,Session._exit_()也能被調用,從而保證會話能被正常地關閉[3]

TF的會話還具有保存和恢復模型、設置GPU運行參數、為占位符張量提供數據的作用。我們會在后面的章節中逐步學習。

3.1.3 占位符

除了常數和變量之外,TF還有一種奇特的張量:占位符。調用tf.placeholder()可以創建一個占位符。構建一個占位符同樣需要提供值類型、形狀和名字,就像定義一個常數一樣(參數的次序不一樣)。名字雖然不是必需的,但是本書建議提供一個有意義的名字,這樣在出錯的時候可以幫助定位代碼。常數能參與的運算,例如算術運算、關系運算、邏輯運算等,占位符一樣能夠參與。兩者的區別是常數的值永遠不會變,而占位符的值可以在會話運行時(Session.run())提供。示例代碼如下:

打印結果:[101 202 303]。由于占位符的性質是初值可以改變的常數,所以占位符常被用來接受用戶在運行一個張量時提供的輸入數據。但是,這并不是用戶輸入數據的唯一方法。

假設a=tf.constant([1,2,3],tf.int8),b=tf.constant([4,5,6],tf.int8),c=a+b。運行時,c的值當然變成了[5,7,9]。但是,我們也可以在run時直接設定a或者b張量的值。下面是示例:

打印結果是:[50,70,90]。所以,只要是張量,我們都可以在調用Session.run()函數時,通過feed_dict參數設定它的值,而不是僅僅只有placeholder張量才可以被設定值。例如圖3-1中的加法張量依賴于x和3。一般情況下,如果要計算加法的值就必須先求得x和3的值。可是,如果在Session.run()的feed_dict中設置這個加法張量的值,那x和3的值就不會被求解。這是因為張量的實質是計算圖(或者說依賴關系圖)中的結點。任意結點的值既可以通過所依賴的結點計算,也可以直接人為設定。這就為計算圖的使用提供了多種可能,為使用者帶來了很多便利。

3.1.4 矩陣算術運算

TF中矩陣的算術運算是指有運算符的數學運算,如加法(+)、減法(-)、乘法(?)、除法(/)、求反(-)、乘方(??)等。

形狀相同的兩個矩陣進行算術運算(以及對一個矩陣求反)的結果可以得到同樣形狀的一個矩陣,其中每一個元素就是對這兩個矩陣(或求反的那個矩陣)中相應位置處元素進行該算術運算的結果。下面是算術運算示例:

運行結果:

3.1.5 矩陣運算的廣播

我們知道,在數學上,標量3與矩陣A相乘的結果就是3與A的每一個元素相乘構成的矩陣。這在TF中也成立,且對幾乎所有兩元矩陣運算(包括算術運算以及后面提到的關系運算、函數等)都成立。TF要求兩個參與運算的矩陣(含標量、向量,以下同)的形狀ab必須是相容的。相容性的遞歸定義如下:

1)如果a= =b,即兩個矩陣的形狀完全相同,則ab是相容的,且運算結果的形狀等于a。例如形狀都是[3,5,2]的兩個矩陣可以進行任意二元算術運算,且結果的形狀也是[3,5,2]。

2)如果a==[]或者b==[],即兩個矩陣中有一個是標量,則ab是相容的,且結果矩陣的形狀等于ab中不為空的那個列表。如果ab都是空列表,則結果也是空列表。例如形狀為[3,5,2]的矩陣可以和任何標量(標量的形狀為[])進行任意二元算術運算,結果的形狀仍是[3,5,2]。

3)如果ab相容,則ba也相容,且結果的形狀等于ab進行二元算術運算的結果形狀,即相容性是對稱的。

4)如果ab相容,c是任意一個正整數列表,則a+cb+c也相容,且結果形狀等于ab的結果形狀+c。例如形狀[3,5]與形狀[5]相容,結果形狀是[3,5]。

5)如果ab相容,則a+[n]與b+[1]也相容,且結果形狀等于ab的結果形狀+[n]。例如形狀[3,5,2]與形狀[3,5,1]相容,結果形狀是[3,5,2]。

規則1)、2)和3)比較好理解,規則4)和5)的含義是指可以在相容的兩個形狀的尾部添加數量相同的維度,只要它們一一對應相等或者對應的兩個新增維度中有一個是1即可,示例見表3-1。

表3-1 相容和不相容形狀示例

假設矩陣AB的形狀分別是[3,5]和[5],則A+B的結果的形狀是[3,5]。TF的做法是把B重復3遍,然后分別與A的每一行相加,從而得到形狀為[3,5]的結果。如果AB的形狀分別是[3,5,2]和[3,1,2],則A+B相當于3個[5,2]矩陣和3個[1,2]矩陣分別相加。其中的[1,2]矩陣只有1行2列,這一行數據被重復了5遍,然后分別與對應[5,2]矩陣的每一行相加即得到結果。

這個現象稱為矩陣運算的廣播(Broadcasting)。在Python的常用包numpy中,矩陣運算也是可以廣播的。事實上,TF的矩陣運算不但類似于numpy,而且Session.run()的結果就是numpy.ndarray數據。不同的是,TF的運算元是張量,numpy的運算元是真實存在的矩陣數據。

3.1.6 TF矩陣運算

TF中可用于矩陣運算的基本函數主要有:

1)矩陣乘法tf.matmul()。

2)對數tf.log()。

3)三角函數,如tf.sin()、tf.cos()、tf.tan()等。

4)反三角函數,如tf.asin()、tf.acos()、tf.atan()等。

還有一些與神經元網絡搭建有關的數學函數,例如激活函數、全連接操作、卷積操作等。后面會有章節專門講述,這里沒有列出。

其他函數比較容易理解,且在神經元網絡中使用較少,我們這里只講矩陣乘法tf.matmul(a,b)。根據線性代數中對矩陣乘法的要求,如果ab都是二維矩陣張量,且形狀分別是[mn]和[np],則tf.matmul(a,b)的結果就是一個形狀為[mp]的張量。代碼示例如下:

運行結果:

[[82 88]

 [199 214]]

矩陣乘法不是可廣播的。參與矩陣乘法的兩個矩陣的階必須相等,且大于等于2。如果大于2,則除了最右邊兩個維度之外,其他維度必須一一對應相等。例如,形狀分別是[3,2,9]和[3,9,5]的兩個矩陣可以matmul,結果形狀為[3,2,5]。TF把前者和后者分別看成是3個[2,9]和3個[9,5]形狀的矩陣,讓它們分別對應相乘,最后再把結果拼接在一起即可。

形狀分別是[3,2,9]和[1,9,5]的兩個矩陣就不能進行矩陣相乘。

3.1.7 形狀和操作

矩陣的形狀是一個列表,例如[2,3,4]代表的是一個2×3×4的矩陣,階為3,共有24個元素。一個標量的形狀是[],即空列表;一個向量的形狀形如[n],其中n是這個向量的長度。注意,[2,3,4]作為形狀,它的階是3;作為一維矩陣,它的形狀是[3],階是1。

針對形狀的操作主要有以下幾個:

第一,整形操作,即tf.reshape(tensor,new_shape)。tensor是一個張量,這個函數會把它轉成new_shape形狀,然后作為結果返回。整形操作能夠執行的前提是,new_shape所蘊含的元素的個數必須與矩陣當前的元素總數相同。

假設tensor的形狀為[2,3,4],則它可以被整形為以下形狀中的任何一種:[24]、[1,24]、[2,12]、[12,2]、[6,4]、[4,6]、[1,8,3]、[3,1,8]、[1,2,2,2,3]、[2,1,3,1,4]……

整形操作常常被用來改變一個矩陣的形狀,從而使它可以和另一個形狀不同且不滿足廣播條件的矩陣進行運算。例如,形狀[2,3,4]整形為[3,8]之后就可以和形狀[2,1,8]進行可廣播的運算(如加、減、乘、除等)。而本來,它們是不可以這樣運算的。

第二,轉置操作,即tf.transpose(tensor,perm)。把張量tensor的各個維度重新排列,perm是維度的新排列次序。假設tensor的階等于n,則perm必須是0,1,2,…,n-1這n個數的一個排列,代表各維度的新次序。例如,假設perm=[1,0],則形狀為[3,9]的矩陣就會被轉置為[9,3]。

轉置操作的意義在于不僅改變了一個矩陣的形狀,還改變了元素參與運算的次序。假設一個矩陣A的形狀是[4,8,2],則從左到右,維度的優先級依次升高。運算時,A[0,0,0]總是第一個被提取,接著按照優先級改變下標提取下一個數,即A[0,0,1],接著是A[0,1,0],A[0,1,1],A[0,2,0],…,A[0,7,1],A[1,0,0],…,A[3,7,1]。兩個可運算的矩陣就是這樣各自提取數據,然后再一對對地進行運算的,而tf.transpose()會改變這種次序。代碼示例如下:

第三,升維操作,即讓矩陣的階變大。最簡單的辦法是把若干形狀相同的矩陣并列在一個列表里。例如假設A的形狀是[3,4],則[AA]的形狀就是[2,3,4]。這樣增加的維度在最左邊。如果想在任意一個位置增加維度,請調用tf.expand_dims(A,1),得到形狀[3,1,4][4]。其中第二個參數axis表示新維度所在位置(從0開始數)。新維度的長度總是1,這樣可以保證矩陣中元素的數量一致。

第四,降維操作,即讓矩陣的階變小。這主要是指reduce打頭的一系列函數,如tf.reduce_sum()、tf.reduce_prod()、tf.reduce_mean()、tf.reduce_max()、tf.reduce_min()、tf.reduce_all()、tf.reduce_any()。下面舉例說明:

假設矩陣A的形狀是[2,3,4],則tf.reduce_sum(A,1)的含義是消除維度1(從0開始數),這樣最后得到的形狀是[2,4]。tf.reduce_sum(A,[0,2])是消除維度0和2,得到形狀[3]。第二個參數axis可以省略,此時表示消除所有維度。所以tf.reduce_sum(A)的結果形狀是[],即結果是標量。當然也可以設置第三個參數keepdims=True,表示保留axis指定的維度,僅僅把它的長度變為1。例如tf.reduce_sum(A,[0,2],True)的結果形狀是[1,3,1]。

reduce_sum具體是怎樣進行降維操作的呢?例如B=tf.reduce_sum(A,1),把形狀從[2,3,4]變為[2,4],元素總數從24個減為8個,這意味著每三個數變成一個數。對于Bij]來說,它的值來源于Ai,0,j]、Ai,1,j]和Ai,2,j]三個數的求和。代碼示例如下:

其他reduce函數與reduce_sum()類似。不同之處僅僅在于對數據集合做什么操作:

1)tf.reduce_sum(),對數據集合求和。

2)tf.reduce_prod(),對數據集合求乘積。

3)tf.reduce_mean(),對數據集合求平均數。

4)tf.reduce_max(),對數據集合求最大值。

5)tf.reduce_min(),對數據集合求最小值。

6)tf.reduce_all(),對布爾值數據集合求邏輯與。

7)tf.reduce_ any(),對布爾值數據集合求邏輯或。

后面章節中我們會用更多的例子說明reduce系列函數的用法。

3.1.8 關系運算和邏輯運算

關系運算符有6個:>、> =、= =、!=、<、< =,對應的函數分別是tf.greater()、tf.greater_equal()、tf.equal()、tf.not_equal()、tf.less()、tf.less_equal(),都帶有兩個參數。例如矩陣AB的結果是一個布爾值矩陣,其中的每個元素是AB中對應元素進行大于比較運算后的結果。其他關系運算符類似。

邏輯運算有4個:tf.logical_not()、tf.logical_and()、tf.logical_or()、tf.logical_xor()。其含義這里不再贅述。要注意的是,邏輯運算是沒有運算符的。

關系運算和邏輯運算都是可廣播的。

主站蜘蛛池模板: 宁波市| 新干县| 抚顺市| 邵阳县| 祥云县| 余江县| 霍林郭勒市| 攀枝花市| 达孜县| 津市市| 鹿泉市| 申扎县| 肥城市| 大荔县| 康定县| 全椒县| 合阳县| 汉沽区| 随州市| 商都县| 临江市| 洛川县| 治多县| 湘西| 苍南县| 林州市| 南华县| 汉阴县| 西畴县| 红桥区| 黄骅市| 平定县| 齐齐哈尔市| 大邑县| 外汇| 逊克县| 丰镇市| 洛川县| 理塘县| 罗平县| 南华县|