I have pretrained VGG16 based FCN-32s like model, defined like:
def pop_layer(model):
if not model.outputs:
raise Exception('Sequential model cannot be popped: model is empty.')
model.layers.pop()
if not model.layers:
model.outputs = []
model.inbound_nodes = []
model.outbound_nodes = []
else:
model.layers[-1].outbound_nodes = []
model.outputs = [model.layers[-1].output]
model.built = False
def get_model():
#Fully convolutional part of VGG16
model = VGG16(include_top=False, weights='imagenet')
#Remove last max pooling layer
pop_layer(model)
#Freeze pretrained layers
for layer in model.layers:
layer.trainable = False
model = Model(inputs=model.inputs, outputs=model.outputs)
#print('len(model.layers)', len(model.layers)) #
#print(model.summary()) #
x = Conv2D(512, (3, 3), activation='relu', padding='same')(model.output)
x = Conv2DTranspose(NUMBER_OF_CLASSES, kernel_size=(32, 32), strides=(16, 16), activation='sigmoid', padding='same')(x)
head = Reshape((-1,NUMBER_OF_CLASSES))(x)
model = Model(inputs=model.inputs, outputs=head)
model.compile(optimizer=Adadelta(), loss='binary_crossentropy')
print('len(model.layers)', len(model.layers)) #
print(model.summary()) #
return model
Model summary:
len(model.layers) 21
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_1 (InputLayer) (None, None, None, 3) 0
_________________________________________________________________
block1_conv1 (Conv2D) (None, None, None, 64) 1792
_________________________________________________________________
block1_conv2 (Conv2D) (None, None, None, 64) 36928
_________________________________________________________________
block1_pool (MaxPooling2D) (None, None, None, 64) 0
_________________________________________________________________
block2_conv1 (Conv2D) (None, None, None, 128) 73856
_________________________________________________________________
block2_conv2 (Conv2D) (None, None, None, 128) 147584
_________________________________________________________________
block2_pool (MaxPooling2D) (None, None, None, 128) 0
_________________________________________________________________
block3_conv1 (Conv2D) (None, None, None, 256) 295168
_________________________________________________________________
block3_conv2 (Conv2D) (None, None, None, 256) 590080
_________________________________________________________________
block3_conv3 (Conv2D) (None, None, None, 256) 590080
_________________________________________________________________
block3_pool (MaxPooling2D) (None, None, None, 256) 0
_________________________________________________________________
block4_conv1 (Conv2D) (None, None, None, 512) 1180160
_________________________________________________________________
block4_conv2 (Conv2D) (None, None, None, 512) 2359808
_________________________________________________________________
block4_conv3 (Conv2D) (None, None, None, 512) 2359808
_________________________________________________________________
block4_pool (MaxPooling2D) (None, None, None, 512) 0
_________________________________________________________________
block5_conv1 (Conv2D) (None, None, None, 512) 2359808
_________________________________________________________________
block5_conv2 (Conv2D) (None, None, None, 512) 2359808
_________________________________________________________________
block5_conv3 (Conv2D) (None, None, None, 512) 2359808
_________________________________________________________________
conv2d_1 (Conv2D) (None, None, None, 512) 2359808
_________________________________________________________________
conv2d_transpose_1 (Conv2DTr (None, None, None, 3) 1572867
_________________________________________________________________
reshape_1 (Reshape) (None, None, 3) 0
=================================================================
Total params: 18,647,363
Trainable params: 3,932,675
Non-trainable params: 14,714,688
_________________________________________________________________
None
But when I train model it only predict most dominant class, my dataset is unbalanced:
Pixel area per class ratio:
class1 : 62.93 %
class2 : 25.46 %
class3 : 11.61 %
So my questions are: is my model definition ok? How to deal with class inbalanced? maybe batch should be constructed in some special way?
It seems like your loss doesn't fit your problem. You use binary cross entropy loss here:
model.compile(optimizer=Adadelta(), loss='binary_crossentropy')
But you have more than two classes. So I would suggest to use categorical_crossentropy loss (appears in the list of losses here. Read on the bottom of the page how to prepare your data in order to use this loss).
There are additional types of losses which may fit better inbalance classes situation. You may try to use dice loss, which is a differential approximation of IoU (intersection over union).
This loss is described on page 6, section 3 here.
A common approach is to use class weighing for the loss function, so you can penalize the effect of the prominent class.
weight_class = 1/ln(c + class_probability)
where c is a constant and mostly used value 1.03.
Related
# Load data set
(X_train, _), (X_test, _) = fashion_mnist.load_data()
# Define the input shape
input_shape = (28, 28, 1)
latent_dim = 16
# Define the number of kernels for each convolutional layer
encoder_conv_kernels = [64, 32, 16]
decoder_conv_kernels = [16, 32, 64]
# Define the kernel size for all convolutional layers
kernel_size = (3, 3)
# Define the pool size for all max pooling layers
pool_size = (2, 2)
# Define the up sampling factors for all up sampling layers
up_sampling_factors = (2, 2)
# Define the encoder model
inputs = Input(shape=input_shape, name='encoder_input')
x = inputs
for filters in encoder_conv_kernels:
x = Conv2D(filters=filters,
kernel_size=kernel_size,
activation='relu',
padding='same')(x)
x = MaxPool2D(pool_size=pool_size)(x)
# Read and preserve the dimensions of the tensor
shape = backend.int_shape(x)
# Then we have a flattening layer
x = Flatten()(x)
latent_outputs = Dense(latent_dim, name='latent_vector')(x)
# Define the encoder model
encoder = Model(inputs=inputs, outputs=latent_outputs, name='encoder')
encoder.summary()
below is what I got from summary, as you can see from 7,7 it became 3,3:
Model: "encoder"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
encoder_input (InputLayer) [(None, 28, 28, 1)] 0
conv2d_111 (Conv2D) (None, 28, 28, 64) 640
max_pooling2d_51 (MaxPoolin (None, 14, 14, 64) 0
g2D)
conv2d_112 (Conv2D) (None, 14, 14, 32) 18464
max_pooling2d_52 (MaxPoolin (None, 7, 7, 32) 0
g2D)
conv2d_113 (Conv2D) (None, 7, 7, 16) 4624
max_pooling2d_53 (MaxPoolin (None, 3, 3, 16) 0
g2D)
flatten_10 (Flatten) (None, 144) 0
latent_vector (Dense) (None, 16) 2320
=================================================================
Total params: 26,048
Trainable params: 26,048
Non-trainable params: 0
_________________________________________________________________
But in decoder, from 3,3 it became 6,6 instead of 7,7
# Define the decoder model
# First we have the input layer
latent_inputs = Input(shape=(latent_dim,), name='decoder_input')
x = Dense(shape[1] * shape[2] * shape[3])(latent_inputs)
x = Reshape((shape[1], shape[2], shape[3]))(x)
for filters in decoder_conv_kernels[::-1]:
x = Conv2D(filters=filters,
kernel_size=kernel_size,
activation='relu',
padding='same')(x)
x = UpSampling2D(size=up_sampling_factors)(x)
# Define the output layer with sigmoid activation function
# then we add one more convolutional layer to control the channel dimension
x = Conv2D(filters=1,
kernel_size=kernel_size,
padding='same')(x)
# and one activation later with the sigmoid activation function
outputs = Activation('sigmoid', name='decoder_output')(x)
# Define the decoder model
decoder = Model(inputs=latent_inputs, outputs=outputs, name='decoder')
decoder.summary(line_length=110)
below is what I got from the summary:
Model: "decoder"
______________________________________________________________________________________________________________
Layer (type) Output Shape Param #
==============================================================================================================
decoder_input (InputLayer) [(None, 16)] 0
dense_11 (Dense) (None, 144) 2448
reshape_12 (Reshape) (None, 3, 3, 16) 0
conv2d_114 (Conv2D) (None, 3, 3, 64) 9280
up_sampling2d_48 (UpSampling2D) (None, 6, 6, 64) 0
conv2d_115 (Conv2D) (None, 6, 6, 32) 18464
up_sampling2d_49 (UpSampling2D) (None, 12, 12, 32) 0
conv2d_116 (Conv2D) (None, 12, 12, 16) 4624
up_sampling2d_50 (UpSampling2D) (None, 24, 24, 16) 0
conv2d_117 (Conv2D) (None, 24, 24, 1) 145
decoder_output (Activation) (None, 24, 24, 1) 0
==============================================================================================================
Total params: 34,961
Trainable params: 34,961
Non-trainable params: 0
______________________________________________________________________________________________________________
How can I make it from 3,3 to 7,7 instead of 6,6 in decoder? Thx!
what I expected to happen is in the decoder the output will be 28,28,1 instead of 24, 24,1
I am fairly new to Deep Learning, but I managed to build a multi-branch Image Classification architecture yielding quite satisfactory results.
Not so important: I am working on KKBox customer churn (https://kaggle.com/c/kkbox-churn-prediction-challenge/data) where I transformed customer behavior, transactions and static data into heatmaps and try to classify churners based on that.
The classification itself works just fine. My issue comes in when I try to apply LIME to see where the results are coming from. When following the code here: https://marcotcr.github.io/lime/tutorials/Tutorial%20-%20images.html with the exception that I use list of inputs [members[0],transactions[0],user_logs[0]], I get the following error: AttributeError: 'list' object has no attribute 'shape'
What springs to mind is that LIME is probably not made for multi-input architectures such as mine. On the other hand, Microsoft Azure have a multi-branch architecture as well (http://www.freepatentsonline.com/20180253637.pdf?fbclid=IwAR1j30etyDGPCmG-QGfb8qaGRysvnS_f5wLnKz-KdwEbp2Gk0_-OBsSepVc) and they allegedly use LIME to interpret their result (https://www.slideshare.net/FengZhu18/predicting-azure-churn-with-deep-learning-and-explaining-predictions-with-lime).
I have tried to concatenate the images into a single input but this sort of an approach yields far worse results than the multi-input one. LIME works for this approach though (even though not as comprehensibly as for usual image recognition).
The DNN architecture:
# Members
members_input = Input(shape=(61,4,3), name='members_input')
x1 = Dropout(0.2)(members_input)
x1 = Conv2D(32, kernel_size = (61,4), padding='valid', activation='relu', strides=1)(x1)
x1 = GlobalMaxPooling2D()(x1)
# Transactions
transactions_input = Input(shape=(61,39,3), name='transactions_input')
x2 = Dropout(0.2)(transactions_input)
x2 = Conv2D(32, kernel_size = (61,1,), padding='valid', activation='relu', strides=1)(x2)
x2 = Conv2D(32, kernel_size = (1,39,), padding='valid', activation='relu', strides=1)(x2)
x2 = GlobalMaxPooling2D()(x2)
# User logs
userlogs_input = Input(shape=(61,7,3), name='userlogs_input')
x3 = Dropout(0.2)(userlogs_input)
x3 = Conv2D(32, kernel_size = (61,1,), padding='valid', activation='relu', strides=1)(x3)
x3 = Conv2D(32, kernel_size = (1,7,), padding='valid', activation='relu', strides=1)(x3)
x3 = GlobalMaxPooling2D()(x3)
# User_logs + Transactions + Members
merged = keras.layers.concatenate([x1,x2,x3]) # Merged layer
out = Dense(2)(merged)
out_2 = Activation('softmax')(out)
model = Model(inputs=[members_input, transactions_input, userlogs_input], outputs=out_2)
model.compile(optimizer="adam", loss='categorical_crossentropy', metrics=['accuracy'])
model.summary()
The attempted LIME utilization:
explainer = lime_image.LimeImageExplainer()
explanation = explainer.explain_instance([members_test[0],transactions_test[0],user_logs_test[0]], model.predict, top_labels=2, hide_color=0, num_samples=1000)
Model summary:
__________________________________________________________________________________________________
Layer (type) Output Shape Param # Connected to
==================================================================================================
transactions_input (InputLayer) (None, 61, 39, 3) 0
__________________________________________________________________________________________________
userlogs_input (InputLayer) (None, 61, 7, 3) 0
__________________________________________________________________________________________________
members_input (InputLayer) (None, 61, 4, 3) 0
__________________________________________________________________________________________________
dropout_2 (Dropout) (None, 61, 39, 3) 0 transactions_input[0][0]
__________________________________________________________________________________________________
dropout_3 (Dropout) (None, 61, 7, 3) 0 userlogs_input[0][0]
__________________________________________________________________________________________________
dropout_1 (Dropout) (None, 61, 4, 3) 0 members_input[0][0]
__________________________________________________________________________________________________
conv2d_2 (Conv2D) (None, 1, 39, 32) 5888 dropout_2[0][0]
__________________________________________________________________________________________________
conv2d_4 (Conv2D) (None, 1, 7, 32) 5888 dropout_3[0][0]
__________________________________________________________________________________________________
conv2d_1 (Conv2D) (None, 1, 1, 32) 23456 dropout_1[0][0]
__________________________________________________________________________________________________
conv2d_3 (Conv2D) (None, 1, 1, 32) 39968 conv2d_2[0][0]
__________________________________________________________________________________________________
conv2d_5 (Conv2D) (None, 1, 1, 32) 7200 conv2d_4[0][0]
__________________________________________________________________________________________________
global_max_pooling2d_1 (GlobalM (None, 32) 0 conv2d_1[0][0]
__________________________________________________________________________________________________
global_max_pooling2d_2 (GlobalM (None, 32) 0 conv2d_3[0][0]
__________________________________________________________________________________________________
global_max_pooling2d_3 (GlobalM (None, 32) 0 conv2d_5[0][0]
__________________________________________________________________________________________________
concatenate_1 (Concatenate) (None, 96) 0 global_max_pooling2d_1[0][0]
global_max_pooling2d_2[0][0]
global_max_pooling2d_3[0][0]
__________________________________________________________________________________________________
dense_1 (Dense) (None, 2) 194 concatenate_1[0][0]
__________________________________________________________________________________________________
activation_1 (Activation) (None, 2) 0 dense_1[0][0]
==================================================================================================
Hence my question: does anyone have experience with multi-input DNN architecture and LIME? Is there a workaround I am not seeing? Is there another interpretable model I could use?
Thank you.
I'm new to Deep Learning. I'm trying to follow along with the fast.ai lecture series, and trying to reproduce the work manually in a Kaggle kernel.
I'm trying to work through cats vs. dogs Redux in Kaggle. I'm not concerned with accuracy, I just want to get something working.
I'm using Keras and the VGG16 model, as outlined in the fast.ai course. I'm also leaning on code outlined in this article to get me off the ground.
This is my Kaggle notebook.
I'm encountering an error when trying to fit my model that I don't know how to interpret:
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-26-596f25281fc2> in <module>()
12 #model.fit(input[0].transpose(), output[0].transpose())
13
---> 14 model.fit(X, Y, epochs=100, batch_size=6000, verbose=1)
/opt/conda/lib/python3.6/site-packages/Keras-2.1.2-py3.6.egg/keras/engine/training.py in fit(self, x, y, batch_size, epochs, verbose, callbacks, validation_split, validation_data, shuffle, class_weight, sample_weight, initial_epoch, steps_per_epoch, validation_steps, **kwargs)
1591 class_weight=class_weight,
1592 check_batch_axis=False,
-> 1593 batch_size=batch_size)
1594 # Prepare validation data.
1595 do_validation = False
/opt/conda/lib/python3.6/site-packages/Keras-2.1.2-py3.6.egg/keras/engine/training.py in _standardize_user_data(self, x, y, sample_weight, class_weight, check_batch_axis, batch_size)
1428 output_shapes,
1429 check_batch_axis=False,
-> 1430 exception_prefix='target')
1431 sample_weights = _standardize_sample_weights(sample_weight,
1432 self._feed_output_names)
/opt/conda/lib/python3.6/site-packages/Keras-2.1.2-py3.6.egg/keras/engine/training.py in _standardize_input_data(data, names, shapes, check_batch_axis, exception_prefix)
81 'Expected to see ' + str(len(names)) + ' array(s), '
82 'but instead got the following list of ' +
---> 83 str(len(data)) + ' arrays: ' + str(data)[:200] + '...')
84 elif len(names) > 1:
85 raise ValueError(
ValueError: Error when checking model target: the list of Numpy arrays that you are passing to your model is not the size the model expected. Expected to see 1 array(s), but instead got the following list of 24500 arrays: [array([[1],
[0]]), array([[1],
[0]]), array([[0],
[1]]), array([[1],
[0]]), array([[1],
[0]]), array([[1],
[0]]), array([[1],
[0]]), array([[0],
...
Here's some more information:
X = np.array([i[0] for i in train]).reshape(-1,IMG_SIZE,IMG_SIZE,3)
Y = [i[1] for i in train]
> type(X)
numpy.ndarray
> X.shape
(24500, 50, 50, 3)
> type(Y)
list
> len(Y)
24500
> Y[0]
[1 0]
> model.summary()
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_7 (InputLayer) (None, 50, 50, 3) 0
_________________________________________________________________
block1_conv1 (Conv2D) (None, 50, 50, 64) 1792
_________________________________________________________________
block1_conv2 (Conv2D) (None, 50, 50, 64) 36928
_________________________________________________________________
block1_pool (MaxPooling2D) (None, 25, 25, 64) 0
_________________________________________________________________
block2_conv1 (Conv2D) (None, 25, 25, 128) 73856
_________________________________________________________________
block2_conv2 (Conv2D) (None, 25, 25, 128) 147584
_________________________________________________________________
block2_pool (MaxPooling2D) (None, 12, 12, 128) 0
_________________________________________________________________
block3_conv1 (Conv2D) (None, 12, 12, 256) 295168
_________________________________________________________________
block3_conv2 (Conv2D) (None, 12, 12, 256) 590080
_________________________________________________________________
block3_conv3 (Conv2D) (None, 12, 12, 256) 590080
_________________________________________________________________
block3_pool (MaxPooling2D) (None, 6, 6, 256) 0
_________________________________________________________________
block4_conv1 (Conv2D) (None, 6, 6, 512) 1180160
_________________________________________________________________
block4_conv2 (Conv2D) (None, 6, 6, 512) 2359808
_________________________________________________________________
block4_conv3 (Conv2D) (None, 6, 6, 512) 2359808
_________________________________________________________________
block4_pool (MaxPooling2D) (None, 3, 3, 512) 0
_________________________________________________________________
block5_conv1 (Conv2D) (None, 3, 3, 512) 2359808
_________________________________________________________________
block5_conv2 (Conv2D) (None, 3, 3, 512) 2359808
_________________________________________________________________
block5_conv3 (Conv2D) (None, 3, 3, 512) 2359808
_________________________________________________________________
block5_pool (MaxPooling2D) (None, 1, 1, 512) 0
=================================================================
Total params: 14,714,688
Trainable params: 14,714,688
Non-trainable params: 0
_________________________________________________________________
And the model:
model = VGG16(weights='imagenet', include_top=False, input_shape=(img_rows, img_cols, img_channel))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
model.fit(X, Y, epochs=100, batch_size=6000, verbose=1)
I've googled around but I'm at a loss for how to interpret this. This SO question seems similar, and seems to indicate that the output is the problem, but I'm not sure how that would apply to me here.
You should simply transform your Y to a numpy array with shape (24500, 2):
Y = np.ndarray(Y)
I'm testing the convolutional autoencoder from the author of Keras just here :
https://blog.keras.io/building-autoencoders-in-keras.html
But I have this problem :
Exception: Error when checking model target: expected convolution2d_7 to have shape (None, 8, 32, 1) but got array with shape (60000, 1, 28, 28)
I precise, I already setted the field 'border_mode='same'' in the last conv layer.
So I really don't know from where it come from..
Here is the summary :
Layer (type) Output Shape Param # Connected to
====================================================================================================
input_1 (InputLayer) (None, 1, 28, 28) 0
____________________________________________________________________________________________________
convolution2d_1 (Convolution2D) (None, 1, 28, 16) 4048 input_1[0][0]
____________________________________________________________________________________________________
maxpooling2d_1 (MaxPooling2D) (None, 1, 14, 16) 0 convolution2d_1[0][0]
______________________________________________________________________________ ______________________
convolution2d_2 (Convolution2D) (None, 1, 14, 8) 1160 maxpooling2d_1[0][0]
____________________________________________________________________________________________________
maxpooling2d_2 (MaxPooling2D) (None, 1, 7, 8) 0 convolution2d_2[0][0]
____________________________________________________________________________________________________
convolution2d_3 (Convolution2D) (None, 1, 7, 8) 584 maxpooling2d_2[0][0]
____________________________________________________________________________________________________
maxpooling2d_3 (MaxPooling2D) (None, 1, 4, 8) 0 convolution2d_3[0][0]
____________________________________________________________________________________________________
convolution2d_4 (Convolution2D) (None, 1, 4, 8) 584 maxpooling2d_3[0][0]
____________________________________________________________________________________________________
upsampling2d_1 (UpSampling2D) (None, 2, 8, 8) 0 convolution2d_4[0][0]
____________________________________________________________________________________________________
convolution2d_5 (Convolution2D) (None, 2, 8, 8) 584 upsampling2d_1[0][0]
____________________________________________________________________________________________________
upsampling2d_2 (UpSampling2D) (None, 4, 16, 8) 0 convolution2d_5[0][0]
____________________________________________________________________________________________________
convolution2d_6 (Convolution2D) (None, 4, 16, 16) 1168 upsampling2d_2[0][0]
____________________________________________________________________________________________________
upsampling2d_3 (UpSampling2D) (None, 8, 32, 16) 0 convolution2d_6[0][0]
______________________________________________________________________________ ______________________
convolution2d_7 (Convolution2D) (None, 8, 32, 1) 145
upsampling2d_3[0][0]
====================================================================================================
Total params: 8273
____________________________________________________________________________________________________
Finally found the answer.
I think the creator of the tutorial tested it with 32x32 MNIST images and not 28x28.
Because, when adding to the last conv layer border_mode='same', you get an output shape of (32,32,1)
So, to get the good output (28,28,1) you need to add border_mode='valid' to the before last conv layer.
In summary :
Correct the dimension ordering to 28x28x1 instead of 1x28x28.
Then add border mode same to the last conv layer
And finally add border mode valid to the last conv layer.
Hope this will help.
I'm adapting a CNN model that was designed around the CIFAR-10 dataset.
Images in CIFAR-10 are 32x32. My data set has images that are irregularly shaped, 192x108.
The initial convolution layer has 32 filters, and a 3x3 kernel size, increasing to 64 and then 128 on later layers.
Is it best practice to increase the number of filters, and/or kernel size, if the image size increases? If so what heuristic should I use?
Does the kernel need to remain symetrical?
Below is my model definition:
Using TensorFlow backend.
____________________________________________________________________________________________________
Layer (type) Output Shape Param # Connected to
====================================================================================================
convolution2d_1 (Convolution2D) (None, 32, 192, 108) 896 convolution2d_input_1[0][0]
____________________________________________________________________________________________________
dropout_1 (Dropout) (None, 32, 192, 108) 0 convolution2d_1[0][0]
____________________________________________________________________________________________________
convolution2d_2 (Convolution2D) (None, 32, 192, 108) 9248 dropout_1[0][0]
____________________________________________________________________________________________________
maxpooling2d_1 (MaxPooling2D) (None, 32, 96, 54) 0 convolution2d_2[0][0]
____________________________________________________________________________________________________
convolution2d_3 (Convolution2D) (None, 64, 96, 54) 18496 maxpooling2d_1[0][0]
____________________________________________________________________________________________________
dropout_2 (Dropout) (None, 64, 96, 54) 0 convolution2d_3[0][0]
____________________________________________________________________________________________________
convolution2d_4 (Convolution2D) (None, 64, 96, 54) 36928 dropout_2[0][0]
____________________________________________________________________________________________________
maxpooling2d_2 (MaxPooling2D) (None, 64, 48, 27) 0 convolution2d_4[0][0]
____________________________________________________________________________________________________
convolution2d_5 (Convolution2D) (None, 128, 48, 27) 73856 maxpooling2d_2[0][0]
____________________________________________________________________________________________________
dropout_3 (Dropout) (None, 128, 48, 27) 0 convolution2d_5[0][0]
____________________________________________________________________________________________________
convolution2d_6 (Convolution2D) (None, 128, 48, 27) 147584 dropout_3[0][0]
____________________________________________________________________________________________________
maxpooling2d_3 (MaxPooling2D) (None, 128, 24, 13) 0 convolution2d_6[0][0]
____________________________________________________________________________________________________
flatten_1 (Flatten) (None, 39936) 0 maxpooling2d_3[0][0]
____________________________________________________________________________________________________
dropout_4 (Dropout) (None, 39936) 0 flatten_1[0][0]
____________________________________________________________________________________________________
dense_1 (Dense) (None, 1024) 40895488 dropout_4[0][0]
____________________________________________________________________________________________________
dropout_5 (Dropout) (None, 1024) 0 dense_1[0][0]
____________________________________________________________________________________________________
dense_2 (Dense) (None, 512) 524800 dropout_5[0][0]
____________________________________________________________________________________________________
dropout_6 (Dropout) (None, 512) 0 dense_2[0][0]
____________________________________________________________________________________________________
dense_3 (Dense) (None, 3) 1539 dropout_6[0][0]
====================================================================================================
Total params: 41708835