在 TensorFlow 之中自定義網絡層與模型
在上一節之中,我們學習了如何進行微分操作以及梯度的求導。那么這節課我們便開始自定義的下一步 —— 自定義網絡層與模型。
這節課主要分為兩個大部分進行講解,它們分別是:
- 自定義網絡層;
- 自定義模型。
我們會分別對這兩個方面進行講解。而網絡層是模型的基礎,因此我們會對網絡層進行著重的講解。
1. 如何自定義網絡層
在 TensorFlow 之中,我們目前一般采用創建 keras 層的方式來進行網絡層的構建,因此,我們的創建步驟大致分為以下幾步:
- 定義網絡層的類并繼承 tf.keras.layers.Layer 類;
- 在初始化方法或者 build 方法之中定義網絡的參數;
- 在 call 函數之中編寫具體的處理流程。
在第二步之中,我們可以在初始化或者 build 方法之中定義網絡的參數,兩者的效果在目前的教程之中時一樣的,因此為了簡單起見,我們統一在初始化函數之中定義我們的網絡參數。
以下是一個簡單的例子:
import tensorflow as tf
class MyLayer(tf.keras.layers.Layer):
def __init__(self, hidden_units, input_units):
super(MyLayer, self).__init__()
self.w = self.add_weight(shape=(input_units, hidden_units), initializer="random_normal")
self.b = self.add_weight(shape=(hidden_units,), initializer="random_normal")
def call(self, inputs):
return tf.matmul(tf.matmul(inputs, inputs), self.w) + self.b
在這個例子之中,我們定義了一個網絡層,該網絡層的數學形式為:
y = w*(x**2) + b
我們讓 x 進行平方之后乘上一個系數 w ,然后加上一個常數項 b 。
通過代碼我們可以發現,我們在初始化函數之中進行了參數的初始化操作,然后再在 call 方式之中進行了具體的計算。
對于添加參數,我們最常用的方式是采用 add_weight 方法來進行的;該方法最常用的參數有兩個:
- shape: 表示該參數的形狀,比如 3x3 等;
- initializer: 初始化器,一般為 “zeros”(初始化為 0),或者 “random_normal”(隨機初始化)。
我們可以通過具體的數據進行查看:
x = tf.ones((5, 5))
my_layer = MyLayer(10, 5)
y = my_layer(x)
print(y)
輸出為:
tf.Tensor(
[[ 0.5975663 0.46636996 -0.4837241 0.3024946 0.33699942 0.1420103
0.07284987 -0.5888218 0.13172552 0.01993698]
[ 0.5975663 0.46636996 -0.4837241 0.3024946 0.33699942 0.1420103
0.07284987 -0.5888218 0.13172552 0.01993698]
[ 0.5975663 0.46636996 -0.4837241 0.3024946 0.33699942 0.1420103
0.07284987 -0.5888218 0.13172552 0.01993698]
[ 0.5975663 0.46636996 -0.4837241 0.3024946 0.33699942 0.1420103
0.07284987 -0.5888218 0.13172552 0.01993698]
[ 0.5975663 0.46636996 -0.4837241 0.3024946 0.33699942 0.1420103
0.07284987 -0.5888218 0.13172552 0.01993698]], shape=(5, 10), dtype=float32)
因此我們可以發現我們的網絡成功的得到了輸出。但是因為我們是隨機初始化的參數,因此輸出一定會是雜亂無章的。
2. 網絡層中的固定參數
在上面的例子中,我們學習到了如何對自己的網絡層添加參數,但是我們會發現,我們剛剛添加的參數都是可以進行訓練的參數,那么如何添加不可訓練的參數呢?
我們可以在添加參數的方法之中添加屬性 trainable:
self.b = self.add_weight(shape=(hidden_units,), initializer="random_normal", trainable=False)
通過 trainable 屬性,我們可以將該參數設置為不可訓練的參數,也就是說,在以后的訓練過程之中,該參數不會改變,而是一直保持不變的狀態。
我們可以通過以下代碼進行實踐:
class MyLayer2(tf.keras.layers.Layer):
def __init__(self, hidden_units, input_units):
super(MyLayer2, self).__init__()
self.w = self.add_weight(shape=(input_units, hidden_units), initializer="random_normal")
self b = self.add_weight(shape=(hidden_units,), initializer="random_normal", trainable=False)
def call(self, inputs):
return tf.matmul(tf.matmul(inputs, inputs), self.w) + self.b
my_layer2 = MyLayer2(10, 5)
print(len(my_layer.trainable_weights))
print(len(my_layer2.trainable_weights))
輸出為:
2
1
在該程序之中,我們定義了兩個模型,一個模型的 b 可訓練,另一個模型的 b 不可訓練,于是我們可以發現,兩個模型的可訓練參數不一樣,說明我們的屬性起到了相應的效果。
3. 決定網絡是否進行訓練
我們的網絡在使用的時候包括兩個情景:
- 訓練的情景,需要我們進行參數的調整等;
- 測試的情景或其他情景,固定參數,只進行輸出。
在大多數時間,我們都需要在訓練或者測試的過程之中做一些額外的操作。
而我們的網絡是默認進行訓練的,那么如何才能將我們的網絡調整為測試狀態或是訓練狀態呢?答案就是 call 方法的參數:training。
如以下示例:
class MyLayer2(tf.keras.layers.Layer):
def __init__(self, hidden_units, input_units):
super(MyLayer2, self).__init__()
self.w = self.add_weight(shape=(input_units, hidden_units), initializer="random_normal")
self.b = self.add_weight(shape=(hidden_units,), initializer="random_normal", trainable=False)
def call(self, inputs, training=True):
if training:
self.b = self.b * 2
# ...... Other Operations
return tf.matmul(tf.matmul(inputs, inputs), self.w) + self.b
我們在 call 之中定義了 training 參數,從而可以根據是否進行訓練進行額外的操作,我們可以通過如下方式來具體使用:
my_layer2 = MyLayer2(10, 5)
y = my_layer2(x, traing=True)
y = my_layer2(x, traing=False)
于是我們在第一調用的時候進行訓練,而第二次調用的時候不進行訓練。
4. 網絡層的嵌套使用
在網絡層的使用之中,我們可能會遇到網絡層嵌套使用的情況。而且 TensorFlow 也可以支持網絡層的嵌套使用。
比如以下代碼:
class MyLayer(tf.keras.layers.Layer):
def __init__(self, hidden_units, input_units):
super(MyLayer, self).__init__()
self.w = self.add_weight(shape=(input_units, hidden_units), initializer="random_normal")
self.b = self.add_weight(shape=(hidden_units,), initializer="random_normal")
def call(self, inputs):
return tf.matmul(tf.matmul(inputs, inputs), self.w) + self.b
class MyLayer2(tf.keras.layers.Layer):
def __init__(self, hidden_units, input_units):
super(MyLayer2, self).__init__()
self.w = self.add_weight(shape=(input_units, hidden_units), initializer="random_normal")
self.b = self.add_weight(shape=(hidden_units,), initializer="random_normal", trainable=False)
def call(self, inputs, training=True):
return tf.matmul(inputs, self.w) + self.b
class MyLayer3(tf.keras.layers.Layer):
def __init__(self):
super(MyLayer3, self).__init__()
self.l1 = MyLayer(10, 5)
self.l2 = MyLayer2(5, 10)
def call(self, inputs, training=True):
x = self.l1(inputs)
y = self.l2(x)
return y
在這個網絡層之中,我們在前面重新定義了兩個網絡層類,并在后面我們嵌套了我們之前的兩個網絡層,我們通過順序調用來實現了一個新的網絡層的操作。
我們可以通過具體的數據進行測試:
x = tf.ones((5, 5))
my_layer = MyLayer3(10, 5)
y = my_layer(x)
print(y)
我們可以得到輸出:
tf.Tensor(
[[ 0.00422265 0.02767846 0.04585129 0.10204907 -0.08051172]
[ 0.00422265 0.02767846 0.04585129 0.10204907 -0.08051172]
[ 0.00422265 0.02767846 0.04585129 0.10204907 -0.08051172]
[ 0.00422265 0.02767846 0.04585129 0.10204907 -0.08051172]
[ 0.00422265 0.02767846 0.04585129 0.10204907 -0.08051172]], shape=(5, 5), dtype=float32)
可以發現,我們的程序成功地運行了相應的數據,并產生了結果。
5. 自定義模型
既然了解了如何自定義網絡層,那么便要知道如何自定義網絡模型,其實網絡模型是由網絡層構成的,因此只要將網絡層定義清楚,那么網絡模型便可以很輕松得到了。
網絡模型的構建大致也分為以下幾步:
- 自定義模型類,并繼承自 tf.keras.Model 類;
- 在初始化函數之中定義要用到的網絡層;
- 在 call 函數之中定義具體的處理方式。
于是,借用上面的網絡層,我們可以定義我們的網絡模型:
class MyModel(tf.keras.Model):
def __init__(self):
super(MyModel, self).__init__()
self.l1 = MyLayer(10, 5)
self.l2 = MyLayer2(5, 10)
def call(self, inputs, training=True):
x = self.l1(inputs)
y = self.l2(x)
return y
我們的網絡模型使用了 MyLayer 和 MyLayer2 兩個網絡層,并且在 call 函數之中進行了順序處理,從而得到最后的結果。
我們可以通過測試:
x = tf.ones((5, 5))
model = MyModel()
y = model(x)
print(y)
得到輸出:
tf.Tensor(
[[ 0.07020754 -0.14177878 -0.00841531 -0.0398875 0.14821295]
[ 0.07020754 -0.14177878 -0.00841531 -0.0398875 0.14821295]
[ 0.07020754 -0.14177878 -0.00841531 -0.0398875 0.14821295]
[ 0.07020754 -0.14177878 -0.00841531 -0.0398875 0.14821295]
[ 0.07020754 -0.14177878 -0.00841531 -0.0398875 0.14821295]], shape=(5, 5), dtype=float32)
我們發現我們的網絡模型也是很正確的進行了輸出。
6. 小結
在這節課之中,我們學習了如何自定義網絡層,如何指定網絡層參數是否訓練,如何進行運行模式的指定以及如何進行網絡層嵌套等,最后我們又學習了如何進行自定義網絡的構建。