自然语言处理NLP(4)——序列标注a:隐马尔科夫模型(HMM)

在上一部分中,我们已经了解了神经网络语言模型和词向量:自然语言处理NLP(3)——神经网络语言模型、词向量
在对现阶段NLP领域的最基本、最常用的架构有所了解之后,在这一部分中,我们将要介绍NLP领域最常见的一类问题:序列标注。

【一】序列标注

在NLP领域中,有许多的任务可以转化为“将输入的语言序列转化为标注序列”来解决问题。比如,命名实体识别、词性标注等等。

命名实体识别

命名实体识别是NLP中的一个经典问题,比如从一句话中识别出人名、地名、组织机构名等等。
举例来看:
任务目标:将给定的输入序列中的组织机构名识别出来。
命名实体识别
输入序列中,“远大公司”是一个组织机构名。根据任务,我们的目标是将其标注出来。
标注序列中,B表示实体开始符,E表示实体结束符,I表示实体中间内容,O表示非实体的单个字。

词性标注

顾名思义,词性标注的目标就是将给定的输入序列中词的词性标出来。
举例来看:
词性标注
很明显,这也可以看作一个序列标注任务。

除此之外,序列标注还可以解决很多问题,例如分词、短语识别、信息抽取等等,在这里不一一举例,有兴趣的朋友们可以自行百度进行了解~

那么,如何解决序列标注问题呢?
常用的方法有三种:
1.隐马尔科夫模型(HMM)
2.条件随机场(CRF)
3.神经网络与条件随机场相结合(RNN+CRF)
下面,我们对这三种方法一一进行介绍。

【二】隐马尔科夫模型(HMM)

在介绍隐马尔科夫模型(简称隐马模型,HMM)之前,我们先来了解一下马尔可夫模型。

马尔可夫模型

假设一个系统有 n n n个状态: S 1 , S 2 , . . . S n S_1,S_2,...S_n S1,S2,...Sn,且随着时间(空间)的推移,系统从某一状态转移到另一状态,如果系统在时间 t t t的状态 q ( t ) q(t) q(t)只与其在时间 t − 1 t-1 t1的状态相关,则系统构成离散的一阶马尔可夫链

以掷骰子为例:
很明显,在“掷骰子”这个系统中,状态集是这个样子的:
状态集
随着时间的推移,状态序列有可能是这个样子的:
状态序列
在这里,需要区别一下状态集和状态序列之间的差别:状态集是一个无序集合,而状态序列是一个有序序列。

在马尔可夫链的基础上,构建马尔可夫模型:
p ( S 0 , S 1 , . . . , S T ) = ∏ t = 1 T p ( S t ∣ S t − 1 ) p ( S 0 ) p(S_0,S_1,...,S_T)=\prod_{t=1}^Tp(S_t|S_{t-1})p(S_0) p(S0,S1,...,ST)=t=1Tp(StSt1)p(S0)
公式很容易理解,即,“当前状态仅与上一时刻状态有关”。

假设状态序列(马尔可夫链)如下:
马尔可夫链
那么根据马尔可夫模型,有独立于时间 t t t的随机过程:
p ( q t = S j ∣ q t − 1 = S i ) = a i , j , 1 ≤ i , j ≤ n p(q_t=S_j|q_{t-1}=S_i)=a_{i,j},\quad1≤i,j≤n p(qt=Sjqt1=Si)=ai,j,1i,jn
其中,将 a i , j a_{i,j} ai,j定义为状态转移概率, a i , j ≥ 0 a_{i,j}≥0 ai,j0,且 ∑ j = 1 N a i , j = 1 \sum_{j=1}^Na_{i,j}=1 j=1Nai,j=1
这部分很好理解,就不再解释具体细节。

对于马尔可夫模型而言,最重要的部分还是老问题,四元组:
模型输入:状态序列
模型输出:状态序列概率
模型参数: p ( q t ∣ q t − 1 ) p(q_t|q_{t-1}) p(qtqt1)
对应运算关系: p ( S 0 , S 1 , . . . , S T ) = ∏ t = 1 T p ( S t ∣ S t − 1 ) p ( S 0 ) p(S_0,S_1,...,S_T)=\prod_{t=1}^Tp(S_t|S_{t-1})p(S_0) p(S0,S1,...,ST)=t=1Tp(StSt1)p(S0)

所以,马尔可夫模型的组成是一个三元组: M = ( S , π , A ) M=(S,\pi,A) M=(S,π,A)
其中, S S S表示有限状态集合; π \pi π表示初始状态空间的概率分布; A A A表示与时间无关的状态转移概率矩阵。

在“掷骰子”系统中:
(1) S = { 1 , 2 , 3 , 4 , 5 , 6 } S=\{1,2,3,4,5,6\} \tag{1} S={1,2,3,4,5,6}(1)

(2) π = ( 1 , 0 , 0 , 0 , 0 , 0 ) \pi=(1,0,0,0,0,0) \tag{2} π=(1,0,0,0,0,0)(2)

(3) A = [ 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 1 / 6 ] A=\left[ \begin{matrix} 1/6 & 1/6 & 1/6 & 1/6 & 1/6 & 1/6\\ 1/6 & 1/6 & 1/6 & 1/6 & 1/6 & 1/6\\ 1/6 & 1/6 & 1/6 & 1/6 & 1/6 & 1/6\\ 1/6 & 1/6 & 1/6 & 1/6 & 1/6 & 1/6\\ 1/6 & 1/6 & 1/6 & 1/6 & 1/6 & 1/6\\ 1/6 & 1/6 & 1/6 & 1/6 & 1/6 & 1/6\\ \end{matrix} \right] \tag{3} A=1/61/61/61/61/61/61/61/61/61/61/61/61/61/61/61/61/61/61/61/61/61/61/61/61/61/61/61/61/61/61/61/61/61/61/61/6(3)
其中, ( 2 ) (2) (2)表示初始状态为‘1’

隐马尔科夫模型

隐马尔科夫模型(HMM)是一个双重随机过程,我们不知道具体的状态序列,只知道状态转移的概率。即,模型的状态转换过程是不可观察的(隐蔽的),而可观察事件的随机过程是隐蔽状态转换过程的随机函数。

这个描述可能有些难以理解,不过没关系,我们举个例子来看:
假设我们现在有一个描述天气变化的马尔可夫模型,描述该模型的三元组如下:
天气变化MM
图中,天气变化“晴云雨雨晴”是状态序列。
而刚好,天气变化会引起海藻的潮湿度变化。于是,我们得到了海藻变化的序列,我们将其称为观察序列:
海藻变化HMM
如图中所说,观察序列的变化是由状态序列的变化引起的,前者时后者的随机函数。

当状态序列不可见时,这就是一个隐马尔可夫模型:
HMM
当状态序列不可见时,可以通过观察序列推出状态序列。

看到这里,也许有朋友会问,这个有什么用啊?
还是以天气和海藻为例,假设你在一个山洞里,你想看看外面是什么天气。但是你受伤了,走不出去,于是你只能通过身边的海藻变化推测外面的天气。

与马尔可夫模型类似,隐马模型的组成是一个五元组: λ = ( S , O , π , A , B ) λ=(S,O,\pi,A,B) λ=(S,O,π,A,B)
其中, S , π , A S,\pi,A S,π,A的含义与马尔可夫模型中的含义相同,分别指的是状态序列中的状态集、初始概率分布和状态转移概率矩阵(即上例中的天气);而 O O O表示每个状态可能的观察值, B B B表示给定状态下,观察值的概率分布,也称为发射概率。

如果对五元组含义有不理解的部分,上图中有清晰的解释,这里就不再赘述。

特别地,该模型也可以简写为: λ = ( π , A , B ) λ=(\pi,A,B) λ=(π,A,B)

通过对HMM的描述,我们可以看出HMM三个明显的假设,在这里对其形式化为:
对于一个随机事件,有观察序列: O = O 1 O 2 . . . O T O=O_1O_2...O_T O=O1O2...OT
该事件背后隐含着一个状态序列: Q = q 1 q 2 . . . q T Q=q_1q_2...q_T Q=q1q2...qT

假设1:马尔可夫性假设(状态序列构成一阶马尔可夫链)
即, p ( q i ∣ q i − 1 . . . q 2 q 1 ) = p ( q i ∣ q i − 1 ) p(q_i|q_{i-1}...q_2q_1)=p(q_i|q_{i-1}) p(qiqi1...q2q1)=p(qiqi1)
假设2:不动性假设(状态与具体时间无关)
即, p ( q i + 1 ∣ q i ) = p ( q j + 1 ∣ q j ) p(q_{i+1}|q_i)=p(q_{j+1}|q_j) p(qi+1qi)=p(qj+1qj) 对任意 i , j i,j i,j成立
假设3:输出独立性假设(输出观察值仅与当前状态有关)
即, p ( O 1 , O 2 , . . . , O T ∣ q 1 , q 2 , . . . , q T ) = ∏ t = 1 T p ( O t ∣ q t ) p(O_1,O_2,...,O_T|q_1,q_2,...,q_T)=\prod_{t=1}^Tp(O_t|q_t) p(O1,O2,...,OTq1,q2,...,qT)=t=1Tp(Otqt)

由此,我们可以总结出HMM的特点:
1.HMM的状态是不可见的,只有通过观察序列的随机过程才能表现出来。
2.观察到的事件与状态并不是一一对应的,而是通过一组概率分布相联系。
3.HMM是一个双重随机过程,两个组成部分分别为:
a.马尔可夫链:描述状态的转移,用转移概率描述;
b.一般随机函数:描述状态与观察序列间的关系, 用观察值概率(发射概率)描述。

别忘了,HMM也是一种模型。对于HMM,描述模型的四元组分别是:
输入:观察序列
输出:1.观察序列的概率值;2.隐状态序列
参数: p ( q t ∣ q t − 1 ) p(q_t|q_{t-1}) p(qtqt1) p ( O t ∣ q t ) p(O_t|q_t) p(Otqt),即 A , B A,B A,B矩阵

也许有朋友会问,对应运算关系哪儿去了?
由于对应运算关系描述的是输入到输出之间的关系,而HMM的输出有两种(分别对应HMM可以解决的两类问题),所以,我们将在两类问题中对运算关系进行阐述。

针对HMM不同的输出,我们可以定义如下两类问题:
1.在给定模型中出现观察序列的可能性(概率值),也称为HMM评估问题;
2.通过观察序列找出最大可能的隐状态序列,也称为HMM解码问题。

HMM参数学习

在介绍HMM的这两类问题之前,我们先来看看HMM模型的学习问题。

在HMM中,参数就是 A , B A,B A,B矩阵中的元素,即: p ( S t ∣ S t − 1 ) p(S_t|S_{t-1}) p(StSt1) p ( O t ∣ S t ) p(O_t|S_t) p(OtSt)

在训练过程中,以观察序列 O = O 1 , O 2 , . . . , O T O=O_1,O_2,...,O_T O=O1,O2,...,OT作为训练数据,利用极大似然估计(MLE),使得观察序列 O O O的概率最大。
很明显,我们会遇到两种情况:
1.产生观察序列 O O O的隐状态序列 Q Q Q已知
2.产生观察序列 O O O的隐状态序列 Q Q Q未知

对于第一种情况,隐状态序列已知,我们可以很容易地利用有监督学习的方法,用极大似然估计计算参数;
对于第二种情况,隐状态序列未知,很明显极大似然估计不可行,此时可以采用无监督的EM方法进行学习。

在这里,不对这两种方法进行更多说明,有兴趣的朋友们可以自行百度了解~

【三】HMM评估问题

首先,我们对HMM评估问题进行描述:
对于给定的观察序列 O = O 1 , O 2 , . . . , O T O=O_1,O_2,...,O_T O=O1,O2,...,OT 以及模型 λ = ( π , A , B ) λ=(\pi,A,B) λ=(π,A,B),求观察序列的概率值 p ( O ∣ λ ) p(O|λ) p(Oλ)

看到这里,大家也许会一头雾水,疑问的来源大概有两个方面:
1.这个 p ( O ∣ λ ) p(O|λ) p(Oλ)是什么?
2.这个 p ( O ∣ λ ) p(O|λ) p(Oλ)该怎么求?

首先,给出 p ( O ∣ λ ) p(O|λ) p(Oλ) 的定义:
对于给定的一个状态序列 Q = q 1 q 2 . . . q T Q=q_1q_2...q_T Q=q1q2...qT,根据条件联合分布的分解公式,可以得到:
p ( O , Q ∣ λ ) = p ( Q ∣ λ ) p ( O ∣ Q , λ ) p(O,Q|λ)=p(Q|λ)p(O|Q,λ) p(O,Qλ)=p(Qλ)p(OQ,λ)
(这部分分解不太明白的朋友们可以自行百度,在这里不进行过多叙述)

其中,
p ( Q ∣ λ ) = π q 1 a q 1 , q 2 a q 2 , q 3 . . . a q T − 1 , q T p(Q|λ)=\pi_{q_1}a_{q_1,q_2}a_{q_2,q_3}...a_{q_{T-1},q_T} p(Qλ)=πq1aq1,q2aq2,q3...aqT1,qT
p ( O ∣ Q , λ ) = b q 1 ( O 1 ) b q 2 ( O 2 ) . . . b q T ( O T ) p(O|Q,λ)=b_{q_1}(O_1)b_{q_2}(O_2)...b_{q_T}(O_T) p(OQ,λ)=bq1(O1)bq2(O2)...bqT(OT)
所以,有如下公式:
p ( O , Q ∣ λ ) = p ( Q ∣ λ ) p ( O ∣ Q , λ ) = π q 1 a q 1 , q 2 a q 2 , q 3 . . . a q T − 1 , q T b q 1 ( O 1 ) b q 2 ( O 2 ) . . . b q T ( O T ) p(O,Q|λ)=p(Q|λ)p(O|Q,λ)=\pi_{q_1}a_{q_1,q_2}a_{q_2,q_3}...a_{q_{T-1},q_T}b_{q_1}(O_1)b_{q_2}(O_2)...b_{q_T}(O_T) p(O,Qλ)=p(Qλ)p(OQ,λ)=πq1aq1,q2aq2,q3...aqT1,qTbq1(O1)bq2(O2)...bqT(OT)
公式很好理解, π q 1 \pi_{q_1} πq1表示时间1状态为 q 1 q_1 q1的概率, a q i , q i + 1 a_{q_i,q_{i+1}} aqi,qi+1表示状态由(第 i i i天) q i q_i qi变为(第 i + 1 i+1 i+1天) q i + 1 q_{i+1} qi+1的转移概率, b q i ( O i ) b_{q_i}(O_i) bqi(Oi)表示(第 i i i天)状态 q i q_i qi的观察值为 O i O_i Oi的概率(发射概率)。

按照时间推移来计算,该公式可以写为:
p ( O , Q ∣ λ ) = p ( Q ∣ λ ) p ( O ∣ Q , λ ) = π q 1 b q 1 ( O 1 ) a q 1 , q 2 b q 2 ( O 2 ) . . . a q T − 1 , q T b q T ( O T ) p(O,Q|λ)=p(Q|λ)p(O|Q,λ)=\pi_{q_1}b_{q_1}(O_1)a_{q_1,q_2}b_{q_2}(O_2)...a_{q_{T-1},q_T}b_{q_T}(O_T) p(O,Qλ)=p(Qλ)p(OQ,λ)=πq1bq1(O1)aq1,q2bq2(O2)...aqT1,qTbqT(OT)
与上面的式子完全相同,希望对大家的理解有所帮助。

当然,上述概率仅针对某特定的状态序列 Q Q Q,对于全部状态序列,有:
(4) p ( O ∣ λ ) = ∑ Q p ( O , Q ∣ λ ) = ∑ Q p ( Q ∣ λ ) p ( O ∣ Q , λ ) p(O|λ)=\sum_Qp(O,Q|λ)=\sum_Qp(Q|λ)p(O|Q,λ) \tag{4} p(Oλ)=Qp(O,Qλ)=Qp(Qλ)p(OQ,λ)(4)

清楚了 p ( O ∣ λ ) p(O|λ) p(Oλ)的定义之后,下一个问题就是:如何计算 p ( O ∣ λ ) p(O|λ) p(Oλ)?

( 4 ) (4) (4)式可以看出, p ( O ∣ λ ) p(O|λ) p(Oλ)是一个积分项(求和项),所以针对所有可能的隐状态序列 Q Q Q进行穷举后加和是一个可行的方法。然而,很显然的是,这种方法效率非常低。假设模型中的隐状态有 n n n种,序列长度为 T T T,可能的情况有 n T n^T nT种。

然而,在穷举过程中发现,某些计算过程可以重复利用,于是我们可以通过动态规划的方法使用递归,降低计算复杂度,这种方法被称为”前向算法“:
定义前向变量 α t ( j ) α_t(j) αt(j),表示达到某个中间状态的概率( t t t时刻隐状态为 j j j的概率)。

t = 1 t=1 t=1时,为初始概率:
α 1 ( i ) = π i b i ( O 1 ) , t = 1 α_1(i)=\pi_ib_i(O_1),\quad t=1 α1(i)=πibi(O1),t=1
1 &lt; t ≤ T 1&lt;t≤T 1<tT时,
α t ( j ) = [ ∑ i = 1 n α t − 1 ( i ) a i , j ] b j ( O t ) , 1 &lt; t ≤ T α_t(j)=[\sum_{i=1}^nα_{t-1}(i)a_{i,j}]b_j(O_t),\quad 1&lt;t≤T αt(j)=[i=1nαt1(i)ai,j]bj(Ot),1<tT
最终结果: p ( O ∣ λ ) = ∑ i = 1 n α T ( i ) p(O|λ)=\sum_{i=1}^nα_T(i) p(Oλ)=i=1nαT(i)

由于观察序列给定,所以每一步的每个值都可以通过查阅 A , B A,B A,B矩阵进行计算。

前向算法的过程很好理解,即,对每一时刻,计算观察值为当前观察值的概率,直到最后一个时刻,将所有满足条件的序列概率加和即为最终结果。

很明显,前向算法的时间复杂度为 O ( n 2 T ) O(n^2T) O(n2T),远小于穷举法的 O ( n T ) O(n^T) O(nT)

与前向算法对应的还有后向算法,具体算法不在这里进行介绍,有兴趣的朋友们可以自行百度~
个人认为后向算法从理解上要比前向算法更绕一些,没有特殊需求的朋友们可以略过这一part~

【四】HMM解码问题

同样,我们首先对HMM解码问题进行描述:
对给定观察序列 O = O 1 , O 2 , . . . , O T O=O_1,O_2,...,O_T O=O1,O2,...,OT 以及模型 λ = ( π , A , B ) λ=(\pi,A,B) λ=(π,A,B),选择一个对应的状态序列 S = S 1 , S 2 , . . . , S T S=S_1,S_2,...,S_T S=S1,S2,...,ST,使得 S S S能够最合理地解释观察序列 O O O(即,产生某观察序列 O O O的,概率最大的状态序列)。

以天气和海藻湿度为例:
HMM解码问题
同样,我们可以用穷举法,计算每个状态序列产生该观察序列 O O O的概率,最后选取概率最大的状态序列即可。但同样,穷举法的复杂度为 O ( n T ) O(n^T) O(nT),效率极低。

于是,聪明的人们再次利用动态规划的递归方法,降低了运算的复杂度,这种方法被称为Viterbi算法。

在这里,我们不对Viterbi算法的正确性进行证明,而只对算法过程进行描述,对其正确性证明感兴趣的朋友们可以自行查阅相关资料~

Viterbi算法

定义部分概率 δ δ δ δ t ( i ) δ_t(i) δt(i)表示 t t t 时刻,到状态 i i i 的所有路径(隐状态序列)中概率最大的路径的概率
δ 1 ( i ) = π i b i ( O 1 ) , 1 ≤ i ≤ n δ_1(i)=\pi_ib_i(O_1), \quad 1≤i≤n δ1(i)=πibi(O1),1in
δ t ( j ) = [ m a x 1 ≤ i ≤ n ( δ t − 1 ( i ) a i , j ) ] b j ( O t ) , 2 ≤ i ≤ T , 1 ≤ j ≤ n δ_t(j)=[max_{1≤i≤n}(δ_{t-1}(i)a_{i,j})]b_j(O_t), \quad 2≤i≤T,\quad1≤j≤n δt(j)=[max1in(δt1(i)ai,j)]bj(Ot),2iT,1jn

在这个过程中,利用一个后向指针 ψ \psi ψ 记录导致某个状态最大局部概率的前一个状态。即,利用变量 ψ \psi ψ 记录路径。
ψ t ( i ) = a r g m a x j ( δ t − 1 ( j ) a j , i ) \psi_t(i)=argmax_j(δ_{t-1}(j)a_{j,i}) ψt(i)=argmaxj(δt1(j)aj,i)

通过Viterbi算法公式我们可以发现,在递归过程中(除 t = 1 t=1 t=1 外),每一时刻概率值的计算都依赖于上一时刻传来的 m a x max max值,而通俗来讲, ψ \psi ψ 就是用来记录这个 m a x max max值是从上一时刻的哪个隐状态传过来的。

于是,在计算到最后一个时刻 T T T 之后,我们同样选取概率最大的一个隐状态作为最后时刻的隐状态输出,并以此利用之前记录的 ψ \psi ψ 指针回溯,得到所有时刻的隐状态,得到的结果即为最后的结果:
q T ∗ = a r g m a x 1 ≤ i ≤ n [ δ T ( i ) ] q_T^*=argmax_{1≤i≤n}[δ_T(i)] qT=argmax1in[δT(i)]
q t ∗ = ψ t + 1 ( q t + 1 ∗ ) , t = T − 1 , T − 2 , . . . , 1 q_t^*=\psi_{t+1}(q_{t+1}^*), \quad t=T-1,T-2,...,1 qt=ψt+1(qt+1),t=T1,T2,...,1

Viterbi算法的整个过程与前向算法类似,只不过将每步递归中的求和改为了取最大值。

【五】HMM应用

介绍过HMM模型之后,让我们回归到一开始的问题——HMM是用来干什么的?在本篇文章的开头,我们曾经提到过:HMM是解决序列标注问题的常用方法。
那么它究竟是如何解决序列标注问题的呢?

在文章的开头,我们说过,词性标注可以看作一个序列标注问题,现在我们就以词性标注为例,介绍HMM在其中如何发挥作用:
假设我们需要对‘Flies like a flower’进行词性标注。

在自然语言处理领域,HMM的观察序列往往是,隐状态序列往往是词类(例如词性)。
现在,我们已知观察序列:‘Flies like a flower’,要求取最合理的隐状态序列(词性序列),这可以看作一个HMM解码问题。
假设我们有如下的从语料库中训练得到的词性转移概率矩阵和词语生成概率矩阵:
HMM词性标注
这就相当于我们HMM里的 A , B A,B A,B矩阵,于是我们有了Viterbi算法实现的种种必要条件:观察序列以及HMM的 A , B A,B A,B矩阵,可以进行计算,从而得到词性标注的结果。

HMM在NLP领域中有着广泛的应用。除序列标注之外,HMM还可以进行分词过程中的消歧:当分词出现多种可能时,可以利用HMM取概率最大的序列(相当于HMM评估问题);直接利用HMM进行分词(将分词任务看作序列标注问题)等等。
HMM的其他应用在这里不再进行赘述,有兴趣的朋友们可以自行查阅相关资料~

这一部分中,我们主要介绍了序列标注问题以及解决该问题的第一种方法:HMM。
对于HMM的评估问题和解码问题,我们主要关注对原理的理解,而没有对具体的例子和运算细节进行太多纠结,这方面的内容网上有很多,有兴趣的朋友可以自行查阅~
在下一部分的内容中,我们将介绍余下两种我们提到过的,解决序列标注问题的方法:条件随机场(CRF)以及,神经网络与条件随机场相结合的方法(RNN+CRF)。

如果本文中某些表述或理解有误,欢迎各位大神批评指正。

谢谢!

  • 9
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值