Note
Go to the end to download the full example code. or to run this example in your browser via Binder
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)