By: Logan Kilpatrick
Re-posted from: https://betterprogramming.pub/why-you-should-invest-in-julia-now-as-a-data-scientist-30dc346d62e4?source=rss-2c8aac9051d3------2
Know what Julia has to offer and the resources to get started
By: Logan Kilpatrick
Re-posted from: https://betterprogramming.pub/why-you-should-invest-in-julia-now-as-a-data-scientist-30dc346d62e4?source=rss-2c8aac9051d3------2
Know what Julia has to offer and the resources to get started
By: DSB
Re-posted from: https://medium.com/coffee-in-a-klein-bottle/visualizing-data-with-julia-using-makie-7685d7850f06?source=rss-8bd6ec95ab58------2

When starting learning Julia, one might get lost in the many different packages available to do data visualization. Right out of the cuff, there is Plots, Gadfly, VegaLite … and there is Makie.
Makie is fairly new (~2018), yet, it’s very versatile, actively developed and quickly growing in number of users. This article is a quick introduction to Makie, yet, by the end of it, you will be able to do a plethora of different plots.
When I started coding in Julia, Makie was not one of the contenders for “best” plotting libraries. As time passed, I started to here more and more about it around the community. For some reason, people were saying that:
“Makie is the future” — People in the Julia Community
I never fully understood why that was the case, and every time I tried to learn it, I’d be turned off by the verbose syntax, and, frankly, ugly examples. It was only when I bumped into Beautiful Makie that I decided to put aside my prejudices and get on with the times.
Hence, if you are starting to code in Julia, and is wondering which plotting package you should invest your time to learn, I say to you that Makie is the way to go, since I guess “Makie is the future”.

The versatility in Makie can make it a bit unwelcoming for those that “just want to do a damn scatter plot”. First of all, there is Makie.jl, CairoMakie.jl, GLMakie.jl WGLMakie.jl ?. Which one should you use?
Well, here is the deal. Makie.jl is the main plotting package, but you have to choose a backend to which you will display your plots. The choice depends on your objectives. So yes, besides Makie.jl, you will need to install one of the backends. Here is a small description to help you chose:
In this tutorial, we’ll use CairoMakie.jl.
After picking our backend, we can now start plotting! I’ll go out on a limb and say that Makie is very similar to Matplotlib. It does not work with any fancy “Grammar of Graphics” (but if you like this sort of stuff, take a look at the AlgebraOfGraphics.jl, which implements an “Algebra of Graphics” on Makie).
Thus, there are a bunch of ready to use functions for some of the most common plots.
using CairoMakie #Yeah, no need to import Makie
scatter(rand(10,2))

Easy breezy… Yet, if you are plotting this in a Jupyter Notebook, you might be slightly ticked off by two things. First, the image is just too large. And second, it’s kind of low quality. What is going on?
By default, CairoMakie uses raster format for images, and the default size is a bit large. If you are like me and prefer your plots to be in svg and a bit smaller, then no worries! Just do the following:
using CairoMakie
CairoMakie.activate!(type = "svg")
scatter(rand(10,2),figure=(;resolution=(300,300)))
In the code above, the CairoMakie.activate!() is a command that tells Makie which backend you are using. You can import more than one backend at a time, and switch between them using this activation commands. Also, the CairoMakie backend has the option to do svg plots (to my knowledge, this is not possible for the other backends). Hence, with this small line of code, all our plots will now be displayed in high quality.
Next, we defined a “resolution” to our figure. In my opinion, this is a bit of an unfortunate name, because the resolution is actually the size of our image. Yet, as we’ll see further on, the attribute resolution actually belongs to our figure, and not to the actual scatter plot. For this reason we pass the whole figure = (; resolution=(300,300)) (if you are new to Julia, the ; is just a way of separating attributes that have names, from unnamed ones, i.e. args and kwags).
Congrats! You now know the bare minimum of Makie to do a whole bunch of different plots! Just go to the Makie’s website and see how to use all the different ready-to-use plotting functions! In order to be self contained, here is a small cheat sheet from the great book Julia Data Science.
Of course, we still haven’t talked about a bunch of important things, like titles, subplots, legends, axes limits, etc. Just keep on reading…

Commands like scatter produce a “FigureAxisPlot” object, which contains a figure, a set of axes and the actual plot. Each of these objects has different attributes and are fundamental in order to customize your visualization. By doing:
fig, ax, plt = scatter(rand(10,2))
We save each of these objects in a different variable, and can more easily modify them. In this example, the function scatter is actually creating all three objects, and not only the plot. We could instead create each of these objects individually. Here is how we do it:
fig = Figure(resolution=(600, 400))
ax = Axis(fig[1, 1], xlabel = "x label", ylabel = "y label",
title = "Title")
lines!(ax, 1:0.1:10, x->sin(x))

Let’s explain the code above. First, we created the empty figure and stored it in fig . Next, we created an “Axis”. But, we need to tell to which figure this object belongs, and this is where the fig[1,1] comes in. But, what is this “[1,1]”?
Every figure in Makie comes with a grid layout underneath, which enable us to easily create subplots in the same figure. Hence, the fig[1,1] means “Axis belongs to fig row 1 and column 1”. Since our figure only has one element, then our axis will occupy the whole thing. Still confused? Don’t worry, once we do subplots you’ll understand why this is so useful.
The rest of the arguments in “Axis” are easy to understand. We are just defining the names in each axis and then the title.
Finally, we add the plot using lines! . The exclamation is a standard in Julia that means that a function is actually modifying an object. In our case, the lines!(ax, 1:0.1:10, x->sin(x)) is appending a line plot to the ax axis.
It’s clear now how we can, for example, add more line plots. By running the same lines! , this will append more plots to our ax axis. In this case, let’s also add a legend to our plot.
fig = Figure(resolution=(600, 400))
ax = Axis(fig[1, 1], xlabel = "x label", ylabel = "y label",
title = "Title")
lines!(ax, 1:0.1:10, x->sin(x), label="sin")
stairs!(ax, 1:0.1:10, x->cos(x), label="cos", color=:black)
axislegend(ax)
#*Tip*: if you are using Jupyter and want to display your
# visualization, you can do display(fig) or just write fig in
# the end of the cell.

Ok, our plots are starting to look good. Let me end this section talking about subplots. As I said, this is where the whole “fig[1,1]” comes into play. If instead of doing two plots in the same axis we wanted to create two parallel plots in the same figure, here is how we would do this.
fig = Figure(resolution=(600, 300))
ax1 = Axis(fig[1, 1], xlabel = "x label", ylabel = "y label",
title = "Title1")
ax2 = Axis(fig[1, 2], xlabel = "x label", ylabel = "y label",
title = "Title2")
lines!(ax1, 1:0.1:10, x->sin(x), label="sin")
stairs!(ax1, 1:0.1:10, x->cos(x), label="cos", color=:black)
density!(ax2, randn(100))
axislegend(ax)
save("figure.png", fig)
This time, in the same figure, we created two axis, but the first one is in the first row and first column, while the second one is in the second column. We then just append the plot to the respective axis. Lastly, we save the figure in “png” format.
That’s it for this tutorial. Of course, there is much more the talk about, as we have only scratched the surface. Makie has some awesome capabilities in terms of animations, and much more attributes/objects to play with in order to create truly astonishing visualizations. If you want to learn more, take a look at Makie’s documentation, it’s very nice. And also, the Julia Data Science book has a chapter only on Makie.
This article draws heavily on the Julia Data Science book and Makie’s own documentation.
Storopoli, Huijzer and Alonso (2021). Julia Data Science. https://juliadatascience.io. ISBN: 9798489859165.
Danisch & Krumbiegel, (2021). Makie.jl: Flexible high-performance data visualization for Julia. Journal of Open Source Software, 6(65), 3349, https://doi.org/10.21105/joss.03349
Visualizing Data with Julia using Makie was originally published in Coffee in a Klein Bottle on Medium, where people are continuing the conversation by highlighting and responding to this story.
By: Random blog posts about machine learning in Julia
Re-posted from: https://rkube.github.io/julia/gan/2021/11/08/training-gans.html
In order to effectively run machine learning experiments we need a fast
turn-around time for model training. So simply implementing the model is not
the only thing we need to worry about. We also want to be able to change the
hyperparameters in a convenient way. This could either be through a configuration
file or through command line arguments. This post demonstrates how I train
a vanilla GAN on the
MNIST dataset. It is not about GAN theory, for this the original paper by
Goodfellow et al. [[1]] is a good starting point. Instead I focus on how to
structure the code and subtle implementation issues I came across when writing
the code. You can find the current version of the code on github.
I am taking a starting point in the vanilla GAN implementation on the
FluxML website. This
implementation works and the trained generator indeed generates images that
look indistinguishable from images belonging to the MNIST dataset.
But how do we arrive there? Why are the learning rates chosen as \(\eta = 2 \times 10^{-4}\)? IS the leakyrelu the optimal activation function or does it perform
on-par with relu in some regime? To answer these questions we need a code that
quickly allows us to change these parameters.
And while we are at it, lets bundle the code together with its dependencies in a
Julia package. This allows us to conveniently a package dependencies to the code.
Taken together, the code and well defined dependencies make the behaviour reproducible. The Julia documentation gives a comprehensive introduction on
packages here.
In order to run the code in the project I first checkout the code from github,
then enter the repository and then execute the runme script:
$ git checkout https://github.com/rkube/mnist_gan.git
$ cd mnist_gan
$ julia --project=. src/runme.jl --activation=ADAM --train_k=8 ...
All packages installed in the project are local to this project and don’t interfere
with packages installed in the general environment. This allows for example to
specify for certain version numbers and will give us producibility of our results.
The code is structued as a standard Julia project. The root folder layout looks
like this
├── Manifest.toml
├── Project.toml
├── README.md
└── src
├── Manifest.toml
├── mnist_gan.jl
├── models.jl
├── Project.toml
├── runme.jl
└── training.jl
The root folder contains Manifest.toml and Project.toml which include information
about dependencies, versions, package names. More information is given in the
Pkg.jl documentation.
The src folder contains all source codes files. In particular it contains a
mnist_gan.jl file. This is named after the package name and in the simple case
here only twofines the package as a module, includes all other modules and
my two source files
module mnist_gan
using NNlib
using Flux
using Zygote
using Base:Fix2
# All GAN models
include("models.jl")
# Functions used for training
include("training.jl")
end #module
As additional structure I put the models in models.jl and training functions in
training.jl.
To quickly train the GAN with specific hyperparameters one can either read the
hyperparameters from a configuration file or pass them through the command line.
Here we do the second approach. To comfortably parse command line arguments I’m
using (ArgParse.jl)[https://argparsejl.readthedocs.io/en/latest/argparse.html].
Condensing to only single argument, my code looks like this:
using ArgParse
s = ArgParseSettings()
@add_arg_table s begin
"--lr_dscr"
help = "Learning rate for the discriminator. Default = 0.0002"
arg_type = Float64
default = 0.0002
args = parse_args(s)
That’s it. Now I can access the single command line arguments via args[lr_dscr].
Keeping track of the model performance while training is crucial when performing
parameter scans. For the vanilla GAN alone I defined 10 parameters that can be
varied. Letting each parameter assume only two distinct values this allows for
1024 combinations. Julia’s [logging facilities(https://github.com/JuliaLogging)
provide means to systematicallylog model training for a large hyperparameter scan.
In particular, we can use TensorBoardLogger.jl. TensorBoard
provides a visualization of training and includes numerous useful features, such
as visualization of loss curves, displaying of model output images and more. To
use TensorBoardLogger.jl in my code I have to include the module, instantiate
a logger. Then I can easily log my experiments:
# Import the modules
using TensorBoardLogger
...
# Instantiate TensorBoardLogger
# Let's log the hyperparamters of the current run.
dir_name = join(["$(k)_$(v)" for (k, v) in a])
tb_logger = TBLogger("logs/" * dir_name)
with_logger(tb_logger) do
@info "hyperparameters" args
end
# Wrap the main training loop in a with clause to enable logging
lossvec_gen = zeros(Float32, args["num_iterations"])
lossvec_dscr = zeros(Float32, ["num_iterations"])
with_logger(tb_logger) do
for n ∈ args["num_iterations"]e
# Do machine learning ...
...
# Code to log PNG images to tensorboard, inside the main training loop
if n % args["output_period"] == 0
noise = randn(args["latent_dim"], 4) |> gpu;
fake_img = reshape(generator(noise), 28, 4*28) |> cpu;
# I need to clip pixel values to [0.0; 1.0]
fake_img[fake_img .> 1.0] .= 1.0
fake_img[fake_img .< -1.0] .= -1.0
fake_img = (fake_img .+ 1.0) .* 0.5
#
log_image(tb_logger, "generatedimage", fake_img, ImageFormat(202))
end
# Log the generato and discriminator loss
@info "test" loss_generator=lossvec_gen[n] loss_discriminator=lossvec_dscr[n]
end # for
end # Logger
First, I’m generating a string from all keys and values defined in the command
line argument dictionary. Later this will allow me to filter these arguments.
Then I’m logging the args dictionary, which contains the hyperparameters of
the current experiment. Then I’m generating a fake image using the generator
and log it as well. Here I need to clip the pixel values to [0.0; 1.0]. Since
the Generator is trained on images with pixel values between -1.0 and 1.0 I need
to transform the pixel space. Note that he last argument to the call in
log_image encodes the layout of the fake_img array. I had to look up the
available encodings via
?
To resolve the correct loss function from command line arguments I’m using the
getfield method. To make it a little more convoluted, we also need to distinguish
between loss functions that take an additional, tunable parameterr
like celu, elu, leakyrelu and trelu, and loss functions who do not.
The following code block shows how to map a string that encodes the function name
to the actual function using getfield. To create a closure over an optional
parameter I’m using Fix2. The code below is from models.jl
function get_vanilla_discriminator(args)
...
if args["activation"] in ["celu", "elu", "leakyrelu", "trelu"]
# Now continue: We want to use Base.Fix2
act = Fix2(getfield(NNlib, Symbol(args["activation"])), Float32(args["activation_alpha"]))
else
act = getfield(NNlib, Symbol(args["activation"]));
end
return Chain(Dense(28 * 28, 1024, act),
...);
I found out that can have an impact on performance how I pass the activation
function as an argument to the dense layer. By passing only the function, the
implementation of Dense handles how the activation function is applied to the linear
transformation. This is how it should be. If I manually prescribe how to apply
the broadcast I find slower performance:
julia> d1 = Dense(100, 100, act)
Dense(100, 100, relu) # 10_100 parameters
julia> @btime d1(randn(Float32, 100, 100));
163.863 μs (6 allocations: 117.33 KiB)
julia> d2 = Dense(100, 100, x -> act(x))
Dense(100, 100, #5) # 10_100 parameters
julia> @btime d2(randn(Float32, 100, 100));
3.041 ms (20016 allocations: 430.30 KiB)
So manually prescribing how to perform the broadcast is about 20 times slower.
Instead, I let the code above return a function that Flux knows how to apply a
broadcast on.
Now we are set up to run a parameter scan. For this I generate runscripts
where I vary my command line arguments. The resulting scripts look like this
#SBATCH things
cd /location/of/the/repo
julia --project=. --lr_dscr=0.0002 --lr_gen=0.0002 --batch_size=8 --num_iterations=10000 --latent_dim=100 --optimizer=ADAM --activation=leakyrelu --activation_alpha=0.2 --train_k=8 --prob_dropout=0.3 --output_period=250
Of course the arguments vary across the scripts. After crunching all the numbers,
the log file directory is populated with the tensorboard log files. The next
blog post will discuss how the results look like and how to pick the best
hyperparameters.
[1]
I. Goodfellow et al. Generative Adversarial Networks