2017年7月18日星期二

tf.scan 详解

关于tensorflow的资料虽然多,但是针对具体函数的资料却是很少。笔者因为工作需要,想要仔细学习一下tf.scan,探索如下,仅供参考。(笔者是个tensorflow初级菜鸟,不保证理解全部正确)

tf.scan 函数定义如下

scan(
    fn,
    elems,
    initializer=None,
    parallel_iterations=10,
    back_prop=True,
    swap_memory=False,
    infer_shape=True,
    name=None
)

该函数就是一个递归函数。
我们先来看几个简单的例子:
例子一:
elems = np.array([1, 2, 3, 4, 5, 6])
sum = scan(lambda a, x: a + x, elems)
# sum == [1, 3, 6, 10, 15, 21]

在这里的scan中,由于没有initializer这个初始化参数,所以 ,在运行过程中,elems的第一个元素会被当作是初始化的值赋给a,
index     a      x     sum
1            1     #       1
2            1      2      3
3            3      3      6
4            6      4      10
5           10     5      15
6           15     6      21
故a中存储的是上一时刻的值,而x中存储的是输入的值的依次遍历


例子二:
import tensorflow as tf
import numpy as np elems = np.array([1, 2, 3, 4, 5, 6])
initializer = np.array(0)
sum_one = tf.scan(
    lambda a, x: x[0] - x[1] + a, (elems + 1, elems), initializer)
# sum_one == [1, 2, 3, 4, 5, 6]

观察这个函数,首先,它传入的值是两个list(elems+1,elems),
其中elems+1为[2,3,4,5,6,7],elems为[1,2,3,4,5,6]
a被初始化为0
故在函数执行过程中
index   a       x        sum_one
1          0    [2,1]     2-1+0=1
2          1    [3,2]     3-2+1=2
3          2    [4,3]     4-3+2=3
4          3    [5,4]           4
5          4    [6,5]           5
6          5    [7,6]           6
由于当传入多个值时,是依次取对应索引上的值,故而在elems中传入的值必须要shape相同,也很容易注意到,a保存的是前一时刻的计算结果,x中保存的是当前的输入值。

例子三:
import tensorflow as tf
import numpy as np
elems = np.array([1, 0, 0, 0, 0, 0])
initializer = (np.array(0), np.array(1))
fibonaccis = tf.scan(lambda a, _: (a[1], a[0] + a[1]), elems, initializer)
sess = tf.Session()
print initializer
print sess.run(fibonaccis)
# fibonaccis == ([1, 1, 2, 3, 5, 8], [1, 2, 3, 5, 8, 13])

index       a          x      fibonaccis
1            [0,1]      1          [1],[1]
2            [1,1]      0          [1,1],[1,2]
3            [1,2]      0          [1,1,2],[1,2,3]
4            [2,3]      0          [1,1,2,3],[1,2,3,5]
.....依次类推
在初始化值有多个的情况下,是这样调用的。

到此我们分别总结了初始化为一个列表的情况,和输入为列表的情况。现在就要来自己实现一个,初始化为多值的列表,输入同样是为一个列表的情况。(大部分代码中应该都有这样的需求)
前面的列子均来自于官网tf.scan函数下面的函数。下面的这个例子属于笔者胡编乱造,为了看下如何实现两个输入均为列表的情况

import tensorflow as tf
import numpy as np
def fn(xs,ys):
    (x1,x2) = xs
    (y1,y2) = ys
    return (y1+y2+x1,y2*y1+x2)

elems = np.array([1,2,3,4,5,6])
initializer = (np.array(0),np.array(1))
outcome = tf.scan(fn,(elems+1,elems),initializer)

sess = tf.Session()
print "outcome",sess.run(outcome)

#outcome (array([ 3,  8, 15, 24, 35, 48]), array([  3,   9,  21,  41,  71, 113]))

让我们来看看它是如何生成序列的
首先,我们在scan中设置了elmes和initializer,
在调用函数时,xs中为[[2,3,4,5,6,7],[1,2,3,4,5,6]]
                      ys中为[0,1]
下面的xs和ys仅表示在某一时刻下的值
index       xs        ys        outcome
1            [0,1]    [2,1]      [3],[3]
2            [3,3]    [3,2]      [3,8],[3,9]
3            [8,9]    [4,3]      [3,8,15],[3,9,21]
...
依次类推

故而,对于tf.scan函数,可以总结为以下几点
1.当没有赋初始值时,tf.scan会把elems的第一个元素作为初始化值,故,elems在initializer没有值时一定要有值。
2.initializer参数和elems参数都可以有若干个。
3.initializer中的值只在第一轮中用到,然后它就用来存储每次计算出来的值,所以,初始化值和输出的中间结果个数一定要想等。

到这里,tf.scan常用参数大家应该都已经很清楚了,笔者反正感觉应该可以开始写代码了。

加油↖(^ω^)↗
于7月19日两点,笔者成功的把tf.scan用在了自己的程序上,并且完美的实现了功能,点个赞。


补充:在实践过程中 ,你会遇到一个问题。就是有些参数被用来初始化参数,但是却并不被返回。而有些值则是在调用函数中被生成,并且会被返回,遇到这样的情况,我们应该怎么做呢?

通过探索,在tf.scan中并没有这个功能,对于那种用作初始化并且不会在循环中改变的值,我们只能用self.* = *的形式传入。我在tf的相关函数中,均没有看到对这种问题的解决方法。
但在这里我不能妄下定论,只是笔者没有找到,因为看到这篇文章并且知道的大神可以给予指正















没有评论:

发表评论

leetcode 17

17.   Letter Combinations of a Phone Number Medium Given a string containing digits from   2-9   inclusive, return all possible l...