《Python自然语言处理-雅兰·萨纳卡(Jalaj Thanaki)》学习笔记:05 特征工程和NLP算法

5.1 理解特征工程

特征工程是开发NLP应用程序的重要组成部分。特征是机器学习(ML)算法的输入参数。这些ML算法根据输入特性生成输出。特征工程是一门艺术和技术,因为它产生了可能的最佳特征,而选择最佳算法来开发NLP应用需要大量的工作和对特征工程以及NLP和ML算法的理解。在第2章,语料库和数据集的实际理解中,我们看到了数据是如何收集的,以及数据或语料库的不同格式是什么。在第三章,理解句子结构时,我们提到了一些基本的,但是NLP和语言学的重要方面。我们将在本章中使用这些概念来派生特性。在第四章,预处理中,我们研究了预处理技术。现在,我们将处理预处理的语料库,并从该语料库生成特性。

什么是特征工程?

了解NLP的基本特征

NLP的基本统计特征

除此之外,我们还将探讨各种工具或库是如何开发来生成特性的,我们可以使用的各种库是什么,以及在需要时如何调整开放源代码库或开放源代码工具。我们还将研究每个概念的挑战。在这里,我们不会从头开始开发工具,因为它超出了本书的范围,但是我们将引导您完成用于开发工具的过程和算法。因此,如果您想尝试构建定制的工具,这将帮助您,并将给您一个如何处理这些问题陈述的想法。

5.1.1 特征工程的定义

特征工程是从原始数据或语料库生成或派生特征(现象的属性或单个可测量属性)的过程,这将帮助我们开发NLP应用程序或解决与NLP相关的问题。

特征可以定义为一条信息或可测量的属性,在构建NLP应用程序或预测NLP应用程序的输出时很有用。

我们将使用ML技术来处理自然语言,并开发出能够提供最终输出的模型。这个模型称为机器学习模型(ML模型)。我们将机器学习算法的功能作为输入并生成机器学习模型。之后,我们将使用生成的机器学习模型为NLP应用程序生成适当的输出。

如果您想知道什么信息可以作为一个特性,那么答案是,任何属性都可以作为一个特性,只要它对于生成一个好的ML模型很有用,它将准确高效地为NLP应用程序生成输出。这里,您的输入特性完全依赖于您的数据集和NLP应用程序。

特性是使用NLP应用程序的领域知识派生的。这就是我们探索自然语言的基本语言学方面的原因,这样我们就可以在特征工程中使用这些概念。

5.1.2 特征工程的目的

在本节中,我们将介绍有助于我们理解特征工程的主要功能:

我们有计算机无法理解的自然语言的原始数据,而算法没有能力接受原始自然语言并生成NLP应用程序的预期输出。当您使用机器学习技术开发NLP应用程序时,功能扮演着重要的角色。

我们需要生成能够代表我们的语料库的属性,以及那些可以被机器学习算法理解的属性。ML算法只能理解通信中的特征语言,提出合适的属性或特征是一件大事。这就是特征工程的全部目的。

一旦我们生成了特征,我们就需要把它们作为输入,输入到机器学习算法中,在处理完这些输入特征之后,我们将得到ML模型。该ML模型将用于预测或生成新特性的输出。ML模型、精度和效率主要取决于特征,这就是为什么我们说特征工程是一种艺术和技能。

5.1.3 一些挑战

以下是特征工程中涉及的挑战:

提出好的特性是困难的,有时是复杂的。

在生成特性之后,我们需要决定应该选择哪些特性。当我们在此基础上执行机器学习技术时,这些特性的选择也起着重要的作用。选择适当特征的过程称为

特性选择。

有时,在特征选择过程中,我们需要消除一些不太重要的特征,而这种特征的消除也是特征工程的一个关键部分。

手工特征工程是耗时的。

特征工程需要领域专业知识,或者至少需要领域的基本知识。

5.2 NLP中的基础特征

除了挑战之外,NLP应用程序还严重依赖于基于各种NLP概念手工构建的特性。从这一点开始,我们将探索NLP世界中可用的基本特性。

5.2.1 句法解析和句法解析器

通过解析句子,几乎对每个NLP应用程序都有帮助,您可以得到一些最重要的特性
我们将探讨句法解析和句法解析器的概念。稍后,我们将了解上下文无关语法(CFG)和概率上下文无关语法(PCFG)。我们看看怎么开发统计分析器。如果您想创建自己的解析器,那么我们将解释这样做的过程,或者如果您想要调整现有的解析器,那么您应该需要执行什么步骤。我们还将使用可用的句法解析器工具进行实际工作。我们会看的在同一节后面的挑战。
句法解析器的基础
这里,我将用NLP域来解释句法解析器。解析器概念也存在于其他计算机科学领域,但是让我们集中讨论NLP领域,并开始了解解析器以及它可以为我们做什么。

在NLP中,解析器是一个程序,或者更具体地说,是一个以语句或词条序列的形式分析自然语言的工具。它将输入流分成更小的块。这将有助于我们理解流中每个元素的句法作用以及句子的基本语法层含义。在NLP中,解析器实际上使用上下文无关语法或概率上下文无关语法的规则来分析句子。我们已经在第3章,理解句子结构中看到了上下文无关语法。

解析器通常以解析器树或抽象语法树的形式生成输出。让我们看看这里的一些示例解析器树。解析器使用某些语法规则来生成包含单个单词或词汇项的解析树。
Alt
让我们先讨论符号:
S 句子开始
NP 名词短语
VP 动词短语
V 动词
N 名词
ART 冠词 a, an, or the

我们还将研究自顶向下的解析器和自下而上的解析器之间的区别。
理解句法解析的概念
首先,我们来讨论解析是什么。让我们定义术语解析。解析是一种形式化的分析或过程,它使用一个句子或符号流,借助于定义好的形式语法规则,我们可以理解句子的结构和意义。因此,解析句子中的每个单词,并组成结构。什么是一致的结构?构成结构是根据观察哪个词与其他词结合形成一个合理的句子单位。所以,英语,主语,在句子中主要排在第一位;He is Tom 这个句子对我们来说是有意义的,而这个句子is Tom he,没有意义。通过解析,我们实际上检查并试图获得一个合理的组成结构。以下几点将解释句法解析和句法解析器对我们的作用:

句法解析器按照语法规则执行解析过程,并生成一个解析树。这种分析树结构用于验证句子的语法结构。如果句子的分析树遵循语法规则并生成有意义的句子,那么我们就说语法以及使用该语法生成的句子是有效的。

在句法解析结束时,将生成一个解析树作为输出,它将帮助您检测句子中的歧义,因为。模棱两可的句子常常导致多个分析树。
Alt
让我们来看看自顶向下的解析器和自下而上的解析器之间的区别:

自顶向下分析自下而上分析
自上而下的分析是假设驱动的。自下而上的分析是数据驱动的。
在解析的每个阶段,解析器都假定一个结构,并从句子中按顺序取一个单词,并测试所取的单词或标记是否满足假设。在这种类型的解析中,第一个单词取自输入字符串,然后解析器检查是否存在任何预定义的类别,以生成有效的句子结构,最后,它尝试将它们组合成语法中可接受的结构。
它以从左到右的方式扫描一个句子,当语法生成规则派生出词汇项时,解析器通常会检查输入是否派生出右句子。这种分析从终端的输入字符串开始。这种类型的分析会搜索工作字符串的子字符串,因为如果任何字符串或子字符串与语法的右侧生成规则匹配,则它会将左侧非终结符替换为匹配的右侧规则。
它包括一个回溯机制。当确定使用了错误的规则时,它会备份并尝试另一个规则。它通常不包括回溯机制。

从头开发解析器
在本节中,我们将尝试了解最著名的斯坦福解析器的过程,以及使用哪种算法来开发最成功的统计解析器。

为了了解最后一个过程,我们首先需要了解一些构建块和概念。然后,我们将结合所有概念来理解构建统计解析器(如斯坦福解析器)的整个过程。
语法类型
我们将看到两种类型的语法,它们将帮助我们理解解析器如何工作的概念。作为一个先决条件,我们将简单地解释它们,避免对主题太过深入。我们将使它们尽可能简单,我们将探索概念的基本直觉,这些概念将用于理解开发解析器的过程。

上下文无关语法

概率上下文无关文法
上下文无关语法
我们已经在第三章“句子的理解结构”中看到了上下文无关语法的基本概念。我们已经看到了CFG的正式定义。现在,我们将了解在构建解析器时语法规则的重要性。

CFG也称为短语结构语法。因此,CFG和短语结构语法是两个术语,但指的是一个概念。现在,让我们看一些与这种语法类型相关的例子,然后讨论遵循的约定,以便生成更自然的语法规则形式。语法规则、词汇和句子如下图:
Alt

概率上下文无关文法

Alt

计算树的概率
如果我们想计算一棵树的概率,这是很容易的,因为你需要乘以词汇和语法规则的概率值。这将给我们一棵树的概率。
Alt
P(t1) = 1.0 * 0.7 * 0.4 * 0.5 * 0.6 * 0.7 * 1.0 * 0.2 * 1.0 * 0.7 * 0.1 = 0.0008232
Alt
P(t2) = 0.00024696
计算字符串的概率
计算字符串的概率比计算树的概率更复杂。这里,我们要计算单词串的概率,为此,我们需要考虑所有可能的树结构,它们生成我们要计算概率的字符串。我们首先需要考虑将字符串作为树的一部分的所有树,然后通过添加不同的概率来计算最终概率,以生成最终概率值。P(S) = P(t1) +P(t2)
= 0.0008232 + 0.00024696
= 0.00107016
语法转换
语法转换是一种使语法更具限制性的技术,它使解析过程更加高效。我们将使用Chomsky Normal Form(CNF)来转换语法规则。让我们先研究一下CNF,然后再看一个例子。X-> Y Z or X-> w where X, Y, Z ε N and w ε T规则的含义很简单。在任何语法规则的右侧不应该有两个以上的非终结点;可以包括规则右侧有一个终结点的规则。要将现有语法转换为CNF,可以遵循以下基本过程:

可以使用递归函数删除空规则和一元规则

N元规则通过在语法规则中引入新的非终结点来划分。这适用于在右侧具有两个以上非终结点的规则。使用CNF时,可以使用新的转换规则获得相同的字符串,但其解析结构可能不同。应用CNF后新生成的语法也是CFG。

在现实生活中,不需要使用完整的CNF,这样做通常会非常痛苦。它只是让解析更高效,语法规则更清晰。在实际应用中,我们将一元规则作为语法规则,因为它们告诉我们一个单词是作为动词还是名词来对待,以及非终端符号信息,这意味着我们拥有词性标注的信息。
用Cocke-Kasami-Younger算法开发一个解析器
在计算机科学领域,CYK算法(也称为Cocke–Younger–Kasami算法)是一种用来对 上下文无关文法(CFG,Context Free Grammar)进行语法分析(parsing)的算法。该算法最早由John Cocke, Daniel Younger and Tadao Kasami分别独立提出,其中John Cocke还是1987年度的图灵奖得主。CYK算法是基于动态规划思想设计的一种自底向上语法分析算法。对于英语语言,有很多解析器可以使用,如果你想为任何其他语言构建一个解析器,你可以使用cocke-kasami-younger(CKY)算法。在这里,我们将查看一些在生成解析器方面对您有用的信息。我们还将研究CKY算法的主要逻辑。

我们需要先看看我们正在考虑的假设,然后再开始使用算法。我们的技术假设是,这里的每个解析器子树都是独立的。这意味着,如果我们有一个树节点np,那么我们只关注这个np节点,而不是它的派生节点;每个子树独立工作。
一步一步开发解析器
1、您应该标记具有人工注释解析树的语料库:如果它是按照Penn-TreeBank注释格式标记的,那么您就可以开始了。
2、有了这个标记的解析语料库,您可以派生语法规则并为每个语法规则生成概率。
3、您应该应用CNF进行语法转换
4、使用概率语法规则并将其应用于大型语料库;使用Viterbi最大分数的CKY算法获得最可能的解析结构。如果您提供了大量的数据,那么您可以使用ML学习技术,将此问题作为一个多类分类器问题来处理。
5、最后一个阶段是根据概率值为给定数据获取最佳的解析树。
现有的分析器工具

斯坦福解析器(https://stanfordnlp.github.io/CoreNLP/)

from stanfordcorenlp import StanfordCoreNLP
 
nlp = StanfordCoreNLP(r'E:\JavaLibraries\stanford-corenlp-full-2018-10-05')
#这里改成你stanford-corenlp所在的目录
sentence = 'The boy put tortoise on the rug.'
print('Tokenize:', nlp.word_tokenize(sentence)) 
print('Part of Speech:', nlp.pos_tag(sentence)) 
print('Named Entities:', nlp.ner(sentence)) 
print('Constituency Parsing:', nlp.parse(sentence))
print('Dependency Parsing:', nlp.dependency_parse(sentence)) 
 
nlp.close() # Do not forget to close! The backend server will consume a lot memery.

Tokenize: ['The', 'boy', 'put', 'tortoise', 'on', 'the', 'rug', '.']
Part of Speech: [('The', 'DT'), ('boy', 'NN'), ('put', 'VBD'), ('tortoise', 'NN'), ('on', 'IN'), ('the', 'DT'), ('rug', 'NN'), ('.', '.')]
Named Entities: [('The', 'O'), ('boy', 'O'), ('put', 'O'), ('tortoise', 'O'), ('on', 'O'), ('the', 'O'), ('rug', 'O'), ('.', 'O')]
Constituency Parsing: (ROOT
  (S
    (NP (DT The) (NN boy))
    (VP (VBD put)
      (NP (NN tortoise))
      (PP (IN on)
        (NP (DT the) (NN rug))))
    (. .)))
Dependency Parsing: [('ROOT', 0, 3), ('det', 2, 1), ('nsubj', 3, 2), ('dobj', 3, 4), ('case', 7, 5), ('det', 7, 6), ('nmod', 3, 7), ('punct', 3, 8)]

如果要分析中文句子可以使用下面的代码:

# _*_coding:utf-8_*_
 
# Other human languages support, e.g. Chinese
 
sentence = '清华大学位于北京。'
 
with StanfordCoreNLP(r'E:\JavaLibraries\stanford-corenlp-full-2018-10-05', lang='zh') as nlp:
    print(nlp.word_tokenize(sentence))
    print(nlp.pos_tag(sentence))
    print(nlp.ner(sentence))
    print(nlp.parse(sentence))
    print(nlp.dependency_parse(sentence))
['清华', '大学', '位于', '北京', '。']
[('清华', 'NR'), ('大学', 'NN'), ('位于', 'VV'), ('北京', 'NR'), ('。', 'PU')]
[('清华', 'ORGANIZATION'), ('大学', 'ORGANIZATION'), ('位于', 'O'), ('北京', 'STATE_OR_PROVINCE'), ('。', 'O')]
(ROOT
  (IP
    (NP (NR 清华) (NN 大学))
    (VP (VV 位于)
      (NP (NR 北京)))
    (PU 。)))
[('ROOT', 0, 3), ('compound:nn', 2, 1), ('nsubj', 3, 2), ('dobj', 3, 4), ('punct', 3, 5)]

另外:启动CoreNLP服务器命令
在windows中,cmd中
cd E:\JavaLibraries\stanford-corenlp-full-2018-10-05

进入到Stanford CoreNLP目录中执行该命令
java -mx4g -cp “*” edu.stanford.nlp.pipeline.StanfordCoreNLPServer -port 9000 -timeout 15000

现在我们可以使用如下的代码来调用这个server:

# coding=utf-8
 
from stanfordcorenlp import StanfordCoreNLP
nlp = StanfordCoreNLP('http://localhost', port=9000)
#这里改成了我们server的地址
sentence = 'Functions shall be declared at file scope.'
print(nlp.word_tokenize(sentence))
print(nlp.pos_tag(sentence))
print(nlp.ner(sentence))
print(nlp.parse(sentence))
print(nlp.dependency_parse(sentence))
nlp.close()
['Functions', 'shall', 'be', 'declared', 'at', 'file', 'scope', '.']
[('Functions', 'NNS'), ('shall', 'MD'), ('be', 'VB'), ('declared', 'VBN'), ('at', 'IN'), ('file', 'NN'), ('scope', 'NN'), ('.', '.')]
[('Functions', 'O'), ('shall', 'O'), ('be', 'O'), ('declared', 'O'), ('at', 'O'), ('file', 'O'), ('scope', 'O'), ('.', 'O')]
(ROOT
  (S
    (NP (NNS Functions))
    (VP (MD shall)
      (VP (VB be)
        (VP (VBN declared)
          (PP (IN at)
            (NP (NN file) (NN scope))))))
    (. .)))
[('ROOT', 0, 4), ('nsubjpass', 4, 1), ('aux', 4, 2), ('auxpass', 4, 3), ('case', 7, 5), ('compound', 7, 6), ('nmod', 4, 7), ('punct', 4, 8)]

Spacy解析器

pip install spacy  (原因是因为spacy2.0版本的模块路径(spacy/lang/en);而spacy1.9版本不会报这种错误。)
python -m spacy download en
import spacy
from spacy.en import English
parser = English()
nlp = spacy.load('en')
example = u"The boy with the spotted dog quickly ran after the firetruck."
document = nlp(example)

这个 document 现在是 spacy.english 模型的一个 class,并关联上了许多的属性。它会输出 document 中各种各样的属性,例如:token、token 的 index、词性标注、实体、向量、情感、单词等。下面让我们会对其中的一些属性进行一番探究。

  • Tokenization

spaCy 的 document 可以在 tokenized 过程中被分割成单句,这些单句还可以进一步分割成单词。你可以通过遍历文档来读取这些单词:

document[0]
The
document[len(document)-5]
ran
list(document.sents)
[The boy with the spotted dog quickly ran after the firetruck.]
  • 词性标注

词性标注即标注语法正确的句子中的词语的词性。这些标注可以用于信息过滤、统计模型,或者基于某些规则进行文本解析。

all_tags = {w.pos: w.pos_ for w in document}
all_tags
{82: 'ADJ',
 83: 'ADP',
 84: 'ADV',
 88: 'DET',
 90: 'NOUN',
 95: 'PUNCT',
 98: 'VERB'}
for word in list(document.sents)[0]:  
    print(word, word.tag_)
The DT
boy NN
with IN
the DT
spotted JJ
dog NN
quickly RB
ran VBD
after IN
the DT
firetruck NN
. .

来看一看 document 中的最常用词汇。我已经事先写好了预处理和文本数据清洗的函数。

#一些参数定义
noisy_pos_tags = ["PROP"]
min_token_length = 2

#检查 token 是不是噪音的函数
def isNoise(token):     
    is_noise = False
    if token.pos_ in noisy_pos_tags:
        is_noise = True
    elif token.is_stop == True:
        is_noise = True
    elif len(token.string) <= min_token_length:
        is_noise = True
    return is_noise
def cleanup(token, lower = True):
    if lower:
       token = token.lower()
    return token.strip()

# 评论中最常用的单词
from collections import Counter
cleaned_list = [cleanup(word.string) for word in document if not isNoise(word)]
Counter(cleaned_list) .most_common(5)

[('boy', 1), ('firetruck', 1), ('ran', 1), ('spotted', 1), ('dog', 1)]
  • 实体识别
    spaCy 拥有一个快速实体识别模型,这个实体识别模型能够从 document 中找出实体短语。它能识别各种类型的实体,例如人名、位置、机构、日期、数字等。你可以通过“.ents”属性来读取这些实体。下面让我们来获取我们 document 中所有类型的命名实体:
labels = set([w.label_ for w in document.ents])
for label in labels:
    entities = [cleanup(e.string, lower=False) for e in document.ents if label==e.label_]
    entities = list(set(entities))
    print(label,entities) 
  • 依存句法分析
    spaCy 最强大的功能之一就是它可以通过调用轻量级的 API 来实现又快又准确的依存分析。这个分析器也可以用于句子边界检测以及区分短语块。依存关系可以通过“.children”、“.root”、“.ancestor”等属性读取。
# 取出所有句中包含“dog”单词的评论
dog = [sent for sent in document.sents if 'dog' in sent.string.lower()]

# 创建依存树
sentence = dog[0] 
for word in sentence:
    print(word, ': ', str(list(word.children))) 
The :  []
boy :  [The, with]
with :  []
the :  []
spotted :  []
dog :  [the, spotted]
quickly :  []
ran :  [boy, dog, quickly, after, .]
after :  [firetruck]
the :  []
firetruck :  [the]
. :  []

5.2.2 词性标注和词性标注器

理解词性标注和词性标注器的概念

词性标注是在语料库中标记与词性标记相对应的单词的过程。这个词的词性取决于它的定义和它的背景。它也被称为语法标记或词类消歧。词性标注还取决于它们给定的短语、句子和段落相邻和相关单词的关系。

词性标注器是用于为给定数据分配词性标注的工具。根据句子结构和意义,词性标注不是一项简单的任务,让我们举个例子。我们用狗这个词吧;一般来说,我们都知道狗是复数名词,但在某些句子中它充当动词。看句子:The sailor dogs the hatch。在这里,狗的正确词性是动词而不是复数名词。一般来说,许多词性标注器使用宾夕法尼亚大学生成的词性标注。
Clause Level

S - simple declarative clause, i.e. one that is not introduced by a (possible empty) subordinating conjunction or a wh-word and that does not exhibit subject-verb inversion.
SBAR - Clause introduced by a (possibly empty) subordinating conjunction.
SBARQ - Direct question introduced by a wh-word or a wh-phrase. Indirect questions and relative clauses should be bracketed as SBAR, not SBARQ.
SINV - Inverted declarative sentence, i.e. one in which the subject follows the tensed verb or modal.
SQ - Inverted yes/no question, or main clause of a wh-question, following the wh-phrase in SBARQ.

Phrase Level

ADJP - Adjective Phrase.
ADVP - Adverb Phrase.
CONJP - Conjunction Phrase.
FRAG - Fragment.
INTJ - Interjection. Corresponds approximately to the part-of-speech tag UH.
LST - List marker. Includes surrounding punctuation.
NAC - Not a Constituent; used to show the scope of certain prenominal modifiers within an NP.
NP - Noun Phrase.
NX - Used within certain complex NPs to mark the head of the NP. Corresponds very roughly to N-bar level but used quite differently.
PP - Prepositional Phrase.
PRN - Parenthetical.
PRT - Particle. Category for words that should be tagged RP.
QP - Quantifier Phrase (i.e. complex measure/amount phrase); used within NP.
RRC - Reduced Relative Clause.
UCP - Unlike Coordinated Phrase.
VP - Vereb Phrase.
WHADJP - Wh-adjective Phrase. Adjectival phrase containing a wh-adverb, as in how hot.
WHAVP - Wh-adverb Phrase. Introduces a clause with an NP gap. May be null (containing the 0 complementizer) or lexical, containing a wh-adverb such as how or why.
WHNP - Wh-noun Phrase. Introduces a clause with an NP gap. May be null (containing the 0 complementizer) or lexical, containing some wh-word, e.g. who, which book, whose daughter, none of which, or how many leopards.
WHPP - Wh-prepositional Phrase. Prepositional phrase containing a wh-noun phrase (such as of which or by whose authority) that either introduces a PP gap or is contained by a WHNP.
X - Unknown, uncertain, or unbracketable. X is often used for bracketing typos and in bracketing the…the-constructions.

Word level

CC - Coordinating conjunction
CD - Cardinal number
DT - Determiner
EX - Existential there
FW - Foreign word
IN - Preposition or subordinating conjunction
JJ - Adjective
JJR - Adjective, comparative
JJS - Adjective, superlative
LS - List item marker
MD - Modal
NN - Noun, singular or mass
NNS - Noun, plural
NNP - Proper noun, singular
NNPS - Proper noun, plural
PDT - Predeterminer
POS - Possessive ending
PRP - Personal pronoun
PRP$ - Possessive pronoun (prolog version PRP-S)
RB - Adverb
RBR - Adverb, comparative
RBS - Adverb, superlative
RP - Particle
SYM - Symbol
TO - to
UH - Interjection
VB - Verb, base form
VBD - Verb, past tense
VBG - Verb, gerund or present participle
VBN - Verb, past participle
VBP - Verb, non-3rd person singular present
VBZ - Verb, 3rd person singular present
WDT - Wh-determiner
WP - Wh-pronoun
WP$ - Possessive wh-pronoun (prolog version WP-S)
WRB - Wh-adverb

一步步开发词性标注器

1、你需要一个标记的语料库
2、选择特征
3、使用Python库中scikit-learn提供的决策树分类器执行训练
4、检查你的准确性
5、试着用你自己训练的模型来预测词性标注

import nltk
from nltk import word_tokenize
import pprint
from sklearn.tree import DecisionTreeClassifier
from sklearn.feature_extraction import DictVectorizer
from sklearn.pipeline import Pipeline
tagged_sentences = nltk.corpus.treebank.tagged_sents()
print( tagged_sentences[0])
[('Pierre', 'NNP'), ('Vinken', 'NNP'), (',', ','), ('61', 'CD'), ('years', 'NNS'), ('old', 'JJ'), (',', ','), ('will', 'MD'), ('join', 'VB'), ('the', 'DT'), ('board', 'NN'), ('as', 'IN'), ('a', 'DT'), ('nonexecutive', 'JJ'), ('director', 'NN'), ('Nov.', 'NNP'), ('29', 'CD'), ('.', '.')]
def features(sentence, index):
    " sentence: [w1, w2, ...], index: the index of the word "
    return {
    'word': sentence[index],
    'is_first': index == 0,
    'is_last': index == len(sentence) - 1,
    'is_capitalized': sentence[index][0].upper() == sentence[index][0],
    'is_all_caps': sentence[index].upper() == sentence[index],
    'is_all_lower': sentence[index].lower() == sentence[index],
    'prefix-1': sentence[index][0],
    'prefix-2': sentence[index][:2],
    'prefix-3': sentence[index][:3],
    'suffix-1': sentence[index][-1],
    'suffix-2': sentence[index][-2:],
    'suffix-3': sentence[index][-3:],
    'prev_word': '' if index == 0 else sentence[index - 1],
    'next_word': '' if index == len(sentence) - 1 else sentence[index + 1],
    'has_hyphen': '-' in sentence[index],
    'is_numeric': sentence[index].isdigit(),
    'capitals_inside': sentence[index][1:].lower() != sentence[index][1:]
    }
pprint.pprint(features(['This', 'is', 'a', 'sentence'], 2))
{'capitals_inside': False,
 'has_hyphen': False,
 'is_all_caps': False,
 'is_all_lower': True,
 'is_capitalized': False,
 'is_first': False,
 'is_last': False,
 'is_numeric': False,
 'next_word': 'sentence',
 'prefix-1': 'a',
 'prefix-2': 'a',
 'prefix-3': 'a',
 'prev_word': 'is',
 'suffix-1': 'a',
 'suffix-2': 'a',
 'suffix-3': 'a',
 'word': 'a'}
cutoff = int(.75 * len(tagged_sentences))
training_sentences = tagged_sentences[:cutoff]
test_sentences = tagged_sentences[cutoff:]
def untag(tagged_sentence):
    return [w for w, t in tagged_sentence]

def transform_to_dataset(tagged_sentences):
    X, y = [], []
    for tagged in tagged_sentences:
        for index in range(len(tagged)):
            X.append(features(untag(tagged), index))
            y.append(tagged[index][1])
            #print "index:"+str(index)+"original word:"+str(tagged)+"Word:"+str(untag(tagged))+"  Y:"+y[index]
    return X, y
X, y = transform_to_dataset(training_sentences)
clf = Pipeline([
    ('vectorizer', DictVectorizer(sparse=False)),
    ('classifier', DecisionTreeClassifier(criterion='entropy'))
])

clf.fit(X[:10000],
        y[:10000])  # Use only the first 10K samples if you're running it multiple times. It takes a fair bit :)

print('Training completed') 

X_test, y_test = transform_to_dataset(test_sentences)

print("Accuracy:", clf.score(X_test, y_test)) 
Training completed
Accuracy: 0.8959505061867267
def pos_tag(sentence):
    tagged_sentence = []
    tags = clf.predict([features(sentence, index) for index in range(len(sentence))])
    return zip(sentence, tags)
sentence = 'This is my friend, John.'
word_tokenize('This is my friend, John.')
['This', 'is', 'my', 'friend', ',', 'John', '.']
list(pos_tag(word_tokenize('This is my friend, John.')))
[('This', 'DT'),
 ('is', 'VBZ'),
 ('my', 'NN'),
 ('friend', 'NN'),
 (',', ','),
 ('John', 'NNP'),
 ('.', '.')]

即插即用现有词性标注器

Stanford 词性标注器

from stanfordcorenlp import StanfordCoreNLP
 
nlp = StanfordCoreNLP(r'E:\JavaLibraries\stanford-corenlp-full-2018-10-05')

text = "This is a car."
print('Part of Speech:', nlp.pos_tag(text)) 
Part of Speech: [('This', 'DT'), ('is', 'VBZ'), ('a', 'DT'), ('car', 'NN'), ('.', '.')]

使用词性标注作为特征

既然我们已经使用pos标记器为文本数据生成了pos标记,那么我们可以在哪里用它们?我们现在将介绍可以使用这些POS标签作为功能的NLP应用程序。

使用机器学习构建聊天机器人时,POS标签非常重要。当一台机器必须理解各种各样的句子结构。如果您正在构建一个标识多字的快递(MWE)系统,那么它也很有用。你知道,一些mwe短语的例子可以什么,等等。
如果你有句话:He backed off from the tour plan of Paris。这里,backed off是MWE。要在句子中识别这些类型的mwe,可以使用pos标记和pos标记序列作为特征。你可以在情绪分析中使用pos标签,还有其他的应用程序也是如此。

挑战

以下是POS标签面临的一些挑战:

  • 在不明确的语法结构中,很难为某些单词识别正确的pos标记,如果该单词具有非常不同的上下文意义,则pos标记器可能会生成错误的pos标记。

现在让我们继续下一节,在这里我们将学习如何在句子中找到不同的实体。

5.2.3 命名实体识别

在本节中,我们将介绍一个名为实体识别(NER)的工具。此工具的使用方法如下。如果你有句话,比如Bank of America announced its earning
today,我们作为人类可以理解,Bank of America是一个金融机构的名称,应该被称为一个单独的实体。然而,对于机器来说,处理和识别那个实体是相当具有挑战性的。在那里,NER工具进入画面来拯救我们。使用NER工具,您可以找到诸如人名、组织名称、位置等实体。NER工具具有特定的类,在这些类中它们对实体进行分类。在这里,我们正在考虑句子中的单词,以找出这些实体,以及句子中是否存在任何实体。让我们使用一些可用的NER工具,在我们的句子中找到更多关于什么样的实体的详细信息。

NER类

NER工具通常将实体划分为一些预定义的类。不同的NER工具有不同类型的类。斯坦福NER工具基于NER类有三个不同的版本:

第一个版本是三类NER工具,可以识别实体——无论是位置、人员还是组织。

第二个版本是四类NER工具,可以识别位置、人员、组织和杂项。杂项被称为杂项实体类型。如果实体不属于位置、人员或组织,并且仍然是实体,则可以将其标记为杂项。

第三个版本是一个七类工具,可以识别人员、位置、组织、资金、百分比、日期和时间。 SPAcy解析器还具有一个NER包,可用于以下类。

PERSON类 标识一个人的姓名
NORP类 指民族、宗教或政治团体。
FACILITY类 包括建筑物、机场、公路等
ORG类 组织、机构等的组织类
GPE类 城市、国家等的GPE课程
LOC类 非GPE位置(如山脉和水体)的LOC等级
PRODUCT类 包括物品、车辆、食物等但不包括服务的产品
EVENT类 体育赛事、战争、命名飓风等的活动类
WORK_OF_ART类 书名、歌曲等艺术类作品
LANGUAGE类 标记任何命名语言的语言
除此之外,Spacy的NER包还包含日期、时间、百分比、货币、数量、序数和基数等类。

Stanford NER

from stanfordcorenlp import StanfordCoreNLP
 
nlp = StanfordCoreNLP(r'E:\JavaLibraries\stanford-corenlp-full-2018-10-05')

text = 'While in France, Christine Lagarde discussed short-term ' \
       'stimulus efforts in a recent interview at 5:00 P.M with the Wall Street Journal.'

print('Named Entities:', nlp.ner(text)) 

Named Entities: [('While', 'O'), ('in', 'O'), ('France', 'COUNTRY'), (',', 'O'), ('Christine', 'PERSON'), ('Lagarde', 'PERSON'), ('discussed', 'O'), ('short-term', 'O'), ('stimulus', 'O'), ('efforts', 'O'), ('in', 'O'), ('a', 'O'), ('recent', 'O'), ('interview', 'O'), ('at', 'O'), ('5:00', 'TIME'), ('P.M', 'TIME'), ('with', 'O'), ('the', 'O'), ('Wall', 'ORGANIZATION'), ('Street', 'ORGANIZATION'), ('Journal', 'ORGANIZATION'), ('.', 'O')]

Spacy NER

import spacy
nlp = spacy.load('en')
doc = nlp(u'London is a big city in the United Kingdom.')
for ent in doc.ents:
    print(ent.label_, ent.text)
GPE London
GPE the United Kingdom
doc1 = nlp(u'While in France, Christine Lagarde discussed short-term stimulus efforts in a '
           u'recent interview on 5:00 P.M. with the Wall Street Journal')
for ent1 in doc1.ents:
    print(ent1.label_, ent1.text)
GPE France
PERSON Christine Lagarde
TIME 5:00 P.M.
ORG the Wall Street Journal

提取和理解特征

NER标签非常重要,因为它们帮助你理解句子结构,帮助机器或NLP系统理解句子中某些单词的含义。
让我们举个例子。如果您正在构建校对工具,那么这个NER工具非常有用,因为NER工具可以找到一个人的姓名、一个组织的名称、与货币相关的符号、数字格式等,这将帮助您的校对工具识别文本中存在的异常情况。然后,根据NER标签,系统可以建议必要的改变。以这句话来说,美国银行今天早上宣布了它的盈利。在这种情况下,NER工具为美国银行提供了标签组织,这有助于我们的系统更好地理解句子的含义和句子的结构。

如果您正在构建一个问答系统,那么NER标签也非常重要,因为提取该系统中的实体非常重要。生成实体后,可以使用句法关系来理解问题。在此阶段之后,您可以处理问题并生成答案。

挑战

NER工具在封闭域数据集上训练。因此,为一个领域开发的NER系统在另一个领域通常表现不好。这需要一个通用的NER工具,它可以适用于所有领域,并且在培训之后,它应该能够足够的通用化来处理看不见的情况。

有时你会发现一些单词,这些单词既是地点的名字,也是一个人的名字。NER工具无法处理一个单词可以表示为位置名称、人名和组织名称的情况。对于所有的NER工具来说,这是一个非常具有挑战性的案例。假设你有“塔塔医院”这个词;

单字塔塔可以是一个人的名字,也可以是一个组织的名字。在这种情况下,NER工具不能决定Tata是一个人的名字还是一个组织的名字。

专门为微博网站平台构建一个NER工具也是一项具有挑战性的任务。

5.2.4 n元语法

from nltk import ngrams
sentence = 'this is a foo bar sentences and i want to ngramize it'
n = 4 # you can give 4, 5, 1 or any number less than sentences length
ngramsres = ngrams(sentence.split(), n)
for grams in ngramsres:
    print(grams)
('this', 'is', 'a', 'foo')
('is', 'a', 'foo', 'bar')
('a', 'foo', 'bar', 'sentences')
('foo', 'bar', 'sentences', 'and')
('bar', 'sentences', 'and', 'i')
('sentences', 'and', 'i', 'want')
('and', 'i', 'want', 'to')
('i', 'want', 'to', 'ngramize')
('want', 'to', 'ngramize', 'it')

5.2.5 词袋

理解 BOW

Text document 1: John likes to watch cricket. Chris likes cricket too.
Text document 2: John also likes to watch movies.List of words= [“John”, “likes”, “to”, “watch”, “cricket”, “Chris”, “too”,“also”, “movies”]这个列表叫BOW

频率计数 Document 1: [1, 2, 1, 1, 2, 1, 1, 0, 0]
频率计数 Document 2: [1, 1, 1, 1, 0, 0, 0, 1, 1]那么,我们是如何生成频率计数列表的呢?为了生成文档1的频率计数,请考虑单词列表并检查每个列出的单词在文档1中出现的次数。在这里,我们首先要说的是约翰这个词

在文档1中一次;文档1的频率计数为1。文档1的频率计数:[1]。对于第二个条目,like两次出现在文档1中,因此频率计数为2。文档1的频率计数:[1,2]。现在,我们要第三个

我们列表中的单词是to。这个词出现在文档1中一次,所以我们将频率中的第三个条目计数为1。文档1的频率计数:[1,2,1]。我们以同样的方式生成了文档1和文档2的频率计数。我们将在本章的下一节tf-idf中进一步了解频率。

from sklearn.feature_extraction.text import CountVectorizer
import numpy as np
ngram_vectorizer = CountVectorizer(analyzer='char_wb', ngram_range=(2, 2), min_df=1)
counts = ngram_vectorizer.fit_transform(['words', 'wprds'])
ngram_vectorizer.get_feature_names() == ([' w', 'ds', 'or', 'pr', 'rd', 's ', 'wo', 'wp'])
True
print(counts.toarray().astype(int))
[[1 1 1 0 1 1 1 0]
 [1 1 0 1 1 1 0 1]]

比较n-grams和BOW

我们已经研究了n-grams和bow的概念。现在让我们来看看N-grams和bow是如何不同或相互关联的。

让我们先讨论一下区别。这里,区别在于它们在NLP应用程序中的用法。在n-grams中,词序很重要,而在bow中,维持词序并不重要。在NLP应用程序中,n-gram用于按实际顺序考虑单词,以便我们了解特定单词的上下文;bow用于为文本数据集构建词汇表。

现在让我们来看看N-grams和bow之间的一些有意义的关系,这将让您了解N-grams和bow是如何相互关联的。如果您将ngram视为一个特性,那么bow是使用unigram派生的文本表示。因此,在这种情况下,n-gram等于特征,bow等于使用其中包含的unigram(1 gram)表示文本。

现在,我们来看看BOW的应用程序。
如果您想制作一个NLP应用程序,将文档分类为不同的类别,那么您可以使用bow。

bow还用于从数据集中生成频率计数和词汇表。这些派生属性随后用于NLP应用程序,如情感分析、word2vec等。

5.2.6 语义工具及资源

语义分析算法采用词频-逆文档频率(tf idf)和线性代数的概念,如余弦相似性和欧氏距离,找到意思相近的词。这些技术是分布语义学。另一个是word2vec。这是最近由google和可以帮助我们找到具有相似含义的单词和单词的语义。我们将在第6章“高级功能工程”中探讨word2vec和其他技术和NLP算法。
除了word2vec之外,另一个强大的资源是wordnet,它是我们可用的最大语料库,并且被人类标记。它还包含每个单词的意义标签。这些数据库确实有助于找出特定单词的语义。

5.3 NLP中的基础统计特征

5.3.1 数学基础

线性代数

标量:它们只是一个,实数

向量:它们是一维数字数组

矩阵:它们是二维数字数组

张量:它们是n维的数字数组。

概率论

独立和依赖的事件
条件概率

5.3.2 TF-IDF

TF(t) = (Number of times term t appears in a document) / (Total number of terms in the document)
IDF(t) = log10(Total number of documents / Number of documents with term t in it)
TF * IDF = [ (Number of times term t appears in a document) / (Total number of terms in the
document) ] * log10(Total number of documents / Number of documents with term t in it)

from textblob import TextBlob
import math

def tf(word, blob):
       return blob.words.count(word) / len(blob.words)

def n_containing(word, bloblist):
    return 1 + sum(1 for blob in bloblist if word in blob)

def idf(word, bloblist):
    x = n_containing(word, bloblist)
    return math.log(len(bloblist) / (x if x else 1))

def tfidf(word, blob, bloblist):
    return tf(word, blob) * idf(word, bloblist)

text = 'tf idf, short form of term frequency, inverse document frequency'
text2 = 'is a numerical statistic that is intended to reflect how important'
text3 = 'a word is to a document in a collection or corpus'

blob = TextBlob(text)
blob2 = TextBlob(text2)
blob3 = TextBlob(text3)
bloblist = [blob, blob2, blob3]
tf_score = tf('short', blob)
idf_score = idf('short', bloblist)
tfidf_score = tfidf('short', blob, bloblist)

print("tf score for word short--- "+ str(tf_score)+"\n") 
print("idf score for word short--- "+ str(idf_score)+"\n") 
print("tf x idf score of word short--- "+str(tfidf_score)) 
tf score for word short--- 0.1

idf score for word short--- 0.4054651081081644

tf x idf score of word short--- 0.04054651081081644
import numpy as np
import nltk
import string
import os
from nltk.stem.porter import *
from sklearn.feature_extraction.text import TfidfVectorizer
from collections import Counter
from nltk.corpus import stopwords


def get_tokens():
    with open('./TFIDFdemo/shakes/shakes1.txt', 'r') as shakes:
        text = shakes.read()
        lowers = text.lower()
        #remove the punctuation using the character deletion step of translate
        no_punctuation = lowers.translate(string.punctuation)
        tokens = nltk.word_tokenize(no_punctuation)
    return tokens

tokens = get_tokens()
count = Counter(tokens)
#print count.most_common(10)

tokens = get_tokens()
filtered = [w for w in tokens if not w in stopwords.words('english')]
count = Counter(filtered)
#print count.most_common(100)

def stem_tokens(tokens, stemmer):
    stemmed = []
    for item in tokens:
        stemmed.append(stemmer.stem(item))
    return stemmed

stemmer = PorterStemmer()
stemmed = stem_tokens(filtered, stemmer)
count = Counter(stemmed)
#print count.most_common(100)

path = './TFIDFdemo/shakes'
token_dict = {}
stemmer = PorterStemmer()


def stem_tokens(tokens, stemmer):
    stemmed = []
    for item in tokens:
        stemmed.append(stemmer.stem(item))
    return stemmed


def tokenize(text):
    tokens = nltk.word_tokenize(text)
    stems = stem_tokens(tokens, stemmer)
    return stems


for subdir, dirs, files in os.walk(path):
    for file in files:
        file_path = subdir + os.path.sep + file
        shakes = open(file_path, 'r')
        text = shakes.read()
        lowers = text.lower()
        no_punctuation = lowers.translate(string.punctuation)
        token_dict[file] = no_punctuation

# this can take some time
tfidf = TfidfVectorizer(tokenizer=tokenize, stop_words='english')
tfs = tfidf.fit_transform(token_dict.values())

str = 'this sentence has unseen text such as computer but also king lord juliet'
response = tfidf.transform([str])
#print response


feature_names = tfidf.get_feature_names()
for col in response.nonzero()[1]:
    print(feature_names[col], ' - ', response[0, col]) 


feature_array = np.array(tfidf.get_feature_names())
tfidf_sorting = np.argsort(response.toarray()).flatten()[::-1]
n = 3
top_n = feature_array[tfidf_sorting][:n]
print(top_n) 

n = 4
top_n = feature_array[tfidf_sorting][:n]
print(top_n) 

d:\Program Files\Anaconda3\lib\site-packages\sklearn\feature_extraction\text.py:1015: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  if hasattr(X, 'dtype') and np.issubdtype(X.dtype, np.float):


thi  -  0.34618161159873423
lord  -  0.6633846138519129
king  -  0.6633846138519129
['king' 'lord' 'thi']
['king' 'lord' 'thi' 'excel']

一般来说,使用tf-idf可以方便地进行文本数据分析。您可以获得有关数据集最准确关键字的信息。

如果您正在开发一个文本摘要应用程序,其中有一个选定的统计方法,那么tf-idf是生成文档摘要最重要的功能。

搜索引擎经常使用tf-idf加权方案的变化来找出文档与给定用户查询的相关性的评分和排名。

文档分类应用程序与bow一起使用此技术。

5.3.3 向量化

编码器和解码器
独热编码

假设您有一个大小为n的词汇表。近似语言状态的方法是以一种热编码的形式表示这些单词。此技术用于将单词映射到长度n的向量,其中n位表示

特定词的存在。如果您将单词转换为一种热编码格式,那么您将看到诸如0000…001、0000…100、0000…010等向量。词汇表中的每一个词都由一个二进制向量的组合表示。在这里,

每个向量的第n位表示词汇表中第n个单词的存在。那么,这些向量是如何与语料库中的句子或其他单词相关的呢?让我们来看一个例子,它将帮助您理解这个概念。

Jalaj likes NLP

00010 00001 10000

import pandas as pd
from sklearn.feature_extraction import DictVectorizer
df = pd.DataFrame([['rick','young'],['phil','old']],columns=['name','age-group'])
df
nameage-group
0rickyoung
1philold
pd.get_dummies(df)
name_philname_rickage-group_oldage-group_young
00.01.00.01.0
11.00.01.00.0
X = pd.DataFrame({'income': [100000,110000,90000,30000,14000,50000],
                  'country':['US', 'CAN', 'US', 'CAN', 'MEX', 'US'],
                  'race':['White', 'Black', 'Latino', 'White', 'White', 'Black']})
v = DictVectorizer()
qualitative_features = ['country']
X_qual = v.fit_transform(X[qualitative_features].to_dict('records'))
print(v.vocabulary_)
{'country=US': 2, 'country=CAN': 0, 'country=MEX': 1}
print(X_qual.toarray())
[[0. 0. 1.]
 [1. 0. 0.]
 [0. 0. 1.]
 [1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]

5.3.4 规范化

min-max scaling

5.3.5 概率模型

Markov假设

P(the | its water is so transparent that) = P(the | that) or you can consider last two words P(the | its water is so transparent that) = P(the | transparent that)

最大似然估计

5.3.6 索引

5.3.7 排序

5.4 特征工程的优点

更好的功能给你很大的灵活性。即使你选择了一个不太理想的ML算法,你也会得到一个很好的结果。好的功能为您提供选择算法的灵活性;即使您选择一个不太复杂的模型,也会得到很好的精度。

如果您选择了好的特性,那么即使是简单的ML算法也能做得很好。

更好的功能将使您获得更好的准确性。您应该在特性工程上花费更多的时间来为数据集生成适当的特性。如果你得到了最好和合适的特性,你就赢得了大部分的挑战。

5.5 特征工程面临的挑战

在NLP域中,您可以很容易地派生出属于分类特性或基本NLP特性的特性。我们必须把这些特征转换成数字格式。这是最具挑战性的部分。

将文本数据转换为数字格式的有效方法非常具有挑战性。这里,试错法可以帮助您。

虽然有一些技术可以使用,如tf-idf,onehot编码,排名,共轭矩阵,嵌入单词,word2vec,等等,以将您的文本数据转换成数字格式,但没有很多方法,所以人们发现这部分具有挑战性。

5.6 总结

在本章中,我们看到了许多在NLP领域中广泛使用的概念和工具。所有这些概念都是功能工程的基本构建块。当您想要生成特性以生成NLP应用程序时,可以使用这些技术中的任何一种。我们已经研究了parse、pos-tagers、ner、n-grams和bag-of-words如何生成自然语言相关的特性。我们还探讨了它们是如何构建的,以及在您需要自定义的情况下,调整一些现有工具的不同方法是什么。

开发NLP应用程序的功能。此外,我们还看到了线性代数、统计学和概率的基本概念。我们还看到了概率的基本概念,这些概念将在未来的ML算法中使用。我们已经研究了一些很酷的概念,如tf-idf、索引、排名等,以及作为概率模型一部分的语言模型。

在下一章中,我们将介绍诸如word2vec、doc2vec、glove等高级功能。所有这些算法都是单词嵌入技术的一部分。这些技术将帮助我们有效地将文本特性转换为数字格式,特别是当我们需要使用语义时。下一章将为您提供更多关于word2vec算法的详细信息。我们将介绍word2vec模型背后的每一项技术。我们还将了解如何使用人工神经网络(ann)来生成单词之间的语义关系,然后我们将从单词级别、句子级别、文档级别等方面探索这个概念的扩展。我们将为word2vec构建一个包含一些很棒的可视化的应用程序。我们还将讨论矢量化的重要性,所以继续阅读!

致谢
《Python自然语言处理》1 2 3,作者:【印】雅兰·萨纳卡(Jalaj Thanaki),是实践性很强的一部新作。为进一步深入理解书中内容,对部分内容进行了延伸学习、练习,在此分享,期待对大家有所帮助,欢迎加我微信(验证:NLP),一起学习讨论,不足之处,欢迎指正。
在这里插入图片描述

参考文献


  1. https://github.com/jalajthanaki ↩︎

  2. 《Python自然语言处理》,(印)雅兰·萨纳卡(Jalaj Thanaki) 著 张金超 、 刘舒曼 等 译 ,机械工业出版社,2018 ↩︎

  3. Jalaj Thanaki ,Python Natural Language Processing ,2017 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值