在theano中无序列扫描? (模拟范围())

Scanning without sequences in theano? (emulating range())

这里是 Theano 新手。我正在做一些实验以生成可变长度的序列。我从想到的最简单的事情开始:模拟 range()。这是我写的简单代码:

from theano import scan
from theano import function
from theano import tensor as  T

X = T.iscalar('X')
STEP = T.iscalar('STEP')
MAX_LENGTH = 1024  # or any othe very large value

def fstep(i, x, t):
    n = i * t
    return n, until(n >= x)

t_fwd_range, _ = scan(
    fn=fstep,
    sequences=T.arange(MAX_LENGTH),
    non_sequences=[X, STEP]
)

getRange = function(
    inputs=[X, Param(STEP, 1, 'step')],
    outputs=t_fwd_range
)

getRange(x, step)
print list(f)
assert list(f[:-1]) == list(range(0, x, step))

所以我不得不使用 MAX_LENGTH 作为范围的长度,用作 fstep theano scan 的输入。所以,我的主要问题是:有没有办法在没有输入序列的情况下使用 scan ?而且,正如我认为答案是 ,下一个问题是:这是做我想做的事情的正确(最有效,ecc)方法吗?

无需提供输入序列即可扫描。您可以改为通过扫描的 n_steps 参数指定迭代次数。或者,您还可以通过 theano.scan_module.until.

指定扫描应提前停止的条件

因此,Python 的 range 函数可以使用 Theano 的 scan 来模拟,而无需指定输入序列,方法是计算出构建请求的序列需要多少次迭代。

下面是基于 Theano 的 scan 的范围函数的实现。唯一复杂的部分是计算需要多少步骤。

import numpy
import theano
import theano.tensor as tt
import theano.ifelse


def scan_range_step(x_tm1, step):
    return x_tm1 + step


def compile_theano_range():
    tt.arange
    symbolic_start = tt.lscalar()
    symbolic_stop = tt.lscalar()
    symbolic_step = tt.lscalar()
    n_steps = tt.cast(
        tt.ceil(tt.abs_(symbolic_stop - symbolic_start) / tt.cast(tt.abs_(symbolic_step), theano.config.floatX)),
        'int64') - 1
    outputs, _ = theano.scan(scan_range_step, outputs_info=[symbolic_start], n_steps=n_steps,
                             non_sequences=[symbolic_step], strict=True)
    outputs = theano.ifelse.ifelse(tt.eq(n_steps, 0), tt.stack(symbolic_start), outputs)
    f = theano.function([symbolic_start, symbolic_stop, symbolic_step],
                        outputs=tt.concatenate([[symbolic_start], outputs]))

    def theano_range(start, stop=None, step=1):
        assert isinstance(start, int)
        assert isinstance(step, int)
        if step == 0:
            raise ValueError()
        if stop is None:
            stop = start
            start = 0
        else:
            assert isinstance(stop, int)
        if start == stop:
            return []
        if stop < start and step > 0:
            return []
        if stop > start and step < 0:
            return []
        return f(start, stop, step)

    return theano_range


def main():
    theano_range = compile_theano_range()
    python_range = range

    for start in [-10, -5, -1, 0, 1, 5, 10]:
        for stop in [-10, -5, -1, 0, 1, 5, 10]:
            for step in [-3, -2, -1, 1, 2, 3]:
                a = theano_range(start, stop, step)
                b = python_range(start, stop, step)
                assert numpy.all(numpy.equal(a, b)), (start, stop, step, a, b)


main()

显然,这对 do/use 来说确实是一件愚蠢的事情,因为 Theano 已经提供了 Python 的 range 函数的符号版本,即 theano.tensor.arange。内置实现也比我们的 scan 版本高效得多,因为它不使用 scan,而是使用自定义操作。

根据经验:您必须 通过rangen_steps 参数设置最大迭代步数。您可以将它设置为一个非常大的数字,然后在满足停止条件的情况下使用 theano.scan_module.until 在较早的阶段停止迭代。