Table Of Contents

Previous topic

toolbox – [doc TODO]

Next topic

Optimizations

This Page

scan – Looping in Theano

Guide

The scan functions provides the basic functionality needed to do loops in Theano. Scan comes with many whistles and bells, that can be easily introduced through a few examples :

Basic functionality : Computing A^k

Assume that, given k you want to get A**k using a loop. More precisely, if A is a tensor you want to compute A**k elemwise. The python/numpy code would loop like

result = 1
for i in xrange(k):
  result = result * A

The equivalent Theano code would be

# Symbolic description of the result
result,updates = theano.scan(fn = lambda x_tm1,A: x_tm1*A,\
                     outputs_info = T.ones_like(A),\
                     non_sequences  = A, \
                     n_steps        = k)

# compiled function that returns A**k
f = theano.function([A,k], result[-1], updates = updates)

Let us go through the example line by line. What we did is first to construct a function (using a lambda expression) that given x_tm1 and A returns x_tm1*A. Given the order of the parameters, x_tm1 is the value of our output at time step t-1. Therefore x_t (value of output at time t) is A times value of output at t-1. Next we initialize the output as a tensor with same shape as A filled with ones. We give A to scan as a non sequence parameter and specify the number of steps k to iterate over our lambda expression.

Scan will return a tuple, containing our result (result) and a dictionary of updates ( empty in this case). Note that the result is not a matrix, but a 3D tensor containing the value of A**k for each step. We want the last value ( after k steps ) so we compile a function to return just that. Note that there is an optimization, that at compile time will detect that you are using just the last value of the result and ensure that scan does not store all the intermediate values that are used. So do not worry if A and k are large.

Multiple outputs, several taps values - Recurrent Neural Network with Scan

A more practical task would be to implement a RNN using scan. Assume that our RNN is defined as follows :

x(n) = \tanh( W x(n-1) + W^{in}_1 u(n) + W^{in}_2 u(n-4) +
W^{feedback} y(n-1) )

y(n) = W^{out} x(n- 3)

Note that this network is far from a classical recurrent neural network and might be useless. The reason we defined as such is to better ilustrate the features of scan.

In this case we have a sequence over which we need to iterate u, and two outputs x and y. To implement this with scan we first construct a function that computes one iteration step :

def oneStep(u_tm4, u_t, x_tm3, x_tm1, y_tm1, W, W_in_1, W_in_2,  W_feedback, W_out):

  x_t = T.tanh( theano.dot(x_tm1, W) + \
                theano.dot(u_t,   W_in_1) + \
                theano.dot(u_tm4, W_in_2) + \
                theano.dot(y_tm1, W_feedback))
  y_t = theano.dot(x_tm3, W_out)

  return [x_t, y_t]

As naming convention for the variables we used a_tmb to mean a at t-b and a_tpb to be a at t+b. Note the order in which the parameters are given, and in which the result is returned. Try to respect cronological order among the taps ( time slices of sequences or outputs) used. For scan is crucial only for the variables representing the different time taps to be in the same order as the one in which these taps are given. Also, not only taps should respect an order, but also variables, since this is how scan figures out what should be represented by what. Given that we have all the Theano variables needed we construct our RNN as follows :

u  = T.matrix() # it is a sequence of vectors
x0 = T.matrix() # initial state of x has to be a matrix, since
                # it has to cover x[-3]
y0 = T.vector() # y0 is just a vector since scan has only to provide
                # y[-1]


([x_vals, y_vals],updates) = theano.scan(fn = oneStep, \
                             sequences    = dict(input = u, taps= [-4,-0]), \
                             outputs_info = [dict(initial = x0, taps = [-3,-1]),y0], \
                             non_sequences  = [W,W_in_1,W_in_2,W_feedback, W_out])
     # for second input y, scan adds -1 in output_taps by default

Now x_vals and y_vals are symbolic variables pointing to the sequence of x and y values generated by iterating over u. The sequence_taps, outputs_taps give to scan information about what slices are exactly needed. Note that if we want to use x[t-k] we do not need to also have x[t-(k-1)], x[t-(k-2)],.., but when applying the compiled function, the numpy array given to represent this sequence should be large enough to cover this values. Assume that we compile the above function, and we give as u the array uvals = [0,1,2,3,4,5,6,7,8]. By abusing notations, scan will consider uvals[0] as u[-4], and will start scaning from uvals[4] towards the end.

Using shared variables - Gibbs sampling

Another useful feature of scan, is that it can handle shared variables. For example, if we want to implement a Gibbs chain of length 10 we would do the following:

W = theano.shared ( W_values ) # we assume that ``W_values`` contains the
                               # initial values of your weight matrix

bvis = theano.shared( bvis_values)
bhid = theano.shared( bhid_values)

trng = T.shared_randomstreams.RandomStreams(1234)

def OneStep( vsample) :
   hmean   = T.nnet.sigmoid( theano.dot( vsample, W) + bhid)
   hsample = trng.binomial( size = hmean.shape, n = 1, prob = hmean)
   vmean   = T.nnet.sigmoid( theano.dot( hsample. W.T) + bvis)
   return trng.binomial( size = vsample.shape, n = 1, prob = vsample)

sample = theano.tensor.vector()

values, updates = theano.scan( OneStep, outputs_info = sample, n_steps = 10 )

gibbs10 = theano.function([sample], values[-1], updates = updates)

Note that if we use shared variables ( W, bvis, bhid) but we do not iterate over them ( so scan doesn’t really need to know anything in particular about them, just that they are used inside the function applied at each step) you do not need to pass them as arguments. Scan will find them on its on and add them to the graph. Of course, if you wish to (and it is good practice) you can add them, when you call scan (they would be in the list of non sequence inputs).

The second, and probably most crucial observation is that the updates dictionary becomes important in this case. It links a shared variable with its updated value after k steps. In this case it tells how the random streams get updated after 10 iterations. If you do not pass this update dictionary to your function, you will always get the same 10 sets of random numbers. You can even use the updates dictionary afterwards. Look at this example :

a = theano.shared(1)
values,updates = theano.scan( lambda : {a:a+1}, n_steps = 10 )

In this case the lambda expression does not require any input parameters and returns an update dictionary which tells how a should be updated after each step of scan. If we write :

b = a+1
c = updates[a] + 1
f = theano.function([], [b,c], updates = updates)

print b
print c
print a.value

We will see that because b does not use the updated version of a, it will be 2, c will be 12, while a.value is 11. If we call the function again, b will become 12, c will be 22 and a.value 21.

If we do not pass the updates dictionary to the function, then a.value will always remain 1, b will always be 2 and c will always be 12.

Reference

This module provides the Scan Op

Scanning is a general form of recurrence, which can be used for looping. The idea is that you scan a function along some input sequence, producing an output at each time-step that can be seen (but not modified) by the function at the next time-step. (Technically, the function can see the previous K time-steps of your outputs and L time steps (from the past and future of the sequence) of your inputs.

So for example, sum() could be computed by scanning the z+x_i function over a list, given an initial state of z=0.

Special cases:

  • A reduce operation can be performed by returning only the last output of a scan.
  • A map operation can be performed by applying a function that ignores each previous output.

Often a for-loop can be expressed as a scan() operation, and scan is the closest that theano comes to looping. The advantage of using scan over for loops is that it allows the number of iterations to be a part of the symbolic graph.

The Scan Op should typically be used by calling the scan() function.

theano.map(fn, sequences, non_sequences=[], truncate_gradient=-1, go_backwards=False, mode=None, name=None)

Similar behaviour as python map

Parameters:
  • fn – the function to be applied over the elements in sequences ( see scan fn for more info)
  • sequences – list of arrays over which map should iterate (see scan for more info)
  • non_sequences – list of other arguments of fn over which map shouldn’t iterate (see scan for more info)
  • truncate_gradient – see scan for more info
  • go_backwards – set to true if you want map to start at the end of the provided arrays in sequences going towards 0 (back in time)
  • mode – see scan
  • name – see scan
theano.reduce(fn, sequences, outputs_info, non_sequences=[], go_backwards=False, mode=None, name=None)

Similar behaviour as python reduce

Parameters:
  • fn – the function to be applied over the elements in sequences ( see scan fn for more info)
  • outputs_info – information about outputs (mainly the initial state of each, but other options are available ), see scan for more info
  • sequences – list of arrays over which reduce should iterate (see scan for more info)
  • non_sequences – list of other arguments of fn over which reduce shouldn’t iterate (see scan for more info)
  • go_backwards – set to true if you want map to start at the end of the provided arrays in sequences going towards 0 (back in time)
  • mode – see scan
  • name – see scan
theano.foldl(fn, sequences, outputs_info, non_sequences=[], mode=None, name=None)

Similar behaviour as haskell foldl

Parameters:
  • fn – the function to be applied over the elements in sequences ( see scan fn for more info)
  • sequences – list of arrays over which foldl should iterate (see scan for more info)
  • outputs_info – information about outputs (mainly the initial state of each )
  • non_sequences – list of other arguments of fn over which foldl shouldn’t iterate (see scan for more info)
  • mode – see scan
  • name – see scan
theano.foldr(fn, sequences, outputs_info, non_sequences=[], mode=None)

Similar behaviour as haskell foldr

Parameters:
  • fn – the function to be applied over the elements in sequences ( see scan fn for more info)
  • sequences – list of arrays over which foldr should iterate (see scan for more info)
  • outputs_info – information about outputs (mainly the initial state of each )
  • non_sequences – list of other arguments of fn over which foldr shouldn’t iterate (see scan for more info)
  • truncate_gradient – see scan for more info
  • mode – see scan
  • name – see scan
theano.scan(fn, sequences=[], outputs_info=[], non_sequences=[], n_steps=None, truncate_gradient=-1, go_backwards=False, mode=None, name=None)

Function that constructs and applies a Scan op

Parameters:
  • fn

    Function that describes the operations involved in one step of scan Given variables representing all the slices of input and past values of outputs and other non sequences parameters, fn should produce variables describing the output of one time step of scan. The order in which the argument to this function are given is very important. You should have the following order:

    • all time slices of the first sequence (as given in the sequences list) ordered in the same fashion as the time taps provided
    • all time slices of the second sequence (as given in the sequences list) ordered in the same fashion as the time taps provided
    • ...
    • all time slices of the first output (as given in the initial_state list) ordered in the same fashion as the time taps provided
    • all time slices of the second otuput (as given in the initial_state list) ordered in the same fashion as the time taps provided
    • ...
    • all other parameters over which scan doesn’t iterate ordered accordingly

    If you are using shared variables over which you do not want to iterate, you do not need to provide them as arguments to fn, though you can if you wish so. The function should return the outputs after each step plus the updates for any of the shared variables. You can either return only outputs or only updates. If you have both outputs and updates the function should return them as a tuple : (outputs, updates) or (updates, outputs).

    Outputs can be just a theano expression if you have only one output or a list of theano expressions. Updates can be given either as a list of tuples or as a dictionary. If you have a list of outputs, the order of these should match that of their initial_states.

  • sequences

    list of Theano variables or dictionaries containing Theano variables over which scan needs to iterate. The reason you might want to wrap a certain Theano variable in a dictionary is to provide auxiliary information about how to iterate over that variable. For example this is how you specify that you want to use several time slices of this sequence at each iteration step. The dictionary should have the following keys :

    • input – Theano variable representing the sequence
    • taps – temporal taps to use for this sequence. They are given as a list of ints, where a value k means that at iteration step t scan needs to provide also the slice t+k The order in which you provide these int values here is the same order in which the slices will be provided to fn.

    If you do not wrap a variable around a dictionary, scan will do it for you, under the assumption that you use only one slice, defined as a tap of offset 0. This means that at step t scan will provide the slice at position t.

  • outputs_info

    list of Theano variables or dictionaries containing Theano variables used to initialize the outputs of scan. As before (for sequences) the reason you would wrap a Theano variable in a dictionary is to provide additional information about how scan should deal with that specific output. The dictionary should contain the following keys:

    • initial – Theano variable containing the initial state of the output
    • taps – temporal taps to use for this output. The taps are given as a list of ints (only negative .. since you can not use future values of outputs), with the same meaning as for sequences (see above).
    • inplace – theano variable pointing to one of the input sequences; this flag tells scan that the output should be computed in the memory space occupied by that input sequence. Note that scan will only do this if allowed by the rest of your computational graph and if you are not using past taps of the input.
    • return_steps how many steps to return from your output. If not given, or 0 scan will return all steps, otherwise it will return the last return_steps. Note that if you set this to something else then 0, scan will try to be smart about the amount of memory it allocates for a given input.

    If the function applied recursively uses only the previous value of the output, the initial state should have same shape as one time step of the output; otherwise, the initial state should have the same number of dimension as output. This is easily understood through an example. For computing y[t] let us assume that we need y[t-1], y[t-2] and y[t-4]. Through an abuse of notation, when t = 0, we would need values for y[-1], y[-2] and y[-4]. These values are provided by the initial state of y, which should have same number of dimension as y, where the first dimension should be large enough to cover all the required past values, which in this case is 4. If init_y is the variable containing the initial state of y, then init_y[0] corresponds to y[-4], init_y[1] corresponds to y[-3], init_y[2] corresponds to y[-2], init_y[3] corresponds to y[-1]. The default behaviour of scan is the following :

    • if you do not wrap an output in a dictionary, scan will wrap it for you assuming that you use only the last step of the output ( i.e. it makes your tap value list equal to [-1]) and that it is not computed inplace
    • if you wrap an output in a dictionary and you do not provide any taps but you provide an initial state it will assume that you are using only a tap value of -1
    • if you wrap an output in a dictionary but you do not provide any initial state, it assumes that you are not using any form of taps
    • if you provide a None instead of a variable or a dictionary scan assumes that you will not use any taps for this output (this would be the case for map)

    If you did not provide any information for your outputs, scan will assume by default that you are not using any taps for any of the outputs. If you provide information for just a subset of outputs, scan will not know to which outputs these correspond and will raise an error.

  • non_sequences – Parameters over which scan should not iterate. These parameters are given at each time step to the function applied recursively.
  • n_steps – Number of steps to iterate. If the input sequences are not long enough, scan will produce a warning and run only for the maximal amount of steps allowed by the input sequences. If the value is 0, the outputs will have 0 rows. If the value is negative, scan will run backwards (or if the flag go_backwards is already set to true it will run forward in time). If n_steps is not provided, or evaluetes to None, inf or nan, scan will figure out the maximal amount of steps it can run given the input sequences and do that.
  • truncate_gradient – Number of steps to use in truncated BPTT. If you compute gradients through a scan op, they are computed using backpropagation through time. By providing a different value then -1, you choose to use truncated BPTT instead of classical BPTT, where you only do truncate_gradient number of steps.
  • go_backwards – Flag indicating if you should go backwards through the sequences ( if you think as the sequences being indexed by time, this would mean go backwards in time)
  • name – The name of the theano function compiled by the Scan op. It will show in the profiler output.
  • mode – The mode used when compiling the theano function in the Scan op. If None, it will use the config mode. If None and the config mode is set to profile mode, it we will create a new instance of the ProfileMode in order to compute the timming correctly. If no new instance is created the time spend in Scan will show up twice in the profiling, once as the time taken by scan, and the second time as the time taken by the ops inside scan. This will be even worse for multiple cascading scans. The new profiler instance will be printed when python exits.
Return type:

tuple

Returns:

tuple of the form (outputs, updates); outputs is either a Theano variable or a list of Theano variables representing the outputs of scan. updates is a dictionary specifying the updates rules for all shared variables used in the scan operation; this dictionary should be pass to theano.function