Introduction

A first attempt at a deep learning model. Most of the code is from: https://shirinsplayground.netlify.com/2018/06/keras_fruits/

The dataset is a fruit images dataset from Kaggle: https://www.kaggle.com/moltean/fruits/data

This code will build a neural network to classify fruit from images

Setup

# list of fruits to model
fruit_list <- c("Kiwi", "Banana", "Apricot", "Avocado", "Cocos", "Clementine", "Mandarine", "Orange",
                "Limes", "Lemon", "Peach", "Plum", "Raspberry", "Strawberry", "Pineapple", "Pomegranate")

# number of output classes (i.e. fruits)
output_n <- length(fruit_list)

# image size to scale down to (original images are 100 x 100 px)
img_width <- 20
img_height <- 20
target_size <- c(img_width, img_height)

# RGB = 3 channels
channels <- 3

# path to image folders
train_image_files_path <- "datasets/fruits-360_dataset/fruits-360/Training/"
valid_image_files_path <- "datasets/fruits-360_dataset/fruits-360/Test/"

Loading Images

“Augmentation” creates variations of the training data.

# optional data augmentation
train_data_gen = image_data_generator(
  rescale = 1/255 #,
  #rotation_range = 40,
  #width_shift_range = 0.2,
  #height_shift_range = 0.2,
  #shear_range = 0.2,
  #zoom_range = 0.2,
  #horizontal_flip = TRUE,
  #fill_mode = "nearest"
)

# Never use augmentation on test data, but do rescale!
test_data_gen = image_data_generator(
  rescale = 1/255 #,
)

Load into memory and resize.

# training images
train_image_array_gen <- flow_images_from_directory(train_image_files_path, 
                                          train_data_gen,
                                          target_size = target_size,
                                          class_mode = "categorical",
                                          classes = fruit_list,
                                          seed = 42)

# validation images
valid_image_array_gen <- flow_images_from_directory(valid_image_files_path, 
                                          test_data_gen,
                                          target_size = target_size,
                                          class_mode = "categorical",
                                          classes = fruit_list,
                                          seed = 42)
typeof(train_data_gen)
## [1] "environment"
names(train_image_array_gen)
##  [1] "allowed_class_modes"  "batch_index"          "batch_size"          
##  [4] "class_indices"        "class_mode"           "classes"             
##  [7] "color_mode"           "data_format"          "directory"           
## [10] "dtype"                "filenames"            "filepaths"           
## [13] "image_data_generator" "image_shape"          "index_array"         
## [16] "index_generator"      "interpolation"        "labels"              
## [19] "lock"                 "n"                    "next"                
## [22] "num_classes"          "on_epoch_end"         "reset"               
## [25] "sample_weight"        "samples"              "save_format"         
## [28] "save_prefix"          "save_to_dir"          "seed"                
## [31] "set_processing_attrs" "shuffle"              "split"               
## [34] "subset"               "target_size"          "total_batches_seen"  
## [37] "white_list_formats"
table(factor(train_image_array_gen$classes))
## 
##   0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15 
## 466 490 492 427 490 490 490 479 490 492 492 447 490 492 490 492

Store the fruit name to index mapping:

fruits_classes_indices <- train_image_array_gen$class_indices

Define Keras model

# number of training samples
train_samples <- train_image_array_gen$n
# number of validation samples
valid_samples <- valid_image_array_gen$n

# define batch size and number of epochs
batch_size <- 32
epochs <- 10
# initialise model
model <- keras_model_sequential()

# add layers
model %>%
  layer_conv_2d(filter = 32, kernel_size = c(3,3), padding = "same", input_shape = c(img_width, img_height, channels)) %>%
  layer_activation("relu") %>%
  
  # Second hidden layer
  layer_conv_2d(filter = 16, kernel_size = c(3,3), padding = "same") %>%
  layer_activation_leaky_relu(0.5) %>%
  layer_batch_normalization() %>%

  # Use max pooling
  layer_max_pooling_2d(pool_size = c(2,2)) %>%
  layer_dropout(0.25) %>%
  
  # Flatten max filtered output into feature vector 
  # and feed into dense layer
  layer_flatten() %>%
  layer_dense(100) %>%
  layer_activation("relu") %>%
  layer_dropout(0.5) %>%

  # Outputs from dense layer are projected onto output layer
  layer_dense(output_n) %>% 
  layer_activation("softmax")

Show the structure of the neural network:

model
## Model
## Model: "sequential"
## ___________________________________________________________________________
## Layer (type)                     Output Shape                  Param #     
## ===========================================================================
## conv2d (Conv2D)                  (None, 20, 20, 32)            896         
## ___________________________________________________________________________
## activation (Activation)          (None, 20, 20, 32)            0           
## ___________________________________________________________________________
## conv2d_1 (Conv2D)                (None, 20, 20, 16)            4624        
## ___________________________________________________________________________
## leaky_re_lu (LeakyReLU)          (None, 20, 20, 16)            0           
## ___________________________________________________________________________
## batch_normalization (BatchNormal (None, 20, 20, 16)            64          
## ___________________________________________________________________________
## max_pooling2d (MaxPooling2D)     (None, 10, 10, 16)            0           
## ___________________________________________________________________________
## dropout (Dropout)                (None, 10, 10, 16)            0           
## ___________________________________________________________________________
## flatten (Flatten)                (None, 1600)                  0           
## ___________________________________________________________________________
## dense (Dense)                    (None, 100)                   160100      
## ___________________________________________________________________________
## activation_1 (Activation)        (None, 100)                   0           
## ___________________________________________________________________________
## dropout_1 (Dropout)              (None, 100)                   0           
## ___________________________________________________________________________
## dense_1 (Dense)                  (None, 16)                    1616        
## ___________________________________________________________________________
## activation_2 (Activation)        (None, 16)                    0           
## ===========================================================================
## Total params: 167,300
## Trainable params: 167,268
## Non-trainable params: 32
## ___________________________________________________________________________

Output layer has 16 neurons, for 16 fruit types.

# compile
model %>% compile(
  loss = "categorical_crossentropy",
  optimizer = optimizer_rmsprop(lr = 0.0001, decay = 1e-6),
  metrics = "accuracy"
)

Note: there is a warning because I don’t have the exact right specifications for TensorFlow, because it was not built from source.

Fit the model:

# fit
hist <- model %>% fit_generator(
  # training data
  train_image_array_gen,
  
  # epochs
  steps_per_epoch = as.integer(train_samples / batch_size), 
  epochs = epochs, 
  
  # validation data
  validation_data = valid_image_array_gen,
  validation_steps = as.integer(valid_samples / batch_size),
  
  # print progress
  verbose = 2,
  callbacks = list(
    # save best model after every epoch
    callback_model_checkpoint("tmp/fruits", save_best_only = TRUE),
    # only needed for visualising with TensorBoard
    callback_tensorboard(log_dir = "tmp/fruits")
  )
)

View Results

plot(hist)

tensorboard("tmp/fruits")
## Started TensorBoard at http://127.0.0.1:5073