Python 机器学习算法交易实用指南(四)(2)https://developer.aliyun.com/article/1523384
从文本到标记 - NLP 管道
在本节中,我们将演示如何使用开源 Python 库spaCy
构建 NLP 管道。textacy
库构建在spaCy
之上,并提供对spaCy
属性和附加功能的简便访问。
有关以下代码示例、安装说明和额外详细信息,请参考nlp_pipeline_with_spaCy
笔记本。
spaCy 和 textacy 的 NLP 管道
spaCy
是一个广泛使用的 Python 库,具有多语言快速文本处理的全面功能集。标记化和注释引擎的使用需要安装语言模型。本章中我们将使用的特征仅需要小型模型;较大的模型还包括我们将在第十五章中介绍的词向量,Word Embeddings。
安装并链接后,我们可以实例化一个 spaCy
语言模型,然后在文档上调用它。因此,spaCy
会产生一个 doc
对象,该对象对文本进行标记化并根据可配置的管道组件进行处理,默认情况下包括标签器、解析器和命名实体识别器:
nlp = spacy.load('en') nlp.pipe_names ['tagger', 'parser', 'ner']
让我们使用一个简单的句子说明流程:
sample_text = 'Apple is looking at buying U.K. startup for $1 billion' doc = nlp(sample_text)
解析、标记和注释句子
解析后的文档内容可迭代,每个元素由处理流程产生的多个属性组成。以下示例说明了如何访问以下属性:
.text
: 原始词文本.lemma_
: 词根.pos_
: 基本词性标签.tag_
: 详细的词性标签.dep_
: 标记之间的句法关系或依赖关系.shape_
: 词的形状,即大写、标点或数字的使用情况.is alpha
: 检查标记是否是字母数字字符.is stop
: 检查标记是否在给定语言的常用词列表中
我们遍历每个标记,并将其属性分配给 pd.DataFrame
:
pd.DataFrame([[t.text, t.lemma_, t.pos_, t.tag_, t.dep_, t.shape_, t.is_alpha, t.is_stop] for t in doc], columns=['text', 'lemma', 'pos', 'tag', 'dep', 'shape', 'is_alpha', 'is_stop'])
产生以下输出:
文本 | 词根 | 词性 | 标签 | 依存关系 | 形状 | 是字母 | 是停用词 |
苹果 | 苹果 | PROPN | NNP | nsubj | Xxxxx | TRUE | FALSE |
是 | 是 | VERB | VBZ | 辅助动词 | xx | TRUE | TRUE |
寻找 | 寻找 | VERB | VBG | ROOT | xxxx | TRUE | FALSE |
在 | 在 | ADP | IN | prep | xx | TRUE | TRUE |
购买 | 购买 | VERB | VBG | pcomp | xxxx | TRUE | FALSE |
英国 | 英国 | PROPN | NNP | compound | X.X. | FALSE | FALSE |
创业公司 | 创业公司 | NOUN | NN | dobj | xxxx | TRUE | FALSE |
for | for | ADP | IN | prep | xxx | TRUE | TRUE |
$ | $ | SYM | $ | 量词修饰符 | $ | FALSE | FALSE |
1 | 1 | NUM | CD | compound | d | FALSE | FALSE |
十亿 | 十亿 | NUM | CD | pobj | xxxx | TRUE | FALSE |
我们可以使用以下方法在浏览器或笔记本中可视化句法依存:
displacy.render(doc, style='dep', options=options, jupyter=True)
结果是一个依存树:
依存树
我们可以使用 spacy.explain()
获取属性含义的其他见解,如下所示:
spacy.explain("VBZ") verb, 3rd person singular present
批处理文档
现在我们将阅读一组更大的 2,225 篇 BBC 新闻文章(有关数据来源的详细信息,请参阅 GitHub),这些文章属于五个类别,并存储在单独的文本文件中。我们需要执行以下操作:
- 调用 pathlib 的
Path
对象的.glob()
方法。 - 迭代结果路径列表。
- 读取新闻文章的所有行,不包括第一行的标题。
- 将清理后的结果附加到列表中:
files = Path('..', 'data', 'bbc').glob('**/*.txt') bbc_articles = [] for i, file in enumerate(files): _, _, _, topic, file_name = file.parts with file.open(encoding='latin1') as f: lines = f.readlines() body = ' '.join([l.strip() for l in lines[1:]]).strip() bbc_articles.append(body) len(bbc_articles) 2225
句子边界检测
我们将通过调用 NLP 对象来说明句子检测,对第一篇文章进行批处理:
doc = nlp(bbc_articles[0]) type(doc) spacy.tokens.doc.Doc
spaCy
从句法分析树中计算句子边界,因此标点符号和大写字母起着重要但不决定性的作用。因此,边界将与从句边界重合,即使是标点符号不规范的文本也是如此。
我们可以使用.sents
属性访问解析后的句子:
sentences = [s for s in doc.sents] sentences[:3] [Voting is under way for the annual Bloggies which recognize the best web blogs - online spaces where people publish their thoughts - of the year. , Nominations were announced on Sunday, but traffic to the official site was so heavy that the website was temporarily closed because of too many visitors., Weblogs have been nominated in 30 categories, from the top regional blog, to the best-kept-secret blog.]
命名实体识别
spaCy
使用 .ent_type_ 属性
实现命名实体识别:
for t in sentences[0]: if t.ent_type_: print('{} | {} | {}'.format(t.text, t.ent_type_, spacy.explain(t.ent_type_))) annual | DATE | Absolute or relative dates or periods the | DATE | Absolute or relative dates or periods year | DATE | Absolute or relative dates or periods
textacy
简化了访问第一篇文章中出现的命名实体的过程:
from textacy.extract import named_entities entities = [e.text for e in named_entities(doc)] pd.Series(entities).value_counts() year 4 US 2 South-East Asia Earthquake 2 annual 2 Tsunami Blog 2
N-grams
N-gram 结合 N 个连续的标记。在 BoW 模型中,N-gram 可以很有用,因为根据文本环境,将像数据科学家这样的内容视为一个单独的标记可能比将其视为两个不同的标记更有意义。
textacy
方便查看至少出现 min_freq
次的给定长度 n 的 ngrams
:
from textacy.extract import ngrams pd.Series([n.text for n in ngrams(doc, n=2, min_freq=2)]).value_counts() East Asia 2 Asia Earthquake 2 Tsunami Blog 2 annual Bloggies 2
spaCy 的流式 API
要通过处理管道传递更多文档,我们可以使用 spaCy
的流式 API 如下:
iter_texts = (bbc_articles[i] for i in range(len(bbc_articles))) for i, doc in enumerate(nlp.pipe(iter_texts, batch_size=50, n_threads=8)): assert doc.is_parsed
多语言自然语言处理
spaCy
包括针对英语、德语、西班牙语、葡萄牙语、法语、意大利语和荷兰语的训练语言模型,以及用于 NER 的多语言模型。跨语言使用很简单,因为 API 不会改变。
我们将使用 TED Talk 字幕的平行语料库来说明西班牙语言模型(请参阅数据来源参考的 GitHub 存储库)。为此,我们实例化了两个语言模型:
model = {} for language in ['en', 'es']: model[language] = spacy.load(language)
然后我们在每个模型中读取相应的小文本样本:
text = {} path = Path('../data/TED') for language in ['en', 'es']: file_name = path / 'TED2013_sample.{}'.format(language) text[language] = file_name.read_text()
句子边界检测使用相同的逻辑,但找到了不同的分解:
parsed, sentences = {}, {} for language in ['en', 'es']: parsed[language] = modellanguage sentences[language] = list(parsed[language].sents) print('Sentences:', language, len(sentences[language])) Sentences: en 19 Sentences: es 22
POS 标记也是同样工作的:
pos = {} for language in ['en', 'es']: pos[language] = pd.DataFrame([[t.text, t.pos_, spacy.explain(t.pos_)] for t in sentences[language][0]], columns=['Token', 'POS Tag', 'Meaning']) pd.concat([pos['en'], pos['es']], axis=1).head()
结果是英文和西班牙文档的并排标记注释:
标记 | POS 标记 | 含义 | 标记 | POS 标记 | 含义 |
那里 | ADV | 副词 | 存在 | VERB | 动词 |
s | VERB | 动词 | 一个 | DET | 定冠词 |
一个 | DET | 定冠词 | 狭窄的 | ADJ | 形容词 |
紧密的 | ADJ | 形容词 | 和 | CONJ | 连词 |
和 | CCONJ | 并列连词 | 令人惊讶的 | ADJ | 形容词 |
下一节介绍如何使用解析和注释的标记构建文档-术语矩阵,该矩阵可用于文本分类。
使用 TextBlob 进行自然语言处理
TextBlob
是一个提供简单 API 用于常见 NLP 任务的 Python 库,它基于 自然语言工具包 (NLTK) 和 Pattern 网络挖掘库。TextBlob
简化了 POS 标记、名词短语提取、情感分析、分类、翻译等任务。
为了说明使用 TextBlob
,我们从 BBC 体育文章中抽样一篇标题为 Robinson ready for difficult task 的文章。与 spaCy
和其他库类似,第一步是通过 TextBlob
对象表示的管道将文档传递,以分配各种任务所需的注释(请参阅此部分的 nlp_with_textblob
笔记本):
from textblob import TextBlob article = docs.sample(1).squeeze() parsed_body = TextBlob(article.body)
词干提取
要执行词干提取,我们从 nltk
库实例化了 SnowballStemmer
,在每个标记上调用其 .stem()
方法,并显示修改后的标记:
from nltk.stem.snowball import SnowballStemmer stemmer = SnowballStemmer('english') [(word, stemmer.stem(word)) for i, word in enumerate(parsed_body.words) if word.lower() != stemmer.stem(parsed_body.words[i])] ('Andy', 'andi'), ('faces', 'face'), ('tenure', 'tenur'), ('tries', 'tri'), ('winning', 'win'),
情感极性和主观性
TextBlob
使用 Pattern 库提供的字典为解析的文档提供极性和主观性估计。这些字典将产品评论中经常出现的形容词映射到从 -1 到 +1(负面 ↔ 正面)的情感极性分数和类似的主观性分数(客观 ↔ 主观)。
.sentiment
属性为每个相关标记提供平均值,而 .sentiment_assessments
属性列出了每个标记的基础值(请参见笔记本):
parsed_body.sentiment Sentiment(polarity=0.088031914893617, subjectivity=0.46456433637284694)
从标记到数字 - 文档-术语矩阵
在本节中,我们首先介绍 BoW 模型如何将文本数据转换为允许使用距离比较文档的数字向量空间表示法。然后,我们继续说明如何使用 sklearn 库创建文档-术语矩阵。
BoW 模型
BoW 模型根据文档中包含的词或标记的频率来表示文档。每个文档都成为一个向量,每个向量在词汇表中都有一个条目,反映了该标记与文档的相关性。
给定词汇表,文档-术语矩阵很容易计算。然而,它也是一个粗略的简化,因为它抽象了单词顺序和语法关系。尽管如此,它通常很快就能在文本分类中取得良好的结果,因此是一个非常有用的起点。
下图(右边的那个)说明了该文档模型如何将文本数据转换为具有数字条目的矩阵,其中每行对应于一个文档,每列对应于词汇表中的一个标记。生成的矩阵通常是非常高维且稀疏的;也就是说,它包含许多零条目,因为大多数文档只包含总词汇表的一小部分:
![
结果矩阵
有几种方法可以衡量标记的向量条目以捕捉其与文档的相关性。我们将说明如何使用 sklearn 使用二进制标志,这些标志指示存在或不存在、计数以及加权计数,这些加权考虑了语料库中所有文档中的词频的差异:
测量文档的相似性
将文档表示为词向量将每个文档分配到由词汇表创建的向量空间中的位置。在该空间中将向量条目解释为笛卡尔坐标,我们可以使用两个向量之间的角度来衡量它们的相似性,因为指向相同方向的向量包含相同的词及相同的频率权重。
前面的图表(右边的那个)简化了二维中表示的文档向量 d[1] 与查询向量(搜索词组或另一个文档) q 之间距离的计算。
余弦相似度等于两个向量之间的夹角的余弦。它将角度大小转换为一个范围为 [0, 1] 的数字,因为所有向量条目都是非负的标记权重。值为 1 意味着两个文档在其标记权重方面是相同的,而值为 0 意味着两个文档只包含不同的标记。
如图所示,角的余弦等于向量的点积;也就是说,它们的坐标的和乘积,除以它们的长度的乘积,由每个向量的欧几里得范数测量。
使用 sklearn 创建文档-术语矩阵
scikit-learn 预处理模块提供了两个工具来创建文档-术语矩阵。CountVectorizer
使用二进制或绝对计数来衡量每个文档 d 和标记 t 的词频 tf(d, t)。
相反,TfidFVectorizer
通过逆文档频率(idf)对(绝对)词频进行加权。因此,出现在更多文档中的术语将获得比在给定文档中具有相同频率但在所有文档中出现频率较低的标记更低的权重。具体来说,使用默认设置,对于文档-术语矩阵的 tf-idf(d, t) 条目计算为 tf-idf(d, t) = tf(d, t) x idf(t):
这里 n[d] 是文档数,df(d, t) 是术语 t 的文档频率。每个文档的结果 tf-idf 向量相对于它们的绝对或平方总和进行了归一化(有关详细信息,请参阅 sklearn
文档)。tf-idf 度量最初用于信息检索以排名搜索引擎结果,并且随后被证明对于文本分类或聚类非常有用。
这两个工具使用相同的接口,并在向量化文本之前对文档列表执行标记化和进一步的可选预处理,以生成标记计数来填充文档-术语矩阵。
影响词汇量大小的关键参数包括以下内容:
stop_words
:使用内置或提供要排除的(常见)单词列表ngram_range
:在由 (n[min], n[max]) 元组定义的 n 范围内包含 n-gramlowercase
:相应地转换字符(默认为True
)min_df
/max_df
:忽略在较少/较多(int
)或更小/更大(如果是float
[0.0,1.0])的文档中出现的单词max_features
:相应地限制词汇表中的标记数binary
:将非零计数设置为 1True
有关以下代码示例和额外详细信息,请参阅 document_term_matrix
笔记本。我们再次使用 2,225 篇 BBC 新闻文章进行说明。
使用 CountVectorizer
笔记本包含一个交互式可视化,探索min_df
和max_df
设置对词汇量大小的影响。我们将文章读入 DataFrame,将CountVectorizer
设置为生成二进制标志并使用所有标记,并调用其.fit_transform()
方法生成文档-词矩阵:
binary_vectorizer = CountVectorizer(max_df=1.0, min_df=1, binary=True) binary_dtm = binary_vectorizer.fit_transform(docs.body) <2225x29275 sparse matrix of type '<class 'numpy.int64'>' with 445870 stored elements in Compressed Sparse Row format>
输出是一个scipy.sparse
矩阵,以行格式有效地存储了445870
个非零条目中的小部分(<0.7%),其中有2225
个(文档)行和29275
个(标记)列。
可视化词汇分布
可视化显示,要求标记在至少 1%且少于 50%的文档中出现将词汇限制在几乎 30,000 个标记的约 10%左右。
这留下了每个文档略多于 100 个独特标记的模式(左面板),右面板显示了剩余标记的文档频率直方图:
文档/词频分布
查找最相似的文档
CountVectorizer
的结果让我们可以使用scipy.spatial.distance
模块提供的pdist()
函数来找到最相似的文档。它返回一个压缩的距离矩阵,其中的条目对应于方阵的上三角形。我们使用np.triu_indices()
将最小距离的索引转换为相应于最接近的标记向量的行和列索引:
m = binary_dtm.todense() # pdist does not accept sparse format pairwise_distances = pdist(m, metric='cosine') closest = np.argmin(pairwise_distances) # index that minimizes distance rows, cols = np.triu_indices(n_docs) # get row-col indices rows[closest], cols[closest] (11, 75)
文章编号11
和75
在余弦相似性上最接近,因为它们共享了 58 个标记(请参阅笔记本):
主题 | 技术 | 技术 |
标题 | 在您工作时观看的软件 | 打败拨号诈骗者的 BT 程序 |
正文 | 软件不仅可以监视 PC 上执行的每个按键和操作,而且还可以作为法律约束证据使用。担心网络犯罪和破坏已促使许多雇主考虑监视员工。 | BT 正在推出两项计划,以帮助打败流氓拨号诈骗,这可能会使拨号上网用户损失数千。从五月开始,拨号上网用户将能够下载免费软件,以阻止计算机使用不在用户预先批准名单上的号码。 |
CountVectorizer
和TfidFVectorizer
都可以与spaCy
一起使用;例如,执行词形还原并在标记化过程中排除某些字符,我们使用以下命令:
nlp = spacy.load('en') def tokenizer(doc): return [w.lemma_ for w in nlp(doc) if not w.is_punct | w.is_space] vectorizer = CountVectorizer(tokenizer=tokenizer, binary=True) doc_term_matrix = vectorizer.fit_transform(docs.body)
请查看笔记本以获取更多详细信息和更多示例。
TfidFTransformer 和 TfidFVectorizer
TfidfTransfomer
从文档-词矩阵中的标记计数计算 tf-idf 权重,例如由CountVectorizer
生成的矩阵。
TfidfVectorizer
一次执行两个计算。它为CountVectorizer
API 添加了一些控制平滑行为的参数。
对于一个小的文本样本,TFIDF 计算的工作方式如下:
sample_docs = ['call you tomorrow', 'Call me a taxi', 'please call me... PLEASE!']
我们像刚才一样计算词频:
vectorizer = CountVectorizer() tf_dtm = vectorizer.fit_transform(sample_docs).todense() tokens = vectorizer.get_feature_names() term_frequency = pd.DataFrame(data=tf_dtm, columns=tokens) call me please taxi tomorrow you 0 1 0 0 0 1 1 1 1 1 0 1 0 0 2 1 1 2 0 0 0
文档频率是包含该标记的文档数量:
vectorizer = CountVectorizer(binary=True) df_dtm = vectorizer.fit_transform(sample_docs).todense().sum(axis=0) document_frequency = pd.DataFrame(data=df_dtm, columns=tokens) call me please taxi tomorrow you 0 3 2 1 1 1 1
tf-idf 权重是这些值的比率:
tfidf = pd.DataFrame(data=tf_dtm/df_dtm, columns=tokens) call me please taxi tomorrow you 0 0.33 0.00 0.00 0.00 1.00 1.00 1 0.33 0.50 0.00 1.00 0.00 0.00 2 0.33 0.50 2.00 0.00 0.00 0.00
平滑的效果
为了避免零除法,TfidfVectorizer
对文档和词项频率使用平滑处理:
smooth_idf
:将文档频率加1
,就像额外的文档包含词汇表中的每个标记一样,以防止零除法。sublinear_tf
:应用次线性tf
缩放;换句话说,用1 + log(tf)
替换tf
与标准化权重结合使用时,结果略有不同:
vect = TfidfVectorizer(smooth_idf=True, norm='l2', # squared weights sum to 1 by document sublinear_tf=False, # if True, use 1+log(tf) binary=False) pd.DataFrame(vect.fit_transform(sample_docs).todense(), columns=vect.get_feature_names()) call me please taxi tomorrow you 0 0.39 0.00 0.00 0.00 0.65 0.65 1 0.43 0.55 0.00 0.72 0.00 0.00 2 0.27 0.34 0.90 0.00 0.00 0.00
如何使用 TfidFVectorizer 总结新闻文章
由于它们能够分配有意义的标记权重,TFIDF 向量也用于总结文本数据。例如,Reddit 的autotldr
功能基于类似的算法。请参阅笔记本,了解使用 BBC 文章的示例。
文本预处理 - 回顾
我们在本节介绍的大量处理自然语言以在机器学习模型中使用的技术是必要的,以应对这种高度非结构化数据源的复杂性质。设计良好的语言特征工程既具有挑战性又具有回报,可以说是解锁文本数据中隐藏的语义价值的最重要的一步。
在实践中,经验有助于我们选择去除噪声而不是信号的转换,但可能仍然需要交叉验证和比较不同预处理选择组合的性能。
文本分类和情感分析
一旦文本数据使用前面讨论的自然语言处理技术转换为数值特征,文本分类就像任何其他分类任务一样。
在本节中,我们将这些预处理技术应用于新闻文章、产品评论和 Twitter 数据,并教您各种分类器,以预测离散新闻类别、评论分数和情感极性。
首先,我们将介绍朴素贝叶斯模型,这是一种概率分类算法,适用于袋装词模型产生的文本特征。
本节的代码示例在text_classification
笔记本中。
朴素贝叶斯分类器
朴素贝叶斯算法非常受欢迎,用于文本分类,因为低计算成本和内存需求便于在非常大的、高维的数据集上进行训练。它的预测性能可以与更复杂的模型竞争,提供了一个良好的基准,并以成功的垃圾邮件检测而闻名。
该模型依赖于贝叶斯定理(参见第九章,贝叶斯机器学习)和各种特征独立于结果类别的假设。换句话说,对于给定的结果,知道一个特征的值(例如文档中标记的存在)不会提供有关另一个特征值的任何信息。
贝叶斯定理复习
贝叶斯定理表达了一个事件的条件概率(例如,一封电子邮件是垃圾邮件而不是良性的垃圾邮件)给定另一个事件(例如,电子邮件包含某些词)如下:
后验概率,即一封电子邮件实际上是垃圾邮件的概率,给定它包含某些词,取决于三个因素的相互作用:
- 一封电子邮件是垃圾邮件的先验概率
- 在垃圾邮件中遇到这些词的似然性
- 证据;即在电子邮件中看到这些词的概率
为了计算后验概率,我们可以忽略证据,因为它对所有结果(垃圾邮件与非垃圾邮件)都是相同的,而且无条件先验可能很容易计算。
然而,对于一个相当大的词汇表和一个实际的电子邮件语料库,似然性提出了不可逾越的挑战。原因在于单词的组合爆炸,这些单词在不同文档中是否共同出现,从而阻止了计算概率表并为似然性赋值所需的评估。
Python 机器学习算法交易实用指南(四)(4)https://developer.aliyun.com/article/1523386