一文详解图神经网络(sa)

5.2 《Semi-Supervised Classification with Graph Convolutional Networks》

这篇论文受到谱图卷积的局部一阶近似可以用于对局部图结构与节点的特征进行编码从而确定卷积网络结构的启发,提出了一种可扩展的图卷积的实现方法,可用于具有图结构数据的半监督学习

5.2.1 GCN定义

按照图傅里叶变换的性质,可以得到如下图卷积的定义:
(\boldsymbol{f} * \boldsymbol{h})_{\mathcal{G}}=\boldsymbol{\Phi} \operatorname{diag}\left[\hat{h}\left(\lambda_{1}\right), \ldots, \hat{h}\left(\lambda_{n}\right)\right] \mathbf{\Phi}^{T} \boldsymbol{f}\tag{46}
其中:

  • 对于图\boldsymbol{f}的傅里叶变换为的傅里叶变换为的傅里叶变换为\boldsymbol{\hat{f}}=\mathbf{\Phi}^{T} \boldsymbol{f}
  • 对于卷积核的图傅里叶变换:\hat{h}=\left(\hat{h}_{1}, \ldots, \hat{h}_{n}\right)其中\hat{h}_{k}=\left\langle h, \phi_{k}\right\rangle, k=1,2 \ldots,按照矩阵形式就是\hat{\boldsymbol{h}}=\mathbf{\Phi}^{T} \boldsymbol{h}
  • 对两者的傅里叶变换向量\hat{f} \in \mathbb{R}^{N \times 1}\hat{h} \in \mathbb{R}^{N \times 1}element-wise乘积等价于将{h}组织成对角矩阵,即\operatorname{diag}\left[\hat{h}\left(\lambda_{k}\right)\right] \in \mathbb{R}^{N \times N},然后再求\operatorname{diag}\left[\hat{h}\left(\lambda_{k}\right)\right]\boldsymbol{f}矩阵乘法
  • 求上述结果的傅里叶逆变换,即左乘\mathbf{\Phi}

深度学习中的卷积就是要设计trainable的卷积核,从上式可以看出,就是要设计\operatorname{diag}\left[\hat{h}\left(\lambda_{1}\right), \ldots, \hat{h}\left(\lambda_{n}\right)\right],由此,可以直接将其变为卷积核\operatorname{diag}\left[\theta_{1}, \ldots, \theta_{n}\right]而不需要再将卷积核进行傅里叶变换,由此,相当于直接将变换后的参量进行学习

第一代GCN

\boldsymbol{y}_{\text {output}}=\sigma\left(\mathbf{\Phi} \boldsymbol{g}_{\theta} \mathbf{\Phi}^{T} \boldsymbol{x}\right)=\sigma\left(\boldsymbol{\Phi} \operatorname{diag}\left[\theta_{1}, \ldots, \theta_{n}\right] \mathbf{\Phi}^{T} \boldsymbol{x}\right)\tag{47}

其中,\boldsymbol{x}就是graph上对应每个节点的feature构成的向量,x=\left(x_{1}, x_{2}, \ldots, x_{n}\right),这里暂时对每个节点都使用标量,然后经过激活之后,得到输出\boldsymbol{y}_{\text {output}},之后传入下一层

一些缺点:

  • 需要对拉普拉斯矩阵进行谱分解来求\mathbf{\Phi},在graph很大的时候复杂度很高。另外,还需要计算矩阵乘积,复杂度为O(n^2)
  • 卷积核参数为n,当graph很大的时候,n会很大
  • 卷积核的spatial localization不好
第二代GCN

图傅里叶变换是关于特征值(相当于普通傅里叶变换的频率)的函数,也就是F\left(\lambda_{1}\right), \ldots, F\left(\lambda_{n}\right),即F(\mathbf{\Lambda}),因此,将卷积核\boldsymbol{g}_{\theta}写成\boldsymbol{g}_{\theta}(\Lambda),然后,将\boldsymbol{g}_{\theta}(\Lambda)定义为如下k阶多项式:
g_{\theta^{\prime}}(\mathbf{\Lambda}) \approx \sum_{k=0}^{K} \theta_{k}^{\prime} \mathbf{\Lambda}^{k}\tag{48}
将卷积公式带入,可以得到:
g_{\theta^{\prime}}*x≈\Phi\sum_{k=0}^K\theta^{\prime}_k\mathbf{\Lambda}^{k}\Phi^Tx\\ =\sum_{k=0}^K\theta^{\prime}_k(\Phi\mathbf{\Lambda}^{k}\Phi^T)x\\ =\sum_{k=0}^K\theta^{\prime}_k(\Phi\mathbf{\Lambda}\Phi^T)^{k}x\\ =\sum_{k=0}^K\theta^{\prime}_kL^{k}x\tag{49}
这一代的GCN不需要做特征分解,可以直接对Laplacian矩阵做变换,通过事先将Laplacian矩阵求出来,以及\boldsymbol{L}^{k}求出来,前向传播的时候,可以直接使用,复杂度为O(Kn^2)

对于每一次Laplacian矩阵\boldsymbol{L}\mathbf{x}相乘,对于节点n,相当于从邻居节点ne[n]传递一次信息给节点n,由于连续乘以了k次Laplacian矩阵,那么相当于n节点的k-hop之内的节点能够传递信息给n,因此,实际上只利用了节点的K-Localized信息

另外,可以使用切比雪夫展开式来近似\boldsymbol{L}^{k},任何k次多项式都可以使用切比雪夫展开式来近似,由此,引入切比雪夫多项式的K阶截断获得\boldsymbol{L}^{k}近似,从而获得对g_{\theta}(\mathbf{\Lambda})的近似
g_{\theta^{\prime}}(\mathbf{\Lambda}) \approx \sum_{k=0}^{K} \theta_{k}^{\prime} T_{k}(\tilde{\mathbf{\Lambda}})\tag{50}
其中,\tilde{\mathbf{\Lambda}}=\frac{2}{\lambda_{\max }} \mathbf{\Lambda}-\boldsymbol{I}_{n}\boldsymbol{\theta}^{\prime} \in \mathbb{R}^{K}为切比雪夫向量,\theta_{k}^{\prime}为第k个分量,切比雪夫多项式T_{k}(x)使用递归的方式进行定义:T_{k}(x)=2 x T_{k-1}(x)-T_{k-2}(x),其中,T_{0}(x)=1, T_{1}(x)=x,此时,带入到卷积公式:
g_{\theta^{\prime}}*x≈\Phi\sum_{k=0}^K\theta^{\prime}_kT_k(\tilde{\mathbf{\Lambda}})\Phi^Tx\\ ≈\sum_{k=0}^K\theta^{\prime}_k\Big(\Phi T_k(\tilde{\mathbf{\Lambda}})\Phi^T\Big)x\\ =\sum_{k=0}^K\theta^{\prime}_k T_k(\tilde{\boldsymbol{L}}){x}\tag{51}
其中,\tilde{\boldsymbol{L}}=\frac{2}{\lambda_{\max }} \boldsymbol{L}-\boldsymbol{I}_{n},因此,可以得到输出为:
\boldsymbol{y}_{\text {output}}=\sigma\left(\sum_{k=0}^{K} \theta_{k}^{\prime} T_{k}(\tilde{\boldsymbol{L}}) \boldsymbol{x}\right)\tag{52}

第三代GCN

直接取切比雪夫多项式中K=1,此时模型是1阶近似,将K=1\lambda_{\max }=2带入可以得到:
g_{\theta^{\prime}} * x≈\theta_{0}^{\prime}x+\theta_{1}^{\prime}(\boldsymbol{L}-\boldsymbol{I}_{n})x\\ =\theta_{0}^{\prime}x+\theta_{1}^{\prime}(\boldsymbol{L}-\boldsymbol{I}_{n})x\\ =\theta_{0}^{\prime}x+\theta_{1}^{\prime})(\boldsymbol{D}^{-1 / 2} \boldsymbol{W} \boldsymbol{D}^{-1 / 2})x\tag{53}
其中,归一化拉普拉斯矩阵\boldsymbol{L}=\boldsymbol{D}^{-1 / 2}(\boldsymbol{D}-\boldsymbol{W}) \boldsymbol{D}^{-1 / 2}=\boldsymbol{I}_{n}-\boldsymbol{D}^{-1 / 2} \boldsymbol{W} \boldsymbol{D}^{-1 / 2}。为了进一步简化,令\theta_{0}^{\prime}=-\theta_{1}^{\prime},此时只含有一个参数\theta
g_{\theta^{\prime}} * x=\theta\left(I_{n}+D^{-1 / 2} W D^{-1 / 2}\right)\tag{54}
由于\boldsymbol{I}_{n}+\boldsymbol{D}^{-1 / 2} \boldsymbol{W} \boldsymbol{D}^{-1 / 2}的谱半径[ 0 , 2 ]太大,使用归一化的trick:
\boldsymbol{I}_{n}+\boldsymbol{D}^{-1 / 2} \boldsymbol{W} \boldsymbol{D}^{-1 / 2} \rightarrow \tilde{\boldsymbol{D}}^{-1 / 2} \tilde{\boldsymbol{W}} \tilde{\boldsymbol{D}}^{-1 / 2}\tag{55}
其中,\tilde{\boldsymbol{W}}=\boldsymbol{W}+\boldsymbol{I}_{n}\tilde{D}_{i j}=\Sigma_{j} \tilde{W}_{i j}

由此,带入卷积公式
\underbrace{g_{\theta^{\prime}} * x}_{\mathbb{R}^{n \times 1}}=\theta\left(\underbrace{\tilde{D}^{-1 / 2} \tilde{W} \tilde{D}^{-1 / 2}}_{\mathbb{R}^{n \times n}}\right) \underbrace{x}_{\mathbb{R}^{n \times 1}}\tag{56}
如果推广到多通道,相当于每一个节点的信息是向量
x \in \mathbb{R}^{N \times 1} \rightarrow X \in \mathbb{R}^{N \times C}
其中,N是节点数量,C是通道数,或者称作表示节点的信息维度数。\mathbf{X}是节点的特征矩阵。相应的卷积核参数变化:
\theta \in \mathbb{R} \rightarrow \Theta \in \mathbb{R}^{C \times F}\tag{57}
其中,F为卷积核数量,那么卷积结果写成矩阵形式为:
\underbrace{Z}_{\mathbb{R}^{N \times F}}=\underbrace{\tilde{D}^{-1 / 2} \tilde{W} \tilde{D}^{-1 / 2}}_{\mathbb{R}^{N \times N}} \underbrace{X}_{\mathbb{R}^{N \times C}} \underbrace{\mathbf{\Theta}}_{\mathbb{R}^{C \times F}}\tag{58}
上述操作可以叠加多层,对上述输出激活一下,就可以作为下一层节点的特征矩阵

一些特点:

  • K=1,相当于直接取邻域信息,类似于3\times{3}的卷积核
  • 由于卷积核宽度减小,可以通过增加卷积层数来扩大感受野,从而增强网络的表达能力
  • 增加了参数约束,比如\lambda_{\max } \approx 2,引入归一化操作
5.2.2 论文模型

论文采用两层的GCN,用来在graph上进行半监督的节点分类任务,邻接矩阵为A,首先计算出\hat{A}=\tilde{D}^{-\frac{1}{2}} \tilde{A} \tilde{D}^{-\frac{1}{2}},由此,前向网络模型形式如下:
Z=f(X, A)=\operatorname{softmax}\left(\hat{A} \operatorname{ReLU}\left(\hat{A} X W^{(0)}\right) W^{(1)}\right)\tag{59}
其中,W^{(0)} \in \mathbb{R}^{C \times H}为输入层到隐藏层的权重矩阵,隐藏层的特征维度为HW^{(1)} \in \mathbb{R}^{H \times F}为隐藏层到输出层的权重矩阵,softmax激活函数定义为\operatorname{softmax}\left(x_{i}\right)=\frac{1}{\mathcal{Z}} \exp \left(x_{i}\right)\mathcal{Z}=\sum_{i} \exp \left(x_{i}\right),相当于对每一列做softmax,由此得到交叉熵损失函数为:
\mathcal{L}=-\sum_{l \in \mathcal{Y}_{L}} \sum_{f=1}^{F} Y_{l f} \ln Z_{l f}\tag{60}
其中,\mathcal{Y}_{L}为带有标签的节点集合

5.2.3 代码实现
import torch_geometric.nn as gnn
class GCN(nn.Module):
    def __init__(self, config, in_channels, out_channels):
        '''
            in_channels : num of node features
            out_channels: num of class
        '''
        super().__init__()
        self.config = config
        self.hidden_dim = config.hidden_dim
        self.dropout_rate = config.dropout_rate
        self.conv1 = gnn.GCNConv(in_channels, self.hidden_dim, improved = False, cached=True, bias=True, normalize=True)
        self.conv2 = gnn.GCNConv(self.hidden_dim, out_channels, improved = False, cached=True, bias=True, normalize=True)
    def forward(self, data):
        x, edge_index = data.x, data.edge_index
        x = self.conv1(x, edge_index)
        x = F.relu(x)
        #x = F.dropout(x, p=self.dropout_rate) # If no drop out, accuracy 0.75 --> 0.80
        x = self.conv2(x, edge_index)
        #x = F.dropout(x, p=self.dropout_rate) # there are two dropout.. But performance bad.
        return x  

5.3 《Diffusion-Convolutional Neural Networks》

该模型对每一个节点(或边、或图)采用H个hop的矩阵进行表示,每一个hop都表示该邻近范围的邻近信息,由此,对于局部信息的获取效果比较好,得到的节点的representation的表示能力很强

5.3.1 任务定义
  • 一个graph数据集\mathcal{G}=\left\{G_{t} | t \in 1 \ldots T\right\}
  • graph定义为G_{t}=\left(V_{t}, E_{t}\right),其中,V_t为节点集合,E_t为边集合
  • 所有节点的特征矩阵定义为X_t,大小为N_t\times{F},其中,N_t为图G_t的节点个数,F为节点特征维度
  • 边信息E_t定义为N_t\times{}N_t的邻接矩阵A_t,由此可以计算出节点度(degree)归一化的转移概率矩阵P_t,表示从i节点转移到j节点的概率

模型的目标为预测Y,也就是预测每一个图的节点标签,或者边的标签,或者每一个图的标签,在每一种情况中,模型输入部分带有标签的数据集合,然后预测剩下的数据的标签。DCNN模型输入图\mathcal{G},返回硬分类预测值Y或者条件分布概率\mathbb{P}(Y|X)。该模型将每一个预测的目标对象(节点、边或图)转化为一个diffusion-convolutional representation,大小为H\times{}FH表示扩散的hops,表示为Z_t

  • 对于节点分类任务,表示为Z_t为大小为N_t\times{H}\times{F}的矩阵
  • 对于图分类任务,张量Z_t为大小为H\times{F}的矩阵
  • 对于边分类任务,张量Z_t为大小为M_t\times{H}\times{F}的矩阵
5.3.2 论文模型
  1. 对于节点分类任务,假设P_t^*P_t的power series,大小为N_t\times{H}\times{N_t},那么对于图t的节点i,第j个hop,第k维特征值Z_{tijk}计算公式为:
    Z_{t i j k}=f\left(W_{j k}^{c} \cdot \sum_{l=1}^{N_{t}} P_{t i j l}^{*} X_{t l k}\right)\tag{61}
    使用矩阵表示为:
    Z_{t}=f\left(W^{c} \odot P_{t}^{*} X_{t}\right)\tag{62}
    其中\odot表示element-wise multiplication,由于模型只考虑H跳的参数,即参数量为O(H\times{F})使得diffusion-convolutional representation不受输入大小的限制

    在计算出Z之后,过一层全连接得到输出Y,使用\hat{Y}表示硬分类预测结果,使用\mathbb{P}(Y|X)表示预测概率,计算方式如下:
    \hat{Y}=\arg \max \left(f\left(W^{d} \odot Z\right)\right)\tag{63}\\ \mathbb{P}(Y | X)=\operatorname{softmax}\left(f\left(W^{d} \odot Z\right)\right)

  2. 对于图分类任务,直接采用所有节点表示的均值作为graph的representation
    Z_{t}=f\left(W^{c} \odot 1_{N_{t}}^{T} P_{t}^{*} X_{t} / N_{t}\right)\tag{64}
    其中,1_{N_t}是全为1的N_t\times{1}的向量

  3. 对于边分类任务,通过将每一条边转化为一个节点来进行训练和预测,这个节点与原来的边对应的首尾节点相连,转化后的图的邻接矩阵A_t'可以直接从原来的邻接矩阵A_t增加一个incidence matrix得到:
    A_{t}^{\prime}= \begin{matrix} A_t & B_t^T\\ B_t & 0\\ \end{matrix} \tag{65}
    之后,使用A_t'来计算P_t',并用来替换P_t来进行分类,对于模型训练,使用梯度下降法,并采用early-stop方式得到最终模型

5.3.3 代码实现
import lasagne
import lasagne.layers
import theano
import theano.tensor as T
import numpy as np
class DCNNLayer(lasagne.layers.MergeLayer):
    """A node-level DCNN layer.
    This class contains the (symbolic) Lasagne internals for a node-level DCNN layer.  This class should
    be used in conjunction with a user-facing model class.
    """
    def __init__(self, incomings, parameters, layer_num,
                 W=lasagne.init.Normal(0.01),
                 num_features=None,
                 **kwargs):
        super(DCNNLayer, self).__init__(incomings, **kwargs)
        self.parameters = parameters
        if num_features is None:
            self.num_features = self.parameters.num_features
        else:
            self.num_features = num_features
        self.W = T.addbroadcast(
            self.add_param(W,
                           (1, parameters.num_hops + 1, self.num_features), name='DCNN_W_%d' % layer_num), 0)
        self.nonlinearity = params.nonlinearity_map[self.parameters.dcnn_nonlinearity]
    def get_output_for(self, inputs, **kwargs):
        """Compute diffusion convolutional activation of inputs."""
        Apow = inputs[0]
        X = inputs[1]
        Apow_dot_X = T.dot(Apow, X)
        Apow_dot_X_times_W = Apow_dot_X * self.W
        out = self.nonlinearity(Apow_dot_X_times_W)
        return out
    def get_output_shape_for(self, input_shapes):
        """Return the layer output shape."""
        shape = (None, self.parameters.num_hops + 1, self.num_features)
        return shape

5.4 《Improved Semantic Representations From Tree-Structured Long Short-Term Memory Networks》

将序列型的LSTM模型扩展到树型的LSTM模型,简称Tree-LSTM,并根据孩子节点是否有序,论文提出了两个模型变体,Child-Sum Tree-LSTM模型和N-ary Tree-LSTM模型。和序列型的LSTM模型的主要不同点在于,序列型的LSTM从前一时刻获取隐藏状态h_t,而树型的LSTM从其所有的孩子节点获取隐藏状态

5.4.1 论文模型

Tree-LSTM模型对于每一个孩子节点都会产生一个遗忘门¥f_{jk}¥,这个使得模型能够从所有的孩子节点选择性地获取信息和结合信息

  1. Child-Sum Tree-LSTMs
    该模型的更新方程如下:
    \widetilde{h_j}=\sum_{k\in C(j)}h_k\\ i_j=\sigma({W}^{(i)}x_j+{U}^{(i)}\widetilde{h_j}+b^{(i)})\\ f_{ik}=\sigma({W}^{(f)}x_j+{U}^{(f)}{h_k}+b^{(f)})\\ o_{j}=\sigma({W}^{(o)}x_j+{U}^{(o)}\widetilde{h_j}+b^{(o)})\\ u_{j}=tanh({W}^{(u)}x_j+{U}^{(u)}\widetilde{h_j}+b^{(u)})\\ c_j=i_j⊙u_j+\sum_{k\in C(j)}f_{ij}⊙c_k\\ h_j=o_j⊙tanh(c_j)\tag{66}
    其中,C(j)表示j节点的邻居节点的个数,h_k表示节点k的隐藏状态,i_j表示节点j输入门f_{jk}表示节点j的邻居节点k遗忘门o_j表示节点j输出门

    这里的关键点在于第三个公式的f_{jk},这个模型对节点j的每个邻居节点k都计算了对应的遗忘门向量,然后在第六行中计算c_j时对邻居节点的信息进行遗忘组合

    由于该模型是对所有的孩子节点求和,所以这个模型对于节点顺序不敏感的,适合于孩子节点无序的情况

  2. N-ary Tree-LSTMs
    假如一个树的最大分支数为N(即孩子节点最多为N个),而且孩子节点是有序的,对于节点j,对于该节点的第k个孩子节点的隐藏状态和记忆单元分别用h_{jk}c_{jk}表示,模型的方程如下:
    i_j=\sigma({W}^{(i)}x_j+\sum^N_{ℓ=1}{U}_ℓ^{(i)}{h_{jℓ}}+b^{(i)})\\ f_{ik}=\sigma({W}^{(f)}x_j+\sum^N_{ℓ=1}{U}_{kℓ}^{(f)}{h_{jℓ}}+b^{(f)})\\ o_{j}=\sigma({W}^{(o)}x_j+\sum^N_{ℓ=1}{U}_ℓ^{(o)}{h_{jℓ}}+b^{(o)})\\ u_{j}=tanh({W}^{(u)}x_j+\sum^N_{ℓ=1}{U}_ℓ^{(a)}{h_{jℓ}}+b^{(a)})\\ c_j=i_j⊙u_j+\sum^N_{ℓ=1}f_{jℓ}⊙c_{jℓ}\\ h_j=o_j⊙tanh(c_j)\tag{67}
    该模型为每个孩子节点都单独地设置了参数U_{l}

5.4.2 训练策略
  • 分类任务:

分类任务定义为在类别集\mathcal{Y}中预测出正确的标签\hat{y},对于每一个节点j,使用一个softmax分类器来预测节点标签\hat{y}_j,分类器取每个节点的隐藏状态h_j作为输入:
\hat p_\theta(y|\{x\}_j)=softmax(W^{(s)}h_j+b^{(s)})\\ \hat y_j=\underset{y}{\operatorname{argmax}} \hat p_\theta(y|\{x\}_j)\tag{68}
损失函数使用negative log-likelihood
J(\theta)=-\frac{1}{m} \sum_{k=1}^{m} \log \hat{p}_{\theta}\left(y^{(k)} |\{x\}^{(k)}\right)+\frac{\lambda}{2}\|\theta\|_{2}^{2}\tag{69}

其中,m是带有标签的节点数量,\lambdaL2是正则化超参

  • 语义相关性任务:

该任务给定一个句子对(sentence pair),模型需要预测出一个范围在[ 1 , K ]之间的实数值,这个值越高,表示相似度越高。首先对每一个句子产生一个representation,两个句子的表示分别用h_Lh_R

表示,得到这两个representation之后,从distance和angle两个方面考虑,使用神经网络来得到(h_L,h_R)相似度:
h_× = h_L⊙h_R\\ h_+ = ∣h_L−h_R∣\\ h_s = σ(W^{(×)}h_×+W^{(+)}h_+ +b^{(h)})\\ \hat p_θ=softmax⁡(W^{(p)}h_s+b^{(p)})\\ \hat y=r^T\hat p_θ\tag{70}
其中,r^{T}=\left[1,2…K\right],模型期望根据训练得到的参数\theta得到的结果:\hat{y}=r^{T} \hat{p}_{\theta} \approx y。由此,定义一个目标分布p
y= \begin{cases} y−⌊y⌋, \ \ \ \ \ \ \ \quad i=⌊y⌋+1\\ ⌊y⌋−y+1, \quad i=⌊y⌋\\ 0 \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \quad otherwise \end{cases} \tag{71}
其中,1\le{i}\le{K},损失函数为p\hat{p}_{\theta}之间的KL散度:
J(\theta)=\frac{1}{m} \sum_{k=1}^{m} \mathrm{KL}\left(p^{(k)} \| \hat{p}_{\theta}^{(k)}\right)+\frac{\lambda}{2}\|\theta\|_{2}^{2}\tag{72}

5.4.3 代码实现
class TreeLSTM(torch.nn.Module):
    '''PyTorch TreeLSTM model that implements efficient batching.
    '''
    def __init__(self, in_features, out_features):
        '''TreeLSTM class initializer
        Takes in int sizes of in_features and out_features and sets up model Linear network layers.
        '''
        super().__init__()
        self.in_features = in_features
        self.out_features = out_features
        # bias terms are only on the W layers for efficiency
        self.W_iou = torch.nn.Linear(self.in_features, 3 * self.out_features)
        self.U_iou = torch.nn.Linear(self.out_features, 3 * self.out_features, bias=False)
        # f terms are maintained seperate from the iou terms because they involve sums over child nodes
        # while the iou terms do not
        self.W_f = torch.nn.Linear(self.in_features, self.out_features)
        self.U_f = torch.nn.Linear(self.out_features, self.out_features, bias=False)
    def forward(self, features, node_order, adjacency_list, edge_order):
        '''Run TreeLSTM model on a tree data structure with node features
        Takes Tensors encoding node features, a tree node adjacency_list, and the order in which 
        the tree processing should proceed in node_order and edge_order.
        '''
        # Total number of nodes in every tree in the batch
        batch_size = node_order.shape[0]
        # Retrive device the model is currently loaded on to generate h, c, and h_sum result buffers
        device = next(self.parameters()).device
        # h and c states for every node in the batch
        h = torch.zeros(batch_size, self.out_features, device=device)
        c = torch.zeros(batch_size, self.out_features, device=device)
        # populate the h and c states respecting computation order
        for n in range(node_order.max() + 1):
            self._run_lstm(n, h, c, features, node_order, adjacency_list, edge_order)
        return h, c
    def _run_lstm(self, iteration, h, c, features, node_order, adjacency_list, edge_order):
        '''Helper function to evaluate all tree nodes currently able to be evaluated.
        '''
        # N is the number of nodes in the tree
        # n is the number of nodes to be evaluated on in the current iteration
        # E is the number of edges in the tree
        # e is the number of edges to be evaluated on in the current iteration
        # F is the number of features in each node
        # M is the number of hidden neurons in the network
        # node_order is a tensor of size N x 1
        # edge_order is a tensor of size E x 1
        # features is a tensor of size N x F
        # adjacency_list is a tensor of size E x 2
        # node_mask is a tensor of size N x 1
        node_mask = node_order == iteration
        # edge_mask is a tensor of size E x 1
        edge_mask = edge_order == iteration
        # x is a tensor of size n x F
        x = features[node_mask, :]
        # At iteration 0 none of the nodes should have children
        # Otherwise, select the child nodes needed for current iteration
        # and sum over their hidden states
        if iteration == 0:
            iou = self.W_iou(x)
        else:
            # adjacency_list is a tensor of size e x 2
            adjacency_list = adjacency_list[edge_mask, :]
            # parent_indexes and child_indexes are tensors of size e x 1
            # parent_indexes and child_indexes contain the integer indexes needed to index into
            # the feature and hidden state arrays to retrieve the data for those parent/child nodes.
            parent_indexes = adjacency_list[:, 0]
            child_indexes = adjacency_list[:, 1]
            # child_h and child_c are tensors of size e x 1
            child_h = h[child_indexes, :]
            child_c = c[child_indexes, :]
            # Add child hidden states to parent offset locations
            _, child_counts = torch.unique_consecutive(parent_indexes, return_counts=True)
            child_counts = tuple(child_counts)
            parent_children = torch.split(child_h, child_counts)
            parent_list = [item.sum(0) for item in parent_children]
            h_sum = torch.stack(parent_list)
            iou = self.W_iou(x) + self.U_iou(h_sum)
        # i, o and u are tensors of size n x M
        i, o, u = torch.split(iou, iou.size(1) // 3, dim=1)
        i = torch.sigmoid(i)
        o = torch.sigmoid(o)
        u = torch.tanh(u)
        # At iteration 0 none of the nodes should have children
        # Otherwise, calculate the forget states for each parent node and child node
        # and sum over the child memory cell states
        if iteration == 0:
            c[node_mask, :] = i * u
        else:
            # f is a tensor of size e x M
            f = self.W_f(features[parent_indexes, :]) + self.U_f(child_h)
            f = torch.sigmoid(f)
            # fc is a tensor of size e x M
            fc = f * child_c
            # Add the calculated f values to the parent's memory cell state
            parent_children = torch.split(fc, child_counts)
            parent_list = [item.sum(0) for item in parent_children]
            c_sum = torch.stack(parent_list)
            c[node_mask, :] = i * u + c_sum
        h[node_mask, :] = o * torch.tanh(c[node_mask])
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容