TensorFlow 2 基礎(chǔ)概念語法
TensorFlow 2 簡介
TensorFlow 是由谷歌在 2015 年 11 月發(fā)布的深度學(xué)習(xí)開源工具,我們可以用它來快速構(gòu)建深度神經(jīng)網(wǎng)絡(luò),并訓(xùn)練深度學(xué)習(xí)模型。運用 TensorFlow 及其他開源框架的主要目的,就是為我們提供一個更利于搭建深度學(xué)習(xí)網(wǎng)絡(luò)的模塊工具箱,使開發(fā)時能夠簡化代碼,最終呈現(xiàn)出的模型更加簡潔易懂。
2019 年,TensorFlow 推出了 2.0 版本,也意味著 TensorFlow 從 1.x 正式過度到 2.x 時代。根據(jù) TensorFlow 官方 介紹內(nèi)容 顯示,2.0 版本將專注于簡潔性和易用性的改善,主要升級方向包括:
使用 Keras 和 Eager Execution 輕松構(gòu)建模型。
在任意平臺上實現(xiàn)穩(wěn)健的生產(chǎn)環(huán)境模型部署。
為研究提供強(qiáng)大的實驗工具。
通過清理廢棄的 API 和減少重復(fù)來簡化 API。
張量
張量的概念貫穿于物理學(xué)和數(shù)學(xué)中,如果你去看它的很多理論描述,可能并不那么淺顯易懂。例如,下面有兩種關(guān)于 張量的定:
通常定義張量的物理學(xué)或傳統(tǒng)數(shù)學(xué)方法,是把張量看成一個多維數(shù)組,當(dāng)變換坐標(biāo)或變換基底時,其分量會按照一定規(guī)則進(jìn)行變換,這些規(guī)則有兩種:即協(xié)變或逆變轉(zhuǎn)換。
通?,F(xiàn)代數(shù)學(xué)中的方法,是把張量定義成某個矢量空間或其對偶空間上的多重線性映射,這矢量空間在需要引入基底之前不固定任何坐標(biāo)系統(tǒng)。例如協(xié)變矢量,可以描述為 1-形式,或者作為逆變矢量的對偶空間的元素。
上面的定義不知道你看懂了沒有?估計會有點困難。下面,我們進(jìn)行通俗易懂的說明:
首先,你應(yīng)該知道什么是向量和矩陣。先前的介紹中,我們把1維的數(shù)組稱之為向量,2維的數(shù)組稱之為矩陣。那么,現(xiàn)在告訴你張量其實代表著更大的范圍,你也可以把其看作是N維數(shù)組。
所以,如果現(xiàn)在重新描述向量和矩陣,就可以是:一階張量為向量,二階張量為矩陣。當(dāng)然,零階張量也就是標(biāo)量,而更重要的是N階張量,也就是N維數(shù)組。


所以,張量并不是什么晦澀難懂的概念。如果不嚴(yán)謹(jǐn)?shù)闹v,張量就是 NN 維數(shù)組。前面提到的向量、矩陣,也是張量。
你即將學(xué)習(xí)到的大多數(shù)深度學(xué)習(xí)框架都會使用張量的概念,這樣做的好處是統(tǒng)一對數(shù)據(jù)的定義。NumPy 中,數(shù)據(jù)都使用 Ndarray 多維數(shù)組進(jìn)行定義,TensorFlow 中,數(shù)據(jù)都會用張量進(jìn)行表述。
下面就來學(xué)習(xí) TensorFlow 中對張量的定義。在 TensorFlow 中,每一個 Tensor 都具備兩個基礎(chǔ)屬性:數(shù)據(jù)類型(默認(rèn):float32)和形狀。
其中,數(shù)據(jù)類型大致如下表所示:

另外,TensorFlow 通過三種符號約定來描述張量維度:階,形狀和維數(shù)。三者之間的關(guān)系如下:

值得注意的是,上表中的示例都是形容張量的形狀。例如 [3, 4] 指的張量的形狀為 [3, 4],而不是張量 [3, 4]。
根據(jù)不同的用途,TensorFlow 中主要有 2 種張量類型,分別是:
tf.Variable :變量 Tensor,需要指定初始值,常用于定義可變參數(shù),例如神經(jīng)網(wǎng)絡(luò)的權(quán)重。
tf.constant :常量 Tensor,需要指定初始值,定義不變化的張量。
我們可以通過傳入列表或 NumPy 數(shù)組來新建變量和常量類型的張量:
import tensorflow as tf
tf.__version__
v = tf.Variable([[1, 2], [3, 4]]) # 形狀為 (2, 2) 的二維變量
v
c = tf.constant([[1, 2], [3, 4]]) # 形狀為 (2, 2) 的二維常量
c
仔細(xì)觀察,你會發(fā)現(xiàn)輸出包含了張量的 3 部分屬性,分別是形狀 shape,數(shù)據(jù)類型 dtype,以及對應(yīng)的 NumPy 數(shù)組。
你還可以直接通過 .numpy() 輸出張量的 NumPy 數(shù)組。
c.numpy()
上面我們已經(jīng)介紹了常量張量,這里再列舉幾個經(jīng)常會用到的新建特殊常量張量的方法:
tf.zeros:新建指定形狀且全為 0 的常量 Tensor
tf.zeros_like:參考某種形狀,新建全為 0 的常量 Tensor
tf.ones:新建指定形狀且全為 1 的常量 Tensor
tf.ones_like:參考某種形狀,新建全為 1 的常量 Tensor
tf.fill:新建一個指定形狀且全為某個標(biāo)量值的常量 Tensor
c = tf.zeros([3, 3]) # 3x3 全為 0 的常量 Tensor
c
tf.ones_like(c) # 與 c 形狀一致全為 1 的常量 Tensor
tf.fill([2, 3], 6) # 2x3 全為 6 的常量 Tensor
除此之外,我們還可以創(chuàng)建一些序列,例如:
tf.linspace:創(chuàng)建一個等間隔序列。
tf.range:創(chuàng)建一個數(shù)字序列。
tf.linspace(1.0, 10.0, 5, name="linspace")
tf.range(start=1, limit=10, delta=2)
實際上,如果你熟悉 NumPy 的話,你會發(fā)現(xiàn)這與 NumPy 中創(chuàng)建各式各樣的多維數(shù)組方法大同小異。數(shù)據(jù)類型是一切的基礎(chǔ),了解完張量我們就可以繼續(xù)學(xué)習(xí)張量的運算了。
Eager Execution
TensorFlow 2 帶來的最大改變之一是將 1.x 的 Graph Execution(圖與會話機(jī)制)更改為 Eager Execution(動態(tài)圖機(jī)制)。在 1.x 版本中,低級別 TensorFlow API 首先需要定義數(shù)據(jù)流圖,然后再創(chuàng)建 TensorFlow 會話,這一點在 2.0 中被完全舍棄。TensorFlow 2 中的 Eager Execution 是一種命令式編程環(huán)境,可立即評估操作,無需構(gòu)建圖。
所以說,TensorFlow 的張量運算過程可以像 NumPy 一樣直觀且自然了。接下來,我們以最簡單的加法運算為例:
c + c # 加法計算
如果你接觸過 1.x 版本的 TensorFlow,你要知道一個加法運算過程十分復(fù)雜。我們需要初始化全局變量 → 建立會話 → 執(zhí)行計算,最終才能打印出張量的運算結(jié)果。
init_op = tf.global_variables_initializer() # 初始化全局變量
with tf.Session() as sess: # 啟動會話
sess.run(init_op)
print(sess.run(c + c)) # 執(zhí)行計算
Eager Execution 帶來的好處顯而易見,其進(jìn)一步降低了 TensorFlow 的入門門檻。之前的 Graph Execution 模式,實際上讓很多人在入門時都很郁悶,因為完全不符合正常思維習(xí)慣。
TensorFlow 中提供的數(shù)學(xué)計算,包括線性代數(shù)計算方面的方法也是應(yīng)有盡有,十分豐富。下面,我們再列舉一個示例。
a = tf.constant([1., 2., 3., 4., 5., 6.], shape=[2, 3])
b = tf.constant([7., 8., 9., 10., 11., 12.], shape=[3, 2])
c = tf.linalg.matmul(a, b) # 矩陣乘法
c
tf.linalg.matrix_transpose(c) # 轉(zhuǎn)置矩陣
你應(yīng)該能夠感覺到,這些常用 API 都能在 NumPy 中找到對應(yīng)的方法,這也就是課程需要你預(yù)先熟悉 NumPy 的原因。由于函數(shù)實在太多太多。一般來講,除了自己經(jīng)常使用到的,都會在需要某種運算的時候,查閱官方文檔。
所以說,你可以把 TensorFlow 理解成為 TensorFlow 式的 NumPy + 為搭建神經(jīng)網(wǎng)絡(luò)而生的 API。
自動微分
在數(shù)學(xué)中,微分是對函數(shù)的局部變化率的一種線性描述。雖然微分和導(dǎo)數(shù)是兩個不同的概念。但是,對一元函數(shù)來說,可微與可導(dǎo)是完全等價的。如果你熟悉神經(jīng)網(wǎng)絡(luò)的搭建過程,應(yīng)該明白梯度的重要性。而對于復(fù)雜函數(shù)的微分過程是及其麻煩的,為了提高應(yīng)用效率,大部分深度學(xué)習(xí)框架都有自動微分機(jī)制。
TensorFlow 中,你可以使用 tf.GradientTape 跟蹤全部運算過程,以便在必要的時候計算梯度。
w = tf.Variable([1.0]) # 新建張量
with tf.GradientTape() as tape: # 追蹤梯度
loss = w * w
grad = tape.gradient(loss, w) # 計算梯度
grad
上面,我們演示了一個自動微分過程,它的數(shù)學(xué)求導(dǎo)過程如下:

所以,當(dāng)w等于 1 時,計算結(jié)果為 2。
tf.GradientTape 會像磁帶一樣記錄下計算圖中的梯度信息,然后使用 .gradient 即可回溯計算出任意梯度,這對于使用 TensorFlow 低階 API 構(gòu)建神經(jīng)網(wǎng)絡(luò)時更新參數(shù)非常重要。
常用模塊
上面,我們已經(jīng)學(xué)習(xí)了 TensorFlow 核心知識,接下來將對 TensorFlow API 中的常用模塊進(jìn)行簡單的功能介紹。對于框架的使用,實際上就是靈活運用各種封裝好的類和函數(shù)。由于 TensorFlow API 數(shù)量太多,迭代太快,所以大家要養(yǎng)成隨時 查閱官方文檔 的習(xí)慣。
tf.:包含了張量定義,變換等常用函數(shù)和類。
tf.data:輸入數(shù)據(jù)處理模塊,提供了像 tf.data.Dataset 等類用于封裝輸入數(shù)據(jù),指定批量大小等。
tf.image:圖像處理模塊,提供了像圖像裁剪,變換,編碼,解碼等類。
tf.keras:原 Keras 框架高階 API。包含原 tf.layers 中高階神經(jīng)網(wǎng)絡(luò)層。
tf.linalg:線性代數(shù)模塊,提供了大量線性代數(shù)計算方法和類。
tf.losses:損失函數(shù)模塊,用于方便神經(jīng)網(wǎng)絡(luò)定義損失函數(shù)。
tf.math:數(shù)學(xué)計算模塊,提供了大量數(shù)學(xué)計算函數(shù)。
tf.saved_model:模型保存模塊,可用于模型的保存和恢復(fù)。
tf.train:提供用于訓(xùn)練的組件,例如優(yōu)化器,學(xué)習(xí)率衰減策略等。
tf.nn:提供用于構(gòu)建神經(jīng)網(wǎng)絡(luò)的底層函數(shù),以幫助實現(xiàn)深度神經(jīng)網(wǎng)絡(luò)各類功能層。
tf.estimator:高階 API,提供了預(yù)創(chuàng)建的 Estimator 或自定義組件。
在構(gòu)建深度神經(jīng)網(wǎng)絡(luò)時,TensorFlow 可以說提供了你一切想要的組件,從不同形狀的張量、激活函數(shù)、神經(jīng)網(wǎng)絡(luò)層,到優(yōu)化器、數(shù)據(jù)集等,一應(yīng)俱全。
導(dǎo)數(shù)計算和自動微分實現(xiàn)
導(dǎo)數(shù)計算
Sigmoid 函數(shù)是機(jī)器學(xué)習(xí)中會經(jīng)??吹降囊粋€函數(shù),它是邏輯回歸的基礎(chǔ)也可以充當(dāng)神經(jīng)網(wǎng)絡(luò)的激活函數(shù)。Sigmoid 函數(shù)如下所示:

請參考上方的公式,使用 TensorFlow 2 提供的數(shù)學(xué)計算方法實現(xiàn) Sigmoid 函數(shù)
sigmoid(x)。你可能需要借助于搜索引擎和 TensorFlow 官方文檔 來查找一些適用的函數(shù)。
import tensorflow as tf
tf.__version__
def sigmoid(x):
s = 1 / (1 + tf.math.exp(-x))
return s
接下來,請使用 TensorFlow 2 提供的方法初始化一組 [-10, 10][?10,10] 之間等間隔的 100 個值,并作為 Sigmoid 函數(shù)的輸入。最終,使用 Matplotlib 繪制出 Sigmoid 函數(shù)的曲線。同樣,你只能使用 TensorFlow 2 提供的相關(guān)方法。
from matplotlib import pyplot as plt
%matplotlib inline
x = tf.linspace(-10.0, 10.0, 100)
plt.plot(x, sigmoid(x))

接下來,請對 Sigmoid 函數(shù)求導(dǎo),并實現(xiàn) Sigmoid 導(dǎo)數(shù)函數(shù) sigmoid_derivative(x):
def sigmoid_derivative(x):
d_s = sigmoid(x) * (1 - sigmoid(x))
return d_s
同樣,使用上面初始化好的x值,繪制 Sigmoid 函數(shù)導(dǎo)數(shù)的變化曲線。
plt.plot(x, sigmoid_derivative(x))

自動微分
TensorFlow 中,你可以使用 tf.GradientTape 跟蹤全部運算過程,以便在必要的時候計算梯度。當(dāng)然,對于上方 Sigmoid 一元函數(shù)而言,也就是自動求導(dǎo)過程。
接下來,請使用在實驗中學(xué)習(xí)到的方法,使用 tf.GradientTape 完成自動微分。同樣,你需要傳入前面生成的等間距x并繪制出導(dǎo)數(shù)的變化曲線。
x = tf.Variable(x)
with tf.GradientTape() as tape: # 追蹤梯度
s = 1 / (1 + tf.math.exp(-x))
grad = tape.gradient(s, x) # 計算梯度
grad
plt.plot(x.numpy(), grad.numpy()) # 繪制 sigmoid 導(dǎo)數(shù)變化曲線

可以發(fā)現(xiàn),借助于自動微分機(jī)制,我們只需要書寫計算過程即可得到關(guān)于任意變量的微分結(jié)果。