Deep Learning Side By Side: Julia v.s. Python | by DJ Passey | Towards Data Science

Julia could possibly be the biggest threat to Python. For a variety of applications, Julia is hands-down faster than Python and is almost as fast as C. Julia also offers features like multiple dispatch and metaprogramming that give it an edge over Python.
At the same time, Python is established, widely used, and has a variety of time tested packages. The question of switching to Julia is a hard question to address. Often the answer is a frustrating, “It depends”.
To help showcase Julia and to address the question of whether to use it, I’ve taken samples of deep learning code from both languages and placed them in series for easy comparison. I will walk through training VGG19 model on the CIFAR10 dataset.
Deep learning models can be huge and often take a lot of work to define, especially when they contain specialized layers like ResNet [1]. We will use a medium sized model (no pun intended) , VGG19, for this comparison [2].
VGG19 in Python
I’ve chosen Keras for our Python implementation because its lightweight and flexible design is competitive with Julia.
from keras.models import Sequentialfrom keras.layers import Dense, Conv2D, MaxPool2D , Flattenvgg19 = Sequential()vgg19.add(Conv2D(input_shape=(224,224,3),filters=64,kernel_size=(3,3),padding="same", activation="relu"))vgg19.add(Conv2D(filters=64,kernel_size=(3,3),padding="same", activation="relu"))vgg19.add(MaxPool2D(pool_size=(2,2),strides=(2,2)))vgg19.add(Conv2D(filters=128, kernel_size=(3,3), padding="same", activation="relu"))vgg19.add(Conv2D(filters=128, kernel_size=(3,3), padding="same", activation="relu"))vgg19.add(MaxPool2D(pool_size=(2,2),strides=(2,2)))vgg19.add(Conv2D(filters=256, kernel_size=(3,3), padding="same", activation="relu"))vgg19.add(Conv2D(filters=256, kernel_size=(3,3), padding="same", activation="relu"))vgg19.add(Conv2D(filters=256, kernel_size=(3,3), padding="same", activation="relu"))vgg19.add(MaxPool2D(pool_size=(2,2),strides=(2,2)))vgg19.add(Conv2D(filters=512, kernel_size=(3,3), padding="same", activation="relu"))vgg19.add(Conv2D(filters=512, kernel_size=(3,3), padding="same", activation="relu"))vgg19.add(Conv2D(filters=512, kernel_size=(3,3), padding="same", activation="relu"))vgg19.add(MaxPool2D(pool_size=(2,2),strides=(2,2)))vgg19.add(Conv2D(filters=512, kernel_size=(3,3), padding="same", activation="relu"))vgg19.add(Conv2D(filters=512, kernel_size=(3,3), padding="same", activation="relu"))vgg19.add(Conv2D(filters=512, kernel_size=(3,3), padding="same", activation="relu"))vgg19.add(MaxPool2D(pool_size=(2,2),strides=(2,2)))vgg19.add(Flatten())model.add(Dense(units=4096,activation="relu"))vgg19.add(Dense(units=4096,activation="relu"))vgg19.add(Dense(units=10, activation="softmax"))# Code from Rohit Thakur on GitHub
The task here is to concatenate 21 layers of deep learning machinery. Python handles this well. The syntax is simple and easy to understand. While the .add() function might be a little ugly, it is obvious what it is doing. Furthermore, it is clear in the code what each model layer does. (Convolves, pools, flattens, etc..)
VGG19 In Julia
using Fluxvgg16() = Chain(                Conv((3, 3), 3 => 64, relu, pad=(1, 1), stride=(1, 1)),    Conv((3, 3), 64 => 64, relu, pad=(1, 1), stride=(1, 1)),    MaxPool((2,2)),    Conv((3, 3), 64 => 128, relu, pad=(1, 1), stride=(1, 1)),    Conv((3, 3), 128 => 128, relu, pad=(1, 1), stride=(1, 1)),    MaxPool((2,2)),    Conv((3, 3), 128 => 256, relu, pad=(1, 1), stride=(1, 1)),    Conv((3, 3), 256 => 256, relu, pad=(1, 1), stride=(1, 1)),    Conv((3, 3), 256 => 256, relu, pad=(1, 1), stride=(1, 1)),    MaxPool((2,2)),    Conv((3, 3), 256 => 512, relu, pad=(1, 1), stride=(1, 1)),    Conv((3, 3), 512 => 512, relu, pad=(1, 1), stride=(1, 1)),    Conv((3, 3), 512 => 512, relu, pad=(1, 1), stride=(1, 1)),    MaxPool((2,2)),    Conv((3, 3), 512 => 512, relu, pad=(1, 1), stride=(1, 1)),    Conv((3, 3), 512 => 512, relu, pad=(1, 1), stride=(1, 1)),    Conv((3, 3), 512 => 512, relu, pad=(1, 1), stride=(1, 1)),    BatchNorm(512),    MaxPool((2,2)),    flatten,    Dense(512, 4096, relu),    Dropout(0.5),    Dense(4096, 4096, relu),    Dropout(0.5),    Dense(4096, 10),    softmax)# Code from Flux Model Zoo on Github
Discussion
At a glance, Julia looks slightly less cluttered than Python. The import statements are a little cleaner and the code is a little easier to read. Like Python, it is clear what each layer does. The Chain type is a little ambiguous, but it is pretty clear that it concatenates the layers together.
Something to notice is that there is no model class. In fact, Julia is not object oriented, so each layer is a type instead of a class. This is worth noting because it emphasizes how the Julia model is very lightweight. Each of these layers was defined independently and then chained together without any class structure to control how they interact.
However, avoiding a little clutter doesn’t really matter when training giant models. The advantage for Python here is that Python has a huge amount of support for troubleshooting and working through bugs. The documentation is excellent and there are hundreds of VGG19 examples online. Contrast this with Julia where there are five unique VGG19 examples online (maybe).
For data processing we will look at the dataset CIFAR10 that is commonly associated with VGG19.
Data Processing In Python
from keras.datasets import cifar10from keras.utils import to_categorical(X, Y), (tsX, tsY) = cifar10.load_data() # Use a one-hot-encodingY = to_categorical(Y)tsY = to_categorical(tsY)# Change datatype to floatX = X.astype('float32')tsX = tsX.astype('float32') # Scale X and tsX so each entry is between 0 and 1X = X / 255.0tsX = tsX / 255.0
In order to train the model on image data, images must be put into the correct format. It only takes a few lines of code to do this. Images are loaded into variables along with image labels. To make classification easier, the labels are translated into a one hot encoding format. This is relatively straightforward in Python.
Data Processing In Julia
using MLDatasets: CIFAR10using Flux: onehotbatch# Data comes pre-normalized in JuliatrainX, trainY = CIFAR10.traindata(Float64)testX, testY = CIFAR10.testdata(Float64)# One hot encode labelstrainY = onehotbatch(trainY, 0:9)testY = onehotbatch(testY, 0:9)
Julia requires the same kind of image processing as Python to prepare images for the training process. The code looks extremely similar and does not appear to favor either language.
Next we will look at the model training loop.
Training in Python
optimizer = SGD(lr=0.001, momentum=0.9)vgg19.compile(optimizer=optimizer, loss='categorical_crossentropy', metrics=['accuracy'])history = model.fit(X, Y, epochs=100, batch_size=64, validation_data=(tsX, tsY), verbose=0)
Training In Julia
using Flux: crossentropy, @epochsusing Flux.Data: DataLoadermodel = vgg19()opt = Momentum(.001, .9)loss(x, y) = crossentropy(model(x), y)data = DataLoader(trainX, trainY, batchsize=64)@epochs 100 Flux.train!(loss, params(model), data, opt)
The code here is about equally verbose, but the differences in the languages show. In Python, model.fit returns a dictionary containing accuracy and loss evaluations. It also has keyword arguments to automate the optimization process for you. Julia is much more bare-bones. The training algorithm requires the user to provide their own loss function, optimizer and iterable containing batches of data along with the model.
The Python implementation is much more user friendly. The training process is easy and produces useful output. Julia requires a little more from the user. At the same time, Julia is more abstract, and allows any optimizer and loss function. The user can define a loss function any way that they want without needing to consult a list of built in loss functions. This kind of abstraction is typical of Julia developers, who work to make code as abstract and generic as possible.
For this reason, Keras is more practical for implementing known techniques and standard model training, but makes Flux better suited for developing new techniques.
Unfortunately, there is no available benchmark comparing Flux and Keras on the internet. There are a few resources that give us an idea and we can use TensorFlow speed as a reference.
One benchmark found that on the GPU and on the CPU, Flux is barely slower than TensorFlow. It’s been shown that Keras is slightly slower than TensorFlow on the GPU as well. Unfortunately this doesn’t give us a clear winner but suggests that the speed of the two packages are similar.
The Flux benchmark above was done before a major rework of Flux’s automatic differentiation package. The new package, Zygote.jl, sped up computations considerably. A more recent benchmark of Flux on the CPU found that the improved Flux is faster than TensorFlow on the CPU. This suggests that Flux could be faster on the on the GPU as well, but winning on the CPU doesn’t necessarily imply a victory on the GPU. At the same time, this is still good evidence that Flux would beat Keras on the CPU.
Both languages preform well in every category. Differences between the two are largely matters of taste. However there are two places that each language has an edge.
Python has a huge support community and offers time tested libraries. It is reliable and standard. Deep learning in Python is much more common. Developers who use Python for deep learning will fit in well in the deep learning community.
Julia is cleaner and more abstract. The deep learning code could definitely be faster and improvements are in the works. Julia has an edge on potential. Deep Learning libraries in Python are much more complete, and don’t have as much potential to grow and develop. Julia, with its richer base language has potential for many new ideas and much faster code in the future. Developers who adopt Julia will be closer to the frontier of programming, but will have to deal with forging their own path.
Deep learning is difficult and requires a lot of troubleshooting. It can be very difficult to reach state of the art accuracy. For this reason, Python wins this comparison. Deep learning in Julia does not have a strong level of online support for deep learning troubleshooting. This can make writing complicated deep learning scripts very difficult. Julia is excellent for many applications, but for deep learning, I would recommend Python.
medium.com
towardsdatascience.com
[1] Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun, Deep Residual Learning for Image Recognition, CVPR 2016.
[2] Karen Simonyan, Andrew Zisserman, Very Deep Convolutional Networks for Large-Scale Image Recognition, International Conference on Learning Representations, 2015