深度学习之词向量

一、词向量

自上世纪90年代开始,特征空间模型就应用于分布式语言理解中,在当时许多模型用连续性的表征来表示词语,包括潜在语义分析LSA、隐含狄利克雷分布LDA主题模型。Bengio et al.在2003年首先提出了词向量的概念,当时是将其与语言模型的参数一并训练得到的。Collobert和Weston则第一次正式使用预训练的词向量,不仅将词向量方法作为处理下游任务的有效工具,还引入了神经网络模型结构,为目前许多方法的改进和提升奠定了基础。

词向量(word embedding)又称词嵌入,是自然语言处理NLP中一组语言建模和特征学习的统称,将词汇表的字或词从每个一维的高维空间映射到较低维连续空间,以便计算机进行处理及建模。

词向量是无监督学习少数几个成功的应用之一,优势在于不需要人工标注语料,直接使用未标注的文本训练集作为输入,输出的词向量可以用于下游业务的处理。

词向量用于迁移学习:

(1)使用大的语料库训练词向量(或网上下载预训练好的词向量);

(2)将词向量模型迁移到只有少量标注的训练集任务中;

(3)用新的数据微调词向量(如果新的数据集不大,则这一步不是必须的)。

最早的词向量使用one-hot representation,词向量的维数为整个词汇表的长度,对于每个词,将其对应词汇表中的位置置为1,其余维度都置为0。这种方法的缺点是:维度非常高,编码过于稀疏,易出现维数灾难问题;不能体现词与词之间的相似性,每个词都是孤立的,泛化能力差。

针对one-hot的两个问题,Hilton 1986年提出Distributed Representation,通过矩阵乘法或神经网络降维,将每个词映射为低维的密集词向量dense vector,把语义分散存储到向量的各个维度中。

神经网络将词汇表中的词作为输入,输出一个低维的向量表示,然后使用BP优化参数。生成词向量的神经网络模型分为两种:一种的目的是训练可以表示语义关系的词向量,能被用于后续任务中,如word2vec;另一种是将词向量作为副产品产生,根据特定任务需要训练得到词向量,如fastText。

矩阵乘法是,将待学习的特征矩阵E\in R^{300*10000}乘以词汇表(10000个)中每个词的one-hot编码,如词Mano^{5391}\in R^{10000*1},得到词向量e^{5391}\in R^{300*1}(一般不能像下图那样对词向量每个维度的含义做出解释)。

词向量可以捕捉单词的特征表示,从而实现类比推理 analogy reasoning,如e_{man}-e_{woman}\approx e_{king}-e_{queen}

计算向量相似度可以使用余弦相似度sim(u,v) = \frac{u^{T}v}{||u||_{2}||v||_{2}},或者用欧氏距离(较少),二者的区别在于normalize方式不同。使用t-SNE降维算法将高维数据300维非线性变换为2维从而实现可视化。

词向量类似人脸识别网络最后面的softmax层的特征 face encoding,区别在于图片的数据集可能是海量的,且可以识别未出现过的人脸;而词汇表是固定的,词嵌入也是固定的,未知的词用<UNK>代替。

注意:虽然词向量是神经网络的输入,但并非第一层输入。第一层是词的one-hot编码,乘以一个权重矩阵后得到才是词向量化表示,而权重在模型训练阶段是可以更新的。从记忆的角度来看,神经网络的连接权值更新是长时记忆,因此词向量的学习可以认为是一种长时学习。

 

二、语言模型

神经网络语言模型NNLM是统计学意义上的模型,需要求句子的联合概率,模型参数使用极大似然估计得到:对于某个语料库,估计哪个语言模型(什么样的参数)最有可能产生这个语料库,将这个问题分解成许多个小的概率计算的问题,对语料库中所有的词做相同的计数和除法,解出需要的参数,即可得到这个语料库的语言模型。

语言模型在NLP中有重要的地位,在语音识别、机器翻译、自动分词和句法分析等方面有广泛的应用,因为这些模型都会有噪声、有不同的选择,这时就需要知道每种结果的概率,从而做出正确的选择。

词向量模型与语言模型的关系密切,语言模型质量的评估基于其对词概率分布的表征能力。语言模型可以计算任何句子/序列的概率,对于一个合理的句子,语言模型能够给出一个较大的概率;对于一个不合理的句子则给出较小的概率。对于一个有m个词的句子,其联合概率为:

       P(w_{1}, w_{2}, ..., w_{m})=P(w_{1})P(w_{2}|w_{1})...P({w_{m}|w_{1}, w_{2}, ..., w_{m-1})

直接计算条件概率面临2个重要的问题:参数空间过大、数据过于稀疏,因此引入马尔科夫假设。设语料库中总字数为M,c(w_{1}, w_{2}, ..., w_{m})为n-gramw_{1}, w_{2}, ..., w_{m}在语料库中出现的次数,对于n元语言模型n-gram model,根据一个词前面的n-1个词,计算这个词的条件概率:

       P(w_{i}|w_{1}, w_{2}, ..., w_{i-1})=P({w_{i}|w_{i-n+1}, ..., w_{i-1})

n=1,unigram model:

       P(w_{1}, w_{2}, ..., w_{m})=\prod_{i=1}^{m}P(w_{i})               P(w_{i})=\frac{C(w_{i})}{M}

n=2,bigram model:

       P(w_{1}, w_{2}, ..., w_{m})=\prod_{i=1}^{m}P(w_{i}|w_{i-1})               P(w_{i}|w_{i-1})=\frac{C(w_{i},w_{i-1})}{C(w_{i-1})}

n=3,trigram model:

       P(w_{1}, w_{2}, ..., w_{m})=\prod_{i=1}^{m}P(w_{i}|w_{i-1}, w_{i-2})               P(w_{i}|w_{i-1},w_{i-2})=\frac{C(w_{i},w_{i-1},w_{i-2})}{C(w_{i-1},w_{i-2})}

假设对一个语料库统计,得到下面若干词出现的次数为

       

基于bigram模型计数得到表格,并根据上面统计得到下表

标准化后得到频率分布

 

假设已知概率 P(i|<s>)=0.25,P(</s>|food)=0.6,那么就可以计算句子<s>i want chinese food</s>的概率为:

P(i|<s>)P(want|i)P(chinese|want)P(food|chinese)P(</s>|food) = 0.25*0.33*0.0065*0.52*0.6

为了避免数据溢出、提高性能,通常会对概率取对数后使用加法运算替代乘法运算。

如何选择依赖词的个数n?

更大的n:对下一个词出现的约束信息更多,具有更大的辨别力;

更小的n:在训练语料库中出现的次数更多,具有更可靠的统计信息,具有更高的可靠性。

理论上,n越大越好;经验上,trigram用的最多。尽管如此,原则上能用bigram解决的问题,绝不用trigram(很多词的条件概率为0,数据过于稀疏)。

语言模型评价方法:preplexity(迷惑度/困惑度/混乱度),基本思想是给测试集赋予较高概率值的语言模型较好,迷惑度越小、句子概率越大,语言模型越好。

       PP(W)=P(w_{1}, w_{2}, ..., w_{m})^{-\frac{1}{m}}

       \mathrm{Chain \: rule:}\; \; PP(W)=\sqrt[m]{\prod_{i=1}^{m}\frac{1}{P(w_{i}|w_{1}, w_{2}, ..., w_{i-1})}}

NNLM结构:

(1)输入层,使用特征矩阵获得每一个词的分布式表示;

(2)投影层,将n-1个上下文的词向量拼接;

(3)隐藏层,即全连接层;

(4)输出层,使用softmax对P(wi|context)进行分类,类别是所有词的id。

BP时不仅更新输出层、隐藏层的权重,还需要词向量。

优点:使用NNLM模型生成的词向量是可以自定义维度的,维度并不会因为新扩展词而发生改变,而且这里生成的词向量能够很好的根据特征距离度量词与词之间的相似性。

缺点:计算复杂度过大,参数较多。

 

三、CBOW、Skip-gram

词向量的真正推广源于Google在2013年推出的word2vec工具,可以比之前的方法更快地训练词向量模型。word2vec是一种将词表征为实数值向量的高效算法模型,利用深度学习的思想,使用Distributed Representation的词向量表示方式,通过训练将文本处理为 K 维空间中的向量,向量的相似度可以表示文本语义的相似度。

word2vec模型有两种实现方式

(1)CBOW(Continuous Bag of Words):以一个词的上下文作为输入,预测这个词本身。

输入层:输入词w的上下文 ;

投影层:将输入的向量进行求和 ;

输出层:为Huffman树,以语料中出现过的词作为叶子结点,以各词在语料中出现的次数为权重。假设词汇表V中的词有N个,则树中有叶子结点N个,非叶子结点N-1个(黄色结点)。

目标函数:L=\sum_{w \in V}\log P(w|Context(w))

 

(2)Skip-gram:以一个词作为输入,预测它的上下文。

结构类似CBOW,其中投影层可有可无,因为词w在投影层的加和仍是它本身。

目标函数:L=\sum_{w \in V}\log P(Context(w)|w)

 

对于句子 Hangzhou is a nice city 构造语境与目标词汇的映射关系,即input与label的关系,假设滑动窗口尺寸为1

CBOW的映射关系为:[Hangzhou,a]—>is,[is,nice]—>a,[a,city]—>nice

Skip-Gram的映射关系为:(is,Hangzhou),(is,a),(a,is), (a,nice),(nice,a),(nice,city)

这两个模型互为镜像,CBOW适合小型语料,而Skip-Gram在大型语料中表现更好。

 对比神经概率语言模型NNLM,二者区别在于:

        a. NNLM拼接输入的词向量,word2vec求和后取平均;

        b. NNLM有一个隐藏层,word2vec变为投影层;

        c. NNLM输出层为线性结构,word2vec为树形结构。

由对比可知,word2vec针对NNLM隐藏层输出层之间的矩阵运算、以及输出层的softmax运算这些计算密集的地方进行了改变,输出层改用Huffman树,根据词频使用Huffman编码 ,使得出现频率越高的词激活的隐藏层数越少,这样可以有效降低计算复杂度,从而为利用Hierarchical softmax技术奠定了基础。

 

Huffman树、Huffman编码

Huffman树又称最优二叉树(有序),是带权路径最短的树,权值(词频)较大的结点离根结点较近。带权路径长度,指树中所有叶结点权值乘以其到根结点的路径长度。

假设有n个权值w1, w2, ..., wn,构造有n个叶子结点、n-1个非叶子结点的Huffman树:

(1)将w1, w2, ..., wn 看成是有n棵树的森林(每棵树仅有一个结点);

(2)将两棵权值最小的树,作为左右子树合并成一棵新树,新树的根结点权值为左右子树的权值和;

(3)从森林中删除上一步选择的两棵树,将合成的新树加入森林;

(4)重复上面两步,直到森林中只剩一棵树为止,即可得到Huffman树。

Huffman编码使用变长编码表对字符进行编码,出现几率高的字符使用较短的编码,反之使用较长的编码。为了使不等长编码为前缀编码,即要求一个字符的编码不能是另一个字符编码的前缀,用每个字符作为叶子结点生成一棵Huffman树,字符出现的频率作为结点权值。Huffman编码后的字符串平均长度最短,可以无损压缩数据。

假设约定词频较大的左结点编码为1,词频较小的右结点编码为0,则:我、喜欢、观看、巴西、足球、世界杯 这6个词的Huffman编码分别为:0、111、110、101、1001、1000

 

Hierarchical Softmax

Huffman树的根结点对应投影层的词向量,内部结点类似神经网络隐藏层的神经元,叶子结点类似softmax输出层的神经元,个数等于词汇表的总词数。由于从投影层到输出层的softmax映射是沿着Huffman树一步步完成的,因此称为 Hierarchical Softmax。

word2vec使用sigmoid函数判别正类或负类,规定左子树为负类(编码1),右子树为正类(编码0)。在某一个内部结点,判断路径是沿左子树还是右子树走的标准就是看哪一边的概率更大,影响因素为输入词向量和当前结点的参数θ。

对于词汇表中任意词w,Huffman树中必定存在唯一条从根结点到词w对应叶子结点的路径p_{w},该路径上有l_{w}-1 个分支;w经过输入层求和平均后得到根结点词向量x_{w},第j个结点对应的Huffman编码为d_{j}^{w} \in \left \{ 0,1 \right \}j=2,3,...,l_{w},对应的参数(不包含叶结点)为\theta _{j}^{w}j=1,2,...,l_{w}-1

定义w经过结点j的逻辑回归概率为:

        P(d_{j}^{w}|x_{w},\theta_{j-1}^{w})=\left\{\begin{matrix} \sigma (x_{w}^{T}\theta_{j-1}^{w}) & d_{j}^{w}=0 \\\\ 1-\sigma (x_{w}^{T}\theta_{j-1}^{w}) & d_{j}^{w}=1 \end{matrix}\right.

        P(d_{j}^{w}|x_{w},\theta_{j-1}^{w})=\sigma (x_{w}^{T}\theta_{j-1}^{w}) ^{1-d_{j}^{w}}\cdot (1-\sigma (x_{w}^{T}\theta_{j-1}^{w}) ) ^{d_{j}^{w}}

对于某一个目标输出词w,其对数似然为:

        L(w,j)=\log \prod _{j=2}^{l_{w}} P(d_{j}^{w}|x_{w},\theta_{j-1}^{w})

        =\sum _{j=2}^{l_{w}} [(1-d_{j}^{w})\log \sigma (x_{w}^{T}\theta_{j-1}^{w}) +d_{j}^{w}\log(1-\sigma (x_{w}^{T}\theta_{j-1}^{w}) ) ]

word2vec使用随机梯度上升方法,每次只用一个样本(Context(w),w)更新梯度,即似然L分别对x_{w} 和\theta _{j}^{w} 求导:

        \frac{\partial L(w,j)}{\partial \theta _{j-1}^{w}}\\\\=\frac{\partial }{\partial \theta _{j-1}^{w}} [(1-d_{j}^{w})\log \sigma (x_{w}^{T}\theta_{j-1}^{w}) +d_{j}^{w}\log(1-\sigma (x_{w}^{T}\theta_{j-1}^{w}) ) ]\\\\=(1-d_{j}^{w})[1-\sigma (x_{w}^{T}\theta_{j-1}^{w})]x_{w}-d_{j}^{w}\sigma (x_{w}^{T}\theta_{j-1}^{w})x_{w}\\\\=[1-d_{j}^{w}-\sigma (x_{w}^{T}\theta_{j-1}^{w})]x_{w}

        \frac{\partial L(w,j)}{\partial x_{w}}=[1-d_{j}^{w}-\sigma (x_{w}^{T}\theta_{j-1}^{w})]\theta _{j-1}^{w}

使用梯度表达式,通过梯度上升方法更新x_{w} 和\theta _{j}^{w}\eta 为学习率。由于CBOW模型的投影层是对w周围2c个词向量求和取平均,梯度更新完毕后会用梯度项直接更新原始的各个x_{i},i=1, 2, ..., 2c:

        \theta _{j-1}^{w}:=\theta _{j-1}^{w}+\eta[1-d_{j}^{w}-\sigma (x_{w}^{T}\theta_{j-1}^{w})]x_{w}

        x_{i}:=x_{i}+\eta\sum_{j=2}^{l_{w}}[1-d_{j}^{w}-\sigma (x_{w}^{T}\theta_{j-1}^{w})]\theta _{j-1}^{w}

基于Hierarchical Softmax的CBOW / Skip-gram模型伪代码

输入:基于CBOW / Skip-gram的语料训练样本,词向量的维度,上下文大小2c,学习率η。

输出:Huffman树的内部节点模型参数θ,所有的词向量w。

a. 基于语料训练样本建立Huffman树;

b. 随机初始化所有的模型参数θ、所有的词向量w;

c. 随机梯度上升迭代,对训练集中的每一个样本(context(w),w) / (w, context(w)) 做如下处理:

注意:3和4不能交换顺序,即θ应等贡献到e后再做更新。

 

Negative Sampling (NEG)

一种更简单的word2vec求解方式,能够提高训练速度并改善所得词向量的质量,是NCE(Noise Contrastive Estimation)的简化版。NEG不再使用Huffman树,而是使用随机负采样方法,假设中心词w_{0} 及其周围上下文context(w_{0}) 作为正例(y_{0}=1),通过负采样得到neg个和w_{0} 不同的中心词w_{i}, (y_{i}=0, i=1, 2, .., neg),这样 context(w_{0}) 和w_{i} 就组成了neg个负例。使用这个正例和neg个负例进行二元逻辑回归,更新每个词w_{i} 对应的模型参数以及词向量。

        P(context(w_{0}),w_{i})=\left\{\begin{matrix} \sigma (x_{w0}^{T}\theta^{wi}), & y_{i}=1,i=0 \\\\ 1-\sigma (x_{w0}^{T}\theta^{wi}), & y_{i}=0,i=1,2,...,neg \end{matrix}\right.

为了增大正例的概率同时减小负例的概率,需要最大化对数似然函数:

        L=\sum_{i=0}^{neg} [y_{i}\log \sigma (x_{w0}^{T}\theta^{wi})+(1-y_{i})\log(1-\sigma (x_{w0}^{T}\theta^{wi}))]

类似Hierarchical Softmax使用随机梯度上升法,每次只用一个样本更新梯度,迭代更新 x_{w0}\theta ^{wi}

        \frac{\partial L}{\partial \theta ^{wi}}=y_{i}(1-\sigma (x_{w0}^{T}\theta^{wi}))x_{w0}-(1-y_{i})\sigma (x_{w0}^{T}\theta^{wi})x_{w0}=(y_{i}-\sigma (x_{w0}^{T}\theta^{wi}))x_{w0}

        \frac{\partial L}{\partial x_{w0}}=\sum_{i=0}^{neg}(y_{i}-\sigma (x_{w0}^{T}\theta^{wi}))\theta^{wi}

负采样算法

为了得到neg个负例,需要进行带权采样。设词汇表的大小为V,将一段长度为1的单位线段分成V份,每份对应词汇表中的一个词,高频词对应的线段长,低频词对应的线段短。词汇表中每个词的线段长度为其在语料库中出现的次数,与词汇表中所有词在语料库中出现的次数总和之比:

        \mathrm{len} (w)=\frac{\mathrm{count}(w)}{\sum_{u \in V}\mathrm{count}(u)}

在word2vec中,分子和分母都取了3/4次幂,这是考虑到既不让经常出现的高频词权重过大,也不让低频词权重过小。neg的取值范围与数据集的大小有关,对于较大的数据集,neg的范围为2~5;对于较小的数据集,范围为5~20。

        \mathrm{len} (w)=\frac{\mathrm{count}(w)^{\frac{3}{4}}}{\sum_{u \in V}\mathrm{count}(u)^{\frac{3}{4}}}

将长度为1的线段划分成M等份,这里M>>V(M默认值为10^8),这样每个词对应的线段都会划分成对应的小块。在采样时只需要从M个位置中采样出neg个位置,得到的每一个位置对应线段所属的词就是负例词。

基于Negative Sampling的CBOW / Skip-gram模型伪代码

 

若干源码细节

1)sigmoid函数近似计算

由sigmoid的图形可知,函数在x=0附近y值变化较大, 而在x<-6或x>6以外的区域y值基本不变,前者趋于0,后者趋于1。因此在对精度要求不高的情况下,可以使用近似计算的方法,将区间[-6, 6]等分为K份,剖分节点为 x_{0},x_{1},...,x_{K},其中x_{0}=-6x_{i}=x_{0}+ih,步长h=12/K

事先将K个sigmoid函数的值计算好并保存起来,使用sigmoid函数时,采用如下近似公式:

        \sigma(x)\approx \left\{\begin{matrix} 0, & x\leqslant -6 \\\\ \sigma (x_{k}), & x \in (-6,6) \\\\1, & x\geqslant 6 \end{matrix}\right.

其中k=(x-x0)/h,向上或向下取整均可,x_{k}表示与x距离最近的剖分节点。

 

2)词汇表的存储

词汇表通过哈希技术存储,首先设一个长度为vocab_hash_size(默认值为3*10^7)的整型数组,vocab_hash,并将每个分量初始化为-1,然后为词汇表中的词建立如下映射:

        \mathrm{vocab\_hash}[\mathrm{hv}(w_{j})]=j

其中 \mathrm{hv}(w_{j}) 表示词w_{j} 根据某个公式计算得到的哈希值,当出现 \mathrm{hv}(w_{i})=\mathrm{hv}(w_{j}),\; \; i\neq j 时,采用线性探测的开放定址法来解决冲突,顺序往下查找,直到找到一个未被占用的位置(若已到数组末尾,则从头开始查找)。

 

3)低频词处理

使用语料库建立词汇表时,并不是每个出现过的词都能被收录到词汇表中。代码中引入了阈值参数min_count(默认值为5),若某个词在语料库中出现的次数小于阈值,则将其从词汇表中删除。

为了提高效率,根据词汇表当前的规模来决定是否需要对低频词进行清理,做法是:预先设定阈值参数min_reduce(默认值为1),如果当前词汇表的规模满足 \left | V_{\mathrm{current}} \right |>0.7*\mathrm{vocab\_hash\_size},则从词汇表中删除所有出现次数小于等于min_reduce的词。

 

4)高频词处理

对于常见的且提供有用信息很少词,如“的”,“了”等,使用subsampling技巧提高训练速度及词向量精度,做法是:给定一个词频阈值参数t,词w将以prob的概率被舍弃,f(w)为w的频率。

        \mathrm{prob}(w)=1-\sqrt{\frac{t}{f(w)}}

        f(w)=\frac{\mathrm{counter}(w)}{\sum _{u \in V}\mathrm{counter}(u)},w \in V

word2vec源码中实际使用的公式是:

        \mathrm{prob}(w)=1-\left (\sqrt{\frac{t}{f(w)}}+\frac{t}{f(w)} \right )

5)窗口及上下文

word2vec中事先设置一个窗口阈值参数window(默认值为5),每次构建context(w)前,先生成一个区间[1, window]上的随机整数c,然后取w前后各c个词即可构成context(w)。

 

6)自适应学习率

设初始学习率\eta _{0}(默认值为0.025),每处理10000个词(个数可根据经验调整)后调整学习率:

        \eta =\eta_{0}\left (1-\frac{\mathrm{word\_count\_actual}}{\mathrm{train\_words}+1} \right )        \mathrm{train\_words}=\sum_{w \in V}\mathrm{counter}(w)

其中word_count_actual表示当前已处理过的词个数,+1是为了防止分母为零。此外为了防止学习率过小,设置阈值学习率 \eta_{min}(=10^{-4}*\eta_{0}),一旦\eta 小于阈值,则固定学习率为阈值学习率。

 

7)参数初始化与训练

模型训练采用随机梯度上升法,且只对语料遍历一次,这也是其高效的原因之一。

模型需要训练的参数包括逻辑回归对应的参数向量,以及词汇表中每个词的词向量;前者采用全零初始化,后者采用[-0.5/m, 0.5/m]区间上的随机初始化,m为词向量的长度,具体公式为:

        \frac{(\mathrm{rand}()/\mathrm{RAND\_MAX})-0.5}{m}

word2vec源码中syn0,syn1和syn1neg这三个一维数组,分布对应Huffman树中所有叶子结点的词向量,非叶子结点的参数向量,以及基于负采样模型中与词相关的参数向量。

 

 

四、Glove

GloVe是基于全局词频统计(count-based & overall statistics)的词表征工具,不同于局部上下文建模的word2vec、文档和词共现矩阵分解的LSA,Glove计算简单,认为相比单词同时出现的概率,单词同时出现的概率的比率能够更好地区分单词。

Glove的目标函数为加权最小二乘回归模型,输入为词-上下文同时出现频次矩阵,Xij为词i在词j上下文中出现的次数,如果目标词和上下文是定义在左右各c个词以内的范围,则Xij=Xji;如果定义上下文总是在目标词前一个,则Xij和Xji就不是对称的。当Xij=0时权值函数f(Xij)=0(约定0log0=0),f(Xij)能对不太常见的词进行有意义的运算,也能给出现频繁的词较大但不至于过分的权重。

L=\sum_{i,j=1}^{V}f(X_{ij})(w_{i}^{T}\tilde{w}_{j}+b_{i}+\tilde{b}_{j}-\log X_{ij})^{2}

w_{i}^{T}\tilde{w}_{j}相当于之前的\theta和e,且它们是对称的。因此一种训练方法可以是,一致地初始化二者,梯度下降训练后最终的词向量取它们的平均和。

 

在某些场景下,Glove的表现优于Word2Vec。

 

 

参考资料

吴恩达 序列模型

https://blog.csdn.net/yaoweijiao/article/details/52945186

https://www.cnblogs.com/peghoty/p/3857839.html

https://www.cnblogs.com/pinard/p/7243513.html

http://www.fanyeong.com/2018/02/19/glove-in-detail/

 


版权声明:本文为liuy9803原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。