word2vec之CBOW模型与skip-gram模型

在对自然语言进行处理时,首先需要面对文本单元表示问题。单词(words)作为常考虑的最小文本单元,因而,如何将单词表示成恰当的词向量(word vector)成为了研究者们研究的重点。最简单直观的方法是one-hot representation,也称1-of-N representation,这种方式将每个单词表示成一个词汇表(vocabulary)大小的向量,其中绝大部分元素都是0,只有一个维度的值是1,这个维度就代表了当前的词。如:我们可以将词汇表中所有的词按照字母顺序排序,每个单词对应的one-hot representation中,只有第“其在词汇表中的索引(序号)”维的值为1,其他维都是0。假设我们有“King,Queen,Man,Woman,Child”5个单词组成的词汇表,“Queen”在词汇表中的序号是4,那么其对应的词向量就是 ( 0 , 0 , 0 , 1 , 0 ) (0,0,0,1,0) (0,0,0,1,0)

这种表示方式简单,鲁棒性好,但缺点同样明显:1)没有考虑单词之间的语法/语义相似性,因为one-hot representation中每个词向量都是相互正交的;2)因为我们的词汇表一般都很大,达到十万级别甚至更大,因而每个单词的维度都是万维以上,不仅效率低,而且是内存的灾难。
那能不能想办法将词向量的维度变小呢?

Distributed representation

Distributed representation表示的概念最早由Hinton在1986年提出,其思想是通过训练,将每个单词映射到一个较短的连续的向量上来,每一维代表词向量空间中的特定一维,进而可以通过普通的线性代数操作来研究单词之间的关系。单词的distributed representation方法包括LSA(Latent Semantic Analysis)、LDA(Latent Dirichlet Allocation)以及NNLM(neural network language model)。这里着重介绍下NNLM,对于语料 D \mathcal{D} D中的任意一个词 w w w,我们给定其前面的 n − 1 n-1 n1个单词 C o n t e x t ( w ) Context(w) Context(w),预测下一个单词是 w w w的概率,二元对 ( C o n t e x t ( w ) , w ) (Context(w),w) (Context(w),w)就是一个训练样本,在采用神经网络训练语言模型时,也会生成副产品——词向量(其实,NNLM中,词向量和语言模型通常是捆绑在一起联合训练的,网络训练完成后同时得到两者)。

该模型结构包含输入、映射、隐藏和输出层,在输入层中, N N N个前序词被编码成1-of- V V V向量,其中, V V V是词汇表的大小。接着,输入层通过一个共享的映射矩阵被映射到映射层,其维度是 N ∗ D N*D ND,然后将映射层经过隐藏层,最后将隐藏层输出作为输出层输入得到最终输出,表示词汇表中每个单词作为当前词的概率。注意,从输入层到映射层是将输入层的每个单词经过投影矩阵变化成 D D D维向量然后首尾拼接起来,形成一个 N ∗ D N*D ND维的长向量,接下来的计算与一般的神经网络计算相同
{ Z w = σ ( W X w + p ) y w = U Z w + q \begin{cases} Z_w=\sigma(WX_w+p)\\ y_w=UZ_w+q \end{cases} {Zw=σ(WXw+p)yw=UZw+q

输出层输出 y w = ( y w , 1 , y w , 2 , ⋯   , y w , V ) T y_w=(y_{w,1},y_{w,2},\cdots,y_{w,V})^T yw=(yw,1,yw,2,,yw,V)T是长度为 V V V的向量,其分量还不能表示概率,需要将其经过softmax归一化,
p ( w ∣ C o n t e x t ( w ) ) = e y w , i ∑ i = 1 V e y w , i p(w|Context(w))=\frac{e^{y_{w,i}}}{\sum_{i=1}^Ve^{y_{w,i}}} p(wContext(w))=i=1Veyw,ieyw,i

假设隐藏层神经元数目为 H H H,则其计算复杂度为
Q = N ∗ D + N ∗ D ∗ H + H ∗ V Q=N*D+N*D*H+H*V Q=ND+NDH+HV

其中, H ∗ V H*V HV占主导地位,但是,一些解决方案被提出用来避免其的计算量,例如:层次化softmax(Hierarchical Softmax),或在训练中使用未归一化的模型。采用二叉树表示词汇表,可以将需要计算的输出单元数降到 l o g 2 ( V ) log_2(V) log2(V)。因而,大部分计算量由项 N ∗ D ∗ H N*D*H NDH造成。

Word2Vec

NNLM模型的主要计算量集中在映射层到隐藏层和隐藏层到输出层的计算中,word2vec也是针对此进行优化的。首先,word2vec在词汇表的Huffman树表示上采用层次化softmax,Huffman树给频繁出现的单词分配短的二进制编码,这大大减少了需要计算的输出单元的数目:平衡二叉树需要对 l o g 2 ( V ) log_2(V) log2(V)的输出进行计算,而基于Huffman树的层次化softmax只需计算 l o g 2 ( U n i g r a m p e r p l e x i t y ( V ) ) log_2(Unigram_perplexity(V)) log2(Unigramperplexity(V))的输出。接下来需要解决项 N ∗ D ∗ H N*D*H NDH带来的计算瓶颈,word2vec提出了无隐藏层的网络结构。

Huffman树

在介绍word2vec的结构之前,需要介绍下word2vec用到的一类重要数据结构——Huffman树。给定 n n n个权值作为作为 n n n个叶子节点,构造一棵二叉树,若它的带权路径长度最小,则称这样的二叉树为最优二叉树,也称Huffman树。给定 n n n个权值为 { w 1 , w 2 , ⋯   , w n } \{w_1,w_2,\cdots,w_n\} {w1,w2,,wn}的数据作为二叉树的叶子节点,可通过以下算法构造一颗Huffman树:

  1. { w 1 , w 2 , ⋯   , w n } \{w_1,w_2,\cdots,w_n\} {w1,w2,,wn}看成是有 n n n棵树的森林(每棵树仅一个节点)。
  2. 在森林中选出选出两棵根节点权值最小的的树合并,分别作为新树的左、右子树,且新树根节点的权值为其左、右子树根节点的权值之和。
  3. 从森林中删除选取的两棵树,并将新树加入森林。
  4. 重复2、3步,直至森林中只剩下一棵树,该树即为所要构造的Huffman树。

例如:有{“我”,“喜欢”,“观看”,“巴西”,“足球”,“世界杯”}六个单词其词频为{15,8,6,5,3,1},以这6个单词为叶子节点,相应词频为权值构造Huffman树的过程如下:

在上述Huffman树构造过程中,规定权值较高的节点作为左节点,权值较低的节点作为右节点(这只是一个约定,也可以反过来规定)。由最终的Huffman树可见,Huffman树中,权值越大的节点距离根节点越近。Huffman树的一个应用是Huffman编码,在数据通信中,需要将传输的字符串转换成二进制字符串,用0,1码的不同排列表示字符。例如:要传输的字符串是“AFTERDATAEARAREARTAREA”,其包含的字符集为“A,E,R,T,F,D”,要区分这6个字母,最简单的方式是采用等长二进制编码,需要3( 2 3 ≥ 6 2^3\geq6 236)位二进制,可分别用000,001,010,011,100,101对“A,E,R,T,F,D”进行编码,因而编码“AFTERDATAEARAREARTAREA”需要22*3=66 bits的空间。可以预见,随着字符集的增长,对每个字符进行编码的二进制码也会随之增长,将占用越来越多的空间,然而,通信传输中总是希望总长度越小越好。因为,报文中每个字符对应的频率有高有低,一个很自然的想法是让频率高的字符编码尽可能短,以优化整个报文编码。
Huffman树中,权值越大的节点距离根节点越近,即路径越短,这和我们想要达到的编码效果正好一致,因此可用字符集中每个字符为叶子节点生成一棵二叉编码树。还是以上面{“我”:15,“喜欢”:8,“观看”:6,“巴西”:5,“足球”:3,“世界杯”:1}6个单词为例,在构造好Huffman树后,约定左孩子节点(权值较大的)编码为1,右孩子节点(权值较小的)编码为0

如上图,“我”,“喜欢”,“观看”,“巴西”,“足球”,“世界杯”这6个单词的Huffman编码分别为0,111,110,101,1001和1000。同样的,在上述编码中,我们约定了左孩子节点编码为1,右孩子节点编码为0。以上两条约定都是遵循word2vec中的约定,即word2vec中,约定左孩子的权重大于右孩子的权重,左孩子编码为1,右孩子编码为0

CBOW模型

word2vec中包含两类模型结构,分别是Continuous Bag-of-Words模型和Continuous Skip-gram模型,这两个模型都没有非线性的隐藏层,由输入层映射层输出层三层构成。首先介绍CBOW模型,其思想是在已知当前词 w t w_t wt上下文 w t − 2 , w t − 1 , w t + 1 , w t + 2 w_{t-2},w_{t-1},w_{t+1},w_{t+2} wt2,wt1,wt+1,wt+2的前提下预测当前词是 w t w_t wt的概率,在输出层计算概率时其采用了Hierarchical Softmax技术。与NNLM模型还存在另外一个不同之处:输入层单词经过映射矩阵得到的 D D D维词向量累加构成映射层,而不是首尾相连,将其称为bag-of-words模型是因为上下文词的顺序不影响映射层,下图左边展示了CBOW模型的结构

其计算量为
Q = N ∗ D + D ∗ l o g 2 ( V ) Q=N*D+D*log_2(V) Q=ND+Dlog2(V)

训练过程

下面以{“我”:15,“喜欢”:8,“观看”:6,“巴西”:5,“足球”:3,“世界杯”:1}为词汇表,计算 p ( w ∣ C o n t e x t ( w ) ) p(w|Context(w)) p(wContext(w)),详细讲解CBOW的计算过程。假设 C o n t e x t ( w ) Context(w) Context(w) w w w前后各 c \mathcal{c} c个词构成,细化了的网络结构如下图所示

对需要使用的记号作如下规定:
1、 p w p^w pw:从根节点出发到达 w w w对应叶子节点的路径;
2、 l w l^w lw:路径 p w p^w pw中包含的节点个数;
3、 p 1 w , p 2 w , ⋯   , p l w w p^w_1,p^w_2,\cdots,p^w_{l^w} p1w,p2w,,plww:路径 p w p^w pw中的 l w l^w lw个节点,其中 p 1 w p_1^w p1w表示根节点, p l w w p_{l^w}^w plww表示 w w w对应的节点;
4、 d 2 w , d 3 w , ⋯   , d l w w ∈ { 0 , 1 } d_2^w,d_3^w,\cdots,d_{l^w}^w\in \{0,1\} d2w,d3w,,dlww{0,1}:词 w w w的Huffman编码的每一位,由 l w − 1 l^w-1 lw1位编码构成, d j w d_j^w djw表示路径 p w p^w pw中第 j j j个节点对应的编码(根节点不对应编码);
5、 θ 1 w , θ 2 w , ⋯   , θ l w − 1 w ∈ R m \theta_1^w,\theta_2^w,\cdots,\theta_{l^w-1}^w\in \R^m θ1w,θ2w,,θlw1wRm:路径 p w p^w pw中非叶子节点对应的参数向量, θ j w \theta_j^w θjw表示路径 p w p^w pw中第 j j j个非叶子节点对应的参数向量。

在给出语料 D \mathcal{D} D中的任意词 w w w,在给定其上下文的情况下,计算当前词是 w w w的概率,对于该目标,我们可以将目标函数设定为对数似然函数
L = ∑ w ∈ C log ⁡ p ( w ∣ C o n t e x t ( w ) ) \mathcal{L}=\sum_{w\in\mathcal{C}}\log p(w|Context(w)) L=wClogp(wContext(w))

其中, C \mathcal{C} C是词汇表,现在的关键是根据网络结构给出条件概率 p ( w ∣ C o n t e x t ( w ) ) p(w|Context(w)) p(wContext(w))的计算。我们已经知道映射层输出 X w X^w Xw与词汇表对应的Huffman树,那么,问题就转化为,如何利用 X w X^w Xw以及Huffman树定义函数 p ( w ∣ C o n t e x t ( w ) ) p(w|Context(w)) p(wContext(w))
假设词 w w w=“足球”,以求 p ( 足 球 ∣ C o n t e x t ( 足 球 ) ) p(足球|Context(足球)) p(Context())为例。上图中标红的线给出了从根节点到“足球”叶子节点的路径 p w p^w pw,其长度 l w = 5 l^w=5 lw=5 p 1 w , p 2 w , p 3 w , p 4 w , p 5 w p_1^w,p_2^w,p_3^w,p_4^w,p_5^w p1w,p2w,p3w,p4w,p5w是路径 p w p^w pw上的5个节点。 d 2 w , d 3 w , d 4 w , d 5 w d_2^w,d_3^w,d_4^w,d_5^w d2w,d3w,d4w,d5w分别是1,0,0,1,因此“足球”的Huffman编码是1001。 θ 1 w , θ 2 w , θ 3 w , θ 4 w \theta_1^w,\theta_2^w,\theta_3^w,\theta_4^w θ1w,θ2w,θ3w,θ4w是路径 p w p^w pw上4个非叶子节点对应的参数向量,其值通过训练得到。可以看到,从根节点到叶子节点“足球”共经历4次分支,每次分支可以看作是一次二分类(是选择左孩子代表的类别,还是右孩子代表的类别)。刚好Huffman编码时,给每个非根节点都指定一个0/1的编码,可以将其看作是该节点的类别。因此,最自然的做法是将Huffman编码为1的节点定义为正类,编码为0的节点编码为负类。当然也可以反过来,将编码为1的节点定义为负类,而将编码为0的节点定义为正类,word2vec中正是这么规定的。即
L a b e l ( p i w ) = 1 − d i w , i = 2 , 3 , ⋯   , l w Label(p_i^w)=1-d_i^w,i=2,3,\cdots,l^w Label(piw)=1diw,i=2,3,,lw

每一次分类过程可以采用简单的Logistic回归完成,则一个节点被分为正类的概率是
σ ( X w T θ ) = 1 1 + e − X w T θ \sigma(X_w^T\theta)=\frac{1}{1+e^{-X_w^T\theta}} σ(XwTθ)=1+eXwTθ1

因此,从根节点到叶子节点“足球”经历的4次二分类,每次分类结果的概率如下
1、第一次,选择左孩子,即负类,则 p ( d 2 w ∣ X w , θ 1 w ) = 1 − σ ( X w T θ 1 w ) p(d_2^w|X_w,\theta_1^w)=1-\sigma(X_w^T\theta_1^w) p(d2wXw,θ1w)=1σ(XwTθ1w)
2、第二次,选择右孩子,即正类,则 p ( d 3 w ∣ X w , θ 2 w ) = σ ( X w T θ 2 w ) p(d_3^w|X_w,\theta_2^w)=\sigma(X_w^T\theta_2^w) p(d3wXw,θ2w)=σ(XwTθ2w)
3、第三次,选择右孩子,即正类,则 p ( d 4 w ∣ X w , θ 3 w ) = σ ( X w T θ 3 w ) p(d_4^w|X_w,\theta_3^w)=\sigma(X_w^T\theta_3^w) p(d4wXw,θ3w)=σ(XwTθ3w)
4、第四次,选择左孩子,即负类,则 p ( d 5 w ∣ X w , θ 4 w ) = 1 − σ ( X w T θ 4 w ) p(d_5^w|X_w,\theta_4^w)=1-\sigma(X_w^T\theta_4^w) p(d5wXw,θ4w)=1σ(XwTθ4w)
显然,最终 p ( 足 球 ∣ C o n t e x t ( 足 球 ) ) p(足球|Context(足球)) p(Context())的概率等于上述4个概率的乘积
p ( 足 球 ∣ C o n t e x t ( 足 球 ) ) = ∏ j = 2 5 p ( d j w ∣ X w , θ j − 1 w ) p(足球|Context(足球))=\prod_{j=2}^5p(d_j^w|X_w,\theta_{j-1}^w) p(Context())=j=25p(djwXw,θj1w)

推广总结一下:对于词汇表中的任意一个词 w w w,Huffman树中比存在一条从根节点到词 w w w所对应的叶子节点的路径 p w p^w pw,且这条路径是唯一的,路径 p w p^w pw存在 l w − 1 l^w-1 lw1次分支,每一次分子对应一次二分类,每次二分类对应一个概率值,将这些概率连乘起来就是 p ( w ∣ C o n t e x t ( w ) ) p(w|Context(w)) p(wContext(w)),即
p ( w ∣ C o n t e x t ( w ) ) = ∏ j = 2 l w p ( d j w ∣ X w , θ j − 1 w ) p(w|Context(w))=\prod_{j=2}^{l^w}p(d_j^w|X_w,\theta_{j-1}^w) p(wContext(w))=j=2lwp(djwXw,θj1w)

其中
p ( d j w ∣ X w , θ j − 1 w ) = { σ ( X w T θ j − 1 w ) , if  d j w = 0 ; 1 − σ ( X w T θ j − 1 w ) , if  d j w = 1. p(d_j^w|X_w,\theta_{j-1}^w)= \begin{cases} \sigma(X_w^T\theta_{j-1}^w), &\text{if } d_j^w=0; \\ 1-\sigma(X_w^T\theta_{j-1}^w),&\text{if } d_j^w=1. \end{cases} p(djwXw,θj1w)={σ(XwTθj1w),1σ(XwTθj1w),if djw=0;if djw=1.

写成整体
p ( d j w ∣ X w , θ j − 1 w ) = [ σ ( X w T θ j − 1 w ) ] 1 − d j w ⋅ [ 1 − σ ( X w T θ j − 1 w ) ] d j w p ( w ∣ C o n t e x t ( w ) ) = ∏ j = 2 l w { [ σ ( X w T θ j − 1 w ) ] 1 − d j w ⋅ [ 1 − σ ( X w T θ j − 1 w ) ] d j w } 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}\\ p(w|Context(w))=\prod_{j=2}^{l^w}\big\{[\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}\big\} p(djwXw,θj1w)=[σ(XwTθj1w)]1djw[1σ(XwTθj1w)]djwp(wContext(w))=j=2lw{[σ(XwTθj1w)]1djw[1σ(XwTθj1w)]djw}

简单说明下, ∑ w ∈ C p ( w ∣ C o n t e x t ( w ) ) = 1 \sum_{w\in\mathcal{C}}p(w|Context(w))=1 wCp(wContext(w))=1,可以从叶子节点采用自底向上规约的方式求和,选择非叶子节点的左儿子或右儿子的概率之和等于1,因此可以向上规约一层,这一层又是其上一层的儿子,因此可以逐层规约到根节点,根节点一定发生,因此概率之和是1。
训练的过程是在语料上最大化对数似然函数
L = ∑ w ∈ C log ⁡ ∏ j = 2 l w { [ σ ( X w T θ j − 1 w ) ] 1 − d j w ⋅ [ 1 − σ ( X w T θ j − 1 w ) ] d j w } = ∑ w ∈ C ∑ j = 2 l w { ( 1 − d j w ) log ⁡ [ σ ( X w T θ j − 1 w ) ] + d j w log ⁡ [ 1 − σ ( X w T θ j − 1 w ) ] } \begin{aligned} \mathcal{L}=&\sum_{w\in\mathcal{C}}\log\prod_{j=2}^{l^w}\big\{[\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}\big\}\\ =&\sum_{w\in\mathcal{C}}\sum_{j=2}^{l^w}\big\{(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)]\big\} \end{aligned} L==wClogj=2lw{[σ(XwTθj1w)]1djw[1σ(XwTθj1w)]djw}wCj=2lw{(1djw)log[σ(XwTθj1w)]+djwlog[1σ(XwTθj1w)]}

采用梯度上升法,最大化 L \mathcal{L} L,记
L ( w , j ) = ( 1 − d j w ) log ⁡ [ σ ( X w T θ j − 1 w ) ] + d j w log ⁡ [ 1 − σ ( X w T θ j − 1 w ) ] \mathcal{L}(w,j)=(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)] L(w,j)=(1djw)log[σ(XwTθj1w)]+djwlog[1σ(XwTθj1w)]

对参数 θ j − 1 w \theta_{j-1}^w θj1w X w X_w Xw分别求导
∂ L ( w , j ) ∂ θ j − 1 w = ∂ ∂ θ j − 1 w { ( 1 − d j w ) log ⁡ [ σ ( X w T θ j − 1 w ) ] + d j w log ⁡ [ 1 − σ ( X w T θ j − 1 w ) ] } = ( 1 − d j w ) [ 1 − σ ( X w T θ j − 1 w ) ] X w − d j w σ ( X w T θ j − 1 w ) X w = { ( 1 − d j w ) [ 1 − σ ( X w T θ j − 1 w ) ] − d j w σ ( X w T θ j − 1 w ) } X w = [ 1 − d j w − σ ( X w T θ j − 1 w ) ] X w \begin{aligned} \frac{\partial\mathcal{L}(w,j)}{\partial\theta_{j-1}^w}=& \frac{\partial}{\partial\theta_{j-1}^w}\big\{(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)]\big\}\\ =& (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\\ =& \big\{(1-d_j^w)[1-\sigma(X_w^T\theta_{j-1}^w)]-d_j^w\sigma(X_w^T\theta_{j-1}^w)\big\}X_w\\ =& [1-d_j^w-\sigma(X_w^T\theta_{j-1}^w)]X_w \end{aligned} θj1wL(w,j)====θj1w{(1djw)log[σ(XwTθj1w)]+djwlog[1σ(XwTθj1w)]}(1djw)[1σ(XwTθj1w)]Xwdjwσ(XwTθj1w)Xw{(1djw)[1σ(XwTθj1w)]djwσ(XwTθj1w)}Xw[1djwσ(XwTθj1w)]Xw

于是, θ j − 1 w \theta_{j-1}^w θj1w的更新公式可以写作
θ j − 1 w : = θ j − 1 w + η [ 1 − d j w − σ ( X w T θ j − 1 w ) ] X w \theta_{j-1}^w\coloneqq\theta_{j-1}^w+\eta[1-d_j^w-\sigma(X_w^T\theta_{j-1}^w)]X_w θj1w:=θj1w+η[1djwσ(XwTθj1w)]Xw

同理,可以求得 ∂ L ( w , j ) / ∂ X w \partial\mathcal{L}(w,j)/\partial X_w L(w,j)/Xw
∂ L ( w , j ) ∂ X w = [ 1 − d j w − σ ( X w T θ j − 1 w ) ] θ j − 1 w \frac{\partial\mathcal{L}(w,j)}{\partial X_w}= [1-d_j^w-\sigma(X_w^T\theta_{j-1}^w)]\theta_{j-1}^w XwL(w,j)=[1djwσ(XwTθj1w)]θj1w

这里的 X w X_w Xw C o n t e x t ( w ) Context(w) Context(w)各词向量累加的结果,那如何对每个词向量进行更新吗?word2vec的做法比较简单
v ( w ~ ) : = v ( w ~ ) + η ∑ j = 2 l w ∂ L ( w , j ) ∂ X w v(\tilde{w})\coloneqq v(\tilde{w})+\eta\sum_{j=2}^{l^w}\frac{\partial\mathcal{L}(w,j)}{\partial X_w} v(w~):=v(w~)+ηj=2lwXwL(w,j)

即把 ∑ j = 2 l w ∂ L ( w , j ) ∂ X w \sum_{j=2}^{l^w}\frac{\partial\mathcal{L}(w,j)}{\partial X_w} j=2lwXwL(w,j)贡献到 C o n t e x t ( w ) Context(w) Context(w)中每个词的词向量上。最后,给出CBOW参数更新的伪代码

Skip-gram模型

后续更新

总结

参考文献

Efficient Estimation of Word Representations in Vector Space
word2vec 中的数学原理详解系列

  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值