簡單粗暴 TensorFlow(Xihan Li(雪麒))

簡單粗暴TensorFlow | A Concise Handbook of TensorFlow

基於Eager Execution | Based on Eager Execution

在線閱讀 | Read online : https://tf.wiki

備用地址 | Alternative URL:https://snowkylin.github.io/TensorFlow-cn/

作者 | Author: Xihan Li (snowkylin)

英文版譯者 | Translators of English version: Zida Jin, Ming, Ji-An Li, Xihan Li

本手冊是一篇精簡的TensorFlow入門指導,基於TensorFlow的Eager Execution(動態圖)模式,力圖讓具備一定機器學習及Python基礎的開發者們快速上手TensorFlow。

This handbook is a concise introduction to TensorFlow based on Eager Execution mode, trying to help developers get started with TensorFlow quickly with some basic machine learning and Python knowledge.

PDF下載 | PDF download :

(中文版 | Chinese): https://www.tensorflowers.cn/t/6230
(英文版 | English): https://github.com/snowkylin/TensorFlow-cn/releases
在線答疑區 | Online Q&A area :

(中文 | Chinese): https://www.tensorflowers.cn/b/48
(英文 | English): https://github.com/snowkylin/TensorFlow-cn/issues

開始

基於 Eager Execution | Based on Eager Execution

本手冊是一篇精簡的 TensorFlow 入門指導,基於 TensorFlow 的 Eager Execution(動態圖)模式,力圖讓 具備一定機器學習及 Python 基礎的開發者們快速上手 TensorFlow。

友情提醒:如果發現閱讀中有難以理解的部分,請檢查自己對每章的“前置知識”部分是否有清楚的理解。

答疑區 - TensorFlow 中文社區“簡單粗暴 TensorFlow”版面:https://www.tensorflowers.cn/b/48 (如果 您對本教程有任何疑問,請至 TensorFlow 中文社區的該版面發問) PDF 下載:https://www.tensorflowers.cn/t/6230 GitHub: https://github.com/snowkylin/TensorFlow-cn

This handbook is a concise introduction to TensorFlow based on TensorFlow’s Eager Execution mode, trying to help developers get started with TensorFlow quickly with some basic machine learning and Python knowledge.

Friendly reminder: If you find something difficult to understand in reading, please check if you have a clear understanding of the “Prerequisites”part of each chapter.

Q&A area - TensorFlow Chinese community “A Concise Handbook of TensorFlow”forum: https://www. tensorflowers.cn/b/48 (If you have any questions about this tutorial, please ask in this forum of the TensorFlow Chinese community) PDF download: https://www.tensorflowers.cn/t/6230

GitHub: https://github.com/snowkylin/TensorFlow-cn

1. 前言

2018 年 3 月 30 日,Google 在加州山景城舉行了第二屆 TensorFlow Dev Summit 開發者峯會,並宣佈正 式發佈 TensorFlow 1.8 版本。筆者有幸獲得 Google 的資助親臨峯會現場,見證了這一具有里程碑式意義的 新版本發佈。衆多新功能的加入和支持展示了 TensorFlow 的雄心壯志,同時早在 2017 年秋就開始測試的 Eager Execution(動態圖機制)在這一版本中終於正式加入,併成爲了入門 TensorFlow 的官方推薦模式。

The easiest way to get started with TensorFlow is using Eager Execution. ——https://www.tensorflow.org/get_started/ 在此之前,TensorFlow 所基於的傳統 Graph Execution 的弊端,如入門門檻高、調試困難、靈活性差、無法使 用 Python 原生控制語句等早已被開發者詬病許久。一些新的基於動態圖機制的深度學習框架(如 PyTorch) 也橫空出世,並以其易用性和快速開發的特性而佔據了一席之地。尤其是在學術研究等需要快速迭代模型的 領域,PyTorch 等新興深度學習框架已經成爲主流。**筆者所在的數十人的機器學習實驗室中,竟只有筆者一 人“守舊”地使用 TensorFlow。然而,直到目前,市面上相關的 TensorFlow 相關的中文技術書籍及資料仍 然基於傳統的 Graph Execution 模式,讓不少初學者(尤其是剛學過機器學習課程的大學生)望而卻步。**由 此,在 TensorFlow 正式支持 Eager Execution 之際,有必要出現一本全新的技術手冊,幫助初學者及需要 快速迭代模型的研究者,以一個全新的角度快速入門 TensorFlow。

同時,本手冊還有第二個任務。市面上與 TensorFlow 相關的中文技術書籍大部分都以深度學習爲主線,而 將 TensorFlow 作爲這些深度學習模型的實現方式。這樣固然有體系完整的優點,然而對於已經對機器學 習或深度學習理論有所瞭解,希望側重於學習 TensorFlow 本身的讀者而言,就顯得不夠友好。同時,雖然 TensorFlow 有官方的教學文檔(https://tensorflow.google.cn/tutorials),然而在體例上顯得邏輯性不足,缺 乏一般教學文檔從淺入深,層次遞進的特性,而更類似於一系列技術文檔的羅列。於是,筆者希望編寫一本 手冊,以儘量精簡的篇幅展示 TensorFlow 作爲一個計算框架的主要特性,並彌補官方手冊的不足,力圖能 讓已經有一定機器學習/深度學習知識及編程能力的讀者迅速上手 TensorFlow,並在實際編程過程中可以隨時查閱並解決實際問題。

本手冊的主要特徵有:

• 主要基於 TensorFlow 最新的 Eager Execution(動態圖)模式,以便於模型的快速迭代開發。但依然 會包含傳統的 Graph Execution 模式,代碼上儘可能兼容兩者;

• 定位以教學及工具書爲主,編排以 TensorFlow 的各項概念和功能爲核心,力求能夠讓 TensorFlow 開 發者快速查閱。各章相對獨立,不一定需要按順序閱讀。正文中不會出現太多關於深度學習和機器學 習的理論介紹,但會提供若干閱讀推薦以便初學者掌握相關基礎知識;

• 代碼實現均進行仔細推敲,力圖簡潔高效和表意清晰。模型實現均統一使用 TensorFlow 官方文檔 最新 提出的繼承 tf.keras.Model 和 tf.keras.layers.Layer 的方式(在其他技術文檔中鮮少介紹),保 證代碼的高度可複用性。每個完整項目的代碼總行數均不過百行,讓讀者可以快速理解並舉一反三;

• 注重詳略,少即是多,不追求鉅細靡遺和面面俱到,不進行大篇幅的細節論述。

在整本手冊中,帶“*”的部分均爲選讀。

A 本手冊的暫定名稱《簡單粗暴 TensorFlow》是向我的好友兼同學 Chris Wu 編寫的《簡單粗暴 L T E X 》(https: A //github.com/wklchris/Note-by-LaTeX)致敬。該手冊清晰精煉,是 L T E X 領域不可多得的中文資料,也是 我在編寫這一技術文檔時所學習的對象。本手冊最初是在我的好友 Ji-An Li 所組織的深度學習研討小組中, 由我作爲預備知識的講義而編寫和使用。好友們的才學卓著與無私分享的品格也是編寫此拙作的重要助力。

本手冊的英文版由我的好友 Zida Jin(1-4 章)和 Ming(5-6 章)翻譯,並由 Ji-An Li 和筆者審校。三位朋 友犧牲了自己的大量寶貴時間翻譯和校對本手冊,同時 Ji-An Li 亦對本手冊的教學內容和代碼細節提供了 諸多寶貴意見。我謹向好友們爲本手冊的辛勤付出致以衷心的感謝。

衷心感謝 Google 中國開發者關係團隊和 TensorFlow 工程團隊的成員們對本手冊編寫所提供的幫助。其中 包括開發者關係團隊的 Luke Cheng 在本手冊寫作全程提供的思路啓發和持續鼓勵,開發者關係團隊的 Rui Li, Pryce Mu 和 TensorFlow 社羣維護的小夥伴們在本手冊宣發及推廣上提供的大力支持,以及 TensorFlow 團隊的 Tiezhen Wang 在本手冊工程細節方面提供的諸多建議和補充。

關於本手冊的意見和建議,歡迎在 https://github.com/snowkylin/TensorFlow-cn/issues 提交。這是一個開 源項目,您的寶貴意見將促進本手冊的持續更新。

2. TensorFlow 安裝

TensorFlow 的最新安裝步驟可參考官方網站上的說明(https://tensorflow.google.cn/install)。TensorFlow 支持 Python、Java、Go、C 等多種編程語言以及 Windows、OSX、Linux 等多種操作系統,此處及後文均 以主流的 Python 語言爲準。 以下提供簡易安裝和正式安裝兩種途徑,供不同層級的讀者選用。

2.1 簡易安裝

如果只是安裝一個運行在自己電腦上的,無需 GPU 的簡易環境,不希望在環境配置上花費太多精力,建議按以下步驟安裝(以 Windows 系統爲例):

• 下載並安裝 Python 集成環境 Anaconda (Python 3.6 版本);

• 下載並安裝 Python 的 IDE PyCharm (Community 版本,或學生可申請 Professional 版本的免費授 權);

• 打開開始菜單中的“Anaconda Prompt”,輸入 pip install tensorflow。 完畢。

2.1.1 Test

安裝完畢後,我們來編寫一個簡單的程序來驗證安裝。

在命令行下輸入 activate tensorflow 進入之前建立的安裝有 TensorFlow 的 conda 環境,再輸入 python 進入 Python 環境,逐行輸入以下代碼:

import tensorflow as tf

tf.enable_eager_execution()

A = tf.constant([[1, 2], [3, 4]]) B = tf.constant([[5, 6], [7, 8]]) C = tf.matmul(A, B)

print(C)

如果能夠最終輸出:

tf.Tensor( [[19 22] [43 50]], shape=(2, 2), dtype=int32)

說明 TensorFlow 已安裝成功。運行途中可能會輸出一些 TensorFlow 的提示信息,屬於正常現象。

此處使用的是 Python 語言, 關於 Python 語言的入門教程可以參考 http://www.runoob.com/python3/ python3-tutorial.html 或 https://www.liaoxuefeng.com ,本手冊之後將默認讀者擁有 Python 語言的基本知識。不用緊張,Python 語言易於上手,而 TensorFlow 本身也不會用到 Python 語言的太多高級特性。關於 Python 的 IDE,建議使用 PyCharm 。如果你是學生並有.edu 結尾的郵箱的話,可以在 這裏 申請免費 的授權。如果沒有,也可以下載社區版本的 PyCharm,主要功能差別不大。

3. TensorFlow 基礎

本章介紹 TensorFlow 的基本操作。 前置知識:

• Python 基本操作 (賦值、分支及循環語句、使用 import 導入庫);

• Python 的 With 語句 ;

• NumPy ,Python 下常用的科學計算庫。TensorFlow 與之結合緊密;

• 向量 和 矩陣 運算(矩陣的加減法、矩陣與向量相乘、矩陣與矩陣相乘、矩陣的轉置等。

• 函數的導數 ,多元函數求導 (測試題:f(x, y) = x 2 + xy + y 2 , ∂f ∂x =?, ∂f ∂y =?);

• 線性迴歸 ;

• 梯度下降方法 求函數的局部最小值。

import tensorflow as tf

tf.enable_eager_execution()

a = tf.constant(1) b = tf.constant(1) c = tf.add(a, b)

print(c)

# 也可以直接寫 c = a + b,兩者等價

A = tf.constant([[1, 2], [3, 4]]) B = tf.constant([[5, 6], [7, 8]]) C = tf.matmul(A, B)

print(C)

輸出:

tf.Tensor(2, shape=(), dtype=int32) 
tf.Tensor( [[19 22] [43 50]], shape=(2, 2), dtype=int32)

使用常規的科學計算庫實現機器學習模型有兩個痛點:

• 經常需要手工求函數關於參數的偏導數。如果是簡單的函數或許還好,但一旦函數的形式變得複雜(尤 其是深度學習模型),手工求導的過程將變得非常痛苦,甚至不可行。

• 經常需要手工根據求導的結果更新參數。這裏使用了最基礎的梯度下降方法,因此參數的更新還較爲 容易。但如果使用更加複雜的參數更新方法(例如 Adam 或者 Adagrad),這個更新過程的編寫同樣會 非常繁雜。

而 TensorFlow 等深度學習框架的出現很大程度上解決了這些痛點,爲機器學習模型的實現帶來了很大的便 利。

TensorFlow 的 Eager Execution(動態圖)模式 與上述 NumPy 的運行方式十分類似,然而提供了更快速的運算(GPU 支持)、自動求導、優化器等一系列對深度學習非常重要的功能。以下展示瞭如何使用 TensorFlow 計算線性迴歸。可以注意到,程序的結構和前述 NumPy 的實現非常類似。這裏,TensorFlow 幫助我們做了兩件重要的工作:

• 使用 tape.gradient(ys, xs) 自動計算梯度;

• 使用 optimizer.apply_gradients(grads_and_vars) 自動更新模型參數。

a = tf.get_variable('a', dtype=tf.float32, shape=[], initializer=tf.zeros_initializer) b = tf.get_variable('b', dtype=tf.float32, shape=[], initializer=tf.zeros_initializer) variables = [a, b]

num_epoch = 10000 optimizer = tf.train.GradientDescentOptimizer(learning_rate=1e-3) for e in range(num_epoch):

# 使用 tf.GradientTape() 記錄損失函數的梯度信息 with tf.GradientTape() as tape:

y_pred = a * X + b

loss = 0.5 * tf.reduce_sum(tf.square(y_pred - y)) # TensorFlow 自動計算損失函數關於自變量(模型參數)的梯度 grads = tape.gradient(loss, variables) # TensorFlow 自動根據梯度更新參數 optimizer.apply_gradients(grads_and_vars=zip(grads, variables))

在 這 裏, 我 們 使 用 了 前 文 的 方 式 計 算 了 損 失 函 數 關 於 參 數 的 偏 導 數。 同 時, 使 用 tf.train. GradientDescentOptimizer(learning_rate=1e-3) 聲明瞭一個梯度下降 優化器(Optimizer), 其學習率爲 1e-3。優化器可以幫助我們根據計算出的求導結果更新模型參數,從而最小化某個特定的損失函數, 具體使用方式是調用其 apply_gradients() 方法。

注意到這裏,更新模型參數的方法 optimizer.apply_gradients() 需要提供參數 grads_and_vars,即待更新的變量(如上述代碼中的 variables )及損失函數關於這些變量的偏導數(如上述代碼中的 grads )。 具體而言,這裏需要傳入一個 Python 列表(List),列表中的每個元素是一個(變量的偏導數,變量)對。 比如這裏是 [(grad_w, w), (grad_b, b)] 。我們通過 grads = tape.gradient(loss, variables) 求出 tape 中記錄的 loss 關於 variables = [w, b] 中每個變量的偏導數,也就是 grads = [grad_w, grad_b], 再使用 Python 的 zip() 函數將 grads = [grad_w, grad_b] 和 vars = [w, b] 拼裝在一起,就可以組合出所需的參數了。

在實際應用中,我們編寫的模型往往比這裏一行就能寫完的線性模型 y_pred = tf.matmul(X, w) + b 要 複雜得多。所以,我們往往會編寫一個模型類,然後在需要調用的時候使用 y_pred = model(X) 進行調用。

4. TensorFlow 模型

關於模型類的編寫方式可見下章。

本章介紹如何使用 TensorFlow 快速搭建動態模型。 前置知識:

• Python 面向對象 (在 Python 內定義類和方法、類的繼承、構造和析構函數,使用 super() 函數調用 父類方法 ,使用 call() 方法對實例進行調用 等);

• 多層感知機、卷積神經網絡、循環神經網絡和強化學習(每節之前給出參考資料)。

4.1 模型(Model)與層(Layer)

如上一章所述,爲了增強代碼的可複用性,我們往往會將模型編寫爲類,然後在模型調用的地方使用 y_pred = model(X) 的形式進行調用。模型類的形式非常簡單, 主要包含 init() (構造函數, 初始化)和 1 call(input) (模型調用)兩個方法,但也可以根據需要增加自定義的方法。

class MyModel(tf.keras.Model):

def __init__(self):

super().__init__() # Python 2 下使用 super(MyModel, self).__init__() # 此處添加初始化代碼(包含 call 方法中會用到的層)

def call(self, inputs):
# 此處添加模型調用的代碼(處理輸入並返回輸出)

return output

在 Python 類中,對類的實例 myClass 進行形如 myClass() 的調用等價於 myClass.call() 。在這裏,我們的模型繼承了 tf.keras.Model 這一父類。該父類中包含 call() 的定義,其中調用了 call() 方法,同時進行了一些 keras 的內部操作。這裏, 我們通過繼承 tf.keras.Model 並重載 call() 方法,即可在保持 keras 結構的同時加入模型調用的代碼。具體請見本章初“前置知 識”的 call() 部分。

在這裏,我們的模型類繼承了 tf.keras.Model 。Keras 是一個用 Python 編寫的高級神經網絡 API,現已得到 TensorFlow 的官方支持和內置。繼承 tf.keras.Model 的一個好處在於我們可以使用父類的若干方法 和屬性,例如在實例化類後可以通過 model.variables 這一屬性直接獲得模型中的所有變量,免去我們一 個個顯式指定變量的麻煩。

同時,我們引入 “層”(Layer) 的概念,層可以視爲比模型粒度更細的組件單位,將計算流程和變量進行 了封裝。我們可以使用層來快速搭建模型。

上一章中簡單的線性模型 y_pred = tf.matmul(X, w) + b ,我們可以通過模型類的方式編寫如下:

import tensorflow as tf tf.enable_eager_execution()

X = tf.constant([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) y = tf.constant([[10.0], [20.0]])

class Linear(tf.keras.Model):

def __init__(self):

super().__init__() self.dense = tf.keras.layers.Dense(units=1, kernel_initializer=tf.zeros_initializer(), bias_initializer=tf.zeros_initializer())

def call(self, input):

output = self.dense(input) return output

# 以下代碼結構與前節類似

model = Linear() optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01) for i in range(100):

with tf.GradientTape() as tape:

y_pred = model(X) # 調用模型 loss = tf.reduce_mean(tf.square(y_pred - y)) grads = tape.gradient(loss, model.variables) optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables)) print(model.variables)

這裏,我們沒有顯式地聲明 w 和 b 兩個變量並寫出 y_pred = tf.matmul(X, w) + b 這一線性變換,而是 在初始化部分實例化了一個全連接層(tf.keras.layers.Dense ),並在 call 方法中對這個層進行調用。全 連接層封裝了 output = activation(tf.matmul(input, kernel) + bias) 這一線性變換 + 激活函數的計算操作,以及 kernel 和 bias 兩個變量。當不指定激活函數時(即 activation(x) = x ),這個全連接 層就等價於我們上述的線性變換。順便一提,全連接層可能是我們編寫模型時使用最頻繁的層。

如果我們需要顯式地聲明自己的變量並使用變量進行自定義運算,請參考自定義層。

4.2 基礎示例:多層感知機(MLP)

我們從編寫一個最簡單的 多層感知機 (Multilayer Perceptron, MLP)開始,介紹 TensorFlow 的模型編寫 方式。這裏,我們使用多層感知機完成 MNIST 手寫體數字圖片數據集 [LeCun1998] 的分類任務。

在這裏插入圖片描述

先進行預備工作,實現一個簡單的 DataLoader 類來讀取 MNIST 數據集數據。

class DataLoader():

def __init__(self):

mnist = tf.contrib.learn.datasets.load_dataset("mnist") self.train_data = mnist.train.images ,784] self.train_labels = np.asarray(mnist.train.labels, dtype=np.int32) ,→int32 self.eval_data = mnist.test.images ,784] self.eval_labels = np.asarray(mnist.test.labels, dtype=np.int32) ,→int32

def get_batch(self, batch_size):

# np.array [55000,␣

# np.array [55000] of␣

# np.array [10000,␣

# np.array [10000] of␣

index = np.random.randint(0, np.shape(self.train_data)[0], batch_size) return self.train_data[index, :], self.train_labels[index]

多層感知機的模型類實現與上面的線性模型類似,所不同的地方在於層數增加了(顧名思義,“多層”感知 機),以及引入了非線性激活函數(這裏使用了 ReLU 函數 ,即下方的 activation=tf.nn.relu )。該模型 輸入一個向量(比如這裏是拉直的 1×784 手寫體數字圖片),輸出 10 維的信號,分別代表這張圖片屬於 0 到 9 的概率。這裏我們加入了一個 predict 方法,對圖片對應的數字進行預測。在預測的時候,選擇概率最 大的數字進行預測輸出。

class MLP(tf.keras.Model):

def __init__(self):

super().__init__()
self.dense1 = tf.keras.layers.Dense(units=100,activation=tf.nn.relu) 
self.dense2 = tf.keras.layers.Dense(units=10)

def call(self, inputs):

x = self.dense1(inputs) 
x = self.dense2(x) 
return x

def predict(self, inputs):
logits = self(inputs) 
return tf.argmax(logits, axis=-1)

定義一些模型超參數:

num_batches = 1000 batch_size = 50 learning_rate = 0.001

實例化模型,數據讀取類和優化器:

model = MLP() 
data_loader = DataLoader() 
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)

然後迭代進行以下步驟:

• 從 DataLoader 中隨機取一批訓練數據;

• 將這批數據送入模型,計算出模型的預測值;

• 將模型預測值與真實值進行比較,計算損失函數(loss);

• 計算損失函數關於模型變量的導數;

• 使用優化器更新模型參數以最小化損失函數。 具體代碼實現如下:

for batch_index in range(num_batches):

X, y = data_loader.get_batch(batch_size) with tf.GradientTape() as tape:

y_logit_pred = model(tf.convert_to_tensor(X))

loss = tf.losses.sparse_softmax_cross_entropy(labels=y, logits=y_logit_pred)

print("batch %d: loss %f" % (batch_index, loss.numpy())) grads = tape.gradient(loss, model.variables) optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables))

接下來,我們使用驗證集測試模型性能。具體而言,比較驗證集上模型預測的結果與真實結果,輸出預測正 確的樣本數佔總樣本數的比例:

num_eval_samples = np.shape(data_loader.eval_labels)[0] y_pred = model.predict(data_loader.eval_data).numpy() print("test accuracy: %f" % (sum(y_pred == data_loader.eval_labels) / num_eval_samples))

輸出結果:
test accuracy: 0.947900

可以注意到,使用這樣簡單的模型,已經可以達到 95% 左右的準確率。

4.3 卷積神經網絡(CNN)

卷積神經網絡 (Convolutional Neural Network, CNN)是一種結構類似於人類或動物的 視覺系統 的人工神 經網絡,包含一個或多個卷積層(Convolutional Layer)、池化層(Pooling Layer)和全連接層(Dense Layer)。 具體原理建議可以參考臺灣大學李宏毅教授的《機器學習》課程的 Convolutional Neural Network 一章。

具體的實現見下,和 MLP 很類似,只是新加入了一些卷積層和池化層。
在這裏插入圖片描述

class CNN(tf.keras.Model):

def __init__(self):

super().__init__() self.conv1 = tf.keras.layers.Conv2D( filters=32, # 卷積核數目 kernel_size=[5, 5], # 感受野大小 padding="same", # padding 策略 activation=tf.nn.relu # 激活函數 ) self.pool1 = tf.keras.layers.MaxPool2D(pool_size=[2, 2], strides=2) self.conv2 = tf.keras.layers.Conv2D( filters=64,
kernel_size=[5, 5], padding="same", activation=tf.nn.relu

) self.pool2 = tf.keras.layers.MaxPool2D(pool_size=[2, 2], strides=2) self.flatten = tf.keras.layers.Reshape(target_shape=(7 * 7 * 64,)) self.dense1 = tf.keras.layers.Dense(units=1024, activation=tf.nn.relu) self.dense2 = tf.keras.layers.Dense(units=10)

def call(self, inputs):

inputs = tf.reshape(inputs, [-1, 28, 28, 1]) x = self.conv1(inputs) # [batch_size, 28, 28, 32] x = self.pool1(x) # [batch_size, 14, 14, 32] x = self.conv2(x) # [batch_size, 14, 14, 64] x = self.pool2(x) # [batch_size, 7, 7, 64] x = self.flatten(x) # [batch_size, 7 * 7 * 64] x = self.dense1(x) # [batch_size, 1024] x = self.dense2(x) # [batch_size, 10] return x

def predict(self, inputs):

logits = self(inputs)

將前節的 model = MLP() 更換成 model = CNN() ,輸出如下:

test accuracy: 0.988100

可以發現準確率有非常顯著的提高。事實上,通過改變模型的網絡結構(比如加入 Dropout 層防止過擬合), 準確率還有進一步提升的空間。

4.4 循環神經網絡(RNN)

循環神經網絡(Recurrent Neural Network, RNN)是一種適宜於處理序列數據的神經網絡,被廣泛用於語言 模型、文本生成、機器翻譯等。關於 RNN 的原理,可以參考:

• Recurrent Neural Networks Tutorial, Part 1 – Introduction to RNNs

• 臺灣大學李宏毅教授的《機器學習》課程的 Recurrent Neural Network (part 1) Recurrent Neural Network (part 2) 兩部分。

• LSTM 原理:Understanding LSTM Networks

• RNN 序列生成:[Graves2013]

這裏,我們使用 RNN 來進行尼采風格文本的自動生成。

這個任務的本質其實預測一段英文文本的接續字母的概率分佈。比如,我們有以下句子:

I am a studen

這個句子(序列)一共有 13 個字符(包含空格)。當我們閱讀到這個由 13 個字符組成的序列後,根據我 們的經驗,我們可以預測出下一個字符很大概率是“t”。我們希望建立這樣一個模型,輸入 num_batch 個 由編碼後字符組成的,長爲 seq_length 的序列,輸入張量形狀爲 [num_batch, seq_length],輸出這些序列 接續的下一個字符的概率分佈,概率分佈的維度爲字符種類數 num_chars,輸出張量形狀爲 [num_batch, num_chars]。我們從下一個字符的概率分佈中採樣作爲預測值,然後滾雪球式地生成下兩個字符,下三個字 符等等,即可完成文本的生成任務。

首先,還是實現一個簡單的 DataLoader 類來讀取文本,並以字符爲單位進行編碼。

class DataLoader():

def __init__(self):

path = tf.keras.utils.get_file('nietzsche.txt', origin='https://s3.amazonaws.com/text-datasets/nietzsche.txt') with open(path, encoding='utf-8') as f:

self.raw_text = f.read().lower() self.chars = sorted(list(set(self.raw_text))) self.char_indices = dict((c, i) for i, c in enumerate(self.chars)) self.indices_char = dict((i, c) for i, c in enumerate(self.chars)) self.text = [self.char_indices[c] for c in self.raw_text]

def get_batch(self, seq_length, batch_size):

seq = [] 
next_char = [] 
for i in range(batch_size):
index = np.random.randint(0, len(self.text) - seq_length)
seq.append(self.text[index:index+seq_length])
next_char.append(self.text[index+seq_length]) 
return np.array(seq), np.array(next_char) # [num_batch, seq_length], [num_batch]

接下來進行模型的實現。在 init 方法中我們實例化一個常用的 BasicLSTMCell 單元,以及一個線性變 換用的全連接層,我們首先對序列進行 One Hot 操作,即將編碼 i 變換爲一個 n 維向量,其第 i 位爲 1,其餘 均爲 0。這裏 n 爲字符種類數 num_char。變換後的序列張量形狀爲 [num_batch, seq_length, num_chars]。 接下來,我們將序列從頭到尾依序送入 RNN 單元,即將當前時間 t 的 RNN 單元狀態 state 和 t 時刻的序 列 inputs[:, t, :] 送入 RNN 單元,得到當前時間的輸出 output 和下一個時間 t+1 的 RNN 單元狀態。 取 RNN 單元最後一次的輸出,通過全連接層變換到 num_chars 維,即作爲模型的輸出。

具體實現如下:
在這裏插入圖片描述
在這裏插入圖片描述

class RNN(tf.keras.Model):

def __init__(self, num_chars):

super().__init__() self.num_chars = num_chars self.cell = tf.nn.rnn_cell.BasicLSTMCell(num_units=256) self.dense = tf.keras.layers.Dense(units=self.num_chars)

def call(self, inputs):

batch_size, seq_length = tf.shape(inputs) inputs = tf.one_hot(inputs, depth=self.num_chars) ,→chars] state = self.cell.zero_state(batch_size=batch_size, dtype=tf.float32) for t in range(seq_length.numpy()):

# [batch_size, seq_length, num_

output, state = self.cell(inputs[:, t, :], state) output = self.dense(output) return output

訓練過程與前節基本一致,在此複述:

• 從 DataLoader 中隨機取一批訓練數據;

• 將這批數據送入模型,計算出模型的預測值;

• 將模型預測值與真實值進行比較,計算損失函數(loss);

• 計算損失函數關於模型變量的導數;

• 使用優化器更新模型參數以最小化損失函數。

data_loader = DataLoader() 
model = RNN(len(data_loader.chars)) optimizer = tf.train.Adam
Optimizer(learning_rate=learning_rate) 
for batch_index in range(num_batches):

X, y = data_loader.get_batch(seq_length, batch_size) 
with tf.GradientTape() as tape:

y_logit_pred = model(X)

loss = tf.losses.sparse_softmax_cross_entropy(labels=y, logits=y_logit_pred)

print("batch %d: loss %f" % (batch_index, loss.numpy())) 
grads = tape.gradient(loss, model.variables) optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables))

關於文本生成的過程有一點需要特別注意。之前,我們一直使用 tf.argmax() 函數,將對應概率最大的值作 爲預測值。然而對於文本生成而言,這樣的預測方式過於絕對,會使得生成的文本失去豐富性。於是,我們 使用 np.random.choice() 函數按照生成的概率分佈取樣。這樣,即使是對應概率較小的字符,也有機會被 取樣到。同時,我們加入一個 temperature 參數控制分佈的形狀,參數值越大則分佈越平緩(最大值和最小 值的差值越小),生成文本的豐富度越高;參數值越小則分佈越陡峭,生成文本的豐富度越低。

def predict(self, inputs, temperature=1.):

batch_size, _ = tf.shape(inputs) 
logits = self(inputs) 
prob = tf.nn.softmax(logits / temperature).numpy() 
return np.array([np.random.choice(self.num_chars, p=prob[i, :]) 
for i in range(batch_size.numpy())])

通過這種方式進行“滾雪球”式的連續預測,即可得到生成文本。

X_, _ = data_loader.get_batch(seq_length, 1) 
for diversity in [0.2, 0.5, 1.0, 1.2]:
X = X_ 
print("diversity %f:" % diversity) 
for t in range(400):
y_pred = model.predict(X, diversity)
print(data_loader.indices_char[y_pred[0]], end='', flush=True)
X = np.concatenate([X[:, 1:], np.expand_dims(y_pred, axis=1)], axis=-1)

生成的文本如下:
diversity 0.200000:

conserted and conseive to the conterned to it is a self–and seast and the selfes as a seast the␣ ,→expecience and and and the self–and the sered is a the enderself and the sersed and as a the␣ ,→concertion of the series of the self in the self–and the serse and and the seried enes and␣ ,→seast and the sense and the eadure to the self and the present and as a to the self–and the␣ ,→seligious and the enders

diversity 0.500000:

can is reast to as a seligut and the complesed has fool which the self as it is a the beasing and us immery and seese for entoured underself of␣

,→the seless and the sired a mears and everyther to out every sone thes and reapres and seralise␣ ,→as a streed liees of the serse to pease the cersess of the selung the elie one of the were as we␣ ,→and man one were perser has persines and conceity of all self-el

diversity 1.000000:

entoles by their lisevers de weltaale, arh pesylmered, and so jejurted count have foursies as is descinty iamo; to semplization refold, we dancey or theicks-welf–atolitious on his such which here oth idey of pire master, ie gerw their endwit in ids, is an trees constenved mase commars is leed␣ ,→mad decemshime to the mor the elige. the fedies (byun their ope wopperfitious–antile and the it␣ ,→as the f

diversity 1.200000:
cain, elvotidue, madehoublesily inselfy!–ie the rads incults of to prusely le]enfes patuateded:.–a coud–theiritibaior ,→"nrallysengleswout peessparify oonsgoscess teemind thenry ansken suprerial mus, cigitioum: 4reas. ,→ whouph: who eved arn inneves to sya" natorne. hag open reals whicame oderedte,[fingo is zisternethta simalfule dereeg hesls lang-lyes thas quiin turjentimy; periaspedey tomm–whach

4.5 深度強化學習(DRL)

強化學習 (Reinforcement learning,RL)強調如何基於環境而行動,以取得最大化的預期利益。結合了深 度學習技術後的強化學習更是如虎添翼。這兩年廣爲人知的 AlphaGo 即是深度強化學習的典型應用。深度 強化學習的基礎知識可參考:

• Demystifying Deep Reinforcement Learning (中文編譯)

• [Mnih2013]

這裏,我們使用深度強化學習玩 CartPole(平衡杆)遊戲。簡單說,我們需要讓模型控制桿的左右運動,以讓其一直保持豎直平衡狀態。
在這裏插入圖片描述
我們使用 OpenAI 推出的 Gym 環境庫 中的 CartPole 遊戲環境,具體安裝步驟和教程可參考 官方文檔 和 這裏 。Gym 的基本調用方法如下:

import gym
env = gym.make('CartPole-v1') state = env.reset() while True:

# 實例化一個遊戲環境,參數爲遊戲名稱 # 初始化環境,獲得初始狀態

env.render()

# 對當前幀進行渲染,繪圖到屏幕 # 假設我們有一個訓練好的模型,能夠通過當前狀態預測出這時應該進行的

action = model.predict(state) 動作

next_state, reward, done, info = env.step(action) 動作的獎勵,遊戲是否已結束以及額外信息

# 讓環境執行動作,獲得執行完動作的下一個狀態,

if done: # 如果遊戲結束則退出循環

break

那麼,我們的任務就是訓練出一個模型,能夠根據當前的狀態預測出應該進行的一個好的動作。粗略地說, 一個好的動作應當能夠最大化整個遊戲過程中獲得的獎勵之和,這也是強化學習的目標。 以下代碼展示瞭如何使用深度強化學習中的 Deep Q-Learning 方法來訓練模型。

import tensorflow as tf 
import numpy as np 
import gym 
import random 
from collections import deque

tf.enable_eager_execution() 
num_episodes = 500 
num_exploration_episodes = 100 
max_len_episode = 1000 
batch_size = 32 
learning_rate = 1e-3 
gamma = 1.
initial_epsilon = 1. 
final_epsilon = 0.01

# Q-network 用於擬合 Q 函數,和前節的多層感知機類似。輸入 state,輸出各個 action 下的 Q-value(CartPole 下爲 2 維)。 class QNetwork(tf.keras.Model):

def __init__(self):

super().__init__() self.dense1 = tf.keras.layers.Dense(units=24, activation=tf.nn.relu) self.dense2 = tf.keras.layers.Dense(units=24, activation=tf.nn.relu) self.dense3 = tf.keras.layers.Dense(units=2)

def call(self, inputs):

x = self.dense1(inputs) x = self.dense2(x)
x = self.dense3(x) return x

def predict(self, inputs):

q_values = self(inputs) return tf.argmax(q_values, axis=-1)

# 實例化一個遊戲環境,參數爲遊戲名稱

env = gym.make('CartPole-v1') model = QNetwork() optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate) replay_buffer = deque(maxlen=10000) epsilon = initial_epsilon for episode_id in range(num_episodes):

state = env.reset() # 初始化環境,獲得初始狀態

epsilon = max(

initial_epsilon * (num_exploration_episodes - episode_id) / num_exploration_episodes,

final_epsilon)

for t in range(max_len_episode):

env.render() # 對當前幀進行渲染,繪圖到屏幕

if random.random() < epsilon: # epsilon-greedy 探索策略 action = env.action_space.sample() # 以 epsilon 的概率選擇隨機動作

else:

action = model.predict( tf.constant(np.expand_dims(state, axis=0), dtype=tf.float32)).numpy() action = action[0]

next_state, reward, done, info = env.step(action) # 讓環境執行動作,獲得執行完 動作的下一個狀態,動作的獎勵,遊戲是否已結束以及額外信息

reward = -10. if done else reward # 如果遊戲 Game Over,給予大 的負獎勵

replay_buffer.append((state, action, reward, next_state, done)) # 將 (state, action,␣ ,→reward, next_state) 的四元組(外加 done 標籤表示是否結束)放入經驗重放池

state = next_state

if done:

行下一個 episode

# 遊戲結束則退出本輪循環,進

print("episode %d, epsilon %f, score %d" % (episode_id, epsilon, t)) break

if len(replay_buffer) >= batch_size:

batch_state, batch_action, batch_reward, batch_next_state, batch_done = \ [np.array(a, dtype=np.float32) for a in zip(*random.sample(replay_buffer, batch_ ,→size))] # 從經驗回放池中隨機取一個 batch 的四元組 q_value = model(tf.constant(batch_next_state, dtype=tf.float32)) y = batch_reward + (gamma * tf.reduce_max(q_value, axis=1)) * (1 - batch_done) # 按照論文計算 y 值
with tf.GradientTape() as tape:

# 最小化 y 和 Q-value 的距離

loss = tf.losses.mean_squared_error( labels=y, predictions=tf.reduce_sum(model(tf.constant(batch_state)) * tf.one_hot(batch_action, depth=2), axis=1)

) grads = tape.gradient(loss, model.variables) optimizer.apply_gradients(grads_and_vars=zip(grads, model.variables))

# 計算梯度

並更新參數

4.6 自定義層 *

可能你還會問,如果現有的這些層無法滿足我的要求,我需要定義自己的層怎麼辦?

事實上,我們不僅可以繼承 tf.keras.Model 編寫自己的模型類,也可以繼承 tf.keras.layers.Layer 編 寫自己的層。

class MyLayer(tf.keras.layers.Layer):

def __init__(self):

super().__init__() # 初始化代碼

def build(self, input_shape): # input_shape 是一個 TensorShape 類型對象,提供輸入的形狀 # 在第一次使用該層的時候調用該部分代碼,在這裏創建變量可以使得變量的形狀自適應輸入的形狀 # 而不需要使用者額外指定變量形狀。 # 如果已經可以完全確定變量的形狀,也可以在__init__ 部分創建變量 self.variable_0 = self.add_variable(...)

self.variable_1 = self.add_variable(...)

def call(self, input):

# 模型調用的代碼(處理輸入並返回輸出) return output

例如,如果我們要自己實現一個本章第一節 中的全連接層,但指定輸出維度爲 1,可以按如下方式編寫,在 build 方法中創建兩個變量,並在 call 方法中使用創建的變量進行運算:

class LinearLayer(tf.keras.layers.Layer):

def __init__(self):

super().__init__()

def build(self, input_shape): # here input_shape is a TensorShape

self.w = self.add_variable(name='w', shape=[input_shape[-1], 1], initializer=tf.zeros_initializer())
self.b = self.add_variable(name='b', shape=[1], initializer=tf.zeros_initializer())

def call(self, X):

y_pred = tf.matmul(X, self.w) + self.b return y_pred

使用相同的方式,可以調用我們自定義的層 LinearLayer:

class Linear(tf.keras.Model):

def __init__(self):

super().__init__() self.layer = LinearLayer()

def call(self, input):

output = self.layer(input) return output
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章