因爲再後面一些分享的章節的內容很多是基於經典論文的復現了,裏面會牽扯到很多自定義的模型及其變換。而這些內容有些是我們的Keras API 無法完成的,例如Resnet的residual block。因此這一節課我們有必要去學習一些基礎、底層的張量、微分運算操作以及明白如何去自定義我們的層。
首先我們要知道我們在前面幾個章節所實踐的內容格式都是張量形式的。例如,一張圖片在Tensorflow模型中表現的形式就是張量。那麼什麼是張量?它是一個數據容器,通俗來說就是矩陣向任意維度的推廣。1維的張量其實就向量,而2維的張量則是我們經常使用的矩陣。在Tensorflow2.0,張量(Tensor)運算可以被GPU或TPU加速,減少我們的運行時間。
一.Tensor(張量)的基礎操作
1.導入相關庫。
from __future__ import absolute_import,division,print_function
import tensorflow as tf
import numpy as np
2.一些基礎操作。
print(tf.add(1, 2)) #數值相加
print(tf.add([1,2],[3,4])) #一維向量相加
print(tf.square(5)) #數值求平方
print(tf.reduce_sum([1,2,3])) #全部數相加
x = tf.matmul([[1]],[[2,3]]) #矩陣相乘
print(x)
3.查看向量尺寸、類型。
print(x.shape)
print(x.dtype)
4.Tensor和numpy的ndarray進行轉換。在使用tensorflow的tensor運算時,如果有遇到ndarray格式的數據,它會默認將它轉爲Tensor的形式進行運算。同樣地,如果在使用numpy進行運算時,碰到tensor,它也會自動將其轉爲ndarray進行運算。
ndarry = np.ones([3,3]) #3x3的ndarray格式的單位矩陣
tensor = tf.multiply(ndarry,2)
print(tensor)
print(np.add(tensor,1))
二.Tensor(張量)的自動微分
1.我們可以使用 GradientTape()函數進行自動微分操作求梯度。其中watch函數的作用是跟蹤變量,而assert函數則是判斷該語句是否有異常。如果語句結果不等,則會報錯。我們可以看到我們用gradient函數對z求了微分。而z的函數爲y^2,對y求導則是2y,因爲y的值又等於x值的和(4),因此最後結果等於2x4=8。
x = tf.ones((2, 2))
with tf.GradientTape() as t:
t.watch(x) #跟蹤變量
y = tf.reduce_sum(x)
z = tf.multiply(y, y)
dz_dy = t.gradient(z, y)
assert dz_dy.numpy() == 8.0
2.因爲使用了python的with...as函數語句,因此在第一次gradient之後,GradientTape的資源就會釋放了。那如果我們要進行兩次gradient操作,要怎麼辦?我們可以通過在GradientTape後加一個persistent(持續)參數來解決,並最後再用del語句釋放資源。
x = tf.constant(3.0) #常數
with tf.GradientTape(persistent=True) as t: #資源不釋放
t.watch(x) #跟蹤變量
y = x*x
z = y*y
dz_dx = t.gradient(z,x) # z=x^4,求導後爲4x^3,x代入則爲4*3*3*3=108
dy_dx = t.gradient(y,x) # y = x^2,求導後爲2x,x代入則爲2*3=6
print(dz_dx)
print(dy_dx)
del t #釋放資源
3.所求的梯度還可以進行二次微分。
x = tf.Variable(1.0)
with tf.GradientTape() as t:
with tf.GradientTape() as t2:
y = x*x*x
dy_dx = t2.gradient(y,x) # y=x^3,求導爲3x^2,x代入則爲3
d2y_dx2 = t.gradient(dy_dx,x) #dy_dx爲3x^2,求導爲6x,x代入則爲6
assert dy_dx.numpy() == 3.0
assert d2y_dx2.numpy() == 6.0
三.自定義層
1.這裏以一個自定義層的例子來解釋怎麼自定義我們所需要的層。首先我們需要定義一個類,相關語法可以參照python類語法。其中類中傳入的函數是繼承了keras的layers層函數,__init__層則是傳入一些我們需要的參數,這些參數也是我們在調用時必須傳入的。而build函數則規定了一些層輸入的尺寸大小。call函數則是前向傳播過程。
class MyDenselayer(tf.keras.layers.Layer):
def __init__(self,num_outputs):
super(MyDenselayer,self).__init__()
self.num_outputs = num_outputs
def build(self,input_shape):
self.kernel = self.add_variable('kernel',shape=[int(input_shape[-1]),self.num_outputs])
def call(self,input):
return tf.matmul(input,self.kernel)
layer = MyDenseLayer(10)
以上就是本節的內容。更多相關的操作和應用,我會在後面的系列內容中體現出來,方便大家更進一步理解。謝謝你們的觀看和支持!