Torch.rb

:fire: Deep learning for Ruby, powered by LibTorch

For computer vision tasks, also check out TorchVision

Build Status

Installation

First, install LibTorch. For Homebrew, use:

brew install libtorch

Add this line to your application’s Gemfile:

gem 'torch-rb'

It can take a few minutes to compile the extension.

Getting Started

Deep learning is significantly faster with a GPU. If you don’t have an NVIDIA GPU, we recommend using a cloud service. Paperspace has a great free plan.

We’ve put together a Docker image to make it easy to get started. On Paperspace, create a notebook with a custom container. Set the container name to:

ankane/ml-stack:torch-gpu

And leave the other fields in that section blank. Once the notebook is running, you can run the MNIST example.

API

This library follows the PyTorch API. There are a few changes to make it more Ruby-like:

  • Methods that perform in-place modifications end with ! instead of _ (add! instead of add_)
  • Methods that return booleans use ? instead of is_ (tensor? instead of is_tensor)
  • Numo is used instead of NumPy (x.numo instead of x.numpy())

Tutorial

Some examples below are from Deep Learning with PyTorch: A 60 Minutes Blitz

Tensors

Create a tensor from a Ruby array

x = Torch.tensor([[1, 2, 3], [4, 5, 6]])

Get the shape of a tensor

x.shape

There are many functions to create tensors, like

a = Torch.rand(3)
b = Torch.zeros(2, 3)

Each tensor has four properties

  • dtype - the data type - :uint8, :int8, :int16, :int32, :int64, :float32, float64, or :bool
  • layout - :strided (dense) or :sparse
  • device - the compute device, like CPU or GPU
  • requires_grad - whether or not to record gradients

You can specify properties when creating a tensor

Torch.rand(2, 3, dtype: :double, layout: :strided, device: "cpu", requires_grad: true)

Operations

Create a tensor

x = Torch.tensor([10, 20, 30])

Add

x + 5 # tensor([15, 25, 35])

Subtract

x - 5 # tensor([5, 15, 25])

Multiply

x * 5 # tensor([50, 100, 150])

Divide

x / 5 # tensor([2, 4, 6])

Get the remainder

x % 3 # tensor([1, 2, 0])

Raise to a power

x**2 # tensor([100, 400, 900])

Perform operations with other tensors

y = Torch.tensor([1, 2, 3])
x + y # tensor([11, 22, 33])

Perform operations in-place

x.add!(5)
x # tensor([15, 25, 35])

You can also specify an output tensor

result = Torch.empty(3)
Torch.add(x, y, out: result)
result # tensor([15, 25, 35])

Numo

Convert a tensor to a Numo array

a = Torch.ones(5)
a.numo

Convert a Numo array to a tensor

b = Numo::NArray.cast([1, 2, 3])
Torch.from_numo(b)

Autograd

Create a tensor with requires_grad: true

x = Torch.ones(2, 2, requires_grad: true)

Perform operations

y = x + 2
z = y * y * 3
out = z.mean

Backprop

out.backward

Get gradients

x.grad # tensor([[4.5, 4.5], [4.5, 4.5]])

Stop autograd from tracking history

x.requires_grad # true
(x**2).requires_grad # true

Torch.no_grad do
  (x**2).requires_grad # false
end

Neural Networks

Define a neural network

class MyNet < Torch::NN::Module
  def initialize
    super
    @conv1 = Torch::NN::Conv2d.new(1, 6, 3)
    @conv2 = Torch::NN::Conv2d.new(6, 16, 3)
    @fc1 = Torch::NN::Linear.new(16 * 6 * 6, 120)
    @fc2 = Torch::NN::Linear.new(120, 84)
    @fc3 = Torch::NN::Linear.new(84, 10)
  end

  def forward(x)
    x = Torch::NN::F.max_pool2d(Torch::NN::F.relu(@conv1.call(x)), [2, 2])
    x = Torch::NN::F.max_pool2d(Torch::NN::F.relu(@conv2.call(x)), 2)
    x = x.view(-1, num_flat_features(x))
    x = Torch::NN::F.relu(@fc1.call(x))
    x = Torch::NN::F.relu(@fc2.call(x))
    x = @fc3.call(x)
    x
  end

  def num_flat_features(x)
    size = x.size[1..-1]
    num_features = 1
    size.each do |s|
      num_features *= s
    end
    num_features
  end
end

Create an instance of it

net = MyNet.new
input = Torch.randn(1, 1, 32, 32)
net.call(input)

Get trainable parameters

net.parameters

Zero the gradient buffers and backprop with random gradients

net.zero_grad
out.backward(Torch.randn(1, 10))

Define a loss function

output = net.call(input)
target = Torch.randn(10)
target = target.view(1, -1)
criterion = Torch::NN::MSELoss.new
loss = criterion.call(output, target)

Backprop

net.zero_grad
p net.conv1.bias.grad
loss.backward
p net.conv1.bias.grad

Update the weights

learning_rate = 0.01
net.parameters.each do |f|
  f.data.sub!(f.grad.data * learning_rate)
end

Use an optimizer

optimizer = Torch::Optim::SGD.new(net.parameters, lr: 0.01)
optimizer.zero_grad
output = net.call(input)
loss = criterion.call(output, target)
loss.backward
optimizer.step

Saving and Loading Models

Save a model

Torch.save(net.state_dict, "net.pth")

Load a model

net = MyNet.new
net.load_state_dict(Torch.load("net.pth"))
net.eval

Tensor Creation

Here’s a list of functions to create tensors (descriptions from the C++ docs):

  • arange returns a tensor with a sequence of integers
  Torch.arange(3) # tensor([0, 1, 2])
  • empty returns a tensor with uninitialized values
  Torch.empty(3) # tensor([7.0054e-45, 0.0000e+00, 0.0000e+00])
  • eye returns an identity matrix
  Torch.eye(2) # tensor([[1, 0], [0, 1]])
  • full returns a tensor filled with a single value
  Torch.full([3], 5) # tensor([5, 5, 5])
  • linspace returns a tensor with values linearly spaced in some interval
  Torch.linspace(0, 10, 5) # tensor([0, 5, 10])
  • logspace returns a tensor with values logarithmically spaced in some interval
  Torch.logspace(0, 10, 5) # tensor([1, 1e5, 1e10])
  • ones returns a tensor filled with all ones
  Torch.ones(3) # tensor([1, 1, 1])
  • rand returns a tensor filled with values drawn from a uniform distribution on [0, 1)
  Torch.rand(3) # tensor([0.5444, 0.8799, 0.5571])
  • randint returns a tensor with integers randomly drawn from an interval
  Torch.randint(1, 10, [3]) # tensor([7, 6, 4])
  • randn returns a tensor filled with values drawn from a unit normal distribution
  Torch.randn(3) # tensor([-0.7147,  0.6614,  1.1453])
  • randperm returns a tensor filled with a random permutation of integers in some interval
  Torch.randperm(3) # tensor([2, 0, 1])
  • zeros returns a tensor filled with all zeros
  Torch.zeros(3) # tensor([0, 0, 0])

Examples

Here are a few full examples:

LibTorch Installation

Download LibTorch. For Linux, use the cxx11 ABI version. Then run:

bundle config build.torch-rb --with-torch-dir=/path/to/libtorch

Here’s the list of compatible versions.

Torch.rb LibTorch
0.2.0+ 1.5.0+
0.1.8 1.4.0
0.1.0-0.1.7 1.3.1

Homebrew

For Mac, you can use Homebrew.

brew install libtorch

Then install the gem (no need for bundle config).

Performance

Linux

Deep learning is significantly faster on a GPU. Install CUDA and cuDNN and reinstall the gem.

Check if CUDA is available

Torch::CUDA.available?

Move a neural network to a GPU

net.cuda

rbenv

This library uses Rice to interface with LibTorch. Rice and earlier versions of rbenv don’t play nicely together. If you encounter an error during installation, upgrade ruby-build and reinstall your Ruby version.

brew upgrade ruby-build
rbenv install [version]

History

View the changelog

Contributing

Everyone is encouraged to help improve this project. Here are a few ways you can help:

To get started with development:

git clone https://github.com/ankane/torch.rb.git
cd torch.rb
bundle install
bundle exec rake compile -- --with-torch-dir=/path/to/libtorch
bundle exec rake test

You can use this script to test on GPUs with the AWS Deep Learning Base AMI (Ubuntu 18.04).

Here are some good resources for contributors: