在 TensorFlow 之中自定義網(wǎng)絡(luò)層與模型
在上一節(jié)之中,我們學(xué)習(xí)了如何進(jìn)行微分操作以及梯度的求導(dǎo)。那么這節(jié)課我們便開始自定義的下一步 —— 自定義網(wǎng)絡(luò)層與模型。
這節(jié)課主要分為兩個(gè)大部分進(jìn)行講解,它們分別是:
- 自定義網(wǎng)絡(luò)層;
- 自定義模型。
我們會(huì)分別對(duì)這兩個(gè)方面進(jìn)行講解。而網(wǎng)絡(luò)層是模型的基礎(chǔ),因此我們會(huì)對(duì)網(wǎng)絡(luò)層進(jìn)行著重的講解。
1. 如何自定義網(wǎng)絡(luò)層
在 TensorFlow 之中,我們目前一般采用創(chuàng)建 keras 層的方式來進(jìn)行網(wǎng)絡(luò)層的構(gòu)建,因此,我們的創(chuàng)建步驟大致分為以下幾步:
- 定義網(wǎng)絡(luò)層的類并繼承 tf.keras.layers.Layer 類;
- 在初始化方法或者 build 方法之中定義網(wǎng)絡(luò)的參數(shù);
- 在 call 函數(shù)之中編寫具體的處理流程。
在第二步之中,我們可以在初始化或者 build 方法之中定義網(wǎng)絡(luò)的參數(shù),兩者的效果在目前的教程之中時(shí)一樣的,因此為了簡單起見,我們統(tǒng)一在初始化函數(shù)之中定義我們的網(wǎng)絡(luò)參數(shù)。
以下是一個(gè)簡單的例子:
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
在這個(gè)例子之中,我們定義了一個(gè)網(wǎng)絡(luò)層,該網(wǎng)絡(luò)層的數(shù)學(xué)形式為:
y = w*(x**2) + b
我們讓 x 進(jìn)行平方之后乘上一個(gè)系數(shù) w ,然后加上一個(gè)常數(shù)項(xiàng) b 。
通過代碼我們可以發(fā)現(xiàn),我們?cè)诔跏蓟瘮?shù)之中進(jìn)行了參數(shù)的初始化操作,然后再在 call 方式之中進(jìn)行了具體的計(jì)算。
對(duì)于添加參數(shù),我們最常用的方式是采用 add_weight 方法來進(jìn)行的;該方法最常用的參數(shù)有兩個(gè):
- shape: 表示該參數(shù)的形狀,比如 3x3 等;
- initializer: 初始化器,一般為 “zeros”(初始化為 0),或者 “random_normal”(隨機(jī)初始化)。
我們可以通過具體的數(shù)據(jù)進(jìn)行查看:
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)
因此我們可以發(fā)現(xiàn)我們的網(wǎng)絡(luò)成功的得到了輸出。但是因?yàn)槲覀兪请S機(jī)初始化的參數(shù),因此輸出一定會(huì)是雜亂無章的。
2. 網(wǎng)絡(luò)層中的固定參數(shù)
在上面的例子中,我們學(xué)習(xí)到了如何對(duì)自己的網(wǎng)絡(luò)層添加參數(shù),但是我們會(huì)發(fā)現(xiàn),我們剛剛添加的參數(shù)都是可以進(jìn)行訓(xùn)練的參數(shù),那么如何添加不可訓(xùn)練的參數(shù)呢?
我們可以在添加參數(shù)的方法之中添加屬性 trainable:
self.b = self.add_weight(shape=(hidden_units,), initializer="random_normal", trainable=False)
通過 trainable 屬性,我們可以將該參數(shù)設(shè)置為不可訓(xùn)練的參數(shù),也就是說,在以后的訓(xùn)練過程之中,該參數(shù)不會(huì)改變,而是一直保持不變的狀態(tài)。
我們可以通過以下代碼進(jìn)行實(shí)踐:
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
在該程序之中,我們定義了兩個(gè)模型,一個(gè)模型的 b 可訓(xùn)練,另一個(gè)模型的 b 不可訓(xùn)練,于是我們可以發(fā)現(xiàn),兩個(gè)模型的可訓(xùn)練參數(shù)不一樣,說明我們的屬性起到了相應(yīng)的效果。
3. 決定網(wǎng)絡(luò)是否進(jìn)行訓(xùn)練
我們的網(wǎng)絡(luò)在使用的時(shí)候包括兩個(gè)情景:
- 訓(xùn)練的情景,需要我們進(jìn)行參數(shù)的調(diào)整等;
- 測(cè)試的情景或其他情景,固定參數(shù),只進(jìn)行輸出。
在大多數(shù)時(shí)間,我們都需要在訓(xùn)練或者測(cè)試的過程之中做一些額外的操作。
而我們的網(wǎng)絡(luò)是默認(rèn)進(jìn)行訓(xùn)練的,那么如何才能將我們的網(wǎng)絡(luò)調(diào)整為測(cè)試狀態(tài)或是訓(xùn)練狀態(tài)呢?答案就是 call 方法的參數(shù):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
我們?cè)?call 之中定義了 training 參數(shù),從而可以根據(jù)是否進(jìn)行訓(xùn)練進(jìn)行額外的操作,我們可以通過如下方式來具體使用:
my_layer2 = MyLayer2(10, 5)
y = my_layer2(x, traing=True)
y = my_layer2(x, traing=False)
于是我們?cè)诘谝徽{(diào)用的時(shí)候進(jìn)行訓(xùn)練,而第二次調(diào)用的時(shí)候不進(jìn)行訓(xùn)練。
4. 網(wǎng)絡(luò)層的嵌套使用
在網(wǎng)絡(luò)層的使用之中,我們可能會(huì)遇到網(wǎng)絡(luò)層嵌套使用的情況。而且 TensorFlow 也可以支持網(wǎng)絡(luò)層的嵌套使用。
比如以下代碼:
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
在這個(gè)網(wǎng)絡(luò)層之中,我們?cè)谇懊嬷匦露x了兩個(gè)網(wǎng)絡(luò)層類,并在后面我們嵌套了我們之前的兩個(gè)網(wǎng)絡(luò)層,我們通過順序調(diào)用來實(shí)現(xiàn)了一個(gè)新的網(wǎng)絡(luò)層的操作。
我們可以通過具體的數(shù)據(jù)進(jìn)行測(cè)試:
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)
可以發(fā)現(xiàn),我們的程序成功地運(yùn)行了相應(yīng)的數(shù)據(jù),并產(chǎn)生了結(jié)果。
5. 自定義模型
既然了解了如何自定義網(wǎng)絡(luò)層,那么便要知道如何自定義網(wǎng)絡(luò)模型,其實(shí)網(wǎng)絡(luò)模型是由網(wǎng)絡(luò)層構(gòu)成的,因此只要將網(wǎng)絡(luò)層定義清楚,那么網(wǎng)絡(luò)模型便可以很輕松得到了。
網(wǎng)絡(luò)模型的構(gòu)建大致也分為以下幾步:
- 自定義模型類,并繼承自 tf.keras.Model 類;
- 在初始化函數(shù)之中定義要用到的網(wǎng)絡(luò)層;
- 在 call 函數(shù)之中定義具體的處理方式。
于是,借用上面的網(wǎng)絡(luò)層,我們可以定義我們的網(wǎng)絡(luò)模型:
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
我們的網(wǎng)絡(luò)模型使用了 MyLayer 和 MyLayer2 兩個(gè)網(wǎng)絡(luò)層,并且在 call 函數(shù)之中進(jìn)行了順序處理,從而得到最后的結(jié)果。
我們可以通過測(cè)試:
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)
我們發(fā)現(xiàn)我們的網(wǎng)絡(luò)模型也是很正確的進(jìn)行了輸出。
6. 小結(jié)
在這節(jié)課之中,我們學(xué)習(xí)了如何自定義網(wǎng)絡(luò)層,如何指定網(wǎng)絡(luò)層參數(shù)是否訓(xùn)練,如何進(jìn)行運(yùn)行模式的指定以及如何進(jìn)行網(wǎng)絡(luò)層嵌套等,最后我們又學(xué)習(xí)了如何進(jìn)行自定義網(wǎng)絡(luò)的構(gòu)建。