TowardsDataScience-博客中文翻译-2022-四十三-

TowardsDataScience 博客中文翻译 2022(四十三)

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

自然语言处理任务中模糊匹配的原始文本校正

原文:https://towardsdatascience.com/raw-text-correction-with-fuzzy-matching-for-nlp-tasks-828547742ef7

了解如何修复拼写错误的单词,以便更好地识别重要的文本表达式

Diomari Madulara 在 Unsplash 上拍摄的照片

今天,自然语言处理(NLP)被用于医疗保健、金融、营销等领域的许多 ML 任务和项目中。数据科学家经常努力清理和分析文本数据,以便获得洞察力。对于大多数自然语言处理任务,通常使用诸如标记化、词干化、词汇化等技术。

但是,在某些情况下,需要保持原始文本的完整性,而不是将其拆分成标记。例如,在作为命名实体识别(NER)的私人情况的数据去标识中,一种用于识别文档中不同实体的方法,该方法的输出显示原始文本,其中标签替换期望的实体。

在这些情况下,纠正拼写错误或错误的术语可能会很有挑战性。这篇文章将解释如何结合使用正则表达式和模糊字符串匹配来完成这项任务。

模糊字符串匹配

模糊字符串匹配是一种查找与给定字符串模式近似匹配的字符串的技术。模糊字符串匹配背后的算法使用距离度量(如 Levenshtein 距离)来计算两个字符串之间的差异,方法是确定将第一个字符串转换为第二个字符串所需的最少更改次数。我们将使用 python 库 Fuzzywuzzy 来执行这项任务。

安装和示例:

pip install fuzzywuzzyfrom fuzzywuzzy import fuzzfuzz.ratio("appls","apples")#91

在这个例子中,我们得到了 91 分的相似度,所以单词非常相似。现在,我们可以考虑使用什么阈值来决定是否“纠正”原始单词。

正则表达式

RegEx 是正则表达式的缩写,是一种特殊的文本字符串,用于指定文本中的搜索模式。这种模式基本上是一种语言,它确切地定义了在文本字符串中要寻找什么。例如,如果我们想提取除数字以外的所有字符,正则表达式模式将是:

[^0-9]+

如果我们想要提取所有的电子邮件地址,正则表达式模式将是:

[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}

我们将使用 python 库 re 来执行这项任务。

安装和示例:

pip install reimport restring = "my e-mail is example@email.com"pattern=r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}'print(re.search(pattern,string).group())# example@email.com

原始文本校正

回到最初的问题,当我们需要修复错误的单词或短语,但保持原始文本完整,而不是将其拆分为标记时,该怎么办。保持文本完整也意味着保持完全相同的间距、制表符、换行符、标点符号等。

假设在一个 NER 任务中,我们想要标记医院里给病人的药物。该信息可在电子健康记录(EMR)系统的医生笔记部分获得。例如,这里有一个条目:

病人 XXX 上周住院了。

他被赋予了勇气。

他是男性,65 岁,有心脏病史。

该药物的正确名称是“莫昔普利”。为了纠正这一点,我们需要:

  1. 准备一个我们想要搜索的关键字列表,在这种情况下只有一个关键字
  2. 决定相似性阈值(默认为 85)
  3. 将文本拆分成标记
  4. 在关键字和每个标记之间运行模糊匹配
  5. 如果相似性得分超过预定阈值,则用关键字替换标记
  6. 把它们放回原处

我们可以使用如下函数来实现这一点:

from fuzzywuzzy import fuzz
import redef fuzzy_replace(keyword_str, text_str, threshold=85):
    l = len(keyword_str.split())
    splitted = re.split(r'(\W+)',text_str) #split, keep linebreaks
    for i in range(len(splitted)-l+1):
        temp = "".join(splitted[i:i+l])
        if fuzz.ratio(keyword_str, temp) >= threshold:
            before = "".join(splitted[:i])
            after = "".join(splitted[i+l:])
            text_str= before + keyword_str + after
            splitted = re.split(r'(\W+)',text_str)    
    return text_str

运行此函数后,文本输出现已得到纠正,文本原始结构得以保留:

病人 XXX 上周住院了。

他被给予莫昔普利。

他是男性,65 岁,有心脏病史。

现在让我们看一个更复杂的例子。这一次,医疗记录中包含了一些我们想要纠正的不同的药物,而且有些在文本中出现了不止一次。为了解决这个问题,我们定义了一个包含所有正确药物名称的列表,并简单地遍历文本以找到需要纠正的内容。以下代码片段显示了如何实现这一点:

meds = ["moexipril", "vasotec", "candesartan"] text = """The patient XXX was hospitalized last week.He was given moxiperil and vasotek.He is male, 65 years old, with a history of heart disease.Patient has been taking vasotek for several years.In the past was given candasarta.""" for med in meds: text = fuzzy_replace(med, text)

结果是所有药物名称均已更正的相同文本。

病人 XXX 上周住院了。

他接受了莫昔普利和 vasotec 治疗。

他是男性,65 岁,有心脏病史。

患者服用 vasotec 已有数年。

曾被给予坎地沙坦。

我已经决定不包括强制转换成小写的文本,因为有些时候人们希望保持原来的大小写,例如识别缩写。然而,这可以通过将参数的小写形式输入到函数中很容易地完成,就像这样— fuzzy_replace(med.lower(),text.lower())

结论

我们可以使用模糊字符串匹配和正则表达式的组合来纠正错误的单词或短语,并保持原始文本不变。当处理不同的 NLP 任务(如 NER)时,这种操作是可取的。

rbokeh:如何在 R 中创建交互式情节

原文:https://towardsdatascience.com/rbokeh-how-to-create-interactive-plots-in-r-cf8fd528b3d5

布拉姆·瑙斯Unsplash 上拍摄的照片

简介

数据可视化是人工智能的一个重要方面。数据可视化使您能够从数据中获得洞察力,并使您能够与他人就数据进行交流。我们可以使用许多软件包来可视化数据并构建有意义的仪表板。在用 Python 做可视化的时候,我们有不同的库,比如 Matplotlib,Seaborn,Altair 等等。,和 ggplot2,使用 r 时晶格。

数据可视化中的交互性使它们更上一层楼。当用户可以使用不同的操作与图表进行交互时,如缩放、悬停和使用选择的变量过滤绘图,这为图表增加了很大的灵活性。这种交互性允许用户更深入地挖掘可视化以在数据集中找到附加信息。使用不同的工具在仪表板中提供交互式可视化是当今的常见做法。

Python 和 R 都有构建交互情节的包。Bokeh 是一个流行的交互式可视化 python 包。散景库允许我们用很少的代码行创建不同的图,为高级定制增加了灵活性。Bokeh 现在有一个 R 接口以及 Python、Scala 和 Julia 的现有接口。

rbo keh 是什么?

rbokeh 是一个开源的 R 包,它利用了 bokeh 可视化工具。它提供了一个声明性的界面,对于基于 web 的动态可视化是灵活的。Ryan Hafen 创建并维护 rbokeh 包。你可以在这里找到更多关于 rbokeh 包的细节。在安装 rbokeh 之前,你必须安装 R and R 工作室。还可以用 Kaggle 或 Google Colab 构建 rbokeh 可视化。

安装

首先,我们将使用 R 函数 install。packages()'从 CRAN 获取 rbokeh 包。

install.packages(“rbokeh”)

我们将使用以下命令来导入 rbokeh 包:

library(“rbokeh”)

对于 rbokeh 包,我们还将导入其他 R 库

library(“tidyverse”)
library(MASS)

使用 rbokeh 的可视化

在进行图形可视化之前,需要注意的是 rbokeh 图是通过调用figure()函数生成的。

这个函数相当于一个可以设置的空白画布,然后可以使用管道操作符添加层。这里 x、y 和 ly_geom()是指定所用 geom 类型的数据输入,如 ly_points、ly_lines、ly_hist、ly_boxplot 等。

对于本教程,我们将从 r 的质量包中加载内置的 cars93 数据集

照片由克雷格·冯尼克Unsplash 上拍摄

Cars93 是一个 93 行 27 列的数据集。关于封装和文档的更多细节可以在这里找到。数据集中的汽车是从发表在《消费者报告》和《PACE 购买指南》上的 1993 款乘用车模型中随机选择的。

使用以下命令,我们可以打印“Cars93”数据集的前几行:

head(Cars93)

散点图

我们将首先构建一个简单的散点图。在这里,我们将看到不同汽车的马力和价格之间的关系。散点图有助于可视化两个变量之间的关系,因此,是可视化所有数据点的好选择。首先,我们将创建一个“figure()”函数。然后,我们将创建一个名为“ly_points”的图层,并将以下参数传递给数据参数:-x 轴上的马力-y 轴上的价格-以及数据集,即 Cars93。请注意,我们可以将生成的图形分配给“散点图”,然后通过写“散点图”来显示它。我们需要使用hover命令来查看添加的工具提示。平移和缩放也可以作为交互式元素访问。

#Simple Scatter Plotscatter_plot <- figure(title ="Scatter Plot") %>%
  ly_points(x = Horsepower, y = Price, data = Cars93, hover = c(Horsepower, Price))
scatter_plot

图片来源:作者

为了构建一个散点图,除了显示与马力和价格的关系之外,还显示每个原点的一个点,我们将按颜色对其进行分组,如下所示。

#Scatter Plot with groupingscatter_plot1 <- figure(title ="Scatter Plot") %>%
  ly_points(x = Horsepower, y = Price, color = Origin, data = Cars93, hover = c(Origin, Horsepower, Price))
scatter_plot1

图片来源:作者

折线图

接下来,我们将使用 Cars93 数据集绘制一个基本的折线图。假设我们想要查看小型和紧凑型汽车价格的变化,我们将首先使用类型变量过滤数据。

#Filteringdf <- Cars93 %>%
filter(Type %in% c(“Small”, “Compact”))

然后对于线图,我们将使用价格变量,并用“ly_lines”层中的数据参数指定它,如下所示:

#Line Plotline_plot <- figure(title =”Line Plot”) %>%
ly_lines(Price,color=Type,data = df)
line_plot

图片来源:作者

我们可以改变线条的粗细和颜色。根据默认主题为所有这些地块选择颜色。我们可以用颜色向量或预定义的调色板调用set_palette()函数来获得我们想要的颜色。这里我们将使用 discrete color 属性,因为我们正在处理分类值,为了获得更粗的线条,我们正在更改 width 参数的设置,如下面的代码所示。我们也可以使用十六进制代码的颜色或任何我们在这里选择的 CSS 颜色。

#Changing the color and width of linesline_plot2 <- figure(title =”Line Plot”) %>%
ly_lines(Price, color = Type, width = 2, data = df)%>%
set_palette(discrete_color = pal_color(c(“#6200b3”, “#ff0831”,”#226f54")))
line_plot2

图片来源:作者

现在,我们将探讨如何通过在一个图形中组合点图层和线图层来可视化多层绘图。对于多层绘图,我们将使用ly_lines() 绘制线条,并使用ly_points()添加标记。注意,在这两层中,“类型”都被映射到颜色。可以为图形使用不同的字形。我们可以使用下面的命令来探索字形可用的可能值。

point_types()

图片来源:作者

当在代码中指定一个带编号的标志符号时,fill 和 line 属性将得到有效的管理,以获得所需的结果。

#Multi-layer plot with glyphmulti_layer <- figure(df, legend_location =”top_right”, title =”Multi-layer Plot”) %>%ly_points(Price, color = Type, hover = c(Price,Type),glyph=16) %>%ly_lines(Price, color = Type, width = 1.5, data = df) %>%set_palette(discrete_color = pal_color(c(“#6200b3”, “#ff0831”,”#226f54")))multi_layer

图片来源:作者

直方图

接下来,我们将绘制一个直方图来查看 Cars93 数据集中的 RPM 频率。

#Simple Histogramhistogram <- figure(width = 700, height = 400, title =”Histogram”) %>%
ly_hist(RPM, data = Cars93, breaks = 5, freq = TRUE,color=”green”)
histogram

图片来源:作者

方框图

现在,我们将使用以下代码创建一个方框图来显示马力和气缸之间的关系。我们还可以通过箱线图发现数据集中的一些异常值。

#Box-Plotbox_plot <- figure(width = 600, title =”Box Plot”) %>%
ly_boxplot(Cylinders, Horsepower, data = Cars93)
box_plot

图片来源:作者

条形图

我们将在下一个柱状图中绘制各种类型的汽车,即分类数据。

#Bar Chartbar_chart <- figure(title =”Bar Chart”) %>%
ly_bar(Type, data = Cars93) %>% theme_axis(“x”, major_label_orientation = 90)
bar_chart

图片来源:作者

网格图

我们可以使用网格图在一个布局中显示不同的图形。我们可以将不同类别的数字组合起来,如条形图、折线图、散点图等。在网格布局中。

#Grid Plottools <- c(“wheel_zoom”, “box_zoom”, “box_select”)
p1 <- figure(tools = tools, width = 500, height = 500) %>%ly_points(Horsepower, RPM, data = df, color = Type,hover=c(Horsepower, RPM,Type))p2 <- figure(tools = tools, width = 500, height = 500) %>%ly_points(Length, Wheelbase, data = df, color = Type,hover=c(Length, Wheelbase,Type))grid_plot(list(p1, p2), link_data = TRUE)

图片来源:作者

Hexbin 图

最后,我们绘制了一个赫克斯宾图,这是一个 2D 密度图,以可视化两个数字变量之间的关系,即价格和发动机大小。当数据包含大量点时,Hexbin 图通常用于可视化。绘图窗口用几个十六进制箱分割,以避免重叠。

hexbin <- figure() %>% ly_hexbin(x = EngineSize, y = Price, data = Cars93)
hexbin

图片来源:作者

就是这样!我们从头开始在 rbokeh 中创建和定制了各种图表。

结论

在这个简短的教程中,我们探索了如何在 rbokeh 中绘制一些交互式的、令人愉快的图表。这个 rbokeh 教程的完整代码可以在我的 GitHub 资源库中找到。如果您喜欢使用 ggplot,那么您可能以前使用过 plotly 或 ggiraph 来使您的 ggplot 图表具有交互性。对于您的下一个可视化项目,rbokeh 包可能是一个不错的选择。

参考文献:

罗宾·h·洛克(1993) 1993 年新车数据,统计教育杂志,1:1,DOI:10.1080/10691898 . 1993 . 11910459

React.js —基本挂钩(使用状态、使用效果和使用上下文)

原文:https://towardsdatascience.com/react-js-basic-hooks-usestate-useeffect-usecontext-1ed82a799db2

对 React.js 三个基本的、也是最重要的钩子的简单介绍

埃菲社在 Unsplash 上拍摄的照片

前言——从本文的标题可以推断出,这并不是一个全面的指南,介绍 React.js 新版本中可以使用的所有 钩子,而是对大多数使用 React.js 的人最有可能遇到的 基本 钩子的概述。如果您对可以“挂钩”到 React.js 组件中的所有挂钩的完整列表感兴趣(截至本文发布时的版本 16.13.1),请参考 React.js 文档https://reactjs.org/docs/hooks-reference.html*。此外,如果你不熟悉 React.js,我可以建议你看看 YouTube 上的Ben Awad的* 实用 React 系列,作为你的教育努力的入门,因为本文假设你事先了解 React.js。话不多说,我希望你喜欢它!

React.js 钩子是什么?

要直接引用 React.js 文档,

钩子是 React 16.8 中的新增功能。它们让你不用写类就可以使用状态和其他 React 特性…钩子是一个特殊的函数,它让你“挂钩”React 特性。

React.js 文档

传统上,在 React.js 中,由于 React.js 作为 UI 库与 JavaScript 交互的性质,需要基于类的组件来利用生命周期方法和状态。不过在 React.js 的新版本中,使用 React.js 在前端工作的程序员现在可以创建带有钩子的功能组件 ,它们与基于类的组件相似,但是:

  • 更容易测试 —尽管测试在个人项目中的影响不如在高度可用、公开部署的应用程序中的影响大,但它是任何组织的软件开发生命周期的重要部分。无论这是以持续集成/持续开发服务的形式出现,如 CircleCITravisCI ,还是直接通过库测试个性化功能,如 JestSelenium ,钩子使得测试 React.js 组件比测试基于类的组件容易得多。
  • 更容易阅读——这对有许多团队成员的项目特别有帮助。尽管人们可能会假设团队在如何构建 UI 方面可以遵循特定的设计语言、方法和样式指南,但是去除基于类的组件架构的脂肪允许工程师重新编写位于 JavaScript 语言核心的程序,而不是抽象出不必要的东西。
  • 更接近传统 JavaScript——如上所述,编写功能组件减少了用 JavaScript 编写“特殊函数”(类)的工作量。这并不是说用 JavaScript 编写基于类的组件是一件坏事,因为 JavaScript 由于其多范例方法在技术上可以用作面向对象的语言,但这是因为 JavaScript 如何处理类以及从这种类声明中实例化的对象。这就是 JavaScript 的基本模式和原则发挥作用的地方,比如原型链和继承,虽然不在本文的讨论范围之内,但它们是 JavaScript 非常重要的特性。另一件事是,如果您使用基于类的组件,您总是必须从内置的 React.js 类扩展,而功能组件是纯 JavaScript 函数,其中 JSX 和钩子由 React.js 通过导入来识别。
  • 最佳实践 —软件工程师必须接受的一个事实是,他们今天在自己运营和/或工作的公司中利用的技术,在不远的将来很可能会变得自满,这是好事,还是坏事,取决于你的个人哲学。当然,也有例外:20 世纪 70 年代的 SQL,80 年代的 C++和 Python,但是随着技术的进步和抽象层覆盖在我们今天使用的工具上,更新的 API 出现了,为了构建一个产品,必须在思想上消化并在物质上实现这些 API。没有一个单独的实体掌握着未来技术是什么的答案,但是你可以通过观察谁在构建未来来获得一个很好的想法,比如 React.js 的脸书
  • 代码密集程度较低——这也可以归入“更容易阅读”的范畴,但我的直觉告诉我,基于我之前提到的软件工程中的一个范例,将它划分为自己的要点;抽象。虽然你通常必须用一些性能来换取抽象的实现,但它通常是产品发展的氧气。在我们的案例代码中,将曾经复杂、功能稀疏、在许多实例中重复的东西转化为更容易实现的解决方案,提供整体更好的工程体验,并产生与以前的迭代相同(如果不是更好的话)的方法,这是任何产品生命周期背后的主题。React.js 也不例外。

随着本文的进展,我将讨论三个“基本的”React.js 挂钩,作为构建 UI 的工程师,您很可能会遇到这三个挂钩。它们是:

  • 使用状态()

它声明了一个“状态变量…这是一种在函数调用之间“保留”一些值的方法——useState是一种新的方法,可以使用 *this.state* 在类中提供的完全相同的功能。通常,当函数退出时,变量“消失”,但是状态变量被 React 保留。

React.js Docs

  • useEffect()

如果你熟悉 React 类的生命周期方法,你可以把 *useEffect* 钩子想象成*componentDidMount**componentDidUpdate*,以及 *componentWillUnmount* 的组合。**

React.js Docs

  • 使用上下文()

在典型的 React 应用程序中,数据是通过 props 自顶向下(从父到子)传递的,但是对于应用程序中许多组件需要的某些类型的 props(例如,区域设置首选项、UI 主题)来说,这种用法可能很麻烦。上下文提供了一种在组件之间共享这些值的方法,而不必显式地在树的每一层传递一个属性。

React.js 文档

入门指南

使用状态

useState(),如上所述,将状态挂钩到组件中。很容易整合到功能组件中:

useState()的示例

在上面的截图中,我们可以注意到一些事情。useState()有两个常量,您应该在调用 useState()钩子时认识到:

  • 状态数据结构/类型本身(例如— state)将保存该状态实例的初始值。您只能向 useState()传递一个参数,但是您可以多次调用 useState(),您将在阅读材料中看到进一步的概述。
  • 在组件生命周期的特定点执行的 setter 函数(例如— setState),用于更新状态数据结构/类型的值。

在 React.js 文档中,关于上面例子中看到的数组析构,还有一点需要注意:

当我们用 *useState* 声明一个状态变量时,它会返回一个 pair——一个包含两项的数组。第一项是当前值,第二项是让我们更新它的函数。使用 *[0]* *[1]* 来访问它们有点令人困惑,因为它们有特定的含义。这就是我们使用数组析构的原因。

React.js 文档

需要注意的一点是,你可以随意调用这两个变量,[state, setState]。您会遇到两种常见的命名约定,大多数工程师都会使用:

  • 将所有内容封装在一个 JavaScript 对象中,并将所有状态属性分配给各自的默认值。如果你以前使用过基于类的 React.js 组件,你会非常熟悉。这两个常量被命名为statesetState是很常见的,但是同样,您可以随意命名它们。在管理状态时,我给这种方法起了一个绰号,我将在本文的其余部分提到它,它就是“垂直缩放的组件状态”。示例:
**const [state, setState] = useState({
  loading: true,
  error: '',
  signUpFormFields: {
    username: '',
    password: ''
  },
  scoreCount: 0
});**
  • 将析构数组中的第一个常量命名为组件状态中需要跟踪和更新的部分的大小写形式(例如,——loading, error, isAuthenticated, signupFormFields, etc.)。然后,将第二个常量命名为与第一个相同的名称,但是在命名约定前加上 set- ,并适当调整大小写以遵循骆驼大小写的惯例。在管理状态时,我给这种方法起了一个绰号,我将在本文的其余部分引用它,它就是“水平缩放的组件状态”。示例:
**const [loading, setLoading] = useState(true); // boolean
const [error, setError] = useState(''); // string
const [signUpFormFields, setSignUpFormFields] = useState({
  username: '',
  password: '' ,
}); // object
const [value, setValue] = useState(0); // int**

两者做的事情完全一样(大部分情况下),尽管它们都有各自的优缺点:

  • 当谈到水平缩放状态时,一个负面的影响是您会发现自己必须运行更多的 setter 函数调用(例如— setState())才能将组件的状态更新到期望的结果。确实存在减轻这种情况发生的解决方案,比如使用 useReducer()钩子,但是这超出了本文的范围。如果你正在寻找实现这个解决方案的教程,我强烈推荐 YouTube 上的 Harry Wolff 的视频,他在深入使用 useReducer()钩子方面做了令人难以置信的工作。当您使用异步调用、处理承诺或者使用像 fetchaxios 这样的库对可能直接影响组件状态的 API 执行 CRUD 操作时,水平缩放组件状态的一个好处是。这样做的原因与您将从这些服务中检索多少数据有关,重写或复制整个垂直缩放的状态对象的成本将远远高于只对应用程序逻辑的这一部分进行一次互斥的 useState()调用的成本。
  • 垂直缩放状态的好处是,无论何时想要更新 JavaScript 对象,只需调用一个 setter 函数。但是,有些缺点是,除非您更新 JavaScript 对象中的每个属性,否则您将不得不利用 JavaScript 的扩展语法,以便不会将更改的状态值重置回它们在 useState()钩子中定义的原始声明。这个问题也存在于水平缩放的组件状态中,但是扩展操作的成本通常要低得多,因为您通常只处理非常小的更改。如果这听起来有点混乱,如果我拿上面的垂直缩放的例子并想更新loading: false的话,下面是你必须做的事情:
**const [state, setState] = useState({
  loading: true,
  error: '',
  signUpFormFields: {
    username: '',
    password: ''
  },
  scoreCount: 0
});setState({ error: 'A new error entry!' });/*
   If you wanted 'error' to persist its newly set state value ('A new error entry!'), you will have to use the spread operator the next time you updated your state (as I do below).Something to take note of: We don't have to use the spread operator in the first setState() invocation update above because there haven't been any previous changes to the initial state values.
*/setState({ ...state, loading: false });/*
   Your updated state object would now look like this:{
     loading: false,
     error: 'A new error entry!',
     signUpFormFields: {
       username: '',
       password: ''
     },
     scoreCount: 0
   }*/**

如果您正在寻找一个关于应该水平还是垂直缩放状态的最佳实践,React.js 文档建议使用多个状态调用来代替单个状态(即,使用水平缩放的组件状态):

然而, 我们建议将状态拆分成多个状态变量,基于这些变量的值往往会一起变化。

React.js 文档

这背后的原因主要与 React.js 组件的记忆和它们处理的状态有关。这基本上是一种奇特的说法,即您应该通过缓存一次初始调用来优化高成本函数调用的速度,然后只从缓存中提取已更改的数据。这种方法意味着您很可能不得不加入更复杂的 React.js 挂钩,比如 useMemo() 和/或 useCallback() ,这超出了本文的范围。如果你正在寻找学习如何利用这些更复杂的 React.js 钩子的资源,我会推荐 YouTube 上的 Ben AwaduseMemouseCallback 教程。

使用效果

如前所述,useEffect()通过一次函数调用将主要概念componentDidMount()componentDidUpdate()componentWillUnmount()挂钩到一个组件中。很容易整合到功能组件中:

useEffect()挂钩

在上面的截图中,我们可以注意到一些事情。有两个参数传递给 useEffect():

  • 一个匿名的回调函数,存放你的 useEffect 逻辑。这个逻辑的执行基于如何设置 useEffect()来运行(我们将在下面进一步讨论)。
  • 第二个是一个数组,接受逗号分隔的变量,称为依赖列表。这就是改变 useEffect()操作方式的方法。

重要提示:如果不传递 useEffect()钩子中的第二个参数(即依赖列表),那么钩子将在每个渲染上运行——如果/当你将这个钩子与 useState()之类的东西结合使用时,这可能会有问题,因为你的组件可能会陷入一个重新渲染循环,其中;首先,组件在初始渲染时运行 useEffect()钩子。然后,useState()中的一些数据通过上面在 useEffect()钩子中描述的 setter 函数进行更新。更新后,组件会因为状态更新而重新呈现,并再次执行相同的 useEffect()钩子。我将在下面讨论如何使用 useEffect()来防止这种情况。

尽管 useEffect()确实利用了componentDidMount()componentDidUpdate()componentWillUnmount()的各个方面,但是最好不要把 useEffect()看作是一个钩子,它复制了从这三个生命周期方法中的每一个获得的功能,并将它们压缩成一个可以跨组件内的许多实例调用的函数。

相反,可以将 useEffect()看作是一个函数,它在渲染之前(通常称为“清理”)、渲染之后以及组件卸载之前为组件执行特定的任务。

由于 useEffect()可以以多种不同的方式使用,其中大部分我不会在本文的范围内讨论(不必担心,我将在下面提供更多关于钩子的边缘情况的参考资料),如果我对这个特定的 React.js 特性使用 Pareto 原则,我将只讨论 useEffect()实现 80%时间的 20%的方式。

下面是实现 useEffect()挂钩的更常见的方法:

  • 要使 useEffect()调用仅在每次装载和卸载时运行,请按以下方式使用 useEffect()挂钩:
**useEffect(() => {
   // some component logic to execute...
}, []);/*
  Notice the empty array as the second argument above. We don't pass anything to the array as we don't was useEffect() to depend on anything - thus the purpose of having the dependency list in the first place.
*/**
  • 为了使 useEffect()调用运行得更少或更多,通常基于该 useEffect()调用依赖于什么(即,传递给依赖列表的内容),以下列方式使用 useEffect()挂钩:
**const [value, setValue] = useState(0);useEffect(() => {
  // some component logic to execute...
}, [value, setValue]);/*
  Notice the dependency array as the second argument above. We pass 'value' to the array as an example to showcase how this hook can work. This useEffect() invocation will execute every single time 'value' is updated.Another thing to mention is that arguments in the dependency list don't have to come from other hooks like they do in this example - they can be other forms of data that are assigned to a particular variable where the underlying assigned values can be/are mutated.
*/**

就像 useState()一样,您可以根据组件的需要使用任意多个 useEffect()实例。

钩子让我们根据代码正在做什么来分割代码 而不是一个生命周期方法名。React 将按照指定的顺序应用组件使用的每个效果。

React.js 文档

如果您需要对上面提供的两个示例中没有涉及的 DOM 进行定制修改,我建议您查阅 React.js 文档,主要关注部分是“效果的定时”

如果你正在寻找一个关于 useEffect()钩子的全封装资源,可以解决你在这个钩子上遇到的所有问题,那么有一篇文章我会推荐你去读,就像读 React.js 福音一样:use effect完全指南。由 Redux、Create React 应用程序的合著者丹·阿布拉莫夫撰写,他是一位全能的不可思议的工程师,作者深入研究了这种特殊挂钩必须提供的细微差别和功能,同时提供了易于遵循的示例。我无法推荐比这篇文章更好的关于这个钩子的资源和人了。

使用上下文

前言—在发表本文时,还有另一个针对全局状态管理的 React.js 解决方案,目前正处于开发的试验阶段。这个包被称为 反冲 似乎是 React.js 上下文 API 之类的东西和ReduxMobX之类的不可知全局状态管理工具之间的交叉,同时为如何管理前端的全局状态带来了新的设计模式。我不会在本文的范围内讨论这个库,但是我觉得有必要提一下,因为该产品将来可能会发展到成为实现 React.js 前端全局状态管理解决方案的最佳实践。**

React Context API 可能是 16.8 更新中我最喜欢的新增功能之一,它是一套 API 特性,提供了一个可变的全局状态数据结构,可以在整个组件树中的任何点上挂钩到组件,从而避免了 React.js 反模式(称为 prop drilling)。

以下面的 React.js 组件树架构为例:

React.js 组件树示例

假设您在 Authenticated 文件夹(components/Authenticated)中的index.jsx文件中有一些状态逻辑,并且您想将这些数据传递给 Section3 组件(components/Authenticated/Dashboard/Body/section 3)。在 React Context API 之前,您必须用 props“钻取”每个中介组件,即使您在到达理想的祖先组件之前不打算在该组件中使用该 prop 数据。这可能不是沿组件树向下传递数据的最合适的解决方案,原因有几个,Kent C. Dodds 关于这个主题的博客文章完美地总结了所有这些原因:

It [prop drilling]不可避免地会给你的应用程序带来一个非常混乱的数据模型。任何人都很难找到数据在哪里被初始化,在哪里被更新,在哪里被使用。

回答“我可以在不破坏任何东西的情况下修改/删除这段代码吗?”在那样的世界里是很难回答的。这是你在编码时应该优化的问题。

但是随着应用程序的增长,您可能会发现自己要钻透许多层的组件。当你最初写出来的时候,这通常没什么大不了的,但是在代码已经工作了几个星期之后,对于一些用例来说,事情开始变得难以处理:

-重构一些数据的形状(即:*{user: {name: 'Joe West'}}*->*{user: {firstName: 'Joe', lastName: 'West'}}*)**

-由于(重新)移动了一个需要一些道具但不再需要的组件,导致过度转发道具(传递了多余的道具)。

-欠转道具+滥用 *defaultProps* 这样你就不会意识到道具丢失了(也是因为(重新)移动了一个组件)。

——中途重命名道具(即 *<Toggle on={on} />* 渲染 *<Switch toggleIsOn={on} />* )让你的大脑很难记住这些。

还有各种各样的其他情况,特别是在重构过程中,道具演练会带来一些真正的痛苦。

肯特·c·多兹的博客

这就是 React 的上下文 API 试图缓解出现的任何问题的地方。您可以将 React 的 Context API 视为与 Redux 或 MobX 等众所周知的全局状态管理工具类似的解决方案,但是开销和样板文件要少得多,并且 React.js 的方法更接近于 React . js,而不是与您使用的前端库/框架无关的工具。

当然,这两种解决方案各有利弊:

  • 例如,如果应用程序中的状态不断变化,或者如果您可以采用更大的包大小,因为您知道您的产品将从更全面的全局状态管理套件中受益更多,那么 Redux 或 MobX 比 React Context API 更有意义。因为一个语境。Provider 的行为基本上类似于一个数据类型/结构(即垂直缩放的状态),可以在组件树中的任何位置访问,如果您在一次 Context.Provider 调用中存储大量不断更新值的属性,更新也会变得非常昂贵
  • 另一方面,如果您的应用程序较小,只需要很少与全局状态交互,或者如果您使用多次上下文调用。Provider(不是最好的解决方案——您应该只使用 React 的上下文 API,如下所述)来存储全局状态的不同部分,那么使用上下文 API 可能比 Redux 或 MobX 更有意义。

当不同嵌套层次的许多组件需要访问某些数据时,主要使用上下文。请谨慎使用它,因为它使组件重用更加困难。

如果只是想避免一些道具通过很多关, 组件构成 往往是比上下文更简单的解决方案。

React.js 文档

既然我们已经介绍了 React.js 上下文 API 的一般概述、它解决的问题以及它的“竞争”,下面是一个使用上下文 API 时提供者/消费者关系的示例,以及如何处理更新:

src/providers/some provider/index . jsx

src/index.js

上面的 useEffect()钩子+ SomeContext 更新

上面的照片提供了一个如何使用上下文的例子。提供程序与函数 createContext()和钩子 useContext()并行。如果这些照片有点难以理解,你可以用一种潜在的更容易理解的方式来思考:

通过 React.js 中的createContext()函数创建一个上下文对象(第一张照片中的第 3 行)。这个对象总是带有一个提供者组件(从第一张照片的第 5 行中的对象析构可以看出)和一个消费者组件。提供者允许使用者订阅对上下文对象的更改。提供者组件有一个名为value(第一张照片中的第 14 行)的属性,它接收提供者希望传递给后代消费者组件的数据。在上面的例子中,我们将statesetState变量从提供者的父组件中的 useState()钩子向下传递到任何消费者组件,这样可以发生两件事;首先,消费者组件可以通过state访问最初存在于提供者的父组件状态中的所有值。第二,通过我们传递的setState setter 函数,他们能够在封装组件树中的任何点改变state数据(我正在改变第三张照片中的数据,如第 14-18 行所示)。

然后,提供者可以在组件树中以任何级别多次包装任何组件;在第二张照片中,您可以看到我将提供者组件包装在整个应用程序中。这将提供者和它可以向下传递以供消费的数据放在组件树的顶部。如果我想嵌套 Provider 组件,我也可以这样做。理论上,对于不能将一个提供者组件包装在一个消费者组件上没有限制——您甚至可以将多个提供者包装在一起,并允许消费者组件消费多个嵌套上下文(有关这方面的更多信息,请参见 React.js 文档中的'消费多个上下文)。

你可能会注意到,我没有使用上下文。用于处理消费者的消费者财产。你只需要使用上下文。如果您想访问上下文对象本身,而不是 Context.Provider,请使用 Context 对象的 Consumer 属性。通过value支柱传递statesetState的提供者。这样,我们不仅可以访问状态,还可以优雅地更新状态。如果你只是想让你的上下文对象被消费而不是变异,你可以把 useState()钩子中的所有东西直接放到 createContext()函数中,而不是放在null中。

如果您需要更多地了解 React.js 上下文 API,我强烈推荐几个视频资源:

总而言之,与功能组件并行使用的基本 React.js 钩子提供了一种惊人的、直观的、完全令人愉快的方式来用 JavaScript 构建通用的、可伸缩的 UI。理解这三个基本的钩子将为更复杂的,甚至是定制的钩子以及它们的功能打下坚实的基础,当你在未来的工作中学习和使用它们的时候。

如果你发现了任何错误,请留下评论,我会尽快回复你。

感谢您的时间和关注。

使用 Python 中的 PyMuPDF 读取多列 PDF

原文:https://towardsdatascience.com/read-a-multi-column-pdf-using-pymupdf-in-python-4b48972f82dc

NLP 工具

逐步介绍 OCR 的奇妙世界(附图片)

照片由 Jaizer CapangpanganUnsplash 拍摄

OCR 或光学字符识别是用于从图像或文档中自动提取文本的技术。在这些图像和文档中找到的文本可以是任何键入的、手写的、显示在某种屏幕上的,或者可能是以上所有的!

OCR 最初用作数据输入的高级形式,最常用于将纸质文物中的文本数字化,“以便它们可以被电子编辑、搜索、更紧凑地存储、在线显示,并用于机器处理,如认知计算、机器翻译、(提取的)文本到语音、关键数据和文本挖掘”[1]。例如,在我的工作中,我依靠 OCR 技术将非结构化和半结构化数据转换成足够清晰的格式,以便放入电子表格或数据库中。

OCR 研究包括模式识别、人工智能和计算机视觉。[1]虽然有许多商业产品提供 OCR 服务,如 Amazon Textract 和 ABBYY,但没有什么比用我们最喜欢的编程语言 Python 编写的免费解决方案更好的了。

今天,我们将与 PyMuPDF(也称为 fitz)一起尝试 OCR。

快点,我们跳进去吧!

装置

在我们启动 jupyter 笔记本之前,让我们确保我们的机器上已经安装了软件包。要安装 pyMuPDF,请在终端中键入以下内容:

pip install pymupdf

然后,让我们启动一个 jupyter 笔记本,键入以下内容来导入 PyMuPDF 包。

接下来,让我们设置全局变量。请确保将“replace _ this _ with _ your _ own _ path _ to _ file”替换为您的 pdf 文件的路径,或者您可以下载我在这里使用的示例文件。

数字化 pdf 文件是可搜索的 pdf 文件。要确定 pdf 是否可搜索,请打开一个 pdf 文档,按下CTRL+F并键入文档中出现的单词。如果程序能找到这个词,它就是可搜索的。否则,它可能是一个扫描的 pdf。正如我们将在后面看到的, pymupdf 不支持扫描的 pdf

可搜索(数字化)pdf 文档的示例。作者截图。

不可搜索(扫描)的 pdf 文档示例。作者截图。

文本提取:“文本”

使用 PyMuPDF 从可搜索的 pdf 中提取文本非常容易。在 jupyter 笔记本的单元格块中键入以下内容,然后观看神奇的事情发生:

我们使用with fitz.open(DIGITIZED_FILE) as doc:,这样我们就不用担心用close()关闭文件。接下来,如果有多个页面,我们使用一个for循环遍历 pdf 文档中的所有页面。文本的实际提取发生在代码块的核心部分,text = page.get_text()。在我们的例子中,我们使用默认参数(“text”)。下面是print(text)的结果:

作者截图。

如果我们使用text = page.get_text("text"),也会得到相同的结果。其他选择包括“块”和“词”有关 get_text 方法选项的完整列表,请访问文档。[2]

page.get_text()参数。作者截图。

有趣的是,pyMuPDF 成功地以自然阅读顺序阅读了文档,即使有两列。

PyMuPDF 从左到右阅读文档。作者截图。

PyMuPDF 无缝地读取这两列。作者截图。

文本提取:“块”

默认参数输出一个字符串。但是,其他参数返回不同的格式,如列表和元组。例如,page.get_text("blocks")返回元组的列表。让我们试一试:

上面,我们在page.get_text()里面加了"blocks"。结果如下:

作者截图。

根据文档,的输出是:

作者截图。

其中前四项是边界框坐标,第五项是文本本身(用换行符或“\n”分隔多行),第六项是块号,第七项是块类型(0 表示文本,1 表示图像)。

作者截图。

如果你需要知道页面上的什么地方,额外的信息是有益的。例如,如果您需要绘制边界框或高亮显示特定的块。

作者截图。

文本提取:“单词”

让我们试试“单词”参数,只是为了好玩:

结果显示,参数“words”返回一个元组的列表,该列表逐字表示文本。

作者截图。

单词“sit”被标注为该页第一块的第三行中的第二个单词。作者截图。

严重失败

如前所述, pymupdf 不适用于扫描的 pdf

作者截图。

别担心!有一个包可以解决这个问题…

结论

今天,我们看到了 PyMuPDF 的行动。这是一个有价值的工具。我已经用它自动搜索了数百个 pdf 文档,而不用打开 Adobe Acrobat。

可能性是无限的。

但是扫描的 pdf 呢?!请继续关注我的下一篇文章,内容包括如何使用 pytesseracT3 阅读多列 PDF。

谢谢你过来看我的帖子。希望 PyMuPDF 能像帮助我一样帮助你!你可以在 Github 上这里找到我用的笔记本。

敬请期待!

如果你想了解更多关于我从懒鬼到数据科学家的旅程,请查看下面的文章:

如果你正在考虑改变方向,进入数据科学领域,现在就开始考虑重塑品牌:

你可以通过 TwitterLinkedIn 联系我。

参考

[1]https://en.wikipedia.org/wiki/Optical_character_recognition

[2]https://pymupdf.readthedocs.io/en/latest/textpage.html?highlight=get_text#

使用 Python 中的 Pytesseract 阅读多列 PDF

原文:https://towardsdatascience.com/read-a-multi-column-pdf-with-pytesseract-in-python-1d99015f887a

NLP 工具

逐步介绍 OCR 的奇妙世界(附图片)

Jaizer CapangpanganUnsplash 上拍摄的照片

在前一篇文章中,我们学习了如何使用 PyMuPDF 包阅读 PDF 文档。我们还了解到,这种方法只有在 PDF 被数字化或可搜索的情况下才有效。否则,如果 PDF 被扫描并且不可搜索,PyMuPDF 就不起作用。

宇宙魔方来救援了。

Pytesseract 是另一个 OCR(光学字符识别)工具,作为 Google 的 Tesseract-OCR 引擎的 Python 包装器。它可以“识别”和“阅读”嵌入图像中的文本。”[1]

Tesseract-OCR 安装

在开始编码之前,我们首先必须安装 Tesseract-OCR,并将其安装路径添加到环境变量中。

对于 Windows,我们可以直接去这里下载安装程序。对于其他操作系统,在此进入

让我们开始安装吧!

作者截图

作者截图

作者截图

作者截图

这一次,我没有使用默认选项,而是推荐下载额外的数据,这样您以后就不必再这样做了。勾选下面圈出的方框:

作者截图

作者截图

在下一个屏幕中,请注意将安装 Tesseract-OCR 的目标文件夹。当我们将它添加到环境变量的路径中时,我们将需要它。

作者截图

在下一个屏幕上单击“安装”。

作者截图

安装完成后,单击“完成”。

作者截图

此时,如果我们打开一个终端并键入tesseract --version,我们将看到一个错误。

作者截图

我们首先需要做的是将 Tesseract-OCR 安装文件夹添加到路径中。为此,按下 Windows 键并立即键入env。然后会出现一个搜索框,显示几个选项供你考虑。让我们选择“为您的帐户编辑环境变量”

作者截图

在下一个屏幕上,单击路径和“编辑…”按钮。

作者截图

单击下一个屏幕顶部的“新建”按钮。

作者截图

现在,让我们进入 Tesseract-OCR 安装文件夹。单击“确定”

作者截图

在下一个屏幕上再次单击“确定”。

作者截图

当我们在终端中输入tesseract --version时,我们将得到关于宇宙魔方的信息,而不是一个错误。

作者截图

既然已经安装了 Tesseract-OCR 并且更新了我们的路径,我们就可以开始通过 pip 安装我们需要的 Python 包了。

使用 PIP 安装

因此,回到终端并键入以下内容:

pip install pytesseract

作者截图

完成后,键入以下内容:

pip install opencv-contrib-python

作者截图

现在我们已经准备好启动 jupyter 笔记本了!

Jupyter 笔记本

打开笔记本,让我们导入一些包:

现在,让我们也设置一个全局变量:

现在,让我们来看看我们的输入 pdf:

如果需要的话,让我们放大或放大 pdf。在这里,我们将其水平和垂直放大两倍:

在下面的代码块中,我们打开 SCANNED_FILE (pdf 扩展名),并将其转换为扩展名为 png 的图像。然后,我们利用 for 循环从文件中提取所有页面,脚本每页生成一个 png 图像。

现在我们有了一个图像文件,我们准备预处理这个图像。

在下一个单元格中,我们将加载上一步生成的第一个(可能只有一个)页面。

然后,我们将图像转换为灰度。

作者截图

下一步被称为 Otsu 的阈值技术。阈值处理是指我们制作二进制图像文件。它获取图像的像素并将其转换成黑色或白色。使用 Otsu 的阈值技术,我们不需要自己设置阈值参数。相反,大津是为我们做的。

作者截图

接下来是我们代码的核心。

首先,rectangular_kernel决定在文本周围画多大的矩形。在这种情况下,我们有(66,66)。这个数字越高,我们得到的盒子就越少,因为它通过将不同的单词聚集在一个盒子里来将它们处理成一个单元。反之,如果有(11,11),我们得到的盒子越多,因为它更独立地处理不同的单词。

我们可以看到下面的盒子的遮罩,其内核大小为(66,66)。

作者截图

在下一张图中,我们将内核大小改为(11,11)。

作者截图

最重要的一行是text = pytesseract.image_to_string(cropped, lang='lat', config='--oem 3 --psm 1'),宇宙魔方将图像转换成文本(或字符串)。config 参数允许您指定两件事:OCR 引擎模式和页面分段模式。

OCR 引擎模式或“oem”允许您指定是否使用神经网络。

  1. 仅传统
  2. 神经网络 LSTM 引擎而已。
  3. 传统+ LSTM 发动机。
  4. 默认,基于可用的内容。

页面分割模式或“psm”允许您指定 OCR 工具应该进行哪种页面分割。例如,我们使用“1”表示“带有 OSD(方向和脚本检测)的自动页面分段”,因为我们的文档是多列的。否则,我将使用“4”来表示“假设一列可变大小的文本”。如需使用哪种“psm”的示例,请阅读来自 Nanonets 的本教程:

https://nanonets.com/blog/ocr-with-tesseract/

说到底,这就是我们的结果:

作者截图

今天就到这里吧!

结论

今天,我们看到了宇宙魔方 OCR 的实际应用。一旦你掌握了图像预处理的诀窍,它会是一个很有价值的工具。然而,如果你不喜欢预处理图像,并且你足够幸运有数字化的 PDF 来处理,你可能会更好地使用 PyMuPDF。

但是数字化的 pdf 呢?!查看使用 Python 中的 PyMuPDF 阅读多列 PDF

谢谢你过来看我的帖子。希望 PyMuPDF 能像帮助我一样帮助你!你可以在 Github 上这里找到我用的笔记本。

敬请期待!

如果你想了解更多关于我从懒鬼到数据科学家的旅程,请查看下面的文章:

如果你正在考虑改变方向,进入数据科学领域,现在就开始考虑重塑品牌:

你可以通过 TwitterLinkedIn 联系我。

参考

什么是 ROC-AUC 以及何时不使用它

原文:https://towardsdatascience.com/read-this-before-using-roc-auc-as-a-metric-c84c2d5af621

没有完美的度量标准。

原象

了解指标隐藏和提升了什么。

当一个衡量标准成为目标时,它就不再是一个好的衡量标准——古德哈特定律

当您改进一个度量标准时,您主要是改进该度量标准有利于什么。因为没有一个指标可以衡量一切,盲目地追求一个指标可能是危险的。我想探索这对 ROC-AUC 意味着什么。

接收器操作特征曲线或 ROC 曲线用于评估二元分类器。二元分类器输出数据点属于正类的概率。正是我们为这个数字设定了一个阈值,并给它贴上了一个标签。如果概率高于阈值,我们说预测的类是正的(1),否则是负的(0)。调整阈值通常取决于用例。对于不捕捉正面例子的成本大于将负面例子误分类为正面例子的情况,可以选择较低的阈值,反之亦然。

ROC 曲线有助于我们将阈值之间的权衡可视化。该曲线下的面积(ROC-AUC)是该模型如何很好地分离不同阈值的正例和负例的总结。

将 ROC-AUC 可视化

下面的示例将带您了解一个玩具示例的 ROC-AUC 计算。

原象

上图显示了 7 个点的虚拟数据集的 ROC 曲线,其中有 4 个阴性(蓝色)和 3 个阳性(红色)示例。它们按照模型预测概率的升序排列(0.1、0.1、0.2、0.2、0.3、0.6、0.9)。我们在四个任意选择的阈值 T1、T2、T3 和 T4 测量真阳性率(TPR)假阳性率(FPR) ,值分别为 0、0.15、0.25 和 0.95。

【TPR】=(+ve 个预测概率高于阈值的例子)/(总正例) 【FPR】=(-ve 个预测概率高于阈值的例子)/(总负例)

例如,考虑 T3 (=0.25)。T3 以上的正例数为 2(满分 3),T3 以上的反例数为 1(满分 4)。因此,TPR = 2/3,FPR = 1/4。最终,我们可以计算每个阈值的 TPRFPR ,并将其绘制在二维平面上,如上所示。这条曲线下的面积是 ROC-AUC。阈值越多,曲线越平滑,指标越精确。

AUC 的限制

可实现的最大 AUC 是 1。这是什么意思?

原象

根据该曲线,对于任何非零 FPR,TPR 将总是 1。从概念上讲,如果您选择一个阈值,并且只有一个负数据点越过它,则每个正数据点都会越过该阈值。只有当存在一个完美区分类别的阈值时,这才是可能的。

在不平衡数据集的情况下会发生什么?

在不平衡数据集的情况下,ROC-AUC 往往更乐观。考虑两个排序的数据集(表示为密度),

原象

如图所示,数据集 1 (D1)有 50 多个 ve 示例和 100 个 ve 示例。假设正例与反例的重叠是同质的。也就是说,任何两个重叠部分都有相同比例的正面和负面例子。这同样适用于数据集 2 (D2)。显然,D2 比 D1 更不平衡。

对于 D1 ,at 阈值= 0.5
—精度= 50/(50+50) = 0.5
—回忆= 50/50 = 1
— F-1 评分=(2精度召回)/(精度+召回)=(2 * 0.5 * 1)/(0.5+1)=2/3

D2 在 0.75 阈值,F1 得分(= 2/3 )。

现在让我们做一些有趣的事情。让我们看看 AUC。

原象

对于 D1,当你从阈值 0 到 0.5 时,TPR 保持不变(=1)。类似地,对于阈值为 0 至 0.75 的 D2,TPR=1。

对于 D1,在 0.5 FPR 之后,TPR 和 FPR 线性下降,因为它们是均匀混合的。D2 也是如此,排在 FPR 之后 0.25。但是 D2 的 AUC 比 D1 大,尽管他们有相同的 F1 最高分。观察到对于大多数阈值,不平衡数据集中的 TPR 更高。

这可能具有欺骗性。那么还有什么选择呢?

精确召回曲线

精度-召回曲线类似于 ROC 曲线,除了 Y 轴是精度,X 轴是召回。通过在不同阈值绘制曲线来计算 AUC。考虑上面讨论的同一个例子的 PR 曲线—

对于 D1,任何阈值在 0.5 之后,对于 D2,任何阈值在 0.75 之后,精度=0.5。对于 D1,0.5 之前的任何阈值和对于 D2,0.75 之前的任何阈值都有 recall=1。如你所见,曲线基本相同。1/3 是 D1 的精度,1/5 是阈值=0 时 D2 的精度。我们使用的例子过于简单了。然而,PR-AUC 对不平衡不太敏感。

一般建议

衡量标准的表现很重要。模型评估几乎不会停留在单一的数字指标上。要了解模型学到了什么,请查看模型犯了哪些错误?模型对哪些例子不自信?指标的最大收益来自哪里?

我热爱教学!如果你需要帮助理解数据科学、机器/深度学习或 Python 中难以理解的概念,请随时在这里申请辅导课程。

使用 PyTorch 数据集更快地读取. h5 文件

原文:https://towardsdatascience.com/reading-h5-files-faster-with-pytorch-datasets-3ff86938cc

使用弱洗牌的方法

图片由 Amol 提供,取自 Unsplash

最近,我使用牛津 Pet 数据集(参考文献中的引用)进行了一个多任务深度学习项目。虽然我能够通过使用 Colab 的 GPU 来克服极其缓慢的训练速度,但在加载数据时,我很快就遇到了瓶颈。尽管使用了一个庞大的多任务模型,对于一个 32 的批量,训练速度大约是 0.5 秒,而数据加载过程大约是 12…

在互联网上深入搜索后,我发现这是许多人(参见 PT 论坛S/O #1S/O #2 )面临的问题,也是尚未得到充分解决的问题。我想到了一个 PyTorch 解决方案,我认为它适用于大多数情况。这使得我在 Colab 上的加载时间从 12 秒减少到 0.4 秒左右。本文将首先解释瓶颈,然后介绍解决方案。随后是实证结果和对理论保证的评论。我假设您了解 PyTorch 数据集,但是对 Python 中的类有足够的工作知识就足够了。

瓶颈从哪里来?

PyTorch 中创建标准数据集的方式基于torch.utils.data.Dataset类。这些定制数据集有一个__getitem__方法,在调用该方法时,它会加载一组给定索引的数据。我写的数据集是这样的:

完整的数据加载器可以在 GitHub 资源库中找到,这里。在数据集初始化时调用 _load_h5_file_with_data 方法,将. h5 文件预加载为生成器对象,以防止每次调用 getitem 时被调用、保存和删除。

然后,使用torch.utils.data.Dataloader类,我定义了一个trainloader来批量处理我的数据用于训练目的。这就是问题所在。

每次迭代trainloader时,数据集中的__getitem__方法被调用batch_size次,以生成一批数据。这对于. h5 数据集来说是有问题的,因为从它们中查询数据的时间复杂度与对它们的调用次数成线性比例。因此,如果查询单个索引的开销是 x ,那么在我们的例子中,我们期望每批的总时间是 32*x

这里,列车循环的每一对(输入,目标)将由列车装载器查询数据集 32 次(由于 shuffle=True )而创建。 getitem 方法被调用了 32 次,每次都使用不同的索引。然后, trainloader 后端聚集来自 getitem 方法的单个(输入,目标)返回,返回一个包含 32 项的(输入,目标)对用于训练循环。

使用弱洗牌提高速度

要认识到的关键点是,. h5 文件的瓶颈来自于寻找数据所在的索引。找到随后出现的索引几乎要花上 10 分钟的时间!

这意味着查询索引i处的数据的时间复杂度与查询索引i:i+batch_size处的数据的时间复杂度几乎相同。因此,如果我们进行批处理,然后对批处理进行混洗(我称之为‘弱混洗’),那么我们将通过batch_size因子来加快数据加载过程。下图显示了这种方法与标准洗牌方式的不同之处。

这种方法的明显含义是,模型中引入了一些偏差,因为我们不是在点的水平上洗牌。虽然人们可能试图在批次级别洗牌后逐点洗牌以减少偏差,但这是徒劳的,因为损失函数是可交换的。如下图所示。

对于 m >

弱洗牌实现

数据集不需要更改。这是因为来自__getitem__index参数可以是一个索引列表。因此,如果我们能够传递一个列表list(range(i, i+batch_size)),那么我们就能够通过对. h5 文件的一次查询获得整批数据。

这意味着需要改变的是数据加载器。为此,必须使用sampler参数创建一个自定义加载方案。如下所示:

几个要点:

  • RandomBatchSampler是生成索引i:i+batch_size的自定义采样器
  • BatchSampler类批量采样RandomBatchSampler
  • Dataloaderbatch_size参数必须设置为None。这是因为batch_sizesampler不能同时设置

理论保证

迄今为止,就我们在模型中诱导的偏差而言,我还没有找到弱改组方法的理论保证。我已经在交叉验证数学交流上问过这个问题,如果我得到他们的回复,我会更新这篇文章。在此之前,我会感谢任何建议!

关键要点

  • 本文描述的方法适用于无法将全部数据加载到内存中的情况
  • 使用标准加载方案从. h5 文件中加载批处理很慢,因为时间复杂度与对文件进行的查询数量成比例
  • 瓶颈来自于定位第一个索引,任何后续的索引(顺序排列,中间没有间隔!)可以加载,几乎不需要额外的费用
  • 通过使用批量采样,我们可以批量加载数据,从而将加载时间减少一倍batch_size
  • 这意味着数据将被弱混洗。batch_size的选择仅限于batch _ size<T21【m】T7。因此,这种解决方案不适合瓶颈来自训练时间的情况。

多任务项目的完整代码可以在这里找到。在不久的将来,我会发布更多关于这个项目的文章,敬请关注!

参考

[1] 推荐加载较大 h5 文件的方式。PyTorch 论坛。

[2] torch.utils.data 。PyTorch 文档。

[3] 如何在 Dataloader 中使用 Batchsampler。销售订单

[4] 是否有更有效的方法从 hdf5 数据集中检索批次?销售订单

[5] 读取. h5 文件极其缓慢。销售订单

[6] 从数据加载器随机批量取样。PyTorch 论坛。

[7] 在 PyTorch 中创建一个定制的数据加载器。中等。

[8] 使用 h5py 混洗 HDF5 数据集。销售订单

[9]py torch 的 HDF5 数据集。中等,

数据集

[1] 牛津宠物数据集。许可:知识共享署名-共享 4.0 国际许可

所有图片均来自作者,除非另有说明

用 Python 实现实时异常检测

原文:https://towardsdatascience.com/real-time-anomaly-detection-with-python-36e3455e84e2

使用 PyOD 和 PySAD 进行流数据的机器学习

用于实时异常检测的滑动窗口(图片由作者提供)

在这篇博文中,我们将讨论流数据的异常检测,特别是 Python 的两个库 PyOD 和 PySAD。

简而言之,异常检测是对不符合预期模式的项目、事件或观察结果的识别。这可能像检测欺诈性信用卡交易一样简单,也可能像识别制造数据中的违规行为一样复杂。

异常检测可以帮助您在问题变成问题之前识别问题,并通过识别您可能没有意识到的模式来帮助您优化流程。

说到大数据,主要有两类:批处理和流处理。批处理是指一次性处理所有数据。另一方面,流式传输是指在数据到达时对其进行处理。由于流数据不断更新,异常检测是保持系统平稳运行的关键。

在这篇博文中,我们将通过在几行代码中结合 PyOD 和 PySAD 来解决实时检测异常的挑战。

敬请期待!

异常检测

异常检测是对不符合预期模式或数据集中其他项目的观察值的识别。这是一个重要的数据挖掘工具,用于发现诱发的错误、意外的模式和更普遍的异常。

执行异常检测有多种方法,您采用的方法取决于数据的性质和您要寻找的异常类型。但是一般来说,对于给定的样本分布,有三种方法可以识别可能被认为是异常的观察值:

无监督(来源:PyOD documentation)当训练数据同时包含正常和异常观测值时,该模型在拟合过程中识别出异常值。当“异常值”被定义为存在于数据集的低密度区域中的点时,可以使用该方法-任何不属于高密度区域的新观察值都将由算法自动标记。

半监督(来源:PyOD documentation)
半监督技术背后的想法是,我们可以利用我们关于什么使事情正常的知识,并在无异常的数据点上拟合模型。然后,在推断时间期间检测异常,并假设异常随训练数据的分布而变化。

监督(资料来源:PyOD documentation)
每个观察的基本事实——内部和外部——都是众所周知的。该模型已经对不平衡的训练数据进行了拟合,然后用于对新的观察结果进行分类。当属于异常值类的内容与属于其他类的内容之间存在明显差异时,以及知道它们随后的行为时,可以采用这种方法-假设异常值具有与训练集中相似的分布。

实时异常检测

为了检测异常,机器学习算法必须能够识别数据中的模式。

这些模式可能非常复杂,并且会随着时间的推移而变化。

这就是为什么我们有时需要在生成新数据点的同时在线更新机器学习模型的原因。

出于多种原因,实时异常检测非常重要。首先,它可以帮助您快速识别并纠正错误,以免造成重大损失或中断。其次,它可以让您主动监控系统的潜在问题,并在检测到问题时快速做出响应。最后,它可以让您近乎实时地了解系统中正在发生的情况,让您做出更好的决策,也就是说,在异常发生时而不是事后检测到异常。

机器学习算法的问题在于,它们的训练速度相当慢,而且通常需要大量数据。根据我们试图达到的推理时间,我们应该小心我们选择的模型。正如您所想象的,在速度和准确性之间进行权衡,在使用 PySAD 时有两个主要参数来处理这种折衷:“窗口大小”和“滑动大小”。

窗口大小决定了机器学习算法用来训练模型的数据点的数量,滑动大小是模型更新的周期。

例如,如果我们想检测 Twitter feeds 中的异常,我们就在一个历史窗口上训练算法,并用这个模型检查每个新推文,以确定它是否是异常。然后,在“滑动大小”时间(比如 30 秒)后,模型太旧了,我们在一个新窗口上更新模型,该窗口查看最近的数据点。

这意味着模型定期更新,但不是每次有新的数据点时都更新。

实时异常检测框图(图片由作者提供)

管道与 PyOD 和 PySAD

我最喜欢的两个异常检测库是 PyOD 和 PySAD。PyOD 库是一个全面的 Python 工具包,用于检测多元数据中的异常值,而 PySAD 是一个轻量级库,用于流数据中的无监督异常检测。

这两个库都是开源的,易于安装,并且彼此兼容。

我们可以将这两个库结合起来,与前面描述的窗口模型一起工作:PySAD 负责窗口逻辑,而 PyOD 拥有检测异常的算法。

用于实时异常检测的管道(图片由作者提供)

在实时处理数据时,预处理可以发挥至关重要的作用,因为如果您使用一些降维算法(如主成分分析(PCA )),可以显著提高算法的速度,或者可以通过归一化传入的数据点来提高算法的准确性。

另一方面,后处理是从异常分数中去除噪声所必需的。在管道的末端,概率校准器会给你面对异常的最终可能性。

整个流水线——预处理、窗口模型、后处理和概率校准器——可以用几行代码实现。

这里有一个笔记本展示了合并这两个库的简单性:

结论

异常检测是识别不符合预期模式的事件或观察结果的过程。由于数据生成的高速率和必须处理的数据量,检测流数据中的异常可能很困难。

如果您应该从本文中记住一件事,那就是 PyOD 和 PySAD 可以一起使用来检测数据流中的异常,并且这样做的代码只有几行长。

为了检测异常,机器学习算法必须能够识别数据中的模式,但这些模式可能非常复杂,可能会随时间而变化。由于这个原因,当新的数据点被创建时,我们有时需要在线更新机器学习模型。

好奇想了解更多关于 Anthony 的工作和项目吗?在LinkedInTwitter 上关注他。

需要技术写手?将您的请求发送到https://amigo CCI . io

使用 Kafka、BigQuery 和 Looker Studio 构建实时事件流管道

原文:https://towardsdatascience.com/real-time-event-streaming-with-kafka-bigquery-69c3baebb51e

一个简单的数据工程项目

菲利普Unsplash 上的照片

实时流媒体应用有时可能会很复杂,当试图了解它们时,决定一个实际的用例对于培养有趣有效的学习体验是很重要的。因此,通过下面的例子,我希望您能够以一种简单的方式掌握构建实时应用程序的基础。

方案

假设我们在一家音乐流媒体服务公司的数据工程部门工作,我们需要创建一个实时仪表板,显示某个特定艺术家(比如说 【托尼·阿伦】 )一段时间内最受欢迎的歌曲。为此,我们将利用一个流行的分布式流媒体平台 Kafka 来制作、消费必要的歌曲事件,并将其流式传输到 BigQuery 中,这样我们就可以在 Looker Studio 的仪表盘上可视化这些流行歌曲。

我们的架构最终会是这样的:

实时流架构—作者图片

术语和概念

让我们快速定义一些我们将在本文中涉及的术语。

  • Kafka:Apache Kafka是一个开源的分布式流媒体平台,支持(除其他外)实时事件驱动应用程序的开发,非常适合我们的用例。
  • Kafka 集群:一组服务器(称为代理),它们协同工作,为实时应用程序提供高可用性、容错和存储。
  • 如上所述,代理是在 Kafka 集群中执行实际工作的机器。它托管一些分区集,处理向这些分区写入新事件的传入请求,并允许消费者按主题、分区和偏移量获取消息。
  • 主题:一个主题仅仅是一个 事件日志 。来自生成器的每个新事件都被附加到主题的末尾。而话题又分为分区

作者图片

  • Producer: 您编写的将数据发布(产生)到 Kafka 集群中的主题的应用程序。
  • 消费者:从 Kafka 集群中实时检索数据的应用程序或最终用户。为了有效地获取实时消息,Kafka 消费者必须订阅集群中存在的各个主题。
  • Zookeeper :跟踪 Kafka 集群节点的状态,它还跟踪 Kafka 主题、分区等等。(注:一个名为 的更新 KIP-500 移除了对 Zookeeper 的需求,但我们在本文中将不会使用那个版本的 Kafka)。
  • Poll:poll()方法是 Kafka 消费者调用的从给定主题中检索记录的函数。

我们将在 4 个步骤中设置上述架构。但是在我们开始之前,请确保您具备以下先决条件:

先决条件

#1.用 Docker 部署 Kafka

Kafka 可以通过多种方式部署,但是我们将使用 Docker 来部署它,因为它非常简单。

我们的 Kafka 集群将有两个主要实体;

  • 1 个代理实例和
  • 1 个 Zookeeper 实例。

简单的卡夫卡集群——作者图片

我们将使用一个 Docker 组合文件来配置和运行这些容器。您会注意到下面的docker-compose.yaml文件中公开的 2 个服务和所需的端口:

确保 docker 文件与我们稍后将要编写的 Kafka 生产者和消费者文件位于同一个目录中。

要构建两个 Docker 容器,运行这个命令,您应该在几分钟内就可以启动并运行这两个容器。

docker-compose up -d

#2.构建生成器

我们将编写一个模拟音乐流平台上用户活动的应用程序/制作程序。这个应用程序将发送一个名为song-completed的事件,当用户完成一首歌曲时就会触发这个事件。这个事件将被发送到一个我们称之为tony-allen-plays的卡夫卡主题。

一个生产者和消费者在 Kafka 集群中与主题互动的架构——作者图片

我们将使用 Faker 包为我们的应用程序生成假的流数据。我们的假事件有效负载看起来像这样:

{'user_id':001,
'artist': 'tony-allen',
'song_id': 03, 
'song_name':  'lady',
'event_type':'song_completed',
'timestamp': '2022-11-03 07:22:13'}

要安装 Faker 包,请在终端窗口中运行:

pip install Faker

生成一个假歌曲列表

现在,在我们的代码中,我们将启动 Faker 对象,并创建一个硬编码的托尼·阿伦 10 首随机歌曲的歌曲列表,它将成为事件有效负载的一部分。

我从他在谷歌上的歌曲列表中随机挑选了一些歌曲——作者截图

from confluent_kafka import Producer
from faker import Faker
import json
import time
import logging

#Create Faker object to generate fake data for Producer
fake=Faker()

#Create Tony Allen song list
songs = ["zombie", "lady", "secret-agent","kindness","soldiers","asiko","the-same-blood","upside-down","african-man","vip"]

配置日志格式

每当一个新事件可用时,日志将被附加到主目录中的一个producer.log文件中——我们在下面定义了它。这里,我们正在设置我们希望如何格式化该日志文件的基本配置。

#Configure logger
logging.basicConfig(format='%(asctime)s %(message)s',
                    datefmt='%Y-%m-%d %H:%M:%S',
                    filename='producer.log',
                    filemode='w')

logger = logging.getLogger()
logger.setLevel(logging.INFO)

发起生产者

通过指定 Kafka 集群的端口来启动 Kafka producer 对象,如上面的 Docker compose 文件中所定义的:

#Create Kafka Producer
p=Producer({'bootstrap.servers':'localhost:9092'})

配置回拨

定义一个负责确认新消息或错误的回调函数。当有效消息可用时,它被解码为 utf-8 并以首选格式打印。相同的消息也会附加到日志文件中。

#Callback function
def receipt(err,msg):
    if err is not None:
        print('Failed to deliver message: {}'.format(err))
    else:
        message = 'Produced message on topic {} with value of {}\n'.format(msg.topic(), msg.value().decode('utf-8'))
        logger.info(message)
        print(message)

编写一个生产者循环

这是有趣的部分!这里我们只是创建了一个 3 秒延迟的循环,模拟流媒体平台上的实际用户活动。我们为 JSON 事件创建了一个模式,并利用 Faker 来生成实际的数据点。

#Write Producer loop 
def main():
    for i in range(20):
        random_song_id = fake.random_int(min=0, max=9)
        data={
           'user_id': fake.random_int(min=20000, max=100000),
           'artist': 'tony-allen',
           'song_id': random_song_id, 
           'song_name':  songs[random_song_id],
           'event_type':'song_completed',
           'timestamp': str(fake.date_time_this_month())    
           }
        m=json.dumps(data)
        p.produce('tony-allen-plays', m.encode('utf-8'),callback=receipt)
        p.poll(1) # Polls/checks the producer for events and calls the corresponding callback functions.
        p.flush() #Wait for all messages in the Producer queue to be delivered. Should be called prior to shutting down the producer to ensure all outstanding/queued/in-flight messages are delivered.
        time.sleep(3)

注意,当我们调用p.produce时,我们指定了我们想要发布消息的 Kafka 主题。在这种情况下,称为tony-allen-plays。因为这个主题在我们的 Kafka 集群中还不存在,所以它是在这个应用程序第一次运行时自动创建的。

p.poll很重要,因为它检查事件的生产者并调用我们之前定义的相应回调函数。

我们完整的producer.py脚本应该是这样的:

要确认生成器按预期工作,请在终端窗口中运行以下命令:

python producer.py

您应该看到下面的输出,它打印出每 3 秒钟发送到 Kafka 主题的事件。

终端窗口中的生产者输出—按作者分类的图像

#3.建立消费者

消费者会做两件主要的事情:

  • tony-allen-plays主题中轮询和检索事件
  • 使用 BigQuery 流 API 将这些事件作为流发送到 BigQuery

安装 BigQuery Python 库

首先,使用以下命令安装 BigQuery Python 库。

pip install google-cloud-bigquery

然后我们可以将它导入到consumper.py脚本中,并设置 BigQuery 配置。

from confluent_kafka import Consumer
from google.cloud import bigquery
import ast
from google.oauth2 import service_account

#Create BQ credentials object
credentials = service_account.Credentials.from_service_account_file('PATH-TO-BQ-SERVICE-ACCOUNT')

# Construct a BigQuery client object.
bq_client = bigquery.Client(credentials=credentials)

#Speficy BigQuery table to stream to
table_id = 'PROJECT-ID.DATASET.TABLE-NAME'

启动消费者

接下来,我们通过指定端口来启动 Kafka 消费者,然后我们订阅主题tony-allen-plays。在初始化消费者时,我们指定消费者 groupid,因为所有 Kafka 消费者必须属于一个消费者组。

c=Consumer({'bootstrap.servers':'localhost:9092','group.id':'tony-allen-consumer','auto.offset.reset':'earliest'})
print('Kafka Consumer has been initiated...')

#Subscribe to topic
c.subscribe(['tony-allen-plays'])

您还会注意到有一个属性——auto.offset.reset——最早被设置为‘T5’。基本上是从话题划分开始就在告诉消费者消费。

卡夫卡 auto.offset.reset:最早的 —作者图片

典型的 Kafka 消费应用程序以消费循环为中心。因此,最后一步是编写一个循环,不断地轮询主题中的新消息,如果发现新消息,就将这些消息发送给 BigQuery。

我们完整的脚本应该是这样的:

卡夫卡消费者. py

运行 Kafka 管道

既然已经设置了消费者和生产者,那么打开两个单独的终端窗口并再次运行生产者:

python producer.py

然后运行消费者,以便它实时从主题中读取数据:

python consumer.py

如果您看到生产者生成的消息开始出现在消费者终端窗口中,那么您的消费者正在正常工作,数据也应该流入 BigQuery:

Kafka consumer.py 输出—作者图片

《大查询》中的卡夫卡事件——作者图片

#4.可视化数据

最后一步是将 BigQuery 表连接到 Looker Studio,并创建一个简单的条形图,以接近实时的方式显示流行歌曲。

前往 Looker Studio ,签到并:

  • 选择新的“空白报告”
  • 在“连接到数据”下,选择“BigQuery”作为数据源
  • 然后选择您的 BigQuery 项目、数据集和表

现在您应该看到一个类似的视图。确保尺寸度量字段与下面的屏幕截图匹配,您应该有一个如图所示的简单条形图。

Looker 工作室截图作者。“总播放次数”由“记录计数”更名而来。—作者图片

接近实时的仪表板

Looker Studio 有一个 数据刷新 特性,它指定了仪表板刷新的频率。您可以将其设置为 1 分钟,这是当前可用的最频繁的刷新周期,您的控制面板应该每 1 分钟刷新一次。

结论

我们讲述了如何用 Docker 建立一个最小的 Kafka 集群,将数据加载到一个主题中,然后消费数据并将其传输到 BigQuery。最后,我们创建了一个近乎实时的仪表板,在 Looker Studio 中呈现最终结果。

我希望你觉得这很有用,并祝你好运建立你的下一个实时应用程序!欢迎分享您的任何建议或其他使用案例。

你可以成为中会员支持我,享受更多这样的故事。

参考

使用 Elasticsearch 进行实时预输入搜索(AWS OpenSearch)

原文:https://towardsdatascience.com/real-time-typeahead-search-with-elasticsearch-aws-opensearch-88410d5b31a1

使用 MovieLens 数据集在云上构建可扩展的智能搜索引擎的端到端示例

在谷歌搜索的例子。作者图片

1。
简介 2。数据集准备
3。设置 OpenSearch
4。索引数据
5。基本查询同匹配
6。用 Jupyter 笔记本和 ipywidgets
7 基本前端实现。一些高级查询
7.1 匹配短语前缀
7.2 匹配+前缀带布尔
7.3 多字段搜索
8。结论
关于我的
参考文献

1.介绍

你有没有想过谷歌是如何让它的搜索引擎变得如此智能,以至于它可以预测我们的想法,甚至在我们不输入整个内容的情况下自动完成整个搜索词?它被称为类型预搜索。这是一个非常有用的语言预测工具,许多搜索界面使用它来为用户输入查询提供建议。[1]

作为一名数据科学家或任何从事数据后端工作的人,有时我们可能需要这样一个交互式搜索引擎界面,让我们的用户能够以最少的努力查询结构化/非结构化数据。这样总能让用户体验更上一层楼。

幸运的是,我们不必从头开始构建它。有很多开源工具可以使用,其中之一就是 Elasticsearch

elastic search是一个分布式、免费和开放的搜索和分析引擎,适用于所有类型的数据,包括文本、数字、地理空间、结构化和非结构化数据。Elasticsearch 以其简单的 REST APIs、分布式特性、速度和可伸缩性而闻名,是 Elastic Stack 的核心组件,是一套用于数据摄取、丰富、存储、分析和可视化的免费开放工具。[2]

另一方面,亚马逊创建的 AWS OpenSearch 是 Elasticsearch 的分叉版本,适合其 AWS 生态系统。它有一个非常相似的界面与基础结构与弹性搜索。在这篇文章中,为了简化在本地机器上下载、安装和设置 Ealsticsearch 的过程,我将带您浏览一个使用 AWS Open Search 索引和查询数据的端到端示例。

在现实世界中,使用这种云服务的另一个重要原因是可扩展性。我们可以轻松调整所需的资源,以适应任何数据复杂性。

请记住,即使我们在这里使用 AWS OpenSearch,如果您已经设置了 Elasticsearch,您仍然可以按照其中的步骤操作。这些工具在本质上非常相似。

2。数据集准备

在本例中,我们将使用 MovieLens 20M 数据集 ,这是一个受欢迎的开放电影数据集,被许多数据专业人员用于各种项目。之所以称之为 20M,是因为数据集中包含了 2000 万个收视率。此外,整个数据集中包含 465,000 个标签应用、27,000 部电影和 138,000 个用户。

这个数据集包含几个文件,可以用于非常复杂的例子,但是假设我们只想在这里构建一个可以查询电影名称、年份和流派的电影搜索引擎,我们只需要一个文件 movies.csv

这是一个非常干净的数据集。结构如下所示:

movies.csv (MovieLense 20M)。作者图片

只有 3 个字段: movieId片名(括号内为年份)、以及流派(用|)隔开。我们将使用标题和流派对数据集进行索引,但看起来有些电影没有指定流派(例如,movieId = 131260),因此我们可能希望将这些流派替换为 NA,以防止它们作为不需要的流派关键字被查询。几行处理就足够了:

import pandas as pd
import numpy as npdf = pd.read_csv('../data/movies.csv')
df['genres'] = df['genres'].replace('(no genres listed)', np.NaN)
df.to_csv('../data/movies_clean.csv', index=False)

通过这一小段代码,我们刚刚清理了数据集,并将其保存为一个名为movie_clean.csv的新文件。现在,我们可以继续创建一个 AWS OpenSearch 域。

3.设置开放搜索

下面是来自 AWS OpenSearch 的 官方文档 。你可以跟着它做更详细的介绍,也可以通读我下面做的简化版。

如果你没有 AWS 账号,可以按照这个 链接 报名 AWS。您还需要为 AWS 服务添加一种支付方式。然而,不要惊慌,因为在本教程中,我们将使用最少的资源,成本应该不超过 1 美元。

报名 AW S .图片作者

创建您的帐户后,只需登录您的 AWS 管理控制台 ,搜索 OpenSearch 服务,或点击此处进入 OpenSearch 仪表盘

打开搜索仪表板。作者图片

在仪表板中,按照以下步骤操作:

  1. 选择创建域
  2. 给个域名
  3. 在开发类型中,选择开发和测试

AWS OpenSearch 设置。用户图像

4.将实例类型更改为 t3.small.search ,并保留所有其他类型的默认值。

AWS OpenSearch 设置。用户图像

5.为了项目的简单,在网络中,选择公共接入

6.在细粒度访问控制中,通过设置用户名和密码来创建主用户

AWS 开放搜索设置。用户图像

7.在访问策略中,选择只使用细粒度的访问控制

AWS OpenSearch 设置。用户图像

8.通过保留默认设置来忽略所有其他设置。点击创建。这可能需要 15-30 分钟才能完成,但根据我的经验,通常会更快。

4。索引数据

AWS OpenSearch 或 Elasticsearch 足够智能,可以自动索引我们上传的任何数据,之后我们可以用任何逻辑规则编写查询来查询结果。然而,可能需要一些预处理工作来简化我们的查询工作。

我们记得,我们的数据由 3 列组成:

movies.csv (MovieLense 20M)。作者图片

标题和类型对我们来说都很重要,因为我们可能希望在任一/两者中输入任何关键字来搜索我们想要的电影。open search 支持多字段搜索,但是为了查询的简单性,我们也可以通过将我们感兴趣的所有关键词放入一个专门的列来对其进行预处理,这样提高了效率,降低了查询复杂度

预处理以创建新的 search_index 列。作者代码

使用上面的预处理代码,我们将一个名为 search_index 的新列插入到包含标题和所有流派的数据帧中:

添加了 search_index 的数据帧。作者图片

下一步是将数据转换成 JSON 格式,以便批量上传到我们的域。为批量数据上传指定的格式可以在 开发人员指南 选项 2 中找到。大概是这样的:

{"index": {"_index": "movies", "_id": "2"}}
{"title": "Jumanji (1995)", "genres": "Adventure|Children|Fantasy", "search_index": "Jumanji (1995) Adventure Children Fantasy"}

其中第一行指定要保存在域中的索引(文档)名称以及记录 id(这里我使用 movieId 列作为惟一标识符)。第二行包括数据集中的所有其他字段。

以下代码用于转换:

从 dataframe 转换到 JSON。作者代码

转换后,数据以movies.json的名称存储在数据文件夹中。现在我们需要将它上传到域中,如下所示:

将 JSON 数据批量上传到域中。作者代码

请注意,端点可以在您的 OpenSearch 域页面上找到。用户名和密码是我们在创建该域名时设置的主用户名&密码。

域仪表板。作者图片

如果它返回一个 <响应【200】>,那么我们就可以开始了。数据集已成功上传到 AWS OpenSearch 域。

5.带有匹配的基本查询

现在,随着数据的上传,我们已经完成了服务器端的所有工作。OpenSearch 自动索引数据以备查询。我们现在可以开始在客户端上查询来自域的数据。

要了解关于查询语言的更多信息,这里有两个选项:

  1. 开始使用 AWS OpenSearch 服务开发者指南
  2. 在这个 官方 Elasticsearch 指南查询 DSL 中有一些关于从 Elasticsearch 和 OpenSearch 中查询数据的非常详细的文档。

然而,在这个例子中,我们不需要非常高级的功能。我们将主要使用围绕标准 匹配查询 的一些小变化。

下面是一个基本的例子:

基本匹配查询。作者代码

在这里,我们编写查询来查找任何与 title = "jumanji "匹配的记录,将 JSON 查询序列化为字符串,并将其与端点和凭证一起发送到域。让我们看看返回的结果:

{'took': 4,
 'timed_out': False,
 '_shards': {'total': 5, 'successful': 5, 'skipped': 0, 'failed': 0},
 'hits': {'total': {'value': 1, 'relation': 'eq'},
  'max_score': 10.658253,
  'hits': [{'_index': 'movies',
    '_type': '_doc',
    '_id': '2',
    '_score': 10.658253,
    '_source': {'title': 'Jumanji (1995)',
     'genres': 'Adventure|Children|Fantasy',
     'search_index': 'Jumanji (1995) Adventure Children Fantasy'}}]}}

正如我们看到的,它返回标题等于jumanji的记录。我们的数据集中只有一个匹配的结果,确切的标题是Jumanji (1995),还有其他信息,如 id、流派和 search_index。

OpenSearch 自动处理大写/小写字母、任何符号和空格,因此它可以很好地找到我们的记录。此外,分数意味着返回的结果与我们的查询匹配的可信度,越高越好。这种情况下是10.658253。如果我们在搜索查询中包括年份,比如“jumanji 1995”,那么分数将增加到16.227726。当查询返回多个结果时,对结果进行排序是一个重要的指标。

6.Jupyter 笔记本和 ipywidgets 的基本前端实现

作为一个数据科学家, Jupyter 笔记本是一个很好的朋友,配合现在流行的 ipywidgets ,我们可以让笔记本变得很有互动性。下面是构建一个基本 GUI 的一些代码,该 GUI 包括一个文本框(用于输入关键字)和一个文本输出(用于显示查询结果)。

该准则有 5 个部分:

  1. 搜索功能:上一节介绍的基本查询的包装器。通过输入,它搜索并返回包含输入关键字的结果。
  2. 加粗:这个功能使用 Markdown 来加粗结果中的关键词,以便更好地可视化结果。
  3. printmd :在 IPython 中显示 Markdown 的包装函数
  4. text_change :处理 widget 事件的函数。在这种情况下,只要文本框中的值发生变化,它就会执行搜索功能,并返回按分数排序的前 10 个结果。还实现了一个计时模块来显示搜索需要多长时间。
  5. 代码的最后一部分定义了要显示的小部件元素:一个文本框和一个输出。当框中的值改变时,小部件事件被触发,结果将显示在输出中。

下面是我们执行代码时的样子:

使用 GUI 的基本查询。作者图片

当我们输入关键字“jumanji”时,它会不断搜索更新的关键字。在此过程中,关键字“j”、“ju”和“jumanji”分别返回 3 组结果。但是,对于“jum”、“juma”、“juman”或“jumanj”,它是空的,因为我们在这里使用的匹配查询将输入的关键字视为精确术语,并且我们的数据集中没有包含这些单词的电影。

这看起来不像我们预期的 typeahead 功能。为了提高性能,我们将在下面研究一些高级查询选项,包括前缀布尔多字段搜索。

7.一些高级查询

7.1 匹配短语前缀

一个简单的解决方法是使用 匹配短语前缀查询 代替 匹配查询 。匹配短语前缀查询可以按照与所提供的相同的顺序返回包含所提供文本的单词的文档。所提供文本的最后一个术语被视为前缀,匹配以该术语开头的任何单词。[5]****

这似乎是我们想要的:当我们输入查询时,我们不必在它可以告诉结果之前完成术语。

所以让我们这样修改我们的查询!

这里我不会再展示 Python 代码,因为除了对搜索函数中的查询稍作修改之外,一切都保持不变。现在match变成了match_phrase_prefix:

query = {
    'query': {
        'match_phrase_prefix': {
            'title': prefix
            }
        }
    }

更改后,现在看起来是这样的:

匹配短语前缀查询的结果。作者图片

我们在这里尝试了 3 个例子:

  1. ****巨漫记:现在修好了!当我们输入时,搜索引擎使用输入的文本作为前缀,完美地呈现所有想要的输出。
  2. 《哈利·波特》:和上一个例子做的一样。当我们到第二个学期“波特”时,它开始显示所有的哈利波特电影。
  3. ****哈利波特:然而,如果我们将“哈利波特”反转为“哈利波特”,它不会显示任何结果,因为在匹配短语前缀查询中顺序很重要。

想象一下,当我们使用搜索引擎时,我们并不总是 100%确定我们记住了按正确顺序排列的所有关键词,,所以我们可能想要改进搜索引擎,使它变得更加智能来帮助我们解决问题。

7.2 用布尔值匹配+前缀

要分解此类查询的需求:

  1. ****最后一项需要作为前缀处理
  2. 其他术语需要与完全匹配
  3. 我们不需要确切的顺序来得到想要的结果

遗憾的是,Elasticsearch/OpenSearch 中没有这种我们可以直接使用的查询类型,但是工具足够灵活,我们可以使用 布尔查询 来实现逻辑。

search_prefix 函数,它将最后一项视为前缀,而忽略顺序。作者代码

上面显示的代码是更新后的搜索功能。正如我们在这里看到的,在合并了 bool 操作 must 之后,查询变得更长了:

  • 如果我们输入的搜索文本包含不止一个单词,那么我们将把最后一个单词视为前缀前面的单词与完全匹配。****
  • 如果搜索文本是只有一个单词(或更少),那么我们就把它当作一个前缀来搜索。

实施更改后,让我们看看结果:

使用前缀和布尔语句的复合查询的结果。作者图片

现在,当以完全混合的顺序输入:“波特哈利王子血”时,搜索引擎知道我们在寻找什么,它能够返回正确的结果:哈利波特与混血王子(2009)** 。**

7.3 多字段搜索

到目前为止,我们只在电影标题字段中进行搜索,但是我们可以进一步改进搜索引擎,通过利用一些隐藏字段,例如我们案例中的类型,使其更加智能。

要在多个字段中进行搜索,弹性搜索用户指南中有 多匹配查询 。但是,它没有提供足够的能力来搜索前缀,同时像我们刚才所做的那样忽略订单。我们仍然需要布尔来进行复合查询。

有一个捷径:记住,我们之前处理了数据集,并准备了一个组合的 search_index 列来包含标题和流派信息。有了这个新字段,我们可以很容易地修改我们以前的查询,以包含隐藏的流派信息,而无需显式地使用多匹配查询。

search_prefix 函数,在不显式使用多匹配的情况下进行多字段搜索。作者代码

这是修改后的search_prefix函数,但是如果你仔细看,它和上一个函数没有什么变化,除了我们用search_index代替了title来搜索标题和流派。

以下是更改后的结果:

使用标题和类型信息的多字段搜索结果。作者图片

我们尝试了两个例子:

  1. 假设我们记得有一部冒险电影,关键词是“不可能”。然后我们输入“不可能的冒险”,它会返回所有不可能完成的任务的电影,因为它们被归类为“冒险”电影。
  2. 哈利·波特也一样。如果输入“波特冒险”,它将返回所有的哈利波特电影,因为它们都是“冒险”电影。

8.结论

在这篇文章中,我们通过一个端到端的例子来构建一个智能的预输入搜索引擎,步骤如下:

  • 准备电影数据集
  • 设置 AWS OpenSearch 域
  • 将数据集批量上传到 OpenSearch 服务进行索引
  • 构建一个简单的预输入搜索引擎 GUI
  • 从基础到高级主题,从域中查询数据的几种方法

结果令人印象深刻:我们能够像谷歌一样模仿预输入搜索。在键入关键字的同时,搜索引擎在中近乎实时** (0.2 秒)地分析和搜索数据库,以呈现最接近的匹配,并指导我们进一步缩小搜索结果列表的方向。**

另一方面,我们使用布尔运算修改了查询,使我们能够混合关键字顺序。我们还可以输入属于隐藏字段的值,例如流派名称,以帮助我们缩小范围并找到想要的结果。这两个特性使得搜索引擎更加智能。

在这个例子中,我们还有很多地方可以在将来改进:

  1. Jupyter 笔记本和它的ipywidgets在构建 GUI 方面非常强大。这里我们只使用了文本框和输出,但是本例中可以使用更多的小部件,例如触发搜索的按钮、在结果上添加过滤器的复选框下拉列表,或者点击 Google 提供的建议结果的选择框
  2. 然而,与前端开发人员使用的工具包(如 JavaScript)相比,ipywidgets仍然具有有限的功能。本例中显示的工作流仅用于数据科学家的快速测试和开发用途,他们非常熟悉 Jupyter 笔记本电脑,但在前端开发方面经验有限。如果我们想建立一个强大的搜索引擎应用程序,这不是最好的做法。
  3. Elasticsearch/OpenSearch 领域特定语言(DSL) 是一个非常通用的工具,我们可以用它来编写几乎可以满足任何需求的查询。在这个例子中,我们只是覆盖了它的一个非常肤浅的层面。如果你有兴趣了解更多,这里有 Elasticsearch 提供的完整的 文档
  4. AWS OpenSearch 是可扩展的。在这个例子中,我们为这个 27,000 部电影的数据集选择了t3.small.search,但是如果我们扩大资源,我们通常可以期待更好的结果。在现实世界中,数据量也可能是完全不同的数量级,因此需要为实际的数据大小选择适当的资源。
  5. 对于任何云服务来说,安全性都是一个严肃的话题。在这个例子中,我们几乎完全忽略了所有的安全设置,但是如果是在生产环境中,我们需要付出更多的努力来增强安全级别。

在帖子的最后,最后提醒一句:如果你不再需要 AWS OpenSearch 域名,请记得将其删除,否则会产生不必要的费用!****

感谢您的阅读!如果你喜欢这篇文章,请关注我的频道和/或 成为我今天的推荐会员 (非常感谢🙏).我会继续写下去,分享我关于数据科学的想法和项目。如果你有任何问题,请随时联系我。

**https://zhouxu-ds.medium.com/membership

关于我

我是赛诺菲的数据科学家。我拥抱技术,每天都在学习新技能。欢迎您通过媒体博客LinkedInGitHub 联系我。我的观点是我自己的,而不是我雇主的观点。

请看我的其他文章:

参考文献

[1]动态 Web-type ahead 搜索:https://doc . Dynamic Web . com/documentation-9/how-tos/general/implementing-type ahead-Search

[2]弹性——什么是弹性搜索:https://www.elastic.co/what-is/elasticsearch

[3] MovieLens 20M 数据集:https://grouplens.org/datasets/movielens/20m/

F.麦克斯韦·哈珀和约瑟夫·a·康斯坦。2015.电影镜头数据集:历史和背景。ACM 交互式智能系统汇刊(TiiS) 5,4,第 19 篇(2015 年 12 月),19 页。http://dx.doi.org/10.1145/2827872

[4]亚马逊 OpenSearch 服务开发者指南:https://docs . AWS . Amazon . com/open search-Service/latest/Developer Guide/gsg . html

[5] Elasticsearch Guide —查询 DSL:https://www . elastic . co/Guide/en/elastic search/reference/current/Query-DSL . html**

网站访客预测与脸书先知:完整教程

原文:https://towardsdatascience.com/real-world-website-visitor-forecast-with-facebook-prophet-a-complete-tutorial-8d135b848c5e

包括安装说明、数据、参数调整以及简单和复杂的预测—第 1 部分

预测可能会拯救你或你的公司。美国国家海洋和大气管理局在 Unsplash 拍摄的照片

作为微软的一名数据分析师,我每天都必须调查和理解时间序列数据。除了在历史背景下查看一些关键绩效指标,如 SRPVs、CPCs、转换率,我们有时还需要展望未来,即进行预测。对当前数据和未来的洞察有助于我们和我们的客户提前调整。

我试过很多方法。我个人最喜欢的是 LSTM。但是在现在的公司,他们雇佣了 FB Prophet。入乡随俗。因此,在这个项目中,我将坚持 Prophet 来预测欧洲某网站的唯一访客量。网站域名,后面你会看到,是捏造的。你只会在数据集中找到日期、地点和访客数量。

脸书先知有它的优势,虽然它不是特别健壮。但在预测方面,它简单、易用、全自动、快速。你不必处理数据维度,也不需要反向标准化。如果你不太熟悉 Python 和编码,并且你没有太多的时间建模,那么在我看来,这绝对是最好的选择之一。

在本文中,我将介绍,

  1. 安装先知
  2. 简单的 ETL,以及数据可视化
  3. 【简单预测(默认设置的预测)
  4. 预测与模型评估
  5. 用调好的参数进行预测

但是,原文太长了。因此它现在被分成两部分——前 3 节在第 1 部分中,而第 4 节第 5 节第 2 部分中。如果您对安装和简单预测不感兴趣,请点击此处进入第 2 部分

1。安装

几年前,当 python 还处于 3.8 版本时,新的 Prophet 用户只需 pip 安装库,然后进行预测。但是,尽管大多数 Python 用户已经更新到 3.9x,甚至更高的版本,但 Prophet 遗憾的是更新不快,只支持 3.8x 或更低的 Python 版本。因此,我们大多数人都需要一个解决这个不相容问题的方法。

虚拟环境是解决这一问题的方法之一。要了解什么是虚拟环境,请点击此处

如果你只对 FB 先知感兴趣,请随意跳过。

  1. 要在 Windows 10 中创建一个虚拟环境(本教程是用 Windows 10 平台编写的,而不是我曾经工作过的 Mac),我们首先必须安装 Anaconda。请点击这里进入官方网站

按照安装指南完成 Anaconda 安装。作者图片

然后按照安装指南完成安装。安装很容易。我们只需点击“是,是……”就能得到我们需要的东西。

2。安装完成后,我们将使用命令行提示。但是这一次,我们没有使用 Windows 命令提示符。相反,我们将使用 Conda 命令提示符

在这篇 Prophet 教程中,我们将使用 Anaconda 命令提示符。上面画圈的是 Anaconda 提示符。作者图片

如果您找不到 Anaconda 命令提示符,请转到上面红色箭头所指的搜索栏,搜索“Anaconda 提示符”。

3。会弹出一个小黑窗口。在这个 Anaconda 命令提示符下,输入:

conda create -n python_3_8 python=3.8

它所做的是创建一个名为 python_3_8 的新工作环境,其中安装了 python3.8。

4。然后我们进入:

conda activate python_3_8

输入该命令后,系统将进入虚拟环境 python_3_8。从现在开始,您的 python_3_8 虚拟环境将取代您原来的 python 环境(暂时)。您也可以选择它作为 Visual Studio 代码中的工作内核。

要在完成项目后逃离这个虚拟环境,只需输入:

conda deactivate

5。现在我们有了工作环境,我们需要安装库。下面是我们将需要的库和依赖项。只需逐行安装下面的代码。

conda install libpython m2w64-toolchain -c msys2conda install numpy cython -c conda-forgeconda install matplotlib scipy pandas -c conda-forgeconda install pystan -c conda-forgeconda install -c anaconda ephemconda install -c anaconda scikit-learnconda install -c conda-forge seabornconda install -c plotly plotlyconda install -c conda-forge optunaconda install -c conda-forge Prophet

安装完所有这些之后,我们现在可以开始我们的数据了。

2.简单的 ETL 和数据可视化

我们将从 csv 加载数据,并在此显示数据。因为数据是从其他数据库中提取的,所以我不会在这里介绍这些步骤。因此,我们将直接转到 csv 文件并探索。数据可以在这里下载。

Jupyter 笔记本文件也可以在这个链接中找到— 下载笔记本

首先,我们导入数据。

df = pd.read_csv(‘data.csv’)df2 = df.copy()
df2.head()

我们探索我们的数据。

网站独特访问者的数据框架。国家一栏是网站的服务区域。作者图片

print(df2[‘Country’].value_counts(), “\n”)print(df2[‘Country’].nunique(), “unique values.”)

德国有 23563 个数据点。作者图片

大致了解数据集后,我们清理 DateTime 列,只过滤需要的数据点。我只对德国的数据感兴趣,所以我将使用 loc 进行过滤。

df2[‘date’] = pd.to_datetime(df2[‘datepart’], dayfirst=True).dt.datedf2 = df2.loc[(df2[‘Country’]==’Germany’)] # we are only interested in the visitors from Germany in this tutorial.df_de = df2.copy()

现在我们有了一个数据框,里面只有网站在德国的表现。

确保数据集中没有空值。

df_de.isna().count()/df_de.count()

1.0 表示 100%,没有缺失数据点。作者图片

Prophet 对要馈入的 df 有严格的要求。 ds 和’y列是需要的标准列。其他的是,例如,“上限”和“下限”。我们在教程中不需要它们,因为这次我们将把模型设置为“线性”而不是“逻辑”。我将在本教程的第 2 部分中解释原因。

df_de2 = df_de.groupby(“date”).agg(np.sum)df_de2 = df_de2.reset_index()df_de2.columns = [‘ds’, ‘y’]df_de2 = df_de2[[‘y’, ‘ds’]]df_de2

“y”是要预测的目标值,“ds”是日期。它们是先知模型的两个基本要素。作者图片

然后,我将使用 Plotly 可视化我们的唯一访问者列的数据分布。Plotly 是我最喜欢的可视化库,因为它易于应用和交互。

import plotly.io as piopio.renderers.default = “notebook”fig_line = px.line(df_de2, x=”ds”, y=”y”, title=’The number of unique visitors of [www.lorentzyeung.com](http://www.lorentzyeung.com) in the previous 3 years’)fig_line.show()

在过去的 3 年里,www.lorentzyeung.com的独立访客数量。域名是捏造的。作者图片

用均值和标准差找出可能的异常值。

df_stat = df_de2.describe()mean = df_stat.loc[“mean”][“y”]std = df_stat.loc[“std”][“y”]upper = mean+std*3lower = mean-std*3print(‘ Mean: ‘, mean, ‘\n’, ‘Standard Deviation: ‘, std, ‘\n’, ‘Upper Limit: ‘, upper, ‘\n’, ‘Lower Limit:’, lower)

现在我们清楚地知道均值和异常值在哪里。只是我习惯用 3 个标准差作为上下大纲图截止线。作者图片

然后我们用方框图一点一点地显示可能的异常值。

我们的数据集中没有异常值。作者图片

我们的数据集几乎显示了一个完全对称的钟形(垂直),这意味着发现了正态分布。我们的数据集中没有异常值。这是理想的,因为正态分布是技术市场分析和其他类型的统计分析中最常见的分布类型。

3。简单预测(默认设置的预测)

在这个环节中,我将从一个简单的预言开始。这很简单,因为我们不需要在这里指定任何特定的参数值。我将只使用默认设置。然后,我们将观想我们的结果。

m = Prophet(interval_width=0.95, weekly_seasonality=False, daily_seasonality=False)m.add_country_holidays(country_name=’DE’)m.fit(df_de2)future_df = m.make_future_dataframe(periods=52,freq=’W’)forecast_default = m.predict(future_df)plot_forecast = m.plot(forecast_default)

在 Prophet 中直观显示原始数据点和预测线非常简单。我们的网站前几年一直在改进,甚至在疫情时代也是如此。但奥米克隆似乎带走了势头,扭转了趋势。作者图片

这个预测和图表对人脑有意义,至少对我的大脑有意义。尽管趋势趋于平稳,但 5 %的置信区间告诉我们,未来一年可能会呈下降趋势。

plt_components = m.plot_components(forecast_default)

组件图。作者图片

从上面的组件图中,我们可以看到我们的访问者数量呈上升趋势,尽管假期影响了性能。似乎圣诞节是我们网站最大的障碍。然而,一月和五月的银行假日会给我们的网站带来最强劲的增长。这里的图表完全有意义,因为你的网站销售的产品与圣诞节无关。

现在,您已经学习了如何安装和预测开箱即用的 Prophet 模型。在接下来的第二部分中,我将展示如何评估该模型,以及如何改进/优化它

感谢您的阅读。如果你喜欢这个教程,请分享给你的数据科学朋友,还有关注我。以下是我继续为社区做贡献的动力。

请继续阅读文章的第二部分。

请点击此处进入第二部分

参考:

https://facebook.github.io/prophet/

您可能还喜欢:

https://medium.com/analytics-vidhya/sales-analytics-churn-analysis-and-prediction-with-pyspark-98fffc169f36

用聚类算法重组体育联盟

原文:https://towardsdatascience.com/realigning-sports-leagues-with-a-clustering-algorithm-d6e9de9294d0

使用改进的 K-means 算法将职业运动队分组为最小化旅行距离的组

米克·豪普特在 Unsplash 上的照片

职业体育联盟的赛区结构解释

在美国,职业体育联盟,例如国家橄榄球联盟(NFL)、国家篮球协会(NBA)、职业棒球联盟(MLB)、国家曲棍球联盟(NHL)和职业足球联盟(MLS)将球队分成不同的组。属于同一个赛区的球队之间的比赛更频繁。这种分区结构被用来决定季后赛的参与,并滋生了激烈的竞争,因为经常在竞争中对抗的球队之间的敌意自然增长。然而,最重要的是,分区结构的作用是限制一支球队在一个赛季中为进行预定比赛而必须行进的距离。长途旅行会导致玩家疲劳、碳排放和过多的费用。理想情况下,一个赛区将由地理位置非常接近的球队组成,这样球队就不需要走很远的路去接触他们最频繁的对手。

重组,将球队组合成一个新的分区结构的过程,并不经常进行,但随着联盟期待在新的城市增加新的球队,T2 重新安置现有的球队,这可能很快就有必要了。如果联赛想要重新调整级别以最小化旅行距离,在级别数量保持不变的情况下,球队应该如何分组?此外,这些假设的最优划分与目前使用的划分相比如何?

约束 K-均值聚类算法

形成团队组以最小化每个组中团队之间的距离可以被视为聚类问题。K-means 算法可能特别适合,因为它寻求最小化每个点和其相关聚类中心之间的平方距离之和。然而,标准的 K 均值算法是不够的。运动分区的大小大致相同,标准的 K-means 算法不能保证得到的聚类具有这种性质。因此,我们使用一个受约束的 K-means 聚类算法,该算法将聚类数、最小聚类大小和最大聚类大小作为参数。这种改进的算法依赖于线性规划和网络流的思想。我们使用由 Joshua Levy-Kramer 创建的算法实现,其文档可以在这里找到。在这篇文章中可以找到该算法的高级概述,而在原始论文中提供了详细描述。

定义团队之间的距离

我们使用的算法实现依赖于笛卡尔坐标系中定义的欧几里德距离。因此,我们必须在笛卡尔坐标中定义各个团队的位置。为了做到这一点,我们将首先获得每个球队主场的经度和纬度。这些信息来自维基百科,可以在 Kaggle 上找到。

数据集|作者图片中的行示例

然后使用一些巧妙的三角学(这里描述为并在下面的函数中实现),我们将把体育场的位置转换成笛卡尔坐标,然后以有意义的方式输入到我们的算法中。

函数将纬度、经度点转换到笛卡尔坐标系

执行重新调整的代码

随着方法的确立,我们现在准备开始重新调整。我们首先导入必要的库并执行一些初步的数据操作。

接下来,我们将使用受约束的 K-means 算法将每个联盟中的球队分配到新的赛区。联盟和相关联的新分区分配作为键值对存储在字典中,以供以后使用。

最后,为了可视化原始的分区结构和新的重新排列的结构,我们使用了 plotly 包中的 Scattergeo 功能。

比较当前部门和重新调整的部门

我们将首先看一看美国职业棒球大联盟(MLB),它由 6 个分部组成,每个分部由 5 个队组成。请注意,一些 MLB 体育场,如芝加哥的 Wrigley Field 和 Guaranteed Rate Field,彼此距离很近,这使得标记很难在地图上分辨。

(左)目前的 MLB 部门结构|(右)重新调整的 MLB 部门结构|图片由作者提供

在上表中,我们放弃为重新调整的部门分配名称,因为在当前分组和重新调整的分组之间不一定存在一一对应关系。

接下来,我们考察 NFL 部门在重组后是如何变化的。NFL 分为 8 个分部,每个分部由 4 支球队组成。体育场比球队少,因为索菲体育场由洛杉矶充电器和洛杉矶公羊队共用,而大都会人寿体育场由纽约巨人队和纽约喷气机队共用。正如人们所料,那些共用一个体育场的球队在重新组合后总是被分在同一个组。

(左)当前的 NFL 部门结构|(右)重新调整的 NFL 部门结构|作者图片

NHL 分为 4 个赛区,每个赛区有 8 支球队。新泽西魔鬼队、纽约流浪者队和纽约岛民队的主场非常接近,使得标记很难在地图上分辨。

(左)当前的 NHL 部门结构|(右)重新调整的 NHL 部门结构|作者图片

NBA 和 MLS 都分成两个部分。NBA 的 30 支球队平分秋色,东部赛区 15 支,西部赛区 15 支。MLS 东部联盟有 14 支球队,而西部联盟有 13 支球队。正如下面的地图所强调的,两个联赛的重新划分与他们现在的形式是一样的。

NBA 分区结构|作者图片

MLS 部门结构|作者图片

评估行驶距离的改善

最后,我们将分析通过采用我们重新调整的部门可以节省多少出行距离。为此,我们将确定每个组别的球队之间的平均距离。例如,美联东区的 MLB 球队平均相距 602 英里。然后,对于每个联盟,我们将对当前分区的值和重新调整的分区的值进行平均。这将为每个联赛产生两个数字,分别测量在当前分区结构和我们重新调整的分区结构下球队与分区间对手的平均距离。为了实现这个过程,我们首先需要定义一个函数来返回两个纬度和经度点之间的距离(以英里为单位)。这可以通过这里描述的哈弗辛公式完成,并在下面的函数中实现。

函数查找两个纬度、经度点之间的距离(以英里为单位)

然后,我们可以使用下面的代码计算并打印每个联盟的两个最终平均值。

在目前的分区结构下,MLB 车队与同级别其他车队的平均差距为 608 英里,而在重新调整后的分区结构下,这一差距降至 436 英里。如下表所示,NFL 和 NHL 的距离也有类似的缩短。

结论

虽然计算最优的重组部门只是一个简单的数学问题,但实施它们需要利益相关者之间的巨大合作。许多球迷、球员和团队人员无疑会引用长期部门竞争的损失作为稍微短的部门间旅行的高价格。一些人可能会说,如此激烈的重新调整会让游戏失去其“历史”和“文化”的一个基本部分,更不用说安排竞争游戏以提高收视率的机会了。然而,积极的影响将是可以衡量的和直接的。与团队交通相关的碳排放和费用将会减少,与长途旅行相关的球员疲劳也会减少。这些好处会随着重新划分的比赛和赛季数量的增加而积累。虽然一些旧的部门竞争将会失败,但这些比赛仍然会发生,因为只有一小部分比赛是针对部门间的对手。新的竞争将会随着重新组合的队伍开始频繁地相互竞争而发展。

从更广阔的角度来看,人们可以想象,用于重新排列划分的数学方法可以很容易地应用于其他地理聚类问题。例如,给定 12 个商店位置和 3 个区域经理,公司可以使用约束 K-means 聚类算法将经理最优地分配到 4 个商店的组中。

参考

[1] J .利维-克雷默,k-均值约束 (2020)

每个人都应该参加数据科学课程的 5 个理由

原文:https://towardsdatascience.com/reasons-why-everyone-should-do-a-data-science-class-or-related-46406eea5439

意见

为什么您应该了解数据及其科学

图片来自 Unsplash

数据科学目前是一个热门话题,你可以认为这是一个暂时的炒作——或者不是,这取决于你的观点。无论你持何种观点,有一件事是肯定的:数据科学是在这里停留。而且到处都会是

【1920 年至 2019 年“数据科学”的谷歌 Ngram 浏览器。数据来自谷歌,图片由作者提供。

那么,是什么让它如此受欢迎,为什么如此突然?“数据科学”兴起的一些原因可以总结为三个要点,上图也描述了谷歌 Ngram 查看器工具:

  • 过去几年中计算能力的提高****
  • 算法和框架的各种突破和发展****
  • 收集了越来越多的数据——有一种需要利用这些数据的感觉

…时间会告诉我们更多数据科学作为一个领域如此重要的原因。

数据科学知识不仅对那些整天开发模型、处理数据、分析统计数据和编码的人有用。我建议每个人都去上一堂数据科学的课,不管你从事什么行业。

数据科学对每个人都有用。

为什么?

理由 1 |它让你了解统计学

统计学是数据科学的主要组成部分。数据科学中使用的许多流行方法在核心上依赖于经典的统计模型。参加数据科学课程会让你对统计学有一个基本的了解,通常会帮助你更好地理解描述、情况和含义。

相关性与因果性

你会明白相关性并不总是等于因果关系,你不能仅仅因为某些事件之间存在相关性就对它们做出假设。因果关系意味着一件事引起另一件事,而相关性只是两个事件之间的简单关系,这两个事件相互联系起来。****

我们常说“相关性并不意味着因果关系”。

一个简单的例子可能是:我们通常观察到,当天气温暖和阳光充足时,冰淇淋的销售和鲨鱼攻击的数量会增加。但是,一个****不会导致另一个。它们与有关,因为好天气会让更多的人去海滩吃冰淇淋,去海里游泳,这反过来会增加鲨鱼袭击的可能性。****

在你的日常生活中,这将帮助你更好地理解情况和分析索赔。这将使你更有能力区分好的论点和错误的论点,并帮助你向你的同伴阐述有效和正确的论点。

平均值对中位数

理解平均值 和中位数之间的差异也是通常被低估的事情。人们倾向于更频繁地使用平均数而不是中位数。然而,均值可以隐藏数据上的信息,只有通过同时查看中值我们才能看到这些信息。

平均值是所有数据点的平均值-中值数据分成一半,即 50%的点高于中值,50%的点位于中值。

举个例子,让我们来看看下面的数据集 A 和 B,它们是关于人们的薪水的:

具有平均值和中值的两个数据集的示例。图片作者。

我们可以看到,A 组的平均值比中间值低得多。对于 B 组,我们将数据集中的一个值改为一个比 A 组大得多的数字:我们立即可以看到平均值是如何增加的,而中位数保持不变。突然,我们意识到平均值对于这组数据来说是一个很差的指示,因为它对极端值非常敏感。

在你的日常生活中,这将帮助你在更好地解释数据,并且当你有数据呈现给你时,会让你更加** 怀疑。它会让你明白数据不仅仅意味着,还会让你成为一个更** 可信精确 展示者,以防你自己也要展示数据。****

…还有更多!

理解统计学的基础知识会给你带来更多的好处,上面两个只是一小部分,是它在你日常生活中潜在价值的非常基本的例子。

理由 2 |它让你了解数据

这是相当类似的,并与原因 1 携手并进。没有统计学知识,你很难很好地理解数据。当我开始熟悉数据时,我意识到的主要事情之一是,你可以更好地理解围绕它的通用工具技术

我的意思是:

什么是数据?

您将了解数据可以以何种形式和类型进入:从结构化数据到非结构化数据。从定量和定性值,到名义值、序数、连续值和离散值。

你知道数据如何存储以及存储在哪里,举几个例子:

  • CSV、XML 和 XLS 文件,以及纯文本文件
  • 关系和非关系数据库
  • 二进制数据,比如图像。

您还将了解如何访问数据:我们可以在编程语言(f. e. R 或 Python)的帮助下读取 CSV 文件,或者用 SQL 语言直接从数据库中查询关系数据。

你了解数据的 4 个(甚至到 7 个 ) V:量、种类、准确性、速度等。

在日常生活中,这不仅会让你对 IT 有一个很好的总体了解,而且对于你可能正在进行的一些 IT 相关的对话和小型会谈来说,这也是很方便的知识

我如何显示数据?

数据本身是第一步,解释和可视化是下一步。

大多数基础数据科学课程都会涉及到数据可视化的主题,很快你就会听到这样一句话:

"最好不要使用饼图来可视化数据."

一开始你可能会想:为什么是?但是你看到的例子越多,你就会越意识到其中的道理,很快你可能会提倡自己不要使用饼状图。

条形图与饼图比较。图片作者。

在上面的图片中,我们有一个很好的例子来说明为什么饼状图(一般来说)比 f. e .条形图更不利于可视化。众所周知,人眼很难理解和估计角度,尤其是 T2,饼图的碎片越多。如果没有对饼图进行注释,您会正确估计“借记卡”和“数字钱包”组值之间的差异吗?也许吧,但阅读条形图通常会更容易、更清晰,因为它让我们可以轻松地比较哪怕是最细微的数值差异。

在你的日常生活中,这会让你明白,你应该对你对饼状图的解读持保留态度,因为这些可能会误导你。这也将使你创造更好的演示,并成为你的数据的更有影响力的演示者

理由 3 |你洞察编程逻辑

几乎在数据科学的每一门课中,你都可能需要学习一些编程的入门课程。这对你来说可能听起来很可怕——相信我,一点也不可怕。你不必为了从中获益而将你的编程技能提升到专家水平。

在我上第一堂数据科学课之前,我从未做过任何编程。所以当然,这是相当艰难的——但是你开始学习的每一件新东西在开始时都是艰难的。问题是:你会坚持学多久?或者说,你会有多一致?不管你会坚持多久,对编程有一点基本的了解已经有它的好处了。我的意思是:

透过更符合逻辑的镜头看问题

首先,你会开始有一种感觉,觉得思考更有逻辑,更有计划性。它将帮助你通过一个更符合逻辑的视角来看待这个世界。我们现代世界的大多数解决方案都是基于技术的,并且已经以某种方式被编程。你将会被最终理解这些事情是如何潜在工作的启发,高层次的。

自动完成自己的任务

更进一步,你甚至可以开始做你自己的小编程项目。你可以开始写你自己的脚本自动化日常任务。听起来很酷,不是吗?我可以确认:

我给自己写了一堆脚本来自动化某些时间密集型任务,比如 f. e .复制粘贴或者抓取内容,或者在我的电脑上寻找重复的图像。看到这些项目在几个小时的工作后变成现实,会带来一种令人满足的感觉。

你现在可能会问自己从哪种编程语言开始。在我看来,几乎任何一种编程语言都会给你一个更符合逻辑的世界观。然而,有些语言比其他语言更适合某些任务,也比其他语言更灵活。我个人从 R 开始,继续(并留在)Python,我研究了 SQL、Matlab 和 Java。

说到易用性对数据科学相关内容的支持,我绝对可以推荐从 Python 开始。

理由 4|你知道人们谈论什么

你可能会遇到这样的情况:你与人交谈,然后关于数据科学的对话出现了。现在你将成为这次谈话的一部分,因为你对谈话的内容有了基本的了解。这可能会帮助你进行更有意义的谈话,让你比以前更容易与一些人或团队建立联系。

无论你身处哪个行业,你对数据科学的理解都会让你更加博学,并让你有机会成为团队之间的纽带——这个人既了解故事的一面,也了解故事的数据科学部分

你可能会读到一篇关于一些人工智能算法接管世界的文章,或者一种新开发的预测算法的一些突破:现在你会对有一个简单的了解,这是由组成的。当然还不详细,但是足够让你理解这篇文章可能包含的内容的含义。

在日常生活中,人与人之间的社会联系以及跟上技术发展是我们未来的两个非常重要的部分。技术无处不在,不会少。有必要了解每天围绕着我们的

理由 5|你理解其中的含义

上过几堂数据科学的入门课后,你会更好地理解为什么数据如此有价值,你能用它实现什么,以及为什么在线广告业务如此成功。

你会明白,数据科学中的技术是伟大的,可以用 做好 ,但那个数据也可以是关于,f. e. 有偏差的时候。可能还有关于数据伦理的担忧,或者关于某个算法缺乏可解释性的担忧。

在日常生活中,这将让你很好地理解为什么会有这样的潜力,而且还有关于数据的关注。你将开始问自己更多关于你自己的数据和数据隐私的问题。

结论性理由

总而言之,在你上完第一堂数据科学课后,你将能够更好地与数据科学家的工作联系起来,现在你知道了他们的一些技巧:你将区分相关性与因果关系将开始利用中位数,而你将尽量避免使用饼状图。最终,你会更擅长解读数据,并提供更好的演示

我亲身经历了阅读数据科学的许多好处,因此我强烈推荐任何人参加入门课程。你不必成为一个专家,只是一个基本的了解已经将是非常有益的。希望你也这么想!🎉

欢迎在评论中与我分享您的反馈!💬

下面你会发现几个链接,我个人觉得在开始阅读和学习数据科学的时候非常有用。

建议和参考:

[1]Coursera.com,IBM,数据科学简介 (2022)(优秀的入门级课程,从基础开始)

[2]Coursera.com,深度学习。AI &斯坦福大学,机器学习 (2022)(令人惊讶,但相当高级的课程,深入细节)

[3] 关于 Medium.com 的数据科学(你经常会在这里找到非常好的有教育意义的文章)

食谱烹饪分类

原文:https://towardsdatascience.com/recipe-cuisine-classification-278ea0837c94

伯特变压器&食物-药物负相互作用

Unsplash 上由S O C I A L C U T拍摄

介绍

几个模型架构可以用来执行烹饪分类。一些最受欢迎的模型,按照复杂程度的增加顺序,是支持向量机(Pouladzadeh 等人,2015 年)、BERT (Devlin 等人,2018 年)、RoBERTa(刘等人,2019 年)或 3(布朗等人,2020 年)模型。

变形金刚(电影名)

与计算机视觉不同,在自然语言处理(NLP)中,预训练模型最近才变得广泛可用。部分由于文本数据集的稀缺,NLP 的进展缓慢,直到变压器(BERT)的双向编码器表示的发布(Devlin 等人,2018)。

BERT 有两种预训练模型:基本模型和大型模型。虽然两者使用相同的架构,但前者包含 1.1 亿个参数,而后者包含 3.45 亿个参数。能够使用预先训练的模型并对其进行微调以适应不同的任务意味着即使在缺乏足够的数据来训练具有数百万个参数的模型的情况下,也可以避免模型过度拟合。较大的模型通常具有更好的性能(Devlin 等人,2018 年)。

在微调 BERT 时,有三种可能性(Devlin et al .,2018):

  • 训练所有建筑
  • 训练一些层,冷冻其他层
  • 冻结整个网络并将额外的层附加到模型上

与其他神经网络架构一样,提供的训练数据量越大,它的性能就越好。因此,一个重要的收获是,运行更多代的 BERT 将导致更高的准确性,并有足够的数据。就所需的计算能力而言,它相当于使用多个最先进的 GPU 进行几天的训练(Devlin et al .,2018)。

BERT 的另一个特点是它的双向方法。相比之下,以前的努力要么从左到右要么结合从左到右和从右到左的训练来查看文本序列(Devlin 等人,2018 年)。

与递归神经网络和长短期记忆神经网络相比,BERT 的一个优点是它可以并行化。这意味着它可以通过在多个 GPU 中训练来加速。如果输入数据是文本序列,这意味着它可以一次接受多个标记作为输入(Devlin et al .,2018)。

变形金刚的两个后验实现分别是 Research 和 OpenAI 的鲁棒优化 BERT 预训练方法(RoBERTa)(刘等,2019)和生成预训练变形金刚 3(3)(布朗等,2020)。

BERT transformer 用于根据配料列表构建菜肴预测模型。这使得在烹饪水平上用于治疗 AD 的药物的负面食物-药物相互作用最小化。

烹饪/药物的负面相互作用

因为 BERT 模型以前是用数百万个样本训练的,所以它们可以通过更少的微调数据被广泛的应用程序重新调整用途(Devlin 等人,2018)。在这种特定情况下,烹饪预测。

配料和食谱的选择受地理和文化的影响很大。一些食谱比其他食谱更富含已知 AD 和/或新冠肺炎(Laponogov 等人,2021 年)打浆特性的成分。在烹饪水平上考虑这种变化允许我们在烹饪水平上预测最有可能减少与药物的负面相互作用的数量,即使没有所有成分的这种信息。食物-药物相互作用可导致药物生物利用度的显著降低,这是由于食物分子和药物化学成分之间的直接相互作用,或者是由于食物摄入的生理反应。世界上销售最多的一些药物是抗肿瘤药和针对神经系统疾病的药物。图 1 中提供的信息有助于我们根据食谱估算食谱的健康益处(Jovanovik 等人,2015 年)。

图 1 药物(左图)和菜肴(上图)之间现有相互作用的百分比。作为参考,对于北美料理()和抗肿瘤药物( A ),预计 0.005288%的负相互作用。[图片来源=(约万诺维克等人,2015)]

另一方面,与其他行业一样,推荐系统通常会将产品分为多个类别。食物推荐系统可以通过将用户导向他们最喜欢的选择而受益于美食分类(Anderson 等人,2018)。

一个烹饪分类器被训练、测试和验证,以预测神经(AD)和抗感染(新冠肺炎)药物与烹饪确定的食谱之间的负面相互作用。

方法

使用 BERT 变换器,训练、验证和测试了一个根据配料列表预测菜肴的模型。

Kaggle 和 Nature 数据集包含大约 100000 个配方,用于训练变压器。每个食谱都包括一份配料清单,以及相应的菜肴。一份按字母顺序排列的配料清单给了模型。数据集的 80%用于训练,10%用于验证,10%用于测试。

在训练模型之前,使用 BERT 记号化器,记号化步骤将每个配方分成 50 个记号。初始变压器的所有参数都被冻结。

为神经网络选择的体系结构包括一个下降层,一个包含 768 层 512 个节点的密集层 1 和一个包含 512 层的层 2,每个层有 11 个节点(与菜肴的数量相同)。

在训练时,使用了实现权重衰减为 1e-3 的 Adam 算法的优化器。负对数似然损失被用作损失函数。并且模型收敛需要 40 个时期。训练是在 Google Collab 的一个 GPU 中进行的。

模型的性能是通过对每种菜肴的精确度、召回率和 f1 分数来评估的。

最后,介绍了在抗感染类(针对新冠肺炎)和神经类(针对 AD)中具有最低数量的负面食物-药物相互作用的烹饪。

结果和讨论

菜肴分类

在图 2 中, Kaggle & Nature 数据集基于配料数量的食谱数量分布被表示。

图 2 配方分布在 Kaggle & Nature 数据集中对应的配料数量

根据上图,Kaggle 和 Nature 数据集中的大多数食谱包含 6 到 8 种成分。平均来说,每份食谱有 12 种配料。没有一个食谱包含超过 60 种成分。

在图 3 中,显示了 Kaggle & Nature 数据集中每种菜肴的食谱数量。

图 3Kaggle & Nature 数据集中每个菜系的菜谱数量。

共有 96250 种食谱,涵盖 11 大菜系。食谱最多的三大菜系分别是北美、南欧和拉美,分别有 45843、14178 和 11892 种食谱。

在表 1 中,展示了菜肴分类器精度、召回率和 F1 分数。

表 1 模型分类准确度——精度、召回率和 f1-分数被详细描述到训练数据集中的每一个菜系

南欧、拉丁美洲、北美和东亚的 F1 得分高于 55%。

图 4 显示了与神经系统药物(AD)和抗感染药物(新冠肺炎)的负面食物-药物相互作用最少的菜肴的地理分布。

图 4 拉丁美洲美食与神经类药物——AD 的负面相互作用数量最低。西欧抗感染药效果最好——新冠肺炎。

拉丁美洲和西欧的菜肴分别显示出与神经系统药物和抗感染药物的负面相互作用最少。

Kaggle & Nature 数据库包含分布在各大菜系的不平衡数量的食谱。虽然 BERT 模型是针对不平衡数据集进行训练的,但这可能仍然会影响模型的预测能力。只考虑成分列表而不考虑各自的食谱说明可能会导致较差的检测。以及北欧某些菜系的食谱数据集的有限大小。某些菜系的精确度、召回率和 f1 评分较低,可能与缺乏独特的配料有关。

后续步骤

为了实现训练更准确的烹饪分类器的目标,我们将优化电流互感器的架构(考虑罗伯塔和 GPT-3 的实现),并考虑烹饪过程,而不仅仅是配料。使用带有更多食谱的烹饪标签数据集也将有助于在 11 种烹饪中获得更高的检测率。

参考

Pouladzadeh,p .,Shirmohammadi,s .,Bakirov,a .,Bulut,a .,和 Yassine,A. (2015 年)。基于云的食品分类 SVM。多媒体工具及应用74 (14),5243–5260。https://doi.org/10.1007/s11042-014-2116-x

Devlin,j .,Chang,m-w .,Lee,k .,& Toutanova,K. (2018 年)。 BERT:用于语言理解的深度双向转换器的预训练

刘,y .,奥特,m .,戈亚尔,n .,杜,j .,乔希,m .,陈,d .,利维,o .,刘易斯,m .,泽特勒莫耶,l .,&斯托扬诺夫,V. (2019)。 RoBERTa:一种稳健优化的 BERT 预训练方法

Brown,T. B .、Mann,b .、Ryder,n .、Subbiah,m .、Kaplan,j .、Dhariwal,p .、Neelakantan,a .、Shyam,p .、Sastry,g .、Askell,a .、Agarwal,s .、Herbert-Voss,a .、Krueger,g .、Henighan,t .、Child,r .、Ramesh,a .、Ziegler,D. M .、Wu,j .、Winter,c .……Amodei,D. (2020 年)。语言模型是一次性学习者

Laponogov,I .,Gonzalez,g .,Shepherd,m .,Qureshi,a .,Veselkov,d .,Charkoftaki,g .,瓦西利乌,v .,Youssef,j .,Mirnezami,r .,Bronstein,m .,和 Veselkov,K. (2021)。网络机器学习绘制富含植物化学成分的“超级食物”来对抗新冠肺炎。人类基因组学*, 15 (1),1。【https://doi.org/10.1186/s40246-020-00297-x *

Jovanovik,m .,Bogojeska,a .,Trajanov,d .,和 Kocarev,L. (2015 年)。使用关联数据方法推断烹饪-药物相互作用。科学报道5 (1),9346。https://doi.org/10.1038/srep09346

用机器学习识别排球比赛阶段

原文:https://towardsdatascience.com/recognize-volleyball-game-stages-with-machine-learning-f7af06c414b2

使用人物探测器对玩家位置进行分类

排球比赛阶段

用人工智能分析排球比赛有多种方法。我曾经跟踪球是为了把游戏时间从整个视频中分离出来。

另一种方法是确定游戏阶段,并根据阶段流程做出决策。

让我们假设排球比赛包括 4 个阶段:

  • 没有游戏。没有人或几个人在院子里闲逛。
  • 静止位置。上菜前人们都是站着不动的。
  • 游戏。
  • 欢呼。即使拉力赛输了,聚在一起互相加油也是很常见的。

所有这些阶段都因人们相对于法庭的位置和彼此的位置而不同。这意味着我们首先要做的是找到照片中的人。

人物检测

我在以前的文章中对的最佳探测器做了一些研究,因此我将使用 MobileSSD 来实现这个目的。

人物探测器生成带有玩家坐标的盒子。该信息将是阶段分类模型的输入。

有时探测器会漏掉人,这给数据增加了一些噪声。

数据准备

出于训练目的,需要手动对图片进行分类,将它们移动到 4 个不同的文件夹中。

然后是一个很好的问题,关于如何表示机器学习的输入数据。有两个主要选项:

  • 使用检测到的框的数字表示
  • 使用图片

我决定使用图片选项,因为:

  • 输入大小应该是稳定的,但是我们事先不知道检测的次数。球场上可能有多达 12 名球员,但球迷、裁判和替补队员可能会增加这个数字。图片大小是固定的。
  • 检测是无序的。为了将它们用作数组输入,我们需要以某种方式对它们进行排序,例如,从左到右、按大小排序等等,但哪种选择更好并不明显。对于图片,我们按原样使用检测。
  • 人性化。看着箱子上的数字,很难说现在是什么阶段。一张图片提供了很好的线索。

为了将检测结果表示为输入图片,我将它们绘制为黑色背景上的白色实心矩形,大小调整为 64x64。

def get_mask(boxes):  
  pic = np.zeros((H,W,1), np.uint8)  
  clr = 255   
  for r in boxes:     
    cv.rectangle(pic, (r[0], r[1]), (r[0] + r[2], r[1] + r[3]), clr, thickness=-1)   
  pic = cv.resize(pic, (64, 64))  
  return pic

然后我为每个阶段准备了面具:

欢呼

不休息

静止位置

选择分类方法

多标签分类有多种方式,所以选择并不容易。最后,我选择了两个最基本也是最流行的方法:

KNN 在相同数据上显示 81%的准确率,在随机样本上显示 66%的准确率。

TFK 也稳定在 80%左右。

测试分类

我们来挑选一个测试排球的视频。我使用了由格拉茨计算机视觉研究所提供的奥地利业余联盟的公开视频集。

  1. 从游戏中提取帧。人们移动得不是很快,所以每秒 2 帧可能就足够了:
ffmpeg -i video.mp4 -r 2 frames/%05d.jpg

2.对帧运行 MobileSSD 检测器并生成 json 文件。

def detect_pic(ssd, img, thr = 0.3):
  rows = img.shape[0]
  cols = img.shape[1] ssd.setInput(cv.dnn.blobFromImage(img, 1.0/127.5, (600, 600), (127.5, 127.5, 127.5), swapRB=True, crop=False))
  out = ssd.forward()
  r = np.array([cols, rows, cols, rows]) boxes = []
  scores = []
  for d in out[0,0,:,:]:
    score = float(d[2])
    cls = int(d[1])
    if cls != 1:
      continue
    if score < thr:
      continue box = d[3:7] * r
    box[2] -= box[0]
    box[3] -= box[1]
    boxes.append(box.astype("int"))
    scores.append(score) if len(boxes) < 1:
    return [] dxs = cv.dnn.NMSBoxes(boxes, scores, thr, 0.1)
  return [boxes[i].tolist() for i in dxs.flatten()]

3.检测转换成输入掩码。

4.对输入掩码进行分类

这两种方法的表现都比训练时差:

  • KNN——72%
  • TFK——70%

5.从阶段预测中建立拉力

令人惊讶的是,就反弹的可靠性而言,即使是 KNN 也略胜 TFK,TFK 表现得更好:

  • KNN 认可了 29 次集会中的 8 次
  • TFK 承认了 20 次集会

结论

拉力赛削减是这项工作的最终目标,结果是有希望的。即使不够精确,TFK·卡特也能为视频编辑和观众节省一些时间。

削减反弹有多种原因:

  • 密集的整场比赛录音。它使视频更小(约为原始的 30%),存储更便宜,加载更快,对观众来说更迷人。
  • 将漫长的游戏分割成有意义的部分。这对球员、他们的父母和粉丝来说很重要——小的拉力赛片段可以很容易地分享或保存在个人照片中。

到目前为止,自动对倒可能比目前更精确,第一批结果是有希望的,能够产生一些有用的结果。

链接

参考资料:

(视频数据集所有者要求

利用时空上下文改进体育活动识别
乔治·瓦尔特纳、托马斯·毛特纳和霍斯特·比朔夫
Proc。DVS-体育领域计算机科学会议(DVS/GSSS),2014 年

自动化体育游戏分析的室内活动检测与识别
乔治·瓦尔特纳、托马斯·毛特纳和霍斯特·比朔夫
Proc。奥地利模式识别协会(AAPR/OAGM)研讨会,2014 年

矩阵分解——奇异值分解(SVD)解释

原文:https://towardsdatascience.com/recommendation-system-matrix-factorization-svd-explained-c9a50d93e488

利用潜在因素推荐构建推荐系统管道

图片由 Vlado PaunovicUnsplash 拍摄

本文将概述推荐系统矩阵分解的直觉和 Python 实现。以下是文章的提纲。

目录

  • 矩阵分解背后的直觉
  • 奇异值分解
    -奇异值分解的数学
    -示例演练
  • 问题陈述
  • 数据
    -要求
  • 解决方案架构
  • SVD 推荐系统实现
    -生成用户-项目矩阵
    -计算 SVD
    -生成推荐
  • 挑战
  • 结束语
  • 资源

矩阵分解背后的直觉

推荐引擎是机器学习的一个子类,旨在为一些用户或项目提供评级。在推荐系统中,矩阵分解属于协同过滤的范畴。直观地说,协同过滤旨在基于与用户 A 相似的其他用户的交互来识别用户 A 喜欢的项目。您可以将此视为一个优化问题,我们的目标是找到一种方法来为给定的产品/用户产生最佳评级。

在众多公司和行业中使用的推荐引擎存在各种各样的问题。登录脸书/ Meta、Instagram、Twitter、Pinterest、亚马逊等时。该平台会根据你自己的历史以及与你相似的其他用户的历史,向你推荐帖子/项目。相似性是一个广义的术语,你可以被认为是相似的用户,因为你有相似的品味,追随相同的人,喜欢/互动相同的帖子和用户,等等。

矩阵分解背后的直觉相当简单,给定一些用户-项目矩阵,您希望分解该矩阵,这样您就有一个独立的用户矩阵和一个项目矩阵。这允许我们对原始矩阵的每个潜在因子应用不同的正则化。例如,当产品是歌曲时,因素可以测量说唱和国家之间的比较、歌曲被播放的次数与歌曲持续时间的计数等。下图说明了输入用户项目(歌曲)矩阵 m 的分解。

矩阵分解例子。图片由作者提供。

这种方法是由西蒙·芬克在 2006 年推广的,因为它在网飞挑战赛中广泛流行[1]。Funk SVD 是 Simon Funk 提出的算法名称。尽管 SVD(支持向量分解)名副其实,但并没有 SVD 技术应用于它[1]。

奇异值分解

奇异值分解的数学

给定一些输入矩阵 M,SVD 的公式可以概括如下:

奇异值分解公式(图片由作者提供)。

M : An m x n matrix which you want to decompose
U : An m x m complex unitary matrix (left singular vectors)
Σ : An m x n rectangular diagonal matrix (holds the eigenvalues)
V : An n x n complex unitary matrix (right singular vectors)

步骤 1:通过将矩阵 m 乘以它的转置:M*Mᵀ,将它转换成方阵

第二步:计算矩阵 M*Mᵀ.的特征值和特征向量这样做的结果将对应于σ和 U 矩阵。

线性变换的特征向量是一个非零向量,当对其应用线性变换时,它最多改变一个标量因子。相应的特征值(通常用λ表示)是本征向量缩放的因子。
-【4】https://en.wikipedia.org/wiki/Eigenvalues_and_eigenvectors

第三步:用下面的公式求解 v:v = 1/σmᵀ u

示例演练

我们来计算下面这个矩阵 m 的 SVD。

SVD 的输入矩阵。图片由作者提供

M*Mᵀ

将输入矩阵 M 乘以其转置矩阵

步骤 2:计算特征值和特征向量

你可以使用下面的计算器来计算相关的特征值和特征向量。下面的链接提供了如何计算这些值的分步指南。如果你对此不感兴趣,那么就用 numpy 来计算特征值和特征向量

M*Mᵀ.结果的相关特征值和特征向量图片由作者提供

取非零特征值的平方根,生成σ矩阵:

特征值的平方根。图片由作者提供。

特征值的平方根在对角线上的适马矩阵。图片由作者提供。

为了计算 U 矩阵,你必须计算每个特征向量方向的单位向量。下面的链接概述了如何计算单位向量。

特征向量 1 的单位向量:[1,2,1]。图片由作者提供。

特征向量 2 的单位向量:[1,-1,1]。图片由作者提供。

特征向量 3 的单位向量:[1,0,1]。图片由作者提供。

这就产生了最终的 U 矩阵。

从特征向量的单位向量计算的 u 矩阵。图片由作者提供。

第三步:通过公式求解 v:v = 1/σmᵀ u

V 的计算值。图片由作者提供。

您可以参考下面的参考资料,深入了解 SVD 背后的计算。

问题陈述

我们希望使用音乐数据集建立一个协同过滤推荐系统。生成用户项目矩阵并结合余弦相似度使用奇异值分解来识别用户可能感兴趣的歌曲。

数据

出于本教程的目的,我们将使用 pandas、numpy 和 random 来合成数据集。本节将提供一个脚本,该脚本将合成一个与音乐相关的数据集。该数据集将在以下部分中用于推荐系统的应用。本教程的目的不是获得有意义的结果,而是向用户展示奇异值分解背后的实现。因此,这些建议的结果将是无意义的,但这些方法将类似于工业生产级环境中的方法。

要求

python=3.8.8
pandas=1.2.4
numpy=1.20.1

运行该脚本后,您应该会看到一些类似于下图的示例数据(不精确,因为它是随机生成的)。

合成音乐数据。图片由作者提供。

解决方案架构

现在我们有了一个数据集,我们可以考虑如何解决这个问题。我们将从基于与监听次数相关的值生成用户条目矩阵开始。这种情况下的项目是 song _ ids,而用户对应于 user_id。接下来,我们可以使用 SVD 来计算相关的 U、σ和 V 矩阵。最后,我们可以使用余弦相似性来比较用户向量和项目向量,以确定用户感兴趣的前 N 个项目。

奇异值分解推荐系统的实现

生成用户-项目矩阵

计算奇异值分解

生成建议

挑战

在推荐系统中使用协同过滤方法会面临各种各样的挑战,其中最常见的是冷启动问题。冷启动问题指的是推荐引擎不能为新的项目/用户产生合适的预测。或者具有低交互量的用户/项目,本质上它生成的用户-项目矩阵非常稀疏。这是因为缺乏新产品或产品发布的信息。

源于冷启动问题的协同过滤的另一个常见问题是推荐的多样性。这种方法基于其他用户/产品的历史交互来推荐产品/用户,本质上意味着非常受欢迎的产品几乎总是比交互太少的其他产品更受推荐。

可扩展性是协同过滤算法的一个常见问题,随着用户、产品和交互的数量增加,用户-项目矩阵的大小也成比例地增加。当数据大量扩展时,这会导致问题,并且会增加计算时间。通常,处理较大数据集的代价是性能会提高,而速度会降低,反之亦然。

结束语

本文概述了矩阵分解背后的直觉、数学和实现,特别是奇异值分解(SVD)。我展示了如何使用 SVD 创建一个协同过滤推荐引擎,它使用了我们用 Python 合成的音乐数据集。

请在我的 GitHub 页面这里查看与本教程相关的资源库。

如果你想转型进入数据行业,并希望得到经验丰富的导师的指导和指引,那么你可能想看看最敏锐的头脑。Sharpest Minds 是一个导师平台,导师(他们是经验丰富的实践数据科学家、机器学习工程师、研究科学家、首席技术官等。)将有助于你的发展和学习在数据领域找到一份工作。在这里查看。

资源

如果你喜欢读这篇文章,这里有一个与数据科学和机器学习相关的列表,你可能会感兴趣。

推荐系统——机器学习模型完全指南

原文:https://towardsdatascience.com/recommender-systems-a-complete-guide-to-machine-learning-models-96d3f94ea748

利用数据帮助用户发现新内容

Javier Allegue Barros 在 Unsplash 上拍摄的照片

推荐系统:为什么和如何?

推荐系统 是为每个用户最相关的项目提供个性化建议的算法。随着可用在线内容的大规模增长,用户已经被淹没在选择中。因此,为了提高用户满意度和参与度,网络平台向每个用户提供商品推荐是至关重要的。

YouTube 向用户推荐视频,帮助他们在大量可用内容中发现和观看与他们相关的内容。(图片由作者提供)

下面的列表展示了拥有大量可用内容的知名网络平台的例子,这些平台需要高效的推荐系统来保持用户的兴趣。

  1. Youtube。每分钟都有人上传 500 个小时的视频,也就是说,一个用户要花 82 年才能看完上一个小时上传的所有视频。
  2. https://www.spotify.com/****。用户可以收听超过8000 万首歌曲和播客
  3. 亚马逊 用户可以购买超过3.5 亿种不同的产品

所有这些平台都使用强大的机器学习模型,以便为每个用户生成相关的推荐。

显性反馈与隐性反馈

在推荐系统中,机器学习模型用于预测用户 u 对某个项目It8】的评分 rᵤᵢ 。在推理时,我们向每个用户 u 推荐预测评分最高的 lrᵤt16】t17】ᵢt19】。****

因此,我们需要收集用户反馈,这样我们就可以有一个训练和评估模型的基础事实。这里必须对显式反馈隐式反馈进行重要区分。

推荐系统中显式和隐式反馈的比较。(图片由作者提供)

显性反馈 是用户明确给出的一个评分,用以表达对某个项目的满意程度。例如:购买产品后给出的从 1 到 5 的星级数,观看视频后给出的拇指向上/向下等。这种反馈提供了用户喜欢某件商品的详细信息,但是很难收集,因为大多数用户通常不会为他们购买的每件商品写评论或给出明确的评级。

https://en.wikipedia.org/wiki/Implicit_data_collection另一方面,假设用户-项目交互是偏好的指示。例子是:用户的购买/浏览历史、用户播放的歌曲列表等。这种反馈是极其丰富的,但同时也是不太详细的更嘈杂的(例如,某人可能会购买一件产品作为礼物送给别人)。然而,与这种可用数据的庞大规模相比,这种噪音变得可以忽略不计,并且大多数现代推荐系统倾向于依赖隐式反馈。****

显性反馈和隐性反馈数据集的用户项目评分矩阵。(图片由作者提供)

收集到显性或隐性反馈后,我们可以创建用户项评分矩阵 rᵤᵢ 。对于明确的反馈,在 rᵤᵢ 中的每个条目都是一个数值——例如 rᵤᵢ = “由 u 给电影 i ”的星星——或“?”如果用户 u 没有对项目 i 进行评分。对于隐式反馈, rᵤᵢ 中的值是表示存在或不存在交互的布尔值——例如, rᵤᵢ = “用户 u 看电影了吗。请注意,矩阵 rᵤᵢ 非常稀疏,因为用户与所有可用内容中的很少项目交互,他们查看的项目更少!

基于内容的方法与协作过滤方法

推荐系统可以根据用来预测用户偏好的信息类型分为基于内容的协同过滤。

推荐系统中基于内容和协作过滤的方法。(作者图片)

基于内容的方法

基于内容的方法 通过 已知的元数据来描述**用户和项目。每一项 i 都由一组相关的标签表示——例如 IMDb 平台的电影可以被标记为“动作”、“喜剧”、“等。每个用户 u 都由一个用户配置文件表示,该文件可以根据已知的用户信息(例如,性别和年龄)或用户过去的活动创建。****

用这种方法训练机器学习模型,我们可以使用 k-NN 模型 例如:如果我们知道用户 u 购买了一件 i 的商品,我们可以向 u 推荐那些与 i 最相似的商品。

这种方法的优势是项目元数据是预先已知的,因此我们也可以将其应用于冷启动场景,其中新项目或用户被添加到平台,我们没有用户-项目交互来训练我们的模型。的缺点是我们没有使用所有已知的用户-项目交互(每个用户被单独对待),并且我们需要知道每个项目和用户的元数据信息。

协同过滤方法

协同过滤方法 不使用项目或用户元数据,而是尝试利用所有用户的反馈或活动历史,以便通过从观察到的活动推断用户和项目之间的相互依赖性来预测用户对给定项目的评级。

为了用这种方法训练机器学习模型,我们通常会尝试对评级矩阵 rᵤᵢ 进行聚类或因式分解,以便对未观察到的对( u,i ),即 rᵤᵢ = "?进行预测。在本文的下文中,我们将介绍 矩阵分解算法 ,这是这一类中最常用的方法。

这种方法的优势在于使用了整套用户-项目交互(即矩阵 rᵤᵢ ),这通常允许获得比使用基于内容的模型更高的准确性。这种方法的缺点是在模型拟合之前需要一些用户交互。

混合方法

最后,还有 混合方法 尝试使用已知的元数据和观察到的用户-项目交互集。这种方法结合了基于内容和协作过滤方法的优点,并允许获得最佳结果。在本文的后面,我们将介绍 LightFM ,它是这类方法中最流行的算法。

协同过滤:矩阵分解

矩阵分解算法 可能是推荐系统中最流行、最有效的协同过滤方法。矩阵因式分解是一种潜在因子模型假设对于每个用户 u 和项目 i个潜在向量表示 pᵤ、qᵢt13】∈r**ᶠs . t .rᵤᵢ可以用 pᵤqᵢ 来唯一地表示——即“因式分解”。Python 库 惊奇 提供了这些方法的优秀实现。****

显式反馈的矩阵分解

最简单的想法是通过一个线性模型对用户-项目交互进行建模。为了了解 pᵤqᵢ 的值,我们可以在已知 rᵤᵢ 的组 K 对( ui )上最小化一个正则化的 MSE 损失。这样得到的算法称为【PMF】

概率矩阵分解:rᵤᵢ模型和损失函数。

损失函数可以通过两种不同的方式最小化。第一种方法是使用【SGD】。SGD 容易实现,但是可能会有一些问题,因为 pᵤ和 qᵢ都是未知的,因此损失函数不是凸的。为了解决这个问题,我们可以选择固定值 pᵤqᵢ ,得到一个凸线性回归问题,这个问题很容易用普通最小二乘法(OLS) 来解决。这第二种方法被称为【ALS】,允许显著的并行化和加速。

PMF 算法后来被推广为【SVD】算法,在模型中引入了偏差项**。更具体地说,分别测量用户 u 和项目 i 的观察评分偏差,而 μ 是总体平均评分。这些术语通常解释了大多数观察到的评级 rᵤᵢ ,因为一些项目普遍获得更好/更差的评级,而一些用户对他们的评级一贯或多或少慷慨。****

SVD 算法,概率矩阵分解的推广。

隐式反馈的矩阵分解

SVD 方法 适应于隐式反馈数据集 。这个想法是将隐性反馈视为信心的间接测量。假设隐式反馈 tᵤᵢ 测量用户 u 看过电影 i 的百分比——例如 tᵤᵢ = 0 表示 u 从未看过 itᵤᵢ = 0.1 表示他只看了其中的 10%, tᵤᵢ = 2 表示直觉上,用户更可能对他们看过两次的电影感兴趣,而不是对他们从未看过的电影感兴趣。我们因此定义一个**信心矩阵cᵤᵢt35】和一个评级矩阵 rᵤᵢ 如下。****

隐式反馈的置信矩阵和评级矩阵。

然后,我们可以使用用于奇异值分解的相同线性模型对观察到的 rᵤᵢ 进行建模,但损失函数略有不同。首先,我们计算所有( ui )对的损失——与显式情况不同,如果用户 u 从未与 i 交互,我们就有了 rᵤᵢ = 0,而不是 rᵤᵢ = ”。第二,我们通过 cᵤᵢ 认为 u 喜欢 i. 来衡量每一项损失

隐式反馈奇异值分解的损失函数。

最后,当我们可以访问显式和隐式反馈时,可以使用svd++**算法。这可能非常有用,因为通常用户会与许多项目进行交互(=隐式反馈),但只对其中的一小部分进行评级(=显式反馈)。让我们为每个用户 u 表示 N(u) 已经与 u 交互的项目集合。然后,我们假设与项目 j 的隐式交互与新的潜在向量zⱼr相关联。SVD++算法通过将这些潜在因素的加权和包含到用户表示中来修改 SVD 的线性模型 zⱼ.******

SVD++用于混合(显式+隐式)反馈

混合方法:LightFM

基于矩阵分解的协同过滤方法通常会产生出色的结果,但在https://en.wikipedia.org/wiki/Cold_start_(recommender_systems)冷启动场景中——其中很少或没有交互数据可用于新项目和用户——它们无法做出良好的预测,因为它们缺乏估计潜在因素的数据。混合方法通过利用已知项目或用户元数据来改进矩阵分解模型,从而解决了这个问题。Python 库 LightFM 实现了最流行的混合算法之一。********

在 LightFM 中,我们假设为每个用户 u 收集了一组标签注释aᵁ(u】—例如“男性”“年龄<30”,… —类似地,每个商品 i 都有一组注释 Aᴵ(i) —例如“价格> 100 美元】【t22 …然后我们通过一个潜在因子 xᵁₐ R ᶠ和一个**偏差项bᵁₐ∈**t36】r 对每个用户标签进行建模,并且我们假设用户向量表示 pᵤ 及其相关偏差 bᵤ 可以简单地表示为这些项 xᵁₐ之和 我们对物品标签采取同样的方法,使用潜在因素 xᴵₐ ∈ Rᶠ和偏差项 bᴵₐ ∈ R。一旦我们使用这些公式定义了 pᵤ、qᵢ、bᵤ、bᵢ ,我们就可以使用相同的 SVD 线性模型来描述这些项和 rᵤᵢ 之间的关系。******

LightFM:用户/项目嵌入和偏差是与每个用户/项目相关的潜在向量的总和。

注意,LightFM 的这种混合方法有三个有趣的例子。

  1. 冷启动。**如果我们有一个带有已知标签aᴵ(i的新项目 i ,那么我们可以使用潜在向量 xᴵₐ (通过对之前的数据拟合我们的模型获得)来计算它的嵌入 qᵢ ,并因此为任何用户 u 估计它的评级 rᵤᵢ
  2. ****没有可用的标签。如果我们没有任何已知的条目或用户的元数据,我们唯一可以使用的注释是一个指示器函数,即每个用户和每个条目有一个不同的注释 a 。然后,用户和物品特征矩阵是单位矩阵,LightFM 简化为 SVD 等经典的协同过滤方法。
  3. 基于内容与混合。**如果我们只使用没有指示器注释的用户或项目标签,LightFM 将几乎是一个基于内容的模型。因此,在实践中,为了利用用户-项目交互,我们还向已知标签添加了一个指示器注释一个不同于每个用户和项目的

TL;DR–结论

  • ****推荐系统利用机器学习算法来帮助用户在发现相关内容时淹没在选择中。
  • ****显性与隐性反馈:前者更容易利用,但后者更丰富。
  • ****基于内容的模型在冷启动场景中工作良好,但是需要知道用户和项目元数据
  • **协同过滤**模型一般采用矩阵分解: PMF,SVD,SVD 为隐式反馈,SVD++。
  • ****混合模型充分利用了基于内容和协作过滤的优势。LightFM 是这种方法的一个很好的例子。

参考

重建 538 选举图:制作自定义 Matplotlib 可视化

原文:https://towardsdatascience.com/recreating-a-538-elections-plot-making-custom-matplotlib-visualizations-b6ede7a406b4

有些情况下,您希望您的数据可视化非常复杂

粘土银行Unsplash 拍摄的照片

我最近对制作详细的和高度可定制的可视化感兴趣。虽然在许多情况下,使用基本命令生成标准绘图就足够了,但有些情况下,您希望数据可视化非常复杂。这导致转向低级函数来调整图中的每个元素。作为练习,在这个故事中,我想重现 538 年的这个形象。本质上,它是在描绘美国 50 个大陆州的城市化指数与政治偏好。

首先,一些设置和导入数据。我将使用的两个主要库是用于数据清理的 Pandas 和用于重新创建可视化的 matplotlib。数据集将从这里的获取(这是 538 的 GitHub repo,数据由 CC BY 4.0 许可)。从那里,我们可以连接“state”列上的数据。让我们来看看结果:

作者图片

由于原始图已经计算了 y 轴上方的民主党州和下方的共和党州的“倾斜”,我将添加一个“从属”变量,计算为一个州的民主党百分比减去 50%(这将是两党都受到同等支持的情况)。

在原图中,我们可以看到一个灰色的背景,以及代表城市化指数的 x 轴和各自党派倾向的 y 轴。此外,我更改了 y 轴刻度,以反映“Party”+该方受支持程度的值。让我们先将它们添加到图中,包括我们看到的 y=0 处的粗线:

作者图片

现在,让我们自定义散点多一点:这里我们添加了一个浅蓝色和浅红色的点,这取决于他们的 y 值,以及一个边缘颜色与填充颜色的深色版本,以创建更多的对比:

作者图片

现在,我们可以添加曲线箭头,正如我们在原始图表中看到的那样。这是使用 matplotlib.patch 的 FancyArrowPatch 完成的,第一组坐标设置箭头的位置,第二组坐标设置箭头的位置。“connectionstyle”属性设置箭头的形状,如下所示:

作者图片

结论

原来如此!我试图重现 538 的情节,并附有一些评论。我们使用的主要函数是“annotate”、“FancyArrowPatch”和“scatter”,具有可定制的属性。我希望你喜欢这个小故事,并感谢阅读!

递归、罗生门和任意标签

原文:https://towardsdatascience.com/recurrence-rashomon-and-arbitrary-labels-3adfc296e404

彼得·奥勒克萨在 Unsplash 上拍摄的照片

计算社会科学阅读

介绍

本文对与计算社会科学研究相关的三篇论文进行了评论:(1) 级联会复发吗?(程等,2016);(2) 整合文本和图像:确定 Instagram 帖子中的多模态文档意图 (Kruk et al .,2019);和(3) 统计建模:两种文化 (Breiman,2001);这些论文集中于(1)社交媒体上内容共享的重现(这里,脸书);(2)使用来自社交媒体(这里是 Instagram)的多模态数据(图像-标题对)来改进作者意图的模型;(3)统计领域内从数据建模向算法建模的转变。

下面,我不提供这些工作的总结,所有这些都是我强烈推荐的,而是集中在以下主题上:(1)数据集内相似性(相对于同一性)的模糊性;(2)建模在理解(和影响)系统中的作用;(3)标签作为文化类别的稳定性。结合起来,这些主题旨在探索数据科学中模糊性的来源。

模糊事物的重现

这篇论文通过它的标题问级联会重现吗?、 Cheng 等人[1]讨论了上图片分享的长期模式,以回答随之而来的问题,如曾经病毒式传播的内容能否重获病毒式传播?哪些因素影响复发?。在这项工作中,作者探索了一些现象,如(1)原始内容(即帖子)和复制内容(即转发)的共享瀑布(即使在静止期之后)的重复出现,(2)观众对内容的饱和度(此后兴趣下降),以及(3)内容的吸引力广度(近似为内容的初始分享量激增的幅度)对重复出现行为的调节作用。

如前所述,该研究考虑了原创帖子和复制或模仿之前帖子的转发。为此,将精确副本和几乎相同的副本与原始内容分组,以研究组内的级联循环。通过对(重新)帖子进行分组,作者能够广泛地研究(低方差)内容类型在网络上共享的过程;然而,分组的方法(在这种情况下,二进制k-意味着聚类)不是一个中性的选择,因此提出了许多关于内容相似意味着什么的问题。更具体地说,虽然这项研究主要关注几乎相同的图像(包括具有不同覆盖文本的图像),但内容也可能在其他方面类似,如象征意义或风格。

在这项研究中,图像集群被随机采样,以确认(通过人工检查)它们包含足够相似的图像;94%的样本聚类包含几乎相同的图像,剩下的 6%在文本方面有所不同)。然而,对这一数据或类似数据的后续研究可能会质疑帖子相似的维度(例如,它们是否共享主题?它们以同样的方式转换不同人的形象吗?它们传达相似的信息或激发相似的情感吗?),以及如何通过机器学习来辨别这些维度。围绕内容模仿的问题——不同的用户如何玩相同的主题以及他们这样做的意图——可能是有趣的问题,因为它们可能与创造力和社会行为有关;然而,从技术角度来看,这些问题也很有趣,因为它们可以从聚类算法的角度来看,聚类算法可能只能成功地捕捉某些相似性维度,而在很大程度上遗漏了其他维度。

罗生门效应与叙事空间

统计建模:两种文化【2】中,Leo Breiman 解释了不同的模型如何能够产生大致相同精度的预测(相对于正在考虑的响应),从而产生不同但同样引人注目的对所研究系统的描述;这种效应被称为罗生门效应(源自电影罗生门,其中一个故事是从多个角色的角度讲述的)。从这个想法(从具有相同预测值的不同模型得到的系统的多种描述)延伸,这些模型的解释可以说形成了一个叙事空间,讲述了他们可用的完整故事;换句话说,如果每个模型只能从一个角度讲述故事(平行于罗生门中的人物),那么通过组合这些故事(以某种重叠的方式),就形成了一个包含所有同样引人注目的故事的叙事空间(平行于电影本身)。

(关于不同准确性的模型(不同引人注目的故事)似乎也有一些值得说的东西,但我认为,这样做需要另一个方面的考虑:不仅每个角色都有自己的故事版本,而且一些角色作为叙述者比其他人或多或少更可靠,这影响了观众(或分析师)对故事的解释;然而,以这种方式添加维度会使解释更加复杂,因此,为了简单起见,只考虑获得竞争性能的模型可能更有意义。此外,一个所谓的叙事空间似乎有可能被映射为若干维度上的密度,使得从许多模型的视角可解释的故事的方面(或模型的特征)最为突出。[作为题外话的补充,我最近看了 Emmanuel Candès 在神经信息处理系统会议(NeurIPS)上发表的关于共形预测的演讲,似乎其中概述的关于预测区间的工作和我在这里提出的密度概念之间可能有很好的联系(除了缺点可能没有其他区别)。])

虽然叙事空间的想法与统计学的想法(例如,预测区间、不确定性、集合)完全一致,但在大数据和多参数模型的范式下,模型解释(或应用)可能不太涉及发现真相(即,在叙事空间中定位高密度区域),而是更多地涉及在不必发现真相的情况下调节决策;因此,与其将模型解释为所研究系统的代表以获得对系统的理解,不如根据模型作为决策者的角色对其进行评估,并对其进行修改以限制哪些叙事(在叙事空间内)可以通过预测得到进一步(放大)。

作为一个例子,数据可以显示给定模型的某一组输入总是产生相同的响应,并且模型可以学习这些输入应该总是预测该响应,但是这并不一定意味着给定系统的那些输入就必须总是产生响应(可能没有规定响应的自然法则,样本可能偏向于仅包括系统内发生的某些类型的情况,响应可能完全不希望重复);因此,在模型作为决策者的情况下,预测可以维持现状,除非模型可讲述的叙述是有限的,并且为了使可讲述的叙述是有限的,模型必须是可解释的和可调的,或者是充分可检查的和可否决的。

任意标签的文化稳定性

为了探索被称为意义倍增的符号学概念,Kruk 等人[3]考虑了从社交媒体收集的图像-标题对的多模态数据集。字幕既不是图像的纯转录,也不是图像对字幕的纯描绘,因此,与其假设这两种数据类型之间存在直接和不对称的关系,不如将图像和字幕视为具有复杂的关系,这种关系取决于作者想要通过两者的组合来传达的信息。作者用三组标题(一组捕捉作者意图,一组捕捉上下文关系,一组捕捉符号关系)注释了来自 Instagram 的相当小的图像数据集(n=1299 ),然后根据这种分类法建立了一个注释帖子的模型;他们表明,当同时给定图像和标题时,该模型比只给定其中一个时表现更好(并且当图像和标题在语义上不同时,提升最大),这应该表明意义是成倍增加的,但听起来也像是银行出纳员琳达的情况的反转。

从我收集的情况来看,在本文撰写之时(2019 年),这一系列工作还处于早期阶段,因此有许多方法来分割和扩展这项研究;例如,了解图像-标题对的哪些特征解释了数据类型组合下的准确性提升,这些特征在多大程度上作为交互术语而不是个体,以及这些~特征在社会中是否是文化稳定的,这可能是有趣的。更具体地关于最后一点:因为标签(作为附加到数据的平面符号,而不是文化中定义的深层概念)本质上是任意的(通过从标签到数据的路径隐式定义,而不是通过预定义的特征和相对于其他标签显式定义), 考虑本文中用作标签的概念的明确文化定义是否与模型选择的~特征稳定相关,而不是从数据到没有稳定文化基础的(任意)标签的方便途径,这可能是有趣的,因为毕竟,当前引起争议或有争议的(用于注释数据集的两个类别)可能在其他地方或不同时间仅仅是表达性或娱乐性的(另外两个类别)。

参考

  1. 布雷曼,利奥。2001."统计建模:两种文化."统计学16(3):199–231。
  2. Cheng,Justin,Lada A. Adamic,Jon M. Kleinberg 和 Jure Leskovec。2016."瀑布会复发吗?"在第 25 届万维网国际会议论文集,671–81 页。加拿大魁北克省蒙特利尔:国际万维网会议指导委员会。【https://doi.org/10.1145/2872427.2882993】T4。
  3. 克鲁克,朱莉娅,乔纳·卢宾,卡兰·西卡,肖林,丹·茹拉夫斯基,阿贾伊·迪瓦卡兰。2019."整合文本和图像:确定 Instagram 帖子中的多模态文档意图."在2019 自然语言处理经验方法会议和第九届自然语言处理国际联合会议(EMNLP-IJCNLP) 的会议录中,4621–31。中国香港:计算语言学协会。https://doi.org/10.18653/v1/D19-1469

用一个真实的例子和 Python 代码解释递归神经网络

原文:https://towardsdatascience.com/recurrent-neural-networks-explained-with-a-real-life-example-and-python-code-e8403a45f5de

在情感分析任务中使用递归神经网络

作者图片

这是致力于深度学习系列的第二篇文章,深度学习是一组可以追溯到 20 世纪 40 年代的机器学习方法。深度学习在过去几十年中因其在图像分类、语音识别和机器翻译等领域的开创性应用而受到关注。

第一篇文章关注多层感知器。如果你想看用现实生活中的例子和一些 Python 代码解释不同的深度学习算法,请继续关注。

多层感知器的发展是人工神经网络的一个重要里程碑。第一次,我们可以将许多感知机堆叠在一起,并按层组织它们,以创建最能代表复杂问题的模型。

多层感知器以暂时的、离散的方式工作。它采用一个输入向量,执行一个前馈计算步骤,反向传播误差,一旦损失函数不能再最小化,就停止,生成输出。

有一个隐藏层的多层感知器的例子。(图片由作者提供)

但是许多现实世界的问题都涉及到时间维度。当手头的问题以序列的形式出现时呢?

用于情感分析的神经网络

在上一篇文章中,你使用了多层感知器进行情感分析。你拿了你父母在乡下舒适的床和早餐的评论,训练了一个多层感知器,并预测了评论的整体情绪。

多层感知器用来从你父母的民宿学习评论的情绪。(图片由作者提供)

多层感知器使用每篇评论的符号化单词向量作为输入,但它看着每篇评论一个单独的原子单位。

分析这些评论的更精确的方法是考虑每个单词在评论中的位置,因为句子的结构在赋予其意义方面起着作用。

一个比多层感知器更强大的模型将根据每个句子的结构来分析评论。例如,使用类似于这一次的服务很棒这一次的服务很棒这样的句子,确定这些句子有相同的情感就足够聪明了[1]。即使单词被打乱了。

为了在情感分析任务中更进一步,您需要一个不同的模型。

你需要一个模型,让在每次评论中把看做有序的单词序列,而不是原子单位。每个单词序列也可以有任意长度,因为每个句子可以由不同数量的单词组成。

你需要建立一个递归神经网络

递归神经网络

循环神经网络用于多个领域。例如,在自然语言处理 (NLP)中,它们被用于生成手写文本,执行机器翻译语音识别。但是它们的应用并不局限于处理语言。在计算机视觉中,递归神经网络已经被用于像图像字幕图像问答这样的任务中。

递归神经网络多层感知器区别开来的是,递归神经网络是为了处理代表序列的输入而构建的,就像你父母的床和早餐的评论中的单词序列。但它也处理输出序列,比如当你把一个句子从一种语言翻译成另一种语言时。

递归神经网络的作用就像一条链。在每个时间步长执行的计算取决于之前的计算。

深度学习训练的神奇之处在于隐藏的层。

在训练的开始,网络的结构被定义,在这种情况下,是一个递归神经网络。但是我们只知道哪些输入与哪些输出匹配。该算法将学习如何使用隐藏层来对每个输入数据点进行最佳逼近以输出数据点[1]。

可视化递归神经网络

将递归神经网络可视化的最佳方式之一是作为循环计算图[1]。在这种表示中,递归神经网络具有三种主要状态:

  • 输入状态,捕捉模型的输入数据。
  • 输出状态,捕捉模型的结果。
  • 递归状态,它实际上是一个隐藏状态链,捕获输入和输出状态之间的所有计算。

与其他监督机器学习模型类似,递归神经网络使用损失函数将模型的输出与基础事实进行比较。该损失稍后被反向传播,并且模型权重被更新,就像在多层感知器中一样。

表示为计算图的递归神经网络。(图片作者)

这是一个压缩视图,更像是对递归神经网络机制的总结。在实践中,当你展开这个图表时,更容易形象化重现。尤其是在处理文本序列时。

在递归神经网络的展开视图中,每个计算对应于一个步骤,也称为内部状态。并且每一步都依赖于前一步的计算。

表示为展开的计算图的递归神经网络。(图片由作者提供)

由于每个内部状态都依赖于前一个状态,所以从序列开始起,信息就传播到了网络中的每一层神经元上。像是传递给后代的古老记忆。

如果多层感知器意味着在层中堆叠多个神经元,那么递归神经网络意味着链接多层感知器,以创建一系列相关的计算。

在递归神经网络的情况下,存储器是关于到目前为止应用于序列的计算的信息。

递归神经网络超级能力:参数共享

网络中所有时间步长共享一个权重向量。

递归神经网络的一个关键特征是参数共享。在网络的所有部分中,只有一组参数被使用和优化。如果这些参数没有被共享,模型将不得不学习输入序列的每个部分的参数,并且将有更困难的时间来概括它还没有见过的例子[1]。

共享参数使递归神经网络能够处理不同长度的输入,并且仍然在可接受的时间框架内执行预测。

共享参数对于概化共享输入的序列尤其重要,尽管它们位于不同的位置。在你父母的住宿和早餐评论的情况下,如果没有共享的参数,网络将会有一段非常艰难的时间,并将多次重复学习相同的语言规则,以找出类似于这一次服务很棒这一次服务很棒的句子,具有相同的输出情绪。

这是 RNNs 的一个主要优势,因为如果没有参数共享,您必须为序列中的每个时间步长学习不同的模型,并且您需要一个大的训练集来适应不同的模型训练步骤。

实际上,参数共享意味着输出函数是前一时间步输出的结果,每一步用相同的规则更新。后者是递归神经网络的另一个重要方面,更新规则是相同的,这意味着在每个时间步,网络应用相同的激活函数。

许多隐藏状态,相同的激活功能

网络可以有尽可能多的隐藏状态,但有一个重要的常数。在每个隐藏状态下,你总是在计算相同的激活函数。使用相同的函数计算每层的输出[3]。

应用于递归神经网络的激活函数计算隐藏状态 h 。(图片由作者提供)

激活函数可以像线性函数或 sigmoid 函数一样简单。但是双曲正切也常用于深度学习,因为与 sigmoid 相比,它往往更少出现消失梯度

用双曲正切训练深度神经网络就像训练线性模型一样简单,只要计算量很小[1]。

表示为 tanh(x) 的双曲正切是激活函数的可靠选择。它的行为类似于零附近的恒等函数,即 tanh(0) = 0。

因此,只要这个激活函数的计算量很小,用双曲正切训练深度神经网络就像训练线性模型一样简单[1]。

输出层很特殊:它可能需要 Softmax

现在你可能会想你不是说过每一层都使用相同的激活函数吗?

是的,但是在分类任务中,输出图层是特殊的。

特别是对于处理分类问题的神经网络,还有另一个激活函数,只应用于输出层,几乎像一个后处理步骤。满足 Softmax 功能。

二元分类问题的输出不是 0 就是 1。但有时你在处理一个多类问题,例如,如果你父母的床和早餐的评论被归类为积极,中立或消极。

在这种情况下,有 3 个可能的输出类,了解观察值属于正类的可能性更有用。这就是为什么除了您为网络选择的激活函数之外, Softmax 函数也应用于输出层。

Softmax 方程。(图片由作者提供)

向量 z 是从第一层开始的所有计算的结果,它是到达输出层的向量。

例如,如果您的神经网络只有一个线性层,向量 z 可以看起来像:

线性图层的矢量 z 。(图片由作者提供)

使用 Softmax,该向量被指数化,并且因为这是一个分类任务,所以在所有可能的 K 类中被标准化,将向量变成概率分布。这就是为什么 Softmax 也被称为归一化指数函数。

在应用 Softmax 之后,矢量 z 的所有值的总和总是等于 1。它的值表示在输入层给予网络的观察值属于每个类的概率。

如果不对输出层应用 Softmax,则递归神经网络的输出是一个数字

每个可能的类别。具有最高值的输出是该观察的获胜类[3]。

穿越时间的反向传播

到目前为止,您已经研究了递归神经的更广泛的架构和组件,即激活和损失功能。让我们把注意力放在学习上。

当计算从一层中的每个隐藏层流向下一层时,它向前移动,向输出层移动。到达输出层时,您计算损失函数,这意味着您将生成的输出与该训练观察的预期值进行比较。

如果过程在此停止,网络将无法学习。这将是一个前馈网络,因为信息在网络结构中向前移动。但是学习需要某种循环。就像你在学校或自己学习一样:信息到达你的大脑,这是前馈部分,然后你处理它,当你这样做的时候,你检查自己的心智,有时重新学习某些东西,这是我称之为循环的部分。

神经网络受到大脑的启发,主要是受到神经元工作方式的启发。但是这些神经网络架构,也有这个一个一个的循环部分。他们学习如何将输入数据映射到一个可能的结果的部分。

回到递归神经网络!

输入通过所有层,当它到达输出层时,它计算损失函数。现在,知道计算链与预期的结果有多不同或有多远,它取损失函数的值,并计算它相对于参数的梯度。

然后,在另一种算法的帮助下,如随机梯度下降,梯度被发送回相反的方向。一直回到输入层。

这样你就反向传播了损失函数。

梯度的实际计算被称为反向传播【1】。但是通常与实际的学习部分相混淆,实际的学习部分是通过像随机梯度下降这样的算法完成的。

到达输入层时,随机梯度下降或其他基于梯度的优化算法调整网络权重,并通过每个隐藏层再次计算激活函数。

通过从当前权重向量(θ)中减去关于损失函数(J)的梯度值来更新网络中的每个权重。这也称为重量变化量 (vt)

反向传播权重更新规则(图片由作者提供)。

该过程继续进行,直到损失函数不能再被最小化,因此再次调整权重没有意义,并且在给定当前训练集和架构的情况下,神经网络的性能被最大限度地提高。

更复杂的算法和神经网络架构可能会提高性能,但也会增加所有计算的复杂性,从而导致更长的训练时间。

在机器学习中,你总是要处理取舍。对于更复杂的模型,一些折衷是(a)提高性能,但是降低模型的可解释性,或者(b)提高性能,但是增加训练时间。

现实中的 RNNs:情感分析👍 👎

你决定使用 TensorFlow 强大的库来帮助对你父母的床和早餐的评论进行分类,而不是从头开始建立一个递归神经网络。

你需要做的第一件事是通过 pip 在你的机器上安装 TensorFlow ,因为你将使用本地 Python 环境。

遵循 TensorFlow 文档:

# Requires the latest pip
> pip install --upgrade pip# Current stable release for CPU and GPU
> pip install tensorflow

然后你会注意到 TensorFlow 资源 [6]上有一个使用 RNNs 的文本分类的健壮例子。这是你想做的事情的一个很好的基础,你只需要根据你自己的任务来调整它。

一眼看去,建立一个递归神经网络来对来自你父母的床和早餐的所有评论的情绪进行分类包括:

  1. 组织培训和测试文件
  2. 加载训练和测试数据集
  3. 维度问题:压缩数据集(矢量化)
  4. 构建递归神经网络
  5. 模型拟合和评估
  6. 精确度和损失可视化

组织培训和测试文件

第一步是把你的表兄弟帮助分类的评论整理到相应的目录中。将会有训练测试目录,每一个都有包含正面负面评论的子目录。

在这个组织之后,如果您在您的项目目录上运行命令树,您将会看到如下内容:

**训练和测试数据集目录(图片由作者提供)

在这一步中重要的是,每个文件只有一个检查,因此可以在下面的步骤中正确地加载和处理它。

加载训练和测试数据集

要加载训练和测试数据集,您可以利用 Keras 实用程序方法text _ dataset _ from _ directory,这特别需要您在上一步中整理的目录数据集结构。

**加载训练和测试数据集(图片由作者提供)

维度问题:压缩数据集(矢量化)

你父母的住宿和早餐点评没那么冗长。将会有更多热情的顾客,他们写一篇关于他们体验的短文,但是从数据的角度来看,结果是一个相对低维度的向量。

但是如果你父母的生意如此受欢迎,以至于旅游和休闲杂志开始写关于他们的文章,基本上是评论,那该怎么办?在这种情况下,我们谈论的是更大的数据集,每个评论都是至少 800 字的杂志文章。

在这个场景中,你正在处理一个维度问题。你有非常大和稀疏的向量,这使得所有矩阵计算在计算上具有挑战性。

为了处理这种情况,你需要做一些与主成分分析类似的事情。你想要压缩你的数据集,同时保持它所有的表达能力和核心特征。

这些是通过主成分分析的变体从非常大的文档语料库中建立的。其思想是单词在嵌入空间中的位置保持语义;例如,同义词应该出现在彼此附近。”[5]

一个典型的预处理步骤是用 wor2vec [4]来降低维度。在 TensorFlow 中, Keras TextVectorization 层做了类似的事情。它获取一个字符串,或者将它映射到一个一维的索引张量或者一个表示字符串中数据的一维浮点张量。

**加载数据并对训练数据集进行矢量化。(图片作者)

在这一步结束时,训练数据集被矢量化,数据准备阶段完成。

现在,您已经准备好构建递归神经网络了。

构建递归神经网络

实际上,你的递归神经网络模型是一组连续层

第一个是词汇编码器,在前面的步骤中创建。它在嵌入层中使用,它将编码向量中的值转换成特定的范围。

例如,您的评论中的词汇由 151 个单词组成,通过运行以下命令获得:

*len(encoder.get_vocabulary())*

因此,模型通过 input_dim 将词汇大小作为输入,并返回大小为 64 的输出,使用 output_dim 定义。因为您指定了词汇大小 151,所以映射中最大的整数将是 150。

**加载数据和矢量化训练数据集并构建 RNN。(图片作者提供)

下一层,双向,表示您想要创建一个双向递归神经网络。这意味着网络的输入通过 NRR 层向前和向后传播。网络在处理输入的过程中,随着时间的推移而产生的记忆,不仅被传递给后面的细胞,还被传递给前面的细胞。现在,在输入序列中,网络中的每个细胞都有关于过去和未来的信息。

使用双向递归神经网络的优点在于,不仅仅是网络中的先前信息对输出预测有贡献。知道在序列中什么会在前面出现对模型如何学习有重大影响。

这种优势是一把双刃剑。由于信息现在来回传播,这些网络往往会慢得多,因为梯度现在有一个更长的依赖链。

然而,你父母的住宿和早餐评论是一个小数据集。所以你决定值得一试!

模型中的最后两层是致密层。倒数第二个用于处理模型损失,双曲正切作为激活。最后一个将输出整形为大小 1,假设您希望该模式的输出为负类指数。

在这种情况下,RNN 是使用 30 个单元创建的。这些门控递归单元 (GRU)使用双曲正切作为递归步骤的激活函数。

模型拟合和评估

现在你已经建立并编译了递归神经网络,是时候将它与训练数据集相匹配,并进行一些预测了。

您可以调整的几个参数是:

  • epochs 您希望算法在整个训练集中运行多少次
  • **混洗如果您想在每次历元迭代之前混洗训练数据。默认为 True
  • **验证 _ 步骤验证步骤结束前抽取的样本批次数,算法开始新的时期

加载数据和向量化训练数据集构建和评估 RNN (图片作者提供)

精确度和损失可视化

作为最后一步,可视化模型的损失和准确性总是很有趣的。尤其是当它通过多次迭代运行时。

*import os
import tensorflow as tf
import matplotlib.pyplot as plt# Loading Training and Test Datasets
train_dir = os.path.join('', '../datasets/train')train_dataset = tf.keras.utils.text_dataset_from_directory(
    train_dir, label_mode='int', labels='inferred', follow_links = True
)test_dir = os.path.join('', '../datasets/test')test_dataset = tf.keras.utils.text_dataset_from_directory(
    test_dir, label_mode='int', labels='inferred', follow_links = True
)# Vectorize training dataset
VOCAB_SIZE = 5000
encoder = tf.keras.layers.TextVectorization(max_tokens=VOCAB_SIZE)
encoder.adapt(train_dataset.map(lambda text, label: text))# Building the Recurrent Neural Network
# using GRU cells and Hyperbolic tangent as activation function
cell = tf.keras.layers.GRUCell(30, recurrent_activation='tanh')
model = tf.keras.Sequential([
    encoder,
    tf.keras.layers.Embedding(
        input_dim=len(encoder.get_vocabulary()),
        output_dim=64,
        # Use masking to handle the variable sequence lengths
        mask_zero=True),
    tf.keras.layers.Bidirectional(tf.keras.layers.RNN(cell)),
    tf.keras.layers.Dense(60, activation='tanh'),
    tf.keras.layers.Dense(1)
])# Compile model and use the algorithm Adam as optimization function
model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True), optimizer=tf.keras.optimizers.Adam(1e-2), metrics=['accuracy'])# Fitting the model
history = model.fit(train_dataset, epochs=10, validation_data=test_dataset, validation_steps=10)# Model Evaluation
test_loss, test_acc = model.evaluate(test_dataset)print('Test Loss:', test_loss)
print('Test Accuracy:', test_acc)# Visualize Model Loss and Accuracy
def plot_graphs(history, metric):
  plt.plot(history.history[metric])
  plt.plot(history.history['val_'+metric], '')
  plt.xlabel("Epochs")
  plt.ylabel(metric)
  plt.legend([metric, 'val_'+metric])plt.figure(figsize=(16, 8))
plt.subplot(1, 2, 1)
plot_graphs(history, 'accuracy')
plt.ylim(None, 1)
plt.subplot(1, 2, 2)
plot_graphs(history, 'loss')
plt.ylim(0, None)*

这里,您还可以利用来自 TensorFlow 文档页面的便捷示例中的代码来绘制训练和验证数据集的损失和准确性。

绘制每个时期的模型损失和精度。(图片由作者提供)

在对测试数据集进行预测后,准确性远非完美。它徘徊在 0.5 左右,这意味着你的模型在技术上比随机猜测好不了多少。

在所有评估时期,精度非常稳定地保持在 50%,在第三个时期之后,损耗开始稳定增加。

但是这些结果不应该让你气馁。您的数据集非常小,所用的 RNN 架构非常简单,当然还可以改进。这只是理解 RNNs 的第一步。

结论

根据手头的任务,有几种不同的方法来建立递归神经网络。

它可以具有一对多的结构,就像模型必须为给定的图像创建标题一样。但是如果你把英语翻译成法语或者相反,你将会建立一个多对多结构的递归神经网络。

对于情感分析任务或对你父母舒适的床和早餐的所有评论进行分类,该网络具有多对一的结构,评论中的几个词构成了一个单一的输出,即情感类别。无论是积极的还是消极的。

你创建的网络相对简单,只有 50%的准确率。

但我希望你能更好地理解什么是递归神经网络,为什么它是这样一个改变游戏规则的深度学习网络架构,以及它可以应用于现实生活中的各种问题。

感谢阅读!

参考

  1. 古德费勒,伊恩 j,本吉奥,约舒亚和库维尔,亚伦。深度学习,麻省理工出版社,2016
  2. 递归神经网络的不合理有效性
  3. 希顿杰夫。深度神经网络的应用
  4. 托马斯米科洛夫程凯格雷格 s 科拉多杰弗里院长。向量空间中单词表示的有效估计。(2013)
  5. 加雷思·詹姆斯、丹妮拉·威滕、特雷弗·哈斯蒂、罗伯特·蒂布拉尼。(2021).统计学习导论:应用于 r .(第二版)
  6. 使用 RNN 的文本分类,TensorFlow 文档

反复出现的关于复发的问题

原文:https://towardsdatascience.com/recurring-concerns-concerning-recursions-2f89ac5e5f87

Python 中时髦的编程

“跳出循环”

尼古拉斯·古雷威奇的《怪异的漩涡》。面板左侧的源。[经许可使用]

如果你跳过了处理树遍历的 CS 课程,或者你不明白如何使用 Dijkstra 算法提取从 A 到 B 的最短路径,你可能已经搜索过递归了。我假设你已经阅读了前 2 个搜索结果中的内容。

所以,我将试着用我自己的话来解释如何轻松地处理递归,以及为什么你不应该这么做。之后我们会有一些分形的乐趣。

循环的黑暗面

为了开始运行,我们可以把递归想象成一个循环。或者我们可以把循环想象成递归。不管怎样,他们都有一些相似之处。

循环和递归的相似性[图片来自作者]

显然,在满足某个条件之前,会重复执行一些操作。有一个条件可以停止重复,然后会有一些行动。

递归函数通常调用自己作为重复动作的一部分,常规循环没有这种行为。尽管如此,许多常规循环可以写成递归,我很乐意演示:

谦逊的重复

为了成功地创建一个简单的递归函数,我将遵循以下框架:

**recursive_function:** **end_case:
        do something before ending
        end** **normal_case:
        do something before calling the function again
        recursive_function**

现在,假设我们想重复某个动作 X 次。for 循环在适当的位置自然流动:

for i in range(X):
    repeated_action

和递归函数,遵循前面提到的框架:

def recursive_iteration(X, counter=0):

    #end_case:
    if counter == X-1:
        return

    #normal_case:
    repeated_action
    recursive_iteration(X, counter+1)

不那么光滑…等到我们开始增加它的实用性…

总和

假设我们想要从 0 到 x 的所有整数的和。

我向你呈现;从 0 到 X 的所有整数之和[图片由作者提供]

令人钦佩的 For 循环:

sum = 0
for counter in range(1,X+1):
    sum+=counter

以及一个严格的递归:

def recursive_sum(X, counter=1):

    #end_case:
    if counter == X:
        return counter

    #normal_case:
    return counter + recursive_iteration(X, counter+1)

将它分解

如果你还没有完全理解上面的代码发生了什么,这里有一个一步一步的视觉帮助,我希望它足够了:

这个例子将从 1 到 3 的所有整数相加,但是您可以推断出原理

哎呦,好像我用一些不同的名字制作了动画…

递归还是循环?

如果你在两者之间有明确的选择,选择循环。除了有时与使用递归相关的碍眼和头痛之外,另一个相当大的缺点是开销。做一个递归和一个循环来完成相同的任务,然后计时来揭示问题

仅仅因为递归函数很糟糕,循环函数不一定是最好的选择。在这里,sum 函数远远胜过它

递归用例

当常规循环不适合这项工作时,真正的乐趣就开始了,这就是递归的领域。在循环变得复杂和失败的地方,递归占主导地位。

一旦你能理解递归过程,你需要培养正确使用它们的意识。当我开始思考“这应该重复,但每一步都要放大”这样的问题时,我意识到递归的必要性。现在,我不会向您展示任何有用的东西,相反,我在文章的开头承诺了分形的乐趣,所以让我们开始吧。

分形娱乐时间

如果你在哲学演讲(或在互联网上与陌生人争论)或内省中思考,你可能会注意到一些递归思维模式的出现。似乎递归被编织进了人类的大脑。自然界的一些例子表明,美源于几何规则,递归也不例外,尽管在自然界中它们大多以分形的形式出现。

为了让这篇文章在几何上更吸引人,让我们试着解一个图形递归赋值。

五个人

首先,让我介绍这五位。
Five 是一个 2D 数组,记为[[1,0,1],[0,1,0],[1,0,1]]
为什么叫“the Five”?如果你把 1 想象成一个白色像素,把 0 想象成一个黑色像素,这看起来有点像骰子上的 5(我猜可能有人在读这篇文章时从未见过骰子…)

[1,0,1]
【0,1,0】
【1,0,1】
【图片,作者】

这很容易被硬编码,但是我们不要沉迷于这样的行为…

five = []
for i in range(3):
    row = []
    for j in range(3): # This is a method for generating checkerboard patterns, makes sure that the rows change the order of 1’s and 0's
        if (j+i)%2 == 0:
            row.append(1)
        else:
            row.append(0)five.append(row)

好了,现在我们已经定义并编码了五个。进入下一步…

五个组成五个

下一个需要理解的概念是。如果我们用 5 代替 1,用 3×3 零矩阵代替 0,会怎么样?

五个组成五个,第一次进化[作者动画]

有很明显的简单方法可以达到这个形状,但是我希望你能推断出它的发展方向…因为再多的硬编码也帮不了你。

由…制成的五个(五个由…制成的)

我的朋友叫我分形五

请看分形五,五的最终进化[图片由作者提供]

让我们想一个策略。这个问题可以借助我在上面打出的递归框架来解决。
在我们的正常情况中,我们希望函数做 Five 的形状,但是在递归的每一级都有不同的步长。
例如,第一次迭代将整个图像(2D 矩阵)纳入范围,并将其分成 3×3 块。为此,它跨越 1/3 的范围宽度和高度。
每个块被带入第二次迭代,并定义该次迭代的范围。步幅保持 1/3 的范围,块进一步分割成 3×3 的块。
该过程以递归方式重复,这是应该的。

这个过程需要在某个时候停止,所以我们引入了一个结束案例。 在这个例子中,结束的情况是当范围是 3x3 像素时。显然,更深一层意味着进入亚像素领域,我妈妈总是说永远不要去那里。相反,当我们到达 3x3 px 范围时(步长为 1),我们将只画五个。

要使它工作,我们需要将矩阵、范围和当前的 x 和 y 坐标(以知道在哪里画白色 px)作为参数传递给函数。

def rec(mat,scope,x,y): stride = scope/3#### End Case ################
    if stride == 1/3:   # DO NOT go into the sub-pixel territory...
        x,y = int(x),int(y)
        mat[y,x] = 255 # Paint it white 
        return##### Normal Case ############
    remember_x = x
    for i in range(3):
        for j in range(3):
            if (j+i)%2 == 0:
                rec(mat,stride,x,y) # Call the function again
            x+= stride
        y+= stride
        x = remember_x # The loops can be done as "in range(0,3*stride,stride)
    # But this seemed more readable to me at the time # When all recursions are done, return the matrix for viewing
    if scope == mat.shape[0]:
        return mat

为了让这个函数对用户更友好,我把它包装到了另一个函数中

def fractal_five(number_of_iterations):
    dim = 3**number_of_iterations
    mat = rec(np.zeros((dim,dim)),dim,0,0)
    return mat

这样,可以用一个参数调用该函数,并生成一个填充有分形五的正方形 2d 矩阵。将 numpy 作为 np 导入并使用它的 ndarrays 使得稍后在 opencv 中可视化结果变得容易。

当重新思考这个过程时,似乎反过来会快得多。从 3x3 像素开始,递归到图像的全部范围,但我认为这已经足够了。递归思维过程不仅对计算机来说计算量大,对你的大脑来说也是如此。一定要振作起来,享受有规律的、线性的思维流。

免责声明:随意使用标签为[图片/作者动画]的所有内容

数据科学家的递归

原文:https://towardsdatascience.com/recursion-for-data-scientists-b08b4f96e450

要理解递归,首先你必须理解递归

信用:Pexels.com

对于数据科学家来说,绕过计算机科学基础和软件工程最佳实践以追求工具精通(Pandas & SKLearn 举两个例子)并立即开始为您的组织增加价值的诱惑是真实的。

CS 中的许多基本概念真的值得争论,因为它们将使您在冗长地争论笨拙的数据时构建简洁的、可伸缩的逻辑。在这篇文章中,我们将定义一个递归函数,它可以在 python 字典中搜索目标值。事不宜迟,我们开始吧!

什么是递归?

递归通俗的说就是自引用函数。简单来说,函数可以自己调用!但是这样不会导致无限循环吗?诀窍是通过使用控制流(if/elif/else 逻辑)来定义终止功能的基本情况。)

伪代码

def recursive_function(argument):
   if base case:
      return terminal_action(argument)
   else:
      new_argument = intermediate_action(argument)
      recursive_function(new_argument)

关于上述内容的几点说明:

  1. 我们需要指定一个“岔路口”要么基本情况得到满足,我们退出递归逻辑,要么我们递归地使用函数到达基本情况。
  2. 基本情况下的终端功能是完全可选的。您可能只想按原样返回参数,在这种情况下,您的终端函数将归结为lambda x: x
  3. 为了不陷入无限循环,我们不能一遍又一遍地把同一个参数传递给递归函数;我们需要使用一个中间动作来修改参数。与终端动作不同,中间动作不能是被动的(归结为lambda x: x)。)
  4. 可以根据需要添加副作用,或者对递归逻辑或基本情况不重要的动作。

说明性示例

这个例子,blastoff,是递归函数的 helloWorld。

def blastoff(x):
   # base case 
   if x == 0:
      print("blastoff!") # side effect
      return # terminal action: None/passive # recursive logic
   else:
      print(x) #side effect
      blastoff(x-1) # intermediate action: decrementing x by 1blastoff(5)>>>5
4
3
2
1
blastoff!

真实世界的例子

现在,让我们来处理一个真实世界的例子——在嵌套字典中搜索给定值。

为什么是嵌套字典?因为字典是类似于 JSON 的 python 原生目标。事实上,Python 很容易支持将 JSON 对象转换成字典。此外,API 响应通常是用 JSON 交付的(尽管有些仍然使用 XML,但是这种方式越来越不受欢迎了)。)

所以下一次你需要发现一个给定值是否在你的 API 请求结果中,你可以简单地(1)将结果从 JSON 转换成 Python 字典,然后(2)使用下面的递归函数!

def obj_dfs(obj, target, results=None):
   if not results:
      results = [] # recursive logic
   if isinstance(obj, dict):
      for key, val in obj.items():
         results.append(obj_dfs(key, target, results))
         results.append(obj_dfs(val, target, results)) elif isinstance(obj, (list, tuple)):
      for elem in obj:
         results.append(obj_dfs(elem, target, results)) # base case
   else:
      if obj == target:
         return True
      else:
         return False return any(results)

让我们把它分成几部分!

基础案例

基本情况是一个不是字典、列表或元组的对象。换句话说,如果对象不能包含嵌套字典、元组或列表,则基本情况有效。这些是我们实际搜索的值——字符串、整数、浮点数等。

当我们检测到一个基本案例时,我们简单地评估这个问题,“我们找到目标了吗?“返回真或假

递归逻辑

如果基本情况无效,使得对象可以包含嵌套字典、列表或元组,我们递归地调用那些非常嵌套的字典、列表或元组上的函数

我使用了一个 if 和一个 elif 块以一种方式处理字典,以另一种方式处理列表和元组。这是必要的,因为字典由(键,值)元组组成,而列表和元组只包含对象。

“isinstance”是一个内置的 Python 函数,它确定对象的类型是等于 x 还是 in (x,y)。

结果

我们首先检查结果对象是否存在——如果它不存在,这是默认行为,我们定义一个空列表。在随后的递归调用中,相同的结果对象被更新。

将递归函数追加到列表中似乎不常见,但是函数 本身 永远不会追加到列表中。该函数遵循递归逻辑,仅在基本情况下返回 True 或 False。因此,结果只会被对象填充,每个对象要么等于真,要么等于假。

最后,该函数返回“any(results)”,在 Python 中,如果一个或多个嵌套元素为真,则返回真。因此,如果在至少一个检测到的基本案例中找到目标对象,则该函数全局返回 True。

扩展空间

假设您对精确匹配不感兴趣,而是对模糊匹配感兴趣——或者您有自己的特定于上下文的逻辑。这很好,我们可以将一个函数作为参数传递给我们的递归函数,这将在基本情况下使用。

obj = {'chicago': 
          [{'coffee shops':'hero'}, {'bars':'la vaca'}],
       'san francisco': 
          [{'restaurants':'el techo'}, 
          {'sight seeing':'golden gate bridge'}]}def base(x):
   return 'golden' in xdef obj_dfs(obj, f_base, results=None):
   if not results:
      results = [] # recursive logic
   if isinstance(obj, dict):
      for key, val in obj.items():
         results.append(obj_dfs(key, f_base, results))
         results.append(obj_dfs(val, f_base, results)) elif isinstance(obj, (list, tuple)):
      for elem in obj:
         results.append(obj_dfs(elem, f_base, results)) # base case
   else:
      return f_base(obj) return any(results)

解析 JSON 参数

最后,要在实际的 JSON 格式的 API 响应上使用这个函数,使用下面的代码将 API 响应转换成 dictionary。

import json
with open('data.json') as json_file:
   data **=** json.load(json_file)

您可以参考这个免费的模板 API 获得 JSON 响应来测试这段代码!https://fakestoreapi.com/

import requests
import json
data = requests.get('https://fakestoreapi.com/products/1')
response = json.loads(data.text)def obj_dfs(obj, target, f_base, results=None):
   if not results:
      results = [] # recursive logic
   if isinstance(obj, dict):
      for key, val in obj.items():
         results.append(obj_dfs(key, target, f_base, results))
         results.append(obj_dfs(val, target, f_base, results))

   elif isinstance(obj, (list, tuple)):
      for elem in obj:
         results.append(obj_dfs(elem, target, f_base, results)) # base case
   else:
      return base_case_function(obj) if any(results):
      return list(filter(lambda x: x!=False, results))[0]
   else:
      return Falsedef find_url(x):
   x = str(x)
    if 'http' in x:
      return x
   else:
      return Falseobj_dfs(obj=response, target=None, f_base=find_url)
>>> 'https://fakestoreapi.com/img/81fPKd-2AYL._AC_SL1500_.jpg'

我对代码做了一点小小的改动;具体来说,我没有返回any(results),而是返回了结果中的第一个非 False 元素,这是在 JSON API 响应中找到的图片链接 url。

我希望你能感受到递归的强大,这是你腰带上的新工具。您可以根据需要修改这段代码!

我希望这有所帮助——如果有,请订阅我的博客!

递归特征选择:增加还是消除?

原文:https://towardsdatascience.com/recursive-feature-selection-addition-or-elimination-755e5d86a791

一种执行详尽特征选择的聪明方法

Unsplash 上由 Rodion Kutsaev 拍照

当建立一个机器学习模型时,拥有更多的特征比拥有更少的特征更重要。但是只使用你需要的比全部使用更有用。因此,在第一次建模试验之后,我们需要一个半自动的过程,可以只过滤对我们的监督任务有用的特征。

我们只是指特征选择过程。它包括从原始数据集中仅选择变量子集的所有算法,以用作我们预测模型的输入。有各种技术来执行特征选择,并且每种方法都有各种利弊要考虑。

在生产简单和可维护的管线时,少量特征是有效的;提高泛化能力;降低可能的存储空间或成本;减少推断时间(无论最终预测值是多少)或提供更好的可解释结果。

同时,仅产生有用的预测特征的子集可能是昂贵的、困难的和不明确的。一个好的特征选择算法应该尝试所有可能的特征组合,并根据我们的验证策略标注哪一个导致性能提高。由于时间的限制,数据科学家不鼓励采用大型数据集的特征选择方法。此外,所选特征取决于所用的模型和参数。因此,简单地改变其中一个可能会以一种不好的方式改变最终结果。对于在我们的机器学习工作流程中在哪里放置特征选择以获得最佳效果,也可能会产生疑问。

为了应对这些挑战,我们发布了shap-hype tune:一个用于同步超参数调整和特性选择的 python 包。其目的是在单个流水线中优化最佳数量的特征,同时搜索梯度增强模型的最佳参数配置。它提供了各种参数搜索方法(网格、随机或贝叶斯搜索)和特征选择策略(递归特征消除/添加和 Boruta)以及使用 SHAP 值提高泛化能力的

在这篇文章中,我们关注递归特征选择算法的优点。递归特征选择能够搜索可靠的特征子集,同时提高性能并保持可接受的计算成本。因此,它具备成为现实应用中最有效的特征过滤方法之一的所有先决条件。我们旨在探索不太为人所知的加法方法(递归特征添加),在标准分类问题中将其与最著名的减法方法(递归特征消除)进行比较。

递归特征添加

递归要素添加按照递归添加过程选择要素。工作流程如下图所示:

  1. 使用所有可用的特征来拟合估计量(在我们的例子中是梯度增强)。
  2. 提取要素重要性排名(标准的基于树的重要性或 SHAP 重要性有效)。
  3. 根据功能的贡献对其进行排序。
  4. 仅使用最相关的特征来拟合估计量,并根据验证数据计算性能。
  5. 包括下一个最重要的特征,并适合新的估计器。
  6. 在步骤 5 和 6 中计算模型之间的性能差异。
  7. 如果性能提高,该特征被认为是有价值的预测器。
  8. 重复第 5 步到第 7 步,直到所有特性都被考虑在内。

shap-hypetune 执行递归特征添加是简单明了的。

rfa = **BoostRFA**(
    LGBMClassifier(), 
    step=3, min_features_to_select=1
)
rfa.fit(X_train, y_train, eval_set=[(X_val, y_val)])

在上面的例子中,我们简单地使用 RFA 和一个 LGBMClassifier 。定制有很多。例如,我们可以使用具有 SHAP 特性重要性的 BoostRFA 实例(而不是传统的基于树的实例),或者在搜索最佳参数配置时使用。

对于我们的分类任务,我们使用 BoostRFA ,连同 LGBMClassifier ,计算一个贝叶斯参数搜索。结果报告如下。

折叠外样本的交叉验证结果(图片由作者提供)

递归特征消除

递归要素消除按照递归消除过程选择要素。工作流程如下图所示:

  1. 使用所有可用的特征来拟合估计量(在我们的例子中是梯度增强)。
  2. 提取要素重要性排名(标准的基于树的重要性或 SHAP 重要性有效)。
  3. 根据功能的贡献对其进行排序。
  4. 排除最不重要的特征并拟合新的估计量。
  5. 在连续迭代中,计算步骤 4 中模型之间的性能差异。
  6. 如果性能提高,该特征被释放。
  7. 重复第 4 步到第 7 步,直到所有特性都被考虑在内。

至于 RFA 的情况,用 shap-hypetune 执行递归特征添加是简单明了的。

rfe = **BoostRFE**(
    LGBMClassifier(), 
    step=3, min_features_to_select=1
)
rfe.fit(X_train, y_train, eval_set=[(X_val, y_val)])

在上面的例子中,我们简单地使用了带有 LGBMClassifier 的 RFE。定制有很多。例如,我们可以使用具有 SHAP 特性重要性的 BoostRFE 实例(而不是传统的基于树的实例),或者在搜索最佳参数配置时使用。

对于我们的分类任务,我们使用 BoostRFE ,连同 LGBMClassifier ,计算贝叶斯参数搜索。结果报告如下。

折叠外样本的交叉验证结果(图片由作者提供)

结论

RFA 和 RFE 在我们的模拟分类任务中都显示出惊人的结果。它们还表现出良好的过滤能力。从交叉验证的结果来看,选择最多的是信息特征,其次是线性组合特征(冗余)和噪声特征。

使用交叉验证比较 RFA 和 RFE 的选择能力(图片由作者提供)

总之,我们证明了递归算法对于特征选择的优越性。我们介绍了不太为人所知的递归特征添加,并将其与最流行的递归特征消除方法进行了比较。两种方法都显示出令人满意的结果。然而,完美的选择方法并不存在。我们必须根据我们的任务找到并优化正确的过滤策略。

如果你对题目感兴趣,我建议:

查看我的 GITHUB 回购

保持联系: Linkedin

Redis:内存数据存储很容易解释

原文:https://towardsdatascience.com/redis-in-memory-data-store-easily-explained-3b92457be424

了解基于键值的 NoSQL 数据存储

Khadeeja YasserUnsplash 上拍摄的照片

Redis 是一个基于键值的 NoSQL 数据库,它将数据存储在内存中,即 RAM 中。这个数据存储是当今最常用的键值数据库之一,例如用于缓存。缩写代表RemoteDidictionarySserver。

什么是 NoSQL 数据库?

NoSQL (“不仅是 SQL”)描述了与 SQL 不同的数据库,它们是非关系型的,也就是说,不能被组织在表中,等等。这些方法也可以分布在不同的计算机系统上,因此具有高度的可扩展性。NoSQL 解决方案非常适合许多大数据应用。

数据库的特点在于两个特别的标准,这两个标准非常宽泛。首先,数据不是存储在表中的,其次,查询语言不是 SQL,这一点从名称“不仅是 SQL”也可以看得很清楚。

Redis 属于所谓的键值存储,这是 NoSQL 数据库中的一个类别。它们是一种非常简单的数据结构,其中每条记录都存储为一个具有唯一键的值。使用该键,可以专门检索信息。

Redis 是如何工作的?

Redis 被开发为具有可靠的数据存储,可以在短时间内存储和输出信息。这个数据库的特别之处在于已经提到的键值存储内存存储的组合。

内存数据库将数据存储在计算机的随机存取存储器(RAM)上,而不是将其写入硬盘,如 HDD 或 SSD。这大大加快了读写的速度,但代价是数据的安全性和成本。RAM 通常比类似的硬盘存储更贵,当机器关机或系统崩溃时,RAM 会被完全擦除。

该存储器中的每个条目被分配一个唯一的密钥,该密钥可用于访问数据记录。由于计算机的工作内存通常是一种有限的资源,因此必须小心使用。这也包括使用消耗尽可能少的内存的特殊数据结构。

在大多数情况下,字符串是作为数据结构使用和存储的。此外,Redis 还可以处理其他数据类型(参见 IONOS (2020) ):

  • 字符串:最大内存 512 MB 的字符串。
  • 散列:散列表示和相关字符串之间的映射。
  • 列表:存储在列表中的字符串集合。
  • 位图:布尔值的紧凑表示。
  • Streams :专门为 Redis 开发的一种日志文件。

Redis 用于哪些应用?

尽管 Redis 的使用案例非常有限,但由于其优越的属性,它们很难在这一领域被取代。如前所述,Redis 主要用于缓存,比如在 Twitter 上。缓存通常被理解为保存中间状态,以便将来的查询可以运行得更快。例如,在 Twitter 的情况下,这可能意味着已经加载的个人资料图片或推文会保存在缓存中,以便在再次查询时可以更快地获得它们。

这些功能在聊天或消息服务中尤其有利,因为新消息可以几乎实时地发送给用户。

Redis 有什么优势?

与传统的关系数据库相比,NoSQL 数据库有几个优点。这些优势包括更好的性能和跨多个系统的大数据量和分布。Redis 还可以利用以下优势进行评分:

  • 通过内存中的内存快速访问
  • 支持最常见的编程语言
  • 借助各种工具,Redis 提供了高度的用户友好性
  • 数据也可以分布在多个集群中,并保存在其他计算机的内存中
  • 开源

使用 Redis 的缺点是什么?

与大多数数据库一样,使用 Redis 有一些缺点,在实施之前必须权衡这些缺点。除了系统崩溃时数据丢失的风险之外,以下几点也很重要:

  • 主存储器是一种昂贵的硬件组件
  • 数据只能通过密钥访问
  • 更复杂的数据集很难绘制

这是你应该带走的东西

  • Redis 是在内存中存储数据的键值存储。
  • 快速的读写过程使数据库非常适合缓存应用程序或存储聊天消息。

如果你喜欢我的作品,请在这里订阅https://medium.com/subscribe/@niklas_lang或者查看我的网站* 数据大本营 !还有,medium 允许你每月免费阅读 3 篇 。如果你希望有无限制的 访问我的文章和数以千计的精彩文章,不要犹豫,点击我的推荐链接:【https://medium.com/@niklas_lang/membership】每月花$5***获得会员资格**

* *

使用 dbt 增量模型降低计算成本

原文:https://towardsdatascience.com/reduce-computing-costs-with-dbt-incremental-models-a2025d42e633

它们是什么以及如何实现它们

林赛·亨伍德在 Unsplash 上的照片

dbt 现在在数据转换方面非常流行。而且,这是有原因的!dbt 使编写模块化代码变得容易,彻底改变了编写数据模型的传统过程。您不再需要一遍又一遍地重复相同的代码行,使您的代码难以阅读并且运行缓慢。

dbt 有许多令人敬畏的特性,使它成为最好的数据转换工具。宏和包是我在数据模型中使用的两个工具。虽然,我最喜欢的一个特性是创建增量模型的能力,但这一点没有被充分提及。

围绕增量模型有很多混乱。我应该在什么时候使用它们?我该怎么写呢?

在本文中,我将讨论它们是什么,为什么您想要使用它们,以及如何使用 dbt 成功地编写一个。还有一个为我所有的雪花用户发布的新特性,将使编写增量模型更加容易。

什么是增量模型?

传统的数据模型将在每次运行时针对整个可用数据集执行代码,替换之前存在的整个数据集。增量模型将只对整个可用数据集执行一次,即第一次运行时。然后,在接下来的运行中,它将只对自上次运行以来的新数据执行。

这通过允许 dbt插入更新可用的新记录来实现。与传统的数据模型不同,增量模型只转换尚未转换的数据。但是,如果记录已经更新,它将转换该数据记录以匹配更新。

增量模型的优势

增量模型减少了数据模型的运行时间。当您的运行时间减少时,数据仓库的性能会提高,您可以节省计算成本。这是一个全面的胜利!

当您将增量模型实现到您的 dbt 项目中时,您将会注意到您的模型运行所需的时间要少得多。在实现增量具体化之前,我的一些 dbt 数据模型每天早上需要运行 3 个多小时。这并不理想,原因有二:

  • 你是在为仓库在雪花网上的时间付费
  • 您的业务团队希望在开始工作时就能获得这些数据

在重写 dbt 数据模型以使用增量具体化之后,许多数据模型只需要 40 分钟左右就可以运行。这不仅降低了成本,而且增加了信心,当团队需要时,您的数据模型将是最新的。

使用 dbt 的增量物化

物化引用数据定义语言(DDL)在你选择的数据仓库中创建等价的模型。使用增量具体化,dbt 将在您的特定仓库解决方案中创建一个增量模型的等价物。

增量模型目前被创建为数据仓库中的视图。因此,您只能对 select 语句使用增量具体化,而不能使用复杂的聚合或连接。总是建议在数据模型的最简单层使用增量具体化。

为了将 dbt 模型指定为增量模型,只需将它包含在 SQL 文件的顶部:

{{
    config(
        materialized='incremental'
    )
}}

为了让 dbt 构建一个增量模型,您还需要在这个文件中提供另外两个配置:

  • 如何过滤数据
  • 唯一性约束(可选)

过滤 dbt 模型

dbt 需要一种方法来知道数据集中哪些行是新的。如果它没有一条数据可以过滤,那么它就不知道如何区分旧数据行和新数据行。记住,增量模型只转换以前没有转换过的数据,除非它被更新。

**is_incremental()**宏用于过滤您的 dbt 模型。如果你不熟悉 dbt 宏是如何工作的,你可以在这里阅读更多的。在宏中,您可以指定一个只希望在增量运行时运行的筛选器。这意味着这个过滤器不是在您的数据模型第一次运行时在您的 SQL 代码中编译的。它只在不是第一次运行时编译。

您的is_incremental()宏中的过滤器应该是一个WHERE语句,它基于您的数据集中的时间戳列进行过滤。

{% if is_incremental() %}where event_created_at > (select max(event_created_at) from {{ this }})

{% endif %}

{{this}}简单来说就是指回数据表本身。因此,您实际上是在说,“对这个数据模型最后一次运行之后创建的新数据运行这个转换”。

dbt 模型中的唯一性约束

如果您没有使用行可以更新的数据表,那么这个步骤是不必要的。只有当数据表允许行随着它们的变化而不断更新时,才需要使用这种方法。

例如,如果有一个表显示在给定时间用户的购物车中有什么,您将希望指定一个惟一性约束,以便 dbt 知道何时更新该行。购物车中的商品会不断变化,您希望在您的增量模型中捕捉这些变化。

指定唯一性约束可以防止主键重复。为了添加它,您只需在前面定义的配置中指定它。

{{
    config(
        materialized='incremental',
        unique_key='order_line_id'
    )
}}

在这里,order_line_id充当我们的数据表的主键。如果昨天的数据模型和今天的数据模型中存在唯一的order_line_id,新的数据行将替换旧的数据行。如果我们没有指定唯一性约束,那么就会有两行数据,昨天的和今天的,具有相同的主键。

配置您的增量模型

现在,我们来讨论最难的部分。如何将我的 dbt 模型重新配置为增量模型?这就是事情变得令人困惑的地方。数据模型越复杂,在 dbt 中利用增量具体化就越复杂。你真的需要把你的模型分解成几个部分。

记住,增量模型目前只能使用SELECT语句运行。我的第一个建议是过滤您想要在模型中使用的数据。因此,如果您在较大的增量模型中使用几个不同的表,那么您会希望将它们分割成不同的增量模型,用于所使用的每个数据表。然后,您将在每个模型中指定具体化配置、过滤器和惟一性约束。

虽然这看起来是多余的,但这是目前运行增量具体化的唯一方法。幸运的是,雪花最近宣布他们将推出物化表。这将允许更复杂的操作,如物化模型中的连接和聚合。敬请期待发布的一篇关于如何使用 dbt 和雪花配置物化表的文章!

调试物化模型

最后,让我们讨论一下如何调试物化模型。由于需要验证,当您第一次编写它们时可能会有压力。我的数据模型看起来像预期的那样吗?这些值是否与模型未具体化时的值相比较?

为了进行调试和验证,您可能需要刷新整个表,就好像它不是增量表一样。您可以使用dbt full-refresh命令来完成此操作。这将刷新您的整个表,并像第一次运行模型一样运行它。

在最初的几天里,并行运行您的模型和增量模型的完全刷新可能会有所帮助,以确保它们相匹配。通过允许您相互验证模型,这将有助于最小化未来的数据灾难。如果您有一个已经投入生产一段时间的数据模型,并且您知道它是可靠的,这将是特别强大的。比较它们会让你对自己的改变充满信心。

结论

令人惊讶的是,一旦构建了初始模型,您就可以利用 dbt 中的所有特性。我不断地寻找新的方法来提高模型的运行时间、可读性和质量!将增量具体化到其中只是不断改进数据转换的众多方法之一。

如果您想了解如何使用免费的 dbt 软件包来防止数据丢失,请点击此处。如果你想掌握 dbt 中的数据透视表,点击这里

欲了解更多关于分析工程、现代数据堆栈和 dbt 的信息,订阅我的免费每周简讯

减少您的担忧:使用 PySpark 的“Reduce”

原文:https://towardsdatascience.com/reduce-your-worries-using-reduce-with-pyspark-642106d6ae50

使用 python 轻松重复 PySpark 操作

Unsplash 上由 S Migaj 拍摄的照片

如果您使用 PySpark,您可能已经熟悉了它编写类似 SQL 的查询的能力。您可以轻松地对常见的 SQL 子句进行方法链,如。select(),。filter/where()/,。join(),。withColumn(),。groupBy()和。agg()来转换 Spark 数据帧。它本身就很强大,但是当您将它与 python 风格的脚本结合使用时,它的功能就变得无限了。我将展示两个例子,在这两个例子中,我使用 python 的 functools 库中的“reduce”来重复地对 Spark 数据帧应用操作。

堆叠桌子

第一个技巧是使用类似 SQL 的 union all 堆叠任意数量的数据帧。假设您有一系列结构相同的表,并且您想将它们堆叠在一起。出于共享的目的,可以将这些表划分成许多较小的表,或者每个表可以代表一个月,或者其他任何原因。在这个例子中,我们假设有一个 parquet 文件路径列表,其中包含一系列需要组合的表。我们可以编写一个不必要的 for 循环来一个接一个地堆叠它们,但是更好的方法是利用 functools 库中的“reduce”。

reduce 函数需要两个参数。第一个参数是我们想要重复的函数,第二个参数是我们想要重复的 iterable。通常当你使用 reduce 时,你使用一个需要两个参数的函数。一个常见的例子是

reduce(lambda x, y : x + y, [1,2,3,4,5])

它会这样计算:

((((1+2)+3)+4)+5)

对于这个例子,我们将使用 DataFrame 方法来代替,并在 iterable 上重复链接它。

这个方法链按照我们的期望组合了我们所有的数据框架。

(dfs[0].unionAll(dfs[1])).unionAll(dfs[2])...

嵌套 OR/AND

在下一个例子中,我们将需要应用一个包含一系列条件的过滤器,这些条件可能全部由 or 或 and 连接。一个常见的 SQL 示例是,您可能希望查询与特定市场相关的所有行,您只需要知道“市场”字段的前三个字符。

SELECT * FROM TABLE
WHERE (MARKET LIKE 'AQ1%' OR MARKET LIKE 'AW3%' OR MARKET LIKE 'M89%' OR ...)

对于我的表,每个人都有一行,一系列列表示他们在那个月是否被覆盖。如果他们在那个月被覆盖,他们在列中有 1,否则他们有 0。我想查询至少在某个时间范围的某个点上有覆盖率的所有成员,所以我想编写的类似 SQL 的查询如下所示:

SELECT * FROM TABLE
WHERE (COV_2007 = 1 OR COV_2008 = 1 OR COV_2009 = 1 OR ... OR COV_2106 = 1)

在 SQL 中单独编写这个是一件痛苦的事情,但是使用 python 我们可以很容易地编写这个重复 OR 条件的脚本。

为此,我首先创建一个我感兴趣的列的字符串列表。在示例中,我想要 2020 年 7 月到 2021 年 6 月(它们被命名为“cov _ 2007”—“cov _ 2106”)。

接下来,我创建一个列级过滤器的列表,在本例中,我希望该列等于值 1。使用 reduce 之前的最后一步是创建我想要重复的函数。在这种情况下,我创建了一个 lambda 函数,它只接受两列的逻辑或(如果需要,您可以使用' & '来代替 AND)。

这就够了!使用' reduce '节省了我大量的时间来写出不必要的条件,或者写一个不好的 for 循环。我希望你喜欢阅读!

满怀期望地减少管道债务

原文:https://towardsdatascience.com/reducing-pipeline-debt-with-great-expectations-f1afddbfdc0b

总是知道从你的数据中可以期待什么

本文首发于 海王艾的博客

你是一家产品公司数据科学团队的一员。你的团队有很多机器学习模型。他们的输出指导着关键的业务决策,还有几个显示重要 KPI 的仪表板,这些 KPI 由您的主管日夜密切关注。

在那致命的一天,你刚刚为自己泡了一杯咖啡,正要开始一天的工作,这时宇宙塌了。公司的每个人都疯了。业务指标仪表板显示的似乎是随机数字(除了每隔一个小时,KPI 在短时间内看起来没问题),模型预测该公司即将破产。更糟糕的是,每一次解决这种疯狂的尝试都导致您的数据工程和研究团队报告新的不完善的服务和模型。

那天是讨债日,未付的债务是最糟糕的一种:管道债务。是怎么积累起来的?让我们回到几个月前。

什么是管道债务?

你正要开始一个新的令人兴奋的机器学习项目。您已经找到了分散在公司数据库、功能存储和电子表格中的一些有用数据。为了使数据可用,您构建了一个数据管道:一组接收、处理、清理和组合所有这些数据的作业和 Python 函数。最后,您的管道将数据输入到机器学习模型中。我们可以把整个过程示意性地描述如下。

简单的数据管道易于管理。图片由作者提供。

你的数据管道工作得很好:它不断用数据填充下游的机器学习模型,模型将这些数据转化为准确的预测。然而,在云中作为服务部署的模型不太可行。为了使它更有用,您构建了一组仪表板,用于向业务涉众呈现模型的输出以及重要的 KPI,从而深化了您的管道。

现有管道可能会延长。图片由作者提供。

一天,你在午餐时和研究团队的一位同事谈论你的项目。他们非常兴奋,并决定对他们的数据做一些类似的事情,使公司的数据管道更宽,跨团队边界。

管道越多,系统越复杂。图片由作者提供。

几周后,你们俩又聊了起来。当你们了解了研究团队的工作时,你们都注意到如果你们的两个团队使用彼此的数据为各自的模型和分析提供支持,这将是多么有用和有价值。在实现这个想法时,公司的数据管道是这样的。

如果存在多条管道,它们将不可避免地融合在一起。图片由作者提供。

上面的图片应该已经敲响了警钟——它们显示的是管道债务的积累。

管道债务是数据管道中的技术债务当您的数据管道是三 U 的时候,它就出现了:未记录的、未测试的和不稳定的。

它有多种口味,但都有一些共同的特点。这个系统是纠缠不清的,这意味着一个地方的变化可能会破坏其他地方的不同进程。这使得代码重构和调试异常困难。

管道债务是数据管道中的技术债务。当你的数据管道是三 U 的时候,它就出现了:未记录的、未测试的和不稳定的。

对于软件工程师来说,这听起来像是一个已经解决的问题,这个解决方案叫做自动化测试。然而,测试软件在两个主要方面与测试数据非常不同:

  • 首先,虽然您可以完全控制您的代码,并可以在它不起作用时更改它,但您不能总是更改您的数据;在许多情况下,你仅仅是一个观察者,看着一些真实世界的过程产生的数据。
  • 第二,软件代码总是有对有错:要么它做了它被设计去做的事情,要么它没有。数据从来没有对错。它只能适合或不适合某个特定的目的。这就是为什么当涉及到数据时,自动化测试需要一种特殊的方法。

自动化测试:拯救的期望

为数据管道量身定制的自动化测试是 Great Expectations 的前提,Great Expectations 是一个广泛使用的用于数据验证的开源 Python 包。

永远要知道从你的数据中能得到什么。

由超导公司开发并于 2018 年首次发布的《远大前程》的口号是“永远知道从你的数据中可以期待什么”,这正是它所提供的。

该包是围绕期望的概念构建的。期望可以被认为是对数据的单元测试。它是一个声明性语句,描述数据集的属性,并以简单、人类可读的语言进行描述。

例如,要断言某个表中“num_complaints”列的值介于 1 和 5 之间,您可以编写:

expect_column_values_to_be_between(
    column="num_complaints",
    min_value=1,
    max_value=5,
)

该语句将验证您的数据,并返回成功或失败的结果。

期望可以被认为是对数据的单元测试。

正如我们已经提到的,你并不总是控制你的数据,而是被动地观察它的流动。通常情况下,一个非典型的值会不时地出现在您的数据中,但不一定是令人苦恼的原因。伟大的期望通过“mostly”关键字来适应这一点,该关键字允许描述期望应该多久匹配一次。

expect_column_values_to_be_between(
    column="num_complaints",
    min_value=1,
    max_value=5,
    mostly=0.95,
)

如果至少 95%的“num_complaints”值在 1 到 5 之间,上述语句将返回成功。

为了更好地理解数据,有一些关于为什么我们期望从数据中得到某些属性的上下文是至关重要的。我们可以简单地添加它,方法是将“meta”参数传递给带有任何相关信息的期望。我们的同事甚至我们未来的自己都会为此感谢我们。

expect_column_values_to_be_between(
    column="num_complaints",
    min_value=1,
    max_value=5,
    mostly=0.95,
    meta={
        “created_by”: “Michal”,
        “craeted_on”: “28.03.2022”,
        “notes”: “number of client complaints; more than 5 is unusual and likely means something broke”,
    }
)

这些元数据注释也将形成数据文档的基础,这些数据文档是远大前程凭空产生的——稍后会有更多内容!

该包包含几十个现成可用的预期,它们都有冗长的、人类可读的名称,如“expect _ column _ distinct _ values _ to _ be _ in _ set”、“expect _ column _ sum _ to _ be _ between”或“expect _ column _ KL _ divergence _ to _ be _ less _ than”。这种语法允许人们清楚地陈述对数据的期望以及原因。

有些期望适用于列值,有些则适用于它们的集合函数或整个密度分布。当然,该软件包还可以轻松地为何时需要定制的解决方案创建定制的期望。

《远大前程》有很多不同的后台。您可以在 Pandas 数据框架上本地评估您的期望,就像在 SQL 数据库(通过 SQLAlchemy)或 Apache Spark 集群上一样容易。

那么,这些预期如何有助于减少管道债务呢?这个问题的答案是多方面的。

  • 首先,制定预期的过程迫使我们坐下来思考我们的数据:它的性质,来源,以及它可能出错的地方。这有助于加深理解,并改善团队内部与数据相关的沟通。
  • 第二,通过清楚地陈述我们对数据的期望,我们可以在早期检测到任何异常情况,比如系统中断。
  • 第三,通过对照一组预先存在的预期来验证新数据,我们可以确保我们不会给我们的机器学习模型输入垃圾。
  • 接下来,定义了期望之后,我们就非常接近拥有维护良好的数据文档了。这个清单还在继续。

我们将很快讨论了解一个人的期望的所有上述好处,但首先,让我们设置 GE 包!

满怀期望地开始

在本文的剩余部分,我们将介绍 GE 最有用的特性,以及这个包的几个巧妙的用例。为了保持实用性,这些例子使用了真实的数据集,所以在我们深入研究 Great Expectations 功能之前,让我们花一点时间来讨论问题设置和数据集,以及如何在您的机器上安装和设置 GE。

问题设置

我们将查看芝加哥市的出租车出行数据集。这些数据包含向市政当局报告的每次出租车行程的信息,例如行程的开始和结束时间、出租车 ID、行程的距离、费用、上下车地点等等。原始数据非常庞大(从 2013 年到现在每月更新一次),因此为了演示的目的,我们将它限制在两天内:2022 年 2 月 27 日和 2 月 28 日。这相当于 13,000 多次旅行。

我们将把 2 月 27 日的数据视为我们公司的现有数据,我们将自动对其进行分析,以制作预期套件。然后,我们会将 2 月 28 日的数据视为新的传入数据,我们将根据我们的预期对其进行验证,以确保那里没有任何古怪的事情发生,并且我们可以安全地将这些新数据添加到我们公司的数据库中,并利用它们来训练机器学习模型,例如。

树立远大的期望

让我们从安装包开始。远大前程需要 Python 3,可以用 pip 安装。

pip install great_expectations

上面的命令不仅安装了 Python 包本身,还安装了附带的 CLI(命令行界面),它提供了从终端可用的便利实用程序。我们现在将使用其中的一个命令 init 来建立远大前程项目。

great_expectations init

运行此命令后,您应该会在终端窗口中看到以下提示:

图片由作者提供。

键入 enter 继续,一个名为“great_expectations”的目录将出现在您的项目目录中,其中包含上面截图中显示的所有内容。

所有这些都被包的作者称为数据上下文。数据上下文包含了 Great Expectations 为您的项目提供服务所需的所有文件。它包含各种配置和元数据,并提供对数据源、期望和其他 GE 对象的访问。不需要太担心;现在,我们可以相信设置向导已经正确初始化了数据上下文,并继续连接一些数据。

连接数据

要将新的数据源连接到我们的数据上下文,我们只需在终端中运行以下命令。

great_expectations datasource new

这将生成三个提示。首先,系统会询问我们是想连接到文件系统还是数据库。由于我们在本地将出租车出行数据保存为 CSV 文件,因此我们选择前者。第二个问题是关于我们希望使用的处理引擎:pandas 还是 spark。我们喜欢熊猫。最后,我们需要输入数据文件的路径。

图片由作者提供。

提供所有必要的输入导致 Jupyter 笔记本被打开。名为“datasource_new”的笔记本提供了一些用于配置数据源的样板 Python 代码。默认设置很好,所以我们不需要改变任何东西,也许除了第二个代码单元中的数据源名称。我把我的旅行叫做“旅行”。

图片由作者提供。

更改名称后,我们需要运行所有的笔记本单元,这将有效地创建我们的数据源。最后一个单元格的打印输出应该确认我们的“trips”数据源存在。现在,随着数据源的创建,我们可以安全地关闭和删除笔记本了。

随着软件包的设置和数据的连接,我们可以深入了解远大前程的关键特性!

《远大前程》的主要特点

远大前程提供了三个非常有用的特性:

  • 自动化数据分析,从手头的数据中创建期望套件。
  • 数据文件的自动生成。
  • 最后,对新数据进行验证,以防止新数据进入我们的数据库和机器学习模型。

让我们一个一个地看。

自动化数据分析

期望从何而来?您可以根据您对数据和任何相关领域知识的熟悉程度,逐个手动起草它们。然而,更常用的方法是让 GE 通过分析现有数据来自动创建它们。这是产生一套基本期望的快速方法,我们可以在以后扩展和建立这些期望。

自动分析器考虑数据的几个基本属性:列的类型、聚合统计信息(如最小值、最大值或平均值)、非重复值计数和缺失值的数量等等。

为了运行自动化概要分析,我们需要导航到终端中数据上下文所在的目录,并运行以下命令。

great_expectations suite new

系统会弹出提示,要求我们选择期望的创建方式。我们选择最后一个选项,自动分析器。

图片由作者提供。

通用电气然后询问要分析哪些数据。它在我们的数据上下文中检测到可用的 CSV 文件。如前所述,我们选择 2 月 27 日的数据进行分析。

图片由作者提供。

接下来,我们又得到了两个提示。首先,我们需要键入期望套件的名称,然后确认到目前为止所做的所有选择。正如你可能已经猜到的,这将打开一个充满样板代码的 jupyter 笔记本,允许我们定制我们的期望。

在笔记本的第二个代码单元中,我们可以看到一个名为“ignored_columns”的变量,它被定义为数据中所有列的列表。这里我们需要做的是注释掉我们真正想要分析的列。让我们注释掉旅行时间、距离和费用。

图片由作者提供。

然后,我们只需要运行整个笔记本来创建期望套件。我们的期望套件已经作为一个 JSON 文件保存在我们的数据上下文中的期望目录中。虽然我们可以浏览这个可读性很好的 JSON 文件,但是查看数据文档要方便得多,因为数据文档应该在我们运行笔记本时就已经在浏览器中打开了。这就给我们带来了《远大前程》的第二大特点。

数据文档

这个包自动将一个 expect suite 呈现到一个 HTML 页面中,这个页面可以作为数据文档:一个关于有什么数据以及数据应该是什么样子的真实来源。

图片由作者提供。

数据文档以数据的汇总统计数据和基于它们创建的期望为特色。左侧 Action 面板中的黄色按钮指导我们编辑预期,以便我们可以修复可能错误生成的预期或添加全新的预期。随意点击,探索这个仙境!一旦你回来,我们将继续新数据的验证。

新数据的验证

为了防止管道债务累积,渴望进入你的数据库、分析和模型的每一部分新数据都应该得到验证。这意味着我们希望确保新数据满足我们基于现有数据和/或领域知识产生的所有期望。为了用 GE 做到这一点,我们需要建立一个用软件包的行话来说叫做检查点的东西。

一个检查点针对一批数据运行一个期望套件。我们可以通过将“checkpoint new”关键字传递给 great_expectations,后跟所选的检查点名称来实例化它。在这里,我将我的命名为“2 月 28 日检查点”。

great_expectations checkpoint new feb_28_checkpoint

这将打开另一个配置笔记本。对我们来说唯一重要的单元格是定义“yaml_config”变量的第二个代码单元格。在那里,我们可以选择应该验证哪个数据集(“data_asset_name”)以及应该评估哪个期望套件(“expectation_suite_name”)。这一次,我们可以保留所有缺省值——GE 已经推断出,因为我们只有两个数据文件,其中一个用于概要分析,所以我们可能希望验证另一个。

图片由作者提供。

为了运行我们的检查点,也就是评估我们对新数据的期望,我们只需要取消笔记本最后“运行您的检查点”部分的最后两行代码的注释,然后运行它。这将再次打开数据文档,这次向我们显示验证结果。

对于我们的出租车出行数据,许多期望都落空了。其中一些失败是可以预料到的:例如,基于 2 月 27 日的数据,我们已经创建了一个预期,表明中值票价应该大于或等于 21 英镑,而对于 2 月 28 日的数据,中值票价是 15 英镑。看到不同日子的中间价格不同并不奇怪。

图片由作者提供。

这个例子强调了仔细分析和起草期望套件的重要性。自动化分析器生成的值应该被视为一个起点,而不是一个现成的套件。

手动处理期望值

在前面的章节中,我们借助 Jupyter 笔记本使用了 CLI。但是,可以手动创建和编辑预期。

如前所述,expectation suite 仅仅是一个 JSON 文件,包含我们在本文开头看到的格式的期望,例如:

{
      "expectation_type": "expect_column_values_to_not_be_null",
      "kwargs": {
        "column": "Trip Seconds",
        "mostly": 0.99
      },
      "meta": {}
    },

我们可以使用任何文本编辑器来修改这些期望或添加新的期望。这种与 GE 交互的方式非常方便,尤其是当您有一个大型的期望套件或其中的几个时。

远大期望的用例

在前面的章节中,我们已经经历了一个非常标准的建立数据验证过程的工作流程。让我们稍后再来看看:

  • 我们使用一个自动化的数据分析器来创建一个基于现有数据的期望套件。
  • 我们仔细分析预期,根据需要修正和添加更多内容(我们在本教程中并没有真的这样做,因为这是一个非常数据和领域特定的过程,但它是实践中至关重要的一步!).在这一步,我们可能会在数据中发现一些有趣或危险的属性。现在是澄清这些问题的时候了。
  • 我们根据我们的期望套件运行每一批新的数据,只有当它通过验证时,才允许它进一步进入我们的数据管道。如果失败了,我们会试着去理解为什么以及是否是新的数据出现了偏差,或者可能是我们的预期没有适应某些极端情况。

现在我们知道了如何带着巨大的期望来验证数据,让我们来讨论几个具体的用例,在这些用例中,在 GE 上投入时间会得到很大的回报。

检测数据漂移

部署在生产中的机器学习模型的一个臭名昭著的危险是数据漂移。数据漂移是指模型输入的分布发生变化的情况。发生这种情况有多种原因:数据收集设备容易损坏或需要更新软件,这会影响数据的记录方式。如果数据是由人类产生的,那么随着时尚和人口统计的快速发展,数据会更加不稳定。

数据漂移构成了机器学习模型的严重问题。它可以使算法学习到的决策边界对于新状态数据无效,这对模型的性能有不利影响。

数据漂移可能会影响模型的性能。图片由作者提供。

输入数据验证。

在数据漂移可能令人担忧的情况下,只需对模型输入要素建立预期,以验证其长期趋势、平均值或历史范围和波动性。一旦世界发生变化,你的输入数据开始变得不一样,通用电气将会通过抛出一系列失败的测试来提醒你!

防止异常值扭曲模型输出

对部署在生产中的模型的另一个威胁,与数据漂移有点类似,是离群值。当一个模型得到一个不寻常的值作为输入,通常很高或很低,那么它的输出会发生什么?如果模型在训练期间没有看到这样的极值,诚实的回答应该是:我不知道预测应该是什么!

不幸的是,机器学习模型没有这么诚实。恰恰相反:模型可能会产生一些非常不可靠的输出,没有任何警告。

幸运的是,一个人可以很容易地通过适当的期望套件来防止它!只需根据模型在训练中看到的内容为模型的输入要素设置允许的范围,以确保您不会根据异常值进行预测。

防止管道故障蔓延

数据管道有时确实会失败。你可能错过了一个关键案例。或者您的服务器机房可能暂时断电了。不管是什么原因,一个期待新文件出现在某个地方的数据处理作业突然发现什么也没有。

如果这会导致代码失败,那也不一定是坏事。但通常不会:作业成功了,并高兴地向下游系统宣布您的网站在前一天的访问量为 0。这些数据点随后显示在 KPI 仪表盘上,甚至更糟的是,被输入到自动重新训练的模型中。如何防止这种情况发生?

当然是带着期望。简单地期望最近的数据——例如,带有足够新的时间戳——在那里。

检测有害的偏见

机器学习模型中的偏见是一个最近越来越受到关注和兴趣的话题。考虑到这些模型对人们生活的深远影响,这一点至关重要。公开的问题是如何检测和防止这些偏见做魅力。

虽然它们不能提供最终的答案,但远大的期望至少可以帮助我们发现危险的偏见。机器学习中的公平是一个庞大而复杂的话题,所以让我们专注于大图的两个小部分:进入模型的训练数据,以及它对不同测试输入产生的预测。

当涉及到训练数据时,我们希望它是公平和无偏见的,无论这在我们的特定情况下意味着什么。例如,如果数据是关于用户的,您可能希望以适当的比例包括来自不同地理位置的用户,以匹配他们的全球人口。在数据被允许用于训练之前,可以通过对照适当的期望套件验证每批训练数据来检查是否是这种情况。

至于模型的输出,例如,如果男女的其余特征相同,我们可能希望它对男女产生相同的预测。为了确保这一点,只需在一个保留测试集上测试模型,并根据一套预先设计好的预期运行结果。

改善团队沟通和数据理解

最后但同样重要的是,让我给你举一个非常有创意地使用远大前程的例子,这是我从软件包的作者之一詹姆斯·坎贝尔那里听到的,他在数据工程播客中接受了采访。

也就是说,您可以从创建一个空的期望套件开始,也就是:列出所有的列,但是不要对它们的值进行任何检查。然后,召集参与数据或业务流程的人员,询问他们:令人担忧的每月最大流失率是多少?网站粘性要降到多低才会触发警报?这样的对话可以改善团队之间与数据相关的交流,以及公司内部对数据本身的理解。

额外资源

要了解更多关于 Great Expectations 包的信息,包括如何使用 Apache Spark 或关系数据库,或者如何编写自定义期望,请查看该包的官方文档。写得真的很好,读起来也很愉快。你可能也喜欢听已经提到的对 GE 作者之一的采访,如果你正在寻找一个更短的资源,看看超导公司产品负责人的这个演讲,该公司背后是远大前程。最后,我希望你总是知道从你的数据中期待什么!

感谢阅读!

如果你喜欢这篇文章,为什么不在我的新文章上 订阅电子邮件更新 ?通过 成为媒介会员 ,你可以支持我的写作,并无限制地访问其他作者和我自己的所有故事。

需要咨询?你可以问我任何事情,也可以在这里 预定我 1:1

你也可以试试我的其他文章。不能选择?从这些中选择一个:

</8-hazards-menacing-machine-learning-systems-in-production-5c470baa0163> </8-tips-for-object-oriented-programming-in-python-3e98b767ae79>

重构:使代码可读

原文:https://towardsdatascience.com/refactoring-make-this-code-readable-59b998015218

"编程时,永远遵循露营规则:永远让代码库比你发现它时更健康."马丁·福勒[2]。

本文是为开发人员、技术领导者或好奇的人编写的,他们希望了解如何在给定的代码库上节省时间,如何使开发过程更快,如何让开发人员感觉更好,并使他们能够维护一个健康和敏捷的代码库。重构是软件工程中的嵌入活动,需要技能和经验。我写了这篇文章,作为我自己经验的一个简短总结,并得到了《重构》[2]和《干净手艺》[1]这两本书的支持。

我已经解释了为什么重构是绝对必要的,并且应该是所有开发活动的一部分,并且列出了它提供的许多好处。然后我解释了重构时要达到的目标,最后解释了重构常见模式的内容和方法。

照片由来自 Unsplash 的 andersjilden 拍摄

重构实现一个好的设计

重构旨在改进代码设计。一个好的设计避免管理僵化、不灵活和脆弱的代码库。当代码基础是如此的不可行,以至于测试和重构都是缓慢的和避免的。这是垂死的软件。开发人员总是需要在代码中实现新的特性。变更的成本不应该超过变更的预期范围。

好的设计遵循四条规则[1]:

  1. 没有回归,测试通过
  2. 揭示意图(代码表现力)
  3. 无重复
  4. 小(尽可能少的类,尽可能少的方法)。

测试:

好的设计有好的测试覆盖率。代码设计和代码覆盖率有什么关系?原因是可测试代码是解耦代码。被测试的代码必须被设计成可测试的。在 TDD 框架中,编写代码先于编写测试。

良好的测试覆盖率也有助于随着时间的推移改进设计。

揭示意图:

代码应该是不言自明的。每一个表达代码“做什么”的注释都应该用一个更有表现力的代码来代替。我们只对代码的“为什么”进行评论。变量、函数和类型的名称都有很深的描述性。考虑像写故事一样写代码。首先,角色应该在故事开始前被描述。然后,故事应该让读者明白,直接与已经定义好的人物联系起来,最后,故事应该逻辑清晰。如果一个变量或一个方法的名字不清楚,阅读时就要浪费时间。

作者在故事中唯一没有的是他为什么选择这样写故事。这是我们接受使用注释的地方。

测试给出了代码的上下文及其背后的意图。这就是为什么总的来说,代码和测试是系统的每个元素是什么,系统做什么,以及如何使用系统的表达。

小:

YAGNI 框架:“如果你不需要它怎么办”

好的代码也能解放你的时间。如果代码的含义模糊不清,所有未来的代码用户都会问你问题,会误解你函数的含义,浪费他们和你的时间去理解函数。为你未来的自己和其他人节省时间,让他们第一次就写对。

此外,如果您不在,团队应该仍然能够重用您的工作并推进项目。

记住这一点,现在我们可以开始重构了。

我真的需要重构吗?

在开始重构之前,你需要一个重构的目标作为关注的方向。

为什么我们应该重构?

  • 一些项目需要实验性的代码,实现起来又快又脏。在证明代码可以工作之后,需要对代码进行结构化,以便将来可以维护和扩展。
  • 需求已经改变,或者我们对问题的理解已经发展,现有的代码设计不再满足我们的需求。重构改进了软件开发过程中任何阶段的代码设计。
  • 重构提高了代码的可读性。写代码的时候,有两个步骤:让代码工作,然后让代码可读。同时制作这两个需要很多经验和技巧。对我来说,我确信这将是两个不同的阶段。在使代码在功能上起作用之后(代码符合验收标准并显示出预期的行为),我需要确保代码的未来读者不会花费我已经花费在理解代码行为上的时间。计算机能理解肮脏的代码,但人类不能。
  • 重构有助于发现 bug。当代码存在并工作时,我们大多数人不会花时间去深入阅读它。重构是一个再次仔细阅读代码并记住代码编写时所做的假设的机会。
  • 重构允许简单地进行分析,以便在以后找到性能瓶颈。重构允许将来对功能进行优化,比如它们可以很好地解决当前的需求。如果代码结构良好且清晰,就更容易找到代码的瓶颈并进行优化。
  • 重构的代码允许更快的迭代。当代码库很清晰,组件也很好理解时,添加新特性就很容易了。如果代码库不受控制地成长为一个分支和线条的怪物,开发速度就会受到损害。开发者需要花时间记住代码每次是如何工作的。除了干净的代码和良好的文档,重构是关于理解代码并将这些知识放回代码中。
  • 重构的代码可以节省时间和代码行。添加功能并不意味着添加代码,而是改变现有的代码以包含这种新的行为。当你做一些类似于现有代码的事情时,这一点尤其明显。团队中的每个开发人员都应该推动更多的重构,而不是太少。它使代码库更健康,开发更快。重构并没有减慢这个过程,而是总体上加快了这个过程。这为公司带来了无可争议的经济收益,这是所有领导者都应该追求的。关于节省时间,重构可能是一个兔子洞,所以这项活动需要时间限制和范围。
  • 重构的代码允许更好的模块化(只需要理解程序的一小部分就可以修改程序的能力)。为了获得这种模块化,我需要确保相关的软件元素被分组在一起,并且它们之间的链接易于找到和理解。

减少模块之间相互了解的需要有助于减少我在改变一些东西时需要向我的大脑输入的信息——我的大脑没有以前那么大了(尽管这并没有说它的容器有多大)。[2]

什么时候应该重构?

实际上一直都是。

  • 开始编码前
  • 在编码期间
  • 在编码之后

重构的最佳时间是在我向代码库添加新特性之前[2]。这给了我时间来看看我们是否能改进代码结构。如果函数中的参数是硬编码的,我宁愿将参数作为实参,而不是复制/粘贴带有不同硬编码参数的函数。开始编码前的重构应该是自然而然的。因为我们需要阅读代码库,以便在开始工作之前理解它的功能。当阅读代码时,如果我看到一些小的和最小的改进来帮助下一个读者(分解功能,添加文档等),我应该在开始工作之前实现它们。一旦代码被清理,我们就可以更清楚地看透它。重构不仅是为了帮助未来的用户,也是为了帮助现在的用户。

编码过程中的重构也是非常自然的。当我们让特性工作时,我们需要让它反复可读。

最后,在推送一个新的拉请求之前,我们应该确保我们的代码被清理和格式化。虽然目前在现代代码编辑器(如 VSCode)中格式化是自动的,但变量命名仍然取决于开发人员。变量名应该易于理解,代码对用户来说应该像一个童话故事。每一个变量的引入,故事的逻辑性,几乎不会让读者感到惊讶。

尤其是现在,在同一个代码库中有大量质量不一的代码,重构有助于一起改进代码库。

什么时候不应该重构:

  • 大多数时候,在预见未来需求时,没有必要为了一般化而重构(记住 YAGNI 的概念:“你不会需要它”)。

重构隐含地要求你比在你之前写代码的人更好地了解你需要解决的当前问题。

重构伴随着测试

测试也有很多目的。它通过确保类的行为符合预期来防止错误,并帮助读者理解代码应该做什么和不应该做什么(不要忘记测试您的异常和边缘情况)。最后,测试对于健康的重构过程是必要的。当重构时,我们寻求在保持相同行为的同时改变代码结构。一个测试应该遵循三个阶段:设置、练习、验证 [2]。

在重构之前,我们需要确保测试存在并且测试通过。测试应该运行得很快,不到一分钟。然后你可以做著名的循环重复:

  • 重构
  • 运行测试
  • 提交更改

几乎同样频繁地进行更改,否则,一旦一个测试被破坏,就很难知道哪个更改修改了代码的行为。

所以这个重构循环“重构、测试、提交”应该在非常小的代码块上完成。我们都会犯错误,我们可能会犯错误。如果在修改了 500 行代码后测试失败,我们希望能轻松地发现错误,我将很难找到错误在哪里。

我喜欢布雷特·斯拉特金提出重构的先决条件:

  • 快速测试
  • 源代码控制
  • 愿意犯错误

重构

我们已经看到重构有许多潜在的原因。然而,程序员有更多的事情要做,不能把自己投入到不必要的活动中。然后,我们需要对重构的正确信号非常敏感。在这一节中,我将描述我最喜欢的重构信号。我需要重构并做出一些改变的信号。变化总是遵循三个主要阶段:

  • 试着理解发生了什么
  • 将洞察力移回到代码中
  • 继续理解,在一个有益的正向循环中添加边界。

现在让我们深入研究一下。

重构范围:何时开始,何时停止

  • 不明代码

当你不得不阅读代码,但代码的故事不清楚。没有的函数、模块和变量不是好名字,不能清楚地表达它们应该做什么。

  • 重复代码

当代码在不同的部分被重用时,我们从中提取一个函数,并调用这个函数。

  • 长函数

将长函数分解成更小的函数。您可以使用空格、注释段落或循环来轻松分解它。

  • 大班

当一个类变得太大时,可以尝试提取另一个类。取那些放在一起的变量,它们共享后缀或前缀。这个提取的类与最初的大类的关系可以是继承或组合。试着消除潜在的冗余。

  • 长参数列表

通过使用类或 dataclasses 对象作为参数可以避免这种情况。然后我们用一个查询替换这个参数。例如,如果多个函数同时使用变量“开始日期”和“结束日期”作为参数。您可以使用变量“日期范围”来代替。将数据分组到新的结构中使得参数之间的关系更加明确。

  • 发散性变化和散弹枪手术[2]

Martin Fowler 在他的书《重构》中称之为“不同的变化”,当一个模块经常因为不同的原因以不同的方式改变时,就会发生这种变化。大多数时候,代码中容易改变的部分应该被分离出来,这样我们就可以避免为了接口而改变一个模块。它可以将数据加载到一个模块中,并在另一个模块中处理数据。

“猎枪手术”是相反的模式,当一个变化需要在许多不同的类中进行许多小的编辑。在这种情况下,将小的编辑统一到一个函数中,或者找到一个通用的模式来提取和调用,对于减少编辑时间非常有用。

  • 内置类型的过度使用

对于具体用法,使用封装了特定领域(温度、坐标系、货币)逻辑的对象比原语类型更清晰。

  • 一长串“如果”语句

一长串的条件可能会被多态性所取代。条件可以封装在具有共享方法的不同类中(例如,从抽象类继承)。我们可以调用来自共享相同父类的一族类的实例(然后是抽象方法),而不是调用一系列 if。

  • 将循环改为管道

这是我收到的最违背直觉的重构建议之一。将循环重构为连续的管道阶段。具有多重职责的循环可能会令人困惑。可以使用内置的“过滤器”、“映射”、“简化”(参见链接中的示例)或行列表理解来重构它们。这允许将一个循环分解成多个循环(如果有必要,可以在弄清楚代码后进行性能优化),这些循环可读性更强,调试更容易。

  • 消除不必要的复杂性

它可以合并两个变得常见的类。经常会发生这样的情况,抽象类不再是必需的(只有一个子类)。删除当前不需要的过于通用的类。您还可以从代码库中删除未使用的(死的)代码。

  • 分担责任

一个功能应该承载一个责任。当代码试图做两个(或者更多!)不同的东西,要尝试拆分成不同的功能。越容易将功能分成两个连续的阶段。然后,我们可以专注于单独理解每个功能,而不必同时理解周围的其他功能。

对于变量也是如此,如果您使用相同的变量来保存不同的值,那么最好使用两个不同的变量。它经常与占位符变量一起出现,如tmpvarres等。

# Before
def transform_img(img, angle=90):
    img = rgb_to_binary(img)
    img = rotate_img(img, angle)
    return img# After
def transform_img(img, angle=90):
    binary_img = rgb_to_binary(img)
    rotated_binary_img = rotate_img(binary_img, angle)
    return rotated_binary_img
  • 包装

封装意味着模块需要了解系统的其他部分。

intersection_coordinates = road_edge.geo.coordinates

可以替换为

intersection_coordinates = road_edge.coordinatesclass RoadEdge:
    @property
    def coordinates(self):
        return self.geo.coordinates

这将有助于将来对类RoadEdge的任何修改,并减少所有代码的编辑次数。客户端代码不需要知道类的 geo 属性来调用坐标。

我们还使用封装将可变记录转换成对象:

# Before
image_sample = {"image": image_array, "dataset": "SatelliteDataset_01", "resolution": 0.5}# After
@dataclass
def ImageSample:
    image_array: np.ndarray
    dataset: str
    resolution: float
image_sample = ImageSample(image_array, "SatelliteDataset_01", 0.5)
  • 移动方法

当一个函数从另一个上下文中引用的元素多于它当前所在的上下文时,这是将这个函数移到另一个上下文的标志。

这也可能跨类继承发生。如果子类从其父类继承了一个他们不需要的方法,这意味着这些方法可以向下移动到唯一使用这个方法的兄弟类。

您还可以移动语句,例如相互关联的事物,使其一起出现。如果几行代码引用相同的元素,它们应该一个接一个地出现在代码中,而不是与访问其他数据结构的代码行混杂在一起。滑动语句的一个常见情况是变量声明和变量的使用应该总是尽可能接近。

  • 使用多态性

如果一族类使用相同的接口,它们可以互相替换。

  • 提取代码

从代码块中提取一部分外部代码并重命名。它可以是:

  1. 提取变量
  2. 提取函数
  3. 使用函数中的变量
  4. 从代码中提取一个类

提取函数提高了可读性,类使测试变得容易,因为我们可以通过测试产生它们的函数来测试中间结果。

从代码块中提取函数可能是主观的。我应该将代码提取到多个连续的函数中,还是让逻辑在 50 行连续的代码中流动?有些人说函数应该适合计算机屏幕,有些人说应该只提取重用的函数,否则就让代码内联。当代码变得不清楚它在做什么时,提取函数也是有意义的。一个函数是代码故事中的一章,故事应该清晰易读。变量也是一样,我们可以把复杂的计算分解成中间变量。最后,由你来决定什么是最清楚的。

参考资料:

[1]干净的手工艺,塞西尔马丁。

[2]《重构》,马丁·福勒,肯特·贝克。

脸书-剑桥分析数据丑闻后的隐私问题

原文:https://towardsdatascience.com/regional-differences-in-information-privacy-concerns-after-the-facebook-cambridge-analytica-data-15c6c29a623a

一项关于重大隐私丑闻的跨地区跨语言 Twitter 研究

Firmbee.comUnsplash 上拍照

介绍

2018 年,剑桥分析公司被指控在未经授权的情况下收集和使用超过 8700 万脸书用户的个人信息。与此相关的观点、事实和故事在社交媒体上分享,包括 Twitter,其中#DeleteFacebook 标签成为了几天的热门话题。

虽然全球对数据隐私越来越关注,但大多数隐私研究仅在北美和欧洲的少数几个国家进行。在本文中, 我们描述了一种通过分析与这起重大数据隐私丑闻相关的社交媒体内容来研究更大地理范围内的数据隐私的方法。我们还报告了我们方法的局限性、发现和未来的工作方向

您可以在我们的论文中找到更多关于我们的方法和发现的详细信息:

费利佩·冈萨雷斯·皮萨罗、安德里亚·菲格罗亚、克劳迪娅·洛佩兹和塞西莉亚·阿拉贡。 脸书-剑桥分析公司数据丑闻 后信息隐私担忧的地区差异。发表于计算机支持的协同工作(CSCW) 31 33–77(2022)

我们的论文分析了 100 多万条与脸书-剑桥分析公司丑闻相关的公开推文。数据集按语言(西班牙语和英语)和地区(拉丁美洲、欧洲、北美和亚洲)划分。使用单词嵌入和人工内容分析,我们研究和比较了使用隐私相关术语的语义上下文。然后,我们将我们的结果与最常用的信息隐私关注框架(IUIPC)进行了对比。在我们的结果中,我们观察到了隐私问题的语言和地区差异,这暗示了对当前信息隐私框架扩展的需要。

我们的方法

我们实施了一个四步方法来确定不同语言和世界地区的信息隐私问题的差异(见图 1)。 (1)数据收集:检索特定时期内与数据隐私相关的推文。 (2)数据预处理:过滤数据,去除转发,排除可能由僵尸工具生成的推文。 (3)文本挖掘:根据语言和世界区域为剩余的推文创建单词嵌入(语料库的多维表示)。 (4)编码&分析:分析单词嵌入中隐私关键词语义语境的异同。

图 1:四步法|作者图片

数据采集

我们使用 Tweepy 收集了 2018 年 4 月至 7 月期间与脸书-剑桥分析公司丑闻相关的西班牙语/英语。 Tweepy 是一个 Python 库,用于访问标准的 实时流媒体 Twitter API ,可以检索与给定查询匹配的推文(例如“#DeleteFacebook”、“#Cambridge Analytica”)。我们用来检查数据集的术语/查询的完整列表可以在网上找到。根据 Twitter API 的条款和条件,我们收集推文是被允许的。

数据预处理

由于目标是分析人们对信息隐私的看法,我们决定用三种方式预处理我们的数据。首先,转发被删除,以避免分析完全重复。之后,我们试图识别和过滤由机器人生成的推文。我们的最后一步是将推文与其对应的世界区域链接起来。前面的步骤将在下面进一步解释。

僵尸检测

我们使用 Botometer [1]来检测和删除由机器人创建的推文。Botometer 使用机器学习来分析一千多个特征,包括推文的内容和情感,账户和朋友的元数据,转发/提及网络结构和发布行为,以生成一个从 0 到 1 的分数。较高的值表示被检查的帐户是 bot 的可能性较高。该工具在预测简单和复杂的机器人方面都达到了很高的准确率(94%)。

识别 Twitter 用户的居住国

我们使用 GeoNames API 在我们的数据集中识别 Twitter 用户居住的国家。在 Twitter 上,用户可以自我报告他们的城市或优先国家。然而,对地理位置的文本参考可能是模糊的。例如,全世界有 60 多个地方被命名为【巴黎】【2】。为了应对这一挑战,我们采用了 GeoNames API,这是一个地名词典合作项目,包含超过 1100 万个条目和世界各地各种语言的备用名称。该工具产生的结果准确率超过 80%[2]。

我们发现 81%的西班牙语用户和 79%的英语用户在他们的个人资料中填写了城市或国家字段。然而,GeoNames API 在一些情况下无法检测用户的位置,例如,当提供不准确的信息时(例如“行星地球”)..其他人都是从哪里来的、【火星】)。尽管如此,该工具还是能够识别出创建了 59%西班牙语推文和 60%英语推文的用户的位置。

创建了五个语言区域数据集来比较地理区域的信息隐私问题。西班牙的推特数据集分为两组:来自拉丁美洲和欧洲的用户写的推特。类似地,英语数据集被分为三组:来自(1)北美、(2)欧洲和(3)亚洲的用户写的推文。

文本挖掘:识别语义上下文的单词嵌入

单词嵌入是一种单词表示类型,其将术语的含义编码在向量中,使得相关单词在向量空间中更接近。分析与给定术语最接近的术语可以揭示其使用的语义上下文【3,4】。

为了实现跨语言和跨地区的比较,创建了一组单词嵌入。首先,我们为西班牙语和英语数据集(包含地理定位和非地理定位的 tweets)构建了单词嵌入。然后,我们为五个语言区域数据集中的每一个生成单词嵌入。

在创建单词嵌入时,我们考虑了不同的单词嵌入架构组合,包括 Word2vec/FastText、CBOW/Skipgram 以及不同数量的维度和时期。由于对于哪种单词嵌入评估更合适还没有达成共识,因此使用单词嵌入基准库通过 18 种内在意识评估方法对每个单词嵌入架构进行了评估。

手动编码&分析

我们系统地研究了语义环境中的信息隐私条款出现根据词嵌入。我们的调查集中在英语中的四个关键词:信息隐私用户、公司以及它们在西班牙语中相应的翻译:信息隐私常用、企业。对于每个嵌入,我们检索与四个关键字最接近的术语。使用余弦相似度来测量每个术语和关键词之间的接近度。例如,在英文单词 embedding 中,与关键字信息最接近的术语依次是信息数据细节个人(见图 2)。

图 2:西班牙语和英语单词嵌入中最接近“信息”和“隐私”的前 20 个术语。作者将西班牙语术语翻译成了英语。全部结果可在在线 |图片由作者提供

通过收集和分析这些隐私相关关键词的语义上下文,我们可以观察到收集到的推文中是否存在与信息隐私相关的术语。我们系统地对这些术语进行了开放编码。经过几次迭代,我们开发了一组类别来描述它们。最后,为了评估是否存在信息隐私问题,我们将这些类别与广泛接受的描述互联网用户信息隐私问题的框架(IUIPC)进行了对比。我们发现了一些类别、三个 IUIPC 维度和初始关键词之间的关系(见图 3)。

图 3:我们确定了几个类别,可以很容易地映射到互联网用户信息隐私问题(IUIPC)的三个维度:收集、意识和控制。通过这种方式,我们发现社交媒体内容可以揭示隐私问题的信息|作者图片

然后,我们评估了不同语言和世界地区在信息隐私问题上的差异。为此,我们使用卡方检验来评估语义上下文中的术语比例是否在单词嵌入中有显著差异。在所有这些测试中,我们根据 idák 应用 alpha 调整来说明多重比较。这种方法允许我们在执行多重假设测试时控制错误发现的概率。

我们的结果—关键见解

当前信息隐私问题的概念化可能需要扩展

IUIPC 是一个基于理论的模型,广泛用于研究互联网上的信息隐私问题。它包括三个构造: 采集 ,是指数据采集; 控制 其中涉及到对数据治理的担忧; 意识 指对组织信息隐私实践的认可。

我们的结果建议对 觉知 IUIPC 概念进行更精细的分类。例如,它可以包括用户可以知道的更具体的子主题,例如 隐私和安全术语 (例如,网络安全、保密性) 安全机制 (例如,凭证、加密),以及 隐私和安全风险 (例如,诈骗、疏导)。符合这些类别的术语的出现表明,它们已经是围绕隐私的公共在线对话的一部分。广义的隐私和安全术语、保护数据的机制以及潜在的数据风险之间的区别可能有助于进一步描述人们所拥有的知识种类。此外,对其中一些副主题的了解可能比其他的更有影响力。例如,了解风险和机制可能是更深层次隐私问题的标志,而了解广泛的隐私和安全术语可能不是。子主题之间的区别也可以指导用户、教育者和从业者努力提高隐私素养。

法规不仅仅是数据和法律专家的话题

法规 类别的存在凸显了其在信息隐私问题方面的重要性。法规是指旨在控制个人数据使用的法律或规则。这一类别从我们的开放编码中出现,通过它在关于数据泄露丑闻的公开帖子中的频繁出现,证实了它的相关性。这些法规不仅是数据和法律专家的话题,也似乎是围绕在线数据隐私的公共话语的一部分。

在强调信息隐私方面的语言和地区差异

说英语的人比说西班牙语的人更强调数据收集。

我们的分析显示,在网上自由表达隐私关键词时,说英语的人比说西班牙语的人更重视数据收集。这种差异可以引导研究人员和从业人员探索更适合特定人群的数据隐私活动的有效性。例如,关注收集的人群可能需要更多关于共享他们的信息的好处的信息。

北美的隐私问题不能推广到其他地区。

我们还观察到在认知上的显著地区差异。特别是,来自北美的数据显示对认知度的重视程度最低,而拉丁美洲的认知度最高。这一发现尤其重要,因为大多数关于信息隐私问题的研究都集中在美国。它警告我们不要(有时是含蓄的)认为北美的隐私问题可以推广到其他地区。研究结果提供了观察证据,证明有必要纳入更多不同的人群,以更好地理解围绕数据隐私的现象。这一发现也促使从业者使用不同的服务和隐私政策方法来解决其他地区的问题,例如拉丁美洲。例如,更关注公众意识的人群可能更容易接受那些在个人数据使用方面采取更透明沟通方式的公司。**

我们方法的局限性

和任何研究一样,我们的研究也有局限性。我们使用特定的标签和关键词,通过免费的标准流媒体 Twitter API 收集数据。因此,我们只能获得有限的关于丑闻的推特样本。我们使用 Botometer 来检测和删除可能由机器人创建的推文。这个工具只能分析 Twitter 公众账号;因此,在运行我们的分析时,它不能用于暂停的帐户或那些其推文受到保护的帐户。我们决定从我们的数据集中删除这些账户的推文,因为我们不能自信地声称是人类生成的。事实上,以前的研究表明,社交机器人很可能出现在这个群体中。此外,我们将调查重点放在四个英文关键词上:信息隐私用户公司以及它们相应的西班牙语翻译。虽然使用同义词会带来相似的语义上下文,但添加更多的概念可以加强结果。未来的工作可以探索其他关键词如亲密度消费者

字里行间

我们的论文使用另一种方法来研究大地理范围内的信息隐私问题。这种方法旨在从大规模社交媒体数据集中发现关于某个主题的知识,对于该主题,不存在地面事实。不幸的是,这样的基本事实不太可能存在,因为大规模、多国家、多语言的调查成本太高[5]。

我们仔细分析了语义上下文的一千多个术语,进行了开放编码以形成基于数据的分类,并将我们的分类与 IUIPC [6]进行了对比,IUIPC 是一种广为接受的信息隐私问题的理论概念化。

我们的论文讨论了我们的发现如何扩展当前信息隐私问题的概念化。最后,我们考察了在我们分析的地区,它们与个人数据使用法规的关系。

未来的工作可以更深入地挖掘观察到的差异,并研究潜在的原因。未来的研究可能会基于我们的工作,考虑更多的语言、地理位置或不同的信息隐私框架来检查隐私问题。使用我们的方法来比较更长时期的数据集,可以帮助确定隐私关键字的语义上下文是否随时间而变化。

有兴趣可以在 Twitter 上找我或者访问我的 网站 😃。

感谢

感谢 Claudia LópezIgnacio TampeAdam Geller 对本文提出的改进建议。

参考文献

[1]戴维斯,C. A .,瓦罗尔,o .,费拉拉,e .,弗拉米尼,a .,&门策尔,F. (2016,4 月)。 Botornot:一个评估社交机器人的系统。第 25 届国际万维网会议论文集(第 273–274 页)。

[2]a . Jackoway,h . Samet 和 j . Sankaranarayanan(2011 年 11 月)。使用 Twitter 识别现场新闻事件。在第三届 ACM SIGSPATIAL 基于位置的社交网络国际研讨会会议录(第 25-32 页)。

[3]f .冈萨雷斯、a .菲格罗亚、c .洛佩斯和 c .阿拉贡(2019 年 11 月)。推特上的信息隐私观点:一项跨语言研究。2019 年关于计算机支持的协同工作和社会计算的会议配套出版物(第 190-194 页)。

[4] Rho,E. H. R .,Mark,g .,& Mazmanian,M. (2018 年)。F 在线主持民间话语:跨政治视角的# metoo 文章评论中的语言行为美国计算机学会人机交互会议录2 (CSCW),1–28 页。

[5]李、姚;尤金妮亚·哈 Rim Rho 以及阿尔弗雷德·科布萨(2020)。文化差异对社交网站用户隐私决策的影响。行为&信息技术,1–23。

[6]纳尔什·k·马尔霍特拉;金成;和詹姆斯·阿加瓦尔(2004)。互联网用户的信息隐私担忧(IUIPC):结构、规模和因果模型。信息系统研究,第 15 卷,第 4 期,第 336-355 页。

用 SageMaker 模型注册中心注册和部署模型

原文:https://towardsdatascience.com/register-and-deploy-models-with-sagemaker-model-registry-5af42d678912

SageMaker 模型注册中心介绍

图片来自杰瑞米·贝赞格拍摄的 Unsplash

在 ML 生命周期中管理模型的不同版本是很重要的。当您训练各种模型时,您将需要在各种注册表中对它们进行分类。 SageMaker 模型注册中心帮助您管理不同的模型版本及其元数据。

使用 Model Registry,您可以创建包含模型不同版本的模型包组。您可以为您可能处理的特定模型集创建各种模型包组。在本文中,我们将探索一个训练样本 SageMaker XGBoost 回归模型的例子,然后为模型注册中心创建一个模型包组。使用这个设置,我们将演示如何直接从模型注册中心将模型部署到一个 SageMaker 实时端点

注意:对于刚接触 AWS 的人来说,如果你想继续学习,请确保在下面的 链接 中注册账户。本文将假设一个新手对 AWS 和 SageMaker 有中级的了解。

目录

  1. 设置和模型培训
  2. 模型注册和部署
  3. 其他资源和结论

1.设置和模型培训

对于这个例子,我们将在 SageMaker 工作室工作,这是 SageMaker 提供的 IDE。Studio 将是我们超越经典笔记本实例的选择,因为它为我们将要使用的工具提供 UI 支持,例如模型注册。要全面了解 SageMaker Studio 的设置,请遵循此文档

在 Studio 中,我们将使用一个基本的数据科学内核和一个简单的计算实例(ml.c5.large)作为我们的笔记本实例。启动笔记本后,我们将快速进行一些设置步骤。首先,我们有与 SageMaker 一起工作的 AWS SDK Python 客户端。利用这些,我们将协调从培训到模型注册再到部署的整个过程。

设置

我们现在可以检索公共的回归鲍鱼数据集,并将其上传到我们的工作室环境。

访问鲍鱼数据集

现在我们已经设置好了,我们将继续进行模型训练。对于这篇文章,我们不会关注训练的深度,但如果你想要一篇关于在 SageMaker 上训练 XGBoost 算法的文章,请查看我的另一篇文章这里

我们可以用鲍鱼数据集设置我们的训练输入,并为我们正在检索的 SageMaker XGBoost 算法配置我们的超参数

培训设置

这里的关键部分是我们检索到的图像。这个图像将在我们的模型注册中使用,以正确地对我们训练过的模型进行分类。带有我们的模型数据和图像的元数据对于稍后的端点创建至关重要。使用它,我们现在可以训练 XGBoost 估计器,并从训练作业中检索模型数据。

从培训作业中检索模型数据

2.模型注册和部署

我们使用模型注册中心的第一步是创建一个模型组。这个模型组包含一组版本化的模型,我们将为我们的案例处理这些模型。我们可以使用之前用 SageMaker 定义的 boto3 客户机创建一个模型组。

模型组创建

在这个模型组中,我们还没有定义任何特定于我们模型的信息。这是我们传入正在处理的 XGBoost 图像和模型数据的地方。这有助于为部署提供必要的元数据。

将图像和模型数据传递给模型组

在这个调用中,我们还可以指定输入数据的类型(CSV、JSON ),以便我们的端点知道在创建时会发生什么。我们可以通过list _ Model _ packagesAPI 调用看到这个模型组的资源 arn 和版本。

ARN 车型套装

使用describe _ model _ packageAPI 调用,我们可以看到我们指定的所有细节。

模型包详细信息

现在,我们可以使用检索到的 model_package arn 来批准这个模型。我们可以通过更新模型包状态来做到这一点。

批准模型包

令人惊叹的是,您现在还应该能够在 Studio 的 Model Registry 选项卡中看到这个模型包。如果您在 Studio 的左侧面板中选择了最底部的图标,您应该能够选择 Model Registry 来查看您的模型包组。

模型注册用户界面(作者截图)

如果我们仔细观察,可以看到我们的特定组和我们刚刚批准的版本。

已批准的包(作者截图)

通过批准模型包,我们现在已经准备好创建端点了。这通过一系列三个步骤来实现: SageMaker 模型创建、端点配置创建和端点创建。我们可以通过类似地使用 boto3 客户端来完成所有这些步骤。

我们首先创建一个 SageMaker 模型实体,在这里我们可以传递我们的 model_package arn,而不必指定任何关于模型数据或所用图像的细节。

SageMaker 模型创建

使用这个 SageMaker 模型,我们可以创建一个端点配置,它包含关于实例类型和端点后面的计数的细节。

端点配置创建

使用这个端点配置,我们可以将它输入到我们的端点中,并监控它的创建,直到它成功投入使用。

端点创建

我们还可以看到这个端点在我们的 Model Registry UI 中成功创建。

从模型包成功创建端点(作者截图)

一旦创建了您的端点,我们就可以用一个示例负载来调用它,并查看调用。

祈祷

结果(作者截图)

3.其他资源和结论

额外资源

https://github.com/RamVegiraju/SM-Model-Registry

关于示例的完整代码,请查看上面的资源库。我在下面附上了额外的例子和资源。

结论

SageMaker Model Registry 是一个极其可扩展和强大的工具。对于这个用例,我们只经历了一次训练迭代和一个模型。但是,当你有一个模型,你可能是针对多个数据集进行训练,它的真正威力会多次显现出来。使用 boto3 客户端与 Model Registry 进行交互,可以非常容易地对不同的模型集合进行分组和编目。

我希望这篇文章是关于模型注册中心、SageMaker 和 MLOps 的好入门。在以后的文章中,我们将探讨如何使用 SageMaker 管道将 SageMaker 模型注册中心整合到整个 MLOps 生命周期中。

如果你喜欢这篇文章,请在LinkedIn上联系我,订阅我的媒体 简讯 。如果你是新手,使用我的 会员推荐 报名。

TensorFlow 中使用顺序 API 和函数 API 的回归

原文:https://towardsdatascience.com/regression-in-tensorflow-using-both-sequential-and-function-apis-314e74b537ca

玛利亚·诺沃塞列茨卡娅在 Unsplash 上的照片

演示几种不同类型的模型结构

Tensorflow 可以说是深度学习和神经网络领域中最受欢迎的包。我以前写过一些不同的教程,关于规则密集神经网络、CNN 结构和 RNNs。但是我在 Tensorflow 上的所有教程都是关于分类问题的。有时是图像分类,有时是自然语言分类。

在本文中,我想解决一个回归问题,并演示一些顺序 API 和函数 API 的模型。

我已经清理了所有的数据。因为这篇文章将完全集中于回归问题中的张量流。因此,我不想在这里展示数据清理操作。

可从以下链接下载干净并可用于模型数据集中的产品:

https://github.com/rashida048/Tensorflow/blob/main/auto_price.csv

原始数据集取自 UCI 机器学习知识库。

这里说的是用户权限

因此,我在这里导入数据集:

import pandas as pd
df = pd.read_csv("auto_price.csv")

我已经在清理中处理了一些空值。但是,如果有更多的空值,我会直接删除它们:

df = df.dropna()

在进入张量流模型之前,去掉所有的空值是很重要的,因为数据集中的空值会给你带来错误。

在该数据集中,“价格”列将被用作输出变量或标签。其余的特征将被用作训练特征。

X = df.drop(columns ="price")
y = df['price']

接下来,我将执行另一个常见任务。也就是分割数据集用于训练、验证和测试。

from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScalerX_train_full, X_test, y_train_full, y_test = train_test_split(X, y)
X_train, X_valid, y_train, y_valid = train_test_split(X_train_full, y_train_full)

与分类不同,回归问题不能用精度来评价。在这里,我将使用均方根误差作为评估指标。

import tensorflow as tf
from tensorflow.keras import Sequential
from tensorflow.keras.optimizers import Adam
from tensorflow.keras import backend as K
from tensorflow.keras.layers import Densedef rmse(y_true, y_pred):
    return K.sqrt(K.mean(K.square(y_pred - y_true)))

模型开发

在 TensorFlow 中,模型开发是有趣的部分。对于大多数现实世界的问题,您将为一个项目尝试多个 TensorFlow 模型。在我开始写作之前,我尝试了这个项目的八个模型。我在这里分享其中的一些。

我将从使用顺序 API 的常规 DNN 模型开始,然后展示一些使用函数 API 的模型。所以,你会看到不同之处。

我试过四款 DNN 车型,这款给我的效果最好:

model4= Sequential([
    tf.keras.layers.Input(shape = X_train.shape[1:]),
    Dense(300, activation='tanh'),
    Dense(300, activation='tanh'),
    Dense(300, activation='tanh'),
    Dense(300, activation='tanh'),
    Dense(300, activation='tanh'),
    Dense(300, activation='tanh'),
    Dense(1)
])

正如你所看到的,我们有六个隐藏的致密层,有 300 个神经元和' tanh '激活功能。请随意尝试一些其他的激活功能、不同的层数和不同数量的神经元。我将在本文中保持激活函数的一致性,并对模型进行比较。

因为这是一个回归问题,对于给定的输入,这里只有一个输出。所以,最后的输出层有一个神经元。输入形状和特征的数量。

让我们再次使用“rmse”作为损失、adam optimizer 和评估指标作为“rmse”来编译这个模型。

model4.compile(
    loss=rmse,
    optimizer=Adam(),
    metrics=[rmse]
)

最后运行模型 20000 个时期:

history4 = model4.fit(X_train, y_train, epochs=20000, validation_data=(X_valid, y_valid))

输出:

Epoch 1/20000
4/4 [==============================] - 0s 40ms/step - loss: 14714.9736 - rmse: 15355.5986 - val_loss: 17081.0312 - val_rmse: 15776.2559
Epoch 2/20000
4/4 [==============================] - 0s 9ms/step - loss: 14792.2432 - rmse: 15059.4141 - val_loss: 17076.2695 - val_rmse: 15771.4033
Epoch 3/20000
4/4 [==============================] - 0s 9ms/step - loss: 14842.5195 - rmse: 15015.9941 - val_loss: 17074.5098 - val_rmse: 15769.6104
...
...
Epoch 19997/20000
4/4 [==============================] - 0s 8ms/step - loss: 7850.9614 - rmse: 8218.5664 - val_loss: 9565.8027 - val_rmse: 8386.0020
Epoch 19998/20000
4/4 [==============================] - 0s 7ms/step - loss: 7826.3198 - rmse: 7867.3975 - val_loss: 9565.8008 - val_rmse: 8386.0020
Epoch 19999/20000
4/4 [==============================] - 0s 7ms/step - loss: 7857.6772 - rmse: 7917.7451 - val_loss: 9565.8135 - val_rmse: 8386.0088
Epoch 20000/20000
4/4 [==============================] - 0s 8ms/step - loss: 7846.6616 - rmse: 8078.2676 - val_loss: 9565.8145 - val_rmse: 8386.0088

正如上面的输出所示,训练损失和验证损失分别从 14714 和 17081 开始。在 20000 个时期之后,训练和验证损失是 7846 和 9565。

在训练中使用了训练和验证数据。看看它在完全看不见的数据上的表现会很好。我将测试数据以评估 TensorFlow 模型的性能:

model4.evaluate(X_test, y_test)

输出:

2/2 [==============================] - 0s 2ms/step - loss: 7129.9590 - rmse: 7222.2402

Out[209]:

[7129.958984375, 7222.240234375]

在测试中,数据丢失是 7129,rmse 是 7222。看起来损失和“rmse”比验证损失和“rmse”要小得多。还不错!

让我们尝试改进这些结果 TensorFlow 提供了功能 API 来开发更复杂的模型。

使用功能 API 开发 Tensorflow 模型

使用函数式 API 构建宽深度神经网络的一种方法。在这种方法中,部分或全部输入层通过隐藏层中的所有变换到达输出层(深度路径)。另外,输入层也可以直接到输出层(宽路径)。

下面是一个使用函数式 API 的模型。你看到模型后,我会向模型解释:

input = tf.keras.layers.Input(shape = X_train.shape[1:])
hidden1 = tf.keras.layers.Dense(300, activation='relu')(input)
hidden2 = tf.keras.layers.Dense(300, activation='relu')(hidden1)
concat = keras.layers.Concatenate()([input, hidden2])
output = keras.layers.Dense(1)(concat)
model51 = keras.models.Model(inputs=[input], outputs=[output])

区别非常明显。每一层都有一个名称。在输入层之后,一个隐藏层照常被创建。它被命名为 hidden1。一旦创建了 hidden1,它就作为一个传递输入层的函数。同样,hidden2 也被创建,它也将作为一个函数。hidden1 的输出传递给 hidden2。

这个模型只有两个隐藏层。下一层是 concatenate()层,它也将充当添加了 hidden2 层的输入层和输出层的函数。顾名思义,这一层连接了输入层和 hidden2 层的输出。最后,输出层被创建,和其他层一样,它也被用作一个函数。“concat”的输出被传递到输出层。因此,在输出层,我们将 hidden1 和 hidden2 层的原始输入和转换输入连接在一起。

在最后一行,创建了一个 Keras 模型,其中明确指定了输入和输出。

编译和训练过程与之前相同。这一次,训练只运行了 8000 个周期,而不是 20000 个周期。因为带函数 API 的 TensorFlow 模型应该收敛得更快。

model51.compile(
    loss=rmse,
    optimizer=Adam(),
    metrics=[rmse]
)history5 = model51.fit(X_train, y_train, epochs=8000, validation_data=(X_valid, y_valid))

输出:

Epoch 1/8000
4/4 [==============================] - 1s 64ms/step - loss: 13258.9883 - rmse: 13171.9844 - val_loss: 14222.4844 - val_rmse: 12949.8066
Epoch 2/8000
4/4 [==============================] - 0s 8ms/step - loss: 11380.7041 - rmse: 11492.8750 - val_loss: 12479.9932 - val_rmse: 11245.1924
Epoch 3/8000
4/4 [==============================] - 0s 10ms/step - loss: 9632.6230 - rmse: 10223.1465 - val_loss: 10826.7109 - val_rmse: 9650.0918...
...
Epoch 7998/8000
4/4 [==============================] - 0s 7ms/step - loss: 619.7231 - rmse: 626.7860 - val_loss: 3652.3462 - val_rmse: 2738.8286
Epoch 7999/8000
4/4 [==============================] - 0s 7ms/step - loss: 661.9867 - rmse: 680.8888 - val_loss: 3569.5647 - val_rmse: 2896.3765
Epoch 8000/8000
4/4 [==============================] - 0s 6ms/step - loss: 607.5422 - rmse: 579.1271 - val_loss: 3477.7332 - val_rmse: 2928.8345

经过 8000 个周期后,训练数据的“rmse”下降到 579,验证数据的“RMSE”下降到 2928。与 8078 和 8386 型号 4 相比,它要好得多。

训练和验证“rmse”值非常不同。很明显过度配合了。完全看不见的数据的“rmse”是多少?以下是测试数据的检查:

model51.evaluate(X_test, y_test)

输出:

[2501.0322265625, 2517.703125]

它给出的“rmse”为 2517,更接近验证“rmse”。

下图显示了训练损失和验证损失如何随时期变化:

plt.plot(history5.history['loss'])
plt.plot(history5.history['val_loss'])
plt.legend()

model51 的结构是功能 API 上的一个非常简单的模型。你可以设计比这复杂得多的网络。下一个模型是 model5,它有点复杂,只是为了演示如何使用它。

但是如果一个简单的模型可以完成这项工作,我就不喜欢复杂的模型。模型越简单越好。但如果有必要,我们可能需要在一些复杂的项目中使用更复杂的模型来获得更好的结果。所以,这里我有一个演示:

input = tf.keras.layers.Input(shape = X_train.shape[1:])
hidden1 = tf.keras.layers.Dense(300, activation='relu')(input)
hidden2 = tf.keras.layers.Dense(300, activation='relu')(hidden1)
hidden3 = tf.keras.layers.Dense(300, activation='relu')(hidden2)
hidden4 = keras.layers.Concatenate()([input, hidden3])
hidden5 = tf.keras.layers.Dense(300, activation='relu')(hidden4)
concat = keras.layers.Concatenate()([input, hidden5])
output = keras.layers.Dense(1)(concat)
model5 = keras.models.Model(inputs=[input], outputs=[output])

通常,第一层是输入层

第二层是 hidden1 层,输入层在此处传递。

hidden1 层的输出被传递到 hidden2 层。

hidden2 层的输出被传递到 hidden3 层。

然后,我们将输入层与 hidden3 层的输出连接起来,并将其命名为 hidden4。

然后,hidden4 层的输出被传递到 hidden5。

然后我们再一次把输入和隐藏的输出连接起来。

最后,有一个输出层和指定输入和输出的模型开发。

接下来的步骤与之前相同:

model5.compile(
    loss=rmse,
    optimizer=Adam(),
    metrics=[rmse]
)
history5 = model5.fit(X_train, y_train, epochs=8000, validation_data=(X_valid, y_valid))

输出:

Epoch 1/20000
4/4 [==============================] - 1s 50ms/step - loss: 13927.1045 - rmse: 13915.9434 - val_loss: 13678.9092 - val_rmse: 12434.3320
Epoch 2/20000
4/4 [==============================] - 0s 8ms/step - loss: 10047.6201 - rmse: 9563.8525 - val_loss: 9203.7637 - val_rmse: 8289.6582
Epoch 3/20000
4/4 [==============================] - 0s 7ms/step - loss: 8499.3525 - rmse: 8310.4893 - val_loss: 9033.5527 - val_rmse: 8297.1348
...
...
Epoch 19998/20000
4/4 [==============================] - 0s 9ms/step - loss: 287.1166 - rmse: 261.8001 - val_loss: 3024.3147 - val_rmse: 2331.6790
Epoch 19999/20000
4/4 [==============================] - 0s 9ms/step - loss: 227.0752 - rmse: 238.3496 - val_loss: 2957.6494 - val_rmse: 2263.0854
Epoch 20000/20000
4/4 [==============================] - 0s 9ms/step - loss: 187.4945 - rmse: 180.5027 - val_loss: 3016.0168 - val_rmse: 2355.3120

正如您可以看到的,训练数据和验证数据的“rmse”都在这个模型中得到了进一步改善。

这一次,训练和验证数据集的“rmse”值分别为 180 和 2355。显然,存在过度拟合。

用测试数据评估:

model5.evaluate(X_test, y_test)

输出:

[2499.998291015625, 2547.47216796875]

“rmse”是 2547,超过了看起来非常正常的验证“rmse”。但是“rmse”非常接近模型 51 的评估。所以,我们实际上不能说这个模型很好地改善了结果。

复杂和更大的模型并不总是意味着更好的结果。我们可以尝试不同的型号。以前在我身上发生过很多。我先试着做一个简单的模型,然后做了很多越来越复杂的模型。但最终还是回到了更简单的模型。

可以只变换一部分输入要素,而不是通过变换使用所有输入要素,并且可以将一部分输入要素直接添加到输出图层。

对于此模型,前 15 个特征保存为输入 1,后 18 个特征保存为输入 2。由于要素总数为 26,因此有些要素重叠。因此,一些特性将同时存在于输入 1 和输入 2 中。

模型如下:

input1 = tf.keras.layers.Input(shape = [15])
input2 = tf.keras.layers.Input(shape = [18])
hidden1 = tf.keras.layers.Dense(300, activation='relu')(input2)
hidden2 = tf.keras.layers.Dense(300, activation='relu')(hidden1)
hidden3 = tf.keras.layers.Dense(300, activation='relu')(hidden2)
hidden4 = keras.layers.Concatenate()([input2, hidden3])
hidden5 = tf.keras.layers.Dense(300, activation='relu')(hidden4)
concat = keras.layers.Concatenate()([input1, hidden5])
output = keras.layers.Dense(1)(concat)
model6 = keras.models.Model(inputs=[input1, input2], outputs=[output])

模型结构几乎与之前的模型一样。唯一的区别是,input2 通过隐藏层中的转换传递。Input1 与最后一个隐藏层(hidden5)连接在一起,就在输出之前。因此,input1 没有完成隐藏层中的所有转换。

所以,我们应该分离特征:

X_train1, X_train2 = X_train.iloc[:, :15], X_train.iloc[:, 7:]
X_valid1, X_valid2 = X_valid.iloc[:, :15], X_valid.iloc[:, 7:]
X_test1, X_test2 = X_test.iloc[:, :15], X_test.iloc[:, 7:]

现在让我们编译并使输入和输出数据适合模型。记住,这次我们必须将 input1 和 input2 都作为输入进行传递。

model6.compile(
    loss=rmse,
    optimizer=Adam(),
    metrics=[rmse]
)
history6 = model6.fit((X_train1, X_train2), y_train, epochs=20000, validation_data=((X_valid1, X_valid2), y_valid))

输出:

Epoch 1/20000
4/4 [==============================] - 1s 53ms/step - loss: 14018.6748 - rmse: 14139.8809 - val_loss: 13892.1895 - val_rmse: 12647.2930
Epoch 2/20000
4/4 [==============================] - 0s 6ms/step - loss: 10192.4922 - rmse: 9930.0068 - val_loss: 9372.5049 - val_rmse: 8391.0254
Epoch 3/20000
4/4 [==============================] - 0s 8ms/step - loss: 8807.8857 - rmse: 8779.3643 - val_loss: 9259.1514 - val_rmse: 8631.5068
...
...
Epoch 19998/20000
4/4 [==============================] - 0s 8ms/step - loss: 495.4005 - rmse: 509.9180 - val_loss: 3193.6880 - val_rmse: 3008.2153
Epoch 19999/20000
4/4 [==============================] - 0s 8ms/step - loss: 405.2184 - rmse: 446.1824 - val_loss: 3340.9062 - val_rmse: 3053.0967
Epoch 20000/20000
4/4 [==============================] - 0s 8ms/step - loss: 405.8342 - rmse: 387.4508 - val_loss: 3277.2720 - val_rmse: 3060.6477

最后,训练和验证数据的“rmse”值变成了 387 和 3080。

以下是评价:

model6.evaluate([X_test1, X_test2], y_test)

输出:

[2547.850830078125, 2735.259033203125]

评估的“rmse”也是 2735。看起来这种方法并没有更好。事实上,它比以前的型号做得更差。

结论

我想在 Tensorflow 中使用顺序 API 和函数 API 演示几种不同风格的回归模型,并在这个特定项目中对它们进行比较。但这些结果不能一概而论。不同的模型架构更适合不同的项目。我们需要为每个项目尝试不同的模式,看看哪种模式效果最好。所以,有很多不同的事情可以尝试。请随意尝试更多的模型,看看是否可以改善结果。

请随时在 Twitter脸书页面上关注我,并查看我的 YouTube 频道

更多阅读

https://pub.towardsai.net/data-analysis-91a38207c92b

回归树,循序渐进

原文:https://towardsdatascience.com/regression-trees-step-by-step-4de996e27f85

学习如何构建回归树,了解它们的主要优点和缺点

【免责声明:此帖子包含一些我的 Udemy 课程的附属链接】

决策树已经存在了几十年,在世界各地的几个机器学习项目中都有使用。虽然它们很可能不会在大多数问题上达到最先进的性能,但它们仍然是最值得测试的可解释算法之一。此外,它们是极端梯度提升、随机森林或贝叶斯回归树等著名算法的支柱,了解它们是数据科学家和机器学习工程师的必备知识。

与普通的线性或逻辑回归(不对特征应用二次变换)相反,决策树能够非线性地分割我们的数据,在我们的特征和目标之间建立有趣的关系,这对于其他更简单的算法来说似乎是不可能的。

当然,这不是没有代价的。由于它们的非线性本质,决策树通常是高方差模型,可以很好地适应大多数训练数据集,但在测试数据上表现很差。

在这篇文章中,我们将探索回归树,两种最常见的决策树之一(另一种是分类)。我们将在一个玩具数据集上做几个实验,并走过以下路径:

  • 首先,我们将绘制数据集,了解房屋大小和价格之间的关系。
  • 然后,我们将了解线性回归在我们的数据集中表现不佳的原因。
  • 我们将拟合一个回归树,并可视化它对数据的(几乎)完美拟合。
  • 我们将详细介绍回归树以及它们的超参数如何影响训练。

在整篇文章中,我将使用 R 编程语言来支持每一个解释,展示一些代码来解释我们将探索的每个细节——让我们开始吧!

设置和检查数据集

在这篇文章中,我们将使用一个房价玩具数据集。点击这个链接可以找到 excel 源文件。将这个数据集加载到 R 中非常容易:

library(readxl)
library(ggplot2)
library(dplyr)
library(rpart)
library(rpart.plot)house_prices <- read_excel('./data/house_prices.xlsx')

在上面的代码片段中,我还添加了我们在帖子后面需要的库:

  • readxl加载我们的 excel 文件。
  • ggplot2为我们的地块。
  • dplyr执行一些数据转换。
  • rpart构建我们的决策树。
  • rpart.plot绘制我们的决策树。

我们的数据包含来自一个虚构地方的 57 个房屋实例,我们有两个相关变量—房屋的价格及其面积(平方英尺):

house_price_plot <- ggplot(
  data = house_prices,
  aes(x = area, y = price)
) + geom_point()house_price_plot

房价数据集-按作者分类的图像

自然,房子的价格和面积是线性关系。这是意料之中的,因为我们知道(同一位置的)大房子往往会更贵。

但请注意一些奇怪的事情,81 到 87 平方英尺的房子比 77 到 81 平方英尺的房子便宜——这将打破我们的线性回归。至少对于这些点来说,我们的回归线将无法捕捉到这种关系,尽管该模型将具有质量,但它将无法理解这一小簇房屋打破了整体趋势。

当我们发现这种类型的问题时,我们通常会试图寻找一些我们没有测量的影响来源——例如,房子里浴室的数量可以解释这种下降。在这篇文章中,让我们假设我们无法访问更多关于房子的数据。

所以……我们的假设是在这个图上拟合一条回归线肯定会建立一些次优的东西(同样,忽略我们能够做任何特征变换,因为二次特征可能有帮助)——接下来让我们证明这一点。

拟合线性回归

如果我们用 R 对数据拟合一条回归线:

linear.model <- lm(price ~ area, data=house_prices)

虽然我们实现了大约 0.8998 的 R 平方,但我们的模型仍然存在一些偏差来源。我们是怎么知道的?让我们将我们的预测与真实目标进行对比。

simulation_prices <- data.frame(
  area = seq(min(house_prices$area), 
            max(house_prices$area))
)# Predicting house price based on our linear.model
simulation_prices$predicted_house_price <- predict(
  linear.model,
  simulation_prices
)# Plotting Houses Prices + Predictions
(
  house_price_plot
  +
  geom_line(data=simulation_prices, aes(x=area, y=predicted_house_price), color='red')
)

房价数据集与回归线-作者图片

红色的是我们预测的回归线。虽然我们的模型很好地捕捉了数据的趋势,但它超越了两组房屋:

  • 面积不超过 40 平方米的房屋。脚。
  • 面积在 81 至 87 平方米之间的房屋。脚:

房价数据集与回归线-作者图片

正如我们已经讨论过的,有几种方法可以解决这种偏见,即:

  • 1)构建二次特征以更好地捕捉关系-这些特征将能够将我们的回归线“弯曲”成曲线模型。
  • 2)使用能够解释这些房屋为何打破整体趋势的其他特征。
  • 3)使用其他类型的模型,更好地捕捉这些类型的复杂关系。

我们要走第三条路线!

回归树是基本的非线性模型之一,能够捕捉特征和目标之间的复杂关系-让我们从拟合一个开始,看看它的性能,然后讨论它们为什么有用以及如何从头构建一个。

拟合回归树

决策树是一种算法,能够捕捉我们看到的面积和房价之间关系的下降。有了 1 个特征,决策树(当我们预测连续变量时称为回归树)将构建类似于阶跃函数的东西,如下所示。我将从拟合决策树开始,只是为了让您对我们产生的输出感到好奇。

d.tree <- rpart(price ~ area,
             data = house_prices, method = 'anova',
             control = list(maxdepth=5,
                            minsplit=2, minbucket=2,
                            cp = 0.0001))

暂时忽略rpart的那些参数——我将在接下来详述它们。在上面的函数中,我们使用递归分区库rpart将决策树拟合到我们的数据中——让我们看看拟合生成的“线”:

房价数据集与决策树线-作者图片

看看这和我们的回归线有多不同?我们如何能够更好地发现数据中的奇怪模式?

两个问题展开了:

  • 为什么决策树更善于捕捉这些类型的关系?
  • 它们只有在能够捕捉目标和特征之间的复杂交互时才有优势,还是有缺点?

让我们看看!

回归树是如何工作的?—第一次拆分

回归树基本上按照一定的标准分割数据,直到它们根据它们的超参数集找到同质组。

我知道这听起来令人困惑,但是让我们详细说明一下——你会发现这实际上是一个非常简单的概念。

回归树的核心是只能预测每次分割后的平均值。想象一下,我会让你把我们的数据分成两部分——比如关于area=68的数据:

house_price_plot + geom_vline(xintercept=68, color = "red")

房价数据集与随机分割-作者图片

假设我们最好的猜测是用我们线两边的平均值对每栋新房子进行分类。每一栋面积小于 68 平方米的新房子。ft,我们说它的价格是 115.844 €:

mean_value = house_prices %>%
  filter(area < 68) %>%
  summarize(mean_price = mean(price))(
  house_price_plot 
  + 
  geom_vline(xintercept=68, color = "red")
  + 
  geom_segment(aes(x=30, xend=68, y=mean_value[[1]], yend=mean_value[[1]], color='orange'))
  +
  theme(legend.position="none")
)

房价数据集与平均预测值-作者图片

如果这是我们的猜测,我们清楚地看到,由于低于 68 平方英尺的房屋价格的可变性,我们最终犯了很多错误。制成范围从 100k 到 125k€。

我们如何量化我们的简单模型所犯的错误?我们只是计算每个点与我们的“最佳猜测”的差异。以第一栋房子为例:

房价数据集与第一套房子的平均预测误差-图片由作者提供

黑线表示我们的错误——我们高估了这栋房子 16k€的价格。如果你是房子的买家,你会非常生气,因为你会支付高于房子公允价值的价格。

但是..还有其他一些情况,你(作为买家)会非常高兴,因为我们也低于一些价格,比如这栋房子:

房价数据集与 17 号房屋的平均预测误差-图片由作者提供

不错的交易!

好吧,很明显我们的模型还不够好——但它正在某个地方开始。在右边,对于大于或等于 68 平方英尺的房屋,如果我们用右边的平均值分类,也会发生同样的问题:

mean_value_right_split = house_prices %>%
  filter(area >= 68) %>%
  summarize(mean_price = mean(price))(
  house_price_plot 
  + 
    geom_vline(xintercept=68, color = "red")
  + 
    geom_segment(aes(x=68, xend=100, y=mean_value_right_split[[1]], yend=mean_value_right_split[[1]], color='orange'))
  +
    theme(legend.position="none")
)

超过 68 平方英尺的房屋的房价数据集与平均预测。—作者图片

你知道什么最酷吗?这实际上可以转化为一个真实的决策树!

如果我用根-分支格式来显示我们的分离,而不是用二维图来显示呢?(为了模仿 R 绘制决策树的方式,我将绘制一个颠倒的决策树)

第一次拆分的树形格式,区域< 68 — Image by Author

This is our decision tree with a 单次拆分

虽然单个分裂树总是无法构建一个好的模型(除非你在目标和特征之间有一个完美的阶梯形状关系,这在现实世界的数据集上是不寻常的),我们肯定需要在我们的数据集上进行更多的分裂。

即便如此,我们也要从某个地方开始!我们应该如何选择第一个分割来开始我们的树?

让我们回到房子 1 的例子,在这里我们看到了房子的预测价格和实际价值之间的差异:

房价数据集与第一套房子的平均预测误差-图片由作者提供

每个点将产生一条黑线——这条线代表该特定示例的误差(又名残差)

如果我们将模型生成的所有“黑线”相加,我们将得到一个误差指标——从视觉上看,我们希望我们的黑线尽可能小。如果发生这种情况,您的点接近预测值(水平红线)。

让我们在house_prices数据框架中计算每个具体示例的误差,假设我们使用上面的分割:

house_prices['predicted_price'] <- ifelse(
  house_prices['area'] < 68,
  115844,
  136215
)# Calculate Error
house_prices['error'] <- (
  house_prices['price']-house_prices['predicted_price']
)

看看我们的house_prices数据框架:

房价与误差栏-作者图片

error列表示我们的预测predicted_price和房子的price之间的差异。由于有些误差是负的,有些是正的,我们需要将误差转换成可比较的东西。一些常见的策略包括获得误差的绝对值或平方值——为了便于解释,我们将使用绝对值:

house_prices['error'] <- (
  abs(house_prices['error'])
)

这样,我们就可以产生一个理想的度量标准来理解我们模型的质量,即平均误差:

mean(house_prices['error'][[1]])

目前,我们还差 6428 € 左右的房价。我们可以把这个值和分割area < 68 or area >= 68联系起来。

如果我们可以将误差指标与分割相关联,我们现在能做什么?我们可以找到使这个平均误差最小的分裂!

这正是决策树在选择第一次拆分时的机制——接下来让我们看看如何优化我们的第一次拆分。

回归树是如何工作的?—优化第一次分割

让我们想象一下,我们遍历了area变量上的每一个可能的分裂,并具体检查了那个分裂的平均误差。请记住,对于每个不同的分割值,我们预测的是我们构建的红线每一侧的price的平均值,例如,50 上的分割看起来像这样:

obtain_split_plot <- function(base_plot, split_value) {

  mean_value_right_split = house_prices %>%
    filter(area >= split_value) %>%
    summarize(mean_price = mean(price))

  mean_value_left_split = house_prices %>%
    filter(area < split_value) %>%
    summarize(mean_price = mean(price))

  print(mean_value_left_split)
  print(mean_value_right_split)

  (
    base_plot 
    + 
      geom_vline(xintercept=split_value, color = "red")
    + 
      geom_segment(aes(x=30, xend=split_value, y=mean_value_left_split[[1]], yend=mean_value_left_split[[1]]), linetype ='dashed')
    + 
      geom_segment(aes(x=split_value, xend=90, y=mean_value_right_split[[1]], yend=mean_value_right_split[[1]]), linetype ='dashed')    +
      theme(legend.position="none")
  )

}obtain_split_plot(house_price_plot, 50)

虚线表示左右两侧的预测。如果我们将其转换为树形格式:

任何不同的分割都会产生不同的误差值。一个想法是绘制每个可能分割的平均误差——让我们使用 R 函数:

produce_error_split <- function(house_data) {

  min_area <- min(house_data$area)+1
  max_area <- max(house_data$area)-1

  vector_areas <- seq(min_area, max_area)

  error_summary <- data.frame(
    split = vector_areas
  )

  error_values <- c()

  for (split_value in vector_areas) {

    mean_value_right_split = house_data %>%
      filter(area >= split_value) %>%
      summarize(mean_price = mean(price))

    mean_value_left_split = house_data %>%
      filter(area < split_value) %>%
      summarize(mean_price = mean(price))

    predicted_price <- ifelse(
      house_data$area < split_value,
      mean_value_left_split[[1]],
      mean_value_right_split[[1]]
    )error <- abs(
      house_data['price']-predicted_price
    )

    error_values <- c(error_values, mean(error[[1]]))
  }

  error_summary$mean_absolute_error <- error_values

  return (error_summary)
}error_summary <- produce_error_split(house_prices)

上面的函数为我们的area的每次分割产生平均绝对误差。这将产生类似于“成本函数”的东西:

ggplot(
  data = error_summary,
  aes(x = split, y = mean_absolute_error)
) + geom_line()

每次分割的平均绝对误差—按作者分类的图像

那有多酷?由于我们与目标具有“几乎”线性关系,因此成本函数将近似于由线性回归产生的函数。根据上面的图,产生最小误差的分割是什么?

55 到 59 之间的区域!如果我们以二维和树形格式查看其中一个拆分:

最佳首次分割-2D 格式-作者图片

最佳第一分割树格式—作者图片

我们已经解决了我们的第一次分裂,因为这是一个最小化我们的错误。

但是,谁说我们需要在一次拆分中停止呢?

技术说明:虽然我将误差显示为最小化问题(最小化绝对误差),但 rpart 通过每次分割使增益最大化,这与我上面显示的图相反。不会改变我们所看到的一切背后的推理,但你知道这一点以避免混淆是很酷的。

回归树是如何工作的?—进一步分裂我们的树

现在我们已经有了第一次分裂,如果我们开始进一步分裂我们的每一方呢?例如,让我放大我们的数据集—仅子集化大于或等于 57 平方英尺的区域。英尺:

right_split <- house_prices %>% filter(area >= 57)# Right Splithouse_price_plot_right_split <- ggplot(
  data = right_split,
  aes(x = area, y = price)
) + geom_point()

超过 57 平方米的房子。制成—作者图片

我们现在正致力于我们拆分的右侧:

决策树,第一次拆分-作者图片

如果我们对这个数据子集进行同样的推理,试图找到最佳分割,使我们的平均值和每个点之间的误差最小化,会怎么样?

error_summary_right_split <- produce_error_split(right_split)

树的右分支每次分割的平均绝对误差-作者图片

对于树的这个分支,当我们再次在 88 和 89 sq 之间分割数据时,我们的误差最小。制成在二维格式中:

新的分类,树的右侧分割-按作者分类的图片

但是,不要忘记这个拆分已经依赖于一个过去的拆分了!用树形格式,这就容易理解多了:

新分类,整个决策树—按作者分类的图片

现在,我们的决策树开始看起来像一个真正的算法!我们现在有三条路可走:

  • 如果房子的面积小于 57 平方米。制成,我们说它的价格是 115.844 €。
  • 如果房子的面积大于或等于 57 平方英尺小于 88 平方英尺,我们说它的价格是 131.226 €。
  • 如果房子的面积超过 88 平方米。制成,我们说它的价格是 145.110 €。

当然,我们也可以将相同的推理应用于左边的分支——首先检查哪里是最佳的分割:

树的左分支的每次分割的平均绝对误差-作者图片

根据我们的“成本函数”,对于左边的分支,我们应该为新的分割选择一个介于 47 和 50 之间的值——让我们检查一下我们的新树:

新的分类,两个分支都扩展了——图片由作者提供

我们可以进一步对数据进行分区(现在你知道递归分区名称的来源了!).我们可以保持这一点,直到我们在一个树节点中有一个单独的例子——这有一些主要的后果,比如巨大的过度拟合。

那么,我们怎样才能避免那棵树下降得太深呢?是否有办法控制这种情况?是啊!用超参数!

检查一些超参数

由于这篇文章已经有点长了,我将避免过多地详述超参数。但是检查我的其他帖子来更好地了解他们。

超参数是基于树的模型的关键——正如我们已经看到的,我们的树可以下降,直到单个示例属于单个节点。

为了控制将导致非常糟糕(高方差)模型的行为,超参数来了。由于树确实容易过度拟合,我们有一些超参数来控制训练过程——下面是其中一些超参数的简短描述:

  • maxdepth 此参数给出树的最大深度(层数)。在上面的例子中,我们下降了两级。
  • minsplit 为了尝试分割,结点中必须存在的最小观测值数量。
  • minbucket 任何末端叶节点中的最小观测值。
  • CP——或复杂度参数——节点为了有效而必须贡献的最小误差增益。基本上,不会执行稍微提高树的整体准确性的分割,并且分区在此停止。

在 fit 中,您可以控制更多的超参数——您可以使用?rpart.control来检查它们。用它们做实验会让你很好的理解你的树是如何随着不同的值而变化的。

视觉化我们的契合度

最后,为了证明我们遵循了rpart所遵循的实现,让我们回到我们在本文开始时所拟合的树:

d.tree <- rpart(price ~ area,
             data = house_prices, method = 'anova',
             control = list(maxdepth=5,
                            minsplit=2, minbucket=2,
                            cp = 0.0001))

如果我们使用rpart.plot来绘制它,并专注于前两个级别:

rpart.plot(d.tree)

前两个级别与我们在使用“成本函数”规则之前构建的非常相似!

尽管很强大,决策树也很容易过度适应。如果您决定基于少数几个示例(例如,只有 1 或 2 个观察值)进行预测,您将最终得到一个不能很好地推广到测试集的模型。

在这篇文章中,我们没有遵循训练-测试分离范式,但如果你在实验中这样做,你会看到决策树在看不见的数据上的表现有多差,只需要对超参数做一点小小的调整。

然而,它们确实是很酷的模型,是任何数据科学家或机器学习工程师的必备知识。如今,您不太可能为您将面临的大多数问题部署决策树,但了解它们是投入到研究更高级的模型(如随机森林或极端梯度推进)的 50%的工作!

感谢你花时间阅读这篇文章!最后一点,我已经在 Udemy 上开设了一门课程,从头开始学习数据科学概念——使用前面的链接或下面的链接加入并立即开始学习数据科学!****

数据科学训练营课程

https://medium.com/membership/@ivopbernardo

为浓缩咖啡研磨 5 次咖啡

原文:https://towardsdatascience.com/regrinding-coffee-5-times-for-espresso-d29568427387

咖啡数据科学

来自另一个微米的浓缩咖啡

双磨咖啡在几年前成为一个有趣的讨论。然而,关于这为什么有趣的讨论并没有向前推进。我看了看双重研磨,我发现更有趣的部分是将里面的 T2 粉末与其余的咖啡分离开来。

这让我想到研磨和筛选几次。如果我每次都筛掉 300 微米以下的颗粒,那么经过几轮之后,这些颗粒的粒径会不会基本相同?于是心血来潮,我研磨筛选了 5 遍。

所有图片由作者提供

我用了一个壁龛研磨机,我从设置 30 开始。然后,我用研究员 Shimmy 筛选出小于 300 微米的颗粒。我降低了研磨设置 10,我重复了这个过程。

-10 仍在毛边上方触摸。

这很费时间,但拍摄相当惊人。我非常惊讶,因为我以为它会引导和引起问题。

我在这次调查中最大的遗憾是没有记录粒子分布。

设备/技术

意式咖啡机 : 像样的意式咖啡机

咖啡研磨机 : 小生零 SPP 毛刺的同杆针

咖啡:家庭烘焙咖啡,中杯(第一口+ 1 分钟)

拍摄准备:断音在精神上

预灌注:长,约 25 秒

输液:压力脉动

过滤篮 : 20g VST

其他设备: Atago TDS 计Acaia Pyxis 秤

绩效指标

我使用两个指标来评估技术之间的差异:最终得分和咖啡萃取。

最终得分 是评分卡上 7 个指标(辛辣、浓郁、糖浆、甜味、酸味、苦味和余味)的平均值。当然,这些分数是主观的,但它们符合我的口味,帮助我提高了我的拍摄水平。分数有一些变化。我的目标是保持每个指标的一致性,但有时粒度很难确定。

使用折射仪测量总溶解固体量(TDS),这个数字结合咖啡的输出重量和输入重量用于确定提取到杯中的咖啡的百分比,称为提取率(EY)** 。**

表演

为了查看性能,我将它与相同烤肉的其他几个镜头进行了比较。我有一次常规击球(断奏),一次内外分球,两次断奏击球。这种情况下的实验剂量被称为 75% < 300 微米,因为 75%的咖啡渣小于 300 微米。

****

这个实验性的镜头比其他所有镜头都要好,而且跑得更快。

这个实验性的镜头需要更多的劳动,但我认为它给了我们一个很好的研磨课程。人们可以用筛子和饲料进行研磨,因为只需再研磨较粗的颗粒,或者关键是使用辊式研磨机。

如果你愿意,可以在推特、 YouTubeInstagram 上关注我,我会在那里发布不同机器上的浓缩咖啡照片和浓缩咖啡相关的视频。你也可以在 LinkedIn 上找到我。也可以关注我在订阅

我的进一步阅读:

我未来的书

我的链接

浓缩咖啡系列文章

工作和学校故事集

Python 和 Pandas 中的正则表达式(Regex)示例

原文:https://towardsdatascience.com/regular-expressions-regex-with-examples-in-python-and-pandas-461228335670

Python 中正则表达式的实用实践教程

图片由来自 Pixabay 的 Josefine 提供

在很长一段时间里,我在复制粘贴的 stackoverflow 代码中使用正则表达式,并且从不费心去理解它,只要它能工作。

然而,我很快意识到,要处理软件和数据相关的任务,如网页抓取、情感分析和字符串操作,regex 是一个必备的工具。

本文旨在通过一次一个地解决正则表达式字符和处理它们的 python 函数,同时提供清晰简单的例子,来揭开它们的神秘面纱。

什么是正则表达式?简单地说,它是一个组成模式的字符序列,用于查找、替换和提取文本数据。Regex 包含自己的语法和字符,在不同的编程语言中略有不同。

模式 —这是一个正则表达式字符串,包含我们在长字符串中寻找的信息。它可以是一个单词,一系列正则表达式特殊符号,或者两者的组合。

Match —如果在字符串中找到了模式,我们称这个子串为匹配,并说模式已经匹配。一个操作中可以有多个匹配项。

正则表达式的优势:

  • 正则表达式操作比手动字符串操作执行起来更快。
  • 每种现代编程语言都包含一个用于处理正则表达式的 regex 引擎,同样的逻辑也适用于各种语言。
  • Regex 提供了一种快速编写处理复杂字符串操作的模式的方法,为您节省了时间。

Python 中的正则表达式

Python 处理正则表达式的引擎在名为[re](https://docs.python.org/3/library/re.html).的内置库中。一旦将该库导入到代码中,就可以使用下面提到的任何一个强大的函数。

这些函数接受以下参数:

  • 要搜索的图案
  • 要搜索的文本的字符串
  • 一个可选的标记了参数,该参数改变了正则表达式引擎的行为。例如 flags=re。这使得模式不区分大小写。此处涉及的其他旗帜有 re。Mre。Sre。L

使用 re 库,您可以:

  • 检查字符串中是否存在模式。
  • 返回模式出现的次数。
  • 获取模式的位置。

注意: Regex 从左到右搜索文本。一旦匹配,字符串的这一部分就用完了,不能在同一操作中再次匹配。

re.findall(pattern,text) —该函数返回列表中所有匹配的字符串。下面的代码在文本中搜索‘The’并返回所有匹配字符串的列表。

text = 'There was fur**the**r decline of **the** UHC're.findall("the", text)
###Output
[’the’, 'the’]

regex 模式区分大小写,所以使用参数flags=re.Iflags=re.IGNORECASE来区分大小写。

re.findall("the", text, flags=re.I)
###Output
[’The’, 'the’, 'the’]

请注意' The re '和' fur the r '的部分也是如何匹配的,因为不管前面或后面是什么,它都会查找这个精确的字符序列。

re.search(pattern,text) —返回第一个匹配项作为匹配对象。

re.search("the", text)
###Output
<re.Match object; span=(13, 16), match='the'>

match 对象包含关于匹配字符串的信息,比如它的跨度(文本中的开始和结束位置),以及匹配字符串本身。您可以通过调用它的来进一步提取这些细节。group(),。span(),。start(),。【T21 结束()】方法如下所示。

match_obj = re.search("the", text)*#index span of matched string*
print(**match_obj.span()**)
### (13, 16)*#the matched string*
print(**match_obj.group()**)
### the*#start position of match*
print(**match_obj.start()**)
### 13*#end position of match*
print(**match_obj.end()**)
### 16

re.match(pattern,text) —检查模式文本的开头。如果在开始时出现,则返回匹配对象,否则不返回。在下面的代码中,“the”(区分大小写)没有出现在开头,因此没有输出。

re.match('the', text)
### No output

我们还可以使用 if-else 语句,不管模式是否存在,它都会打印一条定制消息。

text = 'The focus is on 2022'
is_match = re.match('the', 
                    text, 
                    re.I)
if is_match:
    print(f"'{is_match.group()}' 
          appears at {is_match.span()}")

else:
    print(is_match) #None###Output
'The' appears at (0, 3)

re.finditer(pattern,text) —这将返回一个匹配对象的迭代器,然后我们用一个列表包装这些对象以显示它们。

text = 'There was further decline of the UHC'match = re.finditer('the', text, 
                    flags=re.I)
list(match)###
[<re.Match object; span=(0, 3), match='The'>,
 <re.Match object; span=(13, 16), match='the'>,
 <re.Match object; span=(29, 32), match='the'>]

re sub(pattern,repl,text) —用‘repl’字符串替换匹配的子字符串。

text = 'my pin is 4444'
re.sub('4', '*', text)###Output
'my pin is ****'

re.split(pattern,text)——将匹配位置的文本分割成列表中的元素。

text = "wow! nice! love it! bye! "
re.split("!", text)###Output
['wow', ' nice', ' love it', ' bye', ' ']

正则表达式元字符

元字符是在正则表达式语言中具有特殊含义的符号,这就是正则表达式的威力所在。

在这一节中,我们将探索不同的元字符,并使用re.findall()来检查字符串中模式的存在,并返回所有匹配的子字符串。

使用 r (python 原始字符串):在一个模式前面加一个 r 会将该模式的所有字符转换成正常的文字,去掉任何特殊的含义,比如作为转义字符的反斜杠。regex 引擎现在可以搜索它的特殊字符,正如您将看到的,反斜杠非常突出。

用反斜杠对正则表达式字符进行转义:当你想在一个文本中精确地搜索下面的任何正则表达式符号时,你必须用反斜杠对它们进行转义(同时也使用 r raw 字符串),这样它们也就失去了特殊的正则表达式含义。

  1. (点字符或通配符)—匹配并返回字符串中的任何字符,除了新行。这可以是数字、空格、字母或标点符号。
pattern = r'.'
re.findall(pattern,
        "Wow! We're now_25")###Output
['W', 'o', 'w', '!', ' ', 'W', 'e', "'", 'r', 'e', ' ', 'n', 'o', 'w', '_', '2', '5']
  1. \w (小写 w) —任何字母数字字符(字母、数字或下划线)。
pattern = r'\w'
re.findall(pattern,
        "Wow! We're now_25")###Output
['W', 'o', 'w', 'W', 'e', 'r', 'e', 'n', 'o', 'w', '_', '2', '5']
  1. \W (大写 w) —任何非 W 的内容,如空格和特殊字符。
pattern = r'\W'
re.findall(pattern,
        "Wow! We're now_25")###Output
['!', ' ', "'", ' ']

4.\ d—0 到 9 之间的任意数字。

pattern = r'\d'
re.findall(pattern,
        "Wow! We're now_25")###Output
['2', '5']
  1. \D —任何非数字。否定\d。
pattern = r'\D'
re.findall(pattern,
        "Wow! now_25")###Output
['W', 'o', 'w', '!', ' ', 'n', 'o', 'w', '_']
  1. \s (小写 s) —一个空格。
pattern = r'\s'
re.findall(pattern,
        "Wow! We're now_25")###Output
[' ', ' ']
  1. \S (大写 s) —求反\S。返回非空格的任何内容。
pattern = r'\S'
re.findall(pattern,
        "Wow! Now_25")###Output
['W', 'o', 'w', '!', 'N', 'o', 'w', '_', '2', '5']

字符集

  1. [] 匹配方括号内的任意字符。例如,模式“[abc]”在文本中查找 a 或 b 或 c,也可以写成“a|b|c”。您也可以使用破折号在括号内定义一个范围,而不是写下每个字符。例如,[a-fA-F]匹配从 a 到 F 的任何小写或大写字母,下面的代码返回任何元音字母。
pattern = r'[aeiou]'
re.findall(pattern,
        "Wow! We're now_25")###Output
['o', 'e', 'e', 'o']

9.**【】**在左方括号后有一个帽子字符否定了字符集。它返回方括号内的相反字符或范围。下面的代码返回除字母 m 到 z 之外的所有内容。

*#Any char except letters m to z*
pattern = r'[^m-zM-Z]'
re.findall(pattern,
        "Wow! We're now_25")###Output
['!', ' ', 'e', "'", 'e', ' ', '_', '2', '5']

重复正则表达式模式

也称为量词,这些特殊字符写在模式或字符之后,告诉 regex 引擎匹配它的次数。

10.**+**(一次或多次)—如果前一模式出现一次或多次,则匹配。下面的代码匹配前面有‘hell’的字符‘o’

*#match o in hello once or many times*
text = 'hell hello ago helloo hellooo'
pattern = r'hello+'
re.findall(pattern, text)###Output
['hello', 'helloo', 'hellooo']

11.*****(零个或更多)—如果前一模式出现零次或多次,则匹配。

*#match o in hello zero or many times*
text = '**hell hello** ago **helloo hellooo**'
pattern = r'hello*'
re.findall(pattern, text)['hell', 'hello', 'helloo', 'hellooo']

12.**?**(零次或一次)—如果前一模式出现零次或一次,则匹配。

*#match o in hello zero times or once*
text = '**hell hell**o ago **hello**o **hello**oo'
pattern = r'hello*'
re.findall(pattern, text)['hell', 'hello', 'hello', 'hello']

13.{ n }—定义匹配前一个字符或模式的确切次数。例如‘d{3}’匹配‘ddd’

*#Extract years*
text = '7.6% in 2020 now 2022/23 budget'
pattern = r'\d{4}'
re.findall(pattern, text)['2020', '2022']

14.{最小,最大} —定义匹配先前模式的最小(min)和最大(max)时间。例如'd{2,4}’匹配‘dd’‘ddd’‘dddd’

*#Dot followed by 2 to 5 word chars*
text = 'may@gmail.com cal@web.me ian@me.biz'
pattern = r'\.\w{2,5}'
re.findall(pattern, text)['.com', '.me', '.biz']
  1. {min,} —匹配前一个元素至少‘min’次。
*#Long words*
text = 'universal healthcare is low'
pattern = r'\w{5,}'
re.findall(pattern, text)['universal', 'healthcare']

贪婪量词— 上述所有量词都被认为是贪婪的,因为它们试图在每个匹配中占据尽可能多的字符,只要满足模式,就会产生最长的匹配。例如,re.findall(‘b+’, ‘bbbb’)返回一个匹配[‘bbbb’],这是最长的可能匹配,即使[‘b’, ‘b’, ‘b’, ‘b’]仍然是有效的匹配,但匹配较短。

不贪(懒)——可以通过加一个问号(?)在量词后面。这意味着 regex 引擎将在每次匹配时返回最少的个字符。下图显示了量词在贪婪和非贪婪模式下的行为比较。

作者图片

边界/锚点

  1. ^ —只匹配文本的开头,因此被写成模式中的第一个字符。请注意,这不同于[..]它否定了方括号中的模式。
*#Starts with two digits*
text = '500,000 units'
pattern = r'^\d\d'
re.findall(pattern, text)###Output
['50']
  1. $ —匹配字符串的结尾,因此写在模式的结尾。
*#Ends with two digits*
text = '500,000 units'
pattern = r'\d\d$'
re.findall(pattern, text)###Output
[]
  1. \b (单词边界)—匹配单词前后的边界,或 a \w 和 a \W 之间的空字符串。
pattern = r'\b'
re.findall(pattern,
           "Wow! We're now_25")###Output
['', '', '', '', '', '', '', '']

要查看边界,使用re.sub()功能将\b替换为~符号。

pattern = r'\b'
re.sub(pattern, 
       '~',
       "Wow! We're now_25")###Output
"~Wow~! ~We~'~re~ ~now_25~"

  1. () —当你写一个正则表达式模式时,你可以用括号定义组。这对于从字符串中提取和返回细节很有用。请注意,圆括号不会改变模式的结果,而是将它分组为可以单独检索的部分。
text = 'Yvonne worked for von'
pattern = r'(.o.)'
re.findall(pattern, text)###Output
['von', 'wor', 'for', 'von']

使用 **m.group()** 访问匹配的组— 在下面的代码中,我们将电子邮件的不同部分分成 3 组。m.group()m.group(0)都返回整个匹配的字符串。m.group(1)m.group(2)m.group(3)分别返回不同的组。括号是访问匹配组的一种便捷方式。

text = 'this is @sue email sue@gmail.com'
pattern = r'(\w+)@(\w+)\.(\w+)\b'
m = re.search(pattern, text)#match object
print(m)
### <re.Match object; span=(19, 32), 
                match='sue@gmail.com'>#full match
m.group(0)
### 'sue@gmail.com'm.group(1)
### 'sue'm.group(2)
### 'gmail'm.group(3)
### 'com'

使用 **\group_num**引用组—一个正则表达式模式可以包含几个组,如前面包含 3 个组的电子邮件示例所示。您可以在 regex 模式中使用\1、\2…从 1 开始按位置引用和匹配一个组。下面的代码查找字符重复出现的子字符串。

text = 'hello, we need 22 books'
pattern = r'(\w)\1'
list(re.finditer(pattern, text))###Output
[<re.Match obj; span=(2,4), match='ll'>,
 <re.Match obj; span=(11,13), match='ee'>,
 <re.Match obj; span=(15,17), match='22'>,
 <re.Match obj; span=(19,21), match='oo'>]

分别使用 **?P<name>** **?P=name** 命名和访问捕获的组 —您可以为组指定一个名称,以便以后访问。当您有许多组时,这比\grp_number符号要好,并且它增加了您的正则表达式的可读性。要访问匹配的组,请使用m.group(‘name’)

text = '08 Dec'
pattern = '(?P<day>\d{2})\s(?P<month>\w{3})'
m = re.search(pattern, text)
m.group('day')###
'08'

非捕获组

20.?: —匹配但不捕获组。将?:包含在您希望忽略的组中。下面的代码匹配带有百分号的数字,并且只返回数字。

text = 'date 23 total 42% date 17 total 35%'
pattern = r'(\d+)(?:%)'
re.findall(pattern, text)###
['42', '35']
  1. | (or) —这将返回一种或另一种模式的所有匹配。
text = 'the **sun**ny **sun** **shine**s'
re.findall(r'sun|shine', text)###Output
['sun', 'sun', 'shine']

熊猫和正则表达式

Pandas 是一个强大的 Python 库,用于分析和操作数据集。在 Pandas 中搜索和清理基于文本的列时,正则表达式非常方便。

Pandas 包含几个支持正则表达式模式匹配的函数,就像我们看到的re库一样。下面是我们将在本教程中使用的三个主要函数。点击阅读其他熊猫正则表达式函数

  • Series . str . contains(pattern)—此函数检查列(Series)中的模式,在模式匹配的地方返回 True 和 False 值(掩码)。然后可以将掩码应用于整个数据帧,以便只返回真行。
  • Series.str.extract(pattern,expand,flags) —要使用此函数,我们必须在模式中使用括号定义组。该函数提取匹配项,并将组作为数据帧中的列返回。当模式中只有一个组时,使用expand=False返回一个系列,而不是 dataframe 对象。
  • Series.str.replace(pattern,repl,flag) —与re.sub()类似,该函数用 repl 字符串替换匹配项。

在本节中,我们将处理七个正则表达式任务来执行以下操作;

  • 筛选数据以返回符合特定条件的行。
  • 从列中提取子字符串和其他详细信息。
  • 替换列中的值。

为了说明这一点,我使用了 Kaggle 提供的 titanic 数据集,这里是 GNU 自由文档许可证下的。在新的 Jupyter 笔记本上,导入熊猫并加载数据。

import pandas as pd
df = pd.read_csv('titanic.csv')

按作者分类的数据集

过滤数据帧— **s.str.contains(pattern)**

任务 1: 过滤数据帧,返回票号为 C 和 a 的行

下面是所有必须匹配的“C A”票变体的列表。

作者图片

regex 模式以大写字母 C 开头,后面跟一个可选的点,然后是大写字母 A,后面跟一个可选的点。我们对点符号进行了转义,以精确匹配句点,而不是通配符正则表达式字符。

pattern = r'C\.?A\.?'
mask = df['Ticket'].str.contains(pattern)
df[mask].head(10)

作者图片

要获得 CA 的总行数,使用mask.sum(),它将所有显示为 1 的True值和显示为 0 的False值相加。

mask.sum()
### 42

提取数据— **s.str.extract(patt)**

任务 2: 从乘客姓名中提取所有唯一的头衔,如先生、小姐和夫人。

作者图片

Regex 模式:这将搜索一个空格,后跟一系列字母(括在括号中),然后是一个点字符。我们使用括号将想要捕获和返回的子串分组。expand=False返回一个系列对象,使我们能够在其上调用value_counts()

pattern = '\s(\w+)\.'
all_ts = df['Name'].str.extract(
                    pattern, 
                    expand=False)
unique_ts = all_ts.value_counts()

作者图片

Task 3a: 从“Name”列中提取职位、名字和姓氏,并在新的 dataframe 中将它们作为列返回。

作者图片

正则表达式模式;每个名字包含“一个或多个单词字符序列”(姓氏),然后是逗号、空格、另一个字符序列(头衔)、句号、空格、另一个字符序列(名字),然后是零到许多其他字符。

pattern = r'(\w+), (\w+\.) (\w+).*'
df_names = df['Name'].str.extract(
                            pattern, 
                            flags=re.I)
df_names

作者图片

任务 3b: 用名为的的有序列清理上面的数据帧。

如前所述,命名组对于捕获和访问组非常有用。对于 Pandas,这尤其方便,因为这些名称现在将成为我们新数据框架中的列名。

下面的正则表达式与上面的相同,除了使用(?P<name>).命名组。代码提取三个命名的列,然后我们使用df.reindex()对它们重新排序。

pattern = r'(?P<lastname>\w+),\s
                (?P<title>\w+\.)\s
                (?P<firstname>\w+).*'
df_named = df['Name'].str.extract(
                            pattern,  
                            flags=re.I)
df_clean = df_named.reindex(
                        columns = 
                            ['title', 
                             'firstname',
                             'lastname'])
df_clean.head()

作者图片

替换列中的值— **s.str.replace(pattern, repl)**

任务 4a: 用大写字母替换所有标题。

作者图片

regex 模式搜索空格,然后是一个或多个单词字符(用括号括起来),最后是一个句点。然后,我们将捕获的组替换为大写的组。

pattern = r’\s(\w+)\. '
df[’Name’].str.replace(pattern, 
            lambda m:m.group().upper())

上面的 lambda 函数的意思是,对于每一行,取抓取的组,转换成大写。

作者图片

任务 4b: 仅大写Mr.Mrs.标题。在这种情况下,我们在括号内使用|。

pattern = r'\s(Mr|Mrs)\.\s'
df['Name'].str.replace(pattern, 
            lambda m:m.group().upper(),
                      flags=re.I)

作者图片

任务 5: 通过插入破折号来显示日、月和年,从而清除下面一列中的日期。我们要保存列中的其他字,因此不能直接调用[pd.to_datetime()](https://www.w3resource.com/pandas/to_datetime.php)

作者图片

模式:搜索两位数,然后再次搜索两位数,然后搜索四位数,并用括号将它们分成三组。 lambda 功能意味着对于每场比赛,在第一组和第二组之后用破折号连接各组。

pattern = r'(\d{2})(\d{2})(\d{4})'
d['A'].str.replace(pattern,
        lambda m: m.group(1)+'-'+
                   m.group(2)+'-'+
                   m.group(3))

作者图片

结论

在本文中,我们学习了正则表达式,并使用 Python re 库和 Pandas 字符串函数来探索不同的特殊正则表达式元字符。

我们还了解到,在每个模式前面加上一个 r raw 字符串以消除任何特殊的字符串含义,以及在字符串中精确匹配特殊的正则表达式字符时用反斜杠对它们进行转义是很重要的。你可以在 GitHub 上找到所有的代码

请记住,大多数正则表达式都是特定情况下特有的,所以只需尝试每种可能的情况,以避免错误匹配和遗漏。大多数时候,有许多方法可以编写正则表达式,所以选择最简单和最明智的。你可以使用 regex101.com等网站进行测试,直到满意为止。

我希望你喜欢这篇文章。每当我发表文章时,想收到更多这样的消息,请在这里订阅。如果你还不是一个媒体成员,并且愿意支持我成为一名作家,请点击这个链接,我将获得一小笔佣金。感谢您的阅读!

进一步阅读

正则表达式:详细语法

原文:https://towardsdatascience.com/regular-expressions-syntax-in-detail-dcc11e9aa996

编程|模式匹配|计算机科学

对正则表达式及其语法的深入探究

Gerd Altmann 在 Pixabay 上拍摄的照片

在我们的上一篇文章中,我们介绍并讨论了正则表达式(regex)范式。Regex 是一个强大的工具,它允许我们执行字符串模式匹配、替换和其他操作。

我们以一个用例为例,构建一个正则表达式来验证一个十六进制颜色值。

您可以在此处找到介绍材料:

这篇文章的目的是作为一个剪贴簿/备忘单风格指南,介绍 regex 中一些更高级的概念。当我学习这些类型的技能(需要练习才能真正掌握的技能)时,我发现有简明的指导方针和一个实践场所来测试这些指导方针要好得多。

我通常使用下面的在线工具来练习和测试我的正则表达式。还有其他好的在线工具。挑一个你喜欢的,一如既往——练习,练习,再练习。

https://regex101.com

因此,这篇文章将与我的典型文章略有不同。这将是最小的和直接的。我也很想听到你对这种指导风格的反馈,以及你是否喜欢更详细和动手的写作风格。

实验是成长的本质

术语

在开始之前,让我们先回顾一下术语。

剖析正则表达式。图片作者。

分隔符用于指示正则表达式的开始和结束。

在分隔符之间,我们编写正则表达式。regex是我们想要匹配的实际模式。

在结束分隔符之后,我们也可以使用修饰语。但是,稍后我们会详细介绍这一点!

匹配字符

两种主要类型:普通特殊

普通的

普通字符是正则表达式的最简单形式,因为它们匹配自身(即它们的文字字符)。匹配它们意味着如果我们在正则表达式中输入字符A,它实际上会在字符串中寻找一个A。其他一些例子包括09之间的数字,以及字母表中剩余的字母。

给定一个字符串abc123,匹配的正则表达式就是/abc123

控制

控制字符(或转义序列)是代表其他元素的字符序列。例如,控制序列\n代表一个新行。下面,我们展示了一些常用的转义序列。

警告:不同的正则表达式引擎可能有不同的表示——所以最好仔细检查你的正则表达式的文档。

特殊/元字符

人物类

字符类可以列出一个或多个字符。使用字符类实质上就是说任何列出的字符都是匹配的。我们通过使用方括号符号对元字符进行分组来展示这一点。我们也可以使用操作符来指定范围。

例如,/[abc123]表示如果方括号之间的任一字符存在于字符串中,那么它将是匹配的。

同样,/[a-f]表示af之间的任何字母都可以匹配(即a, b, c, d, e, f)。

使用范围时要记住的一件重要事情是,正则表达式范围是基于 ASCII 代码的。假设我们有像\[A-z]这样的正则表达式,这个范围将匹配一些额外的符号,比如\和方括号。其他一些例子包括:

  • **[9-0]** —可能是一个空范围
  • **[],\[\** 无效,将无法编译

看看下面的 ASCII 码,更好地理解我在说什么。

https://www.w3schools.com/charsets/ref_html_ascii.asp

这些例子被称为正类,因为正则表达式表达了它应该匹配的内容。另一方面,我们也有负类,它们表示正则表达式不应该匹配的内容。这是通过在我们不想匹配的模式开始处使用^操作符来完成的。

例如,假设我们有正则表达式/[^abc123]和字符串abchello123,正则表达式引擎将忽略方括号中列出的任何字符,因此只匹配表达式的hello部分。

作者图片

那么,如果我们真的想要匹配一个-,甚至是^字符呢?

该语法取决于所讨论的操作符。例如,对的转义可以通过在正则表达式的最开始或最末尾使用它来完成(即[A-Z_-])。要用破折号开始一个范围,该范围需要是字符类中的第一个范围([--/A-Z],[A-Z+--]))。

至于^这个角色,工作原理也差不多。如果不是第一个字符,它将被计为文字。

对于[],最好的方法是使用\操作符,如\[\]

通配符

—匹配除换行符之外的任何字符,但匹配带有点全修饰符的\n(某些类型的正则表达式引擎用空字节替换新行)—请咨询您的正则表达式引擎!

当在字符类中使用时,它失去了它的功能,并作为文字进行匹配。这是没有用的,因为一个字符类已经匹配任何东西。

警告: 用带量词的点变成了 非常慢 。(即: )。+ )

量词

量词用来表示重复。有 4 个主要的重复量词。

? —重复零次或一次

+ —重复一次或多次(无限制)

* —重复零次或多次(无限制)

{} —允许我们指定我们想要的精确重复。我们也可以传入最小值和最大值— {n,m}{n,}{,m}

{,m}不适用于大多数正则表达式,所以最好使用{0,m}

量词被称为贪婪的,因为它们总是倾向于匹配而不是不匹配。量词也会尽可能频繁地尝试匹配。

让我们快速看一个例子。

作者图片

我们可以看到我们的正则表达式匹配了两个组。第一组(示例中的浅蓝色)由前 5 个字符组成,而第二组(深蓝色)由剩余的 4 个字符组成。

贪婪和懒惰

贪婪意味着只有当条件不能再满足时才会停止。

条件一满足,懒就停了。我们通过使用?操作符指定一个正则表达式为惰性的。这样会让它更不情愿。它仍然倾向于匹配,但在进行匹配的同时尽可能减少匹配次数,它可以是 0。

比如贪婪的h.+l'hello'里的'hell'匹配,而懒惰的h.+?l'hel'匹配。

为了利用这一点并使其更快,我们必须精确并使用否定(这将防止回溯)。否定几乎总是比使用通配符好。

间隔

整个想法实质上是创建一个备用分支。我们可以通过|操作符来实现。

|在一个类中并不特殊;因此,它将作为文字进行匹配。所以比尔。此外,分支顺序也很重要(最左边的分支是最需要的,但不会阻碍其他分支)。

|也有最低的优先级,所以使用分组操作符来表示开始和结束可能更明智。

作者图片

子模式和分组

嗯,顾名思义,他们把东西组合在一起。可以使用( )字符指定分组。

例如,假设我们想要无限次地完全匹配单词hello。我们可以使用 regex /(hello)+来指定我们想要匹配整个组(在本例中是 hello)一次或多次(通过+)。

作者图片

结束语

在这篇文章中,我们回顾了最常用的主要正则表达式语法细节。掌握正则表达式绝对是一项技能,乍一看似乎是一个额外的学习工具,但实际上它非常有用,这是我的经验之谈。

我特意保持这篇文章的简洁,因为正则表达式是你必须使用的工具之一。我强烈建议您尝试一下 regex 学习之旅。如果这样做,您将成为效率提高 10 倍的开发人员——我保证!

你喜欢这篇文章吗?如果是,请考虑订阅我的电子邮件列表,以便在我发布新内容时得到通知。免费的:)

https://david-farrugia.medium.com/subscribe

也许你也可以考虑成为一名会员来支持我和你其他喜欢的作家。每月 5 美元,你就可以无限制地阅读 Medium 上的每一篇文章。

https://david-farrugia.medium.com/membership

想联系吗?

我很想听听你对这个话题的想法,或者任何关于人工智能和数据的想法。

如果你想联系我,请给我发电子邮件至 davidfarrugia53@gmail.comT5T7。

领英

正则表达式:文本分析的瑞士刀

原文:https://towardsdatascience.com/regular-expressions-the-swiss-knife-for-text-analysis-76a34ac71c97

检查促进高级字符串操作的最佳方法

Unsplash 上由 Patrick 拍摄的照片

从数据科学开始,我养成了依赖 Python 内置函数进行字符串操作的习惯,这是可以理解的。

这些功能易于理解和实现。

不幸的是,当执行需要更复杂的文本处理的 NLP 任务时,这些方法变得不够用了。

结果,我发现自己很不情愿地熟悉了正则表达式和 Python 的 RE 模块。这是一项艰巨的任务,因为学习正则表达式几乎类似于学习一门新语言。

然而,一旦我掌握了 RE 的基础知识,我就再也没有回头。现在,RE 是我处理大部分文本相关任务的首选工具。

对于那些不愿意学习正则表达式的人(像我一样),我将解释它优于使用 Python 的基本字符串方法的原因,然后探索它的应用。

正则表达式

利用正则表达式需要使用特殊的字符串来定义特定任务的搜索模式。

这些图案由通常被称为元字符的特殊字符组成。

有了元字符,用户可以用很少的代码和最少的努力完成复杂的处理过程。

虽然这听起来不错,但正则表达式确实有一个缺点。使用这种方法可能需要大量特殊字符的知识。

看看这个备忘单,看看要定义搜索模式需要知道多少元字符。

虽然这些元角色很容易单独理解,但将它们组合在一起可能会是一个更加艰苦的过程。

例如,以下表达式可用于匹配帐户密码:

^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{10,}$

如果您熟悉所使用的特殊字符,您应该能够判断出该表达式匹配的字符串长度为 10 个或更多个字符,并且至少有 1 个小写字母、大写字母和数字。

然而,对于没有受过训练的人来说,这种表达可能看起来像是敲击键盘时产生的简单的胡言乱语。

鉴于新手很难理解搜索模式,Python 的内置字符串方法相比之下更有吸引力(你可以在这里查看方法列表)。

那么,当我们可以坚持使用这些更容易理解的方法时,为什么还要去学习正则表达式呢?

字符串方法的缺点

尽管简单明了,Python 的内置方法有两个主要缺陷。

1。它们的可用性有限

考虑下面的句子:

“I went to the bar with my barber holding a crowbar.”

下面是你如何在文本中找到单词“bar”的位置。

代码输出(由作者创建)

find 方法可以用来定位文本中某个单词的索引。不幸的是,这些类型的功能用途非常有限。

您可以使用它们来查找特定的字符序列。在该示例中,对序列“b”、“a”和“r”执行搜索。

但是,如果您希望执行更广泛的搜索,该怎么办呢?如果您希望找到任何 3 个字母的单词,而不是“bar ”,该怎么办?如果您希望找到以“b”开头、以“r”结尾的单词,该怎么办?

如果用户希望使用 Python 的字符串方法来搜索一般的字符模式,而不是特定的字符序列,他们将需要在代码中增加更多的复杂性来考虑所有情况。

这自然会是一项繁琐的任务。

2。它们会产生不准确的结果。

即使用户愿意在基本的字符串方法上投入额外的时间和精力,他们也有可能从查询中获得误导性的结果。

让我们继续上一个例子,执行另一个任务。

我们可以找到“bar”在句子中出现的次数,而不是定位单词“bar”。

代码输出(由作者创建)

使用 count 方法,我们看到“bar”在字符串中出现了 3 次。字符“b”、“a”和“r”的序列出现在单词“bar”、“barber”和“crowbar”中。

但是,如果您搜索的是单词“bar ”,而不是字符序列,会怎么样呢?在这种情况下,代码输出会不准确,因为单词在字符串中只出现一次。

这可能看起来是无害的差异,但根据具体情况,它可能会导致严重的问题。

例如,如果我们想修改句子,将“酒吧”一词替换为“医院”,会发生什么情况?

下面是用替换方法执行操作的结果:

代码输出(由作者创建)

呀。那对眼睛不太好。

即使可以通过某些措施纠正这些错误,也需要不必要的时间和精力来减轻不必要的风险。

幸运的是,使用正则表达式,可以克服 Python 的字符串方法的局限性。

该环形模块

使用正则表达式,用户可以避免低效率,并抑制在文本分析中实现错误方法的风险。

在 Python 中,正则表达式可以与 RE 模块一起使用。

与只适用于特定字符序列的基本字符串方法不同,RE 提供了进行更广泛查询的自由和使用元字符执行更高级操作的能力。

为了简要演示 RE 模块的功能,我们可以使用上一个示例中的句子:

“I went to the bar with my barber holding a crowbar.”
  1. 找出单词“bar”在句子中出现的次数

我们希望避免以前的错误,在计算“bar”的出现次数时,我们包括了单词“barber”和“crowbar”。

虽然用基本的字符串方法搜索这种情况可能不可行,但用 RE 却非常简单。

让我们创建一个搜索模式,忽略以“bar”开头或结尾的单词。在这种情况下,元角色 \b 是最有用的。

代码输出(由作者创建)

\b 是一个特殊字符,它定义了单词和非单词之间的界限。在这种情况下,在表达式中的“bar”之前和之后添加 \b 可以确保不会选择以“bar”开头或结尾的单词。

2。将“酒吧”一词改为“医院”。

我们可以修改案文,将“酒吧”改为“医院”,而不包括“理发师”和“撬棍”等案件。

RE 有自己的替换方法可以完成这项任务。

代码输出(由作者创建)

很好。没有“医院”或“拥挤医院”的踪影。

总而言之,我们可以看到使用正则表达式如何使用户避免搜索中的错误和文本中的修改。让我们更进一步,探索该模块的其他一些功能。

3。查找包含 3 个或更多字符的单词。

代码输出(由作者创建)

4。找出以“b”开头,以“r”结尾的单词。

代码输出(由作者创建)

自然,正则表达式的功能数不胜数,不可能在一个简短的演示中涵盖。

但是,本练习可以展示 RE 模块的功能。请注意,如何通过定义所需的表达式和使用单一方法来回答每个问题。

不用正则表达式也能达到同样的效果,但需要更费力的方法(例如,创建用户定义的函数、使用循环等。).

应用程序

正则表达式有两个主要功能:信息提取和文本处理。

换句话说,它可以帮助用户从给定的文本中获取所需的信息,或者修改整个文本。

这个技术时代的文本可以以各种形式出现。文章、电子邮件和社交媒体帖子等文档以其独特的格式存储有价值的信息。

使用正则表达式,可以创建合适的表达式来搜索或修改任何场景中的文本。

作为一个例子,让我们在一个包含 tweets 的数据集上使用 RE(在参考资料部分引用)。这里的可以得到的数据。

代码输出(由作者创建)

通过使用 RE 模块获取每条 tweet 中提到的所有用户名,我们可以看到 RE 模块的信息提取功能。

代码输出(由作者创建)

接下来,让我们通过使用 RE 来处理 tweet,删除每个 tweet 中不需要的链接。

代码输出(由作者创建)

注意:对于这个简单的例子,假设所有链接都以“https”开头。

结论

UnsplashPrateek Katyal 拍摄的照片

考虑到创建复杂搜索模式时必须学习的所有元字符,学习使用正则表达式确实需要一些练习。

然而,投入时间和精力来熟悉这个工具无疑会有回报。

我祝你在数据科学的努力中好运!

参考

  1. Preda,G. (2020)。COVID19 推文,版本 24。于 2022 年 4 月 16 日从 https://www.kaggle.com/datasets/gpreda/covid19-tweets.取回

机器学习的正则化

原文:https://towardsdatascience.com/regularization-for-machine-learning-67c37b132d61

杰斯温·托马斯在 Unsplash 上的照片

为什么它是最重要的技术之一,以及如何使用它

这是什么?

正则化是一种通过使损失函数考虑特征重要性来校准机器学习模型的技术。

直观地说,这意味着我们迫使我们的模型对那些在预测目标变量时不太重要的特征给予较小的权重,而对那些更重要的特征给予较大的权重。

为什么有用?

在机器学习中,可能会有“过度拟合”的趋势,这取决于我们拥有的数据和我们使用的模型。也就是说,我们的模型可能“过于努力”来适应训练数据,然后它将无法在以后进行归纳(以在新数据上很好地工作)。

这是一个与好模型相比过度拟合的直观示例,也是一个拟合不足的示例:

图片作者(改编自 sklearn 示例)

当我们没有太多数据时,当我们必须拥有许多特征时,或者当我们的模型太复杂时,这可能会发生。

经验法则:数据越少,就越应该保持模型简单(参数越少,特性越少)

最基本的正则化技术是 L1L2 。我们将详细了解它们是如何工作的,然后对其他技术进行概述。

L1 对 L2

L1 和 L2 提到了这些正则化(也分别称为套索和岭回归)所使用的规范类型,以及回归中添加到损失函数的项。它们之间的差别很微妙,但却很重要。

首先,让我们简要回顾一下回归方程:

回归方程式

其中,ŷ是我们的预测值,x 是特征,β和α是模型的截距。在回归模型中,我们的工作是找到最佳的β和α。我们所说的最好是什么意思?将损失函数最小化的那些:

基本损失函数

其中 n 是观测值的数量,ŷ是预测值,y 是目标变量的实际值。

损失函数简单地计算预测值(ŷ)和实际值(y)之间的差值的平方,并对它们求和。

现在让我们看看这个函数是如何进行 L1 回归的:

带 L1 正则项的损失函数

新术语(绿色,以防您没有注意到)有两个主要部分:lambda,它是正则化参数(它越大,我们的正则化就越强)和|β|,它是回归参数的绝对值。本质上,它所做的是增加高参数的成本,因此为了给参数分配更大的值,必须通过显著提高预测质量来补偿。

另一方面,L2 回归是这样计算损失的:

带有 L2 正则项的损失函数

区别很微妙:我们取β的平方,而不是绝对值。

在 L1,如果各个特征对整个模型的贡献不足,βj 可以等于 0。然而,在 L2,β可以很小,但不等于 0。

在 L1 和 L2 之间做出选择会有什么影响?

首先,因为 L1 可以赋予权重等于 0,这实质上意味着我们可以摆脱不太重要的功能,使我们的模型更简单,数据摄取过程更容易。另一方面,L2 在计算上花费较少(因为 L2 范数有一个封闭形式的解)。

顺便说一下,如果你想把两者结合起来,你有弹性网选项,它增加了两个正则化项,并允许你给一个或另一个更多的权重。

你应该使用多少正则化?这将取决于您的数据集和模型。我建议你做一些超参数调整,通过测试正则化参数的多个值,看看哪一个产生最好的误差。让我们看看如何使用 Python 来实现这一点。

如何使用它

Lasso、Ridge 和 ElasticNet 回归都可以在 scikit-learn 上获得,只需几行代码就可以轻松使用。

我添加了一些代码,可以帮助您实现它们,还可以评估正则化强度的最佳值,在 scikit-learn 中称为 alpha (但相当于我们之前示例中的λ。

我使用了使用 sk learn . datasets . make _ regression 生成的假数据,以及训练数据的标准名称( X_trainy_train ),但是您可以很容易地根据您的数据和代码调整它:

from sklearn.linear_model import Lasso, Ridge, ElasticNetalphas = [0.0001, 0.001, 0.01, 0.1, 1, 10, 100, 1000]# LASSO
errors = []
for alpha in alphas:
    clf = **Lasso(alpha=alpha)**
    clf.fit(X_train, y_train)
    y_hat = clf.predict(X_test)
    mse = mean_squared_error(y_test, y_hat)
    errors.append(mse)
plt.plot(np.log10(alphas), errors)
plt.show()

Lasso 回归的 MSE 与 log(alpha )(图片由作者提供)

我绘制 log(alpha) 而不是 alpha 的原因是因为它的比例呈指数变化,所以在对数比例中可视化变得更容易。

当然,Ridge 和 ElasticNet 也可以类似的方式使用:

# RIDGE
errors = []
for alpha in alphas:
    clf = **Ridge(alpha=alpha)**
    clf.fit(X_train, y_train)
    y_hat = clf.predict(X_test)
    mse = mean_squared_error(y_test, y_hat)
    errors.append(mse)
plt.plot(np.log10(alphas), errors)
plt.show()

岭回归的 MSE 与 log(alpha )(图片由作者提供)

# ElasticNet
errors = []
for alpha in alphas:
    clf = **ElasticNet(alpha=alpha)**
    clf.fit(X_train, y_train)
    y_hat = clf.predict(X_test)
    mse = mean_squared_error(y_test, y_hat)
    errors.append(mse)
plt.plot(np.log10(alphas), errors)
plt.show()

弹性网络回归的 MSE 与 log(alpha )(图片由作者提供)

从这些图中,您可以找到将最小化模型误差的回归类型和 alpha。

其他技术

提前停止

早期停止可用于迭代方法,如梯度下降,它包括在达到某个目标后停止训练过程。这个目标可以是某个精度度量的给定值,或者只是迭代次数。换句话说,你是在告诉模型,如果它表现得足够好,或者如果它走得太远,就停止训练过程。它常用于神经网络和梯度增强方法。

早期停止起到正则化的作用,因为它降低了模型超参数将针对特定训练数据进行优化而不能很好地泛化的风险。

拒绝传统社会的人

Dropout 是训练神经网络时随机丢弃节点的技术(尽管它可以适用于其他方法)。在每个时期,一定百分比的节点没有被使用,这给过程增加了一些噪声,并迫使模型更好地适应和概括。在 XGBoost 中,“DART”booster 还建议在训练过程中随机删除树,以减少过度拟合。

如果你喜欢这篇文章,你可能也会喜欢这些:

如果你想进一步讨论,请随时通过 LinkedIn 联系我,这将是我的荣幸(老实说)。

机器学习中的正则化

原文:https://towardsdatascience.com/regularization-in-machine-learning-6fbc4417b1e5

正则化是如何工作的,如何防止过度拟合?

按作者分列的数字

内容

  1. 介绍
  2. 线性回归概述

2.1 简单数据集的回归

3.0 正规化

3.1 山脊正规化

3.2 套索正规化

4.0 山脊 Vs 套索正则化

4.1 作为约束最小化的脊和套索正则化

4.2 弹性网

5.0 一个简单的正则化示例

6.0 结论

7.0 参考文献

1.0 简介:

灵活性是指模型表示特征变量和目标变量之间复杂变化的能力。模型的灵活性在很大程度上影响其预测能力。在处理复杂数据集时,高度灵活的模型是可取的。使用高灵活性模型的缺点是,这种模型在训练阶段往往会过度拟合数据。当模型密切学习训练数据集中的变化时,会发生过度拟合。过度拟合模型显示了从一个看不见的数据到另一个看不见的数据的预测的巨大变化。如此大的变化导致高方差误差。控制方差误差允许模型对看不见的数据进行概括并表现良好。这是机器学习中的一个重要主题。正则化是用于控制高灵活性模型中过拟合的技术之一。虽然正则化用于许多不同的机器学习算法,包括深度神经网络,但在本文中,我们使用线性回归来解释正则化及其用法。

2.0 线性回归概述:

在深入研究正则化技术之前,我们先快速回顾一下线性回归模型。线性模型表示为:

βⱼ是权重,β₀是偏差项。ῡᵢ是第 I 个数据点 xᵢⱼ.的预测值在上面的等式中,n 是特征的数量,k 是数据点的数量。

线性回归模型通过最小化第 I 个 yᵢ观察值和第 I 个数据点的模型预测值ῡᵢ之间的误差平方和(SSE)来计算未知权重和偏差项。

βⱼ和β₀是上述等式中的未知数。为了便于表示,我们改用无索引符号。

X 是一个 k×n+1 矩阵,由 xᵢⱼ和一个额外的 1 列组成。

观察值由长度为 k 的向量 y 表示。

未知权重βⱼ和β₀由长度为 n+1 的向量表示。

SSE 的表达式可以用矩阵形式重写为:

可以通过将上证 w.r.t β 的偏导数设置为零来找到最小值。

求解上式中的 β 得出:

由于矩阵 XX 可能不总是条件良好的,实际上最小化是通过梯度下降技术实现的。

线性回归这个名称是指模型在未知的βⱼ和β₀.是线性的当观测值 yᵢ和数据点 xᵢⱼ之间的关系接近线性时,该模型工作良好。

增加模型灵活性的一种方法是增加不同的 xᵢⱼ权力(即 xᵢⱼ、xᵢⱼ)等。这允许模型拟合非线性关系。这种模型被称为多项式回归模型。请注意,在未知的βⱼ和β₀.,模型仍然保持线性

2.1 简单数据集回归:

本例中使用的数据以微米为单位测量精密工具的偏心偏差,以摄氏度为单位测量工具温度。该数据集不是真实的,此处仅用于说明目的。下图显示了作为工具温度函数的工具偏转。

工具偏转与工具温度(作者提供的图)

为了保证质量,制造商希望有一个模型来预测给定工具温度下的工具变形。这个简单的单变量数据集有 1090 个数据点。工具温度被标准化为平均值 0 和标准偏差 1.0。

尽管上面所示的曲线形状表明需要一个相当高阶的多项式,但为了简单起见,我们拟合了一个 4 阶多项式模型:

上式中的 t 代表工具温度。以下笔记本的目的是展示如何使用 sci-kit learn 拟合多项式模型。由于特征向量(独立变量温度)已经标准化,因此不需要缩放。目前,为了便于说明,该模型适合整个数据集。

值得注意的是,线性回归用于拟合多项式模型。权重向量 β 可以如下获得:

下图显示了四阶多项式。

四阶多项式拟合(作者图)

虽然四阶模型不是最佳选择,但我们选择它是因为它简单,并将在后续部分中使用它进行说明。

3.0 规则化:

通常,当我们训练一个非常复杂的模型(比如说一个高阶多项式模型)时,权重会不断变化,以更接近地表示训练数据中的信息。

图:A:过度拟合模型,B:偏差-方差误差(图由作者提供)

图 1 的左窗格描述了这个场景。预测值非常接近训练数据点。模型权重对于训练数据是如此具体,以至于当新的看不见的验证数据点呈现给模型时,预测显示出与目标值的较大偏差,即较大的方差误差。图 1 的右窗格显示了随着模型的灵活性,偏置误差如何下降,方差误差如何增加。因此,希望能够在模型训练阶段控制体重的增长。正规化就是这样做的。它控制权重可以变得多大。为了实现这一点,SSE 方程增加了一个附加项。根据我们如何构造这个额外的项,我们得到一个脊或套索正则化模型。

3.1 山脊正则化:

对于岭正则化,带有额外项的 SSE 的表达式如下所示:

上述等式中的第二项是权重的平方乘以正则化参数α的和。注意,这个术语不包括截距。这个附加项表示权重向量长度的平方,也称为 L2 范数,由正则化参数缩放。它的大小取决于重量。SSE 的最小化不仅最小化了误差项,而且最小化了权重向量的缩放幅度,即权重。我们可以通过改变正则化参数的值来控制这个附加项被罚的程度。想象一个极端情况,α如此之大,以至于第一项(误差项)与第二项相比相形见绌,我们可以完全忽略它。在这种情况下,最小化将迫使权重非常小。因此,通过增加α的值,我们可以降低模型的灵活性(通过迫使权重变小)并防止过度拟合。

回到矩阵和向量符号,上面的等式可以写成

I ₀是一个(n+1) X (n+1)矩阵,除了最后一个对角线位置的值为 0 之外,所有对角线位置的值都为 1。

该矩阵用于排除截距项β₀.的影响对矢量 β 取标量函数 wrt 的导数,得到 :

求解上述方程系统的 β 得出:

从上面我们可以看出,脊正则化是在 XX 的对角项中加入了正则化参数。因此,在岭正则化中,所有权重都以相同的比例改变。我们还观察到,α与 β 成反比,即,随着α增加 β 减少。最后,将α设置为零会得到我们在 2.0 节中给出的线性回归解。

3.2 拉索规则化:

对于套索正则化,SSE 表达式为:

第二项现在涉及权重的绝对值(L1 范数)乘以正则化参数的总和。用权重的 L1 范数来增加 SSE 的想法非常类似于岭正则化。这里,通过正则化参数惩罚权重允许我们控制权重的增长。

使用矩阵和向量符号,上述等式可以表示为:

表示两个向量的内积。 1₀ ,是 1 的 n+1 个向量,最后一个分量为 0。

引入它是为了排除截距项的贡献。符号| β |表示向量的各个分量取绝对值。上述标量值函数 wrt 向量 β 的导数得出:

其中 sgn()是应用于向量 β 的各个分量的正负号函数。请注意,因为 signum()函数在其参数为零时没有定义,所以上面的表达式在权重正好为零时没有定义。与岭正则化不同,我们不能从上面的方程推导出解的解析表达式。最小值以数字形式执行。类似于脊正则化,正则化参数α的增加导致权重的减少。但是,套索正则化控制权重的方式不同于山脊正则化。我们接下来看看这些差异。

4.0 山脊 Vs 套索正则化:

在上一节中,我们看了山脊和套索正则化的数学形式。我们还根据正则化参数导出了脊正则化的解。虽然脊和套索正则化在形式上相似,但它们的功能不同。在这里,我们将使用简单的数据集和我们在第 2 节中介绍的四阶多项式来查看脊和套索正则化如何控制模型的灵活性。我们将使用 scikit 学习库来设置管道,以使用脊和套索模型来拟合 4 阶多项式。我们再次指出,特征变量“温度”已经标准化为零均值和单位标准差。

在下图所示的笔记本中,管道由一个四阶多项式和岭正则化模型组成。为了进行比较,我们构建了三个不同的模型,分别对应于正则化参数值 9000.0(大)、100.0(中)和 0.0(零),分别命名为 polyRdgLarge、polyRdgMed 和 polyRdgZero。尽管这些值有些武断,但它们足以证明模型中的差异。在后面的部分中,我们将介绍一种更正式的参数选择方法。因为我们的目标是演示正则化参数如何影响模型权重,所以整个数据集用于模型训练。

表 1:岭回归权重及其 L2 范数

表 1 显示了标记为大、中和零的三个正则化参数的权重。为了完整性,截距也显示在表中。表中最后一行所示的 L2 范数不包括截距。在上一节中,我们提到正则化会降低权重的 L2 范数。从表中可以看出,随着正则化参数α的增大,L2 范数减小。权重随着 L2 范数的减小而减小。图 2 绘制了三个正则化参数的三条四阶多项式曲线。从图中可以看出,随着正则化参数的增加,模型的灵活性降低,即曲线变得更平坦。

图 2:山脊规则化(作者提供的图片)

我们拟合了三个 Lasso 正则化模型 polyLassoMed、polyLassoMed 和 polyLassoZero,分别对应于三个正则化值 5000.0(大)、500.0(中)和 0.0(零)。注意 polyLassoZero 和 polyRdgZero 型号是相同的。再次,三个正则化参数值是任意选择的。与岭正则化的情况类似,整个数据集用于拟合三个模型。

表 2:套索正则化权重及其 L1 范数

上表显示了三种套索模型的权重。最后一行显示的 L1 范数不包括表中显示的截距。类似于脊正则化,L1 范数以及相应的权重随着正则化参数的增加而减小。从图 3 中,我们看到曲线变得更平坦,即模型灵活性随着正则化参数的增加而降低。

图 3:套索调整(作者提供的图片)

虽然岭回归和套索回归的总体行为是相似的,但在表 1 和表 2 中可以看出这两种方法之间的一个重要区别。我们可以看到,套索正则化的一些模型权重正好为零。即使正则化参数设置为较大的值,岭回归的权重也不会恰好为零。下一节将解释山脊正则化和套索正则化之间的区别。

4.1 作为约束最小化的脊和套索正则化

在第 3.0 节中,我们将山脊和套索正则化视为 SSE 扩充形式的最小化。在这一节中,我们以不同的形式重新描述这个问题,以使我们能够理解为什么对于套索正则化,一些权重恰好为零。

在这个替代公式中,通过最小化受约束的 SSE 来获得权重。

对于山脊规则化:

对于套索正则化:

上述等式中的参数“s”与正则化参数相关。首先,我们研究上交所术语。为简单起见,我们考虑一个模型,其中上证综指仅依赖于两个权重β₁和β₂.假设截距项β₀为零。这种简化使得视觉解释变得容易。

图 4: SSE 等高线(作者提供的图)

图 4 显示了β₁-β₂平面中 SSE 的轮廓。图 4 中任何给定曲线上的所有点都具有相同的 SSE 值。最小 SSE 值(由β^表示)通过最小化 SSE 获得,如第 2 节所述。扩张的等高线代表较大的 SSE 值。

脊正则化的约束项在β₁-β₂平面中由半径为 s 的圆表示,如图 5 所示。圆的阴影区域包含满足约束的所有点。

图 5 山脊约束(作者提供的图片)

在第 3 节中,我们提到,一般来说,权重随着正则化参数α的增加而减小。因为 s 是圆在β₁-β₂平面中的半径,所以较小的权重导致较小的半径。因此,参数 s 与正则化参数α成反比。

套索正则化的约束条件在β₁-β₂平面中用菱形表示,如图 6 所示。

图 6:套索约束(作者提供的图片)

菱形内的阴影区域包含满足约束的所有点。菱形的大小由参数 s 控制。参数 s 与正则化参数α成反比。现在我们已经分别可视化了 SSE 和约束条件,让我们将两者放在一起。

满足脊线约束的 SSE 的最小值是轮廓和定义脊线约束的圆之间的交点。

图 7:岭正则化的重量缩减(作者提供的图片)

图 7 中的虚线圆对应于正则化参数的不同值。当α的值接近零时,圆足够大,可以包含最小化 SSE 的最小β^obtained,而没有惩罚项。随着α值的增加,约束圆的尺寸减小,轮廓曲线和圆之间的交点移动。虚线箭头示出了随着正则化参数值的增加,解向原点收缩。这就是为什么正则化模型也被称为收缩模型。

同样的推理也可以应用于套索正则化,如图 8 所示。

图 8:套索收缩(图由作者提供)

对于套索正则化,最小解是轮廓接触菱形的点。

到目前为止,我们已经给出了脊和套索回归如何缩小模型权重的几何解释。

图 9:山脊 Vs 套索(作者提供的图片)

图 9 并排显示了山脊和套索正则化的轮廓和约束区域之间的交点。我们可以看到,因为套索的菱形有一个角,所以轮廓曲线可以与菱形正好在角上相交。因为菱形的角在轴上,所以另一个权重的值为零。图 9 的右窗格显示了这种情况。我们看到相交发生在β₂轴上,即对于这个最小点β₁ = 0。相比之下,脊约束是一个圆(即没有角),因此轮廓和圆的交点不太可能落在轴上。这就是为什么套索正则化的一些权重恰好为零的原因。虽然这里提供的解释只考虑了两个权重β₁和β₂,但是该思想可以扩展到具有更多权重的复杂模型。

4.2 弹性网:

山脊和套索正则化各有优势。由于一些权重正好为零,因此套索正则化模型是一个更简单、更容易解释的模型。另一方面,在某些情况下,岭回归可以生成 SSE 较低的模型。一般来说,对于给定的数据集,很难说这两种方法中哪一种效果最好。如果权重较大的要素较少,套索正则化效果会很好。对于具有相似大小权重的许多要素的模型,岭回归效果很好。那么问题自然就来了,我们能不能结合这两种模型来利用这两种方法呢?

弹性网就是这样做的。它结合了两种收缩模型的加权版本:

Elastic-Net = γ*Lasso 约束+ (1 — γ) *Ridge 约束。

参数γ对两个模型的贡献进行加权。实际上,惩罚参数和权重参数的最佳值是使用交叉验证来确定的。

5.0 一个简单的正则化例子:

选择正则化参数的好值的强力方法是尝试不同的值来训练模型并检查测试集的预测结果。这是一种麻烦的方法。通过 Scikit learn 中的 GridSearchCV 模块,我们可以建立一个管道并在参数网格上运行交叉验证,以自动选择最佳参数。我们使用加州房价数据集来演示这一过程。数据集可以在这里找到:https://www.kaggle.com/camnugent/california-housing-prices

该数据集包含 1990 年以来加州的房价。为了简化我们的模型,我们从原始数据集中删除了“ocean_proximity”列。剔除丢失的数据点后,我们剩下 20433 个数据点。

我们的目标是建立一个模型来预测中值房价,即我们的目标列是“中值房价”。我们还剔除了中值超过 50 万美元的房屋。不同特性和我们的目标列的分布如下所示:

(图由作者提供)

我们的目的是使用 GridSearchCV 演示参数优化。我们跳过了对理解数据和构建良好的预测模型至关重要的详细数据探索。

数据集分为训练集和测试集。管道包括数据的标准缩放、设置多项式回归和弹性网络。paramsPolyEN 字典允许我们为管道中的不同组件设置一个参数网格。这里我们只尝试一阶和二阶多项式。该字典还包含范围从 400.0 到 600.0 的正则化参数值列表,以及范围从 0.5 到 1.0 的套索与脊的权重比列表。可以指定更精确的参数列表,以获得更优的模型。GridSearchCV 使用管道中指定的估计器以及参数值网格来运行 n 重交叉验证,以实现最佳模型。这里,以 R2 评分作为模型评估的标准,进行 6 重交叉验证。

GridSearchCV 的结果可以在上面看到。我们看到,正则化参数为 500.0 的套索正则化二阶模型(L1 比率=1.0)是指定列表中的最佳模型。测试数据的 R2 分数是 0.76。套索模型的权重如下所示。我们可以观察到有几个权重正好为零。值得注意的是,增加多项式次数会导致模型非常复杂,难以解释。

感兴趣的读者可以使用下面提供的代码来试验更大的参数网格,以发现更优的解决方案。

6.0 结论:

在本文中,我们讨论了正则化的数学和几何解释。当我们使用线性回归进行推导时,正则化与更灵活的技术一起使用,例如深度神经网络。这个想法和这里讨论的一样。使用一个简单的单变量例子,我们研究了正则化如何控制权重的增长,从而控制模型的灵活性。这减少了方差误差,提高了模型的通用性。另一个公式用于说明套索正则化和脊正则化之间的差异。使用流水线、参数网格字典和 GridSearchCV 是寻找最优正则化参数的有效方法。我们使用加州住房数据集来演示这种方法。正则化是机器学习中非常有用的工具,用于调整高度灵活的模型。

7.0 参考文献:

1.https://sci kit-learn . org/stable/modules/linear _ model . html #普通最小二乘法

2.https://sci kit-learn . org/stable/modules/generated/sk learn . linear _ model。ElasticNet.html

3.https://www.kaggle.com/camnugent/california-housing-prices。(许可:通用公共领域)

4.《R 语言中的统计学习及其应用导论》,作者:James,Witten,Hastie 和 Tibshirani。

神经网络的正则化技术

原文:https://towardsdatascience.com/regularization-techniques-for-neural-networks-379f5b4c9ac3

当训练深度神经网络时,在训练集和验证集上实现相同的性能通常很麻烦。验证集上相当高的误差是过度拟合的明显标志:网络在训练数据中变得过于专门化。在本文中,我提供了一个关于如何绕过这个问题的综合指南。

来源:pixabay.com

神经网络中的过拟合

在处理任何机器学习应用程序时,对模型的偏差和方差有一个清晰的理解是很重要的。在传统的机器学习算法中,我们谈到了偏差与方差权衡,这包括试图最小化模型的方差和偏差时的斗争。为了减少模型的偏差,即减少错误假设的误差,我们需要一个更复杂的模型。相反,减少模型的方差,即模型在捕捉训练数据的变化时的灵敏度,意味着更简单的模型。很明显,在传统的机器学习中,偏差与方差的权衡来自于同时需要更复杂和更简单的模型的冲突。

在深度学习时代,有一些工具可以只减少模型的方差而不损害模型的偏差,或者相反,减少偏差而不增加方差。在探索用于防止神经网络过度拟合的不同技术之前,澄清高方差或高偏差的含义是很重要的。

考虑一个常见的神经网络任务,如图像识别,并考虑一个识别图片中熊猫存在的神经网络。我们可以自信地估计,人类可以以接近 0%的误差完成这项任务。因此,这是图像识别网络准确性的合理基准。在训练集上训练神经网络并评估其在训练集和验证集上的性能后,我们可能会得出这些不同的结果:

  1. 训练误差= 20%,验证误差= 22%
  2. 训练误差= 1%,验证误差= 15%
  3. 训练误差= 0.5%,验证误差= 1%
  4. 训练误差= 20%,验证误差= 30%

第一个例子是高偏差的典型实例:训练集和验证集的误差都很大。相反,第二个例子的方差很大,在处理模型没有从中学习的数据时,准确性要低得多。第三个结果代表低方差和偏差,模型可以被认为是有效的。最后,第四个示例显示了高偏差和方差的情况:与基准相比,不仅训练误差大,而且验证误差也更高。

从现在开始,我将介绍几种正则化技术,用于减少模型对训练数据的过度拟合。它们对于情况 2 是有益的。第四。前一个例子。

神经网络的 L1 和 L2 正则化

类似于经典回归算法(线性、逻辑、多项式等。),L1 和 L2 正则化也为防止高方差神经网络中的过拟合找到了位置。为了保持这篇文章简短切题,我不会回忆 L1 和 L2 正则化是如何在回归算法上工作的,但是你可以查看这篇文章以获得更多信息。

L1 和 L2 正则化技术背后的思想是将模型的权重限制得更小,或者将其中一些缩小到 0。

考虑经典深度神经网络的成本函数 J:

当然,成本函数 j 是每一层的权重和偏差的函数,L. m 是训练样本的数量,ℒ是损失函数。

L1 正则化

在 L1 正则化中,我们将以下项添加到成本函数 J 中:

其中矩阵范数是网络的每一层 1,…,L 的权重的绝对值之和:

λ是正则项。这是一个必须仔细调整的超参数。λ直接控制正则化的影响:随着λ增加,对权重收缩的影响更加严重。

L1 正则化下的完整成本函数变成:

对于λ=0,L1 正则化的效果为零。相反,选择太大的λ值会过度简化模型,很可能导致网络不匹配。

L1 正则化可以被视为一种神经元选择过程,因为它会使一些隐藏神经元的权重为零。

L2 正则化

在 L2 正则化中,我们添加到成本函数的项如下:

在这种情况下,正则项是每个网络图层权重的平方范数。这个矩阵范数称为 Frobenius 范数,具体计算如下:

请注意,相对于层 l 的权重矩阵具有 n^{[l]}行和 n^{[l-1]}列。

最后,L2 正则化下的完整成本函数变成:

同样,λ是正则项,对于λ=0,L2 正则化的效果为零。

L2 正则化使权重值趋于零,从而产生更简单的模型。

L1 和 L2 正则化如何减少过度拟合?

L1 和 L2 正则化技术对训练数据的过拟合有积极影响,原因有两个:

  • 一些隐藏单元的权重变得更接近(或等于)0。结果,它们的作用被削弱了,最终的网络更简单了,因为它更接近于一个更小的网络。如介绍中所述,较简单的网络不容易过度拟合。
  • 对于较小的权重,隐藏神经元的激活函数的输入 z 也变得较小。对于接近 0 的值,许多激活函数表现为线性。

第二个原因不是微不足道的,值得扩展。考虑双曲正切(tanh)激活函数,其图形如下:

双曲正切值

从函数图中我们可以看出,如果输入值 x 很小,函数 tanh(x)几乎呈线性。当 tanh 用作神经网络隐藏层的激活函数时,输入值为:

这对于小重量 w 也接近于零。

如果神经网络的每一层都是线性的,我们可以证明整个网络的行为是线性的。因此,约束一些隐藏单元来模拟线性函数,导致更简单的网络,并且因此有助于防止过度拟合。

一个更简单的模型通常是不能捕捉训练数据中的噪声,因此过拟合不太频繁。

拒绝传统社会的人

退出正则化的思想是随机删除网络中的一些节点。在训练过程之前,我们为网络的每个节点设置一个概率(假设 p = 50%)。在训练阶段,每个节点被关闭的概率为 p。退出过程是随机的,并且针对每个训练示例单独执行。因此,每个训练示例可能在不同的网络上训练。

至于 L2 正则化,丢弃正则化的结果是更简单的网络,更简单的网络导致更不复杂的模型。

简单网络中的掉线效应。来源:作者

实际辍学

在这个简短的部分中,我将展示如何在实践中实现辍学正规化。我将通过几行简单的代码(python)。如果你只对正则化的一般理论感兴趣,你可以很容易地跳过这一节。

假设我们已经在 numpy 数组a4中存储了网络第 4 层的激活值。首先,我们创建辅助向量d4:

# Set the keeping probability
keep_prob = 0.7# Create auxiliary vector for layer 4
d4 = np.random.rand(a4.shape[0], a3.shape[1]) < keep_prob

向量d4具有与a4相同的维数,并且包含基于概率keep_prob的值TrueFalse。如果我们设置 70%的保持概率,这是给定的隐藏单元被保持的概率,因此,在给定的d4元素上具有True值的概率。

我们将辅助向量d4应用于激活a4:

# Apply the auxiliary vector d4 to the activation a4
a4 = np.multiply(a4,d4)

最后,我们需要用keep_prob值来缩放修改后的向量a4:

# Scale the modified vector a4
a4 /= keep_prob

需要这最后一个操作来补偿层中单元的减少。在训练过程中执行该操作允许我们在测试阶段不应用退出。

辍学如何减少过度拟合?

退出的效果是暂时将网络转换成一个更小的网络,我们知道更小的网络不太复杂,也不容易过度拟合。

考虑上面所示的网络,重点关注第二层的第一个单元。因为它的一些输入可能会由于退出而暂时关闭,所以该单元在训练阶段不能总是依赖它们。因此,隐藏单元被鼓励将它的权重分散到它的输入上。扩展权重具有降低权重矩阵的平方范数的效果,导致一种 L2 正则化。

设置保持概率是有效的丢失正则化的基本步骤。通常,为神经网络的每一层分别设置保持概率。对于具有大权重矩阵的层,我们通常设置较小的保持概率,因为在每一步,我们希望相对于较小的层保持成比例的较少权重。

其他正则化技术

除了 L1/L2 正则化和退出,还有其他正则化技术。其中两个是数据扩充和提前停止。

从理论上,我们知道在更多的数据上训练网络对减少高方差有积极的作用。由于获取更多数据通常是一项艰巨的任务,对于某些应用程序来说,数据增强是一种允许机器学习实践者几乎免费获取更多数据的技术。在计算机视觉中,数据增强通过翻转、缩放和平移原始图像来提供更大的训练集。在数字识别的情况下,我们也可以对图像施加失真。您可以检查手写数字识别数据增强的应用

早期停止,顾名思义,包括在最初定义的迭代次数之前停止训练阶段。如果我们将训练集和验证集上的成本函数绘制为迭代的函数,我们可以体验到,对于过拟合模型,训练误差总是保持减小,但是验证误差可能在一定次数的迭代之后开始增大。当验证误差停止减小时,正是停止训练过程的时候。通过更早地停止训练过程,我们迫使模型更简单,从而减少过度拟合。

用脊线、套索和橡皮筋来调整你的回归模型

原文:https://towardsdatascience.com/regulate-your-regression-model-with-ridge-lasso-and-elasticnet-92735e192e34

什么是正则化技术,我们为什么要使用它们?提供了一个 Python sklearn 示例。

迈克·考克斯在 Unsplash 上的照片

线性模型有广泛的吸引力。即使对 Excel 有基本的了解,也可以创建解释数据模式的模型。将权重(系数)附加到解释变量(特征)后,在解释数据时就很容易评估单个变量的重要性。毫不奇怪,线性模型已经存在了几十年,并广泛应用于许多领域,从心理学到商业管理,从机器学习到统计学。

尽管线性模型表面上很简单,但许多事情可能会出错。两个特别常见的问题是:

  • 过拟合。尤其是当具有包含许多解释变量的大型模型时,存在使模型适应噪声的趋势。R 值在训练数据上可能看起来很好,但模型在样本外数据上的表现会差得多。
  • 多重共线性:在设计解释变量时,它们有可能相互关联(想想卧室的数量和房子的面积)。强烈的多重共线性使得分配适当的权重成为问题。

如果目标是使模型适用于样本外预测,就有解决方案。(注意:对于统计干扰,它们没有什么意义)本文讨论了三种常见的正则化技术,用于解决上述问题并创建更鲁棒的预测模型。如果你只是想看 Python 实现(使用 sklearn ),跳到最后。

线性回归

当我们讨论线性回归时,我们的意思是模型作为统计估计问题是线性的。线性模型可以简单到y_salary=θ*x_years_experience+ϵ(用θ权重、x特征、y因变量、ϵ随机噪声),也可以复杂到高次多元多项式。尽管基本概念保持不变:模型是解释变量的线性组合。我们通过以最小化模型误差的方式估计权重来拟合模型。

最常用的回归程序(普通最小二乘法或 OLS)是直观的,包含在许多工具和库中。对于每个数据点,我们可以通过模型计算观察值和预测值之间的差异(误差)。通过拟合直线使得平方误差最小化,平均而言,该模型可以相当好地解释每个数据点。

普通最小二乘回归以最小化预测和观察之间的平方误差的方式选择权重θ。

技术提示:假设模型本身是正确的,数据点的偏差是随机噪声的结果。

在线性回归中,通过最小化观察值(红点)和模型(蓝线)之间的差异(MSE)来拟合直线。[图片来自维基媒体作者 Oleg Alexandrov

这种情况下用单个解释变量(简单线性回归,y=θ*x+ϵ)很容易直观地表现出来。通常,我们处理多个变量(多元线性回归,由y=∑_i θ_i*x_i+ϵ定义)。包含许多(潜在)解释变量的大型数据集是最直接的例子。

事实上,我们可以设计任意复杂的特征,例如,当处理高次多项式时。变量可以被变换(例如,将一个变量平方)或组合(例如,将两个变量相乘)。创建任意大的特征集来生成非常大的回归模型并不需要太多的想象力。

虽然不是唯一的潜在原因,高维模型(即包含许多特征)往往会引入上述多重共线性和过拟合问题。许多特征可能(在很大程度上)解释了相同的影响,或者某些特征可能被错误地分配来解释某些异常值。此外,许多特征可能几乎没有解释力。

所有这些问题都不是 OLS 回归所能解决的,它只是最小化了误差的平方。相反,我们需要稍微引导一下我们的模型,这就是正则化的作用。

正规化

统计模型在偏差和方差之间有一个内在的权衡。在这种情况下,偏差描述了源自错误假设的误差,而方差指的是由于对噪声敏感而导致的误差。

虽然这里过于简化了,但是高方差通常伴随着多重共线性和/或过度拟合。规则化有助于减少方差,代价是(有意)引入一些偏差。虽然在训练集上可能比常规回归具有更低的 R 分数,但是当偏差和方差之间达到更适当的平衡时,该模型在样本外 数据上可能表现得更好。

偏差和方差之间的权衡。与普通最小二乘回归相比,正则化模型故意增加偏差以减少方差。最终,目标是最小化总误差[图片来自WikiMediabyBigbossfarin

本文讨论了三种最常见的正则化技术:套索、脊和弹性网。

套索正则化

LASSO 是“最小绝对收缩和选择算子”的缩写。完整的标题实际上是相当描述性的:它旨在通过应用绝对惩罚来选择最强的解释变量。它也被称为 L1 正则化。

套索正则化对权重θ的绝对和乘以用户确定的常数λ进行惩罚

LASSO 主要在处理大量解释变量时有用,我们怀疑其中许多变量没有什么相关性。然后可以应用 LASSO 将权重设置为 0,并将我们的模型缩减为一个更小、更全面的模型。该过程可以迭代地执行(例如,每一步增加λ),逐渐减小模型的尺寸。

LASSO 的一个潜在缺点是,它倾向于从一组相关变量中只选择一个变量。虽然解决了多重共线性问题,但该模型也可能会以这种方式失去预测能力。此外,当处理以许多解释变量和很少数据点为特征的数据集时(也称为“大 p ,小 n 的情况),LASSO 将选择最多 n 变量。

总之,LASSO 在缩小回归模型的规模方面可能相当激进,因此应该谨慎使用。

岭正则化

罗得西亚脊背龙。照片由 Gene JeterUnsplash 上拍摄

虽然表面上看起来与套索相似,但脊正则化(也称为吉洪诺夫正则化或 L2 正则化)具有明显不同的效果。它还对 OLS 公式应用惩罚,但是惩罚的是平方权重,而不是绝对权重:

岭正则化惩罚权重的平方和θ乘以用户确定的常数λ

效果比你想象的要强烈。提醒一下,20 =40,2 =4,0.2 =0.04。因此;岭严重惩罚大重量,但小重量实际上是有益的。因此,岭正则化倾向于在整个要素范围内分配许多小的(非零)权重。

你可以想象一个有几个非常高的权重的模型会对相应变量的变化做出剧烈的反应。通过更均匀地分配权重,岭正则化产生了一个“更平滑”的模型,因此得名该过程。

通常,当处理相对较少的解释变量时,我们会使用岭回归。与套索不同,它对特征选择没有帮助。岭回归的主要好处是它倾向于显著减少方差

弹性网正则化

和生活中的许多事情一样,平衡是关键。L1 和 L2 的点球都有特定的目的,但是有时候我们两者都想要一点。更具体地说,我们可能希望减少特征的数量,但也希望在剩余的特征之间更稳健地分布权重。这就是弹性网正则化的作用。它只是将 L1 和 L2 的处罚加在一起,并赋予一定的权重:

弹性网正则化包括 L1 和 L2 惩罚,由用户确定的常数λ_1 和λ_2 加权

注意λ_1λ_2可以由用户设置,使得能够平衡 L1 和 L2 。在极端情况下,您可以应用纯套索回归或岭回归。理想情况下,弹性网络正则化能够选择好的特征并为它们创建鲁棒的模型。然而,它也容易受到套索和山脊的不利影响。弹性网正则化的简单应用会产生很差的结果。

sklearn 示例

获得一些建模技术的实践经验总是最好的,所以我使用sklearn库添加了一个简短的 Python 示例。内置的加州住房数据集包含 8 个特征(例如,靠近海洋、卧室数量)和每个地区的房价中值。请注意,我只是使用原始数据集,没有任何调整工作或迭代过程——当然,您可以做得比这好得多。

样本外预测的实际改进超出了这里的范围(即,结果不是很好…),但是预期的行为在回归系数中是可见的:

`=No regularization=
Weights:
[ 4.33102288e-01 9.32362843e-03 -1.00332994e-01 6.15219176e-01
-2.55110625e-06 -4.78180583e-03 -4.29077359e-01 -4.41484229e-01]
MSE: 0.544
R²: 0.601

=LASSO regularization=
Weights:
[ 1.45469232e-01 5.81496884e-03 0.00000000e+00 -0.00000000e+00
-6.37292607e-06 -0.00000000e+00 -0.00000000e+00 -0.00000000e+00]
MSE: 0.974
R²: 0.286

=Ridge regularization=
Weights:
[ 4.36594382e-01 9.43739513e-03 -1.07132761e-01 6.44062485e-01
-3.97034295e-06 -3.78635869e-03 -4.21299306e-01 -4.34484717e-01]
MSE: 0.543
R²: 0.602

=Elastic net regularization=
Weights:
[ 2.53202643e-01 1.12982857e-02 0.00000000e+00 -0.00000000e+00
9.63636030e-06 -0.00000000e+00 -0.00000000e+00 -0.00000000e+00]
MSE: 0.786
R²: 0.424`

我们看到岭回归使小系数变大了一点。与标准回归相比,R 稍好一些;均方误差略小。套索和弹性网将许多系数设置为 0,表现明显不佳;显然,许多有用的功能都被删除了。

结果表明,盲目地应用正则化技术并不一定会大幅提高你的预测性能;恰恰相反。正规化理论上的好处是显而易见的,但要谨慎行事。

关键要点

  • 尤其是在处理大量特征(如高维数据)时,过拟合多重共线性会在线性回归中造成相当大的问题。
  • 正则化技术通过惩罚权重分配来帮助创建更鲁棒的模型,当正确应用时,通常会产生更好的样本外预测。它们不适合统计干扰。
  • 正则化惩罚被添加到 OLS 回归函数中。与 OLS 相比,他们故意引入偏差以减少方差,使模型对噪音和异常值不那么敏感。
  • 套索 (L1)正则化惩罚绝对权重之和。因此,许多权重被设置为等于 0。LASSO 有助于从大集合中选择显著特征并创建更简单的模型,但也可能会大大降低预测能力。
  • 山脊 (L2)正则化惩罚平方权重之和。结果是,它比 OLS 更倾向于平均分配重量,对某些特征的变化不太敏感。它对选择特征没有帮助。
  • 弹性网正则化结合了 L1 和 L2 惩罚与一定的权重。在最好的情况下,它产生了紧凑和稳健的回归模型,但它也有许多潜在的缺点。
  • 正则化技术的简单应用可能会大大降低回归模型的质量。通常,需要迭代过程和某些转换来充分利用它们。

考虑申请正规化?一定不要犯这种常见的错误:

对回归感兴趣?你可能也想阅读多项式回归:

参考

https://en.wikipedia.org/wiki/Lasso_(statistics) https://www.engati.com/glossary/ridge-regression https://en.wikipedia.org/wiki/Bias–variance_tradeoff#/media/File:Bias_and_variance_contributing_to_total_error.svg https://en.wikipedia.org/wiki/Linear_regression

强化学习:导论

原文:https://towardsdatascience.com/reinforcement-learning-an-introduction-a8783f9ea993

机器学习基础

强化学习:导论

强化学习的基础介绍,所有你需要知道的开始

图片由 shutterstock 的 Andrey_Popov 提供

在 9 个小时内,谷歌的 AlphaZero 从只知道国际象棋的规则变成了击败世界上最好的模型。人类研究国际象棋已经超过 1000 年了,然而强化学习模型能够在可忽略不计的时间内加深我们对游戏的了解,除了游戏规则之外,不使用任何先验知识。没有其他机器学习领域允许在这个问题上取得这样的进展。今天,谷歌的类似模型正在广泛应用于各种领域,如预测和检测改变生活的疾病的早期迹象,改善文本到语音转换系统等。

这篇文章

机器学习可以分为 3 种主要范式。这些是监督学习、非监督学习、强化学习。你们中的大多数人可能知道很多关于监督和非监督学习的知识,但是第三个分支同样重要。最近强化学习引起了很多关注,正确理解它的基本原理是很重要的。强化学习可能有点令人望而生畏,因为即使是基础知识也有点复杂。

在这篇文章中,我将试图让你了解强化学习理论。在我的下一篇文章中,我将把我在这篇文章中展示的算法应用到一个真实的问题中,所以请继续关注!

基础知识

代理、环境、状态、奖励

作者图片

强化学习模型从环境中学习。环境有一套规则,通常被认为是确定性的。强化学习模型通过代理与环境交互。代理在环境中有一个状态,并且代理可以执行改变环境中代理状态的动作

我们来看一个象棋机器人的例子:环境是棋盘,代理是象棋机器人,环境的状态是所有棋子的位置。给定棋盘的状态,只能进行有限数量的合法移动(动作),这是由环境决定的。例如,国王不能像王后一样移动。

当代理采取一个动作时,环境将接收这个动作作为输入,并输出结果状态和奖励(见上图)。在国际象棋的例子中,在代理人移动一个棋子后,环境将返回这个移动和对手移动后棋盘的样子,这样就又轮到代理人了。环境也会回报一份奖励,比如说,如果你捕获了一块。

这些模型是如何学习的?

经典的机器学习算法将很难从上述问题设置中学习。那么,我们如何通过与环境互动来教会模型从环境中学习呢?

在其他机器学习问题中,我们通常从定义损失函数开始,然后我们寻求优化。在强化学习中,我们不能立即做到这一点。为了帮助我们确定损失,我们可以从环境给予的回报开始。

回到象棋的例子,很明显,我们希望机器人赢得象棋比赛。然而,只在模型赢得比赛时奖励它是不切实际的(马尔可夫决策过程)。该模型将很难一步一步地学习,这使得训练过程缓慢并且可能不收敛。我们也想要短期回报,这可能就像在国际象棋的例子中捕捉一个棋子一样(动态编程)。

价值、奖励、政策

让我们概括一下目前所看到的情况。智能体通过动作与环境交互,这些动作改变环境的状态。该模型的目标是确定什么行动将导致最大的回报。

为了确定最佳行动,强化学习通过估计行动的价值来工作。一个动作的值表示一个动作有多好,例如,一步棋有多好。所有的强化学习都围绕着这个估算最优价值函数的思想。

价值:行动的价值被定义为采取行动获得的直接回报加上结果状态的预期值乘以一个比例项的总和。换句话说,一个行动的价值是采取这个行动后下一个状态会有多好,加上从这个新状态预期的未来回报。

强化学习模型通过与环境交互、选择动作、观察新状态、观察回报然后更新来更新它们的价值函数。

除了价值函数,模型还需要学习一个策略。

策略:算法的策略是它如何基于当前状态的值选择采取什么动作。

强化学习算法希望尽可能最好地评估状态(价值函数),以帮助它们做出导致最大回报的决策(政策)。

选择策略

那么我们如何根据行动的价值来选择采取什么行动呢?人们可以定义一个贪婪的策略,总是选择具有最高直接回报的行动。正如我之前所讨论的,仅仅着眼于眼前的回报并不一定会带来长期的回报(在国际象棋中,总是走一步棋会带来更高的眼前回报,但可能不是最好的一步)。我们需要使用价值函数来考虑未来状态的预期回报。

所以最大化直接回报是行不通的,但是第二个政策怎么样呢?T2 总是采取价值最高的行动。请记住,我们正在尝试学习价值函数。采取具有最高值的动作将导致模型陷入局部最小值。如果我们当前的价值函数不是最优的,我们总是选择具有最高价值的行动,那么模型可能永远不会看到会产生更大回报的行动。

为了改进模型对价值函数的估计,模型必须探索。优化价值函数需要勘探和开发之间的良好平衡。剥削是指根据价值函数采取最佳行动(采取我们认为是最佳的举动)。探索是指采取随机行动,而不是价值函数建议的行动,这使我们能够探索其他状态,增加随机性,提高模型的性能。

过多的开发和模型会陷入局部极小值,过多的探索和模型根本不会收敛。

估计价值函数

强化学习都是关于价值函数(我们的状态/行动有多好)。这些模型通过与环境结合来连续学习多集。情节可以被认为是强化学习中的时代,在国际象棋的例子中,它们将指示代理训练的完整游戏的数量。

表格离散化和时间差分学习

学习最佳值函数的贪婪方法是离散化状态空间,然后检查每个状态(在离散化空间中)的每个可能动作的值,并选择具有最高值的动作。

离散化就是把一个连续的空间变成一个离散的空间。例如,如果我们想到一个赛车游戏,有(理论上)无限的速度和转角值可以输入到环境中。相反,我们统一采样汽车的速度。这些速度输入中的每一个都作为一个动作,我们可以用我们的值函数来检查这些动作的值。

为了学习价值函数,时间差分学习结合了蒙特卡罗动态规划【1】中的方法。时间差异学习从原始经验中学习(基于过去的奖励更新价值函数)以及动态更新价值函数的估计,而不需要等待一集的结束。这两种策略的结合使得强化学习如此强大。

蒙特卡洛法

蒙特卡洛方法等待一集结束后更新,它们纯粹根据经验更新,效率很低。以下是价值函数的蒙特卡洛更新:

其中 V 是价值函数,s 是给定时间的状态,alpha 是步长,G 是一集结束时的奖励。最佳值函数 V(S)将在每集结束时为每个状态匹配真实的奖励。为了确定最佳值函数,显示了上面的更新函数。

价值函数蒙特卡罗方法的更新必须等到情节的结尾(其中 G 是已知的)。想象一下,试图训练一个模型下棋,但你只能在每局棋结束时更新模型,这将是一个非常缓慢的过程!

时间差异学习

因此,我们可以根据从所选动作中收集的新信息,在每个时间步估算 G 值,而不是等到一集结束时才得到 G 值。下面是值函数的时间差异学习更新:

其中 R 是根据策略采取行动后的即时回报,S’是根据策略采取行动后的状态,γ是比例项。

我们所做的就是用蒙特卡罗方法中的 G 代替 R+γV(S′)。这是贝尔曼方程,它有助于强化学习。我们将 G 估计为直接回报+下一个状态的估计值乘以一个比例项。

简单回顾一下:我们的目标是让 V(S)如此完美,以至于它总是精确地估计 G。然而,我们不能等到每一集的结尾才更新 v。所以我们用采取一个行动后收集的新信息来近似 G。

请密切关注此更新规则。我们潜在变量的新估计值等于其当前估计值加上一个比例项乘以一个误差。这听起来耳熟吗?类似的更新规则在机器学习中随处可见,从像卡尔曼滤波这样的在线学习方法到像随机梯度下降这样的神经网络中的任何优化器。

梯度下降法

离散化状态空间是解决强化学习问题的一种简单但可靠的方法。然而在现实中,这很快就会变得非常昂贵。简单地存储离散化的空间,所有可能状态下的所有可能动作,如果你想要好的分辨率,需要大量的内存。这就是为什么在实践中使用梯度下降法。

梯度下降法是强化学习中使用的一些最流行的在线近似法。在机器学习的其他部分,通常通过复杂的可微函数来学习输入和输出之间的映射。同样可以在强化学习中逼近价值函数。

让我们从梯度下降法的权重更新函数开始:

其中 v hat 是近似值函数估计(我们不能放 V,因为这是真值函数)。q 是给定行动的即时回报和未来状态的更新价值估计。

该表达式类似于前面显示的时间差异学习中的更新规则。在这里,模型的权重被更新,这又更新了我们的价值函数。和以前一样。采取行动后,从新的估计值中减去旧的估计值。价值函数的梯度乘以更新,类似于随机梯度下降,其中我们沿着权重相对于损失的梯度方向来最小化。

这里的关键是要认识到,模型是通过迈出一步来学习价值函数,查看奖励,并将下一个状态的价值与前一个状态的价值进行比较。

SARSA 和 Q-Learning

我们现在知道所有关于时间差分学习和梯度下降方法来更新我们的价值函数。现在我们只需要一个算法来实现这个更新功能。

我将介绍强化学习中的两个主要算法,Q-learning 和 SARSA。Q-learning 是一种非策略算法,而 SARSA 是策略算法。(请记住,策略是我们在给定价值函数的情况下选择下一步行动时遵循的规则)。

脱离策略算法学习最优策略,而不管代理的动作,而作为策略算法学习代理正在执行的策略的最优值函数。

这两种算法对 Q 的估计是不同的(参见前面的更新规则)。Q-learning 根据下一个状态和贪婪动作(值最高的动作)学习最优 Q。其中,as SARSA 基于下一个状态学习最优 Q,并基于其策略学习动作。

让我们制定一项政策,在勘探和开发中保持良好的平衡。因此,假设概率为 1-ε时,我们采取价值最高的行动(频繁利用),而概率为ε时,我们选择随机行动(有时探索)。

在前面看到的更新规则中,Q-learning 会将 Q 估计为 R + γ max(v(S ',a)),即即时奖励+奖励最高的动作的值的动作。相反,SARSA 将应用我们的策略,并且以ε概率,它将使用随机行动的值而不是产生最大回报的行动的值来估计 Q。

下面是萨顿和巴尔托关于这两种算法的一些伪代码。

Q-Learning:萨顿和巴尔托的非策略算法[1]

Sarsa:萨顿和巴尔托的策略算法[1]

在这两个模型中,我们应用策略来定义当前状态 S 和下一个动作 a。然后我们观察奖励 R 和下一个状态 S’。这两种算法的不同之处在于更新规则和它们如何估计 Q。Q-learning 是贪婪的,并在下一个状态中使用具有最大值的动作来获取 Q。SARSA 再次应用我们的策略,本质上是评估我们的代理在该状态下的价值。

这两种算法都是有效的,并且根据不同的场景,其中一种算法会优于另一种算法。

结论

在强化学习中,我们试图找到一个模型来决定一个代理在一个环境中采取的最佳行动。这个决定必须考虑到环境如何表现,代理人的当前状态,预期的回报,以及代理人的预期未来状态。强化学习问题通常设置在在线设置上,其中在训练时不是所有的数据都可以被模型访问,因此模型必须在运行中学习,当数据进来时顺序地更新模型参数。

我希望这能帮助你理解解决强化学习问题的一些关键方法,比如表格离散化、时间差分学习和梯度下降法。

这些都将在我的下一篇文章中更有意义,我将使用 Python 训练一个强化学习模型,并将比较表格离散化、Q-Learning 和 SARSA 的性能。

支持我

希望这对你有所帮助,如果你喜欢,你可以 关注我!

你也可以成为 中级会员 使用我的推荐链接,获得我所有的文章和更多:https://diegounzuetaruedas.medium.com/membership

你可能喜欢的其他文章

卷积层与全连接层

傅立叶变换:直观的可视化

参考

[1] R .萨顿和 a .巴尔托,强化学习,第二版。麻省理工学院出版社,2018。

强化学习在山地车问题中的应用

原文:https://towardsdatascience.com/reinforcement-learning-applied-to-the-mountain-car-problem-1c4fb16729ba

机器学习的应用

我如何教一台机器玩这个简单的视频游戏

除非特别说明,所有 gif 和图片均由作者提供

增强学习问题是最有趣的机器学习问题之一。在这篇文章中,我教一个机器学习模型玩一个小游戏。

在我的上一篇文章中,我详细介绍了强化学习理论,在这篇文章中,我将把这个理论应用于山地汽车问题。我将通过训练两个模型并比较它们的性能,向您展示解决这个问题的两种主要方法。第一个通过表格离散化训练,第二个通过梯度下降法训练。

1.山地汽车问题

问题设置

GIF。1:山地车问题

以上是山地汽车问题的 GIF(如果你看不到它,试试桌面或浏览器)。我用了 OpenAI 的运行游戏环境的名为 gym 的 python 库。

汽车在两座小山之间启动。目标是让汽车到达右边的山顶。这辆车没有足够的发动机功率通过加速到达山顶。为了获胜,汽车必须通过前后摆动来增加动力,直到它有足够的速度到达旗子。

代理、动作、环境、状态

这辆车是我们的强化学习代理 它与环境相互作用,采取行动;玩游戏。

环境相当简单,我们只需要知道汽车在每一个状态下的两件事,它们是它的位置和它的速度。这个问题是二维的,它的状态空间(具有所有可能的位置和速度的空间)也是二维的。

汽车在每种状态下只有 3 种可能的动作,它可以向前加速,向后加速,或者什么也不做。每次代理采取一个动作,环境(游戏)都会返回一个新的状态(一个位置和速度)。

让我们举个例子,汽车从中间开始,它的位置可能是,速度也是。这是它的初始状态。然后,代理可能决定加速前进,并将该动作给予环境。环境将返回一个新的状态,比如位置 1,速度 1。我们模型的目标是学习在每个状态下采取什么行动来帮助它到达旗帜。

价值、奖励、政策

强化学习就是在代理做得好的时候奖励它(正强化),做得不好的时候惩罚它(负强化)。

每当汽车到达旗帜,它将获得一大笔奖励。然而,我们不想只在模型到达旗帜时奖励它,因为这将使它难以学习。我们也可以奖励每个州的代理人。例如,人们可以在每一个州按比例奖励该模型,奖励的数量与它所建立的能量成比例。汽车需要积累足够的动能(速度),然后将其转化为势能(高度)。奖励汽车每次储存的能量将有助于它学会够到旗子。

为了让我们的汽车学习在每一步采取什么行动,它将试图确定每个行动“有多好”。一个动作有多好,可以用一个状态的来量化。一个国家的价值是该国所有未来预期回报的总和。一个州的价值越高,模型认为它将从中获得的回报就越多。强化学习模型的目标是建立这个价值函数的近似值。

所以在每一个状态下,汽车都可以看到它所有可能的动作(向前加速,向后加速,或者不加速)。然后,它可以计算未来状态的期望值,并选择最佳动作。

但是选择行动并不像采取期望值最高的行动那么简单。强化学习模型的策略是选择什么行动背后的逻辑。人们不能每次都选择价值最高的行动。这是因为如果我们的价值函数是错误的,那么我们就会过多地阻碍我们模型的学习。我们必须平衡探索(尝试随机行动)和开发(采取价值最高的行动)。更多的理论可以参考萨顿和巴尔托对 RL 的介绍[1]。

估计价值函数

我将用两种方法来估计价值函数。这些是通过表格离散化方法和梯度下降方法。

我还会比较两种算法来估计价值函数。这是 Q-Learning 和 SARSA。如果你想了解更多的细节,我建议查看之前的文章,但是总的来说,Q-learning 是一个非策略算法,而 SARSA 是一个策略算法:

让我们设定一个示例策略,有 95%的概率汽车采取具有最高价值的行动,有 5%的概率汽车随机采取行动。

在采取行动时,Q-learning 和 SARSA 都会应用该政策。然而,当估计未来状态的值时,Q-learning 将总是采取具有最高值的动作,其中 as SARSA 将再次应用该策略。

2.表格离散化

表格离散化的目标是以表格形式了解所有可能状态的值。该表将包含所有可能的状态。

我们的状态空间是连续的,意味着有无限多的状态(具有无限分辨率的位置和速度)。假设我们的位置在-20 和 20 之间,其中 0 是汽车的起点。-20 到 20 之间有无限多的位置。

我们不能仅仅制作一个无限大的表来表示我们的状态空间,因此我们把它分成块。

表 1:山地车问题的离散化空间

以上是山地车问题离散化空间的表格形式。分辨率是 N。因此有 N 个可能的速度值,和 N 个可能的速度值。所以总共有 N 种可能的状态。

近似值函数:表格法

现在,我们可以通过 Q-learning 来逼近价值函数,从而确定每个状态下的最佳行动:

图 1:表格法 Q-Learning 的近似值函数

这里我把 N = 200 的空间离散化了。这是与上面相同的表格,但是我在每个框中用最高值的动作进行了着色。深蓝色表示向右加速,浅蓝色表示什么都不做,白色表示向左加速。

这个近似值函数有点像汽车的大脑。汽车将简单地做价值函数所指示的事情。

值函数非常复杂,当它在地图的右侧时,偏好向右移动,在地图的左侧,偏好向左移动。

我们来看看 value 函数是如何实时学习的(如果看不到 GIF 我建议切换到浏览器或者桌面)。

GIF。2:用列表法学习近似值函数

这个 GIF 是在 300 集期间生成的(汽车到达标志 300 次,直到它学习了这个值函数)。

一些很酷的观察:

  • 原点稍微偏离中心,
  • 默认动作是向左加速
  • 模型只更新它看到的状态
  • 这个模型似乎随着时间的推移扩大了它的观察空间
  • 你离原点越远,你的能量越高(你的位置或速度越大)。

除了选择的动作,我还可以画出数值的大小。这表明汽车处于这些状态的次数,因此让我们了解模型的可信度,以及它在哪些状态下最有信心。

图 2:通过表格方法得到的价值函数的置信图

如你所料,原点明亮,表示信心高,外围黑暗,表示信心低。有趣的是左下方的尾巴。当位置在原点附近并且非常快且为负时,模型似乎对该做什么很有信心。这可能是它如何在左边的山上到达好的高度,允许它然后向左推并且到达旗子。

3.梯度下降法

表格离散化效果很好,学习起来也很快。然而,这是一个非常简单的问题,因此状态空间很容易被离散化。当遇到更困难的问题时,在内存中存储状态空间可能是不可能的。这就是梯度下降法的用武之地。

在梯度下降法中,我们学习一个将状态空间映射到值近似值的函数。对于一个线性模型,这可以简单地通过将状态与模型的权重进行点积来完成。然而,我们不仅仅想要输入和输出之间的线性映射,我们还在寻找更复杂的东西。在这里,你可以应用任何复杂的机器学习架构,比如神经网络。相反,我应用可能是我最喜欢的一种模型,径向基函数(RBF)。点击此处了解更多信息!

简而言之,RBF 模型使用一组高斯函数将输入空间映射到更高维的空间。在这个更高维度的空间中,我们可以拟合线性模型,有效地拟合原始空间中的非线性模型。

模型的权重可以用 Q-Learning 和 SARSA 来学习。让我们看看他们的学习价值函数是什么样的:

图 3:梯度下降法的价值函数,10 个基本函数(Q 学习)

图 4:梯度下降法的价值函数,100 个基函数(Q 学习)

我只包含了 Q-Learning 值函数,因为 SARSA 和 Q-Learning 非常相似。有了 10 个基函数,您可以看到映射非常简单(几乎是线性的),而包含 100 个基函数会创建一个更复杂的映射。

请注意,在上面的图片中,我已经将状态空间离散化以可视化值函数,但我实际上可以离散化到任意大的分辨率。这是因为我们的模型正在学习状态空间和值函数之间的映射,而不是学习每个状态的最佳值(像表格方法一样)。

你可以看到近似值函数比表格方法简单得多。这是因为我使用了低维径向基函数,因此映射非常平滑是正常的。由于这种平滑度,模型的性能受到影响。如果我使用非常复杂的东西,比如深度学习神经网络,那么价值函数可能会复杂得多。

4.比较性能

下表比较了所有三种方法的性能:

表 2:RL 中表格和在线学习方法的比较

正如我们所讨论的,在线学习的训练和收敛速度较慢,但内存效率更高,因为它不必以表格形式存储状态空间。因此,表格方法根本无法很好地扩展。对于这个简单的问题他们虽然处理得很好。

培训后绩效

您可能会注意到 Q-learning 模型的培训后表现很差,您可能会想知道这是为什么。让我们来看看培训后的模特表现:

图 5:已训练模型在 2000 个动作中完成运行的百分比

这里是每个模型在 2000 个动作中完成的运行百分比。表格方法在 100%的时候都是成功的,而 as SARSA 只在 78%的时候到达标记,而 Q-Learning 只有 21%。这与 Q-learning 是非策略的事实有关,这意味着当使用该模型时,它总是选择具有最高值的动作。上面看到的值函数不够复杂,模型无法学习最佳值函数,因此我们得到一个近似值。该政策增加的随机性有助于 SARSA 在不利的情况下摆脱困境。

GIF。3:在线 Q-learning 方法未能到达标志

在上面的 GIF 中 Q-learning 在线模型卡住了。因为我们总是根据价值函数采取行动,从不随机化,所以模型可能会陷入这种情况。

趋同;聚集

这些模型一开始几乎不能解决问题,然而,在训练的最后,他们都很快解决了。

图 6:表格方法和在线学习方法的融合(动作数量与迭代次数)

在上图中,你可以看到模特在学习过程中到达终点线的动作数量。开始时,所有的模型都要做上千次动作才能到达终点。到最后,它们都是数百个,Q-Learning 和 SARSA 不那么零星。这可能与我在表格方法中使用的大学习率有关,它在以后的训练中变得平滑了。

结论

在本文中,我将强化学习应用于山地汽车问题。我比较了两种主要方法(表格方法和梯度下降方法),并解释了这些模型是如何学习的。

这两种方法的主要区别在于,在表格离散化中,我们将状态空间分割成多个块,并学习每个状态的最佳值,这意味着该模型可以学习非常复杂的映射。在梯度下降法中,我们学习状态空间和值函数之间的平滑映射。我们发现梯度下降方法中的学习值函数比表格方法简单得多,因此表现不太好。根据您的资源和您正在处理的问题,一种方法可能比另一种更适合。

支持我

希望这对你有所帮助,如果你喜欢,你可以 关注我!

您也可以成为 中级会员 使用我的推荐链接,访问我的所有文章以及更多:https://diegounzuetaruedas.medium.com/membership

你可能喜欢的其他文章

强化学习:简介

内核方法:简单介绍

参考

[1] R .萨顿和 a .巴尔托,强化学习,第二版。麻省理工学院出版社,2018。

库存优化的强化学习系列 II:多级网络的强化学习模型

原文:https://towardsdatascience.com/reinforcement-learning-for-inventory-optimization-series-ii-an-rl-model-for-a-multi-echelon-921857acdb00

建立近似策略优化(PPO)模型来优化多级供应链网络的库存运作

纳斯蒂亚·杜尔希尔在 Unsplash 上的照片

更新:本文是我的博客系列库存优化的强化学习的第二篇文章。下面是同一系列中其他文章的链接。如果你感兴趣,请去看看。

库存优化的强化学习系列 I:单一零售商的 RL 模型

库存优化强化学习系列之三:RL 模型的虚拟到真实传递

在我的上一篇文章中,我提出了一个优化单个零售商库存控制策略的 DQN 模型,其客户需求模式可以用混合正态分布来描述。与传统的( sS )库存控制策略相比,DQN 模型取得了明显更好的结果。在这篇文章中,正如我在上一篇文章的结尾提到的,我将在库存优化的 RL 方面更进一步,并专注于为更复杂的供应链网络(多级网络)构建 RL 模型。

多级网络 RL 建模的挑战

毫无疑问,为多级网络构建 RL 模型远比单个零售商复杂。复杂之处在于供应链中实体的增加导致了可伸缩性问题。在前一篇文章的单个零售商的例子中,我们只关心一个实体——零售商的库存控制策略。因此,只需要建立一个代理,其动作空间表示零售商的订货量。然而,多级网络是一个更复杂的系统,通常涉及不同类型的实体(例如,制造厂、配送中心、零售商等)。).

为了建立这样一个系统的模型,我们有两种可能的方法。第一种方法是将每个实体建模为独立的代理,并建立多代理 RL 模型。在这种方法中,每个代理只关心网络中一个实体的动作,这就限制了动作空间的大小。然而,与单代理 RL 模型相比,多代理 RL 模型通常更难训练和调整,因为整个模型的成功取决于每个代理的良好训练和调整,以及代理之间的合作。第二种方法是使用单个代理对整个网络进行建模,其动作空间足够灵活,可以同时描述所有实体的排序决策,例如,一个 n 维动作空间,每个维度对应于每个实体的排序决策。然而,这种方法的缺点是随着实体数量的增加,动作空间的大小急剧增加。动作空间的大小随着实体的数量呈指数增长。

简单多级网络的 RL 模型

让我们从上一篇文章中的例子开始。假设有一家零售公司,在全国不同地区开设零售店,销售可乐盈利。在前一篇文章中,我们没有关注单个零售店的库存运作,而是假设零售公司拥有自己的配送中心(DC)来满足其零售店的需求,我们寻求优化由配送中心和零售店组成的整个网络的库存控制策略。

为了使例子更简单,我们考虑一个仅由两个配送中心和四个零售商组成的多级网络。下图描述了配送中心和零售商之间的运输联系。

正在调查的简单多级供应链网络(图片由作者提供)

我们看到,零售公司首先从供应商那里购买可乐,然后将可乐运输到 DC 1 号和 DC 2 号。DC 1 存储库存以满足零售商 1 和零售商 2 的订单,DC 2 存储库存以满足零售商 3 和零售商 4 的订单。在本例中,让我们进一步假设所有四家零售商都有与上一篇文章中相同的客户需求模式。具体来说,所有四个零售商的客户需求都遵循混合正态分布,其中周一至周四的需求遵循具有最低均值的正态分布,周五的需求遵循具有中等均值的正态分布,周六至周日的需求遵循具有最高均值的正态分布。

对于这个特殊的问题,需要注意的是整个网络可以分解成两个子网络,一个由 DC 1、零售商 1 和零售商 2 组成,另一个由 DC 2、零售商 3 和零售商 4 组成。这两个子网络具有相同的客户需求分布。因此,为了使训练过程更容易,我们可以在这里采用分而治之的方法。我们不是对整个网络建模,而是仅通过一个 RL 代理对子网络建模,并且我们依次使用来自第一和第二子网络的数据来训练 RL 代理。RL 模型的状态、动作和奖励定义如下:

  1. State: ( i_pt_dc1i_pt_r1,i_pt_r2,dow_t ),其中 i_pt_dc1 为 DC 1 在 t 日结束时的库存位置i_pt_r1 为零售商 1 的库存位置, i_pt_r2
  2. 动作:( a_t_dc1a_t_r1a_t_r2 ),其中 a_t_dc1 是 DC 1 在第 t 天结束时的订单数量, a_t_r1 是零售商 1 在时的订单数量注意,如果 a_t_dc1a_t_r1a_t_r2 为 0 那么我们当时不下单对应的实体。行动空间受到最大订货量的限制,最大订货量由供应商或运输车辆的容量决定。
  3. 奖励:r _ t = r _ t _ R1+r _ t _ R2-h _ t _ R1-h _ t _ R2-h _ t _ dc1-f _ t _ R1-f _ t _ R2-f _ t _ dc1-v _ t _ dc1,其中 r_t_r1r_t_r2 为第(t+1)**日白天在零售商 1 和 2 处销售产品所获得的收益 零售商 1、2 和 DC 1 在第 t 个决策时期产生的配送费用,以及 v_t_dc1 i s 在第 t 天结束时在 DC 1 产生的可变订购成本(供应商收取的产品购买成本)。 因为我们假设可变订购成本仅指购买成本,我们只需要在 DC 1 计算这个成本。很容易看出,报酬 r_t 就是在第 t 个决策历元从 DC 1、零售商 1 和零售商 2 组成的子网络中获得的利润。

注意,这个建模框架遵循上一节提到的第二种建模方法,它可能会遭受维数灾难。如果我们假设每个实体的动作取从 0 到 a_max (最大订货量)的离散值,那么动作空间的大小随着网络中实体的数量呈指数增长。这给有效训练 RL 代理造成了困难。缓解这个问题的一种方法是将一个动作可以采取的值限制在[0,a _ max**区间内。例如如果 a_max = 20 我们可以限制动作只能取值 0、5、15 或 20,而不是允许它取 0 到 20 之间的每个整数。这可能在某种程度上破坏从 RL 代理获得的结果策略的最优性,但是它可以显著地减小动作空间的大小。

数值实验

假设有一家小型零售公司向其客户销售可乐。该零售公司有两个配送中心和四个零售商来满足客户需求。每当该公司想在任何 DC 或零售商那里补充库存时,该公司必须订购整数箱可乐(一箱装 24 罐)。假设对于零售商,可乐的单位售价为每箱 30 美元,持有成本为每箱每晚 3 美元,固定订购成本为每订单 50 美元,库存能力为 50 箱,每订单允许的最大订购量为 20 箱,在一个周日结束时初始库存为 25 箱,提前期为 2 天。周一至周四的需求服从正态分布 N (3,1.5),周五的需求服从正态分布 N (6,1),周六至周日的需求服从正态分布 N (12,2)。对于配送中心,每晚每箱的持有成本为 1 美元,固定订购成本为每订单 75 美元,库存能力为 200 箱,每订单允许的最大订单数量为 100 箱,周日结束时的初始库存为 100 箱,提前期为 5 天。我们从混合分布中生成 52 周的历史需求样本,并将其用作 RL 模型的训练数据集。

关于要使用的特定 RL 算法的选择,我采用了近似策略优化(PPO)算法,因为它是一种最先进的基于策略的 RL 算法,能够给出随机策略。我还尝试使用基于值的算法来生成确定性策略(例如,DQN),但发现 PPO 对于这个特殊的例子更有效。这里,我省略了对 PPO 算法的详细解释,因为这不是本文的重点。感兴趣的读者可以参考这篇文章了解更多细节。

作为基准,我们将使用用于训练 PPO 模型的相同数据集来优化经典( sS )库存控制策略,并在测试集中将其性能与 PPO 进行比较。

PPO 模型的定型代码

首先,我们生成包含四家零售商 52 周历史需求记录的训练数据集。请注意,非整数需求数据会四舍五入为最接近的整数。

*import numpy as np
import matplotlib.pyplot as plt
import random
np.random.seed(0)
demand_hist_list = []
for k in range(4):
    demand_hist = []
    for i in range(52):
        for j in range(4):
            random_demand = np.random.normal(3, 1.5)
            if random_demand < 0:
                random_demand = 0
            random_demand = np.round(random_demand)
            demand_hist.append(random_demand)
        random_demand = np.random.normal(6, 1)
        if random_demand < 0:
            random_demand = 0
        random_demand = np.round(random_demand)
        demand_hist.append(random_demand)
        for j in range(2):
            random_demand = np.random.normal(12, 2)
            if random_demand < 0:
                random_demand = 0
            random_demand = np.round(random_demand)
            demand_hist.append(random_demand)
    demand_hist_list.append(demand_hist)*

然后,我们定义了 PPO 代理交互的库存优化问题的环境。该环境包含一个有一个 DC 和两个零售商的子网络。在本例中,零售商不考虑延期交货,因为我们假设如果顾客在零售店没有看到任何剩余的可乐,他们会去其他商店购买可乐。但是,DC 会考虑延期交货。当 DC 没有足够的库存来满足零售商的订单时,就会发生延期交货,而 DC 会在补充库存后尽快满足延期交货。为了限制行动空间的大小,我们允许 DC 的订货量取值为[0,10,20,30,40,50,60,70,80,90,100],两个零售商取值为[0,5,10,15,20]。动作空间的大小现在是 1155 = 275。

*import itertools
action_lists = [[0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100],[0, 5, 10, 15, 20],[0, 5, 10, 15, 20]]
action_map = [x for x in itertools.product(*action_lists)]

class Retailer():
    def __init__(self, demand_records):
        self.inv_level = 25
        self.inv_pos = 25
        self.order_quantity_limit = 20
        self.holding_cost = 3
        self.lead_time = 2
        self.order_arrival_list = []
        self.backorder_quantity = 0
        self.capacity = 50
        self.demand_list = demand_records
        self.unit_price = 30
        self.fixed_order_cost = 50

    def reset(self):
        self.inv_level = 25
        self.inv_pos = 25
        self.order_arrival_list = []
        self.backorder_quantity = 0

    def order_arrival(self, current_period):
        n_orders = 0
        if len(self.order_arrival_list) > 0:
            index_list = []
            for j in range(len(self.order_arrival_list)):
                if current_period == self.order_arrival_list[j][0]:
                    self.inv_level = min(self.capacity, self.inv_level + self.order_arrival_list[j][1])
                    n_orders += 1
                    index_list.append(j)
            self.order_arrival_list =  [e for i, e in enumerate(self.order_arrival_list) if i not in index_list]
        holding_cost_total = self.inv_level*self.holding_cost
        return n_orders, holding_cost_total

    def satisfy_demand(self, demand):
        units_sold = min(demand, self.inv_level)
        self.inv_level = max(0,self.inv_level-demand)
        self.inv_pos = self.inv_level
        if len(self.order_arrival_list) > 0:
            for j in range(len(self.order_arrival_list)):
                self.inv_pos += self.order_arrival_list[j][1]
        revenue = units_sold*self.unit_price
        return revenue

class DistributionCenter():
    def __init__(self):
        self.inv_level = 100
        self.inv_pos = 100
        self.order_quantity_limit = 100
        self.holding_cost = 1
        self.lead_time = 5
        self.order_arrival_list = []
        self.capacity = 200
        self.fixed_order_cost = 75

    def reset(self):
        self.inv_level = 100
        self.inv_pos = 100
        self.order_arrival_list = []

    def place_order(self, order_quantity, current_period):
        if order_quantity > 0:
            self.order_arrival_list.append([current_period+self.lead_time, order_quantity])

    def order_arrival(self, retailers, current_period):
        if len(self.order_arrival_list) > 0:
            if current_period == self.order_arrival_list[0][0]:
                self.inv_level = min(self.capacity, self.inv_level+self.order_arrival_list[0][1])
                self.order_arrival_list.pop(0)
        holding_cost_total = self.inv_level*self.holding_cost
        return holding_cost_total

    def satisfy_demand(self, retailers, actions, current_period):
        quantity_satisfied = [0,0]
        total_backorder = np.sum([retailer.backorder_quantity for retailer in retailers])
        if total_backorder > 0:
            if self.inv_level <= retailers[0].backorder_quantity:
                retailers[0].backorder_quantity -= self.inv_level
                quantity_satisfied[0] += self.inv_level
                self.inv_level = 0
            if self.inv_level > retailers[0].backorder_quantity and self.inv_level <= total_backorder:
                if retailers[0].backorder_quantity == 0:
                    retailers[1].backorder_quantity -= self.inv_level
                    quantity_satisfied[1] += self.inv_level
                else:
                    quantity_left = self.inv_level - retailers[0].backorder_quantity
                    quantity_satisfied[0] += retailers[0].backorder_quantity
                    retailers[0].backorder_quantity = 0
                    quantity_satisfied[1] += quantity_left
                    retailers[1].backorder_quantity -= quantity_left
                self.inv_level = 0
            if self.inv_level > total_backorder:
                if retailers[0].backorder_quantity == 0 and retailers[1].backorder_quantity != 0:
                    quantity_satisfied[1] += retailers[1].backorder_quantity
                    retailers[1].backorder_quantity = 0
                if retailers[0].backorder_quantity != 0 and retailers[1].backorder_quantity == 0:
                    quantity_satisfied[0] += retailers[0].backorder_quantity
                    retailers[0].backorder_quantity = 0
                if retailers[0].backorder_quantity != 0 and retailers[1].backorder_quantity != 0:
                    quantity_satisfied[0] += retailers[0].backorder_quantity
                    quantity_satisfied[1] += retailers[1].backorder_quantity
                    retailers[0].backorder_quantity = 0
                    retailers[1].backorder_quantity = 0
                self.inv_level -= total_backorder

        if self.inv_level > 0:
            if self.inv_level <= actions[0]:
                quantity_satisfied[0] += self.inv_level
                retailers[0].backorder_quantity += actions[0] - self.inv_level
                self.inv_level = 0    
            if self.inv_level > actions[0] and self.inv_level <= np.sum(actions):
                if actions[0] == 0:
                    quantity_satisfied[1] += self.inv_level
                    retailers[1].backorder_quantity += actions[1] - self.inv_level
                else:
                    inv_left = self.inv_level-actions[0]
                    quantity_satisfied[0] += actions[0]
                    quantity_satisfied[1] += inv_left
                    retailers[1].backorder_quantity += actions[1] - inv_left
                self.inv_level = 0
            if self.inv_level > np.sum(actions): 
                if actions[0] == 0 and actions[1] != 0:
                    quantity_satisfied[1] += actions[1]
                if actions[0] != 0 and actions[1] == 0:
                    quantity_satisfied[0] += actions[0]
                if actions[0] != 0 and actions[1] != 0:    
                    quantity_satisfied[0] += actions[0]
                    quantity_satisfied[1] += actions[1]
                self.inv_level -= np.sum(actions)   
        else:
            retailers[0].backorder_quantity += actions[0]
            retailers[1].backorder_quantity += actions[1]  

        for i in range(len(retailers)):
            quantity_left = quantity_satisfied[i]
            while quantity_left > 0:
                if quantity_left > retailers[i].order_quantity_limit:
                    retailers[i].order_arrival_list.append([current_period+retailers[i].lead_time, retailers[i].order_quantity_limit])
                    quantity_left -= retailers[i].order_quantity_limit
                else:
                    retailers[i].order_arrival_list.append([current_period+retailers[i].lead_time, quantity_left])
                    quantity_left = 0

        self.inv_pos = self.inv_level
        if len(self.order_arrival_list) > 0:
            for j in range(len(self.order_arrival_list)):
                self.inv_pos += self.order_arrival_list[j][1]
        for retailer in retailers:
            self.inv_pos -= retailer.backorder_quantity

class MultiEchelonInvOptEnv():
    def __init__(self, demand_records):
        self.n_retailers = 2
        self.n_DCs = 1
        self.retailers = []
        for i in range(self.n_retailers):
            self.retailers.append(Retailer(demand_records[i]))
        self.DCs = []
        for i in range(self.n_DCs):
            self.DCs.append(DistributionCenter()) 
        self.n_period = len(demand_records[0])
        self.current_period = 1
        self.day_of_week = 0
        self.state = np.array([DC.inv_pos for DC in self.DCs] + [retailer.inv_pos for retailer in self.retailers] + \
                              self.convert_day_of_week(self.day_of_week))
        self.variable_order_cost = 10
        self.demand_records = demand_records

    def reset(self):
        for retailer in self.retailers:
            retailer.reset()
        for DC in self.DCs:
            DC.reset()
        self.current_period = 1
        self.day_of_week = 0 
        self.state = np.array([DC.inv_pos for DC in self.DCs] + [retailer.inv_pos for retailer in self.retailers] + \
                              self.convert_day_of_week(self.day_of_week))
        return self.state

    def step(self, action):
        action_modified = action_map[action]
        y_list = []
        for i in range(self.n_DCs):
            y = 1 if action_modified[i] > 0 else 0    
            y_list.append(y)
        for DC,order_quantity in zip(self.DCs,action_modified[:self.n_DCs]):
            DC.place_order(order_quantity,self.current_period)
        sum_holding_cost_DC = 0
        for i in range(self.n_DCs):
            holding_cost_total = self.DCs[i].order_arrival(self.retailers,self.current_period)
            sum_holding_cost_DC += holding_cost_total
            self.DCs[i].satisfy_demand(self.retailers,action_modified[i*2+1:i*2+3],self.current_period)
        sum_n_orders = 0
        sum_holding_cost_retailer = 0
        sum_revenue = 0
        for retailer,demand in zip(self.retailers,self.demand_records):
            n_orders, holding_cost_total = retailer.order_arrival(self.current_period)
            sum_n_orders += n_orders
            sum_holding_cost_retailer += holding_cost_total
            revenue = retailer.satisfy_demand(demand[self.current_period-1])
            sum_revenue += revenue    
        reward = sum_revenue - sum_holding_cost_retailer - sum_holding_cost_DC - sum_n_orders*self.retailers[0].fixed_order_cost - \
                 np.sum(y_list)*self.DCs[0].fixed_order_cost - np.sum(action_modified[:self.n_DCs])*self.variable_order_cost

        self.day_of_week = (self.day_of_week+1)%7
        self.state = np.array([DC.inv_pos for DC in self.DCs] + [retailer.inv_pos for retailer in self.retailers] + \
                              self.convert_day_of_week(self.day_of_week))
        self.current_period += 1
        if self.current_period > self.n_period:
            terminate = True
        else: 
            terminate = False
        return self.state, reward, terminate

    def convert_day_of_week(self,d):
        if d == 0:
            return [0, 0, 0, 0, 0, 0]
        if d == 1:
            return [1, 0, 0, 0, 0, 0] 
        if d == 2:
            return [0, 1, 0, 0, 0, 0] 
        if d == 3:
            return [0, 0, 1, 0, 0, 0] 
        if d == 4:
            return [0, 0, 0, 1, 0, 0] 
        if d == 5:
            return [0, 0, 0, 0, 1, 0] 
        if d == 6:
            return [0, 0, 0, 0, 0, 1]* 

现在我们开始用 PyTorch 构建 PPO 模型。这部分 PPO 的实现是基于这个资源库的。

*import torch
import torch.nn as nn
from torch.distributions import MultivariateNormal
from torch.distributions import Categorical

################################## set device ##################################
print("============================================================================================")
# set device to cpu or cuda
device = torch.device('cpu')
if(torch.cuda.is_available()): 
    device = torch.device('cuda:0') 
    torch.cuda.empty_cache()
    print("Device set to : " + str(torch.cuda.get_device_name(device)))
else:
    print("Device set to : cpu")
print("============================================================================================")

################################## PPO Policy ##################################
class RolloutBuffer:
    def __init__(self):
        self.actions = []
        self.states = []
        self.logprobs = []
        self.rewards = []
        self.is_terminals = []

    def clear(self):
        del self.actions[:]
        del self.states[:]
        del self.logprobs[:]
        del self.rewards[:]
        del self.is_terminals[:]

class ActorCritic(nn.Module):
    def __init__(self, state_dim, action_dim, has_continuous_action_space, action_std_init):
        super(ActorCritic, self).__init__()

        self.has_continuous_action_space = has_continuous_action_space

        if has_continuous_action_space:
            self.action_dim = action_dim
            self.action_var = torch.full((action_dim,), action_std_init * action_std_init).to(device)
        # actor
        if has_continuous_action_space :
            self.actor = nn.Sequential(
                            nn.Linear(state_dim, 256),
                            nn.Tanh(),
                            nn.Linear(256,256),
                            nn.Tanh(),
                            nn.Linear(256, action_dim),
                            nn.Sigmoid()
                        )
        else:
            self.actor = nn.Sequential(
                            nn.Linear(state_dim, 384),
                            nn.Tanh(),
                            nn.Linear(384, 384),
                            nn.Tanh(),
                            nn.Linear(384, action_dim),
                            nn.Softmax(dim=-1)
                        )
        # critic
        self.critic = nn.Sequential(
                        nn.Linear(state_dim, 128),
                        nn.Tanh(),
                        nn.Linear(128, 128),
                        nn.Tanh(),
                        nn.Linear(128, 1)
                    )

    def set_action_std(self, new_action_std):
        if self.has_continuous_action_space:
            self.action_var = torch.full((self.action_dim,), new_action_std * new_action_std).to(device)
        else:
            print("--------------------------------------------------------------------------------------------")
            print("WARNING : Calling ActorCritic::set_action_std() on discrete action space policy")
            print("--------------------------------------------------------------------------------------------")

    def forward(self):
        raise NotImplementedError

    def act(self, state):
        if self.has_continuous_action_space:
            action_mean = self.actor(state)
            cov_mat = torch.diag(self.action_var).unsqueeze(dim=0)
            dist = MultivariateNormal(action_mean, cov_mat)
        else:
            action_probs = self.actor(state)
            dist = Categorical(action_probs)

        action = dist.sample()
        action_logprob = dist.log_prob(action)

        return action.detach(), action_logprob.detach()

    def evaluate(self, state, action):

        if self.has_continuous_action_space:
            action_mean = self.actor(state)

            action_var = self.action_var.expand_as(action_mean)
            cov_mat = torch.diag_embed(action_var).to(device)
            dist = MultivariateNormal(action_mean, cov_mat)

            # For Single Action Environments.
            if self.action_dim == 1:
                action = action.reshape(-1, self.action_dim)
        else:
            action_probs = self.actor(state)
            dist = Categorical(action_probs)
        action_logprobs = dist.log_prob(action)
        dist_entropy = dist.entropy()
        state_values = self.critic(state)

        return action_logprobs, state_values, dist_entropy

class PPO:
    def __init__(self, state_dim, action_dim, lr_actor, lr_critic, gamma, K_epochs, eps_clip, has_continuous_action_space, action_std_init=0.6):

        self.has_continuous_action_space = has_continuous_action_space

        if has_continuous_action_space:
            self.action_std = action_std_init

        self.gamma = gamma
        self.eps_clip = eps_clip
        self.K_epochs = K_epochs

        self.buffer = RolloutBuffer()

        self.policy = ActorCritic(state_dim, action_dim, has_continuous_action_space, action_std_init).to(device)
        self.optimizer = torch.optim.Adam([
                        {'params': self.policy.actor.parameters(), 'lr': lr_actor},
                        {'params': self.policy.critic.parameters(), 'lr': lr_critic}
                    ])

        self.policy_old = ActorCritic(state_dim, action_dim, has_continuous_action_space, action_std_init).to(device)
        self.policy_old.load_state_dict(self.policy.state_dict())

        self.MseLoss = nn.MSELoss()

    def set_action_std(self, new_action_std):
        if self.has_continuous_action_space:
            self.action_std = new_action_std
            self.policy.set_action_std(new_action_std)
            self.policy_old.set_action_std(new_action_std)
        else:
            print("--------------------------------------------------------------------------------------------")
            print("WARNING : Calling PPO::set_action_std() on discrete action space policy")
            print("--------------------------------------------------------------------------------------------")

    def decay_action_std(self, action_std_decay_rate, min_action_std):
        print("--------------------------------------------------------------------------------------------")
        if self.has_continuous_action_space:
            self.action_std = self.action_std - action_std_decay_rate
            self.action_std = round(self.action_std, 4)
            if (self.action_std <= min_action_std):
                self.action_std = min_action_std
                print("setting actor output action_std to min_action_std : ", self.action_std)
            else:
                print("setting actor output action_std to : ", self.action_std)
            self.set_action_std(self.action_std)

        else:
            print("WARNING : Calling PPO::decay_action_std() on discrete action space policy")
        print("--------------------------------------------------------------------------------------------")

    def select_action(self, state):

        if self.has_continuous_action_space:
            with torch.no_grad():
                state = torch.FloatTensor(state).to(device)
                action, action_logprob = self.policy_old.act(state)

            self.buffer.states.append(state)
            self.buffer.actions.append(action)
            self.buffer.logprobs.append(action_logprob)

            return action.detach().cpu().numpy().flatten()
        else:
            with torch.no_grad():
                state = torch.FloatTensor(state).to(device)
                action, action_logprob = self.policy_old.act(state)

            self.buffer.states.append(state)
            self.buffer.actions.append(action)
            self.buffer.logprobs.append(action_logprob)

            return action.item()

    def update(self):
        # Monte Carlo estimate of returns
        rewards = []
        discounted_reward = 0
        for reward, is_terminal in zip(reversed(self.buffer.rewards), reversed(self.buffer.is_terminals)):
            if is_terminal:
                discounted_reward = 0
            discounted_reward = reward + (self.gamma * discounted_reward)
            rewards.insert(0, discounted_reward)

        # Normalizing the rewards
        rewards = torch.tensor(rewards, dtype=torch.float32).to(device)
        rewards = (rewards - rewards.mean()) / (rewards.std() + 1e-7)

        # convert list to tensor
        old_states = torch.squeeze(torch.stack(self.buffer.states, dim=0)).detach().to(device)
        old_actions = torch.squeeze(torch.stack(self.buffer.actions, dim=0)).detach().to(device)
        old_logprobs = torch.squeeze(torch.stack(self.buffer.logprobs, dim=0)).detach().to(device)

        # Optimize policy for K epochs
        for _ in range(self.K_epochs):

            # Evaluating old actions and values
            logprobs, state_values, dist_entropy = self.policy.evaluate(old_states, old_actions)

            # match state_values tensor dimensions with rewards tensor
            state_values = torch.squeeze(state_values)

            # Finding the ratio (pi_theta / pi_theta__old)
            ratios = torch.exp(logprobs - old_logprobs.detach())

            # Finding Surrogate Loss
            advantages = rewards - state_values.detach()   
            surr1 = ratios * advantages
            surr2 = torch.clamp(ratios, 1-self.eps_clip, 1+self.eps_clip) * advantages

            # final loss of clipped objective PPO
            loss = -torch.min(surr1, surr2) + 0.5*self.MseLoss(state_values, rewards) - 0.01*dist_entropy

            # take gradient step
            self.optimizer.zero_grad()
            loss.mean().backward()
            self.optimizer.step()

        # Copy new weights into old policy
        self.policy_old.load_state_dict(self.policy.state_dict())

        # clear buffer
        self.buffer.clear()* 
*import os
import glob
import time
from datetime import datetime

import torch
import numpy as np

################################### Training ###################################
def train():
    print("============================================================================================")

    has_continuous_action_space = False # continuous action space; else discrete

    max_ep_len = 364                   # max timesteps in one episode
    max_training_timesteps = int(364*15000)   # break training loop if timeteps > max_training_timesteps

    print_freq = max_ep_len * 10        # print avg reward in the interval (in num timesteps)

    action_std = 0.6            # starting std for action distribution (Multivariate Normal)
    action_std_decay_rate = 0.03       # linearly decay action_std (action_std = action_std - action_std_decay_rate)
    min_action_std = 0.03               # minimum action_std (stop decay after action_std <= min_action_std)
    action_std_decay_freq = int(1e5)  # action_std decay frequency (in num timesteps)
    #####################################################

    ## Note : print/log frequencies should be > than max_ep_len

    ################ PPO hyperparameters ################
    update_timestep = max_ep_len/2       # update policy every n timesteps
    K_epochs = 20               # update policy for K epochs in one PPO update

    eps_clip = 0.2          # clip parameter for PPO
    gamma = 0.99            # discount factor

    lr_actor = 0.00005       # learning rate for actor network
    lr_critic = 0.0001       # learning rate for critic network

    random_seed = 0         # set random seed if required (0 = no random seed)
    #####################################################

    state_dim = 9
    action_dim = 275

    torch.manual_seed(random_seed)
    np.random.seed(random_seed)

    ################# training procedure ################

    # initialize a PPO agent
    ppo_agent = PPO(state_dim, action_dim, lr_actor, lr_critic, gamma, K_epochs, eps_clip, has_continuous_action_space, action_std)

    # track total training time
    start_time = datetime.now().replace(microsecond=0)
    print("Started training at (GMT) : ", start_time)

    print("============================================================================================")

    # printing and logging variables
    print_running_reward = 0
    print_running_episodes = 0

    time_step = 0
    i_episode = 0

    # training loop
    for i in range(2):
        env = MultiEchelonInvOptEnv(demand_hist_list[i*2:i*2+2])
        while time_step <= max_training_timesteps:

            state = env.reset()
            current_ep_reward = 0

            for t in range(1, max_ep_len+1):

                # select action with policy

                action = ppo_agent.select_action(state)
                state, reward, done = env.step(action)

                # saving reward and is_terminals
                ppo_agent.buffer.rewards.append(reward)
                ppo_agent.buffer.is_terminals.append(done)

                time_step +=1
                current_ep_reward += reward

                # update PPO agent
                if time_step % update_timestep == 0:
                    ppo_agent.update()

                # if continuous action space; then decay action std of ouput action distribution
                if has_continuous_action_space and time_step % action_std_decay_freq == 0:
                    ppo_agent.decay_action_std(action_std_decay_rate, min_action_std)

                # printing average reward
                if time_step % print_freq == 0:

                    # print average reward till last episode
                    print_avg_reward = print_running_reward / print_running_episodes
                    print_avg_reward = round(print_avg_reward, 2)

                    print("Episode : {} \t\t Timestep : {} \t\t Average Reward : {}".format(i_episode, time_step, print_avg_reward))

                    print_running_reward = 0
                    print_running_episodes = 0

                # break; if the episode is over
                if done:
                    break

            print_running_reward += current_ep_reward
            print_running_episodes += 1

            i_episode += 1
    torch.save(ppo_agent.policy.state_dict(), desired_path)

if __name__ == '__main__':

    train()*

用于优化(S,S)策略的代码

对于这个特定的数值实验,可能的( sS ) 组合的数量大得惊人。因此,列举所有可能的组合来优化( sS )策略是不切实际的。这里,我们采用贝叶斯优化(BO)来获得最优( sS )策略。BO 是一种黑盒优化方法。它通过从目标函数顺序采样并更新逼近目标函数的代理模型来优化目标函数。BO 使用一个获取函数来确定在哪里采样下一个点,并且随着更多的点被采样,代理模型的精度不断提高。最后,BO 返回所有先前采样点中具有最佳目标函数值的点作为最优解。关于 BO 更详细的介绍,感兴趣的读者可以参考本文。这部分业务对象的实现基于这个库

在代码中,我们定义了一个有 6 个参数的目标函数,分别是 s_DC,s_DC,s_r1,S_r1,s_r2,S_r2。目标函数计算在历史需求数据集上获得的利润, s_DCS_DC 定义两个DC 的( s,S ) 策略, s_r1S_r1 定义( s,S ) 策略然后我们使用 BO 优化这个函数。我们将 S_DC 的上限设置为 210,留出一点额外的空间来允许 S取一个高于容量的值。我尝试将 S_r1S_r2 到每个值的上限设置为【60,70,80,90,100】,发现 90 给出了最佳目标值。因此,我选择 90 作为上限。历史需求数据集上的最优解竟然是( s_DC,s_DC,s_r1,S_r1,s_r2,S _ R2)=(62,120,15,41,17,90) ****

*def MultiEchelonInvOpt_sS(s_DC,S_DC,s_r1,S_r1,s_r2,S_r2):
    if s_DC > S_DC-1 or s_r1 > S_r1-1 or s_r2 > S_r2-1:
        return -1e8
    else:
        n_retailers = 4
        n_DCs = 2
        retailers = []
        for i in range(n_retailers):
            retailers.append(Retailer(demand_hist_list[i]))
        DCs = []
        for i in range(n_DCs):
            DCs.append(DistributionCenter()) 
        n_period = len(demand_hist_list[0])
        variable_order_cost = 10
        current_period = 1
        total_reward = 0
        while current_period <= n_period:
            action = []
            for DC in DCs:
                if DC.inv_pos <= s_DC:
                    action.append(np.round(min(DC.order_quantity_limit,S_DC-DC.inv_pos)))
                else:
                    action.append(0)
            for i in range(len(retailers)):
                if i%2 == 0:
                    if retailers[i].inv_pos <= s_r1:
                        action.append(np.round(min(retailers[i].order_quantity_limit,S_r1-retailers[i].inv_pos)))
                    else:
                        action.append(0)
                else:
                    if retailers[i].inv_pos <= s_r2:
                        action.append(np.round(min(retailers[i].order_quantity_limit,S_r2-retailers[i].inv_pos)))
                    else:
                        action.append(0)
            y_list = []
            for i in range(n_DCs):
                y = 1 if action[i] > 0 else 0    
                y_list.append(y)
            for DC,order_quantity in zip(DCs,action[:n_DCs]):
                DC.place_order(order_quantity,current_period)
            sum_holding_cost_DC = 0
            for i in range(n_DCs):
                holding_cost_total = DCs[i].order_arrival(retailers[i*2:i*2+2],current_period)
                sum_holding_cost_DC += holding_cost_total
                DCs[i].satisfy_demand(retailers[i*2:i*2+2],action[i*2+2:i*2+4],current_period)
            sum_n_orders = 0
            sum_holding_cost_retailer = 0
            sum_revenue = 0
            for retailer,demand in zip(retailers,demand_hist_list):
                n_orders, holding_cost_total = retailer.order_arrival(current_period)
                sum_n_orders += n_orders
                sum_holding_cost_retailer += holding_cost_total
                revenue = retailer.satisfy_demand(demand[current_period-1])
                sum_revenue += revenue    
            reward = sum_revenue - sum_holding_cost_retailer - sum_holding_cost_DC - sum_n_orders*retailers[0].fixed_order_cost - \
                     np.sum(y_list)*DCs[0].fixed_order_cost - np.sum(action[:n_DCs])*variable_order_cost

            current_period += 1
            total_reward += reward
        return total_reward*
*from bayes_opt import BayesianOptimization
pbounds = {'s_DC': (0,210), 'S_DC': (0, 210), 's_r1': (0, 90), 'S_r1': (0, 90), 's_r2': (0, 90), 'S_r2': (0, 90)}
optimizer = BayesianOptimization(
    f=MultiEchelonInvOpt_sS,
    pbounds=pbounds,
    random_state=0,
)
optimizer.maximize(
    init_points = 100,
    n_iter=1000
)
print(optimizer.max)*

测试 PPO 策略的代码

我们首先创建 100 个客户需求数据集进行测试。100 个数据集中的每一个都包含四家零售商 52 周的需求数据。我们可以将每个数据集视为未来 1 年需求的可能情景。然后,我们在每个需求数据集上评估 PPO 策略,并收集每个数据集的总回报。

*np.random.seed(0)
demand_test = []
for k in range(100,200):
    demand_list = []
    for k in range(4):
        demand = []
        for i in range(52):
            for j in range(4):
                random_demand = np.random.normal(3, 1.5)
                if random_demand < 0:
                    random_demand = 0
                random_demand = np.round(random_demand)
                demand.append(random_demand)
            random_demand = np.random.normal(6, 1)
            if random_demand < 0:
                random_demand = 0
            random_demand = np.round(random_demand)
            demand.append(random_demand)
            for j in range(2):
                random_demand = np.random.normal(12, 2)
                if random_demand < 0:
                    random_demand = 0
                random_demand = np.round(random_demand)
                demand.append(random_demand)
        demand_list.append(demand)
    demand_test.append(demand_list)*
*import os
import glob
import time
from datetime import datetime

import torch
import numpy as np

has_continuous_action_space = False # continuous action space; else discrete
action_std = 0.6            # starting std for action distribution (Multivariate Normal)
action_std_decay_rate = 0.03       # linearly decay action_std (action_std = action_std - action_std_decay_rate)
min_action_std = 0.03               # minimum action_std (stop decay after action_std <= min_action_std)
action_std_decay_freq = int(1e5)  # action_std decay frequency (in num timesteps
eps_clip = 0.2          # clip parameter for PPO
gamma = 0.99            # discount factor
K_epochs = 20
lr_actor = 0.00005      # learning rate for actor network
lr_critic = 0.0001       # learning rate for critic network

random_seed = 0         # set random seed if required (0 = no random seed)
#####################################################

state_dim = 9
action_dim = 275

torch.manual_seed(random_seed)
np.random.seed(random_seed)

ppo_agent = PPO(state_dim, action_dim, lr_actor, lr_critic, gamma, K_epochs, eps_clip, has_continuous_action_space, action_std)
ppo_agent.policy_old.load_state_dict(torch.load(desired_path))
ppo_agent.policy.load_state_dict(torch.load(desired_path))

reward_RL = []
for demand in demand_test:
    reward_total = 0
    for i in range(2):
        env = MultiEchelonInvOptEnv(demand[i*2:i*2+2])
        state = env.reset()
        done = False
        reward_sub = 0
        while not done:
            action = ppo_agent.select_action(state)
            state, reward, done = env.step(action)
            reward_sub += reward
            if done:
                break
        reward_total += reward_sub
    reward_RL.append(reward_total)*

测试(S,S)策略的代码

我们在同一个测试集上评估( sS )策略。

*def MultiEchelonInvOpt_sS_test(s_DC,S_DC,s_r1,S_r1,s_r2,S_r2,demand_records):
    if s_DC > S_DC-1 or s_r1 > S_r1-1 or s_r2 > S_r2-1:
        return -1e8
    else:
        n_retailers = 4
        n_DCs = 2
        retailers = []
        for i in range(n_retailers):
            retailers.append(Retailer(demand_records[i]))
        DCs = []
        for i in range(n_DCs):
            DCs.append(DistributionCenter()) 
        n_period = len(demand_records[0])
        variable_order_cost = 10
        current_period = 1
        total_reward = 0
        total_revenue = 0
        total_holding_cost_retailer = 0
        total_holding_cost_DC = 0
        total_variable_cost = 0
        while current_period <= n_period:
            action = []
            for DC in DCs:
                if DC.inv_pos <= s_DC:
                    action.append(np.round(min(DC.order_quantity_limit,S_DC-DC.inv_pos)))
                else:
                    action.append(0)
            for i in range(len(retailers)):
                if i%2 == 0:
                    if retailers[i].inv_pos <= s_r1:
                        action.append(np.round(min(retailers[i].order_quantity_limit,S_r1-retailers[i].inv_pos)))
                    else:
                        action.append(0)
                else:
                    if retailers[i].inv_pos <= s_r2:
                        action.append(np.round(min(retailers[i].order_quantity_limit,S_r2-retailers[i].inv_pos)))
                    else:
                        action.append(0)
            y_list = []
            for i in range(n_DCs):
                y = 1 if action[i] > 0 else 0 
                y_list.append(y)
            for DC,order_quantity in zip(DCs,action[:n_DCs]):
                DC.place_order(order_quantity,current_period)
            sum_holding_cost_DC = 0
            for i in range(n_DCs):
                holding_cost_total = DCs[i].order_arrival(retailers[i*2:i*2+2],current_period)
                sum_holding_cost_DC += holding_cost_total
                DCs[i].satisfy_demand(retailers[i*2:i*2+2],action[i*2+2:i*2+4],current_period)

            sum_n_orders = 0
            sum_holding_cost_retailer = 0
            sum_revenue = 0
            for retailer,demand in zip(retailers,demand_records):
                n_orders, holding_cost_total = retailer.order_arrival(current_period)
                sum_n_orders += n_orders
                sum_holding_cost_retailer += holding_cost_total
                revenue = retailer.satisfy_demand(demand[current_period-1])
                sum_revenue += revenue  
            reward = sum_revenue - sum_holding_cost_retailer - sum_holding_cost_DC - sum_n_orders*retailers[0].fixed_order_cost - \
                     np.sum(y_list)*DCs[0].fixed_order_cost - np.sum(action[:n_DCs])*variable_order_cost

            current_period += 1
            total_reward += reward

        return total_reward*
*reward_sS = []
for demand in demand_test:
    reward = MultiEchelonInvOpt_sS_test(62, 120, 15, 41, 17, 90, demand)
    reward_sS.append(reward)*

对数值结果的讨论

PPO 策略在 100 个需求数据集上的平均利润为$31187.03,( sS )策略的平均利润为$26390.87,这表明利润增加了 18.17%。PPO 和( sS )政策在 100 个需求数据集上获得的利润箱线图如下所示。

测试集中 PPO 策略和(S,S)策略获得的利润的箱线图(图片由作者提供)

我们已经看到,PPO 政策在目前的表述下优于( sS )政策。其性能可能还有进一步提升的空间。例如,我们可以将动作建模为[0,1]之间的连续值 x 。因此,每个实体的订单数量将是x**最大订单数量。在我以后的文章中,我可能会朝着这个方向更进一步,看看这种方法的效果如何。*

感谢阅读!

数据科学中的强化学习

原文:https://towardsdatascience.com/reinforcement-learning-in-data-science-e7805f133d7

机器学习中使用的另一种技术

照片由麦克多比胡Unsplash 上拍摄

在过去的几周里,我一直在研究数据科学中的线性回归。然而,这个星期,我想改变一些事情。我们对监督学习方法和非监督学习方法有所了解,但我们还没有谈到一种不同类型的学习:强化学习。这是一种不需要监督的学习,就像无监督学习一样,但也有独特的品质。在我们开始之前,有一点需要注意的是,强化学习不像其他模型那样被广泛使用,比如监督学习方法。到目前为止,许多例子都是理论上的或研究驱动的。因此,当我们讨论强化学习时,我们将讨论一些用例,但请记住,许多技术要么正在兴起,要么仍处于理论测试阶段。像往常一样,快速浏览一下我们的主题。首先,我们将讨论什么是数据科学中的强化学习。接下来,我们将看几个使用案例。最后,我们将讨论使用这种机器学习方法的一些好处和缺点。所以,事不宜迟,让我们开始研究吧。

什么是强化学习

强化学习是机器学习的一种数据科学方法。这是一种无监督的学习方法,因为您不需要提供标记数据。然而,它不同于典型的无监督学习方法,因为尽管数据是无标记的,但需要显式编程。开发人员必须创建算法来确定目标以及将要使用的奖励和惩罚。强化学习不同于监督学习,因为一旦那些初始参数被写入,开发者就不再需要任何中断。相反,机器将根据设定的目标和参数来解释数据。

用奖惩来决定行为?养狗时,这些信息是有意义的。当狗坐下的时候,给狗一个奖励。否则,不请客。但是你如何对待你的机器呢?老实说,这需要一点研究才能理解。我找到了一个来源,确定一台机器可以用状态来惩罚。如果机器做出了错误的决定,你就向机器返回一个错误。一个错误的状态作为惩罚。老实说,我理解错误是一种“惩罚”,但什么是奖励呢?这让我花了更多的时间去理解。我不认为我完全理解这一点,所以如果你能想到一个更好的方式来描述它,请在评论中留下它。否则,下面是我发现的一个例子:

对于一个学习玩 Pacman 的机器,它被给予了一个基于点数的奖励系统。移动到空白空间没有分数,移动到小球有一分,收集水果有两分,获得能量球有三分,当能量球被激活时击中鬼魂有四分,一旦通关有五分。作为对每一次与鬼魂碰撞的惩罚,如果没有来自能量球的不可战胜性,将会被扣除 5 分。因为初始参数的目的是收集尽可能多的点,所以机器将扣分理解为一种惩罚,因为它现在已经最小化了它可以收集的最大点数。这也鼓励机器尝试新的方法来优化它可以收集的点数。

强化学习使用一个代理,其目标是找到达到奖励的最佳可能方式。这可以通过正强化或负强化来实现。有了积极的强化,事件会因为特定的行为而发生,这就像是一种奖励,这增加了所述行为的强度和频率。我们的目标是使用积极的效果来最大化性能,在更长的时间内维持变化,并且不允许过多的强化产生过载的状态。使用负强化,一个负面的条件实际上是为了停止或避免另一种行为。负面条件增加了行为,提供了对最低绩效标准的挑战,并且仅提供了足以满足最低行为的能力。

现在,我们已经谈了一点什么是强化学习,让我们来看几个用例。

强化学习的用例

正如前面提到的,一个例子是教机器玩视频游戏。因为这一点已经解释过了,所以我可以把这一部分讲得简短一些。选择一个视频游戏,写下你的参数(目标、奖励和惩罚),让机器用不同的方法漫游,直到它完美地以最多的分数运行游戏(如果你选择了基于分数的奖励系统)。

另一个更真实的例子是自动驾驶汽车。如你所知,在旅途中没有办法预测所有的可能性。这一点,以及冗长的 If-Then 块可能需要时间,并且缺乏完整的逻辑。但是因为开发者不能总是监控算法,所以学习方法需要无人监督。通过制造强化学习的方法,你允许代理获得复杂性,也变得更加智能久而久之。您还可以允许代理关注速度等特征,以便汽车可以确定最快的路径,并在某些情况下改变所选路径的速度时对路线进行更改。

不同的强化学习技术

我想简单提一下你可以使用的三种不同的强化学习算法可能会有所帮助:

  1. 状态-动作-奖励-状态-动作(SARSA)-SARSA 通过将策略传递给代理开始。政策本质上是告诉代理人某个行为产生回报的可能性。
  2. Q-Learning——在 Q-Learning 中,没有政策。这意味着代理必须在没有指导的情况下在环境中导航,使得学习是自我驱动的。
  3. 深度 Q 学习-深度 Q 学习使用自我驱动导航,但也利用神经网络。神经网络会记住过去的行为及其回报,这有助于影响未来的决策。

强化学习的利与弊

现在我们已经了解了更多关于强化学习的知识,让我们来回顾一下其中的利弊:

赞成的意见

  • 该模型可以纠正在训练过程中发生的错误(例如错误分类或决策)
  • 如果出现错误,重复同样错误的机会就会大大减少
  • 当只想了解环境并与之互动时,这个模型是有用的
  • 目标是最大化性能并达到给定环境下模型的理想行为
  • 保持探索(测试不同的方法以查看其结果)和开发(使用学到的方法以产生最佳结果)之间的平衡
  • 这是实现长期结果的首选方法,而长期结果是很难实现的

这些只是一些优点,所以让我们看看一些缺点:

骗局

  • 过多的强化会导致状态过载,从而降低价值
  • 简单解决方案不推荐
  • 真实物理系统的学习受到维数灾难的限制(在高维空间中分析和组织数据时出现的现象,这些现象不会在低维空间中出现,如日常经验的三维物理空间)
  • 真实世界样本的诅咒(例如维护使用这种学习的机器人,因为硬件昂贵并且会随着时间而磨损,这需要对系统进行昂贵的维护和修理)
  • 数据饥渴(意味着它需要尽可能多的数据和计算)
  • 强化学习的大多数问题需要学习技术的组合来纠正,例如因为与深度学习配对

结论

在今天的文章中,我们谈到了强化学习。我们了解到,这是数据科学中使用的另一种机器学习技术,在为代理创建一组指令(策略)以供其遵循后,该技术在无人监督的情况下工作。我们学习了代理如何探索不同的可能性,并面临相关的奖励或惩罚,因此它可以学习哪些决策产生最佳结果。然后,它可以在探索新决策和利用已经发现的最佳结果之间取得平衡。我认为理解如何奖励模型是理解强化学习最困难的部分。尽管如此,看完这些例子后,我对它的含义更有信心了。我希望你也发现这是有用的和有趣的。一旦弄清楚了这一点,我们回顾了学习方法的一些优点和缺点。现在我们对强化学习有了更多的了解,我希望你对这种技术有更好的理解,如果你曾经尝试过使用强化学习,请在评论中告诉我。感谢您的阅读,一如既往,我们将在下一篇文章中再见。干杯!

用我的 每周简讯 免费阅读我的所有文章,谢谢!

想阅读介质上的所有文章?成为中等 成员 今天!

查看我最近的一些文章

https://medium.com/codex/pointless-automation-dropbox-file-downloads-with-python-e1cb26a41fff https://python.plainenglish.io/getting-started-with-seq-in-python-4f5fde688364 https://medium.com/codex/javascript-cdns-and-how-to-use-them-offline-e6e6333491a3

参考资料:

https://www.techtarget.com/searchenterpriseai/definition/reinforcement-learning https://deepsense.ai/what-is-reinforcement-learning-the-complete-guide/ https://www.geeksforgeeks.org/what-is-reinforcement-learning/ https://www.simplilearn.com/tutorials/machine-learning-tutorial/reinforcement-learning https://pythonistaplanet.com/pros-and-cons-of-reinforcement-learning/

强化学习简介:马尔可夫决策过程

原文:https://towardsdatascience.com/reinforcement-learning-intro-markov-decision-process-c73a030f045d

照片由 Unsplash 上的延斯·勒列拍摄

马尔可夫决策过程是强化学习中最基本的知识之一。它被用来表示优化问题中的决策。

我们在这里提出的版本是有限 MDP,它分析离散时间,离散行动问题,涉及一定量的随机性。这意味着相同的动作序列有可能在两次尝试之间导致不同的情况。

在这里,我们着重于呈现数学框架的一般结构,例如所涉及的元素和将它们链接在一起的方程。我们还将讨论在用 MDP 表述问题时可能会遇到的一些困难。

代理和环境之间的相互作用

在大多数现代文献中,你的学习系统通常被称为代理。这个术语不仅仅包括最近算法中的神经网络估计器。代理与环境交互以解决任务。稍后,我们将进一步详细讨论环境的构成,因为它可能会成为一个问题。环境依赖于两个主要部分:它的转换和奖励功能。两者都与系统和任务的性质有关。

例如,下图显示了一个环境,其中有三种状态和四种可供代理使用的操作。代理必须完成的任务是转换所有三种状态并返回。有些状态有积极和消极的奖励,可能对应于边际收益或有害的附带目标。这个观点来自一个外部的、无所不能的观察者,他能从整体上看问题。对于我们的代理人来说通常不是这样,他们只能观察他们周围的情况和他们直接行动的结果。

问题 : 你能用随机策略求解第一个状态的预期收益吗?如果是,请回复!这种策略在任何状态下都有均等的机会选择行动 0 或 1。请继续关注关于价值函数的文章!

图 1,作者:一个简单的 MDP 图,包括状态、动作、转换和奖励

图形表示是 MDP 的可视化综合,表示为元组:

其中:

  • S 是代理可达到的所有状态的集合。它们是上图中的蓝色节点。
  • A 是动作的集合,称为动作空间。这通常由 S 中的状态 s 决定,即在该状态 s 下可用的动作。它们是上图中的橙色节点。
  • P 是转移概率矩阵。它对应于动作 a 将状态 s 转换到状态s’的概率。它们是上图中一些动作后记录的概率,例如使用动作 1 后从状态 2 转换到状态 1 的概率为 0.6。
  • R 是由 ss’之间的过渡产生的直接奖励。它们由红色曲线指向的整数表示。

虽然该图代表了整个系统,但如果我们想分析一个代理的体验,我们必须在其上下文中描述每个决策。这个上下文一般在一个情节内,一个从 t=0 到 t 的有限序列。

一集的长度一般是由任务决定的,比如用 Y 步解决一个迷宫。但是,有些问题可能是无限的,看不到可控制的尽头,比如金融交易或低级机器人。在这种情况下,一集的结尾是人为定义的,代理人在一段经历中学习。

图 2,作者:代理-环境交互循环

每个交互被定义为元组:

从当前状态 s,代理选择动作 a ,这导致新状态s’,奖励 r 。基于策略π选择动作,策略π是学习算法在任何 RL 方法中试图优化的主要部分。

策略π是状态表示和动作之间的映射。作为人类,我们有类似的行为:当回家时,我们知道在特定的十字路口左转,或者当交通灯变红时踩刹车。我们识别模式,并知道哪些行为符合它们。

但是,要识别某样东西,它需要一个独特的形状,这样我们才能做出正确的决定。如果一栋建筑中的所有走廊都完全一样,没有任何标志,你能在特定时刻仅用眼睛看到的东西找到周围的路吗?

代表系统

状态表示的主要目的是给代理足够的信息,让它知道自己处于哪个状态,下一步应该去哪里。这些可以包括传感器读数、图像或处理过的指示器。

在只需要观察的任务中,例如机械臂拿起一个物体,只需要传感器。如果我们知道臂和物体的位置,我们可以预测转子的增量。

在需要一些历史概念的任务中,处理过的指示器是必要的。在自动驾驶汽车中,感知汽车的位置与知道汽车要去哪里一样重要。

在这两种情况下,适当的信息粒度都很重要。可以使用适合于该任务的任何粒度,从最低的传感器到最高的抽象级别。

当机械臂控制转子时,告诉它必须“向左”走可能还不够:它必须走多远?1 增量,四分之一圈还是一整圈?它必须适应每个任务,以方便代理的学习。

这样,控制器代理可以被训练给出高阶动作,例如“左、右、下、上”,驾驶代理可以将其解释为输入。然后,这种驱动力又会通过电压或增量等低级动作作用于转子。

机器人问题凸显了另一个关注点:我们在哪里定义代理的属性和环境之间的区别?手臂在空间中的绝对位置或库存的数量为代理提供了关于环境的重要信息,但与他们的行为相关联,这些行为应该是代理的一部分,而不是环境的一部分。

然而,它们是行动的结果,无论是对马达施加电压还是购买物品。代理人和环境之间的界限是由代理人直接控制的变量。因此,最后施加的电压不应该被认为是环境的一部分,而是药剂的一部分。

创造你的目标

代理人的目标是从一系列行为中获得最大的期望回报。由于预期的回报是通过整个剧集获得的累积分数,代理可能会做出局部次优的选择,以便在稍后达到更有利可图的结果。这一点在国际象棋比赛中最为明显,在这种比赛中,如果能获得有利的位置,玩家愿意牺牲棋子。奖励函数定义了什么被认为是有利的或不利的。

奖励

对特定的状态转换给予奖励。他们可以在每一步或一集的结尾被触及。因为代理人试图最大化它们,适当地塑造奖励函数可以影响我们希望看到的代理人表现出的期望行为。它可以是任何价值函数,只要它突出了期望的最终目标。

  1. 对于有明确最终目标的任务,当目标达成时,可以给予一大笔奖励。
  2. 对于视频游戏,可以利用游戏中的分数。
  3. 然而,对于以安全为先的机器人任务,我们也需要一种阻止危险行为的方法。
  • 如果人类的安全处于危险之中,用一个大的负面奖励来提前结束一个情节可以确保代理人迅速避开这些行为。
  • 对于不太严重的情况,负奖励确保代理人不会停留在那个状态。

奖励可以包括不同职能的组合,只要它符合基本的度量准则。因此,它应该是凸的,以便给定一个顺序,您能够使用该顺序对性能进行排序。如果分数越高越好,就不应该出现分数越低排名越高的情况。

最后,当使用复合函数时,它们应该都在相同的数量级上,否则低阶函数很少被考虑。

返回

我们将收益 G 定义为我们的代理在一集期间获得的累积奖励:

在许多 RL 问题中,情节的长度相当长。因此,在一个事件的第一步可能会有令人困惑的预期回报,没有明确的行动。一种解决办法是引入贴现因子。它缩小了代理人对其所能获得的回报的预期。我们的收益 G 变成了:

正如我们所看到的,选择一个合适的γ意味着更远的步骤对行动的回报几乎没有贡献:

因此,一集内给定时间的预期回报可以通过以下公式获得:

作为参考,这里有一个基于γ的代理可以预见多远的尺度:

  • 0.9 = 10 步
  • 0.99 = 100 步
  • 0.999 = 1000 步

定义 MDP 背后的数学

从前面的章节中,我们已经看到了转移概率矩阵,以及如何计算一集的预期收益。这些概念是允许我们构思决策过程的基础!

我们可以估计在当前状态下采取任何行动可以获得的回报:

当前状态 s 是固定的,所以我们可以迭代 s 中可用的所有动作 a 。对于每个动作,我们估计到达状态s’的概率,这应该奖励我们一个分数 r 。在许多问题中,函数 Pr 是已知的,但对于其他问题,它是使用蒙特卡罗抽样等技术来估计的。

从这个等式中,我们也可以忽略回报,而专注于状态转移概率,这是我们接下来可能要做的:

为了仅获得预期回报,对于代理在其状态下可用的每条路径,我们使用“状态-动作-下一状态”估计回报:

马尔可夫性质及其重要性

这通常需要更多的直接感觉,但从来没有超过所有过去感觉的完整历史。来自萨顿和巴尔托,2018。

马尔可夫特性是状态信号中包含的内容的一个要求,它完成了“表示系统”一节中提到的内容。

信息必须只包含直接测量的感觉。保留所有过去的观察是非常不鼓励的,因为这种做法很难。但是,对过去行为的感觉是可能的,例如在自动驾驶汽车系统中突出运动。这样做的目的是让图片尽可能的浅,以便于学习一个策略π。

这个属性是必需的,因为数学框架假设下一个状态s’直接链接到当前状态 s 和被选择的动作 a 。如果这种关系不成立,主要方程就失效,学习就变得不可能。

然而,完美的马尔可夫问题在现实世界中很少见,这也是为什么一些宽容被接受的原因,近年来出现了新的框架,包括部分抽象的 MDP 和部分可观测的 MDP ( POMDP )。

这些使我们能够对包括扑克在内的问题进行建模,扑克是一个著名的不完全问题,每个玩家都不知道对手的牌。游戏中涉及的心理学也使它变得更加复杂。玩家可能经常虚张声势,或者在没有把握的时候弃牌。

因此,扑克需要在直接环境中不可获得的信息,因此与马尔可夫性质相矛盾。然而,我们已经看到深度强化学习代理击败人类扑克玩家的一些最新结果。这取决于框架的问题,并找到一种方法来传达足够的信息给代理。

我希望这篇文章能帮助您更好地理解这个无所不在的框架!如果你想获得更多这样的文章,请在 TwitterLinkedIn 上联系我。

稍后再见,了解更多强化学习!

强化学习(RL)——什么是强化学习,它是如何工作的?

原文:https://towardsdatascience.com/reinforcement-learning-rl-what-is-it-and-how-does-it-work-1962cf6db103

强化学习

对强化学习的温和介绍,对概念和术语有清晰的解释

图片由皮克斯拜的 Gerd Altmann 提供

简介

与我以前的文章略有不同,这次我将不包括 Python 代码,因为我想避免读者的信息过载。而是简单介绍一下强化学习(RL) ,讲解一下主要思路术语

我的后续文章将更深入地研究各个 RL 算法,并提供详细的 Python 示例。因此,记得订阅以便在它们发布时直接发送到你的收件箱。

内容

  • 机器学习领域中的强化学习
  • 什么是强化学习?
    -主体和环境
    -行动空间
    -状态(观察)空间
    -奖励(和折扣因子)
    -探索与剥削
  • 训练模型的不同方法
  • 摘要

ML 世界中的强化学习

简单来说,强化学习类似于婴儿如何学习周围环境,或者我们如何训练狗。我们允许他们与环境互动和探索环境,并提供积极/消极的奖励来鼓励特定的行为。

由于 RL 的机器学习方法明显不同于其他类型的 ML,我们将 RL 放在我们的机器学习宇宙图中的一个单独的类别中。

下面的图是交互式的,请点击👇在不同的部分探索和揭示更多。

机器学习算法分类。由作者创作的互动图表。

如果你喜欢数据科学和机器学习 ,请 订阅 获取我的新文章邮件。如果你不是中等会员,可以在这里加入https://bit.ly/36Mozgu

什么是强化学习?

强化学习(RL)是一种机器学习算法,用于训练智能代理在特定环境中通过最大化预期累积回报来执行任务或实现目标。这里有几个例子:

  • 教一个基本的人工智能如何玩电脑游戏,比如雅达利的《太空入侵者》或任天堂的《超级马里奥兄弟》
  • 教一个更高级的人工智能玩现实生活中的游戏,比如国际象棋或围棋
  • 教自动驾驶汽车(或模型车)如何在模拟或现实生活环境中驾驶(参见 AWS DeepRacer )

代理和环境

RL 中的两个关键组件是代理和它的环境代理代表一个玩家、一辆汽车或其他可以与其环境互动的“智能角色”。

同时,环境是代理“生活”或操作的“世界”。

代理及其环境。作者插图。

动作空间

代理可以在其环境中执行动作。例如,马里奥可以做像向左走、向右走、向上跳等事情。,里面是它的超级马里奥游戏关卡。同样的情况也适用于下面我虚构的游戏插图中的代理。

在其环境中执行动作的代理。作者插图

可用的动作空间可以是离散的连续的

  • 离散动作空间——就像在超级马里奥兄弟的例子中,代理只能执行有限数量的动作,使其成为离散的。因此,我们总是可以列出所有可用的操作。
  • 连续动作空间 —代理可以采取无限数量的动作。例如,自动驾驶汽车可以左转 1 度、1.2 度、1.2432 度等。因此,我们无法列出所有可能的行动。

状态(观察)空间

下一个重要元素是环境的观察状态

  • 一个状态描述了代理从环境中获得的信息。例如在计算机游戏中,它可以是显示代理的确切位置的环境的冻结帧。请注意,状态是对世界的完整描述,即完全观察到的环境,就像棋盘的全景。
  • 一个观察是状态的部分描述,也就是说,它只提供了环境的部分视图。例如,超级马里奥兄弟的冻结帧将仅向我们显示接近代理的部分级别,而不是整个级别。

然而,在实践中,状态和观察是互换使用的,所以当我们不一定对环境有一个完整的看法时,不要惊讶地看到“状态”这个术语,反之亦然。

状态 vs 观察。作者插图

奖励(和折扣系数)

也许这个谜题最关键的部分是奖励。我们训练代理采取“最佳”行动,根据行动是否使代理更接近实现特定目标,给予积极、中立或消极的奖励。

假设我们在玩一个游戏,目标是抓一只松鼠。代理将从随机探索它的环境开始,如果它靠近松鼠,我们将奖励代理(+1 分),如果它远离松鼠,我们将“惩罚”(-1 分)。最后,当代理实现了它的目标,也就是抓到了松鼠,我们会给一个很大的奖励(+1000)。

只要采取随机行动并获得相应的奖励,代理人就可以了解需要做什么来使的累积奖励最大化,并使实现目标

奖励是给代理的,所以它学习如何实现游戏的目标。作者插图。

请注意,本例中的奖励值仅用于说明目的。我们在做强化学习的时候,往往会选择创建自己的奖励函数。通常,你的奖励函数的“质量”将是你的模型成功的重要驱动因素(例如,查看奖励函数如何在 AWS DeepRacer 竞赛中使用)。

奖励的另一个重要方面是折扣系数(gamma) 。它的范围可以在 0 到 1 之间,但我们通常会选择 0.95 到 0.99 之间的值。折扣系数的目的是让我们控制对短期和长期回报的偏好。

例如,在国际象棋比赛中,抓住对手棋子的一步棋会得到奖励。然而,我们不希望经纪人优先考虑这一举动,如果这让我们长期处于亏损的境地。因此,平衡短期和长期回报至关重要。

探索与开发

我们要学习的最后一个概念是探索和开发之间的权衡。我们通常希望鼓励代理对其环境进行某种程度的探索,而不是将全部时间用于开发其已有的知识

一个简单的现实生活中的例子是选择午餐。假设你是汉堡的超级粉丝,每天午餐都吃汉堡。因此,你是在利用现有的知识(你对汉堡的口味)

然而,你也可以在你的日常生活中增加一个的探索元素。例如,你可以偶尔尝试不同的食物,比如热狗或烤肉串。当然,通过探索,你会有失望的风险(负面体验/回报),但你也会发现一些你可能比汉堡更喜欢的东西(正面体验/回报)。

探索与开发。作者插图

在强化学习的背景下,我们希望鼓励代理花部分时间去探索。否则,代理可能会不断重复相同的动作,而没有意识到还有更好的动作要做。我们通过一个附加参数(ε)做到这一点。我们指定代理在多大比例的情况下应该采取随机行动(即探索)

**

训练你的模型的不同方法

当我们使用强化学习时,我们希望训练代理采取“最佳”行动来实现其目标。我们称之为 Policy(𝜋) 。它有点像代理的“大脑”。

例如,如果我们想教一个代理如何玩电脑游戏,它需要学习在每种情况下采取什么行动。换句话说,我们希望找到一个最优的 Policy(𝜋) ,从长期来看,它能带来尽可能高的回报。

要找到最佳策略,我们可以使用以下方法之一:

  • 基于策略的方法 —我们直接训练代理在哪个状态下采取什么行动。
  • 基于价值的方法 —我们训练代理识别哪些状态(或状态-动作对)更有价值,因此它可以由价值最大化来指导。例如,在捕捉松鼠的游戏中,站在离松鼠一步远的地方会比站在十步远的地方描述更有价值的状态。

我将在接下来的文章中更详细地介绍每种方法,因为我们将更深入地研究各种 RL 算法,并学习如何用 Python 对它们进行编码。

总结

概括地说,我们已经了解到,强化学习用于教导代理在其环境中操作,并通过根据代理在不同状态下采取的动作向代理提供积极的、中立的或消极的奖励来实现目标或目的(例如,赢得一场游戏)。

我们通过指定随机选择代理的行动比例来平衡探索开发,并且我们应用折扣因子(gamma) 来控制代理对短期与长期回报的偏好。

最后,我们通过优化 Policy(𝜋、来训练模型(教导代理人),我们可以通过基于直接政策的方法基于间接价值的方法来完成。

在我的下一篇文章中,我将带您了解 Q-learning 算法(基于值的方法),并向您展示如何在 Python 中使用它来训练一个代理在一个名为冰封湖的简单游戏中从头到尾成功导航。

Gif 图片由作者使用开放 AI 的健身房 Python 库内的冰湖游戏的组件创建。

请不要忘记关注并订阅,这样你就不会错过和我一起探索强化学习的乐趣。

干杯!🤓
索尔·多比拉斯

如果你已经花光了这个月的学习预算,下次请记得我。 我的个性化链接加入媒介:

*https://bit.ly/3J6StZI *

模拟数据上近似混合整数分配解的强化学习

原文:https://towardsdatascience.com/reinforcement-learning-to-approximate-mixed-integer-allocation-solutions-on-simulated-data-ce8f770fdb5b

这个副业项目创建了一个实验,该实验生成模拟数据,创建混合整数解,并训练强化学习网络来近似最优分配

https://upload . wikimedia . org/Wikipedia/commons/d/de/Distribution _ centre _ % 28J _ Sainsbury % 27s % 29 . jpg

注意:该项目在源代码或数据方面不隶属于任何组织。这是我从零开始开发的一个个人项目。

太久没读了

本文总结了在分配优化领域构建混合整数规划模型时需要考虑的一些业务成果和目标。

开发的源代码创建了以下内容:1)一个框架,用于模拟商店风格-颜色目标库存、承诺投资和配送中心的捆绑包;2)一个数据管道,用于创建最佳 MIP 分配建议;3)一个强化学习算法,用于近似分配建议。

看起来强化学习算法能够学习一些策略来给商店分配捆绑包。然而,该算法的有效性相对来说比 MIP 解决方案差,并且可能贪婪地接近来生成分配提议。

如果您有构建 RL 模型的经验,并且希望改进该算法,请随时创建一个拉取请求。这个项目可以在我的 Github 上找到。

目的

本实验的目的是观察强化学习(RL)算法是否可用于近似混合整数规划(MIP)模型的分配建议。

RL 分配算法的用例是实验性的,对 RL 解决方案和神经网络的改进可能会改变用例。有可能使用 RL 算法来预解 MIP 解空间的某一部分。在某些情况下,它可以用来代替 MIP 模型。也有可能为不同的输入数据或场景建立多个 RL 模型。

分配方案的质量取决于更多因素,而不是解决方案与目标库存的接近程度,即,它还取决于模型能够产生多少商业价值。以下是一些需要考虑的因素:

  1. 超额/不足分配成本,其中不足分配成本是在目标库存下不分配一个单位的成本,超额分配成本是在目标库存上分配一个单位的成本。这在有多个 SKU 的包的情况下发挥作用。这些是设计分配算法时要考虑的可能特征:1)产品的毛利、周期库存、安全库存、可视最小值和服务水平
  2. 分配的统计离差,即当没有足够的库存来满足所有商店的最优分配时,分配库存时算法的公平性如何。
  3. 分配算法是否提高了销售额?这需要复杂的模拟和/或销售损失数据。
  4. 建议算法如何处理不同级别或不同优先级的商店?

出于本实验的目的,MIP 目标和约束条件尽可能简单。在实践中,目标可能需要考虑上述因素和潜在的其他业务成果。

模拟数据

随机生成以下数据元素以创建分配)来训练 RL/MIP 模型:

作者图片

“1”:{“AR”:1,”AB”:1,”AG”:1,”BR”:1,”BB”:1,”BG”:1,”CR”:1,”CB”:1,”CG”:1}“2”:{“AR”:3,”AB”:3,”AG”:3,”BR”:0,”BB”:0,”BG”:0,”CR”:0,”CB”:0,”CG”:0}“3”:{“AR”:0,”AB”:0,”AG”:0,”BR”:3,”BB”:3,”BG”:3,”CR”:0,”CB”:0,”CG”:0}

对于这个实验,对于训练数据集有 200 个随机生成的分配,对于验证数据集有 100 个随机分配。这些数据被保存到有效载荷数据结构中。对于这些有效载荷中的每一个,MIP 模型求解并将解存储到另一个数据结构中。

混合整数规划模型

MIP 模型用输入数据和决策变量构建潜在解决方案空间的计算图。在给定约束和目标函数的情况下,求解器使用一种算法,即“分支-切割”,来有效地搜索最优解。然而,解空间可能很复杂和/或很大,这可能增加 MIP 模型的求解时间。

MIP 模型使用目标库存和分配方案之间的平方差作为目标。

MIP 目标

作者图片

最优分配被定义为 SKU 每个商店的目标库存 T 和承诺库存 I 之间的差额。一个变量 U 用于表示商店收到的包数。跨束的分解 SKU 数量与最优分配进行比较。

约束

该特定目标需要一个约束,即确保商店间分配的捆的数量等于或小于配送中心可用的捆的数量。

这一目标的另一种表述可以使用四个辅助变量来考虑分配不足/过多的成本。

爆炸 SKU 量

根据分配建议计算建议库存:

作者图片

可以通过取捆绑包-PF 和建议分配之间的点积找到分解的 SKU 建议数量,其中 PF 是每个捆绑包的 SKU 乘数。

强化学习算法细节

当网络学习时,分配算法有可能得出不可行或不真实的分配。RL 算法使用连续的、可能是分数的车间捆绑分配表示法。然而,这个问题本质上是离散的。

RL —奖励

作者图片

输入数据

作者图片

RL 网络

作者图片

RL 约束

作者图片

RL 奖励功能

作者图片

验证指标

对于这个实验,对于训练数据集有 200 个随机生成的分配,对于验证数据集有 100 个随机分配。下面显示的分析和图用于验证集。

平均绝对误差比较

下图显示了 RL 解决方案与 MIP 解决方案的 MAE。

作者图片

RL 溶液的 MAE 和 MIP 溶液的 MAE 之间似乎存在线性关系。在最坏的情况下,RL 解决方案的 MAE 大约是 MIP 解决方案的两倍。

相关分析

下图显示了 MIP 建议、RL 建议和目标的最终风格-颜色(SKU)分配之间的相关性。

作者图片

跨验证集的平均相关性:

  • MIP/RL:0.46
  • MIP/Target:0.52
  • RL/Target:0.20

MIP 和 RL 解决方案在分配方案上最大的两个区别是

下图显示了 RL 和 MIP 解决方案之间分配方案的两个最大差异(MAE)。

作者图片

有趣的是,即使在分配方案之间有很大的差异,这并不总是转化为更高的 MAE。0

RL 解决方案的最低两个 MAE 分配方案

下图显示了 RL 解决方案相对于目标方案的两个最佳分配方案(最低 MAE)。

作者图片

RL 解决方案的最高两个 MAE 分配提案

下图显示了 RL 解决方案相对于目标方案的最差的两个分配方案,最高 MAE。

作者图片

结论

该结果似乎表明,使用连续的、分数的、束表示,为分配优化创建有效的强化学习算法是可能的。然而,在强化学习算法中使用这种表示有许多潜在的问题。例如,如果将一个捆绑包平均分配给多个商店,会发生什么情况?在 RL 方法之上,需要另一种算法来解决这些冲突。

过度分配的成本、商店-SKU 最优分配相对于建议数量的幅度,以及跨商店的建议的“均匀性”或“公平性”可能需要领域专业知识来满足特定的业务需求。MIP 方法可以通过添加和调整目标中的成本项来处理来自领域专家的输入,以便处理多个业务需求。强化算法没有这种可调输入。

如果你有建立 RL 模型的经验,并且想改进这个算法,请随时联系我。

资源

强化学习简介
什么是 Q-learning?
谷歌强化学习参考

接触

领英:https://www.linkedin.com/in/rkotwani/

SARSA 强化学习——Q-学习算法的一个很好的替代方案

原文:https://towardsdatascience.com/reinforcement-learning-with-sarsa-a-good-alternative-to-q-learning-algorithm-bf35b209e1c

强化学习

如何使用状态-动作-奖励-状态-动作(SARSA)算法教智能代理玩简单的游戏

图片由 bamenny 来自 Pixabay

介绍

机器学习的美妙之处在于,它不缺乏处理复杂任务的方法。例如,强化学习(RL) 从业者已经开发了多种算法,能够教会智能代理导航他们的环境并执行动作。

在本文中,我深入研究了一种叫做 SARSA (状态-动作-奖励-状态-动作)的 RL 算法,它是 Q-Learning 的近亲。

如果您不熟悉强化学习或 Q-Learning,请随意探索我以前的文章:

内容

  • SARSA 在机器学习(ML)算法领域的地位
  • SARSA 是如何工作的?
    • Q 表
    • SARSA 算法
      -与 Q 学习的比较
  • Python 示例,我们教一个智能代理如何玩 Taxi-v3 游戏

SARSA 在机器学习(ML)算法领域的地位

机器学习算法之间存在相似之处,这使我们能够根据架构和用例对它们进行分类。因此,我创造了一个观想,帮助我们看到整个 ML 宇宙。

图表是交互式的,请点击👇在不同的类别上探索和揭示更多。

你会在基于值的方法组中的强化学习ML 算法分支下找到 SARSA (下一节会有更多相关内容)。

机器学习算法分类。由作者创建的互动图表。

SARSA 是如何工作的?

q 表

SARSA 是一种基于值的方法,类似于 Q-learning。因此,它使用一个 Q 表来存储每个状态-动作对的值。使用基于价值的策略,我们通过教代理识别哪些状态(或状态-动作对)更有价值来间接训练代理

通常,我们从 Q 表中初始化为 0 的所有值开始,并使用训练来优化 Q 表。然后,我们的代理可以使用存储在 Q 表中的信息来选择每个状态下的最佳动作(即,代理选择的每个状态下具有最高值的动作)。

下面是一个空 Q 表的示例:

空 Q 表。图片作者作者

SARSA 算法

SARSA 是一个基于策略的算法,这是它与 Q-Learning(非策略算法)的区别之一。 On-policy 是指在训练过程中,我们对 agent 使用相同的策略 act (代理策略)和updatevalue function(更新策略)。同时,使用非策略方法,我们使用不同的策略来执行和更新。

现在让我们来看看 SARSA 算法本身:

SARSA 算法。图片来自作者

  • q 是值函数,左边 Q(𝑆𝑡,𝐴𝑡项是特定状态-动作对的新值。注意,S 指状态,A 指动作。
  • 在等式的右边,我们找到相同的 Q(𝑆𝑡,𝐴𝑡项,在这种情况下,它是同一状态-动作对的当前值。
  • 为了更新当前值,我们在代理采取行动后获取奖励( 𝑅𝑡+1),为下一个状态-下一个行动对 𝛾𝑄(𝑆𝑡+1,𝐴𝑡+1) 加上用γ贴现的值,并减去当前值** Q(𝑆𝑡,𝐴𝑡).**
  • 因此,方括号中的项产生正值、零或负值,这导致 Q(𝑆𝑡,𝐴𝑡).的新值增加、不变或减少注意,我们还应用了一个学习率(alpha) 来控制每次更新的“大小”。

由于 SARSA 使用时间差分(TD)方法,该算法将在每一步后不断更新 Q 表,直到我们达到最大迭代次数或解决方案收敛到最优。

与 Q-Learning 的比较

如果我们看一下 Q-Learning 算法使用的等式,我们可以看到,区别在于它如何选择下一个状态的值。即 Q-Learning 基于 Q-table 中的现有值为下一个状态取最大值。同时, SARSA 取下一个状态-下一个动作对的值,,如上所示。

q-学习算法。图片由作者提供。

Python 示例,我们教智能代理如何玩 Taxi-v3 游戏

为了更好地理解它是如何工作的,让我们使用 SARSA 算法来教代理如何玩在 Open AI 的健身房环境中可用的 Taxi-v3 游戏。

请看下面的 gif 图片,它展示了游戏的样子:

Gif 图片由作者使用来自 Taxi-v3 游戏的组件创建。

游戏的目标是让出租车从一个上车地点搭载一名乘客(看到一个人),该地点可以是四个彩色方块中的任何一个,然后将该乘客带到下车地点(看到一座建筑),该地点也可以是四个彩色方块中的任何一个。

默认游戏奖励如下:

  • -每一步 1 分,除非触发了另一个奖励
  • 运送一名乘客+20
  • -10 分因非法执行“上车”和“下车”行动

设置

我们需要获得以下库:

  • Taxi-v3 游戏环境打开艾的健身房库
  • Numpy 用于数据操作
  • Matplotlib 和内置的 IPython 库,用于显示代理如何导航其环境

让我们导入库:

上面的代码打印了本例中使用的包版本:

numpy: 1.23.3
gym: 0.26.0
matplotlib: 3.6.0

接下来,我们设置一个 Taxi-v3 环境:

设置好环境后,让我们检查一下描述状态空间动作空间:

Environment Array: 
[[b'+' b'-' b'-' b'-' b'-' b'-' b'-' b'-' b'-' b'-' b'+']
 [b'|' b'R' b':' b' ' b'|' b' ' b':' b' ' b':' b'G' b'|']
 [b'|' b' ' b':' b' ' b'|' b' ' b':' b' ' b':' b' ' b'|']
 [b'|' b' ' b':' b' ' b':' b' ' b':' b' ' b':' b' ' b'|']
 [b'|' b' ' b'|' b' ' b':' b' ' b'|' b' ' b':' b' ' b'|']
 [b'|' b'Y' b'|' b' ' b':' b' ' b'|' b'B' b':' b' ' b'|']
 [b'+' b'-' b'-' b'-' b'-' b'-' b'-' b'-' b'-' b'-' b'+']]State(Observation) space: Discrete(500)
Action space: Discrete(6)

您可以看到环境布局与本节开头的 gif 图像中显示的布局相匹配。

状态空间包含 500 个离散状态,代表 25 个出租车位置、5 个可能的乘客位置(包括乘客在出租车中的情况)和 4 个目的地位置的所有可能组合,即 25 * 5 * 4 = 500。

同时,动作是:

  • 0:向南移动
  • 1:向北移动
  • 2:向东移动
  • 3:向西移动
  • 4:搭载一名乘客
  • 5:让乘客下车

在我们开始模型训练之前,让我们的代理(出租车)执行随机动作,并渲染它在环境中的运动。

以下是上述代码的输出:

作者使用来自 Taxi-v3 游戏的组件创建的 Gif 图像。

模特培训

设置完成后,让我们使用 SARSA 为我们的代理在这个游戏中找到最好的 policy(𝜋)

我们首先初始化几个参数:

为了平衡探索与开发,我们将在整个培训中改变 epsilon。我们将从 epsilon=1(纯探索)开始,并随着每一集而衰减 epsilon,以逐渐从纯探索转向开发。

接下来,让我们初始化 Q 表。正如我们在上一节中所看到的,这将是一个 500 x 6 的表格,行代表状态,列代表动作。我们用全 0 初始化 Q 表,因为在开始训练之前,我们不知道每个状态的价值。

以下是初始化 Q 表的片段:

array([[0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0.],
       ...,
       [0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0.]])

SARSA 是一个 on-policy 算法,因此我们将使用相同的ε-贪婪策略来操作和更新 Q 表。现在让我们定义用于培训和评估的函数:

如果你仔细观察 update_Q 函数,你会发现它包含了上一节分析的 SARSA 算法方程。

最后,让我们定义我们的培训功能:

现在我们可以调用训练函数并查看结果:

在训练之后,我们得到 Q 表作为输出。但是,很难说是否最优。因此,我们需要做一些额外的评估。以下是培训后 Q 表的一个片段:

array([[  0\.        ,   0\.        ,   0\.        ,   0\.        ,
          0\.        ,   0\.        ],
       [-15.88478151, -14.78900627, -16.69581259, -14.66434947,
        -12.6181495 , -23.80156577],
       [ -6.19468628,  -2.88294982,  -6.49649702,  -2.24076811,
         -0.99650814, -13.12010172],
       ...,
       [ -4.25834456,   2.30997394,  -5.05986368,  -3.97161199,
        -12.53096203, -15.46710745],
       [-17.96569618, -12.49725207, -16.13686029, -10.22935569,
        -24.18094778, -27.10102464],
       [ 12.22333031,   6.43016417,  12.76697657,  15.36125068,
          4.18029065,   2.6316856 ]])

评估

既然很难通过考察 Q 表来判断结果,那我们就来运行一万集来评价代理的表现。我们将使用平均值标准差报酬的最小/最大值等统计数据以及分配图

我们调用上述函数,并使用以下代码显示结果:

模型评估结果。图片由作者提供。

让我们来看看结果:

  • 一万集的平均回报是 7.96 +/- 2.59。它表示奖励有所变化,但那是因为在每集开始时,出租车、乘客和下车地点会发生变化。因此,每次我们需要不同数量的步骤来完成任务,所以我们并不总是得到相同的奖励(每走一步减 1 分)。
  • 最小奖励是 3,这证实了我们总是得到正的奖励。查看地图,我们可以很容易地计算出,如果我们在相对的角落初始化出租车和乘客,下车位置与出租车的原始位置相同,则需要 17 步(16 次移动+ 1 次上车动作)。因此,为最远的场景选择最佳路线将导致奖励 3 (20 - 17)。
  • 最高奖励是 15 英镑,所以让我们看看为什么我们不能期望更高。如果乘客在下车地点被初始化,该集立即结束(即,不是有效场景,因此没有希望获得 20 分)。因此,我们所能希望的最好情况是乘客和出租车在同一地点初始化。如果我们选择两个最接近的颜色方块(红色和黄色),代理将使用 5 次移动(1 次上车,4 次移动)将乘客从上车地点运送到下车地点,给我们最大可能的奖励 15 (20 - 5)。
  • 奖励分布图显示奖励接近正态分布。因此,虽然它不能证明我们有一个最优策略,但它表明代理人正在做出理性的行动。

确保我们有最优策略的一种方法是计算每个初始状态所需的最少步骤数。然后验证代理从未采取超过最小数量的步骤。然而,我将把它作为一个练习留给读者。

相反,让我展示一个按照我们的策略执行行动的代理的可视化。

上面的代码给了我们下面的可视化,它确认了,至少在下面捕获的两个场景中,代理已经采取了一个最佳的方法:

作者使用来自 Taxi-v3 游戏的组件创建的 Gif 图像。

结束语

现在你知道了 SARSA 是如何工作的,以及它与标准的 Q 学习算法有什么不同。健身房图书馆提供了许多其他环境(游戏),所以我鼓励你用你新学到的知识来训练一个代理玩不同的游戏。

你可以在我的 GitHub 库 找到完整的 Python 代码。请注意,有两个 Jupyter 笔记本,一个用于 Taxi-v3 游戏,另一个用于冰封湖游戏。

请不要忘记订阅了解其他强化学习算法,我将在我即将发表的文章中涉及这些算法。

干杯!🤓
索尔·多比拉斯

如果您不是中等会员, 请考虑使用我下面的个性化链接加入:

https://bit.ly/3J6StZI

关系与文档数据模型

原文:https://towardsdatascience.com/relational-vs-document-data-models-913f04d8c754

关系数据模型和文档数据模型的概述以及它们之间的比较

约书亚·索蒂诺在 Unsplash 上拍摄的照片

在本文中,我将介绍关系文档数据模型,之后我们将看到在选择使用哪一个时需要考虑哪些因素。

介绍

关系数据模型

本质上,关系可以表示为一个表,它是元组或行的集合。

每个关系都由一组严格的字段或列定义,称为模式。当数据被写入表时,这个模式被严格执行(称为模式写入)。通过对表格的列应用过滤器,可以从表格中查询行。

关系模型为通过外键派生多个关系之间的关系的连接提供了很好的支持。

文档数据模型

在文档数据模型中,文档本身由一个实体的所有属性组成,JSON 数据就是一个很好的例子。

它遵循一个层次结构,在这个结构中,一个实体可以有多个属性,每个属性又可以有子属性等等。这意味着文档模型具有树状结构。属性可以很容易地添加到文档中,任何读取这些数据的应用程序都会解释一个隐式模式(称为读取模式)。获取实体的属性只需要查询该实体的 JSON 表示中的键字段。

考虑因素

选择特定类型的模型主要取决于:

您能让您的应用程序代码保持多简单?

如果应用程序中的数据是类似文档的,比如 JSON,那么最好使用文档模型,尽管有一些限制。人们不能分割数据,创建代表关系模型的独立数据结构,因为这会增加应用程序代码不必要的复杂性和负载。

但是,对于文档模型,从高度嵌套的文档中的属性检索特定元素也会增加代码的复杂性,因为您必须显式地指定它的访问路径。

如果应用程序必须派生多对多关系,那么使用关系模型会更好,因为它对多个表之间的规范化和连接有更好的支持,而不是将工作负载从数据库转移到应用程序代码,以维护从类似文档的数据派生的一致关系。

模型支持模式灵活性吗?

与关系模型相比,文档模型提供了高度的模式灵活性。

假设有一个人实体具有这些属性:

name: John Doe
current city: Amsterdam
current country: Netherlands

如果你想添加一个字段,比如说人的年龄,你只需要在文档模型的情况下再给文档添加一个键。然而,对于关系模型,您将执行添加一列的ALTER TABLE命令,这将作为一个整体应用于表,同时导致停机。在文档模型中,对现有字段的更新只需要很少的时间,但是,在关系模型中,UPDATE命令必须搜索特定的行,然后将它们全部重写。然而,文档模型提供的模式灵活性存在一些问题,这将在下一点中讨论。

此外,如果外部系统不时地对数据结构进行更改,那么在其上使用严格的模式会导致不必要的工作,比如添加列、回填数据等。但是,如果您知道数据的结构是固定的,不会改变,那么在它的基础上实施一个模式会更好。

要查询的数据本地化程度如何?

在关系模型的情况下,您可能有一个规范化的数据库,也就是说,您将有多个具有特定粒度的表,这些表可以与其他表相关联,以派生出特定的关系。实现这一点的唯一方法是使用联接,在联接中,迭代发生在外键的指定值的多个表上,这意味着多个磁盘寻道会影响检索所需数据的时间。

正如我们前面讨论的,文档模型将所有数据作为单个实体放在一个地方,因此数据是高度本地化的。但是,只有在查询文档中的大量属性(模式灵活性的结果)时,这种本地化才有优势。如果只使用/更新文档中的几个属性,则必须将整个文档加载到内存中,读取/更新所需的字段,并写回整个文档,这既占用内存又耗费时间。因此,最好保持文档较小,这在阅读和写作时是有效的。

结论

选择一个特定的模型,不仅是在关系模型和文档模型中,也在其他模型中,比如图模型,完全取决于您的用例,特别是,您将如何使用数据,外部系统如何影响它,下游应用程序有什么要求,等等。

生产中可靠预测模型的生存分析

原文:https://towardsdatascience.com/reliable-forecast-modelling-2094cc5237a1

采取积极措施确保模型在生产中的可靠性和可用性

安德里亚斯·瓦格纳在 Unsplash 上拍摄的照片

随着企业越来越多地转向基于机器学习和分析的解决方案,当模型投入生产时,挑战就出现了。其中一个挑战是模型预测的漂移,这使得它们从长期来看不太可靠。

这种现象在时间序列预测模型中最为明显。经济周期、货币政策、消费者行为和竞争对手活动的变化会导致预测不准确。

这个问题需要被动和主动的解决方案。应对生产中预测模型漂移的被动方法是根据预测的业务规则设置阈值。如果实际时间序列和预测之间的差异超过了指定的阈值,则称为漂移事件,在自动设置中会触发对现有模型的进一步分析或再训练。

模型可靠性和可用性

在我们深入这个主题之前,让我们定义几个术语来进一步讨论。

机器学习社区指的是可靠性的概念,包括模型预测中的不确定性和对生产数据进行概括的失败的密切相关的想法[1]。

在本文中,我将预测可靠性的想法扩展如下:

模型可靠性定义为模型预测在特定时间点的可靠性。

一个可靠的模型是这样一种模型,它的预测是可以依赖的,直到我们在某个时间点想要替换它。这就引出了第二个定义:

型号可用性 是指满足型号可靠性生产要求的型号的可用性。

不符合这些要求的模型应该被重新训练或替换。

比较被动和主动策略以确保模型的可靠性

在以下情况下,对自动化系统中的漂移事件做出反应似乎是一种合理的策略:

  1. 生产的车型数量有限,因此再培训成本较低。
  2. 这种事件发生的频率很少。
  3. 对模型可用性没有严格的要求。

对于某些业务,尤其是零售和物流,要建模的时间序列的数量可能会达到数千,如果不是数十万的话。此外,这些时间序列可能密切相关,例如属于相同的地理、材料和领域等。通过进一步分析或重新训练模型来应对大规模漂移事件,可能会节省大量计算和分析时间。

为了使模型在生产中可靠,在它漂移和触发再训练之前有体面的正常运行时间,必须有一个预先预见模型漂移的策略。这是这样的:

  1. 可以满足与模型可用性相关的 SLA 要求。
  2. 可以在预生产环境中对这类案例进行分析,以进行根本原因分析。
  3. 已知这些事件在模型中的频率和流行程度,可以预先计算出再训练的计算要求。

在上述情况下,积极主动的方法可能更合适。这种方法本质上应该在模型投入生产之前提供对此类事件的合理估计。

用于分析预测模型可靠性的生存模型

我发现对时间序列预测模型有用的一种估计模型可靠性的方法是利用生存分析来模拟预测模型中的性能衰减。

假设关于模型性能的良好历史是可用的,或者大量密切相关的模型是可用的,我们可以创建生存模型来预测模型衰退并进行主动分析。这种分析将有助于我们量化不同场景下的再训练和模型可用性的计算要求,并有助于提前规划。

在这里,我将演示一个简单的例子,通过这个例子,我们可以使用生存模型来查看给定的一组时间序列模型会出现什么样的衰减。

但首先,生存模型速成班。

生存分析——速成班

生存分析是统计学的一个分支,处理对事件时间的分析。生存模型预测事件发生的时间[2]。例子包括设备故障前的时间、贷款违约前的时间等。

这里的想法是模拟如下给出的生存概率分布。

作者图片

这里, T 是直到事件发生的等待时间,生存函数是该等待时间大于某一时间 t 的概率。换句话说,存活到时间 t 的概率。

与生存分析密切相关的是审查制度的概念。如果我们不知道一个事件何时发生,那么这个事件就被称为被删截了。最直观的审查是当我们实验的观察期结束时,被研究的对象没有面对一个事件,即它可能在未来面对一个事件。这样的数据被说成是右删。这是与我们的预测模型可靠性研究最相关的审查类型。

图片来源:可靠性——一个用于可靠性工程的 python 包 [4]

有几种生存建模技术——大致分为两类:即参数化和非参数化。参数模型对生存概率分布的形式做出假设,当配备了支持这些假设的领域知识时,参数模型是很好的选择。另一方面,非参数模型不对概率分布做出假设,而是根据手头的数据进行经验计算。

两种建模类型的最终目标都是获得一个生存函数,给定一个特定的时间点,将输出到该点的生存概率。

示例应用— M5 预测数据集

现在,让我们来看一个具体的例子和一个解决时间序列模型可靠性问题的建议方案。

在本例中,我们将采用 M5 预测数据集。自 1982 年以来,该数据集被用于由 Spyros Makridakis 教授领导的 M 系列预测竞赛的第五次迭代。数据集在这里可用。

该数据包含不同层级的销售额,即项目、部门、产品类别和商店。它包括来自美国三个州的商店,即加利福尼亚州、德克萨斯州和威斯康星州。

方法学

为了进行这项研究,我们将在管道的两个不同阶段,即预处理阶段和评估指标生成阶段,进行一些特殊的考虑。这个评价指标反过来会导致我们的事件和审查。

本实验的设计有如下配置。我将很快详细解释这些参数。

预处理

在预处理中,基于配置,我们过滤我们感兴趣的商店和部门。接下来是将 sales 列融合(宽格式到长格式的转换)成一列。

销售数据存在于项目级别。我们在每个商店部门级别总结这一点。现在,我们有了每个部门的商店级销售数据。

系统模型化

我们为每个商店部门建立 Prophet 模型。Prophet 是由脸书研究所开发的一个非常流行的时间序列预测模型。我们使用默认参数和 1 年的测试预测范围来构建这些模型。

生存分析设计

现在,我们讨论该方法的关键,即执行生存建模所需的步骤。为此有两个实用程序。

第一个实用程序作用于测试集,并在从第 4 周到第 26 周 (6 个月)的扩展窗口上计算 MAPE (平均绝对百分比误差——评估时间序列预测的流行指标)。为什么我们这样计算 MAPE?

如果我们假设生产中预测模型的事件发生时间是其性能下降超过给定阈值之前的时间,我们可以使用生存分析来模拟该等待时间,从而采取主动措施来确保模型的可靠性。

正是通过使用类似于扩展窗口 MAPE 的度量来生成模型相对于时间的评估分数,我们可以生成退化/衰变/漂移的时间,进而可以对其进行建模。

第一个实用程序在测试集上生成一个基于时间的评估度量,而第二个实用程序在性能下降的第一周设定阈值,并将其标记为一个事件。在配置中,阈值为 10 的 MAPE。简单来说,我们在扩张的 MAPE 跨越 10 岁的第一周纪念一个事件。对于研究结束时未超过阈值的商店部门模型,即 26 周被标记为删截。

卡普兰-迈耶估计量

Kaplan-Meier 估计量是生存函数的非参数估计量[3]。它通常是生存分析工具包中的第一个基线模型。假设我们没有重要的领域知识和协变量来通知我们,为了说明起见,我们将使用这个估计量。

估计量由以下公式给出:

礼貌(维基百科:卡普兰-迈耶估计)

其中 dᵢ 是在发生的事件,tᵢnᵢ 是截至 tᵢ.没有面对事件的总被试

在这一阶段,我们从 store-dept 模型中汇总所有事件和审查员的观察结果,并为每个部门构建 Kaplan-Meier 模型。

结果

拟合 Kaplan-Meier 估计值的结果是一个生存函数,在我们的研究期间绘制在这里。

作者图片

它为我们提供了属于爱好和食品部门的模型在研究中每周的生存概率。

讨论和解释

研究开始时的存活概率如预期的 1.0 。这两个部门的中位生存时间是生存概率为 0.5 的时间,是 4 周。

有了生产中模型的生存分布和规定概率阈值业务规则,我们可以通过采取主动措施来确保模型的可靠性,如安排再培训和分析,而无需等待生产中的性能事故。

一个示例设置可以使用第 95 百分位生存时间 2 周,并在生产中达到该限制之前重新培训给定部门的所有商店级模型,以确保模型退化低于指定概率的阈值。

结论

一旦我们采取主动措施,比如上面描述的生存分析,模型的可用性就变得容易了。

这种方法可以跨不同类别的模型执行,也可以跨除零售和物流以外的场景的建模策略执行。

此外,我们没有详细讨论的另一个方面是,这种方法需要新的指标来生成故障时间事件。这些度量不仅为研究生产中的模型可靠性,而且为作为基于时间的单独评估度量提供了大量机会,这对于时间序列预测模型非常有用。

最后,本文专门讨论了预测建模中的问题,但是具有适当评估指标的相同原则也可以用于其他建模方法。

参考文献

[1]苏驰·萨里亚,阿达什·苏巴斯瓦米,教程:安全可靠的机器学习(2019) ,ACM 公平、问责和透明会议

[2] Kartsonaki,c .,生存分析(2016),诊断组织病理学

[3]贾森·t·里奇医学博士、杰·卡尔·尼尔医学博士等人,《理解卡普兰-迈耶曲线的实用指南》( 2014 年),耳鼻喉科——头颈外科

[4]m . Reid,《可靠性——用于可靠性工程的 Python 库》(版本 0.8.2) (2022 年),https://doi.org/10.5281/ZENODO.3938000

如果你喜欢这篇文章,并希望看到更多关于实用和现实世界数据科学的帖子,请在 Medium 上关注我,让我们在Linkedin上联系。干杯!

BigQuery 中的远程函数

原文:https://towardsdatascience.com/remote-functions-in-bigquery-af9921498438

它是如何工作的,你能用它做什么

BigQuery 提供了调用用户定义函数(UDF)的能力,但是这些 UDF 必须在 SQLJavaScript 中。这些函数完全在 BigQuery 中运行,因此提供了很好的可伸缩性和性能(SQL 比 JavaScript 更好)。但是在很多情况下,您需要在 BigQuery 之外运行代码,并且希望运行用其他语言编写的代码。对于这两种情况,现在可以使用遥控功能

凯利·西克玛Unsplash 上拍摄的照片

在本文中,我将向您展示远程函数是如何工作的,然后讨论一些用例。

1.远程功能如何工作

远程功能通过与 Google Cloud Functions (GCFs)的集成来工作,GCFs 是 Google Cloud 上的无服务器功能即服务。

  • 用 Node.js,Python,Go,Java,.net、Ruby 或 PHP
  • 将其包装在 SQL UDF 中—这是现有的新功能
  • 照常使用 SQL UDF

让我们看一个例子。

1.1 [Admin]创建 BigQuery 和云函数之间的连接

嗯,你在权限方面有一点设置。这是您的管理员可能需要做的事情。

  • GCP web 控制台,启用 BigQuery 连接 API
  • 在安装了 gcloud SDK 的 Cloud Shell 或其他环境中,执行以下操作:
gcloud components update
bq mk --connection --display_name='my_gcf_conn' \
      --connection_type=CLOUD_RESOURCE \
      --project_id=$(gcloud config get-value project) \
      --location=US  **gcf-conn**
bq show --location=US --connection gcf-conn

最后一行将向您展示 BigQuery 用来调用云函数的服务帐户。记住这一点,因为我们必须让云功能允许这个服务帐户。

1.2 创建云功能

为了测试这个功能,我们需要创建一个云函数,让我们开始吧。访问 GCP 控制台并创建新的云功能:

  • 调用函数 add_fake_user
  • 接受所有其他默认值
  • 将语言更改为 Python 3.9
  • 将入口点更改为 add_fake_user
  • 将以下代码复制粘贴到编辑器中:
import jsondef add_fake_user(request):
      request_json = request.get_json(silent=True)
      replies = []
      calls = request_json['calls']
      for call in calls:
        userno = call[0]
        corp = call[1]
        replies.append({
          'username': f'user_{userno}',
          'email': f'user_{userno}@{corp}.com'
        })
      return json.dumps({
        # each reply is a STRING (JSON not currently supported)
        'replies': [json.dumps(reply) for reply in replies]
      })

上面的代码从输入请求中提取了两列(call[0]和 call[1])。第一个被视为 userno,第二个被视为用户所属的公司。基于这些,代码“查找”用户的身份。我在这里实际上并没有攻击外部系统,只是假装而已——但关键的想法是,谷歌云函数中的代码几乎可以是任何东西。

请注意上面代码的两点:

  • 云函数不只是从 BigQuery 获得一行。它接收一堆行。这就是为什么我要迭代
for call in calls
  • 我想将一堆不同的字段返回给 BigQuery,所以我应用了一个技巧,将我想返回的所有数据值转换成一个 JSON 字符串:
replies = []
...
replies.append({
          'username': f'user_{userno}',
          'email': f'user_{userno}@{corp}.com'
        })
...
return json.dumps({
        # each reply is a STRING (JSON not currently supported)
        'replies': [json.dumps(reply) for reply in replies]
      })

1.3 试用云功能

试试云功能。

  • 切换到云功能的测试选项卡
  • 将以下内容复制粘贴到编辑器选项卡中:
{
        "calls": [
            [4557, "acme"],
            [8756, "hilltop"]
        ]
}

这是 BigQuery 将发送的 JSON 的格式。本质上是一个行数组。示例输入中的每一行都包含两个字段:一个整数和一个字符串。云函数会把这两个字段拉出来做它的事。

  • 您应该会看到回复 JSON:

作者 GCP 控制台截图

1.4 允许 BigQuery 调用这个云函数

您必须显式地允许 BigQuery 调用这个特定的云函数。因此,切换到 Permissions 选项卡,添加 BigQuery 服务帐户(参见步骤 1.1)作为云函数调用程序。

1.5 将云函数包装在 SQL UDF 中

在 BigQuery 中,创建一个数据集来保存 SQL UDF:

bq show blogs || bq mk -d blogs

编写一个 SQL UDF,它将利用到 BigQuery 的连接并调用云函数:

CREATE OR REPLACE FUNCTION **blogs.add_fake_user**(user_id int64, corp_id STRING) RETURNS STRING
REMOTE WITH CONNECTION `PROJECTID.us.gcf-conn`
    OPTIONS (
        -- change this to reflect the Trigger URL of your cloud function (look for the TRIGGER tab)
        endpoint = '[https://REGION-](https://us-central1-vivid-tuner-338922.cloudfunctions.net/add_fake_user')PROJECTID[.cloudfunctions.net/add_fake_user'](https://us-central1-vivid-tuner-338922.cloudfunctions.net/add_fake_user')
    )

注意上面有两个地方要改。您可以从 GCP 控制台的主页面板中找到项目 ID。您可以在云函数的触发器选项卡中找到触发器 URL:

作者 GCP 控制台截图

1.6 照常使用 SQL UDF

例如,您可以查询:

 SELECT
      start_station_id, end_station_id, 
      **blogs.add_fake_user(trip_id, bikeid)**
    FROM `bigquery-public-data.austin_bikeshare.bikeshare_trips`
    LIMIT 10

返回的是一个 JSON 字符串,因此您可以使用 JSON 函数解析它:

WITH data AS (
          SELECT
            trip_id, bikeid, 
            start_station_id, end_station_id, 
            blogs.add_fake_user(trip_id, bikeid) AS reply
          FROM `bigquery-public-data.austin_bikeshare.bikeshare_trips`
          LIMIT 10
    )

    SELECT 
      * EXCEPT(reply),
      **JSON_QUERY(reply, '$.email')** AS user_id
    FROM data

或者,您可以使用 JSON 类型(它目前在预览版中):

WITH data AS (
          SELECT
            trip_id, bikeid, 
            start_station_id, end_station_id, 
            **SAFE.PARSE_JSON**(blogs.add_fake_user(trip_id, bikeid)) AS reply
          FROM `bigquery-public-data.austin_bikeshare.bikeshare_trips`
          LIMIT 10
    )

    SELECT 
      * EXCEPT(reply),
      **reply.email**
    FROM data

2 个使用案例

在需要在 BigQuery 之外运行代码的情况下,以及希望运行用其他语言编写的代码的情况下,远程函数非常有用。但是,您不希望过度使用远程函数,因为与原生 SQL UDFs 相比,它们在性能和成本方面存在劣势。远程函数的性能通常不如原生 BigQuery SQL,因为它需要额外调用 BigQuery 插槽和自动扩展虚拟机的云函数。此外,在成本方面,您将为 BigQuery 和云功能付费。

以下是一些潜在的使用案例。

2.1 浓缩

一个关键的用例是用外部服务创建的数据丰富 BigQuery,并写出一个新表。

假设你有一个部署到顶点 AI 端点的机器学习模型。远程函数使您能够调用 BigQuery 数据上的模型,并创建一个包含丰富数据的新表。这也适用于预先构建的谷歌模型,如谷歌翻译和顶点实体提取。

非 ML 丰富用例包括地理编码和实体解析。

注意,如果你的 ML 模型在 TensorFlow 中,我推荐你 直接加载为 BigQuery ML 模型。这种方法比远程函数更有效。

2.2 实时查找

作为 SQL 工作流的一部分,您可以使用远程函数来查找实时信息(例如股票价格、汇率)。

例如,您可以让仪表板或交易应用程序简单地调用一个 SQL 查询来过滤一组证券,然后查找符合选择标准的股票的实时价格信息:

WITH stocks AS (
   SELECT
      symbol
   WHERE
      ...
)
SELECT symbol, **realtime_price**(symbol) AS price
FROM stocks

其中 realtime_price()是一个远程函数。

2.3 用动态 ELT 代替计划 ETL

2.2 中的股票价格场景通常的做法是编写一个 ETL 管道,定期为所有证券创建一个当前价格表。这对于交易量少的证券来说可能是一种浪费,并会带来不必要的延迟。针对此类证券,将 ETL 管道更改为 ELT 管道可以显著降低存储和计算成本。

一般来说,远程函数方法允许您在业务中用 ELT 管道替换许多预定的 ETL 管道,ELT 管道可以在需要时动态地查找信息。

2.4 高性能计算

不是所有的事情都可以用 SQL 完成。以交易为例,复杂的投资组合策略可能最好用统计友好的编程语言(比如 Python)来完成,并且可能需要在加速器(比如 GPU)上执行。

SQL 查询可以将这样的高性能计算提供给远程函数。

2.5 Java 中的业务逻辑

2.4 中的高性能计算是需要用高性能语言实现的业务逻辑的一个例子。您可能希望用 Java 之类的语言实现业务逻辑的另一个常见原因是,业务逻辑可以在多个地方重用,包括 web 和后端应用程序。

远程函数允许您将 Java 中的业务逻辑作为 SQL 工作流的一部分来应用。

2.6 与其他系统的集成

我在本文第一部分中的例子是查找用户凭证。通常,当您需要将 BigQuery 与外部系统(如令牌代理等)集成时,远程函数会有所帮助。

注意,在像 Cloud SQL 和 Spanner 这样的事务性数据库的特定情况下,您可以使用 EXTERNAL_QUERY 并完全保留在 SQL 中。但是对于其他类型的外部系统,远程功能将被证明是有用的。

2.7 混合云工作流

因为云功能可以调用任何代码,甚至在 GCP 之外,这也可以是实现混合云工作流的一种方式。确保您调用的服务能够处理并发性!

2.8 遗留代码

如果您有您的用例所必需的专有或遗留代码,远程函数允许您从 SQL 调用它。一种常见的情况是支持强制特定(非标准)加密或令牌化的合规性要求。

2.9 迁移

当从遗留数据仓库迁移时,您可能会使用 SQL 和 JavaScript 之外的语言来定义用户函数。远程功能允许您将这些 UDF 迁移到云中。

在这些情况下,云函数可能比 SQL 更有效,无论如何你都需要外部计算,或者简化是值得的。

尽情享受吧!

感谢克里斯·克罗斯比和沈超的有益讨论

后续步骤:

  1. 为了方便起见,本文中的步骤也列在了与 O'Reilly 的书 BigQuery:权威指南相对应的 GitHub 库中。试试吧!
  2. 阅读远程功能的文档页面。
  3. 阅读云函数文档,了解性能技巧。

远程查看在 AWS Panorama 上运行的计算机视觉模型

原文:https://towardsdatascience.com/remote-view-your-computer-vision-models-running-on-aws-panorama-32922369ecf

监控全景应用程序

SkyLine 简介:从工作站查看 Panorama 应用程序输出的库

照片由 Elisa SchmidtUnsplash 上拍摄

实时智能视频分析应用开发和边缘设备部署是一项复杂的任务。在过去的几年中,行业参与者构建了支持这一活动的平台。著名的例子包括英伟达 Metropolis英特尔的 OpenVINOAWS Panorama 。虽然这些解决方案使视频分析应用程序开发的某些方面更加简单,但在生产中部署视频分析应用程序之前,仍有许多问题需要处理。这篇文章介绍了 SkyLine,它是开源工具系列中的第一款,可以简化 AWS Panorama 应用程序的开发。

AWS Panorama 是一个机器学习设备和软件框架,允许您在 edge 上部署视频分析应用程序。有关部署 Panorama 应用程序的详细介绍和分步指南,请参考 在 AWS Panorama 的边缘部署对象检测器模型

AWS Panorama 框架简化了视频分析应用程序开发的许多方面,包括摄像机连接管理、视频解码、帧提取、模型优化和加载、显示输出管理、应用程序的无线部署以及其他功能。尽管如此,一些任务仍然具有挑战性,包括当模型不按预期工作时的诊断任务。

天际线介绍

获得有关 Panorama 应用程序正确功能的视觉反馈的唯一可用方法是将显示器物理连接到设备的 HDMI 端口。显示器将显示部署在设备上的单个应用程序的输出视频流。然而,实际接触设备并不总是可行的。SkyLine 允许将任何 Panorama 应用程序的输出视频重新流式传输到外部服务,例如,传输到 AWS Kinesis 视频流。这个特性对于远程监控应用程序非常方便。

SkyLine 是如何工作的?

SkyLine 实例化一个头部带有[appsrc](https://gstreamer.freedesktop.org/documentation/app/appsrc.html)元素的 GStreamer 管道。一个 OpenCV [VideoWriter](https://docs.opencv.org/4.5.5/dd/d43/tutorial_py_video_display.html)被配置为向appsrc写入:它不是将连续的帧保存到视频文件,而是流式传输到输出接收器。当打开VideoWriter实例时,用户应该指定输出流的帧宽和帧高以及帧速率。您可以手动设置这些参数,或者让 SkyLine 根据输入尺寸和向其输入新帧的频率来推断这些参数。如果使用这种自动配置功能,一些帧(默认为 100 帧)将在流传输开始时被丢弃,因为 SkyLine 将使用它们来计算帧速率的统计数据并测量帧尺寸。这是天际线的“热身”。如果您发送不同大小的帧,SkyLine 将调整输入的大小,但这会带来性能损失。您还需要以每秒帧数参数中指定的频率向 SkyLine 发送新帧。如果帧以不同的频率发送,视频片段在 Kinesis 视频流中就会不同步,您将无法流畅地重播视频。

Josh Redd 在 Unsplash 上拍摄的照片

SkyLine是处理 GStreamer 管道状态机的抽象基类。具体实现KVSSkyLine亚马逊 Kinesis 视频流服务发送帧。很容易扩展 SkyLine 来支持其他服务,特别是如果已经有了一个 GStreamer 插件。

将 SkyLine 集成到全景应用程序中

配置 Docker 容器

SkyLine 依赖于自定义编译的外部库。所有这些库必须在应用程序的 docker 容器中正确编译和配置,以使SkyLine工作。这些库包括:

  • GStreamer 1.0 安装了标准插件包、libav、工具和开发库;
  • OpenCV 4.2.0,用 GStreamer 支持和 Python 绑定编译;
  • numpy(通常由 Panorama 应用程序的基本 docker 映像安装)。

如果您想使用KVSSkyLine(将视频流传输到 Amazon Kinesis 视频流的SkyLine实现),它还需要以下库:

  • 亚马逊 Kinesis 视频流(KVS)制作商 SDK 编译有 GStreamer 插件支持;
  • 环境变量GST_PLUGIN_PATH配置为指向 KVS 生产商 SDK GStreamer 插件编译后的二进制文件所在的目录;
  • 环境变量LD_LIBRARY_PATH包括 KVS 制作商 SDK 编译的第三方开源依赖;
  • boto3(通常由 Panorama 应用程序的基本 docker 映像安装)。

在 SkyLine 的源代码库中,examples 文件夹中提供了一个样例 Dockerfile ,展示了如何在任何容器中正确安装这些库和 SkyLine。在大多数情况下,只需要将示例中的相关部分复制到应用程序的 docker 文件中。请注意,第一次编译 docker 容器时,可能需要一个小时来正确编译所有的库。

为 KVSSkyLine 设置 AWS IAM 权限

KVSSkyLine使用包装在 GStreamer sink 元素中的 Amazon Kinesis Video Streams(KVS)Producer 库,将处理后的帧发送到 KVS 服务。KVS 生产者需要 AWS 证书。它可以使用与 Panorama 应用程序角色关联的凭据,但您必须明确配置它。KVSSkyLine需要权限来执行以下操作:

kinesisvideo:DescribeStream
kinesisvideo:GetStreamingEndpoint
kinesisvideo:PutMedia

如果KVSSkyLine需要在第一次使用时自动创建 Kinesis 视频流,那么还应该包含kinesisvideo:CreateStream动作。允许KVSSkyLine向 Kinesis 视频流写入数据的示例策略如下所示:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "kinesisvideo:DescribeStream",
                "kinesisvideo:CreateStream",
                "kinesisvideo:GetDataEndpoint",
                "kinesisvideo:PutMedia"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

设置凭据

有两种类型的 AWS 安全凭证:静态和临时。前者永远不会过期,如果发生泄漏,您应该手动使它们失效并重新配置应用程序。因此,强烈建议不要在生产环境中使用它们。静态 AWS 凭证的示例包括 IAM 用户的访问密钥 Id 和秘密访问密钥对。

照片由安吉拉·梅伦科娃Unsplash 拍摄

临时凭据在预定义的时间段后过期。在泄漏的情况下,它们只能在有效期内使用,通常在几个小时的量级。临时凭据可以在过期之前续订,以便延长其使用期限,或者可以在过期后更换为新凭据。这个过程需要来自使用这种类型的凭证的应用程序的额外协调。临时凭证的示例包括 AWS 访问密钥 Id、秘密访问密钥和会话令牌。

我们提供不同的选项来为凭证提供在kvs模块中提供的KVSCredentialsHandler子类。如果您想使用静态凭证进行测试,在您的 AWS 帐户中创建一个 IAM 用户,并为该用户附加一个类似上面的策略。您应该将该用户配置为对 AWS 资源进行编程访问,并获得用户的 AWS 访问密钥和密钥对:在应用程序代码中创建一个KVSInlineCredentialsHandlerKVSEnvironmentCredentialsHandler实例,以便在 GStreamer 管道定义中直接或作为环境变量将这些凭证传递给 KVS 生产者插件。但是,由于这些凭据不会过期,因此不建议在生产环境中使用此配置。即使在开发和测试环境中,您也应该采取适当的安全措施来保护这些凭证:不要在源代码中硬编码它们。相反,使用 AWS Secret Manager 或类似的服务向您的应用程序提供这些参数。

KVSSkyLine也可以使用 Panorama 应用程序角色将应用程序的凭证传递给 KVS 生产者。这些凭据是临时的,这意味着它们将在几个小时内过期,用户应该在过期前更新它们。生成器库需要文本文件中的临时凭据。KVSFileCredentialsHandler管理凭证的更新,并定期用新凭证更新文本文件。如果您希望使用这种方法,请将类似于上面示例的策略附加到您的应用程序角色。请记住,如果在KVSFileCredentialsHandler刷新凭证后 Panorama 应用程序——KVS 集成仍然工作,请始终对其进行测试。让您的应用程序运行几个小时,并定期检查它是否继续向 KVS 传输视频。在更新凭证时,您还可以在应用程序的 CloudWatch 日志中找到诊断信息。

环境变量

需要两个环境变量让 GStreamer 找到 KVS 生产者插件。这些变量的名字是GST_PLUGIN_PATHLD_LIBRARY_PATH。他们指向 KVS 生产商 GStreamer 插件及其第三方依赖项的文件夹。在提供的示例 Dockerfile 文件中,这些变量的正确值被写入容器中一个名为/panorama/.env的小配置文件。您应该将该文件的路径传递给KVSSkyLine,或者确保这些环境变量包含正确的值。

用法示例

当初始化一个KVSSkyLine实例时,您应该传递创建您的流的 AWS 区域名、流名和凭证处理程序实例。如果您想要手动配置帧速率和帧的尺寸,您也应该在这里设置它们。当两个参数都指定时,SkyLine 跳过预热阶段,直接将第一帧发送给 KVS。当您准备好发送帧时,调用打开 GStreamer 管道的start_streaming方法。在这个方法被调用后,您需要定期向调用put方法的流发送新的帧,发送频率在帧速率中指定或者由KVSSkyLine推断。您可以在同一个KVSSkyLine实例上停止和重新启动流式传输。

以下示例使用由 Panorama 应用程序承担的 IAM 应用程序角色的临时凭据:

KVS天际线用法举例

如果一切正常,你可以在 AWS 控制台的 Kinesis 视频流页面上观看重新流过的视频。当然,你可以在将图像发送到 SkyLine 之前对其进行修改:根据深度学习模型的推理结果在其上绘制注释。

释文

注释注释驱动提供了一种在不同渲染后端绘制注释的统一方式。目前,实现了两个注释驱动程序:

  • PanoramaMediaAnnotationDriver允许您在panoramasdk.media对象上绘制,并且
  • OpenCVImageAnnotationDriver允许你在一个 OpenCV 图像(numpy 数组)上绘制对象。

照片由卢卡斯·乔治·温迪特Unsplash 拍摄

该库可以绘制两种类型的注释:标签和框。

使用注释

根据可用的后端,您可以在视频帧处理循环开始时创建一个或多个注释驱动程序实例。在处理单个帧的过程中,您需要在 python 集合中收集要在该帧上绘制的所有注释(例如,在list中)。当处理完成时,您在任意数量的驱动程序上调用render方法,传递相同的注释集合。注释中使用的所有坐标都被标准化为[0; 1)范围。

示例用法:

背包注释的用法示例

刊后语

即使 SkyLine 是一个有用的工具,它的使用可能会引起两个问题,您应该仔细考虑。我们不鼓励在生产环境中使用 SkyLine:它是一种开发辅助工具或调试工具。

格雷格·罗森克在 Unsplash 上拍摄的照片

第一个问题是技术性的。目前,Panorama 应用程序中的应用程序代码不能直接访问板载 GPU 因此,SkyLine 完成的所有视频编码都在设备的 CPU 上运行。这种行为可能会降低设备的速度:使用 SkyLine 传输单个输出流可能需要设备 10–30%的 CPU 容量。

第二个问题与数据保护有关。Panorama 设备旨在保护正在处理的视频流。它有两个以太网接口,将摄像机网络(通常是闭路局域网)与设备的互联网接入分开。使用 SkyLine,您可以将视频流从受保护的闭路摄像机网络传输到公共互联网。在使用 SkyLine 之前,您应该仔细检查您的应用程序和摄像机网络的数据保护要求。此外,根据 AWS 共享责任模型的要求,您有责任对 SkyLine 使用的所有 AWS 凭证保密。

从这里去哪里?

SkyLineBackpack的一部分,后者是一套更广泛的工具,旨在帮助 AWS Panorama 上的软件开发。其他组件将在以后的帖子中介绍,请继续关注我们,获取最新信息。

Backpack是开源的,在 GitHub 上可用。您可以在基于 ARM64 的系统上构建并运行示例 docker 容器。例如,假设您已经在 t4g 类型 EC2 实例上设置了 Panorama 测试实用程序。在这种情况下,您可以在 EC2 实例上使用以下命令在该容器中构建和启动一个 shell:

$ git clone [https://github.com/Neosperience/backpack.git](https://github.com/Neosperience/backpack.git)
$ cd backpack/examples
$ docker build . -t my_backpack_example:1.0
$ docker run -it my_backpack_example:1.0 bash

该容器也可以在装有新的 M1 ARM 芯片的 MacBook Pro 上构建和执行。

backpack 库用 docstrings 进行了广泛的文档化。您可以在线阅读详细的 API 文档或使用容器外壳中的python3解释器;

root@my_backpack_example:/# python3
>>> import backpack.skyline, backpack.kvs, backpack.annotation
>>> help(backpack.skyline)
>>> help(backpack.kvs)
>>> help(backpack.annotation)

您还可以尝试在Backpack中找到的其他SkyLine实现。例如,RTSPSkyLine允许您在应用程序容器中托管 RTSP 服务器,并通过 RTSP 客户端直接连接到 Panorama 设备以监控您的应用程序。

让我知道你是如何使用SkyLine的,你希望看到哪些新特性,并关注未来关于Backpack其他组件的帖子。

2022 年 6 月 20 日更新:backpack库的遥视模块更名为skyline

关于作者

Janos Tolgyesi 是一名 AWS 社区构建者,在 Neosperience 担任机器学习解决方案架构师。他在 ML technologies 工作了五年,在 AWS infrastructure 工作了八年。他喜欢构建东西,让它成为边缘的视频分析应用或基于点击流事件的用户分析器。你可以在 TwitterMediumLinkedIn 上找到我。

背包的开源项目得到了新科学的支持。

我要特别感谢卢卡·比安奇校对这篇文章。

如何删除你不小心上传到 Github 的敏感数据

原文:https://towardsdatascience.com/remove-file-from-git-history-ce404d7463d3

如何避免它们进入那里?—这是一个非常简单的处理方法。

照片由像素皮克斯拜拍摄

我经常遇到这种事。

我创建存储库,并且我知道我需要将我的密钥存储在一个环境文件中。但是我总是提交我的更改,而不把它们添加到 gitignore 文件中。

结果,我们的秘密都在网上了!

现在,即使您将环境文件添加到 gitignore,它也不会从历史中消失。

严重程度可能会有所不同,这取决于您上传的信息以及重置它的难度。然而,这是可以预防的!因此,我们应该始终阻止此类敏感数据进入云中。

但是,如果你不小心按到了什么东西,下面是正确清除的方法。

正确删除 GitHub 中的秘密文件。

当您提交一个机密文件(例如。env),它成为 git 历史的一部分。移除文件并重新提交不会清除它。稍后将它添加到 gitignore 也不会有帮助。

以前,人们使用 git-filter-branch 来挖掘分支模型。相信我;你不会想进去的。

如果你不同意,这里有关于 git-filter-branch 文档的警告信息。

git filter-branch 有太多的缺陷,可能会导致不明显的历史重写——Git filter 分支文档的混乱

幸运的是,我们有一个更简单的选择。它叫做 BFG 回购清道夫。

BFG 是一个社区维护的公用事业。是用 Java 写的。所以确保你已经在电脑上准备好了。

如何不删除 Git 中的秘密文件?

让我们为这个演练创建一个示例 repo。

我创建了一个只有一个文件的回购。它是一个. env 文件,里面有一个密钥。有了它,我做了第一次承诺。现在。env 文件应该在历史记录中。

$ mkdir testrepo
$ cd testrepo/
$ echo "SECRET_KEY=039jrad8932" > .env
$ git init$ git add .
$ git commit -m 'initial commit'

让我们在 GitHub(或者 Bitbucket 等)上创建一个云仓库。)并将我们的代码上传到那里。

$ git remote add origin [git@github.com](mailto:git@github.com):<Your-Repo>.git
$ git branch -M main
$ git push -u origin main

哎呀,我们的秘密在网上!如果您访问您的远程回购,您可以看到。环境文件。

作者截图。

让我们移除它,重新提交并推动它。并且刷新云回购看看效果。

$ rm .env
$ git add .env
$ git commit -m 'remove secret'
$ git push origin main

唷!现在没了。

不,还没有。它仍然在提交历史中。任何能接触到回购协议的人都可以看到。

作者录制的屏幕录像。

没用!

用 BFG 删除 Git 上的秘密文件。

以下是去除它们的正确方法。正如我前面提到的,你需要 BFG。BFG 需要 Java。

您可以使用以下命令确认您的计算机上是否安装了 Java。如果没有,请按照这个文档来安装它。

你可以从他们的官方页面下载 BFG jar 文件。

第一步:要从所有分支的历史中删除文件,首先,您需要创建我们的远程存储库的镜像克隆。

$ git clone --mirror git://<Your-Repo>.git

什么是镜像回购?

假设您进入新克隆的存储库。你看不到你的普通文件。相反,您会看到 Git 用来管理存储库的文件和文件夹。我们称这样的仓库为裸回购。但是镜像回购,除了裸回购内容之外,还有你的文件、分支、历史等的完整副本。,在里面。

第二步:下面是从历史中删除文件的代码。确保从克隆的存储库之外运行这段代码。

java -jar bfg.jar **--delete-files .env <Your-repo>.git**

上面的代码将在终端上打印一些有用的信息。它会通知您是否有任何与您的标准匹配的文件、分支和提交会受到影响,等等。

你的文件没有被删除。它只从 git 历史中删除。因此,在永久删除它们之前,您需要查看屏幕上的信息。

第三步:您可以运行下面的代码,当您完成评审时,将它从所有分支和提交中永久删除。这个命令需要在存储库中运行。

$ cd <Your-Repo>.git # if you're still outside the repo directory. $ git reflog expire --expire=now --all && git gc --prune=now --aggressive$ git push

如果您现在刷新远程存储库页面,您将看不到机密文件。不在文件区。甚至在提交时也没有。

我们刚刚从网上删除了我们的秘密。

最后的想法

推送敏感文件,如。用密码包围是我经常犯的错误。

BFG 可能不是你犯错误的通行证。后果可能是灾难性的。但这是为了营救。

最后,我想说的是,尽管 BFG 删除了所有分支和提交历史中的文件,但它不会影响你的最后一次提交。这种提交也被称为头部。

BFG 假设最后一次提交发生在生产中。改变那里的任何东西都可能使你的部署失败。

BFG 的文件说,“你需要解决这个问题,BFG 帮不了你。”

此外,如果其他人有您的存储库的本地副本,您仍然有风险。

💔-ways-to-deploy-machine-learning-models-in-production-cdba15b00e>

朋友,感谢你的阅读!在LinkedInTwitter,以及Medium上跟我打招呼。**

还不是中等会员?请使用此链接 成为会员 因为,在不为你额外付费的情况下,我为你引荐赚取一小笔佣金。

如何删除熊猫的标点符号

原文:https://towardsdatascience.com/remove-punctuation-pandas-3e461efe9584

展示了从 pandas DataFrame 列中消除标点符号的不同方法

亚历杭德罗·巴尔巴Unsplash 上拍摄的照片

介绍

当处理文本数据时,我们有时可能需要执行一些清理转换。其中之一通常是在标记化之前删除标点符号。

在今天的文章中,我们将展示几种不同的方法来删除 pandas 数据帧中字符串列的标点符号。更具体地说,我们将使用

  • str.replace()
  • regex.sub()
  • 还有str.translate()

首先,让我们创建一个示例数据框架,我们将在本文中引用它来演示一些概念。

import pandas as pd df = pd.DataFrame(
    [
        (1, 10, True, 'Hello!!!'),
        (2, 15, False, 'Hey..'),
        (3, 11, True, 'What?!'),
        (4, 12, True, 'Exactly!'),
        (5, 17, True, 'Not bad'),
        (6, 10, False, 'Yeap.!'),
        (7, 12, False, 'Hi. How are you?'),
        (8, 19, True, 'Nope,'),
    ],
    columns=['colA', 'colB', 'colC', 'colD']
)print(df)
 ***colA  colB   colC              colD*** *0     1    10   True          Hello!!!
1     2    15  False             Hey..
2     3    11   True            What?!
3     4    12   True          Exactly!
4     5    17   True           Not bad
5     6    10  False            Yeap.!
6     7    12  False  Hi. How are you?
7     8    19   True             Nope,*

使用 str.replace()和正则表达式

这里的第一个选项是[pandas.Series.str.replace()](https://pandas.pydata.org/docs/reference/api/pandas.Series.str.replace.html)方法,它可以用来替换 Series 对象中出现的所有正则表达式。

所以对我们有用的一个正则表达式是[^\w\s]+。为了理解这个正则表达式是如何工作的,考虑这样一个事实,即任何标点符号实际上都不是单词或句子。所以我们用否定(即^)来表示我们要用空字符串替换任何非词非句(即标点符号)。

df['colD'] = df['colD'].str.replace(r'[^\w\s]+', '')print(df)
 ***colA  colB   colC            colD*** *0     1    10   True           Hello
1     2    15  False             Hey
2     3    11   True            What
3     4    12   True         Exactly
4     5    17   True         Not bad
5     6    10  False            Yeap
6     7    12  False  Hi How are you
7     8    19   True            Nope*

使用 re.sub()方法

现在另一个选择是re包中的sub()方法,它提供正则表达式匹配操作。

[**re.sub(*pattern*, *repl*, *string*, *count=0*, *flags=0*)**](https://docs.python.org/3/library/re.html#re.sub)

返回用替换*repl* 替换*string**pattern*最左边不重叠出现的string

我们将使用与上一节中相同的正则表达式。

import redf['colD']=[re.sub('[^\w\s]+', '', s) for s in df['colD'].tolist()] print(df)
 ***colA  colB   colC            colD*** *0     1    10   True           Hello
1     2    15  False             Hey
2     3    11   True            What
3     4    12   True         Exactly
4     5    17   True         Not bad
5     6    10  False            Yeap
6     7    12  False  Hi How are you
7     8    19   True            Nope*

还要注意,有时在替换之前先编译正则表达式可能更有效。举个例子,

import re r = re.compile(r'[^\w\s]+')
df['colD'] = [r.sub('', s) for s in df['colD'].tolist()]

使用 str.translate()方法

最后,另一种方法是使用[pandas.Series.str.translate()](https://pandas.pydata.org/docs/reference/api/pandas.Series.str.translate.html)方法,通过给定的映射表映射输入字符串中的所有字符。

我们方法背后的直觉是将每行上的所有字符串组合在一起,形成一个大字符串,其中每个单独的字符串将由我们选择的分隔符分隔。

对于我们的例子,我们将使用|字符作为分隔符。因此,我们首先需要创建一个字符串,其中包含我们希望从字符串中删除的标点符号。请注意,此处不得包含分隔符。显然,选定的分隔符不能出现在任何现有的字符串中,否则这种方法将不起作用。

sep = '|'
punctuation_chars = '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{}~'

现在我们需要使用str.maketrans()方法来创建一个可用于translate()方法的翻译映射表。

mapping_table = str.maketrans(dict.fromkeys(punctuation_chars, ''))

现在我们需要做的是使用选定的分隔符sep连接colD列中的所有字符串。然后使用我们创建的映射表执行str.translate(),以删除punctuation_chars中指定的标点字符,最后split()删除分隔符sep上的结果字符串,并将结果赋回列。

df['colD'] = sep \
    .join(df['colD'].tolist()) \
    .translate(mapping_table) \
    .split(sep)

并且目标列应该没有任何标点字符。

print(df)
 ***colA  colB   colC            colD*** *0     1    10   True           Hello
1     2    15  False             Hey
2     3    11   True            What
3     4    12   True         Exactly
4     5    17   True         Not bad
5     6    10  False            Yeap
6     7    12  False  Hi How are you
7     8    19   True            Nope*

注意,这个方法是用 C 实现的,所以它应该是相当高效和快速的。

最后的想法

在今天的简短教程中,我们探索了几种不同的方法,可以用来从 pandas 数据帧的字符串列中删除标点符号。

更具体地说,我们展示了如何做到这一点,使用了三种不同的方法— str.replace()str.translate()regex.sub()。请注意,不同的方法在不同的数据集规模上可能会表现出明显不同的性能和效率。因此,选择最适合您的特定用例的最佳方式是将结果相互比较,并选择更合适的那个。

成为会员 阅读介质上的每一个故事。你的会员费直接支持我和你看的其他作家。你也可以在媒体上看到所有的故事。

https://gmyrianthous.medium.com/membership

相关文章你可能也喜欢

替换 Python 列表,让你的代码快几个数量级!

原文:https://towardsdatascience.com/replace-python-lists-and-make-your-code-orders-of-magnitude-faster-702c08fb397b

没有花哨的库,没有复杂的工程,只有一种数据结构——哈希表。

作者图片

每个初学编程的人都喜欢 for-loops,因为它们很实用,也很容易理解。同样,大家都爱数组。然而,更多的时候,我们开始在任何事情上使用数组,而不去考虑它。我们上了数据结构的课,但在实践所学内容时,我们做得不够。直到最近我才发现自己掉进了这个陷阱。我正在处理一个编程任务,试图挑战自己写出高效快速的代码。

我们可以做得更好!

在这篇文章中,我们将简要介绍 Python 中的字典和集合。我们将(重新)发现这些数据结构在我们的代码中使数据操作超快的能力。我们将了解哈希表如何导致巨大的算法性能改进。

但首先我们需要回顾一些概念。
是时候回归基础了!

回到基础:散列表

编程语言中的数据结构是计算机科学中抽象数据结构的实现。Python 字典和集合基本上是一个哈希表。哈希表是一种抽象数据结构的实现,称为关联数组,字典或映射。简单地说,哈希表是从键到值的映射。给定一个键,可以查询数据结构以获得相应的值。**

实现这一点的简单方法是使用一个直接地址表或一个数组。
所以对于一个键 1 ,我们将把值存储在数组的索引位置 1

图片作者。用数组直接寻址。
每个键简单地映射到数组中的下一个槽。

但是这种方法有一个问题。如果有很多键,数组的大小会很大。这使得按内存方式存储变得不可行。

对此的解决方案是使用一个散列函数。哈希函数负责将键映射到哈希表中存储的值。你给它传递一个键,它就抛出一个叫做哈希值的值。哈希值确定哈希表中存储对应于该键的值的位置。换句话说,它是一个指数。

图片作者。使用哈希函数和哈希表。

现在,哈希表的大小将小于直接地址表的大小。这意味着哈希表在内存方面是可行的。然而,这也意味着两个键可以映射到相同的值。
这被称为
哈希冲突,当哈希函数在给定两个不同键的情况下输出相同的值时就会发生。

图片作者。通过用链表链接来解决散列冲突。

为了解决这个问题,如果两个键散列为相同的值,我们可以将值存储在一个链表中(见上图)。任何随后的冲突只会导致该值被追加到链表中。这被称为通过链接的冲突解决。

现在你可以想象一下,如果所有的键都被散列为相同的值,那么它们都将被添加到链表中,这意味着性能不会比使用链表更好!

如果哈希表太满,就会调整其大小。该阈值被称为负载系数,并确定已占用插槽与可用插槽的比率。通常,当值超过3/4时,哈希表的大小就会改变。这确保了链表不会太满,并保证了承诺的平均性能。最后,散列函数通常以这样一种方式被选择,即它们跨槽均匀地分布密钥。这意味着碰撞的几率很小。

太好了!现在让我们看看一些大 O 符号。

回到基础:O(1)与 O(n)

在数组中搜索元素的平均时间复杂度为O(n),因为我们需要线性遍历列表来找到元素。
列表越大,搜索元素所需的时间越长。你可以想象这是一排人站在那里,从最左边的人开始,问他是否是你要找的人。
如果不是,你问旁边的人,以此类推。你可以想象,如果有 1000 个人对 10 个人,找到那个人需要更长的时间。

但是对于哈希表,搜索的平均时间复杂度是O(1)——它是常数。不管你存储的元素数量是多少,是 100 万还是 10 个,找到一个元素总是需要一个固定的时间单位。换句话说,对于所有大小的输入,它都是快速的。
你可以想象一群人站着,多一个人是你的帮手。要找到那个人,你只需要问帮助者,而不是在整个团队中寻找。助手会立即告诉你你感兴趣的人在哪里。

元素的插入和删除也是O(1)

点击 此处 获取一份非常好的数据结构和大 o 的备忘单。

好了,现在是编码的时候了,看看哈希表是如何工作的!

代码和基准

我们将模拟以下任务:

  • 一个列表**L**,包含从 0 到 100,000 的排序元素。倒数第三个索引有一个随机生成的负值,范围从 0 到 100,000。
  • 列表**R**包含从 0 到 100,000 的元素,按排序顺序排列。

任务很简单。我们需要找到**L**中没有出现在**R**中的值。

我知道有更简单的方法来解决这个问题,但出于学习的目的,我们将尝试一种更复杂的方法。

solution函数是一个解决方案的简单实现。我们对**L**进行迭代,并检查每个元素是否存在于列表**R**中。in操作符检查成员资格,类似于执行搜索操作。如果它存在,我们简单地返回负整数。

该解决方案的耗时约为60 秒* 。*

如果我们分析solution函数的代码,我们看到时间复杂度是【o(n^2】
这是因为对于 L 中的每个元素,我们都在遍历 R 并检查成员资格。for 循环在运行 n 步时采用 O(n) 。对于每一步,我们都要做 O(n) 操作,因为我们需要检查列表 R 中的每一个元素,看它是否是我们要找的元素。
这就给出了
【o(n)o(n)=o(n^2】***

该操作以二次时间运行。所花费的时间将等于输入大小的平方。你可以想象得到越来越大的数字的平方会产生越来越大的值。

我们能做些什么来改善这种情况?

代替遍历 R 来检查成员资格,我们可以将 R 实现为散列表而不是数组。因此,检查成员资格的时间复杂度将是(1)
因此,运算会取【O(n) O(1)= O(n)*

这是线性的输入大小!因此,所花费的时间将与输入大小成正比(与二次方相反)。

等着看性能提升吧!

计时solution2给出 0.011 秒的经过时间。这是超过 5000 倍的进步!

结论和结束语

总之,你已经了解了使用散列函数和散列表背后的动机。我们简要地看了一下散列表的大 O 符号,并将其与数组的大 O 符号进行了比较。更重要的是,我们看到了一个实现,它利用哈希表在创纪录的时间内解决了一个算法问题。

作为软件工程师,我们必须知道在正确的情况下使用正确的工具,而不是试图在我们遇到的每个场景中使用暴力技术。

记住这一点,有几点需要记住。

就像所有的事情一样,哈希表并不是万能的。它们并不适合所有的用例。例如,重复元素不能存储在哈希表中。因此,如果您的用例需要,您最好使用数组。同样,哈希表也是不排序的。如果您的用例需要一个排序的数据结构,哈希表不是答案。最后,没有内置函数来查找哈希表中的最大或最小元素。最后,上面提到的时间复杂度是平均情况。可能会出现“T2”最差情况,这意味着散列表的性能与数组相同。**

我希望你喜欢这篇文章的内容。下次见。

资源

科尔曼·托马斯·H 等人,算法简介(麻省理工学院出版社,2009)。

原载于https://haseebkamal.com**

在 Keras 中将逻辑回归模型复制为人工神经网络

原文:https://towardsdatascience.com/replicate-a-logistic-regression-model-as-an-artificial-neural-network-in-keras-cd6f49cf4b2c

神经网络和深度学习课程:第 11 部分

图片来自 PixabayGerd Altmann

逻辑回归是一个非常简单的神经网络模型,没有隐藏层,正如我在我的神经网络和深度学习课程第 7 部分中解释的那样。

这里,我们将使用 Scikit-learn 和 Keras 包构建相同的逻辑回归模型。Scikit-learn LogisticRegression()类是构建逻辑回归模型的最佳选择。然而,我们可以用神经网络思维在 Keras 中构建相同的模型,因为逻辑回归模型在技术上可以被视为 ANN。

编写本教程的主要目标是:

  1. 比较使用两个不同库构建的相同逻辑回归模型的性能。
  2. 建立一个 Keras 序列模型。
  3. 熟悉 Keras 类中的一些import约定。
  4. 使用 Keras 顺序模型实例的summary()compile()fit()evaluate()方法。
  5. 在模型训练和评估中,绘制每个时期的损失和准确性分数。

在本文的结尾,你将能够用神经网络的思维方式建立一个逻辑回归模型,并使用数值和可视化技术评估它的性能。

关于我们使用的数据

这里,我们使用 1995 年 11 月创建的 Scikit-learn 乳腺癌威斯康星州数据集

  • 数据集来源:你可以在这里下载原始数据集
  • 数据集许可:这个数据集是捐赠给公众的。它属于 CC0:公共领域许可。你可以在这里找到关于那种许可证的更多信息。
  • 创作者:威廉·h·沃尔伯格博士,w·尼克街,奥尔维·l·曼加萨里安
  • 捐赠者:尼克街
  • 数据集描述:该数据集包含目标列在内共 31 个变量。你可以在这里找到每个变量的描述。

让我们看看整个数据集的概况。

加载乳腺癌数据集(作者代码)

乳腺癌数据集的一部分(图片作者提供)

df.shape

这将返回(569,30)。数据集有 569 个观察值(行)和 30 个变量。这里不包括目标列。它有 0 和 1,代表两个类。

df.isnull().sum().sum()

这将返回 0。这意味着数据集中没有缺失值。

制作 X、y、训练和测试集

让我们创建 X 和 y,特征矩阵和目标向量。

X = df
y = pd.Series(cancer.target)

现在,我们创建训练集和测试集。我们将总数据的 20%分配给测试集。

制作列车和试验部件(作者代码)

用 Scikit-learn 建立逻辑回归模型

现在,我们使用 Scikit-learn LogisticRegression()类在乳腺癌数据集上构建逻辑回归模型。我们通过使用多种度量标准来评估我们的模型。

在 Scikit-learn 中构建逻辑回归模型(作者代码)

上述模型的输出(图片作者)

用 Keras 中的神经网络思维模式构建相同的逻辑回归模型

这是我们今天要强调的主要话题。这里,我们将构建一个可以复制上述逻辑回归模型的人工神经网络。整个过程分为如下 8 个步骤。

步骤 1:定义神经网络架构

在这里,我们建立一个序列模型。顺序模型只不过是一层层的堆叠。输入层、隐藏层和输出层按顺序堆叠在模型中。这是一种人工神经网络。更多详情,请阅读

我们的模型没有隐藏层!它只有输入层和输出层。输入层有 30 个节点(神经元),与数据集中的要素数量相等。输入层不需要激活。输出层只有一个节点,这里使用了 sigmoid 激活函数,因为我们正在执行二元分类(逻辑回归)任务。

步骤 2:实例化 Keras Sequential()类的模型

from keras.models import Sequential
ANN_model = Sequential()

步骤 3:向顺序模型添加层

一旦实例化,就可以使用add()方法将层添加到现有模型中。这里,我们通过使用 Keras InputLayer()类显式添加输入层。使用 Keras Dense()类添加输出层。两者都是 全连通 (也叫)层。在输出层中使用 sigmoid 激活。

*from tensorflow.keras.layers import InputLayer
from tensorflow.keras.layers import DenseANN_model.add(InputLayer(input_shape=(30, )))
# No hidden layers
ANN_model.add(Dense(1, activation='sigmoid'))*

第四步:获取模型的概要

*ANN_model.summary()*

(图片作者)

在我们的模型中有 31 个可训练参数。在训练期间,这些参数的值被更新,直到损失函数被最小化。请注意,这里没有显示输入层。

步骤 5:编译模型

在此步骤中,我们将配置培训步骤中所需的学习参数。这里,我们指定在训练过程中使用的优化器、损失函数和模型评估度量的类型。

*optimizer=tf.keras.optimizers.Adam(learning_rate=0.05)ANN_model.compile(optimizer=optimizer,
                  loss='binary_crossentropy',
                  metrics=['accuracy'])*

这里,我们使用学习率为 0.05 的 Adam 优化器。学习率显著影响我们模型的输出。所以,你可以随意改变这个值,观察不同的结果。随着课程的进行,你将学会如何选择最佳的学习速度。

二元交叉熵(也称为 对数损失 )是默认的损失函数,用于在训练期间测量二元(两类)分类问题的性能。

准确性度量用于评估二进制分类问题对测试数据的性能。

第六步:拟合模型

现在,模型已经为使用fit()方法进行训练做好了准备。这里,我们需要为fit()方法提供训练数据。

调用fit()方法的输出包含一些在训练过程中收集的有用信息,并保存在 历史 变量中。该信息包括在每次迭代(时期)之后计算的损失和任何其他度量分数。我们可以使用它们来绘制模型性能与历元数的关系图。

*history = ANN_model.fit(X_train, y_train, 
                        epochs=10, batch_size=32,
                        validation_split=0.2, 
                        shuffle=False)*

我们为训练数据提供X_trainy_train。我们定义了 10 个时期,以便优化器遍历整个训练数据集 10 次。一个时期是对整个训练数据集的迭代。每批训练数据包含 32 个训练样本,因为我们定义batch_size为 32。大约有(569/32)个批次。要了解更多关于批次大小和时期的信息,请阅读

validation_split是可选参数。如果包括的话,一部分训练数据被保留在一边以在每个时期之后验证模型。将分别计算培训和验证部分的损失和任何其他指标分数。

神经网络模型的输出(图片由作者提供)

当我们想要根据时期的数量来评估模型性能时,validation_split参数非常有用。我们可以忽略这个论点,或者在使用evaluate()方法和训练数据进行训练后评估我们的模型。

步骤 7:绘制模型在训练过程中的表现

现在,我们绘制模型性能与历元数的关系图。我们使用在训练期间计算的训练和验证部分的损失和准确性分数。所有这些都存储在 历史 变量中作为字典的值。你通过调用history.history得到全部。

*# Plot training and validation accuracy scores
# against the number of epochs.
plt.plot(history.history['accuracy'], label='Train')
plt.plot(history.history['val_accuracy'], label='Validation')
plt.ylabel('Accuracy')
plt.xlabel('Epoch')
plt.title('Model Accuracy')
plt.legend(loc='upper left')*

(图片由作者提供)

*# Plot training and validation loss scores
# against the number of epochs.
plt.plot(history.history['loss'], label='Train')
plt.plot(history.history['val_loss'], label='Validation')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.title('Model Loss')
plt.legend(loc='upper right')*

(图片由作者提供)

步骤 8:根据测试数据评估模型

请注意,我们还没有根据测试数据评估我们的模型。在我们的例子中,我们可以通过使用返回 test_losstest_acc 分数的evaluate()方法来轻松实现。

*test_loss, test_acc = ANN_model.evaluate(X_test, y_test)
print("Test loss:", test_loss)
print("Test accuracy:", test_acc)*

(图片由作者提供)

由该 ANN 模型返回的准确度分数更接近于先前由逻辑回归模型返回的准确度分数。

摘要

我们也可以对训练数据使用evaluate()方法。这是在fit()方法中指定validation_split参数的替代方法。

即使对于一个简单的 ANN 模型,我们也需要为下面的超参数指定最佳值,这些超参数控制可训练参数的值( W s 和 b s)。

  • 网络中的层数
  • 每层中的节点数量(也称为层大小)
  • 损失函数的类型
  • 优化器的类型
  • 评估指标的类型
  • 激活功能的类型
  • 批量
  • 纪元
  • 学习率

当执行我在这里包含的代码时,不同的执行会得到不同的结果。这是因为算法中包含的随机性。您可以通过如下设置随机种子来获得静态结果。

*import numpy as np
import tensorflow as tfnp.random.seed(42)
tf.random.set_seed(42)*

今天的帖子到此结束。

如果您有任何问题或反馈,请告诉我。

我希望你喜欢阅读这篇文章。如果你愿意支持我成为一名作家,请考虑 注册会员 以获得无限制的媒体访问权限。它只需要每月 5 美元,我会收到你的会员费的一部分。

*https://rukshanpramoditha.medium.com/membership

非常感谢你一直以来的支持!下一篇文章再见。祝大家学习愉快!*

鲁克山普拉莫迪塔
2022–05–19

使用 Python 的报告自动化技巧

原文:https://towardsdatascience.com/report-automation-tips-with-python-783a9cd58e23

使用 PYWIN32 实现自动化

分享一些技巧,让你的自动化脚本更干净,更容易调试。我从一年前写的新秀剧本中了解到

照片由 Amanpreet DeolUnsplash 上拍摄

嗯,最近,我正在调试我一年前写的自动化脚本,因为它们不能在新电脑上正常运行。我只是不能停止思考我当时在想什么。总的来说,调试我的脚本是一次有趣而痛苦的经历。哈哈!

我从痛苦的调试经历中学到的第一个教训是:

不要硬编码日期计算变量

通常,自动化脚本被编写为在从 ERP 系统生成输入报告的当天或第二天填充每周报告。生成的输入报告将总是具有相同的报告名称,并且总是具有报告生成日期的文件名。

例如材料使用报告 15042022.xlsx材料使用报告 22042022.xlsx 。报告名称“材料使用报告”应该始终一致,日期格式也应该一致。

在我的例子中,生成的报告通过保存在网络驱动器中或通过电子邮件发送出去来共享。然后,自动化脚本将读取它们并执行所需的转换,比如合并表或旋转。

所以,我使用datetime.date.today()函数来获取日期,然后结合需要下载的报告的名称。如果脚本被安排在报告生成的同一天,我将直接使用函数的输出,否则我将使用timedelta函数来计算实际日期。

# the file is processed on the next day of the file received
file_received_date = datetime.date.today() - timedelta(*days* = 1)

这在开发阶段看起来非常好,但是到了测试阶段,我总是不得不手动调整timedelta函数的参数来获得正确的日期。经过无数次的测试,我终于意识到,我不应该这样编码。因为脚本每周运行一次,所以我应该能够在一周的任何一天运行脚本来获得输出。例如下面,脚本被安排在每周一。

# add 1 day for everyday delayed
day_delay = datetime.date.today().weekday()# the process file dated the next day of the file received
date_process_file = datetime.date.today() - timedelta(*days* = day_delay)

weekday()函数将以数字形式返回日期的星期几。因为 Python 索引从零开始,所以一周的第一天,即星期一,用 0 表示,如下所示。

Monday: 0
Tuesday: 1
Wednesday: 2
Thursday: 3
Friday: 4
Saturday: 5
Sunday: 6

因此,如果我在星期二运行脚本,也就是第二天,则 date_process_file 需要减去一天。

如果我的脚本被安排在周一以外的任何一天,我只需要从 day_delay 中减去当天的索引,计算如下。day_delay 以数字形式返回日期的星期几。

# Scheduling on Wednesday
date_process_file = datetime.date.today() - timedelta(*days* = day_delay - 2)

这背后的逻辑如下图所示。

图片作者。用 Excalidraw 创建。

对于计划在星期一运行的报告,如果我们在星期日运行脚本,我们必须将日期减去 6 天才能得到星期一的日期,而 6 是星期日的索引,星期一的索引是 0。

对于计划在周三运行的报告,如果我们在周日运行脚本,我们必须减去 4 天才能得到周三的日期,而 4 等于周日的索引减去周三的索引。

如果我们推导出一个公式,它应该是这样的。

actual_date = datetime.date.today() - timedelta(*days* = weekday_script_run - weekday_report_scheduled)

明智地使用复制方法

我的一个痛点是我当时太懒了。我滥用了 PyWin32 复制方法。当我被指派自动执行每周报告时,已经有旧的报告设置了格式。因此,我只是将上周报告的格式复制并粘贴到由我的自动化脚本生成的新工作簿中。

# copy paste FORMAT only, which includes font colour, fill colour, and border
wb2.Sheets(sheetname).Range("A:D").Copy()
wb.Sheets(sheetname).Range("A1").PasteSpecial(Paste=win32c.xlPasteFormats)

一切都很好,直到用户希望在新报告中添加一个新列。这意味着我的复制粘贴技巧不能再用了,否则格式将会完全不匹配🤣。

这是我学到的第二课:

明智地使用复制方法

在您编写脚本将任何内容复制粘贴到新报告之前,请三思!复制-粘贴就像硬编码任何 Python 变量一样。总是回顾你试图复制粘贴的部分,并彻底思考如果将来有新的需求,这是否会受到影响。

如果您想知道我是如何解决这个问题的,我会显式地对格式进行编码🤣。

wb.Sheets(sheetname).Columns("A:A").AutoFit()
wb.Sheets(sheetname).Columns("B:B").ColumnWidth = 19.71
wb.Sheets(sheetname).Columns("C:C").ColumnWidth = 43
wb.Sheets(sheetname).Columns("D:I").AutoFit()
wb.Sheets(sheetname).Columns("J:N").ColumnWidth = 23

在解决这个问题的过程中,我还学习了一些可以用 Python 实现的 Excel 格式。

文字换行

wb.Sheets(sheetname).Range("B1").WrapText = True

设置边框线条样式

作者要点

在第 12 行,Range(select_table, select_table.End(win32c.xlDown))将选择 Excel 文件中的整个表格,因此我们不需要指定最后一行。第一个参数select_table 是指表格的第一行,第二个参数select_table.End(win32c.xlDown)将根据第一列选择行,当第一列为空时停止。

修复系统生成的 Excel 文件,该文件不能被使用 PyWin32 的熊猫读取

这是指如下的类型错误。

TypeError: expected <class 'openpyxl.styles.fills.Fill'>

因此,Excel 文件可以直接在我的笔记本电脑上打开,但无论我如何尝试,熊猫都无法读取。然后,最终,我解决了这个问题,我想是以一种不太聪明的方式🤣。这是 StackOverFlow 平台上存在的一个问题,人们建议恢复到旧版本的 OpenPyXL 或将文件重新保存为 XLS 文件类型。

我确实尝试过恢复到旧版本的 OpenPyXL,但是没有解决错误。我还试着安装了与我在旧 PC 上安装的版本相同的所有软件包,在旧 PC 上脚本仍然可以工作,但在新 PC 上仍然出现相同的错误。

然后,我尝试建议的第二种解决方案,即将文件重新保存为 XLS 文件。不幸的是,它导致了另一个错误如下。

XLRDError: Workbook is encrypted

我试着通读微软的文档,但是仍然不能保存未加密的文件。当我几乎要放弃的时候,我试着将文件重新保存为 CSV 文件,它运行得非常好!

import win32com.client as win32
import pandas as pd
win32c = win32.constants
import pandas as pdfile_xlsx = r"the original file*.*xlsx"
file_csv = r"the new file*.*csv"# create excel object
excel = win32.gencache.EnsureDispatch('Excel.Application')# excel can be visible or not
excel.Visible = True  # False
wb = excel.Workbooks.Open(file_xlsx_local)wb.SaveAs(file_csv, win32c.xlCSV, win32c.xlLocalSessionChanges, win32c.xlExclusive)wb.Close()
excel.Quit()# read the resaved file
df_csv = pd.read_csv(file_csv, *encoding*='windows-1252', *low_memory*=False)

该文件需要一个特殊的编码来读取,但现在一切都解决了😉。

我把这个问题添加到我的课程中的原因是,我的学长建议我把它保存为一个新文件,但我一开始并没有认真对待。如果我直接在我的笔记本电脑上打开文件,并将其保存为不同的文件,它可以工作。但是,我觉得我甚至不能用熊猫来阅读它,我怎么能把它保存为另一个文件。

在挣扎了几个小时后,我意识到我所需要的就是使用 PyWin32 打开文件并将其保存为另一个文件。这是因为 PyWin32 通过访问 Window 的组件对象模型(COM)[1][2]来访问 Excel 文件。

所以,这里学到的一课是认真对待学长的建议,他们是学长是有原因的。此外,我应该在写剧本的时候保持创造性。

以上是我在调试脚本时学到的三点经验。

你经历过类似的事情吗?

让我知道我并不孤单!🤣

写一个干净的 Python 代码

最后,我很感激我在写这个脚本之前阅读了干净代码的概念。我稍微干净的代码允许我在我第一次写代码 1 年后很快赶上我的代码(仍然有很大的改进空间😁).

我忘了我是在哪里读到的,但是我相信你在学习编程的时候,在某个地方看到过关于编写干净代码的文章或视频。比如这是我最近看的的一篇。

对于编写干净的代码,有各种版本的建议。对我来说,最重要的概念是保持变量和函数名的可解释性。名称应该是描述性的,易于阅读。因此,应该避免含糊的缩写形式。

保持联系

订阅 YouTube

边注

用 Python 自动化 Excel中,我解释了 Excel VBA 的对象、方法和属性。这是使用pywin32时你必须知道的 3 个主要概念。

如果您有兴趣自动化数据透视表,并从数据透视表中提取过滤后的数据到 pandas DataFrame ,那么您可以使用用 Python 自动化数据透视表(创建、过滤和提取)

如果你的工作使用 Google Sheets 而不是 Microsoft Excel,你可以参考这篇文章,“自动化 Google Sheet Report ”来了解可能的自动化。

参考

  1. Excel COM 加载项和自动化加载项
  2. Chris moff ITT 著

祝贺并感谢你阅读到最后。希望你喜欢这篇文章。😊

照片由Courtney hedgeUnsplash 上拍摄

正确报告 A/B 测试的影响

原文:https://towardsdatascience.com/reporting-the-impact-of-your-a-b-tests-correctly-3cdb15d60d79

如何使用背线碰撞来测量和比较实验结果

(图片来自Unsplash.com)

P 实验中的阳性结果是分析师日常工作中的亮点之一(对产品团队也是如此)。从构思到实施,看到一个关键指标朝着预期的方向显著移动,是对实验中所有辛勤工作的巨大回报。

在这种情况下,很容易走开,自豪地向组织的其他人报告一个关键指标的观察到的百分比增长。然而,测试结果通常不能代表全部用户,可能会错误地反映实验的整体业务影响。

在这篇文章中,我将深入探讨如何在整个组织中报告实验结果,以允许不同产品计划之间的可比性,并支持公正的决策。

曝光点

让我们从一个例子开始,在 ebay 的产品页面上看下面的(编造的)AB 测试。新的“免邮费”横幅已添加到测试单元,目的是转化更多用户,增加该群体的购买数量:

(图片由作者提供)

横幅在以下情况下显示:

  • 用户需要进入产品页面(因此,只停留在主页上的用户不会接触到实验)
  • 产品页面上显示的项目必须符合免费送货的条件(因此,只查看不合格产品的用户将不会被添加到测试中)

并非所有用户都是实验的一部分

当进行这样的测试时,并不是网站或应用程序上的所有用户都会立即加入到实验中。通常我们选择一个暴露点,用户必须通过这个暴露点才能成为 AB 测试的一部分。这个点应该尽可能地靠近治疗,以避免结果的稀释,并确保我们只测量新变化的影响。

免费邮寄- 实验中,一个好的曝光点可能是有资格免费送货的产品页面的入口。因此,只有至少进入过一次这样的页面的用户才会成为实验的一部分(无论是在测试组还是对照组)。

让我们假设 80%的活跃用户进入了产品页面,其中 10%的用户查看了一个符合免邮费条件的商品页面。这意味着 8%的活跃用户将会接触到这个实验:

实验人群的构成(占总人口的百分比)(图片由作者提供)

实验与背线碰撞

假设我们运行了这个实验,并在测试组中看到每个用户的事务有+10%的显著增长。在这一点上,在整个组织中广泛宣布这一巨大胜利是非常诱人的("哇,我们每个用户的交易量增加了 10%!")。

团队可能会尝试跟进这一成功,并开始考虑类似的实验。其他实验虽然成功,但只实现了 2%的增长,可能看起来没什么大不了的。

但是让我们记住,只有一小部分用户真正有资格成为实验的一部分。这意味着,如果实验发布,对每个用户的事务的总体影响将显著降低!

报告顶线而不是实验影响

在上面的例子中,假设只有 8%的用户符合条件,实验对整个群体的交易率的总体影响(或顶线影响)将只有 0.8%,而不是 10%。

另一个实验只显示了实验中 2%的用户交易增加,如果它影响了更大的用户群,那么它仍然更有价值(为此,超过 40%的用户必须在新体验的目标群中)。

这意味着实验表明,如果覆盖率更高的话,期望指标的增益更小,最终仍然可以产生更大的影响

产品团队应始终致力于根据对顶线指标的影响来报告他们的结果。这在比较不同测试的影响时避免了混淆,并允许组织的其他部分容易地评估特定产品领域的潜力。

喜欢这篇文章吗?

那么你可能也会喜欢我的另一个关于 AB 测试的帖子:

将英国的公共记录表示为知识图表

原文:https://towardsdatascience.com/represent-united-kingdoms-public-record-as-a-knowledge-graph-647b6fd07b3d

利用英国公报 API 功能构建知识图,并在 Neo4j 中进行分析

我喜欢从各种来源构建知识图表。一段时间以来,我一直想创建一个政府知识图表,但一直在努力寻找任何易于访问且不需要我花费数周时间开发数据管道的数据。起初,我认为我必须使用 OCR 和 NLP 技术从公共记录中提取有价值的信息,但幸运的是,我偶然发现了英国公报。英国公报是一个保存英国官方公共记录信息的网站。在开放政府许可 v3.0 下,网站上和通过其 API 的所有内容都是可用的。这意味着,虽然端点返回个人信息,如相关人员的姓名,但我将在本文中排除这些信息。因为它们通过 API 端点提供公共记录信息,所以我们不必使用任何抓取工具来提取信息。更令人印象深刻的是,您可以以链接数据格式(RDF)导出数据。链接数据是一种表示结构化数据的格式,这种数据是相互链接的,并且在处理节点和关系时实质上表示一个图形。

例如,我们可以看一个包含道路交通法通知的示例通知。如果我们点击通知的链接数据视图,我们可以检查数据结构。由于许可权的不确定性,我在这篇文章中省略了通知截图,但所有数据都可以在官方公共记录网站上找到。

我们可以获取与给定的道路交通行为通知相关联的数据信息,并构建一个知识图。

关联数据信息的图形可视化。图片由作者提供。

因为链接数据结构(RDF)已经包含了关于节点和关系的信息,所以我们不需要手工定义一个图表模式。

大多数图数据库使用 RDF(资源描述框架)或 LPG(标记属性图)模型。如果使用 RDF 图形数据库,链接数据信息的结构将与数据库中的图形模型相同。然而,你可能从我以前的帖子中知道,我喜欢使用 Neo4j,它利用了 LPG 图模型。这里我就不多讲这两种模式的区别了。如果你想了解更多关于 RDF 和 LPG 模型之间的区别,我会向你推荐由 Jesús Barrasa 所做的演讲

由于链接数据结构经常用于传输数据,Neo4j 的人员通过使用 Neosemantics 库,使得导入或导出链接数据格式的数据变得容易。在本帖中,我们将使用 Neo4j 数据库结合 Neosemantics 库来存储从英国公报的 API 中获取的链接数据信息。

和往常一样,如果你想跟着一起去,我准备了一个 Colab 笔记本

环境设置

要继续操作,您需要有一个安装了 Neosemantics 库的 Neo4j 数据库的运行实例。

一种选择是使用 Neo4j 沙盒环境,这是 Neo4j 数据库的一个免费云实例,预装了 Neosemantics 库。如果您想使用 Neo4j 沙盒环境,启动一个带有空数据库的空白项目

另一方面,您也可以使用 Neo4j 的本地环境。如果你选择本地版本,我推荐使用 Neo4j 桌面应用,这是一个数据库管理应用,它有一个简单的界面,只需点击一下就可以添加插件。

设置与 Neo4j 实例的连接

在开始之前,我们必须从笔记本环境建立与 Neo4j 的连接。如果您正在使用沙盒实例,您可以从连接详细信息选项卡中复制详细信息。

沙盒连接详细信息。图片由作者提供。

现在,您只需在代码中输入您的连接详细信息:

配置新语义库

需要在资源节点上定义唯一的约束,新语义库才能工作。您可以使用下面的 Cypher 语句定义 unique 约束。

CREATE CONSTRAINT n10s_unique_uri IF NOT EXISTS ON (r:Resource)
ASSERT r.uri IS UNIQUE

接下来,我们需要定义新语义配置。我们有几个选项来指定我们希望 RDF 数据如何作为 LPG 图导入。我们将保留大部分配置默认值,只设置 handleVocabUriapplyNeo4jNaming 参数。同样,您可以查看配置选项的完整参考文档。

使用下面的 Cypher 语句定义新语义配置。

CALL n10s.graphconfig.init({
  handleVocabUris: 'MAP',
  applyNeo4jNaming: true
})

构建英国公共记录的知识图谱

我们将利用英国公报 API 搜索通知。通知提要 API 是公开可用的,不需要任何授权。然而,出于某种原因,你需要假装你是一个浏览器。我不知道这背后的原因,但我花了 30 分钟试图让它工作。API 的文档可以在 GitHub 上获得。

通过 API 过滤通知的两个主要参数是类别代码和通知类型。类别代码是更高级别的过滤器,而通知类型只允许您选择类别的一个子部分。类别代码和通知类型的完整列表可在以下网站上获得。有一个广泛的选择通知你可以选择,从国家和议会到公司法规等等。

如上所述,我们可以下载每个通知的链接数据格式信息。Neosemantics 库的一个优点是它可以从本地文件以及简单的 API 中获取数据。工作流程如下。

  • 使用通知提要 API 来查找相关的通知 id
  • 使用 Neosemantics 提取关于指定通知 id 的 RDF 信息,并将其存储在 Neo4j 中

以下 Cypher 语句用于从英国 Gazette API 导入 RDF/XML 结构的信息。

Cypher 语句期望数据参数包含一个链接列表,其中包含关于通知的 RDF/XML 信息。Neosemantics 库也支持其他 RDF 序列化格式,比如 JSON-LD 和其他格式,如果你想知道的话。

最后,我们将定义接收类别代码和通知类型参数的函数,并将通知信息存储在 Neo4j 数据库中。

我们从通知提要 API 的每个请求中获得 100 个通知 id。如果您想导入更多内容,我已经在函数中包含了分页功能。正如您可能从代码中看到的,我们向通知提要发出一个请求,并构造一个链接列表,其中存储了关于通知的 RDF/XML 信息。接下来,我们将该列表作为参数输入到 Cypher 语句中,其中 Neosemantics 库将遍历所有链接并将信息存储在 Neo4j 中。这很简单。

现在,我们可以导入州类别代码下的最后 1000 个通知。如果您查看通知代码参考,您可以看到状态通知属于类别代码值 11。

导入图形的图形模式很复杂,不容易可视化,因此我们将跳过它。我没有花太多时间理解整个图形结构,但是我准备了几个示例 Cypher 语句,可以让我们开始。

例如,我们可以检查任何奖项的最后五名获奖者。

MATCH (award)<-[:ISAWARDED]-(t:AwardandHonourThing)-[:HASAWARDEE]->(person)-[:HASEMPLOYMENT]->(employment)-[:ISMEMBEROFORGANISATION]->(organization)
RETURN award.label AS award,
       t.relatedDate AS relatedDate,
       person.name AS person,
       employment.jobTitle AS jobTitle,
       organization.name AS organization
ORDER BY relatedDate DESC
LIMIT 5

结果

我了解到一个人也可以被任命为大英帝国的指挥官。

MATCH (n:CommanderOrderOfTheBritishEmpire)<-[:ISAPPOINTEDAS]-(notice)-[:HASAPPOINTEE]->(appointee),
      (notice)-[:HASAUTHORITY]->(authority)
RETURN n.label AS award,
       notice.relatedDate AS date,
       appointee.name AS appointee,
       authority.label AS authority
ORDER BY date DESC
LIMIT 5

结果

为了避开裁决,我们还可以检查哪些通知与各种法规相关。

MATCH (provenance)<-[:HAS_PROVENANCE]-(n:Notice)-[:ISABOUT]->(l:Legislation:NotifiableThing)-[:RELATEDLEGISLATION]->(related)
RETURN n.hasNoticeID AS noticeID,
       n.uri AS noticeURI,
       l.relatedDate AS date,
       provenance.uri AS provenance,
       collect(related.label) AS relatedLegislations
ORDER BY date DESC
LIMIT 5

结果

我们的知识图表的好处在于它包含了公报网站的所有数据参考。这使我们能够核实并在需要时找到更多信息。此外,通过我的数据探索,我注意到并不是所有的信息都是从通知中解析出来的,因为很多信息很难自动组织成图表。稍后会详细介绍。

假设你和我一样,很快对状态信息感到厌倦。在这种情况下,您可以获取更多与业务相关的信息,如公司回购自己的股票、公司董事被取消资格或合伙关系解除。

我花了 30 分钟弄清楚如何正确使用通知类型和类别代码参数来过滤通知提要。当您要按通知类型筛选时,必须包括类别代码参数。否则,过滤不会像预期的那样工作。

我们不必担心为额外的通知提要创建单独的图表或数据库。图表模式已经在 RDF/XML 数据结构中定义,您可以将所有通知类型导入到一个 Neo4j 实例中。

现在,您可以检查哪些合作关系已经解散。

MATCH (n:PartnershipDissolutionNotice)-[:ISABOUT]->(notifiableThing)-[:HASCOMPANY]->(partnership),
      (notifiableThing)-[:ISENABLEDBYLEGISLATION]->(enabledby)
RETURN n.hasNoticeID AS noticeID,
       notifiableThing.relatedDate AS date,
       notifiableThing.uri AS noticeURI,
       enabledby.label AS enablingLegislation,
       partnership.name AS partnership
ORDER BY date DESC
LIMIT 5

结果

另一个有趣的信息是关于哪些公司已经或打算回购自己的股票。

MATCH (legislation)<-[:RELATEDLEGISLATION]-(n:RedemptionOrPurchase)-[:HASCOMPANY]->(company)
RETURN n.relatedDate AS date,
       company.name AS company,
       company.uri AS companyURI,
       collect(legislation.label) AS relatedLegislations,
       n.uri AS noticeURI
ORDER BY date DESC
LIMIT 5

结果

更上一层楼

如前所述,在一些例子中,并非所有信息都是从链接数据结构中的通知中提取的。一个这样的例子是成员在伙伴关系中的变化。我们有关于成员发生变化的合作关系的信息,但不知道具体发生了什么变化。我们可以检索的所有数据如下:

MATCH (notice)-[:ISABOUT]->(n:PartnershipChangeInMembers)-[:HASCOMPANY]->(company)
RETURN notice.hasNoticeID AS noticeID,
       notice.uri AS noticeURI,
       n.relatedDate AS date,
       company.name AS company
ORDER BY date DESC
LIMIT 5

结果

例如,如果我们检查网站上的第一次通知,我们可以观察到链接数据格式中没有成员的实际变化。我认为遗漏成员变更信息的原因是成员变更通知有太多的变化,无法以结构化的方式捕获它们。

似乎条条大路通罗马,或者在我们的情况下,当处理文本时,你可能不得不利用 NLP 技术。所以我添加了一个简单的例子,使用空间从通知中提取组织和个人实体。

通知的文本没有存储在我们的知识图中,所以我们必须利用英国公报 API 来检索它。我使用 BeatifulSoup 从 XML 响应中提取文本,然后让它通过 SpaCy 的 NLP 管道来检测组织和人员提及。代码没有将实体存储回 Neo4j。我只是想给你一个简单的例子,告诉你如何开始利用 NLP 的能力来提取更多的信息。

我们现在可以在伙伴关系通知的成员中检测到几个变化的实体。

结果如下:

Extracting entities for 3996989

Pursuant to section 10 of the Limited Partnerships Act 1907, notice is hereby given in respect of IIF UK 1 LP, a limited partnership registered in England with registered number LP012764 (the “Partnership”), that:

 1.	FCA Pension Plan Trustee Limited as trustee of the FCA Pension Plan was admitted as a new limited partner of the Partnership.

Entities 
 --------------------
IIF UK 1 LP ORG
LP012764 ORG
the FCA Pension Plan ORG

NLP 管道没有提取特定的变化,但至少这是一个开始,因为由于文本的非标准结构,您无法创建基于规则的差异提取。如果您运行提取,您可以观察到通知在文本结构和信息上有很大的不同。我省略了其他例子,因为它们包含个人信息。

结论

英国公报 API 提供了丰富的官方信息,你可以用来检查国家,企业或个人破产。这些只是我想到的一些使用案例。使用 Neosemantics 库,您可以轻松地将检索到的链接数据格式导入 Neo4j,在那里可以对其进行处理和分析。如果你有一些 NLP 能力,你也可以丰富知识图。

如果您发现了一些令人兴奋的用例,或者已经开发了从文本中提取附加信息的 NLP 管道,请告诉我。

和往常一样,代码可以在 GitHub 上获得。

使用 Conda 的可再现数据科学

原文:https://towardsdatascience.com/reproducible-data-science-with-conda-48f9de6eabd5

教程|管理环境| CONDA

使用 Conda 的可再现数据科学

可重复的数据科学,快速简单的方法

(图片由作者提供)

对于任何类型的研究,重要的是其结果可以被他人复制。如果他们没有复制测试的方法和工具,他们可能会得出不同的甚至是矛盾的结论。在数据科学环境中,有两个非常重要的变异来源需要控制:代码本身,以及用于运行代码的包。

对于代码本身,你可能已经在使用某种版本控制系统,比如 gitGitHub 。这些确保了使用了正确的代码版本。只要您和您的团队正确使用它,找到最新版本的代码应该是一件简单的事情。

变化的另一个来源是代码运行的环境,比如使用的 python 版本或者安装的包的版本。

一个例子

在这里,我假设您已经掌握了 git 和 GitHub,但是需要知道如何确保相同的代码以相同的方式运行。如果这听起来有点混乱,让我们后退一步,看一个例子,这将有助于解释它。

scikit learn 模块中,有一个[LatentDirichletAllocation](https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.LatentDirichletAllocation.html#sklearn.decomposition.LatentDirichletAllocation) (LDA) 功能。这个函数对文本文档执行主题建模,根据文档中包含的单词确定文档中的主题。这个函数有一个名为learning_method的参数,它极大地改变了完成这个任务的方式。有一种batch方法,它在每一步中使用所有的训练数据,还有一种online方法,它使用小批量的训练数据来增量地更新模型。

当 scikit learn 发布 0.20 版本时,他们将参数learning_method的默认值从online更改为batch。这意味着任何使用版本 0.19.2 训练此模型的人都会让它以不同于运行版本 0.20.4 的人的方式运行,假设他们没有显式设置此参数。

首先,这如何改变模型的输出?嗯,根据这篇论文的摘要,

…在线 LDA 发现主题模型与批处理中发现的模型一样好,甚至更好…而且只需很少的时间。

因此,无论选择哪种方法,模型的输出都不会有太大的不同。问题在于计算的速度。对于生产环境,尤其是使用数百万条记录来训练该模型的生产环境,速度很重要。如果没有必要,您不希望不必要地升级机器或配置虚拟机来处理大得多的数据。论文提到了这一点,但是 scikit learn 在他们的文档中也提到了LatentDirichletAllocation (LDA)函数的速度。来自版本 1.0.2 文档:

一般来说,如果数据量很大,在线更新会比批量更新快很多。

因此,如果我们运行完全相同的代码来训练 LDA 模型,但我们使用 2 个不同版本的 scikit learn,我们可能需要完全不同的时间和计算资源来训练模型。为了确保我们使用相同版本的 scikit learn,我们可以设置、导出和加载一个环境。这样,每个人都将使用相同的默认参数运行相同版本的包。

让事物与环境保持一致

环境只是定义代码运行环境的一种方式。在公共存储库中,你可能会看到一个requirements.txt或者一个environment.yml。这些文件将包含用于分析的环境中的包的列表,使用了哪个版本,以及可能是从哪里安装的。在 python 中有几种管理环境的方法,但是在这里我们将集中在使用 conda 和environment.yml文件从命令行进行管理。

先决条件

首先,为了在 Windows 的命令行中使用 conda 命令,您需要下载并安装 Anaconda Navigator 。这是我喜欢安装 python 的方式,因为它允许使用 conda 命令和非常简单的环境管理,同时允许安装许多流行的工具,如 Jupyter 笔记本。它还在 Windows 上安装了 Anaconda 提示符,允许您从命令行轻松地与 python 交互,而无需将其添加到您的系统路径中。你可以在 Anaconda 的网站上了解更多关于安装它的信息。

创造新环境

如果你刚刚开始一个项目,创造一个环境是一个很好的起点。从 Anaconda 提示符中,基本命令是

$ conda create --name envname

其中envname是要创建的环境的名称。如果您想要安装特定版本的 python,您可以将它包含在环境创建命令中。

$ conda create --name envname python=3.7

如果您需要安装软件包,或者这些软件包的特定版本,也可以在这里指定。

$ conda create --name envname scipy=0.15.0

当您创建环境时,您可以添加多个参数来获得您想要的环境,指定 python 的一个版本并一次性安装您想要的包和版本。不知道需要哪些包或版本?不要担心,您可以在以后安装更多的包到环境中。

激活环境

一旦创建了环境,您需要激活它来使用它。

$ conda activate envname

其中envname是环境的名称。您将在下一个命令的命令行中看到该环境的名称,表明您现在正在使用该环境。

“基地”变“DS-775”,我的环境(作者截图)

一旦环境被激活,你就可以启动一个 Jupyter 笔记本,像平常一样开发。

列出环境

如果您已经创建了多个环境,或者即使您使用某个特定的环境已经有一段时间了,您可能想要查看哪些环境是在您的机器上创建的。

$ conda env list

提供系统上所有环境及其位置的列表。如果您忘记了环境名称的拼写,这也使得激活环境变得容易。

环境和 environment.yml 文件

如前所述,一些开发人员会在他们的存储库中包含一个environment.yml文件,这样其他人就可以使用相同的包复制他们的工作。这对于生产环境也很有帮助,这样我们就可以避免我在介绍示例中讨论的包版本之间的差异问题。

$ conda create -f environment.yml

从包含environment.yml的目录中,运行上面的命令。f 标志告诉 conda 我们想要使用文件中的信息来创建环境。

如果我们想创建environment.yml文件,我们可以在环境激活时导出它。

$ conda activate myenv
$ conda env export > environment.yml

这将在当前目录中创建文件,其中包含我们在另一台机器上复制环境所需的所有内容。只需将该文件发送到另一台机器,并使用它创建环境。

结论

我们讨论了使用环境背后的动机,提供了一个例子,当使用相同包的不同版本时,相同的代码可以提供不同的执行。我们还介绍了一些使用 conda 从命令行管理环境的最有用的命令,例如创建环境,使用environment.yml文件创建环境,以及创建environment.yml文件与他人共享您的环境。

https://realdrewdata.medium.com/membership

有关使用 conda 管理 python 环境的更多信息,请查看以下资源:

可重复 ML:也许你不应该使用 Sklearn 的 train_test_split

原文:https://towardsdatascience.com/reproducible-ml-maybe-you-shouldnt-be-using-sklearn-s-train-test-split-ea8550ddd18d

照片由杰森·登特Unsplash 拍摄

再现性对于强大的数据科学至关重要,毕竟,这是一门科学。

但是 ML 中的重现性可能非常困难:

模型的行为不仅取决于你的代码,还取决于用来训练它的底层数据集

因此,严格控制用于训练和测试模型的数据点对于确保重现性至关重要。

分割数据的方式会对感知的模型性能产生重大影响

如果您“随机”分割数据,那么从统计上来说,测试集中的离群值会比训练集中的离群值多。由于您的模型在训练期间不会“看到”许多异常值,因此在预测“异常值”时,它在测试集上的表现会很差。

现在想象一下,你再次随机分割数据,现在“离群值”都在训练集中,没有一个在测试集中。很可能你的‘模特表现’会提高。这种性能提升与所选择的模型关系不大,只是与训练/测试集的统计属性有关。

因此,控制和理解训练和测试拆分非常重要,以便在多次训练运行中有效地比较不同的候选模型。

Sklearn train_test_split

大概最流行的分割数据集的方法是使用 Sklearn 的 train_test_split 函数。

开箱即用,train_test_split函数会将您的数据随机分成一个训练集和一个测试集。

每次运行该函数时,您将获得不同的数据分割。再现性不理想。

“啊!”—你说

"我设置了随机种子,所以它是可重复的!"。

公平点。

设置随机种子当然是一个很好的主意,而且对提高可重复性大有帮助。我强烈建议为任何具有非确定性输出的函数设置随机种子。

然而,随机种子可能不足以确保再现性

本文将展示 **train_test_split** 函数并不总是保证可再现的分割,即使是使用随机种子集。我还将推荐另一种解决方案——散列法——用于更健壮和可重复的分割。

这并不是说你不应该使用train_test_split,只是为了强调它可能比你想象的更敏感。在某些情况下,这可能导致难以调试的不一致分割。

This article was originally published on my blog, [engineeringfordatascience.com](https://engineeringfordatascience.com/posts/ml_repeatable_splitting_using_hashing/)

train_test_split 有什么问题?

如果底层数据没有任何变化,设置随机簧片只能保证可再现的分割。

train_test_split而不是确定性的

train_test_split生成的分割对底层数据的“排序”和添加到现有数据集中的任何“新数据”都很敏感。

如果您的数据集以任何方式被打乱或修改,数据将被完全不同地分割。不能保证单个数据点会总是 *** 在训练集中,或者总是 *** 在测试集中。这意味着原始训练集中的数据点现在可能出现在测试集中,反之亦然,如果数据被打乱的话。

因此,对于同一个数据集,根据数据集中各行的排序方式,可以得到完全不同的拆分。那挺让人担心的。

即使删除了一个数据点,交换了两行的顺序,或者添加了单个数据点,您也会得到完全不同的训练和测试分割。

这种对数据的“超敏感性”可能会令人惊讶——起初对我来说的确如此——并导致意想不到的模型训练结果。

让我们用一个简单的演示来说明这个问题。

💻这篇文章的所有代码都在 GitHub 上的 随机笔记本 中提供🚀

我们将首先从sklearn.datasets下载一个示例数据集,并创建一个“索引”列来唯一标识每一行。

**from** **sklearn.datasets** **import** load_breast_cancer**import** **pandas** **as** **pd***# download an example dataset*
data = load_breast_cancer()
df = pd.DataFrame(data["data"], columns=data["feature_names"])*# create an 'index' column to use to uniquely identify each row*
df = df.reset_index(drop=False)df.head()

现在我们用 Sklearn 的train_test_split拆分数据,设置随机状态(种子)。

**from** **sklearn.model_selection** **import** train_test_splitTEST_RATIO = 0.1
SEED = 42*# split into training and test using a random seed*
x_train_skl, x_test_skl = train_test_split(df, test_size=TEST_RATIO, random_state=SEED)

接下来,我们打乱原始数据帧,并再次分割数据。为了保持一致性,我们仍将使用与之前相同的随机种子。

请注意,没有添加新数据,我们只是对行进行了重新排序。

*# shuffle the orginal dataframe*
df_shuffled = df.sample(frac=1)*# split the shuffled dataframe using the same random seed*
x_train_skl_shuffled, x_test_skl_shuffled = train_test_split(
    df_shuffled, test_size=TEST_RATIO, random_state=SEED
)

理想情况下,x_test_sklx_test_skl_shuffled测试集中包含的行应该是相同的,因为我们使用了相同的随机种子。

然而,当我们比较每个测试集中包含的行 id 时,我们注意到它们是不同的!即使两次的随机状态(种子)是相同的。数据没有任何变化,只是被打乱了。

*# compare the row ids included in the original test set vs shuffled test set. Should return True if identical rows are included in each test set*set(x_test_skl["index"]) == set(x_test_skl_shuffled["index"])False

这凸显了train_test_split函数是多么敏感,甚至对数据的重新排序也是如此。

更重要的是,如果底层数据发生了变化,那么重现原始数据分割和调试模型性能将会非常困难。

依赖随机种子会有什么后果?

这很危险

随机种子仅在数据集未发生任何变化时保证再现性。

您能 100%确定数据集在两次训练之间没有改变吗?如果同事删除了异常数据点或添加了新行。您的数据分割将与原始分割完全不同,无法轻松复制旧的数据分割。

你可以使用数据版本控制工具,比如 dvc 来帮助跟踪变化,然而,这并不能阻止你的数据分割发生变化。最好能防止代码中的分裂变化。

难以有效比较车型

在比较模型时,我们希望能够控制尽可能多的变量。这应该包括哪些数据点用于训练和测试。

如果您的数据拆分在不同的运行之间有很大的不同,您可能会观察到性能上的显著差异。例如,如果您有几个“异常值”数据点,它们在最初的训练运行的训练集中,但现在在您的测试集中,您的模型性能可能会“下降”,因为它不能像以前一样预测测试集中的异常值。

难以调试

如果您不能有效地比较模型,那么就很难调试性能问题。

假设您向数据集添加了一些新的数据点并重新训练了模型,但是模型的性能下降了。

如果您对随机种子集使用了train_test_split,那么当底层数据发生变化时,您将得到完全不同的数据分割。很难理解模型性能的下降是由于新数据的质量,还是如前一点所强调的,仅仅是因为数据被不同地分割。

什么时候 train_test_split 可能不合适?

…如果您将来需要根据原始数据和新数据重新训练您的模型

如前所述,对现有数据的任何底层更改,无论是重新排序还是添加一个额外的数据点,都会导致完全不同的数据拆分。您的原始数据分割将不可复制。

如果你用全新的数据集重新训练模型,这不成问题,因为显然所有的训练和测试数据点都会不同。

但是,如果您再次使用包含原始数据点的数据集进行训练,理想情况下,您应该能够在新的训练运行期间复制它们的原始数据分割。即使有随机种子集,train_test_split也不能保证这一点。

…如果您从一个不断发展的数据源中采样或检索源数据

在理想情况下,您应该完全控制源数据集,但是,有时情况并非如此。

例如,如果您使用存储在 BigQuery 中的表作为许多团队使用的源。您不能保证查询返回的行的顺序,同时新行可能被追加到表中。

另一个例子是,如果您正在处理存储在文件系统中的图像数据。如果新图像被添加到您的源文件夹,您不能保证文件路径的顺序,尤其是在添加新图像的情况下。

…如果您有不适合内存的大型数据集

如果您需要将数据分布在多台机器上,以便并行处理数据,那么使用非确定性方法分割训练和测试数据可能会有问题,并且难以确保可重复性。

…如果您的实验或生产代码将用另一种语言重写

由于train_test_split是不确定的,数据分割不容易跨语言重现。

例如,您可能希望将自定义 Python 模型的性能与使用 BigQuery 的 BQML(使用 SQL 定义)创建的模型进行比较。从train_test_split Sklearn 中分离出来的内容不容易直接转化成 SQL。

团队使用 Python 构建模型原型,然后使用另一种语言(如 Java)编写生产系统,这种情况也很常见。为了帮助将原型模型翻译成另一种语言的过程,理想情况下,我们应该能够以相同的方式在两种语言中分割数据,以确保可再现性,并帮助调试从原始模型到新模型的任何差异。

解决方案:散列法

哈希是什么?

“哈希函数是可用于将任意大小的数据映射到固定大小的值的任何函数” 维基百科

有许多不同的散列算法,但本质上它们允许您可重复地将输入转换成任意值。

哈希函数的输出是确定性的——对于相同的输入,它总是相同的。

它是如何可重复地分割数据的?

在数据分割的上下文中,我们可以使用哈希来可靠地将分割分配给各个数据点。由于这是一个确定性的过程,我们可以确保数据点总是被分配到相同的分割,这有助于再现性。

该过程工作如下:

  • 使用数据点的唯一标识符(例如,ID 或通过连接多个列),并使用哈希算法将其转换为任意整数。每个唯一的数据点将具有来自散列函数的唯一输出。
  • 使用模运算将数据任意分成“桶”
  • 选择存储桶子集中的所有数据点作为训练集,其余的数据点作为测试集

伪码(90:10 数据分割)

row_id = "0001"# convert id into a hashed integer value
hash_value = hash(row_id)# assign a bucket between 0 and 9
bucket = hash_value % 10# add id to train set if less than 9 (i.e. approx 90% of the data)
if bucket < 9:
   train_set.append(row_id)
else:
   test_set.append(row_id)

使用哈希的原因

确定性

train_test_split不同,散列对数据中的潜在变化是健壮的。

使用这种方法,一个单独的数据点将总是被分配到同一个存储桶。如果数据被重新排序或添加了新数据,分配的存储桶将不会改变。这是更可取的,因为数据点的训练/测试分割分配现在独立于数据集的其余部分。

提高开发效率,减少人为错误

当与同事并行处理模型时,非常容易不小心忘记使用随机种子,甚至使用不同的随机种子。这使您面临人为错误的风险。

使用相同的散列算法消除了用随机种子在代码中显式控制可再现性的需要。只要您同意您的团队使用哪种散列算法,您将总是重新创建相同的分割。没有人为错误的风险。

原始数据和预处理数据的一致分割

在实验过程中,您可能会研究不同的预处理步骤,并将中间数据和预处理数据保存在一个新文件中。然后,您可以在另一个阶段加载这些中间数据,以继续您的分析。

由于预处理数据不同于原始数据,当从新文件加载时,使用train_test_split和随机种子将给原始数据和预处理数据不同的分割。

只要用于计算哈希值的列没有改变,哈希将为原始数据和预处理数据提供相同的拆分。

存储和内存高效

还有其他策略来对抗再现性(本文稍后讨论),例如将数据显式保存在“训练”文件和“测试”文件中,或者在数据中添加新列来指示数据点属于哪个训练/测试分割。

但是,有时您无法将数据保存到新文件并添加列,例如,如果您没有权限复制或编辑原始数据源,或者数据太大。

哈希是确定性的,需要时可以在内存中“动态”计算数据点分割,而无需显式更改底层数据或保存到新文件中。

Farmhash 算法

有许多不同的哈希算法,用于多种用例,如校验和和加密。

为了创建可再现的训练/测试分割,我们需要使用“指纹”散列函数。指纹散列函数是轻量级的、高效的和确定性的——它们将总是为相同的输入返回相同的值。

加密哈希函数,如 MD5 和 SHA1,不适合这种使用情况,因为它们不具有确定性,而且它们的计算开销也很大。

Farmhash 由 Google 开发,推荐给这个用例。它有一个简单的 Python 库实现,可以跨许多其他语言使用,包括 BigQuery SQL

farm hash 的另一种替代方法是使用 zlib 和 crc32 校验和算法。本笔记本中显示了一个实施示例,来自 使用 Scikit-Learn、Keras 和 TensorFlow 进行机器实践学习

下面是一个 farmhash 的演示,以及我们如何使用它来为我们的数据点分配存储桶。

演示

PyPI 上的 Python 包

*# install Python library*
*# ! pip install pyfarmhash*

我们可以使用数据点的唯一标识符(即 ID 或列值的连接)来散列数据点。

让我们从使用 farmhash 的fingerprint64函数将单个 ID 转换成散列整数值开始。

**import** **farmhash**example_id = "0001"
hashed_value = farmhash.fingerprint64(example_id)
**print**(hashed_value)6241004678967340495

我们现在可以使用任意函数将这个数据点分配给一个“桶”。

一个有用的方法是使用模函数。散列算法的整数输出是随机分布的,因此,例如,使用除数为 10 的模函数会将数据分成 10 个随机桶(从 0 到 9)。

*# assign a bucket using the modulo operation*
bucket = hashed_value % 10
**print**(bucket)5

因此,我们的数据点 ID“0001”将被分配给存储桶 5。当我们使用除数 10 时,我们将有 10 个不同的桶。因此,例如,我们可以将所有存储桶为“1”的数据点分配给测试集,以使用 10%的数据进行测试。

使用 Farmhash 分割数据集

现在,让我们将这种分割策略应用到我们的数据集。

下面的hash_train_test_split函数可用于使用指定的散列函数将数据帧分割成训练集和测试集。在此示例中,该函数创建一个新列来存储存储桶分配。这只是出于演示目的,没有必要实际将存储桶值存储在您的数据中,因为存储桶可以从行 ID“动态”地重复计算。

和以前一样,我们将使用原始乳腺癌数据集创建训练/测试分割,但使用带有 Farmhash 的哈希方法,而不是带有随机种子的 sklearn 的train_test_split

然后,我们将打乱数据,再次分割数据,并比较测试集 id,以确保分割是相同的。

*# create a training and test set from original dataset using hashing method*
x_train_hash, x_test_hash = hash_train_test_split(
    df,
    split_col="index",
    approx_test_ratio=TEST_RATIO,
) *# create a training and test set from shuffled dataset using hashing method*
x_train_hash_shuffled, x_test_hash_shuffled = hash_train_test_split(
    df_shuffled,
    split_col="index",
    approx_test_ratio=TEST_RATIO,
)*# show which bucket each row has been assigned for demo purposes*
x_train_hash[["index", "bucket"]].head()

*# compare the row ids included in each test set*
set(x_test_hash["index"]) == set(x_test_hash_shuffled["index"])True

问题解决了!即使底层数据帧被打乱,相同的行 id 仍然出现在测试数据集中。

考虑

哈希方法比常见的 Sklearn train_test_split要复杂一点(虽然不多)。因此,在实现这种方法时,有一些额外的重要事情需要考虑和了解。

散列法不会根据您指定的训练/测试比率准确分割您的数据

哈希算法的输出整数是一致的,但仍然是随机的。由于统计上的偶然,您可能会有稍微多一点的输出被分配到一个特定的存储桶,这意味着您可能不会得到恰好 10%的数据被分配到测试集。可能会多一点或少一点。这就是为什么我将hash_train_test_split函数中的参数命名为‘approx _ test _ ratio ’,因为结果将仅是该比率的近似值。

在我们上面的例子中,我们指定比率为 0.1,并期望测试集大小为 56。然而,我们实际上最终在测试集中只有 46 条记录(8%)。

作者图片

数据集越大,存储桶分配就越均匀,分割就越接近您想要的比例(大数定律)。

一般来说,这应该不是问题。使用 90:10 的训练/测试分割是任意的。实际上,你的分成比目标比例多一点或少一点都没关系。

应该选择多少个水桶?

也就是说,您应该使用哪个数字作为模数运算的除数。

这实际上取决于期望的测试分割的粒度。如果您想要 90:10 的分割,您可以使用“10”作为除数将您的数据分割成 10 个桶。然后选择其中一个桶作为您的测试集,它大约是您的数据点的 10%。

如果您想要 15%的数据用于测试,您可以使用100作为除数将数据分成 100 个桶。您可以从您的数据中随机选择 15 个桶,以获取 15%的数据进行测试。

平台交叉兼容性怪癖

尽管如此,哈希方法在不同平台上是一致的。那在某种程度上是真实的。

这里就不赘述了。在我的博客上的原文中可以找到关于在 BigQuery 和 Python 中使用散列法分割数据的“陷阱”的更详细的讨论:

TL;dr:只是要注意不同的语言是如何处理对负数应用模数函数的…

哈希的替代方法

为了完整起见,这里有另外两个常见的更明确的方法来确保一致的训练/测试分割。

在数据中创建附加列

您可以使用train_test_split(或另一种随机分割方法)来初始定义分割。然后在数据集中创建一个额外的列,以明确记录该数据点是应该包含在定型还是测试中(或者指定 K 折叠验证的折叠)。

这里是 Abhishek Thakur 实现的示例,他用它来定义交叉验证的“折叠”。

这将确保您的分割在训练跑之间被“记住”,因为它们是明确定义的。

从积极的方面来看,哪些数据点属于每个拆分是非常透明的。但是,缺点是它会增加数据集的总大小,这对于非常大的数据集来说可能是不可持续的。此外,如果您没有对数据集(例如共享数据库表)的完全控制权,您可能无法向原始模式添加列。

将培训和测试数据保存到不同的文件中

另一种常见的方法是在第一次拆分数据后,将您的训练数据和测试数据存储到单独的文件中。例如,转换成名为train.csvtest.csv的文件。如果数据对于单个文件来说太大,你也可以将多个文件分别保存到名为traintest的文件夹中。

这可能是一种有效的方法。但是,有时将所有数据复制一份并保存到单个文件中并不可行。例如,如果数据集非常大,或者您没有权限从原始源进行复制。

哈希方法可以在运行中和内存中可重复地计算确定性数据拆分,从而无需将数据复制到单个文件中。

结论

Sklearn 的train_test_split对于入门教程和小型静态数据集来说效果很好,然而,在现实世界中事情会变得更加复杂。

再现性是可持续部署 ML 模型的关键。Sklearn train_test_split对底层数据的变化惊人地敏感,可能并不合适,尤其是当您向现有数据中添加新数据时。

散列法将单个数据点的训练/测试任务与数据集的其余部分分离开来。这产生了一种更健壮的分割数据的方法。

散列是一个很好的解决方案,但是,它可能会矫枉过正。如果你正在一个小的静态数据集上完成一次性训练,Sklearn 的train_test_split和一个随机种子就足够了。然而,哈希是数据科学家工具箱中的一个重要补充,可以提高重现性并防止模型性能发生意外变化。

编码快乐!

This article was originally published on my blog, [engineeringfordatascience.com](https://engineeringfordatascience.com/posts/ml_repeatable_splitting_using_hashing/)Code for the article can be found in [this GitHub repository](https://github.com/julian-west/e4ds-snippets/tree/master/best-practices/repeatable-splitting)

参考资料和资源

哈希

Python 中的 requirements.txt 与 setup.py

原文:https://towardsdatascience.com/requirements-vs-setuptools-python-ae3ee66e28af

在开发和分发包时,理解 Python 中 requirements.txt、setup.py 和 setup.cfg 的用途

照片由 Eugen StrUnsplash 上拍摄

介绍

管理 Python 项目中的依赖关系可能相当具有挑战性,尤其是对于语言新手来说。当开发一个新的 Python 包时,您可能还需要使用一些其他的包,这些包最终会帮助您编写更少的代码(用更少的时间),这样您就不必重新发明轮子了。此外,您的 Python 包也可能在未来的项目中用作依赖项。

在今天的文章中,我们将讨论如何正确管理 Python 项目的依赖性。更具体地说,我们将讨论**requirements.txt**文件的用途以及如何使用**setuptools**来分发您的 Python 包并让其他用户进一步开发它。因此,我们还将讨论设置文件(即**setup.cfg****setup.py**)的目的,以及如何将它们与需求文件一起使用,以使包的开发和再分发更加容易。

Python 项目的依赖关系是什么

首先,让我们从包依赖关系开始讨论;它们到底是什么,以及为什么为了让 Python 项目更容易维护而正确管理它们很重要。

简单来说,依赖项是您自己的项目所依赖的外部 Python 包,以便完成工作。在 Python 环境中,这些依赖关系通常可以在 Python 包索引(PyPI)或其他存储库管理工具中找到,比如 Nexus。

作为一个例子,让我们考虑一个使用熊猫数据帧的 Python 项目。在这种情况下,这个项目依赖于pandas包,因为没有预装 pandas 它就不能正常工作。

每个依赖项——它本身又是一个 Python 包——也可能有其他依赖项。因此,依赖性管理有时会变得非常棘手或具有挑战性,需要正确处理,以避免在安装甚至增强软件包时出现问题。

现在,我们自己的 Python 项目可能依赖于第三方包的特定版本。因为这是一种情况,我们也可能以依赖冲突结束,其中(至少)两个依赖可能依赖于另一个包,但是每一个都需要那个外部包的特定版本。这些情况都是可以处理的(嗯,不一定!)通过pip等包管理工具。不过,通常我们应该指导pip它需要如何处理依赖关系,以及我们需要什么特定的版本。

处理依赖关系和指导包管理工具在我们自己的项目中需要什么特定版本的最常见方法是通过一个需求文本文件。

requirements.txt 文件

requirements.txt是一个文件,列出了特定 Python 项目的所有依赖项。如前所述,它还可能包含依赖关系的依赖关系。列出的条目可以是固定的,也可以是非固定的。如果使用 pin,那么您可以指定一个特定的包版本(使用==)、一个上限或下限或者两者都指定。

例题 **requirements.txt** 文件

matplotlib>=2.2
numpy>=1.15.0, <1.21.0
pandas
pytest==4.0.1

最后,您可以使用以下命令通过pip安装这些依赖项(通常在虚拟环境中):

pip install -r requirements.txt

在上面的示例需求文件中,我们使用不同的引脚指定了一些依赖关系。例如,对于没有关联 pin 的pandas包,pip通常会安装最新版本,除非其他依赖项之一与此有任何冲突(在这种情况下,pip通常会安装满足其余依赖项指定条件的最新pandas版本)。现在,对于pytest,包管理器将安装特定版本(即4.0.1),而对于 matplotlib,将安装最新版本,该版本至少大于或等于2.2(同样,这取决于另一个依赖项是否指定了其他版本)。最后,对于numpy包,pip将尝试安装版本1.15.0(含)和1.21.0(不含)之间的最新版本。

一旦安装了所有的依赖项,就可以通过运行pip freeze来查看虚拟环境中安装的每个依赖项的精确版本。该命令将列出所有封装及其特定引脚(即==)。

需求文件非常有用,但是在大多数情况下,它必须用于开发目的。如果您计划分发您的包以便广泛使用(比如在 PyPI 上),您可能需要比这个文件更多的东西。

Python 中的 setuptools

[setuptools](https://setuptools.pypa.io/en/latest/)是建立在distutils之上的一个包,允许开发者开发和分发 Python 包。它还提供了使依赖性管理更容易的功能。

当你想发布一个包时,你通常需要一些元数据,包括包名、版本、依赖项、入口点等等。而setuptools恰恰提供了这样的功能。

项目元数据和选项在一个setup.py文件中定义,如下所示。

from setuptools import setup setup(     
    name='mypackage',
    author='Giorgos Myrianthous',     
    version='0.1',     
    install_requires=[         
        'pandas',         
        'numpy',
        'matplotlib',
    ],
    # ... more options/metadata
)

事实上,考虑到文件是纯声明性的,这被认为是一个有点糟糕的设计。因此,更好的方法是在名为setup.cfg的文件中定义这些选项和元数据,然后在setup.py文件中简单地调用setup()。一个示例setup.cfg文件如下所示:

[metadata]
name = mypackage
author = Giorgos Myrianthous
version = 0.1[options]
install_requires =
    pandas
    numpy
    matplotlib

最后,您可以拥有一个最小的setup.py文件:

from setuptools import setup if __name__ == "__main__":
    setup()

请注意,install_requires参数可以接受一个依赖项列表以及它们的说明符(包括操作符<><=>===!=),后跟一个版本标识符。因此,当安装项目时,环境中尚未满足的每个依赖项都将位于 PyPI 上(默认情况下),并被下载和安装。

关于setup.pysetup.cfg文件之间的区别,你可以阅读这篇文章

我们需要 requirements.txt 和 setup.py/setup.cfg 文件吗?

良好的..看情况!首先,我想澄清的是,requirements.txtsetup.py之间的矛盾并不完全对应于苹果与苹果之间的比较,因为它们通常被用于实现不同的事情。

这是一个关于这个主题的常见误解,因为人们通常认为他们只是在这些文件中复制信息,他们应该使用其中的一个。然而实际情况并非如此。

首先,让我们了解一下,通常情况下,您是拥有这两个文件,还是只有其中一个文件。作为一般经验法则:

  • 如果你的包主要是用于开发目的,但是你不打算重新发布它,**requirements.txt** 应该足够了(即使当包在多台机器上开发时)。
  • 如果你的包只由你自己开发(即在一台机器上)但你打算重新发布它,那么**setup.py** / **setup.cfg** 应该足够了
  • 如果您的包是在多台机器上开发的,并且您还需要重新发布它,那么需要 **requirements.txt** **setup.py** / **setup.cfg** 文件

现在,如果你需要使用这两种符号(老实说,几乎总是如此),那么你需要确保你不会重复自己。

如果您同时使用两者,您的**setup.py** (和/或 **setup.cfg** )文件应该包括抽象依赖项列表,而 **requirements.txt** 文件必须包含具体的依赖项,并带有每个包版本的特定管脚(使用==管脚)。

鉴于install_requires(即setup.py)定义了单个项目的依赖关系,需求文件通常用于定义完整 Python 环境的需求。

鉴于install_requires需求是最小的,需求文件通常包含一个详尽的固定版本列表,目的是实现一个完整环境的可重复安装

最后的想法

在今天的教程中,我们讨论了在开发 Python 项目和应用程序时,正确的依赖性管理的重要性。我们讨论了requirements.txt文件的用途,以及如何与setuptools(即setup.pysetup.cfg)的安装文件一起使用,以确保其他开发人员可以安装、运行、开发甚至测试 Python 包的源代码。

正如已经强调的,setuptools并不能完全取代requirements.txt文件。在大多数情况下,您将需要这两个文件都存在,以便正确地管理您的包依赖关系,以便以后可以重新分发。

如果您现在想知道如何分发您的 Python 包并使其在 PyPI 上可用,以便它也可以通过pip包管理器安装,请务必阅读下面的文章。

阅读介质上的每一个故事。你的会员费直接支持我和你看的其他作家。你也可以在媒体上看到所有的故事。

https://gmyrianthous.medium.com/membership

相关文章你可能也喜欢

</8-must-know-venv-commands-for-data-scientists-and-engineers-dd81fbac0b38> https://betterprogramming.pub/11-python-one-liners-for-everyday-programming-f346a0a73f39

涉及人体研究的研究方法

原文:https://towardsdatascience.com/research-methods-involving-human-studies-67fedb56df42

研究假设、假设检验和显著性检验

来自像素的图像

我目前正在从事两个研究项目,这两个项目都需要我进行人体研究,以验证我的研究假设。我在想:为什么不利用这个机会,把涉及人体研究的研究方法中的关键点记下来,供以后参考呢?这也是这篇文章存在的原因。

研究假设

在进行实验研究时,第一步通常是制定一个研究假设,这是一个可以根据经验进行评估的具体问题陈述。通常,一个实验会包括至少一个零假设 (H0,0 应该被下标)和一个替代假设 (HA,A 应该被下标)。典型地,H0 指出实验处理之间不存在差异。“哈”总是一个与 H0 相矛盾的命题。实验的目的是收集统计数据来反驳或否定 H0,以支持 HA (Rosenthal 和 Rosnow,2008)。通常,一个好的假设符合以下标准:

  • 专注于一个可以在一次实验中解决的可测试主题;
  • 是用简洁、直白的语言写的;
  • 明确描述对照组或实验条件。

变量

一个定义明确的假设必须指定因变量 (DVs)和自变量 (IVs)。为了容易区分这两个概念,静脉指的是研究人员可以控制的治疗或条件,静脉指的是研究人员需要监测的结果(Oehlert,2010)。通常评估的五种类型的 dv 包括效率、准确性、主观满意度、易学性和保持率,以及身体或认知需求。例如,给定一个 H0:

使用 FaceID 和指纹时,登录所需的时间没有区别。

示例中的 IV 是身份验证方法(FaceID 或指纹),DV 是登录所花费的时间,是对效率的衡量。

试验设计

接下来,我们需要根据我们建立的研究假设来设计实验。这使我们能够描绘出实验总体范围的大致图景,并对实验的预算和时间表做出可靠的估计。实验的结构可以通过回答两个问题来定义:

  • 在整个实验中,我们要检查多少个静脉?
  • 每个 IV 包含多少个不同的值(条件)?

对于第一个问题,如果实验只有一个 IV,简单的单水平设计就足够了。如果有两个或更多的 iv,则需要析因设计。

组间和组内

根据第二个问题的答案,我们选择采用组间或组内设计。在组间设计中,每个参与者只暴露在一个实验条件下,但是在组内设计中,每个人在整个实验期间都被要求暴露在所有的实验条件下。下面是上面给出的记录示例的组间和组内设计的图示。

组间(左)和组内(右)(图片由作者提供)

组间设计和组内设计都各有利弊,而这两种设计的利弊恰恰是截然相反的。组间或组内设计的选择将导致不同类型的显著性检验。

组间和组内设计的利弊(图片由作者提供)

那么什么时候选择哪个呢? 一般来说,研究者更倾向于组内设计,除非存在以下条件:实验是考察个体差异很小的简单任务;会受到学习效果显著影响的任务;或者无法使用组内设计进行调查的问题(例如,条件相互排斥,如来自加拿大和中国的用户)。

组内设计更受研究者的青睐,这种设计类型的局限性可以在某些方面得到缓解。使用 拉丁方设计 可以减轻学习效果和疲劳的负面影响。此外,可以通过为用户提供适当的训练时间来熟悉任务,从而降低学习效果,因为人类的典型学习曲线是,我们在第一阶段的学习中取得快速进步,随后随着持续练习而逐渐恶化。此外,为了解决由多个实验任务引起的疲劳问题,通常建议单次会议应该持续 60 至 90 分钟或更少。

此外,为了发现可能的偏差,在进行真实数据收集之前进行多次试点测试是至关重要的。

统计分析

几乎所有的实验研究都是用显著性检验来检验和报告的,显著性检验使我们能够评估在样本人群中获得的结果的可推广性的置信度。然而,在决定使用哪种测试之前,有必要了解第一类错误和第二类错误之间的区别。

第一类和第二类错误

第一类错误有时被称为⍺错误或“假阳性”。这是指拒绝 H0 的错误,而这是事实,不应该被拒绝。II 型错误通常被称为β错误或“假阴性”。它指的是错误的没有拒绝 H0,当它是明显错误的,应该被拒绝(罗森塔尔和罗斯诺,2008)。例如,在 COVID 测试的情况下,我们有一个假设:“这个人是 COVID 阳性的”。

那么类型 I 错误将是:“该人被测试为 COVID 阳性,而她/他实际上是阴性的”。第二类错误将是:“该人被测试为 COVID 阴性,而她/他实际上是阳性的”。

一般认为 I 型错误比 II 型错误危害更大。统计学家将第一类错误称为“轻信错误”,而第二类错误称为“盲目错误”。第一类错误可能导致比现有状态更糟的情况,而第二类错误可能导致失去改善当前状态的机会。

控制一类和二类错误的风险

在计划实验和分析数据时,我们必须考虑犯 I 型和 II 型错误的可能性。在统计学中,犯第一类错误的概率称为α(或显著性水平,P 值),犯第二类错误的概率称为β。测试的统计功效,定义为 1-β,是当 H0 为假且应被拒绝时成功拒绝的概率(Cohen,2013)。

典型地,非常低的 P 值 ( < 0.05)用于最小化 I 类错误的发生。如果显著性检验给出的值大于 P < 0.05 处的 t 值,则表明犯 I 型错误的概率小于或等于 0.05。换句话说,错误拒绝 H0 的概率小于 0.05。

为了控制第二类错误的发生,通常推荐使用一个大样本量,即使当效应量很小时,它也允许我们检测两个条件之间的差异。

显著性测试

合并多个条件或组的用户研究的最终目标是确定条件或组之间是否有任何差异。由于数据的差异,我们不能简单地比较不同条件下的平均值,并宣布存在差异,因为平均值不同。相反,我们必须应用统计显著性检验来确定哪些方差可以用 IVs 来解释,哪些不能。显著性检验将表明观察到的差异随机发生的概率。如果偶然出现差异的概率很小(小于 0.05),我们可以肯定地说,观察到的差异是由于受控静脉的差异。

比较平均值的常用显著性检验(图片由作者提供)

上图展示了一些常用的比较均值的显著性检验。t 检验是 T2 方差分析的简化版本(有时被称为“f 检验”),只涉及两组或两组条件。这些测试可以很容易地在软件中完成,如 SPSSExcel 。让我们看看如何解释测试结果,我们将以 t 检验为例。

t 测试在软件中返回值,t 值越大,表明 H0 为假的可能性越大。换句话说,t 值越大,这两个均值偏离的可能性就越大。请记住,t 值必须与自由度和显著性水平一起报告。这可以帮助读者确定数据分析是否正确进行,并准确解释研究结果。

下面是一个报告 t 检验结果的示例:

独立样本 t 检验表明,使用 FaceID 的组和使用指纹的组在登录时间上存在显著差异(t(15)=2.178,p<0.05).

The t value in the above example is 2.178, which is greater than the t value for the particular degree of freedom (df=15) at the 95% confidence level (t=2.131, which can be found in a t-表)。这就是为什么它被报道为有显著差异。

双尾和单尾

上面讨论的实验是双尾的,意味着差异的方向没有指定,使用 FaceID 可能会提高登录速度,降低登录速度,或者对登录速度没有影响。然而,在某些实证研究中,假设提供了关于差异方向的信息。例如,在登录时间示例中,如果假设被更改为:

使用 FaceID 的人比使用指纹的人花费更少的时间来记录。

在这种情况下,我们预计 FaceID 的使用将提高记录速度,因此应该使用单尾 t 检验。大于 90%置信区间的 t 值表明 H0 假设是错误的,两个平均值之间的差异显著。

结论

总之,人机交互研究实验的典型生命周期如下:

  1. 确定研究假设。
  2. 指定研究设计。
  3. 进行试点研究以评估研究的设计、系统和工具。
  4. 招募参与者。
  5. 运行实际的数据收集会话。
  6. 分析数据。
  7. 报告结果。

我们已经在这篇文章中讨论了很多,但还有更多要讨论的。

如果你对这个话题感兴趣,我强烈推荐你去读、冯、霍赫海塞(,2017)的《人机交互的研究方法》。它进一步讨论了调查、日记、案例研究和访谈等研究方法的常见类型;如何管理结构化可用性测试;如何分析定性数据;数据收集方法、道德问题等等。

感谢你花时间阅读我的博客。请关注最新动态,并随时发表评论或在 Linkedin 上与我联系。

参考资料:

罗森塔尔和罗斯诺(2008 年)。行为研究精要:方法与数据分析

Oehlert,G. W. (2010 年)。实验设计和分析的初级课程

科恩,J. (2013 年)。行为科学的统计功效分析。劳特利奇。

(2017 年)、冯、霍赫海塞(2017)。人机交互中的研究方法。摩根·考夫曼。

4 高效流处理的采样技术

原文:https://towardsdatascience.com/reservoir-sampling-for-efficient-stream-processing-97f47f85c11b

了解如何使用不同的随机采样技术采集数据流。

乔纳森·格林纳韦Unsplash 上拍摄的照片

物联网设备会产生持续的测量和日志数据流,难以进行及时分析。尤其是在嵌入式系统或边缘设备中,内存和 CPU 能力对于这种流的即时分析来说太有限了。即使是强大的系统也(迟早)会在观察到达数据的完整历史方面存在问题。

油藏取样的基本思想是保持一个有代表性的有限空间油藏

通过对溪流取样。分析(例如,发现异常值,进行统计,如均值、方差、统计测试等。)在储层 R 上执行,无需观察所有数据点。续集中总结了一些维护 R 的策略。

代表的含义显然取决于应用。如果您对过去一小时的平均温度感兴趣,一个简单的滑动窗口可能会很好。如果您需要更长的历史记录来比较当前系统状态,则(有偏差的)油藏取样可能更好。

我们假设数据点流 x(t) 在时间 t=1,2,…到达。

根据应用的不同, t 可能是不规则事件的逻辑时间,也可能是固定采样率下的样本索引。

1.推拉窗

这是最简单直接的方法。保持大小为 n 和跳跃/子采样因子 k ≥1 的先进先出(FIFO)队列。除此之外,步长因子 s ≥ 1 描述了在分析之前窗口移动了多少时间步长。

滑动窗口(图片由作者提供)。

优势

  • 易于实施
  • 确定性—储层可以从一开始就非常快地充满

弊端

  • 油藏 R 代表的时间历程较短;长期的概念漂移不容易被发现——异常值会产生嘈杂的分析

2.无偏油藏取样

保持一个容器 R ,使得在时间 t > n 时,容器中接受点 x(t) 的概率等于 n/t

算法[1]如下:

  • 用水流的第一个 n 个点填充储槽 R
  • 在时间 t > n 用接受概率 n/t 替换储库中随机选择的(等概率)条目 R

这导致了一个库 R(t) 使得每个点 x(1)…x(t) 包含在具有相等属性 n/tR(t) 中。

优点

  • 水库以相等的概率包含来自河流所有历史的数据点。
  • 实现非常简单;添加一个点只需要 O(1)

弊端

  • 一个观念的漂移是无法弥补的;在这种采样技术中,最老的数据点 x(1)与最新的数据点 x(t)同等重要。

3.有偏油藏取样

有偏油藏采样算法。3.1,[2]数据点 x(t)在储层中的概率是其在 R 内停留时间的减函数。因此,在 R 中找到较早历史点的概率很高。非常老的数据点将以非常低的概率出现在 R 中。

有偏差的油藏取样图解(图片由作者提供)

点 x(r)包含在 R(t)中的概率等于

所以这是一个指数遗忘。有关算法的详细信息,请参见[2]和 goreservoir 包。

github.com\andremueller\goservoir包中的例子显示了一组无偏油藏取样器的滞留时间。

油藏取样链输出(图片由作者提供)

优点:

  • 添加新数据点的 O(1)算法。
  • 缓慢移动的概念漂移可以得到补偿。
  • 可调遗忘因子可以针对感兴趣的应用进行调整。

弊端:

  • 这是一种随机技术。所以算法是非确定性的。然而,可以通过运行独立储层的集合来估计方差[3]

4.直方图

在观察数据流的同时维护直方图。至此,数据点被分类到区间/桶中

如果预先知道观察值的有用范围,一个简单的带有计数和断点的向量就可以完成这项工作。

v-最优直方图试图最小化每个直方图桶内的方差。[4]提出了一种算法,用于有效地维护来自数据流的近似 V-最优直方图。这与区间数据相关,例如温度值的时间序列;即绝对值和数值之间的距离有意义。

履行

我将方法 1 和 3 实现为 Go [5]库。参见https://github.com/andremueller/goreservoir

要使用库调用

go get github.com/andremueller/goreservoir`

有一个清晰的界面来使用和实现新的采样器。Add功能将多个样本放入容器,并返回丢弃的样本。

// Sample is a single sample data point. As Go currently has no generics the common
// pointer to all types interface{} was used.
type Sample interface{}

type Sampler interface {
    // Add adds one or multiple samples to the Sampler.
    // Returns a set of dropped samples in this step.
    Add(samples []Sample) []Sample

    // Data returns a slice of the current samples within the Sampler.
    Data() []Sample

    // Reset resets the state of the sampler
    Reset()
}

以下算法在 goreservoir 库中实现:

  • Sliding实现了一个滑动窗口<github.com/andremueller/goreservoir/pkg/window>。
  • DynamicSampler实现了 Alg。[1]之 3.1-有偏油藏取样<github.com/andremueller/goreservoir/pkg/reservoir>。
  • EnsembleSampler将相同的数据点分配给多个采样器——因此您可以进行某种引导/集合处理。
  • ChainSampler可用于将一个采样器的输出(丢弃点)堆叠到下一个采样器中。

github.com/andremueller/goreservoir/cmd/reservoir/main.go中可以找到与DynamicSampler一起使用的ChainSampler的简单示例。该示例的输出是一个数据点在油藏中停留的时间(单位为步)。

[1] J. S. Vitter,“储层随机取样”,美国计算机学会会刊。数学。Softw。,第 11 卷,第 1 号,第 37–57 页,1985 年 3 月,doi: 10.1145/3147.3165

[2] C. C. Aggarwal,“河流演变中的有偏油藏取样”,载于第 32 届超大型数据库国际会议会议记录,2006 年,第 607-618 页。

[3] B .埃夫龙和 R. J .蒂布拉尼, 自举简介 。CRC 出版社,1994 年。

[4] S. Guha、N. Koudas 和 K. Shim,“数据流和直方图”,载于第 33 届 ACM 计算理论年度研讨会的会议记录——STOC,2001 年,第 471-475 页,doi:10.1145/380752.380841

[5]“Go 编程语言。”https://go.dev/(2021 年 12 月 27 日访问)。

使用 pivot()将数据帧从长格式改为宽格式

原文:https://towardsdatascience.com/reshaping-a-dataframe-from-long-to-wide-format-using-pivot-b099930b30ae

介绍 Pandas pivot()以及如何使用它将数据帧从长格式转换为宽格式

Josh HildUnsplash 上拍摄的照片

当您处理包含具有某种序列的变量的数据集时,例如时间序列数据,通常需要进行整形。

来源于弗吉尼亚大学研究数据服务部[1]

在之前的文章中,我们已经讨论过使用 Pandas [melt()](/reshaping-a-dataframe-using-pandas-melt-83a151ce1907)将数据从宽格式重塑为长格式。在实践中,我们也经常需要完全相反的操作——将数据从长格式转换为宽格式。这就是熊猫pivot()来帮忙的地方。简而言之,熊猫pivot()[melt()](/reshaping-a-dataframe-using-pandas-melt-83a151ce1907)完全相反。

在本文中,我们将探索如何使用 Pandas pivot()方法将数据帧从长格式重塑为宽格式。本文涵盖以下主题:

  1. 最简单的pivot()
  2. 指定index
  3. 多列
  4. 多重值
  5. 任何重复的值错误
  6. pivot()pivot_table()的区别

源代码请查看笔记本。更多教程可从 Github Repo 获取。

1.简单pivot()

最简单的pivot()必须有一个columns值,该值用于指定生成新 DataFrame 的列的列。在我们的例子中,让我们使用日期列。

df.pivot(**columns='Date'**)

但是,这个输出往往包含大量的NaN。这是因为如果没有指定,pivot()中的values参数将使用剩余的列来填充新 DataFrame 的值。

最简单的 pivot()(图片由作者提供)

结果往往没有太大意义,所以一般用例至少指定了index

2.指定index

index设置为'Country'后,您应该会得到如下结果:

df.pivot(columns='Date', **index='Country'**)

熊猫透视()带列和索引(图片由作者提供)

有了index='Country',结果数据帧不再有NaN。这似乎很神奇,但是对于这个特殊的例子,剩下的列只有 Cases 列,而 Country 列包含分类数据。

您可能会注意到,结果具有分层索引列案例日期(也称为多索引列)。我们可以显式指定values参数来消除它:

df.pivot(columns='Date', index='Country', **values='Cases'**)

熊猫透视()带列和索引(图片由作者提供)

3.多列

与相反的方法 Pandas melt()类似,我们可以指定多个列,将它们作为输出中的列。例如,如果我们想保留国家纬度经度作为列,以便更好地参考:

df_2.pivot(
    columns='Date', 
    **index=['Country', 'Lat', 'Long'],** 
    values='Cases'
)

多列熊猫透视()

4.多重值

类似地,我们可以向values参数传递一个列表,以便并排查看不同的类别:

df_3.pivot(
    columns='Date', 
    index=['Country', 'Lat', 'Long'], 
    **values=['Cases', 'Recovered']**
)

具有多个值的熊猫透视()

5.任何重复的值错误

现在,让我们来看一个具有重复值的数据帧。

df = pd.DataFrame({
    "Store_Manager": ['Tom', 'Tom', 'Chris', 'Chris'],
    "Location": ['London', 'London', 'Oxford', 'Cambridge'],
    "Num_employee": [1, 2, 3, 4]
})

当执行下面的语句时,您将得到一个 ValueError 。这是因为前两行对于indexcolumns参数是相同的。

df.pivot(
    **index='Store_Manager', 
    columns='Location',** 
    values='Num_employee'
)

我们可以使用[pivot_table()](/a-practical-introduction-to-pandas-pivot-table-function-3e1002dcd4eb)来解决这个问题。但是请记住,默认情况下,[pivot_table()](/a-practical-introduction-to-pandas-pivot-table-function-3e1002dcd4eb)将执行 均值 聚合。例如,运行下面的语句,你会得到一个输出显示" Tom 在伦敦平均有 1.5 名雇员"

df.pivot_table(
 **index='Store_Manager', 
    columns='Location',** 
    values='Num_employee'
)

6.pivot()pivot_table()的区别

基本上,[pivot_table()](/a-practical-introduction-to-pandas-pivot-table-function-3e1002dcd4eb)函数是允许执行聚合的pivot()函数的推广。

pivot()的主要使用案例有:

  1. 如果您需要透视一个表并显示没有任何聚合的值。
  2. 您希望将数据帧从长格式转换为宽格式

当您正在寻找一种从数据中获取洞察力并执行聚合的方法时,[pivot_table()](/a-practical-introduction-to-pandas-pivot-table-function-3e1002dcd4eb)应该是最佳选择。如果你想了解更多关于pivot_table()的信息,请查看这篇文章:

结论

在本文中,我们已经讨论了pivot()的 5 个用例,并讨论了pivot()[pivot_table()](/a-practical-introduction-to-pandas-pivot-table-function-3e1002dcd4eb)的区别。在数据预处理和探索性数据分析过程中,这是一个非常方便且最受欢迎的方法之一。

重塑数据是数据科学中一项重要的基本技能。我希望你喜欢这篇文章,并学到一些新的有用的东西。

感谢阅读。请查看笔记本获取源代码,如果您对机器学习的实用方面感兴趣,请继续关注。更多教程可从 Github Repo 获得。

参考

使用 Pandas stack()和 unstack()重塑数据帧

原文:https://towardsdatascience.com/reshaping-a-dataframe-with-pandas-stack-and-unstack-925dc9ce1289

有效使用 Pandas stack()和 unstack()的 7 个技巧

照片由派恩瓦特Unsplash 上拍摄

当您处理包含具有某种序列的变量的数据集时,例如时间序列数据,通常需要进行整形。

来源于弗吉尼亚大学研究数据服务部[1]

Pandas 提供了各种用于重塑数据帧的内置方法。其中,stack()unstack()是重组(也称为索引)最常用的两种方法。

  • stack():从列到行堆叠规定的层次。
  • unstack():从行到列拆分规定的级别。来自堆栈的逆操作。

熊猫栈()和解散栈()(图片由作者提供)

stack()unstack()看起来使用起来相当简单,但是仍然有一些技巧你应该知道来加速你的数据分析。在本文中,您将学习熊猫应对以下用例的技巧:

  1. 个别能级
  2. 多层次:简单案例
  3. 多个级别:缺少值
  4. 多个级别:指定要堆叠的级别
  5. 多个级别:删除缺少的值
  6. 拆分:简单情况
  7. 拆分:更多级别

源代码请查看笔记本。更多教程可从 Github Repo 获取。

1.个别能级

最简单的stack()可以应用在具有单级列的数据帧上。它只是将标签从堆叠到并输出一系列。

df_single_level = pd.DataFrame(
    [['Mostly cloudy', 10], ['Sunny', 12]],
    index=['London', 'Oxford'],
    columns=['Weather', 'Wind']
)df_single_level**.stack()**

数据帧上最简单的堆栈()并输出一系列数据(图片由作者提供)

2.多层次:简单案例

通常,我们会在具有多级列的数据帧上使用stack()。让我们为演示创建一个数据帧。创建具有多级列的 DataFrame 有多种方法,最简单的方法之一是创建一个 MultiIndex 对象MultiIndex.from_tuples(),并将结果传递给pd.DataFrame()中的columns参数:

multi_col_1 = pd.**MultiIndex**.from_tuples(
    [('Wind', 'mph'), ('Wind', 'm/s')]
)df_multi_level_1 = pd.DataFrame(
    [[13, 5.5], [19, 8.5]],
    index=['London', 'Oxford'],
    **columns=multi_col_1**
)

多索引数据框

通过调用stack(),它将获取列级(mph, m/s)并将其堆叠到行轴上。

df_multi_level_1.stack()# Same as 
df_multi_level_1.stack(**level=-1**)
df_multi_level_1.stack(**-1**)

熊猫在多索引数据框架上堆叠()

在幕后,它根据参数level运行操作。参数level默认为-1,它获取最内层,并将其从列轴堆叠到行轴上。

如果您想了解更多关于 MultiIndex 的内容,可以查看这篇文章:

[## 在熊猫的多索引数据框架中访问数据

towardsdatascience.com](/accessing-data-in-a-multiindex-dataframe-in-pandas-569e8767201d)

3.多个级别:缺少值

当堆叠具有多级列的数据帧时,缺少值是很常见的。让我们创建另一个数据帧示例:

multi_col_2 = pd.MultiIndex.from_tuples(
    **[('Wind', 'mph'), ('Temperature', '°C')]**
)df_multi_level_2 = pd.DataFrame(
    [[13, 8], [19, 6]],
    index=['London', 'Oxford'],
    columns=multi_col_2
)df_multi_level_2.stack()

与第一级中具有相同值'Wind'的前一示例multi_col_1不同,multi_col_2具有 2 个不同的值'Wind''Temperature'。现在,我们得到缺失值,因为堆叠的数据帧比原始数据帧有更多的值,缺失值用NaN填充。

熊猫栈()(图片由作者提供)

4.多个级别:指定要堆叠的级别

stack()中的第一个参数是level,它控制堆叠哪一层。让我们创建一个具有两个不同级别的多指数:

multi_col_2 = pd.MultiIndex.from_tuples(
    [('Wind', 'mph'), ('Temperature', '°C')]
)# **Level 0**
multi_col_2.**get_level_values(0)** # Index(**['Wind', 'Temperature']**, dtype='object')# **Level 1**
multi_col_2.**get_level_values(1)** # Index(**['mph', '°C']**, dtype='object')

我们可以传递一个数字来指定要堆叠的级别。要指定要堆叠的多个级别,我们可以传递一个列表:

df_multi_level_2.stack(**0**)df_multi_level_2.stack(**[0, 1]**)df_multi_level_2.stack(**[1, 0]**)

熊猫栈()(图片由作者提供)

5.多个级别:删除缺少的值

默认情况下,当调用stack()时,所有值缺失的行将被删除。这种行为可以通过将dropna设置为False来控制:

df_multi_level_3 = pd.DataFrame(
    [[**None**, 10], [11, 7.0]],
    index=['London', 'Oxford'],
    columns=multi_col_2
)df_multi_level_3.stack()df_multi_level_3.stack(**dropna=False**)

用 Pandas stack()删除丢失的值(图片由作者提供)

6.拆分:简单情况

类似地,Pandas unstack()也支持参数level,它默认为-1,后者将对最内部的索引应用操作。

**index** = pd.MultiIndex.from_tuples([
  ('Oxford', 'Temperature'), 
  ('Oxford', 'Wind'),
  ('London', 'Temperature'), 
  ('London', 'Wind')
])s = pd.Series([1,2,3,4], **index=index**)

通过对具有 MultiIndex 的系列调用unstack(),它会将最内部的索引拆分到一列上。要指定要拆垛的级别,我们可以传递级别编号:

s.unstack()
# It's equivalent to
s.unstack(**level=-1**)# Unstack a specific level
s.unstack(**level=0**)

熊猫解散()(图片由作者提供)

7.拆分:更多级别

通常,我们会在更多的层次上使用unstack()。让我们来看一个包含 3 个级别的示例:

index = pd.MultiIndex.from_tuples([
  ('Oxford', 'Weather', '01-01-2022'), 
  ('Oxford', 'Temperature', '01-01-2022'), 
  ('Oxford', 'Weather', '02-01-2022'),
  ('Oxford', 'Temperature', '02-01-2022'),
  ('London', 'Weather', '01-01-2022'), 
  ('London', 'Temperature', '01-01-2022'),
  ('London', 'Weather', '02-01-2022'),
  ('London', 'Temperature', '02-01-2022'),
])s = pd.Series(
  ['Sunny', 10, 'Shower', 7, 'Shower', 5, 'Sunny', 8], 
  **index=index**
)

两级多指标

通过调用unstack(),它将最里面的索引拆分到列上。

熊猫解散堆叠()

例如,我们可以使用方法链接来运行另一个unstack()或者传递一个列表

# Method chaining
df**.unstack().unstack()**
df**.unstack().unstack().unstack()**# The equivalent
df**.unstack([2,1])**
df**.unstack([2,1,0])**

熊猫解散堆叠()示例

熊猫解散()示例(图片由作者提供)

如果您想了解更多关于方法链接的知识,可以查看这篇文章:

结论

在本文中,我们介绍了使用熊猫stack()unstack()重塑数据帧的 7 个用例。这些方法本身使用起来非常简单,是数据预处理中操作数据的最受欢迎的方法之一。

感谢阅读。请查看笔记本获取源代码,如果您对机器学习的实用方面感兴趣,请继续关注。更多教程可从 Github Repo 获得。

参考

重塑熊猫数据框架:从长到宽,反之亦然

原文:https://towardsdatascience.com/reshaping-a-pandas-dataframe-long-to-wide-and-vice-versa-517c7f0995ad

用这两个简单的方法透视/反透视熊猫数据帧

图片由 Pixabay (作者修改)

重塑pandas数据框架是数据分析领域最常见的数据争论任务之一。它也称为将表格从长格式转置或旋转/取消旋转为宽格式或从宽格式转置或旋转为长格式。那么什么是长数据格式,什么是宽数据格式,我们如何将数据帧从长到宽进行整形,反之亦然?

下面我们来看一个简单的例子。该示例显示了从 2020 年 1 月到 2022 年 4 月美国所有城市 5 种食品类别的平均食品价格。

作者图片

左边的数据帧具有长格式。“系列 ID”和“项目”列代表食品类别。“Year Month”是一个单列,包含从 2020 年 1 月到 2022 年 4 月的所有月份。“价格($)”在“年月”列中有一个对应于每个月的值。

请注意左边的数据框架是如何以长格式构建的:每个食品类别(“项目”)都有多个重复的行,每一行都代表一个特定的年/月以及对应于该年/月的平均食品价格。虽然我们只有 5 个食物类别(“项目”),但我们总共有 139 行,这使得数据帧成为一个“长”形。

相比之下,右侧的数据框具有宽格式,更像电子表格样式的格式。在这种格式中,每行代表一个独特的食物类别。我们旋转左边数据框中的“年/月”列,这样每个月都在一个单独的列中——使右边的数据框成为一个“宽”形。左表中“年/月”列的值现在变成了右表中的列名,我们得到了“平均”。每个月/年列的价格。

现在我们已经了解了什么是长数据格式和宽数据格式,让我们看看如何在 Pandas 中轻松地在这两种格式之间切换。我们将使用上面显示的样本数据集作为示例。你可以点击下载样本数据集。让我们首先将原始 CSV 文件读入 Pandas 数据帧,并对数据做一些轻微的处理:

作者图片

从长到宽重塑:

如前一节所述,该数据帧具有长格式。为了将 Pandas 中的数据帧由长变宽,我们可以使用 Pandas 的pd.pivot()方法。

**pd.pivot(df, index=, columns=, values=)**

columns:用于制作新框架列的列(如“年/月”)。

values:用于填充新帧值的列(如 Avg。价格($))。

index:用于制作新框架索引的列(如“系列 ID”和“项目”)。如果没有,则使用现有索引。

作者图片

从宽到长重塑:

现在,我们如何将宽格式数据转换回长格式呢?为了将一个数据帧由宽变长,我们可以使用 Pandas 的pd.melt()方法。

**pd.melt(df, id_vars=, value_vars=, var_name=, value_name=, ignore_index=)**

id_vars:用作标识符变量的列

value_vars:要取消透视的列。在我们的示例中,它将是年/月列的列表(“2020 年 1 月”、“2020 年 2 月”、“2020 年 3 月”等)。)

var_name:用于“变量”列的名称

value_name:用于“值”列的名称

ignore_index:如果为‘真’,则忽略原始索引。如果为“False”,则保留原始索引

作者图片

总而言之,如果你需要将熊猫的数据帧从长变宽,使用pd.pivot()。如果你需要将一个熊猫数据帧从宽变长,使用pd.melt()。感谢阅读,希望这篇简短的pandas教程对你有所帮助!

数据源:

美国劳工统计局:调查:消费者价格指数——平均价格数据(https://www.bls.gov/data/)。这是一个公共的、开放的数据集,可以使用 BLS 公共数据 API 进行检索。BLS 公共数据 API 允许公众访问所有 BLS 项目的原始经济数据。不需要许可证。

你可以通过这个推荐链接注册 Medium 会员(每月 5 美元)来获得我的作品和 Medium 的其他内容。通过这个链接注册,我将收到你的会员费的一部分,不需要你额外付费。谢谢大家!

深度学习中的剩余块

原文:https://towardsdatascience.com/residual-blocks-in-deep-learning-11d95ca12b00

ResNet 论文中首次引入的残差块解决了神经网络退化问题

图 0:随着深度神经网络的深入,其退化的真实生活模拟(图片由作者提供)

深度神经网络是主要机器学习算法的发电站。这些神经网络是堆叠层(每层有一些神经元)的集合,它们组合起来执行给定的任务。因此,随着我们将更多的层堆叠在一起,即更深或增加模型的深度,我们期望提高性能。

但是,这些深度神经网络受到退化问题的困扰。那么神经网络中的退化到底是什么?如何解决这个问题?这些是残余块破译的一些问题。

在这篇博客中,我们将学习以下一组基本概念(按顺序排列),这些概念增强了数据科学领域的创新能力。

  1. 深度神经网络简介
  2. 深度神经网络中的退化问题
  3. 剩余块(解决问题的方法)
  4. 身份功能
  5. 不同类型神经网络中的剩余块
  6. 结论

让我们开始…

但是,在开始之前,如果你希望更深入地了解卷积(残差块是在牢记卷积神经网络的基础上开发的),请阅读下面的博客:https://towardsdatascience . com/computer-vision-convolution-basics-2d 0 AE 3b 79346

深度神经网络简介

图 1:用于二元分类的深度神经网络(图片由作者提供)

上图代表了一个由层叠 CNN 组成的深度神经网络,对输入图像进行二值分类(是或否)。

所以我们把输入看作‘x’,输出看作“H(x)。CNN 操作本质上是线性的,并且激活函数(假设 relu)用于学习抽象特征(非线性)。这个网络的全部目的是找到最佳映射(拟合),即函数

F(x) -> H(x)

现在,如果我们想提高模型性能,堆叠更多的层有帮助吗?理想情况下,它应该提高性能,因为更多的神经元可用于学习抽象特征。但这并不是每次都管用。该网络面临多个问题,第一个是消失/爆炸梯度的问题。这个问题可以通过规格化初始化和中间规格化层、权重裁剪等等来有效解决。它使模型能够开始收敛于具有反向传播的随机梯度下降(或任何其他优化器)。

一旦更深的模型开始收敛(在考虑消失/爆炸梯度之后),我们可能会有另一个问题。是退化

深度神经网络中的退化问题

那么到底什么是退化呢?

随着我们增加网络的深度,精确度会饱和。也许这些层已经充分了解了我们数据的所有复杂性。但是,我们对精度不满意,我们又叠加了几层。现在,模型开始退化。这是意料之外的,但这种退化不是因为过拟合(训练误差增加)。附加层导致更高的训练误差。

图 2:训练错误(左)和测试错误(右)。Red 是更深的网络,并且具有更高的训练和测试误差。(图片由明凯提供)

退化间接暗示了并非所有的系统都是相似的或容易优化的。如果浅层网络正在学习特征,假设获得 90%的准确性,那么同一模型的深层变体甚至可能获得低于 90%的准确性。这是因为随着网络深度的增加,优化变得非常困难。

那么,如何解决这个问题呢?

残留块(消除退化的方法)

让我们考虑两个网络,网络“A”:浅层网络和网络“B”:网络 A 的更深层对等物。现在,如果不是更好,至少我们希望实现与网络 B 相同的性能。因此,给定网络 A,我们将添加身份映射作为额外的层来构造 B。这种设置应确保更深层的模型 B 不会比其更浅层的对等物产生更高的训练错误。但是实验表明,这种设置无法找到相对更好或相同性能的解决方案。

这证明了模型在优化期间学习身份映射是非常困难的。

为了解决退化的问题,我们有一个叫做跳跃连接的设置。或者称为快捷连接,它跳过架构中的一些层,并将前一层的输出提供给当前位置,从而有助于优化。

图 3:跳过连接的剩余块(图片由明凯提供)

在上图中,skip-connection 跳过两层,直接将输入“x”作为输出。这被称为快捷方式/跳过连接,因为它不涉及任何额外的参数,因为我们只是将先前的信息传递给层。

一个称为深度剩余学习的框架用于解决退化问题。

在前面的部分中,我们学习了网络学习正确的映射,即 F(x) -> H(x),其中 x 是输入,H(x)是要拟合的底层映射(预期输出),我们尝试拟合网络并获得类似 H(x)的 F(x)。

现在,如果我们在同一个设置中添加一个跳过连接(作为身份函数),我们会得到以下等式:

图 4:带有残差块的网络的更新方程(图片由作者提供)

从上图中,我们有了一个新的 F(x),即残差(预期输出和输入之间的差)。堆叠层尝试学习残差和 ta-da 的映射!这就是它被称为残余块的原因。

原来的映射 F(x) -> H(x)现在是 H(x)-x(剩余)。堆叠的非线性层试图拟合残差。

图 5:带有残余块的前向传播(图片由作者提供)

图 5 证明了跳过连接只是执行身份映射。它们的输出被添加到堆叠层的输出中,并且由于某种原因,如果 F(x)趋向于零,则由于身份映射,我们的模型将仍然具有非零权重。这消除了退化。

考虑一个神经网络,其中一些堆叠的层增加了值,而一些层仅仅为零。但是由于残留块,我们能够保持权重并不断优化和获得更好的精度。为此,将残差 F(x)推至零比通过一堆非线性层拟合身份映射更容易。跳过连接是消除更深层神经网络退化的一种幸事。

残差块的整个思想来源于这样一个假设,如果多个非线性层可以逼近复杂函数,那么等价于假设它们也可以逼近残差函数,即 H(x)-x。

现在,如果 x 和 F(x)的维数不同呢?

身份映射

快捷连接(身份映射)既不引入额外的参数,也不增加计算复杂度。

y = F(x,{Wi}) + x,其中 x 是身份映射

但是,如果身份映射和堆叠层的维度不同,那么我们就不能添加它们。为了解决这个问题,我们在身份函数中执行线性投影(使用 CNN)或额外的零条目(称为填充)来匹配维度。我们有以下等式:

y = F(x,{Wi}) + W*x,其中 W 是线性投影

图 6:跳过连接的残差块(左)和线性变换跳过连接的残差块(右)以匹配维度(图片由作者提供)

通常,残差函数具有两个或三个堆叠层,然而更多层是可能的,但是它可以减少残差块的影响。但是,如果 F(x)只有一层,它非常类似于线性层,因此:

y = W1x + x*

这种设置无助于解决退化问题。因此,我们可以假设,通常一个残差块有 2-3 个堆叠层,并有一个跳跃连接。我们可以使用堆叠的剩余块来创建更深层次的神经网络。

终于!!我们已经学习了使更深层次的神经网络能够优化和继续学习的残差块。

不同神经网络中的剩余块

所以,我们只是在一个简单的神经网络上探索了剩余块。但是,我们如何应用相同的块来解决计算机视觉任务,准确地说是卷积神经网络,或者解决使用顺序网络的任务。

残差块可以在没有任何修改的情况下与卷积神经网络一起使用。在 CNN 中,堆叠层的输出会发生变化,但方法完全相同。

对于时序网络,我们有一个称为高速公路网络的网络。高速公路网是附加大门的捷径连接。这些门是数据相关的,并且有一些参数,而剩余网络中的快捷连接没有任何参数。这些门决定是否传递信息。它作为一个调制跳跃连接来调节信息流。这些网络受 LSTM 式网络的启发,在那里我们有多个门来遗忘、更新和输出信息。

图 7:使用循环跳跃层的 LSTNet 架构,类似于具有跳跃连接的剩余块的功能(图片由郭坤提供)

或者,对于时序网络,让我们以时间序列数据为例。就像残差块一样,我们可以创建一个跳过 RNN 连接来跳过输入中的几个时间步长(通过以有序的方式跳过一些时间步长)。LSTNet 是一个最先进的模型,为时间序列数据实现了跳跃 RNN 连接。人们可以通过看一看来加深对斯基普-RNN 的理解。

结论

残差块是 ResNet 的基本单元,ResNet 是用于从图像提取特征的 SOTA 模型。它继续被用于解决深度神经网络中的退化问题。在当今世界,超过 90%的架构使用基于跳过连接的网络来开发特征嵌入。从 ResNet 到 Transformer 再到 BERT,残差块的重要性已经被证明是非常重要的,这将继续成为未来许多创新的一部分。

读完整篇博客后,你应该能够回答以下问题:

  1. 什么是神经网络中的退化?
  2. 降级和过度拟合有什么不同?
  3. 如何解决深度网络中的退化问题?
  4. 什么是跳过连接?
  5. 什么是残块?
  6. 为什么残差块被称为残差块?
  7. 什么是高速公路网?
  8. 跳跃连接和高速公路网有什么关系?
  9. 我们能在顺序网络中使用剩余块吗?
  10. 剩余块中的恒等式(函数)是什么?
  11. 叠加层的同一性和输出的维度总是一样的吗?如果没有,如何处理这种情况?

这是这篇博客回答的几个问题。

我希望这个博客解决了你追求知识的愿望。如果你喜欢这个博客,你可能会喜欢 TDS 上的一些其他读物。

  1. 计算机视觉:卷积基础知识
  2. 神经网络辍学
  3. 联邦学习基础
  4. 非 IID/真实世界数据集
  5. 基于 CIFAR10 的加权平均联合学习

再见…直到下一个博客:D

参考

[1]何,深度残差学习用于图像识别,

[2] Prem Oommem,ResNets —残差块&深度残差学习,https://towards data science . com/ResNets—残差块—深度残差学习-a231a0ee73d2

[3] Sabyasachi,ResNet 的剩余块-构建块,https://towards data science . com/Residual-blocks-Building-blocks-of-ResNet-FD 90 ca 15d 6 EC

[4]赖国坤,用深度神经网络建模长短期时间模式,

弹性人在回路管道与厚皮动物和托洛卡

原文:https://towardsdatascience.com/resilient-human-in-the-loop-pipelines-with-pachyderm-and-toloka-99e33f29df85

为 ML 管理、存储和标记数据

图片来自皮克斯拜戈登·约翰逊

为什么数据准备很难

许多数据科学家和机器学习团队报告说,他们花了大约 80%的时间来准备、管理或管理他们的数据集。

在过去的 5-10 年中,有三件事情使 ML 复兴:算法的突破、快速和可扩展的硬件以及大型精选数据集。数据是人工智能基础的重要支柱,需要付出大量努力才能获得。

不幸的是,很难确切知道特定标签会对你的模型产生什么影响,但你不能只是等待你的数据变得理想。没有一家软件公司会在他们的应用程序完美之前推迟发布——如果是那样的话,他们永远不会发布任何东西。数据也是一样。你必须从某个地方开始,这就是机器学习生命周期出现的地方。

作者图片

机器学习的生命周期就是迭代。但人们经常忽略的事实是,机器学习中实际上有两个具有共生关系的生命周期:代码和数据。

我们不断地对它们进行迭代,提供我们对问题的理解来改进我们的代码和数据。

这意味着迭代必须是我们 ML 过程的基本部分。我们越能整合正确的工具,就越能更好地利用我们人工智能团队的洞察力来解决现实世界的问题。

ML 生产管道

这里是机器学习生产管道的简化版本,从训练阶段开始,然后是验证步骤。

作者图片

通常我们会训练和验证几个模型,一旦我们发现一个表现足够好的模型,我们就把它推向生产。一旦模型投入生产,我们就监控它的性能以捕捉任何精度偏差。如果准确度下降,我们重新训练模型,这意味着我们循环回到最初的训练步骤。

通常,这个过程是一个连续的循环,在产品的生命周期中要执行多次。

回路中的人类

但这真的是管道的全部吗?简而言之,答案是否定的。上面显示的循环是机器学习项目的典型,但它缺少一个重要的组成部分:人类的参与。大多数人工智能产品都需要大量的人力,尤其是在数据标签这一步。

作者图片

当我们采用人在回路原则时,参与可以发生在许多不同的阶段:

  • 在训练阶段,我们需要人类对监督学习的数据进行注释。
  • 为了监控生产中的模型,我们理想地需要人工标记的数据来定期检查我们的预测是否有偏差。
  • 当出现偏差时,我们需要人为标注的样本进行再训练。

在整个过程中,很明显我们不能仅仅依赖自动化。我们需要在机器学习生命周期的每个阶段都包括人类。

作者图片

此外,还有另一个 ML 管道需要不断的人工注释。这是一个传统意义上的人在回路中的工作流,如上图所示。

在这里,人们用困难的情况来帮助实际的 ML 算法。许多最大似然算法给了我们与它们所做的预测相关的置信度。我们可以选择一个阈值概率来过滤算法发现困难的情况,并将它们发送给人类判断。人类预测被发送到最终用户,并且还被发送回算法,以帮助通过再训练来提高性能。

在下一节中,我们将介绍 Toloka 和 Pachyderm,看看这两个工具如何帮助您构建有弹性的人在回路管道。

托洛卡

Toloka 是一个众包数据标注平台,允许标注大量数据。在下面的照片中,您可以看到一些使用平台标记的数据类型的示例。

作者图片

这是一个非详尽的任务列表。您应该能够执行任何类型的数据标记,因为该工具允许您使用构建块或 JavaScript 设计自己的界面。

但是我们为什么需要像 Toloka 这样的标签工具呢?

所有的 AI 产品都需要不断的数据标注。你拥有的数据越多,你的算法就越好。

速度至关重要。标记数据越快,迭代就越快,从而加快模型的开发和评估。

在许多 ML 项目中,雇员被明确地雇佣来做数据标记,但是这并不总是最好的解决方案。Toloka 允许您利用群众的力量按需扩大或缩小标签流程。

作者图片

Toloka 是一个开放的工具,请求者和执行者都可以在电脑或手机上访问它。请求者是 ML 工程师、数据科学家、研究人员和其他发布需要标记的任务的人。一旦发布了这些任务,Tolokers(群众)就可以使用它们,他们可以根据时间和兴趣自由选择他们的工作量。请求者可以确信任务会很快完成,因为总会有一个人可以完成特定的请求。

Toloka 作为工程任务

人群的可用性和多样性允许 Toloka 像对待工程任务一样对待标签任务。这个过程可以通过 Python 和 Java 客户端使用开放 API 来实现自动化。这意味着数据标记可以很容易地集成到 ML 管道中,并且新数据流可以在需要时自动触发标记过程。

作者图片

事实上,我们可以使用 Toloka 作为标记集群,这使得它可以与其他 ML 工具集成,如 Pachyderm。

迟钝的人

Pachyderm 是 MLOps 数据版本和数据管道的领导者。它是数据驱动应用程序的 GitHub。

借助 Pachyderm 的数据版本管理,您可以通过 repos 和 commits 来组织和迭代数据。Pachyderm 允许您对任何类型的数据进行版本化,无论是图像、音频、视频、文本还是任何其他数据。版本化系统经过优化,可以扩展到任何类型的大型数据集,这使得它成为 Toloka 的完美组合,为您提供了内聚的可复制性。

除了版本控制,Pachyderm 的管道还允许您将代码连接到数据仓库。当提交新数据时,他们可以通过重新运行管道来自动化机器学习生命周期的许多组件(如数据准备、测试、模型训练)。厚皮管道和版本控制一起为您的机器学习工作流提供了端到端的传承。

厚皮动物和托洛卡在行动:点击诱饵数据

Toloka 和 Pachyderm 一直在合作,为您带来这两种工具集成的示例。我们已经创建了一个管道,可用于注释 clickbait 数据,如下所示。

作者图片

为了做到这一点,我们建立了厚皮动物管道来协调 Toloka 的标记流程。下图显示了我们为此项目创建的管道流。

作者图片

最初,我们有一个名为“clickbait data”的 repo,它保存可用于训练 ML 模型的数据。然后,我们有几个管道管理 Toloka,用新的例子丰富这个数据集。

然后,我们创建一个厚皮动物管道,用 Toloka 创建一个基本项目。然后,相应的管道接收带文本的 CSV 文件,向 Toloka 添加任务,并创建所谓的蜜罐,帮助我们对注释进行质量控制。我们用它来运行一个管道,该管道启动 Toloka 中的注释过程,然后是聚合管道。一旦我们有了注释结果,就会有一个数据集连接管道,将旧数据与新注释的示例合并在一起。

也许这种设置最令人兴奋的方面是,每次我们向 Pachyderm 中的 repo 添加不带注释的新数据时,都会自动触发标记过程。这意味着,如果你需要不断地注释更多的数据和重新训练机器学习模型,建议的管道是可重用的。

总之

为了建立在现实世界中工作的机器学习模型,我们需要为迭代做好准备。这意味着为数据开发和人在回路中的交互结合正确的工具。有了 Pachyderm 和 Toloka,数据监管和管理变得前所未有的强大和灵活。这是一种全新的方式来扩展您的标注任务,同时对您的数据转换进行版本控制、管理和自动化。

如果你想了解这个集成项目的细节,你可以在这个 GitHub repo 中看到代码。

有关详细信息和示例,请观看带完整管道演示的网络研讨会视频

原发表于https://to loka . ai

本文是与吉米·惠特克合著的。

PS:我正在 Medium 和aboutdatablog.com上撰写深入浅出地解释基本数据科学概念的文章。你可以订阅我的 邮件列表 每次我写新文章都会收到通知。如果你还不是中等会员,你可以在这里加入https://medium.com/@konkiewicz.m/membership

下面还有一些你可能喜欢的帖子

* *

高级软件工程师的职责

原文:https://towardsdatascience.com/responsibilities-of-a-senior-software-engineer-e30d3989610

软件工程

一个关于责任和工程师角色的故事

最近,我被指派去面试我现在工作的公司的一个高级职位,实际上我在想——怎样才能成为一名优秀的高级软件工程师?

我经历了整个交易——我做过初级、中级,然后是高级软件工程师,我看到了前述角色的各种不同的【定义】

我想到了一些事情,我认为这些事情在描述这个角色的行业中应该是常见的,所以今天我想概述一下高级软件工程师的【职责】,希望它可以帮助正在阅读的人,无论是高级工程师还是正在招聘的人。让我们开始吧!

高级软件工程师

图片来源——【afgprogrammer@unsplash.com

高级工程师是团队范围内的个人角色,不一定直接向团队领导报告。

从技术上讲,团队领导和高级软件工程师的角色没有太大的区别。两者都需要很高的工程技术,都是对团队的带动、管理和关心。唯一的区别应该是,团队领导是更多管理任务的关键人物,但接替高级职位的工程师必须有能力在任何给定的时间点履行团队领导的职责。

高级工程师的其他特征是推动计划、带来和交付价值的良好记录。该角色的目标是通过交付稳定且可扩展的工程解决方案来创造价值。

高级工程师必须具备在端到端生命周期内交付软件的知识、广度和熟练程度。

什么是端到端的生命周期?

软件(系统)开发生命周期或 SDLC 本质上是一个管理软件开发的模型,它可以被分解成多个阶段。许多人将这些称为端到端生命周期阶段。

软件生命周期阶段——作者图片

各个阶段可能因公司而异,在某些情况下甚至因团队而异。尽管如此,我们的目标始终是将项目或产品从最初的想法和概念一直带到部署。生命周期主要包括:

  • 规划和分析阶段
  • 设计和原型阶段
  • 软件开发
  • 软件测试
  • 集成和部署
  • 操作和维护阶段

高级工程师对所有这些阶段都有贡献,不仅仅是与开发相关的阶段。除了对这些阶段中的每一个阶段做出贡献之外,一名高级工程师将驱动并拥有最关键的部分。除了编写实现本身,测试它,集成它,然后维护它之外,一个人需要理解并能够承担一组业务需求,提炼它们,设计解决方案,并最终交付它。

所有权心态是一件大事,也许会带来一些的议论,但它绝对是真实的。

高级软件工程师必须在各个方面体现主人翁精神。

高级人员不只是执行任务,这可能是低级角色的职责范围。他们将需要经常扮演不同的角色,有时在业务方面工作,同时推动团队交付实现。

对于高级工程师的日常工作范围,没有一套预定义的方法,但他们需要提取优先级,定义和解决问题,确定范围内和范围外的内容,识别和预防风险,并制定解决方案,并且需要能够独立完成这些工作。高级工程师经常与产品负责人合作,必要时也与其他团队合作。

高级工程师能够处理复杂和简化涉及许多系统的问题,充当团队产品的“团队架构师】。当
适用时,他们构建可能在整个公司共享的组件。他们还对整个公司或部门范围内的计划做出贡献,而不仅仅是团队。

另一个需要的技能是处理团队过程,Scrum,提供好的实践,并作为其他团队成员的导师。

高管必须有影响力,产生影响,推动团队,为他人树立榜样,并应体现公司价值观。

如果我需要编写一份职责的粗略列表,这些职责是:

  • 开发高质量的软件设计和架构
  • 推动计划
  • 照顾好团队
  • 指导其他工程师
  • 在各个方面提供价值

显然,为了检查他人和我们自己,我们需要一些能够证明高级工程师表现的指标。这些通常包括:

  • 对公司/部门级别的影响,无论该影响与技术解决方案、工程流程还是其他相关
  • 产品性能——软件质量,例如软件的吞吐量和响应时间
  • 产品可用性
  • 代码质量
  • 将一个想法从构思到实施的质量
  • 架构可扩展性、设计和优化
  • 自动化
  • 良好的 SCRUM 实践
  • 指导其他工程师

显然,有许多度量标准、目标和属性来定义一个高级软件工程师,但是希望上面总结了它的一个好的部分。

这篇文章是关于一个高年级学生需要如何行动以及他们的目标应该是什么。

如果你是一名软件工程师,正在阅读这篇文章,我希望这篇文章能对你有所帮助,无论你已经是一名大四学生,还是你正期待成为一名大四学生。此外,我希望这篇文章能帮助和指导那些招聘人员和那些需要定义一套原则和描述以在公司或部门内使用的人。

感谢您的阅读!🎉

负责任的并行数据检索

原文:https://towardsdatascience.com/responsible-concurrent-data-retrieval-80bf7911ca06

以 PubChem 化学安全数据为例,探讨如何降低数据检索率的策略

作者在希腊克里特岛南部某地拍摄的照片

目录

设置场景
同步数据检索
异步数据检索
概念化速率节流
并发和速率节流检索使用异步
结果概述
结论

设置场景

一般来说,在外部资源从容不迫地做出响应时,通过巧妙切换上下文,可以使用单核来协调 I/O 绑定的进程。尽管如此,仍有几种方法可以使用,它们在两个主要方面有所不同:

  • 他们是使用抢占式还是协作式(也称为非抢占式)多任务处理,这与谁控制上下文切换有关
  • 如何调节数据检索以满足数据提供者的需求

在我看来,后者是最重要的。当与数据提供者交互时,我们需要确保数据检索率不超过我们所使用的服务指定的限制。数据分析师有责任采取负责任的行动,而不仅仅是从数据接收者的角度着眼于优化流程。

本文以化学品安全为主题。它使用基于 asyncio 的多任务协作,从public chem收集欧盟使用的所有工业化学品的化学安全数据。在欧盟,每年生产或进口 1 吨或以上的物质需要根据 REACH 法规的要求进行注册。注册物质的列表可以方便地从这里下载,格式良好的 excel 文件如下所示(图 1)。即使将来该列表不可用,任何物质标识符列表都可以用作输入。或者,可以从附带的资源库中获得本文撰写时使用的列表。

图 1:欧盟生产或进口的物质,标明了其数字标识符(化学文摘社编号和欧洲委员会编号)

物质清单可以用熊猫阅读,代码如下。

这里没什么特别的。如果过滤不清楚,目的是保留具有化学文摘社编号的物质,这是一个可以很容易地用于从 PubChem 检索数据的标识符。如 PubChem PUG REST 文档教程中所述,也可以使用其他标识符。

本文的用例包括两个步骤。第一步是使用 CAS 号获取相应的 PubChem 化合物 ID (CID)。例如,甲醛的化学文摘社编号为 50–00–0,可通过 POST 请求获得相应的 CID

https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/name/cids/JSON

带标题

Content-Type: application/x-www-form-urlencoded

和身体

name=50-00-0

在编写时,这给出了 CID 712。CAS 号可能对应于由实现处理的多个 CID。这一步也可以通过使用更简单的 GET 请求来完成,但是我们更喜欢 PubChem 提供的 POST 功能,因为它不会受到可以在 URL 中传递的字符的限制(例如,当从物质名称获取 CID 时)。当起点是 CAS 号时,这不太重要,但我选择了更通用的实现。

第二步是获得特定化合物的索引,即列出存在的信息,但没有全部数据内容;实质上是该记录的目录。在甲醛的情况下,这可以使用 GET 请求来获得

https://pubchem.ncbi.nlm.nih.gov/rest/pug_view/index/compound/712/JSON

该用例基本上允许分析工业化学品 PubChem 中可用的信息类型。PubChem 是一个庞大的存储库,提供全面的化学信息和流畅的编程访问,专为数据密集型研究而构建。本文仅使用所提供的部分功能来解释 Python 中的并发性,可以进一步扩展该功能以从 PubChem 获得更多数据,例如生物测定数据。尽管如此,所展示的用例足够复杂,足以代表这种集成可能带来的挑战。

同步数据检索

在考虑并发性之前,使用基于优秀的请求包的同步(阻塞)方法来测试数据检索方法是有用的。

与从 CAS 号中检索 CID 相关的第一步由函数retrieve_CID执行,而与检索特定 CID 的 PUG 视图索引相关的第二步由函数retrieve_pugview执行。

该方法使用化学文摘社编号作为起点。然而,该实现也允许使用 EC 号或物质名称作为起点,以防读者发现这很有用。由于这个原因,需要 URL 和字节编码,这里没有详细解释,但是希望从代码中可以看得很清楚。类似地,我没有提供对import logger导入和日志方法的解释,因为这对本文并不重要。关于日志的复习,请参考这个优秀的资源。通过重试失败的尝试和创建一个请求会话,可以进一步优化同步方法,但是在同步函数中没有讨论这一点,因为本文的重点是异步实现。完整的代码可以在附带的 GitHub 中看到。

回到甲醛的例子,我们可以用

它返回的 JSON 输出与我们在浏览器中使用甲醛 CID 看到的相同

图 2:甲醛的帕格观察指数(CID=712)

响应中一个特别有趣的部分是嵌套的 TOCHeading 值,当涉及到欧盟的工业化学品时,可以递归解析这些值来查看 PubChem 包含多少数据。我们将在文章的结尾回到这个问题。

这种同步方法可能是以编程方式从 PubChem 获取数据的最简单方式,可能已经对一些读者有用了。然而,本文的目的是超越同步方法,展示如何针对相当大数量的 CAS 号并发执行两个 REST 请求(但是不要大到考虑 PubChem 提供的 ftp 数据检索选项)。并发性并不是最容易涉及的话题。因此,在并行工作之前,实现同步方法总是明智的。这确保了正确使用 REST API,并设置了基线来检查并发方法的任何性能增益。根据记录,从化学文摘社编号开始检索甲醛指数大约需要 1.3 秒。

异步数据检索

概念化的速率调节

使用 API 检索数据时,最重要的一个方面是遵守数据提供者的条款和条件,这些条款和条件可能会对检索到的数据施加限制。在我们的例子中,不存在这样的复杂性,因为数据不会用于任何特定的应用程序,因为目的是演示检索数据的过程,而不是构建面向用户的应用程序或以不同的方式利用数据。除了数据使用之外,负责任地检索数据也很重要,这样我们就不会给数据提供商的服务器带来过度的压力。本节尝试如何在我们的用例中实现速率调节。

PubChem 规定了请求量限制,要求对程序性 web 请求进行节流,以便:

  • 每秒不超过 5 个请求
  • 每分钟不超过 400 个请求
  • 每分钟运行时间不超过 300 秒

第二个要求似乎是多余的,如果我们在一整分钟内每秒提交 5 个请求,那么一分钟内提交的请求数将是 300,即少于 400。哪个条件可能是限制条件取决于典型请求的响应时间,而响应时间又可能取决于所请求的数据量、服务的总负载以及与 PubChem 的负载平衡器相关的细微差别。总而言之,这意味着我们不能假设相同的请求如果在不同的时间执行将花费相同的时间。该方法应该是通用的和灵活的,以考虑到这种波动。

编排数据检索的起点是我们每秒可以提交多达五个请求。如果我们假设我们正在运行五个独立的并发流,那么所有的需求都可以得到满足,如果每个请求的响应时间被测量为:

  • 花费t1 秒的响应之后是 1- t 秒的空闲时间
  • 花费 t ≥ 1 秒的响应可以立即跟随另一个请求

下面模拟了响应时间分布不同的四种不同情况(包含utilities.py中的函数visualise_task_orchstrationin)。我们假设有五个并发流,每个并发流发出十个请求。在图 2 中,响应时间相当短,服从正态分布,平均值为 0.2 秒,标准差为 0.05 秒。我们可以看到,速率限制条件是每秒的请求数。五个并发流每秒提交 5 个请求,但是响应时间(PubChem 服务器上的负载)只有墙时间的 20%,而 80%是空闲时间。这意味着我们每分钟只使用 60 秒的运行时间,但不幸的是,我们无法在不违反请求量限制的情况下进一步延长运行时间。

图 2:从平均值为 0.2 秒、标准偏差为 0.05 秒的正态分布中得出的响应时间;响应时间用一条从请求开始处开始的水平线(空心圆圈)表示,并带有注释

图 3 模拟了稍长的响应时间,从平均值为 0.5 秒、标准偏差为 0.12 秒的正态分布中提取。同样,速率限制条件是每秒的请求数。现在运行时间大约是每分钟 5x0.5x60 = 150 秒。

图 3:从平均值为 0.5 秒、标准偏差为 0.12 秒的正态分布中得出的响应时间

图 4 模拟了更长的响应时间,平均值为 1 秒,标准偏差为 0.25 秒。我们可以看到,五个并发流每秒提交的请求总数略少于 5 个,运行时间略少于每分钟 300 秒。

图 4:从平均值为 1.0 秒、标准偏差为 0.25 秒的正态分布中得出的响应时间

最后,图 5 模拟了最长的响应时间,平均值为 1.5 秒,标准偏差为 0.38 秒。很少有请求会有空闲时间。限速条件是运行时间接近每分钟 300 秒。

图 5:从平均值为 1.5 秒、标准偏差为 0.38 秒的正态分布中得出的响应时间

在实践中,由于请求生命周期的复杂性和准确测量响应时间的挑战,我们将把空闲时间增加 0.1 秒,以留出一些误差。

上述方法可以进一步改进,特别是如果响应时间波动,例如通过遵循具有更快和更慢响应的二项式分布。在这种情况下,更快的响应后面会有更少的空闲时间,因为更慢的响应确保我们不会那么容易达到每秒五个请求的限制。然而,这种方法开发起来更复杂,如果不加监督地运行,可能会有危险,因为除非我们能够在理论上保证请求量限制不会被超过,否则我们可能会无意中给 PubChem 施加压力,最终导致 IP 被阻塞。

我们注意到 PubChem 还提供了动态节流功能。伴随所有 PUG-REST web 请求的 HTTP 响应头包含关于用户有多接近极限的信息。PubChem 可以根据总体需求动态调整这些限制,因此可以实时使用这些限制来确保并发率得到相应的动态调整。这种反馈循环机制超出了本文的范围,本文将涉及相当多的内容。在本文中,动态节流仅用于双重检查数据检索率是否保持在可接受的范围内。

在下一节中,我们将使用五个独立且并行的流来实施数据检索方法,以数千种物质的化学文摘社编号为起点来检索其 CID。这些 cid 将是唯一的,然后用于检索它们相应的 PUG 视图索引。

使用 asyncio 的并行和速率受限检索

异步方法在很大程度上基于同步方法,但在本次会议中列出了一些调整。

最关键的调整是用 aiohttp 模块替换阻塞请求模块,这是一个以异步方式发出 http 请求的库。请求本身是使用单个 aiohttp 会话发出的,以利用会话内部连接池的重用。

虽然这种情况很少发生,但实现会重新提交可能暂时失败的请求,例如,由于网络问题或 PubChem 的服务器繁忙。查看日志,这种情况只发生几十次,没有一个请求需要提交两次以上。

速率调节是通过一个异步信号量来实现的

sem = asyncio.Semaphore(limit)

limit=5。信号量管理一个内部计数器,该计数器在每次sem.aquire()调用时递减(一旦我们使用信号量进入上下文管理器),在每次sem.release()调用时递增(一旦我们使用信号量退出上下文管理器)。计数器永远不能低于零;当sem.aquire()发现它是零时,它会阻塞,直到某个任务调用sem.release()唤醒另一个等待获取信号量的任务。这实质上实现了前面概念上的五个并发且独立的流。

短请求之后是一个asyncio.sleep(idle_time)调用,其中空闲时间取决于请求持续时间,假设请求持续时间反映了 PubChem 计算时间。幸运的是,aiohttp 支持客户端跟踪,它通过将监听器协程附加到由TraceConfig实例提供的信号上来工作:

async def on_request_start(session, trace_config_ctx, params):
    trace_config_ctx.start = asyncio.get_event_loop().time()

async def on_request_end(session, trace_config_ctx, params):
    elapsed_time = asyncio.get_event_loop().time() - trace_config_ctx.start
    if trace_config_ctx.trace_request_ctx['request duration'] is not None:
        raise Exception('should not happen')
    trace_config_ctx.trace_request_ctx['request duration'] = elapsed_time

trace_config = aiohttp.TraceConfig()
trace_config.on_request_start.append(on_request_start)
trace_config.on_request_end.append(on_request_end)

该实例用作ClientSession构造函数的参数,其结果是客户端触发了TraceConfig支持的不同信号。这些信号将第一个参数ClientSession和第二个参数[SimpleNamespace](https://docs.python.org/3/library/types.html)称为trace_config_ctx,它们可用于共享属于同一请求和同一TraceConfig类的不同信号的状态。这使得能够测量从请求开始到结束所用的时间。trace_config_ctx参数在请求流开始时初始化。更复杂的是,我们需要将信息从侦听器传递回实现请求的协程,以便可以设置空闲时间。代码使用了在请求执行开始时给出的trace_request_ctx参数,它被所有 HTTP 动词接受,并将作为关键字参数传递给实例化trace_config_ctx的默认工厂。我们不使用这个功能向监听器传递数据,而是获取请求持续时间,因为一旦请求返回,键值对trace_request_ctx = {'request duration': None}的值将被设置。

诚然,这一节介绍了许多概念。在附带的中查看整个实现可能更容易。

结果概述

具有化学文摘社编号的物质数量为 17,829,相当于 17,700 个独特的化学文摘社编号。使用协程retrieve_CID我们获得了 15 361 个唯一的 CID 号码,其中 12 873 个 CAS 号码只有一个 CID 号码。4 117 化学文摘社编号在 PubChem 数据库中找不到,710 化学文摘社编号有两个或两个以上的 CID 编号。后者表明了工业化学品的物质鉴定挑战和 PubChem 中潜在的重复记录。总而言之,整个数据检索花费了 2.04 小时,即使数据检索需要定期重复,这也绝不是令人望而却步的。在处理日志时,我们可以看到两种请求类型的平均响应时间都不到 0.2 秒,响应时间的分布非常窄,如下所示。尽管如此,还是有一些例外。使用 seaborn 生成箱线图的代码也可以在补充中找到。

图 6:响应时间

检查日志,有大约 30 个警告与超过 PubChem 的请求量限制有关,响应代码为 503,消息为“请求太多或服务器太忙”,与大约 33k 的请求相比,这是相当小的。这是一个相当小的数字,最终重试后数据检索成功。限制导致这种情况的原因可能不值得研究,因为很难准确解释 PubChem 对计算时间的定义以及我们的客户端跟踪方法测量的响应时间。PubChem 允许一些回旋余地,我们显然在负责任地使用这些余地。

使用utilities.py 中的递归函数utilities/parse_TOCHeadings,我们看到 PubChem 包含 6 248 个 CID 编号的生物测定数据,即几乎一半的检索 CID 编号。此类数据可用于危险评估,同时 PubChem 还包含大量使用和暴露信息,对风险评估很有价值。包含该文件中所有 TOC 标题的完整数据可用性。

结论

还有其他方法来实现速率限制并发数据检索,例如通过将 asyncio 与一个队列相结合。也允许限制同时连接的数量,但我决定采用信号量方法,因为这是一个更通用的节流解决方案,只依赖于 asyncio 本身。数据检索不是唯一可以异步执行的部分。例如,检索到的数据可以异步存储在 PostgreSQL 数据库文件系统中。asyncio 库和框架的列表在不断地增长,现在有许多优秀的资源可用(例如,请阅读这篇优秀的文章并浏览最后的资源链接)。事实上,甚至还有 asyncio 的替代品,比如 curiotrio 。我对这些替代品的成熟程度了解不多。我承认 asyncio 发展很快,理解何时使用诸如 futures、tasks 和 coroutines 之类的东西并不容易,但同时它包含在标准库中,并且它现在有足够的发展势头。并发不是一个简单的话题,想象力是没有限制的。例如,在不同的[内核](http://use a single core and hence speed-up is achieved by cleverly switching context whilst the external resource is taking its time to respond.)中运行多个 asyncio 循环可能会同时融化您的笔记本电脑和大脑(双关语)。玩笑归玩笑,我觉得 asyncio 对于数据分析师和数据科学家通常遇到的大多数 io 绑定流程来说已经足够了。

为了完整起见,IO 绑定的进程也可以使用多线程来编排。我个人的观点是,asyncio 是比多线程更好的选择,因为切换上下文是显式的,不需要担心线程安全,这并不意味着 asyncio 没有自己的挑战。

实现是本文使用的高级 asyncio 功能,如asyncio.gather()。更明确地与循环和任务进行交互可以导致进一步的优化,从而使数据检索更加接近 PubChem 的请求量约束的限制。这是否值得追求是个人喜好的问题,因为性能的提高可能是微不足道的。此外,实现将不可避免地变得更加复杂,asyncio 不仅复杂,而且在不断发展。维护代码需要相当多的脑力来阅读和试验新的特性,这可能会超过好处,除非你在理论上倾向于并享受挑战多于你需要的结果。人们有不同的观点和世界观,如果你准备好迎接挑战,请给我留言,指出你的方法。我将非常乐意阅读更多关于它的内容!

免责声明

从欧洲化学品管理局获得的物质清单受此处和免责声明中解释的法律条款和条件的约束。PubChem 数据使用政策和免责声明可在这里找到。在使用本文中讨论的任何公开数据之前,请查阅这些页面。

工业 PLC 上的 REST APIs

原文:https://towardsdatascience.com/rest-apis-on-industrial-plcs-eb17e8c4d51b

扩展 PLC 的功能以执行几乎任何任务

与 REST API 的通信,作者的图像

介绍

API 允许两个不同应用程序之间的简单接口,在本文中,Python REST API 的基本实现将用于管理来自 PLC 的各种 SQL 命令。

在这个例子中,数据将从连接到 MP3300iec 控制器的适马-7 伺服驱动器网络中采样。这些数据将用于训练和评估一个异常检测模型,该模型将在类似配置的机器网络中使用。出于这个原因,一个好的设计应该是有一个中央服务器与所有这些机器通信,存储所有数据,计算模型的权重,并将权重实时分配回控制器。

动机

通过 REST API 与 Python 应用程序通信,任何不可能在 PLC 上运行的东西现在都可以在本地服务器上运行,并将结果返回给 PLC。这允许昂贵的计算过程与控制器一起实时运行。例如,如果在控制器上与其他进程和任务一起运行,具有几十万或几百万个参数的大型机器学习模型可能会导致看门狗错误或 cpu 负载问题。有两个直接的解决方案:

  1. 缩小机器学习模型的规模
  2. 在不同的硬件上运行计算

在某些情况下,选项 1 可以很好地工作,但是,模型的准确性可能会受到影响。有些应用可能会损失精度,但其他要求更高精度的应用可能需要选项 2。

除了将计算负载分配给另一个设备,REST API 对于执行控制器不常见的动作也很有用。一些示例可能包括:跟踪订单或库存、从第三方应用程序获取信息或与外部网络通信。

虽然可以直接从控制器与第三方应用程序进行通信,但使用给定应用程序作者提供的预建库和存储库进行通信要容易得多。随着 Python 和其他现代编程语言的流行,有许多库和工具可以为您完成繁重的工作。使用 REST API 允许您这样做,以及流程所需的任何额外的定制操作。

REST API

API 将在位于与所有控制器相关联的本地网络内的服务器上运行。Python 的 Flask 模块用于创建所有用于数据处理的端点。定义的第一个端点将用于向数据库添加数据点,如下所示:

[@app](http://twitter.com/app).route('/add_row', methods=['POST'])
def add_row():
    row_data = request.data
    if insert_sql_row(data):
        return json.dumps({'statusCode': 200, 'data': 'Success'})
    else:
        return json.dumps({'statusCode': 400, 'data': 'Error'})def insert_sql_row(row_data) -> bool:
    try:
        database = mysql.connector.connect(
            host = 'localhost',
            user = 'admin',
            password = 'password'
        )
        cursor = database.cursor() sql_statement = 'INSERT INTO table_name VALUES ('
        for value in row_data:
            sql_statement = f'{sql_statement},{value}'
        sql_statement = f'{sql_statement})' cursor.execute(sql_statement)
        database.commit()
        cursor.close()
        database.close()
        return True
    except:
        return False

上面代码中的几个关键项目:

  • /add_row 端点:这是当控制器希望向数据库添加一行数据时将使用的端点。
  • request.get_data(): 这将返回与请求相关的数据,该数据包含要输入数据库的采样值行,以及与网络请求相关的其他信息
  • mysql.connector: 这是用来连接和查询 sql 数据库的模块。

定义的下一个端点将用于从数据库中检索异常检测模型。模型权重将存储在不同的表中,这反映在下面的代码中:

[@app](http://twitter.com/app).route('/retrieve_model', methods=['GET'])
def retrieve_model():
    model_id = request.data
    return json.dumps(
                       {'statusCode': 200, 
                        'data': str(retrieve_sql_row(model_id))
                       }
                      )def retrieve_sql_row(model_id):
    try:
        database = mysql.connector.connect(
            host = 'localhost',
            user = 'admin',
            password = 'password'
        )
        cursor = database.cursor()
        cursor.execute(f'SELECT * FROM ml_model_table 
                          WHERE model_id="{model_id}"'
                       )
        row = cursor.fetchone()
        cursor.close()
        database.close()
        return row if row else []
    except:
        return []

与 API 通信

控制器通过 TCP 套接字发送的 HTTP 请求与 API 通信。对于将异常检测模型权重返回给客户端的基本 GET 命令,可以使用以下字符串:

GET /retrieve_model HTTP/1.1

字符串的第一部分 ('GET') 表示被请求的 REST 方法的类型,字符串的第二部分 ('/retrieve_model') 表示被请求的端点,字符串的最后一部分 ('HTTP/1.1') 指定 HTTP 协议版本。

当使用上面创建的数据存储端点时,上面的命令将被修改为:

POST /add_row HTTP/1.1

在 PLC 编程方面,已经创建了一个自定义功能块,用于处理发出这些命令所需的所有预处理和后处理。如下所示:

用于发出 REST 请求的功能块,由作者生成图像

“REST_API_1”功能块有以下输入:

  • 执行:发送命令并等待响应的布尔值
  • 端口:这是请求被发送到的服务器的端口号
  • IPAddress: 这是请求被发送到的服务器的 IP 地址
  • LocalIP: 这是发出请求的设备所分配的 IP 地址。
  • 命令类型:这是被请求的 REST 命令的类型。
  • 数据:这是一个包含所有将被附加到请求的数据的结构

存在单一输出“ReceivedString”。这是服务器响应的字符串。在本例中,它将是一个 JSON 结构化响应,格式如下:

{'statusCode': 200, 'data': 'Success'}

例子

该示例从运行将托管 REST API 的 Flask 服务器开始。上面“REST API”一节中的代码用于本例,日志显示服务器正在运行:

Flask 服务器初始化的输出日志,图片作者

在 PLC 程序中,在“REST_API_1”功能块上启用“执行”布尔,包含以下输入:

  • 端口= 5000
  • IP 地址= 192.168.1.71
  • LocalIP = 192.168.207.35
  • CommandType = 'GET '
  • data =

一旦服务器对请求做出响应,“ReceivedString”输出将收到以下输出:

{'statusCode': 200, 'data': [0.0291, 0.1253\. 0.2843, 0.0112, 0.0895, 0.2001, 0.0013, 0.0984]}

现在,控制器具有必要的权重,以利用来自服务器的更新的机器学习模型权重来运行异常检测。

结论

使用 REST API 是扩展 PLC 功能的一种简单方法。它允许控制器请求几乎任何类型的过程,并将计算卸载到另一个设备。使用 Python 的 Flask 是实现这一点的最简单和最容易的方法之一。

参考

[1]维基媒体基金会。(2022 年 5 月 22 日)。具象状态转移。维基百科。2022 年 5 月 25 日检索,来自https://en . Wikipedia . org/wiki/representative _ state _ transfer

[2]维基媒体基金会。(2022 年 5 月 22 日)。可编程逻辑控制器。维基百科。于 2022 年 5 月 25 日从https://en.wikipedia.org/wiki/Programmable_logic_controller检索

[3]列别德津斯基,P. (2021 年 11 月 12 日)。伺服驱动器上的异常检测。中等。2022 年 5 月 12 日检索,来自https://towards data science . com/a-single-number-metric-for-evaluating-object-detection-models-c97f 4a 98616d

工业 PLC 上的 REST APIs 第 2 部分:系统健康监控

原文:https://towardsdatascience.com/rest-apis-on-industrial-plcs-part-2-system-health-monitoring-1ad470f7f896

监控系统中每个设备的运行状况

介绍

API 对于卸载计算和运行在给定编程语言中不容易完成的进程非常有用。在本系列的第一部分工业 PLC 上的 REST APIs】中,我们实现了一个简单的 API,它从 SQL 数据库中添加和检索条目。输入数据库的数据用于训练机器学习模型,以检测连接到控制器的伺服驱动器中的异常。

在这个例子中,我们将为收集的数据运行一个基本的训练循环,并对伺服系统进行推理,以查看它们是否表现异常。为此,我们将使用具有未标记数据的自动编码器网络,并测量重建损失以确定当前伺服状态的分类。

动机

通过实施异常检测模型,我们可以监控配置中每个伺服系统的健康状况。肉眼和耳朵可能不明显的东西将被模型捕捉到,用户可以相应地采取行动。及早发现伺服系统的问题,让我们有时间安排预防性维护,这大大减少了停机时间,并有助于防止因伺服系统故障而造成的任何损坏。

背景

自动编码器网络将用于识别伺服状态中的异常。自动编码器网络是合适的,因为它将从输入数据中滤除噪声,并利用可能存在的非线性关系提供更精确的重建。由于将使用未标记的数据,自动编码器网络的重建损失将用于分类异常。具有高于设定阈值的高重建损失的数据将被视为异常,而具有低于设定阈值的低重建损失的数据将被视为非异常。

3D 打印是伺服系统的一种流行用途;以高速度和加速度运行打印机会加快打印机的吞吐量,也会导致更高的动态负载。打印机可能几乎 100%的时间都在不停地运行,因此以潜在的方式监控伺服系统的状态是很有用的。

配置

在这个例子中,我们将创建一个 Compass 插件,它为数据采样和推断发出 API 请求。虽然 API 请求可以直接从 PLC 发出,但它们将从运行 Compass 的 PC 发出,以省去将请求路由到适当外部网络的麻烦。发出 API 请求的网络架构如下所示:

图 1:带有 PLC 的 REST API 网络架构,图片由作者提供

图上的箭头是以太网/IP 连接。在本例中,MPiec 控制器将与中间的可选 DNS 服务器进行对话。运行 Compass 的 PC 将自动处理 DNS 路由,并将请求发送到适当的服务器。

REST API 服务器托管在 AWS ec2 实例上,具有以下可用端点:

/api/v2/store_data #store a row(s) of data values for training/api/v2/inference #run inference on row(s)/api/v2/check #check on model availability

模型

来自 PyOD GitHub 库的自动编码器模型将用于检测伺服驱动器上的异常状态。自动编码器的网络结构如下所示:

图 2:自动编码器神经网络结构,图像由 Chervinskii⁴

与我的前一篇文章⁵中使用的可以直接在 PLC 上运行的异常检测模型不同,AutoEncoder 计算量很大,必须在一个支持矢量化计算的不同设备上运行。如果你有兴趣了解更多关于自动编码器网络的信息,可以考虑阅读这篇⁵.的论文通过使用 API 来运行推理,我们显著降低了 PLC 的 cpu 负载并避免了性能问题。

数据

来自伺服系统的所有可用的和相关的数据将用于这个例子。一些关键变量包括:

  • 反馈位置、速度、加速度和扭矩
  • 指令位置、速度、加速度和扭矩

最终,减少采样变量的数量,以减少读取变量的 cpu 负载,而不会对自动编码器模型产生重大影响,这将是最佳选择。在本例中,将从以下列表中读取所有与运动相关的轴参数:

图 3:变量采样的轴参数列表,作者图片

插件布局

该插件在启用时,将自动从伺服系统中批量采样数据,并使用/api/v2/store_data端点将数据行发送给 API 以供将来训练。我们将启用插件,并运行几个 g 代码文件几个小时,这样就可以采样一组不同的数据。

图 Compass 中的插件,作者图片

一旦收集了足够多的数据,就会调用/api/v2/check端点。端点被编程为:

  1. 检查自动编码器型号的状态/可用性
  2. 如果存在足够的数据,则启动培训课程

第一次调用/api/v2/check端点将返回以下内容:

图 5 😕 API/v2/check 端点的 API 响应,作者图片

一旦自动编码器完成训练并可用于推断,端点/api/v2/check将返回以下内容:

图 6:具有可用模型的/api/v2/check 端点的 API 响应,图片由作者提供

插件客户端代码

Compass 插件通过 POST 方法发出 HTTP 请求来与 API 通信。该请求包含给定端点的 JSON 格式的相关数据。在推断过程中,一行或多行被发送到 API,服务器将返回每一行的异常状态。该请求是用 c#完成的,代码如下:

图 7:c#中的 API 请求,作者图片

图 7 的复制粘贴友好格式:

private async Task<InferenceResponse> InferenceRequestAPI(Dictionary<string,string> data){HttpClient client = new HttpClient();client.BaseAddress = new Uri(URL);client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));var jsonData = JsonConvert.SerializeObject(data);var contentData = new StringContent(jsonData, Encoding.UTF8, "application/json");var response = await client.PostAsync(urlParameters, contentData);string contents = await response.Content.ReadAsStringAsync();client.Dispose();InferenceResponse responseJSON = JsonConvert.DeserializeObject<InferenceResponse>(contents);return responseJSON;}

服务器将以下列格式返回响应:

图 8:来自服务器的自动编码器推理响应,图片由作者提供

在上面图 8 的例子中,一行数据用于推断。该响应有三个关键响应项:

  1. class —推理的分类(如果异常则为真,如果没有检测到异常则为假)
  2. loss —自动编码器模型返回的重建损失,用于推理
  3. time——推论的时间

分类输出基于一个threshold值,该值使用重建损失将自动编码器的输出二进制化。在这个例子中,服务器的训练系统基于最初给出的训练数据预先选择阈值。服务器预先选择值 0.4 作为截止阈值,这意味着任何重建损失高于 0.4 的推断都将被视为异常。

对于生产应用,用户应仔细选择阈值。此外,应实施后处理算法来跟踪肯定分类的频率和幅度,以便单个肯定分类不会触发任何操作。

监控异常频率的一个简单有效的方法是在不同的时间间隔跟踪 3 个 SMA(简单移动平均线)并相互比较。例如,使用一组 5/30/120 分钟的 SMA,我们可以根据 120 分钟的 SMA 建立一个“标称”值,并将其与 5 分钟和 30 分钟的 SMA 进行比较。当较低的间隔 SMAs 超过标称值以上 3 个标准差时,我们可以考虑向用户发送需要检查 3D 打印机的通知。

结论

出于各种原因,REST APIs 与 PLC 的结合非常有用,包括能够轻松集成高性能机器学习模型来监控设备的健康状况。通过利用 API,几乎任何东西都可以与 PLC 及其连接的设备集成。

如果你对现成的指南针插件感兴趣,联系我

来源

[1] Lebiedzinski,P. (2022 年 5 月 27 日)。工业 PLC 上的 REST APIs。中等。2022 年 9 月 26 日检索,来自https://towards data science . com/a-single-number-metric-for-evaluating-object-detection-models-c 97 F4 a 98616d

[2] 安川指南针。安川。(未注明)。2022 年 9 月 26 日检索,来自https://www . yaskawa . com/products/motion/machine-controllers/software-tools/yaskawa-compass

[3]赵,杨,纳斯鲁拉,李,2019 .PyOD:用于可伸缩异常检测的 Python 工具箱。机器学习研究杂志(JMLR),20(96),第 1–7 页。

[4]作者切尔文斯基—自己的作品,CC BY-SA 4.0,https://commons.wikimedia.org/w/index.php?curid=45555552

[5] Lebiedzinski,P. (2021 年 11 月 12 日)。伺服驱动器上的异常检测。中等。2022 年 5 月 12 日检索,来自https://towards data science . com/a-single-number-metric-for-evaluating-object-detection-models-c97f 4a 98616d

[6]乐,2015 .深度学习教程第 2 部分:自动编码器,卷积神经网络和递归神经网络。谷歌大脑,谷歌公司

保留分析框架

原文:https://towardsdatascience.com/retention-analysis-framework-4eb62933e2b

数据科学视角

幸运的猫在 Unsplash 上的照片

你是一个试图了解你的产品或网站的用户的数据科学家吗?本文将从数据科学的角度向您介绍一个用户保留分析框架。本文将涵盖测量留存的三个维度(时间、用户状态、行动)、分析框架、如何通过分析找到“啊哈时刻”和“习惯时刻”,并调查用户离开和留下的原因。

为什么做留存分析很重要?

一个流行的商业增长框架模型是 AARRR(获取、激活、保留、收入和推荐)。然而,许多人批评 AARRR 过于关注收购,尽管留住人才更重要,尤其是对于许多努力留住人才的互联网产品来说。留住人才更重要的原因有很多:获取策略(如广告)很昂贵,留住一个用户往往比获得一个新用户更便宜;留存是成长的基础;用户保持率比获得率更直接与收入相关。

因此,AARRR 框架后来被重新调整为 RARRR(保留、激活、推荐、收入、收购),保留是第一优先。

AARRR 重新排序为 RARRA(图片由作者制作)

我们如何定义和衡量保留?

保持率衡量的是在特定的时间内有多少用户回到你的产品。在测量保持力方面,有三个维度需要考虑。了解不同的保持措施并进行比较,将有助于我们为您的产品找到合适的保持措施。

维度 1:时间

  • n 天/周/月保留

n 天留存率是计算留存率的最经典方法,它衡量在第 0 天首次使用产品的用户中,有多少比例的用户在第 n 天仍然活跃。这里的“天”可以是周或月。

是否使用每日、每周或每月保留取决于您的产品和用户使用您产品的频率。例如,对于高粘性的游戏产品,通常每天测量 N 天留存率。

  • 无限保留

无限保留(也称为滚动保留)衡量在第 0 天首次使用该产品的用户中,有多少比例的用户在第 n 天及之后仍然活跃

  • 托槽保持力

托槽固位更灵活。你可以定义你感兴趣的时间段。例如,Pinterest 衡量的是"在注册后 28-35 天的一周时间内,仍在进行关键活动的新注册用户的百分比。

维度 2:用户状态

除了时间,用户状态往往是另一个需要考虑的重要维度。首先,我们来看看如何定义用户状态。定义状态的方式有很多种,不同的公司/产品往往有自己定义用户状态的方式。以下是定义用户状态的一种方式:

  • 新用户
  • 搅动用户:x 天不活动
  • 非活动用户:0-x 天非活动
  • 被动用户:搅动后激活/不激活
  • 活跃用户:不是新用户且没有反应的活跃用户

计算不同用户状态的保持率通常很重要。例如,新用户保持率衡量保持活跃的新用户的比例。活跃用户保持率衡量保持活跃的活跃用户的比例。在功能变化的情况下,我们可能会看到活跃用户保持率下降,因为用户已经习惯了产品。然而,与此同时,新用户的保留率可能会上升,这表明从长远来看,这一功能变化可能是一种改进。

维度 3:动作

当我们说用户使用产品时,我们并没有定义我们所说的“使用”是什么意思。我们应该把“使用”定义为访问产品页面,停留一定时间,进行一定的动作,还是购买一件产品?我们将用于计算保留率的操作称为“关键操作”。

一个起点

这么多措施,我们用哪一个,从哪里入手?

  • 对于维度 1,我将从经典的 N 天/周/月保留开始,稍后研究其余的。为了确定时间间隔,我会画出用户使用产品的频率,用不同的时间间隔画出使用产品 x 次的用户百分比,用不同的时间间隔画出保持率,然后看看哪个对你的产品有意义。在您的分析中包含多个时间间隔是可以的。
  • 对于维度 2,我会从新用户保持率开始,然后调查活跃用户保持率,尤其是新产品。
  • 对于 dimension 3 来说,这真的取决于产品目标:你对货币化价值更感兴趣,还是你正处于通过某些行动来增长和留住用户的阶段?

分析框架

滞留曲线

保留曲线绘制了保留时间,x 轴为时间,y 轴为保留率。理想情况下,随着时间的推移,当用户越来越多地回来时,我们希望看到一个微笑的曲线。下降的曲线表示危险,变平的曲线表示健康的产品。产品的目标是向上移动曲线,使曲线变平或上升。

保留曲线(图片由作者制作)

保留群组分析

群组分析告诉我们,我们看到产品在哪里做得好,在哪里做得不好,以及产品在哪些用户群中取得了成功。我们可以从许多不同的类别来定义群组,例如:

  • 人口统计:性别、年龄等。
  • 采集:采集时间、采集来源等。
  • 行为

我们可以用分组保留曲线或三角形保留图来表示保留群组(如下所示,获取时间为群组)。

三角形保留图(图片由作者制作)

统计分析

像任何其他类型的分析一样,我们可以从计算描述性统计和相关性开始。我们还可以进行生存分析,以确定哪些属性很重要。查看我的之前关于生存分析的博文。此外,RFM(保持率、频率、货币)分析框架通常用于用户细分。

一个起点

一个好的起点是使用仪表板显示所有可用群组/组的保留曲线和三角形保留图表。然后,我们可以从仪表板中获得洞察力,并从那里开始。

对于新用户,寻找“啊哈时刻”

对于新用户来说,最重要的是最初几天或几周内的入职体验。几乎所有的产品都想帮助用户尽快实现“啊哈时刻”。问题是:我们如何定义用户的啊哈时刻,有助于啊哈时刻的用户行为是什么?例如,脸书关注的是“在 10 天内获得 7 个朋友”。这种行为(获得朋友)和神奇的数字(7 个朋友和 10 天)预示着用户未来的成功。我不认为我们所有人都应该为所有产品找到一个具有神奇数字的行为,但我认为从我们的数据中找出哪些行为有助于啊哈时刻是很重要的。然后,产品团队可以使用这些信息来改善我们的入职体验

一个起点:

找到与未来几周/几个月的保留相关的用户事件和入职行为。

对于长期用户,找到“习惯时刻”

最成功的长期用户是如何形成使用产品的习惯的?他们是谁,他们一直在使用什么功能,他们有什么属性,他们的用户旅程是什么?目标是找到成功的功能和用户旅程,投资那些成功的功能,并鼓励新用户跟随成功的用户旅程。

一个起点:

我们可以从对长期用户进行描述性分析开始,以识别用户行为和特征,然后进行群组分析,以找到这些用户的用户旅程。

人为什么会离开和留下?

人们离开和流失的原因可能有很多。例如:

  • 他们可能不了解产品。
  • 该产品可能很难使用。
  • 人们可能看不到产品的价值。
  • 人们更喜欢竞争对手的产品。
  • 该产品可能有一些问题,如错误或缓慢。
  • 新用户不是目标用户。用户需求和核心功能可能不匹配。
  • 人们可能只在短时间内需要我们的产品。

人们留下来的原因可能有很多。例如:

  • 人们喜欢这个产品。
  • 个性化通知有效。
  • 人们使用该产品已经成为一种习惯。

用户离开和留下的原因可能很难直接从产品数据中推导出来。相反,做用户研究和实验会有所帮助。此外,进行各种实验也可以解决人们为什么离开的问题,并扩大人们留下来的原因。例如,对更好的入职体验进行实验可能有助于人们在入职期间更好地理解产品,对推送通知的实验可能有助于提醒人们产品的价值,对各种产品功能的实验可能有助于识别和推广人们喜欢的关键功能。

一个起点

协作是关键。为了了解用户,我们可以与 UX 研究团队合作,帮助设计调查和采访,并产生见解。为了进行实验,我们可以与工程师合作设计实验并分析结果。

希望这篇文章能为您提供一些关于如何开始留存分析以及如何深入进行未来分析的观点。如果您对保留分析有任何想法,请告诉我。我很想听听。谢谢大家!

鸣谢:感谢吉姆·贝德纳尔的反馈和支持!

参考:

杨索菲亚2022 年 1 月 17 日

反思折射仪:VST、阿塔戈和迪流体:第一部分

原文:https://towardsdatascience.com/rethinking-refractometers-vst-atago-and-difluid-part-1-b5fdb0e5731e

咖啡数据科学

乔、杰里米和罗伯特

在过去的几年里,折光仪越来越多地被那些想要持续冲泡美味咖啡的人所使用,因为这些设备在这个非常主观的领域提供了一个非常宝贵且相对简单的客观指标。然而,缺点是为了实现高准确度和精确度,这对于较低浓度的酿造方法(例如,过滤)尤其重要,这些装置通常花费很多钱。

VST/米斯科和阿塔戈,有着纠结的过去,一直是咖啡领域主要的数字折光仪制造商。一些设备已经试图打入这一领域,声称可以以低得多的价格获得相当的性能——最近(也是积极营销的)是 DiFluid 公司的不可否认的可爱的折射计。随着咖啡社区的许多人开始测试这款设备(包括我),与 VST 和 Atago 更成熟的设备相比,双流体折光仪的准确度和精确度一直存在争议。

进入 : 苏格拉底咖啡。Socratic Coffee 已经收集并继续收集大量关于咖啡评估和制备设备的数据,包括 DiFluid(这里缩写为 DFT)。通过与他们合作,我们在这里展示了他们的一些数据,看看是否可以帮助我们更好地了解 DFT 设备在评估咖啡浓度方面的性能。

所有图片由作者提供

正如专业咖啡社区的许多讨论一样,得出基于经验的结论的最大挑战是获得高质量的数据。获得设备和必要的供应品是一个很大的障碍,设置和执行一个设计良好的测试协议也是如此。此外,不熟悉实验方法和数据分析技术会导致得出不恰当的结论。

例如,当涉及到咖啡相关折光率仪之间的比较测试时,收集数据的人通常只有 1 个 DFT 和 1 个 Atago 或 VST。在我之前对折光率仪的一些研究中,我认为我们可能没有充分认识到使用折光率仪评估咖啡可溶性的复杂性,这一新数据将我引向另一个问题:制造差异。

当在大规模生产中做任何事情时,必须进行质量控制,以确保设备符合某些规格。这很难做好,即使做得很好,来自同一制造商的设备之间也可能存在差异。例如,很有可能我的 DFT 器件与我的 Atago 相比表现良好,而其他人的 DFT 器件与不同的 Atago 或 VST 相比表现不佳。

谁是对的?假设两组数据都是以相当的严谨性和方法控制收集的,那么两组数据都可以证明他们的观点。需要指出的是,Atago 和 VST 都提供校准证书(Atago 符合 ISO 标准;VST 至 NIST 标准)。DFT 似乎不提供这种服务。

这里提供的数据的简要总结表明了两件事:

  1. 让来自同一制造商的多种设备测试设备间的可变性是很重要的。
  2. 我们需要更好地理解和评价使用光衍射来测量咖啡可溶性含量的局限性。

因为这里有大量的数据需要消化,所以为了便于演示,我们将其分成几个部分。

数据

数据分三批收集,涵盖 16 台 DFT 设备、1 台 VST、1 台 Atago Coffee 和 1 台 Atago RX-5000i。每批使用不同组的 DFT 装置(分别为 5、6 和 5)。此外,一些样品用水分天平进行了分析,提供了一个地面实况测量。使用了几种解决方案,每种都提供了不同的见解:

  1. 蔗糖溶液(白利糖度测量的基础;公认的规范性数据;硬件的“干净”评估)
  2. 浓度为浓缩咖啡的速溶咖啡(高咖啡可溶浓度,不可溶物干扰最小;蔗糖增加了难度,需要软件将折光率读数转换为咖啡可溶物)
  3. 过滤器浓度的速溶咖啡(低咖啡可溶物浓度,不可溶物干扰最小;与速溶咖啡相比,信号强度降低,但与现实解决方案相比,噪音相对较低,因为速溶咖啡几乎完全是咖啡可溶物— 99.9%)
  4. 浓缩咖啡(高咖啡可溶浓度的真实世界应用;噪声增加但信号强的困难测试解决方案)
  5. 过滤咖啡(低咖啡可溶物浓度的实际应用;最困难的测试解决方案,信号减少,噪声增加,测试硬件和软件的鲁棒性)

应该注意的是,并不是所有的测试集都使用了所有这些解决方案。在测试中,一些样品用注射器过滤器过滤,一些没有,但在图表中有明确说明。在收集的每组数据之间校准设备。

在本文中,我们将研究蔗糖溶液、速溶咖啡的过滤强度和过滤咖啡。

分析

蔗糖制成三种相同浓度的溶液,从每种溶液中取三个样品。我们看到 VST 和 Atago 非常接近地面实况,样本之间的差异非常小。DFT 器件始终显示出样品之间的更大差异。

我们可以查看所有样本中每个设备的均值差异。大多数 DFT 器件彼此之间具有相似的方差,并且该方差高于 VST 或 Atago。蔗糖是折光仪测试中使用的绝佳解决方案,因为它由单一可溶性物质(糖)组成,允许我们检查硬件在最简单的解决方案中提供折光读数的能力。

转到过滤咖啡,我们有速溶咖啡过滤强度。在这个测试中,VST 和 Atago 更接近地面真相,但是一些 DFT 设备的样本差异更接近 VST 的差异。DFT II 的读数与其他的不同。

地面实况是 1.33% TDS (2.83/212.83)

当我们从速溶咖啡转向冲泡过滤咖啡时,故事发生了一点变化。有趣的是,关于地面真相,一个 DFT 设备(DFT II)似乎跟踪最近。VST 和 Atago 的表现彼此相似,这表明在这两种设备中的折光率-咖啡可溶性相关算法有显著的相似性。

查看这些设备,人们可能会获得类似 DFT II 的设备,并确定它与 VST 或 Atago 设备相比有显著差异。或者,人们可以获得类似 DFT III 的设备,并确定它在测试中与 VST 或 Atago 一样准确。

DFT 设备彼此之间存在差异,有些设备的差异类似于 VST 或 Atago。

最后,对于这三个测试,我们可以更全面地观察错误率百分比。这是读数的差值除以读数,这样我们就可以比较这三者。蔗糖的误差最小,而速溶咖啡和过滤咖啡的误差很大,即使是 Atago 和 VST 也是如此。

我们可以把标准差看作是方差的度量,除了实际过滤咖啡,阿塔哥和 VST 的标准差更低。这些结果令人困惑,因为它们并不明确。

左:满刻度,右:放大

讨论

我们有意不提供任何数据的统计分析,因为测试中只使用了一台 VST 和一台 Atago 设备。Socratic Coffee 之前报道了他们对多种 VST 和 Atago 设备的测试。从这些测试中最大的收获之一是,即使对于“黄金标准”设备 VST 和 Atago,单个样品的读数也可能不同。

然而,对于许多人来说,这些误差率可能不会造成重大问题,因为折光率是作为一种客观指标与其他指标结合使用,以实现一致性和所需的口味特征。普通咖啡师很可能不需要 VST 或 Atago 级别的性能,考虑到 DiFluid 的大小和价格,它可能是一个有吸引力的选择。

然而,这只是数据冰山的一角。让我们继续查看更多数据,并深入探究。

如果你愿意,可以在推特、 YouTubeInstagram 上关注我,我会在那里发布不同机器上的浓缩咖啡照片和浓缩咖啡相关的视频。你也可以在 LinkedIn 上找到我。也可以关注我在订阅

我的进一步阅读:

我未来的书

我的链接

浓缩咖啡系列文章

工作和学校故事集

反思折射仪:VST、阿塔戈和迪流体:第二部分

原文:https://towardsdatascience.com/rethinking-refractometers-vst-atago-and-difluid-part-2-2aadd3c60c52

咖啡数据科学

罗伯特、乔和杰里米

这是评估 DiFluid (DFT)数字折光仪与 VST 和 Atago 折光仪系列的第二篇文章。折射仪已经被用于通过关联咖啡在特定温度下的折射率变化来确定咖啡中的总溶解固体(TDS)。虽然它们是最常用的咖啡折光仪,但 VST 和 Atago 设备也是最昂贵的。DiFluid 最近以更低的价格和更小的尺寸出现在市场上。社区中各种人的早期测试已经从 DFT 中产生了有趣的结果,但是,正如我们从第 1 部分中得出的结果,设备之间似乎存在显著的、不可预测的可变性。

Socratic Coffee 在多套 DFT 设备上收集数据。该测试的主要目的是了解这些设备对地面实况(即通过水分天平直接测量的咖啡可溶物)的精确度。此外,该测试结合了不同信号+噪声的不同解决方案。通过这些数据,我们对测量的复杂性和使用折光率推断溶解固体含量的困难有了更好的了解。

作者提供的所有图片

数据

数据分三批收集,涵盖 16 台 DFT 设备、1 台 VST、1 台 Atago Coffee 和 1 台 Atago RX-5000i。每批使用不同组的 DFT 装置(分别为 5、6 和 5)。此外,一些样品用水分天平进行了分析,提供了一个地面实况测量。使用了几种解决方案,每种都提供了不同的见解:

  1. 蔗糖溶液(白利糖度测量的基础;公认的规范性数据;硬件的“干净”评估)
  2. 浓缩咖啡浓度的速溶咖啡(高咖啡可溶物浓度,不可溶物干扰最小;蔗糖增加了难度,需要软件将折光率读数转换为咖啡可溶物)
  3. 过滤器浓度的速溶咖啡(低咖啡可溶物浓度,不可溶物干扰最小;与速溶咖啡相比,信号强度降低,但与现实世界的解决方案相比,噪音相对较低,因为速溶咖啡几乎完全是咖啡可溶物——99.9%
  4. 浓缩咖啡(高咖啡可溶浓度的真实世界应用;噪声增加但信号强的困难测试解决方案)
  5. 过滤咖啡(低咖啡可溶物浓度的实际应用;最困难的测试解决方案,信号减少,噪声增加,测试硬件和软件的鲁棒性)

应该注意的是,并不是所有的测试集都使用了所有这些解决方案。在测试中,一些样品用注射器过滤器过滤,一些没有,但在图表中有明确说明。

在本文中,我们将查看所有五种解决方案,这组数据包含过滤和未过滤的样本。

分析

我们将从最理想的情况开始,蔗糖,然后转向过滤咖啡(由于 TDS 低,这是最困难的)。对于蔗糖,不同设备之间仍有差异,但它们非常接近 TDS。应该注意的是,所有关于地块的陈述都只是从数据中观察到的事实。y 轴设置在一个范围内,包括从 espresso 到过滤器强度的所有结果,而不是试图推断更一般化结果的推理分析。

Atago RX-5000i 的 TDS 结果最初以白利糖度为单位,并使用 Atago Pal 咖啡数据进行转换。对于每个读数,Atago Pal 咖啡给出白利糖度和 TDS,因此我们使用这个比率将 Atago RX-5000i 从白利糖度转换为 TDS。应该注意的是,所有这些器件在输出方面都有一些共性,但不是全部。Atago RX-5000i 只有 Brix,Atago Pal 咖啡有 Brix 和咖啡 TDS,DFT 有咖啡 TDS 和 nd(折光率)。

对于浓缩速溶咖啡,我们有一个过滤和未过滤的额外数据集。DFT 传感器的结果在过滤或未过滤的情况下匹配良好,但与实际情况有偏差。奇怪的是,两个 Atago 传感器和 VST 传感器在这种强度下得到更好的结果。

转到过滤强度速溶咖啡,未过滤的仍然表现更好,但只有一个 DFT 表现类似于非 DFT 设备。DFT 器件之间也有更多的可变性。

对于真正的浓缩咖啡,我们看到所有传感器都偏离了地面实况。DFT 器件的性能与其他器件非常相似。过滤后的结果具有更高的背景真实性,但是对于设备来说具有更低的 TDS。对于地面实况,这是使用湿度天平完成的,但只取了一个样本(由于时间限制;一个样品需要一个小时)。

现在,在最难衡量的方面,我们看到跨设备的高估。过滤遵循与之前相似的趋势,但是基础事实在样本之下而不是之上。过滤后的地面真相比未过滤的低,与之前相反,这很奇怪,但需要更多的数据才能更好地理解。

最后,我们可以查看这些不同类型的 TDS 样本的百分比误差。蔗糖表现最好,VST 和 Atago 几乎不相上下。DFT 有一些变化,但有些器件更接近。

这个数据集是各种咖啡的一个很好的样本。然而,
折光仪上的数据似乎指出了咖啡饮料的复杂性。应该对它们进行更好的研究,因为不同咖啡种类之间的差异比以前认为的更加微妙。看到过滤样本的明显影响也很奇怪,这是苏格拉底式咖啡在之前已经研究过的。正如他们的工作所表明的,过滤样品似乎比简单地去除噪音以提高折光率读数更复杂。

如果你愿意,可以在 TwitterYouTubeInstagram 上关注我,我会在那里发布不同机器上的浓缩咖啡照片和浓缩咖啡相关的视频。你也可以在 LinkedIn 上找到我。也可以在关注我,在订阅

我的进一步阅读:

我未来的书

我的链接

浓缩咖啡系列文章

工作和学校故事集

反思折射仪:VST、阿塔戈和迪流体:第三部分

原文:https://towardsdatascience.com/rethinking-refractometers-vst-atago-and-difluid-part-3-e272ad081a81

咖啡数据科学

作者:罗伯特、乔和杰里米

我们还有一个来自另一组 DFT 设备的数据。我们之前看了两个其他批次的设备和实验。这些测试凸显了使用单个设备了解折光率仪性能以及不同设备性能的难度。

另一个变得越来越明显的挑战是,即使是像 VST 和 Atago 这样众所周知且经过校准的设备,也很难测量性能。这并不意味着折光仪在咖啡中没有多大用处,但我们需要更好地了解它们的读数,以及如何从读数中推断出总溶解固体(TDS)含量。

所有图片由作者提供

数据

数据分三批收集,涵盖 16 台 DFT 设备、1 台 VST、1 台 Atago Coffee 和 1 台 Atago RX-5000i。每批使用不同组的 DFT 装置(分别为 5、6 和 5)。此外,一些样品用水分天平进行了分析,提供了一个地面实况测量。使用了几种解决方案,每种都提供了不同的见解:

  1. 蔗糖溶液(白利糖度测量的基础;公认的规范性数据;硬件的“干净”评估)
  2. 浓度为浓缩咖啡的速溶咖啡(高咖啡可溶物浓度,不可溶物干扰最小;蔗糖增加了难度,需要软件将折光率读数转换为咖啡可溶物)
  3. 过滤器浓度的速溶咖啡(低咖啡可溶物浓度,不可溶物干扰最小;与速溶咖啡相比,信号强度降低,但与现实世界的解决方案相比,噪音相对较低,因为速溶咖啡几乎完全是咖啡可溶物——99.9%
  4. 浓缩咖啡(高咖啡可溶物浓度的实际应用;噪声增加但信号强的困难测试解决方案)
  5. 过滤咖啡(低咖啡可溶物浓度的实际应用;最困难的测试解决方案,信号减少,噪声增加,测试硬件和软件的鲁棒性)

应该注意的是,并不是所有的测试集都使用了所有这些解决方案。在测试中,一些样品用注射器过滤器过滤,一些没有,但在图表中有明确说明。

在本文中,我们将研究所有四种解决方案:两种浓缩咖啡浓度,两种过滤浓度。

数据

每个样品对同一溶液有三个读数。阿塔哥咖啡,VST 和阿塔哥阿尔法趋势很好地结合在一起,但正如以前的实验表明,蔗糖对折射率有更清洁的影响。与其他实验类似,人们可以得到一个类似 DFT D 的装置,其精度非常接近 VST/阿塔戈。

更具挑战性的情况是过滤咖啡。VST、阿塔哥阿尔法和阿塔哥咖啡也有些许不同。速溶咖啡实验中的 DFT 设备更接近地面真相,这很奇怪,尽管样本数量太少,无法进行任何统计分析。

对于速溶咖啡来说,所有的样本都离地面真相很远,但彼此很接近。这可能表明在 VST 和阿塔哥咖啡中使用的转换算法试图解释一些信号噪声(阿塔哥阿尔法的读数被转换为 TDS,使用从 VST 和阿塔哥咖啡读数导出的公式,因此使用咖啡相关折光仪进行跟踪是可以预期的)。

这些折光仪的结果指出了咖啡如何影响折光率的一些基本差异。这种差距的一部分可能只是简单的校准问题,但在这些实验中,在测试开始时非常小心地校准了所有设备。该数据还表明,需要多个硬件样品来真正评估任何给定折光率仪的实用性。

如果你愿意,可以在推特、 YouTubeInstagram 上关注我,我会在那里发布不同机器上的浓缩咖啡照片和浓缩咖啡相关的视频。你也可以在 LinkedIn 上找到我。也可以关注我在订阅

我的进一步阅读:

我的书

我的链接

浓缩咖啡系列文章

工作和学校故事集

反思生存分析:如何让你的模型产生生存曲线

原文:https://towardsdatascience.com/rethinking-survival-analysis-how-to-make-your-model-produce-survival-curves-7a9ef112e2af

使用简单的 ML 方法预测事件时间

马库斯·斯皮斯克在 Unsplash 上的照片

在数据驱动的公司中,事件时间应用程序在决策中扮演着至关重要的角色(也超出了我们的想象)。对于事件时间分析,我们指的是所有用于测量一些感兴趣的事件发生之前所经过的时间的技术。这个简单的定义可能会立即概括出在业务环境中开发时间事件应用程序的所有好处(不仅仅是好处)。

时间-事件起源与医学领域相关,以回答诸如“被分析的个体能活多久?”。出于这个原因,术语生存事件发生时间通常被用作同义词。如今,随着机器学习的大规模采用,在医疗/临床部门以外的公司中也经常会发现生存方法学的应用。制造商可能对估计某些发动机的预期寿命感兴趣;服务提供商可能需要计算其客户的预期寿命;金融机构可以评估借款人在一段时间内的破产风险。

实际上,要对事件时间问题建模,有一套合适的方法。从经典的线性统计方法到更复杂的机器学习方法,再到前沿的深度学习解决方案,已经发布了大量的生存框架。他们都很棒,但是他们必须尊重生存建模理论的假设,这可能会导致低适应性或对实际用例的限制。出于这些原因,一种处理生存分析的便捷方法可能包括将时间-事件建模作为一个分类问题。这个想法并不新鲜,在这两部作品[ 1 ]、[ 2 ](帖子底部的引文)中也能找到。

在这篇文章中,我们提出了一个用预测能力进行生存分析的概括。我们的目标是将起始时间和感兴趣的事件之间的经过时间建模为多重二进制分类问题。通过适当和简单的后处理,我们可以获得可靠和稳健的个体生存曲线。我们可以使用我们最喜欢的分类算法,像往常一样搜索参数,并考虑校准我们的结果的可能性,使它们更可信。

数据

安排我们处理的数据来实现一个生存预测应用程序是简单的,不需要特别的努力。我们必须有一些输入特征(数字或分类)和一个原始目标,就像在标准回归/分类表格任务中一样。在这种情况下,目标表示从监控开始到事件发生 经过了多长时间。

事件时间网格(在左侧)。已排序的事件时间网格(在右侧)。[图片由作者提供]

让我们想象成为一家提供在线订阅服务的公司。我们可能对计算客户在订阅时的预期寿命感兴趣。换句话说,当一个新客户登陆我们的平台并订阅获取服务时,我们想知道她/他还会成为我们的客户多久。我们可以通过开发一种输出概率生存曲线(每个客户一条)的生存方法来完成这项任务。生存曲线是单调概率的序列。对于每个时间步,我们有 0 到 1 之间的数字,表示在特定时间范围内某些事件(在我们的例子中是订阅)的生存可能性。

生存函数的图形表示。[图片由作者提供]

我们模拟了一些数字输入特征和一个目标,该目标代表了从第一次订阅开始个人保持我们的客户的时间。从我们的模拟中,我们观察到大多数客户在参与后的第一阶段就离开了(下图的左侧)。对于大多数公司来说,这是一种可靠的动态,在这种情况下,很多客户会在一段时间后流失。相反,我们有一群忠实的订户,他们仍然是我们服务的用户(下图右侧)。在我们的例子中,我们将最大可观察订阅时间限制为 700 个周期(比如说几天)。为了使我们的方法有效,这个假设是强制性的。

事件时间分布(左侧)。二进制标签比例(右侧)。[图片由作者提供]

建模

我们开始将离开时间宁滨成规则长度的组(箱)。对于分析中的每个客户,我们最终都有一个标准化的分类目标,其中几个唯一的类等于所创建的箱的数量。在这一点上,我们可以用一热编码转换目标,产生一个 0 和 1 的多维二进制目标。一个识别我们的客户离开的时间范围(离开箱子)。作为最后一步,我们必须在目标序列中,在“离开库”之前用左边的 1 替换 0。这最后一步很重要,它在准备建模的目标中提供了一个临时路径,其中零标识了客户所处的时间范围。

一键编码入库事件时间(左侧)。累积独热编码入库事件时间(右侧)。[图片由作者提供]

现在,我们已经以正确的格式获得了所有需要的东西。我们有一组特性和一个多维二元目标。换句话说,我们只需解决一个多维二元分类任务。解决它的一种可能性在于使用本地 scikit-learn 方法( 分类链 )。

from sklearn.multioutput import ClassifierChain
from sklearn.linear_model import LogisticRegression

model = ClassifierChain(
  LogisticRegression(random_state=33, max_iter=2000), cv=5
)
model.fit(X_train, y_train)

使用分类器链,我们将多输出分类目标建模为独立但相关的二元分类任务。我们之所以说依赖,是因为上一步的输出与初始特征连接在一起,并用作链中下一次训练的输入。

在训练阶段之后,我们以一组相关的二元分类器结束。它们中的每一个都提供了一个概率结果,这是构建最终个体生存曲线的一部分。概率肯定在 0 和 1 之间,但是不能保证生存函数的单调约束。换句话说,第一时间区间中的生存概率必须高于在随后的时间范围中获得的生存概率。为了满足这一要求,我们在客户级别对分类器链获得的概率进行后处理操作。

from sklearn.isotonic import IsotonicRegression
from joblib import Parallel, delayed

isoreg = IsotonicRegression(y_min=0, y_max=1, increasing=True)
x = np.arange(0, n_bins)

proba = model.predict_proba(X_test)

proba = Parallel(n_jobs=-1, verbose=1)(
    delayed(isoreg.fit_transform)(x, p) 
    for p in proba
)
proba = 1 - np.asarray(proba)

简单地对最终概率应用保序回归,我们获得单调生存曲线作为最终输出

预测生存曲线的样本。[图片由作者提供]

最后,我们可以像在标准监督任务中一样,使用我们感兴趣的度量来测量误差。我们可以使用例如 Brier 评分或更标准的逻辑损失。

from sklearn.metrics import brier_score_loss, log_loss

brier_scores = Parallel(n_jobs=-1, verbose=1)(
  delayed(brier_score_loss)(true, pred, pos_label=1) 
  for true,pred in zip(y_test,proba)
)

logloss_scores = Parallel(n_jobs=-1, verbose=1)(
  delayed(log_loss)(true, pred, labels=[0,1]) 
  for true,pred in zip(y_test,proba)
)

测试数据的 Brier 分数分布(左侧)。测试数据的对数损失分布(在右边)。[图片由作者提供]

摘要

在这篇文章中,我们介绍了一种简单有效的方法,用我们选择的标准机器学习分类器来生成生存曲线。我们发现,我们可以通过将观察到的失败时间建模为二元目标序列来预测存活曲线。通过简单的概率后处理,我们获得了可靠的概率输出。所提出的方法可以很容易地推广和应用于许多情况下(也考虑删截观察,如果他们的添加是合理的,以提供性能的改善)。

查看我的 GITHUB 回购

保持联系: Linkedin

参考

[1]钟,c;和 Tibshirani,R. (arXiv 2019)。作为分类问题的生存分析

[2]余,陈正宁;格雷内尔河;林,香港中文大学;神经信息处理系统进展 24–2011 年第 25 届神经信息处理系统年会。将患者特异性癌症存活分布作为一系列相关回归变量进行学习

反思思维:注意力机制实际上是如何工作的?

原文:https://towardsdatascience.com/rethinking-thinking-how-do-attention-mechanisms-actually-work-a6f67d313f99

大脑、数学和数字鸿沟——2022 年的研究前沿

图一。注意机制的主要类别。照片由作者拍摄。

目录

1.简介:人脑中的注意力

2.深度学习中的注意机制
2.1。 RNNSearch
2.2。注意机制中的键、查询和值到底是什么?

3.注意机制的分类
3.1。注意的柔软度
3.2。输入特征的形式3.3
输入表示法3.4
输出表示

4.研究前沿与挑战
4.1。协作

5.结论

6.参考文献

1.简介:人类大脑中的注意力

注意力是一种认知和行为功能,它赋予我们选择性地专注于一小部分输入信息的能力,这有利于我们正在进行的任务。它赋予大脑通过忽略无关的可感知信息和选择高价值信息来限制其输入量的能力。当我们观察到一个场景,其中有一个特定的重要部分与我们正在做的任务相关时,我们会提取那个部分进行更细致的处理;当这些场景再次出现时,我们可以学会更好地关注这些部分。

根据 j . K . Tsotsos et al .【1】,注意机制可以分为两类。

  • 自下而上的无意识注意
  • 自上而下的有意识注意

第一类是自下而上的无意识注意——基于显著性的注意——受外界因素刺激。举例来说,较大的声音比较小的声音更容易被听到。我们可以在深度学习模型中使用 max-pooling 和 gate 机制产生类似的结果,该机制将更大的值(即更显著的值)传递给下一层(S. Hochreiter 等人【2】)。下一种类型是自上而下的有意识注意——集中注意力——它有一个预先确定的目标,并遵循特定的任务。因此,使用集中注意力,我们可以有意识地、积极地专注于一个特定的对象或现象。

注意力是一种认知资源方案的分配,具有有限的处理能力,如[3]所述。它表现为注意力瓶颈,这限制了传递给下一步的信息量。因此,它可以通过关注更重要的信息部分来显著提高性能。因此,人们一直在努力复制人脑的注意力机制,并将空间和时间注意力纳入各种任务中。例如,研究人员通过引入计算视觉显著性模型来捕捉图像的潜在显著区域,从而将这一概念引入机器视觉【4】。通过自适应地选择区域序列,V. Mnih 等人提出了一种新的递归模型来提取图像的最相关区域,并且只处理所选择的区域。图 2 示出了视觉注意力如何运作的例子。Bahdanau 等人[5]。使用注意机制允许模型自动搜索翻译成目标单词所需的部分。

图二。视觉注意力。Lovearobot 在维基百科上的照片

注意力机制已经成为现代神经网络架构的重要组成部分,用于各种任务,如机器翻译[vas Wani et al . 2017–16],文本分类,图像字幕生成,动作识别,语音识别,推荐和图形。此外,它在现实世界的应用中有几个用例,并在自动驾驶、医疗、人机交互、情感检测、金融、气象、行为和动作分析以及工业领域取得了辉煌的成功。

2.深度学习中的注意机制

长期以来,机器学习的研究人员一直受到大脑生物基础的启发,尽管如此,仍然不完全清楚人类大脑是如何处理不同的周围现象的,他们试图对它们进行数学建模。为了深入研究深度学习和注意力机制的结合,我将通过 Bahdanau 的注意力[5]架构,这是一个机器翻译模型。图 1 显示了典型的共同注意机制。

2.1。RNNSearch

早于 Bahdanau 等人提出的模型[5]。大多数神经机器翻译的架构都归入编码器-解码器模型的范畴。这些模型试图将源句子编码成固定长度的向量,解码器从该向量生成目标语言的翻译(见图 3)。这种方法的一个主要问题是,当句子变得比训练语料库中的句子长时,编码器就很难处理。因此,本文的作者提出了一种有效的解决方案来应对这一挑战,即引入一种新的方法来联合学习翻译和对齐。这里的想法是,在每一步翻译单词的过程中,它(软)搜索位于源句子中不同位置的最相关的信息。然后,它为源单词 wrt 生成翻译。这些相关位置的上下文向量和先前共同生成的单词。

图 3。没有注意机制的编码器-解码器框架。照片由作者拍摄。

RNNSearch 包括一个双向递归神经网络(BiRNN)作为其编码器和一个解码器,以在解码翻译时模仿从源句子的搜索(见图 4)。

输入(从 X _1 到 X _T)被馈入前向 RNN 以产生前向隐藏状态。

反向 RNN 以相反的顺序读取输入(从 X _T 到 X _1),导致反向隐藏状态。

该模型通过连接前向和后向隐藏状态来为 X _i 生成注释,得到 h _i

情商。1

另一个递归神经网络(RNN)和一个注意块组成解码器。注意块计算上下文向量 c ,其表示当前输出和整个输入之间的关系。然后,上下文向量 c _t 被计算为每个时间步长的隐藏状态 h _j 的加权和:

情商。2

α _tj 是每个 h _j 注释的注意力权重,计算如下:

情商。3

e _tj 是,

情商。4

其中 a对齐模型,其表示注释 h _j 对于下一个隐藏状态 s _t 的适合程度,考虑到前一个状态 s _t-1。

其中 s _i 计算如下:

情商。5

然后模型在当前步骤产生最可能的输出 y _t:

情商。6

直观地,通过使用该公式,该模型可以在每个时间步长选择性地聚焦于输入序列的重要部分,并将源句子分布在整个序列中,而不是固定长度的向量。

图 4。注意机制。照片由作者拍摄。

图 4。说明了注意机制,并将基础数学与模型可视化相结合。

2.2 注意机制中的键、查询和值到底是什么?

在 Bahdanau 模型被用于机器翻译之后,研究者们设计了各种各样的注意机制。一般来说,其中有两个主要的共同步骤。第一个是计算输入上的注意力分布,下一个是基于该分布计算上下文向量。在计算注意力分布的过程中。

在计算注意力分布的第一步中,注意力模型从源数据中推断出关键字(【K】),根据任务,注意力分布可以是各种形式和表示。例如,K 可以是文本或文档嵌入、图像特征的一部分(图像的一个区域)、或者序列模型的隐藏状态(RNN 等)。).Query, Q ,是 vector,matrix【7】的另一个术语,或者是你要计算关注度的两个 vector,简单来说就是类似于 RNNsearch 中的 s _t-1(之前的输出隐藏状态)。目标是在生成下一个输出之前,通过评分函数 f (也称为能量函数和兼容性函数)计算出 Q 和所有 K s 之间的关系(权重),以计算出表示 Q 相对于所有 K s 的重要性的能量分数

情商。7

有几个计分函数计算 Q s 和 K s 之间的联系。一些常用的计分函数如表 1 所示。加法注意力【6】和乘法注意力(点积)【8】是使用最广泛的函数。

表 1。可学习的参数有 k,v,b,W,W2,W3;d_k 是输入向量(键)的维数;act 是非线性激活函数(即 tanh 和 ReLU)。表作者作者

在下一步中,这些能量分数被馈送到名为 g 的注意力分布函数中,就像 RNNsearch 中的 softmax 层一样,以通过将所有能量分数归一化为概率分布来计算注意力权重 α

情商。8

softmax(z) 函数有缺点,即它为 z 的每个元素产生一个结果,这在需要稀疏概率分布的情况下会导致计算开销。因此,研究人员提出了一种新的概率分布函数,称为稀疏最大值【9】,它能够将零值分配给输出中的不相关元素。此外,logistic sigmoid【10】被提出,其将能量分数缩放到[0–1]范围。

与其在上下文向量和注意力分布的输入数据表示中使用关键字(这会使训练过程变得困难),不如使用另一个名为 V 的特征表示向量,并显式地分离这些特征表示。更具体地说,在键值注意机制中, KV 是同一输入数据的不同表征,在自我注意的情况下,所有的 KQV 都是同一数据即输入的分离嵌入。

在计算注意力权重之后,上下文向量被计算为

情商。9

其中,通常是的加权和,表示为一个向量:

情商。10

情商。11

其中 Z_i元素的加权表示,n 表示 Z 的大小。**

简而言之,公共注意机制“”可以被描述为将查询和一组键-值对映射到输出,其中查询、键、值和输出都是向量。输出被计算为值的加权和,其中分配给每个值的权重由查询与相应键的兼容性函数来计算。"如 Vaswani 等人[7]所述。

至于评估注意机制,研究者通常将它们嵌入深度学习模型中,并测量它们在注意和不注意情况下的表现,以评估注意机制的效果,即消融研究。它们可以如图 5 所示被可视化,这又可以用于评估,尽管它是不可量化的。

图 5。yelp 评论上的注意力分布。颜色越深,表示关注度越高。照片来自【27】

3.注意机制的分类

到目前为止,我已经完成了注意力机制的数学建模。在这一节中,我将提供关于这些机制的分类的更多细节。尽管注意机制有相似的基本概念,但它们在细节上有很大的不同,以至于在不同的任务中应用它们时会有很大的困难。到目前为止已经提出了多个分类法,基本都差不多。这里我就用牛等人 2021 [6]和 Chaudhari 等人 2021 [11]提出的模型。

表二。显示了注意力机制分类的不同标准。*“Aoft”的正确形式是“Soft”。表作者作者

请注意,不同标准中的注意机制并不相互排斥,它们很可能包括不同的标准组合。

3.1.注意力的柔和

注意力的柔和度可以分为四种类型:

  • ****软:使用所有键的加权平均值来构建上下文向量。
  • Hard :从随机采样的键中计算上下文向量。
  • ****局部:软注意一个窗口周围的一个位置。
  • ****全局:类似软注意。

在软注意中——首先由 Bahdanau 等人[5]提出——注意模块相对于输入是可微分的,因此,整个模型可通过标准反向传播方法训练。

另一方面,由徐等人 2015 [12]首先提出的硬注意从随机采样的键计算上下文向量。相应的,Eq 中的 α 。8 的计算公式为:

情商。12

由于随机抽样,与试图在每一步计算所有注意力权重的软注意相比,硬注意在计算上花费较少。显然,对输入特征做出困难的决定有其自身的缺点,例如不可微性,这使得难以优化。因此,整个模型需要通过最大化近似变分下限或加强来优化。

随后,Luong 等人 2015 [13]提出了对机器翻译的局部和全局关注。如前所述,全局注意和软注意是相似的机制;然而,局部注意可以被看作是硬注意和软注意的混合,我的意思是它使用输入特征的子集而不是整个向量。与软或全局注意相比,这种方法降低了计算复杂度,并且与硬注意不同,它是可微分的,这使得它易于实现和优化。

3.2.输入特征的形式

  • 逐项
  • 地理位置

在前一种情况下,输入表征是一系列显式项目,或者等价地是来自输入的编码。例如,Bahdanau 等人[5]在 RNNsearch 中使用单个单词嵌入,而在 SENet 中使用单个特征图。注意力模型将这些项目编码为单独的代码,并在解码过程中计算它们各自的权重。当与软/硬注意机制结合时,在项目式软注意的情况下,它计算每个项目的权重,然后将它们线性结合;当处于硬注意状态时,它根据概率随机选择一个或多个项目。

后者试图处理难以确定项目离散定义的输入特征,即视觉特征。一般来说,它们通常用于视觉任务;解码器在每一步处理输入的多分辨率裁剪,在一些工作中,它将任务相关区域转换为规范的预期姿势,为整个模型中更容易的推理铺平道路。在结合位置方式和软注意时,整个特征图被输入到模型中,并产生变换的输出。当我们把它和努力的注意力结合起来,最可能的次区域 WRT。注意模块是随机选择。

表 3。显示了不同类别之间不同的可能组合。表作者作者

3.3.输入表示

注意机制有多种形式的输入表征,其中一些较为常见,如 Chaudhari et al. 2019 [11]提出的区别注意,它:

  • 包括单个输入和相应的输出序列
  • 键和查询属于两个独立的序列

共同注意,以及接受多输入的分层注意模型、如鲁等 2016 [14]提出的视觉问答任务中。共同注意有两种方式:a)并行:同时产生视觉和问题注意;b)交替:在两种注意之间顺序交替。

范等 2018 [15]提出了细粒度粗粒度注意模型。他们使用其他输入的嵌入作为每个输入的查询来计算注意力权重。另一方面,细粒度模型计算输入的每个元素对另一个输入的元素的影响。

有几个成功的使用案例,其中他们部署了共同关注,如情感分类,文本匹配,命名实体识别,实体消歧,情感原因分析和情感分类。

【自我(内在)注意是另一个仅使用输入来计算注意权重的模型,由王等人提出【16】。键、查询和值是同一输入序列在不同空间的表示;自我注意效率已经被一些研究者以不同的方式复制,其中 Transformer [7]广受好评,它是第一个只使用自我注意而不使用 RNNs 的序列转导模型。

为了将注意力扩展到其他层次的输入嵌入,Yang 等人[17]提出了用于文档分类的分层注意力模型(HAM)** 。它使用两个不同层次的注意力:单词层次,允许 HAM 将相关单词聚合到一个句子中;句子层次,允许它将关键句子聚合到文档中。研究人员甚至将其扩展到更高的(用户)级别;在文档级别使用它。另一个极端是赵和张[18]提出的自上而下的方法。一些作品也给计算机视觉带来了层次化的注意,在这些作品中,他们将计算机视觉用于物体层面和部分层面的注意。这也是第一个不使用额外信息来计算注意力权重的图像分类方法。**

图 6。分级注意和共同注意的例子。图片来自【14】

3.4.输出表示

注意力模型分类的另一个标准是它们表示输出的方式。单输出注意是一种众所周知的方法,它在每个时间步中输出一个且仅一个向量作为其能量分数。虽然单一输出是在各种情况下使用的常用方法,但一些下游任务需要更全面的上下文才能完成。因此,研究人员提出了其他方法,如多头多维注意,它们属于多输出注意模型的范畴。举例来说,已经表明在计算机视觉任务以及一些基于序列的模型中,单个输出注意可能不恰当地表示输入的上下文。Vaswani 等人[7]在 Transformer 中,将输入向量( KQV )线性地投影到多个子空间中,随后是缩放的点积,然后使用多头注意力将它们串联起来,如图 7 所示,这允许模型基于不同位置的几个表示子空间同时计算注意力权重。

图 7。多头注意。照片来自【7】

为了进一步加强多头注意力权重的多样性,不一致正则化被添加到子空间、参与位置和输出表示中;因此,不同的头部更有可能以更独特的方式表现特征。

接下来,多维注意力被设计成通过使用矩阵而不是权重得分向量来计算 K 的特征得分向量。它允许模型从相同的数据中推断出几个注意力分布。它可以帮助解决自然语言理解领域中一个名为多义性的突出问题,其中表示缺乏表示同一单词或短语的多种含义共存的能力。

图 8。多维度关注。图片来自【19】

与之前一样,研究人员添加了惩罚(即 Frobenius 惩罚),以鼓励该模型从输入中学习更多不同的特征,并通过将其应用于几个任务(如作者简介、情感分类、文本蕴涵和远程监督关系提取)来证明其有效性。

4.研究前沿和挑战

尽管注意机制已被广泛应用于多个研究方向,但仍有很大的潜力和回旋余地。研究人员面临的一些挑战如下:

  • 如朱等[20]和 Tay 等[21]所示,将【K】(按键)和【Q】(查询)结合起来,取得了突出的性能。因此,在自我关注中组合键、查询和值是否有益仍然是一个问题。**
  • Tsai 等人[22]和 Katharopoulos 等人[23]最近的一些研究表明,注意力模型的性能可以通过降低注意力函数的复杂度来显著提高,因为这极大地影响了它们的计算复杂度。
  • 将为特定任务(如 NLP)设计的注意力模型应用到其他领域(如计算机视觉)也是一个有前途的方向。举例来说,当自我注意应用于计算机视觉时,它会提高性能,同时对效率产生负面影响,如 Wang 等人所证明的[24]。
  • 致力于结合适应性机制和注意机制可能导致类似于没有任何明确的架构设计的分级注意结果。
  • 设计评估模型的新方法也非常重要。Sen 等人[25]提出了多种评估方法,使用新的注意力地图相似性度量来定量测量人脑和神经网络中注意力之间的相似性。
  • 记忆建模正在成为深度学习研究的一种趋势。普通模型大多缺乏外显记忆,因此,对记忆的注意可以进一步研究。神经图灵机论文[26]是这些混合模型的一个例子,有潜力被更细致地探索。

4.1 协作

如果你有兴趣研究我个人网站 上提到的 话题,欢迎进一步讨论。请给我留言!让我们在 LinkedInTwitter 上连线。

跟随这个媒体页面,并查看我的 GitHub 以跟上未来的内容。同时,玩得开心!

另外,看看这篇关于变形金刚的文章:

** **

5.结论

在这篇文章中,我主要从各个方面讨论了注意力模型,如注意力和人脑的简要概述;它在深度学习中的用例;基础数学;提出了注意机制的统一模型(来自牛等[6]);他们的分类法基于一系列标准,概述了有待探索的多个领域。

不用说,注意力机制是通往人工智能道路上的一个重要里程碑。它们彻底改变了机器翻译、文本分类、图像字幕生成、动作识别、语音识别、计算机视觉和推荐。因此,他们在现实世界的应用中取得了巨大的成功,如自动驾驶、医学、人机交互、情感识别等。

6.参考

[1] J.K. Tsotsos, S.M. Culhane, W.Y.K. Wai, Y. Lai, N. Davis, F. Nuflo, Modeling
visual attention via selective tuning, Artif. Intell. 78 (1995) 507–545.
[2] S. Hochreiter, J. Schmidhuber, Long short-term memory, Neural Comput. 9 (1997) 1735–1780.
[3] J. R. Anderson, 2005, Cognitive Psychology and Its Implications, Worth Publishers, 2005.
[4] Lu, S., Lim, JH. (2012). Saliency Modeling from Image Histograms. In: Fitzgibbon, A., Lazebnik, S., Perona, P., Sato, Y., Schmid, C. (eds) Computer Vision — ECCV 2012. ECCV 2012. Lecture Notes in Computer Science, vol 7578. Springer, Berlin, Heidelberg.
[5] D. Bahdanau, K. Cho, Y. Bengio, Neural machine translation by jointly learning to align and translate, in: ICLR.
[6] Z. Niu, G. Zhong, H. Yu, A review on the attention mechanism of deep learning, Neurocomputing, Volume 452, 2021, pp. 78–88.
[7] A. Vaswani, N. Shazeer, N. Parmar, J. Uszkoreit, L. Jones, A. N. Gomez, L. Kaiser, I. Polosukhin, Attention is all you need, in: NIPS, pp. 5998–6008.
[8] D. Britz, A. Goldie, M. Luong, Q.V. Le, Massive exploration of neural machine translation architectures, CoRR abs/1703.03906 (2017).
[9] A.F.T. Martins, R.F. Astudillo, From softmax to sparsemax: A sparse model of attention and multi-label classification, in: ICML, Volume 48 of JMLR Workshop and Conference Proceedings, JMLR.org, 2016, pp. 1614–1623
[10] Y. Kim, C. Denton, L. Hoang, A.M. Rush, Structured attention networks, arXiv: Computation and Language (2017)
[11] S. Chaudhari, V. Mithal, G. Polatkan, R. Ramanath, An Attentive Survey of Attention Models, ACM Transactions on Intelligent Systems and TechnologyVolume, 2021 Article No.: 53, pp 1–32.
[12] K. Xu, J. Ba, R. Kiros, K. Cho, A.C. Courville, R. Salakhutdinov, R.S. Zemel, Y. Bengio, Show, attend and tell: neural image caption generation with visual attention, in: ICML, Volume 37 of JMLR Workshop and Conference Proceedings, JMLR.org, 2015, pp. 2048–2057.
[13] T. Luong, H. Pham, C.D. Manning, Effective approaches to attention-based neural machine translation, in: EMNLP, The Association for Computational Linguistics, 2015, pp. 1412–1421.
[14] J. Lu, J. Yang, D. Batra, D. Parikh, Hierarchical question-image co-attention for visual question answering, in: NIPS, pp. 289–297.
[15] F. Fan, Y. Feng, D. Zhao, Multi-grained attention network for aspect-level
sentiment classification, in: EMNLP, Association for Computational Linguistics, 2018, pp. 3433–3442.
[16] B. Wang, K. Liu, J. Zhao, Inner attention-based recurrent neural networks for answer selection, in: ACL (1), The Association for Computer Linguistics, 2016.
[17] Z. Yang, D. Yang, C. Dyer, X. He, A.J. Smola, E.H. Hovy, Hierarchical attention networks for document classification, in: HLT-NAACL, The Association for Computational Linguistics, 2016, pp. 1480–1489.
[18] S. Zhao, Z. Zhang, Attention-via-attention neural machine translation, in:AAAI, AAAI Press, 2018, pp. 563–570.
[19] J. Du, J. Han, A. Way, D. Wan, Multi-level structured self-attentions for
distantly supervised relation extraction, in: EMNLP, Association for
Computational Linguistics, 2018, pp. 2216–2225.
[20] X. Zhu, D. Cheng, Z. Zhang, S. Lin, J. Dai, An empirical study of spatial attention mechanisms in deep networks, in: 2019 IEEE/CVF International Conference on Computer Vision, ICCV 2019, Seoul, Korea (South), October 27–November 2, 2019, IEEE, 2019, pp. 6687–6696.
[21] Y. Tay, D. Bahri, D. Metzler, D. Juan, Z. Zhao, C. Zheng, Synthesizer: rethinking self-attention in transformer models, CoRR abs/2005.00743 (2020).
[22] Y.H. Tsai, S. Bai, M. Yamada, L. Morency, R. Salakhutdinov, Transformer dissection: An unified understanding for transformer’s attention via the lens of the kernel, in: K. Inui, J. Jiang, V. Ng, X. Wan (Eds.), Proceedings of the 2019 Conference on Empirical Methods in Natural Language Processing and the 9th International Joint Conference on Natural Language Processing, EMNLP- IJCNLP 2019, Hong Kong, China, November 3–7, 2019, Association for Computational Linguistics, 2019, pp. 4343–4352.
[23] A. Katharopoulos, A. Vyas, N. Pappas, F. Fleuret, Transformers are rnns: Fast autoregressive transformers with linear attention, CoRR abs/2006.16236
(2020).
[24] X. Wang, R. B. Girshick, A. Gupta, K. He, Non-local neural networks, in: CVPR, IEEE Computer Society, 2018, pp. 7794–7803.
[25] C. Sen, T. Hartvigsen, B. Yin, X. Kong, E.A. Rundensteiner, Human attention maps for text classification: Do humans and neural networks focus on the same words?, in: D. Jurafsky, J. Chai, N. Schluter, J.R. Tetreault (Eds.), Proceedings of the 58th Annual Meeting of the Association for Computational
Linguistics, ACL 2020, Online, July 5–10, 2020, Association for Computational Linguistics, 2020, pp. 4596–4608.
[26] A. Graves, G. Wayne, I. Danihelka, Neural Turing Machines, arXiv preprint: Arxiv-1410.5401.
[27] Z. Lin, M. Feng, C.N. dos Santos, M. Yu, B. Xiang, B. Zhou, Y. Bengio, A
structured self-attentive sentence embedding, in: ICLR (Poster), OpenReview.
net, 2017.

再培训,还是不再培训?具有梯度推进的在线机器学习

原文:https://towardsdatascience.com/retrain-or-not-retrain-online-machine-learning-with-gradient-boosting-9ccb464415e7

Scikit-Learn 中持续学习的改装策略比较

VD 摄影Unsplash 上拍摄

训练机器学习模型需要精力、时间和耐心。聪明的数据科学家组织实验,并对历史数据进行跟踪试验,以部署最佳解决方案。当我们将新的可用样本传递给我们的预构建机器学习管道时,可能会出现问题。在预测算法的情况下,记录的性能可能偏离预期的性能

差异背后的原因多种多样。除了技术错误之外,最常见也最令人担忧的原因是数据漂移。从标准分布转变到偷偷摸摸的多元和概念漂移,我们必须准备处理所有这些情况。

在本帖中,我们不关注如何检测数据漂移。我们尝试概述如何应对数据漂移。最近已经引入了许多有趣的工具和新奇的技术来促进数据漂移检测。那很酷,但是之后我们能做什么呢? “改装就是你需要的一切” 是用来处理这种情况的最广为人知的口号。换句话说,当新的标记数据变得可用时,我们应该让我们的模型不断地从中学习新的见解。

对于在线机器学习,我们指的是一个多步骤的训练过程,以允许我们的算法动态地适应新的模式。如果制作得当,它可能会比从头再训练提供更大的好处(在速度和性能方面)。这正是我们在这篇文章中想要测试的。

实验设置

我们设想在一个流环境中运行,在这个环境中,我们可以定期访问新的标记数据,计算感兴趣的指标,并重新训练我们的预测模型。

我们模拟了一个概念漂移的场景。我们有一些特征可以保持稳定的分布,并且随着时间的推移保持不变。我们的目标是这些特征的线性组合。每个单一特征对目标的贡献是动态的,并且不是随时间恒定的。

左:模拟特征。右图:模拟系数(图片由作者提供)

在这种情况下,使用一段时间前训练的相同预测模型可能是无用的。让我们研究一下我们可以选择的方案。

改装是你所需要的

为了了解目标和特征之间的关系,我们需要经常更新我们的模型。从这个意义上来说,我们有不同的战略可以选择。

我们可以采用状态学习,在这里,我们以一些预定义的时间间隔初始化,用我们处理的数据从头开始训练。我们要做的就是将新样本和历史样本合并。由于我们重新创建了先前拟合的模型,因此不需要存储它。

有状态学习(图片由作者提供)

状态学习的变体它是加权状态学习。它在于给予最新的观察更高的权重。这可能有助于衡量更多的最新数据,并使新模型专注于最新的模式。进行加权训练很简单。许多最新的机器学习算法实现提供了内置的可能性,为每个样本赋予不同的权重。

加权状态学习(图片由作者提供)

另一方面,我们可以考虑连续学习,又名在线机器学习。在连续学习中,我们使用先前的模型知识来初始化新的训练步骤。我们采用新的可用样本集,并使之前拟合的模型从中学习新的模式。通过更新(而不是重新初始化)模型知识,我们希望获得更好的性能,降低从头开始训练的成本。

持续学习(图片由作者提供)

实践中的在线机器学习

所有基于神经网络的算法都支持在线机器学习。我们可以随时通过传递新数据来更新样本损失,从而继续训练过程。

实事求是地说,在 scikit-learn 生态系统中,所有支持***partial_fit***方法的算法都可以进行持续学习。在下面的代码片段中,我们介绍了如何用几行代码就能做到这一点。

cv = TimeSeriesSplit(n_splits, test_size=test_size)for i,(id_train,id_test) in enumerate(cv.split(X)):
    if i>0:
        model = model.**partial_fit**(
            X[id_train[-test_size:]], y[id_train[-test_size:]]
        )
    else:
        model = SGDRegressor(**fit_params).fit(
            X[id_train], y[id_train]
        )

回到我们的实验,我们使用一个**SGDRegressor**在我们的模拟数据上测试三个提到的训练策略(状态学习、加权状态学习、连续学习)。我们不会只做一次,而是通过模拟不同的场景来做多次,以更好地处理模拟过程中的可变性。我们定期评估 20 个周期的模型,并存储所有模拟场景的预测误差(计算为 SMAPE)。

SGDRegressor 在线机器学习性能(图片由作者提供)

我们可以看到,与其他策略相比,持续学习可以实现最佳性能。考虑到最近的观察结果,加权状态学习也可以比标准状态学习做得更好。

这些结果听起来很有希望。有没有可能用其他算法做在线机器学习?我们知道基于树的梯度推进的巨大威力。由于它们在各种情况下的适应性,许多机器学习项目都使用它们。如果能和他们一起操作在线机器学习,那就太好了。

希望我们能做到!和前面的例子一样简单。我们报告了一个片段,其中我们介绍了如何使用**LGBMRegressor**来实现它。

cv = TimeSeriesSplit(n_splits, test_size=test_size)for i,(id_train,id_test) in enumerate(cv.split(X)):
    if i>0:
        model = LGBMRegressor(**fit_params).fit(
            X[id_train[-test_size:]], y[id_train[-test_size:]],
            **init_model = model.booster_**
        )
    else:
        model = LGBMRegressor(**fit_params).fit(
            X[id_train], y[id_train]
        )

让我们在模拟场景中看看它的实际应用。

LGBMRegressor 在线机器学习性能(图片由作者提供)

我们取得了和以前一样令人满意的结果。如果处理得当,在线机器学习听起来是有效的,并且可用于不同的算法

摘要

在这篇文章中,我们介绍了在线机器学习的概念。我们探索了不同的有状态改装策略,将它们与持续学习方法进行比较。在线机器学习在某些应用中被证明是一种很好的测试方法。做在线机器学习有点艺术。人们并不认为这可能会提高成绩。高使模型忘记它所学的东西是有风险的( 灾难性遗忘 )。从这个意义上来说,拥有一个可靠且充分的验证策略比以往任何时候都更加重要。

如果你对题目感兴趣,我建议:

查看我的 GITHUB 回购

保持联系: Linkedin

检索变压器增强强化学习

原文:https://towardsdatascience.com/retrieval-transformer-enhanced-reinforcement-learning-24509e97c4c6

将检索变形金刚与我们的游戏 AI Chappie 相结合

检索章节图|按作者分类的图片

大家好,如果你一直关注我以前的博客帖子,你就会知道我最近的强化学习 (RL)代理(用离线强化学习下棋用广义 AI 下棋)中一直在利用变形金刚。最近,一种被称为检索变压器的新型变压器因其高效而越来越受欢迎。此外,像 OpenAIWebGPTDeepMindRETRO Transformer 这样的模型已经证明,这些较小的模型可以与大型模型如 GPT-3 相媲美。由于这些有希望的结果,我们将跟上最近的趋势,并尝试将这种类型的模型整合到我们的 RL 代理中,这似乎是合乎逻辑的。

检索变压器

在我们的 RL 代理中加入检索转换器之前,让我们先看看什么是检索转换器,以及为什么它如此有效。下面是从 DeepMind 的一篇博客文章中引用的一段话,其中他们描述了复古变形金刚所用方法背后的推理。

“受大脑在学习时如何依赖专用记忆机制的启发,RETRO 有效地查询文本段落以改善其预测。”[10]

这句话暗示了我们的大脑在根据过去的经验做决定时是如何利用记忆作为框架的。例如,当你把某物抛向空中时,你期望它落地,因为你的大脑记得这个规则;

“上升的必然下降。”艾萨克·牛顿[12]

DeepMind 通过利用外部数据库中存储的彼此非常相似的示例,重新创建了这种内存框架思想。为了确定与输入数据最相似的数据,逆向变换器确定 l-1 个输入数据块嵌入的 k-最近邻(KNN)。

k 近邻方程|作者图片

邻居和输入数据使用单独的自关注编码器块进行编码,并通过分块的交叉关注解码器块进行组合,从而为其提供类似于标准变换器的编码器-解码器架构。

分块交叉注意是交叉注意的一种形式,其中我们将查询(Q)、键(K)和值(V)分成更小的块,然后对这些块中的每一个执行交叉注意。在组块交叉注意中,我们不在每个输入组块和它的相邻组块之间执行交叉注意。

不正确的分块交叉注意层匹配图|作者图片

相反,我们将输入数据移动 m-1,并创建 l-1 个新的输入块。这些新创建的输入块包含来自前面的原始输入块的最后一个标记和它的原始输入块的后面的 m-1 个标记。

分块交叉注意力层匹配图|作者图片

然后,我们将最初丢弃的 m-1 个标记添加到交叉注意输出中。通过预先考虑 m-1 个标记,我们保留了更多来自原始输入数据的信息,这一点至关重要,因为我们希望模型的预测受输入数据的影响大于 KNN。

最后,我们在最后一个块中的剩余标记和最后一个邻居之间执行交叉关注。

分块交叉注意力图|图片来自https://arxiv.org/pdf/2112.04426.pdf

分块交叉注意的结果通过前馈层传递。

复古图|图片由 https://arxiv.org/pdf/2112.04426.pdf制作

建造我们的回收变压器

现在我们更好地理解了检索转换器,我们可以在我们的模型中有效地利用它的组件。

首先,我们将建立组块交叉注意模型。由于我们已经在之前的博客文章中创建了一个注意力模型,我们可以在这里重复使用。这里我们需要做的主要事情是创建一个分块机制来传递我们的邻居并输入到我们的注意力模型中。

现在我们已经建立了组块交叉注意模型,我们将创建我们的 KNN 函数。这个函数从潜在的相当大的样本空间中确定邻居,使得循环非常耗时(O(n))。为了优化这个函数,我们将对它进行矢量化,以消除遍历每个数据点的需要。首先,我们将把我们的数据框架转换成一个张量,这将允许使用矩阵计算。矩阵计算显示,与熊猫应用函数相比,性能有所提升。

k 近邻函数比较|作者图片

下面是我们的 KNN 函数的矩阵计算版本的代码。

新创建的 KNN 函数和分块的交叉注意层让这个模型看起来有所不同。下面是我们新模型架构的示意图。

新模型架构图|作者图片

看上面的图表,你会注意到我们在主干层之后添加了新的分块交叉注意层。这种放置是有意的,因为我们的 KNN 函数在单个矩阵上执行查找。将我们的分块交叉注意层放在主干层之后的另一个好处是主干层编码中表示的数据量。这些编码在我们的模型中是数据最丰富的,允许找到的邻居为最终层提供更好的框架。

最后一步,我们需要为外部嵌入数据库构建管道。

嵌入式数据库管道图|作者图片

管道是离线运行的,因为我们只需要在模型主干更新时重新计算嵌入。下面是这条管道的代码。

谢谢

现在你知道了,我们已经成功地升级了我们的国际象棋人工智能,以利用检索变压器的组件。你可以在我的 GitHub 上查看完整版本的代码。

感谢阅读。如果你喜欢这样,可以考虑订阅我的账户,以便在我最近发帖时得到通知。

参考

使用 Python 对 Spotify 包装的人工智能进行逆向工程

原文:https://towardsdatascience.com/reverse-engineering-spotify-wrapped-ai-using-python-452b58ad1a62

查尔斯·德鲁维奥在 Unsplash 上拍摄的照片

实践教程

使用 Python 对 Spotify 包装的人工智能进行逆向工程

揭开年度最酷事件背后的神秘面纱

我远不是一个优秀的音乐家,但我确实热爱 T2 音乐。另一方面,我是一名专业研究人员,我的工作是构建人工智能模型。

我想你们都已经知道为什么我这么喜欢 Spotify。Spotify 基本上是我热爱的许多事物的总和,因为它将令人难以置信的人工智能技术应用于音乐世界。要让某人相信这些技术是不可思议的,只需打开应用程序,播放一首他们推荐的歌曲。你会意识到他们的推荐系统是准确的,而且…几乎是神奇的。

1.介绍 Spotify Wrapped

另一个由瑞典大公司提供的魔术活动是 Spotify 包装的

我做的截图,来自这里

自 2016 年以来,Spotify 决定使用你生成的所有统计数据来给你回顾你的最后一年音乐。在这个摘要中,您可以找到基本信息,如您的热门流派、热门艺术家和热门歌曲。

2021 包裹体验中真正突出的一点是所谓的音频光环

我做的截图从这里

我建议你访问链接网站以了解更多信息,但简而言之,Spotify 团队能够将你的音乐品味与颜色相匹配。

他们是这样做的:

我从这里做的截图

很酷,不是吗?这是我的气场。

我做的截图

但是人工智能在哪里呢?

嗯,人工智能在他们用来检测歌曲是否“平静”(绿色)、“乐观”(粉色)、“情绪”(蓝色)等的工具中。
我在这本笔记本中试图揭示的是Spotify 如何对歌曲的情绪进行分类,并将其与颜色进行匹配。

让我们开始吧。

2.一些提示

我想说实话。我不知道我做的事情是否 100%正确:我不为 Spotify 工作,如果我为 Spotify 工作,我可能没有权利说它是如何工作的。

然而,有一些很好的暗示表明,如果这个过程不是我告诉你们的那样,我可能离真相不远了。

Spotify 的人工智能系统能够为每首歌曲创建 12 个音频特征。例如这篇文章使用这些特征对歌曲流派进行分类。

我从这里做的截图

而另一篇文章以非常详细的方式解释了这些特性,并用它们来玩推荐系统。

例如:

  • 声音度是音轨是否是声音的置信度
  • 可跳性表示一首歌曲有多少可跳性
  • 能量是强度和活动的量度
  • 效价是对音乐积极性的一种衡量

所有这些指标的值都在 0 和 1 之间。

可以说这是 Spotify 最伟大的艺术作品之一。当我们听一首歌时,我们能够确定可跳舞性能量听觉(如果我们学过一点音乐的话),但要建造一台能够为我们做这些事情的智能机器要困难得多

因此,有理由假设,由于这些功能如此复杂且信息量大, Spotify 也会在打包体验中使用它们。事实上,如果我们看看第一章对“情绪”的描述,并将其与上面的特征进行比较,我们可以看到,将颜色与像能量、可舞性和价这样的特征相匹配并不困难。

另一个指向这个方向的很好的暗示是所谓的 Spotify 调色板 ,它根据心情调整功能,并给你一个调色板,给你 Spotify 的可跳舞性、能量。这是我的调色板:

我从这里做的截图

让我们把这个巨大拼图的所有碎片拼在一起:

  • Spotify 有一个 AI,从歌曲中提取出【深度】特征,这是他们自己的秘密。
  • Spotify 有自己的“情绪”定义,这可能与他们的功能有关
  • 奖励 : Spotify 让你能够用一个特定的 API 和一项名为的服务提取这一特性,Spotify 面向开发者

3.逆向工程部分

我们接下来要做的是:

  • 使用 Spotify API 访问我的 Spotify 播放列表,其中包含我在 2021 年播放次数最多的歌曲
  • 提取特征
  • 根据这些特征,理解为什么我最喜欢的音乐情绪是"焦虑""和"冷静"!(见上图)

我们需要做的第一件事是导入一些基本的库+ spotipy ,这是我们用来连接 Spotify 的库。

你需要的第二个东西是 Spotify 上的开发者账户。别担心,它不一定是高级的,而且非常容易创建。然后,您将使用您的客户 ID密码访问:

然后(第三步)我把我所有 100 首听得最多的歌上传到一个公共播放列表(如果你想听一些好的音乐,给一个机会)。在此之后,我提取了所有的跟踪代码,代码如下:

作为第四步,我提取了特征并构建了一个数据框架:

这就是了。

我做的另一件事(只是为了好玩)是绘制对图

我知道这看起来很乱,但是让我们使用关联热图来帮助自己。

例如,我们可以理解能量声音之间的合理的反向相关性,以及响度能量之间的高度相关性。这凸显了一个事实,即这些功能都是经过深思熟虑的,是 Spotify AI 系统的核心元素之一。

但是我们不要分心。他们说我的两种心情是“ 焦虑和“ 寒意”:

我做的截图

在这一点上,对我来说,合理的做法是做一点功能工程,定义一些更接近“愤怒”的东西。考虑到我们所拥有的,我们可以试着将“愤怒”定义为“消极”。鉴于“化合价几乎等于“正性”,化合价是一个介于 0 和 1 之间的数,我们可以将“负性”定义为 1-“正性”:

让我们了解一下这些特性背后的统计数据:

  1. 直方图:

2.柱状图

3.箱线图

4.一些数字:

这是关键时刻。以下是我们可以陈述的一些事情:

A. 舞蹈性和能量特征平均都在 50%以上( 62%63% )

B. 正面特征低( 46% ),而负面(当然)仍然在 50%以上( 53% )。

由于我们的假设是能量可跳舞性价态是 Spotify 在这种情况下使用的三个主要特征,我们可以假设:

1.高数值的舞蹈性是我作为一个顶级舞者所拥有的“寒冷”情绪的原因

2.高值的能量消极性(或低值的化合价)是造成焦虑情绪的原因。

4.结论

我不知道逆向工程过程是否正确,因为我又不为 Spotify 工作。

我知道的是:

1.这些假设对我来说似乎是合理的,所以我认为如果它不完全正确,我可能已经对它的工作原理有了一些大概的了解

2.如果它是完全错误的,至少我在工作中获得了很多乐趣,这是目前对我来说最重要的事情,😄

如果你喜欢这篇文章,你想知道更多关于机器学习的知识,或者你只是想问我一些你可以问的问题:

A.在 Linkedin 上关注我,在那里我发布我所有的故事
B .订阅我的 简讯 。这会让你了解新的故事,并给你机会发短信给我,让我收到你所有的更正或疑问。
C .成为 推荐会员 ,这样你就不会有任何“本月最大数量的故事”,你可以阅读我(以及成千上万其他机器学习和数据科学顶级作家)写的任何关于最新可用技术的文章。

再见:)

如何更有效地反转 Python 列表

原文:https://towardsdatascience.com/reverse-python-list-ad10ad408021

Python 中列表的高效反转

Photo by 愚木混株 cdd20 on Unsplash

列表是 Python 中最基本和最常用的数据结构之一。列表是可变的有序对象集合,也可以存储重复值。它们甚至可以用作队列和堆栈(尽管deque可能更有效)。

列表反转是开发人员在编写 Python 应用程序时执行的一项相当常见的任务。在今天的简短教程中,我们将演示反转列表的几种不同方法,并讨论哪种方法执行得更好,尤其是在处理包含大量数据的列表时。

使用负步长的切片

现在让我们假设我们有以下由一些数值组成的列表:

>>> my_lst = [10, 0, 30, 25, 40, 100, 80]

我们可以使用切片和步长 1 来反转列表。

>>> my_lst_reversed = my_lst[::-1]
>>> my_lst_reversed
[80, 100, 40, 25, 30, 0, 10]

[::-1]符号主要做两件事:

  • 它选择列表中的所有元素(第一个:)
  • 它指定负的步长为 1,这意味着以相反的顺序(从结束到开始)检索元素

如果你想了解更多关于 Python 中切片和索引的知识,你可以参考我以前的一篇文章。

请注意,这种方法看起来更像 Pythonic,但是只有当您想要创建原始列表的副本,以便它包含逆序的元素时,才应该使用这种方法。

如果您想就地反转一个列表(或者迭代反转列表的元素),那么有两个其他的选择,它们比使用负步长进行切片要有效得多。

使用 list.reverse()方法

如果您正在考虑就地反转列表的元素(这意味着您实际上不想创建原始列表的另一个副本),那么list.reverse()方法是最有效的方法。

>>> my_lst = [10, 0, 30, 25, 40, 100, 80]
>>> my_lst.reverse()
>>> my_lst
[80, 100, 40, 25, 30, 0, 10]

注意,由于reverse()方法就地发生,我们不需要将操作的结果赋回给变量。除了性能影响之外,我还发现这种方法可读性更好,并且清楚地表明列表被颠倒了。

使用 reversed()函数

最后,另一种选择是返回反向迭代器的[reversed()](https://docs.python.org/3/library/functions.html#reversed)内置函数。

返回一个反向[iterator](https://docs.python.org/3/glossary.html#term-iterator)

seq 必须是具有__reversed__()方法或支持序列协议的对象(从0开始的__len__()方法和__getitem__()方法,带有整数参数)。

在对列表执行向后迭代时,推荐使用这种方法。

>>> my_lst = [10, 0, 30, 25, 40, 100, 80]
>>> my_lst_reverse_iter = reversed(my_lst)
>>> my_lst_reverse_iter
<list_reverseiterator object at 0x10afcae20>

如你所见,reversed()函数返回了一个迭代器,我们可以对它进行循环:

>>> for element in my_lst_reverse_iter:
...     print(element)
... 
80
100
40
25
30
0
10

请注意,您甚至可以将迭代器转换为列表:

>>> my_lst = [10, 0, 30, 25, 40, 100, 80]
>>> my_lst_reverse = list(reversed(my_lst))
>>> my_lst_reverse
[80, 100, 40, 25, 30, 0, 10]

但是如果这是你的最终目标,我个人会选择list.reverse()方法。

最后的想法

在今天的简短教程中,我们演示了在 Python 中反转对象列表的两种不同方法。更具体地说,我们展示了如何使用步长切片、listreverse()方法以及内置的reversed() Python 方法来实现这一点。

总结一下,

  • 如果您想就地反转列表,请选择list.reverse()
  • 如果您想以相反的顺序创建列表的副本,请使用负步长(即[::-1])进行切片
  • 如果你想遍历一个反向列表,使用reversed()函数

成为会员 阅读介质上的每一个故事。你的会员费直接支持我和你看的其他作家。你也可以在媒体上看到所有的故事。

https://gmyrianthous.medium.com/membership

相关文章你可能也喜欢

深度学习中处理数据量挑战的最新进展

原文:https://towardsdatascience.com/review-of-recent-advances-in-dealing-with-data-size-challenges-in-deep-learning-ac5c1844af73

思想和理论,深度学习中的数据

深度学习中处理数据量挑战的最新进展

深度学习在有效处理不断增长的数据量方面的最新进展综述

如今,机器学习和深度学习社区中的能量和兴奋是有感染力的。这个领域正在发生如此多的突破性进展,但我经常发现自己想知道为什么唯一让这一切发光的东西——是的,我说的是深度学习的黑马**数据如此不受重视。过去几年的 DL 研究给了我很多快乐和兴奋,我现在满怀希望,未来我们可以在这个领域看到一些令人兴奋的进展,探索结合数据的深度学习的进步!在这篇文章中,我总结了深度学习领域最近的一些发展,这些发展让我感到震惊。

本文目录:

  • 深度学习的黑马:数据
  • 标签数据:标签的类型
  • 以数据为中心的常用数据挖掘技术
  • 数据扩充
  • 迁移学习
  • 降维
  • 主动学习
  • 扩展深度学习数据集的挑战
  • 数据相关技术的最新进展
  • 1。正规化
  • 1.1 混淆
  • 1.2 标签平滑
  • 2。压缩
  • 2.1。X-shot 学习:多少才够?
  • 2.2。修剪
  • 2.2.1 核心集
  • 2.2.2 示例遗忘
  • 2.2.3 使用坡度规范
  • 2.3。蒸馏
  • 3。那么,如果你有嘈杂的数据
  • 结论
  • 参考文献

深度学习的黑马:数据

深度学习(DL)算法通过查看训练数据来构建(领域)知识表示,从而学习执行任务。对图像模型的早期研究(分类和分割,2017 年)指出,随着训练数据集的增加,模型的性能以对数形式增加 1 。长期以来,人们一直相信增加训练数据集的大小将会继续提高模型的性能。这也得到了另一项实证研究的支持,该研究在机器翻译、语言建模、图像分类和语音识别中验证了这一信念 2 (见图 1)。

**图 1:显示了泛化误差和数据集大小(对数标度)之间的关系。图片来自乔尔等人的。艾尔22

因此,数据集越大越好,对吗?差不多!以幂律的形式奠定了理论基础,即ε(m)≈αmβg其中 ε 是泛化误差, m 是训练集中的样本数, α 是问题/DL 任务的常数性质, βg 是定义学习曲线陡度的标度指数。这里, βg ,曲线的陡度描述了一个模型通过向训练集 2 添加更多数据可以学习得多好。(见图 2)根据经验, βg 在 0.07 和 0.35 之间,尽管理论上认为 βg 为 0.5 或 1。尽管如此,对数关系仍然成立。如图 2 所示,增益最终以不可减少的误差递减。

图 2:幂律曲线,显示了使用小数据集训练的模型,仅与随机猜测一样好,随着数据集大小的增加而迅速变好,最终稳定在不可减少的误差区域,这由包括导致不完善泛化的不完善数据的因素组合来解释 2 。图片取自乔尔等人。al2

这可归因于几个因素,包括数据的不完善。数据质量和不断迭代的重要性在之前的一些演讲中有所涉及 abc 。数据质量很重要,数据分布也很重要。训练数据集的分布越好,模型就越通用!

**数据肯定是新油!https://www.wired.com/story/no-data-is-not-the-new-oil/

那么,我们能在没有太多抱怨的情况下缩放数据大小吗?请记住,61%的人工智能实践组织已经发现数据和数据相关的挑战是他们的首要挑战 4 。如果围绕数据集的采购、存储、数据质量和分布/人口统计的挑战还没有包含在内,这篇文章将关注另一系列问题。在数据量增长,计算成本和周转时间随数据增长线性增长的情况下,如何高效训练?然后我们开始问有多少数据是多余的,哪些例子更有影响力,我们如何找到它们?鉴于最近的一项调查 4 指出,大约 40%实践人工智能的组织已经在 GPU 和人工智能相关的计算基础设施上每年花费至少 100 万美元,这些是非常重要的问题。这应该关系到我们所有人。不是所有 FAANG 以外的组织(还有,FAANG 中假设的组织,但是省略了缩写!)而那些资产负债表丰厚的公司将能够通过简单地扩展数据集来利用收益。此外,出于环境原因和碳排放影响,这应该关系到我们所有人更多详情

训练一只人工智能的碳足迹高达 284 吨二氧化碳当量——是一辆普通汽车一生排放量的五倍 来源

简单地缩放训练数据集并计算你的幸福的乌托邦状态根本不存在。接下来的问题是,我们在做什么?不幸的是,并不是很多,特别是如果你看看 ML 研究社区在利用大量 GPU 年来获得归因于算法或架构变化的模型性能的微小增加方面的兴奋。但好消息是,这一领域现在正获得越来越多的关注。自 2020 年以来,很少有研究非常有前景,尽管它们还处于起步阶段。我一直在密切关注人工智能(也称为以数据为中心的人工智能)主题中数据使用的文献,因为这是我感兴趣的活跃领域之一。我对这一领域最近的一些发展感到兴奋。在这篇文章中,我将讲述我在这个话题上的一些收获和兴奋。

在详细介绍它们之前,让我们先回顾一下基础知识和先验知识:

标签数据:标签的类型

这篇文章主要关注计算机视觉的监督学习场景。在这个空间中,有两种类型的标签:

  • 硬标签
  • 软标签

传统标签是硬标签,其中真实值是离散值,例如 0 和 1,0 代表否,1 代表是。这些离散值可以是任何值,这取决于数据集是如何管理的。值得注意的是,这些值是绝对的,并且清楚地表明了它们的含义。

有一种新兴的标签形式被称为软标签,其中接地表示可能性。本质上,这些标签是连续的。举个例子,一个像素是 40%猫 60%狗。在接下来的几节中,这将非常有意义。

以数据为中心的常用数据挖掘技术

数据增强和迁移学习是目前深度学习中两种常用的技术,专注于有效地应用数据。这两种技术现在都非常大众化,除非明确省略,否则都被广泛应用。

数据扩充

数据扩充包括多种技术来转换数据点,从而增加数据集的多样性。该技术旨在保持数据分布大致相同,但通过增加多样性来增加数据集的丰富性。主要地,通过这种技术的转化是样品内的。仿射变换、对比度调整、抖动或色彩平衡是数据增强技术的一些例子。Imgaug 和 kornia 对于这样的操作来说是非常好的库,尽管所有的 ML 框架都提供了有限的一组数据扩充例程。

最初提出数据扩充技术是为了提高模型的稳健性和实现更好的泛化,但它们也用作综合增加数据大小的技术。在数据获取确实具有挑战性的情况下尤其如此。如今,数据增强技术已经变得更加复杂和丰富,包括也可以应用模型驱动增强的场景。这方面的一个例子是基于 GAN 的技术来增加和合成样品。事实上,数据扩充也是建立对抗敌对攻击的鲁棒性的技术之一。

增强的例子。图片取自 ImgAug 库 src 示例

迁移学习

迁移学习也是一种非常大众化的技术,如果两个任务的问题域是相关的,它源于在一个新任务中重用所学的表征。迁移学习放宽了训练数据必须与测试数据 5 独立且同分布(i.i.d .)的假设,允许通过从用另一数据集训练的另一学习模型引导模型权重来解决训练数据不足的问题。

图 3:有无迁移学习的训练 6 。图片取自出版物 6 。图片取自 6

使用迁移学习,如果源模型和目标模型的任务有重叠,可以实现更快的收敛。

降维

降维技术也适用于大型数据集:

这些技术分为两类:

  1. 一种是试图保持数据集中所有样本之间的成对距离。主成分分析(PCA)就是一个很好的例子。
  2. 那些在全局距离上保持局部距离的。像均匀流形逼近和投影(UMAP) 23 和 t-分布式随机邻居嵌入(t-SNE) 24 这样的技术就属于这一类。UMAP 可以说保留了更多的全局结构,在算法上比 t-sne 更快。T-SNE 和 UMAP 都使用梯度下降来达到最佳嵌入。

然而,DL 空间中的这些技术主要用于理解数据,也用于可视化目的。UMAP 和 T-SNE 在保持全局结构方面比其他嵌入算法做得更好,但有局限性。这篇博客更详细地讨论了这个话题。

主动学习

主动学习是一种方法,在这种方法中,训练过程主动要求对特定数据进行标记。它在经典的 ML 技术中更常用,但由于反向传播,它在深度学习中并不十分成功。离线或基于池的主动学习已经被大量研究用于深度学习,但没有多少突破性的成功。由于离群值对训练的负面影响,主动学习的使用也不是很直接[25]。基于池的主动学习将在下一节(修剪)中详细介绍。

为深度学习扩展数据集的挑战

除了在上一节中讨论的技术之外,在以数据为中心的人工智能领域还没有进行大量投资。围绕以数据为中心的人工智能的势头最近有所形成,吴恩达通过他的新创业公司的落地推动了以数据为中心的人工智能的努力。AI 。

在我看来,以下是属于以数据为中心的人工智能范围内的一些大类问题:

  1. 如何在数据集快速增加的情况下高效训练?在 2021 年 PyTorch 开发者日期间,Yann LeCun 在与sou Smith chint ala的访谈中呼吁,超过 1 周的培训时间是不可接受的。出于实际原因,这是一个非常好的基线,但如果没有一个庞大的 GPU 舰队供其支配,那么在当前的 DL 实践中,这是很难实现的目标。那么,随着数据集规模的增加,还能做些什么来有效地训练呢?
  2. 所有的样本都同样重要吗?数据集中的样本有多重要?我们能永远利用“重要性因素”吗?
  3. 样本对更好的概括起什么作用?一些样本带有冗余特征,那么当 DL 中的特征不明确时,如何对数据集进行重复数据删除?
  4. 数据大小很重要,但是我们能对数据集中的内容进行战略规划吗?
  5. 巧妙地做到这一点与有效的采样和数据挖掘技术有关。当且仅当我们知道我们的目标是什么时,这些才是容易解决的问题。在我看来,DL 的挑战在于寻找最佳样本?这不是很好理解。
  6. 我们如何利用更多内在的 DL 技术,如目标函数、反向传播和梯度下降,来构建一个流畅而有效的数据集,从而提供最高的投资回报。
  7. 数据集中的噪音被视为邪恶。但是他们总是邪恶的吗?一个人能忍受多少噪音?这个怎么量化?
  8. 如果数据在传统的培训/验证/校准/测试分割中流失,这是多大的罪过。
  9. 对于级联培训场景的数据分割有什么建议?
  10. 在回报开始减少之前,人们能对数据增加有多着迷?
  11. 如果观察到持续学习,如何优化数据的使用?

数据相关技术的最新进展

如果我们认为人类是学习机器,他们有无限的数据可以学习。我们的系统已经发展成有效的策略来解析无限的数据流,以选择我们感兴趣的样本。我们的视觉系统如何利用迅速扫视的眼球运动来执行中心凹注视,以对有趣和有用的数据点进行有效的二次采样,这应该是一个很好的动机。当然,我们有时会失败,因为各种原因,我们看不到桌上的笔,尽管它就在我们面前,但大多数时候我们都打对了。格式塔理论的一些概念已经适用于更好地选择数据,即使机器模型是随机的 T2 鹦鹉。根据这一理论,下面列出的八个主要因素决定了视觉系统如何自动将元素分组为图案。

  1. 邻近性:将彼此靠近的物体或形状视为一组的倾向。
  2. 相似性:如果物理上相似,例如形状、图案、颜色等,倾向于将物体分组。存在。
  3. 封闭性:倾向于看到完整的图形/形式,即使图像中呈现的是不完整的。
  4. 对称性:认为物体对称并围绕一个中心点形成的倾向。50
  5. 共同命运:倾向于将相似的运动联系为共同运动的一部分。
  6. 连续性:倾向于将每个物体视为一个单一的不间断的物体,即连续的物体
  7. 良好的格式塔:如果可以形成一个规则、简单、有序的模式,倾向于聚集在一起
  8. 过去的经验:根据过去的经验对物体进行分类的倾向。

我认为,在这些因素中,邻近性、相似性、共同的命运和过去的经历是相关的。我甚至争论应用闭包的可能性。费尔 22 最近的一项工作表明,通过应用自动编码器常用技术的微小变化,机器模型可以填补空白并正确推断缺失的部分。与基于 GAN 的幻觉技术相比,我之所以如此兴奋地提出这个技术,是因为它比 GAN 更容易建造和训练。

屏蔽-显示模型推断缺失补丁的自动编码器 22图像取自 22

有趣的是,最近在应对大规模数据挑战方面取得的进展很大程度上是受已知深度学习技术的启发,只是它们现在是通过数据的镜头来应用的。例如修剪、压缩、抽样策略,以及利用灾难性遗忘、知识提炼等现象。

技术目前如何用于模型构建提出了以数据为中心的观点运行一种专门的模型压缩技术,通过消除低量级权重来减少大小和计算开销。对泛化贡献不大的样本从训练体系中省略。压缩用于减少大小和计算开销的各种模型压缩技术包括像量化这样的技术,其中预计会有一些信息损失。广泛的数据过滤和压缩技术,在不影响泛化能力的情况下减少数据量。提取从一个更复杂的模型到一个可能更小的模型的学习表示。将较大数据集中的知识提取到较小的综合集中。损失泛函也称为目标函数,是定义问题陈述的 DL 的核心概念之一。如 22 所示,并且还可以更广泛地用于填充数据中缺失的信息。规则化 DL 的一个理论原则是通过各种技术来应用的,如批处理、辍学以避免过度拟合。考虑到数据,采用各种技术确保过度拟合,例如标签平滑 710

表 1:从核心数据挖掘技术混合到以数据为中心的数据挖掘技术的总结

让我们从数据的角度深入了解这些技术是如何应用的:

1.正规化

1.1 混淆

Mixup 是一种特殊形式的数据扩充技术,它超越了样本内修改,探索了样本间修改。混合的想法是将一对样本线性组合(通过凸几何)成一个样本。

x′=λxi+(1-λ)xj,其中,λ∈[0,1]取自贝塔分布,Xi,XJ 为输入/源向量**

y′=λyi+(1-λ)yj,其中 y_i、yj 为独热标签编码**

图 4:在 牛津 Pets 数据集 上应用 mix up7产生的样本。图片作者。**

事实上,Mixup 7 试图调整神经网络,以支持训练示例之间的简单线性行为。如图 5 所示,混合导致更好的模型和更少的遗漏。已经表明,混合增加了泛化,减少了对错误标签的记忆,增加了对对立例子的鲁棒性 78

图 5:显示使用 mixup 7 ,观察到较低的预测误差和较小的梯度范数。图片取自 7

我认为 mixup 不仅仅是一种增强和正则化技术,也是一种数据压缩技术。根据应用混合的频率(比如α),数据集压缩比(Cr)将:

Cr= 1α/2

如果你还没有注意到,应用混合转换标签为软标签。离散值的线性组合将产生连续的标签值,这可以解释前面讨论的例子,其中像素是 40%猫 60%狗(见图 5)。

1.2 标签平滑

标签平滑 10 是正则化技术的一种形式,通过一个非常小的值εɛ来平滑地面真实。这样做的一个动机当然是更好的泛化和避免过度拟合。而另一个动机是阻止模型变得过于自信。 810 都表明,标签平滑导致更好的模型。

I = = k, K 否则,其中 k 为班级人数

由上述等式指示的标签平滑不会导致标签数据中的任何可见差异,因为ɛ非常小。但是,应用混合更改会明显改变源(x)和标签(y)。

应用标签平滑没有明显的区别。图片来自 牛津宠物数据集 。图片作者。

2.压缩

压缩指的是广泛的数据过滤和压缩技术,以在不牺牲泛化能力的情况下减少数据量。以下是这方面一些令人兴奋的最新进展:

2.1.X-shot 学习:多少才够?

由于数据集的增加而导致的高计算成本和长训练时间的问题已经导致了通过少数射击策略进行训练的发展。这种方法背后的直觉是采用一个模型,并指导它仅通过查看几个样本来学习执行一项新任务。这种方法隐含了迁移学习的概念。这条研究路线从仅使用少量样本训练新任务开始,并探索了一次性训练的极端情况,即仅从一个样本学习新任务 1213

最近,一种有趣的基于镜头学习的超级极端方法出现了——又名 LO 镜头学习 11 。这种方法利用软标签概念,并试图将硬标签 N 类样本合并成 M 个样本,其中 M < N,因此名称小于 1!基于 LO 镜头的技术是数据压缩技术的一种形式,可能感觉非常类似于前面讨论的混合技术。然而,与 Mixup 中样本的凸组合相反,LO Shot 利用距离加权的 k-最近邻技术来推断软标签。他们称为distance-weighted soft-label prototype k-Nearest Neighbours (SLaPkNN)的算法实际上是将 k 个最接近目标点 x 的原型的标签向量相加,每个原型的权重与其到 x 的距离成反比。下图显示了使用 SLaPkNN 将 4 个类数据集合并为 2 个样本。

图 LO-Shot: LO Shot 将 4 个类空间分割成 2 个点 11 。图片取自 11

在我的理解中,这是两种技术之间的主要理论差异,另一个差异是 mixup 只使用结合使用λ1-λ从 beta 分布中提取的概率将两个样本合并为一个,而 LO 更通用,可以大大压缩。我并不是说 mixup 不能扩展到更多元的情况,而是说这种方法的实证分析是未知的;而对于 11 来说,已经表明 SLaPkNN 至少可以将 3M 2 类压缩成 M 个样本。

关于这一点的技术解释和代码可以在这里找到。

2.2.修剪

剪枝是压缩技术的一个子类,其中不真正有用或有效的样本被丢弃,而选择的样本保持原样而没有任何内容损失。以下是数据集删减的一些已知技术:

核心集

核心集选择技术涉及从大数据集到几乎近似于给定大集的较小集的子采样。这不是一种新技术,已经使用手工设计的功能和更简单的模型(如马尔可夫模型)进行了大量的探索,以逼近更小的集合。这也不是特定于 DL 的技术,在经典 ML 中也有它的位置。一个例子可以是使用朴素贝叶斯为计算量更大的同类(如决策树)选择核心集。

在深度学习中,使用类似的概念,可以使用更轻量级的 DL 模型作为代理来选择近似数据集 15 。这在不断学习时更容易实现,否则它本身可能是一种非常昂贵的技术,因为代理模型需要首先用完整的数据集进行训练。考虑到代理和目标模型是不同的,并且当数据集中的信息不是集中在几个样本中而是均匀分布在所有样本中时,这就变得特别棘手。这是这种方法不太成功的一些原因。

2.2.2 示例遗忘

一项调查 14 报告称,一些样本一旦学习就永远不会忘记,并且在各种训练参数和超参数中表现出相同的行为。还有其他类别的样本被遗忘。遗忘事件被定义为当模型预测在随后的时期回归时。对此类遗忘样本的定性和定性(见图 6 和图 7)分析表明,噪声标签、具有“不常见”视觉复杂特征的图像是遗忘示例的主要原因。

图 6:跟踪遗忘样本的算法 14 。取自参考文献 14

图 7:表明增加噪声样本的比例如何导致遗忘事件增加 14 。图像取自 14

这项研究中一个有趣的观察结果是,丢失大部分未遗忘的样本仍然会导致测试集上极具竞争力的性能。形成的假设是,未遗忘的样本信息量不大,而遗忘的样本信息量更大,对训练更有用。在他们的情况下,遗忘分数在 75 个时期后稳定下来(使用 RESNET & CIFAR,但该值将根据模型和数据而变化)。

也许几个样本就足以说明一只猫有 4 条腿、一张小脸和尖尖的耳朵,这更多的是关于不同品种的猫看起来如何,尤其是如果它们看起来不同于标准的话,例如 Sphynx 猫。

使用梯度规范

损失函数是在数据集中查找感兴趣的样本的一种非常好的方法,无论这些样本是标注不当的样本还是真正的离群样本。安德烈·卡帕西也强调了这一点:

当你按损失降序排列数据集时,你肯定会发现一些意想不到的、奇怪的、有用的东西。

就我个人而言,我发现损失是一个很好的方法来发现标签不好的样品。因此,自然的问题会是“我们是否应该探索如何使用损失作为一种度量来修剪数据集?”。直到 NeurIPS 2021, 21 才对此进行了适当的调查。这项 Standford 研究调查了单个训练样本的初始损失梯度范数,通过几次权重初始化进行平均,以修剪数据集,实现更好的泛化。这项工作与示例遗忘密切相关,除了不是性能测量,而是更关注在训练早期使用局部信息来修剪数据集。

这项工作提出了一个训练样本(x,y)在时间 t 的综合得分,该得分由对该样本计算的损失梯度的 L2 范数和称为 EL2N 的预期损失 L2 范数(下面的等式)给出。这里的推理是,在给定的训练时间内,具有小的大分数的样本对学习如何对训练数据的其余部分进行分类具有丰富的影响。根据经验,这篇论文发现,对多个权重初始化的常模进行平均会产生与遗忘分数相关的分数 14 ,并导致在训练早期修剪样本的重要部分。他们能够在不影响准确性的情况下从 CIFAR-10 中剪除 50%的样本,而在更具挑战性的 CIFAR-100 数据集上,他们剪除了 25%的样本,准确性仅下降了 1%。
χ t ( xy)=Ewt|gt(xy )||2 (GraNd eq)

χ t ( xy)=E|p(wtx)—y)| | 2(EL2N eq)

这是一种特别有趣的方法,与迄今为止独立处理数据集中样本的其他修剪策略有很大不同。由于 DL 是一个非凸问题 21 ,基于独立统计的样本丢弃提供了较弱的成功理论保证。我很想知道混合是如何影响大比分的(见图 5b ),使用混合会导致更小的梯度范数(尽管是 l1)。

使用 GradNd 和 EL2N 21 进行修剪的结果。图片取自参考文件 21

这项研究的结果如上图所示。尽管这种方法在 CIFAR-10 和 100 数据集上做得很好,但即使使用这种方法,明显的高修剪也是不合算的。当我们丢弃大量样本时,我们是否保留了数据分布?大多数情况下不会,这是唯一有意义的推理。我们又回到了修剪多少才算足够的问题上?数据及其分布是依赖于网络还是更多的属性?这项研究 21 声称,GradND 和 EL2N 分数在多次初始化或训练轨迹上平均后,消除了对特定权重/网络的依赖,呈现出更压缩的数据集。如果这个断言在现实中成立,在我看来,这是一个非常有希望的发现,缓解了 DL 的数据相关的挑战。

这项工作更令人着迷的是,它揭示了底层数据分布如何塑造训练动态。到目前为止,这一直是个错误。特别感兴趣的是识别在训练中相对稳定的模型数据表示的子空间。

2.3.蒸馏

提炼技术是指将复杂或较大集合中的知识提炼为较小集合的方法。知识或模型提取是一种流行的技术,它将一个较大模型的学习表示压缩成一个小得多的模型,而性能没有任何显著下降。已经广泛探索了使用学生-教师培训机制,甚至在变压器网络的情况下,变压器网络甚至比更传统的网络更需要数据,例如卷积网络 DeiT 。尽管被称为数据高效,但这篇论文采用了师生策略来改造网络,而数据本身仅仅被视为一种商品。

最近,这一概念被研究用于数据集提炼的深度学习,目的是从大型数据集 1716 中合成最佳的较小数据集。提取的数据集被学习和合成,但是在理论上,它们近似于更大的数据集。注意,合成数据可能不遵循相同的数据分布。

一些数据集提炼技术也将它们的方法称为压缩。我原则上不同意这一点,因为压缩虽然在这种情况下有损耗,但指的是压缩数据集,而蒸馏是推导/合成数据表示,可能会导致完全不同的样本。也许是适用于这两种技术的压缩系数,也就是压缩比。例如,图 13 显示了提取图像的变化程度。

一数据集蒸馏 17 论文引用:

我们提出了一种新的优化算法,用于合成少量的合成数据样本,不仅捕获了大量的原始训练数据,而且仅用几个梯度步骤 17 为快速模型训练进行了显式定制。

他们的问题表述非常有趣!他们将网络权重导出为合成训练数据的可微分函数,并设置训练目标以优化提取图像的像素值。该研究的结果表明,可以低至每个类别一幅合成图像,同时性能不会下降太多。更具体地说,他们将 MNIST 数字数据集的 6 万幅训练图像提取到仅 10 幅合成图像中(每类一幅),测试时 MNIST 识别性能为 94%,而原始数据集为 99%)。

图 FAIR 研究的数据集提炼结果 17 。图像取自参考文件 17

这里是顶部标记的类别的一些蒸馏样本(图 9)。令人惊讶的是,在这些数据集上训练的 MNIST 表现得如此之好,但 CIFAR one 只达到了随机的水平(54%),而在完整数据集上达到了 80%(图 8 和图 9)。

图 FAIR 研究的数据集提炼结果 17 。图像取自参考文件 17

在这项工作之后,提出了另一种利用核方法的提取技术——更具体地说是核岭回归,以获得原始数据集的ε-近似值 18 。这种技术被称为核诱导点(KIP ),并遵循相同的原理来保持目标函数以最大化近似,并反向传播梯度以学习合成的提取数据。 1817 的区别在于一个 17 使用相同的 DL 网络,而另一个 18 使用内核技术。使用 KIP,另一个优点是不仅可以合成源样品,还可以选择性地合成标记。在 17 中,目标纯粹是学习像素值,从而学习源(X)。本文 18 还提出了另一种技术 Label Solve (LS) in,其中 X 保持不变,仅学习 label (Y)。

图 10:a)带 KIP 和 b)带 LS 18 的蒸馏样品示例。图像取自参考文件 18

对于 10 个样本,来自 17 (图 9)的 CIFAR 10 结果约为 36.79%,对于 KIP,在极端压缩的情况下,性能略有提高。这就提出了一个问题:什么样的压缩比才能保证良好的信息保留。对于像 CIFAR(与 MNIST 相比)这样的复杂任务,考虑到这个数据集相对复杂,10(每个样本一个)可能不够。

图 11:来自 KIP 和 LS 18 的 CIFAR10 结果。图像取自参考文件 18

实际上,前面讨论过的 LO shot 技术 11 也是 X-shot 技术的一种特殊形式,它进行数据集提炼。除此之外,基于梯度的数据集提取技术在过去几年中得到了积极的研究(参考 1617181920 )。另一种方法探索了称为Differentiable Siamese Augmentation (DSA)的连体扩增法,该方法使用匹配损失,并通过反向传播技术 16 合成数据集

图 12:可微分连体增强 16 。图像取自参考文献 16

随着中间层中隐藏单元的数量接近无穷大 20 (图 13),贝叶斯和梯度下降训练的神经网络收敛到高斯过程(GP)。这对于卷积网络来说是真实的,并且它们收敛到特定的高斯过程,信道极限被延伸到无穷大。因此,这些网络可以被描述为被称为神经正切核(NTK)的核。基于 JAX 的神经切线库一个自动微分工具包已经被用于应用这些内核来定义一些最近的蒸馏方法。参考文献 182021 就是这样一个例子。

图 13:收敛到无穷远的无限宽卷积网络 20 。图片取自谷歌神经切线库相关来源 src

KIP 和 LS techniques 18 的作者探讨了如何扩展和加速提取过程,以将这些技术应用于无限宽的卷积网络 20 。他们的结果很有希望(见图 14)。

图 14: KIP ConvNet 结果 20 。图像取自参考文件 20

在图 15 中示出了对来自基于无限宽度 CNN 的 KIP 技术的提取数据集的视觉检查。至少可以说,蒸馏结果很奇怪。在这个例子中,经过蒸馏的苹果图像似乎代表一堆苹果,而经过蒸馏的瓶子产生了明显不同的瓶子,同时仍然显示出原始两个瓶子的伪像。而其他类显示高阶特征(带有一些噪声)。

图 15:蒸馏 CIFAR 装置 20 的 KIP ConvNet 示例。图像取自参考 20

图 16 显示了 MNIST 结果,它们不仅非常有趣,而且看起来非常像 mixup(其中 x 和 y 都是混合的)。

图 16:蒸馏 MNIST 套件 20 的 KIP ConvNet 示例。图像取自参考文件 20

3.那么,如果你有嘈杂的数据呢

数据集中的噪声被认为是有害的。因为模型持有由数据集表示的知识的压缩形式,所以数据集管理技术仔细地寻找以避免数据集中的噪声。

噪声是一种非常强大的技术,可以填补源/图像中缺失的信息。例如,如果只有图像的一部分是已知的,那么代替用默认值(0 或 1 像素值)填充图像,使用随机噪声填充可以是避免与黑色或白色区域相关的实际真实值混淆的有效技术。这在我自己的经历中是真实的。有趣的是,遗忘事件研究实际上是在遗忘事件的分布上添加标签噪声。他们在像素值中添加了噪声,并观察到添加越来越多的噪声会减少令人难忘的样本数量(参见图 7 中使用噪声时的结果)。

DL 网络也能很好地处理来自随机性的噪声。我发现图 17 所示的 22 的结果非常有趣。看看当缺失的补丁是随机的时,模型做得有多好,当缺失的补丁是系统的时,模型做得有多差,这表明机器是多么强大和蹩脚(两者同时存在)!

图 17:屏蔽自动编码器 22 中的噪声补丁。图像取自参考文件 22

grand study21,调查了噪声对声源本身的影响,并进行了一系列实验得出结论,当有足够的数据时,保留高分的例子(通常是嘈杂或困难的)不会影响性能,只会有所帮助。

结论

总之,过去四年对于 DL 空间中的数据来说是令人难以置信的激动人心,2021 年更是如此!我们可以从像 MixUp 这样的简单技术中获得很多好处,但更令人兴奋的发展是剖析训练动态并探索样本在使用 DL 技术解决特定任务中的重要性。蒸馏方法仍处于早期阶段,它们适用于简单的数据集,但老实说,有多少现实世界的问题有简单的数据集?然而,这一领域出现了一些令人振奋的发展。如 21 所示,如果压缩方法适用于广泛的架构,这些技术可能是突破性的。

参考

  1. 【1707.02968】再论深度学习时代数据的不合理有效性。" 2022 年 1 月 3 日访问。https://arxiv.org/abs/1707.02968
  2. 赫斯提斯、乔尔、莎兰·纳朗、纽莎·阿达拉尼、格雷戈里·迪亚莫斯、Heewoo Jun、哈桑·基亚尼贾德、莫斯托法·阿里·帕特瓦雷医学博士、杨洋和周燕琪。“根据经验,深度学习的扩展是可预测的,”2017 年 12 月。【http://arxiv.org/abs/1712.00409】T4。
  3. https://www.wired.com/story/no-data-is-not-the-new-oil/
  4. https://pages . run . AI/hub fs/pdf/2021-AI-State-of-infra structure-survey . pdf
  5. 【1808.01974】深度迁移学习调查。2022 年 1 月 5 日接入。https://arxiv.org/abs/1808.01974
  6. 迁移学习。http://citeseerx.ist.psu.edu/viewdoc/download?doi = 10 . 1 . 1 . 146 . 1515&rep = re P1&type = pdf
  7. 张、、穆斯塔法·西塞、扬恩·多芬和·帕斯。“混淆:超越经验风险最小化”,2017 年 10 月。https://arxiv.org/abs/1710.09412。
  8. [1812.01187]利用卷积神经网络进行图像分类的一系列技巧。2021 年 12 月 30 日访问。https://arxiv.org/abs/1812.01187
  9. 【2009.08449】“不到一个”——Shot Learning:向 Mhttps://arxiv.org/abs/2009.08449 学习 N 节课。
  10. [1512.00567]重新思考计算机视觉的初始架构。2022 年 1 月 5 日接入。https://arxiv.org/abs/1512.00567
  11. [1904.05046]从几个例子中归纳:一项关于少量学习的调查。2022 年 1 月 5 日接入。【https://arxiv.org/abs/1904.05046】T4。
  12. 李菲菲、r .弗格斯和 p .佩罗娜,“对象类别的一次性学习”, IEEE 模式分析和机器智能汇刊,第 28 卷,第 4 期,第 594-611 页,2006 年 4 月,doi:10.1109/tpami . 2006.79 .https://ieeexplore.ieee.org/document/1597116
  13. [1606.04080]用于一次性学习的匹配网络。2022 年 1 月 5 日接入。https://arxiv.org/abs/1606.04080
  14. 【1812.05159】深度神经网络学习过程中样例遗忘的实证研究。2021 年 12 月 29 日访问。https://arxiv.org/abs/1812.05159
  15. [1906.11829]通过代理的选择:深度学习的有效数据选择。2021 年 12 月 29 日访问。https://arxiv.org/abs/1906.11829。
  16. [2102.08259]使用可微分连体增强的数据集压缩。2022 年 1 月 5 日接入。https://arxiv.org/abs/2102.08259
  17. [1811.10959]数据集提取。2022 年 1 月 5 日接入。https://arxiv.org/abs/1811.10959
  18. [2011.00050]来自核岭回归的数据集元学习。2022 年 1 月 5 日接入。https://arxiv.org/abs/2011.00050
  19. [2006.05929]具有梯度匹配的数据集压缩。2022 年 1 月 5 日接入。https://arxiv.org/abs/2006.05929
  20. "[2107.13034]无限宽卷积网络的数据集提取。"2022 年 1 月 5 日接入。https://arxiv.org/abs/2107.13034
  21. [2107.07075]基于数据饮食的深度学习:在训练早期找到重要的例子。2021 年 12 月 10 日接入。https://arxiv.org/abs/2107.07075
  22. [2111.06377]屏蔽自动编码器是可扩展视觉学习器。2022 年 1 月 5 日接入。https://arxiv.org/abs/2111.06377
  23. [1802.03426] UMAP:一致流形近似和降维投影。2022 年 1 月 5 日接入。【https://arxiv.org/abs/1802.03426】T4。
  24. 作者声明:Geoffrey E. Hinton .“使用 t-SNE 将数据可视化。”《机器学习研究杂志》9(2008):2579–2605。https://www . jmlr . org/papers/volume 9/vandermaten 08 a/vandermaten 08 a . pdf
  25. 小心你的异常值!探讨离群点对视觉问答主动学习的负面影响。" 2022 年 1 月 8 日访问。[https://arxiv . org/ABS/2107.02331](https://arxiv . org/ABS/2107.02331)

复习强化学习试卷#13

原文:https://towardsdatascience.com/review-of-reinforcement-learning-papers-13-24ed69a8fdc2

我在我的研究领域发表了 4 篇文章:强化学习。大家讨论一下吧!

作者图片

[ ←上一次回顾 ][ 下一次回顾→

论文 1:用有限的数据掌握雅达利游戏

叶文伟,刘,库鲁塔奇,t,阿贝耳,p .,,高,Y. (2021)。用有限的数据掌握雅达利游戏arXiv 预印本 arXiv:2111.00210

Eefficient zero 是作者给他们新的强化学习算法起的名字。是什么使它不同于其他许多先进的算法?让我们现在就看。

先说它是基于视觉的算法。基于视觉的强化学习非常复杂,因为观察空间是巨大的,并且环境在大多数时候仍然是部分可观察的。其次,作者在这里关注离线学习,即算法使用已经收集的数据,并且不能与环境交互。测试离线视觉算法最常见的基准是 Atari 100k 基准。顾名思义,它是一个包含与 Atari 2600 游戏的 100k 次交互的基准测试,相当于实时游戏的 2 小时。给你一个数量级的概念,雅达利游戏上的大多数在线强化学习算法都使用了几千万或几亿次的交互。说白了,很少。这里的目标是用很少的数据学习最多的东西。这就是所谓的样本效率。

在我们进入 EfficientZero 如何工作之前,让我们先来讨论一下结果。他们的方法在 Atari 100k 基准上达到了 190.4%的平均人类性能和 116.0%的中值性能。相比之下,这是 DQN 用 500 倍多的数据实现的性能。在 DMControl 100k 基准(类似的基准,但用于控制任务)上,他们的方法获得了比著名的从地面真实状态学习的 SAC 算法更好的结果(从而不会遭受基于视觉的学习的困难)。我们可以肯定地说,他们的算法是样本高效的。

但是,这个算法是如何工作的呢?如果 EfficientZero 这个名字听起来耳熟,那是正常的。这是因为这个算法只不过是 MuZero 的三个重要修改:

第一,自我监督的一致性损失。你应该知道,穆泽罗使用了环境的“表征”,这允许它从观察中产生一种状态。这个状态然后被传递给模型,也是在之后,以预测下一个状态。为了一致性,在时间 t 推断的下一个状态必须对应于在 t+1 的表示的状态。考虑到这一点,作者在测量这两种状态之间距离的模型的学习中增加了一个损失:名副其实的自我监督一致性损失

图来自:自我监督的一致性损失

第二,端到端的预测值前缀。对于这一条,我只给你直觉。在许多情况下,可以预测某一事件将会发生,但不能准确预测它将在何时发生。通过利用这种直觉,作者修改了要学习的值的形式,以促进这种预测。

最后,基于模型的偏离策略修正。就这一点而言,这是一个纠正因使用旧政策获得的数据而导致的差异的问题。通过在收集的数据和来自模型的预测之间添加平衡,可以限制这种差异。

就其取得的结果而言,这种方法值得研究。到目前为止,它还不是开源的,但应该很快就会了。

论文 2:通过几何感知多任务学习的灵巧操作

黄,w .,莫达契,I .,阿贝耳,p .,,帕塔克,D. (2021)。通过几何感知多任务学习实现灵巧操作的泛化arXiv 预印本 arXiv:2111.03062

n 手操作包括学习用机器人手操纵物体。本文的出发点是以下简单的观察:一个被训练来操纵一个对象的代理不一定知道如何操纵另一个对象。在大多数关于手动操纵的文章中,很少有冒险改变操纵对象的。那又怎样?我们应该学习一个专门用于操纵所有可能对象的策略吗?不,这篇文章的作者通过提议使用一个单一的策略来操作 100 个不同形状的对象,为这个主题提供了一些启示。

图来自文章:“训练一个可以对大量对象进行手工操作的策略”

本文的关键要素是作者所谓的“几何感知对象表示”。首先,你要知道,大多数时候,我们喜欢用 6 个坐标来表示一个物体:3 代表它的位置,3 代表它的方位。很方便,而且效果很好。但是这种表示并没有抓住物体的形状。所以,当有不同形状的物体时,它就不那么好用了。这就是“几何感知对象表示”的用武之地。它使用对象的点云,而不是仅用位置和方向来表示对象。该点云由第一神经网络编码以构建对象表示。具体地说,这个网络被训练来提取 2 样东西:对象的类别(它是哪个对象?),以及它的方位。正是这种表示作为策略网络的输入。然后可以用任何强化学习方法来训练该策略(他们在文章中使用了 DDPG)。

一旦经过训练,它们表明该策略可以对其数据集中的 100 个对象进行手动操作。
他们还表明,这一政策可以推广到不同大小和形状的看不见的物体。另一个有趣的结果是:当以这种方式学习策略时,学习到的策略似乎比用单个对象(老方法)学习到的策略执行得更好。你认为这是为什么?

论文 3:深度强化学习中的概括综述

柯克,r .,张,a .,Grefenstette,e .,& rocktschel,T. (2021 年)。深度强化学习中一般化的调查arXiv 预印本 arXiv:2111.09794

强化学习中的泛化研究是一个相当年轻的领域。它包括回答以下问题:学习算法表现好吗,即使是在它从未见过的数据上?举个例子:如果我在瑞士教一个代理开车,他会不会在德国会开车?这项任务在全球范围内保持不变,但交通标志可能会略有变化,一些速度限制或一些规则也会有所变化。

对这个问题的研究是至关重要的,特别是如果我们想在现实世界的场景中部署强化学习算法。在这种情况下,环境是不断演变的,而且是极其多样的。与部署过程中实际遇到的数据相比,学习过程不可避免地会有很小的差异。

定义了这一点,现在让我们转向出版物本身。这篇文章试图为强化学习中的概括研究建立一个通用的框架。它为这一问题的未来工作提出了术语和形式。我不会深入讨论这本书的细节,而是从它的结论中提出七点。

  1. 应该考虑零炮学习。这是一个设置(在计算机视觉中众所周知,在强化学习中研究较少),其中代理在一个环境中被训练,但在另一个环境中被评估。代理没有在这个新的环境中完成它的训练,因此术语零射击。
  2. 注意,这并不意味着最优策略应该被认为是固定的。相反,我们必须考虑一个不断变化的最优。这意味着持续学习,也就是说在部署期间持续学习。这是整个研究领域的主题:持续学习。未来的强化学习系统最有可能部署在这样的场景中。
  3. 纯粹的 程序化内容生成环境不足以研究概括。PCG 是一种创造从一集到另一集环境变化的方法。为了理解,想象一个机器人必须学会在一个从一集到另一集改变形状的房间里导航。如果这种方法在迫使智能体适应各种各样的变化方面似乎非常有效,那么作为黑盒工作的 PCG 仍然会带来一个问题:在训练阶段和实验阶段的环境生成策略不能改变。然而,研究非常有趣,例如,如果我们的机器人在训练阶段从未遇到过椅子,它会对椅子做出什么反应。作者的建议是打开这些黑盒来研究这样的场景。
  4. 为了部署现实世界问题的模型,必须特别注意上下文效率。当环境的动态和回报依赖于隐藏的静态参数时,就使用了上下文这个术语。上下文效率是代理在这些参数被修改时快速学习的效率(因此改变了动态和回报)。
  5. 对于离线学习问题的泛化需要进一步探索。离线学习包括在不允许代理与环境交互的情况下训练代理。数据是预先固定的,数量有限。
  6. 在现实世界的场景中,动态和奖励函数很可能在学习过程中发生变化。有必要进一步研究算法在这种情况下的表现。
  7. 由于模拟器不能完美地再现真实世界,当模型被部署到真实世界场景中时,不可避免地会有一些特征发生变化。如果我们能够识别这些特征,就有可能从训练输入中消除它们(例如,颜色,用于颜色不相关的任务)。这就是归纳偏差的一个例子。当泛化问题很强时,这些泛化偏差一定是同等重要的。未来的工作应该更加明确这些归纳偏差,具体评估它们对结果的影响。

此外,作者提供了许多其他未探索的研究途径。我们有很多工作要做!

论文 4:一个通用的手持物体重定向系统

陈,t,徐,j .,,阿格拉瓦尔,P. (2021)。一个通用的手握物体重定向系统arXiv 预印本 arXiv:2111.03043

图来自文章

在本文中,作者提出了一个框架的具体学习这一重定向任务。目标不是像许多以前的作品那样学习操纵单个对象,而是操纵广泛的对象,包括代理在学习阶段从未遇到过的对象。此外,他们还对未探索的情况感兴趣,即手不是掌心向上,而是朝下(这是一个更复杂的任务,因为它必须处理重力)。

框架相当巧妙。作者首先指出,在现实世界中,一些描述符比其他描述符更难测量(例如,物体的速度很难测量,而机器人关节的位置很容易测量)。然而,在模拟中,这些组件很容易访问。他们还发现,拥有包含所有这些特征的完整观察的代理比只拥有容易测量的特征的代理学习得更快更好。这就是他们提出的:在完全观察的模拟中学习。以这种方式训练出来的智能体被称为教师。然后,一个学生被训练模仿老师,但来自简化的观察。结果相当好,作者表明这种方法允许用单个代理学习许多对象的操作。这些结果通过他们所谓的重力课程得到了进一步的改进(我让你参考这篇论文了解更多细节)。

他们最后指出,最令人惊讶的观察结果是操纵物体不需要形状信息。对于操作任务来说,视觉可能没有思维重要。这一观察虽然令人惊讶,但当人们意识到闭着眼睛操纵一个物体并不更复杂时,这一观察仍然是自然的。

谢谢你把我的文章看完。我很乐意阅读你的评论。

你们中的一些人可能注意到我拿走了《周刊》。这很正常。写这篇每周文章的工作量太大了,我经常没有素材。从现在开始,这些评论完成后会发表,这取决于我掌握的材料。

复习强化学习试卷#14

原文:https://towardsdatascience.com/review-of-reinforcement-learning-papers-14-70b6772a8100

我在我的研究领域发表了 4 篇文章:强化学习。大家讨论一下吧!

作者图片

←上一次回顾】【下一次回顾→】

强化学习是解决复杂控制问题的一个强有力的工具,最近的研究在将它应用到各种领域方面取得了重大进展。在本帖中,我们将回顾四篇论文,展示强化学习在不同领域的潜力,包括能源生产、自我监督学习、机器人和自然语言处理。

论文 1:通过深度强化学习对托卡马克等离子体的磁控制

Degrave,j .、Felici,f .、Buchli,J. 通过深度强化学习对托卡马克等离子体进行磁控制性质 602,414–419(2022)。

照片由Zoltan·塔斯Unsplash 上拍摄

我们时代的主要挑战之一与能源有关。如何为人类生产足够数量的清洁能源?Deempind 最近在这个方向上迈出了新的一步。在《自然》杂志上发表的一篇论文中,他们解释了他们是如何成功应对一项重大物理挑战的:控制托卡马克中氢等离子体的聚变。先说一些背景信息。

挑战:重现恒星中发生的核反应,仅此而已!但是为了发生预期的反应,这些元素必须被加热。厨房烤箱是不够的,因为它必须达到 1.5 亿度!为此,物理学家青睐的方法是让等离子体在一个他们称之为托卡马克的空心圆环中旋转。由于反应不稳定,托卡马克的磁场必须非常精确地调整,每秒钟调整几千次,这样等离子体就不会接触到壁(你知道,在这种温度下,壁不会抵抗很久)。让我们总结一下:根据等离子云的位置,目标是选择施加在托卡马克线圈上的电压,以避免等离子云碰到壁。如果这个提法让你想用强化学习来解决这个问题:太晚了,DeepMind 已经做到了,它就是这篇出版物的主题。

主要的困难是数据的可获得性。你知道,为了训练一个强化学习模型,有必要拥有大量的数据,即使对于简单的任务,也需要与环境进行几个小时的交互。然而,这个星球上为数不多的托卡马克是由许多研究项目共享的,只需运行几分钟就需要很长时间的准备和冷却。为了克服这个问题,研究人员不得不使用模拟模型。虽然所涉及的现象非常复杂,以至于模拟非常缓慢,但研究人员能够获得足够多的数据来学习一个模型。

物理学家对几种形状的等离子体感兴趣。这些形状中的一些已经用学习的模型成功地实现了。特别是,该模型首次实现了液滴(见下图)。

图:产生的等离子体形状。(鸣谢:DeepMind & SPC/EPFL)

我们再一次见证了在物理世界中强化学习的成功。这一次,它允许控制一个复杂的机器,这将允许在未来可能获得一个完全清洁的能源。

论文 2:通过行动了解世界

莱文,S. (2022)。通过行动了解世界第五届机器人学习会议论文集164:1752–1757。

我众所周知,更大的模型和更多的数据会带来更好的学习,对吗?但是由于讨厌的计算能力问题,我们的模型能有多大是有限制的。另一方面,数据集只受到构建它们的人类努力的限制。这就是为什么自我监督学习方法变得如此受欢迎——它们不需要手动注释,尽管它们仍然需要大量的工程工作。

现在,事情变得有趣了。自我监督学习和 RL 之间有很强的联系,在 RL 中,代理可以自己做出决定来增加对环境的了解。但事情是这样的——即使代理人做出了自己的选择,它仍然被奖励信号所强化,奖励信号是由……你猜对了,是一个人。因此,作者认为我们应该探索更多的范例,让代理人设定自己的目标。

其次,他坚持离线学习的相关性,离线学习的优点是允许使用以前收集的数据,这些数据可以来自过去的经验,甚至来自其他代理人的经验。这对于现实世界的应用程序来说尤其重要,因为在现实世界中,数据收集是一个棘手的问题。

简而言之,自我监督学习和 RL 有很多可以互相学习的地方,离线学习是一种聪明的方式,可以充分利用我们的数据收集工作。快乐学习!

论文 3:决策转换器:通过序列建模的强化学习

陈,卢,k,拉杰斯瓦兰,a,李,k,格罗弗,a,拉斯金,m,…和莫尔达奇,I. (2021)。决策转换器:通过序列建模进行强化学习。 神经信息处理系统进展,34,15084–15097。

T 这篇论文的作者想出了一种方法来处理强化学习,这完全是关于序列建模的。他们决定使用一种叫做 Transformer 架构的东西,这是目前语言建模中非常流行的东西。他们的模型被称为决策转换器,它采用过去的状态、行动和期望回报(或者奖励,如果你愿意的话),并使用它们来生成将导致所述期望回报的未来行动。这种方法有几个好处——它避免了“致命三重奏”和贴现未来奖励,这是 RL 中令人讨厌的问题,它允许使用现有的 transformer 框架和监督学习系统。

但真正的问题是——在各种任务上,Decision Transformer 的表现与最先进的无模型离线 RL 基线一样好,甚至更好。印象深刻吧。作者还深入研究了在任务之间转移知识和使用决策转换器进行在线学习和连续控制的潜力。总而言之,看起来这种模式可能会改变 RL 世界的游戏规则。所以,如果你对这类东西感兴趣,就去读一读吧!

论文 4:多面手

Reed,s .、Zolna,k .、Parisotto,e .、Colmenarejo,S. G .、Novikov,a .、Barth-Maron,g .、… & de Freitas,N. (2022 年)。通才代理arXiv 预印本 arXiv:2205.06175

A 人工代理仅限于实现单一任务。例如,我们谈到:一个识别图片中交通标志的模型,或者一个总结文本的模型,或者一个自动控制机器人的模型…一个总结文本的模型将无法将该文本翻译成英语,更不用说控制机器人了…直到这篇论文!

DeepMind 的研究人员发表了一篇论文,其中他们描述了第一个能够执行非常多种任务的模型(称为 GATO)。加托可以玩任何雅达利游戏,聊天,图片说明,用真正的机械臂叠积木等等。革命性的是,单个神经网络被用来执行所有这些任务。

来自出版物的数字:“加托接受了 604 项不同任务的训练,训练方式、观察和行动规范各不相同”

加托是如何做到这种普遍性的呢?因为代理将几种模态作为输入,包括图像、文本、本体感受、连接对、按钮按压等。并且产生不同类型的连续和离散动作,它必须在所有这些模态之间做出选择。为此,数据被序列化为一系列令牌。这些标记然后由类似于语言模型中使用的转换器神经网络进行处理。

尽管与文献中的专家模型相比,加托在所有这些任务方面都很差,但它是第一个具有如此高的通用性的模型。这大概是未来几年 AI 研究要走的方向:让最广义的智能体成为可能。敬请关注。

结论

本文回顾的论文展示了强化学习在解决不同领域的各种问题方面的多功能性和潜力。从控制清洁能源生产的托卡马克等离子体,到提高自主和通用系统的鲁棒性,这些论文展示了强化学习在解决复杂控制问题和实现更加智能和自主的系统方面的力量。所有这些论文还强调了适应不断变化的环境和使用大量数据来提高性能的重要性,展示了强化学习在各个领域做出重大贡献的潜力,并为该领域的未来发展铺平了道路。谁知道未来我们能用强化学习做些什么惊人的事情呢?只有时间(也许还有更多的论文)会告诉我们答案。

谢谢你把我的文章看完。我很乐意阅读你的评论。

RStudio 工作坊回顾——打造整洁的工具

原文:https://towardsdatascience.com/review-of-the-rstudio-workshop-building-tidy-tools-434c606029e2

Unsplash 上的 Kvalifik 拍摄的照片

打造整洁的工具是 RStudio 每年在其 RStudio 会议上提供的众多研讨会之一。我参加了 2022 年 7 月 25 日至 7 月 26 日在 DC 举行的上一次会议的研讨会。在这篇博客文章中,你将会看到一份研讨会内容的总结,以及我对整个研讨会的看法。如果你正在考虑参加这个研讨会,希望这篇文章能给你一些帮助。

在进入更多细节之前,这里有一些关于研讨会的快速事实,以帮助您确定这是否是适合您的内容:

这是什么?

该研讨会是一个为期两天的实践培训,教授如何使用【tidy】设计或编码原则在 R 中构建包,这些原则在包含在包 universe—tidy verse中的包中进行了演示。

谁是主要受众?

该研讨会的官方网站称,任何有编写 R 函数经验并准备好发布其作品的人都应该是合适的候选人。我认为这是一个相当准确的要求,但为了使它更具体,我想说如果他/她以前已经尝试过构建 R 包,那么他/她将从这个研讨会中获得最大的收益。但这只是我的看法:)

所以现在让我们深入了解细节。

用两天时间将功能打包

如前所述,这是两天的事情。我认为这两天的内容针对包开发旅程的两个不同方面:创建包的过程和编写 R 编程的最佳实践。最终目标是带领培训生实时地构建和改进一个包。事实上,你可以在这里检查你将在研讨会后生产的最终产品,R 包。

为了使我的总体回顾更有条理,我将把总结和评论分成几个部分来分享。

第 1 天:构建概念验证包

如果你参加这个研讨会的目的是想知道如何扔掉一堆包装在 R 包中的脚本并开始使用它,那么第一天的材料就足够了!你将在一天之内从理解什么是 R 包到构建你的第一个 R 包,甚至为你的包创建一个站点!所有这一切,在我看来,可能是最无痛的方式。

这一天的内容分为四个部分:

第一场:整场比赛

在建立了一些基本规则并讨论了研讨会的议程后,你将会对“什么是 R 包”有一个温和的介绍。本课程将让您开始 R 项目,并为开始构建 R 包做好基础工作。

您将开始您的 R 包开发项目,设置版本控制,将您的第一个函数添加到包中,了解基本的包开发工作流和许可。除非您遇到一些技术异常,否则这个会议应该会非常顺利,并让您对构建 R 包有很好的体验。

第二场会议:文件-最少

顾名思义,这一节是关于文档的,“最少”部分意味着这一节的讨论将只解释最少的文档:填写描述文件和函数的最少描述——如果在 r。

您将了解描述文件——R 包的定义文件之一,了解包含什么和不包含什么。

请特别注意关于包依赖关系的部分。没有恰当地包含依赖关系会导致很多令人头疼的问题。

对于函数文档,您将学习如何使用 Roxygen、devtool 包来自动转换函数脚本中的注释以生成文档。

会议 03:单元测试

可能是一天中最具挑战性的部分 01。您将了解如何使用包 testthat 来设置测试过程,以便在每次迭代或修改函数后自动运行测试。从不同的用例中建立测试用例,将会在一个包的整个生命周期中减少很多不必要的东西。这一节将向您介绍*expect_*()* 系列函数,它将使设置不同的测试场景变得更加容易。

第四场会议:文件共享

这一节详细介绍了使您的包对其他人有用所需的文档——比如在 GitHub 页面中建立一个 readme 文件,创建一个小插图来详细描述包及其内容。

除了这些文档之外,您还将使用 GitHub Actions 建立一个 CI-CD 管道,以确保代码中的任何更改一旦被推送到 GitHub,所有的测试都会运行,文档都会更新。

最激动人心的部分——一天下来,你将拥有一个实用的软件包和一个很酷的网站!

总的来说,第一天进行得很顺利,让大家对拥有一个真正的包裹感到兴奋。它展示了使用像 usethatdevtool 这样的包来打包代码是多么容易。

第 2 天:为你的长期成功做准备——至少我是这么认为的。

如果第 01 天是让您对构建 R 包感到兴奋,那么第 02 天是为您的长期成功做准备,这意味着它超越了严格的包构建,深入研究了 R 的一些内部工作,讨论了最佳实践等等。与第一天相似,第二天也是四节课。

第一场会议:设计

本节首先简要讨论 tiyeval 如何工作,然后讨论编写函数的一些最佳实践,例如:命名、大小写、参数序列,以及将纯函数与有副作用的函数分开。第二节课将更详细地讨论带有副作用的纯 vs 函数。

会议 02:控制副作用

本节讨论了测试和管理函数产生的副作用的方法。您将练习使用方法来验证输入和中止函数(如果需要的话),以及如何编写用户友好的错误消息。此外,如何在不改变全球环境的情况下发挥作用。

会议 03:整洁评估

如果您已经用 R 编写了一段时间的代码,并且也涉猎了 Python 等其他语言,那么您可能已经注意到,与 Python 不同的是,您可以将 R 函数中的列名作为非字符串值传递。Tidy eval 使 R 有可能将裸名理解为列名。这是一个方便的特性,但不是我最喜欢的,本节将详细介绍这个功能。

涵盖的具体主题有:利用三个点(…)作为 tidy 函数的输入,使用拼接(!!!)功能,使用双花({{}}),实用一些非常具体的整理功能,如 any_of()、all_of()、跨和比较代码快照。

总的来说,本节介绍了一些非常具体的工具和功能,这些工具和功能将使功能更加整洁和易于维护。

会议 04:函数式和面向对象编程(OOP)

第 2 天的课程涵盖了许多需要更多关注和注意才能正确理解的内容。作为最后一次会议,由于时间限制,这部分有点仓促。这个会议是关于如何在 r 中实现函数式编程的。

使用函数作为参数是函数式编程的基础。—引自会议幻灯片。

在这次会议中,我没有发现太多关于 OOP 的内容,老实说,我也不认为 R 是实践 OOP 的正确语言。如果你喜欢 OOP,那么 Python 会更适合你。

对利用 purrr 包在 r 语言中进行函数式编程进行了快速讨论和动手练习。

总的来说,除了确定的任务列表之外,第 02 天充满了来自 Ian 的有用的建议,他是一名教师,有着丰富的编程经验。此外,这些幻灯片还加载了额外的资源,以便浏览和了解更多有关 R 内部工作方式和一般编码最佳实践的信息。

总的来说,我对这个研讨会非常满意——它设计得很好,经过深思熟虑,我觉得它对不同专业水平的人都有帮助。对于对构建 R 包感兴趣的人来说,第 01 天是一个很好的激励。对于一个充满热情的 R 程序员来说,第 02 天充满了让他们的 R 之旅更加顺利的建议和技巧。

此外,课程材料是开源的。这里是课程网站,这里是工作坊 GitHub repo

用全新的眼光重游 MNIST

原文:https://towardsdatascience.com/revisiting-mnist-with-fresh-eyes-36f9d19a75d1

动手教程;人工智能视角的转变

MNIST 数据的空间透视,而不是通常的矢量处理

Unsplash 上由 nine koepfer 拍摄的照片

到 2022 年,MNIST 数据集已经成为进入人工智能的人的“Hello-World”数据集。见鬼,可能已经有几年了。

在 MNIST 通常的人工智能处理中——无论是将数据加载到神经网络,逻辑回归,随机森林,插入——人工智能算法——在这里,每个手写数字图像及其像素值都被视为一个向量(因此正方形图像被拉长为一个向量),该向量被馈入算法。对于数千位数字,训练算法意味着将数千个像素拉长的向量作为矩阵输入算法进行处理。

图片来源:https://medium . com/data man-in-ai/module-6-image-recognition-for-insurance-claim-handling-part-I-a 338d 16 c 9 de 0

然而,我希望你,精通人工智能的读者,对数字采取不同的观点——你几乎认为这是理所当然的,因为它看起来非常直观——我们的眼睛完全是为这种观点而生的。

与其他类型的数据(如表格记录、NLP 文本数据、音频数据等)相比,图像数据有一个独特的特点。别有–图像中像素的直观空间配置。因此,不要试图通过将行合并成一个向量来消除图像数据的空间属性,让我们利用我们的眼睛和大脑直观使用的这一属性!

将图像像素转换成笛卡尔点

我们需要采取的第一步是将图像像素转换成笛卡尔(x,y)点。我们拍摄一张图像,并转换这些值,这样我们就可以使用 xy 平面中的点来复制它。这是原始的 3:

图片鸣谢:作者。原始样本“3”的 MNIST 图像

上面黑白图像中的每个像素都有一个坐标:(0,27)是左下角,(0,0)是左上角。所以它与通常的笛卡尔 xy- 平面有点不同,在笛卡尔平面中(0,0)在左下方。首先,我们进行一次转换,将像素坐标转换为笛卡尔坐标,这是在下面的代码中完成的:

normalize_image = np.asarray(image).astype('float32')/255.0
    coordinates = list(itertools.product( range(0,28) , range(0,28)))
    ...
    x_values = np.float16(np.asarray([fp[1] for fp in filtered_points]))
    y_values = 27-np.float16(np.asarray([fp[0] for fp in filtered_points]))

然后,我们给每个点添加统计噪声(使用正态分布),以便它更多地“填充”像素点之间的空间。每个像素处于整数坐标;所以转换到笛卡尔坐标只能得到整数坐标,这不是一个数据丰富的表示。基本思想是,我们到每个整数笛卡尔坐标(从像素转换而来)并在所选坐标周围采样 4 个新点作为随机噪声,并将所有 5 个点绘制在一起。代码是这样的:

for i in range(0, data.shape[0]): # this loops thru each Cartesian coordinate
      point = data[i,:]
      mean = point
      cov = np.array( [[0.0001, 0], [0, 0.0001]] )
      ...
      test_x, test_y = np.random.multivariate_normal(mean, cov, 4).T
        ... # this creates the random noise
    return filled_in

而完成的笛卡尔,坐标 xy- 平面结果看起来是这样的!

图片鸣谢:作者。你可以在这里看到,这张图像由笛卡尔像素-整数坐标(归一化)制成,添加了噪声,看起来真的很像上面的 MNIST 黑白图像!

机器学习来了!

好吧,所以你可能会问:为什么我们要经历这整个过程,只是为了让看起来像另一个有笛卡尔点的 MNIST 图像?有什么大不了的?

好吧,重要的是:因为我们现在有(x,y)点作为数据,我们可以对它执行机器学习,就像它是任何其他 2D 数据集一样!我们要用的机器学习技术叫做高斯混合模型。基本上,这是一种通过使用高斯密度来教导计算机理解点的分布的奇特方式(就把它想象成高斯椭圆;它们看起来真的像椭圆形,你马上就会看到)。

创建它的代码是:(“plot_gmm”是一个定制的可视化函数,这归功于 Jake VanderPlas 可视化集群的代码。他的功能可以在这里找到:https://jakevdp . github . io/python datascience handbook/05.12-Gaussian-mixes . html

selected_model = GaussianMixture(n_components=gaussian_learning( result ), random_state=1, covariance_type='full')# plot the selected model over original image data with a user-defined function. Credit to Jake VanderPlas.
plot_gmm(gmm=selected_model, X=result)

图片来源:作者

颜色不错!但是这具体告诉了我们什么呢?嗯,它向我们展示了人工智能算法能够计算出创建“3”形状的点所需的混合块。

但是等一下:这只是针对数据集中一个特定的“3”。难道我们不需要对 MNIST 集合中的每一个“3”进行可视化吗?嗯,是的,但是不要做可视化,让我们关注从这个设置中收集的不同属性:在混合物机器学习过程中使用的混合物成分

汇总数据中的有趣属性

我们再来看看上面的图片。我们可以看到,ML 算法使用 6 个混合分量来导出构成“3”的点的分布。这在集合中的所有“3”中是共同的吗?为什么是 6 个组件?对我们人类的眼睛来说,它看起来就像“3”是由两条简单的曲线在中心连接而成。

为了回答第二个问题——为什么有 6 个组件——在上面的可视化中,我们看到了使用高斯聚类的局限性:每个组件都是一个具有假想垂直轴的椭圆,就像这样:

图片来源:https://en.wikipedia.org/wiki/Ellipse

为什么这很重要?嗯,具有这些垂直轴的椭圆当然可以覆盖像椭圆一样模糊分布的点的“云”。但这带来了一个限制:在上面的图像中,数据点的形状像一个 3,组成“3 的曲线”的点的主要部分看起来像凸弯曲的“软糖”——虽然椭圆也是凸的,但在不改变方程的情况下,它不能像软糖一样强制弯曲或弯曲。并且改变方程,意味着不使用高斯混合学习。

因此,这基本上告诉我们,高斯混合建模,在 MNIST 从这个 2D,空间配置的背景下,真的只能建模图形的“直线部分”。这就好像你的任务是拍一张 3 的照片,把它分解成多边形的直线部分,然后在这些直线周围画椭圆。

图片鸣谢:作者。把一个“3”分段成几个直线,然后用一个椭圆包住每条直线。看起来很像上面的高斯色聚类,不是吗?

所以想象一下,所有来自上面的可视化的组成 3 的数据点,它们中的每一个都属于这些椭圆中的一个。如您所见,这里的直线是椭圆的长轴直径,根据椭圆的大小和数据点的配置,可能会有更多或更少的椭圆。但是高斯混合学习试图将图像分解成这些簇,这解释了为什么一些“3”与 6 个分量聚集在一起。

但是很明显在图中我画了 6 个以上的椭圆组件,那么哪些图需要多于或少于 6 个呢?为此,我们需要可视化组件数量的直方图。我不会在这里显示所有代码,但对于这个示例,我们将从 MNIST 数据集中的 6,000 张“3”图像中随机抽取 300 张图像作为样本(因此,抽取 5%),将它们输入高斯学习过程,并可视化结果:

图片来源:作者

根据这张图表——并根据采样正态假设外推至整个 MNIST 数据集——37%到 43%的“3”需要六个组件——六个分段聚类来创建 3 的图像。同时,24%到 30%的“3”需要五个组件,15%到 20%的“3”需要七个组件。

那么…我们能从这些信息中收集到什么呢?让我们回头看看这些点的直观图示。如果“3”画得很小,很紧凑,那么它就不需要那么多的分段直线来组成图像——因此,五六个高斯分量就足够了。但是如果“3”画得更大,或者说中间有一个循环,这种情况时有发生,那么这个图将需要更多的组件来覆盖。根据直方图的统计,分量的平均#在 6 左右,样本的标准差是 1 分量,所以不难看出需要 7 个分量的例子。事实上,这里有一个例子:

图片鸣谢:作者。你可以数出这里有 7 个部分,因为这个人在 3 的左下端画了一个额外的卷曲,在中间画了一个强调的弯曲。

最终想法

无论如何,这不一定是 MNIST 图像的新机器学习算法的途径。MNIST 图像已经像一只老狗一样被人工智能算法训练了。相反,我想呈现给读者的是一个替代的,空间启发的视角,将机器学习技术应用于手写数字。

这种高斯混合学习过程不一定比神经网络获得更好的结果;但这里的目标不是结果,而是简单地改变视角。利用像素的空间定位,并转化为数据点,使我们能够使用高斯聚类的视觉几何来查看基本上是什么——一个几何结果——一个手写数字。它还允许我们使用甚至来自高中和大学几何的概念!–使用直线将曲线上的点分段连接的想法,几乎将其视为多边形的边。这个概念并不新鲜;事实上,分段连接在微积分中是用来求曲线的“弧长”的!但在这里,对分段线进行几何上的直观推理,实际上给了我们混合模型内部结构背后的良好推理——并解释了为什么一些相同数字的手写图形比其他图形使用更多的块(组件)。

非常感谢您的阅读,请继续关注我的下一篇文章!与此同时,请随意查看我过去写的一些关于我对人工智能的看法的文章

革新数据管理:人工智能如何改变我们管理和维护数据的方式

原文:https://towardsdatascience.com/revolutionizing-data-management-how-ai-is-transforming-the-way-we-manage-and-maintain-data-deaa05cf1789

人工智能在数据管理领域的使用案例越来越多,变得越来越相关

人工智能(AI)近年来一直在数据管理领域兴风作浪。这项技术有可能彻底改变企业收集、存储和分析信息的方式,使他们能够做出更加明智和有效的决策。

Unsplash 上由 Hitesh Choudhary 拍摄的照片

从最基本的意义上来说,数据管理中的AI指的是使用机器学习算法和其他智能技术来自动化通常涉及大量人工工作的大部分流程。这可能包括数据清理、数据分析和数据丰富等任务。

在数据管理中使用人工智能的一个关键优势是,它允许企业快速准确地处理大量数据。传统的数据管理技术既耗时又容易出错,尤其是在处理大型数据集时。另一方面,人工智能算法可以以更快的速度分析数据,使实时从数据中获得洞察力成为可能。

人工智能在数据管理方面的另一个优势是,它可以帮助企业识别他们数据中的模式和趋势,这些模式和趋势对人类分析师来说可能不明显。这对于揭示有助于企业改善运营和做出更明智决策的见解特别有用。例如,人工智能系统可能能够识别客户行为的趋势,从而优化营销活动或改进产品和服务的设计。

数据管理中的人工智能也可以帮助企业改善他们的数据治理实践。数据治理正变得越来越重要,尤其是在这个个人数据保护至关重要的时代。通过自动化某些数据管理任务,如数据质量检查和数据安全,AI 可以帮助企业确保他们的数据是准确、一致和安全的。这有助于降低错误和数据泄露的风险,提高企业数据的整体质量和完整性。

数据管理的一个重要组成部分是主数据管理(MDM)。

人工智能越来越多地被用于改善组织中主数据管理(MDM)的流程。MDM 涉及主数据和参考数据的整合、治理和维护,而 AI 可以帮助自动化许多相关任务。

人工智能改进 MDM 最明显的方式是通过自动化数据清理和验证。数据清理是识别、删除和/或纠正数据中错误的过程。这是 MDM 流程中的一个重要步骤,因为它有助于确保数据做好进一步处理/分析的准备。在这个阶段,机器学习算法被用来识别和纠正数据中的任何错误/不一致。

人工智能可用于以多种方式支持数据清理。例如,人工智能算法可以被训练来检测和纠正数据集中的错误或不一致,如拼错的名称、不正确的日期或重复的条目。此外,人工智能还可以用于对数据进行分类,并将其分配到适当的类别或组中。这有助于确保以一致和有意义的方式组织信息,使其更易于分析。

在较小的程度上,人工智能算法也可以用于根据数据管理员正在处理的数据,向他们提供建议或推荐。例如,人工智能系统可能会对看起来不正确或不合适的数据提出可能的更正或分类。然后,数据管理员可以审查这些建议,并根据他们对数据的了解和理解,决定是接受还是拒绝这些建议。这有助于提高数据清理过程的效率和准确性。

因此,信息的准确性得到了提高,同时验证数据的时间和资源显著减少。

在大量数据中发现错误/不一致对人类来说是一个挑战,但对人工智能来说不是:)|图片来自 flaticon.com

除了自动化数据清理和验证,AI 还可以通过自动化数据丰富来帮助改进 MDM。数据丰富是添加额外信息以使某些数据更有用的过程。它包括交叉检查来自各种来源的不同数据,寻找一种可能的关系,在这种关系中,某些数据的一个属性可以补充另一个数据缺少的属性。这是人工智能的完美竞技场,因为大型数据集经常被包括在内。

人工智能在数据丰富方面的用例是大量的。例如,可以训练人工智能算法从各种来源中自动识别和提取相关数据,如非结构化文本文档或社交媒体帖子。这有助于扩展可用于分析的数据的范围和深度。

数据丰富的一个常见任务是完成数据中缺失的属性。人工智能算法可以通过分析现有数据集中的模式和趋势来训练预测缺失或不完整的数据。这通常是使用机器学习技术来完成的,其中 AI 系统在包含某些数据点的已知值的大型数据集上进行训练。

随着人工智能系统处理这些数据,它学会了识别不同数据元素之间的模式和关系。例如,它可能知道某些值的组合更有可能指示特定的结果。

当 AI 系统收到一个缺失或不完整的新数据点时,它可以使用它所学习的模式和关系来预测该数据点的可能值。然后,该预测可用于填充缺失或不完整的数据。

数据丰富涉及各种来源和大量数据|图片取自flaticon.com

然而,人工智能在数据管理领域的应用并非没有警告。有几个关键挑战需要被克服,数据管理中的人工智能才能发挥其全部潜力。最大的挑战之一是需要高质量的数据。为了让人工智能算法有效工作,它们需要在大型、多样化和准确的数据集上进行训练。这在实践中可能很难实现,因为许多企业都很难及时有效地收集和清理他们的数据。

另一个挑战是需要熟练的人工智能专家。虽然人工智能算法可以自动化许多数据管理任务,但它们仍然需要人工监督和专业知识才能有效使用。这意味着企业需要投资培训和雇佣人工智能专家,他们可以在数据管理流程中设计、实施和管理人工智能系统。已经与组织合作的现有数据管理员也必须接受培训,以便与 AI 合作。

然而,对于那些能够克服挑战的人来说,人工智能在彻底改变数据管理领域的承诺仍然有效。同样,我们的最终目标是自动化管理和维护信息所涉及的许多任务。通过这样做,组织可以实现一致的结果,并释放宝贵的人力资源,以更加专注于更高层次的任务。

对于规避风险的强化学习,奖励是不够的

原文:https://towardsdatascience.com/reward-is-not-enough-for-risk-averse-reinforcement-learning-64133b63950b

为什么我们不能通过合理设置奖励来解决风险敏感性问题呢?

TL;博士:风险规避在许多 RL 应用中是必不可少的(例如,驾驶、机器人手术和金融)。一些修改后的 RL 框架考虑了风险(例如,通过优化回报的风险度量而不是其预期),但提出了新的算法挑战。相反,人们通常建议坚持旧的良好的 RL 框架,只设定奖励,这样负面结果就会被放大。不幸的是,如下所述,使用对重新定义的回报的期望来模拟风险通常是不自然的、不切实际的,甚至在数学上是不可能的,因此不能取代风险度量的显式优化。这与决策理论的类似结果一致,在决策理论中,风险优化不等同于期望效用最大化。

另见我的相关帖子关于如何实际做避险 RL【neur IPS 2022】,以及如何反正知道什么时候出问题【ICML 2021】。

背景:为什么是风险厌恶型 RL?

强化学习(RL)是机器学习的一个子领域,专注于顺序决策。在典型的设定中,一个特工被训练在中操作;每一集都需要做出一系列的决定(动作),每一个决定都会导致奖励,而累积的奖励形成了该集的结局(回报 ) 。主体的标准目标是找到最大化期望收益E【R】的决策策略。

强化学习图解(图片由 L. Weng 提供)

虽然 RL 已经在各种游戏和模拟中展示了令人印象深刻的结果,但它仍在努力为现实世界的应用服务。一个挑战是 RL 的许多自然应用(如驾驶、机器人手术和金融)的高风险敏感性:虽然允许游戏机器人偶尔出现故障,但自动驾驶汽车必须在任何情况下都能可靠地运行。对风险的敏感性也反映了许多应用中自然的用户偏好[ 1 ],因为规避风险是一种常见和普遍的行为,被认为根植于人类进化[ 2 ]。

风险规避优化的必要性推动了对设置的研究,在设置中收益的风险度量被最大化,而不是其期望值。特别令人感兴趣的是风险条件值(CVaR,也称为预期短缺),它衡量回报率分布中 α 最低分位数(0 < α < 1 )的平均值(与预期相反,它衡量整个分布的平均值)。它是一个连贯的风险度量(事实上,任何连贯的风险度量都可以写成 CVaR 度量的组合[ 3 ])。CVaR 广泛应用于金融风险管理[ 456 甚至银行业监管8;最近在医药、能源生产、供应链管理和调度方面也有所应用。CVaR 优化也在 RL [ 101112 ]的框架下进行了研究,例如针对驾驶[ 13 ]和机器人[ 14

CVaR 图解:分布尾部的平均值(图片由作者提供)

但是 CVaR 优化更难!我们不能减少到标准 RL 吗?

优化像 CVaR 这样的风险衡量标准是一个巨大的挑战。在政策梯度方法中,关注最糟糕的情景会导致代理人忽略大部分一般数据(样本无效率)和特定的成功策略(对成功的盲目 ) [ 12 )。在基于价值的方法中,对风险度量的天真规划会导致不一致的风险规避(即,通过在每个时间步合理地规避风险,我们最终会在整个事件中过于保守)。

那么,如果我们希望优化 CVaR,我们应该怎么做呢?虽然某些解决方案解决了上述限制,但是一些解决方案建议简单地绕过复杂性:代替

建议简单地用一些放大负面结果的 r' 代替奖励 r ,解决标准 RL 问题 wrt r' :

例如,当训练一个汽车司机时,不要优化 CVaR——只给事故分配非常负面的奖励!这遵循了奖励足够了 [ 15 ]的更一般方法的精神,并且可能被认为既简化了算法又反映了我们的“真实”效用函数。在决策理论中,这种方法被称为期望效用最大化16

奖励是不够的

作者图片

在不同的应用中,平均回报和 CVaR 回报都是合理的目标。如上所述,CVaR 优化在许多风险敏感问题中有很强的动机。在这种情况下,不幸的是,通过替换奖励来将 CVaR 优化简化为均值问题通常是不可行的。这个结果在决策理论中是众所周知的,其中期望效用最大化不同于 CVaR 优化(除非有一个策略严格支配所有其他策略)[ 16: Section IV ]。我们在 RL 的上下文中讨论这种差异,其中时间结构形成了一个额外的因素。具体来说,我们提出以下主张:

  1. 很难量化我们“真正的”效用函数。
  2. 量化效用函数目标是不够的:我们的目标是根据回报定义的,而简化为均值优化要求我们重新定义回报。从回报到回报的效用转换通常是不切实际的,甚至在数学上是不可能的(见上面 MDP 的例子)。
  3. 即使我们能够设计出忠实反映我们目标的奖励,它们的优化仍然会受到与风险度量优化类似的限制。

为了方便起见,下面我们以不同的顺序(1,3,2)讨论索赔。

(1)“真正的”奖励是未知的

设计正确反映我们风险厌恶的奖励是一项艰巨的任务。考虑一下驾驶问题:为了防止一起车祸,你愿意在路上多花多少分钟?注意,答案不是无限的,否则汽车就会灭绝。“正确”的答案是未知的,通过猜测,我们可能会鼓励不受欢迎的行为——要么太大胆,要么太保守。

当然,CVaR 优化也需要我们量化自己的风险厌恶水平( α )。然而,这种选择可以说是更加直观和普遍的:而不是“一次事故和一分钟的交通流量之间有多少个数量级?”,我们有“我们应该根据 α=10% 最坏情况,还是 α=1% 最坏情况来做决策?”。

事实上,我们的实际驾驶实践可以说更类似于 CVaR 优化:在我们做出道路决策之前,没有我们隐含分配给事故和浪费时间的内部价值。相反,我们想象结果并据此行动。如果我们厌恶风险,我们会想象更糟糕的结果(例如,害怕另一条车道的车突然向我们的车道移动),从而导致更安全的行为。这正是 CVaR 优化的工作方式:代理学习在最差结果的条件下行动。

(3)重新设计的奖励可能只是将问题转移到其他地方

因此,低于事故成本会容忍太多的事故,而高于事故成本可能会导致我们呆在家里。但是,如果我们神奇地知道一次事故的“正确”成本,即在路上浪费的时间,会怎么样呢?由于事故本来就很少发生且代价高昂,安全驾驶的天真优化仍然会受到有效回报稀少和样本低效的影响。因此,即使我们这次没有将问题公式化为 CVaR 优化,来自风险厌恶 RL 的方法可能仍然是令人感兴趣的(例如,在[ 12 ]中对某些类型的事件进行过采样)。

(2)规避风险的回报规避风险的回报

考虑一个金融投资组合,回报是年利润,回报是日利润。想想你的厌恶风险的朋友,他讨厌亏损:每年的亏损让他付出的心理健康代价是年利润的三倍。与其纠结于风险度量,为什么我们不能简单地用平凡的效用函数转换结果: *U(利润)=利润如果利润≥0,否则 3 利润

为了回答这个问题,我们应该明确我们是将这个结果转换应用于回报还是奖励。如果我们转换回报,我们不再遵循预期累积回报的标准目标,事实上我们优化了回报的风险度量。如果我们转换奖励,那么我们就变成了一个标准的 RL 问题,但它不再考虑我们的实际目标,因为效用是根据年利润定义的,而奖励是根据日来定义的。今天亏了 10 美元,明天赚了 20 美元,仍然为年利润贡献 -10+20=+10 美元——而不是 -310+20=-10 美元!也就是说,对回报的风险厌恶不会直接转化为回报,而一个天真的转化通常会导致过度保守的政策(例如,每天避免损失)*

在上面的例子中,还不清楚如何定义一个回报函数来考虑风险规避。期望的回报函数应该取决于环境内的结构(例如,几天内的相关性)。从这个意义上说,我们需要提前解决问题,这样我们就可以定义奖励,让我们再次解决问题…

但是,即使这种不切实际的解决方案也不总是可行的。

索赔:存在一个 MDP (S,A,P,r,H) ,使得对于任何重新定义的报酬函数r’,CVaR 最优策略 wrt 到 r 不同于均值最优策略 wrtr’

证明:技术证明在单独文件中提供。上图中的 MDP 抓住了主要思想。

备注:如果状态除了奖励还可以修改,那么在某些情况下,CVaR 可以化简为一个扩展状态空间上的优化[ 11 ]。自然,这带来了新的困难,特别是增加了样本的复杂性,从而限制了可伸缩性。替代方法通常可以优化 CVaR,而不会显著增加样本复杂性[ 12 ]。这可以被视为类似于非马尔可夫决策过程:这样的问题可以在数学上简化为 MDP(通过扩展状态以包括历史);然而,实际方法使用专用算法(例如,通过向代理添加存储单元)来解决这些问题。

我们可以* 化简为均值优化吗?*

根据上面的讨论,我们可以标记 CVaR 优化可以安全地简化为标准 RL 问题的某些情况。首先,当然,我们必须相信我们的效用函数——我们赋予各种结果的价值。其次,高风险回报和高风险回报之间的不一致必须可以忽略不计。例如,如果极端负回报主要是由单个极端负回报(如车祸)而不是一系列负回报(如财务损失)引起的,那么这是正确的。或者,在小规模问题中,我们可以通过扩展状态空间[ 11 ]来应用缩减。

此外,虽然 CVaR 优化非常有用,但并不是每个看似风险敏感的问题实际上都遵循 CVaR 目标。例如,如果奖励确实是可加的,并且可以可靠地量化,并且 i.i.d .测试集的数量足够大,那么预期回报就是一个合理的优化目标(这要归功于集的中心极限定理)。

结论

以符合标准算法的方式编写问题是应用机器学习领域中的一个关键步骤,因为它允许我们利用该领域中的伟大算法。然而,在某些情况下,问题不能适应,算法必须改为修改。在本文中,我们解释了为什么风险厌恶 RL 是这些情况之一。幸运的是,最近的工作提出了 RL [ 12131417 中风险度量优化的有效算法。

最后,我要感谢阏氏·曼诺尔、尤里·加多和伊莱·梅洛姆对本文的反馈和有益建议。

参考

[1] 前景理论:风险下的决策分析,卡内曼和特沃斯基,计量经济学 1979**

[2] 风险敏感性作为一种进化适应,辛慈、奥尔森、阿达米和赫特维格,自然 2015**

[3] 关于预期缺口作为连贯风险度量的意义,Inui 和 Kijima,银行杂志&金融 2005**

[4] 条件风险值的优化,Rockafellar 和 Uryasev,风险杂志 2000**

[5] 关于在险价值和条件在险价值的几点注记,Pflug, 2000

[6] 风险值与非独立信用风险投资组合的预期短缺:概念和实践见解,Frey 和 McNeil,银行杂志&金融 2002**

[7] 预期亏空及超出,Tasche,银行学杂志&金融 2002**

[8] 回测预期缺口,亚齐德和策克利,2014 年风险**

[9] 金融之外的条件风险价值:一项调查,菲利皮、瓜斯塔罗巴和斯佩兰萨,运筹学国际交易 2019**

[10] 通过采样优化 CVaR,Tamar,Glassner 和 Mannor, AAAI 2015

[11] 风险敏感和稳健决策:CVaR 优化方法,Chow,Tamar,Mannor 和 Pavone, NIPS 2015

[12] 高效的风险厌恶强化学习,格林伯格,周,加瓦姆扎德和曼诺尔, NeurIPS 2022

[13] 最坏情况政策梯度,唐,张,Salakhutdinov, CoRL 2019

[14] 基于视觉的机器人抓取的分位数 QT-Opt,博德纳尔等人,机器人科学与系统 2020**

【15】奖励够了,西尔弗,辛格,普雷科普和萨顿,人工智能 2021**

[16] 预期缺口与风险价值的比较分析(2):预期效用最大化与尾部风险,Yamai and Yoshiba,货币与经济研究 2002**

[17] 分布强化的隐式分位数网络 learning‏ ,达布尼,奥斯特罗夫斯基,西尔弗和穆诺斯, ICML 2018

Rfviz V2:一个交互式可视化软件包,用于解释 R

原文:https://towardsdatascience.com/rfviz-part-2-5dfa5964c638

从随机森林的创造者的原始工作来看,R

图片通过马修·史密斯上的 Unsplash

去年九月我写了这篇文章。它引入了 Rfviz:一个用于解释 R 中的随机森林的交互式可视化包。它是 Leo Breiman 和 Adele Cutler 的基于 Java 的原始可视化包到 R 的翻译(Breiman 和 Cutler,2004)。这个包的后端是使用 R 包 loon 和为探索性可视化设计的可扩展交互式数据可视化系统构建的。(奥尔德福德和沃德尔,2018 年)。

此 R 包中使用的三个图之一,经典度量多维标度近似度二维散点图已更新为三维。本文将尝试展示三维散点图的新功能。

此升级的主要功能是可视化随机森林模型如何在树中对数据进行分组。

这里将展示的技术可以应用于一系列问题,如无监督聚类和聚类识别。

数据

在本指南中,我们将使用虹膜数据集鱼市场数据集。这两者都是多类分类数据集。

Iris 数据集在知识共享署名 4.0 国际 (CC BY 4.0)许可下获得许可。这允许为任何目的共享和改编数据集,只要给予适当的信用。鱼市场数据集是免费的,在 GPL2 许可下发布。

Iris 数据集在 R 内自动可用,而 Fish 数据集可在这里下载

虹膜数据

library(rfviz)
library(tidyverse)
head(iris)
unique(iris$Species)

Iris 数据集有 3 种花以及关于萼片和花瓣测量的相应特征。

作者图片

作者图片

Fish <- read_csv('Fish.csv')
head(Fish)
unique(Fish$Species)

鱼数据集有 7 种鱼,以及相应的重量、长度、高度和宽度测量值。

作者图片

作者图片

使用这些数据集,让我们使用随机森林在 R 中进行无监督的聚类,并使用 Rfviz 将其可视化。

随机森林 邻近性 +PCA

流程如下:

  1. 建造 n 个随机的森林树木。
  2. 构建树后,将所有数据(包括训练数据和 oob 数据)沿树向下传递,并记录两个观察值在所有树的同一个终端节点中结束的次数。
  3. 将两个观察结果出现在同一终端节点的次数除以树的总数。这是这两个观察值之间的接近分数。
  4. 对所有观测值对重复此操作,以获得邻近矩阵。
  5. 对邻近矩阵应用主成分分析,并取前 3 个主成分。

R 中的 Rfviz 包完成这些步骤,然后在可视化工具中绘制数据。

虹膜数据示例

set.seed(123)
#Run unsupervised Random Forests and calculate the proximity matrix
rfprep <- rf_prep(x=iris[,1:4], y=NULL)
#Plot the data in the visualization tool
myrfplots <- rf_viz(rfprep, input=TRUE, imp=FALSE, cmd=TRUE)

作者图片

这个可视化工具实例由一个 Loon Inspector 工具和两个绘图组成。Rfviz 中还有第三个图,即重要性分数图,但它在这里不用于无监督的随机森林。

  1. Loon Inspector 工具:一个帮助与数据交互的实用工具。
  2. 输入数据图:一个输入数据的平行坐标图。
  3. 度量多维标度邻近图:邻近矩阵的前 3 个主要分量的图。

接下来,按照以下步骤操作:

  1. 单击度量多维标度邻近度图。
  2. 按键盘上的“r”进入旋转模式。一个带有“旋转模式”的小文本框将出现在图的顶部。
  3. 单击并拖动鼠标以改变图中的位置。
  4. 再次按“r”退出旋转模式。

作者图片

5.通过单击并拖动鼠标,选择接近度图顶部的数据分组。

作者图片

7.回到休息状态,运行:

group1 <- iris[myplots$cmd['selected'],]
head(group1)

作者图片

summary(iris[myplots$cmd['selected'],]) 
#or
summary(group1)

作者图片

在事先不知道它是什么物种的情况下,随机森林将 Setosa 物种分组在一起。

8.选中该分组后,单击“修改”部分下的蓝色。

9.再次旋转绘图。

作者图片

10.选择另一个分组。

作者图片

11.查看 R 内选定的数据:

group2 <- iris[myplots$cmd['selected'],]
summary(group2)

作者图片

所以第二组主要是海滨种类。

12.重新着色并选择最后一部分数据。

作者图片

注意:要将图形恢复到原始位置,请单击“移动”部分下检查器工具中最右边的按钮。

作者图片

作者图片

13.查看 R 内选定的数据:

group3 <- iris[myplots$cmd['selected'],]
summary(group3)

作者图片

最后一组数据主要是杂色的。

然后我们可以比较这三个分组。查看组 1、2 和 3 的汇总,我们可以看到在输入数据图中直观看到的数据值的确切差异。

第一组:

第二组:

作者图片

第三组:

作者图片

4 个组别的独特之处:

第 1 组:刚毛种,花瓣值较低。长度,花瓣。宽度和萼片。长度和萼片宽度的较高值

第 2 组:多为海滨锦鸡儿属植物,花瓣价值较高。长度,花瓣。宽度和萼片长度

第 3 组:多数为杂色种,萼片值较低。萼片的宽度和中值。长度,花瓣。长度和花瓣宽度

虹膜示例结论

根据无监督随机森林模型对数据进行分组的方式,我们可以直观地识别 Iris 数据集中的三个分组。这些分组最终大多是不同种类的花。在大多数随机森林模型中,我们可以期待相同类型的结果。同样,我们能够在输入数据图上直观地看到三个分组之间的差异。然后我们研究了 r 中这些差异的精确值。

然而,并非每个案例都如此简单。让我们试试鱼的数据集,它有 7 种不同的鱼。

鱼类数据示例

set.seed(123)
#Calculate Proximity Matrix and prepare for visualization
rfprep <- rf_prep(x=Fish %>% select(-Species), y=NULL)
myrfplots <- rf_viz(rfprep, input=TRUE, imp=FALSE, cmd=TRUE)

作者图片

旋转邻近图,我们可以看到它看起来像一个螺旋。

作者图片

我们从哪里开始?让我们在输入数据图上挑选一些明显的分组。

  1. 拖动并选择输入数据图上高度数据的较高值
  2. 将数据重新涂成绿色。
  3. 通过单击 Loon Inspector 的“按颜色”部分下的绿色方块再次选择数据。

作者图片

我们可以看到,在一些额外的旋转之后,在邻近度图上,数据是如何与其他数据分开分组的。

group1 <- Fish[myplots$cmd['selected'],]
summary(Fish[myplots$cmd['selected'],])

所选的鱼都是单一品种,鳊鱼。

4.在 Loon Inspector 的“修改”部分点击“停用”。这将暂时忽略图中的数据。在任何时候,您都可以点击“重新激活”,数据将再次出现在图上。

5.选择输入数据图上左侧“宽度”的较高值。

group2 <- Fish[myplots$cmd['selected'],]
summary(group2)

鱼数据结论

通过使用输入数据图选择数据,我们能够识别另一种鱼类。我们不会去分离和查看所有的数据。那将由你自己去尝试。但是,我们可以看到,我们可以很快地从视觉上识别鱼的种类。

总体结论

通过使用 Rfviz 和无监督随机森林,我们可以直观地查看数据和随机森林模型的输出,以识别数据中的重要分组。

同样的方法可以应用于各种问题,例如:

  1. 根据分组方式直观地确定最佳聚类数。这可以与其他标准的无监督聚类方法一起使用。

2.通过识别客户群来创建用于营销目的的人物角色。

3.快速识别数据集中数据的类或组之间的关键差异。

总体结论

总的来说,我希望你能看到这个工具在可视化随机森林方面的价值。幸运的是,利奥·布雷曼和阿黛尔·卡特勒参与了创造随机森林的工作,因为这是一个我们都喜欢的无价模型。感谢你的阅读!

如果你有兴趣的话,这里的是这个 R 包的另一个用例。

参考文献:

布雷曼,2001 年。“随机森林。”机器学习http://www.springerlink.com/index/u0p06167n6173512.pdf

布雷曼,我,和一个卡特勒。2004.随机森林https://www . stat . Berkeley . edu/~ brei man/random forests/cc _ graphics . htm

C Beckett,Rfviz:R 中随机森林的交互式可视化包,2018,https://chriskuchar.github.io/Rfviz.

费希尔河..(1988).艾瑞斯。UCI 机器学习知识库。

奥尔德福德和沃德尔出版社(2018 年)。Cran/Loon:这是 cran R 软件包仓库的只读镜像。loon——交互式统计数据可视化。首页:http://Https://great-northern-diver.github.io/loon/**报告此包的 bug:Https://github.com/great-northern-diver/loon/issues GitHub。检索于 2022 年 2 月 23 日,来自https://github.com/cran/loon

Pyae,A. (2019 年 6 月 13 日)。鱼市场。卡格尔。检索于 2022 年 2 月 21 日,来自https://www.kaggle.com/aungpyaeap/fish-market

基于主成分分析的 RGB 彩色图像压缩

原文:https://towardsdatascience.com/rgb-color-image-compression-using-principal-component-analysis-fce3f48dfdd0

主成分分析在降维中的应用

(图片作者)

之前,我们已经讨论了如何使用 PCA 来压缩 MNIST 数字数据集中的灰度图像。你可以在这里阅读那篇文章。

这是之前发表文章的高级版本。这一次,我们应用相同的概念来压缩 RGB 彩色图像,而不是灰度图像。

RGB 和灰度图像的区别

RGB 和灰度图像之间的主要区别在于图像拥有的颜色通道数量。RGB 图像有三个颜色通道:红色、绿色和蓝色,而灰度图像只有一个颜色通道。

RGB 图像的红、绿、蓝三色通道(图片由作者提供)

灰度图像的单色通道(图片由作者提供)

另一个区别是用 ML 和 DL 表示 RGB 和灰度图像。灰度图像由二维(2D) NumPy 数组表示。它也可以用一个扁平的一维(1D) NumPy 数组来表示。

展平灰度 图像(图片由作者提供)

RGB 图像由三维(3D) NumPy 阵列表示。由于 RGB 图像中有三个颜色通道,我们需要一个额外的维度来表示颜色通道。拼合 RGB 图像不好,因为它会丢失大量重要信息。

注:要通过例子和其他图像基础知识了解更多 RGB 和灰度图像的区别,请参考我写的以下文章。

降维图像压缩的基本思想

图像压缩是一种在尽可能保持图像质量的同时最小化图像字节大小的技术。它适用于降低图像在存储和传输时的成本。

维度缩减是减少数据集中的要素(变量)数量,同时尽可能保留原始数据中的方差的过程。

PCA 是可用于压缩图像的降维技术之一。这里有一个大概的想法。

压缩灰度图像

处理灰度图像非常容易,因为它们只包含一个颜色通道。灰度图像由二维(2D) NumPy 数组表示。我们将该阵列作为我们的 2D 数据矩阵,并应用 PCA 来减少矩阵中的列(特征)数量。根据我们选择保留的组件数量,我们会损失一些图像质量。

压缩 RGB 图像

处理 RGB 图像并不容易,因为它们包含三个颜色通道。RGB 图像由三维(3D) NumPy 阵列表示。我们不能用 3D 阵列做 PCA。所以,我们必须将原始图像分割成红色、绿色和蓝色通道。每个颜色通道由二维(2D) NumPy 数组表示。然后,我们对每个通道进行主成分分析,最后合并三个通道得到压缩图像。

如果我们从每个通道数据中损失 3%的变化,合并三个颜色通道后我们损失的总变化将是:

(3 + 3 + 3)% = 9%

大约会损失 9%的图像质量。这不是一个重要的质量。

一个压缩 RGB 图像的真实例子

关于我们使用的图像

我们使用下图来解释如何应用 PCA 来压缩 RGB 图像。下面的图片是我在母亲的花园里捕捉到的。出于教育目的,可以免费下载。所以,请随意下载图片来练习我们今天讨论的内容。

Flower.jpg:我们今天使用的图片(作者图片,取自我母亲的花园)

加载图像

让我们使用 matplotlib imread() 函数加载图像,假设它保存在当前工作目录中。

import matplotlib.pyplot as pltRGB_img = plt.imread("Flower.jpg")

下面我们来检查一下。

print(type(RGB_img))
print(RGB_img.shape)

(图片由作者提供)

我们的图像存储在 3D NumPy 数组中。它有 400 x 400 像素和 3 个颜色通道。

显示图像

现在,我们以原始大小显示图像。为此,我们使用 matplotlib imshow() 函数。

plt.figure(figsize=[7.3, 7.3])
plt.imshow(RGB_img)

原花图(图片由作者提供)

将图像分割成颜色通道

然后,我们将原始图像分成红色、绿色和蓝色通道。为此,我们使用 OpenCV split() 函数。

import cv2b, g, r = cv2.split(RGB_img)

默认情况下,RGB 颜色通道在 OpenCV 中以相反的顺序存储。这就是为什么我们将变量声明为b, g, r而不是r, g, b

下面我们来检查一下。

print("Red channel")
print(type(r))
print(r.shape)
print("\nGreen channel")
print(type(g))
print(g.shape)
print("\nBlue channel")
print(type(b))
print(b.shape)

(图片由作者提供)

每个颜色通道由一个 2D 数字阵列表示。每个通道的高度 x 宽度为 400 x 400。这意味着数组包含 400 行和 400 列(变量)。在这个上下文中,每个通道数据的维数是 400,因为有 400 列。我们应用主成分分析来减少这些列的数量。

显示颜色通道

现在,我们展示红色通道。

plt.figure(figsize=[7.3, 7.3])
plt.imshow(r)

花图像的红色通道(图片由作者提供)

同样,我们也可以显示其他两个颜色通道。然而,我不打算在这里展示它们。

应用 PCA

现在,我们应用主成分分析。

这里,我们通过使用 Scikit-learn PCA() 函数来应用 PCA。在应用 PCA 之前必须进行特征缩放,因为 PCA 方向对特征的相对范围非常敏感。作为可选部分,我将向您展示在应用 PCA 时进行特征缩放的重要性。

我们通常使用 sci kit-learnstandard scaler()函数进行特征缩放。然而,在这个例子中,我们使用下面的简单方法进行特征缩放。这是因为每个图像像素值由范围从 0 到 255(包括 0 和 255)的数字表示。

#(0, 255)/255 ~ (0, 1)r_scaled = r / 255
g_scaled = g / 255
b_scaled = b / 255

现在,我们将对红色通道数据运行 PCA() 函数。这里,临界超参数是 n_components 。首先,最好将其设置为None,这样 PCA()函数将保留所有组件(本例中为 400)。但是,我会将其设置为n_components=100并创建以下类型的图,帮助我们为 n_components 确定正确的整数值。

对红色通道数据应用 PCA(作者代码)

可视化组件的最佳数量(图片由作者提供)

最高的条显示红色通道数据的最重要组成部分。从上面的图中,只有前 25 到 45 个组件捕获了大部分数据。如果我们忽略 45 岁以后的所有成分,我们将只丢失很少的信息,因为它们在数据中捕捉到非常少量的变化。

这同样适用于其他颜色通道的数据。因此,我们用n_components=40n_components=25对每个颜色通道数据进行 PCA。在每种情况下,维度将分别减少 10 倍(400/40)和 16 倍(400/25)。

**n_components=40**

对 n_components=40 的 RGB 颜色通道应用 PCA(作者代码)

现在,我们检查以下内容。

(作者代码

(图片由作者提供)

可变性损失的总量可通过下式计算:

{(100-97.52) + (100-96.55) + (100-97.17)}% = 8.76%

压缩后的图像将损失原始图像质量的 8.76%。

现在,我们运行下面的代码将图像数据恢复到它的原始维度,以便可视化。

pca_r_org = pca_r.inverse_transform(pca_r_trans)
pca_g_org = pca_g.inverse_transform(pca_g_trans)
pca_b_org = pca_b.inverse_transform(pca_b_trans)

现在,我们使用 OpenCV merge() 函数将三个颜色通道合并成一个 3D NumPy 数组。

img_compressed = cv2.merge((pca_b_org, pca_g_org, pca_r_org))

现在,我们展示压缩的图像。

plt.figure(figsize=[7.3, 7.3])
plt.imshow(img_compressed)

n _ components = 40 的压缩花图像(图片由作者提供)

即使在压缩之后,我们仍然可以识别图像的重要部分。但是,维度降低了 10 倍!

**n_components=25**

这里,程序与前面的相同。因此,我不会向您展示代码。我只给你看用n_components=25应用 PCA 后的最终输出。

n _ components = 25 的压缩花朵图像(图片由作者提供)

这里,我们仍然可以识别图像的重要部分。但是,维数降低了 16 倍!

特征缩放的重要性(可选)

作为可选部分,我将向您展示在我们对数据集应用 PCA 时进行要素缩放的重要性。这里,我们使用 Scikit-learn PCA()函数来应用 PCA。PCA()函数使用协方差矩阵进行奇异值分解。图像数据中特征的相对范围也显著不同。因此,我们需要在应用 PCA 之前对特征进行标准化。

下图显示了在不执行 PCA 的情况下应用 PCA 后的输出。即使我们设置了n_components=100,转换后的数据也不能代表原始数据。

应用 PCA 后输出图像,不进行特征缩放(图片由作者提供)

PCA 方向对特征的相对范围高度敏感。如果我们没有在相同的尺度上获得所有的特征,PCA 优先考虑由于特征的相对范围而产生的方差,而不是在搜索主成分时数据中存在的真实方差。因此,这些主成分不能代表原始数据。这就是为什么上面的图像不能显示原始的花。

摘要

现在,考虑下面的图像网格。

花卉图像对比(图片由作者提供)

  • 左上:400 维的原始花卉图像。
  • 右上:只有 40 维的压缩花图像。维数减少了 10 倍,同时保持了原始图像 91.24%的质量!我们仍然可以识别图像中的重要部分。
  • 左下:只有 25 维的压缩花图像。维数减少了 16 倍,同时保持了原始图像 83.41%的质量!我们仍然可以识别图像中的重要部分。
  • 右下:不做特征缩放应用 PCA 后的花朵图像输出。转换后的数据不能代表原始数据。我在这里向您展示了在应用 PCA 之前进行特征缩放的重要性。

今天的文章到此结束。如果你对这篇文章有任何疑问,请在评论区告诉我。

感谢阅读!

下一篇文章再见!一如既往,祝大家学习愉快!

阅读下一篇(推荐)

主成分分析—回答 18 个问题(关于主成分分析的大多数问题的一站式解决方案)

https://rukshanpramoditha.medium.com/principal-component-analysis-18-questions-answered-4abd72041ccd

成为会员

如果你愿意的话,你可以注册成为会员,以获得我写的每一个故事的全部信息,我会收到你的一部分会员费。

https://rukshanpramoditha.medium.com/membership

订阅我的电子邮件列表

订阅我的电子邮件列表,再也不会错过精彩的故事了。我一点击发布按钮,你就会在收件箱里收到每一篇文章。

https://rukshanpramoditha.medium.com/subscribe

鲁克山·普拉莫蒂塔
2022–03–29

RGB GAF 图像:解决格拉米角场成像一个弱点的可能方案

原文:https://towardsdatascience.com/rgb-gaf-image-a-possible-solution-to-one-weak-point-of-gramian-angular-field-imaging-ffc6b31edfbe

为什么这种时间序列成像方法是不完美的,并建议解决这个问题

图片作者:左边和中间是简单的 GAF 图像,右边是相应的 RGB GAF 图像

包括我在内的数据科学家在工作中遇到了大量的时间序列。尽管在自然语言处理、语音识别和计算机视觉方面取得了成功,但遗憾的是,从深度学习发展而来的技术未能在时间序列方面取得许多可匹敌的发展。时间序列成像是一个利用计算机视觉的最新发展带来的技术和见解的概念,因此受到了关注,因为它允许机器“视觉识别时间序列”。

GAF 成像是最流行的时间序列成像算法之一。首先由意大利卡利亚里大学数学和计算机科学系的师生团队提出,该方法将时间序列转换为图像,并因此使用 CNN 来识别预测的视觉模式。

然而,当使用 GAF 时,我遇到了一个可能的弱点,这促使我写这篇文章。接下来,你将会读到:

  1. 简单介绍一下 GAF 和方法可能有点关系。
  2. 我的建议是解决这个问题的一个可能的办法。

GAF 概述

格拉米角场(GAF)表示极坐标系统中的时间序列,而不是典型的笛卡尔坐标。

假设我们有一个范围在[-1,1]内的重定标时间序列,其形式为(x_1,x_2,…x_n)。将其转换到极坐标系统后,即θ_i=arccos(x_i),我们可以通过考虑每个点之间的三角和/差来轻松利用角度透视,以识别不同时间间隔内的时间相关性:在 n*n GAF 矩阵中,i-j 元素被定义为第 I 个和第 j 个角度之和的余弦或正弦。上图显示了在 GAF 影像中采用中间极坐标系步长进行笛卡尔坐标变换的时间序列。现在可以通过 pyts 中的相应功能轻松实现,pyts 是一个 python 包,结合了多种时间序列分类方法。

作者图片:从时间序列到 GAF

将矩阵的 i-j 元素转换回笛卡尔坐标:cos(θ_ I+θ_ j)= cos(θ_ I)cos(θ_ j)+sin(θ_ I)sin(θ_ j)= x _ I x _ j+√( 1-x _ I)√(1-x _ j)我们可以立即看到定义的以下性质:

  1. 当且仅当 x_i=x_j 时,元素等于-1。
  2. 当且仅当 x_i=x_j=0 时,元素等于 1。
  3. 如果 x_i=-x_j 而两者的绝对值都接近-1。这意味着元素的值不能捕捉相关性的符号。

在此快速说明一下,我不是 100%同意“格拉米”角场的名称,因为上面的定义没有给出一个真正的格拉米矩阵,根据线性代数理论,它的元素是由给定向量的内积给出的。

弱点

让我们取一个归一化的时间序列(x_1,x_2,…x_n)并用 GAF 方法来想象它。尽管在最初的工作中同时存在正弦和余弦函数,但现在我所回顾的大多数例子只使用余弦函数来获得 GAF 图像。如上一节所述,这种矩阵的 i-j 元素由 cos(θ_i+θ_j)得到,其中θ_i、θ_j 均以[0,𝜋]变化,其中θ_i=arccos(x_i),θ_j=arccos(x_j)。但是,我想指出的是,这样的转变并不是注射。
注意,对于[0,2𝜋]中的任何θ,我们在[0,2𝜋).]中也有(2𝜋-θ)回想一下,[0,2𝜋]中的余弦函数相对于θ=2𝜋是对称的,因此 cos(θ)等于 cos(2𝜋-θ).回到 GAF 矩阵,我们看到 cos(2𝜋-θ_i-θ_j)=cos(θ_i+θ_j,因此𝜋-θ_i 和𝜋-θ_j 给了我们与 GAF 矩阵的 i-j 元素相同的值。对于[0,𝜋]中的所有θ,我们知道𝜋-θ=arccos(-x),因此我们可以得到这样的事实,即时间序列(-x_1,-x_2,…-x_n)可以给我们相同的矩阵。重新开始,如果我们颠倒时间序列上每个点的符号,GAF 的变换会产生相同的图像。
下图显示了由两个时间序列获得的相同图像,其符号逐点相反。

作者提供的图片:从两个时间序列转换而来的同一个 GAF 图片

以金融预测为例,忽略迹象可能是一个问题,因为金融产品表现的绝对值不足以描述时间模式。想象一个股票价格在一段时间内上升,如果我们反转符号,我们将得到一个下降的时间序列。尽管两个变换的 GAF 图像是等效的,但是两个时间序列对应于完全相反的表现。

一种可能的解决方案:RGB GAF 图像

幸运的是,这个问题很容易解决,因为确实有两种类型的 GAF 形象。单个余弦具有唯一性的问题,但是一对余弦和正弦将消除该问题,因为这两个足以确定角度值。
我想从时间序列中创建一个 RGB 图像,而不是将时间序列编码成灰度图像。考虑创建这样一个 GAF 图像的以下代码:

from pyts.image import GramianAngularField
import numpy as npgasf = GramianAngularField(method='summation')
x_train_gasf = gasf.transform(x_train)
gadf = GramianAngularField(method='difference')
x_train_gadf = gadf.transform(x_train)
x_train_gaf=np.concatenate((x_train_gasf,x_train_gadf,np.zeros(x_train_gadf.shape)),axis=-1)

下图显示了一幅 RGB 图像,它是根据与上一部分相同的时间序列创建的,您可以看到,我们在同一幅图像中同时考虑了正弦和余弦值。

作者图片:RGB GAF 图片

结论

在这篇文章中,我简要地回顾了时间序列成像的 GAF 方法,并指出这种方法可能存在的弱点。然后我建议使用 RGB GAF 图像编码的正弦和余弦函数来解决方法的非唯一性问题。

瑞普·伯特:谷歌的妈妈来了

原文:https://towardsdatascience.com/rip-bert-googles-mum-is-coming-cb3becd9670f

Google MUM 解释:Google 的多任务统一模式背后是什么?

来源:照片由 Ialo Hernandez 在 Unsplash 上拍摄

2021 年 5 月 20 日,谷歌举行了开发者大会 I/O,并宣布了他们搜索引擎的新算法: MUM,一个多任务统一模型 [1]。在过去的两年里,BERT 是他们搜索引擎的基础模型。伯特是一个惊人的释放,是最先进的,直到现在,直到妈妈来了。算法 BERT 在自然语言处理领域发生了很大的变化,并在数千甚至数百万个不同的应用和行业中得到应用。

BERT 是从传统的基于关键字的搜索转向更复杂的搜索查询的开端。它有助于以比以往更高的精度识别实体和实体之间的关系。有了 BERT,Google 不仅可以提供按索引排序的结果和链接参考,还可以在 Google 中显示即时结果。

谷歌特色片段立即显示结果。来源。自己的形象。

MUM 的主要特点是什么?

MUM 继承了这一传统,并试图更加关注复杂的长尾搜索查询,尽可能高效地解决用户的请求。

妈妈输入数字:

MUM 有潜力改变谷歌帮助你完成复杂任务的方式。MUM 使用 T5 文本到文本框架,比 BERT 强大 1000 倍。[2]

根据谷歌的说法,MUM 不仅比强大 1000 倍,在效率上得分也更高,而且它在三个主要类别中表现突出【2】:

  • MUM 是多模态并处理多种媒体类型
  • MUM 处理更复杂的搜索查询
  • 妈妈克服语言障碍

好了,这些是事实,但是让我们看看这意味着什么。

妈妈的多模态意味着搜索算法不仅能理解文本,还能理解视频、图像和音频文件。[2] 这样用户就可以把文字输入和图像输入结合起来。 [2]搜索某个图形图案的用户可以使用 google lens 输入包含该图案的图像,并且可以使用文本来丰富图像以获得更多细节和更好的结果,例如具有该图案的文本结构。

在谷歌博客的例子中,他们通过以下问题[2]展示了妈妈的能力:

来源:谷歌博客

在这种情况下,谷歌声明妈妈会理解你在比较两座不同的山。因为它知道你想为徒步旅行做准备,它会认为你正在秋天行走,并根据这个季节的天气、必要的装备、徒步旅行所需的训练以及更多信息提出建议。根据谷歌的说法,MUM 更有能力识别实体之间的关系,因此更适合更复杂的问题和长尾关键词/问题【2】。

此外,妈妈能够消除语言障碍,从原本是其他语言的搜索结果中传递答案。因此,如果你搜索有关富士山的信息,谷歌将有可能提供你的语言的结果,即使原始内容是在日本网站上找到的。

多任务统一模型算法背后隐藏着哪些技术?

当然,MUM 是一个巧妙选择的营销名称,让科技界可以轻松地交流新的谷歌算法(就像熊猫、企鹅公司一样)。算法背后真正的算法或机器学习系统尚未完全揭示。这是谷歌的策略,向世界温和地介绍他们新的令人惊讶的搜索引擎算法,不让黑暗中有太多的光明。

尽管如此,谷歌经常发表研究论文,介绍机器学习领域的新方法和改进。检查这些并把它们与 MUM 的新特性联系起来,很容易找到最有可能代表全新 MUM 算法的论文。因此,MUM 是一个算法星座,它构成了世界上最强大的搜索引擎的新搜索算法。这极有可能也是朝着正确方向迈出的又一步,以保持其地位和准垄断地位。

其中一篇论文是“重新思考搜索:让领域专家走出范儿”(梅茨勒等人。这解释了当前的搜索引擎对于导航和事务性搜索查询是有用的,但是在满足信息需求方面有困难。传统的信息检索系统(如 2018 年之前的谷歌)提供参考文献,但不提供直接信息[]。

当经历信息需求时,用户希望与领域专家合作,但是通常转向信息检索系统,例如搜索引擎。[5]

如果你在过去几年中使用过谷歌,你可能会看到一些发展,比如向用户提供直接信息的片段,而不是仅仅列出参考文献。这是谷歌将他们的搜索引擎变成领域专家的方式。向专家的转变已经从 BERT 开始,但现在应该进入下一个阶段。

经典的信息检索系统有一个过程,首先是索引,然后是检索,最后是参考文献的排序,也称为检索然后排序范式[5]。新的方法用一个统一的模型代替了这一部分,该模型在同一个组件中进行检索和排序。出于多任务学习的目的,系统基于 T5,当然,MUM 也基于 T5。

资料来源:Metzler,d .等人[4]。

另一篇论文,也是上一篇论文的合著者唐纳德·梅茨勒(Donald Metzler)写的,是 2020 年 8 月发布的“用户活动流的顺序专家的多任务混合】 (MoSE)。底层机器学习系统可以基于用户点击行为和服务器日志进行学习。换句话说,“顺序专家的多任务混合”试图揭示为什么用户有特定的点击和搜索查询历史。那么,第一个输入是如何影响随后的输入的,谷歌能够更早地交付一个(更好的)解决方案吗?这有助于理解如何解决更复杂的搜索查询。MoSE 系统“使用长短期记忆(LSTM)模拟连续用户行为”[4],因此不仅能够预测单个搜索查询的结果,还能够预测一组连续搜索查询的结果。

谷歌研究团队的论文基于两个任务,但它也进一步解释说,他们的目标是在未来将其扩展到更多的任务。该算法在 G Suite(如 Gmail)中的搜索任务上进行了测试和训练。MoSE 的平均错误率比现有模型低 10%。

如果你通读这篇论文,你可能会遇到两个不适合 MUM 算法的主要问题:在 MUM 算法的情况下,谷歌公布了该模型也是基于变压器的,MoSE 系统不包括任何变压器部件。

研究人员指出

MoSE 由通用构建块组成,可以轻松扩展,例如使用除 LSTM 之外的其他顺序建模单元,包括 GRUs、attentions 和 Transformers[4]

由于这篇论文已经在 17 个月前发表了,我相信谷歌团队已经为他们的新系统填补了这个空白,并通过使用 Transformers 对其进行了改进。

MUM 正在解决的另一个棘手问题是处理多个数据源,如图像、文本,将来还有音频和视频。在论文本身中,作者指出“[他们]没有显式地处理多模型数据,如图像或自然语言输入”[4],并进一步解释说“扩展 MoSE 以更好地处理此类数据将是有趣的”[4]。我很确定在过去的 17 个月里,Google 团队能够解决他们博客中宣布的前两种数据类型(图片和文本)。

结论

MUM 的首批功能已经由谷歌实现,但更多功能将在 2022 年推出。正如谷歌所说,多模态将首先用于图像和文本,随后也用于语音和视频。文章中提到的第一篇论文清楚地展示了如何将检索-排序过程转化为一个统一的模型。MUM 是多任务统一模型的简称。因此,第一篇论文使用 T5 模型来处理统一模型。第二篇文章讨论了多任务部分,展示了如何应用 LSTM 来更好地处理复杂的搜索查询。结合两篇论文,我们非常接近妈妈的样子。当然,还有更多的组件,可能还有更多的文件将为妈妈发挥作用。很明显,MUM 仍处于起步阶段,看到 MUM 在未来如何发展以及将会添加哪些功能将会令人兴奋。

感谢你的阅读,希望你喜欢。

消息来源

[1]维肖莱克,C. (2021 年)。麻省理工学院的“妈妈”也会谷歌一下。https://t3n . de/news/mum-Google-komplexe-suchanfragen-1379918/

[2]纳亚克,P. (2021)。MUM:理解信息的新人工智能里程碑。https://blog.google/products/search/introducing-mum/

[3] Nguyen,G. (2019)。常见问题解答:所有关于谷歌搜索中的 BERT 算法。https://search engine land . com/FAQ-all-about-the-Bert-algorithm-in-Google-search-324193

[4]梅茨勒等人(2020 年)。针对用户活动流的顺序专家多任务混合。https://storage . Google APIs . com/pub-tools-public-publication-data/pdf/63a 48017 f 06 CD 046 CBF 97 f 158195 e 9d 96 E8 b 2205 . pdf

[5]梅茨勒等人。艾尔。(2021).重新思考搜索:把领域专家从粉丝中培养出来。https://arxiv.org/pdf/2105.02274.pdfT21

大数据科学火花的升起

原文:https://towardsdatascience.com/rise-of-spark-for-big-data-analytics-e794ca75855d

Apache Spark 已经成为处理大数据的首选解决方案。让我们来看看 Spark 受欢迎背后的三个原因。

随着可用于处理和分析的数据量增加,我们看到了向分布式系统的缓慢但明确的转变(查看我关于分布式系统崛起的文章,特别是 Hadoop 这里)。然而,在 21 世纪初,数据科学和“大数据”的机器学习仍然具有挑战性。当时的尖端解决方案(如 Hadoop)依赖于 Map Reduce,这在一些关键方面存在不足

  • 在数据科学过程中,大部分时间花在探索性数据分析、特征工程和选择上。这需要对数据进行复杂的多步转换,很难仅使用 Hadoop 中的 Map 和 Reduce 函数来表示,这将花费大量开发时间并产生复杂的代码库。因此,需要一种支持复杂数据转换的解决方案。

使用 MapReduce 分析数据可以…..令人沮丧(照片由 Siora 摄影Unsplash 上拍摄)

  • 数据科学是一个迭代过程,Hadoop 中典型的 Map-Reduce 操作需要每次从磁盘读取数据,这使得每次迭代都非常耗时且成本高昂。因此,需要一种解决方案来减少数据科学过程每次迭代的时间。
  • 模型需要生产、部署和维护。因此,我们需要一个框架,不仅允许我们分析数据,还允许我们在生产中开发和部署模型。Hadoop 不支持前面提到的迭代分析,R/Python 等框架也不能很好地扩展到大型数据集。因此,需要一种解决方案来支持大数据的迭代分析,并生产生成的 ML 模型。

进入阿帕奇火花。

它在 2014 年首次发布时就考虑到了上述需求。Spark 保留了 Hadoop 的可伸缩性和容错性(查看我的 Hadoop 文章了解更多细节),并在此基础上构建了以下特性

  • 包括广泛的操作列表(除了 map 和 reduce 之外),允许仅用几行代码构建复杂的数据处理/分析系统。此外,为 Spark 开发的 MLlib 库有助于像使用 Scikit learn 一样直观地构建 ML 模型。这减少了开发人员/科学家的时间,并使代码库更易于维护。
  • Spark 利用一个有向无环图或‘DAG’(把它想象成一个流程图)来跟踪你想要对你的数据执行的操作。因此,与 Hadoop 不同的是,在 Hadoop 中,您会将一系列 Map Reduce 作业串在一起,每个作业都需要从磁盘读取和写入,而 Spark DAG 可以帮助您将操作串在一起,而不必写出中间结果。这意味着多步数据处理/分析作业将运行得更快。Spark 还能够在内存中缓存中间结果。这在机器学习中特别有用,在机器学习中,您可以执行预处理并缓存结果训练数据,以便在优化期间可以从内存中重复访问它(因为梯度下降等优化算法将多次迭代训练数据)。在 Hadoop Map-Reduce 中,必须从磁盘访问列车数据,这使得该过程非常耗时。

使用 Map Reduce 优化逻辑回归?可能要等很久了……(图片由 Unsplash 上的13 拍摄)

  • Spark 不仅可以以迭代和交互的方式分析数据(可以与 jupyter 笔记本集成),还可以构建生产级数据处理和机器学习管道。

这些特性使 Apache Spark 在过去十年中成为分布式数据科学和机器学习的首选框架,现在几乎可以在所有处理真正大数据的组织中找到。

本系列的下一篇文章将介绍如何使用 Apache Spark,从 Scala 的基础知识开始,Scala 是编写 Spark 程序的理想语言。

参考文献。

Sandy Ryza 等人在《Spark 高级分析:大规模数据学习模式》一书中指出了 Spark 在大数据分析方面的优势。

超参数调整期间过多多个局部最小值的风险含义

原文:https://towardsdatascience.com/risk-implications-of-excessive-multiple-local-minima-during-hyperparameter-tuning-fe6f517e57e8

我们的认识论局限和知识幻觉

使用 Matplotlib 的 plot_trisurf 进行 3D 可视化:由 Michio Suginoo 制作

超参数调整期间过多的多个局部最小值是模型性能对超参数值的微小变化高度敏感的症状,如上图所示。

当我在本机 XGBoost API 的超参数对 reg_alpha 和 reg_lambda 上执行网格搜索调优时,我遇到了这种非常崎岖不平的性能状况,有多次起伏。这只是我对房地产价格预测回归分析的一个组成部分。然而,在很大程度上,我全神贯注于以下问题。

  • 那些多个局部极小值是否意味着任何风险?
  • 如果有的话,那些是什么?

在这篇阅读中,我想分享我基于我的项目案例研究对这些问题的观察和思考。

A.超参数调整概述

这里,我只想对我在原始项目中执行的超参数调优过程做一个非常简要的概述。

a)超参数

我使用了 Python 的原生 XGBoost API

在原生 XGBoost API 中内置的许多超参数中,由于给定的计算资源可用性有限,本分析主要关注以下 8 个超参数。

关于这些超参数的定义,请参考原生 XGBoost API 的文档。

除了这 8 个超参数,我在调优过程中使用early _ stopping _ round调优了迭代次数num _ boost _ round

关于 XGBoost 超参数的更多内容,这里有一个 Jason Brownlee 写的很好的总结: 机器学习的梯度提升算法的温和介绍

b)调谐方法

理想地,在所有这 8 个超参数上的单个联合调谐将准确地捕获这些超参数之间所有可能的相互作用。然而,联合调谐具有巨大的搜索网格数据点,因此在计算上将是昂贵的。

为了节约分析的计算成本,我从 8 个参数对中选择了 4 个:即

  • (最大深度,eta)
  • (子样本,列样本 _ 字节树)
  • (最小 _ 孩子 _ 体重,伽玛),以及
  • (寄存器 _alpha,寄存器 _lambda)。

我对这 4 个超参数对中的每一个进行了成对调整。每次调整后,最佳调整结果用于替换超参数对的旧值。通过这种方式,每次调优都会逐步提高调优模型的性能。

这总体上减少了搜索网格数据点的数量,并以网格搜索的公正性为代价节约了调整过程的计算成本。当然,这里的基本假设是,通过 4 个成对调节获得的结果不会在本质上偏离在所有 8 个超参数上的理想单个联合调节。

在调优方法方面,我想强调几个重要的脚注。

c)第一次调谐结果

这里,我们有 4 个成对超参数调整结果的 3D 可视化。每个图表显示了每个成对超参数调整的性能情况。

使用 Matplotlib 的 plot_trisurf 进行 3D 可视化:由 Michio Suginoo 制作

使用 Matplotlib 的 plot_trisurf 进行 3D 可视化:由 Michio Suginoo 制作

使用 Matplotlib 的 plot_trisurf 进行 3D 可视化:由 Michio Suginoo 制作

使用 Matplotlib 的 plot_trisurf 进行 3D 可视化:由 Michio Suginoo 制作

如您所见,最后一个性能场景展示了与其他三个性能场景截然不同的行为。它产生了多个局部最小值和多个凹陷和凸起,并对超参数对的值的微小变化表现出极大的敏感性。相反,前 3 种情况呈现了相对一致的性能景观。

下面,我想介绍超参数对的第一个搜索网格设置。

**for reg_alpha in [0, 1e-2, 0.1, 1, 2, 3, 4, 8, 10, 12, 14] for reg_lambda in [0, 1e-2, 0.1, 1, 2, 3, 4, 8, 10, 12, 14] ]**

数据点之间的间隔故意设置得不均匀,以观察搜索网格的粒度对性能前景的影响。对于这两个超参数,我在 0 和 1 之间设置了一个更精细的粒度间隔,在 4 之后的范围内设置了一个更粗糙的间隔。

下表列出了搜索网格上的前 10 个最佳性能数据点。

制作人:杉尾道夫

该表确定了在搜索网格数据点(reg_alpha =0.1,reg_lambda=8.0)时的最佳性能;第二好的是(reg_alpha =3.0,reg_lambda=0.1)。

为了更深入地了解(reg_alpha 和 reg_lambda)的超参数对的不稳定性能状况,我沿着 reg_alpha 的前 2 个数据点值(即 reg_alpha = 0.1 和 3.0)对性能状况进行了切片。

对于每个切片,这里我沿着 reg_alpha 绘制了切片性能曲线,并在切片性能曲线上绘制了前 10 个最佳性能搜索网格数据点的表格。

制作人:杉尾道夫

制作人:杉尾道夫

沿着上面的 reg_alpha=0.1 切片的性能曲线确认了在(reg_lambda = 0.01、1.00 和 8.00)的 3 个局部最小值。在 0 和 2 之间有明显的下降和上升。在某种程度上,reg_lambda = 0.01 处的局部最小值和那些凹陷和凸起不会被捕获,如果我们没有在 0 和 1 之间的粒度局部搜索网格设置的话。

现在,让我们看看另一条沿 reg_alpha =3.0 切片的性能曲线及其前 10 位性能搜索网格数据点。

制作人:杉尾道夫

制作人:杉尾道夫

沿着(reg_alpha = 3.0)的性能状况的第二部分确定了在(reg_lambda = 0.01)和(reg_lambda = 1.0)之间形成的尖锐深渊。深渊形状的强烈凹凸不平(沿 reg_lambda = 0.01、0.1 和 1.00)由局部搜索网格设置捕获,该设置比搜索网格的任何其他部分都精细得多。换句话说,如果我们在搜索网格中错过了(reg_lambda = 0.1)的单个数据点,我们将无法捕捉到(reg_lambda = 0.01)和(reg_lambda = 1.0)之间的局部最小值的存在。

d)网格搜索的盲点与可视化

这两条切片的性能曲线揭示了网格搜索的一个普遍缺陷:性能前景捕获的只是在所选搜索网格上预测的性能结果。称之为搜索网格性能;我们没有任何关于搜索网格之外的任何数据点的性能的信息。称之为脱离搜索网格的性能。

此外,可视化使情况变得更糟,通过平滑地插入搜索网格上的性能来推测那些不可见的搜索网格外的性能。在非搜索网格间隔内平滑的图像只是一个假象,但根本不是事实。这个人工制品可能会在我们不稳定的感知中造成一种知识幻觉。换句话说,我们可以通过盲目地用平滑的视觉假象来控制我们的视觉来欺骗自己。

搜索网格设置中更精细的粒度可能会揭示更粗糙的性能环境。也就是说,通过更精细的搜索网格设置,我们可能会发现更多的凹陷和凸起。它可能会发现许多其他隐藏的局部最小值的存在,这些最小值在当前的性能环境中是不可见的。

此外,地面实况全局最小值很可能出现在当前搜索网格之外,并且位于与由当前搜索网格设置识别的当前最小值相距很远的地方。

简而言之,当前的性能状况可能不仅捕捉到了其粗糙的部分图像,还欺骗了我们的视觉。

e)reg _ alpha 和 reg_lambda 的第二轮调整。

为了测试假设的视图,我做了一个更细粒度的搜索网格设置,如下所示,并为特定的超参数对重新运行成对调优。

**for reg_alpha in [0, 1e-2, 2e-2, 3e-2, 0.1, 0.5, 1, 1.5, 1.8, 1.9, 2, 2.1, 2.2, 2.5, 3, 3.5, 4]for reg_lambda in [0, 1e-2, 2e-2, 3e-2, 0.1, 0.5, 1, 1.5, 1.8, 1.9, 2, 2.1, 2.2, 2.5, 3, 4, 5, 6, 7, 7.5, 8, 8.5, 9, 9.5, 10, 10.5, 11, 11.5, 12, 12.5, 13, 13.5, 14, 14.5, 15, 15.5]**

这是第二轮调整结果的三维可视化。

用 Michio Suginoo 制作的 Matplotlib 的 plot_trisurf 进行三维可视化

正如预期的那样,超参数对(reg_alpha 和 reg_lambda)的第二轮调整比第一轮调整呈现出更加崎岖的性能状况,有更多的下降和上升。

下面是第二轮调优的前 10 个性能搜索网格数据点。

制作人:杉尾道夫

现在,这证实了我之前的担心是正确的。

  • 首先,第一个搜索网格的粗略粒度低估了性能状况对超参数对值的微小变化的实际敏感度。
  • top 10 performance search-grid data point 表显示,粒度更细的第二轮调整在与第一轮调整结果(reg_alpha = 0.1,reg_lambda = 8.0)完全不同的位置(reg_alpha = 0.5,reg_lambda = 13.5)发现了其最佳性能数据点。

B .正在使用的多个本地最小值和方法的风险影响

该分析使用网格搜索来进行超参数调整,并使用三角插值来渲染超参数值上的性能景观的 3D 可视化。像任何其他方法一样,这些方法在它们自己的设计中有其固有的局限性。以及 reg_alpha 和 reg_lambda 的超参数对的特定成对调谐的结果,展示了多个局部最小值,这是高灵敏度/易变性能的症状,暴露了那些固有设计限制的风险。在某种程度上,这种极度敏感的表现放大并暴露了使用这些方法的风险。

以下是这些局限性的概述。

局限性 1)网格搜索的盲点:

  • 搜索网格被设置为所选离散超参数数据点的集合。称之为搜索网格数据点。网格搜索只计算选定搜索网格中那些离散超参数数据点的性能结果,或搜索网格性能。
  • 调优并没有告诉我们在相邻的两个搜索网格数据点之间发生了什么。我们对所选搜索网格之外或搜索网格之外的性能值(评估指标)一无所知。因此,调优结果最多只能让我们看到底层性能状况的一部分。

限制 2)虚幻的视觉化

  • 3D 可视化仅捕获搜索网格上的结果。
  • 并且 3D 可视化平滑地内插那些离散的数据点,以创建估计的脱离搜索网格的评估度量值的人工制品。该人工制品产生了关于脱离搜索网格的评估度量值的知识幻觉。

如果当前的搜索网格仅捕获其灵敏度的部分图像,则脱离搜索网格的地面实况性能可能比其在平滑的可视化上出现的更敏感/不稳定,并且更崎岖,有更多的下降和颠簸。换句话说,它的高灵敏度表明 地面实况脱离网格性能 可能实质上偏离投影在 3D 可视化上的插值估计的假象。

此外,业绩前景的高度敏感性可能会进一步带来风险,即地面实况全局最小值可能出现在搜索网格之外,并且与搜索网格上的全局最小值相距甚远。在这种意义上,当与仅具有单个局部最小值的 3 个其他情况相比时,多个局部最小值放大了遭受可视化的网格搜索和三角测量插值技术的那些固有限制的风险。

C .偏差- 方差 模型选择中的取舍

现在,在这个阶段,考虑到这两轮调整的结果,我们想对模型选择做出一个试探性的决定来回答下面的问题。我们应该选择哪个调整模型:第一次调整结果还是第二次调整结果?

我们是否可以说,与第一轮结果相比,第二轮调整提供了更好的解决方案,因为它在调整期间产生了更好的性能(k 倍交叉验证)?

可惜没那么简单。到目前为止,我们只使用训练数据集进行 k-fold 交叉验证。

为了回答这个问题,我们需要考虑偏差-方差权衡或欠拟合-过拟合权衡的角度。换句话说,我们需要在测试数据集 上运行调整后的模型,并比较它们的性能。

下面的表格比较了交叉验证数据集和测试数据集之间的性能,分别为“调整(验证 MAE)”和“测试性能(MAE)”三个阶段:预调整阶段、第一次成对调整后以及第二次成对调整后。

制作人:杉尾道夫

该表表明,最佳测试性能实际上是在第一轮调优时产生的,而不是在第二轮调优时产生的。这表明第二轮调整期间较好的交叉验证性能结果说明了 方差 ,或 过拟合

为了解决偏差-方差权衡问题,我们必须选择第一轮结果,拒绝第二轮结果用于模型选择。

对于数据驱动的机器学习,模型不稳定性是由偏差-方差权衡或欠拟合-过拟合权衡引起的关键问题。当新类型的数据集出现在部署域中并进入优化模型时,在开发阶段优化的模型可能会表现出意外的行为。模型性能的稳定性需要在模型风险管理的整个生命周期中进行评估。

D .讨论

尽管如此,第一轮调整结果是否是绝对意义上的最佳解决方案仍不确定。考虑到模型的异常敏感性,当前搜索网格的粒度可能不够精细,无法捕捉到真实的全局最小值。在第一轮和第二轮中使用的搜索网格之外,可能存在更好的解决方案(甚至对于测试数据集)。

我们要不要设置一个新的搜索网格来测试这个疑问?

这将是一个永无止境的故事。

最重要的是,当我们使用网格搜索参数调整时,我们永远无法知道绝对意义上的最佳解决方案。网格搜索最多只能捕捉到仅投射在搜索网格上的性能状况的一部分。我们没有任何关于搜索范围外性能的信息。

从这个意义上来说,我们并不了解整个表演领域的全貌。原则上,我们的知识仅限于那些离散的搜索网格上的数据点。更糟糕的是,三角插值可视化技术呈现了连续性能景观的假象,并在我们对整个性能景观的不确定感知中产生了错觉。总的来说,如果我们认为我们可以获得真实性能的全貌,也就是真实性能的全局最小值,那就太天真了。

除了前面反复提到的两个限制之外,所选择的调优方法还有另一个明显的限制,即成对调优。

限制 3)成对调谐的偏好

  • 所有超参数的单个联合调整将更好地捕捉所有超参数之间的实际性能交互。可能会得出完全不同的结论。从这个意义上说,成对调整捕获的全局最小值可能是不准确的,并且不同于真实的全局最小值。
  • 只有 4 个成对调整,我们没有覆盖 8 个超参数的所有可能的组合对(28=8×7/2)。这些缺失组合之间的相互作用不能被 4 个成对调节捕获。

对本地 XGBoost API 的 reg_alpha 和 reg_lambda 进行成对调整,证明了模型性能对超参数对值的微小变化高度敏感,并揭示了网格搜索和插值 3D 可视化的认识论限制。

  • 网格搜索只能捕获搜索网格上的模型性能,而不会给我们留下任何关于搜索网格外性能的信息。
  • 更糟糕的是,三角形插值通过平滑性能景观,蒙蔽了我们的视觉,并产生了关于实际模型性能的错觉。

简而言之,这两种限制都存在于这些方法的设计中。因此,它们是体系结构固有的,无法解决。这设置了一个认识论的限制:我们永远不会达到我们的地面真实性能景观的完美知识,因此,地面真实的全球最小值。这将引出一个相关的问题:

  • 如果在认识论上不可能找到基本事实的全局最小值,有可能建立它的强大代理吗?什么构成了全局最小值的可靠代理?

此外,在超参数对上使用更细粒度的搜索网格设置进行的第二轮调整揭示了一个复杂的偏差-方差权衡问题。虽然提高了训练成绩,却恶化了考试成绩。不幸的是,更细粒度的网格搜索只能使模型过度适应训练数据集,而不能优化一般的模型性能。这提出了另一个相关的问题:

  • 从调优模型的总体优化角度来看,基本事实全局最小值有什么关系吗?

另一点是:在分析中,认识论的限制似乎对 3 个其他性能场景没有问题,即对于以下 3 对:(max_depth,eta),(subsample,colsample_bytree),(min_child_weight,gamma),因为它们相对一致。很明显,只有在 reg_alpha 和 reg_lambda 的高灵敏度性能环境下,我们才注意到认识论的限制。

这告诉我们一些事情:只要性能状况没有表现出对这些超参数值的微小变化的高度敏感性,三角形插值与网格搜索的结合似乎已经成为一种稳健的解决方案。

这转化为另一个视角:除了这些方法的一般认识论限制,还有一个过度敏感的性能景观的更严重的问题。需要解决过多局部最小值的风险影响。一个相关的问题是:

  • 多个局部最小值是否表明部署域中调优模型的模型不稳定性,其中新类型的数据集可能被引入到模型中?

你怎么想呢?

这些问题似乎都没有不言自明的答案。

D.结论

现在,我将关闭这个职位,同时保持这些问题的答案。展望未来,我希望继续关注过多多重最小值的风险影响,并探索那些尚未解答的问题。此外,我欢迎读者联系我,分享他们对此事的看法。

总的来说,这篇文章揭示了在超参数调优过程中,我们在寻找性能全局最小值的基本事实答案时的认知局限性。

网格搜索只确定了搜索网格上的模型性能,搜索网格外的性能未知。让情况变得更糟的是,3D 可视化渲染了脱离搜索网格的数据点上的三角插值的假象,并创造了一种知识的幻觉。

此外,从偏差-方差权衡的角度来看,我们是否需要获得关于基本事实全局最小值的知识并不是不证自明的。

感谢你阅读这篇文章。

如果你对原分析的整个过程感兴趣,可以访问 my Github repository 查看代码实现。作为脚注,为了生成所有这些图像,我使用了 python 和 Matplotlib。

确认

我要感谢 TDS 的编辑团队,特别是凯瑟琳·普雷里,感谢他们在编辑阶段提供的宝贵意见。

参考

基于分层注意机制的 EHR 数据风险预测

原文:https://towardsdatascience.com/risk-prediction-with-ehr-data-using-hierarchical-attention-mechanism-685028e01faa

LSAN 基本指南:用分级注意建模长期依赖和短期关联

图片由 Jukka Niittymaa 来自 Pixabay

电子健康记录(EHR)是全面的历史健康记录,包含患者就医时的症状。EHR 数据具有两级层次结构,由一组按时间顺序排列的就诊组成,在每次就诊中,有一组无序的诊断代码。诊断代码可以属于 ICD-9 或 ICD-10 格式,表示某种疾病的症状。

风险预测是医疗保健行业中最流行的问题陈述之一。风险预测是指对未来某一疾病高风险成员的预测。现有方法侧重于对时间访问进行建模,而忽略了对访问中的诊断代码进行建模的重要性,并且访问中大量与任务无关的信息通常导致现有方法的性能不令人满意。

在本文中,我们将讨论如何通过保持长期依赖性和短期相关性来执行风险预测。

LSAN 简介:

参考论文:LSAN——对风险预测的长期依赖和短期相关性进行分层关注建模

EHR 数据由两级层次结构组成:就诊层次结构和每次就诊的诊断代码层次结构。

(来源),EHR 的等级表示图

本文提出了一个 LSAN 深度神经网络模型来模拟 EHR 数据的层次结构。

任务:

任务是使用纵向 EHR 数据𝑯 ∈ R𝑚×n.计算能够预测患者‘p’在未来可能发生的某些疾病的函数 f。该函数的主要关注点是从患者数据 h 中提取隐藏的疾病进展信息,并处理噪声信息的问题。

输入符号:

对于每个病人‘p’,它期望历史诊断结果作为一个顺序列表𝑯=【𝒉1,𝒉2,…,𝒉𝑛】,其中𝒉𝑖是𝑖-the 就诊的诊断结果,n 是就诊次数。

每个就诊诊断结果由 ICD-9 代码的子集**𝑪 = {𝒄1,𝒄2,…,𝒄𝑚}**组成,其中𝑚是数据集中唯一诊断代码的数量。

此处**hiCj=1**如果就诊的诊断结果包含 cj 诊断代码,否则**hiCj=0**

创意:

LSAN 是一种端到端模型,

  1. HAM (在诊断代码层次中):它用设计的诊断代码级注意力学习访问嵌入。
  2. TAM (时间聚合模块):它捕获访问之间的长期依赖性和短期相关性。
  3. HAM (在就诊层级中):TAM 的输出用于通过 HAM 中的就诊级注意力来学习最终的综合患者表现。
  4. 分类器:综合表示用于预测

(来源),LSAN 架构

HAM 的功能是利用 HER 的分层表示,并且它具有注意机制来去除 EHR 数据中的噪声。

HAM(在诊断代码层级中):

用途:

在诊断代码的层次结构中,我们应该减少噪声信息,以便为每次访问学习更好的嵌入。在每次就诊中,可能存在与目标类别无关的诊断代码。因此,我们需要区分每次就诊中诊断代码的重要性。

HAM 利用分级注意机制来更加注意与我们的目标疾病相关的诊断代码,而较少注意其他代码。

实施:

  1. 由于𝑯 ∈ R^(m*n)是一个稀疏矩阵,不适合表示学习,所以想法是学习每个代码的密集嵌入
  2. HAM 首先通过一层前馈网络 FFN1 将每个诊断码𝒄𝑖编码成密集嵌入的𝒆𝑖 ∈ R𝑑
  3. HAM 首先通过 1 层前馈网络 FFN1 𝒆𝑖 = FFN1 (𝒄𝑖) = ReLU(𝑾1𝒄𝑖 + 𝒃1), where 𝑾1 ∈ R𝑑×𝑚, 𝒃1 ∈ R^d将每个诊断码𝒄𝑖编码成密集嵌入的𝒆𝑖 ∈ R𝑑
  4. 𝑬 = [𝒆1,…,𝒆𝑚] ∈ R^(𝑑×m),m 个唯一诊断码的 d 维稠密表示
  5. 对于𝑖-th 访问,我们获得一个密集嵌入集𝑺𝑖 = [𝒔1,…,𝒔𝑚]其中𝒔𝑗 = 𝒆𝑗如果𝒉𝑖𝑗 = 1 以反映某种症状或疾病的存在,否则𝒔𝑗 = 0
  6. 尽管如此,𝑺𝑖 ∈ R𝑑×m 对于学习过程来说是多余的。现在哈姆提取每次访问的潜在信息,并将其表示为**𝒉¯𝑖 ∈ R𝑑**
  7. 为了提取潜在的访问信息,HAM 使用了一个三层前馈网络(FFN_2)。FFN_2 学习每个密集嵌入𝑺𝑖的注意力权重并一起出席它们。
  8. 我们得到每个诊断代码的注意力得分𝛼𝑖∈r(𝛼𝑖= ff N2(𝒔𝑖)),如果𝒔𝑖 = 0,我们设置𝛼𝑖=∞。用 softmax: **𝑎𝑖 = exp(𝛼𝑖)/( Σ𝑗=1tom exp(𝛼𝑗) )**归一化关注度,这里𝑎𝑖是归一化权重
  9. 现在,我们为𝑖-th 就诊**(𝒉¯𝑖 = Σ𝑖=(1 to m) 𝑎𝑖 · 𝒔𝑖).**获得了单个嵌入𝒉 𝑖。结果,在诊断代码的层级中,我们获得了患者‘p’的一组关注特征**𝑯¯ = [𝒉¯1,…, 𝒉¯𝑛] ∈ R𝑑×𝑛**

TAM(在诊断代码层级中):

用途:

TAM 将访问嵌入与来自全局和时态结构的两种时态信息聚合在一起。当所有访问的特征被放入 TAM 时,

  1. 它通过 Transformer 对全球结构中的长期依赖关系进行建模,例如在患者的整个医疗旅程中,每次就诊如何与其他就诊相关联。
  2. 通过卷积层在本地结构中进行短期关联,例如每个访问如何在短时间内与其他访问相关联。

实施:

  1. TAM 通过卷积进行短期相关性建模:它过滤掉来自无关诊断代码的噪声,提取每个阶段中相关的疾病进展信息,用于时间聚合

2。TAM 在变压器长期依赖关系建模中:

a.Transformer 并行处理所有的访问功能,不会模糊每个功能的细节

b.我们在 Transformer 中使用多头自关注机制进行特征关注,TAM 中的 Transformer 编码器具有𝑙层,其中每层中的计算是相同的

c.将位置编码添加到𝑖-th 输入访问中,𝒉 𝑡 𝑖 = 𝒉 𝑖 + 𝒕I,其中𝒕𝑖是位置编码

d.每层变压器都有'ℎ'头、

这两个时间信息都有利于学习特征的鲁棒性,因此我们连接𝒉 𝑔 𝑖和𝒉 𝑙 𝑖以获得用于风险预测的特征𝒉𝑖 ∈ R2𝑑,

𝒉𝑖=𝒉𝑙𝑖concate(𝒉𝑔𝑖)

最后,TAM 输出一个矩阵𝑯 = [𝒉1,…,𝒉𝑛] ∈ R2𝑑×n

火腿(按拜访层次):

用途:

在访问层次中,我们应该注意访问之间的相关性。它捕捉了疾病的时间模式。通过提取相邻访问之间的局部时间相关性并利用长期相关性信息来滤除噪声。

它侧重于从所有访问中提取整体语义。

实施:

  1. 类似于诊断代码层次中的 HAM,它首先采用 3 层前馈网络 FFN4 来学习注意力分数𝛽𝑖 ∈ R,**𝛽𝑖 = FFN4 ( 𝒉𝑖).**
  2. 然后,我们用 softmax 函数**b𝑖 = exp(𝛽𝑖)/( Σ𝑗 =1m exp(𝛽𝑗)**得到归一化的注意力权重𝑏𝑖 ∈ R
  3. 风险预测的综合特征𝒙 ∈ R2𝑑通过注意机制学习,其中 **𝒙 = Σ𝑖=1n (𝑏𝑖 · 𝒉~𝑖)**

分类器:

  1. 最后,我们利用𝒙进行风险预测,𝑦ˆ = 𝜎(𝒘T𝒙 + 𝑏),其中𝒘 ∈ R2d 和 b ∈ R
  2. 对于训练集 t,我们使用二元交叉熵损失 l 来训练模型,并获得学习参数𝜽

结论:

LSAN 架构捕获访问之间的长期依赖性和短期相关性。根据论文作者进行的实验,LSAN 方法的性能相对优于浅层方法(SVM、线性回归等)或 RNN/RNN+注意力模型。

(来源),不同 EHR 数据集上的风险预测性能比较

参考资料:

[1] LSAN 论文(2020 年 10 月 19 日):https://dl.acm.org/doi/10.1145/3340531.3411864

感谢您的阅读

river:Python 中的在线机器学习

原文:https://towardsdatascience.com/river-online-machine-learning-in-python-d0f048120e46

一种在生产中更新 ML 模型的快速廉价方法

批量学习的问题

数据从业者使用批量学习从数据中学习是很常见的。批量学习就是批量训练 ML 模型。批量学习的 ML 管道通常包括:

  • 将数据分为训练集和测试集
  • 将模型拟合到列车组
  • 在测试集上计算模型的性能
  • 将模型推向生产

然而,在生产中,流水线并没有到此为止。为了确保模型在输入数据发生变化时是稳健的,数据从业者还需要定期对新数据集和现有数据集的组合进行重新训练。

随着数据的增长,训练模型需要更多的时间和资源。

批量学习演示(作者)

因此,批量学习在以下情况下并不理想:

  • 应用程序需要频繁的模型更新。
  • 公司负担不起存储和训练大数据的计算资源。

在线学习简介

在在线学习中,模型是在数据流而不是整个数据集上进行增量训练的。换句话说,模型每次只从一个观察值或一小组观察值中学习。

在线学习演示(作者)

因此,每个学习步骤既快又便宜,这使得它非常适合快速变化的应用程序和计算资源有限的公司。

在本文中,您将学习如何使用 River 对流数据进行机器学习。

随意发挥,并在这里叉这篇文章的源代码:

https://github.com/khuyentran1401/Data-science/blob/master/machine-learning/river_streaming/streaming.ipynb

什么是河流?

River 是一个在线机器学习的 Python 库。要安装 River,请键入:

pip install river

在接下来的几节中,我们将比较使用 scikit-learn 进行批量学习和使用 River 进行在线学习。

准备数据

在做任何新奇的事情之前,我们将从准备数据开始。

从 seaborn 导入penguins数据集:

import seaborn as sns

df = sns.load_dataset("penguins")

查看数据的信息:

df.info()
Data columns (total 7 columns):
 #   Column             Non-Null Count  Dtype  
---  ------             --------------  -----  
 0   species            344 non-null    object 
 1   island             344 non-null    object 
 2   bill_length_mm     342 non-null    float64
 3   bill_depth_mm      342 non-null    float64
 4   flipper_length_mm  342 non-null    float64
 5   body_mass_g        342 non-null    float64
 6   sex                333 non-null    object

创建特征数据(X)和标签数据(y):

target = 'species'
y = df[target]
X = df.drop(target, axis=1)

现在我们准备用 scikit-learn 和 River 创建一个 ML 管道!

使用 scikit-learn 进行批量学习

导入有用的库:

from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import OneHotEncoder
from sklearn.tree import DecisionTreeClassifier
from sklearn.compose import ColumnTransformer
from sklearn.metrics import confusion_matrix, f1_score

为了在批量学习中训练模型,我们通常从将数据集分成训练集和测试集开始:

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=1)

使用 scikit-learn 的转换器和分类器创建管道:

# Get numerical and categorical features
numeric_features = X_train.select_dtypes(exclude=object).columns
categorical_features = X_train.select_dtypes(include=object).columns

# Specify transformers for each type of features
numeric_transformer = SimpleImputer()
categorical_transformer = make_pipeline(
    SimpleImputer(strategy="most_frequent"), OneHotEncoder()
)
preprocessor = ColumnTransformer(
    transformers=[
        ("num", numeric_transformer, numeric_features),
        ("cat", categorical_transformer, categorical_features),
    ]
)

# Create a pipeline with transformers and classifier
sklearn_clf = make_pipeline(preprocessor, DecisionTreeClassifier())

管道概述:

作者图片

训练模型并预测测试数据:

# Train the model
sklearn_clf.fit(X_train, y_train)

# Get prediction
y_pred = sklearn_clf.predict(X_test)

这些步骤对于数据从业者来说是非常标准的。让我们把这段代码变成在线学习,比较两种方法的区别。

用 River 进行在线学习

流过数据集

导入有用的库:

from river import (
    stream,
    compose,
    preprocessing,
    evaluate,
    metrics,
    tree,
    imblearn,
    stats,
)
import numbers import numbers

在在线学习中,观察是一次提供一个。我们将通过用stream.iter_pandas遍历两个熊猫数据帧(Xy)的每一行来模仿这种行为:

for xi, yi in stream.iter_pandas(X, y):
    pass

让我们看看最后的xiyi是什么样子的:

>>> xi
{'island': 'Biscoe',
 'bill_length_mm': 49.9,
 'bill_depth_mm': 16.1,
 'flipper_length_mm': 213.0,
 'body_mass_g': 5400.0,
 'sex': 'MALE'}

>>> yi
'Gentoo'

计算运行统计

由于数据集中有一些缺失值,我们将用数据集的平均值来估算缺失值。

为了找到数据集的平均值,我们将数据集中的 N 个非空值相加,然后将结果除以 N。

作者图片

在在线学习中,我们不能应用相同的程序来计算平均值,因为我们不知道整个数据集的值。因此,我们将使用运行统计数据来估计平均值。

要计算移动平均值,只要有新值就更新平均值。然后使用运行平均值来更新缺失值。

作者图片

在 River 中,我们可以使用preprocessing.StatImputer用一个运行统计来替换丢失的值。

为了从一个实例中学习并转换该实例,我们将使用learn_one方法和transform_one方法。

X_sample = [{"age": 10}, {"age": 30}, {"age": None}, {"age": 2}]
mean = stats.Mean()
imputer = preprocessing.StatImputer(("age", mean))
for xi_sample in X_sample:
    imputer.learn_one(xi_sample)
    print(imputer.transform_one(xi_sample))
{'age': 10}
{'age': 30}
{'age': 20.0}
{'age': 2}

创建一个 ML 管道

river.compose提供了几种类似于sklearn.compose的方法来构建机器学习管道。

让我们使用这些方法来创建一个转换分类和数字特征的管道:

cat = (
    compose.SelectType(object)
    | preprocessing.StatImputer()
    | preprocessing.OneHotEncoder(sparse=True)
)
num = compose.SelectType(numbers.Number) | preprocessing.StatImputer()
preprocessor = num + cat

使用管道学习和转换观察结果:

preprocessor.learn_one(xi)
preprocessor.transform_one(xi)
{'island_Biscoe': 1,
 'bill_length_mm_49.9': 1,
 'bill_depth_mm_16.1': 1,
 'flipper_length_mm_213.0': 1,
 'body_mass_g_5400.0': 1,
 'sex_MALE': 1,
 'bill_length_mm': 49.9,
 'bill_depth_mm': 16.1,
 'flipper_length_mm': 213.0,
 'body_mass_g': 5400.0}

最后,我们将使用决策树算法从数据中学习。

传统的批量决策树无法处理在线学习需求,因为当有新的观察值时,它们需要用整个数据集进行重新训练。

因此,我们将使用 Hoeffding 树 (HT)分类器进行在线学习。HT 是迄今为止最流行的增量决策树家族。

classifier = tree.HoeffdingTreeClassifier()

了解更多关于决策树在线机器学习

接下来,将转换器和超线程分类器结合到一个管道中,转换数据并从中学习:

def get_pipeline():
    cat = (
        compose.SelectType(object)
        | preprocessing.StatImputer()
        | preprocessing.OneHotEncoder(sparse=True)
    )
    num = compose.SelectType(numbers.Number) | preprocessing.StatImputer()
    classifier = tree.HoeffdingTreeClassifier()

    return (num + cat) | classifier

pipeline = get_pipeline()

可视化管道:

pipeline

作者图片

训练模型并预测新的观察结果

让我们使用这个管道来创建一个函数,该函数根据数据流进行预测并训练一个模型:

def train(X, y):
    pipeline = get_pipeline()

    # Initialize metrics 
    f1_score = metrics.MicroF1()
    cm = metrics.ConfusionMatrix()

    f1_scores = []

    # Iterate over the dataset
    for xi, yi in stream.iter_pandas(X, y, shuffle=True, seed=1):
        # Predict the new sample
        yi_pred = pipeline.predict_one(xi)

        # Get the score
        if yi_pred is not None:
            f1_score.update(yi, yi_pred)
            f1_scores.append(f1_score.get() * 100)
            cm.update(yi, yi_pred)

        # Train the model with the new sample
        pipeline.learn_one(xi, yi)

    return f1_scores, cm, pipeline

f1_scores, cm, pipeline = train(X, y)

在上面的代码中,我们遍历了每个样本。对于每个样本,我们:

  • 用现有模型预测新样本
  • 计算新预测的微观平均 F1 分数和混淆矩阵,然后更新现有分数
  • 将新的 F1 分数保存到 F1 分数列表中,以供进一步分析
  • 用新样本训练模型并更新模型

作者图片

让我们检查树分类器的参数:

pipeline.steps['HoeffdingTreeClassifier'].summary
{'n_nodes': 1,
 'n_branches': 0,
 'n_leaves': 1,
 'n_active_leaves': 1,
 'n_inactive_leaves': 0,
 'height': 1,
 'total_observed_weight': 344.0}

随着迭代次数的增加,用线形图直观显示分数的变化。

import matplotlib.pyplot as plt 

def plot(scores: list):
    iters = range(len(scores))
    ax = sns.lineplot(x=iters, y=scores)
    ax.set(xlabel='num_iters', ylabel='score')
    plt.show()

plot(f1_scores)

作者图片

我们可以看到,微 F1 分数在第 150 次迭代前后达到最高值,然后下降。

我们还可以使用evaluate.progressive_val_score方法在流数据集上评估模型的性能:

metric = metrics.MicroF1()

evaluate.progressive_val_score(
    dataset=stream.iter_pandas(X, y, shuffle=True, seed=1),
    model=pipeline,
    metric=metric,
    print_every=50, # print every 50 iterations
)
[50] MicroF1: 73.47%
[100] MicroF1: 84.85%
[150] MicroF1: 88.59%
[200] MicroF1: 83.42%
[250] MicroF1: 74.70%
[300] MicroF1: 68.90%
MicroF1: 64.72%

最终分数相当低。让我们通过查看混淆矩阵来深入了解模型性能:

cm
 Adelie   Chinstrap   Gentoo  
   Adelie      143           8        0  
Chinstrap       44          22        2  
   Gentoo       66           1       57

我们可以看到,大多数错误预测被归类为Adelie物种。这可能是因为数据不平衡。我们可以通过查看y中每个值的计数来确认这一点。

y.value_counts()
Adelie       152
Gentoo       124
Chinstrap     68

当检查y的值时,我们可以看到Adelie标签比其他标签多。

在下一节中,我们将学习如何用 River 处理不平衡数据集。

处理不平衡数据

为了处理不平衡的数据,我们将使用RandomSampler类来调整每个标签的样本数量,这允许我们获得目标分布。

RandomSampler使用欠采样和过采样来适应指定的约束。

例如,在下面的示例中,desired_dist={"Adelie": 0.1, "Gentoo": 0.4, "Chinstrap": 0.5}告诉 River 对数据进行采样,以便分类器遇到 10%的Adelie,40%的Gentoo,以及 50%的Chinstrap

classifier = tree.HoeffdingTreeClassifier()
sampler = imblearn.RandomSampler(
    classifier=classifier,
    desired_dist={"Adelie": 0.1, "Gentoo": 0.4, "Chinstrap": 0.5},
    seed=2,
)

让我们将RandomSampler类合并到我们的管道中,看看性能是否有所提高:

def get_pipeline():
    # Specify the transfomers
    cat = (
        compose.SelectType(object)
        | preprocessing.StatImputer()
        | preprocessing.OneHotEncoder(sparse=True)
    )
    num = compose.SelectType(numbers.Number) | preprocessing.StatImputer()

    # Specify classifiers
    classifier = tree.HoeffdingTreeClassifier()
    sampler = imblearn.RandomSampler(
        classifier=classifier,
        desired_dist={"Adelie": 0.1, "Gentoo": 0.4, "Chinstrap": 0.5},
        seed=2,
    ) 
    return (num + cat) | sampler

f1_scores, cm, pipeline = train(X, y)

绘制跨迭代的微观平均 F1 分数:

plot(f1_scores)

作者图片

微观平均 F1 分数最初在最初几次迭代中降低,然后在后面的迭代中增加。

混淆矩阵还显示使用RandomSampler时有更多的正确预测。

cm
 Adelie   Chinstrap   Gentoo  
   Adelie      111          37        3  
Chinstrap        5          62        1  
   Gentoo        5           6      113 

让我们调用evaluate.progressive_val_score方法来获得每次迭代的 F1 分数:

pipeline = get_pipeline()

metric = metrics.MicroF1()

evaluate.progressive_val_score(
    dataset=stream.iter_pandas(X, y, shuffle=True, seed=1),
    model=pipeline,
    metric=metric,
    print_every=50,
)
[50] MicroF1: 42.86%
[100] MicroF1: 67.68%
[150] MicroF1: 75.17%
[200] MicroF1: 75.88%
[250] MicroF1: 80.32%
[300] MicroF1: 82.61%
MicroF1: 83.38%

不错!使用RandomSampler时最终微均 F1 分 83.38%。

结论

恭喜你!您刚刚学习了如何使用 River 进行在线机器学习。我希望这篇文章能给你创建一个生产就绪的机器学习模型所需的知识。

我喜欢写一些基本的数据科学概念,并尝试不同的数据科学工具。你可以在 LinkedIn 和 T2 Twitter 上与我联系。

这个回购如果你想检查我写的所有文章的代码。在 Medium 上关注我,了解我的最新数据科学文章,例如:

参考

库马尔,A. (2022,4 月 7 日)。区别在线&批量学习。数据分析。2022 年 12 月 5 日检索,来自https://vital flux . com/difference-between-online-batch-learning

RNN:递归神经网络——如何在 Python 中成功地对序列数据建模

原文:https://towardsdatascience.com/rnn-recurrent-neural-networks-how-to-successfully-model-sequential-data-in-python-5a0b9e494f92

神经网络

RNN:递归神经网络——如何在 Python 中成功地对序列数据建模

rnn 的可视化解释和使用 Keras 和 Tensorflow Python 库构建它们的逐步指南

递归神经网络。图片由作者提供。

简介

对序列数据进行建模和预测需要一种不同于标准回归或分类的方法。幸运的是,一种被称为递归神经网络(RNNs)的特殊类型的神经网络就是为此目的而专门设计的。

在本文中,我将介绍 RNNs 的结构,并给出一个完整的例子,说明如何在 Python 中使用 Keras 和 Tensorflow 构建一个简单的 RNN。

如果你不熟悉神经网络的基本结构,你可能更愿意先熟悉一下前馈深度前馈 NNs。

内容

  • 看看机器学习的世界
  • 递归神经网络的体系结构
  • 如何建立和训练你自己的 RNN 的 Python 例子

看看机器学习的世界

虽然神经网络最常以监督的方式使用带标签的训练数据,但我觉得它们独特的机器学习方法值得单独归类。

递归神经网络有自己的子分支,由简单 RNNs、LSTMs(长短期记忆)和 GRUs(门控递归单元)组成。

下图是交互式的,所以请点击不同的类别来放大并展示更多的👇。

机器学习算法分类。由作者创作的互动图表。

如果你喜欢数据科学和机器学习 ,请 订阅 获取我的新文章邮件。

递归神经网络的结构

首先,让我们提醒自己典型的前馈神经网络是什么样子的。请注意,它可以包含任意数量的输入节点、隐藏节点和输出节点。下面的 2–3–2 结构纯粹是为了说明。

简单的前馈神经网络结构。图片由作者提供。

接下来,如果我们看看 RNN,我们会注意到一个细微的差别。RNN 内部的隐藏单元有一个内置的反馈回路,使信息能够多次传回同一个节点。这些隐藏单元通常被称为循环单元

简单递归神经网络结构。图片由作者提供。

递归单元处理预定数量的时间步长的信息,每次通过激活功能传递该特定时间步长的隐藏状态和输入。

时间步长 —通过循环单元对输入进行单次处理。例如,如果您只有一个时间步长,那么您的输入将只被处理一次(相当于常规的隐藏节点)。如果您有七个时间步长,那么您的输入将被处理七次。

请参见下图,该图显示了循环单元内部的反馈回路:

时间步长 t 处的循环单元操作。图片由作者提供。

注意,在初始时间步,隐藏状态 h0 被初始化为 0。接下来,输出(在 t+1 处的隐藏状态h)被传递回递归单元,并与以下输入一起被再次处理:

时间步长 t+1 的循环单元操作。图片由作者提供。

重复该过程,直到达到指定的时间步长数。

让我们把所有这些联系起来,看看一个简单的 RNN 有一个输入、一个隐藏节点(包含三个时间步长)和一个输出会是什么样子。

递归单元的展开。图片由作者提供。

为了更详细地解释发生了什么,让我们看一个简单的例子。

假设您想根据过去三天的气温序列来预测明天的气温。然后:

  • 输入 —虽然您可能只有一个输入节点,但您必须传递三个数字的序列作为您的输入,因为这是递归层所需要的,即[x0,x1,x2],…,[x_{n-2},x_{n-1},x_{n}]。
  • 递归层 —在典型的前馈神经网络中,隐藏节点有两个参数:权重和偏差。然而,递归层有三个参数需要优化:输入权重、隐藏单元权重和偏差。注意,即使你有十个时间步长,它仍然是三个参数。
  • 训练 —使用反向传播算法训练典型的前馈神经网络。与此同时,训练 RNN 使用稍微修改的反向传播版本,其中包括及时展开来训练网络的权重。该算法基于计算梯度向量,简称为时间反向传播BPTT

现在您已经熟悉了一个简单 RNN 的架构,让我们来看一个 Python 示例。

如何建立和训练你自己的 RNN 的 Python 例子

设置

我们需要以下数据和库:

让我们导入所有的库:

上面的代码打印了本例中使用的包版本:

Tensorflow/Keras: 2.7.0
pandas: 1.3.4
numpy: 1.21.4
sklearn: 1.0.1
plotly: 5.4.0

接下来,我们下载并获取澳大利亚的天气数据(来源: Kaggle )。我们还执行一些简单的数据操作,并导出一个新的变量(中间温度)供我们使用。

一小段 Kaggle 的澳大利亚天气数据做了一些修改。图片由作者提供。

假设数据包含澳大利亚多个地方的天气信息,让我们选择一个城市(堪培拉)并在图表上绘制每日中值温度。

堪培拉的日平均气温。图片由作者提供。

训练和评估递归神经网络(RNN)

在我们训练和评估我们的递归神经网络之前,我们需要创建一个函数来帮助我们重塑数据,以遵循所需的格式。

上述函数可以重构任意数量时间步长的数据。例如,由于我使用了七个时间步长(即,一系列 7 天的温度来预测第二天的气温),它将像这样分割数据:

说明如何为 RNN 重新构建顺序数据。图片来自作者

现在我们可以训练和评估我们的 RNN。我们在这个例子中使用了一个极其简单的神经网络,它有四层,每层只有一个节点。通过添加额外的层、节点或改变激活函数,您可以随意进行试验。

示例中使用的 RNN 的结构。图片由作者提供。

我对下面的代码做了大量的注释,以便让您清楚地理解每个部分的作用。因此,我不会在文章正文中重复同样的内容。

上述代码为我们的递归神经网络打印了以下摘要和评估指标:

递归神经网络性能。图片由作者提供。

现在让我们将结果绘制在图表上,并比较实际值和预测值。注意,我们使用 inverse_transform 函数将目标和预测从缩放(我们在训练 RNN 之前使用了 MinMaxScaler)转换到原始值范围。

RNN 模型对试验数据的预测。图片由作者提供。

以上结果是针对测试数据集的。预测看起来相当准确,但是请记住,我们在每种情况下都取 7 个先前的数据点,并且只预测下一个。因此,如果我们试图预测未来的多个时间点,这个特定模型的结果会不太准确,我将在后面的示例中展示这一点。

使用 RNN 生成预测

您会记得,在上述模型的训练和预测过程中,我们将序列中的每第 8 个观察值作为目标。但是,如果我们想用这个模型为我们的数据框架中的每一项(每一天)生成预测呢?下面的代码正是这样做的:

因为我们将模型预测添加到了原始数据框架中,所以我们可以使用它来绘制结果。

整个数据样本的 RNN 模型预测。图片由作者提供。

再次,相当不错的结果记住,我们只预测未来一天的温度。

如果我们试图预测未来 365 天的温度,一次预测一天的温度,会怎么样?我们将尝试在 7 天序列中反复添加新的预测,同时从序列中删除最早的预测。

最后,我们重用上一步中的图表绘制代码来显示过去两年的结果和未来 365 天的预测。

replace:
x=dfCan['Date'] → x=dfCan2['Date'][-730:] # for both traces
y=dfCan['MedTemp'] → y=dfCan2['MedTemp'][-730:] # for first trace
y=dfCan['MedTemp_prediction'] → y=dfCan2['MedTemp_prediction'][-730:] # for second trace

RNN 模型对未来 365 天的预测。图片由作者提供。

我们可以看到,使用现有的 RNN 模型进行任何长于日+1 的预测都是不明智的。这种结果的原因是,我们设计它只是为了提前一天进行预测,并且部分受到具有相对“短记忆”的 RNNs 的影响。

在即将到来的文章中,我将分析更高级版本的递归神经网络,如 LSTM(长短期记忆)GRU(门控递归单元),所以不要忘记订阅不要错过它们。

结束语

我真诚地希望你喜欢阅读这篇文章,并获得一些新的知识。

请使用本文提供的代码来构建您自己的递归神经网络。你可以在我的 GitHub 库中找到完整的 Jupyter 笔记本。

在我努力让我的文章对读者更有用的时候,如果你能让我知道是什么驱使你阅读这篇文章,以及它是否给了你想要的答案,我将不胜感激。如果不是,缺少什么?

干杯!👏
索尔·多比拉斯

如果你已经花光了这个月的学习预算,下次请记得我。 我的个性化链接加盟媒介:

https://solclover.com/membership

您可能感兴趣的其他文章:

机器人过程自动化——没有麻烦的自动化

原文:https://towardsdatascience.com/robotic-process-automation-automation-without-the-hassle-ac857e5b847a

自动化枯燥和重复的过程,而不触及底层系统。深入了解企业软件中发展最快的分支。

Unsplash尔汗·阿斯塔姆的照片

人类总是被让事情变得更简单、更舒适的愿望所驱使。从第一把斧子到沟渠,从网页脚本到机器学习算法——我们总是努力为重复性任务找到解决方案。

从这个角度来看,电脑在工作场所的兴起对推动人类进步不可或缺,极大地提高了我们的生产力。尽管计算机简化了许多任务,但人类干预在数字化运营中仍然至关重要,不断增长的数字化劳动力就是证明。

尽管计算机拥有巨大的能力,但我们在计算机上执行的许多任务都相当普通。例如,当雇用一名新员工时,我们经常需要处理他们的姓名、出生日期、社会保险号、职称、访问权限等。在各种系统和账户中。像这样的重复性任务会浪费时间和认知技能,而这些可以被更有效地利用。

“自动化”可能是答案,但是传统的流程自动化——在系统的后端运行——并不像看起来那么简单。在系统成功通信和编写准确的脚本之前,我们通常必须克服许多障碍。尽管技术上可行,但这并不总是最明智的商业决策。

此外,优秀的工程师供不应求。如今的企业宁愿将它们部署在实现人工智能能力和创造新的见解上,而不是煞费苦心地复制琐碎的任务。更不用说,数据科学家自己也倾向于在重复任务(如数据清理)上花费大量时间,而不总是有时间自动完成这些任务。简而言之,大家都吃亏。

幸运的是,还有一种选择——机器人!

本文将主要讨论软件机器人的简单化变体。正如我们将在最后看到的,更智能的(决策)变体正在崛起。这就是数据科学社区将发挥推动作用的地方,结合基于规则的自动化和人工智能

RPA 是什么?

图片由 Taras ShypkaUnsplash 上拍摄

与传统的过程自动化相比,机器人过程自动化(RPA) 在系统的前端运行,即图形用户界面(GUI),或系统 API。RPA 是一种软件技术,它部署软件代理,在数字系统上模拟人类操作。由于 RPA 不会侵入现有系统,因此通常可以快速集成以推动数字化转型。

与传统自动化相比,RPA 的主要优势在于它不会干扰现有系统。代理可以直接模仿通常由人类执行的操作。

RPA 超越了传统的屏幕捕捉,因为它可以在系统和桌面之间切换。例如,典型的工作流程可能是选择一封电子邮件,打开附件 pdf,复制相关字段,然后将它们粘贴到计费系统中。此类任务通常跨越多个业务部门,但可以表示为一系列操作。

何时使用 RPA?

在确定是否使用 RPA 来自动化特定工作流时,必须回答一些问题:

  • 自动化成本? —完整的后端自动化通常成本高昂且复杂。完整处理信息需要多少时间?
  • 自动化的好处?— 节省了多少工时?释放出来的人力资本将如何配置?自动化会增加工作满意度吗?
  • 流程是否基于规则? —流程可以分解为简单的、基于规则的任务吗?需要任何(人类)判断吗?
  • 正确输出的数量? —流程是否只有一个正确的输出?
  • 任务的重复性如何? —很低的频率可能更适合手动控制,很高的频率将受益于完全自动化。
  • 流程有多成熟?—GUI 是否有望在更长时间内保持不变?从长远来看,业务流程会保持不变吗?

回答这一系列问题对于决定自动化哪些流程是必要的,但是有点无聊。本质上,它归结为(I)过程足够简单(基于规则,没有例外或决策,等等。)和(ii)产生足够价值的自动化。让我们用一个简洁的行动优先级矩阵来想象一下。

行动优先矩阵。RPA 的理想候选是一个只需有限的努力即可实现自动化并具有重大业务影响的流程[图片由作者提供]

在自动化领域定位 RPA 的另一种方式是在自动化连续体上。RPA 将处于光谱的低端(严格基于规则的自动化),而更复杂的任务可能涉及机器学习来处理决策。

有大量任务可能适合 RPA。完全(后端)自动化既费时又费钱,而且日常运营中仍有许多任务需要很少甚至不需要认知努力。

谁应该使用 RPA?

从零售到保险,从制造到医疗保健,各个领域都有 RPA 的身影。也就是说,RPA 似乎特别适合使用大量遗留系统的机构,这些系统很难更换或自动化。

如前所述,选择正确的自动化流程至关重要。虽然是一个轻量级、非侵入性的解决方案, RPA 也有责任,例如,考虑商业许可、详细的流程步骤、长期维护。大约一半的 RPA 项目会失败,所以愿意应用它的公司必须确保投入一些资源和严格性。半心半意的承诺不起作用。

潜在的用例种类繁多。RPA 可用于全自动化(由系统本身触发),也可用于人在回路解决方案(手动激活机器人)。RPA 还可以自动化流程的各个部分,在谨慎的情况下使用人工判断和监控。

总而言之,RPA 可能适合具有以下特征的组织:

  • 将大量人力资本花在重复性的、基于规则的任务上,禁止认知的、创造价值的活动;
  • 拥有不容易自动化或更新的遗留系统;
  • 对 RPA 的(不)功能有扎实的理解,但不一定有很强的内部软件技能;
  • 能够并愿意严格处理自动化轨迹并投入适当的资源。

RPA 用在哪里?

原则上,RPA 适用于任何具有简单、基于规则的任务的重复流程。在实践中,RPA 应用程序的常见示例包括:

  • 入职 —创建账户、访问权限、系统注册等。对于新员工。
  • 发票处理 —将外部来源的发票明细填入系统。
  • 提取-转换-加载(ETL)过程 —重复的数据操作
  • 付款提醒 —从以前的发票中复制粘贴信息
  • 银行流程自动化 —为涉及抵押、贷款、支付等流程填写客户详细信息。
  • 客户服务自动化 —对客户查询的自动化(非智能)响应

有大量可以应用 RPA 的示例流程。然而,关于自动化的好处和复杂性的问题应该始终是中心问题。

RPA 怎么用?

RPA 领域目前由商业解决方案主导。也有开源的替代方案,但这些并不普遍,通常需要一些软件专业知识才能运行。当然,雇佣顾问来支持自动化轨迹是可能的;成功需要一定水平的技术能力和抽象过程思维。

一个关键的挑战是确定哪些过程应该自动化。既需要****对单个流程** 的深入了解,又需要对可用流程的概述,才能准确确定最大的自动化潜力。为此,访谈、调查、文档分析、流程挖掘或头脑风暴研讨会可能是可行的选择。**

重要的是流程是基于规则的。因此,应该可以将这个过程分解成一系列具体的任务,让软件机器人来执行。业务流程建模语言(BPML)是一种可用于构建业务流程的语言。无法对流程进行全面建模意味着它不适合 RPA。

使用 BPML 建模的简单流程示例。应该可以用具体的规则(如果…那么)和步骤来完整地描述一个适合 RPA 的流程。[图片来自维基媒体,作者是 CLMann ]

最后,由于业务流程和 GUI 往往会随着时间的推移而变化,因此灌输监控程序以验证 RPA 随着时间推移的正确运行是非常重要的。自然地,过程的选择应该倾向于成熟和稳定的过程,但是即使这样 RPA 也不会在它的实现中结束。有必要定期检查忙碌的数字工作者的功能。

不利因素和挑战

RPA 是一种轻量级解决方案,实施和加速自动化通常既快速又便宜。但是它也有自己的局限性和缺点。

****更新——使用图形用户界面的风险之一是它们容易改变。一旦系统布局发生变化,我们也必须更新软件机器人。这同样适用于频繁变化的业务流程。尤其是对于不成熟的系统和流程,更新 RPA 可能是一件痛苦的事情。

可扩展性 —尽管机器人可以比人更快地执行 GUI 任务,但我们不会部署 RPA 来执行一百万次拖放操作。工作负载可能会随着时间的推移而增长,在这种情况下,RPA 无法很好地扩展。最终,我们可能不得不切换到后端自动化来实现期望的性能。

基于规则的完全基于规则的程序比较少见。尽管在例外情况下可以将控制权交给人,但是 RPA 仍然针对有限范围的业务流程。缺乏情报和决策能力是爱国军应该考虑的一个缺点。

现状与展望

几年来,RPA 的业务一直在蓬勃发展。2019 年,Gartner 将其确定为增长最快的企业软件,并且一直保持两位数的增长数字。现代商业仍然严重依赖重复任务的手动执行。RPA 提供了自动化任务的轻量级解决方案,释放人力资本来处理更具吸引力和认知性的问题。

RPA 的一个有趣分支被称为'认知 RPA'** 。许多预期的自动化任务并不完全基于规则,然而一个智能层足以做出正确的决定。因此,RPA 和 AI 的融合开启了一个新机遇的世界。**

从更广泛的意义上来说,RPA 是“超自动化”背后的驱动力,推动可以自动化的一切。为此,RPA 本身是不够的,但与事件处理和人工智能的结合可以释放其全部潜力。通过选择与 RPA 互补的技术,可以自动化的流程范围大大增加。

正是在与人工智能的融合中,RPA 也引起了数据科学界的兴趣。我们喜欢创建和应用机器学习算法,但我们只能在正确的生态系统中获得最佳效果。在未来的几年里,为 RPA 注入健康的智能是高度自动化领域的一项艰巨任务,这也是我们数据科学家可以发挥关键作用的地方!

简而言之,数字世界中 RPA 的持续激增是有原因的。RPA 可能不像成熟的自动化那样令人满意,也不像尖端的人工智能那样令人兴奋,但它在实现速赢方面非常出色。最终,这些胜利会永久改变人类和系统的互动方式。

RPA:当软件代理处理你的无聊任务时,你可以坐下来放松一下

进一步阅读

对于那些对 RPA 感兴趣的人来说,有许多关于这个问题的网站。精选:

https://www . Cai . io/resources/thought-leadership/what-is RPA-and-why-is every one-that-talking-it

https://www . Gartner . com/reviews/market/robotic-process-automation-software

https://www . help systems . com/blog/robotic-desktop-automation-RDA-vs-robotic-process-automation-RPA

https://www . robo motion . io/blog/RPA-成长最快-企业-软件/

https://TechCrunch . com/2019/06/24/Gartner-finds-RPA-is-fast-growing-market-in-enterprise-software/

https://www.uipath.com/rpa/robotic-process-automation

线性回归的一个大问题以及如何解决

原文:https://towardsdatascience.com/robust-regression-23b633e5d6a5

机器学习中的稳健回归简介

经典线性回归背后的想法很简单:在数据点上画一条“最佳拟合”线,使均方误差最小化:

普通最小二乘法的经典线性回归。(图片由作者提供)

看起来不错。但是在现实生活中,我们并不总是得到如此干净、良好的数据。相反,我们可能会得到这样的结果:

与上面相同的算法,但是现在由于异常值而表现不佳。(图片由作者提供)

现在这是一场灾难——只有几个错误,外围点破坏了线性模型,即使人眼很清楚如何画出“最佳”线。

你可能会认为我们所要做的就是剔除那些异常值,我们就完成了,但是当你有一个 4096 维的数据集和一百万个样本时,祝你好运!事情会很快失控。相反,我们需要一个通用的方法,使我们能够执行回归,尽管离群值或严重噪声。

由此出现了稳健回归的概念。

正常回归比稳健回归表现差。(图片由作者提供)

这种离群点的出现是错误测量、噪声、人为错误或错误假设的常见结果,尤其是在(非常)高维自然数据(图像、音频等)普遍存在的机器学习中。

在本文中,您将知道如何正确地将模型拟合到遭受这种问题的数据,如上图所示。

预赛

要解决问题,我们必须知道问题的确切原因。

回想一下线性回归是如何工作的:由于直线( y=mx+b )完全由两个参数 mb 描述,我们必须找到 mb 的值,使得直线和实际数据点之间的均方误差 (MSE)最小化:

MSE =(虚线长度的平方之和)÷(虚线的数量)。(作者制作的动画)

您可以将虚线(代表地面实况数据点和直线之间的误差)视为金属弹簧,将蓝线视为刚性杆。弹簧以正比于其长度平方的力将杆拉向它们的红点,直到达到平衡。

然而,这种方法的问题是,一旦你得到一个外围点…

离群值破坏了模型的运行。(作者制作的动画)

…该点和线之间的误差将计入总误差,算法将尝试将其最小化。换句话说,异常值的“弹簧”会把线拉向他们。

但这激发了一个想法:

忽略或减少离群点的误差,使其不会影响总 MSE。

那么,我们如何识别外围点的错误呢?

胡伯损失回归

回想一下 MSE 损失的定义:

均方误差图。(图片由作者提供)

虚线的长度(即误差或残差)被平方、相加,然后除以点数( n )以获得需要最小化的均方误差损失。

Huber loss 的想法是不平方橙色虚线(代表异常值的误差)的长度,而只平方红色虚线,这样异常值就不会对损失造成太大影响。

为了知道哪些误差不平方,我们定义了一个准则:如果绝对误差大于某个阈值(δ),就不平方。由于异常值通常“远离”原始点(因此它们具有较大的误差),它们希望不会被平方,因此对整体损失的影响较小。

Huber 损失与 MSE 损失的比较。x 轴是数据点和直线之间的误差(残差)。指出了 Huber 损耗的线性部分,其中误差没有平方。(图片由作者提供)

要实现 Huber 损耗,只需用您最喜欢的最小化算法中的 Huber 损耗替换 MSE 损耗:

用误差表示 MSE 损失和 Huber 损失的数学表达式。(图片由作者提供)

让我们看看它是如何工作的:

Huber 回归在行动。红色虚线是方形的,而橙色虚线不是。(作者制作的动画)

不错!现在,异常值对算法的影响没有以前那么大,因为它们相应的损失被衰减了。使用弹簧类比,橙色弹簧现在比红色弹簧弱,拉力也没有红色弹簧大。

然而,这种方法存在一些问题。一个是选择δ的值。它应该是什么并不总是很清楚,所以要不断试错(δ=1.35 是一个典型的开始)。另一个问题是,如果离群值太远…

胡伯回归遭到破坏。(图片由作者提供)

…事情可能会变得不可收拾——它们的误差变得如此之大,以至于即使没有平方,它们也会超过损失。然而,我们仍然很清楚什么是“最佳拟合”线,所以必须有另一种方法来解决这个问题。

泰尔-森回归

统计学家中有一句名言:

中位数比平均数更能抵抗异常值。

原因显而易见:

与数轴上五个数的中值相比的算术平均值。(作者制作的动画)

中值的概念也可以扩展到任意数量的维度(在所谓的 空间中值 中),同时仍然保留其抗异常值的属性:

与四个 2D 点的分量平均值相比的空间中值。(作者制作的动画)

于是产生了泰尔森回归的想法:

用一条直线将每一对点连接起来(y=mx+b)得到斜率截距对列表( m ᵢ, b )。这些对的空间中值将给出最佳拟合线的斜率和截距。**

让我们来看看这个爆炸的案例是如何运作的:

泰尔森回归在行动。(图片由作者提供)

太棒了。

你可能已经注意到,我们需要连接每一对点,这给了我们( n 选择 2)=n(n—1)2 对(如果 n 是点数)。对于大型数据集,这可能导致难以处理的长计算时间(例如,仅 1000 个点就产生 50 万个(mb)对来寻找的空间中值)。****

一种解决方案是只对这些点对的随机子集进行处理,或者不是连接每一对点来得到一条线,而是将一条线拟合到每一个 k 点元组。这牺牲了速度的鲁棒性。另一个解决方案是使用 RANSAC。

兰萨克

随机样本一致性(RANSAC)算法基于这样一种想法,即数据的随机子集具有一定的无异常值概率,因此将模型拟合到该子集可能会产生最佳拟合模型。因此,重复挑选一个随机子集并对其拟合模型的过程足够多次,将有希望产生最佳拟合模型。该算法的典型版本如下:

  1. 从数据中随机抽取点的子集。
  2. 将线性(或其他)模型拟合到随机样本。
  3. 计算所有点和模型之间的误差(即残差)。误差小于预定阈值的点被分类为内点,其余的被分类为外点。
  4. 重复上述步骤特定次数,选择得分最高的型号作为最适合的型号。

RANSAC 插图:将线性模型拟合到两个随机选取的点,计算得分,并重复该过程。得到的模型是得分最高的模型。(作者制作的动画)

分数通常被定义为内球的数量。如果两个模型的内层数相等,选择 MSE 最小或 R 最大的一个。

这种方法的一个直接缺点是它是不确定的。也就是说,它并不总是保证产生我们正在寻找的最佳拟合模型,因为我们在有限的迭代次数中处理随机子集。此外,超参数(子集大小、迭代次数、停止标准)是特定于问题的,需要手动调整。然而,RANSAC 是一种强大且广泛使用的算法,尤其是在计算机视觉中,这是由于其鲁棒性和扩展到非线性模型和更高维数据集的能力。

结论

现在,您知道了如何使模型稳健地适应被异常值破坏的数据。上面的每种方法都有自己的优点和缺点,所以在选择哪种方法来解决给定的问题时要小心。此外,应进行适当的分析,以确保异常值是真正由不必要的误差和噪声引起的,还是由数据中尚未考虑的重要潜在模式引起的。

上述三种算法都可以从 scikit-learn 库中获得。

保持强壮。

基于蒙特卡罗模拟的鲁棒供应链网络

原文:https://towardsdatascience.com/robust-supply-chain-network-with-monte-carlo-simulation-21ef5adb1722

当你设计一个供应链网络时,你考虑到你的需求波动了吗?

(图片由作者提供)

目标

建立一个简单的考虑需求波动的供应链网络设计方法。

介绍

供应链优化充分利用数据分析,找到工厂配送中心的最佳组合,以满足您客户需求

在市场上的很多软件和解决方案中,背后的核心结构是一个线性规划模型

假设 需求 不变,这些模型中的一些找到工厂的正确分配以满足需求并最小化成本。

如果需求波动会发生什么?

你的网络可能会失去稳健性,尤其是当你的需求季节性很强时(电子商务、化妆品、快时尚)。

在本文中,我们将构建一个简单的方法来设计一个健壮的供应链网络,使用 Python 的蒙特卡罗模拟。

💌新文章直接免费放入你的收件箱:时事通讯

**SUMMARY**
**I. Supply Chain Network Design** Find the right allocation of factories to meet a global demand
**II. Problem Statement** What happens if your demand is fluctuating?
**1\. Limits of the initial solution** Understand the robustness of the initial solution **2\. Methodology of simulation** Simulate 50 scenarios based on a normal distribution of the demand
**3\. Analyze the results
What are the different combinations of solutions?
4\. Final Solution** The most robust combination?
**III. Conclusion & Next Steps**

如果你喜欢看,可以看看这篇文章的视频

一.供应链网络设计

1.全球供应链网络

作为一家国际制造公司的供应链管理负责人,你想重新定义未来 5 年的供应链网络。

供应链网络问题—(图片由作者提供)

需求从五个不同市场(巴西、美国、德国、印度和日本)的客户需求开始。

百万单位/月的需求—(图片由作者提供)

美国市场驱动了一半以上的需求。

供货能力 你可以在五个市场开厂。在低容量和高容量设施之间有一个选择。

(图片由作者提供)

示例:美国的低产能工厂每月可生产 500,000 台。

固定成本
如果你开设一家工厂,你的预算中会增加固定成本(电力、房地产、资本支出等)。

(图片由作者提供)

印度高容量工厂的固定成本低于美国低容量工厂。

可变成本

(图片由作者提供)

固定成本+可变成本=生产总成本

运费
将一个集装箱从 XXX 国运到 YYY 国的费用。

运费(美元/集装箱)——(图片作者提供)

总成本

(图片由作者提供)

生产并将产品运送到市场的总成本。

根据市场需求,我需要在哪里开设工厂?

3.线性规划模型

关于更多的细节,你可以在参考链接中找到以前的文章,在那里我详细解释了如何使用 PuLP Python 库来实现它。

这是一个经典的带有目标函数、约束和参数的线性规划问题。

(图片由作者提供)

制造足迹
我需要在哪里打开位置?

(图片由作者提供)

你需要在巴西、美国、日本和印度开设四家分店。除了巴西,所有这些地方都是高容量工厂。

产品流
YYY 工厂为 XXX 市场生产了多少台?

(图片由作者提供)

日本工厂只为当地市场生产,而巴西和印度主要由出口需求驱动。

http://samirsaci.com

二。问题陈述

你可以在我的 Github(跟随我:D)资源库中找到 源代码 哑数据:链接
我的作品集与其他项目: 萨奇

这种解决方案是在假设市场需求不变的情况下实施的。

1。初始解决方案的限制

如果我们在日本有+10%,在美国有+20%,会有什么影响?

(图片由作者提供)

通过查看利用率,您可以很容易地猜到您当前的占用空间将无法适应这种激增的需求。

2.模拟方法论

我们不能依赖单一的解决方案,并期望我们的网络能够吸收全年的需求。

通常,这些参数是根据年平均销售额计算出来的。

(图片由作者提供)

让我们模拟这种需求变化,看看对网络设计的影响。

生成 50 个场景

我们假设需求遵循正态分布,变异系数 CV = 0.5。(您可以根据自己的需要调整发行版)

(图片由作者提供)

现在你有一个 50 列的矩阵,代表你的 50 个不同的场景。

每个场景的最优解 每个场景的最优组合是什么?

场景结果的布尔图—[使用 Python 创建布尔图:教程

  • 印度始终至少有一家工厂开放
  • 场景 16 需要开放所有设施,因为美国需求达到峰值
  • 场景 12 在巴西和印度只需要 2 个低产能设施

最优组合的分布 我们是否有比其他组合出现频率更高的组合?

删除重复后,我们有 16 个独特的组合

场景结果的布尔图—[使用 Python 创建布尔图:教程

哪种组合看起来最稳健?

按解决方案绘制事件的圆环图—[使用 Python 创建圆环图:教程

组合 C2(印度和巴西的高/低植物+日本的 1 株高植物)似乎出现得最多。紧随其后的是 C2、C6 和 C10。

4.最终解决方案

起草一个明确的结论并不容易,但如果我们选择安全的解决方案,我们需要采取 C2 组合。

由于它在低生产成本国家的足迹最大化,我们肯定能满足大多数情况下的需求。

http://samirsaci.com

三。后续步骤

关注我的 medium,了解更多与供应链数据科学相关的见解。

结论

这个问题没有完美的解决方案,因为我们需要在保证供应和降低成本之间权衡轻重。

这种方法是围绕库存服务水平和生产成本目标展开讨论的良好开端。

下一步

现在我们有了一组潜在最佳解决方案的候选方案,我们需要在我们的 50 个场景中测试它们:

  • 每个场景的总成本是多少?
  • 我们的工厂产生的需求百分比是多少?

这将带来量化信息,开始与管理层讨论,并获得最终仲裁。

关于我

让我们在 LinkedinTwitter 上连线,我是一名供应链工程师,使用数据分析来改善物流运营和降低成本。

如果你对数据分析和供应链感兴趣,可以看看我的网站

https://samirsaci.com

参考

[1] —用 Python 优化供应链,萨米尔萨奇链接

ROC 分析和 AUC —曲线下面积

原文:https://towardsdatascience.com/roc-analysis-and-the-auc-area-under-the-curve-404803b694b9

用 Python 中的一个真实例子来解释

(图片由作者提供)

受试者操作特征曲线(ROC)分析和曲线下面积(AUC)是数据科学中广泛使用的工具,借用信号处理来评估不同参数化下模型的质量,或者比较两个或多个模型的性能。

传统的性能指标,如精确度和召回率,严重依赖于正面的观察。因此,ROC 和 AUC 使用真阳性和假阳性率来评估质量,同时考虑阳性和阴性观察结果。

从分解问题到用机器学习解决问题的道路有多个步骤。在高层次上,它涉及数据收集、清理和特征工程、构建模型,最后但同样重要的是,评估模型性能

当您评估模型的质量时,通常会使用像 precision 和recall 这样的指标,也分别称为数据挖掘领域的置信度和敏感度。

这些指标将预测值与实际观察值进行比较,通常来自一个保留集,最好使用混淆矩阵进行可视化。

混淆矩阵(图片由作者提供)

先来关注一下精度,也称为阳性预测值。使用混淆矩阵,您可以将精度构建为所有真阳性与所有预测阳性的比率。

回忆,也称为真阳性率,代表真阳性与观察到的和预测的所有阳性的比率。

使用混淆矩阵中不同的观察集合来描述精度和召回,您可以开始看到这些度量如何提供模型性能的狭窄视图。

一个突出的事实是,精确度和召回率只关注正面的例子和预测[1],而不考虑任何负面的例子。此外,他们没有将模型的性能与中位数情景进行比较,中位数情景只是简单的随机猜测。

在深入研究如何计算精确度和召回率之后,您可以开始看到这些指标如何提供模型性能的一个狭窄视图。

为了补充您的模型评估并从精度和召回中排除偏差,您可以在数据科学家的工具包中使用一些强大的工具:接收器操作特性曲线(ROC) 分析及其曲线下面积(AUC)

ROC 曲线:从信号理论到机器学习

ROC 是一个总结工具,用于可视化精确度和召回率之间的权衡[2]。

这项技术出现在信号检测理论领域,是二战期间雷达技术发展的一部分[3]。对于不熟悉信号理论的人来说,这个名字可能有点混乱,但它指的是由军用雷达操作员读取雷达信号,因此有了接收机工作特性曲线中的接收机工作部分。

雷达操作员的部分工作是在雷达上识别接近的敌方单位,关键部分是能够从噪声(例如,静态噪声或其他随机干扰)中准确区分信号(即,实际进入的单位)。他们是判断什么是信号,什么是噪音的专家,当一个假想的敌人单位是你自己的单位或者根本没有东西的时候,他们可以避免向它冲锋。

现在你可能在想等等,这听起来像是一个熟悉的任务!

事实上,这项任务在概念上非常类似于将图像分类为猫或不是猫,或者检测患者是否患有疾病,同时保持较低的假阳性率。

ROC 分析利用 ROC 曲线确定二进制信号的值有多少被噪声污染,即随机性【4】。它提供了一系列操作点的灵敏度和特异性的总结,作为一个连续的预测器[5]。

ROC 曲线通过将 x 轴上的假阳性率对 y 轴上的真阳性率作图而获得。

因为真阳性率是检测到信号的概率,而假阳性率是错误警报的概率,ROC 分析也被广泛用于医学研究,以确定有把握地检测疾病或其他行为的阈值[5]。

不同 ROC 曲线的示例(图片由作者提供)

一个完美的模型将具有为零的假阳性率和等于一的真阳性率,因此它将是 ROC 图左上方的一个单一操作点。而最差的可能模型将在 ROC 图的右下方有一个工作点,其中假阳性率等于 1,而真阳性率等于 0。**

它[ROC 曲线]提供了一系列操作点的敏感性和特异性的汇总,作为一个连续的预测因子。

随机猜测模型有 50%的机会正确预测结果,因此,假阳性率将总是等于真阳性率。这就是为什么图上有一条对角线,代表检测信号对噪声的 50/50 几率。

使用曲线下面积(AUC)评估机器学习模型

你的父母有一个舒适的床和早餐,而你,作为一名数据科学家,让自己建立一个模型,将他们的评论分为积极或消极。

为了处理这个情感分析任务,你从使用多层感知器开始,并使用准确度和损失作为一种方式来理解是否真的足够好来解决你的分类问题。

知道了 ROC 分析如何抵抗偏差,以及它在机器学习中用于比较模型或比较同一模型的不同参数化的事实,您想知道在对来自您父母的家庭和早餐的评论进行分类时,多层感知器是否确实是一个好模型。

为了重建模型,你需要收集评论集,然后把它分成训练和测试两部分,并对其进行标记。

*from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer

corpus = [
    'We enjoyed our stay so much. The weather was not great, but everything else was perfect.',
    'Going to think twice before staying here again. The wifi was spotty and the rooms smaller than advertised',
    'The perfect place to relax and recharge.',
    'Never had such a relaxing vacation.',
    'The pictures were misleading, so I was expecting the common areas to be bigger. But the service was good.',
    'There were no clean linens when I got to my room and the breakfast options were not that many.',
    'Was expecting it to be a bit far from historical downtown, but it was almost impossible to drive through those narrow roads',
    'I thought that waking up with the chickens was fun, but I was wrong.',
    'Great place for a quick getaway from the city. Everyone is friendly and polite.',
    'Unfortunately it was raining during our stay, and there weren\'t many options for indoors activities. Everything was great, but there was literally no other oprionts besides being in the rain.',
    'The town festival was postponed, so the area was a complete ghost town. We were the only guests. Not the experience I was looking for.',
    'We had a lovely time. It\'s a fantastic place to go with the children, they loved all the animals.',
    'A little bit off the beaten track, but completely worth it. You can hear the birds sing in the morning and then you are greeted with the biggest, sincerest smiles from the owners. Loved it!',
    'It was good to be outside in the country, visiting old town. Everything was prepared to the upmost detail'
    'staff was friendly. Going to come back for sure.',
    'They didn\'t have enough staff for the amount of guests. It took some time to get our breakfast and we had to wait 20 minutes to get more information about the old town.',
    'The pictures looked way different.',
    'Best weekend in the countryside I\'ve ever had.',
    'Terrible. Slow staff, slow town. Only good thing was being surrounded by nature.',
    'Not as clean as advertised. Found some cobwebs in the corner of the room.',
    'It was a peaceful getaway in the countryside.',
    'Everyone was nice. Had a good time.',
    'The kids loved running around in nature, we loved the old town. Definitely going back.',
    'Had worse experiences.',
    'Surprised this was much different than what was on the website.',
    'Not that mindblowing.'
]

# 0: negative sentiment.  1: positive sentiment
targets = [1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0]

# Splitting the dataset
train_features, test_features, train_targets, test_targets = train_test_split(corpus, targets, test_size=0.25,random_state=123)

#Turning the corpus into a tf-idf array
vectorizer = TfidfVectorizer(stop_words='english', lowercase=True, norm='l1')*

多层感知器模型准备好接受训练。

*from sklearn.neural_network import MLPClassifier

def buildMLPerceptron(train_features, train_targets, num_neurons=2):
    """ Build a Multi-layer Perceptron and fit the data
        Activation Function: ReLU
        Optimization Function: SGD, Stochastic Gradient Descent
        Learning Rate: Inverse Scaling
    """

    classifier = MLPClassifier(hidden_layer_sizes=num_neurons, max_iter=35, activation='relu', solver='sgd', verbose=10, random_state=762, learning_rate='invscaling')
    classifier.fit(train_features, train_targets)

    return classifier

train_features = vectorizer.fit_transform(train_features)
test_features = vectorizer.transform(test_features)

# Build Multi-Layer Perceptron with 3 hidden layers, each with 5 neurons
ml_percetron_model = buildMLPerceptron(train_features, train_targets, num_neurons=5)*

都准备好训练模型了!当您运行上面的代码时,您会看到类似下面的内容。

训练多层感知器模型的输出。(图片由作者提供)

为了全面分析 ROC 曲线,并将您刚刚构建的多层感知器模型的性能与其他几个模型进行比较,您实际上想要计算曲线下的面积(AUC) ,在文献中也称为 c 统计量

曲线下面积(AUC) 的值介于 0 和 1 之间,因为该曲线绘制在 1x1 网格上,与信号理论平行,它是信号可检测性的度量[6]。

这是一个非常有用的统计数据,因为它给出了模型如何排列真实观察值和错误观察值的想法。它实际上是一个标准化版本的 Wilcoxon-Mann-Whitney 秩和检验,该检验从单个分布中抽取两个有序测量样本来检验零假设【4】。

c-statistics 对一正一负的平局数进行归一化处理。

[……]与信号理论类似,[曲线下的区域]它是信号可检测性的度量。

为了绘制 ROC 曲线并计算曲线下面积(AUC ),您决定使用 SckitLearn 的 RocCurveDisplay 方法,并将您的多层感知器与随机森林模型进行比较,尝试解决相同的分类任务。

*import matplotlib.pyplot as plt
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import roc_auc_score, RocCurveDisplay

def plot_roc(model, test_features, test_targets):
    """
    Plotting the ROC curve for a given Model and the ROC curve for a Random Forests Models
    """

    # comparing the given model with a Random Forests model
    random_forests_model = RandomForestClassifier(random_state=42)
    random_forests_model.fit(train_features, train_targets)

    rfc_disp = RocCurveDisplay.from_estimator(random_forests_model, test_features, test_targets)
    model_disp = RocCurveDisplay.from_estimator(model, test_features, test_targets, ax=rfc_disp.ax_)
    model_disp.figure_.suptitle("ROC curve: Multilayer Perceptron vs Random Forests")

    plt.show()

# using perceptron model as input
plot_roc(ml_percetron_model, test_features, test_targets)*

上面的代码为你的多层感知器和随机森林模型绘制了 ROC 曲线。它还计算两种模型的曲线下面积(AUC)。

多层感知器与随机森林模型的 ROC 图。(图片由作者提供)

结论

从 ROC 分析图和每个模型的曲线下面积(AUC)值,您可以看到多层感知器模型的总体 AUC(在图中表示为 MLPClassifier)略高。

当与试图解决对你父母的床和早餐的评论的情绪进行分类的相同任务的随机森林模型相比时,多层感知器做得更好。

在这种特殊情况下,通过如何关闭也可以看出,橙色线开始到达图的左上角,在这里预测的真实阳性率越来越高,相反,假阳性率越来越低。

您还可以看到,随机森林模型仅略好于随机模型,后者的 AUC 等于 0.5。

希望您喜欢学习 ROC 分析和曲线下面积,这是两种比较机器学习模型的强大技术,使用更抗偏差的指标。

感谢阅读!

参考

  1. 超能力大卫。(2008).评估:从精度、召回率和 F 因子到 ROC ,国际机器学习技术杂志 2:1 (2011),第 37–63 页
  2. 加雷斯·詹姆斯,丹妮拉·威滕,特雷弗·哈斯蒂,罗伯特·蒂布拉尼。(2021).统计学习导论:在 r .(第二版)中的应用
  3. 凯尔内·j·斯特雷纳:中华民国下面是什么?接收机工作特性曲线介绍。加拿大精神病学杂志。2007;52(2):121–128
  4. Flach,P.A. (2011 年)。ROC 分析。参见:萨姆特,韦伯,G.I .(编辑)机器学习百科全书。马萨诸塞州波士顿斯普林格。
  5. 库克号。风险预测中受试者工作特性曲线的使用和误用。流通。2007 年 2 月 20 日;115(7):928–35.
  6. 绿色 DM。关于信号检测理论的说教。声学学会。2020 年 7 月;148(1):222.

binclass-tools Python 包中的 ROC 和精度召回曲线、概率分布和密度图

原文:https://towardsdatascience.com/roc-and-pr-curves-probabilities-distribution-and-density-plots-now-in-binclass-tools-python-9351681a3803

二元分类的标准图现在可以在 binclass-tools 包中获得

(图片由作者提供)

我最近宣布了一个 Python 包的发布,这个包对于二进制分类的分析非常有用。特别是,开发这一软件包的需要源于分析不平衡的二进制分类的困难。使用交互式绘图进行混淆矩阵和成本分析被证明对研究模型性能至关重要,因此创建了 Pythonbin class-tools包,正如我在下面的文章中强调的:

由于该 Python 包的目标是为最终用户提供一组用于二进制分类模型的有用工具,因此添加了基本图,以及混淆矩阵和成本图,用于测量模型性能。

ROC 曲线和 PR 曲线

通常,为了了解二元分类模型的表现,除了分析其混淆矩阵,分析师还绘制了著名的接收器操作特性 ( ROC )和精确召回 ( PR )曲线。这篇文章超出了解释上述曲线是如何构造的范围。关于如何做到这一点的更多细节可以在参考资料中找到。有趣的是,由于有了 binclass-tools 包,现在也可以非常容易地绘制上述两条曲线。

一旦训练了分类器,就可以很容易地计算包含通过将测试数据集传递给分类器的**predict_proba** (产生变量**test_predicted_proba** )而获得的预测分数的向量。然后,可以使用该包的curve_ROC_plot功能来获得 ROC 曲线,传递预测分数和相应的真实标签:

area_under_ROC = bc.curve_ROC_plot(
    true_y = y_test,
    predicted_proba = test_predicted_proba)

除了绘图之外,该函数还返回 ROC 曲线下的面积值。结果图如下所示:

图 1 —使用 binclass-tools 软件包获得的 ROC 曲线(作者提供)

由于图的交互性,您可以在工具提示中查看曲线上每个点的阈值、假阳性率(FPR)和真阳性率(TPR)的值。

类似地,可以用下面的简单代码来绘制精确召回曲线:

area_under_PR = bc.curve_PR_plot(
    true_y= y_test, 
    predicted_proba = test_predicted_proba, 
    beta = 1)

除了返回 PR 曲线下的面积之外,该函数还返回以下图:

图 2 —使用 binclass-tools 软件包获得的 PR 曲线(作者提供)

同样,图形的交互性允许您探索每个阈值的精确度和召回值。工具提示还显示了 fᵦ-score 值(β值作为参数传递给函数),这是一个通过精度和召回定义的指标,通常用于评估模型性能。此外,该图包含 iso-fᵦ曲线,为了方便起见,这些曲线标识了 fᵦ值恒定的点。

在 ROC 和 PR 曲线中,绘制了基线(随机猜测目标类别的天真模型的虚拟曲线)。

概率分布和密度图

两个目标类别的 predict_proba 分数的分布可以使用交互概率分布图进行研究,该分布图使用 violin 图来最好地表示它们。所讨论的图用于回答问题“每个预测目标类的概率分值是如何分布的?”。

这种交互式绘图的一个非常方便的功能是允许改变分类器阈值的滑块。这允许与混淆矩阵分类(TP、TN、FP、FN)相关联的预测子集显示为每个目标类别的分数分布图上方的点:

图 3 —作者使用 binclass-tools 软件包获得的概率分布图

将鼠标悬停在这些点上会产生一个工具提示,其中包含与该点关联的观察的行号指示器( idx )、观察的真实类( class )以及与观察关联的 predict_proba 分值( pred )。通过将鼠标悬停在图的侧边,我们可以获得两个 violin 图的四分位数信息。不同的颜色区分不同类别的混淆矩阵。

Violin plots 允许您获得按目标类别细分的预测分布的“自上而下”视图。另一方面,如果您想要查看与“轮廓图像”相同的分布(通常显示的分布),您可以借助新的predicted _ proba _ Density _ curve _ Plot函数生成交互概率密度图,使用高斯或 KDE 方法平滑直方图,使用以下代码:

threshold_step = 0.05
curve_type = 'kde' #'kde' is the default value, 'normal' otherwisebc.predicted_proba_density_curve_plot(
    true_y = y_test, 
    predicted_proba = test_predicted_proba, 
    threshold_step = threshold_step,
curve_type = curve_type)

您得到的输出是一个交互式绘图,其中也有阈值滑块,其步长在对上一个函数的调用中定义:

图 4——作者使用 binclass-tools 软件包获得的概率密度图

该图中的两个子图中的每一个都被标识阈值的垂直虚线分成两个区域。该图中的两个子图中的每一个都被标识阈值的垂直虚线分成两个区域。这样就形成了四个区域,每个区域与一个混淆矩阵分类(TN,FP,FN,TP)相关联。因此,工具提示突出显示了每个单独区域的详细信息,显示了 predict_proba 得分以及属于特定阈值的特定分类的预测数量。

结论

从版本 0.3.0 开始,Python binclass-tools 包引入了四个新的交互式情节:

  • 交互式接收机工作特性(ROC)曲线
  • 交互式精确召回曲线
  • 交互概率分布图
  • 交互概率密度图

有了新版本, binclass-tools 包可以被认为是研究二进制分类器性能的完整工具。

您可以在 Github 上找到 binclass-tools 存储库:

https://github.com/lucazav/binclass-tools

信用

你可以在精确召回曲线和概率分布图中找到的一些细节受到了 Yohann LereclusPi Espositoplot-metric 包的启发:

https://github.com/yohann84L/plot_metric

参考

  1. 理解 AUC-ROC 曲线|作者 Sarang Narkhede |走向数据科学
  2. 精确召回曲线。有时一条曲线值一千……|道格·斯汀| Medium
  3. Violin Plots 101:可视化分布和概率密度|模式
  4. 深度:内核密度估计| Python 数据科学手册(jakevdp.github.io)

罗伊 Align 解释道

原文:https://towardsdatascience.com/roialign-explained-d30f6843da3a

使用 Python 实现

https://unsplash.com/@wansan_99

介绍

在本教程中,我们将用 Python 再现并解释来自 torchvision.ops.roi_align 的 RoIAlign 函数。我在网上找不到任何能准确复制 torchvision 库结果的代码,因此我不得不将 torchvision 中的 C++实现翻译成 Python,你可以在这里找到。

背景

计算机视觉中的感兴趣区域(RoI)可以定义为图像中的一个区域,在该区域中,潜在的对象可能位于对象检测任务中。下面的图 1 显示了一个 RoI 提议的例子。

https://web.eecs.umich.edu/~justincj/slides/eecs498/WI2022—图 1

涉及 RoI 的对象检测模型之一是更快的 R-CNN 。更快的 R-CNN 可以由两个阶段组成:区域提议网络(RPN) ,其提议 RoI 并表明每个 RoI 是前景(包含对象)还是背景;以及分类网络,其预测 RoI 中包含的对象类别和偏移,即 RoI 的变换(移动和调整大小)以使用这些偏移将它们变换成最终提议,从而将对象更好地包围在边界框中。分类网络也拒绝不包含对象的负面提议——这些负面提议被分类为背景。
重要的是要知道 ROI 不是在原始图像空间中预测的,而是在从视觉模型中提取的特征空间中预测的。下图说明了这个想法:

https://web.eecs.umich.edu/~justincj/slides/eecs498/WI2022—图二

我们将原始图像传入一个预先训练的视觉模型,然后提取特征的 3D 张量,每个特征的大小都是上面的 20x15 的情况。但是,根据我们从哪个图层提取特征以及使用哪个视觉模型,它可能会有所不同。正如我们所看到的,我们可以在图像特征坐标中找到原始图像坐标中的盒子的精确对应关系。现在,为什么我们真的需要投资回报池?
感兴趣区域的问题在于它们的大小都不同,而分类网络需要固定大小的特征。

https://web.eecs.umich.edu/~justincj/slides/eecs498/WI2022—图 3

因此,RoI 汇集使我们能够将所有 RoI 映射到相同的大小,例如映射到 3x3 固定大小的特征,并预测它们包含的类别和偏移。RoI pooling 有多种变体,在本文中,我们将重点关注 RoIAlign 。让我们最后看看这是如何实现的!

建立

让我们首先定义一个要使用的特征映射的例子。因此,我们假设我们处于从感兴趣的图像中提取了一个尺寸特征的阶段。

作者图片—图 3

现在,假设我们提取了一个 RoI,在图 4 中用红色标出了以下坐标(我们省略了方框中的特征值):

作者图片—图 4

在图 4 中,我们还将我们的 RoI 分为 4 个区域,因为我们正在汇集成一个 2x2 维度特征。使用 RoIAlign,我们通常执行平均池

作者图片—图 5

现在的问题是,我们如何平均汇集这些子区域?我们可以看到它们与网格错位,因此我们不能简单地平均每个子区域内的细胞。解决方案是用双线性插值对每个子区域中间隔规则的点进行采样。

双线性插值和池化

首先,我们需要拿出我们在 RoI 的每个子区域中插入的点。下面我们选择合并到 2x2 区域,输出我们想要插值的点。

# 7x7 image features
img_feats = np.array([[0.5663671 , 0.2577112 , 0.20066682, 0.0127351 , 0.07388048,
        0.38410962, 0.2822853 ],
       [0.3358975 , 0\.        , 0\.        , 0\.        , 0\.        ,
        0\.        , 0.07561569],
       [0.23596162, 0\.        , 0\.        , 0\.        , 0\.        ,
        0\.        , 0.04612046],
       [0\.        , 0\.        , 0\.        , 0\.        , 0\.        ,
        0\.        , 0\.        ],
       [0\.        , 0\.        , 0\.        , 0\.        , 0\.        ,
        0\.        , 0\.        ],
       [0\.        , 0\.        , 0\.        , 0\.        , 0\.        ,
        0.18630868, 0\.        ],
       [0\.        , 0\.        , 0\.        , 0\.        , 0\.        ,
        0.00289604, 0\.        ]], dtype=np.float32)

# roi proposal
roi_proposal = [2.2821481227874756, 0.3001725673675537, 4.599632263183594, 5.58889102935791]
roi_start_w, roi_start_h, roi_end_w, roi_end_h = roi_proposal

# pooling regions size
pooled_height = 2
pooled_width = 2

# RoI width and height
roi_width = roi_end_w - roi_start_w
roi_height = roi_end_h - roi_start_h
# roi_height= 5.288, roi_width = 2.317

# we divide each RoI sub-region into roi_bin_grid_h x roi_bin_grid_w areas.
# These will define the number of sampling points in each sub-region
roi_bin_grid_h = np.ceil(roi_height / pooled_height)
roi_bin_grid_w = np.ceil(roi_width / pooled_width)
# roi_bin_grid_h = 3, roi_bin_grid_w = 2
# Thus overall we have 6 sampling points in each sub-region

# raw height and weight of each RoI sub-regions
bin_size_h = roi_height / pooled_height
bin_size_w = roi_width / pooled_width
# bin_size_h = 2.644, bin_size_w = 1.158

# variable to be used to calculate pooled value in each sub-region
output_val = 0

# ph and pw define each square (sub-region) RoI is divided into.
ph = 0
pw = 0
# iy and ix represent sampled points within each sub-region in RoI. 
# In this example roi_bin_grid_h = 3 and roi_bin_grid_w = 2, thus we
# have overall 6 points for which we interpolate the values and then average 
# them to come up with a value for each of the 4 areas in pooled RoI 
# sub-regions 
for iy in range(int(roi_bin_grid_h)):
    # ph * bin_size_h - which square in RoI to pick vertically (on y axis)
    # (iy + 0.5) * bin_size_h / roi_bin_grid_h - which of the roi_bin_grid_h 
    # points vertically to select within square 
    yy = roi_start_h + ph * bin_size_h + (iy + 0.5) * bin_size_h / roi_bin_grid_h
    for ix in range(int(roi_bin_grid_w)):
        # pw * bin_size_w -  which square in RoI to pick horizontally (on x axis)
        # (ix + 0.5) * bin_size_w / roi_bin_grid_w - which of the roi_bin_grid_w 
        # points vertically to select within square 
        xx = roi_start_w + pw * bin_size_w + (ix + 0.5) * bin_size_w / roi_bin_grid_w
        print(xx, yy)

# xx and yy values: 
# 2.57 0.74
# 3.15 0.74
# 2.57 1.62
# 3.15 1.62
# 2.57 2.50
# 3.15 2.50

在图 6 中,我们可以看到子区域 1 的相应的 6 个采样点坐标。

作者图片—图 6

为了对坐标 (2.57,0.74) 的第一个点对应的值进行双线性插值,我们找到该点所在的方框。所以我们取这些值的——(2,0) ,它对应于盒子的左上角点 (x_low,y_low) ,然后在这些坐标上加 1 就找到了盒子的右下角点 (x_high,y _ high)——(3,1)。这表现在下图中:

作者图片—图 7

根据图 3,点 (0,2) 对应 0.2,点 (0,3) 对应 0.012,以此类推。按照前面的代码,在最后一个循环中,我们找到了子区域内红点的插值:

x = xx; y = yy
if y <= 0: y = 0
if x <= 0: x = 0
y_low = int(y); x_low = int(x)
if (y_low >= height - 1):
    y_high = y_low = height - 1
    y = y_low
else:
    y_high = y_low + 1

if (x_low >= width-1):
    x_high = x_low = width-1
    x = x_low
else:
    x_high = x_low + 1

# compute weights and bilinear interpolation
ly = y - y_low; lx = x - x_low;
hy = 1\. - ly; hx = 1\. - lx;
w1 = hy * hx; w2 = hy * lx; w3 = ly * hx; w4 = ly * lx; 

output_val += w1 * img_feats[y_low, x_low] + w2 * img_feats[y_low, x_high] + \
              w3 * img_feats[y_high, x_low] +  w4 * img_feats[y_high, x_high] 

所以我们对红点有如下结果:

作者图片—图 8

如果我们对子区域中的所有 6 个点都这样做,我们会得到以下结果:

# interpolated values for each point in the sub-region
[0.0241, 0.0057, 0., 0., 0., 0.]

# if we then take the average we get the pooled average value for 
# the first region:
0.004973

最后,我们得到以下平均汇总结果:

作者图片—图 9

完整代码:

对代码的附加注释

上面的代码包含一些我们没有讨论的附加特性,我将在这里简要解释一下:

  • 您可以将 align 变量更改为 True 或 False。如果为真,则将框坐标像素移动-0.5,以便更好地与两个相邻像素索引对齐。该版本用于探测器 2
  • sampling_ratio 定义了 RoI 的每个子区域中采样点的数量,如图 6 所示,其中使用了 6 个采样点。如果 sampling_ratio = -1 ,那么它会像我们在第一段代码中看到的那样自动计算:
roi_bin_grid_h = np.ceil(roi_height / pooled_height)
roi_bin_grid_w = np.ceil(roi_width / pooled_width)

结论

在本文中,我们已经看到了 RoIAlign 是如何工作的,以及它是如何在 torchvision 库中实现的。RoIAlign 可以被视为神经网络架构中的一层,每一层都可以向前和向后传播,从而能够端到端地训练您的模型。
阅读完这篇文章后,我鼓励你也阅读一下 RoI pooling,以及为什么 RoIAlign 更受青睐。如果你理解 RoIAlign,理解 RoI pooling 应该不成问题。

计算复杂性对产品价值的影响

原文:https://towardsdatascience.com/role-of-computational-complexity-in-algorithm-development-and-overall-product-value-623e0b919970

行业笔记

计算复杂性对产品价值的影响

为您的产品选择最有价值的算法

克里斯托夫·高尔在 Unsplash 上拍摄的照片

计算复杂性(包括时间和空间复杂性)的重要性被低估了,尤其是对于成为产品一部分的算法。对于诸如 Kaggle 提供的竞赛的一般算法开发、使用 ML 的数据分析、概念验证(POC)的模型开发和优化,计算复杂度不太重要。然而,对于作为产品进入生产系统的算法,由于有限的资源可用性、预测和模型构建的时间限制,以及最重要的云资源产生的成本,计算复杂性应该得到重视。

计算复杂性在模型训练中起着重要作用,在模型训练中,与预测相比,需要调整和优化,预测的复杂性通常为 O(np)* ,其中 n 是预测的数量,p 是数据集中的特征数量。

模型构建范例:

  1. : 每当你得到一个新的数据,我们就用所有(或有限)以前的数据重建模型。可能是最准确的,但是昂贵、耗时并且在预测上有延迟。非常适合某些时间序列数据,如股票预测和推荐系统(网飞、Hulu 等)。).由于预测中的高延迟,几乎没有人会采用这种方法。考虑到最好的硬件配置和数据架构(最大),有人会等 5-10 分钟让网飞提供最准确的电影推荐吗?最大 RAM 和 GPU 的计算节点数量)?绝不!!!
  2. 批量 : 每小时或每天或每周重建模型。很可能不如流式方法准确,但成本较低,并且根据模型构建的频率和数据量,预测可能会有微小的延迟。适用于数据模式频繁变化、需要重建模型以提高准确性的产品—推荐系统、网络安全威胁检测、产品销售或需求预测等。
  3. 周期性 : 每月或每 3 个月或每 6 个月重新构建模型。与分批法相比,周期要长得多。非常适合很少或从不改变数据模式的应用,例如特定的时间序列数据,包括能源预测、产品或图像分类等。

批量周期范式被公司普遍采用。被训练的算法类型和所使用的数据量可能会显著改变所需的训练时间,并且可能需要昂贵的计算节点来进行处理。尽管从技术上来说,在云环境中使用最大处理能力、RAM 和/或 GPU 来扩展您的资源、计算节点是可能的,但出于上述原因之一,通常不希望这样做,即成本。因此,我们应该经常检查算法的计算复杂度——时间和空间复杂度。没有人会愿意花 5 美元在市场上只卖 9.99 美元的算法开发和维护上,即使它可能是市场上最好的。

例如,让我们考虑一个数据集 D 的分类问题,该数据集具有 n 个观察值和 p 个特征。我们正处于模型开发阶段,我们考虑使用 3 种算法来构建预测模型—逻辑回归、决策树和随机森林。

算法的时间和空间复杂度是:

  • 逻辑回归:O(n*p)和 O(p)
  • 决策树 O(nlog(n)p)和 O(树的深度)
  • 随机森林 O(nlog(n)pk)和 O(树的深度k)

其中 k 是树的数量。

特征 p = 100,树深度 k = 10(图片由作者提供)

O(n * p)<O(n * log(n) p)<O(n * log(n) p * k)<O(n * n)**

随着元素 n 数量的增加,操作数量也会增加,如上图所示。为了在大约相同的时间内获得结果(建模),可能需要更多的资源。逻辑回归在计算上比决策树便宜,而决策树又比随机森林便宜。当我们添加更多数量的计算节点时,我们会增加运行时构建算法的成本。随着数据量的增加和高度复杂的算法,我们可能需要改变基础设施本身,以实现与不太复杂的算法相同的结果。

例如,对于一个具有 O(np)* 的简单算法,我们可以使用一个简单的基于 spark 的系统进行并行化,计算节点的数量要合理地少(例如:5 个),而当复杂性增加时,我们可能需要增加节点的数量来在规定的时间内实现相同的结果,这导致了计算成本的增加。当数据量很大时——如前所述,计算时间明显增加,此外,我们可能需要更改基础架构——从 DB read 切换到 Kafka 以减少数据读取延迟,从而增加基础架构和维护成本。对于机器学习(ML)模型,除了深度学习(DL)模型之外,空间复杂度通常较低。

在一个企业中,保持合理的低成本是利润最大化的第一件事,这将允许企业自身的发展。一家公司不会花费超过为服务或产品提供的金额,除非是某些产品,如人工智能系统,在这些产品中,建立最准确的预测系统将成倍提高客户满意度,并建立市场领导地位——这是一种创业意识,在早期阶段通过牺牲利润来赢得客户。对于模型构建——在成本和准确性或模型的复杂性之间总是有一个权衡(假设复杂模型比简单模型更准确,这在大多数情况下是正确的)。对于预测-在成本和预测时间之间进行权衡。

“产品价值是客户通过使用产品满足其需求而获得的收益,减去相关的成本。复杂性是将这样的产品交付给客户所付出的努力。”休伯特·帕兰

产品价值=收益—成本

  • Dev(t) — 在算法和相关产品的整个生命周期中发生的开发、维护和基础设施成本。
  • 基础设施成本( Infra(t) )包括软件或工具购买、订阅等。,以支持产品的开发和维护。
  • 资源(t) — 与开发和维护人员(resᵢ)相关的成本/工资,取决于工资率(rateᵢ)和时间(tᵢ)
  • 管理——与整个管理相关的成本
  • 营销——与营销工作相关的成本

与讨论主题相关的唯一成本是开发(t) 成本,包括算法开发、优化、部署到生产系统中,以及实时执行和系统支持。作为一名数据科学家,我们有责任尽最大努力保持合理的低成本,同时牺牲与产品价值相关的预测准确性。

线性回归、逻辑回归、朴素贝叶斯等算法。比随机森林、SVM、神经网络等复杂算法便宜。这在大多数情况下是高度精确的,当然涉及高计算成本。因此,我们绝对需要考虑为客户提供相当好的产品所花费的成本(可接受的精度水平、模型构建和预测时间)。请注意,我们可能不会为所有事情构建一个单一的 ML 模型。我们经常需要为每个客户或服务或两者建立模型,并且所有这些都需要在一定的时间范围内发生——比如一个小时或几个小时。预测时间框架也是如此,它通常要短得多,比如一秒或一分钟。这仅适用于您计划在生产系统中部署并应用到产品中的算法。

如果上述示例中考虑的所有 3 ML 算法产生几乎相同的交叉验证结果—假设 85% (LR)、87% (DT)和 88% (RF) 。基于手头的产品,我们应该倾向于 LR,因为它是最具成本效益的算法,并且 O(np) 不会显著降低预测精度。*

我希望我已经足够强调了算法中计算复杂性的重要性,以及它是如何影响产品成本的。如果你是数据科学家,重要的是始终考虑与算法相关的成本,这反过来取决于计算的复杂性。通常,计算成本不会是产品成本的重要组成部分之一,但如果我们不专注于控制它,它可能会成为一个重要组成部分。

感谢阅读!如果我遗漏了什么,请留下您的反馈并帮助改进这篇文章。

NumPy 中的滚动窗口——时间序列分析方法的支柱

原文:https://towardsdatascience.com/rolling-windows-in-numpy-the-backbone-of-time-series-analytical-methods-bc2f79ba82d2

NumPy 中的滚动(滑动)窗口计算是如何工作的?它们和熊猫打滚相比如何?

图片作者。

滚动或滑动计算在时间序列分析中至关重要。从金融到流行病分析,很可能你需要执行移动窗口计算,所以学会如何做并做好它们是至关重要的。

这个故事还将探索 NumPy 中滚动计算的基础及其局限性;它还将包括一些日常用例的配方。

在 Pandas 中,执行滚动计算非常简单,也许比 NumPy 更简单。在 NumPy 中,执行这个任务的实用程序有些隐藏;也许这就是为什么在时间序列滚动中很少看到 NumPy 代码的原因。

一个自然的问题出现了:如果我们可以很容易地在熊猫身上做到这一点,为什么要使用 NumPy?在这个故事中,我们将比较熊猫和 NumPy 的滚动窗口。我喜欢熊猫,但有时我们需要更深入地了解 NumPy,以便更精细地处理数据。

在 Pandas 和 NumPy 中,对向量(1D 数组)进行滚动计算非常简单,但是对矩阵进行滚动计算更具挑战性;这就是为什么我们需要 NumPy 的粒度。

故事结构

  • 滑动窗口->添加额外维度
  • For 循环与 NumPy
  • 滚动阵列存储器配置文件
  • 函数:1D 数组->标量
  • 功能:多个 1D 阵列-> 1D 阵列
  • 熊猫大战熊猫
  • 2D 阵列中的滚动函数
  • 多元回归的滚动最小二乘系数
  • 多元回归的滚动最小二乘 R-squared
  • 2D 阵列的滚动线性组合
  • 这个故事的寓意

滑动窗口->添加额外维度

滚动机制[图片由作者提供]。

NumPy 的滚动窗口解决方案是创建另一个具有额外维度的数组。这种数组包含附加轴的每个索引上的指定滑动窗口处的滚动原始数组。这个工具有些隐蔽,你可以通过导入中的
点的数量来判断:

np.lib.stride_tricks.sliding_window_view

例如,让我们创建一个包含 1000 个元素的 1D 数组:

(1000,)

然后我们用 100 的窗口滚动它:

(901, 100)

如您所见,形状的维度增加了一,这个新维度的大小正好是窗口的大小。

对于维数更高的数组也是如此,例如,对于具有 1,000 行 10 列的 2D 数组:

(1000, 10)

如果我们沿着行滚动它:

(901, 10, 100)

For 循环与 NumPy

由于滑动(滚动)计算在按时间索引的过程中最有用,首先想到的编程控制流语句是for循环。

NumPy 以其相对于纯 Python 的性能而闻名。因此,让我们比较一下使用for循环和 NumPy 的sliding_window_view循环通过滚动回顾窗口遍历数组所需的时间:

每圈 37.2 秒±4.06 秒(平均标准偏差。戴夫。7 次运行,每次 10,000 个循环)

每圈 297s±14.2s(平均标准偏差。戴夫。7 次运行,每次 1000 个循环)

众所周知,如果使用得当,NumPy 至少比 Python 快一个数量级。这次考试也不例外。

滚动阵列存储器配置文件

一个自然的问题出现了:

如果一个额外的维度被添加到滚动窗口大小的数组中,这难道不是内存效率很低吗?

答案是否定的,因为新数组实际上是原始数组的视图;因此,内存中没有副本。

让我们创建两个数组,并检查它们是否共享内存

真实的

当然,他们有。

然而,新的滚动数组的内存使用量稍高,因为为view本身创建了一些新数据。但是内存使用率高多少呢?

让我们使用 Pympler 内存分析器编写一个小测试。我们将通过参数size_lb(数组大小下限)size_hb(数组大小上限)和size_steps来改变原始数组的大小,以控制试验的次数。窗口的大小通过window 参数指定:

返回的dataclass具有每个大小的数组内存使用量和滚动数组使用量。结果总结在下面的内存使用率图中:

内存比率图:滚动数组/原始数组[图片由作者提供]。

我们可以看到,对于较小的阵列,滚动阵列视图的开销很大,但随着阵列变大,它会收敛到 1。

函数:1D 数组->标量

一个函数:1D 数组-> float,当滚动时产生另一个 1D 数组。

在 1D 数组中执行滚动计算是最简单的情况。直觉上,我们希望映射一个函数,它接受回看数组并返回一个标量;这样,一个新的 1D 阵列就产生了。新数组的长度为:

原始数组的长度—窗口大小+ 1

一旦我们有了滚动数组(有了额外的维度),我们需要沿着最后一个轴(滚动轴)应用一个 NumPy 函数;为此,我们有两个选择:

  • 使用 NumPy 内置函数
  • 在滚动轴中使用带有reduce方法的数字ufunc

如果你想了解更多关于ufuncs和如何创建你自己的真正矢量化ufuncs的信息,请查看这个故事

计算过程中使用的函数必须针对数组进行优化;这是利用 NumPy 性能的唯一方法。使用 NumPy 的实用程序如apply_along_axis不会导致性能提升。

一个简单的例子是计算滚动标准差。让我们首先用标准正态分布的样本创建一个数组,然后滚动该数组。我们可以保持生成的数组不变,或者回填缺失的索引:

图片作者。

功能:多个 1D 阵列-> 1D 阵列

一个函数:2D 数组(多个 1D 数组)-> 1D 数组(多个浮点数),当滚动时产生另一个 2D 数组。

我们也可以同时对多个向量进行滚动计算。这些向量可以排列成 2D 阵列,但是滚动计算本身不在滚动矩阵上。所有列(行)都以同样的方式处理;在适当的矩阵卷上,列(行)索引之间没有区别。这就是矢量化的工作原理。

例如,我们可以用标准正态样本创建一个 2D 数组(每列一个过程),然后一次性计算所有列的滚动标准偏差:

图片作者。

事实证明,适当的矩阵滚动,即我们可以以特定的方式组合列(行),要复杂得多,我们将在后面的章节中看到。

熊猫大战熊猫

Pandas 有一个非常方便的滚动序列和数据帧的方法。我发现它比 NumPy 的更直观,更容易使用。但正如我们将看到的,它也有缺点。

就速度而言,熊猫滚动和 NumPy 相比如何?让我们计算滚动标准偏差并找出:

每圈 258s±20.7s(平均标准偏差。戴夫。7 次运行,每次 1000 个循环)

每圈 262s±8.16s(平均标准偏差。戴夫。7 次运行,每次 1000 个循环)

如你所见,速度性能都差不多。因此,对于内置计算来说,这是一个平局。

如果我们想应用一个自定义函数呢?我们可以在 pandas 中使用apply方法,在 NumPy 中使用apply_along_axis方法来使用我们的函数,该函数接受一个 1D 数组(序列)并返回一个浮点数:

每循环 5.31 毫秒 386 秒(平均标准偏差戴夫。7 次运行,每次 100 个循环)

每循环 6.64 毫秒 693 秒(平均标准时间。戴夫。7 次运行,每次 100 个循环)

同样,它们在速度上不相上下。

但是最后一个测试有一个警告;在使用 NumPy 时,如果可以的话,我们应该避免使用apply_along_axis,因为它没有针对速度进行优化。为了释放 NumPy 的性能,我们需要在滚动数组中使用的函数下,使用编译的、真正的矢量化、ufuncs。查看这个故事来了解关于ufuncs的一切。

例如,如果我们以矢量化的方式进行相同的计算:

每圈 79.8 秒±3.77 秒(平均标准偏差。戴夫。7 次运行,每次 10,000 个循环)

令人印象深刻的提升。

我们可以争辩说,我们可以在 Pandas 中应用编译的矢量化函数和ufuncs 。然而,在熊猫滚动的工作流程中迷路更容易。经常会混淆什么是矢量化(快速性能),什么不是矢量化(慢速)。NumPy 说得很清楚。

NumPy 提供的只是一个额外维度的滚动数组视图,我们可以根据自己的需要使用这样的数组。如果您正在进行非向量化操作或在 NumPy 数组上使用 for 循环,您知道这样做是在自担风险。

2D 阵列中的滚动函数

将 2D 数组滚动到标量中[图片由作者提供]。

正如我们前面指出的,滚动矩阵(2D 阵列)是棘手的。在 NumPy 或 Pandas 中,对滚动矩阵应用用户定义的函数没有简单的解决方案。如前一节所述,同时滚动多个 1D 阵列(技术上是 2D 阵列)与滚动一个矩阵不同。

滚动矩阵的一个示例是对多个回归变量(具有多个列的矩阵)执行滚动最小二乘回归,并对每个行索引(可以将此轴视为时间)获取 R 平方度量或所有回归变量系数。我经常面临的问题。在前一种情况下,2D 数组在滚动时会产生 1D 数组,而在后一种情况下,2D 数组在滚动时会创建另一个 2D 数组。

有很多方法可以做到这一点,但是没有一个通用的 T2 解决方案。没有任何方法可以应用一个任意的、可能是纯 Python 函数,并期望它能够工作并且很快。相反,我们需要能够产生一种算法,该算法可以利用一个或多个编译和矢量化的操作来操纵滚动数组。通常,除了 NumPy 的工具之外,还需要一些数学知识。

在下面的部分中,我们将回顾一些我经常使用的滚动 2D 阵列的配方。

多元回归的滚动最小二乘系数

要做滚动最小二乘回归,我们首先需要知道矩阵形式的数学。以下形式的线性模型:

其中 ε 为内径,可通过 yx 的多个样本进行拟合。考虑所有样本可得出模型规格的矩阵方程:

其中 y,βε 是大小等于样本大小的列向量, T 上标表示转置。 X 是一个有 n 列的矩阵,行数等于样本大小。如果我们想包含一个不为零的截距,矩阵 X 的第一列中有 1。

那么 β 的 OLS(普通最小二乘法)估计为:

利用这个方程,我们可以进行适当的矢量化滚动。但是首先,让我们用最简单的方法,使用一个for循环来滚动。然后我们将实现矢量化版本并比较性能。

我们的玩具数据将是 1,000 x 10 个标准正态样本的数组:

图片作者。

我们需要应用的在滑动窗口的每个索引处获得 β 的函数是:

然后,我们使用一个for循环编写一个通用滚动代码,该循环使用一个Func4Loop类型的静态函数进行滚动:

如果我们传递我们的函数,我们可以创建一个矩阵,其中每一行都是一个 β 向量:

现在让我们实现矢量化版本。它比for循环稍微复杂一点,但是值得努力。

  • 如果需要,第一个函数_get_X_n_y_rolled将一个常数加到 X 上,并生成滚动阵列视图。
  • 第二个函数_get_beta_vec_from_Xy通过np.einsum执行滚动矩阵乘法,对产品进行粒度控制, t 轴(第一个轴)保留在所有产品中。
  • 最后一个函数只是合并了前面函数的逻辑。

调用我们的函数,我们创建我们的滚动 β 矩阵,向量化:

两种方法产生相同的结果:

真实的

尽管有一些微小的数字差异。

好的部分来了,让我们测试一下速度:

每循环 314 毫秒±141 毫秒(平均标准时间。戴夫。7 次运行,每次 1 个循环)

每循环 56.4 毫秒±2.48 毫秒(平均标准时间戴夫。7 次运行,每次 10 个循环)

矢量化版本确实值得努力。

多元回归的滚动最小二乘 R-squared

获得滚动 R 平方需要更多的数学知识。请记住,该指标可以表示为:

其中 y_c 是居中的y 矢量【贬低】,矢量 e 是剩余矢量:

和上一节一样,我们首先用一个for循环开始滚动。也就是说,我们定义了在循环的每次迭代中应用的静态函数:

借用上一节中的get_rolling_func_loop,我们计算滚动 R 平方:

对于矢量化版本,我们还借用了上一节中的函数_get_X_n_y_rolled_get_beta_vec_from_Xy。像上一节一样,我们使用np.einsum进行滚动矩阵乘法来保留第一个轴(即 t 轴)。

如您所见,两种计算是等效的:

真实的

图片作者。

和速度:

每循环 958 毫秒 397 毫秒(平均标准时间。戴夫。7 次运行,每次 1 个循环)

每循环 73.7 毫秒±2.11 毫秒(平均值±标准偏差戴夫。7 次运行,每次 10 个循环)

和往常一样,矢量化版本不会让人失望。

2D 阵列的滚动线性组合

2D 阵列的滚动线性组合(按列排列)。

我想和大家分享的最后一个配方是滚动线性组合。这在时间序列分析中对向量进行协整时非常有用。

在每个时间点,我们都有一个权重向量(w_vec),形成多个时间序列的线性组合。所以实际上,我们有两个列数相同的矩阵,一个用于权重(w_mat),另一个用于时间序列(每列一个序列)。

每一个w_vec都是在一个使用回顾窗口的协整设置中找到的。因此,滚动线性组合允许我们分析协整向量算法的演变。

对于我们的玩具数据,我们创建了一个数组(a )包含时间序列,w_mat,包含线性组合系数(列的索引对应于a 's列)。

然后我们创建滚动数组(a_rolled)并执行乘积(a_prod_w_rolled)。这个产品是 2D 的;每行包含滚动线性组合时间序列:

最后,我们可以对最后一个轴应用一个函数;我们使用标准偏差进行演示。

图片由 autor 提供。

说清楚。该函数适用于随时间变化的系数和过程的滚动线性组合。

这个故事的寓意

这个故事没有单一的寓意,而是有四个:

  • 没有什么神奇的方法可以应用一个通用函数来执行快速的矢量化滚动计算,尤其是当我们滚动 2D 数组并且列(行)的顺序很重要的时候。我们需要停下来,设计一种算法,能够利用矢量化代码的性能,实现我们的功能目标。
  • 有时 NumPy 提供了熊猫所缺乏的粒度。
  • 使用 NumPy 时,一定要注意不要把编译(矢量化和优化)的代码和纯 Python 代码混在一起;否则,我们的性能会受到影响。如果我们必须这样做,界限应该尽可能的清晰。
  • 当您需要在阵列之间定制产品时,请使用np.einsum

我希望这个故事对你有用。 订阅 到我的邮件列表如果你想知道更多这样的故事。

喜欢这个故事吗?通过我下面的推荐链接成为一个媒体成员来支持我的写作。无限制地访问我的故事和许多其他内容。

*https://medium.com/@diego-barba/membership *

不用机器学习就能把任何语言罗马化

原文:https://towardsdatascience.com/romanize-any-language-without-machine-learning-c9ef6bc88b77

一种利用 Unicode 表的启发式方法

图片来自 Pixabay

罗马化是将语言和文字转换成一种通用的拉丁表示法的任务。

让我们以日语单词“アメリカ".”为例如果你没有学过日语,你可能不知道它是什么意思。让我们把它罗马化——我们得到:“美国。”

现在,你能猜出“アメリカ”是什么意思吗?

你猜对了:美国。

罗马化不是翻译。它比翻译简单得多,但可以帮助你理解你不知道的语言中的单词,甚至句子。

在自然语言处理(NLP)中,罗马化是一种非常廉价的方式,可以在不同的语言中进行单词或句子的比较,例如使用利用字符串相似性的方法或度量。

NLP 示例

“アメリカ”和“美国”之间的 Levenshtein 距离是最大值 7,因为我们必须替换 4 个字符并添加 3 才能将“アメリカ”转换为“美国”。它并没有告诉我们,这两个词实际上在意义或发音上非常接近。如果我们将“アメリカ”罗马化为“amerika”,那么“amerika”和“America”之间的 Levenshtein 距离是 2,表明这两个词彼此相对接近。

在本文中,我将介绍一种简单而高效的罗马化方法,它基于适用于任何语言的 Unicode 表和规则。该方法由 Hermjakob et al. (2018) 提出。不需要任何机器学习。因此,它是轻量级的,在小型 CPU 上工作,除了 Unicode 表之外不需要任何数据。

基于启发式的罗马化

目标是将任何语言的 Unicode 字符映射到 ASCII 字符。

定义

Unicode 是世界上大多数书写系统和语言中一致的文本编码标准,包括那些已经灭绝的书写系统和语言。当前版本(15.0)包含 161 个书写文字的 149,186 个字符。

ASCII 是另一种编码标准。它的第一版可以追溯到 1963 年,当时计算机的功能非常有限。ASCII 标准只包含 95 个可打印字符。

罗马化文本不会包含任何音调符号。例如,法语单词“café”将被映射为“cafe”。

注意:说到这里,我必须补充一点,罗马化不是音译。换句话说,罗马化不会为可能使用隐式元音的语言(例如,阿拉伯语)创建任何丢失的元音。

既然我已经阐明了目标,让我们来讨论这种基于启发式的方法是如何工作的。

Unicode 表包含明确的字符描述。维基百科上的 Unicode 字符列表给出了这些描述。由 Hermjakob 等人(2018) 提出的方法利用它们来绘制角色。

让我们看看这些描述中的一些,例如越南文字:

截图来自Unicode 字符列表(维基百科)。第一列是语言。第二列是字符代码。第三列是角色本身。第四列是描述。—图片由作者提供。

越南语使用许多音调符号,即附加在拉丁字母上的小符号。正如我们在这个 Unicode 表中看到的,描述明确指出了字符是如何形成的。例如,“ỏ”是一个“上面带钩的拉丁文小写字母 o”

鉴于这种描述,基于启发式的罗马化只是制定了一个规则,即“ỏ”是拉丁字母“o”。实际上,上面截图中的越南语字符都被罗马化成了“o”。

让我们举另一个例子,比如在俄语中使用西里尔字母。

截图来自Unicode 字符列表(维基百科)——图片由作者提供

这种罗马化采用其他启发法,例如如果辅音后面跟着一个或多个元音,罗马化将只保留辅音。

这里,对于这些西里尔字符,罗马化简单地映射如下,给出了 Unicode 表中的描述:

  • “х”到“h”
  • “ц”到“ts”
  • “ч”到“ch”
  • “ш”到“上海”

我给出的越南语和西里尔语的例子非常简单。在实践中,要使这种方法具有普遍性,还需要制定更多的规则。

此外,对于某些字符家族,Unicode 表中没有任何描述。例如,对于中文、朝鲜语和埃及象形文字,我们必须使用其他资源(如汉字的汉语拼音表)来制定单独的罗马化规则集。

还有数字的特例。一个罗马化程序应该将任何语言的数字映射到相同的数字以保持一致性。

我们可以选择将数字罗马化为西方阿拉伯数字,例如 0 到 9。对于某些语言,当一个数字可以映射到其对应的西方阿拉伯数字时,映射将是微不足道的。例如,在孟加拉语中,通过以下一对一的转换,数字“২৪৫৬”将被罗马化为 2456:

  • “২”改为“2”
  • “৪”到“4”
  • “৫”到“5”
  • “৬”到“6”

对于其他语言,计数系统可能会有很大不同。中国人、日本人或阿姆哈拉人就是这种情况。我们必须为这些语言创建另一组规则,如下例所示:

  • 在阿姆哈拉语中:፲፱፻፸由四个数字፲、፱、፻和፸组成,它们被映射到 10、9、100 和 70,然后将被转换成罗马化序列 1970。፲፱፻፸是 1970 年。
  • In Japanese: 32万 is made of three numbers, 3, 2, and 万, that will be mapped to 3, 2, and 0000 which will be then transformed into the romanized sequence 320000. 32万 is 320000.

乌洛曼

幸运的是,我们不必实现所有这些规则。Ulf Hermjakob (USC 信息科学研究所)发布了一个“通用”的罗马化器,称为“uro man”实现它们。

该软件是用 Perl 编写的,可以在任何有 CPU 的机器上运行。它是开源的,可以无限制使用

它将需要罗马化的字符序列作为输入,并将罗马化的版本作为输出。

所有罗马化的规则都列在一个单独的文件中,如果需要,可以修改这个文件来添加新的规则。

为了进行演示,我们将尝试在不翻译的情况下理解一些俄语。

我将把下列短语罗马化:

  • Компьютер
  • Графическая карта
  • Америка
  • Кофе
  • Россия
  • есть хлеб
  • бег по утрам

如果你不懂俄语,你就猜不出它们是什么意思。

我将短语保存到文件“phrases.txt”中,每行一个短语,然后使用以下命令将文件 phrases.txt 作为输入提供给 uroman:

bin/uroman.pl < phrases.txt

它将生成如下罗马化短语:

  • компьютер→komp yuter
  • графическаякарта→graficheskaya karta
  • америка→美国
  • коте→kofe
  • россия→罗西亚
  • есть хлеб → est khleb
  • бег по утрам → beg po utram

现在,我们可以猜测它们的意思。我可以对前 5 个条目这样做:

  • “kompyuter”是“计算机”
  • “graficheskaya karta”是“图形卡”
  • “amerika”就是“美国”
  • “kofe”是“咖啡”
  • “罗西亚”是“俄罗斯”

它们中的大多数在被借用的语言中有相似的发音。你猜测罗马化单词意思的能力很大程度上取决于你已经知道的语言。

最后两个短语更有挑战性。如果你不熟悉一种接近俄语的语言,那么罗马化就不是很有用。

  • “est khleb”是“吃面包”
  • “乞博乌特拉姆”是“晨跑”

这只是俄语的一个例子。你可以对任何语言做同样的事情。

这种方法的局限性

虽然这种方法是通用的,但对于有限的几种语言来说并不准确。

For instance, the Chinese character “雨” (rain) will be romanized into “yu”.

但是在日语中,同样的一个字,意思相同,但是发音却大不相同,怎么办呢?

这种方法不做区分。如果你用这种方法罗马化日文,你会得到一个更接近中文发音的罗马化版本。没有任何语言标识来阻止它。

结论

这种罗马化方法获得了 ACL 2018 最佳演示论文奖。

该方法简单、通用。它通过将任何语言转换成相同的书写脚本来简化许多自然语言处理任务。

然而,如果你需要一个精确的特定语言的罗马化器,我不推荐。如果您有合适的训练数据和所需的计算资源,序列到序列深度学习方法将产生更准确的结果。

如果你喜欢这篇文章,支持我工作的最好方式就是使用我的链接成为一个媒体成员:

https://medium.com/@bnjmn_marie/membership

如果你已经是会员,想要支持这项工作, 就在 Medium 上关注我吧。

posted @ 2024-10-18 09:33  绝不原创的飞龙  阅读(376)  评论(0)    收藏  举报