Keras ではConv2Dクラスが畳み込み層のクラスになります。
では早速、Conv2D クラスを使って前ページの例を演算してみましょう(ソース1)。
import tensorflow as tf
'''
# CUDA を有効にしている時に「UnknownError:  Failed to get convolution algorithm.」というエラーが
# 出て動かない時はコメントアウトを外し、カーネルを再起動してから実行してください
from tensorflow.compat.v1.keras.backend import set_session
config = tf.compat.v1.ConfigProto()
config.gpu_options.allow_growth = True
config.log_device_placement = True
sess = tf.compat.v1.Session(config=config)
set_session(sess)
'''
import numpy as np
rows = 4 # 画像の高さ
cols = 4 # 画像の幅
Cin = 2  # 入力画像のチャンネル数
Cout = 2 # 出力画像のチャンネル数
kernel_size = 3 # カーネルサイズ
# 入力画像の 0 番目のチャンネル
data_0 = np.array(
    [
        [ 1, 2, 3, 0 ],
        [ 2, 0, 1, 2 ],
        [ 0, 1, 2, 0 ],
        [ 0, 0, 3, 1 ]
    ]
)
# 入力画像の 1 番目のチャンネル
data_1 = np.array(
    [
        [ 0, 2, 1, 3 ],
        [ 1, 1, 0, 1 ],
        [ 2, 2, 3, 2 ],
        [ 3, 1, 0, 0 ]
    ]
)
# 入力画像の 0 番目のチャンネルと出力画像の 0 番目のチャンネルに対するカーネル
kernel_00 = np.array(
    [ 
        [ 0, 1, 0 ],
        [ 2, 0, 3 ],
        [ 0, 4, 0 ]
    ]
)
# 入力画像の 0 番目のチャンネルと出力画像の 1 番目のチャンネルに対するカーネル
kernel_01 = np.array(
    [ 
        [ 2, 0, 1 ],
        [ 0, 1, 0 ],
        [ 1, 0, 3 ]
    ]
)
# 入力画像の 1 番目のチャンネルと出力画像の 0 番目のチャンネルに対するカーネル
kernel_10 = np.array(
    [ 
        [ 0, 0, 0 ],
        [ 0, 1, 0 ],
        [ 0, 0, 0 ]
    ]
)
# 入力画像の 1 番目のチャンネルと出力画像の 1 番目のチャンネルに対するカーネル
kernel_11 = np.array(
    [ 
        [ 1, 0, 0 ],
        [ 0, 2, 3 ],
        [ 2, 0, 0 ]
    ]
)
# 出力画像の 0 番目のチャンネルに対するバイアス
b_0 = 1
# 出力画像の 1 番目のチャンネルに対するバイアス
b_1 = -1
# 入力画像全体: shape = (データ数, rows, cols, Cin) の 4 階テンソル
data = tf.constant( [ np.stack( [data_0,data_1], -1) ], dtype=tf.float32 )
# カーネル全体: shape = (kernel_size, kernel_size, Cin * Cout) の 3 階テンソル 
kernel = np.stack( [kernel_00,kernel_01,kernel_10,kernel_11], -1)
# バイアス全体 :  shape = (Cout) のベクトル
b = [b_0, b_1]
# モデル作成
model = tf.keras.Sequential()
model.add( tf.keras.Input(shape=(rows,cols,Cin,)))
model.add( tf.keras.layers.Conv2D( Cout, (kernel_size,kernel_size), 
                                  kernel_initializer=tf.keras.initializers.Constant(kernel), 
                                  bias_initializer=tf.keras.initializers.Constant(b) ))
model.summary()
# 畳み込み演算
y = model.predict( data )
print('')
np.set_printoptions(precision=0, suppress=True)
for i in range(Cin):
    print(f'data_{i}=')
    print( data[0,:,:,i].numpy())
    print('')
for i in range(Cin):
    for i2 in range(Cout):
        print(f'kernel_{i}{i2} = ')
        print(kernel[:,:,Cin*i+i2])
        print('')
for i in range(Cout):
    print(f'b_{i} = {b[i]}')
    print('')
for i in range(Cout):
    print(f'y_{i} =')
    print(y[0,:,:,i])
    print('')
基本的にはMLPと同様に Sequential モデルを作成し、predict メソッドで出力を求めますが、
model.add( tf.keras.layers.Conv2D( Cout, (kernel_size,kernel_size), 
                                  kernel_initializer=tf.keras.initializers.Constant(kernel), 
                                  bias_initializer=tf.keras.initializers.Constant(b) ))
の行で畳み込み層(Conv2Dクラス)をモデルに追加しています。
では上のソースの実行結果を以下に示します。
Model: "sequential_2" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= conv2d_2 (Conv2D) (None, 2, 2, 2) 38 ================================================================= Total params: 38 Trainable params: 38 Non-trainable params: 0 _________________________________________________________________ data_0= [[1. 2. 3. 0.] [2. 0. 1. 2.] [0. 1. 2. 0.] [0. 0. 3. 1.]] data_1= [[0. 2. 1. 3.] [1. 1. 0. 1.] [2. 2. 3. 2.] [3. 1. 0. 0.]] kernel_00 = [[0 1 0] [2 0 3] [0 4 0]] kernel_01 = [[2 0 1] [0 1 0] [1 0 3]] kernel_10 = [[0 0 0] [0 1 0] [0 0 0]] kernel_11 = [[1 0 0] [0 2 3] [2 0 0]] b_0 = 1 b_1 = -1 y_0 = [[15. 18.] [ 9. 19.]] y_1 = [[16. 14.] [34. 21.]]
ところでモデル概要を見るとパラメータの個数が Total param : 38 個となっています。
カーネル1つに含まれるパラメータ数は 9 個、カーネルは計 4 個、バイアスは計 2 個より、9*4+2=38 なので確かに合っています。