<p><div><div><div><div data-show-faces="false" data-height="21" data-width="100" data-layout="button_count" data-send="true" data-href="https://www.r-bloggers.com/r-vs-python-image-classification-with-keras/"></div></div><div></div></div><div></div></div>
<div>
(This article was first published on <strong><a href="https://datascienceplus.com/r-vs-python-image-classification-with-keras/"> R Programming – DataScience+</a></strong>, and kindly contributed to <a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers)</a> </div>
<p>Even though the libraries for <a rel="nofollow" href="https://rpy2.bitbucket.io/" target="_blank">R from Python</a>, or <a rel="nofollow" href="https://rstudio.github.io/reticulate/" target="_blank">Python from R</a> code execution existed since years and despite of a recent <a rel="nofollow" href="http://wesmckinney.com/blog/announcing-ursalabs/" target="_blank">announcement</a> of <a rel="nofollow" href="https://ursalabs.org/" target="_blank">Ursa Labs</a> foundation by <a rel="nofollow" href="http://wesmckinney.com/" target="_blank">Wes McKinney</a> who is aiming to join forces with RStudio foundation, <a rel="nofollow" href="http://hadley.nz/" target="_blank">Hadley Wickham</a> in particularly, (find more <a rel="nofollow" href="https://qz.com/1270139/r-and-python-are-joining-forces-in-the-most-ambitious-crossover-event-of-the-year-for-programmers/" target="_blank">here</a>) to improve data scientists workflow and unify libraries to be used not only in Python, but in any programming language used by data scientists, some data professionals are still very strict on the language to be used for ANN models limiting their dev. environment exclusively to Python. </p>
<p>As a continuation of my <a rel="nofollow" href="https://www.dkisler.de/2018/02/19/r-versus-python-loops-required-time/" target="_blank">R vs. Python comparison</a>, I decided to test performance of both languages in terms of time required to train a <a rel="nofollow" href="http://cs231n.github.io/convolutional-networks/" target="_blank">convolutional neural network</a> based model for image recognition. As the starting point, I took <a rel="nofollow" href="https://shirinsplayground.netlify.com/2018/06/keras_fruits/" target="_blank">the blog post</a> by <a rel="nofollow" href="https://www.linkedin.com/in/shirin-glander-01120881/" target="_blank">Dr. Shirin Glander</a> on how easy it is to build a CNN model in R using Keras.</p>
<p>A few words about <a rel="nofollow" href="https://keras.io/" target="_blank">Keras</a>. It is a Python library for artificial neural network ML models which provides high level fronted to various deep learning frameworks with <a rel="nofollow" href="https://www.tensorflow.org/" target="_blank">Tensorflow</a> being the default one.<br>
Keras has many pros with the following among the others:</p>
<ul>
<li>Easy to build complex models just in few lines of code => perfect for dev. cycle to quickly experiment and check your ideas</li>
<li>Code recycling: one can easily swap the backend framework (let’s say from <a rel="nofollow" href="https://docs.microsoft.com/en-ca/cognitive-toolkit/" target="_blank">CNTK</a> to <a rel="nofollow" href="https://www.tensorflow.org/" target="_blank">Tensorflow</a> or vice versa) => <a rel="nofollow" href="https://en.wikipedia.org/wiki/Don%27t_repeat_yourself" target="_blank">DRY principle</a></li>
<li>Seamless use of GPU => perfect for fast model tuning and experimenting</li>
</ul>
<p>Since Keras is written in Python, it may be a natural choice for your dev. environment to use Python. And that was the case until about a year ago when <a rel="nofollow" href="https://www.rstudio.com/" target="_blank">RStudio</a> founder <a rel="nofollow" href="https://en.wikipedia.org/wiki/Joseph_J._Allaire" target="_blank">J.J.Allaire</a> <a rel="nofollow" href="https://blog.rstudio.com/2017/09/05/keras-for-r/" target="_blank">announced</a> release of <a rel="nofollow" href="https://keras.rstudio.com/" target="_blank">the Keras library for R</a> in May’17. I consider this to be a turning point for data scientists; now we can be more flexible with dev. environment and be able to deliver result more efficiently with opportunity to extend existing solutions we may have written in R.</p>
<p>It brings me to the point of this post.<br>
<strong>My hypothesis is</strong>, when it comes to ANN ML model building with Keras, Python is not a must, and depending on your client’s request, or tech stack, R can be used without limitations and with similar efficiency. </p>
<h2>Image Classification with Keras</h2>
<p>In order to test my hypothesis, I am going to perform image classification using the <a rel="nofollow" href="https://www.kaggle.com/moltean/fruits/data" target="_blank">fruit images data</a> from kaggle and train a CNN model with four hidden layers: two 2D convolutional layers, one pooling layer and one dense layer. <a rel="nofollow" href="https://youtu.be/_e-LFe_igno" target="_blank">RMSProp</a> is being used as the <a rel="nofollow" href="https://keras.io/optimizers/" target="_blank">optimizer function</a>.</p>
<h3>Tech stack</h3>
<p><strong>Hardware</strong>:<br>
CPU: Intel Core i7-7700HQ: 4 cores (8 threads), 2800 – 3800 (Boost) MHz core clock<br>
GPU: Nvidia Geforce GTX 1050 Ti Mobile: 4Gb vRAM, 1493 – 1620 (Boost) MHz core clock<br>
RAM: 16 Gb</p>
<p><strong>Software</strong>:<br>
OS: Linux Ubuntu 16.04<br>
R: ver. 3.4.4<br>
Python: ver. 3.6.3<br>
Keras: ver. 2.2<br>
Tensorflow: ver. 1.7.0<br>
CUDA: ver. 9.0 (note that the current tensorflow version supports ver. 9.0 while the up-to-date version of cuda is 9.2)<br>
cuDNN: ver. 7.0.5 (note that the current tensorflow version supports ver. 7.0 while the up-to-date version of cuDNN is 7.1)</p>
<h2>Code</h2>
<p>The R and Python code snippets used for CNN model building are present bellow. Thanks to fruitful collaboration between F. Chollet and J.J. Allaire, the logic and functions names in R are alike the Python ones.</p>
<h3>R</h3>
<pre>
## Courtesy: Dr. Shirin Glander. Code source: https://shirinsplayground.netlify.com/2018/06/keras_fruits/

library(keras)
start &lt;- Sys.time()
fruit_list &lt;- c(&quot;Kiwi&quot;, &quot;Banana&quot;, &quot;Plum&quot;, &quot;Apricot&quot;, &quot;Avocado&quot;, &quot;Cocos&quot;, &quot;Clementine&quot;, &quot;Mandarine&quot;, &quot;Orange&quot;,
                &quot;Limes&quot;, &quot;Lemon&quot;, &quot;Peach&quot;, &quot;Plum&quot;, &quot;Raspberry&quot;, &quot;Strawberry&quot;, &quot;Pineapple&quot;, &quot;Pomegranate&quot;)

# number of output classes (i.e. fruits)
output_n &lt;- length(fruit_list)

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

# RGB = 3 channels
channels &lt;- 3

# path to image folders
path &lt;- &quot;path/to/folder/with/data&quot;
train_image_files_path &lt;- file.path(path, &quot;fruits-360&quot;, &quot;Training&quot;)
valid_image_files_path &lt;- file.path(path, &quot;fruits-360&quot;, &quot;Test&quot;)

# optional data augmentation
train_data_gen %>% image_data_generator(
  rescale = 1/255
)

# Validation data shouldn't be augmented! But it should also be scaled.
valid_data_gen &lt;- image_data_generator(
  rescale = 1/255
)  

# training images
train_image_array_gen &lt;- 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 &lt;- flow_images_from_directory(valid_image_files_path, 
                                                    valid_data_gen,
                                                    target_size = target_size,
                                                    class_mode = 'categorical',
                                                    classes = fruit_list,
                                                    seed = 42)

### model definition
# number of training samples
train_samples &lt;- train_image_array_gen$n
# number of validation samples
valid_samples &lt;- valid_image_array_gen$n

# define batch size and number of epochs
batch_size &lt;- 32
epochs &lt;- 10

# initialise model
model &lt;- 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')

# compile
model %>% compile(
  loss = 'categorical_crossentropy',
  optimizer = optimizer_rmsprop(lr = 0.0001, decay = 1e-6),
  metrics = 'accuracy'
)
# fit
hist &lt;- 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(file.path(path, &quot;fruits_checkpoints.h5&quot;), save_best_only = TRUE),
    # only needed for visualising with TensorBoard
    callback_tensorboard(log_dir = file.path(path, &quot;fruits_logs&quot;))
  )
)

df_out &lt;- hist$metrics %>% 
  {data.frame(acc = .$acc[epochs], val_acc = .$val_acc[epochs], elapsed_time = as.integer(Sys.time()) - as.integer(start))}
</pre>
<h3>Python</h3>
<pre>
## Author: D. Kisler  - adoptation of R code from https://shirinsplayground.netlify.com/2018/06/keras_fruits/

from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
from keras.layers import (Conv2D,
                          Dense,
                          LeakyReLU,
                          BatchNormalization, 
                          MaxPooling2D, 
                          Dropout,
                          Flatten)
from keras.optimizers import RMSprop
from keras.callbacks import ModelCheckpoint, TensorBoard
import PIL.Image
from datetime import datetime as dt

start = dt.now()

# fruits categories
fruit_list = [&quot;Kiwi&quot;, &quot;Banana&quot;, &quot;Plum&quot;, &quot;Apricot&quot;, &quot;Avocado&quot;, &quot;Cocos&quot;, &quot;Clementine&quot;, &quot;Mandarine&quot;, &quot;Orange&quot;,
                &quot;Limes&quot;, &quot;Lemon&quot;, &quot;Peach&quot;, &quot;Plum&quot;, &quot;Raspberry&quot;, &quot;Strawberry&quot;, &quot;Pineapple&quot;, &quot;Pomegranate&quot;]
# number of output classes (i.e. fruits)
output_n = len(fruit_list)
# image size to scale down to (original images are 100 x 100 px)
img_width = 20
img_height = 20
target_size = (img_width, img_height)
# image RGB channels number
channels = 3
# path to image folders
path = &quot;path/to/folder/with/data&quot;
train_image_files_path = path + &quot;fruits-360/Training&quot;
valid_image_files_path = path + &quot;fruits-360/Test&quot;

## input data augmentation/modification
# training images
train_data_gen = ImageDataGenerator(
  rescale = 1./255
)
# validation images
valid_data_gen = ImageDataGenerator(
  rescale = 1./255
)

## getting data
# training images
train_image_array_gen = train_data_gen.flow_from_directory(train_image_files_path,                                                            
                                                           target_size = target_size,
                                                           classes = fruit_list, 
                                                           class_mode = 'categorical',
                                                           seed = 42)

# validation images
valid_image_array_gen = valid_data_gen.flow_from_directory(valid_image_files_path, 
                                                           target_size = target_size,
                                                           classes = fruit_list,
                                                           class_mode = 'categorical',
                                                           seed = 42)

## model definition
# 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 = Sequential()

# add layers
# input layer
model.add(Conv2D(filters = 32, kernel_size = (3,3), padding = 'same', input_shape = (img_width, img_height, channels), activation = 'relu'))
# hiddel conv layer
model.add(Conv2D(filters = 16, kernel_size = (3,3), padding = 'same'))
model.add(LeakyReLU(.5))
model.add(BatchNormalization())
# using max pooling
model.add(MaxPooling2D(pool_size = (2,2)))
# randomly switch off 25% of the nodes per epoch step to avoid overfitting
model.add(Dropout(.25))
# flatten max filtered output into feature vector
model.add(Flatten())
# output features onto a dense layer
model.add(Dense(units = 100, activation = 'relu'))
# randomly switch off 25% of the nodes per epoch step to avoid overfitting
model.add(Dropout(.5))
# output layer with the number of units equal to the number of categories
model.add(Dense(units = output_n, activation = 'softmax'))

# compile the model
model.compile(loss = 'categorical_crossentropy', 
              metrics = ['accuracy'], 
              optimizer = RMSprop(lr = 1e-4, decay = 1e-6))

# train the model
hist = model.fit_generator(
  # training data
  train_image_array_gen,

  # epochs
  steps_per_epoch = train_samples // batch_size, 
  epochs = epochs, 

  # validation data
  validation_data = valid_image_array_gen,
  validation_steps = valid_samples // batch_size,

  # print progress
  verbose = 2,
  callbacks = [
    # save best model after every epoch
    ModelCheckpoint(&quot;fruits_checkpoints.h5&quot;, save_best_only = True),
    # only needed for visualising with TensorBoard
    TensorBoard(log_dir = &quot;fruits_logs&quot;)
  ]
)

df_out = {'acc': hist.history['acc'][epochs - 1], 'val_acc': hist.history['val_acc'][epochs - 1], 'elapsed_time': (dt.now() - start).seconds}
</pre>
<h2>Experiment</h2>
<p>The models above were <strong>trained 10 times</strong> with R and Pythons on GPU and CPU, the elapsed time and the final accuracy after 10 epochs were measured. The results of the measurements are presented on the plots below (click the plot to be redirected to <a rel="nofollow" href="https://plot.ly/" target="_blank">plotly</a> interactive plots).</p>
<p><a rel="nofollow" href="https://i1.wp.com/datascienceplus.com/wp-content/uploads/2018/06/3.png?ssl=1" target="_blank"><img sizes="(max-width: 490px) 100vw, 490px" src="https://i1.wp.com/datascienceplus.com/wp-content/uploads/2018/06/3-490x350.png?w=450&ssl=1" alt="" class="wp-image-14284" data-recalc-dims="1"></a></p>
<p><a rel="nofollow" href="https://i2.wp.com/datascienceplus.com/wp-content/uploads/2018/06/5.png?ssl=1" target="_blank"><img sizes="(max-width: 490px) 100vw, 490px" src="https://i0.wp.com/datascienceplus.com/wp-content/uploads/2018/06/5-490x350.png?w=450&ssl=1" alt="" class="wp-image-14285" data-recalc-dims="1"></a></p>
<p>From the plots above, one can see that:</p>
<ul>
<li>the accuracy of your model doesn’t depend on the language you use to build and train it (the plot shows only train accuracy, but the model doesn’t have high variance and the bias accuracy is around 99% as well).</li>
<li>even though 10 measurements may be not convincing, but Python would reduce (by up to 15%) the time required to train your CNN model. This is somewhat expected because R uses Python under the hood when executes Keras functions.</li>
</ul>
<p>Let’s perform unpaired t-test assuming that all our observations are normally distributed.</p>
<pre>
library(dplyr)
library(data.table)
# fetch the data used to plot graphs
d &lt;- fread('keras_elapsed_time_rvspy.csv')

# unpaired t test:
# t_score = (mean1 - mean2)/sqrt(stdev1^2/n1+stdev2^2/n2)

d %>%
    group_by(lang, eng) %>%
    summarise(el_mean = mean(elapsed_time),
              el_std = sd(elapsed_time),
              n = n()) %>% data.frame() %>%
    group_by(eng) %>%
    summarise(t_score = round(diff(el_mean)/sqrt(sum(el_std^2/n)), 2))
</pre>
<table>
<tr>
<th>eng</th>
<th>t_score</th>
</tr>
<tr>
<td>cpu</td>
<td>11.38</td>
</tr>
<tr>
<td>gpu</td>
<td>9.64</td>
</tr>
</table>
<p>T-score reflects a significant difference between the time required to train a CNN model in R compared to Python as we saw on the plot above. </p>
<h2>Summary</h2>
<p>Building and training CNN model in R using Keras is as “easy” as in Python with the same coding logic and functions naming convention. Final accuracy of your Keras model will depend on the neural net architecture, hyperparameters tuning, training duration, train/test data amount etc., but not on the programming language you would use for your DS project. Training a CNN Keras model in Python may be up to 15% faster compared to R</p>
<h2>P.S.</h2>
<p>If you would like to know more about Keras and to be able to build models with this awesome library, I recommend you these books: </p>
<ul>
<li><a rel="nofollow" href="https://www.amazon.com/dp/1787125939" target="_blank"> Deep Learning with Python by F. Chollet</a> (one of the Keras creators)</li>
<li><a rel="nofollow" href="https://www.amazon.com/dp/161729554X" target="_blank"> Deep Learning with R by F. Chollet and J.J. Allaire</a></li>
</ul>
<p>As well as this <a rel="nofollow" href="https://www.udemy.com/zero-to-deep-learning/" target="_blank">Udemy course</a> to start your journey with Keras.</p>
<p>Thanks a lot for your attention! I hope this post would be helpful for an aspiring data scientist to gain an understanding of use cases for different technologies and to avoid being biased when it comes to the selection of the tools for DS projects accomplishment.</p>
<ul></ul>
<p><strong>
<p>Related Post</p>
<p></p></strong>
<ol>
<li><a rel="nofollow" href="https://datascienceplus.com/update-can-we-predict-flu-outcome-with-machine-learning-in-r/" title="Permanent Link toUpdate: Can we predict flu outcome with Machine Learning in R?" target="_blank">Update: Can we predict flu outcome with Machine Learning in R?</a></li>
<li><a rel="nofollow" href="https://datascienceplus.com/evaluation-of-topic-modeling-topic-coherence/" title="Permanent Link toEvaluation of Topic Modeling: Topic Coherence" target="_blank">Evaluation of Topic Modeling: Topic Coherence</a></li>
<li><a rel="nofollow" href="https://datascienceplus.com/natural-language-generation-with-markovify-in-python/" title="Permanent Link toNatural Language Generation with Markovify in Python" target="_blank">Natural Language Generation with Markovify in Python</a></li>
<li><a rel="nofollow" href="https://datascienceplus.com/anomaly-detection-in-r-the-tidy-way/" title="Permanent Link toAnomaly Detection in R – The Tidy Way" target="_blank">Anomaly Detection in R – The Tidy Way</a></li>
<li><a rel="nofollow" href="https://datascienceplus.com/forecasting-with-arima-part-i/" title="Permanent Link toForecasting with ARIMA – Part I" target="_blank">Forecasting with ARIMA – Part I</a></li>
</ol>
	<p>
<div>
<div>To <strong>leave a comment</strong> for the author, please follow the link and comment on their blog: <strong><a href="https://datascienceplus.com/r-vs-python-image-classification-with-keras/"> R Programming – DataScience+</a></strong>.</div>
<hr>
<a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers.com</a> offers <strong><a rel="nofollow" href="https://feedburner.google.com/fb/a/mailverify?uri=RBloggers">daily e-mail updates</a></strong> about <a rel="nofollow" href="https://www.r-project.org/" title="The R Project for Statistical Computing">R</a> news and <a rel="nofollow" href="https://www.r-bloggers.com/search/tutorial" title="R tutorials">tutorials</a> on topics such as: <a rel="nofollow" href="https://www.r-bloggers.com/search/data%20science" title="Data science">Data science</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/Big%20Data" title="Big Data">Big Data, <a rel="nofollow" href="https://www.r-users.com/" title="R jobs">R jobs</a>, visualization (<a rel="nofollow" href="https://www.r-bloggers.com/search/ggplot2" title="ggplot and ggplot2 tutorials">ggplot2</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/boxplot" title="Boxplots using lattice and ggplot2 tutorials">Boxplots</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/map" title="Maps and gis">maps</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/animation" title="Animation in R">animation</a>), programming (<a rel="nofollow" href="https://www.r-bloggers.com/search/RStudio" title="RStudio IDE for R">RStudio</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/sweave" title="Sweave and literate programming">Sweave</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/LaTeX" title="LaTeX in R">LaTeX</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/SQL" title="SQL and databases">SQL</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/eclipse" title="Eclipse IDE for R">Eclipse</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/git" title="git and github, Version Control System">git</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/hadoop" title="Large data in R using Hadoop">hadoop</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/Web+Scraping" title="Web Scraping of google, facebook, yahoo, twitter and more using R">Web Scraping</a>) statistics (<a rel="nofollow" href="https://www.r-bloggers.com/search/regression" title="Regressions and ANOVA analysis tutorials">regression</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/PCA" title="principal component analysis tutorial">PCA</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/time+series" title="Time series">time series</a>, <a rel="nofollow" href="https://www.r-bloggers.com/search/trading" title="finance trading">trading</a>) and more...
<img width="1" alt="" src="http://feeds.feedburner.com/~r/RBloggers/~4/H64MUoskd_E" height="1"></a></div>