深度学习——结构递归神经网络(Recursive NN)

深度学习——结构递归神经网络(Recursive NN)

1、递归神经网络介绍

目前,递归神经网络一共包含两种,一种是时间递归神经网络(Recurrent NN),另外一种是结构性递归神经网络(Recursive NN)。

1.1 神经网络处理变长序列

对于常规的神经网络而言,其输入节点的长度往往都是固定的。例如下面的一个普通的前馈神经网络。
(图片来源:https://image.baidu.com/search)
在这里插入图片描述

对于上述的前馈网络而言,其每次的输入就只有X1,X2,X3,X4四个。我们知道,在NLP中,句子序列的长度往往都是不固定的。那么如何处理变长的数据呢?

在这里,我们提出了采用递归神经网络的方式来处理不定长度的数据。由于输入单元个数的不确定,所以,必须采用循环,或者递归的方式来对数据进行输入,由此也就有了基于时间的递归神经网络和基于结构的递归神经网络。首先介绍基于时间的递归神经网络对于不定长序列的处理。

图片来源:https://www.sohu.com/a/128784058_487514


当在处理一个变长的句子的时候,将句子看作是不同词汇所构成的序列,每次向递归神经网络中输入一个词汇,知道所有的词汇输入完成,网络产生对应的输出。

下面,我们给出这样的一个序列,“两个计算机学院的学生”,从字面上来看,这句话可以有两种理解方式,第一种,是同一个计算机学院的两个学生,第二种,来自两个不同计算机学院的学生。当我们以基于时间的递归网络进行输入的时候,显然,网络是对无法对这两种语义进行辨析的。
在这里插入图片描述
在这里插入图片描述

上图是不同语义对应的语法树,根据不同语法树结构,我们自然而然的就可以想到使用树状的神经网络来对序列进行语义理解。这也就是基于结构的递归神经网络的应用。

基于结构的递归神经网络类似于一个自编码器,它将由句子构成的树或者图结构进行自编码,将其整个结构映射的向量空间中。语义相近的句子在向量空间中拥有更近的向量距离。同时,当把词汇,句子、段落映射到相同的向量空间中的时候,可以利用向量组合的方式,由词构成句子,由句子构成段落。不停地向上组合,最后将组合出来的语义树统一的表示成一个有意义的向量。

2、递归神经网络前向传播过程

2.1 前向计算过程

输入:两个或者多个子节点,
输出:两个子节点编码之后产生的父节点
在这里插入图片描述

下面两个节点表示子节点向量,上面表示的是父节点的向量,子节点和父节点组成一个全连接到的神经网络,也就是子节点和父节点上的神经单元进行全连接。计算公式为:

F = f ( W ( C 1 ∣ ∣ C 2 ) + b ) F =f(W(C1||C2) + b) F=f(W(C1C2)+b)

其中C1,C2分别表示的是两个子节点的向量,F表示父节点的向量,W表示权重,b表示偏重。f是激活函数。然后,我们将父节点的向量和其他子节点进行拼接,再次产生下一个父节点向量,不停的递归,直到生成最终的根节点向量。如下图所示:
在这里插入图片描述

2.2 前向传播的代码展示
#encoding=utf-8
import numpy as np
def sigmoid(x):
    return 1/(1+np.exp(-x))
def IdentityActivator(x):
    return x
#定义递归树的节点信息
class TreeNode(object):
    def __init__(self,data,children=[],children_data=[]):
        self.parent = None
        self.children = children
        self.children_data = children_data
        self.data = data
        for child in children:
            child.parent = self
class RecursiveNetWork(object):
    def __init__(self,node_dim,child_count,lr):
        '''
        node_dim : 每一个节点的维度
        child_count : 每一个节点的子节点的个数
        lr : 学习率
        '''
        self.node_dim = node_dim
        self.child_count = child_count
        self.lr = lr
        self.W = np.ones((child_count * node_dim,node_dim))
        self.B = np.ones((node_dim,1))
        #生成最后的根节点
        self.root = None
    def concatenate(self,tree_nodes):
        '''
        定义向量拼接的方法
        '''
        concat = np.zeros((0,1))
        concat = np.concatenate((concat,tree_nodes[0].data))
        concat = np.concatenate((concat,tree_nodes[1].data))
        #for node in tree_nodes:
         #   cocat = np.concatenate((concat,node.data))
        return concat

    def forward(self,*children):
        #首先,将向量进行拼接
        children_data = self.concatenate(children)
        #前向传播,获取父节点的数据
        parent_data = IdentityActivator(np.dot(self.W.T,children_data)+ self.B)
        #生成根节点
        self.root = TreeNode(parent_data,children,children_data)

3 反向传播

3.1 反向传播的过程计算(BPTS算法 )
3.1.1 少量节点的情况

对于基于结构的递归神经网络,其训练过程和基于时间的递归神经网络不同,基于时间的递归神经网络的反向传播是将当前时刻 t k t_k tk的误差不同的向前一个时刻 t k − 1 t_{k-1} tk1进行反向传播,直到传播到初始时刻 t 0 t_0 t0。对于基于结构的递归神经网络而言,它的反向传播的方式是从根节点反向传播到所有的子节点,这种反向传播的方式成为BPTS算法。

我们先考虑三个节点的情况,为了便于说明,我们首先说明一个父节点,两个子节点的情况。具体结构如下图所示:
在这里插入图片描述
根据上图,我们构造出输入的节点矩阵X和权重W矩阵。输入包括两个子节点 X 1 , X 2 X_1,X_2 X1,X2,每一个节点包含两个属性:
X 1 = X 11 X 12 , X 2 = X 21 X 22 X_1= \begin{matrix} X_{11}\\ X_{12}\\ \end{matrix} ,X_2= \begin{matrix} X_{21}\\ X_{22}\\ \end{matrix} X1=X11X12X2=X21X22
将两者合并成一个向量之后,表示为X:
X = X 11 X 12 X 21 X 22 X= \begin{matrix} X_{11}\\ X_{12}\\ X_{21}\\ X_{22}\\ \end{matrix} X=X11X12X21X22
下面构建出对应的W矩阵:
W = W 111 W 112 W 121 W 122 W 211 W 212 W 221 W 222 W= \begin{matrix} W_{111}&W_{112}\\ W_{121}&W_{122}\\ W_{211}&W_{212}\\ W_{221}&W_{222}\\ \end{matrix} W=W111W121W211W221W112W122W212W222

根据前向传播的过程,设:
n e t Y = W T X , Y = s i g m o i d ( n e t Y ) net_Y=W^TX,Y=sigmoid(net_Y) netY=WTXY=sigmoid(netY)
设J表示误差项,设 δ F δ_F δF是对于父层节点的误差函数,则有:
δ n e t Y = ∂ J n e t Y = ∂ J ∂ n e t Y 1 = ∂ J ∂ Y 1 ∂ Y 1 ∂ n e t Y 1 = ∂ J ∂ Y 1 ∗ Y 1 ∗ ( 1 − Y 1 ) ∂ J ∂ n e t Y 2 = ∂ J ∂ Y 2 ∂ Y 2 ∂ n e t Y 2 = ∂ J ∂ Y 2 ∗ Y 2 ∗ ( 1 − Y 2 ) δ_{net_Y}=\frac{∂J}{net_Y}= \begin{matrix} \frac{∂J}{∂net_{Y_1}}=\frac{∂J}{∂{Y_1}}\frac{∂Y_1}{∂{net_{Y_1}}}=\frac{∂J}{∂{Y_1}}*Y_1*(1-Y_1)\\ \frac{}{}\\ \frac{∂J}{∂net_{Y_2}}=\frac{∂J}{∂{Y_2}}\frac{∂Y_2}{∂{net_{Y_2}}}=\frac{∂J}{∂{Y_2}}*Y_2*(1-Y_2)\\ \end{matrix} δnetY=netYJ=netY1J=Y1JnetY1Y1=Y1JY1(1Y1)netY2J=Y2JnetY2Y2=Y2JY2(1Y2)
我们假设 δ F δ_F δF已知,我们下面继续推导 ∂ J ∂ x 11 \frac{∂J}{∂x_{11}} x11J
∂ J ∂ x 11 = ∑ i = 1 2 ∂ J ∂ n e t Y i ∗ ∂ n e t Y i ∂ X 11 = ∑ i = 1 2 ∂ J ∂ n e t Y i ∗ W 11 i \frac{∂J}{∂x_{11}}=∑_{i=1}^2\frac{∂J}{∂net_{Y_i}}*\frac{∂net_{Y_i}}{∂X_{11}}= ∑_{i=1}^2\frac{∂J}{∂net_{Y_i}}*W_{11i} x11J=i=12netYiJX11netYi=i=12netYiJW11i

同理,可以推导出关于 X 12 , X 21 , X 22 X_{12},X_{21},X_{22} X12X21X22的导数为:
∂ J ∂ x 12 = ∑ i = 1 2 ∂ J ∂ n e t Y i ∗ ∂ n e t Y i ∂ X 12 = ∑ i = 1 2 ∂ J ∂ n e t Y i ∗ W 12 i \frac{∂J}{∂x_{12}}=∑_{i=1}^2\frac{∂J}{∂net_{Y_i}}*\frac{∂net_{Y_i}}{∂X_{12}}= ∑_{i=1}^2\frac{∂J}{∂net_{Y_i}}*W_{12i} x12J=i=12netYiJX12netYi=i=12netYiJW12i
∂ J ∂ x 21 = ∑ i = 1 2 ∂ J ∂ n e t Y i ∗ ∂ n e t Y i ∂ X 21 = ∑ i = 1 2 ∂ J ∂ n e t Y i ∗ W 21 i \frac{∂J}{∂x_{21}}=∑_{i=1}^2\frac{∂J}{∂net_{Y_i}}*\frac{∂net_{Y_i}}{∂X_{21}}= ∑_{i=1}^2\frac{∂J}{∂net_{Y_i}}*W_{21i} x21J=i=12netYiJX21netYi=i=12netYiJW21i
∂ J ∂ x 22 = ∑ i = 1 2 ∂ J ∂ n e t Y i ∗ ∂ n e t Y i ∂ X 22 = ∑ i = 1 2 ∂ J ∂ n e t Y i ∗ W 22 i \frac{∂J}{∂x_{22}}=∑_{i=1}^2\frac{∂J}{∂net_{Y_i}}*\frac{∂net_{Y_i}}{∂X_{22}}= ∑_{i=1}^2\frac{∂J}{∂net_{Y_i}}*W_{22i} x22J=i=12netYiJX22netYi=i=12netYiJW22i

整理成向量的形式:
δ X = ∂ J ∂ X = W δ Y = ∂ J ∂ X 11 ∂ J ∂ X 12 ∂ J ∂ X 21 ∂ J ∂ X 22 δ_X=\frac{∂J}{∂X}=Wδ_Y= \begin{matrix} \frac{∂J}{∂X_{11}}\\ \frac{}{}\\ \frac{∂J}{∂X_{12}}\\ \frac{}{}\\ \frac{∂J}{∂X_{21}}\\ \frac{}{}\\ \frac{∂J}{∂X_{22}} \end{matrix} δX=XJ=WδY=X11JX12JX21JX22J
如,X也是采用sigmoid激活的,那么根据激活函数的求导,可以直接定义:
δ n e t X = ∂ J ∂ n e t X = δ X ∗ X ∗ ( 1 − X ) δ_{net_X}=\frac{∂J}{∂net_{X}}=δ_X*X*(1-X) δnetX=netXJ=δXX(1X)
这里可以根据我们选择的激活函数进行实际的求导。,设激活函数为 f ( x ) f(x) f(x),则最终获取的值为:
δ n e t X = ∂ J ∂ n e t X = δ X ∗ f ′ ( X ) δ_{net_X}=\frac{∂J}{∂net_{X}}=δ_X*f'(X) δnetX=netXJ=δXf(X)
下面就是将 δ n e t X δ_{net_X} δnetX拆解成 δ n e t X 1 和 δ n e t X 2 δ_{net_{X_1}}和δ_{net_{X_2}} δnetX1δnetX2两个部分的误差。
δ n e t X 1 = ∂ J ∂ n e t X 1 = W δ n e t X 1 = ∂ J ∂ n e t X 11 ∂ J ∂ X n e t 12 , δ n e t X 2 = ∂ J ∂ n e t X 2 = W δ n e t X 2 = ∂ J ∂ n e t X 21 ∂ J ∂ n e t X 22 δ_{net_{X_1}}=\frac{∂J}{∂net_{X_1}}=Wδ_{net_{X_1}}= \begin{matrix} \frac{∂J}{∂net_{X_{11}}}\\ \frac{}{}\\ \frac{∂J}{∂Xnet_{{12}}}\\ \end{matrix}, δ_{net_{X_2}}=\frac{∂J}{∂net_{X_{2}}}=Wδ_{net_{X_2}}= \begin{matrix} \frac{∂J}{∂net_{X_{21}}}\\ \frac{}{}\\ \frac{∂J}{∂net_{X_{22}}}\\ \end{matrix} δnetX1=netX1J=WδnetX1=netX11JXnet12JδnetX2=netX2J=WδnetX2=netX21JnetX22J

3.1.2 多节点的情况

当构造的递归树的层数较多的时候,我们可以根据上面的少量节点的推导公式,扩展到多层递归树的情况。
在这里插入图片描述
在上图中,已经将总的误差标出来了。假设 δ r o o t δ_{root} δroot已知,下面我们来逐层推导一下:
δ n e t X = W δ r o o t ∗ f ′ ( X ) , δ n e t X 1 = δ n e t X [ 0 : n o d e d i m , : ] , δ n e t X 2 = δ n e t X [ n o d e d i m : , : ] δ_{net_X}=Wδ_{root}*f'(X),δ_{net{X_1}}=δ_{net_X}[0:nodedim,:],δ_{net_{X_2}}=δ_{net_X}[nodedim:,:] δnetX=Wδrootf(X)δnetX1=δnetX[0:nodedim,:]δnetX2=δnetX[nodedim:,:]
δ n e t m = W δ X ∗ f ′ ( m ) , δ n e t m 1 = δ n e t m [ 0 : n o d e d i m , : ] , δ n e t m 2 = δ n e t m [ n o d e d i m : , : ] δ_{net_m}=Wδ_{X}*f'(m),δ_{net{m_1}}=δ_{net_m}[0:nodedim,:],δ_{net_{m_2}}=δ_{net_m}[nodedim:,:] δnetm=WδXf(m)δnetm1=δnetm[0:nodedim,:]δnetm2=δnetm[nodedim:,:]
δ n e t k = W δ m ∗ f ′ ( k ) , δ n e t k 1 = δ n e t k [ 0 : n o d e d i m , : ] , δ n e t k 2 = δ n e t k [ n o d e d i m : , : ] δ_{net_k}=Wδ_{m}*f'(k),δ_{net{k_1}}=δ_{net_k}[0:nodedim,:],δ_{net_{k_2}}=δ_{net_k}[nodedim:,:] δnetk=Wδmf(k)δnetk1=δnetk[0:nodedim,:]δnetk2=δnetk[nodedim:,:]
其中,W是所有节点共用的。nodedim表示节点数据的维度。

4、权重W,B的更新

我们可以看到,权重W在树的每一层都出现了,我们在更新W的时候,就需要将每一层对于W的更新做一个累加和。下面,根据上面的例子进行逐层计算W,并累加。

4.1 逐层计算关于W的梯度
  1. 首先 X 1 , X 2 , r o o t X_1,X_2,root X1,X2,root层的W,根据计算公式 n e t r o o t = W T X + B net_{root}=W^TX+B netroot=WTX+B有:
    ∂ n e t Y ∂ W 111 = ∂ n e t Y 1 ∂ W 111 = X 11 \frac{∂net_Y}{∂W_{111}}=\frac{∂net_{Y_1}}{∂W_{111}}=X_{11} W111netY=W111netY1=X11
    ∂ n e t Y ∂ W 112 = ∂ n e t Y 2 ∂ W 112 = X 11 \frac{∂net_Y}{∂W_{112}}=\frac{∂net_{Y_2}}{∂W_{112}}=X_{11} W112netY=W112netY2=X11
    ∂ n e t Y ∂ W 121 = ∂ n e t Y 1 ∂ W 121 = X 12 \frac{∂net_Y}{∂W_{121}}=\frac{∂net_{Y_1}}{∂W_{121}}=X_{12} W121netY=W121netY1=X12
    ∂ n e t Y ∂ W 122 = ∂ n e t Y 2 ∂ W 122 = X 12 \frac{∂net_Y}{∂W_{122}}=\frac{∂net_{Y_2}}{∂W_{122}}=X_{12} W122netY=W122netY2=X12
    同理,可以计算关于第二个子节点 X 2 X_2 X2的梯度。将上述的计算过程总结成向量的形式可以看到:
    ∂ J ∂ W X = X δ n e t Y T \frac{∂J}{∂W_X}=Xδ_{net_Y}^T WXJ=XδnetYT
  2. 对于上述的过程,扩展到其他层有:
    ∂ J ∂ W m = m δ n e t X T \frac{∂J}{∂W_m}=mδ_{net_X}^T WmJ=mδnetXT
    ∂ J ∂ W k = k δ n e t m T \frac{∂J}{∂W_k}=kδ_{net_m}^T WkJ=kδnetmT
4.2 权重B的更新

B的更新相对于W要简单很多。同样也是将每一层对于B的更新的梯度的求和。

  1. 首先 X 1 , X 2 , r o o t X_1,X_2,root X1,X2,root层的B,根据计算公式 n e t r o o t = W T X + B net_{root}=W^TX+B netroot=WTX+B有:
    ∂ n e t Y ∂ B 1 = ∂ n e t Y 1 ∂ B 1 = 1 \frac{∂net_Y}{∂B_{1}}=\frac{∂net_{Y_1}}{∂B_{1}}=1 B1netY=B1netY1=1
    ∂ n e t Y ∂ B 2 = ∂ n e t Y 2 ∂ B 2 = 1 \frac{∂net_Y}{∂B_{2}}=\frac{∂net_{Y_2}}{∂B_{2}}=1 B2netY=B2netY2=1
    n e t Y net_Y netY关于B的梯度值为: δ n e t Y δ_{net_Y} δnetY
    同理可以推导到其他层中:
    δ n e t X δ_{net_X} δnetX
    δ n e t m δ_{net_m} δnetm
4.2 求和更新

∂ J ∂ W = ∂ J ∂ W X + ∂ J ∂ W m + ∂ J ∂ W k = ∑ i = X , m , k ∂ J ∂ W i \frac{∂J}{∂W}=\frac{∂J}{∂W_X}+\frac{∂J}{∂W_m}+\frac{∂J}{∂W_k}=∑_{i=X,m,k}\frac{∂J}{∂W_i} WJ=WXJ+WmJ+WkJ=i=X,m,kWiJ
∂ J ∂ B = δ n e t Y + δ n e t X + δ n e t m \frac{∂J}{∂B}=δ_{net_Y}+δ_{net_X}+δ_{net_m} BJ=δnetY+δnetX+δnetm
W n e w = W − α ∂ J ∂ W W_{new}=W-α\frac{∂J}{∂W} Wnew=WαWJ
B n e w = B − α ∂ J ∂ B B_{new}=B-α\frac{∂J}{∂B} Bnew=BαBJ

5、反向传播及误差更新的实现

def backward(self,parent_delta):
        self.calc_delta(parent_delta,self.root)
        self.W_grad,self.B_grad = self.calc_gradient(self.root)
    def calc_delta(self,parent_delta,parent):
        '''
        计算δ值
        '''
        parent.delta = parent_delta
        if parent.children:
            children_delta = np.dot(self.W,parent_delta)
            slices = [(i,i*self.node_dim,(i+1)*self.node_dim) for i in range(self.child_count)]
            #针对每一个子节点,计算梯度
            for s in slices:
                self.calc_delta(children_delta[s[1]:s[2]],parent.children[s[0]])
    def calc_gradient(self,parent):
        '''
        计算关于W和B的梯度的累加和
        '''
        W_grad = np.zeros((self.child_count*self.node_dim,self.node_dim))
        B_grad = np.zeros((self.node_dim,1))
        if not parent.children:
            return W_grad,B_grad
        parent.W_grad = np.dot(parent.children_data,parent.delta.T)
        parent.B_grad = parent.delta
        W_grad += parent.W_grad
        B_grad += parent.B_grad
        for child in parent.children:
            W,B = self.calc_gradient(child)
            W_grad += W
            B_grad += B
        return W_grad,B_grad
    def update(self):
        self.W -= self.lr * self.W_grad
        self.B -= self.lr * self.B_grad

注意:在代码中,为了计算方便,选择了y=x的激活方式

6、参考文章

零基础入门深度学习(六):递归神经网络

  • 3
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要使用递归神经网络(Recurrent Neural Network, RNN)对英文垃圾信息进行分类和预测,可以使用长短时记忆网络(Long Short-Term Memory, LSTM)作为RNN的一种变体。以下是一个基本的步骤指导: 1. 数据准备:收集大量的英文邮件或文本数据,包括垃圾邮件和非垃圾邮件。将数据分为训练集和测试集。 2. 数据预处理:对文本数据进行预处理,包括去除停用词、标点符号和特殊字符,将文本转化为数字表示(如词袋模型或词嵌入)。 3. 构建模型:使用递归神经网络(LSTM)构建分类模型。一种常见的模型架构是:输入层 - LSTM层 - 全连接层 - 输出层。可以根据需要进行多层堆叠。 4. 训练模型:使用训练集对模型进行训练。通过反向传播算法来更新网络中的权重和偏置,以最小化损失函数(如交叉熵)。 5. 模型评估:使用测试集评估模型的性能,计算准确率、精确率、召回率等指标来衡量分类效果。 6. 参数调优:根据评估结果对模型进行调优,可以尝试不同的超参数设置、网络结构或优化算法,以提高分类性能。 7. 预测分类:使用训练好的模型对新的文本数据进行分类预测,判断是否为垃圾信息。 以下是一个使用Python和TensorFlow框架实现的示例代码: ```python import tensorflow as tf from keras.preprocessing.text import Tokenizer from keras.preprocessing.sequence import pad_sequences # 准备数据 texts = ["This is a spam message", "This is a normal message", "Get a free vacation now!", "Limited time offer!"] labels = [1, 0, 1, 1] # 对文本进行预处理 tokenizer = Tokenizer(num_words=1000) tokenizer.fit_on_texts(texts) sequences = tokenizer.texts_to_sequences(texts) padded_sequences = pad_sequences(sequences, maxlen=10) # 构建模型 model = tf.keras.Sequential([ tf.keras.layers.Embedding(1000, 16, input_length=10), tf.keras.layers.LSTM(32), tf.keras.layers.Dense(64, activation='relu'), tf.keras.layers.Dense(1, activation='sigmoid') ]) # 编译模型 model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) # 训练模型 model.fit(padded_sequences, labels, epochs=10) # 预测新数据 new_texts = ["You have won a prize!", "Hello, how are you?"] new_sequences = tokenizer.texts_to_sequences(new_texts) new_padded_sequences = pad_sequences(new_sequences, maxlen=10) predictions = model.predict(new_padded_sequences) print(predictions) ``` 请注意,这只是一个简单的示例代码,实际应用中可能需要根据具体情况进行更复杂的模型设计和调优。另外,还需要适配数据集和进行更详细的处理过程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值