# NeuralGPU卷积与RNN结合超线性计算时间多因子时间序列预测

By [zekeer.eth](https://paragraph.com/@zekeer) · 2022-05-30

---

看NTM blog的时候发现的NeuralGPU的paper，感觉这个东西要是能够打破RNN架构或者是更加巧妙的利用卷积将会是一个令人惊叹的设计，但是几篇论文里面都还是在糅合CNN和RNN，个人感觉这个设计应该还是处于早期设计阶段。

更新：

NeuralGPU简单介绍---在我前面的短文里面介绍了一种常见的使用卷积CNN网络处理多因子股票收益率序列的玩法，在处理多因子数据的时候，将多因子数据合成一副图片，然后使用CNN处理这个图片，进行预测。这里NeuralGPU的玩法大致类似，有一点区别的地方在于，模型将卷积核里面的通道（每层卷积滤波器的数量）扩展成一个输入多因子数据的维度，同时将原来的2D图片的高度当作是时间序列步，这样在第一副图片输入的时候（原始数据嵌入）in\_channels进入通道数量从原来的表示3幅红绿蓝三基色的图片变成了多因子数据。

为了便于理解，可以参照下面的立方体图片，前表面就是常见的卷积图片，立方体的厚度也就是通常的卷积滤波器的数量，这里将前表面的上第一列的数据点当作时刻标度，然后每个数据点对应的厚度也就是一个超长的数据列嵌入多因子序列。这个时候这个立方体就嵌入一张图片，在yoz轴线上，这个立方体其他地方初始置0就完成初始数据嵌入了。

更新：

在后续的paper里面任务原始的模型设计对NLP处理效果欠佳，并探索了马尔科夫扩展和卷积扩展，这里由于使用many to one的数据处理，稍作调整，在最后一步直接使用tf.matmul处理取代卷积操作。

数据扩展如下图1，在对模型循环n次之后，使用另外的n次循环每次依次获得一个时刻输出，并累积时刻输出进入迭代公式，显式达到前一个时刻输出影响后一时刻的效果（这里又从新回到了RNN模型思路上了），设置tape缓存，初始置0，然后没循环一次填充一个t时刻的输出结果并进入下一次循环中。

对于一个实例来学习算法，NeuralGPU最大的一个优点是实现超线性计算时间的算法学习，这个是目前仅有的一个支持超线性计算时间的RNN变种\[2\]。在对NeuralGPU进行扩展之后取得了相当不错的效果在NLP上面\[1\]。

\\

![](https://storage.googleapis.com/papyrus_images/408cf4533a941681cef920bf18e0e460c469990574f1f9ff66bff4b2a9010033.png)

NeuralGPU的使用方法可以假想下面的立方体。\\

使用tf.nn.conv2d里面的in\_channels表示多因子数据，对应卷积操作为，2D卷积中图片的高度为RNN中的时间步，宽度为初始置0，多通道的为多因子数据。

如上图，对应一个2d卷积核操作，第一列（阴影列）为时间步，每个像素点对应一个RNN的时刻k的输入，也就是图片的高度输入时间步，宽度非第一列初始置0. 将多通道（多滤波器）。

输入数据格式为\[batch, in\_length, in\_width\] batch x 时刻 x 多因子数据

调整为\[batch, in\_length, w, in\_width\]数据格式，其中w第一列为输入数据，其余置0在初始状态。多因子数据对应2D卷积的通道操作。

![](https://storage.googleapis.com/papyrus_images/c697e252d8fdfd4ff967731611bbc8ddc0421c9d4e1078ddee41d5349d9a50b9.jpg)

立方体 in\_length\*w 面为论文里面的mental image，深度（in\_width,多因子数据）对应tf.nn.conv2d里面的channels，这个地方是一个比较容易错意的地方。总的来说虽然这个模型引入的卷积，并且拥有一个超线性的计算时间，但是经过仔细研究，我认为这依然是一个广义线性玩法，这样进行建模对应多因子序列预测会探索多因子之间的相互关系，但是并不会改变多因子因子的阶次，所以该模型建立之后，经过学习对应的APT模型依然是i=1的情况。

![r_p = \alpha + \beta\_1 x^n\_1+...+ \beta_i x^n_i ](https://storage.googleapis.com/papyrus_images/3e21313de45280323a86d194bdb20de68cafe3a5af14897b30b7f5b2b480ba43.svg)

r\_p = \\alpha + \\beta\\\_1 x^n\\\_1+...+ \\beta\_i x^n\_i

这个模型我在14~16年HS300成份股横面数据处理方式进行多因子时间序列预测的时候，泛化效果相当不错，加上两层dropout之后泛化效果相当好，但是模型构建求解特别麻烦。

  
参考：

[Can Active Memory Replace Attention?](https://link.zhihu.com/?target=http%3A//sciencewise.info/articles/1610.08613/)\\

[Extensions and Limitations of the Neural GPU](https://link.zhihu.com/?target=https%3A//www.researchgate.net/publication/309631810_Extensions_and_Limitations_of_the_Neural_GPU)\\

[Neural GPUs Learn Algorithms](https://link.zhihu.com/?target=https%3A//arxiv.org/abs/1511.08228)\\

python代码：

卷积RNN函数部分

    import tensorflow as tf
    
    def CGRU(state, parameter, prefix):
        kh = parameter[0]
        kw = parameter[1]
        c_in = parameter[2]
        c_out = parameter[3]
        
        with tf.variable_scope(prefix):
            # reset
            U_1 = tf.get_variable(name='U1_kernel_bank', shape=[kh, kw, c_in, c_out], initializer=tf.truncated_normal_initializer())
            B_1 = tf.get_variable(name='B1_bias_vectors', shape=[c_out], initializer=tf.truncated_normal_initializer())    
            reset = tf.sigmoid(tf.add(x=tf.nn.conv2d(input=state, filter=U_1, strides=[1,1,1,1], padding='SAME'), y=B_1))
            #reset = sigmoid_cutoff(tf.add(x=tf.nn.conv2d(input=state, filter=U_1, strides=[1,1,1,1], padding='SAME'), y=B_1))
            
            
            # update
            U_2 = tf.get_variable(name='U2_kernel_bank', shape=[kh, kw, c_in, c_out], initializer=tf.truncated_normal_initializer())
            B_2 = tf.get_variable(name='B2_bias_vectors', shape=[c_out], initializer=tf.truncated_normal_initializer())  
            update = tf.sigmoid(tf.add(x=tf.nn.conv2d(input=state, filter=U_2, strides=[1,1,1,1], padding='SAME'), y=B_2))
            
            # CGRU(s)
            U_0 = tf.get_variable(name='U0_kernel_bank', shape=[kh, kw, c_in, c_out], initializer=tf.truncated_normal_initializer())
            B_0 = tf.get_variable(name='B0_bias_vectors', shape=[c_out], initializer=tf.truncated_normal_initializer())  
            cgru_tmp = tf.add(tf.nn.conv2d(input=tf.multiply(reset,state), filter=U_0, strides=[1,1,1,1], padding='SAME'), B_0)
            return tf.add(tf.multiply(update, state), tf.multiply(tf.subtract(1.,update),tf.tanh(cgru_tmp)))
        
    def Multi_CGRUs(state, parameter, layers_num, prefix):
        for i in range(layers_num):
            state = CGRU(state, parameter, prefix+"multi_%d"%i)
        return state
    
    def CGRU_d(state, tape, prameter, prefix):
        kh = parameter[0]
        kw = parameter[1]
        c_in = parameter[2]
        c_out = parameter[3]
        
        with tf.variable_scope(prefix):
            # reset
            U_1 = tf.get_variable(name='U1_kernel_bank', shape=[kh, kw, c_in, c_out], initializer=tf.truncated_normal_initializer())
            W_1 = tf.get_variable(name='W1', shape=[kh, kw, c_in, c_out], initializer=tf.truncated_normal_initializer())
            B_1 = tf.get_variable(name='B1_bias_vectors', shape=[c_out], initializer=tf.truncated_normal_initializer())    
            r1 = tf.nn.conv2d(input=state, filter=U_1, strides=[1,1,1,1], padding='SAME')
            r2 = tf.nn.conv2d(input=tape, filter=W_1, strides=[1,1,1,1], padding='SAME')
            reset = tf.sigmoid(tf.add(tf.add(r1,r2),B_1))
            
            # update
            U_2 = tf.get_variable(name='U2_kernel_bank', shape=[kh, kw, c_in, c_out], initializer=tf.truncated_normal_initializer())
            W_2 = tf.get_variable(name='W2', shape=[kh, kw, c_in, c_out], initializer=tf.truncated_normal_initializer())
            B_2 = tf.get_variable(name='B2_bias_vectors', shape=[c_out], initializer=tf.truncated_normal_initializer())
            u1 = tf.nn.conv2d(input=state, filter=U_2, strides=[1,1,1,1], padding='SAME')
            u2 = tf.nn.conv2d(input=tape, filter=W_2, strides=[1,1,1,1], padding='SAME')
            update = tf.sigmoid(tf.add(tf.add(u1,u2),B_2))
            
            # CGRU(s)
            U_0 = tf.get_variable(name='U0_kernel_bank', shape=[kh, kw, c_in, c_out], initializer=tf.truncated_normal_initializer())
            W_0 = tf.get_variable(name='W0', shape=[kh, kw, c_in, c_out], initializer=tf.truncated_normal_initializer())
            B_0 = tf.get_variable(name='B0_bias_vectors', shape=[c_out], initializer=tf.truncated_normal_initializer())  
            c1 = tf.nn.conv2d(input=tf.multiply(reset,state), filter=U_0, strides=[1,1,1,1], padding='SAME')
            c2 = tf.nn.conv2d(input=tape, filter=W_0, strides=[1,1,1,1], padding='SAME')
            c3 = tf.tanh(tf.add(tf.add(c1,c2),B_0))
            return tf.add(tf.multiply(update, state), tf.multiply(tf.subtract(1.,update),c3))    
    
    def Multi_CGRUDs(state, tape, parameter, layers_num, prefix):
        for i in range(layers_num):
            state = CGRU_d(state, tape, parameter, prefix+"multi_%d"%i)
        return state
    

原始Nerual\_GPU的 拓扑结构构建，使用tf.whlie\_loop的swap节省显存，这一部分主要是一个元细胞结构，也就是可以学习n多进制位“四则运算”的那个初始模型。

    with tf.variable_scope('previous') as scope_previous:
        tmp = Multi_CGRUs(X, parameter, layers, prefix='previous_part_for_n_steps_')
        
        def cond(tp, tmp):
            return tf.less(tp, in_length)
    
        def body(tp, tmp):
            scope_previous.reuse_variables()
            tmp = Multi_CGRUs(tmp, parameter, layers, prefix='previous_part_for_n_steps_')
            tp = tp +1
            return tp, tmp
    
        ftp, prev = tf.while_loop(cond, body, [1, tmp]) 
    Previous_Part = prev   
    len(tf.trainable_variables())
    

NerualGPU 扩展输出部分，这是扩展对输出结果进行n步扫描或者卷积处理NLP的扩展模型，本文所说的多因子时间序列是使用如下的拓扑结构构建的。这一部分我暂时没有找到好的方法，可以使用笨法子，显式配置多GPU显存跑到512滤波器。

\\

    for tp in range(in_length):
        with tf.variable_scope('previous') as scope_previous:
            if tp == 0:
                tmp = Multi_CGRUs(X, parameter, layers, prefix='previous_part_for_n_steps_')
                #print(tp)
            else:
                scope_previous.reuse_variables()
                tmp = Multi_CGRUs(tmp, parameter, layers, prefix='previous_part_for_n_steps_')
                #print (tp)
    Previous_Part = tmp 
    

\\

    tape = tf.Variable(tf.zeros(shape=[batch_size, in_length, w, in_width], dtype=tf.float32), trainable=False)
    
    for tl in range(in_length):
        with tf.variable_scope('latter') as scope_latter:
    
            if tl == 0:
                state_l = Multi_CGRUDs(Previous_Part, tape, parameter, layers, prefix='latter_part_for_n_steps_')
                tmp_1 = tf.split(value=state_l, num_or_size_splits=in_length, axis=1)[0]
                tmp_1 = tf.split(value=tmp_1, num_or_size_splits=w, axis=2)[0]
                tmp_1 = tf.concat(values=[tmp_1]+[tf.zeros_like(tmp_1)]*(w-1), axis=2)
                tmp_2 = tf.split(value=tape, num_or_size_splits=in_length, axis=1)
                tmp_2[tl] = tmp_1
                tape = tf.concat(values=tmp_2, axis=1)
                #print (tl)
            
            else:
                scope_latter.reuse_variables()
                state_l = Multi_CGRUDs(state_l, tape, parameter, layers, prefix='latter_part_for_n_steps_')
                tmp_1 = tf.split(value=state_l, num_or_size_splits=in_length, axis=1)[tl]
                tmp_1 = tf.split(value=tmp_1, num_or_size_splits=w, axis=2)[0]
                tmp_1 = tf.concat(values=[tmp_1]+[tf.zeros_like(tmp_1)]*(w-1), axis=2)
                tmp_2 = tf.split(value=tape, num_or_size_splits=in_length, axis=1)
                tmp_2[tl] = tmp_1
                tape = tf.concat(values=tmp_2, axis=1)
                #print (tl)
    len(tf.trainable_variables())

---

*Originally published on [zekeer.eth](https://paragraph.com/@zekeer/neuralgpu-rnn)*
