I’m mod for neural networks (Chapter 2)

Exploring the neural-redis module with Node.js

Kyle
7 min readDec 20, 2016

This is part 27.2 of my Node / Redis series. The previous part was I’m mod for neural networks (Chapter 1).

In the last entry, we talked about setting up the environment for neural-redis. Go back and read that one first. If you’ve worked through the first part, you should have a good working environment to start messing about with Node.js, Redis Unstable and Neural Redis.

Our goal in this part of the article is to develop a full playground for the Neural Redis module and dive a bit deeper into the module’s API. As we build this playground, some of the concepts may seem foreign but there are lots of resources out on the web. I stumbled across this video while writing that goes into some detail conceptually of how neural networks work and how to apply them to solve problems.

But let’s get started on some code. Like some of my previous articles, we’ll be using the NEAR stack — Node.js / Express / Angular / Redis. Go ahead and download the repo and get it running on your machine:

$ npm install
$ node --credentials ../path-to-your-credentials.json
Server running.

As in previous installments, the JSON file is a node_redis config object stored as a JSON file. Make sure and keep it outside of your repo — no one wants their credentials floating around the public web. After starting, the server should be available at http://localhost:8781/

This playground will need to be able to create a neural network (NR.CREATE), input items into the network (NR.OBSERVE), initiate training (NR.TRAIN), find out info about the neural network (NR.INFO) and run the neural network against values (NR.RUN/NR.CLASS). Generally, you’re working with these commands on one single key, so in the playground you can set one key to use for all the operations.

When you click off the input (aka blur), the playground will immediately run the NR.INFO. NR.INFO turns out to be very important for our script. This command gives you a list of information covering the setup and status of the network. In our situation, we want to be able to manipulate any configuration of network — it’s vital to get the setup of the network. Once a successful NR.INFO has run we’ll extract the following information:

  • type — either regressor or classifier, which will dictate how we validate the outputs, and if we enable the NR.CLASS command
  • layout — indicates how many inputs and outputs we need to show
  • auto-normalization — determines if we need to validate inputs to -/+1 or if we can leave them alone.

NR.INFO will be mapped to a HTTP call at /info/[your-key]. Out of the box, node_redis will just give you an interleaved list of properties and values. We’ll break it down into a more usable collection and send it back across the wire. On the client side, we’ll grab the relevant information and display it with the ever-useful ng-tables module. The relevant information will be stashed in scope — much of our “work” to modify the layout is done in the Angular template itself.

The Neural Redis API doesn’t map neatly into CRUD operations, but we can map it roughly into a RESTful interface for the most part. Our above-mentioned info call is outside of this pattern, but the rest we can kinda-sorta map with HTTP verbs. Let’s see how we can lay it out:

  • PUT /network NR.CREATE based on the JSON object passed in the HTTP request body
{ 
networkType : ['CLASSIFIER' || 'REGRESSOR'],
numberOfInputs : [number of inputs],
outputs : [number of outputs],
hiddenLayers : [number of hidden layers] optional,
dataset : [dataset size] optional,
testset : [testset size] optional,
normalize : [true || false] optional
}
  • PUT /network/[key] NR.OBSERVE with the JSON object passed in the HTTP request body.
[
{
input : [array of input values],
output : [array of output values],
dataset : ['TRAIN' || 'TEST'] optional
},
...
]
  • PATCH /network/[key] NR.TRAIN with the JSON object passed in the HTTP request body
{
maxCycles : [maximum number of cycles] optional,
maxTime : [maximum time in milliseconds] optional,
backtrack : [true || false] optional,
autostop : [true || false] optional
}
  • GET /network/[key]/[input-1]/…/[input-n][?classify=true] Get the result of the key and inputs passed either with NR.RUN or with NR.CLASS (if classify query parameter set).
  • DELETE /network/[key] Execute NR.RESET on the given key

In the server there is a particular pattern that I want to highlight. One quirk of the neural network module is that many of the commands have a variable number of arguments. This is not uncommon in Redis, but usually you’ll have a very good reason for varying arguments in the scripts code. In this playground though, depending on the number of inputs/outputs specified in NR.INFO, you need to be able to handle n inputs and n outputs. In the case of something like NR.OBSERVE you have the pattern [key] [input-1]..[input-n] -> [output-1]..[output-n] [callback]. To do this, I’m specifying my arguments as an array:

var myArgs = []

Then I need to add in the inputs, which are already in an array:

myArgs = myArgs.concat(theInputs);

At this point you need to push in the arrow construction

myArgs.push('->');

Then the output, also already in an array:

myArgs = myArgs.concat(theOutputs);

What’s also neat is that you can optionally add in arguments, so in this case, I’ll look at a conditional and if it exists, then I’ll push in different arguments:

if (dataset) {
myArgs.push('DATASET');
}

Now that you’ve got your arguments lined up in an array, you can push them directly into a command and supply the first argument as the key and the last argument as a callback. node_redis is smart enough to figure out what you mean.

client.nr_observe(myKey,myArgs,function(err,resp) { ... });

In the past, I have done similar things with a client.command.apply(client,…) pattern, but it always seemed confusing. This is a much clearer way of accomplishing the same thing.

On the server, I’m also handling most of the errors with one generic closure-genericReply. This saves on code complexity and allows for a matched generic handler in the client side code. This goes without saying, but you’d likely want something more robust and helpful in production.

One thing I did catch while developing is the odd handling of errors in a MULTI/EXEC block. When observing data, I’m allowing multiple rows to come in at once and then iterating over them adding to a MULTI. Originally, I was just checking the length of the reply to see how many items were added but I noticed that no-matter what I did — inserting with no parameter, alphabetical parameters instead of numeric, whatever — it never failed. At least from the error response on the EXEC. On closer inspection, I was seeing that each NR.OBSERVE was getting reported as either a success or failure in a multi-bulk response. From my understanding, this is not the expected behaviour and I’ve filed a bug report with both neural-redis and redis. This will likely get fixed very soon and I’ll update this in the future.

On the client/Angular.js side, it’s a fairly heavy, single page playground. Each command is broken out into a separate panel. The panels have interactive inputs that should (more or less) do some weak validation. Most panels have an area that shows what your command would look like should you want to put it into redis-cli.

For most commands, success or failure is indicated by a Growl-like notification through the Angular module toaster.

As mentioned above, the errors for NR.OBSERVE are a bit strange as it can both succeed and fail at the same time. Also, NR.RUN/NR.CLASS will return results in the panel itself, since you’re likely going to want to dwell on the results more than a few seconds that the notification is up.

Now, to start messing with the playground, you should first create a neural network by filling out the “Create” panel. While normalize isn’t ticked by default, it’s usually what you want. Once you’ve created your neural network, it should unlock all the rest of the panels.

After you have unlocked the panels, go ahead and start throwing data into the “Input (Observe)” panel. You can add multiple rows at a time by clicking “Add Row.” Once you’ve added your data, then you can train your next work in the Train panel and finally you can see how your neural network performs in the “Run/Classify” panel. This should give you enough to start experimenting with the Neural Redis commands.

I plan to build on this platform for future installments and eventually doing something useful using the building blocks provided in the server code. In the mean time, check out Daniel Shih’s series on Neural Redis for a more mathematical perspective on the subject. And stay tuned here for future Node/Redis/Neural Network experiments!

--

--

Kyle

Developer of things. Node.js + all the frontend jazz. Also, not from Stockholm, don’t do UX. Long story.