TF2.0 是以前學習的內容,當時是寫在了私有的YNote中,重寫於SF。
TF2.0-GPU 安裝教程傳送門:https://segmentfault.com/a/11...
以前接觸過 TF1, 手動session機制,看着非常頭疼。 TF2.0不須要作這些
TF2.0 理解起來更容易(逐漸 Pythonic and Numpic)
TF2.0 後端採用keras接口 (構建網絡層),更方便。
TF2.0 的keras接口定義的模型層,都實現了 call方法。意味大多數實例對象能夠看成函數來直接調用python
以列表爲例(抽象舉例,摞起來的麪包片。。。。)segmentfault
[ # 最外層,無心義不用記 [1,2,3], # 麪包片1 (第一個樣本) [4,5,6], # 麪包片2 (第二個樣本) ]
以剛纔的數據爲例:後端
t = tf.constant( [ [1., 2., 3.], [4., 5., 6.] ] ) print(tf.reduce_sum(t, axis=0)) # 求和操做,上下壓扁, 聚合樣本 >> tf.Tensor([5. 7. 9.], shape=(3,), dtype=float32) print(tf.reduce_sum(t, axis=1)) # 求和操做,左右壓扁, 聚合屬性 >> tf.Tensor([ 6. 15.], shape=(2,), dtype=float32)
注:Numpy軸也是這樣的,我最初用x,y軸方式 抽象 去記憶, 基本上是記不住的。。太多概念混淆。
但若是你記不住,你每次使用各類操做和聚合API時,都會本身在心理從新花大量時間理一遍。浪費時間。網絡
因此你必定要練習理解,要作到:「瞄一眼,就能知道這種維度的數據的意義,以及軸操做的意義」
我本身的記憶方式(axis=0, axis=1):session
常須要用 axis參數 的相關聚合函數:app
tf.reduce_sum() # 求和 tf.reduce_mean() # 平均值 tf.reduce_max() # 最大值 tf.reduce_min() # 最小值 tf.square() # 平方 tf.concat() # 拼接 注: 若是 axis參數 "不傳", 那麼"全部維度"都會被操做。
# 基本會用到的 import numpy as np import tensorflow as tf from tensorflow import keras # 可選導入 import os, sys, pickle import scipy import pandas as pd import matplotlib.pyplot as plt from sklearn.preprocessing import StandardScaler # 標準化 from sklearn.model_selection import train_test_split # 訓測分離
c = tf.constant( [[1., 2., 3.], [4., 5., 6.]] ) # 數字後面加個點表明 轉float32 類型 print(c) >> tf.Tensor([[1. 2. 3.] [4. 5. 6.]], shape=(2, 3), dtype=float32)
先說矩陣乘法(大學都學過的,運算過程不說了):框架
語法格式: a @ b 條件要求: a的列數 === b的行數 (必須相等) eg: (5行2列 @ 2行10列 = 5行10列) 特例: (第0維度,必須相等) t1 = tf.ones([2, 20, 30]) t2 = tf.ones([2, 30, 50]) print( (t1@t2).shape ) >> (2, 20, 50) # 第0維沒變, 後2維照常按照矩陣乘法運算
矩陣轉置:函數
tf.transpose(t) # 不只能夠普通轉置,還能夠交換維度 t2 = tf.transpose(t,[1,0]) # 行變列,列變行。 和基本的轉置差很少(逆序索引,軸變逆序) # 或假如以 (2,100,200,3)形狀 爲例 t = tf.ones([2, 100, 200, 3]) print(tf.transpose(t, [1, 3, 0, 2]).shape) # 軸交換位置 >> (100, 3, 2, 200) # 原1軸 -> 放在如今0軸 # 原3軸 -> 放在如今1軸 # 原0軸 -> 放在如今2軸 # 原2軸 -> 放在如今3軸
加減乘除都具備"廣播機制" :
形象(廣播機制解釋)解釋:性能
我嘗試用白話解釋下: 1. 我形狀和你不同, 但我和你運算的時候,我會盡力擴張成 你的形狀 來和你運算。 2. 擴張後若是出現空缺, 那麼把本身複製一份,填補上 (若是補不全,就說明不能運算) 3. 小形狀 服從 大形狀 (我比你瘦,我動就行。 你不用動。。。) eg: t = tf.constant( [ [1, 2, 3], [4, 5, 6], ] ) t + [1,2,1] 過程分析: [1,2,1] 顯然是 小形狀, 它會自動嘗試變化成大形狀 -> 第一步變形(最外層大框架知足, 裏面還有空缺): [ [1,2,1], ] 第二步變形 (把本身複製,而後填補空缺): [ [1,2,1], [1,2,1], # 這就是複製的本身 ] 第三步運算(逐位相加) [ + [ = [ [1,2,3], [1,2,1], [2,4,4], [4,5,6], [1,2,1], [5,7,7], ] ] [
抽象(廣播機制)演示:學習
假如 t1 的 shape爲 [5,200,100,50] 假如 t2 的 shape爲 [5,200] 注意:我如下的數據演示,全是表示 Tensor的形狀,形狀,形狀! [5,200,1,50] # 很明顯,開始這2行數據 維度沒匹配, 形狀也沒對齊 [5,1] ------------------------ [5,200,1,50] [5,50] # 這行對齊補50 ------------------------ [5,200,5,50] # 這行對齊補5 [5,50] ------------------------ [5,200,5,50] [1, 1, 5,50] # 這行擴張了2個, 默認填1 ------------------------ [5,200,5,50] [1,200, 5,50] # 這行對齊補200 ------------------------ [5,200,5,50] [5,200,5,50] # 這行對齊補5 注意: 1. 每一個維度形狀:兩者必須有一個是1, 才能對齊。 (否則ERROR,下例ERROR->) [5,200,1,50] [5,20] # 同理開始向右對齊,可是 50與20都不是1,因此都不能對齊,因此ERROR 2. 若維度缺失: 依然是所有貼右對齊 而後先從右面開始,補每一個維度的形狀 而後擴展維度,並默認設形狀爲1 而後補擴展後維度的形狀(由於默認設爲1了,因此是必定能夠補齊的)
固然上面說的都是運算時的自動廣播機制
你也能夠手動廣播:
t1 = tf.ones([2, 20, 1]) # 原始形狀 【2,20,1】 print(tf.broadcast_to(t1, [5,2,20,30]).shape) # 目標形狀【5,2,20,30】 [5,2,20,30] [2,20, 1] ----------- [5,2,20,30] [2,20,30] ----------- [5,2,20,30] [1,2,20,30] ----------- [5,2,20,30] [5,2,20,30] 注:由於是手動廣播,因此只能 原始形狀 本身向 目標形狀 」補充維度,或者補充形狀「 而目標形狀是一點也不能動的。
擴充維度(f.expand_dims)+ 複製(tile) 代替 => 廣播(tf.broadcasting)
一樣是上面的例子,我想把形狀 [2,20,1] ,變成 [5,2,20,30]
t1 = tf.ones([2, 20, 1]) a = tf.expand_dims(t1,axis=0) # 0軸索引處插入一個軸, 結果[1,2,20,1] print(tf.tile(a,[5,1,1,30]).shape) # 結果 [5, 2, 20, 30] 流程: [5,2,20,30] [2,20,1] ----------- [5,2,20,30] # tf.expand_dims(t1,axis=0) [1,2,20,1] # 0號索引插入一個新軸(增維) ----------- [5,2,20,30] # tf.tile(5,1,1,30) (形狀對齊,tile每一個參數表明對應軸的形狀擴充幾倍) [5,2,30,30] 1*5 2*1 20*1 1*30
tile 與 broadcasting的區別:
壓縮維度(tf.squeeze):
就是把每一個維度爲1的維度都刪除掉 (就像數學 a * 1 = a)
print(tf.squeeze(tf.ones([2,1,3,1])).shape) >>> (2, 3)
固然你也能夠指定維度壓縮(默認不指定,全部維度爲1的所有壓縮):
print(tf.squeeze(tf.ones([2,1,3,1]), axis=-1).shape) >>> (2, 1, 3)
靈魂說明:不管索引仍是切片, (行列 是使用 逗號 分隔的), 而且不管行列,索引都是從0開始的。
索引:取一個值
print(t[1,2]) # 逗號前面表明行的索引, 逗號後面是列的索引 >> tf.Tensor(6.0, shape=(), dtype=float32)
切片:取子結構 (有兩種方式)
方式1(冒號切片):
print(t[:, 1:]) # 逗號前面是行。只寫: 表明取全部行。逗號後面是列。 1: 表明第二列到最後 >> tf.Tensor([[2. 3.] [5. 6.]], shape=(2, 2), dtype=float32)
方式2(省略號切片): (我相信不瞭解Numpy的人都沒據說過 python的 Ellipsis , 就是省略號類)
先本身去運行玩玩這行代碼:
print(... is Ellipsis) >>> True
回到正題:(省略號 ... 切片,是針對多維度的, 若是是二維直接用:便可)
(咱們以三維爲例,這個就不適合稱做行列了) # shape 是 (2, 2, 2) t = tf.constant( [ # 一維 [ # 二維 [1, 2], # 三維 [3, 4], ], [ [5, 6], [7, 8], ], ] ) 僞碼:t[1維切片, 二維切片, 三維切片] 代碼:t[:, :, 0:1] # 1維不動, 2維不動, 3維 取一條數據 結果: shape爲 (2,2,1) [ # 一維 [ # 二維 [1], # 三維 [3], ], [ [5], [7], ], ]
看不明白就多看幾遍。
發現沒,即便我不對 1維,和 2維切片,我也被迫要寫 2個: 來佔位
那假若有100個維度,我只想對最後一個維度切片。 前99個都不用動, 那難道我要寫 99個 : 佔位??
不,以下代碼便可解決:
print(t[..., 0:1]) # 這就是 ... 的做用 (注意,只在 numpy 和 tensorflow中有用)
tensor 轉 numpy 類型
t.numpy() # tensor 轉爲 numpy 類型
定義:
v = tf.Variable( # 注意: V是大寫 [ [1, 2, 3], [4, 5, 6] ] )
變量賦值(具備自身賦值的性質):
注意: 變量一旦被定義,形狀就定下來了。 賦值(只能賦給同形狀的值) v.assign( [ [1,1,1], [1,1,1], ] ) print(v) >> <tf.Variable 'Variable:0' shape=(2, 3) dtype=int32, numpy=array([[1, 1, 1],[1, 1, 1]])>
變量取值(至關於轉換爲Tensor):
特別: 變量自己就是 Variable類型, 取值取出得是 Tensor (包括切片取值,索引取值等) print( v.value() ) >> tf.Tensor([[1 2 3] [4 5 6]], shape=(2, 3), dtype=int32)
變量 索引&切片 賦值:
常量:是不可變的。因此只有取值,沒有賦值。 變量:取值、賦值均可以 v.assign(xx) 相似於 python的 v=xx v[0, 1].assign(100) # 索引賦值, v.assign 等價於 v[0, :].assign([10, 20, 30]) # 注意,切片賦值傳遞的須要是容器類型 特別注意: 前面說過,變量 結構形狀 是 不可變的,賦值的賦給的是數據。 可是你賦值的時候要時刻注意,不能改變變量原有形狀 拿切片賦值爲例: 你切多少個,你就得賦多少個。 而且賦的值結構要一致。 舉個栗子: 你從正方體裏面挖出來一個小正方體。那麼你必須填補一塊如出一轍形狀的小正方體) 還有兩種擴展API: v.assign_add() # 相似python 的 += v.assign_sub() # 相似python 的 -=
變量 索引&切片 取值
同 常量切片取值(略)
Variable 轉 Numpy
print(v.numpy())
定義:
rag_tensor = tf.ragged.constant( [ [1,2], [2,3,4,5], ] ) # 容許每一個維度的數據長度良莠不齊
拼接:假如須要"拼接 不規則張量" (可以使用 tf.concat(axis=) )
0軸:豎着拼接(樣本豎着摞起來)可隨意拼接。 拼接後的依然是"不規則張量" 1軸:橫着拼接(屬性水平拼起來)這時候須要你樣本個數必須相等, 不然對不上,報錯 總結: 樣本豎着隨便拼, 屬性橫着(必須樣本個數相等) 才能拼
RaggedTensor 普通 Tensor:
說明:普通Tensor是必需要求, 長度對齊的。入 對不齊的 末尾補0 tensor = rag_tensor.to_tensor()
特色(可理解爲 記錄索引):
定義:
s = tf.SparseTensor( indices=[[0, 1], [1, 0], [2, 3]], # 注意,這個索引設置須要是(從左到右,從上到下)的順序設置 values=[1, 2, 3], # 將上面3個座標值分別設值爲 1,2,3 dense_shape=[3, 4] # Tensor總範圍 ) print(s) >> SparseTensor(indices=tf.Tensor([[0 1], [1 0],[2 3]], shape=(3, 2), dtype=int64)。。。
轉爲普通 Tensor (轉爲普通Tensor後,看見的纔是存儲真正的值)
tensor = tf.sparse.to_dense(s) print(tensor) >> tf.Tensor([ [0 1 0 0],[2 0 0 0],[0 0 0 3] ], shape=(3, 4), dtype=int32)
若是上面使用 to_dense() 可能會遇到錯誤:
error: is out of range 這個錯誤的緣由是建立 tf.SparseTensor(indices=) ,前面也說了indices,要按(從左到右,從上到下)順序寫 固然你也能夠用排序API,先排序,而後再轉: eg: _ = tf.sparse.reorder(s) # 先將索引排序 tensor = tf.sparse.to_dense(_) # 再轉
這個API做爲一個裝飾器使用, 用來將 Python 語法轉換 儘量有效的轉換爲 TF語法、圖結構
import tensorflow as tf import numpy as np @tf.function def f(): a = np.array([1, 2, 3]) b = np.array([4, 5, 6]) return a + b print( f() ) >>> tf.Tensor([5 7 9], shape=(3,), dtype=int32)
你應該發現了一個特色,咱們定義的 f()函數內部,一個tf語法都沒寫。 只裝飾了一行 @tf.function
而調用結果返回值竟然是個 tensor 。
這就是 @tf.function 裝飾器的做用了!
固然函數裏面,也能夠寫 tf的操做,也是沒問題的。
但注意一點, 函數裏面不容許定義 變量, 須要定義的變量 應 拿到函數外面定義
a = tf.Variable([1,2,3]) # 如需tensor變量,應該放在外面 @tf.function def f(): # a = tf.Variable([1,2,3]) # 這裏面不容許定義變量! pass
個人理解就是(基本數學的 合併同類項)
# 合併同類項的原則就是有1項不一樣,其餘項徹底相同。 # 前提條件:(最多,有一個維度的形狀不相等。 注意是最多) t1 = tf.ones([2,5,6]) t2 = tf.ones([6,5,6]) print( tf.concat([t1,t2],axis=0).shape ) # axis=0,傳了0軸,那麼其餘軸捏死不變。只合並0軸 >> (8,5,8)
個人理解就是(小學算術的,進位,(進位就是擴充一個維度表示個數))
# 前提條件:全部維度形狀必須所有相等。 tf1 = tf.ones([2,3,4]) tf2 = tf.ones([2,3,4]) tf3 = tf.ones([2,3,4]) print(tf.stack([tf1,tf2,tf3], axis=0).shape) # 你能夠想象有3組 [2,3,4],而後3組做爲一個新維度,插入到 axis對應的索引處。 >> (3, 2, 3, 4) # 對比理解,若是這是tf.concat(), 那麼結果就是 (6,3,4)
和tf.stack正好是互逆過程,指定axis維度是幾,它就會拆分紅幾個數據,同時降維。
a = tf.ones([3, 2, 3, 4]) for x in tf.unstack(a, axis=0): print(x.shape) 結果以下(分紅了3個 [2,3,4]) >>> (2, 3, 4) >>> (2, 3, 4) >>> (2, 3, 4)
和tf.unstack的區別就是,tf.unstack是均分降維, tf.stack是怎麼分都不會降維,且能指定分隔份數
a = tf.ones([2,4,35,8]) for x in tf.split(a, axis=3,num_or_size_splits=[2,2,4]): print(x.shape) 結果: >> (2, 4, 35, 2) # 最後一維2 >> (2, 4, 35, 2) # 最後一維2 >> (2, 4, 35, 4) # 最後一維4
假如咱們想切分數據集爲(train-test-valid) 3分比例爲 6:2:2
方法1:(scikit-learn 連續分切2次)
x_train, x_test, y_train, y_test = train_test_split(x,y,test_size=0.2) x_train, x_valid, y_train, y_valid = train_test_split(x_train, y_train,test_size=0.2) # 源碼中顯示 test_size若是不傳。默認爲0.25。 # 思路,由於 scikit-learn 只能切出2個結果: 因此咱們須要切2次: # 第一次 從完整訓練集 切出來 (剩餘訓練集, 測試集) # 第二次 從剩餘數據集 切出來 (剩餘訓練集2, 驗證集)
方法2: (tf.split)
x = tf.ones([1000, 5000]) y = tf.ones([1000, 1]) x_train, x_test, x_valid = tf.split( x, num_or_size_splits=[600,200,200], # 切3份 axis=0 ) y_train, y_test, y_valid = tf.split( y, num_or_size_splits=[600,200,200], # 一樣切3份 axis=0 ) print(x_train.shape, y_train.shape) print(x_test.shape, y_test.shape) print(x_valid.shape, y_valid.shape) 結果 >>> (600, 5000) (600, 1) >>> (200, 5000) (200, 1) >>> (200, 5000) (200, 1)
numpy這種索引叫作 fancy indexing(若是我沒記錯的話)
data = tf.constant([6,7,8]) # 看成真實數據 index = tf.constant([2, 1, 0]) # 看成索引 print(tf.gather(data, index)) >> tf.Tensor([8 7 6], shape=(3,), dtype=int32)
data = tf.constant([6, 7, 8]) print(tf.sort(data, direction='DESCENDING')) # 'ASCENDING' # 默認是ASCENDING升序 tf.argsort() # 同上, 只不過返回的是排序後的,對應數據的index
查找出最大的n個(比先排序而後切片的性能要好)
a = tf.math.top_k([6,7,8],2) # 找出最大的兩個,返回是個對象 print(a.indices) # 取出最大的兩個 索引 () print(a.values) # 取出最大的兩個 值 >> tf.Tensor([2 1], shape=(2,), dtype=int32) >> tf.Tensor([8 7], shape=(2,), dtype=int32)
v1, v2 = tf.Variable(1.), tf.Variable(2.) # 變量 會 被自動偵測更新的 c1, c2 = tf.constant(1.), tf.constant(2.) # 常量 不會 自動偵測更新 y = lambda x1,x2: x1**2 + x2**2 with tf.GradientTape(persistent=True) as tape: """默認這個 tape使用一次就會被刪除, persistent=True 表明永久存在,但後續須要手動釋放""" # 由於常量不會被自動偵測,因此咱們須要手動調用 watch() 偵測 tape.watch(c1) # 若是是變量,就不用watch這兩步了 tape.watch(c2) f = y(c1,c2) # 調用函數,返回結果 c1_, c2_ = tape.gradient(f, [c1,c2]) # 參數2:傳遞幾個自變量,就會返回幾個 偏導結果 # c1_ 爲 c1的偏導 # c2_ 爲 c2的偏導 del tape # 手動釋放 tape
v1, v2 = tf.Variable(1.), tf.Variable(2.) # 咱們使用變量 y = lambda x1,x2: x1**2 + x2**2 with tf.GradientTape(persistent=True) as tape2: with tf.GradientTape(persistent=True) as tape1: f = y(v1,v2) once_grads = tape1.gradient(f, [v1, v2]) # 一階偏導 # 此列表推導式表示:拿着一階偏導,來繼續求二階偏導(注意,用tape2) twice_grads = [tape2.gradient(once_grad, [v1,v2]) for once_grad in once_grads] # 二階偏導 print(twice_grads) del tape1 # 釋放 del tape2 # 釋放
求導數(一個自變量):tape1.gradient(f, v1) # gradient傳 1個自變量 求偏導(多個自變量):tape1.gradient(f, [v1,v2]) # gradient傳 1個列表, 列表內填全部自變量
方式1:手撕(不使用優化器)
v1, v2 = tf.Variable(1.), tf.Variable(2.) # 咱們使用變量 y = lambda x1, x2: x1 ** 2 + x2 ** 2 # 二元二次方程 learning_rate = 0.1 # 學習率 for _ in range(30): # 迭代次數 with tf.GradientTape() as tape: # 求導做用域 f = y(v1,v2) d1, d2 = tape.gradient(f, [v1,v2]) # 求導, d1爲 v1的偏導, d2爲v2的偏導 v1.assign_sub(learning_rate * d1) v2.assign_sub(learning_rate * d2) print(v1) print(v2) 實現流程總結: 1. 偏導 自變量v1,v2求出來的。 (d1, d2 = tape.gradient(f, [v1,v2])) 2. 自變量v1,v2的衰減 是關聯 偏導的( 衰減值 = 學習率*偏導) 3. 咱們把前2步套了一個大循環(並設定迭代次數), 1-2-1-2-1-2-1-2-1-2 步驟往復執行
方式2:借用 Tensorflow 優化器(optimizer) 實現梯度降低
v1, v2 = tf.Variable(1.), tf.Variable(2.) # 咱們使用變量 y = lambda x1, x2: x1 ** 2 + x2 ** 2 # 二元二次函數 , 一般這個函數咱們用做計算loss learning_rate = 0.1 # 學習率 optimizer = keras.optimizers.SGD(learning_rate=learning_rate) # 初始化優化器 for _ in range(30): # 迭代次數 with tf.GradientTape() as tape: f = y(v1,v2) d1, d2 = tape.gradient(f, [v1,v2]) # d1爲 v1的偏導, d2爲v2的偏導 optimizer.apply_gradients( # 注意這裏不同了,咱們以前手動衰減 [ # 而如今這些事情, optimizer.SGD幫咱們作了 (d1, v1), # 咱們只需把偏導值,和自變量按這種格式傳給它便可 (d2, v2), ] ) # 一般這種格式,咱們用 zip() 實現 # eg: # model = keras.models.Sequential([......]) # ....... # grads = tape.gradient(f, [v1,v2]) # optimizer.apply_gradients( # zip(grads, model.trainable_variables) # ) print(v1) print(v2) 實現流程總結: 1. 偏導 是自變量v1,v2求出來的 (d1, d2 = tape.gradient(f, [v1,v2])) # 此步驟不變 2. 把偏導 和 自變量 傳給optimizer.apply_gradients() optimizer.SGD() 自動幫咱們衰減。 3. 咱們仍是把前2步套了一個大循環(並設定迭代次數), 1-2-1-2-1-2-1-2-1-2 步驟往復執行。 注: 假如你用adam等之類的其餘優化器,那麼可能有更復雜的公式,若是咱們手撕,肯能有些費勁。 這時候咱們最好使用 optimizer.Adam ...等各類 成品,優化器。通用步驟以下 1. 先實例化出一個優化器對象 2. 實例化對象.apply_gradients([(偏導,自變量)])