出處:https://yuerblog.cc/2020/12/14/tensorflow-%E9%80%8F%E8%BF%87%E6%95%B0%E5%AD%A6%E7%90%86%E8%A7%A3model/
本文透過一個tensorflow例子,通俗的說明神經網路是如何工作的,以便我們更自信的駕馭它。
什麼是神經網路?神經網路就是一個數學問題,無論我們的網路結構多麼複雜,它仍舊可以透過一個y與x之間的很複雜的數學公式進行表達。
當我們看到一張神經網路圖結構時,
我們可以堅信一件事情:
當我們輸入x時,一定可以經過某個數學表示式得到y,其數學形式可以高度簡化為:y=f1(f2(f3(f4(x)))),巢狀函式越多則網路越深。
這裡f1,f2,f3,f4代表了4組數學變換,具體是y= w x+ b 還是y= w log(x)還是y=x^2+ b ,這就取決於各種模型理論的發明了,我們完全不必展開。
當神經網路輸出y之後,我們可以計算與真實y之間的誤差(loss),此時誤差函式表達為:
loss = y’ – f1(f2(f3(f4(x))))
此時我們會發現loss比較大,證明模型擬合樣本不是很好,怎麼最佳化呢?
在相同的輸入x情況下,要想要loss值變小,只能調整f1()、f2()、f3()、f4()這些數學變換中的權重係數(例如:上面的w和b)。
每個係數調整多少呢?變大還是變小呢?如果隨機碰運氣那就訓練不出什麼模型了。
所以出現了梯度下降調整權重係數的方案:
為了讓loss可以在當前輸入的x情況下更小,我們可以將x視為常量,將f1中某個權重係數w視為變數,對loss函式求關於該w係數的導數,這樣就可以明確w係數如何調整才能讓loss函式影象向更低的位置移動,這就是梯度下降的原理。
loss函式本身是數學公式表達的,因此對loss求各個係數的導數也是數學公式推導問題,因此網路中所有權重係數均可以得到調整,即向樣本進一步擬合。
經過反覆用不同的x輸入到模型,進行loss計算與所有權重的梯度下降,模型整體上會不斷的更好擬合訓練樣本,達到一種”全域性最優”。
以tensorflow為例我們藉由tensorflow的functional api構造模型,以此來幫助到工作實踐。
(程式碼地址: https://github.com/owenliang/tf-graph-explore/blob/main/tf-graph-explore.ipynb )
我實現了一個簡單的”雙塔”網路結構:
圖中的input、dense1、dense2、concat、dense3共5個layer,也就是我們之前說的f1()、f2()、f3()等數學函式…
輸入層接受輸入x,其形狀為2列的向量:
inputs=tf.keras.Input(shape=(2,),dtype=tf.float32,name=’input’)
定義網路要使用的3個全連線層:
dense1=tf.keras.layers.Dense(1,activation=None,name=’dense1′) dense2=tf.keras.layers.Dense(2,activation=None,name=’dense2′) dense3=tf.keras.layers.Dense(1,activation=None,name=’dense3′)
三個神經元均不使用啟用函式,其中dense1有1個神經元、dense2有2個神經元、dense3有1個神經元,神經元的個數決定了輸出向量的維度:
dense層的數學公式都是y=wx+b,不同神經元個數的區別是w和b係數的形狀。dense1有1個神經元,假設w=[ [0.3], [0.4] ],b=0.2,輸出y長相為[0.24]dense2有2個神經元,假設w=[ [0.3, 0.6], [0.4, 0.12]],b=[0.1, 0.35],輸出y長相為[0.1, 0.2]w的行數與x的列數相等,w的列數與神經元的個數相等,這些w和b矩陣的初始化都由tensorflow自行判斷生成。
然後我們按網路結構連線這些layer:
outputs1=dense1(inputs) outputs2=dense2(inputs) outputs3=tf.keras.layers.concatenate([outputs1,outputs2],axis=1,name=’concat’) outputs4=dense3(outputs3)
這裡將dense1和dense2的輸出向量進行了concatenate連線,這裡讓人困惑的在於向量連線似乎並不能用數學符號表達,這似乎違背了神經網路是數學問題的事實,但其實連線2個向量是可以用數學公式表達的:
這裡dense1輸出是1維向量,dense2輸出的2維向量,連線後應該是3維向量。
在數學中只需要準備一個長度為3的全1向量K,讓它的第1維與dense1向量做乘法,另外2維與dense2向量做乘法,最後加起來就連線起來的效果了,總之是數學可表達的,那麼就不會影響我們對神經網路的認知。
接下來可以定義出model:
model=tf.keras.Model(inputs=inputs,outputs=outputs4)model.summary()
檢視到所有layer:
Model: "functional_1"__________________________________________________________________________________________________Layer (type) Output Shape Param # Connected to ==================================================================================================input (InputLayer) [(None, 2)] 0 __________________________________________________________________________________________________dense1 (Dense) (None, 1) 3 input[0][0] __________________________________________________________________________________________________dense2 (Dense) (None, 2) 6 input[0][0] __________________________________________________________________________________________________concat (Concatenate) (None, 3) 0 dense1[0][0] dense2[0][0] __________________________________________________________________________________________________dense3 (Dense) (None, 1) 4 concat[0][0] ==================================================================================================Total params: 13Trainable params: 13Non-trainable params: 0
我們關注一下每一層的權重引數個數params,可以看到dense1有3個引數需要學習,也就是w帶來的2個和b帶來的1個;dense2則是w帶來的4個和b帶來的2個;
concat層沒有需要學習的引數,因為K就是一個長度為3的[1,1,1]常量向量。
畫出網路結構會更加清晰(圖片在上面已經貼過了):
tf.keras.utils.plot_model(model, “graph.png”, show_shapes=True)
我們隨機生成10個樣本,x是2維的,y是1維的:
x=tf.random.normal(shape=(10,2)) y=tf.random.normal(shape=(10,1)) print(x) print(y)tf.Tensor( [[-0.8464397 -0.3152412 ] [ 0.9817092 -0.57270414] [ 0.86039394 0.57590604] [-1.5055276 0.45981622] [ 1.40179 1.0307338 ] [ 0.5882102 2.671993 ] [ 0.5666892 -0.33787787] [ 0.36999676 0.5678155 ] [ 2.131917 0.33147094] [-0.23225114 0.84211487]], shape=(10, 2), dtype=float32) tf.Tensor( [[-0.9018226 ] [-0.83541167] [-0.70780784] [ 0.43620512] [-1.2712636 ] [ 0.39236164] [ 0.11044435] [ 2.7505376 ] [ 0.64985305] [-1.4352192 ]], shape=(10, 1), dtype=float32)
然後向model輸入10行樣本x,計算返回10個y:
pred_y = model(data) print(pred_y)tf.Tensor( [[ 1.4824426 ] [ 0.5423604 ] [-1.0151732 ] [ 2.2172787 ] [-1.9427378 ] [-1.8751484 ] [ 2.486527 ] [ 0.03885526] [-0.62673503] [ 0.23725389]], shape=(10, 1), dtype=float32)
為了最佳化模型,我們需要計算pred_y與y之間的誤差loss,並對loss函式在當前x輸入的情況下對各個權重係數進行梯度求導:
loss_f = tf.keras.losses.MeanAbsoluteError()with tf.GradientTape() as tape: pred_y = model(data) # 模型計算 loss = loss_f(y, pred_y) # 計算誤差 print(loss) # 列印損失grads=tape.gradient(loss, model.trainable_variables) # 反向傳播誤差到各層, 對各權重係數求梯度print(grads)
我們將整個loss函式的完整數學表示式(計算過程)用tape錄製下來,這樣tensorflow可以自動幫我們求出所有各個權重係數的導數,grads是網路中所有權重引數的梯度:
tf.Tensor(1.6826286, shape=(), dtype=float32) [<tf.Tensor: shape=(2, 1), dtype=float32, numpy= array([[ 0.37924558], [-0.17607355]], dtype=float32)>, <tf.Tensor: shape=(1,), dtype=float32, numpy=array([0.], dtype=float32)>, <tf.Tensor: shape=(2, 2), dtype=float32, numpy= array([[-0.28494048, 1.0743711 ], [ 0.13229021, -0.4988016 ]], dtype=float32)>, <tf.Tensor: shape=(2,), dtype=float32, numpy=array([0., 0.], dtype=float32)>, <tf.Tensor: shape=(3, 1), dtype=float32, numpy= array([[ 0.02184648], [ 1.1978055 ], [-0.9128995 ]], dtype=float32)>, <tf.Tensor: shape=(1,), dtype=float32, numpy=array([0.], dtype=float32)>]
列印當前Loss是1.6826286。
列印grads是按某種順序排列好的模型中的各個權重的導數,稍後將這些導數應用到對應的權重引數上即可。
optimizer = tf.keras.optimizers.Adam(learning_rate=0.01)
optimizer.apply_gradients(zip(grads, model.trainable_variables)) # 將梯度應用到各個權重係數
利用zip將各個權重引數的梯度與對應的權重引數繫結到一起,然後交給optimizer完成最終的修改,此時模型已經得到了一輪最佳化,理論上更加擬合訓練資料,為此我們重新進行一次預測:
pred_y = model(data) # 使用學習後的模型再預測 print(pred_y)tf.Tensor( [[ 1.4320835 ] [ 0.53110886] [-0.9584385 ]他們說是 [ 2.1270993 ] [-1.888143 ] [-1.8089024 ] [ 2.3986485 ] [ 0.03384402] [-0.5806757 ] [ 0.22927463]], shape=(10, 1), dtype=float32)
看一下現在的loss是否更小了:
loss = loss_f(y, pred_y) print(loss)tf.Tensor(1.6360016, shape=(), dtype=float32)
最佳化過一次的模型loss為1.6360016,比之前的1.6826286要小,說明最佳化過程有效。
出處:https://yuerblog.cc/2020/12/14/tensorflow-%E9%80%8F%E8%BF%87%E6%95%B0%E5%AD%A6%E7%90%86%E8%A7%A3model/