Joeyboy
Joeyboy

Reputation: 472

Target array shape different to expected output using Tensorflow

I'm trying to make a CNN (still a beginner). When trying to fit the model I am getting this error:

ValueError: A target array with shape (10000, 10) was passed for output of shape (None, 6, 6, 10) while using as loss categorical_crossentropy. This loss expects targets to have the same shape as the output.

The shape of labels = (10000, 10) the shape of the image data = (10000, 32, 32, 3)

Code:

import pickle
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import (Dense, Dropout, Activation, Flatten, 
                                     Conv2D, MaxPooling2D)
from tensorflow.keras.callbacks import TensorBoard
from keras.utils import to_categorical
import numpy as np
import time

MODEL_NAME = f"_________{int(time.time())}"
BATCH_SIZE = 64

class ConvolutionalNetwork():
    '''
    A convolutional neural network to be used to classify images
    from the CIFAR-10 dataset.
    '''

    def __init__(self):
        '''
        self.training_images -- a 10000x3072 numpy array of uint8s. Each 
                                a row of the array stores a 32x32 colour image. 
                                The first 1024 entries contain the red channel 
                                values, the next 1024 the green, and the final 
                                1024 the blue. The image is stored in row-major 
                                order, so that the first 32 entries of the array are the red channel values of the first row of the image.
        self.training_labels -- a list of 10000 numbers in the range 0-9. 
                                The number at index I indicates the label 
                                of the ith image in the array data.
        '''
        # List of image categories
        self.label_names = (self.unpickle("cifar-10-batches-py/batches.meta",
                            encoding='utf-8')['label_names'])

        self.training_data = self.unpickle("cifar-10-batches-py/data_batch_1")
        self.training_images = self.training_data[b'data']
        self.training_labels = self.training_data[b'labels']

        # Reshaping the images + scaling 
        self.shape_images()  

        # Converts labels to one-hot
        self.training_labels = np.array(to_categorical(self.training_labels))

        self.create_model()

        self.tensorboard = TensorBoard(log_dir=f'logs/{MODEL_NAME}')

    def unpickle(self, file, encoding='bytes'):
        '''
        Unpickles the dataset files.
        '''
        with open(file, 'rb') as fo:
            training_dict = pickle.load(fo, encoding=encoding)
        return training_dict

    def shape_images(self):
        '''
        Reshapes the images and scales by 255.
        '''
        images = list()
        for d in self.training_images:
            image = np.zeros((32,32,3), dtype=np.uint8)
            image[...,0] = np.reshape(d[:1024], (32,32)) # Red channel
            image[...,1] = np.reshape(d[1024:2048], (32,32)) # Green channel
            image[...,2] = np.reshape(d[2048:], (32,32)) # Blue channel
            images.append(image)

        for i in range(len(images)):
            images[i] = images[i]/255

        images = np.array(images)
        self.training_images = images
        print(self.training_images.shape)

    def create_model(self):
        '''
        Creating the ConvNet model.
        '''
        self.model = Sequential()
        self.model.add(Conv2D(64, (3, 3), input_shape=self.training_images.shape[1:]))
        self.model.add(Activation("relu"))
        self.model.add(MaxPooling2D(pool_size=(2,2)))

        self.model.add(Conv2D(64, (3,3)))
        self.model.add(Activation("relu"))
        self.model.add(MaxPooling2D(pool_size=(2,2)))

        # self.model.add(Flatten())
        # self.model.add(Dense(64))
        # self.model.add(Activation('relu'))

        self.model.add(Dense(10))
        self.model.add(Activation(activation='softmax'))

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

    def train(self):
        '''
        Fits the model.
        '''
        print(self.training_images.shape)
        print(self.training_labels.shape)
        self.model.fit(self.training_images, self.training_labels, batch_size=BATCH_SIZE, 
                       validation_split=0.1, epochs=5, callbacks=[self.tensorboard])


network = ConvolutionalNetwork()
network.train()

Would appreciate the help, have been trying to fix for an hour.

Upvotes: 6

Views: 7256

Answers (4)

Oleg Vorobiov
Oleg Vorobiov

Reputation: 512

Un-comment the flatten layer before your output layer in create_model(self), conv layers don't work with 1D tensors/arrays, and so for you to get the output layer of the right shape to add a Flatten() layer right before your output layer, like this:

def create_model(self):
        '''
        Creating the ConvNet model.
        '''
        self.model = Sequential()
        self.model.add(Conv2D(64, (3, 3), input_shape=self.training_images.shape[1:]), activation='relu')
        #self.model.add(Activation("relu"))
        self.model.add(MaxPooling2D(pool_size=(2,2)))

        self.model.add(Conv2D(64, (3,3), activation='relu'))
        #self.model.add(Activation("relu"))
        self.model.add(MaxPooling2D(pool_size=(2,2)))

        # self.model.add(Dense(64))
        # self.model.add(Activation('relu'))
        self.model.add(Flatten())

        self.model.add(Dense(10, activation='softmax'))
        #self.model.add(Activation(activation='softmax'))

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

        print ('model output shape:', self.model.output_shape)#prints out the output shape of your model

The code above will give you a model with an output shape of (None, 10).

Also please use activation as a layer parameter in the future.

Upvotes: 3

Stewart_R
Stewart_R

Reputation: 14485

You have to get your model output into the same shape as your labels.

Perhaps the simplest solution would be to ensure the model ends with these layers:

model.add(Flatten())
## possibly an extra dense layer or 2 with 'relu' activation
model.add(Dense(10, activation=`softmax`))

This is amongst the most common 'endings' to a categorisation model and is arguably the most straightforward to understand.

It's not clear why you commented out this section:

# self.model.add(Flatten())
# self.model.add(Dense(64))
# self.model.add(Activation('relu'))

which would appear to give you the required output shape?

Upvotes: 1

Pedro Marques
Pedro Marques

Reputation: 2682

Use model.summary() to inspect the output shapes of your model. Without the commented out Flatten() layer the shapes of your layers retain the original dimensions of the image and the shape of the output layer is (None, 6, 6, 10).

What you want to do here is roughly:

  1. start with a shape of (batch_size, img width, img heigh, channels)
  2. use convolutions to detect patterns through the image by applying a filter
  3. reduce the img width and height with max pooling
  4. then Flatten() the dimensions of the image so that instead of (width, heigh, features) you end up with just a set of features.
  5. match against your classes.

The commented out code does step 4; when you remove the Flatten() layer you end up with the wrong set of dimensions at the end.

Upvotes: 2

Djib2011
Djib2011

Reputation: 7432

You need to uncomment the Flatten layer when creating your model. Essentially what this layer does is that it takes a 4D input (batch_size, height, width, num_filters) and unrolls it into a 2D one (batch_size, height * width * num_filters). This is needed to get the output shape you want.

Upvotes: 6

Related Questions