TowardsDataScience-博客中文翻译-2022-四十一-
TowardsDataScience 博客中文翻译 2022(四十一)
提督+ AWS ECS Fargate + GitHub 动作让无服务器数据流变得像。巴拉圭
用 Python 和 Prefect 在 AWS 上编排无服务器

一只名叫马文的蓝鸭子将无服务器容器运送到云中——图片由作者提供
mazon 弹性集装箱服务(ECS)是一个管理任何规模的集装箱的平台。简单的任务定义 API 允许您单独启动容器化的工作负载,也可以作为连续运行的服务启动。您可以将您的容器部署到一个无服务器的 Fargate 数据平面、一个自我管理的 EC2 实例集群,甚至您的本地数据中心( ECS Anywhere )。
尽管这个容器编排平台有很多好处,但是围绕它构建一个可靠的工程流程(包括自动化部署、版本控制、调度或重试)可能会很有挑战性。类似地,CloudWatch 控制台也没有为可观察性提供很好的 UX。这就是 Prefect 和 GitHub 的作用所在。
这篇文章将深入使用 GitHub Actions 工作流部署一个完美的代理到 AWS ECS Fargate。它将演示如何将任何 Python 脚本转换成在 AWS 云中的无服务器容器上运行的工作流。
**Table of contents**· [What is Prefect?](#0119)· [Demo: Deploy a Prefect agent to AWS ECS](#db7b)
∘ [Create a new repository from the DataflowOps template](#3039)
∘ [Create an API Key in Prefect Cloud](#095b)
∘ [Install Prefect locally](#b510)
∘ [Authenticate your terminal with Prefect Cloud & run first flow](#f944)
∘ [Retrieve the PREFECT_API_URL and PREFECT_API_KEY](#754f)
∘ [Set the PREFECT_API_URL and PREFECT_API_KEY repository secrets](#50f6)
∘ [Start the GitHub Actions workflow to deploy your Prefect agent](#7dd2)
∘ [Inspect the output and download run artifacts](#0178)
∘ [Video walkthrough as a recap](#a257)· [Resources created with CloudFormation and GitHub Actions workflow](#8bdd)
∘ [Validate deployments](#56b7)
∘ [Inspect and optionally modify Block values](#18b1) · [Deploy flows using ECSTask infrastructure blocks](#8d94)
· [Delete all resources](#6255)
· [Next steps](#f738)
什么是提督?
对于任何数据栈来说,Prefect 都是一个全局协调平面。它允许您设计、构建、调度和监控您的数据流,涵盖从可观察性到编排的全部范围。下面是一个简单的提督流程示例:
使用一个CLI 命令,您可以构建一个可以在任何基础设施上运行的调度部署——本地流程、Docker 容器、Kubernetes 作业或 ECS Fargate 上的无服务器任务。在这篇文章中,我们将关注后者。但是在我们能够部署流之前,我们需要一个能够轮询预定运行的代理。我们将在接下来讨论这个问题。
演示:将一个提督代理部署到 AWS ECS Fargate
要在 AWS 上运行的无服务器容器中启动一个完全代理,您需要创建一个 ECS 任务定义。然后,您可以使用该任务定义来启动 ECS 服务,这将确保代理容器全天候可靠运行。
为了使本教程易于理解,我们将跳过为任务角色和执行角色配置 IAM 权限以及将依赖项打包到 Amazon ECR 映像的细节。所有这些信息都可以在 GitHub 资源库 dataflow-ops 的基础设施文件夹中找到。您可以使用现有的 Cloudformation 模板,也可以根据自己的需要进行调整。
从 DataflowOps 模板创建新的存储库
使用存储库模板 dataflow-ops 创建您自己的存储库:

从模板创建新的存储库—按作者分类的图像
在完美云中创建 API 密钥
为了开始使用提督,注册了提督云的免费等级。一旦登录,创建一个工作空间和一个 API 键。

在 Prefect Cloud 中创建 API 密钥—图片由作者提供
创建工作空间和 API 键对于 AWS ECS Fargate 设置非常重要。
在本地安装提督
开始使用 Prefect 的最简单方法是在本地安装它:
pip install prefect
使用 Prefect Cloud 验证您的终端并运行您的第一个流程
运行下面的命令来验证与您的完美云工作区的本地终端会话:
prefect cloud login
系统会提示您输入之前创建的 API 密钥,并选择您的工作区。然后,您可以像运行任何其他 Python 脚本一样运行示例流:
python maintenance_flow.py
当您切换到您浏览器时,您现在应该看到流程在您的完美 UI 中运行:

完美的云显示来自本地执行流程的日志——图片由作者提供
你可能会问自己,这怎么可能?提督怎么知道你从你的终端运行这个流?那就是提督 API 的魔法!您不必创建任何 Dag 或学习任何定制词汇来跟踪工作流执行。完美的流程就像涡轮增压的 Python 脚本。无论您是在本地运行它们,还是从 AWS 上的无服务器容器运行它们,Prefect 都会确保您的执行元数据保持可观察性。
检索提督 API URL 和提督 API KEY
现在您的终端已经过身份验证,运行:
prefect config view
您应该得到类似于以下内容的输出:

从终端输出中检索 API URL 和 API 键——按作者排序的图像
将提督 _API_URL 和提督 _API_KEY 设置为存储库机密
使用终端输出中显示的提督 _API_URL 和提督 _API_KEY 的值来配置存储库机密,如下图所示。

配置存储库机密-按作者排列的图像
添加您的 IAM 用户的 AWS 访问密钥作为存储库机密: AWS_ACCESS_KEY 和 AWS_SECRET_ACCESS_KEY 。
启动 GitHub 操作工作流来部署您的提督代理
现在,您的 GitHub 存储库秘密指向您的 AWS 和 Prefect Cloud 帐户,您已经准备好触发GitHub 操作工作流:

启动 GitHub 操作工作流部署提督代理并流向 AWS ECS 和 S3 —图片由作者提供
在运行此工作流之前,您可以配置提督版本、AWS 区域、CPU 和内存分配,以及您的提督存储和基础结构块的名称。
检查输出并下载运行工件
一旦此工作流成功完成,您应该会看到类似的图表和输出:

工作流摘要-作者提供的图片
您可以下载用于部署 ECS 代理的 ECS 任务定义以及流部署的 YAML 清单,如上图中突出显示的部分所示。这些工件有助于可审计性和故障排除。
视频演练作为回顾
以下链接以视频格式演示了相同的工作流程:
使用 CloudFormation 和 GitHub 操作工作流创建的资源
您可以检查代理的 CloudFormation 堆栈的输出,以验证创建了哪些资源。
类似地,如果您检查 Prefect Cloud UI,您会发现与我们之前提到的相同的维护流程。该流程计划每 10 秒运行一次,以演示使用 Prefect 的计划工作流的功能。如果您检查该流的任何运行日志,您将看到它运行在部署到 AWS EC2 的无服务器容器上:

流量运行日志—作者提供的图片
验证部署
您可以从完美的 UI 中验证部署:

完美 UI 中的部署页面—作者提供的图片
上图显示了本演示中设置的两种部署:
- 名为
dataflowops-local的部署被配置为直接在代理的本地进程中运行,也就是说,在与您的完美代理相同的容器中运行。 - 相比之下,名为
dataflowops的部署被配置为作为独立的 ECS 任务运行。
第二个选项带来了更高的延迟(必须首先提供无服务器容器),但在大规模应用时可能会更好。当您达到一定数量的工作流时,您可能会遇到在同一个容器中运行所有内容的极限。ECSTask基础设施块允许您在自己的无服务器容器中运行每个流,而不占用代理进程的资源(在自己的 ECS 任务中运行)。
检查并可选地修改块值
访问“blocks”页面时,您可以查看作为本演示的一部分而创建的所有块。您应该会看到 S3、AWS 凭证和 ECS 任务块。您可以直接从 UI 更改值,如 CPU、内存或环境变量,而不必重新部署代码。

从完美用户界面修改 ECSTask 块值—按作者排序的图像
如果你想创建一个新的ECSTask块,你可以这样做:
a)从 UI,如上图所示,
b)从 Python 代码,如这个代码片段所示。
使用 ECSTask 基础架构块部署流
要在您的部署中使用ECSTask块,您需要做的就是将ecs-task/block_name指定为您的基础设施块(-ib)。这里有一个例子:
prefect deployment build flows/healthcheck.py:healthcheck -n prod -q prod -a -sb s3/prod -ib ecs-task/prod
您不仅可以从 CI/CD 中这样做,甚至可以从您的本地终端中这样做。关于话语的完美部署常见问题解答提供了许多关于如何使用各种部署用例的有用资源。
删除所有资源
要删除作为本演示的一部分创建的所有 AWS 资源,请运行以下工作流。这将删除两个云形成堆栈。

删除云信息资源,包括 ECR 存储库和 ECS 集群——作者图片
后续步骤
这篇文章介绍了如何开始使用 Prefect 和 ECS Fargate,以及如何将代理和流程部署为自动化的 GitHub Actions 工作流。如果你对这个设置有疑问,你可以直接在 dataflowops repository 上提交一个 GitHub 问题,或者通过 Prefect Discourse 或 Slack 提问。
感谢阅读!
pre gex:Python 中普通英语的正则表达式
原文:https://towardsdatascience.com/pregex-regular-expressions-in-plain-english-in-python-4670425d0eb5
用 Python 创建正则表达式从未如此简单

记住正则表达式(regex) 中的元字符并不难,但是构建一个匹配复杂文本模式的元字符有时很有挑战性。
如果我们可以用简单的英语构建正则表达式会怎么样?
现在,您可以使用名为 PRegEx 的 Python 库编写易于理解的正则表达式。这个库可以温和地向初学者介绍 regex 的世界,甚至帮助那些已经知道 regex 的人。
它是这样工作的。
安装库
首先,我们需要安装 PRegEx(它需要 Python >= 3.9)
pip install pregex
探索 PRegEx 库
假设我们只想捕获包含姓氏、头衔和以下文本名称的行。
text = """
Here are the full name of some people:
Smith, Mr. Robert
Johnson, Ms Mary
Wilson, Mrs. Barbara
Taylor, Dr Karen
Lewis, Mr. John
"""
我们可以通过编写以下代码用 PRegEx 解决这个问题。
from pregex.core.classes import AnyButWhitespace
from pregex.core.quantifiers import OneOrMore, Optional
from pregex.core.operators import Either
family_name = OneOrMore(AnyButWhitespace())
title = Either("Mrs", "Mr", "Ms", "Dr")
given_name = OneOrMore(AnyButWhitespace())
pre = (
family_name +
', ' +
title +
Optional(".") +
' ' +
given_name
)
pre.get_matches(text)
以下是输出结果:
['Smith, Mr. Robert',
'Johnson, Ms Mary',
'Wilson, Mrs. Barbara',
'Taylor, Dr Karen',
'Lewis, Mr. John']
这是等价的正则表达式模式。
>>> pre.get_pattern()
'\\S+, (?:Mrs|Mr|Ms|Dr)\\.? \\S+'
让我们看看我们从 pregex 导入的那些元素意味着什么:
AnyButWhitespace匹配除空白以外的任何字符(相当于 regex 中的\S)OneOrMore匹配一个字符一次或多次(相当于 regex 中的+)Either匹配所提供的模式之一(相当于 regex 中的|)Optional零次或一次匹配一个字符(相当于 regex 中的?)
虽然现在我们使用普通英语来匹配文本模式,但我们仍然需要熟悉可以从 pregex 导入的所有元素。
让我们通过一些例子来探索 pregex 库。
示例 1:获取正确的日期格式
假设我们希望在下面的文本中获得格式 DD-MM-YYYY。
text = """
04-13-2021
2021-04-13
2021-13-04
"""
这是我们如何用 pregex 解决的。
from pregex.core.classes import AnyDigit
from pregex.core.quantifiers import Exactly
two_digits = Exactly(AnyDigit(), 2)
four_digits = Exactly(AnyDigit(), 4)
pre = (
two_digits +
"-" +
two_digits +
"-" +
four_digits
)
pre.get_matches(text)
以下是输出结果:
['04-13-2021']
这是等价的正则表达式模式。
>>> pre.get_pattern()
'\\d{2}-\\d{2}-\\d{4}'
让我们看看我们从 pregex 导入的那些元素意味着什么:
AnyDigit匹配从 0 到 9 的任何数字(相当于 regex 中的\d)Exactly匹配重复 n 次的精确字符数(类似于 regex 中的{n})
示例 2:获得正确的电子邮件格式
假设我们希望在下面的文本中得到正确的电子邮件格式。
text = """
example@python.com
example@@python.com
example@python.com.
"""
这是我们如何用 pregex 解决的。
from pregex.core.classes import AnyButFrom
from pregex.core.quantifiers import OneOrMore, AtLeast
from pregex.core.assertions import MatchAtLineEnd
non_at_sign_space = OneOrMore(AnyButFrom("@", ' '))
non_at_sign_space_dot = OneOrMore(AnyButFrom("@", ' ', '.'))
domain = MatchAtLineEnd(AtLeast(AnyButFrom("@", ' ', '.'), 2))
pre = (
non_at_sign_space +
"@" +
non_at_sign_space_dot +
'.' +
domain
)
pre.get_matches(text)
以下是输出结果:
['example@python.com']
这是等价的正则表达式模式。
>>> pre.get_pattern()
'[^ @]+@[^ .@]+\\.[^ .@]{2,}$'
让我们看看我们从 pregex 导入的那些元素意味着什么:
AnyButFrom匹配除括号内的字符之外的任何字符(相当于 regex 中的[^])AtLeast匹配至少一定数量的重复 n 次的字符(类似于 regex 中的{n,})MatchAtLineEnd在行尾断言位置(相当于当标志“多行”打开时 regex 中的$)
恭喜你!您已经完成了掌握正则表达式的第一步!如果你想学习如何构建标准的正则表达式,查看本指南。
用 Python 学习数据科学? 通过加入我的 10k+人电子邮件列表,获取我的免费 Python for Data Science 备忘单。
如果你喜欢阅读这样的故事,并想支持我成为一名作家,可以考虑报名成为一名媒体成员。每月 5 美元,让您可以无限制地访问数以千计的 Python 指南和数据科学文章。如果你使用我的链接注册,我会赚一小笔佣金,不需要你额外付费。
https://frank-andrade.medium.com/membership
PRegEx:用 Python 编写人类可读的正则表达式
原文:https://towardsdatascience.com/pregex-write-human-readable-regular-expressions-in-python-9c87d1b1335
用人类可读的语法创建复杂的正则表达式
动机
假设你试图找到一个文本中的所有网址。每个 URL 都必须:
- 以
http://或https://或 URL 的域开始 - 以
.com或.org结尾
您最终可能会编写一个复杂的正则表达式(RegEx),如下所示:

作者图片
这个正则表达式很难阅读和创建。有没有一种方法可以让你更容易地编写一个人类可读的正则表达式?
这时 PRegEx 就派上用场了。
什么是 PRegEx?
PRegEx 是一个 Python 包,它允许你以一种更加人性化的方式构建正则表达式模式。
要安装 PRegEx ,输入:
pip install pregex
本文中使用的 PRegEx 版本是 2.0.1:
pip install pregex==2.0.1
为了学习如何使用 PRegEx,让我们从一些例子开始。
捕获 URL
获取一个简单的 URL
首先,我们将尝试使用 PRegEx 获取文本中的 URL。
输出:
['[https://github.com/khuyentran1401'](https://github.com/khuyentran1401')]
在上面的代码中,我们使用:
AnyButWhitespace()匹配除空白字符以外的任何字符OneOrMore()一次或多次匹配提供的模式。Either匹配任何一个提供的模式。
具体来说,
OneOrMore(AnyButWhitespace())匹配一个或多个非空白字符。Either(".com", ".org)匹配.com或.org。

HTTP 或 HTTPS
有时候,一个 URL 可能使用方案http而不是https。让我们通过使用Optional()使字符s可选:

作者图片
输出:
['http://github.com/khuyentran1401']
不使用方案匹配 URL
文本中的一些 URL 可能不包含类似于https或http的方案。用Optional让方案可选。

作者图片
为了使我们的代码更具可读性,我们将把 PRegrex 的方法赋给一个变量。
输出:
['mathdatasimplified.com/', 'https://github.com/khuyentran1401']
让我们来看看基本的正则表达式模式:
(?:https?\:\/\/)?[^\s]+(?:\.com|\.org)[^\s]+
我们刚刚避免了用一些人类可读的代码行创建复杂的模式!
捕获时间
AnyDigit()匹配任何数字字符。让我们用它来匹配文本中的时间。
['6:0']
现在,我们只匹配:两边的一个数字。让我们把AddLeastOnce()放在AnyDigit()的周围,这样就更一般了:

['6:00']
获取电话号码
电话号码的常见格式有:
##########
###-###-####
### ### ####
###.###.####
这些格式的数字之间要么有标点符号,要么没有。我们可以使用AnyFrom("-", " ", ".")来匹配-、.或空格。

我们还使用Optional()使标点符号可选。
['3452352312', '345-235-2312', '345 235 2312', '345.235.2312']
捕获电子邮件地址
现在,让我们利用我们目前所学的知识,从文本中获取一个电子邮件地址。

输出:
['abcd@gmail.com']
下一步
本文概述了如何使用 PRegEx 来匹配复杂的模式,而无需花费大量时间。
我鼓励您查看 PRegEx 的文档以了解其他有用的方法。
随意发挥,并在这里叉这篇文章的源代码:
https://github.com/khuyentran1401/Data-science/blob/master/productive_tools/pregex.ipynb
我喜欢写一些基本的数据科学概念,并尝试不同的数据科学工具。你可以在 LinkedIn 和 Twitter 上与我联系。
如果你想查看我写的所有文章的代码,请点击这里。在 Medium 上关注我,了解我的最新数据科学文章,例如:
https://medium.com/the-prefect-blog/sending-slack-notifications-in-python-with-prefect-840a895f81c 💔-tools-to-track-and-visualize-the-execution-of-your-python-code-666a153e435e> [## 3 个跟踪和可视化 Python 代码执行的工具
towardsdatascience.com](/3-tools-to-track-and-visualize-the-execution-of-your-python-code-666a153e435e)
通过滚动交互式显示空间数据
原文:https://towardsdatascience.com/presenting-spatial-data-interactively-by-scrolling-502df345dea6

来源:作者,2022
通过交互式地图讲述故事和见解
关于这篇文章
这篇文章是关于我制作的一个故事地图:
…这篇文章讲述了我是如何构建它的。

演示(来源:作者,2022)
介绍
制图员的产品是地图。我们使用 shapefiles(这是空间数据存储的遗产)、地理数据库、geojsons 和许多形式的空间数据,并使用 GIS 根据它们制作地图。然而,这些地图通常是 JPEGs、PNG 或 PDF 文档,不具有交互性。在大多数情况下,是的,这是可行的,但是我喜欢在不需要做太多工作的情况下讲述和被讲述故事。事半功倍,怎么才能被讲故事?
然后遇到了库斯伯特·周关于 D3.js 的文章,D3.js 是一个 javascript 库,用来操作 DOM(文档对象模型)中的元素和 svg。人们使用 D3.js 来呈现数据,其中 Chow 做了精彩的演示,扩展了 Jim Vallandingham 的文章。看他下面的文章。
然后我想到:我们也可以用地图来制作这个!使用 javascript 可以实现滚动和交互显示数据的想法。这篇文章讲述了我是如何做到的。
您可以在此处找到现场演示:

该项目(作者,2022)
大量
所有数据和地图资源都是开源的(openstreetmap)。以下是我用过的堆栈
- jquery 和 d3.js:文档操作
- fleet . js:交互式网络地图
一般想法
我写的是空间数据科学,在接下来的文章中我写了一篇关于表示空间数据的文章。本文就是论证。
基本上它的工作原理是:
- 使用 Python 获取和分析空间数据。
- 用 HTML 来布局文档,用 CSS 来美化事物;呈现内容。
- 使用 Javacript 使事物互动。在这种情况下,当我们滚动时改变地图。
此外(不在本文/项目中),我们可以做一些关于数据源的事情:
- 在 postgresql + postgis 数据库服务器中存储数据。
- Apache Airflow 来编排/自动获取数据并填充数据库服务器。
- 使用 geoserver 作为后端地图服务器。
也许是为了将来的项目。
它是如何工作的
我就不赘述细节了,不然这篇文章会变得很长。我将解释的是开发人员…开发的高层次想法。
大部分代码来自 Jim 关于 javascript scroller 的文章。基于这段代码的演示是这里有。
https://vallandingham.me/scroller.html
但是在这篇文章中,我讨论的是我的演示。我将根据这些组件对其进行分解:
- 布局
- 节和滚动
- 数据存储
布局-修复地图
布局基于 Bootstrap 5 css 模块。这是一个很常见的快速美化 HTML 的模块。它提供了最基本的 UI 组件。
具体来说,我做了两列,如下图所示。灰色是主体的背景,蓝色是边距,白色/浅色是列的背景。

布局(来源:作者,2022)
<div class="container">
<div class="row">
<div class="col-4">
the first column, text contents / stories
</div>
<div class="col-8">
the second wider column where the map is going to live
</div>
</div>
</div>
我希望卷轴与故事/文本交互,而不是与地图交互。这意味着无论我们滚动多少,地图都需要固定。这就是 css 的用武之地。所有忽略滚动的东西都需要固定位置,所以我创建了stay css 类。
.stay {
position: fixed;
height: 100%;
width: 100%
}
然后贴在地图会住的div里。
<div class="col-8">
<!-- the second wider column where the map is going to live. -->
<div class="stay" id="mapcontainer">
<div id="map" ></div>
</div>
</div>
这样,地图div就固定了。
地图和 Javascript
随着地图div的创建,我们现在可以导入leaflet javascript 库来制作我们的地图。Leaflet提供我需要的地图交互工具。这是一个绝妙的包装;如此简单却有效!
var map = L.map('map').setView([51.505, -0.09], 13);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
L.marker([51.5, -0.09]).addTo(map)
.bindPopup('A pretty CSS3 popup.<br> Easily customizable.')
.openPopup();
节和滚动
就像 Chow 和 Jim 的结构一样,到达伦敦的页面也由 id 为step的部分组成。
<div class="col-4 full ">
<section class="step ">
my first section
</section>
<section class="step ">
the second section
</section>
<section class="step ">
and so on
</section>
<section class="step ">
...
</section>
</div>
滚动互动
定义步骤后,文档需要跟踪用户的滚动活动。下面的代码在用户每次滚动时调用trackPosition函数。
d3.select(window)
.on("scroll.scroller", trackPosition);
trackPosition如下:
// sectionsArray are the div step (s) from the HTML that we previously defined
let activeSection;
let previousSection;
const trackPosition = ()=>{
let pos = window.pageYOffset - 140;
let sectionIndex = d3.bisect(sectionPositions, pos);
sectionIndex = Math.min(sections.size() - 1, sectionIndex);
activeIndex = sectionIndex
if (sectionsArray[sectionIndex] !== activeSection){
previousSection = activeSection
activeSection = sectionsArray[sectionIndex]
d3.select(`#${activeSection}`)
.transition()
.style("opacity", "1");
d3.select(`#${previousSection}`)
.transition()
.style('opacity', '0.2')
;
positionMap(sectionIndex)
}
}
请注意positionMap功能!这个函数是地图改变的原因。
// positionMap: changes the map based on the active step
const positionMap = (sectionIndex) =>{
if (sectionIndex === 0){
map.flyTo([51.505404,-0.118658], 9,) // zoom in to coords
airportLayer.addTo(map) // leaflet layer
elizaebthLine_st.remove() // leaflet layer
popupairport() // popup the map
attractions.remove() // leaflet layer
}
if (sectionIndex === 1){
map.flyTo([51.509687,-0.115464], 13,) // zoom in to coords
attractions.addTo(map) // leaflet layer
attractions.eachLayer((l)=>{l.openTooltip()}) // open the tooltips
// add another if and manually code the interactivity. Read the leaflet documentation.
}
数据存储
在无模式数据结构中,基本上是一个包含 JSON 对象的数组,我们可以添加任意属性。我在数据结构文章中演示了这一点。
我们可以这样定义一个最小的空间数据:
// minimum spatial data
const berlin = {
"city": "berlin",
"country": "germany",
"location" : {
"type" : "Point",
"coordinates": [52.507650, 13.410298]
}
}
但是我局限于 GeoJSON 空间数据规范,所以空间数据看起来像这样:
const attractions_geojson = {
"type": "FeatureCollection",
"features": [
{
// minimum spatial data but with geojson spec
"type": "Feature",
"properties": {
"name": "big ben"
},
"geometry": {
"coordinates": [
-0.12466064329075266,
51.50067738052945
],
"type": "Point"
}
},
{
// minimum spatial data but with geojson spec
"type": "Feature",
"properties": {
"name": "leicester square"
},
"geometry": {
"coordinates": [
-0.13047682089788282,
51.51079955591317
],
"type": "Point"
}
}
]
}
在上面的代码中,包含我们的层的attractions_geojson.features由两层组成:大本钟和莱斯特广场。geojson.features是所有最小空间数据的列表。
当您在浏览器中加载演示链接时,您可以在variable.js中查看空间数据。

结论
使用 Javascript 和 HTML,我们可以使我们的数据具有交互性。作为一名制图员,这意味着我可以使我的地图互动和响应!棘手的是将空间数据存储为 javascript 文件,而不是传统的 shapefile 和 geojson 对象。
使用这个免费的 dbt 软件包防止数据丢失
原文:https://towardsdatascience.com/prevent-data-loss-with-this-free-dbt-package-a676c2e59c97
因为你的数据价值百万…

米克·豪普特在 Unsplash 上的照片
您是否曾经在编写数据模型或完成深入分析时发现您的数据有问题?这可能是作为数据专业人员最糟糕的发现之一。你花了这么多时间来确保你的数据是可靠的,进行了多重质量检查,结果却出了问题。
我去过那里。几周前,我验证了自己编写的数据模型,却发现我们最重要的表之一的数据量非常少。经过深入挖掘,我们发现这个问题已经持续了整整 2 周。
幸运的是,我们能够修复它,但是 2 周的数据丢失太多了。尤其是当您是一家希望利用您的数据实现重大增长的小型企业时。
因此,作为一名数据问题解决者,我开始寻找一种解决方案,当我们的日常数据量出现差异时,这种解决方案将有助于提醒我们。
令人惊讶的是,这是一个很难找到的东西。我们使用 Fivetran 将我们的数据接收到雪花数据仓库中,但这两者都没有针对数据量的警报。他们甚至没有让我编写自己的自动化脚本的选项!
什么是 re_data?
在经历了很多挫折之后,我偶然发现了 re_data ,“一个开源的& dbt 原生的,为现代数据栈构建的数据可靠性框架”。这个框架允许您创建警报、跟踪指标和存储 dbt 测试历史。
对于表,这些指标包括数据量和新鲜度。
对于列,这些度量包括聚合、空值、标准偏差、长度等等。
您可以跟踪许多指标来创建更可靠的数据生态系统。
re_data 的工作原理是检测数据中的趋势(并向您发出警报)以及发送清除、规范化和验证数据的宏。通过向您的 YAML 文件添加一些配置细节,您可以轻松地设置它来跟踪您的仓库中的数据。
在本文中,我将教您如何建立一个警报系统,当您的表的数据量远远超出正常水平时,该系统会向您发送一条 Slack 消息。
下载 re_data
无论您是否是 dbt 用户,re_data 都在他们的文档中包含了安装说明。幸运的是,这一步非常简单。如果您是 dbt 用户,只需将包名添加到 packages.yml 文件中,然后运行dbt deps。
您的文件应该如下所示。

作者图片
您还需要使用命令 pip install re-data 安装 Python 包,这样您就可以利用 Slack 通知特性和 re_data UI。
设置您的监控配置
re_data 文档概述了如何利用 dbt 原生配置来使用它们的框架。对于本教程,我只监视一个表中的row_count。我编写的代码将放在 dbt SQL 文件的开头,该文件对应于您希望监视的表。
为了设置这个,我设置re_data_monitored为真。然后,我选择了我想要使用的时间过滤器。确保时间过滤器是指定表中行创建时间的列。我的情况是event_created_at。
例如,如果你在监控网站的点击率,你的time_filter栏将会是你的网站发生点击的时间。

作者图片
现在,让我们导航到dbt_project.yml文件。我们不是在单个模型的配置中编写re_data_metrics块,而是将它添加到这里。这将使得row_coun t 为我们添加 re_data 配置的每个模型进行计算。如果您想要为多个模型计算它们,那么您还可以向这个 dbt 项目文件中添加其他指标。

作者图片
您还可以在 dbt_project.yml 文件中指定特定的 z 得分,您希望 re_data 使用该 z 得分来确定指标是否远离其基线。默认的 z 值为 3,但您可以根据表的性质随时更改该值。就我个人而言,我把我的改为 2,因为我宁愿在开始时被提醒太多,而不是不够。如果你发现它过于频繁地提醒你,你可以随时调整。

作者图片
现在,我们可以将相同的配置对象添加到任何模型的相应 SQL 文件的顶部,并将计算 row_count。对于所有这些模型,当 z 分数超过 2 时,将创建警报。
数据仓库配置
现在是运行框架的时候了!您可以使用以下命令运行它:
dbt run — m re_data
请注意,这个命令与您用来运行已经创建的任何其他 dbt 数据模型的命令相同。
运行该命令后,我希望您导航到您的数据仓库,并查看是否有任何更改。您会注意到 re_data 创建了两个模式, RE 和 RE_INTERNAL 。
请记住,这些模式将在 profiles.yml 文件中定义的数据库中创建。在我的概要文件中,我有两个目标——一个用于开发,一个用于生产,每个目标都有不同的已定义数据库。根据您的目标,这些模式将填充到数据库中。
例如,当我在本地开发 dbt 模型时,我的目标在我的profiles.yml中被设置为 dev。因此,在我的开发数据库中创建了两个 re_data 模式。然而,在生产中,我的目标设置为我的生产数据库。因此,当我在生产中运行相同的代码时,模式是在我的生产数据库中创建的。
你可以在 dbt 的文档中了解更多关于设置个人资料的信息。
默认数据仓库中的 RE 模式包含从包中的宏创建的各种表。我们需要注意的是 RE_DATA_BASE_METRICS 和 RE_DATA_Z_SCORE 。

作者图片
您还可以看到它创建了 4 个不同的视图。我们将需要查看 RE_DATA_ALERTING 视图。这包含根据我们提供的 YAML 配置创建的任何警报。
至于 RE_INTERNAL 模式,您不会看到它。这是宏内部使用的,为您提供我们刚刚讨论过的漂亮的表和视图。

作者图片
已创建 re _ 数据表
re_data_base_metrics
让我们看看“re_data_base_metrics”表。您可以看到,根据您运行模型的时间和我们在dbt_profiles.yml中定义的“metrics_base ”,有不同的行。因为我们只在那个文件中定义了row_count,所以我们只会在 metric 列中看到row_count。

作者图片
re_data_z_score
接下来,我们来看“re_data_z_score”表。当您尝试计算的指标至少有 2 行数据时,您将在该表中看到至少一行。如果只有一行,则无法计算 z 值。请记住,这也意味着您使用该工具的时间越长,它就越有效。如果你回想一下大数定律,随着样本量的增加,平均值越来越接近真实的总体平均值。

作者图片
如果您还记得,我们在 YAML 文件中将 z 值设置为 2。因为此表中的 z 值小于 2,所以不会触发任何警报。如果此 z 值大于 2,将触发警报,并在“re_data_alerting”表中创建一行。
在生产中运行 re_data
现在,你可能想知道,我如何在生产中运行它?你可能想让这个提醒自动工作,而不需要太多思考。您不希望每天都在本地运行这个命令。
就我个人而言,我使用perfect来部署我的 dbt 数据模型,所以我也选择使用它来部署 re_data。因为它是如何构建的,所以您会希望像运行 dbt 模型一样运行它。对你来说,这可能是与气流或 Dagster,但对我来说,这是与提督。
我在 Python 文件中使用 dbt_tasks 来运行我所有的 dbt 模型。我有一个装满这些任务的文件,然后将它复制到 Docker 容器中。对于 re_data,我简单地创建了一个新的 dbt_task 来运行模型。
dbt_task(command=’dbt run -m re_data’, task_args={“name”: “re_data monitoring”})
我还确保为 web events 表设置了上游依赖项,因为这是我使用 re_data 监视的表。如果您使用它来监视所有的表,请确保为所有的数据同步设置了一个依赖项。
设置松弛命令
re_data 刚刚发布了一项功能,允许您发送带有警告更新的 Slack 消息。这是自治数据环境的理想选择,在这种环境中,您只想关注出错的事情。
我尝试设置我的数据环境,这样我就不必每天监控它。当事情出错时,那是我希望得到通知的。re_data 就是这么做的。
运行该命令之前,请确保您拥有最新版本的包。这是一个相当新的版本,所以如果您没有使用最新的版本,该命令将会失败。
请务必在更改版本后运行dbt deps,否则该命令将再次失败。
re_data Slack 命令如下所示:
re_data notify slack \-- end-date $(date +”%Y-%m-%d”) \--webhook-url {webhook_url}
如果希望每天都生成时差通知,可以不指定开始日期。相反,只需以正确的格式将您的结束日期指定为当前日期。$(date +”%Y-%m-%d”)在运行 Bash 命令时就是这么做的。
然后,您需要包含一个 webhook-url,它可以在 Slack 上生成。这些 webhooks 是专门为您希望警报发送到的通道而创建的。
在我的完美环境中,我在 dbt shell 任务中包含了这个命令。它利用 dbt 凭证,所以您不希望只使用普通的 shell 任务。

作者图片
同样,如果您使用的是 Prefect,请确保设置了上游依赖项。对于 slack 命令,我设置了 re_data dbt 运行的依赖项。

作者图片
如果您在本地运行这个命令,而不是部署到生产环境,那么 re_data 有一个 UI 可以充分利用。您将运行一个类似于 Slack 命令的命令,但是这将生成 UI。
re_data overview generate — end-date $(date +”%Y-%m-%d”) — interval days:1
然后,只要您已经使用这个包生成了一些模型,您就可以“服务”UI 了。
re_data overview serve
这将在您的浏览器中打开,并允许您查看您的所有警报以及您的模型的沿袭。
结论
现在,我们有了一个配备有用警报系统的数据环境。我们不必生活在恐惧中,担心我们的数据在源头可能会出问题。这种数据量警报是确保一切按预期运行的好方法。
不要坐等数据管道出错。在遇到问题之前,现在实施类似 dbt 包的检查和平衡。相信我,你不会想像我一样丢失两周的数据。数据实际上等于金钱和重要的见解!不惜一切代价保护它。
re_data 最好的一点就是它是免费的!您不必在已经很贵的数据堆栈上支付额外的费用。您可以充分利用 dbt 的所有惊人特性。
欲了解更多关于工具和软件包的建议,以改善您的现代数据堆栈,订阅我的时事通讯。
防止结果饥饿
原文:https://towardsdatascience.com/preventing-outcome-starvation-73af6324e2d4
为什么在操作机器学习模型时需要紧急处理退化的反馈回路

图片由 Vertigo3D 提供,通过 iStock 授权
预测模型很少是静态的——可操作模型通常具有更新节奏。例如,在 Mobilewalla,我们的模型每 30-180 天更新一次。在每个更新周期结束时,基于对自上次更新以来其输出的保真度的评估来修改模型。这是标准模型维护实践的重要组成部分,被称为反馈回路。
当先前的输出不公平地影响未来的结果时,就会出现退化反馈回路 (DFL)。我最喜欢的关于 DFL 的解释是在 Chip Hyuen 的课堂笔记中,她在斯坦福大学教授 CS329S (ML 系统设计)课程(在这里找到 T8)。为了说明 DFLs,让我直接引用其中的一段话:假设你构建了一个系统,向用户推荐他们可能喜欢的歌曲。系统排名高的歌曲首先显示给用户。因为它们首先被显示,所以用户更多地点击它们,这使得系统更加确信这些推荐是好的。一开始,两首歌曲 A 和 B 的排名可能只有微小的不同,但是因为 A 最初的排名稍微高一些,所以 A 得到了更多的点击,这使得系统对 A 的排名更高。过了一段时间,A 的排名变得比 b 高得多。退化的反馈循环是流行电影、书籍或歌曲不断变得更受欢迎的原因之一,这使得新项目很难进入流行列表。
DFLs 在推荐系统中得到了广泛的研究。最近,随着机器学习中公平和偏见的概念变得流行(参见此处的教程),DFL 尤其被认为是 ML 偏见的关键驱动因素[1]。然而,除了在某些特定的环境中,例如驱动公共/社会服务的模型(例如,跨社区的犯罪预测和财富预测),公平以及 DFL 还没有成为主流。大多数构建推动商业成果的模型的数据科学从业者通常不会在其建模工作流中优先处理 DFL。如果一个模型最终受到了公平性的挑战,只要它提供的输出具有可接受的预测准确性,大多数建模者和 MLOps 专业人员就不会过分担心。这种缺乏关注也反映在通常用于模型构建的软件实用程序中,其中不提供防止 DFL 的特征。总之,退化的反馈回路现象,以及随之而来的模型中的不公平性,对于广大数据科学家来说,还没有被优先考虑为具有重要实际意义的问题。
在这样的背景下,我来到了本文的主题:为什么这个视角需要改变。事实证明,虽然我们可以忍受大多数模型中某种程度的不公平,但由于 DFL 对预测质量产生负面影响,还会出现另一个更紧迫的实际问题——结果饥饿 。我们首先解释这种现象,然后提供两个真实世界的例子来说明它的范围。
什么是结果饥饿?
任何预测模型都必须通过三项测试才能实际使用:
1.它的预测精度是否超出了可接受的阈值?
2.模型有弹性吗?
3。它能产生有意义的结果吗?
前两个广为人知,但第三个对大多数读者来说是新的——我们从未在文学作品中看到过讨论。为了理解这个问题,考虑机器学习中的分类问题,其中模型的目标是将输入映射到多个预先确定的桶或类中。分类构成了机器学习中最广泛的一类实际问题——大量的实际预测问题都归结为分类场景,如下表所示。

分类方案
在上述每种情况下,模型结果(所需预测栏)关键地告知相关业务流程(实际/商业用例栏)。模型不仅需要有弹性,并产生准确的预测结果,这些结果还需要以商业上有意义的数量出现。以上面的个性化用例为例,考虑一个电子商务网站。一旦一个匿名用户登陆该酒店,ML 模型会预测用户的人口统计数据,以便随后的参与可以个性化。假设该模型遵循预定的更新节奏,并且具有弹性。进一步假设其结果具有可接受的预测保真度,例如,它以 80%以上的准确度预测性别。然而,对于模型中的每个输入,它只能成功分类 10% 。换句话说,对于每个出现的匿名用户,该模型只能在 10%的情况下有足够的把握确定他们的年龄/性别。很明显,尽管具有弹性,并且高度准确,但是该模型是没有用的,因为它不能产生足够有意义的结果来驱动它所支持的用例。在这种情况下,模型是结果饥饿。在 Mobilewalla,我和我的同事构建了支持各种行业和使用案例的模型——电信行业的用户流失、拼车和食品配送行业的高价值用户获取、“先买后付”(BNPL)公司的信誉预测、消费品行业的股票/库存预测等等——我们发现结果饥饿是一种普遍存在的现象。我们生产的 120 多种型号中,超过 50%的型号都随着时间的推移而出现问题。有趣的是,像弹性一样,结果饥饿是一种“渐进疾病”——模型从来不会一开始就饥饿,但随着时间的推移会变得饥饿。说到本文的关键,结果饥饿的主要原因是退化反馈循环(DFL)的存在。
退化的反馈回路如何导致结果饥饿
我们现在提供两个真实世界的例子来说明 DFL 如何导致结果饥饿。在这些项目中,我们直接参与相关模型的构建和操作。
示例 1:拼车中的高价值用户获取
拼车公司的主要运营成本是获取用户。然而,获得的用户转变为“有价值的客户”的可能性很低。根据我们与许多全球共乘服务提供商的合作经验,只有 12–14%的收购用户会转化为高价值客户(HVC)。因此,这些公司投入了大量的数据科学资源来开发拼车消费倾向模型。这些模型将一个潜在客户(及其特征)作为输入,返回一个“消费倾向得分”作为输出。这个分数代表潜在客户成为 HVC 的可能性。使用这些分数,提供商试图通过试图优先获得那些更有可能变成 HVC 的用户来在大的潜在客户群中进行区分。这些模式对这些提供商至关重要——即使被收购资产池中的 HVC 比例出现 1%或 2%的有利波动,也能带来每年数亿美元的额外收入。
现在让我们考虑这种模型的一个实例。更具体地说,让我们考虑锚定该模型的预测性消费者特征。虽然有许多这样的特征,但事实证明具有高预测能力的一个是在过去 90 天内在非国内机场观察到的 10 次以上的前景 —让我们简称为 OBSGT10。这是一个真实世界的预测器,我们发现它在这些模型中有很大的实际用途。为了说明 DFL 对该模型的影响,进一步假设 OBSGT10 是我们模型中最具预测性的特征。这通过模型函数中的“特性权重”得到反映——obsg T10 开始时的权重略高于许多其他特性。随着模型的运行,它会稍微优先考虑(这是它的本意)满足 OBSGT10 的潜在客户,但也会关注其他特征。一切顺利——该模型产生了预期的良好结果——并提高了用户获取的投资回报率。
当模型更新时间到来时,新的训练集现在充满了来自上一个周期的反馈,与原始训练集相比,满足 OBSGT10 的用户略多,并且这一次 OBSGT10 功能的权重比前一个版本稍高。这种循环继续下去,很快,OBSGT10 的权重明显高于其同类,模型变得明显偏向 OBS gt 10——它在前景集中渴望这一特性。事实上,在现实世界中,当这种情况发生时,我们已经看到特征集发生了变化——开始时权重稍低但完全可以预测的属性开始完全脱离特征集。
现在我们来看问题的症结所在——我们的模型很快就用完了在目标池中满足 OBSGT10 的潜在客户,并且在其改变的状态下,无法再为有意义规模的收购集提供动力。请注意,在最初的版本中,当第一次运行时,它被赋予了各种各样的功能,仍然可以驱动完全可接受的规模。然而,由于 OBSGT10 导致的 DFL,它最终成为结果饥饿。
示例 2:评估“先买后付”( BNPL)公司的申请人信用风险
我们现在关注一个完全不同的行业中完全不同的用例。
BNPL 公司是金融科技公司的一个重要类别,它们主要是在新兴市场发展起来的,以应对两种社会现象交织在一起造成的局面:
“大众富裕阶层”的崛起
大多数大众富裕阶层缺乏传统的信用记录(信用卡持有情况、信用记录)
以印度为例,在超过 2.5 亿的中产阶级中,只有大约 5700 万张信用卡(2021 年https://www . statista . com/statistics/1203267/India-number-of-credit-cards/)。
BNPL 供应商向传统信贷保护伞之外的消费者提供信贷。因为这些消费者中的大多数没有信用评分,或创建信用评分所需的原始数据,BNPLs 依赖于使用机器学习创建自己的信用风险模型。这些模型将一个潜在客户(和她的特征)作为输入,返回一个“信用评分”作为输出。这些模型对这些提供商至关重要,代表了他们核保策略的支柱。
和前面的例子一样,让我们考虑锚定信用风险模型的预测特征。因为传统的信用数据对于典型的贷款申请人来说是不可用的,所以特征是从“替代数据”设计的。一个具有特别高预测能力的是居住在手机平均价值超过$Threshold ( ) 的家庭——让我们简称为 HHPHVAL。按照示例 1 中描述的完全相同的模型操作和反馈序列,使用 HHPHVAL 创建了一个退化的反馈环路,导致该特性在几个更新周期后被不公平地加重。因此,满足这一特征的消费者得到优先考虑,并且不值得推荐的应用的量很快减缓到涓涓细流,因为只有那么多家庭具有足够高的 HHPHVAL。该模型现在缺乏结果。
摘要
DFL 被认为是 ML 模型中公平和偏差问题的主要原因,但没有受到从业者的重视,因为大多数构建商业模型的数据科学家都愿意忍受某种程度的偏差。然而,DFLs 的一个结果和由此产生的不公平是结果饥饿,这是 ML 模型的实用性的实质性实际减损,否则 ML 模型是高质量的。令人惊讶的是,这种现象在文献中并没有得到太多的关注,至少在我们的经验中,它的发生是普遍的。在本文中,我介绍了结果饥饿的概念,以及它与 DFL 的关系。在未来的一篇文章中,我将讨论 Mobilewalla 如何使用 Anovos ,一个开源特性工程库,通过减少 DFL 的影响来解决结果饥饿问题。
[1] Dana Pessach & Erez Shmueli,关于机器学习公平性的评论, 计算调查,第 55I 卷 (3),2023 年 4 月,第 1–44 页
( )不同地区的$Threshold 值不同。在我们的模型中,它在印度尼西亚的实例化价格是 400 美元,在印度是 225 美元
使用 nbpreview 在终端中预览您的 Jupyter 笔记本

来源: pixabay
有了 nbpreview,你不用离开终端就可以阅读和检查笔记本!
有很多程序员爱从终端做尽可能多的事情。虽然我不一定是其中之一,但直接从命令行执行一些操作通常非常方便。说到终端——谁没有花大量的时间调整终端的外观(主题、字体等)。)…
今天,我偶然发现了一个 Python 库,它增加了一项我们现在可以从终端轻松完成的功能——预览 Jupyter 笔记本。这包括细胞的输出——包括数据帧和图表!
让我来告诉你如何用nbpreview来完成这一切。
nbpreview
nbpreview 是一个 CLI 和库,允许我们在终端中渲染 Jupyter 笔记本。它的工作方式有点像。ipynb 文件。
该库提供以下功能:
- 代码单元格的语法突出显示
- 它不仅检测 Python,还检测像 Julia 这样的语言
- 渲染降价、数据帧、LaTeX
- 绘制图像
- 解析 HTML
- 呈现 stderr 输出和回溯
装置
我们可以通过运行以下命令来安装该库:
pip install nbpreview
动手示例
在这篇文章中,我们将预览一个来自我的 2021 年回顾文章的笔记本。我选择它是因为它包含一些我们想要预览的元素,例如,数据帧输出、绘图等。
使用nbpreview非常简单和直观。我们可以通过在终端中运行以下命令来预览笔记本:
nbpreview file_name.ipynb
的路径。ipynb 文件是nbpreview命令唯一需要的参数。自然地,有相当多的可选参数可供我们选择。但是首先,让我们看看默认输出是什么样的。
下面我们可以看到笔记本是如何在终端中渲染的。我们已经看到数据帧被很好地可视化了,就像在普通的 Jupyter 笔记本中一样。

作者图片
此外,像数据帧和图像这样的输出会得到一个可点击的链接,您可以通过该链接查看原始输出。
就我个人而言,我对这些图像非常好奇。下面,您可以看到条形图是如何在终端中呈现的。

作者图片
默认情况下,图像绘制为块。我们也可以使用另外两个设置之一:character或braille。
nbpreview --image-drawing character file_name.ipynb

作者图片
nbpreview --image-drawing braille file_name.ipynb

作者图片
对于代码突出显示,我们可以从几十个可用的主题中进行选择(包括一些流行的主题,如 emacs、vim、dracula)。我们可以通过使用theme参数来改变主题:
nbpreview --theme vim file_name.ipynb
在下图中,你可以看到相同的笔记本渲染了 vim 主题(上图)和默认的深色主题(下图)。

作者图片
最后,让我们一起来看看终端中由 VS 代码呈现的笔记本:

作者图片
虽然不仔细看可能不清楚,但我们故意抛出了一个错误(除以零),以表明 stderr 也被nbpreview捕获。
要了解更多关于nbpreview提供的功能,我们可以通过运行以下命令阅读所有选项的文档:
nbpreview --help
或者在这里参考可用的文档。
外卖食品
nbpreview是一个 Python 库/CLI,允许在终端中预览 Jupyter 笔记本,- 它处理渲染数据帧输出、图像、降价、LaTeX 等。
一如既往,我们欢迎任何建设性的反馈。你可以在推特上或者评论里联系我。
喜欢这篇文章吗?成为一个媒介成员,通过无限制的阅读继续学习。如果你使用这个链接成为会员,你将支持我,不需要你额外付费。提前感谢,再见!
您可能还会对以下内容感兴趣:
参考
价格弹性:探索数据分析后在 Azure ML Designer 中建模

(作者)
价格弹性:探索数据分析后在 Azure ML Designer 中建模
找出 EDA 对机器学习模型性能的好处
在我之前关于价格弹性的帖子中,我展示了在创建机器学习模型之前使用 Exploration Data Analysis 分析数据集的重要性,以便更好地了解您的数据优势和劣势:
当时我用 Azure Machine Learning Studio Classic 作为无代码/低代码平台,快速训练线性模型。现在,当你试图登录 Azure ML Studio Classic 时,会弹出一条消息,宣布它将于 2024 年 8 月 31 日退役:

图 1 — Azure ML Studio 经典退休(作者)
这就是为什么在这篇文章中,我将使用全新的 Azure 机器学习设计器(可以认为是旧 Azure ML Studio Classic 的 2.0 版本)来应用上面博客文章中显示的所有清理和转换操作。
我还将向您展示在 Designer 中实现残差图(用于检查回归模型的健康状况)的方法。我在我的这篇博文中详细探讨了它们:
非常感谢我的同事 Greta Villa,他帮助我实现了这篇文章中的大部分 Python 代码。
让我们开始实施我们的价格弹性实验。
我想当然地认为读者了解 Azure ML Designer 的基础知识。粗略地说,那些熟悉旧的 Azure ML Studio 经典平台的人会发现自己非常容易使用该设计器。为了完整起见,这是一个简单的教程,向初级读者介绍设计师。
价格弹性实验的源数据集取自微软关于 Azure ML Studio Classic 的教程,每个人都可以免费获得。它们与前一篇博文中分享的数据集相同。为了方便起见,这是在执行完所有源数据集之间的连接操作后,到数据集的链接。
在设计器中设置实验
请继续在 Azure ML 中将源数据集注册为 TabularDataset 。将其作为源数据模块导入到设计器画布中。然后添加一个“执行 R 脚本”模块,以便应用 R 代码从数据集中删除重复的数据:

图 2 —添加源数据和执行 R 脚本模块(作者)
然后在上面提到的模块中输入下面的 R 脚本,您也可以在之前的博客文章的“异常值处理”一节中找到:
此时,我们可以尝试绘制每个产品组合的价格-数量散点图。如果你还记得以前的帖子,有 4 个产品组合由数字 ID (SELL_ID)标识:
- 单个汉堡(1070)
- 汉堡+可乐(2051)
- 汉堡+柠檬水(2052)
- 汉堡+可乐+咖啡(2053)
价格-数量点是通过取与数据集中每个价格相关的所有数量的平均值来计算的,不考虑所有其他变量。对于数量的每个平均值,也使用学生的 t 分布计算置信区间(详见参考文献)。因此,首先在您的实验中添加一个“执行 Python 脚本”:

图 3-添加执行 Python 脚本以绘制需求曲线(作者)
然后将以下 Python 脚本添加到模块中:
正如您可能已经注意到的,在设计器中,不可能像 Azure ML Studio Classic 那样,直接从“执行 Python 脚本”或“执行 R 脚本”模块的输出引脚显示代码生成的图。
出于这个原因,为了查看生成的图像,您必须使用 Azure ML Python SDK 来引用与模块执行相关联的运行,然后使用一些特定于 SDK 的函数来记录图像。
这就是为什么我使用 Python 而不是 R 来生成图的原因。
运行实验后,您可以点击“执行 Python 脚本”模块,然后点击“图片”选项卡,以查看脚本创建的图片:

图 4 —所有产品组合的价格-数量散点图(作者)
从与每个产品组合相关的价格-数量散点图中,您可以看到这些点是根据线性函数排列的,并且存在一些异常值,特别是对于 2051 和 2052 组合。因此,在对最后一个模块的左输出引脚返回的数据集进行简单分析后,我们将使用另一个“执行 R 脚本”模块将它们从数据集中排除:

图 5——添加了一个“执行 R 脚本”模块来删除异常值,并添加了另一个 Python 模块来显示图表(作者提供)
这里的 R 脚本删除离群值:
在图 5 中,您还可以看到另一个“执行 Python 脚本”模块,该模块包含与前一个模块相同的脚本来生成散点图,这次使用清理后的数据集作为源。事实上,新的散点图不再显示异常值:

图 6 —所有产品组合的无异常值的价格-数量散点图(作者)
在这一点上,我应用了一系列的特性工程转换,正如我在以前的博客文章中所解释的那样,并且使用如下的一系列模块来修复列名:

图 7 —应用特征工程转换(作者)
用于特征工程转换的 R 脚本是这样的:
此后,变量 HOLIDAY、SELL_ID 和 WDAY 通过“编辑元数据”模块被转换成分类变量,以便通过“转换成指标值”模块用一组一次性编码变量替换它们。然后,使用以下 R 脚本重命名一些因其不常见的名称而被重命名的功能:
接下来,尝试使用 Box-Cox 变换消除价格和数量变量的偏态,然后使用 z 得分归一化对所有数值变量进行归一化:

图 8 —归一化数字特征(作者)
Box-Cox 转换的 R 脚本将分别转换 T_PRICE 和 T_QUANTITY 中的变量 PRICE 和 QUANTITY。这里是脚本:
请注意,变换后的要素将被添加到第一个输入数据集中,并返回到模块的第一个输出引脚。同时,第二个输出引脚返回用于两个变换特征的λ值,以便稍后可以执行逆变换。
类似地,R 脚本用于规范化所有数字特征(T_PRICE、T_QUANTITY、YEAR、AVERAGE_TEMPERATURE、DAYS_FROM_BEGINNING):
你可能想知道为什么我没有使用设计者的本机“规范化数据”模块。原因总是在于我们需要进一步应用逆变换,因此我们需要在第二个输出引脚上返回第二个数据帧,其中包含已变换变量的平均值和标准偏差。
此时,我们可以将数据集分成一部分用于训练,另一部分用于测试。按照前一篇博文中的划分,我们必须首先通过“编辑元数据”模块转换设计者提供的 DateTime 中的 CALENDAR_DATE 特征数据类型,然后使用它将日期小于或等于 2015-06-30 的所有观测值分配给训练数据集:

图 9 —使用日历日期将数据集分为测试和训练(作者)
分割数据模块中使用的关系表达式如下:
【日历 _ 日期】<= 2015–06–30T00:00:00Z
That said, the training and testing workflows are the usual ones (线性回归、训练、得分、评估模型和排列特征重要性模块),图 10 中红色突出显示的模块除外:

图 10-实验的训练和测试工作流程(作者)
测试工作流顶部突出显示的第一个 Python 脚本从测试数据集中删除了与二战庆祝日相关的观察结果(参见之前的博客文章),因为它们第一次被视为假日,并且模型无法从数据历史中了解该场合的价格和数量行为。这里是脚本:
另一个突出显示的 Python 模块包含 R 脚本的 Python 移植,您可以在“如何更好地评估回归的拟合优度”博文的结尾找到,该博文用于为训练模型生成残差图。这里是脚本:
请记住,即使您不使用 Azure 机器学习,您也可以将之前的 Python 脚本用于您的自定义机器学习项目。只要记得注释掉 run.log_residuals 函数,用 fig.show() 替换 run.log_image 函数即可。
运行定制评估模块后,只需点击它,然后点击指标选项卡,即可查看在测试数据集上计算的指标:

图 11——测试数据集上模型的定制度量(作者)
点击图像选项卡,您可以访问模块执行期间记录的所有剩余图:

图 12 —根据测试数据集计算的模型残差图(作者)
也就是说,在管道的最后,记得对目标变量进行逆变换,这样输出的不再是应用了规范化的 Box-Cox 变换值,而是真实的量。为了进行逆变换,您需要 Box-Cox 变换所应用的 lambda 值以及用于 z 得分变换的平均值和标准偏差值,所有这些都应用于 QUANTITY 变量。为此,您必须将之前转换模块的第二个输出引脚链接到相应的执行 R 脚本模块,以进行逆转换:

图 13 —使用先前输出作为第二输入的逆变换模块(作者)
请记住,如果您通过首先应用 Box-Cox 然后应用 z-score 来转换数量变量,则应以相反的顺序应用逆转换。
反向 z 得分变换的 R 脚本如下:
Box-Cox 逆变换的 R 脚本如下:
最后,点击自动布局重排闪电图标后,完整的流水线看起来如下:

图 14 —新实验的完整流程(作者)
右键单击最后一个转换模块的左输出引脚,并单击预览数据,您可以看到转换数量和原始数量情况下的真实值和得分值:

图 15 —逆变换后的得分值(作者)
一旦您运行了整个管道,我敢打赌您一定很想看看当前的模型是否比之前博客中的模型表现得更好。我们去看看。
比较模型性能
显然,为了决定两个模型( original vs new)中的哪一个表现得更好,我们需要与用于目标变量的度量尺度无关的标准化度量或系数。例如,考虑目标真值和预测值之间的归一化均方根误差(NRMSE)决定系数 (R)和现在引入的斯皮尔曼相关系数(ρ,rho),这里比较:

图 16——原始模型和新模型的性能测量(作者)
对于 NRMSE 来说,最小的度量就是最好的,而对于其他两个国家来说,最大的度量就是最好的。因此,新模型明显优于原始模型。通过分析残差图,这一论断也是显而易见的。
在下图中,我们比较了左边的原始模型和右边的新模型。例如,在图 17 的中,新模型的残差分布呈现更对称的钟形,以零为中心:

图 17 —两个模型的残差直方图之间的比较(作者)
新模型残差正态 Q-Q 图与图 18 中象限的主对角线明显更接近,这也支持了之前的断言:

图 18 —两种模型的正常 Q-Q 图之间的比较(作者)
同样的事情也适用于预测与实际图,在新模型的情况下,点更靠近对角线:

图 19——两种模型的预测图与实际图之间的比较(作者)
即使在预测与残差图的情况下,尽管即使对于新模型,异方差仍然很明显,但点更多地集中在 0:

图 20——两种模型的预测图与残差图之间的比较(作者)
我们可以有把握地说,新型号比原来的性能更好。
此外,如果您查看排列特征重要性模块的结果,您可以看到,从训练数据集中移除具有负得分的高亮特征,您可能会有一个更好的模型,不容易过度拟合:

图 21-可能导致由置换特征重要性模块产生的过拟合的特征
让我们看看从这个实验中能得出什么结论。
结论
仔细的探索性数据分析(EDA)是获得性能更好的模型的正确方法。我们通过将 EDA 应用于价格弹性数据集并训练一个新模型来证明这一点,结果证明这个新模型比原来的模型更好。
在下一篇文章中,我们将尝试使用 Azure AutoML 从本文中看到的相同输入数据集和转换中训练一个额外的模型。
参考
- 置信区间:如何找到它:最简单的方法!—统计如何进行
- Azure Machine Learning SDK for Python—Azure Machine Learning Python |微软文档
- 日志&查看指标和日志文件— Azure 机器学习|微软文档
基于 PyCaret 和深度学习的价格推荐系统建模
实践教程
电子商务案例研究

马库斯·温克勒在 Unsplash 上的照片
**Table of Contents**· [The Business Problem](#9aa7)
· [Baseline Model](#8147)
· [Exploratory Data Analysis](#a593)
· [Preprocessing](#d1e3)
· [Machine Learning](#ffc1)
· [Deep Learning](#dbe3)
· [Inference](#d5c3)
· [Conclusion](#fd1b)
商业问题
假设你刚开始在一家真正看重用户和合作伙伴体验的电商公司实习。有一天你发现你公司的 app 里卖的两件类似的衣服价格相差几百块钱。它们有相同的颜色,相同的用途,只是在商标和描述等细节上有所不同。
有了这个观察,你的首要任务就是为卖家建立一个定价推荐系统,让他们知道自己的产品到底值多少钱,市场愿意出多少钱。这将缓解卖家和买家之间的交易,让卖家很容易适应成为利基参与者。
我不知道这种情况是否合理,但让我们假装并继续:)
您将获得可用的过去交易数据,如下所示。

它由七个特征组成,包括产品的名称、品牌和类别,谁支付了运费(买方或卖方),产品状况和描述,以及相应的美元价格。在建立模型时,前六个特征将是你的预测因素,价格将是你的目标。
您将逐步构建您的模型,从最简单的开始,一步步增加复杂性:
- 基线模型:从人类会做什么来推荐价格
- 机器学习:经典模型,由 PyCaret 简化
- 深度学习:神经网络模型,利用张量流
基线模型
在实际应用中,选择模型时需要考虑许多因素。除了性能,我们还应该考虑模型对推理的响应速度、重新训练的时间、计算成本、内存大小、可解释性等。
在性能方面,我们要做的第一件事是构建一个简单的基线来比较模型。由于这是一个回归问题,我们将考虑简单的性能指标,如 MAE、RMSE 和 RMSLE。
首先,我们将 12/1/1 比例的数据分成训练、验证和测试三部分。我们暂时不会使用验证分割来学习超参数,因为我们没有这个基线模型的任何超参数。
Train shape: (1200000, 7)
Validation shape: (100000, 7)
Test shape: (100000, 7)
作为基线,我们应该考虑最简单但合理的模型。如果你的朋友问你,“这要多少钱?”,你可能会回答“什么是' it '?”。你的朋友问你一个更合适的问题是“一部电话多少钱?”,因此你回答“嗯,大约 800 美元”。
现在,这 800 美元从哪里来?一个合乎逻辑的答案将是从许多电话价格的中心的测量,也就是说,从他们的平均或中间值(当然,品牌应该被考虑)。因此,为了估算电话价格,我们将每部电话分组,取其价格的平均值或中值。从我们的数据来看,我们实际上有这种群体:按品牌和按类别分组。
name 0
brand 510245
category 4884
shipping 0
condition 0
description 657
price 0
dtype: int64
因为从上面来看,brand上有许多缺失值,我们将使用category来代替。由于一些特定的手机品牌比其他品牌贵得多,因此最好使用中位数聚合。
可能会有一些产品的category是在测试部分新推出的,而在列车测试部分没有出现过。在这种情况下,模型无法预测那些产品的价格。我们将通过简单地用所有价格的中位数来估算它们来处理这个问题。
这是基线模型的性能分数。

探索性数据分析
EDA 应该回答重要的问题,并使提取洞察力变得更容易。这里,我们特别感兴趣的是price。观察列车分裂中price的直方图,我们直接看到其分布遵循 幂定律 ,其中一个量的相对变化作为另一个量的幂而变化。

图片作者作者
如果目标变量遵循正态分布,线性模型族或神经网络中的一些模型通常表现更好。因此,我们可能需要对price进行变换,其中一个叫做 box-cox 变换。请注意,基于树的模型不受此影响。
请参见下面的转型实践。

图片作者作者
最终的分布不一定是正态的,但它肯定比转换前的分布更正态。
预处理
在进行任何建模之前,我们需要对数据进行预处理。对于预处理步骤,我们执行如下简单任务:
- 用空字符串填充任何缺少的值,
- 将间隔开的
name和brand连接起来,替换name, - 通过连接由空间分隔的
description、name和category来创建一个新特征text, - 删除除
name、text、shipping、condition和price(针对目标)之外的所有功能。 - 从
name和text生成 TF-IDF 特征,以及 - 用
text和shipping组合 TF-IDF 的两个特性。
下面是代码,它保存在工作目录中的一个单独的preprocess.py文件中。这很重要,因为稍后我们会隐式使用pickle,它需要在另一个模块/文件中定义自定义类,然后导入。否则, PicklingError 将被引发。
如前所述,从preprocess.py导入必要的功能。注意,我们还将gc作为垃圾收集器导入,以便在对象不再使用时释放内存(因为我们的数据很大)。
现在,预处理数据。

机器学习
作为一名实习生,假设你对机器学习算法的艺术和手艺了解不深。你需要一个低代码的机器学习库来帮助你完成大部分工作。
看哪, PyCaret 。
我们在这一部分所做的事情受到了 PyCaret 教程的启发。对 Moez Ali 和团队,我们感激不尽👏。
创建一个 TF-IDF 矢量器。现在,我们将使用max_features = 10000,但以后会增加。我们用几个主题演讲建立了一个实验setup:
- 使用的数据仅占第一次原型设计所有观察值的 0.1%,随机选择,
- 对预测器不做进一步的预处理,
- 根据 EDA 的建议,通过
box-cox变换对price进行变换,并且 - 训练策略是简单地通过将训练拆分成训练和验证拆分。

通过调用models函数,我们可以从 PyCaret 中看到可用的模型。

接下来挑几款,对比一下。这里我们选择其中的八个。你可能想试试其他的。

有些模型比基线模型表现得更好,有些则更差。我们可以很容易地看到ridge和huber模型通过他们的指标表现得非常好,并且他们的训练时间是最快的。
删除一些无用的变量,释放一些内存。
146
现在,用max_features = 100000创建另一个 TF-IDF 矢量器。新的实验setup和以前一样,除了现在我们使用新的矢量器和数据中的所有观察。

通过这个新的设置,我们训练了两个表现最好的模型,ridge和huber。我们可以看到性能有所提高。




我们也可以将两种模式融合。

混动款肯定比huber好。此外,混合模型在所有指标上都比ridge略差,但 RMSE 除外,它在该指标上明显胜出。我们将选择混合模式作为我们的冠军(目前)。
PyCaret 与 MLflow 集成在一起。您可以启动 MLflow UI,通过在终端中运行下面的命令,在 http://localhost:5000/ 上查看当前实验的元数据。
$ mlflow ui

MLflow UI,图片作者作者
让我们将结果和基线模型一起制成表格。

PyCaret 还有许多其他特性,比如模型调优和其他集成方法,我们在这里不做探讨。我们鼓励你自己去尝试。
深度学习
首先,让我们像以前一样矢量化训练和验证分割。
X_train: (1200000, 193754) of float32
X_valid: (100000, 193754) of float32
然后,使用之前拟合的 box-cox 变换对验证分割中的price进行变换。
如上所述,我们将使用 TensorFlow 来帮助我们。
在深度学习建模中,你可以做的最简单的事情就是建立几层密集的神经网络。这里,我们建立了一个具有三个隐藏层的网络,这三个隐藏层被训练了两个时期。
让我们从X_train和X_valid对中训练两个模型。
Model 1
586/586 [========] - 11s 19ms/step - loss: 0.3623 - val_loss: 0.3151
293/293 [=========] - 8s 28ms/step - loss: 0.2163 - val_loss: 0.3077
Model 2
586/586 [========] - 11s 19ms/step - loss: 0.3626 - val_loss: 0.3178
293/293 [=========] - 8s 28ms/step - loss: 0.2171 - val_loss: 0.3078
现在,通过将X_train和X_valid转换成布尔矩阵来尝试一些别的东西,也就是说,如果X_train或X_valid的元素不为零,那么它就变成 1,如果元素为零,它就保持为 0。用这些新矩阵训练两个模型。
Model 1
586/586 [========] - 11s 19ms/step - loss: 0.3682 - val_loss: 0.3247
293/293 [=========] - 8s 28ms/step - loss: 0.2103 - val_loss: 0.3151
Model 2
586/586 [========] - 12s 20ms/step - loss: 0.3680 - val_loss: 0.3250
293/293 [=========] - 8s 29ms/step - loss: 0.2101 - val_loss: 0.3162
将所有四个模型收集到一个 python 列表中,并对所有四个验证拆分进行预测。明确地说,这四种模式是:
- 具有 TF-IDF 数据的两个模型
- 具有布尔数据的两个模型
我们建立一个score函数来计算预测的质量。
对于一个模型,我们有以下分数。
{'MAE': 9.2815766537261,
'RMSE': 26.11544796731921,
'RMSLE': 0.4090026031074859}
通过混合两个模型,我们得到以下分数。有明显的改善。
{'MAE': 9.052237654724122,
'RMSE': 25.90078324778221,
'RMSLE': 0.4006770676979191}
最后,所有四个模型的混合给了我们最好的结果。
{'MAE': 8.918359263105392,
'RMSE': 25.910815619338454,
'RMSLE': 0.39423109095498304}
将结果与基线和机器学习结果一起附加。我们直接看到深度学习模型是赢家。而且训练时间也快,不到 80 秒。

当然,我们可以进一步调整该模型的超参数,甚至尝试其他神经网络架构。然而,这篇文章太长了,读不下去。但是我们鼓励你去尝试。
推理
现在,我们将对测试分割进行预测,以确认我们的深度学习模型的最终性能。和以前一样,测试分割需要在进入模型之前进行预处理。
正如所料,分数与验证分割的分数相似。
{'MAE': 8.916460799703598,
'RMSE': 27.392445134423518,
'RMSLE': 0.3933138510387536}
最后,我们保存模型以备将来使用。
INFO:tensorflow:Assets written to: model1\assets
INFO:tensorflow:Assets written to: model2\assets
INFO:tensorflow:Assets written to: model3\assets
INFO:tensorflow:Assets written to: model4\assets
结论
我们已经为一个电子商务应用程序的案例研究建立了一个价格推荐系统模型。从基线模型开始,通过最少的数据预处理,我们已经取得了相当好的结果,如下所示。

我们利用 PyCaret 和 TensorFlow 来构建模型。这些图书馆确实为我们做了艰苦的工作。
👏谢谢!如果你喜欢这个故事,想支持我这个作家,可以考虑 成为会员 。每月只需 5 美元,你就可以无限制地阅读媒体上的所有报道。如果你注册使用我的链接,我会赚一小笔佣金。
📧如果你是我推荐的 Medium 成员之一,请发送电子邮件至geo lid . members[at]Gmail . com获取这个故事的完整 python 代码。
更喜欢 R 中的数据科学动手教程?好奇机器学习是如何工作的?看看这个:

R 中的数据科学
View list7 stories



从零开始的机器学习
View list8 stories


素数分数和循环倒数
原文:https://towardsdatascience.com/prime-fractions-cyclic-reciprocals-b1074f6ebc3b
倒数和质数领域的进一步视觉冒险

图片作者。
我目前的副业是关于使用数据可视化技术来创造分数的视觉表现。它的灵感来自于 Matt Henderson 最近关于 Numberphile 的一些工作,以及我自己对数据分析和可视化的兴趣。基本思想是把分数作为一种新的数据来源。更准确地说,分数的十进制展开的数字是一个可以被操作、分析和可视化的数据源。在这篇文章中,我描述了我如何使用分数的十进制展开中包含的数据来创建相应的可视化表示,希望它们可以揭示一些有趣的模式,从而导致支撑数字和分数的一些重要属性。
简而言之,I ' 用它的十进制展开式的位数来控制一系列单位线段的角度,从而画出一个分数。在某种程度上,每个分数的十进制展开充当一组简单类型的计算机程序的可视化指令,以创建分数的形状。
结果令人惊讶地优雅和引人注目,至少在我看来是这样——比如下面显示的 1/39 的令人愉快的模式——但它们也有助于揭示分数的一些有趣的性质,正如我将在本文中讨论的那样。

图片作者。
顺便说一下,在我的研究中,我看到了 Jones & Pearce(数学杂志,2000 年)的一篇有趣的文章https://www.maa.org/sites/default/files/pdf/upload_library/22/Allendoerfer/2001/0025570x.di021214.02p0077u.pdf,其中他们也描述了一种可视化循环小数的方法,但这种方法基于除法过程中获得的余数,而不是这里使用的商/循环数字。
许多简单的分数导致复杂而对称的形状,这是因为它们的十进制展开式中有(无限)重复的数字序列。例如,在 1/39 的十进制扩展中,数字 025641 会永远重复。因此,1/39 被称为重复小数(或循环小数)。它的重复位数(025641)称为一个 reptend 或reptend,reptendreptend的长度称为它的周期。**
这个项目开始时是平等的数据可视化/生成艺术,再加上探索数据丰富的数字世界的另一种方式,它诞生于对数学之美的更大欣赏的渴望。当我探索熟悉的分数产生的模式时,我开始意识到这种数据驱动的方法和视觉视角也可以揭示一些关于数字本质的更深刻的见解。

图片作者。
例如,在我之前的文章中,我不仅能够说明重复分数和一些引人注目的形状之间的关系,还能够说明所产生的形状的复杂性如何取决于分数的十进制展开中重复序列的长度。看看上面 1/9967 的细节吧——小于 10,000 的最大质数——它有 9,966 个重复的数字!也请注意它的周期(9966)是如何比最初的恶魔化者少一的?我稍后将回到这一点…
L 让我们看看前 100 个倒数——形式为 1/n 的分数——如下所示;实际上,我只包括了具有重复小数展开式的分数,我只画出了它们的表示。换句话说,下面我已经排除了重复数字后面的不重复数字,例如 1/60 中的01(= 0.01666……),因为这有助于突出各种倒数之间存在的一些明显的相似之处。例如,注意 1/3、1/12、1/30、1/48、和 1/75 都具有相同的形状,还有 1/7、1/14、1/35、1/56 和 1/70 以及 1/9 和 1/90 。为什么?

图片作者。
首先,每个重复的倒数 1/n 与 1/10n 具有相同的观想。这很容易看出,因为如果 10 是分母的一个因子,那么它不会影响 reptend,除非延迟它在十进制展开中的出现。因此,1/3 = 0.333…,1/30 = 0.0333…,1/300 = 0.00333 …等等,都有相同的观想,因为它们都有相同的再现;虽然这是一个基数为 10 的现象,但它可以推广到任何基数 b 。
这也解释了为什么 1/14、1/28、1/35、1/56 看起来也像 1/7,而 1/21、1/42、1/77 却不像。前者可以重写为 1/70 的倍数(1/14 = 5/70;1/35 = 2/70 等。)但 1/21、1/42、 1/77 不能。
我说部分地解释了,因为还有别的事情在发生。例如,如下所示,其他 7 的倍数也会产生与 1/7 相同的形状,即使它们的分母中不包含因子 10,并且它们的表示不同。但是,请注意,下面的每个 reptend 都是 1/7 reptend (142857)的数字的旋转,这解释了产生的相似形状。

7 的倍数。图片作者。
例如,2/7 的 reptend 与 1/7 相同,只是向左移动了两个位置(142857 → 285714)。产生的视觉效果是相同的,因为当数字旋转时,数字的顺序保持不变,因此用于产生形状的角度顺序也保持不变;由此产生的观想在不同的点开始和结束,但是产生的整体形状是相同的。这适用于所有适当的倍数, m/7,如下所示的 m < 7。
1/7 在这方面特别吗?还是这指向了一些更深层次的真相?经过一番研究,我意识到 1/7 有些特殊,因为它的 reptend (142857)是一个循环数。当一个 n 位的循环数乘以任意整数直到 n-1 时,我们得到其位数的旋转;顺便说一下,当它乘以 n 时,我们得到一串 9,但那是另一个故事了。这里是 142,857 的适当倍数,是上面 1/7 的适当倍数的表示。
142857×1 = 142857
142857×2 = 285714
142857×3 = 428571
142857×4 = 571428
142857×5 = 714285
142857×6 = 857142
因此,1/7 的倍数之间惊人的视觉重合让我了解并更好地理解了一种叫做循环数的特殊数字。这并不是说视觉方法对于理解循环数或者它们与倒数的关系是必要的,但是它是有帮助的,也许它使循环数的概念更加容易理解和直观。
这里有一些其他的循环数,它们有相应的素倒数,在这些循环数中它们表现为 reptends?事实上,据推测存在无限数量的循环数,并且产生循环数的素数的分数似乎约为 3/8,但未被证明。
下一个例子是 1/17 的倒数。它有一个 16 位的 reptend (0588235294117647),它的倍数如下:

17 的倍数。图片作者。
并不是每一个素数的倒数都是这样的,但是在我们仔细观察之前,让我们先注意一下这些循环倒数的另一个有趣的关系:它们的长度表示为 p-1。例如, 1/7 的重复周期为 6(7–1),而 1/17 的重复周期为 16(17–1)。事实上,循环往复运动和循环往复运动都是如此,但其他往复运动则不然。
C 举个例子,看看 1/13 的倍数。它是另一个质数倒数,但它有一个 6 位数(6≠13-1)reptend(076923),而且它不是一个循环数。

13 的倍数。图片作者。
实际上, 1/13 的倍数包括两种不同的可视化形式,因为有两种不同的重复旋转,一种用于 076913 的旋转,一种用于 153846 的旋转。无论我们检查哪个合适的(互质)倍数( m/13 ),都不会产生其他的重复。
另一个例子是 1/37。它的固有倍数有 3 位数表示(3≠37–1)),它有 12 种不同的表示形式,首先出现在对应于 m=1、2、3、5、6、7、9、11、14、17、18、和 21、的倍数,如下图所示;换句话说,所有随后的真倍数都产生这些形式中的一种。**

1/37 的倍数。图片作者。
因此,1/13 有 6 位数的重复,有 2 种观想形式,而 1/37 有 3 位数的重复,有 12 种形式。事实上,这指出了这些互易的一个重要规则:
一般来说,素数 p 的适当倍数的倒数集合由 n 个子集组成,每个子集的重复长度为 k ,其中 NK = P1。(来源)
换句话说,如果 p 是素数并且 1/p 的重复周期为 k ,那么 1/p 的适当倍数将有 n 个不同的可视化形式,其中 n = (p-1)/n 。再一次,对互易性的直观探索帮助我们确定了互易性的一个重要特征。
然而,这种关系不适用于复合的倒数——T2 1/n,其中 n 不是质数。例如, 1/22 (= 0.0454545 )与 5 种不同的视觉形式相关联(对于其 5 种不同的两位数表示的旋转, 45,09,36,18,27 ),因此NK = 5 x2 = 10(<21);一些初步的研究表明在这方面还有很多工作要做。
在这篇文章中,我进一步探索了简单分数的视觉表现,揭示了它们的十进制展开的一些有趣的性质。作为一名数据科学家,我采用的方法很大程度上是将数据分析和可视化思想应用于简单分数的产物。一个分数的十进制展开中包含的数字作为分析和可视化的数据来源,我非常好奇这种方法在未来会走向何方。
现在,我想澄清的是,我并不认为这种可视化的方法会带来任何改变游戏规则或关于分数或质数倒数的新见解,相反,它有助于揭示数字的许多有趣的已知属性,使它们更可见、更容易理解,特别是对那些不太精通数学的人来说。
至少,我相信这种方法有潜力帮助孩子们把数学视为一门比其他情况更直观、更有创造性的学科,补充似乎主导早期数学教育的表格和规则的学习..
朱莉娅和 Python 的黄金时间
原文:https://towardsdatascience.com/prime-time-for-julia-and-python-96da5963d995
通过生成素数和计算素数的倒数来比较 Julia 和 Python 的性能

基于我在 Python 中遇到的一个非常聪明的素数生成器,我为 Python 和 Julia 设置了一些性能基准。不仅用于生成素数,还用于计算素数的倒数。而且结果挺有意思的…
质数
这是我开始写的 Python 代码。
def python_primes(n):
D = {}
q = 2
r = []
counter = 0 while counter < n:
if q not in D:
r.append(q)
D[q * q] = [q]
counter += 1
else:
for p in D[q]:
D.setdefault(p + q, []).append(p)
del D[q]
q += 1 return r
例如,运行python_primes(10)返回前 10 个素数的列表。
对于 Julia,我做了两个函数:一个没有类型声明,一个有类型声明。
这是带有类型声明的。
# Julia with Type Declarationfunction jlPrimesTypes(n::Int64)::Vector{Int64}
D = Dict{Int64, Vector{Int64}}()
P = Vector{Int64}()
q = 2
counter = 0 while counter < n
if !(q in keys(D))
D[q * q] = Int64[q] # Vector{Int64}([q])
push!(P, q)
counter += 1
else
for p in D[q]
get!(D, p + q, Int64[]) # Vector{Int64}()
push!(D[p + q], p)
end
delete!(D, q)
end
q += 1
end
return Pend
MacBook Air 2020 (M1 处理器,8Gb,256GB 固态硬盘)用于进行性能基准测试。所有代码都是使用 Visual Studio 代码在 Jupyter 笔记本上运行的。
Python 代码是使用 Julia 包PyCall调用的,基准测试使用了 Julia 包BenchmarkTools。在所有基准测试中,产生了 1,000,000 个素数。
Julia 1.7.2 (MacOS ARM)和 Python 3 . 9 . 12(MiniCondaARM 64/Apple Silicon)用于运行基准测试。
完整的 Jupyter 笔记本可以在 Github 上获得。

作者图片
我期望 Julia 比 Python 快得多,但是没有类型声明它会慢得多。有了类型声明,Julia 速度更快。然而,这种差异并不显著。
互易素数
当 1 除以一个质数时,会出现一个重复的数字模式。1/7 = 0.142857142857142857…重复的模式是 142857。这对于所有的质数都是成立的,除了拐角情况 2 和 5。
一个很好的性能测试是计算重复模式和该模式的长度。
def python_reciprocals(prime: int) -> dict: # corner cases
if prime in [2, 5]:
raise ValueError('Corner cases are not valid') # valid primes
start = 10
reciprocals = ''
while True:
reciprocals += str(start // prime)
start = (start % prime) * 10
if start == 10:
break return {'length': len(reciprocals), 'reciprocals': reciprocals}
比如:python_reciprocals(17)返回字典{'length': 16, 'reciprocals': '0588235294117647'。
function jlReciprocalsTypes(prime::Int64)::NamedTuple
# corner cases
if prime in [2, 5]
throw(DomainError(prime, "Corner cases are not valid"))
end # valid primes
start = 10
group = ""
while true
group *= string(start ÷ prime)
start = (start % prime) * 10
if start == 10
break
end
end
return (lenght=length(group), group=group)
end
对于上面的 Julia 版本(带有类型声明),我还测试了一个没有类型声明的版本。所有测试都使用质数 1212121。下图显示了结果。

作者图片
完整的 Jupyter 笔记本可以在 Github 上获得。
再次不完全是我所期待的。Python 胜过了 Julia,甚至在类型声明上也胜过了 Julia。
我怀疑 Python 处理字符串对象的速度更快。让我们再次做同样的测试,将重复的模式存储在一个列表(Python)或向量(Julia)中。

作者图片
这里的结果超出了我的预期。即使不使用类型声明,Julia 的表现也比 Python 出色得多。
最后的想法
我喜欢朱莉娅语言。然而,它并不是在所有情况下都像我想象的那样有性能。当处理数字数据和向量/数组时,Julia 速度很快!但是当处理字符串对象时,Python 似乎是更快的选择。
你对什么时候用 Julia,什么时候用 Python 有什么想法?有没有可以加快速度的代码优化?
清理文本数据入门
原文:https://towardsdatascience.com/primer-to-cleaning-text-data-7e856d6e5791
清洗文本是自然语言处理预处理的一个重要部分

来自像素的免费使用照片
介绍
在自然语言处理(NLP)领域,预处理是一个重要的阶段,在这里进行文本清理、词干提取、词汇化和词性标注等工作。在 NLP 预处理的这些不同方面中,我将涵盖我们可以应用的文本清理方法的综合列表。这里的文本清理指的是移除或转换文本的某些部分,以便文本变得更容易被正在学习文本的 NLP 模型理解的过程。这通常通过减少文本数据中的噪声使 NLP 模型表现得更好。
将所有字符转换成小写
string 包(Python 中的默认包)包含各种有用的字符串函数。lower 函数就是其中之一,把所有字符都变成小写。
**def** make_lowercase(token_list):
# Assuming word [tokenization](https://nlp.stanford.edu/IR-book/html/htmledition/tokenization-1.html) already happened # Using list comprehension --> loop through every word/token, make it into lower case and add it to a new list
words = [word.lower() for word in token_list] # join lowercase tokens into one string
cleaned_string = " ".join(words)
return cleaned_string
删除标点符号
Python 中的 string.punctuation(就是前面提到的包)包含以下几项标点符号。
#$%&\’()*+,-./:;?@[\\]^_{|}~`**import** stringtext = "It was a great night! Shout out to @Amy Lee for organizing wonderful event (a.k.a. on fire)."PUNCT_TO_REMOVE = string.punctuationans = text.translate(str.maketrans('', '', PUNCT_TO_REMOVE))ans
>> "It was a great night Shout out to Amy Lee for organizing wonderful event aka on fire"
字符串包中的另一个方法 translate 函数使用输入字典来执行映射。maketrans 函数是 translate 函数的兄弟方法,它创建用作 translate 方法输入的字典。请注意,maketrans 函数接受 3 个参数,如果总共传递了 3 个参数,则第三个参数中的每个字符都被映射为 None。这个特性可以用来删除字符串中的字符。
根据上面的代码片段,我们将 maketrans 函数的第一个和第二个参数指定为空字符串(因为我们不需要这些参数),并将第三个参数指定为上面 string.punctuation 中定义的标点项。然后,存储在变量文本中的字符串中的标点符号将被删除。
删除号码
text = "My cell phone number is 123456\. Please take note."text_cleaned = ''.join([i for i in text if not i.isdigit()])text_cleaned
>> "My cell phone number is. Please take note."
你也可以使用正则表达式做同样的事情,它是字符串操作最好的朋友之一。
text_cleaned = [re.sub(r’\w*\d\w*’, ‘’, w) for w in text]text_cleaned
>> "My cell phone number is. Please take note."
移除表情符号
随着各种社交媒体平台生成的非结构化文本数据的数量不断增加,更多的文本数据包含非典型字符,如表情符号。表情符号可能很难被机器理解,并且可能会给你的 NLP 模型添加不必要的噪声。从文本数据中删除表情符号就是这种情况。然而,如果你试图进行情感分析,尝试将表情符号转换成某种文本格式而不是彻底删除它们可能是有益的,因为表情符号可以包含与手头文本相关的情感的有用信息。一种方法是创建您自己的自定义词典,将不同的表情符号映射到一些表示与表情符号相同情感的文本(例如{🔥:火})。
看看这篇帖子,它展示了如何从你的文本中删除表情符号。
**import** re**def** remove_emoji(string): emoji_pattern = re.compile(“[“ u”U0001F600-U0001F64F” # emoticons u”U0001F300-U0001F5FF” # symbols & pictographs u”U0001F680-U0001F6FF” # transport & map symbols u”U0001F1E0-U0001F1FF” # flags (iOS) u”U00002702-U000027B0" u”U000024C2-U0001F251" “]+”, flags=re.UNICODE) return emoji_pattern.sub(r’’, string)remove_emoji(“game is on 🔥🔥”)>> 'game is on '
拼出宫缩
python 中的收缩包(需要使用!pip 安装收缩)允许我们拼出收缩。通过在执行标记化时创建更多的标记,拼出缩写可以为文本数据添加更多的信息。例如,在下面的代码片段中,当执行基于空白的单词标记化时,标记“would”不会被视为单独的标记。相反,它是象征“她愿意”的一部分。但是,一旦我们修复了缩写,我们就会看到,在执行单词标记化时,单词“would”作为一个独立的标记存在。这为 NLP 模型添加了更多的令牌以供使用。这可以帮助模型更好地理解文本的含义,从而提高各种 NLP 任务的准确性。
**import** contractionstext = “She**'d** like to hang out with you sometime!”contractions.fix(text)>> “She **would** like to hang out with you sometime!”
但是由于这个包可能不是 100%全面的(即没有覆盖存在的每一个缩写),您也可以创建自己的自定义字典,将包没有覆盖的某些缩写映射到这些缩写的拼写版本。这篇帖子向你展示了如何做到这一点的例子!
剥离 HTML 标签
我们使用 Python 的 BeautifulSoup 包来剥离 HTML 标签。这个包是用于网页抓取的,但是它的 html 解析器工具可以用来剥离 HTML 标签,如下所示!
**def** strip_html_tags(text):
soup = BeautifulSoup(text, "html.parser")
stripped_text = soup.get_text()
**return** stripped_text # Below is another variation for doing the same thing**def** clean_html(html):
# parse html content
soup = BeautifulSoup(html, "html.parser") for data in soup(['style', 'script', 'code', 'a']):
# Remove tags
data.decompose( ) # return data by retrieving the tag content
return ' '.join(soup.stripped_strings)
删除重音字符
**import** unicodedata**def** remove_accent_chars(text):
text = unicodedata.normalize('NFKD', text).encode('ascii', 'ignore').decode('utf-8', 'ignore')
**return** text
删除 URL、提及(@)、hastags (#)和特殊字符
我们可以利用正则表达式来删除 URL、提及、标签和特殊字符,因为它们保持一定的结构和模式。下面只是一个例子,说明我们如何匹配字符串中的 URL、提及和标签模式,并删除它们。请记住,应该有多种方法,因为有多种方法可以形成正则表达式来获得相同的输出。
**## Remove URLs****import** re**def** remove_url(text):
return re.sub(r’https?:\S*’, ‘’, text)print(remove_url('The website [https://www.spotify.com/](https://www.google.com/) crashed last night due to high traffic.'))
>> 'The website crashed last night due to high traffic.'**## Remove Mentions (@) and hastags (#)****import** re**def** remove_mentions_and_tags(text):
text = re.sub(r'@\S*', '', text)
**return** re.sub(r'#\S*', '', text)print(remove_mentions_and_tags('Thank you @Jay for your contribution to this project! #projectover'))
>> 'Thank you Jay for your contribution to this project! projectover'**## Remove Special Characters****def** remove_spec_chars(text):
text = re.sub('[^a-zA-z0-9\s]', '' , text)
**return** text[https://medium.com/mlearning-ai/nlp-a-comprehensive-guide-to-text-cleaning-and-preprocessing-63f364febfc5](https://medium.com/mlearning-ai/nlp-a-comprehensive-guide-to-text-cleaning-and-preprocessing-63f364febfc5)
删除停止字
停用词是一些非常常见的词,在帮助选择文档或为自然语言处理建模时可能没有什么价值。通常,当我们对自然语言处理进行预处理时,这些单词可能会从文本数据中被丢弃或删除。这是因为停用词由于出现频率过高,可能不会增加提高 NLP 模型准确性的价值。就像典型的机器学习模型一样,低方差的特征价值较低,因为它们无助于模型基于这些特征区分不同的数据点。这同样适用于 NLP,其中停用词可以被认为是低方差特征。同样,停用词会导致模型过度拟合,这意味着我们开发的模型对于看不见的数据表现不佳,并且缺乏推广到新数据点的能力。
# Retrieve stop word list from NLTK
stopword_list = nltk.corpus.stopwords.words(‘english’)stopword_list.remove(‘no’)stopword_list.remove(‘not’)**from** **nltk.tokenize.toktok** **import** ToktokTokenizertokenizer = ToktokTokenizer( )**def** remove_stopwords(text, is_lower_case=**False**): tokens = tokenizer.tokenize(text) tokens = [token.strip( ) **for** token **in** tokens] # List comprehension: loop through every token and strip white space filtered_tokens = [token **for** token **in** tokens **if** token **not** **in** stopword_list] # Keep only the non stop word tokens in the list filtered_text = ' '.join(filtered_tokens) # join all those tokens using a space as a delimiter **return** filtered_text
请注意,有另一种方法可以从一个名为 SpaCy 的不同包中检索停用词,这是另一个常用于 NLP 任务的有用包。我们可以这样做:
**import** **spacy**en = spacy.load('en_core_web_sm') # load the english language small model of spacystopword_list = en.Defaults.stop_words
警告和一些结束语
就像任何其他数据科学任务一样,不应该盲目地进行 NLP 的预处理。考虑你的目标是什么。例如,从你搜集的社交媒体文本数据中移除标签和提及符号,你想从中得到什么?是因为这些符号没有给你正在构建的预测某个语料库的情感的 NLP 模型增加多少价值吗?除非您提出这些问题并且能够清楚地回答,否则您不应该临时清理文本。请记住,询问“为什么”在数据科学领域非常重要。
在本文中,在进入 NLP 循环的下一阶段之前,我们查看了清理文本的各种方法的综合列表,如词汇化和如何实现它们的代码片段。
如果你觉得这篇文章有帮助,请考虑通过以下链接注册 medium 来支持我: )
joshnjuny.medium.com
你不仅可以看到我,还可以看到其他作者写的这么多有用和有趣的文章和帖子!
关于作者
数据科学家。加州大学欧文分校信息学专业一年级博士生。
密歇根大学刑事司法行政记录系统(CJARS)经济学实验室的前研究领域专家,致力于统计报告生成、自动化数据质量审查、构建数据管道和数据标准化&协调。Spotify 前数据科学实习生。Inc .(纽约市)。
他喜欢运动、健身、烹饪美味的亚洲食物、看 kdramas 和制作/表演音乐,最重要的是崇拜我们的主耶稣基督。结账他的 网站 !
主成分分析
原文:https://towardsdatascience.com/principal-component-analysis-ac90b73f68f5
机器学习(ML)建模包括在考虑的数据中寻找模式。在监督学习中,模型通过标记数据学习模式;也就是说,提供的数据有自变量和因变量。基于领域,独立变量可能有其他名称,如解释变量、预测变量、回归变量、协变量、特征(在机器学习和模式识别中)、输入或控制。另一方面,因变量也被称为目标、标签、响应、预测、输出、回归、结果、解释或测量。
通常,有几个特性,只有一个目标。根据数据的可用性和手头任务的性质,独立变量和数据点的数量因数据集而异。一个有很多自变量的数据集被称为高维。虽然数据可用性是一件好事,但高维数据会直接影响 ML 模型的性能,并带来数据存储需求的问题。
减少高维数据中的特征数量称为降维。降维的目的是:
- 移除数据集中的多重共线性。
- 减少数据所需的存储空间。
- 轻松可视化。
- 降低拟合和测试模型所需的计算能力,因为很少使用低维数据执行操作。
- 防止过度拟合。
- 避免维数灾难——与高维数据相关的一系列问题。
降维有两种方式:特征消除或特征提取。
(一)。特征消除包括识别不重要的独立变量并将其移除。比如两个高度相关的变量,去掉一个就行了。
不足:特征消除是指完全移除变量,使得被移除变量中的少量信息完全丢失。
(b)。特征提取 —这项技术需要从原始特征中构建一个新变量。新功能旨在提供信息(最大限度地减少信息损失)和非冗余(没有不重要的变量)。主成分分析是最流行的特征提取方法。
为了理解 PCA,让我们看一些线性代数和统计概念,即,
- 方差和协方差
- 特征值和特征向量
方差和协方差
方差衡量给定变量中数据围绕其均值的分布。变量X的方差由下式给出

变量 x 的方差。
与测量一维分布的方差不同,协方差测量两个变量相对于彼此的平均值的偏差。换句话说,协方差衡量两个维度之间的关系。

X 和 Y 的协方差
注:一个变量与其自身之间的协方差等于该变量的方差,即cov(x, x)=var(x)。
协方差值的解释
协方差值的符号是解释的组成部分。正值表示两个变量之间的直接关系;也就是说,这两个变量同时增加或减少。另一方面,负协方差值表示变量之间的负关系,即一个变量增加,另一个变量减少,反之亦然。协方差为零表示不存在任何关系;也就是说,这两个变量是独立的。
协方差值的大小(多大或多小)并不重要。它没有解释所考虑的两个变量之间关系的强度。这是因为量值受测量单位的影响。要衡量关系的强度,请使用相关系数。

X 和 Y 的相关性
注意:相关性只是协方差的一种缩放形式。这意味着缩放数据的协方差等于原始数据的相关性。
对于低维数据(2 和 3 个特征),可视化更容易,并且可以绘制数据(例如,在散点图上),并解释关系。然而,在高维数据上,可视化变得复杂,这就是协方差计算变得重要的地方。
协方差矩阵
对于 m 维数据,协方差是针对变量和协方差矩阵中呈现的值的 m×m 组合计算的。
对于 m 维数据,可以对协方差矩阵中呈现的变量和值的 m×m 个组合计算协方差。例如,对于 X、Y 和 Z 变量的 m=3,协方差矩阵将表示如下。

3×3 covariance matrix for X, Y and Z variables.
此矩阵的属性
- 协方差矩阵沿主对角线对称。这是 be-
原因,对于任意两个变量X和Y、cov(x, y) = cov(y, x)。 - 变量的方差在主对角线上。记得
我们说cov(y, y) = var(y)为变量Y。 - 对于
m维数据,构建m × m协方差矩阵。
特征值和特征向量
设A是一个m × m矩阵,使得

那么λ是**A**的特征值,**v**是与λ相关联的**A**的特征向量。v是非零向量。
注 1 :矩阵**A**必须是正方形。
注 2 :特征向量根据定义是非零的。
关系**Av** = λ**v**意味着**Av**和λ**v**与原点共线。那
就是,原点的一个非零向量**v**经过**Av**,如下图所示。

v 对于特征值λ是 A 的一个特征向量但是 z 不是因为
找不到对应的特征值。
给定一个m × m矩阵**A**,那么我们可以通过公式找到特征值λ和特征向量v

其中**I**是m × m单位矩阵,det(**B**)是矩阵B的行列式。
举例(求一个 **2 × 2** 矩阵的特征值和特征向量)
在这个例子中,我们感兴趣的是寻找2 × 2矩阵**A**的特征值和特征向量。

我们得到了两个特征值。接下来的事情就是找到这些特征值对应的特征向量
。我们将使用关系**Av** = λ**v**。

特征值和特征向量的性质
- 只能对方阵计算特征向量。我的意思是,我们在它的计算中使用行列式,行列式只能对方阵进行计算。
- 不是所有的方阵都有特征向量。
- 关键性质: 一个对称矩阵的所有特征向量都是正交的,即垂直/独立。无论维数多少都会发生这种情况(惊讶?我也是)。这个性质是关键的,因为协方差矩阵是对称的,这意味着,通过这个性质,通过 PCA 产生的新变量将是独立的(这将很快清楚)。
现在我们可以继续讨论 PCA 了。

Stephen Dawson 在 Unsplash 上拍摄的照片
主成分分析
对于有m个独立变量的数据集,PCA 将在下面的
步骤中实现:
步骤 1:数据标准化
这包括将数据转换为所有独立
变量的统一尺度,以便在计算方差等统计数据时进行公平比较。让我解释一下。如果不进行标准化,范围大的变量将支配范围小的变量。例如,如果我们有两个
变量,一个范围为 200–1000,另一个范围为 0–10,
,那么前者将支配后者,导致有偏分析。
数学上,变量X中原始值x的标准分数
计算如下。

其中x̄ (x_bar)是平均值,S是X的标准差。标准差就是前面解释的方差的平方根。标准
分数z正态分布,平均值为0,标准差为1。
步骤 2:为标准化数据构建协方差矩阵
如前所述,协方差矩阵将解释数据中任意两个要素之间的关系。此过程用于共线变量。正协方差值表示直接关系(两个变量都增加或减少)。负协方差意味着属性之间的反向关系(一个变量增加,而另一个变量减少,反之亦然)。
如前所述,协方差矩阵是对称的,意味着生成的特征向量是正交的,因此,最终导出的特征是独立的。
这一步相当于计算原始未标准化数据的相关矩阵。
步骤 3:计算协方差矩阵的特征值λ和特征向量。
**λ** = [λ1 , λ2 , · · · , λm ]是特征值列表,**V**是矩阵,第一列是对应于λ1的特征向量,第二列是对应于λ2的特征向量,依此类推。
步骤 4:决定要保留的特征数量。
在这一步中,您将决定我们需要保留的要素数量并生成新要素。可以通过以下方式决定要保留的功能:
- 任意选择。例如,如果您对
可视化的易用性感兴趣,那么您可以使用 2 或 3 个特性来运行它。 - 使用变化百分比解释。
对于这些方法中的任何一种,首先按降序对λ中的特征值进行排序。
第一个r主成分解释的方差比例定义如下

如果原始数据上的特征高度相关,我们需要很少的
主成分,即r比m小得多。另一方面,如果特征不相关,r接近m并且 PCA 在特征提取中失去其
重要性。
第五步:衍生新特征。

其中X是原始缩放特征数据,而V^T是基于在步骤(iv)中做出的选择的特征向量的
矩阵的转置。这一步是线性代数 中 变基的一个概念。如果你想更深入地了解为什么会这样,也许你可以查一下。
PCA 实施示例
考虑具有 3 个特征 x1、x2和x3以及 10 条记录的数据集。
注:在[sklearn.decomposition.PCA](https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.PCA.html)中有一个函数可以用来以更直接的方式实现 PCA。我们将在本文后面使用它,但现在我们将使用其他 Python 包(如 numpy)一步一步地实现 PCA。
代码片段 1:这个代码片段为原始数据生成一个 pandas 数据帧。我们只是用熊猫来以表格的形式显示数据。我的意思是,我们更经常看到表格数据。
实现 PCA 的第一步是缩放数据。这是通过使用代码snippet 2 below和显示在Figure 2 below中的结果完成的
代码片段 2:使用前面讨论的公式和 zscore 缩放数据集。

图 2:原始数据和缩放数据。
第二步:计算协方差矩阵
在这一步,我们计算协方差矩阵。这个矩阵用于建立变量之间的关系。
代码片段 3:协方差矩阵的计算
下面的输出显示x1s和x2s为强正相关(0.96)和cov(x1s, x3s)=0.82和cov(x2s, x3s)=0.89 ( 不要被这里使用的相关性混淆。缩放数据上的协方差只是相关性)。
array([[1\. , 0.96240631, 0.82362337],
[0.96240631, 1\. , 0.89326123],
[0.82362337, 0.89326123, 1\. ]])
步骤 3:协方差矩阵的特征值和特征向量
在这一步中,我们要生成主成分(特征值),并建立每个解释的变化比例。
代码片段 4:特征值和特征向量
输出(分类的特征值、分类的特征向量和分别由每个主成分解释的方差比例)
sorted_eigenvalues:
[2.7873923775066234, 0.1864183595384753, 0.026189262954900528]sorted_eigenvectors:
[array([-0.57770188, -0.59193503, -0.56202621]),
array([-0.57125709, -0.19861728, 0.79637712]),
array([-0.58303163, 0.78113002, -0.22340551])]variance_explained:
[0.9291307925022081, 0.06213945317949179, 0.008729754318300179]
第一个主成分解释了原始数据变化的92.9%,第二个主成分解释了6.2%,第三个主成分解释了0.9%。在这一点上,你现在可以决定主成分的数量。如果你选择前两个,那么它们将解释总共99.1%个变量。以下两个图显示了由单个组件和累积解释的变化。

图 3:左—各主成分解释的方差比例,右—解释的累积比例。
第四步:生成新数据
最后一步是通过等式new _ data = np.dot(X,V.T)生成新数据,其中X是原始缩放数据,而V.T是根据特征值排序后的特征向量矩阵的转置。
代码片段 5:用一个特性生成新数据。当然,您可以选择生成 2 个甚至 3 个特征的新数据。
输出(z1)
[-2.1859,
-2.967,
1.3283,
1.3625,
-0.2836,
-0.4782,
1.8748,
1.7916,
0.3921,
-0.8345]
使用sklearn.decomposition实现 PCA。主成分分析
使用[sklearn.decomposition.PCA](https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.PCA.html)功能可将上述所有步骤浓缩如下。
代码片段 6:使用 sklearn 实现 PCA。
输出(2 列 PCA 生成数据的前 5 行):

PCA 是一个很好的特征提取工具,但是也有一些缺点。以下是一些例子:
- PCA 生成的特征可解释性较差。这些特征是原始特征的组合,因此我们无法清楚地解释原始特征如何影响因变量。
- 在降维过程中,PCA 会导致一定程度的数据丢失。然而,这种损失比使用特征消除方法要好。
这就是结尾,我希望这篇文章能让你明白这个概念。
感谢您的阅读:-)
主成分分析:你需要知道的一切
原文:https://towardsdatascience.com/principal-component-analysis-everything-you-need-to-know-5f834c9eaa83
👨🏫数学
协方差,特征值,方差和一切

作者图片
主成分分析(PCA)是一种减少数据维度的流行技术,包含在大多数 ML/DS 课程的“无监督学习”部分。有很多博客和 YT 视频一起解释 PCA,那么为什么会有这个博客呢?
又一个关于 PCA 的博客?
作为一个更热爱数学的 ML 学习者,我发现 PCA 上的每个博客都不完整。总结我阅读的每一篇博客,我每次只能得出一个结论, PCA 最大化投影数据点的方差,我没有任何地方是错误的。但由于我发现到处都只有这种观点,我无法深入这个话题,以了解更多的数学知识以及特征值是如何突然出现的。
作者的故事
我决定认真对待这件事,我收集了书籍、论坛和其他博客来制作我写的这个博客。它包含了不那么简单的数学,但是我保证没有一个概念会让你挠头超过 10 分钟。这个故事是为那些,
- 好奇想知道 PCA 背后的数学
- 了解线性代数概念,如基、矩阵的迹、特征值和特征分解
- 了解统计概念,如随机变量、随机向量、方差和协方差
让我知道你过得怎么样!
小小免责声明
这里涉及的数学将使用线性代数和统计学中的大量术语。故事中引入的每一个新术语都将被标记上一个资源,可以参考该术语的更多知识。
问题是
PCA 的主要目的是将给定的观察变量(数据)解释成一组潜在变量,使得潜在变量保留观察变量的大部分信息。
观察变量是那些真实值被测量的变量。考虑一个问题,其中我们被要求对特定股票的价格进行建模,观察变量可以是公司进行的投资、销售、回报和其他可以直接影响股票价格的变量。
“潜伏”这个词的意思是隐藏的或隐蔽的。在我们的例子中,变量不是直接观察到的,而是在观察到的变量上使用的模型的帮助下推断出来的。在相同的股票价格预测示例中,潜在变量可以是某人购买该股票的机会、该股票的未来价格等。
就认证后活动而言,目标是,
确定潜在变量,使其包含观察变量中包含的大部分信息。潜在变量的数量应该少于观察变量的数量。
为什么我们不能从数据集中去掉一些特征来降低维度呢?
在大多数真实世界的数据集中,要素或观测变量在某种程度上相互依赖。例如,在气象数据集中,降雨量可能取决于也可能不取决于特定地区的温度。如果我们直接删除温度变量,我们可能会丢失特征中包含的一些有用信息。因此,我们需要以这样一种方式转换观察到的变量,即我们可以很容易地从数据集中删除不太突出的特征,同时保留信息。
1.寻找数据点的替代表示
首先,我们考虑包含 N 个样本的数据集,其中每个样本具有 D 个特征。我们可以用矩阵 X 的形式表示我们的数据集,其中每行包含一个样本,每列对应一个特定特性的值。

(1)定义我们的数据集和数据矩阵 X
在故事的后面部分,我们还会将 X 视为一个随机向量,

随机向量 X,它的每一次实现都可以被视为一个数据点。
我们认为每个特征都是随机变量。你不需要担心这个,当我们考虑把 X 作为随机向量时,我会清楚地提到。
另外,我们做的一个重要假设是数据点以平均值为中心。我们可以通过从每个样本中减去均值向量来做到这一点,这样得到的数据点就以均值为中心。
我们希望用新的基向量来表示这些数据点。一个基是一组跨越它们周围向量空间的独立向量。更简单地说,基(或基向量)中所有向量的线性组合产生了向量空间中存在的每个向量。新的基础将为特征提供另一种表示,通过这种表示,我们可以很容易地去掉不太重要的特征。通过选择新的基,我们还需要新的坐标来表示数据点。
这些基向量很特别,因为我们可以选择需要多少基向量,来得到想要的维数。但是,我们需要仔细选择这些基准,以便它们能使几乎与完全相同的数据点,而没有任何相当大的损失。为了建立这样一个基础,我们需要,
- 标准正交基以替代方式表示数据点(如上所述)。基向量的数量等于向量空间的维数,所以在我们的例子中,我们需要个基向量。我们的新基将跨越我们的数据点所在的相同向量空间。所以,在新的基中,我们会有 D 向量。
- 坐标用新的标准正交基唯一地表示每个数据点 x_i 。
我们可以在 D 维空间中定义新的标准正交基,

(2)定义一个新的标准正交基。标准正交基意味着基向量彼此正交,并且具有单位幅度。
将所有这些正交向量收集到一个矩阵中,其中每列包含一个向量,我们剩下一个正交矩阵(它有一些有用的属性,我们将在后面的部分中探讨),

(3)将新的标准正交基装入矩阵 W 中。该矩阵将是正交矩阵,其性质将被进一步利用。
接下来,我们需要一些坐标或权重,它们可以乘以新的基来表示数据点。坐标和基向量的加权组合不会精确地表示数据点,但是我们希望得到良好的近似,

(4)将数据点表示为新基向量的加权组合。
对于所有的 N 数据点,坐标将是不同的,因此在矩阵向量符号中移位是有益的,
**
(5)对于矩阵向量符号中的所有数据点,写出表达式(4)
2.寻找最佳参数
我们希望使用新的基和坐标来获得我们的数据点的近似。为了测量数据点 x 及其近似 Wz 的“接近度,我们计算所有 x-Wz、 的平方 L2 范数(欧几里德范数)的平均值

(6)定义一个目标函数,最小化它,我们使用 z 和 w 获得数据点 x 的“最佳”近似。
函数 L 的值越低(函数 L 的值是一个标量),我们得到的近似值就越好。所以,现在我们的问题进入最优化理论,其中我们需要确定为【L】给出最低值的 W 和的值。我们可以计算Lw . r . t .z _ n的导数,并使其等于零。**
下一步,我们要计算目标函数 w.r.t .对向量的偏导数。如果你对矩阵演算不太熟悉,你可以参考这些笔记,其中包含一些有用的结果及其推导。

(7)根据矩阵/向量乘积重写 L2 范数。简单来说,考虑到欧几里得空间,向量的 L2 范数就是它的大小。

(8)计算函数 L w.r.t z_i. 的偏导数这里, z_i 保存数据集中某个数据点 x_i 的坐标。
正如所承诺的,在(7)的最后一步中,我们使用了正交矩阵的一个优良特性。接下来,我们将(8)中得到的偏导数等于零,从而根据 W 和 x 得到 z 的最优值。

(9)使偏导数等于 0,从而得到在函数 L 的全局最小值处 z_i 的值。
正如你可能观察到的,当 z_i 等于数据点 x_i 乘以 W 矩阵的转置时,得到目标 L 的最小值。作为我们目标函数表达式中的第一项,σx_n^t x _ n,** 是一个常数,我们可以把它去掉,继续展开项σz_n^t z _ n,**

(10)去掉常数项,代入(9)的结果,这样我们得到一个仅用 z 表示的表达式。
z 和 W 之间的关系从(9)中是显而易见的,但是我们需要熟练地使用它来获得想要的结果,

(11)使用跟踪算子的性质,我们能够根据数据点的协方差矩阵来减少我们的目标函数
在上面的评估中,我们有跟踪操作符的一些属性,

(12)我们在(11)中使用的跟踪算子的一些性质
这里有必要使用 trace 运算符,因为它将帮助我们得出一个重要的结果,通过这个结果可以计算出 W 的最佳值。此外,我们还利用了统计学的一个重要结果,

(13)表示为数据点的外积总和的经验协方差矩阵。
经验协方差矩阵 K_XX 是保存随机向量中包含的不同随机变量对之间的协方差的矩阵。乍一看,该表达式可能看起来不可信,但您可以通过考虑将产生 2 * 2 协方差矩阵的 2D 数据点来验证它。

(14)随机向量 x 的协方差矩阵。在(13)中,我们用数据点数值计算它。
协方差矩阵也是对称的,我们可以很容易地从(14)中计算出来。对称矩阵还拥有一些漂亮的特性比如,
- 对称矩阵有实特征值,所以没有复杂的东西!
- 相应的特征向量彼此正交。它们可以作为向量空间的优秀基,称为特征基(希望你得到了提示!
为了确定 W 的最佳值,我们可以建立一个拉格朗日函数,
****
(15)定义拉格朗日目标,以最大化函数L’的值。
在(11)中,观察到最大化轨迹将导致最小化L’。在我们的优化问题中,我们对 W 只有一个约束,那就是它的正交性。为了在拉格朗日乘数的上下文中保持定义良好,我们在上面的表达式(14)中定义了矩阵内积,

(16)矩阵内积( Frobenius 内积)
它被称为 Frobenius 内积,它采用两个矩阵并返回一个标量(所有内积的典型属性)。矩阵λ将是包含乘数的方阵,

(17)矩阵 λ 包含 W 中每个向量的拉格朗日乘数。
继续,我们计算目标PW . r . t .W的偏导数,并使其等于零,以获得 λ 的值。
****
(18)使 P 的偏导数等于零。通过这样做,我们观察到 λ 是具有经验协方差矩阵的特征值的对角矩阵
上面的表达式为我们提供了迄今为止最美丽的结果,我们将在这里等待欣赏它的美丽。
线性代数中的谱定理说对称矩阵是可对角化的。它仅仅意味着一个对称矩阵可以通过使用一些其他的特殊矩阵转换成一个对角矩阵(其中元素只位于矩阵的主对角线上)。可对角化是一个很好的性质,因为它大大减少了各种其他有用的性质,如行列式,迹等的计算。一个对称矩阵 一个 是可对角化的,比如,

(19) 对称实矩阵 A 的对角化
其中 D 是从 A 导出的对角矩阵。 D 的元素是矩阵 A 的实特征值。矩阵 P 的列包含矩阵 A 的特征向量。由于矩阵 A 是对称的,其特征向量相互正交,因此矩阵 P 也是正交的。
回到表达式(18),

(20)因为我们的数据点的协方差矩阵也是实对称矩阵,所以它是可对角化的。这里的 λ 是一个对角矩阵。
协方差矩阵 K_XX 是对称的,所以对角矩阵 λ 包含协方差矩阵的特征值 W 包含归一化的特征向量。所以,最后我们得到了标准正交基 W 和坐标 z as,

(21)我们得到的关于 W 和 z 的最终结果。
我们的目标是为数据点找到一种新的表示方法,以便可以轻松地删除这些特征。此外,我们还讨论了为什么这些特性不能轻易删除,因为它们是相互依赖的。我们在步骤(2)和(3)中所做的是,我们试图使这些特性相互独立,这样我们就可以轻松地选择它们,而不用担心它们之间的依赖性。这不仅仅是一个想法,而是我们用步骤(2)和(3)从数学上证明了它。
让我们回到过去,我们会意识到两个随机变量之间的协方差大约捕获了变量之间的相关性。如果两个变量是独立的,协方差为零。

(22)协方差矩阵 X ,其中 X 为随机向量。 X_1 、 X_2 、…、 X_D 是表示为随机变量的特征。注意,在(18)中,该矩阵被转换成对角矩阵,从而将非对角元素设置为 0,即特征之间的协方差。
在表达式(21)中,我们已经将协方差矩阵 K_XX 表示为对角矩阵。在协方差矩阵中,所有非对角元素表示随机变量或特征对之间的协方差。通过将协方差矩阵转换为对角矩阵,我们已经破坏了特征/随机变量之间存在的所有协方差。我们已经尝试让每个特性独立,这样我们就可以使用合适的标准轻松地对它们进行排序,而不用太担心内部的依赖性。
通过最小化函数 L ,我们已经认识到数据点 x_i 的最佳近似可以通过将数据点的基变换为新的基,即特征基来获得!
3.缩小尺寸(去除特征)
到目前为止,我们已经找到了一个新的基础来表示数据点,这样我们就可以很容易地分离特征,而不用考虑一个特征对另一个特征的依赖性。但问题是,我们应该放弃哪些功能?我们需要一些标准来从数据集中保留最好的 K 特征。
对于 PCA,我们将选择具有较高方差的特征。简单的逻辑是,具有低方差的特征与常数一样好,并且对响应变量几乎没有影响。这里有一个关于同一主题的交叉验证的好讨论。这也是为什么您可能会注意到sklearn.feature_selection模块中的VarianceThreshold选择特性的方差大于给定阈值的原因。正如我们前面讨论的,我们的数据的协方差矩阵 K_XX 在新基中看起来像一个对角矩阵,

(23)变换后的数据点的协方差矩阵是一个对角矩阵,因为我们已经破坏了
对于上面的表达式,我们将 z (转换后的数据点)视为一个随机向量,

协方差矩阵的对角线包含特征的单个方差。因此,特征值对应于变换数据点的方差,我们希望选择顶部的 K 特征值。同样,从矩阵 W 中,我们将选择相应的 K 特征向量,并最终使用它们来变换数据集。
因此,我们将选择 K 特征向量,其中每个特征向量的维数为 D ,并将它们打包成矩阵 W_K, 转置它,并与每个数据点 x_i 相乘,以获得更低维的表示 z_i ,

当我们选择 K < D (数据的实际维度)时,我们将得到数据点的近似值,而不是它在新基中的精确表示,这只是因为我们抛弃了来自 W 的基向量。这就是我们如何通过以一种聪明的方式剥离特征来减少数据的维度。
这都是关于主成分分析及其在降维中的作用。
怒发冲冠!那是一大堆数学!
作者的其他故事
我希望这个故事提供了主成分分析的全貌,以及它与协方差、特征向量和维数约简的关系。在那之前,继续学习数学,祝你有美好的一天(如果你坚持到这里)!
主成分分析
原文:https://towardsdatascience.com/principal-component-analysis-fbce2a22c6e0
PCA 的概念性解释及其背后的数学原理的逐步演示。Python 和 R 中结果的可视化

丹尼尔·罗伯特在的照片
在本主成分分析指南中,我将给出 PCA 的概念性解释,并提供一步一步的演练,以找到用于计算主成分和主成分得分的特征向量。我还将展示我们如何使用 Python 和 R 中的 sklearn 在 iris 数据集上执行小 PCA 后找到并可视化结果。在 R 中使用 factoextra 和 gglot2 包可视化 PCA 结果非常容易。
- 什么是主成分分析
- PCA 背后的数学:如何计算主成分
- 解释 PCA 的结果
- Python 中的主成分分析
- 使用 ggplot2 和 factoextra 可视化 R 中的 PCA 结果
该指南以笔记本的形式提供,其中包括更多 python 代码,用于本 Github repo 中的所有计算和绘图。repo 还包含一个笔记本,其中包含 r 中的 PCA 和可视化。
1.什么是主成分分析
主成分分析可以作为一种降维技术,我们可以形成新的变量,这些变量是原始变量的线性组合。如果您正在进行回归建模,并且数据中存在多重共线性,这也是一种有用的技术。多重共线性可能导致参数的高标准误差,从而导致不可靠的估计值。主成分分析将形成不相关的新变量。
1.1 PCA 的几何表示
在进入主成分分析的数学之前,查看 PCA 的几何表示可能有助于获得概念上的解释。希望它能帮助我们将数学计算与主要成分的不同元素联系起来。考虑下面的图表,它显示了一些我们想要找出主要成分的数据。

作者图片
如果我们观察数据点,很明显 X1 和 X2 都不是获取数据最大方差的轴。获得最大方差的轴是 X1,这是我们在主成分分析中想要识别的备选轴。这个新的轴将成为我们的第一个主要组成部分。原始数据点在 X1上的投影将是数据点相对于这个新 X1*的坐标。
在上面的图中,我们让 X1*是二维平面中的任意轴,与轴 X1 成θ度角。点相对于新轴的坐标是点相对于原始轴的坐标的线性组合:

由新轴 X1捕获的总方差的百分比将根据角度θ的度数而变化。这意味着有且只有一个备选轴会产生一个新变量,该变量保留数据中的最大方差。这个新轴不能解释数据的所有方差,所以我们需要确定第二个新轴,X2。与 X1正交的第二个轴将说明 X1未捕捉到的最大方差。我们可以认为 PCA 是将轴旋转到一个位置,使新的轴能够捕获数据中的最大方差。
1.2 需要牢记的重要概念
从 PCA 的几何表示中,我们可以得出一些重要的观察结果:
- 当我们旋转轴时,二维空间中数据点的方向不会改变。这意味着观察值可以相对于原始轴或新轴来表示,而不会丢失任何信息。
- 新轴𝑋1和𝑋2是我们的主成分,新变量的值是我们的主成分分数,我们的转换数据。
- 新变量 X1和𝑋2是我们原始特征𝑋1 和𝑋2 的线性组合,它们保持均值校正(或标准化,取决于我们使用的数据中心化方法)。
- 新变量的总方差与原始变量相同。
- 新变量的相关性为零。换句话说,主成分之间不会有多重共线性。
上面的几何表示只有两个变量,因为很难说明更高的维度。然而,该方法可以很容易地扩展到任何数量的变量 p 。具有 p 个变量的数据集可以在 p 维空间中表示,具有 p 原始轴和 p 新轴,使得:
- 每个新变量都是原始变量的线性组合。
- 选择第一个新变量来获取数据集中的最大方差。
- 第二个将说明第一个没有捕捉到的最大方差。
- 第 p 个新变量将解释 P1 个新变量未捕捉到的数据中的最大方差。
- 新变量不相关。
在下一节中,我们将找到新的轴,并手动计算主成分。我们将从变量的协方差矩阵计算特征向量,这给出了用于形成上述方程的权重 w_𝑖𝑗(这是我们的主分量):

其中 X1,X2,…,Xp 是 p 个主成分,w_ ij 是第 i 个主成分的第 j 个变量的权重(特征向量)。
2.主成分分析的数学:如何计算主成分
在第一部分中,我们将研究 PCA 背后的数学,我们将创建一个小的虚拟数据集,小到足以用手轻松地进行计算。数据集将包含两个要素 X1 和 X2 以及七个观测值。
# Create a small datasetdata = pd.DataFrame({'X1':[110, 112, 112, 114, 116, 116, 118],
'X2': [179, 180, 181, 182, 182, 184, 186]})

作者图片
2.1 将数据居中
在进行主成分分析之前,我们可以对数据进行均值校正或标准化,并且要记住,主成分分析的结果可能会受到我们使用的方法的影响。使用均值校正数据将允许单个变量的相对方差影响用于计算主成分的权重。方差相对于其他变量较高的变量将获得较高的权重,方差相对于其他变量较低的变量将获得较低的权重。如果我们把数据标准化,每个变量的方差都是一样的(均值 0,std/var 1)。
方法的选择可能取决于数据和分析的目的等因素。例如,如果我们要查看消费者价格指数的食品价格,不同食品项目的方差可能会有很大差异。在下面的主成分中,由于较高的方差,鳄梨比其他食物项目具有更高的影响。如果我们没有理由相信对于一个家庭来说,鳄梨比其他食物更重要,我们应该将数据标准化,以避免这种人为增加的鳄梨重量。
*PC1 = 0.13 *牛肉+0.85 *牛油果+0.04 *大米+0.36 牛奶
可能还有其他情况,我们有理由相信特征的方差确实表明了某些特征的重要性,在这些情况下,我们可以使用均值校正数据,并允许对某些因素产生更高的影响。
对于我们的计算,我们将使用平均校正数据。均值校正数据仅仅意味着我们从变量的每个观察值中减去变量的均值:
*# Center the data with mean-correction* data_centered = data.apply(lambda x: x-x.mean())

作者图片
我们可以绘制原始数据和居中数据的数据点,我们可以看到,将数据居中并不影响数据点的方向,只是轴的比例发生了变化。我们将数据集中在原点周围,这将有助于我们在旋转轴寻找主分量时的计算。

作者图片
2.2 计算协方差矩阵
我们将使用的方法是协方差矩阵的特征分解,因此我们首先需要计算的是协方差矩阵。协方差矩阵由每对变量的协方差组成,每个变量的方差在主对角线上。
数据居中不会影响要素的方差或协方差。非居中和居中数据的协方差矩阵是相同的。我们可以检查情况是否如此:
*# Print values of the non-centered data and its covariance matrix*
non_centered = np.stack((data['X1'], data['X2']), axis=0)
cov_mat = np.cov(non_centered)
print(non_centered)
print(cov_mat)*# Print values of the centered data and its covariance matrix*
centered = np.stack((data_centered['X1'], data_centered['X2']), axis=0)
cov_mat_centered = np.cov(centered)
print(centered)
print(cov_mat_centered)*Output:*[[110 112 112 114 116 116 118]
[179 180 181 182 182 184 186]]
[[8\. 6.33333333]
[6.33333333 5.66666667]][[-4\. -2\. -2\. 0\. 2\. 2\. 4.]
[-3\. -2\. -1\. 0\. 0\. 2\. 4.]]
[[8\. 6.33333333]
[6.33333333 5.66666667]]
2.2.1 每个特性的差异
矩阵的对角线包含每个特征 X1 和 X2 的方差。差异的计算公式如下:

由于居中数据的平均值为 0,我们可以简单地将变量的平方值相加。

特征的协方差
两个变量 X1 和 X2 之间的协方差通过以下公式计算:

我们可以找到 X1 和 X2 的协方差。同样,由于居中数据的平均值为 0,我们可以将 X1 和 X2 中的值相乘并求和。

计算 X1 和 X2 的方差和协方差显示了 Python 中的np.cov()函数打印的相同结果。我们可以看到,X1 的方差大于 X2,协方差介于 X1 和 X2 的方差之间。
[[8\. 6.33333333]
[6.33333333 5.66666667]]
2.3 计算协方差矩阵的特征值
现在我们有了协方差矩阵,我们可以继续计算特征值。在下面的行列式公式中,我们将协方差矩阵表示为 A ,将单位矩阵表示为 I 。

从协方差矩阵中减去λ÷I 矩阵得到以下矩阵

我们可以继续计算它的行列式

简化后,我们得到这个二次方程

求解 0,我们得到𝜆1=0.39 和λ2=13.27,这些是我们协方差矩阵的特征值。
2.4 计算特征向量
一旦我们有了特征值,我们就可以计算这些特征值对应的特征向量。一个特征向量是一个非零向量,它满足方程𝐴𝑣=𝜆v.计算协方差矩阵的特征向量,我们使用这个方程,把协方差矩阵代入𝐴,把其中一个特征值代入λ,并把它们与列向量𝑣=(𝑥,𝑦).相乘我们从特征值λ=13.27 开始:

我们得到下面的方程组:

我们将第一个等式中的 8.0x 项和第二个等式中的 5.7y 项移到等式的右侧,求解 y,得到 y = 0.83x:
𝑦 = 0.83𝑥
0.83𝑥 = y
如果将 x 设为 1,y 将为 0.83,从而得到特征向量𝑣_2:

这是协方差矩阵的特征向量。我们使用以下公式将向量归一化为单位长度,这意味着它的长度为 1:

归一化后,特征向量 v_2 是这样的。

如果我们对另一个特征值𝜆1=0.39 做所有的计算,我们得到另一个特征向量 v1:

2.5 计算主成分和主成分得分
我们将最大特征值的特征向量先λ1>λ2 重新排序。

这些特征向量现在可以用来转换数据,计算主成分。特征向量包含表示形成主分量的等式中的权重 w_ ij,的值。

我们将这两个特征向量放在一个矩阵 E 中,这样我们可以使用矩阵乘法将所有主成分的特征向量与整个原始数据集相乘。第一列表示具有最高特征值的第一特征向量,第二列是具有第二高特征值的第二特征向量。

这个矩阵现在可以用来转换我们的中心数据,存储在矩阵 D,通过乘以它们。

当我们将原始中心数据乘以特征向量时,我们得到新的矩阵 DE,它包含转换后的数据、我们的主成分、和我们的主成分得分。

新矩阵 DE 包含我们的主成分,其中每一列都是一个主成分。左栏是我们的第一个主成分,PC1,捕捉数据中的最大方差,右栏是第二个主成分,PC2。
这些变换后的数据点的值就是主成分得分。这些分数表示主成分空间中的居中数据,因为它们已经被投影到旋转轴,从而最大化沿着第一主成分的轴的方差。从分值可以看出,PC1 的方差比 PC2 大得多。
这两个新变量是不相关的。
3 解释主成分分析的结果
3.1 视觉比较:绘制 PCA 前后的数据点

作者图片
如果我们通过观察数据点如何落在新旧轴上来比较原始数据和主成分的方差,我们可以看到第一主成分 PC1 包含比原始特征 X1 多得多的方差。
3.2 数值比较:PCA 前后的方差和协方差

作者图片
如果我们观察我们的变换数据的协方差矩阵,我们可以看到主成分 1 和主成分 2 的方差分别是 13.27 和 0.39,并且它们之间没有相关性。
这里需要注意的是,主成分的方差是我们之前根据原始数据的协方差矩阵计算的特征值。完成主成分分析后,您将查看特征值,以帮助确定提取多少主成分用于数据的进一步分析/建模。特征值用于生成碎石图。
variance=𝜆1/(𝜆1+𝜆2)=13.27/(13.27+0.39)=0.97 的百分比
转换后,第一个主成分捕获了数据中所有方差的 97%。原始数据中的特征 X1 仅捕获了 58%的变化。
通过进行这种主成分变换,我们获得了两大好处。我们已经消除了两个要素之间的多重共线性问题,并且最大化了较少要素的方差,以便减少模型/分析中包含的要素数量。
3.3 加载和评分
负载是原始特征和新的主成分之间的相关性。载荷给出了原始变量对形成主分量有多大影响的指示。
如前所述,分数是原始数据乘以特征向量后得到的转换数据。可以绘制分数图,以便对结果进行进一步分析。它们可能给出具有相似性的数据点的集群的指示。更常见的是,它们将被用作进一步多变量分析和建模的输入数据,例如分类或回归。
4.Python 中虹膜数据集的主成分分析
在对数据执行 PCA 之后,您可以使用下面的代码访问结果。
分数
pca.transform(data)将数据转换成 PCA 分数
载荷
pca.components_将打印载荷。如果你希望每个主成分的载荷以列的方式显示,你可以请求转置pca.components.T
各主成分的方差(特征值)
pca.explained_variance_
解释方差比例
pca.explained_variance_ratio_
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCAimport plotly.express as px
import plotly.io as pio
import plotly.graph_objects as go
4.1 导入和缩放数据
*# Import iris data set and drop Id column*
iris = pd.read_csv('Iris.csv')
iris = iris.drop('Id', axis=1)*# Store label in y*
y = iris.pop('Species')*# Store column names*
col_names = iris.columns*# Center data*
iris_centered = scale(iris)
4.2 执行 PCA 并将分数存储在数据帧中
*# Perform PCA on centered iris data*
pca = PCA()
pca.fit(iris_centered)
iris_scores = pca.transform(iris_centered) # pca.transform gives the scores*# Convert scores to a data frame*
iris_scores_df = pd.DataFrame(iris_scores)
iris_scores_df = iris_scores_df.set_axis(['PC1', 'PC2', 'PC3', 'PC4'], axis=1)*# Add labels to score data frame for plotting*
iris_scores_df = pd.concat([iris_scores_df, y], axis=1)
iris_scores_df.head()

作者图片
4.3 每个主成分(特征值)的方差和累积方差
如前所述,我们首先要看的是每个主成分保留了多少数据方差,更有趣的是,总方差的比例。我们使用pca.explained_variance_ratio.访问方差的比例,我们可以将这些值与累积比例一起存储在数据框中,以便绘图。
*# Proportion of variance each principal component*
prop_variance = pca.explained_variance_ratio_
prop_variance_df = pd.DataFrame(prop_variance, columns=['Proportion of Explained Variance'])*# Add cumulative proportion of variance*
cumulative_prop = np.cumsum(np.round(prop_variance, decimals=4))
cumulative_prop_df = pd.DataFrame(cumulative_prop, columns=['Cumulative Proportion of Variance'])*# Combine into one dataframe*
pc_df = pd.DataFrame(['PC1', 'PC2', 'PC3', 'PC4'], columns=['Principal Component'])
variance_df = pd.concat([pc_df, prop_variance_df, cumulative_prop_df], axis=1)
variance_df

作者图片
4.4 碎石图
选择主成分数量的一个常用方法是绘制方差百分比,并寻找一个拐点。这个图被称为碎石图。
*# Plot proportion of explained variance*
fig = px.line(variance_df, x = 'Principal Component', y='Proportion of Explained Variance',
text='Proportion of Explained Variance', width=600)fig.update_traces(texttemplate='%{text:.3f}')
fig.update_layout(title_text='Proportion of Explained Variance')
fig.show()

作者图片
我们也可以用柱状图表示方差的比例,并将累积方差加在一条线上。
*# Plot proportion of variance in bars and cumulative proportion in line*fig = go.Figure()*# Plot explained variance*
fig.add_trace(
go.Bar(
x=variance_df['Principal Component'],
y=variance_df['Proportion of Explained Variance'],
marker=dict(color='Blue')
)
)*# Plot cumulative variance*
fig.add_trace(
go.Scatter(
x=variance_df['Principal Component'],
y=variance_df['Cumulative Proportion of Variance']
)
)fig.show()

4.5 分数图
我们还可以绘制分数图来进一步解释结果。iris 数据集是一个很好的例子,因为它显示了不同物种的不同聚类。
# 3d scatter plot with scores of the first 3 principal components
fig = px.scatter_3d(iris_scores_df, x='PC1', y='PC2', z='PC3', color='Species', symbol='Species', opacity=0.6)fig.update_layout(template='seaborn')fig.show()

5.在 R 中可视化 PCA 结果
r 有一个名为 factoextra 的有用软件包,可以帮助提取和可视化多变量分析的结果,包括主成分分析。我将包括几个图表,以简洁的方式显示分数和加载。r 允许以一种非常简单紧凑的方式运行 PCA 并提取结果。让我们在绘图之前快速地看看结果是如何被访问的。
*# Perform PCA and store PCA object in iris_pca*
iris_pca <- prcomp(iris, scale=TRUE)
执行 PCA 后,您可以使用str(PCA_object)来查看 PCA 对象包含的内容。简单地打印对象将显示主成分的标准偏差和特征向量。
*# Printing the pca object which will show the eigenvectors*
iris_pca*Output:*Standard deviations (1, .., p=4):
[1] 1.7061120 0.9598025 0.3838662 0.1435538
Rotation (n x k) = (4 x 4):
PC1 PC2 PC3 PC4
SepalLengthCm 0.5223716 -0.37231836 0.7210168 0.2619956
SepalWidthCm -0.2633549 -0.92555649 -0.2420329 -0.1241348
PetalLengthCm 0.5812540 -0.02109478 -0.1408923 -0.8011543
PetalWidthCm 0.5656110 -0.06541577 -0.6338014 0.5235463
打印摘要将显示每个主成分的标准差、方差比例和累积方差比例。
*# Print summary to show st.dev, proportion of variance and the cumulative proportion of variance of each principal component*
summary(iris_pca)*Output:*Importance of components:
PC1 PC2 PC3 PC4
Standard deviation 1.7061 0.9598 0.38387 0.14355
Proportion of Variance 0.7277 0.2303 0.03684 0.00515
Cumulative Proportion 0.7277 0.9580 0.99485 1.00000
分数作为 x 变量存储在对象中。
*# Get the scores using dollar sign (X_pca$x) and store scores in a dataframe for plotting in ggplot2*
scores_df <- as.data.frame(iris_pca$x)*# Add column with species to the scores_df*
scores_label <- cbind(scores_df, data$Species)
5.1 碎石图
factoextra 包允许我们通过简单地将 PCA 对象传递给fviz_eig()来绘制 scree 图。
*# Scree plot showing the proportion of variance of the principal components*
fviz_eig(iris_pca)

作者图片
5.2 情节得分
在 python 图中,我们看到了分数的 3D 图。在这里,我们将看看 2D 的几个地块。
*# Graph of individuals, similar observations are grouped closer together.*
fviz_pca_ind(iris_pca,
col.ind = 'cos2',
repel = FALSE)

作者图片
我们还可以包括用 ggplot2 绘制的分数图,因为这允许通过颜色和围绕聚类的多元 t-分布的 95%置信区间来绘制结果。
*# Plot scores with colored labels and confidence interval*
ggplot(scores_label, aes(x = PC1, y = PC2, col = data$Species)) +
geom_point() +
stat_ellipse() *# add a confidence interval*

作者图片
5.3 地块荷载
如果我们想知道每个变量对前两个主成分的形成有多大影响,我们可以画出载荷。
*# Graph of loadings*
fviz_pca_var(iris_pca,
col.var = 'contrib',
repel = TRUE # Do not overlap text
)

作者图片
该图显示,对于第一主成分,花瓣具有正相关性。长度,花瓣。宽度和萼片。第二个主成分对所有的特征都有负的系数,尽管对花瓣的相关性。长度和花瓣。宽度非常小。
5.4 显示分数和负荷的双标图
我们也可以使用双标图在同一个图中显示得分和负载。
*# Biplot of individuals and variables*
fviz_pca_biplot(iris_pca,
col.var = 'blue', # color of variables
col.ind = 'darkgray', # color of individuals
)

作者图片
我希望这篇指南能帮助你更好地理解主成分分析及其工作原理。Python 和 R 中的示例旨在让您了解可以使用什么类型的图来解释 PCA 的结果。在 PCA 的实际应用中,您可能会发现自己处理的数据要大得多,可能不如 iris 数据集直观。毕竟,PCA 是一种常用的降维技术。但是具有小的、良好的数据的例子是理解方法元素的好方法。我希望这篇文章能帮助您理解如何使用协方差矩阵、特征值和特征向量来寻找我们的主成分和主成分得分,以及它们如何表示原始数据。
基于 Python 的主成分分析
0.介绍
假设你有一堆数据点,你想从中找出模式。主成分分析是一个工具,可以帮助你做到这一点。它会找到数据中最重要的特征,并减少数据的维数。这意味着它需要大量的数据点,并将它们转化为更容易处理的少量数据点。
在统计学中,主成分分析(PCA)是一种用于降低数据维度的技术。这是一种线性变换形式,其中数据被变换到一个新的坐标系,使得数据在新轴上的任何投影的最大方差出现在第一个轴上,第二个最大方差出现在第二个轴上,依此类推。这种变换由数据的协方差矩阵的特征向量来定义,这些特征向量被称为主分量。换句话说,PCA 是一种找到数据变化最大的方向并将数据投射到这些方向上的方法。
PCA 是一种用于数据分析的强大工具,并被用于各种领域,例如机器学习、图像分析和信号处理。在本文中,我们将对主成分分析做一个简单的介绍,包括它背后的数学概述,以及主成分分析的一些应用。
1.有用的库
NumPy 是 Python 中科学计算的基础库。它用于:
1.数组
2.矩阵
3.线性代数
4.随机数生成
还有更多!

Numpy 橱窗。作者图片由 @carbon_app 创作
Scikit-learn 是一个免费的 Python 机器学习库。它用于:
1.分类
2.回归
3.使聚集
4.降维
5.型号选择
6.预处理
还有更多!

sci kit-学习展示。图片作者由 @carbon_app 创作
2.主成分分析
主成分分析(PCA)是一种用于确定数据集底层结构的统计技术。它通过识别一组相互正交(即垂直)的新轴来实现这一点,并最好地解释数据中的差异。第一个轴解释了最大的方差。第二个轴代表第二大方差,依此类推。
主成分分析常用于降维,即减少数据集中变量的数量,同时尽可能保留变量的过程。这是通过将每个数据点仅投影到前几个主成分上来实现的。
数学上,PCA 是计算数据协方差矩阵的特征向量的过程。协方差矩阵是描述数据集中每个变量的方差的矩阵。特征向量是数据中代表最大方差的方向。方差是数据点围绕平均值的变化量。
有许多不同的方法来计算数据集的主成分。一种流行的方法叫做奇异值分解(SVD)。SVD 是一种矩阵分解技术,它将一个矩阵分解为三个矩阵:
- 左奇异矩阵包含协方差矩阵的特征向量。
- 右奇异矩阵包含数据矩阵的特征向量。
- 对角矩阵包含协方差矩阵的特征值。
SVD 用于计算数据集的主成分,因为它在计算上是高效的,并且因为它可以用于解决与 PCA 相关的许多问题。
PCA 程序可总结如下:
- 将数据居中(即,从每个数据点减去每个变量的平均值)。这是必要的,因为 PCA 是一个方差最大化的过程,并且将数据居中可以确保第一主成分解释最大可能的方差。
1.计算数据的协方差矩阵。
2.计算协方差矩阵的特征向量和特征值。
3.按特征值降序排列特征向量。
4.选择前 k 个特征向量,其中 k 是所需主成分的数量。
5.计算数据在所选特征向量上的投影。
Python 实现
您可以将这些行复制/粘贴到您喜欢的 IDE 中。如果不知道选哪个,我推荐 PyCharm。
下面是我们在上面代码中所做工作的逐步解释:
- 我们生成一些数据。
请注意,我们没有将数据居中,因为平均值已经是 0。
- 我们计算数据的实际协方差矩阵。
- 我们计算协方差矩阵的特征向量和特征值。
- 我们按照特征值降序排列特征向量。
- 我们选择前两个特征向量,并计算数据在所选特征向量上的投影。
看看我们如何在第 35 行计算解释的方差比。等于[ 0.93134786 , 0.06865214 ]。这意味着第一个因素单独解释了我们数据中约 93%的方差,第二个因素仅增加了 6.9%。累积和等于 100%,这意味着两个主成分解释了我们数据中的所有方差。
在某些情况下,您可能希望只保留解释数据中大部分差异的几个组成部分,因为在现实世界的应用程序中,您通常会有太多的有噪声的要素,这些要素对模型的预测能力没有什么帮助。
6 。我们将我们的解决方案与 scikit-learn 进行比较
7.我们绘制数据。

图一。作者图片
在图 1 中,特征向量(主成分)被可视化为按照长度排序的直线。每条线都标有编号,并根据组件类型进行颜色编码。这些成分按照它们在数据中解释的差异量排序。如前所述,两者相互正交。
8.我们从数据中去除最有价值的成分,看看数据会发生什么。

图二。作者图片
这是不言自明的。然后所有数据点被投影到第二个轴上。
9.我们从数据中移除第二大信息量,看看数据会发生什么。

图 3。作者图片
因此,所有数据点都被投影到解释最大差异的第一个轴上。
10.只是为了好玩,我们比较线性回归。
主成分分析(PCA)和线性回归之间有一些相似之处。这两种方法都可以找到数据中的线性关系。然而,有一些重要的区别。最重要的区别是,PCA 找到的是使数据方差最大化的方向,而线性回归找到的是使模型误差最小化的方向(图 4)。
重要的是要记住,PCA 的目的不是找到线性回归模型的最佳预测变量,而是找到数据的底层结构,以降低其维数。相反,线性回归是一种从预测变量的线性组合预测定量响应变量的技术。线性方程的系数是代表每个预测变量对响应变量的影响的回归系数。

图 4。作者图片
4.结论
从代码中我们可以看出,PCA 是一个强大的工具,可以用来发现数据集的底层结构。这也是一种计算效率很高的降维方法。
在本文中,我们看到了如何使用主成分分析来发现数据集的底层结构。我们还看到了如何使用 PCA 进行降维,以及如何选择要保留的组件数量。最后,我们看到了 PCA 与线性回归的关系。
主成分分析:物理直观的数学介绍
原文:https://towardsdatascience.com/principal-component-analysis-pca-8133b02f11bd

亨特·哈里特在 Unsplash 上的照片
主成分分析(PCA)涉及旋转欧几里德空间中的数据点云,使得方差沿着第一轴最大,即所谓的第一主成分。主轴定理确保数据可以以这种方式旋转。在数学术语中,PCA 涉及寻找正交线性坐标变换,或者更一般地,寻找新的基。
PCA 背后的数学在刚体转动的描述中再次被发现。这种物理解释对于理解 PCA 是有指导意义的。
这篇博客文章可以在 github 的 julia 笔记本上找到。
定义参数以生成样本数据
首先,我们将生成一个由欧几里德空间中的 N 个随机分布的数据点组成的云 ℝⁿ ,坐标为 { x⃗⁽ ⁾,x⃗⁽ ⁾,…,x⃗⁽ᴺ⁾ } = X 。我们将基于 3 维度数据来演示这个概念,其中 n=3 和 X ⊂ ℝ,基向量 e⃗₁ 、 e⃗₂ 和 e⃗₃ 以原点 (0,0,0) 为中心。为简单起见,我们将沿基向量 e⃗₃ 的所有坐标设置为零。它将允许我们在 e⃗₁ 和 e⃗₂ 平面上可视化数据。
在我们的例子中,我们用 N=820 个数据点创建了一个数据集。数据中的方差设置为 R1=4.0 和 R2=8.5 ,数据在空间中旋转角度=35.0 。

图一。a)沿 e⃗₂.分布的随机点的直方图 b)欧几里得空间ℝ中随机分布的数据点的云,具有坐标 { x⃗⁽ ⁾,x⃗⁽ ⁾,…,x⃗⁽ᴺ⁾ } = X ,具有基本向量(图 b 中的轴) e⃗₁ 和 e⃗₂ 。b)图中的灰线表示 u⃗₁和 u⃗₂.的主轴线 c)沿 e⃗₁ 分布的随机点的直方图。沿 e⃗₃的所有值都等于零。
转动惯量
刚体的转动惯量 J 也称为转动惯量,决定了围绕旋转轴的期望角加速度所需的扭矩。这取决于物体的质量分布和选定的旋转轴。转动惯量 J 较大的物体需要更大的扭矩来改变物体的转速。对于同一个刚体,不同的旋转轴会有不同的惯性矩。换句话说,它取决于身体的质量分布和选择的轴,更大的力矩需要更大的扭矩来改变身体的转速。
刚体的所有转动惯量都可以用一个矩阵来概括。一般来说,它可以相对于空间中的任何一点来确定。为了简单起见,我们将计算相对于质心的转动惯量。
物体的主轴(也称为图形轴)和主惯性矩可以通过旋转点质量云来找到。在数学术语中,PCA 涉及寻找正交线性坐标变换,或者更一般地,寻找新的基。
最大主惯性矩对应的图形轴是质点分布最大的平面的面积矢量。

图二。六个气缸质量相同,但转动惯量不同 J 。当它们滚下斜坡时,转动惯量越小的圆柱体加速越快。(图片取自维基百科 ))*)
PCA 中主轴和惯性矩的视觉比较
在下面,我们将以两种方式解释上面的随机数据点集合。首先,我们用协方差矩阵 C 将数据点 X 解释为统计分布的数据。二、 X 用惯性矩矩阵 J 表示一个刚体的一组点质量。

图 3。a) PCA:第一主分量 u⃗₁是沿着方差最大的轴,由红色箭头指示 b)惯性矩:图轴 u⃗₂对应于由绿色箭头指示的(实际上第二)最大的主惯性矩。
借助图 1 和 3 的视觉支持,我们预计 PCA 的主轴和惯性矩是相同的。然而,最大主成分和主惯性矩的值对于大多数数据集来说是不同的。
注:在物理学中,惯性矩是针对三维刚体定义的。为简单起见,我们将数据投影到 e⃗₁和 e⃗₂.之间的平面上在我们的例子中,质点分布最大的平面是由 e⃗₁和 e⃗₂.展开的对应于最大力矩的主轴指向沿 e⃗₃的平面外,并与 e⃗₁和 e⃗₂.正交
沿着 u⃗₁的线相当于方差最大的方向。在下文中,我们将通过数学上探索 PCA 和惯性矩来支持我们的视觉理解。
刚体转动惯量的定义
对于一个质量为 mᵢ的刚性物体 N 点在ℝⁿ,转动惯量jt23】由下式给出

它的组成部分由等式定义。(1)作为

方程式(1)
其中 δⱼ,ⱼ' 是克罗内克δ,而 M = ∑ᵢᴺ mᵢ 是总质量。
注:这里,我们用总质量来归一化转动惯量。在物理学中,惯性矩通常不会这样归一化。
走近一看,我们看到 J 与 Jⱼ,ⱼ' = Jⱼ',ⱼ 对称。谱定理告诉我们 J 有实特征值λ,并且可由一个正交矩阵对角化(正交可对角化)。
协方差矩阵的定义
欧几里德空间中以平均值为中心的点云的协方差矩阵 C 由下式给出

它的组成部分由等式定义。(2)作为

方程式(2)
求解特征值问题
PCA 的主轴和惯性矩可以通过旋转空间中的数据点来确定。更准确地说,通过求解特征值问题来计算主分量和轴。
一个实对称矩阵(如 C 和 J )的特征分解为一个旋转矩阵 R 和一个对角矩阵λ的乘积

由j=rλrᵀ给出。旋转矩阵 R 的列定义主轴的方向,常数λ₁、…、λₙ是矩阵λ
的对角元素,称为主矩。
矩阵 J 和 C 的结构是相同的,除了非对角元素的符号。我们将在下面看到,对于 C 和 J ,特征向量将是相同的。另外我们会看到 C 的特征值λ与 J 的特征值是如何关联的。
显示 C 和 J 的特征向量相等
让我们改写一下由 Eq 定义的转动惯量矩阵 J 。(1)就等式中的协方差矩阵 C 而言。(2).

等式 3
其中 I 为单位矩阵。

为了获得 C 的特征向量和特征值,我们通过将 C 分解为一个旋转矩阵 R 和一个对角矩阵λₒᵥ的乘积来解决特征值问题
c=rλₒᵥrt115】ᵀ
其中 R 由特征向量 v⃗ʲ.组成在 3 的情况下——维空间r=【v⃗v⃗v⃗】
和

协方差矩阵 C 的 jᵗʰ特征向量 v⃗ʲ和 jᵗʰ特征值λʲₒᵥ由下式给出

在下面,我们将删除索引\(j\)并将上面的公式重写为 C λₒᵥ = λₒᵥv⃗.乘法等式 Eq。(3)通过右侧的特征向量 v⃗,我们找到等式。(4).

等式 4
情商。(4)暗示 C 和 J 具有相同的特征向量 v⃗.
计算转动惯量矩阵的特征值
C 和 J 的特征向量相同,但特征值不相同。为了将它们联系起来,我们需要注意到

等式 5
其中我们使用了矩阵的迹在循环置换下是不变的,所以

使用 Eq。(4)和情商。(5)我们可以写出特征值λJ的转动惯量 J (Eq。(1))作为

等式 6
我们看到,kᵗʰ特征值λᵏj可以用特征值λₒᵥ.来表示
物理上,我们可以通过考虑沿着例如轴 v⃗的协方差特征值由数据点在 (1) 方向上的分量确定,而围绕轴 (1) 旋转的转动惯量由数据点离该轴的欧几里德距离确定,来获得对这两组特征值之间关系的直观理解。
使用上述数据集 X 计算特征值和特征向量
接下来,我们计算协方差矩阵元素、特征值和特征向量。数据集 X 的协方差矩阵C为

对于变异矩阵 C ,我们求出特征值

和特征向量

使用 Eq。(6)我们可以计算出JJ的特征值λ J

计算 J 的特征值和特征向量
现在,我们计算转动惯量的矩阵元素、特征值和特征向量。数据集 X 的惯性矩矩阵J为

对于转动惯量矩阵 J ,我们求出特征值

和特征向量

在数据点云的两种解释中——第一种是以平均值为中心的点云,第二种是围绕质心旋转的刚体——我们获得了相同的特征向量。

图 4。随机分布的数据点云 X 。覆盖的是缩放的特征向量(显示为红色和绿色箭头)。
接下来,我们使用特征向量和特征值来旋转数据,并在新的基中表示它

我们旋转数据集 X


图 5。随机分布的数据点云 X 由矢量【u⃗₁u⃗₂u⃗₃】所表示的基跨度。灰色箭头表示旧的基矢 e⃗₁和 e⃗₂ 在这个新的基中。
最后的想法
在机器学习和数据科学中,使用 PCA 有两个原因。
首先,一些机器学习算法的准确性和数值稳定性对相关的输入数据很敏感。特别是,执行协方差矩阵反演的机器学习算法可能会遇到奇点问题——高斯混合模型浮现在脑海中。另一个不同的例子是应用随机森林算法来检测不同特征之间的相互作用,其中大的相关性可以掩盖这些相互作用。首先执行 PCA 允许我们梳理相关性的影响,这可以改进特征重要性分析。
第二,PCA 用于降低数据集的维数,例如用于数据压缩。在我们的例子中,我们使用了一个三维数据集 X ,但是 e⃗₃组件没有携带任何信息(通过构造)。我们可以使用主成分分析来证明删除第三维是正确的,因为主成分分析会显示在 e⃗₃方向上的方差是最小的。像这样使用高维数据集到低维空间的投影是处理高维数据和处理[ 维数灾难 ]的有力工具。
对 PCA 的两种应用都进行了广泛的讨论。参见下面的进一步阅读建议。
进一步阅读
- 鲍克哈格、克里斯蒂安&董、天丝;主成分分析教程第一部分:动机 (2018)
- 鲍克哈格、克里斯蒂安&董、天丝;主成分分析教程第二部分:通过惯性矩的主轴 (2018)
- 洪、梁;LOS 检测中“基于转动惯量的方法”和“基于 PCA 的方法”等价性的证明;先进材料研究。945–949.2071–2074 (2014)
- 凯文·墨菲;机器学习的概率视角,麻省理工学院出版社,第 12.2 章(2012 年)
- 斯蒂芬·马斯兰德;从算法角度看机器学习,第二版,Chapman and Hall/CRC (2014)
- Will Badr 为什么特征相关性很重要…很多!博客走向数据科学(2019)
零数学直观解释主成分分析(PCA)
主成分分析(PCA)是数据科学可视化和降维的一个不可或缺的工具,但通常被隐藏在复杂的数学中。至少可以说,我很难理解为什么会这样,这使得我很难欣赏它的全部魅力。
虽然数字对于证明一个概念的有效性很重要,但我相信分享数字背后的故事也同样重要——用一个故事。
什么是 PCA?
主成分分析(PCA)是一种将高维数据转换为低维数据,同时保留尽可能多的信息的技术。

原始三维数据集。红色、蓝色、绿色箭头分别是第一、第二和第三主分量的方向。图片由作者提供。

PCA 后的散点图从三维减少到二维。图片由作者提供。
在处理具有大量特征的数据集时,PCA 非常有用。图像处理、基因组研究等常见应用总是需要处理成千上万的列。
虽然拥有更多的数据总是好的,但有时它们包含了太多的信息,我们将有不可能的长模型训练时间,并且维数灾难开始成为一个问题。有时候,少即是多。
我喜欢把 PCA 比作写一本书的总结。
找时间读一本 1000 页的书是一种奢侈,很少有人能负担得起。如果我们能在两三页内总结出最重要的要点,这样即使是最忙的人也能很容易地理解这些信息,这不是很好吗?在这个过程中,我们可能会丢失一些信息,但至少我们了解了全局。
PCA 是如何工作的?
这是一个两步走的过程。如果我们没有阅读或理解书的内容,我们就不能写一个书的总结。
PCA 的工作方式是一样的——理解,然后总结。
用主成分分析法理解数据
人类通过使用表达性语言来理解故事书的含义。可惜 PCA 不会说英语。它必须通过自己喜欢的语言,数学,在我们的数据中寻找意义。
这个百万美元的问题是…
- PCA 能理解我们数据的哪一部分重要吗?
- 我们能从数学上量化数据中包含的信息量吗?
嗯,方差可以。
方差越大,信息越多。反之亦然。
对于大多数人来说,方差并不是一个陌生的术语。我们在高中学过,方差衡量的是每个点与均值的平均差异程度。

方差公式。
但是它没有把变化和信息联系起来。那么这种联想从何而来呢?为什么会有意义?
假设我们正在和朋友玩猜谜游戏。游戏很简单。我们的朋友会遮住他们的脸,我们需要仅仅根据他们的身高来猜测谁是谁。作为好朋友,我们记得每个人有多高。

记忆中我们朋友的身高。图片由作者提供。
我先来。

三个相同的朋友的剪影,我们需要根据他们的身高差异来识别他们。图片由 7089643 来自 Pixabay ,经作者许可编辑。
毫无疑问,我会说 A 是克里斯,B 是亚历克斯,C 是本。
现在,让我们试着猜一组不同的朋友。

另一组我们铭记于心的朋友身高。图片由作者提供。
轮到你了。

三个同样高的朋友的剪影,我们需要确认他们的身份。图片由 7089643 来自 Pixabay ,经作者许可编辑。
你能猜出谁是谁吗?当他们的身高非常接近时,这很难。
早些时候,我们很容易区分 185 厘米的人和 160 厘米和 145 厘米的人,因为他们的身高差异很大。
同样,当我们的数据具有较高的方差时,它包含更多的信息。这就是为什么我们在同一个句子中不断听到 PCA 和最大方差。我想引用维基百科的一个片段来正式说明这一点。
PCA 被定义为正交线性变换,其将数据变换到新的坐标系,使得数据的某个标量投影的最大方差位于第一坐标(称为第一主分量),第二最大方差位于第二坐标,依此类推。
在 PCA 看来,方差是一种客观的数学方法,可以量化我们数据中的信息量。
差异是信息。
为了说明这一点,我提议再进行一次猜谜游戏,只是这一次,我们要根据身高和体重来猜谁是谁。
第二回合。

同一套朋友和各自的身高体重。图片由作者提供。
一开始,我们只有身高。现在,我们基本上已经将朋友的数据量增加了一倍。这会改变你的猜测策略吗?
这是进入下一节的一个很好的小插曲 PCA 如何总结我们的数据,或者更准确地说,如何降低维数。
使用 PCA 汇总数据
就我个人而言,体重差异很小(也就是一个小的方差),这根本不能帮助我区分我们的朋友。我仍然不得不主要依靠身高来进行猜测。
直观地说,我们刚刚将数据从二维减少到一维。这个想法是,我们可以选择性地保留方差较高的变量,然后忘记方差较低的变量。
但是如果,如果身高和体重有相同的方差呢?这是否意味着我们不能再降低这个数据集的维度?我想用一个样本数据集来说明这一点。

虚线代表身高和体重的差异。图片由作者提供。

为了公平比较,所有特征都被标准化为相同的尺度。图片由作者提供。
在这种情况下,很难选择我们想要删除的变量。如果我丢弃任何一个变量,我们就丢弃了一半的信息。
我们能同时保留和吗?
或许,换个角度。
最好的故事书总是有隐藏的主题,这些主题没有写出来,但是暗示了。单独阅读每一章是没有意义的。但是如果我们全部读完,它给了我们足够的背景来拼凑这些谜题——潜在的情节出现了。
到目前为止,我们只是分别研究了身高和体重的变化。与其限制我们只能选择其中之一,为什么不把它们结合起来呢?
当我们仔细观察我们的数据时,方差的最大值不在 x 轴上,也不在 y 轴上,而是在一条对角线上。第二大方差是一条与第一个方差成 90 度的线。

虚线表示最大方差的方向。图片由作者提供。
为了表示这两条线,PCA 结合了身高和体重来创建两个全新的变量。可能是 30%的身高和 70%的体重,或者 87.2%的身高和 13.8%的体重,或者任何其他组合,这取决于我们现有的数据。
这两个新变量被称为第一主成分(PC1) 和第二主成分(PC2) 。我们可以分别使用 PC1 和 PC2,而不是在两个轴上使用身高和体重。

(左)红色和绿色箭头是原始数据中的主轴。图片由作者提供。| ( 右)主轴的方向已经旋转成为新的 x 轴和 y 轴。图片由作者提供。
在所有的恶作剧之后,让我们再来看看差异。

(左)原始数据中身高体重方差相近。图片由作者提供。| ( 右)PCA 变换后,所有的方差都显示在 PC1 轴上。图片由作者提供。

为了公平比较,所有变量都被标准化为相同的尺度。图片由作者提供。
PC1 可以单独获取身高和体重的总方差。因为 PC1 拥有所有信息,所以您已经知道了该步骤—我们可以轻松地删除 PC2,并且知道我们的新数据仍然代表原始数据。
当涉及到真实数据时,通常情况下,我们不会得到一个主成分来捕捉 100%的方差。执行 PCA 将给出 N 个主成分,其中 N 等于原始数据的维数。从这个主成分列表中,我们通常选择最少数量的主成分来解释我们原始数据的最大数量。
帮助我们做出这个决定的一个很好的视觉辅助工具是 Scree Plot 。

三维数据集的碎石图示例。图片由作者提供。
条形图告诉我们由每个主成分解释的方差的比例。另一方面,叠加的折线图给出了解释方差的累积和,直到第 N 个主成分。理想情况下,我们希望仅用 2 到 3 个分量就能获得至少 90%的方差,以便保留足够的信息,同时我们仍能在图表上可视化我们的数据。
看着图表,我会觉得使用 2 个主要成分很舒服。
逃脱的个人电脑
由于我们没有选择所有的主成分,我们不可避免地会丢失一些信息。但是我们还没有确切地描述我们正在失去什么。让我们用一个新的玩具例子来深入探讨这个问题。

这些点是分散的,但我们仍然可以在对角线上看到一些正相关。图片由作者提供。
如果我们通过主成分分析模型输入数据,它将首先绘制第一个主成分,然后绘制第二个主成分。当我们将原始数据从二维转换到二维时,除了方向之外,一切都保持不变。我们刚刚旋转了数据,使最大方差出现在 PC1 中。这里没什么新鲜的。

(左)虚线是第一和第二主成分的方向。图片由作者提供。| ( 右 ) PCA 旋转数据,因此将最大方差放在 PC1 上,然后是 PC2。图片由作者提供。
然而,假设我们决定只保留第一个主成分,我们必须将所有数据点投影到第一个主成分上,因为我们不再有 y 轴。

(左)虚线是第一和第二主成分的方向。图片由作者提供。| ( 右)现在所有的点都在虚线上,因为我们去掉了第二个主成分。图片由作者提供。
我们会丢失第二个主成分中的距离,下面用红色线突出显示。

所有红线都是第二主成分中的值,它们已被移除。图片由作者提供。
这对每个数据点的感知距离有影响。如果我们查看两个特定点之间的欧几里德距离(也称为成对距离),您会注意到原始数据中的一些点比转换数据中的要远得多。

在(左)和(右)维度之前和之后的两个点之间的成对欧几里德距离的比较从 2 维减少到 1 维。图片由作者提供。
主成分分析是一种线性变换,因此它本身不会改变距离,但是当我们开始移除维度时,距离就会发生扭曲。
事情变得更棘手了——并不是所有成对距离都受到同等影响。
如果我们取最远的两个点,你会看到它们几乎平行于主轴。虽然它们的欧几里得距离仍然是扭曲的,但程度要小得多。

在降维之前(左)和之后(右)这两个点之间的成对欧几里德距离保持相当一致。图片由作者提供。
原因是主成分轴被画在方差最大的方向上。根据定义,当数据点相距较远时,方差会增加。因此,自然地,相距最远的点会更好地与主轴对齐。
总而言之,用 PCA 降维改变了我们数据的距离。它以一种比小的成对距离更好地保持大的成对距离的方式来做到这一点。
这是使用 PCA 降低维数的少数缺点之一,我们需要意识到这一点,尤其是在使用基于欧氏距离的算法时。
有时,在原始数据上运行您的算法可能更有好处。这就是你,一个数据科学家需要根据你的数据和用例做出决定的地方。
毕竟,数据科学既是科学,也是艺术。
在 Python 中实现 PCA
除了本文的前提之外,PCA 还有更多内容。真正领略 PCA 之美的唯一途径就是亲身体验。因此,我很乐意在这里分享一些代码片段给任何想动手的人。完整的代码可以通过 Google Colab 在这里访问。
首先,让我们把导入的东西去掉,并生成一些我们将要使用的数据。
我们的玩具数据集有 3 个变量——x0、x1 和 x2,它们分布在 3 个不同的簇中。“cluster_label”告诉我们数据点属于哪个集群。

玩具示例数据集的前 5 行。图片由作者提供。
只要有可能,将它们形象化总是一个好主意。

三维图表上的玩具数据。图片由作者提供。
数据似乎已准备好进行主成分分析。我们将尝试降低它的维度。幸运的是,Sklearn 使得 PCA 非常容易执行。尽管我们花了 2000 多字来解释 PCA,但我们只需要 3 行代码来运行它。
这里有几个活动部件。当我们将我们的数据拟合到 Sklearn 的 PCA 函数时,它会执行所有繁重的工作来返回 PCA 模型和转换后的数据。
该模型让我们获得了众多的属性,如特征值,特征向量,原始数据的平均值,方差解释,等等。如果我们想了解 PCA 对我们的数据做了什么,这些非常有见地。
我想强调的一个属性是pca.explained_variance_ratio_,它告诉我们由每个主成分解释的方差的比例。我们可以用碎石图来想象这一点。

叠加在条形图上的线形图,显示每台电脑的差异比例。图片由作者提供。
图表告诉我们,使用 2 个主成分而不是 3 个主成分很好,因为它们可以捕捉 90%以上的方差。
除此之外,我们还可以看看用pca.components_**2创建每个主成分的变量组合。我们可以用热图来展示这一点。

每台电脑都是由多个变量组合而成的。图片由作者提供。
在我们的示例中,我们可以看到 PCA1 由 34%的 x0、30%的 x1 和 36%的 x2 组成。PCA2 主要受 x1 支配。
Sklearn 提供了许多更有用的属性。对于感兴趣的人,我推荐看看 Sklearn 文档上 PCA 的属性部分。
现在我们对主成分有了更好的理解,我们可以最终决定我们想要保留的主成分的数量。在这种情况下,我觉得 2 个主成分就足够了。
因此,我们可以重新运行 PCA 模型,但是这次使用了n_components=2参数,它告诉 PCA 只为我们保留前 2 个主成分。
这将返回给我们一个带有前两个主要成分的数据帧。最后,我们可以绘制一个散点图来可视化我们的数据。

(左)原始数据。图片由作者提供。| ( 右)相同的数据,但用 PCA 简化为 2-D。图片由作者提供。
结束语
PCA 是一个数学上美丽的概念,我希望我能够以一种随意的语气来表达它,这样就不会感到势不可挡。对于那些渴望了解本质细节的人,我在下面附上了一些有趣的讨论/资源供你阅读。
谢谢你的时间,并有一个伟大的一天。
[1]:中等,法尔哈德·马利克(2019 年 1 月 7 日)。什么是特征值和特征向量? https://medium . com/fintech explained/what-are-特征值和特征向量-机器学习的必备概念-80d0fd330e47
[2]: GitHub。深入:主成分分析 https://jakevdp . github . io/python datascience handbook/05.09-Principal-Component-Analysis . html
[3]: StackExchange,whuber(2013 年 2 月 21 日)。在进行主成分分析之前,是否应该去除高度相关的变量? https://stats . stack exchange . com/questions/50537/should-one-remove-high-correlated-variables-before-do-PCA
[4]: StackExchange,ttnphns(2017 年 4 月 13 日)。 PCA 和方差比例解释https://stats . stack exchange . com/questions/22569/PCA-和-方差比例解释
[5]: StackExchange,阿米巴原虫(2017 . 04 . 13)。PCA 仅保留大的成对距离是什么意思? https://stats . stack exchange . com/questions/176672/what-is-mean-by-PCA-preserving-only-large-pair-distance
[6]: StackExchange,阿米巴原虫(2015 年 3 月 6 日)。主成分分析的意义,特征向量&特征值 https://stats . stack exchange . com/questions/2691/Making-sense-of-main-component-analysis-特征向量-特征值/140579#140579
主成分回归——明确解释并实施
基于主成分分析(PCA)的回归技术的概念和 Python 实现

约翰·安维克在 Unsplash 上的照片
主成分分析(PCA)是一种众所周知的降维技术,但是您知道吗,我们也可以在回归分析中应用 PCA 背后的概念?
本文提供了对主成分回归 (PCR)的清晰解释,包括它的理论概念、好处、注意事项和 Python 实现。
内容
(1)理论概念
主成分回归(PCR)是一种回归技术,其目标与标准线性回归相同,即模拟目标变量和预测变量之间的关系。
不同的是,PCR 使用主成分作为回归分析的预测变量,而不是原始特征。
PCR 分三步进行:
- 应用 PCA 从预测变量生成主成分,主成分的数量与原始特征的数量相匹配 p
- 保留解释大部分方差的第一个 k 主成分(其中k<p),其中 k 由交叉验证确定
- 在这些 k 主成分上拟合线性回归模型(使用普通最小二乘法)
这个想法是,较少数量的主成分代表数据中的大部分可变性和(假定的)与目标变量的关系。因此,我们不是使用所有的原始特征进行回归,而是仅利用主成分的子集。
尽管与目标变量的关系的假设并不总是成立的,但它通常是产生良好结果的足够合理的近似。

(2)益处和注意事项
利益
- PCR 在 k 主成分而不是所有原始特征上拟合线性回归模型,从而帮助减少过拟合。理论上,这比基于所有原始特征训练的标准线性回归模型具有更好的性能。
- PCR 通过移除与小特征值相关的主成分来帮助消除数据中的多重共线性。
- 在具有许多特征(高基数)和显著多重共线性的数据中,性能提升更为显著。
警告
- PCR不被认为是特征选择方法,因为回归中使用的结果主成分是原始特征的线性组合。这意味着我们仍然依赖于所有的原始特性,而不是它们的子集。
- 由于在模型中使用了主成分,与原始特征相比,这些预测器失去了它们的“解释能力”。
- PCA 变换是无监督的,这意味着在确定主成分时,它不而不是考虑目标变量。因此,不能保证方差最大的主成分将是预测目标的最佳主成分。具有高预测能力但低方差的方向有可能被忽略,从而导致较差的模型性能。
(3) Python 实现
现在让我们一步一步地看一下在 Python 中应用 PCR 的指南。在这个项目中,我们将使用传统的葡萄酒质量数据集 ,因为它容易获得,简单,并且是数字格式的。
目标是预测葡萄酒质量,作为回归任务的一部分,使用诸如酒精含量和酸度等葡萄酒特性。
你可以参考这个 GitHub repo 的 Jupyter 笔记本 中的代码跟随。
https://kennethleungty.medium.com/membership
步骤 1 —初始设置
我们首先导入所有必要的库和数据,然后执行适当的训练测试分割。原始数据集有十个预测变量和一个目标变量()。

原始葡萄酒质量数据集样本|作者图片
步骤 2 —标准化功能
在运行 PCR 之前,至关重要的是在生成主要成分之前将每个原始特征标准化到相同的比例。
原因是主成分计算是基于方差,方差受特征的范围影响。
如果不应用标准化,则范围较大(因此方差较大)的特征将占主导地位,并在生成的主成分中扮演不公平的重要角色。
我们使用 sklearn .预处理 中的**scale()**方法对原始特征进行标准化,即中心到均值,刻度到单位方差。
步骤 3 —运行基线回归模型
为了评估 PCR 模型的性能,我们需要有基准来比较。因此,我们运行三个基线模型(标准线性回归、套索回归和岭回归)并保存 RMSE 分数。
特别是,我们希望保存以下内容:
- 训练集中的 10 重交叉验证 RMSE
- 在测试集上预测 RMSE
步骤 4 —生成主成分
原始特征的主要成分可以使用PCA()从sk learn . decomposition中生成。
得到的 11 个主成分的预览如下所示,第一个主成分(包含最多信息)位于最左栏。

主成分值预览|作者图片
我们还可以使用pca.explained_variance_ratio_查看每个主成分的解释方差。**

解释了所有主要成分的差异|图片由作者提供
步骤 5-确定主成分的数量
由于我们不想使用所有的主成分,现在的问题是我们应该使用多少主成分进行回归分析。**
主成分的数量( k )通常由
交叉验证和视觉分析确定。
值 k 本质上是一个我们需要调优的超参数。我们迭代越来越多的主成分,以纳入回归建模和评估结果 RMSE 评分。
下一个任务是检查训练集交叉验证 RMSE* 与所用主成分数量的关系图:***

RMSE 与主成分数的关系图|图片由作者提供
我们看到,PCR 的训练集性能随着主成分的增加而提高(即 RMSE 降低),这与我们的预期一致。
绿线是使用所有原始特征的基线标准线性回归模型的 RMSE 基准。
该图显示,当存在 M=9 主成分时,出现最低交叉验证 RMSE(图中的最小点)。实际上, M=9 处的 RMSE 略低于绿线。**
注:如果我们想要更少的元件,同时保持相对相似的性能,这里选择M = 7也是合理的选择。
步骤 6 —用最佳数量的主成分运行 PCR
在确定要使用的主成分的最佳数量(即 M=9 )之后,我们继续在我们的测试数据集上运行 PCR。
首先,这是通过在训练集的前 9 个主成分上训练新的线性回归模型来完成的。然后,我们使用这个训练好的模型对测试集的前 9 个主成分进行预测。**
我们也可以重复上述步骤来评估在 M=7 主成分上训练的 PCR 模型的性能。
第 7 步—评估和解释结果
下表显示了来自各种模型的 RMSE 结果,包括使用不同数量的主成分训练的两个 PCR 模型。

回归模型的 RMSE 结果。RMSE 越低,模型越好|作者图片
我们看到,具有 9 个主成分的 PCR与基于所有 11 个原始特征训练的三个基线回归模型表现相当。****
具有 7 个成分的 PCR 的性能稍差是因为需要更多的主成分来充分模拟该数据中的目标。
值得记住的是,在最初的几个主成分捕获了预测因子的大部分变化以及与目标的关系的情况下,PCR 表现得更好。
包装东西
主成分回归被称为回归分析的降维方法,因为它减少了回归建模中要估计的系数数量。**
虽然与标准线性回归相比,它可能会导致模型性能的提高,但有几个注意事项需要考虑,我们在本文中对此进行了探讨。
要查看这个项目的 Python 代码,欢迎你来探索这个 GitHub repo 。
在你走之前
欢迎您加入我的数据科学学习之旅。跟随此媒体页面并查看我的 GitHub 以了解实用和教育数据科学内容。同时,享受应用主成分回归的乐趣!**
*** https://kennethleungty.medium.com/membership [## 通过我的推荐链接加入媒体-梁建华
作为一个媒体会员,你的会员费的一部分会给你阅读的作家,你可以完全接触到每一个故事…](https://kennethleungty.medium.com/membership)***
人工智能中的先验知识——真的是“作弊”吗?
原文:https://towardsdatascience.com/prior-knowledge-in-ai-is-it-really-cheating-229604179e7f
为什么先验知识对人工智能系统如此重要
让我们从一种视错觉开始:著名的灰色棋盘错觉,经常被作为愚弄我们大脑和视觉处理系统的视错觉的一个例子。我想把重点放在灰色棋盘的一个特别的错觉上,这个错觉很流行,涉及到颜色和光照。

我想问你一个简单的问题:正方形 A 和正方形 B 是同一个色调吗?显然不是!正方形 A 和 B 肯定是灰色的,但是 A 的色调明显比 B 暗…对吧?

等一下…通过用灰色的桥连接正方形来去除阴影…为什么它们现在看起来是同样的色调?到底发生了什么事?!
大脑是如何得出结论的?
为什么我们的大脑如此迅速地察觉到这两种色调是不同的,而去除阴影后发现色调强度值实际上是相同的?这可以归结为理解我们的大脑如何得出主要结论——我们的大脑如何推断出结论。
AGI——人工智能——至少在 20 世纪 60 年代和 70 年代,或许现在也是这样——的希望是尝试模仿人类的智能,以及我们大脑强大的推断、概括和推理能力,并将其与计算机非凡的计算能力相结合。因此,我们应该首先尝试理解我们的大脑是如何得出主要结论的。

为了理解这种错觉,我们需要意识到当我们看着棋盘错觉时,我们的大脑同时无意识地判断两种假设:
H1:方块 B 和方块 A 真的是不同的色调——只是在阴影中看到的
H2:正方形 B 和正方形 A 的色调相同——只是在灯光下看。
现在,让我们将变量 D 视为数据——在这种情况下,这里的“数据”是到达我们眼睛并记录在那里的两个中心方块的像素颜色强度值。原来,我们的眼睛有点下意识地“知道”两个正方形的像素强度是相同的!根据你的观察,数据同样符合 H1 和 H2 的假设。但是你的眼睛并不是全部——最终你的大脑必须理解这些测量结果。这就是幻觉发生的地方。
看看上面的两张图片:你注意到它们有什么不同吗?它不是棋盘,而是围绕在它后面的东西——圆柱体的影子。你的大脑有先验知识(来自日常经验,或者也许天生来自进化)关于光如何产生阴影,以及阴影如何影响颜色并使它们的表观色调变暗。
如果我们的大脑同意这种一般的阴影假设,那么正面上的正方形 B 实际上可能与正方形 A 具有相同的灰色色调的唯一方式是,如果只有正方形 B 被光照亮——一个被物体的阴影包围的单一细长光源。在过去的经历中,你的大脑见过影子“包围”一个细长光源的例子吗?大概…..不是。
所以最终,你的大脑判断 H1 是正确的——正方形 B 与正方形 A 的色调确实不同——因为我们的大脑在最终的决定中结合了先前阴影的三维背景。
但是等等!两种颜色仍然是灰色的色调!
是的,他们是——这就是为什么这是一个错觉。
如果没有关于阴影是如何产生并影响颜色的先验知识,H1 和H2 这两个假设都同样有效,所以你的大脑会一直困惑,因为它无法区分这两者并决定一个特定的假设。这就是幻觉发生的原因;你的眼睛无意识地将 H1 和 H2 视为同等有效,但你的大脑无意识地加入了先前的知识。只有在第二张图中的大揭示之后,你才会有意识地被掩盖的阴影所迷惑,以揭示色调强度。
那又怎样?我们的大脑会一直欺骗我们吗?一切都是幻觉吗?
可能吧。
事实上,这就是西蒙斯神经科学研究所的计算神经科学家奥尔森教授的观点。顺便说一下,在这个精彩的短视频中,他带领观众通过一个非常相似的错觉,但使用了著名的魔方,并解释了我们的大脑如何根据他所谓的“一般阴影先验”结合我们眼睛的视觉测量,以一种优雅的推理来推断颜色。关键是,我们大脑的感知通常是主观的,取决于我们在经验上建立的先验,以及我们的大脑如何将先验与眼睛的视觉处理结合起来。不同的人有不同的个人经历,一些人的推理机制的工作方式与其他人不同。
好吧,但是这和人工智能有什么关系呢?这是你在标题里写的。到目前为止,你只告诉了我关于大脑的事情!肉≠电子。
(关键是推断!将数据与背景知识相结合)

因为你的大脑用来推断颜色的概率推理类型类似于贝叶斯推理——当对世界建模、做出决定、行动和计划时,大脑可能在不同的抽象层次上近似这种类型的计算和推理。
贝叶斯推理可能听起来像一个新概念,因为它在数据科学和人工智能领域得到了更多的媒体报道,但在人工智能的整个历史中,它一直是人工智能领域的关键角色。事实上,艾伦·图灵——人工智能领域的创始人之一——早在 20 世纪 40 年代,就写了大量关于概率推理的文章。图灵在第二次世界大战期间的助手 I . j . Good在 1979 年写了一本关于他与图灵的经历的传记,并提到图灵引入了“贝叶斯因素的想法”,并且是“使用贝叶斯推理的顺序分析的独立发明者”之一。
那么你那篇关于先验知识和“欺骗”的文章的标题是怎么回事?
嗯,与艾伦·图灵在 20 世纪 50 年代的时代不同,21 世纪的人工智能由深度学习主导——顺便说一下,深度学习是指神经网络中的层数——不一定是关于神经网络能够如何“抽象”概念和对象。
但是深度学习的一个主要问题是——这也适用于逻辑回归和许多其他算法(我只关注深度学习,因为它已经成为人工智能的“面孔”)——它通常完全由数据驱动。它通常不包含先验知识。这些类型的模型被称为“监督模型”,通常会摒弃先验知识,尝试完全从零开始学习物体识别或自然语言理解。
有没有想过为什么要从零开始训练深度学习模型(我说的不是预先训练好的模型!)如果没有专用的 GPU 芯片就在笔记本电脑上拍 forever?因为在没有关于世界的先验知识的情况下,像 GPT-3 这样的系统实际上需要数十亿的数据来训练和执行关于我们世界中自然语言的推理。
也许先验知识在人工智能中被视为“作弊”,因为否则机器就不会“通过例子学习”了?毕竟,机器学习的最初定义是关于机器通过例子学习,而不是显式编程。如果你把先验知识注入机器,那不是显式地给它编程吗?
为什么先验知识对 AI 中的推理如此重要?
但是自然语言处理人工智能的“国王”之一 GPT-3 的一些错误——比如 GPT-3 说铅笔比烤面包机重——表明 GPT-3 对这个世界没有先验知识。这些人工智能系统都不理解语言将我们头脑中的概念与现实世界联系起来。为了推断烤面包机更重,你需要事先了解烤面包机比铅笔更重这一事实。
凯文·拉克尔在他的博客上给 GPT-3 做了一个图灵测试,它在许多问题上表现得出奇的好!不过,最终凯文能够通过问一些人类永远不会想到的常识性问题来难倒人工智能——因为我们的大脑已经能够利用先验知识和进化给我们的多年推理技术来推断这些答案——这就是为什么这些问题看起来“显而易见”而且平淡无奇。这里有一个问题:
Q: How many eyes does my foot have?
A: Your foot has two eyes.
人类永远不会犯这样的错误,因为我们有关于世界和人类脚的先验知识。但是 GPT-3 没有先验知识——它只知道它被训练过的网站——一个非常奇特、复杂的统计搜索引擎。凯文尝试了更多的问题:
Q: How many eyes does a blade of grass have?
A: A blade of grass has one eye.Q: How many rainbows does it take to jump from Hawaii to seventeen?
A: It takes two rainbows to jump from Hawaii to seventeen.
他提到,这些“平凡”的问题是你在网上文档中永远看不到的——因为人们不会在网上文档中谈论这些。我们从日常知识和常识中学习和推断这些。
常识推理:先验知识在哪里闪耀

Robina Weermeijer 在 Unsplash 上拍摄的照片
如果你仍然认为机器学习/人工智能系统中的先验知识可能看起来像“作弊”,让我们看看最后一个问题/答案对。
我们先前的知识是夏威夷是一个地点;一块土地。GPT 3 号的记忆中可能没有这种类型的抽象层次。人类从先前的知识中也知道 17 是一个数字,而不是是一个位置。而且,从先前的知识——来自我们对物理世界的经验——你不能从一个地点旅行到一个数字。不是因为我们被教导使用输入-输出的例子不起作用,而是因为物理上的,我们的先验告诉我们这是不合理的。最后,我们从以前的经验中知道,你不能站在彩虹上或跳到彩虹上——因为彩虹不是人类可以与之互动的物体。GPT-3 对此一无所知——没有前科。它对物理世界没有任何经验来回答这类问题。它不能回答:“这是一个不合理的问题!”
好吧,但是没有人把先验知识注入我们的大脑……?
啊…你,读者,说对了一半。我们的许多先验知识来自于我们的大脑和身体与物质世界的互动。但是人工智能目前还不能做到这一点!我们表面上让人工智能系统从世界学习的唯一方法是 1)创造一个机器人与我们的物理现实互动,或者 2)创造一个虚拟现实(类似于我们的)供人工智能互动。
还有一点:你说我们没有先验知识是错误的。之后的研究科学研究揭示了大脑具有光照先验假设,例如,当视觉数据不足时,帮助我们的眼睛和大脑在模糊的 3D 照明场景中做出决策。光照先验…从一开始的棋盘错觉中想起什么了吗?
甚至在深度学习领域,人工智能从业者已经开始向神经网络的权重中注入先验知识,这似乎与人类可以轻松完成的常识推理相斗争。【2018 年的这项研究提到了在深度神经网络中对权重进行先验分布如何帮助“调整”网络,防止它过度拟合数据。另一篇来自 2020 年的文章谈到了在为视频和音频分类而训练的深度神经网络上注入严格的先验如何帮助提供“可靠的不确定性估计”使用权重的先验假设已经开始导致更基础的深度学习理论,涉及一个称为“贝叶斯高斯过程”的主题。如果你有兴趣,可以看看这篇来自 2017 的重要研究论文。
我希望我已经能够让你,读者,对人工智能中的先验知识的想法感到温暖,并展示它在常识推理和对目标和计划的灵活推理等任务中是如何极其有用,这将是极其重要的——不仅仅是为了创造人工一般智能(AGI)——而是为了创造人工智能系统和机器人,以便在日常生活中与人类互动。我谦卑地相信,拥有常识性推理和灵活的推断能力将有助于人工智能系统在处理人类数据时做出更好的决定,并有助于阻止人工智能的一些歧视性判断。
B iased 数据会一直存在,因为我们生活在一个不平等的世界。但是,如果我们给我们的人工智能注入先验知识,并赋予它用常识进行推理的能力,并使其目标与人类平等、公平、合作、集体正义的目标相一致——那些人工智能系统将更好地与我们的世界互动。
参考文献
- 很好,I. J . "研究概率和统计的历史。XXXVII A. M .图灵的二战统计工作。”《生物信息学》,第 66 卷,第 2 期,[牛津大学出版社,生物信息学信托],1979 年,第 393-96 页,https://doi.org/10.2307/2335677.**
结合联邦学习和联邦平均算法的隐私保护深度学习
利用保护隐私的人工智能构建您的竞争优势——在不了解任何人的情况下向任何人学习——联合学习——数据科学

介绍
苹果最近宣布其 iPhone 收入同比增长 47%,2021 年第二季度售出 4926 万部智能手机[1]。对于 AAPL 持有者来说,这绝对是个好消息。但是对于我们这些数据科学和 AI 爱好者来说,这意味着什么呢?
好吧,让我们在这里做一些计算。平均而言,每个人每秒产生大约 1.7 兆字节的数据[2],人们通常每天花大约 3 个小时在手机上。这意味着 iPhones 每天产生 848.46 PB(PB)的数据,比脸书每天产生的数据量(约 4 PB)大 200 倍。请记住,这只是仅来自 iPhones 的数据量。
虽然这些数字并不精确,但您现在会了解这些数据有多大,以及为什么这些数据会成为大数据的新来源。正如您可能看到的,这种丰富的数据源超过了任何一个组织所能容纳的信息量。这些数据可用于创建个性化体验,有助于提高用户对服务或应用的满意度。因此,世界各地的大组织都在试图利用这种新资源来增加现有产品的附加值,或者围绕它们创建新的商业模式。

然而,使用这种类型的信息会带来许多挑战。与由组织集中存储和管理的传统大数据源不同,移动设备上的数据通常是大规模分布的,由用户而不是服务提供商拥有。由于用户拥有这些数据,出于隐私考虑,他们有权在自己的设备上保持这些数据的私密性,因此,这是希望利用这些数据的服务提供商面临的最大挑战。一方面,企业需要更多数据来更好地为客户服务,但另一方面,私有数据无法共享。
拉斯维加斯 CES 2019 上的苹果广告牌
处理隐私和敏感信息并不容易,解决这个问题最简单的方法就是不要接触它。你宁愿不使用这些数据在服务中添加新功能,也不愿侵犯用户隐私,这绝对是一个有效的观点。然而,如果你的竞争对手能够以某种方式利用这些数据构建人工智能应用,在不违反任何隐私限制的情况下提供更好的个性化服务,那么他们将在你失去竞争优势的同时获得竞争优势。实现这种能力的技术将是一种颠覆性的技术。
现在觉得服气了?让我们继续读下去!
好吧,那么现在你想在这个数据金矿的基础上建立一个人工智能模型,而不把它们从用户的设备中取出来。当您考虑数据隐私和相关保护时,加密是最流行的方法之一,其中数据可以用用户的私钥加密,然后发送到服务器。然而,加密也意味着你不能再访问信息,因为你没有用户的私钥。如果你不能把金子放进石头里,石头就只是石头。
解决方案确实比你想象的要简单。您可以将模型训练发送到用户的设备上,而不是将所有数据加载到服务器上进行模型训练,这些数据属于用户的设备,并且用户可以完全控制这些数据。这是联邦学习的核心思想。
联合学习
联合学习是人工智能系统架构的一种新范式,它本质上是在客户端设备上训练模型,因此不会发送任何私人数据。联合学习的训练过程通常包括四个步骤:
- 步骤 1: 开始在参与联合学习的所有设备上使用它们的本地数据训练模型。
- 步骤 2: 设备将它们本地训练的模型发送到服务器。请注意,上传的是模型,而不是私有数据。没有私人数据会离开设备。
- 步骤 3: 服务器通过使用聚合算法来组合从设备接收的所有模型。研究人员开发了许多聚合算法,但最受欢迎的算法是 2017 年由 Google 发布的联邦平均算法。
- 步骤 4: 服务器将聚合模型分发给所有设备。然后,聚集的模型可以用于提供 AI 功能,或者为下一轮训练做好准备(如果需要)。

联合学习过程(由我创建的图)
在不了解任何人的情况下向每个人学习
聚集算法在联合学习中起着重要的作用,因为它负责组合来自所有设备的知识,而不知道用户的私人数据。在本文中,我将只解释联邦平均(Federated Averaging,FedAvg ),因为它是一种简单而有效的算法,正被用于 Google 产品的生产中(尽管我正在研究一种可能击败 FedAvg 性能的新算法)。
联邦平均算法通过对所有模型更新进行加权平均来生成聚合模型。为了帮助您更好地理解这一点,我将提供一个忽略算法的“加权”部分的过于简化的示例。给定一个有三个设备的系统,下面是它们的模型是如何聚合的。
- Device A sends model A with value **0.6** to the sever
- Device B sends model B with value **0.1** to the sever
- Device C sends model C with value **0.2** to the sever
- Server takes the average of these three values: **(0.6 + 0.1 + 0.2)/3 =** **0.3** - Server sends aggregated model with value **0.3** to A, B and C
就是这样!算法很简单,但把它们放在一起,这个想法已经被谷歌证明是“可生产的”,当他们在数十亿台 Android 设备上使用它作为他们的谷歌键盘产品的下一个单词预测功能时。他们的自然语言处理(NLP)模型超级准确,因为它从来自多种语言和文化的数十亿用户那里学习。但是,这样做的好处是不会收集任何输入数据,并且尊重用户隐私。这是您可能想要的数据产品的竞争优势。
你可以看看谷歌首席执行官桑德尔·皮帅在这段视频中对联合学习的介绍:
谷歌主题演讲—联合学习—谷歌 I/O 2019
应用和挑战
联合学习是一种新兴技术,由于其巨大的潜力,被世界各地的许多组织采用、研究和开发。人们可以使用联合学习为医院建立一个超级强大的诊断人工智能模型,同时保留患者的隐私。人们还可以利用街道上真实司机的驾驶行为来训练自动驾驶汽车。还可以建立基于个人数据的个性化推荐系统,以推荐与客户需求精确匹配的产品或服务(目前大多数推荐系统都是以产品为中心,而不是以客户为中心)。
就这项技术的成熟度而言,谷歌已经在他们的产品中使用了一段时间。此外,有许多由大公司支持的联邦学习框架,以便您在采用这项技术时不会从头开始。我自己试过的两个框架是 Intel 的 OpenFL 和 Google 的 TensorFlow Federated。这些框架提供了易于使用的编程接口,因此您的数据科学家只需一小段代码就可以构建和部署他们的第一个联邦学习应用程序。
每一个伟大的机遇都伴随着巨大的挑战。
然而,最大的挑战通常不是来自技术本身,而是来自实现技术解决方案的产品的总体设计。
当谷歌和苹果在 Android 和 iOS 中应用一项新技术时,默认情况下,你是作为用户选择加入的。他们从来不会问你这样的问题:“你能让我们访问你的隐私和敏感数据以帮助改善你的体验吗?我们保证不会将您的数据发送出去”。当你看到这个弹出窗口的时候,我猜你会立刻按下“不,谢谢”。

我的 iPhone 截图
由于谷歌和苹果拥有操作系统,用户没有选择,除非他们停止使用设备。然而,作为一家在操作系统上开发应用程序的公司,你没有这种奢侈。每次应用程序需要访问用户的数据时,即使数据不会上传到其他地方,它仍然需要特别请求许可。想象一下,如果你必须向你的客户解释“我们只发送模型”部分以获得他们的许可,你会意识到这不仅是不可能的,而且你的组织的声誉可能会受到影响,因为你听起来像一个间谍公司,除非你像脸书那样做(你知道我的意思)。否则,你的产品或服务必须有足够的价值,让用户愿意“妥协”他们的数据,即使你的意图是将数据保存在他们的设备上。当我使用“妥协”这个词时,我表达的是用户的想法,而不是服务提供商的意图。例如,每个人都在使用脸书时做出妥协,因为他们都知道它正在收集他们的数据,但他们仍然继续使用它。
虽然客户的同意对 B2C 来说是一个挑战,但对 B2B 来说是一个更容易解决的问题。两家或两家以上的公司可以就共享数据达成协议,这对各方都有利,事实上,这种方法目前相当流行。现在有了联合学习,他们甚至可以共享数据,而不用实际发送出去。例如,互联网服务提供商(ISP)可以与银行交换其用户的互联网流量数据,以构建一些高级人工智能应用程序,为双方带来价值。
ISP 可以使用自己的数据构建需求预测模型,这些数据由来自银行的交易数据丰富。一个家庭的支出和他们的互联网使用之间可能有联系。这方面的一个例子可能是,一个家庭刚刚访问了一家家庭娱乐零售店(销售平板电脑、笔记本电脑和游戏机的商店),并支付了价值约 3000 美元的款项。这也可能意味着家庭现在有更多的智能设备,因此他们将需要更多的互联网带宽。预测模型可以帮助 ISP 向客户提供更好的计划,并在基础设施规划中更加主动。
另一方面,银行可以利用 ISP 提供的互联网流量数据丰富的交易数据,为其机构客户(例如零售店)建立更好的现金流预测模型。特定网站的流量和拥有该网站的公司下周的收入之间可能存在相关性。例如,ISP 报告本周地址www.theretailstore.com的流量增加,银行可以将此信息与该零售店的历史交易数据结合起来,预测下周的收入,因为客户可能会在购买产品之前在网站上寻找信息。
我可以在这里列出更多的应用和想法,潜力是无限的。因此,无论是 B2B 还是 B2C,您可能都需要考虑您将从以前由于隐私问题而无法访问的数据中获得的价值,并投资于这项技术,以便获得与竞争对手相比的竞争优势。每一个巨大的机会都伴随着巨大的挑战,那些能够管理这些挑战所带来的风险的人将会获得机会并扰乱市场。
参考
- 苹果销量不及预期,蒂姆·库克称供应问题让公司损失 60 亿美元
- 2021 年每天创造多少数据?
- 联合学习:没有集中训练数据的协作机器学习
- 与谷歌的联合学习
- 英特尔开放式联合学习
- TensorFlow Federated:对分散数据的机器学习
- 从分散数据进行深度网络的通信高效学习
基于强化学习的隐私保护推荐器
将隐私与强化学习相结合

Pic 鸣谢:探索未知,作者 Soma Biswas (Flickr: 链接,经允许转载)
介绍
在本文中,我们主要关注在对话环境中实现推荐。更具体地说,我们考虑一个能够与用户对话的应用程序,向用户提供有趣和相关的信息;(按需)响应特定的用户查询,或者以及时和相关的警报形式主动提供。
互动个性化:虽然今天的聊天机器人能够根据用户情绪来个性化对话,但这种情绪通常来自用户的查询/响应——通常由用户明确提供的用户反馈。这导致了对提供这种反馈的用户的依赖,并意味着代表用户的额外努力。此外,反馈是非常个人化的,由用户特别提供,而不考虑周围环境、位置(家、办公室、市场……)、附近的其他家庭成员等。
这篇文章旨在通过提供基于隐含用户反馈、适应历史和环境背景的高度个性化和交互式体验来改善这一方面。
- 隐式反馈:基于用户提供的显式反馈(例如,用户响应、NPS 分数/评级)和隐式反馈(例如,在接收到聊天机器人响应/通知时面部表情的变化)来计算用户情绪
- 环境背景:第二个区别点基于我们考虑用户环境(周围)因素的事实,例如位置(家庭、办公室、市场……)、温度、照明、一天中的时间、天气、附近的其他家庭成员等等;根据用户/环境情绪调整对话的参数。
- 对话历史:任何用户反馈都需要与迄今为止用户对对话的反应相结合。这使得我们可以忽略用户反馈/情绪的任何突然变化,这可能是由于外部因素造成的,与应用程序/聊天机器人的交互无关。
隐私保护交互:捕捉用户情感,尤其是。环境方面,需要高度的知识规。用户上下文。因此,我们需要以保护隐私的方式执行此操作,以充分解决用户隐私问题,同时提供高度个性化的体验。
本文通过向核心聊天机器人和强化学习(RL)框架添加隐私层来实现这一点。
RL 是我们用来使对话实时适应用户/环境上下文的核心技术。
相对于当前技术水平,此处的主要优势是:
从聊天机器人的角度来看,我们展示了用户反馈(包括环境参数)和对话历史如何以隐私保护的方式作为参数来适应/个性化用户对话。
从 RL 框架的角度,我们展示了 RL 奖励计算是如何分布的,从而在提供奖励函数的准确表示的同时保护用户隐私。
背景
建议:拥有一个由有趣的文章、事实、数据和视频组成的知识库,其中的文章根据他们最可能感兴趣的用户资料进行分类。然后向用户提供最相关文章的基于简档的提醒/通知。
聊天机器人:以上是一次性互动的经典例子,虽然个性化;错过互动部分。聊天机器人可以克服这个限制。今天的企业聊天机器人是基于 FAQ 的[1,8]。这种聊天机器人的核心是一个意图识别自然语言理解(NLU)引擎,它通过问题变体的硬编码示例进行训练。当没有符合 30%以上置信度的意图时,聊天机器人会返回一个后备答案。对于所有其他情况,NLU 引擎会随响应一起返回相应的可信度。高级聊天机器人还会考虑嵌入在查询/响应中的用户情绪,以相应地调整响应。
强化学习(RL) : RL 指的是 AI/ML 的一个分支,针对目标导向的问题。RL 算法能够通过在许多步骤上最大化奖励函数来实现复杂的目标,例如在许多步骤上在游戏中赢得的分数。奖励功能的工作原理类似于用糖果和打屁股来激励孩子,这样算法在做出错误决定时会受到惩罚,在做出正确决定时会受到奖励——这就是强化。RL 算法如下图所示——图 1。有关 RL 框架的详细介绍,感兴趣的读者可以参考[2]。

图 1:强化学习(作者图片)
基于 RL 的推荐解决方案架构
在这一节中,我们概述了基于隐式用户反馈实现个性化交互的解决方案架构,接下来是下一节中的隐私保护扩展。
图 2 中的架构示出了包括隐式用户反馈的用户和环境条件如何
1.使用可用的传感器收集以计算“当前”反馈,包括环境背景(例如,用户的网络摄像头照片可用于推断用户对聊天机器人响应/通知的情绪、房间照明条件和附近的其他用户)
2.其然后与用户对话历史相结合,以量化用户“情感曲线”,并忽略由于不相关因素引起的情感的任何突然变化
3.导致与提供给用户的最后聊天机器人响应/应用通知相对应的合计奖励值。
然后,该奖励值被反馈给 RL 代理,以便 RL 策略从知识库中选择下一个最佳聊天机器人响应/应用程序通知。
这导致了分布式 RL 奖励计算,据我们所知,这在以前的文献中还没有考虑过。

图 2:个性化架构——分布式 RL 奖励计算(图片由作者提供)
隐私保护交互
隐私保护保证由多种技术提供:
- 在“边缘”设备/传感器上分布情感计算,例如可以在摄像机本身上计算“当前”情感。它可以被计算和匿名化,使得只有归一化的情感分数被传播到移动设备或云以供进一步计算。不需要在任何点传播捕获的(网络摄像头)用户图像,该图像可以在“当前”情感计算之后被销毁。
- 隐私保护方案,例如秘密共享和同态加密[3],可以用于将正/负“当前”情感值与移动设备上的历史情感曲线相加和,而移动设备上的客户端应用不知道实际的“当前”情感值。
这允许 RL 代理受益于非常详细和准确的奖励值,尽管是以隐私保护的方式。
隐私保护 RL 奖励计算流水线如图 3 所示。

图 3:隐私保护对话架构(图片由作者提供)
隐私保护强化学习
传感器/ 边缘设备的隐私保护
在这种情况下,边缘设备可以是(独立的)摄像头、麦克风、恒温器、智能手表,或者是嵌入在承载应用的移动设备中的摄像头、麦克风、加速度计。
例如,我们认为用户情绪是基于边缘设备/传感器捕获的音频、视觉、文本反馈来计算的。我们进一步假设,对于每个感觉参数,用户可以提供对应于不同方面(例如位置、人、健康/活动)的隐私敏感度等级(低、中、高)。
让在、 Vt 、 Tt 参考在 t 期间捕获的音频、视觉和文本反馈。
- 例如,让 Vt 对应于背景中有家庭成员的用户的视频帧。在这种情况下,关于“人”的“低”、“中”和“高”隐私设置将对应于保持画面原样、模糊人的面部、裁剪图像以将人从画面中完全移除。
- 类似地,让 Tt 对应于用户提供的文本响应,例如“我明天要去克拉科夫出差”在这种情况下,关于“位置”的“低”、“中”和“高”隐私设置将对应于保持响应原样,在响应中将“克拉科夫”抽象为“欧洲的某个地方”,从响应中完全移除目的地:“我明天将出差”
通过移动应用程序保护隐私
让 P(At) 、 P(Vt) 和 P(Tt) 表示各自捕获的感官反馈,由边缘设备传感器应用隐私保护——根据用户指定的设置。边缘设备然后与移动应用共享 P(At) 、 P(Vt) 和 P(Tt) 。
让

表示基于各自的感觉反馈独立计算的用户情绪。用户情感计算可以被认为是输出在1–10之间的值的分类器。
下一步,也是移动应用的主要功能,是聚合上述(独立计算的)情感值,并计算合并的用户情感 St 。聚集函数例如可以是加权平均值:

只要它满足下面的一致性检查:如果在 3 个用户情感值之间存在显著的差异;然后可以应用不同的策略来巩固它们:
a.忽略这个反馈周期,因为不同的感官反馈之间有太多的差异。这通过将值 0 分配给综合用户情绪 St 来表示。
b.向显式反馈提供比隐式反馈更高的权重,例如,如果文本情感得分是基于用户键入的响应计算的,而视觉得分是基于背景帧计算的;然后给予基于文本的情感分数更高的权重。例如,用户可能在快照中带着他的孩子微笑,但从他的文本/语音响应来看,他似乎“有压力”——在这种情况下,我们优先考虑“有压力”的用户情绪。
隐私保护应用的输出是提供(合并的)用户情感 St ,以及由用户提供的任何明确的用户响应 {P(At),P(Vt),P(Tt)} (根据边缘设备/传感器处的用户设置匿名化)。
例如,在聊天对话的情况下,在“位置”隐私设置为“中等”的情况下,它会将 St 连同用户响应“我明天将去欧洲出差”一起发送到后端 RL/NLP 引擎。
后端隐私保护— RL 引擎
我们关注强化学习(RL)引擎的两个关键功能:奖励功能和(RL 代理)策略——它们共同调节 RL 引擎内容个性化。我们考虑一个分类内容目录,其中应用推荐/(聊天)响应被分组为与用户兴趣相关的类别,例如旅游、娱乐、健康、位置等。
我们首先概述奖励函数 fr ,它基于确定的用户情绪 St 计算对应于最后一个 RL 引擎(在的动作)推荐/响应的奖励。

其中函数 fr 逻辑如下所示:
- 如果 (St = 0) ,由于反馈收集过程中的不一致性,我们简单地忽略这个学习循环。
- 历史归一化:对于St =[1–10],在将 St 值赋给奖励值 rt 之前,会根据历史上下文进一步归一化。历史背景确定如下:
(a)在正在进行的(连续的)对话的情况下,将当前情绪与到目前为止的对话的情绪曲线进行比较,以标准化其影响。
例如,如果对话情绪已经变得更糟,低的当前情绪可能不仅仅是上一个动作的错误;因此,奖励需要相应地调整。
另一方面,给定恶化的情感曲线,高情感值将暗示最后的动作对用户具有非常积极的影响;因此相应的回报需要进一步提高。
- 在特别推荐的情况下,可以应用类似的归一化逻辑,其中根据先前接收到的反馈(情感值)对当前情感 St 进行校准,该反馈用于与上一个动作相同类别的推荐。
- 延迟奖励:对于连续对话和特别推荐,也可以应用“延迟奖励”的替代策略,其中最后 m 行动的奖励被组合并(追溯性地)应用于【在的、在-1* 、…、在-m 。例如,如果对于类别“旅行”的推荐,当前情绪 St 较低,已知用户过去对该推荐(对其他“旅行”推荐)反应非常积极;延迟奖励策略将简单地缓冲当前值(在、和),并向 RL 代理(策略)提供指示以尝试相同类别的另一个推荐来“验证”用户情绪——在更新在的和在+1* 的的奖励之前。有关 RL 延迟奖励政策的更正式描述,请参考[4]。
参考
- Biswas,D.: 聊天机器人&自然语言搜索。走向数据科学,2019 ( 链接)
- 巴尔托,a .,萨顿,R.S.: 强化学习:导论。麻省理工学院出版社,2018 年,http://incompleteideas.net/book/RLbook2018.pdf
- D.比斯瓦斯。保护隐私的聊天机器人对话。进行中。第 34 届 NeurIPS 隐私保护机器学习研讨会(PPML),2020 年( pdf )。也发表在《走向数据科学》(链接)
- 基于强化学习的推荐系统。走向数据科学(链接),也发表在 2020 年 9 月第 24 届欧洲人工智能大会(ECAI)的“医疗保健人工智能进展”专题中。
- 崔顺实,夏,黄,吴..使用双聚类技术的基于强化学习的推荐系统。arXiv:1801.05532,2018。
- 刘,冯,唐,r,李,x,叶,y,陈,h,郭,h,张,y:基于深度强化学习的推荐与显式用户-项目交互建模。arXiv:1810.12027,2018
- Taghipour,Kardan,a .,Ghidary,S.S.: 基于使用的网络推荐:强化学习方法。摘自:美国计算机学会推荐系统会议,第 113-120 页(2007)。
- Ricciardelli,e .,Biswas,D.: 基于强化学习的自我改进聊天机器人。in:2019 年第四届强化学习与决策多学科会议。也发表在《走向数据科学》(链接
人工智能/人工智能应用中的隐私风险最小化
原文:https://towardsdatascience.com/privacy-risk-minimization-in-ai-ml-applications-cac35c0077b6
如何评估使用客户数据时的隐私风险,并利用隐私增强技术将风险降至最低

米格尔·Á拍摄的照片。帕德里纳对派克斯
隐私问题
在这个数据优先的组织时代,无论您处于哪个行业,您都很可能收集、处理和分析大量的客户数据。这可能是为了满足客户的服务请求,出于法律或监管原因,或者通过使用人工智能或机器
学习的个性化为您的客户提供更好的用户体验。然而,根据 Statista 的统计,数据泄露每年都在增加,2021 年 1862 报告的数据泄露比 2020 年增加了 68%,其中 83%涉及敏感信息。由于身份盗窃、跟踪、勒索软件攻击等原因,此类敏感信息落入坏人之手可能会给客户的生活造成严重破坏。这再加上各州隐私法和立法的兴起,使得增强隐私的数据处理技术走到了前沿。
隐私与数据效用的权衡
对于个性化等人工智能应用,隐私和数据效用可以在光谱的相反侧可视化。不包含任何个人信息的数据,即不暴露客户的特征或特性的数据,没有个性化的价值。然而,包含个人信息的数据可以用来提供高度个性化的体验,但如果数据集最终落入任何人手中,都可能导致客户数据隐私的丢失。因此,在隐私风险和数据的实用性之间总是存在固有的权衡。
对组织而言,隐私至上的价值
健康保险便携性和责任法案(HIPAA)、加州消费者隐私法案(CCPA)、儿童在线隐私保护法案(COPPA)、生物识别法案只是美国众多以隐私为中心的法律和法规中的一部分。未能遵守此类法规可能会给组织带来数十亿美元的罚款。例如,最近,得克萨斯州起诉脸书的母公司 Meta,要求赔偿数十亿美元的损失,因为该公司错误处理和利用了该州数百万人的敏感生物数据。隐私第一有助于避免巨额罚款,而且不仅限于失去经营执照。此外,消费者的信任和忠诚度、品牌形象和感知也会遭受巨大损失。忽视消费者的数据隐私会破坏客户的终身价值,影响转换和更新。事实上,像苹果这样的公司已经颠倒了这个问题,事实上正在利用隐私作为竞争的护城河,作为与其他技术公司的区别。

组织收集的数据中的隐私风险来源
组织内隐私风险有三个主要来源:
- 原始客户数据及其衍生产品。原始客户数据可以是客户输入的数据,如姓名、地址、年龄、性别和其他个人资料详细信息,或者是关于客户如何使用产品的数据,如页面访问量、会话持续时间、购物车中的商品、购买历史、支付设置等。
- 元数据和日志。元数据和日志包括客户位置、产品网站的访问位置、设备的 IP 地址、MAC 地址、服务日志、客户支持电话记录等。
- 接受过客户数据培训的 ML 模型。ML 模型本身看起来似乎不包含任何个人信息,但 ML 模型可以记住它所训练的数据中的模式。针对关键客户数据训练的模型可以在模型中保留客户的个人数据,并呈现客户个人数据暴露风险,而不管模型是部署在云中还是边缘设备上。如果恶意行为者获得了这种模型的访问权限,即使是作为黑盒,他们也可以运行一系列攻击来恢复个人数据,从而导致隐私泄露。
ML 模型的安全分类应该基于其训练数据的数据分类来确定。ML 模型工件可以包含明文客户数据,ML 模型本身容易受到隐私攻击。如果一个组织正在运行一个市场并与外部合作伙伴共享 ML 模型,即使在 NDA 和数据共享协议下,ML 模型也存在隐私攻击的高风险。
识别隐私姿态中的差距
希望确保其数据隐私合规性的组织应进行差距分析,以确定任何潜在的风险和弱点。数据隐私影响评估(DPIAs)是组织运行差距分析的重要工具。该流程包括检查与隐私和数据保护相关的现有实践、政策和程序,以评估它们与当前法律要求的一致性。差距分析通常由组织内的安全和数据隐私职能部门进行,因此将由数据保护官(DPO)进行。差距分析也可以外包,但提出请求的组织仍然对其负责。
进行差距分析时,组织需要考虑数据保护的所有方面,包括物理安全措施、访问控制和数据加密技术。他们还应审查与信息处理、数据存储和共享相关的政策和程序。组织应考虑来自外部来源的潜在威胁(例如网络罪犯),以及由人为错误或恶意意图导致的内部威胁。例如,对于 GDPR 来说,重要的是不仅要考虑哪些用户可以访问客户数据,还要评估为什么员工首先需要访问客户数据。如果用例不符合与个人数据的处理相关的预定义原则,则应立即撤销用户权限。评估还应考虑针对受保护数据资产的各种威胁发生的可能性,以及每种威胁一旦发生对组织运营的估计影响。
一旦发现任何弱点,组织就可以采取措施,通过实施必要的更改(如采用新工具或更新现有策略)来缩小差距。例如,组织可以选择实施细粒度的访问控制,如仅在短期内有效的访问(有时间限制的访问控制)、仅在预定义的地理位置内访问或仅从一组固定的设备或 IP 地址访问。此外,他们可能需要为员工创建额外的培训课程,以便员工了解最新的数据保护法规,并在处理客户数据时能够采取正确的措施。
DPIA 和差距分析不是一次性的事情,组织在考虑引入涉及个人数据的新系统或实践时,应考虑进行 DPIA。总的来说,差距分析是在组织内维护有效的数据隐私计划的重要组成部分。它有助于降低违规风险,并确保遵守适用的数据保护法。通过采取积极主动的方法进行数据隐私合规性差距分析,组织可以更好地保护其客户的敏感信息,同时确保处理个人数据所涉及的所有系统和操作的最高安全级别。
使用隐私增强技术来增强隐私姿态
顾名思义,宠物是组织识别、减少或消除潜在数据隐私风险的工具。通过在其系统中部署 pet,组织可以帮助最大限度地减少敏感个人信息的泄露,并证明符合适用的数据保护要求。PETs 的一些例子包括标记化、差分隐私、同态加密、联合学习和安全多方计算。
T okenization: 是用假名、匿名令牌或随机字符串替换敏感客户数据(如姓名或 SSN)的过程,这些数据没有任何关联意义。这可以防止任何恶意行为者在发生违规时访问有价值的客户数据。例如,零售商可以通过用随机生成的字符串或字符替换中间的 8 个数字来存储一个假设的信用卡号码1234–5678–9011–2345。通过这种方式,零售商仍然可以识别和使用信用卡,但如果数据库被攻破,它将永远不会暴露给任何恶意行为者。这种技术的一个缺点是,为了将来再次将信用卡用于合法用途(如自动订阅支付),组织需要一种确定性的方法来从令牌化的值中恢复原始卡号。如果令牌化算法落入坏人之手,可能会导致数据隐私泄露。
D 不同的隐私 : 是一种通过向数据中添加随机噪声来保护数据集中个人隐私的方法,这种方法在保持整体信息的同时很难识别任何个人。目标是确保不会泄露数据集中任何个人的任何信息,同时仍然允许对整体数据进行有用的分析。这种工作方式的一个例子是美国人口普查中“差别隐私”的使用。人口普查局从个人那里收集大量信息,包括收入和种族等敏感信息。为了保护个人隐私,人口普查局在向研究人员发布数据之前会在数据中加入噪声。这使得任何人都很难确定特定个人的信息,但仍允许对数据中的总体趋势和模式进行分析。添加噪声也带来了挑战,因为它很难从数据中提取准确的见解。随着数据量的增加,保证一定隐私级别所需的噪声量也会增加,这可能会降低数据对分析的有用性。差分隐私算法可能非常复杂并且难以实现,尤其是对于大型数据集或某些类型的查询。最后,实现差分隐私在计算上可能是昂贵的,并且可能需要专门的硬件或软件。
H 同态加密: 同态加密是一种允许对密文执行计算的加密类型,密文是加密的数据。计算的结果仍然是加密的,但是它可以被解密以揭示对原始明文的计算结果。这允许处理和分析敏感数据,而不必解密,从而维护数据的隐私和安全。投票系统就是一个例子。投票系统可以使用同态加密来确保投票的私密性和安全性。系统可以对投票进行加密,然后对加密的投票进行计算,以确定选举的获胜者。加密的投票可以被解密以揭示计算的结果,但是个人投票仍然是保密的。同态加密由于其计算效率低、功能有限、安全风险、密钥管理、可扩展性、缺乏标准化、复杂性和有限的商业用途而难以实现。此外,还需要更多的研究来提高同态加密算法的效率,使其在现实世界中更加实用。
F 这是通过在每个设备或团体上本地训练模型,然后通过安全通信信道聚集模型更新,而不是共享数据本身来实现的。联合学习的一个例子是在移动设备的环境中。一家移动公司可能希望训练一个模型来提高他们的键盘应用程序的性能。通过联合学习,该公司可以根据用户设备上的数据训练模型,而不必收集或共享数据。来自每个设备的更新模型可以被聚集以改进整体模型。联合学习在计算上是昂贵的,并且可能需要典型组织可能无法访问的专门基础设施。此外,来自不同方的组合数据可能具有不同的分布,这使得很难训练出适合所有方的单一模型。
随着过去 5 年取得的巨大进步,隐私增强技术正在快速发展。然而,PET 不是灵丹妙药,仍然有一些挑战需要克服。最大的一个问题是,宠物以它们自己的方式是独一无二的,每种宠物都提供不同的能力,具有不同的隐私与效用权衡。组织需要深入了解他们的用例,并评估哪种宠物最适合他们的组织。此外,一些解决方案可能需要大量的 IT 资源或技术专业知识来进行安装,这意味着并非所有组织都有能力利用这种技术。对于组织或个人来说,实施 PETs 的成本也很高。最后,这些解决方案需要定期维护,例如模型漂移校正或用最新数据重新训练模型,因此组织或个人很难跟上必要的更新以确保有效的安全措施仍然存在。
学术界、研究和创业公司的热情成员正在努力克服挑战,使宠物成为每个组织的 SaaS 工具包的一部分。我强烈鼓励任何感兴趣的人通过参加会议、阅读研究论文和加入开源社区来了解最新动态。
Pushpak Pujari 是人工智能(AI)、计算机视觉(CV)、物联网(IoT)、数据隐私和安全领域的行业专家。他是众多会议的专题演讲人和主讲人,如 边缘 AI 峰会 、 隐私增强技术峰会 、以及 AI 硬件峰会 。他还在众多出版物中撰写文章和白皮书,如 【企业观点】 和 产品学院 。普什帕克是领先的湾区企业安全摄像机初创公司Verkada的安全摄像机产品负责人,拥有沃顿商学院的 MBA 学位和印度 IIT 德里大学的电气工程学士学位。 在 LinkedIn 上关注他 。
用于变化点检测的概率累积和
原文:https://towardsdatascience.com/probabilistic-cusum-for-change-point-detection-121f793ab3a1
CUSUM 可以说是物联网或金融应用中最简单的变点检测算法。

克里斯·劳顿在 Unsplash 上的照片
介绍
根据著名的奥卡姆剃刀原理,较简单的模型比复杂的模型更可能接近真理。对于变化点检测问题,如在物联网或金融应用中,可以说最简单的是CumulativeSum(Cu Sum)算法。
尽管它很简单,但它仍然是一个强大的工具。事实上,CUSUM 只需要对潜在的时间序列做一些松散的假设。如果这些假设得到满足,就有可能证明大量有用的统计特性。
快速浏览 CUSUM
总之,CUSUM 检测在两个变点之间静止的时间序列的平均值的变化。考虑以下时间序列:

具有变化点(绿色直线)的分段平稳时间序列(蓝色)示例。CUSUM 可以处理这样的数据。(图片由作者提供)
这个例子在每对变化点之间是固定的,因此是我们的 CUSUM 算法的完美用例。对于像下一个这样的非平稳时间序列的变点检测,CUSUM 可能无法按预期工作:

两个变化点之间具有非平稳性的时间序列示例。CUSUM 无法正常处理这样的数据。(图片由作者提供)
虽然 CUSUM 可能仍然能够检测到从稳定段到非稳定段的偏移,但不能保证它能可靠地做到这一点。
总的来说,CUSUM 背后的思想可以大致概括如下:
如果时间序列具有恒定的零均值,其实现的累积和收敛于零均值正态分布(给定一些相对宽松的技术假设)。因此,如果累积和偏离零均值正态分布,潜在的时间序列可能会出现转折点。
我们可以从众多中心极限定理 (CLTs)中的一个推导出这一点。虽然每个 CLT 都有一些额外的要求(例如,李亚普诺夫 CLT 的独立抽签和有限方差),但您的特定时间序列很可能满足其中一个要求。
在实践中,我们将估计当前状态的平均值,将其从时间序列中减去,并计算累积和。这就只剩下一个问题,那就是当一个改变点发生时,该如何设置规则。
wikipedia 中的标准 CUSUM 算法建议对时间序列的 z 标准化实现求和。每当该总和超过预定义的阈值时,就会出现变化点。因此,整个过程是一个“在线”算法,即我们可以在实时数据流上使用它。
标准累积和算法的一些问题
您可能已经问过自己应该如何在 CUSUM 中设置变化点阈值。毕竟门槛设置太松会导致改点未被发现。另一方面,狭窄的阈值容易导致频繁的错误警报。
遗憾的是,要找到解决这个问题的明确说明并不容易。虽然经验法则或对某些设置进行试验偶尔会奏效,但这显然不是一个可靠的解决方案。此外,当我们想要对大量数据流应用 CUSUM 时,这是不可行的。
另一个问题是给定子序列的异常程度。即使没有变化点发生,发现时间序列何时出现意外行为仍然是有意义的。
幸运的是,我们可以通过稍微修改原始的 CUSUM 算法来应对这两个挑战。
累积和的概率版本
在这一点上,我们将最终需要一些方程。首先,我们定义时间序列的任意子序列的标准化观测值:

(图片由作者提供)
帽子符号强调,我们只能使用序列的均值和标准差的估计值。我们可以计算这些值,例如,通过使用我们估计的第一个N实现。
如果我们假设某些 CLT 的条件适用于我们的序列,则下列条件在极限情况下大致成立:

(图片由作者提供)
通过将累计总和除以时间范围的平方根,我们得到一个(理论)标准正态分布。因此,只要我们的 CLT 假设有效,以下适用于已实现时间序列的标准化累积和:

(图片由作者提供)
得到的值可以解释为理论累积和与我们观察到的值一样小的概率。这实际上相当于经典假设检验中对一个 p 值的定义。
但是请注意,上述数量目前仅在一个方向上起作用,即如果标准化总和为负。为了使这成为一个双边统计,我们可以要求标准化和的概率至少和我们的实现值一样远离均值。由于我们的和是一个标量值,我们可以简单地将从零开始的'距离'定义为绝对值,并简化为:

(图片由作者提供)
我们现在可以使用这个概率代替原始的标准化累积和来检测变化点。与最初的总和相反,这一措施有一个明确的,概率性的解释。对于每一个新的数据点,我们可以直接获得相应观察值的极端程度。
一旦超过“不太可能”的某个阈值,我们将相应的时间戳标记为变化点,并重新开始算法。
大致而言,算法如下所示:

(图片由作者提供)
在 Python 中,可能的实现如下所示。我使用 PyTorch 来考虑未来可能的扩展,包括自动签名功能:
实践中的概率累积和
让我们用两个例子来试试上面的算法。首先,我们使用简介中的模拟恒定均值数据集:

模拟数据的概率累积和——红色阴影标记了上述定义的极端概率。(图片由作者提供)
我们修改后的 CUSUM 版本能够检测所有的变化点,尽管在检测上有一些延迟。然而,所有的变化点都落在我们的概率度量已经检测到异常行为的区域。因此,通过一些微调,甚至可以更早地发现关键的变化点。
对于我们的第二个例子,让我们使用来自 Kaggle 的 Skoltech 异常基准数据集的摘录。我选择时间序列时考虑了 CUSUM 背后的假设(特别是常数均值假设)。因此,结果不应作为可靠的基准,而应作为说明性的例子:

现实世界数据集上的概率累积和。(图片由作者提供)
虽然我们的 CUSUM 变量在线性趋势模式上有一些问题,但总体结果看起来是合理的。这也证明了这种算法的局限性,一旦常数均值假设被违反。然而,尽管简单,CUSUM 似乎是一个有用的选择。
结论
虽然 CUSUM 是一个非常简单的算法,但只要满足底层假设,它就可以非常强大。通过一个简单的概率修改,我们可以很容易地改进标准版本的 CUSUM,使其更具表现力和直观性。
然而对于更复杂的问题,更复杂的算法可能是必要的。一个特别有用的算法是贝叶斯在线变点检测,我希望能在未来涵盖它。
原载于 2022 年 8 月 4 日【https://www.sarem-seitz.com】。
张量流中从头开始的概率线性回归
概率深度学习
介绍
本文属于“概率深度学习”系列。这个每周系列涵盖了深度学习的概率方法。主要目标是扩展深度学习模型,以量化不确定性,即知道他们不知道的东西。
我们使用张量流和张量流概率开发我们的模型。TensorFlow Probability 是一个构建在 TensorFlow 之上的 Python 库。我们将从能在张量流概率中找到的基本对象开始,并理解我们如何操纵它们。我们将在接下来的几周内逐步增加复杂性,并将我们的概率模型与现代硬件(如 GPU)上的深度学习相结合。
迄今发表的文章:
- 张量流概率简介:分布对象
- 张量流概率简介:可训练参数
- 张量流概率中从零开始的最大似然估计
- tensor flow 中从头开始的概率线性回归
- 使用 Tensorflow 进行概率回归与确定性回归
- Frequentist 与 Tensorflow 的贝叶斯统计

图 1:我们今天的口头禅:用我们的概率(和某种艺术)帽子来拟合线性模型(来源
像往常一样,代码可以在我的 GitHub 上找到。
确定性线性回归
为了使我们的过程更容易理解,让我们从创建一些遵循线性模式的数据开始。通过综合生成这些数据,我们有优势知道什么是确切的生成过程,因此,我们只需要找到一种方法,从观察结果回到生成过程。这听起来并不复杂,但一旦我们进入更复杂的生成过程,我们对它进行逆向工程的能力将变得越来越具有挑战性。
我们将使用的数据是根据以下等式人工创建的:

其中𝜖𝑖∽n(0,1)独立同分布。
x_train = np.linspace(-1, 1, 100)[:, np.newaxis]
y_train = x_train + (3/8)*np.random.randn(100)[:, np.newaxis]
plt.scatter(x_train, y_train, alpha=0.4)
plt.xlabel('x')
plt.ylabel('y')
plt.show()

图 2:遵循带有高斯噪声的线性方程人工生成的数据。
对于 TensorFlow 和 Keras,在将模型拟合到我们的数据之前,需要定义一些组件。首先,我们选择 Keras API,因为 Keras 提供了两个选项:顺序和函数 API。在这种情况下,我们将使用顺序 API,但是作为练习,您可以尝试使用函数 API 重新实现我们的算法。接下来,我们需要为简单的神经网络定义输入。您可以将此输入视为真实数据的占位符,稍后,我们将通过模型传递这些数据。它的目的是告知模型在未来某个时间点要摄取的数据的形状。请注意,我们没有明确定义输入的第一维,因为它是批量大小,模型应该能够处理任何批量大小。回想一下,批量大小是每个训练时期使用的训练样本的数量。
既然我们已经指定了输入,是时候选择损失函数了。对于线性回归情况,通常的候选值是均方误差。下一步,我们需要构建我们的神经网络架构,为此,我们将采用最简单的形式:具有 1 个单元的密集层。最后,我们可以选择我们的优化器(在这个阶段不相关)和训练时期的数量。
让我们定义一个模型,它将每个输入作为一个长度为一的向量来接收,这个向量转化为一个input_shape=(1,)。这意味着我们只有一个特征。该模型仅预测一个目标变量,使用上面定义的密集层。我们正在编译具有均方误差损失的模型,使用RMSprop优化器并训练 400 个时期。
model = Sequential([
Dense(1, input_shape=(1,))
])
model.compile(loss=MeanSquaredError(), optimizer=RMSprop(learning_rate=0.005))
model.fit(x_train, y_train, epochs=400, verbose=False)
在上一篇文章中,我们看到了如何在 TensorFlow 中定义Variable对象。这些对象是在训练过程中被更新的对象。这次我们没有明确定义它们,但是 Keras 已经为我们定义了它们。让我们在拟合数据后检查训练变量的值。因为我们正在定义一个确定性线性回归,我们有两个变量,斜率和截距。在上式中,斜率等于 1,截距等于 0。我们成功地取回了它们。
model.weights
[<tf.Variable 'dense_11/kernel:0' shape=(1, 2) dtype=float32, numpy=array([[ 0.97764325, -0.09925082]], dtype=float32)>,
<tf.Variable 'dense_11/bias:0' shape=(2,) dtype=float32, numpy=array([-0.08040976, -0.7941095 ], dtype=float32)>]
我们可以绘制我们的模型,并对照观察到的数据检查它看起来如何。除了观察到的噪声之外,它对数据建模得相当好。我们没有得到任何关于预测不确定性的参考(从蓝点和红线之间的距离可以看出,存在显著的不确定性)。确定性线性回归不能捕捉任何关于数据生成过程中不确定性的信息,我们称这种类型的不确定性为随机的。
plt.scatter(x_train, y_train, alpha=0.4, label='data')
plt.plot(x_train, model.predict(x_train), color='red', alpha=0.8, label='model')
plt.legend()
plt.show()

图 3:符合观察数据的确定性线性回归模型。
概率线性回归
我们现在准备戴上我们的概率帽子。我们的兴趣是扩展我们的确定性方法来模拟观测数据中的随机不确定性。为了能够实现它,我们需要利用通常的猜想:张量流概率。如果您发现需要回顾一些关于张量流概率的概念,请随意查看我在本系列中的早期文章。
为了构建我们的概率模型,我们知道需要在某处添加一个高斯分布(因为我们在人工生成的数据中添加了高斯噪声)。我们想要拟合相同的线性回归模型,但是要学习两个额外的变量。让我们从高斯噪声的平均值开始。
既然我们很满意以前的架构来建模我们的数据,那么让我们用一个单元来定义相同的密集层。回想一下,这个设置确保我们在模型中添加了两个变量,斜率和截距。为了扩展我们的模型,使其能够捕捉产生噪声的高斯分布的平均值,我们将在架构中添加一个新层。这个新层是概率性的,而不是确定性的。在 Keras 中将任何分布纳入深度学习架构的通用方法是使用 TensorFlow Probability 的DistributionLambda层。DistributionLambda层返回一个分布对象,这是我们在以前的文章中非常熟悉的东西。在这种情况下,我们指定一个高斯分布对象。
请注意,前一层的输出定义了高斯分布的平均值,我们假设标准偏差是固定的(在我们的例子中为 0.5)。换句话说,DistributionLambda的构造函数有一个必需的参数,即函数。由于我们处于神经网络设置中,该函数接收前一层的输出作为其输入,并输出分布对象。在这种情况下,我们使用 lambda 函数来实例化DistributionLambda层。lambda 函数接收一个输入t,它是前一个密集层的输出张量,并返回一个平均值由张量t定义的高斯分布。
通过这种设置,模型在被调用时会返回一个分布对象。
model = Sequential([
Dense(1, input_shape=(1,)),
tfpl.DistributionLambda(lambda t: tfd.Normal(loc=t, scale=0.5))
])
model.summary()
Model: "sequential_1"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense_1 (Dense) (None, 1) 2
distribution_lambda (Distri ((None, 1), 0
butionLambda) (None, 1))
=================================================================
Total params: 2
Trainable params: 2
Non-trainable params: 0
_________________________________________________________________
像往常一样,我们需要指定我们的损失函数。在概率情况下,正如我们在以前的文章中已经看到的几个例子,我们需要将负对数似然定义为我们的损失函数。
def nll(y_true, y_pred):
return -y_pred.log_prob(y_true)
最后,我们准备编译并拟合我们的模型。
model.compile(loss=nll, optimizer=RMSprop(learning_rate=0.005))
model.fit(x_train, y_train, epochs=400, verbose=False)
与我们上面看到的相反,我们可以在右边的图中看到,我们的新模型现在正在捕捉任意的不确定性,这允许我们建立置信区间。如果我们使用平均值的两个标准差的区间,我们将有 95%的样本落在这些边界内。我们很容易就能看出,下面这个情节并不是这样。我们有 100%的值在区间内。这是因为虽然我们正确地获得了平均值,但我们没有推断出标准差。我们使用手动输入的值,它与真实值相差甚远。
在左图中,我们可以看到使用概率模式的主要优势之一:可以生成遵循与观察值相同的生成过程的样本。请注意,关于我们生成的样本,我们遇到了同样的问题,它们比真实数据分散得多。
y_model = model(x_train)
y_sample = y_model.sample()
y_hat = y_model.mean()
y_sd = y_model.stddev()
y_hat_m2sd = y_hat -2 * y_sd
y_hat_p2sd = y_hat + 2*y_sd
fig, (ax1, ax2) =plt.subplots(1, 2, figsize=(15, 5), sharey=True)
ax1.scatter(x_train, y_train, alpha=0.4, label='data')
ax1.scatter(x_train, y_sample, alpha=0.4, color='red', label='model sample')
ax1.legend()
ax2.scatter(x_train, y_train, alpha=0.4, label='data')
ax2.plot(x_train, y_hat, color='red', alpha=0.8, label='model $\mu$')
ax2.plot(x_train, y_hat_m2sd, color='green', alpha=0.8, label='model $\mu \pm 2 \sigma$')
ax2.plot(x_train, y_hat_p2sd, color='green', alpha=0.8)
ax2.legend()
plt.show()

图 4:符合人工生成数据的概率线性回归模型。只有平均值是从数据中推断出来的。
我们的下一步是添加标准偏差作为模型的参数。请记住,标准偏差是严格为正的,因此我们也需要将此约束添加到我们的模型中。我们使用一个 SoftPlus 函数,它是 ReLU 函数的平滑近似,并且确实可以约束模型的输出始终为正。
model = Sequential([
Dense(2, input_shape=(1,)),
tfpl.DistributionLambda(lambda t: tfd.Normal(loc=t[...,:1], scale=tf.math.softplus(t[...,1:])))
])
model.summary()
Model: "sequential_2"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense_2 (Dense) (None, 2) 4
distribution_lambda_1 (Dist ((None, 1), 0
ributionLambda) (None, 1))
=================================================================
Total params: 4
Trainable params: 4
Non-trainable params: 0
_________________________________________________________________
我们的损失函数和之前完全一样。
def nll(y_true, y_pred):
return -y_pred.log_prob(y_true)
因此,我们准备编译并适应我们的新模型。
model.compile(loss=nll, optimizer=RMSprop(learning_rate=0.005))
model.fit(x_train, y_train, epochs=500, verbose=False)
y_model = model(x_train)
y_sample = y_model.sample()
y_hat = y_model.mean()
y_sd = y_model.stddev()
y_hat_m2sd = y_hat -2 * y_sd
y_hat_p2sd = y_hat + 2*y_sd
请注意,我们能够捕捉到高斯噪声的正确平均值,并且这一次,我们还能够学习到正确的标准偏差值。
print("True μ: ", 1)
print("Estimated μ: ", y_hat.numpy().mean())
print("True σ: ", 3/8)
print("Estimated σ: ", y_sd.numpy().mean())
True μ: 1
Estimated μ: -0.06890577
True σ: 0.375
Estimated σ: 0.37177968
现在,我们终于能够正确地捕捉到任意的不确定性了!置信区间看起来不错,因为我们可以看到大约 95%的样本在它的边界内。因此,我们现在能够使用刚刚从数据中学到的相同过程来生成新样本。
y_model = model(x_train)
y_sample = y_model.sample()
y_hat = y_model.mean()
y_sd = y_model.stddev()
y_hat_m2sd = y_hat -2 * y_sd
y_hat_p2sd = y_hat + 2*y_sd
fig, (ax1, ax2) =plt.subplots(1, 2, figsize=(15, 5), sharey=True)
ax1.scatter(x_train, y_train, alpha=0.4, label='data')
ax1.scatter(x_train, y_sample, alpha=0.4, color='red', label='model sample')
ax1.legend()
ax2.scatter(x_train, y_train, alpha=0.4, label='data')
ax2.plot(x_train, y_hat, color='red', alpha=0.8, label='model $\mu$')
ax2.plot(x_train, y_hat_m2sd, color='green', alpha=0.8, label='model $\mu \pm 2 \sigma$')
ax2.plot(x_train, y_hat_p2sd, color='green', alpha=0.8)
ax2.legend()
plt.show()

图 5:符合人工生成数据的概率线性回归模型。平均值和标准偏差都是从数据中推断出来的。
结论
本文介绍了一种从概率角度看待线性回归的新方法。我们从拟合确定性线性回归开始,虽然我们能够正确捕捉斜率和截距,但我们发现我们对数据生成过程中的不确定性一无所知,我们称这种不确定性为随机的。然后,我们通过在我们非常简单的深度学习架构中增加一层来扩展这种方法。我们没有添加传统的层,而是添加了一个概率层,它返回一个分布对象。通过在新的概率层中定义两个附加变量来指定我们的模型,我们能够学习用于生成人工数据的高斯噪声的均值和标准差。通过使用这些参数,我们还能够建立置信区间,但更重要的是,使用我们从数据中学到的相同过程来生成新样本。
下周,我们将把这种方法扩展到非线性数据。到时候见!
保持联系: LinkedIn
参考资料和材料
[1] — Coursera:深度学习专业化
[2] — Coursera:深度学习的 tensor flow 2专业化
[3] — 张量流概率指南和教程
[4] — TensorFlow 博客中的 TensorFlow 概率帖子
概率机器学习系列文章之三:具有相关噪声的权重不确定性
原文:https://towardsdatascience.com/probabilistic-machine-learning-series-post-3-369cbf7ca8d3
使用相关漏失量化神经网络预测的不确定性

来源:https://www . pexels . com/photo/question-mark-on-chalk-board-356079/
贝叶斯方法的主要缺点之一是它不可扩展。2015 年,Blundell & al。提出用丢失来量化神经网络中的不确定性。这种方法称为 Bayes by backprop,它使用重新参数化技巧来逼近后验概率的变分界限。在这篇文章中,我们将通过反向传播对贝叶斯方法进行一些调整。主要目标将是看看我们如何能够添加受约束的参数,而不向我们的优化问题添加约束(即,约束参数的可能值)。第一个修改是,我们将通过从正态多元分布而不是独立的正态分布中采样来使不同权重之间的丢失相关。这将说明最后一层中的权重可能是相关的。修改的第二个方面是,我们将只在神经网络的最后一层使用贝叶斯观点。这是为了检查我们是否可以通过添加尽可能少的参数来获得一个概率模型。
损失函数的起源
损失函数来自变分近似。重要的是要记住贝叶斯框架的目标是找到后验分布。有很多方法可以做到这一点。在过去的几年中引起注意的一个方法是变分法,它提供了一种以可伸缩的方式近似后验概率的方法。为了实现这一点,我们将寻找后验概率的问题转化为可以应用梯度下降(或类似技术)的连续优化问题。给定一组描述变分分布的参数,它试图找到最接近的解,而不是找到精确的后验概率。我们所说的最接近解是指使变分分布和后验分布之间的 Kullback-Leibler 散度最小的解。在我们的情况下,变分分布将是多元正态分布。

损失函数代表变分分布和后验分布之间的 Kullback-Leibler 散度。
损失函数可以通过根据变分分布生成权重样本来近似。它被分解成三个不同的项:变分项、似然项和先验项。

基于随机生成的样本估算的损失(漏失)。
将根据正态分布对似然性进行建模,其中均值由 LSTM 的最后一层的结果与向量 w 的标量积给出,方差由σ给出。我们可以将模型的参数分成两大类。第一个是确定性参数;LSTM 参数试图通过作为特征提取器来赋予我们的数据以意义。第二类是定义概率分布的,它们是μ向量、σ矩阵(变分分布)和σ参数。因此,我们从以μ和σ为参数的多元正态分布中生成α。产生的权重将与 LSTM 的最后一层相乘(标量积),以给出具有标准偏差σ的单变量正态分布的平均值ν,该正态分布对观察量的固有噪声进行建模。在图形表示中,虚线表示确定性变换,而实线表示随机变量的参数。

模型不同参数之间关系的图形表示。虚线表示确定性变换,而实线表示基于分布参数的随机采样结果。
如何得到协方差矩阵
协方差矩阵需要满足一些特定的限制。它必须是对称的,所有的元素都必须是正的。为了能够使用自动微分并满足这些约束,我们使用了两个转换。第一种是使用正定矩阵的 Cholesky 分解来分解矩阵,这给出了对称矩阵。第二个转换是采用 softplus 激活函数来确保所有的元素都是正的。因此,被训练的参数不是将被约束的协方差矩阵的元素,而是矩阵 L 的参数,其值可以在实轴上自由移动。由于矩阵乘积和 softplus 函数都是可微函数,我们可以构造一个计算图。

基于矩阵 L 的协方差矩阵适马的表示。L 的系数被训练。
培训阶段
第一个训练阶段是为任务找到好的 LSTM 砝码。如果权重给出了很好的预测精度,并且精度在权重的小扰动下是稳定的,我们将认为权重是好的。如果是第一阶段,损失函数将是任务的通常损失(这里是均方误差),这将给出最大似然估计。在给定数量的步骤之后,高斯噪声将被添加到权重,以确保所获得的权重是稳定的。添加的噪声相当于将在第二训练阶段使用的协方差矩阵的对角元素。在此阶段训练的参数是 LSTM 权重。第二个训练阶段是寻找后验概率贝叶斯参数的完整解。我们不仅要训练 LSTM 权重,还要训练协方差矩阵。在这两个阶段,都使用了早期停止。
预测和校准
在下图中,我们可以看到真实值(橙色)大多在置信区间内(蓝色阴影区域)。

与真实值(橙色)相比,具有 95 %置信区间(蓝色阴影)的预测值(蓝色)。
但愿这个论点没有说服力。因此,我们将着眼于概率预测的校准。通过校准,我们直观地表示 90 %的预测值应该低于针对 90%概率给出的逆累积分布函数。有许多方法可以测量校准,但我们暂时只看这一种。我们将通过绘制落在逆累积分布函数下的数据的比率作为参考概率 p 的函数来直观地表示校准,参考概率 p 将在 0 和 1 之间。正如我们将看到的,对于相同的训练参数,不同的随机种子有很大的差异。这种现象经常发生,因为我们陷入局部最小值,无法摆脱它们。最简单的策略是从新运行或现有运行重新启动。在第一种情况下,我们希望算法能在不同的初始条件下找到一个好的局部极小值。在后一种情况下,目标是通过将学习率重置为更高的值来避免局部最小值,因为它在之前的每次迭代中都有所下降。

模型训练不同重启的校准曲线(即不同的种子)。
末日之语
我们使用了对神经网络方法中权重不确定性的简单修改,以了解如何在不丧失自动微分优势的情况下解除分布参数的约束。主要思想是使可训练参数在轴上自由移动,但是用可微分函数对它们进行变换,这将给出满足期望属性的新参数。
参考文献和推荐读物
[1] C .布伦德尔。Cornebise,K. Kavukcuoglu 和 D. Wierstra,神经网络中的权重不确定性 (2015) ICML
*除非另有说明,所有图片均为作者所有。
第一数字定律的概率再现
原文:https://towardsdatascience.com/probabilistic-recreations-of-the-first-digit-law-d13a55dc570
大自然创造了数字模式,并规定一些人在外表上比其他人更有特权。

第一位定律、第一位现象或前导数字现象是一个现象定律。根据本福特定律,在大多数世界范围的人口普查中,前导数字 1 比其他前导数字出现得更多,频率为 30%。这就好像我们有一个精心操纵的骰子,它总是倾向于 1 比 2,2 比 3,等等。这显然不同于我们在估计从 1 到 9 的每个数字出现的比例时,直觉上认为的 9 个数字中有一个的概率。

作者图片(本福德定律)
本福德定律一直被认为是一个迷人而神秘的自然法则。
它的解释范围从超自然到测量理论。与 it 相关的用例从欺诈检测到计算机磁盘空间分配。
关于这一主题的出版物近年来有所增加,其中大多数侧重于从欺诈和计算机科学应用的各种数据来源审查法律。然而,本福特定律的基本原因仍然是个谜。
本文的目的不是提供理论证据来证明这样一个法律的起源。正如你可能已经注意到的,从概率的角度来看,用来近似有点棘手。
不管你用什么作为基本分布,即使你从样本中取第一个数字,这个分布也远远不能接近本福特分布。
在第一步中,我们提出了三种概率方法来得到一个类似本福特的分布。在第二部分中,我们处理两个更现实的例子,本福特分布更可能出现在现实生活中。
所以让我们开始实践吧。
模拟本福特定律的三种方法:
1。对均匀样本进行 N 次幂运算
第一种方法是从 0 到 1 之间的均匀分布中采样值,并将它们提升到n的幂,n是一个相对较大的整数:

作者图片
下面是我们如何对单个采样值执行此操作的示例:

作者图片
我们写下采样函数,它将分布的边界、样本的大小以及样本中每个元素的幂作为参数。
我们写下一个extract_first_digit,作为取样本中每个数字最左边第一个数字的函数:
让我们第一次模拟点火:

样本的 1 次方(图片由作者提供)

样本的 2 次方(图片由作者提供)

样本的 50 次方(图片由作者提供)

样本的 100 次方(图片由作者提供)
指数越高,分布越像本福特定律。你可以试着自己用另一个代替统一的分布。我不太确定你最终会发现同样的结果,尽管它看起来很有趣。
2.将一个均匀分布除以另一个均匀分布:

作者图片
第二种方法是将 0 到 1 之间的均匀分布样本除以另一个具有相同参数的样本:

作者图片
让我们重现一下:
一个简单的情节:
sns.histplot(history, stat="probability", discrete = True, color = 'b')

作者图片
不幸的是,就像第一个模拟技巧一样,如果没有使用均匀分布,您将无法获得相同的本福特式分布。
3.马尔可夫链的构造;
第三种方式在于构建马尔可夫链,该马尔可夫链以从 0 到 10 (10 优选地不包括在内)之间的均匀分布中采样的随机值开始。
然后,每个新状态由新的均匀分布形成,该均匀分布由 0 和从前一状态采样的值mantissa来界定。

作者图片
通俗地说,一个mantissa就是一个数的第一个数字,用花括号表示。这里有一个更具体的例子:

作者图片
让我们行动起来,编写一些代码:

作者图片
显而易见,这些数字的不同概率与本福特的官方概率值有所不同。形状完全不变:1 is prone to be more frequent than other digits。
本福德定律可能出现的两个真实案例:
现在我们已经完成了热身,我们将处理两个具体的使用案例,本福特定律在其中发挥了它的魔力。
1.超市的价格分布
第一个案例涉及超市中的价格,人们发现,总体而言,数字 1 比数字 2 更频繁地出现在价格的左侧,而数字2本身比数字 3 更频繁地出现,以此类推。
我们的朋友爱丽丝又来了,带我们去她住处附近的一家超市参观。她习惯于在那里跑腿,因为她能找到她需要的任何东西。到达后,她浏览了过道,走到一个自助餐台旁边,那里有些商品打折出售。

作者图片
她不知道所有遇到的价格都来自不同的分布,这产生了一个非常特殊的概率定律。每个分销被认为是一个单一品牌的变体的价格范围。

作者图片
让我们帮爱丽丝找到它。
首先,让我们定义自定义函数来绘制样本分布,从中抽取值。
例如,我们为正态分布定义了一个分布范围,这个分布范围是通过我们指定为输入参数的均值和方差范围创建的。在下一步中,我们将对平均值和方差进行采样,我们将创建一个高斯实例并从中采样一个值。
我们编写相同的机制来重新创建伽玛和均匀分布。
在下一步中,我们将运行一系列采样操作,每个操作都包括随机选择均匀分布、高斯分布或伽马分布,从中抽取样本,收集第一个数字并将其存储在一个列表中。
再一次,我们在本福特分布的前面。

作者图片
1998 年,Theodore P.Hill给出了一个严格的证明,从混合分布中抽取的样本遵循本福特定律。所以这并不奇怪。
2.股票价格的倍增波动
让我们仔细看看股票价格的演变。
考虑到它每次都乘以从不同的正态分布中抽取的随机样本,

作者图片
我们将追踪每一个新价格的第一个数字,看看是否会有所收获。

作者图片
这里有一个快速演示:
一旦我们通读了所有的价格记录并存储了所有的数字,我们就绘制分布图(交叉手指)..):

作者图片
本福德定律再次出现在我们面前。
在 2001 年,L.Pietronero、E. Tosatti、V. Tosatti,和A. Vespignani在他们题为Explaining the uneven distribution of numbers in nature的论文中解决了这个问题,其中作者从乘法过程的研究开始,并与中心极限定理进行类比,中心极限定理不是处理乘法,而是概括随机过程。他们声称:
这个练习表明,表征一些物理量或物体的数字 N 自然会遵循本福特定律,如果它们的时间演化受乘法涨落支配的话。
结束语:
许多数学家成功地解释了本福特定律在普通数字中的自然表现。
直到今天,这个话题引起了每个人的好奇心,尽管解释可能很透彻。从个人的角度来看,用小而有趣的模拟来检查专家的发现总是很好的,这样可以更容易地吸收这些概念。
参考资料:
张量流的概率回归与确定性回归
概率深度学习
介绍
本文属于“概率深度学习”系列。这个每周系列涵盖了深度学习的概率方法。主要目标是扩展深度学习模型,以量化不确定性,即知道他们不知道的东西。
本文将探讨确定性回归和概率性回归之间的主要区别。一般来说,当自变量和因变量之间的关系很好理解并且相对稳定时,确定性回归是实用的。另一方面,当数据存在不确定性或可变性时,概率回归更合适。作为支持我们主张的练习,我们将使用张量流概率来拟合非线性数据的概率模型。
迄今发表的文章:
- 张量流概率简介:分布对象
- 张量流概率简介:可训练参数
- 张量流概率中从零开始的最大似然估计
- tensor flow 中从头开始的概率线性回归
- 使用 Tensorflow 进行概率回归与确定性回归
- Frequentist 与 Tensorflow 的贝叶斯统计

图 1:我们今天的咒语:不是所有的线都是直的(来源)
我们使用张量流和张量流概率开发我们的模型。TensorFlow Probability 是一个构建在 TensorFlow 之上的 Python 库。我们将从能在张量流概率中找到的基本对象开始,并理解我们如何操纵它们。我们将在接下来的几周内逐步增加复杂性,并将我们的概率模型与现代硬件(如 GPU)上的深度学习相结合。
像往常一样,代码可以在我的 GitHub 上找到。
概率回归与确定性回归
定义
确定性回归是一种回归分析,其中自变量和因变量之间的关系是已知和固定的。因此,在给定一组已知自变量的情况下,它是预测因变量的值的有用工具。换句话说,如果向确定性回归模型提供相同的输入,它将总是产生相同的输出。
如果我们考虑线性回归模型,Gauss-Markov 定理立即浮现在脑海中,因为它在某些假设下建立了普通最小二乘(OLS)估计的最优性。特别是,高斯-马尔可夫定理表明 OLS 估计量是最好的线性无偏估计量(蓝色),这意味着它在所有线性无偏估计量中具有最小的方差。然而,高斯-马尔柯夫定理没有解决不确定性或估计中的信念的问题,这是概率方法的一个关键方面。
另一方面,概率回归将自变量和因变量的交互方式视为未知,并假设它们会因数据集而异。概率回归模型不是预测因变量的单个值,而是预测因变量的可能值的概率分布。它允许模型考虑数据中的不确定性和可变性,并且在某些情况下可以提供更准确的预测。
我们举一个简单的例子来简化理解。一名研究人员研究学生为考试而学习的时间和他们的分数之间的关系。在这种情况下,研究人员可以使用 OLS 方法来估计回归线的斜率和截距,并使用高斯-马尔可夫定理来证明该估计量的选择。然而,正如我们之前所述,高斯-马尔可夫定理并没有解决估计中的不确定性或置信问题。在概率世界中,重点是使用概率来描述模型或参数的不确定性或可信度,而不仅仅是估计量的最优性。因此,我们可以使用不同的方法来估计回归线的斜率和截距。因此,根据数据和对斜率和截距值的先验信念,我们可能会对学习时间和考试分数之间的关系得出不同的结论。
贝叶斯统计和偏差-方差权衡
概率回归可以被视为贝叶斯统计的一种形式。它包括将自变量和因变量之间的未知关系视为随机变量,并根据可用数据估计其概率分布。这样,我们可以把它看作是把不确定性和可变性纳入回归分析的一种方法。回想一下,贝叶斯统计是一种统计分析框架,其中所有未知量都被视为随机变量,它们的概率分布随着新数据的观察而更新。这与经典统计学相反,经典统计学通常假设未知量是固定的,但参数是未知的。
考虑这两种方法之间差异的另一种方法是考虑统计估计中的偏差和方差之间的权衡。偏差是指参数的估计值和实际值之间的差异,而方差是指估计值的分布或可变性。通过提供模型参数的分布而不是单点估计,概率回归有助于减少估计中的偏差,从而提高模型的整体准确性。此外,概率回归可以提供估计值的不确定性或置信度的度量,这有助于根据模型做出决策或预测。当处理有噪声或不完整的数据时,估计值的不确定性较高,这可能是有益的。
非线性概率回归
让我们跳到一个例子,让这些概念更容易理解。我们不会涵盖完全贝叶斯方法,这种方法需要估计认知的不确定性——模型的不确定性。我们将在以后的文章中研究这种不确定性。然而,我们将估计一种不同的不确定性——随机不确定性。它可以定义为数据生成过程中的不确定性。
这一次,我们将讨论更复杂的回归分析——非线性回归。与使用直线对变量之间的关系进行建模的线性回归相比,非线性回归允许对变量之间更复杂的关系进行建模。它使非线性回归成为许多机器学习应用程序的有价值的工具,在这些应用程序中,变量之间的关系可能过于复杂,无法使用线性方程精确建模。
我们首先创建一些遵循非线性模式的数据:

请注意,噪声𝜖𝑖∽n(0,1)是独立的,并且同分布。
x = np.linspace(-1, 1, 1000)[:, np.newaxis]
y = np.power(x, 3) + (3/15)*(1+x)*np.random.randn(1000)[:, np.newaxis]
plt.scatter(x, y, alpha=0.1)
plt.show()

图 2:遵循带有高斯噪声的非线性方程人工生成的数据。
像往常一样,我们将对数似然定义为损失函数。
def negative_log_like(y_true, y_pred):
return -y_pred.log_prob(y_true)
正如我们在以前的文章中看到的,将我们的确定性深度学习方法扩展为概率性的方法是通过使用概率层,例如DistributionLambda。回想一下,DistributionLambda层返回一个分布对象。它也是 TensorFlow Probability 中实现的其他几个概率层的基类,我们将在以后的文章中使用它们。
为了建立我们的模型,我们从添加两个致密层开始。第一个有 8 个单元和一个 s 形激活函数。第二个有两个单元,没有激活功能。我们没有添加一个,因为我们想要参数化我们的高斯分布,该分布跟随具有任何真实值的第二密集层。高斯分布由DistributionLambda层定义。请记住,分布的规模是标准差,这应该是一个正值。如前所述,我们通过 softplus 函数传递张量分量来考虑这种约束。
请注意,线性和非线性模型之间的真正区别是添加了密集层作为模型的第一层。
model = Sequential([
Dense(input_shape=(1,), units=8, activation='sigmoid'),
Dense(2),
tfpl.DistributionLambda(lambda p:tfd.Normal(loc=p[...,:1], scale=tf.math.softplus(p[...,1:])))
])
model.compile(loss=negative_log_like, optimizer=RMSprop(learning_rate=0.01))
model.summary()
我们可以检查模型的输出形状,以便更好地理解正在发生的事情。我们得到一个空的事件形状和一个批处理形状(1000,1)。1000 指的是批量大小,而额外的维度在我们的问题陈述中没有意义。我们想要表示正态分布的单个随机变量。
y_model = model(x)
y_sample = y_model.sample()
y_model
<tfp.distributions._TensorCoercible 'tensor_coercible' batch_shape=[1000, 1] event_shape=[] dtype=float32>
我们可以使用 TensorFlow Probability 提供的包装器来简化最后一层的实现,并使它更符合我们期望得到的输出形状。通过使用IndependentNormal层,我们可以构建一个类似于用DistributionLambda构建的发行版。同时,我们可以用一个静态的方法,输出概率层需要的参数个数来定义前面Dense层的单元个数:tfpl.IndependentNormal.params_size。
model = Sequential([
Dense(input_shape=(1,), units=8, activation='sigmoid'),
Dense(tfpl.IndependentNormal.params_size(event_shape=1)),
tfpl.IndependentNormal(event_shape=1)
])
model.compile(loss=negative_log_like, optimizer=RMSprop(learning_rate=0.01))
model.summary()
如我们所见,该形状现在已被正确指定,因为批处理形状中的额外维度已被移动到事件形状中。
y_model = model(x)
y_sample = y_model.sample()
y_model
<tfp.distributions._TensorCoercible 'tensor_coercible' batch_shape=[1000, 1] event_shape=[] dtype=float32>
是时候让模型符合我们合成生成的数据了。
model.fit(x=x, y=y, epochs=500, verbose=False)
model.evaluate(x, y)
正如预期的那样,我们能够捕捉到数据生成过程中的任意不确定性。从下面我们可以得出的置信区间可以看出。概率模型的一个更有趣的特征是我们可以生成的样本,正如我们在下面看到的,遵循数据的原始生成过程。
y_hat = y_model.mean()
y_sd = y_model.stddev()
y_hat_u = y_hat -2 * y_sd
y_hat_d = y_hat + 2*y_sd
fig, (ax_0, ax_1) =plt.subplots(1, 2, figsize=(15, 5), sharey=True)
ax_0.scatter(x, y, alpha=0.4, label='data')
ax_0.scatter(x, y_sample, alpha=0.4, color='red', label='model sample')
ax_0.legend()
ax_1.scatter(x, y, alpha=0.4, label='data')
ax_1.plot(x, y_hat, color='red', alpha=0.8, label='model $\mu$')
ax_1.plot(x, y_hat_u, color='green', alpha=0.8, label='model $\mu \pm 2 \sigma$')
ax1.plot(x, y_hat_d, color='green', alpha=0.8)
ax1.legend()
plt.show()

图 3:从概率非线性回归模型生成的样本(左边)及其与数据的拟合(右边)。
结论
本文探讨了确定性回归和概率性回归之间的主要区别。我们看到,当自变量和因变量之间的关系很好理解且相对稳定时,确定性回归是实用的。另一方面,当数据存在不确定性或可变性时,概率回归更合适。作为一个练习,我们然后将一个概率模型拟合到非线性数据中。通过在我们的模型的开始添加一个具有激活函数的额外密集层,我们可以学习数据中的非线性模式。我们的最后一层是概率层,它输出一个分布对象。为了与我们的问题陈述更加一致,我们扩展了我们的方法,使用了我们在几篇文章之前探索过的IndependentNormal层。它允许我们将批次维度移动到事件形状。接下来,我们成功地拟合了数据,同时提供了对任意不确定性的度量。最后,我们生成了新的样本,这些样本严格遵循数据的原始生成过程。
下周,我们将探讨频繁投资者和贝叶斯方法之间的区别。到时候见!
保持联系: LinkedIn
参考资料和材料
[1] — Coursera:深度学习专业化
[2] — Coursera:深度学习的 tensor flow 2专业化
[3] — 张量流概率指南和教程
[4] — TensorFlow 博客中的 TensorFlow 概率帖子
初学者的概率分布
原文:https://towardsdatascience.com/probability-distributions-for-beginners-d14f59aba2cb
理论系列
初级统计,基本概念,伯努利分布

埃里克·普劳泽特在 Unsplash 上拍摄的照片
如果你在数据科学/机器学习领域,你可能会决定,在某些时候,阅读学术论文或带有统计概念的软件包文档可能是有用的。这样做的时候,你可能会很快被语法和无法解释的符号表示所淹没,这可能会阻止你。它甚至会让你相信这是一个愚蠢的尝试。
这就是我阻止你的地方。不,这不是一个愚蠢的尝试。
数学写作中的一个关键问题是明显缺乏象征性的传说和相关的解释。我希望这些统计概念的描述能帮助你下次再试。一定要再试一次。
“我失败了一次又一次。这就是我成功的原因。”—迈克尔·乔丹
1.先决条件
对于那些熟悉随机变量的人,跳到下一节:分布函数。
1.1 概率
- 没有机会发生的事情概率为 0。
- 一定会发生的事情概率为 1。
1.2 随机变量:Y
随机变量是具有内在随机性的汇总变量。把它想成一个多用途变量。它总结了许多价值观。让我们用一个抽象的例子。如果我们让随机变量 Y 表示“亚马逊购物车中的商品数量”,我们可以在计算中使用这个 Y 变量,而无需假设给定购物车中商品的实际数量。通过这样做,我们可以推导出逻辑关系,例如使用汇总变量的可能性、功效和假设检验,而不强加 y 的假定值。一旦建立了关系,我们就可以使用真实数据。
我们需要将两个东西与每个随机变量联系起来:(1)逐点分布函数和(2)累积分布函数(CDF)。

爱德华多·苏亚雷斯在 Unsplash 上拍摄的照片
1.3 逐点分布函数
离散……P(Y = Y)…Y = Y 的概率
连续的……f(y)……Y=y 的概率密度
Y 的实现值称为小写 Y。带有下标 Y 的小写 f 表示分布函数特定于随机变量 Y,使用实现 Y 计算。
随机变量是一个函数。函数从一个值映射到另一个值。随机变量从样本空间中的输入:事件映射到输出:实数。
考虑 y=mx+b .输入:x .输出:y。
考虑购物车 Y. Input: empty cart 事件。输出:0。
一个随机变量必然与概率有关。这需要第二个函数,逐点概率分布。它将接受 y 的实现值 y 的输入。它的输出是介于 0 和 1 之间的奇异概率。
离散情况……P(Y = Y)……Y = Y 的概率
在我的亚马逊购物车中,一个常见的事件是我的购物车是空的。(饥饿的学生。)Y 把这个“事件”实现映射到一个实数:0。在某一天,y 为 0 的概率可能是 0.95。那就写成 P(Y=0)=0.95。因此,我们可以说 Y 取值的概率是由某个函数表示的。这个函数被称为概率质量函数(PMF;离散情况)或概率分布函数(PDF 连续情况)。
离散与连续的区别指的是输入中的数据类型。对于任何给定的随机变量,它的逐点概率函数要么是 PMF 要么是 PDF,不可能同时是两者。如果我们只有整数输入(如计数),这将是离散输入的一个例子。如果我们有跨越一个范围的输入(例如,正实数(0,∞)),这将是连续输入。
连续案例….. f(y)……Y=y 的概率密度
为了提供一个连续情况的快速示例,让我们定义随机变量 W。W 可以是亚马逊订单的价格,其范围可以从 0 美元到 32.55 美元到 10,000.67 美元或更高。没有完整的基于微积分的讨论,细节变得模糊不清,但是,对于连续的情况,这里要知道的关键是连续 W 的 PDF 类似于 但不同于离散 y 的PMF
Y 取给定值 Y 的概率可以是非零数字。
pr(Y = Y)= {对于某些值不为零}
然而,W 取给定值的确切概率总是零。我知道这不是直觉。对于更长的描述,尝试这个好读。不过简而言之就是 f(x)是瞬时密度,也就是说它的权重为零,或者在任意给定值 的概率为零。就当是一时冲动吧。然而,在 w 范围内的积分将是非零的,正概率在 0 和 1 之间。
pr(W = W)= {所有值为零}
因为 W 是 恰好是 a 的概率是 0,那么这几个表达式是等价的:
Pr(a
Pr(a

Two cases for pointwise probability: PMF and PDF
1.4 累积分布函数
离散………P(Y≤Y)…Y≤Y 的概率
连续……F(y)……..Y≤y 的概率密度
Y 的 CDF F(y)是变量取值小于或等于特定实现值 Y 的概率。对于购物车,计算 P(Y ≤ 0)将告诉我任何购物车为空购物车的概率。
CDF 是 PMF 或 PDF 的函数。它使用 PMF 的级数或 pdf 的积分对实数范围内的逐点概率求和。
综合发展框架的主要特性
1.CDF F(y)是 y 的非减函数,这意味着 F(y)要么保持不变,要么从左向右增加,作为 y 的函数。
2.随着 y 趋近-∞,F(y)将趋近于 0。当 y 接近∞,F(y)将接近 1。
3.F(y)是右连续的。这意味着图上的空心圆将总是在线段的右侧。

两种情况(数学上),都叫 CDF
对于离散情况,Y 小于或等于 b 的累积概率是从 k=1 到 k=b 的所有 PMF 概率的总和
对于连续情况,Y 小于或等于 b 的累积概率是从 y=-∞到 y=b 的所有 PDF 概率的积分。
2.第一次分配
2.1 伯努利
- 输入:离散的二进制域
- 输出:概率在 0 和 1 之间

我写的
我们将从最简单的概率分布开始。对于中级和高级发行职位,这些将是即将推出的。
伯努利分布是离散概率的基础。对于二进制输出,比如掷硬币,对于一次投掷,正面或反面的概率是多少?公平硬币还是加权硬币?这两种情况都被伯努利分布所捕获。一个结果将被命名为“成功”。这是任意的,但必须始终如一地使用。我们将称首脑会议的结果为成功。伯努利分布给了我们一次抛硬币成功的概率。
就我个人而言,虽然我经常捡硬币(我知道这很有争议),但我很少费心去扔硬币。然而,作为 ML 实践者,我们可以用这个分布来描述一个更相关的场景:当模型预测者与结果不相关或几乎不相关时。如果我构建了一个糟糕的模型,并且如果预测因子无关紧要,则每个模型预测可能取决于某个比率 p。该模型可能预测“成功率”为 p 的类 1 和比率为 1-p 的类 0(记住样本空间中每个值的概率总和为 1)。然后,我们真的可以节省运行坏模型的时间。我们可以通过给定 p 的伯努利分布来预测类 1 的概率。给定数据,我们可以稍后估计比率 p。如果 20%的训练集是类 1,则通过矩法或 MLE 估计 p 将是 20%。
图由我使用数据包装器
分布的最后一点是参数。在 frequentists 统计中,参数是一个常数。对于任何 RV,PDF/PMFs 和 CDFs 都是参数的函数。在上面的伯努利 PDF 图中,我们可以看到 Pr(y=0|p=0.2) =0.8。给定 0.2 的成功率,获得 0 的观测值的概率为 0.8。我们可以用上面的等式来计算。
然后,我们可以对这些分布求和,得到下面的 CDF。请注意,CDF 也是参数 p 的函数,我们可以看到,CDF 从 0 开始,到 1 结束。此外,阶跃函数 CDFs 中存在不连续性。这是离散结果的特征,而不是连续结果的特征。
伯努利 CDF

我用 R 算出
如果我们不进行 n 次翻转或 n 次预测,并且每次翻转/预测都不受前一次的影响,我们可以使用伯努利 n 次。在 n 次试验中,看到人头一定次数 x 次的概率有自己的分布,称为二项式分布。注意:这个特殊的发行版是对未来文章的一个试探。
结束了。
请在下面评论您希望在未来帖子中看到的统计问题或评论!
参考
喜欢这篇文章吗?下面更喜欢!
https://www.linkedin.com/in/kate-wall/
数据科学需要了解的概率分布(带代码)
了解数据的属性

罗伯特·斯汤普在 Unsplash 上拍摄的照片
概率统计知识是数据科学和机器学习的核心;你需要统计学和概率知识来有效地收集、回顾、分析和交流数据。这意味着你必须很好地掌握一些基本术语,它们的意思,以及如何识别它们。
你会经常听到的一个术语是“分布所有这些都是指数据的属性。现实世界中有几个被认为是统计性质的现象(例如,天气数据、销售数据、金融数据等)。这意味着在一些情况下,我们已经能够开发出一些方法,帮助我们通过能够描述数据特征的数学函数来模拟自然。
“[……]概率分布是一种数学函数,它给出了实验中不同可能结果出现的概率。”
— 维基百科
了解数据的分布有助于我们更好地模拟我们周围的世界。它帮助我们确定各种结果的可能性,或对某一事件的可变性做出估计。所有这些都使得了解不同的概率分布在数据科学和机器学习中非常有价值。
在本文中,我们将介绍一些发行版,并分享一些 Python 代码来直观地展示它们。
均匀分布
你可能遇到的最直接的分布是均匀分布;均匀分布是一种概率分布,其中所有结果的可能性相等。例如,如果我们掷一个公平的骰子,落在任何数字上的概率是 1/6。这是一个离散的均匀分布。

离散均匀分布的概率质量函数(PMF);图片作者。
然而,并非所有的均匀分布都是离散的,它们也可以是连续的。这意味着有无限多的同样可能的可测量值,它们可以取指定范围内的任何真实值。a 和 b 之间连续均匀分布的概率密度函数(PDF)如下:

连续均匀分布的概率密度函数;作者图片
让我们看看如何用 Python 对它们进行编码:

上面代码的输出:Python 中的离散和连续均匀分布;图片作者。
正态分布
高斯分布可能是你最熟悉的分布。它有几个名字:有人称它为钟形曲线,因为它的概率图看起来像一个钟;有人称它为高斯分布,以第一个描述它的德国数学家卡尔·高斯的名字命名;还有人称它为正态分布,因为早期的统计学家注意到它反复出现。
正态分布的概率密度函数如下:

正态分布的概率密度函数;作者图片
σ是标准偏差,μ是分布的平均值。但是需要注意的是,在正态分布中,均值、众数和中位数都是相等的。
当我们绘制正态分布的随机变量时,曲线关于平均值是对称的,一半的值在中心的左边,另一半在中心的右边。曲线下的总面积是 1。

上面代码的输出:Python 中的高斯分布;图片作者。
关于正态分布的一些已知的东西是经验法则。经验法则告诉我们数据的百分比落在平均值的一定数量的标准偏差内。这些百分比是:
- 68%的数据在平均值的一个标准差范围内。
- 95%的数据落在平均值的两个标准偏差内。
- 99.7%的数据落在平均值的三个标准偏差内。
对数正态分布
对数正态分布是对数呈正态分布的随机变量的连续概率分布。因此,如果随机变量 X 是对数正态分布,那么 Y = ln( X )具有正态分布。
[ 来源 : 维基百科 ]
这是对数正态分布的 PDF:

对数正态分布的概率密度函数;作者图片
对数正态分布的随机变量只取正的实数值。因此,对数正态分布产生右偏的曲线。
让我们用 Python 来绘制这个:

上面代码的输出:Python 中的对数正态分布;图片作者。
泊松分布
泊松分布是以法国数学家 T2 的名字命名的。这是一种离散的概率分布,这意味着它对具有有限结果的事件进行计数,换句话说,这是一种计数分布。因此,泊松分布被用来显示一个事件在特定时期内可能发生的次数。
如果一个事件以固定的速率及时发生,那么及时观察到的事件数量(n)的概率可以用泊松分布来描述。例如,顾客可能以平均每分钟 3 英镑的速度到达咖啡馆。我们可以使用泊松分布来计算 9 个客户在 2 分钟内到达的概率。
下面的概率质量函数公式有所帮助:

泊松分布的概率质量函数(PMF);作者图片
λ 是单位时间内的事件率——在我们的例子中,它是 3。 k 是出现的次数——在我们的例子中,是 9。我们可以使用Scipy来解决我们的示例问题。
泊松分布的曲线类似于正态分布,λ表示峰值。让我们用 Python 绘制一个,看看这在视觉上是什么样子。

上面代码的输出:Python 中的泊松分布;图片作者。
指数分布
指数分布是泊松点过程中事件之间时间的概率分布。
[ 来源 : 维基百科
指数分布的概率密度函数如下:

指数分布的概率密度函数;作者图片
λ 是速率参数, x 是随机变量。
让我们看看如何用 Python 绘制指数分布:

上面代码的输出:Python 中的指数分布;图片作者。
二项分布
你可以把二项分布想象成实验中成功或失败的概率。有些人也可能把它描述为抛硬币的概率。
这是维基百科的定义:
参数为 n 和 p 的二项分布是一系列 n 个独立实验中成功次数的离散概率分布,每个实验都提出一个是-否问题,并且每个实验都有自己的布尔值结果:成功或失败。
[ 来源 : 维基百科 ]
本质上,二项式分布衡量两个事件的概率。一个事件发生的概率为概率 p,另一个事件发生的概率为 1-p。
这是二项式分布的公式:

二项分布的公式;来源: 谷歌页面
让我们用 Python 来看看:

上面代码的输出:Python 中的二项式分布;图片作者。
学生 t 分布
Student 的t-分布(或简称为t-分布)是连续概率分布族中的任何成员,在样本量较小且总体标准偏差未知的情况下估计正态分布总体的平均值时会出现这种分布。它是由英国统计学家威廉·希利·戈塞以笔名“学生”开发的
[ 来源 : 维基百科
PDF 如下:

学生 t 分布的概率密度函数(PDF);作者图片
n 是自由参数,称为“自由度”,但您也可以将其称为“ d.o.f. ”。对于高 n 值,t 分布更接近正态分布

上面代码的输出:Python 中学生的 t 分布;图片作者。
卡方分布
卡方分布是伽马分布的特殊情况;以 k 为自由度,卡方分布对于某些独立的标准正态随机变量来说是k 的平方和。
这是 PDF:

卡方分布的概率密度函数;作者图片
这是一种常见的概率分布,通常用于假设检验和构建置信区间。
让我们用 Python 画一些示例图:

上面代码的输出:Python 中的卡方分布;图片作者。
对于数据科学来说,掌握统计学和概率是非常重要的。在本文中,我向您展示了一些您可能想要记住的发行版,因为您可能会在旅程中的某个时刻遇到它们。请随意保存和参考这篇文章,以便将来记录。反馈对我也非常有帮助。
感谢阅读。
如果你喜欢阅读这样的故事,并希望支持我的写作,可以考虑成为一名灵媒。每月支付 5 美元,你就可以无限制地阅读媒体上的故事。如果你使用我的注册链接,我会收到一小笔佣金。
已经是会员了?订阅在我发布时得到通知。
*https://kurtispykes.medium.com/subscribe *
数据科学家的概率:有效卡方分布
理论系列
分布函数的交互式可视化
本文的目的是介绍卡方概率分布。在生成一系列关于概率的文章时,我的目标是以直观、简洁、有用的方式描述每个分布。不会有推导或证明的重点。而是希望把重点放在围绕分布的直觉上。
本文未涉及的发行版:
- 初学者的概率分布
- 数据科学家的概率:伟大的几何分布
- 数据科学家的概率:强大的泊松分布(链接即将推出)
- 更多的发行版和可视化教程在我的页面
键
RV =随机变量。
如果不熟悉随机变量,先决条件解释可以在这里找到。
卡方分布为什么重要?
这种分布是一个强有力的理论模型。
它的威力来自 3 个关键的统计特性:
1.中心极限定理本质上表明,对于来自许多不同总体*的样本,随着样本量的增加,样本均值遵循正态分布。
2.通过简单的算术变换,任何遵循正态分布的随机变量都可以被“标准化”,以表示 0 和方差 1,即标准正态分布。
3.对标准正态分布的任何变量求平方都会产生一个卡方随机变量。
因此,任何正常表现的量都可以转换为卡方量!这一点非常重要。
什么是卡方随机变量?
有多种不同的方法来定义卡方 RV。在这里,我将展示其中的三个。
方式 1
虽然最初很可怕,但对我来说最简单的定义如下。卡方 RV 是由以下概率密度函数描述的任何 RV:

作者写的
第一个方程 f(x|p)是“给定 p 个自由度,X=x 的概率密度”。本质上,把这句话解释为“X 取已实现价值 X 的概率”是 p 的函数!
第二个等式指定 p=1 个自由度。
你会问,什么是自由度?自由度只是一个参数。参数改变函数的形状。在下面的 PDF 和 CDF 图中,您可以看到 PDF 的形状随着 p 值的变化而急剧变化。我们在许多统计问题中的目标是从我们的数据中估计 p。
重复一遍。在许多统计问题中,我们的目标是从数据(给定样本)中估计 p(人口的一个属性)。
这是数据科学和机器学习中的一个共同主题。神经网络是做什么的?它有一堆参数,这些参数是使用反向传播从数据中估计出来的。这是我们在许多问题中所做的非参数扩展。
在上面的公式中,我们还可以使用恒等函数来使其在数学上更加正确。当 x 在 0 和正无穷大之间时,identity 函数“打开”(乘以值 1)概率,对于 x 的所有其他值,“关闭”(乘以值 0)。
方式二
根据定义,PDF 的积分是 CDF * *。我们也可以说,任何卡方 RV 都由以下形式的 CDF 指定:

作者写的
暂停。
有时候缩写很有用,有时候又很讨厌。在这个例子中,我想指出一种情况,缩写可能是一种麻烦,而不是一种帮助。如果您参考卡方分布的 wikipedia 页面,您可能会看到如下所示的表单:

维基百科页面上的 CDF 表情
它指定小 doo-dad γ (gamma)表示下不完全 gamma 函数。如果您碰巧交叉引用了 wolfram alpha,您会发现一种形式的下不完全伽玛函数,如下所示:

来自 Wolfram Alpha 的下不完全伽玛函数
如你所见,这只是我第一次写的方程的一种(过于)复杂的写法。
方式 3
在不进行完全推导的情况下,我将给出指定卡方的另一种方法。
任何标准正常 RV 的平方都是具有 1 个自由度的卡方 RV。k 个独立的标准正态 RV 的平方之和是具有 k 个自由度的卡方 RV。这里,我们假设 Za、Zb 和 Zc 是独立的。那么,它们的平方和就是 df=3 的卡方。

作者写的
注意这些定理的含义。因为卡方来自平方值,所以它将只取 0 到无穷大之间的 x 值。这一特性有助于对与误差相关的量进行建模。
许多关于收敛或随机变量极限行为的数学证明,都依赖于包含一个误差/容差项。该公差项始终被视为大于 0 的正值。实际上它也是一个小数值。例如,简要考虑以下概率收敛的定义:

在这个语句中,ε大于 0。我们对ε没有其他的规定,但是没有它,我们就不能定义收敛!
这很重要!!停止分区。收敛很重要,因为它允许我们观察随机变量样本的行为如何随着观察样本量的增加而变化。这意味着当我们收集大量数据时,我们可以从逻辑上量化我们正确估计参数的能力!
最后,正态分布用于模拟理想化的场景,因为它具有方便的对称性,这赋予了它良好的数学特性。它的近亲,卡方分布,同样可以用来模拟许多理想化的场景。因此,它在统计中被大量使用。
注* CLT 的人口需求
- 独立样本抽取。
- 总体均值存在。
- 总体方差是存在的,而且是有限的。
注意** PDF/CDF 关系

由作者撰写,参考文献[1]
概率质量函数
现在,我们可以观察参数值如何移动 PDF。PDF 描述了 y 的每个连续值的概率。单击播放并拖动该条来更改参数 p。对于卡方分布,该参数只能是正整数。这是前面提到的求和的结果。对于 p=8,Y 为 5 的概率为 0.11。
作者绘制的图形

作者对 p ={1,…,20}的 PDF 追踪
对于交互式绘图,由于宿主图像大小的限制,我减少了显示的参数(仅显示偶数值);但是,所有值都显示在矩阵图上。
累积分布函数
最后,我们可以观察参数值如何改变累积分布函数(CDF)。CDF 描述了 Y 的每个离散值的概率,点击播放,拖动滚动条改变参数 p,对于 p=8,Y 小于等于 10 的概率为 0.74。
作者绘制的图形

作者对 p ={1,…,20}的 CDF 追踪
更多关键特性
- 由 1 个参数 p 指定
- 期望值= p =变量的平均值

作者写的
3.方差=2p =值的分布
卡方分布的优良性质(高级)
- 中心性/非中心性!
- 与 t 分布的关系
- 与 f 分布的关系
- 伽马分布的特例
- 广泛用于假设检验

由作者生成
那是一个包裹。为了访问我生成的图像的代码,完整的 github 笔记本被链接到。请在下面评论您希望在未来帖子中看到的统计问题或评论!
参考
- 卡塞拉,G. &伯杰,R. L. 统计推断。(森盖奇学习,2021)。
- https://en . Wikipedia . org/维基/Convergence _ of _ random _ variables # Convergence _ in _ probability
- https://en.wikipedia.org/wiki/Chi-squared_distribution
- https://www.wolframalpha.com/
喜欢这篇文章吗?更像下面的。
https://www.linkedin.com/in/kate-wall/
数据科学家的概率:伟大的几何分布
理论系列
PMF/CDF 的交互式可视化&模拟真实数据
本文的目的是介绍几何概率分布。这是概率系列文章之一。我的目标是以直观、简洁、有用的方式描述每个发行版。不会有推导或证明的重点。相反,我希望把重点放在分布的效用上,而不是推导上。
本文未涉及的发行版:
- 初学者的概率分布
- 数据科学家的概率:卡方分布
- 数据科学家的概率:强大的泊松分布(链接即将推出)
- 更多的发行版和可视化教程在我的页面
什么是几何分布?
几何分布模拟了许多次失败,直到第一次成功。
换句话说,它是一个等待时间分布。它描述了一个场景,在这个场景中,通过 y 次失败,您一直等待,直到某个事件发生。它回答了这样一个问题,“我经历了 y 次失败才看到成功的概率是多少?”
例子
想象一下,每天按顺序打开和阅读媒体文章。当你读到一篇你喜欢的文章时,你就成功了。当你读到一篇你不喜欢的文章时,就会出现失败。你一直读到写完一篇你喜欢的文章。然后,你马上结束午休,回去工作。打开应用程序。
事件
星期一……失败,失败,失败,成功!
星期二……失败,成功!
星期三…失败,失败,失败,失败,失败,失败,成功!
周四…..成功!
星期五…..…成功!
我们收集了 5 个连续试验的观察结果。我们可以把这些观察总结如下。
从样本空间映射到实现值 y1,y2,y3,y4,y5:
星期一……3 失败直到成功
星期二……..一
星期三…6。
星期四……0
星期五……0
但是这些只是可能的 y 值的 5 种可能的实现。暂时将这些值留在这里。我们一会儿还会用到它们。
我们如何用数学方法描述所有可能的值?
"几何分布模拟了第一次成功之前的失败次数(y). "
对于任何给定的序列,失败会发生 y 次。成功在序列结束前发生一次。因此,我们将失败的概率提高到 1 次方,将成功的概率提高到 1 次方。

其中 y 是集合{0,1,2,…,∞}中的任意值
嗯,还不算太糟。虽然在上面的等式中不明显,但是在上面的公式中做出了 3 个关键假设。

这是一个很好的表达方式。这是一组很好的观察,但是这有什么用呢?

克里斯蒂安·霍尔辛格在 Unsplash 上拍摄的照片
发行版的使用
现在我们有了一个观测值的概率表达式,我们可以为多个观测值生成一个联合概率表达式。联合概率在数学上等同于似然性。(注:虽然联合概率和可能性在数学上包含相同的表达式,但它们没有等价的解释。)给定我们的观察,最大化似然性产生参数 p 的估计。
该分布允许我们从样本 y1,y2,y3,y4,y5 估计 p̂。这个估计 p̂是我们对总体真实参数 p 的最佳猜测。样本 y1、y2、y3、y4、y5 中的观察值来自中等用户体验人群。
我们可以手动导出最大似然估计,在许多统计课程中,我们会这样做。但是有很多关于这些细节的文章。
让我们跳到使用 R 中的 EnvStats 包来分析几何分布数据并产生 MLE 估计。

按作者输出
我们从 y~geometric(p)的 5 个观测值的样本中估计任何试验都有⅓成功概率。然后,我们可以使用这个 p 估计来模拟媒体用户活动的未来观察。预测行为可能会引导未来的商业决策。
概率质量函数
现在,我们可以观察参数值如何改变概率质量函数(PMF)。PMF 描述了 Y 的每个离散值的概率。单击播放并拖动该条来更改参数 p。对于 p=0.6,Y 为 1 的概率,即等待时间为 1 的故障,为 0.6。
作者精心制作的互动图形
下图跟踪:

累积分布函数
最后,我们可以观察参数值如何改变累积分布函数(CDF)。CDF 描述了 Y 的每个离散值的概率,点击播放,拖动滚动条改变参数 p,对于 p=0.6,Y 小于等于 1.5 的概率为 0.6。
请注意,如果不对图进行大量定制,很难显示 CDF 的步进性质。在每个整数值处,累积概率会发生变化,因为 CDF 是右连续的。1 因此,对于 p=0.6,Y 小于或等于 2 的概率为 0.84。
作者绘制的互动图形
以下所有图迹。

关于如何生成上述图形的代码,请参见下面的 GitHub 笔记本。
更多关键特性
- 仅由 1 个参数 p 指定!
- 期望值——给定 p,分布的预期失败次数
- 方差——给定 p 时,故障数量相对于平均值的分布
- 备选参数化:设 y= '试验次数'(点击次数)而不是'失败次数'。这只是意味着我们让 y 为总点击次数,其中包括最后一次成功。

Y 的期望值
Y 的期望值是失败概率和成功概率的比值。

Y 的方差
Y 的分布是失败概率和成功概率的平方的比值。
几何分布的美好性质(高级)
- 无记忆!
- 每一次试验都可以被描述为一次伯努利试验
- 负二项分布的特例
- 指数分布的离散模拟
- 广泛用于随机建模
分布的高级性质在推导中非常有用。然而,它们在发行版初学者入门中没有用。需要额外的推导。如果你想知道将来如何使用这些属性,请发表评论!
那是一个包裹。
参考
喜欢这篇文章吗?下面更喜欢!
https://www.linkedin.com/in/kate-wall/
机器学习的概率
原文:https://towardsdatascience.com/probability-for-machine-learning-b4150953df09
概率在机器学习中是如何使用的?

1.介绍
在本帖中,我们将介绍概率论的构建模块,并使用这些知识来激发机器学习的基本思想。在第一部分,我们将讨论随机变量以及它们如何帮助量化真实世界的实验。下一节将讨论概率分布函数。最后一节将讨论如何将这些数学概念结合起来解决机器学习问题。
2.随机变量
让我们从一个有趣的实验开始我们的旅程。拿笔和纸;走到你家门前的大街上。看着每一个从你身边走过的人,注意他们头发的颜色;以厘米为单位的高度近似值;以及其他你感兴趣的细节。这样做大约 10 分钟。
恭喜你!你进行了你的第一次实验!通过这个实验,你现在可以回答一些问题:有多少人从你身边走过?走过你身边的人有多少是蓝头发的?从你身边走过的人的平均身高是多少?也许在这个实验中, 10 人从你身边走过;这些人中有 3 人有蓝色的头发;并且他们近似身高的平均值可能是 165.32 厘米。对于这些问题中的每一个,我们都给它绑定了一些数字;我们把它和一些可测量的量联系在一起。
随机变量是将实验结果映射到可测量数量的函数。
我们现在可以用一个随机变量来表示 3 个问题中的每一个。例如,设 X₁ 为随机变量,代表走过你身边的人数。从定义上注意,随机变量是函数。因此,我们可以在功能符号中编写以下内容。

等式 1
这意味着随机变量 X₁ 是一个将“走过你身边的人数”(一个实验结果)映射到某个非负整数 10 (一个可测量的量)的函数。同样,让 X₂ 成为随机变量,代表走过你身边的蓝头发的人的数量。然后我们可以写下面。

等式 2
这意味着 X₂ 是一个将“走过你身边的蓝头发的人的数量”(一个实验的结果)映射到一个非负整数量 3 (一个可测量的量)的函数。同理,设 X₃ 为随机变量,代表从你身边走过的人的平均身高。

等式 3
这意味着 X₃ 是一个将“从你身边走过的人的平均高度”(一个实验结果)映射到一个非负实数 165.32 (一个可测量的量)的函数。这些随机变量之所以如此有用,是因为它们能够将观察人类的有趣实验变成我们可以用来进行数学运算的数字。在下一节中,我们将看到这些随机变量是如何构成概率分布函数的核心的。
3.概率分布函数
随机变量为我们提供了一种量化实验结果的方法。但是这些结果(以及随机变量)到底是如何表现的呢?我们可以用概率分布函数来理解这一点。
在我们的实验中,我们看到 10 个人从我们身边经过。“10 个人”就是一个“结果”的例子。然而,我们可以有 0 个人超过我们,或者 1 个人超过我们,或者 2 个人超过我们,等等。因此,实验的所有可能结果的集合 S 如下。

等式 4
这组可能的结果被称为“事件”。现在让我们用正式的数学符号写出等式 4 。设单次实验的结果为ω ₁ 对应的随机变量为 X₁.然后我们可以把所有这些可能的结果写成如下。

等式 5
大括号{。}代表一个集合;冒号“:”翻译成英语短语“这样”;ℤ⁺代表了 X₁可以接受的非负整数值的集合。给定这个上下文,等式 5 翻译成下面的英语语句。
事件 S 是一组 结果 ω₁ 使得 随机变量 X₁ 可以取任何非负整数的值。
事件 S 发生的概率是 100% ,因此不太有趣。通常,我们感兴趣的事件是 S 的子集。例如,我们可能有兴趣回答“两个人超过我们的概率是多少?”。我们可以将另一个事件 A 定义为我们感兴趣的一组结果;对于我们的例子,我们只对“两个人”的结果感兴趣。

等式 6
我们可以用下面的数学符号更一般地写出来。

等式 7
在英语中,我们可以将等式 7* 翻译成以下语句。*
事件 一个是一组 结果 ω₁ 使得 随机变量 X₁ 可以取值为 2。
既然我们已经了解了一个“事件”,现在让我们定义一个概率分布函数。
概率分布函数是将一个事件映射到该事件发生概率的函数
设 P 为概率分布函数。根据定义, P 是一个函数。因此 P 接受一个输入并返回一个输出。这个概率分布函数的输入是一个事件,而输出是某个概率值。在数学记数法中,我们可以把 P 写成如下方式。

等式 8
使用来自等式 7 的事件 A 在等式 8 中的定义,我们得到以下符号。

等式 9
这个等式看起来很混乱。但是使用随机变量,我们可以用更简洁和直观的方式重写方程 9 。

等式 10
通过使用随机变量,我们可以利用等式 10 中显示的简明数学符号,而不是等式 1-3 中显示的冗长的英文陈述以及等式 9 中冗长的数学符号。现在让我们来谈谈概率分布函数的类型;希望随着我们继续讨论,随机变量的效用会变得更加清楚。
3.1 概率质量函数
这是一个离散随机变量的概率分布函数。这个函数接受一个随机变量的值,并将它映射到一个概率值。例如,概率质量函数 p ₓ可以写成下面的数学符号。

等式 11
X 是离散随机变量; x 是它可以采用的样本值。根据这个定义,在 X=x 的概率质量函数是随机变量 X 取值 x 的概率。你可以把这个输出看作随机变量的概率质量;这就像物理学中的质量。**

等式 12
如果一个随机变量可以取有限个值或可数无限个值,那么它就是离散的。
在我们的示例实验中,我们讨论了一个随机变量 X₁ ,它代表走过你身边的人数。理论上,如果你在一个安静的农村地区,这个数字可能低至 0 ,如果你在一个假想的世界大都市的中心,这个数字可能高达世界人口(大约 80 亿)。因此 X₁ 可以取 80 亿个不同值中的一个: 1,2,3,…,8⨉10⁹;这是有限数量的值。因此, X₁ 是一个离散的随机变量。随机变量 X₁ 的概率分布函数可能如下图所示。

图 1
x 轴显示 X₁ 可以取的不同整数值。 y 轴显示与 X₁ 的相应值相关的概率。从图中我们可以推断出实验过程中 10 人经过你的概率在 0.03 左右。换句话说,与 X₁ = 10 关联的概率质量为 0.03 。**

等式 13
给定概率质量函数的这个定义,让我们讨论它们表现出的两个性质。
性质 1 :对于随机变量可以取的每一个值,概率质量函数的值都是大于等于 0 的概率数。在数学语言中,我们可以为随机变量 X₁ 写如下。

等式 14
ℤ⁺代表了 X₁可以接受的非负整数值的集合。
属性 2 :如果我们取随机变量 X₁ 可以假设的每一个可能的值,并确定这些值中每一个的概率质量函数,总数将是 1 。

等式 15
希望离散随机变量和概率密度函数的概念有意义。
3.2.概率密度函数
这是一个连续随机变量的概率分布函数。该函数接受连续随机变量的值,并将其映射到概率值。概率密度函数 f ₓ可以写成以下数学符号。

等式 16
根据这个方程,概率密度函数是连续随机变量 X 在无限小的区间内取值时除以区间(体积)本身长度的概率(质量)的分数。因此这里看到的密度和物理学中的密度是一样的。

等式 17
为了清楚起见,我们要求在等式 16 中有一个极限,因为 X 是一个连续的随机变量,可以取实值。极限表示当δx从正方向接近 0 时,该密度将收敛到的值。在这一节中,我们将推导出概率密度函数的一些性质,这样就清楚了为什么我们需要它们。但首先,让我们从连续随机变量的定义开始。
如果一个随机变量可以取不可数的值,那么它就是连续的。
考虑随机变量 X₃ ,它代表实验中走过你身边的人的平均身高。该值可以是类似于 165 厘米、 170 厘米的数字。这是我们第一次尝试绘制这些数据。

图 2
这张图表很好地概括了数据。然而,这意味着实验中测得的人的平均身高只能是相隔 5 的整数值。这不一定是真的;毕竟,我们可以看到平均身高可能接近 166 厘米的情况。所以也许,我们可以把它分解,这样每一厘米都用 x 轴表示。

图 3
但是图 3 仍然不准确,因为我们可以得到平均身高值为 165.5 厘米或 165.25 厘米或 165.25495824 厘米;X₃可以进行无数次测量。因此, X₃ 是一个连续随机变量。为了在图上表示连续随机变量的分布,每个垂直矩形条的宽度需要变得无穷小;这导致平滑的曲线。

图 4
性质 1 :连续随机变量的一个核心性质是,对于随机变量可能取的任何值,它们的概率质量函数都是 0 。数学上,这可以用下面的等式来表示。

等式 18
我们将用一些微积分来说明这种情况。让我们用累积分布函数来证明这个事实。它是随机变量 X₃ 取任何小于或等于某个值 x 的概率。如果我们把 F 表示为累积分布函数,我们可以写出下面的符号。

等式 19
为了计算与连续随机变量相关的概率质量,让我们取 X₃ 的累积分布函数之间的差,当它取值为 x 和稍小的值时。让我们只用概率分布函数 P 的符号来写这个,看看发生了什么。

等式 20
对于连续变量,δx是一个无穷小的值,收敛于 0 。所以我们可以用数学符号中的极限来写这个。

等式 21
求解这个极限,我们会看到连续随机变量 X₃ 取任意值 x 的概率质量函数将会是 0 。

等式 22
直觉上,这个等式是有道理的。实验中人的平均身高恰好是165cm而不是 165 + 10 ⁻ ⁰⁰或165-10⁻⁰⁰的概率会收敛到值 0 。这意味着在处理特定点的连续随机变量时,实际上没有概率“质量”。因此,让我们用密度的概念来代替测量质量。密度是每单位体积的质量。**

等式 23
对于像 X₃ 这样的连续变量,delta x 是一个无穷小的值,收敛于 0 。数学上,我们用极限来表示。

等式 24
右手边是累积分布函数的导数的正式定义。

等式 25
性质 2 :因为累积分布函数可以取 0 到 1 之间的值,所以导数的行为类似。因此,概率密度函数的另一个重要性质是它大于或等于 0。

等式 26
我们可以两边积分,去掉导数。

等式 27
性质 3 :这显示了另一个重要性质:连续随机变量所有值的概率密度函数积分为 1。

等式 28
要了解更多关于这些概率概念的趣闻,请查看 YouTube 上的附带视频(链接见第 6 节)。**
3.3.联合概率分布
现在让我们来谈谈离散和连续随机变量背景下的联合概率分布。为了激发在机器学习/现实环境中有意义的讨论,让我们进行另一个实验。在zillow.com随机搜索一所房子,记下它包含的房价和卧室数量;这样做 10 个房子。现在让我们定义一些随机变量

等式 29
我们定义了 10 离散随机变量 X₁ 到x₁₀;每栋房子一个。这些随机变量中的每一个都是离散的,因为它们的取值是可数的。现在我们有了随机变量来将事件的结果映射到数字上,我们可以做一些分析了。例如,第一所房子有 3 间卧室的概率 mass 可以用下面的等式进行数学表示。**

等式 30
记住,这是概率质量,因为我们在处理一个离散的随机变量。类似地,第三个房子的卧室数量为 9 的概率质量可以用这个等式表示**

等式 31
联合概率分布将指示这两个随机变量同时呈现某些值的概率质量。第一个房子有 3 间卧室,第三个房子有 9 间卧室的联合概率可以用数学来表示。

等式 32
在这个实验中,我们观察了 10 所房子;如果我们写出观察到这 10 个值的联合概率,我们最终会得到下面的符号,它从每个随机变量中抽取一个值。

等式 33
这是针对概率质量函数的性质仍然成立的离散情况。也就是概率质量大于等于 0。在数学符号中,它看起来像下面这样。

等式 34
第二个属性表明,每个随机变量的所有可能值的联合概率质量总和应该为 1。

等式 35
现在让我们把这个例子推广到连续随机变量的情况。记得当我们记录这 10 所房子时,我们记下了卧室的数量和价格。让我们定义另一组 10 个随机变量。

等式 36
我们定义了 10 个连续的随机变量 Y₁ 到y₁₀;每栋房子一个。这些随机变量中的每一个都是连续的,因为它们可以取的值是不可数的。现在我们有了随机变量来将事件的结果映射到数字上,我们可以做一些分析了。例如,第一所房子低于 30 万美元的总概率密度可以用下面的等式进行数学表示。**

等式 37
同样,第三个房子低于 70 万美元的总概率密度可以用下面的等式进行数学表示。**

等式 38
联合概率密度函数的性质类似于单个随机变量的密度函数的性质。
性质 1 :例如,一个连续的随机变量在任何一点的概率质量都是 0,正如我们之前所讨论的。

等式 39
概率 2 :对于随机变量的任意值,联合概率密度将是一个大于 0 的数。

等式 40
属性 3 :同样,每个连续随机变量的所有值的总联合概率密度总和应为 1

等式 41
给定概率分布的上下文,现在让我们将所有这些与机器学习中的应用联系在一起。
4.机器学习应用
概率在机器学习中最基本的应用之一可能是在统计模型的参数估计中。让我们继续使用查看zillow.com挂牌出售的房屋信息的实验。以下是您可能会看到的一些示例清单。



***图 5:来源:【www.zillow.com ***
给定这些列表,假设我们想要构建一个统计模型,在给定房屋信息(如房屋的面积、房龄和卧室数量)的情况下,预测房屋的销售价格。

图 6
在我们的【zillow.com】实验中,让我们查看 10,000 所房子,并为每所房子收集这些信息。我们最终可以构建一个如下所示的表。

图 7
现在,让我们来谈谈数学是如何适用的。在这个实验中,我们可以将一个事件视为查看每栋房子并收集信息的行为。所以,我们可以得出一些随机变量如下。**

等式 42
换句话说,对于我们在 Zillow 上看到的每栋房子,我们可以创建 4 个随机变量。假设我们观察的第五宫是图 5 中的第一宫。我们可以定义 4 个随机变量如下。

等式 43
所以对于 10000 栋房子,我们可以用同样的方法创造 40000 个随机变量。因此,我们能够将查看 zillow.com上房屋列表的事件转化为我们可以进行数学运算的数字。
现在我们已经定义了我们的随机变量,其中哪些是离散随机变量;哪些是连续随机变量?卧室的数量是可计数的,因此所有 10,000 个 Xᵢ 随机变量都被作为离散随机变量进行分析。另一方面,房子的大小、年龄和价格都是可以有无数数值的衡量标准。因此,所有其他 30,000 个随机变量 Wᵢ、Vᵢ、Yᵢ 都作为连续随机变量进行分析。
现在让我们使用我们收集的数据和随机变量的概念来估计我们的统计模型的参数。但是在这样做之前,让我们给模型蓝图添加一些正式的数学知识。我希望统计模型是简单的线性回归。

等式 44
这是线性回归假设方程。让我们把这个方程写得更正式一点。

等式 45
注意这个方程中的变量 x,y,v,w 不是随机变量;它们是随机变量的输出可以取的特定值。 y 是我们要预测的房价; x,v,w 是我们拥有的房屋信息片段;𝜃项是我们需要估计的模型参数;ε是不可约误差。等式 X 中的𝜃项通常用一种称为最大似然估计的技术来计算。
4.2 最大似然估计
直观上,我们想确定方程 X 中最符合我们所看到的 10000 套房价的参数值;这应该是最好的模式。从数学上来说,这相当于找到最大化观测到第一套房子的房价为 y1,第二套房子的房价为 y2 等等的联合概率密度的参数值。在数学符号中,我们用下面的等式来表示。

等式 46
注意单词密度的使用,因为我们正在处理连续的随机变量。 arg max 用于表示“最大化该功能的参数值”;这个函数是联合概率密度函数。我们在机器学习中做的一个假设是,房价独立同分布;我们会打破这一点。“独立分布”意味着房屋 1 的房价不影响我们的 10,000 房屋数据集中的任何其他房屋的价格,也不受其影响;这是一个合理的假设。从数学上讲,这意味着联合概率密度与其组成部分的乘积相同。**

等式 47
因为我们知道每个 Y ᵢ 是一个连续的随机变量,我们可以使用我们学习过的概率密度函数的符号。结果是概率密度函数的乘积。

等式 48
因此,我们可以用概率密度函数的乘积来代替方程 X 的右边。

等式 49
非常好!独立同分布的第二部分是“同分布”。正如我们在连续随机变量一节中所看到的,每个随机变量的行为可能不同;因此它们有自己的分布函数。然而,我们假设第一所房子的潜在房价分布与其他 10,000 所房子相同。这意味着任何一个 Yᵢ 随机变量在相同点的概率密度是相同的。

方程式 50
这意味着我们可以重写方程 x,只使用一个随机变量 y,而不是 10,000 个 Yᵢ。

方程式 51
让我们用产品符号来压缩这个符号,如下所示。

等式 52
又漂亮了!我们有一个简洁的符号。我们在这里做什么实际上取决于我们正在构建的统计模型的类型。在我们的例子中,我们试图建立一个线性回归模型;所以假设概率密度函数遵循正态分布。完成此数学运算后,一个有趣的发现是,在线性回归情况下,𝜃的最佳值将是使残差平方和方程最大化的值,这是机器学习中的一个基本方程。如果您对这个推导过程感兴趣,下面是参考资料的链接。此外,如果你对可能性及其与概率的联系的数学讨论感兴趣,请查看我在 YouTube 频道“代码商场”上的另一篇媒体博客文章和视频。
5.结论
在这篇文章中,我们讨论了随机变量如何让我们量化实验的结果。然后,我们使用概率分布函数来理解这些随机变量的行为。根据所测量的随机变量是离散的还是连续的,我们可以画出不同类型的概率分布函数;离散随机变量的概率质量函数,以及研究连续随机变量的概率密度函数。通过理解如何使用最大似然估计来估计统计模型的参数,我们最终使用机器学习将所有这些概念联系在一起。
谢谢你一直读到最后!关于数学概念以及它们如何与机器学习结合的详细讨论,请查看我的 YouTube 频道“代码商场”和下面的其他资源。
所有未注明出处的图片均由作者创作
6.资源
[1] Code Emporium,机器学习的概率论 (2022), YouTube 。
[2]伦敦帝国理工学院,机器学习的数学, Coursera
[3]约翰·霍普金斯大学,数据科学高级统计学, Coursera
[4]悉尼大学,微积分入门, Coursera
[5] Ajay Halthor,可能性、概率和你应该知道的数学 (2022),走向数据科学**
[6]丹尼斯·孙,概率导论 (2020)
[7] Joram Soch,简单线性回归的最大似然估计 (2021),统计证明之书**
数据科学家的概率面试问题
原文:https://towardsdatascience.com/probability-interview-questions-for-data-scientists-ef71b1747ec2
在本文中,我们将从顶级公司的数据科学面试中了解一些概率问题,并概述各种解决方法。

作者在 Canva 上创建的图片
概率统计是机器学习和数据科学的基础。虽然深奥的算法可能是城里最新的闪亮事物,但大多数有抱负的数据科学家很少关注基础。概率面试问题显得过于简单,同时又具有挑战性。每个涉及概率的问题最终都归结为一个简单的关系。

概率问题在数据科学面试中非常受欢迎。面试官最喜欢的话题包括-
- 机会游戏
- 组合学
- 概率分布
- 期望值
- 贝叶斯定理
在本文中,我们将查看 Meta 等顶级公司的数据科学面试中的一些概率问题,并概述各种解决方法。
概率面试问题

作者在 Canva 上创建的图像
让我们从一个简单的问题开始。这个问题出现在 Meta /脸书数据科学的一次采访中。
两张相同花色的牌
从同一花色中抽两张牌的概率有多大?(假设从一副牌中抽完牌后没有放回原处)

截图来自 StrataScratch
你可以在这里的 StrataScratch 平台上解决这个问题:https://platform . StrataScratch . com/technical/2003-two-cards-same-suite
解决这个问题的一种方法是使用组合学。
从 52 张牌中找出任意两张牌的抽牌方式数,就可以找到所有可能的情况。这可以用 52C2 或 52.51/2 = 1326 种方式来实现。
有利案例同样可以找到。首先,我们从想要配对的四套西装中挑选一套。这在四个方面是可能的。然后,我们以与上面类似的方式进行。从特定花色的 13 张可能的牌中选择两张。这是 13C2 或 78 路。总的来说,从同一套牌中选择任意两张牌的方法有 4×78 = 312 种。我们现在可以简单地找到概率

注意:你也可以在这里用置换(有排列的选择)来代替组合。只要确保你在有利的和所有可能的情况下保持过程的一致性。
让我们试试解决这个问题的另一种方法。要做到这一点,让我们把问题分解一下。我们被要求从同一花色中找出两张牌。第二张牌决定了我们是否匹配了花色。抽完第一张牌后,这副牌还有 51 张。
比如说我们画了红心皇后。剩下的 51 张牌中,只有 12 张来自红心(回忆一下,我们已经抽到了红心皇后)。因此,我们的概率和前面一样:

这种分解概率问题的方式对于解决看起来复杂的概率问题非常有帮助。让我们增加一点难度。这个问题是在 Yammer Data Science 的一次采访中提出的。
过生日的人在哪里?
找出在一个有 k 个人的房间里,至少有两个人生日相同的概率。(假设一年有 365 天,k≤ 365)

截图来自 StrataScratch
你可以在这里回答问题。https://platform . strata scratch . com/technical/2028-生日的人在哪里
让我们把这个问题分解一下。所有可能的情况都很容易找到。这 k 个人的生日可以是一年 365 天中的任何一天。因此,所有可能情况的数量将是:
365 x 365 x 365 x 365 … k 次
或者

找到个有利案例有点复杂。我们可以让 k 个人中的两个人同一天生日,或者 k 个人中的三个人同一天生日,以此类推,直到所有 k 个人同一天生日。为了解决这个问题,我们可以按照下面的方式将所有可能的情况分成不同的场景。我们可以:
场景
A)没有一个 k 人共享任何生日
B)共享生日的 k 人中的 2 人
共享生日的 k 人中的 3 人
…..
k 个人中的所有 k 个人分享他们的生日。
如果仔细观察,除了第一种情景(情景 A),其他所有情景都构成有利案例。因此,只有第一种情况是不利的。因此,我们可以通过找出不利情况的数量,然后从所有可能的情况中减去这些情况来找到有利情况,从而相对容易地解决这个问题。
不利情况是 k 人都不共享生日。这与指定 365 天中的 k 个生日没有重复是一样的。这可以通过以下方式实现:

因此,有利的情况是:

因此,所需的概率是,

让我们以一个涉及条件概率的问题来结束我们的课程。
三条尾巴中的两条
同时掷四枚公平硬币,恰好得到三条反面的概率是多少?已知至少出现两条尾巴。

截图来自 StrataScratch
你可以在这里回答问题https://platform . stratascratch . com/technical/2285-two-out-of-three-tails
虽然这个问题可以使用条件概率关系来解决,但我们将把它留给稍微复杂的情况。让我们通过分解问题来解决这个问题。如果不考虑额外的信息(至少两次掷硬币是反面),那么我们可以很容易地找到有利的情况和所有可能的情况。
所有可能的情况都是正面或反面(两种结果),四次翻转或 24 次或 16 次场景。
有利案例正好是四翻的三尾。这可以简单地通过选择四个翻转中的哪一个是正面或四种可能性来找到。我们也可以轻松列举案例。TTTH,TTHT,HTTT。
这个问题的不同之处和潜在的棘手之处在于,它给出了至少有两条尾巴出现。这减少了可能情况的数量,而不影响仍然只有四个的有利情况。为了帮助说明这一点,让我们列出所有可能的情况。

截图来自 StrataScratch
因此,我们可以简单地减去那些不可能的情况,即没有尾巴出现和只有一个尾巴出现的情况。(以上斜体)。让我们来计算这些。
没有出现反面:只有一种可能——四枚硬币都出现正面。
只有一条尾巴出现: 这有四种可能(我们有利的例子的另一面,只有一个头出现)。
所有可能的情况现在减少到 11 个(16 个,减去上面的 5 个)或 11 个。
因此,所需的概率是:4 / 11
结论
在本文中,我们查看了基于概率的数据科学家访谈中提问类型的示例。涉及概率的问题涉及数学,但它们也可以通过将问题分解为基础来解决。和其他技能一样,一个人可以通过坚持和练习来提高他或她在这方面的熟练程度。此外,查看我们的帖子“ 数据科学家的 30 个概率和统计面试问题 ”,找到更多这样的问题,可以帮助你提高技能,赢得数据科学面试。
测试呈阳性时受影响的概率
原文:https://towardsdatascience.com/probability-of-being-affected-while-the-test-is-positive-e62ac19311fa
用 Excel 计算,以减少对假阳性悖论的混淆
我们对统计数据的直觉可能是错误的,统计悖论可能无处不在。当我们意识到这一点时,我们可以对我们的直觉如何误导我们得出的结论持更加批判的态度。
在这篇文章中,我将谈论假阳性悖论。对于一直知道这个悖论的人,你也可以看看我对它和机器学习关系的思考。
悖论
想象一下,有一个疫情,政府试图为每个人组织测试。
通常给出测试的以下特征:
- 测试的灵敏度表示该测试正确识别受感染人群的性能。它也被称为真阳性率或回忆。最高值为 100%,意味着所有感染者都被正确识别。
- 测试的特异性表明测试的性能能够正确识别未被感染的人。最高值也是 100%,意味着所有未受感染的人都被正确识别。在我们的例子中,假设测试的特异性为 98%。现在,你认为它已经很高了吗?我们将改变这个值,看看它将如何影响结果。
受影响的人口估计为 2%。我们也称这个指标为疾病的患病率。
现在想象你做了一个测试,它是阳性的。你真正被感染的概率有多大?该指标也被称为测试的精度。
凭直觉,你觉得有什么价值?90 %左右?如果告诉你是相当于 50%呢!是的,测试是阳性的,但是你只有 50%的机会被真正感染。
为了清楚地说明计算过程,我创建了一个Google Sheet来完成所有的计算。我们开始吧!****

在 Unsplash 上 Mufid Majnun 拍摄的照片
低患病率的混淆矩阵
真正的积极和真正的消极
让我们从真正感染和未感染的人数开始。考虑到总人口是 1000 人,由于患病率是 2%,我们只有 20 人感染。

作者图片
现在,给定特异性和敏感性,我们可以计算真阳性和真阴性的数量。
- 由于检测是高度特异性,所有感染者都被识别出来。
- 特异性还不完善,我们在 980 人中鉴定了 960 人。

作者图片
假阴性和假阳性****
为了完成表格,我们可以做一些减法,我们有了假阴性和假阳性的数量。

作者图片
测试的精确度
完成混淆矩阵后,我们现在可以通过将真阳性的数量除以测试阳性的总数来计算测试的精确度。
由于低流行率导致真阳性的数量很低,即使测试非常特异(98%),假阳性的数量与真阳性相比相对较高。
所以测试的精度相当低,51%左右!

作者图片
我们可以对测试的特征做一些改变,更高的特异性会增加测试的精确度。

作者图片
假阳性悖论
统计学上的悖论是虽然特异性很高,但由于患病率非常低,精确度很低。这被称为假阳性悖论,因为尽管事实上测试是阳性的,我们仍然有相对高的几率成为假阳性。
这也是一种基本比率谬误,因为我们忽略了一个事实,即相对于总人口而言,受感染的人数很低。
高患病率的混淆矩阵
有了 Excel 文件,如果你增加流行度,那么我们就不会看到这个悖论。

作者图片
真实世界的数据
我搜索了一些关于 Covid 测试的真实数据。你可以阅读这个关于测试特性的文件。我们可以看到特异性通常高于 99.8%。有时候,甚至是 100%。
与机器学习的联系
性能赋值
混淆矩阵用于医疗诊断,也用于信息检索和机器学习分类任务。
用机器学习的术语来说,低流行率意味着不平衡的训练数据。在这种情况下,评估一款车型的性能,少不了既看精度又看召回。然后我们就可以计算出 F1-score ,这是精度和召回率的调和平均值。
我们可能知道,在建立模型时,不可能同时获得高精度和高召回率。所以我们要选择强调其中的一个。对于医学诊断的情况,你决定强调哪一个?
欠拟合
如果我们考虑到混淆矩阵是为一个机器学习模型计算的,那么这个模型只有一个变量(测试的结果),是一种欠拟合。在实践中,我们有更多的变量可以是不同的症状。所以把它们结合起来,我们应该能提高精度。当然,如果疾病是无症状的,我们就无法获得更多的信息,所以我们会在高缺失值的情况下。
比如基础谬误率的维基百科页面上,我们有一个酒驾的例子。悖论是可以应用的,但是在实践中,通常还有其他的符号来增加精度。如果司机不能直线驾驶,或者他不能清楚地说话,那么,在现实中,我们在我们的“模型中有更多的变量来确定司机是否喝醉。

作者图片
结论
对于这个悖论和其他我也想写的悖论,我们能说些什么呢?一方面,重要的是要有一种统计文化,让我们知道有时我们的直觉可能会误导我们得出错误的结论。另一方面,现实世界要复杂得多,我们不应该停留在简单的统计数字上。
****https://medium.com/@angela.shi/membership ****
概率模拟:利用随机性解决复杂的概率问题:第 1 部分
通过设计实验并在虚拟世界中重复几次来估计有利结果的概率。
在我们的生活中,我们都和概率打过交道。无论是在需要掷骰子或处理纸牌的游戏中,还是在估计我们在体育赌博中获胜的机会中,或者是在计算疫苗的有效性中。在所有可能的结果中,当你想估计一个有利(或不利)结果的“机会”时,概率无处不在。
注意 这篇文章不是介绍性的,需要一些关于概率的基础先验知识,包括贝叶斯定理。

纳赛尔·塔米米在 Unsplash 上拍摄的照片
快速介绍
简单来说,概率就是给定事件发生的几率。或者用技术术语来说,根据 merriam-webster ,它是产生一个给定事件的一组完全相同的可能结果中的结果数与可能结果总数的比率。
即(事件的)P =(有利结果的数量)/(总的可能结果)
- 举一个快速且非常熟悉的例子,让我们来看一枚公平硬币。两个可能的结果是正面(H)和反面(T),每一个都有相等的发生几率。
因此,P(H) = 1/2,P(T) = 1/2,因为在每种情况下,结果的总数是 2 → {H,T},正面的有利结果是 1 {H},反面的有利结果是 1 {T}。因此,每种情况的概率是 1/2。请注意,所有可能的结果的概率总和应为 1。即 P(正面或反面)= P(正面)+ P(反面)- P(正面和反面)= 1/2 + 1/2 - 0 = 1。注意,在这里,硬币同时落在 H 和 T 上是不可能的。这些被称为互斥事件(它们不能同时发生)。
快速回顾一下贝叶斯定理
给定一个假设 H 和证据 E,贝叶斯定理建立了得到证据 E 之前假设 P(H)的概率与得到证据 P(H | E)之后假设的概率之间的关系。这是一个非常重要的概念,如果你不熟悉条件概率和贝叶斯定理,请随时参考这些链接[1][2[3]。

贝叶斯定理

解释上述贝叶斯公式的一个注释
如果你想做一个更详细的概率回顾,请参考这个维基链接。
模拟概率:为什么?
现在大部分概率问题都可以在纸上解决了。但有时,问题会变得复杂和耗时。即使你成功地解决了它,也可能没有一个参考来检查你计算的答案是否正确,特别是如果你是一个在现实生活中使用概率的人——因为问题很可能是独特的。因此,模拟一个问题或实验来计算感兴趣事件的概率对于以下方面很有用:
- 通过实际进行虚拟实验,对问题有更深入的了解。
- 验证您计算的答案。
- 学习如何使用代码模拟简单到复杂的真实生活实验和场景;例如,对一个地区的经济稳定性建模,或者对一个国家的 COVID 扩展建模,等等。
模拟概率:设计步骤
- 设计实验。这来自于问题陈述或你试图计算其概率的场景。
- 重复实验 N 次。N 越大,你估计的概率数字就越准确。(大数定律)
- 计算有利结果与全部可能结果的比率以计算概率。
注意:我们将使用 Python 内置的随机模块来生成“随机”样本。简单地说,如果你让 random 模块从一个正面和反面中挑选,每个正面和反面的概率都是 0.5,那么它将随机挑选一个。但是如果你重复很多次,平均来说,大约 50%的时候会选择正面,另外大约 50%的时候会选择反面。(我们也可以使用 numpy 的随机模块,它对于数组来说更快)。
关于随机模块的一个旁注:随机模块生成伪随机数,也就是说,这些数字可能看起来是随机的,但它们实际上并不是真正科学意义上的完全随机[ 来源 ] 。话虽如此,对于我们的大多数日常用例来说,使用这个模块应该足够了。但不建议将此模块用于任何加密或类似的风险和隐私相关应用。
让我们通过一个例子来看看这个过程。
- 假设你在尝试回答这个问题:当你掷两枚公平硬币或掷一枚公平硬币两次,得到 2 个头(H)的概率是多少?
- 因为得到一次掷硬币独立于另一次掷硬币或自己先前的掷硬币,所以 P(2H)= P(H)* P(H)= 0.5 * 0.5 = 0.25 = 1/4。你也可以直观地这么说→两次投掷的四种可能结果是{HH,HT,TH,TT}。在这 4 个中,只有 1 个符合我们对两个头的预期{HH}。所以,答案是 1/4。
- 让我们模拟这个实验,看看我们会发现什么..
简单的抛硬币示例(在 N 次抛硬币中观察 X 个头)
函数 coin_flip 是我们的单模块实验,它模仿了翻转 n_flips 数量的公平币或翻转一个公平币 n_flips 次数。我们使用了 随机选择 来模拟翻转。人口 参数是结果列表, 权重 是每个结果的概率列表(如果没有通过,将假设每个结果的概率相等),而 k 是替换抽取的样本数。请注意,我们选择“替换”,因为第二次获得正面或反面不取决于第一次翻转的结果,它们是独立的事件。你也可以使用random . choice进行单次翻转,并将其放入 for 循环中进行 n 次翻转,然后计算这些结果的总和,但那样会更慢,结果也不会有太大不同。
我们的“实验”函数的设计结果是 n 次翻转的和,其中,对于单次翻转,H 和 T 分别被赋值为 1 和 0。因此,对于一个掷两枚硬币的实验,我们寻找的是 sum = 2 的结果(即两个正面)。(注意,如果我们对获得 1H 和 1T 的场景感兴趣,我们的 sum 滤波器应该是 sum = 1。如果我们对 2T 感兴趣,我们可以使用 sum = 0 的滤波器。)
然后,我们重复实验 N 次(越高越好),然后将结果汇总到熊猫数据框架中。
概率计算为有利结果(总和为 2)与总可能结果的比率。正如你所看到的,这是 0.25,更接近我们计算的已知概率 1/4。
你准备好了吗?我们将使用相同的方法来计算一些简单到复杂问题的概率和/或期望值(在本帖以及第 2 部分中)。还有什么比著名的蒙蒂霍尔问题更好的开始呢?这个问题在 20 世纪晚期困扰了一些最聪明的统计学家。
蒙蒂·霍尔
原始问题

来源:https://en.wikipedia.org/wiki/Monty_Hall_problem
你在一个游戏节目中。主持人给你看了三扇门,告诉你一扇门后有一辆汽车,另外两扇门后有山羊。** 主人确切地知道哪扇门后是什么。
- 他让你选一扇门。假设你选了 1 号门。
- 然后他打开 3 号门,门后有一只山羊。注意,主持人保证打开一扇门,门后面有一只山羊,因为他知道。
- 然后,他会问你是继续之前选择的 1 号门,还是切换到 2 号门?
你应该怎么做才能最大化你的胜算?
让我们先在纸上解决这个问题。最初,当你选择一扇门时,有 1/3 的机会你会选择一扇后面有车的门。因此,门 1 有 1/3 的机会是正确的选择,门 2 和门 3 加起来有 2/3 的机会有一辆车在其中任何一个后面。当主持人打开 3 号门显示后面有一只山羊时,P(3 号门后面的车)变成 0,P(2 号门后面的车)变成 2/3。P(门 1 后面的汽车)保持不变(1/3 ),因为你没有获得关于门 1 的新信息。所以,回答这个问题,如果你切换到门 2,你将以 2/3 的概率获胜。

这是另一种看待蒙蒂·霍尔问题的方式。* *注意:无论您构建什么场景,都要确保每个车门上车的次数相同。例如,如果您播放 30 个场景,其中 10 个应该是汽车在门 1 后面,10 个是汽车在门 2 后面,10 个是汽车在门 3 后面。因为汽车在门后的初始概率总是从 1/3 开始。
你也可以使用贝叶斯定理来解决这个问题,如下所示。

图片和方程式由作者提供
让我们模拟这个场景,看看结果是否符合我们的计算值。
蒙蒂霍尔问题模拟(主持人知道的地方)

图片由作者提供
可以看到,随着 n_rounds 的增加,模拟概率接近理论概率。也就是说,在玩的 10,000 场游戏中,你通过切换赢得了几乎 2/3 的游戏。
你也可以增加门的数量,玩同样的游戏(主持人在剩下的一扇门后展示一只山羊)。**切换的概率仍然较高,但是随着门数量的增加,切换和不切换之间的差异减小并接近于 0。见下文。这是因为随着 n_doors 的增加,其他门的优势越来越小。如果主人打开不止一扇门,这种情况将会改变。请随意使用这个场景,看看您会得到什么。 )**

图片由作者提供
宿主不知道的变异
和上面的游戏完全一样,但是这次主人不知道哪扇门后面有什么,他随机选择了一扇门(不包括你选择的那扇门)来打开。所以他可能不小心打开了后面有车的门,在这种情况下,游戏要么结束,要么重新开始,要么你自动获胜。
- 主人让你选一扇门。假设你选了 1 号门。
- 然后他打开 3 号门,发现门后有一只山羊。
- 然后,他会问你是继续之前选择的 1 号门,还是切换到 2 号门?
你应该怎么做才能最大化你的胜算?
现在,这可能看起来与前一个完全一样,但在这种情况下,要注意的主要区别是主机不知道。主人缺乏知识会改变一切。

图片和方程式由作者提供

这是另一种想象这种变化的方式。* *注意:无论您构建什么场景,都要确保每个门进入汽车的次数相同。例如,如果您播放 30 个场景,其中 10 个应该是汽车在门 1 后面,10 个是汽车在门 2 后面,10 个是汽车在门 3 后面。因为汽车在门后的初始概率总是从 1/3 开始。
注意在上面的计算和下面的模拟中,我们只考虑那些“出现了山羊”的回合。
让我们模拟这个场景,看看结果是否符合我们的计算值。
蒙蒂霍尔问题模拟(主持人不知道的地方)

图片由作者提供
如您所见,模拟结果与计算结果相符。
万岁!!到现在为止,你一定已经掌握了如何设计概率问题模拟的要点。
我们将在本帖的第二部分中查看更多的例子。在第 2 部分之后,我还会分享整个 Jupyter 笔记本的链接。
感谢阅读。请随时留下评论、问题或建议。此外,欢迎对下一篇文章提出任何新的想法。
PS:这篇文章的动机是我最近和我的数据科学同事讨论了一个她试图解决的概率分布问题。
这可能是制作 Python 图动画的最简单的方法
原文:https://towardsdatascience.com/probably-the-easiest-way-to-animate-your-python-plots-f5194ebed75f

图片由 u_w8477gyh9u 来自 Pixabay
生成绘图帧,并将其合并为 GIF
可视化一直是 Python 在数据科学和数据分析中的重要用途。有时,我们想让我们的情节移动,以获得更高级的展示和见解。然而,大多数动画可视化库需要一些额外的努力来学习,如matplotlib.animation。当我们只是想快速简单地生成一些动画图形时,这可能不是最好的方法。
在本文中,我将介绍一种可扩展性相对较差但更容易的方法来制作 Python 绘图动画,这就是使用 ImageIO 库。这个库通常用于在 Python 中操作图像,以及组合多个图像来生成 gif。它非常容易使用。
在我们可以使用 ImageIO 库之前,我们需要使用pip安装它,如下所示。
pip install imageio
1.折线图的一个例子

让我们从这个演示的基本折线图开始。为了简化它,我想用 50 个整数生成一个 NumPy 数组。然后,这些整数可以绘制成折线图。
import numpy as np
import matplotlib.pyplot as plt
import imageionp.random.seed(0)
SIZE = 50
y = np.random.randint(-5, 5, SIZE)
plt.plot(y)
plt.ylim(-10, 10)plt.show()

在上面的代码中,我设置了随机种子,这样如果你愿意的话,你可以复制出和我完全一样的结果。SIZE被定义为一个常量,因此您可以根据自己的喜好对其进行更改。
整数在[-5,5]范围内生成。此外,为了使图表更容易阅读,我想添加ylim(-10, 10),这样所有的点都在图表的中间部分。

现在,我们有了这个静态的基本折线图。比方说,我们希望通过逐个绘制点来制作折线图。最好是把结果做成 GIF 格式,这样我们就可以把它嵌入到任何我们想嵌入的地方。
当然,合适的方法是使用matplotlib.animation模块。然而,我将介绍的快速简单的方法是将帧生成为单独的 PNG 图像。然后,我们可以使用 ImageIO 将它们组合成一个 GIF。
1.1 生成帧
事实上,生成帧并不十分困难。这个想法是用 2 点,3 点,…和 50 点来绘制折线图。每个图都应该保存到单独的图像中。
代码如下所示
for i in range(2, SIZE+1):
plt.plot(y[0:i])
plt.ylim(-10, 10)
plt.savefig(f'line-{i}.png')
plt.close()

for 循环从 2 个点开始,因为单个点无法生成有效的折线图。它将在SIZE+1处停止,这样range()将在精确的点数处终止。换句话说,在我们的例子中,最后一个i是 50。
在每一帧中定义ylim很重要。否则,y 轴将被自动确定,因此它在帧中是不同的。
配置绘图后,我们可以用预定义的文件名模式将图形保存为 PNG 图像。稍后将使用这个文件名模式来填充这些帧。最后,我们需要通过调用plt.close()来结束这个情节。这样,下一个循环中的下一帧才能正确生成。
运行这段代码后,当前工作目录下将生成 49 个 PNG 文件。

1.2 生成 GIF
下一步是将它们组合成一个 GIF。ImageIO 库提供了一个“编写器”,我们可以很容易地将图像附加到框架中。
with imageio.get_writer('line.gif', mode='i') as writer:
for i in range(2, SIZE+1):
image = imageio.imread(f'line-{i}.png')
writer.append_data(image)

我们可以使用 with 语句,这样就不需要担心流的关闭。GIF 文件的名称将被称为line.gif。标志mode='i'是一个提示,告诉 ImageIO 输入将是图像。
然后,我们可以进行另一个 for 循环来获取索引。因此,我们可以使用索引来获取帧的文件名。一旦我们有了文件名,只需将它附加到当前的 writer。
在我们运行上面的代码之后,GIF 文件line.gif应该已经生成了。


顺便说一句,如果框架不再有用,我们可以使用下面的代码删除它们。
import osfor i in range(2, SIZE+1):
os.remove(f'line-{i}.png')

1.3 一个小小的改进
上面显示的 GIF 可能并不完美。具体来说,x 轴也随着帧移动。这可能取决于我们想要实现的目标,但假设我们想要固定 GIF 中的 x 轴,我们肯定可以做到。
这个例子还表明,这里介绍的方法非常灵活。基本上,当我们想要在我们的动画图表中有一些特性时,我们不依赖于库的任何 API,而是依赖于帧是如何生成的。
在这种情况下,我们只需要在原始代码中添加xlim()配置,就可以生成 x 轴固定的帧。
for i in range(2, SIZE+1):
plt.plot(y[0:i])
plt.ylim(-10, 10)
plt.xlim(0, 50)
plt.savefig(f'line-{i}.png')
plt.close()

之后,用于生成 GIF 的 ImageIO 代码甚至不需要更改,因为我们已经更新了帧的内容,但是图像的数量仍然相同。
最后,新的 GIF 看起来如下。

2.条形图的一个更好的例子

该方法已在上面的折线图示例中得到了很好的介绍。然而,我只是想说明该方法有更大的潜力。这一次,让我们制作一个条形图。
在演示开始之前,我们需要定义一个 x 轴和一个 y 轴列表。我们之所以需要一个 y 轴值的列表,是因为我们想让 GIF 显示多个帧,每个帧都是一个条形图。换句话说,它们的 x 轴相同,但 y 值不同。
x_axis = [1, 2, 3]
y_axis_list = [
[0, 0, 0],
[1, 2, 3],
[3, 2, 1],
[5, 5, 5],
[7, 7, 7],
[9, 2, 9],
[2, 9, 2],
[1, 1, 1],
[9, 9, 9],
[0, 0, 0]
]

上面的代码只是一个例子。没有模式,只是编出来的:)
2.1 生成条形图 GIF
然后,让我们生成框架条形图。
png_files = []for i, y_axis in enumerate(y_axis_list):
# Create Plot
plt.bar(x_axis, y_axis)
plt.ylim(0,10) # Create PNG file
filename = f'bar-{i}.png'
png_files.append(filename) # Save Figure
plt.savefig(filename)
plt.close()with imageio.get_writer('bar.gif', mode='i') as writer:
for filename in png_files:
image = imageio.imread(filename)
writer.append_data(image)

这次我不想被 for 循环中的range()方法所困扰。我们可以定义一个列表png_files来包含所有帧的文件名。因此,稍后,当我们想要从 PNG 图像生成 GIF 时,我们只需要从这个列表中获取文件名。
生成 GIF 的代码与前面的例子没有什么不同。这也说明该方法相当通用。
生成的 GIF 如下。

嗯,可以,但不理想。其实根本看不懂。
2.2 生成平滑过渡的 GIF
从动画的角度思考。好看的动画无非就是 FPS 多,过渡流畅。基本思想是在两个状态之间添加更多的过渡帧。
因此,我们可以做到以下几点。
smooth_coef = 10
png_files = []for i in range(0, len(y_axis_list)-1):
# Get Current & Next Frame
y_axis_curr = y_axis_list[i]
y_axis_next = y_axis_list[i+1]# Generate Middle Frames
y_diff = np.array(y_axis_next) - np.array(y_axis_curr)
for j in range(0, smooth_coef+1):
y_axis = (y_axis_curr + (y_diff / smooth_coef) * j)
# Create Plot
plt.bar(x_axis, y_axis)
plt.ylim(0,10)
# Create PNG file
filename = f'bar-{i}-{j}.png'
png_files.append(filename)
# Stretch the last frame
if j == smooth_coef:
for _ in range(5):
png_files.append(filename)
# Save Figure
plt.savefig(filename)
plt.close()

我来解释一下。smooth_coef代表“平滑系数”。这意味着我们要在每两个主帧之间添加 10 个过渡帧。主帧正好是演示数据集中的 y 轴。
在 for 循环中,我们将获得当前的 y 轴和下一个。之后,我们可以用下一个 y 值减去当前值。所以,结果将是这两个主要框架之间的“差异”。那么,如果我们让“差”除以平滑系数,就会得到阶梯式的差。总之,我们可以计算每个过渡帧之间的差异。所有过渡帧相加将把当前帧变成下一帧。
y_axis = (y_axis_curr + (y_diff / smooth_coef) * j)
另一个技巧是让 GIF 在关键帧处“停留”一会儿,这样我们可以看得更久一点。这可以通过在特定时间重复文件名来实现,这样帧就会重复。在生成的 GIF 中,我们将多次看到该帧,这也意味着更长的时间。
让我们看看结果。

那就好多了!
3.该方法是通用的

图片由 Julius Silver 从 Pixabay 拍摄
不同于任何特定的绘图库,如matplotlib.animation,ImageIO-GIF 方法更加通用。也就是说,我们甚至不受使用什么库的限制。只要图形可以保存成图像文件,我们就可以用这种方法让它移动。
下面是一个简单的例子。我们可以使用 Seaborn 来生成条形图。
import seaborn as snssns.set_style('darkgrid')
Seaborn 模块被导入,样式被设置。然后,我们需要修改的代码部分如下。
sns.barplot(x_axis, y_axis)

生成的 GIF 如下所示。

摘要

图片来自 Pixabay 的 Miguel Barrera
在本文中,我介绍了一种不使用任何专门构建的绘图动画库将 Python 可视化制作成 GIF 的方法。这个想法是逐帧生成图像文件的情节。然后,可以使用 ImageIO 模块将这些图像文件组合成一个 GIF。
该方法也非常通用。我们不需要学习任何特定动画风格的 API 或方法。我们需要做的就是生成不同的帧来达到要求。此外,它甚至不局限于任何可视化库,因为它依赖于作为帧生成的图像。
请注意,这种方法可能不是绘图的常规方法。其中一个问题可能是可扩展性,因为我们需要生成许多单独的文件作为帧,然后才能获得 GIF。
https://medium.com/@qiuyujx/membership
如果你觉得我的文章有帮助,请考虑加入 Medium 会员来支持我和成千上万的其他作者!(点击上面的链接)
程序双关语和文字游戏生成与尝试和 GPT-3
我们能教一个人工智能按需产生源源不断的笑话吗?

埃里克·克鲁尔在 Unsplash 上的照片
游泳者为什么要吟诵?成为一名游泳商人!
到本文结束时,你将拥有一个点击按钮就能产生成千上万个笑话的人工智能。
我一直喜欢各种各样的双关语。有些聪明,有些笨拙可爱。我最喜欢的是同形双关语,它使用的单词书写相似,但具有不同的意思或声音。海盗可能会大喊“我的裤子!”,而通勤者可能会抱怨地铁用户数量的增加。以一种巧妙的方式将这些联系起来,创造了一个令人愉快的脑海形象:一个海盗在地铁里被偷了裤子。想出这些本身就是一件有趣的事情,但是作为一名计算机科学家,我开始怀疑我们是否可以完全自动化这种类型的幽默。幽默被认为是将我们与机器区分开来的独特的人类特征。笑话吐机会是什么样子?
这篇文章探讨了一个简短的下午如何生成程序性笑话。它需要一些巧妙的数组结构来构建可应用的笑话基线,并利用神经网络将这些基线转化为笑话。最后,我们有了一台可以工作的笑话机器,它可以在几秒钟内产生成千上万个笑话。该程序最初是为我的母语(芬兰语)编写的,但由于流行的 NLP 模型,我将该模型改编为我们时代的通用语,英语。通过非常有限的调谐,同样的原理可以用于用法语、西班牙语或蒙古语讲笑话。
你可能已经猜到了,文章的第一个笑话是由一台机器生成的。
一个好笑话需要多少次尝试?
一个
从技术上来说,只需要一次尝试就能产生所有的笑话。我们的目标很简单:想出一种方法来计算英语语料库中的单词可以被划分和重新排列成新单词的所有方式。在这个挑战中,我们将看到一组非常特殊的单词,其中一个很好的例子可能是 maples 和 lessons 。枫树和课程可以排列成地图课程和枫树之子。开个玩笑,你可能会立刻想象一个多伦多曲棍球迷在研究一张地图。确切地说,我们正在寻找满足以下原则的两个词:
- 这些单词都是标准的、可以理解的英语(或者斯瓦希里语、匈牙利语或者任何其他语言)
- 这些单词有一个共同的部分——让我们称之为连接器。该部分在第一个单词的末尾和第二个单词的开头(在我们的例子中,这是 les
- 即使我们去掉这个共同的部分,单词也是有意义的:地图和儿子
- 对于我的应用程序,我想将连接器限制为英语中的单词而不是,并且至少有几个字符长。虽然这肯定是可能的,但我只是觉得没有那些明显的笑话,比如梭子和棍子(连接符: s )或工作簿和书虫(连接符:书),这些笑话会更加巧妙。
最简单的方法是遍历英语中的所有单词,检查其中是否有与其他单词重叠的子串。英语有大约 170,0 00 个单词(取决于你问谁,你在哪里划分复合词),即使通过限制更有用的单词,我们最终也能得到一个数万单词的字典。我用一本有 58109 个单词的字典找到了这里。遍历所有这些单词,将它们相互匹配并找出子串的复杂度是 O(m *n),其中 n 是单词的数量,m 是单词的平均长度。我们能做得比蛮力更好吗?研究了一下之后,我决定创建一个 Trie 的实现,它的复杂度总是为 O( n )。如果您不熟悉尝试,它们是搜索树的一个子组,其中搜索字符串是一系列字符(单词),每个字符将树分成子树。单词的最后一个字母在包含要获取的值的叶子处结束。从某种意义上说,它们是紧凑的哈希表,其中的哈希键充当字符串。在我们的应用程序中,我们对使用该结构获取数据不感兴趣,而是构建一个包含所有单词逻辑的可查询结构。下图阐明了该系统:

作者图片
蓝色节点是问和灰以及看护和推车的共同根。另外, as 和 car 都是有效的英文单词。回想一下我们的先决条件列表,如果一个节点包含子节点并且本身就是一个单词,我们可以简单地存储——这将立即满足所有三点。从以这种方式构建的 trie 中收集所有单词的复杂度是 O( n )。
显然,这只是解决了问题的前半部分,我们仍然需要想出一种方法来解释单词的常见结尾。但我们稍后会回到这个话题。下面是 Tries 的实现,我对它进行了修改,以便在构造期间获取元数据而不是叶值。
class trieNode():
def __init__(self, letter = None):
# Keep track of children as nodes and letters
self.children = []
self.children_nodes = []
self.is_leaf = False
self.letter = letter
# Utility: store the "words" up to this point,
# as well as all the child strings that follow this.
# This exhanges some memory for easier lookup later
# Additionally, we store how many times a node has been visited
self.string = None
self.child_strings = []
self.visited = 0
class Trie():
def __init__(self):
self.root = trieNode()
self.n_nodes = 0
def insert(self, key):
# Start at the root
pointer = self.root
idx = 0
for i in key:
if i in pointer.children: # If the child exists
order = pointer.children.index(i) # simply move forward
pointer = pointer.children_nodes[order]
else: # Otherwise create and append a new node
pointer.children.append(i)
pointer.children_nodes.append(trieNode(i))
self.n_nodes += 1
pointer = pointer.children_nodes[-1]
pointer.string = key[0:(idx + 1)]
# Update the other values
pointer.visited += 1
# ...and if the node is a leaf, or if we should simply add new children
idx += 1
if idx == len(key):
pointer.is_leaf = True
else:
pointer.child_strings.append(key[(idx):len(key)])
这使我们能够创建一个树模型,其中每个节点跟踪它是否是一个单词的结尾,它是否有孩子以及那些孩子是什么。作为一个额外的好处,创建这个表是一个单遍操作,之后我们可以无休止地查询结构,以获取任何以单词结尾的节点(要求 2 & 3)和具有不同长度结尾的节点(要求 3 & 4)。对于我的 80k+单词的字典来说,这导致了一个由 143 638 个节点组成的 trie,或者比单词数多 60k 多一点的节点。这只花了几秒钟。你可以把字典压缩成这样的数据结构,这真的很令人吃惊。
如果您想了解代码,可以在这里查看 Colab。我将只在这里粘贴更有趣的代码,所以继续复制您自己的游戏场以获得完整的体验。
文字游戏来了
还记得我说过,我们会回到如何想出词尾吗?现在是时候了。到目前为止,我们已经有了一种为许多结尾创建公共开头的方法。再想想我们如何实现相反的结果:获得许多前缀的共同结尾。
…
没错,我们可以利用现有资源。如果我们简单地翻转每个单词并创建我们的 Trie,我们将得到一个由常见词尾和首字母前缀组成的结构。有一点工作在幕后来回翻动单词,但最终我们可以把所有的东西放在一起。
def create_dict(trie):
result = {}
def crawl(trie):
if len(trie.children_nodes) == 0:
return
if trie.is_leaf and len(trie.children) > 0:
for child_string in trie.child_strings:
if child_string not in result.keys():
result[child_string] = []
result[child_string].append(trie.string)
for child in trie.children_nodes:
crawl(child)
crawl(trie)
return result
def create_flipped_dict(trie):
result = {}
def crawl(trie):
if len(trie.children_nodes) == 0:
return
if trie.is_leaf and len(trie.children) > 0:
for child_string in trie.child_strings:
flipped_string = child_string[::-1]
if flipped_string not in result.keys():
result[flipped_string] = []
result[flipped_string].append(trie.string[::-1])
for child in trie.children_nodes:
crawl(child)
crawl(trie)
return result
class jokeGenerator():
def __init__(self):
self.trie = Trie()
self.flipped_trie = Trie()
self.words = None
self.result = None
self.flipped_result = None
self.common_keys = None
self.wordplays = None
self.tokenizer = None
self.model = None
def loadWords(self, source):
words = pd.read_csv(source, na_filter = False)
words = words.values.tolist()
words = [x[0] for x in words]
print(f'Loading {len(words)} words')
i = 0
n_words = len(words)
for word in words:
i += 1
if i % int(n_words/10) == 0:
print(f'{int((i+1)/n_words*100)}% ({i}/{n_words})')
self.trie.insert(word)
self.flipped_trie.insert(word[::-1])
print(f'Done')
self.words = words
# normal: all words
# not_short: the connector is longer than 2 characters
# not_a_word: all words, where the connecting word is not a word in itself
# not_a_short_word: all words, where the connecting word is not a word in itself and it is more than 2 chracters
# not_a_short_word_or_ing: all words, where the connecting word is not a word in itself and it is more than 2 chracters and is not "ing"
def generateWords(self, type = 'normal'):
if self.flipped_trie == None or self.trie == None:
print('You must load the words first: loadWords(source)')
self.flipped_result = create_flipped_dict(self.flipped_trie.root)
self.result = create_dict(self.trie.root)
common_keys = list(set(self.result.keys()).intersection(self.flipped_result.keys()))
if type == 'normal':
self.common_keys = common_keys
elif type == 'not_short':
self.common_keys = [x for x in common_keys if (len(x) > 2)]
elif type == 'not_a_word':
self.common_keys = [x for x in common_keys if (x not in self.words and x != '-')]
elif type == 'not_a_short_word':
self.common_keys = [x for x in common_keys if (x not in self.words and x != '-' and len(x) > 2)]
elif type == 'not_a_short_word_or_ing':
self.common_keys = [x for x in common_keys if (x not in self.words and x != '-' and x != 'ing' and len(x) > 2)]
self.wordplays = {}
for c_key in self.common_keys:
for r in self.result[c_key]:
for f_r in self.flipped_result[c_key]:
self.wordplays[f'{r}_{c_key}_{f_r}'] = [f'{r}', f'{c_key}',f'{f_r}']
对于我们以这种方式收集的开始-结束对,我们可以简单地搜索前缀和后缀的共同匹配(联合),如下所示:

作者图片
哒哒,我们已经准备好了我们的同形双关语列表。事实上,10 174 817 是我们的最终分数。这些大部分都不符合我们任意的第四个要求,所以让我们把它降低一点。您可能会注意到generate words-方法,它有许多过滤器。这些过滤连接器的长度,删除单词并确保它不会被过度表示(例如“ing”,它的两端和开头都是一堆英语单词)。按照我们最严格的标准,我们最终会有大约 10 000 个文字游戏。你可以在这里找到完整的名单:【https://oskarniemenoja.fi/files/wordplays.txt】T4,但这里有一些我最喜欢的:
['absorb', 'ent', 'rapping'], or absorbent entrapping
['strut', 'ter', 'rains'], or strutter terrains
['hi', 'res', 'cued'], or hires rescued
['environ', 'ment', 'ally'], or environment mentally
['hips', 'ter', 'race'], or hipster terrace
['spa', 'res', 'ponds'], or spares responds
['for', 'mal', 'formation'], or formal malformation
['for', 'mer', 'its'], or former merits
['sit', 'com', 'posers'], or sitcom composers
['by', 'tes', 'table'], or bytes testable
我们可以继续下去,但这些作为一个不知疲倦的缪斯为你不同质量的双关语加油。最棒的是,它们的数量高达 1000 万。
听起来像是自然语言处理的工作
这一切都很好,我们有了一台机器,可以以你能想象的任何速度吐出双关语。但是想出笑话的角色还是在人类身上。我们能不能进一步自动化,并有一个真正的端到端的管道来产生幽默?我们当然可以。让我们给我们的类添加几个方法:
def loadModels(self, language = 'english'):
if language == 'finnish':
self.tokenizer = GPT2Tokenizer.from_pretrained('Finnish-NLP/gpt2-finnish')
self.model = GPT2LMHeadModel.from_pretrained('Finnish-NLP/gpt2-finnish')
elif language == 'english':
self.tokenizer = GPT2Tokenizer.from_pretrained('gpt2-large')
self.model = GPT2LMHeadModel.from_pretrained('gpt2-large')
# Adding new languages is laughably easy. Search for a passable model on Huggingface, chuck it here and let the script do it's magic.
def generateJoke(self, first_string = '', second_string = '', n = 1, length = 30):
joke = self.wordplays[random.choice(list(self.wordplays.keys()))]
joke_words = joke[0] + joke[1] + ' ' + joke[2]
joke_string = first_string + ' ' + joke_words + ' ' + second_string
input_ids = self.tokenizer.encode(joke_string, return_tensors = 'pt')
output = self.model.generate(input_ids,
max_length = length,
num_beams = n,
num_return_sequences = n,
no_repeat_ngram_size = 3)
result = []
for op in output:
result.append(self.tokenizer.decode(op))
return result
这些从 Huggingface 加载一个预先训练好的 transformer 模型,并使用我们预先生成的文字游戏中的提示生成一个字符串。我提供了两种语言,但是你可以自由地测试你自己的语言,并在评论中写下你的经历。可以说,同样的原则应该适用于任何语言,如果您只是将任何目标语言的字典输入到 Tries 中,并获取一个用您选择的语言训练的正确的转换器。然而,在实践中,我注意到芬兰语版本比英语版本差得多,所以你的里程可能会有所不同。
即便如此,我们离真正的人工智能幽默又近了一步。看看这个坏小子吐出什么来。
Prompt: Here is a funny story about environment ions and environ mentions
Result: that I found on the internet. It is a story about a man who was trying to get a job in a chemical plant. He was told that the job was for a man with a PhD in chemistry. He went to the job interview and was told he was not qualified. He then went to his supervisor and said that he was qualified.
以一种可爱的方式,这有点接近 20 世纪早期的幽默——与社会规范和读者的期望相矛盾。它还注意到这个笑话是关于环境化学的,甚至评论这个笑话是“在互联网上找到的”。虽然它没有赢得任何奖项,但它是可以理解的,看起来很可能像一个幽默感很差的人写的东西。谁会因为这个可爱的小机器人尽力而为而生气呢?
Prompt: Tell me a joke about entrainment and mentally
Result: weirdness. I'm not sure if I'm going to be able to do this. I'm not a comedian. I don't have a lot of experience with comedy. I've never done a standup show. I have no idea what I'm doing. I just want to do it. I want to be a comedian
尽管如此,这些都不是我们所理解的幽默。看起来变形金刚在笑话方面没有得到足够的训练,而是尽力去写散文。我花了一些时间寻找一个专门针对笑话进行训练的模型,但它并没有产生太多不同的结果。我们能做得更好吗?事实证明,我们可以。
请打开 GPT-3
OpenAI 的可爱的人们已经将他们的 GPT-3 模型投入了公开测试,尽管它不是免费的(产生一个笑话需要大约 0.003€,或者每 1000 个笑话需要 3€),但它在人类语言的细微差别方面表现得相当好。你可以在这里注册你的 api 密匙。让我们修改代码来查询 API。
import os
import openai
words = eng_gen.wordplays[random.choice(list(eng_gen.wordplays.keys()))]
input_word = ''.join(words[0:2]) + ' ' + words[2] + ' and ' + words[0] + ' ' + ''.join(words[1:3])
openai.api_key = "api_key_here"
result = openai.Completion.create(
engine="text-davinci-002",
prompt=f"Write a joke containing the words '{input_word}'",
max_tokens=60,
temperature = 0.3,
n = 1,
echo = True
)
result.to_dict()['choices'][0]['text']
让我们看看结果如何
Prompt: Write a joke containing the words 'potent angle and pot entangle'
Result: What do you call a potent angle that's also good at entangling? A pot-entangle!
你看看那个。它不仅能理解笑话的典型措辞,还能将单词本身的细微含义联系起来。这实际上让我笑了,它是 100%程序生成的。我可以捣烂这个按钮,得到无数的笑话。实际上,在我们结束之前,让我们收拾一下,就这么做吧。
def create_jokes(wordplays, n, api_key):
results = pd.DataFrame({'input' : [], 'prompt' : [], 'answer' : []})
openai.api_key = api_key
for i in range(n):
words = wordplays[random.choice(list(wordplays.keys()))]
input_word = ''.join(words[0:2]) + ' ' + words[2] + ' and ' + words[0] + ' ' + ''.join(words[1:3])
prompt = f"Write a joke containing the words '{input_word}'"
result = openai.Completion.create(
engine="text-davinci-002",
prompt=prompt,
max_tokens=60,
temperature = 0.3,
n = 1
)
answer = [input_word, prompt, result.to_dict()['choices'][0]['text']]
results.loc[len(results)] = answer
return results
create_jokes(eng_gen.wordplays, 10, "an_api_key")
让我们看看它为我们准备了什么。
Why did the cables sons take cab lessons? So they could get a job as a taxi driver!
I was going to use a cleanser rated and clean serrated knife to make dinner, but then I realized that would be overkill.
What do you call a bagger who's also a manic German? A Bagger Manic Germanic!
What do you call a superintendent who's good at enticement? A superintend-ice-nt!
这些都出奇的不差。诚然,我必须按几次按钮才能得到一套令人尊敬的笑话,但尽管如此,这些都没有任何一个人扭曲和转折的话。你可以自己尝试一下 colab,看看你能想到什么。
结束语
总而言之,这比我最初预期的要好得多。我开始创建一个不错的单词对字典来激发我自己的想象力。有了一些聪明的数据结构,我们现在可以用我们想要的任何语言获得无穷无尽的数据结构。将这些插入现成的 NLP 模型,产生了令人惊讶的好结果。有没有喜剧演员因为这个 AI 而倒闭?大概不会。但我可以很容易地看到一个由一个写笑话的机器人和一个语音合成器在舞台上共同表演组成的利基之旅。或者充满程序性笑话的书。这些类型的程序可以帮助作家、喜剧演员或编剧克服白皮书综合症,或者只是作为机器人如何实现令人惊讶的逼真效果的介绍。
幽默是人类的典型特征之一。很难说机器能感觉到情感,悲伤、快乐或真正的笑声。几天前,我会认为他们缺乏语境意识,无法创造巧妙的文字游戏,将语言转换成新的含义。这个小实验是一个有趣的小练习,但却令人惊讶地深入探究了人类最终是什么。看着一个机器人写出可以理解、有时甚至是巧妙幽默的散文,感觉就像打破了一堵我不知道存在的墙。这种感觉更像是和电脑一起工作,而不是简单地命令它做什么。
无需闲置数据块集群即可按需处理数据
原文:https://towardsdatascience.com/process-on-demand-data-without-idle-databricks-clusters-cbf0bd99d8d6
在您的工作流程之前添加云功能,实现经济高效的解决方案
Databricks 是一种平台即服务产品,允许数据工程师执行数据的接收、探索和转换。最近,他们在多任务工作流平台中加入了创建数据管道的功能。此外,它们还提供了一种可配置和可扩展的数据接收功能,称为自动加载器。有了它,您可以按需和按计划接收数据。但是,如果您希望在数据到达时立即接收数据,则必须在等待文件到达时激活作业。这与工作流功能不兼容,并且会让您的计算集群无限期开启。情况就是这样,我以前工作的一家公司在一个项目上花费了数千美元,让空闲集群监听数据。
但是你一定要这样做吗?使用云功能作为您的数据传感器,您可以经济高效地触发您的 Databricks 管道。您将只使用您的集群进行实际处理。祝您身体健康

现在,云就是一切。在 Unsplash 上 Jerry Zhang 的照片。
0.预赛
你可以在这里找到我的伙伴库。微软 Azure 将是这个特定练习的云提供商,但是概念可以被翻译到其他提供商。数据集将是“纽约黄色出租车出行数据”,并且可以在这里找到。但是,数据集有许多分区。下载一个 CSV 文件,并使用该数据的子集创建一个新文件,以便在测试步骤中将其上传到 Azure 容器时最大限度地减少等待。
1.文件暂存区
您将需要一个存储服务,您的数据将在这里着陆,并在稍后由数据块提取以供消费。这种服务可以是亚马逊 S3、Azure Blob 存储容器等。该条目将使用 Azure 服务和 VS 代码。
在 Azure 插件中,有一个标签页叫做 【资源】 。点击“+”按钮,选择 【创建存储账户】:

创建存储帐户。截图由作者拍摄。
给它一个独特的名字,一个蔚蓝色的区域。然后,检查终端区域中的 Azure 活动日志,直到创建了资源。在左窗格中查找资源,右键单击并创建一个新容器:

创建 blob 容器。截图由作者拍摄。
创建容器后,您需要将它挂载到 Databricks 文件系统上。为此,请在您的 Databricks 工作区内运行以下代码单元:
正在安装 blob 容器。作者创建的片段。
2.数据库和表格
现在,根据您的数据模型创建适当的表。在这个简单的示例中,使用了一个表:
创建数据库表。作者创建的片段。
该表将接收来自中转区文件的出租车行程数据。
3.自动装载机查询
这是工作量的核心!但是首先,让我们使用dbutils在数据块中定义所需的参数:
Dbutils 小部件。作者创建的片段。
Autoloader 查询将定义从登台区到表的 ETL。让我们回顾一下代码:
自动加载器查询。我创建的片段:)
cloudFiles:向结构化流查询指示您要使用自动加载器。cloudFiles.format:传入数据的格式。在这种情况下,CSV。cloudFiles.schemaHints:默认情况下,所有发现的字段都将被视为字符串。您可以在这里指定特定字段的数据类型。load():指定将要加载的数据的位置。在这种情况下,是已装载容器的位置。select():选择所有列的子集。trigger(availableNow=True):非常重要。它向查询指定您想要处理所有尚未使用且现在可用的数据。否则,默认情况下,该查询将每半秒运行一次。table():将数据存储在指定的表格中。
4.数据砖块的工作
既然已经定义了查询,那么就要创建实际的数据管道了。重新定义的 Databricks 工作流的优点在于,您可以定义一组任务和依赖关系。以前在这个平台上做不到的事情。对于这个简单的例子,将定义一个单一的任务。你可以在这里阅读如何创建更复杂的工作流程。此外,请查看我的关于 Databricks 中管道编排的文章。
在【Path】里面,寻找自动加载器脚本所在的笔记本位置。此外,发出屏幕截图中描述的必需参数:

创建数据块作业。自己的作者身份截图。
有几点需要注意:
dbfs:/mnt/test是挂载的 Azure 存储容器的目录,如之前在挂载代码片段中定义的。default是默认创建的数据库的名称。- check point _ directory设置为
/tmp/,该目录在每次集群重启时都会被清除。千万不要在真实场景中将其设置为检查点目录,这只是为了练习。
5.功能应用程序
这是实现预期的按需行为的关键组件。一旦数据到达临时区域,该功能将被触发,然后,它将使用 Databricks 作业 API 运行 Databricks 工作流。

按需处理。我使用 Azure 图标创建的图片:)
要在 VS 代码中创建一个函数,寻找 Azure 插件。在工作区选项卡上选择“+”,点击 【创建功能】 :

创建本地函数。截图由作者拍摄。
现在,一件很棒的事情是,我们可以为函数触发器提供多种选择。这意味着我们的 Databricks 工作流可以通过这些方法中的任何一种来激活。在本练习中,请遵循以下步骤:
- 点击 【天蓝色斑点存储】 触发。
- 给这个函数一个唯一的名字,然后按回车键。
- 点击 “创建新的本地应用设置”。
现在,您的 VS 代码文件夹中应该填充了来自所创建函数的文件。当项目在编辑器中可用时,在local.settings.json中定义所需的环境变量:
函数的必需变量。作者创建的片段。
<your-storage-account-name>_STORAGE:将保存来自你的 Azure 存储帐户的连接字符串。若要获取该值,请转到 Azure 门户并选择您的存储帐户。点击左窗格中的 【访问键】 ,复制连接字符串。DB_JOB_ID:您的数据块作业的 id。创建工单后,复制与之关联的 id 并粘贴到此处。DATABRICKS_HOST:是您的 Databricks 工作区的 URL。应该类似于“https://ADB-xxxxxxxxx . xx . azuredatabricks . net/”DATABRICKS_TOKEN:您需要创建一个 Databricks 个人访问令牌,并在此处插入值。这将允许该函数与您的数据块工作空间进行交互。- 忽略其他参数。
设置好参数后,我们将在__init__.py脚本中实现函数的行为:
Azure 函数行为。我和❤一起创作的片段
该脚本需要注意的事项:
- Databricks CLI 是为 shell 设计的工具。然而,这个脚本利用了它是用 python 编写的这一事实,我们可以将这些对象用于这个函数的逻辑。在
requirements.txt文件中包含databricks-cli。 EnvironmentVariableConfigProvider是 Databricks CLI 中的一个类,它使用DATABRICKS_HOST和DATABRICKS_TOKEN环境变量来初始化环境。JobsService也是 Datbaricks CLI 中的一个类,允许您创建、运行、列出和删除作业。这是一个非常强大的工具。- 代码的控制流旨在避免多个函数执行触发同一作业的多个并发运行。一次只能运行一次。
代码完成后,我们必须创建实际的云资源,并上传我们刚刚实现的配置。为此,在 Azure 插件下,在 【资源】 窗格中点击 【在 Azure 中创建功能应用】 :

在云上创建功能。自己署名截图。
此外:
- 给这个函数一个唯一的名字,然后按回车键。
- 点击最新的 python 运行时堆栈。
- 对于本例,选择“美国东部”作为地区。在更现实的情况下,您应该选择最适合您业务案例的地区。
在创建函数时。右键点击后者,选择 “部署到功能 App”:

将代码部署到云功能。截图是我拍的。
这将把代码上传到云资源。要完成,在您的功能内的 【应用设置】 子菜单下点击右键,选择 【上传本地设置】 :

上传变量到云函数。截图由作者拍摄。
它会将local.settings.json内容设置为我们函数 app 的环境变量。就是这样!Databricks 工作区应该能够对存储容器中的文件上传做出反应,并运行适当的工作流。
6.测试项目
现在基础设施已经准备好了,是时候删除样本数据集了。初始黄行数据太重,无法快速上传。此外,选取一小部分记录样本,并将其上传到存储容器:

将文件上传到存储容器。截图是我拍的:)
一旦文件被加载,该函数将被触发,然后,它将向 Databricks 作业发出运行信号。这是 Azure 函数生成的日志示例:

Azure 函数运行日志。截图由作者拍摄。
此外,如果您在几分钟后转到 Databricks,作业将会完成:

数据块作业运行。自己的作者身份截图。
表格中应填入黄色的行程数据记录:

查询表中加载的数据。截图是我拍的。
它像魔法一样有效!
其他注意事项
这个项目的实现是在 Visual Studio 代码和 Databricks 工作空间中完成的。然而,如果你需要你的基础设施是 CI-CD 或 IaC 友好的,你总是可以使用 Azure 资源管理器模板和 Azure CLI 来创建环境。只需提取本文的核心概念,并根据您的用例进行调整。
此外,相应地测试和调整函数和数据块作业的行为。这种逻辑没有经过在很短的时间间隔内加载大量文件的实战测试。但是,它应该提供一个起点,以便您可以按需接收数据,而不需要无限期活动的群集消耗资源和资金,同时只侦听数据。
最后,作业被设置为在冷启动设置中创建集群。这将增加几分钟的执行时间。如果您需要作业尽可能快地运行,请考虑创建计算池,以便群集可以使用预热的实例。
如果你喜欢这个内容,请鼓掌并订阅,并在我的 LinkedIn 上与我联系。干杯!👾
https://github.com/hector6298 [## 赫克托 6298
github.com](https://github.com/hector6298)
用 ELK 离线处理 SCADA 报警数据记录
原文:https://towardsdatascience.com/processing-scada-alarm-data-records-offline-with-elk-4ad509ef97d3
工业自动化的开源工具

米卡·鲍梅斯特在 Unsplash 上的照片
本文是我们使用开源数据科学工具来分析 SCADA 系统产生的警报和事件日志的系列文章的继续。
这是本系列的第三篇文章,包括以前的文章,
在使用 ELK 离线处理 SCADA 报警数据中,出现了以下内容:
- 工业控制系统(又名 SCADA 系统)、其架构和用途的介绍
- 引入并安装了弹性搜索堆栈(ELK)
- 来自商业 SCADA 系统的警报和事件日志被清理并加载到 ELK 中
- 使用 ElasticSearch Kibana 和创建的简单仪表板完成了对原始警报事件的简单分析
在 SCADA 报警标准&指标中,涵盖了以下内容:
- 警报管理标准
- 警报生命周期
- 报警系统性能指标
这就是我们已经去过的地方,但我们要去哪里?在本文中,我们将…
- 检查警报生命周期
- 将报警事件处理成报警记录
- 用从报警标签中提取的有关过程区域和模块的信息增加报警记录
- 用有用的统计数据增加警报记录
这将使我们在后续文章中创建仪表板,使用 SCADA 警报标准&指标中描述的行业标准指标来分析警报记录。
SCADA 系统
正如前一篇文章中所讨论的,SCADA 是基于计算机的工业控制系统的通称。SCADA(监控和数据采集)系统可用于监测和控制工业过程,包括水处理、制造、发电站、食品和饮料等。事实上,任何使用自动化制造设备的工业过程。
警告
定义
警报的目的是提醒操作员需要干预的异常或危险过程条件。
警报生命周期
下图展示了一个简化的警报生命周期,它符合 IEC 标准。
对于每个报警点,被监控的物理过程包含正常和异常两种状态。
因此,每个报警都有两个属性,报警状态(正常|报警)和确认状态(已确认|未确认)。由于警报有两个属性,它可以在任何时候处于四种状态之一。允许的状态转换如下图所示,

作者图片
下面的时序图显示了两个信号之间的典型相互作用,并指定了三个时序值,将在后面的分析中使用。

作者图片
从警报日志创建警报记录
我们的 SCADA 系统生成的日志文件描述了每个状态转换(这些在使用 ELK 离线处理 SCADA 报警数据中进行了分析)。为了能够执行更详细的警报分析(如 SCADA 警报标准&指标中所述),状态转换事件将被处理,我们将为每个警报的整个生命周期生成一个警报记录。
报警日志的格式
我手头的报警日志文件是由施耐德电气 CitectSCADA 产品生成的。这些文件最初是空格分隔的文本文件,我们之前已经将其转换为 CSV。下面是一个典型文件的前五条记录的示例,

作者图片
报警记录的格式
下面是我们将存储在 Elasticsearch 数据库中的警报记录的定义。

作者图片
各个字段的详细信息如下:
时间戳:
发出警报时的时间戳。与 raiseTime 的值相同
desc:
产生警报的设备的描述(如增压泵#1 电流)
优先级:
指示报警优先级的数值。对于此数据集,1–5。
状态:
警报确认状态。(这是处理报警事件的内部变量,不应写入报警记录)。
p_state:
“过程状态”对应于报警状态。(这是处理报警事件的内部变量,不应写入报警记录)。
a_state:
报警状态;a、B、C 或 D 与状态转换图对齐。(这是处理报警事件的内部变量,不应写入报警记录)。
提出时间:
警报激活时的时间戳。
确认时间:
指示报警被确认的时间的时间戳。
RTNTime:
指示警报何时转换回正常状态(返回正常)的时间戳。
t_active
警报处于活动状态的时间长度。
t_ack:
警报被确认之前的时间长度。
t_interval:
从 RTN 到标签再次进入报警状态的时间长度。
喋喋不休:
一个布尔值,表示这是一个振动警报(即每秒触发 2 次以上的警报)
稍纵即逝:
一个布尔值,表示这是一个短暂的警报(即在操作员有机会采取行动之前返回到正常状态的警报)。被认为小于 1 秒)
装备 _ 代码
模块编号
流程编号:
子流程 _ 编号:
设备 _ 编号:
附加的资产信息在下面的数据扩充中描述。
标签:
报警信号的字母数字标识符。
数据扩充
许多资产编号方案将资产数据嵌入到资产标识符中。如果提取资产标识符的各个组成部分并将其放入警报记录的各个字段中,则警报记录的分析会变得更容易。
下面是在这里分析的数据中使用的方案,

作者图片
一个简单的正则表达式被用来提取这些组件,

代码—记录创建
将 CSV 报警事件数据转换为报警记录的代码包含在 csv-2-record.py 文件中,该文件可在以下要点中找到。
主要功能
主要功能,
- 处理命令行输入参数
- 设置循环以迭代所有数据文件
- 将每个数据文件读入熊猫数据帧
- 使用状态机处理数据帧的每一行
数据结构
由于我们需要处理多个警报日志条目,然后才能将完整的警报记录条目写出到文件中,因此我们需要一个内部数据结构来存储部分处理的警报记录。
创建了一个 AlarmRecord 类来存储数据,该类包含一个 写出 方法来写出数据。 AlarmRecord 类是一个 Python @dataclass 是一个用于管理主要由数据组成的类的有用库。
使用一个数据变量,它是一个报警记录的字典,使用报警标签作为关键字。
正在处理的报警记录存储在数据变量(Python字典)中,由报警标签(唯一标识符)索引。
状态机
要处理报警日志条目,需要一个状态机。

作者图片
因为只有四种状态,所以使用了 it-then-else 结构来创建状态机。这是可行的极限,如果有更多的状态,将需要使用更复杂的实现(例如 GOF 状态模式)。
我们将只研究两个转换。
如果我们处理一个报警事件,其中报警处于活动状态(on 为真)且报警未被确认(ack 为假),那么从我们的报警状态转换图中我们知道我们应该转换到状态 b。
下面的代码描述了这一点,

如果我们处理一个报警事件,其中报警处于活动状态(on 为真)且报警被确认(ack 为真),那么从我们的报警状态转换图中我们知道我们应该从状态 B 转换到状态 c。
下面的代码描述了这一点,

加载到弹性搜索
csv-record-2-es.py 文件(在要点中有)用于将报警记录 csv 文件加载到弹性搜索中。
下面的代码将所有记录读入 Pandas dataframe,然后使用 Python Elasticsearch 库中的 bulk upload 函数将数据加载到数据库中。
将来的
到目前为止,我们已经处理和分析了原始警报日志文件,并创建了警报记录。在 SCADA 警报标准&指标中,我们看到警报管理标准为警报管理系统定义了 12 项性能指标。在本系列的下一篇文章中,我们将使用 ELK 创建仪表板,根据这 12 个性能标准来评估我们的系统性能。
资源
完整的代码位于下面的要点。
进一步阅读
感谢阅读,希望你喜欢这篇文章。
为了进一步探索,
- 订阅电子邮件通知
- 阅读本系列的第 1 部分,使用 ELK 离线处理 SCADA 报警数据
- 阅读本系列的第 2 部分, SCADA 报警标准&指标
- 点击文章顶部的“关注”按钮
- 对于工业 4.0 的所有内容,请查看我的工业数字化转型&工业 4.0 出版物
- 欢迎在 LinkedIn 上加入我的社交网络(记得提到你已经读过这篇文章)
为了支持中型作者,可以考虑订阅。
记得点击订阅和关注按钮,

产品分析数据科学家面试问题
原文:https://towardsdatascience.com/product-analytics-data-scientist-interview-questions-eab46ed01338
产品分析面试问题是领先公司在数据科学家和数据分析师的技术面试中最常问的问题

作者在 Canva 上创建的图片
大型数据科学公司经常雇佣数据科学家和数据分析师在负责特定产品的团队中工作。这就是为什么在技术面试中问产品分析问题是一种常见的做法。本文主要关注涉及使用 Python 或 SQL 等编程语言基于数据集计算相关产品指标的问题。
候选人被要求检索或计算的产品指标是不同的,从简单的事件或交互量到更复杂的增长率或保留率。产品分析访谈问题通常需要按用户、用户组或时间范围处理日期、编写条件和汇总数据。
产品分析访谈问题#1:按平均会话时间划分的用户

截图来自 StrataScratch
问题链接:https://platform . stratascratch . com/coding/10352-users-by-avg-session-time
这是一个非常有趣的问题,在元数据科学面试中经常被问到。这个产品分析访谈问题的标题是“按平均会话时间划分的用户”,我们被要求计算每个用户的平均会话时间。据说会话被定义为页面加载和页面退出之间的时间差。为简单起见,我们可以假设用户每天只有一个会话,如果当天有多个相同的事件,则只考虑最近的 page_load 和最早的 page_exit。
这是一个中等水平的问题,因为虽然从事件中获取单个会话的持续时间相当简单,但要识别有效的会话并从时间差中获取平均值却比较困难。
理解数据
第一步总是要理解数据。在典型的面试中,你不会看到任何实际的数据,只会看到一个包含列名和数据类型的模式。在这种情况下,情况相当简单。我们只给了一个表,或者数据帧,因为我们是用 Python 写的,叫做 facebook_web_log。它有三列:用户 id、时间戳和动作。

截图来自 StrataScratch
根据问题中给出的解释,我们可以猜测“action”属性的两个示例是“page_load”和“page_exit”。但是不能排除这个字段不取任何其他值。也许我们还会跟踪用户在会话期间是向上还是向下滚动。那么“action”列中的其他值可能是“scroll_down”或“scroll _ up”——这种值我们不需要解决问题,但我们仍然需要预测它们可能在那里。
我们还可以看到时间戳属性是一个日期时间。这很重要,因为它决定了我们可以用什么函数来操作这些值。总而言之,我们可以推断出这个数据集中的每一行都代表了某个用户在某个时间点执行的一个操作。
制定方法
接下来,我们可以制定编写代码时将遵循的方法或几个一般步骤:
1.第一步可以是从数据集中只提取我们实际需要的这些行。特别是,我们感兴趣的是这些行中的动作是‘page _ load’或‘page _ exit’。我们这样做是因为我们预计数据集中可能会存储其他操作。
2.在下一步中,明智的做法是创建一个包含所有单独会话的表。这个表有 3 列:一列是 user_id,一列是 page_load 动作的时间戳,一列是 page_exit 动作的时间戳。我们可以通过合并一个用户的所有 page_load 时间戳和同一个用户的所有 page_exit 时间戳来实现。
合并时间戳的问题是,我们最终会得到一些不真实的会话。因为,如果一个用户有多个会话,并且我们在合并时考虑了他们的 page_load 和 page_exit 操作的所有可能的组合,我们将不可避免地得到一些 page_load 来自一个会话而 page_exit 来自另一个会话的行。
3.为了检测和消除这些情况,我们可以从添加一个条件开始,即 page_exit 应该总是在 page_load 之后发生。
这将涵盖一种情况,但是当 page_exit 发生在 page_load 之后,但仅仅是几天之后的情况呢?或者如果一个用户在一天内有几个 page_load 操作,而只有一个 page_exit 操作呢?这个问题实际上在这里给了我们一个提示,如果当天有多个相同的事件,我们应该只考虑最晚的 page_load 和最早的 page_exit。因此,在选择最新的 page_load 和最早的 page_exit 时间戳时聚合数据应该涵盖这两种情况。
但是我们应该用什么来汇总数据呢?如果我们只按 user_id 进行聚合,那么我们最终将为每个用户提供一个会话,而不一定是有效的会话。但是问题中还有一个提示可以帮助我们。它说一个用户每天只有一次会话。这意味着每个有效的会话都可以由一个 user_id-date 元组唯一地标识,因此我们可以按 user_id 和 date 聚集结果。
4.因此,作为第四步,让我们添加一个包含每个会话日期的列,我们可以从 page_load 时间戳中提取它,然后聚合数据并选择 latest page_load 和 earliest page_exit。
5.此时,我们将看到每个用户的有效会话,以及它们各自的 page_load 和 page_exit 时间戳。从这里开始,得到最终的解决方案是相当简单的。我们可以从计算两个时间戳之间的差异开始,这是会话的持续时间。
6.但是让我们记住,我们感兴趣的是每个用户的平均会话持续时间,每个用户仍然可以在不同的日子进行多个会话。为了得到解决方案,我们可以再次聚合数据,这次只使用 user_id,并获得属于每个用户的平均持续时间。
编写代码
现在,我们可以按照这些步骤,使用 Python 编写这个产品分析面试问题的实际解决方案。让我们从导入一些有用的库开始。Pandas 是流行的用于操作数据集的 Python 库,Numpy 提供了数学函数,我们特别需要它在最后一步计算会话持续时间的平均值。
import pandas as pd
import numpy as np
第一步是从数据集中提取 page_load 和 page_exit 操作。我们可以创建原始数据集的两个副本,每个动作一个。要获得 page_load 操作的时间戳列表,让我们使用 Pandas loc 属性。我们可以从原始数据集 facebook_web_log 开始,并对其应用 loc 属性。然后,让我们指定条件或应该返回哪些行。我们需要原始数据集的“action”列中的值等于“page_load”的这些行。一旦我们定义了输出哪些行,我们也可以决定返回哪些列。我们只需要 user_id 和时间戳,因为“action”对于数据集副本中的所有行都是相同的。
import pandas as pd
import numpy as np
*# Extract page_load and page_exit actions from the dataset*
loads = facebook_web_log.loc[facebook_web_log['action'] == 'page_load', ['user_id', 'timestamp']]
当您运行这段代码时,您可以看到只剩下 2 列。这些实际上是 action 列等于 page_load 的所有行。接下来,通过使用相同的 loc 属性,我们可以为 page_exit 操作获得一个类似的表,只需更改条件中的值。
exits = facebook_web_log.loc[facebook_web_log['action'] == 'page_exit', ['user_id', 'timestamp']]
您可以添加这一行代码,一旦运行它,您将看到原始数据集中的一组行,其中的操作等于“page_exit”。
下一步是识别每个用户可能的会话,正如已经提到的,我们可以通过合并只有 page_loads 和 page_exits 的两个表来实现。我们可以调用这个新的数据集会话,并使用 Pandas merge()函数。在这个函数中,我们首先指定哪两个数据帧应该合并在一起,在这个例子中,它将是 loads 和 exits。接下来,我们需要决定我们想要执行哪种合并。你可能记得在 SQL 中,我们有不同种类的连接:左连接,右连接,内连接等等。这里也一样,我们可以用左合并。最后,我们还需要指定要连接哪个或哪些列。这与 SQL 中的 ON 关键字相同,Pandas 中的参数也被调用。
import pandas as pd
import numpy as np
*# Extract page_load and page_exit actions from the dataset*
loads = facebook_web_log.loc[facebook_web_log['action'] == 'page_load', ['user_id', 'timestamp']]
exits = facebook_web_log.loc[facebook_web_log['action'] == 'page_exit', ['user_id', 'timestamp']]
*# Identify possible sessions of each user*
sessions = pd.merge(loads, exits, how='left', on='user_id')
运行这段代码时,您可以看到我们有一个 user_id,然后是 page_load 操作的时间戳和 page_exit 操作的时间戳。然而,这里有两个问题。首先,我们可以看到 user 2 有一个 page_load,但是没有与之匹配的 page_exit。这可能只是数据集中的一个错误,我们不知道,但我们仍然需要处理它。面试问题没有具体说明在这种情况下应该做什么,但是因为我们现在有 page_exit,所以不可能计算这样一个会话的持续时间,所以我建议我们跳过这个会话。为了避免这种情况,我们可以从左合并切换到内合并。后者将只返回两个时间戳都存在的行。在上面的代码中,将 how='left '改为 how='inner ',看看结果会如何变化。
第二个问题是,这些列名 timestamp_x 和 timestamp_y 很容易混淆。为了更清楚地说明哪个对应于 page_load,哪个对应于 page_exit 操作,我们可以在 merge 函数中再添加一个参数。它被称为后缀,允许用其他名称替换这些 x 和 y。这些名称的顺序必须与我们合并表的顺序一致,这一点很重要。在上面的代码中,添加一个新的参数 suffixes=['_load ',' _exit'],看看列名将如何变化。
现在很明显,我们正在处理用户会话。但是正如你所看到的,并不是所有这些会话都是真实的或可能的,因为我们合并了所有可能的 page_load 时间戳和所有可能的 page_exit 时间戳。这就是为什么我们有以下几个步骤来过滤有效的会话。
这里我们可以做的第一件事是确保 page_exit 发生在 page_load 之后。要在 Python 中添加条件,我们需要选择“sessions”表的一部分,其中“sessions”表的“timestamp_load”列中的值小于或早于“sessions”表的“timestamp_exit”列中的值。
import pandas as pd
import numpy as np
*# Extract page_load and page_exit actions from the dataset*
loads = facebook_web_log.loc[facebook_web_log['action'] == 'page_load', ['user_id', 'timestamp']]
exits = facebook_web_log.loc[facebook_web_log['action'] == 'page_exit', ['user_id', 'timestamp']]
*# Identify possible sessions of each user*
sessions = pd.merge(loads, exits, how='inner', on='user_id', suffixes=['_load', '_exit'])
*# Filter valid sessions:*
*## page_load before page_exit*
sessions = sessions[sessions['timestamp_load'] < sessions['timestamp_exit']]
现在,表中剩下的所有会话都有可能已经发生,但是我们仍然可以看到一些跨越几天的会话,或者同一个用户在一天内的几个会话,这显然是不正确的。要删除它们,让我们按照我们定义的步骤,从添加一个带有 page_load 时间戳日期的列开始。为此,我们可以定义一个新列,让我们称之为“date_load ”,它将几乎等于“timestamp_load”列中的值,但我们只想提取日期并跳过时间。我们可以使用 dt.date 函数。多亏了这个“dt”,我们可以将这个函数专门应用于“timestamp_load”列中的日期时间值。
sessions['date_load'] = sessions['timestamp_load'].dt.date
您可以添加这行代码来查看新列是如何添加到结果中的。完成这些后,我们现在可以使用每个 user_id-date_load 对来惟一地标识每个会话。因此,如果同一个用户有几个会话,并且日期相同,我们知道其中只有一个是有效的。而问题告诉我们,这将是最短的一个。为了选择这些会话,我们可以通过使用 Pandas groupby()函数来聚集数据,特别是通过每个会话的唯一标识符来分组,因此是通过 user_id-date_load 对——这样我们每个会话只剩下一行。然后,我们应该定义聚合函数,因为我们希望同时应用两个函数,所以我们将使用 Pandas agg()函数,并说我们希望在每一行中有最新或最大的时间戳 _load 和最早或最小的时间戳 _exit。我们还可以添加 Pandas reset_index()函数——在聚合时添加它是一个很好的做法,因为如果没有它,Pandas 会将 user_id 和 date_load 列视为索引,并且我们不会显示它们——您可以尝试从下面的代码中删除它,以查看区别。
import pandas as pd
import numpy as np
*# Extract page_load and page_exit actions from the dataset*
loads = facebook_web_log.loc[facebook_web_log['action'] == 'page_load', ['user_id', 'timestamp']]
exits = facebook_web_log.loc[facebook_web_log['action'] == 'page_exit', ['user_id', 'timestamp']]
*# Identify possible sessions of each user*
sessions = pd.merge(loads, exits, how='inner', on='user_id', suffixes=['_load', '_exit'])
*# Filter valid sessions:*
*## page_load before page_exit*
sessions = sessions[sessions['timestamp_load'] < sessions['timestamp_exit']]
*## Add a column with the date of a page_load timestamp*
sessions['date_load'] = sessions['timestamp_load'].dt.date
*## aggregate data and select latest page_load and earliest page_exit*
sessions = sessions.groupby(['user_id', 'date_load']).agg({'timestamp_load': 'max', 'timestamp_exit': 'min'}).reset_index()
现在,我们只剩下每个用户的有效会话,我们可以继续计算每个会话的持续时间。这相当简单,因为从 timestamp_exit 列中减去 timestamp_load 就足够了。让我们将结果存储在一个名为 duration 的新列中。
sessions['duration'] = sessions['timestamp_exit'] - sessions['timestamp_load']
您可以添加这行代码来查看新列是如何添加到结果中的。最后一步是计算每个用户的平均会话持续时间。因此,我们只想获得每个用户的两个持续时间,并返回它们的平均值。乍一看,我们应该能够按 user_id 列聚合数据,因为我们希望每个用户有一行,并取 duration 列的平均值。
result = sessions.groupby('user_id').mean()['duration']
但很遗憾,这是行不通的。那是因为我们只能对数字使用 mean()函数。但是即使 duration 列对我们来说看起来像一个数字列,对 Python 来说,它是一种叫做 time delta 的数据类型——它是一种特殊的类型,用于存储两个时间戳之间的差异。这种数据类型使我们能够使用一些特定于时间的函数,例如将持续时间转换为小时甚至天,但是对我们来说,这并不那么有用,因为取平均值并不那么简单。
解决这个问题的方法是使用 NumPy 库中的 mean()函数——它更高级,支持取时间增量的平均值。为了能够在 Pandas 中使用自定义聚合函数,我们可以再次使用 agg()函数。
result = sessions.groupby('user_id')['duration'].agg(np.mean())
然而,这还不行。这是因为,根据定义,NumPy 库中的 mean()函数必须将平均值作为参数。那么如何才能把每个用户的所有时长分别作为参数传递呢?我们可以使用熊猫λ关键字。通常,lambda 允许遍历所有行,同时对每一行分别应用一些函数。
在这种情况下,有点棘手,因为我们已经过了聚合步骤,但是在应用聚合函数之前。这意味着 lambda 函数将首先立即返回一个用户的所有持续时间,然后立即返回下一个用户的所有持续时间,依此类推。这就是为什么我们可以用它向 mean()函数传递正确的参数。mean()函数将知道一次从一个用户的所有持续时间中取平均值。不要忘记像上次一样的 reset_index()函数,这应该会产生预期的结果!
import pandas as pd
import numpy as np
*# Extract page_load and page_exit actions from the dataset*
loads = facebook_web_log.loc[facebook_web_log['action'] == 'page_load', ['user_id', 'timestamp']]
exits = facebook_web_log.loc[facebook_web_log['action'] == 'page_exit', ['user_id', 'timestamp']]
*# Identify possible sessions of each user*
sessions = pd.merge(loads, exits, how='inner', on='user_id', suffixes=['_load', '_exit'])
*# Filter valid sessions:*
*## page_load before page_exit*
sessions = sessions[sessions['timestamp_load'] < sessions['timestamp_exit']]
*## Add a column with the date of a page_load timestamp*
sessions['date_load'] = sessions['timestamp_load'].dt.date
*## aggregate data and select latest page_load and earliest page_exit*
sessions = sessions.groupby(['user_id', 'date_load']).agg({'timestamp_load': 'max', 'timestamp_exit': 'min'}).reset_index()
*# Calculate the duration of the session*
sessions['duration'] = sessions['timestamp_exit'] - sessions['timestamp_load']
*# Aggregate to get average duration by user*
result = sessions.groupby('user_id')['duration'].agg(lambda x: np.mean(x)).reset_index()
运行这段代码时,只有 2 行,每个用户一行,第二列实际上包含平均会话持续时间,取自该用户的所有会话。这是产品分析面试问题的答案。
产品分析面试问题#2:慷慨评论的性别

作者在 Canva 上创建的图像
这是一个在 Airbnb 的数据科学面试中被问到的简单问题。这与第一个问题类似,因为我们也被要求找出每个人的平均值。这一次,没有涉及日期,所以过程容易得多。

截图来自 StrataScratch
问题链接:https://platform . strata scratch . com/coding/10149-gender-with-慷慨-reviews
我们被要求写一个查询,以找出作为客人写评论时,哪种性别给出的平均评论分数更高。这里的提示是使用“from_type”列来标识客人评论。问题还指示我们输出性别和他们的平均复习分数。

截图来自 StrataScratch
这个产品分析访谈问题可以通过以下一般步骤来解决:
- 使用 pd.merge(dataframe1,dataframe2,on = common key)执行内部连接;
- 使用[column_name]从 dataframe 中筛选特定的列,然后选择值等于“= =”guest 的行;
- 使用。groupby(column_name) on gender 对指定列的数据帧进行分组,并使用 mean()获得每组的平均值;使用 to_frame('column_name ')将结果对象转换为 dataframe
- 使用 max()和['column_name']选择平均分数最高的性别,以便只返回性别列。
产品分析面试问题#3:最低价格订单
下一个问题来自 Amazon,也涉及到按用户聚合数据集,这次使用另一个聚合函数,因为我们不再对平均值感兴趣,而是对最低数量感兴趣。

截图来自 StrataScratch
问题链接:https://platform . stratascratch . com/coding/9912-最低价-订单
我们被要求找出每个客户的最低订单成本,并输出客户 id 以及名字和最低订单价格。

截图来自 StrataScratch
在这种情况下,只需要完成两个主要步骤:
- 使用 pd.merge(dataframe1,dataframe2,on = common_table_keys)对订单和客户执行内部联接;
- 使用。groupby(column_name)根据指定的列对数据帧进行分组,然后使用 min()获得每组的最小值。
产品分析面试问题 Messenger 上最活跃的用户

作者在 Canva 上创建的图像
这是一个更难的问题,Meta 在对数据科学家和数据分析师的采访中也问过这个问题。在这种情况下,汇总每个用户的数据和统计事件是不够的,还需要额外的步骤来查找前 10 个结果。

截图来自 StrataScratch
问题链接:https://platform . stratascratch . com/coding/10295-messenger 上最活跃的用户
我们被告知,Messenger 将用户之间的消息数量存储在一个名为“fb_messages”的表中。在该表中,“用户 1”是发送方,“用户 2”是接收方,“消息计数”是他们之间交换的消息数量。然后,我们被要求通过计算 Facebook Messenger 上最活跃的 10 个用户发送和接收的消息总数来找出他们。该解决方案应该输出用户名和他们发送或接收的消息总数。

截图来自 StrataScratch
在这个产品分析访谈问题中,需要遵循的步骤如下:
- 连接数据帧的两个片段;
- 使用 group by 函数的所有用户的聚合邮件计数;
- 按邮件总数对用户进行降序排序。
产品分析面试问题 5:用户增长率
这个产品分析问题来自 Salesforce,涉及用户增长率。解决这个问题更加困难,因为每个月的数据需要单独汇总,然后合并在一起计算比率。

截图来自 StrataScratch
问题链接:https://platform . stratascratch . com/coding/2052-user-growth-rate
我们被要求找出每个账户从 2020 年 12 月到 2021 年 1 月的活跃用户增长率。规定增长率定义为 2021 年 1 月的用户数除以 2020 年 12 月的用户数。我们应该输出 account_id 和增长率。

截图来自 StrataScratch
如上所述,这个问题可以通过以下两个主要步骤来解决,然后将输出调整为问题所需的格式:
- 计算 2021 年 1 月和 2020 年 12 月的不同用户总数。按 account_id 对数据进行分组;
- 除以用户总数得到增长率;
- 输出 account_id 和计算的增长率。
结论
在本文中,我们详细解释了 Meta 的一个产品分析面试问题的解决方案,随后是 4 个不同难度的其他类似问题的示例。这应该让你对在数据科学面试中提出的产品分析面试问题的类型有所了解,并了解如何处理和解决此类问题。
请随意浏览 StrataScratch 平台上的其他产品分析面试问题。使用“方法提示”功能来显示解决问题的一般步骤,并与其他用户讨论您的代码,以获得更多的见解和反馈。练习使用真题后,你应该准备好在面试时面对产品分析问题!
【https://www.stratascratch.com】最初发表于https://www.stratascratch.com/blog/product-analytics-data-scientist-interview-questions/?utm_source=blog&utm_medium=click&utm_campaign=medium。
用于相似性搜索的产品量化
原文:https://towardsdatascience.com/product-quantization-for-similarity-search-2f1f67c5fddd
如何利用非对称距离计算(ADC)在内存中压缩和适应海量向量集进行相似性搜索

马库斯·温克勒在 Unsplash 上的照片
相似性搜索和最近邻搜索是非常流行的,在很多领域都有广泛的应用。它们用于推荐系统、支持产品图像搜索的在线商店和市场,或者专用于文档、媒体或对象匹配和检索的系统。
相似性搜索通常是在大量的对象嵌入集合上完成的,通常是以高维向量的形式。
在内存中拟合一组庞大的高维向量来执行相似性搜索是一个挑战,乘积量化可以通过一些权衡来帮助克服这一点。
什么是产品量化
乘积量化(PQ)是一种用于矢量压缩的技术。这对于压缩高维向量进行最近邻搜索是非常有效的。根据作者的 对最近邻搜索【1】、
其思想是将空间分解成低维子空间的笛卡儿积,并分别量化每个子空间。
一个向量由一个由它的子空间量化索引组成的短码来表示。两个向量之间的欧几里德距离可以从它们的代码中有效地估计出来。非对称版本提高了精度,因为它计算矢量和代码之间的近似距离”。
注意,产品量化不是降维。
在乘积量化中,它是矢量量化的一种形式,量化后矢量的数量将保持不变。然而,压缩向量中的值现在被转换成短代码,因此它们是符号的而不再是数字的。使用这种表示法,每个向量的大小都大大减小了。
产品量化也是在 Faiss (脸书人工智能相似性搜索)中实现的许多索引类型之一,这是一个为高效相似性搜索而高度优化的库。
产品量化如何工作
假设我们在数据库中有一个向量集合,每个向量的维数(或长度)是 128。这意味着一个向量的大小是 128 x 32 位= 4096 位(相当于 512 字节)。

除非另有说明,所有图片均为作者所有
首先,我们将向量分割成几段。下图显示了向量被分割成 8 段,其中每段的长度为 16。

将一个向量分割成线段
我们对每个向量都这样做,因此我们实际上是把向量分成 8 个不同的子空间。如果我们在数据库中有 1000 个向量,那么每个子空间将包含 1000 个长度为 16 的段。
培养
接下来,我们通过在每个子空间上运行 k 均值聚类来训练我们的向量。基于我们选择的k的值,k-means 聚类将在该子空间内生成k质心(即聚类中心),并且这些质心具有与片段相同的长度。
质心也称为再现值。质心的集合被称为码本,我们稍后会详细讨论它。
举个例子,如果我们选择k=256,我们将会得到总共 256 x 8 = 2048 个质心。

在每个子空间上运行 k-means 聚类
质心也称为再现值,因为它们可以用于通过连接来自每个片段的相应质心来近似重构矢量。然而,重构的矢量不会与原始矢量完全相同,因为乘积量化是一种有损压缩。
对于训练,也可以使用不同的向量集或子集,只要它们具有与数据库向量相同的分布。
编码
在训练完成后,对于数据库中的每个向量段,我们从各自的子空间中找到最近的质心。换句话说,对于向量的每一段,我们只需要从属于同一子空间的 256 个质心中找出最近的质心。
对于每个线段,在获得最近的质心后,我们用该质心的 id 替换它。质心 id 只不过是子空间内质心的索引(一个从 0 到 255 的数)。
这就是它们,它们是矢量的压缩表示。这些就是我们前面讲过的短码,姑且称之为 PQ 码。
在这个例子中,一个 PQ 码由跨段的 8 个质心 id 组成。我们在数据库中有 1000 个向量,因此会转换成 1000 个 PQ 代码。

PQ 码,向量的压缩表示
基本上,我们所做的是用质心 id 编码我们的原始向量,其中每个片段用 8 位编码。
每个向量有 8 个段,因此每个编码的向量仅占用 8×8 比特= 8 字节的空间。与存储 512 字节的原始向量相比,这节省了大量空间。
如本例所示,我们的内存使用量减少了 64 倍(每个向量从 512 字节减少到 8 字节),这在我们处理数十万条记录(如果不是数百万条的话)时是一个巨大的数目!

产品量化后内存使用减少

照片由 Unsplash 上的 krakenimages 拍摄
*k*的值通常是 2 的幂。对于
M段,一个 PQ 码的存储要求是M*(log base 2 of k)位。
量化搜索
因此,我们有效地使用 8 个质心 id 来表示一个矢量。但是这种表示对于相似性搜索究竟是如何工作的呢?
答案就在码本中,它包含了质心,即再现值。该过程解释如下。
给定一个查询向量q,我们的目标是从数据库中的向量集合中找到与q非常相似的向量。
典型的过程是计算并比较查询向量和数据库中所有向量之间的距离,并返回距离最短的前 N 条记录。
然而,我们要做一些不同的事情。我们不会用原始向量来计算距离。相反,我们将进行非对称距离计算(ADC ),并使用矢量到质心的距离来估计距离。
我们首先将查询向量分割成相同数量的片段。
对于每个查询向量段,我们使用来自码本的相同段的所有质心预先计算部分平方欧几里德距离。
这些部分平方欧几里德距离被记录在距离表d中。在我们的示例中,如下所示,距离表由 256 行和 8 列组成。

预计算距离表
现在我们有了距离表,只需查找部分距离并求和,就可以轻松获得 PQ 代码每行的距离。

从距离表中查找部分距离
在获得 PQ 代码的所有行的距离之后,我们按照升序对它们进行排序,并且从顶部结果(即,具有最短距离的记录)开始,从数据库中找到并返回相应的向量。
下图总结了相似性搜索的产品量化过程。

相似性搜索的产品量化过程
值得注意的是,对于乘积量化,搜索仍然是强力搜索,因为距离查找和求和是穷举的,并且需要对 PQ 码的所有行进行。
此外,因为我们比较的是矢量到质心的距离,这些距离并不是精确的矢量到矢量的距离。它们只是估计的距离,因此结果可能不太精确,并且可能不总是真正的最近邻。
可以通过调整质心的数量或分段的数量来提高搜索质量。更多的质心或段导致更高的准确度和精度,但是它们也会减慢搜索操作以及训练和编码所需的时间。除此之外,更多的质心可能会导致表示代码所需的比特数增加,从而节省更少的内存。
这里有一个简单的 Python 实现产品量化的例子,灵感来自于 nanopq 。
摘要
乘积量化将矢量分割并拆分成段,并分别量化矢量的每一段。
数据库中的每个向量都被转换成一个短码(PQ 码),这是一种对于近似最近邻搜索来说非常节省内存的表示。
带有产品量化的相似性搜索是高度可扩展的,但是我们用一些精度来换取存储空间。
以较不精确的搜索为代价,产品量化使大规模搜索成为可能,否则这是不可能的。
由于产品量化本身并不是非常大规模搜索的最有效方法,我们将在下一篇文章中看到如何实现更快的非穷举搜索方法。点击下面的链接了解更多信息。
参考
[1] H. Jégou,M. Douze,C. Schmid,最近邻搜索的乘积量化 (2010)
[2] C. McCormick,k-NN 教程第一部分的产品量化器(2017)
[3] J. Briggs,乘积量化:将高维向量压缩 97%
[4] 纳米乘积量子化(nanopq)
***Before You Go...****Thank you for reading this post, and I hope you’ve enjoyed learning about product quantization for similarity search.**If you like my post, don’t forget to hit* [***Follow***](https://peggy1502.medium.com/) *and* [***Subscribe***](https://peggy1502.medium.com/subscribe) *to get notified via email when I publish.**Optionally, you may also* [*sign up*](https://peggy1502.medium.com/membership) *for a Medium membership to get full access to every story on Medium.*📑 *Visit this* [*GitHub repo*](https://github.com/peggy1502/Data-Science-Articles/blob/main/README.md) *for all codes and notebooks that I shared in my posts.*© 2022 All rights reserved.
有兴趣阅读我的其他数据科学文章吗?查看以下内容:
https://pub.towardsai.net/building-a-product-recommendation-engine-with-aws-sagemaker-321a0e7c7f7b 
掌握动态编程系列
View list2 stories

使用 Python 进行生产规划
原文:https://towardsdatascience.com/production-fixed-horizon-planning-with-python-8dd38b468e86
使用 python 实现 Wagner-Whitin 算法进行生产规划,以最小化生产的总成本

(图片由作者提供)
目的 使用 python 设计一个最优的生产计划,以满足客户需求并使总生产成本最小化。
简介
T5主生产计划是商业团队和生产之间的主要沟通工具。
您的客户发送采购订单和特定数量以便在特定时间交付。

客户订单示例—(图片由作者提供)
生产计划用于通过在最小化库存和最大化根据设置生产的数量之间找到平衡来最小化生产的总成本。
在本文中,我们将通过 python 使用 Wagner-Whitin 方法实现最优生产计划。
💌新文章直接免费放入你的收件箱:时事通讯
如果你喜欢观看,可以看看这个教程的视频版本
如何用 Python 规划自己的生产?
方案
你是一家小型工厂的生产计划经理,生产服务于本地和国际市场的无线电设备。
客户将采购订单(PO) 发送到您的商务团队,其中包含数量和预计交付日期。

生产计划—(图片由作者提供)
您的角色是安排生产,以最小的总生产成本按时交付包括
- 安装成本:每次安装生产线的固定成本
- 生产成本:每生产单位的可变成本
- 持有成本:每单位时间的存储成本
在我们的示例中,客户订购了未来 12 个月的产品

客户订单—(图片由作者提供)
设置与库存成本
您面临的主要挑战是
- 减少现有的平均库存,以最小化存储成本
- 最小化生产设置的数量

生产调度挑战—(图片由作者提供)
然而,这两个约束是对立的。所以你很难找到一个直观的方法来建立一个最优的方案。
例 1: 最小化库存

最小化库存解决方案—(图片由作者提供)
在本例中,您每月生产准确的需求数量
- 优点:没有多余的库存
- 缺点:你每个月的生产都有积极的需求
示例 2: 尽量减少生产设置的数量

将安装成本降至最低—(图片由作者提供)
在本例中,您将构建库存以最大限度地减少设置数量
- 优点:整个时期只有两个生产设置
- 缺点:大量库存会增加库存成本
结论
你需要一个优化算法来平衡这两个约束。
解决办法
你可以在这里找到带哑数据的源代码: Github
假设
假设您收到了下一个 12 个月的采购订单,数量如上图所示。
- 安装成本:500 美元
- 持有成本:1 美元/单位/月
- 单位生产成本:50 美元/单位
- m 月生产的产品可以在同一个月发货
- 库存成本从第 m+1 个月开始计算
瓦格纳-怀廷算法
这个问题可以被看作是经济订货量模型的一个推广,该模型考虑到了对产品的需求随时间而变化。
Wagner 和 Whitin 开发了一种通过动态规划寻找最优解的算法。
这样做的目的是了解每个月将当月的需求数量添加到过去几个月的订单中是否比建立新的生产周期更经济。
正向计算
从周期 1 开始:
- 计算满足第 1 个月 D(1)需求的总成本
周期 N:
- 计算满足 t,D(t)月需求的总成本
- 查看所有过去的订单(t=1..n)并通过将数量添加到过去的订单 D(t-1)中来计算订购 D(t)的成本
- 取最经济的选项,去 t = N+1

瓦格纳-怀廷算法—(图片由作者提供)
逆向计算
从周期 t = N 开始,反向工作,找出满足每个 D(t)需求的最低选项。
结果和结论
欢迎随时关注我的 medium,获取更多与数据分析和供应链管理相关的文章
正向计算
您应该使用下表导出正向计算的结果:

向前计算—(图片由作者提供)
让我举几个例子:
期间 1,如果您为
- 仅第一个月需求(D(1) = 200 单位):500 美元
- 头两个月(D(1) + D(2) = 350 单位):650 美元
逆向计算
我们可以使用上面的表格,使用前面解释的规则来进行算法的可视化解析。

逆向计算—(图片由作者提供)
- 从 t = 12
开始最便宜的解决方案是为 D(11) + D(12)生成月 11 - 继续 t = 10
最便宜的解决方案是为 D(9) + D(10)产生月 9 - 继续 t = 8
最便宜的解决方案是为 D(6) + D(7) + D(8)产生第 6 个月 - 继续 t = 6
最便宜的解决方案是为 D(1) + D(2) + D(3) + D(4) + D(5) + D(6)产生第 1 个月
最终解决方案
- 第 1 个月:生产 550 台以满足前 5 个月的需求
- 第 6 个月:为第 6、7 和 8 个月生产 450 个单位
- 第 9 个月:为第 9 个月和第 10 个月生产 450 个单位
- 第 11 个月:第 11 个月和第 12 个月生产 550 件
库存优化
在下图中,您可以看到现有库存(IOH)可能远远高于需求预测。

现有库存—(图片由作者提供)
这表明,在设置和库存对总成本的影响之间没有平衡。这意味着,保持高库存比增加设置数量要便宜得多。
如果持有成本更高,你可能会看到更接近预测的 IOH 和可能更多的生产月。
库存和准备成本之间的巨大平衡
在下图中,您可以跟踪 12 个月内的累计持有和设置成本:

持有成本与设立成本
在这里,我们可以清楚地看到该算法如何在库存优化和减少设置数量之间取得平衡,因为保持和设置累积成本几乎相等。
这意味着,
- 如果你有非常高的单位持有成本,该算法将倾向于增加设置的数量,以尽量减少库存水平
- 如果每次设置的成本非常高,算法将更倾向于以最小的设置成本建立库存。
用 Python 实现
在 GitHub 存储库中,您可以使用 python 从头开始找到该方法的实现。
使用 pandas 函数来操作数据框,这种方法易于实现,适用于小型数据集。
正向计算
# Create columns
data_calc = data.copy()
for i in data_calc['period'].unique():
data_calc['Order {}'.format(i)] = 0
# costs
set_up = 500
holding = 1
# Order 1
order = 1
for index, row in data_calc.iterrows():
current_month = data_calc.loc[index,'period']
cost = 0
# 1 set up
cost += set_up
if current_month > 1:
for t in range(1, current_month+1):
cost += (t-1) * data_calc.loc[t-1,'forecast'] * holding
data_calc.loc[index,'Order {}'.format(order)] = cost
# Order 2
for order in range(2, 13):
for index, row in data_calc.iterrows():
current_month = data_calc.loc[index,'period']
if current_month >= order:
cost = 0
# Best option best Period 1
values = list(data_calc.loc[order-2,
['Order {}'.format(i) for i in range(1, order+1)]].values)
best = min([i for i in values if i >0])
# Add
cost += best + set_up
for t in range(order, current_month+1):
cost += (t-order) * data_calc.loc[t-1,'forecast'] * holding
data_calc.loc[index,'Order {}'.format(order)] = cost
data_calc = data_calc.set_index('period').drop(['forecast'], axis = 1).T
data_calc
由于第一个月没有以前的订单,我已将其与其他订单分开。
逆向计算
costs, initials, nexts, quantities = [], [], [], []
i = 12
while i>1:
# Order with the minimum cost
initial_step = i
next_step = data_calc[data_calc[i]>0][i].idxmin()
cost = data_calc[data_calc[i]>0][i].min()
# Next Step
next_id = int(next_step.replace('Order ',''))
i = next_id - 1
# Quantity
quantity = data[data['period'].isin(range(next_id, initial_step+1))]['forecast'].sum()
costs.append(cost)
initials.append(initial_step)
nexts.append(next_id)
quantities.append(quantity)
df_results = pd.DataFrame({'backward':range(1, len(initials)+1),
'initial':initials,
'nexts':nexts,
'cost':costs,
'quantity':quantities}).set_index('backward')
print("Total Cost: {:,}$".format(df_results.cost.sum()))
df_results
- 从上个月开始,向后移动
- 不要忘记获取用于输出分析的成本
后续步骤
将您的算法包含在数字双胞胎中
数字孪生是物理对象或系统的数字复制品。
供应链数字模型是一种计算机模型,代表供应链中涉及的各种组件和流程,如仓库、运输网络和生产设施。

使用 Python 的数字孪生示例—(图片由作者提供)
您的核心模型将包括一个子模型来模拟您工厂的行为
- 输入数据:生产计划,预计生产数量和交货期
- 输出数据:实际生产数量和交货日期
本文中开发的算法可以集成到数字双胞胎中,以连接您的商店和工厂
- 商店的输出将提供历史销售数据来预测未来需求:算法输入
- 工厂的输入将采用 Wagner-Within 算法生成的计划作为每个时间段的生产数量
然后,您可以轻松模拟几种计划算法对仓库库存水平、总生产和交付成本或生产线利用率的影响。
欲知详情,
https://www.samirsaci.com/what-is-a-supply-chain-digital-twin/
生产线调度
今天用的例子是从宏观的角度,我们把工厂看作一个黑箱,有生产能力,库存水平,固定或可变成本。
通过使用 python 的线性编程,生产调度可以帮助您提高生产能力。

具有三种不同工作的生产线示例—(图片由作者提供)
作业车间调度问题(JSSP) 是一个 NP-hard 问题,由一组作业定义,每个作业必须由一组机器按照特定的顺序执行。

我们的例子是使用 3 台机器的 3 个作业—(图片由作者提供)
由于您的解决方案可能需要非常高的生产峰值,因此可以使用线性规划来计划作业,以便在相同的设备和资源下每小时生产更多的单位。
欲知详情,
https://www.samirsaci.com/optimize-warehouse-value-added-services-with-python/
关于我
让我们在 Linkedin 和 Twitter 上连线,我是一名供应链工程师,正在使用数据分析来改善物流运营和降低成本。
如果你对数据分析和供应链感兴趣,可以看看我的网站
生产机器学习:确定 ML 技术债务
这篇文章的目标读者是技术和非技术利益相关者,目的是确定和解释 ML 技术债务。对 ML 技术债务的理解防止了各种利益相关者,例如项目经理、ML 工程师、数据科学家、客户和投资者被当前 ML 产品的兴奋所蒙蔽,从而在几个月后才发现 ML 产品预测是无用的。更糟糕的是,预期的最终 ML 产品似乎从来没有达到部署价值的点,并且由于财务和时间的限制,ML 技术债务不能被还清。

在 Unsplash 上由 olieman.eth 拍摄的照片
机器学习(ML)技术债务是在没有开发高效迭代所需的代码、基础设施、工具和过程的情况下部署 ML 系统,加上缺乏对实际 ML 产品需求的理解和预见而产生的债务。为已部署的 ML 系统提供持续和实际的价值受到累积的 ML 技术债务的阻碍,因为已部署的 ML 系统不能对变化作出反应,并且不能一致地实现必要的性能。
在这篇文章中,我总结了两个主要问题来询问你的内部团队、外包团队或投资公司,以确定潜在的 ML 技术债务。
问题 1:团队能否快速迭代?
生产中成功的 ML 系统的主要要求之一是能够迭代修正、改进或保持 ML 模型性能到对利益相关者持续有价值的水平。
为了理解由迭代失败或耗时的迭代过程导致的 ML 系统的潜在技术债务,我们首先必须理解 ML 系统的典型生命周期。

简化的 ML 生命周期
展示简化的 ML 生命周期图的目的是强调一点,ML 系统需要不断的迭代,不同的模块可以有效地相互作用。只关注建模而不开发流水线对于生产 ML 是不够的。不同的运营规模需要不同的 ML 管道成熟度,因此找到对您的运营有意义的东西非常重要。
通常在概念验证(PoC)部署中,团队不会考虑 ML 系统需要任何未来更新或持续学习的事实。PoC 部署可以在静态数据集上完成,无需进行任何开发来处理在研究中非常常见的对新数据的迭代。PoC 部署可能非常具有欺骗性,因为它会欺骗利益相关者,让他们认为产品已经接近完成,但实际上还很远。
PoC 部署的定义还包括 ML 系统,这些系统只能工作很短一段时间,因为随着时间的推移,性能会下降到无用,而团队由于缺乏有效的管道而无法在降级期内解决问题。因此,这包括一些 ML 系统,它们实际上部署在生产中并为客户服务,但实时性能下降很快,最终会失去它们的价值,这可能会导致巨大的意外损失。
不要被 PoC 部署系统所花费的时间和金钱所迷惑,根据 PoC 部署推断实际生产部署是危险的。我们需要了解团队在 PoC 部署过程中产生的技术债务。ML 系统在实际生产中持续成功的原因是什么?
问题 1.1:常见的生产 ML 问题
- 数据和概念漂移。
数据漂移是我们训练的输入分布发生变化的地方。概念漂移是指输入和输出之间的关系发生变化。漂移会导致我们最初的成功部署性能随着时间的推移而下降。
- 附加功能要求
涉众可能会在项目中途甚至在部署之后请求额外的特性。该团队还可能意识到,需要额外的功能来解决一些部署问题。
- 数据处理和建模的变化
ML 团队可能会进行一些实验,并意识到改变一些数据处理脚本或模型架构是有益的。
- 未预见的问题
示例:需要收集一类新的数据来正确处理问题,以提供实际价值。
- 部署性能差
在实际部署过程中,团队意识到,尽管在研发阶段表现出色,但性能并不令人满意。
应该提前考虑解决这些生产问题所需的工作量和成本!
问题 1.2:提前解决生产问题

Tine ivani在 Unsplash 上拍照
为了解决这些问题,构建一个完整的生产系统管道,在发现问题的同时对新的需求和变化做出反应,将会带来难以置信的好处。提到的每个生产问题都要求团队能够在当前进度的基础上进行迭代和改进。
从 PoC 部署到实际的 ML 生产系统需要团队确保开发的解决方案是可重复的、可扩展的和安全的;这就是迫切需要工程工作的地方。许多 ML 团队仍然试图找出什么是他们部署的 ML 系统的最佳 MLOps 实践和工具。
机器学习应用很少是静态的,因为它与之交互的外部世界是不断变化的。对于根本不考虑迭代管道的团队来说,生产就绪的 ML 系统将是一个遥不可及的目标。
ML 团队必须采取措施,通过考虑代码库模块化、干净的代码实践和易于配置来提前简化协作。通过建立适当的协作系统,团队可以更高效地工作,开发时间将大大减少。
从个人经验来看,有许多经常被忽视的生产 ML 问题会招致额外的技术债务,例如:
- 缺乏监督系统。部署中的问题发现得太晚或者根本没有发现。
- 缺乏数据运营实践的不良数据管理渠道会导致数据质量差且不一致
- 单独跟踪模型的代码。以数据为中心的方法很难进行实验和协作。这也导致再现性差。
- 在较小的精度优势上增加系统的复杂性,例如,改变到具有难以优化的复杂层的新的最先进的模型。
最后,在我看来,最容易被忽视的最有价值的 ML 问题是创建有价值的数据集。大多数 ML 团队在数据收集和采样技术方面很差;这些数据技术之所以缺失,是因为 ML 工程工作通常更重视建模而不是数据。
例如,在数据注释中,ML 团队可能最终只是随机抽取数据进行注释,而不是将精力集中在智能抽样技术上。随机抽样最终会推高成本(注释可能非常昂贵!)而不一定提高模型性能。ML 团队也可能忽略了制定全面的注释指南和与贴标机建立适当合作所需的努力。
ML 团队还必须将数据收集方法作为 ML 管道中的重中之重,因为收集的数据会直接影响模型性能和价值贡献。ML 团队必须确保收集的数据分布与生产中的传入数据分布相关,以便 ML 模型可以在部署期间潜在地提供实际价值。ML 团队不应该仅仅扮演数据消费者的角色,还应该参与到数据收集过程中,并努力引入新的想法来改进该过程。
总结问题 1:
理解技术债务可以在 ML 系统的任何部分积累。明智的做法是提前考虑问题在未来可能如何变化的潜在场景,并据此确定生产中的 ML 系统根据新的需求变化和改进的速度。这将提供一个更全面的画面,展示构建一个持续成功的产品所需的努力、时间和成本,而不是被早期的原型成功所愚弄。
问题 2:模型必须表现得多好才有价值?
我们需要定义哪些指标对我们来说是重要的,以及定义的所有指标的性能如何,该模型才会被认为对我们的利益相关者有价值。

价值的确定应该在风险承担者之间进行讨论,以便恰当地确定项目需求的范围,从而满足期望。不同的问题需要不同的性能需求才有价值(记住,在等式中也要包括业务指标!).性能要求越高,我们可能被蒙蔽的技术债务就越多。
高失败成本和低技术成熟度的问题也会增加潜在的技术债务。产生的技术债务金额由以下因素驱动:
- 基准人员绩效/被认为有价值的绩效
- 失败的代价
- 技术成熟度
两个不同部门的简化示例:
- 某自动驾驶公司要求交通标志分类模型达到 99.9999%的准确率才有价值。如果精度低于,则不符合安全要求,不能用于实际部署。失败的代价可能是生命的损失。为了训练一个分类模型达到 99%的性能,ML 工程师可以在几个月内完成,但是技术债务实际上是巨大的;从 99%到 99.9999%的准确率需要巨大的努力。
- 一家制造公司要求产品缺陷分类模型达到 99%的准确率才有价值。如果准确度低于,最好雇佣多个人工来检测不良品。失败的代价将是向客户交付有缺陷的产品。为了训练一个分类模型达到 99%的性能,ML 工程师能够在几个月内完成。
对于这两种分类模型用例(自动驾驶与制造),技术的成熟度是相同的(基于 ML 的视觉分类模型),但故障成本和性能要求有很大不同。从 PoC 到实际的有价值的部署,由于过高的期望和无法实现从最初的 PoC 成功到实际生产成功的必要努力,自动驾驶问题很容易使利益相关者措手不及。
ML 团队也可能严重低估了从 99%到 99.9999%准确性(PoC 到可部署度量需求)的难度。这是一个过于简单的例子,实际的挑战要复杂得多,无法用单一的模型指标来定义。然而,这种比较的目的不是为了了解自动驾驶的实际基线要求和技术堆栈,而是为了指出提前确定适当的项目范围的重要性。如果你真的对自动驾驶需求感兴趣,这篇论文“关于安全和可扩展自动驾驶汽车的正式模型”值得一读。引用报纸上的话:
“首先考虑已知的每小时(人)驾驶事故造成死亡的概率为 10^−6.有理由假设,为了让社会接受机器代替人类驾驶,死亡率应该降低三个数量级,即每小时 10^−9 的概率”
对这两个问题进行比较是为了强调需要明确定义性能被认为有价值的要求,并理解问题的范围。
对于这两个例子,通过从完全自动驾驶到驾驶员在回路中的自动驾驶(L5 到 L3 ),或者从完全缺陷检测到工人在回路中处理不确定预测的缺陷检测,保持人在回路中,可以使问题变得更容易。
对于我们的缺陷检测问题,工程工作也可以用于修复制造工厂的环境,使其保持恒定,这本身就解决了大量的机器学习挑战。通过努力约束环境,数据复杂性降低,并且通常需要较少的技术返工。对于自动驾驶的问题来说也是如此,截至今天,许多自动驾驶汽车公司都受到某些地区和天气的限制。然而,关键是要了解什么对我们的最终用户有价值,什么对公司有利。项目范围界定阶段至关重要!
利益相关者需要意识到技术的成熟度和被认为有价值的所需性能。如果没有对此进行规划和研究,您可能最终会构建或投资一个没有生产价值的概念验证 ML 系统,并招致大量永远无法还清的技术债务。
总结问题 2:
通过适当地界定和理解需求,ML 产品的潜在技术债务可以在前期得到更准确的评估。请注意从概念验证指标性能到必要的可部署指标性能所需的工作。确保确定可部署的度量需求,并与所有利益相关者讨论它们,尤其是开发团队(例如 ML 工程师、数据工程师和数据科学家)。与利益相关者保持明确的期望是很重要的。
结论

伊恩·斯陶费尔在 Unsplash 上拍摄的照片
对 ML 技术债务的无知会蒙蔽利益相关者,使他们忽略了构建具有实际和持续价值的 ML 产品所需的真实成本和时间。因此,对于包括 ML 工程师或数据科学家在内的利益相关者来说,重要的是理解构建有价值的生产就绪 ML 产品所需的真正努力,而不仅仅是关注他们的利基领域。
ML 管道/系统的标准仍在不断发展,许多新公司正在开发软件工具来支持健壮的 ML 管道,可以有效地迭代。没有引入特定的解决方案来预先“偿还”ML 技术债务或保持其较低水平;这是故意保持开放的,因为本文的目的只是确定 ML 技术债务可能积累的潜在领域,并暗算利益相关者。在未来,我的计划将是为这里提出的两个问题编写技术指南。
在 LinkedIn 上与我联系,愉快地聊天并就未来的项目进行合作!我希望与在 ML、MLOps、自动驾驶汽车和创业方面有相同兴趣的人联系。
生产 MLOps 入门
原文:https://towardsdatascience.com/production-ml-getting-started-with-mlops-4856c38b3eab
MLOps 如何帮助您生产您的 ML 项目?为什么您需要尽快开始采用 MLOps?

马库斯·温克勒在 Unsplash 上的照片
长期以来,持续集成和持续交付(CI/CD)问题在“经典”IT 项目中是众所周知的。这就是 DevOps 这个术语诞生的原因,它指定了促进软件开发所有阶段的自动化和监控的编程原则。
在我们的机器学习系统中,越来越多地开始工业化并集成到现有的 IT 系统中,挑战是将这些原则应用到数据科学的世界中。
构建模型很难,而将它们投入生产则更难。复制您的管道和结果,或者允许其他数据科学家这样做,也同样复杂。有多少次你不得不放弃以前的工作,因为它没有被适当地记录或太难复制?
在本文中,我们将看到 MLOps 如何解决这个问题,以及如何将这种方法集成到完整的机器学习生命周期中。
什么是 MLOps?

图片由作者提供—使用 open peeps 库。
MLOps 往往被定义为机器学习的 DevOps 的对应物。因此,我们可以推断 MLOps 允许机器学习项目的步骤的自动化和监控。
但在进一步深入这个主题之前,让我们简单回忆一下机器学习项目的不同阶段:
- 业务需求和要求的定义,
- 数据收集和分析,
- 数据验证,
- 数据预处理,
- 模型探索、培训、选择和验证,
- 在生产中部署模型。
那么,MLOps 和这一切有什么关系呢?

图片由作者提供——使用 open peeps 库。
嗯,理想情况下,到处都是!但是我把它分成了三个主要部分,我认为这三个部分很重要,我将在下面详述:
- 实验管理,
- 管道自动化,
- 和模型监控(尤其是功能性的)。
并非所有的原则对你的项目都是必要的。相反,它们是工具箱,您可以根据自己的需要决定是否应用。
实验跟踪
在某些情况下,很难跟踪一段时间内完成的所有实验(因为它们非常多,或者因为它们分散在几个月中,或者因为团队非常大,等等)。).此外,代码并不总是保留(当我们决定尝试另一种模型时,这有什么意义呢?).但是,跟踪这些运行对于审计来说非常有用,有助于调试或证明所做的选择。
这就是实验跟踪的用武之地:
实验跟踪侧重于迭代模型开发阶段,此时数据科学家尝试不同的配置以实现期望的性能。
例如,如何避免截图来记录和分享项目进展,并快速重现以前的实验?
实验跟踪可以总结为几个简单的原则:
- 在一个地方集中和组织所有的实验,
- 对比实验和分析结果来调试模型,
- 改善协作:查看每个人都在做什么,轻松共享实验结果,访问实验数据(输入、指标等。),
- 分析和管理实验。
从技术上来说,我们该怎么做?
每个团队可以使用其工具来管理和遵循其项目的 ML 实验的不同步骤。然而,出于效率的原因,建议考虑关于该主题的专门工具。许多开源或软件工具可以满足这种需求,并且可以轻松、安全地集成到所需的工作流程中。
根据需要,实验跟踪工具可以具有不同的功能。从本质上讲,这是一个从纯技术角度出发的专用工具可以为我们提供的服务的非详尽列表。
创建模型:在研究和数据探索阶段,必须能够:
- 记录、记录、显示模型元数据(数据、指标、标签等。);
- 比较几个执行:比较表,图表等。
- 创建或自动生成仪表板/摘要报告;
- 跟踪硬件消耗,包括 CPU、GPU、内存等。;
- 能够在同一个项目上合作,并分享这些新的集成;
部署到生产:一旦数据科学家选择了一个模型,ML 工程师就必须将它投入生产。为此,在一个好工具的帮助下,实验跟踪允许:
- 轻松快速地重现和重新执行实验;
- 跟踪实验及其元数据;
- 将模型集成到自动化和 CI/CD 链中
管道自动化
管道自动化的挑战是满足自动化我们的模型部署中可以自动化的东西的需求。毕竟,我们需要一个人每个月去运行 Jupyter Notebook 中的一个代码来重新培训生产中的模型吗?这个数据科学家在创建新模型时不是更有用吗?
因此,让我们来看看应该采取什么措施:
- 代码的版本管理(首先是模型,还有数据和预测) :对于这一步,建议在代码管理工具(例如 Git)中注册代码,并设置管理模型注册、ML 元数据存储和特性存储的工具。
- 自动重新训练:一个模型在生产中的性能会随着时间的推移而下降,所以我们必须能够用更多的数据或更新的数据来重新训练模型,以跟上最新的趋势。注意,可以根据时间表或根据触发器(例如,性能下降到阈值以下)来触发再训练
- 自动测试:测试必须像传统的开发周期一样关注代码,但也要关注数据。
- 生产监控能够适应模型在生产中的行为。
如果我们总结一下,就会得到下面的图表:

演职员表:https://ml-ops.org/content/mlops-principles(抄送-SA)
功能监控
机器学习模型是动态的,并且根据输入数据不断进化。因此,可以肯定地说,当数据科学家进行最后的修改时,模型在部署之前处于最佳状态。但是,如何确保这种模式随着时间的推移保持有效呢?
将监控、日志记录和警报工具与数据处理流程相结合可以防止风险。
但是什么会出错呢?
来源已更改,新数据,处理错误,数据规范化。
以下是我们可能遇到的不同步骤:
如果数据与您的模型预期不符,很可能会影响性能。
我们可以控制每个输入变量的类型、格式和缺失数据的数量。此外,还有各种各样的统计测试和工具来测量数据中的偏差:
对于连续变量:散度和距离检验如 Kullback-Leibler 散度、Kolmogorov-Smirnov 统计量(广泛使用)、总体稳定性指数(PSI)、Hellinger 距离等。
分类变量:卡方检验、熵、基数或模态频率。
输入变量和目标之间的关系也会随着时间而改变。
模型漂移可以用与上述相同的统计测试来检查。此外,度量和检查应该放在现实与早期的预测进行比较。最后,使用生产数据(当它变得可用时),我们可以计算我们的模型的真实准确性。
可以解决的最后一点是业务 KPI 的创建,因为我们不能忘记我们的模型应该对业务问题做出响应。因此,遵循这些指标也有助于理解我们模型的性能。
现在已经发现了差距,我们应该做什么?
- 记录问题并确定它是季节性问题还是一次性异常。
- 重新训练模型,并将此模型应用于新数据。
- 记录
- 提醒:不需要经常看仪表盘;当有必要采取行动时,我们会得到提醒。
结论
MLOps 是机器学习模型管理的可操作化。它旨在创建一个端到端的过程,用于创建、实现和管理可重复、可测试和可扩展的机器学习模型。
重要的是要明白实践 MLOps 的方法,顾名思义,并不是固定不变的。过程、架构和工具必须适应您环境的现实。
然而,在大多数情况下,有一些工作流模型可以用作指南。这些工作流程中的每个主要步骤都可以使用特定的工具来执行。有许多工具,无论是专有的还是开源的,都有助于 MLOps 的实践。
如果要记住一件事,那就是:MLOps 是一套实践,它应该尽可能敏捷,并且应该基于 ML 应用程序的交付和持续集成过程的自动化。
关于作者:
Hajar 是人工智能倡导者、演讲者、顾问和认证机器学习工程师,他重视技术、创新和卓越。
她帮助任务驱动的组织利用数据科学和机器学习的力量来最大限度地发挥它们的影响。她以倾听客户需求并精心设计架构完善、创新且可扩展的 ML 解决方案来帮助客户实现目标而自豪。
数据科学家的生产力提示
原文:https://towardsdatascience.com/productivity-tips-for-data-scientists-eb66242fde27

安德里亚斯·克拉森在 Unsplash 上拍摄的照片
数据科学家的生产力提示
作为一名数据科学家,如何更好、更聪明但不一定更努力地工作
我一直坚信“更聪明地工作,而不是更努力地工作”,过去几年在快节奏的公司工作强化了这一信念。根据我作为一名数据科学家的观察,在“缓慢”的分析(或工程)工作和企业需要解决方案的速度之间,似乎有一个永久的差异。
尽管这种紧张主要是由于高度技术性的工作具有难度的水平,并且需要花费时间的精度的水平,但它可以通过适当的利益相关者管理(我在其他几篇文章中已经讨论过)以及通过使自己成为更有效和高效的数据科学家来部分缓解。我在过去几年中学到了一些经验,希望能帮助你让数据科学家的工作变得“更聪明”。
帮助别人帮助你
如果你曾经收到类似“有多少 XX(交易,旅行,等等。)我们上个月有吗?”(我称之为“数据 Siri 问题”),你知道你在那些看似很小的问题上花了多少时间,它们有多令人沮丧。
不让这些小问题拖累你的生产力(和情绪)的最好方法是提升你的利益相关者的技能,这样他们可以在一定程度上自给自足。
我合作过的大多数利益相关者都非常乐意在每周或每月的办公时间或数据团队主持的培训会议中学习有关分析的基础知识,因为没有人喜欢一直依赖他人来完成工作。
成为“令人讨厌”的数据科学家,并询问“数据去了哪里”
一次又一次,当人们问“我们可以跟踪 XXX(与最近推出的产品或流程相关的)吗?”时,我观察到数据团队争先恐后地获取数据数据团队意识到数据没有被正确跟踪或根本没有跟踪。
我避免这种情况的方法是尽早“打断”谈话。当你参加一个会议,会上人们谈论构建一个新产品和/或流程时,如果你知道你最终将负责分析数据(这可能是你作为数据科学家参加会议的原因),尽可能早地问一些“愚蠢的”问题“数据将存储在哪里?”,“我们决定了表的模式了吗?”…
我过去常常认为,一定有人已经想到了这些问题。不要做那些假设。你会惊讶地发现,人们经常忘记事情的数据方面,即使是在以数据驱动为荣的公司里。
相信我,人们会感谢你尽早问那些“愚蠢”的问题,因为这将避免未来的瓶颈。
注意“不相干的谈话”
在我看来,能够吸收似乎不直接属于你的直接范围的事情的信息是一种被低估的能力。我一次又一次地意识到,我在 Slack threads 和群聊中读到的、当时似乎与我无关的对话,包含了对我未来工作有用的信息。
仔细想想真的不奇怪。公司的每一项工作都是相互交织、相互关联的,因为归根结底,每个人都在朝着同一个目标努力。与你无关的事情变得相关只是时间问题;当他们这样做时,你就有了一些基本的背景,知道已经做了什么,或者至少知道从哪里开始寻找信息。
所以,对别人正在做的事情保持好奇和好奇,不要被你的视野所局限。未来的你会感谢过去的你,因为你抓住了某些“不相关”的信息,并因此节省了大量的时间。
做海绵和圆点的连接体
这一条与上一条直接联系在一起,因为只有当你关注你周围发生的事情时,你才能将这些点联系起来。
能够吸收信息和将点点滴滴联系起来是我在我雇佣的人身上寻找的最有价值的能力之一。我甚至无法形容我有多感激团队成员告诉我“XX 提到他/她做了 YY,我认为与这个新的 ask 非常相似”。
就像我在之前的帖子中提到的,当谈到高影响力项目时,很可能有人在过去考虑并做过类似的事情;因此,能够不重新发明轮子,但建立在以前的工作可以节省大量的时间,并创造大量的协同作用。如果另一个团队目前正在积极地进行一个类似的项目,你可以联合起来,或者简单地调整优先级,在他们为你解决问题的同时做其他事情(假设时间表和输出符合你的需求)。
批量处理你的工作,利用生产力黑客
除非你有一个极度微观管理的经理,否则你通常可以控制如何安排工作的优先次序。这些年来,我学到的提高效率的最好方法是在合适的时间瞄准容易摘到的果实,并通过批量处理类似的工作来降低转换成本。
即使你成功地提高了所有利益相关者的技能,你也不可避免地会遇到一些“数据 Siri”问题,这些问题不应该占用你太多的大脑空间,所以当你觉得大脑像被烧焦了一样,无法真正集中注意力时,或者当你在参加一个无聊的会议时,你可以用这些问题来打发时间。
分批处理工作是提高效率的另一种方式,因为在不同的主题之间来回切换会分散注意力,导致效率低下和进度缓慢。所以我通常把相似的任务放在一起,一次完成尽可能多的任务。将所有的会议安排在背靠背的位置,并在会议结束后留出几个小时的时间,让大家戴上耳机,埋头苦干,高效地编写代码。你的大脑会感谢你没有在短时间内把它拉向各个方向。
不后退一步就不要一头扎进一个问题——质疑一切
不要让别人解决问题的方法成为你思考过程的“盒子”。跳出框框思考。我的意思是,如果你的利益相关者有一个分析请求,他们对你如何实现它有一些想法,那很好,听听他们的意见;但是不要让它成为你必须如何对待它的圣经。
作为一名数据科学家,你应该具备数据方面的专业知识,能够帮助人们从数据的角度决定处理问题的最有效的方式。不要害怕挑战方法和提出建议。
重点提示:
作为一名数据科学家,有几种方法可以让你变得更加高效:
- ****提升技能您的利益相关者减轻您的临时请求负担
- 在与数据相关的对话中更加积极主动
- 采取更多主动in/多注意那些似乎不在你范围内的对话****
- 学会把点连接起来****
- ****通过分批你的工作和分解大任务来区分的优先次序
- 不要想当然,总是寻找更有效的方法
喜欢这篇文章吗?订阅我的邮件列表,这样你以后就不会错过一篇文章了!不知道接下来要读什么?以下是一些建议:
** **
用 Python 过滤音频文件中的脏话
原文:https://towardsdatascience.com/profanity-filtering-audio-files-python-3dd2335bc567
用 Python 和 AssemblyAI 过滤音视频文件中的脏话

沃洛季米尔·赫里先科在 Unsplash 上的照片
介绍
在我最近的一篇文章中,我们探讨了针对音频和视频文件的***敏感内容检测和调节。另一个密切相关的主题是亵渎检测和过滤。换句话说,我们如何用星号替换亵渎的话?*****
在今天的教程中,我们将探索如何使用 Python 和 AssemblyAI API 执行语音到文本转换。此外,我们将确保文本转录是免费的亵渎。
使用 Python 过滤脏话的语音转文本功能
现在,让我们从如何在 Python 中实现脏话过滤的分步指南开始。对于本教程,我们将使用 AssemblyAI API,因此如果您想继续学习,请确保从他们的网站上获得一个访问令牌(绝对免费)。
下一步是准备将发送到 AssemblyAI 端点的 POST 请求的头。这应该如下所示(确保使用您自己的访问令牌):
导入请求库和定义请求头—来源:作者
现在我们需要读取将要执行语音到文本转换的音频文件,然后将它上传到 AssemblyAI 提供的托管服务。端点将在托管服务上返回一个 URL,然后用于后续请求,以便获得输入文件的副本。
读取输入的音频文件并上传到 AssemblyAI 主机服务上—来源:作者
现在,我们打算转录的音频文件已经上传到托管服务上。在下一步中,我们将使用作为响应的一部分返回的upload_url,以便获得文本转录。
默认情况下,API 将返回音频的逐字记录,这意味着如果在音频中说脏话,将会出现在记录中。
为了对输入执行脏话过滤,我们只需要在POST请求的主体中提供一个附加参数,这个参数叫做filter_profanity,应该设置为true。
向转录端点发送 POST 请求—来源:作者
现在转录过程应该启动并运行了,我们需要发出一个GET请求来访问结果。注意,我们需要发出几个请求,直到端点返回的状态是error或completed。
这是因为 API 提供了 异步转录 。提交音频文件进行转录时,通常会在音频文件持续时间的 15–30%内完成。请注意,AssemblyAI 还支持实时转录——要了解更多信息,请务必阅读我的另一篇关于用 Python 进行实时语音识别的文章。
从 AssemblyAI 端点检索转录结果—来源:作者
最后,我们可以将转录结果写入输出文件,这将使我们在检查输出时更加轻松。
将文本转录写入文本文件—来源:作者
解读回应
现在,如果我们检查写入输出文件的结果,我们应该看到文本转录。如果检测到亵渎,则会用星号替换,如下面的输出示例所示:
**It was some tough s*** that they had to go through. But they did it. I mean, it blows my f****** mind every time I hear the story.**
下面给出了返回响应的更完整形式
来自文本转录端点的示例响应—来源:作者
实际的文本转录在响应的text字段中提供。
words字段将包含在音频(或视频)文件中检测到的每个单词,以及开始和结束时间戳,以及confidence。后者对应于模型对检测到的单词的置信度(在0-1之间测量)。显然,confidence越高,该模型对口语单词越确定。
请注意,除了每个检测到的单词的confidence之外,响应还包括整个转录的confidence(外部字段)。
最后,响应包括在文本转录期间使用的配置和参数。例如,您可以验证是否启用了profanity_filtering选项,因为它在转录端点给出的响应中是true。
关于响应中包含的各个字段的更完整描述,请参考官方文件中的相关章节。
完整代码
作为本教程一部分的完整代码可以在下面的 GitHub Gist 中找到。
用 Python 和 AssemblyAI 过滤脏话的完整代码—来源:作者
最后的想法
在今天的文章中,我们展示了如何使用 Python 和 AssemblyAI API 对音频和视频文件进行脏话过滤。当需要过滤脏话时,您可能通常希望(或需要)执行敏感内容检测。关于如何执行内容审核的分步指南,请阅读下面的文章
**** ****
成为会员 阅读介质上的每一个故事。你的会员费直接支持我和你看的其他作家。你也可以在媒体上看到所有的故事。
****https://gmyrianthous.medium.com/membership ****
你可能也会喜欢
**** **** **** ****
Python 中的专业树形图:饼图的更好替代方案
数据可视化
理解 rcParams & Matplotlib 色彩映射表
作为一个狂热的 python 爱好者,我最近想用 python 生成一个可供发布的 treemap。树映射通常用于使用嵌套矩形显示分层的分类数据。树形图的每一片叶子都是一个时尚的矩形饼状图,它将分类数据可视化。然而,关于如何生成树图,尤其是如何生成好看的树图的可用文档很少…
本文的目的是分享使用 Squarify 包定制树形图的技巧和诀窍。这篇文章关注的是叶子级的树图,而不是嵌套……(如果对一个关于嵌套的帖子感兴趣,在下面评论!).然而,类似于维恩图教程,这些技巧中的许多适用于任何基于 Matplotlib 构建的包(例如 Seaborn、plotnine 等。).希望本教程能帮助您有效地定制树形图,以生成可用于发布、仪表板或报告的图形。完整的笔记本托管在 GitHub 上。
以下数据集是一个玩具数据集,用于演示如何在树形图中快速利用熊猫数据框中的数据。需要注意的是,您不需要使用数据帧。事实上,使用列表会更快。然而,我选择了广泛使用的结构,以便在更大规模的应用中高效使用。使用下面的代码,我生成了一个简单的案例:三组项目 A、B、c。
在这种情况下,组成员(如人员 1、人员 2、…)不是唯一的。例如,person1 是所有三个组[A、B 和 C]的成员,并且被多次计数。对于数据可视化练习来说,这是不相关的,但是在应用到您的数据时,请记住这一点。
由我使用数据包装器创建
以下代码将数据从上述格式转换为压缩的分组格式。
形象化
现在,我们开始将可视化应用于一个非常简单的 3 矩形案例。
使用 prep_df 表,我们使用 Squarify 对一个正方形 3x3 图形(dx x dy 图形)的计数数据进行归一化。然后,我们绘制标准化的值和标签,使用完全饱和的颜色(alpha=1),并在矩形之间应用填充(padding=True)。
我们最终得到了一些位置很好的方块,但是没有一个头脑正常的人会把这个图给他们的老板看。

默认值,由作者生成
接下来,我们移除轴。做完那件事后,我身体上感觉平静多了。我们可以通过修改底层 Matplotlib 呈现中的 rcParams 字典来定制标签的文本颜色和文本大小。在这里,我调用 matplotlib 作为 mpl,并调整 font.size 和 font.color 键的值。我还手动指定了我希望 Squarify 用来绘图的颜色列表。这个情节看起来干净多了!

手动颜色和字体,由作者生成
现在,我们可能赶时间。如果我们不想手动选择颜色,我们可以从 Matplotlib 颜色图中随机选择一组颜色。然而,这些数字有些看起来很好,有些看起来真的很差。幸运的是,你可以尝试不同的随机种子,直到你得到你喜欢的颜色。这个还可以。

随机颜色,由作者生成
Matplotlib 彩色地图究竟是如何工作的?
要了解底层色彩映射表是如何使用的,请参见下文。对于每个方块,我们可以使用 0 到 1 之间的任意数字来代替随机值。唯一的问题是列表中的数字应该是不同的。
使用 len(counts)=3 的组数,我们可以继续创建一个介于 0 和 1 之间的值的顺序列表,使用 1/3 的间距来保持每个值的不同。在 Out[9]的下面,显示出你得到一个 3 元素列表:0,0.33,0.66。
接下来,我们可以使用色图调色板将 I 的每个值映射到 RGBA 颜色值(红色、绿色、蓝色、不透明度)。Matplotlib 有许多色彩映射表可供选择。我使用了“等离子体”色图,这是 cmap 功能的一个参数。cmap 函数的作用是获取[0,1]之间的输入,并将其转换为 RGBA。红色、绿色、蓝色值的范围是[0,1],而不是您可能熟悉的[0,255]。这个结果产生了嵌套列表,我们称之为 mapped_list,它仍然包含 3 个顶级元素。每个元素都是一个 RGBA 元组。
最后,我们使用 mapped_list 作为 Squarify 函数中的颜色参数进行绘图。这表明我们可以使用色彩映射表,并且仍然可以对它们进行一定程度的控制。使用色彩映射表不需要猜测和检查的努力!


等离子色彩图,由作者生成
现在,对于任何适当的报告,我们需要添加一个标题。但是如果我们坚持使用默认选项,我们将会以这个不令人满意的占位符结束,因为图形的文本字体是白色的。如果我们改变字体颜色,我们最终会得到难看的标签文本。第二十二条军规!但是不要害怕,凯特在这里!

标题占位符,由作者生成
在我们在子图形中使用标题技术之前,我发现给一个单独的图形添加标题的最简单的方法是添加一个额外的文本框。我们可以定制对齐方式以使图形居中,并定制绘图顺序(zorder ),以确保它绘制在下方白色标题文本的顶部。
注意坐标。我们在位置(50,100)绘制标题框。坐标(0,0)映射到图的左下区域。(100,0)映射到图的右下区域。

标题作为自定义文本添加,由作者生成
在我们继续进行子绘制之前,我想提醒大家注意一个事实,即有更多的定制选项可用。rcParams 在教程中不常用到,但是很容易访问!要找出可以定制的内容,只需调用 rcParams 字典的键:
mpl.rcParams.keys()

rcParams 中一些可用的自定义选项,由作者打印
用支线剧情升级
最后,使用 Squarify 最有用的方法是制作复杂的树形图。为了增加复杂性,通常需要将多个底层地图拼凑在一起。可以使用前面讨论的所有技术定制每个底层图形。我们将使用随机生成的 8 个矩形序列绘制 6 个单独的图。有多种方法可以自定义每个支线剧情。对于代码,点击这里只是这个数字,或这里为整个笔记本。
图像支线剧情
- 默认设置。
- 手动指定颜色。这导致整个图形重复两种颜色。这对于艺术目的来说是有趣的,但是对于解释的目的来说可能是误导的。
- 使用之前相同的技术指定颜色。
- 按顺序添加标签。
- 添加非标准化值。
- 添加标签和值并更改字体大小。

多个支线剧情使用 Squarify,由我生成
最后,你可以删除支线剧情和标题之间的空白来制作一个复合树形图!

浓缩支线剧情,由我生成
作为我的最后一个技巧,女士们先生们,我使用了 Seaborn color_palette 而不是 Matplotlib colormap 来自动生成配色方案。Seaborn 通常包含比 Matplotlib 更专业的配色方案集合,我们可以使用 Squarify 轻松地使用它们!此外,我们可以通过调用颜色映射列表中的 np.flip()函数来更改颜色顺序的顺序(见第 18 行)。

作者用 Seaborn 调色板生成的摘要图
本教程是一个练习(1)学习一个新的可视化工具和(2)使 matplotlib 不那么笨拙。如果您希望看到特定的定制,请在下面发表评论!Medium 的社区教程非常精彩,我的教程受到了像这个这样的好例子的启发。
是时候绘制一张树状图了!
参考
- 创作者尤里·拉森写了这个 Squarify 包。
喜欢这篇文章吗?下面更喜欢!
https://www.linkedin.com/in/kate-wall/
剖析和分析 Python 程序的性能
原文:https://towardsdatascience.com/profiling-and-analyzing-performance-of-python-programs-3bf3b41acd16
快速找到 Python 程序中的所有瓶颈并修复它们的工具和技术

由 Unsplash 上的 Alexas_Fotos 拍摄的照片
分析对于任何代码和性能优化都是不可或缺的。如果不知道在哪里应用,您可能已经拥有的任何性能优化经验和技能都不会非常有用。因此,找到应用程序中的瓶颈可以帮助您快速解决性能问题,而不需要花费太多精力。
在本文中,我们将探讨一些工具和技术,它们可以帮助我们缩小关注范围,找到 CPU 和内存消耗的瓶颈,以及如何在即使目标明确的代码更改也无济于事的情况下,实现简单(几乎不费力气)的性能问题解决方案。
识别瓶颈
性能优化的时候偷懒是好事。我们不需要试图找出代码库的哪一部分使应用程序变慢,我们可以使用分析工具来找到需要注意或进一步挖掘的地方。
Python 开发人员最常用的工具是cProfile。它是一个内置模块,可以测量代码中每个函数的执行时间。
让我们考虑下面这个(缓慢地)计算e的X次方的函数:
现在,让我们针对上面缓慢的代码运行cProfile:
在这里,我们使用-s cumulative按照每个函数的累积时间对输出进行排序,这样更容易找到输出中有问题的代码区域。我们可以看到,在单次调用期间,几乎所有的时间(~2.764 秒)都花在了exp函数上。
这种剖析可能是有用的,但不幸的是可能并不总是足够的。cProfile只给出关于函数调用的信息,而不是关于单个代码行的信息——如果你在不同的地方调用某个特定的函数,比如append,那么它将在cProfile输出中全部聚集成一行。我们上面使用的脚本也是一样——它包含一个只被调用一次的函数,所以没有什么需要cProfile报告的。
有时,我们没有能力在本地分析这些麻烦的代码,或者当生产环境中出现性能问题时,我们可能需要即时分析。在这些情况下,我们可以利用py-spy,它是一个分析器,可以检查已经运行的程序,例如生产环境或任何远程系统中的应用程序:
在上面的代码片段中,我们首先安装py-spy,然后在后台运行我们长期运行的 Python 程序。这将自动显示 PID 号,但是如果我们不知道,那么我们可以使用ps命令来查找。最后,我们在 PID 中传递的top模式下运行py-spy。这将产生终端视图,模拟 Linux top实用程序的自动更新输出,类似于下面的截图。

py-间谍上衣
这实际上并没有给我们提供太多的信息,因为这个脚本只有一个长期运行的函数,但是在真实的场景中,您很可能会看到许多函数共享 CPU 时间,这可能有助于了解程序正在发生的性能问题。
深入挖掘
上面的分析器应该可以帮助您找到导致性能问题的函数,但是如果这还不足以缩小关注范围,让您知道应该修复什么,那么我们可以转向在更细粒度级别上运行的分析器。
第一个是line_profiler,顾名思义,它可以用来深入了解每一行代码所花费的时间:
line_profiler库与kernprof CLI(以 Robert Kern 命名)一起发布,用于有效地分析测试运行的结果。通过对我们的代码运行这个实用程序,我们产生一个带有代码分析的.lprof文件以及上面的输出(当使用-v时)。这个输出清楚地显示了我们在函数中花费时间最多的地方,这对于发现和修复性能问题非常有帮助。在输出中,您还会注意到@profile装饰器被添加到了exp函数中——这是必要的,以便line_profiler知道我们想要分析文件中的哪个函数。
即使在逐行运行分析时,也可能不清楚性能问题的罪魁祸首是什么。这样的例子可以是由多个表达式组成的while或if条件。在这种情况下,将特定行重写为多行以获得更全面的分析结果是有意义的。
如果你真的是一个懒惰的开发者(像我一样),并且阅读 CLI 文本输出太难了,那么另一个选择是使用pyheat。这是一个基于pprofile的分析器——另一个受line_profiler启发的逐行分析器——它生成一个占用最多时间的代码行/区域的热图:

pyheat 热图
考虑到我们使用的示例代码的简单性,前面的kernprof的 CLI 输出已经很清楚了,但是上面的热图使得函数中的瓶颈更加明显。
到目前为止,我们已经讨论了 CPU 概要,但是 CPU 的使用可能并不总是我们所关心的。RAM 很便宜,所以我们通常不会考虑它的用途,至少在用完之前不会。
即使您没有耗尽 RAM,分析您的应用程序的内存使用情况仍然是有意义的,这样可以发现您是否可以优化代码以节省内存,或者您是否可以添加更多的内存来提高代码的性能。为了分析内存使用情况,我们可以使用名为memory_profiler的工具,它模拟了前面显示的line_profiler的行为:
对于这个测试,我们选择了一段稍微不同的代码。memory_intensive函数创建和删除大型 Python 列表,以清楚地展示memory_profiler如何有助于分析内存使用。与kernprof分析一样,这里我们也必须添加@profile在文本下运行,以便memory_profiler识别我们想要分析的代码部分。
这表明,仅仅为了一个简单的None值列表,就分配了 100 多 MB。但是请记住,这个输出并没有显示内存的真实使用情况,而是显示了每行上的函数调用分配了多少内存。在这种情况下,这意味着列表变量实际上并没有存储那么多内存,只是 Python list可能会过度分配内存来适应变量的预期增长。
正如我们从上面看到的,Python 列表经常会消耗数百兆甚至数千兆字节的内存,一个快速的优化方法是切换到普通的array对象,这样可以更有效地存储原始数据类型,比如int或float。此外,您还可以通过使用typecode参数选择较低精度类型来限制内存使用,使用help(array)查看有效选项及其大小的表格。
如果这些更具体、更细粒度的工具还不足以找到代码中的瓶颈,那么您可以尝试反汇编它,看看 Python 解释器使用的实际字节码。即使反汇编不能帮助您解决手头的问题,它仍然有助于您更好地了解和理解每次调用某个特定函数时 Python 在后台执行了哪些操作。记住这些事实可能有助于您将来编写更高性能的代码。
通过将函数/代码/模块传递给dis.dis(...),可以使用内置的dis模块生成代码反汇编。这将生成并打印该函数执行的字节码指令列表。
在整篇文章中,我们一直在使用非常慢的e到X的幂的实现,所以我们在上面定义了快速实现的平凡函数,这样我们就可以比较它们的反汇编。试图把它们都拆开,会产生完全不同的输出,这就更加明显地说明了为什么一个要比另一个慢得多。
最快的一个:
旧的,缓慢的版本:
为了更好地理解上面的输出中实际发生了什么,我推荐阅读 this StackOverflow answer ,它解释了输出中的所有列。
解决方案
在某种程度上,对你的代码和算法做额外的调整将开始提供递减的回报。此时,将注意力转向外部工具来提供一些额外的性能提升是一个好主意。
提高代码速度的一个可靠方法是将其编译成 c 语言,这可以使用各种工具来完成,例如 PyPy 或 Cython。前者是一个 JIT(实时)编译器,可以用来替代 CPython。它可以不费吹灰之力提供显著的性能提升,让您轻松、快速地赢得胜利。您需要做的就是下载归档文件,解压并运行您的代码:
为了向您证明我们不费吹灰之力就能获得立竿见影的性能提升,让我们用 CPython 和 PyPy 快速检查一下脚本的运行时间:
除了上面提到的好处,PyPy 还不需要对代码做任何修改,并且支持所有内置模块和函数。
这听起来很神奇,但是使用 PyPy 也有一些利弊。它支持需要 C 绑定的项目,比如numpy,但是这会产生很大的开销,使得库明显变慢,实际上抵消了任何其他的性能提升。在使用外部库或与数据库交互的情况下,它也不能解决性能问题。同样,你也不能指望 I/O 相关的程序有很大的性能提升。
如果 PyPy 没有成功,那么您可以尝试使用 cy thon——一种使用类似 C 的类型注释(而不是 Python 类型提示)来创建编译后的 Python 扩展模块的编译器。Cython 还使用 AOT(提前)编译,这可以通过避免应用程序冷启动来带来更大的性能增益。然而,使用它需要您重写代码以使用 Cython 语法,这反过来增加了代码的复杂性。
如果你不介意切换到 Python 稍微不同的语法,那么你可能也想看看prometeo——一种基于 Python 的嵌入式领域特定语言,专门针对科学计算。Prometeo 程序可转换为纯 C 代码,其性能可与手写 C 代码相媲美。
如果上述解决方案都不能满足性能要求,那么您可能需要用 C 或 Fortran 编写优化的代码,并使用外部函数接口(FFI)从 Python 中调用代码。可以帮助你的库的例子是 C 代码的ctypes或cffi和 Fortran 的f2py。
结束语
优化的第一条规则是不做。如果你真的必须这么做,那么就在适当的地方进行优化。使用上面的分析工具来寻找瓶颈,这样你就不会浪费时间去优化一些无关紧要的代码。为您试图优化的代码创建一个可重复的基准也是有用的,这样您就可以测量实际的改进。
本文将帮助您找到性能问题的根源。然而,解决这个问题是完全不同的话题,在我以前的文章让 Python 程序快得惊人中可以找到一些让你的 Python 代码明显更快的简单方法。
本文最初发布于martinheinz . dev
用 Python 描述机器学习算法
原文:https://towardsdatascience.com/profiling-machine-learning-algorithms-in-python-7d12e0d740f6
用于分析机器学习任务的有用工具

概要分析是一项软件工程任务,其中软件瓶颈是通过编程来分析的。这个过程包括分析内存使用情况、函数调用的数量以及这些调用的运行时间。这种分析很重要,因为它提供了一种严格的方法来检测软件程序中可能很慢或资源效率低的部分,最终允许优化软件程序。
剖析在几乎每种类型的软件程序中都有用例,包括那些用于数据科学和机器学习任务的程序。这包括提取、转换和加载(ETL)以及机器学习模型开发。您可以使用 Python 中的 Pandas 库对 ETL 进行分析,包括分析 Pandas 操作,如读入数据、合并数据帧、执行 groupby 操作、类型转换和缺失值插补。
识别机器学习软件中的瓶颈是我们作为数据科学家工作的重要部分。例如,考虑一个 Python 脚本,它读入数据并对数据执行若干操作,以进行模型训练和预测。假设机器学习管道中的步骤是读入数据、执行分组、拆分数据以进行训练和测试、拟合三种类型的机器模型、对测试数据的每种模型类型进行预测以及评估模型性能。对于第一个部署的版本,运行时间可能是几分钟。
然而,在数据刷新之后,想象一下脚本的运行时间增加到几个小时。我们如何知道 ML 管道中的哪一步导致了问题?软件概要分析允许我们检测哪部分代码是负责任的,这样我们就可以修复它。
另一个例子与记忆有关。考虑已部署的机器学习流水线的第一个版本的存储器使用。该脚本可能每月运行一小时,并使用 100 GB 的内存。未来,在更大的数据集上训练的模型的更新版本,可能每个月运行五个小时,需要 500 GB 的内存。随着数据集大小的增加,预计资源使用量也会增加。检测这样的增加可能有助于数据科学家和机器学习工程师决定他们是否希望以某种方式优化代码的内存使用。优化有助于防止公司在不必要的内存资源上浪费金钱。
Python 在运行时和内存方面为分析软件提供了有用的工具。其中最基本和最广泛使用的是 timeit 方法,它提供了一种简单的方法来测量软件程序的执行时间。Python memory_profile 模块允许您测量 Python 脚本中代码行的内存使用情况。您可以用几行代码轻松实现这两种方法。
我们将使用信用卡欺诈数据集并建立一个预测交易是否欺诈的机器学习模型。我们将构建一个简单的机器学习管道,并使用 Python 分析工具来测量运行时和内存使用情况。这些数据拥有开放数据库许可证,可以自由共享、修改和使用。
准备数据
首先,让我们导入 Pandas 库并将我们的数据读入 Pandas 数据框:
df = pd.read_csv("creditcard.csv")
接下来,让我们使用 Pandas 方法 set_option()放宽列和行的显示限制:
pd.set_option('display.max_columns', None)pd.set_option('display.max_rows', None)
接下来,让我们使用 head()方法显示前五行数据:
print(df.head())

作者图片
接下来,为了了解这个数据集有多大,我们可以使用 len 方法来查看有多少行:
print("Number of rows: ", len(df))

作者图片
我们可以做类似的事情来计算列数。我们可以评估 Pandas 数据框对象的 columns 属性,并使用 len()方法计算列数:
print("Number of columns: ", len(df.columns))

作者图片
我们可以看到这个数据集比较大:284,807 行 31 列。此外,它占用 150 MB 的空间。为了展示 Python 中概要分析的好处,我们将从这个数据的一个小的子样本开始,在这个子样本上我们将执行 ETL 并训练一个分类模型。
让我们从生成一个小的子样本数据集开始。让我们从我们的数据中随机抽取 10,000 条记录。我们还将为 random_state 传递一个值,这将保证我们每次运行脚本时都选择相同的记录集。我们可以在 Pandas 数据框上使用 sample()方法来实现这一点:
df = df.sample(10000, random_state=42)
接下来,我们可以将数据的子样本写入一个新的 csv 文件:
df.to_csv("creditcard_subsample10000.csv", index=False)
定义我们的 ML 管道
现在,我们可以开始构建数据准备和模型训练的逻辑了。让我们定义一个方法,读入我们的 csv 文件,将它存储在数据帧中并返回它:
def read_data(filename):
df = pd.read_csv(filename)
return df
接下来,让我们定义一个选择数据中列的子集的函数。该函数将数据框和列列表作为输入,并返回包含所选列的新数据框:
def data_prep(dataframe, columns):
df_select = dataframe[columns]
return df_select
接下来,让我们定义一个方法,该方法本身定义模型输入和输出,并返回这些值:
def feature_engineering(dataframe, inputs, output):
X = dataframe[inputs]
y = dataframe[output]
return X, y
然后,我们可以定义一种方法,用于为训练和测试拆分数据。首先,在脚本的顶部,让我们从 Scikit-learn 中的 model_selection 模块导入 train_test_split 方法:
from sklearn.model_selection import train_test_split
现在,我们可以定义拆分数据的方法:
def split_data(X, y):
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42, test_size = 0.33)
return X_train, X_test, y_train, y_test
接下来,我们可以定义一种方法,使我们选择的模型适合我们的训练数据。让我们从一个简单的逻辑回归模型开始。我们可以从 Scikit-learn 中的线性模型模块导入逻辑回归类:
from sklearn.linear_models import logistic_regression
然后,我们将定义一个方法,该方法接受我们的训练数据和一个指定模型类型的输入。稍后我们将用于定义和训练更复杂模型的模型类型参数:
def model_training(X_train, y_train, model_type):
if model_type == 'Logistic Regression':
model = LogisticRegression()
model.fit(X_train, y_train)
elif model_type == 'CatBoost':
model = CatBoostClassifier()
model.fit(X_train, y_train)
return model
接下来,我们可以定义一种方法,将我们训练好的模型和测试数据作为输入,并返回预测:
def predict(model, X_test):
y_pred = model.predict(X_test)
return y_pred
最后,让我们定义一个评估我们预测的方法。我们将使用平均精度,这对于不平衡分类问题是一个有用的性能指标。不平衡分类问题是指其中一个目标的实例明显少于其他目标。在这种情况下,大多数交易数据对应于合法交易,而一小部分交易是欺诈性的:
def evaluate(y_pred, y_test):
precision = average_precision_score(y_test, y_pred)
print("Precision: ", precision)
现在我们已经为简单的 ML 管道准备好了所有的逻辑。让我们对数据的小子样执行这个逻辑。首先,让我们定义一个用于执行代码的主函数。在这个主函数中,我们将读入二次抽样数据:
def main(): data = read_data('creditcard_subsample10000.csv')
接下来,使用数据准备方法选择我们的色谱柱。让我们选择 V1、V2、V3、金额和等级:
def main(): … # preceding code left out for clarity columns = ['V1', 'V2', 'V3', 'Amount', 'Class'] df_select = data_prep(data, columns)
然后让我们定义输入和输出。我们将使用 V1、V2、V3 和金额作为输入;该类将是输出:
def main(): … # preceding code left out for clarity inputs = ['V1', 'V2', 'V3'] output = 'Class' X, y = feature_engineering(df_select, inputs, output)
我们将为训练和测试拆分数据:
def main(): … # preceding code left out for clarity X_train, X_test, y_train, y_test = split_data(X, y)
符合我们的数据:
def main(): … # preceding code left out for clarity model_type = ‘Logistic Regression’ model = model_training(X_train, y_train, model_type)
进行预测:
def main(): … # preceding code left out for clarity y_pred = predict(model, X_test)
最后,评估模型预测:
def main(): … # preceding code left out for clarity evaluate(y_pred, y_test)
然后,我们可以使用以下逻辑执行 main 函数:
if __name__ == “__main__”: main()
我们得到以下输出:

作者图片
现在,我们可以使用一些分析工具来监控内存使用和运行时。
让我们从监控运行时开始。让我们从 python 中的 timeit 模块导入 default_timer:
from timeit import default_timer as timer
剖析运行时
接下来,让我们先看看将数据读入 Pandas 数据帧需要多长时间。我们定义开始和结束时间变量,并打印差值,以查看已经过去的时间:
def main(): #read in data start = timer() data = read_data(‘creditcard_subsample10000.csv’) end = timer() print("Reading in data takes: ", end — start) … # proceeding code left out for clarity
如果我们运行我们的脚本,我们看到读取数据需要 0.06 秒:

作者图片
让我们对 ML 管道中的每一步做同样的事情。我们将计算每一步的运行时间,并将结果存储在一个字典中:
def main():
runtime_metrics = dict()
#read in data
start = timer()
data = read_data('creditcard.csv')
end = timer()
read_time = end - start
runtime_metrics['read_time'] = read_time
#slect relevant columns
start = timer()
columns = ['V1', 'V2', 'V3', 'Amount', 'Class']
df_select = data_prep(data, columns)
end = timer()
select_time = end - start
runtime_metrics['select_time'] = select_time
#define input and output
start = timer()
inputs = ['V1', 'V2', 'V3']
output = 'Class'
X, y = feature_engineering(df_select, inputs, output)
end = timer()
data_prep_time = end - start
runtime_metrics['data_prep_time'] = data_prep_time
#split data for training and testing
start = timer()
X_train, X_test, y_train, y_test = split_data(X, y)
end = timer()
split_time = end - start
runtime_metrics['split_time'] = split_time
#fit model
start = timer()
model_type = 'CatBoost'
model = model_training(X_train, y_train, model_type)
end = timer()
fit_time = end - start
runtime_metrics['fit_time'] = fit_time
#make predictions
start = timer()
y_pred = predict(model, X_test)
end = timer()
pred_time = end - start
runtime_metrics['pred_time'] = pred_time
#evaluate model predictions
start = timer()
evaluate(y_pred, y_test)
end = timer()
pred_time = end - start
runtime_metrics['pred_time'] = pred_time
print(runtime_metrics)
执行后,我们得到以下输出:

作者图片
我们看到读入数据和拟合数据是最耗时的操作。让我们用大数据集重新运行一遍。在 main 函数的顶部,我们将文件名改为:
data = read_data('creditcard.csv')
现在,让我们返回我们的脚本:

作者图片
我们看到,当我们使用完整数据集时,将数据读入数据帧需要 1.6 秒,相比之下,较小数据集需要 0.07 秒。识别导致运行时间增加的数据读入步骤对于资源管理非常重要。了解这样的瓶颈来源可以防止公司浪费计算时间等资源。
接下来,让我们修改模型训练方法,使 CatBoost 成为模型选项:
from catboost import CatBoostClassifierdef model_training(X_train, y_train, model_type): if model_type == ‘Logistic Regression’: model = LogisticRegression() model.fit(X_train, y_train) elif model_type == ‘CatBoost’: model = CatBoostClassifier() model.fit(X_train, y_train) return model
让我们重新运行我们的脚本,但是现在指定一个 CatBoost 模型:
def main(): … #fit model start = timer() model_type = 'CatBoost' model = model_training(X_train, y_train, model_type) end = timer() fit_time = end — start runtime_metrics[‘fit_time’] = fit_time
我们看到以下结果:

作者图片
我们看到,通过使用 CatBoost 模型而不是逻辑回归,我们将运行时间从大约 2 秒增加到大约 22 秒,这在运行时间上增加了 10 倍以上,因为我们更改了一行代码。想象一下,如果运行时间的增加发生在原本需要 10 个小时的脚本上:仅仅通过切换模型类型,运行时间就会增加到超过 100 个小时。
分析内存使用情况
另一个需要跟踪的重要资源是内存。我们可以使用 memory_usage 模块在代码中逐行监控内存使用情况。首先,让我们使用 pip 在终端中安装 memory_usage 模块:
pip install memory_profiler
然后,我们可以简单地在每个函数定义前添加@profiler。例如:
@profile
def read_data(filename):
df = pd.read_csv(filename)
return df@profile
def data_prep(dataframe, columns):
df_select = dataframe[columns]
return df_select
诸如此类。
现在,让我们使用逻辑回归模型类型运行我们的脚本。让我们看看我们拟合模型的步骤。我们看到,用于拟合我们的逻辑回归模型的内存使用量约为 4.4 MB(第 61 行):

作者图片
现在,让我们为 CatBoost 重新运行这个:

作者图片
我们看到拟合逻辑回归模型的内存使用量是 13.3 MB(第 64 行)。这相当于内存使用量增加了三倍。对于我们这个简单的例子,这并不是什么大问题,但是如果一家公司部署了一个新版本的产品,并且它从使用 100 GB 的内存增加到 300 GB,这在资源成本方面可能是很大的。此外,拥有这样的工具可以指出哪里出现了内存使用的增加是非常有用的。
这篇文章中使用的代码可以在 GitHub 上获得。
结论
监控资源使用是软件、数据和机器学习工程的重要组成部分。不管应用程序是什么,理解脚本中的运行时依赖性在几乎所有依赖软件开发和维护的行业中都是非常重要的。在新部署的机器学习模型的情况下,运行时间的增加会对业务产生负面影响。生产运行时间的显著增加会导致提供实时机器学习预测的应用程序的用户体验降低。
例如,如果 UX 要求用户不应该为预测结果等待超过几秒钟,而这突然增加到几分钟,这可能会导致客户沮丧,他们最终可能会寻找更好/更快的工具。
了解内存使用情况也很重要,因为可能会出现不必要过度使用内存的情况。这种使用可以转化为在不必要的内存资源上浪费数千美元。考虑我们将逻辑回归模型转换为 CatBoost 模型的例子。导致内存使用增加的主要原因是 CatBoost 包的默认参数。这些默认参数可能会导致包执行不必要的计算。
通过理解这种动态,研究人员可以修改 CatBoost 类的参数。如果做得好,研究人员可以保持模型的准确性,同时减少拟合模型所需的内存。对于构建生产就绪软件的工程师和数据科学家来说,使用这些分析工具快速识别内存和运行时瓶颈是一项基本技能。
如果你有兴趣学习 python 编程的基础知识、Pandas 的数据操作以及 python 中的机器学习,请查看Python for Data Science and Machine Learning:Python 编程、Pandas 和 sci kit-初学者学习教程 。我希望你觉得这篇文章有用/有趣。
本帖原载于 内置博客 。原片可以在这里找到https://builtin.com/data-science/python-profiling-tools-tutorial。
用 Python 编写圣诞灯程序
原文:https://towardsdatascience.com/program-christmas-lights-with-python-2a6bfa243d71
如何使用 Raspberry Pi 编写自定义 LED 灯光秀

假期已经正式开始,这意味着大量的购物、烘焙,当然还有装饰。家装商店里有许多不同形状、颜色和特殊效果的灯。但是你有没有想过用定制的颜色和动作来打造自己的节日灯呢?几行简单的 Python 代码让这一切成为可能。

图片作者。完整演示见结果部分。
这是我的第一个树莓派项目,我很高兴在假期前与你分享。我用树莓 Pi 来控制可单独寻址的 LED 灯条。与典型的节日灯不同,这些灯条允许为每个 LED 定制输入。下面列出了这些材料的链接。
背景
WS2812B 发光二极管(LED)是可单独寻址的 LED,可以对单个 LED 进行编程。该条有 3 根电缆连接到每个 LED:
- 负极电源线/地线(黑色)
- 正极电源线(红色)
- 数据线(绿色)
这种品牌的 LED 灯条分叉电源线(红色和黑色电缆),以便更容易连接到单独的电源。即使最后有 5 条电缆,也只有 3 条路径。

图片作者。
该条的另一端有一个公连接器,允许串联添加其他 led 条。然而,如果没有足够的电力,添加更多的条带会导致过热。
材料
- LED 灯。我使用了两个 5 米长的 WS2812B 条。但是,任何 WS281X 指示灯都应该与 rpi-ws281x 库兼容。
- 树莓派。我用的是树莓 Pi 3,但任何带有接地和 GPIO 18 引脚的树莓 Pi 都可以。
- 公母跳线
- 电源
- 剥线器(可选)
布线方向
启动前,确保 Raspberry Pi 和外部电源均已断开。
- 使用跨接电缆将 LED 灯条接地电缆(黑色)连接到 Raspberry Pi 上的接地引脚。下图使用引脚 6 作为接地。
- 使用跳线将 LED 条形数据线(绿色)连接到 Raspberry Pi 上的 GPIO 18。在 Raspberry Pi 3 上,GPIO 18 是引脚 12。
- 使用连接器将 LED 灯条连接到外部电源。你可能需要剥去电线上的一些绝缘层。
- 打开树莓和外部电源。

图片来自https://www . raspberry pi . com/documentation/computers/raspberry-pi . html(CC BY-SA 4.0)。作者制作的亮点。
密码
以下说明假设 Raspberry Pi 安装了 Raspian 操作系统。在 Raspberry Pi 终端上运行代码。
我推荐使用无头树莓 Pi 设置,它将允许你远程访问树莓 Pi。然而,这不是必须的。
更新系统
- 更新现有包
sudo apt-get update
2.在 Raspberry Pi 上安装 git
sudo apt install git
3.通过编辑以下文件停用音频
sudo nano /etc/modprobe.d/snd-blacklist.conf
4.将下面一行添加到打开的文件中。按 Ctrl-O 和 Ctrl-X 保存并关闭。
blacklist snd_bcm2835
5.使用 nano 编辑器打开配置文件。
sudo nano /boot/config.txt
6.使用 Ctrl-W 搜索文件。添加#来注释掉下面一行。
#dtparam=audio=on
7.重启 Raspberry Pi 以确保所有更改生效。
sudo reboot
安装和测试库
- 克隆库。
git clone https://github.com/rpi-ws281x/rpi-ws281x-python.git
2.编辑测试文件中的输入参数。
sudo nano rpi-ws281x-python/examples/strandtest.py
我的设置包括两个 150 个发光二极管的条带,总共 300 个发光二极管。是我唯一需要改变的变量。
# LED strip configuration:
LED_COUNT = 300 # Number of LED pixels.
使用 Ctrl-O 和 Ctrl-X 保存并关闭文件。
3.测试设置!
sudo python3 rpi-ws281x-python/examples/strandtest.py
自定义灯光
为了定制我的灯,我从rpi-ws281x-python库中复制了strandtest.py来创建xmas.py。
# Change directory
cd rpi-ws281x-python/examples
# Copy strandtest.py and name copy xmas.py
cp strandtest.py xmas.py
这让我可以修改现有的功能并创建新的功能,而不用重写strandtest.py。在xmas.py中,我添加了一个功能,当键盘中断(Ctrl-C)或脚本运行完成时,所有的灯都将关闭。当运行完成时,脚本需要不到 4 个小时。
#!/usr/bin/env python3
import time
from rpi_ws281x import PixelStrip, Color
import argparse
import numpy
# LED strip configuration:
LED_COUNT = 300 # Number of LED pixels.
LED_PIN = 18 # GPIO pin connected to the pixels (18 uses PWM!).
# LED_PIN = 10 # GPIO pin connected to the pixels (10 uses SPI /dev/spidev0.0).
LED_FREQ_HZ = 800000 # LED signal frequency in hertz (usually 800khz)
LED_DMA = 10 # DMA channel to use for generating signal (try 10)
LED_BRIGHTNESS = 75 # Set to 0 for darkest and 255 for brightest
LED_INVERT = False # True to invert the signal (when using NPN transistor level shift)
LED_CHANNEL = 0 # set to '1' for GPIOs 13, 19, 41, 45 or 53
# Define functions which animate LEDs in various ways.
def colorWipe(strip, color, wait_ms=50):
"""Wipe color across display a pixel at a time."""
for i in range(strip.numPixels()):
strip.setPixelColor(i, color)
strip.show()
time.sleep(wait_ms / 1000.0)
def theaterChase(strip, color, wait_ms=50, iterations=10):
"""Movie theater light style chaser animation."""
for j in range(iterations):
for q in range(3):
for i in range(0, strip.numPixels(), 3):
strip.setPixelColor(i + q, color)
strip.show()
time.sleep(wait_ms / 1000.0)
for i in range(0, strip.numPixels(), 3):
strip.setPixelColor(i + q, 0)
def candyCane(strip, red_start):
for i in range(strip.numPixels()):
strip.setPixelColor(i, Color(255,255,255))
for i in range(strip.numPixels()):
if i % 10 == red_start or i == 10:
for j in range(i, i + 5):
strip.setPixelColor(j, Color(255, 0, 0))
strip.show()
def candyCaneMoving(strip, iterations):
i = 0
for iter in range(iterations):
candyCane(strip,i)
if i < 9:
i += 1
else: i = 0
time.sleep(1)
# Main program logic follows:
if __name__ == '__main__':
# Process arguments
parser = argparse.ArgumentParser()
parser.add_argument('-c', '--clear', action='store_true', help='clear the display on exit')
args = parser.parse_args()
try:
# Create NeoPixel object with appropriate configuration.
strip = PixelStrip(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS, LED_CHANNEL)
# Intialize the library (must be called once before other functions).
strip.begin()
print('Color wipe animations.')
candyCaneMoving(strip, 3600)
colorWipe(strip, Color(127,127,127))
LED_BRIGHTNESS = 255
# Create NeoPixel object with appropriate configuration
strip = PixelStrip(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS, LED_CHANNEL)
# Intialize the library (must be called once before other functions).
strip.begin()
theaterChase(strip,Color(127,0,0),1000,1000)
theaterChase(strip,Color(0,127,0),1000,1000)
theaterChase(strip,Color(127,127,127),1000,1000)
colorWipe(strip, Color(255, 0, 0)) # Red wipe
time.sleep(600)
colorWipe(strip, Color(0, 255, 0)) # Green wipe
time.sleep(600)
colorWipe(strip, Color(0, 0, 0), 10)
except KeyboardInterrupt:
colorWipe(strip, Color(0, 0, 0), 10)
计划任务
我想让我的灯每晚都亮着,不需要用户输入,所以我用 Cron 安排了一个任务。Cron 自带树莓 Pi Raspian 操作系统。
- 在编辑器模式下打开 Cron 作业表。
crontab -e
选择一个编辑器。我推荐 nano。
2.将任务添加到文件中。
0 17 * * * sudo python3 ~/rpi-ws281x-pi/examples/xmas.py
Cron 作业遵循格式[minute] [hour] [day of month] [month] [day of week] [command to execute]。*表示任何值,因此 Cron 将对该字段中的所有值执行命令。上面的作业将在每月的每天下午 5 点(也就是 17:00)运行。
结果
xmas.py 中的函数演示
参考
"通过 Raspberry Pi 连接和控制 WS2812 RGB LED 灯."树莓派教程。https://tutorials-raspberry pi . com/connect-control-raspberry-pi-ws 2812-RGB-led-strips/
" Cron Jobs 和 Crontab 初学者指南." PiMyLifeUp 。https://pimylifeup.com/cron-jobs-and-crontab/
结论
感谢您阅读我的第一个树莓 Pi 项目。如果您有任何问题,请联系我们或发表评论。我非常乐意帮忙。此外,我很想知道你希望未来的 LED 项目采用什么样的模式。
感谢您阅读我的文章。如果你喜欢我的内容,关注我或者加入 Medium 。
在 LinkedIn 、 Twitter 或 Instagram 上与我联系。
欢迎所有反馈。我总是渴望学习新的或更好的做事方法。
请随时留下您的评论或联系我 katyhagerty19@gmail.com。
项目管理的正确方式
原文:https://towardsdatascience.com/project-management-the-right-way-57ce2d8b56bd
使用这个现成的 Excel/Python 甘特图模板跟踪您的任务

团队合作就是一切(照片由兰迪·法特拍摄)
背景:
项目管理工具多如牛毛。但有时简单的解决方案就足够了,特别是如果他们将 Excel VBA 与 Python 结合起来,那么你就不必用大锤敲碎坚果(或花一大笔钱)。
项目管理工具的一个主要组成部分是根据项目的分配能力和完成情况来跟踪项目里程碑。此外,投入和评估应尽可能方便用户。在本文中,我们将看到一个简单的自行编程的解决方案是什么样子的。
解决方案:
首先,让我们看一下项目模板。打开文件后,我们被告知我们是当前唯一正在处理该文件的用户。

打开文件后的欢迎信息(图片由作者提供)
这一功能已通过 VBA (ThisWorkbook)实现。
Private Sub Workbook_Open()
Application.ScreenUpdating = False
Range(“r4”).Value = Date
If Range(“G2”).Value = “” Then
MsgBox (“Hello “ & Application.UserName & “! This file is currently edited only by you.”)
Range(“G2”).Value = Application.UserName & “ works on this file since “ & Time & “.”
Else
MsgBox (“Mrs/Mr “ & ActiveSheet.Cells(2, 7))
End If
Application.ScreenUpdating = True
End Sub
这个特性的原因是为了促进与同事的协作:因为这个模板还不是一个多客户机数据库应用程序,所以几个用户不能同时处理这个文件。这一需求的前景将在本文的最后提到。
首先,我们来看一下总体概况。

项目模板(图片由作者提供)
正如我们所看到的,模板基本上分为三个区域。
第一个是带有功能按钮的上部区域。

然后左边是项目概述。

最后,右侧是相应的甘特图。

项目概述
此区域列出了各个里程碑,包括项目任务。除了几个自由文本注释栏,还可以通过搜索表单在 H 栏中选择项目经理。每当您单击 H 列时,就会弹出一个表单,启用字符串搜索以方便搜索和选择:

这一点已经在另一篇文章中详细解释过了。因此,这里不详细讨论其功能。
P 列包含存储优先级的可能性。通过双击该单元格,可以添加一个条,这样最多可以存储四个优先级条。

通过双击设置相关性(图片由作者提供)
这是最高优先级。再次双击或删除单元格会删除优先级栏,优先级可以从 0 再次增加。
另一个有趣的表格在 Q 和 r 列中。这表示子任务应该开始和结束的时间窗口。当您在此单元格中单击时,会出现一个日历。非常感谢 H. Ziplies,他最初创造了这个令人敬畏的日历形式。

点击 Q 或 R 栏时会弹出日历表单(图片由作者提供)
日历表单背后的 VBA 代码主要由以下部分组成:
Private Sub UserForm_Activate() ‘ start instance
For InI = 1900 To 2100
Cbo_Jahr.AddItem InI ‘ Year input
Next InI
Cbo_Jahr.Tag = 1 ‘ so that Create at Change is not triggered
Cbo_Jahr = Year(Date) ‘ Display of the current year
Cbo_Jahr.Tag = “” ‘ so that create is triggered on change
Lbl_Datum = Format(Date, “MMMM YYYY”) ‘ show current date show month and year
For InI = 1 To 12 ‘ enter months
Cbo_Monat.AddItem Format(DateSerial(Year(Date), _
InI, 1), “MMMM”) ‘ enter month
Next InI
Cbo_Monat = Format(Date, “MMMM”) ‘ Display of the current month, the calendar is created
End SubPrivate Sub Cbo_Year_Change() ‘ Event when changing the selection
If Cbo_Jahr.Tag = “” Then
Erstellen DateSerial(Cbo_Jahr, _
Cbo_Monat.ListIndex + 1, 1) ‘ create calendar
End If
End SubPrivate Sub Cbo_Month_Change() ‘ Event when changing the selection
If Cbo_Monat.Tag = “” Then
Erstellen DateSerial(Cbo_Jahr, _
Cbo_Monat.ListIndex + 1, 1) ‘ create calendar
End If
End SubPrivate Sub Scb_Month_Change() ‘ Change event
If Scb_Monat.Tag = “” Then
Erstellen DateSerial(1900 + Val(Scb_Monat / 12), Scb_Monat — _
Val(Scb_Monat / 12) * 12, 1) ‘ create calendar
End If
End Sub
这款日历的突出特点之一是突出显示特定日期:

底部列出了公共假日的名称(图片由作者提供)
公共假日用绿色突出显示,当天用黄色突出显示,周末用红色突出显示。在这种情况下,假期是为德国设置的,但可以为任何国家定制:
Function PublicHoliday(Datum As Date) As String
Dim J As Integer, D As Integer
Dim O As Date
J = Year(Datum)
D = (((255–11 * (J Mod 19)) — 21) Mod 30) + 21 ‘ Easter calculation
O = DateSerial(J, 3, 1) + D + (D > 48) + 6 — _
((J + J \ 4 + D + (D > 48) + 1) Mod 7)
Select Case Datum
Case DateSerial(J, 1, 1)
PublicHoliday = “Neujahr”
Case DateAdd(“D”, -2, O)
PublicHoliday = “Karfreitag”
Case O
PublicHoliday = “Ostersonntag”
Case DateAdd(“D”, 1, O)
PublicHoliday = “Ostermontag”
Case DateSerial(J, 5, 1)
PublicHoliday = “Erster Mai”
Case DateAdd(“D”, 39, O)
PublicHoliday = “Christi Himmelfahrt”
Case DateAdd(“D”, 49, O)
PublicHoliday = “Pfingstsonntag”
Case DateAdd(“D”, 50, O)
PublicHoliday = “Pfingstmontag”
Case DateSerial(J, 10, 3)
If Datum > “01.01.1990” Then
PublicHoliday = “Deutsche Einheit”
Else
PublicHoliday = “”
End If
Case DateSerial(J, 12, 25)
PublicHoliday = “1\. Weihnachtstag”
Case DateSerial(J, 12, 26)
PublicHoliday = “2\. Weihnachtstag”
Case Else
PublicHoliday = “”
End Select
End Function
S 列计算开始日期和结束日期之间的持续时间(以天为单位)。在 T 列中输入子项目的完成百分比,该百分比也显示在甘特图中。

里程碑的完成——蓝色行——由存储的公式自动计算(图片由作者提供)
甘特图
后三列是甘特图的基本要素。该图表仅使用 Excel 格式,没有任何额外的 VBA。这里显示了从开始日期到计划结束日期的每个子项目。还显示了每个里程碑的总体完成情况。浅蓝色的区域被认为已经完成,而深蓝色反映了子项目尚未完成的百分比。垂直的蓝线表示当天。

这张截图摄于 10 月 20 日(图片由作者提供)
您可以通过单选按钮在每日分组和每周分组之间切换。

日选择显示每日时间框架的甘特图。

显示单日和分组周(图片由作者提供)
否则,如果您选择周,时间线将显示如下。

更低的粒度,仅显示周数(按作者分类的图片)
您也可以使用顶部的滑块滚动时间线。

上层丝带
左起第一个按钮是绿色红绿灯,可以过滤不同的状态。

例如,如果您单击阶段 3 取消隐藏,将只显示这些状态。

要再次显示所有内容,只需选择“所有阶段取消隐藏”。这基本上只是为了说明在与电子表格和一点点 VBA 的互动中你已经可以实现多少。
团队图标有助于查看由不同人员筛选的任务。

借助表格简化报告(图片由作者提供)
作为一种想法,您当然可以组合任意多的子查询。根据对评估有用的内容,额外的时间过滤器可能对您有意义。

结合输入按钮的表单可以提高可用性
电子邮件按钮的想法是选择一个任务,并以方便的方式将数据发送给收件人。

自动发送电子邮件(作者图片)
请注意,该解决方案是为 Lotus Notes 电子邮件客户端设计的。
Sub LotusMailSenden()
..MailDoc.body = “Hello “ & ActiveSheet.Cells(ActiveCell.Row, 7) & “,” & “<br><br>the task “ & ActiveSheet.Cells(ActiveCell.Row, 2) & “ (with the ID number: “ & ActiveSheet.Cells(ActiveCell.Row, 19) & “) starts at “ & ActiveSheet.Cells(ActiveCell.Row, 10) & “ and is due until “ & ActiveSheet.Cells(ActiveCell.Row, 11) & “.” & “<br>Priority is set to “ & ActiveSheet.Cells(ActiveCell.Row, 9) & “.” & “<br>Please …” & “<br><br>Kind regards,” & “<br>” & Application.UserName
..
如果您需要 VBA 或 Python 的 MS Office Outlook 等解决方案,请告诉我。
显示和隐藏列的复选框是用一个简单的 VBA 例程创建的。
Private Sub CheckBox1_Click()
If ActiveSheet.CheckBox1 = True Then
Columns(“H:H”).EntireColumn.Hidden = False
Else
Columns(“H:H”).EntireColumn.Hidden = True
End If
End Sub
由于团队注释可能非常重要,我们将更仔细地看一下实现。点击语音气泡图标将打开此表单。

评论有助于更好地跟踪项目(图片由作者提供)
所以你可以输入评论..

..这将自动添加用户和时间戳:

任何附加的评论也是如此,因此您可以获得所有评论的历史记录。

这是这些注释功能的 VBA 代码:
Sub KommentarErfassen()
Dim Kom As Comment
Dim S As String
S = InputBox _
(“Enter your comment”, “Create comment”)
If S = “” Then Exit Sub
Set Kom = ActiveCell.AddComment
Kom.Text Application.UserName & Chr(10) _
& Date & Chr(10) & S
With Kom.Shape.TextFrame
.Characters.font.Name = “Courier”
.Characters.font.Size = 12
.AutoSize = True
End With
End SubSub KommentareErgänzen()
Dim sAlt As String
Dim sNeu As String
sNeu = InputBox _
(“Add a comment”, “Add comment”)
If sNeu = “” Then Exit Sub
With Selection
On Error Resume Next
sAlt = .Comment.Text
If sAlt = “” Then .AddComment
sNeu = sAlt & Chr(10) & Application.UserName _
& Chr(10) & “Kommentar vom “ & Date & _
Chr(10) & sNeu & Chr(10)
.Comment.Text sNeu
.Comment.Visible = True
.Comment.Shape.TextFrame.AutoSize = True
End With
End Sub
我们看到,您已经可以从 VBA 的 Excel 中获益良多。然而,在某个时刻,你会达到极限。幸运的是,有了 Python,VBA 女士的局限性就不复存在了。在另一篇文章中,我们已经学习了如何使用 Python 充分利用电子表格。因此,在本文中,只给出了一个非常简单的使用 Python 进行扩展的例子。例如,让我们使用 Python 从 k 列创建项目类别的单词云。
from wordcloud import WordCloudimport matplotlib.pyplot as plttext = ‘Project Category A Project Category B Project Category A Project Category C Project Category A Project Category C Project Category B Project Category F Project Category A Project Category E Project Category A Project Category C Project Category D Project Category E Project Category E Project Category F Project Category A Project Category G’wordcloud = WordCloud().generate(text)plt.imshow(wordcloud, interpolation=”bilinear”)plt.show()

当然不是最有用的可视化,但它应该只为我们提供一个基本的想法,对不对?(图片由作者提供)
我们将使用 Python 来添加真正的数据库功能,同时在另一篇文章中仍然使用这个 Excel 模板作为前端。在那之前,你可以从我的 Github 库下载 Excel 项目管理模板。
结论:
我们已经看到,如果你只懂一点点编程,你已经可以用简单的方法做非凡的事情了。在下一篇文章中,我们将借助 Python 将这个 Excel 模板扩展到一个真实的数据库应用程序。
在此之前,非常感谢您的阅读!希望这篇文章对你有帮助。请随时在 LinkedIn 、 Twitter 或工作室与我联系。
https://jesko-rehberg.medium.com/membership
project OrnithoWav——利用深度学习进行鸟类物种识别
使用超低功率深度学习的可扩展的鸟类多样性遥感
鸟类多样性是评估环境变化的常用指标,并且越来越重要。在这个项目中,我们使用 MAX78000 根据鸟类的鸣叫声对它们进行检测和分类。从而能够遥感鸟类多样性
由于气候变化及其对动物的生态影响,鸟类多样性监测变得越来越重要。在过去的几年里,基于深度学习的方法在减少专家劳动密集型鸟类识别过程方面显示出了希望。康奈尔大学基于 157 层 ResNet 的鸟网是目前该领域最先进的技术,可以识别近 1000 种不同的鸟类[1]。然而,该模型的实现仅限于高带宽互联网可用的区域,因为该模型需要在云中进行评估。
提议的解决方案是基于 KWS-20 模型的简化 CNN,根据声音和叫声识别 20 种不同的鸟类。

系统框图(图片由 Kenneth Joel 提供)
运行在超低功耗 MAX78000 上的 PoC 可以在非常偏远的地方进行鸟类监测,而不必担心高带宽连接或大电池。它也可以被那些想要跟踪拜访他们家或农场的鸟类的观鸟者使用。

照片由 Jongsun Lee 在 Unsplash 上拍摄
这里有一个项目执行的详细演练,包括 4 个阶段:
1。文献综述&开源数据集识别
2。数据工程
3。模型训练&使用 ai8x 的 C 代码合成——训练&ai8x——合成库
4。使用测试集中的音频样本对硬件进行测试。
第一阶段:文献综述&开源数据集识别
康奈尔鸟类学实验室是这一领域的一个活跃的研究小组,定期发表论文,并在 ka ggle(BirdCLEF 2021:https://www.kaggle.com/c/birdclef-2021,CBL 2020:【https://www.kaggle.com/c/birdsong-recognition/overview】T2 举办比赛
这些出版物和 kaggle 数据集为这个项目奠定了坚实的基础。
赢得这些竞争的网络非常庞大和复杂,对于我们的项目,我们使用了这个数据集的子集,并通过用鸟替换单词来重新训练来自 Maxim Integrated 的 KWS-20 模型(ai85kws20)。
然而,一个主要的限制是,所有这些数据的标签都很弱,并且没有按照不同的长度、质量和采样频率进行标准化,如下图所示:

元数据(肯尼斯·乔尔截图)
第二阶段:数据工程
这是最具挑战性的阶段。
前面提到的大部分数据都来自这一个资源:https://www.xeno-canto.org/(知识共享许可)
本网站上的数据是由世界各地的观鸟者收集的,因此样本的质量会有所不同。采样率没有标准化,有时鸟的声音不是很清晰。每只鸟还可以发出多种不同的叫声、声音和警报,增加了复杂性。
为了标准化数据集并使其类似于 KWS 数据集,采取了两个步骤:
1 .将 mp3 转换为 wav,并将所有数据重新采样为 16kHz(与 KWS 数据集相同,鸟类叫声的频率通常在 1-8 khz 之间,因此 16k 足够了):参考 code convert.py 和 convert16k.py
2。将音频分割成 1 秒的片段:数据集中的记录长度从 1.2 秒到 900 秒不等,一个记录中有 1 到 100 个相关的鸟类声音。编写一个处理脚本,首先过滤,然后识别并提取有用的 1 秒长的片段。请参考代码 python_slicer.py 和下图,直观了解该算法的工作原理。
注意:代码 convert16k.py 和 python_slicer.py 可以在文件夹 python-code.zip 中找到

音频分段(python_slicer.py)照片由 Kenneth Joel 拍摄
这一过程的结果是一个 79025 个独特样本的数据集,每只鸟有 1000-4000 个样本,总共 20 只独特的鸟。请注意,这些数据在分段后没有被手动清理。
阶段 3:模型训练&使用 ai8x 的 C 代码合成——训练&ai8x——合成库
https://github.com/MaximIntegratedAI/ai8x-training
一旦数据准备好了,只需要对训练脚本做一些修改来训练新数据,步骤如下:
-用新数据更新数据文件夹
-从 kws20.py (__extract_archive 函数)
-更新类数组和字典(kws20.py 中的多个位置)
参考目录/brd20 获取更新的代码。
运行脚本 train.sh,但是将历元数减少到 50。
使用的网络:ai85net-kws20.py 中的 ai85kws20net。
我们发现网络在 50 个周期后开始过度训练,因此我们停止了训练过程。
以下图像记录了训练输出:
- PNG 文件 e1、e10、e20、e30、e40 和 e50 显示了训练期间混淆矩阵的进展
- PNG 文件 eval 和 test 显示了训练后评估脚本和测试运行的结果。
-tensorboard-plots.png 显示了张量板控制台的输出
然后按照标准合成程序,使用 ai8x-合成文库合成该模型。合成代码可以在Synthesized-C-Code.rar中找到
阶段 4:使用测试集中的音频样本在硬件上进行测试。
然后使用 KWS20 代码库作为参考,将代码闪存到 MAX78000 羽毛板上。对类别标签进行了适当的更改。
通过笔记本电脑扬声器播放测试音频。
相同的演示可以在下面的视频中找到:
项目演示视频
请参考 brd20_demo (基于 kws20_demo)以便能够在您自己的板上运行项目。还包括视频演示中使用的测试样本。不需要额外的硬件来测试这个代码,因为我们已经使用了板载麦克风。
硬件设计
我们已经基于 MAX78000FTHR 原理图构建了定制硬件。对设计的主要改变是
1。移除所有视频相关硬件。
2。增加了 32MB 闪存,用于存储收集的音频数据,并随着时间的推移提高检测精度。
3。具有成本效益的 BoM 和 PCB 设计有助于简化制造和扩大规模。
未来范围:
-清理数据集:生成的数据集是纯基于代码的,没有手动清理。由于 xeno-canto 数据有时会因大量背景噪音而质量不佳,人工清理和标记可以大大提高准确性。
-增强数据集:更多的鸟类,更多的样本,通过添加人类和非鸟类动物的声音来改进“未知”类别。
-更大的输入尺寸:将输入长度从 1 秒增加到 2.5 秒可以提高精确度。[3]
-神经架构搜索:由于资源限制,没有进行这么多的研究。希望在下一个
-改进硬件:这个项目不需要额外的硬件,因为板载麦克风已经足够了。然而,我们有理由相信,更好的话筒可以带来更好的效果。由于这是一个遥感解决方案,电池组和 GSM 模块也可以增加价值,使其更易于部署。
P.S. 这是一个爱好项目,没有商业意图,但有很大的社会影响潜力。我已经分享了我为这个项目编写和修改的所有源代码,并将非常兴奋地支持想要继续这项工作的人。随意伸手!不幸的是,重新采样和切片的数据集太大,无法上传,但我很乐意分享。
参考文献:
【1】斯特凡·卡尔,康纳·m·伍德,马克西米利安·艾布尔,霍尔格·克林克,
BirdNET:鸟类多样性监测的深度学习解决方案,
生态信息学,
第 61 卷,2021,101236,ISSN 1574–9541,
https://doi.org/10.1016/j.ecoinf.2021.101236.
(https://www . science direct . com/science/article/pii/s 157495412100021
概念验证的演示视频
Github 链接
https://github.com/Kenneth-Joel/Ornithowav
从计算趋势预测人工智能进展
原文:https://towardsdatascience.com/projecting-ai-progress-from-compute-trends-d8c2af025d30
播客
杰米·塞维利亚论变革性人工智能和普通智能的时间线
编者按:TDS 播客由杰雷米·哈里斯主持,他是人工智能安全初创公司墨丘利的联合创始人。每周,Jeremie 都会与该领域前沿的研究人员和商业领袖聊天,以解开围绕数据科学、机器学习和人工智能的最紧迫问题。
机器学习中有一个想法,我们在 AI 中看到的大部分进步并不来自于模型架构的新算法。相反,一些人认为,进步几乎完全来自扩大计算能力、数据集和模型规模——除了这三个因素,其他什么都不重要。
透过这个镜头,人工智能的历史变成了处理能力和计算预算的历史。如果这被证明是真的,那么我们可能能够通过研究计算能力的趋势及其对人工智能发展的影响来预测人工智能的进展。
这就是为什么我想与独立研究员兼人工智能预测者 Jaime Sevilla 交谈,他是剑桥大学存在风险研究中心的附属研究员,在那里他从事技术预测,特别是理解人工智能的趋势。他的工作在很多很酷的地方被引用,包括我们的数据世界,他使用他的团队的数据整理了一份关于计算趋势的报告。在这一集的 TDS 播客中,Jaime 和我一起谈论了计算趋势和人工智能预测。
以下是我在对话中最喜欢的一些观点:
- 詹姆的工作包括预测人工智能的趋势,以估计我们何时可能达到被称为变革性人工智能(TAI)的人工智能能力水平。“太”大致是这样一个阶段,在这个阶段,人工智能非常强大,通常能够触发一个类似于工业革命的过程,而工业革命对人类文明的影响是深远的,完全不可预测的。有趣的是,TAI 的确切定义可能并不重要,因为研究人员和预测者的普遍共识是,当达到 TAI 这样的阶段时,技术进步将会如此之快,以至于即使是相当大的能力差异也会在很短的时间内被分开。因此,关于如何精确定义 TAI 的分歧很可能导致估计值只相差几年,而不是像工业革命那样相差几十年或几个世纪。
- 詹姆的工作确定了人工智能历史上的三个不同时代。首先,人工智能的进步是由预算相对固定的学术实验室推动的。尽管如此,由于摩尔定律,在此期间,计算能力每 20 个月就会变得便宜一倍,因此实验室往往每两年将计算使用量翻一番。但在 2010 年左右,事情开始发生变化:深度学习显示出解决视觉和自然语言处理中各种问题的真正潜力——但深度学习是高度计算密集型的。结果,公司开始在人工智能开发上投入越来越多的资源,因此也是在计算上。值得注意的是,詹姆认为这实际上在 AlexNet(深度学习的典型和广受欢迎的“启动”时刻)之前就开始了。这个深度学习时代给我们留下了令人印象深刻的系统,用于视觉和玩游戏等多种多样的任务,但它们仍然无法完全破解语言生成。正如 GPT-3 戏剧性地显示的那样,通往更好的语言模型的道路将是第三个扩展阶段——詹姆认为这一趋势始于 2015/2016 年的某个时候,比 GPT-3 早了五年。
- 关于计算投资将如何转化为新的人工智能能力,以及我们何时能达到 TAI,有很多争论。一种方法是观察生物智能,并尝试估计有多少计算能力用于创造人脑。有几种方法可以做到这一点:例如,你可以想象估计一个人从出生到成年需要进行的计算次数,或者估计数十亿年前大自然从无生命物质进化成人类大脑所消耗的计算量。这些被称为人工智能的“生物锚”。
- Jaime 指出了计算趋势的两个主要驱动因素。第一个本质上是摩尔定律:计算能力正以指数方式变得越来越便宜,随着人类想出新的方法来挤压更多的处理能力或更少的物质,每 20 个月成本就会降低两倍。第二个驱动因素是企业对计算的投资:随着企业认识到人工智能的价值,以及处理能力在其发展中发挥的关键作用,他们一直在加速向计算投入越来越多的资源。
- 这第二个驱动因素目前是最重要的,肯定会导致近年来企业人工智能预算的暴力扩张,但尚不清楚这些投资是否能以目前的速度无限增长。Jaime 估计,这一预算增长阶段可能会在十年结束时结束,因为与人工智能相关的计算在企业预算中的比例非常大。也就是说,除非人工智能相关的收入增长足够快,以维持它的运行,这也是一种明显的可能性。
你可以在这里的 Twitter 上关注杰米,或者在这里关注 T2。

章节:
- 0:00 介绍
- 计算的两点趋势
- 4:30 变革的人工智能
- 13:00 工业应用
- 19:00 GPT 三号和缩放
- 25:00 两篇论文
- 33:00 生物锚
- 项目的时间安排
- 43:00 权衡
- 47:45 总结
视觉语言微调中的即时上下文学习
原文:https://towardsdatascience.com/prompt-context-learning-in-vision-language-fine-tuning-3608e39ebcaf
用于有效模型适应的参数有效方法

更新:
【2022 年 9 月 27 日】追加CSP
【2022 年 9 月 29 日】追加 V-VL
视觉语言模型已经在计算机视觉领域产生了影响。通过图像-文本对之间的对齐学习,训练的图像编码器能够具有少量/零镜头学习能力,这可以以数据有效的方式实现快速模型适应。感兴趣的读者可以参考我以前的文章,了解视觉语言模型和剪辑的一般介绍:
在下游视觉任务中仅利用图像编码器的缺点是来自文本编码器的提示信息被放弃。文本编码器提供即时嵌入,这有助于下游视觉任务的性能。因此,通过在下游任务的微调中协作两个编码器,我们可以研究图像-文本嵌入之间的交互,并理解它们在有效的模型适应中的有效性。
有效的模型适应可以通过两种方式实现:数据有效和参数有效。这两种方法都通过减少相关资源(标记数据和模型参数)来减轻模型训练负担。前者可以帮助实现少/零镜头学习,而后者可以通过只训练总参数的一小部分来实现高性能。即时语境学习兼具两者优势。
提示上下文学习是一种微调提示向量的方法,以实现视觉语言模型的高效模型适应。如果没有学习,提示上下文是由人类创建的,并且最优性是未知的。在这篇文章中,我将总结一些最近在即时语境学习方面的成就。
CoOp 和 CoCoOp

让我们看看上面的图片。在即时上下文学习的帮助下,各种评估数据集的准确性都有很大提高。特别是对于一些专门的数据集,例如 EuroSAT,精确度惊人地从大约 30 增加到超过 80。结果来自于 CoOp 论文,模型架构如下:

( CoOp 建筑
CoOp 是最早的即时上下文学习架构。CoOp 和 CLIP 之间唯一区别是左上角的部分:可学习上下文。在微调过程中,只有这部分通过反向传播进行更新。很简单但是很有效!

然而,CoOp 的一个缺点是泛化能力差,这是由于学习类的过拟合。如上所示,在对看不见的类(如火车铁路)进行评估时,准确度会下降。这很有趣,因为在 CoOp 中冻结的文本编码器,当输入句子由人类创建的提示上下文和看不见的类名组合时,具有很强的零触发泛化能力,如 CLIP 论文所示。但是 CLIP 的零镜头概化对于已学习的提示上下文并不保留。

( CoCoOp 架构)
如上所示,同一组作者提出了 CoCoOp 作为 CoOp 的扩展,以恢复原始剪辑的零镜头可推广性。他们通过使用每个输入图像作为即时上下文学习的条件来实现它。编码图像嵌入通过元网、、两层瓶颈子网,生成条件元令牌 π、,添加到每个上下文令牌中。在训练期间,只有元网和上下文令牌向量用反向传播进行微调。
在推理过程中,不可见类的图像嵌入与学习到的上下文标记融合,以动态生成文本嵌入。由于这种动态文本嵌入包括来自相应的看不见的类图像的信息,所以它更有可能与相应的图像嵌入对齐,并且提高零拍摄性能。
与 CoCoOp 相反,CoOp 在没有图像调节的情况下学习提示上下文,这导致了静态提示上下文。因此,在零镜头推理过程中,由于过度拟合提示上下文,而提示上下文对输入的看不见的类别图像是不可知的,所以性能会大大下降。回想一下,在 CLIP 中,人为设计的提示上下文是中性,不能过拟合。这可能是 CLIP 具有很强的零镜头泛化能力的原因之一。
DenseCLIP
今年 CVPR 与 CoCoOp 的一个平行作品是 DenseCLIP 。与 CLIP 和(Co)CoOp 使用全局图像嵌入与图像分类中的相应文本嵌入对齐不同,DenseCLIP 旨在通过利用像素级图像嵌入来提高下游密集任务的性能。

( DenseCLIP 架构)
如上所示,图像编码后,我们有 2D 像素级图像嵌入。在 CLIP 和(Co)CoOp 中,这些 2D 像素级图像嵌入被汇集成 1D 嵌入向量,表示输入图像的全局特征。在 DenseCLIP 中,这些 2D 嵌入被保留用于下游密集任务的性能改进,例如语义分割。2D 嵌入的每个像素,即 1D 特征向量,也与相应的 1D 文本嵌入相乘,以获得像素-文本得分图,其中较高(较低)的得分表示较高(较低)的相似性。像素级得分图可以连接到 2D 图像嵌入,以提高下游微调的性能。
2D 嵌入通过一个转换器作为条件来生成动态提示上下文嵌入,如在 CoCoOp 中一样。作者使用交叉注意来交互图像和文本嵌入,其中文本嵌入充当查询,图像嵌入充当键和值。在交互之后,使用原始和交叉注意编码的文本嵌入的总和。该过程如下所示:

要学习的参数是 transformer 解码器中的提示上下文向量和交叉注意模块。与(Co)CoOp 和 CLIP 中文本嵌入与图像级嵌入对齐不同,DenseCLIP 中的文本嵌入与像素级嵌入交叉参与。通过这种细粒度的调节,DenseCLIP 学习到的提示上下文涉及像素级的关联性信息和相应的 2D 图像嵌入,这有助于提高下游密集任务的性能。这种密集对齐学习也在另一个分支工作中进行研究,称为视觉语言模型的对象级预训练,这种训练更加复杂和棘手。我还有一篇文章详细阐述了这个话题:
**https://ai.plainenglish.io/object-level-vision-language-contrastive-pre-training-8f0552b4b109
由于在文本编码之后,文本嵌入与嵌入空间中的图像嵌入是交叉参与的,所以在推断期间,文本嵌入可以被保留,而无需从文本编码器进行推断。这不同于 CoCoOp,在 CoCoOp 中,提示标记在文本编码器之前与图像嵌入相加,因此在推断期间不能省略计算。
DenseCLIP 的另一个优点是,该架构中的图像编码器可以由任意的图像编码器取代,而不一定是预先训练好的剪辑中的编码器。虽然图像嵌入最初没有与相应的文本嵌入对齐,但是它们可以在微调下游任务中以少量拍摄的方式有效地对齐。在文本编码器的帮助下,少镜头学习通过对齐相应的图像和文本嵌入来工作,实现类似于多镜头单峰图像编码器的性能。
芯片尺寸封装

( CSP 架构)
提出合成软提示(CSP)作为上述方法的替代:当上下文(例如的照片)是如上所示的人类设计时,类名被调整。CSP 起源于组合学习的思想,其中一个类概念被分解为属性和对象词汇,例如蓝猫、幼虎。在训练期间,可学习的(属性,对象)向量对,具有从预训练的剪辑文本编码器初始化的值,用于替换提示模板中的[属性]和[对象]以获得文本嵌入。在推理过程中,学习到的属性和对象词汇向量被重组以形成看不见的类别概念,从而提高零射击开放世界概括的性能。

(【CLIP 和 CSP 的提示模板替换比较
CSP 可以被视为人类设计的提示上下文和可学习的类词汇的组合。根据 CoOp 的调查,由于可学习的提示上下文容易过度适应,人为设计的提示上下文对于防止过度适应很重要。虽然 CSP 不像 CoCoOp 和 DenseCLIP 那样使用图像条件提示学习,但学习的属性和对象词汇向量的重组可以生成大量看不见的类概念,这也大大提高了零镜头推广能力。

CSP 是 CLIP 比(Co)CoOp 和 DenseCLIP 自然得多的扩展:保留了人类设计的提示上下文;提示中的类名扩展为属性和对象,并进行微调;学习的属性和对象向量在零镜头推理期间被重组。CSP 也是参数高效的,尽管文本编码器也是微调过的。CSP 的唯一缺点是巨大的重组提示表示,在推断过程中必须计算与图像嵌入的余弦相似性。然而,重组的提示表示可以预先计算并保存在内存中,以空间换取时间效率。
根据作者的说法,CSP 比 CoOp 平均高出 5.8%。我对它与 CoCoOp 的比较感兴趣,表明了图像条件和纯文本提示学习之间的普遍性。
五. VL
V-VL ,即基于视频的视觉语言预训练是 I-VL(即基于图像的视觉语言预训练)的扩展,以将文本嵌入与相应的视频嵌入对齐,从而在零/少镜头模型适应中提高性能。由于(视频,文本)对是稀缺和沉重的,而(图像,文本)对是巨大的,可以从网络上大量爬取,V-VL 的作者提出了一种基于预训练的 I-VL 模型(如 CLIP)的参数有效的模型自适应方法。

(在 V-VL 中提示设计)
在处理视频帧时,如何对时间轴进行编码至关重要。在 V-VL 中,作者使用多头自关注来整合帧级图像特征序列。在处理提示学习时,如何设计提示架构,即可学习的提示向量和类名标记之间的关系,是至关重要的。如上所示,作者在类名标记中预先计划和添加可学习向量。要预先计划和附加的可学习向量的数量(作者选择 16 个)是超参数。然而,作者声称优化可以完全学习忽略这些向量中的任何一个,因此消融研究是不必要的。
一旦计算出视频和文本嵌入,它们就通过 NCE 损失来对齐。V-VL 是参数有效的,因为只有可学习的提示向量和多头自我注意层被调整,其架构如下所示:

( V-VL 架构)
V-VL 是 I-VL 的一个高效和轻量级的适应,特别是对于视频中的动作识别和定位。总体来看,在 CoOp、CSP 和 V-VL 中,提示上下文中类名标记的数量和位置是不同的,这表明它们是超参数,也是可学习的。我认为类名标记的数量和位置可以以更加动态和结构化的方式与提示上下文交错,而不是像当前方法那样包含在输入序列中。
参考
DenseCLIP:具有上下文感知提示的语言引导的密集预测,2022
https://dushuchen.medium.com/membership **
促使工程 GPT-3 解决项目欧拉问题
原文:https://towardsdatascience.com/prompt-engineering-gpt-3-to-solve-project-euler-problems-1ff3b12f7d56
用人工智能解决数学问题,这样你就不用

可爱的霓虹灯机器人做数学!使用稳定扩散生成(作者)
GPT-3 在解决数学和编程问题上有多好?具体来说,GPT-3 在解决欧拉项目的问题上有多好?
Project Euler (以著名数学家命名)是一个网站,里面有数百个数学和计算机编程问题,难度不等。我第一次了解欧拉项目是在中学的时候,当时我的(亚裔)父亲坚持让我在空闲时间从网站上做数学题,以提高数学水平(我很不情愿地这样做,也许这解释了为什么我现在的数学能力相当一般)。

显示前十个问题的欧拉项目网站截图(作者)
GPT3 是由 OpenAI 于 2020 年创建的一个生成语言模型。它拥有理解和生成各种人类语言的巨大能力,包括回答常识问题、总结文章、生成创意小说、撰写营销内容、创作菜谱、和其他疯狂用例。GPT-3 也被用来解决小学数学问题。
但是欧拉项目的复杂问题呢?欧拉项目网站对它的问题做了如下描述:“尽管数学将帮助你找到优雅而高效的方法,但解决大多数问题需要使用计算机和编程技巧”这表明数学知识和编程技能的结合可能是解决这些问题所必需的。
让我们来测试一下 GPT 3 号!在这篇文章中,我将展示提示工程的迭代步骤,或者制作用于提示 GPT-3 生成答案的文本,我采取这些步骤是为了从几个项目欧拉问题中获得正确的解决方案。
GPT-3
为了使用 GPT-3,我使用了 OpenAI Playground ,它可以让你使用友好的 UI 与 GPT-3 一起玩。您可以调整许多不同的超参数或设置来获得不同的结果,它们显示在右侧的窗格中。这篇文章详细介绍了不同超参数的含义。对于我的实验,我保留了所有的默认设置。

OpenAI 游乐场使用 GPT 3 的截图(作者)
问题 1:3 或 5 的倍数(5%难度)
欧拉项目的第一个问题难度较低,可以认为是对网站上可以找到的各种问题的介绍。内容如下:
If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9\. The sum of these multiples is 23.Find the sum of all the multiples of 3 or 5 below 1000.
尝试 1:天真的方法(自然语言)
一个简单(且天真)的方法可能是一字不差地向 GPT-3 提出这个问题。这是我直接从网站上复制粘贴的。GPT 3 号的输出以绿色突出显示。

第一次尝试解决问题 1 的 GPT 3 代(作者)
正如你所看到的,GPT-3 开始时很强大,但后来迷失在它的手动方法中,列出了所有能被 3 或 5 整除的数字。事实上,它失去了动力,很快就放弃了。即使你不是一个学数学的人,你也可能会推断出这显然不是解决这个问题的最佳方法。那么我们还能做什么呢?
尝试二:自然语言+思维链
最近在机器学习中,出现了一个新概念叫做思维链提示。本质上,不是提示语言模型你想要它做的任务,而是强迫它一步一步地推理任务——就像你在现实生活中可能做的那样(而不是“让我起床”,思维链推理可能会像这样:“让我把被子推开”——>“让我把一只脚放在地上”——>“让我把另一只脚放在地上”,等等)。
因此,在下一次迭代中,我在原问题的末尾添加了“让我们一步一步思考”这句话。

第二次尝试解决问题 1 时由 GPT-3 生成(作者)
GPT 3 号似乎朝着正确的方向前进……直到它转向一个完全不同的方向。它知道寻找 3 的倍数和 5 的倍数的数字,但是它忘记了最初的任务,并且在途中的某个地方搞砸了。(你能想出在哪里或发生了什么吗?)
尝试 3:程序化方法
我从之前的尝试中得出的一个假设是,对于需要数学和编程知识的复杂问题,GPT-3 可能会被纯文本混淆,如果它使用编码语言思考,可能会表现得更好。由于 GPT-3 也接受了代码训练,所以值得一试。尤其是因为其他人已经看到了 GPT 3 号通过将数学问题翻译成代码取得的良好结果。
因此,我在格式化问题/解决方案示例中重新格式化了问题陈述。这是一个少数镜头学习的例子,这是当模型从少数例子(在这种情况下,一个例子)学习如何解决产生的问题(事实上,GPT-3 是众所周知的少数镜头学习!).然后我促使 GPT-3 想出一个“程序化的方法”来解决这个问题。然后,我用一个代码块的开头提示它,它就想到了解决问题的办法。
## Example:
Problem: Find the sum of all the multiples of 3 or 5 below 10.
Solution: If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3,5,6 and 9\. Th esum of these multiples is 23.
Answer: 23
---
Problem: Find the sum of all the multiples of 3 or 5 below 1000.
Solution: Let's think of a programmatic way to solve the problem.

第三次尝试解决问题 1 时由 GPT-3 生成(作者)
结果看起来干净简洁。如果将代码复制到 Python 解释器中,就能得到正确的答案。然而,对于像我这样懒惰的人来说,为了得到你的答案,这是一个额外的步骤。如果您希望答案不费吹灰之力就能送到您手中,该怎么办?这可能吗?
尝试 4:方案方法+参数化响应
在我的最后一次尝试中,我将编程方法与强制 GPT-3 使用参数将其输出格式化成某种格式相结合。GPT-3 将知道根据我的提示把问题和答案放到正确的部分。
Use this format to find the solution:
Problem: ${Problem}
IPython session:
${IPython function and commands needed to find solution}
Answer: ${Answer}

第四次(也是最后一次)尝试解决问题 1 的 GPT 3 代(作者)
我们走吧!我们得到了迄今为止最简洁的解,答案在最后给了我,所以我根本不用做额外的工作。
其他问题呢?
这是第一个也是最简单的欧拉问题。更难的问题呢?
问题 51 —质数替换(15%难度)
我们可以用同样的方法解决一个稍微难一点的问题。问题 51 是关于质数替换的,难度略高(难度等级为 15%)。正如你所看到的,这个问题稍微长一些,也更复杂一些。
By replacing the 1st digit of the 2-digit number *3, it turns out that six of the nine possible values: 13, 23, 43, 53, 73, and 83, are all prime.By replacing the 3rd and 4th digits of 56**3 with the same digit, this 5-digit number is the first example having seven primes among the ten generated numbers, yielding the family: 56003, 56113, 56333, 56443, 56663, 56773, and 56993\. Consequently 56003, being the first member of this family, is the smallest prime with this property.Problem: Find the smallest prime which, by replacing part of the number (not necessarily adjacent digits) with the same digit, is part of an eight prime value family.Use this format to find the solution:
Problem: ${Problem}
IPython session:
${IPython function and commands needed to find solution}
Answer: ${Answer}
我使用了与上次尝试解决问题 1 时相同的格式——通过编程迫使 GPT-3 解决问题。

答案是正确的!我能够毫不费力地获得这个答案,只需从 Project Euler 网站上复制并粘贴问题,并使用与我解决问题 1 时相同的提示格式。
问题 111 —质数替换(45%难度)
我想挑战 GPT-3 的数学能力,所以我强迫它解决了第 111 个问题(45%难度)。这个问题肯定需要我思考才能解决,但是 GPT 3 号只用了 10 秒钟就解决了。这个问题比前两个问题更复杂,因为它包括一个表格和几个解释问题的段落。
Considering 4-digit primes containing repeated digits it is clear that they cannot all be the same: 1111 is divisible by 11, 2222 is divisible by 22, and so on. But there are nine 4-digit primes containing three ones:1117, 1151, 1171, 1181, 1511, 1811, 2111, 4111, 8111We shall say that M(n, d) represents the maximum number of repeated digits for an n-digit prime where d is the repeated digit, N(n, d) represents the number of such primes, and S(n, d) represents the sum of these primes.So M(4, 1) = 3 is the maximum number of repeated digits for a 4-digit prime where one is the repeated digit, there are N(4, 1) = 9 such primes, and the sum of these primes is S(4, 1) = 22275\. It turns out that for d = 0, it is only possible to have M(4, 0) = 2 repeated digits, but there are N(4, 0) = 13 such cases.In the same way we obtain the following results for 4-digit primes.
Digit, d M(4, d) N(4, d) S(4, d)
0 2 13 67061
1 3 9 22275
2 3 1 2221
3 3 12 46214
4 3 2 8888
5 3 1 5557
6 3 1 6661
7 3 9 57863
8 3 1 8887
9 3 7 48073For d = 0 to 9, the sum of all S(4, d) is 273700.Problem: Find the sum of all S(10, d).Use recursion in the final solution.Use this format to find the solution:
Problem: ${Problem}
IPython session:
${IPython function and commands needed to find solution}
Answer: ${Answer}

问题 111 的 GPT-3 输出(作者截图)
这一次,GPT 3 号的代码又长又复杂。虽然我很难说出 GPT-3 在训练中记住了多少代码,以及它自己进行了多少推理,但它最终能够获得正确的答案。
结论
在本文中,我经历了不同的尝试,这些尝试是我用来迭代我的提示,以从 GPT-3 中引出不同的响应来解决 Project Euler 数学问题。主要收获是,我们不断学习接近 GPT 3 号的最佳方法,以便为我们执行某些任务。虽然在某些情况下,直截了当的提示可能会得到你想要的答案,但更有创意的提示可能会得到更有趣或更正确的答案!
如果你喜欢这篇文章,你可以在我的帖子里看到更多这样的内容!https://blog.yenniejun.com/
通过倾向得分的因果效应
原文:https://towardsdatascience.com/propensity-score-5c29c480130c
如何估计观测数据的影响
本文是因果效应系列文章的第二篇。在前一篇文章中,我们为因果关系奠定了理论基础,但仍有一些挥之不去的实际问题。也就是说,我们如何从观测数据中计算因果关系?在这里,我将讨论一套技术,使用一种叫做倾向得分的东西来做到这一点。在讨论之后,我们将给出使用这些技术处理真实数据的示例 Python 代码。
要点:
- 倾向评分评估治疗的可能性
- 具有相似倾向得分的受试者具有相似的基线协变量
- 3 流行的倾向评分技术有:匹配、分层和治疗加权的逆概率

倾向评分估计治疗的可能性。图片作者。
为了估计因果关系,我们需要数据。更具体地说,我们需要包含结果、治疗和协变量的数据(之前的定义为)。然而,并不是所有的数据源都是相同的。在讨论倾向得分之前,我们必须明确区分两种获取数据的方式。
观察性与介入性研究
第一个我们可以称之为观察研究。这包括被动地测量系统,而不干预数据生成过程。大多数数据收集都属于这一类别。
第二种获取数据的方式我们可以称之为介入研究。这是我们为了一个特定的目标而故意影响数据生成过程的时候。这包括像随机对照试验这样的事情,其中一个群体的随机样本被分成两组(即对照组和治疗组)并进行比较。随机化的主要好处是,它将倾向于在测量和未测量协变量[ 1 ]方面平衡治疗和对照人群。
区分这些类型的研究有两个原因。一,观察数据一般不控制亚群体大小(如对照组可能比治疗组大 10 倍),而二,对照组和治疗组可能存在系统差异。
例如,如果试图估计吸烟对肝脏健康的因果影响,如果吸烟者比不吸烟者倾向于喝更多的酒,比较吸烟者和不吸烟者(在野外)可能会产生有偏见的估计。
因此,如果试图从观察数据中计算出无偏的因果效应,我们可能会感到绝望。然而,你可能已经从副标题中猜到了,有一个解决方案:倾向得分。
倾向得分
一个倾向得分估计一个受试者 基于其他特征(即协变量)接受特定治疗的概率。这个分数接近平衡分数,这意味着当条件成立时,治疗组和对照组的协变量看起来相似。这使得对照人群和治疗人群的比较令人想起介入研究,但来自观察数据[ 1 ]。
生成倾向得分最流行的方法是通过逻辑回归[ 1 ]。考虑一下上一篇文章中的 Tylenol 例子。在那里,我们进行了治疗(即一种药丸),我们想测量它对结果(即头痛状态)的影响。
但假设现在我们无法获得来自 RCT 的数据,只有包含以下信息的观察数据:年龄、收入、性别、是否服药以及头痛状况的变化。
有了这个数据集,我们可以通过逻辑回归模型以一种直接的方式获得倾向得分。我们简单地将我们的协变量(即年龄、收入和性别)设置为预测值,将“是否服药”变量设置为目标值。下图对此进行了说明。

生成倾向得分的第一步。将协变量和治疗变量传递到逻辑回归模型中。图片作者。
一旦我们有了逻辑回归模型,我们就可以获得数据集中每个受试者的治疗概率(即倾向得分)。

生成倾向得分的第 2 步。将每个受试者的协变量数据输入到逻辑回归模型中,获得得分。图片作者。
3 种倾向评分方法
既然我们知道了什么是倾向得分以及如何计算,我们转向如何使用倾向得分来估计因果关系。在这里,我描述了 3 种流行的方法来做到这一点。
1)匹配
匹配包括创建具有相似倾向得分的治疗-未治疗对。一个简单的方法是通过以贪婪方式(贪婪搜索先前描述的**)将每个治疗的对象与具有最接近倾向得分的未治疗的对象进行匹配。
换句话说,挑选一个接受治疗的受试者,将他们与倾向得分最接近的未接受治疗的受试者一一匹配。如果我们确保没有未经治疗的受试者与一个以上经治疗的受试者匹配,则称之为无替代匹配。

倾向评分匹配(无替换)的工作原理说明。图片作者。
该过程将创建所谓的匹配样本,其中每个治疗对象与未治疗对象匹配。注意,在上图中,倾向分数为 0.07 和 0.12 的受试者没有与接受治疗的受试者匹配,因此这些受试者没有进入匹配样本。
有了我们匹配的样本,就可以通过直接比较治疗和未治疗的人群来计算治疗效果,就像我们在之前的博客中所做的那样。更多匹配策略见奥斯汀的论文。
2)分层
倾向得分的分层将受试者分成具有相似倾向得分的组,在这些组中可以计算因果关系。这通常作为两步流程来完成。首先,受试者根据其倾向得分进行排名排序(即从最小到最大 PS 排序)。然后*,排序后的科目被拆分成大小相等的组(即分位数)。*
基于 Cochran (1968) 的工作,一个好的经验法则是将受试者分成 5 组(即五分位数),然而,也可以做出其他选择。例如,在下图中,这个两步过程是针对被分成 4 个大小相等的组(即四分位数)的受试者进行说明的。

对倾向评分进行分层的两步流程图。图片作者。
一旦我们根据 PS 对受试者进行了分层,我们就可以通过直接比较每组内的治疗和未治疗人群(作为前的)来计算治疗效果。然后,我们更进一步,通过加权平均汇总组水平的治疗效果,以获得总体治疗效果。
3)治疗权重的逆概率(IPTW)
治疗加权的逆概率与我们迄今为止讨论的方法相比有着根本的不同。在匹配和分层中,我们使用倾向评分来导出受试者组,这些受试者组可用于计算治疗效果。然而,对于 IPTW,**我们使用倾向得分来推导每个受试者的权重,然后这些权重用于直接计算治疗效果**。

说明如何计算治疗加权逆概率的权重(IPTW)。图片作者。
为了计算效果,我们先从开始,我们根据上图右侧的等式给每个受试者分配一个权重。注意,治疗和未治疗受试者的体重方程是不同的。
接下来,我们使用刚刚得出的权重,分别对接受治疗和未接受治疗的受试者的结果变量值进行汇总。最后*,我们取治疗和未治疗人群的汇总结果值的差值。这种差异被视为平均处理效果(ATE)。下面给出了整个过程的直观总结。*

如何使用治疗加权的逆概率计算平均治疗效果的直观总结(IPTW)。图片作者。
平衡诊断:评估倾向评分绩效
在理想情况下,倾向得分是一个平衡得分,也就是说,如果我们看两个得分相同的受试者,他们的潜在协变量应该是相同的。然而,在实践中,这种情况很少发生。倾向分数仅仅作为这种理想情况的近似。
也就是说,我们可以使用这个想法来推导策略,以评估我们的倾向得分模型的性能。例如,在匹配的情况下,我们可以在匹配的样本中选取处理组和未处理组,并比较它们的潜在协变量分布。
对于正态分布的变量,这可以通过简单地比较平均值和标准差来实现。对于其他类型的分布,可以使用诸如 KS 检验、KL 散度、Wasserstein 距离等技术。关于平衡诊断的进一步讨论,我建议读者参考奥斯汀[1]T3]的精彩论文。
基于回归的技术
在进入示例代码之前,值得一提的是,当试图从观察数据中计算因果效应时,基于倾向得分的方法并不是我们唯一的选择。还有另外一类技术,我们可以称之为 基于回归的 。这包括诸如线性回归、元学习器和双机器学习等方法。事实上,在之前关于因果推理的一篇文章中,我们使用了一个元学习者来估计读研对收入的影响。在这里,我们将重复这一分析,但现在使用基于倾向得分的方法。
*
示例:估计研究生院对收入的治疗效果(使用倾向得分)
在这个例子中,我们将使用前面讨论的 3 种倾向得分方法来估计研究生学位对收入的因果影响。该分析与因果推理博客中的例子几乎相同。我们再次使用 DoWhy Python 库,并且在我们的因果模型中只考虑 3 个变量:年龄、hasGraduateDegree 和大于 50k(收入)。示例代码可以在 GitHub Repo 中找到。
我们从导入必要的 Python 模块开始。
**import** pickle
**import** dowhy
**from** dowhy **import** CausalModel
**import** numpy **as** np
接下来,我们加载数据集。这里我们使用同样的美国人口普查数据来自因果发现例子【3】。
df **=** pickle**.**load( open( "df_propensity_score.p", "rb" ) )
使用 Python DoWhy 库估算因果效应的第一步是明确定义因果模型,即陈述变量之间的因果关系。这里,我们将假设与之前的相同的因果模型。
model**=**CausalModel(
data **=** df,
treatment**=** "hasGraduateDegree",
outcome**=** "greaterThan50k",
common_causes**=**"age",
)model**.**view_model()

view_model()方法的输出是我们的因果模型的图形表示。
接下来,我们生成一个 estimand,这是计算因果效应的基本方法。这确保了测量的混杂因素(即年龄)得到适当的调整。
identified_estimand_experiment **=** model**.**identify_effect(proceed_when_unidentifiable**=True**)
然后,我们最终可以使用每种倾向得分方法来估计 hasGraduateDegree 对 greaterThan50k 的因果效应。
*# create list of names of the propensity score methods*
ps_method_name_list **=** ["matching", "stratification", "weighting"]*# initialize dictionary to store estimates from each method and list to store ATEs*
ps_estimate_dict **=** {}
ps_estimate_val_list **=** []# estimate effect for each method
**for** ps_method_name **in** ps_method_name_list:
ps_estimate **=** model**.**estimate_effect(identified_estimand_experiment,
method_name**=**"backdoor.propensity_score_" **+** ps_method_name,
confidence_intervals**=False**,
method_params**=**{})
*# add estimate to dict and ATE to list*
ps_estimate_dict[ps_method_name] **=** ps_estimate
ps_estimate_val_list**.**append(ps_estimate**.**value)
print(ps_estimate)
print("\n")
每种方法的平均处理效果估计如下:匹配 — 0.136,分层 — 0.25, IPTW — 0.331。给出大约 0.24 的总平均值。
我该如何解读这个?
我们可以把这个总比率解释为:在给定的年龄,即读研究生,获得研究生学位的期望值的单位变化将导致某人一年挣 5 万多的概率增加大约 24%。这些结果与我们之前使用元学习者方法看到的大致相同(我们看到 ATE = 0.20)。
一句警告
最后,我要提醒从业者对这些方法要有所保留。不像随机对照试验那样,随机化可以处理来自测量的 和未测量的混杂因素的偏倚,倾向评分方法只帮助我们对抗测量的混杂因素。
例如,可以想象,还有其他一些影响学位和收入的因素没有被我们的数据集捕捉到,比如:父母的职业/收入、研究领域和职业道德。
在本系列的下一篇博客中,我们将讨论如何应对不可测量的混杂因素,并将 do 运算符带回我们对因果关系的讨论。
资源
更多关于因果关系 : 因果关系概述 | 因果关系:介绍 | 因果推断 | 因果发现
https://shawhin.medium.com/membership
[1] 减少观察性研究中混杂效应的倾向评分方法简介作者 Peter C. Austin
[2] 保罗·r·罗森鲍姆唐纳德·b·鲁宾&的《倾向评分在因果效应观察研究中的核心作用》
[3] Dua,d .和 Graff,C. (2019)。http://archive.ics.uci.edu/ml UCI 机器学习库。加州欧文:加州大学信息与计算机科学学院。*
马氏链的性质
原文:https://towardsdatascience.com/properties-of-markov-chains-79380dc0358f
简单解释一下马氏链最常见的特征

亚历山大·托多夫在 Unsplash 上拍摄的照片
介绍
在我之前的文章中,我们一直在获得一种 马尔可夫链 的直觉。简而言之,马尔可夫链是一个随机过程,它在离散状态空间的离散时间中演化,其中状态间转移的概率仅取决于当前状态。系统完全https://en.wikipedia.org/wiki/Memorylessness无记忆。要全面理解前面的句子,请参考我以前的文章:
**
在本文中,我们将讨论马氏链的一些常见特征和性质!
不可约性
一个马尔可夫链被称为是 不可约 ,如果它有可能在某个给定的时间步长内从任意给定的状态跃迁到另一个状态。所有状态互相通信。数学上,存在一些时间步长 t > 0 其中:

LaTeX 中生成的方程。
即在某个给定的时间步I 转换到状态 j 的概率 P
下面的马尔可夫链是不可约的,因为我们可以从系统中的任何状态到任何其他状态:

由作者生成的图像。
吸收态和周期性
一个状态被定义为 吸收 ,如果当你到达那个状态,你就无法离开它。换句话说,它有 100%的概率过渡到自身。
据说一个吸收态的周期为 1,我们知道,对于每一个随后的时间步长,我们将回到那个相同的状态。周期为 1 的状态也称为 非周期性 和如果所有的状态都是非周期性的,那么马尔可夫链也是非周期性的。
注意:自转移概率不一定需要为 1 才是非周期性的。它只需要大于 0。因此,所有的吸收态都是非周期性的,但不是所有的非周期性态都是吸收态。
一般来说,a 状态的周期 I是所有整数的最大公约数为t>0:****

LaTeX 中生成的方程。
例如,对于下面的马尔可夫链,每个状态的周期为 3。这是因为,举例来说,一旦我们在 t = 0 处离开状态At = 3 处回到。此外,由于每个状态都是周期性的,因此**马尔可夫链是周期为 3 的周期性的。****

由作者生成的图像。
瞬态和循环状态
一个给定的状态*被认为是https://mpaldridge.github.io/math2750/S09-recurrence-transience.html如果一旦进入该状态,有的正概率该链可能永远不再返回状态。*****
一个 轮回的 状态基本上就是一个不瞬的。因此,当进入状态 i 时,存在 1 的概率,链将无限次肯定返回到给定状态(假设我们采取无限时间步长)。********
考虑下面的马尔可夫链:

由作者生成的图像。
我们可以看到状态 A 和 B 是瞬态因为有一种可能性,当离开这两个状态时,我们可以在状态 C 或 D 结束,它们只相互通信。因此,我们永远不会回到状态 A 或 B 因为马尔可夫链会循环通过C【d】********
另一方面,状态【C】和【D】是循环**,因为当我们离开 C, 时,我们知道我们将在两个时间步长内返回那里(因此它的周期为 2!).****
结论
希望你喜欢这篇文章!这是较短的一边,但有助于进一步建立我们对马氏链和不同类型的存在的直觉。在下一篇文章中,我们将讨论隐马尔可夫链!
和我联系!
- 要在媒体上阅读无限的故事,请务必在这里注册! 💜
- /subscribe/@egorhowell😀
- 领英 👔
- 推特 🖊
- github🖥**
- https://www.kaggle.com/egorphysics🏅****
(所有表情符号都是由 OpenMoji 设计的——开源的表情符号和图标项目。许可证: CC BY-SA 4.0**
提出将人工智能平台作为一种产品
原文:https://towardsdatascience.com/proposing-ai-platform-as-a-product-18f38ed7c099
如何通过客户和利益相关者研究为企业组织中的 AI 平台产品准备建议书?
人工智能平台,最好以自助服务产品的形式提供,对于企业组织的人工智能愿景的成功展示至关重要。交付这样一个产品需要一个可靠的、令人兴奋的提案作为文档。在本文中,我们将介绍如何生成这样一个文档的高级框架。这样的文档需要很好地理解产品生命周期以及与人工智能生态系统中的团队/社区和利益相关者一起做研究的机制。
主要想法是确定人工智能产品的阶段,然后如何在企业人工智能生态系统的目标群体中进行研究。

图 1:人工智能产品生命周期的阶段。图片由作者提供。
像任何其他产品一样,企业组织中的 AI 平台产品有五个阶段,如图 1 所示。
- 开始:在这个阶段,人工智能平台产品处于未知领域。平台产品团队需要在很多假设下工作。验证假设的方法是分析假设,快速制作原型,并测试原型。成功的验证定义了产品范围内的特性。这个阶段的最终结果是一个最小可行的平台产品,被一小组高影响力的 AI 团队喜欢和使用。保证这样一个成功的结果具有很高的不确定性和风险,需要通过对产品范围的仔细试验来减轻这些风险。
- 扩展:在这个阶段,目标是成熟一个最小可行的平台产品,以扩展到尽可能多的人工智能团队。平台产品团队需要对活动进行优先排序,以解决大规模适应问题。这些活动可以分为两种类型。第一个活动是关于提炼、重组或删除特性,以改善人工智能团队的用户体验。第二个活动是关于改进设计模式和底层系统架构,以处理来自不断增加的人工智能团队的负载,等等。这个阶段的主要成果是一个精炼的核心产品,它被高度评价这个平台的大量用户所采用。
- 扩展:在这一阶段,核心平台产品的新机会被发掘出来,这些机会可以通向产品。这些机会通常转化为与人工智能社区一起为新的客户群开发/测试新功能。这可以通过重新利用现有功能或创建新功能来实现。成功的功能将核心产品线扩展到更广泛的客户。扩大人工智能平台产品的范围具有适度的风险。新的特性至少会疏远当前客户的人工智能团队,最糟糕的是会导致糟糕的体验。然而,如果做得好,关键的结果是一个被组织广泛使用的丰富的产品。
- 维护:在这一阶段,产品与市场的契合最终得以实现,在平台产品的增长和留存方面存在强大的网络效应。该团队不需要执行任何主要的功能展示,但它需要确保用户体验不断改善。这需要人工智能团队的系统参与,并调整平台产品功能,以推动高影响力的参与。这一阶段的最终结果是一个稳定但不断改进的产品,对其庞大的客户群具有高度影响力。
- 中止:生命周期的最后阶段包括结束平台产品的生命,这需要仔细的策略和计划。尽管它令人失望,但该产品需要从市场上撤下,因为它可能不再相关,或者很难让它与人工智能团队保持相关。人工智能技术格局正在迅速变化。因此,当平台依赖于与行业不再相关的技术时,考虑这样一个阶段非常重要。在这个阶段,团队需要引导使用平台的人工智能团队轻松、安全地离开。这个阶段的最终结果是向作为产品客户的 AI 团队发布一个关于产品停产的发布说明。这封信不应该让他们感到惊讶或担心,但可能会有点宽慰。
产品提案框架
产品提案通常是由推动产品开发的人员撰写的文档,例如项目经理、产品所有者等。然而,它绝不应该单独完成。提案所有者在起草文档时应该从组织(或地区级)文档和演示文稿中获得灵感。此外,该提案应基于来自平台团队、潜在客户团队或社区细分市场以及人工智能生态系统中的利益相关者的反馈和见解。这些反馈应该来自有针对性的访谈、调查、研究、讨论以及与上述各方的集思广益会议。在广泛共享该文档之前,开发团队和客户群的一些成员应该对其进行审查和评估。

图 2:产品提案框架。图片由作者提供。
图 2 显示了产品提案的结构。本文档的各个部分如下所述。
- 问题陈述:产品团队要解决哪些 MLOPs 或 AI 问题。
- 成果:产品团队采用的解决方案的价值主张是什么。
- 不公平优势:产品团队相对于竞争团队有什么优势,比如外部厂商,现成的解决方案等。
- 关键指标:产品团队将使用哪些绩效指标来衡量影响和改进产品。
- 客户细分:客户群的特征是什么,并举例说明有代表性的用户/团队。
- 关键合作伙伴&资源:在产品处理中有哪些支持合作伙伴、见解和技术,以及如何利用它们来推出解决方案。
- 挑战&策略:在产品的生命周期阶段,开发和采用解决方案的挑战是什么,团队将使用什么策略来应对挑战。
请注意,文档的长度应受到限制,例如 2-3 页。
进行客户研究
文档的一个关键组成部分是问题陈述和解决方案取得的成果。在定义产品提案时,专业团队通常关注的是这些,而不是解决方案的功能。对于人工智能平台团队来说,这一切都始于非常好地了解人工智能团队和社区的问题。该团队需要进行探索性研究来找出这一点,并提出一个解决方案方法,该领域的一些团队愿意测试和使用该解决方案。团队和社区共享的问题往往是比实际问题更大的问题的症状,这要求平台团队在收集和分析问题信息时保持开放的思维。同样的研究可用于收集客户细分以及挑战和战略信息。
产品团队在研究过程中需要使用的关键概念是理解角色和人物角色。就角色而言,团队需要与购买者和用户打交道。对于人工智能平台产品,买方是产品所有者或人工智能团队的工程经理,他们最终决定团队是否要使用/测试该产品。用户对产品的使用有着细致入微的看法,并且经常提供关于产品特性的核心输入。通常情况下,产品团队与用户一起进行客户研究,确定问题范围,设计解决方案,从用户那里获得解决方案的价值主张,并在 AI 团队的路线图中协商平台产品使用/测试的性质。
人物角色适用于用户。角色可以基于技能学科,例如,数据科学家、机器学习工程师、数据分析师等。它可以基于职责,例如读者、作者、管理员等。也可以根据能力等级,如新手、高级初学者、精通等。在进行客户研究时,平台团队需要找出哪些人物角色是定义产品交付范围所必需的。团队还需要用正确的问题集与目标人物角色中的人交流,以提取设计解决方案方法所需的信息。应该从不同的人物角色类型中提取不同的信息,以确保从讨论中获得更广泛的见解。
重要的是研究要分阶段进行。首先,它应该专注于定义问题陈述。然后,它应该专注于识别客户群,这包括更详细地了解用户和角色。然后,它应该关注解决方案方法的范围以及采用解决方案时的挑战,这有助于确定结果和策略。
如何从客户那里收集信息取决于产品的生命周期。在开始阶段,平台团队应该询问关于目标人工智能团队工作方式的痛点,这些痛点属于解决方案方法的范围。因为还没有任何东西存在,所以洞察力的收集必须非常广泛,并且需要针对不同的利益相关者。
在扩展阶段,需要对访谈进行范围界定。要收集的关键信息是可用性和用户体验需求,以及团队的潜在路线图。讨论不需要局限于平台的现有用户。然而,对于潜在用户来说,在参与讨论之前,应该对平台的当前状态进行一些培训。在这个阶段,团队应该开始建立系统收集平台使用信息的流程。
对于扩展阶段,团队需要执行两种不同类型的洞察收集。首先,它需要像开始阶段一样,从潜在用户那里收集新产品范围的信息。然而,与此同时,平台团队需要研究新产品范围如何影响当前范围。因此,团队需要接触现有用户,探索扩大的产品范围如何影响他们。
对于维护阶段,团队需要从平台交互中收集信息,以便系统地优化用户体验。一些信息可以通过监控平台使用情况自动收集。定性信息可以通过调查收集。
对于停产阶段,团队需要从当前客户那里收集信息,了解在一段时间后平台是否会停产。
进行利益相关者研究
人工智能平台产品的利益相关者包括:
- 人工智能团队/社区:他们通过提供价值和优先级信息来规定产品的范围
- 技术专家:他们帮助理解与产品相关的技术堆栈
- 数据、IT 和云平台团队:他们决定产品的集成模式、治理过程和部署过程
- AI、数据、云、IT 和业务开发领域的高管:T 他们共同确保开发平台的预算获得批准,并在关键会议和讨论中提及该平台。
- 技术供应商:他们提供平台解决方案的核心构件。他们还可以从与其他组织的合作中了解采用挑战和缓解策略。
- 劳动力供应商:他们提供劳动力来开发和交付平台的 MVP 版本,特别是当平台存在于组织中缺乏能力或者没有足够的员工来推动计划的领域时。
- 商业协调员:他们通过对技术和劳动力供应商进行适当的尽职调查,帮助签订合同
- 学习/能力发展团队:他们提供支持,通过学习之旅、培训和研讨会提高组织内的平台使用能力
与其他现有的或现成的解决方案相比,上述利益相关者集体提供了平台产品在组织内可能具有的不公平优势。他们还提供与产品相关的关键合作伙伴和资源信息。
为了弄清楚这一点,不同类型的讨论需要与不同类型的利益相关者进行。人工智能团队/社区需要对他们未来将如何从产品中受益感到兴奋。技术专家需要详细理解问题陈述,以便理解技术堆栈的需求。数据、IT 和云平台领域的不同技术团队需要将他们的技术方法和发布时间表与产品的技术方法和发布同步。高管们需要了解资源是如何使用的,投资的潜在回报是什么。技术供应商需要了解解决方案方法、时间表、所需组件和性能规格。劳动力供应商需要了解解决方案方法、时间表和期望的能力。商业服务商需要知道供应商名单、时间表和交付规格。学习和能力发展团队需要了解使用平台的核心能力以及时间表,以便能够正确设计和规划学习计划。
评论
人工智能产品提案必须对其应用领域和用户群有充分的了解。不这样开始会导致产品团队在黑暗中漫游,场景可能是虚构的、有偏见的、不相关的。提案必须基于收集或更新上述关键输入的前期工作。虽然它没有为需要的每一个上下文做好准备,但是它们涵盖了一些最重要的上下文。如果你觉得设计中应该包含其他东西,请分享。
保护自己免受数据咨询项目中最大问题的困扰
清楚地定义你的可交付成果以避免项目漂移

大足石刻。中国重庆。图片由作者提供。
作为我专业工作的一部分,我在法律和非营利部门做了大量的数据咨询。这项工作通常涉及分析管理数据,从大量文本文档中提取信息,以及构建可视化和仪表板。我喜欢这份工作,因为它的灵活性、学习新事物的机会和经济收益。与此同时,数据咨询可能具有挑战性。本文讨论了如何保护自己免受我认为所有数据咨询项目中最大的问题:项目漂移。
什么是项目漂移?
项目漂移是指项目的目标和范围随着时间的推移而改变。有时项目漂移在项目启动时立即开始。或者问题慢慢出现,并随着时间的推移而积累。
通常,项目偏离是由于你的客户对要解决的问题不清楚。项目漂移表现为一系列探索性的活动,这些活动不会导致特定的可交付成果。有时,项目中会出现新的想法,这会导致对新的分析或向仪表板添加另一个功能的请求。有些请求简单快捷,所以我不介意免费提供。其他看似很小的请求,比如添加一个“按钮”来过滤数据,可能是一项非常耗时的任务。
项目漂移对客户和顾问都有影响。对客户来说,这意味着为不能解决原始问题的工作付费。对于顾问来说,这意味着做更多的工作却没有得到适当的补偿。
保护您自己和您的项目
如果你对数据咨询感兴趣,你需要战略性地考虑保护每个项目——和你自己——不受项目漂移的影响。
定义您的可交付成果
避免项目漂移的最好方法是清楚地定义可交付成果。花尽可能多的时间去弄清楚对你的客户来说成功是什么样的。如果你的客户不能定义成功,你可能还没有准备好启动这个项目。在他们试图解决的问题和你的分析将如何导致可行的见解或解决方案之间建立清晰的联系。
当被要求分析一个数据集时,要清楚你将回答哪些问题以及如何格式化。与出版物质量的图形或交互式数据可视化相比,提供格式良好的输出是一种完全不同的交付方式。在阐明可交付成果时,要尽可能具体。了解结束项目需要什么。如果你的项目永远不会结束,你就不能建立一个可持续的数据咨询实践。
明确合同中的可交付成果
我最大的菜鸟错误之一是在口头同意交付细节的情况下接受数据仪表板项目。还是那句话,尽可能具体,把一切都写下来。因此,当你收到额外的超出范围的工作请求时,你可以将对话带回书面合同,而不是试图回忆最初的协议。
处理请求的一个方法是创建一个不同想法的列表,以确保你的客户感到被倾听。当您将对话引向实际交付成果时,您可以讨论这些想法如何成为补充合同的一部分。


从头到尾的项目说明书。图片由作者提供。
创建项目时间表
拥有一个项目时间表有助于进一步界定你的工作范围。通过概述将交付的内容以及何时可以帮助管理客户期望。与客户一起使用项目管理工具是确保所有工作透明的一个很好的方法。有许多优秀的工具可以达到这个目的。我已经成为 Airtable 的忠实粉丝,它有一个非常棒的免费增值选项。您可以使用此附属链接创建一个帐户。
结构化项目会议
围绕可交付成果保持导向的另一个重要方法是召开结构化的项目会议。我喜欢在所有咨询项目会议中使用以下议程:
- 回顾自上次会议以来的成就
- 讨论下一次会议的交付内容
- 定义谁在做什么
- 识别可能影响交付成果完成的障碍
- 根据需要审查其他问题
关键是确保每个人都与项目目标和可交付成果保持一致。当你从数据中发现真知灼见时,小心不要让太多的讨论涉及探索有趣的途径。“有趣的”和“有用的”之间存在着显著的差异——而且许多这样的对话并没有让你更接近可交付成果。

作者的第一次正式项目会议。
助手的自主权
我经常让助手参与到我的项目中,给他们机会来建立使用真实世界数据的数据技能。当我和助手们一起工作时,我会经常检查以确保我们保持在正轨上。不熟悉数据处理的助手可能会很快偏离主题,有时会从事不属于商定的可交付成果的活动。我强烈建议在与助手一起工作时,尽可能使用实时通信工具。我更喜欢 Slack,因为它可以让我在不同的项目中集中所有的交流。
总结
最终,成功的项目需要高度的结构化。为了避免项目漂移,您必须清楚地定义可交付成果,并积极地管理项目活动以保持在正确的轨道上。
使用蛋白质语言模型预测蛋白质结构比 AlphaFold 2 快一百万倍
生物学家的梦想即将实现?
方法概述及其相关性;试图在 Google Colab 中运行它;测试它是否真的有那么好。

作者和 DallE 两代人的概念艺术。
新一轮的 CASP(蛋白质结构预测竞赛)离公布结果只有一个月了,我们继续看到关于使用 AlphaFold 2 和 AlphaFold 启发的神经网络预测蛋白质结构的论文,甚至更好,更快,或完全开源的程序;也是为了预测蛋白质与其他生物分子的界面,甚至设计新的蛋白质。虽然进展太快,我无法涵盖所有这些发展,但我偶尔会选择一些特别相关和令人感兴趣的发展。这里有一个我认为值得特别关注的问题:一个使用语言模型预测蛋白质结构的神经网络,其精度与 AlphaFold 2 相似,但速度快一百万倍,并且对同源序列的缺乏不太敏感。
这一模型和其他使用语言模型进行蛋白质结构预测的新兴模型突出了语言模型在总体上的作用,远远超出了最初使用它们处理自然语言的兴趣,正如 GPT-3 今天掌握的。
让我们看看这是怎么回事。
蛋白质结构预测的 AlphaFold 概述
从氨基酸序列预测蛋白质的(3D)结构在现代生物学中非常重要。有鉴于此,大约 30 年前,一项关于蛋白质结构预测的两年一度的竞赛(CASP,more 在此和在此)启动了,该竞赛促进了该领域的发展,并伴随着对模型的批判性评估和对进展的分析(如果有的话)。
多年来,CASP 表明蛋白质结构预测只适用于简单的蛋白质,即那些相似序列的结构已知的蛋白质。也就是说,只有同源建模有效,而硬目标(即没有已知结构的同源物的目标)仍然非常具有挑战性——预测者几乎不能掌握整体形状:

这张幻灯片(由作者在 CASP13 会议期间制作和演示)显示了预测硬目标是多么困难,并且在 CASP12-CASP13 之前没有或几乎没有进展。
到了 CASP12(当我自己作为评估员加入时),一项突破开始改变事情:残基对之间的联系现在可以通过应用“残基协同进化检测”方法(正式的直接信息分析等)来计算与想要建模的序列相关的多个序列的比对。这背后的基本原理是,在蛋白质结构中进行物理接触的残基对必须一起改变;因此,检测多序列比对中的这种同时发生的变化将指向在结构预测过程中必须被强迫在一起的残基对。这很有效,事实上在我的分析中,我观察到可以建立更大比对的蛋白质(因为数据库中有更多类似的序列)被证明是更好的模型:

在 CASP 12 中,针对每个靶产生的最佳模型的最高 GDTTS(用于评估模型的主要 CASP 指标)与通过建模序列的长度标准化的可用序列数的对数。情节中的每个点是一个目标,最难的 FM 或中等难度的 FM/TBM;这条线是 FM 案例的线性回归。图由作者根据免费提供的 CASP 数据制作。
然后在 CASP13 中,一些小组调整了这个想法,导致预测的进一步改进:现在不仅可以预测接触,还可以预测距离,甚至残基之间的相对方向。Deepmind 在这里进入了 CASP,应用了这一招,但并不只有他们。所有使用这种技巧的小组都表现得非常好,当然比 CASP12 好,也比以前的 CASP 版本好得多。Deepmind 将程序推向了最大限度,以微弱优势“赢”了第二名 CASP13。
作为 CASP12 和 CASP13 中的评估者,我可以清楚地看到 CASP13 中仍然存在大序列比对的要求,具有更高的偏移(在更低的序列数下有更好的模型),但仍然有很大的斜率:

与上图相同,但使用了 CASP13。请注意相似的斜率,但 y 轴上的截距更高,这意味着对于在比对中具有相似序列数的蛋白质,获得了比 CASP12 更好的模型。图由作者根据免费提供的 CASP 数据制作。
理想情况下,人们会希望在完全不需要辅助比对的情况下单独预测序列。也就是说,当上述图中的 Neff_max 为 1,或者 Neff_max/length 非常小时,例如对于 1000 个残基长的蛋白质为 0.001(因此 log = -3)。这是为什么呢?嗯,因为许多蛋白质只是在数据库中缺少相似的序列,而其他的则完全是人类发明的(“设计的”,如这里的和这里的所解释的);因此,对于这些蛋白质,任何预测都必须完全依赖于分离的序列,完全不需要比对的帮助。
今天,即使是 AlphaFold 2,凭借其完全重新设计的协议,并在 CASP14 中击败了所有其他组,也无法从单个序列中产生完美的模型。这是目前蛋白质结构预测中的“圣杯”,在这个问题上,本文讨论的论文通过使用语言模型方法预测蛋白质结构取得了重大进展。
使用语言模型和深度学习的单序列蛋白质结构预测
该论文由 Chowdhury 等人在 2022 年 10 月初刚刚出版的自然生物技术上发表。正如我们在上面所看到的,目前蛋白质结构预测中的主要挑战是对缺乏已知同源物的序列实现良好到完美的预测,因此不能为它们建立序列比对。这些被称为“孤儿”或“单一”序列,它们比你想象的要常见得多(约占所有已知蛋白质序列的 20%,超过 10%的真核生物和病毒蛋白质),即使数据库中的蛋白质序列不断增加,新的孤儿序列仍会不断出现。因此,发展预测它们结构的方法是很重要的。
包括 AlphaFold 2 在内的当前方法的另一点,不太关键但也是期望的,是以更快的速度获得精确结构预测的能力。AlphaFold 2 每次预测需要几分钟,这对于低通量需求来说是合理的,但对于处理完整的蛋白质组或探索蛋白质设计的序列空间来说太慢了。
来自 AlQuraishi 小组的 Chowdhury 等人提出的新方法称为 RGN2,该方法建立在他们以前的工作基础上,从比对得到的位置特异性评分矩阵预测蛋白质结构。在以前的模型中,结构被参数化为相邻残基之间的扭转角,以在 3D 空间中顺序放置蛋白质骨架;所有组件都是可微分的,因此可以端到端地优化网络,以最小化预测误差(预测模型与用于训练的实际结构相比的 RMSD)。尽管之前的方法没有明确使用协同进化数据,但它需要比对来计算位置特异性评分矩阵,因此它对序列的可用性很敏感。
新的 RGN2 使用在蛋白质序列上训练的蛋白质语言模型,从单个蛋白质序列预测蛋白质结构,而不需要任何比对。就像语言模型试图从一系列单词中提取信息一样,这里使用的蛋白质语言模型试图捕捉一串氨基酸中的潜在信息——这指定了蛋白质结构。在 RGN2 中,由早期方法中从比对得到的位置特异性评分矩阵提供的信息被语言模型直接从输入序列计算的信息所取代。
RGN2 使用的“蛋白质语言”模型称为 AminoBERT,其灵感来自 Rives 等人的早期工作,Rives 等人开发了一个通过对蛋白质序列的无监督学习训练的深度上下文语言模型,该模型学习了蛋白质的基本属性,因此能够进行远程同源性检测,预测二级结构和长程残基间联系等。这正是制造蛋白质结构所需要的信息。
通过将在结构上训练的蛋白质语言模型耦合到在旋转和平移不变性的规则下用于结构生成的网络模块,RGN2 可以产生具有呈现真实蛋白质结构特征的骨架几何形状的 Cα轨迹;此外,他们实际上似乎正确地预测了空间中的蛋白质折叠,正如作者所声称的那样。当可以产生比对时,完整的 RGN2 平均不如 AlphaFold 2 好,但可以推测它对不能产生比对的蛋白质更好。即使在前一种情况下,RGN2 也比 AlphaFold 2 快一百万倍。
注意,如果你在前面的段落中错过了这一点,RGN2 的预测只存在于 C𝛼轨迹中,而不是全原子模型中。在作者开发的管道中,剩余的主链和侧链原子最初是用 ModRefiner 在这个轨迹的顶部构建的,得到的结构用作 AlphaFold2 的模板,没有对齐,然后提供最终的模型。
作为 Google Colab 笔记本电脑运行
试用这个工具非常简单,因为作者为此创建了一个 Colab 笔记本- ,就像 Deepmind 向公众开放 AlphaFold 2 一样,从那以后其他科学家开始做。
笔记本在这里:
https://colab.research.google.com/github/aqlaboratory/rgn2/blob/master/rgn2_prediction.ipynb
我刚刚尝试过。
我观察到的第一点是,加载模型需要相当长的时间。我猜在对多种蛋白质的实际应用中,你实际上会加载模型一次,然后在几秒钟内运行预测。
第二,输出根本不是图形化的,所以不要指望像 ColabFold 这样的应用程序,它是为简单高效地使用 AlphaFold 2 而开发的。
第三,记住主要的输出只是一个 C𝛼轨迹,之后常规的 AlphaFold 2 运行(只是没有比对)来产生最终的模型。
那么,效果如何呢?
我尝试了两种相对简单的蛋白质(“简单”是因为在 PDB 中有一些结构,在数据库中有几个相关的序列),预测与已知的结构相差甚远。在这两种情况下,蛋白质的整体形状都被正确地捕捉到了,但在我看来,对于你能想象的最酷的应用来说,实际结构的 RMSD 仍然太高了。
这里有一个例子,在将产生的 C𝛼轨迹(从左到右第三个)与实际结构的 C𝛼轨迹对齐后,RMSD 几乎是 14 埃,对于由 CA 轨迹预测指导的最终 AlphaFold 2 模型,rmsd 几乎是 18 埃——两者都相当大!

从左到右:预期结构显示为漫画,预期结构只有 CA 原子,从 RGN2 获得 CA trace 模型,使用 RGN2 用 AlphaFold2 获得最终模型。图由作者使用 PyMOL 的免费版本。
从这个例子和其他一些例子中总结,我的结论是它还没有出现。我尝试过的所有蛋白质都在 PDB 中,并且有可用的序列,所以在训练蛋白质语言模型时一定已经看到了关于它们的一些东西。然而,与 AlphaFold 2(及其置信度预测,这里缺乏)让我们习以为常的情况相比,预测的结构与实际结构有些距离。
然而,该模型显然具有潜力,并且它肯定是非常有趣的(I)作为一种新技术,特别是表明语言模型在取代结构信息提取的比对方面做了合理的工作;和(ii)关于主要的实际目标,它提出了合理质量的快速预测。
从全球范围来看,语言模型在处理蛋白质序列问题(已经在 AlphaFold 2 中的一个类似 BERT 的模块中处理序列比对,并且也在化学和生物学的其他一些 ML 模型中使用)和蛋白质结构预测(特别是查看下一篇笔记和我即将发表的文章)方面的成功应用突出了语言模型在总体上的作用,远远超出了最初对使用它们处理自然语言的兴趣,就像今天由 GPT-3 掌握的那样。
最后请注意:Meta 在这方面与一个巨大的蛋白质语言模型竞争,在准确性和速度上挑战 AlphaFold 2
在 Meta 研究语言模型预测蛋白质结构的团队刚刚发布了一份预印本,显示他们的模型可以使用大型语言模型高速从一级序列中推断结构。就像 AlphaFold 和 RGN2 一样,该模型可以在谷歌 Colab 笔记本上运行;此外,Meta 提供了一个 API 端点,使得仅仅通过 API 调用就可以运行得更加容易!我的初步测试表明,这个模型可能比 RGN2 更好,事实上,预印本声称,该方法的预测速度比最先进的技术(AlphaFold 2)快 60 倍,同时保持了分辨率和准确性——我补充说,它可能对浅比对不太敏感。我希望很快在一篇文章中带来关于 Meta 对这个问题的看法和方法的消息。这个领域发展极快,也许生物学家的梦想会比预期的更快成为现实。
进一步阅读
https://github.com/aqlaboratory/rgn2
《自然生物技术》的同行评议论文
https://www.nature.com/articles/s41587-022-01432-w
在同一份杂志上,有一篇关于这篇论文的评论:
https://www.nature.com/articles/s41587-022-01466-0
了解更多 AI 驱动的蛋白质结构预测、CASP 等。:
www.lucianoabriata.com我写作并拍摄我广泛兴趣范围内的一切事物:自然、科学、技术、编程等等。 成为媒介会员 访问其所有故事(我免费获得小额收入的平台的附属链接)和 订阅获取我的新故事 通过电子邮件 。到 咨询关于小职位 查看我的 服务页面这里 。你可以 这里联系我 。
有效可视化和 Web 分析应用的原型
使用原型设计增强您的设计思维技能和生产力

概观
使用视觉图像来讲述一个强有力的故事,并提供可操作的见解,这既是一门艺术,也是一门科学。虽然有几种商业智能工具可供使用,但一些数据爱好者更喜欢创建独一无二的可视化和图表来传达他们的故事。许多可视化专家更喜欢使用 Javascript 中的开源库,如 Chart.js 和 D3.js,或者使用 Gleam、Altair 和 Python 中的其他可视化库来创建引人入胜的可视化效果。有些人走得更远,将这些可视化捆绑到一个漂亮的网络分析应用程序中。这种解决方案可能比实现标准的商业智能解决方案更便宜,尤其是对于中小型公司。
在选择定制的可视化解决方案时,可能会出现利益相关者犹豫不决的情况。这可能是因为它对他们来说仍然是一个抽象的概念。这时应该向他们展示使用定制可视化解决方案的切实好处。在某些情况下,利益相关者可能已经知道了好处,但是希望得到某种保证,以便能够信任专家并为项目发放资金。这就是快速原型法的用处。
让我们来讨论为什么快速原型是开发伟大可视化的可行选择。
利益相关者偏见

有时,一个可视化或 web 分析项目可能涉及多个利益相关者;他们中的每一个都可能想要结合不同的元素。即使是正式的业务需求收集会议也可能无法解决利益相关者相互冲突的期望。在许多情况下,项目可能会因为利益相关者的冲突请求而超时。最终的可视化结果可能不会令利益相关者和最终用户满意。因此,在开发可视化之前,将原型开发作为一项活动来添加是很重要的。原型可以很快地被修改,在相对较短的时间内融合不同涉众的需求。
迭代设计和开发成本
原型可以减少与迭代设计和开发以及后续的重新设计/返工相关的时间和金钱。如果实际的可视化工作涉及到需要在每次业务需求改变时修改的几行代码,这一点尤其正确。当增加额外的需求时也是如此。另一方面,原型需要相对较少的时间来进行修改。一旦涉众为每个迭代签署了原型,就可以开始该迭代的开发。

里卡多·迪亚斯在 Unsplash 上的照片
厌恶改变
当试图修改现有的产品或流程时,通常会出现厌恶改变的情况。当一个产品在功能上被修改或视觉上被重新设计时,随之而来的变化会在用户中产生负面反应。虽然厌恶改变是完全自然的,但是包含最终用户反馈的早期原型策略将确保更好的采用。这也将确保利益相关者和最终用户确信他们是变革的一部分。

原型工具
在这里,我们来看两个原型工具,它们提供了很好的特性,并且拥有一个共享大量想法和资源的活跃用户社区。
菲格玛
Figma 是一个工具,可用于开发各种应用程序的原型,从 web 应用程序和移动应用程序的用户界面设计到线框可视化和报告。下面是一个使用 Figma 创建的样本原型

作者图片
Figma 有一个活跃的用户社区,他们通过提供免费和付费的插件、小部件和 UI/UX 组件为该工具做出贡献。Figma 还提供了一个具有 API 和集成支持的开发平台,能够创建新的插件、小部件和应用程序。具体来说,在可视化和报告的上下文中,Figma 有一些插件,使您能够创建 KPI 图块、趋势线和各种图表,这些图表可以帮助您快速创建可视化的原型。Figma 还提供协作工具和白板,使团队能够聚集在一起并参与原型制作会议。许多用户使用 Figma 构建线性原型,并构建相关的自动化流程。
Axure
Axure 是另一个快速原型开发工具,可以帮助创建网站、应用程序和可视化的原型,而无需代码。Axure 提供了一个独立的桌面应用程序和一个云平台。它有一个强大的小部件库,用于流行的开源可视化库,如 chart.js. Axure 支持与吉拉、Confluence、微软团队和 Slack 的集成,从而确保团队成员随时了解任何新的变化。
素描
Sketch 是另一个强大的工具,可用于构建原型,以可视化使用标准商业智能工具难以可视化的用户和客户旅程。Sketch 也可以用于 A/B 测试活动,在这些活动中,您可以轻松地向测试人员展示您的原型的多个版本,并立即获得反馈。
结束语
虽然我只谈到了这三种原型工具,但市场上还有很多可供尝试的工具。在这里提到这些产品并不意味着我在推广或认可它们。我只是在传达这样一个事实,即对于构建优秀的可视化和 web 分析应用程序来说,这样的原型工具可能是一个有价值且高效的选择。
数据科学中的原型
原文:https://towardsdatascience.com/prototyping-in-data-science-32ceb118caf8
Jose A. Rodriguez 和杨奇煜·吉拉尔丁关于数据科学家如何超前思考的对话

图片由生殖艺术家 Iskra Velitchkova 提供。
数据科学家的定义在不断发展,尤其是随着领域专业化程度的提高。我不反对专业化,除了我认为不是所有的数据科学家都应该被归类到解决特定问题或自动化特定流程的角色中。数据科学实践的相当一部分是关于探索尚未成型的想法。它是关于好奇,测试假设,寻找没有人在看的地方,最重要的是,它是关于理解该做什么,而不是跟随当前的“生产东西”的技术文化。这就提出了一个问题,即如何给数据科学家空间,让他们超前思考。
在与 BBVA人工智能工厂的高级数据科学家和项目经理 Jose A. Rodriguez-Serrano 的对话中,我们引入了术语“发现驱动的原型开发”来描述数据科学中的原型开发如何导致探索决策的可能路径。
👋你好!我的名字是 杨奇煜 。每周,我都会留出几个小时与任何人(如学生、同事、朋友、其他专业人士等)进行视频通话。)谈论任何与数据科学和/或设计相关的话题。这些对话提供了时间来思考和反思我们的实践和项目,远离围绕数据和人工智能工作的持续宣传。这是其中一次幕后讨论的大致记录。

创意公司作者图片。
法比安:不久前,你推荐我读皮克斯的联合创始人艾德·卡姆尔的《创造力公司:克服阻碍真正灵感的看不见的力量》。有一句话让我印象深刻:
选择一条路是不够的,你必须沿着这条路走下去。这样做,你会看到你刚开始时不可能看到的东西;你可能不喜欢你所看到的,其中一些可能会令人困惑,但至少你会像我们喜欢说的那样,“探索邻近地区。”这里的关键点是,即使你决定你在错误的地方,仍然有时间走向正确的地方。”——艾德·卡姆尔
这种心态与我实践数据科学的方式产生了很好的共鸣。随着该领域专业化的增加,我经常听到对组织中数据科学职能角色的狭隘定义。我观察那些埋头苦干的团队,忙于小的改进或短期需求。他们的范围仅限于解决特定的业务问题或优化现有的流程。
但是当问题基于假设或者没有很好的定义时会发生什么呢?如果优化系统的设计没有考虑其影响(如偏差、质量),会发生什么?当没有人真正知道什么样的基础设施或数据集是必要的时,会发生什么?当一个想法在会议中只被抽象的观点讨论过,会发生什么?
我相信数据科学实践的一部分旨在解决这些问题。它的目的是在路径未知时“探索邻居”。它旨在发现未知的未知,通过创建可能的路径(如原型、实验)来指导决策。
你同意这个观点吗?

用数据探索社区。图片作者。
何塞:完全同意。创意公司擅长论证企业探索和实验的必要性。用简单的话来说,你提到的那句话是:只有在让一个好的团队为之努力之后,你才会看到某些想法的价值,而在此之前不会。**
这本书让我相信,当满足三个特定条件时,这种方法是有意义的:首先,在一个创造力是区分因素的行业工作;第二,在不断变化的环境中工作;第三,与高技能团队合作。虽然这本书谈到了电影行业,但大多数在科技行业工作的人,尤其是数据科学家,都会在他们的工作环境中认识到这三个条件。
然而,我也相信数据科学团队可以超越他们的实验思维。类似于creative,Inc 的论点,我认为在数据科学项目中有两种“深度”的探索。第一个是众所周知的:如果我们想到数据科学家的“典型一天”,实验自然会出现在探索性数据分析、验证新假设或优化资源的创造性任务中。称之为“解决方案驱动的原型”。第二个是关于发现没人注意的地方。称之为“发现驱动的原型”。这种深度探索了这样的问题:我们确定没有完全不同的更好的方法来设计这种服务吗?我们是否忽略了使用当前数据或能力的新产品功能?
因为大多数数据科学家来自科学或工程背景,我认为解决方案原型对我们来说是很自然的。但是,正如你所建议的,在数据科学中,发现驱动的原型还有很大的空间。在 BBVA AI 工厂,我们有几项探索性的举措。最近,我们启动了一项关于数据科学原型的计划,以扩大这种思维模式,并发现正在进行的项目之外的价值。
我知道你长期以来一直倡导一种跨越数据科学和设计界限的实践,那么所有这些是否与你的经历有共鸣?
是的,但是我从来没有那样想过你的原型范畴。这让我想起了 Alexander Osterwalder 和其他商业理论家如何谈论组织的“利用”和“探索”功能。开拓意味着改善一个成功建立的业务,而探索则是拓展组织核心活动的边界。他们将一家能够实现这两个目标的公司描述为一个“双手并用的组织”。
所以——是的,我一直在思考开发双手通用的数据科学功能。
我也有工程背景,但我已经学会了通过设计的语言来处理数据。在麻省理工学院的研究过程中,我意识到原型驱动思维对于技术专家超前思考的价值。那时,我提出了“用数据画草图”的概念,试图描述我们在那里用数据和软件(如可视化、地图、算法、交互界面、模型)为需要“探索社区”的组织快速产生有形结果的实践。

2014 年用可视化编程平台 Quadrigram 绘制数据草图。图片作者。
在工程和数据科学领域,原型意味着快速试验技术,看看它是否可行,但在设计领域,一个好的原型还提供了第二功能。它通过创造一个“东西”来帮助愿景超越假设和松散的想法。结果讲述了一个关于“可能是什么”的故事。它不一定是为了建立一个商业案例或展示一个技术解决方案,但它必须揭示问题的空间。例如,几年前,在不久的将来实验室,我们为一个客户制作了他们的第一个推荐系统的原型。其结果不是一个硬业务问题的解决方案,而是组织内的某种特洛伊木马,以发现数据中的缺点、他们缺乏的特定知识集以及如果有一天决定开发大规模推荐系统的影响。
JOSE: 首先,我承认我很喜欢你的措辞“用数据画草图”。它带回了“草图”的概念,这在设计中表示一个非常初步的产品版本,以快速和廉价的方式生产。它的目标是进一步“思考”产品,并分享和讨论产品创意以完善它,正如巴克斯顿的草图用户体验中突出解释的那样。

图片作者。
巴克斯顿区分草图和原型,对我来说,数据科学原型属于快速廉价,甚至一次性工件。为什么?使用数据进行编码和编程比 15 年前(巴克斯顿的书写出来的时候)更容易、更快、更便宜,因此原本要在纸上画草图的概念可以在计算机屏幕上用一些数据进行真实测试(例如,参见基于可视化工具的原型聊天机器人界面或客户关怀洞察、Flask 之类的 python 库和对第三方 NLP APIs 的调用)。
我认为我们正在接近一个时代,在这个时代,你可以在构思过程的早期就制作原型,这些原型是用真实的软件和真实的数据构建的,作为一种早期思考和讨论产品想法的方式。或者正如亚马逊的应用科学家尤金·严所说:
“有时,由大量研究和数据支持的深思熟虑的提议无法说服决策者。但是一个简单原型的演示会让他们兴奋并准备好投入。我很惊讶这种情况经常发生。”— 尤金·严
或者就像设计师和城市学家丹·希尔说的那样:“让原型做决定”。
我对发现驱动的原型开发有更广泛的定义,不仅仅是编写代码。事实上,原型可以是任何讲述尚不存在的服务的真实故事的东西:一个快速入门指南,一个拆箱视频,一个城市指南,产品目录,一个地图,一个 FAQ 页面,一个维修手册,新闻文章等等。最近,我们为一家初创企业制作了他们的年度报告《来自未来》。该原型为联合创始人提供了一种独特的方式,在编写任何代码之前,预先设想他们想法的机会、风险和后果。受我的同事朱利安·布里克(Julian Bleecker)的启发,他在 2020 年写了他的初创公司的年度报告——从 2024 年开始(T9),该报告鼓励就公司对数据的使用和数据科学的作用进行详细的决策讨论。你不是向未来前进,而是从未来回来报告,就好像它已经发生了一样。在不久的将来实验室,我们称这种方法为设计虚构。

OMATA 2024 年年报…写于 2020 年。图片由朱利安·布里克提供。
除了设计虚构之外,我还观察到了在主要组织中应用的不同风格的发现驱动原型。例如,在谷歌担任工程总监时,阿尔贝托·赛唯雅曾经谈到过预打字。亚马逊首席技术官沃纳·威格尔在 2006 年就已经阐明了公司的“逆向工作”惯例。
这些组织的共同点是,他们都明白决策的目的是框定形势,而不是选择最佳方案。他们擅长观察周围的角落,而不是做出最好的决定。他们探索出可供选择的方案。然而,即使有这些例子,仍然很难说服那些只依赖 ROI 和 KPI 来管理组织的人。
发现驱动的原型的影响很难在电子表格中报告,但是实践对于“去风险”(例如,快速识别和纠正错误的决策)是必不可少的。想想尽早发现你的假设是错误的价值。或者,预测数据中的盲点对组织有什么价值?错过一个机会有什么价值?所以,探索是有成本的:做的成本和不做的成本。而且,正如你之前强调的,制作原型的成本从未像今天这样便宜。
我理解一个企业短期 KPI 和激励机制的局限性,但你与怀疑主义斗争的经验是什么?
JOSE: 在选择原型策略时,有许多挑战需要克服,从团队如何组织到沟通。我解释了发现驱动的原型开发的主要目的是确保有价值的想法进入下一个阶段(反过来,在深入开发阶段之前,不太好的想法可以被停止或改进)。有效的方法因组织而异。
我听说过一些公司在 5 天的“高峰期”开发原型,或者其他公司遵循成熟的原型框架。这些都是准时的例子;我确实相信,每个组织都必须进行试验,找到自己喜欢的“收获”创意和消除风险的方式。
在 BBVA 人工智能工厂,我在过去几年里一直领导着与应用研究、创新或水平能力相关的项目。我已经提出了一系列在建立原型项目之前可以应用的良好实践,这些实践对以后会产生影响:
- 转换语言:把重点放在传达原型带来的差异化优势上,而不是原型本身或底层技术上。或者,如哈潘 : “爱上问题,而不是爱上解决方案。问‘我们如何优化购物流程’和问‘我们如何优化购物流程’有很大的区别。vs,“我们如何帮助用户重复购买?”。
- 有一个采用计划:根据目前的假设,当原型工件完成时,谁会是它的用户?他们将开始体验什么好处?
- 参与:与这些潜在用户就计划达成一致,让他们尽早参与讨论、决策,甚至成为原型开发团队的一员。
- 期望责任:必须指定至少一个人来“关心”原型的采用。
最后,虽然这看起来是可有可无的,但我倾向于强烈主张将原型计划的结果“民主化”(一般来说,任何创新和研究计划的结果)。这可以是简单的行动,如内部“展示和讲述”和外部交流(我非常喜欢快速前进实验室传播他们的发现),但也可以包括通过内部源代码库提供原型供内部重用。因为原型是关于学习的,通过使结果大众化,你希望其他人在你的工作的基础上产生更多的学习。
法比安:最后一点让我想起了建筑师、设计师和未来学家巴克明斯特富勒是如何将实验设计成获取知识的途径以及知识的网络生产和传播的。那是 70 年前的事了(!)他已经强调了过度专业化的问题:
“当然,我们的失败是许多因素造成的,但可能最重要的一个因素是,社会运作的理论是专业化是成功的关键,而没有意识到专业化排除了全面的思考。”—巴克明斯特·富勒

围绕短程线圆顶的巴克明斯特富勒实验。图片作者。
继续对话……
你可以通过正规的网络终端与我联系。每周我都会留出几个小时用于办公时间的视频电话让任何人谈论任何与数据科学或设计相关的事情。我们可以讨论你的早期项目,我们可能合作的方式,最近的阅读,你面临的挑战,等等。📅你可以在我的日历上预定一个 45 分钟的时间段。
杨奇煜·吉拉尔丁是一位双手灵巧的思想家/实干家,他在远见、技术和设计之间来回跳跃。他是不久的将来实验室的创始合伙人,这是一个由以设计小说先锋闻名的成功实践者组成的分布式网络。BBVA 数据分析公司前联合首席执行官,麻省理工学院研究员。🧑之父🏻🚀和🌙。你可以在 Twitter 和 LinkedIn 上关注他,或者直接联系。
Jose a . Rodriguez-Serrano是 BBVA 人工智能工厂的高级数据科学家和项目经理。以前,他是施乐公司的区域经理和研究科学家。几年前,我们在金融服务公司 BBVA 合作开发了一个“Edge”项目。我们的目标是通过开发具有机器学习新兴趋势的原型来拓展组织核心能力的边界。阅读他与人合著的一些帖子,或者在 Twitter 和 LinkedIn 上关注他。
解释了最近策略优化(PPO)
原文:https://towardsdatascience.com/proximal-policy-optimization-ppo-explained-abed1952457b
从强化到连续控制中的 go-to 算法

诺亚·布舍尔在 Unsplash 上的照片
最近策略优化(PPO)目前被认为是强化学习的最新发展。OpenAI 在 2017 年推出的算法似乎在性能和理解之间取得了正确的平衡。从经验上看,它与质量基准具有竞争力,甚至在某些任务上远远超过它们。同时,对于广泛的用户的实际采用来说,它是足够简单的,这不能对每个 RL 算法都这样说。
从表面上看,传统的政策梯度法(如加强法)和 PPO 的差别并不大。基于这两种算法的伪代码,你甚至可以说它们有点相似。然而,这一切背后都有丰富的理论,需要充分欣赏和理解算法。
我将简要讨论策略梯度方法、自然策略梯度和信任区域策略优化(TRPO)的要点,它们共同构成了走向 PPO 的垫脚石。
普通政策梯度
要理解这篇文章,很好地理解政策梯度方法是必要的。如果还没有,你可能想先看看这篇文章:
在 RL 的上下文中,策略 π 只是一个函数,它返回给定状态 s 的可行动作。在基于策略的方法中,函数(如神经网络)由一组可调参数θ定义。我们可以调整这些参数,观察所得奖励的差异,并朝着产生更高奖励的方向更新 θ 。这一机制是所有政策梯度方法概念的基础。
策略 π_θ(a|s) 是随机的,这意味着参数规定了动作 a 的采样概率,从而影响跟随轨迹 τ=s_1,a_1,…s_n,a_n 的概率。这样,我们可以表示依赖于 θ 的目标函数:

目标函数,产生与随机政策成比例的国家行动轨迹。
在随机策略中,我们用相应的概率对各种行为进行采样——这样我们就可以衡量回报的差异。观察到的奖励,结合行动概率,产生一个奖励信号。为了确定改进目标函数的更新方向,策略梯度方法依赖于梯度 ∇_θ (导数的向量)。这产生了以下更新函数:

传统的策略梯度更新函数,基于目标函数梯度∇_θJ(θ和步长α更新策略权重θ
虽然我们有奖励信号和更新方向,但是我们不知道到我们应该更新多少政策。选择正确的学习率 α 是一个挑战。权重更新的不一致影响加剧了这个问题。考虑以下两种策略同等幅度的权重更新。显然,左边的分布比右边的分布受影响更大。但是,欧几里德距离(即参数距离)是一样的!

正态分布对的比较。左边有μ_1=0,μ_2=1,σ_1=σ_2=0.3。右边有μ_1=0,μ_2=1,σ_1=σ_2=3.0。虽然两对之间的欧几里德距离是 1,但是很明显右边的一对比左边的一对更相似。[图片由作者提供]
由于政策变化大的风险,样本仅使用一次。然后,我们使用更新后的策略进行重新采样,这是相当低效的。另一个问题是回报轨迹可能变化很大,导致更新的波动。
总之,政策梯度存在重大缺陷:
- 样品低效— 样品仅使用一次。此后,更新策略,并使用新策略对另一个轨迹进行采样。由于采样通常很昂贵,这可能会令人望而却步。然而,在一次大的政策更新后,旧样本根本不再具有代表性。
- 不一致的策略更新— 策略更新往往会超过并错过奖励高峰,或者过早停止。特别是在神经网络结构中,消失和爆炸梯度是一个严重的问题。该算法可能无法从糟糕的更新中恢复。
- 高回报差异— 政策梯度是一种蒙特卡罗学习方法,考虑了完整的回报轨迹(即完整的一集)。这样的轨迹经常遭受高方差,阻碍了收敛。【这个问题可以通过添加一个评论家来解决,这超出了本文的范围】
自然政策梯度
不熟悉自然政策梯度?先看这篇文章:
Amari (1998)和 Kakade,S. M. (2001)提出的自然政策梯度解决了选择适当学习率的问题。为此,该方法嵌入了二阶导数,量化了更新权重时梯度的敏感度。
Kullback-Leibner (KL)背离衡量政策 π_θ 因更新而改变的程度。请记住,策略是由概率分布表示的,它决定了选择给定操作的概率。 KL 散度定义如下:

KL-divergence,量化权重更新前后政策之间的距离。
现在,如果我们将更新的散度限制为不超过 ϵ,我们可以使用以下方案来更新权重:

权重更新方案,最大 KL 偏差由ϵ设定上限
上述方案实际上是不可用的,因为 RL 使用采样观测值。为了将发散考虑在内,使用了修改后的目标函数上的泰勒展开。我们对目标执行一阶展开,并对 KL 散度执行二阶展开。经过一些数学努力,下面的更新公式出现了:

自然政策梯度的权重更新公式
总之,难以确定的学习速率 α 已经被一个动态学习速率所取代,这取决于目标函数的局部灵敏度。这种灵敏度由费希尔信息矩阵 F(θ) 表示。当部署时,自然梯度更新公式允许在不敏感区域(例如,平台)进行较大的更新,而在弯曲区域(例如,接近奖励峰值)进行较小的更新。
理论上,自然梯度允许以适当的步长确保稳定的更新。由于限制了策略改变的幅度,我们也可以多次重复使用样本,提高样本效率。然而,在现实中,自然梯度表现出许多缺点:
- 对费希尔矩阵 F 求逆比较繁琐——矩阵求逆是一种运算复杂度为 O(N ) 的运算。鉴于神经网络通常包含数千个参数,这通常是禁止的。此外,由于所做的近似引入的数值不稳定性,反演可能会失败。
- KL 约束可能不满足— 泰勒展开式只是理论目标函数的近似值。因此,分析得出的步长通常太大,并且仍然表现出过冲行为。
- 政策改进未得到验证 —尽管自然梯度提供了所有令人放心的理论保证,但该方法并不检查更新是否实际产生了改进。同样,由于近似值,情况可能并非如此。
信任区域策略优化(TRPO)
作为 PPO 的直接前身,补上 TRPO 可能会不错:
**
自 1990 年代后期自然政策梯度出现以来,已经取得了一些进展。这些最佳实践已经被结合在流行的 TRPO 算法中,由舒尔曼等人于 2015 年推出。
为了简洁起见,我将再次省略许多细节。不过,有两个等式至关重要。首先,替代优势𝓛_π_θ(π_θ+δθ)——根植于重要性抽样——反映了更新的预期优势:

替代优势𝓛的定义,使用来自策略π_θ的样本描述更新策略π_θ+δθ的预期优势
第二,我们可以通过更新前后策略之间的最坏情况 KL-divergence 来限制替代优势的近似误差。这产生了以下不等式:

描述更新后目标值最小变化的不等式。右手边表示下限,考虑到对发散误差进行了修正的替代优势。
罚分 C 已经过分析推导,产生了一个相当严重的发散罚分。如果我们遵循理论上的 TRPO 方法,我们会有非常小的更新和缓慢的收敛。
因此,实际的 TRPO 算法在一些关键点上不同于理论基础。区分 TRPO 的理论模型 (基于惩罚)和实际实现(基于约束)是很重要的——这种区别在后面讨论 PPO 时是相关的。
具体而言,TRPO 的实际实施通过以下三个调整改进了自然梯度:
- 共轭梯度法—自然政策梯度计算只需要乘积f^-1∇logπ_θ(x)我们实际上对逆矩阵本身不感兴趣。共轭梯度可以比矩阵求逆更快地逼近这个乘积,从而大大加快了更新过程。
- 线搜索 —由于简化,为自然政策梯度导出的分析步长可能违反 KL-divergence 上限。TRPO 从最初建议的步长开始,但是在更新之后测量是否不违反发散约束。如果是,它迭代地和指数地收缩信赖域(使用收缩参数 α^j ),直到满足散度约束。
- 改进检查——最终,我们希望我们的更新能够改进政策。TRPO 通过在接受替代损失(𝓛(θ)之前,实际验证替代损失在更新之后是否有所改善,来认可这种观点。回想一下,由于近似值,理论保证不再成立。
后两种改进在下面的算法片段中都可以看到:

线搜索算法。该算法确保更新不会违反发散约束(使用指数衰减α^j),并且更新不会降低策略的性能[来源: Joshua Achuam,UC Berkeley
TRPO 解决了许多与自然政策梯度相关的问题,并获得了 RL 群体的广泛采用。然而,仍然存在一些缺点,特别是:
- 无法处理大型参数矩阵 —尽管部署了共轭梯度法,TRPO 仍然难以处理大型 Fisher 矩阵,即使它们不需要求逆。不幸的是,深度神经网络包含许多参数,精确逼近 F 需要大量样本。
- 二阶优化速度慢—TRPO 的实际实现是基于约束的,需要计算前述的 Fisher 矩阵,这大大降低了更新过程的速度。此外,我们不能利用(一阶)随机梯度优化器,如亚当。
- TRPO 很复杂 — TRPO 很难解释、实施和调试。当培训没有产生预期的结果时,很难确定如何提高绩效。因此,它不是最用户友好的 RL 算法。
近似策略优化(PPO)
2017 年,舒尔曼等人在 TRPO 基础上引入了近似政策优化(PPO)。给定所有的背景信息,PPO 比以前的算法做得更好和不同的是什么?
PPO 有两个主要的变种要讨论(都在 2017 年的论文中介绍过):PPO 罚分和 PPO 夹分。我们将首先看一看基于惩罚的变体,它在概念上最接近于 TRPO。
PPO —具有自适应 KL 惩罚的变体
TRPO 的理论基础将 KL 偏差封装为软惩罚——如前一节所述——然而实际实现是基于约束的。理论上的 TRPO 通过分析得出一个与偏离相乘的罚值,然而在实践中,这个罚值通常过于严格,只能产生非常小的更新。因此,问题是如何可靠地确定缩放参数β以允许有意义的更新,同时避免过度漂移:

该约束的问题是很难确定适用于多个问题设置的 β 的单个值。事实上,即使是同一个问题,特征也可能随着时间而改变。由于我们没有对分歧设置硬性限制,有时我们会比其他时候更严厉地处罚。
PPO 以务实的方式解决了这个问题。它设置了一个‘目标散度’δ;我们希望我们的更新在这个分歧的附近。目标差异应该大到足以实质性地改变策略,但又小到足以使更新稳定。
每次更新后,PPO 都会检查更新的大小。如果实现的偏离超过目标偏离超过 1.5,对于下一次迭代,我们通过加倍 β 来更严厉地惩罚偏离。反过来,如果更新太小,我们就将 β 减半,有效地扩展了信任区域。你可以在这里看到 TRPO 的线搜索的一些相似之处,然而 PPO 的搜索是双向的。
如您所料,基于超过某个目标阈值的 1.5 倍将β加倍和减半并不是精心证明的结果;这些值是启发式地确定的。我们将不时地违反约束,但是通过调整β可以相当快地纠正它。根据经验,PPO 似乎对数值设置相当不敏感。总之,为了实用主义,我们牺牲了一些数学的严谨性。
PPO 的基于惩罚的变体概述如下。请注意,它非常类似于 TRPO 的理论模型,但使用了一些启发式近似,使其也可实际应用。
由于 PPO 更新相当小,在一定程度上重复使用生成的样本是可能的,用重要性采样项来校正更新的动作概率。PPO 执行 K 个更新步骤;当超过某个偏离阈值时,实现通常部署早期停止。

PPO,罚变。目标函数嵌入了对 KL 散度的惩罚。基于相对于目标散度的测量散度动态更新相应的权重β_ k[来源:舒尔曼等人 2017
PPO 比自然梯度和 TRPO 更容易实现,不需要分析结果、基于约束的程序或二阶导数。相反,我们可以使用流行的随机梯度下降算法(如 ADAM)来执行更新,并在更新过大或过小时调整惩罚。
总之, PPO 比 TRPO 更容易合作,同时也更有竞争力,甚至经常超越它。让我们看看我们是否能做得更好。
PPO —具有剪切物镜的变体
修剪过的 PPO 是目前流行的变体,也是我们在讨论“PPO”时通常所指的。它通常优于基于惩罚的变体,并且更易于实现。
我们不再费心去改变惩罚,而是简单地限制政策可以改变的范围。由于剪辑范围之外的更新所获得的优势不用于更新目的,我们提供了保持相对接近现有策略的激励。目标函数仍然依赖于代理优势,但现在看起来是这样的:

这里, ρ_t 是重要抽样比:

项 (1-ϵ)⋅A 和 (1+ϵ)⋅A )不依赖于 θ ,因此产生的梯度为 0。因此,可信区域之外的样本被有效地丢弃,阻止过大的更新。因此,我们没有明确地约束策略更新本身,而是简单地忽略了过度偏离策略所带来的好处。和以前一样,我们可以简单地使用 ADAM 这样的优化器来执行更新。

在 PPO 的这种变体中,替代优势被削减了。如果更新后的策略与原始策略的偏差超过ϵ,则样本产生的梯度为 0。这种机制避免了策略的过大更新,将其保留在可信区域内[图片由舒尔曼等人 2017 ]
‘为什么是最小运算符?’,你可能会问。为什么不直接剪辑?问得好。在积极优势的情况下,当更新的政策与旧政策偏离太大时,我们总是会进行剪辑。赢的时候,我们倾向于谨慎。失败时,有一种情况需要特别注意:优势 a 为负,1+ϵ为负。换句话说,我们进行了一次大规模更新,增加了采取更糟糕行动的可能性。在这种情况下,无界优势 r_t ⋅ A 比削波优势 (1+ϵ) ⋅ A 低。当我们取两者的最小值时,我们使用无界信号来执行更新,允许更大的更新来纠正我们的错误更新。这种形式的回溯是一种非常漂亮的修正机制,允许快速撤销错误方向的更新。
有六种具有相应更新行为的重要情况(即,策略应该不同且优势非零)。为了理解 PPO,最好花点时间来理解下表中总结的案例:

PPO 剪切目标的六个非平凡案例的总结。修剪后,梯度为零,无助于训练,不利于大规模政策更新[作者表格,基于 Bick,2021 ]
为了实现期望的行为,我们应该调整 ϵ ,作为对 KL 发散的隐式限制。根据经验, ϵ=0.1 和 ϵ=0.2 是运行良好的值。这些值与自然政策梯度的理论基础有些偏离(该理论建立在假设 π_θ=π_θ_k 的局部近似基础上),但从经验上看,它完成了工作。
完整的算法详述如下。

PPO,剪切的客观变体。新旧政策之间的最大差异被缩小了。如果策略偏离得太远,它们的样本就不会用于更新,从而阻碍了大规模更新。注意,文中用 ρ_t 代替 r_t 来描述重要性抽样比【来源:舒尔曼等 2017 】
PPO2?
你可能听说过 PPO2 这个术语。我们还需要更多的数学吗?
幸运的是,PPO2 只是 Open AI 发布的算法的更新版本。PPO2 是一个矢量化环境的实现,它针对 GPU 进行了优化,并更好地支持并行训练。它还有许多其他不同之处(例如,优势被自动规范化,值函数也被裁剪),但使用了本文中概述的相同数学基础。如果您计划直接使用 OpenAI 实现,只需记住 PPO 已经过时,现在应该使用 PPO2。
结束语
自 2017 年推出以来,PPO 已迅速成为连续控制问题中的最佳算法。五年对于机器学习来说是一段很长的时间,但在基准问题(主要是经典控制问题和雅达利游戏)的类别中,它仍然具有很高的竞争力。
看来 PPO 在速度、谨慎和可用性之间取得了恰当的平衡。尽管缺乏自然梯度和 TRPO 所具有的理论保证和数学技巧,PPO 比其竞争对手收敛得更快更好。有些矛盾的是,在深度强化学习中,简单似乎是有回报的。**
今天的强化学习还没做完吗?看看下面的文章吧!
**
进一步阅读
论文
- 阿马里,S. I. (1998 年)。自然梯度在学习中很有效。 神经计算, 10 (2),251–276。
- 比克博士(2021)。https://fse.studenttheses.ub.rug.nl/25709/1/mAI_2021_BickD.pdf(硕士论文)对最近的政策优化提供一个连贯的自足的解释。格罗宁根国立大学。
- Kakade,S. M. (2001 年)。自然的政策梯度。 神经信息处理系统的进展, 14 。
- 舒尔曼,j .,莱文,s .,阿贝耳,p .,乔丹,m .,莫里茨,P. (2015)。信任区域策略优化。参加机器学习国际会议。**
- 舒尔曼,j .,沃尔斯基,f .,德里瓦尔,p .,拉德福德,a .,&克里莫夫,O. (2017)。近似策略优化算法。 arXiv 预印本 arXiv:1707.06347 。
讲座幻灯片
- 高级政策梯度(CS 285)。加州大学柏克莱分校。
- Achiam,J. (2017 年)。高级政策梯度方法。加州大学伯克利分校。
- 自然政策梯度,TRPO,PPO (CMU 10703)。卡内基梅隆。
- 舒尔曼,J. (2017)。高级政策梯度方法:
自然梯度、TRPO 等。 OpenAI。
网站
PSP py:Python 中的倾向评分匹配——以及为什么需要它
原文:https://towardsdatascience.com/psmpy-propensity-score-matching-in-python-a3e0cd4d2631

作者图片
当我们试图回答这样的问题时:
“服用β受体阻滞剂能降低心脏病发作的风险吗?”
“吸烟会致癌吗?”
“小学的 STEM 项目鼓励更多的学生进入科学领域吗?”
我们经常尝试使用随机对照试验(RCT)来回答问题,我们用它来进行 A|B 测试。我们试图控制可能影响这一结果的变量的种类和数量,以确定某些干预(或缺乏干预)是否与结果有潜在的因果联系!在上面提出的第三个问题中,你可能想控制社会经济地位(SES),邻里关系或父母教育。在药物治疗的情况下,我们可能要考虑:年龄、性别、种族、潜在/先存健康状况等。输入 RCTs
RCT 是前瞻性的(计划好的,未来会有结果),患者通常是匹配的(共享相似的特征——SES、年龄等)。)对于我们想要控制的所有变量,1:1,其中 1 接受干预,1 不接受。

作者图片
随机对照试验的问题:
- 昂贵的美元
- 不要扩展(随着我们想要控制的变量数量的增加)
- 患者/参与者退出
- 可能最终不会产生有意义的结果
倾向评分匹配(PSM)是一种使用回顾性数据的统计技术,试图完成通常发生在 RCT 中的任务。它是以观察到的基线协变量为条件的治疗分配概率:

因此,它的工作原理如下:
- 已经收集的大量患者/参与者数据(历史数据还记得吗?)—年龄、性别、SES、体重。这些是协变量。通常我们的协变量是潜在的混杂因素,可能会使我们的结果产生偏差。干预=服药(是/否)并跟进我们的结果=心脏病发作(是/否)。想象一下电子健康记录数据!
- 试着找一个与服用药物的 X 夫人“匹配”的人,她有许多与她相同的协变量,但没有服用药物【2】。我们需要为尽可能多的人这样做,以建立一个治疗/未治疗的队列。
- 我们对我们的人口进行了一系列逻辑回归,其中所有的“预测”变量都是我们的协变量(年龄、性别、体重),我们试图预测我们的干预。与大多数机器学习任务不同,我们在这里不计算准确性。我们感兴趣的是预测的可能性。使用索引(参与者 id ),我们知道谁来自哪个干预组、治疗组或对照组。
- 使用计算出的概率,我们从对立的组中找到最接近的匹配。例如,X 夫人的概率(倾向)得分为 0.4,并接受了药物治疗,而 Y 先生没有接受药物治疗,其概率得分为 0.42。k-最近邻通常用于寻找这些匹配,我们可以进行 1:1 匹配或 1:多匹配(允许重复)。如果概率上的差异被认为太大,匹配可能被排除。
- 效应大小是我们能够控制所有这些协变量的程度的量度[4]。我们希望它们越小越好,这样结果的任何变化都可以归因于所进行的干预,给我们一个潜在的因果联系。所以我们要计算匹配前后的这些来了解我们的数据!
- 匹配后,我们确定了研究人群中的一个亚组,我们可以研究我们的干预如何影响我们的结果。虽然干预是二元的,但是结果可以是连续的,也可以是二元的,我们只需要正确的统计检验。
PSM 的优势:
- 快的
- 便宜——这是一个数学匹配
- 无患者退出问题(自历史数据以来)
- 尺度——可以有任意多的协变量!
- 轻松地重复这个过程,并随心所欲地改变变量(假设我们有数据支持这一努力)
PSM 的缺点:
- 数学匹配可能不是流行病学匹配
数学匹配意味着接受药物治疗的 68 岁女性 X 夫人不一定与未接受药物治疗的 68 岁女性匹配。使用 PSM,如果模型中具有更大权重的其他协变量相似,她可能与一名 55 岁男性匹配。
虽然不如随机对照试验准确,但随着更多的数据被转移到数字领域,PSM 可以提供对现实世界设置的见解。它可以自动检测有意义的结果,没有自动 RCT 的麻烦或成本。
由于目前 python 中还没有一个广泛适用且性能良好的 PSM 库,所以输入— PsmPy !【https://pypi.org/project/psmpy/
该库的功能包括:
- 评估前后平衡的附加绘图功能
- 更加模块化、用户指定的匹配流程
- 能够定义一对一或一对多匹配
装置
PsmPy 在 pypi.org 有售,可以通过终端中的 pip 安装:
$ pip install psmpy
数据准备
读入你的数据。
# import other relevant libraries (that you want) # set the figure shape and size of outputs (optional)
sns.set(rc={'figure.figsize':(10,8)}, font_scale = 1.3)# read in your data
data = pd.read_csv(path)
导入 psmpy 类和函数
将 PsmPy 库导入 python 和其他两个支持函数:
CohenD 计算效果大小,并可用于计算匹配前后变量施加的效果大小。这个数字越接近 0,我们就越能有效地控制协变量
from psmpy import PsmPy
from psmpy.functions import cohenD
from psmpy.plotting import *
实例化 PsmPy 类
psm = PsmPy(df, treatment='treatment', indx='pat_id', exclude = [])
注意:
PsmPy-上课。它将使用数据集中的所有协变量,除非在exclude参数中正式排除。df-传递给类的数据帧exclude-(可选)参数,将忽略模型拟合过程中传递给它的任何协变量(列)。这将是一个字符串列表。没有必要在这里传递唯一索引列。indx-必需参数,引用数据集中每个案例的唯一 ID 号。
预测分数
计算逻辑倾向得分/逻辑:
psm.logistic_ps(balance = True)
注意:
balance-逻辑回归是否以平衡方式运行,默认值= True。
在数据中经常存在显著的类别不平衡。这将被自动检测。我们通过在调用psm.logistic_ps()时设置balance=True来说明这一点。这告诉PsmPy在拟合逻辑回归模型时创建平衡样本。这将计算每个条目的逻辑倾向得分和 logits。你可以这样想,例如:
较小类别 n=10
较大类别 n=30
psm.logistic_ps(balance=True)导致发生 3 次逻辑回归(较小类别的 n=10,较大类别的 n=10),然后重复 3 次,直到较大类别的所有条目都有分数。它们是主要类别的重复条目的平均值。
psm.logistic_ps(balance=False)导致发生符合 n=10 和 n=30(不平衡)的单一回归
要查看数据框架中的概率/逻辑:
psm.predicted_data
匹配算法-版本 1
该方法将创建一系列 1:1 或 1:多的匹配(因为替换是假的,所以它是多的),因此仍然是 1:1 匹配,但是来自较大类的一些条目被重复。
执行 KNN 匹配
psm.knn_matched(matcher='propensity_logit', replacement=False, caliper=None)
注意:
matcher-propensity_logit(默认)并且在上一步中生成的替代选项是propensity_score,指定匹配将要进行的自变量replacement-False(默认),决定替换与否,当替换为假匹配时,1:1 匹配caliper-None(默认),用户可以指定相对于标准的卡尺尺寸。对照样本的标准差,限制符合匹配条件的邻居
匹配算法-版本 2
此方法将创建一系列 1:1 或 1:n 的匹配,其中“n”是将较小的类与较大的类进行匹配的次数。
执行 KNN 匹配:
psm.knn_matched_12n(matcher='propensity_logit', how_many=2)
注意:
matcher-propensity_logit(缺省值),上一步生成的备选项是propensity_score,指定匹配将要进行的自变量how_many-1(默认-1:1 匹配),确定匹配是以 1:1 还是 1:n 的方式进行,其中“n”是您希望从较小类别(治疗类别)匹配到较大类别的唯一次数

作者图片
图形输出
绘制倾向得分或倾向对数
并排绘制两组的倾向分数(或对数)分布图。
psm.plot_match(Title='Side by side matched controls', Ylabel='Number of patients', Xlabel= 'Propensity logit', names = ['treatment', 'control'], save=True)
注意:
title--"并排匹配控件"(默认),创建绘图标题Ylabel--"患者数量"(默认),字符串,y 轴标签Xlabel--‘倾向逻辑’(默认),字符串,x 轴标签names- '治疗','控制',图例字符串列表save- False(默认),如果为 True,则将生成的图形保存到当前工作目录
使用心脏病健康指标数据集[3]证明了这一点。

作者图片
在这里,我们可以看到治疗组和对照组相互对照(匹配后)。这两个分布越相似,我们的匹配就越好。
绘制效果尺寸图
psm.effect_size_plot(save=False)
注意:
save- False(默认),如果为 True,则将生成的图形保存到当前工作目录

作者图片
我们可以使用上面的代码绘制效果大小。这里我们可以看到影响大小(使用 Cohen 的 D 计算),这是一个修改的卡方检验。这在群组匹配之前和之后对每个变量执行。
额外属性
用户可用的其他属性:
原始数据帧
psm.df_matched
df_matched-返回数据框架中所有案例的计算倾向得分和倾向逻辑
匹配的 id
psm.matched_ids
matched_ids-从次要类别返回指数的数据帧,并从主要类别 psm 返回相关的匹配指数。
注意:不是所有的匹配都是唯一的,如果replacement=True

作者图片
每个变量的效果大小
psm.effect_size
effect_size-返回包含“变量”、“匹配”(之前或之后)和“效果大小”列的数据帧
注:Cohen [4]描述了小、中和大效应大小的阈值。其中相对尺寸效应分为:小≤ 0.2,中≤ 0.5,大≤0.8。

作者图片
理想情况下,“匹配后”变量产生的效果大小应该小于匹配前。这些效应大小越接近 0,我们感兴趣的结果就越不依赖于协变量。
目前在 Python 环境中还不存在易于使用的 PSM 技术。R 环境[5]中有 package MatchIt 和 SPSS 中的另一个备选项。 MatchIt 软件包提供了一些基本的绘图功能,然而,PSP py是开源的(与 SPSS 不同),提供了易于使用/定制的视觉愉悦的图形,以确定匹配有多“好”,以及几个类属性,以使最终用户能够获得算法计算的原始数据。
引用这部作品!: )
A.Kline,Y. Luo,PSP py:用 Python 编写的回顾性队列匹配包,(2022 年接受)[1]
联系预印本!
此外,如果你喜欢看到这样的文章,并希望无限制地访问我的文章和所有由 Medium 提供的文章,请考虑使用下面的我的推荐链接注册。会员费为 5 美元/月;我赚一小笔佣金,这反过来有助于推动更多的内容和文章!
https://medium.com/@askline1/membership
参考
[1] A. Kline,Y. Luo,PSP py:Python 中的回顾性队列匹配包,(2022 年在接受),doi:10.1109/embc 48229 . 2022 . 9871333 .
【2】Paul r . Rosenbaum&Donald b . Rubin,“倾向评分在因果效应观察研究中的核心作用”,1983al,“MatchIt:参数化
因果推断的非参数预处理”,《统计软件学报》,第 42 卷第 8 期,2011
doi:10.18637/jss . v 042 . i08
地球科学中机器学习的公共数据集
原文:https://towardsdatascience.com/public-datasets-for-machine-learning-in-geoscience-cf880862300a
应用数据科学、机器学习和岩石物理技术的最佳开源地球科学数据集

多年来,测井和岩石物理数据集一直被锁在石油和天然气公司的服务器或政府服务器上,只有那些有适当访问权限的人才能访问。然而,最近几年出现了明显的向开放数据的转变,这使得大量的测井和现场数据变得公开可用且易于访问。
在实践机器学习、数据科学或岩石物理学时,课程中提供的数据通常会被净化,以使概念教学更容易。然而,当涉及到现实世界时,数据往往是杂乱的,包含错误和价值/特征可能会丢失。当你开始使用真实世界的数据进行练习时,你的技能将会得到检验,你的知识也会得到发展。
本文提供了一个主要网站列表,您可以在这些网站上免费下载测井数据等。数据集可以包含常规岩心分析数据、特殊岩心分析数据、地层压力数据、详细报告、地震数据,甚至完全开发的地质模型。
如果你更喜欢通过视频来了解这些库,请查看我的 YouTube 视频,其中我更详细地介绍了其中的 5 个数据集:
Equinor Volve 现场数据集

Equinor Volve 数据集网站。作者捕获的图像。
我们拥有的第一个数据集是 Equinor 在 2018 年发布的 Volve 数据集,作为一种通过提供完整和现实的案例研究来促进学习和研究的方式。Volve 油田位于挪威西海岸约 200 公里处,包含 10 年的数据。
该数据集包含大约 40,000 个文件,从测井数据到地质模型,对于任何希望从事岩石物理学、机器学习或数据科学的人来说,都是一座金矿。
一旦进入网站,你必须注册才能访问数据,但是一旦你进入网站,你就可以很容易地下载你需要的数据。
请点击下面的链接查看:
NLOG —荷兰石油和天然气门户网站

荷兰石油和天然气门户网站。作者捕获的图像。
荷兰有数千口油井的海上和陆上油井数据。dat 包含井眼测井数据、地震数据和生产数据的混合物。但是,找到您需要的数据可能很困难,您可能需要花一些时间在井中搜索。

NLOG 网站上显示如何访问测井数据的示例。作者捕获的图像。
无需注册即可通过以下方式访问数据:
- 如果荷兰语不是您的第一语言,点击网页页脚的英语,将语言更改为英语
- 导航到主菜单中的数据
- 选择钻孔
- 在出现的左侧菜单中,单击搜索,然后列出钻孔或选择钻孔地图
- 从这里,您应该能够找到您需要的数据
根据我使用的井,从 L05、L06 和 L07 开始是一个好的起点。
请点击下面的链接查看:
XEEK —强制机器学习预测岩性

XEEK Force 2020 机器学习竞赛。作者捕获的图像。
2020 年,XEEK 和 Force 举办了一场机器学习比赛,目的是从测井测量中预测岩性数据。该数据集包含 100 多口井,混合了测井测量和地质解释岩性。
这个数据集为理解如何应用基于监督分类的机器学习算法提供了一个很好的机会。
点击以下链接访问:
或者您可以通过以下链接直接访问 github 资源库:
https://github.com/bolgebrygg/Force-2020-Machine-Learning-competition
国家海洋石油信息管理系统

澳大利亚政府国家海洋石油信息管理系统(NOPIMS)的例子。作者捕获的图像。
澳大利亚政府发布了一个极好的数据库,其中包含澳大利亚近海的测井测量数据。这些数据很容易获取和搜索。
当你到达网站时,你会看到搜索框。如果您想浏览整个数据库,请将这些字段留空,然后按搜索。或者,如果您正在查找特定的数据类型,您可以从报告类型中选择它,然后按搜索。
您可以通过单击下面的链接来访问数据库:
COASTA 模型——碳酸盐测井和地震数据
COSTA 模型数据库最近由作者(Costa Gomes J、Geiger S 和 Arnold D)和 Heriot Watt 大学向公众发布。该数据库包含与碳酸盐岩层序相关的各种数据,包括测井测量、常规岩心分析和特殊岩心分析数据,以及静态和动态模型。
由于很难找到高质量的碳酸盐数据,这个数据集非常值得点击下面的链接查看。
堪萨斯地质调查局

堪萨斯地质调查局网站。作者捕获的图像。
堪萨斯地质调查局公布了大量数据,包括岩心图像、电缆测井(LAS 文件)、顶部和其他相关的油井数据。
如果你熟悉的话,你可以根据县、运营商或租约进行手动搜索。或者您可以通过选择其他在线信息下的选项下载测井 ascii (LAS)文件。这将允许您下载特定年份的 LAS 文件、包含测井数据的油井信息等。
你可以点击下面的链接查看:
1240 口挪威油井的孔隙度、渗透率、岩石类型岩心数据

挪威岩心数据库包含 1240 口井的数据。作者捕获的图像。
挪威石油管理局在 2018 年底发布了 1240 口井的岩心测量数据。已经尝试进行有意义的岩石类型描述,但是,这项工作似乎不完整,使用这些数据时应小心谨慎。
您可以通过单击下面的链接来访问它:
https://zenodo.org/record/4419060#.YiNGFO7P3kp
格罗宁根气田海燕地质模型

戈尔宁根气田海燕地质模型。
nederlandse Aardolie Maatschappij(NAM)开发并发布了荷兰格罗宁根气田的地质模型。
该模型是在 Petrel 2018 中开发的,包含许多开源软件包可以读取的 RESCUE 3D 网格数据。数据还包括测井数据和地震数据。
请点击下面的链接查看:
地下数据

数据地下网站,允许您搜索基于地质和地球科学的数据。作者捕获的图像。
地下数据已经创建了一个可搜索数据库,您可以在其中找到在线地下数据集。这些包括来自全球各地的测井和地震数据。
请点击下面的链接查看:
国际海洋钻探计划

国际钻井计划,开放原始资料获取测井数据。作者捕获的图像。
测井数据不仅是在石油和天然气勘探期间获得的,也是作为科学勘探和研究的一部分获得的。JRSO 数据库包含从科学研究任务中获得的各种测井数据(测井、岩心、高级岩心分析),非常值得查看。
点击下面的链接查看:
https://www.iodp.org/resources/access-data-and-samples
勘探地球物理学家协会(SEG)维基开放数据

SEG Wiki 包含地球物理数据集的链接。作者捕获的图像。
勘探地球物理学家协会(SEG) Wiki 包含大量地球物理数据集的链接,可以直接下载或请求下载。
您可以通过以下链接访问维基:
https://wiki.seg.org/wiki/Open_data
感谢阅读!
如果你觉得这篇文章有用,请随时查看我的其他文章,这些文章从不同的角度研究了 Python 和测井数据。你也可以在 GitHub 找到我在这篇文章和其他文章中使用的代码。
如果你想联系我,你可以在LinkedIn或者我的 网站 找到我。
有兴趣了解更多关于 python 和测井数据或岩石物理学的知识吗?跟我上 中 。
如果你喜欢阅读这些教程,并且想支持我这个作家和创作者,那么请考虑报名成为一名 Medium 会员。一个月 5 美元,你就可以无限制地阅读数千篇各种主题的文章。如果您使用 我的链接注册,我将为您赚取一小笔佣金,无需额外费用!
*https://andymcdonaldgeo.medium.com/membership *
在云中发布您的 Streamlit 应用
原文:https://towardsdatascience.com/publish-your-streamlit-apps-in-the-cloud-3ac5a5fe3d51
使用这些免费工具分享您的 Streamlit 应用程序:Streamlit Cloud、Github 和 VSCode

在 Streamlit Cloud 上运行的示例 Streamlit 应用程序—截图和作者提供的应用程序
您已经使用 Streamlit 创建了一个出色的数据科学应用程序,并且很自然地希望与世界分享它。
这很好,因为 Streamlit Cloud 让你免费发布无限数量的公共应用。如果你愿意,你可以拥有一个完整的应用程序库,并且创建和发布它们所需的所有工具都是免费的。
在本文中,我们将介绍发布首个 Streamlit 应用程序所需的步骤。
但首先你需要三样东西:一个 Streamlit 云账户,一个每个应用的 Github 存储库,为了让你自己轻松,一个可以很好地使用 Github 的编辑器(在本文中我将使用 VSCode)。
发布 Streamlit 应用程序需要四个步骤:
- 开发您的应用并在本地测试
- 为应用程序创建一个 Github 存储库
- 将本地 app 推送到 Github
- 在 Streamlit Cloud 中创建一个新应用,并将其连接到回购
在所有工作开始之前,还需要做一些设置,但是一旦完成,发布和更新就很简单了。
开源代码库
我们将从 Github 的中间开始。你需要建立一个账户,所以先去 GitHub 网站注册。开始时你会被要求经历一些困难,但没什么太难的——只要按照说明做就行了。
有不同的 Github 帐户,但对于我们的目的,我们需要的是一个基本的免费帐户。
一旦你有一个帐户,你可以创建你的第一个仓库。点击绿色的创建存储库按钮或启动项目按钮。

作者截图
这将弹出一个表格供您填写:

作者截图
给 repo 一个名称(上图中显示为“temp ”)和描述,并确保选择了 Public 选项。
然后会要求您指定一些选项。创建一个自述文件是一个好主意,您可以在其中描述回购的目的及其内容,因此选择该文件,它将自动为您创建。
gitignore 是一种指示 Git 忽略您不想包含在回购中的特定类型文件的方式——暂时忘掉它,我们不会使用它。
最后,选择一个许可证并点击创建存储库。

作者截图
你知道有一个地方保存你的 Streamlit 应用程序的代码,任何人都可以阅读,最重要的是通过 Streamlit 云。
现在,您已经有了一个可以下载到本地机器上的基本回购协议。为此,我们克隆了回购协议。单击 code 按钮并复制 url —稍后您将在 VSCode 中使用它。

在 GitHub 中克隆存储库—图片由作者提供
虚拟代码
现在让我们转向编辑。如果您还没有 VSCode,那么下载并安装它,以及 Python 扩展(我假设您已经有 Python 了)。
打开 VSCode,关闭任何已经打开的文件或文件夹,并选择源代码控制图标。这使您可以选择打开一个现有的 Git repo 或克隆一个新的。我们要做的是后者。

在 VSCode 中创建存储库—按作者排序的图像
点击克隆库,你会被要求提供一个 URL——这是我们刚才从 GitHub 网页上复制的,所以把它粘贴进去。
点击回车键,系统会询问你是否想打开回购——你当然想。然后它可能会问你是否信任回购协议的作者——同样,你信任(你信任自己,对吗?).
一两秒钟后,您应该会看到这样的内容:

VSCode 中的一个新存储库—作者图片
现在我们准备创建一个新的应用程序。
在这里,我创建了一个简单的 Streamlit 应用程序,并准备从终端窗口运行它。

一款新应用——作者截图
它运行正常:

示例应用程序—作者截图
所以现在我们准备把它上传到我们新的 Github repo。
请注意,在 VSCode 中,源代码管理图标现在有了一个通知—这是因为发生了一些变化。再次打开源代码控制图标,您可以看到改变了列表 stapp1.py 。
关于 Git 的简短说明
Git 是 Github 使用的源代码控制系统,
从根本上说,它跟踪你在代码中所做的任何更改——编辑一个文件,Git 就知道了。但是为了更新一个项目,你需要提交变更。提交有两个部分,首先,您将想要提交的文件添加到提交中——这被称为 staging——然后,您实际上用一条短消息提交更改(通常描述您所做的更改)。通过这种方式,Git 保存了一个代码库,其中包含最新版本的最新文件以及所做更改的历史记录。
一旦你的代码版本被提交,它就可以被推到一个像 GitHub 这样的远程仓库,并与全世界(或你的团队)共享。
相反,一个版本的代码可以从远程存储库被拉到本地机器上进行处理,然后当你完成后被推回远程存储库。
回到应用程序
我们现在可以通过输入提交消息并点击Ctrl+Enter来提交我们的更改。

作者截图
现在,由于我们还没有转移变更,VSCode 会询问您是否想要自动转移它们,以及您是否应该总是这样做(或者从不这样做)。我从不费心准备事情,因为我总是希望我的所有更改都在提交中——如果你也想这样,那么选择是或总是。如果您选择从不,您将不得不手动进行分段。

自动暂存—按作者分类的图像
您现在应该注意到通知已经消失了。
现在,我们做这一切的全部目的是将我们的代码保存在 GitHub 上,所以已经做了更改,我们现在需要将它推到那里。

作者截图
只需点击蓝色按钮,更改将被推送到您的 Github repo。
第一次做这个 GitHub 需要知道你被授权推送你的回购。第一步:

作者截图
这将打开一个浏览器窗口,并要求您以通常的方式登录 GitHub,以确认 VSCode 可以使用您的帐户。
当你完成后,本地回购将被推送到 GitHub。
成功!
我们已经创建了我们的应用程序,并将其推送到 Github,现在我们所要做的就是将它告诉 Streamlit Cloud!
流线云
有几个计划你可以报名参加。我们要的是首发计划。这是免费的,让你发布无限数量的公共 Streamlit 应用程序(和一个私人应用程序)。
因此,请访问 Streamlit Cloud 网页,向下滚动直到找到注册部分。当我注册时,我不得不使用我的 Google 或 Github 帐户,这对我来说没问题,但我相信其他注册选项也在酝酿中。我注册了 Github,当然,Streamlit Cloud 需要能够访问你的 Github 库才能访问你的应用。
一旦你理清了你的 Streamlit 云账户,剩下的就一帆风顺了。

作者截图
点击新应用,系统会询问您应用的详细信息:

作者截图
键入存储库路径和 Streamlit 应用程序文件的名称,然后单击 Deploy (分支保持为“main”)。
Streamlit 现在似乎正在“烘烤”您的应用:

作者截图
右边的面板告诉你实际上正在发生什么,而左边的图标在各种食物之间循环(上面的那个应该是土豆)。
过了一会儿,你会得到一个卡通气球来庆祝你的应用程序已经可以使用了,它会适时地出现在屏幕上。
就是这样。您可以使用任意数量的公共应用程序浏览。唯一真正的限制是,如果你的应用程序没有收到太多流量,Streamlit 会让它休眠,以免浪费资源。当睡眠时,应用程序仍然可用,但在运行之前,它必须再次通过“烘焙”过程。Streamlit 将向你发送一封友好的电子邮件,告诉你他们何时会这样做,并建议你,如果你想,你可以简单地访问你的应用程序,以防止它打瞌睡。
因此,我们已经看到使用完全免费的工具来发布您的 Streamlit 应用程序是多么简单。我希望它是有用的,我将很快在云中看到你的应用程序。如果你这样做了,请在下面留下评论和链接。你可以在这里找到我的一些简单的努力。
一如既往地感谢你的阅读,如果你想知道我何时发表新文章,请考虑注册下面的电子邮件提醒或订阅我在子栈上的免费时事通讯。
如果你还不是一个媒体订阅者,那就注册吧,这样你就可以每月花 5 美元阅读尽可能多的文章。在这里注册,我将赚取一小笔佣金。
出版是强大的,因为它是范围和写作决策的催化剂
《可解释的机器学习》一书的作者 Christoph Molnar 的访谈
一系列采访强调了作家在数据科学领域的出色工作以及他们的写作之路。

照片由克里斯托夫·莫尔纳尔提供
“如果你在书架上没有看到你想要的书,那就写出来。”——贝芙莉·克莱瑞
为了将这种引人注目的工作推向前沿,我去年开始了一系列采访。在 第一季 中,我展示了来自知名数据科学家和 Kaggle 大师的故事,他们分享了他们的旅程、灵感和成就。第二季,我在采访书籍作者。作为一名作家,我非常尊重写书的人。一篇写得很好的文章需要大量的时间、精力和耐心,而为一本书复制同样的内容绝非易事。因此,本期访谈将揭示数据科学领域一些知名作者的故事。
见见作者:Christoph Molnar
Christoph Molnar 是一位机器学习专家,也是一位拥有统计学背景的独立作者。他在慕尼黑获得了学士和硕士学位,目前正在攻读可解释机器学习的博士学位。此前,Christoph 在金融行业的一家初创公司担任数据科学家,从事开发机器学习模型的工作。后来,他担任了一个更传统的角色,在一家风湿病患者登记处担任统计学家,与风湿病学家一起研究某些药物的效果。
如果你曾经想开始可解释的机器学习,你可能会挑选的第一本书是 Christoph 的 可解释的机器学习 。语言的清晰,现实的例子,以及将复杂的理论分解成更简单易懂的部分的艺术是这本书众多 USPs 中的几个。这本书的第二版最近已经发行,如果你想得到它,你可以在这里找到所有的信息。
https://christophm.github.io/interpretable-ml-book/
问:这本书的想法是如何产生的?
克里斯托夫 :在苏黎士,我做兼职,每个周五都在学习机器学习的新东西。最初,我上了一门关于深度学习的课程。然后,我开始看研究论文。我设法找到了著名的 LIME paper,它提出了一种解释黑盒机器学习模型预测的方法。因为我的统计学背景,我一直认为机器学习缺乏可解释性,所以我真的很喜欢论文的想法。

我被激励去学习更多,并开始寻找其他的解释方法,比如博客文章和书籍。然而,我没有找到太多关于机器学习可解释性的材料。我决定看论文,把方法总结成一本书。这成了我新的“星期五”计划。我在网上免费发布了这本书的进展版本,并得到了大量的反馈,鼓励我将它变成一本全面的书。
问:你能为读者总结一下这本书的要点吗?
Christoph :可解释的机器学习涵盖了解释机器学习模型的各种方法。每章涵盖一种解释模型的方式。我希望这本书在很长一段时间内都是相关的,所以我主要讲述了所谓的模型不可知的方法。这些适用于任何机器学习模型。此类技术的一些著名示例有:置换特征重要性、Shapley 值、LIME、累积效应图等。但由于神经网络在任何地方都被广泛使用,因此一大部分也涵盖了专门解决深度学习的解释方法。
问: 你认为这本书的目标读者是谁?
Christoph :可解释的机器学习是为每一个想学习如何解释机器学习模型的人准备的。我知道许多建立预测模型的数据科学家都是这本书的读者。然后是机器学习的学生和老师,技术经理,还有很多其他人。
问:在你看来,充分利用这本书的最好方法是什么?
克里斯托夫:我曾经认为我的读者会根据他们想学的方法从一章跳到另一章。就像一本参考书。我很惊讶地得知,许多人仍然从头到尾地阅读它。

如果你是机器学习可解释性的新手,阅读可解释性机器学习会给你一个极好的主题概述。如果您目前正在研究一个预测模型,您可以将这本书作为参考。例如,这本书将允许你选择对你的问题有意义的解释方法。但是您也可以回顾每种解释方法的局限性和问题。
问: 对于一个刚刚起步的新作家,你有什么建议?
克里斯托夫 :早发表,勤发表。出版是强大的。它是范围和写作决策的催化剂,有助于获得早期读者的反馈,是良好的营销,并使写作过程更像是与读者的对话。
问: (在技术或非技术空间)你最喜欢的作者是谁?
克里斯托夫:我喜欢布兰登·桑德森的奇幻小说。
👉你期待与 Christoph 的交流吗?跟着他上 推特 。
👉阅读本系列的其他访谈:
将您的 ML 模型从服务器中拉出:数据库解决方案
何时将模型放入数据库,如何放置,为什么

本杰明·雷曼在 Unsplash 上的照片
在上一篇文章中,我们看到了您希望使用 Streamlit 和 Gradio 等工具来快速部署和部署许多版本的机器学习(ML)应用程序的一个极好的理由。
我们看到了服务器模型架构的优势,以及为什么您在构建原型时一定要走这条路。这是从信任的测试人员的私人圈子中获得快速反馈和评估你的产品想法的市场可行性的最简单的方法。
然而,我们的结论是,当进入生产阶段时,您需要重新考虑您的设计,并将您的 ML 模型从您的应用服务器中取出。有几个问题,比如编程语言、不同的扩展需求和独立的更新周期,使得服务器架构模型方法对于生产来说不是一个好主意。
那么我们能做什么呢?如果我们重新查看简单的 web 应用程序架构图,我们会看到有三个选项:
- 将您的模型放入数据库中
- 将您的模型放入它自己的推理服务器
- 将您的模型放在边缘(即更靠近您的客户)

一个通用的 web 服务架构——作者图片
在这个故事中,我们将研究如何让第一个选项发生,当你想要走向这个解决方案时,以及这个方法的优点和缺点。
Learning Rate 是一份时事通讯,面向那些对 AI 和 MLOps 世界感到好奇的人。你会在每个月的第一个星期六收到我关于最新人工智能新闻和文章的更新和想法。订阅这里!
将您的 ML 模型移入数据库
将您的 ML 模型移入您的数据库是一种黑客行为。你不用在你的数据库系统中运行任何代码。相反,您可以定期对新数据离线运行模型,将结果保存在本地,并遵循标准的 ETL(提取-转换-加载)过程将预测存储在数据库中。
这是将模型投入生产时您能想到的最简单的设计。您不必维护模型运行的基础设施,您不必真正关心它的性能,并且您不会因为每个请求的延迟而失眠。
因此,这似乎是完美的解决方案,但是,正如你所想象的,这不是一个银弹。这种方法适用于特定的用例:
- 推荐系统
- 营销自动化
如果你的推荐系统每天只为每个用户做一个预测,我们正在谈论的设计是完美的。想象一个顾客走进一家商店。一进门,他们的智能手机就会收到一条推荐信息,告诉他们哪些产品符合他们的偏好。
在这个用例中,您可以每天运行您的模型,将每个用户的预测存储在您的数据库中,然后完成它。然而,它并不与每个推荐系统相关。如果您需要在动态环境中进行实时预测,数据库中的模型体系结构将无法满足您的需求。话虽如此,今天生产中的许多推荐系统都是这样工作的。所以,如果你的情况很合适,不要犹豫。
对于营销自动化,比如客户细分,在数据库中移动模型可能会更好。例如,考虑这样一种情况,您想要运行某种无监督学习算法来识别用户群,并随后针对他们开展促销活动。您可以运行一次该算法,并将每个用户的组标识符存储在数据库中。就是这样!
你为什么要这么做?
那么,这种方法的优点是什么?首先,也是最重要的,它实现起来很简单。我们看到,您不需要关心基础架构或性能之类的东西。您甚至可以在笔记本电脑上运行您的模型,并将预测存储在 CSV 文件中。然后将 CSV 加载到您的数据库中。
它也很容易扩展。您依赖于几十年来对数据库系统的创新和工程设计,使它们能够适应数百万的请求。出于同样的原因,你(通常)为你的用户获得低延迟。
你为什么不应该做它?
坏处呢?第一个缺点是这种方法不能扩展到复杂的输入类型。如果你的用户想要得到一张图片或者一段文字的信息,该怎么办?这个问题根本没有办法解决。
然后,在训练过程中,您的模型会暴露在推断时可能不相关的数据点中。因此,您的用户无法获得最新的预测,并且您的模型经常变得陈旧。
结论
数据库中的模型方法是将 ML 模型从 web 服务器中取出来的一种途径。我们看到了这种方法覆盖的用例类型以及它的优缺点。我的建议是,如果你的情况是这种设计的候选,从这里开始。它很容易实现,没有理由过度设计。
下一篇文章将介绍何时应该将模型放入自己的推理服务器,以及如何做。这可以说是最常见的架构设计,几乎涵盖了所有用例。
关于作者
我叫迪米特里斯·波罗普洛斯,我是一名为阿里克托工作的机器学习工程师。我曾为欧洲委员会、欧盟统计局、国际货币基金组织、欧洲央行、经合组织和宜家等主要客户设计和实施过人工智能和软件解决方案。
如果你有兴趣阅读更多关于机器学习、深度学习、数据科学和数据运算的帖子,请关注我的 Medium 、 LinkedIn 或 Twitter 上的 @james2pl 。
所表达的观点仅代表我个人,并不代表我的雇主的观点或意见。
pur——保持您的需求文件最新的最简单的方法

米哈伊尔·瓦西里耶夫在 Unsplash 上拍摄的照片
用一行代码更新您的requirements.txt中的所有库
我不认为我需要让你相信保持你的 Python 库(或者事实上其他软件)最新的好处:随着时间的推移,错误被修复,潜在的安全漏洞被修补,兼容性问题可能出现,等等。这样的例子不胜枚举。
在 Python 项目中,我们经常使用一个requirements.txt文件作为在我们的 Python 环境中应该使用哪些库(以及它们的哪个版本)的信息来源。当我们想要更新库的时候,我们经常在我们的环境中更新它们,然后相应地修改需求文件。
但是,有一个更简单的方法。我最近发现了一个小的 Python 库,它对于保持一个干净的requirements.txt文件和加速更新过程非常有帮助,尤其是对于有大量依赖项的项目。

照片由 Tran Mau Tri Tam 在 Unsplash 拍摄
pur在行动
pur代表 pip 更新需求,是一个小的 Python 库,可以用一个命令更新需求文件。它的基本用法非常简单,我们将用一个例子来说明它。
首先,我们需要安装pur:
pip install pur
让我们假设我们在一个虚拟环境中,我们有项目的requirements.txt文件,它包含以下内容:
pandas==1.2.4
yfinance==0.1.63
这是一个非常简单的例子,但它符合我们的目的。此外,我们知道这两个库都过时了。在这种情况下,我们可以通过在终端中运行以下命令来使用pur库:
pur -r requirements.txt
它用文件中列出的库的最新版本修改了requirements.txt。执行此操作时,它会打印以下内容:
Updated pandas: 1.2.4 -> 1.4.1
Updated yfinance: 0.1.63 -> 0.1.70
All requirements up-to-date
在这一点上,强调一个关键的事情是很重要的:库从不修改环境本身,也就是说,它既不安装也不更新任何库。它只做一件事——更新需求文件。话虽如此,现在我们实际上需要安装库的更新,例如,通过运行以下命令:
pip install -r requirements.txt
它安装了最新版本的库——yfinance的 0.1.70 和pandas的 1.4.1。此时,如果依赖项的版本发生冲突,安装将会失败。
pur还提供了额外的选项,例如,迭代地询问需求文件中每个库的更新。在我们知道一些库实际上需要被固定到某些版本的情况下,这可能是有用的,否则事情将会崩溃。
外卖食品
pur是一个方便的轻量级库,它负责用库的最新版本更新requirements.txt文件,- 库从不安装/更新库,它只修改需求文件,
- 当一些版本需要保持固定以使我们的代码正常工作时,应该谨慎使用这个库。
此外,欢迎任何建设性的反馈。你可以在推特或评论中联系我。
喜欢这篇文章吗?成为一个媒介成员,通过无限制的阅读继续学习。如果你使用这个链接成为会员,你将支持我,不需要你额外付费。提前感谢,再见!
您可能还会对以下内容感兴趣:
https://eryk-lewinson.medium.com/my-2021-medium-recap-650326b2832a </5-free-tools-that-increase-my-productivity-c0fafbbbdd42>
参考
Python 中 SQL Left Join 的等价及其在数据清理中的应用
使用 Python 识别表中未引用值的技巧

如果你曾经使用过关系数据库,你一定知道 SQL 连接——它们有很多用例,但是在这篇文章中,我将把重点放在数据清理上。
当执行左、右或全外连接时,您创建的表要么包含所有记录,要么只包含某些表中的记录。对于没有匹配项的行,将放置一个空值。因此,连接对于识别缺失或未引用的值非常有用。
假设您的数据库中有一个包含所有用户的users表。此外,您还有其他几个表引用了users表的 id,如posts、logins、subscriptions等。您有兴趣找出哪些用户可以从数据库中删除,因为他们没有与您的网站进行有意义的交互。这可以通过检查 id 是否在其他地方被引用来完成。
SELECT
u.userID AS 'User ID',
p.userID AS 'Post table'
FROM users AS u
LEFT JOIN posts AS p ON u.userID = p.userID
-- as users can have multiple posts
GROUP BY p.userID;
User ID Post table
--------- ----------
1 Null
2 Null
3 3
4 4
5 Null
上表显示用户 1、2、5 没有创建任何帖子。您可能想要进一步研究并添加logins和subscriptions——这很好,但是如果您有许多额外的表想要以这种方式连接,您可能会遇到一些性能问题(提示 : 如果您在使用 SQL 脚本,千万不要在您的生产数据库上这样做,首先创建一个本地副本)。
用 Python 分析你的表
如果您遇到性能问题或者需要更好的工具来分析您的数据库,一个想法是求助于 python,因为它有一个非常好的数据生态系统。例如,您可以使用 SQLAlchemy 或 Jupyter Notebook 的 SQL magic 函数来获取记录并将它们存储在列表(或字典)中。
为了演示如何用 python 进行左外连接,在这种情况下,我们不打算连接到数据库,而是创建一些随机数据并将其存储在字典中。我们将有一个包含所有可能的用户 id 的users表和五个随机引用这些 id 的其他表:
import random
import pandas as pd
# defining range for userIDs, one to ten
r = (1, 11)
s, e = r
# creating dict to hold 'tables' and adding all possible user IDs
tables = {}
tables['users'] = list(range(*r))
# generating ten tables with random IDs from the initial defined range of userIDs
for i in range(1, 6):
table = random.sample(range(*r), random.randint(s-1, e-1))
tables[f'table{i}'] = table
熊猫
使用 pandas 似乎是显而易见的,因为它是 python 中数据的首选包。它有两个连接表的函数,pd.merge()和pd.join()(还有pd.concat()——注意它的工作方式有点不同),但是如果你至少有两列,其中一列是你要连接的,另一列包含你的值,这些函数工作得最好。这不是我们的情况,因为我们只有 id 列表。
让我们看看如果我们加入这两个列表会发生什么,tables['users']和tables['table1']:
df_users = pd.DataFrame(tables['users'])
df_table1 = pd.DataFrame(tables['table1'])
pd.merge(df_users, df_table1, how='left')
OUTPUT:
0
---
0 1
1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 9
9 10
嗯,结果是令人失望的,它似乎没有做任何事情。默认情况下,该函数连接唯一一列上的两个数据帧,因此我们获得所有的用户 id,仅此而已(除了索引之外)。在底层,它确实执行了正确的连接,但是因为我们没有额外的列,所以没有什么可显示的。我们需要添加indicator=True参数来查看结果:
pd.merge(df_users, df_table1, how='left', indicator=True)
OUTPUT:
0 _merge
--------- ---------
0 1 left_only
1 2 left_only
2 3 both
3 4 both
4 5 left_only
5 6 left_only
6 7 both
7 8 left_only
8 9 left_only
9 10 both
_merge列显示记录是存在于两个列表中还是只存在于第一个列表中。通过将初始数据帧的索引设置为唯一的现有列,并连接这些列,我们可以使结果更好:
pd.merge(df_users.set_index(0), df_table1.set_index(0), how='left',
left_index=True, right_index=True, indicator=True)
OUTPUT:
_merge
---------
0
1 left_only
2 left_only
3 both
4 both
5 left_only
6 left_only
7 both
8 left_only
9 left_only
10 both
虽然这种方法可行,但是如果您想要连接多个列表(表),它就很笨拙。
设置
虽然这不是一个连接,但是使用 Python 集合(注意,集合不能包含重复的值)可以实现预期的结果——识别未引用的值。
set_users = set(tables['users'])
set_table1 = set(tables['table1'])
unreferenced_ids = set_users - set_table1
通过从另一个集合中减去一个集合,可以找到两个集合之间的差异——元素出现在users中,而不在table1集合集合中。其余的表也可以重复这个过程。
使用循环
对我来说最有效的解决方案是遍历列表(表格)并为未引用的 id 添加None值。这是可能的,因为列表是有序的,我们可以遍历所有用户 id 并检查它们是否存在于其他表中。
# creating a new dict
final_tables = {}
# transfering user IDs
final_tables['users'] = tables.pop('users')
# looping through the tables
for key, value in tables.items():
# creating a new column
column = []
# checking values against all user IDs
for user in final_tables['users']:
# adding True if ID is referenced
if user in value:
column.append(True)
# adding None if ID is not referenced
else:
column.append(None)
final_tables[key] = column
# converting the new dict holding the processed tables to a dataframe
df = pd.DataFrame.from_dict(final_tables).set_index('users')
OUTPUT:
table1 table2 table3 table4 table5
------ ------ ------ ------ ------
users
1 True True True True True
2 True None Nooe True None
3 None True True None True
4 None None True True True
5 True None None True None
6 True True True None True
7 None None True True True
8 True True None True None
9 True None True None None
10 None None True True None
…就这样。该表显示了在 pandas 数据框架中的其他表中如何引用用户 id。
总而言之,如果您习惯于在关系数据库中执行左表连接,并希望在 Python 中实现类似的功能,您有几个选择。有熊猫、,但是令人惊讶的是,在两个单独的列上执行连接来查找未引用的值并不简单。或者,您可以使用集合来获得两列的唯一值的差异。但是最好的选择可能是使用简单的循环,尤其是当您想要识别几个表中不匹配的记录时。
通过统计过程控制推动您的销售
原文:https://towardsdatascience.com/push-your-sales-with-statistical-process-control-52321f329405
质量控制图是一个被低估的销售分析概念吗?

使用 SPC 识别随机噪声或系统漂移(法国海岸,图片由作者提供)
统计学的一个子领域是统计过程控制(SPC)。在任何有关数据科学的书籍中,SPC 几乎从未被提及。最重要的是,SPC 很少用于销售数据。我认为 SPC 在这个分析概念中被错误地定位了。实际上,SPC 可以作为探索性数据分析(EDA)和——顾名思义——过程控制的有力补充。本文将解释使用 Python 对销售数据进行 SPC 的基本概念,并为您提供一个现成的 Excel 模板,其中包括质量控制图(QCC)和箱线图,以便快速直观地了解。
我们要做的是:
- 分析销售时间序列的趋势和残差
- 建立一个 QCC 来检测“失控”的销售事件
- 通过分解成更有意义的次级销售过程来提高 QCC
SPC 是如何工作的?
SPC 简而言之就是控制过程稳定可靠地运行,以保证达到规范目标[1]。这可以很好地应用于“受控环境”,如生产车间过程或实验室条件下。至于销售,那就更难了。我们现在将发现原因。
地点:
让我们假设我们的销售人员每天能够卖出这么多商品:

在日常销售中,很难只见树木不见森林。
看到这里,我们能知道我们的销售流程做得好不好吗?显然,在这个细节层次上,这真的很难做到。问题是,当我们按月汇总时,是否会丢失销售流程的重要信息:

每月总销售额
这已经让我们对销售结果有了更清晰的了解。我们甚至可以通过采用月平均值而不是总和来进一步平滑吗?

每月平均销售额。
均值并不平滑,事实上它指出似乎有三个时间序列具有相似的平均销售额。这一见解将有助于我们以后建立 QCC。
我们一眼就可以看出总体趋势是积极的。2016 年初,我们的销售额相对较低。在 2018 年 2 月,我们面临着异常高的销售额。从 2019 年开始,我们达到了最高的平均销售额。
我们的目标是让我们的 salesforce 同事快速了解他们的销售活动进展情况。他们给我们的反馈是每日分析是两个细节。另一方面,如果只看月销售额或平均销售额,他们担心会失去对销售活动的重要洞察。他们是绝对正确的,因为例如离群值对平均销售额有很大的影响。中位数受异常值的影响较小,但是中位数本身也不能给我们一个公平的销售发展观点。因此,我们觉得需要在我们的合计值中添加一些东西,以便更好地说明整体销售流程的运行情况,同时不要用过高的粒度使其过载。
离散度:
平均值或总数本身并不能告诉我们太多关于销售过程的信息,但我们必须至少加上标准偏差才能看到月销售额的变化:

每月标准偏差
让我们计算连续几个月之间的差异,看看我们能否发现销售发展中的任何季节性模式:

逐月发展百分比。

查看月份之间的销售额增加或减少。
将这些平均百分比绘制为每月的总体发展情况,我们可以看到 1 月(1)和 2 月(2)与前一个月相比是销售额增长最高的月份。另一方面,与前一个月相比,3 月(3)、4 月(4)和 12 月(12)的下降幅度最大。

逐月增加或减少的平均百分比。
箱线图对于直观检查标准差、最小值和最大值非常有用。范围是最小值和最大值之差。每个百分点代表 25%的数据。Q1 是从分钟开始的 25%的数据。Q2 涵盖了 50%的数据。这就是为什么盒子里的线代表中间值。如果平均值和中间值接近,这可能是正态分布的一个标志(我们将看到为什么这可能是特别有趣的)。通过计算第 25 个(Q2)、第 50 个(中间值)和第 75 个(第 3 季度)百分点,我们可以计算出中间 50%的数据。这个箱线图指标被称为四分位距(IQR= Q3-Q1 ),它给出了关于平均值的平均方差的指示。让我们选择一个特定的月份,查看详细信息:
我们正在使用 Plotly Express 创建一个交互式的方框图

如果箱线图背后的计算对您来说是新的,您也可以查看 Excel 公式来交叉检查上面的 Plotly Express 公式:

Excel 公式可能有助于新手更快地理解箱线图计算
描述性统计解释:
现在,让我们用一个更深刻的统计基础来确认我们的视觉检查:

只有我们的时间序列的系统观测、趋势、季节和剩余(噪声)成分可以被建模。在我们的例子中,趋势与时间序列的斜率增加有关(总体趋势是积极的)。当我们将销售过程分成更有意义的连续子过程(关于时间框架)时,趋势分析非常重要,我们稍后会看到这一点。季节性是由重复的短期周期引起的与平均值的偏差。我们正面临着二月份的季节性高峰。残差也很有趣,显示了 2017 年 9 月至 11 月、2018 年 1 月至 3 月以及 2018 年 9 月至 11 月期间的较高可变性。噪声是序列中的随机变化。
到目前为止,我们主要计算描述性统计数据来描述我们的销售历史。下一步,我们希望设置警报,一旦我们的销售结果超出历史发展的预期,就会触发警报。因此,我们现在将弄清楚如何将月总和、平均值、标准差和预警全部放在一个图表中。
质量控制图:
一般的六适马[2]方法是使用三倍标准差(+/-)来计算平均值的控制上限和下限。这种技术背后的想法是,我们估计 99.73%的数据将落在这些控制上限和下限之间(假设特定的分布,我们将在后面更详细地讨论)。因此,如果数据点不时失控,也没关系。那只是正常的噪音。只要随后的数据点落在控制范围内,该过程仍然可以被描述为稳定的。但是一旦太多的点超出了控制范围,这就强烈地表明过程开始失控。这将在该过程用尽随机噪声时给我们一个早期指示。我们稍后会看到,在没有事先进行分布分析的情况下,是否允许使用 3 倍标准差。
为 X 和 QCC 先生策划 UCL 和 LCL
如果 QCC 背后的计算对你来说是新的,你也可以看看 Excel 公式来交叉检查上面的 Python 代码:

如果小于 0,将 mR 图表中的 LCL 设置为 0 是不常见的。由于这是一个控制销售的 QCC(而不是医疗设备),我相信为了我们的目的稍微调整一下 SPC 背后的统计数据是可以的。
在 X 图中,显示了每月的总和(黑线)。控制上限和下限(UCL 和 LCL)按平均值(蓝线)的 3 倍标准差+/-计算:

在 X 图表中使用月销售总额..
使用控制图,我们可以很容易地看到转变中的变化。因为通常所有的数据点都应该平均分布。虽然我们仍在控制范围内,但平均值在整个时间段内发生了系统性的变化。在 mR 图表中,我们可以看到不同月份之间的离差非常不同:

..并结合月度波动范围的 mR 图表。
通过持续的监控,我们可以确保我们能够尽早发现流程中的变化。我的过程能满足规格要求吗?虽然这听起来像是生产流程的必要要求,但我们也可以用它来衡量我们的销售。当然,我们不会不高兴收到高于控制上限的销售。这就是为什么没有控制上限的控制图也是可能的。但我更喜欢保留它们,因为它有助于我们的眼睛更容易发现系统的变化。
我们已经看到,我们的销售呈现出整体积极的趋势。在建立统计流程图时,趋势是违反直觉的。这就是为什么我们不应该尝试在整个销售时间框架内使用一个控制图的原因。但相反,我们应该将销售分成更有意义的子流程。
QCC 的改进:
我们不会用一张控制图来控制整个销售。原因是数据点彼此差异很大。例如,一开始销售新产品通常与销售已经在市场上销售了很长时间的产品是非常不同的销售过程。
我们还没有涉及的一件事是数据的分布。当涉及到 QCC 和任何类型的统计建模时,了解分布是至关重要的。
分发:
只有当我们找到一个模型——来自数据的分布——足够公平地描述我们的销售数据时,我们才能设置有意义的控制限制。
让我们计算一些指标来更精确地说明分布情况:

绘制销售金额直方图:

直方图描述了销售分布。
除了上面的直方图之外,添加偏度和峰度以进一步了解分布的形状:

甚至均值和中位数也有很大差异,这也表明我们的数据不符合正态分布。

这也与我们之前绘制的残差相匹配:

残差表示噪声信号。
因此,我们的销售时间序列不能被描述为正态分布也就不足为奇了:

这清楚地表明,不要像我们刚刚做的那样,对完整的时间序列使用一个具有 3 倍标准差的 QCC。我们需要更详细地检查我们的时间序列。
异常值检测:
调查数据的分布总是与异常值检测密切相关。因为这篇文章的主题不是发现异常值,所以我们不会详细讨论这个问题。你可能想读一下我的先知帖子,在那里我给出了一个例子,关于如何发现不寻常的销售数据点。

蓝色阴影置信区间外的黑点可视为异常值。
控制图是用来控制你的过程的。这意味着在失控时采取对策。为了以富有成效的方式建立控制图,您需要预先决定在计算控制极限时应该考虑哪些数据。原因是您不想让错误输入或异常数据伪造您的控制范围。因为我们正在谈论销售数据(而不是技术生产过程),我们将考虑所有的销售数据。
子流程:
我们现在将把我们的销售历史分成三个子流程。为什么是三个?还记得我们之前画的月平均销售额吗?

每月平均销售额。
很明显,至少有三个平均数,而不是一个。第一个销售子流程从开始持续到 2017 年 1 月:
这个时间序列现在可以描述为正态分布:

遵循正态分布并不意味着它确实是正态分布。
我们现在做得很好,预计销售额将在平均销售额的+或- 3 倍标准差范围内。因此,如果下个月 salesforce 高于控制上限,他们就完成了一项非凡的工作。而如果他们在下面,他们一定可能对这一意外事件有特殊的原因:

让我们对其他两个子流程重复一下:


这让我们对销售过程中的位置和分布有了更清晰的了解。
结论:
那么在销售分析中,质量控制图是否被低估了呢?将 SPC 用于销售背后的想法是,我们正在寻找系统性转变的迹象。SPC 判断销售过程运行是否稳定可靠。控制范围内的所有数据点都分布在正常噪声中。但是销售失控的限制表明了系统性的转变。可能的原因是多方面的,可能是由于特殊的促销活动导致了更高的销售额等..不时出现一个失控点并不一定意味着你的销售过程已经“失控”。假设正态分布,我们估计所有销售数据点的 99.7%应该在控制范围内。而另一方面,0.3%的人被认为是没有控制的,这很好。因此,只有当你面对控制范围之外的连续数据点时,才能告诉你你的销售过程已经发生了重大变化(或者根本的分布没有得到很好的理解)。
SPC 不是控制收入的最直观的方法。我们的销售同事可能会问,他们为什么要关心销售额是否超过控制上限?一方面,销售人员通常不太熟悉除了总和与平均值之外的统计数据(无意冒犯同事)。他们完全正确,我们都想比过去卖得更多。另一方面,销售商品往往不太严格地遵循任何规范。说白了,售出商品的数量是高度不稳定的,例如,在生产工厂制作肉饼,可以而且必须非常精确地锁定目标。因此,如果你现在认为 SPC 是一种对销售数据进行过多理论统计的方法,而没有增加太多的支持,我认为密切关注销售的位置和可变性确实非常重要。例如,你的销售同事中有多少人在谈论他们的总销售额或平均销售额,却从来没有提到标准差?关注箱线图和质量控制图可以帮助您的销售人员在压缩图表上更好地了解他们的销售位置和分布情况。即使您可能决定不向您的 salesforce 同事传播(质量)控制图,您仍然可以采用这种方法,并在其中添加更多“实际销售的生命消化率”。
非常感谢您的阅读!希望这篇文章对你有帮助。请随时在 LinkedIn 、 Twitter 或工作室与我联系。
https://jesko-rehberg.medium.com/membership
你可以在我的 Github 资源库中找到完整的 Jupyter 笔记本和 Excel 模板。
参考:
[1]工业中的统计过程控制,SPC 的实施和保证,Does,R.J .,Roes,C.B .,Trip,a .,1999:https://www.springer.com/de/book/9780792355700
[2]James r . Persse 博士的《过程改进要点》, 2006 年 9 月发布,出版商:O'Reilly Media,Inc .,ISBN: 9780596102173
将 Docker 图像推送到 Amazon 弹性容器注册中心
逐步指南

图片来自 Unsplash 作者
张秀坤·吕克曼
亚马逊弹性容器注册中心(ECR) 是一个容器图像注册中心,我们可以在 AWS 上使用 push Docker 图像。为什么要使用容器注册表?它有助于轻松管理您的各种图像和单独的项目。例如,当我第一次开始在本地使用 Docker 时,我并没有意识到我已经创建了多少图像。我很难为不同的项目跟踪我的图像,我需要一个中心资源,ECR 是我的首选注册表,因为我经常与 AWS 一起工作。
这就引出了我使用 ECR 的第二个原因。有不同的容器注册中心可用,但是如果您是 AWS 的大用户,ECR 可以直接与平台上的许多其他服务集成。这方面的例子包括为你的 Lambda 函数提供定制图像,为 SageMaker 训练和推理构建你自己的容器。在本文中,我们将快速了解如何将图像推送到 ECR 上的存储库中。
注意:对于刚接触 AWS 的人来说,如果你想继续学习,请确保在下面的 链接 中注册账户。本文还假设读者对 Docker、AWS 和使用 AWS CLI 有基本的了解。
本地提取 Docker 图像
首先,确保你已经安装了Docker并且正在运行。在 AWS 端,确保您安装了AWS CLI,以便我们使用 ECR 等服务。
为了简单起见,我们不担心构建我们自己的定制图像,我们可以从 Docker Hub 中为本文抓取一个现有的图像。
拉式 Docker 图像
如果运行以下命令,您应该能够看到 hello-world 图像。
列出 Docker 图像

Hello World Image(作者截图)
现在让我们将此图像推送到 ECR。
创建 ECR 存储库
在推送我们的映像之前,我们需要创建一个 ECR 存储库。所有这些与 ECR 的交互都可以通过 AWS CLI 进行管理。要进一步了解 CLI 的设置和配置,请参考这篇文章。
在创建存储库之前,我们需要登录到我们的 ECR 注册中心,这可以通过下面的命令来完成。
登录到 ECR
确保提供正确的帐户 ID,如果你遇到一个错误说“没有认证证书”,这将取决于你所在的地区。

登录到 ECR
登录后,我们可以通过“ create repository ”调用来创建我们的存储库。
创建存储库
成功创建后,您应该会在终端中看到您的存储库元数据,我们也可以在控制台中确认这一点。

hello-world 知识库已创建(作者截图)
接下来是获取我们的本地映像,并将其推送到我们创建的 hello-world 存储库中。
将图像推送到 ECR
在推送我们的图像之前,我们需要对其进行标记,以识别我们的存储库。预期格式如下:*aws_account_id*.dkr.ecr.*region*.amazonaws.com/my-repository:tag
我们可以将这种格式应用于您的“hello-world”图像,方法是使用以下命令对其进行标记。
标记本地图像
您还可以通过运行另一个“docker images”命令来列出并搜索新标记的图像,从而验证该图像是否已被正确标记。

标签图片(作者截图)
现在,我们可以用下面的命令将这个映像推送到 ECR。
将 Docker 图像推送到 ECR
我们可以通过再次检查控制台来验证 repo 中是否存在该映像。

图片推送(作者截图)
既然您的映像已经在存储库中,您就可以提取它,推送多个版本,就像您在本地使用 Docker 一样。这里的主要好处是你可以用注册表来组织和跟踪所有这些不同的项目和图像。
其他资源和结论
https://github.com/RamVegiraju/AWS-CLI-Cheatsheet/blob/master/ecr.sh
您可以通过上面的链接访问所有 ECR AWS CLI 命令,还有用于 Lambda 等其他流行 AWS 服务的 CLI 命令。当您熟悉 Docker 和 CLI 时,使用 ECR 相当简单。如果你对 Docker with AWS 更感兴趣,请点击这里查看你在上运行 Docker 的其他服务。
我希望这篇文章是将你的 Docker 图片推送到 ECR 的好入门,你可以在这里参考官方文档。请随意查看下面的列表了解其他 AWS 内容。
如果你喜欢这篇文章,请在LinkedIn上与我联系,并订阅我的媒体 简讯 。如果你是新来的中号,用我的 会员推荐 报名吧。
推动可解释的人工智能:神经网络是决策树
探索一篇旨在解释 DNN 行为的新论文
最近,来自 AAC Technologies 的一位伟大的研究员卡格拉尔·艾特金发表了一篇题为“神经网络是决策树”的论文我仔细阅读了它,试图理解这篇论文的重大发现到底是什么。许多数据科学家可能会同意,许多转换会将一种算法转换成另一种算法。然而,(深度)神经网络(DNNs)很难解释。那么,艾特金是否发现了一些新的东西,让我们离可解释的人工智能时代更近了一步?

不溅
在这篇文章中,让我们探索这篇论文,并试图理解这是否真的是一个新发现。或者,我们将检查这是否只是任何数据科学家在处理 DNN 可解释性挑战时需要了解和记住的一个重要焦点。
简介和背景
Aytekin 证明了任何具有分段线性激活函数的经典前馈 DNN(如 ReLU)都可以用决策树模型来表示。让我们回顾一下两者之间的主要区别:
DNN 拟合参数,以转换输入,并间接指导其神经元的激活。
决策树明确适合参数来引导数据流。
这篇论文的动机是解决 DNN 模型的黑箱性质并以另一种方式解释 DNN 行为。这项工作处理全连接和卷积网络,并提出了一个直接等价的决策树表示。因此,本质上,它检查了从 DNN 到决策树模型的转换,即在它们之间采用非线性的权重序列,并将其转换为新的权重结构。艾特金讨论的另一个结果是相应的 DNN 在计算复杂性方面的优势(存储内存更少)。
Frosst 和 Hinton 在他们的工作[4]“将神经网络提炼为软决策树”中提出了一种使用决策树来解释 DNNs 的伟大方法。然而,他们的工作不同于 Aytekin 的论文,因为他们结合了 DNN 和决策树的优点。
想法的关键部分
通过计算新的权重来构建生成树:建议的算法采用到达网络的信号,并搜索 ReLUs 被激活和未被激活的信号。最终,算法(转换)替换/放置一个 1(或斜率值)和 0 的向量。
该算法在所有层上运行。对于每一层,它查看来自前一层的输入是什么,并计算每个输入的依赖关系。实际上,在每一层中,一个新的有效滤波器被选择,因此它将被应用于网络输入(基于先前的决定)。通过这样做,全连通 DNN 可以被表示为单个决策树,其中通过变换找到的有效矩阵充当分类规则。
您也可以针对卷积层实现它。主要区别在于,许多决策是在部分输入区域上做出的,而不是对图层的整个输入做出的。
关于维数和计算复杂度:得到的决策树中类别的数量显得巨大。在一个完全平衡的树中,我们需要 2 的树的深度的幂(难处理)。然而,我们还需要记住提供无损修剪的违反规则和冗余规则。

作者图片
总结和注意事项
- 这个想法适用于分段线性激活函数的 DNN
- 神经网络是决策树这一观点的基础并不新鲜
- 就我个人而言,我发现解释和数学描述非常简单明了[1],激励我使用它并推进可解释的人工智能领域
- 需要有人在 ResNet 上测试这个想法😊
参考
原文可见:https://arxiv.org/pdf/2210.05189.pdf
[1]艾特金,卡格拉尔。"神经网络是决策树."arXiv 预印本 arXiv:2210.05189 (2022)。
如果你想看 30 分钟。关于论文的采访看这里:
[2]伟大的扬尼克·基尔彻在 YouTube 上就这篇论文采访了亚历山大·马蒂克:https://www.youtube.com/watch?v=_okxGdHM5b8&ab _ channel =扬尼克·基尔彻
一篇关于将近似理论应用于深度学习以研究 DNN 模型如何以分层方式组织信号的伟大论文:
[3]兰德尔·巴勒斯特里罗。“深度学习的样条理论。”机器学习国际会议。PMLR,2018。
结合了决策树和 DNNs 力量的伟大作品:
[4]弗罗斯特、尼古拉斯和杰弗里·辛顿。"将神经网络提炼为软决策树."arXiv 预印本 arXiv:1711.09784 (2017)。
你可以在 Medium 上阅读一篇总结这项工作的帖子【4】:
[5]razor think Inc,Medium,2019,将神经网络提取为软决策树。
关于作者
Barak Or 是一位企业家和 AI &导航专家;前高通。Barak 拥有 Technion 的工程学硕士学位、理学学士学位和经济学学士学位。Gemunder 奖的获得者。巴拉克完成了他在人工智能和传感器融合领域的博士学位。多篇论文和专利的作者。他是 ALMA Tech 的创始人兼首席执行官。一家人工智能和高级导航公司。
利用谷歌云平台和 DVC 将机器学习模型投入生产
介绍
机器学习模型作为一个独立的产品没有太大的价值。无论他们的表现有多不可思议,在交付给相关用户之前,他们都没有给企业带来任何实质性的价值。然而,部署方面通常被考虑在 ML 项目的最后,因为它不是数据科学家熟悉的主题。
本文将展示一种简单的方法来将 ML 模型付诸实践,以及用来完成它的工具,这样那些从未部署过模型的人就能更好地了解它的样子。
模型部署策略
在我们开始之前,让我们看看有哪些选项可以用来部署我们的模型。将 ML 模型投入生产的方法有多种,大致可分为以下三类:
- 模型即服务(Model-as-a-service)
模型是软件的一部分,它按照用户的要求实时提供服务。大多数情况下,它以 web 服务或 API 的形式出现,其中模型的输入和输出通过 HTTP 或其他众所周知的协议进行传输。

模型即服务策略(图片由作者提供)。
- 批量预测 与实时服务相反,批量预测策略离线进行推理。通过数据管道为模型准备输入,一旦处理完成,结果在交付给用户之前保存在数据库或数据存储器中。处理管道可以由计划的作业或用户请求触发,但输出不会实时提供。

批量预测策略(图片由作者提供)。
- 边缘模型 与前两种通过互联网来回传输输入和输出的策略不同,边缘模型策略在传感器收到数据后立即使用 ML 模型进行预测,以便做出实时决策。这些情况通常会在自动化系统、机器人和物联网应用中大量出现。

边缘模型策略(图片由作者提供)。
概观
我们将创建一个工作流,从云存储中获取图像,执行图像分割,并将结果放回云存储中。您可能已经猜到了,我们在这里使用的策略是批量预测。
模型
我们要部署的模型是一个图像分割模型,它可以识别图像中的云。模型的输入和输出如下:

模型输入和输出(图片由作者提供)。
关于模型架构、训练过程和性能的细节超出了本文的范围。我们将假设模型已经过训练,可以使用了。
数据管道
批外预测流水线包括如下 3 个阶段:
- 用户上传图像到云存储桶()实际上,这个步骤可能由系统处理,而不是手动上传。然而,这不会影响我们的批量预测管道的工作方式。
- 调度的作业向 API 发送 HTTP 请求来触发计算。
- 计算服务通过
3.1 开始计算。下载一个训练好的模型
3.2。下载输入云存储
3.3 中的所有图片。对所有图像进行推理 - 所有输出都保存到输出云存储中
下图显示了工作流程:

数据管道(图片作者提供)。
模型注册表
模型部署的另一个关键方面是软件组件(无论是应用程序、数据管道等。)访问训练好的模型。一个可能的选择是将训练好的模型存储在远程存储上,然后将文件路径放入配置文件中。这种方法,即使是一个有效的选项,也使得训练和部署模型的过程不连续,这导致了不期望的情况,例如应用程序没有意识到存在模型的较新版本,系统因为文件被重定位到新路径而无法启动,等等。现在更好且更广泛接受的方法是使用模型注册中心。
模型注册中心是一个集中的存储库,用于存储、版本化、服务和管理经过训练的 ML 模型。它充当模型开发和模型部署之间的桥梁,前者只需要知道模型应该在哪里注册,而后者只需要知道要使用什么模型。在这篇文章中,我们将使用数据版本控制 (DVC)从 GitHub 项目中创建一个简单的模型注册表。
履行
管道组件
我们将使用 Google 云存储作为输入和输出桶,使用云调度器作为调度作业。下面是使用 Google Cloud SDK 创建 buckets 和计划作业的命令。您也可以通过 GCP 控制台或谷歌云 CLI 来完成这些操作。
对于计算服务,为了简单起见,使用了云函数,因为它只需要 Python 代码及其依赖项。注意,在现实中,对于图像处理和深度学习等计算密集型任务,使用云运行或计算引擎等服务可能更好。运行推理的代码如下:
def run_inference(request):
model = load_model()
with torch.no_grad():
for blob in input_bucket.list_blobs():
obj_name = blob.name
img = load_image(obj_name)
out = inference(img)
save_image(out, obj_name)
blob.delete()
函数load_model负责连接模型注册表并下载训练好的模型,而load_image、save_image、inference分别从指定位置读取/写入图像并进行推理。
模型加载
如前所述,我们将使用 DVC 创建一个简单的模型注册中心。简而言之,DVC 是一个用于数据科学项目的工具,它让你以类似于 Git 版本化你的源代码的方式来版本控制你的数据。DVC 的一个便利特性是,它可以让你毫不费力地将你的存储库变成一个数据注册中心。假设您有一个 DVC 项目,其中有一组被跟踪的文件,您可以像这样列出并下载所有被跟踪的文件。

使用 DVC 加载模型(图片由作者提供)。
除了使用 CLI,DVC 还提供 Python API,允许您以编程方式使用这些功能。我们使用这个 API 从存储库中加载训练好的模型,如下所示:
import dvc.api
def load_model():
with dvc.api.open(path=model_path, repo=url, mode='rb') as f:
params = yaml.safe_load(io.BytesIO(f.read()))
model_params = params['model'] with dvc.api.open(path=model_path, repo=repo_url, mode='rb') as f:
buffer = io.BytesIO(f.read())
state_dict = torch.load(buffer) model = Model(n_classes=model_params['n_classes'],
in_channel=model_params['in_channels'])
model.load_state_dict(state_dict)
return model
部署
即使我们可以手动部署代码,最好是自动化这个过程。我们可以使用 GitHub Actions 通过 google 提供的插件Google-GitHub-Actions/deploy-Cloud-Functions来自动化云功能部署,如下所示:
name: Mergeon:
push:
branches:
- mainjobs:
deploy_function:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2 - name: "Authenticate GCP"
id: auth
uses: google-github-actions/auth@v0
with:
credentials_json: ${{ secrets.gcp_credentials }} - name: "Deploy a Cloud Function"
id: deploy-function
uses: google-github-actions/deploy-cloud-functions@v0
with:
name: cloud-segmentation
runtime: python37
entry_point: run_inference
memory_mb: 2048MB
deploy_timeout: 600
env_vars: INPUT_BUCKET_NAME=cloud-segmentation-input,OUTPUT_BUCKET_NAME=cloud-segmentation-output
添加这个之后,每当新的 PR 合并到main分支,云功能就会自动部署。GCS bucket 和 scheduler 只创建一次,所以如果需要,我们可以手动创建。
下面的 GIF 显示了当我们将所有组件放在一起时管道的样子。

所有组件组装在一起(图片由作者提供)。
结论
正如您所看到的,尽管我们已经有了一个训练好的模型,但是要让它能够提供真正的价值,还有很多工作要做。选择部署策略、挑选模型服务模式、设计基础设施和构建 CI/CD 管道,所有这些对于机器学习项目的成功都至关重要,就像使用正确的算法、确保数据质量和训练模型以实现高精度指标一样重要。
让闪亮的应用熠熠生辉
原文:https://towardsdatascience.com/putting-the-shine-in-shiny-apps-e3de370b5661
三个简单的技巧让你的 R 闪亮应用从枯燥变得漂亮

作者图片
闪亮的应用程序不一定很无聊。你可以做一些简单的事情,只用几行代码就能给你的应用程序带来活力!在本教程中,我们将使用一个简单的、信息丰富的关于太阳系卫星的应用程序,并给它一点光亮。
逃离地心引力的计划
简而言之,我们将把这个普通的应用变成这个有趣的应用。这两个应用程序有完全相同的数据,但一个比另一个看起来更专业一些。如果你曾经做过一个闪亮的教程,你的应用可能最终看起来像普通的。遵循一些提示,很容易从简单变得有趣。
数据
我们的应用将会是关于太阳系的卫星。这些数据只是来自维基百科页面的自然卫星列表中的一个表格。本页列出了太阳系中所有已知的卫星(219 颗,还在继续,哇哦!).
简单的应用程序
先来看看素 app 。目标很简单,每个行星都有一个下拉列表,这个下拉列表显示了所选行星的卫星和一些相关的统计数据。
你可以从 GitHub 下载 plain 应用的完整项目。只需将文件下载到您的计算机上,然后打开 R 项目。如果你想在本地运行这个程序,打开 Rproj 文件就可以了。
代码如下:
这里没什么可花哨的,显而易见。按照出现的顺序,我们有:
- titlePanel:这是应用程序顶部的标题
- 选择输入:这让你选择一个星球来改变表格
- tableOutput('moons '):这是我们的表,由后端(也就是' server '里面的东西)交给我们。
- df:这是我们月球数据的一个数据框。
- output\(moons:这将获取我们的数据框,并对其应用一个简单的过滤器。在选择输入中选择的行星被传递到这个过滤器中,所以如果你选择“木星”,只有木星的卫星被返回到前端(也就是 ui 中的所有东西)。上面代码中的 tableOutput('moons ')只是等待 output\)moons 告诉它该做什么。多好的羊啊。
你可能在读的时候睡着了,因为我在写的时候睡着了。是无聊的代码产生了无聊的 app。但是我们能做些什么来增加这个应用的趣味呢?
有趣的应用程序
现在我们开始有趣的东西,这里是不那么简单的应用。同样的数据,只是显示方式略有不同。你可以下载整个 R 项目,包括我从 GitHub 手绘的行星图片。
这是新应用程序的代码。r:
现在,我们将讨论发生了哪些变化,以及这些变化有何帮助。
技巧 1:使用闪亮的主题
闪亮的主题是用一行代码让你的整个应用看起来更好的最简单的方法。如果你不知道,Shiny 默认使用 bootstrap,这是开发网站时广泛使用的 html 和 css 的集合。安装 shinythemes 包的说明可以在这里找到。
有相当多的主题可供选择,你可以在这里浏览它们,看看实际的配色方案,以及它们如何设计你可能放入闪亮应用程序的不同类型的对象(例如,下拉菜单、导航栏、按钮、表格等等!).例如,这是我选择添加到我们的应用程序的闪亮主题的完整调色板;阴暗地。
将这个主题应用到我们的普通应用程序的代码是:
fluidPage(theme = shinytheme("darkly"),
您可以在 fluidPage 中添加您想要的主题。就是这样。确保将库(shinythemes)添加到文件的顶部。
技巧 2:利用引导程序
Bootstrap 是目前最强大的 HTML 可视化库。Bootstrap 有转盘、旋转器、下拉等等!他们也有一些标准标题面板的漂亮替代品,比如大屏幕。
下面是实现超大屏幕的代码:
这是怎么回事?
-
标签$div:这创建了一个
-
class = "jumbotron text-center ":这将我们的 div 转换成一个超大屏幕。
-
样式:我在两个不同的地方去掉了 html 元素上面和下面的空白。这是可选的样式,但我更喜欢我的大屏幕稍微小一点。
-
标签$h2:这是大屏幕标题,也就是大文本。这相当于
元素。
-
p():这是 Shiny 版的
,是一个段落元素。这是小文。
“黑暗”闪亮的主题加上自举大屏幕是我们如何把这个标题:

作者图片
变成了这个标题:

作者图片
技巧 3:使用可反应表代替表格
所以 R 区的普通桌子有点烂。它们是有用的,但不是特别令人兴奋。r 为我们提供了几个漂亮有用的表选项,但是 Reactable 绝对是我最喜欢的。
Reactable 库是一个优秀的 JavaScript“React Table”库的实现,它充满了很酷的特性。这里的是这个库的一些特性的参考。我们追求的让普通应用看起来更好的主要特性是样式和分页。
我们希望我们的表匹配我们的“黑暗”引导主题,所以我们需要一些黑暗的表(我们的普通应用程序使用的默认表相当明亮)。
我们想要的第二个特性是分页,这意味着我们希望我们的表有一个固定的高度,并允许您“翻页”通过。我们最初的普通应用程序中的表格与我们的黑暗主题形成了过于明亮的对比。
这是我们的旧桌子,滚动了好几天:

作者图片
这是我们新的分页表格:

作者图片
下面是制作这个表格的代码:
那么我们在这里做了什么?
- renderTable()已被 renderReactable()换出
- options():这段代码只是用来设计我们的 reactable。如果这段代码不在这里,我们的 reactable 仍然可以很好地工作,只是不符合我们的主题。
- reactable():这将我们的数据帧转换成一个 reactable 对象。
只是不要忘记在 ui 中将 tableOutput()替换为 reactableOutput!
技巧 4:使用图像
我不想这么说,但是数据表看起来可能有点无聊。在你的应用程序、网页或报告中添加相关图片可以更好地表达你的观点,同时让你的头脑看起来不那么无聊。
在我们的应用程序中,我用笔在平板电脑上画出了每个星球的图像。你不必走这么远,网上的普通旧图片就可以了。

作者图片
下面是基于下拉列表交换我们的行星图像的代码:
这段代码是这样工作的:
- planet:这个变量保存我们选择输入的值。当我们在下拉菜单中选择一个新的行星时,这个动作会使这段代码失效,一个新的图像就会呈现出来
- picFilePath:我们基于所选的行星构建图像的文件路径(例如,“地球”变成了“Earth.png”)。所有图像都位于 shiny 项目的主文件夹中,并通过相对文件路径进行访问。
- return(list):这最终将我们的服务器代码变成了一个元素。
- 这些都是通过 imageOutput()在我们的 ui 中获得的
技巧 5:使用闪亮的布局
我们不必受限于自上而下读取的应用程序。Shiny 为我们提供了大量放置应用程序的选项。这些选项包括列、行、边栏、导航条等等!
我们采取了一个非常简单的方法,将我们非常垂直的普通应用程序转换成不需要滚动的水平应用程序。
代码如下:
我们只有一行两列。左栏是图像,右栏是可反应的。每列旁边的数字是它的大小。fluidRow 的总大小是 12,所以图像占总宽度的 1/3,表格占另外的 2/3。
这是最终产品:

作者图片
还不错!
Python 中 pycache 是什么?
原文:https://towardsdatascience.com/pycache-python-991424aabad8
了解运行 Python 代码时创建的 pycache 文件夹

照片由 dimas aditya 在 Unsplash 上拍摄
介绍
您可能已经注意到,在执行 Python 代码时,会(有时)创建一个名为__pycache__的目录,其中包含许多扩展名为.pyc的文件。
在今天的简短教程中,我们将讨论由 Python 解释器创建的这些文件的用途。我们将首先讨论为什么生成它们,如何抑制它们的创建,以及如何确保它们不会被提交给任何远程存储库。
。pyc 文件和 pycache 文件夹
Python 是一种解释型语言,这意味着你的源代码在运行时被翻译成一组能够被 CPU 理解的指令。当运行你的 Python 程序时,源代码被编译成字节码其中是 CPython 的一个实现细节(Python 的原始实现)。字节码也缓存并存储在.pyc文件中,这样下次重新运行代码时,同一个文件的执行速度会更快。
请注意,本节中围绕解释器和字节码讨论的几个概念过于简单,仅部分正确,但它们足以帮助您理解 *pyc* 文件和 *__pycache__* 文件夹。我计划在以后的文章中更详细地介绍这些概念。
因此,在第一次执行您的源代码之后,将会创建一个__pycache__文件夹,其中包含几个与您的.py文件同名的.pyc字节码文件。如上所述,这些将在后续执行中使用,以便您的程序启动得更快一些。
每次修改源代码时,都会重新编译,并再次创建新的字节码文件。请注意,在某些情况下,这可能不是真的,Python 将使用缓存文件执行代码,这给你带来了一些麻烦。例如,您可能已经修复了一个 bug,但是 Python 可能运行在一个有 bug 的缓存版本上。在这种情况下,您可能必须删除__pycache__文件夹,或者甚至禁止创建这些文件,如下节所示。
禁止创建 pycache
当使用 CPython 解释器(不管怎样,它是 Python 的原始实现)时,您可以通过两种方式禁止创建该文件夹。
第一个选项是在运行 Python 文件时传递-B标志。当提供该标志时,Python 不会试图在导入源模块时写入.pyc文件:
**python3 -B my_python_app.py**
或者,您可以将PYTHONDONTWRITEBYTECODE环境变量设置为任何非空字符串。同样,这将阻止 Python 试图写.pyc文件。
**export PYTHONDONTWRITEBYTECODE=abc**
请注意,这两种方法是等效的。
正在将 pycache 添加到。gitignore 文件
当在本地存储库中工作时,Git 将跟踪 Git repo 下的每个文件。每个文件都可以被跟踪(即已经暂存并提交)未跟踪(未暂存或提交)或忽略。
在大多数情况下,您应该忽略特定的文件,例如包含敏感数据的文件、特定于系统的文件或由 IDE 或特定工作区创建的自动生成的文件。
最优雅的方式是通过位于远程 Git 存储库顶层目录中的.gitignore文件,在该文件中,您可以显式指定 Git 将忽略并且不再跟踪的文件或目录(也可以应用正则表达式)。
__pycache__是不应该推送到远程存储库的目录之一。因此,您需要做的就是在.gitignore文件中指定目录。
# .gitignore__pycache__/
注意,对于一般的 Python 项目,有更多的文件需要放入.gitignore。更全面的列表,请参考本文件。
最后的想法
在今天的文章中,我们讨论了在.pyc文件中缓存的字节码,以及它们和__pycache__目录的用途。此外,我们探讨了如何抑制这个目录的创建,以及如何避免将它们包含在 Git 提交中(从而意外地将它们推送到远程存储库)。
关于字节码的一个重要警告是, *.pyc* 文件将在对等的 *.py* 文件不再存在的情况下使用。例如,如果您删除或重命名了一个 *.py* 文件,但出于某种原因,您仍然可以看到它们以任何可能的方式被执行,那么这可能是实际原因。
成为会员 阅读介质上的每一个故事。你的会员费直接支持我和你看的其他作家。你也可以在媒体上看到所有的故事。
https://gmyrianthous.medium.com/membership
你可能也会喜欢
PyCaret 2.3.6 来了!了解新内容?
原文:https://towardsdatascience.com/pycaret-2-3-6-is-here-learn-whats-new-1479c8bab8ad
从 EDA 到部署到 AI 公平性 PyCaret 迄今为止最大的版本

官方文档—https://py caret . git book . io
🚀介绍
PyCaret 是一个用 Python 编写的开源、低代码的机器学习库,可以自动化机器学习工作流。这是一个端到端的机器学习和模型管理工具,可以成倍地加快实验周期,提高您的工作效率。
到目前为止,PyCaret 2.3.6 是新特性和功能方面最大的版本。本文展示了在最近发布的 PyCaret 2.3.6 中添加的新功能的使用。
重要链接
📚官方文件:py caret 的圣经。一切都在这里。
🌐官网:查看我们的官网
😺 GitHub 查看我们的 Git
⭐ 教程py caret 新手?查看我们的官方笔记本!
📋社区创建的示例笔记本。
📙博客投稿人的教程和文章。❓常见问题解答查看常见问题。
📺视频教程我们的视频教程来自各种事件。
📢讨论有疑问?与社区和贡献者互动。
🛠️ 变更日志变更和版本历史。
🙌用户组加入我们的 Meetup 用户组。
💻装置
安装很容易,只需几分钟。PyCaret 从 pip 的默认安装只安装在 requirements.txt 文件中列出的硬依赖项。
pip install pycaret
要安装完整版:
pip install pycaret[full]
📈仪表盘
此功能将为已训练的模型生成交互式仪表板。仪表板是使用解释器仪表板实现的。
**# load dataset**
from pycaret.datasets import get_data
data = get_data('iris')**# init setup**
from pycaret.classification import *
s = setup(data, target = 'species', session_id = 123)**# train model** lr = create_model('lr')**# generate dashboard** dashboard(lr)

解释器仪表板
视频演示:
📊探索性数据分析
该功能将使用 AutoViz 集成生成自动化 EDA。
**# load dataset**
from pycaret.datasets import get_data
data = get_data('iris')**# init setup**
from pycaret.classification import *
s = setup(data, target = 'species', session_id = 123)**# generate EDA** eda()

PyCaret 中的探索性数据分析(EDA)
视频演示:
🚊转换模型
该函数将训练好的机器学习模型转换成不同编程语言(Python,C,Java,Go,JavaScript,Visual Basic,C#,PowerShell,R,PHP,Dart,Haskell,Ruby,F#)的原生推理脚本。如果您想将模型部署到无法安装普通 Python 堆栈来支持模型推理的环境中,那么这个功能非常有用。
**# load dataset**
from pycaret.datasets import get_data
data = get_data('iris')**# init setup**
from pycaret.classification import *
s = setup(data, target = 'species', session_id = 123)**# train model** lr = create_model('lr')**# convert model** lr_java = convert_model(lr, language = 'java')
print(lr_java)

在 PyCaret 中转换模型
视频演示:
☑️检查公平性
有许多方法来概念化公平。这一新功能遵循了被称为群体公平的方法,该方法询问:哪些群体的个体有遭受伤害的风险。该函数提供不同组(也称为子群体)之间的公平性相关指标。
**# load dataset**
from pycaret.datasets import get_data
data = get_data('income')**# init setup**
from pycaret.classification import *
s = setup(data, target = 'income >50K', session_id = 123)**# train model** lr = create_model('lr')**# check fairness** check_fairness(lr, sensitive_features = ['race'])

检查 PyCaret 中的公平性
视频演示:
📩创建 Web API
该函数将为 ML 管道创建一个 POST API,以便使用 FastAPI 框架进行推理。它只创建 API,并不自动运行它。
**# load dataset**
from pycaret.datasets import get_data
data = get_data('iris')**# init setup**
from pycaret.classification import *
s = setup(data, target = 'species', session_id = 123)**# train model** lr = create_model('lr')**# create API** create_api(lr, 'my_first_api')**# Run the API
!python my_first_api.py**

在 PyCaret 中创建 API 函数

在 PyCaret 中创建 API 函数(在本地主机上测试)
视频演示:
🚢创建 Docker
这个函数将为您的 API 端点创建一个Dockerfile和requirements文件。
**# load dataset**
from pycaret.datasets import get_data
data = get_data('iris')**# init setup**
from pycaret.classification import *
s = setup(data, target = 'species', session_id = 123)**# train model** lr = create_model('lr')**# create API** create_api(lr, 'my_first_api')**# create Docker** create_docker('my_first_api')

在 PyCaret 中创建 Docker 函数
视频演示:
💻创建 Web 应用程序
该功能创建一个基本的 Gradio web 应用程序用于推理。它稍后将扩展到其他应用程序类型,如 Streamlit。
**# load dataset**
from pycaret.datasets import get_data
data = get_data('iris')**# init setup**
from pycaret.classification import *
s = setup(data, target = 'species', session_id = 123)**# train model** lr = create_model('lr')**# create gradio app** create_app(lr)

在 PyCaret 中创建应用程序函数
视频演示:
🎰监控 ML 模型的漂移
一个名为drift_report的新参数被添加到predict_model函数中,该函数使用显然是 AI 框架生成漂移报告。目前,这项功能还处于试验阶段,只能用于测试数据。稍后,它将被扩展用于生产用途。
**# load dataset**
from pycaret.datasets import get_data
data = get_data('iris')**# init setup**
from pycaret.classification import *
s = setup(data, target = 'species', session_id = 123)**# train model** lr = create_model('lr')**# generate report** preds = predict_model(lr, drift_report = True)

监控 PyCaret 中的漂移

PyCaret 中的漂移报告(显然是使用 AI 生成的)
视频演示:
🔨地块模型现在更易于配置
函数 PyCaret 现在更具可配置性。例如,以前如果你想在混淆矩阵中看到百分比而不是绝对数字,这是不可能的,或者如果你想改变视觉效果的颜色图,这是不可能的。现在有了plot_model函数中的新参数plot_kwargs就有可能了。参见示例:
**# load dataset**
from pycaret.datasets import get_data
data = get_data('iris')**# init setup**
from pycaret.classification import *
s = setup(data, target = 'species', session_id = 123)**# train model** lr = create_model('lr')**# plot model (without plot kwargs)** plot_model(lr, plot = 'confusion_matrix') **# plot model (with plot kwargs)** plot_model(lr, plot = 'confusion_matrix', plot_kwargs = {'percent' : True})

PyCaret 中的 plot_model 函数
🏆优化阈值
这不是一个新功能,但是在 2.3.6 中已经完全修改了。该函数用于优化二元分类问题的概率阈值。以前,您必须将成本函数作为true_positive、false_positive、true_negative、false_negative传递到这个函数中,现在它会自动选取所有指标,包括来自您的活动实验运行的自定义指标。
**# load dataset**
from pycaret.datasets import get_data
data = get_data('blood')**# init setup**
from pycaret.classification import *
s = setup(data, target = 'Class', session_id = 123)**# train model** lr = create_model('lr')**# optimize threshold** optimize_threshold(lr)

优化 PyCaret 中的阈值函数
📚新文档
最大也是最难的是全新的文档。从官方教程到发行说明,从 API 参考到社区贡献,这是 PyCaret 所有相关内容的唯一来源。观看视频:
最后,如果您想浏览 2.3.6 中添加的所有新功能,请观看这段 10 分钟的视频:
要了解 PyCaret 2.3.6 中的所有其他更改、错误修复和微小更新,请查看详细的发行说明。
感谢您的阅读。
作者:
我写的是 PyCaret 及其在现实世界中的用例,如果你想自动得到通知,你可以在 Medium 、 LinkedIn 和 Twitter 上关注我。
PyCaret 3.0 即将推出-有什么新功能?
原文:https://towardsdatascience.com/pycaret-3-0-is-coming-soon-whats-new-e890e6a69ff5
首个候选版本最早将于 2022 年 5 月推出

安迪·赫尔曼万在 Unsplash上的照片
介绍
我们致力于 PyCaret 3.0 已经有一段时间了。此版本的主要目标是集成一个新的面向对象的 API,包括 PyCaret 的新时序模块,并提高管道性能和效率。这篇文章是对下一个版本的快速回顾。
PyCaret 是一个用 Python 编写的开源、低代码的机器学习库,可以自动化机器学习工作流。这是一个端到端的机器学习和模型管理工具,可以成倍地加快实验周期,提高您的工作效率。
想了解更多关于 PyCaret 的信息,可以查看官方网站或者 GitHub 。
完全兼容最新版本的 scikit-learn
PyCaret 2。x 需要 scikit-learn 0.23.2,这意味着如果您想在同一个 Python 环境中使用最新版本的 scikit-learn 和 PyCaret,您不能。直到现在。PyCaret 3.0 将与最新版本的 scikit-learn 完全兼容。

面向对象的 API
PyCaret 很棒,但是它缺少 Python 中最常见的工作方式。通过对象和类。这个变化要求我们重新思考我们在 1.0 发布时所做的许多设计决策。不用说,这是一个重大且具有挑战性的变革。让我们看看这对你意味着什么。
# Functional API (Existing)# load dataset
from pycaret.datasets import get_data
data = get_data('juice')# init setup
from pycaret.classification import *
s = setup(data, target = 'Purchase', session_id = 123)# compare models
best = compare_models()

compare_models 的输出
这很好,但是如果现在您想在同一台笔记本电脑上用设置功能中的不同参数运行另一个实验呢?你可以这么做,但这会覆盖第一次实验的设置。使用我们新的面向对象的 API,因为设置与对象相关联,所以您可以在同一个笔记本中运行任意多的实验,并无缝地比较它们,不仅可以跨不同的建模选项,还可以跨不同的预处理设置。
**# load dataset**
from pycaret.datasets import get_data
data = get_data('juice')**# init setup 1** from pycaret.classification import ClassificationExperiment
exp1 = ClassificationExperiment()
exp1.setup(data, target = 'Purchase', session_id = 123)**# compare models init 1** best = exp1.compare_models()**# init setup 2** exp2 = ClassificationExperiment()
exp2.setup(data, target = 'Purchase', normalize = True, session_id = 123)**# compare models init 2** best2 = exp2.compare_models()

exp1.compare_models()的输出

exp2.compare_models()的输出
您还可以使用get_leaderboard功能为每个实验生成排行榜,然后进行比较。
**# generate leaderboard** leaderboard_exp1 = exp1.get_leaderboard()
leaderboard_exp2 = exp2.get_leaderboard()
lb = pd.concat([leaderboard_exp1, leaderboard_exp2])

排行榜(为了显示,输出被截断)
**# print pipeline steps**
print(exp1.pipeline.steps)
print(exp21.pipeline.steps)

管道支出 1 /支出 2 步骤
您还可以随意在函数式 API 和面向对象 API 之间切换。
**# set current experiment to exp1**
from pycaret.classificatiom import set_current_experiment
set_current_experiment(exp1)
时间序列模块
PyCaret 的时序模块作为一个独立的 PyPI 库(pycaret-ts-alpha)已经有一段时间了。现在它终于走到了一起,并将在 PyCaret 3.0 中普遍可用。
**# load dataset** from pycaret.datasets import get_data
data = get_data('airline')**# init setup**
from pycaret.time_series import *
s = setup(data, fh = 12, session_id = 123)**# compare models** best = compare_models()

compare_models 的输出
**# forecast plot** plot_model(best, plot = 'forecast')

plot_model 的输出(最佳,plot = '预测')
改进的管道
预处理模块被完全重写,以实现与 scikit-learn 最新版本的兼容性,并提高效率和性能。3.0 中引入了几个新的预处理功能。x,其中之一是不同类型的分类编码。直到 2 点。x,分类特征仅使用一个热编码进行编码。2 中相同random_state的实验快速对比。x 对 3.X。

2 的比较。X / 3。收入数据集上的 x
3.0 中的一些新功能包括:
- 新的分类编码技术
- 为机器学习建模处理文本特征
- 检测异常值的新技术
- 特征选择的新技术
- 保证避免目标泄漏
模块化和轻量化
我们特别关注模块化和使 PyCaret 3.0 轻量级。如果只是比较 2 的requirements.txt。x 对 3。x,它的依赖性减少了 33%,并且在安装时间方面也有显著的改进。也将有一个单独安装模块的选项,如pip install pycaret[nlp]将安装 NLP 相关的依赖项。

PyCaret 2 的 requirements.txt 比较。x 对 3。X
自动化数据类型处理
没有更多数据类型确认。当你在 3 中运行setup时。x,它不会出现在这里。然而,您仍然有能力使用numeric_features和categorical_features参数覆盖数据类型(这不会改变),但是setup函数的默认行为现在是静默的。因此silent参数将从设置中删除。

文本特征工程
PyCaret 3。x 将能够处理文本输入。如果您的数据集中有一个文本列,没问题,我们将从中提取要素以用于模型训练。setup中有两个新参数来解决这个问题。

PyCaret 中的文本功能
我们正在寻找 3.0-rc 测试人员。请加入我们的松驰帮我们发布。
重要链接
📚文档py caret 的详细 API 文档
⭐ 教程py caret 新?查看我们的官方笔记本!
📋社区创建的示例笔记本。
📙博客投稿人的教程和文章。
📺视频教程我们的视频教程来自各种赛事。
📢讨论与社区和贡献者互动。
🛠️ 变更日志变更和版本历史。
作者:
我写的是 PyCaret 及其在现实世界中的用例,如果你想自动得到通知,你可以在媒体、 LinkedIn 和 Twitter 上关注我。
PyCaret 分类:一个诚实的审查
原文:https://towardsdatascience.com/pycaret-review-65cbe2f663bb
Python 的低代码机器学习库

我知道,这是脱字符而不是胡萝卜,但仍然… |照片由 Miggz 在 Unsplash 上拍摄
又一个 ML 库?为什么?嗯,我不得不做一些快速 ML 工作,想尝试一些相当新的东西。我看到 PyCaret 到处跑,所以我必须试一试。
PyCaret 到底是什么? PyCaret 是 Python 的低代码开源机器学习库。它基本上包装了一堆其他库,如sklearn和xgboost,并使超级容易尝试许多不同的模型,混合它们,堆叠它们,搅拌锅,直到有好的东西出来。从 0 到英雄只需要很少的代码。
PyCaret 允许您尝试许多不同的模型,并有效地将它们集成为更好的模型。
今天我就来看看 PyCaret 的分类模块,不过它也可以做回归,NLP,异常检测,聚类。我会告诉你它有什么好处,为什么以及何时应该使用它,还有我不喜欢它的什么地方。
要获得所有媒体文章的完整信息,包括我的文章,请点击这里订阅。
让我们开始建立模型

归功于 XKCD
因为我喜欢例子,所以我通过一个例子来做这个回顾。我们将从乳腺癌威斯康星(诊断)数据集中构建一个分类器,我们可以轻松地用sklearn加载它:
我在这里导入了mlfow,因为 PyCaret 可以很好地使用它。这是分数 1,但是我们会更深入一点。现在,让我们看看准备数据集有多容易:
这个设置呼叫有很多其他选项,我建议你查看文档了解更多细节。这里最重要的是:
- 设置要在其上构建模型的数据框架
- 设置
target列名 - 将按照您的预期进行操作,并将缩放功能列,以便算法有更轻松的时间
log_experiment将实验记录到我们的mlflow实验中,experiment_name指定我们想要跟踪的确切实验
如果你在一个交互式环境中运行这个,比如本地 Jupyter 笔记本或者 Google Colab,那么你会被问一些问题,比如列类型。这相当简单,在这种情况下 PyCaret 在推断列类型方面做得很好,所以除了类型y之外,我们不需要做任何事情来确认。

PyCaret 将推断数据类型,并允许您以交互方式更改这些数据类型——作者截图
现在我们真的准备好训练一些模型了。但是是哪些呢?嗯,为什么不尝试一大堆型号?
这将尝试相当多的可用分类模型,根据它们的f1分数对它们进行排序,并将表现最好的 5 个模型保存到一个列表中。
太简单了,对吧?——gif 来自期限
这是我们的输出:

已训练模型的指标—作者截图
现在告诉我你是否曾经训练过 12 个模型,包括比这更快的虚拟基线分类器?这太牛逼了!
Mlflow 和 PyCaret 是朋友
此时,我想展示如何从 Google Colab 连接到mlflow ui,因为我一直在那里运行这段代码。谢天谢地,有人已经发现了这一点,所以我所需要做的就是用pip安装pyngrok,注册pyngrok以获得一个令牌,并运行以下代码:
然后点击印在底部的网址,你就到了天堂👼:

来自 Google Colab 的 mlflow UI 由作者捕获
如果你在本地运行,那么你可以直接跳转到本地的
mlflowUI URL。如果你在 Databricks 上,那么你会在屏幕的右上角自动获得 UI 链接。
用一行代码调优模型🤯
现在我们有了这个任务的前 5 个模型,让我们继续做一些超参数调整** …在一行代码中!**
是的,这仍然是一行代码。
只需调用tune_model并传入我们的模型对象,PyCaret 就会为我们调优所有 5 个模型。所有这些仍然在mlflow中跟踪,因此您可以返回到 UI 并查看改进等。
在一行代码中混合模型😎
当你可以融合它们的时候,为什么要满足于单一的模式呢?这是 Kagglers 的一项流行技术,其中一个人将采用多个模型,然后在模型上使用投票分类器——平均分数——以产生更强大的分类器。同样,PyCaret 可以在一行代码中为您做到这一点:
使用 PyCaret 绘图📝
一旦我们的模型被训练好了,我们很自然地会想看看它在视觉上的表现。在上面提到的mlflow UI 之上, PyCaret 也可以非常容易地生成诊断图。你可以绘制 ROC 曲线、混淆矩阵和一大堆其他有用的东西。

作者截图
如您所见,这显然是一个超级干净的数据集,对于今天的工具来说是一个非常简单的问题。
超级简单的预测
使用 PyCaret,您也不需要担心产生预测。这再一次变得超级简单,整个数据集以 dataframe 的形式返回,在末尾有一个额外的Label列,为您提供预测的类:

不确定你能不能看到这个,但是最后有一个标签栏,相信我!—作者截图
设置
raw_scores=True查看概率。
超级棒的医生👏
以上总结了我们对一个简单模型的示例运行。您可以看到 PyCaret 非常容易用于分割训练/测试数据集、训练模型、调整它们、混合它们,甚至使用这些模型进行预测。最重要的是,整个事情可以在mlflow中直接跟踪。
但也许 PyCaret 最好的特性是它的 文档 。我发现很容易阅读,组织得很好,不同的 ML 任务有不同的部分,警告/提示分散在各处。它甚至还附带了一堆 教程笔记本 。真的很容易上手。
坏的和丑的
你已经看到了好的部分,但如果不谈论坏的部分,这将是一个不公平的评论。我遇到了一些烦人的事情。
错误消息
尽管文档很好,错误消息并不多。举个例子,我试图用 PyCaret 做一些特性工程,创建一个列分组。问题是我的一些列被推断为str类型,而另一些列被推断为int。现在,PyCaret 不再友好地说“嘿,你有冲突的列类型”,它从不检查它们,而是在堆栈的最底部用失败:“不能比较 int 和 str”。对于一个新人来说,这可能会引起很多麻烦。考虑到 PyCaret 想要低代码,它应该迎合那些没有多少编码经验的人,否则这些错误消息可能会让新手感到非常困惑。
全球状态混乱

拉尔夫(拉维)凯登在 Unsplash 上的照片
您可能已经注意到了这一点,但是我们实际上并没有传递 setup 对象。我们刚刚说了使用此数据框架进行设置,然后给出了前 5 个模型。PyCaret 可以做到这一点,因为它使用全局作用域来存储关于您的设置和意图的信息。
出于几个原因,这是一个非常糟糕的编程实践。首先,这会降低代码的可复制性,对全局状态的更改会对您的模型产生意想不到的后果。如果您稍后运行另一个安装脚本会怎么样?其次,只是很难读懂。当计算出函数的输入时,您如何知道要寻找什么对象或代码行呢?
我知道 PyCaret 希望代码尽可能少,但我认为更严格的设置过程会让它受益。创建一个设置对象并显式地传递它。或者有一个类似sklearn和sparkml的管道模型。这可以使人们从许多令人头痛的事情中解脱出来,并且会使那些必须阅读 ML 代码的工程师们更加高兴。
太多的争论
对于某些函数来说,参数的数量可能会让人不知所措。我是说,看看这个:

链接到文本 —作者截图
里面有很多东西。而且很容易漏掉一些东西。现在,我不得不告诉开发者,这在大多数地方都有非常合理的默认设置,但这仍然是对单个功能的大量责任。
更好的方法可能是将数据集分割、插补/清理和特征提取分成不同的类别或功能。然后,这些对象 c 可以被传递到主设置函数中。
太多模型
拥有强大的力量……—来源:吉菲
PyCaret 支持许多不同的模型,这很好,但不幸的是,其中一些模型与其他模型不同,不会产生相同的输出。不要期望我在这里提出一个解决方案,因为模型对象一致性不是一个简单的问题,但是让我给你一个你可能会发现的问题类型的例子。
GaussianNB一直以顶级表演者的身份出现,然而sklearn的实现并不产生概率,这意味着当混合时间到来时,整个事情都失败了。我可以解决这个问题的唯一方法是手动从最佳表现者中删除模型,或者预先指定一个模型列表。
⭐️⭐️⭐️⭐️⭐️总体印象

由 Towfiqu barbhuiya 在 Unsplash 上拍摄的照片
如果你想开始解决一个新问题,PyCaret 是一个很好的起点。不费吹灰之力,你马上就能得到一个工作模型。然后你可以很容易地开始调整和堆叠这些模型。一旦你对你的怪物组合满意了,预测结果就再简单不过了。这将使您能够专注于特征工程和将您的模型投入生产。
这是一款真正出色的工具,具有:
- 简单易用的界面
- 大量可供选择的型号
- 优秀的文档
- 伟大的
mlflow整合
我故意没有深入研究 PyCaret 的更多功能,但您应该知道,它也可以用于并行(或在 GPU 上)训练模型,可以处理数据集分割、插补、特征提取和特征创建。它可以做很多事情,所以 在深入研究之前一定要阅读文档 !
谷歌 Colab 笔记本的链接可以在 GitHub 上找到。
现在去做一些模型吧!
感谢您花时间阅读本文!我主要写的是数据科学工具和 Julia,所以如果你喜欢这篇文章,请随意订阅!
PyCon US 2022 概述
原文:https://towardsdatascience.com/pycon-us-2022-recap-2bbf2e59801e
一些我们最喜欢的演讲和要点

PyCon 2022 标志。图片由 PyCon US 2022 提供。
从 2022 年 4 月 27 日到 5 月 1 日,我们参加了第一届PyCon——Python 编程语言最大的年度大会。每年,PyCon 在世界各地举行几次会议,我们参加了在犹他州盐湖城举行的美国会议。这真是一次非常棒的经历。
虽然常规与会者说人群是前疫情派孔斯的一半,我们仍然发现活动充满了伟大的人和谈话。因此,我们想回顾一些我们最喜欢的与数据科学相关的演讲。
如果你不能参加 PyCon,不要担心!PyCon 计划让每个人都能在他们的 YouTube 频道上欣赏这些演讲。不幸的是,PyCon 在录制研讨会时遇到了一些技术困难,无法在 YouTube 上发布我们的 NLP 研讨会。所有其他讲座链接如下。
以下是我们将涉及的演讲的简要概述:
- 自然语言处理导论——莉亚·辛普森和雷·麦克兰登
- Lukasz Langa 的主题演讲
- 王蒙杰的主题演讲
- 如何在整个企业中成功使用 Python——Greg Compestine
- 利用快照提高应用性能— Juan Gonzalez
- 测试机器学习模型——卡洛斯·基德曼
- 使用 DVC 和 Streamlit 为 Python 编码人员提供灵活的 ML 实验跟踪系统——Antoine Toubhans
- 写更快的 Python!常见的性能反模式—安东尼·肖
- 雷·麦克伦登的闪电谈话
自然语言处理导论— 莉亚·辛普森和雷·麦克伦登
在会议开始前两天,PyCon 提供了几个不同的教程,涉及各种各样的主题。这些教程是与会者了解某个主题并实际应用 Python 实例的好方法。
我们很高兴被选中去做一个关于 Python 中自然语言处理(NLP)的三小时初级研讨会。我们涵盖了从文本预处理技术到主题建模的所有内容。与会者通过分析谷歌 Colab 笔记本中的 500 篇亚马逊家居和厨房产品评论获得了现实世界的经验。
不幸的是,PyCon 在我们的研讨会期间遇到了技术困难,无法按计划发布到 YouTube 上。然而,我们在 Skillshare 上有一个浓缩的一小时版本的研讨会。如果你想读一篇文章,我们在这里为你准备了。
现在,让我们深入到会议中我们最喜欢的一些演讲中。
主题演讲— 卢卡斯·兰加
Python 的 Black formatting library 的创始人 Lukasz Langa 以关于 Python 中类型注释的重要性的主题演讲开始了第一天的会议。类型注释用于描述变量的数据类型和函数的输入/输出。文档,尤其是类型注释,通常是开发人员或数据科学家最不想做的事情,所以很高兴看到 Lukasz 在这方面有所建树。

让类型注释变得非常复杂很有诱惑力,但是 Lukasz 强调难看的类型注释暗示着难看的代码。它们不会产生额外的复杂性,而是暴露已经存在的复杂性。重要的是,类型注释要尽可能简单,以便人们能够理解它们。如果别人不能理解你的类型注释,你可以考虑重构。
Lukasz 在他的主题演讲中给出了各种各样的提示和技巧,但是我们最喜欢的一个是一个 str (string)类型的注释通常可以更加具体。例如,字符串可能是电子邮件地址、文件路径或姓氏。
主题演讲— 王蒙杰
Anaconda 首席执行官王蒙杰分享了他和他的团队开发的令人兴奋的新框架,名为 PyScript 。他们的网站称 PyScript“允许用户使用 HTML 的界面在浏览器中创建丰富的 Python 应用程序。”
PyScript 让数据科学家们兴奋不已,因为它可能允许他们共享他们的模型和 HTML 文件中的结果,当在浏览器中打开该文件时,将执行该文件。

Peter 还描述了 PyScript 对 STEM 教育的巨大影响。因为几乎每个人都可以访问网络浏览器,PyScript 可以帮助孩子们更快地开始编程,而没有任何基础设施障碍。
到目前为止,可能已经有很多关于 PyScript 的文章,但是要了解更多信息,我们推荐 Anaconda 的这篇文章。
如何跨企业成功使用 Python—Greg Compestine
虽然这次演讲没有特别关注数据科学,但它确实击中了我们的要害。如果你是一个大型组织的数据科学家,并且你是唯一使用 Python 的部门或领域之一,这个演讲是为你准备的。
不熟悉 Python 的组织对数据科学家来说是一个严峻的挑战,因为他们试图通过 IT、法律和任何其他提供繁文缛节的部门获得所需的工具和环境。如果让 Python 在本地机器上运行是一场战斗,那么等到您必须让它在生产环境中工作时再说。
正如 Greg 所解释的,要想在整个企业中取得成功,您需要对 Python 的支持。提供支持的一种方式是在您的组织中创建一个 Python 公会。Greg 给出了创建一个成功的公会的几个建议,包括确保宽松的公会成员要求。任何对 Python 感兴趣的人(不管技能水平如何)都应该被允许成为公会成员。

公会成员将是任何负责构建工具的基础设施团队的关键。它也代表了 It 部门在测试新 Python 版本发布时可以接触到的一个群体。我们实际上已经看到了企业范围内的 Python 推送,这些推送没有提前在一个小团队中进行测试,结果导致虚拟环境崩溃。公会可以作为这类更新的测试者。
在演讲的后面,格雷格提到了一个非常关键的问题。为了更好地参与到工会中,你应该将它作为使用 Python 的员工的预期职责的一部分。太多时候,员工被赋予堆积如山的工作,还被“鼓励”参加这样的社区。这种方法我们一直不太成功。在你的软件开发人员、数据工程师、数据科学家或机器学习工程师的职责中包含社区参与是一个更好的方法。这将确保公会有多元化的参与者,可以给他们所依赖的各个团队很大的反馈。
利用快照提高应用性能— 胡安·冈萨雷斯
在这个演讲中,Juan 讨论了由于使用对象关系映射器(ORM)的数据库查询而导致的缓慢性能。你可能熟悉 Python 中的 ORM,如 SQLAlchemy 或 Django 。虽然 ORM 可以让您的代码更 Pythonic 化,但是它们也有一些严重的缺点。
一个缺点是,您可能不知道 ORM 生成的实际 SQL 查询是什么样子的。我们的查询对我们隐藏的事实意味着我们不能调试它。
Juan 引入了一个名为 snapshot_queries 的库,允许您查看 ORM 生成的所有 SQL 查询。下面是该库 GitHub 的一些代码,展示了它是如何工作的。
from django.contrib.auth import get_user_model
from snapshot_queries import snapshot_queries
User = get_user_model()
with snapshot_queries() as queries_executed:
User.objects.only('email').get(id=1)
User.objects.only('email').get(id=7)
queries_executed.display()
在使用 ORM 展示了看似非常简单的查询之后(见上文),Juan 使用 snapshot_queries 库展示了实际在幕后运行的 SQL。谈话中的每个人都清楚实际的查询是多么低效。在这个库的帮助下,您可以调整您的查询,并有可能降低您的基础设施成本。我们怎么推荐这个演讲都不为过。我们在下面包含了一些示例输出来说明库的结果。
Query 1
---------
2 ms
/path/to/module.py:5 in function_name
User.objects.only('email').get(id=1)
SELECT "auth_user"."id",
"auth_user"."email"
FROM "auth_user"
WHERE "auth_user"."id" = 1
Query 2
---------
< 1 ms
/path/to/module.py:6 in function_name
User.objects.only('email').get(id=7)
SELECT "auth_user"."id",
"auth_user"."email"
FROM "auth_user"
WHERE "auth_user"."id" = 7
测试机器学习模型— 卡洛斯·基德曼
卡洛斯在强调传统软件和机器学习之间的差异方面做得非常出色。Carlos 介绍了在正确测试机器学习模型时要考虑的架构、数据和模型。具体的数据考虑事项包括确定数据来自哪里、存储在哪里,以及数据是流数据还是批处理数据。
卡洛斯强调,模型应该进行行为、公平性和可用性测试,而不是只使用学术界教授的经典指标,如准确性、精确度和召回率。我们需要知道用户是如何体验这个模型的。
如果你还不熟悉测试机器学习的所有概念,这绝对是适合你的谈话。正如卡洛斯在演讲中强调的那样,缺乏适当的测试可能会导致你的组织成为下一个 Zillow ,一家使用机器学习来预测何时买卖房屋的公司。他们因糟糕的数据质量和测试而遭受巨大损失的故事是对所有人的警示。
使用 DVC 和 Streamlit 为 Python 编码人员提供灵活的 ML 实验跟踪系统— 安托万·图班斯
安托万轻松地提供了一个最聪明的谈话。首先,他实际上使用了 Streamlit 来构建他的演示文稿。从那时起,这个话题就充满了价值。虽然我们在这次演讲之前没有使用过 Streamlit,但我们非常希望在这次演讲之后开始使用它。

Streamlit 网站截图:https://streamlit.io/
安托万开始使用数据版本控制( DVC )跟踪他所有的机器学习实验。这个库通常被数据科学家用来对 Git 中的数据进行版本控制。然而,我们还没有看到很多人像 Antoine 那样使用 DVC 构建有向无环图(DAGs) 来运行机器学习管道。阿帕奇气流通常被用来制造 Dag,但是 Antoine 能够纯粹在 DVC 制造它们。
虽然这本身是一个很好的功能,但是 Git 并没有让数据易于交互。这就是 Streamlit 来拯救我们的地方。Antoine 能够轻松地构建一个漂亮的 UI 来显示他所有的机器学习实验结果。这包括一个简单的滑块,用于显示模型在某个预测置信度范围内的预测。
但是我们最喜欢的部分是能够从你的实验数据集中比较任意两个模型。通过几个下拉菜单,您可以选择任意两个模型,并查看这两个模型之间预测的所有差异。说一个很棒的功能!
写更快的 Python!常见表现反模式— 安东尼·肖
Anthony 讲述了如何让您的 Python 代码更快。如果你熟悉测试驱动开发(TDD),你就会知道任何初始编码都应该尽快完成。重点应该放在节省你的时间上,因为你的时间可能比电脑的时间更贵。但是一旦你写出了有效的代码,就该重构了。当我们重构时,我们并不试图改变代码的行为。我们仍然希望它做和以前一样的事情,但是我们希望它更快、更易读、更易维护。
Anthony 做了很好的工作,介绍了如何使用分析器来帮助加速您的代码。他还就何时针对速度和可读性进行优化给出了很好的建议。此外,他给出了最好的数据结构以及最好的分析器。我们一直使用 Python 自带的标准分析器,但是 Anthony 共享了库 Austin 和 Scalene 作为高级分析器。它们比现成的 Python 分析器运行得更快,并且它们提供逐行的性能数据,而不是函数级的性能数据。似乎这还不够,Anthony 还提供了一系列关于如何加速代码的技巧。很明显,安东尼是这方面的专家。
行为驱动的机器学习— 雷·麦克伦登
虽然这不是会议计划中的演讲之一,但是这篇文章的作者之一(Ray)做了一个非常有趣的闪电演讲!在 PyCon 上,闪电谈话是与会者在最多五分钟的时间里在舞台上展示他们所感兴趣的任何东西的机会。
在这个演讲中,Ray 解释了他在学校被教导如何构建机器学习模型与他现在在行业中如何构建它们之间的差异。虽然他被教导在构建模型时使用整个数据集,但这种方法存在一些问题。在他的演讲中,他分享了一种迭代方法来建立一个优秀的模型,在这个模型中,你不需要使用所有的数据。
结论
总的来说,我们在 PyCon 上玩得很开心,也喜欢和其他与会者交流。我们一定会回来的!虽然我们对机器学习讲座更感兴趣,但我们在面向通用软件开发的会议中发现了一些很好的信息和技术。如果您是一名数据科学家,正在寻找参加 Python 会议的机会,如果您有兴趣成为一名更全面的程序员并学习更多关于软件开发的知识,我们推荐您参加这个会议。
Pydantic 还是 dataclasses?为什么不两者都要?在它们之间转换
在 FastAPI 项目中使用 dataclasses,并加速您的 pydantic 模型操作

图片作者。
Python 数据类非常棒。Pydantic 太棒了。如果我们真的面临选择一个或另一个,这是一个艰难的选择。我会说,比较这两个伟大的模块就像比较梨和苹果,尽管在某些方面相似,但总体上不同。
Pydantic 的竞技场是数据解析和净化,而 dataclasses a 是一个快速且内存高效的(尤其是使用 slots,Python 3.10+) 通用数据容器。查看这个故事,在这里我彻底比较了 Python 数据容器,包括 pydantic 和 dataclasses。
然而,有时,似乎某些代码依赖试图让我们做出选择。一个很好的例子是使用 FastAPI 时;它建立在 pydantic 之上。如果我们的应用程序很简单,我们也可以用 pydantic 做所有的数据模型,避免任何兼容性问题。另一个例子是当我们继承一个已经在使用 dataclasses 的代码库时。为什么我们在使用 FastAPI 时会错过数据类的性能呢?当我们需要使用 FastAPI 实现 web API 时,如何将我们的 dataclasses 模式转换为 pydantic 模式呢?顺便说一下,FastAPI 也很棒。
在这个故事中,我们使用一些简单的技巧将动态平面数据类转换成 pydantic,反之亦然。最后,我们将使用一个 FastAPI 测试这种实际转换的实现。
故事结构
- 数据类(无默认值)转换为 pydantic
- 默认为 pydantic 的数据类
- 数据类中的 pydantic(无默认值)
- 数据类中有默认值的 pydantic
- 构建工具:数据类到 pydantic
- 构建工具:从 pydantic 到 dataclass
- FastAPI 示例
数据类(无默认值)到 pydantic
问题是:
为了解决这个问题,我们使用 pydantic 的实用程序来动态创建模型, pydantic.create_model 。我们需要为这个函数提供一个动态创建的模型的名称。我们还需要为 kwargs(每个关键字参数都是动态创建的模型中的属性名称)提供一个属性类型和默认值的元组(如果没有默认值,那么使用…代替)。
为了获得 pydantic 模型所需的信息,我们使用 dataclasses 模块中的字段实用程序;使用字段,我们可以访问属性的特性。
现在,我们可以创建动态生成的 pydantic 模型的实例:
打印模型及其类型:
name = ' Diego ' age = 33
<class ' pydantic . main . dynamicpydanticancer '>
所以我们在这里是金色的。
数据类中的 pydantic(无默认值)
问题是:
我们现在使用 dataclasses 实用程序来动态创建类( dataclasses.make_dataclass),其中需要动态创建的类的名称和每个属性的元组列表;每个元组应该包含属性的名称和类型。
我们可以从 dataclasses 模块中用 is_dataclass 来检查这个类是否确实是一个 dataclass。所以我们这样做并实例化这个类:
prints:
True
dynamic person(姓名='Diego ',年龄=33 岁)
这是我们预期的结果。
默认为 pydantic 的数据类
现在我们来看看 dataclass 有默认值的情况,我们想把它转换成 pydantic。
问题是:
这个想法类似于没有违约的情况;然而,现在我们需要将…替换为默认值。此外,为了检查 dataclass 的字段中的默认值是否丢失(没有默认值),我们检查字段 default 是否是 dataclasses 模块中的 _MISSING_TYPE 类的实例(这里有点麻烦,但是我们需要把事情做好)。
我们继续测试我们的dynamicpydanticancer:
打印数据类及其类型:
使用默认:
age = 33 name = ' Jane '
<class ' pydantic . main . dynamicpydanticancer '>
覆盖默认:
age = 33 name = ' Diego '
<class ' _ _ main _ _。PydanticPerson' >
正如我们所料。
数据类中有默认值的 pydantic
问题是:
请注意默认字段。
这种情况与没有违约的情况非常相似。如果属性有默认值,则每个属性的元组有三个元素,而不是两个,第三个元素是默认值( _field.required 表示没有默认值):
测试它实际上是一个数据类,以及 default 和 default override 的实例:
prints:
True
dynamic person(年龄=33 岁,姓名='John')
DynamicPerson(年龄=33 岁,姓名='Diego ')
我们准备好了。
构建工具:数据类到 pydantic
我们将数据类转换成 pydantic 模型的代码片段是成功的,有默认值和没有默认值。现在是时候用更有用的工具来包装它们了。一个为我们完成转换的函数:
我们来测试一下。
目标数据类:
将其转化为 pydantic:
印刷品:
PydanticPerson
年龄=33 姓名= '简'
注意 name 取自原始数据类(Person)的名称,前缀“Pydantic”是通过转换函数中的 f 字符串添加的。也可以选择提供自定义名称。
构建工具:从 pydantic 到 dataclass
将数据类转换为 pydantic 的函数:
我们来测试一下。
目标分解模型:
将其转换为数据类:
印刷品:
DataClassPerson
DataClassPerson(年龄=33 岁,姓名= '约翰')
注意 name 取自原始 pydantic 模型类(Person)的名称,并且在转换函数中添加了前缀“DataClass”。这是默认行为,或者可以将自定义名称传递给转换函数。
FastAPI 示例
最后给出了一个使用 FastAPI 的例子。这个简单的例子展示了在 FastAPI 中使用 dataclass 是多么容易。要使此示例生效,请将前两节中的代码保存到名为“dataclass_to_pydantic.py”的文件中,并将其放在运行以下示例的同一目录中。下面的例子应该被命名为“fast_api_example.py”,这对 uvicorn 服务器在脚本中运行非常重要。
我们创建一个数据类 FooQuery ,然后将其转换为 pydantic,作为类型传递给 foo 端点:
为了测试它,我们执行脚本并使用请求模块对 foo 端点进行简单测试:
一切按预期运行。
最后的话
现在我们知道了如何以简单的方式将平面 pydantic 转换为 dataclasses,反之亦然。这种转换在几种情况下可能很方便,包括将 dataclasses 与 FastAPI 一起使用。
然而,我认为首先要仔细考虑你的应用程序的架构,尽量避免不必要的转换。pydantic 和 dataclasses 都有很多特性和定制,其中大部分在翻译时会丢失。
最后,关于嵌套模型,它们的转换很棘手。使用故事中的代码中的工具转换成嵌套模型的实例非常简单。但是要转换成一个类型(非实例化类),我们必须首先声明所有嵌套模型的转换。如我所说,这部分很棘手。
我希望这个故事对你有用。如果你想知道更多类似的故事,请订阅。
https://medium.com/subscribe/@diego-barba
喜欢这个故事吗?通过我下面的推荐链接成为一个媒体成员来支持我的写作。无限制地访问我的故事和许多其他内容。
https://medium.com/@diego-barba/membership
如何从一个 CSV 源文件创建一个表格
原文:https://towardsdatascience.com/pyflink-how-to-create-a-table-from-a-csv-source-ca4851a71d0c
在关于 Apache Flink 的第一篇教程中,学习如何使用 Python Table API 将数据从 CSV 源导入到表中。

大卫·塞尔伯特在 Pexels.com 拍摄的照片
建议的点播课程
您是否希望了解更多关于Apache Flink,Apache Spark及其他 数据流 技术?以下是一些用 Python 教授的在线课程,强烈推荐:
- Apache Flink 关系编程使用表 API 和 SQL
- 阿帕奇卡夫卡&阿帕奇火花纳米级 数据流
- 数据工程纳米学位 → 优质课程+编码项目如果你有更多的时间投入。 → 通过此链接获得七折优惠
介绍
几年前,当我开始熟悉 Apache Spark 生态系统时,我立即注意到有多少现成的资源可供 Python 开发人员使用。
事实上,考虑到大量的教程、点播课程和技术书籍,我在学习如何使用 PySpark 的 DataFrame API 时几乎没有遇到任何障碍。
去年,当我学习成为 Apache Spark 3.0 认证开发人员时,我的第一印象得到了加强:材料和考试本身以多种语言提供,包括 Python。文档是清晰的,并且在涵盖新主题的同时,有许多编码示例可供参考。
然而,如果你正在处理大数据量(也许作为一个软件工程师,数据工程师,或数据科学家?你可能也偶然发现了阿帕奇·弗林克。由于其高吞吐量和可扩展性,这是市场上最有前途的实时数据处理框架之一。
但是,就目前情况来看,Python 开发人员使用 Apache Flink 有多容易?
根据我的个人经验,事情并没有那么简单:JAVA 工程师可以利用的资源很多,但是当涉及到学习PyFlink(的意思是它的 Python 等价物 ) 的选择池就大大推脱了。
在这第一篇教程中,我通过展示如何使用 PyFlink 表 API 从 CSV 源导入(有界)数据来填补空白。
但首先,让我们多了解一下 PyFlink,以及它可以用来做什么。
什么是 PyFlink?
文档指出 PyFlink 是一个 Python API,可以构建可扩展的批处理和流工作负载,例如:
- 实时数据处理管道,
- 大规模探索性数据分析,
- 机器学习管道,
- ETL 流程。
在某些方面,它可能被认为是 PySpark 的等价物,但在 Apache Flink 中。根据所需的抽象级别,PyFlink 提供了两种不同的 API:
- PyFlink 表 API: 允许用户编写强大的关系查询,类似于使用 SQL 或在 Python 中处理数据帧。因为数据集可以用类似 SQL 的查询来导入、操作和丰富,这个 API 甚至为 Python 知识有限的个人提供了无限的解决方案。
- PyFlink DataStream API: 提供对 Flink 的核心构建块的控制,构建更复杂的流处理用例。
数据集
在 Apache 中,Flink 数据可以被处理为 有界 或 无界 流。
本教程将重点介绍有界的流,因为处理有限的数据是开始学习 PyFlink 最简单的方法。
事实上,当一个流是有界的,它有一个定义的开始和结束。这意味着可以在执行任何计算之前获取数据。有界流的处理也称为批处理。
具体来说,本教程中使用的fin_trxs数据集以 CSV 格式存储,其中包含有关一千笔虚假银行交易的信息,可以在此处下载。数据集的前 10 行显示如下:

数据故意没有标题,但您可以看到下面八列中每一列的定义:
**column_names = [‘trx_id’, ‘trx_date’, ‘src_curr’, ‘amnt_src_curr’, ‘amnt_gbp’, ‘user_id’, ‘user_type’, ‘user_country’]**
现在,让我们学习如何用 PyFlink 从这个 CSV 文件创建一个表。
从 CSV 源创建表格
使用 PyFlink 表 API ,至少有两种方法可以将数据从源导入到表中。
方法 1:使用 Python 语法
第一种方法使用标准的 PyFlink 语法从 CSV 文件导入有界数据。完整代码如下所示:
如您所见,代码已经被包装到一个main()函数中,因此它可以作为一个应用程序工作。让我们一步一步地看看函数中发生了什么。
在使用表格 API 时,最初的步骤是创建一个表格环境,所以我首先想到的是从pyflink.table模块导入EnvironmentSettings和TableEnvironment。请注意,我还导入了即将使用的DataTypes和CsvTableSource:**
我已经定义了环境设置(env_settings),指定在这个应用程序中数据将被批量处理(in_batch_mode),并将这个对象传递给TableEnvironment.create()方法,以生成我将用来导入数据的表环境:**
然后,我将column_names和column_types(使用DataTypes定义数据集模式)存储为列表,并将它们传递给CsvTableSource()类——连同我的fin_trxs.csv文件的本地路径——以创建数据source:**
最后,我在financial_trxs名称下注册了 CSV source,并通过将源名称传递给from_path()方法创建了一个实际的表tbl:
如果您希望在控制台上可视化tbl中的一些记录,我建议您使用以下命令:
**tbl.limit(10).execute().print()**
或者,如果数据集足够小,可以放在内存中,也可以使用方便的to_pandas()方法,将tbl对象转换为 pandas dataFrame:
**print(tbl.to_pandas())**
这是我通过运行main()函数得到的结果:

main()函数的输出,使用方法 1
这个 PyFlink 应用程序确实从一个 CSV 源文件中创建了一个 1000 行 8 列的表!
方法 2:使用 SQL 语法
在前面的方法中描述的一些步骤(像定义一个模式,创建一个源并注册它)有点乏味。**
幸运的是,在 PyFlink 中你还可以通过标准的 SQL 数据定义语言 (DDL)直接创建一个表。完整代码如下所示:
如您所见,设置env_settings和tbl_env的方式与之前完全相同。
然而,现在实际的financial_trxs_2表已经由 SQL 语句定义,在with()子句中传递 CSV source path。
为了让表存在,需要执行查询并使用from_path()方法导入源。
**tbl_env.execute_sql(source_ddl)tbl = tbl_env.from_path('financial_trxs')**
你猜怎么着?执行这段代码会导致相同的结果:

main()函数的输出,使用方法 2
奖励:PySpark 中的等效代码
在这个奖金部分,我将回答这个问题:
PyFlink 表 API 和PySpark data frame API创建 一个来自 CSV 源文件的表之间有什么不太冗长的语言?
为了便于比较,下面我分享了 PySpark 中的等价代码:
等效 PySpark 代码的输出
有输出:

从上面的代码中可以得出三个注意事项:
- 总的来说, PySpark DataFrame API 确实减少了输入。特别是,创建一个
SparkSession比创建一个TableEnvironment要简单得多。 - 然而,使用 PyFlink 表 API ,用 method_1 定义表模式更简单,用 method_2 定义更简单。
- 当需要从源中读取实际数据时, PySpark DataFrame API 提供了一个更加流畅的语法,不涉及任何初步注册或代码执行。
结论
在本教程中,您学习了如何利用 PyFlink 表格 API 从 CSV 源文件创建表格。
此外,还对 PyFlink 和 PySpark 进行了比较,以了解处理这项任务最简便的语言是什么。
在下一篇教程中,您将学习如何通过数据流应用程序从 Kafka 主题导入 PyFlink 中的无界数据。敬请期待!
给我的读者一个提示
这篇文章包括附属链接,如果你购买的话,我可以免费给你一点佣金。
来源
PyFlink 官方文件。
编码示例是作者对在线课程Apache Flink Relational Programming Using Table API And SQL中介绍的材料的重新解释。
pymoode:Python 中的差异进化
原文:https://towardsdatascience.com/pymoode-differential-evolution-in-python-78e4221e5cbe
用差分进化算法求解单目标和多目标优化问题

差分进化(DE) (Storn & Price,1997)最初是为标量目标优化而设计的。然而,由于其简单的实现和高效的问题解决质量,DE 已经以不同的方式被修改来解决多目标优化问题。
在整篇文章中,我们将看到 Python 包 pymoode 中可用的算法和操作符,以及对单目标、多目标和多目标优化问题的应用。它在 PyPI 上可用,因此任何 Python 用户都可以用简单的命令行安装它:
pip install pymoode
该软件包是使用pymoo基本结构(Blank & Deb,2020)编写的,它有效、易用,并在用户希望或需要时给予高度定制的余地。我强烈建议查看它的文档和可用特性。 Julian Blank 也写了这篇关于媒介的伟大文章,概述了遗传算法,其中他介绍了 pymoo 的一些特征。
接下来的部分将涵盖以下主题。
- pymoode :算法和附加运算符
- DE:概述和控制参数
- 在pymoo中定义问题
- 单目标 DE
- 多目标优化简介
- 多目标算法:GDE3 和 NSDE
- 改进双目标问题的解决方案
- 多目标算法:NSDE-R 和 GDE3-MNN
- 应用和前景
- 结论
- 致谢
- 参考文献
我希望你们都能好好阅读,并成功地将 pymoode 应用到你们的一些项目中!感兴趣的人可以跟随本 示例笔记本 了解所有实施步骤。该软件包也可以在 GitHub 上获得。
如果您不熟悉非线性编程或差分进化,我建议您阅读我以前的文章,以便更好地理解这里介绍的概念。
*
虽然以前我们追求的是一个单一的目标,但在这篇文章中,我们有许多方向可以同时遵循!
为了给读者提供不同部分的导航,每个部分的末尾都有一个“返回目录”链接,可以将页面滚动回目录列表。
pymoode :算法和附加运算符
pymoode 的目的是提供 pymoo 中可用算法的扩展,重点关注差分进化变体。因此,这些算法将共享 DE 复制操作符之间的一些共同特征。此外,我还实现了一些 survival 操作符,这些操作符在 pymoo 中还没有,为用户提供了更多的选择。这些算法和操作符可能会被未来版本的 pymoo 所整合。
该软件包可以通过以下树结构进行总结:
pymoode
├───algorithms
│ ├───DE
│ ├───GDE3
│ ├───NSDE
│ └───NSDER
├───survival
│ ├───RankAndCrowding
│ └───ConstrRankAndCrowding
└───performance
└───SpacingIndicator
可用的算法有:
- 德:Storn&Price(1997)提出的单目标问题的差分进化。还提供了后来实现的其他功能,如抖动、抖动、选择变量和交叉策略。详情见 Price 等人(2005 年)。
- NSDE :非支配排序差分进化,一种多目标算法,将 DE 变异和交叉算子结合到 NSGA-II (Deb et al .,2002)生存中。
- GDE3 :广义差分进化 3,一种多目标算法,将 DE 变异和交叉算子结合到 NSGA-II 生存和混合型生存策略中。在该算法中,在通过多目标生存算子截断种群之前,个体可能在一对一的比较中被移除。它是由 Kukkonen,S. & Lampinen,J. (2005 年)提出的。具有 M-最近邻和 2-最近邻存活的变体也是可用的。
- NSDE-R :基于参考方向的非支配排序差分进化(Reddy & Dulikravich,2019)。这是一种针对多目标问题的算法,是使用 NSGA-III (Deb & Jain,2014)生存策略的 NSDE 的扩展。
其他幸存运算符包括:
- RandAndCrowding :实现 NSGA-II 等级和拥挤生存的灵活结构,具有拥挤度量和个体淘汰的不同选项。
- constrankandcrowding:Kukkonen,s .和 Lampinen,J. (2005)提出的基于等级和拥挤的生存算子,具有特殊的约束处理方法。
可用的拥挤指标有:
- 拥挤距离(CD’):由 Deb 等(2002)在 NSGA II 中提出。从 pymoo 导入。
- 修剪拥挤距离(PCD):由 Kukkonen & Deb (2006a)提出,它递归地重新计算拥挤距离,从种群中移除个体以提高多样性。
- M-最近邻(mnn’)**:由 Kukkonen & Deb (2006b)在 GDE3 对多目标问题的扩展中提出,其中 M 对应目标的个数。
- 2-最近邻( 2nn ') :也是 Kukkonen & Deb (2006b)提出的,是邻居个数为 2 的M-最近邻的变种。
- 拥挤熵(ce’):王等(2009)在 MOSADE 提出。
DE:概述和控制参数
DE 算法的基本结构如下图所示,接下来将描述在 pymoode 中实现的最重要的控制参数。要了解更多的细节,我推荐阅读我在上一篇关于德的文章和普莱斯等人的书(2005)。

差分进化算法的基本结构。(图片由作者提供)。
- pop_size (int,可选):人口规模。
- 采样(采样,群体,或类数组,可选) pymoo 的采样策略。
- 变体 (str,可选):差分进化策略。必须是格式为“去/选择/n/交叉”的字符串。默认为“DE/rand/1/bin”。
- CR (浮点,可选):交叉参数。在范围[0,1]中定义。要加强突变,请使用更高的值。要控制收敛速度,请使用较低的值。
- F (可迭代的 float 或 float,可选):比例因子或突变参数。在范围(0,2)中定义。为了加强探索,使用更高的值;而对于剥削,使用较低。
- gamma (float,可选):抖动偏差参数。应该在范围(0,2)内。默认为 1e-4。
- 变异(变异,可选):交叉后 Pymoo 的变异算子。默认为无。
- de_repair (callable 或 str):对问题边界外的突变载体的修复策略。所采用的策略基于 Price 等人(2005)。默认为“反弹”。
- 生存(生存,可选):Pymoo 的生存策略。应该在多目标算法中考虑。
在选择 DE 的控制参数时,我建议优先选择 pop_size 、 variant 、 CR 和 F (按先后顺序)。据文献报道,当使用非常小的群体规模时(Price 等人,2005 年),抖动是有用的,在这种情况下,使用更高的伽马值可能是可取的。我发现它对于在帕累托前沿连接线性段的情况也是有用的。参数突变是指交叉后可能执行的 pymoo 突变。当使用强化剥削和精英主义的变体时,如 DE/best,使用多项式突变非常有用。
de_repair 参数选择问题边界外突变载体采用的修复策略。它可能作为一个可调用的或一个字符串被传递。目前的实施使用 Price 等人(2005 年)提出的替代方案。默认反弹策略将违反边界的变量设置为 DE 变异对应基向量与变量边界之间的随机值;中间,以类似的策略,将数值重置为两者之间的中间点。策略 rand-init 随机采样违反决策变量的问题边界之间的新值,这对于增强多样性是有用的,尽管它可能会破坏接近边界的解决方案的进展。
生存策略是对多目标变体有效的论点,而单目标 DE 使用经典的一对一替换。考虑到要解决的问题,选择适当的策略是至关重要的。对于双目标问题,推荐使用 pymoode 的 RankAndCrowding 生存,使用crowding _ func=‘PCD’,而对于多目标问题,优选使用crowding _ func=‘mnn’,或者将算法切换到 NSDE-R
在接下来的部分中,让我们动手使用 pymoo 的结构来解决问题。
在 pymoo 中定义问题
在 pymoo,中,有三种不同的方式来定义一个问题。这三者都创建了一个继承自问题类的实例。在文档中对它们进行了如下描述:
- 问题:面向对象的定义实现了一个评估一组解决方案的方法。
- ElementwiseProblem :面向对象的定义,实现一次评估一个单个解决方案的函数。
- 功能问题:通过对每个目标和约束使用功能来定义问题。
from pymoo.core.problem import Problem, ElementwiseProblem
from pymoo.problems.functional import FunctionalProblem
在这一节中,我将使用三种可用的结构来展示解决同一个优化问题的不同方法。
第一个例子将基于 Rastrigin 函数的单目标优化问题。这是非凸优化中常见的单目标测试问题,因为它包含多个局部最优解。它由下面的等式描述。

拉斯特里金问题。(图片由作者提供)。
其中, A 是用户指定的参数(通常为 10),而 n 是所使用的决策变量的数量。在我们的实现中,我们将使用两个决策变量。都将以-5.12 和 5.12 为界。
可能,定义问题最直观的方式是函数,代码如下。
# Defining the objective function
def rastrigin(x):
return np.sum(x * x - \
10 * np.cos(2 * np.pi * x)) \
+ 10 * np.size(x)
# Functional
functional_rastrigin = FunctionalProblem(
n_var=2, objs=rastrigin,
xl=np.full(2, -5.12), xu=np.full(2, 5.12),
constr_ieq=[],
)
或者,同样的问题可以被定义为基本问题 …
class ElementwiseRastrigin(ElementwiseProblem):
def __init__(self):
xl = np.full(2, -5.12)
xu = np.full(2, 5.12)
super().__init__(
n_var=2, n_obj=1, n_constr=0,
xl=xl, xu=xu)
def _evaluate(self, x, out, *args, **kwargs):
out["F"] = rastrigin(x)
…或者作为一个问题,定义为以矢量化的方式评估一组解决方案。
class Rastrigin(Problem):
def __init__(self):
xl = np.full(2, -5.12)
xu = np.full(2, 5.12)
super().__init__(
n_var=2, n_obj=1, n_constr=0,
xl=xl, xu=xu)
def _evaluate(self, x, out, *args, **kwargs):
out["F"] = np.sum(x * x - \
10 * np.cos(2 * np.pi * x), axis=1) \
+ 10 * x.shape[1]
注意问题结构中的 x 是一个形状为( N , m )的种群中决策变量对应的数组,其中 N 对应种群大小, m 对应决策变量的个数。
在下一节中,让我们使用单目标 de 的 pymoode 实现来解决这个问题。
单目标差分进化算法
为了解决 Rastrigin 问题,我们将首先导入一些有用的操作符。它们将是算法( DE )、最小化接口和单目标终止操作符(defaultsingleobjecttermination)。
from pymoode.algorithms import DE
from pymoo.optimize import minimize
from pymoo.termination.default import DefaultSingleObjectiveTermination
我们将如下实例化 DE 的第一个实现。
de = DE(pop_size=30, variant="DE/rand/1/bin", F=(0.3, 1.0), CR=0.5)
这是一个低维问题,尽管由于存在许多局部最优而具有挑战性。因此,我使用 30 作为种群大小 N 以确保全局收敛。选择 DE 变量作为最常用的 DE/rand/1/bin, F 参数为(0.3,1.0),使用随机均匀分布抖动, CR 参数为 0.5,以加强坐标轴上的搜索并控制收敛。
对于这个问题,我在调整控制参数方面做得不多。因此,其他组合可能更有效。对 DE 的工作原理感兴趣的,建议看一下我的上一篇文章,里面我有详细的描述。
接下来,让我们根据 f ( f_tol )、x(x _ tol)、约束违反( cv_tol )和最大代数( n_max_gen )的变化来定义终止标准。参数n _ gen控制检查标准的频率,参数 n_last 控制评估变更时考虑多少个前几代。
termination_single = DefaultSingleObjectiveTermination(
xtol=1e-8,
cvtol=0.0,
ftol=1e-8,
period=20,
n_max_gen=200
)
现在,让我们使用最小化接口来解决这个问题。
res_de_rastrigin = minimize(
Rastrigin(),
de,
termination=termination_single,
seed=12,
save_history=True,
verbose=False,
)
该算法是有效的,并引导我们找到了函数的全局最优解,该函数是一个在 x 中为零的向量,目标函数也为零。
因为我已经将参数 save_history 指定为 True,所以我可以访问各代的人口属性,我们可以创建如下所示的惊人视觉效果。

拉斯特里金问题中种群的世代进化。(作者动画)。
在创建它的时候,我使用了 pyrecorder 库,也是由 Julian Blank 开发的。
多目标优化简介
顾名思义,多目标优化涉及同时优化多个目标。当目标相互冲突时,即一个目标函数的最优解不同于另一个目标函数的最优解时,问题变得具有挑战性。在解决此类问题时,无论是否存在约束,这些问题都会产生一组折衷最优解,通常称为 Pareto 最优解。由于解决方案的多样性,建议使用进化算法来适当解决这些问题,该算法在其搜索过程中使用群体方法(Deb,2011)。
使用支配的概念可以理解帕累托最优解。它们是在给定群体中不受任何其他因素支配的解决方案。
根据 Deb (2001),如果满足以下条件,一个解 x1 优于另一个解 x2 :
- x1 在所有目标上不比 x2 差。
- x1 至少在一个目标上比 x2 强。
已经提出了几种处理约束的想法。一种常用的方法是 Deb 等人(2002)在 NSGA II 中采用的方法,其中可行的解决方案总是比不可行的好,不可行的解决方案按照整体约束违反情况进行排序。
双目标最小化问题的帕累托最优解如下所示。

帕累托最优解。(图片由作者提供)。
生存算子必须强调好的解决方案而不是坏的解决方案。在现代多目标算法中,非支配排序(或类似操作)几乎总是存在,它根据支配标准将每个解决方案归入一个等级。等级从最好到最差的解决方案按升序排列,通常是第一生存标准。

非支配排序和等级。(图片由作者提供)。
当超过种群规模时,具有相同等级的个体通过几种策略进行排序,其中两种常见的策略是 NSGA-II 中的拥挤距离(Deb 等人,2002 年)和 NSGA-III 中的参考方向(Deb&Jain,2014 年)。
在 pymoode 算法中,默认情况下,GDE3 和 NSDE 使用与 NSGA-II 相同的生存策略,而 NSDE-R 使用与 NSGA-III 相同的策略。
在下一节中,我们将使用 GDE3、NSDE 和 NSGA-II 解决一个多目标问题(在 pymoo 中实现)。
多目标算法:GDE3 和 NSDE
GDE3 和 NSDE 是 DE 的两个多目标变体,在生存阶段使用非支配排序和拥挤度量。它们共享相同的繁殖算子,尽管它们的生存策略不同。
NSDE 使用完全(+ λ )生存策略,将双亲群体与后代结合起来,而 GDE3 在将群体截断成原始大小之前,在双亲群体中的每个成员与其相应的试验向量之间进行一对一的比较。在这种一对一的比较中,每对个体中的一个可能会在(+ λ )存活之前被另一个提前从组合群体中移除。
对于大多数问题,GDE3 和 NSDE 将导致非常相似的结果。NSDE 在某些问题上可能会稍微快一点,而 GDE3 在使用低 CR 值时可能会更好地避免过早收敛。
让我们导入本节中使用的操作符。
# Algorithms
from pymoode.algorithms import GDE3
from pymoode.algorithms import NSDE
from pymoo.algorithms.moo.nsga2 import NSGA2
# Termination
from pymoo.termination.default import DefaultMultiObjectiveTermination
在这个例子中,我将引入两个冲突的凸目标,这些目标由于约束而增加了难度。该问题将被定义在两个决策变量 x1 和 x2 上,它们都被-5.0 和 5.0 所限制。

多目标示例的目标函数。(图片由作者提供)。

多目标示例的约束函数。(图片由作者提供)。
在 Python 代码中,使用了 pymoo 问题结构。
class TwoObjExample(Problem):
def __init__(self):
xl = np.full(2, -5.0)
xu = np.full(2, 5.0)
super().__init__(n_var=2, n_obj=2, n_ieq_constr=2, xl=xl, xu=xu)
def _evaluate(self, x, out, *args, **kwargs):
F1 = (x[:, 0] - 0.5) ** 2 + 0.7 * x[:, 0] * \
x[:, 1] + 1.2 * (x[:, 1] + 0.7) ** 2
F2 = 1.1 * (x[:, 0] + 1.5) ** 2 + 0.8 * x[:, 0] * \
x[:, 1] + 1.3 * (x[:, 1] - 1.7) ** 2
out["F"] = np.column_stack([F1, F2])
G1 = x[:, 0] ** 2 + (x[:, 1] - 1) ** 2 - 9
G2 = - (x[:, 0] + 0.5) ** 2 - (x[:, 1] - 1) ** 2 + 2
out["G"] = np.column_stack([G1, G2])
然后,让我们定义停止标准。由于这是一个多目标问题,所以 f 和x的区别就不那么直观了。在 pymoo 中, IGD 度量用于评估两个不同代之间 f 和 x 的差异。这个定义只是为了说明defaultmultiobjectterminationoperator的工作方式。
termination_multi = DefaultMultiObjectiveTermination(
xtol=1e-8,
cvtol=1e-8,
ftol=1e-8,
period=50,
n_max_gen=200
)
在解决这个问题时,除了 GDE3 和 NSDE,我们将使用 pymoo 中的 NSGA-II 的实现。再说一次,我还没有调整 DE 算法的控制参数。因此,他们可能已经得到改善。在 NSGA-II 中,采用了默认的模拟二进制交叉和多项式变异的控制参数,隐含为可选的关键字参数。
gde3 = GDE3(pop_size=50, variant="DE/rand/1/bin",\
F=(0.0, 1.0), CR=0.7)
nsde = NSDE(pop_size=50, variant="DE/rand/1/bin",\
F=(0.0, 1.0), CR=0.7)
nsga2 = NSGA2(pop_size=30)
一旦我们定义了算法和问题,让我们使用最小化接口来解决它。
res_gde3_problem_2 = minimize(
TwoObjExample(),
gde3,
termination_multi,
seed=12,
save_history=True,
verbose=False,
)
所有算法的过程都是一样的,只是改变了传递给的第二个参数,使最小化。因此,我将省略其他实现。
可以使用几个度量来比较多目标问题中的性能。一些最常见的是世代距离、反向世代距离和超级卷。它们都是在 pymoo 中用例子实现的。由于在这个问题中真实波前是先验未知的,我只使用了超体积来比较算法性能。
完整的实施可在 示例笔记本 中找到,主要结果总结如下。
hv GDE3 0.753470843848252
hv NSDE 0.752272658484699
hv NSGA-II 0.7527205037080124
这三种算法表现出非常相似的性能,尽管 GDE3 略胜一筹。或许,在几次独立的运行中,这不会呈现出统计学意义。
为了说明进化的过程,我使用 GDE3 解决方案的 pyrecorder 创建了一些简短的动画。前 70 代在决策变量和目标空间中都有说明。

基于 GDE3 的多目标实例在决策空间中的进化过程。(作者动画)。

基于 GDE3 的多目标实例在目标空间中的演化过程。(作者动画)。
改进双目标问题的解决方案
虽然前一部分的结果很好,但是 Pareto 前沿可以更平滑,元素的分布也可以更均匀。这是由于与 NSGA-II 中提出的原始策略相比,等级和拥挤生存的其他变体的优越性能。为了在生存阶段给用户提供更多的选择, RankAndCrowding 类已经在 pymoode 中实现。
为提高多样性而采用的相关策略是递归地消除个体并重新计算拥挤度量,而不是基于整个前沿的拥挤度量从超过群体大小的最后一个前沿立即消除所有个体。Kukkonen & Deb (2006a)对修改后的策略进行了深入分析。
为了使用 Kukkonen & Deb (2006a)的剪枝策略,我们必须在 RankAndCrowding 算子中选择不同的拥挤函数。在 pymoode 中,表示为【PCD】,代表“修剪拥挤距离”。由于 Cython 的实施,它的速度与原来的运营商一样快。
就目前而言,可以执行如下。
# Import survival operator
from pymoode.survival import RankAndCrowding
# Instantiate it with respective elimination rule and crowding metric
pruning_survival = RankAndCrowding(crowding_func="pcd")
# Pass it in the survival argument of the algorithm
gde3_ps = GDE3(pop_size=50, variant="DE/rand/1/bin", F=(0.0, 1.0),
CR=0.7, survival=pruning_survival)
这产生了比原始拥挤距离算法更均匀的解分布。

GDE3 中修剪拥挤距离与原始拥挤距离的比较。(图片由作者提供)。
为了评估帕累托前沿元素的分布情况,可以使用间距指示器,该指示器在 pymoode 中实现。该指标的值越小,要素在帕累托前沿的分布越均匀。
from pymoode.performance import SpacingIndicator
ideal = np.vstack(
(res_gde3_problem_2.F, res_gde3_ps_problem_2.F)
).min(axis=0) - 1e-3
nadir= np.vstack(
(res_gde3_problem_2.F, res_gde3_ps_problem_2.F)
).max(axis=0) + 1e-3
sp_p2 = SpacingIndicator(
zero_to_one=True, ideal=ideal, nadir=nadir
)
print("SP GDE3", sp_p2.do(res_gde3_problem_2.F))
print("SP GDE3-PS", sp_p2.do(res_gde3_ps_problem_2.F))
它返回:
SP GDE3 0.012024880243150753
SP GDE3-PS 0.004881671664926163
现在提供了一个定量的度量标准来衡量由于采用了修改的剪枝解决方案策略而带来的分布上的改进。
多目标算法:NSDE-R 和 GDE3-MNN
由于 GDE3 和 NSDE 最初是使用 NSGA-II 等级和拥挤生存设计的,它们在许多目标问题上表现不佳。然而,从用户的角度来看,简单的修改就可以解决这个问题。
NSDE-R (Reddy & Dulikravich,2019)结合了 NSGA-III 的生存算子和 DE 的繁殖算子,在多目标问题中表现出色。GDE3-MNN 是由 Kukkonen 和 Deb (2006b)提出的 GDE3 的变体,其用具有递归消除和重新计算的基于 M 最近邻的拥挤度量代替了等级和拥挤生存的原始拥挤距离。它大大提高了 GDE3 在多目标问题上的性能。
特别是在多目标问题中,由于目标空间的稀疏性,当使用相同的生存策略时,NSDE 和 GDE3 会产生比双目标问题更加相似的结果。因此,在本节中,将只实现每种方法的一种变体。
除了 DE 算法,我们还将实现 NSGA-III,这是多目标优化中最成功的算法之一。
让我们导入新的运算符。
# Algorithms
from pymoode.algorithms import NSDER, GDE3
from pymoo.algorithms.moo.nsga3 import NSGA3
# Interface to obtain reference direction
from pymoo.util.ref_dirs import get_reference_directions
在这个例子中,我将使用 get_problem 接口导入一个在 pymoo 中实现的多目标问题。它将是 DTLZ2 ,其中 x 中的每个组件都被定义在范围[0,1]中,并且没有函数约束。这些目标由下面的等式描述。

DTLZ2 问题的目标函数。(图片由作者提供)。
让我们举例说明本节中使用的算法。注意参考方向是 NSDE-R 和 NSGA-III 的必选项。幸运的是, pymoo 有一个接口可以轻松获得通常的方向。此外,请注意,在 GDE3 中, RankAndCrowding 运算符与crowding _ func=' mnn '一起使用,后者使用前面提到的 M -NN 策略。或者,我们可以只导入 GDE3MNN 类。
# Define the reference directions
ref_dirs = get_reference_directions(
"das-dennis", 3, n_partitions=15)
# Suggestion for NSGA-III
popsize = ref_dirs.shape[0] + ref_dirs.shape[0] % 4
nsder = NSDER(ref_dirs, pop_size=popsize,
variant="DE/rand/1/bin", F=(0.0, 1.0), CR=0.5)
gde3mnn = GDE3(pop_size=popsize,
variant="DE/rand/1/bin", F=(0.0, 1.0), CR=0.2,
survival=RankAndCrowding(crowding_func="mnn"))
nsga3 = NSGA3(ref_dirs, pop_size=popsize)
在这一节中,我将采用一个简化的终止准则(' n_gen ',250),仅基于世代数,这对于多目标问题是常见的。
res_nsder_problem_3 = minimize(
get_problem("dtlz2"),
nsder,
('n_gen', 250),
seed=SEED,
save_history=True,
verbose=True,
)
在这个问题上,NSGA-III 比差分进化算法产生了更接近参考点的解,尽管差分进化算法的性能也很好。
通过完成算法获得的帕累托前沿显示在下面的动画中。

在 DTLZ2 中获得的 Pareto 前沿。(作者动画)。
太好了!现在我们已经完成了 pymoode 中的操作符,解决了几个不同的问题。
应用和前景
多目标进化算法已经应用于许多不同的领域,包括投资组合优化(赵等,2020),电厂运行(王等,2014),供水系统(Monsef 等,2019),以及网络控制(Gonç alves 等,2022)。此外,工程过程设计和控制有许多应用,作为一名化学工程师,这就是我开始的地方。
我第一次遇到多目标进化算法是在为我的第一篇文章苯乙烯反应器的模拟和优化做文献综述(Leite 等人,2021)。Yee 等人(2003)使用 Srinivas & Deb (1994)的原始 NSGA 算法对该问题进行了首次应用。它产生了很大的结果,后来激发了 Tarafader 等人(2005)的研究,除了增加问题公式化的复杂性以在目标中加入更多的过程外,还采用了更有效的算法:NSGA-II (Deb 等人,2002)。Babu 等人(2005 年)和 Gujarathi & Babu (2010 年)也提出了一种解决苯乙烯反应器问题的多目标差分进化方法。
我发现看到算法在过去几十年中是如何改进并提供给用户的令人惊讶。包 pymoode 开始试图通过进化方法优化复杂的反应堆模拟模型,但现在我相信它已经变得更好,因为它为任何 Python 用户提供了一些迄今为止提出的最有效的差分进化算法和工具。
当我现在更新这篇中型文章时,开发了 pymoode 的关于苯乙烯反应器的多目标优化的主要研究论文以标题 使用广义差分进化的绝热苯乙烯反应器的多目标优化 3(GD E3)(Leite 等人,2023)发表。如果出于学术目的使用 pymoode ,请相应引用。
结论
在本文中,Python 包 pymoode 提供了解决单目标、多目标和多目标问题的教程。该软件包是对专注于差分进化算法的pymoo的扩展,具有各种各样的操作符,可确保出色的性能并允许高度定制。用于生成本文的所有代码都可以在 Github 上获得。py mood的新功能可能会在以后被 pymoo 整合。**
承认
朱利安·布兰克,他创造了皮穆的惊人结构,使得这样一个项目成为可能。
感谢小埃斯利·费雷拉·达·科斯塔,是他让这一切成为可能。
参考
Babu,B. V .,Chakole,P. G .和 Mubeen,J. H. S .,2005 年。绝热苯乙烯反应器优化的多目标差分进化算法。化学。英语。Sci。,第 60 卷,第 4822–4837 页。
Blank,j .和 Deb,k .,2020 年。pymoo:Python 中的多目标优化。 IEEE 访问,第 8 卷,第 89497–89509 页。
k . deb,2001 年。采用进化算法的多目标优化。1 版。奇切斯特:约翰·威利的儿子们。
k . deb,2004 年。工程优化遗传算法导论。载于:G. C. Onwubolu 和 b .诉 Babu,编辑。模糊性和软计算工程研究中的新优化技术,第 141 卷。海德堡:施普林格出版社,柏林海德堡,第 13-51 页。
黛比,k,2011。使用进化算法的多目标优化:导论。产品设计和制造的多目标进化优化。伦敦:施普林格出版社,第 3-34 页。
Deb,k .和 Jain,h .,2014 年。使用基于参考点的非支配排序方法的进化多目标优化算法,第一部分:解决带盒约束的问题。 IEEE 进化计算汇刊, 18(4),第 577–601 页。
戴布,k,普拉塔普,a,阿加瓦尔,s &梅亚利万,交通运输管理,2002 年。一种快速的精英多目标遗传算法:NSGA-II。 IEEE 进化计算汇刊, 6(2),第 182–197 页。
贡萨尔维斯,E. N .,贝洛,M. A. R .和巴蒂斯塔,A. P .,2022。网络控制系统中优化网络使用的具有优先前沿精英策略的自适应多目标差分进化算法。应用软件计算。,第 114 卷,第 108112 页。
古吉拉特邦,A. M .和 Babu,B. V .,2010 年。工业苯乙烯反应器的多目标优化:绝热和伪等温操作。化学。英语。Sci。,第 65 卷,第 2009–2026 页。
Kukkonen,s .和 Deb,k .,2006 年 a。基于拥挤距离的双目标优化问题非支配解的改进剪枝。温哥华,南北卡罗来纳州,第 1179-1186 页。
Kukkonen,s .和 Deb,k .,2006 年 b。一种快速有效的多目标问题非支配解剪枝方法。In: 来自自然的并行问题求解——PPSN 九世。柏林:施普林格出版社,第 553-562 页。
Kukkonen,s .和 Lampinen,j .,2005 年。GDE3:广义差分进化的第三步进化。2005 年 IEEE 进化计算大会,第 1 卷,第 443-450 页。
莱特,b,科斯塔,A. O. S .和科斯塔少年,E. F .,2021。基于非均相动力学模型的乙苯脱氢轴向流和径向流反应器的模拟与优化。化学。英语。Sci。,第 244 卷,第 116805 条。
莱特,b,科斯塔,A. O. S .,科斯塔,E. F .,2023。基于广义差分进化算法 3 的绝热苯乙烯反应器多目标优化。化学。英语。Sci。,第 265 卷,第 118196 条。
Monsef,h .,Naghashzadegan,m .,Jamali,A. & Farmani,r .,2019 年。给水管网优化设计中进化多目标优化算法的比较。 Ain Shams 工程杂志,第 10 卷,第 103–111 页。
普莱斯,K. V .,Storn,R. M .和 Lampinen,J. A .,2005 年。差分进化:一种实用的全局优化方法。第一版。柏林。
Reddy,S. R .和 Dulikravich,G. S .,2019 年。基于参考点的多目标差分进化优化:NSDE-R. Struct。多碟。Optim。,第 60 卷,第 1473—1455 页。
斯里尼瓦斯,n .和德布,k .,1994 年。遗传算法中使用非支配排序的多目标优化。进化计算, 2(3),第 221–248 页。
storn r .和 Price k .,1997 年。差分进化——连续空间全局优化的简单有效的启发式算法。全球 j。Optim。,第 11 卷第 4 期,第 359—341 页。
Tarafder,a .,Rangaiah,g .和 Ray,A. K .,2005 年。工业苯乙烯单体生产过程的多目标优化。化学。英语。Sci。,第 60 卷第 2 期,第 363—347 页。
王等,2014。基于差分进化的燃煤电厂多目标优化。应用能量,第 115 卷,第 254–264 页。
王燕宁,吴,李良宏,袁晓峰,2010。基于精英保留和拥挤熵多样性测度的多目标自适应差分进化。软计算。,第 14 卷第 3 期,第 209—193 页。
易,雷,兰盖亚,2003。工业苯乙烯反应器的多目标优化。计算机。化学。英语。,第 27 卷第 1 期,第 130—111 页。
赵平,高,杨,倪,2020。基于 MOEA/D 的多目标投资组合优化问题求解 2020 年第 12 届高级计算智能国际会议(ICACI)第 30–37 页。*
PyMySQL —为数据科学连接 Python 和 SQL
原文:https://towardsdatascience.com/pymysql-connecting-python-and-sql-for-data-science-91e7582d21d7
轻松访问 MySQL 数据库并使用 Python 执行 SQL 查询

迈克尔·奥斯特林克在 Unsplash 上的照片
SQL 和 Python 是数据从业者有效处理数据不可或缺的工具。
一个常见的用例是最初使用 SQL 查询从关系数据库中检索数据,然后使用 pandas 等库在 Python 中操作和分析数据。
但是您知道吗,这两个看似独立的任务可以合并到一个 Python 脚本中,轻松实现相同的结果。
在本文中,我们发现了从 Python 连接到 MySQL 数据库并与之交互的 PyMySQL 的概念和实现。
目录
(1)PyMySQL 是什么,为什么要用它?(2)初始设置(3)连接与光标对象(4)建立与 MySQL 服务器的连接

Rubaitul Azad 在 Unsplash 上拍摄的照片
(PyMySQL 是什么,为什么使用它?
PyMySQL 是一个纯 Python 的 MySQL 客户端库,这意味着它是一个 Python 包,为我们创建了访问 MySQL 关系数据库的 API 接口。
文档页面声明 PyMySQL 是基于 PEP 249 构建的。这意味着 PyMySQL 是基于 Python 数据库 API 规范开发的,该规范旨在促进用于数据库访问的 Python 模块的标准化。
使用 PyMySQL 的主要原因是,通过在 Python 脚本的范围内合并 SQL 语句,它可以作为直接与 MySQL 数据库交互的便捷接口。
这也意味着我们不需要访问单独的 RDBMS 接口来运行下游 Python 操作所需的 SQL 查询。
(2)初始设置
(一)安装
我们可以使用 pip 安装 PyMySQL:
pip install PyMySQL
还需要满足以下版本要求:
㈡数据设置
对于这个演示,我们从 Mockaroo 生成一个 1000 行的模拟数据集来表示一个客户数据集。然后通过表数据导入向导功能将数据导入到本地 MySQL 数据库中,形成客户表。

MySQL Workbench 中的 customers 表截图|作者图片
我生成的数据可以在 GitHub repo 项目中的这里找到。
如果您想了解更多关于为这样的实验生成模拟数据的免费资源,请查看以下参考资料:
㈢进口包装
然后,我们导入项目所需的所有包:
import **pymysql**
import pandas as pd
import matplotlib.pyplot as plt
(3)连接和光标对象
PyMySQL 库建立在两个关键的 Python 对象之上:连接对象和光标对象。
(4)建立与 MySQL 服务器的连接
在与 MySQL 数据库交互之前,我们必须连接到服务器。第一步是定义配置参数。
下面是我们连接到本地数据库实例的默认配置设置。请注意,根据您为模式和数据库创建的特定设置,这些值可能会有所不同。
这些参数最好存储在单独的。py 文件,并使用像 configparser 这样的工具解析到主 Python 脚本中。
定义好配置参数后,我们可以将它们解析成 PyMySQL connect 方法调用,该方法调用可以包装在 Python 函数中。
然后,我们通过调用 Python 函数并以变量形式返回连接对象来执行连接:
(5)从 MySQL 数据库中检索数据
假设我们想从客户表中提取数据的一个子集(例如,开宝马的客户)。我们首先定义一个执行以下操作的函数:
- 从前面设置的连接对象创建一个游标对象。
- 将 SQL 查询解析为游标对象并执行查询。对于这个例子,查询是
SELECT * FROM customers WHERE car_make = 'BMW';,我们将它作为一个字符串存储在一个名为query的 Python 变量中。 - 将执行提交给连接对象,以便查询到达数据库。这是因为默认情况下,连接对象不会自动提交。
- 获取执行的 SQL 查询产生的所有数据记录。
- 将数据记录(作为字典列表返回)转换成 pandas 数据帧。
上述步骤被包装在如下所示的 Python 函数(get_records)中:
运行该函数将返回以下输出:

作者图片
现在,来自 SQL 查询的记录被存储为 Python pandas 数据帧,我们可以自由地对数据执行各种分析和操作技术。
例如,我们可以使用 pandas 和 matplotlib 来可视化这些客户访问特定移动应用程序的频率分布(基于列freq_app_visit):

dataframe | Image 的“频率应用程序访问”列的直方图(按作者)
https://kennethleungty.medium.com/membership
(6)其他 CRUD 功能
除了从 MySQL 数据库读取数据,我们还可以使用 PyMySQL 执行其他 CRUD(创建、读取、更新、删除)功能来管理数据库。
例如,我们可以执行一个查询,让将数据记录插入到客户表中。像以前一样,我们为数据插入的这个特定操作创建一个 Python 函数。
完成后,我们可以使用之前创建的get_records Python 函数查看新添加的记录:

按作者检索新插入的记录|图像的数据
(7)包装
在本文中,我们学习了如何利用 PyMySQL 的功能,通过 Python 与 MySQL 数据库进行交互。
特别是,我们看到了在一个 Python 脚本或笔记本的范围内,从 MySQL 数据库中检索、操作和分析数据是多么容易。
你可以查看这个项目的 GitHub repo这里 和 PyMySQL 官方文档 这里 。
如果您想了解 PyMySQL 是如何用于云数据库(而不是本演示中所示的本地数据库)的,请查看以下内容:
在你走之前
欢迎您加入我的数据科学学习之旅!点击此媒体页面,查看我的 GitHub ,了解更多令人兴奋的实用数据科学内容。同时,祝您在使用 PyMySQL 的过程中愉快!
PyScript:浏览器中的 Python
原文:https://towardsdatascience.com/pyscript-python-in-the-browser-aadaf8960a80

资料来源:pyscript.net
你是数据科学家还是主要使用 Python 的开发人员?你会嫉妒那些编写 Javascript 代码,在浏览器中构建花哨网站的开发者吗?如果我们能用 Python 写网站该有多好?令人惊讶的是,在 PyCon US 2022 上,Anaconda 的首席执行官王蒙杰宣布了一项名为 PyScript 的闪亮新技术,允许用户在浏览器中编写 Python 和实际上许多语言。
PyScript 是什么?
由 Anaconda 的团队开发,包括王蒙杰、法比奥·普利格和菲利普·鲁迪格, PyScript 是,正如彼得在他的演讲中提到的,“一个在 HTML 中交错 Python 的系统(像 PHP)。”这意味着你可以用 HTML 编写和运行 Python 代码,用 PyScript 调用 Javascript 库,用 Python 做所有的 web 开发。听起来很神奇!
使用 PyScript 对世界和数据科学家来说意味着什么?
- 最明显的是,有了 PyScript,我们现在可以用 HTML 编写 Python(可能还有其他语言)并构建 web 应用程序。PyScript 使更多的前端开发人员和创作者能够使用 Python 的强大功能。
- 正如彼得在演讲中提到的,“网络浏览器是世界上最普遍的便携式计算机环境。”事实上,每个人都可以使用网络浏览器,不管是在你的电脑上还是手机上。这意味着任何人都可以无障碍地访问和开始编程。
- 有了 PyScript,我们不再需要担心部署问题。PyScript 提供了“超越云的架构转变”一切都将发生在你的网络浏览器中。作为数据科学家,我们可以在一个 html 文件中共享我们的仪表板和模型,当其他人在 web 浏览器中打开该文件时,它将运行代码。

图一。PyScript 概述。
PyScript 背后的魔力是什么?
PyScript 目前构建在 Pyodide 之上,这是一个“CPython 到 WebAssembly/ Emscripten 的端口”。PyScript 支持在浏览器中编写和运行 Python 代码,将来还会提供对其他语言的支持。

图二。PyScript 技术栈。
什么是 WebAssembly?
使用 Python 编写网站成为可能的基础技术是 WebAssembly。最初开发 WebAssembly 时,web 浏览器只支持 Javascript。
WebAssembly 于 2017 年首次发布,到 2019 年迅速成为官方的万维网联盟(W3C)标准。它包括人类可读的。wat 文本格式语言,然后转换成二进制。浏览器可以运行的 wasm 格式。这使得我们可以用任何语言编写代码,编译成 WebAssembly,然后在 web 浏览器中运行。
如何使用 PyScript?
PyScript 的 alpha 版本可以在 pyscript.net 上找到。该代码可在https://github.com/pyscript获得。按照这些指示试一试。PyScript 允许您使用以下三个主要组件用 html 编写 Python:
定义运行 Python 代码所需的 Python 包。 是您编写 Python 代码并在网页中执行的地方。 创建一个 REPL(读取-评估-打印循环)组件,评估用户输入的代码并显示结果。
< py-env >例
下面是一个例子,说明我们如何使用

< py-script >示例
这个例子演示了一个带有流数据的面板仪表板。正如你在代码中看到的,我们能够像在 Python 文件中一样,在< py-script >中编写我们熟悉的 Python 代码。如果你不喜欢直接在< py-script >下编写你的 Python 代码,你也可以像

来源:https://github . com/py script/py script/blob/main/pyscript js/examples/panel _ stream . html
< py-repl >例子
这个例子使用 DeckGL 和 REPL 组件演示了一个带有纽约出租车数据的面板仪表板。如您所见,我们能够定义半径并选择数据帧的子集(hour<3) in the REPL. How magical!

Source: https://github . com/py script/py script/blob/main/pyscript js/examples/panel _ deck GL . html
你还可以在 PyScript Github 页面上找到许多其他的例子,包括一个 D3 可视化和一个带有计算机视觉的马里奥游戏。
PyScript 将 Python 引入浏览器。我对这项新的创新感到非常兴奋,迫不及待地想看看它如何在社区中展开。敬请关注即将推出的许多出色的新功能!
参考文献:
https://github.com/anaconda/pyscript
主题演讲,王蒙杰在 2022 年美国 PyCon 上
. . .
2022 年 4 月索菲亚·杨
原载于https://www . anaconda . cloud。
我在浏览器上尝试 Python 的第一印象
原文:https://towardsdatascience.com/pyscript-tutorial-a8fba77abd1b
让 Python 成为通用编程语言的唯一缺失部分就在这里

每当我们与其他开发人员讨论最佳编程语言时,我们都会谈论 JavaScript 和 Python 几个小时。
两者都是强大、灵活的语言,主导着当今世界。但是 Python 的一个死结是它不能在浏览器上运行。
有了节点的发现,JavaScript (JS)几乎可以在任何平台上运行。它甚至有模块来构建机器学习算法。JS 有潜力成为未来事实上的编程语言。
Python 是一门美丽的语言。超级简单易学;工作起来超级给力。还没有。它不能在浏览器上运行,这使它远离了许多应用程序。
这一差距现已弥合。Python 现在可以在浏览器上运行。
您可以使用 Pandas 开始构建复杂的数据操作,构建机器学习模型,进行数据可视化,以及在 HTML 页面中进行更多操作。
在本帖中,我们将讨论:
- 在边缘设备上运行 Python 的好处;
- PyScript 入门;
- 完全在浏览器上运行的 ml 模型,以及;
- 我使用 PyScript 的第一印象。
为什么我们在浏览器上需要 Python?
出于许多原因,我们更喜欢在客户端进行计算。我们现在最流行的客户端设备是浏览器。
这里有一些客户端的 PyScript 可以帮助我们。
将数据发送回服务器和接收预测可能会比较慢。您可以通过在浏览器上进行计算来防止 网络延迟 。
假设你开发了一个探测火灾的系统。发生火灾时,它对网络问题有什么帮助?
此外,我们更喜欢保持敏感数据不进入云端。现在,您可以在不离开客户端设备的情况下进行必要的计算。
最后,将计算转移到边缘设备也能带来成本效益。除了前期的架构成本,你还可以计划投资扩展应用。
在您的浏览器上开始使用 py script-Python。
PyScript 不需要特殊的安装就可以开始。我们只需要将一些 web 资源导入到我们的 HTML head 标签中。
让我们创建一个名为index.html的 HTML 文件并导入它们。将这些放在结束的</head>标签之前。
PyScript 提供了三个 HTML 标签来编写 Python 脚本。
您可以在一个 py-env 标签中指定要导入到您的运行时环境中的模块。理想情况下,它需要在我们前面讨论的两个导入标签之后的 HTML 头部。
这些还没有导入到您的脚本中。把它想象成pip install.
您仍然需要在 py-script 中导入它,下面的 py-script 标记。
Py-script 标签是你的 Python 脚本所在的地方。你可以把它放在 HTML 主体的末尾。
您还会得到一个 py-repl 标签。它允许浏览器执行用户输入的任意 Python 脚本。
这是一个带有 REPL 的完整 hello world 示例。
现在,您可以启动 python 开发服务器,并在浏览器中访问它。以下命令将启动服务器。
python -m 'http.server' 8080
你可以在你的浏览器上访问[http://localhost:8080](http://localhost:8080)来看看它的运行。它应该看起来像下面的。

我们的 py-script 标记中的 print 语句出现在浏览器中,就像它出现在终端窗口中一样。
一个很好的替代方法是将我们的 python 文件链接到 py-script 标记,而不是将它写在 HTML 块中。在下一节中,我们将使用这个特性来构建一个功能更加丰富的应用程序。
浏览器上的一些机器学习
我们已经在浏览器上成功运行了 Python 脚本。它不需要预先安装 Python 或任何依赖项。
但是 Python 的真正威力并没有在简单的 HTML 操作中显露出来。让我们进入稍微高级的工作。
让我们构建一个 K-Means 算法,完全在浏览器中对数据点进行聚类。
我们将把 python 代码放在一个单独的文件中,并将其链接到 py-script。让我们在工作目录的根目录下创建一个名为 script.py 的文件,其内容如下。
让我们也调整我们的 HTML 文件。在下面的文件中,我们添加了一个单独的 div 来呈现脚本文件的输出。注意,py-script 标记的输出属性是我们的 div 的 id。
此外,我们不是直接用 HTML 编写 Python 脚本,而是在一个单独的常规 Python 文件中编写它。然后,我们将 py-script 标记的 src 属性设置为新脚本文件的路径。
如果我们在浏览器上再次尝试访问该 URL,您将看到以下输出。

浏览器输出中的 K-Means 聚类——由作者截屏。
在上面的例子中,我们从外部 URL 下载数据。然后我们使用流行的 scikit-learn 库进行了 K-Means 聚类。
然而,这些都不需要预先安装软件包。它完全在浏览器上运行。
我对 PyScript 的第一印象。
PyScript 是 Python 发展过程中令人印象深刻的一步。然而,我不会很快在项目中使用它。
此时,如果我想用 Python 开发 web 应用程序,我会使用 Dash 或 Streamlit。
这并不意味着我不喜欢这种事情发生。我不打算切断它,因为它开始跳跃。但是,我期待很快会有更多的进展。
如果您正在使用 PyScript,这些将帮助您获得一个想法。
PyScript 非常慢。
在屏幕上打印东西不应该花这么长时间。
在一台像样的电脑上显示“你好,世界”信息需要 25 秒。在开发人员控制台上,我们可以看到出现这种情况的原因。
即使没有第三方包导入,PyScript 仍然从 web 上下载了大约 22.7MB 的资源。如果在浏览器上启用了兑现,后续请求将在大约 4 秒钟内加载。4 秒在 web 开发者中仍然被认为是有缺陷的。

PyScript 的网络延迟——作者截图。
同样,你也不知道它还在加载。您需要打开开发人员控制台才能看到它。在它完全加载之前,你只会看到一个空白页。
一件好事是所有的 Python 模块都异步加载。它们在所有其他页面组件加载后在后台加载。
浏览器控制台调试对 PyScript 帮助不大。
前端开发人员依赖浏览器控制台输出进行调试。
如果打开浏览器控制台,PyScript 会打印一些日志。但是没有办法进入这个 API,并把我们的自定义控制台日志。

PyScript 在浏览器控制台中打印日志。但是他们不通融。我们无法添加客户控制台日志。—作者截图。
我也不能在 PyScript 上使用 VSCode 调试器。如果我们有一些技术来做到这一点,可能会有所帮助。
因此,我们现在能做的最好的事情就是编写完全独立于 HTML 的 Python 脚本,并通过 py-script 标签的 src 属性链接它。
远离 HTML 代码格式化程序。
如果使用 VSCode 或任何其他现代 IDE,也可以使用代码格式化。
我有经常按格式化快捷键的习惯。
当你在 HTML 中使用 PyScript 代码时,你的 ide 会假设它是一个普通的 HTML 来格式化它。但是,在 Python 中,缩进就是一切。
所以,如果你像我一样,经常使用代码格式化程序,请使用外部脚本文件。
缺少关于 PyScript 用法的文档。
PyScript 还没有官方文档页面。你可以在他们的 GitHub repo 上找到入门指南。
我很难从外部来源读取数据。
我花了几个小时才明白我们应该使用来自 Pyodide 的‘open _ URL’方法。我不得不浏览 GitHub 上提供的示例列表来找出答案。文档可以节省我的时间。
最后的想法
PyScript 是一个很好的倡议。然而,它还太不成熟,不能用于任何项目。希望我发现的小缺口能很快被填补。
此外,在客户端运行 Python 脚本有几个好处。我们可以发挥 Python 的全部潜力,而不必像在 Tensorflow 中那样将其转换成 JS。
目前,我会坚持使用 Dash 应用程序或 Streamlits。如果那不可能,那么纯粹用 JS 来做还是比较好的。
希望 Python 社区支持 PyScript,进一步完善。
感谢阅读,朋友!看来你和我有许多共同的兴趣。我很乐意在LinkedInTwitter和 Medium 上与您联系
还不是中等会员?请使用此链接 成为会员 因为,在不为你额外付费的情况下,我为你引荐赚取一小笔佣金。
PyScript——在浏览器中释放 Python 的威力
原文:https://towardsdatascience.com/pyscript-unleash-the-power-of-python-in-your-browser-6e0123c6dc3f

如何从 HTML 代码中运行 Python
在 PyCon US 2022 的主题演讲中,Anaconda 的首席执行官王蒙杰公布了一个相当令人惊讶的项目——py script。它是一个 JavaScript 框架,允许用户在浏览器中混合使用 Python 和标准 HTML 来创建 Python 应用程序。该项目的最终目标是让更广泛的受众(例如前端开发人员)受益于 Python 及其各种库(statistical、ML/DL 等)的强大功能。).
关于 PyScript 要知道的关键事情
- 允许我们在浏览器中使用 Python 及其庞大的库生态系统(包括
numpy、pandas、scikit-learn)。 - 通过使用环境管理用户可以决定在运行页面代码时哪些包和文件是可用的。
- 我们可以使用一些现成的精选 UI 组件,例如:按钮、容器、文本框等。
- 我们不必担心部署,因为有了 PyScript,一切都会在我们的 web 浏览器中发生。作为数据科学家,我们可以与我们的利益相关者共享包含仪表板和/或模型的 HTML 文件,他们将能够在他们的浏览器中运行这些文件,而无需任何复杂的设置。
它是如何工作的?
PyScript 建立在 Pyodide 之上。我希望我不是那种不太了解 Pyodide 到底是什么的数据科学家。所以是基于 WebAssembly 的浏览器和 Node.js 的 Python 发行版(CPython 的端口)。这就带来了下一个问题:什么是 WebAssembly?
WebAssembly 是让用 Python 写网站成为可能的技术。它使用人类可读的文本格式语言,然后被转换成浏览器可以运行的二进制 T4 格式。得益于此,我们可以用任何语言编写代码,编译成 WebAssembly,然后在 web 浏览器中运行。
在下面展示技术堆栈的图片中,我们还可以看到 Emscripten ,这是一个开源的编译器工具链。它允许将任何可移植的 C/C++代码库编译成 WebAssembly。
值得庆幸的是,作为最终用户,我们不需要完全理解到底发生了什么。然而,它肯定是重要的,例如,出于安全原因。

来源:https://anaconda.cloud/pyscript-python-in-the-browser
目前,PyScript 支持在浏览器中编写和运行 Python 代码。未来的目标是它也将提供对其他语言的支持。
这也是潜在限制发挥作用的地方。目前,当使用 PyScript 时,我们只能使用 Pyodide 支持的库。你可以在这里找到整个列表。
带它去兜风
PyScript 目前处于 alpha 阶段,但我们已经可以尝试一下,并可能对其 GitHub repo 提出任何改进建议。我们可以直接从 PyScript 的网站下载包含所有代码和资产的压缩文件夹。
PyScript 网站上提供的最基本的示例如下:
正如我们所见,Python 代码嵌入在<py-script>块中。在浏览器中打开文件(或使用 VS 代码的 Life Saver 扩展)会产生以下输出:

作者图片
在第二个例子中,我们将做得更多一点。我们将使用numpy生成来自标准正态分布的数字,然后使用matplotlib绘制它们。我们使用以下代码来实现这一点:
这一次,我们还做了以下工作:
- 我们通过在
<py-env>块中列出想要在 Python 环境中使用的库来定义它们, - 我们通过在
<py-script>块:<py-script output=”plot”>中指定来表示我们将输出一个图。

作者图片
自然,随着我们的代码库变得越来越大,我们不需要将它完全嵌入到 HTML 文件中。我们可以使用下面的块结构来加载任何.py脚本:
<py-script src="/our_script.py"> </py-script>
在这里(已经在浏览器中运行)和这里(GitHub 上的代码)可以找到相当多使用 PyScript 的例子。
包扎
- 有了 PyScript,我们将能够直接从浏览器上运行 Python 代码,
- 该项目由 Anaconda 开发,
- 该项目仍处于 alpha 阶段,但我们已经可以使用 Pyodide 支持的一些库了。
就我个人而言,我不确定此时该如何看待 PyScript。这看起来很有希望,但是它可能会带来很多新的安全问题。此时,我们还可以看到,即使运行一些简单的脚本,在执行时间方面也已经产生了很大的开销。所以我不确定在浏览器中运行大块代码有多实际。
我很想知道你对这项新技术有什么想法!请在评论中或在 Twitter 上让我知道。
喜欢这篇文章吗?成为一个媒介成员,通过无限制的阅读继续学习。如果您使用 这个链接 成为会员,您将支持我,无需您额外付费。提前感谢,再见!
您可能还会对以下内容感兴趣:
PyScript v. Flask:如何在浏览器或服务器上创建 Python 应用程序
PyScript 允许您在不需要服务器的情况下用 Python 创建 web 应用程序。Flask 是一个 Python web 应用程序框架,用于制作基于服务器的应用程序。我们使用两者编写相同的简单应用程序。

多梅尼科·洛亚在 Unsplash 上拍摄的照片
PyScript 是浏览器中的 Python,它承诺了一种编写 web 应用程序的新方法。目前它还处于 alpha 阶段,但已经是一个可用的系统了。
但是我们需要新的方法吗?的确,我可以用 PyScript 创建一个全功能的 web 应用程序(借助一些 HTML,可能还有一点 Javascript ),但是我已经可以用 Flask 做到了!
使用 Flask,Python 代码在服务器上运行,并根据需要更新网页。使用 PyScript,Python 代码在浏览器中运行,并直接与网页交互。
那么,使用 PyScript 比使用基于服务器的应用程序更好还是更容易呢?我们将比较两个类似的应用程序,一个用 PyScript 编写,另一个使用基于 Python 的应用程序框架 Flask。
在浏览器中运行代码至少有一个好处,那就是部署。一个基于浏览器的应用程序只需要被复制到一个网络服务器上,它就可以工作了。然而,基于服务器的应用程序可能需要更多的努力才能部署到 Heroku、Azure 或 AWS 等平台上。但是一旦你习惯了部署基于服务器的应用,这并不需要太多的努力。
我在这里使用的 PyScript 应用程序在以前的文章中已经介绍过了,所以您可以看看这些文章来更深入地了解 PyScript 是如何工作的(参见下面的注释)。
我们将首先查看 PyScript 应用程序,然后看看我们如何使用 Flask 构建类似的东西。
该应用程序本身相当简单,但它包含了仪表板类型应用程序的所有基础:它允许用户交互,加载远程数据,显示交互式图表,并使用 Bootstrap UI 框架使一切看起来很好。
具体来说,该应用程序显示了代表 2020 年英国伦敦天气状况的四个图表之一。这些图表是最高和最低月气温、降雨量和日照时间,它们使用了我在 Github 上的英国历史天气报告中的数据。
用户可以从下拉菜单中选择一个图表,新的图表将会显示出来。这两个版本的应用程序都不会刷新页面:在 PyScript 应用程序中,对 Python 代码的调用会读取数据并直接更新图表,而在 Flask 应用程序中,会对服务器进行异步回调,服务器会使用用于更新图表的数据进行响应。
让我们看看代码。
PyScript 应用程序
PyScript 应用程序是一个 HTML 网页,其结构如下:
标签包含了你能在网页上找到的所有常见的东西,还包含了对 PyScript CSS 和 Javascript 文件的引用。<body>标签包含页面的 HTML 和任何所需的 Javascript,而<py-script>包含——谁会想到呢——py script 代码。
PyScript 部分可以引用一个外部文件,但是由于 CORS 限制,在本地运行该文件时它将不起作用,它必须在一个服务器上运行(当然,那个服务器也可以在您的本地机器上运行)。
这里是对头部的近距离观察:
我们首先引用运行 PyScript 所需的 PyScript 文件,然后引用将在 HTML 中使用的 Boostrap 和 Plotly CDNs。
<py-env>简单地列出了将在<py-script>部分使用的 Python 库。
现在让我们看看正文的第一部分,HTML 和 Javascript:
不需要太多的细节,我们从一个自举超大屏幕元素开始,它充当一个标题。接下来是一个下拉菜单,允许选择要显示的图表。
然后我们有一个<div>作为图表的容器,最后是一个简短的 Javascript 函数,它获取图表数据和容器的 id,并使用 Plotly 库绘制图表。这个函数将直接从 Python 代码中访问。
这就是整理出来的用户界面。剩下的工作是加载远程数据,过滤它以获得我们想要的特定数据,并创建图表数据。所有这些都是用 Python 实现的。
这里是包含 Python 代码的 PyScript 部分。
首先,与任何 Python 程序一样,我们导入所有需要的库。
为了获得数据,我们需要使用 Pyodide 包中的函数open_url(PyScript 基于 Pyodide,其库集成到了 py script 中)。然后我们可以从这些数据中创建一个熊猫数据框架。
接下来,我们过滤数据。数据框架目前包含几十年的数据,但我们只打算使用 2020 年的数据。这就是下面的代码将为我们做的。
df = df[df['Year']==2020]
代码的剩余部分主要是函数定义。
函数plot()创建 Plotly 图表,并使用 Javascript 函数在其容器中绘制图表。它接受一个用于选择要显示的图表的参数,用 Plotly Python 包创建图表数据,最后调用我们前面看到的 Javascript 函数在其容器中显示图表。
请注意调用 Javascript 函数是多么容易。我们只需导入js库,所有的 Javascript 函数都可供我们使用。
接下来的两个函数依赖于它们前面的imports。selectChange()是当下拉菜单中的值改变时将被调用的函数。它从<select>标签中读取新值,并调用我们刚刚看到的 Python plot()函数来显示所选图表。
注意,使用js 包中的document可以让我们以与内置 Javascript 函数完全相同的方式查询 DOM。
接下来,我们需要将用户更改选定值所生成的事件与该函数联系起来。这就是setup()的工作。首先,这个函数为 Python 函数selectChange()创建一个代理,Javascript 可以使用这个代理直接调用这个函数。代理叫做change_proxy。
接下来,我们设置一个事件监听器来检测下拉菜单中的变化,并指示它通过我们刚刚设置的代理调用selectChange函数。
最后两行简单地运行setup()来设置代理和事件监听器,并调用plot("Tmax")来默认显示一个图表。
对于那些习惯于传统网页开发或 Python 编程的人来说,这个应用程序可能感觉不太直观。但是 PyScript 应用程序的设计非常优雅:用户界面由 HTML 和 CSS 定义(在 Javascript 的帮助下),而应用程序的逻辑几乎完全在 PyScript 部分定义。
在我看来,这是为每项工作使用正确的工具。HTML 和 CSS 都是关于如何显示内容的,而 Python 是用于编程的,这两者应该并且确实是分开的。
烧瓶应用程序
这种分离也存在于基于服务器的应用程序中。但是在这种情况下,Python 逻辑在服务器上运行。
一个基本的 Flask 应用程序由两个组件组成,基于服务器的 Python 代码和 HTML 模板。当应用程序被调用时,它将 HTML 模板作为网页返回。它还可能用 Python 代码中的值填充模板中的字段,尽管我们在这里没有使用这种功能。
如果你不熟悉 Flask 应用,我在文章“如何创建和运行 Flask 应用”中写了一个简单的介绍,在 Flask 网站上有更全面的介绍。
简而言之,Flask 应用程序的 Python 部分定义了端点和 Python 函数,这些函数决定了当这些端点被寻址时会发生什么。这个应用程序定义了两个端点,一个对应于根,另一个调用回调函数。
根端点简单地返回 HTML 模板,而回调端点加载所需的数据并返回 Plotly 图表数据以显示在 web 页面上。
让我们先来看看 HTML。
该文件在功能上与 PyScript 应用程序的 HTML 部分相同。事实上,除了缺少 PyScript 部分之外,大部分代码都是相同的。
此外,事件监听器和它调用的函数现在是用 Javascript 编写的,当然,不需要代理。
最大的变化是访问 Python 代码的方式。这是通过调用服务器上的回调函数来完成的。
我将在这里重复这段代码:
这个函数是异步的,这意味着在它被调用后,应用程序的执行不会停止,而是允许该函数在后台继续执行,直到它完成。这是调用服务器函数的理想方法,因为我们不能确定服务器需要多长时间来响应,也不想在等待响应时冻结应用程序。
服务器上的回调端点称为callback,它期望看到一个称为data的参数,该参数将保存下拉菜单中的选择值。
服务器将以 JSON 格式的图表数据进行响应,这将用于绘制图表,就像以前一样,使用 Plotly Javascript 库。
这将 Python 代码留在了服务器上。
我们从导入开始,然后我们可以看到两个端点的定义。代码的格式如下:
@app.route('/')
def index():
*# some code that returns a web page or other data*
第一个端点是根,使用 Flask 函数render_template()返回我们上面看到的 HTML 模板。
第二个端点是回调函数。这不返回网页,只返回网页自我更新所需的数据。它调用函数getGraph(),该函数的工作与 PyScript 版本相同,它加载并过滤数据,然后创建图表数据,该数据返回给异步 Javascript 函数。
我希望你能看到,Flask 应用程序和 PyScript 版本做的完全一样。
那么你为什么要选择一种方法而不是另一种呢?
结论
两个应用程序都可以工作,而且看起来一样。那么,如何选择使用哪种方法呢?
首先,我们应该意识到这是一个非常简单的应用程序。更复杂的应用程序可能会下载更多的数据,可能需要更多的处理,或者可能需要更复杂的用户界面。
但是这些例子说明了这种类型的应用程序所需的基本操作。
我在一台典型的家用笔记本电脑上运行了这两个应用程序,它们都运行得很好。PyScript 应用程序的加载时间要长得多,但响应时间可能会稍微快一些。坦率地说,在您等待 PyScript 版本加载(需要几秒钟)之后,它们之间并没有太大的区别。
这里有几个变量:
- 互联网连接的速度将决定向服务器发出请求的速度以及数据返回的速度
- 服务器的力量。大多数服务器比任何台式机或笔记本电脑都更强大
- 用户计算机的能力。当然,一个较弱的硬件会比一个更强的硬件慢,但这将对 PyScript 应用程序产生更大的影响,因为处理是在本地完成的。
获胜者是…
这两种技术都是有效和有用的。PyScript 可能还有很长的发展路要走,毫无疑问还会改进,但是,即使是现在,它也是轻量级应用的一个好的解决方案。
如果有大量的处理工作要做,那么目前基于服务器的解决方案可能仍然是最好的方法,但是随着硬件变得更强大(它总是这样)和 PyScript 的改进,这种情况可能会改变。
PySript 是工具箱中另一个看起来很有前途的工具。我怀疑它会完全取代基于服务器的应用程序,但对于后端处理在典型主机能力范围内的应用程序,PyScript 可能会找到自己的位置。
一如既往,感谢阅读。要下载代码或找到更多关于 PyScript、Python、Flask 和其他数据科学相关主题的文章,请参见我的 Github 网页。
你也可以订阅我的时事通讯 Technofile 来了解我正在做的事情。
https://technofile.substack.com
笔记
- 文章用 PyScript 和 Pandas 创建一个交互式 web 应用包含了使用 Pandas 绘图的原始 Web 应用。如何结合 PyScript 使用 Plotly展示了如何使用 Plotly 创建一个类似的应用程序。
- 天气数据来自我的报告英国历史天气,并来自英国气象局历史气象站数据。它是根据英国开放政府许可证发布的,可以在相同的条件下使用。
PySpark 还是熊猫?为什么不两者都要
原文:https://towardsdatascience.com/pyspark-or-pandas-why-not-both-95523946ec7c
整体大于部分之和

戴维·马尔库在 Unsplash 上的照片
导入并开始数据集
系列到系列和多个系列到系列
系列的迭代器到系列的迭代器和多个系列的迭代器到系列的迭代器
数据帧的迭代器到数据帧的迭代器
系列到标量和多个系列到标量
组映射 UDF
最终想法
PySpark 允许许多开箱即用的数据转换。然而,在熊猫身上可以得到更多。Pandas 功能强大,但由于其内存处理特性,它无法处理非常大的数据集。另一方面,PySpark 是一个用于大数据工作负载的分布式处理系统,但不支持 pandas 提供的丰富的数据转换。随着 Spark 3.x 的发布,PySpark 和 pandas 可以通过利用多种方法创建 pandas 用户定义函数(UDF)来进行组合。本文的目的是展示一组使用 Spark 3.2.1 的熊猫 UDF 示例。在后台,我们使用 Apache Arrow,这是一种内存中的列数据格式,可以在 JVM 和 Python 进程之间高效地传输数据。更多信息可以在 PySpark 用户指南中的官方 Apache Arrow中找到。
本文中的内容不要与官方用户指南中描述的 Spark 上的最新熊猫 API 相混淆。这是 Spark 中利用熊猫表现力的另一种可能性,代价是一些不兼容性。
导入和启动数据集
对于本文中的例子,我们将依赖熊猫和 numpy。我们还使用(希望)常用的约定从pyspark.sql导入函数和类型模块:
import pandas as pd
import numpy as np
import pyspark.sql.functions as F
import pyspark.sql.types as T
所有示例都适用于 20 行 4 列的小型数据集:
- group,一个用作分组键的
T.StringType()列 - x,一个
T.DoubleType()栏目 - y_lin,一个
T.DoubleType()列,它是 x 的两倍,带有一些噪声 - y_qua,一个三倍于 x 的平方的
T.DoubleType()列,带有一些噪声
火花数据帧可以通过以下方式构建
g = np.tile(['group a','group b'], 10)
x = np.linspace(0, 10., 20)
np.random.seed(3) # set seed for reproducibility
y_lin = 2*x + np.random.rand(len(x))/10.
y_qua = 3*x**2 + np.random.rand(len(x))
df = pd.DataFrame({'group': g, 'x': x, 'y_lin': y_lin, 'y_qua': y_qua})
schema = StructType([
StructField('group', T.StringType(), nullable=False),
StructField('x', T.DoubleType(), nullable=False),
StructField('y_lin', T.DoubleType(), nullable=False),
StructField('y_qua', T.DoubleType(), nullable=False),
])
df = spark.createDataFrame(df, schema=schema)
其中spark是产生的 spark 会话
spark = (
SparkSession.builder
.appName('learn pandas UDFs in Spark 3.2')
.config('spark.sql.execution.arrow.pyspark.enabled', True)
.config('spark.sql.execution.arrow.pyspark.fallback.enabled', False)
.getOrCreate()
)
数据帧可通过以下方式检查
def show_frame(df, n=5):
df.select([F.format_number(F.col(col), 3).alias(col)
if df.select(col).dtypes[0][1]=='double'
else col
for col in df.columns]).show(truncate=False, n=n)show_frame(df)
# +-------+-----+-----+------+
# |group |x |y_lin|y_qua |
# +-------+-----+-----+------+
# |group a|0.000|0.055|0.284 |
# |group b|0.526|1.123|1.524 |
# |group a|1.053|2.134|3.765 |
# |group b|1.579|3.209|7.636 |
# |group a|2.105|4.300|13.841|
# +-------+-----+-----+------+
# only showing top 5 rows
注意双栏的格式化/截断。仅显示了 20 行中的 5 行。
串联到串联和多个串联到串联
最简单的熊猫 UDF 将一个熊猫系列转换为另一个熊猫系列,没有任何聚合。例如,通过减去平均值并除以标准偏差来标准化一个系列,我们可以使用
# series to series pandas UDF
@F.pandas_udf(T.DoubleType())
def standardise(col1: pd.Series) -> pd.Series:
return (col1 - col1.mean())/col1.std()
res = df.select(standardise(F.col('y_lin')).alias('result'))
装饰者需要熊猫 UDF 的返回类型。还要注意函数定义中 python 类型的使用。结果可通过以下方式检查
print(f"mean and standard deviation (PYSpark with pandas UDF) are\n{res.toPandas().iloc[:,0].apply(['mean', 'std'])}")# mean and standard deviation (PYSpark with pandas UDF) are
# mean 6.661338e-17
# std 9.176629e-01
# Name: result, dtype: float64
正如我们在上面看到的,平均值在数字上等于零,但标准差不是。这是因为 PySpark 的分布式特性。PySpark 将执行 Pandas UDF,方法是将列分成批,并调用每个批的函数作为数据的子集,然后将结果连接在一起。因此,在上面的例子中,标准化应用于每个批次,而不是作为整体的数据帧。我们可以通过用熊猫本身测试熊猫 UDF 来验证这种说法的有效性:
res_pd = standardise.func(df.select(F.col('y_lin')).toPandas().iloc[:,0])print(f"mean and standard deviation (pandas) are\n{res_pd.apply(['mean', 'std'])}")# mean and standard deviation (pandas) are
# mean -2.220446e-16
# std 1.000000e+00
# Name: y_lin, dtype: float64
在那里可以使用standardise.func()从装饰过的熊猫中找回原来的熊猫 UDF。验证语句有效性的另一种方法是使用重新分区
res = df.repartition(1).select(standardise(F.col('y_lin')).alias('result'))
print(f"mean and standard deviation (PYSpark with pandas UDF) are\n{res.toPandas().iloc[:,0].apply(['mean', 'std'])}")# mean and standard deviation (PYSpark with pandas UDF) are
# mean -2.220446e-16
# std 1.000000e+00
# Name: result, dtype: float64
这当然不是现实生活中所希望的,但有助于在这个简单的例子中演示内部工作原理。
多个串联到串联的情况也很简单。作为一个简单的例子,我们添加两列:
# multiple series to series pandas UDF
@F.pandas_udf(T.DoubleType())
def add_cols(col1: pd.Series, col2: pd.Series) -> pd.Series:
return col1 + col2
res = df.select(F.col('y_lin'), F.col('y_qua'), add_cols(F.col('y_lin'), F.col('y_qua')).alias('added columns'))show_frame(res)
# +-----+------+-------------+
# |y_lin|y_qua |added columns|
# +-----+------+-------------+
# |0.055|0.284 |0.339 |
# |1.123|1.524 |2.648 |
# |2.134|3.765 |5.899 |
# |3.209|7.636 |10.845 |
# |4.300|13.841|18.141 |
# +-----+------+-------------+
# only showing top 5 rows
返回的序列也可以是类型T.StructType(),在这种情况下,我们指示熊猫 UDF 返回一个数据帧。举个简单的例子,我们可以通过在数据框中组合两列来创建一个结构列
# series to series (struct) pandas UDF
schema = T.StructType([
StructField('y_lin', T.DoubleType()),
StructField('y_qua', T.DoubleType()),
])
@F.pandas_udf(schema)
def create_struct(col1: pd.Series, col2: pd.Series) -> pd.DataFrame:
return pd.DataFrame({'y_lin': col1, 'y_qua': col2})res = df.select(F.col('y_lin'), F.col('y_qua'), create_struct(F.col('y_lin'), F.col('y_qua')).alias('created struct'))show_frame(res)
# +-----+------+------------------------------------------+
# |y_lin|y_qua |created struct |
# +-----+------+------------------------------------------+
# |0.055|0.284 |{0.05507979025745755, 0.28352508177131874}|
# |1.123|1.524 |{1.123446361209179, 1.5241628490609185} |
# |2.134|3.765 |{2.134353631786031, 3.7645534406624286} |
# |3.209|7.636 |{3.2089774973618717, 7.6360921152062655} |
# |4.300|13.841|{4.299821011224239, 13.8410479099986} |
# +-----+------+------------------------------------------+
# only showing top 5 rowsres.printSchema()
# root
# |-- y_lin: double (nullable = false)
# |-- y_qua: double (nullable = false)
# |-- created struct: struct (nullable = true)
# | |-- y_lin: double (nullable = true)
# | |-- y_qua: double (nullable = true)
上面的一个小麻烦是列y_lin和y_qua被命名了两次。很高兴在评论中听到如果这可以避免的话!
系列迭代器到系列迭代器,多个系列迭代器到系列迭代器
当我们想要为每一批执行一次昂贵的操作时,迭代器变体是很方便的,例如,通过初始化一个模型。在下一个例子中,我们通过简单地为每批生成一个随机倍数来模拟这一点
# iterator of series to iterator of series
from typing import Iterator
@F.pandas_udf(T.DoubleType())
def multiply_as_iterator(col1: Iterator[pd.Series]) -> Iterator[pd.Series]:
# the random multiple is generated once per batch
random_multiple = np.random.randint(1,10,1)[0]
for s in col1:
yield random_multiple*sres = df.select(F.col('y_lin'), multiply_as_iterator(F.col('y_lin')).alias('multiple of y_lin'))show_frame(res)
# +-----+-----------------+
# |y_lin|multiple of y_lin|
# +-----+-----------------+
# |0.055|0.496 |
# |1.123|10.111 |
# |2.134|19.209 |
# |3.209|28.881 |
# |4.300|38.698 |
# +-----+-----------------+
# only showing top 5 rows
如果我们想要控制批处理大小,我们可以在创建 spark 会话时将配置参数 spark . SQL . execution . arrow . maxrecordsperbatch 设置为所需的值。这只影响像 pandas UDFs 这样的迭代器,即使我们使用一个分区,它也适用。
从多个系列的迭代器到系列的迭代器相当简单,如下图所示,我们在对两列求和后应用了倍数
# iterator of multiple series to iterator of series
from typing import Iterator, Tuple
@F.pandas_udf(T.DoubleType())
def multiply_as_iterator2(col1: Iterator[Tuple[pd.Series, pd.Series]]) -> Iterator[pd.Series]:
# the random multiple is generated once per batch
random_multiple = np.random.randint(1,10,1)[0]
for s1, s2 in col1:
yield random_multiple*(s1 + s2)
res = df.select(F.col('y_lin'), F.col('y_qua'), multiply_as_iterator2(F.col('y_lin'), F.col('y_qua')).alias('multiple of y_lin + y_qua'))show_frame(res)
# +-----+------+-------------------------+
# |y_lin|y_qua |multiple of y_lin + y_qua|
# +-----+------+-------------------------+
# |0.055|0.284 |1.693 |
# |1.123|1.524 |13.238 |
# |2.134|3.765 |29.495 |
# |3.209|7.636 |54.225 |
# |4.300|13.841|90.704 |
# +-----+------+-------------------------+
# only showing top 5 rows
函数定义稍微复杂一些,因为我们需要构造一个包含熊猫系列的元组的迭代器。
数据帧的迭代器到数据帧的迭代器
数据帧的迭代器到数据帧的迭代器的转换类似于多重序列的迭代器到序列的迭代器。当我们需要在完整的数据框上而不是在选定的列上执行 pandas 操作时,这是首选的方法。
作为一个简单的例子,考虑最小-最大归一化
# iterator of data frame to iterator of data frame
from typing import Iterator
def min_max_normalise(frames: Iterator[pd.DataFrame]) -> Iterator[pd.DataFrame]:
for frame in frames:
yield (frame-frame.mean())/(frame.max()-frame.min())
schema = T.StructType([
StructField('y_lin', T.DoubleType()),
StructField('y_qua', T.DoubleType()),
])
res = df.select(F.col('y_lin'), F.col('y_qua')).mapInPandas(min_max_normalise, schema=schema)show_frame(res)
# +------+------+
# |y_lin |y_qua |
# +------+------+
# |-0.497|-0.378|
# |-0.245|-0.287|
# |-0.007|-0.121|
# |0.246 |0.164 |
# |0.503 |0.622 |
# +------+------+
# only showing top 5 rows
首先要注意的是需要向mapInPandas方法提供一个模式,并且不需要装饰器。mapInPandas方法可以改变返回数据帧的长度。同样,迭代器模式意味着数据帧不是作为一个整体进行最小-最大归一化,而是分别对每一批进行最小-最大归一化。
序列到标量和多个序列到标量
可以使用或不使用拆分-应用-组合模式将一个系列聚合为标量。典型地,使用分组的分离-应用-组合被应用,否则整个列将被带到驱动程序,这首先违背了使用 Spark 的目的。作为一个简单的例子,我们使用另一列进行分组来计算一列的平均值
# series to scalar
@F.pandas_udf(T.DoubleType())
def average_column(col1: pd.Series) -> float:
return col1.mean()
res = df.groupby('group').agg(average_column(F.col('y_lin')).alias('average of y_lin'))show_frame(res)
# |group |average of y_lin|
# +-------+----------------+
# |group a|9.509 |
# |group b|10.577 |
# +-------+----------------+
这是一个人为的例子,因为没有必要用熊猫 UDF,而是用普通香草派斯帕克
res = df.groupby('group').agg(F.mean(F.col('y_lin')).alias('average of y_lin'))show_frame(res)
# |group |average of y_lin|
# +-------+----------------+
# |group a|9.509 |
# |group b|10.577 |
# +-------+----------------+
也可以将一组列简化为标量,例如通过计算两列之和的平均值
# multiple series to scalar
@F.pandas_udf(T.DoubleType())
def average_column(col1: pd.Series, col2: pd.Series) -> float:
return (col1 + col2).mean()
res = df.groupby('group').agg(average_column(F.col('y_lin'), F.col('y_qua')).alias('average of y_lin + y_qua'))show_frame(res)
# +-------+------------------------+
# |group |average of y_lin + y_qua|
# +-------+------------------------+
# |group a|104.770 |
# |group b|121.621 |
# +-------+------------------------+
组图 UDF
在迄今为止的例子中,除了(多个)系列到标量,我们无法控制批次组成。串行到串行 UDF 将在分区上操作,而串行到串行 UDF 的迭代器将在每个分区的批处理上操作。在本文使用的示例数据框中,我们包含了一个名为 group 的列,我们可以用它来控制批次的组成。在现实生活中,需要注意确保该批具有熊猫一样的大小,以避免出现内存不足的异常。
使用组图 UDF,我们可以输入熊猫数据框并生成熊猫数据框。一个简单的例子标准化了数据帧:
# group map UDF
def standardise_dataframe(df1: pd.DataFrame) -> pd.DataFrame:
tmp = df1[['y_lin', 'y_qua']]
return (tmp - tmp.mean())/tmp.std()
schema = T.StructType([
T.StructField('y_lin', T.DoubleType()),
T.StructField('y_qua', T.DoubleType()),
])res = df.groupby('group').applyInPandas(standardise_dataframe, schema=schema)show_frame(res)
# +------+------+
# |y_lin |y_qua |
# +------+------+
# |-1.485|-1.009|
# |-1.158|-0.972|
# |-0.818|-0.865|
# |-0.500|-0.691|
# |-0.170|-0.443|
# +------+------+
# only showing top 5 rows
默认情况下不包含组名,需要在返回的数据框和方案中明确添加,例如使用…
# group map UDF
def standardise_dataframe(df1: pd.DataFrame) -> pd.DataFrame:
tmp = df1[['y_lin', 'y_qua']]
return pd.concat([df1['group'], (tmp - tmp.mean())/tmp.std()], axis='columns')
schema = T.StructType([
T.StructField('group', T.StringType()),
T.StructField('y_lin', T.DoubleType()),
T.StructField('y_qua', T.DoubleType()),
])res = df.groupby('group').applyInPandas(standardise_dataframe, schema=schema)show_frame(res)
# +-------+------+------+
# |group |y_lin |y_qua |
# +-------+------+------+
# |group a|-1.485|-1.009|
# |group a|-1.158|-0.972|
# |group a|-0.818|-0.865|
# |group a|-0.500|-0.691|
# |group a|-0.170|-0.443|
# +-------+------+------+
# only showing top 5 rows
组图 UDF 可以更改返回的数据框的形状。例如,我们将通过对 y_lin 和 y_qua 列拟合二次多项式来计算系数
# group map UDF
def fit_polynomial(df1: pd.DataFrame) -> pd.DataFrame:
tmp = df1[['x', 'y_lin', 'y_qua']]
# see https://numpy.org/doc/stable/reference/generated/numpy.polynomial.polynomial.Polynomial.fit.html
poly_lin = np.polynomial.polynomial.Polynomial.fit(x=tmp['x'], y=tmp['y_lin'], deg=2).convert().coef
poly_qua = np.polynomial.polynomial.Polynomial.fit(x=tmp['x'], y=tmp['y_qua'], deg=2).convert().coef
df2 = pd.DataFrame({'group': df1['group'].iloc[0], 'y_lin fitted coffficients': [poly_lin.tolist()], 'y_qua fitted coefficients': [poly_qua.tolist()]})
return df2
schema = T.StructType([
T.StructField('group', T.StringType()),
T.StructField('y_lin fitted coefficients', T.ArrayType(T.DoubleType())),
T.StructField('y_qua fitted coefficients', T.ArrayType(T.DoubleType())),
])
res = df.groupby('group').applyInPandas(fit_polynomial, schema=schema)
show_frame(res)show_frame(res)
# +-------+---------------------------------------------------------------+--------------------------------------------------------------+
# |group |y_lin fitted coefficients |y_qua fitted coefficients |
# +-------+---------------------------------------------------------------+--------------------------------------------------------------+
# |group a|[0.05226283524780051, 1.9935642550858421, 4.346056274066657E-4]|[0.24210502752802654, 0.14937331848708446, 2.9865040888654355]|
# |group b|[0.07641111656270816, 1.9894934336694825, 8.012896992570311E-4]|[0.38970142737052527, 0.10989441330142924, 2.9877883688982467]|
返回的列是数组。我们可以看到,考虑到添加到原始数据帧的噪声并不过分,这些系数非常接近预期值。我们还看到两组给出了非常相似的系数。
最后的想法
Pandas UDFs 很好地补充了 PySpark API,并允许更具表现力的数据操作。PySpark 发展迅速,从 2.x 版到 3.x 版的变化非常显著。虽然本文涵盖了许多当前可用的 UDF 类型,但可以肯定的是,随着时间的推移,将会引入更多的可能性,因此在决定使用哪一种之前,最好先查阅文档。
PySpark 配方:映射和取消透视
原文:https://towardsdatascience.com/pyspark-recipes-map-and-unpivot-d724cca7d0d4
PySpark API 真的缺少关键功能吗?

PySpark 提供了一个流畅的 API,可以满足大多数需求。尽管如此,有经验的 pandas 用户可能会发现一些数据转换并不那么简单。本文旨在提供少量的配方来涵盖一些用户可能认为 PySpark API 本身不支持的用例。实际上它们是被支持的,但是它们确实需要更多的努力(和想象力)。
我们从必要的导入和 spark 会话的创建开始
注意,出于本文的目的,我们使用一个本地 PySpark 环境,它有 4 个内核和 8gb 内存。我们将为每个配方创建简单的数据框。
地图值
第一个方法处理映射值,并基于创建一个映射列
映射键值对存储在字典中。构造chain(*mapping.items())返回一个键值对的链对象 as (key1,value1,key2,value2,...),这些键值对用于创建映射列。这个映射列本质上是一个常量,因此,我们在数据框的每一行中都有相同的映射。映射是通过检索原始列中每个键的映射值来实现的。原始列可能包含在映射中作为键存在的值,这些值因此被映射为 null。原始列中的空值不会导致错误,并且在映射后保持为空。可以使用默认值代替空值,例如
为了完整起见,也可以在 spark 上使用 pandas API 来实现相同的结果
但是本文并没有深入研究这个新的 API,它是一个很大的发展,尤其是对于原型开发而言。我在结论中包含了更多关于这个问题的想法。
融化(又称逆透视)
使用过熊猫的用户可能想知道我们如何模仿熊猫 API 中的融化功能。Melt 将数据帧转换为一种格式,其中一列或多列为标识符变量(id_vars),而所有其他列(被视为测量变量(value_vars ))被“解投影”到行轴。换句话说,数据帧从宽格式转换为长格式。
演示该配方的起始数据框可以用以下内容构建
在 PySpark 中可能有几种方法来实现熔化函数。在本教程中,我们演示了两个,试图介绍一种通用的思维方式,这种方式在其他情况下也是有帮助的。第一个解决方案构造了一个随后被分解的结构数组
为了增加行数和减少列数,我们需要以某种方式将值变量的内容打包到一个容器中,然后将容器分解成多行。使用一个理解,我们创建一个结构体数组,该数组被展开并存储在一个新创建的名为_vars_and_vals的列中。展开后,我们在每一行中都有一个结构,可以从中检索所有值变量的列名和列值。
举例来说,我们可以在起始数据框中取消透视一些或所有列
第二个解决方案依赖于从数组中创建一个地图,然后对其进行分解。
地图的创建也可以通过其他方式来实现,例如F.create_map(*chain(*((F.lit(x), F.col(x)) for x in value_vars))). 使用地图作为容器可能不太明显,因为分解地图可能不会立即出现在脑海中,尽管文档对此有明确说明。这两个解决方案提供了相同的结果,但是我不确定哪个解决方案的性能更好(欢迎评论)。就可读性而言,我个人认为第一个解决方案更好。
结论
希望这篇文章提供了关于如何使用生成和处理容器(如映射、数组和结构)的pyspark.sql.functions来模拟众所周知的 pandas 函数的见解。另一个选择是使用最近推出的 PySpark pandas API ,在 Spark v3.2 之前它曾被称为考拉。在文档中的官方建议是,如果你已经熟悉 pandas,就使用 PySpark pandas API,如果你不熟悉,就直接使用 PySpark API。我认为自己是一个有经验的 pandas 用户,但是除了快速原型之外,我仍然更喜欢依赖 PySpark API。主要原因是,与考虑直接使用 PySpark API 的解决方案相比,我不需要担心可能需要更多时间才能发现的不兼容性。例如,Spark 提供了 null(在 SQL 意义上,作为缺失值)和 nan(数字而不是数字),而 pandas 没有可用于表示缺失值的原生值。在使用 PySpark pandas API 时用 pandas 思考,在某种程度上就像试图通过翻译你的母语来说一种语言。从长远来看,直接用新语言思考可能更好。这只是个人观点,并不是对 PySpark 熊猫 API 质量的批评。我确信有不同的观点,意见的多样性对取得进展至关重要。
10 分钟内完成标记、模仿和固定装置的 Pytest
原文:https://towardsdatascience.com/pytest-with-marking-mocking-and-fixtures-in-10-minutes-678d7ccd2f70
用 Python pytest 编写健壮的单元测试

杰夫·谢尔登在 Unsplash 上拍摄的照片
在我的上一篇关于单元测试的文章中,我阐述了单元测试的目的、单元测试生态系统和最佳实践,并用 Python 内置的unittest包演示了基本和高级的例子。有不止一种方法(和不止一个 Python 包)来执行单元测试,本文将演示如何用 Python pytest包实现单元测试。本文将紧密跟随上一篇文章的流程,因此您可以比较unittest vs. pytest的各个组件。
虽然unittest包是面向对象的,因为测试用例是以类的形式编写的,但是pytest包是功能性的,导致代码行更少。就个人而言,我更喜欢unittest,因为我觉得代码可读性更强。也就是说,这两个包,或者更确切地说是框架,都同样强大,在它们之间进行选择只是个人喜好的问题。
更新 :本文是系列文章的一部分。查看其他“10 分钟内”话题 此处 !
目录
设置 Pytest
与unittest不同,pytest不是内置的 Python 包,需要安装。这可以简单地通过终端上的pip install pytest来完成。
请注意,unittest的最佳实践也适用于pytest,因此
- 所有的单元测试必须写在一个
tests/目录中 - 文件名应该严格以
tests_开头 - 函数名应该严格以
test开头
必须遵循命名约定,以便检查器可以在运行时发现单元测试。
单元测试的结构
def test_function_with_scenario_one():
print("Testing function with scenario one")
assert 1 + 1 == 2, f"Check addition value {1 + 1} does not match {2}"
pytest中的单元测试是在函数中编写的,断言是在函数中做出的。这使得pytest很容易理解,并且单元测试代码看起来短小精悍!
运行单元测试
可以通过在命令行中键入pytest来运行单元测试,这将发现所有的单元测试,如果它们遵循命名约定的话。单元测试输出返回测试运行的总数、通过、跳过和失败的测试数、运行测试所用的总时间以及失败堆栈跟踪(如果有)。

图 1:运行单元测试——作者图片
要对特定的目录、文件或函数运行单元测试,命令如下
$ pytest tests/
$ pytest tests/test_sample.py
$ pytest tests/test_sample.py::test_function_one
还有更多定制可以附加到命令中,例如
-x:立即退出或快速失败,在遇到第一次测试失败时停止所有单元测试-k "keyword":指定选择性运行测试的关键字,可以匹配文件名或函数名,可以包含and和not语句--ff:第一次失败,从上次失败的测试开始运行所有测试(首选)--lf:上次失败,运行上次运行失败的测试(缺点:可能无法发现之前通过的测试中的失败)--sw:逐步地,在第一次测试失败时停止,并从那里继续下一次运行(缺点:可能无法发现先前通过的测试中的失败)
额外提示:如果单元测试运行时间太长,你可以并行运行它们,而不是顺序运行!安装pytest-xdist Python 包,并在运行单元测试时将其添加到命令中,
-n <number of workers>:并行运行测试的工人数量
调试错误:当您的测试脚本从基本目录或任何源目录中的一个文件夹导入时,您可能会面临错误ModuleNotFoundError。例如,您的函数驻留在src/sample_file.py中,而您驻留在tests/目录中的测试脚本执行导入from src.sample_file import sample_function。
为了克服这个问题,在基本目录中创建一个配置文件pytest.ini,以指示相对于基本目录执行导入的目录。要添加到配置文件中的内容示例如下:
[pytest]
pythonpath = .
这个配置文件可以扩展到更多的用途,这将在后面的章节中详细说明。目前,这种配置允许您绕过ModuleNotFoundError错误。
在了解了单元测试的基本结构以及如何运行它们之后,是时候深入研究了!
实际上,单元测试可能不像调用函数和测试给定输入的预期输出那样简单。可以有高级的逻辑,例如浮点精度的计算、预期错误的测试、将测试分组在一起、单元测试的有条件跳过、模拟数据等。这可以通过pytest包中的上下文管理器和装饰器来完成(基本上已经为您编码好了!).
高级:内置断言
除了测试预期的输出,您还可以测试预期的错误,以确保函数在以非设计方式使用时会抛出错误。这可以通过pytest.raises上下文管理器,使用with关键字来完成。
import pytest
def test_expected_error():
with pytest.raises(TypeError):
assert 1 + "a", "This should raise an error"
高级:跳过单元测试(通过标记)
单元测试可以使用pytest.mark decorator 进行标记,这允许各种扩展功能,例如,
- 将单元测试分组:多个单元测试可以作为一个组来运行
- 标记为失败:表示单元测试预计会失败
- 标记为跳过/有条件跳过:单元测试默认行为将被跳过,或者在满足某些条件时被跳过
- 标记为插入参数:测试单元测试的各种输入
装饰者可以被堆叠以提供多种扩展功能。例如,可以将测试标记为一个组,并标记为跳过!
上面阐述的各种功能以这种方式实现,
a)对单元测试进行分组
取代在一个文件夹、文件中或者通过关键字搜索来运行单元测试,单元测试可以被分组并且用pytest -m <group-name>来调用。测试的输出将显示运行的测试数量以及由于不在组中而被取消选择的测试数量。
这可以通过下面的例子用pytest.mark.<group-name>装饰器来实现,
import pytest
@pytest.mark.group1
def test_sample():
pass
为了使这个工作,我们需要在配置文件中定义这个组,下面的内容可以添加到pytest.ini文件中的现有内容中,
markers =
group1: description of group 1
b)标记为失败
对于预计会失败的测试,可以用pytest.mark.xfail装饰器进行标记。如果单元测试失败(与正常场景中抛出错误相反),输出将显示xfailed,如果单元测试意外通过,输出将显示xpassed。一个例子如下,
import pytest
@pytest.mark.xfail
def test_fail():
assert 1 == 2, "This should fail"
c)标记为跳过/有条件跳过
将一个单元测试标记为跳过或在满足某些条件时跳过与上一节类似,只是装饰者分别是pytest.mark.skip和pytest.mark.skipif。如果在较新的 Python 版本或较新的 Python 包版本中测试不再按预期工作,跳过单元测试是有用的。
import sys
import pytest
@pytest.mark.skip
def test_skip():
raise Exception("This test should be skipped")
@pytest.mark.skipif(sys.version_info.major == 2, reason="requires Python 3.x")
def test_skipif():
pass
d)标记为插入参数
在某些情况下,我们会希望针对一些输入来测试函数,例如,针对正常情况和边缘情况来测试代码库。与在一个单元测试中编写多个断言或编写多个单元测试不同,我们可以按照如下方式自动测试多个输入,
import pytest
@pytest.mark.parametrize("n", [2, 4, 6])
def test_even_number(n):
assert not n % 2, f"{n} is not an even number"
@pytest.mark.parametrize(["n", "expected_output"], [(1, 3), (2, 6)])
def test_multiplication(n, expected_output):
assert n * 3 == expected_output, "Check multiplication"
高级:单元测试中的模拟
模拟在单元测试中用来替换函数的返回值。替换不应该在测试环境中运行的操作是有用的,例如,当测试环境没有相同的数据访问时,替换连接到数据库并加载数据的操作。
在pytest中,嘲讽可以代替函数内函数的返回值。这对于测试期望的函数和替换我们正在测试的期望函数中的嵌套函数的返回值是有用的。
因此,模拟减少了单元测试的依赖性,因为我们测试的是期望的函数,而不是它对其他函数的依赖性。
例如,如果所需的函数通过连接到数据库来加载数据,我们可以模拟加载数据的函数,使它不连接到数据库,而是提供要使用的替代数据。
要实现模拟,安装pytest-mock Python 包。在这个例子中,在src/sample_file.py文件中,我们定义了期望的函数和要模仿的函数。
def load_data():
# This should be mocked as it is a dependency
return 1
def dummy_function():
# This is the desired function we are testing
return load_data()
在测试脚本中,我们通过指定完整的点路径来定义要模拟的函数,并定义应该返回的值,
from src.sample_file import dummy_function
def test_mocking_function(mocker):
mocker.patch("src.sample_file.load_data", return_value=2)
assert dummy_function() == 2, "Value should be mocked"
Mocking 可以修补代码库中的任何函数,只要您定义完整的虚线路径。请注意,您不能模拟正在测试的期望函数,但是可以模拟期望函数所依赖的任何依赖项,甚至是嵌套依赖项。
高级:其他
a) Pytest 配置
正如前面章节中所讨论的,配置文件pytest.ini可以在基本目录中定义,以绕过ModuleNotFoundError并定义单元测试组。现在看起来应该是这样的,
[pytest]
pythonpath = .
markers =
group1: description of group 1
配置文件允许用户指定运行单元测试的默认模式,例如,pytest --ff用于失败的第一次设置,或者pytest -ra -q用于压缩的输出结果。默认模式可以通过在配置文件中添加行addopts = -ra -q来指示。
为了抑制警告,我们还可以将ignore::DeprecationWarning或ignore::ImportWarning添加到配置文件中。
可以向配置文件中添加更多的项目,但这些是更常见的项目。pytest配置文件的官方文档可以在这里找到。
b)重用变量(通过夹具)
Fixtures 可以用来标准化跨多个单元测试的输入。例如,可以定义一个 fixture 来加载一个文件或创建一个对象,作为多个测试的输入,而不是在每个测试中重写相同的代码行。
import pytest
@pytest.fixture
def input_dict():
return {"a": 1}
def test_fixture(input_dict):
assert input_dict["a"] == 1, f"Check fixture {input_dict}"
夹具可以在同一个 Python 文件中定义,也可以在由pytest自动处理的文件tests/conftest.py中定义。
c)考虑浮点精度
当断言数值的相等条件时,由于浮点运算限制,小数点位置的值可能会有差异。
为了解决这个问题,我们可以用一定的容差来比较数值的相等性。默认情况下,使用assert output_value == **pytest.approx**(expected_value)将允许相等比较放宽到公差1e-6。
希望你已经学习了更多关于用pytest实现单元测试的知识,以及一些你可以用单元测试做的很酷的技巧。还提供了更多的功能,比如使用monkeypatch模仿数据,在 fixtures 中定义范围,结合unittest Python 包使用pytest,等等。有需求的话可以出续集;)
感谢您的阅读!如果你喜欢这篇文章,请随意分享。
相关链接
pytest文档:https://docs.pytest.orgpytest教程:https://www.tutorialspoint.com/pytest/index.htm
体育分析中的毕达哥拉斯期望,以及不同体育项目的例子
毕达哥拉斯期望用于不同的运动,如棒球、篮球、足球、曲棍球等,以推动数据驱动的分析和预测建模

毕达哥拉斯的期望是一个体育分析公式,是伟大的棒球分析师和统计学家之一- Bill James 的发明。最初源于棒球,并为棒球而设计,最终用于其他职业运动,如篮球、足球、美式足球、冰球等。
该公式基本上说明了职业运动队在给定赛季中赢得比赛的百分比应与该队在该赛季中得分/得分/进球的平方除以该队及其对手在整个赛季中得分/得分/进球的平方之和的比率成比例:

这个概念不仅有助于解释团队成功的原因,还可以作为预测未来结果的基础。这是一种我们可以用数据来衡量的关系。我们实际上可以计算每支球队的毕达哥拉斯期望,然后我们可以测试它是否真的与球队在给定赛季的胜率相关。
随着时间的推移,毕达哥拉斯的期望公式已经根据不同的用例进行了修补和增强。修改主要集中在指数的值上。棒球的理想指数是 1.83,而不是 2。python report和python pat是比尔·詹姆斯(Bill James)原始公式的两种修改形式,已在棒球比赛中使用,用于计算跑步环境的理想指数,而不是使用固定的指数值。
类似地,统计学家也研究和挖掘了其他运动的不同理想指数——篮球是 13.91,冰球是 2.37。篮球的指数较高是因为与棒球等运动相比,机会在篮球中的作用较小。
然而,在本文中,我们将深入研究毕达哥拉斯期望公式的基本形式,看看它如何与不同职业运动中的团队胜率相关联。在随后的文章中,我们还将了解如何使用毕达哥拉斯期望作为预测指标,也就是说,我们如何使用历史毕达哥拉斯期望来预测未来的胜率。
毕达哥拉斯的期望和美国职业棒球大联盟(MLB)
我们将从将以下模块导入 Jupyter 笔记本开始:
import pandas as pd
import numpy as np
import statsmodels.api as sm
import matplotlib.pyplot as plt
import seaborn as sns
在这一部分,我们将关注 2018 赛季的 MLB 奥运会,可以从 Retrosheet 下载日志。以下是数据框的一瞥:

作者图片
上面的截图只覆盖了前几列。MLB 数据集中总共有 161 列/特征/变量,总共有 2431 行,每行代表一个游戏。但是,对于本文,我们只需要几列:主队、客队、主队得分、客队得分、比赛日期。为了便于使用,我们还可以将列重命名为稍短的变量名:
MLB18 = MLB[['VisitingTeam','HomeTeam','VisitorRunsScored','HomeRunsScore','Date']]
MLB18 = MLB18.rename(columns={'VisitorRunsScored':'VisR','HomeRunsScore':'HomR'})
现在,我们的数据集由单个游戏组成,每个游戏中有两个团队;有一个主队和一个客队。如果我们想计算整个赛季球队得分的总数,我们需要考虑主队得分和客队得分。此外,我们还需要主队得分,客队得分。为了做到这一点,我们将把这个数据帧分割成两个更小的数据帧;一个给客队,一个给主队。然后,我们将合并这两个数据集,以获得整个赛季每支球队的总数。
在此之前,我们必须定义每场比赛的赢家,这在棒球比赛中很简单——得分最多的队获胜。我们可以使用 np.where Numpy 方法将胜利分成两个不同的列:主队胜利和客场胜利:
MLB18['hwin'] = np.where(MLB18['HomR'] > MLB18['VisR'],1,0)
MLB18['awin'] = np.where(MLB18['HomR'] < MLB18['VisR'],1,0)
MLB18['count'] = 1
当我们将记录合并成一个单一的合并数据框时,将在后端使用新列 count:

作者图片
接下来,我们将创建两个独立的数据框,从主队的数据框开始。我们按主队对 MLB18 数据集进行分组,以获得胜场和跑垒(得分和失球)的总和,以及显示比赛场次的计数器变量(在 MLB,各队在常规赛中的比赛场次不一定相同):
MLBhome = MLB18.groupby('HomeTeam')['hwin','HomR','VisR','count'].sum().reset_index()
MLBhome = MLBhome.rename(columns={'HomeTeam':'team','VisR':'VisRh','HomR':'HomRh','count':'Gh'})
总共有 30 支球队,在下表中显示了以下信息:球队的名称、作为主队的球队的获胜次数、作为主队的球队得分的次数、当主队是客队时客队对该队得分的次数以及在给定赛季中作为主队的球队比赛的总次数。

作者图片
我们对客队重复同样的过程。我们从下面的代码片段中获得以下详细信息:球队的名称、作为客队的球队获胜的次数、作为客队的球队得分的次数、当客队时东道主对球队得分的次数以及在给定赛季中作为客队的球队比赛的总次数。
MLBaway = MLB18.groupby('VisitingTeam')['awin','HomR','VisR','count'].sum().reset_index()
MLBaway = MLBaway.rename(columns={'VisitingTeam':'team','VisR':'VisRa','HomR':'HomRa','count':'Ga'})

作者图片
这两个数据框总结了主场球队和客场球队的表现。我们接下来需要做的是将这两个数据框架合并在一起,以给出每个团队在整个赛季的总表现。为此,我们使用 pd.merge Pandas 方法来合并“team”列上的两个数据帧。

作者图片
从这个合并的数据集中,我们现在可以将这些列相加,以获得整个赛季球队的总胜场数、比赛场次、得分以及得分。
MLB18['W']=MLB18['hwin']+MLB18['awin']
MLB18['G']=MLB18['Gh']+MLB18['Ga']
MLB18['R']=MLB18['HomRh']+MLB18['VisRa']
MLB18['RA']=MLB18['VisRh']+MLB18['HomRa']
请注意,有 30 个不同的团队,但为了便于查看,我们在列表中显示了前 10 个团队:

作者图片
准备数据的最后一步是定义胜率和毕达哥拉斯期望值。胜率就是在一个特定的赛季中赢得的比赛总数与比赛总数的比率。
MLB18['wpc'] = MLB18['W']/MLB18['G']
MLB18['pyth'] = MLB18['R']**2/(MLB18['R']**2 + MLB18['RA']**2)ax = sns.scatterplot(x="pyth", y="wpc", data=MLB18)
plt.show()

作者图片
上面的散点图相当清楚地告诉我们,在我们的特定用例中,毕达哥拉斯期望和胜率之间有很强的相关性——毕达哥拉斯期望越高,团队的胜率可能越高。这证实了比尔·詹姆斯所描述的关系的存在。
为了实际量化这种关系,我们可以拟合这种关系的回归方程,以观察毕达哥拉斯期望中每增加一个单位,获胜百分比增加多少。
model = sm.OLS(MLB18['wpc'],MLB18['pyth'],data=MLB18)
results = model.fit()
results.summary()
回归输出告诉你许多关于赢率和毕达哥拉斯期望值之间的拟合关系的事情。回归是一种确定最适合数据的方程的方法。在这种情况下,关系是:wpc =截距+系数 pyth

作者图片
我们可以看到截距值是 0.0609,系数是 0.8770。我们感兴趣的是后一种价值。这意味着毕达哥拉斯的期望值每增加一个单位,赢率的值就会增加 0.877。
(I)标准误差(std err)让我们了解估计的精确度。系数(coef)与标准误差的比值称为 t 统计量(t ),其值告诉我们统计显著性。这由 P 值(P > |t|)来说明—这是我们偶然观察到值 0.8770 的概率,如果真值真的为零。这里的概率是 0.000 —(这不完全是零,但表格中没有足够的小数位来显示这一点),这意味着我们可以确信它不是零。按照惯例,通常的结论是,如果 p 值大于 0.05,我们不能确信系数值不为零
(ii)表格的右上角是 R 平方。该统计数据告诉您 y 变量(wpc)的变化百分比,这可以通过 x 变量(python)的变化来解释。r 平方可以被认为是一个百分比——在这里,毕达哥拉斯的期望可以解释 89.4%的胜率变化。
毕达哥拉斯期望与 NBA
在篮球的例子中,我们有一个具有非常不同特征的数据集。与 MLB 示例的一个重要区别在于,这里每场比赛出现在两行中,每支球队一行,即每支球队在每场比赛中出现两次,首先作为主队,然后作为客场队。从这个意义上说,我们的行数是游戏数的两倍。因此,我们不需要为主队和客队分别创建两个数据框,因为在本场景中已经为我们完成了这一工作。
数据由 2018 赛季的比赛组成,下面是数据框中的列/特征/变量列表:

游戏结果是标有“WL”的栏。我们创建了一个变量,如果球队赢了,这个变量的值为 1,如果输了,这个变量的值为零。现在,为了计算毕达哥拉斯期望,我们只需要结果,得分(PTS)和失分(PTSAGN)。
NBAR18['result'] = np.where(NBAR18['WL']== 'W',1,0)
NBAteams18 = NBAR18.groupby('TEAM_NAME')['result','PTS','PTSAGN'].sum().reset_index()
由于每支球队在 NBA 赛季都要打 82 场比赛,我们可以用下面的方法计算每支球队(n=30)的胜率和毕达哥拉斯期望值:
NBAteams18['wpc'] = NBAteams18['result']/82
NBAteams18['pyth'] = NBAteams18['PTS']**2/(NBAteams18['PTS']**2 + NBAteams18['PTSAGN']**2)

作者图片
现在,通过我们的统计分析,我们首先在 Seaborn 创建一个散点图,看看这种关系是什么样子的。如图所示,它看起来非常类似于棒球的例子。

作者图片
我们可以为这种关系拟合一个回归方程,以观察毕达哥拉斯期望值每增加一个单位,篮球的胜率会增加多少。

作者图片
上面的结果摘要显示了非常大的 t 统计量和 0.000 的 P 值,这基本上意味着这在统计学上非常显著。R 平方(决定系数)值接近 100%,这意味着几乎所有因变量(wpc)的变动都可以完全用自变量(python)的变动来解释。
毕达哥拉斯期望与印度超级联赛
在本文的最后一个例子中,我们将研究板球最引人注目的比赛 IPL 的一个例子。我们将使用 2018 年 IPL 赛季比赛的数据,数据集包括以下几列:

作者图片
首先,我们确定主队何时获胜,客队何时获胜。接下来,我们确定主队和客场队的得分(注意:与棒球不同,每队有九局,在 T20 板球中,每队只有一局,一旦第一队完成这一局,对方队就有这一局)。最后,我们包括一个计数器,我们可以把它加起来,给出每支球队的比赛总数。
IPL18['hwin']= np.where(IPL18['home_team']==IPL18['winning_team'],1,0)
IPL18['awin']= np.where(IPL18['away_team']==IPL18['winning_team'],1,0)
IPL18['htruns']= np.where(IPL18['home_team']==IPL18['inn1team'],IPL18['innings1'],IPL18['innings2'])
IPL18['atruns']= np.where(IPL18['away_team']==IPL18['inn1team'],IPL18['innings1'],IPL18['innings2'])
IPL18['count']=1
这里需要注意的一点是,IPL18 数据帧中只有 60 行(匹配)。因此,我们在板球例子中拥有的数据量明显少于我们前面提到的篮球和棒球例子,这可能是一个潜在的问题,我们稍后会发现。
与我们在 MLB 的例子中所做的类似,在 IPL 的例子中,我们也必须为主队和客场队创建两个独立的数据框架。我们使用相同的。groupby 命令汇总 2018 赛季主客场球队的表现,并将这两个数据帧合并,以获得显示八支 IPL 球队表现的组合数据帧:
IPLhome = IPL18.groupby('home_team')['count','hwin', 'htruns','atruns'].sum().reset_index()
IPLhome = IPLhome.rename(columns={'home_team':'team','count':'Ph','htruns':'htrunsh','atruns':'atrunsh'})IPLaway = IPL18.groupby('away_team')['count','awin', 'htruns','atruns'].sum().reset_index()
IPLaway = IPLaway.rename(columns={'away_team':'team','count':'Pa','htruns':'htrunsa','atruns':'atrunsa'})IPL18 = pd.merge(IPLhome, IPLaway, on = ['team'])
这是我们的基本数据,我们需要汇总每支球队的以下数据:胜场数、主队胜场数和客场胜场数、主队和客场比赛场次、主队和客场得分、主队和客场比赛场次:
IPL18['W'] = IPL18['hwin']+IPL18['awin']
IPL18['G'] = IPL18['Ph']+IPL18['Pa']
IPL18['R'] = IPL18['htrunsh']+IPL18['atrunsa']
IPL18['RA'] = IPL18['atrunsh']+IPL18['htrunsa']

作者图片
胜率,即胜率除以游戏次数,毕达哥拉斯期望值,即得分的平方除以得分的平方与得分的平方之和,现在可以很容易地计算出来:
IPL18['wpc'] = IPL18['W']/IPL18['G']
IPL18['pyth'] = IPL18['R']**2/(IPL18['R']**2 + IPL18['RA']**2)
准备好数据后,我们现在准备用散点图来检验因变量和自变量之间的关系。

作者图片
我们可以看到,胜率和毕达哥拉斯预期之间的相关性非常弱。首先,因为我们只有八个团队,我们的点要少得多,所以当你的数据中的观察数据少得多时,很难辨别任何关系。要注意的第二件事是,这些点往往分散在整个图中,它们没有像我们在前两个例子中看到的那样,从左到右整齐地组织成向上倾斜的关系。

作者图片
当我们对这种关系拟合线性回归模型时,这一点得到了进一步证实。这一次,虽然 pyth 上的系数是正的——意味着更高的毕达哥拉斯期望导致更大的胜率,但标准误差也非常大,1.353 的 t 统计意味着 p 值为 0.225——远高于通常的阈值 0.05。反过来,这意味着系数估计值实际上与零相差不大,我们可以自信地说,在 IPL 示例中,毕达哥拉斯期望和获胜百分比之间没有统计学上的显著关系。
毕达哥拉斯期望模型没有为 IPL 数据集产生好的结果可能有几个原因。首先,如上所述,我们拥有的 IPL 数据非常有限:60 场比赛和 8 支球队,而 MLB 有大约 2,300 场比赛和 30 支球队。在大规模分析数据时,随机变化可能会被消除,因此,如果毕达哥拉斯模型在 IPL 示例中是正确的,则随机变化有更大的机会淹没该模型。
另一种解释可能是,板球和棒球等运动之间存在一些根本差异,这使得毕达哥拉斯模型适用于其中一项,而不适用于另一项。例如,在板球比赛中,第二棒球队只需要比对手多得一分就可以获胜,所以如果达到这个里程碑,这一局就结束了。如果击球第二的队是获胜的队,那么得分的差距就会很小。然而,如果首先击球的队可以便宜地得到所有十个三柱门,那么分数的差距可能会非常大。在我们的数据中,当第二棒球队获胜时,平均得分差是 2 分,而当第一棒球队获胜时,平均得分差是 30 分。这种不对称解释了为什么毕达哥拉斯的期望可能不是赢得 IPL 的良好指南。
也许,我们可以在另一篇文章中研究每局的数据,并尝试分析获胜队分别打第一或第二局的比赛。目前,本文到此结束。在接下来的文章中,我们将探讨毕达哥拉斯期望如何在英超联赛(EPL)中被用作预测指标。
参考资料:
python:2022 年要知道的 10 个终极技巧
原文:https://towardsdatascience.com/python-10-ultimate-tricks-to-know-in-2022-4da9f799e2d1
计算机编程语言
在喝咖啡的时候掌握这些省时又有效的技巧

学习 Python 是必须的!🔋
Python 现在很流行,主要是因为它简单易学。
如果我们学习 Python 的方法来完成任务,python-像所有其他编程语言一样更容易。我列出了作为 Python 用户应该知道的 10 个 Python 技巧和诀窍。
我把这篇文章写得很短,这样您就可以边喝咖啡边完成它,掌握最有用、最省时的 Python 技巧。
您可以使用此索引快速导航到您最喜欢的技巧。
***·*** [***Check if all the elements in a list are same***](#ef5d) ***·*** [***Remove Duplicates from a List***](#f24e) ***·*** [***Iterate over multiple lists simultaneously using zip()***](#b72a) ***·*** [***Merge 2 or more dictionaries***](#580d) ***·*** [***Multiple conditions in a single if-statement***](#9533) ***·*** [***Delete all items in the list***](#2626) ***·*** [***Sort Dictionary by Value***](#b2d7) ***·*** [***Access Dictionary Items***](#776a) ***·*** [***Print string output using f-strings***](#a258) ***·*** [***Regular Expressions — regex***](#5b6d)
好了,我们开始吧…🚀
检查列表中的所有元素是否都相同
这里最简单和 Python 化的方法是使用 Python 数据结构— Set 。
在 Python 中,与列表不同,集合不能多次出现相同的元素,也不能存储无序值。
也就是说,如果列表中的所有元素都相同,那么将其转换为集合将得到只有一项的集合。

列表中的所有元素都不同|图片由作者提供
如您所见,列表中的所有元素都是不同的,因此结果集的长度将是 8。因此输出为假。
当输出为真时,列表中的所有元素都相同。看看下面的例子,

列表中的所有元素都相同|图片由作者提供
你可以在这里 更深入的了解 Python 数据结构 。
从列表中删除重复项
列表集属性的另一个经典用例是列表元素的重复数据删除。
正如我前面提到的,Python 中的集合只包含唯一的元素。因此,将列表转换为集合会删除列表中的所有重复项,并且每个元素只保留一个外观。✔️

按作者删除列表|图像中多次出现的元素
如上图所示,我首先将new_list转换成一个集合以消除重复,然后再次将这个集合set(new_list)转换成一个列表以得到一个列表,该列表恰好包含每个new_list元素出现的一次。
你可以在我的一篇趋势文章中读到更多关于这个技巧的内容。
</5-most-powerful-one-liners-you-should-know-in-python-programming-c9d49a89b7f3>
使用 zip()同时遍历多个列表
通常for循环用于迭代单个列表,然而,在不使用迭代计数器的情况下迭代多个列表**zip()**函数用于for循环。
根据定义,python 中的zip()函数用于将来自多个可迭代对象的元素按元素聚合到一个可迭代的元组中。
例如,在两个列表上使用zip()函数,如下例所示,生成一个元组列表

使用 zip() | Image by Author 的元组可迭代
zip()函数的这个属性可以在 for 循环中使用,在 Python 中迭代多个 iterables(列表或字典)。

并行遍历多个列表|按作者排序的图片
很简单!
🚩注意:
zip()当最小的或最短的可重复项用尽时,函数将停止从提供的可重复项生成元素元组。例如,如果 list_1 有 3 个元素,list_2 有 2 个元素,zip()将只返回 2 个元组,结果 for 循环将只执行 2 次迭代。
zip()的这个限制可以通过zip _ longest()函数来克服。
合并 2 个或更多词典
合并两个或更多字典最简单的方法是使用update()方法,如下例所示。

使用 update() | Image by Author 合并两个字典
方法update()接受另一个字典作为参数,并将这个传递的字典的内容合并到当前字典中。
🚩然而,update()方法改变了原始字典,即上面例子中的 dict1 用 dict2 中的元素更新。这不会用两个字典中的合并值创建一个新字典。
因此,另一个简单的解决方案是使用 **kwargs 。
当我们将******应用于字典时,它将字典中的内容扩展为键值对的集合。简而言之,它删除了键值对周围的花括号{ }。✔️
所以,**dict1相当于‘first’: 10, ‘second’: 20, ‘third’: 30
看看下面的例子,学习如何使用 **kwargs 合并两个字典。

使用**kwargs | Image by Author 合并词典
使用 **kwargs 合并字典时,原始字典保持不变。💯
单个 if 语句中的多个条件
有时,您需要在 if 语句中传递多个条件,如果所有或任何条件为真,则运行一段代码。
🚩只要具备一两个条件,就可以在 if-statement 里面提到。但是,当您需要检查 4 个或更多条件时,if 语句看起来又长又混乱。
但是,不用担心!Python 内置的函数——**all()**和**any()**——可以帮你解决问题并节省你的时间。
根据定义,如果满足所有条件,all()将返回真值,如果满足至少一个条件,any()将返回真值。
您需要做的是用 Python 列出您想要检查的所有条件。和使用,
1️⃣ **all(list_of_conditions)**:当你想检查所有的条件是否都为真的时候。即所有条件都处于逻辑“与”状态。
举个例子,

Python 内置函数 all() |图片作者
当所有条件变为真时,执行 if 语句下的代码块。
2️⃣ **any(list_of_conditions)**当你想检查任何一个条件是否为真时。即所有条件都是逻辑或,如下所述。

Python 内置函数 any() |图片作者
由于四个条件中只有三个为真,执行elif下的代码块。
通过这种方式,您可以高效清晰地在 if 语句中传递多个条件,而不会显得冗长。
删除列表中的所有项目
删除列表中所有条目的一种优雅方式是使用切片和del关键字。
根据列表中索引的基础,列表中的所有项目都可以使用冒号(**:**)运算符进行选择,例如list_name[:]。
要删除列表中的所有项目,只需在list_name[:]前添加del关键字,如下所示。

按作者删除列表|图像中的所有项目
这样,列表被清空,而不是完全删除它。可以使用新项目重新填充该列表。
或者,您也可以使用**.clear()**方法,如new_list.clear()将其清空。✔️
⚙️这在系统自动化项目中非常有用,你需要保存机器人位置一段时间,并在使用后删除它们,而不用删除列表本身。这在我上一份工作中非常有用。
按值对字典排序
Dictionary,我们都知道,作为一个键值对,默认情况下可以基于键排序。然而,有时我们需要根据值对字典进行排序。
可以使用sorted函数对字典进行排序,该函数只接受三个参数— iterable、key、reverse,使用key参数可以决定如何对字典进行排序。
下图显示了这两种方法,按键和按值对字典进行排序。

按值排序字典|按作者排序图像
默认情况下,排序是按字母顺序或升序进行的。要对字典进行降序排序,应该在排序函数中设置reverse = True。♻️
访问字典项目
根据您想从下面的字典中访问的内容,有四种方法。
使用。keys(),。值()和。items() 🏆
顾名思义,。按键() 和 。values() 只能用来分别访问键和值。在上面例子中已经创建的字典中,

从 Python 字典中提取键、值和键值对|按作者排序的图片
如上图所示,key-value对作为元组,可以用.items()提取。
关于这个话题你可以在本期 有趣阅读 中了解更多。
使用 f 字符串打印字符串输出
f-strings 是 format() 的简单实现,用于格式化指定的值并将其插入字符串的占位符中。
举个例子,
name = 'Alex'
age = '20'print('{} is {} years old'.format(name,age))Output:
Alex is 20 years old
上例中的 2 个花括号是两个变量的占位符— 姓名和年龄。
虽然它可以满足目的,但是当您处理多个参数和更长的字符串时,.format()仍然会很长。
解决这个问题的一个简单方法是— f 弦。此类 f 字符串在要格式化的字符串开头有一个字母***f*** ,并且在字符串中有多个占位符***{}*** 。
与上面相同的例子用 f 弦简化如下。
name = 'Alex'
age = '20'print(f'{name} is {age} years old')Output:Alex is 20 years old
你可以在这里 阅读更多关于 f 弦 的内容。
⚡️ Python f-strings 提供了一种在 Python 中格式化字符串的更快、更可读、更简洁、更不容易出错的方法。
正则表达式—正则表达式
如果没有正则表达式,这个最有用的技巧列表是不完整的。💯
最常见的数据类型之一是文本或字符串,没有什么比这更强大的了💪🏻作为正则表达式来有效地分析这些数据。
正则表达式(regex)是用于从数据中提取文本模式的字符序列。使用 regex,您可以将输入数据与某些模式匹配,并用最少的代码用其他数据替换它。
为了利用这个工具,您需要在开始使用它之前简单地将包导入为import re。
这里有一个简单的例子。

作者在文本|图片中有多少两位数
在这个例子中,我想找到所有出现在text中的两位数字。因此,我使用了文本模式[0–9][0–9],并在命令re.findall(“[0–9][0–9]”, text)中搜索text中出现的所有这种模式。这就是为什么它只返回一个两位数的数字— 10。
我找到了 这个 很棒的资源来学习和练习 regex。
仅此而已!!
我希望你喝着咖啡读完了这篇文章,发现它令人耳目一新,非常有用。我使用 Python 已经有 4 年了,我发现这些技巧非常省时,而且在一段时间内非常有效
你可以通过 成为中级会员 来探索中级物品。相信我,每月只需 5 美元,你就可以无限制地阅读《大众传媒》和《大众日报》的故事。当然,你的费用的一小部分会作为支持给我。🙂
别忘了注册我的电子邮件列表 来接收我文章的第一份拷贝。
感谢您的阅读!
Python 3.11 有什么值得期待的
探索 Python 3.11 中的新增内容和更新,以及如何尽早获得 3.11 Alpha 版本

介绍
Python 最新版本 3.11 预计将于 2022 年 10 月发布,尽管 alpha 版本(3.11.0a6)已经发布。
今天,我们将讨论最新版本预计将提供的更新和新增功能,并展示如何安装 3.11 Alpha 版本,以便在正式发布之前习惯新功能。
如何安装 Python 3.11 Alpha
在新 Python 版本的最终发布之前,新特性已经实现并测试了大约五个月。因此,alpha 阶段预计将于 2022 年 5 月结束。
一个新的 alpha 版本将(大约)每月发布一次,这样核心开发人员就可以提前访问并在需要时进行调整。在撰写本文时,第六个 alpha 版本已经发布(即3.11.0a6 )—如果您想确保最新的 alpha 版本可用,您可以简单地参考官方文档。
下面你可以找到关于如何在你的本地机器上安装 Python 3.11 alpha 版本的所有相关信息,以便试验和习惯新特性,或者甚至向社区提供一些反馈。
对接安装安装
如果你想通过 Docker 安装 Python 3.11 alpha 版本,那么你首先需要拉[python:3.11-rc-slim](https://hub.docker.com/_/python)镜像
$ docker pull python:3.11-rc-slim
最后运行它
$ docker run -it --rm python:3.11-rc-slim
Ubuntu 安装
$ sudo apt update && sudo apt upgrade -y
$ sudo apt install software-properties-common -y
$ sudo add-apt-repository ppa:deadsnakes/ppa -y
$ sudo add-apt-repository ppa:deadsnakes/nightly -y
$ sudo apt update
$ sudo apt install python3.11
现在应该已经安装了 Python 3.11 alpha,但是您可能还需要安装一些额外的组件(例如,如果您希望能够创建一个虚拟环境):
$ sudo apt install python3.11-dev
$ sudo apt install python3.11-venv
$ sudo apt install python3.11-distutils
$ sudo apt install python3.11-lib2to3
$ sudo apt install python3.11-gdbm
$ sudo apt install python3.11-tk
OSX 安装
对于 Mac 用户来说,.pkg安装可以在官方文档中找到。可用的最新 alpha 版本将在预发布部分的右侧列出。
Windows 安装
为了在你的 Windows 机器上安装 Python Alpha 版本,你可以使用[pyenv-win](https://pyenv-win.github.io/pyenv-win/)。
C:\> pyenv update
C:\> pyenv install 3.11.0a6
安装完成后,您可以继续使用您在上一步中指定的 Python Alpha 版本创建和激活虚拟环境:
C:\> pyenv local 3.11.0a6
C:\> python -m venv myenv
C:\> myenv\Scripts\activate.bat
更好的错误消息
第一个主要更新与标准输出中出现错误时的回溯相关。更准确地说,Python 3.11 预计在报告的回溯中有一个增强的错误位置。
以前的 Python 版本的解释器只会指向导致错误的特定行。由于不清楚是哪个表达式或对象触发了所报告的错误,这一点相当模糊。
例如,假设在我们名为test.py的脚本中,我们通过传递两个参数,即a和b,调用了my_test_function()方法,其中后者是None。现在,如果我们试图在None对象上使用一个属性,我们将以AttributeError结束。
通过增强的错误定位,回溯将指向特定的函数调用以及导致AttributeError的特定表达式,如下所示。
Traceback (most recent call last):
File "test.py", line 15, **in** <module>
print(my_test_function(a, b))
^^^^^^^^^^^^^^^^^^^^^^^
File "test.py", line 3, **in** my_test_function
**return** abs(param1.x - param2.x) + abs(param1.y - param2.y)
^^^^^^^^
**AttributeError**: 'NoneType' object has no attribute 'x'
这在处理深度嵌套对象时也非常有用,比如字典。举个例子,
Traceback (most recent call last):
File "test.py", line 37, **in** <module>
extract_msg('my_key')
^^^^^^^^^^^^^^^^^^^^^
File "test.py", line 18, **in** extract_msg
**return** extract_counts(x)
^^^^^^^^^^^^^^^^
File "test.py", line 24, **in** extract_counts
**return** query_nums(x)
^^^^^^^^^^^^^
File "test.py", line 32, **in** query_nums
**return** response['a']['b']['c']['nums']
~~~~~~~~~~~~~~~^^^^^^^^
**TypeError**: 'NoneType' object **is** **not** subscriptable
此外,在处理复杂的数学表达式时,这也很有帮助。假设我们试图将一个数除以 0。新的误差符号将指向我们试图执行这个无效除法的精确位置。
Traceback (most recent call last):
File "test.py", line 54, **in** <module>
print((x / y / z) * (a / b))
~~~~~~~~^
**ZeroDivisionError**: division by zero
有关此更新的更多详细信息,您可以参考 PEP-657 。
使用 except*进行多重异常处理
Python 3.11 中另一个有趣的语言扩展与多重异常处理相关。这个特性将允许程序同时引发和处理多个异常(甚至是不相关的)。
这个功能是由一个新的标准异常类型ExceptionGroup提供的,它对应于一组正在一起传播的异常。新的ExceptionGroup将使用新的except*语法来处理。*符号表示每个except*子句可以处理多个异常。
例如,为了引发多个异常,您可以创建一个ExceptionGroup的实例:
raise ExceptionGroup('Example Exception Group', (
TypeError('Example TypeError'),
ValueError('Example ValueError'),
KeyError('Example KeyError'),
AttributeError('Example AttributeError')
))
最后使用except*在一个子句中处理来自ExceptionGroup的单个或多个异常,如下所示:
try:
raise ExceptionGroup('Example ExceptionGroup', (
TypeError('Example TypeError'),
ValueError('Example ValueError'),
KeyError('Example KeyError'),
AttributeError('Example AttributeError')
))
except* TypeError:
...
except* ValueError as e:
...
except* (KeyError, AttributeError) as e:
...
有关此次更新背后的更多细节和理由,您可以参考 PEP-654 。
名为 Self 的新打字功能
Python 3.11 还将实现一种更直观的方式来注释返回类实例的方法。
到目前为止,我们可以通过 PEP-484 中描述的TypeVar方法实现相同的行为,但这个新功能肯定更简单,也更直观。
例如,假设我们有一个Person类,它有一个名为update_name的实例方法,该方法返回一个Person类的实例。使用TypeVar符号,类型提示将如下所示。
from typing import TypeVarTPerson = TypeVar('TPerson', bound='Person') class Person:
def update_name(self: TPerson, name: str) -> TPerson:
self.name = name
return self
这种旧的符号非常不直观,而且不必要地冗长。随着 Python 3.11 中的新增加,这被简化为
from typing import Selfclass Person:
def update_name(self, name: str) -> Self:
self.name = name
return self
更干净,更直观,对吧?
有关此次更新的更多详情,您可以参考 PEP-673 。
一个解析 TOML 文件的新模块
Python 3.11 将带来一个名为[tomllib](https://docs.python.org/3.11/library/tomllib.html#module-tomllib)的全新模块,添加它是为了方便 TOML 解析。注意,该模块不支持编写 TOML(至少暂时不支持)。
tomllib可用于从文件中加载 TOML
import tomllib
with open('settings.toml', 'rb') as f:
data = tomllib.load(f)
或字符串:
import tomllib
settings = """
python-version = "3.11.0"
python-implementation = "CPython"
"""
data = tomllib.loads(settings)
有关这一新增内容的更多详情,您可以参考 bpo-40050 。
一些单元测试函数现在已被弃用
除了新的增加和更新,Python 3.11 预计还会有一些函数被弃用。其中一些包括unittest模块中的三个函数。这些是
unittest.findTestCases()unittest.makeSuite()unittest.getTestCaseNames()
并且计划在 Python 3.13 中移除。相反,用户现在应该使用[TestLoader](https://docs.python.org/3.11/library/unittest.html#unittest.TestLoader)模块中的相应方法。
[unittest.TestLoader.loadTestsFromModule()](https://docs.python.org/3.11/library/unittest.html#unittest.TestLoader.loadTestsFromModule)[unittest.TestLoader.loadTestsFromTestCase()](https://docs.python.org/3.11/library/unittest.html#unittest.TestLoader.loadTestsFromTestCase)[unittest.TestLoader.getTestCaseNames()](https://docs.python.org/3.11/library/unittest.html#unittest.TestLoader.getTestCaseNames)
有关这一新增内容的更多详情,您可以参考 bpo-5846 。
CPython 性能优化
与以前的版本相比,3.11 版本中的 CPython 解释器有望得到更好的优化和更快的速度。
有关具体操作的性能提升的更多详细信息,您可以参考下面分享的推文中发布的表格。
你也可以在 GitHub 上找到描述这些操作的原始代码。
最后的想法
在今天的文章中,我们讨论了令人兴奋的新增内容和更新,预计将于 10 月初发布 Python 3.11 版本。
总而言之,在 Python 3.11 中,我们期望通过回溯中更细粒度的错误位置看到更直观的错误消息。此外,我们还探索了称为ExceptionGroup的新标准异常类型,它可用于传播多个异常,以及用于在单个子句中处理多个异常的新except*子句。
此外,我们讨论了名为Self的新类型特性,它提供了一种更直观的方式来注释返回类实例的方法。我们还展示了如何使用新添加的模块tomllib从文件和字符串中解析 TOML。
此外,我们还讨论了在 Python 3.11 中被弃用并在 3.13 中被删除的unittest函数,以及 CPython 解释器中一些重要的性能优化,预计与以前的版本相比会明显更快。
最后,我们还展示了如何安装最新的可用 alpha 版本,以防您想要试验新特性,习惯它们,或者甚至向社区和正在积极开发该版本的核心开发人员提供一些反馈。
要更全面地了解 3.11 版本,你可以阅读官方文档的相应章节。在本文中,我们讨论了一小部分即将到来的更新。更多的反对意见,模块更新和补充可以在上面的链接中找到。
成为会员 阅读介质上的每一个故事。你的会员费直接支持我和你看的其他作家。你也可以在媒体上看到所有的故事。
https://gmyrianthous.medium.com/membership
相关文章您可能也喜欢
Python 3.11 确实比 3.10 快
原文:https://towardsdatascience.com/python-3-11-is-indeed-faster-than-3-10-1247531e771b
与冒泡排序和递归函数并排比较证实了这一点

来自 Pexels 的 KoolShooters 摄影。
Python 3.11 重新发布版出来了。我很想知道里面有什么新东西。
当我打开变更日志时,我看到的第一件事完全吸引了我的注意力。
Python 3.11 比 Python 3.10 快了10-60%。平均而言,我们在标准基准测试套件上测量到 1.25 倍的加速。详见更快的 CPython 。— Python 3.11 Changelog。
这是我渴望的东西。
我在运行 Python 的生产系统上工作没有任何问题。但是 Python 速度不快的事实困扰着我。出于性能原因,我们总是需要使用 Cython 或 Tuplex 来转换关键代码。
随着 Python 3.11 的发布,我相信它会走到尽头。
然而,我想亲自尝试一下。它如何加快我在数据科学领域的日常工作?
我特别感兴趣的是看看它在处理熊猫数据帧方面是否有任何改进。
因此,我决定亲自测试一下。
首先,让我们用一些斐波纳契数来试试。
如何安装 Python 3.11 预发布版本?
Windows 用户可以从发布页面下载安装程序。如果你在 Linux 上像我一样,这里是你需要得到它。
# If you're on Ubuntu 22.04 (Jammy Jellyfish,) you may not need the following line
$ sudo add-apt-repository ppa:deadsnakes/ppa$ sudo apt install Python3.11
在这篇文章中,我不得不经常在两个版本之间切换。因此,我创建了单独的虚拟环境来存放两个 Python 版本。
我们可以用下面的命令来完成:
$ virtualenv env10 --python=3.10
$ virtualenv env11 --python=3.11# To activate v11 you can run,
$ source env11/bin/activate
Python 3.11 相比 Python 3.10 有多快?
我创建了一个小函数来生成一些斐波那契数来找出答案。
def fib(n: int) -> int:
return n if n < 2 else fib(n - 1) + fib(n - 2)
然后我测量了这个函数在两个 Python 版本中的性能。
让我们用 Timeit 运行斐波那契数生成器来确定执行时间。以下命令将重复生成过程十次,并显示最佳执行时间。
# To generate the (n)th Fibonacci number
python -m timeit -n 10 "from fib import fib;fib(n)"
以下是 Python 3.10 和 Python 3.11 的对比结果。

Python 3.11 和 Python 3.10 的并行性能比较—图片由作者提供。
如你所见,Python 3.11 的每一次尝试都优于 Python 3.10。执行时间在 3.11 版本中大致减半。
呜!太不寻常了!
但是我想检查它在熊猫任务中的表现。不幸的是,到目前为止,Numpy 和 Pandas 还没有支持 Python 3.11 的版本。
冒泡排序的性能基准。
由于我无法对熊猫进行基准测试,我在考虑一个涉及数字的替代方案。这将最终对数据科学家的生活产生影响。
所以,我想测试一下冒泡排序。
在下面的代码中,我们试图测量对一百万个数字进行排序所需的时间。
上面的代码在代码内部生成了一百万个随机数。然而,timeit函数被设置为仅测量冒泡排序函数执行的持续时间。
结果是惊人的!

Python 3.11 在冒泡排序挑战上的表现优于 3.10——图片由作者提供。
Python 3.11 只用了 21 秒就完成了排序,而 3.10 的对应版本用了 39 秒。
I/O 操作是否存在性能差异?
一个有趣的性能挑战是我们的程序在磁盘上读写信息的速度。
如果您正在处理一个庞大的文件列表,或者在不使用数据库的情况下存储/检索数据,I/O 性能是至关重要的。
这里有一个简单的测试,将 Python 3.11 和它的 3.10 兄弟进行对比。
我有两个程序。第一个将一百万个文件写入磁盘。
我们使用timeit函数来打印持续时间。通过设置number参数,我们可以多次重复该任务并取平均值。
第二个程序也使用了timeit函数。但是它只涉及读取一百万个文件。
下面是我们运行两个版本得到的输出。

尽管 Python 3.10 似乎比 Python 3.11 有优势,但这并不显著。事实上,多次运行这个实验会得出不同的结论。
最后的想法
3.11 似乎是 Python 历史上一个了不起的版本。
Python 3.11 仍然是预发布版本。因此,你不能指望你所有的第三方软件包都能正常工作。现在连 Numpy 和熊猫都不行了。
然而,关于 3.11 版本的一个有趣的说法是,它比前一个版本快 60%。
在这篇文章中,我做了一些实验来验证这个说法。而且 Python 3.11 确实更快。
您可以复制代码并亲自尝试。
感谢阅读,朋友!在LinkedInTwitter,以及Medium上跟我打招呼。**
还不是中等会员?请使用此链接 成为会员 因为,在没有额外费用的情况下,我为你引荐赚取一小笔佣金。
Python 3.11:您将真正喜欢的新特性
原文:https://towardsdatascience.com/python-3-11-new-features-that-you-will-truly-enjoy-9fd67882fdf
世界各地志愿者的努力值得称赞

图片来源: Unsplash
Python 3.11 于 2022 年 10 月 24 日发布,为我们提供了一些令人着迷的改进。你可以在这里看到所有 pep 的完整文档。在本文中,我将为您带来 5 个很酷的新功能,外加额外的奖励,我想您会喜欢的。
PEP 657 :错误追溯定位器
在 Python 3.11 之前,当出现异常时,错误回溯中唯一的信息是描述错误的行。举个例子,
x, y, z = 1, 2, 0
a, b, c = 3, 4, 5
result = (x / y / z) * (a / b / c)
这段代码会抛出一个错误,因为我们在除X/Y by Z,等于 0。下面是错误消息,它很有意义,但不提供信息,因为我们仍然不知道代码的哪一部分导致了这个问题。

在 Python 3.11 中,我们会看到这个,

在错误定位器~~^~~的帮助下,很明显根本原因是 Y 或 Z 为零。当您的代码变得复杂时,这种带注释的回溯将更加强大。
人教版 673 :自身型
对于类型提示,以前,如果我们需要引用当前类本身,我们必须显式定义一个类型变量,如下所示。

现在在 3.11 中,我们可以使用一个Self 类型,它指的是封装类本身。这将消除定义类型变量的麻烦。

改进的{asyncio} :异步上下文管理器
对于异步编程,代码仍然是一次执行一步,但是系统不会等待这一步完成后再进入下一步。
在 Python 中,这是由{asyncio}模块处理的。我们创建几个异步任务,等待每个任务运行,然后用asyncio.gather()收集它们。比如,让我们去跑跑腿,
在终端中运行它会得到这样的输出,

然而,在用asyncio.gather等待任务之前手动监控任务list 是很麻烦的。3.11 中引入了一个新的特性/类TaskGroup(),
TaskGroup的功能是作为一个上下文管理器,保存您的一组任务,这些任务将在退出时等待所有的任务。另外,语法更简单。
PEP 654 :异常组
异常处理的一个类似的“分组”特性是 3.11 中增加的异常组。我们可以把它想象成几个常规的异常被包装在一个异常中,

正如我们所看到的,当一个错误触发了ExceptionGroup时,它引发了两个子异常,显示在它自己的面板中。
为了处理ExceptionGroup ,Python 3.11 还添加了一个新的关键字except*,

通过except*,可以处理ExceptionGroup 中包含的多个错误。当在{ asyncio }中使用许多异步任务一起运行时,这个特性会更有效。
PEP 678 :自定义异常注释
错误处理的另一个很棒的新特性是带有add_note的异常注释,它允许您添加定制的消息。举个例子,

奖励 PEP 659 :更快的执行速度
值得一提的是,由于更快的 CPython 计划,Python 3.11 预计将比其前辈 T22 快10%-60%。
结论:该不该升级到 Python 3.11?
看情况!就个人而言,我反对升级生产环境,因为你的项目中使用的某些库可能还不兼容 Python 3.11。
如果你想测试一下,我建议你在 Google colab 上测试一下。您可以运行以下命令将 Python 版本升级到 3.11,
!sudo apt-get update -y
!sudo apt-get install python3.11
!sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.7 1
!sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.11 2
在本文中,我们只介绍了最有趣的新特性;请务必查看官方发布文件了解所有改进和变更。
***🎥加入我们的 YouTube 数据科学家和开发者社区 【数据与 Kat 对话】 😄热门视频: 为什么有的人学编码比较快?
想要更多数据科学和编程技巧?使用 我的链接 注册 Medium,获得我所有内容的全部访问权限。
你可能会觉得鼓舞人心的其他博客,
https://levelup.gitconnected.com/3-small-sql-errors-that-cost-a-data-science-job-offer-7ee920084aed </6-sql-tricks-every-data-scientist-should-know-f84be499aea5>
Python 3.14 将比 C++更快
原文:https://towardsdatascience.com/python-3-14-will-be-faster-than-c-a97edd01d65d
令人印象深刻的新 Python 3.11 基准测试
Python 是数据科学(DS)和机器学习(ML)中使用最多的脚本语言之一。根据编程语言的流行程度,Python 是谷歌上搜索次数最多的语言。除了是一个伟大的胶水语言连接各种 DS/ML 解决方案在一起,它有许多库,几乎可以做任何与数据有关的事情。
大约一个月后,我们将迎来全新的 Python 年度版本:版本 3.11。我对这个新版本感到非常兴奋,因为这个版本的主要特点是速度显著提高。
在 LinkedIn 上,我已经看到了一些人测试新版本的帖子,他们的结果令人震惊。但是感受 Python 3.11 到底有多快的最好方法是自己运行测试。
在这篇文章中,我将分享我对 Python 3.11 的逐步分析。所有代码都可以在我的 github 页面上找到。

对编程语言进行基准测试一点也不简单。当你读到 x 比 y 快时,你永远要对这个结果半信半疑。一种算法的实现可能在 x 上更好,而另一种在 y 上更好。对于我们的基准测试来说,这要简单一些,因为我们是在用 Python 来测试 Python,但是我们可能已经从语言中选择了一些元素,这些元素只受到了很小的影响。考虑到这一点,我想介绍我用来进行基准测试的算法:使用蒙特卡罗方法估计圆周率。
这个算法的想法很简单,但是当 a 在大学的数学课上第一次看到它时,我大吃一惊。我们有一个大小为 2r 的正方形,在这个正方形中我们拟合了一个半径为 r 的圆T21。现在我们拿一个在平面上产生数字的随机数发生器: < -r,r >,< -r,r > 。圆上的点与正方形上的点之比(即所有点)是面积比的近似值,我们可以用它来近似圆周率。这在等式中更清楚一点:

图 1:蒙特卡罗方法估算圆周率的推导。两个面积之比是 Pi/4,因此,Pi 大约是 N_circle / N_all 的 4 倍。(图片由作者提供)。
在 Python 中,我从测试脚本中分离出实际的估计,这样我可以重复测试并取平均值。这里没有显示,但是我已经使用 Argparse 对脚本进行了参数化,这是一个从命令行界面(CLI)解析参数的标准库。Python 代码如下所示:
这个脚本已经准备好运行了,但是我们想用它来测试 Python 的各种版本,而不仅仅是当前安装(或激活)的版本。测试多个版本的 Python 最简单的方法是使用 Docker。Python 维护许多 docker 图片。当然是所有受支持的版本,但也包括一些已停产(EOL)的版本,如 2.7 或 3.2。它也有候选版本的图像,例如版本 3.11。要使用 Docker,您需要安装它。在 Linux 和 Mac 上相对容易,在 Windows 上我不太确定,但可能也不难。我建议只安装 docker 命令行界面,桌面对我来说太臃肿了。要在容器化 Python 环境中运行本地脚本,请运行:
docker run -it --rm \
-v $PWD/your_script.py:/your_script.py \
python:3.11-rc-slim \
python /yourscript.py
为了自动化各种版本的测试,我们当然也会使用 Python。这个脚本将简单地启动一个 子流程 来启动一个具有特定 Python 版本的容器,然后收集结果。没什么特别的:
当运行这些测试时,机器与机器之间的绝对数量不同,这取决于处理器(它是 CPU 密集型的)。以下是最近 7 个主要 Python 版本的结果:
The new Python 3.11 took 6.4605 seconds per run.Python 3.5 took 11.3014 seconds.(Python 3.11 is 74.9% faster)
Python 3.6 took 11.4332 seconds.(Python 3.11 is 77.0% faster)
Python 3.7 took 10.7465 seconds.(Python 3.11 is 66.3% faster)
Python 3.8 took 10.6904 seconds.(Python 3.11 is 65.5% faster)
Python 3.9 took 10.9537 seconds.(Python 3.11 is 69.5% faster)
Python 3.10 took 8.8467 seconds.(Python 3.11 is 36.9% faster)
Python 3.11 的基准测试平均耗时 6.46 秒。与之前的版本(3.10)相比,这几乎快了 37%。相当令人印象深刻!3.9 版和 3.10 版之间的差异大致相同,这使得 3.11 版的速度几乎快了 70%。我在图 2 中画出了所有的时间。

图 2:所有测试的 Python 版本的平均循环时间。(图片由作者提供)。
当谈到速度时,我们总是听到有人说:如果你想要速度,为什么不用 c 呢?
c 比 Python 快多了!—那个人
虽然我的 C 很生疏,但我想我还是要试一试。我使用 GNU C++因为它有一个很好的时间测量库(chrono)。找到下面的代码:
众所周知,C++是一种编译语言,因此,我们需要在使用它之前编译源代码。当您安装了典型的build-essentials时,您可以键入:
g++ -o pi_estimate pi_estimate.c
编译完成后,只需运行构建可执行文件。输出应该是这样的:
Pi is approximately 3.14227 and took 0.25728 seconds to calculate.
Pi is approximately 3.14164 and took 0.25558 seconds to calculate.
Pi is approximately 3.1423 and took 0.25740 seconds to calculate.
Pi is approximately 3.14108 and took 0.25737 seconds to calculate.
Pi is approximately 3.14261 and took 0.25664 seconds to calculate.Each loop took on average 0.25685 seconds to calculate.
我们不得不同意那个家伙真的(读:真的)快。只花了 0.257 秒就完成了我们之前用 Python 编程的同样的循环。让我们将它作为一条线添加到之前的图中,如图 3 所示。

图 3: Python 版本与用 C++编译的相同基准的比较。(图片由作者提供)。
现在,在欣赏了前面的图一段时间后,我们清楚地看到 Python 获得了动力。自 3.9 版本以来,Python 的速度提高了约 35%。Python 开发者提到接下来的几个版本将会有显著的速度提升,因此,我们可以假设这个速度将会保持(是的,超级安全的假设)。
现在的问题是,在这种势头不变的情况下,Python 什么时候会超越 C++的时代。为此,我们当然可以使用外推法来预测下一个 Python 版本的循环时间。这些可以在图 4 中看到。

图 4:外推 Python 速度-> Python 3.14 将在速度上超越 C++。太神奇了!(图片由作者提供)。
结果真是惊艳!保持这个速度,Python 3.14 会比 C++更快。确切地说,循环时间将是 -0.232 秒,所以它将在您想要进行计算之前完成。时空连续体中似乎有一个洞,但这些计算是坚如磐石的。因此,我认为我们可能不得不质疑爱因斯坦和他的朋友们的工作。
放弃
虽然 Python 3.5 的这些基准测试..Python 3.11 是有效的,这种推断当然是开玩笑的。XKCD 样式的数字是对这一点的额外提醒;-).
如果您想在各种 Python 版本上运行这些测试或您自己的测试,请在 my Github 页面上下载代码。
如果您有任何意见,请告诉我!在 LinkedIn 上随意连接。
Python 及其在 R 语言中用于数据框操作的类似命令
Python 和 R 中数据争论的备忘单

威尔·斯旺在 Unsplash 上的照片
介绍
因为除了 Python 之外,我现在还在使用 R,所以我偶尔会对这两种语言的语法感到困惑,不得不查找它们。我不得不承认我有一条金鱼的记忆;这就是为什么毫无疑问,我浪费了很多时间去寻找。这就是我写这篇小抄的动机。希望它能为那些希望学习或开始学习这两种语言的人提供一份方便的小抄。
对于 Python 中的数据角力,我会用Pandas。另一方面,tidyr、d plyr或 R base 是用于文章中几乎所有命令的 R 包。
数据集
对于本文,我将使用 Python 和 R 中一个公开可用的名为penguins的数据集,它很容易获得,如下所示:
数据来源参考: Waskom,m .等人,2017。mwaskom/seaborn:v 0 . 8 . 1(2017 年 9 月),芝诺多。可在:https://doi.org/10.5281/zenodo.883859.
以下是数据集的一瞥:

图 1:“企鹅”数据集——作者图片
数据浏览命令
在分析任何数据集之前,我们必须对数据有一个概述,并检查数据帧的一些基本信息,如数据类型、列数、行数等。例如,在这种情况下,penguins数据帧的某些方面检查如下:
图 2:数据概述——按作者
自动化 EDA
在自动化 EDA 库的支持下,EDA 变得前所未有的简单。我确实曾经写过一些 Python 自动化 EDA 库,我发现它们有助于快速生成对数据集的透彻理解。你可以在 Python 库阅读更多你可能错过的 有效 EDA 的详细内容。
我认为有必要在这个备忘单上提供这样的库建议,以方便您在单行代码中检查数据。
计算机编程语言
图 Python 中 EDA 的自动化库——作者
这里是对自动化 EDA 的强大库之一DataPrep的一瞥。
图 4:作者的 EDA-Gif 数据准备
稀有
许多 R 包也提供自动化 EDA,只需一行代码。个人认为 R 的包比 Python 的更多样化。我正在收集这些库,也许我很快就能和你分享这些可爱的包的一些细节。
图 4:R 中 EDA 的自动化库——按作者
图 5: ExPandar 库示例——作者 Gif
数据过滤
切片数据帧
例如,我只想选择数据的一列species来显示。在 R 和 Python 中都有几种方法可以做到。
图 6:切片方法——按作者
用行条件选择数据
大多数情况下,您必须选择特定条件的子集进行分析。选择的子集必须满足一个或多个要求。
在 Python 中,你可以使用带有合理逻辑表达式的loc、query或apply(lambda x:...)方法来产生结果。另一方面,在 R 中,我们可以通过使用filter或which和subset.的 base R 方法来过滤行。下面的代码显示了我如何通过不同的方式获得比斯开岛雌性企鹅的数据。
图 7:按作者过滤行
选择包含特定模式的子集
当我们想要检索包含所需字符串的数据时,情况又如何呢?比如选择名字以Ad开头或者以lie结尾的企鹅的信息。
图 8:过滤字符串——按作者
聚合和分组
巨蟒
聚合和 group by 函数在数据转换阶段非常重要。以下是我经常用来操作数据的最常用命令。如你所见,在下面的例子中,我们有nunique, mean, max, min, sum, median的聚合函数,我想通过**island & sex** 对数据进行分组,以查看不同企鹅种类的特征。
图 9:Python——按作者分组和聚合
R
图 10: R—分组依据和聚集依据—作者
整理
在分组和聚合之后,通常会进入按降序或升序对值进行排序的步骤。在 Python 中,sort_values()是我经常使用的函数。同时,在 R 中,我更喜欢使用dplyr包中的arrange()函数,因为我发现这样最容易记住。
下面的例子展示了我是如何按照企鹅的体型大小降序排列它们的。
计算机编程语言
图 11:Python——按作者排序
稀有
图 12: R—按作者排序
改变栏目
例如,您想在聚合后重命名您的列或者使列名更容易记住,您可以使用函数rename().这个函数在 Pandas 和 Dplyr 中都有,目的相同。下面的代码还展示了一些示例,在这些示例中,您可以删除不需要的列或添加新列。
计算机编程语言
图 13: Python —按作者调整列
稀有
图 14: R—按作者调整列
结论
我相信联系一些你已经很熟悉的东西可以帮助你更有效地学习新知识。我希望这些指针在您将来使用 Python 或 R 数据操作时能够派上用场。祝你好运!
Python 和真相
原文:https://towardsdatascience.com/python-and-the-truth-90cf08380246
以及类型对它的反应

马库斯·温克勒的照片,Unspash。
一段代码的逻辑流程可以想象成一棵树的树枝。条件帮助开发人员处理特定的情况,一般来说,它们以特性和验证的形式增加了新的分支。
布尔值给数据结构带来意义。任何数字或字符串本身都不是正确或错误的,直到我们将其与我们的规则和期望进行比较。
如果我的杯子空了,我会倒更多的咖啡;但是如果我没有足够的咖啡,我需要去商店。如果是星期天,商店关门,零咖啡,我会难过。
人们已经将他们的大部分日常事务和互动建立在一套隐含应用的规则之上,这些规则基于值和状态。例如,如果我的咖啡少于 7 粒,我就不能准备更多的咖啡,如果杯子的状态是Empty,我会把它装满。
然而,Python 为对象赋予了真值,即使没有构建条件表达式。在本帖中,我们将探索真值背后的定义,如何操作它们并避免讨厌的错误,以及如何调整自定义类的真值。
真实与虚假
Python 对象根据它们的值或状态持有一个True或False表示。默认情况下,所有值都是True减去一个特定的集合。
请注意我们是如何将任何类型直接转换成bool的。如果结果是True,否则我们就说这个值是真、或假。访问值的真实性允许我们在条件或布尔运算中直接使用它们,而无需添加任何比较。
现在,这个逻辑是如何用 Python 编程的呢?如果你从来没有这样做过,我建议你读一些标准类型的help,比如str或者int。如果我们分析这两个,我们会发现一些有趣的定义:
请注意,如果int值不等于 0,则它们是True,如果所有str的长度不等于 0,则它们都是True,即使没有实现__bool__。如果我们看一下object的 Python 文档,我们会看到对这种行为的解释:
object.**__bool__**( 自我)被调用来实现真值测试和内置操作
bool();应返回False或True。当这个方法没有被定义时,调用[__len__()](https://docs.python.org/3/reference/datamodel.html#object.__len__),如果它被定义,并且如果它的结果是非零的,对象被认为是真的。如果一个类既没有定义[__len__()](https://docs.python.org/3/reference/datamodel.html#object.__len__)也没有定义[__bool__()](https://docs.python.org/3/reference/datamodel.html#object.__bool__),那么它的所有实例都被认为是真的。
有了这个定义,我们可以快速扩展任何序列或迭代器对象的答案,甚至将其应用于我们的自定义类。
如果你是认真的,请不要勾选
我们在上面的例子中忽略的一个独特的对象是None。通过添加类型提示,在阅读代码和对缺失值进行干净的异常管理时,使用Optional s 是一个巨大的帮助。
然而,如果我们在检查这种特定情况时没有注意,我们可能会隐藏一些难以调试的问题。拥有一个缺失值None和拥有一个真值为False的值是不同的。
由于None的bool值为False,第一个场景将捕捉到缺失的count和count == 0。而第二个将只为None显式执行。
这里没有好坏之分,但是你必须考虑什么对你的商业逻辑有意义。
玩弄真相
了解 Python 如何将True或False赋给它的类型允许我们在我们的类中复制行为,这可以方便地使代码更简单和更有表现力。
有了一个相对简单的定义,我们可以重用一个整数的真值,我们可以顺利地识别空咖啡杯。
布尔运算符
此外,理解值的真实度 使用布尔运算符打开了一扇令人兴奋的门。它们不仅可以应用于if和while条件,还可以基于现有对象的真实性定义新的变量。我们将根据这份文档来完成:
注意我们如何应用or和and操作符来获得有趣的结果,特别是在表达式求值方面。根据第一个论点的真实性,第二个论点可能会也可能不会被评估。这被称为短路,它允许我们仅在需要或需要满足特定条件时延迟特定的执行。
假设我们正在开发一个银行应用程序。查询账户资金,需要告知密码;否则,可能会发生异常。
通过短路条件,只有在正确告知密码的情况下,get_funds()才会被(安全地)执行。否则,函数不会被求值,因为结果布尔表达式已经是False。
结论
Python 的灵活性有时是有代价的。例如,不知道特定行为的开发人员可能会用小的实现细节来掩盖实际的程序逻辑。解药?好奇心和测试。
在这篇文章中,我们看到了 Python 如何将对象转换成bool,这在条件和布尔运算中是一个强大的工具。我们还将这些定义应用于一个定制类,同时学习关注逻辑的细节。
简而言之,这是 Python 的禅仍然正确的又一个例子:“显式比隐式好”。认真检查None!
让脚本找出优于您的 ML 模型
原文:https://towardsdatascience.com/python-automl-sklearn-fd85d3b3c5e
这个自动 ML Python 库让每个人都成为数据科学向导

将数据科学交给 AutoML —照片由 Aidan Roof 从 Pexels 拍摄
自动化是一个古老的概念,它改变了一切。我们发明的所有机器和技术都是一种方式,或者说是一种自动化形式。
数据科学领域是我们自动化思维的一种方式。我们不是手动给物体贴标签,而是训练计算机大规模地这样做。
但是教授计算机的过程在很长一段时间里是手工操作的;直到最近,科学家们还在考虑 AutoML。
构建预测系统是一个多阶段的过程。它可能包括
- 收集数据(或整合数据源;)
- 准备数据集;
- 训练机器学习模型和调整超参数;
- 在生产系统上部署模型,以及;
- 监控已部署模型的性能。
Auto ML 是一个术语,指的是在这些阶段中消除手工操作的技术。
很多数据科学家用 Python,大部分用 scikit-learn。很难找到不是这样的人。
Scikit-learn 提供了优秀的高级 API 来构建 ML 模型。他们删除了大量样板代码,否则这些代码会让科学家们抓狂。
这篇文章是关于一个将 scikit-learn 推进了一步的工具——auto-sk learn。你可以从高效稳健的自动化机器学习,福雷尔等人,神经信息处理系统进展 28 (NIPS 2015)中了解更多。
为什么我们需要在 sklearn 之上安装 auto-sklearn?
除了数据清理和准备,数据科学家生活中最具挑战性的部分是找到正确的模型。
当 AutoML 还不流行的时候,我和我的团队有过几周这样做的经历。虽然如果你是初学者,我仍然推荐这种方法,但对于实际项目来说,它可能不是最佳的。
如果不充分利用你的脑力,你会陷入困境。反复改变超参数并等待结果并不能更好地利用你的智力技能。
这就是 auto-sklearn 解决的问题。你可以让计算机改变超参数,评估它们的输出,然后选择最佳模型。另外,你可以给培训设定一个时间限制。除了这些好处,你可以在桌面分布式系统上运行并行的贝叶斯优化。
您可以从 PyPI 存储库中安装 auto-sklearn。下面的代码将安装这个库,并通过检查它的版本来确认。
pip install auto-sklearn
python -c "import autosklearn;print(autosklearn.__version__)"# 0.14.6
其他安装方法请参考 auto-sklearn 文档。
为我们的数据集构建(自动)ML 模型。
让我们开始使用自动学习建立一个实际的模型。
为了便于说明,我使用了 Kaggle 上的葡萄酒质量数据集。
该数据集包括 11 个预测变量和一个响应变量。响应变量葡萄酒质量有六个不同的等级。我们的任务是建立一个机器学习模型,在给定其他变量的情况下预测质量类别。
以下命令将加载数据集,并为模型定型做准备。它删除 id 列并分隔响应变量。
import pandas as pddf = pd.read_csv('./WineQT.csv')df.drop('Id', axis=1, inplace=True)y = df.quality.copy()
X = df.drop('quality', axis=1)
葡萄酒质量数据集的特征变量— CC0:公共领域
让我们也将数据集分成几组进行训练和测试。
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=1)
到目前为止,我们还没有做任何与通常使用 scikit-learn 不同的事情。但是这里有一个 auto-sklearn API,它可以训练多个模型,并找到最适合我们的模型。
model = AutoSklearnClassifier(
time_left_for_this_task=5*60,
per_run_time_limit=30
)
在上面的代码中,我们给出了两个参数值。这些不是模型超参数。相反,我们限制了训练过程的时间。
在本例中,我们将 API 配置为每次运行 30 秒(对于一组特定的超参数),整个过程不超过 5 分钟。
出于说明目的,这些数字是任意的。在决定这些值之前,您应该考虑真实情况下的数据集大小和计算资源。
让我们也根据我们的测试集来评估我们的结果模型。
y_hat = model.predict(X_test)
acc = accuracy_score(y_test, y_hat)
print(f"Model Accuracy:{acc}")Model Accuracy:0.6693121693121693
这是一个相当不错的葡萄酒质量分类的准确性。
如果我们自己找出最好的模型会是什么样的?
手工建立分类器的方法及其问题。
如果你想用 scikit-learn 来做这件事,你需要像下面这样的东西。这里的问题是你不知道max_features和n_estimators.的最佳值
RandomForestClassifier(max_features=15, n_estimators=512)
为了找到比其他组更好的一组,您可能需要为这些超参数尝试几个不同的数字。
但这只是在我们确信模型将是随机森林模型的时候。如果没有,你可能还得尝试其他算法。这给我们的流程增加了额外的复杂性。
另一方面,auto-sklearn 在很短的时间间隔内用许多超参数尝试了大约 15 个不同的分类器。
我们还可以看到 auto-sklearn 尝试的不同模型及其在此过程中的排名。下面的代码将打印所有跑步的排行榜。
model.leaderboard()
Auto-sklearn 排行榜告诉我们在我们的数据集上试用的不同算法及其统计数据。
如果我们需要更多地了解这些不同的模型,我们可以调用 show_models 方法来获取细节。
用 AutoML 在大数据集上训练 ML 模型
当数据集很大时,自动 ML 技术将消耗更多的资源,并且运行更长的时间。
但是凭借我们的专业知识和偏好,我们可以大大缩短这个时间。以下代码将分类器搜索空间限制为高斯朴素贝叶斯和 k 个最近邻。
我们也可以关闭特征预处理。如果我们的任何特征是分类的,auto-sklearn 会自动进行一次性编码。在大型数据集上,这可能需要相当长的时间。
automl = autosklearn.classification.AutoSklearnClassifier(
include = {
'classifier': ["gaussian_nb", "k_nearest_neighbors"],
'feature_preprocessor': ["no_preprocessing"]
},
exclude=**None**
)
include 和 exclude 配置使我们能够更好地控制 AutoML 搜索空间。然而,对于小数据集,我不会改变它们。这确保了我得到的模型是最好的,超出了我的启发式假设。
最后的想法
在我们结束之前,我有一些想法要分享。
auto-sklearn 等自动 ML 库解决了每个数据科学家面临的噩梦般的问题。如果没有这样的工具,熟练的专业人员的宝贵时间将被浪费。
但是不能保证 AutoML 模型永远是最好的。数据科学家可以通过领域知识和专业技能找到更好的模型。
但是 AutoML 会是未来吗?
我想是的。
当汽车上的自动变速器上市时,许多人批评它有一些小缺点。但是今天,自动挡的车比手动挡的车多。
就像汽车类比一样,Auto ML 在这一点上可能并不完美。但很快就能超越专业科学家。
感谢阅读,在 LinkedIn , Twitter , Medium 上向我问好。
还不是中等会员?请使用此链接 成为会员 因为,不需要你额外付费,我为你引荐赚取一小笔佣金。
基于 Python 的数据,即(无需安装)
原文:https://towardsdatascience.com/python-based-data-viz-with-no-installation-required-aaf2358c881
为非计算用户编写和交付交互式 Python 应用

多萝西娅·奥德尼在 Unsplash 上的照片
无论你在什么类型的组织工作,从事数据科学的人都有一系列的计算技能和背景。精通计算的研究人员面临的最大挑战之一是如何最有效地将有用的工具交付给他们不太擅长计算的同事。虽然 python 是一个非常强大的数据分析和可视化工具,但对于非计算研究人员来说,在自己的计算机上安装和运行基于 Python 的应用程序并不容易。
然而,最近在 WebAssembly 开源项目中的创新使得在 web 浏览器中直接运行 Python 代码成为可能。现在,你可以建立一个静态网页,直接在用户的机器上执行所有需要的计算,而不是让 Python 服务器一直运行。在本教程中,我将带你通过几个简单的步骤来设置一个基于 Python 的 web 应用程序(使用 Streamlit ),由用户启动,而不需要安装任何东西。
在数据科学中权衡收益和成本
作为一名生物信息学家,我一直在寻找新的工具来帮助我的合作者进行真正有用的分析。但是,决定采用一种新工具并不纯粹基于它能提供什么——我还必须权衡学习它的难度。很长一段时间以来,web 开发的世界让我觉得遥不可及,纯粹是因为学习 JavaScript 和 HTML/CSS 的明显困难。
虽然 Python(和 R)确实都可以用来建立交互式 web 应用程序,但这些库( Flask 、 Dash 、 Streamlit 和 Shiny )旨在运行在主动服务器上,这些服务器在后端执行计算,然后将结果发送到用户浏览器的前端。以这种方式运行 web 应用程序本来就困难得多,这既是因为保持机器持续运行的成本,也是因为提供受保护的网络连接的复杂性。有一些很棒的托管解决方案,用于共享基于 R 和 Python 的应用程序,但它足够复杂,我不太倾向于建立自己的版本。
它是如何成为可能的
彻底改变了软件开发前景的变革性工具是 WebAssembly ,它使编译 Python 代码成为可能,从而可以直接在 web 浏览器中运行。编写在 web 浏览器中运行的代码是非常棒的,因为你不再需要要求用户安装任何依赖项——他们几乎肯定已经有了 web 浏览器。
在 JS 中实现了 Python 的项目叫做 Pyodide 。使用这个框架, Yuichiro Tachibana 制作了一个名为 stlite 的 Python GUI 库 Streamlit 的端口。使用 stlite 可以编写完全在 web 浏览器中运行的 Python 代码,这意味着用户不需要安装任何东西就可以运行它。
如果我不是 Streamlit 的超级粉丝,我可能不会对此感到如此兴奋。这个 Python 库使得构建一个简单的 GUI 变得非常容易,它可以驱动您喜欢的任何类型的数据可视化。与多个强大的绘图库( Pyplot 、 Altair 、 Vega Lite 、 Plotly 、 Bokeh 、 pydeck 和 graphviz )进行了本机集成,以及对用户输入、页面布局和媒体显示的灵活控制。
最重要的是,大脑空间开销很低——您不需要学习太多就可以启动并运行。如果您已经在使用 Python,并且希望快速原型化和部署一个交互式 web 应用程序,那么绝对值得您花时间探索 Streamlit。
现在,这些 Streamlit 应用程序可以提供给用户,并直接在浏览器中运行。
预期用途和限制
你可以用 Python 和 stlite 制作一个有效的 GUI,只要你记得它是直接在用户的浏览器中运行的。
- 加载需要一分钟——你的用户会从耐心中受益;
- 需要大量内存、CPU 或 I/O 的操作很可能会导致问题——尽量让计算变得轻量级;
- 您需要读入的任何文件也必须对用户的浏览器可用,或者通过(1)自己托管它们,(2)在公共 URL 访问它们,或者(3)当用户将它们“上传”到浏览器时;
- 访问控制很重要——任何可以访问网页的人都可以运行应用程序和读取其源代码。
入门指南
本指南将带您了解:
- 在 GitHub 上复制模板库
- 添加您的 Streamlit 应用程序
- 本地测试
- 使用 GitHub 页面在 web 上公开部署
要使用本指南,您应该熟悉(1)在 GitHub 上操作软件仓库和(2)在本地运行 Streamlit。
为了了解如何将 GitHub 存储库转化为交互式数据可视化应用程序,您可以看到这个模板存储库(Fred hutch/ST lite-template)已被托管在https://fredhutch.github.io/stlite-template/。

作者图片
模板中的示例应用程序读入一组 RNAseq 计数(使用来自 BRITE-REU 编程研讨会的数据),通过 CLR 或比例丰度对数据进行归一化,对行和列执行关联聚类,并向用户显示交互式热图。
1.派生模板存储库
导航到Fred hutch/stlite-template存储库,并将其分支到您自己的帐户或组织中。请务必更改名称和描述,因为您将制作全新的东西。

作者图片
2.添加您的 Streamlit 应用程序
运行应用程序所需的所有代码都应该放在存储库中。根据您的应用程序的功能,您可能还需要采取一些额外的步骤:
- 将所有 Python/streamlit 代码放在文件
app.py中; - 在
index.html的第 38 行添加 app 导入的任何库(如requirements: ["click", "scipy", "plotly"],); - 如果你的 streamlit 应用中有任何
@st.cachedecorator,添加参数show_spinner=False(解释 stlite 中的一个已知 bug)
2a。读入数据
我遇到的这个过程中最棘手的部分是如何读入外部数据源(例如 CSV 文件)。幸运的是,解决方案并没有变得太复杂。
核心问题是 Pyodide 目前不支持requests库。这导致在使用像pd.read_csv这样有用的函数时出错,这些函数在幕后使用requests。相反,从浏览器内部读入远程文件的最佳方式(记住,所有文件对您的用户来说都是远程的,甚至是您在存储库中设置的任何附加文件)是使用pyodide.http库。
然而,在 Python 内部进行本地测试时,pyodide库是不可用的,就像requests库在浏览器内部运行时是不可用的一样。
为了解决这个问题,我制作了一个小的助手函数,它使用适合执行上下文的库从 URL 读取 CSV:
import streamlit as st
import importlib
import requests
from io import StringIO
if importlib.util.find_spec("pyodide") is not None:
from pyodide.http import open_url@st.cache(show_spinner=False)
def read_url(url:str, **kwargs):
"""Read the CSV content from a URL""" # If pyodide is available
if importlib.util.find_spec("pyodide") is not None:
url_contents = open_url(url)
else:
r = requests.get(url)
url_contents = StringIO(r.text) return pd.read_csv(
url_contents,
**kwargs
)
您可以根据需要随意复制或修改这些代码,以读入您的应用程序可能需要的数据文件。
2b。暂存数据
最有趣和最有用的应用程序以某种方式处理和转换数据,以便显示和与用户交互。在考虑如何将数据导入应用时,有三个主要选项:
- 使用公共 URL 上的可用数据(如示例库中的所示);
- 要求用户使用 streamlit 文件上传实用程序直接上传文件;
- 自己托管数据,将数据上传到应用程序可以访问的位置。
虽然自己托管数据(选项 3)可能看起来令人生畏,但实际上使用下面概述的使用 GitHub Pages 发布您的应用程序的步骤是非常容易的。一旦您将应用程序发布到特定的 URL,您添加到存储库中的任何附加文件也将在该 URL 可用,并且可以由应用程序读取。因此,如果你想添加一些应用程序可以读取的数据文件,请按照本教程从头到尾了解它将在哪个 URL 可用,然后更新你的应用程序以从该 URL 读取。
3.本地测试
在部署您的应用程序之前,在本地进行测试是非常有帮助的。首先,您可以使用 Python 的本地副本(安装了 streamlit)启动该应用程序,包括:
streamlit run app.py
调试完您发现的任何错误后,下一步是启动一个本地 web 服务器,直接在浏览器中测试您的代码:
python3 -m http.server
在浏览器中检查错误时,最好打开 JavaScript 控制台(在 Chrome 中显示):

作者图片
4.使用 GitHub 页面在 web 上公开部署
虽然部署网站有很多方法,但我发现 GitHub Pages 是将代码库转变为公共网页的最简单的方法。为企业级帐户付费的用户也可以创建私人网站,但任何人都可以创建面向公众的页面。
需要明确的是,即使你的库是私有的,发布的网页仍然是公开的——你已经被警告过了。
要部署到 web:
- 导航到您的存储库的网页(
www.github.com/<ORG>/<REPO>); - 点击“设置”;
- 点击“页面”(在“代码和自动化”下);
- 在“分支机构”下,选择“主分支机构”(或您想要设置的回购分支机构);
- 就是这样!

作者图片
您将很快能够在基于您的组织和存储库名称的 URL 上找到您的网页(尽管有自定义域名的选项)。如https://<ORG>.github.io/<REPO>/。
例如,模板库[https://github.com/FredHutch/stlite-template](https://github.com/FredHutch/stlite-template)在[https://fredhutch.github.io/stlite-template/](https://fredhutch.github.io/stlite-template/)作为网页托管。
回到上传静态数据文件的解释,任何添加到模板库中的文件都可以被具有该 URL 的应用读取,例如[https://fredhutch.github.io/stlite-template/data_file.csv](https://fredhutch.github.io/stlite-template/data_file.csv.) 。
包扎
既然你已经成功地构建了你的第一个无服务器的 web 应用程序,也许有必要思考一下 GUI 在你的工作中可能扮演的角色,如果有的话。虽然将一个工具直接放在你的用户/客户/合作者手中是非常吸引人的,但是很明显,并不是所有的工具都应该放在他们手中。
数据科学中的许多任务需要大量的计算资源,这要么需要在笔记本电脑上运行太长时间,要么需要通过强大的授权和身份验证控制来访问远程资源。其他任务由复杂的参数输入驱动,这些输入可能不容易向非专业用户解释,或者可能返回难以解释的结果。
网络应用的理想 GUI 将提供响应和信息可视化,允许用户以静态图像不可能的方式探索和发现。值得探索鼠标悬停数据、交互式小部件和任何其他可以暴露数据的丰富特性和复杂性的方式的机会。如果你真的成功,有人会从你的数据中发现一些你自己都没有意识到的东西。
你需要知道的 Python 基础知识:数据结构
原文:https://towardsdatascience.com/python-basics-you-need-to-know-always-data-structures-2c0f290e4747
数据结构是一种存储、组织和处理数据的格式,它允许您高效地对数据执行操作

保罗·花冈在 Unsplash 上的照片
例如,存储人们的电子邮件地址,记录全天每小时的温度等。这些结构中的数据可以被称为元素。
有许多数据结构,几乎所有的数据结构都可以在任何编程语言中实现——要么它们内置于语言中,要么它们可以被创建或导入。
在本文中,我们将使用 Python 来探索数据结构。
因此,有 4 种数据结构被直接内置到 python 中— 列表、字典、集合和元组— ,我们将在本文中讨论它们。在我们进入这些结构之前,有一件事需要快速理解,那就是可变性的概念——改变的能力。一些数据结构是可变的,即该结构可以被修改——在创建数据结构之后,可以添加、移除和更新元素。其他数据结构是不可变的,即,在它们被创建之后,不能对它们进行修改。
目录
列表将数据存储为方括号内逗号分隔的元素。它是有序的、连续的、异构的、索引的、可变的、非唯一的数据结构。
odd = [1, 3, 5, 7, 9]
有序:当您插入元素时,列表会保持它们的顺序。
顺序:一个列表可以被迭代。
异构:存储在一个列表中的元素可以是不同的数据类型,所以你可以拥有一个包含一个整数、一个字符串、一个浮点数的列表。由于这个特性,数据不那么紧密地打包在一起(内存方面),因此列表会占用更多的空间。
索引:列表中的每个元素都有一个与之关联的索引,基本上就像一个地址。索引用于访问元素。
正索引从列表中第一个元素的 0 开始,一直到最后一个元素的 n-1,其中 n 是元素的数量。这就是所谓的向前遍历。
负索引从列表中最后一个元素的-1 开始,一直到第一个元素的-n,其中 n 是元素的数量。这就是所谓的反向遍历。

按作者。
可变:在 python 中,列表在后台被实现为动态数组,因此它们是可变的。您可以添加、删除和修改列表中的元素,python 会相应地自动释放或分配内存。一些操作,比如在列表的开头添加或删除一个元素,开销很大,因为列表中每隔一个元素的索引都需要改变。
非唯一:同一个元素可以在一个列表中存储两次,即列表可以包含重复的值。
我们来编码吧!
创建列表—您可以使用方括号[]或通过调用 list()方法来创建列表。
ids = [234, 534, 239, 392]letters = list(‘shubhangi’)
#creating a list of a string breaks the string into individual #characters
>>> ['s', 'h', 'u', 'b', 'h', 'a', 'n', 'g', 'i']names = list([‘shubhangi’, ‘nita’, ‘pooja’, ‘jordan’])
>>> ['shubhangi', 'nita', 'pooja', 'jordan']mixed = [234, ‘shubhangi’, ‘nita’, 23.9]
将元素添加到列表末尾
names.append(‘shagun’)
>>>['shubhangi', 'nita', 'jordan', 'shagun']names.append(‘nita’)
>>>['shubhangi', 'nita', 'pooja', 'jordan', 'shagun', 'nita']
向列表的特定索引添加元素
names.insert(2, ‘nidhi’)
>>>[‘shubhangi’, ‘nita’, ‘nidhi’, ‘pooja’, ‘jordan’, ‘shagun’, ‘nita’]
计算相同值出现的次数
names.count(‘nita’)
>>>2
从列表中删除具有特定值的元素。如果该值的元素出现多次,则只删除第一次出现的元素。
names.remove(‘pooja’)
>>>[‘shubhangi’, ‘nita’, ‘nidhi’, ‘jordan’, ‘shagun’, ‘nita’]names.remove(‘nita’)
>>>[‘shubhangi’, ‘nidhi’, ‘jordan’, ‘shagun’, ‘nita’]
移除特定索引处的元素
names.pop(3)
>>>[‘shubhangi’, ‘nidhi’, ‘jordan’, ‘nita’]
从列表中删除最后一个元素
names.pop()
>>>[‘shubhangi’, ‘nidhi’, ‘jordan’]
获取具有特定值的元素的索引
names.index(‘jordan’)
>>>2
颠倒列表
names.reverse()
>>>[‘jordan’, ‘nidhi’, ‘shubhangi’]
列表理解:这是我最喜欢的列表部分。你可以以理解的形式创建、添加、修改列表,或者对列表做任何事情,这是一种更加优雅和全面的将多行代码转换成一行代码的方式!
假设您想要一个从 1 到 40 的数字平方列表。一个乏味的方法是
squares = []
for x in range (1, 41):
squares.append(x**2)>>>[1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361, 400, 441, 484, 529, 576, 625, 676, 729, 784, 841, 900, 961, 1024, 1089, 1156, 1225, 1296, 1369, 1444, 1521, 1600]
有了列表理解,你可以简单地写
squares = [x**2 for x in range (1,41)]
>>>[1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361, 400, 441, 484, 529, 576, 625, 676, 729, 784, 841, 900, 961, 1024, 1089, 1156, 1225, 1296, 1369, 1444, 1521, 1600]
你也可以在列表理解中添加一个 if 条件!让我们从“正方形”列表中创建一个由可被 2 整除的数字组成的列表
nums = [num for num in squares if num%2==0]
>>>[4, 16, 36, 64, 100, 144, 196, 256, 324, 400, 484, 576, 676, 784, 900, 1024, 1156, 1296, 1444, 1600]
我们再加一个 else 吧!
result = [num if num%2==0 else ‘no’ for num in squares]
>>>[‘no’, 4, ‘no’, 16, ‘no’, 36, ‘no’, 64, ‘no’, 100, ‘no’, 144, ‘no’, 196, ‘no’, 256, ‘no’, 324, ‘no’, 400, ‘no’, 484, ‘no’, 576, ‘no’, 676, ‘no’, 784, ‘no’, 900, ‘no’, 1024, ‘no’, 1156, ‘no’, 1296, ‘no’, 1444, ‘no’, 1600]
注意:当只有一个 if 条件时,则 if 在 for 之后。当存在 if-else 条件时,则 if 和 else 先于 for 。
切片:这是列表的另一个很酷的特性!这是一种使用索引从列表中提取子集的方法。基本语法是
names = [‘shubhangi’, ‘nita’, ‘nidhi’, ‘pooja’, ‘jordan’, ‘shagun’]
获取整个列表
names[::]
>>>[‘shubhangi’, ‘nita’, ‘nidhi’, ‘pooja’, ‘jordan’, ‘shagun’]
获取两个索引之间的元素
names[1:5]
>>>[‘nita’, ‘nidhi’, ‘pooja’, ‘jordan’]
#(elements with index 1, 2, 3, 4)
从列表中提取替代元素
names[::2]
>>>[‘shubhangi’, ‘nidhi’, ‘jordan’]
#(starts at index 0, skips the next index, returns the next element, # and so on)
获取两个索引之间的替代元素
names[1:6:2]
>>>[‘nita’, ‘pooja’, ‘shagun’]
颠倒列表
names[::-1]
>>>[‘shagun’, ‘jordan’, ‘pooja’, ‘nidhi’, ‘nita’, ‘shubhangi’]
元组
元组将数据存储为括号内逗号分隔的元素。它是不可变的、异构的、有序的、顺序的和索引的。
odd = (1, 3, 5, 7,9)
不可变:元组本身是不可变的,这意味着不能动态地添加、删除或修改元素。一旦元组被初始化,它就不能被改变。这就是为什么它们比列表更有内存效率,因为内存在创建时就固定了。这也使得查找活动更快。
异构:像列表一样,元组可以保存不同数据类型的元素,包括不同的数据结构。例如,一个元组可以有一个字符串,后跟一个浮点,再跟一个列表。因此,尽管元组是不可变的,但它可以保存可变对象。由于这个原因,数据不那么紧密地打包在一起(就像在列表中一样)。
有序:和列表一样,元组保留了元素的顺序。顺序将始终与您创建的顺序相同。
顺序:元组和列表一样是可迭代的。
索引:元组的索引方式与列表完全相同,因此元素的访问方式也相同。
所以基本上,元组是一个不可变的列表。
我们来编码吧!
创建元组
data = (1, 3, ‘strawberry’, [9, 3, 4])even_nums = 2, 4, 6, 8
>>> (2, 4, 6, 8)type(even_nums)
>>> <class ‘tuple’>nums = even_nums, (1, 3, 5, 7) # nested tuples
>>> ((2, 4, 6, 8), (1, 3, 5, 7))letters = tuple([‘a’, ‘b’, ‘c’])
>>> (‘a’, ‘b’, ‘c’)# initialise a tuple with only one element. if done without the
# comma it won’t create a tuple
single = 1,
>>> (1,)
single_test = (1)
type(single_test)
>>> <class 'int'>
解包元组(将其值存储到单独的变量中)
a, b, c, d = even_nums
print(a, b, c, d)
>>> 2 4 6 8
除了不插入、删除或更新元素之外,其他方法与列表相同。
词典
字典以键值对的形式存储数据。它是一种顺序的、异构的、无序的、索引的、可变的数据结构。
numbers = {'odd':[1, 3, 5, 7, 9], 'even':[2, 4, 6, 8]}
很像一个电话地址簿,对于一个人的名字你有一个电话号码。名字是键,数字是值。一个键只能有一个值。值本身可以是另一种可以保存多个元素的数据结构,如列表。字典也称为映射、哈希映射、关联数组和查找表。
Sequential: 像列表和元组一样,字典也可以被迭代。
异构:和列表一样,字典可以保存不同数据类型的键和值。
无序: python 3.6.2 起在已建字典中是有序的;它们保留了插入键的顺序。不过在此之前,他们不会保留订单。
Indexed: 它的要点很简单,你可以根据元素的键来获取元素。这些键是字典的索引,它们可以是任何可哈希类型——可哈希对象的值在其生命周期内不会改变,因此任何不可变的对象都是可哈希的,比如数字和字符串。
可变:虽然字典中的键是不可变的,但是字典本身是可变的。这意味着您可以在创建后添加、删除和更新键值对。因为字典是可变的,所以它在创建时没有固定的大小——它是动态的。
字典在执行操作时也非常有效——对于查找、插入、更新和删除,它们的时间复杂度为 O(1)。
虽然这本字典可以直接在 Python 中获得,但它有不同特性的版本,可以从集合库和类型库中导入。其中一些版本如下:
- 有序字典:它保留了键插入的顺序,并且有一些基本字典没有的方法(python 3.6+普通字典也保留了插入顺序)
- 默认字典:如果您试图获取一个尚不存在的键值,它将返回一个默认值/响应
- 链式映射:这将多个字典组合成一个映射;在对映射执行查找时,会搜索映射中的每个字典,直到找到键
- Counter:它对可散列对象进行计数,也就是说,它对一个元素出现的次数进行计数,并将其存储为一个值,将相应的元素作为键
- 映射代理类型:这是一个包装器,它基于原始的可变字典创建一个只读的,即不可变的代理字典
我们来编码吧!
创建词典
d = {‘a’:’apple’, ‘b’:’banana’} # using {}d = dict(a=’apple’, b=’banana’) # using dict() methodd = dict([(‘a’,’apple’), (‘b’,’banana’)]) # using dict method on a list of tuplesids = [234, 534, 239, 392]
names = ['shubhangi', 'nita', 'pooja', 'jordan']
students = dict(zip(ids, names))
>>>{234: 'shubhangi', 534: 'nita', 239: 'pooja', 392: 'jordan'}
# using the zip method on two separate lists
获取字典的关键字
students.keys()
>>>dict_keys([234, 534, 239, 392])list(students) # returns keys as a list
>>>[234, 534, 239, 392]sorted(students) # returns keys in a sorted order
>>>[234, 239, 392, 534]
获取字典的值
students.values()
>>>dict_values(['shubhangi', 'nita', 'pooja', 'jordan'])
获取特定键的值
# time efficient but can raise KeyError if key doesn't exist
students.get(239)
>>>'pooja'# return default value if key is not present
students.get(123, 'not present')
>>>'not present'students[239]
>>>'pooja'
删除特定的键值对
students.pop(239)
>>>{234: 'shubhangi', 534: 'nita', 392: 'jordan'}del students[239]
>>>{234: 'shubhangi', 534: 'nita', 392: 'jordan'}
检查字典中是否存在特定的关键字
234 in students
>>>True123 not in students
>>>True
检查字典是否为空
if students:
...print("not empty")
else:
...print("emptyy")
>>>not empty
迭代键值对
for k, v in students.items():
...print(k, v)
>>>234 shubhangi
534 nita
392 jordan
将键值对添加到字典中
students.update({123:'nidhi'})
>>> {234: 'shubhangi', 534: 'nita', 392: 'jordan', 123: 'nidhi'}students[901] = 'radhika'
>>>{234: 'shubhangi', 534: 'nita', 392: 'jordan', 123: 'nidhi', 901: 'radhika'}
字典理解:就像列表一样,字典也可以使用理解来制作和修改!
nums = { k: k**2 for k in (1, 2, 3)}
>>> {1: 1, 2: 4, 3: 9}nums.update((k, k**3) for k,v in nums.items())
>>>{1: 1, 2: 8, 3: 27}nums.update((k, v**3) for k,v in nums.items())
>>>{1: 1, 2: 64, 3: 729}
清空词典
nums.clear()
>>>{}
设置
集合是花括号中逗号分隔的、无序的、唯一的元素的集合。它是可变的、无索引的和异构的。
vowels = {'a', 'e', 'i', 'o', 'u'}
无序:元素的顺序没有保持。
唯一:与列表不同,集合不存储重复值。
未索引:因为集合中的元素是无序的,所以没有索引。
可变:和列表一样,集合也是可变的。因此,可以在创建后添加和删除元素。然而,元素不能被改变,因为由于缺乏索引,无法访问特定的元素。
异构:集合可以保存不同数据类型的元素,只要它们是可散列的,即元素本身应该是不可变的。例如,集合中不能有列表。
集合允许诸如数学中的集合(有文氏图的那些)——交集、并集、差集等运算。所有这些操作的时间复杂度为 O(n)。
在 python 中,集合由 dictionary 数据类型支持,因此具有许多相似的特征。
还有一种类型的集合是不可变的,称为冻结集合。这可以用作字典中的键(因为它们是不可变的)或另一个集合的元素。
我们来编码吧!
创建集合
vowels = set(‘aeiou’)
>>> {‘u’, ‘e’, ‘o’, ‘i’, ‘a’}myname = set(‘shubhangi’)
>>> {‘u’, ’n’, ‘b’, ‘h’, ‘s’, ‘g’, ‘i’, ‘a’}
像列表和字典一样,集合也可以和理解一起工作!
even = {x for x in range (20) if x%2==0}
>>> {0, 2, 4, 6, 8, 10, 12, 14, 16, 18}
获取两个集合的交集,即两个集合中的公共元素
myname.intersection(vowels)
>>> {‘u’, ‘i’, ‘a’}
将两个集合连接在一起(获取它们的联合)
myname.union(vowels)
>>> {‘u’, ’n’, ‘e’, ‘o’, ‘b’, ‘h’, ‘s’, ‘g’, ‘i’, ‘a’}
获取在一个集合中存在而在另一个集合中不存在的元素
myname.difference(vowels)
>>> {’n’, ‘b’, ‘h’, ‘s’, ‘g’}
向集合中添加元素
myname.add(‘hora’)
>>> {‘u’, ’n’, ‘hora’, ‘b’, ‘h’, ‘s’, ‘g’, ‘i’, ‘a’}
从集合中移除元素
myname.remove(‘hora’)
>>> {‘u’, ’n’, ‘b’, ‘h’, ‘s’, ‘g’, ‘i’, ‘a’}
我希望这篇文章能帮助你学习/修改 python 中列表、集合、元组和字典的方式、时间和原因!除此之外,还有一些数据结构,比如数组、栈、队列、链表等等,它们可以从 python 库中导入或者使用现有的数据结构创建。我们将在以后的文章中讨论这一点:)
编写行业标准 Python 代码的最佳实践
原文:https://towardsdatascience.com/python-best-practices-for-writing-industry-standard-code-66b848783e0b
停止在 Python 代码中犯这些错误

图片来源:Unsplash
为什么是 Python 编码最佳实践?
获得良好分析结果的旅程充满了探索、实验和多种方法的测试。在这个过程中,我们编写了许多代码,我们和其他人在团队工作时会参考这些代码。如果您的代码不够干净,那么理解、重用或修改您的代码对其他人(您也一样)来说将是一种痛苦。
如果你写了一个整洁干净的代码,你会感谢自己,别人也会感谢你。
这篇博客将向您介绍编写整洁干净代码的 Python 编码最佳实践。
python 编程有 PEP8 等行业标准。PEP 代表 Python 增强提案。让我们详细看看这些标准:
Python 编码最佳实践
1.变量命名约定
设置直观的变量名对任何人理解我们试图做什么都非常有帮助。
通常,我们使用单个字符创建变量,如 I、j、k——这些不是自明的变量,应该避免使用。
注意:切勿使用 l、o 或 I 单字母名称,因为根据字体不同,这些名称可能会被误认为“1”和“0”。
o = 2 # This may look like you're trying to reassign 2 to zero不好练
我们可以使用下划线“_”来创建多单词变量名,如下所示:
好的做法
注意:停止使用 df 来定义数据框,而是使用更直观的名称。
2。函数中的注释
注释是使我们的代码可读和易于理解的最重要的方面之一。
有两种类型的注释:
- 行内注释:与代码在同一行的注释。# '用于设置行内注释。
a = 10 # Single line comment
2.块注释:多行注释称为块注释。
""" Example of Multi-line comment
Line 2 of a multiline comment
"""
不好的做法
好做法
3。缩进
缩进是一个强大的特性,可以使代码易于阅读和理解。每个缩进级别使用 4 个空格/制表符来设置缩进。
一些语法要求代码默认具有缩进。例如,函数定义体中的内容:
**def** function**(**var_one**):**
print**(**var_one**)**
我们可以使用缩进来创建清晰易读的多行函数成分(如自变量、变量等)。).
不好的做法
好的做法
缩进用制表符还是空格?空格是首选的缩进方法。制表符应该只用于保持与已经用制表符缩进的代码一致。Python 不允许混合使用制表符和空格来缩进。
4。最大线路长度
根据 PEP8,最佳实践是将所有行限制为最多 79 个字符。一般来说,80-100 个字符也可以。
对于圆括号或方括号内的代码,Python 默认假设行连续,而对于其他情况,我们可以使用“\”。
不好的做法
好的做法
5.单独行上的导入
建议在单独的行上导入新包。
不好练
**import** sys**,** os, pandas
好的做法
**import** os
**import** sys
**import** pandas
6.间距建议
间距还会影响用户的整体可读性体验。以下是关于间距的最佳实践:
始终用一个空格将以下二元运算符括起来:赋值(=)、增广赋值(+=、-=,等。)、比较(==、<、>、!=、<>、<=、>=、in、not in、is、is not)、布尔(and、or、not)。
不好的做法
i**=**i**+1**
submitted **+=1**
x **=** x ***** **2** **-** **1**
hypot2 **=** x ***** x **+** y ***** y
c **=** **(**a **+** b**)** ***** **(**a **-** b**)**
好做法
i **=** i **+** **1**
submitted **+=** **1**
x **=** x***2** **-** **1**
hypot2 **=** x*****x **+** y*****y
c **=** **(**a**+**b**)** ***** **(**a**-**b**)**
参考资料:
- https://gist.github.com/sloria/7001839
- https://sphinxcontrib-Napoleon . readthe docs . io/en/latest/example _ Google . html # example-Google
谢谢!
你可能也会喜欢下面的故事:
https://medium.com/codex/top-10-python-operations-that-every-aspiring-data-scientist-should-know-92b6f9a98ff9 https://medium.com/codex/every-data-analysis-in-10-steps-960dc7e7f00b
Python 集合模块:被遗忘的数据容器
原文:https://towardsdatascience.com/python-collections-module-the-forgotten-data-containers-f282bf6c1fe6
如果您没有使用集合模块中的容器数据类型,您应该

图片作者。
在编程语言的学习过程中,开发我们自己的技巧来实现特定的任务并不少见。作为数据科学家,我们最终可能会有一些自己制作的小配方,使我们能够以特定的方式操纵数据。我们倾向于把这些食谱记在我们珍藏的笔记本上。
有时候,这些食谱使用的工具并不是最适合手头的任务。尽管如此,我们从不费心寻找更好的工具,因为这项工作是琐碎的。我们的实现是可行的——例如,实现一个计数器或者存储一个直播提要的最后十次出现。
当有更合适的数据容器时,我们可能会使用 Python 基本数据结构的某种组合来实现我们的目标。Python 的集合模块就是一个例子。这个有点被遗忘但很棒的模块实现了数据容器,如果你给它一个机会,它将极大地简化你的生活。也许一个集合模块容器将取代那些旧配方中的一个。
这个故事将回顾集合模块中一些最有用的数据容器。更多信息,请查看官方文档。
故事结构如下:
- 默认字典
- 双端队列
- 计数器
- 命名元组
- 最后的话
默认字典
列表中的第一个容器是模块中我最喜欢的容器, defaultdict 。它会以意想不到的方式简化你的生活。这个容器是一个字典,但是它的值是根据默认工厂初始化的。
通常情况下,我们希望字典中的所有值都是列表,例如。为每个键初始化列表既痛苦又麻烦。每次我们想对一个值进行列表操作时,我们必须检查这个键是否存在;如果没有,首先初始化列表,然后继续进行列表操作。非常头疼。嗯, defaultdict 解决了这个。
下面是列表默认工厂的一个例子:
defaultdict(
,{'a': [1,2,3]})
如果我们没有使用 defaultdict ,我们将不得不做这样的事情:
看着眼熟?
用设置为默认工厂怎么样?
defaultdict(
,{'a': {1,2,3}})
正如你所看到的,所有的键都保存了集合值,我们可以对它们进行集合操作,而不需要单独初始化每个键。
现在我们用 int 作为默认工厂;这对于计数很有用:
defaultdict(
,{'a': 3})
如果没有 defaultdict ,我们每次想要改变计数时都必须做这样的事情:
如何不爱 defaultdict ?下次你打算使用普通的旧字典时,评估一下默认字典是否能更好地服务于你的目的。如果是这样的话,将会大大简化你的生活和代码。
双端队列
双端队列,或 deque ,是一个容器,它概括了队列(FIFO)和堆栈(LIFO)。在许多方面,这个容器类似于一个列表;但是,它实现了优化的固定长度操作。它还具有最大长度的性质;列举了没有的东西。
想象一个简单的用例;我们实时存储推文,并想要一个包含十条最新推文的容器。如果我们将 tweets 保存在一个列表中,每次我们添加一个,我们就必须检查长度是否大于十;在这种情况下,我们需要精简列表。有了 deques ,那就不再需要了;我们可以继续追加,它将保持预定义的最大长度。
虽然在某些方面可以将 deques 比作列表,但是它们要多得多;它们也可以用作 LIFO 或 FIFO 队列。它们类似于队列标准模块中的队列。然而,这是为线程之间的通信而设计的(线程锁等)。),而 deques 只是一个数据结构。
由于 deque 不用于线程间通信,所以它不实现 put 方法,而是实现 append 和 extend。我们可以从右边追加:
德克([2,3,4],maxlen=3)
错误的
或者从左边追加:
德克([3,2,1])
我们可以进行浅拷贝,清除 deque :
德克([])
德克([3,2,1])
我们可以用一个 iterable 来扩展dequee,就好像它是一个列表并计算出现次数:
德克([4,5,3,2,1,3,4])
2
1
我们可以得到整个队列或一个索引范围内元素的索引:
2, 4
5
当然,我们可以加入流行元素。如果我们不能弹出元素,它怎么能被称为双端队列呢?
4
德克([4,5,3,2,1,3])
4
德克([5,3,2,1,3])
我们可以删除元素:
德克([5,2,1,3])
反转德克尔:
德克([3,1,2,5])
非常方便的是,我们还可以旋转按钮或移动它:
德克([5,3,1,2])
德克([1,2,5,3])
当您想要一个队列并且不关心多线程时,队列应该是您的首选数据结构。虽然队列是线程安全的,但是来自队列模块的队列更适合。
计数器
虽然我们可以使用default dictwithintdefault factory 作为计数器,但是 collections 模块自己实现了一个计数器。计数器可以创建为空对象,稍后更新:
0
请注意,不在计数器上的按键计数始终为零。
我们可以通过直接更新元素或更新 iterables(列表、元组等)来增加每个元素的计数。)我们可以使用元素方法来获取计数器中的所有元素。
2 1
['a ',' a ',' b']
a
a
b
对象中还有许多其他有用的方法,例如对最常见的元素进行排序,获得总计数,或者我个人最喜欢的减去计数器:
[('a ',2),(' b ',1)]
[('a ',2)]
3
计数器({'a': 1,' b': 1})
计数器({'a': 1,' b': 0})
在前面的例子中,我们从一个空的计数器开始。然而,也可以从一个 iterable 或一个字典中创建一个计数器,其中包含每个键的计数:
计数器({'a': 2,' b': 1})
计数器({'a': 2,' b': 1})
当在不太合适的数据结构(如普通的字典或列表)中实现计数器时,这种计数器实现可以为您省去很多麻烦。下一次你需要计算出现次数时,旋转一下集合模块中的计数器。
命名元组
如果我们寻求不可变的数据容器,Python 中的元组是很棒的。它们速度快,内存效率高;他们有什么不可爱的?我来告诉你;我不喜欢整数索引数据访问。显然,Python 核心开发人员也不喜欢这样。对于具有名称访问的元组已经有了许多迭代。其中一个数据容器是名为的。
我们首先需要使 tuple 类创建一个名为 tuple 的。我们设置类名和属性名(元素,按照我们希望元组被索引的顺序)。然后,我们用实际数据实例化该类:
人(姓名= '迭戈',年龄=33 岁)
迭戈,迭戈
33, 33
虽然这是一个好的解决方案,但我并不完全信服。我觉得创作机制有些离奇。我会使用来自类型化模块的命名元组:
人(姓名= '迭戈',年龄=0)
迭戈,迭戈
0, 0
如果你想了解更多关于 Python 中类型化数据结构的知识,可以看看这个故事。它对 Python 中的所有主要容器进行了彻底的比较,比如 NamedTuple 。
最后的话
下次当你使用 Python 的基本数据结构来实现一个感觉像是黑客的任务时,看看集合模块。你也许能找到适合这项工作的工具。
不要忘记查看官方文档以获取更多信息。
喜欢这个故事吗?通过我下面的推荐链接成为一个媒体成员来支持我的写作。无限制地访问我的故事和许多其他内容。
https://medium.com/@diego-barba/membership
我希望这个故事对你有用。如果你想知道更多类似的故事,请订阅。
https://medium.com/subscribe/@diego-barba
Python 并发性——concurrent . futures
原文:https://towardsdatascience.com/python-concurrency-concurrent-futures-15b56dc9a14d
Python 并发系列的第 3 部分。多线程和多处理带来的界面简单性。

图片作者。
浏览 Python 并发系列:
以前的故事:
在连续头疼处理多线程和多进程代码之后,一个梦想开始成形:有没有一种更简单的方法来实现它?有办法隐藏线程、进程、队列和管道的创建吗?有没有一种方法可以将计算转移到其他地方,并获得结果?
原来有这么个办法。
concurrent.futures实现了一个简单、直观、坦率地说非常棒的 API 来处理线程和进程。到目前为止,我们已经了解了多进程和多线程代码。我们知道如何创建进程和线程,但有时我们需要更简单的东西。
可能有些情况下,我们真的希望在代码中管理单独的进程或多线程。然而,对于数据科学家来说,进程和线程通常是实现并行性或从程序的主线程上卸载计算的手段。这里是concurrent.futures模块的亮点。它隐藏了多线程/进程代码的大部分复杂性,让我们专注于自己的事情,检索、处理和应用 CPU 密集型数据。
故事结构
Futuremap发电机- 重用池执行器
- ProcessPool 和
Executor类 - 多线程->多进程
- 多进程->多线程
- 这个故事的寓意
Future
未来的概念是concurrent.futures 模块简洁背后的精髓。未来是一个还不存在但将来会存在的结果的代理。一个任务被提交给一个执行者,执行者给我们一个未来。因此,我们可以把它看作是一种收据,这样我们以后就可以回来使用它来获得我们任务的结果。执行者将为我们管理所有的线程和进程管理。
在底层,执行器是一个进程或线程池和一个任务调度器。
作为例子,我们将使用一个ThreadPoolExecutor。我们将提交一个休眠了sleep_secs的任务(do_work(),然后返回一个字符串。executor 类的submit()方法返回一个Future类的实例。
我们将探索两种不同的实现:
wait_for_future():我们在创建未来之后立即调用result方法。这个调用会一直阻塞,直到结果准备好。get_future_after():我们在做了一些其他重要的事情(睡觉)之后,调用result方法。在这种情况下,我们等待的时间比任务所花费的时间要多;因此,当我们进行调用时,结果已经准备好了(没有阻塞)。
在这两个实现中,我们将跟踪时间并引入一些print语句来阐明future的行为。
— — — —等待未来— —
已创建的未来| 0.00046324729919433594
等待未来… | 0.0004940032958984375
未来结果:foo | 5.00242519378621
———获得已创建的未来之后— — —
已创建的未来| 0.000028621
从打印语句中可以看出,submit()方法没有阻塞。只有当任务在被调用时没有完成时,result()方法才会被阻塞。
map发电机
执行器有另一种调度任务的方法,即map(方法(完全类似于 Python 的map())。此方法使用 iterable 的元素作为参数来执行给定的函数。本质上,这是一种编写for循环和存储期货的更紧凑的方式。在接下来的章节中,我们将讨论为什么这种方法是我最喜欢的并行计算方法之一。
map()返回一个发电机;因此,调用不会阻塞。但是,如果相应的任务没有完成,从生成器弹出元素可能会阻塞。
下面是一个简单的使用map(方法的例子。传递给map()的函数签名应该只有一个参数,即 iterable。如果我们的函数没有相同的签名,我们可以使用来自functools模块的partial函数:
地图生成器已创建| 0.001207113265991211
正在等待地图结果… | 0.0012679100036621094
地图结果:['foo-1 ',' foo-2 ',' foo-3 ',' foo-4 ',' foo-5 ',' foo-6 ',' foo-7 ',' foo-8 ',' foo-9 ']| 9.99380612373355
可重用的池执行器
在上一节中,我们使用了ThreadPoolExecutor作为上下文管理器(with语句)。虽然这种方法是首选的,因为它负责终止池,但它有一个明显的缺点;执行人不能重复使用。
我们经常需要连续访问池,并希望避免池创建和终止对性能的影响。在这种情况下,我们可以创建一个 executor 类的实例,在我们认为合适的地方使用它,并使用shutdown()方法手动终止它。
下面的例子说明了我们如何重用执行器:
一些地图结果:['foo-1 ',' foo-2 ',' foo-3 ',' foo-4 ',' foo-5 ',' foo-6 ',' foo-7 ',' foo-8 ',' foo-9']
更多地图结果:['foo-10 ',' foo-11 ',' foo-12 ',' foo-13 ',' foo-14 ',' foo-15 ',' foo-16 ',' foo-17 ',' foo-18 ',' foo-19']
进程池和Executor类
如果您还没有注意到,到目前为止给出的代码为 executor 导入使用了一个别名,PoolExecutor。有一个很好的理由,线程池(ThreadPoolExecutor)和进程池(ProcessPoolExecutor)实现了相同的接口。它们都继承自Executor类,并实现相同的三个方法:
submit()map()shutdown()
这是我最喜欢的concurrent.futures API 的特性之一,我们可以通过最少的代码重构从线程切换到进程。以下示例显示了使用ProcessPoolExecutor是多么简单:
映射结果:['foo-1 ',' foo-2 ',' foo-3 ',' foo-4 ',' foo-5 ',' foo-6 ',' foo-7 ',' foo-8 ',' foo-9 ']| 6.14733169555664
当通过submit()或map()计算进程池中的多个任务时,它们将被并行计算,直到达到池中的进程数。如果池中的进程数是 8,而任务数更大,那么在任何给定的时间最多只有 8 个并行任务在执行。
使用进程池,map()方法可用于并行化仅依赖于可迭代值的for循环。
多线程->多进程
在同一个程序中使用多线程和多进程可能并不普遍,但我经常发现自己处于这种情况。也许太频繁了。
逻辑如下:
- 一个事件触发一个线程;这可能是客户请求或一些传感器读数。
- 在这个新线程中,我们需要做一些 CPU 繁重的工作,当然,我们希望它使用多个内核。所以我们使用多个进程进行一些并行计算。
虽然逻辑听起来非常简单,但是使用裸进程和线程实现这种方法可能会很麻烦。然而,concurrent.futures API 让它变得非常简单。下面的例子说明了这一点。在该示例中,CPU 密集型函数(do_CPU_bound_work())被另一个函数(do_parallel_work)并行调用。该流程由两个线程同时执行:
A 线程,做并行 _ 工作…
B 线程,做并行 _ 工作…
A 线程,完成
B 线程,完成
如果我们使用ThreadPoolExecutor而不是裸线程,我们可以进一步简化它:
A 线程,做并行 _ 工作……
B 线程,做并行 _ 工作……
A 线程,完成
B 线程,完成
另一个很棒的特性是,我们可以从future或map()生成器获得计算结果,而不需要队列、管道或时髦的共享变量。
一个伟大的界面设计能把我们的生活简化到如此程度,真是不可思议。
多进程->多线程
如果我们不想从多个线程中产生进程,而是想从多个进程中产生线程,那该怎么办?没问题,我们可以用最简单的方法来做。以下示例显示了我们如何创建多个线程来处理来自多个进程的并发 I/O:
A 进程,正在进行并行 I/O…
B 进程,正在进行并行 I/O…
B 进程,完成
A 进程,完成
我们还可以从concurrent.futures API 创建流程:
A 进程,执行并行 I/O…
B 进程,执行并行 I/O…
A 进程,完成
B 进程,完成
这个故事的寓意
不爱concurrent.futures实现的接口很难。就我个人而言,这是我处理并发性的首选,无论是使用线程进行 I/O 还是使用进程进行并行计算。
如果您现在还没有使用它,那么您绝对应该使用它!
一切都应该尽可能简单,但不是更简单
——阿尔伯特·爱因斯坦
我希望这个故事对你有用。 订阅 到我的邮件列表如果你想知道更多这样的故事。
喜欢这个故事吗?通过我下面的推荐链接成为一个媒体成员来支持我的写作。无限制地访问我的故事和许多其他内容。
https://medium.com/@diego-barba/membership
Python 并发性——多重处理
原文:https://towardsdatascience.com/python-concurrency-multiprocessing-327c02544a5a
Python 并发系列的第 2 部分。多重处理模块使我们能够执行真正的并行任务。然而,有许多事情需要注意。

浏览 Python 并发系列:
上一篇:
接下来的故事:
多处理宣传多核心使用。是不是很多 Python 用户祈祷的答案?是最终绕过 GIL 的方法吗?
首先,这并不新鲜。现在其实已经挺老了。早在 2008 年,Python 2.6 就引入了多处理模块。
事实上,我们可以将不同的任务提交给在不同内核中同时执行的不同操作系统进程。然而,这可能不是我们期望的解决方案。有两个主要原因:
- 我们首先需要编写并行代码,在多个内核中运行计算。也就是说,将一个较大的任务分成许多可以并行执行的较小的作业。这样的努力并不总是可能的。
- 设计多进程应用程序时,许多细节需要微调。实现流程之间的协调、通信和同步不是一件容易的事情。
在这个故事中,我们将介绍流程创建和流程间数据共享的基础知识,同时指出哪里可能出错、如何出错以及如何避免出错。故事的最后一部分致力于一个激动人心的话题:多进程和多线程。
故事结构
- 加速一个过程
- 子进程和守护进程
- 上下文
- 跨流程共享变量
- 竞争条件和锁
- 队列和管道
- 进程池
- 进程间数据交换的性能
- 多处理+多线程???
- 这个故事的寓意
加速一个过程
如果你熟悉线程模块,当你使用多重处理模块时,你会有宾至如归的感觉。相似概念的接口几乎相同。一个恰当的例子是加速一个新过程的方法:
做事…
事情做完了
进程对象的初始化参数与线程对象中的基本相同。主要区别在于,我们需要使用习语来启动新流程:
if __name__ == ‘__main__’:
否则,我们会得到一个 RuntimeError,除非是在使用fork上下文时(我们将在下一节讨论更多关于上下文的内容)。
与线程不同,我们可以创建许多真正并行的进程:
1 做事…
0 做事…
2 做事…
4 做事…
3 做事…
1 做事
0 做事
2 做事
4 做事
3 做事
这是正确的; GIL 不干涉进程;它们是同时发生的。
子进程和守护进程
任何进程都可以启动新的进程;即,它可以有“子”进程,除非“父”进程是一个守护进程进程。例如:
父母睡眠 5 秒…
孩子睡眠 5 秒…
父母完成
孩子完成
在这个例子中,foo进程从主进程开始;是的,您可能创建的所有流程都是主 Python 流程的子流程。然后,foo进程启动它自己的子进程(foo_child)。所以这个孩子实际上是 Python 主进程的“孙子”。
像守护线程一样,程序不会等待守护进程完成;即,当所有非守护进程完成时,程序将退出:
父进程休眠 5 秒…
守护进程子进程休眠 10 秒…
父进程完成
在这个例子中,子进程,一个守护进程,比父进程睡眠时间更长;因此,程序不等子进程完成就退出。我们从来没有看到“儿童完成”打印。
总而言之,守护进程与普通进程有两个不同之处:
- 它们不能有子进程。
- 程序在退出前不会等待它们完成。
上下文
多处理环境允许我们选择子进程如何开始,也就是说,它从父进程继承了什么。有三种选择:
- 产卵:启动一个全新的 Python 进程。新进程不会从父进程继承不必要的对象。特别是,它不复制线程锁。这是 macOS 和 Windows 的默认方法。
- fork :是父进程的副本。虽然它不复制线程,但它复制线程锁。在 Unix 中是默认的。这种方法被认为是线程不安全的,尤其可能导致 macOS 上的子进程崩溃。
- forkserver :第一次启动时,它创建一个新的 Python 进程和一个服务器。每当我们想要启动一个新流程时,我们就连接到服务器并请求最初创建的新 Python 流程的一个分支。
查看官方文档了解每种方法的更多信息。
三种方法中,“fork”最快最不安全,“spawn”最慢最安全。
我们所说的过程安全是指什么?
除了使用“fork”方法时报告的 macOS 崩溃之外,安全意味着线程安全,特别是关于线程锁。在故事的最后一部分,我们将探讨多线程多进程程序是如何陷入死锁的。
原则上,使用“fork”创建流程应该比使用“spawn”方法更快。我们来测试一下。
下面的脚本用给定的上下文创建了n_procs。它测量创建每个进程所花费的时间,并将它们保存到一个 JSON 文件中:
要运行脚本:
$ python [script name].py [number or processes] [context (spawn, fork or forkserver)
结果如下:
最小最大值=(1.795373,9.288665),平均值= 3。18666.866676666867
fork: minmax=(1.151489,8.550412),mean = 1 . 155041967365
forkserver:minmax =(1.133119,87.569707),mean = 3.8111726700000004
时间以毫秒表示。
正如我们所看到的,的确,“spawn”方法是最慢的。
“forkserver”方法很有趣;最小创建时间比“fork”方法要短,但是它的平均值要大。这种方法的文献不多;我以前没有在生产中使用过它,但是用它做更多的实验可能是值得的。
跨流程共享变量
所以“spawn”上下文是避免问题的首选方式。你能猜到如果两个子进程试图修改主进程中定义的一个全局变量会发生什么吗?什么都没有,变量什么都不会发生。因为子流程是一个新的 Python 流程,所以全局变量在实际应用中表现为不同的变量。看看这个例子:
进程号初始化 0
进程号完成 100
进程号初始化 0
进程号完成 100
主进程完成,编号 0
我们错误地期望看到在主流程中定义的数字变成 200。期望的行为是在两个过程中各加 100。进程的行为不是这样的;在“spawn”上下文中,它们不共享内存。问题来了,我们如何在进程间共享变量?
多处理模块实现了两个对象在进程间共享数据,*Value*和*Array*。
Value对象用于共享单值变量,如数字或字符串。例如:
进程号初始化 0
进程号初始化 0
进程号完成 100
进程号完成 101
主完成,号 101
正确结果:
进程号初始化 0
进程号完成 100
进程号初始化 100
进程号完成 200
主进程完成,数量 200
我们通过从数组模块中声明它的 typecode 来实例化Value(用“l”表示有符号长整型)并使用*value*属性访问它的值。
出了问题;我们没有得到预期的 200 英镑。现在怎么办?比赛条件。我们成功地在进程间共享变量;现在,我们需要实现同步机制。
竞争条件和锁
即使有 GIL 的帮助,在执行多线程时避免竞争情况也是至关重要的。现在我们有了真正的并行执行,事情变得更糟了。当使用线程时,我们可以避免使用锁,通常情况下,我们会没事的。当在执行写操作的进程之间使用共享变量时,锁是必须的。
幸运的是,多处理模块有一个锁,它实现了与线程模块中的锁相同的简单接口。通过向上一节示例中的示例添加锁,我们可以获得正确的行为:
进程号初始化 0
进程号完成 100
进程号初始化 100
进程号完成 200
主完成,号 200
最后我们在主流程中定义的number得了 200。
在模块中还有其他有用的同步原语,请务必查看。它们非常相似,但也有一些不同,这可能使其中一个更适合您的特定用例。
队列和管道
共享变量很好,但是队列呢?它们对于多线程非常有用。我们能在多个过程中使用它们吗?让我们试试:
睡眠 5 秒……
睡完了,q 放…死锁
结果是行不通的。程序陷入了僵局。我们不能天真地在多处理代码中使用线程安全对象;否则,我们可能会陷入困境。幸运的是,在多重处理模块中有一个多重处理安全队列:
睡眠 5 秒…
完成睡眠后,q 放
foo 4 秒。54630.64636646666
一切都像预期的那样工作。
队列非常适合于进程间的单向通信;一个进程将东西放入队列,另一个进程获取它们。没有比这更简单的了。但是如果我们需要双向沟通呢?有一个奇妙的对象实现了这一点,即管道:
proc 1 休眠 5 秒…
proc 1 完成休眠,连接发送
proc 2 foo 5.0035919869551435 秒
proc 2 休眠 5 秒…
proc 2 完成休眠,发送 ack 并关闭连接 2
proc 1 接收 ack 并关闭连接
从例子中可以看出,当我们实例化*Pipe*时,我们得到了连接的两端。然后我们给每个进程传递一个,并使用send和recv进行通信。太棒了,不是吗?提醒一句,recv方法会阻塞。
进程池
正如我们已经知道的,产生一个过程是缓慢的。通常,我们只在相对较短的时间内需要其他进程。大多数情况下,它们用于减轻主进程的 CPU 密集型负担(可能是并行计算)。任务被卸载到另一个进程,一旦完成,我们就把结果返回给主进程。每次都创建新的流程是无效的。进入进程池。*Pool*为我们负责流程创建和沟通。此外,池界面是为提交任务而设计的。相比之下,香草*Process* 界面就通用多了。
在流程池中的方法中,我们将讨论三种最常用的方法:
map:模拟 Python 的普通地图apply:发送一个函数到池中进行计算,等待(阻塞)直到结果准备好apply_async:将一个函数发送到池中进行计算,然后以异步方式检索结果
下面的脚本使用阶乘函数(get_factorial)的一个简单(计算上很糟糕)的实现作为计算的目标。
每种方法都有一个函数。这些函数接受一个Pool 实例和一个整数列表来计算阶乘。此外,函数测量执行时间并返回它。
main 函数创建一个包含 8 个进程的处理池,并从这三种方法中的每一种方法中获取前 20 个整数(包括零)的列表的解决方案。它为n_trials重复计算。
毕竟,计算完成后,结果保存在 JSON 文件中供进一步分析:
结果是:
map: minmax=(0.801848,47.466579),mean = 1.4865362500000003
应用:最小最大值=(3.544991,6.260502),平均值=4.26371475
apply_async: minmax=(0.792829,2.11585),mean=1.12916867
apply是 iterable 最慢的方法。这是有意义的,因为我们在提交对应于 iterable 的下一个元素的另一个任务之前等待结果。apply不太适合 iterables。我们使用apply_async来避免等待每个任务。然而,我震惊地意识到apply_async比map方法更快(或者至少是可比的)。
处理 iterables 时,给apply_async一个机会;不会让人失望的。
进程间数据交换的性能
使用多处理作为并行计算手段的主要注意事项是移动数据。也就是说,使数据对需要它的过程可用。这一部分将测试在所有三个多处理环境中移动数据所花费的时间。
测试的计算是添加一个矩阵列。整个矩阵以及列的索引被提交给许多过程。每列一个过程。当然,这是一种相对低效的求和方式。尽管如此,它在测试中达到了两个目的:
- 即使我们只需要一列,也必须将整个矩阵移动到每个过程中。
- 单独遍历列(我们有意避免 NumPy 的向量化)相当于一个有许多类似试验的测试。因此,使测试在统计意义上更加稳健。
在下面的代码中,函数sum_matrix_columns定义了要提交给流程的任务。main 函数遍历矩阵列,为每一列创建一个过程。请注意,这些过程不是并行的;它们是连续的。测量每个进程的时间,然后保存到 JSON 文件中:
要运行该脚本,我们需要提供上下文、矩阵行数和矩阵列数作为参数,即,
$ python [script name].py [context(spawn, fork or forkserver) ] [matrix rows] [matrix columns]
结果是:
spawn:minmax =(284.932112,464.454816),mean = 297.296767676767
叉: minmax=(0.963039,316.494265),mean=15.52602248
forkserver:minmax =(176.789977,319.979666),mean = 188.88676658675
时间以毫秒表示。
我们可以清楚地看到,“fork”上下文更快,因为它没有从头开始一个过程;太糟糕了,有时不是很安全。相反,“产卵”是最慢的。然而,这种差异是惊人的,“fork”和“spawn”之间的性能差异约为 20 倍。
多处理+多线程???
我们把最好的留到了最后。一个非常有趣的话题:当我们在一个多进程程序中使用多线程时会发生什么?在上下文部分,我们讨论了“fork”不会将线程复制到子进程中的事实。但这意味着什么,为什么会不安全?
它在线程的意义上变得不安全,因为多线程程序中使用的锁可能会突然失去所有所有权,导致死锁。锁的所有者是最后锁定它的线程;只有那个线程可以打开它。
考虑下面的例子:
main 函数启动一个线程(在主进程中),然后启动一个子进程。反过来,子进程启动另一个线程。因此,这是一个多进程、多线程的程序。目标函数请求锁定max_iter的迭代。如果它得到锁,它就睡觉。
如果我们使用“spawn”的“forkserver”运行代码,程序将花费几秒钟(等待所有迭代)然后退出。这是预期的行为。然而,如果我们使用“fork”上下文运行程序,它将会一直处于死锁状态。这是为什么呢?
“fork”上下文的问题是它不从父线程复制线程。但是,它确实从父对象复制了锁。
因此,就子进程而言,锁没有所有者;所有者是子子进程中不再存在的线程。
最后一个例子非常清楚地说明了这一点;如果一定要做一个多线程+多进程的应用,明智的做法是不要使用“fork”上下文。
这个故事的寓意
我们已经证实,在某些情况下,“分叉”上下文是最快的,快了一个数量级。虽然官方文档说使用 macOS 可能会崩溃,但我还没有亲眼目睹过。事实上,这种方法的速度有时值得冒险。
我会说,如果子进程对程序至关重要,就要避免“fork”。然而,如果子进程只执行 CPU 密集型任务,速度是最重要的,并且不涉及线程,那么您可能会陷入危险的境地,并使用“fork”。
我希望这个故事对你有用。 订阅 到我的邮件列表如果你想知道更多这样的故事。
喜欢这个故事?通过我下面的推荐链接成为一个媒体成员来支持我的写作。无限制地访问我的故事和许多其他内容。
https://medium.com/@diego-barba/membership
Python 并发性——线程和 GIL
原文:https://towardsdatascience.com/python-concurrency-threading-and-the-gil-db940596e325
Python 并发系列的第 1 部分。线程和 GIL 是 Python 中比较有争议的话题,但是大多数时候被误解了。

约翰·安维克在 Unsplash 上的照片
浏览 Python 并发系列:
接下来的故事:
有些人喜欢线程,但更多人讨厌线程。然而,在许多情况下,这种厌恶可能源于关于 Python 中线程工作方式的错误信息。是的,编写多线程应用程序可能很难看,我们必须非常小心以避免常见的陷阱。然而,在大多数情况下,多线程是在 Python 中实现并发的最简单的方式。
线程经常被比作多处理或异步编程。虽然这是有意义的,因为所有三种范式都实现了某种形式的并发,但在某些情况下,每种范式都比其他范式更有意义。无论何时有 I/O,线程都应该被认为是一个可靠的选择,不要让错误的厌恶阻止了你。
Async 炒作多,那是肯定的。但是一旦你在代码中写了async def,就没有回头路了。你必须全力以赴。你将穿过兔子洞,对你的应用程序进行一次大爆炸式的重写,把所有东西都改成异步。不总是最好的主意。
本系列将介绍 Python 中并发性的基础知识,以及我在那个时代遇到的见解。这样的事情我希望我在开始用 Python 做并发的时候就知道了,忽略它们导致了一些长期困扰代码的神秘 bug。
这个故事将解决线程和全局解释器锁(GIL)。希望在本系列的最后,您能给 threads 一个机会。如果幸运的话,您将不再认为 GIL 是 Python 中必须淘汰的烦人的东西。一旦我们理解了 GIL 是如何简化许多事情的,我们可能会开始从不同的角度看待它。
故事结构
- 纺一根线
- 守护进程
- 线程返回???行列
- 全局解释器锁(GIL)
- I/O 和 CPU 绑定的线程
- 线程锁和线程安全
join方法- 关键要点
纺一根线
线程模块不是 Python 中最受欢迎的模块之一。公平地说,它有点晦涩难懂。一开始就变得令人困惑的事情之一是创建线程的方式;等等,在 Python 中有两种创建线程的方法:
在这种方法中,我们将一个函数传递给作为目标的Thread对象(以及它的参数)。另一种方法是直接从Thread类继承并实现run方法。
这明显违反了 Python 的禅。
应该有一种——最好只有一种——显而易见的方法来做这件事。
PEP 20—Python 的禅宗
虽然从函数中运行线程可能更简单,但是当我们想要存储状态时,类方法是有用的。因此,在这种情况下,拥有两种方法并不是一个坏主意。
守护进程
在操作系统级别,守护进程是后台进程,运行时不与用户交互。在 Python 线程的上下文中,守护进程只是后台线程。与普通线程的区别在于,当只有守护线程在运行时,程序会退出。换句话说,程序将等待正常线程完成(不取消);一旦完成,所有正在运行的守护进程将被终止,程序将退出。
例如,考虑下面这个我们将用作线程目标的函数,它休眠了sleep_secs:
考虑两个线程,一个普通线程和一个守护进程,分别有 20 秒和 10 秒的睡眠时间:
正常线程,休眠 20 秒:初始化
守护线程,休眠 10 秒:初始化
守护线程,休眠 10 秒:完成
正常线程,休眠 20 秒:完成
守护进程在正常线程之前完成,程序等待正常线程完成。然而,如果我们改变睡眠时间:
正常线程,休眠 10 秒:初始化
守护线程,休眠 20 秒:初始化
正常线程,休眠 10 秒:完成
正常线程在守护进程之前完成,程序不等待守护进程就退出。
Python 中的线程取消是不可能的,至少直接不可能;我们必须等待线程完成或实现另一个机制。有时,我们最终会得到一个因为一些流氓线程而拒绝退出的多线程程序,并且我们可能最终会在操作系统级别杀死这个进程。守护进程的作用就是避免这种令人头疼的问题。
线程返回???行列
线程的主要问题是如何返回函数的结果并不明显。虽然我们可能希望传递这样一个函数作为线程的目标:
然而,线程对象并不公开从其目标返回某些内容的方法:
虽然 Python 的异步协同例程使用线程实现了常规函数的返回,但人们必须发挥创造力。这种行为可能会阻止您在应用中使用多线程。但是不要烦恼;我们可以使用队列(queue.Queue)来提取结果。
我们可以在模块级别定义队列:
线程正在工作…
线程正在工作…来自队列的函数结果
或者,我最喜欢的是,将一个队列作为参数(q_out)传递给线程函数,并将结果存储在那里:
线程正在工作…
线程正在工作…来自队列的函数结果
这种队列方法也适用于永远运行的守护进程或线程。我知道这并不理想,尤其是在我们有许多线程和它们的队列的情况下,但是在较小的规模下这并不麻烦。
全局解释器锁(GIL)

多线程和 GIL 概念图。
与您可能听说的相反,Python 使用实际的 OS 线程(Unix 上的 POSIX 线程和 Windows 线程)。Python 是多线程的。你可能也听说过 Python 不能同时运行多个线程;这是真的。
早在 90 年代初,Python 解释器(CPython,最常用的一个)的多线程和内存管理就有问题。CPython 引用计数(内存管理)在比赛条件下遇到了麻烦。多个线程修改了程序中对象被引用的次数;偶尔,由于比赛条件,计数是错误的。
有时候,在应该计数的时候,计数从来没有达到 0;内存从未被释放;内存泄漏。在其他情况下,计数在不应该的时候达到了 0;该对象在不应该的时候被从内存中释放;程序崩溃了。
这个问题的解决方案是 GIL,一个在解释器级别实现的锁,以避免线程同时运行,确保引用计数在程序的生命周期内是正确的。
GIL 尽最大努力为所有活动线程提供时间,即使线程没有显式释放 GIL。线程在休眠(time.sleep)或执行 I/O 时释放 GIL。但是,如果一段时间过去了,线程还没有“自愿”释放 GIL,它将被迫这样做,从而给其他线程运行的机会。这个选择过程并不总是最佳的。
今天,对 Python 的一个主要批评是,它不是多线程的,并且不能(本机)利用多核等等。大多数人指责 GIL。然而,GIL 是一个简单而优雅的解决方案,它赋予了 Python 最珍贵的品质之一——简单,尤其是在内存管理方面。任何用 C++做过内存管理的人,无疑都能体会到 Python 的简单。
GIL 并不完美,但它让多线程变得非常简单且相对安全。主要问题是:
- GIL 和操作系统不协调。有时,操作系统可能会将 CPU 时间给不持有 GIL 的线程,反之亦然。
- 如果发生事件,I/O 线程将发送一个 GIL 丢弃请求,并启动一个超时周期(5 毫秒),从而导致额外的延迟。
- GIL 将给予 CPU 绑定的线程更高的优先级。因此,给 I/O 线程增加了不希望的延迟。
如果你想更多地了解 GIL 及其局限性,请查看大卫·比兹利的这篇精彩的主题演讲。
I/O 和 CPU 绑定的线程
本节将分析套接字服务器(I/O)线程在与 CPU 绑定的线程竞争 GIL 时会发生什么。编写服务器应用程序时,最重要的是以最小的延迟确认客户端请求。只要客户端知道其请求正在被处理,请求本身就可能需要更长时间。因此,了解多线程套接字服务器在与 CPU 密集型线程竞争时的行为是至关重要的。
下面的代码实现了:
- 一个简单的服务器(
echo_server),它在给定的端口发送回它接收到的任何东西,一个 echo 服务器。 - 另一个函数执行 CPU 相关的任务(
hammer_cpu)。 - 一切都储存在
time_data字典里。 - 当程序退出时(控制 c 或 socket 客户端断开),数据被保存到一个文件中(
save_time_data_to_file)。为了捕捉退出信号,我们使用了 atexit 模块。 - 最后,主函数启动线程;我们将所需的 CPU 线程数作为参数传递。
要运行服务器,请执行以下操作:
$ python [server script name].py [experiment_name] [number of CPU-bound threads]
客户端是单线程的;它还存储数据,并在退出时保存。
要运行客户端:
$ python [client script name].py [experiment_name]
以下是发送到服务器的消息的接收时间图,分别对应于无 CPU 绑定线程、1 个、2 个和 3 个 CPU 绑定线程:

Echo 客户端接收时间[图片由作者提供]。
我们可以看到,随着 CPU 绑定线程数量的增加,不仅平均套接字响应时间会增加,而且它们的标准偏差也会增加。因此,echo 服务器变得更加不稳定。这是 GIL 和操作系统不太协调的一个例子。请记住,在发生事件的情况下,I/O 线程将请求 GIL,但即使它得到了,操作系统也可能决定运行另一个不持有 GIL 的 CPU 绑定线程。
线程锁和线程安全
尽管有些人迫不及待地想让 Python“放弃”GIL,但 GIL 让多线程编程变得尽可能简单。只要操作是原子的,即线程安全的,GIL 将确保没有竞争条件。换句话说,由于 GIL,没有两个操作会同时执行。
然而,大多数时候,我们希望进行非原子的操作,如果多线程没有正确完成,我们会遇到麻烦。考虑下面的例子:
有一个线程(check_cache)遍历my_cache 的元素,而另一个线程(add_to_cache)则添加项目。迭代不是原子的。所以我们会得到一个异常:
"运行时错误:字典在迭代过程中改变了大小."
这个问题的解决方案是显式地使用线程锁。在下面的例子中,我们使用来自线程模块的Lock对象。我们只有在获得锁的情况下才能对my_cache进行操作;现在程序正常运行了。
所以多线程的关键是问自己:
我使用的对象和执行的操作是线程安全的吗?
如果没有,使用Lock。线程安全问题不仅仅是程序崩溃,如果不解决,我们可能会遇到各种各样的错误。我们可能会弄错库存、账户余额等等。
连接方法
线程 模块为多线程应用程序实现各种同步原语和实用程序。你可能需要的一切都会在那里找到。例如信号量、锁(前面讨论过的)和许多其他的。它还实现了处理线程的方法;我们不会一一介绍,只介绍一个:迄今为止我们还没有讨论过的最重要的方法join方法。
当我们start一个线程时,它不会阻塞。程序将继续执行下面的代码行,而不会等待线程完成。大多数时候这种行为恰恰是我们想要的;守护进程就是一个很好的例子;我们开火然后忘记。但是有时候,我们需要等待一个线程。在这种情况下,我们使用join方法。
考虑下面的代码片段和打印顺序:
线程函数 init
线程开始
等待线程…
线程函数循环完成
线程完成
join方法让程序在打印“等待线程”后等待(10 秒钟)
多线程应用的构建模块是使用队列和锁来连接线程。因此,我们必须彻底理解它们,以避免错误和重大错误。线程的特点是大多数潜在的异常都是隐藏的,直到最糟糕的时刻才会出现。
关键要点
线程是实现并发的一种简单方式。我们不需要事件循环、时髦的函数定义,也不需要生成额外的进程并与之通信。我们可以坚持 Python 基础,实现并发性。既然我们已经知道了 GIL 和线程同步与通信的基础知识,我们就可以着手多线程编程了。
主要的收获是线程对 I/O 有意义,而不是 CPU 受限的任务。每当有 CPU 线程时,操作系统和 GIL 之间缺乏协调就会变得更加明显。如果您需要进行 CPU 密集型计算,请尝试多处理或以下技巧:到处添加一些time.sleep,以便 CPU 绑定的线程释放 GIL,让 I/O 线程完成它们的工作。
我希望这个故事对你有用。 订阅 到我的邮件列表如果你想知道更多这样的故事。
喜欢这个故事吗?通过我下面的推荐链接成为一个媒体成员来支持我的写作。无限制地访问我的故事和许多其他内容。
https://medium.com/@diego-barba/membership
Python Conda 环境,适用于 M1 苹果芯片上的 arm64 和 x86_64
在 M1 苹果芯片上的 arm64 和 x86_64 Python 依赖项之间无缝切换

介绍
新款 MacBook Pros 中 M1 苹果芯片的发布可以被认为是芯片技术最大的时代飞跃之一。新的 M1 芯片具有更高的功率和计算效率,基于 arm64 架构,不同于前几代的英特尔 x86_64 芯片。
虽然这一变化引发了许多兼容性问题,但 M1 芯片的单线程和多线程性能明显优于其英特尔前代产品。这使得 M1 MacBook Pro 成为数据科学相关工作负载的最佳笔记本电脑之一。
像苹果发布的许多新技术一样,行业往往需要一段时间才能赶上。当我们等待开发者发布对我们所依赖的许多依赖项的原生支持时,苹果已经发布了 Rosetta 来支持仍然基于 x86_64 架构的软件。尽管如此,对于像我一样的许多 Python 开发人员来说,仍然经常会遇到与我们的 Python 环境的兼容性问题。
为此,这篇博客详细介绍了我在 M1 苹果芯片上设置 Python 环境的配置步骤。在这篇博客的结尾,你将学会如何配置 Python 来使用基于 arm64 或 x86_64 的依赖项。
出于演示的目的,我们将为tensorflow-macos设置我们的 Python 环境,因为 tensorflow-macos 只能在 arm64 Python 环境中执行。然而,这里详述的过程将允许您为 arm64 和 x86_64 环境安装 Python 包。
安装依赖项
我们需要首先安装两个特定的依赖项。
- Xcode
- 小型锻造 3
1.安装 Xcode:
若要安装 Xcode,请先打开终端会话,方法是前往“应用程序”>“实用工具”>“终端”。然后键入命令:
xcode-select --install
请注意 Xcode 的安装可能需要一段时间。
2.安装小型锻造 3
接下来,我们将安装 Miniforge3。Miniforge3 是社区(conda-forge)驱动的极简主义conda安装程序。此外,Miniforge3 具有对基于 arm64 的架构的原生支持,这无疑是有利的。
要安装 Miniforge3,请从以下网址下载 shell 脚本:
确保选择 arm64(苹果芯片)架构。
https://github.com/conda-forge/miniforge
下载完 shell 脚本后,您可能需要使用以下命令来启用 shell 脚本的执行:
chmod +x Miniforge3-MacOSX-arm64.sh
之后,使用以下命令执行脚本:
sh Miniforge3-MacOSX-arm64.sh
按照提示进行操作,这将在您的机器上安装conda。我们可以使用以下命令来确认是否安装了conda:
conda --version
您应该得到如下所示的输出。

图片来自作者
Conda 设置
一旦安装了所有的依赖项,我们就可以继续为 arm64 或 x86_64 Python 环境配置conda。
我们将从在当前 shell 中添加一些快捷方式开始,以便于安装不同的conda环境。下面的代码片段将添加两个快捷函数,这两个函数将创建一个osx-64或osx-arm64 conda 环境,我们可以在其中安装 Python 包。
为此,将以下代码添加到~/.zshrc或~/.bashrc。
# Create x86 conda environment
create_x86_conda_environment () {# example usage: create_x86_conda_environment myenv_x86 python=3.9
CONDA_SUBDIR=osx-64 conda create -n $@
conda activate $1}# Create ARM conda environment
create_ARM_conda_environment () {# example usage: create_ARM_conda_environment myenv_x86 python=3.9
CONDA_SUBDIR=osx-arm64 conda create -n $@
conda activate $1}
创造康达环境
Miniforge3 的特性之一是能够为特定的 Python 环境定义特定于处理器的子目录。例如,通过设置CONDA_SUBDIR==osx-64,将指示conda从 x86_64 (osx-64)特定子目录安装软件包。
这将使用户能够根据CONDA_SUBDIR定义的值创建安装 arm64 或 x86_64 (osx-64) Python 包的环境。
让我们使用之前创建的快捷方式安装两个不同的环境,一个基于 x86_64 (osx-64),另一个基于 arm64 (osx-arm64)。
x86_64 (osx-64)
创建一个名为 env_x86 的 Python 3.9.13 osx-64 (x86_64)环境:
create_x86_conda_environment env_x86 python=3.9.13
或者,如果您选择不使用快捷方式,您可以使用命令获得与上面相同的结果:
CONDA_SUBDIR=osx-64 conda create -n env_x86 python=3.9.13
conda activate env_x86
arm64 (osx-arm64)
使用快捷方式创建一个名为 tensorflow_ARM 的 Python 3.9.13 osx-arm64 (arm64)环境:
create_ARM_conda_environment tensorflow_ARM python=3.9.13
或者,如果您选择不使用快捷方式,您可以使用命令获得与上面相同的结果:
CONDA_SUBDIR=osx-arm64 conda create -n tensorflow_ARM python=3.9.13
conda activate tensorflow_ARM
执行上述步骤将安装两个使用 x86_64 (osx-64)或 arm64 (osx-arm64)的 Python 环境。通过使用以下命令激活特定环境,可以在这两种环境之间无缝切换:
conda activate <NAME_OF_ENVIRONMENT>
张量流装置
让我们验证是否安装了 arm64 环境。为此,我们将安装tensorflow-macos。如前所述,tensorflow-macos只能在 arm64 Python 环境中执行。
注意: **tensorflow** 在 x86_64 Python 环境下无法工作
- 使用以下命令激活
tensoflow_ARM环境:
conda activate tensorflow_ARM
2.安装 arm64 特定 tensorflow 依赖项。
注: **-c** =频道
conda install -c apple tensorflow-deps
3.安装 tensorflow 库:
pip install tensorflow-macos tensorflow-metal
张量流验证
让我们验证 tensorflow 安装是否有效。
- 首先激活适当的环境并启动 python 环境。
conda activate tensorflow_ARM && python
2.导入张量流并创建常数:
import tensorflow as tf
tf.constant([1,2,3])

作者图片
3.让我们也检查一下 tensorflow 是否在您的机器上使用了 GPU 加速:
tf.config.list_physical_devices('GPU')
解决纷争
安装 tensorflow 时,您可能会遇到的一个最常见的问题是错误:
[1] 21781 illegal hardware instruction python
这个错误是由于将tensorflow-macos安装在错误的conda环境中造成的。确保您已经在 arm64 conda环境中安装了tensoflow-macos。
结论
管理 Python 依赖关系一直很困难。M1 芯片的发布只是增加了一层额外的复杂性。在行业赶上来之前,像这篇博客中详细介绍的方法这样的变通方法就足够了。
希望这篇博客中的详细说明能让你在新的 M1 MacBook 上更容易地管理 Python 的依赖性。
如果您有任何问题或疑问,请留下您的评论,我将非常乐意帮助您。



浙公网安备 33010602011771号