1. 程式人生 > >[論文閱讀] RNN 在阿里DIEN中的應用

[論文閱讀] RNN 在阿里DIEN中的應用

# [論文閱讀] RNN 在阿里DIEN中的應用 ## 0x00 摘要 本文基於阿里推薦DIEN程式碼,梳理了下RNN一些概念,以及TensorFlow中的部分原始碼。本部落格旨在幫助小夥伴們詳細瞭解每一步驟以及為什麼要這樣做。 ## 0x01 背景知識 ### 1.1 RNN RNN,迴圈神經網路,Recurrent Neural Networks。 人們思考問題往往不是從零開始的,比如閱讀時我們對每個詞的理解都會依賴於前面看到的一些資訊,而不是把前面看的內容全部拋棄再去理解某處的資訊。應用到深度學習上面,如果我們想要學習去理解一些依賴上文的資訊,RNN 便可以做到,它有一個迴圈的操作,可以使其可以保留之前學習到的內容。 最普通的RNN定義方式是: ```python output = new_state = f(W * input + U * state + B) = act(W * input + U * state + B) ``` - U,W 是網路引數(權重矩陣),b 是偏置引數,這些引數通過後向傳播訓練網路學習得到。 - act 是啟用函式,通常選擇 sigmoid 或 tanh 函式。 ### 1.2 DIEN專案程式碼 在DIEN專案中,把TensorFlow的rnn程式碼拿到自己專案中,做了一些修改,具體是: - 使用了 GRUCell; - 自定義了 VecAttGRUCell; - 因為修改了VecAttGRUCell介面,所以修改了rnn.py; ## 0x02 Cell RNN的基本單元被稱為Cell,別小看那一個小小的cell,它並不是只有1個neuron unit,而是n個hidden units。 因此,我們注意到tensorflow中定義一個cell(BasicRNNCell/BasicLSTMCell/GRUCell/RNNCell/LSTMCell)結構的時候需要提供的一個引數就是hidden_units_size。 ![](https://img2020.cnblogs.com/blog/1850883/202011/1850883-20201106212054722-54128773.png) 在實際的神經網路中,各個門處理函式 其實是由一定數量的隱含層神經元來處理。 在RNN中,M個神經元組成的隱含層,實際的功能應該是 f(wx + b), 這裡實現了兩步: - 首先M個隱含層神經元與輸入向量X之間全連線,通過w引數矩陣對x向量進行加權求和; - 其次就是對x向量各個維度上進行篩選,加上bias偏置矩陣後,通過f激勵函式, 得到隱含層的輸出; 在LSTM Cell中,一個cell 包含了若干個門處理函式,假如每個門的物理實現,我們都可以看做是由num_hidden個神經元來實現該門函式功能, 那麼每個門各自都包含了相應的w引數矩陣以及bias偏置矩陣引數,這就是在上圖中的實現。 從圖中可以看出,cell單元裡有四個門,每個門都對應128個隱含層神經元,相當於四個隱含層,每個隱含層各自與輸入x 全連線,而輸入x向量是由兩部分組成,一部分是上一時刻cell 輸出,大小為128, 還有部分就是當前樣本向量的輸入,大小為6,因此通過該cell內部計算後,最終得到當前時刻的輸出,大小為128,即num_hidden,作為下一時刻cell的一部分輸入。 下面我們結合TensorFlow來具體剖析下Cell的實現機制和原理。 ### 2.1 RNNCell(抽象父類) #### 2.1.1 基礎 **“RNNCell”,它是TensorFlow中實現RNN的基本單元**,每個RNNCell都有一個call方法,使用方式是:(output, next_state) = call(input, state)。 RNNCell是一個抽象的父類,其他的RNNcell都會繼承該方法,然後具體實現其中的call()函式。 RNNCell是包含一個State(狀態)並且能夠執行一些處理輸入矩陣的物件。RNNCell將輸入的矩陣(Input Matrix)運算輸出一個包含”self.output”列的輸出矩陣(Ouput Matrix)。 **state**: state就是rnn網路中rnn cell的狀態,比如說如果你的rnn定義包含了N個單元(也就是你的self.state_size是個整數N),那麼在你每次執行RNN網路時就應該給一個 [batch_size,self.state_size] 形狀的2D Tensor來表示當前RNN網路的狀態,而如果你的 self.state_size 是一個元祖,那麼給定的狀態也應該是一個Tuple,每個Tuple裡的狀態表示和之前的方式一樣。 - 如果定義了 “self.state_size”這個屬性,並且取值為一個整數,那麼RNNCell則會同時輸出一個狀態矩陣(State Matrix),包含 “self.state_size” 列。 - 如果 “self.state_size” 定義為一個整數的Tuple,那麼則是輸出對應長度的狀態矩陣的Tuple,Tuple中的每一個狀態矩陣長度還是和之前的一樣,包含 “self.state_size” 列。 RNNCell其主要是zero_state()和call()兩個函式。 - zero_state 用於初始化初始狀態 h0 為全零向量。 - call 定義實際的RNNCell的操作(比如RNN就是一個啟用,GRU的兩個門,LSTM的三個門控等,不同的RNN的區別主要體現在這個函式)。 除了call方法外,對於RNNCell,還有兩個類屬性比較重要,其中 state_size() 和 output_size() 方法設定為類屬性,可以當做屬性來呼叫(這裡用到的是Python內建的@property裝飾器,就是負責把一個方法變成屬性呼叫的,很像C#中的屬性、欄位的那種概念): - state_size,是隱層的大小(代表 Cell 的狀態 state 大小) - output_size,是輸出的大小(輸出維度) 比如我們通常是將一個batch送入模型計算,設輸入資料的形狀為(batch_size, input_size),那麼計算時得到的隱層狀態就是(batch_size, state_size),輸出就是(batch_size, output_size)。 但這裡兩個方法都沒有實現,意思是說我們必須要實現一個子類繼承 RNNCell 類並實現這兩個方法。 ```python class RNNCell(base_layer.Layer): def __call__(self, inputs, state, scope=None): if scope is not None: with vs.variable_scope(scope, custom_getter=self._rnn_get_variable) as scope: return super(RNNCell, self).__call__(inputs, state, scope=scope) else: with vs.variable_scope(vs.get_variable_scope(), custom_getter=self._rnn_get_variable): return super(RNNCell, self).__call__(inputs, state) def _rnn_get_variable(self, getter, *args, **kwargs): variable = getter(*args, **kwargs) if context.in_graph_mode(): trainable = (variable in tf_variables.trainable_variables() or (isinstance(variable, tf_variables.PartitionedVariable) and list(variable)[0] in tf_variables.trainable_variables())) else: trainable = variable._trainable # pylint: disable=protected-access if trainable and variable not in self._trainable_weights: self._trainable_weights.append(variable) elif not trainable and variable not in self._non_trainable_weights: self._non_trainable_weights.append(variable) return variable @property def state_size(self): raise NotImplementedError("Abstract method") @property def output_size(self): raise NotImplementedError("Abstract method") def build(self, _): pass def zero_state(self, batch_size, dtype): with ops.name_scope(type(self).__name__ + "ZeroState", values=[batch_size]): state_size = self.state_size return _zero_state_tensors(state_size, batch_size, dtype) ``` #### 2.1.2 call **每個派生的RNNCell必須有以下的屬性並實現具有如下函式簽名的函式(output, next_state) = call(input, state)。** 可選的第三個輸入引數 ‘scope’,用於向下相容,給子類定製化使用。scope傳入的值是tf.Variable型別,用於更方便的管理變數。 > 從給定的state開始執行,根據rnn cell的輸入 > > args: > > > inputs:是一個具有二維的張量shape為[batch_size, input_size] > > states:如果 `self.state_size` 是一個整數,state就應該是一個二維張量 shape是 `[batch_size, self.state_size]`,否則,如果 `self.state_size` 是一個整數的tuple(例如LSTM需要計算cell state和 hidden unit state ,就是一個tuple),那麼state就應該是`[batch_size, s] for s in self.state_size` 形狀的tuple。 > > Scope:由其他子類建立的變數。 > > Return: > > > 是一對,包括: > > 輸出:`[batch_size, self.output_size]`State: 和state相匹配的shape **每呼叫一次RNNCell的call方法,就相當於在時間上“推進了一步”,這就是RNNCell的基本功能。** ### 2.2 BasicRNNCell(基礎類) #### 2.2.1 基礎 RNNCell只是一個抽象類,我們用的時候都是用的它的兩個子類 BasicRNNCell 和 BasicLSTMCell。顧名思義,前者是RNN的基礎類,後者是LSTM的基礎類。 BasicRNNCell 就是我們常說的 RNN。 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200325151904907.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTI4NTY4NjY=,size_16,color_FFFFFF,t_70) 最簡單的RNN結構如上圖所示。其程式碼如下: ```python class BasicRNNCell(RNNCell): def __init__(self, num_units, activation=None, reuse=None): super(BasicRNNCell, self).__init__(_reuse=reuse) self._num_units = num_units self._activation = activation or math_ops.tanh self._linear = None @property def state_size(self): return self._num_units @property def output_size(self): return self._num_units def call(self, inputs, state): """Most basic RNN: output = new_state = act(W * input + U * state + B).""" if self._linear is None: self._linear = _Linear([inputs, state], self._num_units, True) output = self._activation(self._linear([inputs, state])) # output = Ht = tanh([x,Ht-1]*W + B) # 一個output作為下一時刻的輸入Ht,另一個作為下一層的輸入 Ht return output, output ``` #### 2.3.2 引數意義 可以看到在初始化 `__init__`中有若干引數。 ```python def __init__(self, num_units, activation=None, reuse=None): ``` `__init__`最重要的一個引數是 num_units,意思就是這個 Cell 中神經元的個數
,另外還有一個引數 activation 即預設使用的啟用函式,預設使用的 tanh,reuse 代表該 Cell 是否可以被重新使用。 我們知道一個最基本的RNN單元中有三個可訓練的引數W, U, B,以及兩個輸入變數。所以我們在構造RNN的時候就需要指定各個引數的維度了。 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20181030162931251.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L1RoZV9sYXN0ZXN0,size_16,color_FFFFFF,t_70) 注,上圖中的`n`表示的是輸入的維度dim 從原始碼中可以看出BasicRNNCell中: - state_size 就是num_units : `def state_size(self): return self._num_units` - output_size 就是num_units : `def output_size(self): return self._num_units` - 即 把state_size和output_size定義成相同, - ht和output也是相同的(call函式輸出是兩個output :`return output, output`,即其並未定義輸出部分)。 - 從 _linear 可以看出,`output_size`指的是偏置B的維度(下文會講解 _Linear)。 #### 2.2.3 功能 其主要功能實現就是call函式的第一行註釋,就是input和前一時刻狀態state經過一個線性函式在經過一個啟用函式即可,也就是最普通的RNN定義方式。也就是說 ```python output = new_state = f(W * input + U * state + B) = act(W * input + U * state + B) ``` 在 state_size()、output_size() 方法裡,其返回的內容都是 num_units,即神經元的個數。 接下來 call() 方法中: - 傳入的引數為 inputs 和 state,即輸入的 x 和 上一次的隱含狀態 - 首先例項化了一個 _Linear 類,這個類實際上就是做線性變換的類,將二者傳遞過來,然後直接呼叫,就實現了 w * [inputs, state] + b 的線性變換 : `output = new_state = tanh(W * input + U * state + B).` - 其次回到 BasicRNNCell 的 call() 方法中,在 _linear() 方法外面又包括了一層 _activation() 方法,即對線性變換應用一次 tanh 啟用函式處理,作為輸出結果。 - 最後返回的結果是 output 和 output,第一個代表 output,第二個代表隱狀態,其值也等於 output。 #### 2.2.4 Linear 上面寫到使用了 _linear 類,現在我們就介紹下。 這個類傳遞了 [inputs, state] 作為 __call__() 方法的 args,會執行 concat() 和 matmul() 方法,然後接著再執行 bias_add() 方法,這樣就實現了線性變換
。 而output_size是輸出層的大小,我們可以看到 - BasicRNNCell中,output_size就是_num_units; - GRUCell中是2 * _num_units; - BasicLSTMCell中是4 * _num_units; 這是因為_linear中執行的是RNN中的幾個等式的 `Wx + Uh + B` 的功能,但是不同的RNN中數量不同,比如LSTM中需要計算四次,然後直接把output_size定義為4_num_units,再把輸出進行拆分成四個變數即可。 下面是原始碼縮減版 ```python class _Linear(object): def __init__(self, args, output_size, build_bias, bias_initializer=None, kernel_initializer=None): self._build_bias = build_bias if not nest.is_sequence(args): args = [args] self._is_sequence = False else: self._is_sequence = True # Calculate the total size of arguments on dimension 1. total_arg_size = 0 shapes = [a.get_shape() for a in args] for shape in shapes: total_arg_size += shape[1].value dtype = [a.dtype for a in args][0] # 迴圈該函式 num_step(句子長度) 次,則該層計算完; def __call__(self, args): # 如果是第 0 時刻,那麼當前的 state(即上一時刻的輸出H0)的值全部為0; # input 的 shape為: [batch_size, emb_size] # state 的 shape為:[batch_zize, Hidden_size] # matmul : 矩陣相乘 # array_ops.concat: 兩個矩陣連線,連線後的 shape 為 [batch_size,input_size + Hidden_size],實際就是[Xt,Ht-1] if not self._is_sequence: args = [args] if len(args) == 1: res = math_ops.matmul(args[0], self._weights) else: # 此時計算: [input,state] * [W,U] == [Xt,Ht-1] * W,得到的shape為:[batch_size,Hidden_size] res = math_ops.matmul(array_ops.concat(args, 1), self._weights) # B 的shape 為:【Hidden_size】 # [Xt,Ht-1] * W 計算後的shape為:[batch_size,Hidden_size] # nn_ops.bias_add,這個函式的計算方法是,讓每個 batch 得到的值,都加上這個 B # 加上B後:Ht = tanh([Xt, Ht-1] * W + B),得到的 shape 還是: [batch_size,Hidden_size] # 那麼這個 Ht 將作為下一時刻的輸入和下一層的輸入; if self._build_bias: res = nn_ops.bias_add(res, self._biases) return res ``` ### 2.3 GRUCell GRU,Gated Recurrent Unit。在 GRU 中,只有兩個門:重置門(Reset Gate)和更新門(Update Gate)。同時在這個結構中,把 Ct 和隱藏狀態進行了合併,整體結構比標準的 LSTM 結構要簡單,而且這個結構後來也非常流行。 接下來我們看一下GRU的定義,相比BasicRNNCell只改變了call函式部分,增加了重置門和更新門兩部分,分別由r和u表示。然後c表示要更新的狀態值。其對應的圖及公式如下所示: ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20200325154030552.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTI4NTY4NjY=,size_16,color_FFFFFF,t_70) ```python r = f(W1 * input + U1 * state + B1) u = f(W2 * input + U2 * state + B2) c = f(W3 * input + U3 * r * state + B3) h_new = u * h + (1 - u) * c ``` GRUCell的實現程式碼縮減版如下: ```python class GRUCell(RNNCell): def __init__(self, num_units, activation=None, reuse=None, kernel_initializer=None, bias_initializer=None): super(GRUCell, self).__init__(_reuse=reuse) self._num_units = num_units self._activation = activation or math_ops.tanh self._kernel_initializer = kernel_initializer self._bias_initializer = bias_initializer self._gate_linear = None self._candidate_linear = None @property def state_size(self): return self._num_units @property def output_size(self): return self._num_units def call(self, inputs, state): value = math_ops.sigmoid(self._gate_linear([inputs, state])) r, u = array_ops.split(value=value, num_or_size_splits=2, axis=1) r_state = r * state if self._candidate_linear is None: with vs.variable_scope("candidate"): self._candidate_linear = _Linear( [inputs, r_state], self._num_units, True, bias_initializer=self._bias_initializer, kernel_initializer=self._kernel_initializer) c = self._activation(self._candidate_linear([inputs, r_state])) new_h = u * state + (1 - u) * c return new_h, new_h ``` 具體函式功能解析如下: 在 state_size()、output_size() 方法裡,其返回的內容都是 num_units,即神經元的個數。 call() 方法中,因為 Reset Gate rt 和 Update Gate zt 分別用變數 r、u 表示,它們需要先對 ht-1 即 state 和 xt 做合併,然後再實現線性變換,再呼叫 sigmod 函式得到: ```python value = math_ops.sigmoid(self._gate_linear([inputs, state])) r, u = array_ops.split(value=value, num_or_size_splits=2, axis=1) ``` 然後需要求解 ht~,首先用 rt 和 ht-1 即 state 相乘: ```python r_state = r * state ``` 然後將其放到線性函式裡面 `_Linear` ,再呼叫 tanh 啟用函式即可: ```python c = self._activation(self._candidate_linear([inputs, r_state])) ``` 最後計算隱含狀態和輸出結果,二者一致: ```python new_h = u * state + (1 - u) * c ``` 這樣即可返回得到輸出結果和隱藏狀態。 ```python return new_h, new_h ``` ### 2.4 自定義RNNCell 自定義RNNCell的方法比較簡單,那就是繼承_LayerRNNCell這個抽象類,然後一定要實現__init__、build、__call__這三個函式就行了,其中在call函式中實現自己需要的功能即可。(注意:build只調用一次,在build中進行變數例項化,在call中實現具體的rnncell操作)。 ### 2.5 DIEN之VecAttGRUCell 呼叫VecAttGRUCell的程式碼如下: ```python rnn_outputs2, final_state2 = dynamic_rnn(VecAttGRUCell(HIDDEN_SIZE), inputs=rnn_outputs, att_scores = tf.expand_dims(alphas, -1), sequence_length=self.seq_len_ph, dtype=tf.float32, scope="gru2") ``` 首先我們要注意到 tf.expand_dims的使用,這個函式是用來把 alphas 增加一維。 ```python alphas = Tensor("Attention_layer_1/Reshape_4:0", shape=(?, ?), dtype=float32) ``` -1 表示在最後增加一維。 ```java att_scores = tf.expand_dims(alphas, -1) ``` 阿里在這裡做的修改主要是call函式,是關於att_score的修改: ```python u = (1.0 - att_score) * u new_h = u * state + (1 - u) * c return new_h, new_h ``` 具體程式碼是: ```python def call(self, inputs, state, att_score=None): ...... c = self._activation(self._candidate_linear([inputs, r_state])) u = (1.0 - att_score) * u # 這裡是新增加的 new_h = u * state + (1 - u) * c # 這裡是新增加的 return new_h, new_h ``` 其中執行時變數如下: ```java inputs = {Tensor} Tensor("rnn_2/gru2/while/TensorArrayReadV3:0", shape=(?, 36), dtype=float32) state = {Tensor} Tensor("rnn_2/gru2/while/Identity_2:0", shape=(?, 36), dtype=float32) att_score = {Tensor} Tensor("rnn_2/gru2/while/strided_slice:0", shape=(?, 1), dtype=float32) new_h = {Tensor} Tensor("rnn_2/gru2/while/add_1:0", shape=(?, 36), dtype=float32) u = {Tensor} Tensor("rnn_2/gru2/while/mul_1:0", shape=(?, 36), dtype=float32) c = {Tensor} Tensor("rnn_2/gru2/while/Tanh:0", shape=(?, 36), dtype=float32) ``` 具體對應論文中就是: ![](https://img2020.cnblogs.com/blog/1850883/202011/1850883-20201106212155260-1341227270.png) ## 0x03 RNN ### 3.1 一次執行多步 #### 3.1.1 基礎 基礎的RNNCell有一個很明顯的問題:對於單個的RNNCell,我們使用它的call函式進行運算時,只是在序列時間上前進了一步。比如使用x1、h0得到h1,通過x2、h1得到h2等**。這樣的h話,如果我們的序列長度為10,就要呼叫10次call函式,比較麻煩。對此,TensorFlow提供了一個tf.nn.dynamic_rnn函式,使用該函式就相當於呼叫了n次call函式。**即通過{h0,x1, x2, …., xn}直接得{h1,h2…,hn}。 ```python def dynamic_rnn(cell, inputs, att_scores=None, sequence_length=None, initial_state=None, dtype=None, parallel_iterations=None, swap_memory=False, time_major=False, scope=None): ``` 重要引數介紹: - **cell**:LSTM、GRU等的記憶單元。cell引數代表一個LSTM或GRU的記憶單元,也就是一個cell。例如,cell = tf.nn.rnn_cell.LSTMCell((num_units),其中,num_units表示rnn cell中神經元個數,也就是下文的cell.output_size。返回一個LSTM或GRU cell,作為引數傳入。 - **inputs**:輸入的訓練或測試資料,一般格式為[batch_size, max_time, embed_size],其中batch_size是輸入的這批資料的數量,max_time就是這批資料中序列的最長長度,embed_size表示嵌入的詞向量的維度。 - **sequence_length**:是一個list,假設你輸入了三句話,且三句話的長度分別是5,10,25,那麼sequence_length=[5,10,25]。 - **time_major**:決定了輸出tensor的格式,如果為True, 張量的形狀必須為 [max_time, batch_size,cell.output_size]。如果為False, tensor的形狀必須為[batch_size, max_time, cell.output_size],cell.output_size表示rnn cell中神經元個數。 返回值如下: outputs就是time_steps步裡所有的輸出。它的形狀為(batch_size, time_steps, cell.output_size)。 state是最後一步的隱狀態,它的形狀為(batch_size, cell.state_size)。 詳細如下: - **outputs**. outputs是一個tensor,是每個step的輸出值。 - 如果time_major==True,outputs形狀為 [max_time, batch_size, cell.output_size ](要求rnn輸入與rnn輸出形狀保持一致) - 如果time_major==False(預設),outputs形狀為 [ batch_size, max_time, cell.output_size ] - **state.** state是一個tensor。state是最終的狀態,也就是序列中最後一個cell輸出的狀態。一般情況下state的形狀為 [batch_size, cell.output_size ],但當輸入的cell為BasicLSTMCell時,state的形狀為[2,batch_size, cell.output_size ],其中2也對應著LSTM中的cell state和hidden state max_time就是這批資料中序列的最長長度,如果輸入的三個句子,那max_time對應的就是最長句子的單詞數量,cell.output_size其實就是rnn cell中神經元的個數。 #### 3.1.2 使用 假設們輸入資料的格式為(batch_size, time_steps, input_size),其中: - batch_size是輸入的這批資料的數量; - time_steps表示序列本身的長度,如在Char RNN中,長度為10的句子對應的time_steps就等於10; - input_size就表示輸入資料單個序列單個時間維度上固有的長度; 如下我們已經定義好了一個RNNCell,呼叫該RNNCell的call函式time_steps次 ```python # inputs: shape = (batch_size, time_steps, input_size) # cell: RNNCell # initial_state: shape = (batch_size, cell.state_size)。初始狀態。一般可以取零矩陣 outputs, state = tf.nn.dynamic_rnn(cell, inputs, initial_state=initial_state) ``` 對於引數舉例如下: > 樣本資料: > > > 小明愛學習 > > > > 小王愛學習 > > > > 小李愛學習 > > > > 小花愛學習 > > 通常樣本資料會以 `(batch_size, time_step, embedding_size)` 送入模型,對應的可以是(4,5,100)。 > > 4表示批量送入也就是(小,小,小,小)第二批是(明,王,李,花)… > > 5表示時間步長,一句話共5個字。 又比如如下程式碼: ```python import tensorflow as tf import numpy as np from tensorflow.python.ops import variable_scope as vs output_size = 5 batch_size = 4 time_step = 3 dim = 3 cell = tf.nn.rnn_cell.BasicRNNCell(num_units=output_size) inputs = tf.placeholder(dtype=tf.float32, shape=[time_step, batch_size, dim]) h0 = cell.zero_state(batch_size=batch_size, dtype=tf.float32) X = np.array([[[1, 2, 1], [2, 0, 0], [2, 1, 0], [1, 1, 0]], # x1 [[1, 2, 1], [2, 0, 0], [2, 1, 0], [1, 1, 0]], # x2 [[1, 2, 1], [2, 0, 0], [2, 1, 0], [1, 1, 0]]]) # x3 outputs, final_state = tf.nn.dynamic_rnn(cell, inputs, initial_state=h0, time_major=True) sess = tf.Session() sess.run(tf.global_variables_initializer()) a, b = sess.run([outputs, final_state], feed_dict={inputs:X}) print(a) print(b) ``` #### 3.1.3 time_step 具體解釋如下: > **文字資料** > > 如果資料有1000段時序的句子,每句話有25個字,對每個字進行向量化,每個字的向量維度為300,那麼batch_size=1000,time_steps=25,input_size=300。 > > 解析:time_steps一般情況下就是等於句子的長度,input_size等於字量化後向量的長度。 > > **圖片資料** > > 拿MNIST手寫數字集來說,訓練資料有6000個手寫數字影象,每個數字影象大小為28*28,batch_size=6000沒的說,time_steps=28,input_size=28,我們可以理解為把圖片圖片分成28份,每份shape=(1, 28)。 > > **音訊資料** > > 如果是單通道音訊資料,那麼音訊資料是一維的,假如shape=(8910,)。使用RNN的資料必須是二維的,這樣加上batch_size,資料就是三維的,第一維是batch_size,第二維是time_steps,第三位是資料input_size。我們可以把資料reshape成三維資料。這樣就能使用RNN了。 ### 3.2 如何迴圈呼叫 dnn有static和dynamic的分別。 - static_rnn會把RNN展平,用空間換時間。 - dynamic_rnn則是使用for或者while迴圈。 呼叫static_rnn實際上是生成了rnn按時間序列展開之後的圖。開啟tensorboard你會看到sequence_length個rnn_cell stack在一起,只不過這些cell是share weight的。因此,sequence_length就和圖的拓撲結構繫結在了一起,因此也就限制了每個batch的sequence_length必須是一致。 呼叫dynamic_rnn不會將rnn展開,而是利用tf.while_loop這個api,通過Enter, Switch, Merge, LoopCondition, NextIteration等這些control flow的節點,生成一個可以執行迴圈的圖(這個圖應該還是靜態圖,因為圖的拓撲結構在執行時是不會變化的)。在tensorboard上,你只會看到一個rnn_cell, 外面被一群control flow節點包圍著。對於dynamic_rnn來說,sequence_length僅僅代表著迴圈的次數,而和圖本身的拓撲沒有關係,所以每個batch可以有不同sequence_length。 對於DIEN,程式執行時候,堆疊如下: ```java call, utils.py:144 __call__, utils