3

At my job Interview yesterday, I was asked to build a neural network using TesnorFlow in python to classify images from the flowers images dataset.

But even though it should've worked theoretically, for some reason I couldn't increase the accuracy above 20s%.

Python Version: 3.8.13 TensorFlow Versioin: 2.4.1

The data preprocessing methods from the interviewer were given as follows:

# create datase
IMG_SIZE = 160
BATCH_SIZE = 32
AUTOTUNE = tf.data.experimental.AUTOTUNE
def _parse_data(x,y):
  image = tf.io.read_file(x)  
  image = tf.image.decode_jpeg(image, channels=3) 
  image = tf.cast(image, dtype=tf.float32)
  image = tf.math.l2_normalize(image)
  
  image = tf.image.resize(image, (IMG_SIZE, IMG_SIZE))
  return image,y
 
def _input_fn(x,y):
  ds = tf.data.Dataset.from_tensor_slices((x,y))
  ds = ds.map(_parse_data)
  ds = ds.shuffle(buffer_size=data_size)
  ds = ds.repeat()   
  ds = ds.batch(BATCH_SIZE)    
  ds = ds.prefetch(buffer_size=AUTOTUNE)   
  return ds

train_ds = _input_fn(x_train, y_train)
validation_ds = _input_fn(x_valid, y_valid)  

With both training and validation datasets being

<PrefetchDataset shapes: ((None, 160, 160, 3), (None,)), types: (tf.float32, tf.int32)>

With the network being as follows:

from tensorflow.keras import datasets, layers, models

model_seq = models.Sequential()
    model_seq.add(layers.experimental.preprocessing.RandomFlip("horizontal",input_shape=(IMG_SIZE,IMG_SIZE,3)))
    model_seq.add(layers.experimental.preprocessing.RandomRotation(0.2))
    model_seq.add(layers.experimental.preprocessing.Rescaling(1./255))
    model_seq.add(layers.Conv2D(16, 3, padding='same', activation='relu'))
    model_seq.add(layers.MaxPooling2D())
    model_seq.add(layers.Conv2D(32, 3, padding='same', activation='relu'))
    model_seq.add(layers.MaxPooling2D())
    model_seq.add(layers.Conv2D(64, 3, padding='same', activation='relu'))
    model_seq.add(layers.MaxPooling2D())
    model_seq.add(layers.Dropout(0.2))
    model_seq.add(layers.Flatten())
    model_seq.add(layers.Dense(128, activation='relu'))
    model_seq.add(layers.Dense(len(label_names), activation='softmax'))
    model_seq.summary()

The output layer being the only thing that isn't allowed to be change.

model_seq.add(layers.Dense(len(label_names), activation='softmax'))

(Please note I was for some reason asked to use model_seq.add(), and even though it could be triggering for some of you, please ignore it this once :) )

For compiling the model, I used the following:

model_seq.compile(optimizer="Adam",
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
              metrics=['accuracy'])

And for fitting the model:

history = model_seq.fit(train_ds,epochs=20,
                        validation_data = validation_ds,
                        steps_per_epoch=100,validation_steps=100)

The things I've tried:

  1. Using different Augmentation methods (or removing the whole section from the network).

  2. Changing the Batch and Image sizes.

  3. Using Dropout layers.

  4. Using early stopping as follows:

    callback = tf.keras.callbacks.EarlyStopping(
        monitor='val_loss', 
        min_delta=0, patience=3, verbose=0, 
        mode='auto',baseline=None, 
        restore_best_weights=True)
    
    history = model_seq.fit(train_ds,epochs=20,
                            validation_data = validation_ds,
                            steps_per_epoch=100,validation_steps=100,
                            callbacks = [callback])
    

Yet despite all of the above, I couldn't get any results. Since I couldn't find out what I did wrong exactly, I'm hoping someone here could tell me, so I could learn from this experience. (Please take into consideration that I wasn't allowed to change the preprocessing functions, with the parameters IMG_SIZE and BATCH_SIZE being the only exception).

I'mahdi
  • 23,382
  • 5
  • 22
  • 30
Virgal K
  • 43
  • 6
  • Do you want another solution without your code but on tensorflow? – I'mahdi Jun 25 '22 at 11:02
  • Tbh at this point anything would be appreciated. Another solution, some hints, or even telling me what i did wrong would be fine. :) – Virgal K Jun 25 '22 at 11:05
  • One mistake I can see is that you're augmenting data for both train and validation data, which you are not supposed to do. Do it only for the train data. – Vishal Balaji Jun 25 '22 at 11:07
  • 1
    I don't know if this will help, but here is an article that might be of use to you https://stats.stackexchange.com/questions/352036/what-should-i-do-when-my-neural-network-doesnt-learn – FlippingTook Jun 25 '22 at 11:08
  • @VishalBalaji could you maybe tell me where exactly? Because for some reason, i don't seem to see it. Thanks in advance! – Virgal K Jun 25 '22 at 11:11
  • @EliasALICHE Thanks for the link !! I didn't know you could test a neural network, i'll make sure to try it ! :) – Virgal K Jun 25 '22 at 11:11
  • 2
    @VirgalK, did you try to use `tf.keras.losses.SparseCategoricalCrossentropy()`?, from the [docs](https://www.tensorflow.org/api_docs/python/tf/keras/losses/SparseCategoricalCrossentropy), `from_logits=True` is used when `y_pred` is a logits tensor. The output of the [softmax](https://keras.io/api/layers/activations/#softmax-function) layer is not a logits tensor. – Iran Ribeiro Jun 25 '22 at 11:13
  • 2
    @VirgalK Comments are not conducive to this, but you've defined some flipping and rotation at the start of `model_seq`. This is applied to both the train and the val data. You're supposed to do augmentation only for train data. I also noticed that you're doing some normalization when you're getting the image itself with `tf.math.l2_normalize`. You are again rescaling with `1/255.` inside your model. Not sure how exactly this will affect your model because I haven't done it before, but this is something you should definitely look into. – Vishal Balaji Jun 25 '22 at 11:16
  • @IranRibeiro I actually did use it from the docs as a force of habit. Do you reckon that removing it would be enough, or should I go for another loss? – Virgal K Jun 25 '22 at 11:17
  • @VishalBalaji oh now i see it! I'll try to fix it !! Thanks again ! – Virgal K Jun 25 '22 at 11:18
  • 1
    I think removing the `from_logits=True` part it will do it. But you should try to do what @VishalBalaji said, too. – Iran Ribeiro Jun 25 '22 at 11:23
  • 1
    Yes, from_logits=True is incorrect in this case – Dr. Snoopy Jun 25 '22 at 13:07

1 Answers1

1

“TLDR: If you want to use their preprocessing part and don't change anything go to First Approach. If you want to augment images, Go to Second Approach and use ImageDataGenerator”.

First Approach As you say: I had to use the preprocessing functions and don't change this and because I don't access your data, I use cifar10 dataset and use your preprocessing part. (only line of reading from file changed).

  1. Because you shouldn't change IMG_SIZE=160 in the preprocessing part, I add this layer : tf.keras.layers.Lambda(lambda image: tf.image.resize(image, (32, 32)))) to the network because working with large images causes a crash.
  2. You don't need a very large network, first check with a small network then step by step add parameters then add layers.

We can get a better result like the below: (On cifar10 with your network I get 10% accuracy like you.)

import tensorflow as tf
(X_train, y_train), (X_test, y_test) = tf.keras.datasets.cifar10.load_data()

# create datase
IMG_SIZE = 160
BATCH_SIZE = 32
data_size = 32
AUTOTUNE = tf.data.experimental.AUTOTUNE
def _parse_data(x,y):
#   image = tf.io.read_file(x) <- because don't read from path 
  image = x
#   image = tf.image.decode_jpeg(image, channels=3) <- because don't read from path and don't have jpeg
  image = tf.cast(image, dtype=tf.float32)
  image = tf.math.l2_normalize(image)

  image = tf.image.resize(image, (IMG_SIZE, IMG_SIZE))
  return image,y

def _input_fn(x,y):
  ds = tf.data.Dataset.from_tensor_slices((x,y))
  ds = ds.map(_parse_data)
  ds = ds.shuffle(buffer_size=data_size)
  ds = ds.repeat()   
  ds = ds.batch(BATCH_SIZE)    
  ds = ds.prefetch(buffer_size=AUTOTUNE)   
  return ds

train_ds = _input_fn(X_train, y_train)
validation_ds = _input_fn(X_test, y_test) 

model = tf.keras.Sequential([
    tf.keras.Input(shape=(160, 160, 3)),
    tf.keras.layers.Lambda(lambda image: tf.image.resize(image, (32, 32))),
    tf.keras.layers.Conv2D(filters=32,kernel_size=(3,3),activation='relu'),
    tf.keras.layers.MaxPooling2D(2,2),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(50,activation='relu'),
    tf.keras.layers.Dense(10,activation='softmax')

])

model.compile(optimizer="Adam",
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
              metrics=['accuracy'])

history = model.fit(train_ds,epochs=4,
                        validation_data = validation_ds,
                        steps_per_epoch=100,validation_steps=100)

Output:

Epoch 1/4
100/100 [==============================] - 8s 38ms/step - loss: 2.2445 - accuracy: 0.1375 - val_loss: 2.1406 - val_accuracy: 0.2138
Epoch 2/4
100/100 [==============================] - 3s 33ms/step - loss: 2.0552 - accuracy: 0.2688 - val_loss: 1.9764 - val_accuracy: 0.3250
Epoch 3/4
100/100 [==============================] - 4s 38ms/step - loss: 1.9468 - accuracy: 0.3022 - val_loss: 1.9014 - val_accuracy: 0.3200
Epoch 4/4
100/100 [==============================] - 4s 36ms/step - loss: 1.8936 - accuracy: 0.3341 - val_loss: 1.8883 - val_accuracy: 0.3419

Second Approach: Augment images with ImageDataGenerator:

import tensorflow as tf

flowers = tf.keras.utils.get_file(
    'flower_photos',
    'https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz',
    untar=True)


data_gen = tf.keras.preprocessing.image.ImageDataGenerator(
    rescale=1./255, 
    rotation_range = 10,    # Degree range for random rotations.
    horizontal_flip = True, # Randomly flip inputs horizontally.
    vertical_flip = True,   # Randomly flip inputs vertically.
    )
    
imgs_dataset = data_gen.flow_from_directory(flowers, class_mode='categorical', 
                                            target_size=(160, 160), batch_size=32,
                                            shuffle=True)


model = tf.keras.Sequential([
    tf.keras.layers.Conv2D(filters=32,kernel_size=(3,3),input_shape=(160, 160, 3),activation='relu'),
    tf.keras.layers.MaxPooling2D(2,2),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(50,activation='relu'),
    tf.keras.layers.Dense(5,activation='softmax')

])
model.compile(optimizer='adam', loss='categorical_crossentropy',metrics=['accuracy'])

model.fit(imgs_dataset,epochs=5)

Output:

Found 3670 images belonging to 5 classes.
Epoch 1/5
115/115 [==============================] - 39s 311ms/step - loss: 1.7904 - accuracy: 0.4161
Epoch 2/5
115/115 [==============================] - 27s 236ms/step - loss: 1.0878 - accuracy: 0.5605
Epoch 3/5
115/115 [==============================] - 28s 244ms/step - loss: 1.0252 - accuracy: 0.6005
Epoch 4/5
115/115 [==============================] - 27s 233ms/step - loss: 0.9735 - accuracy: 0.6196
Epoch 5/5
115/115 [==============================] - 29s 248ms/step - loss: 0.9313 - accuracy: 0.6455
I'mahdi
  • 23,382
  • 5
  • 22
  • 30
  • I usually do use the ImageDataGenerator but unfortunately as I stated in the question during this interview I had to use the preprocessing functions given and only build a network that would work with them :( – Virgal K Jun 25 '22 at 11:21
  • @VirgalK, OK No problem, I will send another approach with preprocessing of you – I'mahdi Jun 25 '22 at 11:22
  • Do they give you a dataset?? – I'mahdi Jun 25 '22 at 11:25
  • @l'mahdi yes the data set consist of 2 columns first one being the image path second being the class The image path is then processed through _parse_data() into an image tensor that is used to create the dataset – Virgal K Jun 25 '22 at 11:30
  • I know, because I see this: `tf.io.read_file(x)`, then how do we run your code? without having image and path – I'mahdi Jun 25 '22 at 11:31
  • @VirgalK, where is this code of question you write and you want edit this to get better result? – I'mahdi Jun 25 '22 at 11:35
  • that's a good question. The dataset used is the same flowers one you're using, and they just extracted it locally and loaded it. Unfortunately, I don't have the data because they were provided in the interview, but I believe just extracting the same dataset you're using ant iterating over the folder using blob would give the same dataset. – Virgal K Jun 25 '22 at 11:35
  • @VirgalK, I add the first approach with your preprocessing part, please check this. Do you get a better result? – I'mahdi Jun 25 '22 at 12:14