TowardsDataScience-博客中文翻译-2020-一百二十五-
TowardsDataScience 博客中文翻译 2020(一百二十五)
在 Numpy 中使用 Q-Learning 玩游戏

有一些机器学习模型可以被训练以基于训练期间使用的输入-输出对将任何给定的输入映射到期望的输出。通过输入输出对,我显然是指输入和它各自的基础事实或标签。这种算法被称为监督学习算法。分类和回归就是其中的一些例子。
此外,存在一类机器学习模型,它们在数据中寻找潜在的模式,而不需要明确地知道标签。这些算法被称为无监督学习算法。聚类和密度估计就是其中的一些例子。
除了监督学习和非监督学习,还有第三种机器学习范式,称为强化学习,它与前两种类型有着根本的不同。
这基本上包括在一个环境中做出连续的决策,以最大化累积的未来回报。稍后我会更详细地解释这一点。
Q - 学习就是这样一种无模型强化学习算法,它学习在各种情况下做出正确的动作。
让我们首先熟悉强化学习设置的基本术语。
让我们熟悉一些基本的 RL 术语
有一种叫做环境的东西,它实际上包含了构成系统的所有东西。它有一个代理,它观察环境(从环境中获取输入),然后根据输入采取动作,改变环境的状态,并在此过程中收集奖励。
如果这对你来说还不够完美,请不要担心!
让我们借助一个例子来理解这一点。
于是就有了 6x 6 的网格。我们让代理在网格中 6x6=36 个可能的单元中随机产生。同样,在我们网格世界的另外两个细胞中,我们有一瓶啤酒和一种致命的病毒。药剂、一瓶啤酒和致命病毒每次都会在独特的细胞位置上繁殖。代理人想要够到啤酒瓶子,以避免可能感染它的病毒,并因此结束游戏。对代理移动的限制是它只能在任何对角线方向上移动一个单元格。
在这个设置中,我们的 6x6 网格世界就是环境。代理可以访问它相对于一瓶啤酒(奖励)和致命病毒(敌人)的位置,我们称这个代理可以访问的环境信息为状态。根据环境的当前状态,代理可以采取某些预定义的动作,比如在四个对角线方向的任何一个方向上移动一个单元。代理的行为可能会给它一瓶啤酒(一个正奖励)或者它可能会让代理被致命病毒感染(一个负奖励)。因此,代理人的目标是通过与环境互动来了解它的环境,并最终学会采取行动,以使未来的累计回报最大化。
状态、动作和奖励的顺序,直到它全部结束(要么伸手拿啤酒瓶,要么被病毒感染),被称为集。
导致情节终止的状态称为终端状态。在我们的网格世界的例子中,代理人相对于啤酒瓶或病毒的相对位置变为零的状态是终端状态。
回报是未来奖励的累计总和。在没有终端状态的情况下,这可能达到无穷大,也称为非情节任务。为了使它成为一个有限的总和,我们使用一个叫做 gamma 的贴现因子来贴现未来的奖励。
下面是 t 时刻的回报公式,用不同时间点的回报来表示。这里 T 是最终的时间步长,γ是贴现率,使得 0≤γ≤1

价值函数是状态(或状态-动作对)的函数,估计代理处于给定状态有多好(或在给定状态执行给定动作有多好)
价值函数总是相对于称为策略的特定行为方式来定义的。
一个政策只不过是从状态到选择每个可能行动的概率的映射。
我想多谈谈状态-行动价值函数,因为我们将在博客的其余部分使用它。
如前所述,状态-动作函数是一个返回预期收益的函数,从状态“s”开始,采取动作“a”,然后遵循策略 π 。
因此,比较给定状态的状态-动作函数值和动作空间中的所有动作,可以引导我们找到在该状态下可以采取的最佳可能动作。这实际上是我们的代理如何选择在给定状态下可以采取的最佳行动。
Q 表是由所有状态-动作对组成的表。这就像一个查找表,如果给出一个当前的状态和动作,我们可以得到状态-动作或 q 值。
我们最初用随机数初始化 q 表,然后随着代理在各种状态下继续采取各种行动,继续更新每个状态-行动对的值。在代理对环境进行了一定程度的探索之后,我们可以通过使用 q 表来判断对于任何给定的状态应该采取哪种最佳行动!
是不是很疯狂!😄
让我们把手弄脏吧!
我们现在要学习如何实现 q-learning。这将是一次有趣的旅程。😄,系好你的安全带
我们从导入所有必需的库开始。
然后,我们定义了 q 学习算法和显示窗口的所有超参数。
接下来,我们为我们的代理、啤酒瓶(正奖励)和病毒(负奖励)定义图像的路径,并读取它们以显示在屏幕上。这只是为了让显示窗口看起来有趣。
我们需要创建一个名为【Blob】的类,我们的代理、啤酒瓶和病毒将继承这个类。一个 Blob 类对象将有一个相关联的生成位置(x 和 y 坐标),它将能够根据其‘移动’方法中传递的输入进行对角移动。我们也能够加减两个斑点对象。它将简单地增加或减少两个斑点对象的 x 和 y 坐标。
现在让我们来定义 q 表,它将被一次又一次地用来选择给定状态下的最佳行动。我们将不断更新此表,以提高我们代理商的决策能力。
请注意,这里我们对状态空间的取值范围是 -SIZE+1 到 SIZE 。这是因为我们将状态空间定义为啤酒瓶和病毒之间的相对位置。所以这可以是正面的,也可以是负面的。例如,如果网格世界的大小是 6,那么代理相对于啤酒瓶的相对位置将从-5 变化到 5。
现在不要担心更新 q 表中 q 值的更新规则。我们稍后会谈到这一点。
在每一集里,我们的特工、啤酒瓶和病毒都需要在特定网格世界的特定细胞位置繁殖。但是我们的 Blob 类只是在 grid-world 中的一个随机单元位置上产生了一些人。这可能会导致角色在同一位置产生,这是我们不希望的。为此,我们编写了一个函数,它将一个元组列表作为输入,该列表包含已经被繁殖的个体的坐标。这将确保任何两个家伙,代理人,啤酒瓶和病毒不共享他们的细胞位置。
设置和定义好一切后,我们开始训练代理(更新 q 表)。
这就是奇迹发生的地方。
让我们一行一行来理解。所以我们从一个循环开始,这个循环将运行规定的集数。
我们使用前面已经定义的get _ unique _ spacing _ location函数和 Blob 类来初始化播放器、啤酒和病毒对象。
然后,我们使用 SHOW_EVERY 参数来打印 epsilon 的当前值、到目前为止的平均奖励(这应该随着训练而增加)以及用于显示实际网格世界的显示参数‘SHOW’。
接下来,我们将‘插曲 _ 奖励’初始化为 0,代理开始采取 200 个时间步长的动作。
对于每个时间步长,我们需要环境的状态,我们将它定义为代理相对于啤酒瓶和病毒的相对位置。
那么代理将需要采取行动,这也将取决于ε的当前值。我们从均匀分布中抽取一个随机数,并将其与 epsilion 的当前值进行比较。如果随机值大于ε的当前值,则代理使用 q 表并选择 q 表中具有最大 q 值的动作。否则代理采取随机行动。
代理利用 q 表挑选具有最高 q 值的动作的第一种情况被称为利用,而代理采取随机动作的第二种情况被称为探索。参数ε负责开发-勘探权衡。
然后,我们检查代理是否接触到啤酒瓶,或者它是否被病毒感染。我们相应地定义当前时间步长的奖励。
然后代理采取行动,导致它在网格世界中的状态发生变化。
然后,我们计算新的 q 值,并使用下面提到的公式更新 q 表。

为了显示网格世界和这些家伙,我们制作了一个空画布,根据 DISPLAY_SIZE 参数调整大小,并将代理、啤酒瓶和病毒的图像粘贴到它们各自的当前位置。
最后,如果代理人得到了他的啤酒或者他被病毒感染了,我们就打破了这个循环。
我们计算移动平均线,并绘制回报图。
最后,我们保存更新的 q 表。
结果
下面是报酬移动平均值的曲线。它的上升趋势表明,随着越来越多的训练,代理变得越来越聪明。

移动平均数
这里有一些 gif 展示了代理如何在每一次训练中变得更聪明。
这是一个口渴的代理用随机初始化的 q 表寻找一瓶啤酒。这意味着代理还没有关于环境的线索。

愚蠢的代理人想要啤酒,但没有得到它
经过一些训练后,代理人在做出连续决策方面做得相对更好。他还不是很快,但他最终找到了啤酒。

嗯,这样更好
最后,经过数千次的训练,代理人真的很擅长做连续的决定,并且很快就找到了啤酒!😄

这才是个聪明的特工!b)
Q 学习的优势
这是一个易于理解和实施的简单概念。它非常适合没有巨大国家行为空间的环境。
Q 学习的局限性
q 学习包括从 q 表中查找。并且 q 表由每个可能的单个状态-动作对的值组成。因此,随着状态空间的增加,q 表的大小呈指数增长。因此,Q-Learning 有利于在状态-动作空间较小的环境中训练智能体。对于更复杂的设置,使用深度 Q 学习。
密码
你可以在我的 GitHub 账号上找到这个 q-learning 项目的代码,点击这个链接【https://github.com/n0obcoder/Q-Learning】
参考
- 【https://www.youtube.com/playlist? list = plqvvvaa 0 qudezjfiou 5 wddfy 4 e 9 vdnx-7
- https://www . coursera . org/learn/fundamentals-of-reinforcement-learning
- https://web . Stanford . edu/class/psych 209/Readings/suttonbartoiprlbook 2 nded . pdf
我写这篇博客是因为我通过阅读别人的博客学到了很多东西,我觉得我也应该尽可能多地写下并分享我的学习和知识。所以,请在下面的评论区留下你的反馈,让我知道如何改进我未来的博客!😄
我也是一个独立的音乐艺术家,喜欢在空闲时间演奏和录制音乐。也许你可以看看我的艺人页面,表示支持:)
Spotify 上的 8 楼和声!
在 Google Sheets 公式中使用 R 和 Python
Google Sheets 中的自定义编程语言功能

在 Unsplash 上由 Carlos Muza 拍摄的照片
谷歌床单太神奇了!
谷歌的电子表格版本叫做谷歌表格,它有一个隐藏的宝石,可以显著增加你的电子表格的功能,叫做谷歌应用程序脚本。Google Apps Script 使用 Javascript 调用其他 Google 服务,创建交互式弹出对话框,甚至进行 API 调用。通过一点设置,您可以创建电子表格公式供其他人使用,执行 R、Python 或几乎任何编程语言代码!虽然 Shiny 和 Dash 是分享您的工作的好方法,但是按照本文中的说明,您将能够直接在 Google Sheets 中创建 R 和 Python 仪表板。另外,这些方法允许您将 R 和 Python 功能插入到其他 Google 服务中,比如文档、表单和幻灯片!
这里有几个例子
- 矩阵乘法

作者图片
2。绘图(基数 R)

作者图片
3。绘图(ggplot2)

作者图片
4。部署模型

作者图片
5。任何 R 脚本!

作者图片
这是什么魔法?
在幕后,每个自定义函数都是一个 Google Apps 脚本,它对我在 Google Cloud Run 中设置的一个端点进行 API 调用。如果您可以设置运行 R、Python 或任何其他语言脚本的 API 端点,那么您可以将这些功能导入到 Google 电子表格中。这是惊人的!
尝试一下
下面的 6 个步骤从最容易到最难排列,将简单地让你开始使用你自己的定制的电子表格函数。关于 Google Apps 脚本、创建自己的 API 和 Google Cloud Run 还有很多内容可以在另一篇文章中介绍。
- 在 Google Drive 中创建电子表格
- 工具菜单- >脚本编辑器

作者图片
3.将下面的代码复制并粘贴到编辑器中。适应你的需求。
/**
* Run an R, Python, or other language script on spreadsheet data.
*
* [@param](http://twitter.com/param) var1 The value or cell contents to pass to your API.
* [@param](http://twitter.com/param) var2 The value or cell contents to pass to your API.
* [@return](http://twitter.com/return) The results of the API call you're about to make.
* [@customfunction](http://twitter.com/customfunction)
*/
function myCustomFunction(var1 = null, var2 = null){
// Assuming your API endpoint is like {baseURL}/{endpoint}.
var baseURL = '(Copy and paste API base url w/o trailing /)';
var endpoint = 'myCustomFunction';
// Encode the variable values as JSON (or XML, or something else).
// See Google Apps Script UrlFetchApp documentation.
var data = {
'var1': var1,
'var2': var2,
}// Set up the API call. Use POST requests to pass variables.
// You can pass variables as query params of GET requests instead.
var options = {
'method' : 'post',
'contentType': 'application/json',
'payload' : JSON.stringify(data)
};
// Make the API call. NOTE: Trailing slashes are important!
var response = UrlFetchApp.fetch(baseURL + '/' + endpoint + '/', options);
// Parse the response.
data = JSON.parse(response.getContentText());
// I return "Error: {the error}" on script errors.
// Not necessary, but it shows useful error messages in cells.
if(String(data).substring(0,6) == "Error:"){
throw(String(data));
}
return(data);
}
4.创建一个名为“Dockerfile”的不带扩展名的纯文本文件。 把它放在硬盘上自己的文件夹里。不要紧张;你不需要在你的系统上安装 Docker 来执行这些步骤(但是你绝对应该在你的日常工作中使用 Docker!)
将以下内容之一复制并粘贴到 docker 文件中。
- R 示例 1:这里有一个 R API 的 docker 文件,它使用了令人惊奇的 Plumber 包。这不是 Tidyverse 的产品,但它能创造出苗条的形象。
FROM trestletech/plumberCOPY [".", "./"]ENTRYPOINT ["R", "-e", "pr <- plumber::plumb(commandArgs()[4]); pr$run(host='0.0.0.0', port=as.numeric(Sys.getenv('PORT')), swagger = T)"]CMD ["Plumber.R"]
- R 示例 2:这里有一个 R API 的 rocker 文件,它确实带有 Tidyverse 和 Tensorflow(基于 rocker/ml image )。这就产生了臃肿的图像,并附带了 RStudio。它不是用来生产的。
FROM samterfa/rmlsheetsCOPY [".", "./"]ENTRYPOINT ["Rscript", "-e", "pr <- plumber::plumb(commandArgs()[9]); pr$run(host='0.0.0.0', port=as.numeric(Sys.getenv('PORT')), swagger = T)"]CMD ["Plumber.R"]
- Python 示例:这里有一个 Dockerfile 文件,它使用了名副其实的快速 API 。
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7RUN pip install pandasCOPY [".", "./"]
5.创建支持自定义功能的脚本。将以下文件添加到与您的 docker 文件相同的文件夹中。请注意,Google Sheets 中的单元格引用是作为单个单元格的单个值或多个单元格的嵌套列表传入的。
- R API 文件:创建一个名为 Plumber.R 的文件。
# Swagger docs at ...s/__swagger__/ (needs trailing slash!)
if(Sys.getenv('PORT') == '') Sys.setenv(PORT = 8000)#' [@apiTitle](http://twitter.com/apiTitle) R Google Sheets Formulas
#' [@apiDescription](http://twitter.com/apiDescription) These endpoints allow the user to create custom functions in Google spreadsheets which call R functions.#* Return the product of 2 matrices
#* [@param](http://twitter.com/param) var1 An array of values representing the first matrix.
#* [@param](http://twitter.com/param) var2 An array of values representing the second matrix.
#* [@post](http://twitter.com/post) /myCustomFunction/
function(var1, var2){
err <- tryCatch({
return(data.matrix(var1) %*% data.matrix(var2))
}, error = function(e) e)
return(paste0('Error: ', err$message))
}#* Confirmation Message
#* [@get](http://twitter.com/get) /
function(msg=""){
"My API Deployed!"
}
- Python API 文件:创建一个名为 Main.py 的文件。
from fastapi import FastAPI
from pydantic import BaseModelclass Matrices(BaseModel):
var1: list
var2: list
app = FastAPI()[@app](http://twitter.com/app).post("/myCustomFunction/")
def myCustomFunction(matrices: Matrices):
import sys
import numpy as np try:
var1 = np.matrix(matrices.var1)
var2 = np.matrix(matrices.var2)
results = np.matmul(var1, var2)
return np.array(results).tolist()
except: e_type, e_value, e_traceback = sys.exc_info()
return 'Error: ' + str(e_type) + ' ' + str(e_value)@app.get("/")
def myAPIdeployed(): return "My API Deployed!"
注意:要在 Google Sheet 中运行任意的 R 或 Python 脚本,该脚本需要确定传递的数组是否包含列名。代码有点笨拙,但是我在本文的结尾加入了一个 R 尝试。
6.部署您的 API(到 Google Cloud Run) 如果您已经知道如何部署 R 或 Python API,那么您可能已经停止阅读这篇文章并开始比赛了!对于我们其余的人,我将指导您使用 Google Cloud Run 来托管您的 API。
马克·埃德蒙森创造了一个惊人的 R 包,名为 googleCloudRunner 。这个包处理 auth,创建构建,并将容器部署到 Google Cloud Run。这真的很了不起。如果您没有安装 R,您可以遵循这些项目设置说明,以及这些部署说明,记住我们正在尝试从上面部署我们的 Dockerfile。从现在开始,我将假设您已经安装了 R running 和 googleCloudRunner。
- 转到https://console.cloud.google.com/
- 如果您以前从未创建过项目,请同意条款和服务。
- 点击选择一个项目,然后新建项目。
- 命名并创建项目。我喜欢脚本、Pyscripts 或类似的东西。
- 确保项目名称在左上方的下拉列表中。
- 点击左侧下拉菜单,点击计费,然后添加计费账户。我保证,很便宜!

作者图片
- 对于其余的设置,遵循马克埃德蒙森的指示这里。
- 确定包含 docker 文件和 API 文件的文件夹的路径。姑且称之为“/用户/我/我的/API/路径”。
- 使用 R,运行以下命令来部署您的 API!在 Google Cloud Run 中创建的服务的名称将是文件夹路径中最后一个文件夹的名称,所以请确保您喜欢这个名称,并且它的名称是小写的。
googleCloudRunner::cr_deploy_run(local = "/users/me/my/api/path")# Wait, that's it? Just that?
- 将打开一个浏览器,显示您的构建进度。如果有错误,您可以使用日志消息来确定哪里出错了。常见的问题是缺少服务帐户权限和代码问题。如果您对 Docker 感到满意,可以在本地进行测试,以确保没有代码问题。如果成功,您应该会看到一条消息(如果您复制并粘贴了我的代码)。

作者图片
- 一旦启动,你需要将上面的 API URL 复制并粘贴到你的 Google Apps 脚本中。

作者图片
- 试试吧!在 Google 工作表中创建两个数字数组,它们可以作为矩阵相乘,开始输入“=myCustomFunction(",突出显示单元格,然后给自己留下深刻印象。

作者图片
资源
- 免费 APISwagger网页 这些都是自动为你生成的,因为你用了 R 包 Plumber 或者基于 Python 的 FastAPI。
- r 版本位于{YourBaseURL}/swagger/

作者图片
- Python 版本位于{YourBaseURL}/docs

作者图片
2.samterfa/rpysheets****GitHub Repo这个 Repo 包含上面列出的所有文件加上一些额外的材料。奖励材料包括通用脚本函数,以及 MNIST 数字预测函数。如果你想对大型模型进行预测,谷歌表单会给你 30 秒的时间限制。你可能还需要增强你的谷歌云运行服务。
使用 R 和 SQL 倡导哈莱姆住宅修缮
为纽约市工作人员建立维修清单,以帮助哈莱姆区经济适用房的维修合规性

纽约市的经济适用房
经历了多年的预算短缺和不断增加的债务,纽约市住房管理局(NYCHA)在维护其五个区的 326 个公共住房开发项目的基础设施方面严重落后。
来自纽约市 40 多万租户的令人痛心的紧急维修报告每周出现在周刊上。
根据纽约市公共辩护律师办公室的数据,截至 2019 年 11 月,纽约市建筑的未完成工作订单接近 35 万份(自 2018 年以来增长了 43%)。随着这一毁灭性的消息,朱玛恩·威廉姆斯(Jumaane Williams)在 2019 年 12 月发布年度房东观察名单时,将纽约市视为“2019 年最糟糕的房东”。
作为法院创新中心的高级研究和数据助理,我的部分工作是支持哈莱姆社区司法中心(HCJC) 。21 世纪初,HCJC 作为一个解决问题的法庭而成立,为居住在哈莱姆区及其周边的个人提供支持。
我在那里承担的一个特殊项目是帮助员工跟踪住房情况。作为一个基于社区的中心,HCJC 的一部分工作是帮助倡导租户的权利,要么帮助哈莱姆区的居民向住房法院提起诉讼,要么推动纽约市跟进过时的维修——所有这些文书工作和咨询都发生在东 121 街 170 号的住房和资源中心。自该中心在 2018 年底开始跟踪这些信息以来,收到的 2000+次上门服务中,超过一半来自整个哈莱姆社区 NYCHA 大楼的租户。
如前所述,随着纽约市越来越多的维修工作订单,HCJC 决定加倍努力,利用其内部数据库向纽约市的工作人员发送维修时间表,以加快维修过程,并代表其客户进行宣传。
为此,我决定采用数据科学方法,在 SQL 和 R 中构建一个自动化流程,将我们的住房和资源中心数据从我们的内部数据库中提取到最终的 Excel 电子表格中,该电子表格列出了计划在一周的特定日期进行维修的租户的信息(请参见下面的最终产品截图)。

此工作流创建的自动化租户列表的示例。请注意,真实姓名和信息已从该截图中删除,但实际输出包括我们系统中每个租户的联系信息,计划访问日期为该周/日。
以下是工作流程的完整概述,从一名哈莱姆居民走进哈莱姆社区司法中心的大门,到发送给纽约市工作人员以跟进维修的 Excel 列表:
维修案例和自动化工作流程

我们将讨论其中的自动化部分,从 SQL 步骤开始,一直到创建 Excel 文件。
SQL 查询
spool "C:\directory_name\data.csv"
SELECT /*csv*/ distinct
.
.
.
FROM
internal tables
.
.
.
WHERE
date range variable and other parameters
spool off;
我不能共享用于该查询的特定表和变量,但是需要注意的重要一点是,我在 SQL 查询的开始声明了一个文件路径,然后指示脚本将信息放入该目录中的一个. csv 文件中。完成后,该目录将有一个假脱机列表。csv 文件本质上是从查询中创建的 SQL 视图:

一旦 SQL 脚本运行完毕(不到 20 秒),我就可以打开我的 R 脚本并在这些新的。来自 SQL 的 csv 文件。
r 脚本
打开我的 R 脚本后,我会高亮显示整个脚本并点击“Run”。在运行的 15 秒钟内,它将完成以下步骤(为了节省时间,我将省略其他一些步骤):
使用的库
library(devtools)
library(statsr)
library(dplyr)
library(ggplot2)
library(tidyverse)
library(readxlsx)
library(Hmisc)
library(plyr)
library(DT)
library(XLConnect)
library(lubridate)
library(reshape)
library(xlsx)
- 创建一个新的 Excel 电子表格,最终保存我的最终日历/列表数据:
# GET CURRENT DATE
date <- format(Sys.Date(), "%m-%d-%Y")# EXCEL SHEET CREATED WEEKLY FOR NYCHA ACCESS/FOLLOW-UP REPAIRS
new_filename <- paste("internal_directory/nycha_accessfollowup_", date, ".xlsx", sep = "")# SAVING THE WORKBOOK IN A VARIABLE
nycha_summary <- loadWorkbook(new_filename)saveWorkbook(nycha_summary)
2.正在加载假脱机的 SQL。我之前声明的目录中的 csv 文件:
# SAVING THE SPOOLED CSV FILENAME IN A VARIABLE TO USE LATERrepair_file_name <- "repair-complaints.csv"# SET THE WORKING DIRECTORYsetwd("C:/Help Center/Zach-Housing-Resource-Center/SQL_outputs")# FILES TO LOAD repairsData <- read_csv(repair_file_name)
lengthOfData_repairs <- length(repairsData$`Episode ID`)-1
repairsData <- slice(repairsData, 1:lengthOfData_repairs)
3.一旦。csv 文件已加载,我知道根据我们从数据库收到的内部案例注释的结构,我需要创建几个函数来筛选这些条目并识别未完成的维修案例,将这些条目与其他住房案例分开,然后创建几个列来区分:
- “无法进入”案例——被记录为 NYCHA 未能在预定日期出现在租户公寓进行维修的案例
- “待定访问”案例—已安排但尚未实施的案例
- “成功访问”案例 NYCHA 确实在预定日期参与了维修工作
cleanNotes <- function(dataset, dataLength){
dataset <- dataset
for (x in 1:dataLength){
if(isTRUE(dataset$Note_category[x] == "Successful Access NA NA")){
dataset$Note_category[x] = "Successful Access"
} else if(isTRUE(dataset$Note_category[x] == "NA NA NA")){
dataset$Note_category[x] = NA
} else if(isTRUE(dataset$Note_category[x] == "NA NA Pending Access")){
dataset$Note_category[x] = "Pending Access"
} else if (isTRUE(dataset$Note_category[x] == "NA Failed Access NA")){
dataset$Note_category[x] = "Failed Access"
} else {
dataset$Note_category[x] = NA
}
}
return(dataset)
}
4.创建最终清洁功能,将上述功能与一些附加功能结合起来:
dataCleaning <- function(dataset, dataLength){
dataset <- dataset
dataset <- addAccessField(dataset)
dataset <- checkforSuccessAccess(dataset)
dataset <- checkforFailedAccess(dataset)
dataset <- checkforPendingAccess(dataset)
dataset <- dataset %>%
mutate(Note_category = paste(Access_success, Access_failed, Access_pending))
dataset <- cleanNotes(dataset, dataLength)
dataset <- selectColumns(dataset)
dataset <- parseAccess(dataset, dataLength)
dataset <- followupDecipher(dataset, dataLength)
dataset <- reminderDecipher(dataset, dataLength)
dataset <- failedAccessTenant(dataset, dataLength)
return(dataset)
}
5.隔离本周的日期:
# SEQUENCE OF MONDAYS AND FORMATTING INTO A TIBBLEmondays <- seq(as.Date("2019/01/08"), as.Date("2022/01/04"), "7 days")mondays <- format(as.POSIXct(mondays), "%m-%d-%Y")
workweek <- tibble(mondays = 1:157)# ISOLATING MONDAYS AND FRIDAYS IN THE WEEK AND DATE FORMATTINGworkweek$mondays <- mondays
workweek$fridays <- format(as.Date(mondays, format = "%m-%d-%Y") + 4, "%m-%d-%Y")# CALCULATE DAYS OF THE CURRENT WEEKcurrent_Monday <- workweek$mondays
current_Tuesday <- format(as.Date(workweek$mondays, format = "%m-%d-%Y") + 1, "%m-%d-%Y")
current_Wednesday <- format(as.Date(workweek$mondays, format = "%m-%d-%Y") + 2, "%m-%d-%Y")
current_Thursday <- format(as.Date(workweek$mondays, format = "%m-%d-%Y") + 3, "%m-%d-%Y")
current_Friday <- workweek$fridays
正如你在上面看到的,我创建了一个长列表,包含从 2019 年初到 2022 年初的每个星期一的日期。然后,从这个列表中,通过在脚本的第一部分分离出当前日期,我创建了一周的其余日子。
6.过滤维修信息以仅存储当前周的案例数据框,并将其处理成最终产品的日历/列表格式:
for(y in valuesLength){
indexIWant <- which(!is.na(access_calendar[[y]]))
for(j in valueOne){
access_calendar[, columnName[y]] <- NA
for(i in indexIWant){
access_calendar[j,columnName[y]] <- pendingAccess_week$`Full Name`[i]
access_calendar[j+1,columnName[y]] <- ifelse(is.na(pendingAccess_week$`Development Name`[i]), 'No Development Name Provided', pendingAccess_week$`Development Name`[i])
access_calendar[j+2,columnName[y]] <- pendingAccess_week$`Address`[i]
access_calendar[j+3,columnName[y]] <- ifelse(is.na(pendingAccess_week$`Home Number`[i]), 'No Home Phone # Provided', pendingAccess_week$`Home Number`[i])
access_calendar[j+4,columnName[y]] <- ifelse(is.na(pendingAccess_week$`Mobile Number`[i]), 'No Mobile Phone # Provided', pendingAccess_week$`Mobile Number`[i])
access_calendar[j+5,columnName[y]] <- pendingAccess_week$`Case Note`[i]
access_calendar$index[j] <- j
access_calendar$index[j+1] <- j
access_calendar$index[j+2] <- j
access_calendar$index[j+3] <- j
access_calendar$index[j+4] <- j
access_calendar$index[j+5] <- j
j <- j + 6
}
}
}
从上面的 R 语法中可以看出,我创建了两个“for”循环来遍历一周中的日期以及在一周中有预定访问日期的租户的案例信息(每个租户总共 7 行)。
创建数据框后,我们可以在 R:

R 数据框快照,包含访问日期的租户列表。
尽管出于保密原因,许多访问信息不得不被隐藏,但您可以看到,最终的数据框根据一周中的某一天以及 NYCHA 联系居民所需的重要租户信息,以整齐的列和行打印租户信息。
最终 Excel 输出
数据框在 R 中正确格式化后,剩下的唯一事情就是将数据框保存在我之前创建的 Excel 电子表格中,我在 R 脚本的开头表示:
# SAVE THE DATA FRAME IN AN EXCEL SPREADSHEETwb <- openxlsx::write.xlsx(access_calendar, file = new_filename, col.names = TRUE)
我之前也分享过的最终产品如下:

自动化流程的最终 Excel 输出。
从运行 SQL 查询到获得 Excel 输出的整个过程每周花费大约 1 分钟。
这一进程的初步效果
对于这样一个每周一次的任务,利用 R 和 SQL 来自动化这个过程是必不可少的。
很难将维修合规率的提高直接归因于我们对该流程的实施,但是,早期的分析表明,我们对这类工具的跟进和尽职调查为与住房和资源中心互动的哈莱姆居民带来了更好的结果。至少,我们能够向 NYCHA 工作人员和利益相关者提供的更多信息只会有助于他们跟进居民的维修投诉。
当我们寻求在这些问题上与城市机构更紧密地合作,并最终帮助整个哈莱姆社区的居民时,自动化工具将在沟通各方和协助赤字方面发挥越来越重要的作用,例如及时解决经济适用房维修案件。
第一次使用 R?
最近,我的任务是让一名实习生熟悉 R。当我考虑如何最好地指导他时,我想起了当 R 对我来说完全陌生时,我是多么努力地站稳脚跟。我最初发现了这么多解释各种包、最佳实践和分析的伟大资源,但是我甚至不知道在哪里键入我的代码!我最终通过 YouTube 视频拼凑出了对 R 用法的大致理解,感觉自己正在成功前进。
然后,我很幸运地在研究生院学习了一个非常棒的基于 R 的数据科学资源,并意识到我的代码一团糟。我不知道 R 项目和 R Markdown 文件的存在,所以我所有的代码都存在于桌面上组织混乱的脚本中。我通过给自己发电子邮件来备份我的工作,并利用了现在让我畏缩的变量名和间距。那门课程帮助我学会了如何创建不仅可以运行,而且可重复的代码,这有助于我的数据分析团队进行有效的协作。
希望帮助这位实习生避免我最初的错误,我编写了以下 r 入门指南。它是为完全的初学者设计的——第一部分实际上涵盖了安装软件的位置!我确保包含了我在数据科学课程中学到的许多重要经验,因此在创建本指南时经常参考课程网站。我分享这个链接不仅是为了赞扬杰夫·戈德史密斯的想法,也是为了给那些寻找超出这里所涵盖的材料的指导的人提供一个非常有用的资源。本指南当然不是详尽无遗的,但是应该可以让你用一个好的工作流开始并运行 R!
1。 安装软件
首先,您需要安装 R 和 RStudio!
r 下载链接:https://cloud.r-project.org/
从这里,选择您的操作系统并选择最新版本。
RStudio 下载链接:https://rstudio.com/products/rstudio/download/
从这里,选择免费的 RStudio 桌面选项。
2。 熟悉 RStudio
打开 RStudio,这应该是您看到的屏幕:

你在看什么?
蓝色的是您的控制台—您可以在这里运行不会作为脚本的一部分保存的代码(您可能希望使用此选项来安装一个包,或者快速查看一个表),在这里您将看到有关您已经运行的代码以及您可能收到的任何错误的信息。其他选项卡允许您访问您的终端和作业,但是随着您对 r 的熟悉,您将很少使用这些选项卡。
绿色的是环境。您创建的任何命名实体(如数据帧、向量、函数)将在此显示。通常,您可以单击环境中的内容进行查看,这在您加载外部数据并对其进行清理时非常有用。小扫帚图标可以用来清空这个环境。一旦我们设置了 git 和到 GitHub 的连接,这里也会看到一个“Git”标签。
红色是一个查看器窗格。目前它在“包”标签上,允许你看到什么包被安装在本地。带有勾号的是当前加载的。根据 RStudio 设置,代码输出可能会显示在“绘图”和“查看器”选项卡中。您可以在“帮助”标签中搜索软件包名称以获得有用的信息。
3。 文件类型

我将要经历的工作流尽可能利用 R Markdown 文件,但是我想快速介绍 R 脚本和闪亮的 Web 应用程序。
剧本:

脚本是用 R 编写代码的最基本的文件类型,通常用于存储最终将被部署的函数或其他代码。即使您在开始使用 R 时能够选择独占 R Markdown 用法,您也会经常收到来自他人的脚本代码,熟悉这种格式很重要。如您所见,蓝色框中包含的是一个新面板,现在您已经在脚本中了。这是您可以输入代码的地方。
我已经创建了三个命名值:“x”、“y”和“sum”,这是 x 和 y 的总和。由于这三个都是命名的,所以可以在环境面板中找到它们及其相关的数值。
看一下控制台。
当我运行这三行代码时,前三条蓝色的线出现了——它们只是表明代码已经运行了。
在第四行,我直接在控制台中键入“x + y”并按下“enter”下面的黑色文字提供了这个等式的答案。因为这只是在控制台中输入的,所以输出不会保存在任何地方,也不会影响脚本。
试着复制我这里非常简单的代码。你可以通过点击“运行”按钮来运行代码,或者在 Mac 上同时按下 command + return 键(对于 Windows,按下 control + enter )。
闪亮:
我不打算解释如何创建闪亮的应用程序,因为一旦你相当熟悉 R 语言编码,这是一个需要解决的问题,但是知道它们的存在是很好的。Shiny 允许您创建交互式仪表板,是一个非常好的数据可视化工具。您可以通过 RStudio 的服务器公开托管仪表板,将它们嵌入网站等等。
如果你感兴趣,这里有一些关于 Shiny 的进一步信息:https://shiny.rstudio.com/
R 降价文件:
使用 Markdown 文件是创建可重复的、可理解的代码的重要工具。除了编写代码的实际 Markdown 文件之外,您还可以将它“编织”成一个易于阅读的输出文件(我将解释这是什么意思),即使没有安装 R 也可以打开它。您可以从下面的气泡中选择想要的输出类型(HTML、PDF 和 Word)。我喜欢坚持使用 HTML,因为它格式好,允许在线共享,但我经常为最终报告创建 pdf,与数据分析团队以外的同事共享。

继续点击“确定”!这是您接下来应该看到的内容:

你在看什么?
蓝色是您的 YAML 标题,上面有您刚刚输入的信息。想要更改标题、日期或输出类型吗?通过编辑与标题、作者和日期相关联的引号之间的文本,以及将“html_document”替换为“pdf_document”或“word_document”,您可以很容易地做到这一点如果您对在输出文件中列出标题、作者或日期不感兴趣,您可以简单地删除相关的行。
绿色的是代码块。您的所有代码都将包含在代码块中。代码块都遵循以下格式:
```{r chunk_name, specifications}```
可以通过 option + command + i 快捷键(Windows 上, control + Alt + i )在 MAC 上直接插入代码块。您可以通过按块右上角的绿色箭头来运行单个代码块。
给你的代码块命名是一个好习惯。这对可能使用您的代码的任何人都很有用,也有助于在您的代码产生错误时有效地进行故障排除。您希望对代码块命名使用与变量命名相同的准则:唯一的、描述性的、不带空格。例如,您可能有名为“数据导入”、“数据清理”、“分析”和“绘图”的代码块。
您可以使用多种规范来更改任何给定代码块中的代码在输出文件中的显示方式,下面提供了一些最常用的规范:

https://p8105.com/writing_with_data.html 杰夫·戈德史密斯的名单
红色的是标题。您可以使用井号来确定输出文件中文件头的大小,每增加一个“#”会使文件头变小:
# Main Heading
## Subheading 1
### Subheading 2
灰色是常规文本,它将在输出文件中显示出来。好的 Markdown 文件通常有大量的文本来帮助其他人理解你的代码,使它更容易理解。文档记录良好的代码也将使您在以后回过头来看自己的代码时更容易理解!
知道可以加粗和斜体文本以及添加内联代码是很有用的:
*bold*
**italicize**
`r desired_inline_code`
一个有用的使用和格式化 R Markdown 文件的备忘单可以在这里找到:https://R studio . com/WP-content/uploads/2016/03/rmarkdown-cheat sheet-2.0 . pdf
橙色的是“编织”按钮——这就是你创建输出文件的方式!只有在代码中没有错误的情况下,文件才会被编织,因此编织也是检查工作的一种有用方式。现在尝试单击“编织”按钮,看看示例代码在输出文件中的样子。
4。 工作流和 Git
有效地、可重复地使用 R 的一个基本技能是拥有一个清晰的、标准化的工作流程。我将在这里使用 GitHub 演示一个,当你在 r 中工作时,你应该总是使用一致的工作流程。
Git 和 GitHub 是版本控制和代码共享的常用工具。为了开始,你需要在 GitHub 上创建一个免费账户:https://github.com/
接下来,您需要验证 git 是否已经安装并准备好在您的计算机上运行。为了做到这一点,你应该遵循这个指南:https://happygitwithr.com/install-git.html(尽管如果你愿意,你可以使用 RStudio 中的 terminal 选项卡而不是 shell 来遵循 6.1 中的说明——我发现这要容易得多)。
现在,您应该按照以下工作流程进行配置:
- 创建一个有意义命名的 GitHub 库
- 在 R 中创建新项目,并将其链接到此回购
- 将所有相关文件存储在该项目文件夹中,并使用有意义的命名、一致的子文件夹(如存储所有数据的“数据”文件夹)
- 使用 R 中的 Git 选项卡来一致地编织、提交和推送
让我们走一遍:
一、创建一个有意义命名的 GitHub 库
在 GitHub 中,点击存储库,然后点击新建:

在那里,您可以为您的 repo 命名并进行简要描述。这个报告包含了使用在线聊天数据的第二阶段研究的数据,因此被命名为“chat_phase_2”我知道这个名字将允许我在几个月后再次使用它时找到回购协议,并且我的合作者也将能够跟踪它。
您的回购可以是公开的(任何人都可以找到您的回购及其内容),也可以是私有的(只有您和指定的合作者可以看到它及其内容)。公开回购有许多很好的用途(例如,共享您创建的包,共享您创建的酷项目),但如果您正在使用包含机密信息的数据和/或正在进行尚未发表的研究,您总是希望使用私人回购。您可以在以后更改这些名称,但是应该始终小心您公开的信息。
通常,您会希望创建一个自述文件,以指导您自己和其他任何可能通过其内容访问您的 repo 的人。一份好的自述文件概述了你的报告的内容,并为你所做的工作提供了简要的理由。

单击“创建存储库”,然后复制将出现在以下页面上的链接:

二。在 R 中创建新项目,并将其链接到这个 repo
在 R 中,单击文件→新建项目
接下来,选择“带有版本控制的项目”:

选择 Git:

最后,将您从 GitHub 复制的 URL 粘贴到 Repository URL 下,然后单击“创建项目”。我已经在我的桌面上创建了我的项目文件夹,但是你可以在你电脑的任何地方浏览创建你的项目。

项目应该在 r 中打开。您将知道它工作了,因为您的项目将在屏幕的右上角显示为打开的项目,并且您现在将在右上角面板中有一个“Git”选项卡。

三世。将所有相关文件存储在该项目文件夹中,并使用有意义的命名、一致的子文件夹(如存储所有数据的“数据”文件夹)
既然您已经创建了一个本地项目和一个 GitHub repo,那么您会希望在进行过程中保持它们的整洁和有序。
我将数据添加到数据文件夹中的项目中,因此我当前的项目文件夹如下所示:

随着我的进展,任何额外的数据都将以一致的文件名添加到这个“数据”文件夹中,我将为我创建的任何重要图表或其他输出创建一个“输出”文件夹,并且我将有几个故意划分的 R Markdown 文件。例如,我可能有一个用来清理数据的 Markdown 文件,一个用来执行分析,一个用来生成可视化。
四世。使用 R 中的 Git 选项卡一致地编织、提交和推送
GitHub 只对版本控制有效,如果你坚持把你的工作推给他们的平台。为此,请点击 RStudio 中的“Git”选项卡。每当您对本地项目进行更改时,它们都会出现在该选项卡下。首先,单击自上次更新回购以来所做的所有更改。接下来,单击“提交”按钮。

您需要输入一条提交消息来描述您所做的更改。这使得其他人可以更容易地浏览您的回购协议,如果您以后需要访问它,也可以更容易地找到您代码的以前版本。
输入提交消息后,选择右下角的“提交”。如果运行无误,就可以使用“按钮”了。点击“推送”将您的更新发送到 GitHub。

现在你所有的修改都备份在 GitHub 上了!你会希望在工作中坚持这样做。我建议你第一次这样做的时候检查一下你的 GitHub 账户,看看你的作品是如何存储在那里的。如果你正在访问别人的回购,或者别人对你的回购做了更改,你可以使用“拉”将最新版本的回购拉至你的电脑。
5。 装载包裹
现在一切都设置好了,您可以开始在 R 中工作了!
首先,打开一个新的 R Markdown 文件。如果您在项目打开时单击“文件”→“新建文件”→“R Markdown ”(同样,如果您的项目的名称出现在窗口的右上角,您就可以知道该项目是打开的), R Markdown 文件将会在您的项目中自动创建。
R 自动带有“base R”函数。然而,R 是开源的(这意味着任何人都可以贡献包),并且有许多令人惊奇的包,你将需要有效地使用 R。您必须一次性安装这些软件包,然后在每次需要使用它们时加载它们。下面提供了这样做的代码(注意双引号表示安装包,单引号表示加载包)。我喜欢在控制台中安装包,因为我只需要这样做一次,因此没有理由保存安装包的代码行。您将在控制台中看到确认信息,表明软件包已正确安装和加载。
install.packages("tidyverse")
library('tidyverse')
注意:Tidyverse 实际上是包的集合而不是单个包。我强烈建议您熟悉 tidyverse 及其包含的软件包。它们被广泛使用和流行,因为它们会让你的生活变得无限简单——我每次创建一个新的减价文件时都会自动加载它。你可以在这里找到更多关于 tidyverse 及其组件包的信息:【https://www.tidyverse.org/
6。 其他注释
r 是区分大小写的,不像 SAS 和其他一些语言。这种区分大小写是使用一致的文件和变量命名结构如此重要的原因之一——您的代码中不太可能出现令人讨厌的错误,其他查看您代码的人也更容易理解它。我个人是专门用 snake case(everything _ is _ lower case _ with _ understand)的。
对于如何格式化代码,也有既定的指导方针。你应该努力让代码不仅运行,而且整洁!哈德利·韦翰的一本有用的时尚指南可以在这里找到:http://adv-r.had.co.nz/Style.html
7。 启动运行!
现在,您应该能够打开一个新的 R Markdown 文件,作为有意工作流的一部分,并在该文件中安装和加载包。这些技巧可以让你更好地利用精彩的教程,而不是带你在 r 中导入、清理、分析和可视化数据。
使用 R 分析我的 Strava 数据
使用 Strava 数据学习 R 的初学者之旅
有了这些空闲时间,我决定花相当多的时间来提高我的统计知识和编程技能。作为一名数据分析师,我每天都使用 SAS 和 SQL,但目前没有使用更高级的 R -这对于可视化、更高级的统计分析和机器学习非常有用。考虑到提高上述技能的愿望,我决定 R 是实现我的目标的一个好起点。
因为没有像现在这样的时间(确切地说是此刻),我要陷进去!
入门
首先,分析什么?当然,我可以在网上找到一些东西,有很多有趣的新冠肺炎数据集可以开始使用。然而,对我来说,当学习新的东西时,有一定程度的熟悉是好的,这样手头的任务看起来就不那么令人生畏了。在这种情况下,这种熟悉的形式是我的数据集;我过去 5 年的斯特拉发活动的输出。那就是 355 次跑步,7 次循环和 3 次游泳(对我喜欢的项目有什么猜测吗?如果我要在今年完成我的铁人三项的话,我也许应该多学学另外两项。).使用我自己的数据集对我来说很重要,因为如果我犯了错误,如果我知道并理解数据,我更有可能发现它。显然,在处理庞大的数据集时,情况并不总是这样,但当我是一个完全的 R 新手时,这是一个很好的起点。

Strava 数据样本(36 个变量中的 7 个)
虽然我通常会为我试图利用数据实现的目标设定目标(以便提供清晰性和方向),但在这种情况下,我已经偏离了轨道,虽然我已经熟悉了 R,但我的主要目标只是更好地理解该计划,同时希望得出一些有趣的见解。
因为我以前从未使用过 R,所以我在谷歌上搜索了一些初学者教程。我发现对 R and R 工作室的介绍真的很有用,也很容易理解。我没有像班上其他人一样使用样本数据集,而是使用我自己的数据,只是将我自己的变量和字段应用于函数,而不是导师给的那些。这是一个开始适应 R 的好地方,经过几个小时的基础学习,我觉得已经准备好开始构建自己的见解了。
完全公开,我肯定还在掌握 R,但我想分享我的想法和迄今为止我创造的情节的发现。
直方图
我制作的第一个图表是这个直方图。这相对容易做到(图片下面的代码),但它很好地显示了我在每个框中的活动频率。如你所见,我绝大多数的活动都是 5 公里。

活动距离直方图
#Histogram
hist(strava2$Distance, breaks = 21, main = "Histogram of Activity Distances", xlab = 'Distance (KM)')
散点图
然后我转向散点图,这花了我一段时间,主要是因为我在处理日期,这被证明是一个巨大的痛苦。我从 Strava 下载的 CSV 文件将活动日期生成为带有时间戳的日期,格式为 Jan 01,2016,07:30:00’,并作为字符传递给 R。您可能会认为转换成 date 类很简单,但是由于最后的时间戳,这就不那么简单了。在花了几个小时试图解决这个问题之后,我真的快要放弃了,用 Excel 来修改原始数据,这样可能会快得多。然而,我坚持了下来,并最终使用了“mutate”函数。我不确定我是否把这个问题复杂化了——我认为众所周知,时间序列数据在大多数程序中很难处理——但是我欢迎任何关于处理这个问题的最佳方法的想法。

活动散点图
我喜欢这个情节的输出。这是一个很好的方式来展示这些年来我的行为是如何改变的。这个图表非常清楚地表明,从 2018 年开始(大约是我第一次使用 Garmin 的时候),我的活动频率显著增加。你可以看到相当多的活动都在 5 公里左右。你还可以看到,从 2019 年年中开始,游泳和骑行开始出现在图表上(就在我进入我的铁人三项之后!).
#Remove time from the date/time timestamp
strava2 <- strava2 %>%
mutate(Activity.Date = as.Date(Activity.Date, format='%B %d, %Y, %H:%M:%S'))#Scatter Plot
ggplot(data=strava2, aes(x = Activity.Date, y = Distance, colour = Activity.Type)) +
geom_point() +
theme_bw() +
theme(legend.position = "bottom", legend.title = element_blank())+
theme(plot.title = element_text(hjust = 0.5))+
labs(title = "Activities",
colour = "Activity.Type",
y = "Distance (KM)",
x = "Date" + theme_bw(base_size = 15))
方块剧情
盒须图。这也不是太难(感谢 Stack overflow 上丰富的论坛、网站和帖子)。我可以花一些时间在颜色和格式上,但是为了节省时间,我放弃了。我发现 T4 的网站对颜色选择很有用。

按活动类型划分的距离箱线图
你在这里可以看到,与跑步相比,游泳框图相对较短(尽管数据集中只有少数游泳)。这表明我的游泳距离相似,而我的跑步距离相差更大。
我跑步的四分位数范围(中间 50%的数据)在 5 到 7 公里之间。这意味着我跑的 50%的距离在 5-7 公里之间。
#Boxplot
boxplot(Distance~Activity.Type,
data=strava2,
xlab="Activity Type",
ylab="Distance (KM)",
cex.lab=0.75,
cex.axis=0.75,
col="lightsalmon",
border="lightsalmon3"
)
柱状图
我想将我的数据分组,以便按月份和年份查看趋势。我最初认为按月计算我每天的路程会很困难,但惊喜地发现这相当简单。

按月的距离条形图
这是一个很好的图表,可以快速提取见解并讲述一个故事。从 2019 年初开始,我可以看到我的活动有所减少,特别是在我受伤的 2 月份。我还可以看到 2020 年 3 月我的总距离出现了巨大的峰值,当时我们被告知在家工作,并进入了锁定状态,这意味着更多的时间和锻炼的需要。我在 4 月初导出了这些数据,但我认为,到月底,4 月的数据会比 3 月的数据更高,因为跑步是我在禁闭期间的主要安慰来源之一。
有趣的是,看到每年七月我的活动量显著下降。起初这有点令人惊讶,但当我仔细想想,这是有道理的。首先,7 月份是我的生日,其次,在过去连续三年(2017-19 年)里,我一直是我姐姐和我两个最好朋友 7 月份婚礼的伴娘,而且(有时)天气很热,比起长跑,我更想在外面喝点杜松子酒。所有这些因素意味着跑步可以而且已经退居二线(相当正确)。然而,我敢打赌,如果我画出活动的数量(而不是距离),它看起来不会有太大的不同。我想这与其说是因为我不经常跑步,不如说是因为我跑的距离更短(我的意思是,当气温为 25 度,并且可以与家人和朋友一起享受乐趣时,谁会愿意跑 15 公里呢?不是我)。
#Bar Plot
barplot(t(rowsum(strava2$Distance,
format(strava2$Activity.Date,"%Y-%m"))),
las=2, col = "darkolivegreen3",
cex.lab=0.75, cex.axis=0.75, cex.names = 0.75,
xlab="Month",
ylab= "Distance (KM)"
)
总结
我在这里主要关注的是距离,但还有很多可以做的。我想接下来我会研究我跑步的速度,以及它是如何随着一天的时间、季节、年份等变化的。例如,我可以研究我是否更有可能在早上、下午或晚上跑得更快,以及这是否有意义。
这很有挑战性,但我真的很喜欢开始使用 R,我期待着很快用它做更复杂的分析。
使用 R 分析我的 Strava 数据:第 2 部分
在我的第一篇博文中,我讨论了想要学习 R 的原因,并分享了我在掌握该软件的前三周内所取得的成就。在这篇文章中,我将继续阐述我所学到的东西和我所遇到的更多困难,并希望与其他 R 初学者分享一些有趣的见解和有用的技巧。
这里提醒一下我的数据集中的一些变量。

Strava 数据样本
累积线状图
首先是累积线图,它显示了我从 2017 年开始的距离的累积和。很高兴看到 2020 年有了一个良好的开端(在运动方面!).

每年累积距离
由于这个输出需要将多个操作串在一起,管道操作符(来自 dplyr 包)真的派上了用场,它可以帮助跟踪我正在做的事情,因为它以逻辑步骤来布局代码。管道操作符实际上翻译成‘然后’。所以在下面的例子中,我说:获取我的 Strava 数据集,然后按年份分组,然后按活动日期的顺序排列,然后将距离变量突变为该年距离的累积和,等等。这可以在没有管道操作员的情况下完成,但在我看来,这样做要干净得多,也更容易阅读。
#Cumulative distance per year
cum_plot <- strava2 %>%
group_by(year) %>%
arrange(Activity.Date) %>%
mutate(cumulative_distance = cumsum(Distance)) %>%
ungroup() %>%
filter(year >= 2017) %>%
ggplot(aes(x = day_of_year, y = cumulative_distance, color = factor(year))) +
geom_line() +
scale_color_brewer(palette = "Dark2") +
theme(legend.position = "bottom") +
#theme(panel.background = element_blank())+
labs(title = "Cumultative Distance (KM)",
y = "Distance (KM)",
x = "Day of Year",
colour = "Year")
这个视频解释了如何使用管道操作符,以及 dyplyr 包中最有用的函数(选择、过滤、排列、变异、总结和分组)。我曾多次提到它,并向其他刚开始在 r 的人推荐一款手表。
小提琴剧情
在我的上一篇博客文章中,我展示了一个按活动类型划分的方框图,但鉴于我原本打算在 8 月份进行的伦敦铁人三项赛最近被推迟到 2021 年,并且由于数据中明显缺乏周期和游泳,我认为可能值得只为跑步做一个变化的方框图。
进入小提琴情节。这个图的小提琴形状来自于数据的密度。较厚的部分意味着小提琴该部分的值具有较高的频率,而较薄的部分意味着较低的频率。

按月距离的小提琴图
事实上,六月和七月的小提琴比其他月份短得多,宽得多,这证明了我的假设,即在夏季,我更有可能进行短途旅行。
#Violin plot
strava_runs <- filter(strava2, Activity.Type == “Run” )
ggplot(strava_runs, aes (x = calendar_month, y = Distance, fill = calendar_month)) +
geom_violin() +
scale_fill_brewer(palette=”Set3") +
theme(legend.position=’none’) +
labs(y = “ Distance (KM)”,
x = “Month”)
由于我的行为按月有明显的不同,我决定把我的跑步按季节分组是值得的。这个变量并不在我的数据集中,所以我使用了一个“case when”语句将每个月归入各自的季节。
#Runs by season
strava2 <- strava2 %>% mutate (Season = case_when (
calendar_month %in% c(“Mar”, “Apr”, “May”) ~ “Spring”,
calendar_month %in% c(“Jun”, “Jul”, “Aug”) ~ “Summer”,
calendar_month %in% c(“Sep”, “Oct”, “Nov”) ~ “Autumn”,
calendar_month %in% c(“Dec”, “Jan”, “Feb”) ~ “Winter”,
TRUE ~ “Others” ))

就像我之前说的,夏天不适合长跑!
密度脊
在与一位朋友分享了我之前的博客后,他指出,鉴于我的一些图表中显示的 5 公里活动的集中程度,我的数据集中可能存在跑偏。我认为情况很可能是这样;数据集内 355 次运行中的 70 次(20%)是停车运行-运行。
这一观察让我想到,按照一天中的小时和一周中的天来可视化我的跑步,来可视化我的数据集中 Parkrun 的普遍性,这将会很有趣。对密度脊线图进行排队,这是显示特定组的数值分布的理想方式,在本例中,为星期几和一天中的小时。

按一周中的每一天和一天中的每一小时分配活动
周六上午 9 点的高峰是显而易见的,我显然是一个超级 Parkrun 粉丝!我还发现很有趣的是,在周一早上,我不太可能锻炼,而在周五和周六晚上,你同样不太可能发现我在跑步(不言自明)。从美学的角度来看,我喜欢这张图,但密度脊可能会对这样的数据产生误导,所以事后来看,条形图或直方图可能是更好的选择,因为它们不会平滑点之间的数据。
这个图花了一段时间才完成,主要是因为 Strava 数据下载的日期和时间戳有更多的问题。一旦我解决了如何将时间从 12 小时制转换为 24 小时制,我就回到了正轨,但在按照逻辑顺序安排一周中的日子时遇到了一些小困难(我对其他有类似问题的人使用了“因子”和“级别”)。
我选择了柔和的配色方案,但在 Brewer palette 中有许多不错的调色板,在这里可以找到。
# Density Ridges
density_ridge <- strava2$wday <- factor(strava2$wday, levels = c(“Sun”, “Sat”, “Fri”, “Thu”, “Wed”,”Tue”,”Mon”))
ggplot(strava2, aes(x = Hour, y = wday, fill = wday )) +
geom_density_ridges(scale = 1.1) +
theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank(), panel.background = element_blank())+
scale_x_continuous(breaks=seq(0,24,2)) +
scale_y_discrete(expand = c(0.01, 0) , labels = c(“Sun”, “Sat”, “Fri”, “Thu”, “Wed”,”Tue”,”Mon”))+
theme(axis.text=element_text(size=rel(0.8))) +
theme(legend.position=’none’) +
scale_fill_brewer(palette=”Pastel2") +
ylab(NULL)
几何十六进制
当数据集中有大量数据点时,这种类型的图对于表示两个数值变量(在本例中为经过的分钟数和距离)的关系非常有用。不是重叠,而是将绘图窗口分割成几个‘六边形格’,并计算每个六边形格的点数。颜色表示这个点数。

按距离划分的已用分钟数的几何十六进制曲线图
正如我在其他图表中展示的那样,活动主要集中在 5 公里范围内。但是该图增加了一个额外的维度,显示了经过的分钟数,您可以清楚地看到,大多数活动都在 5 公里的距离内,大约需要 25 分钟。然而,在这个图中丢失的是我的 5 公里跑所用时间的变化;25 分钟和 22 分钟的 5000 米之间的差别非常显著,我觉得这一点在这里被忽略了。
#Geom hex
filter(strava2, Activity.Type == ‘Run’ & Elapsed.Mins < 150) %>%
ggplot(aes(x = Distance, y = Elapsed.Mins)) +
geom_hex() + scale_fill_distiller(palette = “Spectral”) +
labs(y = “Elapsed Minutes”,
x = “Distance (KM)”) +
theme(panel.background = element_rect(fill = “white”),
panel.border = element_rect(colour = “lightgrey”, fill = NA, linetype = 1, size = 0.3),
panel.grid.major.x = element_line(colour = “lightgrey”, linetype = 3, size = 0.3),
panel.grid.major.y = element_line(colour = “lightgrey”, linetype = 3, size = 0.3)
创建主题
正如你在上面的代码中看到的,我在很多文本中格式化了情节的小细节。然而,作为一个 R 新手,我最近发现了一些我认为可以节省大量时间的东西。这就是创建你自己的主题的能力,这样你就不必在每次创建一个情节时改变你的视觉化的小细节(游戏改变者!).如何创建自己的主题在这段视频中解释得非常清楚。
这是我的主题示例:
#Create my own theme
theme_rosie <- function() {
theme(
# add border 1)
panel.border = element_rect(colour = “lightgrey”, fill = NA, linetype = 1),
# color background 2)
panel.background = element_rect(fill = “white”),
# modify grid 3)
panel.grid.major.x = element_line(colour = “lightgrey”, linetype = 3, size = 0.5),
panel.grid.minor.x = element_blank(),
panel.grid.major.y = element_line(colour = “lightgrey”, linetype = 3, size = 0.5),
panel.grid.minor.y = element_blank(),
# modify text, axis and colour 4) and 5)
axis.text = element_text(colour = “black”, family = “Arial”),
axis.title = element_text(colour = “black”, family = “Arial”),
axis.ticks = element_line(colour = “black”),
# legend at the bottom 6)
legend.position = “bottom”
)
}
平滑线散点图
在过去的几个月里,我感觉自己比以往任何时候都更有规律地跑步,尽管如此,还是有很多天我发现跑步真的很难,即使是在我以通常感觉舒适的速度跑步的时候。一个人跑步时的感觉取决于许多因素,但我认为值得做一个基本测试,将我的距离总和与我每月的平均时间进行比较,看看这两个变量之间是否有任何相关性。我试图回答的问题是:你投入的越多(就距离而言),你就越能以更快的速度获得更多的回报,还是事实上当你投入更多时,你会发现自己很疲劳,因此跑得更慢?
显然,有很多因素会影响你的速度,从温度到一天中的时间等等,更不用说你还做了什么其他的健身运动,比如交叉训练和速度训练。然而,为了这个练习的目的,我只关注了我每个月的总距离,仅仅是我的 5 公里跑。

有趣的是,看起来我每月跑的 5 公里越多,我的平均时间就越慢。所以说到跑步,总的来说,我投入的越少,出去的就越多。虽然这可能不是我生活的座右铭…
#Filter, group and summarise: 5K Runs
five_k_runs <- strava2 %>%
filter(Activity.Type == ‘Run’, Distance_Rounded == 5, Elapsed.Mins < 70) %>%
group_by(year, calendar_month) %>% summarise (Sum_Distance= sum(Distance_Rounded), Avg_Time = mean(Elapsed.Mins), Num_Runs = length(Activity.ID))
#create plot
avg_5k_by_month <-
five_k_runs %>%
group_by(year) %>%
ungroup() %>%
filter(year >= 2018, year <= 2019) %>%
ggplot(aes(x = calendar_month, y = Avg_Time, color = factor(year))) +
geom_point(aes(size = Sum_Distance)) +
guides( size = FALSE) +
scale_color_brewer(palette = “Dark2”) +
theme(legend.position = “bottom”) +
labs(y = “Avg 5K Time (Minutes)”,
x = “Month”,
colour = “Year”) +
geom_text(aes(label=Sum_Distance),hjust=2, vjust=2, size = 3)
#apply theme
avg_5k_by_month + theme_rosie()
总结
我确信我只是触及了可以做的事情的表面,但是使用 R 来分析我的 Strava 数据是一项非常有价值的活动,原因有很多。
首先,它让我对自己的训练有了更深刻的认识。这是我想强调的地方,比如夏季的长距离跑和冬季的长距离跑。这让我想在增加距离方面更进一步——我能经常跑 5 公里以上吗?这让我开始考虑天气数据和跑步后情绪跟踪等额外信息如何为我的洞察力增加额外的维度。这激励我继续努力工作。
就学习 R 本身而言,这是一个很好的方式来更好地理解它是如何工作的,以及 if 必须提供什么。这让我意识到 R 实际上并不像我预期的那么难学,但也让我更加确信你花时间去练习是很重要的。就个人而言,我认为使用你感兴趣的数据集是一种非常愉快的学习方式。无论是 Strava,英超联赛统计数据还是你的 Spotify 收听历史,如果你觉得有趣,它更有可能让你有动力继续下去。
在 Python 中使用 Reduce
如何使用 Python 中的 reduce 函数

凯文·Ku 在 Unsplash 上的照片
介绍
Python 是一种面向对象的编程(OOP)语言。然而,它提供了一些提供函数式编程风格的工具。其中一些工具包括 map()、filter()和 reduce()函数。在本教程中,我们将探索 reduce()函数,并揭示它提供的多功能性和实用性。
贴图()和滤镜()函数在这里详细介绍:
如何使用 python 中内置的地图和过滤功能
towardsdatascience.com](/using-map-and-filter-in-python-ffdfa8b97520)
使用 For 循环
引入 reduce()函数的最佳方式是从一个问题开始,尝试用传统的方法解决它,使用 for 循环。然后,我们可以使用 reduce 函数尝试相同的任务。
假设我们有一个数字列表,我们想返回他们的产品。换句话说,我们希望将列表中的所有数字相乘并返回一个值。我们可以使用 for 循环来实现这一点:
我们有我们的数字列表。我们想把这个列表中的数字相乘,得到它们的乘积。我们创建变量 product 并将其设置为 1。然后,我们使用 for 循环遍历 num_list ,并将每个数字乘以前一次迭代的结果。在循环通过 num_list 之后,乘积或累加器将等于 120,这是列表中所有数字的乘积。
减少功能
事实证明,我们可以使用 reduce 函数代替 for 循环来完成上述任务。reduce 函数可以接受三个参数,其中两个是必需的。两个必需的参数是:一个函数(它本身接受两个参数)和一个 iterable(比如一个列表)。第三个参数是初始化式,是可选的,因此我们将在后面讨论它。
reduce(函数,可迭代[,初始值设定项])
导入减少
reduce 函数在模块 functools 中,该模块包含高阶函数。高阶函数是作用于或返回其他函数的函数。因此,为了使用 reduce 函数,我们要么需要导入整个 functools 模块,要么可以只从 functools 导入 reduce 函数:
import functoolsfrom functools import reduce
注意: 如果我们导入 functools,我们需要访问 reduce 函数,如下所示:functools.reduce(arguments)。如果我们只从 functools 模块导入 reduce 函数,我们只需输入 reduce(arguments)就可以访问 reduce 函数。
探索归约函数
如前所述,reduce 函数接受两个必需的参数:一个函数和一个 iterable。为了避免 reduce 函数和它作为参数的函数之间的混淆,我将 reduce 函数称为 reduce 。
reduce 接受的第一个参数,函数本身必须接受两个参数。Reduce 然后将这个函数累积地应用到 iterable 的元素上(从左到右),并将其减少到一个值。
我们来看一个例子:
假设我们使用的 iterable 是一个列表,比如上面例子中的 num_list :
num_list = [1,2,3,4,5]
我们用作 reduce 的第一个参数的函数如下:
def prod(x, y):
return x * y
这个 prod 函数接受两个参数:x 和 y。然后返回它们的乘积,即 x * y。
让我们分别传入 prod 函数和 num_list 作为我们的函数和 iterable,以减少:
from functools import reduceproduct = reduce(prod, num_list)
我们的 iterable 对象是 num_list ,也就是 list: [1,2,3,4,5]。我们的函数 prod 接受两个参数 x 和 y。Reduce 将从接受 num_list 的前两个元素 1 和 2 开始,并将它们作为 x 和 y 参数传递给我们的 prod 函数。 prod 函数返回他们的乘积,即 1 * 2,等于 2。Reduce 随后将使用这个累加值 2 作为新的或更新的 x 值,并使用 num_list 中的下一个元素 3 作为新的或更新的 y 值。然后,它将这两个值(2 和 3)作为 x 和 y 发送给我们的 prod 函数,然后该函数返回它们的乘积 2 * 3 或 6。然后,这个 6 将被用作我们新的或更新的 x 值,并且 num_list 中的下一个元素将被用作我们新的或更新的 y 值,即 4。然后,它将 6 和 4 作为我们的 x 和 y 值发送给 prod 函数,后者返回 24。因此,我们的新 x 值是 24,我们的新 y 值是来自 num_list 的下一个元素,即 5。这两个值作为我们的 x 和 y 值传递给 prod 函数,然后 prod 返回它们的乘积,24 * 5,等于 120。因此,reduce 接受一个可迭代对象,在本例中是 num_list ,并将其缩减为一个值,120。然后将该值分配给变量 product。换句话说,x 参数用累积值更新,y 参数从 iterable 更新。
Python 中的可迭代对象、迭代器和迭代
towardsdatascience.com](/iterables-and-iterators-in-python-849b1556ce27)
第三个参数:初始化器
还记得我们说过 reduce 可以接受可选的第三个参数,初始化器吗?它的默认值是无。如果我们传入一个初始化器,它将被 reduce 用作第一个 x 值(而不是 x 是 iterable 的第一个元素)。所以如果我们把上面例子中的数字 2 作为初始化式传入:
product = reduce(prod, num_list, 2)print(product) #240
那么 x 和 y 的前两个值或自变量将分别是 2 和 1。所有后续步骤都是一样的。换句话说,在计算中,初始化器放在 iterable 的元素之前。
使用 Lambda 表达式
在上面的示例中,我们可以使用 lambda 表达式(或匿名函数)来显著缩短代码,而不是将 prod 作为函数传入:
product = reduce(lambda x,y: x*y, num_list)
注意 lambda 表达式如何接受两个参数:x 和 y,然后返回它们的乘积 x * y。
以下是关于如何使用 lambda 表达式的完整教程:
如何用 python 写匿名函数
towardsdatascience.com](/lambda-expressions-in-python-9ad476c75438)
使用 Reduce 的其他示例
我们可以使用 reduce 的场景还有很多。
例如,我们可以找到列表中数字的总和:
num_list = [1,2,3,4,5]sum = reduce(lambda x,y: x + y, num_list)print(sum) #15
或者我们可以在列表中找到最大数量:
num_list = [1,2,3,4,5]max_num = reduce(lambda x,y: x if x > y else y, num_list)print(max_num) #5
或列表中的最小数量:
num_list = [1,2,3,4,5]min_num = reduce(lambda x,y: x if x < y else y, num_list)print(min_num) #1
还有很多其他的应用!
注意:Python 确实有内置函数,比如 max()、min()和 sum(),这些函数对于这三个例子来说更容易使用。然而,目标是展示如何使用 reduce()来完成许多不同的任务。
如果你喜欢阅读这样的故事,并想支持我成为一名作家,可以考虑注册成为一名灵媒会员。每月 5 美元,你可以无限制地阅读媒体上的故事。如果你用我的 链接 注册,我会赚一小笔佣金。
阅读卢艾·马塔尔卡的每一个故事(以及媒体上成千上万的其他作家)。您的会员费直接支持…
lmatalka90.medium.com](https://lmatalka90.medium.com/membership)
结论
在本教程中,我们学习了如何在 python 中导入和使用 reduce 函数。然后我们看到 lambda 表达式如何作为一个参数传入 reduce。最后,我们看到了一些如何使用 reduce 的例子。
使用回归确定影响人均国内生产总值的最重要的社会因素
基于回归系数的 t 统计量对社会因素进行排序
介绍
世界各地的经济学家使用各种指标来衡量一个国家的繁荣和经济发展。其中一个指标是人均国内生产总值。一个国家的 GDP 或国内生产总值是一个国家在特定时期内生产的最终商品和服务的货币价值。人口较多的大国往往会有较高的 GDP。因此,为了消除这种对高人口的偏见,GDP 除以该国的总人口,这就给了我们该国的人均 GDP。
一个国家的发展会受到各种经济和社会因素的影响。本文只关注影响一个国家人均 GDP 的社会因素。总共选择了七个社会因素进行分析。世界银行的数据被用来收集所有这些特征。
- 识字率:成人识字率是指 15 岁及以上能够读写并理解简单日常生活语句的人口比例
- 贫困:每日生活费不足 3.2 美元的总人口比例。
- 贫民窟下:居住在贫民窟的城市人口百分比。
- 腐败:得分在-2.5 到 2.5 之间,得分越高表示腐败越少。
- 政府效力:它反映了公众对公共服务质量、公务员质量及其不受政治压力影响的程度、政策制定和执行的质量以及政府对这些政策承诺的可信度的看法。这是一个介于-2.5 到 2.5 之间的分数,正值越大表示治理越好。
- 政治稳定:衡量没有暴力和恐怖主义。这是一个介于-2.5 到 2.5 之间的分数,正值越大,表明政治越稳定。
- 成人死亡率:是每 1000 人中 15 岁到 60 岁之间死亡的人数。
目标
该分析使用回归来发现所选择的因素是否影响人均国内生产总值。该研究还试图根据这些特征对人均国内生产总值的影响对其进行排名。
利益相关者
政府:知道了影响发展的特征的相对重要性,政府就可以计划投资资金和制定正确方向的政策。
投票者:投票者可以把它作为分析政府工作的一种手段,依据的是政府是否把重点放在重要的方面,或者它的政策是否针对对一个国家的发展贡献不大的因素。
变革者:希望在国家发展中有所作为的个人可以选择努力的方向,从而对国家的发展产生最大的影响。
分析
不同的特征是从世界银行的数据中收集的,并被争论以将它们转换成期望的结构。使用回归来确定系数。为此,对所有特征进行了缩放,使得通过拟合回归模型获得的权重对应于每个特征的相对重要性。
对缩放特征拟合线性回归模型的结果表明Literacy对人均 GDP 没有影响。这个结果看起来很奇怪,因为识字总是与发展联系在一起。为了研究这一点,绘制了单个解释变量,以了解它们与因变量人均国内生产总值的关系。

这种分析表明,这些特征不是线性的,这意味着线性回归模型不能适合给定的数据。该图表明 Y 轴上的最大变化是从 0 到 150,000,而 X 轴上的最大变化是从 0 到 100。因此,人均国内生产总值和其他变量之间没有明显的关系。因此,人均国内生产总值的对数被用来使两个变量相似。

对数变换后,X 轴和 Y 轴的变化变得相似,人均国内生产总值和其他独立变量之间几乎呈线性趋势。例如,从识字率图中可以看出,随着识字率的增加,人均国内生产总值的对数也在增加,同样,其他变量也可以观察到这种趋势。这给了使用 GLM(广义线性模型)的动机。从上面的图中可以看出,链接函数显然是一个log链接函数,因为在对数变换后,数据的趋势呈线性。现在的挑战是决定在 GLM 使用哪一个发行系列。为了有所了解,绘制了因变量的 pdf:

上述密度的形状和GDP per Capita是一个正连续量的事实表明Gamma分布可以用来模拟密度函数。
于是用具有
log连接功能的Gamma分布族对缩放后的数据拟合出 GLM。
通过对数变换因变量GDP per Capita还拟合了一个线性模型。如果模型是为预测目的而建立的,那么转换因变量并不是一个好的做法。但是这里的主要任务是识别特性的重要性。另外,log作为单调递增函数不影响可解释性。因此,对数转换的线性模型被用作第二模型。
确定特征重要性
确定特征的相对重要性的最直观和简单的方法之一是通过直接评估与从拟合回归模型获得的缩放特征相关联的权重。但是这没有考虑在确定权重或者换句话说置信区间时的误差。因此,为了减轻这种影响,t statistic被用来衡量重要性,因为它也包含了计算权重时的相关误差。
代码和详细信息
整个分析是在 r 中执行的。要了解详细的代码和整个流程,数据争论是如何执行的,一些导致共线性的特性是如何处理的,以及其他微小的细节,请查看我的GitHub资源库中的笔记本。笔记本包含对每个步骤的文档分析,这将有助于更好地理解分析。
结果和结论
在考虑的七个因素中,只有以下四个特征对人均国内生产总值有独立影响:
- 识字
- 成人死亡率
- 政府效能
- 政治稳定
其他三个因素,即贫困、贫民窟和腐败,取决于上述四个因素。改变这四个变量可以改变三个因变量的值。

相关矩阵
上图显示了所有因素的相关值。相关性的数值从 0 到 1 不等。如果两个特征的相关性接近 0,这意味着这两个特征根本不相关,而如果相关性接近 1,则这两个特征可以被认为高度相关。负值表示增加一个变量会导致另一个变量减少,而正值表示增加一个变量会增加另一个变量。
- 贫民窟下:该变量与识字率呈高度负相关,与成人死亡率呈高度正相关。这表明贫民窟人口可以通过提高识字率、降低成人死亡率或改善健康状况来减少。
- 低于贫困水平:这一变量也与识字率高度负相关,与成人死亡率高度正相关。因此,可以通过提高识字率、降低成人死亡率或改善健康状况来减少贫困。
- 腐败:腐败与政府效能有非常高的正相关关系。因此,一个有效的政府可以帮助减少腐败,即增加腐败分数,因为较高的分数代表较低的腐败。
下图显示了四个独立特征如何影响人均 GDP:

从上图中,可以得出结论:
- 提高识字率、政府效率和政治稳定性会导致人均国内生产总值的增加。
- 降低成人死亡率或改善健康状况可以提高人均国内生产总值。
现在我们知道了四个变量中的每一个是如何影响人均 GDP 的,让我们看看影响它的最重要的因素是什么:

这里分析了两个模型,这两个模型对于特征的重要性给出了相同的结果。重要性与拟合两个模型得到的 t 统计量的绝对值成正比。从上面的两个图表中,可以观察到:
识字率是影响人均国内生产总值的最重要因素,其次是成人死亡率,然后是政府效率和持久的政治稳定。
国家比较
让我们看看 2018 年人均 GDP 最高的前 10 个国家

图表显示,摩纳哥在 2018 年的所有国家中人均 GDP 最高,美国排名第十。
印度、中国和美国的比较

根据人均 GDP,美国排名第 10,中国第 90,印度第 182。识字率也有类似的差异。美国和中国的识字率非常高,分别为 99%和 97%,而印度远远落后,为 74%。
根据维基百科的数据,2014 年美国在教育上花费了其 GDP 的 5%。2016 年,中国将国内生产总值的 4%用于教育。另一方面,印度 2019 年的教育支出仅占 GDP 的 3%。这显示了一种严重的担忧。美国和中国都有很高的识字率,为了赶上他们,印度需要在教育上投入更多。
使用相关数据的回归
视频教程
教程(包括 R 代码)使用广义估计方程和多级模型。

虽然回归模型由于其简短的语法而易于运行,但这种可访问性也使得不适当地使用回归变得容易。这些模型有几个关键的假设,需要满足这些假设才能使它们的输出有效,但是无论这些假设是否满足,您的代码通常都会运行。
视频教程
对于线性回归(用于连续结果),这些假设如下:
- 独立性:所有观测值相互独立,残差不相关
- 线性:X 和 Y 之间的关系是线性的
- 同方差:不同 X 值的残差的恒定方差
- 正态性:数据应该在回归线周围呈正态分布
对于逻辑回归(与二元或有序分类结果一起使用),这些假设如下:
- 独立性:所有观测值相互独立,残差不相关
- logit 中的线性:X 和 Y 的 logit 之间的关系是线性的
- 正确指定模型,包括缺少多重共线性
在这两种简单的回归模型中,独立的观察值对于拟合一个有效的模型是绝对必要的。如果你的数据点是相关的,这种独立性的假设就被违反了。幸运的是,仍然有办法用相关数据生成有效的回归模型。
相关数据
数据中的相关性主要通过多次测量(例如,对每个参与者间隔 1 周进行两次测量,并且个体内部的数据点不是独立的)或者如果数据中存在聚类(例如,在不同学校就读的学生中进行调查,并且来自给定学校的学生的数据点不是独立的)。
结果是,结果是在单个观察的水平上测量的,但是存在单个(在多个时间点的情况下)或集群的第二个水平,在该水平上可以关联单个数据点。忽略这种相关性意味着标准误差无法精确计算,并且在大多数情况下会被人为降低。
了解您的数据是否相关的最佳方式是简单地通过熟悉您的数据和产生它的收集过程。如果您知道您有来自相同个体的重复测量,或者有可以分组到家庭或学校的参与者的数据,您可以假设您的数据点可能不是独立的。您还可以通过计算 ICC(组内相关系数)来确定数据点在可能的组内的相关程度,或者通过在残差中寻找相关性,来调查数据的可能相关性。
相关数据的回归建模
如前所述,简单回归会产生不准确的相关数据标准误差,因此不应使用。
相反,您希望使用能够解释数据中存在的相关性的模型。如果相关性是由于一些分组变量(如学校)或随着时间的推移而重复测量,那么您可以在广义估计方程或多水平模型之间进行选择。这些建模技术可以处理二元或连续的结果变量,因此当数据相关时,可以用来代替逻辑或线性回归。
广义估计方程
广义估计方程(GEE)将给出与简单回归产生的β估计相同或相似的β估计,但具有适当的标准误差。当您对相同的个人或单位进行重复测量时,广义估计方程特别有用。当您有许多小的集群时,这种建模技术往往工作得很好,这通常是对大量参与者进行少量测量的结果。GEE 还允许用户指定众多关联结构中的一个,根据您的数据,这可能是一个有用的特性。
多层建模
多水平建模 (MLM)在数据点不独立时也提供了适当的标准差。当用户对聚类组内和聚类组之间的关系感兴趣,而不是简单地考虑标准误差估计中的相关性影响时,这通常是最佳的建模方法。MLM 还有一个额外的优势,就是能够处理两个以上级别的响应变量。MLM 模型的主要缺点是,它们需要在每个聚类中有更大的样本量,因此当聚类很小时可能不太适用。
在 r 中,GEE 和 MLM 都很容易使用。下面,我将介绍两种最常见的相关数据的示例:来自个人的重复测量数据和从具有重要分组变量(在本例中为国家)的个人收集的数据。我将为每个数据集拟合简单的回归、GEE 和 MLM 模型,并讨论哪种建模技术最适合这些不同的数据类型。
例子 1:来自脆弱家庭和儿童福利研究的数据
我将首先处理的数据来自普林斯顿大学9 年级和 15 年级的脆弱家庭&儿童福利研究,该研究跟踪了 1998 年至 2000 年间在美国主要城市出生的选定儿童的家庭。数据是公开的,可以通过在脆弱家庭数据和文件页面提交一个简短的请求来获取。由于这项研究年复一年地跟踪相同的家庭,来自相同家庭单位的不同时间点的数据点并不独立。
该数据集包含数十个变量,代表参与调查的儿童及其父母的健康状况。作为一名精神病学专家,我主要对检查儿童的心理健康感兴趣。参与的孩子们被问到他们是否经常感到悲伤,我将用这个“经常感到悲伤”问题的答案作为我的结果。由于药物使用与青少年心理健康状况不佳有关,我将使用代表酒精和烟草使用的变量作为预测指标*。
*注意:本文中创建的模型仅用于演示目的,不应视为有意义。在构建这些模型时,我没有考虑混杂、中介、其他模型假设或其他可能的数据问题。
首先,让我们加载将要使用的包。我加载了“tidyverse”来清理我们的数据,“haven”是因为我们将要读入的数据是 SAS 格式的,“geepack”运行我们的 GEE 模型,“lme4”运行我们的多级模型:
library(tidyverse)
library(haven)
library(geepack)
library(lme4)
现在让我们做一些数据清理,让这些数据为建模做好准备!
第 9 年和第 15 年的数据存放在单独的 SAS 文件中(可通过. sas7bdat 扩展名识别),因此我们有一个代码块来读入和清理每个文件。这种清理必须单独进行,因为变量名和编码在不同的研究年份略有不同(参见代码簿的数据和文档页)。
数据集中包含数百个变量,因此我们首先选择将在我们的模型中使用的变量,并为其分配有意义的变量名,这些变量名在数据框中保持一致。接下来,我们过滤数据,只包括我们感兴趣的变量的完整数据的个人(下面的代码排除了这些变量的缺失数据的个人以及拒绝回答的个人)。
然后,我们以标准的 1 =“是”,0 =“否”格式重新编码变量。对于“feel_sad”变量,这也意味着将变量分成 4 个等级,代表不同程度的悲伤。我们最终得到一个二元变量,其中 1 =“悲伤”,0 =“不悲伤。”一些回归技术可以处理您的响应变量中的多个级别(包括 MLM),但为了简单起见,我在这里将其二进制化。最后,我们创建一个“time_order”变量来表示观察结果是来自第一轮还是第二轮研究。
year_9 = read_sas("./data/FF_wave5_2020v2_SAS.sas7bdat") %>%
select(idnum, k5g2g, k5f1l, k5f1j) %>%
rename("feel_sad" = "k5g2g",
"tobacco" = "k5f1l",
"alcohol" = "k5f1j") %>%
filter(
tobacco == 1 | tobacco == 2,
alcohol == 1 | alcohol == 2,
feel_sad == 0 | feel_sad == 1 | feel_sad == 2 | feel_sad == 3
) %>%
mutate(
tobacco = ifelse(tobacco == 1, 1, 0),
alcohol = ifelse(alcohol == 1, 1, 0),
feel_sad = ifelse(feel_sad == 0, 0, 1),
time_order = 1
)year_15 = read_sas("./data/FF_wave6_2020v2_SAS.sas7bdat") %>%
select(idnum, k6d2n, k6d40, k6d48) %>%
rename("feel_sad" = "k6d2n",
"tobacco" = "k6d40",
"alcohol" = "k6d48") %>%
filter(
tobacco == 1 | tobacco == 2,
alcohol == 1 | alcohol == 2,
feel_sad == 1 | feel_sad == 2 | feel_sad == 3 | feel_sad == 4
) %>%
mutate(
tobacco = ifelse(tobacco == 1, 1, 0),
alcohol = ifelse(alcohol == 1, 1, 0),
feel_sad = ifelse(feel_sad == 4, 0, 1),
time_order = 2
)
然后,我们通过使用 rbind()堆叠两个清理过的数据框来合并第 9 年和第 15 年的数据。rbind()函数在这里工作得很好,因为两个数据框现在共享所有变量名。接下来,我们将“idnum”变量(它标识唯一的家族单元)转换成一个数值变量,以便它可以正确地用于对最终代码块中的数据进行排序。这一步是必要的,因为我们将用于运行 GEE 模型的 geeglm()函数假设数据帧首先按唯一标识符(在本例中为“idnum”)排序,然后按观察顺序排序(在此由新的“time_order”变量表示)。
fragile_families = rbind(year_9, year_15) %>%
mutate(
idnum = as.numeric(idnum)
)fragile_families =
fragile_families[
with(fragile_families, order(idnum)),
]
上述代码生成了以下清理后的数据框,现在可以用于回归建模了:

让我们适应我们的模型:
- 简单逻辑回归
首先,我们使用 glm()函数来拟合一个使用“脆弱家庭”数据的简单逻辑回归模型。因为我们有一个二元结果变量,“家庭=二项式”被用来指定应该使用逻辑回归。我们还使用“broom”包中的 tidy()来清理模型输出。我们创建这个模型只是为了进行比较——如前所述,已经违反了独立性假设,与这个模型相关的标准误差将无效!
glm(formula = feel_sad ~ tobacco + alcohol,
family = binomial, data = fragile_families) %>%
broom::tidy()
上面的代码产生了下面的输出,后续的建模方法将与它进行比较。吸烟和饮酒似乎都是参与儿童悲伤的重要预测因素。

2。广义估计方程
使用“geepack”包中的 geeglm()函数指定 GEE 模型的语法与标准的 glm()函数非常相似。“公式”、“系列”和“数据”是两个函数完全相同的参数。新增的是“id”、“waves”和“corstr”参数(参见包文档中所有可用的参数)。在“id”参数中指定了链接来自相同主题的观察的唯一标识符。在这种情况下,ID 是“idnum”,这是分配给参与研究的每个家庭的唯一标识符。在数据清理过程中创建的“time_order”变量在“waves”参数中起作用,它表示进行观测的顺序。最后,“corstr”可以用来指定主题内的相关结构。“独立性”实际上是这个参数的默认输入,它在这个上下文中是有意义的,因为当集群很小时它是有用的。然而,当一个对象内的所有观察值可以被认为是同等相关时,可以指定“可交换的”,当内部相关性随着时间而变化时,“ar1”是最好的。关于选择正确相关结构的信息可以在这里和这里找到。
geeglm(formula = feel_sad ~ tobacco + alcohol,
family = binomial, id = idnum, data = fragile_families,
waves = time_order, corstr = "independence") %>%
broom::tidy()
我们的 GEE 模型给出了以下输出:

如您所见,我们的β估计值与使用 glm()得出的结果完全相同,但标准误差略有不同,因为已经考虑了数据中的相关性。虽然烟草和酒精仍然是悲伤的重要预测因素,但 p 值略有不同。如果这些 p 值更接近 0.05,那么通过精确的标准误差测量可以很容易地将 p 值推到显著性水平之上或之下。
- *注:GEE 和逻辑回归的检验统计看起来完全不同,但这只是因为逻辑回归输出中提供的检验统计是 Z 统计,而 GEE 输出中提供的检验统计是 Wald 统计。Z 统计量的计算方法是将估计值除以标准误差,而 Wald 统计量的计算方法是将估计值除以标准误差的结果平方。因此,这两个值在数学上是相关的,通过对 GEE“统计数据”列中的值求平方根,您将会看到与初始 Z 统计数据相比更加温和的变化。
使用 geeglm()函数,验证您的分类是否被正确识别也很重要。您可以通过运行上面的代码来做到这一点,而不需要 broom::tidy()步骤,因此:
geeglm(formula = feel_sad ~ tobacco + alcohol,
family = binomial, id = idnum, data = fragile_families,
waves = time_order, corstr = "independence")
这段代码产生如下所示的输出。您需要查看输出的最后一行,其中描述了“集群数量”和“最大集群大小”。我们对几千个人进行了 2 次观察,因此这些值在我们的数据环境中是有意义的,并且表明函数正确地注册了聚类。但是,如果分类的数量等于数据集中的行数,则可能是工作不正常(很可能是数据的排序被关闭)。

3。多级建模
接下来,让我们使用来自 lme4 包的 glmer()拟合一个多水平模型。同样,所需的代码几乎与用于逻辑回归的代码相同。唯一需要的更改是在公式参数中指定随机斜率和截距。这是通过代码的“(1 | idnum)”位完成的,它遵循以下结构:(随机斜率|随机截距)。分组变量,在本例中为“idnum”,在|的右边被指定为“随机截距”,而“1”表示我们不希望预测因子的效果在不同的组之间有所不同。Rense Nieuwenhuis 的一篇有用的博客文章提供了这种 glmer()语法的各种例子。
lme4 包与 broom 包不兼容,因此我们在创建了一个包含模型输出摘要的列表后提取模型的系数。
mlm = summary(glmer(formula =
feel_sad ~ tobacco + alcohol + (1 | idnum),
data = fragile_families, family = binomial))mlm$coefficients
同样,输出类似于简单的逻辑回归模型,烟草和酒精的使用仍然是悲伤的重要预测因素。估计值与使用 glm()和 geeglm()函数得出的值略有不同,因为数据中的分组不再被忽略,也不再被视为需要通过纠正标准误差来解决的烦恼;相反,它们现在作为模型的一个重要部分被合并。与通过逻辑回归产生的估计值相比,所有估计值的标准误差估计值更高,Z 值和 p 值保持相似,但反映了估计值和标准误差值的这些重要变化。

例 2:来自全球学校学生健康调查的数据(GSHS)
我们将浏览的第二个数据集来自世卫组织的全球在校学生健康调查(GSHS)。这项调查在 13-17 岁的学龄儿童中进行,目的是帮助各国确定健康优先事项,确定健康相关行为的流行率,并促进直接比较各国的流行率。我们将使用来自两个国家的数据,分别是印尼和孟加拉,这些数据可以直接从这些国家各自的描述页面下载。
这些数据是跨部门的:一次在两个国家的学生中进行了相同的调查。我感兴趣的是使用这个数据集中的变量来描述孩子是否有朋友,孩子是否被欺负(我的预测因素)以及孩子是否认真考虑过自杀(我的结果)之间的关系。很可能这两个国家之间的这些关系是不同的,孩子们与来自同一个国家的其他孩子更相似。因此,知道一个孩子是来自印度尼西亚还是孟加拉国提供了关于那个孩子的反应的重要信息,并且违反了独立观察的假设。
让我们再次加载包:
library(tidyverse)
library(haven)
library(lme4)
library(gee)
请注意,“geepack”包已被替换为“gee”包。“gee”软件包更容易用于(在我看来)按分组变量(如国家)进行聚类的数据,而不是具有多个观察值的个人数据。
接下来,让我们加载数据(也是 SAS 格式,所以我们再次使用“haven”包)并进行一些基本的清理。这里的数据清理遵循与脆弱家庭和儿童福利研究数据所使用的程序类似的结构:选择重要的变量并为其指定有意义的、一致的名称,并且创建一个新的变量来指示观察值属于哪个聚类(在这种情况下是新的“国家”变量)。
indonesia = read_sas("./data/IOH2007_public_use.sas7bdat") %>%
select(q21, q25, q27) %>%
rename(
"bullied" = "q21",
"suicidal_thoughts" = "q25",
"friends" = "q27"
) %>%
mutate(
country = 1,
)bangladesh = read_sas("./data/bdh2014_public_use.sas7bdat") %>%
select(q20, q24, q27) %>%
rename(
"bullied" = "q20",
"suicidal_thoughts" = "q24",
"friends" = "q27"
) %>%
mutate(
country = 2
)
同样,两个数据框堆叠在一起。由于在这两个国家的收集过程中变量的编码是一致的,因此使用这种组合数据集可以只进行一次清理。丢失的数据被删除,所有变量都从字符串格式转换为数字格式。最后,变量变异成一致的二进制格式。
survey = rbind(indonesia, bangladesh) %>%
mutate(
suicidal_thoughts = as.numeric(suicidal_thoughts),
friends = as.numeric(friends),
bullied = as.numeric(bullied),
suicidal_thoughts = ifelse(suicidal_thoughts == 1, 1, 0),
friends = ifelse(friends == 1, 0, 1),
bullied = ifelse(bullied == 1, 0, 1)
) %>%
drop_na()
我们清理后的数据框现在如下所示:

让我们适应我们的模型:
- 简单逻辑回归
除了变量名称和指定的数据之外,glm()代码与用于脆弱家庭研究数据的代码保持一致。
glm(formula = suicidal_thoughts ~ bullied + friends,
family = binomial, data = survey) %>%
broom::tidy()
不出所料,在这个样本中,孩子是否有朋友和是否被欺负都是自杀想法存在的重要预测因素。

2。广义估计方程
gee 包中的 gee()函数允许我们轻松地将 GEE 用于我们的调查数据。该函数比以前使用的 geeglm()函数更适合,因为数据不随时间相关,而是由一个单独的变量表示,该变量可以用“id”参数表示(在本例中为“country”)。formula 和 family 参数与 glm()函数中使用的参数相同,geeglm()函数中使用的“corstr”参数也是相同的。但是,与 geepack 包不同,gee 包与 broom::tidy()函数不兼容,因此使用 summary()函数查看输出。
gee = gee(suicidal_thoughts ~ bullied + friends, data = survey,
id = country, family = binomial,
corstr = "exchangeable")summary(gee)
我特别喜欢 gee()函数的一个原因是,输出中实际上包含了朴素的标准误差和 Z 检验统计数据(朴素的意思是这些值是由回归产生的,其中没有考虑聚类,您将看到这些值与上面的 glm()函数产生的值完全相同)。您会注意到使用 GEE(“稳健”)产生的标准误差和 Z 检验统计数据发生了巨大变化,尽管我们的两个预测值仍然很显著。似乎考虑到国内相关性,可以使用低得多的标准误差。

3。多级建模***
-
- *注:如上所述,模型仅用于演示目的,不一定有效。在这种情况下,对于我们的 MLM 模型,我们希望有两个以上的组(即来自其他国家的数据)。如果你真的只用两组 MLM 模型,你应该考虑一个小样本量修正。
最后,我们用调查数据集尝试 MLM。代码与用于脆弱家庭研究数据的代码完全相同,但指定了新的公式、分组变量和数据集。
mlm = summary(glmer(formula =
suicidal_thoughts ~ bullied + friends +
(1 | country),
data = survey, family = binomial))mlm$coefficients
同样,beta 估计值和标准误差估计值现在也比使用 glm()得到的值稍有调整。与“被欺负”和“朋友”变量相关的 z 值和 p 值略小,尽管欺负和有朋友仍然是自杀想法的重要预测因素。

哪个模型最适合这些例子?
普林斯顿大学脆弱家庭和儿童福利研究的数据最好用 GEE 来表示。这是由于 2 个观察值的最大聚类大小,单个家庭在一段时间内有多个数据点的事实,以及我们更感兴趣的是在标准误差估计中考虑分组,而不是实际评估家庭之间的差异。
多水平建模最适合来自全球基于学校的学生健康调查(GSHS)的数据,因为这些数据是跨地区收集的,可以分为两大类。此外,可以进一步研究输出,以确定组内和组间的差异,我们可能对国家内部和国家之间的关系感兴趣。
如何解释违反独立观察假设的情况将取决于您的数据结构和您对数据收集过程的一般了解,以及您是否认为相关性是一个需要调整的烦恼或值得探索的事情。
总之,回归是灵活的,某些回归模型可以处理相关数据。然而,检查给定技术的假设并确保您的分析策略适合您的数据总是很重要的。
利用强化学习产生更好的连接排序策略
我们可以训练一个 AI 模型来决定首先加入哪些表吗?

连接表格。由 Unsplash 上的 chuttersnap 拍摄
假设您在 RDBMS 上有以下查询:
从 A、B、C、D 中选择*
你是要先加入 A & B 再加入结果跟C&D 吗?还是先把 A & C 加入,再把结果用 B 加入,再把结果用 D 加入比较好?
今天我们将讨论一篇有趣的论文,作者是 Ryan Marcus 和 Olga Papaemmanouil,题目是“连接顺序枚举的深度强化学习”本文将机器学习世界与数据库世界交叉。他们试图训练一个强化学习代理来决定连接关系的顺序,即首先连接哪些关系(RDBMS 表),接下来是哪些关系,等等。作为一个具体的例子,假设我们有四个关系 A、B、C 和 D。我们希望代理决定是加入 A & B,然后将结果关系加入 C 并最终加入 D,还是先加入 A & C,然后加入 B & D,然后加入两个结果关系,或者任何其他排序可能性。
连接排序策略是数据库查询优化的一部分,其目标是产生一个低延迟运行的物理查询计划。我们可以把这个问题看作是搜索所有可能的连接顺序并选择最便宜的一个。如果我们可以生成一个可以减少初始步骤中的行数的顺序,那么我们就可以省去后续步骤来联接最终将被丢弃的行。目前,DBMSs 使用试探法来决定连接顺序。System-R 使用动态规划寻找一个代价最低的左深树,而 PostgreSQL 使用贪婪算法选择一个代价低的对,直到构建出整个订单。然而,这些策略在某种程度上是静态的,并且数据库不从反馈中学习(例如,基数、查询延迟)。因此,他们可能会重复使用同样糟糕的连接排序策略。
重新加入枚举器
现在让我们来谈谈 ReJOIN,这是一个通过使用强化学习来枚举和选择连接顺序的解决方案。
强化学习
我们将把连接顺序枚举问题建模成强化学习问题。在强化学习中,我们训练一个与环境交互的代理。环境将告诉代理当前状态 s 以及在 = { a₀、a₁、.。。,一个当前状态下可以拍的 }。然后,代理必须选择一个动作,并进入下一个状态,在该状态下,它将接收一组新的动作。代理反复执行这个过程,直到它到达一个终端状态,在这里没有更多的动作可用。此时,代理完成了一个集,并将根据其采取的行动获得奖励。代理的目标是通过从经验中学习来最大化回报。
将连接顺序问题映射到强化学习的术语

图 1:二元连接树(Marcus & Papaemmanouil,2018)
连接排序可以表示为一棵二叉连接树,如上图所示。为了采用强化学习来连接排序问题,我们将使用以下映射:
- 每个状态将代表一个二元连接树。然后,我们将这个二元连接树转换成一个状态向量,并添加更多的信息,如连接谓词和选择谓词。
- 每个动作代表将两个子树合并成一个树。
- 当我们已经加入所有关系时,一集就结束了。
- 奖励将基于结果连接排序的成本模型评估来计算。
状态向量

图 2:作为向量的状态表示(Marcus & Papaemmanouil,2018)
如前所述,我们将二叉连接树和进一步的连接信息表示为向量。我们将每个子树编码为大小为 n 的向量 v ,其中 n 是数据库中关系的总数。如果关系不在二元连接树中, vᵢ 的值为 0,并且 1/h(i,x) 其中 h(i,x) 是关系 i 在子树 x 中的高度(到根的距离)。例如,在图 2 的最左边,我们可以看到我们将子树 A 表示为[1 0 0 0],因为在这种情况下,关系 A 的高度为 1,其他关系不存在于子树中。在左起第二张图片上,假设我们连接了 A 和 C ,产生了新的关系 A ⨝ C。现在 A & C 的高度为 2,因此我们用 1/2 填充 A & C 列上的值。列 B 和 D 保持为 0,因为它们不在这个子树中。
此外,我们添加了连接谓词和列选择谓词向量。连接谓词将是一个 n×n 二进制矩阵 m,其中如果在和之间存在连接谓词,则 m(i,j) 的值将为 1,否则为 0。在图 2 中, m(1,2) = m(2,1) 的值是 1,因为我们在 A 和 B 之间有一个连接谓词(A.id == B.id)。 m(1,3) = m(3,1) 的值也是 1,因为我们在 A 和 C 之间有一个连接谓词( A.id == C.id )
列选择谓词是一个 k 维二元向量,其中 k 是所有关系的属性总数。如果我们在这个属性中有选择谓词,那么这个向量中的项的值将为 1,否则为 0。在图 2 中,B.a2 的值是 1,因为在 SQL 查询中,我们有一个选择谓词 B.a2 > 100 。
训练模型

图 3:重新加入的架构(Marcus & Papaemmanouil,2018)
我们通过寻找产生最高回报的优化策略来训练强化学习代理。我们通过训练神经网络来实现这一点,该神经网络将状态向量作为输入,并将动作向量作为输出。动作向量是一个保存了 n 个项目的向量,每个项目代表一个可能采取的动作。我们在中间放置了两个隐藏层。我们训练模型来优化产生高回报行动的权重。
ReJOIN 将前几集的信息记录为一对权重及其奖励 (θ,r) 。然后,它使用此信息中的样本来估计接下来事件的政策梯度。
结果

图 3:性能比较(Marcus & Papaemmanouil,2018 年)
上图显示了使用 PostgreSQL 和 ReJOIN 生成的计划执行查询的比较。作者通过使用连接顺序基准数据集和查询来执行这个基准测试。正如我们所看到的,最初,ReJOIN 给出了一个糟糕的执行计划(例如,比 PostgreSQL 差 9 倍)。在几千集之后,它开始实现与 PostgreSQL 相同的性能,并且在某个时候,开始产生更好的执行计划。

图 4:计划时间对比(Marcus & Papaemmanouil,2018)
本文的另一个极好的结果是,当我们向查询中添加更多的关系时,重加入的计划时间不会变长。在 PostgreSQL 的情况下,它花费更多的时间来计划更多关系的连接,因为可能的连接组合需要计算更多的成本。
你也可以在这个库中找到关于这篇论文的实现和扩展实验。
结论和公开挑战
这项研究显示了利用强化学习进行数据库优化的一些潜力。它显示了积极的结果,在某些查询上,该模型生成的连接顺序在执行性能和计划时间方面可以超过 PostgreSQL 生成的连接顺序。
然而,它花费了大量的片段(查询执行)来达到一个好的结果。然后,作者提出了未来研究的一些机遇和挑战,例如:
- 使用实际查询延迟作为奖励。目前提出的方法是使用成本模型作为奖励。成本模型支持快速训练,但也容易出错,因为实际的查询延迟可能与成本模型不一致。
- 扩展优化器。在查询优化的世界里,重加入只处理连接顺序问题。研究社区可以进一步研究在查询优化的其他方面应用类似技术的可能性,比如选择物理操作符、使用索引和合并谓词。
参考文献:
[1] Marcus,r .,& Papaemmanouil,O. (2018 年 6 月)。连接顺序枚举的深度强化学习。在第一届利用人工智能技术进行数据管理的国际研讨会会议录(第 3 页)。ACM。
使用 ResNet 处理心电图时间序列数据
像普通 RNN 这样的递归神经网络或者像 LSTM 和 GRU 这样的更高级的模型曾经是深度学习实践者进入时间序列领域的 goto 模型。NLP 提供了丰富的序列数据,提供了一个自愿的对象。但是像伯特和 GPT 这样的变压器架构肯定已经占领了这个领域。除了这些变压器架构之外,CNN 还在时间序列领域取得了回归或进步。CNN 擅长模拟时间序列吗?
CNN 在模拟时间序列方面有多好?
为了回答这个问题,这篇文章复制了一篇名为“ECG heart beat class ification:A Deep Transferable re presentation”[1]的文章,该文章将基于 CNN 的架构 ResNet 应用于心电图(ECG)数据。把迁移学习应用到这个问题上。
Keras 代码是以笔记本的形式提供的,可以很容易地用例如 Google Colab 在这里执行。
这篇文章的概述
该职位的结构如下:
- 数据集简介
- ResNet 简介
- 使用平原 MLP 和 ResNet 建立基线
- 运用迁移学习
- 讨论
- 结论
数据集简介
与原始研究相关的文章使用了两组心电图数据:
(两个数据集都可以在 Kaggle 上获得,详情见笔记本。)
两个数据集都包含标准化的 ECG 信号。每次观察有 187 个时间步。在 2D 绘制的观察示例呈现:

观察值的 2D 表示
在最初的麻省理工学院-BIH 数据集中,每个观察值被分配了以下标签之一:
- 答:房性早搏
- f:心室融合搏动
- n:正常节拍
- v:室性早搏)
- n:正常窦性心律
- 室性心动过速
在 Kaggle 数据集中,这恰好是最初研究的来源,这些标签被融合成 5 个类别。数据集提供了长度分别为 87554 和 21892 的训练和测试数据集。不算太寒酸!

融合类别的分布
PTB 数据集(长度为 14552)仅将正常和异常标签分配给观察值;标签的分布如下所示。

PTB 数据集中标签的分布
在最初的研究中,适合麻省理工学院-BIH 数据集的模型权重被转移到 PTB 数据集。由于麻省理工学院-BIH 的数据集比 PTB 的数据集大得多,这可能是有意义的。
在复制开始之前,首先提供一个简短的 ResNet 刷新程序。
ResNet 简介
ResNet 的目标是允许优化具有更多层的深层网络。为此,ResNet 引入了跳过连接。跳过连接是通过将信号从前面的层添加到后面的层来创建的。原文中提供了一个很好的例证:

研究的 ResNet 设置
这里发生了什么事?来自第一卷积层的信号被加到第一 ResNet 块中的第三卷积层的信号上。(注意,信号是在 ReLU 之前添加的。块末尾的池层严格来说不是 ResNet 块的一部分,它是特定于这个应用程序的。)
事实证明,增加这些跳跃连接可以实现深度学习网络。
关于卷积的一个注记。虽然输入数据是二维的,但它是一维的。因此,卷积也将是一维的;过滤器将从左向右移动。
让我们编码!
使用平原 MLP 和 ResNet 建立基线
在评估原始研究的迁移学习设置之前,首先在 PTB 数据集上拟合基线多层感知器(MLP)和没有迁移学习的 ResNet 模型。如果没有必要,为什么要把事情复杂化?有人可能会说深度学习模型本身就是一个复杂的问题。在这种情况下,有人会说神经网络很容易适应数据,给出了非常有效的实现。
基线 MLP 网络由 15、10 和 5 的 3 个隐藏 ReLU 层组成,后面是一个 sigmoid 激活层。Keras 制作了这个模型的图片:

克拉斯笔下的 MLP
MLP 几乎以 97.5%的训练准确度解析了训练数据。这大概是这种架构的最大可能。尽管使用了一些 L2 正则化,对训练数据的过拟合还是发生了,如下所示。

MLP 对训练数据的过度拟合
最终验证准确率为 95.2%,测试准确率为 95.1%。10%的小验证数据集似乎工作得很好。下面是使用测试数据生成的混淆矩阵。

MLP 模型的混淆矩阵
为了更好地测量,MLP 模型使用循环学习率进行优化,学习率使用学习率查找器进行估计。fast.ai 推广了这种做法。将这种做法应用于 SGD 和 Adam,这种设置的效果似乎提高了约 1%的准确性,即在验证数据集上从 93.7%提高到 94.7%。有趣到可以留到以后用。不幸的是,Keras 对这些工具的支持还没有标准化。
在 MLP 无法完全解析训练数据的情况下,ResNet 会像热刀穿过黄油一样处理数据。训练数据被完全解析,几乎验证数据也是如此。

ResNet 的损耗曲线
你肯定是在开玩笑吧,费曼先生?放大图的最后一点,由于验证损失增加,可以观察到一些过度拟合。

增加验证损失表明过度拟合
对于神经网络来说,解析训练数据集并不罕见,事实上这被认为是一个很好的开始实践。但是验证指标也非常好,准确率达到 99.7%。在讨论部分,产生了一个可以解释这些结果的假设。
运用迁移学习
对于迁移学习模型,ResNet 模型首先适用于更大的麻省理工学院-BIH 数据集。迁移学习设置很简单:最后完全连接的层在 PTD 数据集上重新训练。出于好奇,下面描述了实现这一点的代码片段。

Keras 中的迁移学习
使用汇总功能,可以验证可训练参数的数量已经显著减少。

可训练参数减少
在麻省理工学院 BIH 分校上的结果很好,准确率达到 98.5%。应用迁移学习后,在 PTB 数据集上的测试准确率达到 96.4%。原始论文报告迁移学习后的准确率为 95.9%,这些数字大致相同。
讨论
虽然这很有诱惑力,但在 PTB 数据集上没有迁移学习的 ResNet 的最初几乎完美的验证和测试分数应该非常谨慎地解释。对这些分数的合理解释可能是,由于训练、验证和测试集的随机化,网络实际上记住了样本所来自的人的心电图模式。处理这个问题的正确方法是确保验证和测试集中的数据不包含来自训练数据中存在的人的数据。在这个装置中,对人的概括被积极地测试。然而,造成这种情况的特征在数据中并不存在。
麻省理工学院-BIH 数据集上的 ResNet 结果没有证实这一假设:测试集上的准确率为 98.5%。即使对于倾斜的类,这也是一个非常好的结果。请注意,该模型必须预测 5 个类别,而不是 2 个,这是一项更困难的任务。
所使用的数据心电图是高度周期性的,幸运的是!这使得时间序列可以被格式化成更小的“瓦片”,在这些瓦片中可能存在与众不同的心脏活动模式。直觉上,CNN 网络非常适合这种环境。
就迁移学习而言,迁移学习能提高成绩的假设不无道理。潜在的假设是,构成信号形状的基本分量在具有不同标签的信号中是共享的。在这种情况下,结果并没有证实它的用途。通过在网络的末端重新训练更多的层,通过一次一步地将层的权重设置为可训练的,可以改善结果。
没有与 LSTM 或 GRU 的网络进行比较;这可能是以后的主题。
结论
虽然可能有一些方法上的缺陷进入了这一重复研究,但将 CNN 应用于时间序列的案例已经提出。对于心电图数据,搜索的是一组重复信号的重复模式:这正是 CNN 网络所擅长的。
参考
[1] Kachuee,m .,Fazeli,s .,Sarrafzadeh,m .:心电图心跳分类:一种深层次的可转换表示法。In: 2018 IEEE 医疗保健信息国际会议(ICHI)。第 443-444 页。IEEE (2018)。
[2]穆迪 GB,马克 RG。麻省理工学院-BIH 心律失常数据库的影响。医学和生物工程 20(3):45–50(2001 年 5-6 月)。
[3] Bousseljot R,Kreiseler D,Schnabel,a . EKG 的神经病学-信号数据库生物医学技术,40 级,ergnzungsband 1(1995)S 317。
使用 ResNets 检测工业物联网纺织品生产中的异常
用于图像分类的机器学习模型通常使用卷积神经网络(CNN)从图像中提取特征,同时采用最大池层来降低维度。目标是从图像区域中提取越来越高级的特征,最终进行某种预测,例如图像分类。
为了提高精度,简单地向网络添加更多的层是很有诱惑力的。然而,研究表明,添加太多的层会增加训练时间,甚至导致精度下降。
在这篇博客中,我们将深入探讨为什么深度神经网络会出现这种情况,如何解决这个问题,并以一个工业物联网用例为例,看看如何在感知实验室中构建一个更有效的模型。
消失的渐变问题
影像分类模型通常使用一系列相互反馈的层来实现其目标,其中额外的层可以提高最终分类的准确性。然而,添加太多的层会导致反向传播期间的消失梯度问题,其中损失函数的越来越小的梯度导致对权重的更新变得越来越小,直到它们趋向于零。这反过来会导致上面提到的培训问题。
拯救残余网络
克服这个问题的一种流行方法是合并残差块,它们共同形成一个残差网络 ( ResNet) 。残差块通过引入早期层跳过未来层的架构模式解决了这个问题。这些“跳过连接”通常通过一个加法操作来实现,由此,在一些卷积层之后,来自网络中较早的特征被添加到新计算的特征,如下所示:

跳跃连接为梯度在反向传播期间的流动提供了额外的、更短的路径,并且经验表明这使得更深的计算机视觉网络能够更快地训练。
残差块的工作方式如下:
- 输入张量 X 流向 ResNet 块,并沿两条路径向下流动。
- x 通过卷积层(如图 1 权重层所示)沿主路径向下流动,就像在普通 CNN 中一样,这通常被称为卷积块。结果输出近似于函数 F(x)。
- X 还流经跳过连接,也称为身份 块,因为它只是转发 X,同时保留其维度。
- F(x)和 X 然后被加在一起,然后被发送到激活函数(在这个例子中是 ReLU )。
如下面图 2 中的所示,残差块通常被“链接”在一起以形成 ResNets。来自最终残差块的输出与来自最终跳过连接的输出一起被组合,然后被传递到其他类型的层(例如,平均池和完全连接层)用于分类:

图 2:50 层 ResNet 模型示例。形象由 感知力 灵感 来源 。
在用于图像分类的典型 CNN 中,较早的层通常学习较低级别的抽象(例如,线、角等。),而后续层学习更高级别的抽象(例如,形状组等)。).通过使用跳过连接,我们允许梯度通过网络,而不需要通过激活函数。这有助于网络将信息传递给网络中的后续层,使它们能够学习复杂的特征,而没有梯度消失的风险。
图 3 描绘了 ResNet 块对图像分类模型的应用:

图 3:由使用卷积和批量归一化计算的两层组成的 ResNet 块的示例。在此示例中,在通过 RelU 生成标识块(x)的激活之前,通过 skip 连接将 ResNet 块的输出添加到标识块(x)。当输入与输出激活具有相同的维度时,可以使用这种架构。图片由 感知 灵感 来源 。
注意,在某些情况下,可以用卷积块代替恒等块,例如当输入和输出尺寸不同时:

图 4:ResNet 块的例子,其中输入和输出具有不同的维度。在这种情况下,在生成卷积块(x)的激活之前,ResNet 块的输出通过 skip 连接被添加到卷积块(x)。形象由 感知器 灵感 来源 。
由于残差块,梯度现在可以在通过两条路径的反向传播期间流回网络:

图 5:示出了梯度在反向传播期间可以流经的两个路径的图。形象由 感知力 灵感 来源 。
当梯度流回穿过梯度路径-2 时,残差块中的权重层(在图 5 中表示为 F(x ))被更新,并且新的梯度值被计算。随着梯度继续流过较早的残差块,它将继续减小(即,趋向于 0)。但是,梯度也会流回跳过连接,从而完全避开残差块中的权重层,因此将保留其值。这允许保持完整的渐变值传播回早期层,允许它们学习激活,从而避免渐变消失的问题。
将 ResNets 应用于工业物联网应用
图像分类技术对于计算机视觉问题特别有用,例如在工业物联网(例如,制造业)中遇到的那些问题。
为了说明这一点,我们在 GitHub 上整合了一个纺织品分类项目。该项目以 72000 幅纺织纤维的特写图像作为输入,每幅图像包含六种可能的制造分类之一(例如,颜色问题、织物切割等)。),以及每个图像的相应分类:

图 6:具有各种类型制造分类的纺织织物图像示例。其中五个代表异常,而“良好”分类意味着在图像中没有发现问题。图像由感知。
项目中的示例 PerceptiLabs 模型包含三个 ResNet 块,每个 ResNet 块对图像进行卷积以提取特征,并包含直接按原样转发每个块的输入(即,作为标识块)的跳过连接。使用配置为应用加法操作的合并组件,将其添加到块的输出:

图 7:感知实验室中的纺织品图像分类模型概述。图像由*感知。***
该项目展示了这种模型如何用于工业物联网的质量控制。例如,相机可以在制造过程中随机拍摄织物的特写照片,然后将它们呈现给模型以进行异常检测。
最重要的是,这个项目展示了在 PerceptiLabs 中构建 ResNet 块是多么容易。
结论
具有大量层的神经网络容易遭受消失梯度问题,由此在反向传播期间梯度趋向于零。由于梯度值如此之小,网络中位于较早位置的层可能很少更新或根本不更新其激活,从而导致训练速度变慢、停止,甚至失去其准确性。
ResNet 块是这个问题的一个很好的解决方案,因为它们允许你使用大量的层,最重要的是,它们很容易在 PerceptiLabs 中构建。
因此,要想知道这有多简单,请务必查看我们在 GitHub 上的纺织品分类项目。一如既往,我们希望您能参与我们的社区频道的讨论。
使用 RStudio 项目模板帮助数据科学团队实现项目标准化
改善数据科学中的团队协作和项目标准化

数据科学领域的许多工作仅仅依靠数据科学家/分析师来保证项目的标准化和代码的可复制性;不幸的是,这变成了混乱/混乱的来源,因为每个人(即使在同一个团队)都有不同的工作策略。为了帮助确保团队共享相同的项目标准,RStudio 提供了一种 cookiecutter,您可以在其中开发(以包的形式)许多模板,供同一团队的用户共享。

项目模板背后的故事
最近的研究旨在为社会提供方法,帮助用户更好地理解和组织他们的项目、数据和从这些数据中产生的见解。从 CRISP-DM 一直到团队数据科学流程,经过 KDD ,我们经历了许多方式来增强我们在团队中的工作能力,共同努力以更快的速度和更高的可重复性获得所需的洞察力。
尽管如此,这个过程会重复无数次,并且回过头来重新开发一个洞察力,或者重新训练一个模型的过程会变得很有挑战性,因为很久以前编写的代码不容易获得,并且可能很难理解几周/几个月/几年前开发这个项目的整个过程。
一些项目可能看起来像这样:

代表无组织方式存储数据和代码的图像
即使只是打开一个存储库,把脚本、文件、函数。gitignores 和任何类型的文件需要交付一个闪亮的网页或机器学习模型,或某种分析,这个过程缺乏创建代码和过程的能力,这些代码和过程可以被将来可能被分配到这个项目的任何其他人理解。
因此,这个 RStudio 项目模板旨在帮助用户在开始项目时创建某种模式,帮助团队标准化项目组织,以便每个人都可以轻松地被分配到一个项目,并了解那里发生了什么。
也许,把项目变成这样:
-- project_directory/
| -- data/
| -- raw/
| -- reference/
| -- processed/
| -- scripts/
| -- modelling/
| -- analysis/
| -- production/
| -- markdown/
-- .gitignore
-- project_directory.Rproj
创建新的 R 包
项目模板可用于创建具有预先指定结构的新项目,创建这些项目模板的一种方法是创建 R 包,这将允许用户与任意多的用户共享其模板。
R 包的创建过程(相当简单)非常简单。 Hadley 的 R 包指南涵盖了创建包过程中的许多注意事项。但是由于我们的包将只包含一个函数,它不应该需要开发一个更健壮的包所涉及的复杂性。
从项目的菜单中创建一个 R 包很容易,应该让用户看到一个样例包,它包含 R 包结构、hello.R函数和一些其他文件。第一步是删除位于R/的hello.R文件和位于man/的hello.Rd文件。之后,我们从一个干净的包开始,第一步是创建我们的函数。
创建将创建模板的函数
为了“创建模板”,我们必须指示 R 将如何处理它,以及当我们在新的项目菜单中提供这个函数时,它将如何表现。因此,这个函数采用一个强制参数,另一个是附加参数,可以帮助您的项目创建工具的逻辑。第一个参数将始终是这个新项目的路径,因为它也将创建一个. RProj 文件,它将驻留在一个新文件夹中。该函数的其他参数作为...传递,可以在代码中用它们的名字调用它们,或者将它们分配给dots <- list(...)。
我们的函数将执行一些任务,显示一些可以嵌入到项目创建模板中的特性。它将:
- 用
writeLines()创建一个 README.md 文件; - 如果一个复选框被选中,它将创建一个
.gitignore文件; - 给定来自用户的选定输入,创建具有特定名称的文件夹;
为了创建一个函数,并确保roxygen2可以解释它,并导出应该由最终包导出的函数,我们将根据下面的语法编写函数。
#' This package will create a function called create_project()
#'
#' It's callback is at: inst/rstudio/templates/project/create_project.dcf
#'
#' @exportcreate_project <-
function(path, ...) {# Create the project path given the name chosen by the user:
dir.create(path, recursive = TRUE, showWarnings = FALSE)# Change the working directory to the recently created folder:
setwd(file.path(getwd(), path))# Collect the list of inputs in a list to be called later:
dots <- list(...)# In the project template we've added 2 choices for the user:
# * One allows them to select if the project will have a .gitignore file
# * The other will create a folder, given a select input from the user# Check .gitignore argument
if(dots[["createGitignore"]]) {
git_ignores <-
c(
'.Rhistory',
'.Rapp.history',
'.RData',
'.Ruserdata',
'.Rproj.user/',
'.Renviron'
)writeLines(paste(git_ignores, sep = '\n'), '.gitignore')
}# Check selected folder
if(dots[["folder"]] == "Production"){
dir.create("production", recursive = TRUE, showWarnings = FALSE)
} else {
dir.create("development", recursive = TRUE, showWarnings = FALSE)
}}
创建.dcf 文件
创建了函数文件后,下一步是创建.dcf文件。这个文件负责创建用户将与之交互的框。除了用户将要输入的路径,您还可以创建任意数量的复选框/文本输入/选择输入,以便向最终用户授予定制权限。对于我们的项目,我们将创建一个复选框,以便用户可以决定是否创建.gitignore文件和一个选择输入,以便用户可以定义项目的范围(开发或生产);
本解决方案(开发或生产)仅用于说明目的,并不反映项目的任何实际状态。
必须在文件夹inst/rstudio/templates/project中的包内用您选择的名称创建.dcf文件,它应该遵循下面的语法:
Binding: create_project
Title: My First Project Template
OpenFiles: README.md
# In the project you can also add icons (the icon should be a PNG, smaller than 64kb
# and in the inst/rstudio/templates folder
# Icon: name_of_pic.pngParameter: folder
Widget: SelectInput
Label: Choose the scope of the project
Fields: Production, Development
Default: Production
Position: leftParameter: createGitignore
Widget: CheckboxInput
Label: Create .gitignore
Default: On
Position: right
devtools::document() + devtools::install()
现在我们的包结构已经创建好了,我们可以使用devtools::document()函数来创建它的文档页面。尽管这个包主要是作为 RStudio 的一个插件开发的,但是在将它安装到 R 中之前,最好对它进行文档化,因为它会搜索项目运行所需的任何依赖项和包。
通过运行devtools::install()函数,在我们的包中,我们将安装它,并从现在开始使它在任何其他 R 会话中可用。这应该足以使 RStudio 可以将外接程序作为新的项目模板进行访问。
之后,您的项目模板应该可以在 RStudio 的项目列表中找到。如果您想了解更多关于调整您的项目模板并增加更多可定制性的不同功能,请查看 RStudio 项目模板页面。
结论
鉴于数据科学的最新发展及其与软件工程技术的集成,团队内标准化项目的需求变得很方便,以确保每个人都能理解,并被他们的搭档理解。这允许更快地调试项目,并减少了遗留代码的缺点,这些代码是同一个团队中的人无法理解的。当然,这个特性本身不会帮助记录代码,或者标准化编写 R 脚本的方式(请参考tidy verse 风格指南),但是它是一个工具,可以帮助用户创建关于项目管理的模式。
查看托管本文中使用的代码的 GitHub 页面,如果您想尝试这个例子,可以克隆这个库,或者通过运行:devtools::install_github('paeselhz/rstudioProjectTemplate')将其安装在 R 中。
利用卫星描绘纽约市的经济机会
纽约市高低经济机会的光谱特征初探。
纵观历史,纳入空间背景的数据揭示了在不断增加的海量数据中不可见的变革性见解。在 19 世纪中期的英国,一位名叫约翰·斯诺的医生因追踪 1854 年伦敦霍乱爆发而闻名。斯诺以绘制霍乱病例地图并展示其在城市水井周围的聚集而闻名——他认为霍乱是通过受污染的水源传播的,而不是像当时普遍认为的那样通过“不良空气”传播的。由于 Snow 的空间数据工作,公共卫生政策和实践在霍乱的检测和缓解方面取得了进展。在新冠肺炎指数感染全球数百万人的时候,似乎应该记住斯诺的经典著作。

霍乱病例地图。来源:《论霍乱的传播方式》约翰·斯诺著,检索自维基百科。
作为洛克菲勒基金会的一名空间数据科学家,我使用卫星和地理参考数据集来获得关于跨景观的模式和关系的见解,这是受雪的启发。洛克菲勒基金会和我们在股权和经济机会方面的工作感兴趣的一个主题是机会区 (OZs),这是官方指定的经济困难社区,作为备受讨论的 2017 年减税和就业法案的一部分,投资者可以获得特殊的税收减免。如果人口普查区的个人贫困率至少为 20 %,家庭收入中位数不超过该地区中位数的 80 %,并且州政府从所有合格的人口普查区中选择一部分人口普查区,那么这些人口普查区就有资格获得 OZ 地位。今年早些时候,我们的团队询问是否有可能使用卫星图像来识别纽约市的机会区域。这个问题的核心是一个更深层次的问题:在高收入和低收入地区,我们的建筑环境中是否有独特的特征和模式?换句话说,高收入地区和低收入地区之间是否存在空间和光谱上截然不同的特征?
我们假设,是的,低收入和高收入地区之间确实存在显著的特征和模式,这将有助于不同的光谱特征,并决定在纽约市测试这一假设。在多项研究中,研究人员发现,纽约市和美国 T2不同地区的经济状况(以及其他社会经济因素)可以使用谷歌街景图像进行统计评估。虽然卫星图像是从上面捕捉的,分辨率低得多——通常捕捉地面上几十平方米的像素——但其他研究人员发现,卫星测量的夜光可以解释发展中地区高达 75%的经济成果。我们还认识到,卫星数据对于捕捉机会区域并不完美,因为它是栅格类型(或多或少是连续的)数据源,而人口普查边界是矢量类型(多边形),可捕捉指定区域内不同社会经济因素的平均值;我们并不期望在卫星数据中找到完美的边界框,而是簇的梯度显示或多或少可能是机会区域的区域。
作为我们的遥感负责人,我首先使用中位数家庭收入百分比和个人贫困率百分比确定了 2011 -2015 年期间纽约市内符合 oz 资格的所有人口普查区域——总共 806 个。在这项工作中,所有符合条件的人口普查区域都被视为机会区,并在下方以深绿色显示在纽约市边界上方,以白色显示。然后,我使用谷歌地球引擎,用 Sentinel-2(一颗中等分辨率的公共卫星,每 10 天重复一次)为 2016 年的纽约市创建了一张合成的镶嵌图像。请注意,2016 年是 Sentinel-2 影像与所使用的 2011-2015 年人口普查数据最接近的一年。为了做到这一点,我提取了纽约市地区 2016 年全年的所有图像(总共超过 500 张图像),并通过过滤云来处理每张图像,然后在所有重叠图像的每个波段的每个像素处取平均值。这个过程产生了一组相邻的图像,每个波段中的每个像素都包含 2016 年的平均反射率值,然后我将它们拼接在一起,创建了一个单一的图像。这种合成使数据集正常化,并使季节变化以及云覆盖的差距均匀化。

有资格成为机会区的人口普查区域以绿色显示,覆盖在白色的纽约市上。
再次使用 Earth Engine,我使用 60,000 个点(代表不到 1%的可能像素)训练了随机森林分类器和最大熵分类器,这些点是使用 oz 和非 oz 的普查几何从合成 Sentinel-2 图像中提取的。我将这些分类器应用于整个合成图像,以将每个像素分类为 OZ 或 non-OZ,然后使用多数投票(一致)将两者组合成一个集成。生成的分类器如下所示,深绿色显示分类为 OZ 的像素,白色显示非 OZ。

集合 OZ 分类器,其中绿色分类为可能是 OZ,而白色分类为可能不是 OZ。
概括地说,这个模型比 random 要好,在 Bronx 和东布鲁克林表现得特别好,这两个地区的剪辑如下所示。我们第一个问题的答案是肯定的,纽约市的高收入和低收入地区有不同的共享光谱特征。很明显,拥有大量植被的区域往往被归类为非机会区域(公园是白色块证明了这一点),而拥有大量水泥和沥青的区域往往被归类为机会区域(深绿色的主要道路证明了这一点)。

布朗克斯:左边是 OZs,右边是 Classifier。

东布鲁克林和皇后区:左边是 oz,右边是 Classifier。
很明显,拥有大量植被的地区往往被归类为非 OZ(公园是白色块证明),而拥有大量水泥和沥青的地区往往被归类为 OZ(深绿色的主要道路证明)。然而,这些并不是唯一的驱动因素——否则,机场周围的区域将由于跑道而被完全归类为机会区域。因为对这种分类最重要的光谱带是红色、近红外和短波红外,所以植被、人造材料类型以及水和湿气积累可能起了重要作用。同样可能的是,某些类型的屋顶、街道的宽度和经常在街道上行驶的汽车数量也对分类有很大的影响。随着纽约市的街区继续中产阶级化,建筑物在新的发展时期之前被重新规划,过渡中的区域很可能提供混合的光谱特征。曼哈顿下城就是一个例子:虽然生活成本在过去几十年里飙升,但新建筑很少,改建的旧建筑受到青睐。分类器在曼哈顿下城的表现特别差,可能就是因为这个原因。

曼哈顿下城:左边是真正的 OZs,右边是分类器。
这项工作仍处于早期阶段,但它支持这样的假设,即存在与卫星可观测的经济机会相关的不同光谱特征的空间模式。卫星数据可能是一种有价值的工具,可用于以高分辨率了解经济机会、追踪某些类型的中产阶级化,以及以低成本和跨时间总体了解整个城市的社会阶层状况。随着新冠肺炎对纽约市的毁灭性影响,这种卫星地图的潜在用途已经成为前沿。在疫情早期就很清楚,低收入家庭更有可能生病,但也更有可能遭受严重的医疗并发症,从长远来看,更有可能受到不利影响。其他博客、文章和媒体来源已经利用公开的纽约市健康数据将病例与邮政编码联系起来,对此进行了广泛的研究(尽管许多研究人员没有按照人口对邮政编码数据进行标准化),我在这里不会深入研究这些趋势。展望未来,当我们学习减轻新冠肺炎时,高分辨率制图工具在选择测试地点或弹出式诊所方面可能是无价的。本着 John Snow 的精神,洛克菲勒基金会的数据科学团队致力于探索数据科学在现实世界问题中的这些新颖应用,以实现高效的社会变革。
将散点图发挥到极致
制图指南
我会教你散点图的来龙去脉;如何获得见解,以及如何以最具影响力的方式展示这些见解。啪啪!

“简单性可以归结为两步:
识别本质。
排除其他的。”—利奥·巴伯塔
我记得当我第一次开始为工作创建报告时,我想通过展示各种图形和图表来展示我的技能。我的想法是通过从每个角度呈现每个数据点,包括尽可能多的细节到一个图表中,来最大化我的报告和演示。我现在一想到这个就害怕。随着时间的推移,我了解到每个图表都有一个正确的情况,当涉及到用数据有效地传达一个故事时,越少越好。这就是我今天想和大家分享的信息。
无论您是专家还是新手,我相信这篇文章对任何想要提高或更新图形知识的人来说都是一个很好的资源。在这篇文章中,我想回顾以下内容:
- 何时使用散点图
- 如何在散点图中表示数据
- 理解和解释你的散点图
通过这些部分,我的目标是帮助你识别不必要的琐碎和噪音,为你的观众呈现一个简单、直白的故事,同时尽量避免过于专业。
何时使用散点图
散点图主要用于确定一对数值数据点之间是否存在因果关系。散点图在显示关系方面很有效,因为它可以很好地显示任何部分的数据点范围。如果我们使用条形图或折线图,我们能得出的最好结果就是数据的趋势。我们会错过很多其他潜在的见解。让我们看一个例子:


这里的图形表示相同的数据点,但是表示为折线图和散点图(忽略我不会画直线)。数据点是虚构的。它们并不代表任何已开展研究的实际数据。
假设我们有一组弓箭手的数据以及他们的高度和射击精度。如果我们用这些数据画一个线形图,我们可以看到身高和准确性之间有一个积极的趋势:射手越高,射得越准。但是这条线的方向并没有给我们带来额外的意义,并且使图表更加混乱。两点之间的线一般代表一段时间(通常是时间)内的变化,而不是其他数值。如果我们将数据绘制成散点图格式,我们可以看到高度和准确度对之间的趋势,并正确地看到值的范围。如果数据点没有趋势,那么折线图就不会给我们任何信息。我们只会看到像意大利面条一样的混乱,唯一可以接受的时候是当你是一个蹒跚学步的孩子发脾气的时候。
这里有一些问题,你可以问自己,以确定是否应该使用散点图:
- 我的数据需要用时间来表示吗?
- 我的数据需要汇总吗(取数据的平均值或总和)
- 我的数据是否少于两个非日期/时间变量?
如果你对这三个问题都回答了否,那么使用散点图应该没问题。
因此,在比较两个变量(其中一个不是时间)时,最好用散点图来表示数据,以便于阅读和分析。
如何在散点图中表示数据
现在我们知道了什么时候使用散点图,让我们花一些时间来确定如何最好地表示我们的图表。根据散点图的结果,我们可以专注于讲述一个特定的故事或发现。以下是我们强调我们发现的方法。
趋势线
趋势线是显示数据对之间关系的好方法。数据显示的是正相关还是负相关?数据对之间的相关性有多强?


正相关还是负相关?左图显示了身高和准确度之间的正相关关系,而左图显示了负相关关系。
看左边的图表,我们可以看到趋势线向上倾斜,显示了射手的高度和准确性之间的正相关,但这些点离趋势线相当远,表示弱相关。相比之下,右边的图表显示数据点向下倾斜并接近趋势线,表明有很强的负相关性。
斜率也告诉我们趋势线是完全水平还是垂直。水平趋势线表示 x 轴上的变量不影响 y 轴上的变量,而垂直趋势线告诉我们 x 轴变量与 y 轴变量无关。这两个结果是可以互换的,因为我们可以将 x 轴和 y 轴设置为任意一个变量。
使用趋势线,我们可以更容易地解释我们绘制的数据的性质。我在下面添加了趋势线能告诉我们的更简洁的定义:
趋势线斜率:趋势线的陡度告诉我们 y 轴的变化率是由 x 轴引起的。从原点向上或向下的斜率分别代表正向或负向趋势。完全水平的趋势线表示 y 轴没有变化,这取决于 x 轴的变量。垂直趋势线将显示 y 轴上的变化是由 x 轴上使用的变量之外的其他变量引起的。
趋势线的数据密度:趋势线的数据密度告诉我们变量之间的相互依赖程度。如果数据点接近趋势线,它们是非常依赖的。较低的密度(远离趋势线的数据点)表示其他因素可能与 y 轴或 x 轴相关。
趋势线的曲率:一条直的趋势线代表一个恒定的比率变化:对于 x 中的每个值,y 中的一个定义值都会发生变化。如果我们看到一条曲线趋势线(抛物线),那么变化率可能沿着 x 轴加速或减速。
如果您需要更多详细信息,下面是一个很好的资源,提供了有关趋势线、如何理解其斜率以及在坐标几何中的一般用途的更多信息:
定义:直线的斜率是一个衡量其“陡度”的数字,通常用字母 m 表示。它是…
www.mathopenref.com](https://www.mathopenref.com/coordslope.html)
将趋势线与图表的各个部分结合起来可以帮助我们讲述关于数据的更具体的故事。
横截面
根据我们试图告诉观众的故事,我们可以专注于散点图的某些部分。通常,从对数据的一般性解释开始,然后进行更深入的研究是一个好主意。这有助于你的观众理解并参与故事。

创建你的散点部分将有助于吸引你的观众的眼睛到与你的故事相关的信息。
从上图中,我们可以看出弓箭手数据的两个特点:
- 平均身高在 5 英尺 4 英寸到 5 英尺 6 英寸之间的弓箭手的准确度低于平均水平。我们可以做出这样的推断,因为该范围内的大多数数据点都低于趋势线。
- 6 '到 6' 8 "之间的弓箭手往往是平均身高和准确度。我们看到大多数数据点聚集在这个区域,代表平均值。
分割散点图的各个区域是关注平均值、高于/低于平均值的比率,甚至数据集的异常的好方法。使用这种技巧将真正帮助我们引导我们的观众通过我们试图讲述的故事。
环绕星团
当我们绘制散点图时,我们不一定会生成带有明显趋势线的图表,但这并不意味着我们不能从数据中获得洞察力。正如我们在趋势线部分所讨论的,我们可以有一个散点图,其中趋势线可以是垂直的或水平的。见下文:

同样,这个图表不是用任何事实数据构建的。其目的是为了说明一个观点。具体来说,要说明很多点!(双关绝对有意)。
这里我们有一个不同的数据集(最后,我也厌倦了弓箭手的数据)。x 轴代表作家使用的平均字数,y 轴代表他们的经验年限。让我们通过圈出数据点的聚类来看看我们能从这个数据集看出什么。

是的,我确实意识到我说过散点图不应该与时间间隔一起使用,但是在这个场景中,年不是作为一个连续的时间间隔使用的。岁月是对一个人有多少经验的定量衡量,而不是过去了多少时间。
看上面图表中圈出的数据,似乎有几年写作经验的作家,以及有更多年写作经验的作家,倾向于使用更短的词。另一方面,中等写作经验范围内的作家使用更长的单词。我们可以从中得出的结论是:
新写手的词汇量更小/更简单,因此单词长度更短。随着作者变得更有经验,词汇量扩大,使用更长的单词。最有经验的作家会重新使用较短的词语,因为经验告诉他们,易读性有助于更好的写作和更广泛的受众。
我们不需要寻找一种趋势来确定一种模式。我们可以把重点放在聚类或异常上,看看我们是否能理解这些数据,或者下一步把我们的研究重点放在哪里。
理解和解释你的散点图
我们花了大量时间讨论解释和呈现数据的不同方式,但知道如何真正理解和解释数据所呈现的内容同样重要。从散点图中很容易做出有偏见的假设,所以让我们来看看一些潜在的陷阱,看看我们如何避免犯这些错误。
确保样本具有总体代表性
我们的散点图很少包含我们试图考虑的全部人口。包括我们自身偏见在内的不同因素会影响我们对数据的采样方式。谷歌新闻就是一个很好的例子。根据你过去的搜索历史,谷歌会向你展示“相关的”新闻话题和广告,但这些话题和广告并不代表整个人群,它只是你感兴趣的范围。
如果你想了解更多关于如何让谷歌新闻表面不偏不倚的文章,你可以在这里查看我早先的媒体帖子:
在我们这个以技术进步、数字化和几乎无限制为特征的全球社会中…
medium.com](https://medium.com/@irfanhashmi/using-google-news-for-unbiased-information-why-it-matters-6d0fba31138f)
让我们看看下面的图表,更深入地了解这一现象。

代表财富和爱猫/爱狗人士平均分配的图表。(这是我不介意进入的朋友区)。
让我们假设我们生活在一个财富与某人更喜欢狗还是猫没有关联的世界。在这个世界上,事实是富有的爱狗人士和不那么富有的爱狗人士一样多,猫也是如此——人口分布均匀。现在让我们假设你主要和富有的养猫人在一起。如果你根据对你相处过的人的调查制作了一个散点图,你会错误地看到爱猫的人和更富有的人之间的相关性。你会错过调查铁杆爱狗人士和财富地位较低的人。
这里有另一个例子:你向所有 1000 名订户发送了一封电子邮件,宣传一种要出售的新产品。您的 100 名订户购买了该产品。其中 60 人住在俄亥俄州,其余 40 人住在其他州。你可以使用 100,并确定:“哇,俄亥俄州人特别喜欢我的产品!”现在,假设你的 1000 名用户中有 800 名来自俄亥俄州。这改变了现在的故事,不是吗?你刚刚向比其他州更多的俄亥俄州人推销。俄亥俄州人不一定喜欢你的产品,只有 7.5%的人买了你的产品。另一方面,20%的非俄亥俄州人也购买了你们的产品。(不过,俄亥俄州人很喜欢你,所以订阅了你的博客)。
为了确保您有一个良好的样本集,请确保您使用的数据真正代表您试图描绘的洞察力。一个很好的练习是:
- 记下你试图评估的行为或活动。
- 确定进行评估需要考虑的所有变量。
- 检查您当前的取样方法是否足够彻底,能够包含步骤 2 中确定的变量。
相关性并不总是可传递的
每个人都得到一个坚韧不拔的重启这些天…smh
如果所有的鲍勃都是建筑工人,并且所有的建筑工人都能修理它,那么所有的鲍勃都能修理它吗?是的,他们可以。因为能够“修复”是 bob 的传递相关性。但是在这个例子中,关键字是所有。我们做了一个非常笼统的陈述。如果我们说如果所有的 bob 都是建造者,并且一些建造者可以修复它,那么不是所有的 bob 都可以修复它。
所以当我们制作多重散点图时,我们必须小心不要假设不同的相关性和它们的变量是相互关联的。它们并不总是可传递的。
这里有一个更非 SAT 风格的场景。假设我们绘制了两个图表:
- 睡眠时间与生产时间的对比图
- 生产小时数与一天内写的文章数的关系图。
如果两个图表都显示了每个变量的正相关,那么似乎合乎逻辑地说“一个睡得更多的人可以在一天内写更多的文章。”这可能是真的,但我们无法推断出这个结论。我们不知道相关性是否传递。一个人写更多的文章可能是因为他们吃了富含碳水化合物的早餐,为他们的大脑提供了能量。我们真的不知道。所以最好避免这种比较,除非我们能证明相关性是传递的。如果你能花时间证明传递性的存在,那么添加这些细节可能是值得的。

老实说,这是我在会议中最喜欢画的图表之一。(请随意使用它来获得“房间里最聪明的人”奖。我知道,出于同样的原因,我也很内疚。
让我们用上面的图表来说明这个例子。这包括一些多维图形,我们不会在这里深入讨论(关于理解这一点的参考资料在文章的底部)。可以说,“x”是一个向量,代表我们的样本的所有睡眠时间,“y”是一个向量,代表我们的同一样本的所有生产时间,“z”是一个向量,代表同一样本撰写的所有文章。相差小于 90 度的矢量被称为相关矢量,因为它们通常在同一方向运动。这些是向量 x 和 y,y 和 z。如果我们观察矢量‘x’和‘z ’,它们有一个大于 90 度的差,表明这两个矢量是不相关的。
所以为什么要费这么大的劲来解释这个呢?因为,作为讲故事的人,你的工作是引导你的听众,帮助他们避免对你的发现的误解。我个人发现在白板上使用向量示例对解释这个概念非常有帮助。许多企业将根据这些发现做出决策,因此确保我们帮助他们做出最好、最准确的决策非常重要。
决定先有鸡还是先有蛋的问题
我们已经学到了很多关于从散点图中可以做出什么样的推论,但是我们仍然需要确定先有鸡还是先有蛋?
我们已经看到了弓箭手的身高和他们的准确性之间的正相关关系,但是是高的弓箭手更准确还是射击准确的弓箭手变高了?这可能听起来像是一个荒谬的问题,但这是一个你必须以某种方式证明的观点。
这里有一个更模糊的例子。让我们假设比一般孩子更爱发脾气的孩子和学校成绩差之间存在关联。哪个是原因,哪个是结果?成绩差会让孩子发更多的脾气吗,或者发脾气的孩子成绩更差吗?鸡还是蛋?
隔离相关结果的一个好方法,或者像我喜欢说的,“快速和肮脏的方法”,是创建更多的可能因素的图表。我们可以检查营养如何影响成绩吗?我们还可以检查单亲家庭或双亲家庭是否有所不同吗?可能会发生很多反复,但在我们向观众讲述之前,我们作为故事讲述者有责任确定真相。
如你所见,我们可以做很多事情,甚至在创建散点图时需要考虑更多。为了帮助总结它们,下次创建散点图时,请记住以下问题:
- 我想向我的观众展示什么故事或推荐?
- 使用散点图是表示我的数据的最佳方式吗?
- 关于我绘制的数据,我的趋势线告诉了我什么?或者还有其他出现的模式吗?
- 我的数据集能代表整个人口吗?
- 我是否恰当地关联了我的发现?
- 我是否采取了足够的步骤来确定真正的因果性质?
浏览这些步骤将帮助您识别数据中最重要和最相关的方面,并恰当地呈现它们。由于我们还进行了一些关于如何思考你的发现的心理练习,你将能够在演示环境中自信地对你的观众说话。
如果你喜欢学习散点图和可以深入其中的思想,我强烈推荐阅读乔丹·艾伦伯格的书《如何不出错:数学思维的力量。
这是我 2019 年最喜欢的读物,你可以从中学习很多很好的概念,并将其应用到日常工作中。本书的第 4 部分, Regressions,有很多关于如何从概念上看待数据以及从中可以得出什么含义的详细内容。这本书那一部分大量使用了散点图。它还包含了许多其他易于理解和有力使用的数学概念。(上述链接是一个附属链接,我可能会通过该链接的销售佣金得到补偿)。
我喜欢收到对我工作的反馈,所以请随意评论或给我发消息。如果你对我写的任何东西有任何问题或需要澄清,请随时给我发消息这里。我很乐意尽我所能帮忙!
快乐学习!
使用 Scikit-learn 的二叉树有效地查找经纬度邻居
在不破坏 Python 解释器的情况下将多组 GPS 坐标连接在一起

来自纬度和经度数据的工程特征看起来像是一项杂乱的任务,可能会诱使新手创建自己的 应用函数 (或者更糟:一个巨大的 for 循环 )。然而,这些类型的 强力 方法是潜在的陷阱,当数据集的大小增加时,这些陷阱会很快暴露出来。
例如:假设您有一个包含 n 项的数据集。将这些 n 项与 n-1 其他项进行显式比较所需的时间基本上接近 n 。这意味着数据集中的行每增加一倍,查找所有最近邻的时间就会增加 4 倍!
幸运的是,你不需要计算每个点之间的距离。在scikit-learn中有一些数据结构可以有效地确定邻居权限,这些数据结构利用了 优先级队列 的力量。
它们可以在邻居模块 中找到 ,本指南将向您展示如何使用这些难以置信的类中的两个来轻松解决这个问题。
入门指南
首先,我们加载库。
import numpy as np
from sklearn.neighbors import BallTree, KDTree# This guide uses Pandas for increased clarity, but these processes
# can be done just as easily using only scikit-learn and NumPy.
import pandas as pd
然后我们将根据从国家海洋和大气管理局公开获得的气象站位置制作两个样本数据框架。
# Column names for the example DataFrame.
column_names = ["STATION NAME", "LAT", "LON"]# A list of locations that will be used to construct the binary
# tree.
locations_a = [['BEAUFORT', 32.4, -80.633],
['CONWAY HORRY COUNTY AIRPORT', 33.828, -79.122],
['HUSTON/EXECUTIVE', 29.8, -95.9],
['ELIZABETHTON MUNI', 36.371, -82.173],
['JACK BARSTOW AIRPORT', 43.663, -84.261],
['MARLBORO CO JETPORT H E AVENT', 34.622, -79.734],
['SUMMERVILLE AIRPORT', 33.063, -80.279]]# A list of locations that will be used to construct the queries.
# for neighbors.
locations_b = [['BOOMVANG HELIPORT / OIL PLATFORM', 27.35, -94.633],
['LEE COUNTY AIRPORT', 36.654, -83.218],
['ELLINGTON', 35.507, -86.804],
['LAWRENCEVILLE BRUNSWICK MUNI', 36.773, -77.794],
['PUTNAM CO', 39.63, -86.814]]# Converting the lists to DataFrames. We will build the tree with
# the first and execute the query on the second.
locations_a = pd.DataFrame(locations_a, columns = column_names)
locations_b = pd.DataFrame(locations_b, columns = column_names)
这创建了两个极小的数据帧:一个有 7 行,一个有 4 行。对于这么小的数据集,我们将要使用的数据结构不会在性能上提供任何帮助(实际上会是一个障碍)。 文档 提供了更多关于何时选择一种算法比另一种算法更有利的信息。
使用 k-d 树
我们先用一棵 k-d 树 来演示。它会将数据点分割成二叉树,每次评估一个维度,并在中间值上进行分割。然后,将使用另一组点和值 k 对其执行查询,以确定每个点要返回多少个相邻点。
# Takes the first group's latitude and longitude values to construct
# the kd tree.
kd = KDTree(locations_a[["LAT", "LON"]].values, metric='euclidean')# The amount of neighbors to return.
k = 2# Executes a query with the second group. This will return two
# arrays.
distances, indices = kd.query(locations_b[["LAT", "LON"]], k = k)
结果是两个数组。一个用于距离,一个包含相邻位置的索引(指用于构建树的数据帧)。
然后,索引可以被 映射到 到有用的值,并且两个数组容易地 与其余数据合并 。

显示的距离是原始坐标之间的欧几里德距离。
一些限制
k-d 树在没有大量维度的情况下表现很好。虽然这似乎适合这里的数据;在纬度和经度的情况下,基于欧几里德距离的差异进行评估将会以精度为代价。

图片来自 Pixabay 的 Gerd Altmann
为了更接近地近似坐标间的实际距离,我们可以使用。不幸的是,k-d 树算法对此不起作用,因为它对于每个维度都有一种有点僵硬的方法。要查看哪些可用的距离度量可用于 k-d 树数据结构,请使用以下命令:
*KDTree.valid_metrics*
使用球树
一个 球树 类似于一个 k-d 树,除了它不是在一个单独的维度上进行划分,而是根据到中心的径向距离来划分点。它能更好地处理更高维度的数据,也允许使用哈弗斯度规。
要在 scikit-learn 中使用具有哈弗线距离的球树,必须首先将坐标从角度转换为弧度。
*# Creates new columns converting coordinate degrees to radians.
for column in locations_a[["LAT", "LON"]]:
rad = np.deg2rad(locations_a[column].values)
locations_a[f'{column}_rad'] = rad
for column in locations_b[["LAT", "LON"]]:
rad = np.deg2rad(locations_b[column].values)
locations_b[f'{column}_rad'] = rad*
除此之外,这两个 API 几乎是相同的,并且大部分过程将在最小的修改下重复。
*# Takes the first group's latitude and longitude values to construct
# the ball tree.
ball = BallTree(locations_a[["LAT_rad", "LON_rad"]].values, metric='haversine')# The amount of neighbors to return.
k = 2# Executes a query with the second group. This will also return two
# arrays.
distances, indices = ball.query(locations_b[["LAT_rad", "LON_rad"]].values, k = k)*
最后一点注意:返回的距离将基于半径为 1 的 单位球体 。如果您想查看反映典型测量值的值,这是一个简单的转换。
- 到英里:距离 x 3,958.8(地球的半径以英里为单位)
- 到千米:距离 x 6,371(地球半径以千米为单位)

以英里为单位的最终数据帧。
在这个例子中只有 12 个数据点,使用具有哈弗斯度量的球树的优势无法显示。但是在一个更大的稀疏度更低的数据集中,这将是你在用纬度和经度进行评估时想要做出的选择。
当然,这两种算法都不仅限于这种用例,此处显示的步骤可以扩展到可以通过多种距离度量之一排序的任何一组要素。
我们建模的复杂系统有无数的影响因素,这些因素导致了数据科学家们一直在努力减少的误差水平。有了这些工具,您将更有能力有效地发现其他因素,并将特征工程提升到一个新的水平。
使用 scispaCy 进行命名实体识别(第 1 部分)
从生物医学文献中提取数据的分步指南

比阿特丽斯·佩雷斯·莫亚在 Unsplash 上的照片
2019 年,艾伦人工智能研究所(AI2)开发了 scispaCy,这是一个完整的开源 Python 空间管道,旨在使用自然语言处理(NLP)分析生物医学和科学文本。scispaCy 是一个强大的工具,特别是用于命名实体识别(NER),或识别关键字(称为实体)并将其分类。我将向您介绍在 NER 使用 scispaCy 的基本知识,您将很快成为 NLP 大师。
议程
- 设置环境
- 安装熊猫
- 安装 scispaCy
- 选择型号
- 导入包
- 输入数据
- 选择数据
- 实现命名实体识别
- 更大的数据
设置环境
第一步是选择一个工作环境。我用过 Google Colab,但 Jupyter 笔记本或简单地从终端工作也很好。如果您确实在终端上工作,请确保创建一个虚拟的工作环境。如果你是在 Google Colab 工作,没必要这么做。关于创建虚拟环境的简单易懂的说明可以在这里找到。
因为我使用的是 Google Colab,所以使用的语法可能与其他环境下使用的略有不同。
这个项目的完整代码可以在这里找到。
自从写了这篇博客之后,这个项目的代码已经被更新了。总体布局保持不变。
安装熊猫
Pandas 是一个用于数据操作的 Python 库。这将有助于导入和表示我们将要分析的数据(在下一节中讨论)。如果你使用 Google Colab,pandas 是预装的,所以你可以忽略这一步。否则,使用 Conda 或 PyPI(无论您喜欢哪个)安装 pandas。您可以在此查看安装过程的所有步骤。
安装 scispaCy
安装 scispaCy 非常简单。它的安装就像其他 Python 包一样。
!pip install -U spacy
!pip install scispacy
选择一个预先训练好的科学模型
安装 scispaCy 之后,接下来需要安装他们的一个预制模型。科学模型有两种风格:核心和 NER。基于存储的词汇数量,核心模型有三种大小(小、中、大),它们识别实体但不对它们进行分类。另一方面,NER 模型对实体进行识别和分类。有 4 种不同的 NER 模型建立在不同的实体类别上。您可能需要尝试不同的模型,以找到最适合您需求的模型。型号和规格的完整列表可在这里找到。一旦你选择了一个模型,使用模型 URL 安装它。
!pip install [https://s3-us-west-2.amazonaws.com/ai2-s2-scispacy/releases/v0.2.4/en_core_sci_sm-0.2.4.tar.gz](https://s3-us-west-2.amazonaws.com/ai2-s2-scispacy/releases/v0.2.4/en_core_sci_sm-0.2.4.tar.gz)
安装“en_core_sci_sm”模型的例子
导入您的包
一旦您安装了所有的软件包,并且创建了一个虚拟环境,只需导入您刚刚下载的软件包。
import scispacy import spacy import en_core_sci_sm from spacy import displacy import pandas as pd
您可能会注意到我们还导入了一个额外的包“displacy”。Displacy 不需要执行任何 NER 动作,但它是一个可视化工具,可以帮助我们看到正在发生的事情。
导入数据
对于这个例子,我们使用了来自 CORD-19 的元数据,这是一个关于新冠肺炎研究论文的开放数据库。元数据,以及文章的完整收藏,可以在这里找到。
元数据文件有点挑剔,所以如果该文件不符合您的要求,只需将内容复制到一个新文件中。这应该可以解决您在使用该文件时遇到的任何问题。
为了导入数据,我们使用 pandas read_csv() 函数。
df = pd.read_csv(“content/sample_data/metadata.csv”)
该函数读入文件路径并将数据存储为 DataFrame,这是 Pandas 的主要数据结构。关于 pandas 如何存储和操作数据的更多信息,你可以在这里查看文档。
如果您使用的是 Colab,可以将文件拖到“文件”部分,然后右键单击并选择“复制路径”来轻松访问您想要的文件的路径。
选择相关数据
元数据提供了大量关于 CORD-19 中 60,000 多篇论文的有用信息,包括作者、参考号等。然而,出于我们的目的,我们关心的数据是摘要。每篇论文的完整摘要列在“摘要”一栏下。所以,我们的下一步是选择这段文字。我们将使用 DataFrame loc 函数来实现。该函数接收数据帧中单元格的位置,并返回该单元格中的数据。要访问特定的摘要,只需键入所需的特定行和列标题,并存储为字符串变量。
text = meta_df.loc[0, “abstract”]
这将找到位于表的第一行的摘要(记住,在 Python 中,索引从 0 开始)。然后,您可以打印新创建的字符串,以验证您是否拥有所需的数据。它应该是这样的:

命名实体识别
现在你有了你的文本,你可以进入有趣的部分。多亏了 scispaCy,实体提取相对容易。我们将使用核心模型和 NER 模型来强调两者之间的差异。
核心型号:
nlp = en_core_sci_sm.load()
doc = nlp(text)
displacy_image = displacy,render(doc, jupyter = True, style = ‘ent’)
您的输出应该如下所示:

NER 模式:
nlp = en_ner_jnlpba_md.load()
doc = nlp(text)
displacy_image = displacy,render(doc, jupyter = True, style = ‘ent’)
在这里,我们使用了一个模型来识别 DNA、细胞类型、RNA、蛋白质、细胞系类型的实体
输出应该如下所示:

扩展到更大的数据
就这样,你成功地在一个样本文本上使用了 NER!但是,这只是 CORD-19 元数据中超过 60,000 个摘要中的一个。如果我们想在 100 篇摘要中使用 NER 会怎样?1000 呢?他们所有人呢?这个过程,虽然需要更多的技巧,但本质上和以前是一样的。
当你阅读这一部分的时候,我强烈推荐跟随 Google Colab 项目来充分理解我们正在做的事情。
所以,第一步和之前一样。我们需要读入数据。
meta_df = pd. read_csv(“/content/metadata.csv”)
再次使用元数据文件的特定路径
接下来,我们加载我们的模型。对于这个例子,我们将使用所有 4 个 NER 模型,所以如果您还没有安装和导入它们,您将需要安装和导入它们。只需按照前面描述的说明,然后加载它们。
nlp_cr = en_ner_craft_md.load()
nlp_bc = en_ner_bc5cdr_md.load()
nlp_bi = en_ner_bionlp13cg_md.load()
nlp_jn = en_ner_jnlpba_md.load()
接下来,我们想要创建一个空表来存储实体和值对。该表将有 3 列:“doi“”、“实体”和“类”。该表将被规范化,以便每个实体/类对的 doi 将在“doi”列中,即使该 doi 已经被列出。这样做是为了在任何列中都没有空格,如果您以后想将数据用于其他程序,这将很有帮助。要创建这个表,您需要创建一个包含 3 个列表的字典。
table= {“doi”:[], “Entity”:[], “Class”:[]}
现在事情变得有点复杂了。我们将从遍历整个文件开始。为此,我们使用 pandas index 函数,它给出了值的范围(行数)。然后我们使用 itterrows() 函数迭代整个文件。所以,你的循环应该是这样的。
meta_df.index
for index, row in meta_df.iterrows():
对于循环的每次迭代,我们想要提取相关的抽象和 doi。我们也想忽略任何空洞的摘要。在 Python 中,空单元格存储为 nan,其类型为 float。
text = meta_df.loc[index, “abstract”]
doi = meta_df.loc[index, “doi”]
if type(text) == float:
continue
continue 语句结束循环的当前迭代,并继续下一次迭代。这允许我们跳过任何带有空白摘要的行。
现在我们有了文本,我们需要使用之前加载的一个模型来提取实体。如果你在 Google Colab 上查看代码,这个步骤被分成了几个独立的方法,但是也可以不使用任何 helper 方法来编写。但是,请注意,最好是一次运行一个模型,尤其是在 Colab 中,读写文件需要相当长的时间。使用 4 个 NER 模型之一的聚合代码应该如下所示:
doc = nlp_bc(text)
ent_bc = {}
for x in doc.ents:
ent_bc[x.text] = x.label_for key in ent_bc:
table[“doi”].append(doi)
table[“Entity”].append(key)
table[“Class”].append(ent_bc[key])
记住所有这些代码都在最初的 for 循环中
这段代码可能看起来很吓人,但实际上,它与我们已经练习过的非常相似。我们通过一个模型传递文本,但是这次不是使用 displacy 显示结果,而是将它存储在一个字典中。然后,我们遍历字典,将结果以及我们正在查看的文章的相应 doi 添加到我们的表中。我们继续这样做,循环遍历文件中的每一行。一旦表被填充,并且 for 循环已经结束,最后一步是创建一个输出 CSV 文件。感谢熊猫,这很容易。
trans_df = pd.DataFrame(table)
trans_df.to_csv (“Entity.csv”, index=False)
您可以为输出文件选取任何标题,只要它遵循所示的格式。一旦你的代码运行完毕,输出文件就会出现在 Google Colab 的“文件”部分。然后你可以下载文件,欣赏你所有的辛勤工作。

使用 bc5cdr 模型的 CSV 输出示例(在 Excel 中打开)
或者,您可以从 Gofile 下载所有 4 个输出 CSV 文件。
进一步探索
有许多方法可以使用我们刚刚收集的数据。我们可以采取的一个方向是使用图形数据库在语义上链接发布。查看这个项目的第二部分,我将带你了解如何创建一个图表并对我们的数据建模。
结论
如果你关注了这篇文章,那么恭喜你!你刚刚在科学文献的科学空间和 NER 的世界里迈出了第一步;然而,还有更多的东西需要探索。仅在 scispaCy 中,就有缩写检测、依存解析、句子检测等方法。我希望您喜欢学习一些关于 scispaCy 的知识,以及如何将它用于生物医学 NLP,并且我希望您继续尝试、探索和学习。
资源:
- https://uoa-e search . github . io/e search-cookbook/recipe/2014/11/26/python-virtual-env/
- https://github . com/akash-Kaul/Using-scis pacy-for-Named-Entity-Recognition
- https://pandas . pydata . org/pandas-docs/stable/getting _ started/install . html
- https://allenai.github.io/scispacy/
- 【https://www.semanticscholar.org/cord19/download
- https://pandas.pydata.org/pandas-docs/stable/index.html
使用 SelectFromModel & SHAP 创建一个更好解释的 XGBClassifier 模型
如何改进你的模型,并将结果传达给你的非技术团队。

图片由 Aral Tasher 在 Unsplash 上拍摄
处理零售数据总是很有趣,尤其是与客户的人口统计数据结合在一起的时候。当我在探索星巴克简化的促销、产品组合和顾客数据时,我知道我想用它做什么。这很简单;练习一些编码、数据工程、特征工程和模型设计。
该模型将用于更好地预测未来促销的影响,以及谁将从中受益。然而,我在这里的真正目标是探索【选择来自模型】函数&【SHAP】库。
因为我们需要一个分类模型,我们有一些很好的选项可以尝试。我们可以尝试集合方法,或者 KNN,神经网络,或者任何可以完成这项工作的方法。然而,由于数据是高度分类的,并且神经网络计算量大,我们将使用集成方法。此外,将神经网络用于所有事情会阻止我们扩展我们的技能集和对数据的理解。
数据探索和清理
除了客户的收入和性别。三个数据框中的所有字段都没有丢失任何值。例外的两个字段是不可归入的,因为许多值缺失(各 12.79%),并且由于没有提交此类数据的客户也提供了假的出生年份(他们是 118 岁,这不太可能)。由于估算是不可能的,而且我不想输入缺少所有人口统计数据的记录,所以这些记录被从数据框中删除了。
之后,是 ol '常规预处理;更正数据类型,为非二进制分类字段创建虚拟变量,解析字段,并删除不重要的字段。
数据和特征工程
为了利用一些看起来不重要或无用的领域,需要设计新的领域。字段“time”包含从数据收集开始到一个月之前的小时计数器。我已经创建了字段“一天中的某个小时”和“一周中的某天”。

照片由 Isaac Smith 在 Unsplash 上拍摄
出于探索的目的,我还在字段【值】之外创建了字段【关键字】和【值】。然而,它们没有任何用处,因为“价值”中的不同值表示不同的概念(一些是花费的金额,另一些是报价标识符),而“密钥”只是降级的“标签”。
此外,我还创建了字段“任期”来显示每个人注册该应用程序后的天数。
现在,这三个数据框已准备好合并成一个大数据框。
模型设计和改进
我决定对购买的状态进行分类,不管是不是已经兑现。我选择分类的一个原因是,数据不包含要约-赎回交易的支付金额。如果所有交易的价值都是可用的,我们可能会得到一个更好的模型,甚至更好,探索每个报价对销售和收入的影响。
有四个标签要分类;a) 【交易】,当没有报价要查看时进行交易,b) 【报价已接收】,报价已发送,但未被查看,c) 【报价已查看】,发送的报价已被用户查看,但未被兑换,最后,d) 【报价已完成】,用户已兑换报价。
在将数据帧合并成一帧并删除所有不需要的列之后。我已经将数据分为训练和测试特征和标签。我手动这样做是为了确保标注的比例与原始数据框的比例相同。
第一个分类器表现不错;其准确率为 84.23%。然而,只有少数特征变得重要,其余的对模型的结果几乎没有影响。

初始 XGBClassifier 的特征重要性
如上所示,一些顶级特性是工程变量。由此可见,深入理解数据是多么重要。我没有进一步探索模型的性能,因为数字功能将大幅减少,模型的参数肯定会变得更好。
对模型的偏差结果有一个奇怪的观察。混淆矩阵显示,该模型能够 100%正确地对“收到的报价”进行分类。然而,它只在 44%的情况下正确地分类了“交易”。
在将我们的模型传入 "SelectFromModel" 函数,并迭代各种阈值后,我得到了以下模型,性能达到 84.73%的准确率。
SelectFromModel(estimator=XGBClassifier(base_score=0.5,
booster='gbtree',
colsample_bylevel=1,
colsample_bynode=1,
colsample_bytree=1, gamma=0,
learning_rate=0.01,
max_delta_step=0,
max_depth=3,
min_child_weight=1,
missing=None,
n_estimators=100,
n_jobs=1, nthread=None,
objective='multi:softprob',
random_state=1, reg_alpha=0,
reg_lambda=1,
scale_pos_weight=1,
seed=None, silent=None,
subsample=1,
verbosity=1),
max_features=None, norm_order=1, prefit=True,
threshold=0.0058939322)
微调和结果解释
由于人口统计不适合作为特征,我保留了之前删除的记录,然后再次运行改进的模型。这导致模型的准确性略微提高了 1.28%(现在为 86.01%)。
更好的是,该模型对“transaction”的偏见大幅减少,59%的时间正确标记了类别。【报价已完成】和【报价已查看】偏差略有增加。然而,这个模型现在看起来好多了。

微调后的混淆矩阵
接下来,我们应该尽可能地解释结果。这将有助于决策者更好地理解数据、模型和输出,从而做出明智的决策。
我为此使用了“SHAP”库(SHapley Additive exPlanations 的简称)。这个库将 ML 模型从黑盒变成了人类可读的图形。

特征对标签分类的影响
该图显示了我们选择的每个特征对每个标签的分类的影响。请注意,并非所有功能都对每个标签有影响。
接下来,我们将分别探讨对每个标签的影响,以更好地理解每个特征的值如何影响分类器。

特征对交易的影响
现在我们看到了每个标签上的每个功能的影响。我们可以用人类能够理解的方式向决策者解释我们的发现和模型。类似于“随着报价难度的增加,客户更有可能进行常规交易。”或更老的

功能对已查看产品的影响

功能对报价的影响已完成

功能对收到的报价的影响
客户更有可能兑现优惠。然而,这并不意味着年轻客户更喜欢不兑现优惠。听起来比好多了“年龄对要约兑现有负面影响。”。
请注意,某些特征对某些标签来说比其他标签更重要。如果每个标签上的每个特征的效果被适当地解释,而不是特征的一般意义,这将使行动的计划变得容易得多。
此外,我们可以看到,并非所有类别的功能都很重要。在对通信渠道进行处理后,我们可以看到只有【移动】类别的【渠道】特征对分类有一定的影响。
结论
这是数据分析的一个经典案例,其中包括探索、预处理、建模和微调。这可以通过创建一个端到端的管道来自动化整个过程,或者至少是大部分过程来进一步改进。
使用 Selenium 和 deepL 自动翻译 PowerPoint 文件

照片由 Aaron Burden 在 Unsplash
曾经想要高效地翻译 PowerPoint 幻灯片吗?了解如何使用 Python 自动完成这一过程!
对自动化的需求
像许多顾问一样,我非常习惯使用 PowerPoint 演示文稿。但是最近,我被要求以一种非常特殊的方式与那些 *打交道。pptx 文件。我没有展示我的工作或任何策略,而是被要求将一打又一打的幻灯片从法语翻译成英语。我的第一个想法和你一模一样:“真无聊…!!"。
是的,很无聊!但是你知道吗?事实是,开发人员非常喜欢被分配无聊的工作。这样他们就可以自动操作了!下面是我翻译数百张幻灯片中数万个单词的故事。
在本文中,您将发现如何用 Python 操作 PowerPoint 文件,以及如何从翻译网站提取信息。但是让我们从一个小的基准开始,从为什么开始,然后再到如何!
寻找现有的解决方案
紧接着绝望的“太无聊了…”、和自信的“我会花一天时间给自己造一个神奇的机器人,这将使我接下来 3 天的工作顺利进行”之后,是务实的“我肯定能在网上找到免费的 PowerPoint 翻译器”。
一个表演工具需要调和两者:
- 短句和长句的良好翻译水平
- 不修改原始文件的设计
在用于文档修改的免费在线工具的丛林中,很容易找到来自 *的转换器。pdf 至 **。docx,来自倍数 *。jpgs 到单个。pdf、文本翻译、表格提取、文件压缩等。表现非常好。不幸的是,PowerPoints 是复杂的文档,你可以找到许多不同的对象,它们可以组合在一起。
我轻而易举地找到了提供这种服务的网站,在几张幻灯片上做了一些测试,然后……立刻让自己完全听天由命了。在尝试了不同的翻译器之后,结果相当令人失望:翻译过于近似,并且字体/大小/颜色与源文档相比不时改变。毫无疑问,这可以完成部分工作。但是我仍然需要非常谨慎和耗时地阅读这些文件。冒着忘记明显的误译的风险,这些误译一眼就能看穿我。不会吧。
但愿我已经听同事们谈论过 PowerPoint 模板脚本,并阅读了许多关于这个主题的文章。有几个星期,我想实现一个 selenium 项目,那里有强大的在线翻译器,对网络用户免费。定制工具的想法诞生了。
基准测试和起草工具
堆栈展示
经过一些研究后,我相信下面的堆栈会产生有趣的结果。使用 python-pptx 包与文件进行交互。它允许我们处理演示文稿、幻灯片、形状、表格、段落、分组框等等。这个工具似乎很有前途。对于翻译部分,我会选择由人工智能驱动的翻译机。由于它的 API 不是免费的,Selenium 将非常适合访问网页和自动翻译。
为什么选择 Python-pptx?
Python-pptx 是一个 Python 包,可以通过常用的pip install python-pptx命令行获得。它允许我们以一种非常结构化的方式探索 PowerPoint 文件。首先处理一个Presentation对象,它包含所有高级信息。然后你访问Slides对象,在每张幻灯片中你访问Shapes对象。形状可以是文本框、表格、图表、图像,甚至是一组形状。非常直观。
当你处理一个text_frame实例时,你可以访问文本和字体。看起来完美地满足了我们的需求:翻译文本而不改变其他任何东西!
最重要的是,文档非常全面,写得很好。
为什么是 DeepL?
DeepL 是一个基于卷积神经网络的人工智能翻译器,它是在linguie数据库上训练的。它于 2017 年推出,据报道,很快就在盲测中超越了竞争对手,包括谷歌翻译、微软翻译和脸书。与竞争对手的速度一样,它也将更加精确,更加细致入微。

一个强大的基于人工智能的翻译器,在线提供
我不会实现一个完整的基准,但这里有一个 DeepL 和谷歌翻译之间的比较。
英文原文
在过去,蚂蚁和蝉是朋友。他们非常不同。当蚂蚁努力工作的时候,蝉什么也没做。他们整天唱歌跳舞。
谷歌翻译
秋天、四月和十一月。 Ils 培训课程差异。在工作期间,没有香烟。在整个旅程中,他们都在歌唱和跳舞。
深度翻译
家庭旅游、旅游和吸烟。Ellesétain tèRSdifferentes。在这四个月的工作中,没有香烟。在整个旅程中。
对于不懂法语的读者,我们在这里介绍了 Google 和 DeepL 之间的两项重大改进:性别检测(在法语中,蝉和蚂蚁都是阴性词,因此我们用 elles 而不是ils来修饰它们,后者是 elles 的阳性词),以及时间的一致性,即根据事件发生的时间使用正确的变化形式。
这里的例子已经足够了,但是在很少的单词的短句中,翻译也要好得多,并且可以很好地处理停用词。这对 PowerPoint 用例非常重要。我们开始吧,我们将使用 DeepL。
为什么是硒?由于 DeepL 的 API 不是免费的,我们将不得不从它的网络接口使用它。无需争辩,Selenium 是完成这类任务的完美工具。正如他们自己所说:
Selenium 自动化浏览器
要发现 Selenium 的威力,我建议你阅读这篇文章,它详细介绍了这个库,并解释了如何创建 Instagram 机器人。
动手操作
从无到有的工具
正如你可能已经猜到的,这个项目有两个不同的部分,这基本上意味着三个步骤:
- 构建翻译器
- 与 PowerPoint 演示文稿互动
- 放在一起
让我们深入研究并发现解决方案是如何工作的!
构建翻译器
为了记住这个过程,我们要做的是
- 创建 selenium 驱动程序
- 打开 deepL 网页
- 将文本发送到网页的输入文本区域
- 从翻译文本区域阅读翻译文本
- 关闭页面和 selenium 驱动程序
直截了当,是吧?
不幸的是,deepL 页面都是用 Javascript 编写的,翻译后的文本不会出现在 HTML 内容中,即使显示在屏幕上也是如此。这是第一个挑战:不可能确定从哪个 Html div(或其他什么)组件中提取文本内容。
希望 Selenium 是一个伟大的工具,deepL 在另一点上使它变得容易:他们为我们提供了一个“复制到剪贴板按钮”。识别之后,只需单击一下就可以将翻译后的文本放入剪贴板(注意剪贴板库已经(安装&)导入到第 3 行。
注意:在文本输入和点击复制按钮之间有一个等待时间:
time.sleep(3)。这是由于互联网连接和通过网络的请求。除了翻译过程之外,无论文本有多长,在您的浏览器中收到翻译文本之前都有一个最短的等待时间。
这里有一个句子翻译器。现在,如何从单个句子到一个完整的语料库?第一种解决方案是循环整个过程。但是考虑到3s-translation需要等待,对于更长的语料库来说,它会非常慢。想象一下,一个有 50 张幻灯片的 PowerPoint,包含一个标题和几个文本框,需要几个小时。
但是再一次,DeepL 让我们变得容易了。它允许我们一次翻译不同的段落。因此,我们可以同时发送多个句子,从句子中建立一个语料库。为了实现这一点,我们通过将句子连接成一个长字符串来创建一个更长的文本。为了确保算法不会在我们的句子之间添加任何上下文,我们使用一个连接字符串进行连接。翻译后,连接符字符串被用作拆分符字符串,这样我们就可以逐句得到翻译。
从不同的句子创建单个字符串语料库
太棒了。所以,如果我发送我的全部语料库,我将在几秒钟内得到它的翻译?
还没有。使用 DeepL,对翻译的字符数有限制。让我们发现这一点。

来自 DeepL 网站的截图
注意:您可以看到“翻译文档”选项。如果你点击它,你就可以上传一个 PowerPoint 文件。不幸的是,文件大小是有限的,并且有一个到高级选项的重定向,这不是免费的。
事实上,如右下角所示,翻译的限制是 5000 个字符。如果有人试图多放一些,页面就会变红,翻译只针对文本第 5000 个字母之前的句子。
这不是一个真正的问题,只要它是一个非常合理的限制,我们可以创建调整后的批量句子。想法是这样的:给定一个巨大的语料库,我们将创建一批句子,这样每一批大约(略少于)5000 个字符。
对于每一批,我们将运行前面的函数。如果我们有一个 200 个句子的语料库,平均 90 个字符(15 个单词,5 个字母/单词+1 个空白/单词),总共 18 000 个单词,它将可能创建 4 批:35000 + 13000。计算并不完全正确,因为由于 joiner 而有额外的字符,但你得到了想法。
调整语料库的大小
注意:随着文本大小的增加,翻译时间也会增加。它不是线性增加的,但是为了翻译 5000 个字符,在将翻译的文本提取到剪贴板之前,等待 20 秒而不是 3 秒是合理的。时间也取决于翻译语言。例如,如果你想从英语翻译成法语或西班牙语,它很快。翻译成中文要长得多。我做了一些测试,在那种情况下等待的时间更多在 45s 左右。关于翻译的准确性,我相信 DeepL,但我无法验证!
太好了!现在一切都好吗?我能把我最喜欢的书从葡萄牙语翻译成荷兰语吗?
再次,有一些新的挑战。运行前面的脚本可以很好地处理少量文本。事实上,selenium 的send_keys功能模拟了一个人在键盘上一个接一个地按键。它做得更快,但他做到了。这意味着 deepL 一个接一个地接收近 5000 个字符。网站检测到这是一个机器人行为。然后反机器人插件将翻译过程替换为一种不稳定的行为:改变 DeepL 网站的页面,打开弹出窗口,或者试图在你的计算机上下载翻译。非常糟糕。
我没有做很多测试来了解速度限制是什么,因为对于大型语料库来说,打字慢会花很多时间。
另一个非常直观的方法是更好地模仿人类与网页互动的方式。确实如此。如果让你翻译十句话,你会把这十句话从头开始写到文本框里吗?或者你会从你的原始文件中使用一个基本的复制/粘贴?就是它:复制/粘贴现在是我们最好的盟友。
使用 Selenium 的复制/粘贴功能
警告 : selenium 复制/粘贴的方式似乎取决于你使用的操作系统和导航器。我来解释一下:当你模拟使用你的键盘时,在 MacOS 中你会实现 COMMAND + V,而在 Windows 上,你会实现 CONTROL + V。
在 MacOS + Chrome 上,我发现它可以很好地与:
element.send_keys(Keys.SHIFTT, Keys.INSERT)
好了,我们快好了。这很好,但是还有另一个问题需要解决:当我们添加这么多文本时,翻译框变得更大:比你的屏幕还要大。
起初,这似乎不会改变什么,但是我们使用的“复制到剪贴板按钮”位于页面的底部。而现在已经出屏了。那么我们仍然可以在 Html 语法中检测到它,但是 selenium 不能模拟对它的点击。
下一步:滚动页面。Selenium 有一个内置的方法来做到这一点。
使用 Selenium 进行滚动,以便能够点击选定的按钮
我们到了!我们的翻译器现在可以工作了,可以翻译我们需要的所有文本了。免费准确的翻译。太好了!
一个好的实践是将 selenium scrappers 实现为类。基于我们在这里看到的,你可以在这里找到和完整的seleniumDeepL类,这使得翻译更加容易。注意,它从同一个 GitHub repo 中的可用的类继承了一些基本的 selenium 属性。
三句话语料库中的翻译示例
*corpus_fr = [
"Bonjour j’aimerais traduire tous ces documents",
"Mais je ne suis pas sur d’avoir un niveau d’anglais suffisant",
"Et surtout ! Je suis Data Scientist, pas traducteur."
]*

3 句话翻译的外壳日志
seleniumDeepL对象提供了那篇文章中没有讨论的有趣的附加特性,例如:
- 选择翻译的目标语言。我们可以使用 DeepL 中所有可用的语言。截至今日,已有:英语(*
en)、法语(fr)、西班牙语(sp)、德语(de)、荷兰语(nl)、葡萄牙语(pt)、波兰语(pl【T24)、汉语(zh和日语(ja【T30)DeepL 会自动检测源语言,要选择目标语言,您需要将www.deepl.com/en/translatorURL 中的en更改为所需的语言。* - 通过只翻译语料库或当前批次中尚未看到的句子,加快处理速度。
- 从/向 json 文件导入和导出翻译{句子 1:翻译 1,句子 2:翻译 2,…}
总而言之,我们已经看到了如何:
- 以字符串列表的形式加载数据,使用连接字符串将它们以 5k 字母为一批连接起来,并在一个较小的语料库中转换这些批次,以加快翻译速度。
- 通过将它们添加到剪贴板来翻译每个批处理,并从剪贴板粘贴到 DeepL 翻译框中。我们等待几秒钟的翻译,找到,滚动并点击“复制到我的剪贴板按钮”。最后一步是使用 joiner 字符串将翻译后的数据分割成原始大小。
PowerPoint 探索
这一部分更短更简单:给定一个 PowerPoint 文件,我们希望提取它的所有文本,将其提供给我们的翻译人员,并用他们的翻译替换所有文本。
提取文本
我们现在想要获取演示文稿中的所有文本。接下来阅读所有这些需要有条不紊地浏览文件。

PowerPoint 屏幕截图—全文(1/3)
每个演示文稿都以相同的方式构建:
- 演示文稿包含幻灯片
- 幻灯片包含形状
- 形状可能包含文本
python-pptx 有对应的内置对象。这迅速导致了这个功能:
用 python-pptx 从 PowerPoint 中提取文本
我们现在有演示文稿的所有文本吗?全部吗?真的吗?我们去看看。
为了确定我们正在收集的文本,我不是只收集文本,而是用空字符串替换它们,然后以空版本保存演示文稿。打开它,我们将快速检查是否所有的文本都已被选中(和删除),并推断我们的算法是否运行良好。
让我们分两步来做:
- 将
texts.append(paragraph.text)替换为paragraph.text = ‘’ - 在我们的
get_texts_from_file函数的末尾使用prs.save(output_file)
这是上一张图片的演示结果:

PowerPoint 屏幕截图—空文本(2/3)
这看起来几乎不错,但你可能会在第三张幻灯片上看到,我们还有一个文本。
事实上,在代码中,我们检查了所有将has_text_frame属性设置为True的形状。
但是所有的形状并不完全具有相同的类型。具体来说,我们要处理两种特殊形状:**
- 组合形状 : 您可能知道,在 PowerPoint 中编辑包含大量信息的文件时,您可能会选择几个形状并将它们组合在一起。如果你仔细观察第一张截图,你会发现在的右边部分有两个矩形(一个用于图片,一个用于文本),而在的左边部分只有一个。这是因为 Selenium 徽标和 Selenium 文本组合在一起。在 python-pptx 中,这些元素被称为 GroupShapes ,事实上,GroupShapes 没有
text_frames属性,因此has_text_frame属性默认设置为False。这就是为什么我们的算法没有隐藏该文本。希望 GroupShapes 有一个Shapes属性,它包含组合形状的单独形状。 - 表格:上例中没有表格,但问题是一样的。有些形状,除了
has_text_frame,形状还有一个has_table属性。而当has_table is True我们可以访问表对象。表格没有文本值,但是Table包含cell,单元格有text_frame对象。问题解决了。
当这两种类型的形状都考虑在内时,结果要令人满意得多。

PowerPoint 屏幕截图—空文本(3/3)
注意:还有一些其他形状,但它们主要是图形,并不意味着文本,所以我们不需要为它们费心。但是如果你必须处理 PowerPoint 文件,你肯定应该仔细看看写得很好的 python-pptx 文档;) !
现在我们确信我们已经阅读了演示文稿的所有文本,让我们进入下一步。
翻译
我们有一个句子列表,它看起来像一个为我们的翻译格式化的语料库。这个把它转化成批量语料库,在 deepL 网站上做一些迭代,返回一个字典。
我们可以再次遍历我们的 PowerPoint 并使用翻译词典替换文本。关键词是原文,价值是译文。
插入文本
这是最后一步:重新插入文本。这和我们以前做的没什么不同。我们必须检查文件的每一个text_frame,而不是设置空值,我们将设置翻译。
注意:为了简化代码,我们在 l.15 上迭代通过
all_presentation_text_frames。这是为了说明的目的:处理表格和分组形状的真实过程并不那么简单。ppt 操作的完整代码可从这里获得。
最后一次更新是必要的。这里我们替换了text_frame.paragraph.text值。事实上,它封装了比文本更多的信息。要理解这一点,您必须知道每个形状都有自己的参数(颜色、大小、位置、格式和字体)。这些是给定形状和 PowerPoint 模板的默认值。但有时我们可以手动更改这些信息。例如,如果文本太长,人们可能会决定将默认大小点从 16 改为 14,以便它正确地适应形状。在这种特定情况下,信息存储在text_frame.paragraph.text.runs中。这样我们就不能轻易地覆盖文本。
不幸的是,这是一个关键点:我们必须保持与输入文件完全相同的结构!
当然,有一个字体属性给出了关于字体的信息。但是这个不能存放和更换。因此,我们必须在幻灯片/形状/文本 _ 框架/段落/运行分析中更深入地修改文本,而不修改字体,并最终获得我们期望的结果。
我想说,尽管这是项目的终极挑战,但也是最难克服的。它需要理解runs的概念,并仔细阅读 python-pptx 文档。因为它变得非常技术性,我把这个函数放在这里,以防你对它感兴趣,但是如果你不明白它是如何工作的,不要担心。
替换 text_frame 文本而不影响字体属性
所以我们来了!我们漫长旅程的终点!还有最后一件事要做:从头开始运行所有这些!
放在一起
我们已经到了最后一步:使用我们构建的翻译器进行 PowerPoint 转换。我不会在这上面花太多时间,因为它只是我们到目前为止所看到的所有内容的组合,还做了一些重构。
翻译 PowerPoint 文件
结论
我们已经看到了如何创建一个很好的工具来自动翻译 PowerPoint 文件,而不需要修改它们的外观,并且非常自信。尽管如此,请记住没有什么是完美的。因此,它不会阻止重新讲授输出,以检测最终的错误。事实上,DeepL 是一个非常强大的工具,但有时如果我们没有给它足够的上下文信息,它可能会产生近似的翻译。
该项目的一个关键成果是翻译部分,毫无疑问,它非常有用,对于所有涉及翻译的用例都是可重用的。
这也让我发现了 python-pptx 库,这是一个有趣而伟大的 PowerPoint 版本工具。
下一步,我们可以想象构建一个基本的 web 界面,包括上传区、目标语言下拉列表和翻译下载按钮。像烧瓶或破折号这样的框架可以完成这项工作。
最后,我希望你喜欢阅读那个故事/教程,就像我喜欢这个项目一样。意识到这一点比花几天时间翻译幻灯片有趣得多。请注意,所有代码都可以在我的 Github 上的这个库中找到。如果你想使用它,请随意,不要犹豫让一个明星参与项目。当然,欢迎任何备注或建议:)!
我要感谢我的同事和朋友 Ismail Mebsout ,他刚刚发表了一堆关于深度学习基础的文章,并激励我发表了这第一篇帖子!
使用语义 ML 构建语言驱动的应用程序

使用语义 ML 构建语言驱动的应用
作者戴尔·马科维茨 — 7 分钟阅读
大多数人更擅长用语言描述世界,而不是用代码描述世界(嗯……大多数人)。那么,如果机器学习可以帮助弥合这两者之间的差距,那就太好了。

用神经网络预测能源需求
本文总结了我们用来赢得由新加坡南洋理工大学和 ai4impact 联合举办的深度学习数据大会的方法。如有任何疑问,请通过 LinkedIn 联系我们。

德里克·汤姆森在 Unsplash 上拍摄的照片
用数据理解幸福动态(第一部分)
通过 Lina Faik — 12 分钟阅读
在这些特殊时期,封锁给我们许多人留下了很多思考的时间。想想过去和未来。想想我们的生活方式和我们的成就。但最重要的是,想想我们对这个世界的贡献。
用通用句子编码分析不同音乐类型的歌词
应用谷歌的通用句子编码器和主成分分析来识别不同音乐流派的异同

在 Unsplash 上 Annie Theby 拍摄的照片
TLDR:使用谷歌的通用句子编码器/ PCA 和 Plotly 在 3d 空间中表示歌词。点击这里玩:【https://plotly.com/~kitsamho/238/
我对音乐的持久热爱始于 1992 年,在我 12 岁的圣诞节,溺爱我的父母掏钱给我买了一台 Alba HiFi。
皮带传动转盘?检查!
双卡带播放器?检查!
带低音增强的图形均衡器?当然啦!
从那以后,我一直在听各种各样的音乐。
我现在快 40 岁了,在这 28 年的音乐生涯中,我加深了与音乐的关系。我是一名吉他手,一名 DJ,一名舞曲制作人。音乐以多种快乐的方式打断了我的生活——我想对你们许多人来说也是如此。
因此,尽管我觉得我(相当)有资格谈论音乐流派,但我发现很难定义它们和/或区分它们。
流派不断演变,流派是主观的

划分流派的界限是模糊的,随着时间的推移,这种界限更加模糊。滚石乐队和甲壳虫乐队最初被一些人指责为魔鬼的作品,但如今却像平板家具一样成为主流。
*** 杀戮者 在位至尊为恶魔的作品仅供参考。\m/
流派被操纵

当商业利益盖过真实性时(几乎总是令人难过),就可以制造“类型”。这些人为的标签被唱片公司用来锁定客户群以获得商业优势,有时以某种方式反映出社会中更深层次的缺陷。
那么我们有什么方法可以衡量流派呢?

威廉·沃比在 Unsplash 上拍摄的照片
很明显,流派分类可能是主观的、有争议的,甚至可能是徒劳的——毕竟,好音乐就是好音乐,对吗?
然而,我想说至少有两种有效的方法来区分不同的流派:一首歌的音乐性和歌词。
这音乐听起来像什么?

由 Blaz Erzetic 在 Unsplash 上拍摄的照片
是不是…
- 直播还是测序?(摇滚 vs. Techno?)
- 大声还是安静?(Dubstep vs. Chillout?)
- 不协和还是有旋律?(痛击金属 vs 灵魂?)
- 可预见还是不可预见?(流行与爵士?)
音乐在说什么,它是如何被表达的?

照片由维达尔·诺德里-马西森在 Unsplash 上拍摄
- 歌词是否基于某种情绪(如悲伤、快乐、愤怒)?
- 歌词是在说话题(爱情、失去、战争、不公、压迫、死亡)吗?
对于这个项目,我想特别关注歌曲作者在歌曲中使用的歌词和语言。
我们都觉得抒情的内容因流派而异。但是有没有一种方法可以定量地测量这个呢?
项目目标
不同流派的歌词之间存在差异,我们可以量化和衡量这一点!
挑战和方法

我们从哪里获得数据?
- 我们将从Musixmatch.com搜集歌词,这是一个免费网站,拥有大量准确的歌词内容
什么样的方法/度量将允许我们以直观的方式量化抒情多样性?
- 我们将使用来自谷歌通用句子编码器的高维向量来表示歌词
- 我们将应用主成分分析(PCA) 将这些高维表示降低到 n=1,2,3 维
我们如何以一种有意义的方式将它可视化?
- 我们将使用 Plotly 并建立一个简单的情节和更复杂的 2d 和 3d 散点图的组合,以查看歌词的相似之处和不同之处
我的 GitHub 这里 有所有笔记本和全部原始数据
获取数据

来源:https://www.musixmatch.com/
为了准备这个项目,我发现很难在网上找到准确的歌词来源。歌曲要么不完整,要么可用的音量不够全面。
Musixmatch.com 似乎是少数几个遗址之一:
- 有很多内容。
- 相当准确。
所以让我们把它作为我们的数据源。
Musixmatch 有一个开发者 API,我们可以用它来获取歌词数据,但他们只允许你一天打 2k 个电话,而且你只能玩每首歌 30%的歌词。

资料来源:Giphy
让我们通过结合使用请求和 HTML 解析来解决这个问题。这是一个“浏览器外”的过程,因此我们可以轻松实现多线程来加快进程。这意味着我们可以在更短的时间内获得更多的内容。
我在下面写了一个 Python 类,它包含了为艺术家提取歌词内容的所有方法。
Musixmatch.com 的 Python scraper 类
运行铲运机
你所需要做的就是用一个 Musixmatch 艺术家 URL 和一个流派标签来实例化这个类…
scraper = MusixmatchScraper('[https://www.musixmatch.com/artist/Jack-Johnson'](https://www.musixmatch.com/artist/Jack-Johnson'),'folk')
..然后调用self.Run()方法启动刮刀。
告诫 —如果你想刮很多歌词,在可能的情况下,你应该轮换你的 IP 地址。如果你向 Musixmatch.com 发出太多请求,你的 IP 地址将会被屏蔽,尽管只是暂时的。我发现在这种情况发生之前,你可以删除 c.250 歌曲。
刮多个艺术家
MusixmatchScraper()的编码方式允许你抓取一个艺术家的内容。如果你想得到多个艺术家,你可以像这样创建一个循环:
抓取多位艺术家的歌词
该类返回一个熊猫数据帧,如下所示:

示例数据帧输出
太好了,现在我们有数据了。让我们研究一下数据,看看我们有什么。
探索性数据分析

在我们开始看句子嵌入之前,让我们用 Plotly 来激发一些简单的情节。
熊猫& Plotly

资料来源:www.plotly.com
比起其他任何东西,我通常更喜欢视觉化的情节。它们看起来更好,具有交互性,你可以使用 Plotly Chart Studio 轻松管理和分享它们。唯一的缺点是它需要相当多的代码(和许多嵌套的字典!)从 Plotly 中获得有用的东西,当你只想快速浏览数据时,这很烦人。

资料来源:Giphy
我推荐的一件事是将 Plotly 更新到至少 4.8 ,因为熊猫现在可以使用 Plotly 作为后端绘图的库,而不是“审美受损”的 Matplotlib。
在下面的代码块中——我们可以通过将.plot.bar()添加到 DataFrame 对象中,从 Pandas DataFrame 中生成一个简单的条形图。
简单如 123,abc!
pd.DataFrame([1,2,3],['a','b','c']).plot.bar()

Plotly 后端输出示例
如果你想的话,你可以调整你的图形的外观和感觉,通过使用你通常用在 Plotly Graph Objects 上的相同参数,这样就有了额外的熟悉的灵活性。
好了,回到这个项目...
我们有哪些流派和艺术家?
我决定从一系列流派中挑选歌词,有些流派比其他流派更极端,以便看看我们是否能看到差异,但我也选择了非常重叠的流派,看看是否有共性。
让我们看看每个流派有多少首歌曲。
#plot genres
fig_genre = pd.DataFrame(df.genre.value_counts()).plot.bar(template='ggplot2')#title parameters
title_param = dict(text='<b>Count of Genre</b><br></b>',
font=dict(size=20))#update layout
fig_genre.update_layout(title=title_param,
width=1000,
height=500,
xaxis = dict(title='Genre'),
yaxis = dict(title='Count'))#change the colour
fig_genre.update_traces(marker_color='rgb(148, 103, 189)')#show plot
fig_genre.show()
每一种类型都有相当好的表现——至少足以进行有意义的比较。

这里的原剧情:https://chart-studio.plotly.com/~kitsamho/240/#/
按流派划分的艺术家呢?
让我们带着一些视觉化的东西进城,创建一个流派和艺术家的旭日图。如果想和剧情互动,原剧情的链接在脚注里。

这里互动剧情:【https://plotly.com/~kitsamho/254/
我有一系列的乐队和艺术家,我觉得他们代表了这些流派。你可能不同意我的观点。没关系,流派的定义可能会有争议。
歌词长度有区别吗?
让我们按体裁来探讨歌词长度。
#get lyric frequencies for each song
df['lyric_count'] = df['lyrics'].map(lambda x: len(x.split()))

此处原创剧情:【https://chart-studio.plotly.com/~kitsamho/244】T4
Hip-hop 是一个非常抒情的流派——许多描述 hip-hop 歌词数量的关键描述性指标都不同于所有其他流派。
当我们分析那些歌词的内容时,看看那些歌词的内容是否也不同,这将是很有趣的。这很方便地把我带到了 EDA 的下一部分——词性(PoS)分析。
词性分析

所以下一件有趣的事情是理解这些歌词的内容。为了做到这一点,我将使用空间模型将歌词内容分类到其相关的词类中,即分类歌词中的哪些词是名词、动词、形容词等等。这将使我们对不同体裁的抒情内容的差异有更多的了解,并可能暗示相似和不同之处。
import spacy#load spacy model
nlp = spacy.load('en_core_web_sm')def pos(string,pos):
"""Returns any token that qualifies as a specific part of speech"""
doc = nlp(string) #fit model
return ' '.join(list(set([i.text for i in doc if i.pos_ == pos]))) #return any tokens that qualify
让我们在一个循环中调用上面的函数,并获得包含所有有用词类的新列。
#get nouns
pos_of_interest = ['NOUNS','VERBS','ADJ','ADV]#loop through each PoS
for pos in pos_of_interest:
df[pos] = df.lyrics.map(lambda x: pos(x,pos))

PoS 的示例数据帧输出
最常见的单词
下面是一个计算字符串中最常见标记的函数。我们可以用它来分析一首歌中最常见的词,包括我们刚刚制作的词表。
计算字符串中最常见单词的函数
#call the function on one gram
musixmatch_mostcommon(df.lyrics,token=1).head(10)

示例数据帧输出
更有用的是找到一种方法,在所有的体裁和不同的词类中使用这种功能。一种有效的方法是使用树形图可视化。Plotly 对此有一个很好的模板,所以我构建了一个函数来完成这项工作。
树形图可视化
在 Plotly 中创建树形图可视化的函数
让我们调用该函数并查看输出
noun_treemap = musixmatch_genremap(df,pos='nouns')
警告——其中一些歌词很露骨

这里互动剧情:【https://plotly.com/~kitsamho/246/
如果你使用的是台式机,我强烈建议你访问树状图脚注中的链接(如上),以便与之互动。
通用句子编码
现在我们开始真正有趣的部分——使用向量来代表我们的歌词。然后,我们可以使用各种技术来显示哪些向量/歌词是相似的或不同的。
什么是向量表示?

单词的向量表示对于自然语言处理来说是一个重大的进步。试图“理解”语言的早期方法通常涉及计数单词(例如单词袋模型或加权模型/ TFIDF 方法)。这些方法的局限性在于它们没能抓住语言的语境。毕竟,‘孩子弄坏滑板’这句话和‘滑板弄坏孩子’的意思很不一样……尽管有相同的类型和字数。

来源:Giphy——孩子玩滑板

来源:Giphy——滑板断了孩子
Tomas Mikolov 等人的 Word2vec 是游戏规则的改变者,因为单词不再基本上表示为计数,而是表示为高维向量。这些向量,或者说嵌入,是通过在文本语料库上训练一个简单的神经网络而产生的。这些单词嵌入表示一个单词与相邻单词的关系,因此也表示其上下文。因此,单词“walking”和“walked”的向量将比单词“swimming”和“swam”更相似。

如果你想了解这方面的更多信息,我建议你阅读原始白皮书或我与 ASOS 评论在 TrustPilot 上做的一个项目。
谷歌的通用句子编码器

来源:https://tfhub.dev/google/universal-sentence-encoder/ 4
在这个项目中,我们将使用谷歌的通用句子编码器,这是一种经过数百万(可能数十亿)人训练的最先进的编码器?)的文档。
它就像 word2vec,但使用了类固醇。
通用语句编码器将文本编码成高维向量,这些向量可用于文本分类、语义相似性、聚类和其他自然语言任务。
该模型针对大于单词长度的文本进行训练和优化,例如句子、短语或短段落。它在各种数据源和各种任务上接受训练,目的是动态适应各种各样的自然语言理解任务。
来源:【https://tfhub.dev/google/universal-sentence-encoder/】T44
如果你想了解更多关于这个模型的信息,我强烈建议你阅读关于 TensorFlow hub 的文档,因为它涵盖了更多的细节。
在我们的歌词数据上使用谷歌通用句子编码器

https://tfhub.dev/google/universal-sentence-encoder/4
让我们使用 Tensorflow Hub 加载模型..
#get universal sentence encoder
USE = hub.load("[https://tfhub.dev/google/universal-sentence-encoder/4](https://tfhub.dev/google/universal-sentence-encoder/4)")
让我们写一个简短的函数,它可以使用这个模型获得任何字符串输入的高维向量。
def getUSEEmbed(_string,USE = USE):
"""Function takes a string argument and returns its high dimensional vector from USE"""
return np.array(USE([_string])[0])
让我们得到一个简单句子的向量…
example = getUSEEmbed('Hello how are you?')

高维向量表示“你好吗?”
如你所见,一个简单的句子现在已经被编码成一个形状为(512,)的高维向量
评估向量之间的相似性和差异

弗兰克·麦肯纳在 Unsplash 上拍摄的照片
这个项目的基石是想办法找到歌词的异同。当我们将歌词编码成向量表示时,我们需要使用一些技术来让我们找到向量的相似性和差异性
我们可以通过以下方式做到这一点:
- 使用余弦相似度
- 维度缩减和可视化(使用类似主成分分析的东西)
余弦相似性
通常,当我们拥有连续的低维数据时,我们会使用类似欧几里德距离的东西来衡量相似性/差异。我们将把这个距离度量用于类似 KNN 分类器的东西,或者甚至在聚类维数减少的向量时,例如在 PCA 之后。
然而,因为我们来自句子编码器的向量具有非常高的维度,那么使用余弦相似度是一个比欧几里德距离更好的接近度度量,至少开始是这样。

余弦相似度将相似度计算为两个向量的归一化点积/来源:【https://en.wikipedia.org/wiki/Cosine_similarity
余弦相似度越接近 1,意味着两个向量之间的差异最小(输入字符串相似),而余弦相似度越接近 0,意味着两个向量非常不同(输入字符串不同)
让我给你看一个有四个句子的例子。
其中两个是关于天气的..
- “今天天气会很暖和”
- “今天将是一年中阳光最充足的一天”

另外两个是关于早餐
- “我喜欢我的鸡蛋单面煎”
- “早餐是我最喜欢的一餐”

我特别选择了一些例子,在这些例子中,单词“sunny”/“sunniest”被用作隐喻和形容词。让我们看看编码是否能捕捉到这种差异。
#some example sentences
example_sentences = ["The weather is going to be really warm today",
"Today is going to be the sunniest day of the year",
"I like my eggs sunny side up",
"Breakfast is my favourite meal"]#get embeddings for each example sentence
embed = [getUSEEmbed(i) for i in example_sentences]#set up a dictionary where each sentece is a key and its value is its 512 vector embedding
dic_ = dict(zip(example_sentences,embed))#let's find all the unique pairwise sentence combinations
combo = [list(i) for i in itertools.combinations(example_sentences, 2)]
现在我们需要一个函数来计算相似度。
def cosineSimilarity(vec_x,vec_y):
"""Function returns pairwise cosine similarity of two vector arguments"""
return cosine_similarity([vec_x],[vec_y])[0][0]
让我们制作一个数据框架,映射每个独特句子对的余弦相似度。
#empty list for data
cs = []#lop through each unique sentence pairing
for i in range(len(combo)):
#get cosine similarity for each
cs_ = cosineSimilarity(dic_[combo[i][0]],dic_[combo[i][1]])
#append data to list
cs.append((combo[i][0],combo[i][1],cs_))#construct DataFrame
cs_df = pd.DataFrame(cs,columns['sent_1','sent_2','cosine_similarity']).\
sort_values(by='cosine_similarity',ascending=False)
让我们检查数据帧。

比较四个句子的余弦相似度
这太酷了。最相似的句子有更多的相似向量(余弦相似度 0.41 / 0.51)。即使使用一个作为隐喻和形容词的词有歧义,编码也是足够不同的。让我们看看是否可以将这一点应用到我们的歌词中。
获取每首歌曲的嵌入

由塞萨尔·马丁内斯·阿吉拉尔在 Unsplash 拍摄的照片
首先,我们需要得到每首歌的向量。
#get USE embeddings for each song
df_embed = pd.DataFrame([getUSEEmbed(df.lyrics[i]) for i in range(df.shape[0])],\
index=[df.song[i] for i in range(df.shape[0])])
用df_embed.head(5)检查数据帧

每首歌曲的向量—高维度/列
让我们使用另一种数据简化技术— 主成分分析,来减少这些嵌入的维度,同时保留尽可能多的方差。
我构建了一个全面的函数来完成这项工作。
PCA 函数将维数减少到 n=1,2,3 个分量
让我们调用这个函数并获得三个新的数据帧。
pc_1 = musixmatch_PCA(df,df_embed,n_components=1)
pc_2 = musixmatch_PCA(df,df_embed,n_components=2)
pc_3 = musixmatch_PCA(df,df_embed,n_components=3)

示例数据帧输出
我们来做一些分组,探讨一下哪些艺人的歌词彼此最相似。
正如我们所看到的——即使只有一个组成部分,不同流派的艺术家也倾向于聚集在一起。死亡金属、重金属和摇滚彼此略有不同,但与流行音乐和灵魂乐有很大不同。这是有道理的。
如果你熟悉这些乐队,当你向下滚动时,你会对流派如何演变有强烈的感觉,我们可以在主成分 1 的变化中看到这一点。

此处原创剧情:https://chart-studio.plotly.com/~kitsamho/248
让我们看看同样的分析,但是是按流派分组的。

此处原创剧情:https://chart-studio.plotly.com/~kitsamho/248
这个平均视图证实了我们上面的怀疑。
让我们做一个更复杂的可视化,使用散点图来显示这些数据。
我们可以在二维空间(PC1、PC2)中观察,也可以在三维空间(PC1、PC2、PC3)中观察
下面的函数将上面的一些函数与一些更复杂的 Plotly 函数结合起来,生成二维(PC1 / PC2)或三维(PC1 / PC2 / PC3)散点图
让我们调用函数,看看情节是什么样的。同样,我强烈建议你点击页脚处的剧情链接,如果你想和所有类型互动,以及它们在剧情中的位置。
scatPlot_2 = musixmatch_scatplot(df,df_embed,n_components=2)

互动剧情点击这里:https://plotly.com/~kitsamho/236/
我们也可以创建一个类似的图,但是利用 PCA 的 3 个维度。
scatPlot_3 = musixmatch_scatplot(df,df_embed,n_components=3)

互动剧情点击这里:https://plotly.com/~kitsamho/238/
结论

我们已经表明,不同音乐流派的歌词确实有所不同,尽管有趣的是,当我们以逐步的方式从一种流派转移到另一种流派时,歌词内容之间有着非常明显的关系。我认为下面的形象化很清楚地将这一点带到了生活中。

不同流派的歌词并不完全相互排斥——除非你比较两个极端。也就是说,死亡金属和灵魂乐之间有很大的区别,但区别流行音乐和流行摇滚的方式却很少。
这些发现反映了音乐的真实本质,因为音乐类型是不固定的,对于每一种类型,都可能有一种“邻近的”类型在抒情内容上是相似的。
毕竟很多 r&b/soul 的粉丝也会喜欢 pop…..但也许不是朋克和/或金属?
应用程序

照片由JESHOOTS.COM在 Unsplash 上拍摄
我们已经表明,通用句子编码/迁移学习是一种非常强大和非常简单的技术,可以用来找出不同流派歌词的差异和相似之处。
它在其他问题上的应用有很多:
- 歌曲/歌词推荐系统 —使用相似性度量来查找具有相似歌词的歌曲
- 通过查找相似的文档(如电子邮件、客户反馈/评论)对大量文本进行分段
- 识别抄袭
- 用作分类任务的特征
感谢您的阅读,如果您想进一步交流,请联系我们!我把笔记本保存在 GitHub 上,所有代码都准备好了,可以在其他项目中使用。如果有,请告诉我,我很想看。
萨姆(男子名)
参考和链接
- [1] GitHub(含 Jupyter 笔记本):https://GitHub . com/kitsamho/song lyrics _ univeral _ sentence _ encoder
- 【2】美汤:https://www.crummy.com/software/BeautifulSoup/bs4/doc/
- [3]谷歌通用句子编码器:https://tfhub.dev/google/universal-sentence-encoder/4
- [4]阴谋地:https://plot.ly/python/
- [5]musix match . com:【https://www.musixmatch.com/
使用无服务器和 SAM 部署预定的 Lambda
实践教程
使用 AWS SAM 和无服务器构建和部署计划的 AWS Lambda 以及外部平台相关包

图片来源:来自 unsplash 的古伊列梅·加西亚
Lambda 构建和部署
有几种方法可以部署 Lambda 函数,最常见的方法是在 IDE 中编写代码,测试,保存,然后部署。您可能还需要它在某个时间运行,同样,更多的点和 Cloudwatch 事件的点击。这不符合最佳实践、易于管理如撤销变更、版本控制和代码可重用性。
基础设施作为代码
然而,由于许多开源工具的可用性,管理和供应云资源已经变得容易得多,这些工具允许您通过使用人类可读的配置文件(如 YAML)来定义和配置您的资源。其中, AWS 无服务器应用程序模型(SAM) 和无服务器,是将 Lambda 部署为依赖项和 IaC 的最佳框架。
问题陈述:暂停笔记本资源

Cron 调度的 Lambda 的设计视图
与许多数据科学家一起工作,我们经常面临的问题之一是笔记本电脑在下班后不使用时仍在运行。在本文中,我将演示如何使用 SAM 和 Serverless 部署一个带有外部依赖的 cron 调度的 Lambda,以挂起 SageMaker 笔记本。
我们将部署的 Python 代码如下:
注意:任何带有 always_on: no 标签的笔记本都将被暂停
和需求文件:

requirements.txt 内容
我们用熊猫做什么……没什么,我只是想展示一个引入外部依赖的例子。
设置 AWS CLI
确保您的计算机上安装了 AWS CLI,并且您已经使用aws configure设置了环境。
最好练习 使用临时凭证,或 CI/CD 环境进行部署。
AWS 无服务器应用程序模型(SAM)

图片来源: AWS
SAM 概述
AWS SAM 是一个开源框架,允许您将 Lambda 等无服务器应用程序部署到 AWS。它支持配置管理、本地测试和调试,以及与 DevOps 流程的集成。SAM 使用最佳实践方法将配置文件转换为 Cloudformation 脚本。
部署具有外部依赖性的 SAM 调度 Lambda
5 个简单步骤:

1。安装 SAM CLI
按照此链接安装 SAM CLI,我们将使用它将 SAM 包部署到 AWS 上。SAM 需要 Docker 和 amazon 映像来进行本地测试和构建部署,因为 Lambda 是基于 Linux 的环境。
> docker pull amazon/aws-sam-cli-build-image-python3.8
2。创建您的 SAM 项目
运行以下命令创建您的项目:
> sam init --runtime python3.8 *--app-template hello-world --name suspend_sm_notebook*
您应该看到创建了以下文件夹和文件:

注意:文件夹 hello_world 重命名为 source,并添加了 requirements.txt
将 Python 代码复制到handler.py中,然后就可以构建 YAML 文件来部署 Lambda 和基于时间的 Cloudwatch 事件了。
3。构建 SAM 模板
完整模板
使用 SAM,您可以部署各种无服务器模型,但是因为我们想要部署 Lambda,所以资源类型将是Function。还增加了一项政策,允许其与 SageMaker 笔记本电脑进行交互。SAM Event将通过 Cron 表达式经由Schedule调用 Lambda 函数。
4。建造你的萨姆
一旦您完成了测试,您就可以构建您的部署,该部署会将您的代码与来自需求的任何依赖项打包在一起。由于pandas的性质,它必须在类似 lambda 的容器中编译。
**>** sam build --use-container --manifest source/requirements.txt
5。部署您的 SAM
最后,您可以部署到您的 AWS 环境中。SAM 将使用在您的设备上设置的 AWS 配置,只需确保它具有所需的访问权限和现有的 S3 存储桶。
**>** sam deploy --stack-name SuspendSMNotebookSAM --s3-bucket sam-lambda-deploy-apse2-jy --region ap-southeast-2 --capabilities CAPABILITY_IAM
无服务器应用框架

图片来源:无服务器
无服务器概述
Serverless 是一个开源框架,它允许您将无服务器应用程序部署到云中。与 SAM 不同,它还允许您部署到其他云,如 GCP 和 Azure,并有一个免费的托管仪表板来提供完整的应用程序生命周期管理视图。
部署具有外部依赖性的 SAM 调度 Lambda
4 个简单步骤:

1。安装无服务器 CLI
按照此链接进行无服务器设置。对于那些已经安装了节点的用户,可以使用 npm 进行安装。无服务器允许您安装可用于部署的插件。在这种情况下,python-requirements 插件用于允许无服务器将 python 与需求中的依赖项打包在一起。
**>** npm install -g serverless
**>** docker pull lambci/lambda:build-python3.8
**>** serverless plugin install -n serverless-python-requirements
2。创建您的无服务器项目
运行以下命令创建您的项目:
**>** serverless create --template aws-python3 --path suspend_sm_notebook_serverless
您应该会看到以下创建的文件:

注意:添加一个 requirements.txt
3。构建无服务器模板
完整模板
作为一个多云框架,必须将目标提供商定义为 AWS 和 ap-southeast-2 region。添加 Lambda 的策略和角色是为了授予 SageMaker 访问权限,以及授予 cron 调度调用schedule访问权限。
4。部署您的无服务器
这一步很简单,只需运行下面的命令,它会在后台将您的无服务器模板转换为 Cloudformation,在需求中打包依赖项,并为您的部署创建一个 S3 存储桶。
> serverless deploy
回滚和删除部署(可选)
无服务器允许您回滚到以前的部署:
> serverless deploy list
> serverless rollback --timestamp <timestamp>
或者清理并从 AWS 中删除我们部署的堆栈。
> serverless remove
那么…我应该用哪一个呢?

图片来源:作者
正如您所看到的,AWS SAM 和 Serverless 都实现了允许您使用外部平台相关包将 cron 调度的 Lambda 部署到 AWS 的目标。这些框架在配置格式和简单性方面本质上也是相似的。然而,它们也有各自的优点和缺点:

无服务器和 SAM 的比较
在选择 Serverless 还是 SAM 时,一些因素应该基于您想要实现的目标、您的团队能力和专业知识以及强大的社区(开源)。
作为一个建议,如果您在一个多云环境中工作,无服务器可能是一个更好的选择,但是如果本地测试和评估更重要,那么 SAM 可能更适合您的团队。
关于我
我喜欢写媒体文章,并为知识共享社区做贡献。除了尝试新的食物配方,我还帮助企业构建云和数据解决方案。请随时与我联系并向我问好!
— 李帝努·雅玛
使用跳过连接来增强去噪自动编码器算法
在 Flickr 的一个 RGB 图像样本上,比较了具有跨越瓶颈的残差网络的自动编码器和没有残差网络的自动编码器的去噪性能。
自动编码器:
官方的 Keras 博客,称自动编码器为“自我监督算法的一个例子,因为它们的目标是从输入数据生成。因此,它们被用于图像重建的任务。
自动编码器的主要部件有: 编码器、瓶颈和解码器 。编码器在每一步提取图像特征,并在此过程中压缩输入数据。瓶颈将输入限制在其最低维度,即输入数据的压缩表示。解码器出现在这个瓶颈之后,用于重构输入数据。损失测量用于比较生成或重建的数据与输入数据的接近程度。**
图像去噪:

来源: GIPHY
数据去噪图像是自动编码器的一个常见应用。图像中的噪声可以理解为图像颜色或亮度的随机变化,降低图像质量。在图像数据的各种使用情况下,去除这种噪声通常是预处理步骤。卷积自动编码器可用于此目的。编码器学习提取将它们与图像中的噪声分开的特征。从而压缩图像。来自瓶颈的最终压缩表示被传递到解码器。解码器最终解压缩图像,使噪声最小化。
跳过从编码器到解码器的连接
我们知道深度神经网络遭受退化问题 。由于自动编码器具有多个卷积和去卷积层,因此当重建图像时,由于这种信息丢失,它们的性能也会受到影响。由跳跃连接组成的剩余网络是解决这个问题的已知方案。因此,为了提高自动编码器的性能,可以从编码器到解码器添加这样的“跳过连接”,即跨越瓶颈 的 。这些附加连接可以直接将特征映射从编码器的较早层发送到解码器的较晚层。这有助于解码器形成输入图像的更清晰定义的解压缩。****

跨越瓶颈的剩余网络,来源:作者
为了了解这些残差网络的优势,让我们来看看卷积自动编码器模型不同阶段的激活输出。首先,我们训练了一个没有跨越瓶颈的跳跃连接的自动编码器模型。然后,我们添加它们,并在相同的图像样本上再次训练模型。
数据集
10K RGB 图像的样本取自 Kaggle 上 Flickr 图像的数据集。所有的分析都是使用 Google Colab 的 GPU 完成的。
模型
我们使用 Keras 的功能 API 来构建自动编码器模型。由于计算限制,输入图像被调整到较小的尺寸(128,128,3)。图像像素也以 1/255 的因子标准化。此外,通过应用高斯噪声矩阵,图像被故意破坏。这些损坏的图像形成了自动编码器的输入,而原始图像在训练模型时被用作目标。构建卷积网络时,重要的是要记住,随着深入,通道或滤波器的数量会增加,而输入的大小(高度和宽度)会减小。
****#Input**
input_img = Input(shape=(128, 128, 3))**#Encoder**
y = Conv2D(32, (3, 3), padding='same',strides =(2,2))(input_img)
y = LeakyReLU()(y)
y = Conv2D(64, (3, 3), padding='same',strides =(2,2))(y)
y = LeakyReLU()(y)
y1 = Conv2D(128, (3, 3), padding='same',strides =(2,2))(y) **# skip-1**
y = LeakyReLU()(y1)
y = Conv2D(256, (3, 3), padding='same',strides =(2,2))(y)
y = LeakyReLU()(y)
y2 = Conv2D(256, (3, 3), padding='same',strides =(2,2))(y)**# skip-2**
y = LeakyReLU()(y2)
y = Conv2D(512, (3, 3), padding='same',strides =(2,2))(y)
y = LeakyReLU()(y)
y = Conv2D(1024, (3, 3), padding='same',strides =(2,2))(y)
y = LeakyReLU()(y)**#Flattening for the bottleneck**
vol = y.shape
x = Flatten()(y)
latent = Dense(128, activation='relu')(x)**
下面是解码器的代码。转置卷积或反卷积层用于构建该解码器。去卷积层的工作方式大致类似于卷积层和上采样层的组合。最初,我们在没有第一个和第二个跳过连接的情况下训练模型。然后,使用 Keras 中 layers API 的 Add()将这些连接从编码器的早期层添加到解码器的后期层。lrelu_bn()辅助函数用于将激活应用于这些添加,并将结果传递给批处理规范化层。
****# Helper function to apply activation and batch normalization to the # output added with output of residual connection from the encoder**def lrelu_bn(inputs):
lrelu = LeakyReLU()(inputs)
bn = BatchNormalization()(lrelu)
return bn**#Decoder**
y = Dense(np.prod(vol[1:]), activation='relu')(latent)
y = Reshape((vol[1], vol[2], vol[3]))(y)
y = Conv2DTranspose(1024, (3,3), padding='same')(y)
y = LeakyReLU()(y)
y = Conv2DTranspose(512, (3,3), padding='same',strides=(2,2))(y)
y = LeakyReLU()(y)
y = Conv2DTranspose(256, (3,3), padding='same',strides=(2,2))(y)
y= Add()([y2, y]) **# second skip connection added here**
y = lrelu_bn(y)
y = Conv2DTranspose(256, (3,3), padding='same',strides=(2,2))(y)
y = LeakyReLU()(y)
y = Conv2DTranspose(128, (3,3), padding='same',strides=(2,2))(y)
y= Add()([y1, y]) **# first skip connection added here**
y = lrelu_bn(y)
y = Conv2DTranspose(64, (3,3), padding='same',strides=(2,2))(y)
y = LeakyReLU()(y)
y = Conv2DTranspose(32, (3,3), padding='same',strides=(2,2))(y)
y = LeakyReLU()(y)
y = Conv2DTranspose(3, (3,3), activation='sigmoid', padding='same',strides=(2,2))(y)**
因为输入图像是标准化的,所以它们的结果像素值在 0 和 1 之间。为了获得可比较的重建图像,在最终层中使用了“sigmoid”激活。在训练集之外,1k 个图像被用作验证集。使用二元交叉熵作为损失函数。最后,两个模型都被训练了 200 个时期,最小批量为 32。学习率为 0.001 的 Adam 优化器在收敛时给出最小的训练和验证损失。
让我们来看看测试图像模型各层的激活输出。这将有助于我们清楚地看到编码器-解码器的作用!
激活输出是使用 keract 包创建的。
测试图像:
我们拍摄一张哥伦比亚大学校园的测试图片。测试图像根据模型调整了大小,并添加了随机噪声。“有噪声的”图像被用作模型的输入。
****
********
原始调整大小的图像(左),添加了随机噪声的图像用作模型的输入。(右)****
编码器的激活输出

第一卷积层的激活输出
由于两种型号的编码器相同,因此该部件的激活输出也相同。在检查前三个卷积层的激活输出时,可以看到大部分图像信息被保留。这也可能意味着图像中的噪声也保留在模型的这个阶段。然而,当我们深入模型时(参见第三个卷积层之后的层的激活输出),保留的信息相当抽象。该模型开始提取更高层次的特征,例如边界、拐角和角度。

第二和第三卷积层的激活输出



“摘要”第四、第五和第六卷积层的激活输出

第七卷积层的激活输出和输入图像的压缩表示
解码器的激活输出(没有跨越瓶颈的跳跃连接的模型)
有趣的是,解码器的激活输出与上述编码器的序列完全相反。



第一、第二和第三去卷积层的激活输出
在第三个去卷积层之后,我们仍然看不到图像的任何边缘被再次形成。



第四、第五和第六解卷积层的激活输出
如下图所示,在解码器的最后一层,没有明确定义图像的解压缩方式!
********
第七反卷积层和最后一层的激活输出
解码器的激活输出(添加了跳过瓶颈连接的模型)
正如在编码器的激活输出中所看到的,其早期层保留了噪声,但是后期层提取了图像的更高表示。因此,从编码器的第三和第五卷积层到解码器的第三和第五去卷积层进行跳跃连接。该解码器的第一、第二和第三解卷积层的激活输出与先前版本的解码器相同。然而,在第三层之后,我们看到激活输出与先前解码器的不同。下面我们可以清楚地看到,在第六个反卷积层的激活输出中,图像再次形成。



第四、第五和第六解卷积层的激活输出
该解码器的最后一层给出了测试输入图像的清晰定义的解压缩版本。
********
第七反卷积层和最后一层的激活输出
结果的比较
第一个模型的去噪图像输出:

具有跨越瓶颈的跳跃连接的第二模型的去噪图像输出:

我们可以清楚地看到,跨越瓶颈连接的 autoencoder 模型性能更好!
前方的路…
除了对图像去噪之外,这种方法可以用于自动编码器的任何图像重建应用。虽然跳过连接提高了自动编码器的性能,但是可以试验这些连接的位置和数量。该模型也可以用不同级别的噪声因子来训练,以便更好地概括结果。完整的代码可以访问这里!
参考资料:
J.董,人。毛,沈振华。杨,【2017】利用卷积自动编码器和对称跳跃连接学习深度表示
使用通气管进行多标签标注。
如何使用浮潜的多类实现来创建多标签

hazy research @Snorkel.org的浮潜
sculpt 是一个很好的小软件包,它允许我们使用标签函数(LFs ),简单如启发式或关键字,复杂如算法和人工注释器,以便创建一个标签数据集,用于训练分类器。
“通气管是一个系统,用于以编程方式建立和管理训练数据集,而无需手动标记。在 snuck 中,用户可以在几小时或几天内开发大型训练数据集,而不是在几周或几个月内手工标记它们。”—Snorkel.org
动机
使用通气管来创建多标签的动机很简单。通气管允许我们使用简单的试探法或关键字来创建一个监督数据集。当我们需要理解为什么一个样本被分配到一个特定的类别时,使用它作为一个标记算法允许一定程度的清晰度。你也可以想到这样一个场景,你创建了一个启发式的多标签算法,分配稀疏数量的多标签;当您的产品或功能需要大量的多标签,以便为您的客户提供更高的价值。
通气管
值得注意的是,在整个过程中,我们实际上是在创建两个分类器,第一个使用的是 sauce 的 MajorityLabelVoter 或 LabelModel,前者为我们提供了一个多数票作为基线,后者为我们提供了 secret-sauce 模型,后者是为了训练一个机器或深度学习(MLDL)算法,因为我们不想依赖我们创建的标签函数。我们希望训练一个不局限于我们的关键字、正则表达式等的分类器。我们想要一个比我们给它的更一般化的分类器。理想情况下,它会发现,例如,我们在标记过程中没有考虑到的标记与我们的最终标记之间的相关性。
使用多级通气管
首先,我们需要了解如何使用通气管。考虑一个情感分类任务和下面的句子:1。“蛋糕尝起来真难吃”,2。“奶油真好”& 3。“这食物是公平的”。这些句子分别是否定句、肯定句和中性句。因此,我们将创建几个相应地分配标签的逻辑框架。
*from snorkel.labeling import labeling_function
@labeling_function()
def lf_keyword_good(x):
return POSITIVE if "good" in x.text.lower() else ABSTAIN
@labeling_function()
def lf_keyword_bad(x):
return NEGATIVE if "bad" in x.text.lower() else ABSTAIN@labeling_function()
def lf_keyword_fair(x):
return NEUTRAL if "fair" in x.text.lower() else ABSTAIN*
剩下的过程很简单,一旦你有了很多 LFs,你就可以把它们用在你的熊猫身上。DataFrame* 训练其中一个模型,即 MajorityLabelVoter 或 LabelModel。*
*from snorkel.labeling import LabelModel, PandasLFApplier
# Define the set of labeling functions (LFs)
lfs = [lf_keyword_bad, lf_keyword_good, lf_keyword_fair]
# Apply the LFs to the unlabeled training data
applier = PandasLFApplier(lfs)
L_train = applier.apply(df_train)
# Train the label model and compute the training labels
label_model = LabelModel(cardinality=3, verbose=True)
label_model.fit(L_train, n_epochs=500, log_freq=50)
df_train["label"] = label_model.predict(L=L_train, tie_break_policy="abstain")*
df_train["label"] 也将包含弃权标签,因此为了进一步训练我们的二级分类器,我们必须将它们过滤掉。
*df_train = df_train[df_train.label != ABSTAIN]*
同样,在过滤的数据集上训练二级分类器(在本例中为随机森林)的目的是“推广到标注函数和T0 的覆盖范围之外
***from** **sklearn.ensemble** **import** RandomForestClassifier
clf = RandomForestClassifier()
clf.fit(df_train.drop(['label'],axis=1), df_train["label"])*
将通气管用于多标签
通气管只能作为多类贴标机开箱使用。要将其用于多标签,您可以执行以下三种方法之一:
- 使用 MajorityLabelVoter 的 predict_proba()并分配所有“概率”≥ 0 的类。即第一个和最后一个[0.5,0,0.5]。我们可以把它想象成一个样本,它存在于两个聚类中,或者来自两个类别的两个关键词,这允许一个样本具有多个标签。例如,“汉堡包有好有坏”。
- 使用 LabelModel 的 predict_proba,分配所有概率高于“拐点”的类。可以用 Kneed 算出来。本质上,我们的概率是 softmax,只有少数人会得到高值。请注意,根据我的实证测试,MajorityLabelVoter 的概率值与 LabelModel 的概率值之间有很高的相关性,即前者是一个“硬”softmax,而后者是你对 softmax 的预期。即[0.5,0,0.5]对[0.45,0.06,0.49]。
- 使用 MajorityLabelVoter 或 LabelModel 训练“一对所有”模型,并根据预测分配多标签。请注意,在考虑放弃标签时,您需要考虑一个策略。

最高概率的图示。
实践中的多标签
因为“1”和“2”给出了相似的输出,并且从“1”获得多标签的计算更简单,所以我选择基于 1 分配多标签。
请注意,当一个模型分配一个弃权标签时,它不能决定谁是赢家。我们在 predict_proba()的输出中观察到了这种行为。比如考虑下面这个概率向量:[1.0,0,0],这里的赢家是第一类。现在,考虑下面的向量:[0.5,0,0.5],我们看到这不是一个明确的赢家,因此,浮潜将分配一个弃权标签。
当我们为每个具有非零概率的类分配一个标签时,我们实际上消除了所有的放弃标签,标记了所有的数据集,并为每个样本分配了许多多标签。
以下代码使用了一个 MajorityLabelVoter 分类器,并根据所有分数高于零的类分配标签。就这么简单:)。
*from snorkel.labeling import MajorityLabelVoter
from sklearn.preprocessing import MultiLabelBinarizerY = [['POSITIVE', 'NEGATIVE', 'NEUTRAL']]# fit a MultiLabelBinarizer
mlb = MultiLabelBinarizer()
mlb.fit_transform(Y)# create a majority vote model and predict
majority_model = MajorityLabelVoter(cardinality=3)
predictions = majority_model.predict_proba(L=L_test)df_multilabel = pd.DataFrame()
df_multilabel['predict_proba'] = predictions.tolist()# get all the non zero indices which are the multi labels
df_multilabel['multi_labels'] = df_multilabel['predict_proba'].apply(lambda x: np.nonzero(x)[0])
#transform to mlb for classification report
df_multilabel['mlb_pred'] = df_multilabel['multi_labels'].apply(lambda x: mlb.transform([x])[0])print(df_multilabel.head())#convert to str in order to see how many multi labels did we gain
multi_label_string = df_multilabel.multi_labels.apply(lambda x: ", ".join(le.inverse_transform(x)))
print(multi_label_string.value_counts()[:50])# print some metrics using classification report
y_pred = df_multilabel.mlb_pred.apply(lambda x: list(x)).to_numpy().tolist()
y_true = mlb.transform(Y.values).tolist()print(classification_report(y_true, y_pred, target_names = mlb.classes_))*
就这样,您已经为每个样本创建了多个标签。请记住,由于这种使用浮潜的方法论,在标记策略方面非常贪婪,您可能会得到一个非常面向回忆的模型。你的经历会有所不同。
Ori Cohen 博士拥有计算机科学博士学位,主要研究机器学习。他是 TLV 新遗迹公司的首席数据科学家,从事 AIOps 领域的机器和深度学习研究。
用 SQL 玩‘你有过吗?’
高级 SQL 技术第 2 部分:collect_list、group_concat、agg_array、string_agg 等等

学习使用高级 SQL 技术可以在两个方面帮助你。它使您能够更快地获得洞察力,如果您使用输出进行进一步分析,您可以以最有效的布局清理和格式化数据。如果您的数据已经到达一个可以直接在 python 中使用的格式良好的列表中,该怎么办?让我们回顾一下不同数据库中的一些技术,继续使用我在以前的文章中使用的恶劣天气细节数据。
虽然这很重要,但我不想只讨论代码的语法,而是想把重点放在业务用例上。学习如何找出哪种技术可以帮助你回答业务问题是至关重要的。为了让它变得有趣,我喜欢关注那些超出典型范围的问题。好吧, Sharknado 的文章有点出格。
这个问题
佛罗里达,你曾经…经历过导致生命损失的天气事件吗?
给我一份所有死亡事件的清单。
用 SQL 怎么回答这个问题?
答案取决于你的数据库。每个数据库可能需要稍微不同的 SQL。这也取决于你希望你的结果如何格式化。
需要考虑的事项:
请求者将如何使用该列表?如果他们要将列表传递给 python 程序,您需要提供不同的格式,以便他们将列表剪切并粘贴到 PowerPoint 演示文稿中。
你想要那个列表排序吗?您希望对列表进行重复数据删除吗?
数据
数据本质上是“垂直的”,由每个事件的时间戳列出。我们的目标是将这个事件数据展平成一列。

样本风暴数据—作者截图

样本风暴数据—作者截图
如果你想在家玩,这里有一个数据链接:
恶劣天气数据目录(SWDI)是美国恶劣天气记录的综合数据库。
www.ncdc.noaa.gov](https://www.ncdc.noaa.gov/ncei-severe-weather-data-inventory)
MySQL: GROUP_CONCAT()
MySQL Group_Concat 返回找到的元素的列表,以逗号分隔。它没有类似 python 的列表格式["c "," d "," e "," f"]。您可以使用一些额外的代码来创建这种格式,如下所示。

MySQL Group_Concat —作者截图
SELECT
STATE
, CASE WHEN DEATHS_DIRECT > 0 OR DEATHS_INDIRECT > 0
THEN 1 ELSE 0
END AS DEADLY_EVENT
, SUM(CASE WHEN DEATHS_DIRECT > 0 OR DEATHS_INDIRECT > 0
THEN 1 ELSE 0 END)
AS TOTAL_DEADLY_EVENTS
, COUNT(*) AS TOTAL_EVENT_COUNT
, GROUP_CONCAT(EVENT_TYPE) AS EVENT_LIST
, GROUP_CONCAT(DISTINCT EVENT_TYPE) AS UNIQUE_EVENT_LIST
, CONCAT('[' , GROUP_CONCAT( DISTINCT CONCAT( '\"', EVENT_TYPE ,'\"')), ']') AS UNIQUE_EVENT_LIST_BRACKETED
FROM severe_events_details
WHERE STATE = 'FLORIDA'
GROUP BY STATE, (CASE WHEN DEATHS_DIRECT > 0 OR DEATHS_INDIRECT > 0
THEN 1 ELSE 0 END)
ORDER BY COUNT(*) DESC
雪花:ARRAY_AGG()和 LISTAGG()
如果请求者想要一个重复数据删除的 python 列表,那么 ARRAY_AGG(DISTINCT)就是您所需要的。如果他们想要一个简单易读的列表,LISTAGG()就很好。

雪花数组 _Agg —作者截图

雪花数组 _Agg —作者截图
SELECT
STATE AS STATE
, CASE WHEN DEATHS_DIRECT > 0 OR DEATHS_INDIRECT > 0
THEN 1 ELSE 0 END AS DEADLY_EVENT
, SUM(CASE WHEN DEATHS_DIRECT > 0 OR DEATHS_INDIRECT > 0
THEN 1 ELSE 0 END) AS TOTAL_DEADLY_EVENTS
, COUNT(*) AS TOTAL_EVENT_COUNT
, ARRAY_AGG(EVENT_TYPE) AS EVENT_LIST
, ARRAY_AGG(DISTINCT EVENT_TYPE) AS UNIQUE_EVENT_LIST
, LISTAGG(DISTINCT EVENT_TYPE ,', ') AS UNIQUE_LIST_AGGFROM MEDIUM_DEMO.PUBLIC.WEATHER_EVENT_DETAILS
WHERE STATE = 'FLORIDA'
GROUP BY STATE, (CASE WHEN DEATHS_DIRECT > 0 OR DEATHS_INDIRECT > 0 THEN 1 ELSE 0 END)
ORDER BY COUNT(*) DESC
MS SQL-SERVER TSQL: STRING_AGG()
TSQL 有点混乱。创建完整列表和唯一列表需要分成两个不同的查询。请求时,Sting_Agg 不允许使用 Distinct。
你可以得到你需要的东西,但是要达到它需要更多的工作。

TSQL String_Agg —作者截图
SELECTSTATE AS STATE, CASE WHEN DEATHS_DIRECT > 0 OR DEATHS_INDIRECT > 0
THEN 1 ELSE 0 END AS DEADLY_EVENT, SUM(CASE WHEN DEATHS_DIRECT > 0 OR DEATHS_INDIRECT > 0
THEN 1 ELSE 0 END) AS TOTAL_DEADLY_EVENTS, COUNT(*) AS TOTAL_EVENT_COUNT, STRING_AGG(CAST(EVENT_TYPE AS NVARCHAR(MAX)) , ',') AS EVENT_LISTFROM demo.dbo.[StormEvents_details-ftp_v1.0_d2020_c20200922]WHERE STATE = 'FLORIDA'GROUP BY STATE, (CASE WHEN DEATHS_DIRECT > 0 OR DEATHS_INDIRECT > 0 THEN 1 ELSE 0 END)ORDER BY COUNT(*) DESC

TSQL String_Agg —作者截图
SELECTSTATE AS STATE
, CASE WHEN DEATHS_DIRECT > 0 OR DEATHS_INDIRECT > 0
THEN 1 ELSE 0 END AS DEADLY_EVENT, SUM(CASE WHEN DEATHS_DIRECT > 0 OR DEATHS_INDIRECT > 0
THEN 1 ELSE 0 END) AS TOTAL_DEADLY_EVENTS
, COUNT(*) AS TOTAL_EVENT_COUNT, STRING_AGG(CAST(EVENT_TYPE AS NVARCHAR(MAX)) , ',') AS UNIQUE_EVENT_LIST, CONCAT('[' , (STRING_AGG(CONCAT('"', CAST(EVENT_TYPE AS NVARCHAR(MAX)) , '"'), ',')) , ']') AS UNIQUE_EVENT_LIST_BRACKETEDFROM(SELECT DISTINCT STATE, EVENT_TYPE, DEATHS_DIRECT,DEATHS_INDIRECTFROM demo.dbo.[StormEvents_details-ftp_v1.0_d2020_c20200922]) XWHERE STATE = 'FLORIDA'GROUP BY STATE, (CASE WHEN DEATHS_DIRECT > 0 OR DEATHS_INDIRECT > 0 THEN 1 ELSE 0 END)ORDER BY COUNT(*) DESC
Hive 和 Spark SQL: COLLECT_SET 和 COLLECT_LIST
Collect_List 返回所有值。Collect_Set 返回唯一值。
COLLECT_LIST(EVENT_TYPE)返回[Rip 电流,Rip 电流,闪电,Rip 电流]
COLLECT_SET(EVENT_TYPE)返回[Rip 电流,闪电]
结论
根据您的需要和您的数据库,使用 SQL 有许多方法来回答一个业务问题。当您经常在平台之间切换时,所有的命令开始融合在一起。这就是为什么如果有人告诉我他们编码时很少使用谷歌,我会立刻不信任他们。除非他们连续几年只使用一种语言,否则我会把他们叫出来。
用状态机解决复杂编码面试问题。

图片来自 unsplash.com@ othentikisra
这篇博客是我的“大型科技公司黑客技术面试 15 天备忘单”的一部分。在这篇博客中,我们将使用状态机来解决两个编码面试问题。我希望这些例子能帮助你理解状态机解决问题的方法,以及它在解决包含许多复杂情况的问题时是多么的强大。
背景:
有限状态机(FSM)是一种用于设计算法的抽象。它通过转换将有限数量的状态映射到其他状态。状态机在任何给定时刻只能处于一种状态。
使用 FSM 处理编码面试问题的步骤:
- 识别所有类型的输入数据。
- 识别 FSM 的所有可能状态。
- 识别给定输入状态之间的有效转换。
- 画出有限状态机图。
- 按照 FSM 图实现算法。
面试问题示例:
问题 1: 实现将字符串转换为整数的
*atoi*。 完整描述:https://leetcode.com/problems/string-to-integer-atoi/举例:*" -42"*=>*-42*=>*4193*``*"words and 987"*
在这个问题中,我们需要处理三种类型的输入数据:空白字符(空格)、符号字符(+或-)和数字字符(0->9)。我们从初始状态 1 开始。对于空白字符,状态保持在 1,对于符号字符,我们移动到状态 2,对于数字字符,我们移动到状态 3。继续确定状态 2 和 3 中的有效转换,我们有下图。

有了这个图表,我们可以得出下面的算法:
问题 2 :验证给定的字符串是否可以解释为十进制数。 完整描述:【https://leetcode.com/problems/valid-number/】举例:
*"0"*=>*true*``*" 0.1 "*=>*true*``*"abc"*=>*false*``*false*``*" 6e-1"*=>*true*
*" 99e2.5 "*=>*false*``*"53.5e93"*=>*true*``*" --6 "*=>*false*
相关的输入类型有:空白字符(空格)、符号字符(+或-)、数字字符(0->9)、*e*和*.* 我们从初始状态 1 开始,扩展到不同的状态,以构建下面的 FSM 图。

如果输入以状态 3、5、8 或 9 结束,则为有效数字。其余的是中间状态,它们会给出错误的输出。
下面是我对 FSM 图的实现。
结论
使用状态机有助于避免代码中出现太多嵌套的 if-else 语句,并使代码更加简单。
使用统计距离进行机器学习的可观察性

图片作者。统计检查:输入、输出和实际值
介绍
统计距离用于量化两个分布之间的距离,在 ML 可观测性中非常有用。这篇博文将深入研究统计距离度量,以及如何使用它们来检测常见的机器学习模型故障模式。
为什么要进行统计距离检查?
机器学习中的数据问题可能有很多种,从突然的数据管道故障到特征输入的长期漂移。统计距离度量为团队提供了影响模型的数据变化的指示和故障排除的洞察力。在模型部署后的真实世界中,这些数据分布的变化可能以各种不同的方式出现,并导致模型性能问题。
这里是我们在实践中看到的一些真实世界的数据问题。
- 不正确的数据索引错误—破坏数据的上游映射
- 错误的文本处理—导致新令牌模型从未见过
- 错误处理案例
- 新文本字符串的问题
- 具有不同坐标或索引的不同要素源
- 第三方数据源做出更改,删除要素、更改格式或移动数据
- 新部署的代码改变了特征向量中的项目
- 定期的每日数据收集失败,导致丢失值或缺少文件
- 软件工程改变了一个领域的意义
- 第三方库功能变更
- 假设有效格式发生变化,但突然无效
- 日期字符串改变格式
- 系统会自然发展,功能也会改变
- 外部世界急剧变化(如新冠肺炎疫情),每个功能都在变化
- 数量的急剧增加扭曲了统计数据
这些是使用统计距离检查可以发现的数据问题的示例。
在哪里使用统计距离检查
统计距离可用于分析
- 模型输入:模型输入的变化,特别是关键的最重要的特征或可能是上游模型输出的特征。
- 模型输出:模型输出的变化
- 实际值:实际值的变化(地面接收到的真实值)。在某些情况下,预测后的短时间内可能无法获得真实情况。在这些情况下,团队经常使用代理指标/数据。
这些检查对于模型性能故障排除非常有洞察力,并且它们允许团队在主要的模型问题影响业务结果之前就发现这些问题。在下图中,可以对模型输入(要素)和模型输出(预测)进行统计检查。

图片作者。统计检查:输入、输出和实际值
如何使用统计距离检查
统计距离度量是在两个分布之间定义的——分布 A 和分布 b。其中一个分布通常被称为 参考分布 (我们将它称为分布 A)——这就是你要比较的。另一个分布通常是您正在与参考分布进行比较的系统的当前状态(我们称之为分布 B)。
什么是参考分布?
在 ML 可观测性的背景下,参考分布可以是许多不同的选项。首先要区分的是,参考分布可以是跨固定时间窗口的分布(分布不变)或移动时间窗口的分布(分布可能变化)。
在下图中,您可以看到固定参考分布的示例包括培训分布的快照、初始模型部署分布(或认为分布稳定的时间)以及验证/测试集分布。使用固定参考分布的统计距离的一个示例是将从训练环境(分布 A)得到的模型预测分布与从生产环境(分布 B)得到的模型预测分布进行比较。
参考分布也可以是移动窗口。在下图中,有两个参考分布是移动窗口的例子——上周和 A/B 测试用例。对于第一个例子,人们可能想要比较上周的预测和本周的预测的分布。在这种情况下,每周都会有一个新的参考分布。如果您正在生产中 A/B 测试两个不同的模型版本,您可以比较来自您的冠军模型的预测分布是否类似于来自挑战者模型的预测分布。
您设置为参考分布的选项将取决于您试图捕捉的内容,我们将深入探讨常见的统计距离检查以设置模型。

图片作者。参考分布
统计距离检查的常见用例
模型输入
模型输入可能突然漂移、逐渐漂移或周期性漂移,具体取决于参考分布的设置。俗话说,“垃圾进来,垃圾出去。”换句话说,模型的好坏取决于流入模型的数据。如果输入数据发生变化,并且与模型之前观察到的或训练的数据有很大不同,则可能表明模型性能可能有问题。
以下是对模型输入设置的一些统计距离检查:
- 培训中的特征分布vs生产中的特征分布
在生产过程中,特征的分布会随时间漂移。了解产品中的特征分布是否随着时间的推移而改变,以及这是否会影响模型,这一点非常重要。在该设置中,参考分布(分布 A)是训练中的特征分布。当前窗口(分布 B)可以设置为某个时间窗口(例如:一天、一周、一个月等)上的特征分布。如果特征分布变化很大,设置更长的回看窗口会很有帮助,这样统计距离检查的噪声会更小。

图片作者。功能(培训)与功能(生产)
2.生产时间窗 A 的特征分布 vs生产 时间窗 B 的特征分布
在生产中的两个不同时间间隔对要素设置分布检查也很有用。与培训和生产检查相比,这种分布检查可以关注更短期的分布变化。如果将训练分布设置为参考分布,如果有任何波动(例如:交通模式、季节变化等),设置较短的生产时间窗口可能会产生噪音。针对上周和本周设置统计距离检查可以指示特征值中的任何突然异常值或异常值。这些对于识别可能被更大的时间窗口掩盖的任何数据质量问题也非常有用。
识别特征中是否有分布变化可以给出模型性能退化的早期指示,或者如果该特征不影响模型性能,是否可以丢弃该特征。如果对模型性能有重大影响,则可能导致模型重新训练。虽然应该调查特性分布的变化,但这并不总是意味着会有相关的性能问题。如果该特征对模型不太重要,并且对模型预测没有太大影响,那么特征分布变化可能更多地表明它可以被丢弃。
现实世界中的团队使用模型输入检查来确定模型何时变得陈旧,何时重新训练模型,以及可能指示性能问题的特性片段。我与之交谈过的一个团队使用财务模型进行核保,通过将产品中的功能与培训中的功能进行比较来生成对功能稳定性的分析,以确保模型决策仍然有效。
模型输出
就像模型输入会随时间漂移一样,模型输出分布也会随时间变化。对模型预测设置统计距离检查可确保模型的输出与其参考分布不会有太大差异。
3.培训中的预测分布vs生产中的预测分布
输出漂移的目标是检测模型相对于训练的工作方式的巨大变化。虽然这些对于确保模型在先前测试和批准的范围内运行是极其重要的,但这并不保证存在性能问题。类似于特征分布变化不一定意味着存在性能问题,预测分布变化也不保证存在性能问题。一个常见的例子是,如果一个模型被部署到一个新的市场,在一些模型输入和模型输出中会有分布变化。

图片作者。距离检查的参考分布示例
4.生产时间窗 A 的预测分布对比生产 时间窗 B 的预测分布
与模型输入类似,预测分布也可以被监控到生产中的另一个时间窗口。我们与之交谈的一个团队正在评估一个垃圾邮件过滤器模型,他们使用模型输出的分布与一个固定的时间框架来显示可能通过模型的攻击模式的变化。这里的参考分布可以是移动时间窗口或固定时间帧。我们听到的一个常见的固定时间框架是使用初始模型启动窗口。
5.型号版本 A 的预测分布与型号版本 B 在同一时间窗口的预测分布
支持 canary 模型部署的团队可以对不同模型版本的预测分布设置统计距离检查。A/B 在生产中测试两个不同的模型,每个模型接收一定量的流量,或者根据历史数据回测一个模型,通过比较预测分布,可以深入了解一个模型相对于另一个模型的表现。
模型实际值
6.培训的实际分配与生产的实际分配
在做出模型推断后,实际数据可能不总是在短期范围内。但是,对实际分布的统计距离检查有助于确定从训练数据中学习的结构是否不再有效。这方面的一个主要例子是新冠肺炎·疫情,它导致交通、购物、需求等模式与疫情开始前的生产模式有很大不同。除了大规模的转移之外,了解特定群组的训练数据与生产之间的实际分布可以识别是否存在
模型预测与实际
7.产量的预测分布与产量的实际分布
这种统计距离检查是比较预测和实际的生产分布。这有助于通过精确定位与实际值差异最大的特定预测群组来发现性能问题。这些检查有时可以发现被平均数据掩盖的问题,如梅伊或 MAPE。
常见的统计距离度量
我们刚刚讨论了统计距离的常见用例。有许多不同的统计距离度量来量化分布之间的变化。不同类型的距离检查对于捕捉不同类型的问题很有价值。在这篇博文中,我们将介绍以下 4 种距离测量方法,以及每种方法最具洞察力的时间。
- 人口稳定指数
- Kullback Leibler 散度( KL 散度
- 詹森香农发散( JS 发散)
- 推土机距离(EMD)
- 科尔莫戈罗夫斯米尔诺夫试验 (KS 试验)
磅/平方英寸(pounds per square inch)
PSI 指标 在金融行业有很多现实应用。对于分布相当稳定的数值和分类特征来说,这是一个很好的度量。
等式:PSI =(Pa-Pb)ln(Pa/Pb)PSI 是一种理想的分布校验,用于检测分布中的变化,这些变化可能会使作为模型输入的要素的有效性降低。它经常在金融中用于监控模型的输入变量。它有一些众所周知的阈值和有用的属性。

图片作者。根据分布计算 PSI 的原始值
通过以上 PSI 值,我们可以看到:
- 分布的任何变化都将增加 PSI 总数,无论变化是正还是负。这意味着不管分布是增长还是收缩,任何变化都会增加 PSI。
- ln(Pa/Pb)项意味着,代表小百分比分布的仓中的大变化将比具有大百分比分布的仓中的大变化具有更大的影响(对 PSI)
- 与从 12%增加到 18%相比,分配箱从 4%增加到 9%对 PSI 的影响几乎是两倍
- 上面的示例分布包括许多小百分比变化(小于 6%),其中没有一个单独产生超过 0.1 的 PSI 项,这是模型调查的经验法则基准。关键是,相对于行业基准,微小的变化不会改变指针。
- 阈值的设置我们建议采用通用的金融行业基准,或者基于该特征/预测/实际的 PSI 的先前样本的天数/小时数
- 0.1-0.25 的金融行业基准通常可以捕捉仓位之间 10%左右的波动

图片作者。PSI 计算开关分布:对称
PSI 是对称的,也就是说,如果颠倒分布,PSI 值是相同的。在上面的示例中,我们将紫色图形与之前示例中的黄色图形进行了交换,值 0.98 与之前的反向分布相同。
下面的示例对一个重要要素使用了种群稳定性指数(PSI)。该检查定期运行,权衡您希望收到更改警报的速度与您试图检测的更改类型。
当检查低于一个明确定义的阈值时,就需要对变化进行调查,这可能表明存在模型性能问题。

图片作者。PSI —按日开启功能
下面显示了一个实时特征,其中稳定性指数远低于设置的 0.15 限制(0.1–0.25 金融行业标准范围)。在设置时,我们建议查看多天的统计窗口来设置检测范围。

图片作者。PSI(较大变化)-按日启用功能
上述每日 PSI 分布变化是引入新的分类特征时测量特征的真实变化。
KL 散度
如果一个分布相对于另一个或小样本量具有高方差,KL 散度统计是有用的。
等式:KLdiv = Ea[ln(Pa/Pb)]=(Pa)ln(Pa/Pb)
KL 散度是一个众所周知的度量,可以认为是样本分布和参考(先前)分布之间的相对熵。像 PSI 一样,KL 散度也有助于捕捉分布之间的变化。也和 PSI 类似,它有信息论的基础。
与 PSI 的一个重要区别是 KL 散度是不对称的。反向分布将具有不同的值-您将从 A -> B 然后 B -> A 获得不同的值。非对称指标对于分布监控来说并不理想,因为当您切换参考分布与比较分布时,您会获得不同的值。对于监控用户来说,这可能显得不直观。

图片作者。KL-散度示例计算
虽然 KL-Divergence 中的不对称使得它不适合于监视度量,但是它确实为 KL-Divergence 提供了一些其他有用的应用。例如,KL-散度不对称与贝叶斯定理中比较先验值和后验值时发现的不对称并没有什么不同。我们发现,当参考分布中有大量样本,而比较分布中有非常少的一组样本(导致更多的方差)时,这种不对称性特别有用。

图片作者。KL-散度:开关分布不对称
JS 分歧
方程:JS Div(P,Q) = KL-DIV(P,M) + KL-DIV(Q,M)
参考= M(混合分布)= (P + Q)
JS 散度有一些有用的属性。首先,它总是有限的,所以不存在被零除的问题。当一个分布在某些区域有值而另一个没有值时,就会出现被零除的问题。其次,与 KL-Divergence 不同,它是对称的。
JS 散度使用两种分布的混合作为参考。这种移动窗口检查的方法存在挑战;混合参考基于移动窗口分布的变化而变化。由于移动窗口在每个周期都在变化,所以混合参考也在变化,并且如果没有经过深思熟虑的处理,每个周期中的度量的绝对值不能直接与之前的周期进行比较。有一些变通方法,但对于移动窗口来说并不理想。

图片作者。JS 分歧问题(不稳定参考)
对于每个分配检查,移动窗口在每个期间都会发生变化。它代表当前期间分布的一个示例。JS 分布对于移动窗口有一个独特的问题,因为混合将随着您比较的每个时间窗口而变化。这会导致 JS Divergence 返回的值的含义定期发生变化,从而在不同的基础上比较不同的时间范围,这不是您想要的。
PSI 和 JS 都是对称的,并且具有用于度量监控的潜力。相对于 JS,我们建议对 PSI 进行一些调整,作为移动窗口的距离度量,用于警报。
推土机的距离
方程式:𝐸𝑀D0=0
𝐸𝑀𝐷𝑖+1=(𝐴𝑖+𝐸𝑀𝐷𝑖) − 𝐵𝑖
𝑇𝑜𝑡𝑎𝑙𝐷𝑖𝑠𝑡𝑎𝑛𝑐𝑒=∑|𝐸𝑀𝐷𝑖|
推土机的距离测量给定区域内两个概率分布之间的距离。推土机距离对于非重叠数值分布移动和高维空间(例如图像)的统计非常有用
作为分布检查的一部分,使用以上 PSI 和 KL 计算,将 Bin0 与 Bin0、Bin1 与 Bin1 等进行比较。Bin0 永远不会与 Bin1 进行比较,计算会修复 Bin 比较。
以下统计距离检查在计算中不包含锁定的条柱。箱号是不相关的;更重要的是分布之间的距离。
推土机的距离是一个相当古老的计算方法——它是在 1781 年制定的。在一维分布的情况下,它捕获在将一个分布移动到另一个分布时,分布的形状和到平均值的距离保留了多少。

图片作者。一维经验模态分解的可视化
推土机的距离可以简单地用一个一维的例子来说明,如上图所示(最初 EMD 算法被设计用来解决移动泥土的问题)。这里,推土机的距离可以被认为是将一堆泥土移动到另一堆泥土中所需的功。卡车沿着直路(X 轴)将泥土装入卡车,从而填满泥土。移动泥土所需的功由沿 X 轴的每个单位计算,以及卡车中有多少泥土,以及卡车可以运输多少单位的泥土。卡车将泥土倒入另一个分配区。分布的距离越远,推土机的距离就越大,因为卡车会将泥土从一个位置运输到另一个位置。分布越分散和重叠,数量就越小。
与 KL 散度相比,EMD 处理 KL/PSI 需要修改的自然非重叠分布。
使用统计距离和模型性能指标
统计距离检查对于模型的可观察性非常有用。有许多设置配置可以帮助识别不同的模型问题(漂移、数据分布变化、数据质量问题、模型性能退化等)。这些统计距离度量最好与模型性能度量一起分析。
使用分布变化的性能度量使团队能够识别可能降低模型整体性能的预测片段。在下面的示例中,整体模型精度为 71%。使用统计距离度量,我们可以看到在 2 个箱中有显著的移动。这些箱子的性能如何?这个运动在模型中引起任何全局性能问题了吗?在本例中,我们可以看到这些频段的性能低于整体精度。

图片作者。功能的性能分析
将统计距离检查与更广泛的模型故障排除工作流联系起来有助于识别不同的模型故障模式。我们的建议是:
- 对要素、预测和实际值设置统计距离检查
- 当统计距离度量超过合适的阈值时,确定这是否会影响模型性能
- 当查看模型性能时,与来自训练/验证集的性能进行比较
- 查看与更改相关的特定存储片的性能

图片作者。分布变化并不总是意味着性能问题
分布的变化可能会也可能不会引起大的下游问题。关键是,不应该在真空中看待任何变化,或者仅仅因为某些事情发生了变化就进行调查。应该根据其他系统性能指标过滤这些更改,以调查哪些是重要的。
结论
以下是我们将总结的最终想法/建议:
- 统计距离检查在跟踪模型的输入、输出和实际情况的变化方面非常有用,允许团队在业务影响之前发现问题。
- PSI 应用于监控顶级特性的特性漂移
- PSI 应用于监控概念漂移的预测输出和实际值
- 当一个分布的样本数小得多并且方差大时,应该使用 KL 散度
- JS 散度可用于特征漂移,但由于混合(参考)是变化的,你应该看绝对数字而不是每天
- 零值比较,当一个 bin 有值而另一个没有值时,需要以一种非常周到的特定方式来处理。需要对脱离分布的事件进行事先处理,以便脱离分布的小变动不会破坏 PSI calc。拉普拉斯平滑的标准方法,即给每个面元增加一个小值,效果并不好。请了解更多详情。
- 当发生重大分布变化时,我们建议查看绩效指标,并调查再培训是否是一种解决方案。
联系我们
如果这个博客引起了你的注意,并且你渴望了解更多关于机器学习可观察性和模型监控,请查看我们的其他博客和 ML 监控上的资源!如果您有兴趣加入一个有趣的 rockstar 工程团队,帮助模型成功生产,请随时联系我们,并在此处找到我们的空缺职位!
在 Pandas 中使用字符串方法
如何在 Python 中对 pandas 数据帧中的列应用字符串方法

通常在熊猫数据帧中,我们有包含字符串值的列。幸运的是,pandas 提供了一种将字符串方法应用于整个列的简单方法,这些列只是 pandas 系列对象。我们可以将这些 series 对象(或列)的值作为字符串访问,并通过使用 series 的 str 属性对它们应用字符串方法。让我们在这里看一些例子。
创建数据帧
我们将使用在这里发现的不明飞行物报告来创建我们的数据框架。然后,我们将使用 dataframe head 方法查看 dataframe 的前 5 行,并使用 dataframe info 方法检查我们的列值。

info 方法的输出告诉我们,列中的所有非空值很可能是字符串对象。在本教程中,我们将主要关注城市列。
将字符串方法应用于列
记住熊猫数据帧是由列组成的。这些列中的每一列都是熊猫系列对象。在 pandas 中,我们可以访问这些数据帧和系列对象的方法和属性。Pandas 为我们提供了一系列用于处理字符串的方法。这些字符串方法将应用于列中的每个条目。
为了将字符串方法应用于列,我们将使用 Series 对象的 str 属性。因此,一般来说,我们将使用以下格式:
Series.str.
假设我们想将 ufo 数据帧中的所有城市名称转换成大写。首先,我们需要访问该系列(或列),然后添加。str,最后加上我们要用的具体方法。要找到我们可用的所有字符串方法,请转到这里并找到字符串处理部分。
我们可以使用括号或点符号来访问系列或列,如下所示:
# bracket notation
ufo[‘City’] # dot notation
ufo.City

注意城市列的类型是一个序列对象。因此,我们可以对该对象应用任何级数方法。
因此,要将我们列中的所有城市名称转换为大写,我们可以添加。str 后跟 upper 方法,如下所示:
ufo.City.str.upper()

注意:这实际上并没有改变我们的原始数据帧。如果我们想改变我们的原始数据帧,我们可以用下面的代码将这个输出值赋给我们的原始城市列:
ufo.City = ufo.City.str.upper()

现在,我们原始 ufo 数据帧的城市列包含大写的城市名称。
如何在 Python 中使用 loc 和 iloc 方法
towardsdatascience.com](/how-to-index-data-in-pandas-with-python-4437c24ff332)
替换列中的字符
如果我们想用下划线替换 City 列中的所有空格,该怎么办?我们可以通过使用 replace 方法来实现这一点。我们只需指定要替换的内容,然后用什么来替换它。
ufo.City.str.replace(‘ ’, '_')

注意所有的空格都被替换成了下划线。
链接方法
我们也可以将字符串方法链接在一起。记住,这些系列方法返回一个熊猫系列对象。所以我们可以给它们添加另一个字符串方法。例如,假设我们想用下划线替换所有空格,并将所有城市名改为小写。我们可以通过下面的代码来实现这一点:
ufo.City.str.replace(‘ ‘, ‘_’).str.lower()

自从不明飞行物。City.str.replace(',' _ ')返回一个 Series 对象,我们可以只使用或链接另一个 Series 方法到它。
本教程使用的所有代码:
如果你喜欢阅读这样的故事,并想支持我成为一名作家,考虑注册成为一名媒体成员。每月 5 美元,你可以无限制地阅读媒体上的故事。如果你用我的 链接 注册,我会赚一小笔佣金。
阅读卢艾·马塔尔卡的每一个故事(以及媒体上成千上万的其他作家)。您的会员费直接支持…
lmatalka90.medium.com](https://lmatalka90.medium.com/membership)
结论
在本教程中,我们学习了如何访问 pandas 数据帧中的列并对其应用字符串方法。
使用 Stringr 和 Regex 从 R 中的文本和字母数字数据中提取特征
使用 Titanic 数据集的特征提取教程
大型数据集通常充斥着难以破译的数据。人们可能会想到文本数据、名称、唯一标识符和其他种类的代码。通常,分析这些数据集的人会很快丢弃这些变量。然而,有时这种类型的数据中可能有有价值的信息,这可能有助于您的分析。
很高兴地,R 提供了惊人的包“stringr”,它非常适合这些目的。本快速教程将向您展示如何从这些变量中提取微小但仍有洞察力的数据。在这种情况下,我们将从 Titanic 数据集提取这种数据。

美国国家海洋和大气管理局在 Unsplash 拍摄的照片
加载数据集
如果你想跟随教程,使用下面的命令加载 Titanic 数据集。泰坦尼克号数据集以泰坦尼克号乘客的各种信息为特色,以练习预测他们的生存。本教程旨在研究一些大多数人在开始分析时会立即丢弃的变量(如姓名和票证)。然而,我们希望与他们合作,看看我们是否能提取一些有用的信息。如果您想知道如何使用 Stringr 和 regex,请跳过这一部分。
### install package
install.packages("titanic")### load package
library(titanic)### load dataset
df = titanic_train #load dataset
安装和加载字符串
在开始之前,我们需要安装和加载 Stringr 包。
### install package
install.packages("stringr")### load package
library(stringr)
现在,我们都准备好了,可以开始了。
从名称中提取标题
作为第一个练习,我想从名字中提取标题。这是因为我相信,比如说,贵族比凡人更有可能生存下来。还有,我假设年轻的未婚女性(“小姐”)更有可能活下来。可能,标题中可能有很多信息。
titanic 数据集中的名称(df$Name)都是这样保存的:
Futrelle,夫人雅克希思(莉莉可能皮)
为了避免任何错误,我们首先要运行一个命令,将所有这些字符串转换成小写,并保存为新变量:
df$lcName = str_to_lower( df$Name )
标题(上例中的 Mrs)前面有一个空格,前面有一个点。我们可以使用 regex(正则表达式)作为一种语言,将这种模式传递给 Stringr,并要求它以如下方式查找标题:
(?<=\s)[[:alpha:]]+(?=\.)
This pattern consists of three parts:
- (?< =\s) 告诉 Stringr 我们要寻找的文本段在(?由空格(\s)分隔。
- [[:alpha:]]+ 告诉 Stringr 我们要寻找的文本由一个或多个(+)字母([[:alpha:]])组成。
- (?=\.)告诉 Stringr 我们要找的那段文本已经开始(?=)加一个点(\。).
可以看到 regex 使用了各种符号来交流模式。当你想开发自己的模式时, Stringr 备忘单是一个有用的指南。这个网站提供了一个测试正则表达式模式的简单方法。
我们提取标题,并通过让 Stringr 在小写的“Name”字符串中查找这个模式,将其保存为一个新变量。
df$title = str_extract( df$lcName, "(?<=\\s)[[:alpha:]]+(?=\\.)" )
现在让我们使用 Ggplot2 和下面的命令来绘制我们的新变量“title”。
### install package (ggplot2 is a must have)
install.packages("ggplot2")### load package
library(ggplot2)### plot titles
ggplot(data.frame(table(df$title)),aes(x=reorder(Var1, -Freq),y=Freq))+
geom_col()+
labs(title="Count of Title", x="Title",y="Count")

您可以看到,Stringr 很好地从名称中提取了所有的标题,现在我们有了一个新的分类变量,可以用作我们分析的输入。
从名称中提取族
作为第二个练习,我想从 names 字符串中提取姓氏。我相信家庭成员可能是生存的一个预测因素,也就是说,如果一个家庭成员存活,在控制了性别等其他因素后,其他成员也更有可能存活。
在上面的示例“name”字符串中,您可以看到这个人的姓,例如 Futrelle,是第一个单词。所以我们可以告诉 Stringr 获取第一个匹配模式([[:alpha:]]+)的单词(str_extract)并获取第一个单词,这就是 family。
但是,数据集中有姓氏,如“范德·普兰克”或“奥德怀尔”如果我们在这种情况下取第一个单词,我们只会得到“vander”或“o”,而不是整个家族的名字。因此,我们需要修改模式以包含任何类型的字符(。)长度为 1 个字符以上,后跟一个逗号(?=\,).
df$family=str_extract(df$lcName,".+(?=,)")
这一次,因为我们有很多姓,所以让我们画出 10 个最常用的姓。
t=data.frame(table(df$family))t=t[order(-t$Freq),]ggplot(head(t,10),aes(x=reorder(Var1,-Freq),y=Freq))+
geom_col()+
labs(title="Count of Family Name", x="Title",y="Family Name")

同样,我们现在有了新的变量“family name”,我们可以使用它进行进一步的分析。
从票中提取信息
在大多数对泰坦尼克号数据集的分析中,人们很快就丢弃了船票信息。然而,机票代码中的一些字母可能意味着什么。例如,它们可以表示票是最后一分钟购买的,并且乘客没有为航行做好充分准备。
但是,这些缩写和代码的写法不一致(例如,“SOC”和“S.o.C .”),这就是为什么这些数据需要一些准备工作。让我们打扫它。
### set ticket codes to lowercase
df$lcTicket=str_to_lower(df$Ticket)### remove punctuation from ticket codes
df$lcTicket=str_replace_all(df$lcTicket,"[:punct:]","")
现在我们已经清理了一些数据,让我们提取这些缩写。通常,机票代码看起来像这样:
索托诺 2 3101287
在我看来,“sotono 2”是一个首字母缩写词,可能意味着什么,“3101287”是一些机票号码(我稍后会详细介绍)。如果我们考虑缩写词的模式,它是字符(。)后面跟一个空格(\s),空格后面跟一个三位数或更长的数字([[:digit]])({ 3,})。有时,整个机票代码是一些首字母缩写词(例如,一个乘客将“line”作为机票),这意味着我们可以给 Stringer 另一个选项(“|”是“or”运算符),即整个代码是感兴趣的首字母缩写词([[:alpha:]]+)。我们使用下面的命令告诉 Stringr 查找这个模式,并将其保存在一个新变量“ticketinfo”中。然后,我们删除空白并替换 NA 值。
### identify acronym
df$Ticketinfo = str_extract( df$lcTicket, ".+(?=\\s(?=[[:digit:]]{3,}))|[[:alpha:]]+" )### remove whitespaces from ticketinfo
df$Ticketinfo = str_replace_all( df$Ticketinfo, "\\s", "" )### remove NA values
df$Ticketinfo = ifelse( is.na(df$Ticketinfo), "none", df$Ticketinfo )
让我们再次绘制 10 个最常见的 ticketinfo 值。
t=data.frame(table(df$Ticketinfo[df$Ticketinfo!="none"]))t=t[order(-t$Freq),]ggplot(head(t,10),aes(x=reorder(Var1,-Freq),y=Freq))+
geom_col()+
labs(title="Count of Ticketinfo Acronym", x="Ticketinfo Acronym",y="Count")
你可以看到这对许多乘客来说增加了相当多的信息。将近 60 名乘客的值为“PC”在检查数据后,我相信它可能意味着“私人客舱”,因为许多(但不是所有)头等舱乘客的机票上都印有这个值。

从机票号码中提取免费升级
关于泰坦尼克号命运多舛的处女航,有趣的是船只有一半的容量。这艘船本可以轻而易举地运送更多的乘客横渡大西洋。这让我想到一些乘客可能已经“免费”升级到了更高的舱位。对机票号码的初步检查显示,第一个数字似乎与乘客等级(Pclass)相关,但也有例外。我假设第一个数字可以携带一些关于阶级变化的信息,这将对乘客的生存概率产生深远的影响。让我们提取首字母缩写词后的数字的第一个数字,并将其保存为假定的乘客类别(PresumedPclass)。
我们先提取一下票号。同样,票号由数字([[:digit:]])组成,长度为 3 个或更多字符({3,})。让我们也将它存储为一个字符(as.character),而不是一个数字,以便仍然对它使用 Stringr。
df$Ticketno = as.character( str_extract( df$lcTicket, "[[:digit:]]{3,}" ))
那么我们就取这个票号的第一个数字([[:digit:]])存储为推定的旅客类别。
df$PresumedPclass = str_extract( as.character(df$Ticketno) ,"[[:digit:]]{1}")
现在,让我们再次绘制这个新变量,但是让我们在所提供的乘客类的上下文中绘制它,看看它是如何相关的。
ggplot(df, aes(x=Pclass, fill=PresumedPclass))+
geom_bar()+
labs(title="Count of Passenger Class", x="Passenger Class",y="Count")+
scale_fill_discrete(name="Presumed Passenger Class")
正如我们在下图中看到的,这两个值大部分是相关的。但有时他们不会。在这两个类别不匹配的情况下,这可能是令人兴奋的信息。

进一步阅读
您想了解更多信息吗?在 Medium 上关注我看我的其他故事!
使用生存回归获得关于用户的洞察并优化黑名单保留策略
使用生命线 Python 库和生存分析来估计用户的“寿命”

克里斯·利维拉尼在 Unsplash 上的照片
公共网站和 SAAS 应用程序的安全管理员面临的最大挑战之一是检测非人类或恶意用户的非法活动。幸运的是,当有足够的使用数据可用时,通常不难发现某个用户的“行为”有些奇怪或不寻常。然而,要决定 T4 如何对这样的发现做出反应,通常也是一个不小的挑战。
在大多数情况下,最好的方法是简单地将可疑用户列入黑名单或阻止(通过阻止其 IP 地址、会话、设备、帐户等)。然而,用户有时不能被列入黑名单,直到得到进一步通知,安全管理员必须回答的下一个问题是“我们应该将可疑用户列入黑名单多长时间?”,“我们如何确保选择正确的阻塞持续时间?”,以及“我们如何测试自己?”当攻击者滥用合法用户的设备或 IP 地址时尤其如此。通常,这种关于黑名单的维护和保留策略的问题是使用相当多的直觉和假设来回答的——当使用直觉和假设的人足够有经验时,这是非常好的。
在这篇文章中,我将提出一个更可靠的方法来解决这个问题,它需要更少的直觉和更多的数据。我的方法是基于一种被称为生存分析或“事件发生时间”分析的方法论或统计工具。虽然我将集中讨论生存分析如何帮助我们处理一个非常具体的问题,但我相信这种方法,更重要的是,这里介绍的工具在各种环境和类似问题中是有价值的。
生存分析主要被研究人员和科学家用来估计在给定的时间发生某个【死亡】事件的几率。传统上,它用于比较或确定给定特定情况下不同人群的存活率,或回答一些问题,如“给予某种治疗,患者多活 3 年的可能性有多大”或“有多少人在被诊断患有 X 后有可能活 10 年”。
然而,生存分析也被用来估计其他类似死亡事件的可能性。生存分析用于估计用户在离开网站前可能会在网站上花费多长时间,引擎在发现某个故障后会继续运行多长时间,或者政府在不同类型的政治制度下“存活”多长时间。

该图表摘自我将在这里使用的生命线 Python 库的文档(https://lifelines.readthedocs.io/)
例如,上图中的分析描述了一条生存曲线,显示与平均执政时间不超过 10 年的民主政权相比,非民主政权更有可能“存活”20 年以上。
生存分析是一个巨大的话题,但我更愿意在下面引用一些好的介绍资源,然后继续展示它如何帮助我们解决这里提到的问题。然而,在我们继续之前,对于生存分析,或者更准确地说,对于它通常基于的表单数据,只有一个概念需要解释。
驱使科学家使用生存分析的一个主要问题是他们的数据被 T4 审查。假设我们试图在某一种树长得太大并倒塌之前估计它的寿命。同样,假设我们有一个数据集,其中包含我们在全国范围内监控的 1000 棵树。现在,假设我们的数据集中只有 50%的树倒塌了,剩下的 500 棵树仍然挺立着。我们应该如何解释这些树?在我们知道它们的寿命结束之前,我们能报告它们的寿命吗?我们是否应该将这些树从我们的数据集中排除,因为我们对它们的预期寿命一无所知?换句话说,我们的数据是右删截的——我们知道它从哪里开始(在图表的右侧),但我们不一定知道它至少在哪里结束。我不打算在这里讨论这个理论(你可以使用下面引用的一个很好的来源),但是生存估计量的一个主要目的就是解释这种数据。否则,生存分析会是一个更简单的任务。
因此,任何生存分析估计器都需要至少 2 个变量或特征: (1) 持续时间(例如,树在倒塌之前站立了多长时间);以及 (2) 事件(是否已经发生“死亡”事件——树是否已经倒塌)。这一点很快就会清楚。
黑名单、保留策略和用户生活
因此,假设我们有一个 web 应用程序,它经常成为僵尸程序或自动化攻击的目标。幸运的是,我们知道如何识别由非人类和恶意用户生成的请求,并且我们可以轻松阻止生成这些请求的设备或地址。然而,我们也知道这些设备或地址也被真实用户使用,因此我们希望尽可能短时间地阻止它们。我们的安全顾问建议将我们黑名单的保留策略设置为 14 天,此后设备将不再被阻止,因为她的经验表明这是一种非常常见的模式。我们能测试这个吗?
我相信我们可以,生存分析是这项工作的一个非常好的工具。假设我们有了所需的数据,我们可以使用生存分析来估计这些设备或地址在停止生成非人类恶意请求之前将被邪恶用户接管多长时间。如果我们的安全专家是正确的,那么 14 天后,当我们检测到设备或地址产生非人类或恶意请求时,我们应该可以安全地重新激活我们已列入黑名单的设备或地址。
我们拥有的数据由大约 25 天前首次观察到的非人类行为的设备或地址的汇总统计数据组成。这里有两个我们特别感兴趣的特性: days_with_bad_req,记录设备或地址生成被我们识别为非人类的请求的天数,以及一个名为 event 的变量,告诉我们设备或地址是否已经停止生成非人类的请求,或者“攻击”是否仍在继续。当一个地址或设备发出任何请求已经超过 10 天时,我们将该地址或设备视为已经“死亡”或停止生成非人类请求。因此,如果有人接管了我的 IP 地址,并在 6 天内生成“坏”请求,然后消失了 14 天,我的地址将有个 days_with_bad_req = 6 和个 event = 1。如果他昨天接管了我的 IP,并仍然使用它来攻击网站,那么它将有个 days_with_bad_req = 2 和个 event = 0。

这里有两个更有趣的特性,我们稍后会用到: req_day 记录该设备或地址每天生成的请求数,这些请求应该会给我们一些关于其活动强度的测量值。 bad_req_days_rate 是告诉我们该设备或地址产生非人类流量的时间百分比(天数)的功能(即,它是否专门用于生成恶意请求)。
初步数据显示,恶意用户实际操作的时间比 14 天短得多,但请记住,这里报告的一些数字可能是仍在进行的攻击。

我们将首先使用 KaplanMeierFitter 来确认这一声明。KM 可能是最流行的单变量生存分析估计器,Lifeline 库版本非常容易使用,并且具有非常有用的绘图功能,可以立即给我们一些见解。请注意它只需要两个变量:变量持续时间——对我们来说是设备或地址创建非人类请求的天数,以及变量事件——在我们的情况下,它等于设备或地址的攻击是否正在进行,或者是否被认为已经完成(即,自其上次活动以来已经超过 10 天)。
KM fitter 绘制的图表显示,超过一半的恶意用户会在 2 天的活动后停止攻击,大约 90%的攻击者会在第 5 天结束时消失。

生命线库实际上有一个非常有用的函数,名为【百分位()】,它可以很容易地帮助我们进一步解释生存曲线,并确定所需的边界。 percentile() 函数可以只取一个参数,这是所需的百分位数,因此,例如, percentile(0.1) 将给出 90%的人口将“死亡”的时间点—回想一下,我们使用生存分析来估计“死亡时间”事件。

让我们用一些有用的百分位数来更好地了解我们的生存曲线。
50% of the population will not make bad req after 1.0 days
75% of the population will not make bad req after 2.0 days
99% of the population will not make bad req after 9.0 days
no one will make bad req after 14.0 days
嗯,这肯定证实了我们的安全专家的直觉,因为任何人都不可能到达的时间点是 14 天。然而,如果尽快从黑名单中删除地址或设备对我们来说真的很重要,那么我们的分析表明,我们也可以将保留时间设置为仅仅 10 天,以避免大约 99%的恶意非人类请求。
我们也可以使用 predict() 方法来确认这一点,该方法将我们想要估计存活几率的天数作为参数。
print(f’Probability that device/address takeover continues after 3 days is: {kmf_breq.predict(3)}’)**Probability that device/address takeover continues after 3 days is: 0.14585572331045113**print(f'Probability that device/address takeover continues after 10 days is: {kmf_breq.predict(10)}')**Probability that device/address takeover continues after 10 days is: 0.0033031481126646763**
生存回归越来越复杂
在大多数情况下,使用 Kaplan-Meier 估计量就足以获得所需的洞察力,但有时我们可能需要(或想要)做出更准确的估计。例如,假设我们有充分的理由相信,更具侵略性的非人类行动者也可能更快消失。如前所述,我们有另一个名为 bad_req_days_rate 的功能,它告诉我们设备或地址被用于生成恶意非人类活动的天数百分比。我们想检验这一特征(以及其他特征)的变化如何改变我们的生存功能。
Lifelines 库提供了另一个非常有用的估计器,名为 CoxPHFitter,这也是一个非常流行的估计器,用于 survival 回归。与 KMF 等单变量估计不同,生存回归估计允许我们在分析中添加更多的协变量,并根据持续时间对它们进行回归。因此,让我们将特性 bad_req_days_rate 与特性 req_day 一起添加到我们的模型中,该特性通过计算每天生成多少请求(任何请求)来记录用户、地址或设备运行的一般速度。

这个总结告诉我们许多需要用教科书来解释的事情,但请注意几个要点:尽管这两个特征的 p 值在统计上是显著的(它小于 0.05),但我们的模型的一致性得分并不令人印象深刻,这意味着我们的模型(我们选择的协变量)不会很好地预测我们想要估计的最终寿命。这通常意味着我们需要重新思考我们的模型甚至数据的结构。然而,为了便于解释,我现在将忽略这一点。
要注意的第二件重要的事情是在 exp(coef) 列下的两个特性的值,这是我们用来解释模型的主要值。变量 req_day 的 exp(coef) = 0.89,这意味着 req_day 增加一个单位将降低死亡的“危险”,从而增加设备或地址随时间存活的机会约 11%。另一方面,我们的第二个参数, bad_req_days_rate 的为正(或> 1.0) exp(coef) — 1.04。这意味着 bad_req_days_rate 每增加一个单位(例如,设备在%51 而非 50%的活动天数内发出错误请求)将增加死亡风险,从而将用户继续发出错误请求的几率降低 4%。
换句话说,正如我们假设的那样,更具攻击性的攻击持续的时间更短。实际上,我们可以使用 CoxPHFitter 通过 plot_covariate_groups()方法来可视化这一点。
cp.plot_covariate_groups('bad_req_days_rate', [75, 100])

这向我们展示了 bad_req_days_rate 为 75%和 100%的用户的生存函数。正如预期的那样,发起更集中攻击的用户“存活”更少或更早死亡。
我们也可以通过使用 predict_survival_function()来看到这一点。下面的代码通过创建两个用户来说明这一点,这两个用户的 bad_req_days_rate 只有一个不同,即一个是 80%,另一个是 95%。
**#Create the first user with *bad_req_days_rate = 80***
user_a_df = pd.DataFrame(np.array([0,2,1,80]).reshape(1,-1),columns=cols)**#Create the second user with *bad_req_days_rate = 95*** user_b_df = pd.DataFrame(np.array([0,2,1,95]).reshape(1,-1),columns=cols)**#predict the survival function for each user and plot it**surv = cp.predict_survival_function(user_a_df)
plt.plot(surv)surv = cp.predict_survival_function(user_b_df)
plt.plot(surv)

结论:这是对生存分析的一个简短而深刻的介绍,我希望它能成为一个强大的工具,使我们能够对用户活动数据的重要方面获得有趣的见解。虽然这只是一个(有趣的)用例,但我试图使它足够简单,以便在其他环境中使用。
祝你好运!
我的笔记本在这里可以找到
[1]生命线图书馆
[2] SciKit-Survival 是一个与我使用的不同的生存分析 python 库,但是一个极好的资源。
【2】几个比较好的来源这里,这里这里。还有几个短的这里这里和这里。
在亚马逊 SageMaker PyTorch 培训工作中使用 TensorBoard:一步一步的教程
理解 Amazon SageMaker 培训工作的数据流,并通过示例学习如何在 SageMaker Pytorch 培训工作上设置 TensorBoard
TL;速度三角形定位法(dead reckoning)
在这篇文章中,我们在这个博客中向你展示如何在亚马逊 SageMaker PyTorch 培训工作中使用 TensorBoard。这些步骤是:
- 在 SageMaker 培训作业运行时安装 TensorBoard,此处为
- 将 PyTorch SageMaker 估计器初始化时的tensor board _ output _ config参数配置为这里
- 在 PyTorch 培训脚本中,记录您想要监控和可视化的数据,如这里的所示
- 启动 tensorbard 并将日志目录指向步骤 2 中配置的 s3 位置
写这篇博客的原因
在我最近的一个项目中,我需要使用 TensorBoard 来可视化来自亚马逊 SageMaker PyTorch 培训工作的指标。在网上搜索并查看了 AWS 官方文档、SageMaker SDK 示例和 AWS 博客后,我意识到没有针对该主题的现有分步教程。所以,我写这篇文章,希望给你一个现成的解决方案。这篇文章也可以在我的博客里找到。
SageMaker 培训作业如何在 S3 和培训实例之间交换数据
首先,让我们在执行 PyTorch SageMaker 培训工作时有一个总体的了解。SageMaker 简化了以下流程:

SageMaker 培训工作
- 启动并准备请求的 ML 实例
- 从 S3 下载输入数据
- 从 ECR 中提取训练图像
- 执行训练文件(上图中的 train.py)作为训练的入口点
- 把训练模型神器推回 S3
让我们通过下面的例子来放大 ML 实例和 S3 之间的数据交换。这里我们使用 SageMaker Python SDK 。
输入数据:
- 输入数据位置由估算器配置。拟合函数输入参数。在这个例子中,训练数据是具有 S3 前缀S3://input-data-bucket/train的所有对象,验证数据也是如此。
- 数据下载到 ML 实例中,位于/opt/ML/input/data/train和/opt/ML/input/data/val文件夹中。“ train ”和“ val ”通道通过 estimator.fit.inputs 参数字典中的键进行配置。
输出模型神器
- 您的训练脚本应该将您所有的最终模型工件写到目录 /opt/ml/model 中。
- SageMaker 将 /opt/ml/model 下的数据作为单个对象以压缩 tar.gz 格式复制到您在估计器对象 output_path 参数中指定的 S3 位置。在我们的示例中,在训练作业成功之后,模型工件被定位为S3://output-data-bucket/model/model . tar . gz。
在 SageMaker PyTorch 培训工作中逐步使用 tensor board
同步张量板日志
SageMaker 调试器是去年年底的一个特性。它提供了一种非常简单的方式从 SageMaker 培训工作中发出 TensorBoard 数据。要使调试钩子发出张量板数据,需要如下指定新选项TensorBoardOutputConfig:
在训练作业期间,调试挂钩将生成的张量板数据近乎实时地上传到从配置中提供的s3_output_path的值导出的 S3 路径。
在你的训练脚本中,你应该把你的 TensorBoard 日志写到 ML 实例中由s3_output_path指示的本地文件夹中。如果跳过设置,默认目录是/opt/ml/output/tensor board/。以下是一些示例代码:
在 SageMaker PyTorch 容器中安装 TensorBoard
默认情况下,SageMaker PyTorch 容器中不包含 TensorBoard 包,SageMaker Python SDK 中的 PyTorch estimator 使用该包。你得先装 TensorBoard。最简单的方法是使用在 SDK 文档中描述的 requirements.txt 文件:
如果您想在您的脚本中使用其他包,您可以在与您的训练脚本相同的目录中包含一个
requirements.txt文件,以便在运行时安装其他依赖项。requirements.txt和你的训练脚本应该放在同一个文件夹里。创建 PyTorch 估计器时,必须在source_dir参数中指定该文件夹。
启动 TensorBoard 服务器
现在,我们可以通过命令*F_CPP_MIN_LOG_LEVEL=3 AWS_REGION=YOUR-AWS-REGION tensorboard --logdir s3://tensorboard-output-location*启动 TensorBoard 服务器
- TensorBoard 支持直接从 S3 加载日志文件。我们将日志目录指定为在
TensorBoardOutputConfig中配置的目录。 - 您需要正确设置 AWS 环境变量,例如
AWS_ACCESS_KEY_ID and AWS_ACCESS_KEY_ID. - F_CPP_MIN_LOG_LEVEL 可以抑制详细日志
- 如果您在 SageMaker 笔记本实例中运行 TensorBoard 服务器,您可以通过预先设计的笔记本实例 url 访问 TensorBoard 页面,例如https://YOUR-NOTEBOK-INSTANCE-name . notebook . YOUR-region . SageMaker . AWS/proxy/6006/。 注意最后一个反斜杠是 madantory,否则看不到 TensorBoard 页面。
参考
- SageMaker 容器提供的环境变量列表:https://github . com/AWS/SageMaker-Containers # list-of-provided-environment-variables-by-SageMaker-Containers
- SageMaker 培训工作如何运作https://docs . AWS . Amazon . com/sage maker/latest/DG/how-it-works-training . html
- SageMaker 调试器https://docs . AWS . Amazon . com/SageMaker/latest/DG/train-debugger . html
- Estimator:通过 SageMaker Python SDKhttps://sagemaker.readthedocs.io/en/stable/estimators.html为 SageMaker 培训提供的高级接口
使用 TensorFlow Go 通过 web 服务服务于对象检测模型
使用 TensorFlow Go 构建 web 服务来服务 MobileNet 对象检测模型。

张量流这个术语超出了 Python 和神经网络的范畴。在这个时髦的词背后,有一个由几个框架、项目甚至硬件组成的完整生态系统。其中一个项目是 TensorFlow Go 。这个 TensorFlow API for Go 擅长在 Go 程序中加载和部署模型,这些模型是用 Python 对应物创建的。在本教程中,我们将看到一个这样的例子。这里的目标是在 Go 中创建一个 web 服务器,为 TensorFlow 中训练的对象检测模型提供服务。
安装 TensorFlow Go
安装 TensorFlow Go 需要使用下载软件包
$ go get github.com/tensorflow/tensorflow/tensorflow/go
此外,您需要安装 TensorFlow C 库。安装起来没那么简单,为了避免侧钻,这里就不讨论了。你可以在 https://www.tensorflow.org/install/lang_c找到说明。
项目的设计
对于第一次迭代,我只向项目中添加一个对象检测模型。然而,我将这些功能实现为包,以防将来我决定添加额外的模型。如果发生这种情况,我将重用部分代码或将其他部分重构为接口。
模型
我们将服务的模型是在从http://download . tensor flow . org/models/object _ detection/SSD _ MobileNet _ v1 _ COCO _ 2018 _ 01 _ 28 . tar . gz获得的 COCO 数据集上训练的 SSD MobileNet 对象检测模型。“但是胡安,MobileNet,SSD,还有 COCO。这是什么意思?”很高兴你问了!我将从 MobileNet 开始简要解释这些术语。
MobileNet ( Howard et al. )是一个轻型机器学习模型家族,以精度换取速度和性能。顾名思义,MobileNet 设计用于移动或嵌入式设备。SSD,代表单次多盒检测器(刘等),是指使用单个神经网络来检测对象的方法。与这种“单镜头”方法相对应的是一种架构,它使用一个称为“提议生成器”的额外组件来查找图像的感兴趣区域。最后,我们有 COCO,它是“上下文中常见对象”的缩写(林等人),用于训练模型的数据集。COCO 数据集是一个超过 200,000 个标记图像的集合,这些图像分布在 90 个类别中,包括“鸟”、“猫”、“人”和“车”。"
服务
我们将构建的服务有一个端点——predict——它将图像的路径作为参数。它的响应是一个 JSON,包含以下字段:
detections:被检测对象的数组,包括以下对象:score、box和label。score:检测的置信度得分。box:检测对象的边框([yMin, xMin, yMax, xMax])。这个数字不是检测的像素位置。相反,返回值是相对于图像的宽度和长度的位置。例如,在大小为[400,200]的图像中,位于其垂直中心的对象的yMin值为 0.5(图 1)。label:被检测对象的 COCO 类。numDetections:检测总数。

图一。检测到的对象的示例。
图 2 给出了一个响应示例。

图二。服务的输出。
构建服务
下载模型
从这里下载模型开始教程http://download . tensor flow . org/models/object _ detection/SSD _ mobilenet _ v1 _ coco _ 2018 _ 01 _ 28 . tar . gz。然后解压缩文件。在该目录中,您填充了几个文件,其中包括关于模型的元数据、训练配置文件(来自 TensorFlow 的对象检测 API)、检查点等。从所有这些中,您只需要 saved_model/目录。所以复制它,并粘贴到您的工作目录。
加载模型
下载完模型后,下一步是将其加载到 TensorFlow Go 中。在工作目录中,创建一个新文件夹,并将其命名为 models。在里面,创建一个文件 coco_ssd_mobilenet.go 。然后添加以下代码:
在最上面,我们有包的名字,imports,和一个 struct Coco,我们将在其中添加模型。结构之后是文件的路径和创建结构的构造函数。很标准的做法。然后就变得有趣了。
该结构的第一个方法Load()加载模型。它的第一行tf.LoadSavedModel(),使用模型的路径、一系列标签和一个我们目前不需要的SessionOptions对象作为参数:
等等,标签?那是什么?
SavedModel 标记和签名
像我们这样的 TensorFlow SavedModel 可以有多个图。因此,为了识别它们,这些图表与标签相关联。因此,当加载模型时,你需要指定你要使用的图。在这个例子中,我们需要标记“serve”,它预测。要查看 SavedModel 的标签,以及我们即将使用的模型的其他信息,您需要 TensorFlow 安装附带的工具 saved_model_cli 。要使用它,请执行以下命令:
$ saved_model_cli show --dir PATH/TO/MODEL/ssd_mobilenet_v1_coco_2018_01_28 --all
下面的图 3 显示了输出。

图 3。模型的“服务”标签。
上面是标签“serve”的签名。SavedModel 签名定义了模型的输入和输出。这个模型的输入是一个名为“image_tensor”的张量,其dtype为UINT8,输入形状(-1,-1,-1,3)(任意大小的一批彩色图像)。至于输出,它们与我们在 JSON 响应中返回的值相同(detection_boxes、detection_classes、detection_scores 和 num_detections)。
有关 SavedModel 的更多信息,请访问https://www.tensorflow.org/guide/saved_model
阅读标签
加载模型后,下一步是读取标签文件。Coco 模型有 91 个类,因此,模型的输出是 1 到 91 之间的一个数字。但是这个值对用户来说没有意义。如果你给我看,我会说,“这是什么数字?我需要实际的标签。”因此,我将数据集的标签收集在一个名为 labels.txt 的文件中,目标是将模型的输出映射到一个标签:
以上是功能readLabels()。这个函数读取标签文件并将它们作为字符串片段返回。要使用该函数,从Load()添加对它的调用:
预测
现在最酷的部分来了:预测。总而言之,预测包括获取输入图像,将其转换为张量,然后将其提供给模型。正如我们在模型的签名中看到的,在预测之后,它产生四个张量detection_boxes、detection_classes、detection_scores和num_detections。这些值是用户将收到的信息:
Predict()有一个参数[]byte,代表图像。在函数的第一行,我们将使用[]byte并使用即将到来的函数makeTensorFromBytes()将其转换为张量。然后,我们使用Run()方法和刚刚转换的张量作为输入来执行图形。至于输出,使用一个[]tf.Output切片,包含模型的所有可能输出。接下来是函数responses.NewObjectDetectionResponse()将输出值封装在一个结构中。在进入细节之前,我们先来看看makeTensorFromBytes():
函数makeTensorFromBytes()取自这里的,将[]byte转换为张量。在它的内部有一个对makeBatch()的调用,这个函数为一个张量增加了一个额外的维度,并将其转换为大小为 1 的一批(还记得输入形状第 4 维输入形状):
最后,我们需要一个方法CloseSession()来结束会话:
准备响应
在预测并得到结果后,我们需要提取值,并使它们对用户来说是可显示的。为了实现这一点,我创建了一个名为responses的新包,并在其中添加了一个由检测列表和检测数量组成的ObjectDetectionResponse结构:
这里最重要的部分是NewObjectDetectionResponse函数,负责创建响应。这个函数有两个参数:输出张量和标签列表。输出张量与几个类型断言语句一起使用,以获取它们的值。然后,在一个循环中,我们迭代numDetections次,每次都检查检测的置信度是否低于给定的阈值。如果是真的,我们继续。否则,创建一个由乐谱、边界框和标签组成的detection实例,并将其添加到列表detection中。在函数结束时,返回ObjectDetectionResponse对象。
网络服务
因此,我们到达了最后一部分:网络服务。如上面的例子所示,该服务有一个 POST 方法,它接收一个图像并以 JSON 的形式返回ObjectDetectionResponse:
两个函数中的第一个,main()加载模型并设置服务。而第二个函数,处理函数predict(),接收图像并执行预测。
要运行服务器,请执行$ go run main.go。要进行测试,执行 run a curl 命令,就像这样curl -F "data=@PATH/TO/IMAGE" [http://localhost:8080/predict](http://localhost:8080/predict)。
尽情享受吧!
概述
TensorFlow 是一个由多个平台组成的庞大生态系统。其中之一 TensorFlow Go 能够执行由 TensorFlow (Python)和 TensorFlow.js 等同类产品生成的图形。在本教程中,我们构建了一个程序来加载对象检测 MobileNet 模型,并通过 web 服务为其提供服务。
该项目可以用作一个库。如果您想使用它,运行$ go get github.com/juandes/tensorflow-go-models/models。为了进行测试,您可以使用上面给出的 web 服务器代码。将来,我想在项目中添加更多的模型,并将我们使用的一些方法转换成接口。这样,我们可以轻松地扩展项目以支持更多的模型。如果你想尝试并添加一个新的模型,请随时提交您的 PR。
完整的源代码可以在 https://github.com/juandes/tensorflow-go-models获得
使用 Tensorflow Lite 进行对象检测
了解如何使用在 Python 中的 COCO 数据集上预训练的 MobileNet SSD 模型,在具有完整代码和非最大抑制的边缘设备上运行。

伊利亚·赫特在 Unsplash 拍摄的照片
Tensorflow 最近发布了用于 Tensorflow 2 的对象检测 API,tensor flow 2 有一个非常大的模型动物园。然而,他们只提供了一款采用 Tensorflow lite 的 MobileNet v1 SSD 型号,此处描述的是。在那篇博文中,他们提供了在 Android 和 IOS 设备上运行的代码,但没有为 edge 设备提供代码。随着 edge 设备的普及和 OpenCV 发布空间 AI 套件,我想填补这一空白。这正是我们将在这里看到的。
要求
这不需要安装 Tensorflow 对象检测 API 即可运行。一个简单的 Tensorflow 安装,以及用于图像处理的 OpenCV 足以运行它。
pip install tensorflow
pip install opencv
我使用了对象检测 API 中提供的一些代码来使工作变得更容易,但没有必要担心它,因为不需要显式安装它,你可以在这里找到完整的代码。
预训练模型可以从 Tensorflow 的博客这里下载,或者也提供代码。
标签
我们的第一步是将标签制作成随后需要的格式,这是一个嵌套的字典,里面有 id 和标签。我们将使用随模型提供的标签地图文本文件,并将其转换为所需的格式。
注 :-标签用“???"需要被忽略,并且除了第一个索引之外,跳过那些索引。所以每个标签都向后移动了一个位置。例如,标签“person”位于第一行,但它将被分配一个标签 0,依此类推。
这可以在它的 Java 实现中找到注释。
// SSD Mobilenet V1 模型假设类 0 是后台类
//在标签文件和类标签中,从 1 开始到 number_of_classes+1,
//而 outputClasses 对应于从 0 到 number_of_classes 的类索引
这可以通过下面的代码来完成:
def create_category_index(label_path='path_to/labelmap.txt'):
f = open(label_path)
category_index = {}
for i, val in enumerate(f):
if i != 0:
val = val[:-1]
if val != '???':
category_index.update({(i-1): {'id': (i-1), 'name': val}})
f.close()
return category_index
这里,忽略第一行,我使用 if 语句并存储i-1。这将创建一个字典,如下所示。

字典已创建
它将有 80 行,89 个键。
TfLite 解释程序
标签做好了,让我们来了解一下 TfLite 的解释器,以及如何得到结果。
初始化解释程序
import tensorflow as tfinterpreter = tf.lite.Interpreter(model_path="path/detect.tflite")
interpreter.allocate_tensors()
只需加载你的 tflite 模型的正确模型路径,分配张量即可。
输入和输出详细信息
要获得输入和输出的详细信息,请编写:
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
现在,让我们研究它们,看看给出什么类型的输入和我们将得到的输出。
输入细节是一个只有 1 个元素的列表,它是一个字典,如下所示。

输入详细信息
这里,我们可以看到输入形状是[1, 300, 300, 3]。除此之外,它要求输入图像的数据类型为np.uint8。
输出细节是一个包含 4 个元素的列表,每个元素包含一个类似输入细节的字典。每个调用返回 10 个结果,第一项存储矩形边界框,第二项存储检测类,第三项存储检测分数,最后一项存储返回的检测数。返回的边界框是标准化的,因此它们可以缩放到任何输入尺寸。
为了获得输出,我们需要读取图像,如果使用 OpenCV,将其转换为 RGB,适当地调整其大小,并在用输入帧设置张量后调用解释器。然后可以使用解释器的get_tensor功能获得所需的值。
import cv2img = cv2.imread('image.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
img_rgb = cv2.resize(img_rgb, (300, 300), cv2.INTER_AREA)
img_rgb = img_rgb.reshape([1, 300, 300, 3])interpreter.set_tensor(input_details[0]['index'], img_rgb)
interpreter.invoke()de_boxes = interpreter.get_tensor(output_details[0]['index'])[0]
det_classes = interpreter.get_tensor(output_details[1]['index'])[0]
det_scores = interpreter.get_tensor(output_details[2]['index'])[0]
num_det = interpreter.get_tensor(output_details[3]['index'])[0]
使用对象检测代码进行绘制
为了可视化,我使用了可用的 python 代码这里是,它不仅可以用来绘制边界框,如果需要的话还可以用来绘制关键点和实例遮罩。我们需要传递要绘制的图像、边界框、检测到的类、检测分数和标签字典。除此之外,当我们从解释器接收到归一化的边界框坐标时,我们还将归一化的坐标设置为 true。
from object_detection.utils import visualization_utils as vis_utilvis_util.visualize_boxes_and_labels_on_image_array(
img,
output_dict['detection_boxes'],
output_dict['detection_classes'],
output_dict['detection_scores'],
category_index,
use_normalized_coordinates=True,
min_score_thresh=0.6,
line_thickness=3)
结果
它将给出如下所示的结果。

结果
但是,仍然有一个问题需要解决,如下所示。

问题结果
这可以通过非最大抑制来实现。
非最大抑制
我不打算解释它,因为它已经在互联网上的各种文章中深入讨论过了。一个这样的例子是这篇文章。为了实现它,我将使用combined_non_max_suppression Tensorflow Image 来执行这个任务,因为它允许我们同时处理多个类。它获取输出并返回阈值后剩余的预测。
def apply_nms(output_dict, iou_thresh=0.5, score_thresh=0.6):q = 90 # no of classes
num = int(output_dict['num_detections'])
boxes = np.zeros([1, num, q, 4])
scores = np.zeros([1, num, q])
# val = [0]*q
for i in range(num):
# indices = np.where(classes == output_dict['detection_classes'][i])[0][0]
boxes[0, i, output_dict['detection_classes'][i], :] = output_dict['detection_boxes'][i]
scores[0, i, output_dict['detection_classes'][i]] = output_dict['detection_scores'][i]
nmsd = tf.image.combined_non_max_suppression(
boxes=boxes,
scores=scores,
max_output_size_per_class=num,
max_total_size=num,
iou_threshold=iou_thresh,
score_threshold=score_thresh,
pad_per_class=False,
clip_boxes=False)
valid = nmsd.valid_detections[0].numpy()
output_dict = {
'detection_boxes' : nmsd.nmsed_boxes[0].numpy()[:valid],
'detection_classes' : nmsd.nmsed_classes[0].numpy().astype(np.int64)[:valid],
'detection_scores' : nmsd.nmsed_scores[0].numpy()[:valid],
}
return output_dict
下面给出了完整的代码,或者你可以访问我的 Github repo ,它也包含了visualization_utils.py和模型。
在结束之前,我想澄清一件事,如果你试图在采用英特尔处理器的 Windows 上运行它,你会得到一个可怕的 fps。我在 i5 上得到了大约 2,作为比较,没有 tflite 的相同 Tensorflow 模型给了我大约 8 fps。这里的就是解释这个。然而,在 edge 设备上,这不是问题,而且内存占用相当少,这将有利于他们的内存限制。
虽然这个模型不是很准确,但我希望我会提供一个样板文件,使您在使用 Tflite 的对象检测器时更容易完成任务。
使用 TFRecords 训练 CNN 了解 MNIST
编写、读取和使用 TFRecords 的简短演练
当我开始使用 TFRecords 时,我花了一段时间来理解它的概念。有那么多新事物。为了让其他人免于这种麻烦,我创建了一个基于 MNIST 数据集的实践演练。
注意:这篇博文现在在有一个更通用的版本,包含了更多最新的概念。此外,还要注意的是,一旦你开始使用,TFRecord 格式并不是那么难,这就是为什么我创建了一个的实用介绍。要了解更多,在本教程之后,我建议您参考这两个资源。
概观
MNIST 数据集由黑白的数字化手写数字组成。每张图片 28x28x1,非常小。整个数据集的内存占用只有 32 MB。

数据集概述。作者约瑟夫·斯泰潘。
导入和助手函数
让我们从必要的进口开始;两个库,os 和 Tensorflow。此外,我们还设置了一个全局变量 AUTOTUNE,我们稍后会用到它。
首先,我们将 MNIST 数据集下载到本地机器上。然后,我们将两个选项设置为 True, shuffle_files 和 as_supervised 。
当我们创建 TFRecord 数据集时,使用第一个选项;第二个选项允许稍微舒服一点的迭代。
我们还可以通过调用。基数()。
以下四个函数提高了可读性,并用于创建我们写入 TFrecord 文件的单个示例。
写入 TFRecords
接下来,我们检查所有拆分(这里,只有“训练”和“测试”)。
对于每个分割,我们创建一个 TFRecordWriter ,它将解析后的示例写入文件。请注意,我们将当前处理的分割添加到文件名中——这允许我们稍后通过字符串模式对文件进行分组。我们使用的额外索引用于计算我们写入磁盘的样本数量。这个小技巧对于自定义数据集很有帮助。cardinality()操作不会返回元素的数量。
因为我们之前设置了 as_supervised,所以现在我们可以迭代(例如,label)对。
主要的示例生成发生在临时字典数据的创建期间。首先,我们将稍后要使用的每个数据字段转换成一个 tf.train.Feature 。对于图像的高度和宽度,我们使用一个 _int64_feature (因为这些数字是整数);对于实际的图像数据,我们首先序列化数组,然后将其转换成一个 bytes_list 。存储非标量数据需要这种转换。
定义好所有特性后,我们现在可以创建一个单独的示例并将其写入 TFRecord 文件。
我们继续进行,直到处理完当前拆分的所有示例,然后对以下子集重复该过程。
在处理完两个子集之后,我们已经创建了前两个 TFRecord 文件(耶!),一个保存训练,一个保存测试数据。我们为每个子集的每个记录增加的索引在训练模型时很有用:
对于 tensorflow_datasets 附带的数据集,只需调用。基数()。这不会报告像我们这样的自定义数据集的实际大小,而是返回-1,这意味着示例的数量是未知的。
但是,当在这样的数据集上训练时,我们必须知道我们的数据集可以交付多少批。否则,我们可能会在一个无限循环中运行;详情见下文。
简单回顾一下:我们使用了 MNIST 数据集,并将所有示例写入 TFRecord 文件。
正在读取 TFRecords
在创建之后,我们希望将它们读回内存中。这个过程与上面类似,但方向相反:
我们创建了一个从 TFRecord 文件中读取示例的函数。在这里,我们创建了一个字典,其中包含了我们希望从示例中使用的所有字段;这本字典类似于我们用来写数据的那本。对于每个键,我们定义一个占位符。注意最后一个字段:它的类型是 tf.string (尽管我们将它存储为一个字节列表);所有其他字段都用与以前相同的类型初始化。
准备好字典后,我们可以从 TFRecord 文件中提取示例。最后,我们获得原始图像数据。注意,我们将 uint8 设置为数据类型。如果我们的图像包含浮点,我们将设置 float64 为数据类型。由于 MNIST 数据的范围在 0 到 255 之间,因此我们可以接受整数:
创建数据集
通过下面的函数,我们围绕 TFRecord 文件创建了一个数据集。以前,我们只定义了一个函数来得到一个例子;现在我们创建一个 TFRecordDataset 来将所有的例子映射到这个函数。
我们在中间语句中这样做。这里,我们使用上面创建的自动调优器。在训练过程中,稍后,它会自动确定我们可以并行处理多少个示例。这可以减少 GPU 的空闲时间。
之后,我们重组数据,设置批量大小,并设置 repeat,不带参数;这意味着无休止地重复。这需要我们记住我们的数据集有多少个例子(如上所述)。
[作为一种简单的替代方法,我们可以在这里将 repeat 设置为我们稍后想要训练的时期数,并将 fit()-函数中的时期数设置为 1。这使得数据集通过我们的网络解析“epoch”次(因为我们用 epoch 的数量设置了 repeat(),但是只解析一次(因为我们用 epochs = 1 设置了 fit())。]
最后,我们让自动调优器决定预取的最合适的例子数量。
到目前为止,我们只创建了一个数据集,并向它映射了一个数据生成函数。作为健全性检查,让我们看一下数据集给我们的一个样本。
它返回两个张量。第一个张量的形状为(32,28,28,1)(因为我们取了一个批次,批次大小为 32),第二个张量的形状为(32,)(因为我们有 32 个标签,在我们的批次中每个示例一个)。
到此为止,我们来回顾一下:
我们创建了两个 TFRecord 文件,一个用于训练数据,一个用于测试数据。我们通过迭代原始 MNIST 数据集中的所有(影像,标注)对,将它们转换为 tf.train.Feature。所有要素一起形成一个示例。然后,我们创建了一个函数来反转这一点;它从存储在 TFRecord 文件中的示例中提取特征。最后,我们将该函数映射到我们的数据集,并进行健全性检查,看看是否一切正常。
培养
我们的下一步是在 TFRecordDataset 上训练一个网络。
我们使用一个简单的卷积神经网络;请随意尝试不同的架构。
为了保持可读性,我们编写了一个函数来返回我们的网络,并检查我们的输出层是否符合标签形状,
然后在我们的训练数据集上拟合网络。我们将历元数(网络看到完整数据集的次数)设置为 2。
现在,我们需要知道我们的训练数据集有多少个例子。由于我们将数据集设置为无限重复,所以我们需要告诉网络要查询多少批,直到完成一个历元;这是参数步数每纪元:
训练完成后,在启用 GPU 的情况下,在 Colab 上大约需要 30 秒,我们将测试该模型。由于我们还将测试数据写入了 TFRecord 文件,我们可以使用我们的 get_dataset() 函数从这些文件中快速创建另一个 TFRecordDataset。
由于我们将函数调用中的第二个参数设置为“test”,所以我们的数据集不会重复;我们不需要确定步骤的数量。
然后我们调用 model.evaluate(),它返回两个值的数组。
第一个数字是损失;第二个数字是我们感兴趣的:精确度。仅仅过了两个时代,它就徘徊在 95%左右,这是一个好的开始。
摘要
我们把重点放在 MNIST 数据集上作为我们正在进行的例子。使用它,我们创建了两个 TFRecord 文件,一个用于训练数据,一个用于测试数据。接下来,我们讨论了将数据读回内存,以最终训练 CNN。
这篇博文现在在有了一个更通用的版本,包含了更多最新的概念。此外,还要注意的是,一旦你开始使用,TFRecord 格式并不是那么难,这就是为什么我创建了一个的实用介绍。要了解更多,在本教程之后,我建议您参考这两个资源。
涵盖整个过程的 Colab 笔记本在这里有售。
这篇文章讨论了现有数据集环境中的 TFRecords。如果你有兴趣看到这种用于自定义数据集的文件格式,请看一下这段代码
创建自定义 TFR 数据集
colab.research.google.com](https://colab.research.google.com/drive/1yQRDYzJsHX8w5g042w02y0lm1S3vMscL?usp=sharing)
对于这个帖子
包含代码的端到端示例项目。
towardsdatascience.com](/custom-audio-classification-with-tensorflow-af8c16c38689)
使用卡方检验进行特征选择和实现
特征越少,解释模型就越容易

https://unsplash.com/@purzlbaum
让我们使用卡方问答方式来解决这个特征选择问题。如果你是一个视频爱好者,你可以看看我们在同一个的 youtube 讲座。
问题 1:什么是特性?
对于任何 ML 或 DL 问题,数据都是按行和列排列的。让我们以泰坦尼克号沉船问题为例。
- 这些行由乘客的数据组成,实际上,每一行都由单个乘客的数据组成。这通常被称为实例、实体或观察。
- 这些栏目描述了乘客的性别、年龄等。这些被称为特征或属性。

泰坦尼克号的数据、观察、特征(Imag 来源作者)
问题 2:有哪些不同类型的功能?
不涉及太多细节,让我们以上面的例子为例:
- 年龄、费用、Pid 是数字属性。但是,Pid 不会泄露任何模式,因为它通常是连续或随机分配的。
- 姓名、性别等。是分类属性(它们可以进一步分为名词性的和序数性的‘大小’,值有小、中、大之类)
- 特殊类是具有两个可能值的分类变量或二元变量,如性别或幸存变量
- 在问题的上下文中,我们要预测的变量的值是因变量,而我们要确定的变量或特征称为自变量。
问题 3:什么是特征选择?
在泰坦尼克号沉船数据集中,要解决的问题是给定乘客的特征,要确定乘客是否幸存(即,他是否能够接近船只)。所以,这是一个分类问题。

图片来源:https://unsplash.com/photos/MeGmdPNe36w
如果原始数据集有关于乘客的 8 个特征,并且分类模型带来大约 90%的分类准确度,特征选择的目标是从 8 个特征中选择可能的 3 个或 4 个,并且仍然达到相似的准确度。
问题 4:为什么选择功能?
具有较少的功能:
- 这些模型更容易解释
- 模型训练更快,模型所需的空间也更大
问题 5:特征选择和卡方有什么联系?
实际上,这不是卡方检验,而是假设检验。同样的典型设置是:
- H0:属性性别在乘客的生存中不起作用(特征不重要)
- H1:属性性别在生存中扮演了一个角色(特征很重要)
值得注意的是,这些技术被称为单变量技术,因为我们分别检查每个特征。
C 当特征是分类的,目标变量是任何可以被认为是分类的时,使用卡方检验。它衡量两个分类变量之间的关联程度。如果两者都是数字,我们可以使用皮尔森的积差相关,如果属性是数字,并且有两个类别,我们可以使用 t 检验,如果超过两个类别,我们可以使用 ANOVA
当我们探讨性对生存状态的影响时,我们可以从一个简单的形象化开始。

泰坦尼克号生存状态对性别(图片来源:作者)
上面的图像清楚地表明,两个班级的男女比例并不相同。假设检验是一种更正式的方式。卡方统计使用以下公式计算,其中“O”代表观察值或实际值,“E”代表期望值(如果这两个类别是独立的)。如果它们是独立的,这些 O 和 E 值将是接近的,并且如果它们有一些关联,那么卡方值将是高的。

卡方公式(来源:作者)

乘客性别对抗生存(来源:作者)
上表显示了乘客的性别与生存状态。这也被称为列联表。
- 如果他们是独立的,那么幸存和未幸存的男女比例应该是相同的。
- 幸存的比率为 449/1313
- 如果没有依赖或关联,463 * 449/1313 的女性应该存活,即 158.13
- 这是独立状态下的预期计数
- 所有四种情况计算如下

卡方计算观察值与期望值(图片:作者)
这些卡方统计由自由度调整,自由度随变量的级别数和类变量的级别数而变化。
可以注意到,在适当离散化之后,卡方也可以用于数值变量。
问题 6:如何实现相同?
导入库
from sklearn import datasets
from sklearn.feature_selection import chi2
from sklearn.feature_selection import SelectKBest
我们将对葡萄酒数据集进行特征选择

来源:https://archive.ics.uci.edu/ml/datasets/wine
让我们看看卡方得分
# Loading wine data
X,y=datasets.load_wine(return_X_y=True)
# k = 4 tells four top features to be selected
# Score function Chi2 tells the feature to be selected using Chi Square
test = SelectKBest(score_func=chi2, k=4)
fit = test.fit(X, y)
fit.scores_
它将按如下方式打印这些值
array([5.44549882e+00, 2.80686046e+01, 7.43380598e-01, 2.93836955e+01,4.50263809e+01, 1.56230759e+01, 6.33343081e+01, 1.81548480e+00,9.36828307e+00, 1.09016647e+02, 5.18253981e+00, 2.33898834e+01,1.65400671e+04])
现在,如果我们想选择前四个特性,我们可以简单地做以下事情
X_new=test.fit_transform(X, y)
尾注:
卡方是用于分类的单变量特征选择的非常简单的工具。它没有考虑特征的相互作用。这最适合分类变量,因此在文本数据中应用广泛。
参考资料:
[1]https://youtu.be/hkDfi5j-6lM
[2]https://machine learning mastery . com/feature-selection-machine-learning-python/
[3]https://www . ka ggle . com/saptarsi/feature-selection-using-chi-square/
在 Python 中使用当前人口调查

克里斯里德在 Unsplash 上的照片
我花了长周末的大部分时间浏览所有公开的关于消费者金融和劳动力的数据。这是一个长期项目的一部分,但我现在必须分享我遇到的一些困难,这些困难无法通过堆栈溢出或其他资源来解决。
教训很明显:用 Python 处理 CPS 数据很难。我依靠了一些资源,比如 Brian Dew 的经济学博客和 Tom Augspurger 的工具,它们在缺少 API 的情况下帮助很大。但是因为我主要是想导入尽可能多的数据,并把它读入熊猫数据框,所以我不得不利用他们的技术来做一些符合我的目的的东西。我想出的一般方法如下。
该方法
我遇到的第一个障碍是,我找到的大多数资源都在 CPS 中寻找数据子集,无论是按时间框架还是按变量。你可能会认为,不用担心子集,我所要做的就是处理 CPS 数据本身的古怪,但是唉。
总的来说,CPS 数据的古怪相对简单地分解了方法。
对于一年中的任何给定月份,CPS 提供:
- 。存储在 zip 文件夹中的 dat 文件
- 答。当年使用的 txt 数据字典
虽然 CPS 没有以更容易阅读的方式提供。csv 或。dta 的步骤还是很容易理解的。一般的方法是(1)使用数据字典查找变量及其位置的解析参数,以及(2)从。dat 文件转换成数据帧。
同样,你会认为给定资源,我发现实现我的目标会很容易。对我发现的代码进行一些调整,应该可以让我轻松地获得给定月份的每个变量的数据,并在 CPS 中这样做很多年。然而,这第一步是最困难的,需要最多的变通办法。我希望提供一个更完整的解决方案,但我会在这里检查几个问题和解决方案。
问题 1:检索并解析出相关的数据字典
我不确定我是否有先见之明,或者只是固执地记录我从网上检索数据的确切位置,但第一个挑战涉及实际检索和解析数据字典。
首先,正如您在下面看到的,存储字典的地方没有一致的 URL 样式。第二,有时数据字典适用多年或仅适用几个月。最后,数据字典有多种组织相关数据的方式。
第一课:如果其他方法都失败了,那就放弃吧
如果你快速浏览一下自 1998 年以来所有可用的数据字典,你会发现它们所在的 URL 并不一致。并且没有明确的模式来决定一个字典何时退役或者更新到足够多的异常。
真正让我明白如何解析每个 URL 的是编写一长串 if-elif 语句。正如您在下面的片段中所看到的,2020 年大大改变了他们的 URL,数据字典在 2012 年进行了更新,在 2012 年之前,URL 格式似乎有所改变。使用下面的 if-elif 代码可以更容易地识别和归纳这些模式,但是显然需要归纳成可读性更好的东西。
URL 的变化是最不重要的问题。数据字典本身在 2002 年发生了巨大的变化,用一种完全不同的模式来描述 CPS 变量。下面你可以看到另一个 if-elif 链,显示了这些年来的变化。尽管它们已经相对一般化了,但是这个 if-elif 链还有一个额外的好处,那就是显示主要变化发生的时间。
问题 2:准备用于熊猫数据框的数据
如果你看一个. dat 文件,人眼完全看不懂。所以要把它变成类似桌子的东西需要一点准备。这里的主要问题是获得。dat 文件转换成稍微更有组织的形式,就像一个元组列表,其中每个元组都是数据帧中的一行。
之前的资源提供了一个很棒的例子【2017 年 4 月数据:
然而,当我查看前几年的数据时,你开始遇到问题。dat 文件,如包含杂散*字符或字母。你可以在代码中看到这一点。dat 数据将被转换成 int。
第二课:垂直阅读而不是水平阅读
为了解决这个问题,我必须创建一个助手函数来识别哪里的数据不能作为 int 读取,并提供一个估算值。在这种情况下,估算值将为“-1”,因为它经常在 CPS 数据中用作“不适用”值,但肯定需要对变量进行双重检查。
鉴于上面的代码缺乏可读性和代码嵌套,调整它来执行这样的操作有点困难。一般来说,水平阅读代码比垂直阅读代码更难,即使垂直阅读一开始看起来有点奇怪。
您可以看到下面的转换示例有一些好处。首先,更容易理解列表理解是如何工作的,以及每行代码是如何分组的。
其次,它读起来几乎完全像一步一步的指导,而不是文学的一句俏皮话。每行都有一个括号,读起来就像它自己的步骤,就像“开始组”或“结束列表”。
下面的代码仍然不漂亮,需要更多的 Python 禅宗,但它的工作。
问题 3:我想要更多的数据!!
这是更严重的问题之一。因为我的主要目标是尽可能多地收集数据,所以我倾向于让我的代码把所有东西放在一个整洁的包里。当然,在我的笔记本电脑上这样做肯定会导致一些内核重启。
第三课:概括,但不要太多
解决方法很简单:保持简单。在这种情况下,我不得不限制自己编写能够一次获得一个月 CPS 数据的代码。我还不得不阻止自己制作额外的助手功能,通过互联网存档对数据源进行存档,但我认为这将是我的下一个项目。
最后的想法
真的没有什么可以代替你自己去尝试。因为我真的对获取尽可能多的数据感兴趣,如果你真的只是对特定的数据集感兴趣,你可能会遇到较少的问题。但是你和我一样,希望这些课程能对你的 CPS 工作有所帮助。
通过 Python 使用 Fitbit Web API

上图是由 Fitbit 数据制作的,只是没有通过 API。我只是想分享我同事的工作。https://journals.plos.org/plosone/article?id = 10.1371/journal . pone . 0227709
Fitbit 提供了一个 Web API,用于从 Fitbit 活动跟踪器、Aria & Aria 2 秤和手动输入的日志中访问数据。所以如果你一直在使用 Fitbit,你可以使用【https://dev.fitbit.com/build/reference/web-api/basics/】的 Fitbit API ( 的)来获取自己的数据。除了使用 API 获取数据的便利之外,您还可以在在线应用程序上获取数据,您可以使用 API 获取在线应用程序上不可用的当天(每分钟)数据。虽然本教程最初与 Stephen Hsu 的惊人的教程相似,但我想我应该稍微更新一下这个过程,解决几个你可能会遇到的错误,并展示一点你如何绘制数据。和往常一样,本教程中使用的代码可以在我的 GitHub 上获得。就这样,让我们开始吧!
1.)创建一个 Fitbit 帐户
您可以点击这里创建一个 Fitbit 账户。它会带你到一个类似下面的页面。

你不需要像我一样勾选“随时更新”框。此外,fakeuser@gmail 不是我的帐户使用的电子邮件。
2.)设置您的帐户并创建应用程序
前往dev.fitbit.com。将鼠标悬停在“管理”上,然后点击“注册应用程序”。

应该会出现一个类似于下图的页面。

(答)你需要指定个人,以便能够更方便地要求下载日内数据(如果你没有或得到一个错误,你可以随时要求它这里)。( B )回调 URL 是 http://127.0.0.1:8080,因为我们将使用的 Python API 将它作为默认重定向 URL。
对于图像中的每个字段,这里有一些关于您可以在注册页面中放置什么的建议。
应用名称:可以是任何东西。
描述:可以是任何东西。
申请网址:可以是任何东西。
组织:可以是任何东西。
组织网站:由于我是个人使用(查看个人 fitbit 数据),这可能不适用。
服务条款网址:我放了 Fitbit 服务条款:https://dev.fitbit.com/legal/platform-terms-of-service/
隐私政策网址:我放了 Fitbit 隐私政策:https://www.fitbit.com/legal/privacy-policy
OAuth 2.0 申请类型:如果您想下载您的日内数据,OAuth 2.0 申请类型应为“个人”。顺便说一下,如果你不知道 OAuth 是什么,这里有一个解释。
回调 Url: 确保回调 Url 是 http://127.0.0.1:8080/这是因为我们将使用的库需要这样做,如下所示。

最后,单击协议框,然后单击注册。应该会出现一个类似于下图的页面。

记下您的 OAuth 2.0 客户端 ID 和客户端机密。
我们将从这个页面中需要的部分是 OAuth 2.0 客户端 ID 和客户端密码。稍后您将需要客户端 ID 和客户端密码,因此请记下它们。
# OAuth 2.0 Client ID
# You will have to use your own as the one below is fake
12A1BC# Client Secret
# You will have to use your own as the one below is fake
12345678901234567890123456789012
3.)安装 Python 库
下一步是使用一个 Fitbit 非官方 API 。点击链接后,点击绿色按钮。接下来,单击 Download Zip 并继续解压缩文件。

下载 zip 文件。
之后,打开终端/命令行,将目录切换到解压后的文件夹,并运行下面的命令。
#reasoning for it here:
#[https://stackoverflow.com/questions/1471994/what-is-setup-py](https://stackoverflow.com/questions/1471994/what-is-setup-py)
# If you want to install it a different way, feel free to do so. python setup.py install

进入文件夹目录,在终端/命令行中输入 python setup.py install 。
4.)API 授权
在开始本节之前,我应该注意两件事。首先,本教程中使用的代码可以在我的 GitHub 上找到。第二,如果你有错误,我在博文的后面有一个潜在错误部分。
下面的代码导入各种库,并将步骤 2 中的 CLIENT_ID 和 CLIENT_SECRET 赋给变量。
# This is a python file you need to have in the same directory as your code so you can import it
import gather_keys_oauth2 as Oauth2import fitbit
import pandas as pd
import datetime# You will need to put in your own CLIENT_ID and CLIENT_SECRET as the ones below are fake
CLIENT_ID='12A1BC'
CLIENT_SECRET='12345678901234567890123456789012'
下面的代码使授权过程能够发生
server=Oauth2.OAuth2Server(CLIENT_ID, CLIENT_SECRET)
server.browser_authorize()
ACCESS_TOKEN=str(server.fitbit.client.session.token['access_token'])
REFRESH_TOKEN=str(server.fitbit.client.session.token['refresh_token'])
auth2_client=fitbit.Fitbit(CLIENT_ID,CLIENT_SECRET,oauth2=True,access_token=ACCESS_TOKEN,refresh_token=REFRESH_TOKEN)

当您运行上面的代码(如 A 所示)时,单元格显示仍在运行,直到您登录您的 fitbit 帐户(B)并单击允许访问您的 Fitbit 帐户中的各种数据。
授权和登录后页面的外观。

这个窗口应该是你得到的。
5a。)获取一天的数据

您将使用 intraday_time_series 方法来获取数据。
我将首先从获得一天的数据开始,这样下一节将更容易理解。
# This is the date of data that I want.
# You will need to modify for the date you want
oneDate = pd.datetime(year = 2019, month = 10, day = 21)oneDayData = auth2_client.intraday_time_series('activities/heart', oneDate, detail_level='1sec')

如果你愿意,你可以把这些数据放入熊猫数据框。

当然,您总是可以将数据导出到 csv 或 excel 文件中。
# The first part gets a date in a string format of YYYY-MM-DD
filename = oneDayData['activities-heart'][0]['dateTime'] +'_intradata'# Export file to csv
df.to_csv(filename + '.csv', index = False)
df.to_excel(filename + '.xlsx', index = False)
5b。)获取多天的数据
下面的代码从startTime变量(在下面的代码中称为oneDate)开始获取所有日期的数据。

你当然可以导出final_df到一个 csv 文件(我建议不要试图导出到 excel,因为可能有太多的行很容易导出到 excel)。
filename = 'all_intradata'
final_df.to_csv(filename + '.csv', index = False)
6.)试着用图表显示当天的数据
这是一个部分,我强烈建议你用你自己的数据看看 GitHub 代码。
这一部分的最终目标是获得特定日期的特定时间的适当图表(日期也包括当天的时间)。我应该注意到,部分由于我的懒惰,我没有使我的代码高效。如果你不明白是怎么回事,不要着急。您可以向下滚动并查看最终结果。
# I want to get the hour of the day and time. The end goal of this section is to get a particular time on a particular day.
hoursDelta = pd.to_datetime(final_df.loc[:, 'time']).dt.hour.apply(lambda x: datetime.timedelta(hours = x))minutesDelta = pd.to_datetime(final_df.loc[:, 'time']).dt.minutes.apply(lambda x: datetime.timedelta(minutes = x))secondsDelta = pd.to_datetime(final_df.loc[:, 'time']).dt.seconds.apply(lambda x: datetime.timedelta(seconds = x))# Getting the date to also have the time of the day
final_df['date'] = final_df['date'] + hoursDelta + minutesDelta + secondsDelta
我的下一步将是查看 3 天的数据,而不是几天。
startDate = pd.datetime(year = 2019, month = 12, day = 24)
lastDate = pd.datetime(year = 2019, month = 12, day = 27)coupledays_df = final_df.loc[final_df.loc[:, 'date'].between(startDate, lastDate), :]
请记住,您也可以尝试通过按日期和小时(如果您愿意,甚至可以按秒)分组并取心率的平均值来绘制图表。代码还试图使图形看起来更好一些。
fig, ax = plt.subplots(figsize=(10, 7))# Taken from: [https://stackoverflow.com/questions/16266019/python-pandas-group-datetime-column-into-hour-and-minute-aggregations](https://stackoverflow.com/questions/16266019/python-pandas-group-datetime-column-into-hour-and-minute-aggregations)
times = pd.to_datetime(coupledays_df['date'])
coupledays_df.groupby([times.dt.date,times.dt.hour]).value.mean().plot(ax = ax)ax.grid(True,
axis = 'both',
zorder = 0,
linestyle = ':',
color = 'k')
ax.tick_params(axis = 'both', rotation = 45, labelsize = 20)
ax.set_xlabel('Date, Hour', fontsize = 24)
fig.tight_layout()
fig.savefig('coupledaysavergedByMin.png', format = 'png', dpi = 300)
有了这些工作,也许这不是我下去的好方法,因为目前的数据没有给出足够的背景来知道它是在休息或四处走动时。
7.)静息心率
有研究称静息心率可以反映你现在和未来的健康状况。实际上有一篇研究论文显示了持续佩戴 Fitbit 的 92,447 名成年人的日常静息心率的个体间和个体内可变性及其与年龄、性别、睡眠、身体质量指数和一年中的时间的关联。下面是论文中的一张图片(为清晰起见,添加了文字)。

【https://journals.plos.org/plosone/article? id = 10.1371/journal . pone . 0227709
我们一直使用的 api 调用也返回静息心率,因此下面的代码与前面的步骤没有太大的不同。
# startTime is first date of data that I want.
# You will need to modify for the date you want your data to start
startTime = pd.datetime(year = 2019, month = 11, day = 21)
endTime = pd.datetime.today().date() - datetime.timedelta(days=1)date_list = []
resting_list = []allDates = pd.date_range(start=startTime, end = endTime)for oneDate in allDates:
oneDate = oneDate.date().strftime("%Y-%m-%d")
oneDayData = auth2_client.intraday_time_series('activities/heart', base_date=oneDate, detail_level='1sec')
date_list.append(oneDate)
resting_list.append(oneDayData['activities-heart'][0]['value']['restingHeartRate'])# there is more matplotlib code on GitHub
fig, ax = plt.subplots(figsize=(10, 7))ax.plot(date_list, resting_list)
试着用图表显示你的数据。
8.)获取睡眠数据
在不涉及太多细节的情况下,下面的代码获得了每分钟的睡眠数据(final_df)和睡眠摘要信息final_stages_df,即 Fitbit 用户在深度睡眠、浅度睡眠、快速眼动睡眠和觉醒阶段总共花费了多少分钟。

final_stages_df是一个熊猫数据框架,显示了 Fitbit 用户在给定日期的给定夜晚每个阶段的睡眠时间(清醒、深度、浅睡、快速眼动)以及在床上的总时间。

final_df是一个熊猫数据帧,给出日期时间、日期和值。值栏中的 1 是“睡着了”, 2 是醒着的, 3 是真的醒着。

潜在错误
没有名为 gather_keys_oauth2 的模块
这是针对你得到一个类似下面的错误的情况。

要解决这个问题,可以将 gather_keys_oauth2.py 文件放在 python 文件或 jupyter notebook 所在的同一个目录下。你可以在下面看到我的项目目录是如何排列的。

请注意,gather_keys_oauth2.py 与我的 jupyter 笔记本在同一个目录中。这是我们在步骤 3 中下载的 python-fitbit-master 中包含的一个文件。
无法识别 cherrypy,没有名为 CherryPy 的模块
如果你得到一个类似下面的错误,首先关闭你的 jupyter 笔记本或者 python 文件。

您可以使用下面的命令解决这个问题。
pip install CherryPy

Https vs Http 错误
以下错误来自第 2 步,当时我曾试图将回调 url 设置为 https 而不是 http。

HTTPTooManyRequests:请求太多

fitbit 社区论坛有这个问题的答案。使用该 API,您可以每小时为每个用户/fitbit 处理多达 150 个请求。这意味着,如果您有超过 150 天的数据,并且您使用了本教程中步骤 5b 的代码,那么您可能需要等待一段时间才能获得其余的数据。
结束语
这篇文章讲述了如何从单个 Fitbit 获取数据。虽然我已经讲了很多,但是还有一些事情我没有讲,比如说 Fitbit api 如何获得你的步数。Fitbit API 是我仍在学习的东西,所以如果你有贡献,请让我知道。如果你对教程有任何问题或想法,欢迎在下面的评论中或通过推特联系。
用基尼系数评价信用评分模型的性能
基尼系数背后的机制、计算方法、常见陷阱及其主要缺点。
当一个新的信用评分模型诞生时,通常出现的第一个问题是:“它的基尼系数是多少?”。对于一个局外人来说,这听起来一定像是对迪士尼电影《阿拉丁》的奇怪引用。但是,基尼系数或基尼系数是金融业用来评估信用评分模型性能的最受欢迎的指标之一。

照片由 Cesira Alvarado 在 Unsplash 上拍摄
基尼系数是一个指标,表明模型的区分能力,即模型在区分“坏”借款人(将来会违约)和“好”借款人(将来不会违约)方面的有效性。该指标通常用于比较不同模型的质量,并评估它们的预测能力。

FICO 使用基尼系数展示了其信用评分模型的质量。来源: FICO
尽管基尼系数具有共性,但一些从业者并不真正熟悉基尼系数之外的机制,并错误地将其与同名的不同指标相混淆。虽然许多从业者错误地将基尼系数与洛伦兹曲线的摘要、科拉多基尼的不平等度量[1]联系起来,但他们使用的基尼系数大多数时候是 Somers' D ,即 CAP(累积准确度分布)曲线的摘要。
Somers' D 是以 Robert H. Somers 的名字命名的,他在 1962 年提出了这个概念[2]。它是两个变量之间顺序关系的度量。在信用评分模型的情况下,它衡量模型预测(以违约概率或评分表示)与实际结果(违约或不违约)之间的顺序关系。如果模型是有用的,低分(高 PD)应该比高分(低 PD)更与违约相关。
Somers' D 取值在(-1)和 1 之间。(-1)是完美的负序数关系,1 是完美的序数关系。在实践中,Somers' D 为 0.4 的信用评分模型被认为是好的。(此后,我将把萨默斯称为基尼系数)
出于极简主义的考虑,我不会描述计算基尼系数所涉及的数学。相反,我将展示三种不同的方法来导出它。
为了演示每一种方法,我将使用一个样本信用评分模型,该模型是使用逻辑回归和来自 Lending-club 的 10,000 名借款人的数据开发的。
model <- glm(default ~ fico + loan_amnt + annual_inc + home_ownership, family = "binomial", data = data_set)
基于模型的预测——估计违约概率(PD ),我从 1 到 1000 给每笔借款打分;1 代表最低 PD,1000 代表最高 PD。
从 CAP 曲线中提取基尼系数
在我们的上下文中,CAP 曲线旨在捕捉得分(PD)和违约率之间的顺序关系。如果我们的模型在区分好的和坏的借款人方面做得很好,我们将会发现低评分借款人比高评分借款人有更多的违约。CAP 曲线通过从最低分到最高分抽样借款人时累计违约率来捕捉这一概念。
为了构建 CAP 曲线,所有模型的总体需要按照违约的预测可能性排序。也就是说,得分最低的观察值在前,得分最高的观察值在后。然后,我们从头到尾对总体进行抽样,每次抽样后,计算累计违约率。帽曲线的 x 轴代表抽样人口的部分,y 轴代表相应的累积违约率。
如果我们的模型具有完美的区分能力,我们将期望在对一部分观察值进行采样后达到 100%的累积违约率,这等于我们数据中的违约率(下图中的绿线)。例如,如果我们数据中的违约率是 16%,那么在对 16%的观察值进行抽样后,我们将捕获我们数据中的所有违约。相反,如果我们使用随机模型,即以相等分布随机分配分数的模型,累积违约率将始终等于抽样观察的部分(下图中的红线)。

基尼系数被定义为模型曲线和随机模型线(A)之间的面积与完美模型曲线和随机模型线(A+B)之间的面积之比。换句话说,基尼系数是一个比率,代表我们的模型离“完美模型”有多近,离“随机模型”有多远。因此,“完美模型”的基尼系数为 1,“随机模型”的基尼系数为 0。
我的模型获得了低基尼系数 0.26 :

构造洛伦茨曲线,提取科拉多基尼系数,然后得出基尼系数
洛伦兹曲线是 CAP 曲线的逆曲线;它是使用相同的抽样观察和累计违约率机制构建的,但抽样顺序相反(从最高分到最低分)。洛伦兹也有一条对角线,相当于 CAP 随机模型’线,被称为‘等比线’(下图中的红线)。
两条曲线的另一个区别是“完美模型”线。由于洛伦茨曲线是为了捕捉财富的分布而设计的,因此最具歧视性的结果是人口的所有财富都集中在一个观察值中的情况。也就是说,相当于洛伦兹曲线中的“CAP perfect model”线的线是由两条垂直线构成的,这两条垂直线是 x 轴和一条垂直线,垂直线是从 x 轴的末端以 100%的值(下图中的绿线)演变而来的。这条线表示所有累积结果都在最后一次采样观察中的情况。科拉多基尼系数的值被定义为模型曲线和“平等线”之间的面积与“平等线”和 x 轴之间的面积之比。
然而,当使用洛伦兹曲线来评估信用评分模型的区分能力并将其 y 轴指定为累积违约率时,问题出现了。由于 y 轴描述的是二元结果(1 或 0)的集合,因此不存在所有累积违约率都集中在一次观察中的情况。换句话说,在使用洛伦兹曲线评估一个信用评分模型时,不可能达到“完美模型”线。因此,这种评估的合适的“完美模型”线应该调整到人群中的违约率,就像在 CAP 曲线中一样。

因此,为了调整 Corrado Gini 的信用评分模型评估的措施,我们需要从其分母中扣除不可及区域。
最后,为了从科拉多基尼系数的测量中得出基尼系数,我们可以使用以下公式:

我的模型获得了科拉多基尼系数 0.22:

我的样本中的违约率是 16%,因此我的模型的基尼系数可以计算如下:

构建 ROC 曲线,提取 AUC,然后得出基尼系数
计算基尼系数的第三种方法是通过另一种流行的曲线:ROC 曲线。ROC 曲线下的面积,通常称为 AUC,也是评估和比较信用评分模型性能的常用指标。ROC 曲线总结了混淆矩阵中的两个比率:真阳性比率(TPR 或回忆)和假阳性比率(FPR)。
对于给定的阈值,混淆矩阵总结了以下情况的数量:
- 该模型预测会出现违约,而借款人违约了——T4——真的是正数。
- 该模型预测会出现违约,但借款人没有违约——假阳性。
- 该模型预测没有违约,借款人也没有违约——T2——真阴性。
- 模型预测没有违约,借款人违约——假阴性
例如,让我们用 850 分作为我们的阈值,即 850 分以下的借款人被预测为违约,850 分以上的借款人被预测为不违约。

850 阈值的混淆矩阵
真实正比率(TPR)定义为我们的模型捕捉到的违约借款人的数量超过我们数据中违约借款人的总数。假阳性率(FPR)的计算方法是,模型错误预测违约的案例数占非违约案例总数的比例。

ROC 曲线是通过使用源自 1 到 1000 之间阈值的混淆矩阵并驱动它们的 TPR 和 FPR 来构建的。ROC 曲线的 y 轴代表 TPR 值,x 轴代表 FPR 值。AUC 是曲线和 x 轴之间的面积。

令人惊讶的是,正如 Schechtman & Schechtman,2016[3]所示,AUC 和基尼系数之间存在线性关系。因此,要从 AUC 中得出基尼系数,只需使用以下公式:

从业者倾向于在他们的模型验证报告中披露 AUC 和基尼系数。然而,由于这些指标具有线性关系,这些指标的公开对模型质量的评估没有任何价值。
我的模型的 AUC 是 0.63 ,因此基尼系数是这样计算的:

基尼系数的弊端和陷阱
尽管它的共性,基尼系数有一些缺点和陷阱,你应该考虑当使用它来评估和比较信用评分模型。为了极简主义,在这一节中,我将描述一个在试图推导基尼系数时的常见陷阱及其主要缺点。
为了说明这些概念,我将使用一个玩具示例:15 个借款人,2 个“坏”和 13 个“好”,分数从 1(最高 PD)到 10(最低 PD)。
在试图得出基尼系数时,一个常见的陷阱是相同的分数。在大多数情况下(尤其是当使用大型数据集时),信用评分模型将为不同的观察估计相同的分数。当试图使用 CAP 曲线法得出基尼系数时,这种分数重复提出了一个问题。如上所述,导出 CAP 曲线的第一步是根据得分对观察值进行排序,如以下两个示例所示:

这两个例子使用相同的借款人,得分相同。这两个表之间的唯一区别是第二级排序。在示例 1 中,当存在相同分数的情况时(在分数 5 中),首先出现默认的观察值。例 2 正好相反。这种微小的变化会对基尼系数的值产生重大影响,例如,在这种情况下,实施例 1 的基尼系数为 0.67,实施例 2 的基尼系数为 0.38。
为了避免这个陷阱,我建议像例 1 一样进行二次排序,或者简单地使用上述 AUC 方法得出基尼系数。
基尼系数的主要缺点在于,它是一个有序的度量标准,也就是说,它记录了数值的顺序,而忽略了数值之间的距离。基尼系数的这一特征有时会掩盖模型的不良表现。

当根据两个模型预测的分数对借款人进行排序时,我们会得到相同的“违约”列。这表明这两个模型具有相同的基尼系数(0.85)。
当比较由这两个模型预测的分数分布时,我们得到以下图表:

具有相同基尼系数的两个模型的分数分布
两个模型的基尼系数相同。但是,模型 B 只能将借款人分为三种类型:1、2 和 3,而模型 A 能够捕捉所有 10 个级别的风险。这表明,模型 A 比模型 B 对借款人的不同特征更敏感,可以更好地区分不同的风险水平。
为了理解上述特性的重要性,假设模型 A 的所有者和模型 B 的所有者决定设置一个分数,该分数将是他们贷款批准的阈值。即分数低于或等于阈值的借款人的贷款请求将被拒绝,而分数高于阈值的借款人的贷款请求将被批准。两个模型所有者都检查了他们在测试样本上的模型结果,并决定使用获得 100%累积违约率的分数作为他们的阈值。因此,模型 A 的所有者将阈值设置为 6,而模型 B 的所有者将她的阈值设置为 2。通过选择这些分数,模型所有者得到了非常不同的结果:

模型 A 的所有者拒绝了 8 个“好”借款人,批准了 5 个,而模型 B 的所有者拒绝了 12 个“好”借款人,只批准了 1 个。模型 A 的所有者设置的阈值产生 62%的 FPR,而模型 B 的所有者设置的阈值产生 92%的 FPR。
因此,基尼系数无法捕捉模型区分不同风险水平的有效性是一个主要缺陷。
为了克服这一缺点,我建议对模型预测的分布进行眼球测试,如 Gambacorta,Huang,Qiu 和 Wang,2019[4]所述,并使用精确回忆曲线来评估模型在捕捉“坏”借款人和错误预测“好”借款人违约之间的权衡。
总结
- 金融行业中用于评估信用评分模型质量的基尼系数实际上是 Somers' D,而不是 Corrado Gini 的不平等衡量标准。
- 计算基尼系数有三种常用方法:
- 从 CAP 曲线中提取基尼系数。
- 构造洛伦茨曲线,提取科拉多基尼系数,然后得出基尼系数。
- 构建 ROC 曲线,提取 AUC,然后得出基尼系数。
- 在计算基尼系数时,一个常见的陷阱是相同的分数。
- 基尼系数的主要缺点是,它没有捕捉到模型对不同风险水平的敏感性。
参考文献
[1]基尼,C. (1914 年)。再版:关于测量字符的集中性和可变性(2005)。密特隆,LXIII(1),338。
[2]萨默斯,R. (1962 年)。有序变量关联的一种新的非对称度量。《美国社会学评论》, 27 (6),799–811。从 www.jstor.org/stable/209040取回 8
[3]谢克特曼,e .,&谢克特曼,G. (2016)。基尼系数法与 ROC 曲线的关系 (SSRN 学术论文编号 ID 2739245)。纽约罗切斯特:社会科学研究网络。
[4]甘巴科尔塔,李,黄,杨,邱,黄,王(2019)。机器学习和非传统数据如何影响信用评分?来自一家中国金融科技公司的新证据。从 https://www.bis.org/publ/work834.htm取回
对数据帧使用 Pandas Append 函数
数据科学家的 python 教程,用于向已建立的数据帧追加新行

Theodor Lundqvist 在Unsplash【1】上拍摄的照片。
目录
- 资料组
- 熊猫
- 附加
- 教程代码
- 摘要
- 参考
资料组
本分析和教程中用于熊猫追加函数的数据集是一个虚拟数据集,创建该数据集是为了模拟具有文本和数字特征的数据帧。请随意使用您自己的 csv 文件,包含文本和/或数字列,以遵循下面的教程。
熊猫
Pandas【2】是数据科学家和机器学习工程师最常用的库之一。它主要用于构建模型的探索性数据分析步骤,以及模型结果的即席分析。它还包含几个函数,包括 append 函数。
附加
虽然我之前写了一篇关于 pandas 查询函数的文章[3 ],其中提到了添加多行,但我认为强调 append 函数本身的好处是有用的。append 函数通过向现有数据帧添加新行来工作。这些行是利用 Series 函数创建的,该函数存储的值将被追加到 index 参数中列出的列的顺序中。
附加功能的一些好处包括:
- 处理熊猫数据帧
- 与熊猫系列功能配合使用
- 向现有数据添加新数据的快速方法
教程代码
在下面的代码片段中,虚拟数据缺少一些信息。为了解决这个问题,我求助于 append 方法。使用 append 函数,我可以轻松地添加新行,而不会丢失原始数据帧的数据和完整性。首先,我导入了 pandas 库并创建了别名‘PD’——这个别名有助于以简写格式使用 pandas 库。接下来,我从现有的 csv 文件中读取数据帧,引用它在本地驱动器上的位置和路径。我打印出数据帧,看看前五行是什么样子。下一步是设置 append 函数的代码。使用 Series 函数按顺序将值(在本例中为文本或数字)作为列表放置,然后在各自的列上对其进行索引,新行被写出并保存为‘rows’。
实际的 append 函数本身相当简单;将这些行作为第一个参数传递,然后将' ignore_index' 设置为'True【T3]',这样就不会使用索引标签。最后,我使用 tail 函数检查我的行是否正确,该函数从 append 代码返回最后三行的数据帧。
下面是我使用的所有代码的截图:

追加 3 个新行的示例。作者截图[4]。
这里是代码【5】便于参考。请随意导入您自己的数据和引用您自己的文件路径,以及更改系列中的数据值,以查看在您的 python 环境或 jupyter 笔记本中追加是如何工作的。
# All the python code below for use:
# import library
import pandas as pd# read in your dataframe
df = pd.read_csv('/Users/data.csv')# print out your first five rows
df.head()# write out new rows
rows = [pd.Series([200, 200, 30,'Yellow','Label_1'], index=df.columns),
pd.Series([200, 120, 40,'Red','Label_2'], index=df.columns),
pd.Series([120, 40, 200,'Red','Label_3'], index=df.columns)]# append the multiple rows
new_df = df.append(rows, ignore_index=True)# check the newest 3 rows you made
new_df.tail(3)
要查看 python 格式的代码,而不是截图或写出来的代码,下面是嵌入的要点 [5]:
GitHub 要点来自作者【5】。
摘要
append 函数是向数据帧添加新数据的一种简单快捷的方法。还引用了 Series 函数,以便保存以后要追加的行。在最终创建数据科学和机器学习模型之前,我希望本教程对您创建数据和在数据框架上执行探索性数据分析有所帮助。感谢您的阅读!如上所述,请随意阅读我的类似的文章,它讨论了熊猫的查询功能。
参考
[1] T.Lundqvist , Unsplash (2017)
[2]熊猫,熊猫 (2020)
[3] M.Przybyla,条 (2020 年)
[4] M.Przybyla,截图(2020 年)
[5] M.Przybyla,要点 (2020)
在日常生活中使用熊猫数据框架
在这篇文章中,你将知道如何在日常生活中使用熊猫数据框。

B 在开始之前,让我们快速复习一下,Pandas 是一个 python 库,它为 python 编程语言的数据分析工具提供了优秀的、简单易用的信息结构。下面的文章将帮助你提高对熊猫图书馆的理解水平。别忘了看一看。事实上,这是官方文件。值得一读。
pandas 是一个快速、强大、灵活且易于使用的开源数据分析和操作工具,构建于…
pandas.pydata.org](https://pandas.pydata.org/)
帮助您入门的必要设置
有两个重要的必要步骤,你需要注意,以执行顺利运作:
- IDE 或者 Jupyter 笔记本:不管你用的是像 PyCharm 、Visual Studio Code、 IntelliJ IDEA 还是 Jupyter 笔记本 这样的 IDE 环境,其实都不重要。我个人用的是Google Colabwith isJypter 笔记本环境 因为它良好的文档化特性。我们可以更清晰、更准确地解释事物。
- 如果这是你第一次安装 Python,不要担心,我会支持你的。遵循下面给出的文章,并遵循有效安装 Python 的方法。
[## Python 3 安装和设置指南-真正的 Python
开始使用 Python 的第一步是在您的机器上安装它。在本教程中,您将学习如何…
realpython.com](https://realpython.com/installing-python/)
注意 → 如果你正在使用Google Colab或者任何Jupyter 笔记本 环境,那么你可以跳过 Python 安装这一步。
方案
让我们在这里理解一个场景,并使用 Pandas DataFrame 提出一个解决方案。
每个人都喜欢购物。不是所有时候你都在同一家商店购物,你是基于折扣和优惠购物的(我就是这样)。现在,很明显,记录购物细节是一个很好的做法,比如商店名称、位置、数量、日期等。”
为了存储和操作日期,我知道你会使用“”Microsoft Excel”但是这里的要点是我们将使用“ Pandas DataFrame ”,它使用起来更容易也更有趣。别担心,我会告诉你怎么做。
别误会, 微软 Excel “就是它在的地方。但是对于所有的 Python 爱好者来说,这是理解熊猫并使其更上一层楼的最好例子。
解决办法
让我们使用 Pandas DataFrame 来解决上面的场景。为了做到这一点,你需要遵循一定的步骤来避免犯错误。我会提供一个使用熊猫的系统方法,这样你就可以在你即将到来的项目中机械地使用它。
导入 Pandas 数据帧库
我们用走样的概念,用熊猫当**pd**。所以在后面的步骤中,与其每次都使用**pandas** ,我们可以直接告诉**pd**。
import **pandas** as **pd**
感谢引入混叠概念的人。
第一次将购物细节存储在列表中
对于那些不了解 Python 列表的人来说。请在这里查找。因为我现在住在加拿大萨斯喀彻温省的里贾纳。所以大多数杂货店和你的相比,可能相似,也可能不同。请记住这一点。这真的没关系,你可以根据自己的选择输入数据。
**date** = ['1-9-20', '3-9-20', '3-9-20', '6-9-20', '9-9-20']**storeName** = ['Walmart', 'Real Canadian Superstore', 'Co-op Food Store', 'Sobeys', 'M&M Food Market']**storeLocation** = ['Gordon Road', 'Albert Street', 'Albert Street', 'Quance Street', 'Gordon Street']**amount** = [55.65, 21.62, 7.10, 15.56, 5.85]
现在,我说的“第一次是什么意思,就是以后说假设你以后继续去买菜,而不是手动把值存储在列表里。这个列表将会增长到一列火车那么长。另外,不建议这样做。所以我在下面的部分写了一个处理这种情况的方法。
创建一个 Pandas 数据框架来存储所有的列表值
在这里,因为我们将所有的值都存储在一个列表中,所以让我们将它们放在一个数据帧中。我们可以使用**pd.DataFrame()**并传递值,这种情况下是所有的列表。
df = pd.DataFrame({'Date': date,
'Store Name': storeName,
'Store Location': storeLocation,
'Amount Purchased': amount})df
执行此操作后,您将得到一个漂亮的数据帧,如下所示

塔努·南达·帕布拍摄的照片
将购物细节作为用户的输入——用于将来的访问
正如我所说的,将所有值存储在一个列表中并不是一个很好的做法。这样,我们自动使列表增长。处理这种情况有一个简洁而可爱的方法。首先,让我们从输入中提取输入,并将它们存储在临时变量中,如下所示:
**dateNew** = input("Enter the date in dd-mm-yy format ---> ")
**storeNameNew** = input("Enter the name of the store ---> ")
**storeLocationNew** = input("Enter the location of the store ---> ")
**amountNew** = float(input("Enter the total amount purchased ---> "))
所以第二天,我的意思是**10–9–20**我和戈登·拉姆齐一起去杂货店买了些香料,开玩笑我是一个人去的。以下是新店的详细情况。
**Enter the date in dd-mm-yy format** ---> 10-9-20
**Enter the name of the store** ---> India Market
**Enter the location of the store** ---> Albert Street
**Enter the total amount purchased** ---> 24.68
将新数据追加到现有列表中
这是一个明显的步骤,因为我们需要将新数据(购物细节)追加到现有的数据帧中。我们可以在**append()** Python 的帮助下做到这一点。
date.**append**(dateNew)
storeName.**append**(storeNameNew)
storeLocation.**append**(storeLocationNew)
amount.**append**(amountNew)
通过数据帧显示更新的结果
这一步很简单,因为我们所做的只是将上述步骤中所有更新的结果显示为一个数据帧。类似于上面所示的“创建熊猫数据帧以存储所有列表值”步骤。
df = pd.DataFrame({'Date': date,
'Store Name': storeName,
'Store Location': storeLocation,
'Amount': amount})df
执行这段代码后,将提示您上一步中添加的新杂货店的更新结果

塔努·南达·帕布拍摄的照片
这就对了,你已经成功地解决了这个问题。您可以继续添加更多的数据(购物详情)并保持每月或每年明智的。
奖金
您可以在现有数据帧上执行的漂亮技巧
下面是一些你可以在空闲时间执行或做的提示和技巧。因为它们很容易理解。让我来解释一下。
绘制条形图
绘制数据图以获得更好的可读性始终是一个好习惯。现在为了绘图,让我们使用**Amount** 栏,这样我们就可以在绘图的帮助下知道到目前为止我们已经花了多少钱。这可以通过使用如下所示的**df.plot.bar()**来完成
注意:切记在熊猫 中只能绘制数字数据
df.plot.bar()
在编写这一行代码时,您可以看到如下所示的美丽情节:

塔努·南达·帕布拍摄的照片
删除整行/整列
假设我们需要删除最后一行,因为条目中有一个错误,所以可以按如下方式执行。现在要删除一整行,我们需要在熊猫中使用**drop()**,如下所示:
df = df.**drop**([5])
df
这里的值**5** 是最后一行的索引值。通过执行这个,我们得到

塔努·南达·帕布拍摄的照片
删除列就像删除行一样,你所要做的就是传递要删除的列名,这种情况下就像**df.drop([column_name, axis = 1])**``**axis = 1**在这里很重要。假设您需要删除数据帧中的列**Amount** ,那么您只需要说:
df = df.**drop**(['Amount'], axis = 1)
df
通过执行该操作,您现在可以看到列**Amount** 已经被删除。

塔努·南达·帕布拍摄的照片
修改特定条目
假设我们需要更新数据帧中的特定条目,在这种情况下,存储名称为**M&M Food Market**的第 4 个索引在其存储位置有一个错误(错误条目)**Gordon Street**,我们需要将其更正为**Gordon Road**。为此,只需使用:
df['Store Location'][4] = "Gordon Road"
df
我们需要知道特定条目的索引来更新条目,所以在执行上述操作之后,我们得到了更新的结果

塔努·南达·帕布拍摄的照片
结论
好了,祝贺你们,你们已经成功地阅读/实现了这篇美丽的文章“在日常生活中使用熊猫数据框架”。现在,这还不是结束,我们可以使用 DataFrame 的许多其他方法或函数,并使其更上一层楼。我在这里只涉及了基础知识。如果你们发现了一些新的或有创意的东西,请在下面评论。我希望你们今天学到了新东西。敬请关注更多更新,下次再见。再见,祝你愉快,注意安全!
使用 R 中的预测能力评分
最近一个关于预测能力得分的帖子吸引了许多数据科学家的注意。让我们看看它是什么以及如何在 R 中使用它

最近几个月 Florian Wetschoreck 在《走向数据科学》的媒体频道上发表了一篇文章,由于其极具煽动性的标题:“RIP correlation”,这篇文章在 LinkedIn 上吸引了许多数据科学家的注意。介绍预测能力评分"。让我们看看它是什么,以及如何在 r 中使用它。
预测能力得分的定义
预测能力得分 (PPS)是一个标准化指数(范围从 0 到 1),它告诉我们有多少变量 x (无论是数字还是分类)可用于预测变量 y (数字还是分类)。PPS 指数越高,变量 x 在预测变量 y 时越具有决定性。
PPS 与相关系数在概念上的相似性是显而易见的,尽管存在以下差异:
- PPS 还检测 x 和y之间的非线性关系
- PPS 不是对称指数。这就意味着 PPS( x 、 y ) ≠ PPS( y 、 x )。换句话说,并不是说如果 x 预测 y ,那么 y 也预测 x 。
- PPS 允许数字和分类变量。
基本上,PPS 是一个非对称的非线性指数,适用于所有类型的变量进行预测。
在幕后,它实现决策树作为学习算法,因为它们对异常值和糟糕的数据预处理具有鲁棒性。
分数是在 sci kit-learncross _ val _ score函数给出的默认 4 重交叉验证的测试集上计算的,并根据目标变量定义的问题类型(回归或分类)由不同的度量给出:
- 回归:给定 y 的“原始”值基线,将平均绝对误差(MAE)归一化为目标变量的中值。
- 分类:给定作为目标变量最常见值或随机值计算的 y 的“原始”值基线(有时,随机值比最常见值具有更高的 F1),归一化到[0,1]区间的加权 F1。
你可以深入了解 Python 代码的细节,这要归功于预测能力评分项目在 Github 上作为开源发布的事实。
皮尔逊相关与 PPS
这两个指数来自不同的领域,必须进行基本区分:
- 皮尔逊相关性由两个数值变量之间的归一化协方差给出。协方差取决于两个变量相对于各自均值的偏差,因此它是一种统计度量。给定两个数值变量,Pearson correlation 是一个描述性指标在数学上得到很好的定义,它给出了描述变量之间关系的最佳线性函数的拟合优度。
- PPS 试图通过应用决策树估计来解决相关性分析中仅测量线性相关性和仅测量数字变量的问题。它来源于该评估的性能指标。在撰写本文时(版本 1.1.0),默认情况下,它从输入数据集中获得一个 5000 行的随机样本(如果需要,可以分层),以加快计算速度(样本的大小可以使用适当的参数进行修改)。没有进行任何调整来获得决策树的最佳模型参数。因此,存在过度配合或配合不足的可能性。此外,由于算法固有的随机性(随机种子可用于保证结果的可重复性),在同一数据集上每次运行时,PPS 结果可能不同。所以,PPS 可能不准确。但是它的目的不是给出一个精确的分数,而是给出依赖的一般概念和一个快速的结果。
这两个索引都应该在勘探数据分析阶段使用。正如同一位作者所说:
对于在数据中寻找预测模式而言,PPS 明显优于相关性。然而,一旦发现了模式,相关性仍然是传达发现的线性关系的一个很好的方式。
PPS 使用案例
作者在他的文章中列出了几个 PPS 可能增加价值的用例:
- 查找关联找到的每一个关系,甚至更多
- 特征选择
- 检测信息泄漏
- 通过将 PPS 矩阵解释为有向图,找到数据中的实体结构。
如何在自己的研发项目中使用 PPS
【https://github.com/paulvanderlaken/ppsr】更新:2020 年 12 月下旬研发出 R 专用包:
如前所述,项目 ppscore 是开源的,它是用 Python 开发的。R 中目前没有 ppscore 的移植项目,那么如何在 R 脚本中使用 ppscore 函数呢?由于为 Python 和 R 之间的互操作性而创建的一些库,可以将 R 的强大功能与 Python 的编程能力结合起来使用,反之亦然。在其他库中,有两个库使用最广泛:
- rpy2: 嵌入在 Python 进程中的 R running 接口
- reticulate: 由于在 R sessions 中嵌入了 Python 会话,这是一套用于 Python 和 R 之间互操作性的综合工具

图 1——得益于 rpy2 和 reticulate,Python 和 R 之间的互操作性
在我们的场景中, reticulate 将允许从 R 脚本中调用 ppscore 函数。
为 ppscore 准备单独的 Python 环境
首先,如果你没有在你的机器上安装 Python,我建议安装Miniconda(Anaconda 的一个小的引导版本,只包括 conda、Python 和少数其他有用的包),如果是 Windows 机器,让安装程序将 Python 安装路径添加到你的 path 环境变量中。
Python 开发者通常使用虚拟环境。虚拟环境是一种工具,通过创建包含特定 Python 版本的 Python 安装以及许多附加包的自包含目录树,有助于将不同项目所需的依赖关系分开。Conda 是一个帮助管理环境和包的工具。
在这种情况下,将创建一个新的 Python 环境,以便在其上安装 ppscore 所需的包。您可以使用下面的 R 脚本为 ppscore 库准备一个新的 Python 环境:
# Install reticulate
if( !require(reticulate) ) {
install.packages("reticulate")
}# Load reticulate
library(reticulate)# List current python environments
conda_list()# Create a new environemnt called 'test_ppscore'
conda_create(envname = "test_ppscore")# Install the ppscore package and its dependencies using pip into the test_ppscore environment.
# Requirements are listed here: [https://github.com/8080labs/ppscore/blob/master/requirements.txt](https://github.com/8080labs/ppscore/blob/master/requirements.txt)
conda_install(envname = "test_ppscore", packages = "pandas", pip = TRUE)
conda_install(envname = "test_ppscore", packages = "scikit-learn", pip = TRUE)
conda_install(envname = "test_ppscore", packages = "ppscore", pip = TRUE)# Check if the new environment is now listed
conda_list()# Make sure to use the new environment
use_condaenv("test_ppscore")# Import the ppscore Python module in your R session
pps <- import(module = "ppscore")
Python 模块和类中的函数和其他数据可以通过$操作符访问(以及与 R 列表交互)。导入的 Python 模块支持代码完成和内联帮助。例如, pps 模块中的matrix功能可通过以下方式访问:

图 2 — pps 模块的矩阵功能
如果你想了解更多关于网纹的信息,你可以通过它的主页。
《泰坦尼克号》预测因子和可变重要性的 PPScore 热图
从作者包含在资源库中的示例开始,可以使用matrix函数复制 Titanic PPScore 热图:

图 3 — PPScore 热图
给定目标“幸存”的变量重要性可通过适当过滤由matrix函数给出的输出(预测器之间的完全相互作用)获得。但是也可以直接使用predictor函数直接得到相同的结果,注意在两个函数中使用相同的random_seed参数,以避免得到不同的结果。
给定目标变量“存活”的变量重要性图如下:

图 4 —使用 PPS 的可变重要性图
用于获得上述图的 R 代码如下:
library(reticulate)
library(readr)
library(dplyr)
library(ggplot2)heatmap <- function(df, x, y, value,
main_title = "Heatmap", legend_title = "Value",
x_title = "feature", y_title = "target") {
x_quo <- enquo(x)
y_quo <- enquo(y)
value_quo <- enquo(value)
res <- ggplot( df, aes(x = !!x_quo, y = !!y_quo, fill = !!value_quo) ) +
geom_tile(color = "white") +
scale_fill_gradient2(low = "white", high = "steelblue",
limit = c(0,1), space = "Lab",
name="PPScore") +
theme_minimal()+ # minimal theme
# theme(axis.text.x = element_text(angle = 45, vjust = 1,
# size = 12, hjust = 1)) +
coord_fixed() +
geom_text(aes(x, y, label = round(!!value_quo, 2)), color = "black", size = 4) +
theme(
axis.text.x = element_text(angle = 45, vjust = 1,
size = 12, hjust = 1),
axis.text.y = element_text(size = 12),
panel.grid.major = element_blank(),
panel.border = element_blank(),
panel.background = element_blank(),
axis.ticks = element_blank()
) +
xlab(x_title) +
ylab(y_title) +
labs(fill = legend_title) +
guides(fill = guide_colorbar(barwidth = 1, barheight = 10,
title.position = "top", title.hjust = 1)) +
ggtitle(main_title)
return(res)
}lollipop <- function(df, x, y,
main_title = "Variable Importance",
x_title = "PPScore", y_title = "Predictors",
caption_title = "Data from Titanic dataset") {
x_quo <- enquo(x)
y_quo <- enquo(y)
res <- ggplot(df, aes(x=!!x_quo, y=forcats::fct_reorder(!!y_quo, !!x_quo, .desc=FALSE))) +
geom_segment( aes(x = 0,
y=forcats::fct_reorder(!!y_quo, !!x_quo, .desc=FALSE),
xend = !!x_quo,
yend = forcats::fct_reorder(!!y_quo, !!x_quo, .desc=FALSE)),
color = "gray50") +
geom_point( color = "darkorange" ) +
labs(x = x_title, y = y_title,
title = main_title,
#subtitle = "subtitle",
caption = caption_title) +
theme_minimal() +
geom_text(aes(label=round(!!x_quo, 2)), hjust=-.5, size = 3.5
) +
theme(panel.border = element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
axis.line = element_blank(),
axis.text.x = element_blank())
return(res)
}df <- read_csv("[https://raw.githubusercontent.com/8080labs/ppscore/master/examples/titanic.csv](https://raw.githubusercontent.com/8080labs/ppscore/master/examples/titanic.csv)")df <- df %>%
mutate( Survived = as.factor(Survived) ) %>%
mutate( across(where(is.character), as.factor) ) %>%
select(
Survived,
Class = Pclass,
Sex,
Age,
TicketID = Ticket,
TicketPrice = Fare,
Port = Embarked
)use_condaenv("test_ppscore")pps <- import(module = "ppscore")# PPScore heatmap
score <- pps$matrix(df = df, random_seed = 1234L)score %>% heatmap(x = x, y = y, value = ppscore,
main_title = "PPScore for Titanic's predictors", legend_title = "PPScore")# Variable importance
vi <- pps$predictors( df = df, y = "Survived", random_seed = 1234L)vi %>%
mutate( x = as.factor(x) ) %>%
lollipop( ppscore, x,
main_title = "Variable Importance for target = 'Survived'",
x_title = "PPScore", y_title = "Predictors",
caption_title = "Data from Titanic dataset")
PPS 和与各种分布的相关性
Denis Boigelot 在 Wikimedia Commons 上发布了一张关于 Pearson 关联示例的非常著名的图片,并分享了用于获得这张图片的 R 代码:

图 Boigelot 分布的皮尔逊相关性
从这段代码开始,可以计算相同分布的 PPScore。这里是 X→Y 的结果:

图 Boigelot 分布中“x 预测 y”的 PPScore
这里是 Y→X 的结果:

图 Boigelot 分布中“y 预测 x”的 PPScore
很明显,在写这篇文章的时候
PPScore 很难在原点线存在最小离散点的情况下确定线性关系(如前面图片中黄色和红色突出显示的)。
这些案例表明
为了避免得出错误的结论,使用相关指数来检查线性是多么重要。
我与作者分享了这些结果,他证实他们正在研究解决线性关系问题的解决方案。他还在这个环节分享了为什么 PPScore 1.1.0 不能很好地处理线性关系的原因。
相反,PPScore 在 Pearson 相关性不足的一些分布上提供了良好的结果(如前面图片中绿色突出显示的)。
用于获得上述 Boigelot 发行版的 R 代码如下:
# Install packages
pkgs <- c("dplyr","ggplot2","ggpubr","mvtnorm")for (pkg in pkgs) {
if (! (pkg %in% rownames(installed.packages()))) { install.packages(pkg) }
}# Load packages
library(mvtnorm)
library(dplyr)
library(ggplot2)
library(ggpubr)
library(reticulate)# Functions
MyPlot <- function(xy, xlim = c(-4, 4), ylim = c(-4, 4), eps = 1e-15,
metric = c("cor", "ppsxy", "ppsyx")) {
metric <- metric[1]
df <- as.data.frame(xy)
names(df) <- c("x", "y")
if (metric == "cor") {
value <- round(cor(xy[,1], xy[,2]), 1)
if (sd(xy[,2]) < eps) {
#title <- bquote("corr = " * "undef") # corr. coeff. is undefined
title <- paste0("corr = NA") # corr. coeff. is undefined
} else {
#title <- bquote("corr = " * .(value))
title <- paste0("corr = ", value)
}
subtitle <- NULL
} else if (metric == "ppsxy") {
pps_df <- pps$matrix(df = df, random_seed = 1111L)
value <- pps_df %>%
filter( x == "x" & y == "y" ) %>%
mutate( ppscore = round(ppscore, 1) ) %>%
pull(ppscore)
title <- bquote("pps"[X%->%Y] * " = " * .(value))
subtitle <- NULL
} else if (metric == "ppsyx") {
pps_df <- pps$matrix(df = df, random_seed = 1111L)
value <- pps_df %>%
filter( x == "y" & y == "x" ) %>%
mutate( ppscore = round(ppscore, 1) ) %>%
pull(ppscore)
title <- bquote("pps"[Y%->%X] * " = " * .(value))
subtitle <- NULL
}
ggplot(df, aes(x, y)) +
geom_point( color = "darkblue", size = 0.2 ) +
xlim(xlim) +
ylim(ylim) +
labs(title = title,
subtitle = subtitle) +
theme_void() +
theme( plot.title = element_text(size = 10, hjust = .5) )
}MvNormal <- function(n = 1000, cor = 0.8, metric = c("cor", "ppsxy", "ppsyx")) {
metric <- metric[1]
res <- list()
j <- 0
for (i in cor) {
sd <- matrix(c(1, i, i, 1), ncol = 2)
x <- rmvnorm(n, c(0, 0), sd)
j <- j + 1
name <- paste0("p", j)
res[[name]] <- MyPlot(x, metric = metric)
}
return(res)
}rotation <- function(t, X) return(X %*% matrix(c(cos(t), sin(t), -sin(t), cos(t)), ncol = 2))RotNormal <- function(n = 1000, t = pi/2, metric = c("cor", "ppsxy", "ppsyx")) {
metric <- metric[1]
sd <- matrix(c(1, 1, 1, 1), ncol = 2)
x <- rmvnorm(n, c(0, 0), sd)
res <- list()
j <- 0
for (i in t) {
j <- j + 1
name <- paste0("p", j)
res[[name]] <- MyPlot(rotation(i, x), metric = metric)
}
return(res)}Others <- function(n = 1000, metric = c("cor", "ppsxy", "ppsyx")) {
metric <- metric[1]
res <- list()
x <- runif(n, -1, 1)
y <- 4 * (x^2 - 1/2)^2 + runif(n, -1, 1)/3
res[["p1"]] <- MyPlot(cbind(x,y), xlim = c(-1, 1), ylim = c(-1/3, 1+1/3), metric = metric)
y <- runif(n, -1, 1)
xy <- rotation(-pi/8, cbind(x,y))
lim <- sqrt(2+sqrt(2)) / sqrt(2)
res[["p2"]] <- MyPlot(xy, xlim = c(-lim, lim), ylim = c(-lim, lim), metric = metric)
xy <- rotation(-pi/8, xy)
res[["p3"]] <- MyPlot(xy, xlim = c(-sqrt(2), sqrt(2)), ylim = c(-sqrt(2), sqrt(2)), metric = metric)
y <- 2*x^2 + runif(n, -1, 1)
res[["p4"]] <- MyPlot(cbind(x,y), xlim = c(-1, 1), ylim = c(-1, 3), metric = metric)
y <- (x^2 + runif(n, 0, 1/2)) * sample(seq(-1, 1, 2), n, replace = TRUE)
res[["p5"]] <- MyPlot(cbind(x,y), xlim = c(-1.5, 1.5), ylim = c(-1.5, 1.5), metric = metric)
y <- cos(x*pi) + rnorm(n, 0, 1/8)
x <- sin(x*pi) + rnorm(n, 0, 1/8)
res[["p6"]] <- MyPlot(cbind(x,y), xlim = c(-1.5, 1.5), ylim = c(-1.5, 1.5), metric = metric)
xy1 <- rmvnorm(n/4, c( 3, 3))
xy2 <- rmvnorm(n/4, c(-3, 3))
xy3 <- rmvnorm(n/4, c(-3, -3))
xy4 <- rmvnorm(n/4, c( 3, -3))
res[["p7"]] <- MyPlot(rbind(xy1, xy2, xy3, xy4), xlim = c(-3-4, 3+4), ylim = c(-3-4, 3+4), metric = metric)
return(res)
}output <- function( metric = c("cor", "ppsxy", "ppsyx") ) {
metric <- metric[1]
plots1 <- MvNormal( n = 800, cor = c(1.0, 0.8, 0.4, 0.0, -0.4, -0.8, -1.0), metric = metric );
plots2 <- RotNormal(200, c(0, pi/12, pi/6, pi/4, pi/2-pi/6, pi/2-pi/12, pi/2), metric = metric);
plots3 <- Others(800, metric = metric)
ggarrange(
plots1$p1, plots1$p2, plots1$p3, plots1$p4, plots1$p5, plots1$p6, plots1$p7,
plots2$p1, plots2$p2, plots2$p3, plots2$p4, plots2$p5, plots2$p6, plots2$p7,
plots3$p1, plots3$p2, plots3$p3, plots3$p4, plots3$p5, plots3$p6, plots3$p7,
ncol = 7, nrow = 3
)
}#-- Main -------------------------------------
use_condaenv("test_ppscore")pps <- import(module = "ppscore")output( metric = "cor" )
output( metric = "ppsxy" )
output( metric = "ppsyx" )
结论
Florian Wetschoreck 提出的预测能力得分(PPS)指数试图帮助数据科学家给出提示,在项目的 EDA 阶段找到两个变量之间的任何类型的关系,无论它们是数字还是类别。PPS 可能不准确,但它的目的不是给出一个精确的分数,而是给出两个变量之间依赖关系的一般概念和一个快速的结果。
使用 Boigelot 分布表明,PPS(当前版本为 1.1.0)能够识别相关性指数无法识别的非线性关系。相反,PPS 似乎很难识别线性关系,即使这些点与原点线的离散度很小。
虽然 PPScore 是用 Python 开发的,但由于有了 reticulate ,它的函数也可以用在 R 脚本中。
更新。一个 R 专用的包已经在 2020 年 12 月下旬开发出来,你可以在这里找到:https://github.com/paulvanderlaken/ppsr
所有的代码都可以在这个 Github 库上找到。
为你的神经网络使用正确的维度
当建立你的人工神经网络时,一个令人沮丧的错误是把向量和矩阵的形状弄错了。这是理解底层数学运算的简要指南,并阐明了要传入的维度。

用什么形状?(作者图片)
更新— 8 月 8 日 该系列已简化为三集系列。
目标
如果你刚刚开始机器学习,你应该已经遇到了基本的模型,如顺序网络。这些全连接神经网络(FCNN)是在转向更复杂的架构之前理解基本深度学习架构的完美练习。
初学者会发现,通过 Keras 和 TensorFlow 等高级库很容易开始这一旅程,技术细节和数学运算都是从你这里抽象出来的。好处是能够快速启动学习之旅,而不会被数学所困,但如果你不知道自己在做什么,它会很快带来问题。
如果你像我一样,你可能遇到过代码不能按你期望的方式工作的情况,通过反复试验,你似乎能够让它工作。还是做到了?
为了建立基础,我决定参加 Coursera 的在线培训(神经网络和深度学习由 deeplearning.ai 提供),并希望分享我的学习。主要目标是强迫自己写作,在这个过程中,确保我已经真正理解了这些概念。
结构
这是 3 部分系列的第一部分,我们将逐步建立对以下领域的基本理解:
- 为你的神经网络使用正确的维度
- 神经网络是如何学习的?
- 为二元分类构建您自己的神经网络
在本系列结束时,您将理解序列神经网络背后的基本数学,并编写自己的前向和后向传播例程来执行二进制分类。
让我们从第一个主题开始,理解并使用向量和矩阵的正确维数。
为什么选择正确的尺寸很重要
像 TensorFlow 和 Keras 这样的高级库从用户那里抽象出复杂的数学运算。这使得用户更容易开始深度学习的旅程。另一方面,如果没有正确的基础,深度学习有时会感觉像“黑魔法”。
它只是工作。我想。
凭直觉,你可能知道为什么,但不明白到底发生了什么,也无法验证答案。这可能是一个危险的开始,因为如果你没有掌握好基础知识,你可能会发现自己在更复杂的架构中碰壁了。
当我发现自己使用试错法调整代码只是为了让它工作时,我决定我必须从基础开始。
我选择这个主题作为起点,因为我总是对向量和矩阵维数感到困惑。在一些文献中,我看到变量的不同排序。有时,我看到一个换位操作,有时我看不到。
让我们定义一些术语和一致的表示方式,首先使用简单的单个神经元,这将是监督学习 FCNN 架构的构建块。

具有 3 个输入的单神经元(图片由作者提供)
在上图中,我们有 3 个 输入 ,每个输入代表一个独立的特征,我们用它来训练和预测输出。单个神经元的每个输入都有一个与其相关的 权重 ,它形成了正在被训练的参数。有多少个输入信号就有多少个权重进入一个神经元,并且每个神经元有单个 偏差 (实数)。我们使用下面的 线性函数 来计算一个值 z ,然后将其传递给一个 非线性激活函数 g(z) 。
在定义了基本术语后,我们现在将其扩展到一个简化的 FCNN,其中一个隐藏层包含 4 个神经元和 1 个输出节点。

简单的顺序架构,带有一个 4 节点隐藏层(图片由作者提供)
Python 中向量和矩阵的实现
在 Python 实现中,计算通常使用矢量化算法进行,如 numpy 和 pandas,这消除了低效的for循环。
由于这些基础计算,正确设置向量和矩阵的维数至关重要。如果做得不正确,你可能会陷入两种情况中的一种。
第一,如果行和列不匹配,代码会拒绝运行,导致运行错误。第二种更危险的结果是,如果形状是正确的,但方向是错误的,则会导致不正确的输出(例如,如果在 4 元素层中有 4 个输入,则尺寸可能会被交换,从而导致错误的输出)。
在上面提到的 Coursera 模块中,我从吴恩达那里学到的一个教训是,首先要草拟出你的神经网络的架构。拥有一个标准的约定和对基础数学运算的理解有助于你轻松地决定是否需要移调和/或整形。它还帮助您更好地理解输出矩阵如何映射到您的神经网络架构。
让我们从通过 FCNN 发送的单个训练示例开始,上面有单个 4 神经元隐藏层。稍后我们将把它扩展到 m 训练组。
单一训练示例
我个人发现,记住以下几条经验法则会更容易记住结构:
- 使用建筑布局的结构作为输入向量的形状。如果我们看下面的图表,将单个训练示例的形状记为一个 单列 (就像您将如何绘制它一样),在本例中是(3,1)。
- 请记住,权重和偏差仅取决于层中单元的数量。它与训练样本的数量无关。换句话说, m (训练集的数量)不改变权重和偏差矩阵。
- 权重的形状在垂直方向上与该层中神经元的数量相同。在水平方向上,它将基于输入的数量。

使用建筑的形状来模拟你的矢量形状(图片由作者提供)
了解矩阵乘法的排序对于确认形状和排序是否正确很有用。
(a, b) . (b, c) will result in (a, c)
下面的代码片段有助于理解约定,您可以自己尝试。
尝试形状和点操作(来源:作者)
矩阵乘法如下图所示。我们现在包括了偏置向量,它与输出的形状相同,只需要是一列(或者一行,如果你愿意的话)。如果提供 m 训练集,它将被广播到正确的形状。

计算输出 Z(来源:作者)
把它堆在一起
我了解到,一旦你有了正确的基础,就很容易把训练的例子堆在一起。我遵循了吴恩达在他的课程中建议的惯例,用 m 来表示训练样本的数量。
记住,权重和偏差不受训练集数量的影响。它们的结构是由体系结构决定的,这就是为什么我先花时间解释单个集合。
如果您遵循了我的约定,我们现在只需要将输入向量扩展成一个有 m 列的矩阵。我发现训练集的水平方向很容易记住,因为不同的输入被发送到网络中。
因此,我们现在有:

m 训练集的矩阵乘法
如您所见,权重和偏差矩阵和向量保持不变。通过扩展输入向量以覆盖 m 个训练集,我们简单地相应扩展输出矩阵。
结论
我希望这篇介绍性的文章有助于揭开矩阵形状的一些令人沮丧的秘密,也为你提供验证和检查你的结果的基础。
在我将在接下来的几周内撰写的后续文章中,我们将逐步构建简单的 FCNN 来预测 MNIST 人物。
这篇文章是基于我自己的学习,以及我试图阐明我的理解而写的。如果我的理解有错误,请随时纠正我,这样我们可以在这个过程中一起学习。
在 Scala 中使用 Spark 聚合器类
注意:如果你已经理解了Aggregator类,并且只是想看看它是如何在 Scala 中实现的例子,请随意跳到“使用我们的聚合器”部分。

马库斯·温克勒在 Unsplash 上拍摄的照片
类型安全聚合
它们是什么?
你在机场的行李认领处等你的行李。你内心的混乱正在加剧,因为大多数其他乘客现在已经收到了他们的行李,你肯定不想丢失你的衣服、化妆品或你托运的那瓶豪华精酿啤酒。就在你开始绝望的时候,你看到了它:一个大而亮的橙色行李箱,显然是你的。你兴高采烈地把它捡起来,微笑开始掠过你的脸庞——直到你注意到从拉链上滴下来的水滴。然后又是一个。另一个。虽然袋子本身和你托运时一样,但是里面的东西都湿透了。你不禁好奇,这是什么时候的事?是飞机里的低压吗?它是从手推车上掉下来的吗?我应该用气泡包装吗?不幸的是,你真的没有任何办法知道。虽然你应该用气泡包装。
当数据科学家在 Spark 中对数据集执行聚合时,也会有类似的苦恼,只是输出数据集的内容以某种方式显示为无效;也许输入和输出都是完整的数据集,但输出的内容类型有些不正确。聚合的内部机制以及哪里出了问题通常只能在大量的检测工作之后才能得到诊断。即使这样,也不能保证输入数据的未来实例不会给聚合带来另一个问题。
然而,就像添加更多的气泡包装以防止瓶子在您的手提箱中破碎一样,我们可以使用 Spark 的Aggregator类创建强类型化的自定义聚合。这个类允许数据科学家准确地指定 worker 节点执行什么转换,如何跨节点组合数据,以及每个步骤在预期的输入和输出类型方面应该包含什么。结果是对聚合转换是如何执行的有了深入的了解,并且有了一种自动的方法来诊断可能出现的任何类型问题。换句话说,这是一个万无一失的方法,可以让你下次坐飞机时省下一瓶啤酒。
何时使用类型安全聚合
决定是否对 Spark 数据集执行类型安全聚合可能是一个困难的决定。一方面,很容易就豁出去了,把输入输出类型声明为Dataset,就到此为止。根据我的经验,这适用于数据科学家可能遇到的许多探索和分析实例,因为数据和类型验证可以在笔记本或脚本环境中动态执行。
另一方面,在有些情况下(例如生产中的自动化 ML 管道,为其他数据科学家创建 API ),您需要确保转换不会在没有警告的情况下产生意外的和潜在的错误结果。这些是您想要在 Spark 中使用聚合器类的情况。该类允许数据科学家在执行某种类型的自定义聚合时识别输入、中间和输出类型。
第一次遇到的时候发现 Spark 的Aggregator类有些混乱。有几个步骤,看似不用的参数,网上例子有限。我见过的大多数例子都倾向于描述简单的用例,比如对一组整数求和或者计算平均值。虽然这些对于同义反复来说肯定是有用的,但是它们并没有提供对可能需要执行的其他常见聚合的更多了解。因此,我在这里的目的是演示Aggregator类的两种用法,人们可能会在编写数据预处理应用程序时实际使用它们,并逐行分解它们。
安`T3例
让我们假设您有以下家装商店的零售数据
我们的五金店数据集。
并希望以类型安全的方式执行以下操作:
- 统计每个
retailCategory中有多少独立客户 - 收集唯一的
productID数组,按retailCategory分组 - 将上述数据转换成包含前两步的
Dataset
让我们假设我们想要使用下面的通用结构来实现我们的聚合器:
这将导致数据科学家在使用我们的库时调用以下 API:
val myValue = DoSomething.transformData(…)
请注意,我们正在创建一个对象,其中包含我们希望用来执行聚合的方法,以及我们希望在该方法中使用的两个聚合器。这与我在网上看到的许多其他例子不同,原因有二。首先,我们试图创建一个 API 供数据科学家在数据预处理阶段使用,而不是创建一个工作流来调用某个mainApp对象。其次,我们正在构建两个以上的由同一个转换使用的聚合器。
我们的待办事项列表上的第一项是导入Aggregator类、ExpressionEncoder类,为上面显示的输入数据模式创建一个case class,并为我们的输出模式创建另一个case class:
import org.apache.spark.sql.expressions.Aggregator
import org.apache.spark.sql.catalyst.encoders.ExpressionEncoder// input schema
case class RetailByCustomer(customerID: String,
productID: String,
retailCategory: String)// output schema
case class AggRetailByCategory(retailCategory: String,
customerCount:Int,
productSet: Set[String])
注意:我们需要导入ExpressionEncoder类来为Set[String]类型定义我们自己的编码器,因为这不是 Scala 中的默认编码器。
现在我们开始写我们的聚合器。我现在将完整地展示这两种方法,然后逐行分解。
我们的两个聚合器。
正如您可能从值名中推断的那样,我们的第一个聚合器提供了不同客户的计数。这是通过将每个客户添加到一个Set来完成的,因为集合只包含任何给定值的一个实例。在这种情况下使用不同的计数也很重要,以避免在传入数据可能包含重复值或没有映射到任何retailCategory的productID值的情况下出现过度计数。现在,让我们看看第一行:
val distinctCustomerCountAggregator:
TypedColumn[RetailByCustomer, Int] = new
Aggregator[RetailByCustomer, Set[String], Int]
这里我们声明一个值,该值将返回一个具有预期输入(RetailByCustomer —一个表达式)和预期输出(Int —一个表达式编码器)的 TypedColumn 。我们将这个值设置为一个新的Aggregator,并定义输入类型(RetailByCustomer)、中间类型(Set[String])和输出类型(Int)。也就是说,这个聚合器将接受在我们的RetailByCustomer case 类中定义的某种类型,执行某种类型的转换,这将产生一个Set[String]类型,并在聚合后返回类型Int的某个值。
现在来看函数的实质。但首先,这里有一只小狗来打破单调,让你的灵魂稍微清醒一下。

如果您正在阅读本文,您可能知道 Spark 是一个以分布式方式执行数据转换的框架,因此您可能知道有一个主节点向工作节点执行器发送任务来执行数据转换。但是,在对整个数据集执行聚合时,您会向工作节点发送哪些任务来协同工作呢?
我喜欢从高层次考虑这个问题从单个工人节点开始。Aggregator类将任务发送给单个工作节点(以及该作业的所有其他活动工作节点)上的执行器,说明如何开始聚合:
override def zero: Set[String] = Set[String]()
也就是说,在我们的例子中,每个 worker 节点应该用一个类型为String的空集开始聚合。
接下来,Aggregator类告诉每个 worker 节点如何处理它在内存中的数据:
override def reduce(es: Set[String], rbc: RetailByCustomer):
Set[String] = es + rbc.customerID
我们的reduce定义中的指令告诉每个 worker 节点期待来自上一步的“空集”es和上面定义的RetailByCustomer case 类rbc的输入。此外,每个 worker 节点上的这个转换的输出将会产生一个期望的类型Set[String]。最后,定义了在每个 worker 节点上发生的实际转换。每个存储为类型RetailByCustomer.customerID(我们定义为String)的内存值被添加到现有的空集,从而产生一个包含不同客户 id 的Set[String]。
为了在实践中看到这一点,假设我们的数据已经由retailCategory进行了分区,这样关于“照明”和“园艺”的信息存储在一个 worker 节点上,“空调”和“清洁”存储在第二个节点上,而“油漆”存储在第三个节点上。每个节点上的结果集应该如下所示:

既然已经在每个工作者节点上执行了寻找唯一的customerID的转换,那么Aggregator类必须指示工作者节点如何相互交互,以便在节点上寻找唯一的customerID s 。这让我们想到了merge的定义:
override def merge(wx: Set[String], wy: Set[String]):
Set[String] = wx.union(wy)
这里,我们将两个预期输入定义为类型Set[String];一个来自“工人 X”(wx),一个来自“工人 Y”(wy)。同样,我们将预期的输出类型定义为Set[String]。最后,Aggregator类必须给出每个 worker 节点在与其他节点交互时应该做什么的指令。在这种情况下,我们告诉每个工人将来自reduce步骤的输出与另一个工人的reduce输出合并(即返回两个集合之间的所有元素)。
虽然信息在工作节点之间传递的方式有很多细微差别,但是为了理解Aggregator类,我们可以将merge步骤的输出可视化如下:

假设如果我们的 workers 节点没有联合结果,它们将返回什么。
这将导致类型Set[String]的输出“集合(“001”、“005”、“003”、“004”、“002”)”。
下一步finish,为驾驶员提供执行动作的指令:
override def finish(reduction: Set[String]): Int = reduction.size
这里,我们将期望的输入类型定义为我们的union ed Set[String],期望的输出类型定义为Int。我们还通过调用size方法定义了对Set[String]输入采取的动作,该方法返回一个表示集合中项目数量的整数。在我们的例子中,这会返回5。
最后一步是为我们的缓冲(或中间)步骤和输出步骤定义Encoder:
override def bufferEncoder: Encoder[Set[String]] =
implicitly(ExpressionEncoder[Set[String]])override def outputEncoder: Encoder[Int] =
implicitly(Encoders.scalaInt)
distinctCustomerCountAggregator的输出是一个Int,它在 Scala 中有一个预定义的编码器。因此,我们可以在定义我们的outputEncoder时简单地调用Encoders.scalaInt,并隐式地将它传递给其他定义。然而,尽管Set和String在 Scala 中都有预定义的编码器,但是Set[String]没有。因此,我们必须使用第一步中导入的ExpressionEncoder类定义我们自己的Set[String]编码器。
productIDAggregator实际上与distinctCustomerCountAggregator非常相似,除了一个关键的区别:我们期望的是Set[String]类型的输出,而不是Int。这将改变几个步骤。
练习 : 如果你正在全文阅读这篇文章,我鼓励你在继续下一步之前,思考哪些步骤需要改变以促进这一点(提示:有 3 个)。
第一个区别是将TypedColumn和预期Aggregator输出类型都改为Set[String]:
val productIDAggregator:
TypedColumn[RetailByCustomer, Set[String]] =
new Aggregator[RetailByCustomer, Set[String], Set[String]]
接下来的三个步骤对于两个聚合器来说实际上是相同的:我们指示工作节点创建空集,向这些集合添加productID s,并联合它们自己得到的集合。
然而,第二个区别出现在finish定义中。我们希望将预期的输出类型从Int改为Set[String],并删除size方法,因为我们只想返回实际的一组productID s:
override def finish(reduction: Set[String]): Set[String] = reduction
最后,我们需要改变outputEncoder。由于在这种情况下我们没有真正在类型之间转换,我们的bufferEncoder和outputEncoder将是相同的:
override def bufferEncoder: Encoder[Set[String]] =
implicitly(ExpressionEncoder[Set[String]])override def outputEncoder: Encoder[Set[String]] =
implicitly(ExpressionEncoder[Set[String]])
就是这样!我们已经创建了两个执行两种不同功能的聚合器!
使用我们的聚合器
为了在实践中使用这些,让我们看一下完整的代码:
编译并发布上述代码后,数据科学家可以在构建数据转换管道时调用以下代码(或者您可以在文件底部调用它):
import com.wherever.code.is.PreprocessDataval retailData = Seq(
("001", "zk67", "Lighting"),
("001", "gg89", "Paint"),
("002", "gg97", "Paint"),
("003", "gd01", "Gardening"),
("003", "af83", "A.C."),
("003", "af84", "A.C."),
("004", "gd77", "Gardening"),
("004", "gd73", "Gardening"),
("005", "cl55", "Cleaning"),
("005", "zk67", "Lighting"),
).toDF("customerID", "productID", "retailCategory")val transformedRetailData = PreprocessData.createArrayAndCount(retailData)
这里显示的val transformedRetailData的输出应该类似于

结论
- 类型安全聚合允许数据科学家指定如何进行转换,如何在节点内和节点间组合数据,以及每个步骤在预期的输入和输出类型方面应该包含什么。
- 使用 Spark 的
Aggregator类来执行类型安全转换。典型的用例是在生产级环境中,编写 API,或者计划重复使用聚合。 - 理解 Spark 中发生的事情对于理解
Aggregator类如何工作是很重要的。
伸手
感谢阅读这篇文章!如有任何意见/问题,请随时联系我们或通过 Twitter 。你也可以访问我的个人网站:【mfjackson.github.com。我一直在学习并寻求提高我在数据科学方面的知识,所以如果我做了任何错误的陈述,或者你知道比本文更好的方法,请告诉我!!
利用拓扑文本分析迎接新冠肺炎公开研究挑战
我对新冠肺炎·卡格尔挑战科学白皮书的分析。这项研究是帮助病毒学、药学和微生物学专家找到问题答案的第一步。

使用 DataRefiner 平台的拓扑图可视化
编者按: 走向数据科学 是一份以数据科学和机器学习研究为主的中型刊物。我们不是健康专家或流行病学家,本文的观点不应被解释为专业建议。想了解更多关于疫情冠状病毒的信息,可以点击 这里 。
3 月 12 日,白宫和一个领先的研究团体联盟准备了新冠肺炎开放研究数据集,以应对冠状病毒疫情。该数据集由超过 44,000 篇学术文章组成,包括超过 29,000 篇关于新冠肺炎、新型冠状病毒和相关冠状病毒的全文。这个免费提供的数据集提供给全球研究界,以应用自然语言处理和其他人工智能技术的最新进展,产生新的见解,支持正在进行的抗击这种传染病的斗争。这个挑战的 Kaggle 页面可以在这里找到:https://www . ka ggle . com/Allen-institute-for-ai/CORD-19-research-challenge
召集所有主题专家
如果您是病毒学、药学或微生物学方面的专家,我们很乐意让您免费访问 DataRefiner 平台上的以下分析,以帮助加快科学研究和应对病毒。请在 ed@datarefiner.com 与我们联系。

该数据集是由艾伦人工智能研究所与陈·扎克伯格倡议,乔治敦大学安全和新兴技术中心,微软研究院和国家医学图书馆-国家卫生研究院合作创建的,并与白宫科技政策办公室合作
挑战
挑战赛为机构群体提出了许多问题:
关于传播、潜伏期和环境稳定性,我们知道些什么?我们对该病毒的自然史、传播和诊断了解多少?关于感染预防和控制,我们学到了什么?
我们对新冠肺炎风险因素了解多少?
我们从流行病学研究中学到了什么?
我们对病毒的遗传、起源和进化了解多少? 我们对人与动物界面的病毒起源和管理措施了解多少?
我们对非药物干预了解多少? 关于非药物干预的公平性和合规性障碍,大家都知道些什么?
我们对疫苗和疗法了解多少?
关于疫苗和疗法的研发和评估工作,已经发表了什么?
我们对诊断和监控了解多少?
数据预处理
我们在分析中使用了文件的摘要和全文。该数据集中的文档通常只有摘要或全文。将它们融合在一起有助于展现全貌。

来自 kaggle 页面的挑战数据质量仪表板
科技论文的摘要通常相当复杂,分析它们的一种方法是将它们分解成句子。在这种情况下,每个句子都是一小段信息,这对于自动分析来说应该足够了。
这是其中一个摘要的例子:
传染因子的基本繁殖数是在一个未被感染的人群中,一个病例在传染期内可以产生的平均传染数。众所周知,由于一些方法问题,包括不同的假设和参数选择、使用的模型、使用的数据集和估计期,对这一数字的估计可能会有所不同。随着新型冠状病毒(2019-nCoV)感染的传播,发现繁殖数量有所变化,反映了冠状病毒爆发的传播动态以及病例报告率。“由于控制策略的显著变化(随着时间的推移一直在变化),以及由于检测技术的引入,这些技术得到了快速改进,能够缩短从感染/症状发作到诊断的时间,从而更快地确认新冠状病毒病例,我们之前对 2019-nCoV 传播风险的估计需要修订。”
为了我们的分析,我们把它分成句子:
- 感染因子的基本繁殖数是在一个未受感染的原始人群中,一个病例在感染期内可能产生的平均感染数
- 众所周知,这一数字的估算可能因若干方法问题而有所不同,包括不同的假设和参数选择、所用模型、所用数据集和估算期
- 随着新型冠状病毒(2019-nCoV)感染的传播,发现繁殖数量发生变化,反映了冠状病毒爆发的传播动态以及病例报告率
- 由于随着时间的推移,控制策略发生了显著变化,并且由于检测技术的引入得到了快速改进,从而能够缩短从感染/症状发作到诊断的时间,从而更快地确认新冠状病毒病例,因此我们之前对 2019-nCoV 传播风险的估计需要修订
正如你所看到的,每一句话实际上足以表达研究人员至少部分回答一个问题所需的一条信息。我们总共有 291281 个句子——这对分析来说足够了。
句子分析
所有的句子都进行了分析,并显示为下面的拓扑图,图上的每个点都是一个或多个句子,相似的句子彼此靠近放置。聚类是自动形成的,代表文本中不同的主题。

数据集中所有句子的拓扑图
在我们训练模型时,除了自动聚类,我们还配置了它,以提取包含“起源”、“稳定性”、“进化”、“风险”和“传播”等关键词的句子。我们使用半监督方法在地图上手动分离这些集群。
让我们来看看“起源”集群,我们可以从中提取什么知识。

“起源”分类的热门关键词
这些热门关键词是从句子中自动提取的,我们在这里显示前 20 个,其余的将用于查找以最佳方式描述集群的热门摘要句子。

为源聚类自动选择的前几个句子
描述聚类“起源”的顶级句子也被自动识别,并且相当准确。
让我们来看看另一个集群——这一次是完全自动形成的——“冠状病毒 sars / sars 冠状病毒/冠状病毒 mers”。

“冠状病毒 sars / sars 冠状病毒/冠状病毒 mers”聚类的热门关键词

用户调查自动发现的集群“冠状病毒 sars / sars 冠状病毒/冠状病毒 mers”的顶级摘要句子
分析与“传输”关键字相关的句子作为单独的地图
除了对一整套句子进行分析之外,该系统还能够处理与特定关键字相关的句子,例如“transmission”。下面是与关键词“传输”相关的 8198 个句子的图:

“传输”关键字的映射
该数据集不仅包括冠状病毒白皮书,还包括大量关于其他近期病毒的论文,如“SARS”(严重急性呼吸综合征相关冠状病毒)、“MARS”(中东呼吸综合征相关冠状病毒)、“TGEV”传染性胃肠炎病毒和其他一些病毒。我们分别审查了不同的组群,以了解关于这种疾病和类似疾病传播的所有关键信息。

“传染病/方法使用/使用数学”群集的描述

“传染性胃肠炎/细胞/病毒”群集的描述

“疾病爆发/控制工作/传染病”组的描述

“估计概率/模型/速率/传输速率”群集的描述

“控制措施/减少/人/ sars”组的描述

“传染病/数字接触”集群的描述
该系统自动识别关键词簇,并在其中分割数据集。聚类越接近,相似的术语和主题就越多。这有助于理解高级分组并在数据中找到元聚类。
结论
文本的拓扑分割有助于研究人员将大量文本信息中的知识系统化。在执行分段和分析每个数据段时,整个数据量被分成簇,这简化了数据的分析和理解。识别出的关键词和总结句子有助于找到最有代表性的信息,并加快寻找答案的速度。
本文的分析是使用 DataRefiner 平台执行的。我们已经在许多公司使用这项技术,不仅用于文本分析,还用于用户活动和物联网细分。如果您想了解更多信息或预订演示,请联系我们。
【https://datarefiner.com/feed/covid-challenge】原文:https://datarefiner.com/feed/covid-challenge
DataRefiner.com是一家总部位于英国的公司,专门从事用户活动、传感器或文本等复杂数据的分析和细分。DataRefiner 平台是本文讨论的多年提炼方法的结果,但它被广泛应用于各种行业,包括航空、社交网络、欺诈检测等。如需了解更多关于您所在行业的信息,请通过 ed@datarefiner.com 联系我们
- 我们对诊断和监控了解多少?
关于系统的、整体的诊断方法(从公共卫生监督的角度到能够预测临床结果)已经发表了什么?
通过 Keras 在 Google Colab 上使用 TPU

罗曼·维涅斯在 Unsplash 上的照片
今天,我在温习一门关于情感分类的旧 NLP 课程,突然感觉到在一些大型数据集上尝试一下的冲动。我玩得很开心,也看到了 Colab 上的免费 TPU 到底有多快。
速度快了 10 倍,但过程并不简单。包括代码变更(大部分是样板文件),因此这篇文章。
作为第一步,我只是想建立一个简单的模型来编译和提供一些预测。我选择了 Kaggle 上的sensition 140 数据集。该数据集有 160 万条带有积极和消极情绪标签的推文。它的大小是 288 MB,这对于我的目的来说是很好的。我为训练模型编写的代码非常简单,因为我正在做非常基本的预处理。
训练时间——几乎每个时代 1 小时 30 分!这对于最初的模型来说是行不通的。是时候测试一下 Colab 上提供的免费 TPU 了。
我最初以为这只是一个简单的设置变化。所以我进入编辑菜单中的笔记本设置,要求一个 TPU 硬件加速器。训练仍然需要一个多小时,所以很明显没有提供 TPU。在浏览 TPU 文档时(这里:使用 TPU ),很明显我们必须明确设置什么是计算分布策略并在它下面构建我们的模型。以下文本中的解释,以及相关的样板文件:
- 首先,我们必须明确要求在代码中使用 TPU。Colab 和真实的 GCP 云 TPU 是不同的,所以必须小心。
*import tensorflow as tf
#Get a handle to the attached TPU. On GCP it will be the CloudTPU itself
resolver = tf.distribute.cluster_resolver.TPUClusterResolver(tpu=’grpc://’ + os.environ[‘COLAB_TPU_ADDR’])**#Connect to the TPU handle and initialise it
tf.config.experimental_connect_to_cluster(resolver)
tf.tpu.experimental.initialize_tpu_system(resolver)*
接下来,我们设定分销策略
*strategy = tf.distribute.experimental.TPUStrategy(resolver)*
- 之后,我们创建模型
*with strategy.scope():
model = create_model()#Build your model
model.compile(optimizer=…)#Set your parameters*
- 然后用通常的方式训练它
*model.fit(…)*
这现在起作用了。过去需要 90 分钟的训练,现在只需 9.5 分钟就能完成。毫无疑问非常有效和高效,尽管主题相当神秘。
问题是:它所需要的只是一些样板代码,那么它为什么没有隐藏在一些 Keras 抽象下呢?也许在未来的版本中会有。
使用基于转换器的语言模型进行情感分析

来源: shutterstock
如何轻松击败最先进的情感模型
情感分析对许多企业来说是有用的,有助于评估客户对公司或特定产品的情绪。它的任务是根据文本的极性对文本进行分类,即识别作者对主题的感觉是积极的、消极的还是中性的。
为了能够大规模地自动进行情感分析,我们需要在带注释的数据集上训练模型。本文将展示如何通过使用开源框架 FARM 以快速简单的方式将最先进的 transformer 模型应用于情感分析,从而大幅超越当前的基准测试(提高约 5 个百分点)。
数据集
如前所述,我们需要带注释的数据来监督训练一个模型。为此,我使用了两个共享任务的数据集: SemEval 2017 和 Germeval 2017 。
seme val 数据集由英语推文组成,而 GermEval 数据集包含来自不同社交媒体和网络来源的关于 Deutsche Bahn (德国铁路公司)的德语文本。两个数据集合的文本根据其极性(即正、负或中性)进行标记。
用 FARM 微调变压器模型
当涉及不同的 NLP 任务时,如文本分类、命名实体识别和问答,像 BERT 这样的 Transformer 模型是当前最先进的。有多种不同的预训练模型。对于 Germeval 2017 数据集,使用了 deepset 的 GermanBERT ,因为它在一系列任务中表现出了强大的性能。
然而,对于 SemEval-2017 数据集,由于训练数据由英语文本组成,但结果模型的目的是评估德国客户对文本的情感,因此尝试了零镜头学习方法,因此需要多语言模型。为此,使用了 XLM-罗伯塔-拉奇,它受过 100 种不同语言的训练。如果你想更多地了解 XLM-罗伯塔,我强烈推荐这篇博客文章。
由于预训练模型仅被训练来捕捉对语言的一般理解,而不是特定下游 NLP 任务的细微差别,所以我们需要使这些模型适应我们的特定目的。为了实现这一点,使用了用于调整表示模型(FARM) 的框架。FARM 允许轻松调整变压器模型以适应不同的 NLP 任务。为了实现构建一个可靠的情感分类器的目标,我遵循了 FARM 的 doc_classification 示例。下面的代码片段展示了我如何通过几个步骤使 GermanBERT 适应情感分析的任务:
数据处理
为了能够加载训练实例,数据集需要在 csv 文件中,其中每一行都由训练示例及其标签组成。TextClassificationProcessor加载并转换数据,以便建模组件可以使用它。为此,我们需要指定可能的标签集、包含训练集和测试集的数据目录,以及包含每个训练实例的标签的列的名称。这里,我们还必须指出我们的模型中使用的最大序列长度。
转换后的数据随后被传递到DataSilo。其目的是存储数据,并逐批提供给模型。因此,必须在这一步指定批量大小。
建模
下一步是定义模型架构。首先,我们需要决定我们想要微调的语言模型。在这种情况下,我们使用bert-base-german-cased。然后,我们必须为我们的特定任务选择正确的预测头。因为我们想使用离散值根据文本的情感对文本进行分类,所以我们需要选择一个TextClassificationHead。最后一步是在语言模型上堆叠预测头,这是由AdaptiveModel完成的。
培养
既然已经加载了数据并且定义了模型架构,我们就可以开始训练模型了。首先,我们必须初始化一个优化器。在这里,我们设置学习率和我们想要训练模型的时期数。不仅初始化一个优化器,而且初始化一个学习率调度器。默认情况下,所有训练步骤的前 10%是学习率的线性热身。
最后,我们可以将所有组件输入到Trainer,开始训练并保存生成的模型以备后用。在 Germeval-17 数据集上微调 GermanBERT 在 Tesla V100 16GB GPU 上耗时不到 16 分钟;根据 SemEval-17 的数据调整 XLM-罗伯塔需要 28 分多一点的时间。
结果
对于 Germeval 2017 共享任务,使用微观平均 F1 分数评估提交模型的性能。提供了两个不同的测试集:一个包含与训练数据集相同时期的 tweet(同步测试集),另一个测试集包含稍后时期的 tweet(历时测试集)。
最佳提交(Naderalvojoud et al. 2017)在共时测试集上取得了 74.9%的微观平均 F1 分,在历时测试集上取得了 73.6%的微观平均 F1 分。使用 GermanBERT 和 FARM 训练的模型比这些分数高出 5%以上,在同步测试集上实现了 80.1%的微观平均 F1 分数,在历时测试集上实现了 80.2%的微观平均 F1 分数。
Germeval-2017 数据集的评估结果
然而,对 SemEval 2017 共享任务的提交的性能是通过宏观平均召回来评估的。在那里,最佳提交(placend 2017,Baziotis 等人 2017)实现了 68.1%的宏观平均召回率。同样,使用 XLM-罗伯塔-大型和农场训练的模型比这些提交的模型高出 5%以上,实现了 73.6%的宏观平均召回率。
SemEval-2017 数据集的评估结果
因为使用 XLM-罗伯塔而不是单语模型的原因是将该模型应用于德国数据,所以也在 Germeval-17 测试集上评估了 XLM-罗伯塔情感模型。这里,我们在同步测试集和历时测试集上分别获得了 59.1%和 57.5%的微观平均 F1 值。这一表现比分数分别为 65.6%和 67.2%的基本多数类基线差。出现这种情况的一个原因可能是两个数据集的类别分布差异很大。虽然 Germeval 数据集中的大多数实例被标记为中性,几乎没有包含正面情绪的案例,但 SemEval 数据集中的大多数类别是正面。
另一个问题可能是两个数据集包含主题不同的文本。Germeval 数据集就其主题而言非常有限,主要包含来自不同社交媒体和网络来源的关于德国铁路公司的文本。相反,SemEval 数据集不受主题限制。
Germeval-2017 和 SemEval-2017 数据集的类别分布
所有上述结果都是使用以下超参数获得的:
用于训练模型的超参数
结论
这篇博客文章展示了我们如何使用 transformer 模型来训练我们自己的情感模型。我们看到,在框架农场的帮助下,可以轻松地对预训练模型进行微调,我们甚至能够击败不同共享任务的排行榜。如果你有兴趣亲自尝试一下 GermanBERT 情绪模型,你可以通过 huggingface 的模型中心访问它。
使用无监督学习生成艺术家推荐
Scikit 的最近邻 Python 学习者
在本文中,我将探索使用无监督的机器学习来使用 Spotify 的数据生成艺术家推荐。虽然有许多算法可以用于此目的,但这里考虑的是最近邻学习器,使用 Python 中的 Scikit Learn 实现。
让我们开始吧!
1。数据
我们将使用来自 Spotify Web API 的数据(可以在这里找到)。该数据集包含了从 1921 年到 2020 年的数据,涵盖了大约 27000 多位艺术家!
对于每个艺术家来说,有几个领域可以描述他们的音乐:响度、听觉、舞蹈性、流行度等等。(关于这些意味着什么的细节可以在这里找到)。我们将只使用这些特征(没有其他信息,如艺术家属于哪个流派)来训练我们的模型,然后要求它为类似于我们指定的艺术家生成推荐。
2。算法
虽然许多无监督学习算法可以用于这个问题,但我们将考虑一个最简单的算法——最近邻学习器。该算法本质上扫描 n 维空间(n 是我们给模型的“特征”或特性的数量)来寻找最接近我们指定的点。例如,如果我们只给模型响度和流行度,并要求它推荐 5 位与 Eminem 相似的艺术家,它将浏览所有艺术家并找到响度和流行度值最相似的 5 位艺术家。
形象化的一个有用方法是想象我们生成了一个散点图,响度在 x 轴上,流行度在 y 轴上。每个艺术家的 x-y 坐标可以被认为是他们的“地址”,我们的问题归结为找到“最近的邻居”(即在这个二维空间中离阿姆“距离”最小的艺术家)。这里我们将在 11 维空间中工作,但同样的原理也适用。
幸运的是,我们不需要费心去弄清楚如何编写代码来实际实现这个算法,它已经为我们完成了,我们可以直接使用 Python 中的 SciKit Learn 包。

作者在二维平面图像中寻找“最近的邻居”
3。流程
首先,我们将数据加载到 Python 中。可以从 Kaggle 下载 csv 格式的文件。
二。然后,我们将决定应该将哪些“特性”或特征传递给模型。
三。这些特征中的一些可能具有偏斜分布。例如,大多数歌曲的“器乐性”值很低,这意味着我们有许多艺术家的器乐性得分接近 0,但相比之下,少数艺术家的得分要高得多(见下图)。机器学习算法通常不能很好地处理这种分布,因为异常值会在数据中引入大量“噪声”。为了解决这个问题,我们将使用 SciKit Learn 的 StandardScaler 函数来缩放我们的数据,这将减少这些异常值对我们数据的影响。更多关于定标器的细节可以在这里找到。

工具性显示了一个偏斜的分布:我们需要通过作者来修正这个图像
四。在我们的数据被缩放后,我们准备好训练我们的模型。我们将数据传递给最近的邻居学习者(文档)。虽然我们有机会调整该学员考虑的许多技术参数,但对于这个问题,我们将只让每个参数取默认值。我们将告诉我们的模型的唯一事情是我们想要多少推荐。
动词 (verb 的缩写)就是这样!我们现在将要求我们的模型扫描所有 27,000+位艺术家,并推荐与我们指定的艺术家相似的艺术家。
让我们开始吧:
import pandas as pd
import numpy as npdata_artist = pd.read_csv('./Spotify/data_by_artist.csv') *#This is a datafram with 27621 rows (each row represents one artist) and 15 columns (each column has a unique feature)**## Let's see the 10 most popular artists according to Spotify's algorithm*data_artist.sort_values(by='popularity',ascending=False).head(10)['artists']

Spotify 上最受欢迎的十位艺术家
我们现在将编写一个函数来准备数据、缩放数据、训练模型并生成建议:
features= list(data_artist.columns[0:12]) # We select only the features we want to pass to the model
df = data_artist[features]def getArtist_recommendations(data,artist,numArtists):
X = data.iloc[:,1:] #This contains all our features
Y = data.iloc[:,0] #This contains the name of the artists
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X) #We have now scaled our data to reduce the impact of outliers
from sklearn.neighbors import NearestNeighbors
recommender = NearestNeighbors(n_neighbors=numArtists+1).fit(X_scaled) #Training the model took a single line of code :)
distances, indices = recommender.kneighbors(X_scaled)
output = pd.DataFrame(indices)
output['Artist'] = Y
recommendations = output.loc[output.Artist==artist,output.columns!='Artist']
Y[list(recommendations.values[0])][1:]
return(Y[list(recommendations.values[0])][1:])
就是这样!让我们测试一下模型的表现。我们将要求它推荐 10 位与阿姆、肯德里克·拉马尔和阿肯相似的艺术家。
getArtist_recommendations(df,'Eminem',10)
getArtist_recommendations(df,'Kendrick Lamar',10)
getArtist_recommendations(df,'Akon',10)

推荐与阿姆相似的艺术家-作者图片

推荐与 Kendrick Lamar 相似的艺术家-作者图片

推荐与阿肯相似的艺术家-作者图片
快乐聆听:)
利用加权 K-均值聚类确定配送中心位置

Marcin Jozwiak 在 Unsplash 上拍摄的照片
另一个你可能不知道的 K-means 算法的修改版本的用例
背景
我们所有人都一定熟悉快餐连锁公司,如肯德基或麦当劳,因为它们在我们周围有大量的分店。这些名字以快速提供即食餐而闻名,这要归功于提前散装烹饪,有时还包括预包装。作为其商业模式的一个暗示,快餐连锁店需要快速的供应链执行,特别是对于配料。这是为了确保它们总是新鲜的提供给顾客。
关于这一点,你有没有想过他们如何能及时提供这些易腐的原料?这并不是一个听起来简单的问题,因为涉及的出口数量巨大。在这方面,仓库所描绘的配送中心起着至关重要的作用。
快餐连锁店的仓库是供应商卸下货物的地方,这些货物随后被包装,最后被分发到商店。通常,一个仓库负责周围几个城市的所有经销店。因此,快餐连锁店会有几个仓库分布在他们的经营区域。
在本文中,我们考虑一家全球快餐公司,该公司最近计划进入印度尼西亚市场。管理层预计分两个阶段进行扩张:第一阶段和第二阶段。第一阶段将只覆盖爪哇岛,而其余地区将在第二阶段处理。为此,我们将帮助确定其仓库的位置,特别是在第一阶段
我们将帮助一家快餐店确定遍布爪哇岛的仓库位置。
回忆(标准)K 均值聚类
K 均值聚类是一种将数据划分为 K 个不同聚类的算法。关于算法如何工作的高级视图如下。给定 K 个簇(通常是随机的)的初始(这意味着来自 K 个质心),该算法在以下两个步骤之间迭代:
- 计算所有数据点和现有 K 个质心之间的距离,并相应地将每个数据点重新分配给其最近的质心。
- 基于这个新的聚类分配,通过取数据点的平均值来计算新的 K 个质心。
执行上述迭代,直到质心不随迭代而改变(算法收敛)或者满足特定的停止标准(例如,触发最大迭代次数)
读者可能已经知道,该算法中的数字 K 是一个超参数(即预定义的数字)。在实践中,我们可以通过使用例如肘方法来选择最佳 K,我们将在本文后面使用并解释该方法。
此外,在选择距离度量时也有一定的自由度,而不是在原始算法中使用的标准欧几里德距离。如果有必要或者看起来更合适,我们可以使用其他距离度量,如哈弗森距离(我们将在文章中使用的距离)或曼哈顿距离。
使用加权 K 均值聚类解决问题
让我们回到我们的问题!确定仓库的位置可以看作是寻找相应服务分支的聚类的质心。因此,这是 K-均值聚类,特别是加权 K-均值聚类的一个很好的用例。我们所说的“加权”一会儿就明白了。
我们首先已经知道,这家快餐公司将在爪哇开设分店。因此,我们可以通过首先确定分支机构的位置来解决这个问题,我们可以用 Java 中所有城市/地区的经度-纬度(相当于(x,y))来合理地近似这个位置。使用这些城市中心的数据点,我们实际上可以运行标准的 K-means 聚类来解决这个问题。然而,这种方法没有考虑到这样一个事实,即一些城市确实比其他城市更大,这意味着对供应的原料有更高的需求量。
标准的 K-均值方法没有考虑到这样一个事实,即一些城市确实比其他城市大,这意味着对供应的原料有更高的需求量。
考虑到这一点,我们还包括人口数据,作为配料需求的代理。这些人口数据将成为相应城市(数据点)的权重,瞧!我们已经有了运行加权 K 均值聚类算法的设置。
为了解释权重对算法的影响,假设我们现在正在处理不同大小的数据点(由它们的权重表示)。我们可以进一步看到大小与数据点所拥有的重力成比例。因此,权重越大,数据点将质心拉得越近。总之,与标准算法的根本区别在于质心计算,现在使用加权平均值,而不是标准平均值。

计算质心的标准(左)与加权(右)平均值的图示
数据来源
我们感谢 benangmerah GitHub 帐户的这个存储库提供了所考虑的城市的经纬度坐标数据。原来,爪哇岛上有 119 个城市,从西部的奇勒贡到靠近巴厘岛的左部的班尤万吉。此外,我们还参考了印度尼西亚统计局(BPS)2018 年爪哇省各城市的人口数据。
哈弗森距离
请注意,我们将使用经纬度,它代表地球的球面——与(x,y)中的标准平面 2D 坐标系相反。因此,欧几里德距离不是这里使用的最佳距离度量。相反,我们将使用哈弗线距离,这是球面上一个合适的距离度量。
下面的等式中,φ是纬度,λ是经度,R 是地球半径(平均半径= 6371 公里),这就是我们如何使用哈弗辛方法计算距离(记为 d )。
A = sin((φB—φA)/2)+cosφA * cosφB * sin((λB—λA)/2)
c = 2 * atan2( √a,√( 1a))
⋅ c
下面给出了这种距离度量的另一种实现方式。
#haversine distance functionhaversine_dist = function(point1, point2) { #each argument is a numeric vector with two elements (lon, lat)
lon1 = point1[1]
lat1 = point1[2]
lon2 = point2[1]
lat2 = point2[2]
R = 6371000 #earth radius in meters
phi1 = lat1 * pi / 180 #convert to radian
phi2 = lat2 * pi / 180 #convert to radian
delta_phi = (lat2 - lat1) * pi / 180
delta_lambda = (lon2 - lon1) * pi / 180
a = (sin(delta_phi/2))^2 + cos(phi1) * cos(phi2) * ((sin(delta_lambda/2))^2)
c = 2 * atan2(sqrt(a), sqrt(1-a))
distance = R * c #haversine distance between point1 and point 2 in meters
return(round(distance, 2))
}
逐步解决方案和 R 中的代码
好了,让我们动手做代码吧!在本文中,我们将使用 R 来运行算法。
- 数据准备
我们导入一个包含所需列的数据帧:城市名称、经度、纬度和人口。
df_city = read.csv('/Users/parara/Documents/Project/Weighted K-Means/city_data.csv')head(df_city)

负责人(df_city)
- 初始化
我们固定集群的数量 K,比如说 5 个。接下来,我们随机选择五个城市作为初始质心。之后,使用这些质心,我们将每个城市分配到其最近的质心(初始聚类)。回想一下,我们使用哈弗线距离作为距离度量。
#number of clusters
K = 5#initial centroids by random
init_centroids_index = sample(nrow(df_city),K)#initiate containers
distance_matrix = matrix(data = NA, nrow = nrow(df_city), ncol = K)
cluster = vector()
centroid_long = vector()
centroid_lat = vector()#compute distance between cities and initial centroids
for (k in c(1:K)) {
for (i in c(1:nrow(df_city))) {
city_i = as.numeric(df_city[i,2:3])
centroid_k = as.numeric(df_city[init_centroids_index[k],2:3])
distance_matrix[i,k] = haversine_dist(city_i,centroid_k)
}
}#initial cluster assignment for each city
for (i in c(1:nrow(df_city))) {
cluster[i] = which.min(distance_matrix[i,])
}
- 循环
有了初始集群,我们就可以开始循环了。我们迭代地更新质心,并将城市重新分配给聚类,直到聚类分配在迭代之间保持不变。
#iteration baseline
old_cluster = vector(length = length(cluster))
new_cluster = cluster#iterations
while (!all(old_cluster == new_cluster)) {
#update old cluster assignment
old_cluster = new_cluster
#calculate centroids using weighted average
for (k in c(1:K)) {
cluster_k = which(old_cluster == k) #city index of cluster k
centroid_long[k] = weighted.mean(df_city$longitude[cluster_k], df_city$population[cluster_k])
centroid_lat[k] = weighted.mean(df_city$latitude[cluster_k], df_city$population[cluster_k])
}
df_centroid = as.data.frame(cbind(centroid_long, centroid_lat))
#compute distance between cities and centroids
for (k in c(1:K)) {
for (i in c(1:nrow(df_city))) {
city_i = as.numeric(df_city[i,2:3])
centroid_k = as.numeric(df_centroid[k,])
distance_matrix[i,k] = haversine_dist(city_i,centroid_k)
}
}
#update cluster assignment for each city
for (i in c(1:nrow(df_city))) {
cluster[i] = which.min(distance_matrix[i,])
}
#update new_cluster
new_cluster = cluster
}
- 选择最佳 K
事实是,上面选择的 K = 5 的值是追溯性的。我们事先根据下面的肘法挑选了号码。但是,理解这种技术需要我们首先理解什么是所谓的平方和误差(SSE)。在 K-means 算法中,SSE 中的术语“误差”是指数据点到其质心之间的偏差。因此,我们通过对所有数据点的所有此类平方误差求和来获得 SSE 的值。
我们注意到,任何集群分配都对应于一个 SSE 值。因此,我们可以绘制不同 K(聚类数)下的收敛算法的 SSE 值。使用该图,我们查看 K,在 K 处,值之后的下降斜率不再显著。这是我们正在寻找的最佳 K。通常,这个 K 值会在整个图上形成一个“肘形”,因此得名。
对于我们来说,情节如下。请注意,在 K = 5 之后,上证指数的下降不再显著,因此我们选择这个数字作为最佳 K。

上证指数在不同 k 线下跌
- 结果
最后,算法的结果如下。

加权 K 均值结果
基于上述结果,我们对五个仓库位置的建议如下:
- 西爪哇省德博克附近
- 西爪哇 Majalengka 附近
- 靠近爪哇中部的 Temanggung
- 东爪哇马迪恩附近
- 东爪哇 Probolinggo 附近
结论和备注
在本文中,我们演示了加权 K 均值聚类算法在确定快餐店仓库位置中的应用。总之,K-means 的这个修改版本与原始版本的不同之处在于它计算聚类质心的方式,它使用加权平均值而不是常规平均值。
关于我们的实现还有最后一句话(代码在这篇文章中给出)。 K-means 算法只产生局部最优,而不是全局最优。也就是说,获得的最终簇分配取决于初始分配,这是随机的。因此, 在实际应用中,我们需要多次复制整个算法,并从产生最小 SSE 的最佳复制中挑选结果。
对于感兴趣的读者,我把这个“真实实现”的代码放在我的 GitHub 库这里。在那里,您还可以找到 SSE 函数的代码,生成本文中显示的图,当然,还有本文中使用的数据集。
总之,感谢您的阅读,让我们在 https://www.linkedin.com/in/pararawendy-indarjo-5b87bab7的 LinkedIn 上与我联系。
使用词语嵌入进行新闻研究
为了分析政治演讲中词语的语境,我不得不跨界到计算语言学。

一个词向量从 1949 年到 2019 年随时间变化的可视化。
像大多数其他领域一样,新闻业正在走向数字化。我是德国《南德意志报》数据和数字调查团队的一员,在那里,我们试图利用现代技术手段开展新闻研究。
在我最新的项目中,我结合了政策分析和机器学习。我想知道:随着时间的推移,政治语言和某些词汇的使用发生了怎样的变化?
自 1949 年 5 月德意志联邦共和国成立以来,70 年已经过去了。在这段时间里,被称为 联邦议院 的联邦议会 4200 多次会议的每一次都被一丝不苟地记录下来:每一次演讲、每一次评论、每一次鼓掌都被记录下来。一个保存德国近代史、深远发展和重大政治冲突的数据宝库。
当然,计算机理解语言的方式与人类不同。相反,计算语言学的数学系统从上下文中揭示单词的含义。语言学家约翰·鲁帕特·弗斯在 1957 年说过一句著名的话:“你应该从一个单词所交的朋友那里知道这个单词”,这远在计算机被用来寻找内容的上下文之前。然而,这个原则在 21 世纪仍然适用,那时算法可以接管这项任务。
我选择的自动评估文本的方法叫做单词嵌入。我们无时无刻不在与这项技术互动,例如在进行简单的谷歌搜索或使用翻译软件时。我将计算语言学科学领域的这些方法应用于政治修辞学。
在我读到的每一篇关于单词嵌入的论文中,都有这样的短语:“有前途的新领域”或“大量研究”。是的,两者都被证明是真的…
算法输入:输入数据
像世界上其他国家一样,德国议会记录了议员们的一言一行。得到的文档是我分析的语料库。
我对随着时间的推移而发生的变化感兴趣,所以我不会在这些年里创建一个大型语料库,但是每个立法术语都有自己的语料库。所以总共有 19 个不同的语料库。
从表中可以看出,单个语料库并不是很大。这导致了如何将单词嵌入方法应用于 19 个相当小的议会记录语料库的第一个重大决定。
大多数研究论文引用了更多数据的项目。这是发人深省的,因为这些方法不能简单地应用于我的情况。但是我在 Antoniak et 里发现了一张桌子。艾尔。(2018) 那真让我振奋。它区分了两种不同的方法:以下游为中心的方法,一种是针对大型语料库的通用方法,另一种是针对特定领域(如政治)的以语料库为中心的方法。

以下游为中心和以语料库为中心的词汇嵌入方法比较。截图:安东尼亚克等人。艾尔。(2018)
这种区别决定了分析的方法,正如安东尼亚克和明诺所写的:
“与以下游为中心的方法不同,以语料库为中心的方法基于对嵌入向量的最近邻居的直接人类分析,训练语料库不仅仅是现成的便利,而是研究的中心对象。”
人类和计算机之间的翻译:单词嵌入
计算语言学使用的一种从上下文中提取单词含义的方法叫做单词嵌入。语料库中的每个词通过上下文和语义嵌入到模型中。关于德国议会的记录,这意味着:当计算单词嵌入的模型时,议会成员的语言的上下文和语义被嵌入到模型中。
每一个独特的单词都由一系列数字(也称为维度)来表示,这些数字是在学习上下文的过程中计数和权衡的结果。这个过程叫做“训练模型”。
结果看起来像这样:
环境(um welt)(0.08567299 0.11279329 0.2180736-0.3106728…) 自然保护(Naturschutz)(-0.012405696 0.07818038-0.045643393-0.0051204716…) 气候灾害
学习机的能力隐藏在这些乍看起来不起眼的单词和数字序列中。
这组向量携带以下信息:
- 每个向量描述了单词的上下文
- 相对于所有其他向量,每个向量对单词的含义进行编码
理论上,维度越多,一个向量可以包含的意义就越多。但实际上,它受到你输入算法的文本量的限制。寻找最佳长度可能是一个反复试验的评估过程。在这里,我使用 300 个维度。这是许多研究人员使用的默认设置,我坚持他们的经验。
单词嵌入的用户可以自由决定应该计算多少维度。这样的设置选项在机器学习中被称为超参数。它们的调整会严重影响结果,并且用户在实现机器学习模型时会反复面临做出这样的决定。
请记住,嵌入模型中的含义只适用于特定的领域。在这种情况下:在过去的 70 年里,德国议会的演讲。
因此,一种算法将文本语料库作为输入,并返回计算出的单词向量。但是这是怎么发生的呢?
单词如何变成数字:word2vec 算法
有几种可能的算法可以解决计算单词嵌入的任务。对于这个项目,我选择了有据可查的 word2vec 算法。它于 2013 年在谷歌推出(米科洛夫等人,2013 年 a )。许多科学论文测试了单词嵌入算法,并得出结论:word2vec 是最佳选择。我使用了 Python 的实现 gensim ( Rehurek 和 Sojka,2010 )。
首先,构建词汇表:从输入文本中提取并存储每个唯一的单词。Word2vec 通过检查哪些单词在特定上下文中一起出现来从文本中学习。上下文被定义为在被检查的单词周围的窗口。窗口的大小是要设置的另一个超参数。
我们来看这句话:

截图:jaye sh Bapu Ahire 的《词向量入门》
现在我们要用五个单词的窗口大小来训练狗的向量:用黄色标记的单词是从词汇表中挑选出来的,并在窗口内的每一对可能的单词上进行训练: The 、 fluffy 、汪汪汪、 as 。紧邻的单词被赋予更高的权重。例如,蓬松的 + 剥皮的被赋予比更大的权重,把 + 称为。
接下来,我们再深入一个词,看看吠。窗口向前移动一个单词,并且相邻单词被加权。

通过这种方式,该窗口迭代了自 1949 年以来的所有议会演讲。
所有出现的单词对都是一维神经网络训练的输入。Word2vec 旨在计算一个模型,该模型可以预测一个单词与另一个单词出现的可能性。为了解决这个任务,有一个隐藏层,其神经元的数量与向量的维数一样多。
“单词 vector 是模型试图学习单词的良好数字表示,以最小化其预测的损失(误差),Jayesh Bapu Ahire写道。
最后,当训练过程结束时,输出层被剥离。单词向量是经过训练的神经元的结果。在机器学习领域,这被称为假任务:你可以在一个任务上训练一个模型,你有足够的数据,然后扔掉最后一层。

word 2 vec 的内部运作方式。截图:Jayesh Bapu Ahire对词向量的介绍
如果你想更深入地研究词向量和神经网络,我推荐这些博客帖子(按书呆子气升序排列):
- Jayesh Bapu Ahire 对单词向量的非常好的介绍。你会认出上面的图形
- 在 Word2Vec 教程—跳格模型中有更多关于神经元网络和跳格实现的细节
- 如果你还需要更多关于神经元网络内部发生了什么的信息: Word2vec 使之变得容易
用语言计算:文字作为数字的魔力
在训练过程中,该模型不仅识别单词的上下文,还从概念上学习语法类别,如数、格或性别。这些关系被编码在模型的维度中。
一个概念性的例子:想象向量“女总理”( Bundeskanzlerin )、“男总理”( Bundeskanzler )、“女人”( Frau )和“男人”( Mann )有四个维度和虚构的数字。
考虑从左边开始的第二维:向量“女总理”( Bundeskanzlerin )和“女人”( Frau )具有高值,而“男总理”( Bundeskanzler )和“男人”( Mann )具有低值。这个维度可能被贴上女性化的标签。从左边算起的第三维度则是相反的方向,暗示着男子气概。

德国联邦议院模型中有 300 个维度,而不是 4 个维度——当然,没有标签。不仅仅是四个词,仅最近的选举任期就有超过 61,000 个词。这导致了 1830 万个数据点——对我们的大脑来说太复杂了,难以想象。尽管这是不可想象的,但是用向量来计算是可能的,就像回到高中一样。
描述 Word2Vec 的这一特性时,一个通俗的例子是:“国王—男人+女人=女王”。应用来自德国议会的数据,数学运算可以用“女总理——女人+男人=男总理”来进行。
一个很好的计算不同语料库的应用是北欧语言处理实验室的语义计算器。
用向量计算很有趣。但是探索广阔的向量空间可能会超出我们读者的耐心。此外,它无法回答主要问题:政治辩论正在发生怎样的变化?
解决不稳定的单词嵌入:不是一个,而是 30 个模型
发现政治演讲转变的主要想法是比较一届立法任期与前一届有多大不同。因此,下面的测量结果是有用的:余弦相似性。它计算两个向量之间角度的余弦值。结果是一个介于 0 和 1 之间的数字,其中 0 表示没有相似性,1 表示绝对相似。余弦相似度越接近 1,一个词就越能被解释为同义词。
自 71 年前德意志联邦共和国成立以来,共有 19 届选举。对于每个术语,我计算了一个模型,然后查询模型,以提取与 180 个特别感兴趣的词最相似的词,例如:“气候变化”( Klimawandel ),“交通”( Verkehr ),“难民”( Flüchtlinge )或“种族主义”(rassi mus)。

我们用于研究 word 嵌入模型的内部仪表盘截图。
我们在《南德意志报》的团队定义了一组 180 个高频相关术语,以便进行更深入的研究。我们没有事先选择这个查询词列表。花了一些时间在档案和数据中探索和研究,以识别有故事可讲的单词。
例如:下表显示了每个立法术语中与德语 Umwelt 中的“环境”一词最相似的词:
很明显,在最初的 20 年里,这个最相似的词每一个词都在变化,而且有着与今天不同的含义。那时候,“环境”( Umwelt )经常被用来描述周围的世界。这种情况在 20 世纪 80 年代发生了变化,当时绿党被选入议会,其对环境问题的关注极大地影响了“环境”一词的使用。同义词首先变成了“生计”(lebengrundlage),然后进一步变成了“自然保护”( Naturschutz )。近来最相似的词是“生物多样性”。
这真的很强大:单词嵌入可以重现政治辩论。但是它们有一个巨大的缺点:它们不稳定,特别是对于像我这样的相对小的语料库[1,2]。每次您使用相同的数据和相同的设置编写新模型时,针对特定术语嵌入的查询结果都会因模型而异。安东尼亚克等人。艾尔。[1]在他们的论文中研究了这一现象:
“我们发现嵌入中存在相当大的可变性,这些可变性对于这些方法的用户来说可能并不明显。大多数相似单词的排名是不可靠的,并且这种列表中的排序和成员资格容易发生显著变化。一些不确定性是预料之中的,对于‘可接受的’方差水平没有明确的标准,但我们认为我们观察到的变化量足以对整个方法提出质疑。”
下表列出了从 1983 年到 1987 年与“环境”最相似的 15 个单词。这六列代表不同的型号。这些值是“环境”和行中相应单词之间的余弦相似度。空单元格意味着该模型没有将该单词识别为十大相似单词之一。
因为单词嵌入模型是不稳定的,所以它们不会在不同的模型上提供相同的结果。例如,第二个模型不承认德语leben straum中的“生活空间”是“环境”的同义词,但其他五个模型承认。
当模型产生不同的结果时,我应该相信哪一个?没有。单一模型如此不稳定的结果是新闻研究不可接受的基础。我通过训练 30 个模型来解决这个问题,而不是每个选举任期只训练一个。总共有 570 个模型用于 19 个选举任期。我测试了一下:我删除了所有的模型,重新计算。我的查询对新模型产生了相同的结果。所以分析是可重复的。
当查找政治相关术语的相似词时,我从一个选举术语的所有 30 个模型中提取了 10 个最相似的词。下一步是计数过程:一个词在每个术语中作为同义词出现的频率是多少?
这种查询多个模型并对出现次数进行计数的过程确保了我们只检查那些在模型中反复出现并且可以被视为稳定结果的单词和伴随的嵌入。
为了可视化结果,我们使用热图。下表显示了“环境”最常见的同义词。
左边的十二个字是按照 1949 年以来的整体面貌排序的。在这里,我们也绘制低于 30 的值,因为它们表示“环境”话语中某个单词的上升或下降。

热图是一个很好的工具,可以检查整个时间跨度并查看随时间的变化。但它们不足以详细评估一个选举任期。为此,我们使用了由 30 个模型的平均余弦相似性排序的相似单词列表,并用更强的字体权重标记稳定的单词。
我在 Github 上发表了 2017–2019 最新选举任期的 word embedding models。在德国政治领域,你可以随意使用它来完成你自己的项目。如果你这样做,请写信给我,我对你的工作很感兴趣。
意犹未尽:对历时词汇语义变化的探索
到目前为止,我们已经研究了单词的上下文是如何变化的。另一种方法是关注单词本身,看单词向量的变化。这就是所谓的历时词汇语义变化。
金等人。艾尔。[3]提供了这样做的方法。他们与谷歌图书 ngram 语料库合作,计算了从 1900 年到 2009 年每年的一个模型。有两个词特别值得注意:“细胞”和“同性恋”。在这 100 年里,它们的意义发生了很大变化。" gay "的邻近词从"欢快的"、"愉快的"、"辉煌的"变成了"女同性恋"、"双性恋"和"女同性恋",而" cell "从"壁橱"、"地牢"、"帐篷"变成了"电话"、"无绳"、"蜂窝"。
为德国政治争取这些词汇不是很好吗?

不幸的是,这篇论文经常被引用来批评其寻找这些单词的方法,例如,因为他们正在处理不稳定的单词嵌入。
幸运的是,我联系到了张秀坤·施勒特韦格,他是一名博士生,与萨宾·舒尔特教授一起在瓦尔代研究自然语言处理研究所(斯图加特大学)语义变化的分布式模型。他耐心地建议我将该方法应用于我的数据。
Schlechtweg 等人。艾尔。 [4]测试了几种测量这些变化的方法:“总体表现最好的模型是具有正交排列和余弦距离的 Skip-Gram。”
该方法可以应用如下:
- 来自一个时间段的每个模型必须与下一个时间段的模型对齐:时间段 1 到时间段 2,时间段 2 到时间段 3 等等。结果是更多的模型:每次新的选举期开始,我都会得到一对新的模型。
- 对于每个单词,计算从模型到模型的余弦距离。
- 棘手的部分:为了消除频率[5]的统计影响,应该额外提取在相应术语上具有相同频率变化的可比单词。
等等,这里有个例子:
视觉推理的首次尝试:

你看到的蓝线是向量“ Umwelt ”(环境)在 15 个模型中的平均变化。浅蓝色的一个点表示来自一个模型的单个余弦距离值(余弦距离= 1-余弦相似性)。
蓝线和灰线的对比很重要,灰线显示了“Umwelt”的频率。如你所见:如果灰线改变,蓝线也会改变,因为余弦相似度直接取决于频率。可用单词越多,相似度越低。
有了这个背景你就明白了:没什么可看的。单词向量(蓝线)的变化是由于频率变化(灰线)。为了消除这种统计效应,你必须找到频率变化相同的单词,并绘制出它们之间的距离。
下一次尝试以引用词结束,如下图所示:

它看起来更好,因为有一个项,第 10 项,蓝色点位于浅蓝色点之上。这表明不管频率如何,词向量都有真实的变化。
蓝点代表向量“Umwelt”(这 15 个模型)的余弦距离。浅蓝色的点现在表示与在模型对中的最近术语中与我感兴趣的单词共享相同频率的可比单词的余弦距离。参考字从模型对变化到最佳地表示该时间点的频率。
你注意到这个缺陷了吗?
当我比较和计算来自两个不同选举术语的模型对的相似性时,我还需要比较来自两个选举术语的参考词的频率变化。词向量在一段时间内的距离,并不是与另一段时间内的课程进行比较,而是一个特定的时间点。这就是缺陷。
我试图通过搜索那些与“Umwelt”有相同频率变化的单词来修复它。但是没有相同的频率变化,即使我让频率有小幅度的波动。语料库实在太小了。
那么,让我们进入下一个迭代。而不是绝对变化,也许相对频率变化可以有所帮助?
看上面的图表:当频率折线图改变时,余弦距离折线图也随之改变。
如果选择相对频率变化相同但绝对频率更高的参考词,余弦相似性可能会更小,因为频率和余弦相似性之间存在反比关系。
因此,根据感兴趣的单词,你会仅仅因为基于频率差异的统计效应,而对单词向量的视觉变化进行推理。
这个问题有一个理论上的解决方案:寻找绝对数变化相同但频率更高的词,然后回到最初的语料库。现在的关键是通过随机屏蔽对参考单词进行采样,直到频率与感兴趣的单词相同。然后计算新模型,将它们对齐,并查询图表的相似度。那是我停下来的地方。
当我们讨论这个未解决的任务时,张秀坤写道:“我还想说,自动意义变化检测领域还没有准备好为你的应用问题提供标准方法。两年后,情况可能会有所不同。”
寻找故事:新闻的结果
基于这项研究发表了三个故事。
- 德国联邦议院如何搞糟气候变化:自 20 世纪 80 年代海尔马特·斯克米特总理时代起,联邦议院就一直在讨论气候灾难。但是一项数据研究表明:一代又一代的议员几乎不屑于对他们采取行动。
- 匆忙的议会:所谓的难民危机是一个转折点,德国变了。一项数据研究显示:关于难民和移民的话语已经向右转移——同样是由 AfD(德语中的)提出的。
- 《在语言的引擎室》:一个关于方法论的大故事。目标群体是《南德意志报》的普通受众。其野心是解释机器学习的话题,而不是求助于一个“这是一个黑箱”(德语中的)。
交流不确定性:技术沙文主义的危险
我对计算机如何捕捉自然语言很着迷。但是这种迷恋有技术沙文主义的危险。技术沙文主义是梅雷迪思·布鲁萨德提出的一个术语——“相信技术总是解决问题的方法”。
我们如此热衷于给机器分配我们的大脑无法解决的任务。所以我们很容易忽略这样一个事实,结果也必须被质疑。我们陷入了计算机总是客观地产生完美的结果的想法。这就是为什么我认为沟通像这样的项目的不确定性是很重要的。如果我改变算法的超参数,数据中的其他关系可能会被更强烈地强调,潜在的文章可能会产生不同的结果。这就像就一个具体问题询问多位证人。我如何表达我的问题也会影响我得到的答案。
你有答案或意见吗?我感谢任何反馈。
特别感谢卡塔琳娜·布鲁纳和卡门·海格的评论。
DIY:Github 上的 30 个单词嵌入模型
我在 Github 上发布了当前选举任期(2017–2019)的 30 个单词嵌入模型。随意使用它们并引用我。
阅读更多:相关的科学工作
[1]安东尼亚克,玛丽亚;米姆诺,大卫。基于嵌入的词语相似度的稳定性评估。《计算语言学协会汇刊》,第 107–119 页,2018 年 2 月。
[2]约翰内斯·赫尔里奇和乌多·哈恩。2016.坏公司——神经嵌入空间中的邻居被认为有害。《2016 年计算语言学国际会议论文集》,2785-2796 页,日本大阪。
[3] Yoon Kim、Yi-I Chiu、健太郎·哈纳基、达尔山·黑德和斯拉夫·彼得罗夫。2014.通过神经语言模型对语言进行时间分析。ACL 语言技术和计算社会科学研讨会论文集,第 61-65 页。
[4]施莱希特韦格,张秀坤等,“变化之风:检测和评估跨时间和领域的词汇语义变化。”计算语言学协会第 57 届年会论文集(2019)
[5] Haim Dubossarsky、Daphna Weinshall 和 Eitan Grossman。2017.失去控制:语义变化的规律和单词表征模型中的固有偏差。《2017 年自然语言处理经验方法会议论文集》,1147-1156 页,丹麦哥本哈根。
使用单词向量以数学方式找到意思相似的单词
入门
学习使用词向量以编程方式计算文本的语义相似度

卡尼·查拉科娃摄
简而言之,单词向量只不过是代表自然语言单词含义的一系列实数。这项技术是有用的 NLP 能力的重要使能器,使机器能够“理解”人类语言。本文讨论了如何使用单词向量以编程方式计算文本的语义相似性,例如,如果您需要根据文本所涵盖的主题对其进行分类,这将非常有用。它从一个概念视图和示例开始,然后说明如何使用 spaCy(一个领先的用于 NLP 的 Python 库)来确定文本的语义相似性。
词向量的概念
所以,让我们先从概念上看一下单词向量,这样你就可以基本理解如何用数学方法计算以向量形式表示的单词之间的语义相似度。然后,您将查看 spaCy 的 similarity()方法,该方法比较容器对象(Doc、Span、Token)的单词向量,以确定它们含义的接近程度。
在统计建模中,您将单词映射到反映单词语义相似性的实数向量。你可以把一个单词向量空间想象成一朵云,其中有相似意思的单词向量位于附近。例如,表示单词“apple”的向量应该更接近单词“pear”的向量,而不是单词“car”的向量。因为前两个指的是可食用的水果,而后者指的是四轮公路车辆。为了生成这些向量,你需要对这些单词的意思进行编码。事实上,有几种方法可以对意义进行编码。
用坐标定义意义
生成有意义的单词向量的一种方式是将现实世界中的对象或类别分配给单词向量的每个坐标。例如,假设您正在为以下单词生成单词向量:罗马、意大利、雅典和希腊。向量这个词应该在数学上反映出罗马是意大利的首都,并且与意大利有着雅典所没有的联系。同时,它们应该反映出雅典和罗马是首都城市,而希腊和意大利是国家。
下图以矩阵的形式展示了这个向量空间的样子。

在这里,你在一个四维空间的坐标之间分配每个单词的含义,代表“国家”、“首都”、“希腊”和“意大利”等类别。在这个简化的例子中,坐标值可以是 1 或 0,指示相应的单词是否属于该类别。
一旦你有了一个向量空间,其中的数字向量捕获了相应单词的含义,你就可以在这个向量空间上使用向量算术来洞察一个单词的含义。要找出雅典是哪个国家的首都,您可以使用以下等式,其中每个令牌代表其对应的向量,X 是未知向量:
意大利—罗马= X —雅典
这个等式表达了一个类比,其中 X 代表单词 vector,它与雅典的关系就像意大利与罗马的关系一样。为了解出 X,你可以把等式改写成这样:
X =意大利—罗马+雅典
首先,通过减去相应的向量元素,从向量意大利中减去向量罗马。然后你把结果向量和雅典向量相加。下图总结了这一计算。

通过从代表意大利的单词 vector 中减去代表罗马的单词 vector,然后加上代表雅典的单词 vector,可以得到一个等于希腊的矢量。
用维度来表达意义
虽然您刚刚创建的向量空间只有四个类别,但是现实世界的向量空间可能需要成千上万个这样的类别。这种大小的向量空间对于大多数应用来说是不切实际的,因为它需要一个巨大的字嵌入矩阵。例如,如果您有 10,000 个类别和 1,000,000 个实体要编码,您将需要一个 10,000 × 1,000,000 的嵌入矩阵。
减少嵌入矩阵大小的明显方法是减少向量空间中类别的数量。单词向量空间的实际实现不是使用坐标来表示所有类别,而是使用向量之间的距离来量化和分类语义相似性。各个维度通常没有固有的含义。相反,它们表示向量空间中的位置,向量之间的距离表示相应单词含义的相似性。要看一个真实向量空间的例子,你可以在https://fasttext.cc/docs/en/english-vectors.html下载 fastText 单词向量库,它将单词的意思分布在一个 300 维的单词向量空间中。
空间相似性()方法
在 spaCy 中,每种类型的容器对象都有一个相似性方法,允许您通过比较两个任意类型的容器对象的词向量来计算它们之间的语义相似性估计。为了计算跨度和文档的相似性(它们没有自己的词向量),spaCy 对它们包含的标记的词向量进行平均。
即使两个容器对象不同,也可以计算这两个对象的语义相似度。例如,您可以将令牌对象与 Span 对象进行比较,将 Span 对象与 Doc 对象进行比较,等等。
以下示例计算 Span 对象与 Doc 对象的相似程度:
doc=nlp('我想要一个青苹果。')
这段代码计算句子“我想要一个青苹果”之间的语义相似度。短语“一个青苹果”也来源于这个句子。如您所见,计算出的相似度足够高,可以认为两个对象的内容是相似的(相似度的范围从 0 到 1)。毫不奇怪,当您将一个对象与其自身进行比较时,similarity()方法返回 1:
doc . similarity(doc)
1.0
doc[2:5]。相似性(doc[2:5])
1.0
注:文中使用的例子摘自我最近由 No Starch 出版社(https://nostarch.com/)出版的《用 Python 和 spaCy(https://nostarch.com/NLPPython)进行自然语言处理》一书。
使用 YOLOv3 实时检测个人防护设备和火灾
工业安全中的人工智能:这篇文章解释了我们如何使用 YOLOv3:一种用于实时检测个人防护设备(PPE)和火灾的对象检测算法
“安全不是一个小玩意,而是一种心态!”
世界摩天大楼的不断攀升是一个不断演变的挑战的故事。我们每天生活在摩天大楼、购物中心和仓库周围。大多数时候,我们对建造这些建筑需要什么一无所知。建筑业负责创造如此宏伟的杰作。截至 2020 年 3 月,建筑业拥有 1.3 万亿美元的年支出(约占 GDP 的 6.3%)[1]和 760 万名员工(约占总劳动力的 5%)[2],是美国经济中最大的行业之一。然而,由于建筑工地的高死亡率,它也被认为是最危险的行业之一。
与建筑相关的伤亡的主要原因是跌倒、人员被设备卡住、触电和碰撞。如果工人穿戴适当的个人防护装备,如安全帽、安全背心、手套、护目镜和钢头靴,大多数伤害都是可以避免的。为了确保工作场所的安全,美国职业安全与健康管理局(OSHA)要求承包商强制执行并监控个人防护设备(PPE)的正确使用。
目前,PPE 检测技术可以分为两种类型:基于传感器的和基于视觉的。基于传感器的方法使用 RFID(射频识别 RFID)标签,需要在购买、安装和维护复杂的传感器网络方面进行大量投资。相反,基于视觉的方法使用摄像机记录施工现场的图像或视频,然后对其进行分析以验证 PPE 合规性。这种方法提供了关于场景的更丰富的信息,可用于更迅速、精确和全面地理解复杂的建筑工地。
本文解释了如何使用基于 YOLOv3(你只看一次)架构的深度学习模型来满足各种安全要求,如建筑工地工人的 PPE 合规性和使用最少硬件(即监控摄像头)的火灾探测。深度学习算法通过单个卷积神经网络(CNN)框架同时检测单个工人并验证 PPE 合规性。在当今世界,卷积神经网络(CNN)最广泛地用于图像分类和对象检测,因为它能够从大量带注释的训练数据中进行自我学习。
各种实时物体检测技术
计算机视觉中的任何对象检测问题都可以定义为识别图像中的对象(也称为分类),然后精确估计其在图像中的位置(也称为定位)。如今,物体检测几乎在任何地方都被使用。用例是无穷无尽的,无论是跟踪对象、视频监控、行人检测、异常检测、自动驾驶汽车还是人脸检测。
目前在工业中使用的主要对象检测算法有三种:
R-CNN:基于区域的卷积网络
R-CNN 首先识别几个感兴趣的区域,然后使用 CNN 对这些区域进行分类,以检测其中的对象。由于原始的 R-CNN 很慢,所以已经提出了它的更快的变体,例如快速 R-CNN、更快 R-CNN 和屏蔽 R-CNN。在 R-CNN 中,图像首先被分成大约 2000 个区域推荐(区域比例),然后 CNN (ConvNet)被分别应用于每个区域。
确定区域的大小,并将正确的区域插入到人工神经网络中。这种方法最大的问题是时间。由于图中的每个区域分别应用 CNN,训练时间约为 84 小时,预测时间约为 47 秒。
YOLO:你只看一次
大多数目标检测算法使用区域来定位图像中的目标。网络不会看到完整的图像。取而代之的是,他们观察图像中包含物体的概率较高的部分。YOLO 或你只看一次是一个对象检测算法,与上面看到的基于区域的算法有很大不同。在 YOLO,单个卷积网络预测边界框和这些框的类别概率。

YOLO 天体探测器管道的简化图( 来源 )
YOLO 使用的策略与早期的探测系统完全不同。单个神经网络被应用于整个图像。该网络将图片分成多个区域,并计算每个区域的边界框和概率。这些边界框由预测概率加权。
SSD:单次多盒探测器
SSD 是物体检测中比较流行的算法。一般比更快的 RCNN 快。SSD 将边界框的输出空间离散化为一组默认框,每个要素地图位置具有不同的纵横比和比例。在预测时,网络为每个默认框中每个对象类别的存在生成分数,并对框进行调整以更好地匹配对象形状。此外,该网络结合了来自不同分辨率的多个特征地图的预测,以自然地处理各种尺寸的物体。[4]

截图来自解释固态硬盘架构的文章
现在,我们将看到我们如何为这个项目和 YOLOv3 的实现收集数据。
数据集采集
在这个项目中,我们首先将正在探测的物体分为四类:头盔(戴头盔的人)、人(不戴头盔的人)、消防和安全背心。

作者照片
YOLO 格式的图像标注
训练数据集是作为样本收集的图像,并被注释用于训练深度神经网络。我们从多个来源为我们的数据集收集图像,如可以在 github 上找到的安全头盔数据集,来自 google 和 Flickr 的图像,并使用 labelIMg 对它们进行注释。此外,为了获得这些对象的定制数据,我们使用 Flickr API &下载了使用 python 代码的图像。为了给图像加标签,我们使用了标签工具。
标签工具
LabelImg 是一个图形化的图像注释工具。它是用 Python 编写的,图形界面使用 Qt。对于对象检测,有许多格式可用于准备和注释训练数据集。图像注释以 PASCAL VOC 格式保存为 XML 文件,这是 ImageNet 使用的格式。它也支持 YOLO 格式,我们使用了这种格式,注释保存在下面的文本文件中。
文本文件中的格式
举例:
2 0.513125 0.209167 0.173750 0.358333

作者照片
你可以参考这篇文章https://medium . com/deepquestai/object-detection-training-preparing-your-custom-dataset-6248679 f0d 1d来全面了解如何使用这个工具来注释你的定制数据集。
贴标签时的重要注意事项
为了避免收集火灾图像数据时的错误预测,我们确保我们包括每个对象的各种方向、背景和角度,以更好地训练我们的模型。例如,我们已经包含了落日的图像,将注释置为空,以避免它被误认为是火。
这里有一个链接用于我们的数据集,其中包含一个戴头盔的人、一个不戴头盔的人、消防和安全背心的图像。
为什么使用 YOLO?
与其他检测系统相比,使用 YOLO 的最大优势之一是,它的预测是由测试时整个图像的全球背景提供的。与 R-CNN 等基于分类器的系统不同,它还通过单一网络评估进行预测。YOLO 比 R-CNN 快 1000 多倍,比快速 R-CNN 快 100 多倍。[10]
- 速度(每秒 45 帧,比实时速度快)
- 网络理解一般化的对象表示(这允许他们在真实世界的图像上训练网络,并且对艺术品的预测仍然相当准确)
- 更快的版本(架构更小)—每秒 155 帧,但精度较低
- 开源:https://pjreddie.com/darknet/yolo/
不同版本的 YOLO
我们在项目中使用的版本是 YOLOv3。YOLO 目前有 4 个版本:v1、v2、v3 和 v4。每个版本都有不断的改进。我们使用的 v3 中最显著的改进是在类别预测和特征提取方面。对于类预测,YOLOv3 不使用 softmax,因为它假设每个“盒子”只能有一个类,这通常是不正确的。相反,研究人员使用独立的逻辑分类器。对于特征提取,他们使用了一个有 53 个卷积层的网络——称为 Darknet-53。[11]

地图上 5 IOU 度量的速度/精度权衡参考网站
YOLO 的不同实现
- 暗网(https://pjreddie.com/darknet/)
- AlexeyAB/darknet(https://github.com/AlexeyAB/darknet)
- 暗流(【https://github.com/thtrieu/darkflow/】T2
我们使用 YOLO 的暗网实现
yolov 3 是如何工作的?
请参考关于“现场安全深度学习:个人防护装备实时检测的论文,以更好地了解 YOLOv3 的工作情况。
- 在 YOLO-v3 中,我们将 416 × 416 RGB 图像作为输入,它包含三个输出层,每个层将输入图像分别划分为 13 × 13 网格(输出-1)、26 × 26 网格(输出-2)和 52 × 52 网格(输出-3),如下图所示。[12]

YOLOv3 算法,如“现场安全深度学习:个人防护设备的实时检测”中所述
2.三个锚盒连接到三个输出层中的每一层,总共有九个锚盒。[12]
3.输出-1 层指的是三个最大的锚定框(用于检测大的对象),输出-2 层连接到下三个最大的锚定框(用于检测中等大小的对象),输出-3 层与三个最小的锚定框相关联(用于检测小的对象)。[12]
4.在训练过程中,输出层中的每个网格单元采用适当的锚定框,并学习如何移动和/或缩放这些锚定框,以使改变后的框(也称为边界框)完美地适合感兴趣的对象,如图所示..[12]
5.每个预测的边界框与一个(N +5)维向量相关联。[12]

在上面的向量 tx 中,ty、tw 和 th 分别表示中心的 x 和 y 坐标,以及盒子的宽度和高度。
6.值 p0(也称为客体分数)是主体/客体在边界框内的概率。其余的值是 N 个条件概率,P(Ci|object),每个表示给定一个存在于盒子内的对象,它属于 Ci 类的概率是多少,其中 i = 1,…,N[12]
7.总的来说,YOLO-v3 对于单个图像产生总共 10,647 个盒子,因为(13×13×3)+(26×26×3)+(52×52×3)= 10,647。然而,直觉上,输入图片中的大多数输出框要么是假阳性,要么代表相同的东西。[12]
8.为了避免推理部分的不一致和重复,YOLO 使用非最大抑制(NMS)技术来消除重叠百分比较高但置信度较低的框,从而确保给定项目的单个边界框的保留。[12]
结果的定量分析
衡量对象检测算法性能的一个常见度量是交集/并集(又称 IoU)。如下图所示,IoU 表示两个框(如地面实况框(G)和预测框(P ))之间的重叠百分比,并使用下面给出的公式进行计算:[12]

计算欠条

在下图中,我们可以看到白色的边界框代表真实情况,红色的边界框代表预测。地面真实值和预测值之间的差异用于获得我们模型的平均精度。

作者图片

作者图片

作者图片
AP(平均精度)是测量物体检测器(如更快的 R-CNN、SSD、YOLOv3 等)精度时使用最广泛的指标。平均精度计算 0 到 1 之间的召回值的平均精度值。
下图显示了我们对整个数据集的预测结果。我们获得了 77.58%的平均精度

作者图片
我们在一个视频上测试了我们训练过的模型,视频中有人戴着头盔,穿着安全背心,还有一个有火的场景。
我们训练好的模型对视频输入的结果:由我的队友上传到他的 YouTube 频道
如果你对在我们的自定义数据集上实现 YOLOv3 的代码感兴趣,请使用下面的链接访问它。
* [## rroy1212/PPE_Detection_YOLOv3
permalink dissolve GitHub 是超过 5000 万开发人员的家园,他们一起工作来托管和审查代码,管理…
github.com](https://github.com/rroy1212/PPE_Detection_YOLOv3/blob/master/PPE_and_Fire_Detection.ipynb)
我们项目的视频讲解
视频讲解个人防护设备和使用 Yolov3 进行火灾探测
贡献者
由 Aastha Goyal、Harshit Nainwani、Rishi Dhaka、Rupali Roy 和 Shweta Shambhavi 为 UT Austin 的数据科学实验室课程 EE 460J 设计的项目。特别感谢 Alex Dimakis 教授!
参考
- 美国人口普查局,建筑支出,https://www.census.gov/建筑/c30/prpdf.html ,访问日期:2020 年 5 月 11 日。
- 劳工统计局,《行业一览:建筑业》,https://www . bls . gov/iag/TGS/iag 23 . htm,访问日期:2020 年 5 月 11 日。
- https://towardsdatascience . com/r-CNN-fast-r-CNN-faster-r-CNN-yolo-object-detection-algorithms-36d 53571365 e
- https://arxiv.org/abs/1512.02325
- https://www . science direct . com/science/article/ABS/pii/s 0926580519308325?通过%3Dihub
- https://medium . com/@ Jonathan _ hui/map-mean-average-precision-for-object-detection-45c 121 a 31173
- T.-林毅夫、戈亚尔、吉希克、贺铿和杜大伟。密集物体探测的聚焦损失。arXiv 预印本:1708.02002,2017
- https://arxiv.org/abs/1506.02640
- https://medium . com/analytics-vid hya/初学者-目标探测指南-算法-6620fb31c375
- https://towards data science . com/yolo-you-only-look-once-real-time-object-detection-explained-492 DC 9230006
- j .雷德蒙,a .法尔哈迪。YOLOv3:增量改进,2018。arXiv
- Nath、Nipun D .、Amir H. Behzadan 和 Stephanie G. Paal 关于“现场安全的深度学习:个人防护设备的实时检测”建筑自动化 112 (2020): 103085*
用你的声音交易股票
嘿,谷歌,购买 10 只 MSFT 股票。了解如何将您的谷歌助手连接到 IFTTT,并使用羊驼 API 进行股票交易。

如果炒股可以像告诉谷歌开灯一样简单,那不是很好吗?有了羊驼毛 API 和 IFTTT,就是!羊驼最近宣布账号用户可以在 IFTTT 上构建小程序。这项新功能使用户能够用声音进行股票交易。

首先,你需要拥有羊驼、谷歌和 IFTTT 的账户。一旦你为每项服务创建了账户。登录 IFTTT。你需要将羊驼服务和谷歌助理服务链接到你的 IFTTT 账户。你可以点击上面的链接或者搜索这些服务。它会询问您的登录信息(每个帐户的用户名和密码。)
成功连接两个服务后,您可以通过单击“设置”来检查它们是否已连接。您将看到这样一个屏幕,表明状态是活动的。

下一步是构建 applet。您将通过导航到 IFTTT 的“创建”区域来完成此操作。创建平台如下所示。

IFTTT 使用的逻辑是 IF This(触发动作)Then That(服务动作)。我们这个语音交易服务的逻辑是这样的。如果(谷歌助手听到某个短语)那么(羊驼会帮我们一个忙)。

当您为(此)触发器服务选择 Google Assistant 时,系统会提示您选择触发器类型。您将选择允许数字和文本成分的选项。

然后,它会提示您添加触发短语和触发操作的详细信息。

我把羊驼放在短语的开头,这样更独特,也更容易让谷歌助手触发 IFTTT。我用“购买”而不是“购买”,因为我的谷歌助手一直听到“按”而不是“购买”,它不会触发 IFTTT。创建触发器后,它会提示您选择服务。您将选择/搜索羊驼。

这将向您展示羊驼服务的所有当前/可用选项。对于此子视图,我们将选择“下达市场订单”。
这是您将使用“配料”的地方

如果您选择“添加配料”,它会列出所有可能的选项。* *注:这就是我一直使用谷歌助手的原因。目前,亚马逊 Alexa 触发选项不包括文本和数字成分。
对于该子视图,我们将选择“购买”。添加完所有正确的逻辑后,单击 Create action。
最后一步是保存操作,并选择是否希望在 applet 运行时收到通知。

最后,在测试之前,IFTTT 将显示一个连接的屏幕——这允许您打开和关闭这个小程序。

考验的时候到了!

它非常有效!
最后一步是在 IFTTT 中创建另一个 applet(参考前面的步骤)来销售股票。一旦你完成了这个小程序,你就可以用你的声音来交易股票了。

结论
使用 API 进行股票交易已经从各个方面改变了股票交易。利用 IFTTT 和 Google Assistant 就是一个很好的例子。我希望你喜欢这篇关于如何为自己设置的教程。我也希望这能引发其他人思考如何使用羊驼和 IFTTT。
干杯。
利用新型数据流对抗新冠肺炎病毒

美国墨西哥湾;照片由美国宇航局拍摄。
随着社交距离措施的到位,大量关于新冠肺炎的讨论现在发生在 Twitter 等社交媒体平台上。这些平台包含丰富的信息,可以帮助我们回答诸如今天有多少人表现出冠状病毒症状?然而,并非所有信息都是平等的——这些平台也包含大量错误信息,可能会对公众造成伤害。
我们开发了一个系统来跟踪和分析提到新冠肺炎症状的推文。这个系统“听”提到新冠肺炎症状的推文。一旦被识别,推文就会通过机器学习分类器进行分类,识别它是否与用户的个人症状、其他人的症状有关,或者推文是否包含错误信息。
我们还可以使用地理定位数据来计算在给定国家的每个地区(用户允许地理定位的地方)发布症状的用户数量。根据这些数据,可以确定在给定国家的不同地区之间旅行的用户数量。这些信息可能有助于识别一个国家内的新爆发群集,并提供对公众如何应对封锁措施的深入了解。

显示一段时间内与新冠肺炎症状相关的推文数量的仪表板
为了方便获取这些信息,我们开发了一个 【症状观察】 仪表板,报告每天提到症状的推文数量。目前,美国各州和英国各级(地方和上层机构、NHS 地区和国家)都提供这些计数。这项功能将在不久的将来扩展到其他国家。
我们还与 Evergreen Life 合作,分析他们健康应用程序中的数据。作为对新冠肺炎的回应,常青人寿一直在向应用程序用户提问,以深入了解疫情。例如,用户被要求报告他们是否被隔离,或者他们或他们家中的某人是否有症状。收集的数据的深度和广度令人印象深刻,可以回答无数的问题。
该团队已经开发出解决这些问题的方法,例如,一个人经历新冠肺炎症状的平均持续时间。Evergreen Life 应用程序的用户报告是零星的,因此我们看不到一个人出现症状的完整时间表。为了处理用户报告的零星性质,我们在“Stan”编程语言中定义并拟合了一个贝叶斯模型,使我们能够确定用户最有可能在 3.06 天内经历症状。

常青人寿仪表板数据显示
当用户报告一名家庭成员出现症状时,我们可以通过确定两名家庭成员患病之间的时间来了解新冠肺炎在家庭内的相互作用。我们还知道用户是否正在隔离并随后出现症状。从这些报告中,我们可以量化隔离是否会降低您患冠状病毒的几率。我们分析了今年 3 月至 6 月期间收集的数据,并确定未隔离的个人在报告未隔离的 7 天内报告症状的可能性增加了 35%。
到目前为止,我们所做的工作展示了如何利用新的数据流来加深对新冠肺炎疫情的理解。当与更传统的数据流结合时,这些新的数据流可以帮助政府做出更明智的决策来抗击病毒。
Matthew Carter 是一名博士生,他是 EPSRC 分布式算法 CDT 的成员。这篇博客最初发布在利物浦大学新冠肺炎中心。
利用这段时间学习新的数据科学技能
哪些鲜为人知的技能在数据科学家中变得越来越普遍
这段时光一去不复返,好好利用吧。不做任何有建设性的事情就让时间、日子、星期飞逝太容易了。把这段额外的时间作为一份礼物,让自己变得积极主动。

戴维·特拉维斯在 Unsplash 上拍摄的照片
这些是最常见的技能,在数据科学家中变得非常流行。
码头工人

它不需要介绍,因为容器化你的应用程序是很常见的。但是让我们用一个例子来理解,为什么它如此有帮助。
假设您在 tensor flow lite for embedded devices 的帮助下构建了一个用于对象检测的应用程序。它必须部署在 20 个树莓设备上。现在,问题是您必须将源代码复制到所有的 raspberry pie 设备上,并安装要求,还需要设置一个进程控制监视器,以确保当设备重启或启动时,应用程序立即启动。您了解这种设置的复杂性,并且您必须在 20 台设备上执行类似的设置。你很有可能会犯一些错误,并且会花相当多的时间去改正。此外,如果有任何更新,你理解的复杂性。
现在,让我们看看 Docker 对类似设置的实现。当设备启动时,您不需要任何过程控制监视器来启动应用程序,因为有一个参数 restart,只需将其设置为 always。我们将使用应用程序的 Docker 图像。这将确保每个覆盆子馅饼得到相同的代码,这种方式也可以很容易地安装。
使用 Docker 的机器学习模型部署中的动手学习,并提供各种示例。
towardsdatascience.com](/docker-made-easy-for-data-scientists-b32efbc23165)
瓶
Flask 是一个为机器学习模型或数据库创建 API 的微框架。学起来用起来超级简单。
这是使用 flask 的示例代码片段
大多数情况下,你会使用类似的脚本,你的同事,比如前端开发人员或后端开发人员会问你 API 和有效负载。
生产机器学习模型
medium.com](https://medium.com/zykrrtech/what-i-wish-i-knew-earlier-in-ml-1212f2eed73a)
PyTest(写作测试)

戴维·特拉维斯在 Unsplash 上拍摄的照片
这被认为是最无聊的事情之一,但是相信我,你必须开始写测试了。原因是当你的应用程序开始增长,很多人都在工作时,很难跟踪变化的东西,我说的变化不是指 Git。想象一下,由于某人更新了一段代码,应用程序的某个特性突然停止了工作。现在,如果有测试,在部署之前就可以很容易地检测出来。
您可能会问,如果有人在部署之前忘记运行测试用例,该怎么办。这就是 CI/CD 派上用场的地方。您可以创建一个管道来运行代码测试,然后部署它。如果测试失败,那么您的应用程序将不会被部署。
哨兵

Sentry 用于您必须在数据科学应用程序上设置的警报。我认为这是监控机器学习模型的一部分。不同之处在于,我们用它来监控错误,而不是性能。当您已经部署了您的模型,并且它仍然处于早期阶段时,监控错误是一件非常重要的事情。
您还可以使用 丨t丨e丨l丨e丨g丨r丨a丨m丨s丨 或 Slack API 将所有错误或警告发送给一个组,所有团队成员都可以看到。
许多人认为这些事情是 DevOps 的工作,但让我诚实地告诉你,它增加了你的技能,给你的简历增加了更多的价值,而且如果你在一家初创公司工作,你有机会自己做类似的事情。
绝对要结帐的附加文章
让我们看看生产级代码的一些行业标准。
towardsdatascience.com](/best-python-practices-for-data-scientists-11056edda8c7)
谢谢你..!
利用您的数据科学项目(第 2 部分)
使用 PyQt5 为 LendingClub 仪表板创建 GUI

我的 LendingClub 仪表板应用程序的图形用户界面
这是两部分系列的第二部分。查看第一篇文章,点击 此处 。
在过去的几年里,数据科学就业市场对软件工程技能和将机器学习模型投入生产的经验的重视程度显著提高。在这个故事的第 1 部分,我经历了在最终产品(理论上是面向用户的)的环境中编写利用机器学习模型的程序的一些考虑。这个过程揭示了许多启示,我相信这些启示启发了我对数据科学产品过程的了解。
在这一部分,我将重点介绍一个对许多数据科学家或学生来说可能不熟悉的过程;创建用户界面。如果你想提高数据科学就业市场的技能,学习如何为消费者包装机器学习产品,或者创建一个程序,使你的项目更容易用于个人用途,请继续阅读!
选择 PyQt5 框架
PyQt5 是一组 Python 绑定(一种从 Python 调用 C 函数的方式),允许我们创建 GUI(图形用户界面),在 C++中实现 Qt 库,只使用 Python,允许开发人员相当容易地从头创建应用程序。我也和 Kivy 和 Tkinter 一起玩过,但是 PyQt5 似乎最适合我的需要。Tkinter 是一个相当标准的 GUI 库,但它在 MAC 上有点问题,Kivy 对于移动应用程序开发来说很棒,但对于桌面应用程序来说不太有效。最终,我选择了 PyQt5,因为它非常直观和灵活。只要你熟悉创建和定义类,以及面向对象编程的一般原则,这是很容易掌握的。
我大量使用了来自编程频道“Tech With Tim”的 YouTube 教程来学习这个库和框架的基础知识。你可以在这里找到他的系列视频。
用 Qt Designer 设计界面
Qt 框架的一个我最喜欢的特性是能够用一个叫做 Qt Designer 的工具手动布局窗口。Designer 允许您拖放不同的元素(如按钮、标签、滑块等)。)拖到一个示例窗口上,这样您就可以设计一个视觉上吸引人的程序,而不用在代码中摆弄 x/y 坐标或维度。

在 Qt Desginer 中设置我的仪表板
一旦有了喜欢的布局,就可以将其保存为. ui 文件。从那里,我们可以在命令行中使用 pyuic5 命令从我们的。ui 文件。在确保您位于正确的工作目录后,只需在您选择的命令行界面中键入以下内容:
pyuic5 -x saved_file.ui -o new_file.py
该命令的“-x”部分添加了一段 Python 代码,当我们运行生成的 Python 脚本时,该代码将执行并显示程序。“-o”命令指定输出文件的名称。
一旦你完成了这些,你就可以在你喜欢的文本编辑器中打开这个文件并创建一个工作应用程序!
你可以在这里下载 Qt Designer for Mac 或 Windows 。在 Windows 上,您还可以使用:
pip install pyqt5-tools
在命令行中安装一套与这个库相关的工具。
向界面添加功能
一旦你为你的用户界面创建了一个 Python 文件,你可以试着运行它。它应该像正常程序一样显示在你的屏幕上,但是按钮或小部件都没有做任何事情。我们仍然需要用 Python 编写代码来指导我们的程序做我们想要做的事情。
这一部分显然会根据您创建的应用程序而有所不同,但是我将概述我创建 Lending Club 程序的主要步骤。
填充仪表板
在第一部分中,我概述了从 Lending Club 下载实时数据,并使用该模型预测还款可能性的步骤。完成后,我们希望在仪表板中显示贷款信息以及我们的预测。
我们自动生成的代码创建了一个类(我的名为 Ui_Form),然后是一个名为 setupUi 的方法。我们的主窗口将是这个类的一个实例,正如您可能猜到的,setupUi()将在脚本运行时创建并设置这个实例。生成我们的表或 tableWidget 的代码是自动为我们编写的,设置列维度也是如此。使用以下代码设置行数(display 是我们想要显示的数据框):
self.tableWidget.setRowCount(display.shape[0])
更多自动生成的代码创建表的列,然后用以下循环填充表:
for r in range(display.shape[0]):
for c in range(7):
self.tableWidget.setItem(
r,c,
QtWidgets.QTableWidgetItem(
str(display.iloc[r,c]))
)
很可能有一种更有效的方法来做到这一点。然而,我们的数据框可能不会超过 60 或 70 行。代码在速度方面没有留下任何需要改进的地方,所以我没有进一步优化。
添加过滤器
我希望用户能够缩小他们正在查看的贷款选项,以符合他们的个人偏好。首先,我添加了各种贷款类别的复选框(Lending Club 将所有贷款从 A 到 G 进行评级)。当程序启动时,我的程序自动检查每个等级的复选框,然后连接到一个名为“redraw”的类方法,每当单击它时,它将在接下来被定义。
self.a_box.setCheckState(2) # A state of 2 means the box is checked
self.a_box.clicked.connect(self.redraw)
的功能。connect()将一个事件(在本例中是 self.a_box.clicked,或者是改变状态的 A 框)连接到一个方法。这是我们赋予界面生命的主要方法。除了“全部”框之外,前面的代码对每个复选框都重复。这个框连接到一个单独的函数,该函数检查或取消检查所有的框(取决于状态),然后调用 redraw 方法。
接下来,我为用户添加了滑块,以便按利率或期望值/平均结果过滤显示的贷款。这个过程是相似的,除了我们必须设置最小和最大值,并且可能在初始化时为滑块设置一个值。
self.min_int.setMinimum(min(display_raw.intRate)-1)
self.min_int.setMaximum(max(display_raw.intRate)+1)
self.min_int.sliderMoved.connect(self.redraw)self.max_int.setMinimum(min(display_raw.intRate)-1)
self.max_int.setMaximum(max(display_raw.intRate)+1)
self.max_int.setValue(max(display_raw.intRate)+1)
self.max_int.sliderMoved.connect(self.redraw)
同样,无论何时移动滑块,动作都连接到 redraw 函数。
重画功能
每当更改过滤器时,都会调用 redraw 函数。它实际上做的和它听起来的一样。首先,创建一个过滤后的贷款等级列表。每当复选框被选中时,方法 checkState()为 2,因此 disp_rows 是用户希望返回的贷款等级列表。接下来,创建一个对象 newdf,它是反映过滤器状态的贷款数据框架的子集。最后,表会像最初一样被填充。
def redraw(self):disp_rows = [(self.a_box.checkState() == 2) * 'A',
(self.b_box.checkState() == 2) * 'B',
(self.c_box.checkState() == 2) * 'C',
(self.d_box.checkState() == 2) * 'D',
(self.e_box.checkState() == 2) * 'E',
(self.f_box.checkState() == 2) * 'F',
(self.g_box.checkState() == 2) * 'G',
]newdf = display[[x in disp_rows for x in display.grade] &
(display_raw['intRate']>=self.min_int.value()) &
(display_raw['intRate']<=self.max_int.value()) &
(display_raw['EV'] >= self.min_ev.value()) &
(display_raw['EV'] <= self.max_ev.value())
]
self.tableWidget.setRowCount(newdf.shape[0])for r in range(newdf.shape[0]):
for c in range(7):
self.tableWidget.setItem(
r,c,
QtWidgets.QTableWidgetItem(
str(newdf.iloc[r,c]))
)
登录屏幕
最后需要做的事情是合并一个登录屏幕。除了让它感觉更像一个“真实的”应用程序,登录屏幕还为用户提供了一个输入登录信息的简单方法(在本例中,是一个连接到 Lending Club API 的 API 键)。
为了创建一个单独的窗口,定义了一个新的类(我再次使用了 Qt Designer)。这个新的登录窗口将是程序启动时初始化的窗口,主窗口稍后创建。
一旦用户输入他们的 API 键,我们必须将一个按钮点击事件连接到一个函数。
self.ok_button.clicked.connect(self.logging_in)
当调用这个函数时,程序将用户的输入存储为一个名为 apikey 的全局变量,当我们设置主窗口时会再次调用这个变量。之后,主窗口被初始化,我们所知道的程序开始了。
def logging_in(self):
global apikey
apikey = ui.keybox.text()
Form = QtWidgets.QDialog()
uimain = Ui_Form()
uimain.setupUi(Form)
Form.show()
Form.exec_()
结论
经历这个过程教会了我很多关于将机器学习模型投入生产的生命周期、面向对象编程以及一般的应用程序开发。对于使用像 PyQt5 这样的框架可以完成的事情,我仅仅触及了皮毛。如果你有兴趣了解更多,请查看之前提到的 Tim(不是我)的 Youtube 教程、 PyQt5 文档,或者尝试自己创建一个应用或项目!
如果你想了解更多关于我的应用程序的后端,请在这里阅读第一部分*。在我的 Github* 上随意查看完整代码,这里 。
利用二元交叉熵的内生变量
如何使用内生变量做出更明智和准确的决策,而不破坏模型的基本数学和逻辑。

内生性通常是由于因果关系。时间的方向可以告诉我们变量在模型中应该如何相互关联。(burst.shopify.com)
最近,我的任务是使用具有有趣的内生变量的数据构建一个分类器——这对于告诉我的模型如何训练非常有价值。在本文中,我将详细介绍我所采取的步骤,您自己也可以采用类似的策略。最终,我们将有办法使用内生变量来训练一个模型,而不用担心它对模型假设的影响,也不用走任何逻辑捷径。很明显,使用一个不能包含在模型中的单独的数据,我们可以通过告诉它是否做了一个好的预测来改进模型。
开始之前,您应该对分类算法的工作原理和成本函数的作用有一个基本的了解。此外,对微分学有所了解会对最后一节有所帮助。

Sigmoid 函数——二元分类的关键数学公式。(维基共享资源)
什么是二元交叉熵?
二元交叉熵,也称为对数损失或逻辑损失,是逻辑回归的成本函数。Log-loss 也是一种流行的损失函数,用于深度学习模型的分类,以及像 XGBoost 这样的树集成模型。“二元交叉熵”恰好是这篇文章中探索的方法的一个特别合适的名字,因为我们将充分利用这个方程的可分性。

对数损失作为分段函数
上面是写为分段函数的二元交叉熵方程。
- T5【J】T6是损失函数
- y 才是真正的标签
- p 是模型的连续输出,预测概率 y =1

逻辑损失是并排绘制的两个方程。
我们的预测离实际值越远,成本函数值就越高。
它也可以合并成一个等式:

对数损失作为一个等式
请注意,该等式简化为 y=1 和 y=0 的第一个函数片段,这是 y 的唯一可能值。
快速旁注…
损失函数、成本函数、和目标函数经常互换使用。“损失函数”指的是与单个观测相关联的损失,而“成本”指的是整个数据集。“目标函数”只是一个函数被最小化或最大化的更一般的术语。
渐变和粗麻布
在分类模型中,目标是找到优化 p 的模型参数,最小化所有观察的该函数。为了做到这一点,我们需要使用函数的梯度,有时也是 hessian。这些是通过分别对模型参数取一阶和二阶导数而得到的。
对数损失的梯度和 hessian 如下所示。在讨论了原始成本函数的修改版本后,我们稍后将回到这个问题,并更深入地研究数学。

什么是内生变量?
内生变量是依赖于模型输出的变量。通常,人们可能将模型的目标变量称为内生变量,但在某些情况下,可能不止一个。
例如,如果一个模型预测某人是否会买一只宠物狗,他们是否会给这只狗取名为“矮胖”将取决于他们是否买过这只狗。如果我们试图在“这个人会买狗吗”模型中使用被命名为 Chunky 的狗作为变量,就会出现两个问题。
- 因果关系:买狗先于命名。未来发生的事情不能决定过去的事情。
- 训练偏差:只有买过狗的人才会有关于他们是否给自己的狗起名为矮胖的有用信息。有些人差点就买了一只狗,并给它取名为矮胖,但他们没有买。如果我们在分类模型中使用“矮胖”变量,我们只能对一组结果这样做。我们无法得到一只不存在的狗的名字。
想一想第二点……一个人愿意给他们的狗起名叫矮胖,这可能首先就说明了这个人和他们对狗的喜爱。我们稍后将回到这个概念。
分类模型的其他内生变量示例:
- 预测是否会下雨的模型中的降雨量
- 就职演说的长度预测某人是否会赢得政治选举的模型
- 在一个预测学生是否能通过课程的模型中,最终成绩达到了 T21

一个雨量计,本意是测量 后 下雨。(维基共享)
到目前为止,你会注意到四个例子,有时变量是可量化的,有时是不可量化的。例如,你可以测量零英寸的降雨量,但是你不能得到一个没有发生的演讲的长度。有不同的方法来估算缺失值,但那是采用了不必要的假设,给我们的预测添加了不必要的警告。在下一节中,我们将讨论一个通用的解决方案。
我们如何修改二元交叉熵,为什么我们要这样做?
让我们再看一个降雨的例子。一个预测“无雨”的模型实际上只下了半英寸的雨,从数量上来说,这比一个预测“无雨”的模型实际上只下了六英寸的雨要少得多。使用这些信息来改进模型可能是个好主意。
有时,这种关系并不十分清晰,但是如果内生变量和目标变量之间的关系可以被合理化并以数学方式公式化,,那么它就可以被纳入成本函数。
这将“惩罚”一个模型,而不是预测一个错误更多的结果,而不是一个错误更少的结果,从而相应地为成本函数产生更高或更低的输出。
修改成本函数有什么作用?
修改成本函数将改变模型参数的误差表面的最小值和斜率。这意味着收敛可能发生在模型参数被更好地概括的地方,因为模型现在包含了额外的信息。
为什么不用回归模型来代替呢?
在某些情况下,预测正在讨论的内生变量,然后建立一个决策边界来预测原始目标变量可能是有效的。然而,可能会出现一些问题:
- 带有决策边界的回归可能无法很好地回答最初的问题。回到降雨量的例子,累积十分之一英寸的降雨量仍然是雨,在二进制尺度上,这仍然是 1。
- 连续内生变量的分布可能不适用于大多数参数建模技术。(例如,参见下面的 Tweedie 分布)。
- 对于缺失的内生变量来说,一个合理的值可能并不存在。在某些情况下,估算值可能是合理的,但在我的特定项目中却不是这样。

特威迪分布
另一个想法可能是使用内生变量为 y 设定一些修改值,但是**您必须考虑这对成本函数有什么影响。**如果 p > 1,将使用原始成本函数来定义值。
一种改进的分类代价函数。
由于二进制交叉熵可以像分段函数一样处理,我们可以只修改方程的一部分。我们将添加一个比例因子, s ,它是我们内生变量的函数,对于 y=1 的情况。该比例因子是所讨论的内生变量的函数,在相当中性的情况下取值 1,当成本应该更高时取值高,当成本应该更低时取值低。
这最终会根据内生变量的值,或多或少地惩罚模型。现在不仅仅是估计是否错误的问题,还有另一个因素在某些情况下发挥作用。你可以原谅这样的情况,你可以说类似于“这个估计在技术上是错误的,但是我不在乎这个其他的因素,所以我不希望这个模型有太大的改变。”本质上,这就是改变成本函数的作用。

现在我们可以看到,对于我们缺失的内生变量的值,我们估算什么并不重要,因为对于 y=0 的情况,它在数学上是不相关的。当将原始函数转换为非分段函数时,这仍然成立。

微积分!
一些机器学习库,以及任何定制的梯度下降实现,将需要计算成本函数的梯度。有时,也需要粗麻布。
现在我们将计算这个二进制交叉熵的修改版本的梯度和 hessian。
梯度

首先,求 J 对 p 的偏导数。

请记住,p 是模型输出的 sigmoid 变换,我们称之为“x”

上面显示了 s 形曲线的导数。

代入上式,求梯度。

修改后的成本函数的梯度,使用我们上面计算的两个偏导数。
对于 s =1 和 y =0 的情况,这简化为原始梯度。我们现在已经找到了修改后的成本函数的一阶导数,这是基本梯度下降算法所需要的。
打包麻布
对于更复杂的算法,我们也需要二阶导数。请记住,我们已经将 x 定义为预转换模型输出。

定义黑森人。

替代。

重构。

我们可以用 p 来继续区分。

换人后。

求导之后。

将 p 替换回。

简化。
这简化为原始的 hessian 对于 s =1,以及 y =0 的情况。
摘要
仅仅因为一个变量是内生的,并不意味着它不能用来改进一个模型。在这里,我提出了一个调整逻辑损失的建议,以纳入通常不会在模型中使用的数据,这可能会改善结果。对如何使用这些变量的决策进行合理化仍然很重要,但有足够的空间进行实验。
后续步骤
这是我自己想出来的方法,以前没有遇到过类似的解决问题的方法。如果你已经找到了关于这个主题的其他资源,请在评论中分享!

矮胖的
利用您的数据科学项目(第 1 部分)
将 Lending Club 机器学习模型投入生产

我的 LendingClub 仪表板应用程序的图形用户界面
在过去几年中,数据科学行业最强劲的趋势之一是越来越重视在生产环境中部署机器学习模型。雇主期望的不仅仅是特征工程和建模。你执行至少一些基本软件工程任务的能力可能决定求职过程的成败。
我目前正在找工作。特别是因为我来自一个非技术背景,我不想把自己限制在只能拟合、预测和评分模型,而不能走出沙盒。在大多数现实世界的应用程序中(没有双关语的意思),一个模型将是一个更大的产品的一部分。构建一个将模型投入生产的简单程序,向您介绍生产环境特有的想法。就我而言,我还想证明我可以学习一个新的库或用例,而我并没有明确的课程。
还有一个更明显的原因让你想把你的模型投入生产:你可以使用它,或者和其他人分享它!我住在一个不允许投资者使用 Lending Club 的州,我希望能够与能够使用它的朋友或家人分享我的模型,无论他们是否受过 Python 训练。
对于那些不熟悉的人来说,LendingClub 是一个点对点的借贷市场。实质上,借款人申请贷款,并由 LendingClub 分配一个利率。个人投资者可以选择贷款来资助或投资,以类似于众包活动的方式为贷款筹集资金。作为投资者,你的回报根据你选择的贷款(利率和违约率)而变化。因此,如果你能更好地预测哪些借款人会还贷,你就可以期待更好的投资回报。
在我的一个个人项目中,我建立了一个模型,可靠地选择了一个超过市场平均水平的贷款组合。在我的模型被完全微调后(即“数据科学”部分完成),将模型投入生产有 5 个主要步骤。
保存/序列化您的模型
当你在 jupyter 笔记本上展示一个模型时,你的模型运行多长时间并不重要。然而,在生产环境中,速度通常很重要。即使在我们不断收到新数据的情况下,模型也很少需要立即重新训练。根据用例的不同,模型的训练可能只发生一次,在每天的某个时间,由外部事件触发,或者完全不同的事情。就我们的目的而言,在开发应用程序时,训练我们的模型一次就足够了。
为了以功能性的方式利用我们的模型,我们需要保存结果以供将来使用。这也称为序列化。在 python 中实现这一点的一个简单方法是使用 pickle 库
从库文档中:
[pickle](https://docs.python.org/3/library/pickle.html#module-pickle)模块实现了二进制协议,用于序列化和反序列化 Python 对象结构。【pickle】是将 Python 对象层次转换成字节流的过程,【unpickle】是逆操作,将字节流(来自二进制文件或类字节对象)转换回对象层次。
我们需要知道的是,这个库为我们提供了一个简单的方法来保存一个合适的模型,这样我们就可以在应用程序启动时加载它,而不是每次我们想要运行程序时都必须适应一个可能很耗时的模型。
我设置了两个 python 程序。第一个包含所有的代码来训练我的应用程序将要使用的模型。程序做的最后一件事是保存这个模型以备将来使用。
rf.fit(X,y)
import pickle
pickle.dump(rf, open('rf.sav', 'wb'))
然后,在我们面向用户的程序中,当我们想要重新加载模型时:
rf = pickle.load(open(‘rf.sav’, ‘rb’))
这将在 Python 中加载我们的对象的实例,并允许我们像刚刚创建它一样使用它。
加载实时数据—使用 Lending Club API
这一步会有很大的不同,取决于你正在处理的具体问题。它可能涉及抓取网页、加载放入数据库的数据,或者为查看您的网页的客户加载客户信息。在这种情况下,我们希望利用我们的模型让我们知道哪些当前可资助的贷款可能给我们带来最高的投资回报。为了做到这一点,我们需要一个实时的数据源。
幸运的是,Lending Club 提供了一个易于使用的 API,我们可以使用请求库与之通信。Requests 允许您非常容易地发出 HTTP 请求,允许您与 web 页面和 API 进行交互。你可以在这里找到文档。
前两行代码为我们的请求指定了变量和参数。我们的变量‘API key’是在用户登录(并输入他们的 API 密钥)时定义的,而‘Content-Type’规定了 API 返回的数据格式。Lending Club 列出了一天中四个不同时间的新贷款。参数“showAll”告诉 API 是返回所有可资助的贷款,还是只返回最后一个列表中的贷款。
header = {‘Authorization’: apikey, ‘Content-Type’: ‘application/json’}params = {‘showAll’: ‘True’}
以下代码从 Lending Club API 发出请求:
url = ‘https://api.lendingclub.com/api/investor/v1/loans/listing’resp = requests.get(url, headers=header, params=params)
最后,我们从响应中提取贷款信息,并将其格式化为 pandas 数据框。
loans = resp.json()[‘loans’]loans = pd.DataFrame.from_dict(loans)
我们现在有来自 LendingClub 的熟悉格式的实时数据。
处理实时数据
我不会在这里做得太深入,但是值得注意的是,如果您关心准确的预测,那么您对测试和训练数据执行的任何预处理步骤也必须对实时数据执行。这意味着离群值的去除、转换等。
这对于我们在数据清理和特征工程过程中所做的一些决定有影响。例如,在生产中以相同的方式实现对数转换比 box-cox 转换要简单得多。
使用模型预测还款概率
从程序上来说,这与预测测试数据集的概率没有太大区别。然而,这将是你第一次为动态数据释放模型,你不知道最终结果。你在预测未来!这是你通过几个月的教程或课堂作业努力争取的时刻,所以拥抱它吧!
用用户界面实现模型
这是另一个部分,将根据用例的不同而显著变化。有些模型,比如推荐系统,会影响应用程序的行为。我的情况简单一点。我只是显示预测概率以及实时贷款信息。
我的第一个程序只是一个脚本,它将带有模型结果的数据框打印到终端。这确实迫使我思考上面的许多教训,我认为这是一个完全富有成效和有价值的练习,如果就此而言的话。然而,我也想构建一个图形用户界面(大多数人认为是程序或应用程序)。我觉得这将大大提高我与他人分享我的程序的能力,我也觉得我不能创建一个应用程序是我应该补救的事情。
我选择 PyQt5 作为我的开发库。这个过程相当广泛,我在本文的第二部分中写了更多。有几个在线演练,以及文档,使之有可能拿起。Qt designer 也非常有用,它可以让你直观地设计一个窗口,并为你生成代码。
结论
作为申请人,将你的模型产品化,说明你比只知道在 jupyter 笔记本上评价模型的人更全能。你实际上已经考虑了你的模型在现实世界中的用途,并且潜在地权衡了你在更理论化的项目中不会有的权衡。创建一个图形用户界面显示了学习一个新的库和超越典型数据科学工具箱的分支的能力。
根据您的具体项目,将您的模型放入最终产品中可能会有一些实实在在的好处。我非常期待进一步开发我的应用程序和模型,这样我就可以与朋友和家人分享了。如果您目前正在寻找一份数据科学方面的工作,或者碰巧有一个有趣的副业项目,利用它将是有益的,您应该考虑尝试做同样的事情!
如果你对我如何为这个程序设置图形用户界面(GUI)的基础知识感兴趣,请在这里查看第二部分。
疫苗学 3.0 —数据科学的作用
回顾
当全世界屏息等待新冠肺炎疫苗的时候,这篇文章探索了数据科学对疫苗学的未来意味着什么?
T 何本文的目的是在数据科学技术的范围内揭示现代疫苗学的发展。它试图回答一些问题,这些问题引起了越来越多的关注,因为全世界都在看着科学家和制药公司在创纪录的时间内竞相开发武汉疫情病毒的疫苗。例如:
- 开发现代疫苗的挑战是什么?
- 在现代人工智能和数据科学工具的保护下,疫苗学 3.0 是如何发展的?
这是一篇综述文章,重点关注数据科学,但目标是涵盖疫苗学和相关概念的要点。因此,在尝试实现回顾数据科学在现代疫苗学中的机遇/成就的目标之前,有必要粗略浏览一下该领域中几个关键术语的定义。
定义
疫苗学是与疫苗开发有关的医学分支。其基本思想是将无害的实体引入体内,引发机体针对致病病原体的免疫反应,所述致病病原体持续存在于体内,从而防止未来的感染。根据引入体内的实体不同,疫苗有三种。

全病原体疫苗(传统)引入死亡或减弱的病原体以引发终身免疫。亚单位疫苗仅由病原体成分(或抗原)和佐剂组成。佐剂是增强疫苗所需免疫反应的物质。必须提到的是,佐剂的安全性还存在争议。核酸疫苗基于引入编码抗原的遗传物质的方法,针对该抗原需要免疫。粗略地说,疫苗学 1.0 和 2.0 这两个术语可以分别归因于前两种疫苗的开发。**
疫苗学 3.0 或反向疫苗学 、哪个更近、涉及到病原体基因组信息使能的抗原发现过程。从解题的角度来看,疫苗学 3.0 类似于来自物理学的第一原理。它首先获得候选疫苗的流行病学信息,并对宿主-病原体相互作用进行建模,从而有效地减少候选疫苗的庞大列表。**
组学 数据的爆炸式增长使这成为可能;组学是用来指来自(花式!)学科即。转录组学、蛋白质组学、代谢组学、细胞组学、免疫组学、分泌组学、表面组学或互作组学。由于这种大数据的支持,与之前自下而上和假设驱动的方法相比,逆向疫苗学本质上成为了一种自上而下的方法。因此,组学术语的最新补充是疫苗组学,其定义为研究疫苗诱导的免疫反应。
理解了这些关键术语,尽管是高层次的,并了解了疫苗开发的一些背景,我们可以深入研究数据科学的方法学,这种方法学使疫苗学 3.0 成为可能。为了有效地做到这一点,让我们列出疫苗开发中的关键挑战;这是逆向疫苗学的新范例,旨在有效解决这一问题。针对每一项挑战,强调如何利用数据科学和大数据技术。
疫苗开发的关键挑战
疫苗的开发和交付存在大量问题。例如,其中一个社会问题是反 vax 运动的增加。世界人口老龄化是另一个问题。梅奥诊所的科学家在的一篇文章中列出了当前的四大挑战,这里正在讨论。
1.对免疫学的不完全理解
人体及其免疫系统极其复杂,包含大量组件。尽管近几十年来取得了相当大的进展,但目前对这一系统的了解还远远不够。科学家不可能准确预测特定疫苗接种和相关感染的免疫系统行为。
这就是疫苗组学和系统生物学基于数据的计算方法为填补知识空白提供的机会。值得一提的是美国著名科学家、ACM 图灵奖获得者唐纳德·克纳特(Donald Knuth),他激励了多代计算机科学家。当他说(承蒙:计算机扫盲书店,公司)时,Knuth 对计算生物学的范围非常乐观:
“我无法像对生物学一样对计算机科学充满信心。生物学很容易就有 500 年的令人兴奋的问题要解决,它处于那个水平。”唐纳德·克纳特。
这是他 1993 年采访的节选,事情朝着他预期的方向呈指数级发展。计算硬件、云技术以及最重要的深度学习打开了这一领域创新的闸门。

免疫系统中的一个主要潜在活动是由相对自主和特化的细胞执行的,这些细胞被称为 T 细胞;它们通过它们的表面受体相互激活进行交流。最近,测量(全体)细胞状态、功能及其产物以及它们的基因编码的技术已经变得可行。这实质上产生了大量数据,可用于开发更全面的免疫系统模型——这在传统上是不可能的。因此,它催生了系统免疫学领域— 数据科学家的另一个游乐场。它还使科学家能够开发更好的佐剂,并提高免疫反应的持久性。
2.人类群体的可变性
人类群体的免疫反应因遗传倾向而异。这使得开发对所有人群都有效的单一疫苗变得困难。解决这个问题的方法是使用全基因组关联研究 (GWAS),其目的是识别特定感兴趣特征下的常见遗传变异。
对于门外汉,让我稍微深入一下基因组研究。生物是由细胞组成的。我们的每个细胞都由一个细胞核组成。细胞核由组织成染色体的 DNA 组成。DNA 有四个碱基,分别用 T、A、C、g 表示 DNA 测序 是确定一个 DNA 分子中这些碱基的物理顺序。在基因组数据科学中,基本上,从受试者收集样本,并进行 DNA 测序以定义受试者的基因组。你的基因组决定了你的身体如何工作。人类基因组的总长度超过 30 亿个碱基对。基因组由小的功能片段组成,这些功能片段可能与特定的过程和特征有关。基因组学涉及基因组的结构、功能、进化和绘图。
人类基因组计划是世界上最大的合作生物学项目,始于 80 年代,目标是识别和绘制人类基因组的所有基因,并于 2003 年实现。
回到 GWAS,它的主要目标是将特定的基因变异与特定的疾病联系起来。它包括扫描许多不同人的基因组,寻找可以用来预测疾病存在的遗传标记。最近的一个例子是,GWAS 针对新冠肺炎的一项研究报告显示,O 型血患者的行为差异很大,与 T2 a 型血患者的高风险相比,O 型血患者表现出了保护作用
从数据科学的角度来看,除了在实现更快更便宜的测序技术方面的巨大贡献外,许多机器学习方法也使受益于 GWAS。这篇文章很好地总结了许多 ML 对 GWAS 的贡献,例如,不同的方法如何处理“维度的诅咒”——数据的特征比样本多——这是 GWAS 的一个常见问题。
3。病原体可变性
伦敦大学国王学院的研究人员刚刚报道了美国、英国和瑞典的六种新冠肺炎病毒,患者人数只有几千人。他们使用了患者症状进展的时间序列数据,并对数据进行聚类,以确定归因于病毒可能变异的 6 种不同模式。属于这两类的病人需要重症监护。
病原体的可变性从两个方面对疫苗开发提出了挑战。首先,如果毒株的数量很高,那么制造对所有毒株都有效的疫苗是困难的,其次,病原体可能不断变异,需要不断地重新配制疫苗。
结合疫苗组学的基因组测序再次成为应对这一挑战的重要方法。
流感病毒疫苗开发的具体案例凸显了数据科学应对这一挑战的重要性。

流感疫苗的数据科学
流感有两种不同的表面蛋白,H 和 N,它利用这两种蛋白进入宿主细胞。这些蛋白质有多种变体,H1N1 和 H3N2 是两种最常见的亚型。1968 年至 1969 年,H3N2 杀死了世界上超过 100 万人。1918 年的 H1N1 导致了西班牙流感。****
由于上述病原体的可变性,流感疫苗每年都要重新配制。根据病毒以前和当前变种的数据(基因组),疫苗学家使用机器学习算法对病毒的未来行为进行建模和预测。实现这一点的方法之一是构建一个系统发育(进化)树。系统进化树代表了不同菌株在遗传学方面的接近程度。最大似然法系统发育分析( PAML )是一个帮助构建和分析树的软件包。为此,非负最小二乘回归是可以采用的另一种技术。同样,贝叶斯分析在这个研究中也是相当流行的。
4.疫苗安全
考虑到我们对免疫系统和疫苗与我们身体相互作用方式的理解存在差距,确保安全和避免新发明疫苗的任何副作用至关重要。在这个新闻和假新闻像病毒一样传播并带来许多不良后果的社交网络时代(例如,在最近疫苗管理安全失误后,最近世界上爆发了麻疹),疫苗安全极其重要。另一个例子,CDC 在这个链接提供了一个关于疫苗的九个安全问题的列表。最近一次是在 2013 年召回一批 HPV 疫苗。
登革热疫苗是一种疫苗,当给没有登革热病毒感染史的健康个体接种时,由于一种称为抗体依赖性增强** (ADE)的现象,实际上可能会增加严重登革热的风险。ADE 导致病毒的传染性增加,并已在登革热病毒、HIV、黄热病毒、寨卡病毒和冠状病毒中观察到。**
状语 是一个新的研究领域,旨在识别、表征和预测对疫苗的不良免疫反应。这些研究将我们带到了个性化疫苗时代的门口。它涉及复杂的生物统计学方法,以确定模式和因果网络的大量非常高的维度数据。
迈向疫苗学 3.0 的最新数据科学技术
现在让我们来讨论竞争对手互联网公司最近将人工智能和人工智能用于疫苗学的两个突出应用。
Alphafold(谷歌 DeepMind)
生物学的核心挑战之一是蛋白质折叠。蛋白质折叠问题是蛋白质的氨基酸序列如何决定其三维结构的问题。换句话说,蛋白质的结构可以从它的氨基酸序列预测出来吗?这很重要,因为通过折叠获得特定的三维结构表征了蛋白质的生物功能。据估计,人体含有 80,000 到 400,000 种蛋白质。
例如,人类肺细胞含有与新冠肺炎互补的特殊蛋白质,这种蛋白质激活病毒引发感染。因此,当设计针对新冠肺炎病毒的疫苗或药物时,解决蛋白质折叠问题至关重要。
Alphafold 是 DeepMind 开源的基于深度学习的蛋白质结构预测工具。本质上,它使用蛋白质数据库和蛋白质序列数据集(通过一段时间的 DNA 测序建立)来寻找与目标序列相似的序列。通过关联序列和靶序列之间氨基酸残基位置的变化,有可能预测残基之间的接触,然后用于预测蛋白质结构。

在引擎盖下,Alphafold 使用卷积神经网络来预测氨基酸残基之间的距离和扭转角度。这些值用于确定蛋白质的统计潜力。用一种非常简单的方式,把这想象成蛋白质系统的能量,蛋白质会以这种能量最小化的方式折叠自己。使用梯度下降,这种蛋白质潜力被最小化,以获得更精确的扭转角和距离。
DeepMind 已经表明,Alphafold 比最先进的方法更准确,它可能有利于蛋白质科学的所有领域。更重要的是,这可能会加快寻找对抗疫情的疫苗和药物的步伐。
线性设计(百度研究)
在讨论百度的 LinearDesign 之前,先简单说一下 Moderna 是有意义的。Moderna 是一家生物技术公司,致力于药物和疫苗开发,其技术完全基于 mRNA。对于外行人来说,RNA 是一种核酸,像 DNA 一样,但是是单链的。RNA 的关键过程之一是蛋白质合成。信使 RNA (mRNA)携带从 DNA 复制的遗传信息到细胞核外的蛋白质合成位点。然后,tRNA 和 rRNA 执行后续活动,本质上是作为氨基酸(蛋白质的构建模块)必须如何相互连接以形成蛋白质的指导者。

像 Moderna 这样试图基于 mRNA 制造疫苗的公司,基本上是试图开发一种模拟病毒实际 mRNA 的合成 mRNA。病原体利用病毒 mRNA 在宿主体内构建病毒蛋白。因此,基于合成 mRNA 的疫苗的目标是欺骗免疫系统产生抗体,然后抗体将对抗真正的病毒。
这是一种开发病毒的新模式,目前仍处于激烈的研究中。Moderna 还试图利用这项技术制造冠状病毒疫苗,如果成功的话,这可能是第一个。
百度开发的 LinearDesign 是一款针对疫苗开发设计优化 mRNA 序列的工具。简而言之,这就是他们的工作要解决的问题——可能导致编码相同蛋白质序列的可能 mRNA 的数量呈指数增长,这使得寻找最佳 mRNA 设计变得困难。他们将这个设计问题简化为形式语言理论和计算语言学中的经典问题,然后使用近似算法来确定一个快速解决方案,与精确搜索确定的解决方案相比,该解决方案具有较高的准确性。
百度的 LinearDesign 受他们之前的技术 LinearFold 和类似工具的启发,对于开发新冠肺炎疫苗至关重要,一般来说,也是疫苗学 3.0 的基础。
特别是,基于 mRNA 的疫苗开发技术可能会开创一个疫苗开发和管理的新时代,这与 PlanetLabs 在太空技术中通过使用敏捷方法简化卫星的进化和开发非常相似。为此,数据科学和人工智能技术将发挥关键作用。
当前的疫情正在成为创新疫苗开发技术发展的催化剂,而数据科学是关键的推动者。
在 Linkedin 上找到我@https://www.linkedin.com/in/deepak-karunakaran
由于随机初始化和选择偏差,验证集过度拟合
“提前停止”真的能保证 AI 模型泛化吗?

hawk 的标志:AI,我在这里担任首席数据科学家,感谢同事们的不断支持。
深度学习模型被称为通用函数逼近器。他们的优势来自于他们对给定的输入和输出之间的关系进行建模的强大能力。然而,这也是他们对问题提出一般化解决方案的主要弱点,也是他们如此倾向于过度适应(记忆)训练集而不使用新数据的原因。目前确保深度学习模型泛化的方法是简单地使用验证集来决定为训练模型应该进行多少次迭代(时期);或者换句话说,提前停止。然后,数据科学家将在盲测试集上测试训练的模型,以确保没有训练超参数也是过度拟合的。到目前为止,这种方法在处理这样的问题时效果很好,在这些问题中,你可以有这样的独立数据集,每个数据集都有相似的分布。然而,对于金融或医疗保健(这两个领域我都有很深的经验)中的一些问题来说,情况并非如此,对于强化学习问题来说当然也是如此,在强化学习问题中,新数据既依赖于环境,也依赖于行动。
早期停止经常失败的关键场景示例!
在医疗保健中,部署的模型对每个看不见的患者进行概括是极其重要的,这些患者既不会出现在训练集中,也不会出现在测试集中。由于这个原因,通常的做法是用留一个受试者出来交叉验证来验证模型,其中每个留一个受试者将是不同的,并试图基于每个折叠的统计来决定早期停止时期;这在人口众多的情况下是可以接受的。在金融领域,情况甚至更糟,因为人们需要对未来数据进行归纳;同时,股票市场数据的分布总是变化的,几乎不可能有如此同质的训练、验证和测试集。一些定量分析师训练人工智能模型进行交易,尽管他们在实验期间的回溯测试中表现很好,但他们在真实数据中往往表现不佳。最后,大多数强化学习方法都有再现性问题(特别是当使用不同的随机种子时),而像增强随机搜索这样的无梯度方法在这些方法中表现得更好。最后,相同模型的集合(甚至)通常会产生更好的结果。
良好的验证性能并不是概括的指标!
深度学习模型(迭代机器学习模型)的持续学习也是一场噩梦,需要同样的验证。事实上,如果使用相同数量的历元用相同大小的新数据集从头开始重新训练,人们永远无法确保深度学习模型在泛化方面收敛到相同或更好的解决方案。简直是一团糟!经过多年训练模型的经验,我已经确定了这种情况的主要原因是验证集过度拟合。由于任何机器学习模型都是随机初始化的,它们可以收敛到局部最小值,这在使用验证集时表现很好,如果你在这一点上提前停止训练,模型的泛化能力将低于最优解。对于我作为例子给出的问题,没有办法通过检查验证集来确保这种情况不会发生。我发现了一种仅通过使用训练集来检测过度拟合的好方法,即跨许多小批量检查模型性能。由于该模型在过拟合期间会失去其泛化能力,当试图学习其他模型时,它会在某些小批量中表现更差,因此在训练期间损失的变化会在它们之间波动。
“基于群体”的持续再平衡投资组合选择!
最近,我在研究投资组合选择(优化)问题时遇到了这个问题,我终于发现了一个解决方案,它可能适用于其他领域。我发明了一种方法,我将其命名为“基于群体的常数再平衡投资组合选择”。对于这种方法,我使用 PyTorch 中的自动签名功能在 GPU 中同时优化 8192 个投资组合。然后,我使用前 50%投资组合的平均权重来检查验证集的性能,而不是从正在训练的投资组合权重中选择一个候选。(当它们都用于取平均值时,该方法收敛到类似的结果,并且使用前 50%只是加速了训练过程。)总之,结果证明我能够获得平滑的训练和验证曲线,我能够使用该曲线来确定没有随机初始化偏差的早期停止时期。这种方法的另一个有趣的可能用途是同时进化的对抗例子,用于攻击深度学习模型。与现有的进化优化方法的主要区别在于,在所提出的方法中,每个候选项被独立地优化。关于该方法的进一步细节如下:
关于提议的“基于群体”的常数再平衡投资组合选择方法的陈述。
文献中现有的投资组合选择方法已经过时了!
以前的投资组合选择方法只解决了最优购买和持有投资组合的选择,但没有帮助选择一个不断再平衡的投资组合。同时,一个最小波动率常数再平衡投资组合,当简单持有时没有正回报,也可以由于均值回复而产生利润。因此,我更倾向于在交易成本(1%)之下,为给定的交易政策(如 UCRP)和风险调整后的回报选择一个最佳投资组合。此外,用于决定何时重新平衡回所选恒定投资组合的散度阈值也与投资组合权重一起被优化。据我所知,这也是第一个投资组合选择方法,通过模拟策略来优化投资组合以及定义的交易策略的参数。奖励函数作为有效前沿优化的替代。
80%胜率和 20 倍盈亏比的战略源代码
总之,这种方法使我能够为给定的交易策略(在这种情况下是常数再平衡投资组合)找到通用的投资组合权重和参数,并定义风险敏感的回报函数。事实上,我已经能够优化这样一个交易策略,在 QuantConnect 进行的样本外回溯测试中,它能够实现 80%的胜率和 20 倍的盈亏比。为了展示这种方法的优势,我在下面的链接中分享了这种策略的源代码;这样任何人都可以自己复制结果。请注意,即使在训练集上也很难找到这样的策略。最后,我期待其他研究人员为我在本文中定义的问题开发更多的解决方案;我希望它能引发更多高质量的研究来解决人工智能中的这个严重问题。
在基于群体的常数再平衡投资组合选择的策略参数的优化过程中,不利用该回测期。
www.quantconnect.com](https://www.quantconnect.com/terminal/processCache?request=embedded_backtest_dfc0fa7d17e0029e9a7dee3483fe8b7c.html)
感谢阅读!作者(我)的简短自动传记:
我是一名前院士(T-Labs,微软研究院)和企业家(OTA 专家,客厅),他改革了工作中的帕金森病治疗(ConnectedLife ),同时入侵了国内的股票市场。我以前在著名的研究机构工作过,包括微软剑桥研究院计算机媒介生活实验室的社会数字系统(人类体验和设计)组和德国电信创新实验室(T-Labs)的质量和可用性组。我领导了几个关于深度学习、机器学习、模式识别、数据挖掘、人机交互、信息检索、人工智能、计算机视觉和计算机图形学的研究项目;并在许多会议和期刊上合作发表了 35 篇论文。我目前在 hawk:AI 从事金融科技领域的工作,担任首席数据科学家。
有价值的数据分析与熊猫价值计数

使用这个简单的工具,你可以做比你想象的更多的数据分析
流行的 python 数据科学库 Pandas 中的**value_counts()**函数是一种快速计算单个列中唯一值的方法,也称为一系列数据。
这个函数对于在 Pandas 数据帧中包含的特定数据列上快速执行一些基本的数据分析非常有用。关于熊猫数据帧的介绍,请看上周的帖子,可以在这里找到。
在下面的文章中,我将向你展示一些使用这个工具进行数据分析的技巧。这篇文章将向你展示如何在你的代码中添加一些东西,你就可以使用这个函数进行大量的分析。
数据
在本文展示的例子中,我将使用来自 Kaggle 网站的数据集。它是为机器学习分类任务而设计的,包含有关医疗预约的信息和一个目标变量,该变量表示患者是否赴约。
可以在这里 下载 。
在下面的代码中,我已经导入了我将在整篇文章中使用的数据和库。
import pandas as pdimport matplotlib.pyplot as plt
%matplotlib inlinedata = pd.read_csv('KaggleV2-May-2016.csv')
data.head()

来自 Kaggle.com 的医疗预约失约数据集的前几行
基本计数
可以通过以下方式使用value_counts()函数来获得数据集中某一列的唯一值的计数。下面的代码给出了性别列中每个值的计数。
data['Gender'].value_counts()

要按升序或降序对值进行排序,我们可以使用排序参数。在下面的代码中,我添加了**sort=True**,以降序显示年龄列中的计数。
data['Age'].value_counts(sort=True)

与 groupby()结合
value_counts 函数可以与其他 Panadas 函数结合使用,以获得更丰富的分析技术。一个例子是与**groupby()**函数结合。在下面的例子中,我正在计算性别列中的值,并应用groupby()来进一步了解每组中未出现的人数。
data['No-show'].groupby(data['Gender']).value_counts(sort=True)

使标准化
在上面的例子中,显示绝对值不容易使我们理解两组之间的差异。更好的解决方案是显示每个组中唯一值的相对频率。
我们可以将 normalize 参数添加到 value_counts()中,以这种方式显示值。
data['No-show'].groupby(data['Gender']).value_counts(normalize=True)

扔掉
对于有大量唯一值的列,value_counts()函数的输出并不总是特别有用。一个很好的例子是年龄列,我们在这篇文章的前面显示了它的值。
幸运的是,value_counts()有一个bin参数。此参数允许我们以整数形式指定箱(或我们想要将数据分成的组)的数量。在下面的例子中,我添加了**bins=5**来将年龄分为 5 组。我们现在有了每个箱中的值的计数。
data['Age'].value_counts(bins=5)

同样,显示绝对数字并不是特别有用,所以让我们也添加normalize=True参数。现在我们有了一个有用的分析。
data['Age'].value_counts(bins=5, normalize=True)

与 nlargest()结合使用
在我们的数据集中还有其他列有大量的唯一值,宁滨仍然不能为我们提供有用的分析。一个很好的例子就是邻域列。
如果我们简单地对此运行 value_counts(),我们会得到一个不是特别有见地的输出。
data['Neighbourhood'].value_counts(sort=True)

展示这一点的更好方法可能是查看排名前 10 位的社区。我们可以通过结合另一个名为**nlargest()**的熊猫函数来做到这一点,如下所示。
data['Neighbourhood'].value_counts(sort=True).nlargest(10)

我们还可以使用**nsmallest()**来显示排名最末的 10 个街区,这可能也很有用。
data['Neighbourhood'].value_counts(sort=True).nsmallest(10)

测绘
另一个方便的组合是 Pandas 绘图功能和 value_counts()。将我们从 value_counts()获得的分析显示为可视化的能力可以使查看趋势和模式变得容易得多。
我们可以显示以上所有的例子,以及 Pandas 库中可用的大多数绘图类型。可用选项的完整列表可在这里找到。
让我们看几个例子。
我们可以使用条形图来查看排名前 10 的社区。
data['Neighbourhood'].value_counts(sort=True).nlargest(10).plot.bar()

我们可以制作一个饼图来更好地显示性别栏。
data['Gender'].value_counts().plot.pie()

value_counts()函数通常是我进行数据分析的第一个起点,因为它使我能够非常快速地绘制趋势图,并从数据集中的各个列中获得洞察力。本文已经给出了可以使用该函数的各种类型的分析的快速概述,但是该函数有更多的用途,超出了本文的范围。
感谢阅读!
我每月都会发一份简讯,如果你想加入,请点击此链接注册。期待成为您学习旅程的一部分!
价值函数逼近—控制方法
强化学习之旅
未知、无模型环境中的控制算法
欢迎回到我的强化学习专栏。今天,我们将继续建立在我的上一篇关于价值函数逼近的文章的基础上。在上一篇文章中,我们展示了价值函数逼近中使用的政策预测方法,这一次,我们将看看控制方法。
我在专栏的旧帖子中提到了这些帖子中使用的很多符号。此外,像往常一样,为我提供了对强化学习世界的惊人见解的资源将在帖子底部链接到。事不宜迟,让我们更深入地探讨价值函数逼近。
在这一点上,广义策略迭代对我们来说应该不是新概念。这里的变化将是使用近似策略评估。我们将从一些参数向量 w 开始,定义一些值函数。然后,我们将贪婪地进行一点探索,关于价值函数,给我们一个新的政策。然后,为了评估这个新策略,我们更新我们的价值函数的参数,不断重复这个过程,直到我们(希望)收敛到一个最优的价值函数。下图说明了这一过程。

你会注意到,像以前一样,我们不需要一直走到顶线,或者换句话说,浪费时间/经验样本去尝试精确地拟合我们的函数逼近器。在我们的政策稍作调整后,我们立即行动,掌握最新的数据。
上述算法的问题是可能找不到最佳值函数,因为在现实中,我们只是越来越接近近似值函数。
现在,我们的第一步将是近似动作值函数,而不是状态值函数。

对于每一个状态和任何行为,用参数 w ,我们建立一个函数,预测我们期望从那个状态和行为中得到多少回报。
我们最小化近似动作值函数和真实动作值函数之间的均方误差。

使用链式法则和随机梯度下降,我们找到一个局部最小值:

让我们考虑最简单的情况,使用线性动作值函数近似。我们构建一个特征向量来表示状态和动作:

这些特征解释了整个状态-动作空间。我们通过构建特征的线性组合来做到这一点,但我们也可以使用更复杂的系统,如神经网络。

然后,梯度下降更新收缩为:

现在我们来看一个例子。下面的例子是强化学习中最广泛使用的问题之一,也是一个有趣的问题。

我们的想法是这辆车陷入了困境。汽车没有足够的动力直接开上山坡到达目的地,所以它必须向上开一点,松手,允许自己向后摆动通过下坡和后面的山坡,获得一些动量,并不断重复这个过程,直到它有足够的动量到达顶部。我们想要实现这个目标,即在不知道世界动力学(如重力、摩擦力等)的情况下,无模型地计算出控制策略。
首先,我们来定义这个问题的状态空间。这里的状态空间是汽车的位置,以及速度,使它成为一个二维的状态空间。
下面的伪代码显示了一个用于逼近最优值函数的分段 Sarsa TD 控制方法。

在让我们的山地汽车代理运行 100 集之后,结果显示了不可否认的学习:




感谢你再次加入我对强化学习知识的探索。从这里开始只会变得更好,因为下一次我们将着眼于深度 Q 学习,使用深度神经网络作为函数逼近器,建立在这篇文章和上一篇文章的概念之上。期待再次学习!
R 资源
YouTube 上大卫·西尔弗的 RL 课程
强化学习 Github 由 dennybritz
价值函数逼近—预测算法
强化学习之旅
大型未知环境的学习方法
欢迎再次深入强化学习!这一次,我们将回顾价值函数近似,更具体地说,它背后的预测算法,理解它的用途,并围绕实现进行思考。在我的下一篇文章中,我将像往常一样,通过将预测和控制与一个挑战结合起来,将价值函数近似法绑在一起。然而,这篇文章将主要包括价值函数逼近和我们目前所学的一切之间的联系。
正如我们逐渐意识到的,有可能遇到非常大的问题或状态空间;甚至是无限/连续的状态空间。有时,环境就像 gridworld 一样简单,就像国际象棋等游戏的状态空间一样定义明确,但情况并非总是如此。例如,在机器人领域,遥控直升机可能会遇到无限多的情况/状态,无法对每一种情况/状态的实际值进行分类(以前从未见过类似的情况)。
我在专栏的前几篇文章中提到了这篇文章中使用的很多符号。我会在文章底部贴上非常有用的资源,帮助我理解这些概念。我们来看看什么是价值函数逼近。
回顾过去,我们会注意到,我们一直在通过非常具体地表示价值函数,利用查找表来解决马尔可夫决策过程(MDP)。每个状态 s 都有一个条目 V(s) ,每个状态-动作对 s,a 都有一个条目 Q(s,a) 。实际上,我们能够查看我们的表,并通过最大化所有可能的行动来决定下一步做什么。
在大型 MDP 事件中使用这种策略会暴露出两个问题,第一个问题是最终我们会耗尽内存。在某些时候,将会有太多的状态和/或动作需要存储。第二个问题很简单,即使我们有足够的内存,单独估计每个值的过程也太慢了。价值函数逼近是解决这一问题的方法。
价值函数逼近试图通过创建使用较少参数的价值函数的紧凑表示来构建一些函数以估计真实的价值函数:

一种常见的做法是使用深度学习——在这种情况下,神经网络的权重是权重向量,它将用于估计整个状态/状态-动作空间的价值函数。这个权重向量将使用我们之前见过的方法更新,蒙特卡罗或时间差学习。
我们将从查看如何利用价值函数近似中的随机梯度下降来调整每个示例后的权重向量开始。目标是找到使近似值函数和真实值函数之间的均方误差最小化的参数向量 w 。梯度下降通过找到局部最小值来做到这一点:

为了更好地理解这个概念,让我们来理解什么是特征向量。每个特征都告诉我们关于状态空间的任何事情。它允许我们严格定义我们用什么来代表环境/我们与环境的互动。通过将特征的线性组合编辑在一起,我们用加权和表示价值函数。从数学上讲,我们的特征向量和我们的权重向量的点积将是我们对价值函数的估计。

我们的目标函数,我们希望优化的均方误差函数,在参数 w 中变成二次函数。

使用以下更新规则,随机梯度下降收敛于全局最优:

我们权重的变化来自于一小步,取决于步长参数,根据特定情况调整相关的功能。
现在,为了使这与强化学习相关,我们必须利用我们的学习方法。显然,最初并不知道真正的价值函数。因此,我们在一个目标中代入*。在蒙特卡罗学习中,目标是收益 Gₙ*

在 TD(0)中,目标是 TD 目标:

更新看起来像这样:

作为即将到来的高峰,下图应该看起来很熟悉!我们将基于广义策略迭代* (GPI)的思想,利用价值函数逼近进行控制。*

类似于上面的状态值函数近似,我们可以用随机梯度下降来近似动作值函数。

在下一篇文章中,我们将能够将这些预测信息与必要的控制方法结合起来,以便能够使用价值函数近似法实际解决问题。感谢阅读!
资源
强化学习:萨顿和巴尔托 介绍
YouTube 上大卫·西尔弗的 RL 课程
强化学习 Github by dennybritz
Q-函数的值迭代
深度强化学习讲解— 11
Q 函数的冻结湖码

在的上一篇文章中,我们介绍了如何通过求解冰湖环境来实现计算状态值 V-function 的值迭代法。在本帖中,我们将回顾 Q 函数,并展示学习动作值以创建策略的值迭代方法。
正如我们将在本文后面看到的,Q 值在实践中更方便,对于代理来说,基于 Q 值做出行动决策比基于 V 值更简单。
请访问第 4 页的自由介绍
medium.com](https://medium.com/aprendizaje-por-refuerzo/4-programación-dinámica-924c5abf3bfc)
Q 函数的值迭代
值迭代法可用于学习 V 值或 Q 函数。也就是说,将状态值或动作值存储在一个表中。在这里,我们将介绍如何使用值迭代方法来计算 Q 值,而不是上一篇文章中介绍的 V 值。
基于 V-function 的先前值迭代方法实现,在动作值的情况下,只需要对先前的代码进行微小的修改。最明显的变化是我们的价值表。在前一个例子中,我们保存了状态的值,所以字典中的键只是一个状态。现在我们需要存储 Q 函数的值,它有两个参数:state 和 action,所以值表中的键现在是一个复合键。
关于更新,在关于贝尔曼方程的帖子中,我们表明我们行动状态的最优值可以定义为:

接下来,我们将使用 Q 函数来求解冰湖环境。
Q 函数的值迭代在实践中的应用
这篇文章的全部代码可以在 GitHub 上找到,而可以通过这个链接作为一个谷歌笔记本来运行。
然后,我们将在用 Q-函数构成值迭代方法的代码中展示与 V-函数版本的主要区别。
保存我们的表和函数的中心数据,我们将在训练循环中使用,与前面的 V-function 示例相同。主要的变化是对values表的修改,这个字典现在将一个状态-动作对映射到这个动作的计算值。
在前一个例子中,我们保存了状态的值,所以字典中的键只是一个状态。现在我们需要存储 Q 函数的值,它有两个参数:state 和 action,所以值表中的键现在是一个复合键。这意味着另一个区别在于calc_action_value函数。我们只是不再需要它,因为我们的动作值存储在值表中。
最后,代码中最重要的变化是代理的value_iteration()方法。以前,它只是一个围绕calc_action_value()调用的包装器,完成贝尔曼近似的工作。现在,由于这个函数已经消失并被一个值表所取代,我们需要在value_iteration()方法中做这个近似。
Q 函数的冻结湖码
让我们看看代码。由于与之前的实现几乎相同,我将直接跳到主要区别,读者可以在 GitHub 代码中了解细节。先说主要功能value_iteration():
def value_iteration_for_Q(self):
for state in range(self.env.observation_space.n):
for action in range(self.env.action_space.n):
action_value = 0.0
target_counts = self.transits[(state, action)]
total = sum(target_counts.values())
for tgt_state, count in target_counts.items():
key = (state, action, tgt_state)
reward = self.rewards[key]
best_action = self.select_action(tgt_state)
val = reward + GAMMA * \
self.values[(tgt_state, best_action)]
action_value += (count / total) * val
self.values[(state, action)] = action_value
读者会注意到,该代码相当于前面实现中的calc_action_value代码。其思想是,对于给定的状态和动作,它需要使用我们通过函数play_n_random_steps收集的信息来计算动作值,该函数从环境中随机播放N步骤,用随机经验填充reward和transits表。
然而,在前面的实现中,我们将 V 函数存储在值表中,所以我们只是从该表中取出它。我们不能再这样做了,所以我们必须调用select_action方法,它将为我们选择 Q 值最大的最佳动作,然后我们将这个 Q 值作为目标状态的值。
事实上,这个方法的实现是不同的,因为它不再调用calc_action_valu方法,而是我们只是迭代动作,并在我们的值表中查找它们的值。
def select_action(self, state):
best_action, best_value = None, None
for action in range(self.env.action_space.n):
action_value = self.values[(state, action)]
if best_value is None or best_value < action_value:
best_value = action_value
best_action = action
return best_action
正如你所注意到的,学习循环的代码和前一篇文章中的一样。以及测试客户端并在 TensorBoard 中绘制结果的代码:

最后,读者可以像以前一样,用 FrozenLake8x8 环境进行测试,或者测试其他超参数。
鸣谢:这篇文章中的代码灵感来自于马克西姆·拉潘的代码 ,他写了一本关于 主题的优秀实用书籍。
结论
在实践中,Q 值要方便得多,对于代理来说,基于 Q 值做出关于动作的决策要比基于 V 值简单得多。在 Q 值的情况下,为了基于状态选择动作,代理只需要使用当前状态计算所有可用动作的 Q 值,并选择具有最大 Q 值的动作。
为了使用状态的值来做同样的事情, V 值,代理不仅需要知道值,还需要知道转移的概率。在实践中,我们很少预先知道它们,所以代理需要估计每个动作和状态对的转移概率。
在 V-函数的值迭代方法中,这种对概率的依赖给代理增加了额外的负担。也就是说,了解这种方法很重要,因为它们是高级方法的重要组成部分。
深度强化学习讲解系列
由 UPC 巴塞罗那理工 和 巴塞罗那超级计算中心
一个轻松的介绍性系列以一种实用的方式逐渐向读者介绍这项令人兴奋的技术,它是人工智能领域最新突破性进展的真正推动者。
本系列的内容](https://torres.ai/deep-reinforcement-learning-explained-series/)
关于这个系列
我在五月份开始写这个系列,那是在巴塞罗那的禁闭期。老实说,由于封锁,在业余时间写这些帖子帮助我到了#寄宿家庭 。感谢您当年阅读这份刊物;这证明了我所做的努力。
免责声明 —这些帖子是在巴塞罗纳封锁期间写的,目的是分散个人注意力和传播科学知识,以防对某人有所帮助,但不是为了成为 DRL 地区的学术参考文献。如果读者需要更严谨的文档,本系列的最后一篇文章提供了大量的学术资源和书籍供读者参考。作者意识到这一系列的帖子可能包含一些错误,如果目的是一个学术文件,则需要对英文文本进行修订以改进它。但是,尽管作者想提高内容的数量和质量,他的职业承诺并没有留给他这样做的自由时间。然而,作者同意提炼所有那些读者可以尽快报告的错误。
V 函数的值迭代
深度强化学习讲解—10
v 函数在冰湖环境中的应用

在之前的帖子中,我们提出了值迭代方法来计算基于价值的代理所需的 V 值和 Q 值。在本文中,我们将介绍如何通过求解冰湖环境来实现计算状态值的值迭代法。
请访问第 4 页的自由介绍
medium.com](https://medium.com/aprendizaje-por-refuerzo/4-programación-dinámica-924c5abf3bfc)
V 函数的值迭代在实践中的应用
这篇文章的完整代码可以在 GitHub 上找到,并且可以使用这个链接作为一个 Colab google 笔记本运行。
接下来,我们将详细介绍组成我们在上一篇文章中介绍的值迭代方法的代码。所以让我们来看看代码。开始时,我们导入使用的库并定义主要的常量:
import gym
import collections
from torch.utils.tensorboard import SummaryWriterENV_NAME="FrozenLake-v0"GAMMA = 0.9
TEST_EPISODES = 20
N =100
REWARD_GOAL = 0.8
代理的数据结构
保存代理信息的主要数据结构有:
rewards:组合键为“源状态”+“动作”+“目标状态”的字典。价值是从眼前的奖励中获得的。transits:一个表作为字典,保存所经历的转变的计数器。关键是复合“state”+“action”,值是另一个字典,它将目标状态映射到我们看到它的次数。values:将一个状态映射到这个状态的计算值(V 值)的字典。state:代理的当前状态。
主数据结构是在代理的类构造函数中创建的。
训练算法
我们训练算法的整体逻辑很简单。在未达到预期奖励目标之前,我们将执行以下步骤:
- 从环境中随机选择 N 步来填充
reward和transits表格。 - 在这 N 个步骤之后,它对所有状态执行一个值迭代步骤,更新
value表。 - 然后,我们播放几集完整的剧集,使用更新后的值表来检查改进。
- 如果这些测试的平均回报高于期望的界限,那么我们就停止训练。
在我们深入了解该代码的更多细节之前,先回顾一下代理的代码会有所帮助。
代理类
在Agent类构造函数中,我们创建了一个将用于数据样本的环境,获得了我们的第一个观察值,并定义了奖励、转换和值的表:
class Agent:
def __init__(self):
self.env = gym.make(ENV_NAME)
self.state = self.env.reset()
self.rewards = collections.defaultdict(float)
self.transits = collections.defaultdict(
collections.Counter)
self.values = collections.defaultdict(float)
玩随机步骤
请记住,在上一篇文章中,我们提出了转换和奖励的估计将通过代理与环境的交互历史来获得。
这是通过方法play_n_random_steps完成的,它从环境中随机播放N步骤,用随机体验填充reward和transits表格。
def play_n_random_steps(self, count):
for _ in range(count):
action = self.env.action_space.sample()
new_state, reward, is_done, _ = self.env.step(action)
self.rewards[(self.state, action, new_state)] = reward
self.transits[(self.state, action)][new_state] += 1
if is_done:
self.state = self.env.reset()
else:
self.state = new_state
注意,我们不需要等到一集结束才开始学习;我们只是执行N步骤并记住它们的结果。这是之前一个帖子展示的值迭代和交叉熵方法的区别之一,需要全集学习。
行动的价值
下一个方法使用代理的transits、reward和value表计算 Q 函数,即来自一个状态的动作值。我们将把它用于两个目的:从状态中选择要执行的最佳动作,并根据值迭代算法计算状态的新值。
def calc_action_value(self, state, action):
target_counts = self.transits[(state, action)]
total = sum(target_counts.values())
action_value = 0.0
for tgt_state, count in target_counts.items():
reward = self.rewards[(state, action, tgt_state)]
val = reward + GAMMA * self.values[tgt_state]
action_value += (count / total) * val
return action_value
首先,从transits表中,我们提取方法作为参数接收的给定状态和动作的转换计数器。我们对所有的计数器求和,以获得我们从状态执行动作的总次数。然后,我们迭代我们的动作已经到达的每个目标状态,并使用贝尔曼方程文章中的公式计算它对总动作值的贡献:

该值等于即时奖励加上目标状态的贴现值,并将该总和乘以该转换的概率(单个计数器除以之前计算的总值)。我们将每次迭代的结果添加到一个变量action_value,这个变量将被返回。
选择最佳操作
为了从给定的状态中选择最佳的动作,我们有方法select_action,它使用前面的calc_action_value方法来做出决定:
def select_action(self, state):
best_action, best_value = None, None
for action in range(self.env.action_space.n):
action_value = self.calc_action_value(state, action)
if best_value is None or best_value < action_value:
best_value = action_value
best_action = action
return best_action
该方法的代码迭代环境中所有可能的动作,计算每个动作的值,并返回具有最大 Q 值的动作。
值迭代函数
这里我们有一个主要功能,正如我们在上一篇文章中所描述的,值迭代方法所做的只是在环境中的所有状态上循环:
def value_iteration(self):
for state in range(self.env.observation_space.n):
state_values = [
self.calc_action_value(state, action)
for action in range(self.env.action_space.n)
]
self.values[state] = max(state_values)
对于每个状态,我们用该状态可用的动作的最大值来更新它的值。
训练循环和监控代码
在介绍了代理的类及其方法之后,我们回来描述主循环。首先,我们创建将用于测试的环境,创建代理类的实例,TensorBoard 的摘要编写器,以及我们将使用的一些变量:
test_env = gym.make(ENV)
agent = Agent()
writer = SummaryWriter()iter_no = 0
best_reward = 0.0
记住值迭代法形式上需要无限次迭代才能精确收敛以获得最优值函数。实际上,在上一篇文章中,我们展示了一旦值函数在训练循环的迭代中仅发生少量变化,我们就可以停止。
在这个例子中,为了保持代码简单和迭代次数少,我们决定在达到某个奖励阈值时停止。但是其余的代码都是一样的。
我们代码的整体逻辑是一个简单的循环,它将迭代直到代理达到预期的性能(如果这些测试集的平均回报高于REWARD_GOAL界限,那么我们停止训练):
while best_reward < REWARD_GOAL:
agent.play_n_random_steps(N)
agent.value_iteration()
...
循环体由前面介绍的步骤组成:
步骤 1:播放调用方法plays_n_random_steps.的 N 个随机步骤
第 2 步:调用value_iteration.方法,对所有状态进行值迭代扫描
第三步:然后我们播放几集完整的剧集,使用更新后的值表来检查改进。为此,代码使用agent.elect_action()来寻找在新的test_env环境中采取的最佳行动(我们不想弄乱用于收集随机数据的主环境的当前状态),以检查代理的改进:
iter_no += 1
reward_avg = 0.0
for _ in range(TEST_EPISODES):
total_reward = 0.0
state = test_env.reset()
while True:
action = Select_action(state)
new_state, new_reward, is_done, _ = test_env.step(action)
total_reward += new_reward
if is_done: break
state = new_state
reward_test += total_reward
reward_test /= TEST_EPISODES
此外,代码将数据写入 TensorBoard,以便跟踪最佳平均奖励:
writer.add_scalar("reward", reward_test, iter_no)
if reward_test > best_reward:
print("Best reward updated %.2f at iteration %d " %
(reward_test ,iter_no) )
best_reward = reward_test
仅此而已!
运行程序
好,让我们运行我们的程序:
Best reward updated 0.40 in iteration 13
Best reward updated 0.65 in iteration 20
Best reward updated 0.80 in iteration 23
Best reward updated 0.85 in iteration 28
Best reward updated 0.90 in iteration 76
测试客户端
现在,如果我们尝试使用与交叉熵相同的客户端测试代码,我们可以看到我们构建的代理可以从一个不稳定的环境中学习:
new_test_env = gym.make(‘FrozenLake-v0’, is_slippery=True)
state = new_test_env.reset()
new_test_env.render()
is_done = False
iter_no = 0
while not is_done:
action = Select_action(state)
new_state, reward, is_done, _ = new_test_env.step(action)
new_test_env.render()
state = new_state
iter_no +=1
print(“reward = “, reward)
print(“iterations =”, iter_no)

. . .

结论
请注意,提出的算法是随机的,对于不同的执行,它采用不同的迭代次数来获得解决方案。然而,它可以从光滑的环境中学习,而不是前面提到的交叉熵。我们可以使用 Tensorboard 绘制它们,它会显示如下图所示的图形:

我们可以注意到,在所有情况下,最多需要几秒钟就可以找到一个在 80%的运行中解决环境问题的好策略。如果你还记得交叉熵方法,对于一个湿滑的环境,花了许多小时才达到只有 60%的成功率。
而且,我们可以把这个算法应用到更大版本的 FrozenLake 上,这个版本的名字是 FrozenLake8x8-v0。更大版本的 FrozenLake 可能需要更多的迭代来求解,根据 TensorBoard 图表,大多数时间它会等待第一个成功的情节(它需要至少有一个成功的情节来开始从有用值表学习),然后它会很快达到收敛。下图比较了在 FrozenLake-4x4 和 8x8 版本上训练期间的奖励动态:

除了在ENV_NAME(“frozen lake-v 0”vs“frozen lake 8 x8-v 0”)中改变环境外,读者还可以使用不同的超参数值进行测试,如GAMMA, TEST_EPISODE, REWARD_GOAL 或N。你为什么不试试呢?
下一步是什么?
在下一篇文章中,我们将展示学习动作值的值迭代方法代码,而不是像我们在这里所做的那样学习状态值。下一个帖子见!
这篇文章的全部代码可以在 GitHub 上找到,并且可以通过这个链接作为 Colab google 笔记本运行。
鸣谢:这篇文章中的代码灵感来自于 Maxim Lapan 的代码,他写了一本关于这个主题的优秀实用书籍 。
深度强化学习讲解系列
一个轻松的介绍性系列逐渐并以实用的方法向读者介绍这一令人兴奋的技术,这是人工智能领域最新突破性进展的真正推动者。
本系列的内容](https://torres.ai/deep-reinforcement-learning-explained-series/)
关于这个系列
我是在五月份开始写这个系列的,那是在巴塞罗纳的封锁期。老实说,由于封锁,在业余时间写这些帖子帮助了我 #StayAtHome 。感谢您当年阅读这份刊物;这证明了我所做的努力。
免责声明 —这些帖子是在巴塞罗纳封锁期间写的,目的是分散个人注意力和传播科学知识,以防对某人有所帮助,但无意成为 DRL 地区的学术参考文献。如果读者需要更严谨的文档,本系列的最后一篇文章提供了大量的学术资源和书籍供读者参考。作者意识到这一系列的帖子可能包含一些错误,如果目的是一个学术文件,则需要对英文文本进行修订以改进它。但是,尽管作者想提高内容的数量和质量,他的职业承诺并没有留给他这样做的自由时间。然而,作者同意提炼所有那些读者可以尽快报告的错误。
价值迭代求解 OpenAI 健身房的 FrozenLake
素君的承担…
从零开始理解和实现价值迭代…

在我的叙述下,我们将制定 值迭代 并实现它来解决 FrozenLake8x8-v0 环境来自 OpenAI 的健身房。
这个故事有助于强化学习的初学者 理解 值迭代 从零开始实现,并了解 OpenAI Gym 的 环境。
简介:FrozenLake8x8-v0 环境下,是一个离散有限 MDP。
我们将计算出 最优策略 对于一个(最佳可能 行动给定 状态 )在给定 环境下达到目标, 因此得到最大的 预期报酬**

哑代理使用随机策略
代理控制一个角色在【8x8】网格世界中的移动。格子的有些瓷砖是可走的【F】,有些导致代理人落水【H】。此外,智能体的运动方向是不确定的(未知策略),并且仅部分取决于所选择的方向(环境动力学)。代理人每走 (0) 步找到一条通往目标方块的可行走路径,将获得 (1) 奖励。

冰冻湖-v0
action _ space:离散(4),agent 可以采取 4 个离散动作:左(0),下(1),上(2),右(3)。
状态 _ 空间: 离散(64),离散 64 个网格单元。
转移概率 :由于环境的不确定性,以转移概率为例,给定 状态(0)动作(1) 将…

环境的属性 : ' env.env.nA ',' env.env.nS '给出了可能的动作和状态的总数。
P[s|a] = P[s'],s ',r,done
到达后继状态的概率(s ')及其回报(r)。
更多环境详情, FrozenLake8x8-v0 。
让我们来理解策略迭代:
预测和控制
策略评估:对于给定的策略(π),确定状态值函数 Vπ(s)。
对于给定的策略 (π) ,初始近似值 v0 被任意选择,对于最终状态为‘0’,值函数的逐次近似值使用贝尔曼方程作为更新规则。

****贝尔曼期望方程作为更新规则
其中,策略:** π(a|s) 是策略 (π) 下状态 (s) 下采取行动 (a) 的概率。环境 跃迁动力学: P(s ',r|s,a) 是到达后继状态 (s') 并从状态 (s) 采取行动 (a) ,γ是一个折扣因子。**

花一点时间来理解迭代策略评估的伪代码****
我们迭代更新规则,直到迭代中的值估计变化变得可以忽略。
策略控制:改进现有策略(π)
在我们的例子中,我们贪婪地对待期望值函数,这给了我们确定性的策略。从状态 (s) 中采取具有最高值的动作 (a) ,简单。

argmax() 函数返回动作,该动作可以将我们带到更高的值状态。

arg max()的实现
策略迭代:

策略评估和策略改进将针对每个状态迭代执行,以改进策略和值函数。

快速浏览一下策略迭代的伪代码****
哥们……
谁使用这种多步多扫的同步 DPs,让我们用“ 【贝尔曼最优性方程】 把这两步结合起来,称之为 【值迭代】 ,并说它属于 【广义策略迭代】 。
酷……
价值迭代:
所以忘了一切,来个 值迭代 (不,我只是开玩笑……别忘了什么)。
在价值迭代中,我们不运行策略评估直到完成。我们只对所有状态进行一次扫描,并贪婪地使用当前值函数。

实现贝尔曼最优方程的
当估计值的变化变得可以忽略不计时(

****贝尔曼最优性方程作为更新规则

实现值函数的
因此, 更新规则 不是指任何特定的策略 而是最大化当前值估计 的动作。
唯一等待的事情就是 C 所有的价值函数 和评估 应用策略 :

解决方案:
从策略中,我们从给定的状态中提取价值和要采取的行动。

确定性策略
我们将策略应用到环境中,并运行 100 集:

执行政策到 FrozenLake8x8-v0
结论:
我们看到了价值迭代的公式,并提取了达到目标的最优策略,并在 OpenAI 的 FrozenLake 环境上实现了相同的结果。

****代理使用最优策略并在的 8 个步骤中达到目标……但受到环境动态的影响
带走:
即使我们得到了一个 严格收敛的最优策略 ,给定了 环境动力学 ,智能体总是达不到目标。
从下一篇文章开始,我们将使用能够更好地制定最优策略的算法。请继续收听更多…
获取算法的完整代码。
参考资料:
[1] 强化学习:导论|第二版,作者理查德·萨顿&安德鲁·g·巴尔托。
[2] 大卫·西尔弗的 RL 课程:DeepMind 。
[3] 开艾健身房。
来源:
[4] 强化学习专业:阿尔伯塔大学。
[5] 深度强化学习:加州大学伯克利分校
用 Python 评估一家公司
使用价格销售比
销售价格是分析师用来比较类似股票价值的常用工具。在这篇文章中,我们将使用销售价格比来评估数百家科技行业的公司。这个 Python 脚本将让我们找到按照这个比例最便宜的公司。

价格与销售额的比率是多少?
销售价格的计算方法是将公司的 市值除以年收入。 它告诉我们需要多少年的收入才能覆盖一家公司目前的市值。
与市盈率不同(点击此处了解如何用 Python 计算 PE 比率),我们可以对没有任何收益的公司使用市盈率。由于这个原因,价格销售比对于那些在成长时期可能没有利润的成长型公司非常有用。
价格销售比本身并不能说明什么。因此,重要的是计算类似公司的行业平均价格销售比,以用作参考。
销售本身可能不足以做出投资决策。除了价格销售比之外,我们还将计算每家公司的毛利率,并结合价格销售比来使用。毛利率告诉我们一家公司在销售成本上赚了多少利润。一个拥有更大毛利率的公司,可以分配更多的资源到研发上来进一步发展公司。或者,它也可以向股东分配更多收益。
毛利率=(销售额—销货成本)/销售额
用 Python 计算销售价格
理论够了。让我们计算一下科技行业的一些公司的价格销售比和毛利率。我们将把我们的财务分析局限于市值超过 100 亿美元的公司。
对于我们的财务分析,我们将使用一个很棒的财务 API fmpcloud 。通过在他们那里开一个账户,你每天可以得到一些免费的 API 调用。让我们构建我们的脚本来逐步计算价格与销售额的比率:
首先,我们得到所有的科技公司,并把它们添加到 Python 列表中。我们使用下面的API终点传递作为参数的技术部门和市值。
import requests
import pandas as pd
import requests
demo= 'your_api key'
companies = requests.get(f'https://fmpcloud.io/api/v3/stock-screener?sector=technology&marketCapMoreThan=100000000000&limit=100&apikey={demo}')
companies = companies.json()
technological_companies = []
for item in companies:
technological_companies.append(item['symbol'])
print(technological_companies)
#['MSF.BR', 'MSFT', 'AAPL', 'AMZN', 'GOOG', 'GOOGL', 'FB', 'INCO.BR', 'INTC', ...
然后,我们遍历列表中的每只股票,向 API 发出 http 请求,然后检索损益表数据 。我们解析对的响应,得到收入和毛利率。请注意,我们请求年度损益表来获取年度收入。接下来,我们检索最新市值。最后,我们计算价格与销售额的比率,并将它们添加到一个空字典中。
pricetosales = {}
for item in technological_companies:
try:
#annual income statement since we need anual sales
IS = requests.get(f'https://fmpcloud.io/api/v3/income-statement/{item}?apikey={demo}')
IS = IS.json()
Revenue = IS[0]['revenue']
grossprofitratip = IS[0]['grossProfitRatio']
#most recent market capitliazation
MarketCapit = requests.get(f'https://fmpcloud.io/api/v3/market-capitalization/{item}?apikey={demo}')
MarketCapit = MarketCapit.json()
MarketCapit = MarketCapit[0]['marketCap']
#Price to sales
p_to_sales = MarketCapit/Revenue
pricetosales[item] = {}
pricetosales[item]['revenue'] = Revenue
pricetosales[item]['Gross_Profit_ratio'] = grossprofitratip
pricetosales[item]['price_to_sales'] = p_to_sales
pricetosales[item]['Market_Capit'] = MarketCapit
except:
pass
print(pricetosales)
#
{'AAPL': {'Gross_Profit_ratio': 0.37817768109,
'Market_Capit': 1075385951640,
'price_to_sales': 4.133333659935274,
'revenue': 260174000000},
'ADBE': {'Gross_Profit_ratio': 0.850266267202,
'Market_Capit': 143222958000,
'price_to_sales': 12.820620380963822,
'revenue': 11171297000},
'AMZN': {'Gross_Profit_ratio': 0.409900114786,
'Market_Capit': 960921360000
最后,我们在 Python 字典中给出了每家公司的价格销售比和毛利比。然而,为了做进一步的分析,在熊猫的数据框架中有这些信息是很好的。我们可以从 _dict 中使用 Pandas DataFrame 方法,并将我们的字典作为参数传递。
price_to_sales_df = pd.DataFrame.from_dict(pricetosales, orient='index')
现在,我们计算技术行业的平均价格销售比,并将其添加到名为 ps_average_sector 的新列中。我们还使用价格销售比作为估价工具来计算额外信息,例如每个公司的价格:
price_to_sales_df['ps_average_sector'] = price_to_sales_df['price_to_sales'].mean()
price_to_sales_df['pscompany_vs_averagesector'] = price_to_sales_df['price_to_sales'] - price_to_sales_df['ps_average_sector']
price_to_sales_df['price_as_per_average_industryPS'] = price_to_sales_df['ps_average_sector'] * price_to_sales_df['revenue']
price_to_sales_df['price_difference'] = price_to_sales_df['price_as_per_average_industryPS'] - price_to_sales_df['Market_Capit']
包扎

在上面的图片中,我们可以看到我们生成的熊猫数据帧的样本。令人惊讶的是,像苹果和亚马逊这样的公司的价格销售比低于行业平均水平。仅分别为 4.12 和 3.42,而行业平均水平为 5.99。
列根据平均行业价格显示如果我们将价格应用于销售行业平均价格作为乘数以获得每只股票的价格,那么股票的市值应该是多少。
例如,通过使用苹果的平均价格销售比(平均 ps 比率*公司收入:5.99 *4.133),我们得到的市值为 1.560851e+12,远低于其当前的真实市值。这是否意味着这是一个购买苹果的好机会?
在做出任何投资决定之前,需要进一步分析其他基本面因素。查看我的Python for Finance——基本面分析,了解其他不错的分析工具。
原载于 2020 年 4 月 5 日【https://codingandfun.com】。
R 中的香草神经网络
看看神经网络架构的引擎盖:从零开始,在 R 中设计并构建一个神经网络,而不使用任何深度学习框架或包
特别感谢:亚历克斯·斯克里文

图片来源: GitHub
内容:
1.简介
2。背景3。语义
4。设置
5。获取数据
6。检查数据7
。准备数据
8。实例化网络
9。初始化网络
10。正向传播11。计算成本
12。反向传播
13。更新模型参数14
。端到端运行模型
15。创建预测16
。结论
17。帖子脚本
1.介绍
现代数据科学技术经常使用健壮的框架来设计和构建机器学习解决方案。在[R](https://www.r-project.org/)社区中,[tidyverse](https://www.tidyverse.org/)、[caret](http://caret.r-forge.r-project.org/)等包被频繁引用;并且在[Python](http://127.0.0.1:17287/rmd_output/0/)内,经常引用[numpy](https://numpy.org/)、[pandas](https://pandas.pydata.org/)、[sci-kit learn](https://scikit-learn.org/)等包。甚至有一些包已经被构建为可以在两种语言中使用,比如[keras](https://keras.io/)、[pytorch](https://pytorch.org/)、[tensorflow](https://www.tensorflow.org/)。然而,使用这些包的限制是'黑盒 ' 现象,用户不理解幕后(或者说“引擎盖下”)发生了什么。用户知道如何使用这些功能,并且可以解释结果,但是不一定知道软件包是如何实现这些结果的。
本文的目的是创建一种“回归基础”的方法来设计深度学习解决方案。其意图不是创建最具预测性的模型,也不是使用最新最棒的技术(如卷积或递归);但其意图是创建一个基本的神经网络,从开始,使用没有的框架,并向走过的方法论。
注意:“香草神经网络”中的单词“香草”只是指它是从零开始构建的,并且在其构建中不使用任何预先存在的框架。
2.背景
2.1.语境
已经有很多网站和博客解释了这个过程是如何完成的。如 Jason Brownlee 的文章如何用 Python 编写一个带反向传播的神经网络(从头开始),以及 DeepLearning.ai 的笔记本 dnn_app_utils_v2.py (上微软 Azure 笔记本网)。但是,这些源码都是用 Python 写的。这很好,如果这是所需要的,并且有一些非常合理的理由使用Python而不是其他语言。但是这篇论文会写在R里。
选择R语言有两个原因:
- 我熟悉这种语言。我会说
Python(还有其他语言);我选择了R来展示如何使用这种语言来实现。 - 证明有很多不同的方法可以达到同样的结果。因此,虽然有时选择一种语言比选择另一种语言有合理的限制(业务遗产、技术可用性、系统性能等),但有时选择一种语言只是因为它在风格上更可取。
因此,让我们看看如何在R中设计和构建一个香草神经网络。
2.2.什么不是
本文不涵盖最新的和最伟大的深度学习架构(如卷积或递归)。因此,如果使用这些其他架构,最终性能可能不会像可能的那样好。
这篇文章没有向读者讲授神经网络如何工作背后的理论数学概念。有很多其他的讲座教授这些信息(例如神经网络背后的数学)。事实上,本文假设读者有很多关于编程、微积分和神经网络概念背后的基础知识。
这篇文章不包括为什么神经网络以这样的方式工作,以及前馈结构背后的概念理解。有很多其他博客(例如神经网络入门)和视频(例如神经网络系列)涵盖了这些信息。
本文并没有向读者指出其他可能已经设置并运行这些信息的包和应用程序。像[tensorflow](https://www.rdocumentation.org/packages/tensorflow)和[nnet](https://www.rdocumentation.org/packages/nnet)这样的包已经包含了这一点。
这篇文章实际上是一个功能走查,如何创建一个香草神经网络(一个前馈网络),从零开始,一步一步,用R编程语言。它包含大量代码和技术细节。
3.语义学
3.1.布局
这篇文章就是以这样一种方式来描述神经网络是如何从头开始构建的。它将完成以下步骤:
- 访问并检查数据
- 实例化和初始化网络
- 向前传播
- 计算成本
- 反向传播
- 更新模型
- 建立一个训练方法来循环每一件事
- 预测和评估绩效
为了简洁起见,这里定义的函数将不包括典型函数中应该包括的所有注释和验证。它们将只包括基本步骤和提示。然而,本文的源代码(位于这里)确实包含了所有适当的函数文档字符串和断言。
3.2.句法
在很大程度上,本文中的语法保留了[dplyr](https://www.rdocumentation.org/packages/dplyr)‘pipe’方法(使用了%>%符号)。然而,在某些部分使用了 R [base](https://www.rdocumentation.org/packages/base)语法(例如,在函数声明行中)。
在整篇文章中,编写了许多自定义函数。这些都带有前缀get、let和set。每个的定义如下。
get_*():
—它将get将来自对象的元数据的某些属性解析到此函数。
—或将使用解析到该函数的信息来导出和get其他值或参数。set_*():
—它将set(或‘更新’)解析到该函数的对象。
—通常用于在正向和反向传播过程中更新网络。let_*():
—与get类似,它使用解析到该函数的其他值来导出结果,但是let该值将被另一个对象或函数使用。
—主要用于初始化和激活功能。
4.建立
4.1.加载包
第一步是导入相关的包。此列表包括整个过程中使用的主要软件包;并列出其主要用途。
请注意上面列出的关于不使用现有深度学习包的内容,然而tensorflow包却包括在内。为什么?嗯,这仅用于访问数据,这将在下一节讨论。tensorflow包不用于构建和训练任何网络。
library(tensorflow) #<-- Only used for getting the data
library(tidyverse) #<-- Used for accessing various tools
library(magrittr) #<-- Extends the `dplyr` syntax
library(grDevices) #<-- For plotting the images
library(assertthat) #<-- Function assertions
library(roxygen2) #<-- Documentation is important
library(caret) #<-- Doing data partitioning
library(stringi) #<-- Some string manipulation parts
library(DescTools) #<-- To properly check `is.integer`
library(tictoc) #<-- Time how long different processes take
library(docstring) #<-- Makes viewing the documentation easier
library(roperators) #<-- Conveniently using functions like %+=%
library(plotROC) #<-- For plotting predictions
5.检索数据
5.1.下载数据
要使用的数据集是 CIFAR-10 数据集。选择它有很多原因,包括:
- 数据在图像上,非常适合深度学习目的;
- 包含了相当数量的图像(总共 60,000 张图像);
- 所有图像都是相同的大小(32x32 像素);
- 这些图像被分为 10 个不同的类别;和
- 通过
TensorFlow包可以轻松访问它。
以下代码块具有以下过程步骤:
- 获取数据
—为了导入日期,通过keras元素访问,该元素包含datasets的套件,包括cifar10部分。
—load_data()函数从在线 GitHub 存储库中检索数据。 - 提取第二个元素
—这里的load_package()返回两个不同的对象:
— — 1。训练数据集(包含 50,000 幅图像);
— — 2。测试数据集(包含 10,000 张图像)。
—提取第二个元素(通过使用extract2(2)功能),因为只需要 10,000 张图像。
—本文将展示创建香草神经网络的过程;如果以后需要更多数据,可以在这里轻松访问。 - 说出零件的名称
—下载的数据包含另外两个元素:
— — 1。图像本身(以 4 维阵列的形式);
———2。图像标签(以二维单列数组的形式)。
—该数据没有任何名称,因此使用set_names()函数设置名称。
# Download Data
# NOTE:
# - The first time you run this function, it download everything.
# - Next time you run it, TensorFlow will load from Cache.
cifar <- tf$keras$datasets$cifar10$load_data() %>%
extract2(2) %>%
set_names(c("images","classes"))
5.2.获取类定义
从TensorFlow包中访问这些数据的一个挑战是,对于每种类型的图像,这些类都只是数值(0到9)。这些图像的定义可以在 GitHub 上找到(GitHub>EN10>CIFAR)。这些类在下面的代码块中定义。
# Define classes
ClassList <- c(
"0" = "airplane",
"1" = "automobile",
"2" = "bird",
"3" = "cat",
"4" = "deer",
"5" = "dog",
"6" = "frog",
"7" = "horse",
"8" = "ship",
"9" = "truck"
)
6.检查数据
6.1.检查对象
检查数据是很重要的,以确保数据是正确生成的,并且所有信息看起来都没问题。为此,编写了一个自定义函数(get_ObjectAttributes()),其源代码可以在这里找到。如下面的代码块所示,images 对象是一个四维数字数组,包含10,000个图像,每个32 x 32个像素,以及3个颜色通道。整个物体超过117 Mb大。
# Check Images
cifar %>%
extract2("images") %>%
get_ObjectAttributes("cifar$images") %>%
cat()
它打印:
Name : cifar$images
- Size : 117.2 Mb
- Clas : array
- Type : integer
- Mode : numeric
- Dims : 10000x32x32x3
当检查classes 对象时,它是一个 2 维数字数组(只有 1 列),但是具有与images 对象相同数量的图像(这是预料中的),每个类标签的频率恰好都具有1000图像。总尺寸小于40 Kb。
# Check classes
cifar %>%
extract2("classes") %>%
get_ObjectAttributes(name="cifar$classes", print_freq=TRUE) %>%
cat()
它打印:
Name : cifar$classes
- Size : 39.3 Kb
- Clas : matrix,array
- Type : integer
- Mode : numeric
- Dims : 10000x1
- Freq :
label Freq
1 0 1000
2 1 1000
3 2 1000
4 3 1000
5 4 1000
6 5 1000
7 6 1000
8 7 1000
9 8 1000
10 9 1000
6.2.检查图像
在了解了内存中对象的大小之后,就有必要检查实际的图像本身了。作为人类,我们理解实际的图像和颜色,胜过理解数字。
为了可视化图像,编写了两个自定义函数,如下面的代码块所示。这些函数接收数据(作为一个 4 维数组),并将图像可视化为一个图。
set_MakeImage <- function(image, index=1) {
# Extract elements
image.r <- image[,,1]
image.g <- image[,,2]
image.b <- image[,,3]
# Make rgb
image.rgb <- rgb(
image.r,
image.g,
image.b,
maxColorValue=255
)
# Fix dimensions
dim(image.rgb) <- dim(image.r)
# Return
return(image.rgb)
}plt_PlotImage <- function(images, classes, class_list, index=1) {
# Slice images
image <- images[index,,,]
image %<>% set_MakeImage(index)
lbl <- classes %>%
extract(index) %>%
as.character() %>%
class_list[[.]]
# Create plot
plot <- ggplot() +
ggtitle(lbl) +
draw_image(image, interpolate=FALSE)
# Return
return(plot)
}
对前 16 幅图像运行该功能时,将显示以下内容。如图所示,这些图像非常像素化(这是意料之中的,因为它们每个只有32 x 32像素),你可以看到每个图像是如何分类的。
# Set list
lst <- list()# Loop 16 images
for (index in 1:16) {
lst[[index]] <- plt_PlotImage(
cifar$images,
cifar$classes,
ClassList,
index)
}# View images
plt <- gridExtra::grid.arrange(grobs=lst, ncol=4)

图 1 :初始图像
7.准备数据
准备数据有四个步骤:
- 把…重新分类
- 裂开
- 使再成形
- 使标准化
7.1.把…重新分类
出于本文的目的,让我们假设我们正在尝试预测图片是car还是not。这将需要将数据转换为二进制分类问题,其中神经网络将从数据中预测1或0。这将意味着模型输出将是分数的概率分布,通过改变截止变量可以容易地对其进行分类。
第一步是对数据进行重新分类,使所有汽车的值都是1,其他的都是0。我们从之前定义的类中知道,汽车已经有了值1,这意味着只需要对所有其他类进行转换。
# Implement within a pipe
cifar[["classes"]] <- cifar %>%
extract2("classes") %>%
(function(classes){
# View initial classes
classes %>% as.vector %>% head(40) %>% print
# Reclassify
classes <- ifelse(classes==1,1,0)
# View reclassified classes
classes %>% as.vector %>% head(40) %>% print
# Return
return(classes)
})
它打印:
[1] 3 8 8 0 6 6 1 6 3 1 0 9 5 7 9 8 5 7 8 6 7 0 4 9 5 2 4 0 9 6
[1] 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
7.2.分割数据
下一个任务是将数据分成训练集和测试集。这样做的原因在其他地方有所涉及(如维基百科的交叉验证和谷歌的机器学习速成班:训练和测试集分割数据)。
首先,为了理解当前的数据分割,下面的代码块使用ggplot2包可视化了这些数据。如图所示,数据目前分布在90%和0类别中,剩余的10%和1类别中。
# Print Plot
cifar %>%
extract("classes") %>%
table(dnn="classes") %>%
data.frame() %>%
ggplot(aes(classes, Freq, fill=classes)) +
geom_col(colour="black") +
geom_label(
aes(label=Freq),
show.legend=FALSE
) +
scale_y_continuous(breaks=seq(0,10000,1000)) +
theme(panel.grid.minor.y=element_blank()) +
labs(
title="Count of Each Class"
)

图 2 :每一类的计数
为了实现这种数据分割,我们使用了caret::createDataPartition()函数。这将创建一个partition对象,然后使用它来相应地分离cifar数据。在70%任意选择拆分比例用于训练,其余用于测试。然而,这可能是合理的80%;这是一个超参数,可以在以后的阶段调整。
# Set seed for reproducibility
set.seed(1234)# Create partition
partition <- createDataPartition(cifar$classes, p=0.7, list=FALSE)# Split data
trn_img <- cifar$images[partition,,,]
tst_img <- cifar$images[-partition,,,]
trn_cls <- cifar$classes[partition]
tst_cls <- cifar$classes[-partition]
分割后,数据被重新绘制,很容易看出训练/测试分割在两个类别上实现了均匀的70%分布。
# Print Plot
rbind(
trn_cls %>% table,
tst_cls %>% table
) %>%
set_rownames(c("train","test")) %>%
data.frame() %>%
rename_all(str_remove_all, "X") %>%
rownames_to_column("data") %>%
pivot_longer(2:3, names_to="classes", values_to="Freq") %>%
mutate(
label=paste(data, classes, sep=": "),
data=factor(data, levels=c("train","test"))
) %>%
ggplot(aes(classes, Freq, fill=data), position="dodge") +
geom_col(colour="black", position="dodge") +
geom_label(
aes(label=Freq),
position=position_dodge(width=0.9),
show.legend=FALSE
) +
scale_y_continuous(breaks=seq(0,10000,1000)) +
theme(panel.grid.minor.y=element_blank()) +
labs(
title="Count of Each Class",
subtitle="Split by Train/Test"
)

图 3 :每类计数,按训练/测试划分
检查数据是否被正确分割的另一种方法是再次运行get_ObjectAttributes()函数,如下面的代码块所示。这里显示的信息与上面的图一致。有趣的是,训练图像数组82 Mb很大,这对于以后检查正向传播的性能很重要。
for (name in c("trn_img","tst_img","trn_cls","tst_cls")) {
name %>%
get() %>%
get_ObjectAttributes(
name,
if (name %in% c("trn_cls","tst_cls")) TRUE else FALSE
) %>%
cat()
if (name != "tst_cls") cat("\n")
}
它打印:
Name : trn_img
- Size : 82 Mb
- Clas : array
- Type : integer
- Mode : numeric
- Dims : 7000x32x32x3Name : tst_img
- Size : 35.2 Mb
- Clas : array
- Type : integer
- Mode : numeric
- Dims : 3000x32x32x3Name : trn_cls
- Size : 54.7 Kb
- Clas : numeric
- Type : double
- Mode : numeric
- Dims : 7000
- Freq :
label Freq
1 0 6309
2 1 691Name : tst_cls
- Size : 23.5 Kb
- Clas : numeric
- Type : double
- Mode : numeric
- Dims : 3000
- Freq :
label Freq
1 0 2691
2 1 309
7.3.重塑数据
对于我们神经网络的第一个input层,我们希望是一个一维的节点。因此,有必要将数据从一个 4 维数组调整为一个 2 维数组。这个过程叫做展平,更多信息可以在这里找到:多维数组的内存布局。
使用array()函数可以很容易地实现该方法,因为它有dim=参数,可以用来指定所需的尺寸。
期望的矩阵尺寸应该使每个图像在新的一行,每个像素在不同的一列。因为每个像素由第 2、第 3 和第 4 维组成,我们需要取这三个数的乘积,并使用它来指定所需的列数。实际上,我们正在运行这个等式:32 × 32 × 3,这相当于拥有3072列。这个等式是在下一个代码块中以编程方式内嵌实现的。
# Reshape data
trn_img %<>% array(dim=c(
dim(.) %>% extract(1),
dim(.) %>% extract(2:4) %>% prod()
))tst_img %<>% array(dim=c(
dim(.) %>% extract(1),
dim(.) %>% extract(2:4) %>% prod()
))trn_cls %<>% array(dim=c(
length(.),
1
))tst_cls %<>% array(dim=c(
length(.),
1
))
当再次检查对象属性时,您将看到图像数据已经被正确地处理,以行数作为图像的数量,以列数作为像素的数量。
for (name in c("trn_img","tst_img","trn_cls","tst_cls")) {
name %>%
get() %>%
get_ObjectAttributes(name, FALSE) %>%
cat()
if (name != "tst_cls") cat("\n")
}
它打印:
Name : trn_img
- Size : 82 Mb
- Clas : matrix,array
- Type : integer
- Mode : numeric
- Dims : 7000x3072Name : tst_img
- Size : 35.2 Mb
- Clas : matrix,array
- Type : integer
- Mode : numeric
- Dims : 3000x3072Name : trn_cls
- Size : 54.9 Kb
- Clas : matrix,array
- Type : double
- Mode : numeric
- Dims : 7000x1Name : tst_cls
- Size : 23.6 Kb
- Clas : matrix,array
- Type : double
- Mode : numeric
- Dims : 3000x1
7.4.标准化数据
准备数据的最后一步是标准化数据,以便所有元素都在0和1之间。这样做的原因是为了防止在后面的步骤中出现爆炸和消失的梯度,因为神经网络将试图拟合所有的波峰和波谷,这是由于数据在0到255的值范围内引起的。
如 TensorFlow 网站所述, CIFAR10 数据集由 RGB 图像数据组成。并且,正如维基百科上记载的,RGB 数据都是在0和255之间的值。
所以要做的就是把所有的元素除以255,必然会产生一个在0和1之间的值。由于图像数据当前在一个数组中,下面代码块中的函数将作为一个矢量化函数在整个数组中运行,相应地将所有元素除以255。
trn_img <- trn_img/255
tst_img <- tst_img/255
数据现已准备好,可用于网络。下一步是建立网络。
8.实例化网络
8.1.定义架构
关于网络实际上是什么的一些快速注释:
- 整体架构是一个
list。 - 主
list的每一个element都是另一个list,这些将构成整体网络的每一个层。 - 第一层永远是
input层。 - 最后一层将永远是
output层。 - 中间的每一层都将是
hidden层,这些层的名称简单地用数字命名。 - 每层的每个元素都将被标记为相同,定义如下:
- —
nodz:本层节点数。 - —
inpt:输入矩阵。又名A_prev。这是前一层激活的副本,因此对于大型网络,需要考虑这一点。 - —
wgts:权重矩阵。又名W。 - —
bias:偏置向量。又名b。 - —
linr:线性矩阵。又名Z。这是inpt、wgts和bias之间的线性代数的结果。 - —
acti:激活矩阵。又名A。将激活函数应用于linr矩阵的结果。 - —
acti_func:使用的激活功能。 - —
cost:车型的整体成本。这是一个单一的值(模型的总成本),但被复制到模型的每一层。 - —
back_cost:成本向量的梯度。又名dA_cost。 - —
back_acti:激活矩阵的梯度。又名dA。应用反向传播后的微分结果。具有给定的成本函数。 - —
back_linr:线性代数矩阵的梯度。又名dZ。向后线性微分反向传播的结果。 - —
back_wgts:权重矩阵的梯度。又名dW。也是背撑的结果。 - —
back_bias:偏置向量的梯度。又名db。也是背撑的结果。
8.1.设置实例化功能
对于下面的代码块,定义了函数set_InstantiateNetwork()。它只有三个输入参数,用于指定每层中使用的节点数。基于这些信息,模型将被实例化并返回,为下一步的初始化做好准备。
set_InstantiateNetwork <- function(
input=50,
hidden=c(30,20,10),
output=1
) { # Set up
model = list()
layers = c(
"input",
1:length(hidden),
"output"
)
# Loop
for (layer in layers) {
# Make layer
model[[layer]] <- list(
"nodz" = "",
"inpt" = "",
"wgts" = "",
"bias" = "",
"linr" = "",
"acti" = "",
"acti_func" = "",
"cost" = "",
"back_cost" = "",
"back_acti" = "",
"back_linr" = "",
"back_wgts" = "",
"back_bias" = ""
)
# Set nodes
if (layer=="input") {
model[[layer]][["nodz"]] <- input
} else if (layer=="output") {
model[[layer]][["nodz"]] <- output
} else {
layer_index <- layer %>% as.numeric()
model[[layer]][["nodz"]] <- hidden[layer_index]
}
}
# Return
return(model)
}
8.1.创建网络
下面的代码块实例化了网络。该模型将在每层中设置以下数量的节点:
- 层将有 T2 节点,和上面计算的一样多。
- 从
100到20音符,每个hidden层的节点数量会逐渐减少。 output层会有1节点,因为这一个节点会是0和1之间的浮点数,用来预测相关图像是不是汽车。
network_model <- set_InstantiateNetwork(
input=3072,
hidden=c(100,75,50,30,20),
output=1
)
8.1.将网络可视化
有一个非常好的网站可以让神经网络可视化:http://alexlenail.me/NN-SVG/AlexNet.html。下图是刚刚创建的网络的示意图。
一旦网络完全初始化并向前传播,可以进行进一步的可视化。详见 章节检查模型形状 。

图 4 :网络的可视化
9.初始化网络
初始化网络有四个步骤:
- 设置重量初始化功能
- 设置层初始化功能
- 设置模型初始化功能
- 运行初始化
9.1.重量初始化
在其核心,权重初始化只是生成一个随机的正常数字(用μ=0和σ=1)。然而,通过仅使用这个随机生成的数字,当试图训练更深的神经网络时,发现模型梯度爆炸或消失。因此,这些权重在被初始化后需要被缩放,以便足够健壮以继续在更深的层被训练。
有许多算法可用于重量初始化。两个比较常见的是 Xavier 算法和 He 算法。理解这些算法背后的细节的一些好资源包括:
9.1.1。泽维尔算法
Xavier 初始化的公式为:

等式 1 : Xavier 初始化算法
其中:
- nᵢ 是进入这一层的节点数量。也称为“扇入”。
- nᵢ₊₁ 是从这一层出去的节点数。也称为“扇出”。
9.1.2。何算法
he 初始化的公式为:

等式 2 :初始化算法
其中:
- nᵢ是进入这一层的节点数量。
9.1.初始化功能
出于编程目的,这些函数是用order值作为函数参数的一部分编写的。这意味着方程的数量级可以在稍后阶段改变,并用作超参数。
let_InitialiseXavier <- function(nodes_in, nodes_out, order=6) {
# Do work
numer <- sqrt(order)
denom <- sqrt(nodes_in + nodes_out)
output <- numer/denom
# Return
return(output)
}let_InitialiseHe <- function(nodes_in, nodes_out, order=2) {
# Do work
numer <- order
denom <- nodes_in
output <- sqrt(numer/denom)
# Return
return(output)
}
9.3.层初始化
下一步是构建一个函数,该函数将初始化一个单独层的所有相关方面。这一步很重要,因为这是创建权重矩阵的地方,并且这些权重矩阵必须以某种方式构建,以确保维度允许成功的向前传播。
层构造的步骤如下:
- 确定当前层的层名(
layer)和前一层的层名(layer_prev)。
—用于从network_model列表中访问相关配置。 - 确定馈入本层的节点数(
nodes_in)和馈出当前层的节点数(nodes_out)。
—用于解析初始化算法。 - 创建权重矩阵。
—尺寸如下:
———1。行数是前一层中的节点数。
———2。列数是当前层中的节点数。
—使用rnorm()函数创建每个元素,该函数使用μ=0和σ=1在正常曲线上生成一个随机数。 - 确定要使用的初始化算法。
—解析到该函数中的 value 值是与相关算法相关的小写单词。
—强制转换为标题大写,然后赋予前缀let_Initialise。
—该值然后被解析到get()函数中,该函数然后调用该函数,并执行解析到该函数的参数。
—这是一种灵活调用不同算法的编程方式,基于解析到函数的值。 - 缩放权重矩阵。
—通过将每个元素乘以初始化算法。 - 创建偏差矩阵。
—尺寸如下:
— — 1。行数是当前层中的节点数。
———2。只有一列。
—每个元素都有值0。 - 将重量和偏差矩阵重新应用到
network_model上。 - 返回更新后的
network_model对象。
为了实现这一点,函数使用的函数参数包括:
network_model本身。- 该层的
layer_index(其中1为input层,每个递增的数字为每个后续层)。 initialisation_algorithm,或者是值NA,或者是值xavier或者是相关算法的值he。initialisation_order,它是一个整数值,在相关算法中用作分子。
set_InitialiseLayer <- function(
network_model,
layer_index,
initialisation_algorithm=NA,
initialisation_order=6
) {
# Get layer names
layer_prev <- names(network_model)[layer_index-1]
layer <- names(network_model)[layer_index]
# Get number of nodes
if (layer_index == 1) {
nodes_in <- 0 #First layer is 'input'
} else {
nodes_in <- network_model %>%
extract2(layer_prev) %>%
extract2("nodz")
}
nodes_out <- network_model %>%
extract2(layer) %>%
extract2("nodz")
# Set the seed of reproducibility
set.seed(1234)
# Initialise weight matrix
w_matrix <- matrix(
data=rnorm(nodes_in * nodes_out),
nrow=nodes_in,
ncol=nodes_out
)
# Get initialisation algorithm
if (!is.na(initialisation_algorithm)) {
algorithm <- paste0(
"let_Initialise",
str_to_title(initialisation_algorithm)
)
}
# Scale weights
if (layer_index != 1) {
if (is.na(initialisation_algorithm)) {
w_matrix <- w_matrix
} else {
w_matrix <- w_matrix *
get(algorithm)(
nodes_in=nodes_in,
nodes_out=nodes_out,
order=initialisation_order
)
}
}
# Initialise bias matrix
b_matrix <- matrix(
data=network_model %>%
extract2(layer) %>%
extract2("nodz") %>%
replicate(0),
nrow=network_model %>%
extract2(layer) %>%
extract2("nodz"),
ncol=1
)
# Place data back in to the model
network_model[[layer]][["wgts"]] <- w_matrix
network_model[[layer]][["bias"]] <- b_matrix
# Return
return(network_model)
}
9.4.模型初始化
set_InitialiseModel()功能的目的是循环通过network_model对象中的每一层,根据模型本身的参数设置对其进行初始化。该功能将获取节点数(由set_InstantiateNetwork()功能设置)。
该函数将只接受三个输入参数:
network_model:
—由set_InstantiateNetwork()函数实例化的网络。initialisation_algorithm:
—用于网络初始化的算法。
—这应该是值NA、xavier或he。initialisation_order:
—用于等式分子的顺序值。
—这可以是一个数值,或者是字符串layers,表示顺序应该是网络中隐藏层的数量。
set_InitialiseModel <- function(
network_model,
initialisation_algorithm="xavier",
initialisation_order="layers"
) {
# Redefine 'initialisation_order'
if (initialisation_order == "layers") {
initialisation_order <- get_CountOfElementsWithCondition(
names(network_model),
function(x){is.integer(as.numeric(x))}
)
}
# Initialise each layer
for (layer_index in 1:length(names(network_model))) {
network_model <- set_InitialiseLayer(
network_model=network_model,
layer_index=layer_index,
initialisation_algorithm=initialisation_algorithm,
initialisation_order=initialisation_order
)
}
# Return
return(network_model)
}
注意,该函数使用了用户自定义函数get_CountOfElementsWithCondition()。该函数允许计算模型中隐藏层的数量。该功能的源代码可以在这里找到。
9.5.网络初始化
下面的代码块使用上面定义的函数初始化网络。使用的方法是%<>%,它在magrittr包中定义,说明它用于更新左侧的值,首先通过管道将它输入右侧的第一个参数位置,然后将结果赋回左侧的对象。
network_model %<>% set_InitialiseModel()
9.6.检查模型参数
为了快速检查,下面的代码块检查已定义模型的参数数量。这再次使用了一个定制函数get_ModelParametersCount(),它的定义可以在本文的源代码中找到(位于这里)。
network_model %>%
get_ModelParametersCount() %>%
format(big.mark=",")
它打印:
[1] "320,846"
该神经网络中有超过320,000个参数。这些参数中的每一个都需要训练,并且每一个都会对最终结果产生影响。在接下来的部分中,我们将继续介绍如何实现这一点。
10.正向传播
10.1.该理论
有一个非常好的网站概述了矩阵乘法中实际发生的事情:矩阵乘法。
正向传播方法的理论如下:
- 应用矩阵乘法
— 1。第一个矩阵是来自前一层的激活矩阵。
— 2。第二个矩阵是当前层的权重矩阵。
— 3。第三矩阵是当前层的(第一)线性激活矩阵。 - 应用偏置矩阵
— 1。第一矩阵是当前层的(第一)线性激活。
— 2。第二个矩阵是当前层的偏差矩阵。
— 3。第三矩阵是当前层的(第二)线性激活矩阵。 - 应用激活算法
— 1。第一矩阵是当前层的(第二)线性激活矩阵。
— 2。激活功能由用户在功能运行期间确定。可以是relu激活、sigmoid激活或任何其他激活功能。
— 3。第三个矩阵是当前层的激活矩阵。
10.1.1。第一步
为了说明,下面的矩阵显示了正向传播的第一步,使用了虚拟数字。

图五:前进道具,第一步
下面的代码显示了这个过程是如何以编程方式实现的。
# Declare First matrix
matrix_input <- matrix(
data=1:24,
nrow=8,
ncol=3,
byrow=TRUE
)# Declare Weight matrix
matrix_weight <- matrix(
data=1:15,
nrow=3,
ncol=5,
byrow=TRUE
)# Apply matrix manipulation
matrix_layer <- matrix_input %*% matrix_weight
它打印:
matrix_input:
[,1] [,2] [,3]
[1,] 1 2 3
[2,] 4 5 6
[3,] 7 8 9
[4,] 10 11 12
[5,] 13 14 15
[6,] 16 17 18
[7,] 19 20 21
[8,] 22 23 24matrix_weight:
[,1] [,2] [,3] [,4] [,5]
[1,] 1 2 3 4 5
[2,] 6 7 8 9 10
[3,] 11 12 13 14 15matrix_layer:
[,1] [,2] [,3] [,4] [,5]
[1,] 46 52 58 64 70
[2,] 100 115 130 145 160
[3,] 154 178 202 226 250
[4,] 208 241 274 307 340
[5,] 262 304 346 388 430
[6,] 316 367 418 469 520
[7,] 370 430 490 550 610
[8,] 424 493 562 631 700
10.1.2。第二步
下图显示了如何应用偏置矩阵。如图所示,偏置矩阵中的每个元素水平应用于初始矩阵的每个元素。这张图表显示了它是如何工作的。

图 6 :前进支柱,第二步
下面的代码显示了这个过程是如何以编程方式实现的。
# Declare Bias matrix
vector_bias <- matrix(1:8, 8, 1)# Apply Bias matrix
matrix_biased <- sweep(matrix_layer, 1, vector_bias, "+")
它打印:
matrix_layer:
[,1] [,2] [,3] [,4] [,5]
[1,] 46 52 58 64 70
[2,] 100 115 130 145 160
[3,] 154 178 202 226 250
[4,] 208 241 274 307 340
[5,] 262 304 346 388 430
[6,] 316 367 418 469 520
[7,] 370 430 490 550 610
[8,] 424 493 562 631 700vector_bias:
[,1]
[1,] 1
[2,] 2
[3,] 3
[4,] 4
[5,] 5
[6,] 6
[7,] 7
[8,] 8matrix_biased:
[,1] [,2] [,3] [,4] [,5]
[1,] 47 53 59 65 71
[2,] 102 117 132 147 162
[3,] 157 181 205 229 253
[4,] 212 245 278 311 344
[5,] 267 309 351 393 435
[6,] 322 373 424 475 526
[7,] 377 437 497 557 617
[8,] 432 501 570 639 708
10.1.3。第三步
激活功能是在运行该功能时定义的功能。该算法应用于初始矩阵的每个元素。在这种情况下,使用初始矩阵的简单乘法。

图 7 :前进道具,第三步
下面的代码显示了这个过程是如何以编程方式实现的。
# Apply Activation function
matrix_output <- matrix_biased * (0.01 * matrix_biased)
它打印:
matrix_biased:
[,1] [,2] [,3] [,4] [,5]
[1,] 47 53 59 65 71
[2,] 102 117 132 147 162
[3,] 157 181 205 229 253
[4,] 212 245 278 311 344
[5,] 267 309 351 393 435
[6,] 322 373 424 475 526
[7,] 377 437 497 557 617
[8,] 432 501 570 639 708matrix_output:
[,1] [,2] [,3] [,4] [,5]
[1,] 22.09 28.09 34.81 42.25 50.41
[2,] 104.04 136.89 174.24 216.09 262.44
[3,] 246.49 327.61 420.25 524.41 640.09
[4,] 449.44 600.25 772.84 967.21 1183.36
[5,] 712.89 954.81 1232.01 1544.49 1892.25
[6,] 1036.84 1391.29 1797.76 2256.25 2766.76
[7,] 1421.29 1909.69 2470.09 3102.49 3806.89
[8,] 1866.24 2510.01 3249.00 4083.21 5012.64
10.2.线性分量
当组合在一起时,线性代数函数只用三行代码就实现了,如下面的set_LinearForward()函数中的代码块所示。
set_LinearForward <- function(inpt, wgts, bias) {
# Perform matrix multiplication
linr <- inpt %*% wgts
# Add bias
linr <- sweep(linr, 2, bias, "+")
# Return
return(linr)
}
10.3.非线性分量
神经网络的真正力量来自于它们的激活函数。现在,网络能够捕捉非线性方面,这就是强调其预测能力的原因。
根据网络的目的,激活功能可以是许多不同算法中的一种。激活的选择可以是在稍后阶段选择的超参数。 Desmos 网站提供了一种极好的交互方式来查看不同类型的激活:激活功能。
每个激活函数都是单独定义的,并且每个函数只接受一个参数,这是一个将被激活的矩阵。为了简洁起见,这里提供了四种比较流行的激活方式;但是还有很多很多其他的方法可以使用。还提供了如何计算这些函数的资源和等式:
**sigmoid** :

方程式 3 :乙状结肠激活
来源:
1。如何在 Python 中计算一个 logistic sigmoid 函数
2。使用 Numpy 实现 sigmoid 函数
**relu** :

方程式 4 : Relu 激活
来源:
1。带 Sigmoid、ReLu 和 Softmax 激活功能的 NumPy 初学者指南
**softmax**T25:

方程式 5: Softmax 激活
来源:
1。 Softmax 激活功能说明
**swish** :

方程式 6 :嗖嗖激活
来源:
1。搜索激活功能
2。在 Keras 中实现 Swish 激活功能
这些激活函数以编程方式定义如下:
let_ActivateSigmoid <- function(linr) {
# Do work
acti <- 1/(1+exp(-linr))
# Return
return(acti)
}let_ActivateRelu <- function(linr) {
# Do work
acti <- sapply(linr, max, 0) %>%
structure(dim=dim(linr))
# Return
return(acti)
}let_ActivateSoftmax <- function(linr) {
# Do work
expo <- exp(linr)
expo_sum <- sum(exp(linr))
acti <- expo/expo_sum
# Return
return(acti)
}let_ActivateSwish <- function(linr, beta=0.1) {
# Do work
acti <- linr * (beta * linr)
# Return
return(acti)
}
10.4.设置正向传播函数
set_ForwardProp()函数集合了上面提到的所有组件。它执行以下步骤:
- 循环通过
network_model的每一层。 - 获取当前图层的图层名称。
- 对
input层实施一个“通过”过程。 - 提取相关信息,包括:
—1。上一层的层名
—2。上一层的激活矩阵
—3。当前层
— 4 的权重矩阵。电流层的偏置矩阵 - 应用线性代数组件。
- 应用非线性激活组件。
- 请注意隐藏层的激活和最终层的激活之间的差异。
- 将相关信息应用回网络。
- 返回
network_model对象。
为了实现这个过程,这个函数只有四个参数:
network_model:待更新的网络模型。data_in:训练图像的 4 维数组,如上定义。activation_hidden:应用于隐藏层的激活功能。activation_final:应用于最终(output)层的激活功能。
set_ForwardProp <- function(
network_model,
data_in,
activation_hidden="relu",
activation_final="sigmoid"
) {
# Do work
for (index in network_model %>% names() %>% length() %>% 1:.) {
# Define layer name
layr <- network_model %>%
names() %>%
extract(index)
if (layr=="input") {
# Pass-thru for 'input' layer
network_model[[layr]][["inpt"]] <- data_in
network_model[[layr]][["acti"]] <- data_in
} else {
# Extract data
prev <- names(network_model)[index-1]
inpt <- network_model %>%
extract2(prev) %>%
extract2("acti")
wgts <- network_model %>%
extract2(layr) %>%
extract2("wgts")
bias <- network_model %>%
extract2(layr) %>%
extract2("bias")
# Calculate
linr <- set_LinearForward(inpt, wgts, bias)
# Activate
if (layr=="output") {
func <- activation_final %>%
str_to_title() %>%
paste0("let_Activate", .) %>%
get()
acti <- func(linr)
network_model[[layr]][["acti_func"]] <- activation_final
} else {
func <- activation_hidden %>%
str_to_title() %>%
paste0("let_Activate", .) %>%
get()
acti <- func(linr)
network_model[[layr]][["acti_func"]] <- activation_hidden
}
# Apply back to our model
network_model[[layr]][["inpt"]] <- inpt
network_model[[layr]][["linr"]] <- linr
network_model[[layr]][["acti"]] <- acti
}
}
# Return
return(network_model)
}
10.5.向前传播
正向传播过程的最后一步是实际运行函数。在下面的代码块中,实现了tic()和toc()函数来计算进程运行的时间。
tic()
network_model %<>% set_ForwardProp(trn_img, "relu", "sigmoid")
toc()
它打印:
7.05 sec elapsed
如上面 分割数据 部分所述,trn_img对象超过82 Mb大,模型超过320,000参数。向前传播的整个端到端过程仅用了7 seconds来运行;令人印象深刻,证明了数学的力量。
10.6.检查模型形状
现在可以使用自定义功能get_PrintNetwork()打印网络。这个函数在一个单独的盒子中返回每一层,包含关键信息,如矩阵的相关形状和使用的激活函数。该功能的源代码可以在这里找到。
# Print the Network
network_model %>%
get_PrintNetwork() %>%
cat()
它打印:
+--------------------------------+
| Layer : input |
| Nodes : 3,072 |
| Inpt Shape : 7,000 x 3,072 |
| Wgts Shape : 0 x 3,072 |
| Outp Shape : 7,000 x 3,072 |
| Activation : |
+--------------------------------+
|
V
+--------------------------------+
| Layer : 1 |
| Nodes : 100 |
| Inpt Shape : 7,000 x 3,072 |
| Wgts Shape : 3,072 x 100 |
| Outp Shape : 7,000 x 100 |
| Activation : relu |
+--------------------------------+
|
V
+--------------------------------+
| Layer : 2 |
| Nodes : 75 |
| Inpt Shape : 7,000 x 100 |
| Wgts Shape : 100 x 75 |
| Outp Shape : 7,000 x 75 |
| Activation : relu |
+--------------------------------+
|
V
+--------------------------------+
| Layer : 3 |
| Nodes : 50 |
| Inpt Shape : 7,000 x 75 |
| Wgts Shape : 75 x 50 |
| Outp Shape : 7,000 x 50 |
| Activation : relu |
+--------------------------------+
|
V
+--------------------------------+
| Layer : 4 |
| Nodes : 30 |
| Inpt Shape : 7,000 x 50 |
| Wgts Shape : 50 x 30 |
| Outp Shape : 7,000 x 30 |
| Activation : relu |
+--------------------------------+
|
V
+--------------------------------+
| Layer : 5 |
| Nodes : 20 |
| Inpt Shape : 7,000 x 30 |
| Wgts Shape : 30 x 20 |
| Outp Shape : 7,000 x 20 |
| Activation : relu |
+--------------------------------+
|
V
+--------------------------------+
| Layer : output |
| Nodes : 1 |
| Inpt Shape : 7,000 x 20 |
| Wgts Shape : 20 x 1 |
| Outp Shape : 7,000 x 1 |
| Activation : sigmoid |
+--------------------------------+
11.计算成本
一旦前向传播部分完成,就有必要测量模型的错误程度。这将用于在反向传播步骤中更新模型参数。
11.1.设置成本函数
第一步是编写用于获取模型成本的函数。最终,第一轮训练的结果有多差并不重要;记住,模型是用随机数初始化的。重要的是,成本函数应该确定网络成本的单个值,并且该单个函数将被用于反向传播步骤中使用的导数函数。
请注意,这里使用了一个非常小的ε值(epsi),它有效地调整了模型做出的完美预测。这样做的原因不是我们不希望模型预测一个完美值,而是我们希望模型预测一个完美值的概率。此外,不可能取一个0的对数值,因此有必要将它调整为稍微偏离零点一点。
get_ComputeCost <- function(pred, true, epsi=1e-10) {
# Get number of samples
samp <- length(true)
# Instantiate totals
total_cost <- 0
# Loop for each prediction
for (i in 1:samp) {
# Adjust for perfect predictions
if (pred[i]==1) {pred[i] %<>% subtract(epsi)}
if (pred[i]==0) {pred[i] %<>% add(epsi)}
# Calculate totals
total_cost <- total_cost -
(
true[i] * log(pred[i])
+
(1-true[i]) * log(1-pred[i])
)
}
# Take an average
cost <- (1/samp) * total_cost
# Return
return(cost)
}
然后,必须将成本应用回网络。为此,将完全相同的值应用于网络的每一层。
set_ApplyCost <- function(network_model, cost) {
# Apply back to the model
for (layer in network_model %>% names) {
network_model[[layer]][["cost"]] <- cost
}
# Return
return(network_model)
}
11.2.运行成本函数
下面的代码块运行成本函数,利用上面定义的函数。
network_model %<>% set_ApplyCost(
get_ComputeCost(network_model[["output"]][["acti"]], trn_cls)
)
12.反向传播
反向传播函数旨在获取模型的成本,然后区分网络中的每个权重和偏差矩阵,以确定网络中的每个参数对最终成本值的贡献程度。要做到这一点,流程从结束到开始反向工作,遵循以下步骤:
- 区分最终成本值
- 区分激活矩阵
- 线性代数矩阵的微分
- 继续上一层。
反向传播过程中的每一步都需要微积分来导出值,最终的函数在这里实现。由于本文并不打算展示如何推导方程,而是更多地展示如何运行函数,因此这里不包括必要的代数步骤。
12.1.区分成本
与用于获取成本的函数类似,首先计算成本差值,然后将其应用于网络的每一层。
get_DifferentiateCost <- function(pred, true) {
# Do work
diff_cost <- -(
divide_by(true, pred) - divide_by(1-true, 1-pred)
)
# Return
return(diff_cost)
}set_ApplyDifferentiateCost <- function(
network_model,
cost_differential
) {
# Do work
for (layer in names(network_model)) {
network_model[[layer]][["back_cost"]] <- cost_differential
if (layer=="output") {
network_model[[layer]][["back_acti"]] <- network_model %>%
extract2(layer) %>%
extract2("back_cost") %>%
t()
}
}
# Return
return(network_model)
}
12.2.差异化激活
因为每个激活都有自己的函数,所以也有一个可以用微积分计算的函数的导数。
let_BackwardActivateRelu <- function(diff_acti_curr, linr_curr) {
# Do work
diff_linr_curr <- diff_acti_curr
diff_linr_curr[linr_curr<=0] <- 0
# Return
return(diff_linr_curr)
}let_BackwardActivateSigmoid <- function(diff_acti_curr, linr_curr) {
# Do work
temp <- 1/(1+exp(-linr_curr))
diff_linr_curr <- t(diff_acti_curr) * temp * (1-temp)
# Return
return(t(diff_linr_curr))
}
12.3.微分线性
到目前为止,已经定义了所有这些,下一步是组合成一个单一的函数,它可以在每层使用一次,运行必要的反向传播微分函数。
注意这里的输出实际上是三个元素的列表。这是R和python的关键区别。在R中,函数只能输出单个元素;相比之下python能够从每个函数返回多个元素。
get_DifferentiateLinear <- function(
back_linr_curr,
acti_prev,
wgts,
bias
) {
# get number of samples
samp <- acti_prev %>% dim %>% extract(2)
# Differentiate weights
diff_wgts <- 1/samp * (back_linr_curr %*% acti_prev)
# Differentiate bias
diff_bias <- 1/samp * rowSums(back_linr_curr, dims=1)
# Differentiate activation
diff_acti_prev <- wgts %*% back_linr_curr
# Consolidate in to one list
list_linr <- list(
diff_acti_prev,
diff_wgts,
diff_bias
)
# Return
return(list_linr)
}
12.4.反向传播
在定义了微分函数之后,接下来需要将这些单独的函数组合成一个单一的组合函数,该函数可以每层运行一次。
首先,我们将设置函数,其次我们将运行它。
12.4.1。设置反向传播功能
待定义的反向传播函数(set_BackwardProp())必须设计为通过以下步骤运行:
- 沿反方向穿过每一层。这是必要的,因为从逻辑上讲,反向传播函数需要反向运行。
- 提取图层名称。
- 跳过
input层。同样,这也很符合逻辑,因为这一层位于网络的起点,不需要反向传播。 - 提取上一层的名称。
- 提取相关矩阵用于后续计算。
- 提取该特定层的相关激活函数。
- 设置一些空矩阵,这些空矩阵将存放相关的微分矩阵。
- 求当前层的
activation矩阵的微分 - 区分
linear矩阵,包括:当前层的
—weight矩阵。
—bias当前层的矩阵。
—activation前一层的矩阵。 - 将信息应用回
network_model中的相关位置。 - 返回更新后的
network_model。
set_BackwardProp <- function(network_model) {
# Loop through each layer in reverse order
for (layr_indx in network_model %>% names() %>% length() %>% 1:. %>% rev) {
# Get the layer name
layr_curr <- network_model %>%
names() %>%
extract(layr_indx)
# Skip the 'input' layer
if (layr_curr == "input") next
# Get the previous layer name
layr_prev <- network_model %>%
names %>%
extract(layr_indx-1)
# Set up the existing matrices
linr_curr <- network_model %>%
extract2(layr_curr) %>%
extract2("linr")
wgts_curr <- network_model %>%
extract2(layr_curr) %>%
extract2("wgts")
bias_curr <- network_model %>%
extract2(layr_curr) %>%
extract2("bias")
acti_prev <- network_model %>%
extract2(layr_prev) %>%
extract2("acti")
diff_acti_curr <- network_model %>%
extract2(layr_curr) %>%
extract2("back_acti")
# Get the activation function
acti_func_back <- network_model %>%
extract2(layr_curr) %>%
extract2("acti_func") %>%
str_to_title %>%
paste0("let_BackwardActivate", .)
# Set up the empty matrices
diff_linr_curr <- matrix()
diff_acti_prev <- matrix()
diff_wgts_curr <- matrix()
diff_bias_curr <- matrix()
# Differentiate activation
diff_linr_curr <- get(acti_func_back)(
diff_acti_curr,
linr_curr
)
# Differentiate linear
list_linr <- get_DifferentiateLinear(
back_linr_curr=diff_linr_curr,
acti_prev=acti_prev,
wgts=wgts_curr,
bias=bias_curr
)
diff_acti_prev <- list_linr %>% extract2(1)
diff_wgts_curr <- list_linr %>% extract2(2)
diff_bias_curr <- list_linr %>% extract2(3)
# Apply back to model
network_model[[layr_prev]][["back_acti"]] <- diff_acti_prev
network_model[[layr_curr]][["back_linr"]] <- diff_linr_curr
network_model[[layr_curr]][["back_wgts"]] <- diff_wgts_curr
network_model[[layr_curr]][["back_bias"]] <- diff_bias_curr
}
# Return
return(network_model)
}
12.4.2。运行反向传播功能
定义了这个函数之后,下一步是运行这个函数。下面的代码块用tic()和toc()函数包装,以确定函数运行需要多少时间。
tic()
network_model %<>% set_BackwardProp()
toc()
它打印:
8.84 sec elapsed
如图所示,运行这个函数大约需要 9 秒钟。考虑到有超过320,000个参数需要更新(参见 检查模型参数 一节),这是相当可观的。
13.更新模型参数
13.1.语境
在模型参数被差分后,在通过重新运行前向传播函数再次重新训练网络之前,需要更新网络的相关参数(权重和偏差)。
用于更新这些参数的方法被称为随机梯度下降。更多信息,参见神经网络中的随机梯度学习或反向传播和随机梯度下降法。
当然,还有其他实现神经网络优化的方法。文献在这方面已经花费了大量的精力。存在诸如 RMSProp 和 Adam 的算法,这些算法实现了智能方法以实现更快的收敛和更精确的最终结果。这方面的一些好资料来源包括机器学习模型优化器的经验比较和神经网络不同优化器的概述。为了进一步增强和优化,探索这些优化选项非常重要。
13.2.过程
尽管如此,实现随机梯度下降的过程实际上非常简单:
- 在
0和1之间指定一个给定的学习率(通常是很小的数字,例如0.001)。 - 取差分后的权重和偏差矩阵,在负方向上乘以学习率。
- 将更新的差分权重和偏差矩阵相加,并添加到原始的权重和偏差矩阵。
- 逐层重复这个过程。
13.3.设置更新模型功能
为了设置用于更新模型参数的功能,使用以下步骤:
- 指定
learning_rate作为函数的参数。 - 正向循环通过网络中的每一层。
- 提取图层名称。
- 跳过
input层(因为它不需要更新)。 - 定义
back_wgts和back_bias矩阵的梯度步骤。 - 将梯度步骤应用于原始的
wgts和bias矩阵。 - 返回更新的模式。
set_UpdateModel <- function(network_model, learning_rate=0.001) {
# Do work
for (index in network_model %>% names() %>% length() %>% 1:.) {
# Get layer name
layr <- network_model %>%
names() %>%
extract(index)
# Skip 'input' layer
if (layr=="input") next
# Define gradient steps for the weight
grad_step_wgts <- -1 *
(
learning_rate * network_model[[layr]][["back_wgts"]]
) # Define gradient steps for the bias
grad_step_bias <- -1 *
(
learning_rate * network_model[[layr]][["back_bias"]]
)
# Take steps
network_model[[layr]][["wgts"]] <- network_model %>%
extract2(layr) %>%
extract2("wgts") %>%
add(t(grad_step_wgts))
network_model[[layr]][["bias"]] <- network_model %>%
extract2(layr) %>%
extract2("bias") %>%
add(grad_step_bias)
}
# Return
return(network_model)
}
13.4.运行更新模型功能
定义了函数之后,我们运行网络模型。
network_model %<>% set_UpdateModel(0.01)
14.端到端运行模型
现在,是时候把这一切结合起来了。端到端运行模型实质上意味着:
- 应用正向传播。
- 计算成本。
- 运行反向传播。
- 更新模型参数。
- 重复…
这种重复的每一次被称为一个时期。对于网络来说,很典型的是经过许多时代才被更新。有时数百个,有时数千个纪元;运行的确切历元数由数据科学家根据众多变量自由决定。
这里需要添加一个额外的步骤,那就是批处理数据。为此,我们可以考虑在每个历元内,将数据分批到可等分的组中,并用于后续处理。在这种情况下,模型参数将在每批之后更新。当全部数据都完整地通过模型时,这就被认为是一个时期。
14.1.设置列车模型功能
为了以编程方式说明这一点,编写了以下函数。
注意,这里包含了许多自定义函数(每个函数的源代码可以在这里找到)。这些功能包括:
get_Modulus()get_BatchIndexes()get_VerbosityValues()get_TimeDifference()plt_PlotLearningCurve()
该功能的步骤包括:
- 开始计时。
- 声明将返回哪些信息。
- 实例化网络。
- 初始化网络。
- 循环通过每个时期。
- 循环每个批次。
- 对该特定批次的数据进行子集划分。
- 向前传播。
- 计算成本。
- 将成本应用于网络。
- 区分成本。
- 运行反向传播。
- 更新模型参数。
- 运行下一批。
- 在纪元结束时节省总成本。
- 打印相关纪元编号处的更新(来自
verbosity参数) - 贯穿下一个纪元。
- 保存更新的网络。
- 打印学习曲线。
- 返回输出。
let_TrainModel <- function(
x_train,y_train,
input_nodes=dim(x_train)[2],
hidden_nodes=c(100, 50, 10),
output_nodes=1,
initialisation_algorithm="xavier",
initialisation_order="layers",
activation_hidden="relu",
activation_final="sigmoid",
batches=get_Modulus(dim(x_train)[1])[4],
epochs=500,
learning_rate=0.001,
verbosity=NA,
print_learning_curve=TRUE
) {
# Begin the timer
time_begin <- Sys.time()
# Set return values
output <- list(
network_model=network_model,
results=list(
cost=vector()
# Can add more, such as accuracy or specificity.
)
)
# Instantiate
network_model <- set_InstantiateNetwork(
input=input_nodes,
hidden=hidden_nodes,
output=output_nodes
)
# Initialise
network_model <- set_InitialiseModel(
network_model=network_model,
initialisation_algorithm=initialisation_algorithm,
initialisation_order=initialisation_order
)
# Loop each epoch
for (epoch in 1:epochs) {
# Loop each batch
for (batch in 1:batches) {
# Set indices
batch_indexes <- get_BatchIndexes(
vector=1:dim(x_train)[1],
batches=batches,
batch=batch,
seed=1234
)
# Set data
x_train_batch <- x_train[batch_indexes,]
y_train_batch <- y_train[batch_indexes]
# Forward Prop
network_model <- set_ForwardProp(
network_model = network_model,
data_in = x_train_batch,
activation_hidden = activation_hidden,
activation_final = activation_final
)
# Get cost
cost <- get_ComputeCost(
pred = network_model[["output"]][["acti"]],
true = y_train_batch,
epsi = 1e-10
)
# Apply cost
network_model <- set_ApplyCost(
network_model = network_model,
cost = cost
)
# Differentiate cost
network_model <- set_ApplyDifferentiateCost(
network_model = network_model,
cost_differential = get_DifferentiateCost(network_model[["output"]][["acti"]], y_train_batch)
)
# Backprop
network_model <- set_BackwardProp(network_model)
# Update parameters
network_model <- set_UpdateModel(
network_model = network_model,
learning_rate = learning_rate
)
}
# Save cost
output[["results"]][["cost"]] %<>% c(cost)
# Print update
if (!is.na(verbosity)) {
if (epoch %in% get_VerbosityValues(epochs, verbosity)){
if (epoch == verbosity) {
"Learning rate: {}\n" %>%
str_Format(learning_rate) %>%
cat()
}
"Epoch {}, Cost: {}, Time: {}\n" %>%
str_Format(
epoch,
round(cost, 5),
get_TimeDifference(time_begin)
) %>%
cat()
}
}
}
# Re-apply back to the output list
output[["network_model"]] <- network_model
# Print the results
if (print_learning_curve == TRUE) {
tryCatch(
expr={
output %>%
extract2("results") %>%
extract2("cost") %>%
plt_PlotLearningCurve(
input_nodes=input_nodes,
hidden_nodes=hidden_nodes,
output_nodes=output_nodes,
initialisation_algorithm=
initialisation_algorithm,
initialisation_order=initialisation_order,
activation_hidden=activation_hidden,
activation_final=activation_final,
epochs=epochs,
learning_rate=learning_rate,
verbosity=verbosity,
run_time=get_TimeDifference(time_begin)
) %>%
print()
},
warning=function(message){
writeLines("A Warning occurred:")
writeLines(message)
return(invisible(NA))
},
error=function(message){
writeLines("An Error occurred:")
writeLines(message)
return(invisible(NA))
},
finally={
#Do nothing...
}
)
}
# Return
return(output)
}
14.2.运行列车模型功能
已经设置了培训功能,现在让我们运行它。
请注意:
- 仅使用训练数据(参见 分割数据 部分)。
- 使用 he 初始化算法,阶数为 2。
- relu 激活算法用于隐藏层,sigmoid 激活算法用于输出层。
- 有 56 个批次,50 个纪元。
- 每 10 个时期打印一次模型结果。
- 学习曲线打印在最后。
training_output <- let_TrainModel(
x_train=trn_img,
y_train=trn_cls,
input_nodes=dim(trn_img)[2],
hidden_nodes=c(100,75,50,30,20),
output_nodes=1,
initialisation_algorithm="he",
initialisation_order=2,
activation_hidden="relu",
activation_final="sigmoid",
batches=56, epochs=50,
learning_rate=0.01,
verbosity=10,
print_learning_curve=TRUE
)
它打印:
Learning rate: 0.01
Epoch 10, Cost: 0.32237, Time: 4.86 mins
Epoch 20, Cost: 0.30458, Time: 9.58 mins
Epoch 30, Cost: 0.28903, Time: 12.23 mins
Epoch 40, Cost: 0.29525, Time: 14.87 mins
Epoch 50, Cost: 0.29884, Time: 17.43 mins

图 8 :神经网络学习曲线
有用!这个情节证明了模型是在训练的,随着时间的推移,它在继续学习,提高性能。
请注意,成本线大约从0.4开始,并在 20 个时期后迅速下降到0.3。为了完成全部 50 个纪元,这大约需要12 minutes。这可以看作是一种成功。
14.3.进一步的实验
由于这些功能已经被设置的性质,进一步的实验和优化是非常容易的。
尝试以下方法可能是合理的:
- 不同的隐藏层数或每层的节点数(改变
hidden_nodes参数), - 不同的初始化算法或初始化顺序(改变
initialisation_alghorithm或initialisation_order参数), - 隐藏层上的不同激活功能(改变
activation_hidden参数), - 每个时期不同的批次数量(更改
batches参数), - 不同数量的时期(改变
epochs参数), - 不同的学习率(改变
learning_rate参数), - 获取更多数据(回想在 下载数据 部分,仅
10,000图像被下载;但是还有另一个50,000图像可用于进一步训练)。
这是一个尝试不同参数的实验。注意结果是如何变化的。这下一轮训练花了34 minutes跑。
training_output <- let_TrainModel(
x_train=trn_img,
y_train=trn_cls,
input_nodes=dim(trn_img)[2],
hidden_nodes=c(100,75,50,30,20),
output_nodes=1,
initialisation_algorithm="xavier",
initialisation_order="layers",
activation_hidden="relu",
activation_final="sigmoid",
batches=56, epochs=100,
learning_rate=0.001,
verbosity=20,
print_learning_curve=TRUE
)
它打印:
Learning rate: 0.001
Epoch 20, Cost: 0.33354, Time: 5.4 mins
Epoch 40, Cost: 0.29891, Time: 10.26 mins
Epoch 60, Cost: 0.30255, Time: 15.2 mins
Epoch 80, Cost: 0.29968, Time: 20.84 mins
Epoch 100, Cost: 0.29655, Time: 26.09 mins

图 9 :神经网络的第二条学习曲线
然后,数据科学家的工作是确定这些参数的最佳配置,以实现更好的性能比率。参见机器学习模型中的超参数优化了解更多详情。
然而,由于这篇文章不是为了让达到最佳结果,而是为了让展示如何达到所述结果,以及所涉及的过程/方法。因此,本文不再做进一步的实验。
15.创建预测
训练完网络后,下一步是将它应用于test数据,看看它能多准确地预测未知数据。
15.1.设置预测
第一步是编写两个自定义函数。第一个将接受测试数据和训练模型,并将返回包含预测值和真实值的数据框。然后以一种ggplot风格的输出漂亮地打印出混淆矩阵。
get_Prediction <- function(
x_test,
y_test,
network_model,
threshold=0.5
) {
# Create prediction
predic <- set_ForwardProp(
network_model=network_model,
data_in=x_test,
activation_hidden="relu",
activation_final="sigmoid"
)
# Extract probabilities
probas <- predic %>%
extract2("output") %>%
extract2("acti")
# Define results
result <- data.frame(
probs=probas,
truth=y_test
)
# Add prdic
result %<>%
mutate(prdic=ifelse(probas>threshold, 1, 0))
# Return
return(result)
}plt_ConfusionMatrix <- function(confusion_matrix) { # Do work
plot <- confusion_matrix %>%
extract("table") %>%
as.data.frame() %>%
rename_all(str_remove_all, "table.") %>%
rename("Prediction"=1, "Reference"=2) %>%
mutate(
goodbad = ifelse(
Prediction == Reference,
"good",
"bad"
)
) %>%
group_by(Reference) %>%
mutate(prop = Freq/sum(Freq)) %>%
ungroup() %>%
{
ggplot(.,
aes(
x = Reference,
y = Prediction,
fill = goodbad,
alpha = prop
)) +
geom_tile() +
geom_text(
aes(label = Freq),
vjust = .5,
fontface = "bold",
alpha = 1
) +
scale_fill_manual(
values=c(good="green", bad="red")
) +
scale_x_discrete(
limits=levels(.$Reference),
position="top"
) +
scale_y_discrete(
limits=rev(levels(.$Prediction))
) +
labs(
title="Confusion Matrix",
subtitle=paste0(
"For: '",
.$Prediction[1],
"' vs '",
.$Prediction[2],
"'"
)
)
}
# Return
return(plot)
}
15.2.运行预测
下一步是实际运行预测。这一步是不言自明的。该函数的参数包括测试数据和训练模型。
# Create prediction
Prediction <- get_Prediction(
tst_img,
tst_cls,
training_output[["network_model"]],
0.1
)
15.3.视图预测
创建了这个预测之后,接下来需要可视化输出。与 检查图像 部分相同,以下代码块显示了前 16 个图像,并为每个图像返回一个标签。
正如所见,该模型显然能够识别一些,但它也犯了其他错误。
# Define classes
ClassList <- c("0"="Not", "1"="Car")# Set list
lst <- list()# Loop 16 images
for (image in 1:16) {
lst[[image]] <- plt_PlotImage(
cifar$images[-partition,,,],
Prediction[["prdic"]],
ClassList,
image
)
}# View images
gridExtra::grid.arrange(grobs=lst, ncol=4)

图 10 :预测图像
15.4.检验预测
下一步是对数据进行统计测试,看看它有多准确。为此,使用了confusionMatrix()功能(来自caret包)。根据该输出,可以选择适当的度量,以便进一步优化神经网络。
因为这篇文章仅仅是关于展示这个方法,所以我们不会在这里进行更多的优化。
# Set Up
ConfusionMatrix <- Prediction %>%
mutate_at(c("truth","prdic"), ~ifelse(.==1,"car","not")) %>%
select(prdic,truth) %>%
table %>%
caret::confusionMatrix()# Print
ConfusionMatrix %>% print()
它打印:
Confusion Matrix and Statisticstruth
prdic car not
car 188 1118
not 121 1573
Accuracy : 0.587
95% CI : (0.569, 0.605)
No Information Rate : 0.897
P-Value [Acc > NIR] : 1
Kappa : 0.079
Mcnemar's Test P-Value : <0.0000000000000002
Sensitivity : 0.6084
Specificity : 0.5845
Pos Pred Value : 0.1440
Neg Pred Value : 0.9286
Prevalence : 0.1030
Detection Rate : 0.0627
Detection Prevalence : 0.4353
Balanced Accuracy : 0.5965
'Positive' Class : car
下一步是绘制混淆矩阵。理解和解释混淆矩阵的一个很好的来源可以在这里找到:混淆矩阵。在这个网站中,它还包括一个图像(也复制在下面),以获得非常好的视觉理解。

图 11 :混淆矩阵度量
当我们可视化我们的模型的混淆矩阵时,我们可以看到它成功地预测了大多数图像。然而,最大数量的坏分类是当它预测图像是一辆汽车时,而它实际上不是。
# Plot Confusion Matrix
ConfusionMatrix %>%
plt_ConfusionMatrix()

图 11 :生成混淆矩阵
另一个好的分析和绘图工具是使用 ROC 曲线(接收机工作特性)。一条 ROC 曲线是一个图表,它说明了一个二元分类器系统的诊断能力,因为它的区分阈值是变化的(见接收器操作特性)。
本质上,一个创建完美预测的模型将使曲线完美地“拥抱”该图的左上角。由于我们的模型没有做到这一点,显然进一步的训练和优化是必要的。为此,有必要继续进行 进一步实验 一节中提到的实验。
# Print ROC
Prediction %>%
ggplot() +
plotROC::geom_roc(aes(m=probs, d=truth), n.cuts=0) +
plotROC::style_roc(
theme=theme_grey,
ylab="True Positive Rate",
xlab="False Positive Rate"
) +
theme(
plot.title = element_text(hjust=0.5),
plot.subtitle = element_text(hjust=0.5)
) +
labs(
title="ROC Curve",
y="True Positive Rate",
x="False Positive Rate"
)

图 12 : ROC 曲线
16.结论
这是如何在R中构建香草神经网络的有效表示。在这里,我们展示了如何:
- 访问并检查数据
- 实例化和初始化网络
- 向前传播
- 计算成本
- 反向传播
- 更新模型
- 建立一个训练方法来循环每一件事
- 预测和评估绩效
它还展示了如何完全在R中做到这一点,而不使用任何预定义的深度学习框架。当然,有其他的包可以执行所有的步骤,并且可能以一种计算效率更高的方式。但是这些包的使用不是本文的目标。在这里,目的是消除“黑箱”现象,并展示如何从头开始创建这些深度学习框架。
如图所示,这些前馈神经网络的架构有效地使用了矩阵乘法和微分来调整网络的“权重”,并增加其预测能力。它确实需要大量的数据,也确实需要很长时间来训练和优化。此外,还有许多(许多,许多)不同的架构可供选择,所有这些都由应用该方法的数据科学家决定。
说到底,这篇文章已经证明了深度学习绝对可以在R中实现。从而破除了“为了做深度学习,你必须使用Python”的迷思。当然,深度学习在Python有很多成功的实现;比如:用 Python 从零开始构建前馈神经网络。在 Swift 中也有实现相同目标的例子(Swift 中的深度神经网络,经验教训),在 C++中也有实现相同目标的例子(从零开始用 C++实现神经网络),在 Java 中也有实现相同目标的例子(用纯 Java 实现人工神经网络(无外部依赖性))。因此,所使用的具体语言实际上是不相关的。重要的是用例、环境、业务工件以及数据科学家感到舒适的语言。
17.附言
鸣谢:本报告是在他人的帮助下编写的。致谢:
— 亚历克斯·斯克里文
出版物:本报告同时在以下网站发布:
—RPubs:RPubs/chrimaho/VanillaNeuralNetworksInR
—GitHub:GitHub/chrimaho/VanillaNeuralNetworksInR
—Medium:Medium/chrimaho/VanillaNeuralNetworksInR
变更日志:本出版物在以下日期修改:
—02/11/2020:原始出版日期。
VAR 和面板数据模型——多元预测技术的发电站

时间序列
超越单变量时间序列预测
我们来做一个思维实验。
你是一家专门从事销售预测的预测分析公司。您的客户是一家大型食品连锁店,他要求您预测未来 30 天的销售量。
现在,您将寻找哪些信息来进行预测?
当然是过去的销售数据。假设你得到了他们过去两年的销售数据。所以你的预测模型变成了这样:
未来销售额= f(过去销售额)
现在想想,你能有多自信地说,只用这一个历史数据集,你就能做出一个好的、合理的预测?
在现实世界中当然不是这样。在现实世界中,预测历史销售额只是一条信息,但你还会寻找商店位置、法定假日、任何特殊事件、广告支出或促销活动等,事实上,每一条影响销售的信息。
这些信息的每一部分实质上都成为了预测模型中的一个变量:
未来销售额= f(过去的销售额、特殊事件、假期、促销……)
这就是多元预测的力量所在。现实世界是相互联系的,一个过程的结果通常是多个输入和动作的结果。因此,难怪数据科学家会寻找与构建预测模型相关的每一条信息。
在本文中,我将讨论两个强大的多变量时间序列预测模型——向量自回归(VAR)和面板数据模型——并用两种不同的编程语言——Python 和 r——的代码片段演示它们的应用。
所以让我们开始吧。
一.向量自回归模型
a)什么是向量自回归?
在定义向量自回归之前,让我们了解一下什么是自回归。顾名思义,它是一个变量对自身的回归,对它过去的值的回归——就像我刚刚在引言中写的那样。在这种情况下,独立变量是被预测的同一个单变量数据序列的过去值。
向量自回归概括了这一单变量概念,并允许在模型中包含额外的相关变量。在这种情况下,每个变量都使用其自身的过去值(滞后值)以及外部因素的滞后值进行预测。
a)风险值模型的工作原理
在单变量预测技术中,未来值被预测为过去值的函数。所以,如果你想预测一个城市在 2021 年的人口,你需要一个简单的单变量预测方法:

但如果你想在更长的时间范围内做出预测(比如 2050 年的人口),我们知道还有许多其他因素需要考虑——出生率、死亡率、教育、收入等等。这些因素被称为外源因素或协变量。在短期内,这些外部因素的影响不是很大,所以我们可以忽略它们,但从长期来看,它们的影响是复合的,并产生了可测量的差异。
VAR 就是这样一种在建模过程中融入外生变量的多变量预测模型。在此过程中,因变量使用其自身的过去(滞后)值以及外部因素的滞后值进行预测。

c)用 Python 实现
为了演示它是如何工作的,我使用了一个由 14 个不同变量组成的美国宏观经济数据集。这是从 1959 年到 2009 年的季度数据。然而,出于演示的目的,我只使用了两个变量:国内生产总值(realgdp)和个人消费支出(realcons)。您可以按照下面代码片段中的代码访问数据集。

用于 VAR 实施的完整数据集的几行
我将使用一个 Python 环境来利用其非常丰富的statsmodels库,这是一个非常灵活的 VAR 实现选项的来源。演示的数据集也来自这个 API。
所以这个练习的目标是对未来的实际 gdp 和实际成本进行 5 个时间步的预测。


二。面板数据模型
a)什么是面板数据?
如果你之前没有听说过面板数据你并不孤单。但是我确信你以前见过这种数据,你只是不知道它有一个名字。以下是面板数据的正式定义:
一个 面板数据 是一个观察值的多维数据,随着时间的推移被反复测量。
简单地说,一个面板数据是对同一个对象或个体的重复观察。看看下面这些虚构的数据,它们可能比阅读定义更直观:

图:假想面板数据表
b)面板数据模型如何工作?
从概念的角度来看,面板数据很像多元 OLS 回归,但比正常的 OLS 更有效。使用上述数据集,假设您对以下问题感兴趣:
城市支出增加了入学人数吗?
通过运行 OLS,我们可以快速检查城市支出和学校招生之间是否存在关联,并构建一个模型:
支出= f(学校招生)
然而,数据集不是普通的数据框架;这是一个面板数据,有两个额外的列——城市名称和年份——我们也可以在模型中使用。下面是三种不同的方法来构建模型,每种方法都增加了额外的可变效果。
汇集 OLS 模式

固定特效模式

随机效果模型

c)在R中的实施
我喜欢 Python,但这是我在 r 上看到的为数不多的情况之一。它有一个强大的库叫做plm,你可以用它只用几行代码构建面板数据模型。而且非常直观。
下面我正在创建一个关于随机效果模型的演示,但是你可以通过将模型类型(第 16 行)从“随机”改为“池化”或“内化”以及一些其他的建模变化来玩代码。

图:模型输出汇总
最后的想法
本文的目的是展示两种强大的多元预测技术——向量自回归和面板数据模型——在现实应用中的价值。为什么要多元预测?因为单变量预测有其自身的局限性。认为未来完全依赖于过去是一个过于简单的假设。
建立模型的目的是为了重现和模仿现实世界。现实世界是复杂且相互关联的,因此这需要在我们的模型中得到反映。
使用蒙特卡罗模拟的投资组合风险管理
如何使用 Python 和蒙特卡罗模拟自动计算风险价值(VaR)来管理投资组合或股权和股票的金融风险

在 Unsplash 上由 Austin Distel 拍摄的照片
金融和投资组合风险管理中的 VaR?
VaR 是“风险价值”的首字母缩写,是许多公司和银行用来确定其公司内部金融风险水平的工具。风险值是为一家公司的投资计算的,或者可能是为了检查由银行财富管理部门或精品公司管理的投资组合的风险水平。
该计算可以被认为是孤立的统计测量。也可以简化为下面的语句示例-
VaR 是在一定概率水平(置信区间)下发生的最小损失或在一定概率水平下实现的最大损失。

图片来源——SP 咨询 LLP 公司
上图显示了一家公司在α % 信心下可能面临的最大损失。在个人层面上,VaR 可以帮助你预测或分析你的投资组合可能面临的最大损失——这是我们将很快分析的内容。
蒙特卡洛模拟
蒙特卡洛模型是斯坦尼斯瓦夫·乌拉姆和约翰·诺伊曼的发明,他们在第二次世界大战后开发了这个模型。该模型是以摩纳哥的一个赌博城市命名的,这是因为赌博中面临的机会和随机遭遇。
蒙特卡洛模拟是一种概率模型,它生成与经济因素(预期回报、波动性——在基金投资组合的情况下)一起使用的随机变量,以预测大范围内的结果。虽然不是最准确的,但该模型通常用于计算风险和不确定性。
我们现在将使用蒙特卡洛模拟来为我们的资产组合生成一组预测回报,这将帮助我们找出我们投资的风险值。
用 Python 计算 VaR
我们将首先通过导入所需的库和函数来设置笔记本
#Importing all required libraries
#Created by Sanket Karve
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import pandas_datareader as web
from matplotlib.ticker import FuncFormatter
!pip install PyPortfolioOpt
#Installing the Portfolio Optimzation Libraryfrom pypfopt.efficient_frontier import EfficientFrontier
from pypfopt import risk_models
from pypfopt import expected_returns
from matplotlib.ticker import FuncFormatter
为了我们项目的目的,我考虑了过去两年的‘FAANG’股票。
tickers = ['GOOGL','FB','AAPL','NFLX','AMZN']
thelen = len(tickers)
price_data = []
for ticker in range(thelen): prices = web.DataReader(tickers[ticker], start='2018-06-20', end = '2020-06-20', data_source='yahoo') price_data.append(prices[['Adj Close']])df_stocks = pd.concat(price_data, axis=1)
df_stocks.columns=tickers
df_stocks.tail()
下一步,我们将计算每项资产的投资组合权重。我已经通过使用为达到最大夏普比率而计算的资产权重做到了这一点。我已经发布了下面计算的代码片段。
#Annualized Return
mu = expected_returns.mean_historical_return(df_stocks)
#Sample Variance of Portfolio
Sigma = risk_models.sample_cov(df_stocks)#Max Sharpe Ratio - Tangent to the EF
from pypfopt import objective_functions, base_optimizer
ef = EfficientFrontier(mu, Sigma, weight_bounds=(0,1)) #weight bounds in negative allows shorting of stocks
sharpe_pfolio=ef.max_sharpe() #May use add objective to ensure minimum zero weighting to individual stocks
sharpe_pwt=ef.clean_weights()
print(sharpe_pwt)

最大夏普比率的资产权重
资产权重将用于计算预期投资组合回报。
#VaR Calculation
ticker_rx2 = []#Convert Dictionary to list of asset weights from Max Sharpe Ratio Portfoliosh_wt = list(sharpe_pwt.values())
sh_wt=np.array(sh_wt)
现在,我们将把投资组合的股票价格转换成累计回报,这也可以被认为是该项目的持有期回报(HPR)。
for a in range(thelen): ticker_rx = df_stocks[[tickers[a]]].pct_change()
ticker_rx = (ticker_rx+1).cumprod()
ticker_rx2.append(ticker_rx[[tickers[a]]])ticker_final = pd.concat(ticker_rx2,axis=1)
ticker_final

#Plot graph of Cumulative/HPR of all stocksfor i, col in enumerate(ticker_final.columns):
ticker_final[col].plot()plt.title('Cumulative Returns')
plt.xticks(rotation=80)
plt.legend(ticker_final.columns)#Saving the graph into a JPG file
plt.savefig('CR.png', bbox_inches='tight')

现在,我们将挑选出每项资产的最新 HPR,并用将收益乘以计算出的资产权重。点()功能。
#Taking Latest Values of Returnpret = []
pre1 = []
price =[]for x in range(thelen):
pret.append(ticker_final.iloc[[-1],[x]])
price.append((df_stocks.iloc[[-1],[x]]))pre1 = pd.concat(pret,axis=1)
pre1 = np.array(pre1)
price = pd.concat(price,axis=1)
varsigma = pre1.std()
ex_rtn=pre1.dot(sh_wt)print('The weighted expected portfolio return for selected time period is'+ str(ex_rtn))#ex_rtn = (ex_rtn)**0.5-(1) #Annualizing the cumulative return (will not affect outcome)price=price.dot(sh_wt) #Calculating weighted value
print(ex_rtn, varsigma,price)
计算出预期投资组合回报和波动性(预期回报的标准差)后,我们将建立并运行蒙特卡洛模拟。我用了 1440(一天中的分钟数)的时间进行了 10,000 次模拟运行。时间步长可以根据要求而改变。我使用了 95%的置信区间。
from scipy.stats import normimport mathTime=1440 #No of days(steps or trading days in this case)lt_price=[]final_res=[]for i in range(10000): #10000 runs of simulation daily_return= (np.random.normal(ex_rtn/Time,varsigma/math.sqrt(Time),Time))
plt.plot(daily_returns)plt.axhline(np.percentile(daily_returns,5), color='r', linestyle='dashed', linewidth=1)plt.axhline(np.percentile(daily_returns,95), color='g', linestyle='dashed', linewidth=1)plt.axhline(np.mean(daily_returns), color='b', linestyle='solid', linewidth=1)plt.show()

一天 1440 分钟的回报范围|红色虚线-最小损失|绿色虚线-最小收益
可视化回报的分布图为我们呈现了下面的图表
plt.hist(daily_returns,bins=15)plt.axvline(np.percentile(daily_returns,5), color='r', linestyle='dashed', linewidth=2)plt.axvline(np.percentile(daily_returns,95), color='r', linestyle='dashed', linewidth=2)plt.show()

打印上限和下限的精确值,并假设我们的投资组合价值为 1000 美元,我们将计算出应该保留以弥补最小损失的资金量的估计值。
print(np.percentile(daily_returns,5),np.percentile(daily_returns,95)) #VaR - Minimum loss of 5.7% at a 5% probability, also a gain can be higher than 15% with a 5 % probabilitypvalue = 1000 #portfolio valueprint('$Amount required to cover minimum losses for one day is ' + str(pvalue* - np.percentile(daily_returns,5)))

每天的最小损失为 1.14%,概率为 5%
得出的金额将意味着弥补你每天损失所需的金额。结果也可以解释为你的投资组合以 5%的概率面临的最小损失。
结论
上述方法显示了我们如何计算投资组合的风险价值(VaR)。复习使用现代投资组合理论(MPT) 计算一定投资金额的投资组合,将有助于巩固您对投资组合分析和优化的理解。最后,VaR 与蒙特卡洛模拟模型相结合,也可用于通过股价预测损益。这可以通过将生成的每日回报值乘以相应股票的最终价格来实现。
所有表达的观点都是我自己的。这篇文章不被视为专家投资建议。
可变大小的视频小批量
一种处理帧数不等的视频的批处理算法

亨特·哈里特在 Unsplash 上的照片
T 训练和测试高效机器学习模型最重要的一步是收集大量数据并使用这些数据有效训练它的能力。小批量有助于解决这个问题,因为它能够在每次迭代中使用一小部分数据进行训练。但是,随着大量的机器学习任务在视频数据集上执行,出现了有效批处理不等长视频的固有问题。大多数方法依赖于将视频修剪为相等的长度,以便在迭代期间可以提取相同数量的帧并发送给批处理。但在我们需要每一帧的信息来有效预测一些事情的情况下,尤其是在自动驾驶汽车和动作识别的情况下,这不是特别有帮助。这就是创建小批量视频数据集的动机,该数据集可以处理不同长度的视频。
我使用 LoadStreams 作为由Glenn Jocher(https://github.com/ultralytics/yolov3)创建的 Yolov3 的 Pytorch 模型的基础,在此基础上我对我的 LoadStreamsBatch 类进行建模。
类初始化
在 init 函数中,我接受四个参数。虽然 img_size 与原始版本相同,但其他三个参数定义如下:
- sources: 它接受一个目录路径或一个文本文件作为输入。
- 批量大小:这是所需的最小批量的大小
- subdir_search: 该选项可以切换,以确保当目录作为 sources 参数传递时,在所有子目录中搜索相关文件
我首先检查 sources 参数是一个目录还是一个文本文件。如果是一个目录,我会读取目录中的所有内容(如果 subdir_search 参数为 True ,子目录也包括在内),否则我会读取文本文件中视频的路径。视频的路径存储在列表中。维护指针 cur_pos 以跟踪列表中的当前位置。
列表以 batch_size 为最大值进行迭代,并进行额外检查以跳过错误视频或不存在的视频。这些被发送到信箱功能,以调整图像的大小,并将所有组件堆叠在一起,这与原始版本没有变化,除非所有视频都有问题/不可用。
函数定期检索帧
由线程调用的 update 函数有一个小的变化,我们额外存储了默认的图像大小,用于所有视频都被拾取进行处理,但由于长度不等,一个在另一个之前完成的情况。当我解释代码的下一部分,也就是 next 函数时,会更清楚。
迭代程序
在获得初始运行的所有帧之后,用于处理的帧可以在每个视频的迭代的不同点结束。如果该帧存在,它像往常一样被传递给信箱函数。在帧为 None 的情况下,这意味着视频已经被完全处理,我们检查列表中的所有视频是否已经被处理。如果有更多视频需要处理,指针 cur_pos 用于获取下一个可用视频的位置。
在列表中不再有视频被拾取,但某些视频仍在处理的情况下,一个空白帧被发送到其他批次组件,即它根据其他批次中的剩余帧动态地调整视频的大小。
结论
由于花费在数据收集和数据预处理上的时间量,我相信这可以通过减少花费在使视频适合模型上的时间而有所帮助,并且我们可以更专注于使模型适合数据。
我在这里附上完整的源代码。希望这有所帮助!
注入差异的思维

Riho Kroll 在 Unsplash 上拍摄的照片
要成为一名成功的数据科学家或分析从业者,我们需要练习从概率的角度看待世界
历史看起来是确定的,而且经常被这样解释。本世纪初的科技泡沫显然是一个泡沫,正如所有泡沫都会发生的那样,它最终破裂了。20 世纪 80 年代的日本房地产,2008 年的美国房地产,20000 美元的比特币,杂草股等等也是如此。在崩盘后的几年里,历史学家关注的是风险有多明显,最终的内爆有多不可避免。
成功的故事也是如此。迈克尔·乔丹作为篮球运动员的无与伦比的成功基本上是命中注定的,这要归功于他独特的运动能力和竞争力的完美结合。谷歌和亚马逊世界一流的算法和执行能力意味着万亿美元的市值是不可避免的。
我可能有点夸大其词,但是很多人看待历史的一个主要缺陷是他们通过决定论的视角来看待历史。部分原因是历史是线性的——只有一系列事件导致了今天,而这一系列事件就是历史书上所描述的。历史书不会深究如果,比如如果四年前特朗普输给了克林顿会怎样;相反,他们关注(可能过度关注)过去和现在。
决定性思维的陷阱
我经常想知道为什么概率被认为是数据科学家的必修知识。日复一日,我很少使用概率——在我的日常工作中,我更多地使用统计学、矩阵代数和机器学习。我得出的答案是:概率很重要,因为数据科学家需要学习概率思维。概率性思维意味着认识到结果的序列不是确定的——而是每一个结果本身都是因果关系(或相关性)和机会的混合产物。
下表显示了这种差异。假设我们观察到以下事件序列(只观察了一次):A,然后 B,然后 C。观察世界的确定性方法会得出结论,A 导致 B,B 导致 C。在未来,如果我们再次看到 A,我们会假设 C 就在眼前。这种想法有三个陷阱:
- 我们在不该推断的时候推断出强因果关系(特别是考虑到我们只有一个样本)。
- 我们没有意识到机遇对结果的巨大影响。
- 我们只根据结果来判断过程。
**Deterministic:** A ----> B ----> C
另一方面,一种看待事物的概率方法认为,如果 A 出现(也有可能 A 不会出现),那么它后面可能是 B 或 E(或其他)。无论发生哪种情况,B 或 E,都可能导致一系列后果。因此,即使 A -> B -> C 是我们所观察到的,这并不意味着下一次我们再次看到 A 时,我们应该期待 C。
**Probabilistic:** if A ----> B ----> C
\ \----> D
\
\--> E ----> F
\ \----> G
\ \---> H
\
\------> Other
相反,实际发生的事情,C,可能是最不可能的结果。例如,不久前赫兹(一家破产公司)的股票风靡一时。比方说,我买了一些赫兹看涨期权(对股票的杠杆化押注),几天后卖出,获得了可观的利润。同样为了完整起见,假设 Hertz 的股票上涨了 100%,我的期权将上涨 300%——因此,如果我下注 1000 美元,Hertz 翻倍,我将净赚 3000 美元。赚钱后,我的大脑大概会经历以下想法:
Buy HTZ calls ----> Make money ----> I'm a good investor
但这将是一个错误。我甚至可能再次走运,在其他一些蹩脚的破产公司身上下注赚钱;但在某个时候,小鸡会回家休息,我会失去一切。这是因为我没有诚实地思考我的赫兹赌注的可能结果范围,也就是结果锥(我也没有考虑如果我投资其他地方会发生什么)。相反,我过于关注积极的结果。更现实和全面的评估将实现以下目标:
- 赫兹是一家破产的公司。因此,如果提振股票的动物精神突然蒸发,就没有实际的商业价值来保护我的投资不成为零。
- 由于标的股票的波动性,赫兹期权非常昂贵,其每日波动幅度超过 100%(上下波动)。这意味着股票必须大幅上涨,期权才能兑现——这不是一个很可能的结果。
我们可以用概率画出一个更真实更完整的结果树(下图)。既然我们已经描绘出了可能的结果范围,我们可以看到我的赌博是多么愚蠢。我有 5%的机会扭亏为盈,95%的机会亏损(因为你失去了期权到期时你支付的全部溢价)。此外,由于期权非常昂贵(由于波动性),对我有利的巨大波动的回报只有 3:1——但基于波动的低赔率,我应该要求 20:1 的回报。考虑回报也很重要——即使回报的概率很小,如果回报的大小超过了补偿,那么期望值仍然可以是正的。
所以总而言之,不,我根本没有做好投资,我只是经历了一场幸运的赌博——这绝对是一个运气掩盖了可怕过程的案例,从长远来看,这是不可持续的。
Buy HTZ calls ---> Stock up a lot (Pr. =5%) ----> Options up
\
\-----> Stock up a bit (Pr. =15%) ---> Options worthless
\
\---> Stock no change (Pr. =20%) --> Options worthless
\
\-> Stock down (Pr. =60%) -------> Options worthless
以这种方式陈述时,听起来非常明显——如此明显,你可能会想,谁会愚蠢到这样想呢?但是很多人都这样做。我想到的最明显的例子是某个总统因股市飙升而邀功:
Becomes president ----> Makes economy great ----> Stocks go up
每当他在推特上发布股价创纪录上涨或创历史新高的消息时,就会有成千上万的人陷入确定性思维的陷阱——他们开始相信股市肯定上涨了,因为经济表现良好;多亏了总统,经济发展得很好。但他们没有认识到以下事件的替代顺序和同样可信的解释:
- 股票市场通常与经济脱节。即使在经济低迷的时候,股票有时也会因为纯粹的机会而上涨(股票价格经常以违背理性和解释的方式波动)。因此,总统可能只是运气好(尽管他对经济造成了损害,但股票却不太可能上涨),他正试图为这种运气邀功。这里有一个更现实的结果树(看起来很明显,我知道),它也包括了可能发生的情况:
Becomes president ----> Hurts economy ----> Stocks go up anyway
with Trade War
and pandemic
response
\-------> Stocks go sideways
\
\-----> Stocks go down
- 股票可能会上涨,因为自 2008 年崩盘以来,前所未有的大量流动性被注入金融系统,,这与现任总统无关(与央行政策关系更大)。此外,重要的是要认识到,即使美联储的印钞机全速运转,股价上涨也不是唯一的结果。这可能是最有可能的结果,但也有其他可能的和不太有利的结果。
Fed prints money ----> Stocks go up
\
\---> Stocks go sideways
\
\-> Stocks go downPresident happens to be elected at the same time (unrelated)
正确的思考方式
从分析角度思考未来的正确方式是问:
如果我把这个游戏玩很多次,可能会有什么样的结果?

把未来想象成一个圆锥形的结果
我们应该总是从分布的角度来思考,把未来想象成一个以我们行动的预期结果为中心的潜在结果的圆锥(或任何最合理地接近分布的形状)。此外,我们还需要考虑如果不采取行动的后果。这就是像 A/B 测试这样的受控实验背后的原理——一个实验有一个控制组和一个实验组,适当地隔离出要测试的因素(或行为)。
受控实验类似于模拟,允许我们反复观察如果我们采取行动(上图中的绿色圆锥)和如果我们不采取行动(红色圆锥)会发生什么。如果我们实验的样本量足够大,那么我们就可以对可能的结果范围有一个很好的认识,这使我们能够正确地评估所考虑的行动的预期效果(它是否使我们朝着更理想的平均结果前进?)和围绕行动效果的不确定性()它是否扩大了圆锥并引入了过度的风险?)。
不幸的是,在现实世界中,我们经常没有足够的时间或数据来正确地做这件事。例如,如果美联储没有在 2008 年后向经济注入这么多资金,我们永远不会知道会发生什么。我们只能观察到发生的一组事件——所以我们不仅错过了红色圆锥(没有美联储印钞的结果),我们甚至没有观察到绿色圆锥的变化(我们只看到一条绿线)。这就是为什么学者和专家可以争论到面红耳赤,量化宽松和大规模财政刺激是否真正有效,但事实是没有人真正知道(尽管大萧条提供了有用的教训,但它不是 2008 年的可比对照案例)。

事实上,我们只看到一系列事件
结论
如果听起来很累,那是因为确实很累。想得太多太累了!但我们做得越多,它就越成为习惯或根深蒂固的肌肉记忆。它提高了我们的分析能力——最重要的是,它提高了我们独立于过程产生的结果评估过程的能力。
良好的决策(无论是在个人、团体还是公司层面)需要一个强大的、可重复的分析过程。但是每一个决定,无论多么精明,仍然是一个赌注——即使是最有利的赌注也仍然受到偶然事件的影响。通过正确认识机会的作用,我们防止结果扭曲我们的过程(或模型)。通过磨练我们现实地预测采取行动(或不采取行动)的结果范围(以及概率)的能力,我们可以做到即使在不确定的世界中,胜算也是对我们有利的。
方差:样本与总体

统计 101
ML 中 EDA 的区别以及为什么使用一个而不是另一个

克里斯·贾维斯在 Unsplash 上的照片
在统计学和机器学习中,当我们谈论人口时,我们指的是随机变量的可能值的整个宇宙。如果你知道总体总是可行的,计算平均值和方差如下:

其中 n 是总体的基数(元素的数量)。
大多数时候,我们不能使用整个人口,因为它太复杂了,或者根本不可行。例如,考虑一个问题,当你想分析森林中橡树的高度时。当然,您可以测量森林中的每一棵树,从而收集整个森林的统计数据,但这可能非常昂贵,并且需要很长时间。相反,你可以获取一个样本,比如说,20 棵树,并尝试将样本统计数据和种群统计数据联系起来。因此,对于 N 个样本,我们有:

你现在可以问了:为什么 N-1 而不是 N ?要回答这个问题,我们需要做一些计算。首先,我们计算 s 2 的期望值:

然后,用一点代数知识:

现在,记住:

我们有:

这是什么意思? 使用 N-1 的公式得出样本方差,平均起来等于未知总体方差。 所以,同样用很少的样本,我们就可以得到一个实际但未知的总体分布参数的合理估计。
如果我们用 N 而不是 N-1 来计算会怎么样?让我们看看:

因此,当我们使用 N 而不是 N-1,时,我们会有一个称为统计偏差的误差,这意味着样本方差(估计量)与真实总体参数(在本例中为方差)存在系统性差异。
校正因子 N/(N-1) 被称为 贝塞尔因子 并允许获得无偏方差 s 的平均值作为有偏方差的函数:

如何在机器学习问题上应用它?当我们试图对一个 ML 问题建模时,我们首先查看数据(在监督和非监督学习中),搜索模式,统计参数,维度减少,特征选择等。这被称为探索性数据分析或 EDA 。无论是数值特征还是分类特征,我们首先要看观察值的分布。所以,最初,我们必须估计两个参数:平均值和方差。如果 N ,即观察值的数量很小,我们必须应用贝塞尔因子来更好地估计真实方差。如果 N 很大(我们会看到有多大),我们可以省略这一项,因为 N/(N-1)大约等于 1。
所以,在一个问题上:
- 很少观察到
- 要素的值非常少(因为我们有一个有偏差的数据集)
我们必须应用校正因子。
在任何情况下,我们都不能对结果有信心,因为我们使用的是样本而不是总体。我们所能做的最好的事情是估计一个范围的值,在这个范围内实际方差落在(总体方差的置信区间)内。
让我们看一个例子。想象一片有 10000 棵橡树的森林:这是全部人口。我们想估计高度的分布。假设我们不知道身高呈正态分布,平均值为 10m,标准差(方差的平方根)为 2m。这些是整个人口的统计参数。我们试图通过随机抽取的 20 棵橡树样本来估算这些值。我们重复这个实验 100 次。以下是 Python 中的代码:
import numpy as np
import pandas as pd
from random import sample,choice
import matplotlib.pyplot as plt
import seaborn as sns
import scipynp.random.seed(1234)
mu = 10
sigma = 2
pop = np.random.normal(mu, sigma, 10000)
count, bins, ignored = plt.hist(pop, 100, density=True, color = 'lightgreen')
sns.set_style('whitegrid')
tests = 100
sam = []
mean =[]
std_b = []
std_u = []
fig, axs = plt.subplots(ncols=2)
sns.kdeplot(pop, bw=0.3, ax=axs[0])
for i in range(tests):
sam_20 = np.random.choice(pop, 20)
sns.kdeplot(sam_20, bw=0.3, ax=axs[1])
sam.append(sam_20)
mean.append(np.mean(sam_20))
std_b.append(np.std(sam_20))
std_u.append(np.std(sam_20, ddof=1))frame = {'mean':mean, 'std_b': std_b, 'std_u': std_u}
table = pd.DataFrame(frame)
这样,我们为 100 个实验中的每一个实验获得样本均值"均值"标准差(有偏)" std_b" 和无偏( ddof=1 与 (N-1) 相同,而不是 N )标准差" std_u" 。从图形上看,我们得到:

假设 20 个样本是 10000 个总体项目的一个很小的子集,每次我们运行测试,我们都会得到不同的分布。无论如何,平均起来,我们得到了真实均值和标准差的合理估计。我们可以在 Python 中使用以下命令看到这一点:
table.describe()

如您所见,平均而言,无偏样本标准差比有偏样本标准差更接近总体参数的值。
通过置信度分析来衡量这些评估的质量,我们将在下一次讨论。
结论
在本文中,我们已经看到了样本方差受统计偏差的影响,这是由于与整个总体的基数相比,很少观察到的数据发生了失真。我们学习了一种通过贝塞尔因子来减少这种误差的方法,并给出了一个例子。
可变自动编码器
VAE 和在哪里找到他们

因此,在过去两年的机器学习面试中,我被要求解释变分自动编码器(VAE)大约五次。对于某个公司来说,这个问题实际上是在同一天由两个不同的面试官重复提出的。
为什么 VAE 是如此受欢迎的面试问题?嗯,它们确实涵盖了概率论和信息论中的一些有趣的主题,而且它们被广泛使用,被认为是相对普通的知识。这些模型有一个独特的规格和一个长期的衍生。这意味着他们可以花 30 分钟走一遍,但仍然很容易让面试官验证。此外,人们通常只了解 VAE 背后的直觉,所以这是一个很好的面试问题(在我看来)。
显然,我真的不喜欢把 VAE 作为一个面试问题,但我也认为他们的申请由于其他原因被夸大了。在以后的文章中,我将推导出我所看到的这类“优化游戏”的真正的潜在机制。现在,让我们彻底了解一下 VAE,作为 VAE 召回的一站式商店。
自动编码器
首先,自动编码器是神经网络的一种形式,其使用一些损失函数 L(x,r(x))专门训练重构函数 r = g(f (x)),以便产生更多信息和/或更低维度的数据重构。这个重构函数是编码函数 z = f(x,θ)和解码函数 x = g(z,θ)的组合,编码函数将数据映射到学习的潜在空间,解码函数 x = g(z,θ)充当生成函数,在潜在空间中为 z 产生 x。这种重建是通过无监督学习创建的,并且通常独立于任何标记或预测任务。
虽然神经网络最常用于生成高维参数化函数以解决判别任务,但自动编码器被训练来重新生成模型被训练的数据。如果我们对我们的损失函数进行正则化,并对我们的网络进行战略性结构调整,我们可以改变自动编码器的行为。
通过引入正则化子ω,我们可以影响我们的自动编码器学习的空间类型。一种这样的增强是稀疏自动编码器(SAE ),它学习稀疏潜在表示。考虑正则化损失函数项 L(x,r(x))+ω(z),其中ω(z)=λ∑| z _ I |惩罚我们的潜在表示的分量的幅度。这种损失使自动编码器的编码函数 z = f (x,θ)发生偏移,以根据λ的标度为潜在表示的尽可能多的分量产生零或小的值。
作为正则化损失函数的一种替代方法,我们也可以考虑对输入到自动编码器的数据进行正则化。去噪自动编码器(DAE)旨在通过最小化准则 L_DAE = E[L(x,r(N(x))]]来生成简化的重建 r(x),其中对于某些高斯误差ε = N (0,σ),N(x) = x + ε。输入到网络中的数据本质上是被破坏的,模型学习过滤掉噪声,以便再现源数据。[1]
这些模型使用什么类型的信息来产生这些潜在的表征?自动编码器学习训练数据的概率密度 p(x)的流形结构
∂对数 p(x)/∂x。这种流形结构有助于引导数据潜在空间的构建。形式上,对于去噪自动编码器,可以示出我们产生以下最优重构函数 r*(x) = x + σ^2 ∂对数 p(x)/∂x + o(σ),其行为渐近于高斯噪声的方差。渐近行为(r *(x)-x)/σ→∂对数 p(x)/∂x 为
σ → 0 允许我们通过 p(x)上的诱导流形确定 x 的信息表示。[2]
可变自动编码器
变分自动编码器(VAE)产生了我们的潜在代表符合给定分布的愿望,以及变分推理过程的简单近似使计算易处理的观察。我们将回顾定义潜在空间分布的步骤,以及使用变分推论来训练我们的模型的步骤。
VAE 的嵌入机制是一种学习的编码分布 z = q(x ),可以更好地对数据集的潜在结构进行排序。根据这种分布,可以构建概括的推理模型,以便重现数据。通过概括,我们意味着当通过给定的生成函数 g(z)馈送时,从在潜在空间 Z 中的样本 Z 上学习的分布 P(Z)中抽取的样本更有可能产生我们的原始数据集 X 的样本。母函数 g(z)可以被视为参数化函数 g(z,θ)或数据 P(X | z;θ).形式上,当我们构建我们的潜在空间时,我们试图最大化观察每个 X 的概率,用于由下式定义的生成过程:

为了评估这个函数,我们需要建立假设的先验分布 P(Z ),以及训练的分布 P(X | Z;θ).先验分布通常假设为正态分布,使得 P(Z) = N(0,I)。
对于 VAE,解码网络用于生成 P(X|z,θ)的样本。我们假设的输出服从(通常为多元正态)分布,均值为 g(z,θ),方差为σ2∫I,如下所示

为了评估这些样本有多精确,我们需要建立一些简化的方法来评估变分推断积分。显然,在 g 的每一个优化步骤中,在整个区域内对样本进行充分的积分计算是非常昂贵的。
为了简化对 P(X|z)的评估,我们只关注(近似地)对那些可能产生 X 的 z 进行采样,服从生成过程 g。我们选择用来表示该采样的分布是 Q(z|X),由我们的编码函数 f 实现。然后我们用下式近似 P(X|z)

现在,我们需要把期望 E[P(X|z)]和 P(X)联系起来。我们可以通过对潜在空间上任意分布φ(z)的 KL 散度和交叉熵公式进行一些代数操作来做到这一点。
从 KL(φ(z)| | P(z | X)),我们可以导出(在一些对数、互信息和交叉熵替换之后)

如果我们设置φ(z)= Q(z | X),我们可以看到 log P(X)由我们的编码器 f∞Q(z | X)、我们的解码器 g∞P(X | z)和我们假设的先验分布 P(z)决定。变分自动编码器的主要工程发展是可以有效地优化上述等式的右边。如果 Q(z|X)和 P(z)都假定为高斯分布,KL(Q(z|X)||P(z))有一个封闭形式的解。我们可以引入函数μ(X,θ)和σ(X,θ),作为编码神经网络层,这又允许我们指定 Q(z|X) = N(μ(X),σ(X))。VAE 损耗方程的闭合形式的 KL 发散项 KL(Q(z|X)||P(z))是[3]

我们如何强制我们的编码分布 Q(z|X)遵循正态分布 N(μ(X),σ(X))的约束?在训练期间从
Q(z|X) = μ(X,θ)+εσ(X,θ)采样,噪声项为ε ~N(0,1),允许我们强制我们的样本表现为 Q 上的期望正态分布。这被称为重采样技巧。在评估期间,我们只需要设置ε=0,我们的输出就是确定性的。
结尾部分
现在我们有了 VAE 损失函数的推导,以及 VAE 建筑核心力学的概率解释的基本内容。
在实践中,特别是对于更复杂的层,实际上很难证明由变分体系结构产生的表示实际上遵循其公式中使用的先验分布。这部分是因为在进行任何类型的降维时都会丢失信息。然而,我也认为大多数类似 VAE 的模型在学术数据集之外不太适合他们的先验知识。
在任何情况下,我都会在以后的文章中,试着回顾一下我对变分步骤的更多理论上的抱怨。
参考
[1]伊恩·古德费勒,约舒阿·本吉奥和亚伦·库维尔。深度学习。麻省理工学院出版社。2016.
[2]纪尧姆·阿兰和约舒阿·本吉奥。正则化自动编码器从数据生成分布中学习到什么。2014 年
[3]卡尔·多尔什。变型自动编码器教程。 2016 年。
使用 Keras 作为生成模型的可变自动编码器

使用 Keras 的可变自动编码器和图像生成|图片由“我的镜头生活”来自 Unsplash | 图片来源
实践教程
引入变分自动编码器和使用 Keras 生成图像
概观
本文旨在让读者对变型自动编码器有一些基本的了解,并解释它们在机器学习和人工智能方面与普通自动编码器的不同之处。不同于普通的自动编码器(如稀疏自动编码器、去噪自动编码器。etc),变分自动编码器(VAEs)是类似 GANs ( 生成对抗网络)的生成模型。这篇文章主要集中在变化的自动编码器上,我将很快在我即将发布的帖子中写关于生成敌对网络的内容。
在本教程中,我们将讨论如何从头开始用 Keras(TensorFlow,Python)训练一个变分自动编码器(VAE)。我们将以一个简单 VAE 的生成能力的演示来结束我们的研究。
本教程的其余内容可分为以下几类-
- 背景:变分自动编码器
- 在喀拉斯建造 VAE
- 在 MNIST 数据集上训练 VAE
- 结果
- 图像生成能力
- 汇总
- 延伸阅读和资源
1.变分自动编码器
背景
自动编码器基本上是一种神经网络,它将高维数据点作为输入,将其转换为低维特征向量(即潜在向量),并且稍后仅利用潜在向量表示来重构原始输入样本,而不会丢失有价值的信息。任何给定的自动编码器都由以下两部分组成——编码器和解码器。模型的编码器部分获取输入数据样本,并将其压缩成潜在向量。而解码器部分负责从学习的(由编码器在训练期间学习的)潜在表示中重建原始输入样本。要了解更多基础知识,请查看我关于 Keras 中的自动编码器和深度学习的文章。
让我们继续考虑,直到现在我们都在同一页上。
普通自动编码器的一个问题是它们独立地编码每个输入样本。这意味着属于同一类的样本(或属于同一分布的样本)可能学习到非常不同的(潜在空间中的远距离编码)潜在嵌入。理想情况下,同类的潜在特征应该有些相似(或者潜在空间上更接近)。这是因为我们没有明确强制神经网络学习输入数据集的分布。由于这个问题,我们的网络可能不太擅长重建相关的看不见的数据样本(或不太可归纳)。
在过去关于 Keras 中的自动编码器和深度学习的教程中,我们训练了一个普通的自动编码器,并学习了 MNIST 手写数字图像的潜在特征。当我们在具有相应标签的潜在空间中绘制这些嵌入时,我们发现相同类别的学习嵌入有时出现相当随机,并且在不同类别的嵌入聚类之间没有清晰可见的边界。下图显示了分布情况-

使用 Keras 的可变自动编码器和图像生成|作者图片| 图片来源
可变自动编码器
变分自动编码器本质上略有不同。它不是直接从输入样本中学习潜在特征,而是实际学习潜在特征的分布。假设输入数据的潜在特征遵循标准正态分布。这意味着学习到的潜在向量应该是以零为中心的,并且它们可以用两个统计量来表示——均值和方差(因为标准正态分布可以仅用这两个统计量来表示)。
因此,变分自动编码器(vae)计算每个样本的潜在向量的均值和方差(而不是直接学习潜在特征),并迫使它们遵循标准的正态分布。因此,网络的瓶颈部分用于学习每个样本的均值和方差,我们将定义两个不同的全连接(FC)层来计算这两个值。VAEs 确保潜在空间中彼此非常接近的点代表非常相似的数据样本(相似的数据类别)。我们将在本教程中证明这一事实。
在进入实现细节之前,让我们首先了解一下 KL-divergence,它将被用作我们模型中的两个优化度量之一。
库尔贝克-莱布勒(KL)散度
在上一节中,我们讨论了对输入数据集的潜在要素实施标准正态分布。这可以使用 KL-散度统计来实现。 KL-divergence 是两个概率分布之间差异的统计度量。因此,我们将利用 KL-散度值作为目标函数(连同重建损失),以确保学习分布非常类似于真实分布,我们已经假设真实分布是标准正态分布。
在这种情况下,最终目标可以写成-
*Objective = Reconstruction Loss + KL-Loss*
这里,重建损失项将鼓励模型学习正确重建原始图像(如果不完全相同,则为同一类别的图像)所需的重要潜在特征。而 KL 散度损失项将确保学习的分布类似于真实分布(标准正态分布)。这进一步意味着分布以零为中心,并且在空间中分布均匀。我们将在教程的后半部分证明这一点。
2.在喀拉斯建造 VAE
最后一节解释了机器学习(ML)和人工智能(AI)中可变自动编码器(VAEs)背后的基本思想。在这一节中,我们将使用 Python 中的 Keras 构建一个卷积变分自动编码器。该网络将在 Keras 数据集中可用的 MNIST 手写数字数据集上进行训练。
为逐步理解和简化起见,本节可分为以下几个部分
- 数据准备
- 建筑编码器
- 潜在分布和采样
- 建筑解码器
- 建筑 VAE
- 损失
数据准备
在本节中,我们将下载 MNIST 手写数字数据集并将其加载到我们的 Python 笔记本中,以开始准备数据。
这是预先加载的依赖项-
**import** numpy as np
**import** matplotlib.pyplot as plt
**import** pandas as pd
**import** seaborn as sns
**import** warningswarnings.filterwarnings('ignore')
**%**matplotlib inline
以下 python 代码可用于下载 MNIST 手写数字数据集。下面还显示了一些示例图像-
**from** tensorflow.keras.datasets **import** mnist(trainX, trainy), (testX, testy) **=** mnist.load_data()print('Training data shapes: X=%s, y=%s' **%** (trainX.shape, trainy.shape))print('Testing data shapes: X=%s, y=%s' **%** (testX.shape, testy.shape))**for** j **in** range(5):
i **=** np.random.randint(0, 10000)
plt.subplot(550 **+** 1 **+** j)
plt.imshow(trainX[i], cmap**=**'gray')
plt.title(trainy[i])
plt.show()
数据集已经分为定型集和测试集。训练数据集具有分辨率为 28*28 的 60K 手写数字图像。尽管测试数据集由具有相似尺寸的 10K 手写数字图像组成

MNIST 数据集|使用 Keras 的可变自动编码器和图像生成
数据集中的每个图像都是一个 2D 矩阵,表示从 0 到 255 的像素强度。我们将首先归一化像素值(使它们在 0 和 1 之间),然后为图像通道添加一个额外的维度(由 Keras 的 Conv2D 层支持)。以下是 python 中的预处理代码-
train_data **=** trainX.astype('float32')**/**255
test_data **=** testX.astype('float32')**/**255train_data **=** np.reshape(train_data, (60000, 28, 28, 1))
test_data **=** np.reshape(test_data, (10000, 28, 28, 1))print (train_data.shape, test_data.shape)Out[1]: (60000, 28, 28, 1) (10000, 28, 28, 1)
建筑编码器
在这一节中,我们将定义 VAE 模型的编码器部分。当输入数据类型是图像时,自动编码器的编码器部分通常包括多个重复的卷积层,然后是池层。变型自动编码器的编码器部分也非常相似,只是瓶颈部分与上面讨论的略有不同。
这里是编码器部分的 python 实现,带有 Keras-
**import** tensorflowinput_data **=** tensorflow.keras.layers.Input(shape**=**(28, 28, 1))encoder **=** tensorflow.keras.layers.Conv2D(64, (5,5), activation**=**'relu')(input_data)encoder **=** tensorflow.keras.layers.MaxPooling2D((2,2))(encoder)encoder **=** tensorflow.keras.layers.Conv2D(64, (3,3), activation**=**'relu')(encoder)encoder **=** tensorflow.keras.layers.MaxPooling2D((2,2))(encoder)encoder **=** tensorflow.keras.layers.Conv2D(32, (3,3), activation**=**'relu')(encoder)encoder **=** tensorflow.keras.layers.MaxPooling2D((2,2))(encoder)encoder **=** tensorflow.keras.layers.Flatten()(encoder)encoder **=** tensorflow.keras.layers.Dense(16)(encoder)
上面的片段压缩了图像输入,并将其降低到 16 值特征向量,但这些不是最终的潜在特征。下一节将通过添加潜在特征计算逻辑来完成编码器部分。
潜在分布和抽样
该部分负责从上一部分中提取复杂的特征,并计算潜在特征的平均值和对数方差(因为我们已经假设潜在特征遵循标准正态分布,并且该分布可以用平均值和方差统计值来表示)。两个独立的全连接(FC 层)层用于计算给定数据集的输入样本的平均值和对数方差。
标准正态分布(SND)的这些属性(平均值和对数方差)随后用于估计相应输入数据点的潜在编码。下面定义的函数 sample_latent_features 取这两个统计值并返回一个潜在编码向量。该潜在编码被传递给解码器作为图像重建目的的输入。
def sample_latent_features(distribution):
distribution_mean, distribution_variance = distribution
batch_size = tensorflow.shape(distribution_variance)[0]
random = tensorflow.keras.backend.random_normal(shape=(batch_size, tensorflow.shape(distribution_variance)[1]))
return distribution_mean + tensorflow.exp(0.5 * distribution_variance) * random
distribution_mean = tensorflow.keras.layers.Dense(2, name='mean')(encoder)distribution_variance = tensorflow.keras.layers.Dense(2, name='log_variance')(encoder)latent_encoding = tensorflow.keras.layers.Lambda(sample_latent_features)([distribution_mean, distribution_variance])
这些潜在特征(从学习的分布计算)实际上完成了模型的编码器部分。现在编码器模型可以定义如下
encoder_model **=** tensorflow.keras.Model(input_data, latent_encoding)encoder_model.summary()

编码器非常简单,只有大约 57K 个可训练参数。
建筑解码器
该模型的编码器部分将图像作为输入,并将该图像的潜在编码向量作为输出,该输出是从输入数据集的学习分布中采样的。解码器的工作是将该嵌入向量作为输入,并重新创建原始图像(或与原始图像属于相似类别的图像)。由于潜在向量是特征的非常压缩的表示,解码器部分由多对去卷积层和上采样层组成。去卷积层基本上与卷积层相反。上采样层用于恢复图像的原始分辨率。通过这种方式,它可以重建原始尺寸的图像。
下面是用 TensorFlow 的 Keras API 实现的解码器部分的 python 实现
decoder_input = tensorflow.keras.layers.Input(shape=(2))
decoder = tensorflow.keras.layers.Dense(64)(decoder_input)
decoder = tensorflow.keras.layers.Reshape((1, 1, 64))(decoder)
decoder = tensorflow.keras.layers.Conv2DTranspose(64, (3,3), activation='relu')(decoder)
decoder = tensorflow.keras.layers.Conv2DTranspose(64, (3,3), activation='relu')(decoder)
decoder = tensorflow.keras.layers.UpSampling2D((2,2))(decoder)
decoder = tensorflow.keras.layers.Conv2DTranspose(64, (3,3), activation='relu')(decoder)
decoder = tensorflow.keras.layers.UpSampling2D((2,2))(decoder)
decoder_output = tensorflow.keras.layers.Conv2DTranspose(1, (5,5), activation='relu')(decoder)
解码器模型对象可以定义如下
decoder_model **=** tensorflow.keras.Model(decoder_input, decoder_output)decoder_model.summary()

解码器同样简单,具有 112K 可训练参数。
构建 VAE(可变自动编码器)
最后,可以通过组合编码器和解码器部分来定义变分自动编码器(VAE)。下面是你如何通过在编码器后面粘贴解码器来创建 VAE 模型对象。
encoded **=** encoder_model(input_data)decoded **=** decoder_model(encoded)autoencoder **=** tensorflow.keras.models.Model(input_data, decoded)autoencoder.summary()

整个设置非常简单,只有 170,000 个可训练模型参数。写目标(或优化函数)函数的时间。
失败
如前所述,变分自动编码器(VAE)的最终目标(或损失)函数是数据重建损失和 KL 损失的组合。在本节中,我们将通过结合这两个统计数据来定义我们的自定义损失。
get_loss 函数的以下实现返回一个 total_loss 函数,它是重建损失和 KL-loss 的组合,定义如下

最后,让我们编译模型,为培训做好准备-
autoencoder.compile(loss=get_loss(distribution_mean, distribution_variance), optimizer='adam')
3.训练 VAE(可变自动编码器)
就像普通的自动编码器一样,我们将通过输入和输出完全相同的图像来训练它。该模型被训练 20 个时期,批次大小为 64。
这是训练总结-
autoencoder.fit(train_data, train_data, epochs=20, batch_size=64, validation_data=(test_data, test_data))

我希望它可以被训练得更好一点,但这是验证损失没有太大变化的地方,我继续进行。
4.结果
在本节中,我们将在测试图像上看到我们的模型的重建能力。以下 python 脚本将从测试数据集中选取 9 幅图像,我们将为它们绘制相应的重建图像。
offset**=**400
print ("Real Test Images")# Real Images**for** i **in** range(9):
plt.subplot(330 **+** 1 **+** i)
plt.imshow(test_data[i**+**offset,:,:, **-**1], cmap**=**'gray')
plt.show()# Reconstructed Imagesprint ("Reconstructed Images with Variational Autoencoder")**for** i **in** range(9):
plt.subplot(330 **+** 1 **+** i)
output **=** autoencoder.predict(np.array([test_data[i**+**offset]]))
op_image **=** np.reshape(output[0]*****255, (28, 28))
plt.imshow(op_image, cmap**=**'gray')
plt.show()
这是输出-

结果|使用 Keras 的可变自动编码器和图像生成
上述结果证实了该模型能够以相当高的效率重建数字图像。然而,这里要注意的一件重要事情是,一些重建图像在外观上与原始图像非常不同,而类别(或数字)总是相同的。发生这种情况是因为重建不仅仅依赖于输入图像,它是已经学习的分布。并且这种学习到的分布是模型输出中引入变化的原因。这很有趣,不是吗!
这里要注意的第二件事是输出图像有点模糊。这是变化的自动编码器的常见情况,由于潜在向量(瓶颈)非常小,并且如前所述,存在学习潜在特征的单独过程,它们经常产生有噪声的(或质量差的)输出。变分自动编码器(vae)实际上不是为了重建图像而设计的,真正的目的是学习分布(这给了他们生成假数据的超能力,我们将在后面的帖子中看到)。
潜在特征聚类
如前所述,变分自动编码器(vae)学习潜在特征的潜在分布,这基本上意味着属于同一类的样本的潜在编码在潜在空间中不应彼此相距太远。其次,总体分布应该是标准正态的,应该是以零为中心。
让我们为所有的测试图像生成潜在嵌入,并绘制它们(相同的颜色代表属于相同类别的数字,取自地面真实标签)。这是 python 代码-
x = []
y = []
z = []for i in range(10000):
z.append(testy[i])
op = encoder_model.predict(np.array([test_data[i]]))
x.append(op[0][0])
y.append(op[0][1])df = pd.DataFrame()
df['x'] = x
df['y'] = y
df['z'] = ["digit-"+str(k) for k in z]plt.figure(figsize=(8, 6))
sns.scatterplot(x='x', y='y', hue='z', data=df)
plt.show()

基于 Keras 的变分自动编码器和图像生成
上图显示分布以零为中心。相同类别数字的嵌入在潜在空间中更接近。数字分离边界也可以很容易地画出来。这正是我们想要从可变自动编码器中实现的。让我们跳到最后一部分,测试我们模型的生成能力。
5.伪图像生成
可变自动编码器可以用作生成模型。前面的部分显示了输入数据的潜在编码遵循标准的正态分布,并且对于不同类别的数字存在明显的界限。
请想一想——如果我们已经知道,空间的哪一部分用于哪个类,我们甚至不需要输入图像来重建图像。这意味着我们实际上可以通过从空间(潜在分布空间)传递随机点来生成与训练数据集具有相似特征的数字图像。以这种方式,变分自动编码器可以用作生成模型,以便生成假数据。
正如我们所看到的,潜在编码的范围在[-3 到 3 之间,在 x 轴上也是-3 到 3]。让我们用只属于这个范围的随机潜在编码生成一串数字。
generator_model **=** decoder_modelx_values **=** np.linspace(**-**3, 3, 30)
y_values **=** np.linspace(**-**3, 3, 30)figure **=** np.zeros((28 ***** 30, 28 ***** 30))**for** ix, x **in** enumerate(x_values):
**for** iy, y **in** enumerate(y_values):
latent_point **=** np.array([[x, y]])
generated_image **=** generator_model.predict(latent_point)[0]
figure[ix*****28:(ix**+**1)*****28, iy*****28:(iy**+**1)*****28,] **=** generated_image[:,:,**-**1]plt.figure(figsize**=**(15, 15))
plt.imshow(figure, cmap**=**'gray', extent**=**[3,**-**3,3,**-**3])
plt.show()

生成的数据|使用 Keras 的可变自动编码器和图像生成
你可以在上面的图像矩阵中找到所有的数字(从 0 到 9 ),因为我们试图从潜在空间的所有部分生成图像。生成变化的笔迹的能力是不是太棒了!
6.摘要
本教程解释了深度学习和人工智能中的可变自动编码器。通过一个基本的介绍,展示了如何用 python 中的 Keras 和 TensorFlow 实现 VAE。在 MNIST 手写数字数据集上进一步训练该模型,并给出重建结果。
我们已经看到,潜在的编码遵循标准的正态分布(全部归功于 KL-divergence ),以及如何将模型的训练解码器部分用作生成模型。我们已经通过仅使用模型的解码器部分生成假数字来证明了这一说法。
如果你有兴趣阅读我关于去噪自动编码器的文章
Github 代码链接:https://github.com/kartikgill/Autoencoders
原载于 滴艾 。
感谢阅读!希望这对你有帮助。请在下面评论,让我知道你的反馈。下一篇文章再见。
阅读下一篇>>>
参考
相关研究论文
用于假人的可变自动编码器(VAEs)——循序渐进教程
DIY 实践指南与实践代码的建设和培训与 Keras 的名人脸上的 VAEs
本文介绍了您需要从生成模型中获得的一切。我们提供了如何在大型图像数据集上训练条件值并使用它们生成新的标记图像的分步指南。

Pladicon 在 Pixabay 拍摄的照片
动机
既然已经有这么多数据,为什么我们还需要生成新的数据?据 IDC 称,目前有超过 18 兆字节的数据。
大多数机器学习任务都需要标记数据。获取高质量、有标签的数据是困难的。如果我们自己生成这些数据,我们就可以随心所欲地利用它们。新的数据可以给我们想法和选择。
如何生成看不见的图像?
在这篇文章中,你将学习什么是变分自动编码器,以及如何创建自己的变分自动编码器来生成新的和看不见的图像。我们不用数学来解释潜在的概念和直觉。

使用我们的 VAE 码(自创)的图像及其重建的例子
数据
我们将使用知名名人数据集的子集来帮助我们建立面部生成模型。数据集可以按照名人脸网站上的描述下载。它提供了一个大规模的人脸属性数据集,包含超过 20 万张名人图片,每张图片都有 40 个属性注释。
- 10177 个身份,
- 202,599 个面部图像,
- 5 个地标位置,以及
- 每个图像 40 个二进制属性注释。

下面我们从名人数据集中随机挑选一些面孔,并显示他们的元数据(属性)。图像高度为 218 像素,宽度为 178 像素,有 3 个颜色通道。

什么是自动编码器(AE)?
通过观察数以千计的名人面孔,神经网络可以学习生成不存在的人的面孔。
神经网络是我们可以用来获得函数近似值的许多可能方法之一。他们受欢迎的一个原因是他们学习表达的能力。假设我们为网络提供了正确的标签,网络可以学习在将图像分类为狗或猫时重要的特定表示。这是监督学习。
在某些情况下,我们没有这些标签。然而,我们可以训练两个网络,一个学习表示,另一个通过最小化重建损失函数从表示中重建。这是一个自动编码器。它之所以有这个名字,是因为它会自动找到编码输入的最佳方式,以便解码后的版本尽可能接近输入。
自动编码器由一对两个相连的神经网络组成:编码器模型和解码器模型。它的目标是找到一种方法将名人的脸编码成一种压缩的形式(潜在空间),以这种方式重建的版本尽可能接近输入。

自动编码器的工作组件(自行创建)
编码器模型将输入 x 转换为小型密集表示 z ,类似于卷积神经网络通过使用滤波器来学习表示的工作方式。
解码器模型可以被视为能够生成特定特征x’的生成模型。
编码器和解码器通常作为一个整体来训练。损失函数惩罚网络创建不同于输入面的输出面。
因此,编码器学会在有限的潜在空间中保存尽可能多的相关信息,并巧妙地丢弃不相关的部分,例如噪声。
解码器学习获取压缩的潜在信息,并将其重建为完整的名人脸。
自动编码器也可以用于维度减少和图像去噪,但也可以成功用于无监督机器翻译。
什么是变分自动编码器(VAE)?
通常,编码器产生的潜在空间 z 人口稀少,这意味着很难预测该空间中值的分布。在 2D 制图表达中,值是分散的,空间似乎得到了很好的利用。
这对于压缩系统来说是一个非常好的特性。然而,对于生成新的名人图像,这种稀疏性是一个问题,因为找到解码器将知道如何产生有效图像的潜在值几乎是不可能的。
此外,如果空间在簇之间有间隙,并且解码器从那里接收到变化,它将缺乏生成有用的东西的知识。
变分自动编码器通过使潜在空间更可预测、更连续、更少稀疏来工作。通过强制潜在变量成为正态分布,VAEs 获得对潜在空间的控制。

从 AE 到 VAE 使用随机变量(自创)
VAEs 不是将潜在值直接转发给解码器,而是使用它们来计算平均值和标准偏差。然后从相应的正态分布中对解码器的输入进行采样。
在训练过程中,VAEs 通过在损失函数中包含 kull back-lei bler 散度,迫使该正态分布尽可能接近标准正态分布。VAE 将会改变或探索脸部的变化,而且不只是以随机的方式,而是朝着一个期望的、特定的方向。
条件变分自动编码器允许基于潜在变量 z 和附加信息(如面部元数据(微笑、眼镜、肤色等))对输入进行建模。).
图像数据生成器
让我们建立一个(有条件的)可以在名人脸上学习的 VAE。我们使用定制的 Keras 内存高效生成器来处理我们的大型数据集(202599 张图像,ca。每个 10KB)。这背后的想法是在训练过程中即时获得批量图像。
VAE 网
我们希望编码器是一个卷积神经网络,它获取图像并输出分布参数 Q(z|[x,c]) 其中 x 是人脸的输入图像, c 是条件变量(人脸的属性),而 z 是潜在变量。对于本文,我们使用一个由两个卷积层和一个池层组成的简单架构。
解码器是一个反过来构建的卷积神经网络。它是一个生成网络,向似然分布 P([x,c]|z) 输出参数。
整个 VAE 网络的架构创建如下。
培养
下图显示了 celebA 数据集中图像的 VAE 模型的学习过程。代码在使用 1 个 GPU 的 AWS 实例上运行了大约 8 个小时。

可视化潜在表征
经过训练后,我们现在可以从我们的数据集中随机选取一幅图像,并使用经过训练的编码器来创建图像的潜在表示。


使用 16 个实数的向量的潜在表示,我们可以可视化解码器如何重建原始图像。

虽然重建的图像很模糊,但我们可以看到与原始图像有很强的相似性:性别、衣服颜色、头发、微笑、肤色。
生成看不见的面孔
条件值能够改变潜在空间以产生新数据。具体来说,我们可以使用解码器生成一个不可见图像的随机计数,同时根据给定的属性对其进行调节。

虽然我们的变分自动编码器产生了模糊和非真实感的人脸,但我们可以识别那些从未存在过的人类的性别、肤色、微笑、眼镜、头发颜色。
给人们一个微笑
条件值可以在属性之间进行插值,使一张脸微笑,或者在以前没有眼镜的地方添加眼镜。下面,我们从数据集中随机选择一张名人脸,并利用潜在表征的改变,将其从女性脸变形为男性脸。我们也改变这些脸来展示原本不存在的微笑。

结论
在本文中,我们介绍了条件变分自动编码器,并演示了它们如何学习如何生成新的标记数据。
我们提供了 Python 代码,用于在大型名人图像数据集上训练 VAEs。该方法和代码可以扩展到多个其他用例。
生成敌对网络(GANs)往往会产生更好看的图像,因为它们学会了区分什么对人类来说是真实感的,什么不是。
用防弹 Python 代码理解、构建和训练 GANs 的终极初学者指南。
towardsdatascience.com](/generative-adversarial-network-gan-for-dummies-a-step-by-step-tutorial-fdefff170391)

伊万诺夫·古德在 Pixabay 上的照片
非常感谢 Vincent Casser 的精彩代码,在他的博客中提供了一种更先进的实现图像处理卷积自动编码器的方法。我获得了文森特的明确授权,为这篇文章改编他的 VAE 代码。从头开始构建一个可用的 VAE 相当棘手。功劳归于文森特·卡瑟。
我推荐阅读 Joseph Rocca 的后续文章:了解变分自动编码器(VAEs) 。
感谢安妮·邦纳的编辑笔记。
保持健康和安全!
变分高斯过程(VGP)——当事物不是高斯分布时该怎么办

来自 Pixabay 的图片
人们可能会有这样的印象,贝叶斯方法只有在一切都是高斯的情况下才有效。不是这样的。现代贝叶斯机器学习可以轻松处理非高斯分布。
我将描述高斯过程二元分类模型。它使用高斯先验和伯努利似然。我将解释为什么伯努利分布阻止我们使用贝叶斯规则计算后验概率。我们称这样的后验为难处理的。不管棘手与否,我们都需要后验来做预测。然后,我将提出解决方案——变分推论——来逼近难以处理的后验概率。
二元分类问题
我们有一些训练数据 (X,Y) :

X 为实数值; Y 是作为标签的二进制值。任务是设计一个模型,从输入 X 预测标签 Y 。这是一个二元分类问题。让我们绘制数据,并考虑如何使用高斯过程来构建二元分类器:

在上图中,红叉表示训练数据点。我们意识到,如果我们使用高斯过程回归来对数据建模,就会出现问题:高斯过程回归对连续函数 f(x) 进行建模。我们不能要求这样的函数只返回 1 或 0。高斯过程回归模型可能在某些 x 位置返回 1.1,在其他位置返回 0.3。
这个问题很容易解决。我们使用伯努利分布来模拟二进制输出。伯努利分布只有一个参数,即介于 0 和 1 之间的概率,它描述了事件发生的可能性。为了在这个概率和我们的高斯过程函数 f(x) 之间建立联系,我们定义了函数 g(f(x)) ,它将实值高斯过程函数 f(x) 压缩为范围[0,1]内的一个值。
g 这种方式定义的是 x 的函数。就像当你有 f(x) = x+1 并定义 g(f(x)) = f(x) 时,那么 g 就是 x : g(x) = (x+1) 的函数。
许多挤压函数(检查 sigmoid 函数)都可以工作,我们选择 logit 函数是因为它被广泛使用并且它的数学属性很容易解释:

下图说明了这一想法:

该图有三个部分:
- 顶部是黑色的高斯过程函数 f(x) 。它在对应标签为 1 的 x 位置经历一些大的实数值(相对于范围[0,1])。并且它在 x 位置经过一些小值,在这些位置对应的标签是 0。
- 中间部分用蓝色显示了 g(x) 函数。它将 f(x) 函数的值压缩到范围[0,1]内。
- 底部用红色显示我们的训练数据。
让我们浏览一个示例数据点—图中突出显示的数据点(y₄=1 x₄=9)。对于这一点,顶部的读数为 f(9)=3 。在中间部分,挤压功能 g(9) 为:

伯努利分布以 0.98 为参数,表示 Y₄=1 的概率为 0.98 。根据底部,标签 Y₄ 确实是 1 。这个模式不错。
顺便说一下,这种使用挤压函数的想法与我们如何构建神经网络分类器非常相似:一个 softmax 函数将最后一个隐藏层中神经元的输出值(全实线中的值)转换为 0 到 1 之间的概率。
受到这个例子的鼓舞,我们正式定义了这个贝叶斯模型。但首先,一些符号。
一些符号
中支持文本中的 Unicode。这让我可以写很多数学下标符号,比如 X₁和 Xₙ.但是我不能写下其他的下标。例如:


所以在文中我会用一个下划线“”来引出这样的下标,比如 *X** 和 X_1* 。
如果一些数学符号在你的手机上显示为问号,请尝试从电脑上阅读这篇文章。这是某些 Unicode 呈现的已知问题。
二元分类模型
贝叶斯模型总是由两部分组成:先验和似然。
院长
顾名思义,高斯过程先验总是多元高斯分布。它模拟了上图顶部的函数。为了刷新你的记忆,看一看理解高斯过程,苏格拉底方式。
GP 先验是两个随机变量向量 f(X) 和 f(X_)* 之间的联合多元高斯分布:

- f(X) 为长度为 n 的随机变量向量, n 为训练数据点的个数。 f(X) 代表训练位置 X 的函数值。
- f(X_)* 是另一个随机变量向量。它表示测试位置 X_,的函数值,即您希望模型进行预测的位置。 f(X_) 的长度与测试位置 X_* 的数量相同。
我用 f 作为 f(X) 的简写。该向量中第个随机变量的 fᵢ ,对应于第个第个训练数据点。同样, f_* 是 f(X_)* 的简写。
GP prior 使用以下核函数 k(x,X’)来定义位置 x 处的两个独立随机变量 f(x) 与位置X’处的f(X’)之间的相关性(位置集合 x 和X’都可以位于位置集合 X 或 X_)*

其中 exp 是指数函数。 l 和 σ 为内核参数。 l 称为长度刻度, σ 称为信号方差。 l 和 σ 是标量。由于它们是我们引入模型的参数,我们也称它们为模型参数。内核函数 k 提到了我们在协方差块 k(X,X) 中的训练位置 X 和在协方差块 k(X_,X_) 中的测试位置 X_* 以及在 k(X_,X)* 中的 X 和 X_* 。 k 不提训练数据 Y 。
简而言之,GP 先验在训练位置引入随机变量 f ,在测试位置引入随机变量 f_* ,并使用核函数 k 来定义任意两个独立随机变量之间的相关性。这种相关性使模型能够通过使用训练位置的随机变量的信息来推断测试位置的随机变量的值,换句话说,进行预测。
尽管 GP prior 是 f 和 f_,*的联合分布,但只有 f 与我们的训练数据有关联。我们可以在导出边际分布 p(f) 之前将多变量高斯边际化规则应用于 GP,只是对于 f. 和几个段落之后,我们将通过似然性建立从训练数据 Y 到 f 的联系。
下面是被边缘化的 f ,其中 K 是 k(X,X)的简称:

或者,以概率密度函数的形式:

与符号 p(f) 相比,符号p(f;l,σ ) 强调这个分布是在随机变量 f 之上;并提及模型参数 l 和 σ。它们来自于 K 中提到的指数平方核函数。 det 是矩阵行列式运算符。
注意在本文中,我们不把 l 和 σ 当作随机变量。我们将它们视为模型的标量参数。作为标量,每个模型参数可以取一个单个具体值,如 l=10.4 。我们现在不知道这个值。我将介绍一个参数学习来为 l (以及其他模型参数)找到单个值。这个具体值称为点估计。
还有其他的参数学习方法存在,比如我们可以把 l 和 σ 看成随机变量,可以取一个值的分布。这被称为完全贝叶斯治疗,是未来文章的主题。
可能性
让我们引入一个新的随机变量向量 y(X) ,我将其简称为y。我们将我们的观察值 Y 建模为随机变量中的样本 y — 每个 Yᵢ 都是来自相应随机变量 yᵢ.的样本 y 的长度为 n ,其中 n 为训练数据点数。
由于 y 是我们有观测值的随机变量,我们称之为观测随机变量。相反,随机变量 f 和 f_* 没有关联观测值,我们称之为潜在随机变量。
表示为 p(y|f,f_)* 的可能性是一个条件分布。它描述了观察随机变量 y 的概率,给出了 GP 先验的潜在变量 f 和 f_* 。
但是我们不应该设计我们的模型,使得有观测值的随机变量依赖于没有观测值的随机变量。所以我们强制要求 y 不依赖于 f_* 。这通过在以下条件下丢弃不相关的 f_* 来产生可能性 p(y|f) :

由于我们的观察值 Yᵢ 是一个二进制值,我们将其建模为来自伯努利随机变量 yᵢ.的样本yᵢ的概率密度函数为:

其中 g ᵢ 是伯努利分布的唯一参数。 gᵢ 是函数应用 g(fᵢ) 的简称。通过为 yᵢ 插入值 Yᵢ ,我们可以评估 yᵢ 获得该值的概率。所以 y 是伯努利随机变量的向量。
让我们进一步破译伯努利概率密度: gᵢ ,是 g(fᵢ的简写,代表观察随机变量 yᵢ 的值为 1 的概率。由于 yᵢ 只能取 0 或 1,那么 yᵢ 取值为 0 的概率就是 1-gᵢ 。上面的表达式是一个简洁的形式,它将这两种情况结合成一个公式。
上述伯努利密度函数建立了从观测值 Yᵢ 到观测随机变量 yᵢ.的联系通过调用函数 g 并以 fᵢ 为参数,我们建立了从 Yᵢ到fᵢ的连接:

该函数具有以下形状,x 轴绘图 fᵢ 和 y 轴绘图g(fᵢ:

让我们来看看这个可能性函数在一个地点的全部细节 Xᵢ:

第(1)行是在给定随机变量 fᵢ 的情况下,在单个位置xᵢ的可能性函数的完整公式,该公式来自我们的 GP prior 。
第(2)行将实际观察值 Yᵢ 插入随机变量 yᵢ.
这个可能提到了我们的训练数据 Yᵢ ,但是没有提到训练数据 Xᵢ 。与之前的 GP 相比,它提到了数据 Xᵢ 而不是 Yᵢ 。而随机变量 fᵢ 是先验和似然之间的桥梁,将 Xᵢ 和 Yᵢ 连接在一起。
我们希望我们的模型给可能性一个高值。所以我们来分析一下,当可能性评估为高值时。让我们来关注一下 Yᵢ=1 时的情况(Yᵢ=0 的情况也类似)。当 Yᵢ=1 时,可能性简化为:

为了使这个数量尽可能高(最大值是 1),我们需要 fᵢ 有一个更大的平均值,并且希望方差小。这样, fᵢ的期望值就是一个大值。与此同时,上述公式的期望值将更接近于 1,即其最大值。
注意,我们并不关心 fᵢ.的确切平均值只要可能性评估为高概率数字,我们就高兴,不管平均值是 4 还是 7。我们可以想象 fᵢ回归到一些潜在的数据空间。
在我们理解了单个数据点 (Xᵢ,Yᵢ) 的可能性之后,就很容易理解所有训练数据点的可能性了。我们假设观察值相互独立,因此所有观察随机变量 p(y|f) 的可能性是所有 n 个个体可能性的乘积,其中 n 是训练数据点的数量:

这里,似然 p(y|f) 没有提到任何模型参数, l 或 σ 。但一般来说,似然函数可以引入新的模型参数。
现在你已经学会了如何将非高斯分布纳入你的可能性。同样,您可以引入其他非高斯分布,如 student-t 来处理异常值。
后面的
在我们描述了先验 p(f) 和似然 p(y|f) 之后,就到了后验 p(f|y) 的时候了。贝叶斯规则根据先验和似然性给出了后验的概率密度函数:

后面提到了我们的两个模型参数 l,σ 。它们来自 prior p(f) 内部的内核函数 k 。有时我们用 p(f | y;l,σ ) 来强调这些模型参数。
分母叫做边际可能性。这是一个 n 维积分,每维是随机变量向量 f 中的一个随机变量。我们必须首先计算这个积分的结果,然后我们可以评估后验 p(f|y) 作为一个函数,例如,进行预测。
计算积分意味着计算出积分结果的解析表达式。例如,我们算出 ∫ x dx 的结果为 x 。表达式 x 是解析的——我们可以插入一个值 x 来得到积分的结果。
然而,我们不知道如何计算这个积分。积分符号内的函数是 p(y|f) 中的伯努利概率密度函数和 p(f) 中的一个多元高斯概率密度函数的乘积。你不能从你的微积分书中找到一个规则来计算这个积分。人们称之为棘手的整合。
你可能想知道,后验不应该是来自 GP 先验的同一组随机变量 f_* 和 f 的分布吗?所以不是 p(f|y) ,后验应该是 p(f_,f|y)* ,不是?你是对的。但是我们所需要的就是和 p(f|y) 一起工作,因为:

换句话说,我们决定将联合后验概率 p(f_,f|y)* 分解为条件概率 p(f_|f)* 和边际概率 p(f|y) 。通过将多元高斯条件规则应用于 GP 先验,我们知道了 p(f_|f)* 的公式。所以我们只需要用 p(f|y) 就可以了。
变分推理
我们不知道如何推导后验 p(f|y),的解析表达式,换句话说,我们不知道后验的结构。下一个最好的方法是用另一个分布来近似后验概率,该分布的结构在我们的控制中。所以通过设计,我们知道这个新分布的概率密度函数的解析表达式。
请注意,我们希望使用一个新的分布来逼近后验概率 p(f|y) ,而不是逼近边际可能性 p(y) = ∫ p(y|f) p(f) df。后验概率是对 f 的概率分布。我们需要这个分布来做预测。边际可能性是在贝叶斯规则的右侧引入的一个数,以确保左侧是一个积分为 1 的适当概率密度函数。当我们使用一个新的分布来逼近后验概率 p(f|y) 时,我们自然会考虑边际可能性,因为这个新的分布在设计上是合适的。
下面是我对“用另一种分布逼近后验概率”这句话的理解:
- 这个新分布的概率密度函数覆盖与后验概率相同的一组随机变量 f 。所以这个新的分布可以被插入到后验概率出现的地方。
- 这种新分布的行为类似于后验分布。这意味着对于一个 f ,新分布返回的概率与真实后验概率返回的概率相似。这样,这个新分布的概率密度函数的形状类似于后验分布。
为了理解短语“新发行版的结构在我们的控制之中”,让我们首先进一步发展这个近似概念。
变分分布 q
我们称我们用来近似后验的分布为变分分布。* 我们用 q(f)到表示它的概率密度函数。 q(f) 应该是什么样子?*
我们希望 q(f) 看起来像后验 p(f|y) ,但是我们不知道后验的结构。我们提出 q 是一个均值 μ 协方差矩阵σ的多元高斯分布如何?**
均值和协方差矩阵完全决定了多元高斯分布的形状。随着它们的改变,多元高斯分布的形状也随之改变。因此我们可以找到 μ 和σ的具体值,使得 q(f) 的形状类似于 p(f|y)的形状。我们使用符号q(f;μ,σ)来强调这两个参数。我们称它们为变分参数。
下图说明了制作形状 q(f;μ,σ)接近于p(f | y;l,σ )* ,其中 f 是单个随机变量,而不是随机变量向量:*

紫色分布呈不规则形状,说明后部的结构我们不知道。绿色分布是钟形的,说明了变分分布,这是一个多元高斯分布。我们调整 μ 和σ的值,使绿色尽可能与紫色重叠,使q(f;μ,σ)近似 p(f|y) 。
我们这里有两个非常重要的假设:
- 我们假设存在一些具体的变分参数值,使得我们的多元高斯分布q(f;μ,σ)类似于非高斯后验p(f | y;l,σ)。**
- 我们假设可以通过参数学习为 μ 和σ找到这些具体值中的至少一个。**
这两种假设在现实中都可能是错误的。所以对于某些数据集,我们可能无法找到好的 q(f) 的后验近似。这意味着我们可能找不到一个好的贝叶斯模型来很好地拟合训练数据——机器学习任务失败了。这很糟糕,但它发生了。
顺便说一下,变分推理在现代机器学习中非常重要,是一个你无法逃脱的话题。参见其在时间序列预测和人脸生成中的应用。
我们通过 μ和σ来控制 q 的形状
在多元高斯建议下,的概率密度函数 q(f;μ,σ)是:

其中 n 是训练数据点的数量, det 是矩阵行列式运算符。σ⁻是协方差矩阵σ的逆。
这个公式也显示了解析公式的重要性——它可以在插入未知数的具体值后进行评估: f ,这是一个浮点向量, μ ,这是一个浮点向量,σ,这是一个大小为 n×n 的浮点矩阵。当我们有了这三个的一些具体值后,上面的公式计算出一个单一的概率数——多元高斯分布下 f 的概率。**
变分分布 q 为多元高斯的提议现在看起来是纯粹的猜测,但稍后你会看到这种结构有很大的优势。
变分分布q(f;μ,σ)具有固定的结构——全多元高斯分布的结构。“满”意味着σ是没有零的密集矩阵。让我们稍后在参数学习部分回到这个问题。现在对我们来说重要的是,我们可以通过两个变化参数 μ 和σ来改变它的形状。理论上,通过改变 μ 和σ*的值,我们可以得到随机变量向量 f 上任何可能的高斯分布。这就是为什么 q 的结构在我们掌控之中。*
一旦我们决定了 μ 和σ的具体值,我们就有了完全指定的 q ,我们可以用它作为真实后验概率的近似值。但是我们如何决定将哪些具体值赋给 μ 和σ?显而易见的答案是选择 μ 和σ的值,使得q(f;μ,σ)尽可能靠近后 p(f|y,l,σ ) 。
我们意识到我们需要一个从分布 q(f) 到后验 p(f|y) 的“接近度”度量。我们需要一种方法来最小化这种亲密度。
kull back-lei bler 散度和证据下界
因为我们希望我们的变分分布q(f;μ,σ)向后方靠近p(f | y;l,σ )* ,我们需要一个他们之间的贴近度。我们可以使用 kull back-lei bler 散度(KL 散度)来定义一个分布与另一个分布的接近程度。变分分布之间的 KL 散度公式q(f;μ,σ)和后验p(f | y;l,σ ) 是:*

在上面的推导中,我将模型参数(核参数和变分参数)涂成红色。
线(1)是 KL 发散的符号。KL 运算符内的双竖线||分隔我们要计算距离的两个分布。
第(2)行定义了 KL 背离的含义。它是两个分布之间的预期对数概率比。如果对于某些 f 、 q(f) 和 p(f|y) 评估为相同的概率,那么它们的比值为 1, log(1) 为 0。所以这个特殊的 f 对总的散度度量没有贡献。另一方面,如果该比率不是 1,则该 f 有助于最终的散度测量。因此 KL 散度是在对数标度中 f 的概率密度之间的平均差。
第(3)行用它的数学计算——积分——代替了期望值。它显示被积函数(插入训练数据 X 和 Y )有 4 个未知数: l,σ,μ,σ,这是全套模型参数。随机变量 f 被积分出来。
我们要的是 q 的形状(f;μ,σ)尽可能接近p(f | y;l,σ )* 尽可能。KL 散度 KL(q(f)||p(f|y)) 编码这种接近度,并且它是模型参数的函数。使 q(f) 尽可能接近 p(f|y) 的方法是最小化关于模型参数的 KL 发散:*

你内心一定有个疑问在燃烧——我们不知道后验p(f | y)的解析形式,如何才能最小化提到它的 KL 散度?
为了回答这个问题,我们需要继续操作公式,但是现在为了节省空间,我忽略了推导中的模型参数。请记住,无论我们如何操纵 q(f) 和 p(f|y) 之间的 KL 散度,它始终是四个模型参数的函数。

在第(6)行,我们不写 log p(y) 周围的期望运算,因为 log p(y) 没有提到随机变量 f ,忽略的期望是相对于 f 的。所以期望评估为 log p(y) 。我们对 log p(y) 有一个著名的名字——对数边际可能性。
上面的推导,尤其是第(6)行,证实了我们不能计算这个 KL 散度。这是因为它要求我们计算对数边际可能性log p(y),但是 p(y) 是一个难以处理的积分,它首先阻止我们使用贝叶斯规则来计算后验概率:

既然无法计算 logp(y) ,那就勇敢一点,把它去掉,尽量把剩下的两项最小化。
等价的,我们可以最大化的否定这两个项。我们给这种否定命名为证据下界,或 ELBO :

第(2)行否定了上面 KL 散度公式的前两项,去掉了第三项 log p(y) ,我们无法计算。
第(3)行和第(4)行使用 log 的属性和期望的线性来重新组织术语,即对于两个随机变量 a 和 b ,使用E【a+b】= E【a】+E【b】。
行(5)认识到第二项是变分分布 q(f) 和之间的 KL-散度,GP 先于 p(f) 。
第(6)行将第一项中的期望扩展到其实际的数学定义——积分。
从现在开始, ELBO 将是我们最大化的目标函数,以使变分分布 q(f) 尽可能接近后验 p(f|y) 。
为什么可以删除 log p(y)?
随着 EBLO 的定义,我们有了这个等式:

注意 ELBO 前面的减号。
我们的理想目标是最小化 KL(q(f)||p(f|y)) ,但是因为我们不能计算 log p(y) ,我们最小化 -ELBO ,或者等价地,最大化 ELBO 作为的替代。
你可能会问为什么可以去掉 log 边际可能性 log p(y) ?回答这个问题,我可以引用“所有的模型都是错的,但有些是有用的”。我们知道丢弃 log p(y) 是错误的,但是如果我们保留它,我们就无法计算公式。
玩笑归玩笑,我认为你的问题的潜在含义是:丢弃 log p(y) 我们会失去什么信息?这种信息丢失是可以容忍的吗,这样剩下的模型仍然有能力发挥作用?为了回答这个新问题,让我们考虑一下 log p(y) 包括哪些信息——它是由先前的 p(f) 加权的可能性 p(y|f】、之和:

下面再次显示的 ELBO 公式也包括出现在第一项中的可能性信息和出现在第二项中的先验信息:

因此,通过丢弃 log p(y) ,我们并没有失去一切,这是重要的一点。当然,剩余的信息没有以加权和的形式出现,所以我们有一些信息损失。这就是我们使用可计算的替代公式而不是理想的、但不可计算的公式所付出的代价。
ELBO 背后的直觉
从上面看第(6)行,我们对 ELBO 有一个直观的解释,复制到这里:

第一项是相对于随机变量 f 的期望似然性,现在来自变分分布 q(f)。第二项是 q(f) 与 GP 前 p(f) 之间的 KL 背离——注意,不是后 p(f|y) ,记住一个 KL 背离是非负 。
我们想要最大化 ELBO 。要做到这一点,我们需要期望的可能性大,KL 距离小。这个数学意义很好地符合我们的意图:
- 我们希望我们的模型 q(f) 能够通过很大的可能性很好地解释训练数据。
- 同时,我们希望 q(f) 通过 q(f) 和 p(f) 之间的小 KL 距离,保持接近我们的先验。这是因为我们相信我们先前的模型如何生成训练数据。后验仅重新加权来自前验的函数,使得很好地解释训练数据的函数获得更高的概率。理解 KL(q(f)||p(f))项的另一个角度是它是一个正则项。
ELBO 提到先前的p(f;l,σ ) 和变分分布q(f;μ, Σ).因此它仍然是这四个模型参数的函数。在第一项中,对数似然项只提到了变分参数 μ 和σ*。第二项,即 KL(q||p) 项,提到了所有四个模型参数。*
你可能会想,既然我们放弃了我们无法计算的项——在KL(q(f)| | p(f | y)】公式中的 logp(y) 项,为什么我们不能在意识到有一个难以处理的积分的第一时间放弃它呢?那时我们试图用贝叶斯法则来计算后验概率:

并且我们意识到 p(y)=∫p(y|f)p(f)df ,也就是分母,很难处理。但是我们不能把它放在这里,因为我们会得到一个没有分母的分数,这是不良的。
我们现在可以去掉 logp(y)) 项,因为 KL-divergenceKL(q(f)| | p(f | y))由其他项组成。
你可能会奇怪,既然 KL 散度衡量的是两个分布之间的相似性,为什么我们选择做 KL(q(f)||p(f|y)) ,而不是 KL(p(f|y)||q(f)) ?
- 第一, KL 散度不对称,意思是 KL(q(f)||p(f)) 不等于 KL(p(f)||q(f)) 。
- 第二,如果我们使用 KL(p(f)||q(f)) ,我们会有问题:

从第(3)行我们可以看到,两个积分都提到了我们无法计算的后验 p(y|f) 。
好了,现在我们有了关于模型参数最大化的 ELBO 函数。因此 ELBO 是我们的优化目标。寻找具体值以最大化该目标的过程称为参数学习。
从对数边际似然角度看
您可能还记得,在高斯过程回归设置中,参数学习最大化的目标函数是 log 边际似然 logp(y) ,在该设置中, logp(y) 是易处理的。
现在在分类中,我们最大化 ELBO ,这两个目标函数是如何关联的?事实上,你可以从对数的边际可能性观点中推导出 ELBO :

线(1)是对数边际可能性的符号。
第(2)行将定义 p(y) 扩展为一个积分。
第(3)行引入了数量 q(f)/q(f) ,其计算结果为 1。所以这个量不会改变积分的结果。这是一个著名的操作——它将变分分布 q(f) 带入图像。
第(4)行把第(3)行中的积分改写成关于来自变分分布 q(f) 的随机变量 f 的一个期望。
第(5)行应用詹森不等式将 log 函数从预期之外移到预期之内。由此产生的期望是 ELBO 的公式。我们使用詹森不等式给我们计算带来方便——一个对数的期望比一个期望的对数更容易操作。
ELBO 是一个下界
请注意在应用詹森不等式后,从等号变为大号或等号。它揭示了 ELBO 小于我们真正希望最大化的 log 边际可能性 log(p(y)) 。这个边际可能性也被称为模型证据。而我们的 ELBO 比这个证据低,因此得名证据下界。
你可能会问,这个 ELBO 下界和我们真正想要最大化的 log 边际似然 log p(y) 有什么区别?我们已经从前面的等式中知道了这种差异:

差异是变分分布 q(f) 和真实后验分布 p(f|y) 之间的 KL 散度。重新排列一些术语后,我们有:

注意一个 KL 散度是非负量,所以 KL(q(f)||p(f|y)) ≥0 。这个等式证实了最大化 ELBO 等价于最小化 q(f) 和 p(f|y) 之间的 KL 散度,因为更大的 ELBO 意味着更小的 KL(q(f)||p(f|y)) 。为了正确理解这一点,我们需要了解一些微妙之处:
- log(p(y)) 是提及核参数 lengthscale l 和信号方差 σ 的表达式。
- ELBO 是提及核参数以及变分参数 μ 和*σ的表达式。*
- KL(q(f)||p(f|y)) 是提及核参数以及变分参数 μ 和*σ的表达式。*
通过改变 l、σ、μ、和σ的值来最大化 ELBO 将同时改变 log p(y) 的值。在参数学习过程中,不能将 log p(y) 视为常数。但是无论参数学习过程为 l,σ,选择哪个值,一旦 ELBO 在这个模型参数值配置中最大化,这个配置就给你一个最小化的 KL(q(f)||p(f|y)) 。
ELBO 的解析表达式
我们再来看看爱尔博公式:

ELBO 是一个以模型参数为自变量的函数。我们需要推导出 ELBO 的解析表达式,因为我们想要使用梯度下降法来找到使 ELBO 最大化的那些模型参数的具体值。梯度下降仅适用于分析函数,因为它计算该函数相对于其参数的梯度。
KL 项已经是解析的了
由于 q(f) 和 p(f) 都是多元高斯概率密度函数,它们的 KL-散度的解析形式是已知的。这是选择 q(f) 做多元高斯的好处之一。稍后,我会给你看这个 KL 项的解析表达式。
可能性项不是分析性的
可能性项是一个积分,所以还不是分析性的。让我们稍微操作一下:

以上表达式由结构相同的 n 项组成。为了导出整个似然项的解析表达式,我们可以关注任意项,比如第 I 个项项。下面是一个非常重要的引出:

第(1)行是似然项中第个项的公式。它是对随机变量向量 f 的一个 n 维积分。 n 是训练数据点的数量。这是因为这个积分是计算【log(p(yᵢ|fᵢ】)相对于变分分布 q(f) 的期望,这是一个 n 维多元高斯。注)在 fᵢ 中只提到了单个随机变量,而 q(f) 提到了所有 n 个随机变量。
第(2)行将随机变量向量 f 扩展成单个随机变量 f₁ 到 fₙ.在 f 中有 n 个随机变量,所以有 n 个一维积分。
第(3)行引入一个名字 f₋ᵢ 来表示随机变量【f₁,f₂,…,fᵢ₋₁,fᵢ₊₁,…,fₙ】的向量。这是一个长度为 n-1 的向量。包含 f 中除 fᵢ.外的所有随机变量我们引入 f₋ᵢ 来简化公式。
第(4)行将 n 个积分重新分组为两个积分:一个在单变量 fᵢ 上,另一个在随机变量向量 f₋ᵢ 上。原因是我们想要应用多元高斯边缘化规则来计算 f₋ᵢ 上的内部积分。
第(5)行应用多元高斯边际化规则来计算对 f₋ᵢ 的内部积分,结果为 q(fᵢ).令人惊讶的是,我们知道 q(fᵢ的解析表达式。这一步需要更多的解释。
召回q(f;μ,σ)是均值向量 μ 和协方差矩阵σ的多元高斯分布。这里我显式地展示了 f 、 μ 和σ*的条目 :*

第(3)行的上述推导将分布 q 中的随机变量重新组织到 fᵢ、f₋ᵢ 块中。在这一行:
- μᵢ 是一个浮点值;它是随机变量 fᵢ.的平均值
- μ₋ᵢ 是一个长度为 n-1 的浮点向量;它是向量 f₋ᵢ.中随机变量的平均值
- **σᵢ,ᵢ是一个浮点值;它是随机变量 fᵢ 的方差
- **σᵢ,₋ᵢ和σ₋ᵢ,ᵢ是大小为 (n-1)×(n-1)的矩阵;它们是互相转置的。它们代表随机变量 f₋ᵢ 和随机变量 f₋ᵢ.之间的协方差
- *σ₋ᵢ,₋ᵢ*是一个大小为 (n-1)×(n-1),的矩阵,它是 f₋ᵢ.中随机变量之间的协方差
随着分布 q 以这种方式重新表述,让我们回过头来看看随机变量 f₋ᵢ 的内部积分:

这个积分从联合分布 q(fᵢ,f₋ᵢ) 中整合出随机变量 f₋ᵢ ,得到 q(fᵢ) ,一个单一随机变量 fᵢ.的分布这就是概率论中边缘化的含义。
由于 q(fᵢ,f₋ᵢ) 是多元高斯分布,我们可以应用多元高斯边际化法则,繁琐地推导出 q(fᵢ) 的解析表达式,相当于计算上述积分。这是选择 q(f) 为多元高斯分布的另一个好处。
多元高斯边际化规则表示边际化分布 q(fᵢ) 仍然是高斯分布,其均值和方差等于联合分布中用红框突出显示的部分:

突出显示的部分是随机变量 fᵢ 的一维高斯分布,具有均值 μᵢ 和方差σᵢ,ᵢ。μᵢ* 是 μ 向量的第I项,σᵢ,ᵢ是**矩阵σ的对角线的第I项。概率密度函数 q(fᵢ) 为:*

由此,我们可以看出第 i 个似然项为:

这个函数在 μ 中只提到一个 μᵢ 条目,在σ中只提到一个σᵢ,ᵢ条目。推广到所有的似然项,我们知道总体似然提及向量 μ 中的所有条目,并且仅提及σ的对角线条目。**
似然项提到了哪些模型参数?
重要的是,在似然项中只提到了变分参数 μ 和变分参数σ中的对角线项。在似然项中没有提到σ中的非对角线条目。在似然项中也没有提到核参数 l 和 σ 。**
可能性是 n 个一维积分的总和
上述公式揭示了第个似然项是单个随机变量 fᵢ 的一维积分。推广到似然性中的任意项,我们知道它们都是一维积分,每个积分针对 f 中不同的随机变量。
因此,如果我们能够计算出代表第个个可能性项的积分结果的解析表达式,我们就能够计算出整个可能性的解析表达式。KL 项已经是解析的,我们将得到整个 ELBO 的解析表达式。这将使我们能够使用梯度下降进行参数学习。
第 i 个似然项是一维积分的事实非常重要。因为这个可能性项是一个积分。我们之前说过,它是一个运算,而不是一个我们可以求值的解析表达式。这种一维积分也很难计算。但是由于它是一维的,我们可以应用一种叫做高斯求积的方法,以解析的方式来近似这个积分的结果。
高斯正交
高斯求积法则,或者更具体地说,高斯-埃尔米特求积法则,如下所示,表示左手侧的特定结构的积分结果可以通过右手侧的 m 项的和来近似,其中 F(t) 是 t 的函数:

在右侧, wⱼ 和 tⱼ 是预先计算的,我们可以从高斯正交表中查找它们。 F(tⱼ) 是 F 在 tⱼ 位置的评价。右手边是一个解析表达式,因为我们可以查找 wⱼ和 tⱼ,我们可以计算 F(tⱼ).左侧和右侧都计算出一个数字。
请区分我们介绍的两种近似值:
- 之前我们引入了变分分布 q(f) 来逼近真实的后验概率 p(f|y) 。我们迫使 q(f) 接近 p(f|y) 的方法就是最大化 ELBO 。这是一个分布到分布的近似值。
- 现在我们需要推导出ELBO中第 I 个似然项的解析表达式。由于积分很难计算,我们想用高斯求积来近似这种积分的结果。这是一个数字对数字的近似值。
现在,请接受高斯正交近似是有意义的。事实上,这个近似值通常非常接近积分的真实结果。从这个意义上说,高斯正交近似是真实积分值的估计量。因为近似值与积分结果不同,所以这个估计量有一个偏差,由近似值与真实积分结果之间的差异来衡量。如果选择足够大的 m ,偏差可以任意小,代价是更多的计算。**
高斯求积背后的直觉
我会写另一篇文章来解释为什么高斯求积是有意义的。但是现在为了让你好奇的心平静下来,这里有一个简单的解释。
您可以将积分结果理解为无限多个等宽矩形的总和,如下图所示。每个矩形都有一个无限小的宽度,其高度是被积函数在该矩形 x 位置的值。

图片来自此处
我们可以通过在不同的 x 位置仔细选择几个(而不是无限多个)矩形,并给这些矩形不同的宽度,来近似积分的结果。在函数缓慢移动的区域,矩形较宽,在函数快速移动的区域,矩形较窄。下图说明了这一想法。

图片来自这里
高斯求积公式中的加权和结构正好实现了上述思想。求积理论告诉你如何选择位置 tⱼ 来计算被积函数,以及这个矩形的宽度 wⱼ 应该是多少。这些位置和宽度已经在的表格中计算出来了。你只需要决定你想要多少个矩形,那就是 m 的值。你想要的矩形越多,近似越精确,但是计算越昂贵。
很神奇,不是吗?
用高斯求积逼近第 I 个似然项
现在,让我们使用高斯求积来推导近似第个似然项的结果的解析表达式。
再看集成:

这不是高斯求积法则左侧尺寸所要求的格式:

我们需要将这个积分转换成高斯求积规则所要求的格式。很多时候,我们在数学中做的事情是模式匹配。
由于第 I 个*似然项中的积分是关于一个高斯随机变量 fᵢ ~ 𝒩(μᵢ,σᵢ,ᵢ)的一个期望,我们先应用积分中的变量变化规则,将其转化为关于一个标准高斯分布 𝒩(0,1)的一个期望。我们引入一个新的一维随机变量 u 来自标准高斯分布: u~𝒩(0,1)。*
我们可以将随机变量 fᵢ 定义为:

使用高斯线性变换规则,很容易验证以这种新方式定义的 fᵢ 仍然来自分布 𝒩(μᵢ,σᵢ,ᵢ):

你可以看我的另一篇文章揭开 Tensorflow 时间序列的神秘面纱:局部线性趋势了解更多关于高斯线性变换规则的信息。
利用这个新的随机变量 u ,我们将第个似然项转换为:

线(2)扩展了 fᵢ.的概率密度函数
第(3)行应用变量改变规则将积分变量从 fᵢ 改变为 u 。
第(4)和(5)行简化了公式。在第(5)行,出现了标准高斯分布的概率密度函数。
第(6)行认识到,现在第个似然项是关于标准高斯随机变量 u 的期望。
我们意识到第(5)行的公式仍然不是我们应用高斯求积所需的格式。但是理想的格式只是另一个变量的变化。我们引入一个新变量 t :

并将第 I 个似然项变换为:

我们最终将第一个似然项转换为高斯求积规则所要求的格式,其中:**

在变量变化的这两个应用中,所有新旧变量 fᵢ、和 t、的定义域都是从-∞到+∞,所以这些运算不会改变积分的边界,所以我们一起忽略了边界。
通过应用我们期待已久的高斯求积公式,我们可以得出:

第(1)和(2)行是高斯求积公式。
线路(3)插入 F(t) 。
第(4)行写出了所有的可能性。让我们仔细阅读它,因为它揭示了重要的信息:
- 这个公式是解析的,因为我们可以在将 Yᵢ 插入 yᵢ 后评估其值,决定值 m ,并用高斯求积表中的值替换所有 wⱼ 和 tⱼ 。它计算出一个实数值。
- 由于每个第 I 个似然项由 m 个项组成,所以整个似然项由 m×n 个项组成,其中 n 为训练数据点的个数。这个好像是很多项,比如说 m 是 40,训练点数是 1000,我们对于似然项就有 40000 项。但这是可以承受的,因为项数与 n 成线性比例关系,比例系数为 m。对于高斯过程来说,任何线性变化都是好消息。
- 这个表达式是一个函数,它提到了变分参数 μ 和来自σ的对角元素。因为这个表达式是 ELBO 的一部分,ELBO 是我们要最大化的目标函数,它确实满足了我们对目标函数的要求——它至少需要提到我们的一些模型参数。 ELBO 中的似然项只提到了部分变分参数,并没有提到来自 GP 先验的核参数。请记住这一点,我们稍后会详细说明。
求积公式中的常数
为了让您对高斯求积表有所了解,当我们选择 m=10 时,位置 tⱼ 和权重 wⱼ 的值为:

使用的项数越多(m越大),近似值就越精确,但计算成本也就越高。经验法则是使用 m=40 。
恭喜你!我们推导出了整个 ELBO 的解析表达式,我们为参数学习做好了准备。在此之前,一个负责任的机器学习实践者会问几个问题。
高斯求积是一个好的近似吗?
由于我们使用高斯求积为我们提供了一个解析表达式来近似第 I 个 T2 似然项中的积分结果,我们需要知道这种近似有多好,以及它的计算负担能力如何。
高斯求积非常精确。你可以看看视频高斯正交的魔力——比第二好的东西好十亿倍来说服自己。事实上,高斯求积的偏差可以忽略不计。高斯正交近似结果与积分的真实值之间几乎没有差别。
高斯求积是一个确定性的过程,在近似值中没有方差——您调用高斯求积方法任意次,都会得到相同的结果。
因此,高斯求积是第个似然项的一个很好的近似。
为什么所有的模型参数都可以从训练数据中学习?
我们看到第 I 个似然项的解析表达式只提到了变分参数 μ 和另一个变分参数σ的对角线上的条目。也没有提到内核参数 l 和 σ ,这两个参数来自 GP previous。**
此外,我们注意到在 ELBO 中,只有可能性项提到了训练数据 Y 。你可能会问,σ的非对角条目和内核参数 l 和 σ 如何从数据中学习?这是一个非常重要的问题。我们的训练数据与所有模型参数之间的联系是一个必要条件,使得这些模型参数可以通过梯度下降从训练数据中学习。
要回答这个问题,我们需要考察 ELBO 中另一项的解析表达式,变分分布q(f;μ,σ)和先验的p(f;l,σ )* 。*

首先要注意的是矩阵求逆 K⁻ ,其中 K 是来自 GP 先验的协方差矩阵。K 的形状为 n×n ,其中 n 为训练数据点的个数。所以这种倒置是非常昂贵的。这也意味着,我们的模型不能扩展到大型数据集。
线(2)给出了在q(f;μ,σ)和p(f;l,σ )* 。两者都是相同随机变量 f 的多元高斯分布。计算有点复杂,请接受这是可以做到的,而且是分析性的。 n 是训练数据点的数量。 tr 代表跟踪算子,它是一个方阵对角线上的元素之和。 K 是 GP prior 中定义的 n×n 协方差矩阵。 det(K) 代表 K 的行列式。 K⁻ 是 K 的逆。而 m(X) 是我们给 GP 先验的均值函数,通常情况下, m(X)=0 。*
第(3)行将零均值函数 0 插入公式以简化它。
让我们用两个训练数据点 (X₁,Y₁),(X₂,Y₂) 来研究第(3)行的公式。例如:

协方差矩阵 K 在训练数据中只提到了 X ,没有提到 Y 。作为协方差矩阵的 K 是对称的。并且 K 提到了内核参数 l 和 σ。
让我们看看我们的变分分布(这里,为了节省空间,我把协方差矩阵的一个条目写成σᵢⱼ,而不是我以前使用的符号σᵢ,ⱼ:

有了上面的定义,我们可以使用微积分中的规则 : 计算行列式 det(K) ,det(σ)*和逆 K⁻*
**
现在,我们可以写下 KL 散度公式中的各项:



我们只需要浏览一下这些项,就可以看到在 KL 散度项中:
- 所有模型参数,包括来自变分分布的 μ 中的所有条目和σ中的所有条目(对角线以及非对角线),以及来自 GP prior 的 l 和 σ ,都纠缠在一起。
- 由于这种纠缠,似然项中提到的模型参数部分(即 μ 和σ的对角线条目)会影响似然项中没有提到的其余模型参数(GP prior 中的σ和 l 和 σ 的非对角线条目)。
- 并且,训练数据 Y 可以影响所有模型参数。
这就是为什么所有模型参数都可以从训练数据中学习到。
模型可扩展性
从 q(f) 到 p(f) 的 KL 散度中的矩阵求逆 K⁻ 计算起来很昂贵。运算的次数,比如矩阵中的倍数或者两个数相加,与 n 成比例。 n 是训练数据点的数量。对于现代标准的微小数据集 n=1000 ,反演需要数十亿次运算。
这意味着我们的变分高斯过程无法扩展到大型数据集——计算 K⁻需要太长时间。我的下一篇文章将讨论稀疏和变分高斯过程,这是一个可以扩展到大型数据集的模型。
我们可以用高斯求积来计算后验概率吗?
你可能会问,我们在 ELBO 中用高斯求积来近似似然项,是否可以用它来近似贝叶斯法则中的边际似然?

变分分布 q(f) 和 GP 先验都是多元高斯分布。所以结构上,这两个整合是一样的。为什么不用高斯求积来近似边际似然呢?我完全理解这个问题——它也让我抓狂。
但是在 ELBO 似然项中的log(p(y | f)】和边际似然项中的 p(f|y) 有着至关重要的区别。边际可能性前面没有日志。所以我们不能使用属性 log(ab) = log(a) + log(b) 将边际似然分解成更小项的和。没有这一点,我们不能将多元高斯边际化规则应用于边际似然,以将对 f 的 n 维积分减少到对单个随机变量 fᵢ.的单个积分之和
因此,边际似然仍然是一个 n- 维积分,其高斯正交近似需要 mⁿ 项,即mn 项的幂——总项数随着训练数据点的数量异常增长。即使对于一个小的数据集来说,这也是一个不切实际的大数字。这就是我们不能在直接计算后验概率时使用高斯求积的原因。
本文的附录向您展示了为什么高斯求积需要 mⁿ 项来逼近边际似然。
参数学习
现在我们已经推导出了 ELBO T1 的解析表达式。这个表达式是一个函数,其中我们的所有模型参数都是未知的——核心参数 l,σ,和变分参数 𝜇,σ。参数学习使用梯度下降来最大化 ELBO 以找到这些模型参数的最佳值。这是梯度下降的直接应用。但是仔细思考这种优化提出了一些有趣的问题。
先总结一下我们的模式。
完整的模型
我们的二元分类模型包含以下组件:
- 多元高斯先验p(f;l,σ )* ,其中引入了潜在随机变量向量 f ,以及核参数 l 和 σ。*
- 伯努利似然 p(y|f) ,引入观测随机变量向量 y ,不引入任何参数。
- 变分分布q(f;μ,σ),其中引入了变分参数 μ 和σ。我们要求q(f;μ,σ)向后方靠近p(y | f;l,σ )* 在 KL-散度意义上。*
在数学语言中,模型显示为(红色的模型参数):

第(1)行是 GP 优先。第(2)行是可能性。第(3)行定义了由 Bayes 规则计算的真实但难以处理的后验概率。第(4)行引入了近似真实后验概率的变分分布。第(5)行使用 KL 散度来定义通过变分分布来近似真实后验概率的含义。这种 KL 差异也是难以处理的,所以我们最终最大化 ELBO 作为最小化这种 KL 的代理。
我们需要学习多少标量?
我们的模型有四个不同形状的参数(形状在张量形状的意义上):
- 长度刻度 l 为标量。
- 信号方差 σ 也是标量。**
- **q(f)的均值向量 μ 是一个长度为 n 的向量,所以它有 n 个标量项。
- **q(f)的协方差矩阵σ是一个大小为 n×n,的矩阵,它是对称的,所以有 n(n+1) 自由标量项。
这个模型总共有 2+n+ n(n+1) 个标量,这些标量的值需要通过参数学习来决定。不要忽略列出所有模型参数的任务——这是一个健全性检查,以便您理解模型。
我知道你想喊出来: 2+n+ n(n+1) 是很多标量,它比训练数据点的数量 n !
既然大部分标量来自于变分分布 q 的参数,那我们能不能用更少的标量参数化 q ?是的,这就是平均场参数化。
q 的平均场参数化
我们的变分分布q(f;μ,σ)是完全多元高斯分布,因为σ是没有零点的完全协方差矩阵。如果我们要求σ是对角矩阵,那么 q(f) 就变成了平均场分布。

使用平均场 q(f) 的动机是有较少的变化参数要学习。代替来自全协方差矩阵的 n(n+1) 自由标量,我们现在只有来自平均场协方差矩阵对角线的 n 自由标量,因为它是对角方阵。在平均场配置中,我们需要学习的标量总数是 2+2n 。模型参数越少,参数学习过程越容易,这意味着优化停止得越快。
这种平均场近似的缺点是,它只能模拟更小的分布子集——约束所有随机变量相互独立的子集。然而,在现实中,真实后验概率中的随机变量通常是相互关联的。这意味着平均场近似与具有全协方差矩阵的变分分布相比是更粗略的近似。
这是我们必须决定的模型容量和计算成本之间的权衡。通常的做法是从平均场近似开始。如果学习模型在预测中表现不佳,请使用更昂贵的全协方差近似值。
当变分分布q(f;μ,σ)具有平均场结构,即σ是一个对角矩阵,在 f 中的所有单个随机变量都是相互独立的——它们之间没有任何关系。因此,关于一个个体随机变量的信息,比如说 fᵢ的和 fⱼ的不能用来推断另一个个体随机变量。你可能想知道我们的模型是如何做出预测的。
我们的模型可以在测试位置 X_ 进行预测,因为即使位于训练位置 X、的 f 中的随机变量之间的相关性为零,但是 f 与测试位置 X_* 的随机变量 f_* 之间的相关性不为零。这个非零相关性是我们通过先验强加的条件概率 p(f_|f)* 。它使模型能够通过使用来自 f 的信息来推断 f_* 的值。我们将在做出预测部分更清楚地看到这一点。*
参数太多的模型?
在统计了需要学习的标量数量后,我们意识到一个问题——我们的模型要学习的标量( 2+n+ n(n+1) 在全高斯配置中, 2+2n 在平均场配置中)比训练数据点的数量多,也就是 n 。
这很糟糕,因为模型现在有能力精确地拟合训练数据-模型可以只使用其参数来记住训练数据在哪里。但是这个模型不能推广到新的数据。人们用术语过度拟合来指这种模型任意地很好地拟合了训练数据,但是对测试数据做出了糟糕的预测的情况。
顺便说一下,模型具有足够的参数来任意地很好地拟合训练数据,并不意味着梯度下降可以找到那些参数的具体值来很好地拟合训练数据。模型的参数越多,梯度下降在参数值空间中导航就越困难。梯度下降可能收敛到一些甚至不适合训练数据的模型参数值。
理想情况下,我们希望模型有足够数量的参数和足够的模型结构(核函数、似然函数等)。)使得模型可以很好地总结训练数据的潜在特征,而不是记住训练数据在哪里。这种总结还使模型能够归纳出新的测试数据。但是从设计上来说,我们的二元分类模型,参数太多,不是一个好的模型。真扫兴。
事实上,你学习这个模型的真正原因是为了让你熟悉如何将变分推断应用于高斯过程模型。这是为了让你为实际的实用模型——稀疏变分高斯过程模型(SVGP 模型)做好准备。有关 SVGP 模型的更多信息,请访问:稀疏和变分高斯过程——当数据很大时该怎么办。现在我建议你先完成这篇文章。
没有随机梯度下降?
ELBO 由似然项和 KL 项组成。这两个术语都需要完整的数据来评估。如下所示,可能性项需要完整的 Y 。

而 KL 项,如下所示,需要所有的训练数据来计算:

第(3)行的公式提到了先前的协方差矩阵 K=k(X,X) 。 K 需要整个 X 来计算。
因此,在每个优化步骤中,梯度下降算法需要检查所有训练数据,以计算 ELBO 及其梯度。这就是为什么我们不能在我们的变分模型上使用随机梯度下降。随机梯度下降在每个优化步骤中只使用一批训练数据,因此它需要的内存少得多,而且通常收敛得更快。
好消息是,我将在下一篇文章中讨论的稀疏和变分高斯过程模型可以利用随机梯度下降。所以请继续关注,一切终将解决。
梯度下降压力太大?
我们使用梯度下降来最大化目标函数 ELBO 。如果我们仔细想想, ELBO 实际上在一个公式中编码了两种优化:
- 找到核参数 l 和 σ 的值,使得真实后验很好地解释了训练数据。
- 找到变分参数 μ,σ的值,使得变分分布q(f;μ,σ)非常接近真实的后验。
这对于梯度下降来说要求太高了。这在数学上意味着: ELBO 可以是高度非凹的,有很多局部极大值。梯度下降法作为一种局部优化方法,会卡在一个局部最大值上,不一定是一个好的局部最大值。那么我们如何优化这样的模型就成了它自己的艺术/科学:
- 使用哪些优化器?
- 我们如何降低学习率?
- 我们如何在优化开始前初始化模型参数?
- 我们如何处理数值不稳定性?对于高斯过程模型,当我们对一个矩阵求逆时,这意味着乔莱斯基分解失败。
这是一个很大的话题,我将在不久的将来发表几篇文章来解释它。
做预测
现在我们有了随机变量 f 的后验 p(f|y ) ,它在训练位置 X 。其实我们没有 p(f|y)。我们有类似于后验概率的变分分布 q(f) 。不过还是继续用 p(f|y) 吧,好像我们知道一样。
使用后验概率进行预测
先说清楚为什么需要后验来做预测。
我们的完整模型由三个随机变量组成, f_,f ,和 y 。 f_* 和 f 是潜在随机变量,它们没有相应的观测值。 y 是一个观察型随机变量,它有来自我们训练数据的观察值 Y 。*
通过 GP prior p(f_,f) 我们对随机变量 f 和 f_* 施加了一些特征。通过似然 p(y|f) ,我们建立了潜在随机变量 f 和观察随机变量 y 之间的联系。*
贝叶斯规则使用观测值 Y 以后验分布 p(f_,f|y) 的形式给出 f 和 f_* 的更新特征。这就是我们要使用后验概率进行预测的原因,后验概率会对先验概率中的所有函数进行重新加权,以便更好地解释训练数据的函数获得更高的概率。*
后验 p(f_,f|y) 是随机变量 f_* 和 f 的分布。但是我们只需要对 f_* 进行预测,换句话说,我们只需要对 f_* 进行分布。因此,我们对来自 p(f_,f|y)* 的随机变量 f 进行积分,得到预测分布 p(f_|y)* 。*
注意,即使我们的模型由三个随机变量 f_ 、 f 和 y 组成。我们不需要知道它们三个的联合概率密度,记为 p(f_,f,y)* 。在整篇文章中,这个联合分布 p(f_,f,y)* 从未出现过。我们可以计算出我们需要的,也就是预测分布 p(f_|y)* ,而不需要 p(f_,f,y)。这是一件好事,因为 p(f_,f,y) = p(f_,f|y) p(y)* 我们知道边际可能性 p(y) 不是一个容易处理的量。我们以定义的方式定义了我们的模型,以确保我们不必触及联合分布 p(f_,f,y)* 。*
高斯分布形式的后验
为了在测试位置 X_ 进行预测,我们需要推导随机变量 f_* 在测试位置的后验 p(f_|y)* 。*

第(2)行逆向使用概率论中的链式法则来拆分联合分布。
线(3)在 p(f_|f,y)的条件下下降 y 。我们可以这样做,因为 GP prior 只提到了 f 和 f_* 。所以给定 f , f_* 独立于 y 。 y 在可能性中被介绍。*
线(4)用我们知道的变分分布 q(f) 代替我们不知道的后验 p(f|y) 。这是重要的一步:
- q(f) 与后验 p(f|y)具有相同的 API。还有,
- 在最小 KL 散度意义上,它的行为类似于后一种情况。
所以我们可以用 q(f) 代替 p(f|y) ,这就是变分推理的全部意义。**
在第(5)行,在积分符号内, p(f_)|f) 是通过对 GP 先验应用多变量高斯条件规则(此处为更多该规则的细节)而导出的条件分布:*

并且我们引入名称 A 和 B 来缩短公式,用:

A 和 B 都提到了训练位置 X 和测试位置 X_ 。它们建立了从 f 到 f_* 的连接。*
第(6)行应用多元高斯线性变换规则,通过仅使用来自q(f;μ,σ)不提 f 。
第(7)行将 f_ 的概率密度函数移到积分之外,因为它没有提到积分变量 f ,所以它可以被视为积分的常数。*
第(8)行将积分计算为 1,因为随机变量定义域上的概率密度函数的积分计算为 1。
结果是随机变量 f_ 在测试位置 X_的后验分布 p(f_|y)* 。我们也称之为预测分布。如果 f_* 是一维的就是一维高斯分布,如果 f_* 是多维的就是多元高斯分布。
预测分布
扩展了 A 和 B 的预测分布的完整公式为:

使用:

需要注意几件重要的事情:
首先,预测分布没有提到训练数据 Y 。您可能还记得在高斯过程回归模型(计算后验部分)中,预测分布的平均值是训练数据 Y. 的加权和,但是在我们的变分模型中,预测分布的平均值是来自变分分布 q 的平均值 μ 的加权和。在参数学习过程中, Y 的信息被完全吸收到 μ 的值中。
第二,预测分布提到了训练数据 X — 你可以看到 X 遍布在预测分布的均值和方差中。这是因为模型需要使用训练位置 X 和测试位置 X_之间的距离来计算变量 f(X) 和 f(X_) —* 之间的相关性 X 和 X_* 之间的距离越远,相关性越小,模型给予 f(X) 的权重越小,以预测 f(X_)* 的值*
最后,我们理解我们的模型如何能够做出预测,即使变分分布 q 具有平均场结构,在这种情况下,σ是对角矩阵。我们的模型使用相关性矩阵 k(X_,X) 将训练位置 X 处的随机变量 f 和测试位置 X_* 处的随机变量 f_* 联系在一起。通过条件概率 p(f_|f)* 引入相关性 k(X_,X);它不关心 f 中的单个随机变量是否相互独立。
伯努利分布形式的后验
在二元分类设置中,我们需要以伯努利分布的形式进行预测,而不是以高斯分布的形式。
为了构造伯努利后验概率,我们需要决定它的单个参数的值:

假设只有一个测试点,那么 X_ 是一个标量,而不是一个矢量。所以 f_* 是来自我们后验 p(f_|y)的一维高斯随机变量。以这种方式定义, g(f_) 是一个随机变量,但是伯努利分布需要一个标量作为其参数,因此我们计算 g(f_)* 相对于 f_😗 的期望值*

你会很高兴地意识到线(3)是一个积分,你可以用高斯求积来近似。
计算出 g_ 后, y_* 的伯努利概率密度为:*

或者相当于:

上述伯努利概率密度函数是我们对测试位置的最终预测。你可能会问,难道不是所有来自高斯过程模型的预测都带有不确定性度量吗?是的,伯努利分布确实有它的不确定性度量,继承自高斯后验分布。由于伯努利分布是离散分布,其不确定性被编码在概率参数 g_: 中*
- g_ 接近 1:模型确定事件 y_=1。**
- g_ 接近 0:模型确定事件 y_=0* 。*
- g_ 接近 0.5:模型不确定要预测哪个标签。*
记住 g_ 是一个导出量。它从后验的 p(f_|y)中得到它的值。*p(f _ * | y)*的均值和方差是决定 g_值的两个东西(连同测试位置 X_ )。因此,它们决定了伯努利预测的不确定性。
实验结果
我用这个代码创建了一个变分高斯过程模型。它使用了 gpflow ,这是一个用于高斯过程模型的 Python 库。
下图显示了模型在参数学习后对我们的训练数据 (X,Y) 的预测。

顶部子图显示了来自高斯后验 f(x) = p(f|y)的预测。预测的形式是均值和方差。所以上面的子图用黑色显示平均值,用青色显示 95%的置信区间。可以看到,对于标签为 1 的数据点, f(x) 值在 4 左右,对于标签为 0 的数据点, f(x) 值在-4 左右。
中间的子图用蓝色显示挤压函数 g(x) 。我们可以看到,对于标签为 1 的数据点,它的值接近 1,对于标签为 0 的数据点,它的值接近 0。
底部的子图用红色显示了我们的训练数据。
这个情节和本文开头的手绘情节很像。
结论
这里我总结了我们的变分高斯过程模型的主要步骤:
- 该模型使用多元高斯先验 p(f) ,并使用伯努利似然 p(y|f) 对二元观察值进行建模。
- 当可能性是非高斯的时候,贝叶斯规则会导致一个难以处理的后验 p(f|y) 。
- 使用变分分布 q(f) 来近似后验 p(f|y) 。我们通过要求 KL 散度 KL(q(f)||p(f|y)) 最小来定义一个好的近似。
- KL(q(f)||p(f|y)) 的公式也很难处理。所以我们使用 ELBO 公式作为 KL(q(f)||p(f|y)) 最小化任务的代理。我们需要最大化 ELBO。艾尔博的公式很容易理解。我们需要推导出 ELBO 的解析表达式,这是一个提到所有模型参数的表达式。这样,我们可以使用梯度下降来最大化关于模型参数的 ELBO 以找到那些模型参数的具体值。
- ELBO 由两项组成,似然项和 KL 项 KL(q(f)||p(f)) 。KL 项已经是解析的了。但是可能性项是一个很难计算的积分。
- 似然项是一个 n 维积分,可以分解成 n 维一维积分。高斯求积为每个一维积分的结果提供了一个解析近似。
- 梯度下降使 ELBO 的解析表达式最大化,以找到具体的模型参数值。
- 最后,我们指出,我们的模型包含的参数比训练数据点的数量多。这不是一个实用的模型。它为我们接下来将要学习的真正实用模型——稀疏和变分高斯过程模型做准备。
支持我
如果你喜欢我的故事,如果你考虑支持我,通过这个链接成为灵媒会员,我将不胜感激:https://jasonweiyi.medium.com/membership。
我会继续写这些故事。
参考
附录:可以用高斯求积来近似边际似然吗?
在这里,我给出的推导表明,贝叶斯规则的边际似然的高斯正交逼近需要 mⁿ 项。目标是让你相信使用高斯求积来近似边际似然项是不实际的。我建议你仔细阅读这些推导,因为它们是非常好的贝叶斯统计实践。
这又是贝叶斯法则:

我们之前说过,我们不能用这个规则来计算后验概率,因为分母的积分是很难处理的。现在让我们尝试使用高斯求积来近似这个积分。
可能性 p(y|f) 是 n 伯努利概率密度函数的乘积,如我们之前推导并在此再次显示的:

由于在 p(y|f) 之前没有 log ,我们不能使用 log 的属性将似然分解为n**logBernoulli 概率密度函数之和,每个函数针对单个数据点。这将导致高斯求积规则产生指数数量的项。要理解为什么,让我们继续分析。
让我们通过应用变量变化规则来操纵这种集成:

第(1)行是我们想要导出解析表达式的分母中的积分。被积函数是伯努利似然 p(y|f) 乘以 GP 先验 p(f) 。
第(2)行写出了 GP 先验的分布,它是一个均值为 0、协方差矩阵为 K 的多元高斯分布。
第(3)行通过引入新变量 u 并将原始积分变量 f 重写为 Lu ,其中 L 是协方差矩阵 K 的乔莱斯基分解的下三角矩阵,从而在积分中应用变量变化规则。并且 u 是具有 0 均值和恒等协方差矩阵 1 的 n 维标准高斯分布。 0 是长度为 n 的 0 向量, 1 是形状为 n×n 的单位矩阵。
第(4)行简化了第(3)行。我们需要更多关于如何导出这种简化的解释,但首先,我们验证重新定义为 Lu 的 f 仍然来自同一个高斯分布 𝒩(0,k)。由于 f 是从 u 的线性变换,我们用高斯线性变换规则把 f 的分布写成从 u 的分布,这是一个多维的标准正态分布 𝒩( 0 、 1 。

有关高斯线性变换规则的更多详情,请参见揭秘张量流时间序列:局部线性趋势,搜索“线性变换”。
现在我们已经验证了 f 仍然来自 GP prior。为了说明我们如何在上面的第(4)行得到积分,让我们用一个有 3 个数据点的例子。因此

扩展到:

为了应用变量变化积分规则,我们需要计算雅可比矩阵,然后计算雅可比矩阵的行列式:

以上所有推导都很简单。在第(4)行引入名称 a 、 b、和 c 以缩短公式,因此所有内容都在一行中。
这个推导表明,雅可比矩阵等于来自乔莱斯基分解的下三角矩阵 L 。所以 det(J(u)) = det(L) 。
现在让我们看看变量变化 f=Lu 对 GP 优先 p(f) 的影响:

第(2)行使用属性 det(K) = det(L)。这是来自乔莱斯基分解的一处不错的房产。
线路(3)插入 f = Lu 并使用同样来自乔莱斯基分解的属性:

第(4)行简化了公式。它将 det(L) 移出原来的分母,加上det(1),等于 1 。这是因为我们要构造单位多元高斯的公式。
第(5)行认为公式等于概率密度 u ,由 1/det(L) 缩放。而我们将愉快地认识到 1/det(L) 取消了来自det(J(u)】的 det(L) 。
在我们结束变量变化推导之前,我们需要验证变量变化运算前后的积分边界。
原始积分将 f 作为其积分变量。 f 是一个高斯随机变量的向量。所以 f 中的每个随机变量都有一个全实直线的定义域。我们应用变量变化规律后,积分变量变成 u ,也是一个多变量高斯随机变量,全实域。这就是为什么我们对导数中的边界是隐式的。
使用高斯求积
现在我们有了以下积分,我们希望使用高斯求积来给出近似其结果的解析表达式:

第(1)行是要近似的积分。第(2)行引入了 name h(u) = p(y|Lu) 来缩短下面推导的公式。
在我们的 3 个数据点示例中, u 是一个三维标准正态分布,因此上面是一个三维积分。但是高斯求积法则只适用于一维积分。
标准多元高斯 𝒩(u 的好处是: 0 , 1 ) 是所有的个体随机变量 u₁ , u₂ , u₃ 因为恒等式协方差矩阵 1 而相互独立。于是联合分发𝒩(u;* 0 , 1 ) 分解成:*

通过这种分解,我们有:

第(2)行将 u 扩展为单个随机变量及其概率密度函数。可能性 h( u₁ 、 u₂ 、 u₃) 是所有三个随机变量的函数。
第(3)行将关于随机变量 u₁ 的积分分成一对括号。因为我们将首先解决这个最内部的整合。
这就是:

第(2)行扩展了𝒩(u₁的概率密度函数;0,1) 。
第(3)行重新组织指数函数中的项,为下一行中变量的变化做准备。
第(4)行应用积分中的变量变化规则,新变量 t₁ 使得 u₁=√2 t₁.
第(5)行将公式简化为我们可以应用高斯求积规则的形式。
第(6)行应用高斯求积规则来近似具有 m 项的积分。
术语数量呈指数增长的原因
注意第(6)行,即使随机变量 u₁ 已经被高斯正交“积分”出来,函数 h 仍然包含随机变量 u₂ 和 u₃.这就是指数数量的术语的来源。因为现在我们必须应用高斯求积法则,一次又一次,一个接一个地积分随机变量。每次,我们积分出一个随机变量,接下来是 u₂的和 u₃的。每一次,被积函数变得越来越长。**
例如,对于相对于 u₂ 的集成,我们有:

由于双重求和,上述表达式有 m 项。
我们需要应用高斯求积规则三次来整合所有三个随机变量。之后,我们得到了整个边际似然项的最终解析表达式:

这个最终近似值有 m 项。
为了将其推广到具有 n 个训练点的数据集,我们将在近似边际似然项的分析表达式中有个 mⁿ* 个求积项。因此,术语的数量与数据点的数量成指数关系。它很快变得大得不切实际。这就是为什么我们不能使用高斯求积来近似贝叶斯规则中的边际似然项。*
贝叶斯多元高斯混合模型中的变分推断
变分推理是贝叶斯统计中的一种近似推理方法。给定一个模型,我们经常想要推断它的后验密度。然而,关于后验密度的精确推断需要通过所有不相关的参数进行边缘化,这通常在计算上是难以处理的。VI 的主要思想是使用简单分布来近似查询密度。为了简化问题,我们用一个分布来近似每个单独的目标参数,并假设我们用于近似的分布是独立的。这种方法叫做平均场近似法。

等式 1
说到近似,我们自然会想到 KL 散度,它衡量两个分布之间的相似性。但是 KL 散度本身就包含了我们要推断的后验密度。因此,这又是一个棘手的问题。然后我们介绍证据下界(ELBO)。可以证明 KL 散度的最小化等价于 ELBO 的最大化。

等式 2
现在优化是可能的。我们所做的是,我们遵循与期望最大化相似的程序( EM ),一次更新一个近似分布。原来更新函数的表达式相当简单,如下图所示。

等式 3
注意,z 是我们想要近似其分布的参数。
上面提到的方程是我们进行变分推理所需要的。让我们看看它们在贝叶斯多元高斯混合模型中的具体应用。该模型的图示如下所示。

每个节点的分布如下:

这个模型的直觉是,观察值或数据是从高斯分布生成的。这个高斯分布的参数由另外两个分布控制。首先,我们有 k 个μ选择,它也是由高斯分布产生的。其次,我们有一个分类分布,它决定 k 个生成的μ中的哪个均值负责生成数据。概括地说,生成数据的分布是多元的,这意味着我们的观察值 x 不止一个维度。我们用 p 来表示数据的维数。
这里的异议是,我们想用平均场近似来近似 p(μ,c)。

注意,根据概率图形理论,这里的μ和 c 不是独立的,因为数据是观察到的。然而,当我们设计近似函数时,我们可以使我们的近似分布相互独立。如你所见,这里的第一件事是找到所有 q 分布的形式。虽然我们可以使用任何我们想要的发行版,但是我们希望尽可能地简化它们。自然,我们希望使用与原始参数相同的分布(实际上,为了数学上的方便,我们希望先验与后验共轭。但这里不讨论。).所以,我们把近似分布设计成,


因此,我们的工作变成了优化 mk、sk 和ϕi.
让我们看看我们的 ELBO 长什么样

对于 ELBO 中的每一项,我们可以导出一个封闭形式。
第一期

因为我们假设,

所以,

第二学期

第三期
因为

所以,

第四项

期限 5

对 ELBO 的上述计算表明它是易处理的,并且可以通过监视它是否在增加来跟踪参数的更新。
现在我们使用等式 3 来导出参数的更新函数。
对于 q(c) ,

通过使用来自图形模型的联合分布,

所以,

对于 q(μ) ,

而且,

所以,

因为

所以,

还有,

然后,我们有了所有近似参数的更新函数。
我们可以在数据维数为 2 的情况下测试我们的理论。结果如下所示。



vcpkg:用于机器学习的 C++包管理器
使用 vcpkg 安装和使用 C++ ML 库

使用 TensorFlow C API 的控制台应用程序
Vcpkg 是微软提供的一个很棒的 C++包管理器。它极大地简化了 Windows、Linux 和 macOS 上第三方库的获取和安装。
要在您的 C++项目中使用它,您首先需要将存储库克隆到您的本地机器上。仓库位于这里。克隆时,选择一个在路径上容易找到的文件夹很重要。类似于 C:\dev\vcpkg 的东西。在命令提示符下,您可以这样做:
git clone [https://github.com/microsoft/vcpkg.git](https://github.com/microsoft/vcpkg.git)
将存储库克隆到本地文件夹后,转到该文件夹并发出以下命令来构建可执行文件(vcpkg.exe):
.\bootstrap-vcpkg.bat
现在您已经构建了可执行文件,您可以使用它将 vcpkg 集成到您的构建环境中(如 Visual Studio 2019)。从命令行执行以下操作:
.\vcpkg integrate install
假设您想在您的计算机上安装 OpenCV4,这样您就可以将它集成到您的 C++程序中。你会这样做:
.\vcpkg install opencv4 --triplet x64-windows
三元组描述了您希望为其安装软件包的架构和环境。如果这是您安装的第一个软件包,vcpkg 将下载并安装一个兼容的 cmake,然后使用它来构建软件包。一旦完成,它将计算出 opencv4 的所有依赖项,并首先构建它们。最后,它将构建 opencv4 包并将其存储在包缓存中。一旦你安装了一个包,你就可以在你的 c++项目中使用它。
创建自己的包
创建自己的包非常简单。我在这里举一个例子,我用 TensorFlow 团队创建的预打包文件为 TensforFlow C API(见 https://www.tensorflow.org/install/lang_c)创建了一个。在命令提示符下,执行以下操作:
.\vcpkg create tensorflow-c-gpu https://storage.googleapis.com/tensorflow/libtensorflow/libtensorflow-gpu-windows-x86_64-2.3.0.zip libtensorflow-gpu-windows-x86_64-2.3.0.zip
在这里,您已经将您的包命名为 tensorflow-c-gpu ,并告诉它从哪里下载 zip 文件,以及您将在本地将其命名为什么。发出这个命令将创建一个 cmake 文件,用于构建您的包。在我们的示例中,该文件将是ports \ tensor flow-c-GPU \ tensor flow-c-GPU . cmake。每个包还需要一个名为 vcpkg.json 的元数据文件。要开始编辑这些文件,您可以从命令提示符发出以下命令:
.\vcpkg edit tensorflow-c-gpu
对于我们的例子来说, vcpkg.json 文件应该是这样的:
您还应该编辑创建的 cmake 文件,以使用下载的 zip 文件,并将它放在正确的位置。下面是我的 tensorflow-c-gpu.cmake 文件的样子:
从这个文件中可以看出,这个包只支持 Windows 上的 x64 版本。但这就是我现在所需要的🙂
使用包
让我们创建一个使用这个包的控制台应用程序。在 Visual Studio 中创建一个 C++控制台应用程序。我把我的叫做 TfConsole 。我将在本文末尾包含一个源代码链接。
一旦你创建了这个应用程序,添加一个名为 vcpkg.json 的新文件。这将描述应用程序及其所有依赖项:
你可以看到我们已经指定这个项目需要我们之前定义和安装的 tensorflow-c-gpu 项目。
您需要更改项目的构建属性,以包含此清单文件:

TfConsole 生成属性
将使用 Vcpkg 清单选项更改为是,如此处所示。
如果您在配置属性中没有看到 Vcpkg 选项,这意味着您还没有运行前面显示的“integrate install”命令。
您还需要添加 tensorflow.lib 库作为链接器的输入:

链接器属性
在 main.cpp 中,我们将对 tensorflow C API 进行简单的包含,然后使用它来显示 tensorflow 版本号:
需要注意的一点是,该包包含 TensorFlow 2.3.0 的 GPU 版本,因此您必须在您的机器上安装 CUDA 10.1。
如果一切顺利,您将在控制台输出中看到版本号。应该是 2.3.0。

控制台应用程序正在运行
结论
我认为微软的 vcpkg 管理器是一个很棒的包管理器,越来越多的包被添加到它里面。我还向您展示了如何添加您自己的,一旦您知道自己在做什么,这可能会非常简单。我正计划写一系列关于在 Windows 环境下生产中使用机器学习模型的文章。它将涵盖一个 Windows 前端(这将是一个 WPF C#桌面应用程序)使用各种不同的后端。其中一个后端将使用 tensor flow C API——由于没有相关的文档和很少的使用示例,它被证明是非常核心的!但是它也非常快(它的推理速度比我的 Python 代码还要快)。
观看此空间…
资源
- https://docs.microsoft.com/en-us/cpp/build/vcpkg?view=vs-2019
- VCP kg GitHub:https://github.com/Microsoft/vcpkg
- TF console:https://github.com/ianormy/TfConsole
预测时间序列的向量自回归
VAR 捕捉发展和相互依赖性
包含多个时间序列的经济计量模型

作者图片
https://sarit-maitra.medium.com/membership
V 向量自回归(VAR)时间序列模型在计量经济预测模型中有着广泛的应用;VAR 可以捕捉多个时间序列之间的演化和相互依赖关系。VAR 中的所有变量都是对称处理的,每个变量都包含一个方程,根据其自身的滞后和模型中所有其他变量的滞后来解释其演变。我们可以称之为交易策略的科学方法。
使用科学方法进行交易策略设计的优势在于,如果策略在前一段盈利期后失败,则有可能重新审视最初的假设并重新评估它,从而潜在地产生新的假设,使策略重新盈利。
布伦特原油有四个时间序列。美国和欧洲价格,西德克萨斯原油价格和石油输出国组织原油价格。我们将开发一个回归模型,并尝试预测布伦特原油,美国价格给定的所有系列在我们的模型。

plt.plot(df['opec'], label="opec")
plt.plot(df['euro'], label="Euro")
plt.plot(df['br_crude'], label="Brent Crude Oil")
plt.plot(df['wti'], label="West Texas")plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
plt.title('Opec Oil, Europe Brent Crude, Brent Crude Oil & West Texas Price over Time')
plt.show()

我们可以看到,所有的序列都遵循一个随机趋势,表现出更强的跨时变化和更大的下降;更重要的是,所有的系列似乎都有某种联系。这两个系列的水平看起来都不稳定。它们似乎有一个共同的趋势,表明它们可能是共同整合的。
IQR 绘制所有变量:
plt.title('IQR plot for all variables')
sns.boxplot(x=df)
plt.show()

我们可以在数据中看到一个小的异常值,可以忽略不计。
VAR 模型描述了平稳变量之间的动态相互关系。因此,时间序列分析的第一步应该是单位根检验,以确定序列是否平稳。
相关性检查:
pearsoncorr = df.corr(method='pearson')
sns.heatmap(pearsoncorr,
xticklabels=pearsoncorr.columns,
yticklabels=pearsoncorr.columns,
cmap='RdBu_r',
annot=True,
linewidth=0.5)
plt.show()

尽管相关性经常用于所有多变量金融时间序列用例,但是相关性可能非常不稳定。此外,ADF 或 Hurst 指数有助于我们从统计上确认该序列是否均值回复。然而,我们无法从这些测试中确定形成线性组合所需的套期保值比率,它们只会告诉我们,对于特定的情况,线性组合是否是稳定的。另一种安排是协整,这可能是两个金融序列之间联系的一个更稳健的措施。
增强型迪基-富勒(ADF):
ADF 检验表明,如果一个价格序列具有均值回复,那么下一个价格水平将与当前价格水平成比例。在数学上,ADF 是基于测试自回归时间序列样本中单位根的存在的思想。
def adfuller_test(series, signif=0.05, name='', verbose=False):
r = adfuller(series, autolag='AIC') output = {'test_statistic':round(r[0], 4), 'pvalue':
round(r[1], 4), 'n_lags':round(r[2], 4), 'n_obs':r[3]}
p_value = output['pvalue']
def adjust(val, length= 6):
return str(val).ljust(length)print(f'Augmented Dickey-Fuller Test on "{name}"', "\n ", '-'*47)
print(f'Null Hypothesis: Data has unit root. Non-Stationary.')
print(f'Significance Level = {signif}')
print(f'Test Statistic = {output["test_statistic"]}')
print(f'No. Lags Chosen = {output["n_lags"]}')for key,val in r[4].items():
print(f' Critical value {adjust(key)} = {round(val, 3)}') if p_value <= signif:
print(f" => P-Value = {p_value}. Rejecting Null Hypothesis.")
print(f" => Series is Stationary.")
else:
print(f" => P-Value = {p_value}. Weak evidence to reject the Null Hypothesis.")
print(f" => Series is Non-Stationary.")


没有一个统计数据接近 5%的显著水平。因此,我们可以自信地说,级数在水平上是非平稳的。研究了它们的协整关系,在每种情况下(p 值> 0.05),不能在任何合理的显著性水平上拒绝非平稳性的零假设。
协整检验:
协整检验过程中的步骤:
- 使用单变量单位根检验,比如 ADF、PP 检验,分别检验每个成分序列的单位根。
- 如果单位根不能被拒绝,那么下一步就是检验成分之间的协整,即检验αYt 是否为 I(0)。
如果我们发现时间序列是一个单位根,那么我们就进入协整过程。协整检验主要有三种方法:Johansen 方法、Engle-Granger 方法和 Phillips-Ouliaris 方法。我们将使用恩格尔-格兰杰检验。通过使用协整检验,我们将检验 wti 价格指数和 br_crude 价格指数之间是否存在协整的长期关系。数学上,我们将考虑协整关系:

Engle & Granger 的两步残差检验程序首先估计一个横截面回归,然后使用具有修正临界值的 ADF 分布检验该回归的残差。

这里记录了每个时间点的观测向量。在这种情况下,可能有一些构成平稳时间序列的向量的线性组合和其他非平稳的线性组合。

从上面的模型残差图来看,平均值为 0,这可能是一个趋势。

估计的协整向量非常接近[+1,1],表明存在简单的无套利关系。让我们用一个常数和一个时间趋势(“CT”)重新运行测试。我们在 5% (-3.79)和 10% ( 3.50)水平的临界值的测试统计为-4.231<;p 值< 0.05,我们可以拒绝无协整的零假设。


残差明显为零,但显示了当前年份(2020 年)的结构性突变的证据。

同样,我们测试了其他可能的配对组合,以获得类似的输出。
虽然 Engle-Granger 方法很容易使用,但是它的一个主要缺点是它只能估计变量之间的一个协整关系。
这里,我们有 1 个以上的变量,可能有一个以上的线性独立的协整关系。
p 值的重要性:
现实生活中商业案例的 p 值一直是争论的焦点。p 值在发表的文章上可能看起来不错,但它从来没有像今天这样被使用过。更多详情请阅读这篇有趣的 [文章](http://The P value was never meant to be used the way it's used today.) 。传统上, p- 值< 0.05 是显著性的标准。
OLS 回归:
现在,我们将使用 OLS 拟合时间序列之间的回归,并可视化残差图。如果残差是平稳的,我们可以有把握地假设,给定的序列确实是协整的。

residuals = results.resid
br_crude_fit = results.fittedvaluesfig = plt.figure(1)
ax1 = fig.add_subplot(111)
ax1.plot(br_crude_fit, label='Linear Prediction')
ax1.plot(df['br_crude'], label='Brent Crude-USA')
ax1.set_xlabel('Date')
ax1.legend(loc=0)
ax2 = plt.twinx()
ax2.set_ylabel('Residuals')
ax2.plot(residuals, label='Residuals')
ax2.legend(loc=0)
plt.grid(True)
plt.show()

残差的级别(红线),它看起来比原始序列(蓝线)更像一个平稳序列。在这里,我们可以看到,实际和拟合线是难以区分的。
现在让我们对残差序列进行 ADF 检验。我们使用 DFGLS 测试,并指定 12 个滞后作为 DFGLS 回归的最大滞后阶数。

滞后的数量表明最佳滞后长度为 7。我们的测试统计(–7.059)< Critical Values at 1%, 5% & 10% and null hypothesis of no cointegration can be rejected.

breus ch-Godfrey(BD)测试:
BD 比 DW 更通用,允许我们测试高阶自相关。

残差的分布看起来像一个钟形,尽管有一些可能导致偏斜的异常值。
Jarque-Bera 正态性检验:

χ2 检验的双尾 p 值等于零意味着我们接受残差的零假设总体上是正态分布的,即峰度和偏度都是正态分布的。
向量自动回归(VAR):
VAR 方法使用 AR 模型对每个时间序列的下一步进行建模。模型的符号包括将 AR(p)模型的阶数指定为 VAR 函数{VAR(p)}的参数。
让我们先来看看这个系列的第一个不同点,然后再试试 ADF。通常,如果水平时间序列不是平稳的,第一个差异将是。数据差异或多或少是计算时间序列从一个值到下一个值的变化量。
df = df [['br_crude', 'wti', 'opec', 'euro']]
nobs = 5
df_train, df_test = df[0:-nobs], df[-nobs:]print(df_train.shape)
print(df_test.shape)df_tr_diff = df_train.diff()
df_tr_diff.dropna(inplace=True)
print(df_tr_diff.head())df_tr_diff.plot(figsize=(10,6), linewidth=5, fontsize=20)
plt.show()
考虑到时间因素,为时间序列问题创建一个训练和测试集是很棘手的。在这个阶段任何错误的举动都会打乱这个系列的格局。创建测试集时应该考虑日期和时间值。

现在,我们对数据帧的第一差重复上述所有步骤。
# ADF Test on each column
for name, column in df_tr_diff.iteritems():
adfuller_test(column, name=column.name)
这里,我们看到所有的级数在一阶差分后都是平稳的。
风险值模型:
var_model = smt.VAR(df_tr_diff)
res = var_model.select_order(maxlags=10)
print(res.summary())

#Fit VAR model
var_model = VAR(endog=df_tr_diff)
var_model_fit = var_model.fit(maxlags=10)#Print a summary of the model results
var_model_fit.summary()
我们在模型总结中真正想要关注的是 br_crude 的等式,其中 br_crude 根据自身的滞后值和其他变量的滞后价格来估计其他价格。t 统计值越高,我们就越有可能拒绝 H0,两个变量之间也越有可能存在相关性。p 值也用于拒绝零假设。p 值越低,反对零假设的证据越强。
估计 VAR 的方程式并不需要很强的假设;然而,计算脉冲响应函数(IRF)或方差分解确实需要识别限制。一个典型的限制是假设一对变量之间的动态关系,例如,x 只滞后地影响 y,或者 x 在长期内不影响 y
让我们使用脉冲响应图,在不同的水平上,将一个变量的变化对其他变量的影响可视化。
# Impulse Response Analysis
irf = var_model_fit.irf(20)
irf.plot()
plt.show()

给定参数估计和 Engle Granger 测试结果,这里建立了序列之间的联系。可以看出,对冲击的响应在这里被捕获,并且它们几乎在第 10 个滞后之后消失。
预测误差差异分解(FEVD):
在 FEVD 曲线图的第一行中显示了对 br_crude 价格的冲击对其它价格和它们自身的其它价值的影响。在 br _ 原油价格(深黑色柱)的情况下,归因于自身冲击的误差百分比为 100%,因为 wti 系列解释了约 40%的回报变化;欧佩克约为 60%,欧元系列解释了约 70%的变化。

注:上述测试程序的概念取自 Brooks(2019);参见参考部分。
预测:
现在模型已经建立,让我们生成预测并与测试/验证集中的实际数据进行比较。
# Get the lag order
lag_order = var_model_fit.k_ar
print(lag_order)# Input data for forecasting
input_data = df_tr_diff.values[-lag_order:]
print(input_data)# forecasting
pred = var_model_fit.forecast(y=input_data, steps=nobs)
pred = (pd.DataFrame(pred, index=df_test.index, columns= df.columns + '_pred'))
print(pred)

反转变换:
def invert_transformation(df_tr_diff, pred):
forecast = pred.copy()
columns = df_train.columnsfor col in columns:
forecast[str(col)+'_pred'] = df_train[col].iloc[-1] +
forecast[str(col)+'_pred'].cumsum()
return forecastoutput = invert_transformation(df_tr_diff, pred)
output.loc[:, ['br_crude_pred']]

预测评估:
# forecast bias
forecast_errors = [combine['br_crude'][i]- combine['br_crude_pred'][i] for i in range(len(combine['br_crude']))]
bias = sum(forecast_errors) * 1.0/len(combine['br_crude'])
print('Bias: %f' % bias)# MAE
mae = mean_absolute_error(combine['br_crude'], combine['br_crude_pred'])
print('MAE: %f' % mae)# MSE & RMSE
mse = mean_squared_error(combine['br_crude'], combine['br_crude_pred'])
print('MSE: %f' % mse)
rmse = sqrt(mse)
print('RMSE: %f' % rmse)

实际与预测:
combine = pd.concat([df_test['br_crude'], output['br_crude_pred']], axis=1)
combine['accuracy'] = round(combine.apply(lambda row: row.br_crude_pred /
row.br_crude *100, axis = 1),2)combine['accuracy'] = pd.Series(["{0:.2f}%".format(val) for val in combine['accuracy']],index = combine.index)
combine = combine.assign(day_of_week = lambda x: x.index.day_name())
combine = combine.round(decimals=2)
combine = combine.reset_index()
combine

fig = go.Figure()
fig.add_trace(go.Scatter(x=combine['Date'], y=combine['br_crude'], name="Actual Crude price"))
fig.add_trace(go.Scatter(x=combine['Date'],y=combine['br_crude_pred'],name="Predicted crude price"))
fig.update_layout(
title="Actual vs Predicted Brent Crude Price",
xaxis_title="Date", yaxis_title="Price", font=dict(family="Courier New, monospace",size=18,color="#7f7f7f"))
fig.update_layout(autosize=False,width=1000,height=400,)
fig.update_layout(legend_orientation="h")
fig.show()

结论:
一旦我们有了数据的模型,分析如何评估它的质量是很重要的。第一种选择是使用残差。残差基本上是预测值和实际值之间的平方差。这可能是一个简单的选择来判断我们的模型的准确性。
残差与我们用来创建模型的数据量成比例。因此,不建议使用所有残差的和。一种选择是将残差除以瞬时数量乘以因变量的数量。
参考文献:
- 汉密尔顿法学博士,1994 年。时间序列分析。新泽西,普林斯顿大学出版社。
- 恩格尔,R.F .,格兰杰,C.W.J,1987。协整与误差修正:表示、估计与检验。计量经济学。55, 251–276.
- 布鲁克斯,C. (2019 年)。金融计量经济学导论。剑桥大学出版社。
重叠向量和向量投影
寻找共线向量并解决向量重叠(如果有的话)
向量是表示方向大小的非常强大的方法。它们是几何学的组成部分,广泛应用于各种学科,如物理学、计算机视觉和机器学习。有多种方法可以利用向量的不同性质得到重要的结果,在本文中,我们将介绍三个这样的结果。
1)确定两个向量是否面向同一方向。
2)检查投影到向量上的 3D 空间中的点是否位于向量内部。
3)检查两个向量是否相互重叠,如果是,是否有方法消除重叠?
以上所有的观察都是相互联系的,它们在最后一个观察中结合得很好。
在我们开始之前,让我们快速看一下向量点和叉积。
D ot 乘积:是指向同一方向的两个向量的大小的乘积或乘积。因为它是量值的乘积,所以得到的答案是标量。可以考虑用相同方向的矢量。
a . b = | a | * | b | * cos(θ)=(| a | * cos(θ)) | b | = ax * bx+ay * by*

表示点积的不同方法。(来源:mathisfun)
所有不同形式的写同一个点积表示沿同一方向的向量相乘。这里一个重要的观察是,对于垂直矢量,点积为零。
a . b = 0as cos(90)= 0。
它可以被认为是相同方向上的矢量幅度的乘法。
C 罗斯乘积:是两个矢量 a 和 b 的乘积,使得合成矢量垂直于 a 和 b 。从另一个角度来看,合成向量是由两个输入向量 a 和 b 定义的平面的法线。合成向量的大小将给出由 a 和 b 形成的平行四边形的面积。
a x b = | a | * | b | * sin(θ)=(| a | * sin(θ)) | b |)*

叉积的不同表示方法。(来源:mathisfun)
这里一个重要的观察是,对于面向相同方向的向量,叉积为零。
a×b = 0as sin(0)= 0。
C 罗斯乘积是两个矢量 a 和 b 的乘积,使得合成矢量垂直于 a 和b
现在我们有了矢量交叉和点积的基本知识,我们可以开始推导这些强大的结果。
1。确定两个向量是否面向同一个方向。
G 给定两个向量 a 和 b,如下图所示,用红色和蓝色表示,如果向量尾部之间的角度是锐角或在[0,90]或[0,-90]之间,则称向量方向相同。
这可以通过使用矢量点积很容易地找到。计算点积的角度是通过连接两个向量的尾部并取它们之间的角度得出的,如下图所示。
如前所见,矢量点积由 *a.b = |a| |b| * cos(θ)给出。 现在由于矢量的幅度总是正的,点积的符号由 cos(θ)控制。我们知道,在坐标系的第一或第四象限中,任何一个角度的余弦值在[0,90]或[0,-90]的范围内总是正的,因此,如果点积是 正的 ,我们可以说这些矢量都朝向 同一个方向 。同样,如果点积是 负 ,我们可以确凿地说,这些向量是朝向 相反方向 。

面向相反方向的向量,a)180°角 b)135°角。面向相似方向的向量,c)角度为 0,d)角度为 30。(来源:作者)
因此,
a . b ≥ 0:向量面向同一个方向。
a . b < 0:矢量朝向相反方向。
2)检查投影到向量上的 3D 空间中的点是否位于向量内部。
我们可以使用向量投影来检查一个点在投影到向量上时是否在向量内部。问题清楚的显示在下图中。我们将在后面讨论的一个非常有用的应用是寻找两个向量是否相互重叠。

a,b)投影点位于矢量内部。c,d)投影点位于向量之外。(来源:作者)
我们将使用矢量投影来解决这个问题。请看下图,我们可以看到点 P1 投影到矢量 BA 上形成矢量 P1'A。如果我们能找到|P1'A|的大小,我们就能找到矢量 P1'A,因为它的方向就是矢量 BA 的方向。现在:
BA = B-A; P1A = P1- A; cos(θ1) = |P1'A|/|P1A|;
P1A.BA = |P1A|*|BA|*cos(θ1); Hence **|P1'A| = P1A.BA / |BA|
P1'A = |P1'A| * BA / |BA|**(unit vector along BA)

描绘矢量投影(来源:作者)
现在我们有了大小|P1'A|我们可以简单地使用下面的条件来检查投影点 P1 '是否位于向量 BA 内。
投影点位于向量内部,如果:0 ≤ |P1'A| ≤ |BA|
上面的等式是正确的,因为如果投影长度|P1'A|大于向量|BA|本身的长度,则该点必须位于远离右侧的点 B 处,因此在向量之外。另一方面,如果投影长度小于零,则该点必须远离左侧的点 A。
我们知道 P1A。BA > 0 因为 P1A 和 BA 都面向同一个方向,所以我们可以肯定地说,点 P1 的投影落在向量 BA 上。
同样为点 P2, P2A。巴< 0 ,因为和巴面向相反的方向。因此,点 P2 将不能够满足 0 ≤ |P2'A| ≤ |BA|的条件,因此,点 P2 的投影将不位于向量 BA 内。
3.检查两个向量是否相互重叠,如果是,有没有办法消除重叠?
重叠向量可能有三种情况:
- B1F1 和 B2F2 部分重叠。图的 a 和 b 部分。
- B1F1 与 B2F2 完全重叠,反之亦然。图的 c 部分。
- B1F1 和 B2F2 不重叠。图的 d 部分。

a),b)部分重叠的向量,c)完全重叠的向量,d)不重叠的向量(来源:作者)
让我们把蓝色向量看作根向量,红色向量看作邻居向量。同样,由 F 表示的点是矢量的前端,B 中的点是后端。O1 和 O2 是投影到相邻向量上的根向量的前后点。
现在,为了检查重叠,我们可以使用我们在“检查投影到矢量上的 3D 空间中的点是否位于矢量内部”中导出的公式来检查根矢量的投影前点或后点是否位于相邻矢量上。利用这些信息,我们可以得出三个观察结果:
从投影点 O1 和 O2,
1.如果任一点位于相邻向量上,而另一点不在,则向量部分重叠。
2.如果点 O1 和 O2 都位于相邻向量上,则相邻线与根线完全重叠。
3.如果点 O1 和 O2 都不在相邻向量上,则向量不重叠。
因此,通过上面的观察,我们可以检查两个向量是否相互重叠。
既然我们知道了如何检查重叠向量,那么让我们来讨论一下是否有消除重叠的方法。

(来源:作者)
如果两个几乎共线的向量重叠,并且我们想要将它们合并成一个向量,这可能是有用的。这个过程的第一步是估计根向量和相邻向量上的重叠区域。两条红色虚线描绘了上图中的重叠区域。现在我们已经有了相邻线的重叠区域 O1F1,因为我们之前已经计算了点 O1。我们同样可以得到 O3,它是邻线的前点在根线上的投影。这样,我们已经隔离了两条线的重叠区域 O1F1 和 O3B2。
最后一步是将重叠的向量合并成一个向量。这里有两种可能的解决方案:
- 修剪根向量和相邻向量的重叠区域,并完整连接另一个向量。
- 将根向量和相邻向量分解成多个等距点,并在重叠区域上找到对应点的平均值。
我们总共涵盖了三个重要的观察结果:
1。使用矢量点积检查矢量是否面向同一方向。
2。求一个点在向量上的投影,看投影点是否在向量内。
3。检查重叠向量和消除重叠的方法。
如果你有任何问题,请联系我。
参考资料:
- https://www.quora.com/Why-do-we-sine-for-cross-products
- https://www.mathsisfun.com/algebra/vectors.html
向量空间模型
自然语言处理笔记
自然语言处理专业课程 1 第 3 周笔记

继续我们的自然语言处理笔记系列,你可能已经注意到我跳过了第 2 周。这不是偶然的,我意识到我已经对贝叶斯定理和朴素贝叶斯(下面的链接)做了相当多的笔记,因为这是从第 1 周到第 2 周所发生的所有变化(我们用来预测推文情绪的算法)。
以数据科学的方式学习概率
towardsdatascience.com](/marginal-joint-and-conditional-probabilities-explained-by-data-scientist-4225b28907a4) [## 从零开始的算法:朴素贝叶斯分类器
从头开始详述和构建朴素贝叶斯分类器
towardsdatascience.com](/algorithms-from-scratch-naive-bayes-classifier-8006cc691493)
什么是向量空间模型?
向量空间模型是代数模型,通常用于将文本(尽管它们可以表示任何对象)表示为标识符的向量。有了这些模型,我们能够识别各种文本在意义上是否相似,不管它们是否共享相同的单词。

图 1 :单词如何共享相似的单词但有不同的意思,反之亦然的例子(图片由作者提供)
这个想法是基于一位名叫约翰·鲁珀特·弗斯的英国语言学家(也是 20 世纪 50 年代英国语言学的领军人物)的名言…
“看一个人交什么样的朋友,你就可以知道一个字”——J·r·弗斯
在许多情况下,我们可能会决定采用矢量空间模型,例如:
- 信息过滤
- 信息检索
- 机器翻译
- 聊天机器人
还有更多!
一般来说,向量空间模型允许我们将单词和文档表示为向量。
逐字逐句&逐字逐句
为了用向量来表示我们的文本,我们可以决定使用逐词或逐文档的设计。执行这个任务包括首先创建一个共生矩阵。
虽然我们执行每项任务的方式非常相似,但我们将一次讨论一个设计,尽管如此,目标是相同的。我们想从我们的共生矩阵到一个向量表示。

图 2 :我们的共现矩阵到向量表示的映射(图片由作者提供)
逐字:此设计统计单词在一定距离内出现的次数 k.

图 3:k = 2 的逐字共现矩阵示例(作者提供的图片)
在逐字设计中,共现矩阵在 1 到 N 个条目之间。
Word By Doc :词汇中的单词在属于特定类别的文档中出现的次数。

图 4 :文档共现矩阵单词示例(图片由作者提供)
使用这些向量表示,我们现在可以在向量空间中表示我们的文本或文档。这是完美的,因为在向量空间中,我们可以确定文档类型之间的关系,比如它们的相似性。

图 5 :在向量空间中用 doc 表示单词(图片由作者提供)
欧几里得距离
我们可以用来确定两个向量彼此相距多远的相似性度量是欧几里德距离,它仅仅是连接两个向量的直线的长度。

图 6 :欧几里德距离公式(图片由作者提供)
让我们使用图 6 中的公式,使用来自图 5 的矢量表示来计算更相似的文档。

图 7:计算欧几里得距离(图片由作者提供)
结果告诉我们,经济和机器学习文档更相似,因为基于距离的度量优先考虑具有较低值的对象来检测相似性。也就是说,需要注意的是欧几里德距离不是比例不变的,通常建议对数据进行缩放。
余弦相似性
欧几里得距离的问题在于它会因表示中的大小差异而有偏差。因此,我们可以决定使用余弦相似度,这将确定使用内角的文本有多相似。

图 8 :余弦相似度公式(图片作者提供)
余弦相似性是自然语言处理中最常用的相似性度量之一。为了计算相似性,我们取两个向量之间的夹角的余弦相似性。

图 9 :计算余弦相似度(图片由作者提供)
当余弦值等于 0 时,这意味着两个向量彼此正交,并且不匹配。反之,余弦值越接近 1,则意味着这两个值之间的匹配度越高(因为角度越小)。因此,从我们的结果来看,经济和机器学习是最相似的——在维基百科上阅读更多关于余弦相似性度量的信息。
在向量空间中操纵单词
通过执行一些简单的矢量运算,我们能够推断出单词之间的未知表示。
例如,如果我们知道两个相似者如国王和人之间的关系。为了找到单词“Queen”的向量表示,我们可以将我们从确定 King 和 Man 之间的关系中检索到的向量表示(我们通过减去向量即 King - Man 来检索该向量)添加到 Woman 的向量表示,并且推断最相似的向量表示(在该实例中是 Queen)是我们想要找到的向量。

图 10 :视觉呈现(图片作者)
包裹
总之,我们可以使用向量空间模型来表示向量空间中的文本或文档,当我们的数据在向量空间中时,我们可以使用向量来确定文本(或文档)之间的关系。
让我们在 LinkedIn 上继续对话…
[## Kurtis Pykes -人工智能作家-走向数据科学| LinkedIn
在世界上最大的职业社区 LinkedIn 上查看 Kurtis Pykes 的个人资料。Kurtis 有一个工作列在他们的…
www.linkedin.com](https://www.linkedin.com/in/kurtispykes/)
Python 中的矢量化
PyTrix 系列
PyTrix#1:加速我们的 Python 代码
Python 是一种流行的数据科学语言。它的语法很容易学习(和阅读),这使得新手更容易掌握和使用这种语言。然而,不涉及细节,Python 是一种解释语言,这意味着它运行起来比像 c 这样的编译语言慢得多
当我们执行深度学习时,我们可能会使用大量数据,因为这是深度学习蓬勃发展的时候。

图 Coursera 上吴恩达深度学习专业化的照片
为什么我要说这些?很棒的问题!
如果我们有大量的数据和缓慢的 python 代码,我们很可能最终得到一个以蜗牛速度运行的模型,因为我们的代码在计算上不是最优的...人类对这场大灾难的解决方案是什么?矢量化!😎
什么是矢量化?
通俗地说,它加快了 Python 代码的速度,而不需要循环、索引等。在数据科学中,我们使用 Numpy 来完成这项工作——Numpy 是科学编程的事实框架。从技术上来说,当我们在 Numpy 中实现矢量化形式时,我们仍然执行这些操作,但不在 Python 中——在幕后。相反,这些操作是在优化的预编译 C 代码中完成的——参见文档了解更多信息。
这种用数组表达式替换显式循环的做法通常被称为向量化。一般来说,矢量化数组运算通常比纯 python 运算快一两个(或更多)数量级,在任何类型的数值计算中都有最大的影响”——McKinney,2012 年,第 97 页
纯 Python vs Numpy 示例
在这一节中,我将用 python 实现一些例子,然后用 numpy 实现相同的代码,并比较两者的计算时间,这样我们可以对矢量化有一个直观的了解。下面是我的 Github 库的代码链接。
此时您不能执行该操作。您已使用另一个标签页或窗口登录。您已在另一个选项卡中注销,或者…
github.com](https://github.com/kurtispykes/demo/tree/master/pytrix)
外部产品
两个向量的外积将产生一个矩阵。例如,如果我们有两个维度为n和m的向量,那么这两个向量的外积——见图 2。

图 2:外积公式【来源】
import numpy as np
import timea = np.arange(10000)
b = np.arange(10000)# pure Python outer product implementation
tic = time.process_time()outer_product = np.zeros((10000, 10000))
for i in range(len(a)):
for j in range(len(b)):
outer_product[i][j]= a[i] * b[j]toc = time.process_time()print("python_outer_product = "+ str(outer_product))
print("Time = "+str(1000*(toc - tic ))+"ms\n")# Numpy outer product implementation
n_tic = time.process_time()outer_product = np.outer(a, b)n_toc = time.process_time()print("numpy_outer_product = "+str(outer_product));
print("Time = "+str(1000*(n_toc - n_tic ))+"ms")
这个细胞输出…

图 3:上面代码块的输出
点积
也称为内积,点积接受两个长度相等的数字序列,并返回一个标量—参见图 4。

图 4:点积公式
import numpy as np
import timea = np.arange(10000000)
b = np.arange(10000000)# pure Python outer product implementation
tic = time.process_time()dot_product = 0
for i in range(len(a)):
dot_product += a[i] * b[i]toc = time.process_time()print("python_dot_product = "+ str(dot_product))
print("Time = "+str(1000*(toc - tic ))+"ms\n")# Numpy outer product implementation
n_tic = time.process_time()dot_product = np.dot(a, b)n_toc = time.process_time()print("numpy_dot_product = "+str(dot_product))
print("Time = "+str(1000*(n_toc - n_tic ))+"ms")
这个代码块的输出…

图 5:上面代码块的结果。
最终,矢量化不仅使我们的代码更快、更容易阅读,还减少了我们必须编写的代码量,这通常意味着我们得到的 bug 更少。最重要的是,我们编写的代码看起来更“Pythonic 化”,因为我们去掉了代码库中所有低效的、难以阅读的 for 循环。
注:如果这对你来说是全新的,我建议你观看下面链接的来自吴恩达的视频,这些视频来自 Coursera 上的深度学习专业,并开始掌握 Numpy 文档
图 6:深度学习专业化课程 1 中关于矢量化的吴恩达。
如果您看到此消息,这意味着我们在网站上加载外部资源时遇到了问题。如果你是…
www.khanacademy.org](https://www.khanacademy.org/math/linear-algebra/vectors-and-spaces)
向量化朱莉娅的一切
告别 for loops,广播所有的东西
你有没有过这样的感觉:for loops 正在接管你的生活,你无法逃离它们?你是否觉得被这些循环困住了?别害怕!有出路!我将向你展示如何在没有任何 for 循环的情况下完成 FizzBuzz 挑战。
向量化所有的东西!— 来源
FizzBuzz 的任务是打印 100 以内的每一个数字,但是要把能被 3 整除的数字换成“Fizz”,能被 5 整除的数字换成“Buzz”,能被 3 和 5 都整除的数字要换成“FizzBuzz”。
用 for 循环解决 FizzBuzz 很容易,你甚至可以在 BigQuery 中做这件事。在这里,我将向您展示另一种实现方式——没有任何 for 循环。解决方案是矢量化函数。
如果你已经有了一些 R 和 Python 的经验,你可能已经在标准 R 中或者通过 Python 的numpy库遇到了矢量化函数。让我们看看如何在 Julia 中类似地使用它们。
矢量化函数非常有用,因为它们减少了与 for 循环相关的混乱。
矢量化函数的第一步
在我们开始解决 FizzBuzz 之前,让我们看看如何在 Julia 中用矢量化替代方法替换非常简单的 for 循环。
让我们从一个琐碎的任务开始:给定一个向量 *a* 给它的每个元素加 1。
对于循环版本:
julia> print(a)[2, 3, 4]
矢量化版本:
上面的代码完成了工作,但是它占用了 3 行代码和很多多余的字符。如果a是 Python 中的一个numpy数组🐍,你只需要做a + 1,工作就完成了。但是首先,您必须将您的普通旧数组转换成一个numpy数组。
朱莉娅有一个聪明的解决办法。您可以使用广播操作符.对一个对象的所有元素应用一个操作——在本例中是加法。这就是它的作用:
这与上面的 for 循环给出了相同的答案。并且不需要转换你的数组。
更棒的是,你可以播放任何你喜欢的功能,甚至是你自己的。在这里,我们计算一个圆的面积,然后在我们的阵列中传播它:
是的,pi 是 Julia 中的内置常量!
julia> area_of_circle.(a)3-element Array{Float64,1}:
3.141592653589793
12.566370614359172
28.274333882308138
循环再见

回环拜拜!— 来源
现在我们知道了基本知识,让我们做 FizzBuzz 吧!但是记住,不允许 for 循环。
我们将稍微重新表述一下我们的问题。代替打印数字、嘶嘶声和嗡嗡声,我们将把它们作为一个向量一起返回。我将以与 for 循环文章[链接]中相同的方式分解解决方案,因此如果您还没有看到以前的帖子,现在将是查看它的好时机!
首先,让返回数字直到n作为一个向量:
在这里,collect 只是接受我们的 range 操作符,并对它进行数组求值。
julia> fizzbuzz(5)5-element Array{Int64,1}:
1
2
3
4
5
添加气泡
这个管用。让我们看看能否打印出每个能被 3 整除的数字的嘶嘶声。我们可以用 Fizz 字符串替换所有能被 3 整除的数字。
julia> fizzbuzz(7)7-element Array{String,1}:
"1"
"2"
"Fizz"
"4"
"5"
"Fizz"
"7"
让我们一步一步地分解它:
- 为什么我们把所有东西都换成了
string?这个数组就是一个数组。我们不希望数字和字符串混杂在一个对象中。 - 我们广播
rem.(numbers, 3来寻找所有数字的余数。 - 然后我们将这个余数数组与 0 (
.== 0)进行元素比较。 - 最后,我们用布尔掩码索引我们的字符串数组,并将“Fizz”赋给掩码为
true的每个元素。
请随意分解这些步骤,在你自己的朱莉娅·REPL 身上试试吧!
我知道使用.=将单个元素分配给多个元素可能有点争议,但我实际上很喜欢它。通过显式地指定赋值的广播,你强迫自己去思考这些对象的区别,然后每个阅读你的代码的人将会看到一个是向量,另一个是标量。
添加蜂鸣器
添加蜂鸣器的方法完全相同:
对此不予置评。😃
将这一切结合在一起
我们所缺少的是能被 3 和 5 整除的数字的嘶嘶声元素。
我们使用.*来“乘”两个布尔数组。在这种情况下,您可以将*视为布尔值and。所以这个给了我们一个真值,其中的数字可以被 3 和 5 整除。
让我们看看这一切是否行得通:
julia> fizzbuzz(16)16-element Array{String,1}:
"1"
"2"
"Fizz"
"4"
"Buzz"
"Fizz"
"7"
"8"
"Fizz"
"Buzz"
"11"
"Fizz"
"13"
"14"
"FizzBuzz"
"16"
该打扫了

稍微无关,但请永远清理你的狗了!—照片由哈里·格劳特在 Unsplash 拍摄
以上当然是我们想要的,但它并不是最漂亮的。是时候整理和让我们的代码变得漂亮和像样了。
这与上面的实现完全一样,但是它更容易阅读。我们找到想要替换的索引,然后在需要的地方替换数组元素。我们还使用了%而不是rem函数,因为如果你的函数有一个操作符,那么你应该使用它!
结论
读完这篇文章后,你现在知道如何使用.在 Julia 中传播任何函数,并通过删除一些不必要的 for 循环使你的代码更具可读性。
要获得所有媒体文章的完整访问权限,包括我的文章,请考虑在此订阅。
如何使用 FizzBuzz 执行循环和条件函数
towardsdatascience.com](/learning-julia-the-simplest-of-beginnings-464f590e5665) [## BigQuery 中的循环
了解如何使用 BigQuery 脚本来计算斐波那契数
towardsdatascience.com](/loops-in-bigquery-db137e128d2d)
向量化代码很重要
我来自 MATLAB 和数值计算的世界,在那里,for 循环被删除,向量为王。在 UVM 攻读博士学位期间,拉科巴教授的数值分析课是我上过的最具挑战性的课程之一,对数值代码的深入了解至今仍让我记忆犹新。我最喜欢的矢量化例子是,一位同事在写了一篇非常酷的关于它的论文,并在脚注中提到了所涉及的大量计算之后,与我分享了他的 Lorenz 96 代码。内循环的矢量化速度快了 4 倍,所以现在脚注只是一个碳足迹。
如今,快速数字代码让机器学习成为可能,尽管我不确定现在有多少孩子能用 C 语言编写一个 QR 分解。我是在开玩笑,因为我没有做过,但是我确信我可以用 MATLAB(在某一点上)或者 Numpy 或者 Julia(我将坚持只使用 R 语言中的 magrittr 和 dplyr)来编写它。我在 MassMutual 做的很多工作基本上都是数值计算,耗时几小时甚至几分钟的管道与耗时几秒钟的管道之间的差别是很大的。几秒钟意味着我们可以迭代,尝试更多的选项,并且行动更快。尽管如此,很多数字代码还是用纯 Python 写的(没有 Cython,没有 Numba),为了的灵活性。我要说这是个坏主意!这是一封转述自同事的电子邮件:
在伪代码中,这是我几个月前遇到的‘精算’编码困境:
*EOM = 0 for months in years: PREM = 50 BOM = EOM + PREM WIT = 5 EOM = BOM – WIT*一个简单的例子,但我认为显示了 BOM/EOM 的相互依赖性(还有一些其他变量具有类似的关系。)不了解 EOM 就无法矢量化 BOM,了解 BOM 就无法矢量化 EOM。那么你可能会遇到这样的情况,如果 WIT > 0,PREM = 0。基本上出现了许多相互依赖。现在很多函数都不容易向量化。
我可以把它矢量化,我做到了。以下是 Python 中的非矢量化版本:
import numpy as np
years = 10
bom = np.zeros(years*12)
eom = np.zeros(years*12)
for month in range(1, years*12):
prem = 50
bom[month] = eom[month-1] + prem
wit = 5
eom[month] = bom[month] - wit
这是矢量化版本:
import numpy as np
years = 10
prem = 50
wit = 5eom = np.arange(years*12)*prem - np.arange(years*12)*wit
# and if you still want bom as an array:
bom = eom + np.arange(years*12)*wit
我还通过使用一个字典列表更加灵活地编写了 for 循环:
years = 10
prem = 50
wit = 5
result = [{'bom': 0, 'eom': 0}]
for month in range(1, years*12):
inner = {}
inner.update({'bom': result[month-1]['eom'] + prem})
inner.update({'eom': inner['bom'] - wit})
result.append(inner)
上面这个返回一个不同类型的东西,一个字典列表…不是两个数组。
我们还可以导入 Pandas 来填充以上三个项目的结果(因此它们是一致的输出,我们可以保存到 excel,等等)。如果我们加载了 Pandas,我们可以使用一个空的数据帧进行迭代,因此多了一个选项:
import numpy as np
import pandas as pd
years = 10
prem = 50
wit = 5
df = pd.DataFrame(data={'bom': np.zeros(years*12), 'eom': np.zeros(years*12)})
for i, row in df.iterrows():
if i > 0:
row.bom = df.loc[i-1, 'eom']
row.eom = row.bom - wit
通过所有这些类型的迭代,以及返回数据帧作为结果的选项,我们得到了以下结果:

Cython 和 Numba
我还添加了几个 Cython 版本的代码,展示了通过使用 c,您可以在没有 numpy 的情况下获得矢量化的性能。)和速度。
Numba 也可以保持相同的加速比(它可能和 cy thon/矢量化 Numpy 一样快)。在这两种情况下(Cython/Numba ),您必须小心使用哪些数据类型(不要使用字典或熊猫!).我认为,如果你更聪明地知道如何整合它们,让 Cython + Numpy 循环和矢量化 Numpy 循环一样快是可能的。
最终,你可以鱼和熊掌兼得:可读的 for-loops(是的,矢量化版本可以变得更神秘),用 Python 编写,具有类似 C 语言的矢量化速度。
所有的代码,包括 Cython,都可以在这里找到:https://github.com/andyreagan/vectorizing-matters。
从卫星图像计算植被指数
利用卫星图像挖掘环境中的环境变化。

来源:谷歌地图
人工智能和机器学习彻底改变了我们今天的世界。从医疗保健到汽车,从体育到银行,几乎所有行业都意识到人工智能带来的力量,以提供更好的解决方案。卫星图像领域也在经历大规模发展,机器学习解决方案已经提供了一些积极的成果。ML 算法已经被证明是分析卫星图像的一个很好的工具。然而,访问数据有时会成为一个瓶颈。这主要是由于卫星图像的巨大规模,以及分析它们需要一些领域的专业知识。很多时候,来自其他领域的人也对访问和分析卫星数据感兴趣,特别是研究当地的模式,如一个国家的污染水平、洪水情况,甚至是森林火灾。这促使我寻找一些资源来简化这个过程,或者访问和分析卫星图像过程,这样我们就可以花更多的时间来理解结果并从中学习。
一年前,我写了一篇关于类似概念的文章,名为使用 Python 进行卫星图像分析,其中我们在卫星数据的帮助下检查了一个地区的植被覆盖。其目的是了解卫星图像数据的概念,以及如何对其进行分析以调查现实世界的环境和人道主义挑战。
然而,这个过程需要几个步骤,这一次我在寻找简化这个过程的方法。幸运的是,我遇到了一个名为 EarthAI Notebook 的平台,这是一个完全托管和管理的 JupyterLab 笔记本,专门用于分析栅格数据。它为我提供了使用熟悉的 Jupyter 实验室平台执行卫星图像分析的便利,完全加载了地理空间分析库和对地球按需目录的 API 访问。
在本文中,我将快速向您介绍计算该地区植被指数的步骤。该代码大量改编自 EarthAI 手册中的示例代码。
案例研究:计算孙德尔本斯的植被指数(NDVI)
孙德尔本斯是孟加拉湾的恒河、布拉马普特拉河和梅克纳河汇合形成的三角洲中的红树林区域。它的一部分位于孟加拉国(66%)和印度(34%),是世界上最大的单一潮汐盐生红树林。Sunderbans 具有巨大的经济和环境价值,已被联合国教科文组织宣布为世界遗产。

或归一化差异植被指数(NDVI)是最广泛使用的测量植被的指数之一。它是由美国宇航局科学家康普顿·塔克开发的,可以表达如下。****

其中 NIR 和 Red 分别代表来自植物的反射近红外光和反射可见红光。为什么只有这两种颜色?与其他波长相比,健康的植物会反射更多的近红外和绿光。然而,它吸收更多的红光。因此,NDVI 将植物反射的近红外光(NIR)与反射的可见红光(red)进行了比较。NDVI 值的范围从+1.0 到-1.0,其中 1 代表最健康的植被。

今天的孙德尔本斯已经受到环境干扰和人类活动的深刻影响,因此 NDVI 可以成为测量孙德尔本斯周围植被覆盖率并采取补救措施的有用工具。
设置系统
点击StartFree Trial选项,接下来的屏幕会出现。启动地球笔记本。你将获得 14 天的试用期,这应该足以让你开始。这可能需要几分钟来脱离熟悉的 Jupyter 实验室环境。

接下来,您可以导航到 examples 文件夹,在那里使用用例以及快速参考指南。我将使用quick-example-1.ipynb文件来熟悉这个平台。
导入库和创建火花会话
接下来,我们将从 EarthAI 导入必要的库和依赖项。这又创建了一个新的 SparkSession 。这包括earth_on_demand、rasterframes和pyspark
from earthai.init import *
import pyspark.sql.functions as F
import matplotlib.pyplot as plt
%matplotlib inline
RasterFrames 是一个地理空间开源栅格处理库,用于 Python、Scala 和 SQL ,可通过几种机制获得。RasterFrames 为任意地理空间栅格数据提供了以数据帧为中心的视图,支持时空查询、地图代数栅格操作以及与 Spark ML 的互操作性

查询和加载卫星影像
我们现在需要导入卫星图像数据。我们将使用earth_ondemand模块来查询覆盖桑德邦斯国家公园特定区域的 MODIS 图像。在继续之前,我们先了解一下 MODIS 是什么。
MODIS 代表 中分辨率成像光谱仪(MODIS)。它是Terra(原名 EOS AM-1)和Aqua(原名 EOS PM-1)卫星上的关键仪器。Terra MODIS 和 Aqua MODIS 每 1 到 2 天观察一次整个地球表面,获取 36 个光谱带或波长组的数据(来源:https://modis.gsfc.nasa.gov/)
变量 catalog 返回由指定时间和位置的卫星图像组成的数据帧。我们来看看目录的一些基本信息。

这里的B01和B02指的是红色和近红外(NIR)波段。
波段(也称为通道或层)可以被认为是在同一地点同时拍摄的图像的集合。这篇名为 遥感简介 的文章非常清晰地解释了波段的概念:
地球观测卫星上的许多传感器测量从地球表面反射或发射的电磁辐射(EMR)量。这些传感器被称为多光谱传感器,可以同时测量电磁波谱的多个区域的数据。传感器测量的波长范围称为波段,通常用名称(例如红光或近红外)和所记录能量的波长来描述。
这些列提供了到单波段地理 IFFs 的直接链接。地理信息系统使用 GeoTIFF 和其他格式来组织和存储栅格数据集,如卫星影像。
让我们看看上面目录中的一些关键列。
catalog[['id','eod_collection_attribution', 'datetime', 'eo_cloud_cover', 'proj_crs', 'B01', 'B02']].head(5)

从目录加载影像
现在,是时候加载实际的卫星图像了。我们将从上面指定的场景列表中读入红色(B0)** 和红外(B1) 波段。**

计算 NDVI
df = df.withColumn('ndvi', rf_normalized_difference('nir', 'red'))
计算每周整个图像的 NDVI 平均值
最后,我们将计算一个非常简单的解析。我们将计算数据帧中每周整个图像的 NDVI 平均值。time_series是一个火花数据帧。toPandas调用触发计算,结果在熊猫数据帧中返回。这使我们能够很容易地将时间序列可视化。

这里我们得到了特定时间段的 NDVI。我选择了一个非常小的时间窗口来加速这个过程,但是整个过程保持不变,即使我们需要计算更长时间的 NDVI。事实上,这将提供一个关于生态平衡和气候变化对一个地方的影响的更好的想法。
结论
卫星图像分析是数据科学家工具箱中的必备工具,挖掘出的信息可用于调查现实世界的环境和人道主义挑战。我希望这篇文章能给你一个良好的开端,让你开始用工具和卫星数据进行一些有价值的分析。
附注:这不是 EarthAI 的促销文章。还有其他几个工具可以做同样的分析,但是我对这个平台的体验是积极的,所以我想分享我的经验。
天啊!
深入了解不同 python 环境的初学者指南,每种环境的优点,以及如何开始使用它们

由 Oleksii Hlembotskyi 在 Unsplash 拍摄的照片
当开发人员开始研究 python 环境以及如何清理他们的工作流时,他们会受到各种不同选项的轰炸。如此庞大的菜单自然会导致开发人员不必要地筛选文章和文档,以找到“最好”的一个来使用。在本文中,我们将讨论每个主要虚拟环境选项之间的差异和优势,以便将所有这些参考整合到一篇文章中。最后,希望你能找到最适合你需求的环境!
在我们开始之前,如果您不知道什么是虚拟环境或者为什么您应该使用虚拟环境,请随意停下来看看我的上一篇文章强调了虚拟环境给您的整体工作流程带来的好处。
首先,我强烈建议用户在 Python 3.3+之后避免使用。正如您将看到的, venv 现在是一个标准的附带库,总体来说更不容易出错。
VENV
Venv 创建沙箱化的、全新的、用户可安装库的、多 python 安全的虚拟环境。

布莱恩·多在 Unsplash 上的照片
Fresh要求环境只从 python 附带的标准库开始;这意味着当环境处于活动状态时,您将不得不用pip install重新安装您需要的其他库。

S 加框 表示环境中的任何安装或活动都不会影响基本系统。换句话说,你可以假设炸毁你的整个虚拟环境,放火烧了它,并最终把它扔进垃圾桶,删除整个东西,而不必担心弄乱你的基本 python 安装。

Thomas de LUZE 在 Unsplash 上拍摄的照片
U 如果这没有意义,这仅仅意味着您不需要sudo许可就可以将库安装到虚拟 env 上。

Multi-python safe最后,是 venv 带到桌面上的另一大特色。当您激活虚拟环境时,外壳只能看到用于构建该环境的 python 版本。例如,对于使用 mac 和 python 2.7 并希望使用 python 3.5 运行程序的个人而言,不需要指定您希望运行特定文件/程序的 python 版本(即python3 *file.py* 与python *file.py*),因为环境中只有一个安装。简单地做python *filename*将运行虚拟 env python 版本的软件。
VENV 用法
为了使用 venv,您只需要在项目目录中输入这个简单的命令(假设您已经安装了 python):
$ python3 -m venv env
在本例中,我们刚刚在我们的项目文件夹中创建了一个名为“ env ”的 python 3 环境——您可以随意更改名称。这个新的 env 文件夹包含三个子文件夹;虽然这不重要,但出于好奇,这里列出了每一个包含的内容:
- bin :与虚拟环境交互的文件
- 包含编译 python 包的 : C 头文件
- lib:python 版本的副本,以及安装每个依赖项的 site-packages 文件夹
为了使用该环境的资源,您需要使用以下命令来“激活”它:
$ source env/bin/activate
您现在应该在命令行的开头看到一个 (env) 。还可以通过使用函数来确保您使用的 python 版本是环境的版本:
$ which python
并分析它提供的路径和 python 版本。在环境中完成后,您只需输入:
$ deactivate
回到您的“系统”并退出虚拟环境。就这样,简单!
每当您想要使用您的环境时,一直键入source …/activate可能会很烦人,但是不要担心——阅读本文末尾的奖励部分以查看解决方案!
PYENV

兰迪·法特在 Unsplash 上拍摄的照片
Pyenv主要用于隔离你机器内的 python 版本。例如,如果您想在 Python 2.7、3.6、3.7 等版本上测试您的代码。pyenv 将提供一种跨所有版本测试你的软件的方法。
该环境通过在 PATH 环境变量前面加上~/.pyenv/shims来工作。在高层次上,这几乎只是允许不同的“隧道”供您的代码通过,以便在特定的 python 版本上运行。
Pyenv 还使用命令pyenv install *version* 使多个 Python 版本的下载和安装变得更加容易。
Pyenv 与 venv 非常相似,它允许您管理机器中的多个 python 版本。然而,它不包括回滚库安装的能力,并且可能需要管理员权限来更新库。由于这些争论,我确实认为 venv 更好一点。同样,windows 用户也无法使用该选项。
下一个!
PIPENV

照片由 Cyril Saulnier 在 Unsplash 上拍摄
P ipenv 是一个类固醇上的venv:它力求将pipfile、pip和venv组合成一个单一的命令。
您可以简单地使用pip install pipenv安装 pipenv,然后使用pipenv env开始创建环境。但是,您必须使用命令pipenv(而不是pip)来安装您的所有软件包。pipenv命令允许您:
- 指定将软件包安装到哪个环境中
- 直接与 PyPi 或本地存储库集成
- 为每个环境创建一个具有单独部分的 pipfile(这是每个环境需要的
requirements.txt文件的替代文件,带有virtualenv和venv - 允许您
pipenv lock您的虚拟环境;这创建了一个pipfile.lock文件,它解决了构建所需的所有依赖关系

pipenv 带来的最大优势,在我看来,是与requirements.txt和pip freeze相比,它如何处理依赖管理。
Pipenv 由于上面列出的优点正被越来越多的开发人员所采用,并且正迅速获得关注,所以我确实建议看一看它。
然而,如果你不知道这意味着什么,并且上面的列表项目你都没有想到,我建议你坚持使用 venv 来获得一个更简单和更直接的环境。
DIRENV(奖金!!)
如果玩虚拟环境,我强烈建议你使用direnv。当你cd进入一个包含.env的目录时,direnv 会自动激活环境。再也不需要处理那些烦人的source .../activate事务了。
结论

由斯坦尼斯拉夫·康德拉蒂耶夫在 Unsplash 拍摄
总的来说,我们研究了开发人员在工作流程中使用的三种最流行的虚拟环境选项。根据你的项目范围,我最终建议两种选择: venv 和 pipenv 。如果你不需要 pipenv 带来的所有花哨功能,我建议你去看看 venv。另一方面,如果 pipenv 列表对你不利,那就继续使用它吧!
和往常一样,如果你想对一个话题有更多的澄清或者对任何事情感到困惑,请随时留下你的想法,我可以编辑甚至写一篇新文章!
验证和处理线性回归的假设
线性回归依赖于五个主要假设。能够核实并据此采取行动尤其重要。
线性回归可能是数据科学中最重要的模型。尽管表面上很简单,但它依赖于几个关键的假设(线性、同异方差、不存在多重共线性、误差的独立性和正态性)。对这些的良好了解对于创建和改进您的模型至关重要。在这篇文章中,我将回顾五个主要假设,并解释它们的重要性以及一些解决方案,以防它们得不到尊重。
1。线性关系
显而易见,线性回归假设因变量和预测变量之间存在线性关系。
怎么能验证呢?
成对散点图可能有助于验证线性假设,因为很容易在图上显示线性关系。

在上面的例子中,两个变量之间的关系显然不是线性的
此外,类似地,代表预测值和因变量之间关系的部分残差图,同时考虑所有其他变量,可能有助于可视化变量之间的“关系的真实性质”。

部分剩余图背后的公式
如果不被尊重,对模特来说意味着什么?
如果不考虑线性,回归将会不合适,并且不能准确地模拟因变量和自变量之间的关系。
有什么办法呢?
自变量和因变量可以转换,因此它们之间的关系是线性的。例如,您可能会发现因变量的对数和一些自变量的平方之间的关系是线性的(参见多项式回归和广义可加模型 (GAM)对此进行了有趣的概括)。
2。同质性
同方差意味着无论因变量的水平如何,残差都具有恒定的方差。
如何验证?
为了验证同方差性,可以查看残差图,并验证误差项的方差在因变量的值上是恒定的。

在上面的例子中,存在异方差,因为残差的方差不是常数
如果不被尊重,对模特来说意味着什么?
在异方差的情况下,模型将不会同样适合模型的所有部分,这将导致有偏差的预测。这也通常意味着混淆变量(重要的预测因素)被忽略了(这也可能是因为线性假设没有得到尊重)。虽然对于数据科学的预测环境,这可能不是最重要的,但异方差在推断环境中相对更重要,因为系数的可解释性。
能做些什么?
由于异方差通常反映了混杂变量的缺乏,因此可以通过检查预测值和提供额外的独立变量来解决(甚至可以检查线性假设是否得到了尊重)。
3.多重共线性缺失
多重共线性是指两个或两个以上的自变量高度相关(极端情况下甚至是冗余的)。虽然它对于非参数方法可能不重要,但对于线性回归等参数模型来说却是最基本的。
怎么能验证呢?
通常,多重共线性的一个明显迹象是,一些估计系数具有“错误”符号(即,在试图预测房价的模型中,与 a 的大小相关的系数为负)。
成对相关可能是识别各种独立变量之间潜在关系的第一步。

相关性热图可以允许快速注意到成对的相关性
然而,一个更彻底的方法是看看方差通货膨胀因素(VIF)。它的计算方法是将每个独立变量回归到所有其他变量上,并计算得分,如下所示:

计算独立变量 VIF 的公式
因此,如果自变量和其他变量之间存在线性关系,这将意味着回归的大的 R 平方,从而更大的 VIF。根据经验,VIFs 分数高于 5 通常是多重共线性的指标(高于 10 肯定会被认为是一个问题)。
如果不被尊重,对模特来说意味着什么?
该模型可能产生不准确的系数估计,因此无法解释。因此,这可能会损害推理能力和可能的预测性能。
在多重共线性存在的情况下,回归结果也可能变得不稳定,并且会因训练数据的不同而有很大差异。
有什么办法呢?
多重共线性可以通过执行特征选择来修复:删除一个或多个独立变量。
一种常见的方法是使用后向子集回归:首先用所有潜在的独立变量构建一个回归,并使用特定领域的知识迭代地删除具有高 VIF 的变量。
另一种方法可以是隔离并只保留多个独立变量之间的交互影响(通常使用直觉或正则化)。
随着多重共线性的减少,模型将变得更加稳定,系数的可解释性将得到改善。
4.残差的独立性(没有自相关)
自相关指的是观测误差相关的事实。
如何验证?
为了验证观察值不是自相关的,我们可以使用德宾-沃森测试。测试将输出 0 到 4 之间的值。越接近 2,各变量之间的自相关性越小(0–2:正自相关,2–4:负自相关)。
如果不被尊重,对模特来说意味着什么?
自相关可能意味着没有考虑关系的线性,或者变量可能被忽略。
自相关会导致自变量和因变量之间的虚假关系。
有什么办法呢?
对于时间序列,可以增加一个滞后变量。解决这一问题的另一个潜在方法是将变量从绝对值修改为相对变化(即,不是股票价格,而是从一个时期到下一个时期的变化百分比)。
更一般地说,变量应该进一步微调并添加到模型中。
5。误差的正态性
如果残差不是正态分布,普通最小二乘法(OLS)以及回归可能会有偏差。
如何验证?
要验证误差的正态性,一个简单的方法是绘制残差相对于因变量水平的分布。人们可以使用 QQ 图来测量残差与正态分布的偏差。如果生成的曲线不正常(即偏斜),可能会突出问题。
如果不被尊重,对模特来说意味着什么?
如果没有考虑到这一点,它可能会突出显示大的异常值的存在,或者突出显示违反的其他假设(即线性、同方差)。因此,用标准方法计算 t 统计量和置信区间会有偏差。
能做些什么?
在误差不是正态分布的情况下,可以验证是否考虑了其他假设(即同质性、线性),因为这通常是这种违规的迹象,并相应地微调模型。
否则,还应该尝试处理数据中的大异常值,并检查数据是否可能是使用不同模型的单独子集。
除了上面列举的众多假设之外,验证您的线性回归没有外推超出训练数据的范围,并且没有异常值或单个记录可能会扭曲回归或对回归产生太大影响(参见 Cook 的距离和检测异常值的经验法则)也非常重要
正如这篇文章所强调的,尽管线性回归看起来很简单,但它依赖于许多假设。在构建模型时,重要的是要验证它们是否得到遵守,并在出现潜在违规时加以解决。我希望本指南能帮助你更好地理解线性回归背后的各种假设,并为你提供解决使用时可能面临的潜在问题所需的工具。
PS:我现在是伯克利的工程硕士,我还在学习这方面的知识。如果有什么需要改正或不清楚的地方,请告诉我。你也可以发邮件给我 这里 。
用于语音助手和对话式人工智能的印度语堆栈

(图片由俚语实验室提供)
对话人工智能
巴拉特 Bhāṣā堆栈的案例
Bhārat Bhāṣā Stack 将像 India Stack 为 FinTech 所做的那样,为印度方言催化语音助手和对话式人工智能创新。
十年前,这是不可想象的。
在印度一个不起眼的小镇上,人们可以通过手机扫描挂在购物车上的二维码,向街头小贩付款。即使金额只有 50 卢比(不到一美元)。
将会有很多来自银行和非银行的移动应用和支付钱包。全部无缝互操作。任何两方将通过共享一个类似钱包地址的电子邮件进行交易。无需支付任何交易费用。
无数小企业会在 WhatsApp 上发送目录。送货上门。在家门口接受数字支付。而无需建立网站或支付网关。
十年前,现金是王道。
十年前,这是不可想象的。
但它发生了。感谢印栈。用于认证、支付和授权的数字基础设施。始于 2009 年。
India Stack 是一组 API,允许政府、企业、初创公司和开发者利用独特的数字基础设施来解决印度在无出席、无纸化和无现金服务交付方面的难题。

图片:合众国际社标志
统一支付接口 (UPI)是无现金即时支付的核心。UPI 成为小型企业的数字支付网关。它消除了一个只有大企业才能负担得起的进入壁垒。印度堆栈催化金融科技创新。
Bhārat Bhāṣā栈可以为 印度语 tech 做印度栈为 FinTech 做的事情。
现在,这可能是不可想象的。
印度任何角落的任何人都可以利用互联网的力量。跨语言和社会经济群体。即使他们不会读或写英语。通过与移动应用程序对话。用他们自己的语言。
任何企业都可以在应用程序中为大众烘焙语音助手。在所有印度语中。有负担得起的数据集、人工智能模型和服务。
现在,打字和触摸才是王道。但是巴拉特发现了自己的声音。印度语的语音搜索一直在增长。
所以这是有可能的。如果我们建造了巴拉特 Bhāṣā堆栈。印度语对话式人工智能的技术生态系统。

智能手机的普及率和语音的使用一直在增加。根据谷歌的报告,印度语的语音搜索也在增长。
Bhārat Bhāṣā堆栈可以成为小型企业的语音和语言 API。它可以消除另一个只有大企业才能承受的进入壁垒。它可以催化语音助手和对话式人工智能创新。
Bhārat Bhāṣā堆栈可以跨越印度的语言和社会经济界限,为超过 10 亿人带来难以想象的创新浪潮。
文章的其余部分:
- 地图对话式 AI 当前语音助手和应用的格局,
- 提出 Bhārat Bhāṣā栈和所需的技术和层用于构建这些应用的各种入口点,以及
- 讨论生态系统参与者如何帮助构建堆栈。
对话人工智能
对话式人工智能必不可少,但需要巨额投资。
对话式人工智能使机器像人类一样交流。从研究实验室到消费者手中。它最初是像 Siri 这样的独立语音助手。它正以多种形式出现在应用程序和设备中。

对话式人工智能将在所有应用和设备中普及。(图片由俚语实验室提供)
语音助手
语音助手是使用语音进行交互的智能虚拟助手。这些也被称为语音机器人,尤其是当通过纯语音界面传送时。例如,作为客户支持语音机器人的交互式语音应答(IVR)系统。
Siri 是第一个著名的语音助手。然后是亚马逊 Echo 设备中的 Alexa ,除了其他功能之外,它还使亚马逊购物成为可能。下一个参赛者是 谷歌助手 。它最初是作为谷歌家用设备出现的,后来出现在安卓手机中。
它从有趣发展到有用,即使是在一个封闭和有限的生态系统中。
语音操作
下一个合乎逻辑的进展是让它在应用程序中可用。允许程序员集成语音命令来触发特定的应用程序操作。亚马逊用 Alexa 的应用做到了这一点,谷歌用应用动作做到了这一点。
语音搜索 已经作为常见用例出现。几乎所有应用都有某种搜索功能:
- 搜索互联网
- 在地图应用程序中搜索路线
- 在音乐或视频应用程序中搜索歌曲
- 在购物应用程序中搜索商品
- 在旅行应用程序中搜索航班或火车
虽然所有的都是一种搜索,但每一种都涉及不同类别的世界知识。
应用程序中的语音助手
应用程序中的语音操作有严重的局限性。声音旅程开始就结束。一旦助手调用一个应用程序,用户只能通过触摸与应用程序进行交互。这阻碍了构建适合应用程序的丰富语音体验。
这就是为什么好几个应用在应用内部内置了语音助手(而不是他们的应用藏在 Alexa 或者 Google Assistant 后面)。 Gaana , YouTube , Paytm Travel , My Jio , Jio Mart , Amazon ,Flipkartapp 针对各自领域优化了语音助手。
构建这些优化的助手需要雄厚的资金。它需要大量的投资、努力和时间来构建。这些应用程序大多支持英语和印地语。缺少对大多数印度语的广泛支持。Bhārat Bhāṣā堆栈可以让小型企业家获得这些技术。
Bhārat Bhāṣā堆栈:印度语堆栈
一个开放的 Bhārat Bhāṣā栈可以降低 沟通 的壁垒,降低成本,刺激创新。
Bhārat Bhāṣā堆栈应该有一套模型、服务和 SDK 来构建印度语言的对话应用程序。它应该包括构建语音应用程序所需的语音、语言和视觉技术。堆栈层应该提供使用这些技术的方便入口点。这将使建立聊天机器人、语音机器人、语音助手和应用程序变得容易。
技术
语音助手模仿人类动作:
- 听:将语音音频转换成文字。叫做 自动语音识别 (ASR) 或者 语音转文字【STT】。
- 理解:理解文中意思或意图,提取重要实体。它叫做****【NLU】。
- ****动作:基于这种理解,应用程序执行期望的动作。这就是 app 的业务逻辑。栈为应用程序提供钩子来插入它们的逻辑。
- 说:问问题来澄清、确认或从用户那里寻求所需的信息。它被称为语音合成或文本到语音(TTS) 。

语音助手的步骤。(图片由俚语实验室提供)
其他对话式人工智能任务包括:
- 人类说不同的语言。应用程序可能需要将文本从一种语言翻译成另一种语言。叫做机器翻译(MT)** 。**
- 许多人在罗马键盘上使用语音拼写来输入印度语。计算机可能需要将文本的语音翻译成印度语言文字。叫做音译。
- 参见/阅读:识别手写或印刷字符图像的能力。它叫做光学字符识别(OCR) 。
所有这些都需要一种叫做深度神经网络(DNN)的机器学习技术。建立和训练 DNN 是非常昂贵的,因为它需要大量的数据和计算时间。
总而言之,Bhārat Bhāṣā堆栈涵盖了 dnn 擅长解决的所有三种类型的问题:
- ****语音:自动语音识别,语音合成
- ****语言:自然语言理解,机器翻译,音译
- ****视觉:光学字符识别
层
应用程序开发人员应该能够只关注他们的业务逻辑。Bhārat Bhāṣā堆栈应该为语音助手的其余步骤提供对话式人工智能技术的挂钩。本节描述了应用程序可以挂接的各个层。
剧本
语言的脚本使用 Unicode 字符集进行编码。对话式人工智能技术之间的信息交换使用这些字符集进行。
印度语是语音语言,即单词的拼写与其发音相同。这一特性可能允许使用语音数据进行跨相似语言的训练。
堆栈应利用并解决印度语使用者的独特性:
- 许多印度语是低资源语言。可用的语音和语言数据不足以训练模型
- 来自英语和印地语的外来词在对话中很常见(被称为语码转换)
- 由于共同的语言起源,重要的共享词汇
- 许多印度人会说多种语言。他们会说 2 到 3 种语言,理解 4 到 5 种语言。
在 IIT 钦奈开发的印度文字是印度语言的通用文字。工作表明印度语可以使用一对一的字符集映射进行音译。

在 IIT 钦奈发明的印度文字是印度语的通用文字。(图片来自 Bharati 脚本项目)
数据
数据的可用性和成本是大多数企业家面临的最大障碍。语音和语言的精选数据集是堆栈中的最低层。
许多著名的机构一直在为他们的研究收集有关印度语的数据。这些机构拥有数百小时的数据。但是随着时间的推移,这些数据以及使用这些数据的方法都丢失了。
学生毕业后,论文资料就像祖母珍贵的首饰盒。很少有人知道它在哪里,也没有人打开过它。——一位教授@ IISc Bangalore
对于语音识别,一些可用的数据集:
- 印度语言语言数据联盟(LDC-IL) 可能拥有学术界最大的印度语言数据集。他们通过一个数据发布门户来收费。
- 微软为研究人员发布了一个小型的古吉拉特语、泰米尔语和泰卢固语的语音语料库。
- Mozilla 的 Common Voice 有阿萨姆语、旁遮普语、奥迪亚语和泰米尔语的语音数据。
对于自然语言,一些可用的数据集:
- 来自 LDC-IL 的文本数据语料库
- 来自 IIT 孟买的英语-印地语平行语料库
- 印度语多语言平行语料库孟加拉语、印地语、马拉雅拉姆语、泰米尔语、泰卢固语和乌尔都语
巩固和建立一个类似 LibriSpeech 的印度语研发数据集将会带来丰厚的回报。
模型
拥有数据是强制性的第一步。但是训练 DNN 模特需要高水平的专业知识。它也很昂贵。
合乎逻辑的下一步是为印度语提供预先训练好的现成模型。隐私敏感型应用程序可以在设备上使用这些模型,也可以将其作为服务托管在内部私有云上。
软件即服务(SaaS)
SaaS 将开发人员从托管模型和管理服务基础设施中解放出来。这使得开始构建应用程序变得更加容易。
所有主要的云基础设施都为语音识别、自然语言理解和一些印度语言的文本到语音转换提供了 SaaS。
这些服务相当昂贵(就像支付网关一样)。让更多的 SaaS 供应商使用这些预先训练好的模型可以降低成本。
软件开发工具包(SDK)
流行的编程语言和 OS 平台中的 SDK 构成了最后一层。SDK 可以使用模型或 SaaS。
为应用领域调优模型或服务需要一些 ASR 和 NLU 的专业知识。需要针对特定领域的 SDK(例如,针对银行、电子商务、农业)来进一步降低准入门槛。
我们俚语实验室已经从我们的客户那里了解到这一点。我们现在提供语音助手即服务(VaaS ),以提高客户对语音助手的采用。

****Bhārat Bhāṣā栈。技术:自动语音识别(ASR)、文本到语音(TTS)、自然语言理解(NLU)、机器翻译、音译和光学字符识别(OCR)。(图片由俚语实验室提供)
生态系统参与者
设计和建造 Bhārat Bhāṣā堆栈需要系统和持续的合作:
- ****学术界分享代码为的关于印度相关对话式人工智能问题的研究论文。
- ****行业为普通人打造语音产品和服务。
- ****政府在建设印度堆栈中扮演类似的角色。
- ****行业团体通过会议和联盟加快协作。
政府积极制定人工智能政策:
- Niti Ayog 发表了一篇关于人工智能国家战略(#AIforAll) 的讨论论文
- 印度语言的技术开发隶属电子部&信息技术促成了 IDC-IL 数据发布门户的创建。
NASSCOM 和 FICCI 一直在举办研讨会,将公司和大学聚集在一起。俚语实验室一直是积极的参与者。
摘要
支持语音的应用程序可以跨越不同语言和社会经济群体之间的互联网鸿沟。语音和自然语言技术正在成熟,但仍然非常昂贵。
对于企业家和小企业来说,数据和模型培训成本是巨大的障碍。本地印度语 Bhāṣā堆栈将消除进入障碍。
本文概述了堆栈,以及我们需要构建的组件。我们需要:
- 巩固各组织正在进行的努力
- 集中资源来建造它,和
- 使其以低成本可用。
FinTech 的 India Stack 之所以成功,是因为包括政府在内的所有人都走到了一起。根据印度的独特需求构建 Bhārat Bhāṣā堆栈是其成功和广泛采用的关键。
让我们开始吧!
一名家庭佣工努力在 SBI 应用程序中找到自己的账户余额。她犹豫地尝试了所有的东西,尽管“右”按钮在第一个屏幕上很突出。巴拉特·bhāṣā·斯塔克能让她的生活轻松一点。
如果你喜欢,请:
原载于 黑话实验室博客 。
版本控制 101:定义和好处
什么是版本控制,为什么它很重要?

图片由作者提供(使用 Canva 制作)
软件工程领域是一个快速变化的领域。说到软件,没有最终版本。所有的应用程序和代码总是在不断地发展。软件工程的一个重要方面是版本控制。
版本控制系统是一种特殊类型的软件开发工具,旨在帮助软件开发人员跟踪对任何特定应用程序的源代码所做的任何更改。
通常,任何软件项目都是由一个程序员/开发者团队来处理的。他们中的每一个人都致力于整个项目的一个特定方面。这些开发人员需要为他们分配的任务编写和测试代码,而不影响产品的工作版本。这就是版本控制的用武之地。
除了促进任何软件项目的并行开发之外,当代码中出现错误,导致它中断时,我们可以使用版本控制来跟踪错误,并返回到代码的最新工作版本。
随着我们构建的软件变得越来越大、越来越复杂、越来越通用,版本控制的知识以及如何有效地使用它是任何软件开发人员都需要获得和开发的基本技能之一。
在本文中,我们将深入了解版本控制的世界,它意味着什么,为什么使用它,以及如何开始使用它。
版本控制
如上所述,版本控制是一种帮助开发人员/程序员解决一些日常问题的工具,例如:跟踪代码中的更改,帮助维护代码,并允许他们在不影响彼此工作流的情况下处理相同的源代码文件。
版本控制的概念通常是通过称为版本控制系统(VCS)的特殊系统来实现的。近年来,这些系统经历了许多发展。有时,VCS 被称为源代码管理工具(SCM)或修订控制系统(RCS)。
也许当今世界上数百万开发人员和程序员使用的最流行的 VCS 是 Git。目前使用的其他 VCS 包括:
- 必须的。
- 豆茎。
- 阿帕奇颠覆。
- AWS 代码提交。
版本控制系统的类型
有两种主要类型的版本控制系统:集中式和分布式版本控制系统。
集中式版本控制系统

图片由作者提供(使用 Canva 制作)
集中式版本控制系统(CVCS)是一种版本控制系统,它有一台服务器,包含所有版本的代码文件(通常存储在存储库中)、一些贡献者(处理代码文件的程序员)以及主项目的备份文件。
这种设置允许代码的每个贡献者知道其他人在做什么,使得基于特定任务的人员交流和管理更加容易。
这种设置的主要缺点是,由于所有的代码文件和备份文件都存储在一个集中的服务器上,如果服务器发生了什么问题,那么一切都将丢失。颠覆和被迫是 CVCS 的例子。
分布式版本控制系统

图片由作者提供(使用 Canva 制作)
分布式版本控制系统(DVCS)如 Git、Mercurial 或 Bazaar 是版本控制系统,有一个或多个包含代码文件的主服务器。
然而,与 CVCS 不同的是,在 DVCS,代码文件被镜像到每个贡献者的计算机上,这意味着他们每个人都有自己的代码版本,包括在本地工作的完整历史。
因此,如果任何服务器停止运行,一旦服务器恢复运行,贡献者的任何存储库都可以复制到服务器上,并恢复代码文件的历史。这很容易做到,因为原始代码文件的每个克隆实际上都是所有项目数据的完整备份。
DVCS 允许不同物理位置的不同人群在同一项目中同时协作。这是在中央系统中不可能实现的优势。
版本控制的好处
使用版本控制系统对任何软件开发团队都有各种好处,包括:
1 生成备份
也许使用版本控制系统最重要的好处是,每次贡献者克隆一个存储库,从某种意义上说,他们是在创建该存储库当前版本的备份。在不同的机器上有多个备份是很有价值的,因此我们可以保护数据不会因为服务器故障而丢失。
2 测试和实验
当今世界,任何源代码总是在开发中。新的功能总是在增加,新的受众是目标,新的应用程序是要开发的。
当一个团队在一个软件项目上工作时,他们通常有不同的主项目克隆来开发新的特性,测试它们,并确保它们在这个新的特性可以被添加到主项目之前正常工作。这可以节省时间,因为代码的不同方面可以同时开发。
3 保存历史并跟踪变化
拥有特定代码文件中变更的记录可以帮助您和新的贡献者理解代码的特定部分是如何形成的。它是如何开始并随着时间的推移发展到现在的版本的。
此外,如果随着时间的推移,某个特性的添加给项目的扩展带来了困难,那么使用版本控制可以让开发人员跟踪出那个特定的特性,并在不影响项目功能的情况下更改或删除它。
4 协作和贡献
版本控制系统的主要好处之一,尤其是 DVCS,是它允许我们为我们喜欢的项目做贡献,尽管我们身处不同的国家。我认为 DVCS 的存在是开源技术取得目前成功的主要推动者之一。
外卖食品
充分理解版本控制系统,它们是如何工作的,并有效地使用它们,这是任何程序员或软件开发人员都需要掌握的基本技能之一,并在他们的职业生涯中取得成功。
版本控制系统可能是非常复杂和难以理解的话题之一。这就是为什么我决定撰写一系列文章来解决版本控制的不同方面。这些文章的目标是让您轻松舒适地开始版本控制。
本系列即将发布的文章将涵盖每个开发人员都应该知道的 Git 基本命令,一些让您脱颖而出的高级命令,以及如何有效地使用 Git 来使您的工作流程流畅愉快。
版本控制 101:Git 入门
Git 如何工作的简要概述

图片由作者提供(使用 Canva 制作)
假设你是一名软件开发人员、数据科学家或程序员。在这种情况下,您一定在旅途中的某个时候遇到过 Git。掌握版本控制是所有软件相关领域共有的基本技能之一。
版本控制系统是一种特殊类型的软件,旨在帮助程序员跟踪任何特定的应用程序源代码更改。
一般来说,版本控制系统有两种类型,集中式和分布式系统。这两种类型的主要区别在于,在集中式系统中,代码文件和关于贡献者的信息存储在一个服务器中。然而,在分布式系统中,代码文件被复制到每个贡献者的机器上。
Git 是一个常用的基于存储库的分布式版本控制系统。它是由 Linux 的创造者 Linus Torvalds 创建的。Torvalds 创建了 Git 来管理 Linux 内核源代码。Git 是 GitHub 和 GitLab 等许多服务的核心。
什么是版本控制,为什么它很重要?
towardsdatascience.com](/version-control-101-definition-and-benefits-6fd7ad49e5f1)
在本文中,我们将介绍 Git 的工作原理及其基本构件。
Git 是如何工作的?
Git 的算法被设计成利用真实源代码文件树的公共属性,以及它们通常如何随着时间的推移而被修改。
当处理代码文件时,Git 不关注文件名,而是跟踪其内容。Git 是这样设计的,因为大多数情况下,代码文件的名称是不固定的。
为了存储代码文件,Git 使用 delta 编码——保持文件内容的差异——来显式保存存储库内容和版本的元数据。
Git 处理内容变化的方式是存储不同的快照。每个快照都链接到源代码中的特定更改。Git 还被设计为可以很好地应用或回滚在两个快照之间执行的修改。
Git 基础
仓库
Git 的主要构件是存储库(repo)。将存储库想象成计算机内存中的一个文件夹,该文件夹包含不同的文件,具有不同的类型和不同的编辑日期。
这些文件及其信息和任何子文件夹称为回购的内容。内容的所有版本都存储在 repo 中。repo 是存储内容和修改内容的地方,称为工作目录。
例如,假设您在硬盘上的特定目录中有多个 doc 文件。在这种情况下,这个目录将是 repo,其中的文件就是内容。假设你打开了其中一个文件进行编辑——使用文字处理器——编辑文档的应用程序就是工作目录。
读取内容当前版本的过程称为签出,存储新版本的过程称为提交。回到我们的目录示例,在字处理器中打开 doc 文件就是签出它,而单击 save 按钮相当于提交文件。

图片由作者提供(使用 Canva 制作)
每次提交文件时,您都会在存储库中创建该文件的新版本—快照。一系列版本创建一个分支。在 Git 中,主分支过去被命名为 master。然而,最近 Git 把它改成了 main。分支的概念用于实现一个具有分布式开发人员基础的项目,并在开发新功能的同时提供代码的稳定版本。
在 Git 中,术语 commit 在创建版本时用作动词,并作为结果快照的名称。
Git 包含许多处理和操作回购的命令。init、add、commit、branch、log 等命令。这些命令中的每一个都可以帮助您执行特定的任务。
分支
正如我们刚才提到的,一组变更称为一个分支。repo 通常包含不同的分支,主要分支和用于开发附加功能的独立分支。
在 Git 中,分支由称为变更集的有序变更列表组成。这些更改在应用到版本或快照时进行排序。分支只是指向特定版本的命名指针。当开发人员使用这个分支时,它会记录下哪里发生了变化。
分支背后的思想是,每次提交变更时,都会为该变更集创建一个新的分支。

图片由作者提供(使用 Canva 制作)
合并
在一个分支上完成开发之后,是时候将这些变更添加到主分支中了。这样做的过程称为合并。合并将两个分支的内容合并为一个。
根据所做更改的类型和分支的内容,您可以执行三种类型的合并:
- 快速向前合并:在这种类型中,自创建第二个分支以来,原始分支没有任何新的变化。在这种情况下,Git 在最后一次提交原始分支更新之后添加第二次分支更新。由于原始分支中没有更早的更改,这种类型被称为快进分支,因为它将原始分支向前移动到第二个分支所在的位置。
- 无冲突合并:在两个分支都发生变化的情况下,这些变化是独立的,不会引起任何冲突,Git 可以自动将所有的变化应用到原来的分支,并创建一个新的提交。
- 冲突合并:如果两个分支中的更改发生冲突,例如,它们是在同一个代码文件上完成的,Git 会留下结果,导致工作目录中的冲突。用户必须解决冲突或中止合并。

图片由作者提供(使用 Canva 制作)
Git 检查冲突的方式非常有趣。它主要试图找到内容中发生变化的位置;如果两个分支在相同的位置有内容,Git 将停止并产生一个冲突标志。如果修改了相同的内容,但修改是独立的,Git 可以检测到这一点并执行快速向前合并。
外卖食品
理解 Git 的内部工作可能不是开发人员或程序员需要了解的关键知识,但了解它如何工作以及如何有效使用它的高级部分是一项技能,如果不获得这项技能,你就无法在职业生涯中进步。
Git 由包含不同代码文件以及版本元数据的存储库组成。在本文中,我们介绍了 Git 基础知识,如何创建和处理回购。如何创建和合并分支以及 Git 提供的不同类型的合并。
本系列的下一篇文章将介绍更高级的 Git 概念,比如挑选和回滚。
使用 Git 进行版本控制:不到 15 分钟即可开始
完全初学者的循序渐进教程。

在 Unsplash 上由 Bernd Klutsch 拍摄的照片。
你是否经常听说 Git(或者 GitHub ),但是不知道它意味着什么,它有什么好处,或者它是如何工作的?你来对地方了。
在本教程中,您将学习如何使用最基本的 Git 命令——在使用 Git 时,您大部分时间都需要知道这些命令。更具体地说,在这里您将学习如何为您的项目创建一个 Git 存储库,以及如何使用它来记录项目中所有未来的变更。并且完成这个教程应该花费你不到十五分钟的时间,假设你在安装过程中不会有任何问题(我希望大多数人不会有)。所以让我们开始吧。
版本控制?饭桶?为什么?
简而言之,Git 是一个开源版本控制系统,它允许您随时跟踪项目的变化。例如,使用 Git,您可以很容易地检查项目中每个文件在给定时间的状态,如果您愿意,甚至可以将其恢复到那个状态。这也使得管理多人同时工作的项目变得更加容易,每个人都在自己的计算机上工作。
Git 不是唯一可用的版本控制系统(其他例子: SVN 、 CVS 、 Mercurial ),但它是现今使用最多的一个。如果你是一名软件开发人员、网页设计师、数据科学家,或者从事其他任何涉及文本文件的项目,学习如何使用 Git 对你来说肯定是一项非常有价值的技能。
安装和设置
在本教程中,我们将从命令行终端(在 Windows 中也称为命令提示符)运行 Git 命令,因此如果您已经有一些使用终端的经验,这会很有帮助。但是,如果您以前从未使用过它,也不用担心——您仍然可以遵循本教程。
打开终端/命令提示符
首先,让我们在您的计算机上打开一个命令行终端。根据您的操作系统,以下是一些快捷的方法:
- Windows:按下
[⊞ Win]+[R],然后键入cmd并按回车键。 - Linux:按
[Ctrl]+[Alt]+[T]。 - MacOS:按下
[Command]+[Space],然后输入terminal并回车。
安装 Git
我们将首先检查您的计算机上是否已经安装了 Git 如果没有,我们将安装它。在终端上键入git --version并按回车键。如果安装了 Git,您将看到当前安装的版本,如下图所示。如果您已经有了 Git,您可以直接跳到下一节“设置您的数据”。

如果你得到类似git: command not found的东西,那么你需要安装 Git。在 MacOS 上,运行git --version可能已经提示了一些安装说明,你只需要按照它们去做就可以了。如果不是这种情况,请根据您的操作系统遵循以下说明:
- Windows:为 Windows 安装 Git。
- Linux:在终端上运行
sudo apt-get install git。 - MacOS:安装 Git for Mac 。
设置您的个人数据
安装后,您需要配置您的姓名和电子邮件——它们将用于在您的“提交”中识别您的身份(稍后我将解释它们是什么)。为此,请在终端中使用您的个人数据运行以下两个命令:
git config --global user.name "Your Name"git config --global user.email "your@email.com"
就是这样。现在您已经准备好开始使用 Git 了!
创建您的第一个 Git 存储库
虽然 Git 主要由程序员使用,但它实际上可以用于任何包含一个或多个计算机文件的项目。在本教程中,您将使用 Git 来管理包含两个简单文本文件的项目:一个包含购物清单,另一个包含待办事项清单。您将用于管理这个项目的命令与您用于编程项目的命令完全相同。
为项目创建一个目录和一些文件
让我们从创建项目目录和其中的两个文本文件开始。在硬盘中创建一个名为my_lists的目录,并在其中创建两个文本文件,内容如下:
shopping_list.txt:
- Toilet paper (10 rolls)
- Beer (12 bottles)
- Food
to-do_list.txt:
- Meet friends
- Go shopping
- Learn Git
这将是我们项目的初始状态。
初始化 Git 储存库
使用终端,导航到刚刚创建的目录my_lists。你可以用命令cd来完成。如果你不知道如何使用终端导航,这里有一些说明:Windows 上的;Linux 或 MacOS 上的/。导航到my_lists之后,如果你运行ls(在 Linux/MacOS 上)或者dir(在 Windows 上),你应该能看到你创建的两个文件。
现在,运行git init使您的项目目录正式成为 Git 存储库。之后,你可以在任何时候从那个目录中运行git status来检查里面发生了什么。如果您现在这样做,您将看到您的存储库“还没有提交”,并且这两个文件被标记为“未跟踪”:

添加新文件
要让这些文件开始被 Git 跟踪,您需要将它们“添加”到存储库中。为此,您将使用命令git add。这些选项中的任何一个都可以达到目的:
- 运行
git add shopping_list.txt,然后运行git add to-do_list.txt,每次添加一个文件;或 - 运行
git add shopping_list.txt to-do_list.txt用一个命令添加两个文件;或 - 运行
git add .或git add -A一次性添加当前目录下的所有文件。
添加文件后,再次运行git status,您应该会看到如下内容:

现在,这两个文件位于所谓的“暂存区”中。这意味着这些文件将包含在您的下一次提交中。
你的第一次承诺
在 Git 中,提交是给定时间项目状态的快照。把它想象成一个将被永久保存的项目版本,这样将来你就可以很容易地看到从那时起发生了什么变化,如果你愿意,你甚至可以把项目恢复到那个状态。每次您在项目中进行重要的更改时,您都应该创建一个新的提交,这样您的工作就会记录在项目的历史记录中。
在将这两个文件添加到暂存区域之后,您可以使用命令git commit -m "Your commit message"创建您的第一个提交。确保总是写一个有意义的提交消息来描述什么已经改变了,因为任何检查项目历史的人都会看到这个消息。
提交后,运行git log。这将显示您的提交历史。此时,日志中应该只有一次提交,如下所示:

注意:您也可以在没有 *-m* 选项的情况下运行命令*git commit*——这将打开一个文本编辑器,要求您输入提交消息。但是要小心,因为根据你的系统,这可能会打开一个命令行文本编辑器,如vim,如果你不知道如何使用它,那么 可能很难退出 。你也可以用 *git config --global core.editor <editor>* 命令 改变默认的文本编辑器 。
进行和提交更改
在为您的项目创建 Git 存储库之后,下一步是使用它来记录未来的变更。让我们回到我们的例子:假设您刚刚发现您所在的城市爆发了一场重大病毒,这迫使您修改了购物和待办事项列表,如下所示:
shopping_list.txt:
- Toilet paper (100 rolls)
- Beer (120 bottles)
- Food
- Hand sanitizer
to-do_list.txt:
- Go shopping
- Wash hands
- Learn Git
查看您的更改
和以前一样,您可以用命令git status检查存储库的当前状态。这将显示 Git 已经检测到两个文件中的更改:

但是您也可以获得更多关于自从您最后一次使用git diff提交以来发生了什么变化的细节。这将逐行显示所有文件中所有已更改的内容:

通常,未更改的行打印为白色,而新行打印为绿色,删除的行打印为红色。请注意,被修改的行实际上显示出来,就好像旧版本已被删除,新行已被添加。
创建新提交
要创建一个包含这些更改的新提交,过程与创建第一个提交的过程相同:首先使用git add,然后使用git commit。
虽然您可以为两个文件中的更改创建一个提交,但是让我们为每个文件创建一个提交。首先,运行git add shopping_list.txt将这个文件添加到暂存区。之后,您可以使用git diff --staged检查哪些变更已经被添加到了临时区域。如果您现在运行git commit,这个命令将显示哪些更改将被提交。

如果一切都如预期的那样,那么现在通过运行git commit创建一个包含这些阶段化更改的新提交,就像创建第一个提交时所做的那样。同样,确保包含有意义的提交消息。之后,作为练习,您可以使用文件list.txt中的更改创建第三个提交。
全部完成!
如果您做的一切都正确,那么您的存储库中现在有三个提交。您可以使用git log进行检查,您应该会看到类似这样的内容:

就是这样。现在您知道了如何为您的项目创建一个 Git 存储库,以及如何为您将来的所有更改进行新的提交。一般来说,在使用 Git 的大部分时间里,您将会用到刚刚学到的这几个命令。
你可以做的其他事情
当然,Git 还有更多的东西——本教程的目标只是让您开始上面描述的基本工作流程。下面是您可以用 Git 做的一些其他事情的列表(包括更详细信息的链接):
- 您可以通过
git show查看特定提交引入了哪些更改。 - 你可以用
git reset来撤销你所有未提交的修改(注意这一点)。 - 您可以使用
git revert恢复由特定提交引入的所有更改。 - 你可以用
git stash暂时“隐藏”你的改变。 - 您可以使用
git checkout将项目恢复到旧的状态。 - 您可以使用 Git 分支管理同时实现的多个特性。
- 你可以使用像 GitHub 或 GitLab 这样的远程 Git 平台分享你的项目和/或与其他人合作。
一般来说,你可以在像 StackOverflow 这样的在线论坛上找到非常有用的关于如何使用 Git 的建议。所以如果你不知道如何用 Git 做一些事情,你通常可以通过简单地在网上搜索来学到很多。
进一步阅读
- Git:一个快速简单的版本控制指南,作者 Christian Leonardo。
- Git 和 GitHub 入门:新开发人员版本控制、项目管理和团队合作的综合指南,作者 Mariot Tsitoara。
- 对于一些其他有用的命令,查看你应该知道的 10 个 Git 命令。
- 如果你想学习如何使用 GitHub,请查看数据科学家 GitHub 简介。
更多由同一作者
这是一个简单的策略,可以让你在任何项目中更有效率。
medium.com](https://medium.com/swlh/do-this-before-you-start-working-on-any-project-14fd7bfa7327) [## “大 O”和其他渐近符号背后的数学
像“大 O”、“大ω”和“大θ”这样的符号的正式定义。
towardsdatascience.com](/the-math-behind-big-o-and-other-asymptotic-notations-64487889f33f) [## C/C++中测量执行时间的 8 种方法
不幸的是,没有放之四海而皆准的解决方案。在这里你会找到一些可用的选项。
levelup.gitconnected.com](https://levelup.gitconnected.com/8-ways-to-measure-execution-time-in-c-c-48634458d0f9)
披露:此帖子包含一个或多个来自亚马逊服务有限责任公司协会计划的链接。作为代销商,我从通过这些链接购买的商品中获得佣金,客户无需支付额外费用。
使用 Jupyter 笔记本进行版本控制
编程;编排
Jupytext 分步指南

**Table of Contents**[**Introduction**](#1cd1)1\. [Creating a demo repo](#a92d)
2\. [Jupytext setup](#9aab)
3\. [Converting to a python file](#b1b8)
4\. [Converting multiple files](#f3c3)
5\. [Converted file](#7ac3)
6\. [Adding ipynb to .gitignore](#3d3b)
7\. [Converting to ipynb files](#e9a8)
8\. [Other commands](#56e5)
9\. [Paired notebooks](#6b91)[**Conclusion**](#a9ad)
介绍
Jupyter notebook 生成包含元数据、源代码、格式化文本和富媒体的文件。[git diff](https://gist.github.com/shinokada/fa5c18b1ed26eb11344b5344dc1391ac)中一个字的改变导致成千上万个字母。
Jupytext 可以将 Jupyter Notebook 保存为 git 友好和人类友好的文件格式,包括 Markdown、Python、Julia、Bash、Clojure、Matlab、TypeScript、Javascript 等。
它还将这些文档转换成 Jupyter 笔记本。在本文中,我将使用 Jupytext 一步步地介绍 Jupyter Notebook 的版本控制指南。
如果你用的不是 Github 的 ipynb 渲染、 Nbviewer 或者 Binder ,那么 Jupytext 应该是你版本控制的选择。
支持的扩展有:
Jupyter 笔记本 Diff 入门
levelup.gitconnected.com](https://levelup.gitconnected.com/a-painless-jupyter-notebook-code-review-and-diff-51d36d8cd20b) [## 如何在 Docker 上运行 Jupyter 笔记本
不再有 Python 环境和包更新
towardsdatascience.com](/how-to-run-jupyter-notebook-on-docker-7c9748ed209f) [## Jupyter 用户的生产力提示
使用 Jupyter 笔记本和 JupyterLab 让您的工作流程更加高效
towardsdatascience.com](/stepping-into-intermediate-with-jupyter-f6647aeb1184)
创建演示回购
首先,让我们用下面的代码创建一个新的 Jupyter 笔记本文件。
x = np.arange(-3, 3, 0.1)
y = np.sin(x)
plt.plot(x, y)
plt.show()
请创建一个 Github repo。
echo "# jupyter_notebook_version_control" >> README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin [git@github.com](mailto:git@github.com):username/jupyter_notebook_version_control.git
git push -u origin master
我在 ipynb 文件中将“sin”改为“cos”。
y = np.cos(x)
该链接是“git diff”的结果。它为三封信生成了数千封信。

Jupytext 设置
让我们安装并设置 Jupytext 。
pip install jupytext --upgrade
或者对于康达用户
conda install -c conda-forge jupytext
重启 Jupyter 笔记本。
转换为 python 文件
您可以将 ipynb 文件转换为受支持的文件之一。我将在本文中使用一个 python 文件。
在你的终端里,你可以这样运行。
jupytext --to py <your-file-name>.ipynb
对于我的情况:
jupytext --to py Version_control.ipynb
产出:
[jupytext] Reading ./Version_control.ipynb
[jupytext] Writing ./Version_control.py
第一篇数据科学文章指南
towardsdatascience.com](/7-essential-tips-for-writing-with-jupyter-notebook-60972a1a8901)
转换多个文件
让我们一次转换所有的 ipynb 文件。请在您的目录中创建更多文件。
jupytext --to py *.ipynb
输出:
[jupytext] Reading Version_control.ipynb
[jupytext] Writing Version_control.py
[jupytext] Reading sine.ipynb
[jupytext] Writing sine.py
[jupytext] Reading tangent.ipynb
[jupytext] Writing tangent.py
您可以将文件转换成目录。如果不存在,Jupytext 将创建一个新目录。
jupytext --to destination_folder//py *.ipynb
注意事项:
如果您愿意,可以在其中一个单元格中运行 jupytext。但是这个单元格也会出现在转换后的文件中。
!jupytext --to py <your-file-name>.ipynb
转换文件
让我们在您的终端中查看转换后的文件。
cat Version_control.py
我的输出:
# ---
# jupyter:
# jupytext:
# text_representation:
# extension: .py
# format_name: light
# format_version: '1.5'
# jupytext_version: 1.3.3
# kernelspec:
# display_name: Python 3
# language: python
# name: python3
# ---
x = np.arange(-3, 3, 0.1)
y = np.cos(x)
plt.plot(x, y)
plt.show()
它非常紧凑,文件大小非常小。尼斯(法国城市名)😃 👏👏👏👏。
将 ipynb 添加到。gitignore
因为我们没有跟踪ipynb文件,所以我们可以将它添加到.gitignore文件中。请在您的项目根目录下创建一个.gitignore。git 目录。
touch .gitignore
请加上*.ipynb和。ipynb_checkpoints 忽略所有 Jupyter 笔记本文件。或者将这个完整的列表添加到你的 gitignore 中。
# for Jupytext ignoring ipynb files
*.ipynb
在这个阶段,git 仍然会跟踪.ipynb文件中的变化。要解决这个问题,您需要删除 git 缓存并再次添加所有文件。
git rm -r --cached .
git add .
git commit -m "fixed untracked files"
在你的 Jupyter 笔记本上换了一行后,看看.gitignore是否工作。
# change whatever you want
y = np.arange(-2,2,0.1)
在您的终端中检查它:
git status
它不应该返回修改过的文件。让我们再次运行 Jupytext 来思考我们的变化。请在您的终端中运行以下程序。
jupytext --to py Version_control.ipynb
转换后的文件将被替换。😃
[jupytext] Reading ./Version_control.ipynb
[jupytext] Writing ./Version_control.py (destination file replaced)
让我们检查一下 git 状态。
git status
On branch master
Your branch is up to date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: Version_control.py
no changes added to commit (use "git add" and/or "git commit -a")
它只跟踪 python 文件,不跟踪 ipynb。请运行git diff。
git diff
diff --git a/Version_control.py b/Version_control.py
index 02d91ea..6522717 100644
--- a/Version_control.py
+++ b/Version_control.py
@@ -14,6 +14,7 @@
# ---
x = np.arange(-3, 3, 0.1)
+y = np.arange(-2,2,0.1)
y = np.cos(x)
plt.plot(x, y)
plt.show()
请添加、提交并推送更改。
git add .
git commit -m "Update"
git push
转换为 ipynb 文件
我们将把这个 repo 克隆到另一个目录,并将其转换为 ipynb 文件。
cd ..
git clone git@github.com:shinokada/jupyter_notebook_version_control.git my-new-dir
我将我的 repo 克隆到一个名为 my-new-dir 的目录中。
cd my-new-dir
ls
README.md Version_control.py sine.py tangent.py
或者如果你有[tree](https://formulae.brew.sh/formula/tree)。
tree
.
├── README.md
├── Version_control.py
├── sine.py
└── tangent.py
0 directories, 4 files
我们有我们需要的所有文件。我们把它转换成 ipynb 文件吧。
从您的终端:
jupytext --to ipynb *.py
输出:
[jupytext] Reading Version_control.py
[jupytext] Writing Version_control.ipynb
[jupytext] Sync timestamp of 'Version_control.py'
[jupytext] Reading sine.py
[jupytext] Writing sine.ipynb
[jupytext] Reading tangent.py
[jupytext] Writing tangent.ipynbls
README.md Version_control.py sine.py tangent.py. Version_control.ipynb sine.ipynb tangent.ipynb
其他命令
这些是您可以使用的其他命令。
# convert notebook.md to an .ipynb file and run it
jupytext --to notebook --execute notebook.md# update the input cells in the .ipynb file and preserve outputs and metadata
jupytext --update --to notebook notebook.py# Turn notebook.ipynb into a paired ipynb/py notebook
jupytext --set-formats ipynb,py notebook.ipynb # Update all paired representations of notebook.ipynb
jupytext --sync notebook.ipynb
配对笔记本
Jupytext 可以将一个给定的笔记本写入多个文件。除了原始的笔记本文件之外,Jupytext 还可以将输入单元格保存到文本文件中——脚本或 Markdown 文档。如有兴趣,请阅读更多详情。
结论
Jupytext 易于使用并创建友好的文件,您也可以在另一个编辑器中编辑这些文件。如果你正在使用git diff,这是一个非常好的工具。我觉得这是目前用 Jupyter Notebook 做版本控制最完整的开源工具了。
通过 成为 会员,可以完全访问媒体上的每一个故事。

https://blog.codewithshin.com/subscribe
参考
[## IDE 中的 Jupyter 笔记本:Visual Studio 代码与 PyCharm
我每天都用 Jupyter 笔记本工作。每天我都在使用和编辑 Python 库。这两者都是我的…
towardsdatascience.com](/jupyter-notebooks-in-the-ide-visual-studio-code-versus-pycharm-5e72218eb3e8)
- https://github.com/mwouts/jupytext
- https://jupytext.readthedocs.io/en/latest/index.html
- https://jupytext . readthedocs . io/en/latest/introduction . html # demo-time
你好。你坚持到了最后。既然你来了,请帮我传播这篇文章。你也可以关注我,获取更多的 Jupyter、统计和技术文章。
Jupyter 技巧和提示的集合
medium.com](https://medium.com/series/jupyter-hacks-and-tips-1b1a3a10bc79)
垂直与水平人工智能初创公司
人工智能初创公司有两种截然不同的风格。无论选择哪一种,都要记住什么。
初创公司继续快速涌现。根据斯坦福人工智能指数,自 2014 年以来,超过 15798 家人工智能初创公司获得了至少 40 万美元的投资。根据 CB Insights 的说法,人工智能初创公司在 2019 年也吸引了创纪录的 266 亿美元投资。鉴于预计的数据指数级增长,投资者和企业家都渴望通过人工智能产生影响。

来源:斯坦福人工智能指数,第 88 页
然而,并不是所有的人工智能初创公司都是平等的。本文考察了垂直和水平人工智能创业公司之间的差异和共性。这些见解是基于与人工智能专家的讨论,他们来自一家总部位于硅谷的风投公司、一家总部位于柏林的人工智能风险工作室和一位人工智能企业家。
要了解垂直和水平的 AI 产品,我们先来深入 AI 产品开发的话题。
开发人工智能产品
每个基于人工智能的产品都遵循大致相同的工作流程。对于每个机器学习项目,团队必须收集和准备数据,开发模型,部署并监控其性能。 CRISP-DM 模型描述了下面的机器学习工作流程。

资料来源:德累斯顿大学
理解数据科学工作流程对于区分垂直和水平人工智能初创公司非常重要。垂直人工智能初创公司完成整个工作流程来交付产品,而水平人工智能初创公司则专注于实现一般的数据科学工作流程。
垂直人工智能初创公司
垂直人工智能初创公司提供基于人工智能的产品,拥有整个工作流程来解决非常有针对性的客户需求。这些初创公司从端到端开发一个完整的产品,从理解商业案例开始,到监控产品性能。

Vara 举例来说,它是一家垂直 AI 初创公司,可以在 x 光片上识别乳腺癌。Vara 是第一家获得联邦批准使用其人工智能解决方案来协助放射科医生的公司。该公司是垂直人工智能公司的一个主要例子,该公司完成了完整的数据科学工作流,以提供创新的人工智能产品。

人工智能是一种强大的工具,可以帮助公司在特定任务上实现超人的性能。人工智能为交付新产品、改进现有产品或自动化任务开辟了新的可能性。垂直人工智能初创公司针对特定的用例优化他们的预测。如果他们成功了,他们在这个用例中会比人类或竞争对手表现得更好。
垂直 AI 创业公司包括产品角色等关键岗位的领域专家。例如,制造自动化癌症检测产品的公司很可能拥有内部放射学家和/或肿瘤学家。——萨哈尔·莫尔,隐形模式下的企业家
然而,垂直人工智能初创公司更难调整他们的产品来服务于相邻的用例。对于擅长识别乳腺癌的 Vara 来说,将任务切换到检测脑癌并不是一件小事。人工智能模型在其狭窄的任务中表现良好。Vara 可以训练一个新的人工智能模型,从新的数据集中检测脑癌,但仍然可以重用他们现有的模型开发管道。
在 10 年内,垂直人工智能初创公司将极大地改变我们在某些职业中的工作方式。—Merantix 的联合创始人兼首席技术官 Rasmus Rothe
垂直人工智能初创公司的其他例子有无人驾驶汽车公司 Waymo 和金融犯罪预防公司 hawk.ai 。总而言之,对于垂直人工智能初创公司来说,最重要的是专注于解决一个足够大的用例,以确保增长。因为为新用例训练新模型需要时间和数据,所以最初的用例必须有足够的影响力来支持一个盈利的公司。
接下来我们来评价一下横向 AI 创业公司的具体情况。
横向 AI 公司
一个横向的 AI 公司使数据科学家能够更高效地完成数据科学工作流程。因此,横向 AI 创业公司的客户一般是数据科学家或开发团队。

横向 AI 公司挑选数据科学工作流程的特定或多个部分,并对其进行改进。例如,一家横向人工智能公司可以专注于帮助数据科学家监控他们的模型。横向 AI 产品是用例不可知的,用于开发任何基于 AI 的产品。
土星云是一家横向 AI 初创公司,旨在让数据科学家更加有效。它提供完全托管的服务,使数据科学家能够使用他们最熟悉的工具(Python、Jupyter 和 Dask)进行大规模工作。该工具是通用的,因此它可以用于任何基于人工智能的产品。
“在几十个行业中,我们听到自动化数据科学工作流如何为他们的最终客户带来更高效的团队和更好的体验。与此同时,Python 的使用正在飙升,并迅速主导了数据科学领域,因此采用一套 Python 原生工具进行分析和自动化对大多数数据团队来说是有意义的。”——Hugo Shi,土星云的联合创始人,也是 Anaconda 的前创始人。
根据 IDC 的预测,随着数据量的指数增长,横向人工智能解决方案有望在未来变得更加有用。数据是人工智能产品的燃料,因此可用的数据越多,创造有价值的数据产品的潜力就越大。此外,数据科学家市场继续稳步增长。根据 IBM 的一项研究,2020 年美国对数据科学家的需求将增长 28%,达到 270 万人。因此,目标客户群继续增长。

资料来源: IDC
但是,横向的 AI 产品也面临挑战。他们必须无缝集成到目标数据科学工作流和软件堆栈中。此外,“在开发横向人工智能产品时,争取市场领先地位很重要”,拉斯穆斯·罗斯说。对于横向人工智能初创公司来说,很难收集专有数据,从而建立数据防御。当定义数据科学工作流的哪个步骤要自动化时,记住不同的基于人工智能的模型的不同要求是很重要的。专注于对目标群体最有价值的功能,将使有志于成功的横向人工智能创业公司与众不同。
尽管横向人工智能公司通常面临更多竞争,但它们的产品通常更容易被收购,因为其技术可以被更广泛的收购者收购。— 杰森·里施
横向人工智能初创公司的其他例子有数据标签公司 understand.ai ,以及数据科学工作流管理初创公司 neptune.ai 。横向人工智能创业公司的潜力是巨大的。然而,构建一个超越竞争对手并达到市场领先地位的工具是具有挑战性的。一旦你给很多数据科学家提供了价值,你的横向 AI 创业就成功了。
垂直与水平人工智能初创公司
人工智能创业公司是不同的,理解他们独特的价值主张是建立一个成功的人工智能公司的核心。垂直 AI 创业公司交付产品,而水平 AI 创业公司则赋能 AI 产品创作。两种创业类型的 t 型组合也存在,但会在另一篇文章中讨论。

了解你正在建立的人工智能初创公司的类型很重要,因为这将最终影响你的产品路线图。
- 垂直人工智能初创公司需要专注于解决一个足够大的单一用例,以维持一个不断增长的公司
- 横向人工智能初创公司需要在他们选择的利基市场达到市场领先地位,为许多数据科学家提供价值
感谢您的关注!看完这篇帖子,你对垂直和水平 AI 创业公司的特点有了更深入的了解。如果你想听更多关于商业、项目管理和数据科学的交集,在 Medium 、 LinkedIn 或 Twitter 上关注 me。
Vespa.ai 和 CORD-19 公共 API
体验 Vespa 的功能
Vespa 团队一直在马不停蹄地工作,以由艾伦人工智能研究所发布的新冠肺炎开放研究数据集(CORD-19)为基础,组装 cord19.vespa.ai 搜索应用。前端的和后端的都是 100%开源的。后端基于 vespa.ai ,一个强大的开源计算引擎。由于一切都是开源的,你可以以多种方式为项目做贡献。

作为用户,您可以使用前端搜索文章,或者使用公共搜索 API 执行高级搜索。作为一名开发者,你可以通过向后端和前端发送请求来改进现有的应用程序,或者你可以派生并创建你自己的应用程序,无论是在本地还是通过 Vespa Cloud ,来试验不同的方式来匹配和排列 CORD-19 文章。我写这篇文章的目的是给你一个概述,通过使用 cord19 搜索应用程序公共 API,Vespa 可以完成什么。这只是皮毛,但我希望它能帮助你找到正确的地方,了解更多的可能性。
简单查询语言
cord19.vespa.ai 查询接口支持 Vespa 简单查询语言,允许您快速执行简单查询。示例:
- +新冠肺炎+温度对病毒传播的影响:如果你点击这个链接,你将会搜索到包含新冠肺炎、温度和短语对病毒传播的影响的文章。
- +title:" reproduction number "+abstract:MERS:该链接将返回标题中包含短语 reproduction number 和摘要中包含单词 MERS 的文章。
- +authors.last:knobel :返回至少有一个作者姓 knobel 的文章。
其他资源:
- 更多 cord19 的具体例子可以在 cord19 API 文档中找到。
- 简单查询语言文档是查询语法的地方。
Vespa 搜索 API
除了简单的查询语言,Vespa 还有一个更强大的搜索 API ,它通过名为 YQL 的 Vespa 查询语言来完全控制搜索体验。然后,我们可以通过向搜索端点 cord19.vespa.ai 发送 POST 请求来发送广泛的查询。以下是说明该 API 的 python 代码:
import requests # Install via 'pip install requests'endpoint = 'https://api.cord19.vespa.ai/search/'
response = requests.post(endpoint, json=**body**)
按查询术语搜索
让我们分解一个例子来提示您使用 Vespa 搜索 API 可以做些什么:
**body** = {
'yql': 'select title, abstract from sources * where userQuery() and has_full_text=true and timestamp > 1577836800;',
'hits': 5,
'query': 'coronavirus temperature sensitivity',
'type': 'any',
'ranking': 'bm25'
}
匹配阶段:上面的 body 参数将为所有匹配'query'项中的任何一个('type': 'any')的文章选择标题和摘要字段,这些文章具有可用的全文(has_full_text=true)和大于 1577836800 的时间戳。
排名阶段:根据上述标准匹配文章后,Vespa 将根据文章的 BM25 分数 ( 'ranking': 'bm25')对其进行排名,并根据该排名标准返回前 5 篇文章('hits': 5)。
上面的例子仅仅给出了搜索 API 的一点尝试。我们可以根据自己的需要定制匹配阶段和排名阶段。例如,我们可以使用更复杂的匹配操作符,如 Vespa weakAND,我们可以通过在上面的正文中添加'default-index': 'abstract'来限制搜索,只在摘要中查找匹配。我们可以通过将'ranking'参数更改为搜索定义文件中可用的等级配置文件之一,在查询时试验不同的等级函数。
其他资源:
- Vespa 文本搜索教程展示了如何逐步创建文本搜索应用程序。第 1 部分展示了如何从头开始创建一个基本的应用程序。第 2 部分展示了如何从 Vespa 收集训练数据,并改进 ML 模型的应用。第 3 部分展示了如何通过使用预先训练的句子嵌入开始语义搜索。
- 更多特定于 cord19 应用的 YQL 示例可以在 cord19 API 文档中找到。
按语义相关性搜索
除了通过查询词进行搜索,Vespa 还支持语义搜索。
**body** = {
'yql': 'select * from sources * where ([{"targetNumHits":100}]nearestNeighbor(title_embedding, vector));',
'hits': 5,
'ranking.features.query(vector)': embedding.tolist(),
'ranking.profile': 'semantic-search-title',
}
匹配阶段:在上面的查询中,我们通过使用最近邻操作符来匹配至少 100 篇文章([{"targetNumHits":100}]),这些文章在title_embedding和查询嵌入vector之间具有最小(欧几里德)距离。
排序阶段:匹配后我们可以用多种方式对文档进行排序。在这种情况下,我们使用一个名为'semantic-search-title'的特定 rank-profile,它是预定义的,根据标题和查询嵌入之间的距离对匹配的文章进行排序。
标题嵌入是在将文档提供给 Vespa 时创建的,而查询嵌入是在查询时创建的,并通过ranking.features.query(vector)参数发送给 Vespa。这个 Kaggle 笔记本演示了如何通过使用塞伯特-NLI 模型在 cord19 应用中执行语义搜索。
其他资源:
- 文本搜索教程的第 3 部分展示了如何通过使用预先训练的句子嵌入开始语义搜索。
- 进入排名页面了解更多关于排名的一般情况,以及如何在 Vespa 中部署 ML 模型(包括 TensorFlow、XGBoost 等)。
振动出张量流
为数据科学家设计的张量流。
我们都知道,业界对合格的数据科学家有着巨大的需求。那么,我们如何向学生展示数据科学的世界呢?

我认为对学生来说,从一开始就了解行业标准工具很重要,这有助于塑造他们学习和思考问题的方式。
考虑到这一点,我想考虑学生如何在没有动手机器学习经验的情况下开始学习这些工具,而在第一次遇到这些工具时,动手机器学习经验通常是假定的知识。
因此,我想写一个例子,说明一个学生如何在没有经验的情况下开始研究某些工具。
今天,我选择做一个高层次的,面向学生的概述…

让我们首先从理解名字开始: TensorFlow。
张量
为什么 TensorFlow 的名称中包含了“张量”?
简单来说,张量是一个 n 维物体。含糊不清?让我分享一个我在学习张量时发现很有帮助的形象化:

我想我们大多数人对标量是什么有一个相当直观的理解(一个数!)甚至向量和矩阵,但是从上面的图像看不清楚的是,所有这些东西都是不同维度的张量!
所以,一般来说,张量就像一个矩阵,但它不只是必须有行和列,它还可以有深度(通道)和更多深度(我们无法形象化)!
这意味着我们可以有一个对象存储对 n 特征的引用。这是机器学习中经常出现的东西,尤其是张量,在深度学习中具有重要作用。
有趣的是,TensorFlow 允许你以同步的方式处理机器学习和深度学习(神经网络)模型和算法,所以你可以开始看到这个名字的“张量”部分来自哪里。
流动
正如我稍后将触及的更多内容,TensorFlow 是一个工作流工具,它将一系列行业标准工具联系在一起,以便部署无缝的机器学习和深度学习项目。
简而言之,TensorFlow 给你你的机器学习流程。
“tensor flow 是机器学习的端到端开源平台。它有一个全面、灵活的工具生态系统、库和社区资源,让研究人员推动 ML 的最新发展,让开发人员轻松构建和部署 ML 驱动的应用。”
这是从 TensorFlow 网站复制粘贴的官方描述。
让我们打破这种描述,不要假设任何知识:
“TensorFlow 是一款端到端的开源软件—
等等——“开源”是什么意思??
好吧,既然这样,让我们磨练另一项关键的数据科学技能:

谷歌搜索是学生现在需要练习的技能。
感谢谷歌!所以“开源”是技术社区中广泛使用的一个短语,意思是让任何人都可以贡献和分发代码。
说到 Google,TensorFlow 是 Google Brain 团队打造的!
W 谷歌为什么要让 TensorFlow 开源?
嗯,与大多数开源项目一样,由于而不是是开源的,TensorFlow 将使自己处于巨大的劣势,因为竞争对手的深度学习框架,如 Theano、Keras 和 PyTorch 都是开源的。
它本质上允许 TensorFlow 从世界上一些最优秀的头脑中获得免费输入,以保持他们在最先进的技术上的竞争力。(不要担心,TensorFlow 对采用什么样的贡献有最终决定权,这样世界上最糟糕的想法也不能添加狗 sh * t——这就是开源项目的本质)。
就像我们描述的那样…
…用于机器学习的平台—
好吧,好吧,我知道我正在努力成为一名数据科学家,但是……实际上什么是机器学习?!
为了节省冗长的讨论…

具有讽刺意味的是,现在我已经向谷歌问了所有这些问题,我将在接下来的 4 周内看到机器学习课程的广告…机器学习(和 TensorFlow!!)在它最好的时候。
请注意机器学习和人工智能之间的区别。这两个术语经常互换使用,但正如你在这里看到的,机器学习实际上是人工智能的一个应用——但我们将在另一篇博客文章中讨论这个问题。
它[TensorFlow]拥有一个全面、灵活的工具生态系统、库和社区资源—
工具生态系统?图书馆?社区资源?
这有点像潘多拉的盒子,通常会突出更多需要学习的工具。但目前,我们可以理解为一个数据科学家使用了很多不同的软件工具,从清理和处理数据到开发和训练机器学习的模型。
TensorFlow 通过创建一个用户友好的框架来使用 Python、JavaScript 或 Swift 训练模型,从而帮助数据科学家导航这一工作流。然后,它让我们选择如何部署我们的工作——是在云中、在浏览器中还是在我们的设备上。点击查看更多相关信息。
社区资源符合开源环境的本质。TensorFlow 还通过 GitHub 和 Stack Overflow 提供大量关于如何最好地使用其平台的教程和资源,以及社区论坛和帮助请求。你可以在这里看到更多关于这个的内容。
图书馆。如果你以前没有遇到过图书馆或者从来不知道它们是什么—

所以简单地说,库只是一个函数的集合,这些函数做我们一直需要做的事情。
有一个编码原则,如果你一遍又一遍地写同样的代码,你可能需要把它放到一个函数中。记住这一点,把库想象成人们反复编写的所有函数,最后,有人想“如果我需要这个函数,其他人可能也需要这个函数”。
显然,数据科学家的工作中有非常重复的部分,因此有许多包含执行这项工作所需的所有功能的库。这里的是一些可以通过 TensorFlow 轻松访问的库和扩展。
…这使得研究人员可以推动 ML 的最新发展,开发人员可以轻松构建和部署 ML 支持的应用程序。"
虽然我们可能还没有任何机器学习的经验,但我们现在知道,当时机成熟时,我们有像 TensorFlow 这样的平台随时准备支持我们的努力。
随着你对这个行业和机器学习本身了解的越来越多,TensorFlow 的用途和应用会越来越明显。但总而言之,TensorFlow 是一个平台,它将许多其他行业标准工具联系在一起,创建一个工作流,使我们能够部署端到端的机器学习项目。
我鼓励你继续搜索并比较其他平台,如 PyTorch、Keras 和 Theano。最终,你将能够对你最喜欢使用哪些工具形成自己的看法。
快乐学习!
使用数据科学分析副总统和总统辩论
使用数据科学的辩论分析:使用 YouTube 评论发现选民的真实意图

I 简介
我相信数据科学让我以我从未想象过的方式表达我的好奇心。数据科学中最酷的事情是,我不是将数据视为数字,而是将数据视为机会(业务问题)洞察力(预测建模、统计和数据争论),以及改进(指标)。带着这个想法,我决定分析一下 VP 和总统辩论的 YouTube 评论。
在从新闻来源得到混杂的结果后,我想用数据科学来分析副总统和总统的辩论。
我们的想法是使用 YouTube 评论作为媒介,获取关于辩论的情绪,并从数据中获得见解。在这个分析中,我们绘制常用短语、常用词,我们还分析情绪,最后,我向我所有的数据科学从业者展示了一个完整的数据集,其中包含了 YouTube 上副总统的评论和总统辩论。
如何以及为什么
hy:在从新闻来源获得关于辩论结果的混合结果后,我决定使用数据科学来帮助我看到结果的结果。随着选举的临近,技术或者更准确地说是分析在塑造我们的思想和支持我们的假设方面发挥了关键作用。
为了分析 YouTube 评论,我们使用 Python 和其他各种 NLP 库,然后使用一些数据可视化工具。我们将使用被称为熊猫的令人敬畏的数据争论图书馆的奇迹,我们希望找到一些有趣的见解。
要求
对于这个项目,我们要求:
- Python 3.8
- 熊猫
- sci kit-学习
- Numpy
- 海生的
- NLTK
- Wordcloud
- 文本 Blob
数据集的创建
数据集包含 YouTube 上关于最受欢迎/最受关注的副总统和总统辩论的评论。我们使用 YouTube 数据 API 来获得所有评论(由于大小限制,我们每个视频只能获得 100 条评论)。这些视频是作者经过仔细审查挑选出来的,准确地说,我们关注的是最高浏览量和最高 YouTube 评论数。
def clean_text(string):
string = re.sub(r'[^\w\s]', '', string)
return ''.join(i for i in string if ord(i) < 128)def remove_stopwords(string):
stop_words = set(stopwords.words('english'))
word_tokens = word_tokenize(string)
filtered_sentence = [w for w in word_tokens if not w in stop_words]
filtered_sentence = []
for w in word_tokens:
if w not in stop_words:
filtered_sentence.append(w)return ' '.join(filtered_sentence)
这些函数定义了文本的清理和停用词的删除。
YouTube 评论提取
下面的函数定义了我们丢弃的 video _ ids 和一些基本的提取代码。我们以 JSON 的形式获取数据,然后执行预处理,最后将所有评论和情感评分(使用 TextBlob)组合成一个数据帧。
video_ids = {"vp_debate":['65twkjiwD90','t_G0ia3JOVs','xXE6I3gWiMc'], "prez_debate":[
'yW8nIA33-zY','wW1lY5jFNcQ','K8Z9Kqhrh5c']}video_id = "&videoId="
content = Nonedef extract_comments(resp):
"""
Get comments from the resp (json) yt comment
"""
com = []
for i in resp['items']:
com.append(i['snippet']['topLevelComment']['snippet']['textOriginal'].lower())
return comdef get_videos_comments():
"""
Extract video comments and store in the arrays
"""
vp_debate_data = []
prez_debate_data = []
for i,j in video_ids.items():
print("Getting for: ",i)
for id in j:
video_id = "&videoId=" + id
resp = requests.get(URL+video_id)
print("Length: ",len(resp.content))
content = resp.json()
comments = extract_comments(content)
if i == "vp_debate":
vp_debate_data.extend(comments)
else:
prez_debate_data.extend(comments)
return vp_debate_data, prez_debate_data
class Comment:
def __init__(self, text):
self.text = remove_stopwords(clean_text(text))
# set threshold as 0.5
self.sentiment = 0 if TextBlob(self.text).sentiment.subjectivity <= 0.5 else 1
def return_comment(self):
return selfclass DataGenerator:
def __init__(self, vp_debate_data, prez_debate_data):
self.vp_data = {"comments":[], "sentiment":[]}
self.prez_data = {"comments":[], "sentiment":[]}
for i in vp_debate_data:
c = Comment(i)
self.vp_data['comments'].append(c.text)
self.vp_data['sentiment'].append(c.sentiment)
for i in prez_debate_data:
c = Comment(i)
self.prez_data['comments'].append(c.text)
self.prez_data['sentiment'].append(c.sentiment)
self.df_vp = pd.DataFrame(self.vp_data)
self.df_prez = pd.DataFrame(self.prez_data)
def return_df(self):
print("Loaded dataframe.")
return self.df_vp, self.df_prez
# return corpus given the debate key
# to be used for getting the commonkeywords and plotting
def get_corpus(self, key="vp_debate"):
corpus = []
if key == "vp_debate":
corpus = [i for i in self.vp_data['comments']]
else:
corpus = [i for i in self.prez_data['comments']]
return corpus

我们数据的预览
分析
在检查数据时,我们首先将单词小写,然后去掉所有的标点符号,最后去掉所有的停用词。我们使用流行的文本块(【https://textblob.readthedocs.io/en/dev/】)来执行快速情感分析。

代表评论情感分析的条形图。
这些数据并没有提供太多的信息,但是如果我们试着用特定的词来分类或分组,那么我们可以得到更好的理解。
最常见的单词
我们找到每场辩论中最常见的单词(即 df_vp 和 df_prez)。我们使用称为 NLTK 的流行包来获取最常见的单词(经过预处理)
def most_common(corpus):
fd = nltk.FreqDist(corpus)
print(fd)
fd_t10=fd.most_common(10)
counter = dict(fd_t10)
# pd.Dataframe from dict to show bar plot
df = pd.DataFrame.from_dict(counter, orient='index')
names, values = zip(*fd_t10)
# plot method 1
fd.plot(10)
# plot method two
print("Bar Plot")
df.plot(kind='bar')

副总统辩论视频中的词频分布

总统辩论视频中的词频分布
正如你在总统辩论视频中看到的,最常见的词是“特朗普”和“拜登”。
WordCloud
词云或标签云是词频的图形表示,对在源文本中出现频率较高的词给予更大的重视。视觉效果中的单词越大,该单词在文档中就越常见。
def plot_wordcloud(kind="vp_debate"):
words = get_word_list(kind)
wordcloud = WordCloud(width = 800, height = 800,
background_color ='white',
min_font_size = 10).generate(' '.join(words))
plt.imshow(wordcloud)
plt.axis("off")
plt.tight_layout(pad = 0)
plt.show()

vp _ 辩论和 prez _ 辩论的词云表示
查找关键词:搜索特定单词的独特方式。
这个特性可以找到指定的关键字(最多前 10 个单词)。它作用于每个句子。
def find_keywords(word, df):
"""
Find the specified keyword (most prob top 10 words)
Acts on each sentence
Add to the corresponding dataframe
Returns: DataFrame
Used inconjunction with get_most_common_words()
"""
for i in df.comments:
if word in i.split():
c = Counter(i.split())
if c[word] >= 2:
print(f'Word is {word} that occured {c[word]} times where sentence is {i}\n')
[@interact](http://twitter.com/interact)(keyword="biden", debates=['vp_debate','prez_debate'])
def run(keyword, debates):
df = None
if debates == "vp_debate":
df = df_vp
else:
df = df_prez
find_keywords(keyword,df)

输出的一瞥。(提供演示)
结论
我们看到,通过使用数据科学,我们能够收集关于数据的有趣见解。我们当然可以通过使用更好的情感分类器和测试不同的假设来改进我们的工作。我们也可以使用 n-grams 并比较它们的频率。
试试这里:https://gesis . my binder . org/binder/v2/GH/aaditkapoor/debate analysis/main dataset 这里:http://www . ka ggle . com/dataset/43532333d 82042 a 1287 e 00672 b 86 a2 c 76 e 76 ffbc 4d 85569715309714635172 b 0
视频通话数十亿无互联网
通过蜂窝电话呼叫进行可靠的视频通话和屏幕共享

图片来自 iStockphoto,授权给 P.K. Mishra
本博客中表达的观点和意见纯属我个人的观点和意见,并不代表我现在或过去雇主的官方立场。
上个周末,我看到一篇新闻文章提到数百万印度学生被困在家里,既不能上网,也不能接受在线教育。事实上,超过一半的世界人口仍然没有任何互联网连接。虽然这种数字鸿沟已经在美国显现出来,特别是在冠状病毒疫情封锁期间的儿童教育方面,但亚洲和非洲的问题要严重得多,那里只有不到五分之一的人连接到互联网。
由于在全球范围内实施封锁,许多成人和儿童被排除在在线教育和远程保健之外。

WhatsApp 视频通话
尽管上面提到了五分之一的统计数据,但实际上问题更严重。即使对那些能上网的人来说,价格也很高,而且带宽有限。例如,在与我在印度的父母交谈时,他们经常在配额期之前用完分配给他们的 4 GB,此后带宽被节流:停滞的帧、断断续续的音频、令人痛苦的延迟和最终的断开,以及随后的重试是正常现象,但仍然可以说比正常的电话交谈好得多,因为我可以“看到”它们。
当安德鲁·斯图亚特解释说视频通话更有利于人们的心理健康时,我明白了。但是,视频通话通常需要 2 Mbps 的速度提升和 2 Mbps 的速度下降,就像 Zoom 的情况一样,许多人都无法享受这一特权。
为了解决这个问题,我提出了一种新的方法,这种方法基于这样一种认识,即如果我们愿意放弃一些面部和屏幕的现实主义或逼真的渲染,那么可以为超低带宽导出一个全新的面部和屏幕表示世界,并具有可接受的体验质量。

本文探讨了这样的表示和方法,它们将所需的带宽从正常的 2 Mbps 降低到 1.5 Kbps,允许视频与电话音频一起编码,而音频质量的下降最小。所提出的解决方案可以主要实现为软件,而不需要改变底层基础设施。这反过来会更便宜,并允许目前因支付能力而被边缘化的人接入互联网。
移动电话与互联网
如果你确信手机通话比 VoIP 通话更可靠、更快捷、更好,你可以跳过这一部分
从轶事经验来看,很明显,蜂窝电话呼叫在许多属性上通常比视频呼叫更可靠,例如延迟、掉线、易于使用、呼叫质量变化最小以及长途选项。
像 Google Meet、Discord、Gotomeeting、Amazon Chime 和潜在的脸书这样的视频电话提供商使用一种叫做 WebRTC 的协议,这种协议可以实现两方或多方之间的实时通信。WebRTC 还依赖于一种称为用户数据报协议的互联网协议,该协议提供了快速的数据包传递,但提供的保证较差,导致一些数据包要么没有到达,要么没有按顺序到达,从而导致帧丢失和抖动(对等点之间数据包流的延迟变化)。较新的机器学习技术,如在 Google Duo 中使用 WaveNetEq,提高了音频质量,但视频质量变化仍未解决。
另一方面,虽然蜂窝电话呼叫也越来越多地基于分组,但工程师们已经找到了通过不断确保严格的质量标准和巧妙的带宽管理来提高分组交换手机网络效率的方法。蜂窝电话工作良好的另一个原因是音频所需的带宽比视频低得多。此外,编码标准已经发展了几十年,允许人脑填补空白。我将最终证明,互联网视频通话的许多目标,如双向视频通话、多方视频广播和直播事件、屏幕共享、体验质量的 A/B 测试(QoE)⁷)也可以通过蜂窝电话实现。然而,在本文的其余部分,我们将只关注双向视频通话,以便做好准备。
架构:电话视频
教育视频的主要目标是获得最小的视频表示,优先考虑信息传输(例如白板、草图),其次是人类动作(例如指点、书写)和表情(例如嘴唇、眉毛),,同时排除干扰元素,例如背景、面部的无表情元素,以及恼人的视频呼叫元素,例如定格、可变帧速率等。另一个架构目标是优先考虑具有低延迟和低抖动(平滑和一致)以及高音频质量的可靠帧速率。

可用于视频通话的绝对最差(最低)带宽的信封背面计算
在任何电话呼叫中,人类对延迟的容忍度最多为 200 毫秒。对于电话呼叫,所有的数据包都无关紧要,因为编码方案非常复杂,即使一些数据包丢失,呼叫者仍然可以听到。复杂的方案可以处理基于可用带宽自适应调整的多种传输速率。因此,自适应多码率编解码器 (AMR)大行其道。AMR 编解码器使用八种源编解码器,比特率分别为 12.2、10.2、7.95、7.40、6.70、5.90、5.15 和 4.75 Kbps。对于我们的分析,我们选择 7.95 Kbps 作为最差情况分析。假设帧率为每秒 24 帧,视频压缩比为 100 (H.264 有损视频压缩可高达 200),我们得到一个大约 1kb/帧的未压缩目标作为信封背面计算。假设音频传输速率为 7.5 kbps,其中 3.2 kbps 分配给双向视频传输,4.75 kbps 分配给音频传输。如果我们要达到这个最坏情况的设计目标,我们肯定可以做得更好。
视频捕捉
第一步是拍摄视频。对于我的原型,我以每秒 24 帧的速度捕捉了 640x480 帧大小的视频,因为这个频率对于获得美学电影般的运动特征来说已经足够好了。
接近真实的视频表现
我尝试了不同的视频格式,但我决定采用二值图像(每个像素不是黑就是白)格式,使用简单的全局阈值来获得下面的视频。还有其他可用的二级阈值技术,如自适应均值阈值和自适应高斯阈值,后者噪声较小,但我喜欢将高级视频处理推迟到分割之后,因为我认为最好优先处理某些区域(如面部、白板),而不是全局方法。如果神经计算在发送方是不可能的,那么一个简单的全局阈值可能会起作用。
使用 OpenCV 以 24fps 的简单二值阈值视频
我还试验了 ASCII 渲染格式,如下图所示。
使用 iDevelop Github 提供的 HTML5 getUserMedia API 的 Ascii 视频表示:【https://github.com/idevelop/ascii-camera
此外,我尝试了神经风格转移技术,使用基于 paper⁶的示例代码和由 Justin Johnson 、 Alexandre Alahi 、李飞飞提供的 Github 代码,但最终我出于完全实用的原因决定追求二值图像。
视频语义分割
这里的核心思想是,我们可以使用深度学习来分割和优先考虑一帧的特定区域。在这种情况下,我按优先级降序排列了以下部分:白板、手、脸、躯干和背景。

一帧原始图像

分段训练样本
我使用 Superannotate 来注释我的视频,并结合 OpenCV 和达芬奇解析 16 来增强分割的视频帧。
基于片段的视频增强
下一步是移除背景,然后是曝光、阴影和对比度增强。然而,确切的参数高度依赖于照明和曝光,因此我为我的训练样本开发了一些启发法。
我决定应用 S 曲线增强的试探法,而不是涉及曝光、阴影和对比度增强的多个步骤。

s 曲线增强,通过调整帧的色调属性来增加对比度
在 RGB 图像的色调直方图中,左下象限表示阴影,而右上象限表示高光。一个简单的试探是将阴影区域中的色调减少大约一个标准偏差,并将高亮区域中的色调增加大约两个标准偏差。

设置阈值
在设置二值图像的阈值时,我应用了将它设置在分布峰值的启发式方法。这种启发法似乎至少适用于夜间照明。

简单全局阈值和人工智能分割阈值。请注意白板的背景移除和清晰度
总体而言,基于分割视频帧的局部视频预处理提供了具有更高清晰度的二值图像。
矢量化
下一步是将二值像素化图像转换成矢量图像。由于基于矢量的图像不是由特定数量的点组成的,因此它们不仅可以在不损失任何质量的情况下缩放到更大的尺寸,还可以显著减少图像的占用空间。

左边是原始像素帧,右边是矢量帧

放大脸部:左边是原始像素帧,右边是矢量帧

放大白板:左边是原始像素框架,右边是矢量框架
编码
编码是压缩数据以适应可用的通信带宽以及满足延迟要求所必需的。大多数视频编码标准是基于离散余弦变换(DCT)的。像 H.264 标准这样的转换可以将视频压缩 200 倍,但是 H.264 不仅计算量很大,而且是为“自然”渲染而构建的。出于这个原因,我决定探索视频编码器优化我们的具体用途。视频压缩技术包括空间压缩和时间压缩,空间压缩利用单个帧内的冗余,时间压缩利用帧间的冗余,例如运动冗余。目前,我正在尝试不同的编码技术,以获得体验质量和压缩之间的平衡。这项研究包括基于二级位图(像素)的帧以及基于矢量的帧。流行的二值位图图像压缩方法有游程编码,熵编码如算术编码,以及二值图像的 JBIG2 标准。接下来,我将研究利用运动冗余。
解码和渲染
在接收端解码压缩流视频后,需要在智能手机或电视等大屏幕上呈现视频。矢量表示很容易缩放,并且很适合表示白板信息,但是它们会导致不真实的人脸渲染,这对于我们的目的来说可能很好。但是,我们需要测试体验的质量来证实这一点。另一方面,基于位图的表示在缩放时会导致像素化。应该注意的是,有一些简单的算法可以用于增强这些帧,例如最近邻增强、双三次增强、锐化和去除伪像。
总之,我从 640 x 480 的 RGB 图像(~ 1mb/帧)开始,并将其转换为二值图像(~ 38kb/帧)。矢量化进一步将帧大小减少到大约 10kb/帧。虽然这比我们之前讨论的 1kb/帧的最坏情况目标还远,但我相信有机会达到该目标。通过使用利用冗余的定制压缩技术(例如没有背景)或通过极端矢量化中的进一步机会(例如具有线条画),这是可能的。
研究挑战和后续步骤
基于发送端或接收端的可用计算以及可用的通信带宽,有许多方法值得研究。

基于发送方或接收方可用计算的潜在方法
视频优化
研究领域包括优化空间频率周围的视频编码,以更好地解释人类 actions⁴和面部表情,如面部动作编码 Systems⁵.
小说表述
ASCII、3D 化身和其他人类可理解的表示法等简单表示法值得研究。基于端到端人工智能的方法,如使用自动编码器可以用来生成紧凑的中间表示,尽管中间潜在空间编码不能被人类理解。在所有方法中,我最看好自动编码器和相关技术。另一种方法是面部动作编码系统 (FACS),其中中间表示由控制不同肌肉和运动的动作单元(au)组成。这样的 FACS 中间表示实际上可以以大约 44 个 AUs 编码面部和位置,3 个用于 XYZ 平移,3 个用于角度,导致每帧 50 个浮点或 200 个字节,这在压缩后可以低得多,几乎肯定比 1kb/帧的目标低得多。这种编码可用于在接收者端呈现化身。后两种方法在发送方和接收方都需要大量的计算。
音频编码
这是我认为最大的挑战。在呼叫的任何一端都有大量的音频编码标准,如增强语音服务、可变速率多模式宽带、多速率宽带、增强可变速率编解码器、GSM 增强全速率等。当一个呼叫从一个国家的一个运营商发起,并在另一个国家的另一个运营商结束时,流式音频的质量会因使用的编码和连接质量的不同而有很大差异。面临的挑战是将视频与音频一起编码,以便视频可以被可靠地解码并在传输中“幸存”。
新型应用的框架
有许多新颖的应用,例如使用矢量图形动画的交互式教学方法,甚至极简主义的增强现实以及视频和音频呼叫。
来源维基百科:https://en . Wikipedia . org/wiki/File:Completing _ the _ square . gif
用户:打造安卓 app 做试点。
如果您能提供一些反馈,或者将这篇文章转发给可能拥有资源和人才来开发完整解决方案和可在发展中国家部署用于教育目的的 Android 应用程序的个人或公司,我将不胜感激。在过去的周末,我一直在处理这个问题,对我来说,快速开发一个解决方案是不切实际的。我提前感谢你的帮助!
[1]劳伦·e·谢尔曼;米纳斯·米奇扬;帕特里夏·格林菲尔德; 文字、音频、视频、当面交流对朋友间粘合的影响 。网络心理学:网络空间心理社会研究杂志,2013,7 (2),第 3 条。
[2]加西亚,b;GallegoGortázar,f;Bertolino,a ., 理解和评估 WebRTC 应用程序的体验质量。计算 2018,101,1–23。
[3]帕布罗·巴雷拉,软件工程师,谷歌研究院和弗洛里安·斯廷伯格,研究工程师,DeepMind, 用 WaveNetEQ ,2020 年 4 月 1 日,谷歌 AI 博客。
[4]Steven m . Thurman Emily d . Grossman,诊断空间频率和人类辨别行动的效率,2010 年 11 月 16 日,Springer,注意力、知觉和心理物理学。
[5]保罗·艾克曼,面部动作编码系统,1978 年。
[6] 贾斯廷·约翰逊,亚历山大阿拉希,李菲菲,实时风格传递和超分辨率的感知损失,2016 年,欧洲计算机视觉大会。
[7]朱莉(诺瓦克)贝克利,安迪·海因斯,,马修·沃德罗普,托比·毛,马丁·廷利,[大规模流媒体实验的数据压缩](http://Data Compression for Large-Scale Streaming Experimentation),2019 年 12 月 2 日,科技博客。
使用 Fast.ai 和 OpenCV 进行视频面部表情和意识检测
从实时视频或视频文件中检测面部表情和意识
这背后的灵感?联邦调查局特工通过我的网络摄像头监视我,但被深度学习取代:)
实时视频分类器演示
介绍
本教程的目标?使用 fast.ai 库训练面部表情分类模型,从网络摄像头或视频文件中读取面部表情,最后,添加面部地标来跟踪你的眼睛以确定意识!(TL;博士完整的工作代码在这里【https://github.com/jy6zheng/FacialExpressionRecognition
我写这篇教程的主要原因是,在做这个项目时,一个很大的挑战是弄清楚如何使用我训练过的分类器,并使它有效地处理实时视频和视频文件。额外的眼部标记功能是基于我发现非常有用的本教程:https://www . pyimagesearch . com/2017/05/08/sledy-detection-opencv/
培养
第一步是用卷积神经网络训练图像分类模型。我使用的数据来自https://www . ka ggle . com/jonathanoheix/face-expression-recognition-dataset
我使用了构建在 PyTorch 之上的 fast.ai 库来训练我的分类模型。该模型使用 resnet34 预训练权重和训练数据集进行训练,并导出为. pkl 文件。关于一步一步的指导,请查看我的知识库中的 google colab 笔记本,它包含了训练你的模型的所有代码:https://github.com/jy6zheng/FacialExpressionRecognition
最大的挑战是首先找到一个公共数据集,然后清理数据。最初,当我使用 Kaggle 数据集时,我只能训练到 0.328191 的错误率,这意味着它只有大约 68%的时间是正确的(根本不是很好)。当我绘制产生最高损失的图像时,我很快意识到大量数据被错误地标记(左边是模型预测的表达,右边是标记的情绪)。

坐在最后一排左三的女孩看起来显然不开心
数据清洗后,错误率下降了 16%以上。现在分类器有大约 84%的准确率,这意味着它正确地识别了 84%的面部图像。仍有一些不正确和不完整的数据,因此还有进一步改进的空间。


正如你所看到的,中性和悲伤的脸最容易混淆
在实况视频上使用训练模型
现在,是时候将我们的分类器用于现场视频流了。首先,最好创建一个虚拟环境,这样这个项目就有自己的依赖项,并且不会干扰任何其他项目。然后,下载所需的包和库。创建一个名为 liveVideoFrame.py 的文件(或您想命名的任何名称),并导入以下内容:
from scipy.spatial
import distance as dist
import numpy as np
import cv2
from imutils import face_utils
from imutils.video import VideoStream
from fastai.vision import *
import imutils
import argparse
import time
import dlib
我想选择将预测保存在一个. csv 文件中,并保存带标记的视频,所以我添加了参数解析。我还导出了经过训练的分类模型,并将其移动到我的工作目录中。
ap = argparse.ArgumentParser()
ap.add_argument("--save", dest="save", action = "store_true")
ap.add_argument("--no-save", dest="save", action = "store_false")
ap.set_defaults(save = False)
ap.add_argument("--savedata", dest="savedata", action = "store_true")
ap.add_argument("--no-savedata", dest="savedata", action = "store_false")
ap.set_defaults(savedata = False)
args = vars(ap.parse_args())path = '/Users/joycezheng/FacialRecognitionVideo/' #change this depending on the path of your exported model
learn = load_learner(path, 'export.pkl')
太好了!现在是时候开始我们的视频流了。我使用 imutils.video 的 VideoStream,因为我发现它比 cv2.VideoCapture 工作得更快。注意:对于内置的网络摄像头,视频流的来源是 0,如果您使用不同的相机,如插件,它会有所不同。
haar 级联分类器用于识别视频帧中的正面人脸。我们有一个名为 data 的数组来存储我们的预测。timer 和 time_value 用于标记数据中每个预测的时间,以便在. csv 文件中预测以 1 递增。
face_cascade = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")
vs = VideoStream(src=0).start()
start = time.perf_counter()
data = []
time_value = 0
if args["save"]:
out = cv2.VideoWriter(path + "liveoutput.avi", cv2.VideoWriter_fourcc('M','J','P','G'), 10, (450,253))
现在,我们将实现一个 while 循环,从视频流中读取每一帧:
- 因为图像分类器是在灰度图像上训练的,所以每一帧都被转换成灰度图像
- 级联分类器用于在帧中寻找人脸。我将 minneighbors 参数设置为 5,因为我发现它在实时视频上效果最好。对于录制的视频文件,我将其设置为较高的值,因为每一帧中肯定会有一张脸
- 由于我们的分类器是在没有太多背景的特写人脸上训练的,因此灰度图像随后被裁剪为具有 0.3 缓冲的人脸
- 然后,在每一帧上绘制并显示文本和边界框
- 然后使用 out.write(frame)将每一帧保存到视频编写器中
while True:
frame = vs.read()
frame = imutils.resize(frame, width=450)
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
face_coord = face_cascade.detectMultiScale(gray, 1.1, 5, minSize=(30, 30))
for coords in face_coord:
X, Y, w, h = coords
H, W, _ = frame.shape
X_1, X_2 = (max(0, X - int(w * 0.3)), min(X + int(1.3 * w), W))
Y_1, Y_2 = (max(0, Y - int(0.3 * h)), min(Y + int(1.3 * h), H))
img_cp = gray[Y_1:Y_2, X_1:X_2].copy()
prediction, idx, probability = learn.predict(Image(pil2tensor(img_cp, np.float32).div_(225)))
cv2.rectangle(
img=frame,
pt1=(X_1, Y_1),
pt2=(X_2, Y_2),
color=(128, 128, 0),
thickness=2,
)
cv2.putText(frame, str(prediction), (10, frame.shape[0] - 25), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (225, 255, 255), 2) cv2.imshow("frame", frame) if args["save"]:
out.write(frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
breakvs.stop()
if args["save"]:
print("done saving video")
out.release()
cv2.destroyAllWindows()
现在,我们有了 fast.ai 学习模型,它可以与 imutils 和 OpenCV 合作,从直播视频中预测人脸!
接下来,是时候确定面部的意识了。函数 eye_aspect_ratio 根据眼睛的坐标计算眼睛的纵横比。从 dlib 预训练的面部标志检测器中找到每只眼睛的位置和坐标。函数 data_time 用于每隔 1 秒在数据数组中追加预测。
EYE_AR_THRESH = 0.20
EYE_AR_CONSEC_FRAMES = 10COUNTER = 0def eye_aspect_ratio(eye):
A = dist.euclidean(eye[1], eye[5])
B = dist.euclidean(eye[2], eye[4])
C = dist.euclidean(eye[0], eye[3])
ear = (A + B) / (2.0 * C)
return eardef data_time(time_value, prediction, probability, ear):
current_time = int(time.perf_counter()-start)
if current_time != time_value:
data.append([current_time, prediction, probability, ear])
time_value = current_time
return time_valuepredictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")(lStart, lEnd) = face_utils.FACIAL_LANDMARKS_IDXS["left_eye"]
(rStart, rEnd) = face_utils.FACIAL_LANDMARKS_IDXS["right_eye"]
在遍历面坐标的 for 循环中,添加以下代码块。使用 dlib 面部标志检测器检测眼睛,并将其绘制到帧上。当两只眼睛之间的平均计算眼睛纵横比小于超过十个连续帧的阈值(您可以根据自己的喜好修改该阈值)时,则该面部被标记为分心。
rect = dlib.rectangle(X, Y, X+w, Y+h)
shape = predictor(gray, rect)
shape = face_utils.shape_to_np(shape)
leftEye = shape[lStart:lEnd]
rightEye = shape[rStart:rEnd]
leftEAR = eye_aspect_ratio(leftEye)
rightEAR = eye_aspect_ratio(rightEye)
ear = (leftEAR + rightEAR) / 2.0
leftEyeHull = cv2.convexHull(leftEye)
rightEyeHull = cv2.convexHull(rightEye)
cv2.drawContours(frame, [leftEyeHull], -1, (0, 255, 0), 1)
cv2.drawContours(frame, [rightEyeHull], -1, (0, 255, 0), 1)
if ear < EYE_AR_THRESH:
COUNTER += 1
if COUNTER >= EYE_AR_CONSEC_FRAMES:
cv2.putText(frame, "Distracted", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
else:
COUNTER = 0
cv2.putText(frame, "Eye Ratio: {:.2f}".format(ear), (250, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
time_value = data_time(time_value, prediction, probability, ear)
最后,在代码的底部,我们可以将数据保存为数据帧,然后保存为. csv 文件。
if args["savedata"]:
df = pd.DataFrame(data, columns = ['Time (seconds)', 'Expression', 'Probability', 'EAR'])
df.to_csv(path+'/exportlive.csv')
print("data saved to exportlive.csv")
您可以通过运行以下命令在命令行中测试代码:
python liveVideoFrameRead.py --save --savedata
完整的代码在这里:
对视频文件使用训练模型
我对视频文件使用了与实况视频非常相似的方法。主要区别在于预测每隔一定数量的帧发生一次,这可以使用命令行参数— frame-step 来修改。完整代码如下:
就是这样!你现在可以从视频文件和网络摄像头中预测面部表情。
谢谢你看了这个:),如果有什么改进或者问题请告诉我。完整的工作代码在这里:https://github.com/jy6zheng/FacialExpressionRecognition
使用深度学习和 PyTorch (-lightning)的视频预测
卷积 LSTM 模型的简单实现

托马斯·威廉在 Unsplash拍摄的照片
在本指南中,我将向您展示如何使用一个自动编码器 (seq2seq)架构对卷积长短期记忆(ConvLSTM)进行编码,该架构使用 MovingMNIST 数据集进行帧预测(但也可以轻松集成自定义数据集)。
这种方法最初用于 2015 年NIPS的降水预报,此后通过 PredRNN、PredRNN++和 Eidetic 3D LSTM 等方法得到了广泛的扩展…
我们还使用了 pytorch-lightning 框架,这对于删除大量样板代码非常有用,并且可以轻松集成 16 位训练和多 GPU 训练。
开始之前,我们将简要概述一下我们正在使用的库:
python=3.6.8 torch=1.1.0 torchvision=0.3.0 pytorch-lightning=0.7.1 matplotlib=3.1.3 tensorboard=1.15.0a20190708
1.数据加载器
从以下 repotychovdo/moving NIST下载数据加载器脚本。
该数据集最初是由开发并在此处描述的,它包含 10000 个序列,每个序列长度为 20,帧大小为 64 x 64,显示 2 个数字在不同轨迹上移动(并重叠)。
需要预先注意的是手指轨迹固有的随机性。我们确实期望这将成为我们将要描述的模型的一个主要障碍,并且我们也注意到更新的方法,例如变化的自动编码器,对于这种类型的任务可能是一个更有效的模型。
2.模型架构
我们将使用的特定模型类型称为 seq2seq 模型,通常用于 NLP 或时序任务(它实际上是在 2016 年的谷歌翻译引擎中实现的)。
关于 seq2seq 的原论文有 Sutskever 等人,2014 和 Cho 等人,2014 。
在其最简单的配置中,seq2seq 模型将一系列项目作为输入(例如单词、单词嵌入、字母等。)并输出另一个项目序列。对于机器翻译,输入可以是西班牙语单词序列,输出是英语翻译。
我们可以将 seq2seq 模型分成三个部分,分别是
a)编码器(编码输入列表)
b)编码器嵌入向量(整个输入序列的最终嵌入)
c)解码器(将嵌入向量解码成输出序列)
对于我们的机器翻译示例,这意味着:
- 编码器通过顺序处理每个单词,将西班牙语序列作为输入
- 编码器输出一个嵌入向量作为我们输入的最终表示
- 解码器将嵌入向量作为输入,然后输出英语翻译序列
希望 a)部分和 c)部分对你来说比较清楚。就 seq2seq 模型的直觉而言,可以说最棘手的部分是编码器嵌入向量。你如何准确定义这个向量?
2.1 认识 RNN
在你继续前进之前,我强烈推荐以下关于 RNN/LSTM 的精彩博文。密切了解 LSTM 的是大多数 seq2seq 模型的必要先决条件!
以下是常规 LSTM 电池的方程式:

其中∘表示哈达玛乘积。
让我们假设你完全理解什么是 LSTM 细胞,以及细胞状态和隐藏状态是如何工作的。seq2seq 型号中的编码器和解码器通常由 LSTM 单元组成,如下图所示:

分解
- LSTM 编码器由 4 个 LSTM 单元组成,LSTM 解码器由 4 个 LSTM 单元组成。
- 每个输入(单词或单词嵌入)与来自先前 LSTM 单元的隐藏状态(输出)一起被馈送到新的编码器 LSTM 单元
- 来自最终 LSTM 编码器单元的隐藏状态(通常)是编码器嵌入。它也可以是来自所有编码器 LSTM 单元的隐藏状态的整个序列(注意——这与注意不同)
- LSTM 解码器使用编码器状态作为输入,并通过各种 LSTM 单元迭代处理这些状态以产生输出。这可以是单向的或双向的
存在对标准 seq2seq 模型的几个扩展;最引人注目的是注意力模块。
讨论了 seq2seq 模型之后,让我们将注意力转向帧预测任务!
2.2 帧预测
帧预测与 seq2seq 原有的任务如机器翻译有着本质的区别。这是因为编码器和解码器中的 RNN 模块(LSTM)使用全连接层来编码和解码单词嵌入(表示为向量)。
一旦我们处理了帧,我们就有了 2D 张量,为了按顺序编码和解码,我们需要扩展原始的 LSTM seq2seq 模型。
ConvLSTM
这就是卷积 LSTM(convltm)的用武之地。在 2015 年的 NIPS 上展示,ConvLSTM 修改了 LSTM 机制的内部工作方式,使用卷积运算而不是简单的矩阵乘法。让我们为 ConvLSTM 单元写出新的方程:

∫表示卷积运算,∘表示阿达玛乘积,如前所述。
你能发现这些方程和常规 LSTM 之间的细微差别吗?我们简单地替换四个门之间的乘法运算
a)权重矩阵和输入 (Wₓ xₜ 与、Wₓ 、和
)b)权重矩阵和先前隐藏状态( Wₕ hₜ₋₁ 与 Wₕ 、 Hₜ₋₁ )。
否则,一切照旧。
如果你不想深究上面的方程,首先要注意的是,我们使用卷积(核)来处理我们的输入图像,以获得特征图,而不是从完全连接的层中获得的矢量。
2.2.2 n 步预测
设计帧预测模型(使用 ConvLSTM)时,最困难的事情之一是定义如何产生帧预测。我们在这里列出了两种方法(但也存在其他方法):
- 预测下一帧,并将其反馈到网络中,经过多个 n 步骤,产生 n 帧预测(自回归)
- 通过使 ConvLSTM 层的数量 l 等于 n 步的数量,一次性预测所有未来的时间步。因此,我们可以简单地使用每个解码器 LSTM 单元的输出作为我们的预测。
在本教程中,我们将重点关注数字 1,特别是因为它可以在未来产生任意数量的预测,而不必完全改变架构。此外,如果我们要预测未来的许多步骤,选项 2 在计算上变得越来越昂贵。
2.2.3 ConvLSTM 实施
对于我们的 ConvLSTM 实现,我们使用来自 ndrplz 的 PyTorch 实现
它看起来如下:
希望您可以看到前面定义的方程是如何在上面的代码中编写的。
Seq2Seq 实施
我们使用的具体架构如下:

编码器和解码器
编码器和解码器使用两个 ConvLSTM 单元(encoder_1_convlstm、encoder_2_convlstm、decoder_1_convlstm、decoder_2_convlstm)。
3D CNN
我们的最终 ConvLSTM 单元(decoder_2 convlstm)为每个预测帧(12、10、64、64、64)输出 _nf 特征图。
因为我们本质上是在做回归(预测像素值),所以我们需要将这些特征图转换成实际的预测,类似于您在经典图像分类中所做的。
为了实现这一点,我们实现了一个 3D-CNN 层。3D CNN 层执行以下操作:
- 将作为每个批次和时间步长的输入(nf、宽度、高度)
- 使用 3D 内核迭代所有 n 个预测帧
- 每个图像输出一个通道(1,宽度,高度),即预测的像素值
乙状结肠层
最后,由于我们已经将像素值转换为[0,1],我们使用 sigmoid 函数将我们的 3D CNN 激活转换为[0,1]。
基本上就是这样!
现在我们为 seq2seq 模型定义 python 实现:
3.培养
也许你已经知道 pytorch-lightning 这个优秀的库,它在使用 pytorch 时基本上把所有的锅炉板工程从机器学习中去掉了,比如下面的命令:optimizer.zero_grad()、optimizer.step()。
它还标准化了培训模块,并支持针对 Volta 架构 GPU 卡的轻松多 GPU 功能和混合精度培训。
pytorch-lightning 中有如此多的可用功能,我将尝试演示我创建的工作流,我认为它工作得相当好。
class MovingMNISTLightning的大多数功能都是不言自明的。以下是整体工作流程:
- 我们实例化我们的类并定义所有相关的参数
- 我们采取训练步骤(针对每一批)
- 创建预测 y_hat
- 计算 MSE 损失—
- 在 tensorboard 中每 250 个全局步长保存一个带有输入和地面实况的预测可视化
- 将每批的学习率和损耗保存到 tensorboard 中
当我们实际运行我们的main.py脚本时,我们可以定义几个相关的参数。例如,如果我们想要使用 2 个 GPU 运行,混合精度且 batch_size = 16,我们只需键入:
python main.py --n_gpus**=**2 --use_amp**=**True --batch_size**=**16
请随意尝试各种配置!
当我们运行main.py脚本时,我们会使用多重处理自动启动 tensorboard 会话,在这里您可以迭代跟踪我们模型的性能,还可以看到我们每 250 个全局步长的预测可视化。
感谢阅读这篇文章!我希望你喜欢它!
如果您对上述论文有任何问题或评论,请联系这里或在 Twitter 上。你也可以在我的网页上找到更多的教程https://holmdk.github.io/。
用 5 行代码实现视频分割
视频的语义和实例分割

PixelLib 的第一个版本是为了使用几行代码执行图像分割而构建的。我很兴奋地宣布,新发布的 PixelLib 版本支持五行代码的视频分段。
如果你没有读过关于 PixelLib 图像分割的文章,点击这里。
安装 tensorflow,包括:
- pip3 安装张量流
安装 PixelLib 与:
- pip3 安装 pixellib —升级
视频的语义分割:
基于 pascal voc 模型的视频分割;
我们将在下面解释每一行代码。
import pixellibfrom pixellib.semantic import semantic_segmentationsegment_video = semantic_segmentation()
我们导入了用于执行语义分段的类,并创建了该类的一个实例。
segment_video.load_pascalvoc_model("deeplabv3_xception_tf_dim_ordering_tf_kernels.h5")
我们加载了在 pascal voc 数据集上训练的 xception 模型来执行语义分割,它可以从这里下载。
segment_video.process_video_pascalvoc("video_path", overlay = True, frames_per_second= 15, output_video_name="path_to_output_video")
我们调用函数对视频文件进行分割。
它采用以下参数:-
- video_path :我们要分割的视频文件的路径。
- 每秒帧数:该参数用于设置保存的视频文件每秒帧数。在这种情况下,它被设置为 15,即保存的视频文件每秒将有 15 帧。
- 输出 _ 视频 _ 名称:保存的分段视频的名称。输出的视频会保存在你当前的工作目录下。
sample _ video 1
输出视频
这是使用 pascal voc 模型保存的分段视频。
现场摄像机的语义分割。
我们可以使用相同的模型在相机上执行语义分割。这可以通过对用于处理视频文件的代码进行少量修改来实现。
import cv2capture = cv2.VideoCapture(0)
我们导入了 cv2 并包含了捕捉相机帧的代码。
segment_video.process_camera_pascalvoc(capture, overlay = True, frames_per_second= 15, output_video_name="output_video.mp4", show_frames= True,frame_name= "frame")
在执行分割的代码中,我们将视频的文件路径替换为捕获,即我们正在处理摄像机捕获的帧流,而不是视频文件。我们添加了额外的参数来显示相机的帧:
- 显示帧:该参数处理分段摄像机帧的显示,按 q 退出帧的显示。
- 帧名:这是显示的摄像机帧的名称。
展示 pixelLib 使用 pascal voc 模型对摄像机输入进行语义分割的输出的演示。
干得好!它能够成功地将我和我面前的塑料瓶分割开来。
视频实例分割:
import pixellib
from pixellib.instance import instance_segmentation segment_video = instance_segmentation()
我们导入类来执行实例分段,并创建了类的一个实例。
segment_video.load_model("mask_rcnn_coco.h5")
我们加载了在 coco 数据集上训练的 maskrcnn 模型来执行实例分割,它可以从这里下载。
segment_video.process_video("video_path", frames_per_second = 20, output_video_name = "output_video.mp4")
我们调用该函数对视频文件执行实例分割。
它采用以下参数:-
- video_path: 这是我们想要分割的视频文件的路径。
- 每秒帧数:该参数用于设置保存的视频文件每秒帧数。在这种情况下,它被设置为 15,即保存的视频文件每秒将有 15 帧。
- 输出 _ 视频 _ 名称:这是保存的分段视频的名称。输出的视频将保存在您当前的工作目录中。
样本 _ 视频 2
我们可以通过将参数 show_bboxes 设置为 true 来实现实例分割。
输出视频
现场摄像机的实例分割。
我们可以在摄像机的帧上执行实例分割。这可以通过对用于处理视频文件的代码进行少量修改来实现。
import cv2 capture = cv2.VideoCapture(0)
我们导入了 cv2 并包含了捕捉相机帧的代码。
segment_video.process_camera(capture, show_bboxes = True, frames_per_second = 15, output_video_name = "output_video.mp4", show_frames = True, frame_name = "frame")
在执行分段的代码中,我们替换了要捕获的视频文件路径,也就是说,我们正在处理摄像机捕获的帧流,而不是视频文件。为了显示摄像机画面,我们添加了额外的参数:
- 显示帧:该参数处理分段摄像机帧的显示,按 q 退出帧显示
- 帧名:这是显示的摄像机帧的名称。
一个展示 pixelLib 使用 Mask-RCNN 对摄像机输入进行实例分割的输出的演示。
干得好!它能够成功地检测到我和我的手机。
通过以下方式联系我:
推特: @AyoolaOlafenwa
脸书:阿尤拉·奥拉芬娃
Linkedin: 阿尤拉·奥拉芬娃
使用 OpenCV & Flask 在 Web 浏览器中进行视频流传输
借助计算机视觉,从 IP 摄像机或网络摄像头传输实时视频

罗布·萨米恩托在 Unsplash 上的照片
你不厌其烦地在家里、办公室或任何你拥有的地方安装网络摄像头或监控摄像机。显然,你希望能够随时随地观看视频直播。
大多数人使用 IP 摄像机 (网络摄像机)而不是 CCTV (闭路电视)进行监控,因为它们的分辨率更高,布线成本更低。你可以在这里找到这两个系统的详细区别。在本文中,我们将重点关注 IP 摄像机。
IP 摄像机是一种数字视频摄像机,它通过 IP 网络接收控制数据并发送图像数据,不需要本地记录设备。大多数 IP 摄像机都是基于【RTSP】(实时流协议)的,因此在互联网浏览器中“不支持”。****

图片由《RTSP 议定书》作者提供
那么,你如何使用网络浏览器观看直播呢?
在这篇文章中,我们将学习如何使用 计算机视觉 来做到这一点。
计算机视觉是一个跨学科领域,研究如何让计算机从数字图像或视频中获得高层次的理解。
为了实现计算机视觉部分,我们将使用 Python 中的 OpenCV 模块,为了在网络浏览器中显示实时流,我们将使用 Flask 网络框架。在进入编码部分之前,让我们先简单了解一下这些模块。如果你已经熟悉这些模块,你可以直接跳到下一节。
根据维基百科,Flask 是一个用 Python 编写的微型 web 框架。它被归类为微框架,因为它不需要特殊的工具或库。它没有数据库抽象层、表单验证或任何其他组件,而现有的第三方库提供了通用功能。
根据 GeeksForGeeks 的说法,OpenCV 是用于计算机视觉、机器学习和图像处理的巨大开源库,现在它在实时操作中发挥着重要作用,这在当今的系统中非常重要。
步骤 1-安装烧瓶& OpenCV :
您可以使用' pip install flask '和'pip install opencv-python命令。我使用 PyCharm IDE 开发 flask 应用程序。 要在 PyCharm 中轻松安装库,请遵循以下步骤。**
步骤 2- 导入必要的库,初始化 flask app :
我们现在将导入必要的库并初始化我们的 flask 应用程序。
****#Import necessary libraries**
from flask import Flask, render_template, Response
import cv2
**#Initialize the Flask app**
app = Flask(__name__)**
第 3 步-使用 OpenCV 捕获视频:
创建一个 VideoCapture()对象来触发摄像机并读取视频的第一个图像/帧。我们既可以提供视频文件的路径,也可以使用数字来指定使用本地网络摄像头。为了触发网络摄像头,我们传递“0”作为参数。为了捕捉来自 IP 摄像机的实时反馈,我们提供了 RTSP 链接作为参数。要知道你的 IP 摄像机的 RTSP 地址,请浏览这个— 查找 RTSP 地址 。
**camera = cv2.VideoCapture(0)'''
for ip camera use - rtsp://username:password@ip_address:554/user=username_password='password'_channel=channel_number_stream=0.sdp' for local webcam use cv2.VideoCapture(0)
'''**
步骤 4-添加窗口并从相机生成帧:

按作者分类的图像-帧生成功能
gen_frames()函数进入一个循环,在这个循环中,它不断地从摄像机返回帧作为响应块。该函数要求摄像机提供一个帧,然后它将这个帧格式化为一个内容类型为image/jpeg的响应块,如上所示。代码如下所示:
帧生成函数
第五步-定义网络应用默认页面的应用路径:
路由是指某个 app 的 URL 模式(如 myapp.com/home 或 myapp.com/about)。@app.route("/")是 Flask 提供的一个 Python 装饰器,用于轻松地将我们应用程序中的 URL 分配给函数。
**@app.route('/')
def index():return render_template('index.html')**
装饰器告诉我们的@app,每当用户在给定的.route()访问我们的应用程序域( localhost:5000 用于本地服务器),执行index()函数。Flask 使用 Jinja 模板库来渲染模板。在我们的应用程序中,我们将使用模板来呈现将在浏览器中显示的 HTML。
步骤 6-定义视频馈送的应用程序路由:
**@app.route('/video_feed')
def video_feed():
return Response(gen_frames(), mimetype='multipart/x-mixed-replace; boundary=frame')**
“/video_feed”路由返回流响应。因为这个流返回要在网页中显示的图像,所以这个路由的 URL 在图像标记的“src”属性中(参见下面的“index.html”)。浏览器将通过在其中显示 JPEG 图像流来自动保持图像元素的更新,因为大多数/所有浏览器都支持多部分响应
让我们看看我们的index.html文件:
**<body>
<div class="container">
<div class="row">
<div class="col-lg-8 offset-lg-2">
<h3 class="mt-5">Live Streaming</h3>
**<img src="{{ url_for('video_feed') }}" width="100%">**
</div>
</div>
</div>
</body>**
步骤 7-启动 Flask 服务器:
**if __name__ == "__main__":
app.run(debug=True)**
app.run()被调用,web 应用程序被本地托管在【localhost:5000】上。**
“debug=True”确保我们不需要在每次进行更改时都运行我们的应用程序,我们只需在服务器仍在运行时刷新我们的网页来查看更改。
项目结构:

按作者分类的图像-项目结构
该项目保存在一个名为“相机检测”的文件夹中。我们运行“app.py”文件。在运行这个文件时,我们的应用程序被托管在端口 5000 的本地服务器上。
运行“app.py”后,您只需在 web 浏览器上键入“localhost:5000”即可打开您的 web 应用程序
- app.py —这是我们在上面创建的 Flask 应用程序
- 模板—该文件夹包含我们的“index.html”文件。渲染模板时,这在 Flask 中是强制的。所有 HTML 文件都放在这个文件夹下。
让我们看看运行“app.py”时会发生什么:

作者图片 Flask 应用程序的本地服务器
点击提供的 URL,我们的网络浏览器会打开实时提要。由于我使用了上面的 VideoCapture(0) ,网络摄像头的视频显示在浏览器上:

图片由作者提供-本地网络摄像头在网络浏览器上提供
就是这样!!
您的网络浏览器上有来自 IP 摄像头/网络摄像头的实时视频流,可用于安全和监控目的。
如果你喜欢读这篇文章,请支持我。点击上图。谢谢你****
参考我的 GitHub 代码 。
注意 :本文中已经提到了开始工作所需的所有资源及其链接。希望你好好利用:)
我希望这篇文章能让你对尝试计算机视觉领域的新事物感兴趣,并帮助你增加知识。如果你喜欢读这篇文章,请与你的朋友和家人分享。谢谢你的时间。
越南零售景观:零售足迹分析(上)
在一个过去 5 年以+10%的速度增长但被实体渠道主导的零售市场中,“下一家店开在哪里”是决定一个品牌成败的问题。

这篇博文是我在 IBM-Coursera 应用数据科学峰会上的工作的一部分。
1.介绍
2013 年至 2020 年间,越南零售业快速增长了+10%。作为东南亚中产阶级增长最快的国家,其零售业的这一惊人增长率预计至少在未来五年内将持续下去。德勤越南公司在其关于越南零售业 2019 的报告中报告了以下关键统计数据:

德勤对越南零售业的主要统计数据
根据这份报告,尽管越南的零售业拥有巨大的增长潜力,但竞争程度非常激烈。通过不同的商店形式:商业中心、超市、杂货店和便利店,越南的国内外零售巨头正在进行一场争夺主导权的战争,因为他们开始了积极的扩张战略。与此同时,尽管数字渠道兴起,但实体渠道继续主导零售领域。
因此,一个有趣的问题是:( 1)这些零售巨头目前在越南的零售足迹如何,( 2)新店的下一个目标应该是哪个省/市/区。谁有兴趣回答这些问题?答案是越南的零售巨头,以及任何计划进入越南零售市场的新玩家。
- 对于越南的零售巨头来说,回答这个问题将为他们决定在哪里扩大商店网络提供关键信息,也许他们当前的商店应该关闭(由于同一地区的竞争商店的数量)。用数据驱动的方法做出这些决定将使这些公司在市场中找到自己的最佳位置,并抓住新的需求。
- 对于计划进入越南零售市场的新玩家来说,通过了解当前在越南的零售足迹,可以了解他们的市场进入策略。
2.数据
2.1。数据来源
为了进行分析,获得了以下数据来执行分析:
- 越南行政区划列表:省级和区级。该信息来自越南统计总局(GSO)数据库(于 2020 年 4 月 4 日在 GSO 数据库检索)
- 人口普查数据:包括各省的人口、面积、人口密度、人类发展指数、GDP/资本等数据(2020 年 04 月 04 日检索自维基百科)。这一数据由 2019 年 4 月 1 日的 GSO 人口普查报告提供。然而,由于这些数据无法通过 GSO 数据库获得,维基百科是下一个最好的选择。
- 地理空间坐标:是包含来自 OpenDevelopmentMekong 数据库(2020 年 4 月 5 日检索)的越南各省和行政区地理空间数据的多边形数据集。这些数据集(JSON 格式)为项目结果在越南地图上的可视化提供了输入。
- 主要零售连锁店的店铺列表:根据德勤关于越南零售 2019 的报告,确定了以下零售连锁店,其店铺列表通过网络抓取从其官方网站获得。web 抓取的结果是商店列表,其中包含(1)商店名称,(2)商店地址,以及(3)商店的纬度和经度。
- 夫妻店和小型零售店:除了主要零售连锁店的商店,夫妻店仍然是越南零售市场的重要组成部分之一。夫妻店是家庭经营的杂货店。小型零售店由小公司经营,倾向于针对特定客户群的需求(例如,高端进口糖果、葡萄酒和酒类商店等)。).这类商店的数据是通过 Foursquare API 获得的。
关于主要的零售连锁店,以下超市、便利店(CVS)和食品杂货连锁店,这些都是德勤关于越南零售业前景的报告中所报告的,包括在本分析中:
- Vingroup: VinMart —连锁超市,以及 VinMart+ —不同业态的 CVS,从混合型(即生鲜产品 CVS)到传统 CVS。
- 西贡超市: Co.Op Mart 超市、 Co.Op Smile — CVS、 Co.Op Food —生鲜店
- Satra: SatraMart —超市
- 杂货店
- 7-Eleven : CVS(仅在胡志明市)
- 永旺集团: MiniStop — CVS(仅在胡志明市和平阳省)
- BigC :连锁超市
- 圈 K : CVS
- B'mart : CVS
注:Shop & Go 和欧尚连锁店包含在德勤报告中,然而:
因此,假设在收购后,这些商店将被重新命名并纳入收购方商店链,因此它们不包括在分析中。
2.2。数据清理和探索和
在此分析的所有数据中,行政区划和人口普查数据是结构化的,不需要进一步的数据清理和格式化。相比之下,从主要零售商的网站上搜集的商店数据没有结构化,存在语法错误、重复记录和质量差。对于此商店列表,在数据准备好进行进一步分析之前,采取了以下步骤:
- 从数据中移除越南语重音符号,以便稍后分类
- 检查每个商店的地址、纬度和经度,以确保一致性和数据质量
- 删除重复记录
使用叶库,以下越南地图是使用上述数据构建的,以反映商店的分布情况:

从左至右:(1)越南各省地图,包括主要零售连锁店的所有超市(蓝点)和便利店(绿点)。(2)右上图为越南北部地区概况(以河内市为中心)。(3)右下方的地图展示了越南南部的概况(以胡志明市为中心)
进行解释性数据分析(EDA)是为了进一步了解数据集并为聚类模型选择数据特征。

3.分析
3.1。方法论
对于这个项目,K-Means 聚类方法将用于根据零售足迹和统计普查来描述各省/地区。从到数据科学,K-Means 聚类是一种无监督的机器学习算法,试图在没有首先用标记数据训练的情况下对数据进行分类。由于本项目的目的是了解越南不同行政区划的零售足迹,因此选择了这种方法。
为了确定此分析中的最佳聚类数,我们将评估聚类数之间的关系以及聚类平方和(WCSS)内的关系,以选择 WCSS 变化开始趋于平稳的聚类数(肘形法)。
3.2。k 均值聚类

从第 2 节中进行的 EDA 中,我选择与其他变量具有低相关性(-0.5,0.5)的变量作为聚类模型的输入。选取了以下变量:人口密度、城市化水平、人类发展指数(HDI)、人均收入、位于各省的各类商店数量(超市、CVS 等。).使用肘方法,三个是此分析的最佳聚类数:
使用三作为聚类的数目,我获得了如下三个聚类:
- 集群#1 —“成熟”市场:包括河内和胡志明市—越南最大的两个城市,拥有最多的商店数量(各+1000 家商店),在人口密度、城市化和人均收入方面名列前茅。
- 集群#2 —“新兴”市场:包括 14 个城市/省份,可视为集群#1 的下一级城市/省份
- 集群#0 —“发展中”市场:包括其他省份。
下文概述了分组过程中使用的输入变量的三个分组。

4.结果和讨论

聚类分析:人均 GDP(百万越南盾)和人口密度(人/平方公里)。气泡大小表示每个省的商店数量。颜色代表聚类结果。
在该图中,我们可以看到三个聚类之间的分离:聚类#0 —蓝色,聚类#1 —棕色,聚类#2 —紫色。在聚类#2 中,我们可以确定人口密度和人均收入较高的省份,但与同一个聚类中的其他省份相比,这些省份的商店数量较少。
分析中最有趣的聚类是聚类 2,其中多个城市/省份具有相似的状态(面积、人口、密度等。)但是有非常不同数量的零售商店。回顾归类为群组 2 的省份清单,可以得出一些有趣的观察结果,例如:
- 北宁市:在人均收入和人口密度方面与海防市非常相似,但是商店的数量要少 50%。尽管北宁市的人口和城市化水平比海防市低约 30%,但两省之间人均收入高出约 50%,这应该为进一步扩大店铺提供了巨大的潜力。
- Can Tho:在其他属性方面与岘港非常相似,但商店数量只有岘港的 50%。尽管同城的城市化和人类发展指数分别比岘港低约 30%和 10%,但商店仍有进一步扩张的潜力,因为这两个省份之间的人均收入和人口密度差距仍低于 10%。
有了这个结果,现在的问题是“那又怎样?这些信息能对任何人有什么帮助?”。如本文开头所述,这项分析最初是针对主要的零售连锁店,或任何有兴趣进入越南市场的公司。进行与本分析类似的聚类分析,并确定与本分析中的聚类 2 类似的聚类,可以帮助这些公司找到潜在市场,在这些市场中,需求和增长没有被完全捕获。
5.未来方向
当然,这种分析是极其简单和幼稚的,没有考虑到各省零售市场的几个不同因素。此分析中的以下一些限制是我进一步扩展此分析并获得更多见解的大好机会:
- 夫妻店数据:与超市和现有零售连锁店的商店不同,夫妻店数据很难使用 Foursquare API 完成。Google API 可能是一个更好的选择,但是,即使使用 Google,获得的数据仍然不能反映该类别商店的实际分布情况。
- 普查数据:这种分析使用的普查数据没有反映每个省的增长潜力(即人口增长、人均收入变化等)。).拥有每个省的历史发展的进一步数据可以通过更复杂的聚类获得进一步的洞察力。
- 区一级的分组:目前的分析是在省一级进行的。像河内或胡志明这样的城市,商店的数量很大(+1000 家商店/每个城市),意味着更高的密度。在区一级的进一步详细聚类分析可以提供关于这两个特殊城市的景观和足迹的更有趣的见解。
使用此链接可以访问此分析的更多详细信息和交互式地图: myGithub
当然,请随时分享您对这一分析的宝贵意见。我很乐意看到你对这个项目及其结果的想法和意见。
越南零售景观:零售足迹分析(下)
一个品牌的门店足迹有多好?如何识别商场足迹中的差距?商店占地面积的所有缺口都需要填补吗?

这篇博客文章是我在 IBM-Coursera 应用数据科学峰会上的后续工作。你可以查看第一部分以了解越南零售市场的概况、最初的问题陈述以及我的分析中使用的数据来源。
在上一部分中,我回顾了主要零售品牌在越南全国的商店足迹,并使用聚类技术找到了应该被视为具有吸引力的扩张市场的省份。正如我之前研究的数据一样,大多数商店位于河内和胡志明市(HCMC)也就不足为奇了——这是越南最大的市场。因此,在第二部分,我想深入了解这两个城市中每个品牌的商店网络。
对于这篇文章,我将主要讨论我对 HCMC 的研究结果,因为两个主要原因:(1) HCM 是更大的市场(考虑到两个城市之间的人口、人口密度和人均收入);(2)河内市场由 Vinmart 商店主导,没有多少品牌可以与 Vinmart 竞争(在河内的零售商店中,不包括夫妻店,Vinmart 商店占 77%),而 HCMC 的情况正好相反。
方法
当深入研究 HCMC 的商店网络和位置时,我遵循了三个步骤:
- 商场占地面积:将商场分配分解到区级,并根据区级的人口密度审查商场密度
- 地理覆盖范围:比较不同品牌的店铺网络的地理覆盖范围,找出覆盖范围最好的店铺网络。对于此分析,覆盖范围定义为网络中每个商店给定距离内所有点的集合。这意味着顾客可以在给定的距离内进入零售店的区域。
- 下一步何去何从:确定城市中品牌应考虑开店的区域/位置,以增加其在城市中的覆盖面。
调查的结果
1。商店占地面积
为了探索这个方面,我研究了商店相对于同一网络中的其他商店以及城市中的人口密度的位置。例如:对于 Vinmart(超市和便利店网络),我探索了如下不同的可视化:

从左至右:(1)店铺位置;(2)商店集群;以及(3)使用 HCMC 人口密度(人/平方公里,2019 年数据)choropleth 地图,HCMC VinMart 商店(包括超市和便利店)的商店覆盖范围(2020 年 6 月 30 日获得的商店数据)。
从左至右,地图显示 HCMC 人口密度(人口/平方公里,在地区一级,较深的颜色代表较高的密度)和:
要查看上述地图的互动版本,请使用每张地图描述中包含的链接。
看看 HCMC 店铺数量最多的两个品牌的店铺集群——VinMart和 BachHoaXanh (BHX),我们可以看到一个显著的区别:虽然 VinMart 店铺密集地位于市中心(80%的店铺),但 BHX 店铺更分散,不太集中在最密集的区域(3 区、5 区、10 区和 11 区)。这种差异可能反映了关于其目标客户的品牌战略。

从左至右:( 1) VinMart 和(2)BHX(2020 年 6 月 30 日获得的商店数据)的商店集群地图,以及 HCMC 人口密度(人口/平方公里,2019 年数据)choropleth 地图。
2。地理覆盖范围
为了比较不同品牌商店网络的地理覆盖范围,我使用了 geopandas 中的 buffer 函数来获取同一网络中每个商店的给定距离内所有点的集合。
例如,上面的两张地图显示了从 Vinmart 和 BHX 的每家商店 500 米范围内所有点的集合,这两家商店是 HCMC 商店数量最多的商店网络。难怪 Vinmart 商店大多位于人口密度较高的城市中心,基本上覆盖了中心区域。另一方面,尽管假定人口密度很高,但 BHX 的商店网络使得市中心相当大一部分区域没有商店。

从左至右:(1) VinMart 和(2) BHX 商店网络(2020 年 6 月 30 日获得的商店数据)与 HCMC 人口密度(人/平方公里,2019 年数据)choropleth 地图 500 米距离的商店覆盖范围。
为了进一步比较覆盖范围,我查看了 HCMC 不同品牌的每家商店在给定距离内(100 米到 1 公里之间)的面积百分比。交集和。geopandas 的面积函数。

不同距离(以米为单位)的地理覆盖范围(占 HCMC 总面积的百分比)衡量领先零售品牌在越南的表现
因此,该折线图显示了 HCMC 距离 HCMC 的 VinMart、BHX、CircleK、CoOp(包括 Coopfood 和 Coopsmile)、Satra 和其他零售品牌 100 米至 1 公里范围内的商店网络所占的百分比。
按照预期,商店数量越多,观察到的覆盖范围就越大。然而,唯一的例外是,BHX 在 600 米左右或更远的距离的覆盖范围方面超过了文马特,尽管 BHX 的商店比文马特少 20%。这可以用以下事实来解释:80%的 VinMart 商店位于市中心地区,而 BHX 的这一数字仅为 68%。因此,随着缓冲距离的增加,与 BHX 相比,VinMart 商店网络的覆盖范围增长速度较慢。
3。接下来去哪里?
对于领先的零售品牌 :利用缓冲和覆盖分析,公司可以识别其商店网络中的地理差距,并解决差距,以进一步加强其商店网络。
例如,下图显示了 BHX 商店网络的地理差距。如果 BHX 希望通过增加 500 米的覆盖范围来加强网络,该公司可以考虑在下图中突出显示的一些区域进一步开店。

左图:BHX 商店网络 500 米范围内的商店覆盖范围。右图:BHX 商店网络中的地理差距示例
商店占地面积中的所有缺口都需要填补吗?这取决于公司的战略和市场目标。
- 如果一个品牌的目标是让城市中的所有顾客都能在 10-15 分钟的步行距离内到达其商店,也许是的。
- 如果一个品牌的目标是在市场上有效竞争,它应该缩小位于高人口密度地区的差距,或者换句话说,位于更有潜力的细分市场。
- 如果一个品牌的目标是扩大其商店网络,同时支持其供应链网络,则应考虑进一步的标准。
对于较小的零售品牌或新进入者 :他们可以使用相同的技术来寻找其直接竞争对手的商店足迹的差距,或在市场上主要品牌的整体商店足迹中寻找机会。

从左至右:(1)商店集群,以及(2)主要品牌商店足迹 500 米距离内的商店覆盖范围,以及 HCMC 人口密度(人/平方公里,2019 年数据)choropleth 地图。
请随时分享您对此分析的宝贵意见。我很乐意看到你对这个分析和发现的想法和意见。
维拉:一种通用的视觉和语言对抗训练技术
第一个用于图像和文本模型的多模态对抗训练技术

图片由来自 Pixabay 的 Gerd Altmann 提供
为视觉和语言多模态学习建立大规模预训练模型是近年来机器学习研究的一个热点。像 UNITER 、ViLBERT 和 LXMERT 这样的模型在数百万个具有不同目标函数的图像-文本对上进行训练,并且能够学习图像和文本的有效通用联合表示。
然后,这些表示在特定任务上进行微调,以在无数图像和语言任务上实现最先进的性能,如图像 QA、图像-文本检索、图像基础、图像推理等。在对特定任务进行微调时,可用的数据量只是所用预训练数据的一小部分。如此少量的数据通常会导致模型过度拟合,并打破了试图为图像和文本建立一个通用嵌入的目标。
那么我们如何防止这种过度拟合呢?针对图像和文本的单一模态任务的对抗性训练技术已被证明可以使模型更加健壮和可推广。这种培训可以通过以下方式进行
- 创造人类可以理解的对立例子(如 Szegedy 等人)
- 给嵌入空间增加对抗性扰动(如 FreeLB )。
将对抗性训练技术应用于多模态学习是对单模态技术的自然扩展,这正是微软动态 AI 365 研究的研究人员所做的。
他们提出了 VILLA(视觉和语言大规模对抗性训练),这是第一个已知的针对视觉和语言设计的对抗性训练技术。VILLA 遵循第二种对抗性训练方法,因为他们的目标是建立一个健壮的模型,而不管人类是否理解对抗性的例子。对于文本模态,他们增加了对单词嵌入的对抗性干扰。先前关于图像对抗训练的工作向图像像素添加噪声,并且发现鲁棒性是以性能为代价的。在 VILLA 中,这些对立的扰动被添加到所提取的图像区域特征而不是像素,这提高了鲁棒性和性能。在训练期间,扰动一次仅添加到一个模态,而另一个模态保持不变,如图所示。

维拉还利用“免费”训练策略(沙法希等人,2019;Zhang et al. 2019)在计算输入的梯度时,几乎没有额外的成本就获得了参数的梯度。通过这种方式,模型可以在不增加成本的情况下看到更多的示例,并且培训变得更具成本效益。
对抗性训练技术通常确保对抗性示例的标签与原始示例的标签相同。VILLA 不仅试图做到这一点,还试图使用 KL-divergence 来匹配两个例子的预测类别概率。通过这样做,训练损失函数变得更平滑,并且模型可以更好地估计它。这进一步提高了模型的性能。
VILLA 可以应用于预训练和微调阶段,以提高学习嵌入的泛化能力。当应用于 UNITER-large 时,VILLA 进一步提高了其性能,在 VQA 上从 74.02%提高到 74.87%,在 VCR 上从 62.8%提高到 65.7%。集成后,VQA 性能进一步提高到 75.85%。维拉还在另外 4 项任务上将尤尼特的成绩提高到了最高水平。
VILLA 也是模型不可知的。它被应用于另一个大规模预训练模型 LXMERT,并在 VQA 上显示出性能的提高。
如果你想更详细地了解维拉是如何工作的,这里有一个链接到论文,点击这里看更多他们的出版物和其他工作。
参考文献
- 日元-陈春、李林杰、李成玉、艾哈迈德·埃尔·科利、费萨尔·艾哈迈德、哲甘、于成、刘晶晶、统一者:学习通用的图像-文本表示法、欧洲计算机视觉会议(ECCV) 2020
- J iasen Lu , Dhruv Batra , Devi Parikh , Stefan Lee , ViLBERT:预训练任务不可知的视觉-语言任务的视觉-语言表征,arXiv 预印本,arXiv:1908.02265
- 郝坦和莫希特·班萨尔, Lxmert:从变形金刚学习跨通道编码器表示,EMNLP 2019。
- C 赫里斯蒂安·塞格迪,沃伊切赫·扎伦巴,伊利亚·苏茨克弗,琼·布鲁纳,杜米特鲁尔汉,伊恩·古德费勒,罗布·弗格斯,神经网络的耐人寻味的性质 (2013),arXiv 预印本,arXiv:1312。
- 、于成、哲干、孙思齐、汤姆·戈尔茨坦、刘晶晶、 FreeLB:语言理解的强化对抗训练 (2019),学习表征国际会议,ICLR 2020
- A.沙法希,m .纳吉比,a .吉亚西,z .徐,j .迪克森,c .斯图德,l .戴维斯,g .泰勒,t .戈尔茨坦,对抗性训练免费!、神经信息处理系统、NeurIPS 2019
- 张定淮,张天元,陆,朱占兴,董斌,你只传播一次:最大原理无痛对抗训练,神经信息处理系统,NeurIPS 2019
- 哲淦,晏,李林杰,诸宸,于成,刘晶晶,视觉-语言表征学习的大规模对抗性训练,arXiv 预印本,arXiv:2006.06195。
“Vim —无处不在的文本编辑器”
Vim 很牛逼

McDobbie Hu 在 Unsplash 上的照片
第一次遇到 Vim 时,我被通过命令行编辑文本的完全不同的方式所征服,以至于我没有再回头看它。
但是耐心地试验过之后,我就迷上了。Vim 有一些很棒的高级特性,我已经读到过,但还没有探索过。这篇文章是为了说服你并让你开始使用 Vim。我们将讨论基本命令,然后研究如何为 Vim 启用语法高亮显示。
vim(VIIMproven)是 Bill Joy 的 vi 文本编辑器程序在 Unix 和苹果 OS X 上的克隆版,可以在这里下载。
为什么使用 Vim?
当使用 bash 终端时,Vim 是必不可少的。例如,您可以直接使用命令行界面编辑像.bash_profile这样的文件。这是一项伟大的技能。作为一个高度可定制的文本编辑器,它可以被任何人使用——软件开发人员、数据分析师、数据科学家。这是一个免费的强大的工具,你不能把它扔出窗外。作为一个控制台编辑器,它与 shell 密切配合。这是熟悉 shell 的第一步,之后你就可以做程序员需要做的几乎所有事情了。
基本命令:
要在 Vim 中打开一个文件,在终端中键入vi <name-of-the-file>。我将使用一个空的bash脚本,script1.sh作为例子。
vi script1.sh:打开文件。


空文件- 0 行和 0 个字符
如果您尝试键入任何内容,将不会显示任何内容,因为它处于NORMAL模式。要编辑文件,按键盘上的i。这是进行编辑的INSERT模式。

注意窗口底部黄色的INSERT。

一旦你对这些变化感到满意,你就必须脱离INSERT模式。要在不同模式之间切换,我们使用ESC键。
保存:按下ESC和:w。

还可以看到写的行数和字符数。
保存后退出::q
这两个命令可以组合为::wq
不保存就退出::q!
记得按ESC来切换模式!
语法突出显示:
永久地(因为,当然,我们不希望它是一次性的!)添加语法高亮,我们编辑.vimrc文件(隐藏文件)。
*.vimrc*代表 Vim 运行时配置。当编辑器打开时,它保存编辑器的默认设置,允许用户自定义编辑器的选项。
为了在 Vim: vi ~/.vimrc
中打开.vimrc文件,这个文件驻留在您的主目录中,为了避免更改目录,我们添加了一个~(这是主目录的快捷方式)。

编辑文件:按i并添加syntax on。
ESC和:w保存。

当您保存时,您将在文件中看到语法高亮显示。
:q退出。
要获得可用配色方案的列表:
ls -l /usr/share/vim/vim*/colors/
这在你的根目录中,如果你想找到它,不要着急。/usr是一个隐藏的文件夹,按下command + shift + .就可以看到

现在,要对这些配色方案进行采样并选择您最喜欢的方案,请在 vim 中打开一个文件。

类型::colorscheme <name-of-colorscheme-to-test-out>

按下ENTER。

(诶,不喜欢)
不断尝试不同的方案,直到你选定了你最喜欢的方案!
请注意,通过 Vim 更改的配色方案仅适用于当前 Vim 会话,不会被保存。要永久使用特定的配色方案,我们必须将其添加到.vimrc:
colorscheme <name-of-colorscheme>
如果您想禁用配色方案,则按照上述相同的步骤编辑您的.vimrc文件,并将syntax on更改为syntax off。
自从我掌握了这个窍门,我就喜欢使用 shells 和 Vim。希望你也是!
蒂华纳市的暴力和当地商业
在这项工作中,使用 Foursquare API 识别了提华纳的暴力街区,并找到了它们最常见的场所
专业证书期末项目 IBM 数据科学
该报告的 pdf 版本在 my Github 中
1 简介1.1 背景
T 伊瓜纳是墨西哥最大、发展最快的城市之一,2014 年有 340 万人口[1],是美墨边境上最大的墨西哥城市。“这个城市是下加利福尼亚州大约 49 %人口的家园,而只占该州领土的大约 2%”[10]。蒂华纳每天接纳来自墨西哥其他地方的移民,增加近 96 名新居民。
蒂华纳是北美的医疗设备制造之都,一项研究估计,该地区约占该地区所有视听产品制造的 40%[34]。但是工作的丰富并不意味着生活质量的提高。根据国家社会发展政策评估委员会(CONEVAL,在西班牙语中是他的名字)2018 年的一项最新研究,近 70 %的人口生活在贫困状态中[9],有很多工作岗位,但工资很低,社会流动条件很差。
鉴于该城市的地理位置,在过去几十年中,与贩毒和有组织犯罪集团有关的暴力事件有所增加[11]。这个城市也有很高的药物使用水平,这是由它靠近美国造成的,但仍然没有对真正问题的分析甚至数据[27]。毒品暴力继续在蒂华纳占据主导地位,2017 年的凶杀案比墨西哥其他任何城市都多,这是全国凶杀案数字创纪录的一年,2017 年,墨西哥 20 起谋杀案中有 1 起发生在蒂华纳。政府没有对该市的暴力事件进行简明的分析。这项工作是我希望成为简明分析的开始,从关注暴力对当地商业的影响开始。
1.2 个人动机
我在蒂华纳长大,在那里生活了 18 年。自从我有记忆以来,暴力一直是这座城市的一部分,生活在暴力最严重的街区之一(“Sanchez Taboada”)帮助我从年轻时就意识到了这一点。从我的个人经验来看,过一段时间你习惯了,暴力就变得正常了,是意料之中的,害怕被抢劫、被枪击,或者,对于女人来说,害怕被强奸,这种事情并不总是在你的脑海中出现,但它总是在你的背后,在袖手旁观。晚上在街上意味着危险,如果你听到你家附近有枪响,你不会报警,因为有时警察比罪犯更危险。在墨西哥其他城市的街道上行走的恐惧是我生活中的一个常数,我已经习惯了,我可以继续我的生活,但每次我去蒂华纳看望我的父母,我都很害怕,每次我回来,恐惧都成倍增长,就像暴力和贫困一样。举个例子,一年前,出租车和像优步这样的应用程序服务开始拒绝在晚上 7 点后去我的旧社区,因为害怕被抢劫或被杀。
我不想害怕我成长的城市,害怕墨西哥,我越来越需要了解影响它的问题。也许,如果我理解了这个城市的错综复杂,我的恐惧会开始成为一种燃料,以一种小的方式提供帮助,并最终提供做出真正改变的工具。那么,暴力事件上升的原因是什么?为什么我长大的地方是这个城市里比较暴力的地方之一?也许生活贫困是暴力的原因,或者是缺乏良好的司法系统,还是卡特尔的错?或者是我们社会基础中一个更深层次的问题?。这些问题必须由城市和政府的负责人来回答。但是在我过去几个月所做的研究中,我没有发现墨西哥政府
对提华纳暴力原因的明确调查。我发现的唯一明确的调查是“为墨西哥伸张正义”(Justice for Mexico),这是一个总部位于美国的项目,有一些有趣的分析,但还有很大的发展空间。墨西哥政府的答案总是一样的,需要把“坏人”关进监狱,暴力就会消失,但这几十年来没有奏效,而且永远不会奏效[18,8]。

图 1:蒂华纳 Camino Verde 社区的国民警卫队[36],这是新政府在过去一年里对暴力和移民危机增加的回应。许多人认为这是该国军事化的第一意图。图片来自【36】。
1.3 问题
这个项目的大背景是分析蒂华纳的可用数据,犯罪、教育、移民和经济搜索暴力背后的故事;相关性在哪里?为什么某些街区的暴力事件会增加?这是一个雄心勃勃的项目,我知道这一点,我仍在学习数据科学,这种类型的分析对我来说是新的,但背后有这么多个人动机,我可以学得更快,同时找到有趣的结果。这个项目将被分成几个必要的部分,第一部分,我将在 Coursera“IBM 数据科学”专业的期末项目中展示,是关于提华纳暴力最严重的社区中暴力和当地商业之间的关系。问题是,在提华纳市暴力程度较高的社区,有哪些共同之处?
1.4 目标受众
什么类型的客户或人群会对这个项目感兴趣?
- 想要在暴力社区投资的企业家可以识别社区中缺失的商业类型。
- 负责该社区的当局可以看到机会之窗,帮助更常见类型的企业繁荣发展,提供信贷或管理指导。
- 居民可以更好地理解他们社区的数据
2 数据采集和清理
本节使用的程序是在我的 Github 下面 这个链接
2.1 犯罪
关于蒂华纳在线的数据很少,但由于几年前政府的新政策,更多的数据变得可用。在犯罪案件中,我只能在一个地方找到可用信息,即国家安全和国家调查卫队的官方网站(Guardia Estatal de Seguridad e investigación),但这一信息非常不完整。让我们开始回顾数据集,引用
网站的话,“本节包含的数据库仅细分了被认为具有高度和中度影响的犯罪选择,这些犯罪威胁到人们的生命和完整性以及他们的遗产”,因此数据是不完整的,无法找到更完整的数据集。我尽了最大努力将犯罪数据从西班牙语翻译成英语,但在法律术语中,每项犯罪的定义可能会有一些差异。这些罪行的分类如下:
- 暴力和非暴力车辆盗窃
- 暴力和非暴力入室盗窃
- 暴力和非暴力的商业盗窃
- 在公路上暴力抢劫
- 公共场所的简单抢劫
- 其他没有暴力的抢劫
- 其他暴力抢劫
- 恶意伤人
- 非法伤害
- 凶杀(暴力)
- 绑架
正如你所看到的,分类遗漏了一些非常重要的犯罪,如敲诈勒索、杀害女性和性侵犯,这些在蒂华纳非常常见的犯罪[13,30,23]。出于某种原因,国家检察官不认为强奸属于“被认为具有威胁人的生命和完整性的高度和中度影响的犯罪”的类别。现在,为了理解数据库中包含的字段,需要做一些解释。首先,墨西哥各州划分为市,而不是像美国那样划分为县。蒂华纳是下加利福尼亚州的一个直辖市。蒂华纳市分为行政区或“代表区”。行政区又被划分为“殖民地”,对“殖民地”的最佳翻译是邻居。我将把我的分析集中在“Colonias”上,所以我将使用那个翻译。
- 犯罪:指已登记的犯罪
- 犯罪分类:指上面引用的犯罪分类。
- 犯罪地附近:指犯罪地附近。
- 犯罪记录日期:犯罪报告开始的日期。该日期用于根据月、日和年进行统计计数。
- 犯罪日期:犯罪发生的日期(有时与犯罪登记的日期不同),因为犯罪可能在某一天报案,而在另一天实施。
- 犯罪时间:犯罪发生的时间。
- 自治市:犯罪登记的自治市。
这些数据的来源是州检察长,并由州情报中心统计协调处处理。
2.2 当地企业
鉴于某些社区的犯罪信息,我想探索这些社区的当地企业。我使用 Foursquare API 来获取一个给定街区的场地,但要做到这一点,我需要坐标。这比预期的要困难。我的第一个想法是使用城市的 12 个区(“Delegaciones”)并根据每个区的犯罪情况进行分析。这个想法给我带来的最大问题是,我找不到每个地区的社区列表。在蒂华纳都市规划研究所(IMPLAN)的官方网站上有一个交互式地图,您可以在其中选择区域,区域会出现在地图上[17],但是在蒂华纳有 3000 多个社区,并且用这种方式寻找每个社区的每个社区太复杂了;令人惊讶的是,在蒂华纳政府的任何官方网站上,我都找不到更多关于该地区的信息。我能做的下一个最好的分析是集中在更暴力的社区。
现在,理解什么类型的本地企业是被期待的也是棘手的。例如,近年来所谓的医疗旅游浪潮如此之大,以至于该市在 2018 年接待了约 120 万名来自美国寻求健康治疗的患者。“咨询、程序、药物和手术的低成本——价格比美国低 30 %至 70%——使这座城市成为一个伟大的健康中心”[3]。
因此,预计会有许多药房,但也有许多非正规商业,据估计,蒂华纳超过 30%的企业是非正规的,因此 Foursquare API 可以提供的信息将无法提供该市当地企业的清晰画面。
2.3 街区坐标
为了获得每个邻域的坐标,我使用了 geopy,这是几个流行的地理编码 web 服务的 Python 2 和 3 客户端,geopy 使 Python 开发人员可以使用第三方地理编码器和其他数据源轻松定位全球各地的地址、城市、国家、
和地标的坐标[32]。问题是提华纳的许多社区似乎没有 geopy 中的坐标,结果是 NaN。目前的解决方案
是手动输入其余的坐标,问题是这里有超过 3000 个社区,所以我选择关注 100 个更暴力的社区。这一部分做了一些考虑:
- Mariano Matamoros Centro 街区在谷歌或其他在线网站上没有坐标,所以我将其重命名为最近的街区,在这种情况下,Mariano Matamoros。
- 我找不到 Tres de Octubre 街区的坐标,问题是“Tres”,所以我把它改成了数字 3。
- Obrera Seccion 1 和 Fraccionamiento Natura 街区存在与 Mariano Matamoros Centro 相同的问题,完成了相同的过程,在这种情况下,最近的街区是 Obrera 和 Fraccionamiento Hacienda las Delicias。
- Sanchez Taboada Produsta 和 Sanchez Taboada 街区之间的划分是不存在的。这两个街区总是被视为一体,所以我也这样做了。
- Foranea 和 Hacienda Las Fuentes 社区与 Mariano Matamoros Centro 有同样的问题,但这次我在任何在线地图上都找不到它们的位置。我决定从数据中剔除他们两个。
3 方法论
本节中使用的直方图的程序在我的 Github 中如下 链接 而对于地图 这个

图 2:2014-2019 年期间犯罪分类的频率
下加利福尼亚州是全国汽车盗窃案的第一名[25],在蒂华纳,这种犯罪的数量如此之大也就不足为奇了。对每一项犯罪的分析都很重要,但在这项工作中,重点将放在暴力犯罪上,特别是暴力盗窃和杀人。因此,我们可以将暴力犯罪的数据划分如下:
- 暴力抢劫(公共区域)
- 暴力盗窃生意
- 暴力盗车
- 其他暴力抢劫
- 暴力入室盗窃
- 恶意伤人
- 凶杀(暴力)
这些暴力犯罪仅占数据显示的犯罪总数的 31.49%,但这些类型的犯罪对城市生活的影响更大。

图 3:2014-2019 年期间暴力犯罪的频率
从图 3 中可以看出,对企业的暴力盗窃是更常见的犯罪,这是一个私营部门一直要求当局注意的问题[2]。

图 4:最暴力的街区正如所料,这份名单一直都是一样的
图 4 显示了 15 个最暴力的街区。图 5 显示了 100 个暴力程度较高的社区的犯罪数量集群,通过描述中的链接可以看到每个社区的犯罪数量。

图 5:显示 100 个最暴力的社区和每个社区暴力犯罪数量的地图。点击此链接观看互动地图。
现在我们将关注:对企业的暴力盗窃和杀人;这些都是社区安全的有力指标。
3.1 暴力盗窃企业

图 6:暴力盗窃案件数量最多的社区
在图 6 中,显示了 15 个对企业暴力盗窃较多的街区。图 7 是聚类图,其中红色表示此类犯罪数量较多。

图 7:显示 100 个暴力程度较高的社区中针对企业的暴力盗窃数量的地图,
红色的强度意味着该区域的盗窃数量较高。点击链接观看互动地图。
图 8 显示了企业暴力盗窃的频率。我们可以看到,这种类型的犯罪在晚上更常见,在 0 点时有一个高峰。2017 年是对企业更危险的一年,2015 年是最危险的一年,2018 年和 2019 年没有变化。


图 8:按年份和小时划分的暴力盗窃发生频率
3.2 凶杀案

图 9:凶杀案数量最多的社区
图 9 显示了凶杀案较多的 15 个街区。图 10 是一个聚类图,其中红色显示了这类犯罪的高数量。

图 10:显示 100 个暴力程度较高的社区的凶杀案数量的地图,红色的强度表示该地区有更多的凶杀案。点击链接观看互动地图。
图 11 显示了凶杀案的发生频率。我们可以看到,令人惊讶的是,这种类型的犯罪通常发生在白天和下午高峰的 7、20、21 和 22 小时。2018 年是最危险的一年,2014 年是最危险的一年,2019 年显示出了一点好转。


图 11:按年份和小时分列的凶杀频率
3.3 当地企业

图 12:暴力社区中更常见场所的出现频率
图 12 显示了暴力街区中更常见的场所的出现频率。这张直方图是使用 Foursquare API 搜索该市 100 个暴力程度较高的社区中更常见的场所制作的。最常见的是电子商店,在 Foursquare 城市指南中快速搜索,主要是指智能手机及其配件和互联网提供商办公室的销售。第二和第三常见的是甜甜圈店和潜水酒吧。

图 13:凶杀案较多的 4 个街区和最常见的地点

图 14:对企业实施暴力盗窃较多的 5 个社区及其常见场所
图 14 显示了凶杀案较多的 4 个街区中最常见的公共场所。更多的公共场所是酒吧和食品摊贩。

图 Foursquare API 在 18 个街区没有找到场地
图 15 显示了 Foursquare 中没有场地的 18 个社区。

图 16:城市暴力街区中最常见场所的集群。紫色和红色的
点大多是餐馆和酒吧,绿色的点大多是便利店。点击链接观看互动
地图。
4 结果和讨论
从数据中可以看出,凶杀案的数量在括号中伴随着暴力这个词。对我来说,这很奇怪,我找不到更多关于这种分类定义的信息。我能推断的是,这些数据只显示了被认为是暴力杀人的案件,其余的都被忽略了,这非常令人不安。不被认为是暴力的凶杀案的数量可能会更高。
关于社区中最常见的场馆的信息很少,18 个没有显示任何场馆。剩下的一个被认为是不完整的,考虑到城市中的非正规商业[33]非常高,Foursquare 基于用户输入,但 43 %的墨西哥人是数字文盲,只有 39 %的家庭有网络连接[16]。Foursquare 上提供的信息并不能展示该城市商业的全貌。
考虑到所有这些问题,我们可以看到更重要的本地商业都与食物有关:餐馆、甜点店和街头小吃。酒吧都在离边境最近的社区,至少在暴力地区是这样。
凶杀和暴力盗窃案件较多的 5 个社区如下:
凶杀案(暴力)
- 绿色卡米诺
- 北带
- 桑切斯·塔博达
- 中央区
- 10 月 3 日
暴力盗窃一家企业
- 马里亚诺·马塔莫罗斯
- 中央区
- 北马里亚诺·马塔莫罗斯
- 埃尔弗洛里多 1 区和 2 区
- 埃吉多·弗朗西斯科别墅
只有 Zona Centro 重复出现在两项犯罪的前 5 名中。考虑到凶杀率对一个社区的影响,我将把重点放在凶杀率较高的 5 个社区,并将这些数字放在上下文中。
卡米诺·维德和桑切斯·塔博达

图 17:谷歌地图上的 Camino Verde 和 Sanchez Taboada 街区
在图 17 中显示了 Camino Verde 和 Sanchez Taboada 社区的地图,可以看出它们是并排的,没有明显的分界线。这两个街区可以被认为是一个凶杀率非常高的大区域。

图 18:地质断层和下沉摧毁了 Sanchez Taboada 社区的数千所房屋。图片来自【31】。
几年来,地质断层和下沉摧毁了 Sanchez Taboada 社区的数千所房屋[31,26],导致大约 2000 人无家可归。暴力和吸毒对一个已经贫困的社区的影响还没有被调查过。
北部区域和中部区域

图 19:谷歌地图上的 Zona centro(蒂华纳市中心)附近
在图 19 中显示了 Zona Norte 和 Zona Centro 街区的地图,与 Camino Verde 和 Sanchez Taboada 相同,它们是并排的。它可以被认为是另一个大的暴力区域。Zona Centro 是该市最古老的街区之一,也是游客最多的地方之一,因为它靠近边境。
靠近北部区域和中部区域的是蒂华纳河,它至少从 20 世纪初就被用作废水管道。这条河的隧道里住着大量无家可归者、吸毒者和移民,他们正等着越境去美国或者已经被驱逐出境。这条河是暴力犯罪的热点。
北部区域是这个城市的红灯区。摘自报纸" La voz de la frontera "的一篇文章[15]:圣地亚哥州立大学的维克多·克拉克·阿尔法罗教授估计,蒂华纳是世界上与性旅游有关的一切都最繁荣的城市之一。专家指出,只有在城市的北部,有 2000 名性工作者在工作。这还不包括酒店、夜总会和出租车司机的员工,他们靠直接从国际线接游客为生。“这个地区的酒吧不仅有来自美国的顾客,还有来自欧洲的顾客,他们专门来寻求这种服务。从事性旅游的人是带着巨大的经济能力来到蒂华纳市的,”他说。许多受性服务驱使来到蒂华纳的游客的经济实力如此强大,以至于一些俱乐部和酒吧每天 24 小时都有一辆国际线路的豪华轿车,负责接送他们最尊贵的客户,并将他们直接送到容忍区。
没有官方数据显示它给这个城市带来的经济溢出。人口贩卖和毒品销售是这个地区的大问题,也是暴力犯罪的另一个热点。
十月三日


图 20:10 月 3 日街区的极端贫困。照片来自【7】。
10 月 3 日街区是蒂华纳许多不规则形成的街区之一,只是占据了一部分土地。这导致了基本服务的缺乏,比如水、电力设施和许多其他服务。极端贫困是暴力犯罪的另一个热点。
5 个结论

图 21:“在墨西哥,杀人容易,不进监狱难”【24】。图片来自 Pixabay (CC0)的mizer _ X94
在这项工作中,使用 Foursquare API 确定了蒂华纳最暴力的社区,并找到了它们最常见的场所,得出了以下结论:
- 在暴力街区,更重要的当地商业是酒吧和食品相关的,例如,餐馆、甜品店和街头小吃。在数据采集部分预测的药店的预期高数量是错误的。
- 鉴于墨西哥的数字文盲,从 Foursquare 获得的信息并不能展示该城市商业的全貌。
- 为了完成一部更完整的作品,需要该城市当地商业的完整数据。这些数据可以在政府部门找到。
- 更暴力的社区与贫困、卖淫、吸毒和移民有关
- 蒂华纳的暴力事件只是墨西哥正在发生的事情的一个例子,一个需要改变的失败的系统。例如,记者在《动物政治》上的研究发现:“在墨西哥,杀人很容易,而且永远不会进监狱。每 100 起谋杀案中,只有 5 起有人被定罪。如果我们只专注于寻找那些在 2010 年至 2016 年期间犯下杀人罪的人,我们将需要 124 年才能做到这一点,因为按照这个速度,墨西哥司法系统是有效的”[24]。
“我不可能回墨西哥。我无法忍受身处一个比我的画更超现实的国家。”
著名画家萨尔瓦多·达利的这句话再好不过了,墨西哥是一个充满矛盾的国家。蒂华纳只是一个例子:尽管高度的不安全和暴力,这个城市仍然保持着稳定的经济增长。
参考文献
[1]国家发展规划委员会。下加利福尼亚和城市人口。2014 年 1 月。网址:http://www . copladebc . gob . MX/seis/pdf/apuntepoblacionbcymunicipiosene 14 . pdfpdf(2020 年 4 月 13 日访问)。
[2]Julieta aragón . preo cupa a empresioners robos a commercios,pes a Baja incidents in El Estado .2020 年 1 月 27 日。网址:【https://zetatijuana.com/2020/01/preocupa- a-empresarios-robos-a-comercios-pese-a-la-Baja-incidencia-en-El-Estado/(2020 年 4 月 16 日访问)。
[3]达罗·布鲁克斯。墨西哥的墨西哥旅游:蒂华纳是一个旅游胜地。2019 年 4 月 9 日。网址:https://www.bbc.com/mundo/noticias-美洲-拉丁- 47809220 (于 2020 年 4 月 16 日访问)。
[4] 尤金妮亚·希门尼斯·卡利斯。蒂华纳港,移民和吸毒者的家园。七月 31, 2014. url: https://www.milenio.com/politica/el-borde-de-tijuana-hogar-de-migrantes-y-吸毒者 (访问 04/17/2020)。
[5] 格拉西拉·迪亚兹(Graciela Dìaz),马赫主义-制造-普塔斯(Machism-Fabrica-Putas)。url: https://feminismoinc.org/2018/03/machismo-
fabrica-putas.html(访问于 2020 年 4 月 17 日)。
桑德拉·迪布尔。什么驱动 Tijuana's Next Major?2013 年 7 月 14 日,URL:https://www.sandiegouniontribune.com/news/border-baja-california/sdut-what-drives-tijuanas-next-mayor-2013jul14-story.html(访问于 04/13/2020)。
[7] 拉斐尔·科罗拉多和埃德加·卡拉皮亚。10 月 3 日的。Feb 的。19,2019.url: https://www.facebook.com/TijuanaTelevisa/videos/416565892220378/(访问时间 04/17/2020)。
[8] 以弗所 AMLO 的安全策略不起作用:专家;墨西哥将在 2019 年以更多暴力解雇
Dec.网址:https://www.sinembargo.mx/22-12-2019/3700383(访问于 04/13/2020)。
[9] 国家社会发展政策评估委员会(CONEVAL)。国家贫困 2018.2018.url:https : / / www 。Coneval。组织。mx / 协调 / 实体 / 下加利福尼亚州 / char / Pobreza_2018.aspx (visited on 04/13/2020).
[10] 奥克塔维奥·罗德·拉塔涅格斯·费雷拉和大卫·A。饰 Shirk 蒂华纳暴力犯罪的复苏:墨西哥司法的分析。May 18, 2018. url: https://justiceinmexico.org/el-resurrection- del-crime-violento-en-tijuana-new-spanish-translation-of-justice-in-mexico-working-paper/ (visited on 04/13/2020)。
[美] 温迪·弗莱 Tijuana still Mexico’s bloodiest city 提华纳仍然是墨西哥最血腥的城市。毒品暴力 Blame Drug Violence 简。7,
2020 url: https://www.latimes.com/world-nation/story/2020-01-07/tijuana-drug-violence (访问于 04/13/2020)。
[12] 大卫·加涅蒂华纳的警察腐败现象普遍存在:报告。Feb 的。18, 2016. url: https://es .insightcrime.org/新闻/新闻- del-day/corruption-police-tijuana-generalized-report/ (visited on 04/17/2020)。
[13]胡安·米格尔·埃尔南德斯。杀害妇女,不受惩罚。02 月。http://www . elsoldetijuana . com . MX/local/murders-de-women-sin-un-punishment-489276 . html(访问日期:2020 年 3 月 4 日)。
[14]胡安·米格尔·埃尔南德斯。提华纳在经济上保持稳定。02 月。2,2019 . URL:https://wwwelsoldetijuana。-是啊。mx / local / tijuana —经济上保持—稳定— 3009856。html(于 2020 年 7 月 4 日访问)。
[15]胡安·米格尔·埃尔南德斯/。容忍区仍然存在于提华纳。八月 20,2019 . URL:https://www . lavozdelafrontera . com . MX/local/tolerance zone-persistent-en-Tijuana-404833 . html(访问日期:2020 年 7 月 4 日)。
[16] idc。43 %的墨西哥人是数字文盲。打开它。8,2019. url: https : / / idconline。MX/corporate/2019/04/08/43-de-los-Mexican-son-文盲-数字(访问日期:2020 年 7 月 4 日)。
[17]部署。2014 年各国代表团/2014 年殖民地基本地图。2014 . URL:http://implan . Tijuana . gob。MX/services/map/map . aspx(访问时间:2004/17/2020)。
[18]卡洛斯·加西亚。墨西哥的劳动者洛佩斯安全策略有什么缺陷?七个。5,2019. url: https : / /当前。rt 你好吗?com/current/326183-故障-战略-安全性-Lopez-labor-Mexico(访问日期:2020 年 13 月 4 日)。
[19]劳拉·桑切斯·雷。-卖淫。提华纳的儿童地狱。02 月。页:1。elnivasal . com . MX/article/States/2016/02/28/prostitution-El-hell-infantil-in-Tijuana # image-1(访问日期:2020 年 4 月 17 日)。
[20]米格尔马歇尔。提华纳和贸易的未来。2015 年 5 月 7 日,URL:https://www . weforum . org/agenda/2015/05/Tijuana-and-the future-of-trade/(访问日期:2020 年 13 月 4 日)。
[21]安东尼奥玛雅。在桑切斯塔布达发生的抢劫和暴力事件。2019 年 5 月 4 日,URL:https://www . elsoldetijuanacom . MX/policia/assault-y-violence-en-Sanchez-tabada-3570659 . html(访问日期:2020 年 13 月 4 日)。
[22]伊莎贝尔市场。在当局的漠视下,边缘再次被重新定位。2017 年 7 月 24 日,URL:https://zeta Juana。com/2017/07/ante----对当局无动于衷----返回----重新部署/(访问日期:2020 年 7 月 4 日)。
[23] 亚当·蒙德拉贡在下加利福尼亚州,有超过 7,800 起强奸和 18,000 起性犯罪。Aug。17,2019.url: https://cadenanoticias.com/regional/2019/08/en-baja-california-mas-de-7-mil-800-强奸和 18-mil-性犯罪(访问 04/13/2020)。
[24] 政治动物在墨西哥杀人:有罪不罚的保证。六月 19, 2019. url: https://www.animalpolitico.com/muertos-mexico-凶杀案-有罪不罚/ (访问于 04/17/2020)。
[25] Lourdes Loza Romero。BC:汽车盗窃的第一个国家。Apr。13,2020 URL:https 😕 / zetatijuana 。com / 2020 / 04 / bc — 第一 — 地方 — 国家 — 在 — 盗窃 — 从 — 车 / (访问 04/16/2020).
[26] 丹尼尔·安赫尔·卢比奥迫切需要在 Sánchez Taboada 重新安置 2,000:Rosas。Dec。22, 2019. url: https : // www.elsoldetijuana.com.mx/local/urge- 重新定位- a- 2-mile-en-桑切斯-塔布阿达-玫瑰- 4619362.html (访问 04/17/2020).
[27] 格伦·桑切斯蒂华纳需要研究成瘾。七个。26,2019.url: https://www.elimparcial .com/tijuana/tijuana/Tijuana-required-studio-de-瘾-20190926-0005.html (visited on04/13/2020)。
[28] 国家安全和调查警卫队。犯罪行为数据库。url: https://www.seguridadbc.gob.mx/content/statisticas3.php (visited on 04/13/2020)。
[29] 辛提斯,在你脚下。url:https : / / synesistv 。com。mx /卖家 - 街头小贩 - 在 - 的 - 不规则 - 通过 - 延迟 - 通过 - 约会/下 5 / (visited on 04/17/2020)。
[30] 提华纳的太阳他们在 BC 的电话勒索中筹集了大量资金。May 4, 2019.url:https : / / www 。Elsoldetijuana。com。mx / 本地 / 增加 — 金额 — 从 — 钱 — 在 — 勒索 — 电话 — 从 bc-3573103.html (visited on 04/13/2020).
[31] 提华纳的太阳。他们将拆除 Sánchez Taboada 分裂的房屋。Dec。2018 年 2 月 28 日,URL: https: / / www 。Elsoldetijuana。com。mx / 警察 / 拆除 — 房屋 — 在 — 分割 — 桑切斯 — —
taboada-jaime-bonilla-4632848.html (visited on 04/17/2020).
[32]python geooding toolbox . geo 1 . 21 . 0 .2018 . URL:https://pypi . org/project/geo/(访问时间:2020 年 13 月 4 日)。
[33]卡琳·托雷斯。非正规贸易造成了数百万美元的损失:卡纳克。八月 7,2019 . URL:https://www . elsoldetijuana . com . MX/local/commerce-非正式-代表损失-百万美元-canaco-4005433.html(访问日期:2020 年 7 月 4 日)。
[34]美国-墨西哥研究中心。无边界的工作:就业、工业集中,以及在卡利巴地区的比较优势。2014 . URL:https://usex . ucsd . edu/_ fil/2014 _ report _ jobs withoutdaters . pdf(访问时间:2020 年 13 月 4 日)。
[35]戴维·维森特诺和德国瓦努阿图。国民警卫队是国家的军事化:联阿援助团的学者。-简!10,2019 . URL:https://www . excelsior . com . MX/national/guard-es-la-军事化-del-Pais-academic-de-la-unam/1289456(访问日期:2020 年 4 月 16 日)。
[36]卡罗莱纳·巴斯克斯。国民警卫队开始沿绿线和桑切斯塔布达巡逻。2019 年 7 月 12 日,URL:https://psn . si/Guardia-national-start-patrol-cv/2019/07/(访问日期:2020 年 4 月 16 日)。
[37]劳拉·沃登贝格。被驱逐者的炼狱。2013 年 6 月 9 日,URL:https://www . vice . com/es _latam/article/z9 JMX/El-purgatorio-de-los-expulsado-0000410-v6n4(访问日期:2020 年 17 月 4 日)。
[38]奥斯瓦尔多·扎瓦拉。国民警卫队和边境军事化。2019 年 7 月 12 日,URL:https://www . process . com . MX/592118/la-Guardia-national-y-la-armitation-de-las-fronteras(访问日期:2020 年 4 月 16 日)。
Python 和 OpenCV 中视频会议的虚拟背景——一种愚蠢的方法
用 Python 和 OpenCV 创建视频会议的虚拟背景。基本方法教程和思想。

虚拟背景是目前远程工作的员工的热门话题之一。由于新冠肺炎疫情,我们中的一些人目前被隔离,许多人不得不进行视频通话,以便继续他们的工作。一些用于视频会议的软件工具允许设置虚拟背景,以便用户可以为接听这些电话营造更友好的氛围。
作为一名程序员,我第一次使用这样的虚拟背景时,自然被激起了兴趣。我想知道它是如何工作的。我能建立这样的虚拟背景吗?如果是的话,我该怎么做?剧透:进展不顺利!尽管如此,我认为这是一个很好的教育练习,在研究这个话题的时候,我没有找到太多的信息。因此,正如我对所学的一切所做的那样,我决定把它记录在这里,也许其他人会从中受益。
因此,在本教程中,我们将尝试一种用计算机视觉技术构建虚拟背景的基本方法,使用 Python 和 OpenCV。
介绍
这个项目的目标是拍摄一段视频,试着找出视频的背景和前景,去掉背景部分,用一张图片代替——虚拟背景。因为在这个项目中,我们将使用平凡的方法,我们将需要假设,在一般情况下,前景将有不同于背景的颜色。但是首先,让我们看看我们的工具是什么。
计算机视觉
计算机视觉是一个跨学科领域,研究计算机如何处理和(也许)理解图像和视频。我们说它是一个跨学科领域,因为它从不同学科(计算机科学、代数、几何等等)借用了很多概念,并将它们结合起来解决了很多不同的复杂任务,比如图像和视频中的对象跟踪、对象检测、 对象识别、对象分割。
OpenCV
OpenCV 是为解决计算机视觉任务而构建的库。它是开源的,可用于几种编程语言,包括 Python 和 C++。它有大量的计算机视觉功能,其中一些基于数学和统计方法,另一些基于机器学习。
计算机编程语言
如果您已经阅读了本文,那么您可能知道 Python 是什么😀
构建虚拟背景
我尝试的方法如下。我将展示每一步的代码片段,在文章的最后,您将看到完整的代码。
- 导入依赖项
import numpy as np
import cv2
2.从本地环境加载视频并初始化数据
ap = cv2.VideoCapture('video6.mp4')
ret = True
frameCounter = 0
previousFrame = None
nextFrame = None
iterations = 0
3.从本地环境加载替换背景图像
backgroundImage = cv2.imread("image1.jpg")
4.逐帧分割视频
while (ret):
ret, frame = cap.read()
5.取每一对两帧
if frameCounter % 2 == 1:
nextFrame = frame if frameCounter % 2 == 0:
frameCounter = 0
previousFrame = frame frameCounter = frameCounter + 1
iterations = iterations + 1
6.求两帧的绝对差值并转换成灰度->获取蒙版。
if iterations > 2:
diff = cv2.absdiff(previousFrame, nextFrame)
mask = cv2.cvtColor(diff, cv2.COLOR_BGR2GRAY)
每幅图像都由像素组成——你可以把它想象成一个由行和列组成的 2D 矩阵,矩阵中的每个单元都是图像中的一个像素(当然,对于彩色图像,我们有不止 2 个维度,但为了简单起见,我们可以忽略这一点)。
我们通过在第一幅图像中逐个像素地(在第一矩阵中逐个单元地)替换另一幅图像中的相应像素(在另一矩阵中的相应单元)来获得差异。
这里有一个窍门:如果在两帧之间,一个像素没有被修改,那么结果当然会是 0 。2 帧之间像素怎么会不一样?如果视频是完全静态的(图像中没有任何东西移动),那么所有像素的每一帧之间的差异将为 0,因为没有任何东西发生变化。但是,如果图像中有物体移动,那么我们可以通过检测像素差异来识别物体在图像中的移动位置。我们可以假设,在视频会议中,移动的东西在前景中——也就是你——而静止的部分是背景。
还有这个 0 有什么重要的?图像中每个为 0 的像素将显示黑色,我们将利用这一点。
7.找到遮罩中超过阈值的单元格—我选择了 3 作为阈值,但是您可以使用不同的值。较大的值将从背景中移除更多,但也可能从前景中移除更多。
th = 3
isMask = mask > th
nonMask = mask <= th
8.创建一个空图像(每个单元格为 0),大小为两个帧中的任意一个。
result = np.zeros_like(nextFrame, np.uint8)
9.调整背景图像的大小,使其与框架大小相同。
resized = cv2.resize(backgroundImage, (result.shape[1], result.shape[0]), interpolation = cv2.INTER_AREA)
10.对于遮罩中大于阈值的每个单元,从原始帧复制。
result[isMask] = nextFrame[isMask]
11.对于低于阈值的遮罩中的每个单元格,从替换背景图像中复制。
result[nonMask] = resized[nonMask]
12.将结果帧保存到本地环境。
cv2.imwrite("output" + str(iterations) + ".jpg", result)
结果和结论
那么结果如何呢?老实说,我对这个结果有点失望。然后我做了更多的研究,原因变得更加明显。你需要更先进的方法来解决这个问题,大公司在这类问题上投入大量资源也就不足为奇了。
下面是我试的视频截图。基本上就是我的手在墙前移动的视频。

虚拟背景 Python 和 OpenCV 教程—输入
这是输出图像的截图。作为背景,我用了一张我在罗马尼亚拉斯诺夫的照片。

虚拟背景 Python 和 OpenCV 教程—输出
正如我所说的,我对结果不是很满意。但是我对从这个项目中学到的东西很满意。这是一次有趣的学习经历,也是一种很好的方式来花时间处理我不习惯处理的概念。
创建虚拟背景的其他方法
如果你认为一个问题非常复杂,需要你在计算机软件中看到的不寻常的智能水平,那么答案可能是机器学习。😀
已经有深度学习模型可以执行这种任务。但是这样一个模型需要大量的数据集来训练和大量的处理能力,而在我写这篇文章的时候,我还没有这样的能力。这样的深度学习模型要解决的任务叫做图像分割。
另一种方法是计算机视觉方法,用于寻找照相机和图像中的物体之间的距离。然后你可以建立一个阈值来区分前景和背景。在那之后,你可以使用我用来移除背景的相同遮罩,并引入一个新的遮罩。
本文原载于 程序员背包博客 。如果你想阅读更多这类的故事,一定要访问这个博客。
非常感谢您阅读本文!对更多这样的故事感兴趣?在 Twitter 上关注我,地址是@ b _ dmarius,我会在那里发布每一篇新文章。
使用机器学习的视频会议的虚拟背景
使用 DeepLab v3 图像分割模型和 OpenCV Python 实现

每当我们的环境发生变化,技术也随之发展。COVID 期间增加的视频会议突出了虚拟背景的概念。说出任何一种视频通信服务,无论是 Google Meet、Zoom 还是 MS Teams,你都会注意到这个功能。保护一个人的隐私或者只是隐藏他们混乱的环境,虚拟背景真的很有帮助。
好奇如何用简单的编码实现?本文包含理解并在您自己的系统中执行它所需的所有信息。图像分割和掩蔽的概念用分步编码来解释。
虚拟背景
虚拟背景的目的是为了各种个人原因定制一个人的背景。为了能够修改我们的,让我们了解细节。你如何定义背景?在数码相框中,除了你的身体定义之外的一切都可以被认为是背景。想象一下你的网络摄像头的画面,一个像素一个像素地把你的身体描述从其余部分中分离出来。

https://dahtah.github.io/imager/foreground_background.html中西蒙·巴塞尔姆拍摄的图片
同样,这幅图像也描述了手头的任务。没有背景的分割前景鹦鹉是主要焦点。它可以叠加在任何图像/背景上,形成一个整体图像。使用一个简单的 OpenCV 代码来导入一个虚拟的背景图像
图象分割法
图像分割是一种计算机视觉算法,用于将任何图像分成各种片段。分割的输出完全基于应用。对于对象检测,分割的图像将包含不同颜色的汽车、人、道路、停车标志、树和场景中存在的其他对象。

https://www.anolytics.ai/semantic-segmentation-services/中的图像
我们的目标是如前所述分割背景和前景。因此,如果我们检测到一个人的数字,它的像素化分割应该执行。这种分割是使用 TensorFlow 的 DeepLab v3 模型实现的。它相当于迄今为止最好的开源实现之一,甚至能够在视频分割上给出体面的帧速率。
编写以下代码来下载模型。下载后,将其复制到工作目录并删除该块。它必须使用此代码下载,不能通过链接手动完成。
导入必要的库。PIL 和 OpenCV 主要用于分割后的图像处理,而其他的则用于运行 DeepLab 类。该模型能够在以下提到的标签之间进行分类。它接收一个 zip 模型文件并分别预测输出。
声明 DeepLab 类和分割图像所需的其他函数。以下代码段可以在 DeepLab 在其自述文件中提供的笔记本参考中找到。如果您过于担心这段代码的复杂性,请不要担心。一般来说,所有分割模型都是使用 Coco 或 ImageNet 数据集训练的。根据架构、输入和输出格式的不同,下面的代码会有所不同。
让我们做一个状态检查。我们导入了需要的虚拟背景,下载了模型,加载了模型,定义了 DeepLab 类。通过调用该类的 run 函数可以获得分段输出。总的来说,我们有我们的背景和分割需要叠加在它上面。
OpenCV
OpenCV 在我们的应用程序中的主要任务是在虚拟背景中创建分割的遮罩,将所有正在使用的图像调整到相同的尺寸,最后添加被遮罩和分割的图像。从加载的模型继续,让我们看看 OpenCV 中的每一步是如何执行的。
第一步:视频拍摄
视频的每一帧都必须被提取、分割并添加到虚拟背景中。OpenCV video capture 用于加载网络摄像头视频并提取每一帧。一般来说,这个过程会导致延迟,但是轻量级模型和最少的预处理能够提供不错的帧速率。
步骤 2:帧分割
每一帧都通过 MODEL.run()函数来获得结果分割。该类返回 2 个值:调整大小的原始图像和分割的地图轮廓。分割图用于形成描绘边界的图像。

分段掩码的输出
步骤 3:分段地图处理
分割的图像用于从每一帧中提取原始姿态。首先使用下面的方法将它转换成黑白图像。进行这种转换是为了简化掩蔽过程。

在 seg_im 预处理之后
第四步:遮盖
简单来说,遮罩就是在图像上创建一层遮盖物。一方面,我们有一个黑白分段帧,另一方面,原始网络摄像头帧。在 seg_img 中有分段(黑色/0)的地方,原始帧的相应像素保持不变,否则转换为黑色。这是原始帧中分割的遮罩。使用类似的逻辑,在虚拟背景上执行相反的操作。哪里有分割,哪里的像素就转换成黑色。


a.原始帧的屏蔽分割 b .虚拟背景屏蔽
输出
我们就要到达终点了。在对每个所需图像进行遮罩后,只剩下加法。在显示视频的每一帧之前,添加 seg_img 和 back 。

最终输出
结论
我们已经通过 TensorFlow DeepLab v3 模型实现了图像分割。通过使用遮罩和其他 OpenCV 预处理,可以看到最终的虚拟背景。这种方法符合当前的技术标准。虽然这也可以通过其他计算机视觉和图像处理算法来完成,即使不包括深度学习,也可以观察到许多缺陷。让我们列举一些例子:
- 简单的图像处理算法依赖于颜色变化来分离背景和前景。然而,这是一个深度学习模型,侧重于识别。
- 实时输出的准确性,尤其是在视频中,有时会很差。
- 在一个好的系统中,帧速率相当不错,但否则,输出可能会包含巨大的滞后。为了克服这一点,代码可以在 CUDA 上托管或实现。
- 代码主要使用 TensorFlow 和 OpenCV。它可以很容易地编码并在任何系统上运行,没有太多的依赖性问题。
这篇文章大体上到此为止。我希望这有助于理解虚拟背景是如何实现的。对于最初的 DeepLab v3 实现,请访问 DeepLab 笔记本。结果的准确性可能不太令人满意,因此我期待任何改进的建议。如果你遇到任何错误或有任何疑问,请评论。
关于我
我是一名三年级的 CSE 学生。我的主要兴趣和工作主要在于深度学习、强化学习、遗传算法和计算机视觉。如果你对这些话题感兴趣,可以看看我以前的博客,关注我,了解最新动态。我的项目和个人资料可以在 Github 和 LinkedIn 上查看。
具有身体分割技术的网络摄像机中的虚拟背景
网络摄像头背景变化不仅限于现在缩放,我只是用 tensorflow.js body-pix 模型在浏览器里做了一下

你有没有过这样的时刻,当你在社交媒体上浏览那些漂亮的旅行自拍时,你会自言自语:“我希望我能在那里”?你猜怎么着,我们今天要让它成真。利用最新的人物分割技术,我们可以在像素级上将身体部分与背景分开。
非常类似于视频会议软件 Zoom 的改变背景功能,可以把你身后凌乱的房间隐藏起来,把你运送到一个荒岛或者宁静的海滩。
在这篇文章中,我将向你展示如何构建一个随处自拍应用程序,它可以通过实时改变自拍的背景,让你置身于那些美丽的旅行场景中。不需要 Photoshop,也不需要绿屏。让我们找点乐子,倒数“3,2,1…说茄子”!

自己试试吧,演示在下面的链接里:
[## 使用 BodyPix - Benson 技术进行随处自拍-人物分割
你有没有过这样的时刻,当你在社交媒体上浏览那些漂亮的旅行自拍时,你会自言自语:“我希望我…
bensonruan.com](https://bensonruan.com/selfie-anywhere-person-segmentation-with-bodypix/)
履行
你在上面拍了一些自拍照,然后给你的朋友看了吗?我希望你喜欢它,这个应用程序利用了一种叫做身体分割的先进技术,它可以识别图像或视频流中的人,并从背景中分割出前景身体。
今年早些时候,谷歌发布了 BodyPix ,这是一个开源的机器学习模型,允许在浏览器中使用 TensorFlow.js 进行人和身体部位分割。我对这项技术感到惊讶,并产生了构建上述自拍随处应用程序的想法。下面请跟随我了解我是如何实现它的。
#步骤 1:包含 tfjs 和 body-pix
首先,简单的在 html 文件的<头>部分包含脚本Tensorflow.js及其body-pix模型。
<script src="[https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@1.2](https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@1.2)"></script><script src="[https://cdn.jsdelivr.net/npm/@tensorflow-models/body-pix@2.0](https://cdn.jsdelivr.net/npm/@tensorflow-models/body-pix@2.0)"></script>
或者您可以通过 npm 安装它,以便在 TypeScript / ES6 项目中使用
npm install @tensorflow-models/body-pix
#步骤 2:将网络摄像头传输到浏览器
为了让你的网络摄像头进入浏览器,我使用了 JavaScript 库navigator.mediaDevices.getUserMedia。要了解更多细节,请参考我以前的文章:
[## 如何使用 JavaScript 访问网络摄像头并拍照
介绍网络摄像头-简易 npm 模块
medium.com](https://medium.com/swlh/how-to-access-webcam-and-take-picture-with-javascript-b9116a983d78)
#步骤 3:加载 BodyPix 模型
为了进行分割,我们首先需要通过调用bodyPix.load(modelConfig)的 API 来加载预先训练好的 BodyPix 模型。BodyPix 附带了几个不同版本的模型,具有不同的性能特征,在模型大小和预测时间与准确性之间进行权衡。
默认情况下,BodyPix 加载一个带有0.75乘法器的 MobileNetV1 架构。对于配有中端/低端 GPU 的计算机,建议这样做。对于移动设备,建议使用带有0.50乘数的型号。ResNet 体系结构推荐用于具有更强大 GPU 的计算机。
**bodyPix.load**({
architecture: 'MobileNetV1',
outputStride: 16,
multiplier: 0.75,
quantBytes: 2
})
#第四步:身体分割
接下来,我们通过调用net.estimatePersonSegmentation(video, outputStride, segmentationThreshold)的 API,开始通过 body-pix 模型馈送网络摄像机流以执行人物分割。它将图像分割成像素,这些像素是和不是人的一部分。它返回一个二进制数组,对于作为人的一部分的像素为 1,否则为 0。数组大小对应于图像中的像素数。
**net.segmentPerson**(webcamElement, {
flipHorizontal: true,
internalResolution: 'medium',
segmentationThreshold: 0.5
})
**.then(personSegmentation** => {
if(personSegmentation!=null){
drawBody(personSegmentation);
}
});
cameraFrame = requestAnimFrame(detectBody);
flipHorizontal默认为假。如果分割&姿态应该水平翻转/镜像。对于默认水平翻转的视频(即网络摄像头),此选项应设置为 true,并且您希望分段&姿势以正确的方向返回。
segmentationThreshold用于确定像素分数的最小值,该值必须被视为一个人的一部分。本质上,较高的值将在人的周围创建更紧密的裁剪,但是可能导致作为人的一部分的一些像素被从返回的分段掩码中排除。
它返回一个用SemanticPersonSegmentation对象解析的Promise。图像中的多个人被合并到一个二元蒙版中。除了width、height和data字段,它还返回一个包含所有人姿势的字段allPoses。所有人的数据数组,包含 307200 个值,640x480 图像的每个像素一个值。
{
width: 640,
height: 480,
data: Uint8Array(307200) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, …],
allPoses: [{"score": 0.4, "keypoints": […]}, …]
}
#第五步:去除背景
在上面的函数中,我们用二进制数组来表示像素是否属于人体,现在我们可以用它来去除背景,只在画布上画出人体。在 ImageData 对象中,每个像素保存红色、绿色、蓝色和 alpha(透明度)的值,移除背景的技巧是将像素的transparency值设置为 0。
const canvasPerson = document.getElementById("canvasPerson");
let contextPerson = canvasPerson.getContext('2d');
function drawBody(personSegmentation)
{
contextPerson.drawImage(camera, 0, 0, camera.width, camera.height);
var imageData = contextPerson.getImageData(0,0, camera.width, camera.height);
var pixel = imageData.data;
for (var p = 0; p<pixel.length; p+=4)
{
**if (personSegmentation.data[p/4] == 0) {
pixel[p+3] = 0;
}**
}
contextPerson.imageSmoothingEnabled = true;
contextPerson.putImageData(imageData,0,0);
}
#第六步:在背景图片上覆盖画布
一旦我们有了只包含透明背景的身体的画布,那么我们只需要将它覆盖在令人惊叹的自然场景的背景图像上。
<video id="webcam" autoplay playsinline width="640" height="480"></video>
<div id="selfie-container">
**<div id="background-container"></div>**
<canvas id="canvasPerson" width="640" height="480"></canvas>
</div>
在下面应用 css 样式
#background-container {
height: 100vh;
width: 100vw;
**background-image: url(../images/greatwall.jpg);**
background-position: center center;
background-repeat: no-repeat;
background-size: cover;
background-color: transparent;
}
#canvasPerson{
**background-color: transparent;**
position: absolute;
width: 100vw;
height: auto;
z-index: 9999;
margin: auto;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin-left: auto;
margin-right: auto;
-moz-transform: scale(-1, 1);
-webkit-transform: scale(-1, 1);
-o-transform: scale(-1, 1);
transform: scale(-1, 1);
filter: FlipH;
}
#第七步:截图
为了拍照,我使用了第三方 JavaScript 库 html2canvas.js 。它允许你直接在用户浏览器上截取网页或部分网页的“截图”。
$("#take-photo").click(function () {
beforeTakePhoto();
var captureElement= document.getElementById('selfie-container');
var appendElement= document.getElementById('webcam-container');
**html2canvas(captureElement)**.then(function(canvas) {
canvas.id='captureCanvas';
appendElement.appendChild(canvas);
document.querySelector('#download-photo').href = canvas.toDataURL('image/png');
afterTakePhoto();
});
});
代码就这么多了!除此之外,只是使演示看起来不错。选择一个壮观的场景,摆好你最喜欢的姿势,微笑吧!
GitHub 知识库
您可以通过下面的链接下载上述演示的完整代码:
实例分割将人体与背景分离。通过改变,让自己置身于美丽的自然风景中…
github.com](https://github.com/bensonruan/Selfie-Anywhere)
结论

实例分割技术的进步现在显示出从自动驾驶汽车到医疗诊断等许多领域的前景。有了深度学习神经网络,模型现在变得越来越准确。我希望这篇文章只是你学习更多图像分割和计算机视觉的旅程的开始。
感谢您的阅读。如果你喜欢这篇文章,请在脸书或推特上分享。如果你有任何问题,请在评论中告诉我。在 GitHub 和 Linkedin 上关注我。







浙公网安备 33010602011771号