教程 | 維度、廣播操作與可視化:如何高效使用TensorFlow

2021-02-20 機器之心

選自GitHub

機器之心編譯

參與:Nurhachu Null、李澤南

本文從 Tensorflow 基礎、理解靜態維度和動態維度、廣播操作(Broadingcast 的好處和壞處)、使用 Python 操作的原型內核和先進的可視化等幾個方面詳細梳理如何高效使用TensorFlow。

Tensorflow 基礎

TensorFlow 和其他諸如 numpy 之類的數學計算庫的根本區別在於:在 TensorFlow 中,運算操作是符號化的。這是一個強大的思想,它能夠讓 TensorFlow 做任何事情(例如,自動求微分),而這些事情對於 numpy 等命令式的庫而言是不可能完成的。但是這也導致了隨之而來的代價,就是掌握這個庫會相對困難一些。在本文中,作者會嘗試揭開 TensorFlow 的神秘面紗,並提供一些關於高效使用 TensorFlow 的指南和實踐例子。

讓我們以一個簡單的例子開始,我們想讓兩個隨機矩陣相乘。首先我們看一下用 numpy 來實現這個例子:

import numpy as npx = np.random.normal(size=[10, 10])y = np.random.normal(size=[10, 10])z = np.dot(x, y)print(z)

這樣的計算在 TensorFlow 中會是什麼樣子?

結果如下:

import tensorflow as tfx = tf.random_normal([10, 10])y = tf.random_normal([10, 10])z = tf.matmul(x, y)sess = tf.Session()z_val = sess.run(z)print(z_val)

與 numpy 直接執行計算並將結果複製到變量 z 中的思路不同的是,TensorFlow 僅僅給圖中代表結果的節點給提供了一個操作。如果我們直接列印 z 的值,我們會得到下面的信息:

Tensor("MatMul:0", shape=(10, 10), dtype=float32)

由於兩個輸入矩陣都有被完全定義的維度,TensorFlow 還能夠在指定張量的維度的同時指定它的數據類型。為了計算出張量的值,我們需要使用 Session.run() 這個函數來創建一個會話。

Tip:在使用 Jupyter notebook 的時候,要確保在開始的時候調用一下 tf.reset_default() 函數,以在定義新節點之前清空符號圖。

為了理解符號計算有多麼強大,讓我們來看一下另一個例子。假設我們有一些一條曲線上的樣本點(例如曲線是 f(x) = 5x^2 + 3),但是我們想要在不知道參數的情況下來估計這個函數 f(x)。我們定義一個含參數的函數 g(x, w) = w0 x^2 + w1 x + w2,它是關於輸入數據 x 和隱藏參數 w 的函數,我們的目標就是找到這組隱藏參數,使得 g(x, w) ≈ f(x)。我們可以通過最小化下面的損失函數 L(w) 來實現:L(w) = (f(x) - g(x, w))^2。儘管這個簡單的問題已經有一個閉合的解決方法了,但我們還是選擇使用一個更加通用的方法,這個方法能夠被應用在任何可微分的函數中,它使用了隨機梯度降的方法。我們簡單地計算損失函數 L(w) 在一組樣本點上關於 w 的平均梯度,然後朝著梯度的反方向變化參數 w。

下面展示了這個方法在 TensorFlow 中是如何實現的:

import numpy as np

import tensorflow as tf

# 使用佔位符從python向TensorFlow運算符中傳遞參數值。我們在這裡定義了兩個佔位符,其中一個用來存放輸入特徵x,另一個用來存放輸出y.

x = tf.placeholder(tf.float32)

y = tf.placeholder(tf.float32)

# 假設我們已經知道了期望的函數是一個二次多項式,我們就會分配一個具有3個元素的向量來代表這些參數。這些變量會被以隨機地進行初始化。

w = tf.get_variable("w", shape=[3, 1])

# 我們定義yhat為我們對y的估計值

f = tf.stack([tf.square(x), x, tf.ones_like(x)], 1)

yhat = tf.squeeze(tf.matmul(f, w), 1)

# 損失函數被定義為y的估計值和真實值之間地l2距離。我們還附加了一個收縮項,以確保結果得到的權值會比較小。

loss = tf.nn.l2_loss(yhat - y) + 0.1 * tf.nn.l2_loss(w)

# 我們以0.1的學習率使用Adam優化器來最小化損失函數。

train_op = tf.train.AdamOptimizer(0.1).minimize(loss)

def generate_data():

    x_val = np.random.uniform(-10.0, 10.0, size=100)

    y_val = 5 * np.square(x_val) + 3

    return x_val, y_val

sess = tf.Session()

# 因為我們要使用這些變量,所以我們需要先將它們初始化

sess.run(tf.global_variables_initializer())

for _ in range(1000):

    x_val, y_val = generate_data()

    _, loss_val = sess.run([train_op, loss], {x: x_val, y: y_val})

    print(loss_val)

    print(sess.run([w]))

運行完這段代碼之後,我得到的參數結果是:

[4.98605919,-0.00187828875e-04,3.8395009]

上面是編輯運行完之後的結果,它對應的損失值是 17.6175. 每一次的具體結果都會不同,但是最終結果都很接近期望的函數值。下面是原文作者提供的值。

[4.9924135,0.00040895029, 3.4504161]

這是與期望參數相當接近的近似。

這只是 TensorFlow 能夠做到的事情的冰山一角而已。很多類似於優化具有上百萬個參數的大型神經網絡的問題都能夠用 TensorFlow 以很少量的代碼來高效地實現。與此同時,TensorFlow 的開發團隊還致力於在多種設備、多線程以及支持多平臺等問題上更進一步。

簡單起見,在絕大多數例子中我們都手動地創建了會話,我們並沒有保存和加載 checkpoint,但是這卻是我們在實戰中經常需要做的事情。你很可能想著使用估計 API 來進行會話管理以及做日誌。我們在 code/framework 路徑下提供了一個簡單的可擴展架構,作為使用 TensorFlow 來訓練神經網絡的一個實際架構的例子。

理解靜態維度和動態維度

TensorFlow 中的張量具有靜態維度的屬性,它在構建圖的時候就被確定好了。靜態維度也有可能是不確定的。舉個例子,我們也許會定義一個維度為 [None,128] 的張量。

import tensorflow as tfa = tf.placeholder([None, 128])

這意味著第一個維度可以是任意大小,會在 Session.run() 的過程中被動態地決定。在表現靜態張量的時候,TensorFlow 有著相當醜的 API:

static_shape = a.get_shape().as_list()  # returns [None, 128]

(這個應該寫成 a,shape() 的形式,但是這裡有人把它定義得太不方便了。)

為獲得張量的動態形式,你可以調用 tf.shape 功能,它會返回一個表示給定張量的形狀的張量:

dynamic_shape = tf.shape(a)

一個張量的靜態維度可以使用 Tensor.set_shape() 函數來進行設置。

a.set_shape([32, 128])

僅當你知道自己在做什麼的時候再使用這個函數,事實上使用 tf.reshape() 會更加安全。

a =  tf.reshape(a, [32, 128])

如果有一個函數能在方便的時候返回靜態維度,在可用的時候返回動態維度,那將會很方便。下面就定義了這樣一個函數:

def get_shape(tensor):  static_shape = tensor.get_shape().as_list()  dynamic_shape = tf.unstack(tf.shape(tensor))  dims = [s[1] if s[0] is None else s[0]
         for s in zip(static_shape, dynamic_shape)]  
 return dims

現在設想:我們想通過摺疊第二維和第三維來把一個 3 維的矩陣轉換成一個 2 維的矩陣。我們可以使用上述的 get_shape() 函數來完成這件事:

b = placeholder([None, 10, 32])shape = get_shape(tensor)b = tf.reshape(b, [shape[0], shape[1] * shape[2]])

值得注意的是,這裡無論矩陣是不是靜態的,這個方法都能奏效。

事實上我們可以寫一個更加具有通用目標的函數來摺疊任何幾個維度:

import tensorflow as tfimport numpy as np
def reshape(tensor, dims_list):  shape = get_shape(tensor)  dims_prod = []
 for dims in dims_list:
     if isinstance(dims, int):      dims_prod.append(shape[dims])    
     elif all([isinstance(shape[d], int) for d in dims]):      dims_prod.append(np.prod([shape[d] for d in dims]))    
     else:      dims_prod.append(tf.prod([shape[d] for d in dims]))  tensor = tf.reshape(tensor, dims_prod)  
 return tensor

然後摺疊第二個維度就變得非常容易了。

b = placeholder([None, 10, 32])b = tf.reshape(b, [0, [1, 2]])

廣播操作

TensorFlow 支持廣播逐個元素的操作。正常情況下,當你想執行類似於加法和乘法的操作時,你需要確保算子的維度是匹配的。例如,你不能把一個維度為 [3,2] 的張量與一個維度為 [3,4] 的張量相加。但是在一個特殊的情況下你可以使用異常的維度。TensorFlow 會隱式地把一個張量的異常維度調整到與另一個算子相匹配的維度以實現維度兼容。所以將一個維度為 [3,2] 的張量與一個維度為 [3,1] 的張量相加是合法的。

import tensorflow as tfa = tf.constant([[1., 2.], [3., 4.]])b = tf.constant([[1.], [2.]])# c = a + tf.tile(a, [1, 2])c = a + b

廣播允許我們執行隱式調整,這能夠讓代碼更短,更加高效地使用內存,因為我們不需要存儲調整操作中間結果的內存開銷。這個方法可以被用在一個場景中:那就是結合不同長度的特徵。為了連接不同長度的特徵,我們通常會把輸入張量進行調整,然後把結果連接起來並應用一些非線性處理方法。這是很多神經網絡中的常用方法。

a = tf.random_uniform([5, 3, 5])b = tf.random_uniform([5, 1, 6])

# concat a and b and apply nonlinearity

tiled_b = tf.tile(b, [1, 3, 1])c = tf.concat([a, tiled_b], 2)d = tf.layers.dense(c, 10, activation=tf.nn.relu)

但是這個可以用廣播的方法做得更加有效。我們可以利用 f(m(x + y)) 等價於 f(mx + my) 這一事實。所以我們可以將線性操作分開處理,然後使用廣播的方法去做隱式的連接。

pa = tf.layers.dense(a, 10, activation=None)pb = tf.layers.dense(b, 10, activation=None)d = tf.nn.relu(pa + pb)

事實上這段代碼是相當通用的,只要張量之間能夠進行廣播操作,它就能夠被用於任何維度的張量上。

def tile_concat_dense(a, b, units, activation=tf.nn.relu):    pa = tf.layers.dense(a, units, activation=None)    pb = tf.layers.dense(b, units, activation=None)    c = pa + pb    if activation is not None:        c = activation(c)    
return c

到目前為止,我們討論了廣播操作的好的一面。你會問,那麼它不好的一面是什麼呢?隱式的假設總會讓調試變得更加難。看一下下面的例子:

a = tf.constant([[1.], [2.]])b = tf.constant([1., 2.])c = tf.reduce_sum(a + b)

你認為 c 的值會是多少呢?如果你說是 6,那你就錯了。結果會是 12。這是因為當兩個張量的秩不匹配的時候,TensorFlow 就會自動地以較低的維度來擴展第一維的大小,所以加法的結果會變成 [[2,3],[3,4]],所以在全體參數上的求和操作會給出 12 的結果。

避免這個問題的辦法就是儘可能地顯示化。如果我們顯示地指定了要將哪個維度進行求和,解決這個問題就會變得很容易了。

a = tf.constant([[1.], [2.]])b = tf.constant([1., 2.])c = tf.reduce_sum(a + b, 0)

現在 c 的值會是 [5,7],考慮到輸出結果的維度,我們會立即猜想是不是哪裡出了錯。一般的經驗法則就是在求和操作以及使用 tf.squeeze() 的時候總要指定具體的維度。

原型內核與 Python 操作下的高度可視化

為了更高的效率,TensorFlow 的運算內核是用 C++編寫的。但是用 C++寫 TensorFlow 內核是一件痛苦的事情。所以,在你實現內核之前,你也許會想著快速地實現一個原型系統。藉助於 tf.py_func() 函數,你可以將任何一段 Python 代碼轉化成 TensorFlow 操作。

例如,下面的例子展示了如何在 TensorFlow 中使用 Python 操作來實現一個簡單的 ReLU 非線性核。

import numpy as np
import tensorflow as tf
import uuiddef relu(inputs):    
# Define the op in python    def _relu(x):        
           return np.maximum(x, 0.)    
             # Define the op's gradient in python    def _relu_grad(x):            return np.float32(x > 0)    
   # An adapter that defines a gradient op compatible with Tensorflow    def _relu_grad_op(op, grad):        x = op.inputs[0]        x_grad = grad * tf.py_func(_relu_grad, [x], tf.float32)            return x_grad    
       # Register the gradient with a unique id    grad_name = "MyReluGrad_" + str(uuid.uuid4())    tf.RegisterGradient(grad_name)(_relu_grad_op)    
   # Override the gradient of the custom op    g = tf.get_default_graph()        with g.gradient_override_map({"PyFunc": grad_name}):        output = tf.py_func(_relu, [inputs], tf.float32)      return output

你可以使用 TensorFlow 的梯度檢查器來驗證梯度是否正確:

x = tf.random_normal([10])y = relu(x * x)with tf.Session():    diff = tf.test.compute_gradient_error(x, [10], y, [10])    
print(diff)

函數 compute_gradient_error() 會計算出梯度的數值,並且返回與給定梯度相比的差別。我所期望的是一個很小的差距。

需要注意的是,這個實現是相當低效的,並且僅對原型開發有用,因為 Python 代碼並不是能夠並行的,也無法在 GPU 上運行。一旦你驗證了自己的思想,你肯定會想著把它寫成一個 c++內核。

在實踐中,我們通常會在 Tensorboard 上使用 Python 操作來實現可視化。假設你在構建一個圖像分類的模型,並且想要在訓練的過程中可視化模型的預測結果。TensorFlow 允許使用 tf.summary.image() 函數來做可視化。

image = tf.placeholder(tf.float32)tf.summary.image("image", image)

但是這僅僅會可視化輸入圖像。為了可視化預測結果,你必須尋求一種能夠做圖像註解的方式,這種方式幾乎在現有的操作中根本就不存在。一種比較容易的方法就是在 Python 中畫圖,然後用 Python 操作將其封裝起來。

import io
import matplotlib.pyplot as plt
import numpy as npimport PIL
import tensorflow as tf
 def visualize_labeled_images(images, labels, max_outputs=3, name='image'):    
 def _visualize_image(image, label):  
   
       # Do the actual drawing in python        fig = plt.figure(figsize=(3, 3), dpi=80)        ax = fig.add_subplot(111)        ax.imshow(image[::-1,...])        ax.text(0, 0, str(label),          horizontalalignment='left',          verticalalignment='top')        fig.canvas.draw()        

       # Write the plot as a memory file.        buf = io.BytesIO()        data = fig.savefig(buf, format='png')        buf.seek(0)  
           # Read the image and convert to numpy array        img = PIL.Image.open(buf)        
       return np.array(img.getdata()).reshape(img.size[0], img.size[1], -1)    
  def _visualize_images(images, labels):  
           
       # Only display the given number of examples in the batch        outputs = []        
       for i in range(max_outputs):            output = _visualize_image(images[i], labels[i])            outputs.append(output)        
       return np.array(outputs, dtype=np.uint8)    
       
  # Run the python op.   figs = tf.py_func(_visualize_images, [images, labels], tf.uint8)    
  return tf.summary.image(name, figs)

要注意,因為這裡的 summary 通常都會隔一段時間才評估一次(並不是每一步都有),所以這個方法是實用的,而不用擔心由此引發的效率問題。

原文地址:https://github.com/vahidk/EffectiveTensorflow 

本文為機器之心編譯,轉載請聯繫本公眾號獲得授權

✄---

加入機器之心(全職記者/實習生):hr@jiqizhixin.com

投稿或尋求報導:editor@jiqizhixin.com

廣告&商務合作:bd@jiqizhixin.com

相關焦點

  • TensorFlow極簡入門教程
    「節點」一般用來表示施加的數學操作,但也可以表示數據輸入的起點和輸出的終點,或者是讀取/寫入持久變量(persistent variable)的終點。邊表示節點之間的輸入/輸出關係。這些數據邊可以傳送維度可動態調整的多維數據數組,即張量(tensor)。
  • TensorFlow圖像分類教程
    直到我們做了這個約20分鐘的訓練,Inception才知道如何識別雛菊和鬱金香,這就是深度學習中的「學習」部分。首先,在所選的平臺上安裝Docker。在很多TensorFlow教程中最先且唯一依賴的就是Docker(應該表明這是個合理的開始)。
  • 【TensorFlow超級指南】你能想到的TF教程和資源都在這裡了
    不論是學習還是從事與機器學習相關的工作,能夠靈活使用TensorFlow可以大幅提高作業效率。本文涵蓋與TensorFlow相關的教程、書籍、工具、求職等的大量信息。盡數資源,應有盡有。因為它會涉及幾個高級概念,例如:什麼是神經元;不同類型的激活函數以及為何要使用Relu;如何通過dropout提高模型的精確度;如何評估模型以及如何調參。
  • TensorFlow發布JavaScript開發者的機器學習框架TensorFlow.js
    發布新的 TensorFlow 官方博客(http://blog.tensorflow.org/)與 TensorFlow YouTube 頻道;2. 面向 JavaScript 開發者的全新機器學習框架 TensorFlow.js;3.
  • TensorFlow極速入門
    一、前言目前,深度學習已經廣泛應用於各個領域,比如圖像識別,圖形定位與檢測,語音識別,機器翻譯等等,對於這個神奇的領域,很多童鞋想要一探究竟,這裡拋磚引玉的簡單介紹下最火的深度學習開源框架 tensorflow。
  • tensorflow極速入門
    一、前言目前,深度學習已經廣泛應用於各個領域,比如圖像識別,圖形定位與檢測,語音識別,機器翻譯等等,對於這個神奇的領域,很多童鞋想要一探究竟,這裡拋磚引玉的簡單介紹下最火的深度學習開源框架 tensorflow。本教程不是 cookbook,所以不會將所有的東西都事無巨細的講到,所有的示例都將使用 python。那麼本篇教程會講到什麼?
  • Tensorflow之 CNN卷積神經網絡的MNIST手寫數字識別
    如果你尚未了解,請查看MNIST For ML Beginners(https://www.tensorflow.org/get_started/mnist/beginners)。在學習教程之前,請確保已經安裝Install TensorFlow(https://www.tensorflow.org/install/)。
  • Tensorflow教程-雙向的LSTM文本分類
    原始碼:https://github.com/PrivateThink/tensorflow_tutorial/blob/master/15.py在Tensorflow今天的教程就是利用雙向的LSTM進行文本分類,單向的LSTM的文本分類可以參考Tensorflow教程-循環神經網絡文本分類。
  • 【官方教程】TensorFlow在圖像識別中的應用
    人類在ImageNet挑戰賽上的表現如何呢?Andrej Karpathy寫了一篇博文來測試他自己的表現。他的top-5 錯誤率是5.1%。這篇教程將會教你如何使用Inception-v3。你將學會如何用Python或者C++把圖像分為1000個類別。我們也會討論如何從模型中提取高層次的特徵,在今後其它視覺任務中可能會用到。
  • 如何從Tensorflow中創建CNN,並在GPU上運行該模型(附代碼)
    在本教程中,您將學習卷積神經網絡(CNN)的架構,如何在Tensorflow中創建CNN,並為圖像標籤提供預測。
  • Anaconda軟體安裝TensorFlow教程
    Anaconda軟體的安裝,請閱讀文章《Anaconda軟體安裝和簡易使用教程》第一步:創建r-tensorflow環境打開Anaconda Prompt,執行命令conda create --name r-tensorflow python=3.6該命令用來創建r-tensorflow虛擬環境
  • 可能是史上最全的Tensorflow學習資源匯總
    的安裝、Tensorflow的語法、基本操作、CNN的一些原理和項目實戰等。2)從Tensorflow基礎知識到有趣的項目應用:https://github.com/pkmital/tensorflow_tutorials同樣是適合新手的教程,從安裝到項目實戰,教你搭建一個屬於自己的神經網絡。
  • Tensorflow基礎入門十大操作總結
    但不少小夥伴跟我吐苦水說Tensorflow的應用太亂了,感覺學的雲裡霧裡,能不能搞個Tensorflow的教程呀。今天,就和大家一起梳理下TensorFlow的十大基礎操作。詳情如下:一、Tensorflow的排序與張量Tensorflow允許用戶把張量操作和功能定義為計算圖。
  • 從框架優缺點說起,這是一份TensorFlow入門極簡教程
    這一系列教程分為 6 部分,從為什麼選擇 TensorFlow 到卷積神經網絡的實現,介紹了初學者所需要的技能。機器之心在本文介紹了 PyTorch 和 Caffe 等深度學習框架的優缺點及 TensorFlow 基礎,包括靜態計算圖、張量、TensorBoard 可視化和模型參數的保存等。
  • TensorFlow 實現流行的機器學習算法的教程匯集
    這是使用 TensorFlow 實現流行的機器學習算法的教程匯集。本匯集的目標是讓讀者可以輕鬆通過案例深入 TensorFlow。這些案例適合那些想要清晰簡明的 TensorFlow 實現案例的初學者。本教程還包含了筆記和帶有註解的代碼。
  • 使用Python+Tensorflow的CNN技術快速識別驗證碼
    一開始學習tensorflow是盲目的,不知如何下手,網上的資料都比較單一,為了回報社會,讓大家少走彎路,我將詳細介紹整個過程。本教程所需要的完整材料,我都會放在這裡。限於個人水平,如有錯誤請指出!接下來我將介紹如何使用Python+Tensorflow的CNN技術快速識別驗證碼。在此之前,介紹我們用到的工具:1.
  • Tensorflow常用函數使用說明及實例簡記
    本文記錄Tensorflow中常用函數功能,方便大家使用時查詢!
  • Tensorflow 基礎入門十大操作總結
    但不少小夥伴跟我吐苦水說Tensorflow的應用太亂了,感覺學的雲裡霧裡,能不能搞個Tensorflow的教程呀。今天,就和大家一起梳理下TensorFlow的十大基礎操作。詳情如下:一、Tensorflow的排序與張量Tensorflow允許用戶把張量操作和功能定義為計算圖。
  • 一文看懂CNN、RNN等7種範例(TensorFlow教程)
    CNN 跨空間共享權重,使貓耳以及其他模式的檢測更加高效。CNN 不是只使用密集連接的層,而是使用卷積層 (卷積編碼器)。這些網絡用於圖像分類、目標檢測、視頻動作識別以及任何在結構上具有一定空間不變性的數據 (如語音音頻)。
  • 教程 | 用TensorFlow Estimator實現文本分類
    )第三部分講解了如何創建一個自定義的評估器(https://developers.googleblog.com/2017/12/creating-custom-estimators-in-tensorflow.html)。