Deep Learning專欄--FFM+Recurrent Entity Network的端到端方案

 

好久沒有寫總結了,這篇博客僅做爲最近的一些嘗試內容,記錄一些心得。FFM的優點是能夠處理高維稀疏樣本的特徵組合,已經在無數的CTR預估比賽和工業界中普遍應用,此外,其也能夠與Deep Networks結合(如DeepFM等工做),很好地應用在數據規模足夠大的工業場景中。Recurrent Entity Network是facebook AI在2017年的ICLR會議上發表的,文章提出了Recurrent Entity Network的模型用來對world state進行建模,根據模型的輸入對記憶單元進行實時的更新,從而獲得對world的一個即時的認識。該模型能夠用於機器閱讀理解、QA等領域。java

若是咱們但願作一個關於豆瓣電影的QA機器人,咱們有每一個電影的劇情文本介紹和豆瓣下面的評論,也有每一個電影的特徵(如其導演、演員、獲獎狀況、拍攝年份等),那麼利用FFM對電影特徵進行Embedding向量化,再利用Deep Networks對電影劇情文本和評論文本進行向量化,二者融合起來,豈不是能夠更好地回答提問者的問題?而Entity Networks經過memory機制存儲文本上下文信息,可以更好地捕獲先後信息,抓住實體關係,在QA領域會更加遊刃有餘。python

1. 從線性模型到FFM

1.1 線性模型

常見的線性模型,好比線性迴歸、邏輯迴歸等,它只考慮了每一個特徵對結果的單獨影響,而沒有考慮特徵間的組合對結果的影響。c++

對於一個有n維特徵的模型,線性迴歸的形式以下:算法

$$  f(x) = \omega_0 + \omega_1x_1+\omega_2x_2+...+\omega_nx_n =\omega_0+\sum_{i=1}^n{\omega_ix_i} \tag{1}  $$架構

其中$(\omega_0,\omega_1...\omega_n)$爲模型參數,$(x_1,x_2...x_n)$爲特徵。
從(1)式能夠看出來,模型的最終計算結果是各個特徵的獨立計算結果,並無考慮特徵之間的相互關係。
舉個例子,咱們「USA」與」Thanksgiving」,」China」與「Chinese new year」這樣的組合特徵是頗有意義的,在這樣的組合特徵下,會對某些商品表現出更強的購買意願,而單獨考慮國家及節日都是沒有意義的。app

1.2 二項式模型

咱們在(1)式的基礎上,考慮任意2個特徵份量之間的關係,得出如下模型: dom

$$ f(x)=\omega_0+\sum_{i=1}^n\omega_ix_i+\sum_{i=1}^{n-1}\sum_{j=i+1}^n\omega_{ij}x_ix_j \tag{2} $$ide

這個模型考慮了任意2個特徵份量之間的關係,但並未考慮更高階的關係。 
模型涉及的參數數量爲: 函數

$$ 1+n+\frac{n(n-1)}{2}=\frac{1}{2}(n^2+n+2) \tag{3} $$學習

對於參數$\omega_i$的訓練,只要這個樣本中對應的$x_i$不爲0,則能夠完成一次訓練。
但對於參數$\omega_{ij}$的訓練,須要這個樣本中的$x_i$和$x_j$同時不爲0,才能夠完成一次訓練。
在數據稀疏的實際應用場景中,二次項$\omega_{ij}$的訓練是很是困難的。由於每一個$\omega_{ij}$都須要大量$x_i$和$x_j$都不爲0的樣本。但在數據稀疏性比較明顯的樣本中,$x_i$和$x_j$都不爲0的樣本會很是稀少,這會致使$\omega_{ij}$不能獲得足夠的訓練,從而不許確。

1.3 FM算法

1.3.1 FM基本原理

爲了解決上述因爲數據稀疏引發的訓練不足的問題,咱們爲每一個特徵維度$x_i$引入一個輔助向量:

$$ V_i = (v_{i1},v_{i2},v_{i3},...,v_{ik})^T\in \mathbb R^k, i=1,2,3,...,n \tag{4} $$

其中$k$爲輔助變量的維度,依經驗而定,通常而言,對於特徵維度足夠多的樣本,$k<<n$。

1.3.2 模型及目標函數

目標是要求得如下交互矩陣W:

$$ W=
\begin{pmatrix}
\omega_{11} & \omega_{12}& ... &\omega_{1n} \\
\omega_{21} & \omega_{22}& ... &\omega_{2n} \\
\vdots &\vdots &\ddots &\vdots\\
\omega_{n1} & \omega_{n2}& ... &\omega_{nn} \\
\end{pmatrix}_{n\times n}\tag{7}$$

因爲直接求解W不方便,所以咱們引入隱變量V: 

$$ V=
\begin{pmatrix}
v_{11} & v_{12}& ... &v_{1k} \\
v_{21} & v_{22}& ... &v_{2k} \\
\vdots &\vdots &\ddots &\vdots\\
v_{n1} & v_{n2}& ... &v_{nk} \\
\end{pmatrix}_{n\times k}=\begin{pmatrix}
V_1^T\\
V_2^T\\
\cdots \\
V_n^T\\
\end{pmatrix}\tag{8}$$

$$ VV^T = W\tag{9}$$

若是咱們先獲得V,則能夠獲得W了。
如今只剩下一個問題了,是否一個存在V,使得上述式(9)成立。
理論研究代表:當k足夠大時,對於任意對稱正定的實矩陣$W\in \mathbb R^{n \times  n}$,均存在實矩陣$V\in \mathbb R^{n \times  k}$,使得$W=VV^T$。
理論分析中要求參數k足夠的大,但在高度稀疏數據的場景中,因爲 沒有足夠的樣本,所以k一般取較小的值。事實上,對參數k的限制,在必定程度上能夠提升模型的泛化能力。

假設樣本中有n個特徵,每一個特徵對應的隱變量維度爲k,則參數個數爲1+n+nk。 
正如上面所言,對於特徵維度足夠多的樣本,$k<<n$。

根據以上參數,列出FM的目標函數:

$$ f(x) = \omega_0+\sum_{i=1}^n\omega_ix_i+\sum_{i=1}^{n-1}\sum_{j=i+1}^n(V_i^TV_j)x_ix_j \tag{6}  $$

 

1.3.3 計算梯度

FM有一個重要的性質:multilinearity。若記$\Theta=(\omega_0,\omega_1,\omega_2,...,\omega_n,v_{11},v_{12},...,v_{nk})$表示FM模型的全部參數,則對於任意的$\theta \in \Theta$,存在與$\theta$無關的$g(x)$與$h(x)$,使得式(6)能夠表示爲:

$$ f(x) = g(x) + \theta h(x) \tag{11} $$

從式(11)中能夠看出,若是咱們獲得了$g(x)$與$h(x)$,則對於參數$\theta$的梯度爲$h(x)$。下面咱們分狀況討論。

A. 當$\theta=\omega_0$時,式(6)能夠表示爲:

$$ f(x) = \color{blue}{\sum_{i=1}^n\omega_ix_i+\sum_{i=1}^{n-1}\sum_{j=i+1}^n(V_i^TV_j)x_ix_j} +\omega_0 \times \color{red}{1}\tag{12} $$

上述中的藍色表示$g(x)$,紅色表示$h(x)$。下同。

從上述式子能夠看出此時的梯度爲1.

B. 當$\theta=\omega_l, l \in (1,2,...,n)$時,

$$ f(x) = \color{blue}{\omega_0+\sum_{\substack{i=1 \\ i \ne l}}^n\omega_ix_i+\sum_{i=1}^{n-1}\sum_{j=i+1}^n(V_i^TV_j)x_ix_j}+\omega_l \times \color{red}{x_l} \tag{13} $$

此時梯度爲$x_l$。

C. 當$\theta=v_{lm}$時,

$$ f(x) =\color{blue}{ \omega_0+\sum_{i=1}^n\omega_ix_i+\sum_{i=1}^{n-1}\sum_{j=i+1}^n(\sum_{\substack{s=1 \\ is \ne lm \\js \ne lm}}^k v_{is}v_{js})x_ix_j }+v_{lm}\times \color{red}{x_l\sum_{\substack{i=1\\i \ne l }}^n v_{im}x_i} \tag{14}$$

此時梯度爲$x_l\sum_{i \ne l } v_{im}x_i$。

綜合上述結論,$f(x)$關於$\theta $的偏導數爲:

$$ \frac{\partial f(x)}{\partial \theta} =
\begin{cases}
1, & \theta=\omega_0 \\
x_l, & \theta=\omega_l, l \in (1,2,...,n) \\
x_l\sum_{\substack{i=1\\i \ne l }}^n v_{im}x_i & \theta=v_{lm}
\end{cases} \tag{15}$$

1.3.4 時間複雜度

對於模型的計算時間複雜度,由(6)式,有:

$$ \begin{array}{l}
\sum\limits_{i = 1}^{n - 1} {\sum\limits_{j = i + 1}^n {(V_i^T{V_j})} } {x_i}{x_j}
\\= \frac{1}{2}\left( {\sum\limits_{i = 1}^n {\sum\limits_{j = 1}^n {(V_i^T{V_j})} } {x_i}{x_j} - \sum\limits_{i = 1}^n {(V_i^T{V_i})} {x_i}{x_i}} \right)
\\= \frac{1}{2}\left( {\sum\limits_{i = 1}^n {\sum\limits_{j = 1}^n {\sum\limits_{l = 1}^k {{v_{il}}} } } {v_{jl}}{x_i}{x_j} - \sum\limits_{i = 1}^n {\sum\limits_{l = 1}^k {v_{il}^2} } x_i^2} \right)
\\= \frac{1}{2}\sum\limits_{l = 1}^k {\left( {\sum\limits_{i = 1}^n {({v_{il}}{x_i})} \sum\limits_{j = 1}^n {({v_{jl}}{x_j})} - \sum\limits_{i = 1}^n {v_{il}^2} x_i^2} \right)}
\\= \frac{1}{2}\sum\limits_{l = 1}^k {\left( {{{\left( {\sum\limits_{i = 1}^n {({v_{il}}{x_i})} } \right)}^2} - \sum\limits_{i = 1}^n {v_{il}^2} x_i^2} \right)}
\end{array} \tag{10} $$

上述式子中的$\sum\limits_{i = 1}^n {({v_{il}}{x_i})}$只須要計算一次就好,所以,能夠看出上述模型的複雜度爲$O(kn)$。
也就是說咱們不要直接使用式(6)來計算預測結果,而應該使用式(10),這樣的計算效率更高。

對於訓練時間複雜度,由式(15)能夠獲得,

$$ x_l\sum_{\substack{i=1\\i \ne l }}^n v_{im}x_i = x_l\sum_{i=1}^n v_{im}x_i-v_{lm}x_l^2 \tag{16} $$

對於上式中的前半部分$\sum_{i=1}^n v_{im}x_i$,對於每一個樣本只須要計算一次,因此時間複雜度爲$O(n)$,對於k個隱變量的維度分別計算一次,則複雜度爲$O(kn)$。其它項的時間複雜度都小於這一項,所以,模型訓練的時間複雜度爲$O(kn)$。

具體地解釋,

(1)咱們首先計算$\sum_{i=1}^n v_{im}x_i$,時間複雜度爲n,這個值對於全部特徵對應的隱變量的某一個維度是相同的。咱們設這值爲C。
(2)計算每個特徵對應的$x_l\sum_{i=1}^n v_{im}x_i-v_{lm}x_l^2 =Cx_l-v_{lm}x_l^2$,因爲總共有n個特徵,所以時間複雜度爲n,至此,總的時間複雜度爲n+n。
(3)上述只是計算了隱變量的其中一個維度,咱們總共有k個維度,所以總的時間複雜度爲$k(n+n)=O(kn)k(n+n)=O(kn)$.

 

1.4 FFM算法

1.4.1 FFM基本原理

在FM模型中,每個特徵會對應一個隱變量,但在FFM模型中,認爲應該將特徵分爲多個field,每一個特徵對應每一個field分別有一個隱變量。

舉個例子,咱們的樣本有3種類型的字段:publisher, advertiser, gender,分別能夠表明媒體,廣告主或者是具體的商品,性別。其中publisher有5種數據,advertiser有10種數據,gender有男女2種,通過one-hot編碼之後,每一個樣本有17個特徵,其中只有3個特徵非空。

若是使用FM模型,則17個特徵,每一個特徵對應一個隱變量。
若是使用FFM模型,則17個特徵,每一個特徵對應3個隱變量,即每一個類型對應一個隱變量,具體而言,就是對應publisher, advertiser, gender三個field各有一個隱變量。

1.4.2 模型及目標函數

根據上面的描述,能夠得出FFM的模型爲:

$$ f(x) = \omega_0+\sum_{i=1}^n\omega_ix_i+\sum_{j1=1}^{n-1}\sum_{j2=i+1}^n(V_{j1,f2}^TV_{j2,f1})x_{j1}x_{j2} \tag{17}$$

其中$j1, j2$表示特徵的索引。咱們假設$j1$特徵屬於$f1$這個field,$j2$特徵屬於$f2$這個field,則$V_{j1,f2}$表示$j1$這個特徵對應$f2$($j2$所屬的field)的隱變量,同時$V_{j2,f1}$表示$j2$這個特徵對應$f1$($j1$所屬的field)的隱變量。

事實上,在大多數狀況下,FFM模型只保留了二次項,即:

$$ \phi(V,x) = \sum_{j1=1}^{n-1}\sum_{j2=i+1}^n(V_{j1,f2}^TV_{j2,f1})x_{j1}x_{j2} \tag{18} $$

根據邏輯迴歸的損失函數及分析,能夠得出FFM的最優化問題爲:

$$ \min \frac{\lambda}{2}||V||_2^2+\sum_{i=1}^{m}\log(1+exp(-y_i\phi(V,x))) \tag{19} $$

上面加號的前面部分使用了L2範式,後面部分是邏輯迴歸的損失函數。m表示樣本的數量,yiyi表示訓練樣本的真實值(如是否點擊的-1/1),$\phi(V,x)$表示使用當前的V代入式(18)計算獲得的值。

注意,以上的損失函數適用於樣本分佈爲{-1,1}的狀況。

1.4.3 完整算法流程

與FTRL同樣,FFM也使用了累積梯度做爲自適應學習率的一部分,即:

$$ V_{j1,f2} = V_{j1,f2} - \frac{\eta}{\sqrt{1+\sum_t(g_{v_{j1,f2}}^t)^2}}g_{v_{j1,f2}} \tag{20} $$

其中$g_{v_{j1,f2}}$表示對於$V_{v_{j1,f2}}$這個變量的梯度向量,由於$V_{v_{j1,f2}}$是一個向量,所以$g_{v_{j1,f2}}$也是一個向量,尺寸爲隱變量的維度大小,即k。
而$\sum_t(g_{v_{j1,f2}}^t)^2$表示從第一個樣本到當前樣本一直以來的累積梯度平方和。

$$ (V_{j1,f2})_d=(V_{j1,f2})_{d-1}-\frac{\eta}{\sqrt{(G_{j1,f2})_d}} \cdot (g_{j1,f2})_d\\
(V_{j2,f1})_d=(V_{j2,f1})_{d-1}-\frac{\eta}{\sqrt{(G_{j2,f1})_d}} \cdot (g_{j2,f1})_d \tag{21} $$

其中$G$爲累積梯度平方和:

$$ (G_{j1,f2})_d=(G_{j1,f2})_{d-1}+(g_{j1,f2})_d^2 \\
(G_{j2,f1})_d=(G_{j2,f1})_{d-1}+(g_{j2,f1})_d^2 \tag{22} $$

$g$爲梯度,好比$g_{ji,f2}$爲$j1$這個特徵對應$f2$這個field的梯度向量:

$$ g_{ji,f2}=\lambda \cdot V_{ji,f2} + \kappa \cdot V_{j2,f1}\\
g_{j2,f1}=\lambda \cdot V_{j2,f1} + \kappa \cdot V_{j1,f2} \tag{23} $$

其中$\kappa$爲:

$$ \kappa = \frac{{\partial \log (1 + exp( - {y_i}\phi (V,x)))}}{{\partial \phi (V,x)}} = \frac{{ - y}}{{1 + \exp (y\phi (V,x))}} \tag{24} $$

$g$與$V$都是$k$維的向量,在python中能夠做爲一個向量計算,在java/c++等須要經過一個循環進行計算。

詳細推導(23)式以下:
(1)在SGD中,式(19)能夠轉化爲:

$$ \min \frac{\lambda}{2}||V||_2^2+\log(1+exp(-y_i\phi(V,x))) \tag{25} $$

(2)上式對$V_{j1,f2}$ 求偏導,可得:

$$ \begin{array}{l}
\frac{{\partial {\rm{\{ }}\frac{\lambda }{2}||V||_2^2 + \log (1 + exp( - {y_i}\phi (V,x))){\rm{\} }}}}{{\partial {V_{j1,f2}}}}\\
= \lambda \cdot {V_{j1,f2}} + \frac{{\partial \log (1 + exp( - {y_i}\phi (V,x)))}}{{\partial {V_{j1,f2}}}}\\
= \lambda \cdot {V_{j1,f2}} + \frac{{\partial \log (1 + exp( - {y_i}\phi (V,x)))}}{{\partial \phi }} \cdot \frac{{\partial \phi }}{{{V_{j1,f2}}}}\\
= \lambda \cdot {V_{j1,f2}} + \frac{{ - y}}{{1 + \exp (y\phi (V,x))}} \cdot {V_{j2,f1}}
\end{array} \tag{24} $$

 

1.4.4 時間複雜度

對於計算時間複雜度,因爲式(18)沒法作相似於式(10)的簡化,所以FFM的計算時間複雜度爲$O(kn^2)$。

對於訓練時間複雜度,因爲訓練時,須要先根據式(18)計算$\phi$,複雜度爲$O(kn^2)$,計算獲得$\phi$後,還須要按照式(22)計算1次,按照式(21)計算$2k$次,按照式(23)計算$2k$次,按照式(24)計算$2k$次,也就是說,總的訓練時間複雜度爲:

$$O(kn^2) + 1 + 2k + 2k + 2k = O(kn^2) $$

所以,訓練時間複雜度爲$O(kn^2)$。

1.4.5 模型優化

 特徵歸一化、樣本歸一化。

 

2. Recurrent Entity Network原理介紹

 Recurrent Entity Network簡稱EntNet,最初在論文TRACKING THE WORLD STATE WITH RECURRENT ENTITY NETWORKS中提出,文中給出了lua+torch的代碼地址。做者包括Facebook AI研究院的Mikael Henaff, Jason Weston(MemNN和MemN2N的做者)以及Yann LeCun.

Entity Network模型共分爲Input Encoder、Dynamic Memory和Output Model三個部分。以下圖的架構圖所示:

 

EntNet使用了動態長期記憶,所以能夠用在語言理解任務,QA等;
各個memory cell是獨立的,所以EntNet能夠看作是一系列共享權值的gated RNN。

輸入爲:

$$ {s_t} = \sum\limits_i {{f_i}}  \odot {e_i} $$

輸入是一個固定長度的向量,用來表示一個sentence。$f_i$是須要學習的multiplicative mask, 使用這個mask的目的在於加入位置信息。 $e_i$是單詞的embedding表示。

Dynamic memory爲:

$$ \begin{array}{l}
{g_j} \leftarrow \sigma \left( {s_t^T{h_j} + s_t^T{w_j}} \right)\\
\widetilde {{h_j}} \leftarrow \phi \left( {U{h_j} + V{w_j} + W{s_t}} \right)\\
{h_j} \leftarrow {h_j} + {g_j} \odot \widetilde {{h_j}}\\
{h_j} \leftarrow \frac{{{h_j}}}{{\left\| {{h_j}} \right\|}}
\end{array} $$

輸出爲:

$$ \begin{array}{l}
{p_j} = soft\max \left( {{q^T}{h_j}} \right)\\
u = \sum\limits_j {{p_j}} {h_j}\\
y = R\phi \left( {q + Hu} \right)
\end{array} $$

 

3. FFM + Recurrent Entity Network

 首先要將用於FFM的特徵作處理,生成"feature index"和"feature value",用於FFM作特徵交叉。

feature_index是把全部特徵進行了標序,feature1,feature2......featurem,分別對應0,1,2,3,...m,可是,請注意分類變量須要拆分

就是說若是有性別:男|女|未知,三個選項。須要構造feature男,feature女,feature未知三個變量,而連續變量就不須要這樣。

feature_value就是特徵的值,連續變量按真實值填寫,分類變量所有填寫1。

更加形象的以下:

FFM模塊代碼實現以下(Tensorflow):

    def ffm_module(self):
        # initial weights
        self.weights = dict()
        # feature_size * K
        self.weights["feature_embeddings"] = tf.Variable(
            tf.random_normal([self.feature_size, self.embedding_size], 0.0, 0.01), name="feature_embeddings")
        # feature_size * 1
        self.weights["feature_bias"] = tf.Variable(
            tf.random_uniform([self.feature_size, 1], 0.0, 1.0), name="feature_bias")
        input_size = self.field_size * self.embedding_size
        glorot = np.sqrt(2.0 / (input_size + 1))
        self.weights["use_fm_concat_weights"] = tf.Variable(
                        np.random.normal(loc=0, scale=glorot, size=(self.field_size+self.embedding_size, self.num_classes)),
                        dtype=np.float32)

        # model
        self.embeddings = tf.nn.embedding_lookup(self.weights["feature_embeddings"], self.feat_index)  # None * F * K
        feat_value = tf.reshape(self.feat_value, shape=[-1, self.field_size, 1])
        self.embeddings = tf.multiply(self.embeddings, feat_value)

        # ---------- first order term ----------
        self.y_first_order = tf.nn.embedding_lookup(self.weights["feature_bias"], self.feat_index)  # None * F * 1
        self.y_first_order = tf.reduce_sum(tf.multiply(self.y_first_order, feat_value), 2)  # None * F
        self.y_first_order = tf.nn.dropout(self.y_first_order, self.dropout_keep_fm[0])  # None * F

        # ---------- second order term ---------------
        # sum_square part
        self.summed_features_emb = tf.reduce_sum(self.embeddings, 1)  # None * K
        self.summed_features_emb_square = tf.square(self.summed_features_emb)  # None * K

        # square_sum part
        self.squared_features_emb = tf.square(self.embeddings)
        self.squared_sum_features_emb = tf.reduce_sum(self.squared_features_emb, 1)  # None * K

        # second order
        self.y_second_order = 0.5 * tf.subtract(self.summed_features_emb_square,
                                                self.squared_sum_features_emb)  # None * K
        self.y_second_order = tf.nn.dropout(self.y_second_order, self.dropout_keep_fm[1])  # None * K

        self.ffm_y = tf.concat([self.y_first_order, self.y_second_order], axis=1)
        self.ffm_y = tf.reshape(self.ffm_y, shape=[self.batch_size, self.field_size+self.embedding_size])
        self.ffm_y = tf.nn.dropout(self.activation(self.ffm_y), self.dropout_keep_fm[2])
        self.ffm_y = tf.matmul(self.ffm_y, self.weights["use_fm_concat_weights"])

 

EntityNet的核心代碼:

    def rnn_story(self):
        """
        run rnn for story to get last hidden state
        input is:  story:                 [batch_size,story_length,embed_size]
        :return:   last hidden state.     [batch_size,embed_size]
        """
        # 1.split input to get lists.
        input_split=tf.split(self.story_embedding,self.story_length,axis=1) #a list.length is:story_length.each element is:[batch_size,1,embed_size]
        input_list=[tf.squeeze(x,axis=1) for x in input_split]           #a list.length is:story_length.each element is:[batch_size,embed_size]
        # 2.init keys(w_all) and values(h_all) of memory
        h_all=tf.get_variable("hidden_states",shape=[self.block_size,self.dimension],initializer=self.initializer)# [block_size,hidden_size]
        w_all=tf.get_variable("keys",          shape=[self.block_size,self.dimension],initializer=self.initializer)# [block_size,hidden_size]
        # 3.expand keys and values to prepare operation of rnn
        w_all_expand=tf.tile(tf.expand_dims(w_all,axis=0),[self.batch_size,1,1]) #[batch_size,block_size,hidden_size]
        h_all_expand=tf.tile(tf.expand_dims(h_all,axis=0),[self.batch_size,1,1]) #[batch_size,block_size,hidden_size]
        # 4. run rnn using input with cell.
        for i,input in enumerate(input_list):
            h_all_expand=self.cell(input,h_all_expand,w_all_expand,i) #w_all:[batch_size,block_size,hidden_size]; h_all:[batch_size,block_size,hidden_size]
        return h_all_expand #[batch_size,block_size,hidden_size]

    def cell(self,s_t,h_all,w_all,i):
        """
        parallel implementation of single time step for compute of input with memory
        :param s_t:   [batch_size,hidden_size].vector representation of current input(is a sentence).notice:hidden_size=embedding_size
        :param w_all: [batch_size,block_size,hidden_size]
        :param h_all: [batch_size,block_size,hidden_size]
        :return: new hidden state: [batch_size,block_size,hidden_size]
        """
        # 1.gate
        s_t_expand=tf.expand_dims(s_t, axis=1)       #[batch_size,1,hidden_size]
        g=tf.nn.sigmoid(tf.multiply(s_t_expand,h_all)+tf.multiply(s_t_expand,w_all))#shape:[batch_size,block_size,hidden_size]

        # 2.candidate hidden state
        #below' shape:[batch_size*block_size,hidden_size]
        h_candidate_part1=tf.matmul(tf.reshape(h_all,shape=(-1,self.dimension)), self.U) + tf.matmul(tf.reshape(w_all,shape=(-1,self.dimension)), self.V)+self.h_bias
        # print("======>h_candidate_part1:",h_candidate_part1) #(160, 100)
        h_candidate_part1=tf.reshape(h_candidate_part1,shape=(self.batch_size,self.block_size,self.dimension)) #[batch_size,block_size,hidden_size]
        h_candidate_part2=tf.expand_dims(tf.matmul(s_t,self.W)+self.h2_bias,axis=1)              #shape:[batch_size,1,hidden_size]
        h_candidate=self.activation(h_candidate_part1+h_candidate_part2,scope="h_candidate"+str(i))   #shape:[batch_size,block_size,hidden_size]

        # 3.update hidden state
        h_all=h_all+tf.multiply(g,h_candidate) #shape:[batch_size,block_size,hidden_size]

        # 4.normalized hidden state
        h_all=tf.nn.l2_normalize(h_all,-1) #shape:[batch_size,block_size,hidden_size]
        return h_all  #shape:[batch_size,block_size,hidden_size]

 

 

將二者結合起來:

    def output_module(self):
        """
        1.use attention mechanism between query and hidden states, to get weighted sum of hidden state. 2.non-linearity of query and hidden state to get label.
        input: query_embedding:[batch_size,embed_size], hidden state:[batch_size,block_size,hidden_size] of memory
        :return:y: predicted label.[]
        """
        # 1.use attention mechanism between query and hidden states, to get weighted sum of hidden state.
        # 1.1 get possibility distribution (of similiarity)
        p = tf.nn.softmax(tf.multiply(tf.expand_dims(self.query_embedding, axis=1), self.hidden_state)) #shape:[batch_size,block_size,hidden_size]<---query_embedding_expand:[batch_size,1,hidden_size]; hidden_state:[batch_size,block_size,hidden_size]
        # 1.2 get weighted sum of hidden state
        u = tf.reduce_sum(tf.multiply(p, self.hidden_state), axis=1)  # shape:[batch_size,hidden_size]<----------([batch_size,block_size,hidden_size],[batch_size,block_size,hidden_size])

        # 2.non-linearity of query and hidden state to get label
        H_u_matmul = tf.matmul(u, self.H) + self.h_u_bias  # shape:[batch_size,hidden_size]<----([batch_size,hidden_size],[hidden_size,hidden_size])

        activation = self.activation(self.query_embedding + H_u_matmul, scope="query_add_hidden")           #shape:[batch_size,hidden_size]
        activation = tf.nn.dropout(activation, keep_prob=self.dropout_keep_prob) #shape:[batch_size,hidden_size]
        y = tf.matmul(activation, self.R) + self.y_bias  # shape:[batch_size,vocab_size]<-----([batch_size,hidden_size],[hidden_size,vocab_size])

        # FFM layer
        if self.use_fm:
            self.ffm_module()
            y = tf.add(y, self.ffm_y)
        return y  # shape:[batch_size,vocab_size]

 

 

詳細demo參考:playground_ffm_entitynet.zip

 

參考文章:

1. https://www.jianshu.com/p/71d819005fed

2. https://blog.csdn.net/Irving_zhang/article/details/79204426

3. https://blog.csdn.net/jediael_lu/article/details/77772565

相關文章
相關標籤/搜索