用机器学习构建哈利波特语录机器人
使用 N-grams 和深度学习创建报价生成器应用程序的生成文本模型
语言模型如今被广泛使用。电子邮件、WhatsApp 文本、自动聊天机器人中的下一个单词预测都是基于语言模型的。
生成模型将一些文本作为输入,学习词汇和句子结构,并创建文本。《用 Python 进行深度学习》(Francois Chollet)这本书是理解 LSTMs 和神经网络如何学习文本的绝佳资源。这是理解 N 元语言模型的另一个很好的指南。
我使用生成模型创建了一个引用机器人,它从哈利波特宇宙中创建角色特定的引用!
数据
首先,我下载了《哈利·波特》系列的电影剧本。这给我带来了第一个挑战。在 python 中使用 pdf!幸运的是,我找到了一个包“pdf2txt ”,可以将这些 pdf 文件转换成我们以前的格式。txt 文件。
pdf2txt.py -o HP1_movie_script.txt HP1.pdf
这些更容易处理。
现在,继续前进。
备考
我的 QuoteBot 是特定于角色的,所以要学习一个特定角色的演讲风格,只需要相关的对话。我把每个角色的台词都分离出来,并把它们标记出来。
因为 QuoteBot 需要一个生成模型,所以创建一个语料库并对单词进行标记是我们唯一需要的数据准备。其他传统的 NLP 预处理步骤是不必要的,因为我们希望保持原始文本不变,以便模型学习序列。
模特们
N 元模型
ngrams 模型基于单词或字符出现的条件概率。对于我的 QuoteBot,我创建了一个单词级别的模型,在给定语料库中的一组单词的情况下,该模型查看下一个单词出现的概率。
单字和双字通常过于简单,无法创建连贯的文本。三元模型及以上可能非常有用。但是,我们应该谨慎使用更高阶的“克”。例如,10 克只会记住一个句子,而不是学习结构,导致模型“过度适应”。
三元模型比二元模型产生更一致的输出,例如
“不得不承认,我确实认为我可能看到了什么——当它被风吹走的时候?”—罗恩·韦斯莱
还有,4 克-
“真的吗?昨晚你不是不得不滚到床下以免被碎尸万段吗!做你的朋友会死人的,哈利!”—罗恩·韦斯莱
相当酷!有了这些 ngrams 模型,我在深度学习部分徘徊,看看递归神经网络是否会有帮助。特别是 LSTM 模型。
LSTM 模式
LSTM 模型也可以是字符级或单词级模型。对于我的 QuoteBot,我尝试了两种方法。LSTM 生成模型理解指南
人物级别
模型细节:1 层(有 Dropout),激活函数— softmax,优化器— RMSprop,损失函数—分类交叉熵。我让模型运行了 100 个时期,有了上面的这些细节,我的字符级模型给出了一个稍微不连贯的输出。
“荣!你将带领进化者去赢得荣誉。那是什么意思,先生?我不知道发生了什么?他们可能会羡慕我。”—哈利
有些词不连贯,句子结构有点不对。
字级 LSTM
模型细节:3 层(有 Dropout),激活函数— ReLU & softmax,优化器— Adam,损失函数—分类交叉熵。
我让模型运行了 300 个时期(在 Google Colab 上),有了以上这些细节,我的单词级模型给出了比字符级更好的输出。
”晚上哈利我有一个古老的迷宫继续说你也不会害怕哈利我想是伏地魔。 " —邓布利多
这样好多了!听起来这个角色和所有的单词都是连贯的。
创建 QuoteBot
基于每个生成模型的输出,我选择三元模型来创建 QuoteBot。我用 Flask 创建了一个应用程序,它将一个字符作为输入,并给出字符特定的引用!
学习
少即是多… 不是!深度学习模型需要一个大型数据集来从语料库中“学习”单词的顺序。给模型一个更大的语料库并调整更多的超参数肯定会提高模型的性能。
创建更大的语料库可以通过两种方式实现——
数据扩充——基于现有语料库为角色模拟更多文本
将范围从哈利波特电影系列扩展到我们希望模型学习的任何文本文档!
更多的训练对于优化模型输出总是更好的…斯内普同意!
参考资料:
使用 Kafka 和 spaCy 构建实时 NLP 管道
在本教程中,我们将使用汇合 Kafka 、python 和一个名为 spaCy 的预训练 NLP 库来构建一个实时管道。
作为先决条件,我们应该在本地安装 docker,因为我们将在我们的机器上运行 kafka 集群,还有 python 包 spaCy 和 confluent_kafka - pip install spacy confluent_kafka.
步骤 1:构建 kafka 集群
为了构建集群,我们将使用一个docker-compose
文件来启动所有需要的 docker 容器:zookeeper、一个代理和模式注册表。
现在简单地说,kafka 是一个分布式流媒体平台,能够处理大量的消息,这些消息被组织或分组到主题中。为了能够并行处理一个主题,必须将它分成多个分区,来自这些分区的数据存储在称为代理的独立机器中。最后,zookeeper 用于管理集群中代理的资源。这是卡夫卡经典版本中的元素。汇合平台添加了另一个元素,称为模式注册表。这是一种非常方便的方法,可以确保我们在写入和读取流中的数据时保持相同的模式。通常模式是以独立于平台的方式用avro
格式编写的,它存储在模式注册表中。
为了读写 kafka 集群,我们需要一个代理地址、一个主题和模式注册中心的 url。
docker-compose
将在2181
端口启动zookeper
,在9092
端口启动kafka broker
,在9999
端口启动schema registry
。除此之外,我们使用另一个 docker 容器kafka-create-topic
的唯一目的是在 kafka 代理中创建一个主题(称为 test)。
要启动 kafka 集群,我们必须在定义 docker compose 文件的同一文件夹中运行以下命令行指令:
docker-compose up
这将启动所有带有日志的 docker 容器。我们应该在控制台中看到类似这样的内容:
步骤 2:启动 kafka 生成器,并在流中写入一些消息
我们将用 python 创建一个简单的 kafka producer 来发送消息。这将通过使用confluent_kafka
库来实现。
卡夫卡的信息是键值对。该键通常用于对主题中的数据进行分区。我们将为我们的消息定义一个 avro 模式:
value_schema_str = """
{
"namespace": "my.schema",
"name": "value",
"type": "record",
"fields" : [
{
"name" : "data",
"type" : "string"
}
]
}
"""key_schema_str = """
{
"namespace": "my.schema",
"name": "key",
"type": "record",
"fields" : [
{
"name" : "key",
"type" : "string"
}
]
}
"""
在key schema
中,我们有一个名为key
的字段,类型为string
,在value schema
(实际数据)中,我们也只有一个名为data
的字段,类型为 string。
主要的 python 代码非常简单:
一旦我们用适当的配置定义了一个生产者,我们就可以向某个主题的代理异步发送大量消息。在教程的最后,我们有一个包含完整示例的 github 库的链接,包含配置和其他我们有意跳过的内容。
需要注意的一件重要事情是我们生成数据和密钥的方式:
value = {"data": random.choice(simple_messages)}
key = {"key": str(uuid.uuid4())}
密钥是一个随机的 uuid 字符串。这将确保数据在集群中均匀分布。对于数据,我们只是从预定义的列表中随机选择一个句子。对于我们发送的每一句话,我们将在消费者端应用一些 nlp 规则。
接下来,我们将消息发送到一个缓冲区,一旦达到一定数量的消息,该缓冲区将被刷新:
try:
avroProducer.produce(topic=args.topic, value=value, key=key) except BufferError as e:
messages_to_retry += 1
如果发送数据时出现问题,我们会再次重试。最后,我们只需要flush
缓冲区,我们实际上是将消息发送到 kafka 集群。
要运行生成器,我们只需在终端中运行:
python3 producer.py
我们应该会看到日志we’ve sent 5 messages to 127.0.0.1:9092
producer.py
也有额外的命令行参数。例如,如果我们想要发送 10 条消息,而不是默认数量的5
,我们可以使用python3 producer.py -m 10
。
可选的命令行参数有:
-b
代理 ip 和端口,默认127.0.0.1:9092
-s
模式注册表 url,默认[http://127.0.0.1:9999](http://127.0.0.1:9999)
-t
卡夫卡主题,默认test
-m
消息数量,默认5
步骤 3:消费 kafka 消息并应用 nlp 处理
卡夫卡的消费者是拼图的最后一块。在这一部分中,我们创建了一个AvroConsumer
并为其订阅了test
主题。我们对主题进行投票,直到找到想要的消息数量,并跳过null
或无效的消息。
一旦我们对一条消息进行了去序列化,我们就可以使用spacy
来应用 nlp 管道,并从单词中提取一些信息。
spacy 很酷的一点是,它是根据英语中的一般单词预先训练的,所以我们几乎可以开箱即用。为了下载spacy
的英语词汇,我们需要运行python3 -m spacy download en
。
这个spacy
API 非常容易使用。我们只需要将我们的句子传递给nlp
方法,这将运行一系列算法,如tokenization
(将句子分解成单词)、lemmatisation
(获得单词的基本形式)、part-of-speech tagging
(从单词中获得词性,如动词、副词等。),named entity extraction
(识别命名实体,如组织或地理实体)。一旦应用了算法,我们就可以从每个单词中提取我们需要的数据。
要运行使用者,请执行以下操作:
python3 consumer.py
使用与我们在生成器中相同的可选命令行参数。
输出应该类似于下图:
完整的代码示例可以在 github 上找到。
用 BERT 和 TensorFlow 构建搜索引擎
在这个实验中,我们使用一个预先训练好的 BERT 模型检查点来建立一个通用的文本特征提取器,并将其应用到最近邻搜索任务中。
T-SNE decomposition of BERT text representations (Reuters-21578 benchmark, 6 classes)
基于深度神经概率语言模型如伯特的特征提取器可以提取与大量下游 NLP 任务相关的特征。出于这个原因,它们有时被称为自然语言理解 (NLU)模块。
这些特征也可以用于计算文本样本之间的相似性,这对于基于实例的学习算法(例如 K-NN )是有用的。为了说明这一点,我们将为文本构建一个最近邻搜索引擎,使用 BERT 进行特征提取。
这个实验的计划是:
- 获取预训练的 BERT 模型检查点
- 提取为推理而优化的子图
- 用 tf 创建特征提取器。估计量
- 用 T-SNE 和嵌入式投影仪探索向量空间
- 实现最近邻搜索引擎
- 用数学加速搜索查询
- 示例:构建电影推荐系统
问题和答案
这本指南里有什么?
本指南包含两件事的实现:一个 BERT 文本特征提取器和一个最近邻搜索引擎。
这本指南是给谁的?
本指南对那些有兴趣使用 BERT 进行自然语言理解任务的研究人员应该是有用的。它也可以作为与 tf 接口的工作示例。估计器 API。
需要什么?
对于熟悉 TensorFlow 的读者来说,完成本指南大约需要 30 分钟。
给我看看代码。
这个实验的代码可以在 Colab 这里获得。另外,看看我为我的 BERT 实验建立的库:它包含额外的东西!
现在,我们开始吧。
步骤 1:获得预训练模型
我们从预先训练的 BERT 检查点开始。出于演示的目的,我将使用谷歌工程师预先训练的无外壳英文模型。要训练一个不同语言的模型,请查看我的其他指南。
为了配置和优化用于推理的图表,我们将利用令人敬畏的 bert-as-a-service 存储库。这个存储库允许通过 TCP 为远程客户端提供 BERT 模型。
在多主机环境中,拥有远程 BERT-server 是非常有益的。然而,在实验的这一部分,我们将着重于创建一个本地
(进程内)特征提取器。如果希望避免由客户端-服务器架构引入的额外延迟和潜在故障模式,这是有用的。
现在,让我们下载模型并安装软件包。
!wget [https://storage.googleapis.com/bert_models/2019_05_30/wwm_uncased_L-24_H-1024_A-16.zip](https://storage.googleapis.com/bert_models/2019_05_30/wwm_uncased_L-24_H-1024_A-16.zip)
!unzip wwm_uncased_L-24_H-1024_A-16.zip
!pip install bert-serving-server --no-deps
步骤 2:优化推理图
通常,要修改模型图,我们必须做一些低级的张量流编程。然而,由于 bert-as-a-service,我们可以使用简单的 CLI 界面来配置推理图。
有几个参数需要注意。
对于每个文本样本,BERT 编码层输出一个形状张量[ sequence_len , encoder_dim ],每个标记一个向量。如果我们要获得一个固定的表示,我们需要应用某种类型的池。
POOL_STRAT 参数定义应用于编码器层号 POOL_LAYER 的池策略。默认值’ REDUCE_MEAN '对序列中所有记号的向量进行平均。当模型没有微调时,这种策略最适合大多数句子级任务。另一个选项是 NONE ,在这种情况下,根本不应用池。这对于单词级的任务很有用,例如命名实体识别或词性标注。关于这些选项的详细讨论,请查看晓寒的博客文章。
SEQ _ 莱恩影响模型处理的序列的最大长度。较小的值将几乎线性地提高模型推理速度。
运行上述命令会将模型图和权重放入一个 GraphDef 对象,该对象将在 GRAPH_OUT 序列化为一个 pbtxt 文件。该文件通常比预训练模型小,因为训练所需的节点和变量将被移除。这产生了一个非常可移植的解决方案:例如,英语模型在序列化后只需要 380 MB。
步骤 3:创建特征提取器
现在,我们将使用串行化图来构建一个使用 tf 的特征提取器。估计器 API。我们将需要定义两件事情:输入 _fn 和模型 _fn
input_fn 管理将数据放入模型。这包括执行整个文本预处理管道,并为 BERT 准备一个 feed_dict 。
首先,每个文本样本被转换成一个 tf。示例实例包含输入名称中列出的必要特性。bert_tokenizer 对象包含了单词表并执行文本预处理。之后,在一个 feed_dict 中,示例按特征名重新分组。
tf。估算器有一个有趣的特性,每次调用 predict 函数时,它都会重新构建和初始化整个计算图。因此,为了避免开销,我们将向 predict 函数传递一个生成器,该生成器将在一个永无止境的循环中向模型提供特征。哈哈。
model_fn 包含模型的规格。在我们的例子中,它是从我们在上一步中保存的 pbtxt 文件中加载的。这些特征通过输入映射明确映射到相应的输入节点。
现在我们几乎拥有了进行推理所需的一切。我们开始吧!
由于我们使用了生成器,对 bert_vectorizer 的连续调用不会触发模型重建。
>>> bert_vectorizer = build_vectorizer(estimator, build_input_fn)
>>> bert_vectorizer(64*['sample text']).shape
(64, 768)
上述特征提取器的独立版本可以在库中找到。
第四步:用投影仪探索向量空间
现在是展示的时候了!
使用矢量器,我们将为来自 Reuters-21578 基准语料库的文章生成嵌入。
为了在 3D 中可视化和探索嵌入向量空间,我们将使用一种叫做 T-SNE 的降维技术。
让我们首先获得文章嵌入。
在嵌入投影仪上可以获得生成嵌入的交互式可视化。
从链接中你可以自己运行 T-SNE 或者使用右下角的书签加载一个检查点(加载只在 Chrome 上有效)。
使用生成的特征构建监督模型非常简单:
第五步:构建搜索引擎
现在,假设我们有一个 50k 文本样本的知识库,我们需要根据这些数据快速回答查询。我们如何从文本数据库中检索与查询最相似的样本?答案是最近邻搜索。
形式上,我们要解决的搜索问题定义如下:
给定向量空间 M 中的一组点 S ,以及一个查询点Q****∈M,找出 S 到 Q 中最近的点。在向量空间中有多种方法来定义“最近的”,我们将使用欧几里德距离。
因此,要构建文本搜索引擎,我们将遵循以下步骤:
- 向量化知识库中的所有样本——这给出了 S
- 向量化查询——这给出了 Q
- 计算 Q 和 S 之间的欧几里德距离 D
- 按升序排序D——提供最相似样本的索引
- 从知识库中检索所述样本的标签
为了让这个简单的实现变得更令人兴奋,我们将在纯张量流中实现它。
首先,我们为 Q 和 S 创建占位符
定义欧几里德距离计算
最后,得到最相似的样本指数
现在我们已经建立了一个基本的检索算法,问题是:
我们能让它运行得更快吗?通过一点点数学知识,我们可以做到。
第六步:用数学加速搜索
对于一对向量 p 和 q,,欧几里德距离定义如下:
这正是我们在第四步中的计算方法。
然而,由于 p 和 q 是向量,我们可以扩展并重写它:
其中⟨…⟩表示内积。
在张量流中,这可以写成如下形式:
顺便说一下,上面公式中的 PP 和 QQ 实际上是各自向量的平方 L2 范数。如果两个向量都是 L2 归一化的,那么 PP = QQ = 1。这给出了内积和欧几里德距离之间一个有趣的关系:
然而,进行 L2 归一化丢弃了关于矢量幅度的信息,这在许多情况下是不期望的。
相反,我们可能注意到,只要知识库不变, **PP,**其平方向量范数也保持不变。因此,我们可以只做一次,然后使用预计算的结果进一步加速距离计算,而不是每次都重新计算。
现在让我们把它们放在一起。
当然,您可以将这种实现用于任何矢量器模型,而不仅仅是 BERT。它在最近邻检索方面非常有效,能够在双核 Colab CPU 上每秒处理几十个请求。
例子:电影推荐系统
对于这个例子,我们将使用来自 IMDB 的电影摘要数据集。使用 NLU 和检索器模块,我们将构建一个电影推荐系统,推荐具有相似情节特征的电影。
首先,让我们下载并准备好 IMDB 数据集。
用伯特·NLU 模块矢量化电影情节:
最后,使用 L2Retriever,找到与查询电影情节向量最相似的电影,并将其返回给用户。
我们去看看吧!
>>> recommend = buildMovieRecommender(names, X_vect)
>>> recommend("The Matrix")
Impostor
Immortel
Saturn 3
Terminator Salvation
The Terminator
Logan's Run
Genesis II
Tron: Legacy
Blade Runner
结论
在本指南中,我们构建了一个通用的 BERT 特征提取器。用从 BERT 中提取的特征建立的模型在分类和检索任务上表现充分。虽然可以通过微调来进一步提高它们的性能,但是所描述的文本特征提取方法为下游 NLP 解决方案提供了可靠的无监督基线。
本系列中的其他指南
构建一个“无服务器”的 Chrome 扩展
这是一个关于构建利用无服务器架构的 Chrome 扩展的教程。具体来说——我们将在 Chrome 扩展的后端使用谷歌云功能,为我们做一些奇特的 Python 魔术。
我们将构建的扩展是 SummarLight Chrome 扩展:
[## 介绍 summary light——一个 Chrome 扩展,它突出了
medium.com](https://medium.com/@btahir/introducing-summary-light-a-chrome-extension-that-highlights-the-most-important-parts-of-an-1666e10411a8)
SummarLight 扩展获取你当前所在网页的文本(大概是一个像我这样的媒体上的很酷的博客),并突出显示那个页面/文章最重要的部分。
为了做到这一点,我们将设置一个 UI(在本例中是一个按钮),它将把当前网页上的文本发送到我们的后端。在这种情况下,“后端”将是一个谷歌云功能,它将分析文本并返回其摘要(该文本中最重要的句子)。
体系结构
A Simple & Flexible Architecture
正如我们所见,该架构非常简单灵活。你可以设置一个简单的用户界面,比如一个应用程序,或者在这种情况下,一个 Chrome 扩展,然后把任何复杂的工作交给你的无服务器功能。您可以轻松地更改函数中的逻辑,并重新部署它来尝试其他方法。最后,您可以根据需要扩展它以支持任意多的 API 调用。
这不是一篇关于无服务器的好处的文章,所以我不会详细讨论使用它优于传统服务器的优点。但是通常情况下,无服务器解决方案会便宜得多并且可伸缩(但并不总是这样…取决于您的使用情况)。
Chrome 扩展
这里有一个关于 Chrome 扩展设置的很好的指南:
[## 如何在 20 分钟内创建并发布一个 Chrome 扩展
如何在 20 分钟内创建并发布 Chrome 扩展
www.freecodecamp.org](https://www.freecodecamp.org/news/how-to-create-and-publish-a-chrome-extension-in-20-minutes-6dc8395d7153/)
SummarLight 扩展的所有代码都可以在这里找到:
Summarlight Chrome 扩展突出了帖子/故事/文章中最重要的部分。-btahir/summary light
github.com](https://github.com/btahir/summarlight)
根目录中的 main.py 文件是我们定义 Google Cloud 函数的地方。extension_bundle 文件夹包含了创建 Chrome 扩展的所有文件。
谷歌云功能
我选择了谷歌而不是 AWS Lamba,因为我有一些免费的学分(感谢谷歌!)但是你也可以用 AWS 轻松做到。对我来说,这是一个巨大的优势,他们刚刚发布了 Python 的谷歌云功能,因为我用这种美丽的语言处理大部分数据。
您可以在此了解有关部署谷歌云功能的更多信息:
[## 从本地计算机部署|云功能文档| Google 云
无论您的企业是刚刚踏上数字化转型之旅,还是已经走上数字化转型之路,谷歌云的解决方案…
cloud.google.com](https://cloud.google.com/functions/docs/deploying/filesystem)
我强烈推荐使用 gcloud sdk,并从 hello_world 示例开始。您可以在他们提供的 main.py 文件中编辑您需要的函数。这是我的函数:
import sys
from flask import escape
from gensim.summarization import summarize
import requests
import json
def read_article(file_json):
article = ''
filedata = json.dumps(file_json)
if len(filedata) < 100000:
article = filedata
return article
def generate_summary(request):
request_json = request.get_json(silent=True)
sentences = read_article(request_json)
summary = summarize(sentences, ratio=0.3)
summary_list = summary.split('.')
for i, v in enumerate(summary_list):
summary_list[i] = v.strip() + '.'
summary_list.remove('.')
return json.dumps(summary_list)
相当直接。我通过 read_article()函数接收一些文本,然后使用 awesome Gensim 库返回这些文本的摘要。Gensim Summary 函数的工作原理是按照重要性对所有句子进行排序。在这种情况下,我选择返回最重要句子的前 30%。这将突出文章/博客的前三分之一。
**替代方法:**我尝试了不同的摘要方法,包括使用手套词嵌入,但结果并不比 Gensim 好多少(特别是考虑到由于加载这些大规模嵌入而增加的处理计算/时间)。尽管如此,这里仍有很大的改进空间。这是一个活跃的研究领域,并且正在开发更好的文本摘要方法:
[## 摘要
知识库跟踪自然语言处理(NLP)的进展,包括数据集和当前…
nlpprogress.com](http://nlpprogress.com/english/summarization.html)
一旦我们对这个功能很满意,我们就可以部署它,它将在一个 HTTP 端点上可用,我们可以从我们的应用程序/扩展调用它。
扩展包
现在是前端。首先,我们需要一份 popup.html 档案。这将处理 UI 部分。它将创建一个带有按钮的菜单。
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<ul>
<li>
<a><button id="clickme" class="dropbtn">Highlight Summary</button></a>
<script type="text/javascript" src="popup.js" charset="utf-8"></script>
</li>
</ul>
</body>
</html>
正如我们所看到的,“Highlight Summary”按钮有一个触发 popup.js 文件的 onClick 事件。这将依次调用 summarize 函数:
function summarize() {
chrome.tabs.executeScript(null, { file: "jquery-2.2.js" }, function() {
chrome.tabs.executeScript(null, { file: "content.js" });
});
}
document.getElementById('clickme').addEventListener('click', summarize);
summarize 函数调用 content.js 脚本(是的是的,我知道我们可以避免这个额外的步骤……)。
alert("Generating summary highlights. This may take up to 30 seconds depending on length of article.");
function unicodeToChar(text) {
return text.replace(/\\u[\dA-F]{4}/gi,
function (match) {
return String.fromCharCode(parseInt(match.replace(/\\u/g, ''), 16));
});
}
// capture all text
var textToSend = document.body.innerText;
// summarize and send back
const api_url = 'YOUR_GOOGLE_CLOUD_FUNCTION_URL';
fetch(api_url, {
method: 'POST',
body: JSON.stringify(textToSend),
headers:{
'Content-Type': 'application/json'
} })
.then(data => { return data.json() })
.then(res => {
$.each(res, function( index, value ) {
value = unicodeToChar(value).replace(/\\n/g, '');
document.body.innerHTML = document.body.innerHTML.split(value).join('<span style="background-color: #fff799;">' + value + '</span>');
});
})
.catch(error => console.error('Error:', error));
这里是我们解析当前页面的 html(document . body . innertext)的地方,在用 unicodeToChar 函数进行了一些预处理之后,我们通过 Fetch API 将它发送给我们的 Google Cloud 函数。为此,您可以在 api_url 变量中添加自己的 HTTP 端点 url。
同样,利用 Fetch,我们返回一个承诺,这是从我们的无服务器函数生成的摘要。一旦我们解决了这个问题,我们就可以通过页面的 html 内容解析这个循环,并突出显示摘要中的句子。
由于完成所有这些处理可能需要一点时间,我们在页面顶部添加了一个警告来表明这一点(“生成摘要突出显示。根据文章的长度,这可能需要 30 秒钟。”).
最后,我们需要创建一个 manifest.json 文件来发布扩展:
{
"manifest_version": 2,
"name": "SummarLight",
"version": "0.7.5",
"permissions": ["activeTab", "YOUR_GOOGLE_CLOUD_FUNCTION_URL"],
"description": "Highlights the most important parts of posts/stories/articles!",
"icons": {"16": "icon16.png",
"48": "icon48.png",
"128": "icon128.png" },
"browser_action": {
"default_icon": "tab-icon.png",
"default_title": "Highlight the important stuff",
"default_popup": "popup.html"
}
}
注意“权限”选项卡。我们必须在这里添加我们的 Google Cloud 函数 URL,以确保当我们通过 Fetch API 调用我们的函数时不会出现 CORS 错误。我们还填写了名称/描述和图标等细节,以便在 Google Store 上显示我们的 Chrome 扩展。
就是这样!我们已经创建了一个 Chrome 扩展,它利用了一个无服务器的主干网,也就是谷歌云功能。最终效果是这样的:
A demo of SummarLight
这是一种简单而有效的方式来构建真正酷的应用程序/扩展。想想你用 Python 做过的一些东西。现在你可以把你的脚本挂在一个扩展/应用程序的按钮上,然后用它做一个好的产品。无需担心任何服务器或配置。
下面是 Github 回购:https://github.com/btahir/summarlight
你可以自己使用分机。这是谷歌商店的直播:
突出文章最重要的部分!
chrome.google.com](https://chrome.google.com/webstore/detail/summarlight/ligjmagakdphdlenhhncfegpdbbendlg?hl=en-US&gl=US)
请在评论中分享你对利用谷歌云功能的扩展(或应用)的想法。😃
干杯。
为您的模型构建一个稳定且用户友好的 API
你的模型值得一个伟大的 API
大多数(如果不是全部)数据科学家最终都需要部署他们的模型,并通过 API 使其可用。在机器学习项目的端到端过程中,这通常是最容易被忽视的部分,因为大多数注意力都集中在建立模型或可怕的数据清理上。构建一个稳定且用户友好的 API 是让用户/产品团队开始消费你的服务并持续消费的关键。那么,如何着手构建一个稳定且用户友好的 API 呢?这就是这篇文章的来源,这些年来,我专门为数据科学模型构建了许多 API,我想与您分享我是如何着手构建它们的。本文假设了以下情况:
- Python 知识
- 烧瓶的基本知识
如果你还没有猜到,我们将使用 Python 和 Flask 来构建 API,所有代码都可以在我的 GitHub 账户上找到。
问题背景
我们建立了一个(非常粗糙的)随机森林模型来预测房价。这个模型有三个特点:is_house
、has_garden
和n_bedrooms
。is_house
和has_garden
都是二进制标志,而n_bedrooms
是整数。向利益相关者展示该模型给他们留下了深刻的印象,现在他们希望进入下一阶段,即通过 API 进行质量保证。现在,您的任务是为您的模型构建一个 API。
文件夹结构
我的 API 通常遵循这种文件夹结构。我试图让它尽可能简单,同时也让它易于扩展:
common
:保存常用的模块/类,可以与代码库的其他部分共享(例如,代码库中可能有多个 API)endpoint_docs
:保存端点文档的 YAML 文件(我们稍后会讲到)views
:保存 API 视图的模块api.py
:本地启动 APIblueprint.py
:用于使您的应用程序模块化,并为您的端点生成 URL 前缀config.yml.example
:保存应用程序配置的示例文件Pipfile
:API 需要的包settings.py
:用于从您的配置文件导入设置wsgi.py
:用于启动生产服务器.env.example
:保存环境变量
要在本地安装并运行,请查看 repo 上的自述文件,其中给出了安装说明。
配置和设置文件
这可能看起来像是构建 API 的一个非常无聊的方面,确实如此,但是从一开始就正确地配置 API 确实有助于以后的工作。这里涉及的三个文件是 API 配置:
- 配置. yml .示例
- . env .示例
- settings.py
config.yml
和.env
文件包含应用程序的应用程序设置和环境变量,其中可能包含敏感信息。我们不希望这些信息出现在存储库中,所以我们创建了示例文件,其中省略了敏感信息。例如,config.yml
文件可能包含一个数据库密码:DB_PASSWORD: XXXXX
,但是在config.yml.example
中这个密码将是:DB_PASSWORD:
。密码被删除,但与之相关的变量被保留,这使得应用程序的新用户更容易启动和运行,因为这突出了应用程序所需的配置。要使用这些文件,你需要做的就是复制它们,但是去掉后缀.example
重新命名它们。在我的config.yml
文件中,我只有一个名为MODEL_NAME
的变量,这只是我们希望使用的模型的名称。在config.yml
文件中,用rf_classifier.pkl
填写MODEL_NAME
。在.env
文件中,将APP_SETTINGS_YAML
分配给config.yml
。最后,确保config.yml
和.env
文件都在您的.gitignore
文件中,这样它们就不会被意外地推到存储库中。
settings.py
文件是 API 对这些变量的访问点(如下所示)。以下代码部分的第 9-14 行加载到.env
文件中,并有一个用于MODEL_NAME
的占位符,默认为None
:
接下来,获得APP_SETTING_YAML
(在.env
文件中定义)的值(第 17 行),该值为config.yml
。然后读取该文件并提取键值对(第 18–20 行)。然后,config.yml
中的这些键值对被用来更新settings.py
文件中的值。占位符为None
的变量是要更新的变量。
MODEL_NAME
的初始值是None
,但是在读入config.yml
文件并更新全局值后,它现在被设置为rf_classifier.pkl
。要访问这个变量,您在应用程序中要做的就是:
*import* settings
print(settings.MODEL_NAME)
这里需要注意的重要一点是settings.py
中定义的应用变量名必须与config.yml
中的键名完全匹配,否则settings.py
中的变量不会被更新。
第 22–24 行的最后一段代码实际上查看了您的操作系统中定义的环境变量,并使用这些变量覆盖来自config.yml
的派生变量。我听你说,你为什么要这么做?当您通常部署到生产环境时,可能会有不同的应用程序设置,如生产数据库用户名和密码或端口号等,您不会在本地使用这些设置。你需要做的不是有第二个config.yml
文件,而是在你的操作系统环境中定义这些相同的变量。这允许您在用于本地开发的设置和用于生产的设置之间轻松切换,而无需更改任何应用程序代码。这里要记住的一个关键点是,在操作系统环境中定义的变量优先于在config.yml
文件中定义的变量。
常用文件夹
该文件夹包含 python 模块,这些模块具有与其他 API 相同的功能。因此,如果您要为这个项目添加另一个 API,比如说为另一个模型添加 API,您就不必重复代码。让我们从schemas.py
开始分解这个文件夹中的模块
schemas.py
该模块使用 marshmallow 包将数据加载到我们定义的模式中。Marshmallow 将根据模式检查数据,如果发现任何不一致,将会引发错误。通过对照模式验证数据,我们确保 API 的消费者符合模型被训练的特性范围。这很好,因为它防止 API 的消费者向模型传递意外的参数,从而获得奇怪的结果,这些结果可能会在以后的报告中使用。我们模型的模式如下所示:
我们首先创建一个从 marshmallow 继承了Schema
类的类,这将允许我们创建自己的模式。接下来,模式的变量(模型的特征)被定义并被赋予字段对象。字段对象简单地定义了对象的类型,例如字符串,甚至是字段接受的另一个模式的整数。它们还可以通过传递带有布尔值的关键字参数required
来声明一个字段是否是强制的。这里我们创建三个变量is_house
、has_garden,
和n_bedrooms
,它们在第 13-15 行声明,并根据需要标记。这三个变量都定义了一个整数类型。接下来,我们创建三个函数来验证每一个特性,这些函数的名字前面都有前缀validate
,这不是必须的,只是我自己命名的。这些函数是用@validates
方法修饰的,该方法接受一个参数,该参数是您希望作为类型字符串进行验证的特性的名称。只有用@validates
包装的函数用于验证数据,并且字符串输入必须是在类顶部定义的变量。作为修饰函数的结果,它们现在接受一个位置参数,这里定义为value
。这是传递给每个特性的值,我们根据它执行验证。is_house
和has_garden
都是二进制标志,具有完全相同的检查,只有错误消息不同(第 17-25 行)。
例如,如果validate_is_house
中的 if 语句返回 true,就会产生一个错误,并显示一条特定的消息:is_house must be either 0 or 1
。然后可以将错误消息返回给客户端。这种错误信息为用户提供了有用的调试信息,而不是一般的 HTTP 错误信息。n_bedrooms
验证(第 27–30 行)验证该值是否大于或等于零,如果不大于或等于零则引发错误。
方法make_price_prediction_data_model
在模式检查通过后运行。这是由@posts_loads
装饰者定义的。该函数的执行返回包含特征的数据类对象。这是查看data_models.py
中定义的数据类的好时机
data_models.py
如上所述,一旦模式检查通过,数据模型就会以数据类的形式返回。如果你不知道什么是数据类,我强烈推荐你查看这个帖子。数据类为我们提供了开箱即用的附加功能,还允许我们通过名称访问类的属性,就像普通的类一样。我们模型的数据类如下所示:
这里没什么事情。我们定义了一个由@dataclass
包装的类,并在该类中定义了三个变量,它们是该类的属性。在我们的例子中,这些属性是模型的特征。我还定义了一个__repr__
方法来给我们一个有用的类表示。就像普通的类一样,如果需要的话,你可以添加额外的方法。
logger.py
最后一个模块是common
文件夹,是日志文件。该文件只包含几行设置记录器的代码:
在这里,我设置了一个带有StreamHandler
的日志记录器来记录控制台。如果您愿意,您可以设置一个记录器来记录文件,请参考文档中的基础教程来告诉您如何操作。
时髦的
有了定义的模式,用户如何预先知道什么是模型可接受的输入?此外,用户如何为您的 API 探索其他端点?这就是霸气的用武之地。通过在 API 中实现 Swagger,我们为 API 提供了一个可视端点,列出了所有可用的端点以及它们接受的 HTTP 方法。此外,对于每个端点,您可以提供要运行的模式和示例,本质上是记录 API。下面是我们正在构建的 API 的 Swagger 页面的屏幕截图:
如何实现 swagger for out API?好消息是,有一些软件包可以帮我们做到这一点,比如 Flassager 和connection。对于我们的 API,我们将使用 Flassager。
招摇文件
为了让 swagger 显示信息,我们需要为它提供文档。该文件以yaml
文件格式提供,位于endpoint_docs
文件夹中。如果您导航到在/endpoint_docs/property_price_prediction_v1/get.yml
找到的文件,您将找到上面显示的该端点的文档。该文件中包含端点的参数以及响应示例。有关记录端点的更多信息,请参见 swagger 文档
创建端点
现在我们来看 api 本身的代码。让我们从位于/views/property_price_prediction.py
的 API 的唯一端点开始。该文件包含一个定义端点的类:
ProperyPricePrediction
类从flassagger
继承了SwaggerView
,后者也从 Flask 继承了方法视图。方法视图允许我们使用同一个端点在一个类中接受不同的 HTTP 方法。要做到这一点,我们首先需要定义以它们所服务的方法命名的函数。例如,在上面的代码中,这个端点接受 GET 方法,所以我定义了一个名为get
的函数。如果我想让这个端点也接受一个POST
方法,我要做的就是在这个类中定义一个post
方法,并将POST
添加到在类顶部创建的methods
变量中。这个变量对于 method 视图来说不是必需的,但是对于 swagger 来说是必需的,所以如果你没有使用 swagger,你可以省略它。如果你使用的是 swagger,那么它现在希望文档存在于一个名为get.yml
的文件中,你可能已经猜到了,但是这些文件是以它们所服务的 HTTP 方法命名的。下面的蓝图部分解释了get.yml
文件的目录结构。
转到def get
方法。这个函数中的代码被封装在一个try/except
块中,以防止在任何处理过程中出现错误时 API 关闭。这个方法获得模型的输入,作为第 21 行定义的 URL 参数,并将它转换成一个字典。接下来的第(22)行将这个特性加载到模式中,模式反过来验证输入。如果失败,验证错误出现,在try/except
块中捕获并返回给用户。如果通过,数据将被加载到数据模型中,该模型现在可用于进行预测。下一行代码使用数据模型进行预测(第 23— 27 行)。请注意,我们现在可以通过名称来引用特性,这样在代码中看起来更好。然后,预测作为 JSON 对象返回给用户。
在文件的顶部,我导入了设置模块。在这个模块中定义了一个全局变量MODEL_NAME
。该变量表示要使用的模型名称,该名称随后被分配给MODEL
变量。我将MODEL_NAME
定义为一个 API 配置,因为它可以通过简单地改变应用程序中的配置而不是源代码,轻松地在不同模型之间快速切换。MODEL
变量是在类的外部定义的,因为我们只需要模型加载一次。如果这是在get
方法中定义的,这将意味着每次请求进来时都要加载模型。这将大大降低请求的响应时间,尤其是在模型很大的情况下。
蓝图
蓝图允许我们模块化我们的应用程序路线,例如,我们可能有一个仅供管理员使用的蓝图和一个供用户使用的蓝图,因此分离路线。为了实现这一点,我们需要创建蓝图并向它们注册端点,在blueprints.py
中的代码为我们做了这些:
一般来说,要创建一个蓝图,您所要做的就是创建一个Blueprint
类的实例,并用add_url_rule
方法向它注册端点。在上面的代码中,我通过创建自己的继承 Flask 的Blueprint
类的BaseBlueprint
类来扩展Blueprint
类。我这样做是为了让日志记录更容易。Flask 的 Blueprint 类有两个方法before_request
和after_request
,如果分配给方法,它们将分别在请求之前和之后被调用。在BaseBlueprint
类的__init__
方法中,我调用这两个方法,并分配_log_before_request
在每个请求之前调用,而_log_after_request
在每个请求之后调用。在_log_before_request
方法中,我记录了与任何参数一起被调用的端点。_log_after_request
方法记录 HTTP 响应状态和返回的 JSON 负载。对于单个端点来说,这似乎是一项繁重的工作,但是如果我们要向 API 添加新的端点,我们所要做的就是将新的视图作为一项规则添加到这个BaseBlueprint
实例中(实际上只有一行代码),并且我们已经实现了日志记录!这使得扩展 API 的日志更加容易。我发现这个解决方案比在视图函数本身中实现日志记录要优雅得多。
现在需要做的就是创建一个BaseBlueprint
类的实例,这是在第 31 行完成的。然后,我们获取该实例并向其添加一个 URL 规则(第 33–36 行),这意味着每次调用该 URL 时,都会调用分配的视图函数。add_url_rule
方法接受一个位置参数,它是一个字符串,是这个端点的 URL。我还添加了一个关键字参数view_func
,它接受我们想要分配给这个 URL 的MethodView
(或者在我们的例子中是SwaggerView
)。在这里,我将PropertyPricePrediction
视图分配给这个 URL,并声明它是一个使用as_view
方法的视图。这样做是为了将类转换成路由系统可以使用的视图函数。这个方法中的关键字参数name
只是为这个视图指定一个名称。这里定义的名称也为 Swagger 搜索端点文档提供了参考。因为这个视图有一个get
方法,所以到get
文档的路径会有如下模式:<name of view>/<HTTP method>.yml
并且会转换成:property_price_prediction_v1/get.yml
api.py
该文件是启动 API 的访问点,主要包含配置:
让我们一行一行地过一遍:
- 1–5:所需进口。注意,我导入了 Swagger 和我们在上面创建的蓝图
- 8 :创建一个 Flask 实例
- 10–15:允许所有前缀为
/api
的 URL 上的跨来源请求 - 17–20:注册我们在上面创建的蓝图,并为所有端点创建以
/api
开头的前缀 - 21–23:有些霸气的配置。在这里,我告诉 Swagger 在哪里可以找到端点文档。
- 24–34:围绕第 9 行创建的 Flask 实例创建一个 Swagger 实例。我将 parse 设置为
False
,这意味着不根据端点文档中提供的模式检查传入的请求,因为我更喜欢 Marshmallow 的灵活性。当然,您可以将其设置为true
,所有传入的请求都将被检查。最后,我只是在 API 文档页面上提供一些元数据。 - 36–41:只有当文件作为主程序运行时,才会启动 API。
wsgi.py
这是最后的文件。在生产设置中启动 API 的文件。这个文件只导入 API 实例(从 api.py)。我将这一点分离出来,这样我们就可以在生产中进行不同的设置,并在需要时进行任何额外的检查。你可以直接运行api.py
,但是你将只使用 flask 的内部服务器,这不是我们想要的,并且不推荐用于生产。相反,我们将使用一个名为 Gunicorn 的包,它是一个用于 UNIX 的 Python WGSI 服务器,与许多 python web 框架兼容。如果你在 Windows 上,你可以使用女服务员。启动 API 的调用如下:
Unix / Mac : gunicorn -b 0.0.0.0:5000 -w 1 wsgi:api
视窗 : waitress-serve --listed=*5000 wsgi:api
如果您在 Unix / Mac 上运行,应该会看到类似这样的内容:
API 现在正在运行,如果您导航到http://localhost:5000/apidocs
,您应该会看到:
这是显示 API 文档的页面。单击蓝色条将展开它,显示对模型执行查询的能力,如下所示:
单击“试用”将允许您输入特性值,输入以下特性值,然后点击“执行”(在您单击“试用”之前,按钮不会显示):
is_house
: 5had_garden
: 1n_bedrooms
: 3
is_house
的这个值对于我们的模型是无效的,当您点击 execute 时,它会抛出一个错误。再往下是示例 curl 请求,请求您刚刚输入的数据以及请求 url(示例 curl 请求直到执行查询后才会显示)。下面是来自服务器的实际响应。该示例如预期的那样应该返回 400 —错误:错误的请求以及该响应正文:
{
"error": "{'is_house': ['is_house must be either 0 or 1']}"
}
这正是我们想要的,一个清晰的错误被返回给用户,解释请求失败的原因。现在输入一些应该有效的值:
is_house
: 0had_garden
: 0n_bedrooms
: 2
然后点击执行。在点击 execute 之后,curl 请求和请求 url 应该被更新。您还应该有一个状态代码为 200 的新响应主体,表示查询成功。响应正文应该类似于:
{
"data": {
"predicted_price": [
300000
]
}
}
显示给定输入要素时模型对此属性的值。现在,如果我们看一下打印到控制台的日志,您应该会看到:
日志揭示了对模型的请求、传递的参数以及请求是否失败。我们可以看到第一个失败的请求,因为is_house
必须是二进制值。接下来是我们的第二个请求,它满足模式并成功返回一个预测。
希望你能明白为什么使用 Swagger 是你的 API 的一个必要工具,因为很少的工作你就能得到很多额外的功能,这是完全值得的。这个工具允许用户使用你的 API,查看它的约束,这将允许他们相应地计划他们的资源消耗。
结论
这里,我们构建了一个 API 来处理请求,并优雅地将错误返回给用户,使 API 更加透明,不再是一个黑盒。数据模式是一个很好的约束,因为这可以防止模型在它被训练的特性范围之外的意外使用,从而防止一些尴尬的问题。
就是这样!希望在这篇文章中,我已经向您展示了构建一个稳定且用户友好的 API 的重要性。最后,感谢您的阅读,如果您感兴趣,我可能会写一些关于为 API 添加测试的帖子,以及一篇关于集装箱化的帖子。
用自然语言框架构建文本分类器
了解如何为所有 Apple 平台上的应用程序构建文本分类器。
介绍
你可能知道或者至少听说过像 Siri、Cortana 和 Alexa 这样的虚拟助手。也许你和某个公司的聊天机器人聊过天?如果你不是像我一样以英语为母语,你可能会经常使用谷歌翻译来帮助你阅读像这样的开发材料。
这些都是自然语言处理的应用。NLP 有很多定义,但本质上,它指的是任何一种由计算机执行的自然语言操作,通常使用机器学习的技术。我所说的自然语言是指人类交流中使用的任何一种非结构化或无限制的语言。
去年,苹果发布了natural language Framework(NLF ),为那些愿意让他们的应用程序更具 NLP 智能的开发者提供更简单的体验。该框架提供了一个 API,它隐藏了文本处理任务中通常涉及的大部分复杂性。
NLF 是我在第 21 届佛罗里达会议上演讲的主题,在那里我详细介绍了 NLP 用例、其通用管道以及 NLF 的使用。根据这些建议,我决定写这篇博文来讨论这次演讲的主要部分。因此,我将介绍 NLP 的一些基本概念,并解释如何构建一个文本分类器。
自然语言框架
NLF 可以在所有苹果平台上使用,它是从 iOS 11 开始出现在Foundation
中的实用程序类NSLinguisticTagger
的演变。像它的前身一样,NLF 提供了以下内置功能:
- 语言识别:该框架承诺识别近 60 种不同的语言。
- 标记化:可以将文本分解成代表基本意义单位(即单词)的字符序列。NLF 处理像日语这样的语言,这个任务不仅仅是用空格分割句子。
- 词性(PoS)标注:框架能够用来自某个方案的标签来标注每个标记。例如,我们可以根据词汇类别(如名词、动词、形容词等)对标记进行分类。
- 词汇化:这其实是一种特殊的词性标注方案。它用于减少单词的屈折形式(例如,将复数形式变为单数)。
- 命名实体识别(NER) :这是另一个词性标注方案。它用于识别感兴趣的实体,如位置、人员、组织、日期等。
这些内置函数允许我们执行许多很酷的文本处理任务,而无需任何进一步的机器学习算法。例如,我们可以通过一个基本的词汇化调整来改进文本搜索。
在这篇博客文章中,我不会详述语言识别和标记化(但是你可以在这里找到一篇关于它们的很棒的文章)。相反,我将集中讨论词性标注。
词性标注
NLTagger
是负责词性标注任务的班级。它有一个enumerateTags(in:unit:scheme:options:block:)
函数,用于列出给定文本中的标签,如清单 1 所示。block
参数是一个闭包,每个令牌调用一次,传递它的范围和相应的标签。
Listing 1: Using NLTagger for PoS tagging.
为了实例化一个NLTagger
对象,我们应该提供一个NLTagScheme
数组来告诉标记者需要哪组标记。清单 1 使用了.nameTypeOrLexicalClass
,因此只返回词法类和命名实体。您可以在输出 1 中看到结果。其他常用的方案有.lexicalClass
、.nameType
和.lemma
。
Output 1: Result of Listing 1.
unit
是另一个重要参数。它告诉标记者应该标记哪个文本块。除了.word
,我们还可以用.sentence
、.paragraph
和.document
。但是,对于某些方案和单元的组合,标记器可能不会返回任何结果。例如,使用.lemma
作为 scheme 和.sentence
作为 unit 是没有意义的,因为您只能在每个单词的基础上进行词汇化。
最后一个参数是option
。它只是告诉标记者补充信息,并不要求标记者工作。例如,我们可以用它来告诉标记者省略空格和标点符号,不要把粒子当作单独的标记。
定制模型
所有这些贴吧标签都很酷,但是如果我们想在文本中识别口袋妖怪的名字或者根据它们的类型对文本进行分类呢?好吧,那 NLF 默认做不到。我们需要建立一个自定义模型。
这可能是 NLF 最酷的部分:它可以与 CoreML 集成来构建定制的单词标签和文本分类器。这还不是全部。苹果还发布了 CreateML 框架,用于构建直接进入 NLF 的定制 CoreML 模型。机器学习开始了。
NLP 通常是一个分类问题,目标是在有限的可能性集合中为给定的输入分配一个标签。它还通常使用监督学习算法,其中我们提供一个包含文本及其相应标签的训练集,算法使用该训练集进行学习。我不会深入讨论这些细节,因为这超出了讨论范围(不过,你可能想要查看这篇伟大的文章)。相反,让我们关注 NLF 如何通过构建一个定制的文本分类器来隐藏大部分的复杂性。
在下一节中,我将通过构建一个餐馆评论分类器来演示定制模型是如何工作的。目标是将输入文本标记为正面或负面评论。
构建分类器
培训工作流程如**图 1 所示。**我们将训练数据传递给 CreateML,这样它就可以使用 NLF 从这些数据中提取特征,学习模式并将这些知识保存为 CoreML 模型。
Image 1. Model training workflow.
清单 2 是我们将用来构建分类器的数据集的摘录。这是一个众所周知的数据集,它包含一千条评论及其相应的标签,可以是POSITIVE
或NEGATIVE
。你可以在这里找到完整的 JSON 文件。
Listing 2: Excerpt of the restaurant reviews dataset.
我们还需要一个 macOS 平台来训练模型,因为 CreateML 在其他平台上不可用。所以启动游戏并运行清单 3 中的代码。
Listing 3: Snippet for training the model.
第一步是将数据集加载到一个MLDataTable
中,这是 CreateML 使用的一个表格表示。这个类实际上非常强大。您可以使用它在数据集中执行额外的预处理步骤,例如创建和删除列。
下一步,我们使用randomSplit(by:seed:)
函数将数据集随机分成训练集和测试集。该函数需要一个随机操作的种子和一个比率来确定用于训练和测试的数据集的百分比。种子可以是我们想要的任何整数。这个比例通常在 70%到 90%之间。如果你用更多的数据进行训练,就不会有足够的数据来评估模型;如果您使用较少的数据进行训练,您将无法执行良好的训练,您的大型测试集将会显示这一点。这是一个权衡,80%通常是一个好的选择。
第 3 步是我们实际构建模型的地方。为此,我们初始化MLTextClassifier
,传递训练集和对应于 JSON 文件的文本和标签列。请注意,这个过程可能需要很长时间,这取决于许多因素,如类的数量和训练集的大小。返回的对象是我们用来做预测的分类器。
下一步是评估我们的分类器的性能。我们通过将测试集传递给evaluation(on:)
函数来做到这一点。它对测试集中的每个文本进行预测,并记录分类器是对还是错。然后,我们使用这些记录来计算一些指标,比如清单 3 中使用的准确性,给出了正确预测的百分比。CreateML 还提供了精度、召回和混淆矩阵,用于观察各个类的性能。评估是机器学习中一个非常广泛的话题,因为我们在这里仅仅触及了表面,我强烈推荐关于这个主题的进一步阅读。
评估过程是一个非常重要的步骤,因为它为如何改进模型提供了指导。例如,显示某个类的高错误率的混淆矩阵可能是一个警告,表明我们需要将该类的更多示例添加到训练集中。
最后,如果模型足够令人满意,我们就直接保存它。这是第 5 步,我们将.mlmodel
输出到文件系统。
CoreML 集成
现在我们已经有了模型文件,我们可以将它拖放到 Xcode 项目中。这一次不再有仅限 macOS 的限制,因为 CoreML 和 NLF 可以在所有苹果平台上使用。
Xcode 自动生成一个与.mlmodel
文件同名的MLModel
子类。因此,可以在独立模式下使用该文件,但是我们实际上对 NLF 如何与它集成感兴趣,如清单 4 所示。
Listing 4: Integrating NLF with CoreML model.
该代码的主要部分位于tagger
属性定义中,我们在前面部分创建的模型和一个定制标记方案之间建立了关联。这是必要的,这样标记者就知道何时调用我们的定制模型。
注意,我们不能直接使用 CoreML 模型。我们首先需要通过传递模型的 URL 将其包装在一个NLModel
对象中。至于自定义标记方案,我们只需要提供一个标识符。并在标签上把我们称之为setModels(_:forTagScheme:)
的人联系起来。
因此,当我们传递一个自定义方案来枚举标签时,标记器知道它应该使用自定义模型。该工作流程如图 2 所示。
Image 2: Text classification workflow.
最后,prediction(for:)
使用tags(in:unit:scheme:options)
来获得标签和范围元组的列表。因为我们正在评估整个文本,所以我们只为unit
参数传递.document
,导致 tagger 只返回一个标签,这个标签要么是POSITIVE
要么是NEGATIVE
。
清单 5 和输出 1 描述了分类器的用法和结果。请注意,它正确地标记了大多数评论,除了最后一条,它预测POSITIVE
甚至不是评论。由于机器学习的概率性质,这是意料之中的。为了解决这个问题,我们可以在我们的训练数据中增加非评论的例子,但是我会把这个留给读者。
Listing 5: Using the custom tagger.
Output 1: Print logs from Listing 5.
结论
在这篇博文中,我通过介绍 NaturalLanguage 框架的主要概念并展示如何构建一个定制的文本分类器,详细探讨了它。然而,还有更多需要了解的。在这个 Github 仓库中,你可以找到所有的文本分类器代码和许多其他使用内置函数的例子,组织数据集的不同方式,以及如何构建自定义单词标签(这与我在这里介绍的非常相似)。
我希望你喜欢这个,请离开你的掌声。再见!
[1]伯德、克莱因和洛珀(2009 年)。用 Python 进行自然语言处理:用自然语言工具包分析文本。奥莱利媒体公司。
[2]乔希特,H. (2017)。餐馆评论数据集。【在线】可在:https://www.kaggle.com/hj5992/restaurantreviews/version/1?[2019 年 4 月 14 日]
用 spaCy 和 Gensim 构建主题建模管道
像大多数编程语言一样,Python 有大量优秀的库和模块可供选择。当然,总的来说,这绝对是个好主意,但这也意味着有时模块之间并不总是能很好地配合。在这个简短的教程中,我将向您展示如何将 spaCy 与 Gensim 连接起来,以创建一个连贯的主题建模管道。
是啊,太好了。什么是主题模型?
好问题。我想过重写维基百科的定义,然后我想我可能应该给你维基百科的定义:
基本上,我们在寻找与讨论语料库的内容最相关的词或主题的集合。对于本教程,我们将使用潜在的狄利克雷分配(LDA)。
关于库的简短说明
对于那些不知道的人来说, Gensim 是主题建模的杰出库之一。同时, spaCy 是一个强大的自然语言处理库,在过去几年中赢得了许多崇拜者。
建造管道
首先,让我们导入我们的库:
这里需要注意一些事情:
- 如果你是数据科学的新手,特别是 Python,注意你必须 pip 安装 Gensim 和 spaCy。
- spaCy 有几种不同的型号可供选择。为了便于说明,我使用了大型的通用 web 模块,但是请使用对您有意义的内容。这需要通过命令行下载。查看模型页面点击了解更多信息。
- 如果你以前没有使用过 tqdm,它是一个允许你创建进度条来跟踪你的代码处理时间的模块。
- pprint 是为了让我们看的时候主题的格式更好一点。
我们将使用纽约时报头版文章的数据集。实际的文章本身在我们的数据框架的“内容”栏中。
现在是有趣的部分——我们将建立管道!默认空间管道的布局如下:
- 记号赋予器:将全文分解成单独的记号。
- 标记器:用词性来标记每个标记。
- 解析器:解析成名词块等。
- 命名实体识别器(NER) :标记命名实体,如美国
我们并不真的需要所有这些元素,因为我们最终不会传递 spaCy 的本机输出。文档由组成。令牌),因此标记的信息不会通过。无论如何,NER 将会对我们有用。现在我将把它们都留在这里,同样是为了说明的目的,尽管这会使管道的效率有所降低。
除了现成的功能之外,spaCy 还允许您使用自己的功能构建自定义管道,这就是我们将获得真正价值的地方。spaCy 有一个健壮的停用词列表和内置的 lemmatizer,但是我们需要将该功能添加到管道中。
对于那些不熟悉停用词的人来说,它们基本上是普通的词,不会真正为您的模型增加很多预测价值。例如,如果你不把单词“the”从你的语料库中删除,它可能会出现在你生成的每个主题中,考虑到“the”在英语中的使用频率。
请注意,没有一个词,即使是像“the”这样无处不在的词,会自动成为停用词。停用词通常由手头的特定任务决定。例如,我的任务是给《纽约时报》的头版文章做主题模型,我会用一些你在不同的上下文中不会想到的停用词。我可以将它们添加到 spaCy 的默认停用词列表中,如下所示:
我没有太疯狂地更新我的停用词,只是添加了一些精选的停用词,以向您展示我的思维过程:
- 《纽约时报》通常在文章中使用敬语,所以我们会得到很多我们不需要的“先生”、“夫人”等。
- 由于文章中会充满引用,我们也将“说”添加到我们的列表中。
- 许多文章都有署名,因为大多数是政治性的,“华盛顿”经常出现。
- 最后,spaCy 足够聪明,可以在词汇化时将一些缩写分解成它们的组成词(比如“不是”分解成“是”和“不是”),但“’ s”却不是这样。所以那个也要拿走。
- 另一个重要的警告是,我在这里没有做任何语法分析,这是当你在寻找文本中最常见的两个以上的单词短语时。在这种情况下,我可能会做出稍微不同的选择。
我们还需要对文本进行词汇化,也就是简单地将每个单词简化为它的词根。所以比如“going”和“goes”都简化为“go”。“更好”会被简化为“好”。
好了,现在让我们构建我们的管道,为词汇化和停用词移除添加函数:
这里重要的一点是要确保为最终输出返回 token.text。这将把每个令牌对象转换成一个字符串对象,因此它可以在 Gensim 中使用。
对于 Gensim,我最终需要创建一个列表列表:
下面是其中一篇文章在通过管道后的样子:
现在我们已经处理了文章,我们需要创建主题模型所需的输入:
最后,我们可以运行模型本身:
最后,一些示例主题(注意 pprint 以帮助格式化):
就是这样!希望对你有帮助!
用强化学习构建图灵机
超越人工算法设计
Alan Turing. A name on which the beginning of an era is manifested. The Turing Award remembers each year on him, lastly to our »Fathers of Deep Learning«.
老实说,在计算机科学中,没有比另一种排序算法更无聊的东西了。但是,这个时间排序只是一个通用图灵机用高级强化学习框架自我学习的表示。
Thanks, Alec Baldwin!
事实上,想出一个高效的优化算法需要大量的时间、资源和努力。而且,你永远不知道是否还有更高效的版本。然而,直到 20 世纪 60 年代,在快速排序发明之前,排序被认为是一个需要花费大量时间的问题。更不用说优化矩阵乘法的有趣竞赛了,如下图所示…
Matrix multiplication is known to be in O(n^ω). Currently: ω=2.3728639.
这就是强化学习的用武之地:它不仅能够作为一个通用的启发式工具包,而且它还可以作为一种在优化领域中找出一种数学证明的新算法的方法。
在下文中,我将用排序的例子向您展示后者。然而,请随意应用这个框架来满足你自己的需要,也许你能够通过在多项式时间内解决一个 NP 完全问题来用矛盾证明P = NP-问题:我很乐意得到那 100 万美元价格的一部分。;)
我为什么要烦恼?
优化发生在任何行业。从本质上来说,没有人不能优化的地方。
然而,至关重要的是,在大多数情况下,给定所需的数据,我们不仅不知道一个合适的算法来优化某个问题,而且我们依赖于一个密集的思考过程,结合好的想法和长时间的试错过程。
将优化留给一个通用的框架,这样定义一个目标(比如一个最优策略)就足够了,这不是很好吗?对这种系统的要求将服从于:
- 收敛到一个有效的广义解
- 暴力的显著优势
- 测试期间的时间效率。
通过利用强化学习——机器学习的最新进展——我们应该能够满足这些要求。如果我们在本文后面会谈到这一点。;)
好吧,很公平。有具体的用例吗?
当然可以。怎么样
- 物流 : 先期运输,库存管理,高效的卡车运输甚至一个充分利用的 uber/cab/bus 驱动。
- 架构 : 智能建筑如能源管理、电梯调度等。
- 电子商务 : UX/I 优化,电影推荐,动态定价等。
- 计算机科学 : 排序,神经组合优化(如旅行推销员),神经图灵机,数据中心冷却等。
深信不疑?现在关于技术…
为了便于说明,我选择学习一种排序算法,因为它很容易验证。然而,我的 GitHub 上提供的以下框架也可以用于其他优化技术:
通过强化学习实现任意优化问题的人类可读解决方案— moritzmoritz98/rlopt
github.com](https://github.com/moritzmoritz98/rlopt)
该算法
- 使用贝尔曼方程通过 Q 表学习任务。
- 在转换表中收集所有用于解决任务的状态。
- **通过
- a 最终状态机(fsm)图(自动)、
- a lambda 函数基于转换表(自动)或
- a 简化的 fsm 和代码版本(手动)重构**学习到的算法,用于算法加速。
在学习阶段,我们的目标是学习解决排序目标的算法。为此,我选择了表格 Q-learning 算法作为强化学习的一个特性,因为它易于标记和解释。
对于那些不熟悉表格 Q-learning 的人来说,本质上我们跨越了行中的列和状态上的动作,其中每个单元格通过采取其动作来确定该状态下的预期未来回报。下图描述了学习过程(也称为贝尔曼备份)。
The Bellman equation: Q(s,a) = r + γ*max Q(s’, a’)
如果有进一步的兴趣,看一看我的要点关于表格 Q-learning 是如何工作的。
在强化学习中,表格 Q-Learning 动作、状态和奖励将被定义。因为我们的目标是一个通用的算法,我们必须特别注意如何选择它们。
本质上,我们正在设计一个多头(两个头: I 和 J )图灵机 M=⟨ Q,γ,s,b,f,δ ⟩处理其磁带上的一个待排序列表 L 。与 Alex Graves 的神经图灵机等现有技术相比,在这项工作中,我使用了一种更简单的方法,代价是可定制性更低,但可解释性更高。
Example Turing machine to compute the truncated subtraction (“monus”), after John E. Hopcroft and Jeffrey D. Ullman (1979). In our case, the finite control table corresponds to the learned Q-table applied on a numerical list on the tape.
Q —状态,324
这些状态必须与磁带无关,因此,我将它们称为 RL 相关变量。
**L**[**I**] ≥ **L**[**J**]
∈ {0,1}- I 对比J😗*
**I**<**J**
|**I**==**J**
|**I**>**J**
∈{-1,0,1}** **I** == 0
|间|**I** == len(**L**)
∈ {-1,0,1}**J** == 0
| between |**J** == len(**L**)
∈ {-1,0,1}last_action
∈ {0,1,2,3,4,5}
具体来说,第一个状态是二进制的,而接下来的是三进制的,最后一个是六进制的,因此它们可以用一个布尔、三个三进制和一个六进制来表示,从而产生2¹×3³×6¹=324
不同的状态。实际上是非常低维的,但仍然足以成功地学习一个排序算法。
γ-磁带字母符号:动作,6
在代码中我使用了以下字母:
终止,
INCI ,
INCJ ,
RESETI ,
RESETJ ,
交换(值在 I 和 J )。
我们图灵机上基本上有两个头定位在 I 和 J 。通过增加其中一个或将其重置为零,机器能够在胶带内移动。交换是唯一的磁带修改动作,而终止模拟机器发出的暂停信号。有趣的是,希尔伯特的停顿问题也发生在我的一次实验中。
注意,你可以将磁带移动与实际程序分开,但是,出于实际考虑,我个人决定不采用这种形式主义。此外,为了确保暂停,'终止’动作也是一种变通方法。
本质上,只有“SWAP”和“ NOOP ”应该是γ的一部分。
s ∈ Q —初始状态
i=0
;j=0
;状态编码中的last_action = ‘*TERMINATE*’
→ -1|-1|0|?|0
。
b∈γ—空白符号
然而,为了简单起见,没有包括空白符号,可以考虑添加一个’ NOOP '操作。
f .⊆q——最终/接受国
这些是通过强化学习算法学习的。
δ:(q∖f)×γ→q×γ×{ i0,J0,I++,J++} —转移函数
通过算法了解到:来自 Q 的每个非最终状态与来自γ的动作配对具有确定性结果,该确定性结果包括通过采取来自γ的动作以及磁带移动(即 INCI 、 INCJ 、 RESETI 、 RESETJ 中的一个)而进入的下一个状态。
稍后,当我们讨论结果时,我将可视化这个转换表。
报酬
虽然不是图灵机的一部分,但奖励是通过强化学习来学习排序行为所需要的。为此,我决定让奖励函数尽可能稀疏和简单,以便为算法留出足够的自由空间:
- 300:如果以排序状态终止
- 10:如果交换正确
即使我没有指定禁止越界访问,程序自己也知道了这一点(一个无用的动作会减少可见的回报)。
还要注意,在这种情况下,相对于最终的表现,稀疏的奖励有它的机会,但是,它显著地增加了学习的时间,特别是在处理随机探索技术时。相反,通过应用直方图探索,每个状态根据其对算法的新颖性而被强制使用一个附加项。这种行为消除了奖励函数在开始阶段的稀疏性,并增加了达到全局最大值的机会。
在利用这种高级直方图探索技术之后,该算法能够在几个 1k 步骤中解决该列表,尽管如此,它仍然需要 5M 步骤来确定和优化其结果。
输入数据
关于输入数据的一个小注意:我生成了 3 到 6 个(870 个不同的)列表长度的所有可能排列,以减轻任何可以想到的过度匹配问题。
结果
恭喜你!你通过了艰难的技术部分。现在你想看到结果,对吗?
Looks brilliant, right?
好吧,你抓到我了。桌子看起来还不太好,对吧?
尽管如此,这里发生的事情还是值得一提。基本上,我在的 5,000,000 个步骤中学习了 (324,6) -Q 表,并通过预测列表(长度:3 到 6)的所有 870 个不同排列加上列表长度为 7 的 5,040 个排列来“测试”它,并在执行期间收集实际使用了哪些状态以及在这些状态下采取了什么样的动作。**
令人惊讶的是,没有很多唯一的状态(本质上是 20 ),而且,该算法确实很好地推广到了5040第七列表排列,没有任何错误。**
我真的想再次强调这一点:仅用 500 万步和所有无限可能列表的一个小子集的训练数据,结合一个简单的手工制作的奖励函数,我们就能够分别教会算法或宇宙图灵机如何排序。
你现在能想象一个人可以多么容易地把它推进到其他优化困境吗?
然而,我们仍然没有到达我们旅程的目的地,除非你能够从那些 20 行或最终从下面的 GraphViz 中阅读并主动理解我们学习的算法。
An astonishing transition graph, isn’t it?
完整的图灵机
在算法描述中,我省略了最终状态集和转移函数,因为它是由我们的 Q-learning 学习的。现在我们能够完全定义我们的图灵机了。
首先,最终的状态集很简单,它是状态16
,如转换图所示。
第二,我们的转换函数变得有点棘手,因为它比上面的图表形式上正确的版本更具可读性。尽管不再是一个有效的转换函数,我真的很高兴把它缩短为 4 种不同的情况,其中一个问号代表不相关。
Initial values: j = 0 and i = 1.
最后,从简化的转换表来看,实际上静态编码是相当直观的:我们这篇文章的主要目标!
**j **<-** *0*
i **<-** *1*
**while** action **is not** *TERMINATE*:
**if** i **==** *len*(l):
action **<-** *TERMINATE*
**elif** l[i] **>** l[j]:
movement **<-** *INCJ*
**elif** i **>** j:
action **<-** SWAP
movement **<-** *INCJ*
**else**:
movement **<-** *RESETJ*
movement **<-** *INCI***
老实说,直到现在我都不知道强化学习在这种情况下是否真的能够找到一种新的排序算法,或者只是一种非常不受欢迎的算法。我唯一可以肯定的是,它实际上可以 100%正确地对任意列表进行排序!
也就是说,我真的很感激,如果你能留下一个关于你看起来像什么样的算法的笔记!
性能赋值
由于行动空间有限,学习比 O(n ) 更好的东西从定义上来说是不可能的。然而,可以获得下面的算法复杂性。
非简化/原始版本 w.r.t .所需步骤数:
- 最小 :
O(0.5n² + 1.5n + 1)
- 最大值 :
O(n² + n + 1)
然而在简化版上,它更快并且(令人惊讶地)稳定:
- 最小值 / 最大值 :
O(0.5n² + 0.5n)
注意,通过从实际的与算法相关的动作字母表(即交换、终止、无操作)中分离带移动(即 I0 、 J0 、 I++ 、 J++ ),可以提高算法性能,因为对于一个动作,大多数时候还需要带上的移动,反之亦然然而,我将把它留给以后的工作。**
RLSort vs. BubbleSort
不幸的是,我的 RLSort 算法不如冒泡排序算法快,这可能是由于开销以及 for 循环在 CPU 上比 while 循环在中止条件下优化得更好。此外,较低的性能界限是与快速的 O(n) 相比没有竞争力的O(n)——验证检查:他对一个已经排序的列表做了很多无意义的事情!**
自己试试吧!
我真的很想鼓励你尝试一下,并把这个框架应用到你自己的想法中。如果您正在优化类似于列表的东西,只需要三个简单的步骤和几分钟的 CPU 时间:
你不仅会得到你的训练数据的优化列表的结果,你还会得到一个重构的现成可用的 lambda 函数!
当然,请让我知道你的用例!我真的很有兴趣听听你和我的框架的独特故事。😃
修正案
下面你会发现一个调用 RLSort sorting [6 4 3 7 0]
的例子,详细说明了学习排序算法是如何工作的:谜语玩得开心!
***2* *<<-* [6 4 3 7 0], **i**=*0*, **j**=*0*: *INCI*.
*64* *<<-* [6 4 3 7 0], **i**=*1*, **j**=*0*: *SWAP*.
*281* *<<-* [4 6 3 7 0], **i**=*1*, **j**=*0*: *INCJ*.
*134* *<<-* [4 6 3 7 0], **i**=*1*, **j**=*1*: *RESETJ*.
*227* *<<-* [4 6 3 7 0], **i**=*1*, **j**=*0*: *INCI*.
*64* *<<-* [4 6 3 7 0], **i**=*2*, **j**=*0*: *SWAP*.
*281* <<- [3 6 4 7 0], **i**=*2*, **j**=*0*: *INCJ*.
*136* *<<-* [3 6 4 7 0], **i**=*2*, **j**=*1*: *SWAP*.
*299* *<<-* [3 4 6 7 0], **i**=*2*, **j**=*1*: *INCJ*.
*134* *<<-* [3 4 6 7 0], **i**=*2*, **j**=*2*: *RESETJ*.
*227* *<<-* [3 4 6 7 0], **i**=*2*, **j**=*0*: *INCI*.
*65* *<<-* [3 4 6 7 0], **i**=*3*, **j**=*0*: *INCJ*.
*137* *<<-* [3 4 6 7 0], **i**=*3*, **j**=*1*: *INCJ*.
*137* *<<-* [3 4 6 7 0], **i**=*3*, **j**=*2*: *INCJ*.
*134* *<<-* [3 4 6 7 0], **i**=*3*, **j**=*3*: *RESETJ*.
*227* *<<-* [3 4 6 7 0], **i**=*3*, **j**=*0*: *INCI*.
*64* *<<-* [3 4 6 7 0], **i**=*4*, **j**=*0*: *SWAP*.
*281* *<<-* [0 4 6 7 3], **i**=*4*, **j**=*0*: *INCJ*.
*136* *<<-* [0 4 6 7 3], **i**=*4*, **j**=*1*: *SWAP*.
*299* *<<-* [0 3 6 7 4], **i**=*4*, **j**=*1*: *INCJ*.
*136* *<<-* [0 3 6 7 4], **i**=*4*, **j**=*2*: *SWAP*.
*299* *<<-* [0 3 4 7 6], **i**=*4*, **j**=*2*: *INCJ*.
*136* *<<-* [0 3 4 7 6], **i**=*4*, **j**=*3*: *SWAP*.
*299* *<<-* [0 3 4 6 7], **i**=*4*, **j**=*3*: *INCJ*.
*134* *<<-* [0 3 4 6 7], **i**=*4*, **j**=*4*: *RESETJ*.
*227* *<<-* [0 3 4 6 7], **i**=*4*, **j**=*0*: *INCI*.
*70* *<<-* [0 3 4 6 7], **i**=*5*, **j**=*0*: *RESETJ*.
*232* *<<-* [0 3 4 6 7], **i**=*5*, **j**=*0*: *TERMINATE*.
Sorted list in *28* steps: **[0 3 4 6 7]****
利用计算机视觉构建视频搜索引擎
使用萨尔萨舞视频的案例研究
介绍
在图像分类任务取得前所未有的进展之后,计算机视觉领域的自然进展是朝着视频和视频理解的方向发展,尤其是它如何与识别人类对象和活动相关联。正在这一领域建立一些数据集和基准。
与此同时,与 2D 图像相关的计算机视觉任务正在取得进一步进展,如细粒度分类、图像分割、3D 图像构建、机器人视觉、场景流量估计和 人体姿态估计 。
作为我在 Metis bootcamp 的最终数据科学项目的一部分,我决定将这两个平行的领域结合起来——具体来说是视频和人体姿势估计——以创建一个基于内容的视频搜索引擎。由于将 2D 人体姿势估计应用于视频搜索是一个“没有概念证明”的新颖想法,所以我通过选择 Salsa 舞蹈视频的单个表演者、固定位置单个摄像机视频片段来简化我的方法。
录像
仅次于谷歌的第二大搜索引擎 YouTube 的用户每天观看超过 10 亿小时的视频。世界上最受欢迎的社交网站脸书的用户每天观看大约 1 亿小时的视频!!这些平台对向用户提供搜索和发现感兴趣的相关内容的工具感兴趣。
这些平台提供的搜索工具主要使用视频的元数据(位置、时间、内容创建者等。)、标题、描述、抄本(用户创建的或机器从音频生成的)、用户评级、用户评论等。检索“相似”的结果。这些搜索工具不会浏览视频本身的实际内容。对于搜索来说,视频是不可浏览或可索引的。
视频中的视觉特征太多了,索引它们的计算成本很高,检索起来又太慢。举个例子,如果你要搜索展览 1 中萨尔萨舞者的舞步,目前在 YoutTube 这样的平台上还没有可供用户使用的工具。salsa 步骤没有文本上下文来执行搜索。
Exhibit 1: Salsa Dance Video (Credit: World Salsa Summit 2016. Dancer: Valentino Sinatra, Italy)
作为我的 Metis 项目 5 的一部分,我构建的视频搜索引擎已经索引了来自 YouTube 的大约 70 分钟的萨尔萨舞视频,并将返回一个匹配,如下面的图表所示。Yeifren(中)和 Adriano(右)表演的萨尔萨舞步与 Valentino(左)相似。在这些片段中,表演者走回到舞台中央,并面向舞台左侧进行多次转身。
Exhibit 2: A search results from the Video Search Engine (Credit: World Salsa Summit 2016. Dancers: Valentino Sinatra, Italy; Yeifren Mata, Venezuela; Adriano Leropoli, Montreal)
Valentino(左)和 Yiefren(中)之间的一个非常微妙的动作是,他们都触摸/撞击他们的膝盖/腿!!它在剪辑中的时间是关闭的,但模型拿起这个!!这些片段我看了好多天都没注意到!我认为这是一个迷人的结果。
方法学
建立视频搜索引擎有两个步骤。第一步是下载和处理视频素材,并通过 OpenPose 进行特征提取。OpenPose 是卡内基梅隆大学研究人员(曹哲等人)开发的一种人体姿态估计算法。更多关于 OpenPose 的内容将在后面的章节中介绍。第二步是模型构建和测试查询指标。使用矩阵分解方法(PCA,LSA,NMF)建立模型。欧几里德距离和余弦相似性作为查询度量被测试。作为第二步的一部分,还进行了特征工程/特征表示练习,测试了每一帧的展平特征和特征的 Hu 矩。
所获得的“特征表示/维数减少/查询度量”的最佳组合是通过使用具有 LSA 和余弦相似性的简单平坦姿态估计。
Exhibit 3: Methodology for building a video search engine
数据
这个项目的数据来自处理 YouTube 萨尔萨视频(链接如下)。
为什么是莎莎?
除了是一种具有挑战性的舞蹈形式,涉及微妙和快速的动作,YouTube 上的许多频道还提供了独舞者的视频。对于多人视频帧,用于我的项目的特征提取的 Zhe Cao 等人的姿态估计是快速和准确的,但是那些算法不能逐帧跟踪一个人。当视频中的人交换位置时,这就产生了一个问题——比如一对夫妇在做转体/托举等动作。为了避免这种情况并简化我的问题,我选择了独舞视频。
我考虑过的另一种舞蹈形式是芭蕾。然而,女舞者的服装(读芭蕾短裙!)会导致假阳性,如在芭蕾中常见的某些舞蹈姿势的膝盖。
我收集的萨尔萨舞片段来自以下四个总时长为 160 分钟的视频。这个镜头被处理到 70 分钟,并被格式化成 3 秒钟的剪辑。处理后的视频由大约 30 名艺术家的大约 58 场表演组成。
这些视频是:
- 【https://www.youtube.com/watch?v=4nHElVbT3HY
- https://www.youtube.com/watch?v=ITNiqNcl6Mw
- https://www.youtube.com/watch?v=L5mqL7ADEsY
- https://www.youtube.com/watch?v=M_rPhEjym1o
总而言之,所用的视频包括:
- 单身舞者
- 固定位置的单个摄像机的镜头(没有移动的摄像机!).只有摄像机角度和缩放级别会反映在素材中。
- 视频帧速率从原来的 24 帧/秒降低到 8 帧/秒。
- 固定屏幕分辨率为 640(宽)X 360(高)像素。
- 大约 70 分钟的经过处理的镜头,并格式化为 3 秒钟的剪辑(超过 1,400 次观察)。
OpenPose:实时 2D 多人姿态估计算法
人体姿态估计是识别图像或视频中人体解剖“关键点”的计算机视觉问题。在过去的几年里,许多研究人员取得了令人难以置信的进展,并展示了实时性能⁴.卡内基梅隆大学机器人研究所的曹哲等人使用“自下而上”的方法赢得了首届 COCO 2016 关键点挑战。他们使用安装了 GPU 的笔记本电脑实现了实时速度。
姿态估计用于对图像/视频剪辑中的人类活动进行分类。它们主要应用于 2D 图像,但从未用于视频搜索目标。下面是 OpenPose 如何在 2D 图像中识别一个人的 18 个关键点的示例(为了简单起见,只显示了 14 个关键点)。
Exhibit 4: OpenPose on a 2D Image Identifying Keypoints
当识别出视频中每一帧的关键点时,结果将如下面的展示 5 & 6 所示。
Exhibit 5: OpenPose on a Salsa Dance Video Identifying Keypoints
Exhibit 6: Spatio-temporal features extracted from OpenPose
结果
下面显示了索引 salsa 视频并使用余弦相似度进行搜索的一些结果。右上方的视频是搜索片段,搜索结果要从左到右读,然后从上到下。
请注意,搜索引擎索引在检索舞者正走向他们的位置的视频剪辑时是充分有效的(展示 7 )。这本身就是一个有趣的结果,并展示了这种索引方法如何用于视频编辑。即使对于 2.5 小时的“小的”未处理的镜头,手动编辑这些剪辑也将花费数小时,而搜索引擎运行(后期索引)不到一秒钟,并且能够找到镜头中所有这种剪辑的时间戳。这使得视频编辑更加自动化和快速。
Exhibit 7: Search using a Clip of Dancers Making an Entry
更多的结果
Exhibit 8: Dancers Performing Turns
Exhibit 9: Turn while bearing to the left (& swoop down!)
Exhibit 10: Moving to the left and a turn
其他应用
像这样的视频搜索引擎有很多应用。下面列出了一些例子:
Exhibit 8: Other Applications for Video Search Engine
对于像 YouTube 和脸书这样的平台如何在他们当前的网站上使用视频搜索,我有更多的想法。一些概念性的想法如下所示:
Exhibit 9: A Conceptual Layout of Video Search on YouTube Salsa Videos
Exhibit 10: A Conceptual Layout of Video Search on YouTube Football Game Videos
参考
- 南 Abu-El-Haija 等人 YouTube-8M:大规模视频分类基准。 arXiv:1609.08675be ,2016
- 曹哲等人,利用局部亲和场的实时多人 2D 姿态估计(2017) ,CVPR
- 曹哲等, Github to OpenPose (2017)
- 德里克·姆维蒂,2019 年人体姿势估计指南 (2019),中等文章
最初发布于https://mlbhanuyerra . github . io。
利用深度学习构建声音情感传感器
教机器更好地理解人类交流
人类的表达是多方面的,复杂的。例如,说话者不仅通过语言交流,还通过节奏、语调、面部表情和肢体语言交流。这就是为什么我们更喜欢面对面而不是电话会议来举行商务会议,也是为什么电话会议比电子邮件或短信更受欢迎。我们离得越近,通信带宽就越多。
语音识别软件近年来有了很大的进步。这项技术现在在识别语音并将这些语音拼凑起来以再现口语单词和句子方面做得非常好。然而,简单地将语音翻译成文本并不能完全概括说话者的信息。除了面部表情和肢体语言,与音频相比,文本在捕捉情感意图方面的能力非常有限。
最初,我选择构建一个声音情感传感器,因为这似乎是一个有趣的工作项目。尽管对这个问题想得更多,我意识到通过音频进行情感感应有一些真正有趣的应用。想象一下,如果你的智能家居设备可以播放符合你情绪的歌曲,例如当你悲伤时播放振奋人心的歌曲。客户服务部门可以使用情绪检测来培训员工,或者测量客户在服务电话过程中是否变得更开心。
数据
我用来构建我的情感分类器的数据集是拉夫德斯、苔丝和 SAVEE ,它们都是免费向公众开放的(SAVEE 需要一个非常简单的注册)。这些数据集包含七个常见类别的音频文件:中性、快乐、悲伤、愤怒、恐惧、厌恶和惊讶。我总共获得了 30 名男女演员制作的 4500 个带标签的音频文件中超过 160 分钟的音频。这些文件通常由男演员或女演员说的带有特定情感意图的简短短语组成。
Actors for the SAVEE dataset
特征抽出
接下来,我必须找到可以从音频中提取的有用特征。最初我想用短时傅立叶变换来提取频率信息。然而,对该主题的一些研究表明,当涉及到语音识别应用时,傅立叶变换是相当有缺陷的。原因是尽管傅立叶变换是声音极好的物理表示,但它并不表示人类如何感知声音。
Raw audio waveform. In this form it is useless for classification.
从音频中提取特征的更好方法是使用 Mel 频率倒谱系数,简称 MFCCs。这里提供了一个很好的解释,说明 MFCCs 是如何从音频中获得的。MFCCs 试图以更符合人类感知的方式来表示音频。
为了从音频中导出 MFCCs,需要决定使用多少频率仓以及分段的时间步长有多宽。这些决定决定了输出 MFCC 数据的粒度。语音识别应用的标准实践是应用 20Hz-20k Hz 之间的 26 个频率仓,并且仅使用前 13 个用于分类。大多数有用的信息都在较低的频率范围内,包含较高的频率范围通常会导致性能下降。对于时间步长,10 到 100 毫秒之间的值是常见的。我选择用 25 毫秒。
Mel filter banks used to bin audio frequency content.
导出的 MFCCs 可以绘制在热图上,并用于可视化音频。这样做并不能揭示情感类别之间的任何明显差异。这与其说是因为缺乏模式,不如说是因为人类没有受过训练,无法从视觉上识别这些微妙的情感差异。然而,从这些热图中很容易看出男性和女性说话者之间的差异。
Visualized MFCCs for Happy Male and Female Speakers. Women tend to have stronger high frequency components in their voices, as shown by the brighter colors towards the top of the heatmap.
训练卷积神经网络
通过推导 MFCCs,音频分类问题实质上被转化为图像识别问题。因此,在图像识别领域非常有效的工具、算法和技术在音频分类中也非常有效。为了解决情感分类问题,我选择使用卷积神经网络(CNN ),因为这些网络已经被证明在图像和音频识别方面都是有效的。
在训练 CNN 之前,我将数据集中的文件随机分配给 80/20 分割的训练集或测试集。然后,我对训练文件执行了一些预处理步骤。每个文件的流程如下:
- 切断所有的沉默。
- 随机选择若干个 0.4s 窗口。
- 确定每个窗口的 MFCCs,产生 13×16 阵列。
- 将 MFCCs 调整到 0 到 1 的范围。(这一步超级重要!它使模型无法适应录音的音量水平。)
- 将每个窗口与源文件的情感标签相关联。
完成预处理后,我生成了 75,000 个标记为 0.4s 的窗口用于训练,每个窗口由一个 13x16 的数组表示。然后我用这些数据训练了我的 CNN 25 个时代。
模型检验
为了对测试集上的模型进行基准测试,我应用了一个类似于用来创建训练数据的流程工作流。测试集中每个文件的流程是:
- 切断所有的沉默。
- 创建步长为 0.1s 的“滑动”0.4s 窗口。(例如,第一窗口的范围从 0.0s 到 0.4s,第二窗口的范围从 0.1s 到 0.5s,等等。)
- 确定每个窗口的 MFCCs,范围从 0 到 1。
- 对每个窗口进行分类并返回 softmax 输出。
- 聚合每个窗口的预测。
- 最终预测是聚合后的最大类。
将这个过程应用于测试集中的所有 889 个文件,产生了 83%的总体准确率。我非常怀疑自己能否以接近 83%的准确率给这些文件贴上标签。下面的条形图显示了每种特定情绪的准确度。
外卖食品
这篇博文可能会让构建、训练和测试模型看起来简单明了。我可以向你保证,事实并非如此。在达到 83%的准确率之前,有许多版本的模型表现相当差。在一次迭代中,我没有正确地缩放我的输入,这导致了测试集中的几乎每个文件都被预测为“令人惊讶”。那么我从这次经历中学到了什么呢?
首先,这个项目很好地展示了简单地收集更多的数据如何能够极大地改善结果。我第一次成功的模型迭代只使用了 RAVDESS 数据集,大约 1400 个音频文件。仅用这个数据集我能达到的最高准确率是 67%。为了达到 83%的准确率,我所做的就是将数据集的大小增加到 4500 个文件。
其次,我了解到对于音频分类来说,数据预处理是至关重要的。原始音频,甚至短时傅立叶变换,几乎完全没用。我从惨痛的教训中认识到,适当的缩放可以成就一个模型,也可以毁掉它。无法消除沉默是另一个简单的陷阱。一旦音频被适当地转化为信息特征,建立和训练深度学习模型就相对容易了。
总而言之,为语音情感检测建立一个分类模型是一次富有挑战性但却值得的经历。在不久的将来,我可能会再次访问这个项目,以扩大它。我想做的一些事情包括:针对更广泛的输入测试模型,使模型适应更广泛的情绪,并将模型部署到云上进行实时情绪检测。
附录
无需编码,在 5 分钟内构建一个 WhatsApp 聊天机器人
聊天机器人正在兴起。已经有很多或多或少有用的聊天机器人,其中大多数基于 Facebook Messenger。然而,如果你居住在欧洲、印度、南美或非洲,很可能你正在使用 WhatsApp 作为你的首选沟通渠道( stats )。很好,那么对于 WhatsApp 上的聊天机器人来说,什么是有用的案例呢?由于 WhatsApp 目前专注于客户服务,正如他们在指南中所描述的那样,让我们构建一个机器人来帮助预先确认服务查询。
前提条件是什么?
通过 WhatsApp 商业应用编程接口商业使用 WhatsApp 的先决条件是要么直接从 WhatsApp 申请一个自己的账户,这目前极难获得,要么从官方解决方案提供商那里购买访问权限。我可以推荐第二个选项,因为从技术角度来看设置整个项目是很有挑战性的。顾名思义,它只是一个没有图形用户界面的 API。
当你有了这样一个商业账户,你可以给它添加一个电话号码,开始发送和接收信息。WhatsApp 的一个商业简介是这样的(德语):
建造机器人
在这个小展示中,我正在使用 MessengerPeople 的平台来构建聊天机器人。MessengerPeople 是 WhatsApp 官方解决方案提供商之一,为人工代理引导的聊天提供票证系统,并为自动化对话提供图形聊天机器人构建器界面。
简单说明一下:我们的目标并不是构建一个超级智能的数字助理,它可以回答你能想到的每一个问题!我们的目标是构建一个小助手,它可以服务于一个清晰和狭窄定义的用例。这就是为什么我们必须让机器人来主导对话。机器人总是需要给出对话如何进行的提示。最后但同样重要的是,让我提一下,条条大路通罗马。这只是一个功能非常有限的小例子。
好了,理论够了!让我们开始建设吧!MP 的聊天工具界面非常简单和直观。在左侧,你添加所谓的用户问题,这些是机器人应该做出反应的关键词和短语。在右边,你添加相应的机器人答案。
让我们以欢迎词和介绍开始。我们正在添加一些关键字,如你好,嗨,你好,作为用户问题。您还可以通过添加拼写错误的单词和短语来处理错别字。然后我们让机器人介绍自己,并立即给出选项供选择。WhatsApp 提供的格式选项非常有限。我们可以用两颗星把一个单词括起来,使它变得粗体。当然,我们也可以添加表情符号。
该机器人给用户 3 个选项:产品,新闻和与代理聊天。我们还增加了简化回复过程和优化用户体验的捷径。所以,现在我们可以将相应的对话添加到这三个选项中。先说产品信息。除了快捷方式“1”,我们还添加了关键字“产品”和“产品”。当然,你可以在这里添加任意多的同义词或短语。
然后我们以同样的方式为新闻部分添加第二个对话,快捷键为“2”。我们也可以在机器人回答中放一个链接到一个有更多细节的网页。一个链接缩短器将自动缩短所有链接,并使跟踪点击成为可能。请注意,你不能在 WhatsApp 中使用 HTML,所以你不能添加和样式,例如一个标签。唯一的办法就是加一个纯 URL。
对于快捷键为“3”的第三个对话,我们在机器人回答中增加了一个特殊动作“移交给代理”。这里发生了什么?到目前为止,整个对话是完全自动化的。此时,聊天机器人停止工作,并为人工代理打开一张票。我们称之为混合机器人。然后,代理可以拿起票据并手动回复客户。还可以定义自定义技能,如“销售”、“支持”、“人力资源”等。因此,机器人可以自动将请求路由到正确的部门。
我们的整个配置如下所示:
现在是考验我们小伙伴的时候了!我使用 WhatsApp Web 来开发和测试聊天机器人,因为这是一种在浏览器中使用 WhatsApp 的便捷方式。
很好,不是吗?最后,让我们看看这个请求到达的后端票务系统。这是代理视图:
当然,聊天机器人的功能远不止这种简单的问答游戏。例如,它可以收集数据并存储到用户配置文件中。它可以通过 API 从外部数据源获取信息。它可以处理依赖性并做出相应的响应,例如,“这个用户是否已经提供了他的名字,然后用他的名字称呼他”或“用户用 A 回答了问题 1,所以继续问题 4”。它还可以以定义的延迟发送多条消息,当然,它还可以发送图像、视频、音频和 pdf 等媒体。
用 WiGLE 和 R 建立一个你周围网络的 WiFi 点地图
Map generated using the below code
探索我们周围的世界总是很有趣——当你以数据科学家的 视角 探索世界时,那就更有趣了。
在此分析中,我们将确定我们周围开放的 WiFi 网络,并将它们映射到交互式地图上。
工具包
- wig lrR 包,用于与 WiGLE API 接口
- 用于数据收集的 WiGLE API
- tidy verseR 包用于数据分析&操纵
- 传单 R 用于绘制互动地图的包(界面为 fleet . js)
wiglr 是由 Bob Rudis 开发的 R 包,他的名字是 hrbrmstr 。他是 R 的超级用户之一,同时也积极参与开发新的 R 包并写博客。
先决条件
在我们开始编码之前(不管怎样,只是一堆代码),我们需要做两件重要的事情。
安装所有需要的 R 包
wiglr
在 CRAN 上尚不可用,因此必须根据您的喜好从 Github/Gitlab 安装。下面是代码,你如何使用devtools
包从 Github 安装它
**# install.packages('devtools') # install devtools if not available
devtools::install_github(“hrbrmstr/wiglr”)**
上面提到的其他实用程序包:
**install.packages('tidyverse') # ideally dplyr is what we'd need
install.packages('leaflet')**
正在获取 WiGLE API 令牌
- 在 wigle.net创建您的账户
- 登录后,从你的账户页面获取你的 API 令牌(点击显示令牌)
- 打开 RStudio,将您的( “Encoded for use” ) API 令牌设置为环境变量,如下所示:
**Sys.setenv(WIGLE_API_KEY = "here_goes_my_encoded_token")**
至此,我们已经具备了先决条件,可以开始了!
起飞
加载库
和其他 R 项目一样,第一步是加载所有需要的库。
**library(wiglr)
library(tidyverse)
library(leaflet)**
我们周围景点的一般统计数据
在这一步,我们将给出边界框(以纬度和经度的形式)并提取我们周围的 WiFi 网络的细节。
**# This is for Bengaluru, India - change it according to where you're
wifi_box <- wigle_bbox_search(12.7585, 13.1105, 77.5477, 77.8431)**
样本输出
**> wifi_box
$success
[1] TRUE$totalResults
[1] 252098$search_after
[1] 552772$first
[1] 1$last
[1] 100$resultCount
[1] 100$results
# A tibble: 100 x 25
trilat trilong ssid qos transid channel firsttime lasttime lastupdt housenumber
* <dbl> <dbl> <chr> <int> <chr> <int> <chr> <chr> <chr> <chr>
1 12.9 77.6 oakd… 0 201701… 6 2017-01-… 2017-01… 2017-01… 305, 1st M…
2 12.9 77.6 Sham… 0 201606… 11 2001-01-… 2016-06… 2016-06… ""
3 13.0 77.7 PPAN… 0 201303… 1 2013-03-… 2013-03… 2013-03… ""**
它只是一个列表(JSON 响应的输出),包含实际结果和一些元数据。正如您在上面的示例输出中看到的,我们的关注点是results
。
巡航
说完了,就该画我们承诺要画的交互图了(本文开头)。
下面是我们从wifi_box
中取出results
并创建一个新变量labs
的代码(它将在弹出标签的后续行中使用)。这些数据被进一步输入leaflet()
进行地图创建,我们添加一个基础图层,然后在给定的经度和纬度添加圆形标记。
颜色编码是为了让我们表示 WiFi 网络的安全性是无(无安全性)—红色、未知— 黄色和其他(如 WEP、WPA) —蓝色。
**wifi_box$results %>%
mutate(labs = sprintf("SSID: <code>%s</code><br/>Encryption: %s", ssid, encryption)) %>%
leaflet() %>%
#addTiles() %>%
addProviderTiles(providers$Stamen.Toner) %>%
#addProviderTiles(providers$OpenStreetMap, options = providerTileOptions(opacity = 0.4)) %>%
addCircleMarkers(~trilong, ~trilat,
radius = 10,
popup = ~labs,
color = ifelse(
wifi_box$results$encryption == "none","red",
ifelse(
wifi_box$results$encryption == "unknown",
"yellow","blue")))**
这给了我们这个漂亮的情节(当你在 RStudio 中绘图时,它实际上是交互式的)
你可以使用不同的names(providers)
值来选择你喜欢的基层/地图主题。
安全着陆
这样,我们就成功地绘制了我们周围的 WiFi 网络——以及它的 SSID 和安全加密。这在检测和通知可能被恶意方攻击的没有安全性的开放网络时非常有用。
参考
- 在开发中:使用 wiglr 进入热点
- 我的 github 上使用的完整代码
- 威格尔官方网站
结束语: 如果您对开源感兴趣,请 贡献此包 以成功部署在 CRAN 上,使其能够面向大众
使用 AWS Lambda 和 API Gateway 构建葡萄酒推荐 API
在 RoboSomm 系列的一个章节中,我们构建了一个葡萄酒推荐模式 l。在下面的文章中,我们将探索如何将它转化为一个 API,该 API 可以从 180,000 种葡萄酒的存储库中返回葡萄酒推荐。我们将关注一个特定的用例:返回给定描述符列表的葡萄酒推荐列表。
事实上,让我们说,我的心情是一个大胆的红葡萄酒与黑莓,蓝莓,巧克力和甘草的香味。我希望这款酒单宁含量高,酒体饱满。现在告诉我:什么葡萄酒最符合这个描述?
我们将使用 AWS 服务套件来确保返回葡萄酒推荐的过程完全是无服务器的。
勾画出解决方案
构建 API 的第一步是勾画出提交带有葡萄酒描述符的 POST 请求和返回葡萄酒建议列表之间所需的所有步骤。
虽然在本文中我们将重点关注 Lambda 和 API Gateway 的使用,但我们也将利用 S3 和 SageMaker 中的一个模型端点。概括地说,该流程如下所示:
首先,让我们把注意力集中在 Lambda 函数上,我们将使用它来运行上面概述的过程。当设置 Lambda 函数时,我们需要小心选择一个执行角色,它将允许我们与 S3 和 SageMaker 进行交互。我们需要添加到 IAM 角色的 JSON 如下:
{
“Sid”: “VisualEditor1”,
“Effect”: “Allow”,
“Action”: “sagemaker:InvokeEndpoint”,
“Resource”: “*”
},
{
“Sid”: “VisualEditor2”,
“Effect”: “Allow”,
“Action”: “s3:*”,
“Resource”: “*”
},
现在来看函数本身。因为我们将从函数内部的查找表中检索值,所以我们希望使用 Pandas 库。我们还将处理单词嵌入形式的向量,为此我们希望使用 Numpy。不幸的是,这些库不是 AWS Lambda 的原生库。为了利用这个功能,我们必须创建一个包含 Lambda 函数以及 Pandas 和 Numpy 的 Linux 二进制文件的 zip 文件。本文更详细地概述了如何做到这一点。
Lambda 函数的第一部分如下所示:
除了导入必要的包,我们还定义了 lambda_handler 函数。这是每当调用 Lambda 函数时运行的函数。它处理的事件是 json 文件,其中包含我们的示例葡萄酒描述符列表:
{ “实例”:[“大胆”、“黑莓”、“蓝莓”、“巧克力”、“甘草”、“高单宁”、“醇厚”]}
为了安全起见,这个 json 文件被解码和编码,以确保它具有适当的(json)格式。
接下来,我们希望从 S3 的查找表中检索这些描述符的 IDF 加权单词嵌入。该查找表(S3 的 CSV 文件)的生成过程在本文的中有详细描述。与本文中描述的过程的唯一区别是,我们将嵌入到查找文件中的每个单词乘以它的逆文档频率(IDF)分数。我们希望 Lambda 函数的这一部分获取以下单词嵌入,并将它们存储为一个列表。
代码如下:
将单词嵌入转换成单个葡萄酒嵌入的过程相当简单。通过取它们的平均值,我们最终得到一个单一的词频率逆文档频率(TF-IDF)加权向量。
现在我们已经嵌入了我们的葡萄酒,我们准备检索我们的推荐。为此,我们将调用一个 AWS SageMaker 端点,该端点托管一个 Scikit-Learn Nearest Neighbors 模型,该模型专门为此进行了培训。本文更详细地解释了这样做的过程。
我们的最近邻模型端点返回我们的葡萄酒推荐的索引,以及这些推荐和我们的输入葡萄酒嵌入之间的余弦距离。最终,我们希望我们的 Lambda 函数返回这些葡萄酒的名称和风味特征。这些附加信息存储在 S3 的 CSV 文件中,需要检索:
现在我们已经设置了 Lambda 函数,我们可以将注意力转向创建 API 了。这里详细描述了建立我们的 API 的步骤。我们创建了一个 POST 方法,它将接受一个带有我们的 wine 描述符的 JSON 文件,调用我们的 Lambda 函数,并返回一个推荐列表。
我们可以通过使用 Postman 运行测试来确保我们的 API 正常工作。
推荐的葡萄酒似乎主要包括坦普拉尼洛和赤霞珠。该去购物了!
使用熊猫构建远期汇率协议的零曲线
Photo by Markus Spiske on Unsplash
在金融界,如果你想给一种工具定价,并计算出从 t0(现在)到 t(n)的未来价值,你需要使用现货收益率曲线。在专业交易者中,现货收益率曲线被称为零曲线。
如果你现在有 1000 美元可以投资,你可以去银行存一张一年期的存单,很容易得到即期汇率。CD 利率是你的基准。如果你投资了回报比 CD 低的东西(假设风险相对相同),你知道你最好把钱放在 CD 里。这很容易。
但是,如果你知道从现在起一年后你会有 1000 美元,并且想投资一年,那该怎么办呢?你不能走进一家银行,试图锁定一年后的利率。银行不会也不可能告诉你未来的利率是多少。也许银行的不同部门可以,但不是你走进的那个部门,因为目标客户是不同的。
事实上,银行确实知道未来的利率是多少。这就是 FRA。
FRA,或未来利率协议,是双方之间的协议,如果你借出你的钱,你将在期限结束时获得指定的利息和本金。
在本文中,我们将使用 Pandas 建立一个基于 FRAs(远期利率协议)的零曲线。有了这条零曲线,你可以很容易地对某样东西进行定价,从一天到未来十年的任何天数。
为简单起见,我们使用的 FRA 是一年期。实际上,欧洲美元期货(FRA)可以是一个月也可以是三个月。请注意,所有的利率,无论其期限,总是被称为年利率。
import pandas as pdfra = pd.DataFrame({
'Description': ['2019', '2020', '2021', '2022', '2023'],
'start_date': pd.date_range('2019-01-01', periods=5, freq='YS'),
'end_date': pd.date_range('2019-01-01', periods=5, freq='Y'),
'rate': [2.2375, 2.4594, 2.6818, 2.7422, 2.6625]
})
这里我们有截至 2019 年 1 月 1 日的 5 年 FRA。如果你在 2019-01-01 存入这笔钱,你将在第一年年底获得 2.2375%的利息,第二年获得 2.4594%,以此类推。
我们知道每年的利率,但如果我们想知道最终的复利,我可以向你保证答案不仅仅是把利率相加。下面我们要做的是,年复一年的复合增长。我们来分解一下步骤。
步骤 1:计算从 M 到 N 期间的增长,因此,mxn_growth
。
fra['mxn_growth'] = 1 + fra['rate'] / 100
第二步:复合增长是前期复合增长乘以本期增长。因为增长是从时间 0 开始复合的,所以我们称之为0xn_growth
。
fra['0xn_growth'] = fra['mxn_growth'].cumprod()
步骤 3:每个 FRA 都是一年期,但增长是前几年的复合增长。
fra['years'] = 1
fra['cummulative_years'] = fra['years'].cumsum()
第四步:最后我们知道总年数cummulative_years
的总增长0xn_growth
。我们只需要把它标准化为年率。正如我们前面指出的,利率总是被称为年利率。
fra['zero_rate'] = fra['0xn_growth'] ** (1 / fra['cummulative_years']) - 1
这就是了。你想知道 2020 年末你的回报是多少,2.3484%是你的利率。
等等!你可能会说,这太简单了。
是的,事实上,请记住,我们有意将每个 FRA 设为一年期。如果它们像欧洲美元期货一样是一个月或三个月呢?你会怎么做?我会把这个挑战留给你。如果你想直接得到答案,请阅读复利的数学没有你想象的那么简单(困难)。
这篇文章也有 Juypter 笔记本格式。随便玩吧。
构建一个能读懂你思想的人工智能
基于脑电数据的精神状态分类机器学习
(Source: Shutterstock)
能读懂你思想的人工智能
这听起来可能像科幻电影中的反面未来的情节。然而,随着最近的进展,这项技术正在从科幻小说向纯粹的科学飞跃。
大脑活动的自主非侵入式检测在多个领域都有潜在的用途,比如人类机器人互动和精神保健。它可以在用户和设备之间提供一个额外的交互维度,并使不依赖于语言交流的有形信息能够被导出。
这样的创新也意味着更好的脑机接口。这将为人机交流开辟全新的平台,包括帮助有身体或精神障碍的人。脑机接口可以让瘫痪的人移动机械臂,或者让脊髓损伤的人控制电动轮椅。
随着低成本脑电图(EEG) 设备的日益普及,脑波数据对于消费行业和研究来说变得越来越实惠,从而引入了在不需要专家在场的情况下进行自主分类的需求。
在本文中,我将介绍一个如何使用机器学习来分析大脑活动的示例。通过商业可用设备的脑电图记录,我展示了机器学习模型如何用于预测受试者的相应精神状态。
用于精神状态分类的机器学习
记录高质量的脑电图数据并不容易,除非你隶属于一个进行这种实验的实验室。然而,我最近看到了一篇有趣的文章,Jordan J. Bird、Luis J. Manso、Eduardo P. Ribiero、Anikó Ekárt 和 Diego R. Faria 撰写的文章“使用基于 EEG 的脑机接口进行精神状态分类的研究”。幸运的是,他们已经公开分享了他们研究中使用的数据,供其他人进行实验。在我看来,特别有趣的是消费级设备的使用,你只需花几百美元就可以在亚马逊上订购。他们研究中的数据记录和处理方法将在下一节介绍。
实验细节
这项研究通过一个商用 MUSE 脑电图头带采用了四个干式颅外电极。这是一种可穿戴的大脑传感设备,通过 4 个脑电图 (EEG)传感器测量大脑活动。
为了唤起不同的心理状态,实验使用了下表中显示的精选电影片段,代表正负价
Film Clips used as Stimuli for EEG Brainwave Data Collection. Source
从两个受试者(1 男 1 女,年龄 20-22 岁)的下表中找到的 6 个电影剪辑中的每一个记录 60 秒的数据,产生 12 分钟的大脑活动数据(每个情绪状态 6 分钟)。还收集了 6 分钟的中性脑波数据,导致从受试者记录的总共 36 分钟的 EEG 数据。(每天收集三分钟的数据,以减少静息情绪状态的干扰)。可变频率重采样至 150Hz,这产生了 324,000 个数据点的数据集。
建议的脑电信号特征集
脑电信号的特征提取和分类是脑机接口(BCI)应用的核心问题。当涉及到 EEG 特征提取时,一个具有挑战性的问题是信号的复杂性,因为它是非线性、 非平稳和随机的。信号仅在短时间间隔内被认为是稳定的,这就是为什么最佳实践是应用短时加窗技术来满足这一要求。然而,它仍然被认为是一个在正常大脑条件下成立的假设。
Example of a live EEG stream of the four Muse sensors, Right AUX did not have a device and was discarded due to it simply being noise. This live feed graph has a Y-Axis of measured microvolts at t=0 on each sensor, and an X-axis detailing the time reading. Source
这一小节描述了被认为能够充分区分不同类别的精神状态的一组特征。这些特征依赖于统计技术、基于快速傅立叶变换(FFT) 、香农熵、时间序列中的最大-最小特征等。根据给定时间窗口中信号的时间分布来计算被提议用于对精神状态进行分类的所有特征。该滑动窗口被定义为 1 秒的时间段,即所有特征都是在该时刻内计算的。关于特征提取的更详细介绍,请看原文
机器学习算法
按照上述从原始 EEG 数据中提取特征的方法,我们得到了包含 2548 个特征的数据集。对于数据集的每一行,我们都有相应的目标变量:‘中性’,‘负’或’正’。我们的目标是训练一个机器学习模型,基于这组特征,成功地预测相应的精神状态。
在这个例子中,我从我的一个“定位”算法开始,即随机森林分类器,因为它设置简单,并且经常“开箱即用”地工作,不需要太多的超参数调整。
作为旁注:在原始时域数据上尝试一种卷积神经网络方法(而不是包括信号各种频率特征的提取特征集)也是很有趣的。由于时域中应用的卷积通过卷积定理与信号的频率特性密切相关,这可能是一种减少预处理和特征提取量的有前途的方法。
交叉验证
在应用 ML 时,您首先要了解的事情之一是交叉验证的重要性:在数据集的一部分上评估您的模型的性能,该数据集独立于您用来训练您的模型的数据集。一种方法是在训练您的模型时保持数据集的一部分,并使用例如以下方法来估计已训练模型的性能:
- 用 70%的标注数据训练你的模型
- 对剩余 30%的训练好的模型进行评估
K-fold 交叉验证在这方面有所改进,它允许您多次这样做,这样您就可以看到测试性能是否会根据您用来训练/测试的样本而变化。
Source: http://karlrosaen.com/ml/learning-log/2016-06-20/
通过多次运行训练/测试比较,您将获得对模型性能的更好的估计,并检查模型在对标记数据的不同部分进行训练后的性能是否有很大不同,这本身可能表明您的模型不稳定或样本集太小。
在我的例子中,我在训练模型时进行了 10 重交叉验证,并计算了不同数据段的精确度。模型的最终性能可以通过下面所示的混淆矩阵来可视化。
在机器学习领域,特别是统计分类问题中,混淆矩阵,也称为误差矩阵,是一种特定的表格布局,允许算法性能的可视化。矩阵的每一行代表实际类中的实例,而每一列代表预测类中的实例。该名称源于这样一个事实,即它很容易看出系统是否混淆了类别(即通常将一个类别错标为另一个类别)。
Confusion matrix: True vs. predicted emotional states
当在交叉验证期间评估 10 倍的模型预测时获得的最终结果是 0.987 (+/- 0.01)的相当令人印象深刻的准确度。这意味着,基于从原始 EEG 数据中提取的特征集,我们可以以接近 99%的准确度预测人的精神状态为“中性”、“消极”或“积极”。
结果摘要
虽然这个例子的结果在从脑电图记录中预测情绪状态时确实非常令人印象深刻,但在更高级的应用中还有一段路要走。此外,在实验记录期间,只有两个受试者的有限样本量提出了对新个体进行概括的问题。尽管如此,作为一个显示非常有希望的结果的例子,它代表了进一步调查的良好开端。
观点
那么,我们是否正在接近完全精神控制的反主题科幻未来,在那里,我们甚至放弃了自己思想的隐私?
人工智能读取人的大脑活动的能力引发了围绕隐私和安全的重大伦理问题,研究领导者需要认识到这一点。这项技术打开了一个新的恶意使用的潘多拉魔盒,例如,可以包括在操纵你的大脑思考敏感信息后从你的大脑中窃取这些信息。发表在《自然》杂志上的一篇文章描述了一个迫在眉睫的未来,“将有可能解码人们的心理过程,并直接操纵他们意图、情感和决定背后的大脑机制。”
尽管如此,这一领域的突破是解码思考背后的大脑模式的重要一步。这样的创新也意味着更好的脑机接口,这将为人机交流开辟全新的平台。这可能让瘫痪的人移动机器人手臂,或者让脊髓损伤的人控制电动轮椅。如下面的视频所示,诸如“智能 prostetics”这样的应用可能代表着全世界残疾人向前迈出的一大步。
“Beyond bionics”, The Guardian
虽然我认为自己是一个“技术乐观主义者”,但我仍然认为有必要制定规则和条例,以确保这项技术能够帮助那些需要它的人,而不会带来灾难性的后果。
未来会怎样谁也说不准,但可以肯定的是,我们会找到更多有意或无意地与计算机互动的方式。人工智能驱动的界面的前景很好,但挑战也很大。
(本文原为 原文发表于此 )
你觉得这篇文章有趣吗?如果是这样的话,你可能也会喜欢我的其他一些关于人工智能、机器学习、物理等主题的文章。,你可以在下面的链接和我的中型作者简介中找到:
2018 年夏天我发表第一篇关于“走向数据科学”的文章时,数字背后的统计数据…
medium.com](https://medium.com/@vflovik)
而且,如果你想成为一个媒体会员,免费访问平台上的所有资料,你也可以使用下面我的推荐链接。(注意:如果您使用此链接注册,我也会收到一部分会员费)
[## 通过我的推荐链接加入 Medium—Vegard flo vik
作为一个媒体会员,你的会员费的一部分会给你阅读的作家,你可以完全接触到每一个故事…
medium.com](https://medium.com/@vflovik/membership)*
建立应用科学投资组合
A visualization of my Portfolio
我在 2019 年西 ODSC 会议上的概述
我很高兴今年有机会第一次出席 ODSC 大会!我是开源数据科学项目的忠实粉丝,一直致力于与分享我们在 Zynga 所做的一些工作。
本次会议的目标是强调如何建立一个出色的投资组合,向公司展示应用数据科学技能。应用科学家是一个在包括亚马逊、脸书和微软在内的科技公司中使用越来越多的职位名称。它处于数据科学和机器学习工程的交汇点,尽管行业对应用科学家的需求越来越多,但积累相关经验可能很困难。
在这次演讲中,我确定了在构建向公司展示的投资组合时需要关注的四个主题。我们的目标不仅是证明你可以做很棒的算法工作,而且还展示你可以扩大它的规模,并编写文档,使你的组织能够理解你的方法。以下是我在演讲中讨论的主要主题:
- **生态系统:**亲身体验云计算。
- **规模:**探索分布式计算。
- 集成:将事物粘合在一起。
- **创作:**超越套牌!
一旦完成,我会更新这篇文章链接到幻灯片。
摘要:应用科学家是一个越来越多的科技公司使用的职位,包括亚马逊、脸书…
odsc.com](https://odsc.com/training/portfolio/building-a-portfolio-for-applied-data-science-roles/)
我是 Zynga 的一名数据科学家,我的工作重点是构建投资组合规模的数据产品。这些都是应用科学项目,因为我们需要建立机器学习模型,这些模型可以扩展到数十亿条记录,在我们的游戏中跨不同的事件分类进行翻译,并改进手动功能工程工作。
我很高兴能在 ODSC 做一个关于建立应用科学投资组合的演讲,因为这是我之前为数据科学写的博客。我在 ODSC 演讲的目标是将这篇博文的建议扩展到一个更专业的角色。
找到工作前积累经验
towardsdatascience.com](/six-recommendations-for-aspiring-data-scientists-93d12aeb9b9)
在这次演讲中,我不想强调具体的技术,而是想概括应用科学家应该具备的能力。对于每个主题,我会先问一个可能会被问到的面试问题,然后展示我过去是如何回答这些问题的。
生态系统
你能告诉我你将一个模型投入生产的一次经历吗?
求职者在寻找新工作时面临的一个主要挑战是,他们没有雇主正在寻找的编码/建模/部署生态系统的经验,并且很难获得新平台的实践经验。
虽然许多公司确实锁定了对其数据平台的访问,但我建议有抱负的应用科学家积累云平台方面的经验,即使这意味着要用自己的时间来做。亚马逊网络服务(AWS)和谷歌云平台(GCP)都提供了构建体验的自由层解决方案,尝试不同的系统可以为如何构建生产级数据产品提供更广阔的视角。
Take advantage of the free cloud platform options.
目标是展示云环境的经验,并证明您可以通过利用这些平台来扩展方法。探索多个平台以了解平台之间的权衡也很有用。例如,我写了如何通过 AWS 和 GCP 使用无服务器函数来托管逻辑回归模型。
“生产中的数据科学”第 3 章
towardsdatascience.com](/models-as-serverless-functions-7930a70193d4)
规模
你处理过的最大的数据集是什么?
第二个主题是在整合项目组合时使用大规模数据集。理想情况下,您应该突破单实例计算的极限,探索分布式计算方法来解决大规模问题。在 Kaggle 、 BigQuery 和开放数据上有各种开放数据集可供探索。
使用大数据集的目标是表明您的方法可以扩展到现实世界的工作负载,例如为数千万用户构建流失预测。探索解决大规模问题的不同方法很有用,比如使用 Spark 和 Cloud 数据流,甚至在 SQL 中执行复杂的逻辑。你的作品集的这一部分应该展示你可以采取分而治之的方法来解决问题,并且你可以有效地分配工作流。例如,我在博客中讨论了在 Spark 中并行化任务的不同方法。
提升数据科学任务的速度
towardsdatascience.com](/3-methods-for-parallelization-in-spark-6a1a4333b473)
集成
你能告诉我你不得不整合不同系统的时候吗?
在将原型代码翻译成产品代码时,经常会遇到挑战。例如,当使用 PySpark 和 MLlib 时,对训练 ML 模型有用的 scikit-learn 库可能在生产环境中不可用。投资组合应该展示当生产一个 ML 模型时,你是如何处理这些类型的挑战的,比如使用一个中间模型格式。展示您可以构建需要跨多个组件协调的端到端 ML 管道也很有用。
我在作品集中使用的一个例子是 AutoModel ,其中我使用了 Pandas UDFs 来扩展一个只支持 Pandas 数据框的库。Spark 数据帧和功能工具不兼容,用这些组件构建 ML 管道需要一些新的集成工作。我写过的另一个例子是使用云数据流构建一个完全托管的分析平台。
收集关于应用程序使用和用户行为的数据,如玩家在游戏中的进度,对于产品来说是无价的…
towardsdatascience.com](/a-simple-and-scalable-analytics-pipeline-53720b1dbd35)
对于你的投资组合中的这个主题,包含分析和机器学习之外的项目可能是有用的。例如,我想展示的集成挑战之一是我如何让我的顾问的反应式编程语言使用 Java 本地接口(JNI)与 C++ API 一起工作。
Writing a StarCraft bot using Java.
程序编写
你喜欢如何宣传你的项目?
强有力的书面沟通对于推动组织内数据产品的采用至关重要。虽然幻灯片对于提出想法和提供产品概述很有用,但它们通常缺乏传达数据产品如何工作所需的深度和细节。在 Twitch,我养成了为记录分析和 ML 模型而编写长格式内容的习惯,并且从那以后一直应用这种方法。在 Zynga,我提倡撰写白皮书,为数据产品提供执行摘要、产品概述和实施细节。关于我的作品集,我目前正在写一本名为生产中的数据科学的书。
Long-form content I’ve written on data science.
虽然将代码和文档放在 GitHub 这样的平台上对于共享项目来说很好,但是积累编写关于项目的长格式内容的经验也是很好的。这可以采取白皮书或博客帖子的形式,创作这种类型的内容对于展示书面交流技能很有用。
结论
当构建应用科学角色的文件夹时,展示您过去如何扩展方法以在生产规模下工作是很棒的。我从前三个主题谈起:生态系统、规模和整合。我强调的最后一个主题是为你的项目写作和宣传。
如果您无法到达 ODSC,活动结束后应提供会谈视频。我还将在旧金山湾区的以下活动中发言。
[## 2019 年 Zynga | AI 和大数据博览会上的自动化功能工程
开始时间:结束时间:白天:谈话:Zynga 是一家移动游戏出版商,其工作室遍布全球。我们有…
www.ai-expo.net](https://www.ai-expo.net/northamerica/talk/automated-engineering/) [## AIFOW -影响:AI 2020
2020 年 2 月 4 日和 5 日——旧金山影响:AI 2020 即将在加州旧金山举行。来学习、探索和创造…
aifow.com](https://aifow.com/ai2020)
在 Python 中构建员工流失模型以制定战略性保留计划
Everybody’s working hard but who is most likely to hand in their resignation letter? (Photo by Alex Kotliarskyi on Unsplash)
内容
1.问题定义
员工流动(也称为“员工流失”)对公司来说是一个代价高昂的问题。替换一名员工的真实成本通常会非常高。
美国进步中心(Center for American Progress)的一项研究发现,公司通常会支付员工工资的五分之一来替换该员工,如果高管或收入最高的员工被替换,成本会显著增加。
换句话说,对于大多数雇主来说,替换员工的成本仍然很高。这是由于面试和寻找替代者所花费的时间,签约奖金,以及新员工在适应新角色的几个月中生产力的损失。
了解员工最有可能离开的原因和时间可以导致提高员工保留率的行动,并可能提前计划新的招聘。我将使用一种循序渐进的系统方法,使用一种可以用于各种 ML 问题的方法。这个项目将属于通常所说的人力资源分析或人员分析。
Focused and minding their own business but where’s the collaborative spirit? (Photo by rawpixel on Unsplash)
在这项研究中,我们将试图解决以下陈述的问题:
- 一名在职员工离开公司的可能性有多大?
- 员工离开公司的关键指标是什么?
- 根据调查结果,可以采取哪些策略来提高员工保留率?
假设我们有前雇员的数据,这是一个标准监督分类问题其中标签是一个二元变量,0(在职雇员),1(前雇员)。在这项研究中,我们的目标变量 Y 是员工离开公司的概率。
注意:完整代码请参考本GitHub repo和/或本ka ggle 内核 。
使用监督机器学习预测员工流失-hamzaben 86/员工流失预测模型
github.com](https://github.com/hamzaben86/Employee-Churn-Predictive-Model) [## 带有战略保留计划的员工流失模型| Kaggle
编辑描述
www.kaggle.com](https://www.kaggle.com/hamzaben/employee-churn-model-w-strategic-retention-plan)
2.数据分析
在本案例研究中,HR 数据集来自于 IBM HR Analytics 员工流失&绩效,其中包含 1,470 名员工的数据以及关于这些员工的各种信息。我将使用这个数据集,通过了解员工流失的主要驱动因素来预测员工何时会辞职。
正如 IBM 网站 : 上所述,“这是 IBM 数据科学家创造的虚构数据集。其主要目的是展示 IBM Watson 员工流失分析工具。
Let’s crunch some employee data! (Photo by rawpixel on Unsplash)
2.1 数据描述和探索性可视化
首先,我们导入数据集并为该分析制作源文件的副本。数据集包含 1,470 行和 35 列。
数据集包含几个数字列和分类列,提供关于雇员个人和雇佣详细信息的各种信息。
让我们按照列的类型(例如 int64、float64、object)来细分这些列:
2.2 数据来源
提供的数据没有缺失值。在人力资源分析中,员工数据不太可能出现大比例的缺失值,因为人力资源部门通常会将所有个人和雇佣数据存档。
但是,保存的文档数据类型(即,是否是基于纸张、Excel 电子表格、数据库等)对人力资源数据的准确性和易用性有很大影响。
2.3 数字特征概述
基于数字特征的信息和直方图,可以进行一些观察:
- 几个数字特征是重尾的;事实上,有几个分布是右偏的(例如,MonthlyIncome DistanceFromHome,YearsAtCompany)。在将模型拟合到数据之前,可能需要数据转换方法来接近正态分布。
- 年龄分布是一种略微向右倾斜的正态分布,大多数工作人员年龄在 25 至 45 岁之间。
- EmployeeCount 和 StandardHours 是所有雇员的常数值。它们很可能是多余的功能。
- 考虑到特征的准均匀分布,员工编号可能是员工的唯一标识符。
source code: df_HR.hist() — isn’t Python a beautiful thing?
2.4 按目标属性的特征分布
在本节中,将进行更详细的探索性数据分析。完整代码请参考这个 GitHub repo 和/或 Kaggle 内核 。
2.4.1 年龄
在职和离职雇员的年龄分布仅相差一岁;前员工的平均年龄为 33.6 岁,现任员工的平均年龄为 37.6 岁。
让我们创建一个由目标值着色的核密度估计(KDE)图。核密度估计(KDE)是一种估计随机变量概率密度函数的非参数方法。
2.4.2 性别
性别分布显示,该数据集中男性前雇员的相对比例高于女性前雇员,数据集中前雇员的标准化性别分布为男性 17.0%,女性 14.8%。
2.4.3 婚姻状况
该数据集包含三种婚姻状况:已婚(673 名员工)、单身(470 名员工)、离婚(327 名员工)。单身员工的离职比例最大,为 25%。
2.4.4 角色和工作条件
对商务旅行频率和流失状态之间的关系的初步观察表明,对于“经常”旅行的员工来说,存在最大的归一化离职比例。没有披露与商务旅行状态相关的旅行指标(即多少小时的旅行被视为“频繁”)。
数据集中列出了几种工作角色:销售主管、研究科学家、实验室技术员、制造总监、医疗代表、经理、销售代表、研究总监、人力资源。
在公司工作 2.4.5 年,自上次晋升以来
在职员工在公司的平均工作年限为 7.37 年,离职员工为 5.13 年。
2.4.6 年,现任经理
在职员工在现任经理手下工作的平均年限为 4.37 年,离职员工为 2.85 年。
2.4.7 加班
一些员工有加班承诺。数据清楚地表明,有相当大一部分加班的员工已经离开了公司。
2.4.8 月收入
员工月收入从 1009 美元到 19999 美元不等。
2.4.9 目标变量:减员
特征“**损耗”**就是这个机器学习问题的所在。我们试图通过使用与员工个人和职业历史相关的其他相关特征来预测特征“流失”的价值。
在提供的数据集中,在职员工的比例为 83.9%,离职员工的比例为 16.1%。因此,这是一个不平衡的阶级问题。
当每个类的实例数量大致相等时,机器学习算法通常工作得最好。在实现我们的机器学习算法之前,我们必须解决这个目标特征的不平衡。
2.5 相关性
让我们来看看一些最重要的相关性。值得记住的是,相关系数只测量线性相关性。
如上所示,“月工资”、“工作的公司数量”和“离家的距离”与自然减员正相关;而“总工作年数”、“工作级别”和“在当前职位的年数”与自然减员呈负相关。
3 EDA 结束语
- 数据集没有任何缺失或错误的数据值,所有特征都是正确的数据类型。
- 与目标特征最强的正相关是:绩效等级、月工资率、工作过的公司数量、离家的距离。
- 与目标特征最强的负相关是:总工作年限、工作级别、在当前岗位的年限、月收入。
- 数据集不平衡,大部分观察描述的是当前在职员工。
- 与已婚和离异员工相比,单身员工的离职比例最大。
- 大约 10%的离职者在他们在公司的两周年纪念日离开。
- 与同事相比,住得离工作地点较远的人离职的比例更高。
- 经常出差的人比他们的同行显示出更高的离职比例。
- 不得不加班的人比他们的同事有更高的离职率。
- 与同行相比,之前已经在几家公司工作过的员工(已经在工作场所之间“跳槽”)的离职率更高。
4.预处理流水线
在本节中,我们进行数据预处理步骤,为机器学习算法的实现准备数据集。完整代码请参考这个 GitHub repo 和/或 Kaggle 内核 。
4.1 编码
机器学习算法通常只能将数值作为它们的预测变量。因此标签编码变得必要,因为它们用数值编码分类标签。为了避免为具有大量唯一值的分类特征引入特征重要性,我们将同时使用标签编码和一键编码,如下所示。
4.2 特征缩放
使用最小最大缩放器的特征缩放实质上缩小了范围,使范围现在介于 0 和 n 之间。当输入数值变量落在类似的范围内时,机器学习算法表现更好。在这种情况下,我们在 0 和 5 之间缩放。
4.3 将数据分成训练集和测试集
在实现或应用任何机器学习算法之前,我们必须将训练和测试数据帧与我们的主数据集分离。
5.构建机器学习模型
5.1 基线算法
在我们进入更复杂的解决方案之前,让我们先使用一系列基线算法(使用开箱即用的超参数)。本节考虑的算法有:逻辑回归,随机森林, SVM , KNN ,决策树分类器,高斯 NB。
让我们依次评估每个模型,并提供准确度和标准差分数。完整代码请参考本 GitHub repo 和/或 Kaggle 内核 。
分类准确度是正确预测的数量占所有预测的比例。这是分类问题最常见的评估标准。
然而,经常被误用,因为它只有在每类中有相等数量的观测值,并且所有预测和预测误差同等重要时才真正适用。在这个项目中情况并非如此,所以不同的评分标准可能更合适。
ROC 曲线下面积(简称 AUC)是二元分类问题的性能度量。AUC 代表模型区分正负类别的能力,更适合本项目。面积为 1.0 表示模型能够完美地做出所有预测。面积为 0.5 表示模型与随机模型一样好。
基于我们的 ROC AUC 比较分析,逻辑回归和随机森林显示最高的平均 AUC 分数。我们将这两种算法列入候选名单,以供进一步分析。关于这两种算法的更多细节见下文。
5.2 逻辑回归
GridSearchCV 允许通过搜索估计器的指定参数值来微调超参数。如下所示,GridSearchCV 的结果使用 ROC_AUC 作为评分标准,为我们提供了微调的超参数。
5.3 混淆矩阵
混淆矩阵为我们提供了更详细的准确度分数表示,以及我们的标签正在发生的事情——我们确切地知道哪些标签/如何被正确和错误地预测。Logistic 回归分类器在测试集上的准确率为 75.54。
5.4 标签概率
可以将概率与预测目标相关联,而不是获得二进制估计目标特征(0 或 1)。输出提供了第一个索引,该索引涉及数据属于类别 0 (员工未离职)的概率,第二个索引涉及数据属于类别 1 (员工离职)的概率。预测特定标签的概率为我们提供了一个衡量员工离开公司的可能性的方法。
5.5 随机森林分类器
让我们仔细看看如何使用随机森林算法。我将通过对 AUC 分数进行交叉验证来微调随机森林算法的超参数。
随机森林让我们知道在预测目标特性时哪些特性是最重要的(本项目中的“损耗”)。下面,我们按照重要性来划分特征。
Random Forest 帮助我们确定了 10 个最重要的指标(排列在下表中):(1)月收入,(2)加班,(3)年龄,(4)月工资,(5)离家的距离,(6)日工资,(7)总工作年数,(8)在公司的年数,(9)小时工资,(10)与经理的年数。
随机森林回归分类器在测试集上的准确率为 86.14。下面显示了相应的混淆矩阵。
预测特定标签的概率为我们提供了一个衡量员工离开公司的可能性的方法。使用 RandomForestClassifier 预测概率时的 AUC 为 0.818。
5.6 ROC 图
AUC-ROC 曲线是在各种阈值设置下对分类问题的性能测量。ROC 是概率曲线,AUC 代表可分性的程度或度量。它告诉我们模型在多大程度上能够区分不同的类。绿线代表纯随机分类器的 ROC 曲线;一个好的分类器尽可能远离那条线(朝向左上角)。
如上所示,与随机森林分类器相比,微调的逻辑回归模型显示了更高的 AUC 分数。
6.结束语
6.1 风险评分
随着公司生成更多关于其员工的数据(关于新加入者和最近离职者),该算法可以使用额外的数据进行重新训练,并在理论上生成更准确的预测,以根据算法分配给每个特征变量(即员工)的概率标签来识别高风险离职员工。
员工可以根据预测的标签分配一个“风险分数,以便:
- 标签为< 0.6 的员工的低风险
- 中等风险适用于评分在 0.6 和 0.8 之间的员工
- 高风险标签为> 0.8 的员工
Good work on reading the analysis — but what now? How does this help decision-makers? (Photo by rawpixel on Unsplash)
6.2 指标和战略保留计划
人们离开的更强指标包括:
- 月收入:工资高的人不太可能离开公司。因此,应努力收集当前当地市场的行业基准信息,以确定公司是否提供有竞争力的工资。
- 久而久之:加班的人更容易离开公司。因此,必须努力在前期适当确定项目范围,提供足够的支持和人力,以减少加班。
- 年龄:相对年轻的 25-35 岁年龄段的员工更有可能离职。因此,应该努力清楚地阐明公司的长期愿景以及适合该愿景的年轻员工,并以明确的晋升途径等形式提供激励。
- 离家远:住得离家远的员工更有可能离开公司。因此,应努力为离开同一地区的员工群提供公司交通或交通补贴形式的支持。基于员工家庭所在地的初步筛选可能不被推荐,因为只要员工每天按时上班,这将被视为一种歧视。
- 总工作年限:越有经验的员工越不容易离开。拥有 5-8 年工作经验的员工应被视为潜在离职风险较高。
- YearsAtCompany :忠诚的公司不太可能离开。满两周年的员工应被认定为具有较高离职风险的潜在员工。
- YearsWithCurrManager :大量离职者在现任经理离职 6 个月后离开。通过使用每个员工的直线经理详细信息,可以确定哪个经理在过去一年中经历了最多数量的员工辞职。
这里可以使用几个指标来确定是否应该与直线经理一起采取行动:
- 直线经理担任特定职位的年数:这可能表明员工可能需要管理培训或在组织中被指派一名导师(最好是高管)
- 辞职员工的模式:这可能表明离职员工的重复模式,在这种情况下可以采取相应的措施。
6.3 最终想法
可以为每个风险分值组制定战略保留计划。除了上面列出的每个特征的建议步骤之外,人力资源代表和员工之间的面对面会议可以针对中风险员工和高风险员工展开,以讨论工作条件。此外,与这些员工的直线经理会面可以讨论团队内部的工作环境,以及是否可以采取措施改善工作环境。*
我希望你能像我写这篇文章时一样喜欢它。
再次声明,完整代码请参考本*GitHub repo和/或本 Kaggle 内核 。***
使用监督机器学习预测员工流失-hamzaben 86/员工流失预测模型
github.com](https://github.com/hamzaben86/Employee-Churn-Predictive-Model) [## 带有战略保留计划的员工流失模型| Kaggle
编辑描述
www.kaggle.com](https://www.kaggle.com/hamzaben/employee-churn-model-w-strategic-retention-plan)***
构建复合算法的实验框架
在现有 ML 库的基础上快速开发一个框架来改变模型超参数和混合多个模型以找到最佳执行算法通常非常有用。
Weka 提供了一套全面的库来快速试验不同的模型。在这里,我将演示如何轻松地使用 Weka Api 来构建一个实验平台,以比较不同 ML 算法的性能。
可以先编写一个包装类,比如 KNNTest,使用 maven 构建,创建 CLI 比如 MLExpFwk,然后如下调用它。
Weka maven dependency: {groupId: nz.ac.waikato.cms.weka,artifactId: weka-stable, version: 3.6.8}
Java version : 1.7.0_71
Weka Standalone: weka-3-6-12, Mac compatible Weka: weka-3-6-12-oracle-jvm.dmgEntry Point: MLExpFwk
Executable: MLExpFwk-1.0.0.jar
Run the tool: java -jar MLExpFwk-1.0.0.jar CreditRating-train.arff CreditRating-test.arff* Output of the program :
o Menu options
Welcome to ML Programs Suite !
Which Algorithm do you want to run ?
Type 0 to exit.
Type 1 for Decision Tree - J48
Type 2 for KNN
Type 3 for SVM
Type 4 for MLP
So on ...We can run any algo against any training and test datasets to compare and contrast the accuracy.
实验监督算法
使用不同的超参数生成不同的模型组合:
//**DTree Model properties**
tree.setMinNumObj(minNumObj);
tree.setReducedErrorPruning(true);
tree.setNumFolds(M);
tree.setSubtreeRaising(true);
tree.setUnpruned(false);//**KNN Model properties** IBk ibk = new IBk();
ibk.setKNN(3);
ibk.setCrossValidate(true);
ibk.setMeanSquared(true);//**SVM Model properties**
SMO smo = new SMO();
NormalizedPolyKernel normalizedPolyKernel = new NormalizedPolyKernel();
smo.setKernel(normalizedPolyKernel);
smo.setC(2);//**ANN Attribute Variations** MultilayerPerceptron mlp = new MultilayerPerceptron(); mlp.setLearningRate(0.1);**//Create different Run options for different models to be tested** case 1: “Type 1 — to run AdaBoosting on J4.8 tree“);
testBoostingOnTree(); break;
case 2: “Type 2 — to run AdaBoosting on SVM”)
testBoostingOnSVM(); break;
case 3: ......
default: printmenu(); break;
AdaBoostM1 classifier = new AdaBoostM1();
SMO smo = new SMO(); boosting.setClassifier(smo); classifier.buildClassifier(MLExpFwk.TRAINING_DATA);Evaluation eval = new Evaluation(MLProgramSuite.TRAINING_DATA); eval.evaluateModel(classifier, MLExpFwk.TRAINING_DATA);System.out.println(eval.toSummaryString();
System.out.println(“ — — Now apply the Classifier to the test data — — “);
//runAlgoOnTestData
Evaluation eval = new Evaluation(MLExpFwk.TRAINING_DATA); eval.evaluateModel(classifier, MLExpFwk.TEST_DATA); System.out.println(eval.toSummaryString("\nResults\n\n", false));
例如,让我们在电离层数据集(https://archive.ics.uci.edu/ml/datasets/Ionosphere 上运行不同的监督算法组合!!)
- AdaBoostM1 / MultiBoostAB +/修剪过的树
- SVM+/adaboosting m1+/多内核+/归一化多内核)
- IBK +/ k=3 +/交叉验证)
- MLP(具有非线性 sigmoid 传递函数)等等。
然后我们有了有趣的观察:
- MLP 达到 99%的准确率,但耗时 1.45 秒。它对大量异质特征非常有用。由于函数逼近的全局性质,MLP 学习具有复杂局部结构的非线性函数较慢。在梯度下降过程中,参数在学习过程中陷入的平稳状态需要很长时间才能消除。
- 使用归一化多核的 SVM在 0.10 秒内达到 97.14%的准确率。SMO 算法对过度拟合具有很强的弹性。看起来归一化的 PolyKernel 在测试数据集上比 PolyKernel 表现得更好,因为它归一化了特征向量中的一些稀疏性,并找到了更好的模型。
- J48 的 numFolds = 5 和 reducedErrorPruning 提供了更高的精度,比如 91.22%。当应用 adaboostm 1/multiboosttab 升压时,精度进一步提高到 97.8%。
- k = 3 且交叉验证的 KNN提供了非常好的结果。但是由于未适当处理的不平衡数据分布问题,显示出一些不一致性。
实验无监督算法
现在,让我们对数据集应用无监督算法,通过创建像 ICA、NN-ICA、Kmeans-ICA、NN-KMeans、EM-ICA 这样的包装器类来理解聚类技术和特征选择是如何工作的
让我们用下面的数据集来检查有趣的事实:
用户接受采访,以提供对大型产品品牌的购买行为的意见。
偏好 _ 电子邮件、质量 _ 产品 _ 定价 _ 高、咨询 _ 多个 _ 网站 _ 购买前、偏好 _ 电视 _ 广告、偏好 _ 移动 _ 广告、观看 _ 广告 _ 制作 _ 购买、偏好 _ 在线 _ 网络、女性 _ 主要 _ 决策者、偏好 _ 儿童 _ 友好 _ 广告、偏好 _ 大 _ 品牌、频繁 _ 访问 _ 商场、偏好 _ 在线 _ 优惠、偏好 _ 分期付款、偏好 _ 餐厅 _ 优惠券 _ 购物、偏好 _ 电影 _ 优惠券 _ 购物
用户反馈数据被捕获为数值:
1,3,1,2,3,1,2,2,3,2,2,1,1,1,1
(1 = Strongly Agree, 2 = Agree, 3 = Neither Agree nor Disagree, 4 = Disagree, 5= Strongly Disagree)
让我们首先尝试应用 KMeans 算法,该算法提供基于迭代距离的不相交聚类集。
DataSet ds = CSVFilereader.read(“/resources/marketing_survey.csv”, skipHeader);
KMeansClusterer km = new KMeansClusterer(6); km.estimate(ds);
Weka 输出很容易理解。显然,集群 0 是所有特性中最好的一个。我们还可以进行其他有趣的观察,比如第 5 类客户对“prefer_mobile_ads”的看法比其他类客户更强烈。
接下来让我们运行期望最大化算法,该算法利用先验概率找到聚类。由于需要计算任何实例的聚类成员概率,因此查找聚类需要相当长的时间。
We observe that Cluster 2 offers best market segment.
EMClusterer em = new EMClusterer(); em.estimate(ds);
现在让我们把重点放在降维算法上。
**主成分分析:**通过选择足够的特征向量来解释原始数据 95%的方差,从而实现降维。
PrincipalComponentAnalysis filter =new PrincipalComponentAnalysis(dataset); System.out.println(set.getDescription()); System.out.println(filter.getProjection()); Matrix reverse = filter2.getProjection().transpose();
for (int i = 0; i < set.size(); i++)
{ Instance instance = set.get(i);
instance.setData(reverse.times(instance.getData()).plus(filter.getMean()));
}
一旦协方差矩阵被打印出来,我们就可以找到互特征相关性。PCA 回答了以下问题——这 M 个参数中的哪一个解释了数据集中包含的大量变化?
然后我们选择排名前 5 的属性。
**0.864** 1 0.443**v5**+0.377**v4**+0.354**v11**+0.327**v9**+0.298v6…
**0.7509** 2 -0.461**v4**–0.422**v5**+0.382**v9**+0.377**v11**+0.339v10…
**0.6513** 3 0.519v8–0.505v12–0.415v11+0.388v9–0.294v13…
**0.5564** 4 0.529v6–0.473v3+0.361v5–0.327v10–0.318v4…
**0.4744** 5 -0.446v2–0.373v8–0.362v7–0.36v3+0.326v10…
类似地,我们可以运行**独立分量分析:**它在新生成的特征集中生成足够的特征向量,以提供关于具有较高变化的特征的想法,这些变化可以被选择用于进一步的分类。
System.out.println("Before ICA"); System.out.println(set.getDescription()); System.out.println(set);
IndependentComponentAnalysis filter = new IndependentComponentAnalysis(set, 8);
filter.filter(set);
System.out.println("After ICA");
System.out.println(set);
正如我们前面提到的,我们的目标是混合和匹配算法。
接下来,我们使用 PCA 运行 KMeans 聚类
PrincipalComponentAnalysis filter = new PrincipalComponentAnalysis(dataset, 15); filter.filter(dataset); System.out.println(dataset.getDescription());
KMeansClusterer km = new KMeansClusterer(4); km.estimate(dataset);
然后我们用 ICA 运行 KMeans 聚类
IndependentComponentAnalysis filter = new IndependentComponentAnalysis(set); filter.filter(set); System.out.println(set.getDescription());
KMeansClusterer km = new KMeansClusterer(3); km.estimate(set);java -cp unsupervised_algo.jar:lib/ABAGAIL.jar com.ml.unsupervised.tests.KMeansWithIndependentComponentAnalysisTest
我们还可以应用其他 Weka 降维技术,如不重要成分分析和随机投影
随机投影通过使用具有单位长度列的随机矩阵将数据投影到更低维度的子空间来降低数据的维度(即,它将减少数据中的属性数量,同时保留其大部分变化,如 PCA,但计算成本低得多)。
在减少维度之前,它首先应用 NominalToBinary 过滤器将所有属性转换为数值。
理想情况下,我们应该将代码封装到合适的复合模型构建器包装器中,并测试所有不同的变体。为了更有趣,我们可以用 PCA 运行神经网络
**//Feature Names**
obs#, chk_acct,duration,history,new_car, used_car, furniture,radio_tv, education,retarining,crd_amount,sav_accnt,employment,install_rate,male_div, male_single, coapplication,guarantor, present_redisence, real_estate,prop_unknown,age,other_install,rent,own_res,num_credits,job,num_dependents,telephone,foreign,response**//Feature Values** 2,1,48,2,0,0,0,1,0,0,5951,0,2,2,0,0,0,0,0,2,1,0,22,0,0,1,1,2,1,0,0,0
5,0,24,3,1,0,0,0,0,0,4870,0,2,3,0,1,0,0,0,4,0,1,53,0,0,0,2,2,2,0,0,0PrincipalComponentAnalysis filter = new PrincipalComponentAnalysis(set, 15); filter.filter(set); network = factory.createClassificationNetwork( new int[] {inputLayer, hiddenLayer, outputLayer}); ConvergenceTrainer trainer = new ConvergenceTrainer( new BatchBackPropagationTrainer(set, network, new SumOfSquaresError(), new RPROPUpdateRule())); trainer.train(); System.out.println(“Convergence in “+trainer.getIterations()+”iterations”);Instance[] patterns = set.getInstances();
int true_positives_num = 0; int actual_class = 0; int predicted_class = 0; for (int i = 0; i < patterns.length; i++) { network.setInputValues(patterns[i].getData()); network.run(); actual_class = Math.round(Float.parseFloat(patterns[i].getLabel().toString())); predicted_class = Math.round(Float.parseFloat(network.getOutputValues().toString())); if(actual_class == predicted_class) { true_positives_num++; } }double true_positives = ((true_positives_num*100/(patterns.length)));
因此,本质上,我们可以非常容易地混合所有不同的技术,并构建一套非常强大的复合模型,如 Kmeans-ICA 、 KMeans-PCA 、 NN-LDA 、 NN-ICA 、 NN-KMeans 、 NN-EMC 等。这里没有特定的正确或错误的模型,这都是通过使用 Weka 或任何其他库(如 SciKit)进行实验的结果。
现在让我们换个话题,看看随机搜索并使用 ABAGAIL 和 Weka 库构建一个框架。
有时,MLP 无法收敛到复杂数据集的解决方案,这是由于:(1)无偏估计量,但可能具有高方差(由于过拟合)/ (2)刚性模型相反,导致小方差但高偏差/ (3)需要更长的时间,并且还存在陷入局部最优的风险。
因此,让我们在反向传播网络中应用随机优化而不是梯度下降
使用 ABAGAIL.jar 的本地随机搜索算法(在类路径中)。
这里,我们对训练样本应用 3 种类型的优化算法。我们可以结合多种技术,也可以用不同的超参数以孤立的方式进行测试。
oa[0] = new RandomizedHillClimbing(nnop[0]);
oa[1] = new SimulatedAnnealing(1E11, .1, nnop[1]);
oa[2] = new StandardGeneticAlgorithm(100, 50, 10, nnop[2]);
随机爬山是“一个不断走向增值的循环”。它可以在最佳后继者的集合中随机选择,以避免局部最小值。
模拟退火是随机局部搜索方法的一个例子,以基于目标函数变化大小的概率,在开始时结合横向和下坡移动。这个想法是在早期对整个空间进行足够的探索,以便最终的解决方案对初始状态相对不敏感。这应该会降低在局部最大值、平台或山脊被捕获的机会。"
遗传算法是一种基于遗传学和自然选择原理的优化和搜索技术。GA 允许由许多个体组成的群体在指定的选择规则下进化到最大化“适应度”(即最小化成本函数)的状态。
train (OptimizationAlgorithm oa, BackPropagationNetwork network, String oaName) { //
for(int i = 0; i < trainingIterations; i++) {
oa.train(); double error = 0;
for(int j = 0; j < instances.length; j++) {
if(instances[j] == null || instances[j].getData() ==
null) continue;
network.setInputValues(instances[j].getData());
network.run(); Instance output =
instances[j].getLabel(), example = new
Instance(network.getOutputValues());
example.setLabel(new
Instance(Double.parseDouble(network.getOutputValues().toString()))); error += measure.value(output, example);
} } }
一旦我们运行算法的组合,我们观察到一些非常有趣的 w.r.t .当前数据集。
- 遗传算法提供了一个最佳变量列表,而不仅仅是一个单一的解决方案。它的随机探索(通过交叉)技术可以找到其他局部搜索算法找不到的解决方案。随机突变避免了陷入局部最小值的问题。
- 遗传算法同时从大范围的成本面中搜索,处理大量的变量。所以它非常适合并行计算,
- GA 优化了具有极其复杂的成本表面的变量(它们可以跳出局部最小值)
- 当样本量为 200,配对节点数为 100,变异节点数为 10 时,遗传算法需要更长的时间才能达到高精度
- 如果我们将种群规模设置为 100,交配节点设置为 50,变异节点设置为 20,那么 GA 会工作得更快,并达到同样高的精度。
- 在某些情况下,寻找性能良好的凸函数的解的基于微积分的导数可以优于遗传算法,因为遗传算法可能仍然需要花费周期来分析初始种群的成本,因为它需要为实值数据提供更多位。
- 爬山搜索的主要好处是它只需要有限的内存。
- 我们需要选择一个局部变化来提高目标函数的当前值。
- 爬山搜索的主要好处是它只需要有限的内存。这里,我们需要选择一个局部变化,它应该提高目标函数的当前值。像连续空间中的梯度下降算法一样,它接近局部极小值,但是不使用梯度,爬山使用随机局部搜索。HA 不同于梯度下降算法,梯度下降算法根据山的梯度在每次迭代中调整随机变量 X 中的所有值。
- 一旦迭代次数达到 100,RHC 就可以正确地分类 95%的实例,因为它可以更好地探索更多的邻居,并越过山脊和高原。
- 模拟退火是随机局部搜索的一个例子。如果温度降低 0.1(慢得多),迭代次数为 10,模拟退火的性能提高很多。性能提高的主要原因是“探索非最佳路径的移动频率和这种非最佳空间(平台)的大小随着我们降低温度而减小”。
- 因此,虽然 GA 在节点变异增加时表现更好,但 RHC 需要大量的迭代,而 SA 以较少的迭代次数和较慢的温度变化收敛。
到目前为止,我们已经使用 Weka 和 ABAGAIL 开发了一个基于 Java 的框架,用于监督、非监督和随机搜索算法的实验。在下一篇博客中,我们将讨论强化学习算法的实验方法。