分享

《Deep learning 中译版第10章10.1~10.2》

 昵称38917964 2016-12-25

 

10

序列模型:循环网络和递归网络

循环神经网络或RNNs是处理序列数据的神经网络的一种。就像卷积网络专门用于处理网格数据(比如图像)一样,循环神经网络是一种专门处理序列数据的网络。正如卷积网络可以容易地缩放到具有大的宽度和高度的图像一样,而且有些卷积网络可以处理不同尺寸的图像,与不做基于序列的限定的网络相比,循环神经网络可以扩展到长得多的序列。大多数循环神经网络可以处理不同长度的序列。

为了从多层神经网络(multi-layernetworks)转向循环神经网络,我们需要借鉴来自20世纪80年代的机器学习和统计模型的想法:在模型的不同部分共享参数。参数共享使得我们可以将模型扩展和应用到不同形式的样本(examples)中,这里的不同指的是长度的不同,并且具有一定的泛化能力。如果我们为每一个时间点赋予一个独立的参数,我们就不能将模型推广到那些训练集中未见过的序列长度,也不能在不同序列长度和不同时间位置上共享统计强度(statistical strength)。当一段信息可能出现在序列的许多位置时,参数共享就变得尤为重要。例如,考虑下面两句话,“I went to Nepal in 2009” “In2009,I went toNepal.”。如果我们让机器学习模型读这两个句子,并让模型提取出主人公去尼泊尔的时间,我们希望它能识别出是2009年,无论这个时间是句子中的第六个单词还是第二个单词。假设我们用一个前馈神经网络处理一个固定长度的序列。全连结的前传神经网络会为每一个输入特征赋予一个独立的参数,所以,它可能要学习一个句子中每一个位置的语言分割规则。相比较而言,循环神经网络在几种时间步长上会共享权重。

一种相似的想法是将卷积应用于1维时间序列。这种卷积的方法是延时神经网络的基础。卷积操作使得网络在时间维度上共享参数,但这种共享是粗浅的(shallow)。卷积的输出是一个序列,这个序列的每一个元素都是将输入序列中相邻元素输入一个函数后得到的结果。参数共享的想法表现为在每一个时间步长下应用相同的卷积内核。循环神经网络以另一种方式共享参数。输出中的每一个元素都是将该元素的前几个元素输入一个函数后的到的结果。每一个输出元素都遵循相同的更新规则。这种循环的方式通过一个很深的计算路径图来实现参数共享。

为了便于解释,我们认为RNNs作用于一个时间序列向量x(t),t的取值范围是1到τ。在实际应用中,循环神经网络通常都会处理一个小批量的训练样本,不同的样本有着不同的序列长度。为了简化,我们暂时忽略小批量的问题。而且,这里所提到的时间也不一定专指物理世界的时间,它仅代表序列中元素的位置。RNNs也可以用于2维空间数据,例如图像,甚至也可以将时间维度加进来,在这种情况下,网络可以在时间上产生后向的连接,这也就是假设在将整个序列提供给网络之前整个序列就已经被观察了。

本章扩展了包含循环的计算路径的想法。这些循环表示了变量的当前值在未来时间步长对其自身值的影响。这样的计算路径使我们可以定义循环神经网络。接下来,我们描述了许多不同的构建,训练和使用循环神经网络的方法。

想要获得更多关于循环神经网络信息的读者可以阅读Graves (2012 )的书。

10.1 展开计算路径图

计算路径图是展示一系列计算集合结构的方法,比如那些将输入值和参数映射到输出值和损失值上的计算路径图。这一部分的一般性介绍可以参考6.5.1节。这一节将要向大家解释这个将递归或循环计算加入到具有重复结构的计算路径图中,这一计算路径图特别适合于事件链(a chain of events)。计算路径图的展开体现出了跨深层网络结构的参数共享。

例如,考虑一种典型的动力学系统:


这里,s(t)被称为系统状态。

方程10.1是循环的,因为在时刻ts的定义指向回了t-1时刻的相同定义。

对于一个有限的时间步长τ,可以利用τ-1时的定义将路径图进行展开。例如,如果我们在τ=3时对方程10.1进行展开,可以得到:



通过重复利用按照相同方式做出的定义对方程进行展开,我们可以得到一个表达式,在这个表达式中不涉及循环。现在,我们可以将这个表达式用传统的有向无环计算路径图表示。对方程10.110.3的展开计算路径图如图10.1所示。



再来看第二个例子,我们来考虑这样一个动力系统,它是由外部信号x(t)来驱动,



此处可以看到,当前的状态包含了所有过去序列的信息。

循环神经网络可以用很多不同的方式定义。就像绝大多数函数都可以看成是前馈神经网络一样,本质上,任何包含循环的函数都可以看成是循环神经网络。

许多循环神经网络应用方程10.5或一个类似的方程来定义隐含单元的值。为了表明状态就是网络隐含单元,我们用h表示状态,然后重写方程10.4:


如图10.2所示,典型的RNNs将增加外部层次化特征,比如从状态h读取信息以便进行预测的输出层。



当我们训练一个循环神经网络使其能够根据过去预测未来时,网络通常学习使用h(t)作与任务相关概念(译者注:类似于高层特征)的有损概括(summary),这些概念源自截至到时间t的过去的输入序列。这种概括通常必然是有损的,因为它将一个任意长度的序列                             映射到固定长度的向量h(t)上。这种概括可能会选择性地精确保留过去序列中的某些概念,这取决于使用什么样的训练准则。例如,如果RNN被用于统计语言模型(statistical language modeling),典型情形是给定以前的单词来预测下一个单词。这里可能不需要存储截至到时间t时的所有输入序列,而只存储够用的信息来预测句子的其余部分。绝大部分需求情形是要求h(t)包含了足够信息,使得我们可以利用h(t)来近似恢复输入序列,就像自编码框架那样(第14章)。

         方程10.5可以用两种方式描述。一种方式是将其看成一个包含了所有成分的节点,就像一个生物神经网络一样。在这种观点下,网络定义了一个实时处理的圆,其中,当前状态可以影响未来的状态,就像图10.2中左侧部分所示。我们用一个黑色方块表示1个时间步长的延时,本章会始终使用这种黑色方块作为延时表示。描述RNN的另一种方式是展开的计算路径图,见图10.2右侧部分。展开路径图的大小取决于序列长度。

         我们可以用方程g(t)来描述t步后的展开循环体:


方程g(t)将全部过去的序列
为输入并得到了当前的状态,但展开循环体结构通过重复地使用方程f表达g(t)。因此,这种展开的过程引入了两个主要的优势:

         1.忽略了序列的长度,学习模型常常有相同的输入规模(size),因为它由从一种状态到另一种状态的转变来定义,而不是由一个可变长度的历史状态来定义(specified)。

         2.它使用相同的状态转移方程s,这样就允许可以在每一个时间下使用相同的参数。

这两个因素使我们只需要学习一个模型f就能解决左右时间步长和任意序列长度的数据,而不必为每一个可能的时间点学习一个模型g(t)。只学习一个模型,并在每一个时间点共享该模型,这种方法可以处理任意序列长度的样本,即使这种序列长度的样本在训练集中从未出现过。这种做法也使得所需的训练集样本量极大地减少,远少于训练g(t)所需要的样本量。

         10.2中的两种呈现方式都有他们各自的用处。其中,循环路径图更加简洁,而展开的路径图提供了关于计算机执行方式的显示描述。通过显示地展示信息流的传播路径,展开图有利于表达信息在时间维度上向前流动的思想(计算输出值和损失值)和后向流动思想(计算梯度)。

10.2 循环神经网络

         有了10.1节介绍的展开路径图和参数共享的思想,我们就可以设计各种各样的循环神经网络。

         下面几点是关于循环神经网络的几种重要设计模式:

1)循环神经网络在每一个时刻都有一个输出,并在隐含层之间存在循环连接,如图10.3所示。



2)循环神经网络在每一个时间点上产生一个输出,该出输出与下一个时间点的隐含单元存在循环连接,如图10.4所示。


3)循环神经网络在隐含单元之间存在循环连接,这种网络一次读入整个序列并产生一个单独的输出,如图10.5所示。



10.3所展示的网络是一个比较有代表性的例子,它贯穿于本章的大部分内容。

         10.3所示的循环神经网络和方程10.8是一样的。任何可由图灵机计算的函数都可以用这种有限规模的循环网络实现计算。可以在一定量的时间步长(时间长度)之后可以从RNN中读取输出,这里的时间长度与图形机所使用的时间长度和输入长度是渐进线性关系。图灵机可计算的函数是离散的,因此这些结果注重于函数的精确运算,而不是近似运算。当RNN被用作图灵机来使用时,其输入和输出都是二值化的序列。图灵机的“输入”是计算函数的一种范式,所以模拟这种图灵机的相同的网络足以解决所有问题。理论RNN可以通过用无界精度的有理数表示其激活和权重来模拟无界堆栈。(原文:The theoretical RNN used for the proof can simulate an unboundedstack by representing its activations and weights with rational numbers ofunbounded precision

         我们现在来建立图10.3RNN的前馈方程。图中没有限定图中隐含单元的激活函数。这里我们假设它是双曲正切函数。而且,图中也没有指明输出的形式和损失函数的类型。这里我们假设输出是离散的,就像RNN用于预测字或字符一样。表示离散特征的一种很自然的方式是将输出o看成是每一个离散变量的未经归一化的对数概率值。我们可以对输出值执行softmax操作。前馈其实于一个特殊化的初始状态h(0)。然后,对于从t=1t=τ的每一个时刻,我们应用下面的方程来更新:



这里的参数是向量bc,以及权重矩阵U,V,W,他们分别代表输入和隐含层,隐含层和输出层,隐含层和隐含层的连接。这个循环神经网络可以将输入序列映射到具有相同长度的输出序列。对于给定序列x和其对应的序列y,总的损失即所有时刻的损失之和,即


此处将模型的输出向量中作为y(t)进而算出

。计算损失函数关于各参数的梯度是一项成本很高的操作。计算梯度的过程包括图10.3中从左到右的前馈过程,紧接着是从右到左的反向传播。其时间成本是O(τ)而且不能用并行计算的方式减少运算时间,因为前馈过程本质上是串行的;每一个时间点必须在前一个时间点计算完成后才能够计算。前传过程中计算得到的状态需要被保存下来,这些状态会在反向传播过程中被使用,因此,内存成本也是O(τ)。反向传播算法在这里被称为BPTT(back-propagation through time),其运算成本是O(τ),在接下来的10.2.2节会进一步讨论该算法。这种在隐含单元之间的循环连接的网络因此非常强大,但训练的成本很高。还有其他的选择吗?

10.2.1 教师强迫(Teacher forcing)和循环输出网络

       仅在一个时间点的输出和下一个时间点的隐含单元之间存在循环连接的网络(如图10.4)计算能力较弱,因为它缺少隐含单元之间的循环链接。例如,它不能模拟通用图灵机。因为这种网络缺少隐含单元之间的循环连接,它需要用输出单元去捕捉过去的信息,这些过去的信息会被用于预测未来。因为我们的训练目标是使输出单元与训练集目标值更加相似(匹配),因此,这些输出单元不可能捕捉输入的历史信息,除非使用者知道如何描述系统的全部状态并将其作为训练目标的一部分。对于任何一个通过比较t时刻预测值和输出值的差异作为损失函数的网络,去掉隐含单元之间的循环连接的好处是所有时间点都是解耦的(独立的)。训练因此就变得可以并行运行了,每一个时间点t的梯度可以独立的计算。不必要先为前一个时间点计算输出值,因为训练集提供了该输出的理想值。

         具有从其输出返回到模型中的循环连接的模型可以用教师强迫来训练。教师强迫是一种源自最大似然准则的计算过程,在该过程中,训练模型将真实的输出y(t)作为t 1时刻的输入。关于这一点,我们可以通过在两个时间点检查同一个序列来证实。条件最大似然准则是:



在这个例子中,我们看到在时刻t=2,模型的训练目标是:在从训练集中同时给定截至到当前时刻的x序列和前一个时刻的真实输出y的情况下,最大化y(2)的条件概率。最大似然因此指定在训练期间,而不是将模型自己的输出反馈回本身,这些连接应当被输入进那些已经指定了正确的输出应该是什么的目标值。图10.6对此给出了解释。


我们最初引入教师强化的动机是,这种计算方式在缺少隐含单元之间的连接的情况下,避免了在模型中自始至终地执行BP算法。然而,一旦隐含单元变成了更早时间点信息的函数,使用BPTT算法就变得很必要了。因此,有些模型可能会同时使用教师强化和BPTT算法进行训练。

         如果网络在接下来的时刻在开环模式(open-loop mode)下被使用,其网络的输出(或来自输出值数据分布的采样)被反馈为输出,那么,严格的教师强化就会出现一些缺点。在这种情况下,训练模型时网络得到的输入类型与测试时得到的输入类型差异很大。缓解这一问题的一种方法是同时使用教师强化下的输入和自由运行(free-running)下的输入,例如,在展开的循环输入输出路径图中预测未来几个时间点的正确目标值。用这种方法,网络能够学会考虑在训练期间没有见过的输入条件(比如那些在自由运行模式下的自我生成),并且学会如何将状态映射到一个将使网络在几个步骤后生成适当输出的状态。减小在训练时间看到的输入与在测试时间看到的输入之间的差别的另一种方法是随机选择使用生成的值或实际数据值作为输入。这种方法是用课程学习策略(curriculum learning strategy)来逐渐使用更多的生成值作为输入。

10.2.2 在循环神经网络中计算梯度

         在循环神经网络中计算梯度很简单。一种方式是在展开计算路径图中简单地应用6.5.6节中的反向传播算法。不需要任何的修改。在展开的计算路径图中使用的反向传播称为BPTTback-propagation through time)算法。利用反向传播计算得到梯度后,这些梯度便可被用于任何基于梯度的一般目的的技术来训练RNN模型。

         为了获得一些关于BPTT算法的运行方式的直观认识,我们举一个例子来说明如何利用BPTT算法计算RNN方程(方程10.810.12)的梯度。我们的计算路径图中的节点包括参数U,V,W,bc以及和时间t有关的节点序列x(t),h(t),o(t)L(t)。对于每一个节点N,我们需要基于计算路径图中该节点后面的节点的梯度递归地计算梯度?NL。我们从紧邻最终损失函数的那些节点开始递归计算:


在这个求导计算中,我们假设将softmax函数作用于输出o(t),进而得到对于输出而言的概率向量                             ,另外,我们还假设所谓的损失是指截至到时间t,在给定了输入的情况下,真实值y(t)的负对数似然。时刻t在输出上的梯度?o( t)L对于所有i,t而言(译者注:io(t)向量的第i个元素),其计算公式为:



我们从序列的最后往前算。在最后一个时间点τ h(τ) 后面只接了o(τ),所以它的梯度很简单,即:


我们可以按照时间顺序逐步向前推,从t=τ-1t=1,这里应注意到此时h(t)(对于t<τ)后面接了两个值,分别是o(t)hi(t 1)。因此,它的梯度为


这里的指对角矩阵,该对角矩阵由元素组成。这是在时间t 1隐含单元i的双曲正切函数的Jacobian

         一旦我们得到了计算路径图中内部节点的梯度,我们就可以计算节点参数的梯度了。由于参数在很多时间点都是共享的,我们在表示涉及这些变量的微积分运算时必须小心。我们要用到6.5.6节中的BP算法,该算法计算了在计算路径图中单个边缘(single edge)对梯度的贡献。然而,操作?Wf在计算wf的贡献时,其贡献大小取决于计算路径图中所有的边缘。为了解决这种模糊性,我们引入了假想变量w(t),它表示了W在时间t时的一个拷贝。我们于是可以用?W(t ) 指代t时刻权重w对梯度的贡献。

         使用这一概念,其余参数的梯度计算方式如下:



训练过程中的梯度计算不需要考虑x(t),因为它在定义损失的计算路径图中没有作为上级输入值的任何参数(译者注:即没有任何节点指向它们)。

10.2.3将循环神经网络视为有向图模型

         到目前为止在我们的例子中构建的循环神经网络使用的损失函数是训练目标y(t)和输出o(t)的交叉熵。作为一个前馈网络,原则上我们可以在循环神经网络中使用几乎任何形式的损失函数。损失函数的选择取决于具体的任务。作为一个前馈网络,我们希望将RNN的输出解释成一种概率分布,而且我们通常使用和概率分布相关的交叉熵来定义损失。均方误差(Mean squared error)等价于与单位高斯分布的输出相关的交叉熵,例如,与前馈网络一样。

         当我们使用预测的对数似然作为训练目标时,如方程10.12,我们是在训练RNN去估计下一个序列元素y(t)相对于已给定的历史输入的条件概率分布。这可能就意味着我们在最大化对数似然


或者,如果模型中包含一个时刻的输出与下一个时刻的输出之间的连接,则对数似然为


获得关于整个序列的联合概率分布的一种方法是,将y值序列上的联合概率分解为一系列单个时间点的概率预测结果。当我们不把过去的y值作为输入来预测下一个时间点的值时,这个有向图模型就不包含从任何过去的y(i)到当前y(t)的边。在这种情况下,输出值y只取决于给定的x序列。当我们确实把真实的y值(是真正观察或产生的值,而不是关于他们的预测值)输入回网络时,有向图模型就包含了从所有过去的y(i)到当前y(t)的边。


作为一个简单的例子,让我们考虑这样一种情况,即RNN对一个标量随机变量序列建模,这里没有外部输入xt时刻的输入知识t-1时刻的输出。RNN于是便定义了一个关于变量y的有向图模型。我们用描述条件概率分布的链式法则(方程3.6)来描述这些观察值的联合概率分布。


当然,方程最右边的部分在t=1时是缺失的。因此,根据该模型,集合的负对数似然是



这里



图模型中的边缘表明了变量之间的直接依赖关系。许多图形模型旨在通过忽略弱相互作用的边来实现统计效率和计算效率。例如常见的马尔科夫假设,即y(t)只与前k个时间点的值

有关,而不是全部的历史值。然而,有些情况下,我们认为所有的历史输入都对序列的未来一个时间点的元素有一定的影响。如果我们认为y(t)的分布与之前某个时间点y(i)有关,而这种关系不能通过y(i)y(t-1)的影响得到确定,此时,RNN模型就变得很有用。

         RNN解释成一个图模型的一种方式是认为RNN定义了一种图模型,该图模型的结构是一个完全的图(complete graph),它可以表示任意两个y值得直接依赖关系。这种图模型如图10.7所示。当把隐含单元h(t)从模型中移除时,RNN就相当于一个完全的图。

         如果把隐含单元h(t)看成是随机变量的话,此时再来考察RNNs的图模型结构就会发现它的有趣之处。通过将隐含单元引入图模型,RNNs为观察值的联合概率分布提供了一种高效的参数化方法。假设我们用表格表示离散值得任意联合概率分布,这个表格是一个数组,其中的每一个元素表示每种可能情况(译者注:每种可能情况指所有的y值的所有可能的组合,例如序列长度是3,每个时间点的y值可能有2种取值,那么3y值合在一起形成的序列就一共有8种可能的序列,即23次方)出现的概率。如果yk种取值,那么这个表格表示方式就有O(kτ)个参数。通过比较,由于参数共享,RNN中的参数的数量是Ol),是序列长度的函数。可以调整RNN中参数的数量,而并不一定用序列长度来衡量。方程10.5展示了RNN能有效地参数化变量间的长期关系,这是通过在每一个时间点循环使用函数f和参数θ实现的。图10.8说明了图模型的解释。通过合并图模型中的h(t)节点来解耦过去和未来,这里,h(t)扮演了两者之间中间变量的角色。一个遥远的历史变量y(i)可以通过影响h来影响y(t)。该图中的结构说明,在每一个时间点使用相同的条件概率分布的方法可以高效地对模型进行参数化,此外,当所有变量都被观察到时,所有变量的联合概率可以很容易地计算出来。

         即便我们能对图模型进行高效的参数化,仍然存在一些计算上的挑战。例如,我们难以预测序列中间的缺失值。

         循环神经网络减少参数数量的代价是优化参数可能会很困难。

         循环神经网络中的参数共享是建立在一定的假设之上的,即在每一个时间点上都可以使用相同的参数。这个假设可以说成是:在给定了t时刻的变量后,t 1时刻的变量的条件概率分布是固定的,这意味着前一时刻和其下一时刻的关系与时间无关。原则上,可能会在每一个时间点使用时间t作为一个外部输入,这使得算法可以发现任何形式的时间依赖性,同时在不同的时间点之间尽可能多地共享这种时间依赖关系。这可能已经比在每一个时间点使用不同的条件概率分布要好很多了,但这样的网络在遇到新的时间t时不得不做外推。

         为了完善我们将RNN视为图模型的观点,我们必须讲清楚如何从模型中抽样的问题。我们需要执行的一个主要操作是简单地在每一个时间点从条件概率分布中采样。然而,这里还有一个难点。RNN必须有某种机制来确定序列的长度。这可以使用多种方法实现。

         当输出是从一个词汇表中取出的符号时,我们可以添加对应于序列末尾的一个特殊的符号。当这个符号产生后,采样过程也随之停止。在训练集中,我们将这个符号作为序列的外部成员插入紧随x(τ)之后的位置。

         另一种方法是向模型中引入一个额外的服从伯努利分布的输出,该输出在每一个时间点都做一次关于是继续产生还是停止产生新序列元素的决定。这种方法比向词汇表中添加额外符号的方法更具一般性,因为它可以应用于任何的RNN,而不只是那些产生符号序列的RNN。例如,该方法可以用于产生实数序列的RNN。新的输出单元通常是一个sigmoid单元,训练该单元时使用交叉熵作为损失函数。在这个方法中,训练sigmoid单元来最大化正确预测序列在每个时间点是否停止的对数概率。

         另一个确定序列长度τ的方法是在模型中增加一个额外的输出,该输出用于预测这个整数值τ。该模型可以采集一些τ的值然后再采集τ个步长的相应的数据。该方法在每一个时间点的循环更新处需要增加一个额外的输入以使得这个循环更新能够意识到是否已经处于产生的序列的末端附近。这个额外的输入或者由τ值组成,或者由τ-t值组成,τ-t即剩余的时间步长。如果没有这个额外的输入,RNN可能产生一个突然终止的序列,就好像一个没有说完的句子。该方法基于下面的分解:


直接预测τ的策略被用在了Goodfellow et al.2014d)提供的例子中。

10.2.4 RNNs用于基于上下文的序列建模

         在前面的部分我们介绍了RNN如何转换为一个随机变量y(t)的有向图模型,该有向图模型没有输入x。当然,我们在方程10.8中建立的RNNs是包含输入序列

的。一般情况下,当把RNNs视为图模型时,允许对其进行扩展,扩展后的模型不单单表示变量y之间的联合概率分布,也能表示y关于x的条件概率分布。正如6.2.1.1节中关于前向传播网络的讨论中所说的,任何表示变量的模型都可以被重新解释为一个表示条件概率分布的模型,其中w=θ。像之前利用的做法一样,我们可以扩展这个模型使其表示分布

,只不过此时的wx的一个函数。对于RNN而言,可以有很多种方法做到这一点。我们在此总结一些最显而易见的方法。

         之前我们讨论了以向量序列x(t)为输入的RNNs,其中t=1….,τ。另外一种选择是只以一个单独的向量x作为输入。当x是一个固定长度的向量时,我们可以简单地将其作为RNNs的额外输入用以产生序列y。一些向RNNs提供额外输入的常见方法是:

         1.在每一个时间点都作为一个额外输入;

         2.作为初始状态h(0)

         3.12的方法同时使用。

第一个也是最常见的方法如图10.9所示。


x和每个隐含单元h(t)通过参数R矩阵连接,这个R矩阵在仅有y值序列的模型中是没有的。在每一个时间点,每个隐含单元都有一个新的输入,即对于每一个隐含单元都可以视为一个有效的新的偏置参数,而我们可以把对x的选择看成是对

的调整。权重始终与输入独立。我们可以认为这个模型是采用了非条件模型的参数θ,并把他们转换为w,其中,w中的偏置现在是输入的一个函数。

         此外,RNN还可以接受向量x(t)的序列作为输入。方程10.8描述的RNN对应了一个条件概率分布

该条件概率分布做了条件独立性假设,即该分布可以写成


为了去掉条件独立性假设,我们在t时刻的输出与t 1时刻的隐含单元之间增加一条连接。如图10.10所示。于是这个模型就可以表示y序列的各种概率分布。这种在给定另一个序列的情况下表示一个序列的分布的模型仍然有局限性,这个局限是,这两个序列的长度必须相同。我们在10.4中会讲到如何去除这种局限。


当前浏览器不支持播放音乐或语音,请在微信或其他浏览器中播放 东方红 东方红合唱队 - 百年经典








    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多