Lecture Notes: Introduction to Lua and Torch

Understanding Lua/Torch code will be useful to follow the material presented in the course but you are free to experiment with any other tool that you prefer, especially if you already have experience with a different one.

Why Lua?

  • Mainly because of Torch, a powerful library with lots of vector, matrix, and tensor operations.
  • Torch is heavily developed and maintained by Facebook AI Research, among many people from several universities and research groups.
  • Torch is being used by many research groups and companies around the world, and has a good community built around.
  • It usually supports all the basic primitives needed to implement state-of-the-art research.
  • The Lua Programming language syntax is simple, and the philosphy is very Javascript which you probably are already familiar with.
  • Lua was developed by a Computer Graphics Group in Brazil's PUC Rio University. It means moon in Portuguese.
  • Lua has a niche following as a scripting language for games.

Numbers and strings.

In Lua you have available the 'string' and 'math' libraries providing basic functionalities. (Here is a link to the official Lua manual https://www.lua.org/manual/5.1/)

In [ ]:
-- Declare a string and and some numbers.
str = 'visual recognition'
x = 10
y = 30

-- Manipulating strings using the 'string' library.
print(str)  -- print str
print(string.sub(str, 1, 6))  -- print a substring

-- Manipulating numbers using the 'math' library.
print(x)  -- print the sum.
print(math.log(x))
print(math.sqrt(x))

-- You can also print formatted output as in C.
print(string.format('This is a string: %s, and this is a number: %.3f', str, math.sqrt(x + y)))

Control structures (loops and conditional statements)

In [ ]:
-- For loop syntax.
for i = 1, 10 do
    -- This is the same as string.format('iteration %d', i)
    print(('iteration %d'):format(i))
end

for i = 1, 10 do
   -- If statement syntax.
   if i % 2 == 0 then
        print(('Even number %d'):format(i))
   else
        print(('Odd number %d'):format(i))
   end
end

Variable scope

By default in Lua, all variables are global, meaning you can access a variable value even if you declared such variable inside a loop or in a completely different file. This means that it is probably a good idea to declare most of your variables local unless you really want them to be global.

In [ ]:
a = 'Hi, my name is A'
local b = 'Hi, my name is B'

if string.len(b) > 0 then
    c = 'Hi, my name is C'
    local d = 'Hi, my name is D'
end

print(a)
print(b)
print(c)
print(d) -- This one will print nil.

In jupyter/ipython, cells are in different scopes so in the next cell we still have access to global variables a, and c but will not have access to b anymore.

In [ ]:
print(a) 
print(b) -- This one will print nil.
print(c)

Conditional Assignments

The following ways of initializing variables are also popular in Lua/Torch.

In [ ]:
-- (counter or 0) evaluates to 0 when counter is nil.
for i = 1, 10 do
    counter = 1 + (counter or 0)
end
print(counter)

-- The form 
-- var = (condition) and [value1] or [value2] 
-- is similar to the ternary conditional operator in C: 
-- var = (condition) ? [value1]: [value2];
local passedNine = (counter > 9) and 'Bigger than 9' or 'Smaller than 9'
print(passedNine)

Tables

Tables are the only data structure you will need in Lua, they can behave like arrays, lists, hashmaps, C-like structures.

Tables as structures:

In [ ]:
-- We can initialize a table in this way.
course = {}
course.name = 'Computational Visual Recognition'
course.school = 'University of Virginia'
course.nStudents = 39
course.year = 2016

print(course)  -- Print the table
print(course.school)  -- Print a table field
print(course['school']) -- Another way to print a field.
In [ ]:
-- We can also simply do.
course = {name = 'Computational Visual Recognition',
          school = 'University of Virginia',
          nStudents = 39, year = 2016}
print(course)
print(course.school)
print(course['school']) -- Another way to print a field.

Tables as arrays:

In [ ]:
-- We can also use tables as arrays.
local myarray = {}
for i = 1, 10 do
    myarray[i] = math.exp(i)  -- Store the exp of each i.
end
print(myarray) -- Print the array-table
print(myarray[1]) -- Print the first element (indices start at 1 in Lua).

-- Caution: The # operator only works when using the table as an array or list
-- This means when the table keys are contiguous integer values.
print(#myarray) -- Prints the number of elements

-- Traversing the array.
for k,v in pairs(myarray) do
    print(string.format('index: %d, value %d', k, v))
end

Tables as lists:

In [ ]:
local mylist1 = {'cat', 'dog', 'rabbit', 'monkey'}
local mylist2 = {'frog', 'shark', 'donkey'}

-- insert all elements of the second list in the first list.
for k,v in pairs(mylist2) do
    table.insert(mylist1, v)
end

print(mylist1)
print(#mylist1)

Tables as hashmaps:

In [ ]:
-- Count the number of repeats of each element
local alist = {'do', 're', 'mi', 'la', 'fa', 'do', 're', 'mi', 'si', 're'}
local counts = {}

-- note that (counts[v] or 0) evalutes to 0 when counts[v] is nil.
for k, v in pairs(alist) do
    counts[v] = 1 + (counts[v] or 0)
end

print(counts)
-- The following line returns 0 because # is only defined for contiguous integer indices.
-- print(#counts)

Functions

In [ ]:
-- This is how you define functions in lua.
function sum(x, y)
    return x + y
end
print(sum(2,3))

-- Functions can also be defined nameless or assigned as variables.
local mul = function(x, y) return x * y end
print(mul(2, 3))

-- Function to add some fixed number to input.
local some = 5
function sumSome(x)
    -- notice how "some" was defined outside the function.
    -- you can only use local variables defined right outside the function.
    return some + x
end
print(sumSome(2))
In [ ]:
print(some)  -- some was a local variable in a different cell so this will print nil.
print(sumSome(2))  -- but sumSome is still available and still has access to variable some.
In [ ]:
-- Functions can also be attached to tables to simulate "classes".
-- e.g. 

dog = {}
dog.name = 'Rocky'
function dog:bark()
    print('whoof, I am ' .. self.name .. '!')
end

dog:bark()  -- same as dog.bark(dog)

Torch Tensors

Tensors are not natively part of Lua but part of the Torch Library. Tensors are for our purposes just generalizations of matrices where one can have more than two dimensions. There are no memory limits on Torch tensors, Lua tables have an upper limit on how much memory they can take.

You can find a more detailed treatment of tensors in the Torch documentation: https://github.com/torch/torch7/blob/master/doc/tensor.md

Here I include a matrix multiplication operation but you can consult other operations here: https://github.com/torch/torch7/blob/master/doc/maths.md

In [ ]:
require 'torch' -- Usually you do not need to declare this explicitly.

-- This creates a 5x5 matrix of float values.
local a = torch.FloatTensor(5, 3)
a:fill(3) -- fill all elements of the matrix with 3's.
print(a) -- print the matrix on the console.

-- This creates a 3x2 matrix of float avalues.
local b = torch.FloatTensor(3, 2) 
b:fill(2)
print(b)

-- Multiply the two matrices and output the result.
print(a * b) --- alternatively you can use torch.mm(a, b)

Basic tensor indexing

In [ ]:
-- create a matrix of size 5x5 filled with 8's.
local a = torch.FloatTensor(5, 5):fill(8)

-- change the element in position (3, 3) to 0.
a[{3, 3}] = 0

-- change the first row of the matrix to 2's.
a[{{1}, {}}]:fill(2)

-- change the last column of the matrix to 5's.
a[{{}, {-1}}]:fill(5)

-- change the first two columns to 1's.
a[{{}, {1,2}}]:fill(1)

print(a) -- print the resulting matrix.

Tensor references

Torch tensors are always references unless you use use the clone method. Sometimes you want to have two variables pointing to the same tensor, this is something used extensively in torch. But you have to use this carefully because you might inadvertently modify a tensor somewhere else in your code, or if you are running many threads on the same tensor.

In [ ]:
local a = torch.FloatTensor(1, 3):fill(3)  -- create a 1x3 vector filled with 3's.
local b = a     -- a and b are the same.
local c = a:clone()    -- c is a copy of a.

b[1][3] = 4 -- this will affect tensor a.

print(a) -- a[{1, 3}] is changed.
print(c) -- c is the original tensor a.

Finally here is a couple of urls that you might also find useful at this point:

If you find any errors or omissions in this material please contact me at vicente@cs.virginia.edu