understanding Dense layer in Keras

# simple `Conv1D`
import tensorflow as tf
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, Dense, TimeDistributed, Conv1D, LSTM, MaxPool1D
import numpy as np

def reset_seed(seed=313):
    tf.keras.backend.clear_session()
    tf.random.set_seed(seed)
    np.random.seed(seed)

np.set_printoptions(linewidth=150)

print(tf.__version__, np.__version__)
2.7.0 1.21.6
input_features = 3
lookback = 6
batch_size=2
input_shape = lookback,input_features
ins = Input(shape=input_shape, name='my_input')
outs = Conv1D(filters=8, kernel_size=3,
              strides=1, padding='same', kernel_initializer='ones',
              name='my_conv1d')(ins)
model = Model(inputs=ins, outputs=outs)

input_array = np.arange(36).reshape((batch_size, *input_shape))
conv1d_weights = model.get_layer('my_conv1d').weights[0].numpy()
output_array = model.predict(input_array)
print(input_array.shape)

print(input_array)
(2, 6, 3)
[[[ 0  1  2]
  [ 3  4  5]
  [ 6  7  8]
  [ 9 10 11]
  [12 13 14]
  [15 16 17]]

 [[18 19 20]
  [21 22 23]
  [24 25 26]
  [27 28 29]
  [30 31 32]
  [33 34 35]]]
print(conv1d_weights.shape)

print(conv1d_weights)
(3, 3, 8)
[[[1. 1. 1. 1. 1. 1. 1. 1.]
  [1. 1. 1. 1. 1. 1. 1. 1.]
  [1. 1. 1. 1. 1. 1. 1. 1.]]

 [[1. 1. 1. 1. 1. 1. 1. 1.]
  [1. 1. 1. 1. 1. 1. 1. 1.]
  [1. 1. 1. 1. 1. 1. 1. 1.]]

 [[1. 1. 1. 1. 1. 1. 1. 1.]
  [1. 1. 1. 1. 1. 1. 1. 1.]
  [1. 1. 1. 1. 1. 1. 1. 1.]]]
print(output_array)

print(output_array.shape)
[[[ 15.  15.  15.  15.  15.  15.  15.  15.]
  [ 36.  36.  36.  36.  36.  36.  36.  36.]
  [ 63.  63.  63.  63.  63.  63.  63.  63.]
  [ 90.  90.  90.  90.  90.  90.  90.  90.]
  [117. 117. 117. 117. 117. 117. 117. 117.]
  [ 87.  87.  87.  87.  87.  87.  87.  87.]]

 [[123. 123. 123. 123. 123. 123. 123. 123.]
  [198. 198. 198. 198. 198. 198. 198. 198.]
  [225. 225. 225. 225. 225. 225. 225. 225.]
  [252. 252. 252. 252. 252. 252. 252. 252.]
  [279. 279. 279. 279. 279. 279. 279. 279.]
  [195. 195. 195. 195. 195. 195. 195. 195.]]]
(2, 6, 8)
# multiple inputs multiple layers
input_features = 3
lookback = 3
batch_size=2
input_shape = lookback,input_features
ins1 = Input(shape=input_shape, name='my_input1')
ins2 = Input(shape=input_shape, name='my_input2')
outs1 = Conv1D(filters=8, kernel_size=3,
              strides=1, padding='same', kernel_initializer='ones',
              name='my_conv1d1')(ins1)
outs2 = Conv1D(filters=8, kernel_size=3,
              strides=1, padding='same', kernel_initializer='ones',
              name='my_conv1d2')(ins2)
model = Model(inputs=[ins1, ins2], outputs=[outs1, outs2])

sub_seq = 2
input_shape = sub_seq, 3, input_features
input_array = np.arange(36).reshape((batch_size, *input_shape))
input_array1 = input_array[:, 0, :, :]
input_array2 = input_array[:, 1, :, :]

conv1d1_weights = model.get_layer('my_conv1d1').weights[0].numpy()
conv1d2_weights = model.get_layer('my_conv1d2').weights[0].numpy()
output_array = model.predict([input_array1, input_array2])
print(input_array1, '\n\n', input_array2)
[[[ 0  1  2]
  [ 3  4  5]
  [ 6  7  8]]

 [[18 19 20]
  [21 22 23]
  [24 25 26]]]

 [[[ 9 10 11]
  [12 13 14]
  [15 16 17]]

 [[27 28 29]
  [30 31 32]
  [33 34 35]]]
print(conv1d2_weights)

print(conv1d2_weights)
[[[1. 1. 1. 1. 1. 1. 1. 1.]
  [1. 1. 1. 1. 1. 1. 1. 1.]
  [1. 1. 1. 1. 1. 1. 1. 1.]]

 [[1. 1. 1. 1. 1. 1. 1. 1.]
  [1. 1. 1. 1. 1. 1. 1. 1.]
  [1. 1. 1. 1. 1. 1. 1. 1.]]

 [[1. 1. 1. 1. 1. 1. 1. 1.]
  [1. 1. 1. 1. 1. 1. 1. 1.]
  [1. 1. 1. 1. 1. 1. 1. 1.]]]
[[[1. 1. 1. 1. 1. 1. 1. 1.]
  [1. 1. 1. 1. 1. 1. 1. 1.]
  [1. 1. 1. 1. 1. 1. 1. 1.]]

 [[1. 1. 1. 1. 1. 1. 1. 1.]
  [1. 1. 1. 1. 1. 1. 1. 1.]
  [1. 1. 1. 1. 1. 1. 1. 1.]]

 [[1. 1. 1. 1. 1. 1. 1. 1.]
  [1. 1. 1. 1. 1. 1. 1. 1.]
  [1. 1. 1. 1. 1. 1. 1. 1.]]]
print(output_array)
[array([[[ 15.,  15.,  15.,  15.,  15.,  15.,  15.,  15.],
        [ 36.,  36.,  36.,  36.,  36.,  36.,  36.,  36.],
        [ 33.,  33.,  33.,  33.,  33.,  33.,  33.,  33.]],

       [[123., 123., 123., 123., 123., 123., 123., 123.],
        [198., 198., 198., 198., 198., 198., 198., 198.],
        [141., 141., 141., 141., 141., 141., 141., 141.]]], dtype=float32), array([[[ 69.,  69.,  69.,  69.,  69.,  69.,  69.,  69.],
        [117., 117., 117., 117., 117., 117., 117., 117.],
        [ 87.,  87.,  87.,  87.,  87.,  87.,  87.,  87.]],

       [[177., 177., 177., 177., 177., 177., 177., 177.],
        [279., 279., 279., 279., 279., 279., 279., 279.],
        [195., 195., 195., 195., 195., 195., 195., 195.]]], dtype=float32)]
# multiple inputs shared layer
input_features = 3
lookback = 6
sub_seq = 2
input_shape = sub_seq, 3, input_features
batch_size = 2

ins = Input(shape=input_shape, name='my_input')
conv = Conv1D(filters=8, kernel_size=3,
              strides=1, padding='same',
              kernel_initializer='ones', name='my_conv1d')

conv1_out = conv(ins[:, 0, :, :])
conv2_out = conv(ins[:, 1, :, :])
model = Model(inputs=ins, outputs=[conv1_out, conv2_out])

input_array = np.arange(36).reshape((batch_size, *input_shape))
conv1d_weights = model.get_layer('my_conv1d').weights[0].numpy()
output_array = model.predict(input_array)
print(input_array)
[[[[ 0  1  2]
   [ 3  4  5]
   [ 6  7  8]]

  [[ 9 10 11]
   [12 13 14]
   [15 16 17]]]


 [[[18 19 20]
   [21 22 23]
   [24 25 26]]

  [[27 28 29]
   [30 31 32]
   [33 34 35]]]]
print(conv1d_weights)
[[[1. 1. 1. 1. 1. 1. 1. 1.]
  [1. 1. 1. 1. 1. 1. 1. 1.]
  [1. 1. 1. 1. 1. 1. 1. 1.]]

 [[1. 1. 1. 1. 1. 1. 1. 1.]
  [1. 1. 1. 1. 1. 1. 1. 1.]
  [1. 1. 1. 1. 1. 1. 1. 1.]]

 [[1. 1. 1. 1. 1. 1. 1. 1.]
  [1. 1. 1. 1. 1. 1. 1. 1.]
  [1. 1. 1. 1. 1. 1. 1. 1.]]]
print(output_array[0])
[[[ 15.  15.  15.  15.  15.  15.  15.  15.]
  [ 36.  36.  36.  36.  36.  36.  36.  36.]
  [ 33.  33.  33.  33.  33.  33.  33.  33.]]

 [[123. 123. 123. 123. 123. 123. 123. 123.]
  [198. 198. 198. 198. 198. 198. 198. 198.]
  [141. 141. 141. 141. 141. 141. 141. 141.]]]
print(output_array[1])
[[[ 69.  69.  69.  69.  69.  69.  69.  69.]
  [117. 117. 117. 117. 117. 117. 117. 117.]
  [ 87.  87.  87.  87.  87.  87.  87.  87.]]

 [[177. 177. 177. 177. 177. 177. 177. 177.]
  [279. 279. 279. 279. 279. 279. 279. 279.]
  [195. 195. 195. 195. 195. 195. 195. 195.]]]
# `TimeDistributed Conv1D`
input_features = 3
lookback = 6
sub_seq = 2
input_shape = sub_seq, 3, input_features
batch_size = 2

ins = Input(shape=input_shape, name='my_input')
outs = TimeDistributed(Conv1D(filters=8, kernel_size=3,
              strides=1, padding='same', kernel_initializer='ones',
              name='my_conv1d'))(ins)
model = Model(inputs=ins, outputs=outs)

input_array = np.arange(36).reshape((batch_size, *input_shape))
output_array = model.predict(input_array)
print(input_array)
[[[[ 0  1  2]
   [ 3  4  5]
   [ 6  7  8]]

  [[ 9 10 11]
   [12 13 14]
   [15 16 17]]]


 [[[18 19 20]
   [21 22 23]
   [24 25 26]]

  [[27 28 29]
   [30 31 32]
   [33 34 35]]]]
print(output_array)
[[[[ 15.  15.  15.  15.  15.  15.  15.  15.]
   [ 36.  36.  36.  36.  36.  36.  36.  36.]
   [ 33.  33.  33.  33.  33.  33.  33.  33.]]

  [[ 69.  69.  69.  69.  69.  69.  69.  69.]
   [117. 117. 117. 117. 117. 117. 117. 117.]
   [ 87.  87.  87.  87.  87.  87.  87.  87.]]]


 [[[123. 123. 123. 123. 123. 123. 123. 123.]
   [198. 198. 198. 198. 198. 198. 198. 198.]
   [141. 141. 141. 141. 141. 141. 141. 141.]]

  [[177. 177. 177. 177. 177. 177. 177. 177.]
   [279. 279. 279. 279. 279. 279. 279. 279.]
   [195. 195. 195. 195. 195. 195. 195. 195.]]]]
# So `TimeDistributed` Just applies same `Conv1D` to each sub-sequence/incoming input.

# `TimeDistributed` `LSTM`
tf.random.set_seed(313)

input_features = 3
sub_seq = 2
input_shape = sub_seq, 3, input_features
batch_size = 2

ins = Input(shape=input_shape, name='my_input')
outs = TimeDistributed(LSTM(units=8, name='my_lstm'))(ins)
model = Model(inputs=ins, outputs=outs)

input_array = np.arange(36).reshape((batch_size, *input_shape))
output_array = model.predict(input_array)
print(input_array)
[[[[ 0  1  2]
   [ 3  4  5]
   [ 6  7  8]]

  [[ 9 10 11]
   [12 13 14]
   [15 16 17]]]


 [[[18 19 20]
   [21 22 23]
   [24 25 26]]

  [[27 28 29]
   [30 31 32]
   [33 34 35]]]]
print(output_array[:, 0, :])
[[-0.16062409  0.22913463  0.1058553   0.60561293 -0.00365596 -0.14262524  0.0095918   0.00026523]
 [-0.00133879  0.01697312  0.01358414  0.24525642 -0.00000001 -0.00133852  0.00000001  0.        ]]
print(output_array[:, 1, :])
[[-0.01696269  0.06433662  0.06806362  0.58035445 -0.00000462 -0.0139938   0.00001501  0.00000001]
 [-0.00010769  0.00426498  0.00253441  0.08013909 -0.         -0.00013428  0.          0.        ]]
# manual weight sharing of `LSTM`
tf.random.set_seed(313)

input_features = 3
sub_seq = 2
input_shape = sub_seq, 3, input_features
batch_size = 2

ins = Input(shape=input_shape, name='my_input')
lstm = LSTM(units=8, name='my_lstm')

lstm1_out = lstm(ins[:, 0, :, :])
lstm2_out = lstm(ins[:, 1, :, :])
model = Model(inputs=ins, outputs=[lstm1_out, lstm2_out])

input_array = np.arange(36).reshape((batch_size, *input_shape))
output_array = model.predict(input_array)
print(input_array)
[[[[ 0  1  2]
   [ 3  4  5]
   [ 6  7  8]]

  [[ 9 10 11]
   [12 13 14]
   [15 16 17]]]


 [[[18 19 20]
   [21 22 23]
   [24 25 26]]

  [[27 28 29]
   [30 31 32]
   [33 34 35]]]]
print(output_array[0])
[[-0.16062409  0.22913463  0.1058553   0.60561293 -0.00365596 -0.14262524  0.0095918   0.00026523]
 [-0.00133879  0.01697312  0.01358414  0.24525642 -0.00000001 -0.00133852  0.00000001  0.        ]]
print(output_array[1])
[[-0.01696269  0.06433662  0.06806362  0.58035445 -0.00000462 -0.0139938   0.00001501  0.00000001]
 [-0.00010769  0.00426498  0.00253441  0.08013909 -0.         -0.00013428  0.          0.        ]]
# Curious case of `Dense`
tf.random.set_seed(313)

input_features = 3
lookback = 6
batch_size=2
input_shape = lookback,input_features

input_shape = lookback, input_features
ins = Input(input_shape, name='my_input')
out = Dense(units=5,  name='my_output')(ins)
model = Model(inputs=ins, outputs=out)

input_array = np.arange(36).reshape(batch_size, *input_shape)
output_array = model.predict(input_array)
print(input_array.shape)
(2, 6, 3)
print(input_array)
[[[ 0  1  2]
  [ 3  4  5]
  [ 6  7  8]
  [ 9 10 11]
  [12 13 14]
  [15 16 17]]

 [[18 19 20]
  [21 22 23]
  [24 25 26]
  [27 28 29]
  [30 31 32]
  [33 34 35]]]
print(output_array.shape)
(2, 6, 5)
print(output_array)
[[[  0.27596885   0.70854443  -0.53744566  -0.70061463  -0.20640421]
  [  0.36203665   3.999878    -1.7514435   -2.695434     0.17058378]
  [  0.44810438   7.291211    -2.9654412   -4.6902537    0.5475718 ]
  [  0.53417236  10.582545    -4.179439    -6.685073     0.92456   ]
  [  0.6202401   13.8738785   -5.393437    -8.679893     1.3015478 ]
  [  0.7063078   17.165213    -6.607435   -10.674712     1.6785362 ]]

 [[  0.7923758   20.456545    -7.821433   -12.669531     2.0555243 ]
  [  0.8784433   23.747879    -9.035431   -14.664351     2.4325123 ]
  [  0.9645113   27.039211   -10.249429   -16.65917      2.8094997 ]
  [  1.0505793   30.330545   -11.463427   -18.65399      3.1864882 ]
  [  1.1366472   33.62188    -12.6774235  -20.64881      3.5634766 ]
  [  1.2227142   36.91321    -13.891422   -22.64363      3.9404643 ]]]
tf.random.set_seed(313)

input_features = 3
lookback = 6
sub_seq = 2
input_shape = sub_seq, 3, input_features
batch_size = 2

ins = Input(input_shape, name='my_input')
out = TimeDistributed(Dense(units=5, name='my_output'))(ins)
model = Model(inputs=ins, outputs=out)


input_array = np.arange(36).reshape(batch_size, *input_shape)
output_array = model.predict(input_array)
print(input_array.shape)
(2, 2, 3, 3)
print(input_array)
[[[[ 0  1  2]
   [ 3  4  5]
   [ 6  7  8]]

  [[ 9 10 11]
   [12 13 14]
   [15 16 17]]]


 [[[18 19 20]
   [21 22 23]
   [24 25 26]]

  [[27 28 29]
   [30 31 32]
   [33 34 35]]]]
print(output_array.shape)
(2, 2, 3, 5)
print(output_array)
[[[[  0.27596885   0.70854443  -0.53744566  -0.70061463  -0.20640421]
   [  0.36203665   3.999878    -1.7514435   -2.695434     0.17058378]
   [  0.44810438   7.291211    -2.9654412   -4.6902537    0.5475718 ]]

  [[  0.53417236  10.582545    -4.179439    -6.685073     0.92456   ]
   [  0.6202401   13.8738785   -5.393437    -8.679893     1.3015478 ]
   [  0.7063078   17.165213    -6.607435   -10.674712     1.6785362 ]]]


 [[[  0.7923758   20.456545    -7.821433   -12.669531     2.0555243 ]
   [  0.8784433   23.747879    -9.035431   -14.664351     2.4325123 ]
   [  0.9645113   27.039211   -10.249429   -16.65917      2.8094997 ]]

  [[  1.0505793   30.330545   -11.463427   -18.65399      3.1864882 ]
   [  1.1366472   33.62188    -12.6774235  -20.64881      3.5634766 ]
   [  1.2227142   36.91321    -13.891422   -22.64363      3.9404643 ]]]]
# so far looks very similar to `TimeDistributed(Conv1D)` or `TimeDistributed(LSTM)`.
tf.random.set_seed(313)

input_features = 3
lookback = 6
input_shape = lookback, input_features
batch_size = 2

ins = Input(input_shape, name='my_input')
out = TimeDistributed(Dense(5, use_bias=False, name='my_output'))(ins)
model = Model(inputs=ins, outputs=out)

input_array = np.arange(36).reshape(batch_size, *input_shape)
output_array = model.predict(input_array)
print(input_array.shape)

print(input_array)
(2, 6, 3)
[[[ 0  1  2]
  [ 3  4  5]
  [ 6  7  8]
  [ 9 10 11]
  [12 13 14]
  [15 16 17]]

 [[18 19 20]
  [21 22 23]
  [24 25 26]
  [27 28 29]
  [30 31 32]
  [33 34 35]]]
print(output_array.shape)

print(output_array)
(2, 6, 5)
[[[  0.27596885   0.70854443  -0.53744566  -0.70061463  -0.20640421]
  [  0.36203665   3.999878    -1.7514435   -2.695434     0.17058378]
  [  0.44810438   7.291211    -2.9654412   -4.6902537    0.5475718 ]
  [  0.53417236  10.582545    -4.179439    -6.685073     0.92456   ]
  [  0.6202401   13.8738785   -5.393437    -8.679893     1.3015478 ]
  [  0.7063078   17.165213    -6.607435   -10.674712     1.6785362 ]]

 [[  0.7923758   20.456545    -7.821433   -12.669531     2.0555243 ]
  [  0.8784433   23.747879    -9.035431   -14.664351     2.4325123 ]
  [  0.9645113   27.039211   -10.249429   -16.65917      2.8094997 ]
  [  1.0505793   30.330545   -11.463427   -18.65399      3.1864882 ]
  [  1.1366472   33.62188    -12.6774235  -20.64881      3.5634766 ]
  [  1.2227142   36.91321    -13.891422   -22.64363      3.9404643 ]]]
# So whether we we `TimeDistributed(Dense)` or `Dense`, they are actually equivalent.

# What if we try same with `Conv1D` or `LSTM` i.e. wrapping these layers in
# `TimeDistributed` without modifying/dividing input into sub-sequences?
input_features = 3
lookback = 6
input_shape = lookback, input_features

# uncomment following lines

# ins = Input(shape=(lookback, input_features), name='my_input')
# outs = TimeDistributed(Conv1D(filters=8, kernel_size=3,
#               strides=1, padding='valid', kernel_initializer='ones',
#               name='my_conv1d'))(ins)
# model = Model(inputs=ins, outputs=outs)

input_array = np.arange(36).reshape((batch_size, *input_shape))

The above error message can be slightly confusing or atleast can be resolved in a wrong manner as we do in following case;

input_features = 3
lookback = 6
input_shape = lookback, input_features
ins = Input(shape=(batch_size, lookback, input_features), name='my_input')
outs = TimeDistributed(Conv1D(filters=8, kernel_size=3,
              strides=1, padding='valid', kernel_initializer='ones',
              name='my_conv1d'))(ins)
model = Model(inputs=ins, outputs=outs)

input_array = np.arange(36).reshape((batch_size, *input_shape))
print(input_array.shape)
(2, 6, 3)
# So we are able to compile the model, although it is wrong.
# uncomment following 2 lines
# output_array = model.predict(input_array)
# print(output_array.shape)
# This error message is exactly related to `TimeDistributed` layer. The `TimeDistributed`
# layer here expects input having 4 dimensions, 1st being batch size, second being the
# sub-sequences, 3rd being the time-steps or whatever and 4rth being number of input
# features here.

# Anyhow, the conclusion is, we can't just wrap layers in `TimeDistributed` except
# for `Dense` layer. Hence, using `TimeDistributed(Dense)` does not make any
# sense (to me until version 2.3.0).
# More than just weight sharing

# `TimeDistributed` layer is meant to provide more functionality than just weight
# sharing. We see, pooling layers or flatten layers wrapped into `TimeDistributed`
# layer even though pooling layers or flattening layers don't have any weights.
# This is because if we have applied `TimeDistributed(Conv1D)`, this will sprout
# output for each sub-sequence. We would naturally like to apply pooling and
# consequently flattening layers to each output fo the sub-sequences.
input_features = 3
sub_seq = 2
input_shape = sub_seq, 3, input_features
batch_size = 2

ins = Input(shape=input_shape, name='my_input')
conv_outs = TimeDistributed(Conv1D(filters=8, kernel_size=3,
              strides=1,
              padding='same',
              kernel_initializer='ones',
              name='my_conv1d'))(ins)
outs = TimeDistributed(MaxPool1D(pool_size=2))(conv_outs)
model = Model(inputs=ins, outputs=[outs, conv_outs])

input_array = np.arange(36).reshape((batch_size, *input_shape))
output_array, conv_output = model.predict(input_array)
print(input_array.shape)
(2, 2, 3, 3)
print(input_array)
[[[[ 0  1  2]
   [ 3  4  5]
   [ 6  7  8]]

  [[ 9 10 11]
   [12 13 14]
   [15 16 17]]]


 [[[18 19 20]
   [21 22 23]
   [24 25 26]]

  [[27 28 29]
   [30 31 32]
   [33 34 35]]]]
print(conv_output.shape)
(2, 2, 3, 8)
print(conv_output)
[[[[ 15.  15.  15.  15.  15.  15.  15.  15.]
   [ 36.  36.  36.  36.  36.  36.  36.  36.]
   [ 33.  33.  33.  33.  33.  33.  33.  33.]]

  [[ 69.  69.  69.  69.  69.  69.  69.  69.]
   [117. 117. 117. 117. 117. 117. 117. 117.]
   [ 87.  87.  87.  87.  87.  87.  87.  87.]]]


 [[[123. 123. 123. 123. 123. 123. 123. 123.]
   [198. 198. 198. 198. 198. 198. 198. 198.]
   [141. 141. 141. 141. 141. 141. 141. 141.]]

  [[177. 177. 177. 177. 177. 177. 177. 177.]
   [279. 279. 279. 279. 279. 279. 279. 279.]
   [195. 195. 195. 195. 195. 195. 195. 195.]]]]
print(output_array.shape)
(2, 2, 1, 8)
print(output_array)
[[[[ 36.  36.  36.  36.  36.  36.  36.  36.]]

  [[117. 117. 117. 117. 117. 117. 117. 117.]]]


 [[[198. 198. 198. 198. 198. 198. 198. 198.]]

  [[279. 279. 279. 279. 279. 279. 279. 279.]]]]
input_features = 3
sub_seq = 2
input_shape = sub_seq, 3, input_features
batch_size = 2

ins = Input(shape=input_shape, name='my_input')
conv_outs = TimeDistributed(Conv1D(filters=8, kernel_size=3,
              strides=1, padding='same',
              kernel_initializer='ones',
              name='my_conv1d'))(ins)
outs = TimeDistributed(MaxPool1D(pool_size=2, padding='same'))(conv_outs)
model = Model(inputs=ins, outputs=outs)

input_array = np.arange(36).reshape((batch_size, *input_shape))
output_array = model.predict(input_array)
print(input_array.shape)
(2, 2, 3, 3)
print(input_array)
[[[[ 0  1  2]
   [ 3  4  5]
   [ 6  7  8]]

  [[ 9 10 11]
   [12 13 14]
   [15 16 17]]]


 [[[18 19 20]
   [21 22 23]
   [24 25 26]]

  [[27 28 29]
   [30 31 32]
   [33 34 35]]]]
print(output_array.shape)
(2, 2, 2, 8)
print(output_array)

##############################################
[[[[ 36.  36.  36.  36.  36.  36.  36.  36.]
   [ 33.  33.  33.  33.  33.  33.  33.  33.]]

  [[117. 117. 117. 117. 117. 117. 117. 117.]
   [ 87.  87.  87.  87.  87.  87.  87.  87.]]]


 [[[198. 198. 198. 198. 198. 198. 198. 198.]
   [141. 141. 141. 141. 141. 141. 141. 141.]]

  [[279. 279. 279. 279. 279. 279. 279. 279.]
   [195. 195. 195. 195. 195. 195. 195. 195.]]]]

Total running time of the script: (0 minutes 1.354 seconds)

Gallery generated by Sphinx-Gallery