本文主要說明Keras中Layer的使用,更希望能通過應(yīng)用理解Layer的實現(xiàn)原理,主要內(nèi)容包含:
??1. 通過Model來調(diào)用Layer的運算;
??2. 直接使用Layer的運算;
??3. 使用Layer封裝定制運算蝗蛙;
一.使用Layer做運算
-
Layer主要是對操作與操作結(jié)果存儲的封裝,比如對圖像執(zhí)行卷積運算醉鳖;運算的執(zhí)行兩種方式捡硅;
- 通過Model執(zhí)行;
- 通過Layer的call執(zhí)行盗棵;
下面使用圖像的卷積運算處理來說明Layer承擔(dān)的計算功能壮韭。
1. Conv2D層說明
Conv2D主要是構(gòu)造器,屬性與函數(shù)都是來自Layer纹因。
構(gòu)造器說明
__init__(
filters,
kernel_size,
strides=(1, 1),
padding='valid',
data_format=None,
dilation_rate=(1, 1),
activation=None,
use_bias=True,
kernel_initializer='glorot_uniform',
bias_initializer='zeros',
kernel_regularizer=None,
bias_regularizer=None,
activity_regularizer=None,
kernel_constraint=None,
bias_constraint=None,
**kwargs
)
2. 使用Model來運算層
- 使用Model來運算泰涂,需要使用Layer構(gòu)建一個Model;
% matplotlib inline
import matplotlib.pyplot as plt
import tensorflow as tf
import tensorflow.keras as keras
import tensorflow.keras.layers as layers
import numpy as np
# 1. 加載需要處理的圖像
img = plt.imread('./04imgs/bird.png')
# 2. 初始化器
def kernel_initializer(shape, dtype=None):
print(shape)
# 浮雕卷積核
kernel_value = np.array(
[
[-1.0, -1.0, 0.0],
[-1.0, 0.0, 1.0],
[ 0.0, 1.0, 1.0]
]
)
kernel = np.zeros(shape=(3, 3, 4, 1), dtype=np.float32)
kernel[:, :, 0, 0] = kernel_value
kernel[:, :, 1, 0] = kernel_value
kernel[:, :, 2, 0] = kernel_value
kernel[:, :, 3, 0] = kernel_value
return kernel
# 3. 輸入層
# InputLayer一定按照Conv2D約定的數(shù)據(jù)格式定義input_shape
input_layer = keras.Input(shape=img.shape, dtype=tf.float32)
# 4. 卷積層
conv_layer = layers.Conv2D(
filters=1,
kernel_size=(3, 3),
strides=1,
padding='same',
kernel_initializer=kernel_initializer,
dtype=tf.float32
)
# 5. 構(gòu)建模型
conv = conv_layer(input_layer)
model = keras.Model(inputs=input_layer, outputs=conv)
(3, 3, 4, 1)
# 6. 模型計算
data = np.zeros(shape=(1, img.shape[0], img.shape[1], img.shape[2]), dtype=np.float32)
data[0] = img
conv_result = model(data) # 返回的類型是:class 'tensorflow.python.framework.ops.EagerTensor'
# 7. 顯示計算前后圖像
figure = plt.figure(figsize=(8, 4))
org_ax = figure.add_subplot(121)
cov_ax = figure.add_subplot(122)
org_ax.imshow(img)
print(type(conv_result))
cov_ax.imshow(conv_result[0, :, :, 0] , cmap='gray')
plt.show()
<class 'tensorflow.python.framework.ops.EagerTensor'>
3. Layer的運算
如果是單純的使用Layer來實現(xiàn)計算辐怕,不需要動用Model,Model主要封裝了優(yōu)化訓(xùn)練函數(shù)fit等从绘。
Layer的單純計算通過call調(diào)用實現(xiàn)寄疏;
% matplotlib inline
import matplotlib.pyplot as plt
import tensorflow as tf
import tensorflow.keras as keras
import tensorflow.keras.layers as layers
import numpy as np
# 1. 加載需要處理的圖像
img = plt.imread('./04imgs/bird.png')
# 2. 初始化器
def kernel_initializer(shape, dtype=None):
print(shape)
# 浮雕卷積核
kernel_value = np.array(
[
[-1.0, -1.0, 0.0],
[-1.0, 0.0, 1.0],
[ 0.0, 1.0, 1.0]
]
)
kernel = np.zeros(shape=(3, 3, 4, 1), dtype=np.float32)
kernel[:, :, 0, 0] = kernel_value
kernel[:, :, 1, 0] = kernel_value
kernel[:, :, 2, 0] = kernel_value
kernel[:, :, 3, 0] = kernel_value
return kernel
# 3. 輸入層
# InputLayer一定按照Conv2D約定的數(shù)據(jù)格式定義input_shape
input_layer = keras.Input(shape=img.shape, dtype=tf.float32)
# 4. 卷積層
conv_layer = layers.Conv2D(
filters=1,
kernel_size=(3, 3),
strides=1,
padding='same',
kernel_initializer=kernel_initializer,
dtype=tf.float32
)
# 層的鏈?zhǔn)秸{(diào)用
conv_layer(input_layer)
# 6. 模型計算
data = np.zeros(shape=(1, img.shape[0], img.shape[1], img.shape[2]), dtype=np.float32)
data[0] = img
conv_result = conv_layer.call(data)
# 7. 顯示計算前后圖像
figure = plt.figure(figsize=(8, 4))
org_ax = figure.add_subplot(121)
cov_ax = figure.add_subplot(122)
org_ax.imshow(img)
cov_ax.imshow(conv_result[0, :, :, 0] , cmap='gray')
plt.show()
(3, 3, 4, 1)
二. 定制Layer的運算
- 既然Layer承擔(dān)了運算的職責(zé),本身等同于函數(shù)或者過程僵井。
1. 定制的Layer的編程模式
- Layer定制需要搞清楚Layer相關(guān)成員函數(shù)作用與工作流程陕截。
1.1. 理解Layer運算的相關(guān)接口說明
- Keras 層的工作機制就是一個調(diào)用流程,并通過繼承覆蓋某些成員函數(shù)就可以實現(xiàn)Layer工作過程的干預(yù)批什,其中與計算有關(guān)的只需要覆蓋實現(xiàn)三個方法即可:
- build(input_shape): 這是你定義權(quán)重的地方农曲。這個方法必須設(shè) self.built = True,可以通過調(diào)用 super([Layer], self).build() 完成驻债。
- call(x): 這里是編寫層的功能邏輯的地方乳规。你只需要關(guān)注傳入 call 的第一個參數(shù):輸入張量,除非你希望你的層支持masking合呐。
- compute_output_shape(input_shape): 如果你的層更改了輸入張量的形狀暮的,你應(yīng)該在這里定義形狀變化的邏輯,這讓Keras能夠自動推斷各層的形狀淌实。
-
build函數(shù)接口
- 覆蓋這個函數(shù)冻辩,在函數(shù)中實現(xiàn)權(quán)重的定義猖腕。
- 這個方法必須設(shè) self.built = True,可以通過調(diào)用
super([Layer], self).build()
完成恨闪。 - 該函數(shù)無需返回數(shù)據(jù)倘感,主要用來初始化self.weights
build(input_shape)
- call函數(shù)接口
- 覆蓋這個函數(shù),在函數(shù)中實現(xiàn)功能邏輯的運算咙咽;
- 這個函數(shù)的第一個參數(shù)inputs就是輸入張量老玛;一般使用這個參數(shù)就足夠;如果需要支持masking運算犁珠,可以使用更多的參數(shù)逻炊;
- 返回一個張量,或者輸出值犁享;
call(
inputs,
**kwargs
)
- compute_output_shape函數(shù)接口
- 可選:如果需要層更改輸入張量的形狀余素,則覆蓋這個函數(shù),在函數(shù)中實現(xiàn)形狀變化的邏輯炊昆,定義新的形狀桨吊;
- 該函數(shù)可以讓Keras能夠自動推斷各層的形狀;
compute_output_shape(input_shape)
1.2. 理解Layer的定制工作流程
-
通過程序的輸出凤巨,觀察如下幾個方面视乐,來理解Layer的工作流程:
- 輸出的順序;
- 輸出的參數(shù)敢茁;
代碼與執(zhí)行輸出如下:
import matplotlib.pyplot as plt
import tensorflow as tf
import tensorflow.keras as keras
import tensorflow.keras.layers as layers
import tensorflow.keras.backend as K
import numpy as np
class WorkflowLayer(layers.Layer):
# 1. 覆蓋build函數(shù)
def build(self, input_shape):
print('build權(quán)重初始化:', type(input_shape))
self.built=True
# super(WorkflowLayer, self).build(input_shape)
# 2. 覆蓋call
def call(self, inputs, **kwargs):
print('call運算邏輯實現(xiàn):', type(inputs), kwargs)
return inputs
# 3. 覆蓋compute_output_shape
def compute_output_shape(self, input_shape):
print('......')
return super(WorkflowLayer,self).compute_output_shape(input_shape)
input_layer = keras.Input(shape=(None,))
workflow_layer = WorkflowLayer()
workflow_layer(input_layer) # 觸發(fā)build與call函數(shù)調(diào)用
result = workflow_layer.call([1]) # 只觸發(fā)call函數(shù)調(diào)用佑淀,不觸發(fā)build函數(shù)調(diào)用
print(type(result))
build權(quán)重初始化: <class 'tensorflow.python.framework.tensor_shape.TensorShape'>
call運算邏輯實現(xiàn): <class 'tensorflow.python.framework.ops.Tensor'> {}
call運算邏輯實現(xiàn): <class 'list'> {}
<class 'list'>
1.3. Layer的工作流程說明
-
使用對象調(diào)用
- 使用對象調(diào)用,會先調(diào)用build函數(shù)彰檬,構(gòu)建權(quán)重矩陣伸刃;
- 對象調(diào)用主要用來構(gòu)建計算環(huán)境;
- build的參數(shù)類型為:
<class 'tensorflow.python.framework.tensor_shape.TensorShape'>
-
使用call調(diào)用
- 不會調(diào)用build函數(shù)逢倍,直接計算捧颅;
- 通過對象調(diào)用,傳遞的參數(shù)類型是:
<class 'tensorflow.python.framework.ops.Tensor'>
- 通過call函數(shù)調(diào)用较雕,傳遞的參數(shù)類型就是傳遞的原始類型碉哑。
-
在Model中使用的情況
- call的參數(shù)類型為:
<class 'tensorflow.python.framework.ops.EagerTensor'>
- Model返回的是<Tensor>
- call的參數(shù)類型為:
input_layer = keras.Input(shape=(None,))
workflow_layer = WorkflowLayer()
workflow = workflow_layer(input_layer) # 觸發(fā)build與call函數(shù)調(diào)用
model = keras.Model(inputs=input_layer, outputs=workflow)
model([1])
build權(quán)重初始化: <class 'tensorflow.python.framework.tensor_shape.TensorShape'>
call運算邏輯實現(xiàn): <class 'tensorflow.python.framework.ops.Tensor'> {}
call運算邏輯實現(xiàn): <class 'tensorflow.python.framework.ops.EagerTensor'> {}
<tf.Tensor: id=76, shape=(), dtype=int32, numpy=1>
- 更多的說明,可以參考源代碼
- 從上面代碼的運行亮蒋,我們可以得知
compute_output_shape
函數(shù)沒有被調(diào)用红氯。 - Dense實現(xiàn)的代碼地址:
https://github.com/tensorflow/tensorflow/blob/r2.0/tensorflow/python/keras/layers/core.py#L913-L1079
- 從上面代碼的運行亮蒋,我們可以得知
2. 運行官方文檔提供的例子
-
在官方的文檔中提供了一個例子元旬,運行的時候,需要注意幾個問題:
- 數(shù)據(jù)類型的問題;
- 輸入的數(shù)據(jù)需要使用張量Tensor類型屿附;
下面是代碼及其運行
import matplotlib.pyplot as plt
import tensorflow as tf
import tensorflow.keras as keras
import tensorflow.keras.layers as layers
import tensorflow.keras.backend as K
import numpy as np
class MyLayer(layers.Layer):
def __init__(self, output_dim, **kwargs):
self.output_dim = output_dim
super(MyLayer, self).__init__(**kwargs)
def build(self, input_shape):
print('build函數(shù)袄琳!')
# 為該層創(chuàng)建一個可訓(xùn)練的權(quán)重
self.kernel = self.add_weight(name='kernel',
shape=(input_shape[1], self.output_dim),
initializer='uniform',
trainable=True, dtype=tf.float32) # <-----指定數(shù)據(jù)類型
super(MyLayer, self).build(input_shape) # 或者self.built = True
def call(self, x):
print('call函數(shù)!')
return K.dot(x, self.kernel) # 內(nèi)積運算
#
def compute_output_shape(self, input_shape):
print('compute_output_shape函數(shù)!')
return (input_shape[0], self.output_dim)
layer = MyLayer(1)
input_layer = keras.Input(shape=(2,))
v_layer = layer(input_layer)
result = layer.call(tf.constant(np.array([[1.0, 2.0]], dtype=np.float32))) # <-----指定數(shù)據(jù)類型铸敏,而且需要是Tensor類型
print(result.numpy())
build函數(shù)!
call函數(shù)!
call函數(shù)!
[[0.07733139]]
3. 使用Layer封裝實現(xiàn)backend的卷積操作
-
盡管這種封裝沒有多大意義闰蛔,甚至是多此一舉,唯一的好處就是練習(xí)下Layer的定制封裝與Layer的作用图柏。
- 在tenorflow中tensorflow.keras.backend中提供了基礎(chǔ)的卷積運算:
conv2d
序六;
- 在tenorflow中tensorflow.keras.backend中提供了基礎(chǔ)的卷積運算:
只需要在上述代碼上修改即可。
import matplotlib.pyplot as plt
import tensorflow as tf
import tensorflow.keras as keras
import tensorflow.keras.layers as layers
import tensorflow.keras.backend as K
import numpy as np
class ConvLayer(layers.Layer):
def build(self, input_shape):
print('build函數(shù)蚤吹!')
# 構(gòu)建卷積運算需要的核
kernel_value = np.array(
[
[-1.0, -1.0, 0.0],
[-1.0, 0.0, 1.0],
[0.0, 1.0, 1.0]
]
)
self.kernel = np.zeros(shape=(3, 3, 4, 1), dtype=np.float32)
self.kernel[:, :, 0, 0] = kernel_value
self.kernel[:, :, 1, 0] = kernel_value
self.kernel[:, :, 2, 0] = kernel_value
self.kernel[:, :, 3, 0] = kernel_value
super(ConvLayer, self).build(input_shape) # 或者self.built = True
def call(self, x):
print('call函數(shù)!')
return K.conv2d(x, self.kernel, padding='same',strides=(1, 1, 1, 1))
# 加載圖像
img = plt.imread('./04imgs/bird.png')
data = np.zeros(shape=(1, img.shape[0], img.shape[1], img.shape[2]), dtype=np.float32)
data[0] = img
# 定制的Layer
layer = ConvLayer()
# 輸入
input_layer = keras.Input(shape=img.shape)
# 輸入調(diào)用
layer(input_layer)
result = layer.call(tf.constant(data, dtype=np.float32))
# print(result.numpy()) # 輸出numpy的數(shù)組格式
# 顯示計算前后圖像
figure = plt.figure(figsize=(8, 4))
org_ax = figure.add_subplot(121)
cov_ax = figure.add_subplot(122)
org_ax.imshow(img)
cov_ax.imshow(result[0, :, :, 0], cmap='gray')
plt.show()
build函數(shù)例诀!
call函數(shù)!
call函數(shù)!