bayes classification exercise on mxnet.cr

cleaning up the mxnet.cr exercises.

also demonstrated above is ishi—which tries to be a mathplotlib.pyplot like wrapper on top of gnuplot albeit written in crystal—and iterm2—which outputs charts to the console.

#mxnet #ishi #iterm2

This is a summary of the presentation I did today about MXNet.cr for the Crystal Language 1.0 Conference.

Machine Learning with MXNet.cr

MXNet.cr is bindings to the MXNet machine learning/deep learning library and an implementation of the MXNet Gluon deep learning library. MXNet.cr is also kind of a port of a port.

A few years ago I decided I wanted to tune up my machine learning skills, but I didn't want to use Python. At about the same time, I discovered both MXNet, with support for multiple programming languages, and mxnet.rb, a set of Ruby bindings to MXNet by Kenta Murata. For a while I was happy. But Kenta's bindings didn't include an implementation of Gluon.

Gluon is a high-level framework for building deep neural networks. I was running through the lessons in Deep Learning - The Straight Dope and bumped into lessons that required Gluon. The obvious solution was to implement Gluon. Eventually parts of my implementation made their way into mxnet.rb.

Around the time my Ruby code was being merged, I realized I'd found another language that I was really jazzed about—Crystal. You all know about Crystal—it's got Ruby-like syntax and Ruby-like ergonomics, but it's blazingly fast. So again I did the obvious thing and ported my code to Crystal.

Lessons Learned

You can almost mechanically convert Ruby code into Crystal code. Consider the following two blocks of code. Which is Ruby and which is Crystal?

def self.import(filename, inputs,
                epoch: 0, ctx: MXNet.cpu,
                allow_missing: false,
                ignore_extra: false)
  output = MXNet::Symbol.load('%s-symbol.json' % filename)
  inputs = [inputs] unless inputs.is_a?(Array)
  inputs = inputs.map { |i| MXNet::Symbol.var(i) }
  SymbolBlock.new(output, inputs).tap do |block|
    if epoch
      filename = '%s-%04d.params' % [filename, epoch]
      arg_dict = MXNet::NDArray.load(filename)
      arg_dict = arg_dict.transform_keys do |k|
        k.gsub(/^(arg:|aux:)/, '')
      end
def self.import(filename, inputs,
                epoch = 0, ctx = MXNet.cpu,
                allow_missing = false,
                ignore_extra = false)
  outputs = [MXNet::Symbol.load("%s-symbol.json" % filename)]
  inputs = [inputs] unless inputs.is_a?(Array)
  inputs = inputs.map { |i| MXNet::Symbol.var(i) }
  SymbolBlock.new(outputs, inputs).tap do |block|
    if epoch
      filename = "%s-%04d.params" % [filename, epoch]
      arg_dict = MXNet::NDArray.load(filename)
      arg_dict = arg_dict.transform_keys do |k|
        k.gsub(/^(arg:|aux:)/, "")
       end

Adding the missing but required type annotations and fixing the parts of the Ruby code that play fast and loose took the most work—for example, the Ruby (and Python, in its own way) code makes use of method_missing which is difficult to replicate in Crystal.

Crystal Language syntax for defining bindings to native libraries is first class. Seriously, I don't know who made the call but they sure got it right. In contrast, the Ruby bindings were difficult to write, although I did learn a lot about writing Ruby extensions in the process. (Ruby extensions have long been in the same boat as Elisp programming—something I am interested in but can't work up the enthusiasm to actually do.)

Crystal macros are powerful tools for removing boilerplate. Almost every operation you need in MXNet is discoverable and dynamically callable at runtime. The Ruby (and Python) way is to introspect all of the operation metadata (names, parameters, their documentation strings, etc.) at runtime and to dynamically create methods for use by application code. This is pretty slick and arguably what you want in Crystal as well, albeit at compile time.  For performance reasons I wrote a tool that dumped the metadata into a Crystal language data structure ahead of time, and then used macros at compile time to create methods.

Work on MXNet.cr has temporarily taken a back seat to work on ktistec, but as ktistec matures I look forward to spending more time on MXNet.cr

#crystalconference2021 #machinelearning #mxnet #crystal

project activity

My Covid-19 quarantine plan for the last 12 months was to write code every day. With a few exceptions, I pulled that off. The big pre-Covid plan was to write every day, but in traditional hacker fashion I first built some tools (Ktistec and kin).

The big project was Ktistec, of course, but early on I spent a lot of time on MXNet.cr, mostly on native MXNet bindings, but also on a Gluon compatible library. I also put a lot of time into Ishi, a project that sprang from my desire to visualize MXNet output inside of my iTerm console.

Now, to do some writing...

#ktistec #mxnet #ishi