Deploying Machine Learning Models for Elixir applications #1: Getting Started

You already know how to build scalable and fault tolerant systems with Elixir, but have you thought about making them smarter? Machine Learning is this exciting area in IT we hear about great successes, and this builds curiosity to explore what it can do. Unfortunately, this area is dominated by Python and R, the technologies, that are foreign to BEAM based languages…

I’ve raised this question — would that be possible to train the Machine Learning models and still, make them available to Elixir programs in a way it would feel more native to the programs you implement?

There are a few ways of deploying Machine Learning models, and here, I’ll spend some time trying to make them work with the Elixir as it was part of Elixir program itself.

Finally, enabling the “native BEAM feel” to parts of Python program, will allow designing around the boundaries of the system using tools we already know and use — monitoring and linking the precesses and nodes are powerful, and serve great when we want to build highly available systems.

Join me in this exciting journey. I hope you’ll have as much fun as I had experimenting with this approach.

Project’s setup

Preparing the environment

We will need to install some Python libraries, so it is advised to create a dedicated Python’s environment, so the dependencies don’t get in conflicts with each other.

One of the tools used in Python world is virtualenv.

Let’s give it a go:

and activate it:

Python dependencies

Clone the Pyrlang repository:

$ git clone git@github.com:Pyrlang/Pyrlang.git

There are additional requirements needed for Pyrlang to work, and it provides a convenience script to install them.

Let’s invoke it:

$ pip3 install -r Pyrlang/requirements.txt

This completes the local setup and we are able to go to the next step and build a small app in Python.

First Python app

Firstly — required imports:

Next, the body of the app:

At the time of writing, Pyrlang allows specifying only a full name of a node.

The code above:

Node(node_name=”py@127.0.0.1", cookie=”PYEX”, engine=eng)

would be an equivalent of starting iex like:

$ iex — name “py@127.0.0.1” — cookie “PYEX”

and:

node.send(sender=pid,
receiver=(Atom(“ex@127.0.0.1”), Atom(“iex”)),
message=Atom(“Hello from Python!”))

would be an equivalent of Elixir’s code:

send({:iex, :”ex@127.0.0.1"}, :”Hello from Python!”)

Let’s finish Python’s script with:

This construct will invoke main() method automatically when executing this
Python script.

Starting Elixir node

The above code assumes a node named ex@127.0.0.1 that will be using cookie PYEX, and a process within this node named :iex.

Let’s set up such a node:

$ iex — name ex@127.0.0.1 — cookie PYEX

and register the process:

iex(ex@127.0.0.1)1> Process.register(self(), :iex)
true

Starting Python node

Due to the fact we’ve checked out Pyrlang’s source code, Python will be unable to find the code, and as such, we have to help it a little bit specifying thePYTHONPATH; additionally, for easier debugging, we will enable more granular logging.

The full command would be as follows:

This is quite verbose, and difficult to remember. Let’s add Makefile that will handle this for us:

and invoke it:

There is a lot of logs generated, but it doesn’t look very impressive, so let’s
check a couple of things…

What’s happened

Firstly, in a separate shell, let’s check registered nodes in epmd:

We can see that there are both nodes — ex node we started first, and py
shortly after. Next, let’s check back our ex node. If everything went well, we should have a message from py node.

iex(ex@127.0.0.1)2> flush()
:”Hello from Python!”
:ok

And the message indeed is here!

Unfortunately, out of the box, Pyrlang doesn’t implement whole functionality, and this won’t work:

iex(ex@127.0.0.1)3> Node.ping(:”py@127.0.0.1")
:pang

However, if we monitor the node:

iex(ex@127.0.0.1)4> Node.monitor(:”py@127.0.0.1", true)
true

and once we kill Python’s node (hit CTRL-c), we will be able to see this:

iex(ex@127.0.0.1)5> flush()
{:nodedown, :”py@127.0.0.1"}
:ok

Perfect — this should be just enough for our future needs.

The code is available here.