自动化顾客满意度调查的机器学习方法
泰勒·多尔,马修·巴辛,凯·尼科尔斯,西德尼·约翰逊
在浏览我的一些文档时,我偶然发现了一篇我在大学时参与的论文,我认为它非常适合分享。作为背景,这篇论文是为我的一个班级写的,在那里我们有一个由一家公司赞助的项目。就我而言,我与马修·布辛、凯·尼科尔斯和西德尼·约翰逊合作了一个由 ProKarma 赞助的项目,试图实现客户满意度调查的自动化。这篇论文最初发表在我们的课程网站这里。它已被改编以适应这种格式,原文被嵌入在文章的结尾。尽情享受吧!
介绍
自然语言处理(NLP)是一个关于捕获、分析和解释人类
语言的广泛学科。特别地,NLP 可以用于评估人类语音
,以便确定关于其说话者、上下文、
和情感的不同品质。这些评估可用于通知人机交互、可访问性、自动化和自然语言的统计分析。
这个 NLP 项目是由 ProKarma 的 Edge Intelligence group 提出的,pro karma 是一家 IT 解决方案公司,总部位于内布拉斯加州,提供数字化转型和分析咨询。ProKarma 的
Edge Intelligence group 正在研究如何通过结合机器学习来增加电信行业的客户
服务呼叫,从而改善客户
服务。提高客户服务满意度的一种方法
包括确定客户对特定产品的态度,并通过对客户电话数据进行情感和话题分析,有效地实现自动化客户服务调查。
自动化的客户满意度数据收集将提供
关于消费者对产品
和服务态度的可操作信息,其响应水平比单纯依赖
在线完成电话调查的客户要高得多。这个自动化系统可以与许多无线运营商目前采用的传统电话调查并行运行。对电话中提到的产品的自动化情感分析
可以根据客户完成电话后调查
的情况进行验证
。
由于自然语言中包含大量变量,传统的分析方法很难适应这一目的。机器学习算法允许简单的音频数据线性插值以及强大的模式
查找工具。出于这个原因,神经网络被开发用于
的主题和情感分类。这些模型发现产品类型和消费者态度之间的模式
,这提供了从客户电话音频中生成自动化客户满意度
调查所需的
信息。
方法
完成
自动化调查过程的最终目标需要三个模型:语音转文本转录、情感
建模和主题建模。
- 语音转文本转录:为了自动转录呼叫数据,需要一种自动语音识别(ASR)策略。为了执行后面的主题和情感分析,准确和高性能的 ASR 实现是至关重要的,这些分析将简单语言的抄本作为输入。作为输入,抄本比纯音频更可取,原因有二:首先,主题建模不能直接在音频上执行,因为主题是必须从文本源中直接提取或解释的字符串;其次,音频文件可以用于检测有声情绪,但不能检测非情绪情绪(例如,单调的客户说“谢谢您的帮助”,或者活泼的客户高兴地说“我的电话打不开”)。
- 情绪模型:由于情绪分析对于确定客户满意度非常重要,因此需要开发一个机器学习模型来根据转录的文本输入检测对客户服务代表的情绪(积极、消极或中立)。ProKarma 的 Edge Intelligence group 之前实施了一个双向长短期记忆递归神经网络(LSTM RNN),该网络使用开源数据集进行训练,如交互式情感二元运动捕捉(IEMOCAP)数据库,以便对从音频剪辑中提取的声学特征进行情感和情绪分类。在这个项目中,我们基于抄本的模型旨在捕捉整体情绪,而不是像以前基于声学的模型那样捕捉声音特征。
- 主题模型:为了交叉分析客户情绪和谈话主题,产生了分类模型来识别抄本中的概念和特定主题。对话话题的趋势可以用来理解个人客户对产品和服务的态度,也可以用来理解一般客户群的态度。
这些模型最终形成了一个对客户通话录音进行端到端音频分析的引擎。所有项目代码都是使用
Python 3.6 编写的,通过
Keras 接口使用了 Google 的 TensorFlow 机器学习库,并使用了 Python/R Anaconda 发行版。
使用 git 执行版本控制,GitHub 用于
托管所有项目存储库。该项目是使用
敏捷工作流程开发的,团队协作和沟通是使用软件 Slack
促进的。
系统架构和设计
该项目的目的是证明将客户
服务呼叫作为输入,并将与客户满意度调查相关的数据作为输出是可能的。为完成这项任务而构建的系统,如图 1 所示,由三个主要模型组成:语音到文本模型、主题模型和情感模型。语音转文本模型在网络搜集的客户服务呼叫音频文件上进行训练。主题模型是在一个无线运营商的 FAQ 和论坛帖子的 web 废弃语料库上训练的。情感模型是在注释 IEMOCAP 转录本和电子评论的组合数据集上训练的。然后,客服电话音频通过语音转文本程序生成文字记录,然后输入主题和情感模型。后两种模型输出对整体主题和逐句情感标签的预测。然后将这两个输出进行汇总,以生成客户满意度指标的可视化,如通话过程中随时间推移的情绪以及与特定主题相关的情绪。
Figure 1: Diagram of general system architecture.
技术设计
将卷积神经网络(CNN)用于自然语言处理是最近的发展。CNN 最广为人知的是他们在图像分类中的成功,从过滤器的自然能力中识别图像的组成部分,如线条和形状。用于图像的 CNN 可以成功地
找到形状中的模式,而用于语言的 CNN 的过滤器同样能够
找到 n-gram 中的模式。
用于测试的网络如图 2 所示,并在图 3 中详细说明
,有两个卷积层。第一卷积
层有 64 个滤波器,滤波器大小为 4,第二卷积层有 32 个滤波器
,滤波器大小为 6。这些是测试
型号功能的起点。这些参数可以进一步调整
以提高模型的准确性。使用 CNN 进行情感
分析时,建议使用更多数量的过滤器
和 3–5 范围内的过滤器尺寸,以便根据紧密相邻的
单词之间的关系捕获大量上下文
信息。图中的脱落层旨在防止
过拟合。
Figure 2: Convolutional neural network architecture.
Figure 3: CNN layer type, output shape, and number of parameters.
潜在狄利克雷分配(LDA)主题模型广泛用于
识别一组文档中的潜在主题。给定 K 个要查找的主题
,LDA 模型将根据最常出现的单词和它们各自的主题
将文档中的单词分类成不同的
主题。这通过一个生成过程
工作,其中 LDA 模型假设如下:一个文档由一组主题
组成,每个主题由一组单词
组成。然后,它对这个过程进行逆向工程,以便
识别语料库中的主题。图 4 是图版
符号,其中:
- m 是文档的数量
- n 是文档中的字数
- α是每个文档的主题分布
- β是每个主题的单词分布
- m 是文档 m 的主题分布
- φ_k 是主题 k 的单词分布
- Z_mn 是文档 m 中第 n 个单词的主题
- W_mn 是特定的单词
Figure 4: Diagram of smoothed LDA model.
为了将客户服务记录数据分类到
主题中,使用了无线运营商的在线技术支持讨论板和常见问题解答部分
。通过使用该数据集,主题
可以被预先确定,并且模型可以被调整以识别
特定主题。图 5 提供了主题信息
的例子,这些信息可以用来识别主题。为了做到这一点,
β以如下方式定义:
- 对于每个主题,所有的单词最初都被赋予 1 / 语料库
词汇量分布 - 然后,对于给定的主题,被预先确定为更频繁使用的单词
被严重偏向(1000 倍的权重)
这使得模型偏向于在
文档中查找特定主题,而不是确定自己的主题。
Figure 5: Example of raw output from the LDA model.
设计决策
一些工具跨越了项目的所有部分。整个项目中使用的通用
数据科学工具包括:
- Python: Python 是一种强大的通用编程语言
,有一套健壮的库。它是一个具有广泛支持网络的行业标准
。 - TensorFlow: TensorFlow 是一个开源库,用于跨不同平台部署高性能数值计算。
- Anaconda:Anaconda 发行版包含了大量的数据科学
包,包括 conda,它是一个独立于操作系统的
包和虚拟环境管理器,允许跨机器轻松安装
包。 - Jupyter Notebook:Jupyter Notebook 是包含文本和嵌入代码的文档,可以很容易地在文档中运行。它们易于共享,并为我们的 python 代码提供高质量的交互式输出。Jupyter notebooks 的解释器、降价和可视化
环境的散文式格式
促进了代码和概念的协作。 - Pandas: Pandas 是一个强大的 python 库,在
数据科学中无处不在。在这个项目中,它用于管理培训
和测试数据。 - Matplotlib: Matplotlib 是一个用于数据可视化的 python 库。在这个
项目中,它用于情感分析
和主题建模数据的最终可视化。 - Pickle: Pickle 是一个用于对象序列化和数据
存储的 python 库。在这个项目中,它对导出模型配置
和相关数据框很有用。
由于这是一个影响深远的项目,涉及许多移动部件,因此需要各种
技术工具来具体完成每个主要公用事业公司的
目标:
语音转文本转录
- ffmpeg:由于语音到文本引擎处理音频
文件的方式,使用与引擎预期一致的音频编码
是很重要的。FFmpeg 是一个免费的、轻量级的
命令行音频编码器,可以无缝地融入
我们的音频处理工作流程。最终的编码模式
由 16 kHz 采样频率的单声道音频、
16 位位深度和 300/3000 Hz 高/低通
滤波组成,以专门捕捉语音音频。 - Audacity:除了编码,我们的数据需要少量的编辑,主要是以短语划分的形式。Audacity 的
“Silence Finder”实用程序允许我们将长音频
片段分解成短语长度的声音片段,并轻松导出它们
,以便在 DeepSpeech 中立即进行转录处理。 - DeepSpeech: DeepSpeech 是一个免费的语音转文本引擎,具有高精度上限和简单的转录和训练功能。DeepSpeech 还带有预训练的
模型,可以通过转移学习进行优化
以提高任何特定类型音频的准确性,从而避免
需要延长训练时间或极其庞大的
数据集。我们的迁移学习过程包括移除 DeepSpeech 的最终解释层
,用随机初始化的层
替换它,冻结所有子层并训练最终层
。
主题模型
- gensim: gensim 是一个用于 python
的通用主题建模库,具有许多有用的特性。Gensim 主要用于其 LDA 模型
,因为它非常强大且易于训练
并根据我们的需求进行调整。 - NLTK: NLTK 代表自然语言工具包,是一个用于自然语言处理的
python 库。这个库
用于我们的主题模型,在通过我们的 LDA 模型运行文档
之前对它们进行规范化。 - pyLDAvis: pyLDAvis 是一个 python 包,用于可视化
LDA 模型,并被用于这个项目,因为它
与 gensim 接口良好。pyLDAvis 允许我们轻松地
可视化我们的 LDA 模型的性能。 - 斯坦福 NER:斯坦福命名实体识别器模型
用于识别语料库中的命名实体,如人员、
地点和组织。这有助于我们识别文档的主题。 - 斯坦福词性标注器:斯坦福词性标注器用于识别语料库中每个单词的词性。这有助于我们识别电话记录句子的主语、动词和宾语。
情感模型
- 单词嵌入:Tensorflow_hub 的单词嵌入模块
被选为我们神经网络分类
模型的输入,因为它们允许我们将句子映射到数字
值,并保留词序信息。这些
模块很容易通过 tensorflow_hub
实现,并经过预训练。 - 手动特征选择(MFS):手动特征选择
用于 SVM 和随机森林分类器模型
,因为它允许更快的训练,因此可以处理
更多的特征。这允许我们训练我们的
模型来识别手边特定主题的词汇。 - (MFS) NLTK 词干化:在手动特征选择中,单词
被词干化,因此具有相同词根的单词将被
识别为相同的单词,这减少了我们的特征
空间。选择 NLTK 的词干模块是为了便于使用。 - (MFS)过滤:超过 25 个字符的单词,短于 3 个字符的单词
,出现超过 2000 次的单词,
和出现少于 5 次的单词都被过滤掉
以减少特征空间。 - 二元模型:二元模型被添加到特征空间以嵌入一些关于词序的信息。
- 分类模型:探索了各种分类算法
来比较我们的
模型对训练数据的准确性。探索的模型有
SVM 分类器、随机森林分类器、LSTM 递归
神经网络和卷积神经网络。 - Sklearn、Tensorflow 和 Keras: Tensorflow 和 Keras 用于神经网络训练。Sklearn 被用于
其他分类算法。 - NLTK 和 NLTK-trainer: NLTK 用于情感分析,
因为它有几个预置的情感模型。这使得
我们可以快速建立一个标准模型,并与我们自己的模型迭代进行比较。
绩效结果
语音转文本转录
DeepSpeech,我们客户首选的自动语音识别(ASR)引擎
,是一个端到端可训练的字符级
LSTM 递归神经网络。DeepSpeech 已经被用于产生非常低的单词错误率(WER),特别是 LibriSpeech 上 6%的 WER,这是一个开源的 1000 小时自然语音数据集。6%大约是人类的
错误率。开箱后,DeepSpeech 的通用模型在我们的客户服务电话测试数据上返回了 65.4%的 WER,超过人工处理 WER 的 10 倍。
在使用非常小的训练数据语料库
生成的定制模型上,该 WER 最终减少到大约 18.7%
。
DeepSpeech 在翻译句子长度、
< 10 秒的音频剪辑时效果最佳。Deep-
Speech 为每个音频片段创建一个抄本所需的处理时间在 4 到
9 秒之间,平均为 7.14 秒。因此,5 分钟的
音频可能需要 DeepSpeech 差不多 4 分钟的
处理时间。
情感模型
各种模型的准确性在 80/20 分割的
训练/测试数据上进行比较,这些数据由 IEMOCAP 数据集和
电子评论的网络搜集数据集组成,具有图 6 中包含的情感
分类准确性。这些
是三类分类器,其中情感类别是
积极、消极和中立。然后比较了不同特性实现的精度,结果如图 7 中的
所示。一些特性实现不容易
在不同的分类
模型上运行,因此缺少数据。
Figure 6: Accuracy results of different classifiers for sentiment.
Figure 7: Accuracy results of different feature implementations.
Figure 8: Confusion matrix for training data on the final LSTM model.
Figure 9: Confusion matrix for testing data on the final LSTM model.
在不同的数据集和实现中,最好的
结果是在使用 tf_hub
单词嵌入模块的神经网络分类器上。在单词嵌入模块中,
通用句子编码器的表现优于其他模块。
在相同的功能实现下,一个更深层次的神经网络优于 sklearn 的 DNNClassifier
,并且是所有实现中性能最高的
,测试集精度比第二个竞争者高出约 10%
。最好的
模型迭代是 LSTM RNN,它使用 tf_hub 的通用
句子编码器作为特征输入。该模型在 IEMOCAP 和
电子评论数据集上的
测试集准确率为 77.6%。从 3 个类别中随机选择一个类别的基线准确率为 33.3%。
主题模型
使用从无线运营商的在线讨论
论坛和常见问题部分收集的数据,训练数据被组织成 5 个主题
:账户和服务、安卓产品、
苹果产品、网络和覆盖范围以及其他产品。
这些主题中的每一个还包括一些带有偏见的
植入 LDA 模型的关键词(例如
苹果产品的“iphone”,安卓产品的“galaxy”等)。使用
这个训练数据来训练 LDA 模型,它能够成功地
以 57.6%的时间预测测试数据上的主题。从 5 个类别中随机选择一个类别的基线准确度为 20%。这个 LDA 的可视化可以在图 10 中找到。
使用自然语言工具包
(NLTK)中包含的语料库
来训练词性主题标识符(PoSSI)模型,然后在上述网络搜集的无线运营商
数据上进行测试。这个模型能够产生
连贯的主谓宾(SVO)结果,但是因为
网络抓取的数据集是未标记的,所以没有定量的
方法来分析准确性,除非所有网络抓取的数据
都被手工标记了词性。手工标记整个
网络抓取的数据集不在这个项目的范围内。
客户呼叫数据的可视化和分析
在通话过程中可视化情绪显示了我们可能预期的趋势
:绿色、积极的第一句话介绍
,随后是红色、消极的问题介绍
,以及接近通话结束的深绿色(即更强烈的积极情绪)
。
这些令人鼓舞的结果表明,情感分析
可以应用于自动化客户满意度调查的环境中。
Figure 10: A visualization of the topics and the vocabulary found in the training corpora.
Figure 11: Top left: sentiment for each clip of the call displayed over the call transcript; Top right from top down: a heat-map of the intensity of the sentiment of each sentence of the call, a line graph displaying the intensity of the sentiment during each sentence of the call; Middle: the distribution of sentiment among keywords for each category; Bottom: the distribution of probabilities the call belongs into one of the five topic categories.
Figure 12: Sentiment distribution for sample service keywords.
结论
语音转文本转录
DeepSpeech 是高度可训练的,但是有 1.2 亿个参数,
需要大规模的训练语料库,数百小时的训练和昂贵的设备。迁移学习以冻结模型
的所有子层并重新训练最后一层的形式进行
。改变
先前图层的实验无法产生改进。转移
学习是一个可行的行动过程,可以为未来的转录需求定制深度-
语音,但应该使用更多高质量的音频剪辑进行
训练。
在我们的测试
数据上,DeepSpeech 的表现明显优于 Kaldi 和 CMUSphinx
等替代 ASR 引擎,以及隐马尔可夫模型
等传统 ASR 方法。
未来工作在为转录准备
低质量通话数据方面,以及在转录后处理
以产生更正确的语音
(由于 DeepSpeech 是字符级的,所以有时会产生
废话)方面,可能会有进一步的改进。在大型行业专用 copora 上重新训练 DeepSpeech 的权重也可能会产生更高的精确度,但在计算时间和所需的数据量方面会明显更加昂贵。
情感模型
对所测试的情感分析模型的准确性
影响最大的两个因素是数据准备
和模型选择。数据准备和输入格式化
对较小的数据集有较大的影响。模型选择
对较大的数据集有较大的影响。在我们尝试的各种模型迭代中,使用了三种不同类型的数据输入格式:
- 手动特征选择(在 SVM 和随机森林-
分类器中使用):手动特征选择通过使用技术
词干化、过滤和标记化来完成。使用 NLTK 的词干模块对单词
进行词干处理,因此不同时态的所有
单词将对应于同一个
标记。当单词长度小于 3、大于 25 时,不包括在训练/测试
集中,当单词在完整语料库中出现超过 2000 次或少于 5 次
时,不包括在
集中。然后为语料库中所有的
可行的单字和双字创建标记。 - 预训练单词嵌入模块(用于 DNNClassifier)
-来自 tensorflow_hub
的预训练单词嵌入模块用于 DNN 分类器。
-NNLM 嵌入基于一个具有两个隐藏层的神经网络
语言模型。最不常用的
令牌被散列到桶中。- Word2vec 是一个基于令牌的嵌入模块,在英文维基百科文章语料库上进行训练
。
-通用句子编码器对句子进行编码,用于文本分类
。它使用深度平均
网络进行训练,并具有编码语义
相似性的特征。
- Word2vec 是一个基于令牌的嵌入模块,在英文维基百科文章语料库上进行训练
- 标记化:长度小于 1 的单词被删除,然后使用 Keras 的文本预处理模块
对
句子进行矢量化。为
训练输入中的每个单词选择一个标记。所有的句子都被填充成相等的长度,单词被替换成了记号。
考虑到我们的数据集相对较小的词汇量,
使用预先训练的单词嵌入模块给出了最好的
结果,超过了使用什么模型的影响。还开发了替代
型号,例如:
- SVM 和兰登森林:这两个模型是第一个被用来衡量可以达到何种精度的
模型,以及预计
可以击败的更复杂的模型。sklearn SVM 分类器和 Random-
森林分类器被用于这项任务。这是两种常用的高性能分类器。 - DNN:这个模型是来自
Tensorflow 的预建深度神经网络。该模型用于评估神经网络在数据集上的性能
,并测试 tf_hub 的单词嵌入模块的使用
。 - LSTMs 目前是像自然语言这样的顺序数据的行业标准模型。
- CNN:卷积神经网络已经被证明
通过
使用
卷积层捕获关于 n-grams 的信息,从而在句子分类方面提供了出色的结果。 - 美国有线电视新闻网-LSTM:一个组合的美国有线电视新闻网-LSTM 模型已经被证明
通过用
卷积层编码区域信息和用
LSTM 层编码跨越
句子的长距离依赖而工作良好。
对于中小规模的训练数据集,当使用具有数千个参数的神经网络(如 RNN 和 CNN 神经网络)时,过度适应训练集的风险要高得多。这可以从我们的性能结果中测试和训练集准确性之间的巨大差异中看出。停止训练的理想点是当训练和测试分数开始偏离时,训练分数继续上升,但测试分数开始下降。在训练模型的上下文中,这意味着在训练时保存检查点是很重要的,这样模型就可以恢复到尚未过度拟合的旧检查点。参见附录 B,了解培训和各时期测试分数的示例。过拟合也可以通过向损失函数添加正则化参数或引入漏失层来减少。在大多数类型的神经网络中,向层中添加辍学已经取得了成功。然而,这种方法在使用 LSTM 单位的 RNNs 上效果不佳,而这种类型的模型在这个项目中表现最佳。为了减轻对我们的小训练数据集的过度拟合,我们仅对非递归层应用 dropout。
未来工作
- 手动特征选择:当进行手动特征
选择时,NLTK 词干模块用于寻找
词根。词汇化可能会给出更好的结果,
但是需要词性标注。实现
这将创建一个更精确的特征空间。这可以通过结合使用 NLTK 的 WordNetLemmatizer 和 NLTK 的 pos_tag 来完成。 - 训练数据:使用更大的训练数据集。IEMOCAP
数据集的词汇量为 3000 个单词,相对来说
比较小,测试数据很容易包含
看不见的单词。这一点通过加入
电子评论数据集得到了改善,该数据集将词汇量
增加到了大约 8000 个单词。此外,数据并不像可能的那样 T13 是领域特定的。IEMOCAP 非常通用
目的,由演员阅读关于
日常情景的剧本来表演。电子评论针对的是与技术
无线运营商客户电话故障排除中讨论的产品类似的电子
产品,但不是最近的
。可以引入更多特定于无线
运营商客户服务呼叫中讨论的内容的数据,
例如更近期的电子设备评论或手动
带注释的无线运营商论坛帖子。 - 预训练单词嵌入模块:迁移学习
可以在这些模块上使用与正在尝试分类
的文本数据类型相关的文本语料库
来实现。在这种情况下,单词嵌入
模块可以在
无线运营商相关文本的语料库上进一步训练,例如从
特定无线运营商的在线 FAQ 部分和讨论
论坛帖子中搜集的数据。 - CNN 和 LSTM 神经网络:对层
及其参数的微调,以及向模型提供更多的
数据可以在神经网络模型上得到改进。
这些努力的目的是提高精度
和减少过拟合。
主题模型
LDA 模型的低准确度结果是预料之中的,因为
LDA 模型并不意味着被强制加入预定义的主题
,而是应该用于在语料库中寻找隐藏的主题信息
。使用词性和 NER
标记的 SVO 提取能够产生一致的结果,但是这些结果
在主题之间并不一致,这在尝试按主题对文档进行排序时提出了挑战
。
未来工作
- LDA 模型可以通过使用更特定领域的
词性标注器(而不是 NLTK 的默认词性
标注功能)以及更精确的 alpha
(文档-主题密度)和 beta(主题-词密度)参数来改进。 - PoSSI 模型可以在几个方面进行改进:
-使用一个 POS 标记器和一个命名实体识别
(NER)标记器在特定领域的语料库上进行训练
-一个更特定领域的 POS 标记器模型(目前
使用斯坦福的研究模型)
-一个更特定领域的 NER 标记器模型(目前
使用斯坦福的研究模型)
-一种更好的从标记的
文档中提取主题的方法,例如结合特定领域语料库
的上下文无关
语法的解析器-速度改进,因为该模型对于
大型文档可能非常慢。
-这两种模型可以结合起来识别生成的 svo 中的主题
。
文献学
A.G. Tripathi 和 N. S .,“用于情感分析的特征选择和分类方法
”,“机器学习和应用:
国际期刊,第 2 卷,第 2 期,第 01–16 页,2015 年。
B.C. Busso、M. Bulut、C. Lee、A. Kazemzadeh、E. Mower、S. Kim、
J. Chang、S. Lee 和 S. Narayanan,“IEMOCAP:交互式情感
二元运动捕捉数据库”,《语言资源与评估杂志》
,第 42 卷第 4 期,第 335–359 页,2008 年 12 月。
C.Y. Bengio、R. Ducharme、P. Vincent 和 C. Jauvin,“一种神经
概率语言模型”,《机器学习研究杂志
,第 3 卷,第 1137-1155 页,2013 年 3 月。
D.T. Mikolo、K. Chen、G. Corrado 和 J. Dean,“向量空间中单词表示的有效估计
”, CoRR,第 abs/1301.3781 卷,
2013 年 1 月。
E.D. Cer,Y. Yang,S.-yi Kong,N. Hua,N. Limtiaco,R. St. John,
N. Constant,M. Guajardo-Cespedes,S. Yuan,C. Tar,Y.-H. Sung,B.
Strope,R. Kurzweil,《通用句子编码器》,CoRR,vol .
ABS/1803.1175,2018 .
F.W. Zaremba、I. Sutskever 和 O. Vinyals,“递归神经网络
正则化”,CoRR,第 abs/1409.2329 卷,2014 年。
G.胡和刘。"挖掘和总结客户评论."ACM SIGKDD 国际知识会议论文集
发现数据挖掘,2004 年 8 月。
H.金达尔和刘。“意见垃圾和分析。”第一届 ACM 网络搜索和数据挖掘国际会议论文集
,2008 年 2 月。
一、刘倩、高志强、刘冰、张元林。观点挖掘中面向抽取的自动
规则选择。国际人工智能联合会议论文集
,2015 年 7 月
。
J.Y. Kim,“用于句子分类的卷积神经网络”,
CoRR,第 abs/1408.5882 卷,2014 年。
K.张量流。(2018).文本模块|张量流。【在线】。
长度 X. Ma 和 E. Hovy,“通过双向
进行端到端序列标记的 LSTM-CNN-CRF”,
计算语言学协会第 54 届年会论文集,2016 年第 1 卷。
米(meter 的缩写))J. Cheng,L. Dong,M. Lapata,“机器阅读的长短期记忆-
网络”,2016 年自然语言处理经验方法会议论文集
,2016 年。
名词(noun 的缩写)Y. Goldberg,“自然语言处理的神经网络模型入门”,人工智能研究杂志,
第 57 卷,2016 年 11 月。
O.王、余立春、赖、张,“使用区域 CNN-LSTM 模型的维度情感分析”,计算语言学协会第 54 届年会论文集(第 2 卷:短文),2016 年。
页(page 的缩写)A. Y. Hannun,C. Case,J. Casper,B. Catanzaro,G. Diamos,E.
Elsen,R. Prenger,S. Satheesh,S. Sengupta,A. Coates 和 A. Y. Ng,
“深度语音:扩大端到端语音识别”,CoRR,
第 abs/1412.5567 卷,2004 年。
Q. J .昆泽、l .基尔希、I .库伦科夫、a .克鲁格、j .约翰斯迈尔和
S .斯托伯,“预算内语音识别的迁移学习”,
NLP 第二届表征学习研讨会论文集,2017。
R.S. Bansal 与自然语言处理和机器学习,
《Python 中主题建模的初学者指南》,Analytics Vidhya,
29-Aug-2016。【在线】。
南 J. Brownlee,“如何开发用于情感分析的 N 元多通道卷积
神经网络”,机器学习
掌握,2018 年 2 月 14 日。
T.S. Axelbrooke,“LDA Alpha 和 Beta 参数—直觉”,
LDA Alpha 和 Beta 参数—直觉|思维向量
博客,2015 年 10 月 21 日。【在线】。
单位 A. Crosson,“使用自然语言处理提取文档主题”,
Medium,2016 年 6 月 8 日。【在线】。
动词 (verb 的缩写)J. R. Finkel、T. Grenager 和 C. Manning,“通过 Gibbs
抽样将非局部
信息纳入信息提取系统”,计算语言学协会第 43 届年会会议记录——ACL 05,第 363–370 页,2005 年。
W.K. Toutanova 和 C. D. Manning,“丰富最大熵词性标记器中使用的知识来源”,2000 年在
举行的关于自然语言处理和超大规模语料库的经验方法
的 2000 年联合 SIGDAT 会议的会议记录
与
计算语言学协会第 38 届年会,第 63–70 页,2000 年。
X.S. Bird、E. Klein 和 e . Loper,
Python 的自然语言处理:用自然语言工具包分析文本。北京:
奥雷利,2009 年。
Y.平滑的 LDA。维基百科,2009 年。
Z.S. Ruder,“NLP 最佳实践的深度学习”,Sebastian Ruder,
2017 年 7 月 25 日。【在线】。
Original PDF of our Final Report
word2vec 模型的数学介绍
Image by Gerhard Gellinger from Pixabay
作为 NLP 项目的一部分,我最近不得不处理 Mikolov 等人在 2013 年开发的著名的 word2vec 算法。在网上有很多关于这个主题的教程和指南,有些更侧重于理论,有些则有实现的例子。
然而,我感兴趣的不仅仅是学习这种方法背后的理论,还有理解算法的实现细节,这样我就可以自己编写并正确地重现算法了(此处)。从这个意义上说,我找不到真正对我有帮助的东西,所以我准备了两篇有条理的文章:在这篇文章中,我讨论模型的理论,而在这里我展示如何通过神经网络具体训练模型。
注:我发现在 Medium 上使用 latex 是相当不满意和讨厌的,所以我选择了使用手绘配方(和方案)。我提前为我的书法道歉。
Word2vec 已经成为一种非常流行的单词嵌入的方法。单词嵌入意味着单词用实值向量表示,因此可以像处理任何其他数学向量一样处理它们。一种从文本、基于字符串的域到向量空间的变换,只需要少量的规范运算(主要是和与减)。
A change of domain
为什么要嵌入单词?
通过单词嵌入,我们可以用数学方法处理文本中的单词。如果两个单词有相似的意思,它们的向量是接近的:单词嵌入是一种相似性的度量,这使得不仅单词,而且句子和整个文本都有可能相关联。这在各种 NLP 任务和系统中非常有用,例如信息检索、情感分析或推荐系统。
报纸
2013 年,Mikolov 等人提出了两篇开创性的论文[1] [2]和两种不同的单词嵌入模型,即连续单词袋 (CBOW)和跳格模型。它们在本质上没有太大区别,在下面的讨论中,我将集中讨论后者。
两个模型的假设都是**分布假设:**出现在相同上下文中的单词往往有相似的意思(Harris,1954)。也就是说,一个词的特点是它的同伴。
假设我们的文本由一系列单词组成:
Text as a word sequence of words
那么对于单词 wⱼ, wⱼ 的上下文由其左右邻域给出:
其中 M 是上下文窗口的一半大小。
然后,给每个单词 w 分配一个向量表示 *v,*和概率,即 wₒ 在 wᵢ 的上下文中被定义为它们的向量乘积的 softmax :
Output probability is given by softmax of vector product
跳格模型的目标是预测中心词的上下文。因此,训练模型意味着找到使目标函数最大化的一组 v :
Objective function
等效地,这意味着最小化损失函数,该函数看起来是在语料库上平均的交叉熵(因为真实分布仅在中心词的上下文中等于 1):
到目前为止,这就是我们需要知道的关于 vanilla wordvec 的所有信息。
然而,这种需要考虑每个单词的上下文的所有概率的方法,对于通常包括数十亿个单词的相当大的语料库来说是不可分析处理的。因此 Mikolov 的小组提出了一些提高计算效率的有效措施。令人惊讶的是,它们还改善了嵌入结果。让我们看看他们。
单词子采样
这很简单。我们不是以简单的方式考虑文本中的所有单词,而是给每个单词分配一个概率,这个概率与它出现的频率成反比。保持概率由下式给出:
因此,在生成训练数据的过程中,常用词将更有可能被丢弃,从而减少要处理的数据量。由于这种词通常带来有限的信息量(想想像“the”、“of”、“to”这样的词),从语料库中删除一些词还可以提高文本的质量,并允许专注于更有意义的术语。
这里显示的保持概率公式包含在 Google 代码实现中,它与原始文章中报告的略有不同。感谢克里斯·麦考密克指出了这一点。
负采样
相反,这种技术似乎有点晦涩难懂,但它已被证明非常有效。
负抽样只考虑少量样本来评估跳格概率。样本被称为阴性,因为它们是不属于 wᵢ 的上下文的单词(因此,模型应该理想地将概率分配为零)。
负采样源于噪声对比估计 (NCE)技术;其基本思想是将多项分类问题转化为二元分类问题;
- 多项式分类问题 *。*该模型使用 softmax 函数估计输出单词的真实概率分布。
- 二元分类 问题 。对于每个训练样本,模型被输入一个正对(一个中心单词和出现在其上下文中的另一个单词)和少量的负对(中心单词和从词汇表中随机选择的单词)。该模型学习区分真实对和负面对。
对于负采样,目标函数变为:
Objective function with negative sampling
其中σ是逻辑( sigmoid )函数:
Sigmoid function
与 softmax 不同, sigmoid 是一个单参数函数,因此不需要同时评估所有输出值。
为什么负抽样效果这么好?
这个问题仍在讨论中。分布假设看起来是正确的,负采样通过增加中心词和上下文词之间的相似性来利用它。戈德堡和 Levy⁴认为,二次抽样也可能产生有益的影响。事实上,通过从语料库中消除频繁出现的单词,我们有选择地扩展了上下文的大小,因此我们可以掌握与信息性单词的关系,否则这些信息性单词将留在窗口之外。
参考
[1] Mikolov 等,2013,向量空间中单词表示的高效估计。
[2] Mikolov 等,2013,词和短语的分布式表征及其组合性。
[3]https://mccormickml . com/2017/01/11/word 2 vec-tutorial-part-2-negative-sampling/
[4] Goldberg,Levy,2014, word2vec 解释:推导 Mikolov 等人的负采样单词嵌入法。
线性关系的度量
统计数字
皮尔逊与 Jupyter 笔记本的积矩相关性
Photo by olena ivanova on Unsplash
**Table of Contents**[**Introduction**](#ae6d)1\. [Import necessary libraries](#bde6)
2\. [Finding the regression line](#e399)
3\. [Pearson’s correlation coefficient](#14c9)
4\. [Finding Pearson’s correlation coefficient](#0513)
5\. [Degree of freedom and critical values](#8a6e)
6\. [The least-squares regression line](#d12c)
7\. [The mean point on the line of best fit](#b71a)
8\. [Real-life example](#a7b5)
9\. [Finding r from more than two arrays](#1b9e)
10\. [Heat map](#591d)[**Conclusion**](#1dbc)
介绍
在本文中,我使用 Python 来分析二元数据,使用皮尔逊的积差相关系数 r,绘制散点图、最佳拟合线和最小二乘回归线。二元数据涉及两种类型的相关数据。皮尔逊的积差相关系数告诉你这两个数据的线性相关程度。我们可以在散点图上显示收集的数据。横轴称为 x 轴,我们称 x 轴的坐标为独立变量。纵轴称为 y 轴,我们称 y 轴的坐标为因变量。
我从这个链接下载了数据。
导入必要的库
让我们创建一个样本数据。
我们需要转置数据,以便按列对数据进行分类。熊猫转置不修改原始数据。
我们使用 Seaborn 创建一个散点图。
寻找回归线
linregress
返回斜率、截距、r 值和 p 值。
The equation of reegression line is y=1.221x+1.151.
Pearson's product-moment correlation coefficient is {r_value:.4f}.
p-value is {p_value:.4f}.
皮尔逊相关系数
对于人口数据
两组总体数据 x 和 y 的皮尔逊积差相关系数为:
其中 cov 是协方差, 𝜎𝑥 和 𝜎𝑦 是 x 和 y 的总体标准差
其中 𝜇𝑥 是 x 的平均值,而 𝜇𝑦 是 y 的平均值
对于示例数据
两组样本数据 x 和 y 的皮尔逊积差相关系数为:
其中 𝑠𝑥𝑦 是协方差, 𝑠𝑥 和 𝑠𝑦 是 x 和 y 的样本标准差
因此:
发现单调关系的力量
towardsdatascience.com](/discover-the-strength-of-monotonic-relation-850d11f72046)
求皮尔逊相关系数
让我们使用样本数据。
数学上使用 Numpy
0.93050085576319
在熊猫中,ddf=0
代表种群,ddf=1
代表样本。因为我们使用所有数据,所以这里使用 0。
使用scipy.stats.pearsonr
0.9305008557631897
pearsonr
返回皮尔逊的积差相关系数和 p 值。
自由度和临界值
最小二乘回归线
最小二乘回归线称为最佳拟合线。由于linregress()
返回斜率和 y 轴截距,我们用它们来做一条回归线。
最佳拟合线上的平均点
我们找到每个数据集的平均值,并将其绘制在同一个图表上。
使用 Jupyter 笔记本的卡方初学者指南
towardsdatascience.com](/gentle-introduction-to-chi-square-test-for-independence-7182a7414a95)
现实生活中的例子
我们将探讨澳大利亚人口和劳动力之间的线性关系。我们使用经合组织的历史人口。让我们用read_csv
找到尺寸(形状)并用shape
和head()
显示前五个数据。
从历史人口数据来看,我们选取澳大利亚。我们还需要在性别列中选择 TOTAL,在年龄列中选择 Total。
我们需要选择值列。我将向你展示两种不同的方法。本网站告诉你如何使用loc
和iloc.
由于 Python 索引从 0 开始,所以位于第 8 列。
在第二种方法中,我们使用&
逻辑运算符。你可以在这篇文章中读到更多关于loc
和iloc
的细节。我们选择国家为澳大利亚、器械包为总计、年龄为总计的列。
我们还需要一份经合组织的数据。数据是 ALFS 的劳动力。我们选择国家为澳大利亚的数据。head()
显示前 5 个数据。
我们需要第 15 个索引的值列,所以我们在iloc
中使用 14。记住 Python 索引是从 0 开始的。
我们可以从df_pop
和df_lab
中找到皮尔逊相关系数。pearsonr
返回r
和p-value
。
我们使用linregress
找到梯度/斜率和 y 截距。我们用海牛和熊猫数据框创建了一个散点图。
澳大利亚的人口和劳动力之间有很强的正相关关系。
从两个以上的数组中寻找 r
有时候你想找出哪一对数据的线性关系最强。可以用corr()
来找。
请注意 A 和 B 的皮尔逊相关系数是 0.952816,和我们之前发现的一样。在这种情况下,A 和 C 的线性关系最强。
热图
我们可以使用 Seaborn 来绘制热图。由于在上一节中我们有最小值 0.88 和最大值 1.00,因此我们相应地设置了vmax
和vmin
。我们使用Blues
作为配色方案。
结论
通过使用 Python 及其库,您可以用几行代码找到所有必要的数据。此外,您可以轻松地可视化您的数据。
通过 成为 的会员,可以完全访问媒体上的每一个故事。
https://blog.codewithshin.com/subscribe
参考
- https://data.oecd.org/
- https://www . statistics solutions . com/table-of-critical-values-Pearson-correlation/
- https://stack abuse . com/seaborn-library-for-data-visualization-in-python-part-2/
- https://www . shanelynn . ie/select-pandas-data frame-rows-and-columns-using-iloc-loc-and-IX/
- https://seaborn.pydata.org/generated/seaborn.heatmap.html
一个极简的端到端的零碎教程(第一部分)
面向初学者的系统化网页抓取
Photo by Paweł Czerwiński on Unsplash
网络抓取是数据科学家的一项重要技能。在过去的几年里,我使用 Python、BeautifulSoup 和 Scrapy 开发了许多专门的 web 抓取项目,并阅读了一些书籍和大量在线教程。然而,我还没有找到一个简单的初学者水平的教程,它是端到端的,涵盖了一个典型的零碎的 web 抓取项目中的所有基本步骤和概念(因此标题中的极简主义者)——这就是为什么我写这篇文章,并希望代码回购可以作为一个模板,帮助启动您的 web 抓取项目。
很多人问:我应该用 BeautifulSoup 还是 Scrapy?它们是不同的东西:BeautifulSoup 是一个用于解析 HTML 和 XML 的库,Scrapy 是一个 web 抓取框架。如果你愿意,你可以使用 BeautifulSoup 而不是 Scrapy 内置选择器,但是将 BeautifulSoup 与 Scrapy 进行比较就像将 Mac 键盘与 iMac 进行比较,或者更好的比喻,如官方文档中所述“就像将 jinja2 与 Django 进行比较”,如果你知道它们是什么的话:)—简而言之,如果你想进行严肃而系统的 web 抓取,你应该学习 Scrapy。
TL;博士,给我看看代码:
[## 哈利旺/剪贴簿-教程
这个报告包含了我的教程的代码:一个极简的端到端的零碎教程(…
github.com](https://github.com/harrywang/scrapy-tutorial)
在本系列教程中,我将讲述以下步骤:
- (本教程)从头开始一个 Scrapy 项目,开发一个简单的蜘蛛。一件重要的事情是使用 Scrapy Shell 来分析页面和调试,这是你应该使用 Scrapy 而不是 BeautifulSoup 的主要原因之一。
- (第二部分)介绍 Item 和 ItemLoader,并解释为什么要使用它们(尽管它们让你的代码一开始看起来更复杂)。
- (第三部分)通过管道使用 ORM (SQLAlchemy)将数据存储到数据库,并展示如何建立最常见的一对多和多对多关系。
- (第四部分)将项目部署到 Scrapinghub(你必须为预定的抓取作业等服务付费)或者通过使用伟大的开源项目 ScrapydWeb 和 Heroku 完全免费地建立自己的服务器。
- ( Part V )我创建了一个单独的 repo ( Scrapy + Selenium )来展示如何抓取动态网页(比如通过滚动加载附加内容的页面)以及如何使用代理网络(ProxyMesh)来避免被禁止。
一些先决条件:
- Python(本教程 Python 3)、虚拟环境、自制等基础知识。,参见我的另一篇文章,了解如何设置环境:如何为 Python 开发设置 Mac
- Git 和 Github 的基础知识。我推荐 Pro Git 的书。
- 数据库和 ORM 的基础知识,如结构化查询语言(SQL)介绍。
我们开始吧!
首先创建一个新文件夹,在文件夹里面设置 Python 3 虚拟环境,安装 Scrapy。为了使这一步变得容易,我创建了一个 starter repo ,你可以派生和克隆它(如果需要,请参见 Python3 虚拟环境文档):
$ git clone [https://github.com/yourusername/scrapy-tutorial-starter.git](https://github.com/yourusername/scrapy-tutorial-starter.git)
$ cd scrapy-tutorial-starter
$ python3.6 -m venv venv
$ source venv/bin/activate
$ pip install -r requirements.txt
您的文件夹应该如下所示,我假设我们总是在虚拟环境中工作。注意到目前为止我们在 requirements.txt 中只有一个包。
运行scrapy startproject tutorial
创建一个空的 scrapy 项目,你的文件夹看起来像:
创建了两个相同的“教程”文件夹。我们不需要第一级“教程”文件夹—删除它,并将第二级“教程”文件夹及其内容向上移动一级—我知道这很混乱,但这就是你对文件夹结构所做的一切。现在,您的文件夹应该看起来像这样:
到目前为止,不要担心自动生成的文件,我们稍后将回到这些文件。本教程基于官方 Scrapy 教程。所以我们要爬的网站是http://quotes.toscrape.com,很简单:有几页引用作者和标签:
当您单击作者时,它会转到作者详细信息页面,包括姓名、生日和简历。
现在,在“spider”文件夹中创建一个名为“quotes-spider.py”的新文件,其内容如下:
您刚刚创建了一个名为“quotes”的蜘蛛,它向http://quotes.toscrape.com发送请求,并从服务器获得响应。然而,到目前为止,蜘蛛在解析响应时不做任何事情,只是向控制台输出一个字符串。让我们运行这个蜘蛛:scrapy crawl quotes
,您应该会看到如下输出:
接下来,让我们使用 Scrapy Shell 通过运行以下命令来分析响应,即位于http://quotes.toscrape.com的 HTML 页面:
$ scrapy shell http://quotes.toscrape.com/...2019-08-21 20:10:40 [scrapy.core.engine] INFO: Spider opened2019-08-21 20:10:41 [scrapy.core.engine] DEBUG: Crawled (404) <GET http://quotes.toscrape.com/robots.txt> (referer: None)2019-08-21 20:10:41 [scrapy.core.engine] DEBUG: Crawled (200) <GET http://quotes.toscrape.com/> (referer: None)[s] Available Scrapy objects:[s] scrapy scrapy module (contains scrapy.Request, scrapy.Selector, etc)[s] crawler <scrapy.crawler.Crawler object at 0x105d01dd8>[s] item {}[s] request <GET http://quotes.toscrape.com/>[s] response <200 http://quotes.toscrape.com/>[s] settings <scrapy.settings.Settings object at 0x106ae34e0>[s] spider <DefaultSpider 'default' at 0x106f13780>[s] Useful shortcuts:[s] fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)[s] fetch(req) Fetch a scrapy.Request and update local objects[s] shelp() Shell help (print this help)[s] view(response) View response in a browser>>>
您可以使用 Xpath 选择器或 CSS 选择器和 Chrome DevTools 来选择元素,它们通常用于分析页面(我们不会涉及选择器细节,请阅读文档以了解如何使用它们):
例如,您可以测试选择器并在 Scrapy Shell 中查看结果——假设我们想要获得上面显示的报价块:
可以使用 Xpath response.xpath(“//div[@class=’quote’]”).get()
( .get()
显示第一个选中的元素,使用.getall()
显示全部)或者 CSS response.css(“div .quote”).get()
。我用粗体显示了我们希望从这个报价块中获得的报价文本、作者和标签:
>>> response.xpath("//div[@class='quote']").get()'<div class="quote" itemscope itemtype="http://schema.org/CreativeWork">\n <span class="text" itemprop="text">**“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”**</span>\n <span>by <small class="author" itemprop="author">**Albert Einstein**</small>\n <a href="/author/Albert-Einstein">(about)</a>\n </span>\n <div class="tags">\n Tags:\n <meta class="keywords" itemprop="keywords" content="change,deep-thoughts,thinking,world"> \n \n <a class="tag" href="/tag/change/page/1/">**change**</a>\n \n <a class="tag" href="/tag/deep-thoughts/page/1/">**deep-thoughts**</a>\n \n <a class="tag" href="/tag/thinking/page/1/">**thinking**</a>\n \n <a class="tag" href="/tag/world/page/1/">**world**</a>\n \n </div>\n </div>'
我们可以继续在 shell 中获取如下数据:
- 将所有报价块转换成“报价”
- 使用“quotes”中的第一个引号:quotes[0]
- 尝试 css 选择器
>>> quotes = response.xpath("//div[@class='quote']")
>>> quotes[0].css(".text::text").getall()['“The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”']>>> quotes[0].css(".author::text").getall()['Albert Einstein']>>> quotes[0].css(".tag::text").getall()['change', 'deep-thoughts', 'thinking', 'world']
看起来上面显示的选择器得到了我们需要的东西。注意,出于演示目的,我在这里混合了 Xpath 和 CSS 选择器——在本教程中不需要两者都用。
现在,让我们修改蜘蛛文件并使用关键字yield
将选择的数据输出到控制台(注意,每个页面都有许多引用,我们使用一个循环来遍历所有引用):
import scrapyclass QuotesSpider(scrapy.Spider):
name = "quotes"start_urls = ['[http://quotes.toscrape.com'](http://quotes.toscrape.com')]def parse(self, response):
self.logger.info('hello this is my first spider')
quotes = response.css('div.quote')
for quote in quotes:
yield {
'text': quote.css('.text::text').get(),
'author': quote.css('.author::text').get(),
'tags': quote.css('.tag::text').getall(),
}
再次运行蜘蛛:scrapy crawl quotes
可以在日志中看到提取的数据:
您可以通过运行scrapy crawl quotes -o quotes.json
将数据保存在一个 JSON 文件中
到目前为止,我们从第一页获得所有报价信息,我们的下一个任务是抓取所有页面。您应该注意到首页底部有一个“下一页”按钮用于页面导航,逻辑是:单击“下一页”按钮进入下一页,获取报价,再次单击“下一页”直到没有“下一页”按钮的最后一页。
通过 Chrome DevTools,我们可以获得下一页的 URL:
让我们通过再次运行scrapy shell [http://quotes.toscrape.com/](http://quotes.toscrape.com/)
在 Scrapy Shell 中进行测试:
$ scrapy shell [http://quotes.toscrape.com/](http://quotes.toscrape.com/)
...>>> response.css('li.next a::attr(href)').get()'/page/2/'
现在,我们可以编写以下代码,让蜘蛛浏览所有页面以获取所有报价:
next_page = response.urljoin(next_page)
获取完整的 URL,yield scrapy.Request(next_page, callback=self.parse)
发送一个新的请求来获取下一个页面,并使用回调函数调用相同的解析函数来获取新页面的报价。
可以使用快捷键进一步简化上面的代码:参见本节。本质上,response.follow
支持相对 URL(不需要调用urljoin
),并自动为<a>
使用href
属性。因此,代码可以进一步缩短:
for a in response.css('li.next a'):
yield response.follow(a, callback=self.parse)
现在,再次运行蜘蛛程序scrapy crawl quotes
,你应该会看到所有 10 页的引文都被提取出来了。坚持住——我们就要完成第一部分了。下一个任务是抓取单个作者的页面。
如上所示,当我们处理每个报价时,我们可以通过跟随突出显示的链接进入单个作者的页面—让我们使用 Scrapy Shell 来获得链接:
$ scrapy shell [http://quotes.toscrape.com/](http://quotes.toscrape.com/)
...
>>> response.css('.author + a::attr(href)').get()'/author/Albert-Einstein'
因此,在提取每个引文的循环过程中,我们发出另一个请求,以转到相应作者的页面,并创建另一个parse_author
函数来提取作者的姓名、生日、出生地点和简历,并输出到控制台。更新后的蜘蛛如下所示:
再次运行 spiderscrapy crawl quotes
并再次检查您需要提取的所有内容是否正确输出到控制台。注意 Scrapy 基于 Twisted ,这是一个流行的 Python 事件驱动网络框架,因此是异步的。这意味着单独的作者页面可能不会与相应的报价同步处理,例如,作者页面结果的顺序可能与页面上的报价顺序不匹配。我们将在后面的部分讨论如何将报价与其对应的作者页面链接起来。
恭喜你,你已经完成了本教程的第一部分。
在第二部分中了解更多关于物品和物品装载器的信息。
一个极简的端到端剪贴簿教程(第二部分)
面向初学者的系统化网页抓取
在第一部分中,您了解了如何设置 Scrapy 项目并编写了一个基本的蜘蛛程序来通过页面导航链接提取网页。但是,提取的数据仅显示在控制台上。在第二部分中,我将介绍 Item 和 ItemLoader 的概念,并解释为什么应该使用它们来存储提取的数据。
我们先来看看刺儿头架构:
正如您在第 7 步和第 8 步中看到的,Scrapy 是围绕项目的概念设计的,即,蜘蛛将把提取的数据解析成项目,然后项目将通过项目管道进行进一步处理。我总结了使用 Item 的一些关键原因:
- Scrapy 是围绕 Item 和 expect Items 设计的,作为 spider 的输出——您将在第四部分看到,当您将项目部署到 ScrapingHub 或类似的服务时,会有默认的 ui 供您浏览项目和相关的统计数据。
- 项目在一个单独的文件中明确定义了通用的输出数据格式,这使您能够快速检查您正在收集的结构化数据,并在您错误地创建不一致的数据时提示异常,例如在代码中拼写错误的字段名称,这种情况比您想象的更常见:)。
- 您可以(通过 ItemLoader)为每个项目字段添加前/后处理,如修剪空格、删除特殊字符等。,并将这个处理代码从主蜘蛛逻辑中分离出来,以保持代码的结构化和整洁。
- 在第三部分中,您将学习如何添加不同的项目管道来完成诸如检测重复项目和将项目保存到数据库中之类的事情。
在第一部分的结尾,我们的蜘蛛产生了以下数据:
yield {
'text': quote.css('.text::text').get(),
'author': quote.css('.author::text').get(),
'tags': quote.css('.tag::text').getall(),
}
和
yield {
'author_name': response.css('.author-title::text').get(),
'author_birthday': response.css('.author-born-date::text').get(),
'author_bornlocation': response.css('.author-born-location::text').get(),
'author_bio': response.css('.author-description::text').get(),
}
你可能会注意到author
和author_name
是同一个东西(一个来自报价页面,一个来自对应的个人作者页面)。因此,我们实际上提取了 6 条数据,即引用文本、标签、作者姓名、生日、出生地点和简历。现在,让我们定义保存这些数据的项目。
打开自动生成的items.py
文件,更新其内容如下:
我们只定义了一个名为“QuoteItem”的 Scrapy 项,它有 6 个字段来存储提取的数据。在这里,如果您以前设计过关系数据库,您可能会问:我是否应该有两个项目 QuoteItem 和 AuthorItem 来更好地逻辑表示数据?答案是可以的,但是在这种情况下不推荐,因为 Scrapy 以异步方式返回条目,并且您将添加额外的逻辑来匹配报价条目和其对应的条目——在这种情况下,将相关的报价和作者放在一个条目中要容易得多。
现在,您可以将提取的数据放入蜘蛛文件的条目中,如下所示:
from tutorial.items import QuoteItem
...
quote_item = QuoteItem()
...for quote in quotes:
quote_item['quote_content'] = quote.css('.text::text').get()
quote_item['tags'] = quote.css('.tag::text').getall()
或者,更好的方法是使用 ItemLoader,如下所示:
from scrapy.loader import ItemLoader
from tutorial.items import QuoteItem
...
for quote in quotes:
loader = ItemLoader(item=QuoteItem(), selector=quote)
loader.add_css('quote_content', '.text::text')
loader.add_css('tags', '.tag::text')
quote_item = loader.load_item()
嗯,ItemLoader 的代码看起来更复杂——为什么要这么做?简单的回答是:从 css 选择器获得的原始数据可能需要进一步解析。例如,提取的quote_content
在 Unicode 中有引号,需要去掉。
生日是一个字符串,需要解析成 Python 日期格式:
出生位置在提取的字符串中有“in ”,需要删除:
ItemLoader 使预处理/后处理功能可以很好地从 spider 代码中指定,并且项目的每个字段都可以有不同的预处理/后处理功能集,以便更好地重用代码。
例如,我们可以创建一个函数来删除前面提到的 Unicode 引号,如下所示:
MapCompose 使我们能够对一个字段应用多个处理函数(在本例中我们只有一个)。ItemLoader 返回一个列表,比如标签的[‘death’, ‘life’]
。对于作者姓名,虽然返回一个列表值,如[‘Jimi Hendrix’]
,但 TakeFirst 处理器取列表的第一个值。添加额外的处理器后,items.py 看起来像:
现在的关键问题是,我们从报价页面加载两个字段quote_content
和tags
,然后发出另一个请求来获取相应的作者页面以加载author_name
、author_birthday
、author_bornlocation
和bio
。为此,我们需要将项目quote_item
作为元数据从一个页面传递到另一个页面,如下所示:
yield response.follow(author_url, self.parse_author, meta={'quote_item': quote_item})
而在作者解析函数中,你可以得到条目:
def parse_author(self, response):
quote_item = response.meta['quote_item']
现在,在添加了 Item 和 ItemLoader 之后,我们的 spider 文件看起来像这样:
在控制台 Scrapy stats 中运行蜘蛛scrapy crawl quotes
,可以找到提取的物品总数:
2019-09-11 09:49:36 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
...'downloader/request_count': 111,
...'item_scraped_count': 50,
恭喜你。您已经完成了本教程的第二部分。
一个极简的端到端剪贴簿教程(第三部分)
面向初学者的系统化网页抓取
Photo by Sarah Dorweiler on Unsplash
在第二部分中,您已经从网站中提取了所有需要的数据,并将它们存储在 Items 中。在第三部分中,我将引入项目管道,使用 ORM (SQLAlchemy)将提取的数据保存到数据库中,并处理重复数据问题。
由蜘蛛返回的每个项目被顺序发送到项目管道(如果有的话),以进行额外的处理,例如将项目保存到数据库、数据验证、删除重复项等。项目管道在pipelines.py
文件中被定义为类,打开这个自动生成的文件,可以看到一个空管道被定义为“TutorialPipeline”:
您需要在settings.py
文件中指定启用哪个管道以及管道的顺序——默认情况下,没有管道被启用。要启用上面的空管道,请在settings.py
中注释掉以下部分:
整数值(通常范围从 0 到 1000),例如上面所示的 300,决定了管道的执行顺序(较低值的管道首先运行)。
接下来,让我们开发一个管道来将项目保存到数据库中。在这里,我使用面向对象的范例,使用对象关系映射(ORM)来查询和操作数据库中的数据。特别是,我使用 SQLAlchemy 。我不会涉及 ORM 的细节,请参考这篇文章了解一些利弊。
首先,让我们设计数据库模式。注意,该项目有 6 个字段,如quote_content
、tags
、author_name
、author_birthday
、author_bornlocation
和bio
。我将使用三个表来存储这些数据,即报价、标签、作者。引用和标记之间存在多对多关系(一个引用可以有一个或多个标记,一个标记可以与一个或多个引用相关联),作者和引用之间存在一对多关系(一个作者可以有一个或多个引用,但是一个引用只属于一个作者)。
要通过 SQLAlchemy 使用 ORM 定义这个模式,您需要:
- 在
requirements.txt
中添加SQLAlchemy>=1.3.6
,在虚拟环境中运行pip install -r requirements.txt
安装软件包 - 用以下内容创建一个
models.py
:
db_connect()
功能使用create_engine(get_project_settings().get(“CONNECTION_STRING”))
连接到数据库。CONNECTION_STRING
在settings.py
文件中指定。您可以更改连接字符串以连接到不同的数据库系统,如 SQLite、MySQL、Postgres,而无需更改代码。在本教程中,我使用 SQLite,它本质上是一个名为scrapy_quotes.db
的本地文件,在蜘蛛第一次运行时在根文件夹中创建。
CONNECTION_STRING = ‘sqlite:///scrapy_quotes.db’
我还提供了一个连接 MySQL 的示例(注释掉了):
# MySQL
CONNECTION_STRING = "{drivername}://{user}:{passwd}@{host}:{port}/{db_name}?charset=utf8".format(
drivername="mysql",
user="harrywang",
passwd="tutorial",
host="localhost",
port="3306",
db_name="scrapy_quotes",
)
现在,让我们创建将项目保存到数据库的管道。打开pipelines.py
文件,添加以下类(管道):
确保您还导入了所需的包和函数:
下面的 init 函数使用models.py
中的函数连接到数据库(db_connect
)并创建表格(create_table
),如果表格还不存在的话(否则被忽略)。
在process_item
函数中,我首先为数据库会话和三个表创建实例。然后,我将作者信息和引用文本值分配给相应的表列。
接下来,我们需要检查当前条目的作者和标签是否已经存在于数据库中,如果到目前为止它们还不存在,则只创建新的作者/标签:
最后,我将报价添加到数据库中:
请注意,由于您在 ORM ( quote.author
和quote.tags
)中指定的关系,您不需要显式添加作者和标签—新的作者/标签(如果有)将由 SQLAlchemy 自动创建和插入。
现在,运行蜘蛛scrapy crawl quotes
,你应该看到一个名为scrapy_quotes.db
的 SQLite 文件被创建。您可以使用 SQLite 命令行打开文件以查看提取的内容:
$ sqlite3 scrapy_quotes.db
...sqlite> .tables
author quote quote_tag tagsqlite> select * from quote limit 3;1|The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.|12|Imperfection is beauty, madness is genius and it's better to be absolutely ridiculous than absolutely boring.|23|The person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.|3sqlite> .quit
或者用于 SQLite 的 DB 浏览器:
请注意,我们提取了 50 个报价。假设网站可能会添加额外的报价,并且您希望每周运行一次蜘蛛来收集新报价(如果有的话)。所以,让我们再次运行蜘蛛scrapy crawl quotes
,你可能会注意到一个问题:我们现在在数据库中有 100 个报价——同样的 50 个报价被再次提取和存储!
接下来,让我们添加另一个管道来检查该项,以查看该项是否是重复的,如果是,则删除该项,以便该项不会通过其余的管道。
打开 pipelines.py 文件并添加以下类(pipeline):
确保导入 DropItem 异常:from scrapy.exceptions import DropItem
。逻辑很简单:执行数据库查询,查看当前商品的报价文本是否已经存在,如果存在,则删除该商品。现在,您需要在settings.py
中启用这个管道,并确保在保存到数据库管道之前执行复制管道:
您可以先删除 SQLite 文件,然后运行几次蜘蛛程序,您将看到只有第一次数据库被填充了 50 个引号。之后,您可以看到警告信息,指示重复的项目已被删除。
2019-09-12 11:16:04 [scrapy.core.scraper] WARNING: Dropped: Duplicate item found...
2019-09-12 11:16:04 [scrapy.core.engine] INFO: Closing spider (finished)2019-09-12 11:16:04 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
...'item_dropped_count': 50,'item_dropped_reasons_count/DropItem': 50,...
你已经完成了第三部分!!干杯。在第四部分中,我将向您展示如何部署蜘蛛来进行周期性的爬行和监视,例如,每 10 分钟自动运行一次蜘蛛。
一个极简的端到端剪贴簿教程(第四部分)
面向初学者的系统化网页抓取
Photo by Paweł Czerwiński on Unsplash
在前三部分中,您已经开发了一个蜘蛛,它从http://quotes.toscrape.com中提取报价信息,并将数据存储到本地 SQLite 数据库中。在这一部分,我将向您展示如何将蜘蛛部署到云中。
首先,让我们看看你如何部署到https://scrapinghub.com——开源 Scrapy 框架背后的团队运行的商业服务。
创建一个免费帐户和一个新项目:
然后,点击 Code and deployments 菜单,按照屏幕上显示的说明安装shub
——记录您的 API 密钥和部署号。
回到 scrapy-tutorial 的根目录(scrapy 项目的根目录),使用命令shub login
和shub deploy
将你的项目部署到 Scrapy hub:
(venv) dami:scrapy-tutorial harrywang$ shub login
Enter your API key from [https://app.scrapinghub.com/account/apikey](https://app.scrapinghub.com/account/apikey)
API key: xxxxx
Validating API key...
API key is OK, you are logged in now.(venv) dami:scrapy-tutorial harrywang$ shub deploy 404937
Messagepack is not available, please ensure that msgpack-python library is properly installed.
Saving project 404937 as default target. You can deploy to it via 'shub deploy' from now on
Saved to /Users/harrywang/xxx/scrapy-tutorial/scrapinghub.yml.
Packing version b6ac860-master
Created setup.py at /Users/harrywang/xxx/scrapy-tutorial
Deploying to Scrapy Cloud project "404937"
{"status": "ok", "project": 4xxx, "version": "b6ac860-master", "spiders": 3}
Run your spiders at: [https://app.scrapinghub.com/p/404937/](https://app.scrapinghub.com/p/404937/)
Scrapinghub 配置文件scrapinghub.yml
在根文件夹中创建,您需要编辑它来指定我们特定的软件包需求。否则,将使用默认设置:
- 运行 Python 3 的 scrapy 1.7
- 其他包的需求文件
运行$ shub deploy
再次部署,新配置生效。
假设我在 repo 中有 3 个蜘蛛(quotes_spider_v1.py
和quotes_spider_v2.py
是用于演示目的的中间蜘蛛),您应该在已部署的项目中看到 3 个蜘蛛(quotes_spider.py
是主蜘蛛):
现在,你可以运行你的蜘蛛:
作业完成后,您可以检查结果并下载不同格式的项目:
但是,您必须付费才能运行定期作业,例如,每周二上午 8 点运行您的蜘蛛。
在搜索运行定期爬行作业的免费选项时,我遇到了由 my8100 开发的伟大的开源项目 ScrapydWeb——非常感谢作者开发了这样一个具有伟大特性和文档的好项目。接下来,我将带你通过 Heroku 使用 ScrapydWeb 建立你自己的“ScrapingHub.com”的过程(你也可以按照作者的文档)。
下图显示了 ScrapydWeb 的架构,该架构旨在支持分布式爬行。
source: https://github.com/my8100/scrapyd-cluster-on-heroku
在本教程中,我不介绍分布式爬行。相反,我将只设置两个服务器:一个 ScrapydWeb 服务器(该服务器提供 web UI 来管理不同的蜘蛛和作业)和一个 Scrapyd 服务器(该服务器托管您的蜘蛛代码并实际发送/接收请求/响应)。
ScrapydWeb 的作者通过在他的 repo 中预先配置 Heroku 使这个部署过程变得非常简单,repo 位于ttps://github . com/my 8100/scrapyd-cluster-on-Heroku。
- scrapyd-cluster-on-heroku/ScrapydWeb:该文件夹包含 ScrapydWeb 服务器的 Heroku 配置
- Scrapyd-cluster-on-heroku/Scrapyd:该文件夹包含 Scrapyd 服务器的 Heroku 配置
我们需要定制部署,因为我们的 scrapy 项目有特定的包需求,例如,SQLAlchemy、MySQL、Python 3.x 等。因此,你需要将https://github.com/my8100/scrapyd-cluster-on-heroku的副本放到你的 github 账户中,例如https://github.com/harrywang/scrapyd-cluster-on-heroku并做一些修改来支持本教程。
您可以在https://github . com/Harry Wang/scrapyd-cluster-on-heroku/commit/e 612 dcb 9 a6 c 158 da 4b 744d 311 e 82 c 529497 fba7c查看我对作者回购的修改,包括:
- 在 scrapyd/requirements.txt 中添加 MySQL 和 SQLAlchem 包
- 在 scrapyd/runtime.txt 中将 python 版本更改为 python-3.6.8
- 打开 Scrapy web 服务器认证,在 Scrapy web/Scrapy web _ settings _ v10 . py 中设置用户名和密码(不要这样公开用户名和密码)
接下来,在 heroku.com 创建一个免费账户并安装 Heroku CLI: brew tap heroku/brew && brew install heroku
然后,克隆回购:
git clone [https://github.com/harrywang/scrapyd-cluster-on-heroku](https://github.com/harrywang/scrapyd-cluster-on-heroku)
cd scrapyd-cluster-on-heroku/
登录 Heroku:
scrapyd-cluster-on-heroku harrywang$ heroku login
heroku: Press any key to open up the browser to login or q to exit:
Opening browser to [https://cli-auth.heroku.com/auth/browser/3ba7221b-9c2a-4355-ab3b-d2csda](https://cli-auth.heroku.com/auth/browser/3ba7221b-9c2a-4355-ab3b-d2csda)
Logging in… done
Logged in as [xxx@gmail.com](mailto:xxx@gmail.com)
部署垃圾服务器/应用程序:
- 首先转到/scrapyd 文件夹,并通过运行以下 git 命令使该文件夹成为 git repo:
git init
git status
git add .
git commit -a -m "first commit"
git status
- 创建一个名为
scrapy-server1
的新应用程序(如果这个应用程序被占用,请选择另一个) - 设置一个名为 heroku 的 git 遥控器
- 检查 git 遥控器
- 将/scrapyd 文件夹中的内容推送到遥控器以部署应用程序
$ pwd
/Users/harrywang/xxx/scrapyd-cluster-on-heroku/scrapyd$ heroku apps:create scrapy-server1$ heroku git:remote -a scrapy-server1set git remote heroku to https://git.heroku.com/scrapy-server1.git$ git remote -vheroku https://git.heroku.com/scrapy-server1.git (fetch)heroku https://git.heroku.com/scrapy-server1.git (push)origin https://github.com/harrywang/scrapyd-cluster-on-heroku (fetch)origin https://github.com/harrywang/scrapyd-cluster-on-heroku (push)$ git push heroku master
您可以为远程垃圾服务器设置环境变量,例如设置时区:
$ heroku config:set TZ=US/EasternSetting TZ and restarting ⬢ scrapy-server1... done, **v4**TZ: US/Eastern
现在,你有了一个运行在 http://scrapy-server1.herokuapp.com 的 scrapyd 服务器
接下来,让我们设置 web 应用程序,它为我们添加 scrapyd 服务器、上传 scrapy 项目和安排爬行作业提供了 UI。
同样,让我们部署 ScrapydWeb 服务器/应用程序:
- 首先转到/scrapydweb 文件夹,通过运行以下 git 命令使该文件夹成为 git repo:
git init
git status
git add .
git commit -a -m "first commit"
git status
- 创建一个名为
scrapyd-web
的新应用 - 设置一个名为 heroku 的 git 遥控器
- 检查 git 遥控器
- 将/scrappy web 文件夹中的内容推送到远程设备以部署应用程序
- 设置时区
$ pwd/Users/harrywang/xxx/scrapyd-cluster-on-heroku/scrapydweb$ heroku apps:create scrapyd-web$ heroku git:remote -a scrapyd-webset git remote heroku to [https://git.heroku.com/scrapyd-web.git](https://git.heroku.com/scrapyd-web.git)$ git remote -v
$ git push heroku master
$ heroku config:set TZ=US/Eastern
Setting TZ and restarting ⬢ scrapyd-web... done, **v6**TZ: US/Eastern
您需要向 web 服务器添加至少一个 Scrapyd 服务器(让我们添加您刚刚在scrapy-server1.herokuapp.com上面设置的服务器)。您可以以类似的方式添加更多用于分布式爬网的 scrapyd 服务器:
$ heroku config:set SCRAPYD_SERVER_1=scrapy-server1.herokuapp.com:80Setting SCRAPYD_SERVER_1 and restarting ⬢ scrapyd-web... done, **v6**SCRAPYD_SERVER_1: scrapy-server1.herokuapp.com:80
现在,你有了运行在 http://scrapyd-web.herokuapp.com 的 scrapyd 网络服务器。在浏览器中打开该地址,使用您在 scrapydweb/scrapydweb _ settings _ v10 . py 文件中指定的用户名和密码登录,您应该会看到 web 服务器的管理 UI:
最后一个任务是使用[scrapyd-client](https://github.com/scrapy/scrapyd-client)
部署我们的 scrapy 项目。
去我们的 scrapy 项目回购:
$ pwd/Users/harrywang/xxx/scrapy-tutorial
使用pip install git+[https://github.com/scrapy/scrapyd-client](https://github.com/scrapy/scrapyd-client)
安装 scrapyd-client。
打开scrapy.cfg
文件,修改其内容,添加如下部署配置:
[settings]
default = tutorial.settings[deploy]
url = [http://scrapy-server1.herokuapp.com](http://scrapy-server1.herokuapp.com)
username = admin
password = scrapydweb
project = scrapy-tutorial
然后,使用scrapyd-deploy
将我们的项目打包并部署到 scrapyd 服务器:
$ scrapyd-deploy/Users/harrywang/xxx/scrapy-tutorial/venv/lib/python3.6/site-packages/scrapyd_client/deploy.py:23: ScrapyDeprecationWarning: Module `scrapy.utils.http` is deprecated, Please import from `w3lib.http` instead.from scrapy.utils.http import basic_auth_headerPacking version 1568331034Deploying to project "scrapy-tutorial" in http://scrapy-server1.herokuapp.com/addversion.jsonServer response (200):{"node_name": "5f9ee34d-f6c8-4d80-ac05-3657c4920124", "status": "ok", "project": "scrapy-tutorial", "version": "1568331034", "spiders": 3}
在浏览器中打开http://scrapyd-web.herokuapp.com/1/projects/,您应该看到项目成功部署:
点击菜单"运行蜘蛛",可以运行"行情"蜘蛛:
然后,您可以在“作业”菜单中检查结果,并在“项目”菜单中下载项目——您可以尝试其他菜单来查看统计数据、错误等。:
最后一个任务是指定一个定时任务,比如每 10 分钟自动运行一次报价蜘蛛:点击“定时器任务”,下面的截图显示了一个每 10 分钟运行一次的任务——定时器任务的特性是基于一个名为 APScheduler 的高级 Python 调度器库,参见这部分文档来弄清楚如何为定时器设置不同的值。
然后,您可以检查任务结果:
下面显示计时器任务已经触发了 4 次:
注意,在将我们的项目部署到服务器时,我们不应该使用本地 SQLite 数据库。相反,我们应该保存到一个远程数据库,比如 MySQL 服务器——您只需要像我们在第三部分中讨论的那样更改 CONNECTION_STRING 变量。
恭喜你!你已经完成了本教程,我希望你喜欢学习。
作为奖励,我还创建了一个单独的 repo ( Scrapy + Selenium )来展示如何抓取动态网页(例如通过滚动加载附加内容的页面)以及如何使用代理网络(ProxyMesh)来避免被禁止,请阅读第五部分。
防止语音助手误触发的建议
只需使用内部噪音消除来防止语音助手用反馈触发自己
就是这样。标题说明了一切。
Image by TechCrunch
开个玩笑,我会进一步解释这个想法,为什么我认为这是解决问题的必要方法,为什么我觉得我首先需要写这篇文章。
两句外卖
由于音频反馈,Alexa、Cortana 和谷歌助手等语音助手目前似乎能够在输出的声音包含唤醒词或至少足够接近的内容时触发自己。我建议通过过滤掉助理音频输出的噪声控制算法来输入他们的麦克风输入,从而解决这个问题。
问题是
你曾经通过你的助理设备(Google Home、Amazon Echo 等)播放过播客、新闻甚至音乐吗?)只是为了让它说出那些可怕的单词“Ok Google”/“Alexa”?那么毫无疑问,你已经经历了我试图解决的问题。本质上,这些助手能够通过说出自己的唤醒词来逗自己,然后对唤醒词做出回应。更糟糕的是,当这种情况发生时,唤醒词/短语甚至没有说。
亚马逊朝着正确方向迈出的一步
最近亚马逊的 Alexa 团队想出了一个聪明的方法,可以部分地帮助解决这个问题,但它不是万无一失的。本质上,他们创建了一个已知假阳性“Alexa”触发器(如亚马逊商业广告)的指纹数据库,然后每当它认为有人说了唤醒词时,就会根据这个数据库检查新的触发器。如果你有一个 Echo 设备,并观看了最近的超级 Owl handegg 游戏,你可能会注意到 Alexa 的商业广告令人惊讶地没有让你的 Echo 发疯。此外,Amazon 创建了一种方法来自动扩展此数据库,以防止第三方误报,方法是用相似的指纹标记来自多个客户的唤醒事件,将其记录为“媒体事件”。
An illustration of how fingerprints are used to match audio. Different instances of Alexa’s name result in a bit error rate of about 50% (random bit differences). A bit error rate significantly lower than 50% indicates two recordings of the same instance of Alexa’s name. Image by the Amazon’s developer blog
虽然这应该提供了显著的改进,但是这种方法不足以完全解决这个问题,特别是因为我们现在消费的大多数媒体是流式的,因此是非同步的,所以许多错误触发不会同时发生。此外,这种方法涉及比较云中除了最可预测的触发器之外的所有触发器的指纹,这增加了助理使用的处理时间和带宽。我称赞亚马逊实现了这样一种新颖的方法(尽管它并不完全新颖,因为我非常确定谷歌的个性化语音识别模型遵循了类似的设计模式),即使它不是包罗万象的。
但是多麦克风系统的噪音消除怎么样呢?
的确,现在大多数(如果不是全部的话)语音助手设备都带有多个麦克风来检测背景噪音并将其过滤掉,使用一种称为波束形成的噪音控制技术来专注于你的声音。然而,这个问题只是与手头的问题无关。例如,多麦克风设置非常适合在关掉房间中的大音量电视时清晰(或多或少)地听到您的命令,但当设备在附近没有人说话的情况下听到命令时,这种设置就不是完成任务的正确工具。
“大”的想法
我的提议真的很简单,简单到我曾经以为语音助手的所有主要参与者都在使用这种方法。然而,随着我花更多的时间在家里对着机器人大喊大叫告诉我这个消息,我开始意识到他们要么没有这样做,要么如果他们这样做了,他们做得不好。在听了许多其他人的轶事后,我知道我不是唯一一个确保用耳机听我最喜欢的技术播客的人,以免让我过于热切的人工智能助手产生错误的想法。
好吧,也许他们现在的语音助手不会这么做,但这种方法现在应该已经在其他地方提出来了,对吧?也许吧,但我不这么认为。我使用各种相关的术语和各种平台进行了几次搜索,从常规的谷歌搜索,到谷歌学术搜索,再到语义学者搜索。如果有一篇研究论文、专利、博客文章或任何其他公开的资源,那么我觉得我应该找到它;我通常能找到我想要的。如果我错过了什么关键的东西,我很想听听。我找到了大量关于主动噪声控制(ANC)的资源,以及更多关于人工智能语音助手的资源,但我能找到的最接近这一应用的是这篇关于语音助手噪声消除的论文,但可惜的是,范围仅限于外部噪声,正如上文针对多麦克风设置所讨论的:https://pdfs . semantic scholar . org/e448/5 abce 7703 bace 8 FDD 3 ba 3 e 65688 e 1c 60d 827 . pdf?_ ga = 2.7190005241670009-4167009
Diagram by Silentium
术语的快速概述:
主动噪声控制(ANC) ,有时也称为主动噪声消除,通过记录不想要的噪声,然后回放其相反的波形(基本上就是相同的声音在时间上向后移动了半个相位)来过滤掉不想要的噪声,从而消除不想要的噪声,同时保留想要的噪声。
我们的问题实际上可以归结为音频反馈,即系统在音频输入中拾取自己的音频输出。在像剧院这样的现场音响系统中,由于反馈回路放大高频,反馈很快转变为震耳欲聋的尖叫。幸运的是,我们的语音助手不会屈服于反馈循环,因为他们不会立即输出他们的输入音频,但每当你的助手触发自己时,它都会对反馈做出反应。我在影音系统方面的背景可能是我之前假设这种方法已经被广泛使用的原因。
工作原理:
该系统应该能够完全在大多数语音助理系统的软件中实现,因此能够在当前一代语音硬件(如 Google Homes 和 Amazon Echos)上部署更新。我建议使用发送到扬声器的音频馈送,而不是使用独立的外部麦克风作为 ANC 滤波器的输入。由于设备正在生成音频,我们可以 100%确定我们不需要使用任何音频作为输入。这不仅可以防止反馈意外触发助手,还可以帮助助手在音频输出时专注于人类的命令,从而在你想与助手交谈时更容易通过你的音乐大喊。最重要的是,与亚马逊的方法不同,这种方法不需要 ping 云进行验证,也不会给助理设备增加过多的处理开销,因为 ANC 可以是一种相对简单的计算。就像大多数人无法挠自己痒痒一样,因为我们的大脑知道我们自己的行为产生了什么输入,这种方法将使人工智能助手无法触发自己。
The orange elements represent the software bits that I propose adding to the system.
这个想法的类似实现:
虽然我无法找到这种技术用于语音助手设备的任何证据,但我确实发现这种方法在其他应用中得到利用,例如专业音频音响系统或扬声器电话中的反馈抑制,就像这个过期的专利:【https://patents.google.com/patent/US6212273B1/en】T4
因此,如果这已经是现有人工智能助手设计的一部分,我很抱歉重复这一显而易见的内容,但请继续阅读,因为有一些方法可以建立在这个想法上,我认为这些方法还没有开发出来。据我所知,这种方法或任何类似的方法目前没有在任何人工智能助手中使用。
潜在的障碍:
一个可能的问题是,如果设备没有拾取到显著的声反馈,则以数字方式引入反馈。许多语音助手设备具有非常复杂的麦克风阵列和扬声器,旨在最大限度地减少反馈,因此,如果麦克风拾取的音频不够响亮,理论上可以将新音频添加到输入中。这是由于 ANC 的工作方式:简单地异相播放相同的音频。避免这一问题的一种方法是,根据来自麦克风输入的音频电平,调整发送到 ANC 的输出音频的增益/音量。这种缩放可以作为 ANC 算法的函数动态发生,并且可以在每次设备上电时设置基线,作为针对其特定声学环境对其进行校准的方式。
另一个潜在的缺点可能是需要重新训练触发字检测算法,以考虑 ANC 算法的过滤。然而,从我对算法的理解来看,它们可能足够健壮,能够在这些不同的参数下很好地运行。此外,如果他们用非常干净的音频进行训练,这种过滤后的音频可能会更接近训练数据。
Gif by tenor
更多功能:
人工智能语音接口的圣杯是与人工智能的全双工通信,其中人工智能说话和人类说话之间可以有重叠,双方都可以跟上。当前版本的语音助手就像对讲机,如果你打断它,它必须停止说话;这被称为半双工。虽然制作一个令人信服的人类语音人工智能的步骤比避免反馈要复杂得多,但反馈肯定会成为人工智能的主要绊脚石。这种方法应该有助于我们到达那里:通过阻止人工智能听自己的话。
此外,虽然这种方法讨论的是一个器件的输出通过 ANC 滤波器以数字方式馈入,但没有理由将我们的思维局限于同一器件的反馈。该助理可能从听力范围内的任何设备接收音频馈送。比方说,我们不想让 Chromecast 或智能电视的音频干扰我们的助理,您可以设置这些设备以数字方式将音频馈送发送给助理,以便它过滤掉不相关或不需要的信号。与上面提到的问题类似,如果缺少声音反馈,设备在其环境中进行自我校准将是至关重要的。这可以通过每当一个人启动时,它播放一个快速校准音来实现,然后在听力范围内的助理用它来设置 ANC 滤波器的适当水平。这将解决亚马逊试图利用 Alexa 指纹数据库的问题,只需让电视告诉助手忽略来自它的所有音频,包括任何潜在的触发因素。
为什么要提出这个建议
以我对这个想法的详细程度和我对类似应用的研究,我可能会成功申请专利。但是我相信分享好的想法,据我所知,这个想法从来没有被分享过,尽管它对我来说似乎非常明显。另外,这应该算作的现有技术来阻止任何人申请将内部 ANC 应用于语音助手的专利。我没有足够的资源来实现我的每一个想法,所以为什么不把它们放在那里,看看其他人是否能从中受益?如果你最终使用了这个想法,或者你想在某件事情上合作,我很乐意听到你的意见。
如果任何 ANC、HCI(语音)、对话式 AI 或任何其他相关领域的专家正在阅读,并且希望更深入地探索这一点,请告诉我。我愿意进一步研究或测试它。也许我错过了一些关键因素或已经提出这种方法的工作。如果是这样,我真的很想了解一下。
另外,我有一个用超声波频率防止人工智能意外触发的想法。如果有人有兴趣了解更多,请告诉我,我很乐意进一步发展这个想法。
我期待听到任何人对这篇文章或讨论的话题的反馈或问题,无论是在这里还是在社交媒体上。随时联系我(只要告诉我你看到这篇文章了)→
【twitter.com/theNathanielW
linkedin.com/in/theNathanielWatkins
计算掷骰子的期望值的蒙特卡罗实验
在看到所有 6 个数字之前,掷骰子的期望值是多少?数学和程序上的解释。
Monte Carlo, Wikipedia Commons by MartinP.
这个问题是很多面试中的常见问题,下面是一个使用随机抽样解决这个问题的程序化方法。这个想法是,运行许多实验,在这些实验中,你滚动骰子(均匀分布的样本),直到你看到所有 6 个数字都出现在一个正常和公平的骰子中。最后,你计算期望值。
数学
再说几率,第一次掷出一个数字的几率是 1,看到第二个数字的几率是 5/6 等等。
我们可以用这个公式来概括:
为了解决这个问题并且由于结果是几何分布。一个数字出现的概率是 1/p,这意味着从上面的公式中计算出的每个数字都应该放在而不是“p”作为分母。
最后,我们计算掷骰子直到看到每个数字所需的平均时间,即 14.7。
代码
我们可以使用几行代码,通过运行蒙特卡罗实验,即 10K 随机实验,从 1 到 6 之间的均匀分布中抽取数字,并计算在每个实验中我们掷出所有 6 个数字的次数。所有这些实验的平均值就是期望值。大约是 14.7,你可以改变滚动的次数,看看它在这个模拟环境中的效果。
我要感谢我的同事 Shay Yehezkel。
Ori Cohen 博士拥有计算机科学博士学位,专注于机器学习。他在 Zencity.io 领导研究团队,试图积极影响市民生活。
带有 BERT、Transformer-XL 和 Seq2Seq 的多任务音乐模型
Buzzwordy clickbait 标题本意,但仍然是一个简单的概念。
这是“构建人工智能音乐生成器”系列的第三部分。我将讲述音乐模型多任务训练的基础知识——我们将用它来做一些非常酷的事情,如和声、旋律生成和歌曲混音。我们将在第一部和第二部的基础上展开。
背景
如你所知,变形金刚最近革新了自然语言处理领域。您可能也知道,有几种不同的变压器变体。它们都有相同的基本注意层,但有些是专门用于不同的任务。这里有 3 个最酷的:
Seq2Seq (序列到序列翻译)——使用编码器-解码器架构在语言之间进行翻译。这就是开启革命的 OG 变形金刚。
transformer XL—这款正向解码器是一款令人惊叹的文本生成器。记忆和相对位置编码使超快速和准确的预测成为可能。我们在第二部分中使用了这个模型。
BERT—这个双向编码器在回答问题和填空时产生 SOTA 结果。令牌屏蔽和双向允许特殊的上下文。
该死,所有这些变化都太酷了!与其选择一个,为什么不把它们结合起来呢?
等待…什么?!?
请稍等片刻。
多任务模型来统治他们
如果你正在训练一个语言模型,那么结合这三个模型是没有意义的。
TransformerXL 非常适合生成文本。Seq2Seq 非常适合语言翻译。伯特擅长填空。你没有理由想同时做这三件事。
对于音乐一代来说,这是一个不同的故事。
让我们来看几个你在考虑作曲时的场景:
任务 1。我有几个音符想做成一首 的歌
TransformerXL 非常擅长序列生成。让我们用它来自动完成你的歌曲想法。
任务 2a。我的旋律需要一些和声。
任务 2b。我有一个和弦进行。现在我需要一个钩子。
S eq2Seq 非常适合翻译任务。我们可以用这个把旋律翻译成和弦。反之亦然。
**任务 3a。**我现在有了一首歌,但听起来有些不对劲。
任务 3b。这首歌需要更好的节奏。
伯特很擅长填空。我们可以删除歌曲的某些部分,用伯特来产生一个新的变奏。
如你所见,每一种变形都有助于产生歌曲创意。我们要训练一个能解决所有这些任务的模型。
先玩,后理解
为了理解我们在这里试图做什么,尝试一下我们试图创建的多任务模型可能会有所帮助。
每个演示都是为了解决特定的任务而生成的。在预测值(红色音符)和原始值(绿色音符)之间切换,听出不同之处。
任务 1。宋一代
《典藏 D》作者帕赫尔贝尔
任务 2a 。和谐的旋律
《爱在哪里》黑眼豆豆
任务 2b 。现有和弦进行的新旋律
【史奇雷克斯的《可怕的怪物和可爱的小精灵》
任务 3a。同节拍,异曲
《关卡》由中航工业
**任务 3b。**同一首歌,混音节拍
贝多芬的《富尔伊利斯》
建造怪物
你回来了。现在让我们来建造这个东西。
一开始听起来可能有点令人畏惧,但是说实话并不复杂。我们的多任务模型本质上是 Seq2Seq 架构。我们所做的只是修改它来训练不同的任务。
我假设你已经知道 Seq2Seq 翻译是如何工作的。如果没有,请访问这个不可思议的 变形金刚插图 。
Seq2Seq
好了,现在让我们想象一下 Seq2Seq 变压器如何为音乐工作。
非常类似于你如何翻译一种语言。和弦(输入语言)被翻译成旋律(目标语言)。
“先前的输出”被反馈到解码器,所以它知道什么已经被翻译。
编码器与解码器
该模型被分成蓝色编码器块和红色编码器块。了解这两者之间的区别很重要,因为我们将在其他任务中重新使用它们。
蓝色编码块 是 单双向注意层。他们能够看到以前和将来的令牌。
红色解码块为双层叠前向注意层。双堆叠块使用编码器输出和“先前输出”作为上下文来预测旋律。前向图层无法看到未来的令牌,只能看到之前的令牌。
正如你可能已经猜到的,蓝色双向编码器是训练 BERT 模型的完美匹配。同样,前向解码器层可以重新用于训练 TransformerXL 任务。
让我们来看看。
伯特
擦掉一些笔记,伯特会填空:
Masked notes prevent bidirectional layers from cheating
我们训练编码器预测正确的音符(红色),只要它看到一个屏蔽的令牌(蓝色)。
变压器 XL —下一个字
你会从以前的文章中认出这个图表。
Next tokens are attention masked to prevent cheating
我们训练解码器通过将目标移动一位来预测下一个令牌。
综合考虑
如您所见,Seq2Seq 型号是 BERT 编码器和 TransformerXL 解码器的组合。这意味着我们可以重用 Seq2Seq 模型中的编码器和解码器来训练 BERT 和 TransformerXL 任务。唯一改变的是输入和目标。
这里提醒一下我们之前的三项任务:
**任务 1。**使用 TransformerXL 生成音乐
**任务 2a/2b。**使用 Seq2Seq 进行旋律到和弦/和弦到旋律的翻译
**任务 3a/3b。**宋与伯特的混音
解决多任务#2。
前面你看到了如何进行旋律到和弦的翻译(2a。).和弦到旋律的任务(2b。)是完全相同的,但是输入和目标被翻转。
多任务#1 和#3
由于 BERT 仅使用编码器层,而 TransformerXL 仅使用解码器层,因此可以同时训练任务#1 和任务#3。在 BERT 端,我们屏蔽输入并通过编码器发送。同时,我们将移位后的输入馈入解码器以训练 TransformerXL。
看起来是这样的:
- 编码器接受屏蔽任务的训练。
- 单独且并行地,解码器在下一个令牌任务上被训练。
注意,解码器只有一个箭头作为输入。对于此任务,它不使用编码器输出。
瞧啊。这是我们的模型代码
希望这个模型对使用过 PyTorch 的人来说非常清楚。
Model architecture:
Encoder - Bi-directional attention layers
Decoder - Uni-directional double-stacked attention layers
Head - Dense layer for token decodingForward Prop:
1\. If the input is masked ('msk'), train the encoder.
2\. If the input is shifted ('lm'), train the decoder.
3\. If the input contains both translation input ('enc') and previous tokens ('dec'), use both encoder and decoder.
运行此笔记本进行实际训练。
这就是训练多任务模型的全部内容。
它实际上只是一个编码器/解码器模型,针对各种类型的输入和输出进行训练。
屏蔽令牌训练编码器(BERT)。移位令牌训练解码器(TransformerXL)。成对序列训练两者(Seq2Seq)。
关于结果
如果你使用了本文开头的例子,那么你已经看到了结果。音乐机器人网络应用由多任务转换器驱动。与其听我喋喋不休地谈论结果,不如回去自己创造结果,你会更开心!
要启用任务 1、2a、2b、3a、3b ,只需拨动此开关:
备选计划yg ground
如果你觉得需要更多的动手操作,这个 python 笔记本用代码生成了所有相同的例子。通过浏览笔记本,你会更好地理解预测是如何工作的。
将结果更进一步
呜哇!我们可以生成一些很酷的结果,但是…它似乎缺少了一些流行音乐的魔力。在我们的下一篇文章中,我们将揭开一些神奇的酱料。
第四部分。使用一个音乐机器人来重新混合烟鬼——这是我最喜欢的最后一篇文章了!
谢谢你对我的包容!
使用 Spotify API 和 Python 进行音乐品味分析。
探索音频特征并构建机器学习方法
几天前,我读了一篇非常有趣的文章,作者是我的一个朋友(鲁兹),他使用 API(应用编程接口)分析了他的 Spotify 播放列表。读完之后,我打开应用程序,看了看我的播放列表,意识到我一周甚至一整天都在听不同风格的音乐。我可能一分钟前还在听桑巴,过一会儿又开始听重金属。想到这一点,我决定不仅分析我的音乐品味,也分析我未婚妻的音乐品味,以弄清楚数据对此有何说明。
因此,为了做到这一点,我首先使用 Spotify 的 API 提取了我们在 Spotify 上听得最多的 10 位艺术家的音频特征,以便我能够进行音乐品味分析。除此之外,我训练了一个机器学习模型来预测一首歌更有可能属于的每个列表。
另一个值得注意的事实是,我有一些来自数据科学做零的参考资料,这是巴西一个著名的数据科学社区。
你可以在我的Github页面 上查看我开发的所有代码
工具:
- Spotify 库获取 Spotify 平台音乐数据
- Seaborn 和 matplotlib 实现数据可视化
- Pandas 和 numpy 实现数据分析
- Sklearn 构建机器学习模型
Spotify 音频功能
众所周知,Spotify 是全球最受欢迎的音频流媒体平台之一。同样,Twitter、Slack 和脸书也为开发者提供了一个 API 来探索他们的音乐数据库,并深入了解我们的收听习惯。
基于此,我选择了十位我日常经常听的艺术家,并让我的未婚妻给我一张她最喜欢的艺术家的名单。有了艺术家列表后,我使用了稀有战利品帖子作为参考来获取音频特征数据,以便进行分析。
获得的第一个数据集包含 16 列,其中 1433 首歌曲来自我的列表,691 首来自 Emily 的 lit。因此,为了平衡歌曲的数量,我构建了一个函数,从我的列表中随机删除一些行。在删除过程之后,我们总共找到了 1394 首歌曲(703 首来自我的列表,691 首来自 Emily 的列表)。
值得一提的是,我并没有使用所有的 16 列进行分析。相反,我只选择了那些与音频特性相关的栏目。您可以在下面找到每个功能的解释(过去/复制自 Spotify 网站)。
- 声音:一种置信度,从 0.0 到 1.0,表示音轨是否是声音的。1.0 表示音轨是声学的高置信度。
- 可跳舞性:可跳舞性描述了一个曲目在音乐元素组合的基础上适合跳舞的程度,包括速度、节奏稳定性、节拍强度和整体规律性。值 0.0 最不适合跳舞,1.0 最适合跳舞。
- 能量:能量是一个从 0.0 到 1.0 的度量,代表强度和活动的感知度量。通常,高能轨道感觉起来很快,很响,很嘈杂。例如,死亡金属具有高能量,而巴赫前奏曲在音阶上得分较低。对该属性有贡献的感知特征包括动态范围、感知响度、音色、开始速率和一般熵。
- 乐器性:预测音轨是否不包含人声。“Ooh”和“aah”在这种情况下被视为乐器。Rap 或口语词轨道明显是“有声的”。乐器度值越接近 1.0,轨道不包含人声内容的可能性就越大。高于 0.5 的值旨在表示乐器轨道,但随着该值接近 1.0,置信度会更高。
- 活跃度:检测录音中是否有观众。较高的活跃度值表示音轨被现场执行的概率增加。高于 0.8 的值很有可能表示该音轨是实时的。
- 响度:轨道的整体响度,以分贝(dB)为单位。响度值是整个轨道的平均值,可用于比较轨道的相对响度。响度是声音的质量,是与体力(振幅)相关的主要心理因素。值的典型范围在-60 和 0 db 之间。
- 语速:语速检测音轨中是否存在口语单词。越是类似语音的录音(例如脱口秀、有声读物、诗歌),属性值就越接近 1.0。高于 0.66 的值描述可能完全由口语单词组成的轨道。介于 0.33 和 0.66 之间的值描述可能包含音乐和语音的轨道,可以是分段的,也可以是分层的,包括说唱音乐。低于 0.33 的值很可能代表音乐和其他非语音类轨道。
- 配价:从 0.0 到 1.0 的一种量度,描述音轨所传达的音乐积极性。高价曲目听起来更积极(例如,快乐、愉快、欣快),而低价曲目听起来更消极(例如,悲伤、沮丧、愤怒)。
- 速度:轨道的整体估计速度,单位为每分钟节拍数(BPM)。在音乐术语中,速度是给定作品的速度或节奏,直接来源于平均节拍持续时间。
Features
数据分析
在我决定做这篇文章之前,我从来没有真正考虑过我的音乐品味。事实上,在思考这个问题后,我意识到我没有一个每天都听的特定类型的音乐。其实那要看我的心情了…有些天我醒来听 pagode(来自里约热内卢的巴西风格音乐),有些天起床后只想听一首真正有活力的歌比如铁娘子,等等。所以,我很好奇不仅调查我的,也调查 Emily 的 Spotify 音频功能。也就是说,让我们直接进入分析。我将首先绘制一个柱状图和一个雷达图,显示我们的特征,以便对它们进行比较。
情节表明,在我的艺术家名单中,最突出的特征是活力——可能是因为 ACDC、铁娘子和摩托头乐队等摇滚乐。另一方面,观察 Emily 的特征,我们可以注意到可跳舞性和能量是她列表中普遍的音频属性。这真的很有道理,因为她喜欢听 Ludmila(巴西放克歌手)和 Rihanna 等艺术家的歌曲。
速度是音乐分析的一个重要特征。它可以像旋律、和声或节奏一样重要,因为它代表了歌曲的速度和它唤起的情绪。例如,一首歌的 BPM 越高,这首歌的速度就越快,因此也就越令人振奋和快乐。另一方面,低 BPM 意味着歌曲较慢,这可能表明悲伤、浪漫或戏剧。
查看下图,我们可以看到我们的列表平均速度接近 120 BPM,这表明速度适中/较快。基于这一点,我认为这两种类型的赛道对那些喜欢快走和跑步的人都有兴趣,因为一首 120 BPM 的歌曲通常可以与一个人的步幅模式和他们的步伐同步,以使跑/走更难和更快。
为了比较音频特性,请务必查看下面的条形图。它显示了我的和 Emily 的歌单的每个属性的平均值之间的差异。
与此一致,我们可以清楚地看到,我的列表比她的更有活力,更生动,更具工具性。此外,我们还可以注意到在我们的语音变量上的微小差异,并且看到化合价、和声音在她这边占据首位。
名单有多多样化?!
可以通过检查我们歌曲流派的差异来调查列表的多样性。如果大多数歌曲属于同一流派,我们会说它们变化不大。
问题是:我们如何分析这个问题?嗯,答案很简单:让我们检查每个音频变量的标准偏差,并对它们进行检查。
虽然音频特征本身的标准偏差不能给我们提供太多信息(正如我们在下面的图中看到的),但我们可以将它们相加,并计算列表标准偏差的平均值。通过这样做,我们得到了“各种音频特性”图中表示的值,在我的列表中值为 0.22,在 Emily 的列表中值为 0.20。
我们该如何解释呢?!好吧,让我们说,我们可以有一些歌曲的某个特定特征的值很高,比如能量,而其他歌曲的相同属性的值很低。简而言之,这些数字表明我的歌曲比艾米丽的更多样化。
Variety of Audio Features
Standard Deviation of the Audio Features
变量之间的相关性
我们还可以构建相关图,如散点图,来显示变量之间的关系。在我们的例子中,我们将把描述音乐积极性的特征效价与可舞性和能量联系起来。
为了解释下面的情节,我们必须记住数字一(绿点)和数字零(灰点)分别代表我和艾米丽的歌曲。也就是说,让我们检查散点图。
化合价和能量
配价和能量之间的相关性表明,有一个高能量和低水平的配价歌曲聚集。这意味着我的许多充满活力的歌曲听起来更消极,带有悲伤、愤怒和抑郁的感觉(NF 在这里占据特殊位置哈哈)。然而,当我们看灰色的点时,我们可以看到,随着积极情绪水平的增加,歌曲的能量也增加了。虽然她的数据是分裂的,但我们可以识别这种模式,表明变量之间存在某种“线性”相关性。
配价和可舞性
现在,看看化合价和可跳舞性之间的关系,我们可以看到 Emily 的歌曲在情节的第一和第二象限中具有高的可跳舞性值。另一方面,我的歌曲大多要么在第三象限,要么在第一象限,就这两个特征而言,表现出一种多样性。
机器学习方法
好了,我们从数据中得到了一些启示。为了使这篇文章简短,让我们直接进入最好的部分(至少对我来说),那就是机器学习(ML)算法。
重要的是要记住,我并不是 100%关注模型的准确性。此外,我们将使用支持向量机(SVM ),因为它是我目前正在研究的。此外,我不会解释 SVM 是如何工作的。相反,让我们说它是一种监督的 ML 方法,用于分类和回归问题。此外,它的主要任务(分类)是通过定义一个区分它们的最佳超平面来分离所有的类。也就是说,让我们建立一个算法来预测一首歌更有可能属于我的列表还是 Emily 的列表。
移除功能
第一步是预处理我们的数据集,以便得到一个在所有列中都有数值的数据帧。因此,让我们首先删除所有与我们的模型不相关的特征,如 id、专辑、名称、URI、流行度和 track_number,并将目标(who 列)与我们的数据框架分开。我们可以通过构建函数 features_removal 轻松地做到这一点,该函数接收一个列表,其中包含我们希望作为参数删除的特性。
请注意,在删除它之后,我们仍然有一个分类特征(艺术家)。所以,我们必须在第二步处理这个问题。另外,重要的是要提到我们有两个稍微平衡的类(who 列),这两个类表示这首歌属于谁的列表。简而言之,我的列表中有 703 首歌曲,她的列表中有 691 首,这种数量上的平衡对我们的模型很重要。
标签编码器
第二个任务是将所有分类数据(艺术家姓名)转换成数字数据。为何如此?ML 算法只接受数字数据,因此,我们必须使用 LabelEncoder 类将每个艺术家的名字编码成一个特定的数字。编码过程如下所示。
#Set Labels
artist_label = enc.fit_transform(df_couple.artist)#Create column containing the labels
df_couple['labels_artists'] = artist_label#Remove colum artist (cathegorical data)
features_removal(['artist'])
管道
现在我们的数据集几乎准备好了,我们可以建立我们的模型。你可能想知道:准备好了吗?!当我们处理一些最大似然算法时,我们必须记住,其中一些算法需要特定范围或结构的数据。例如,有时我们不得不将数据转换到一个更高的维度,以便找到分类的界限,如下图所示。为了实现这一点,我们使用了一种叫做内核技巧的技术,这种技术提供了一种高效且成本较低的方法来实现这种映射。此外,它使用不同的函数(核),其中最流行的是多项式,线性和径向基函数(RBF)。例如,最后一种方法需要以 0 为中心的特征,并且具有相同数量级的方差,这就是我们必须预处理数据的原因。
Kernel Trick
Radial Basis Function (RBF)
# Import libraries
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import MinMaxScaler
from sklearn.preprocessing import Normalizer# Cross-validation and accuracy of the model
def accuracy(clf, data, target):
results = cross_val_predict(clf, data ,target ,cv = 10)
return metrics.accuracy_score(target,results)
管道:
# Pipelines
pip_1 = Pipeline([('scaler',StandardScaler()),
('clf', svm.SVC())
])pip_2 = Pipeline([('scaler',StandardScaler()),
('clf', svm.SVC(kernel = 'linear'))
])
pip_3 = Pipeline([('scaler', StandardScaler()),
('clf', svm.SVC(kernel = 'poly'))
])pip_4 = Pipeline([('min_max_scaler', MinMaxScaler()),
('clf', svm.SVC())
])pip_5 = Pipeline([('min_max_scaler', MinMaxScaler()),
('clf', svm.SVC(kernel= 'linear'))
])pip_6 = Pipeline([('min_max_scaler', MinMaxScaler()),
('clf', svm.SVC(kernel='poly'))
])pip_7 = Pipeline([('Normalize', Normalizer()),
('clf', svm.SVC() )
])pip_8 = Pipeline([('Normalize', Normalizer()),
('clf', svm.SVC(kernel='linear') )
])pip_9 = Pipeline([('Normalize', Normalizer()),
('clf', svm.SVC(kernel='poly') )
])
带有管道的列表:
# list with pipelines
pip_list= []
pip_list.append(('Scaler_rbf', pip_1))
pip_list.append(('Scaler_linear', pip_2))
pip_list.append(('Scaler_poly', pip_3))pip_list.append(('Min_max_scaler', pip_4))
pip_list.append(('Min_max_scaler_linear', pip_5))
pip_list.append(('Min_max_scaler_poly', pip_6))pip_list.append(('Normalizer', pip_7))
pip_list.append(('Normalizer_linear', pip_8))
pip_list.append(('Normalizer_poly', pip_9))# Function to build models
def model_accuracy(clf_models, data, target):
results = []
names = []
for name, pip in clf_models:
cross_val = cross_val_predict(pip, data, target, cv = 10)
accuracy = metrics.accuracy_score(target, cross_val)
print(f'name: {name} - Accuracy : {accuracy:.4f}')
把酒装入大酒桶
具有 RBF 核和标准缩放器预处理的模型给了我们最高的精度,但是我们可以通过调整它的超参数来改进它。让我们使用 GridSearchCV 详尽地组合 C 和 Gamma 的不同值,并在我们的算法中测试它们。
# Import GridSearchCV
from sklearn.model_selection import GridSearchCV
# Values for parameter CC_list = [0.001 , 0.01, 0.1 , 1 , 10 , 100]#Values for parameter Gamma
Gamma_list = [0.001, 0.01 , 0.1, 1, 10, 100]
# Dictionary with lists of parameters
Grid_parameters = dict(clf__C = C_list, clf__gamma = Gamma_list)
Grid_parameters
# Grid object with pip_1, Grid_parameters, numbers of folders (cv) and accuracy (scoring) as parameters
grid = GridSearchCV(pip_1,Grid_parameters,cv = 10, scoring = 'accuracy')
#Apply gridsearch to our data and target
grid.fit(df_couple,target)
结论
在这篇文章中,我使用 Spotify API 分析了我未婚妻和我的音乐列表的音频特征。我没有检查我们的播放列表,而是使用了我们在日常生活中听得最多的 10 位艺术家的特征。基于此,我得出结论,我的列表比她的更多样化,令人惊讶的是,我的歌曲更有活力。另一个见解(我预料到了:])是艾米丽的歌曲更适合跳舞。为了补充分析,我还建立了一个机器学习模型(SVM ),来预测一首歌是否更有可能被她或我听到。该模型的准确率约为 74%,这是很好的(尽管还可以改进)。
参考资料:
- https://medium . com/mundo-ia/anáLise-de-dados-de-uma-playlist-do-Spotify-com-python-e-power-bi-BC 848 aa 0880 c
- https://medium . com/@ rare loot/extracting-Spotify-data-on-your-favorite-artist-via-python-d 58 BC 92 a 4330
- https://towards data science . com/is-my-Spotify-music-boring-an-analysis-containing-music-data-and-machine-learning-47550 AE 931 de
- https://datasciencedozero.landen.co/
Python 中的 Apache Spark 尼安德特人指南
完全初学者的 PySpark 入门教程
你听说过 Apache Spark 吗
如果您和我一样,您听说了一种听起来很不错的技术,叫做 Spark,并且想要测试您的编码能力,看看您是否可以在您的数据科学工具包中添加另一种工具。希望你不要和我一样,因为在我的情况下,我很快碰到了安装墙,然后是术语墙,然后是概念墙,四个小时后,我还没有写出一行代码。所以,在花了几个小时在网上搜索,以及比我想提及的更多的所谓“初学者指南”之后,我决定写一篇“尼安德特人指南”,希望能让你免去一些我忍受的麻烦。
为什么要阅读本指南?
甚至在 Spark 上快速搜索学习资料也会让你沉浸在文档、在线课程(其中许多都不便宜)和其他资源中。根据我的经验,大多数人要么假设我对分布式计算了解太多(比如假设我知道分布式计算意味着什么),要么他们提供了高级或背景信息,但没有帮助我理解如何在 Spark 中实际实现任何东西。
考虑到这一点,在本指南中,我试图尽我所能解释一个概念,或者用一个解释把你引向其他地方,所有的目标都是让你尽可能快地编写 Spark 代码。因为我尽可能多地尝试与 Spark 相关的主题,所以如果您已经很好地掌握了某个特定的主题,请随意跳转。我还会试着给你留下一些链接,这些链接是我在钻研 Spark 时发现的有用资源。
这是指南的结构:我首先解释一些关键的术语和概念,这样我们就可以对其余的材料保持一致,同时也降低了进入 Spark 上的外部资源的门槛,您可以在这里和其他地方找到这些资源。接下来,我将使用 Google Colab 在您的机器上运行 Spark 的工作版本。最后,我将通过一个用例来演示 PySpark 是如何实际实现的,以及第一次通过示例问题是什么样子的。
你会学到什么
- 足够的术语和概念,能够阅读其他 Spark 资源,而不会永远感到困惑
- 让 PySpark 在您的计算机上运行的相对简单的方法
- 如何开始使用 PySpark 进行数据探索
- 在 PySpark 中构建和评估基本线性回归模型
- 此处涵盖的大部分材料的有用外部资源
关键术语和概念
这里有一个各种术语和概念的列表,了解这些术语和概念将有助于您深入 Spark 的世界。
什么是火花
如果你谷歌过“什么是 Spark”,很有可能你遇到了下面的描述,或者类似的描述:*“Spark 是一个通用的分布式数据处理引擎”。*如果没有 Spark 的背景或者不熟悉这些术语的含义,这个定义是没有用的。让我们来分解一下:
- 分布式数据/分布式计算 — Apache Spark 在一个与普通计算机科学略有不同的世界中运行。当数据集变得太大,或者新数据进入得太快时,单台计算机可能无法处理。这就是分布式计算的用武之地。这些任务可以在相互通信的多台计算机之间分配,以产生一个输出,而不是试图在一台计算机上处理一个巨大的数据集或运行计算量非常大的程序。这种技术有一些重要的好处,但是在多台计算机之间分配处理任务有其自身的挑战,并且不能以与正常处理相同的方式构建。当 Spark 说它与分布式数据有关时,这意味着它被设计来处理非常大的数据集,并在分布式计算系统上处理它们。
注意:在分布式计算系统中,每台单独的计算机被称为一个节点,所有这些计算机的集合被称为一个集群
(Comic from xkcd)
进一步阅读 — 分布式计算简介 (8 分钟阅读)
- 处理引擎/处理框架 —处理引擎,有时称为处理框架,负责执行数据处理任务(一个启发性的解释,我知道)。比较可能是理解这一点的最佳方式。Apache Hadoop 是一个开源软件平台,也处理“大数据”和分布式计算。Hadoop 有一个不同于 Spark 的处理引擎,叫做 MapReduce。MapReduce 有自己独特的方法来优化在多个节点上处理的任务,而 Spark 有不同的方法。Sparks 的优势之一是它是一个处理引擎,可以单独使用,也可以代替 Hadoop MapReduce 使用,充分利用 Hadoop 的其他特性。
(Image from Brad Anderson)
进一步阅读 — 解释和比较的处理引擎(约 10 分钟阅读)
- 通用—Spark 的主要优势之一是它的灵活性,以及它有多少应用领域。它支持 Scala、Python、Java、R 和 SQL。它有一个专用的 SQL 模块,能够实时处理流数据,并且它有一个机器学习库和基于它的图形计算引擎。所有这些原因促使 Spark 成为大数据领域最受欢迎的处理引擎之一。
Spark Functionality (from Databricks.com)
延伸阅读 — 理解火花意义的 5 分钟指南(大概更像是~10 分钟阅读)
分布式计算术语
- 分区数据(Partitioned Data)——当使用计算机集群时,你不能只是扔进一个普通的数据帧,然后期望它知道该做什么。因为处理任务将跨多个节点划分,所以数据也必须能够跨多个节点划分。分区数据是指经过优化能够在多个节点上处理的数据。
进一步阅读 — 数据分区说明 (2 分钟阅读)
- 容错 —简而言之,容错指的是分布式系统即使在出现故障时也能继续正常工作的能力。例如,故障可能是一个节点突然起火,或者只是节点之间的通信中断。Spark 中的容错围绕着 Spark 的 RDDs(后面会讨论)。基本上,Spark 中处理数据存储的方式允许 Spark 程序在发生故障的情况下正常运行。
进一步阅读 — 火花容错能力如何 (~1 分钟阅读)
- 懒惰评估 —懒惰评估,或懒惰计算,与代码如何编译有关。当一个不懒惰的编译器(称为严格求值)编译代码时,它会依次对遇到的每个表达式求值。另一方面,一个懒惰的编译器不会不断地计算表达式,而是等待,直到它真正被告知生成一个结果,然后一次执行所有的计算。因此,当它编译代码时,它会跟踪它最终必须评估的所有内容(在 Spark 中,这种评估日志可以说被称为谱系图),然后每当它被提示返回某些内容时,它都会根据其评估日志中的内容执行评估。这是有用的,因为它使程序更有效,因为编译器不必评估任何实际上没有使用的东西。
延伸阅读——什么是懒评 (4 分钟阅读)
火花术语
- 关系数据库、数据框架、数据集,天哪!—Spark rdd(弹性分布式数据集)是数据结构,是 Spark 的核心构建块。RDD 是一个不可变的、分区的记录集合,这意味着它可以保存值、元组或其他对象,这些记录被分区以便在分布式系统上处理,并且一旦创建了 RDD,就不可能对其进行更改。这基本上概括了它的缩写:由于它们的不变性和谱系图(稍后将讨论),它们是有弹性的,由于它们的分区,它们可以是分布式的,并且它们是数据集,因为它们保存数据。
需要注意的一个关键点是,rdd*没有模式,这意味着它们没有列结构。记录只是逐行记录,显示类似于列表。进入火花数据帧。不要与 Pandas 数据帧混淆,因为它们是不同的,Spark 数据帧具有 rdd 的所有特性,但也有一个模式。这将使它们成为我们开始使用 PySpark 的首选数据结构。
Spark 有另一种数据结构, Spark 数据集。这些类似于 DataFrames,但是强类型的,*意味着类型是在创建数据集时指定的,而不是从存储在其中的记录类型中推断出来的。这意味着 PySpark 中不使用数据集,因为 Python 是一种动态类型语言。对于这些解释的其余部分,我将参考 RDD,但要知道,对于 RDD 来说是正确的,对于数据帧来说也是正确的,数据帧只是被组织成一个列结构。
进一步阅读 — RDDs、数据帧、&数据集对比(约 5 分钟阅读)
进一步阅读 — Pandas v. Spark 数据帧 (4 分钟阅读)
进一步阅读 — 有用的 RDD 文档(约 5 分钟阅读)
- 变换 —变换是你在 Spark 中可以对 RDD 做的事情之一。它们是创建一个或多个新 rdd 的懒惰操作。值得注意的是,转换会创建新的rdd,因为,记住,rdd 是不可变的,所以一旦它们被创建,就不能以任何方式被更改。因此,本质上,转换将 RDD 作为输入,并根据被调用的转换对其执行一些功能,并输出一个或多个 rdd。回想一下关于惰性求值的部分,当编译器遇到每个转换时,它实际上并不构建任何新的 rdd,而是构建一个由这些转换产生的假设 rdd 链,这些转换只有在调用了动作后才会被求值。这个假设的链,或者“子”RDD,都逻辑地连接回原始的“父”RDD,就是谱系图。
进一步阅读 — 有用的转型文档(约 2 分钟阅读)进一步阅读 — 更深入的文档(5-10 分钟阅读;上半年的转变)
- 动作 —动作是不产生 RDD 作为输出的任何 RDD 操作。一些常见操作的例子是对数据进行计数,或者查找最大值或最小值,或者返回 RDD 的第一个元素,等等。如前所述,动作提示编译器评估谱系图并返回动作指定的值。
延伸阅读 — 有帮助的行动文档 (~1 分钟阅读)
延伸阅读 — 更深入的文档 (~5 分钟阅读;下半年的行动)
- 沿袭图 —沿袭图的大部分内容在转换和操作部分都有描述,但概括来说,沿袭图概括了所谓的“逻辑执行计划”。这意味着编译器从不依赖于任何其他 RDD 的最早的 rdd 开始,并遵循一个逻辑转换链,直到它以一个动作被调用的 RDD 结束。这个特性是 Spark 容错的主要驱动力。如果某个节点由于某种原因失败了,所有关于该节点应该做什么的信息都存储在谱系图中,可以在其他地方复制。
Visualization of example lineage graph; r00, r01 are parent RDDs, r20 is final RDD (from Jacek Laskowski )
延伸阅读——有用的血统文件(大约 2 分钟阅读)
- Spark 应用和作业——当谈到像 Spark 这样的处理引擎如何在分布式系统上实际执行处理任务时,有很多细节。为了对某些 Spark 代码片段的作用有一个工作上的理解,下面是你需要知道的。在 Spark 中,当一个处理项目必须完成时,有一个“驱动”进程负责获取用户的代码并将其转换为一组多任务。还有“执行者”进程,每个进程在集群中的一个单独节点上运行,由驱动程序委派负责运行任务。为了运行任务,每个驱动程序进程都有一组它可以访问的执行器。Spark 应用是一个用户构建的程序,由一个驱动程序和该驱动程序的相关执行器组成。Spark job 是一个或一组由执行器进程执行的任务,由驱动程序指导。作业由 RDD 操作的调用触发。这种东西可能相当令人困惑,所以如果一开始不太明白,也不要担心,熟悉这些术语有助于以后在代码中实现它们。如果你想了解更多信息,我已经在这个主题上包含了额外的资源。
Visualization of Spark Architecture (from Spark API)
延伸阅读 — 集群模式概述来自 Spark API (~3 分钟阅读)
延伸阅读—stack overflow 上的有用回答 (~2 分钟阅读)
延伸阅读—cloud era 上的 Spark 应用概述(~ 2 分钟阅读)
唷,你已经理解了所有的术语和概念!现在让我们开始实施吧!
安装 Spark
这个标题可能有点用词不当,因为严格来说,本指南不会向您展示如何安装 Apache Spark。安装 Spark 可能是一件痛苦的事情。首先,可以用多种语言编写 Spark 应用程序,每种语言的安装都略有不同。Spark 的底层 API 是用 Scala 编写的,但是 PySpark 是用 Python 实现的上层 API。对于数据科学应用程序,使用 PySpark 和 Python 比 Scala 更受推荐,因为它相对更容易实现。因此,本指南将向您展示如何在 Google Colab 中运行 PySpark,而不是安装它。
Google Colab
当我试图让 PySpark 在我的电脑上运行时,我不断收到关于从哪里下载它(例如,它可以从spark.apache.org或 pip installed 下载)、在什么地方运行它(它可以在 Jupyter 笔记本中运行或者在命令行中的本机 pyspark shell 中运行)的相互矛盾的指令,并且有许多模糊的 bash 命令散布在各处。作为一名数据分析师,我对非 pip 安装的 bash 命令的反应通常是厌恶和绝望的混合,所以我求助于 Google Colab。
Google Colab 是一个非常强大的交互式 python 笔记本(。ipynb)工具,它预装了许多数据科学库。关于它是什么以及如何运行它的更多信息,请查看这篇超级有用的文章 (8 分钟阅读)。
一旦你有了一个 Colab 笔记本,要让 Spark 运行,你必须运行下面的代码块(我知道这不是我的错,但我为它的丑陋道歉)。
!apt-get install openjdk-8-jdk-headless -qq > /dev/null
!wget -q [https://www-us.apache.org/dist/spark/spark-2.4.3/spark-2.4.3-bin-hadoop2.7.tgz](https://www-us.apache.org/dist/spark/spark-2.4.3/spark-2.4.3-bin-hadoop2.7.tgz)
!tar xf spark-2.4.3-bin-hadoop2.7.tgz
!pip install -q findspark
import os
os.environ["JAVA_HOME"] = "/usr/lib/jvm/java-8-openjdk-amd64"
os.environ["SPARK_HOME"] = "/content/spark-2.4.3-bin-hadoop2.7"
import findspark
findspark.init()
注意:当我第一次运行这段代码时,它没有运行。这是因为在我找到的代码编写完成后,已经发布了一个新版本的 Spark,而我试图访问一个找不到的旧版本的 Spark。因此,如果上面的代码不运行,仔细检查这个网站看看 Spark 的最新版本是什么,并把上面代码片段中你看到的“2.4.3”替换成任何最新版本。
基本上这段代码所做的就是下载正确版本的 Java (Spark 使用一些 Java)和 Spark,设置这些版本的路径,并在笔记本中初始化 Spark。
如果你想在除了 Colab 之外的另一个平台上使用 Spark,下面是我找到的最有用的指南(按有用性排序),希望其中一个能帮助你:
安装资源—PySpark 和 Jupyter 入门
安装资源 — 如何在自己的电脑上使用 PySpark
安装资源 — 如何在本地安装 PySpark
安装资源 — 如何入门 PySpark
PySpark 中的编码
因为我们想要处理列数据,所以我们将使用 Spark SQL 的一部分 DataFrames。
注意:为了避免可能的混淆,尽管我们将使用 Spark SQL,这些都不是 SQL 代码。当使用 Spark 数据帧时,您可以编写 SQL 查询,但您不必这样做。
配置火花会话
使用 Spark SQL 的入口点是一个名为SparkSession
的对象。它启动一个 Spark 应用程序,该会话的所有代码都将在其上运行。
from pyspark.sql import SparkSession
spark = SparkSession.builder \
.master("local[*]") \
.appName("Learning_Spark") \
.getOrCreate()
注意:这个上下文中的“\”字符称为延续字符,它只是一个有用的换行工具,使长代码行更具可读性。
.builder
—提供对用于配置会话的构建器 API 的访问。.master()
—决定程序运行的位置;"local[*]"
将其设置为在所有内核上本地运行,但是您可以使用"local[1]"
在一个内核上运行。在这种情况下,我们的程序将在谷歌的服务器上运行。.appName()
—命名 Spark 应用程序的可选方法.getOrCreate()
—获取一个现有的SparkSession
,如果没有,则创建一个新的
在构建SparkSession
时,检查构建器 API 以获得更多选项。
加载数据
要在 Google Colab 上打开一个本地文件,您需要运行以下代码,该代码将提示您从您的计算机中选择一个文件:
from google.colab import files
files.upload()
在本指南中,我们将使用 Kaggle 的视频游戏销售数据集。在这里可以找到。
现在使用.read.csv()
函数将我们的数据加载到 Spark 数据帧中:(为了简洁起见,我缩短了文件名)
data = spark.read.csv('Video_Games_Sales.csv',inferSchema=True, header=True)
注意:这个函数专门用于将 CSV 文件读入 PySparkSQL 中的数据帧。它不适用于将数据加载到 RDD 中,并且不同的语言(除 Python 之外)具有不同的语法。在网上搜索帮助时要小心,因为许多资源并不假定 Spark SQL 或 Python。
数据探索
现在让我们开始了解如何更熟悉我们的数据!
我们可以做的第一件事是检查数据帧的形状。不像熊猫,这没有专用的方法,但是我们可以自己使用.count()
和.columns()
来检索信息。
data.count(), len(data.columns)>>> (16719, 16)
.count()
方法返回 DataFrame 中的行数,而.columns
返回列名列表。
注意:我们不必实际打印它们,因为 Colab 会自动显示每个单元格的最后输出。如果你想显示一个以上的输出,你必须打印它们(除非你使用这个解决方法,它非常好,在 Jupyter 笔记本上也能工作)
查看数据帧
要查看数据帧,使用.show()
方法:
data.show(5)
Output from data.show(5)
如您所见,运行data.show(5)
显示了数据帧的前 5 行,以及标题。不带参数调用.show()
将返回前 20 条记录。
让我们使用。printSchema()
方法(也可以使用.dtypes
):
data.printSchema()
Output from data.printSchema()
此输出的一些要点是,Year_of_Release 和 User_Score 具有字符串类型,尽管它们是数字。它还告诉我们,每一列都允许空值,这在前 5 行中可以看到。
我们还可以用.select()
方法有选择地选择我们想要显示的列。让我们只查看名称、平台、用户分数和用户计数:
data.select("Name","Platform","User_Score","User_Count") \
.show(15, truncate=False)
Output from data.select().show()
包含的truncate=False
参数调整列的大小,以防止值被截断。
汇总统计数据/信息
我们可以使用.describe()
方法来获取我们选择的列的汇总统计数据:
data.describe(["User_Score","User_Count"]).show()
Output from data.describe().show()
从这个输出中可以看出,User_Score 列中似乎有一个奇怪的“tbd”值。User_Score 的计数也高于 User_Count,但很难判断这是因为 User_Score 中实际上有更多的值,还是“tbd”值人为地提高了计数。稍后我们将学习如何过滤掉这些值。
我们可能还想获得一些关于平台栏中有哪些平台以及它们是如何分布的信息。我们可以为此使用一个groupBy()
,并使用.orderBy()
对其进行排序:
data.groupBy("Platform") \
.count() \
.orderBy("count", ascending=False) \
.show(10)
Output from data.groupBy().orderBy().show()
这里我们来看一下最常用的 10 个平台。我们可以说这个数据集很旧了,因为我在任何地方都没有看到 PS4🤔
过滤数据帧 让我们创建一个新的数据帧,其中 User_Score 和 User_Count 的值为空,使用.filter()
方法过滤掉“待定”值:
condition1 = (data.User_Score.isNotNull()) | (data.User_Count.isNotNull())condition2 = data.User_Score != "tbd"data = data.filter(condition1).filter(condition2)
condition1
对于 User_Score 或 User_Count 中没有空值的任何记录,返回 True。condition2
对于 User_Score 中没有“tbd”的任何记录,返回 True。
我们可以通过重建先前的可视化来仔细检查我们的过滤是否有效
Reconstructed summary statistics and DataFrame with filtered out values
这是足够的数据探索,现在让我们建立一个模型!
在 PySpark 中构建模型
在 PySpark 中构建模型看起来和你可能习惯的有点不同,你会看到像变压器、和参数这样的术语。本指南不会深入探讨这些术语的含义,但下面的链接会简要介绍它们的含义。
进一步阅读—Spark 中的机器学习(约 5-10 分钟阅读)
设置
以线性回归为例,看看能否从 Year_of_Release、Global_Sales、Critic_Score、User_Count 预测 User_Score。
首先,让我们将所有的预测器重新编码为 Doubles(我发现这消除了后来一些非常棘手的错误)。
from pyspark.sql.types import DoubleTypedata2 = data2.withColumn("Year_of_Release", data2["Year_of_Release"].cast(DoubleType()))data2 = data2.withColumn("User_Score", data2["User_Score"].cast(DoubleType()))data2 = data2.withColumn("User_Count", data2["User_Count"].cast(DoubleType()))data2 = data2.withColumn("Critic_Score", data2["Critic_Score"].cast(DoubleType()))
我们使用方法withColumn()
,它要么创建一个新列,要么替换一个已经存在的列。例如,Year_of_Release 列被替换为自身的一个版本,该版本被转换为 doubles。
下一步是将我们的数据转换成 PySpark 可以用来创建模型的形式。为此,我们使用一种叫做VectorAssembler
的东西。
from pyspark.ml.feature import VectorAssemblerinputcols = ["Year_of_Release", "Global_Sales", "Critic_Score", "User_Count"]assembler = VectorAssembler(inputCols= inputcols,
outputCol = "predictors")
在这里,我们已经描述了我们希望我们的模型使用什么特征作为预测器,以便VectorAssembler
可以将这些列转换成一个列(名为“预测器”),其中包含我们希望预测的所有数据。
predictors = assembler.transform(data)predictors.columns
Output from VectorAssembler.transform().columns
VectorAssembler.transform()
所做的是创建一个新的数据帧,在末尾有一个新的列,其中每一行都包含了我们在创建汇编程序时在inputCols
参数中包含的所有特性的列表。
让我们的数据准备好在模型中使用的最后一步是收集我们刚刚制作的新预测列和 User_Score(我们的目标变量)在数据帧中。
model_data = predictors.select("predictors", "User_Score")model_data.show(5,truncate=False)
The final data we will use to build a model
接下来是将model_data
拆分成一个训练和测试集:
train_data,test_data = model_data.randomSplit([0.8,0.2])
模特训练
现在来训练模特!
from pyspark.ml.regression import LinearRegressionlr = LinearRegression(
featuresCol = 'predictors',
labelCol = 'User_Score')lrModel = lr.fit(train_data)pred = lrModel.evaluate(test_data)
从pyspark.ml.regression
导入LinearRegression
后,我们构建一个回归器,并指定它应该寻找一个名为“预测”的列作为模型的特征,一个名为“User_Score”的列作为模型的标签。接下来我们用.fit()
训练它,最后用.evaluate()
产生预测。
我们可以用下面的代码访问模型的参数
lrModel.coefficients
>>>
[-0.07872176891379576,-0.0350439561719371,0.06376305861102288,-0.0002156086537632538]lrModel.intercept
>>> 160.7985254457876
我们还可以查看我们的模型做出的最终预测:
pred.predictions.show(5)
Model predictions
名为“pred”的对象是一个linear regression summary对象,因此要检索带有预测的数据帧,我们称之为.predictions.show()
模型评估
from pyspark.ml.evaluation import RegressionEvaluatoreval = RegressionEvaluator(
labelCol="User_Score",
predictionCol="prediction",
metricName="rmse")
让我们为模型计算一些统计数据:
rmse = eval.evaluate(pred.predictions)mse = eval.evaluate(pred.predictions, {eval.metricName: "mse"})mae = eval.evaluate(pred.predictions, {eval.metricName: "mae"})r2 = eval.evaluate(pred.predictions, {eval.metricName: "r2"})
它返回
rmse
>>> 1.125mse
>>> 1.266mae
>>> 0.843r2
>>> 0.386
由此我们可以理解,我们的模型倾向于偏离实际 User_Score 大约 1.125 个评分点(根据 rmse)。的 r 值告诉我们,我们模型中的预测值能够解释 User_Score 中总可变性的 40%以下。这只是一个初步的观察,我建议您使用模型参数和特性进行更多的实践!
延伸阅读— 线性回归的详细代码示例 (~20+分钟完成整件事)
延伸阅读— 使用 SQL 的逻辑回归的详细代码示例 (~10 分钟)
延伸阅读— 线性回归、决策树和梯度推进树回归的示例 (6 分钟阅读)
就您可以在 PySpark 中进行的建模而言,这只是冰山一角,但是我希望本指南已经为您提供了足够的知识,让您能够进入大数据的大门!
结论
哇哦。如果你能坚持到最后,那就恭喜你。您接触了大量新概念,从分布式计算和 Spark 的术语,到在 PySpark 中实现数据探索和数据建模技术。我希望这份指南能成为你继续使用 Spark 的资源!
本文使用的所有代码都可以在 GitHub 这里找到。