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

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

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

利用数据的漂移和稳定性建立更具弹性的模型

原文:https://towardsdatascience.com/use-the-drift-and-stability-of-data-to-build-more-resilient-models-13b531d0b6e7

预测模型的好坏取决于为其提供动力的数据

图片由 iStock 授权的 bor chee 拍摄

在构建预测模型时,通过精度、召回率和曲线下面积(AUC)等指标衡量的模型准确性,传统上是模型设计和可操作化的主要驱动因素。虽然这导致了在训练和测试时的高保真模型构造,但是生产中的性能通常会降低,产生比预期更差的结果。

随着机器学习(ML)在组织内的成熟, 弹性 往往会超越原始的预测准确性,成为操作化模型的定义标准。越来越多的 ML 实践者倾向于操作性能良好、可预测的生产模型,而不是那些在测试中表现出高性能但在部署时不太兑现承诺的模型。这种对弹性模型的偏好可以从来自unite . ai【1】rapid miner【2】和卡内基梅隆大学软件工程学院的文章中得到证明【3】,但是我们如何到达那里呢?在我工作的 Mobilewalla,我们亲身经历了这个问题。在过去 3 年中,我们在生产中部署了 100 多个模型,产生了各种各样的结果:从匿名数字用户的年龄和性别预测,到没有信用记录的申请人的消费贷款违约倾向。在内部,我们跟踪一个名为不稳定时间 (TTI)的指标,该指标测量模型表现出不稳定所需的时间,即,与预生产基准相比,它低于阈值精度。我们模型的平均 TTI 约为 90 天,方差在 3-250 天之间,明显偏向该范围的较低等级。

数据漂移和稳定性 模型在生产中的表现与训练和测试时不同的原因有很多,其中之一就是锚定它们的数据属性的变化。用于创建模型训练特征的原始数据与部署中支持模型的数据不同,这种现象被称为数据漂移。当现实世界中的环境以意想不到和计划不到的方式变化时,就会发生数据漂移,这可以说是非弹性模型的主要原因。
实际上,今天的每个企业 ML 软件工具包都包括确定数据漂移的机制,表现为如下漂移函数:

drift_pkg(distribution1, distribution2) — >{Drift_Metric}

Large cloud providers have also provided for data drift reporting within their ML suite of offerings; for example, Microsoft Azure Machine Learning dataset monitor, and Amazon Web Services SageMaker Model Monitor.

These are useful tools, but their biggest drawback is that they are reactive. When a deployed model misbehaves, these tools are invoked to check drift, revealing how the data fed into the underperforming model differs from the data used to train it. If drift is detected, the model is corrected (primarily through retraining).

Towards Resilient Models, Not Reactive Tooling Correction is fine and necessary, but it doesn’t address the most critical ML engineering problem of all — how do we build resilient ML models from scratch? Achieving resiliency means building models that have predictable behavior, and seldom misbehave. Without resiliency, operationalizing ML models will remain a major challenge — modelers will continue to build models that underperform in production, requiring frequent correction. The continual need to re-engineer these models will raise organizational questions over the operational utility of ML/AI.

We have come to believe that it is time to reconsider how drift is used in ML workflows and envision novel ways to incorporate drift into the model-building workflow to prevent, not react to, model misbehavior. To do this, we have formulated an artifact called data stability.

Prioritizing Data Stability Data drift represents how a target data set is different from a source data set. For time-series data (the most common form of data powering ML models), drift is a measure of the “distance” of data at two different instances in time. The key takeaway is that drift is a singular, or point, measure of the distance between two different data distributions.

While drift is a point measure, stability is a longitudinal metric. We believe resilient models should be powered by data attributes that exhibit low drift over time — such models, by definition, would exhibit less drift-induced misbehavior. In order to manifest this property, drift over time, we introduce the notion of data stability. Stable data attributes drift little over time, whereas unstable data is the opposite. We provide additional details below.

Consider two different attributes: the daily temperature distribution in NYC in November (TEMPNovNYC) and the distribution of the tare weights of aircraft at public airports (AIRKG). It is easy to see that TEMPNovNYC has lower drift than AIRKG; one would expect lesser variation between November temperatures at NYC across various years, than between the weights of aircrafts at two airports (compare the large aircrafts at, say JFK, to ones in a smaller airport, like Montgomery, Alabama). In this example, TEMPNovNYC is more stable as an attribute than AIRKG.

Data stability, though conceptually simple, can serve as a powerful tool to build resilient models. To see this, consider the following simple expression of data flow in ML:

data — > features — > model

Base data ingested into the modeling workflow is transformed to produce features, which are then fed into models. Stable data attributes are likely to lead to stable features, which are likely to power resilient models. We use a simple scale of stability values — 0, 1, 2, 3, 4, where 0 indicates “unstable,” and 4 denotes “highly stable” and each data attribute is assigned a stability value. These assignments, provided to our modelers during the feature engineering process, help them build features, and in turn models, that have known stability characteristics. Knowing how stable each input attribute is, modelers attempt to choose stable data elements, while avoiding unstable ones, to build stable features, which, in turn, will power resilient models. Understanding the stability value of data used to construct a feature would alert MLOPs that downstream models (i.e., those using this feature as input) need to be monitored more closely than others. MLOps can now anticipate rather than react.

A Change in Mindset, a Change in Methodology We realize that this is a substantive departure from extant methodologies. The current data pipeline for feature construction and model building doesn’t incorporate how “drifty” specific data items are or factor in the notion of data stability. Instead, it’s an ad hoc process, driven primarily by modeler intuition and expertise, incorporating analytic procedures (such as exploratory data analysis or EDA) whose primary objective is to provide the modeler insights into the predictive power of individual data and features. The primary reason for constructing and eventually using a feature is its contribution to the model’s accuracy. The significant drawback of this approach, as we know now, is that high predictivity shown at model testing doesn’t always translate in production, frequently driven by different properties of data than was determined at training and testing. Thus, unstable models.

Based on our experience, it is time for an approach informed by data stability. The modeler doesn’t need to sacrifice predictive power (i.e., model accuracy) but should be able to trade-off model accuracy and stability, building resilient, “accurate-enough” models with predictable behavior.

Computing Stability While the notion of using data stability to build resilient models makes intuitive sense, the actual computation of the stability artifact turned out to be quite challenging. Without going into the complete mathematical details, we provide the solution intuition below, followed by links to the source code that implement stability.

概括地说,属性的稳定性估计了它在未来经历漂移的可能性——在某种程度上,我们可以认为稳定性是漂移的预测器。我们的第一个直觉(有点明显)是,该属性过去的行为将是这个预测的合理代理——来自过去的漂移属性将保持如此向前移动。更难的问题是如何表示数据属性的本质——应该跟踪它的哪些属性来模拟它的行为。通过对我们存储的大量历史消费者数据进行大量实验,我们发现以下四个宽泛的属性最适合描述一个属性并衡量其“漂移度”:偏斜度形状。最终的稳定性度量是一个封闭形式的数学表达式,它卷积了代表上述四个属性中每一个属性的特定统计度量。最后一个挑战是规模—稳定性指标在计算上相当广泛—重要的是设计一个可扩展的稳定性实施方案,以便可以在数据摄取时作为核心分析的一部分进行高效计算。为了简洁起见,我们不会在本文中深入讨论细节,但对于感兴趣的读者,我们邀请他们在这里(链接到 anovos.ai)访问稳定性的精确公式的细节,在这里(链接到 Github)访问实现和源代码的细节。在这里(Github 的链接),我们还提供了如何用样本数据集计算和使用稳定性的例子。**

数据稳定性的影响

我们已经在生产中使用稳定性来设计功能 6 个多月了,超过 60%的模型都配备了稳定性工具。我们已经看到了模型弹性方面有意义的改进。平均 TTI 值(我们在前面描述过)已经从 90 天上升到接近 130 天,并且随着我们对更多模型进行测试,这一数值还在不断提高。此外,由于稳定性是一个纵向度量,它本身在预测漂移方面越来越好,因为它的计算是基于更长的历史。虽然我们无法预测最终的改善情况,但我们的目标是将平均 TTI 提高 2 倍(即 180 天的目标平均 TTI),这似乎并非遥不可及。有趣的是,除了平均 TTI 的提高,TTI 范围也从[3,250]提高到[10,250],这表明对于我们最具弹性挑战的模型,我们已经成功地将 TTI 提高了 3+倍。虽然我们还没有,但我们正计划通过增加弹性来衡量模型 ROI 驱动的增加。我们期待着继续分享我们的故事。

【1】Ingo miers wa 博士,弹性>准确性:为什么“模型弹性”应该是可操作化模型的真实指标(2020 年 10 月)https://www . unite . ai/Resilience-Accuracy-Why-model-Resilience-should-be-the-true-metric-for-operationalization-models/

【2】英戈·米尔斯瓦博士,模型精度不够:你需要弹性模型(2020 年 3 月)https://rapid miner . com/blog/Model-Accuracy-You-Need-Resilient-Models/

【3】Alexander Petrilli 和 Shing-Han Lau,人工智能和机器学习系统中的弹性(2019 年 12 月)😕/insights . sei . CMU . edu/blog/measuring-Resilience-in-Artificial-Intelligence-and-Machine-Learning-Systems/

PySpark 的有用代码片段

原文:https://towardsdatascience.com/useful-code-snippets-for-pyspark-c0e0c00f0269

当您处理大数据时,您需要将这些代码放在手边

照片由泰勒维克Unsplash 拍摄

PySpark

PySpark 是我们在 Spark 环境中使用 Python 语言编写分布式计算查询代码时调用的方式。这方面最著名的例子是专有框架 Databricks。

Databricks 是由 Apache Spark 的创造者于 2013 年成立的公司,Apache Spark 是分布式计算背后的技术。它目前正在持续增长,并成为该领域的主要解决方案。

然而,不利的一面是,即使语法是相似的,但它不是相同的,您必须了解代码中的一些特性,否则您将不得不等待很长时间来运行您的代码。

初始提示

在我使用 Databricks 的第一年,我学会了一些技巧,我将在下面进行描述,这样您就不会遇到我在运行代码时遇到的同样的性能问题。

使用筛选和选择数据启动查询,以缩短数据集的大小

黄金法则:在创建脚本时,您总是希望只过滤和选择您实际使用的变量。这个简单的操作减少了数据的大小,从而转化为更快的脚本。

对于大型列表,join 比 isin()更快。

当您有一个数据框、一个值列表,并且您希望只为列表中的那些值过滤 df 时,如果您有一个大于一手值的列表,那么最好使用连接而不是isin()

groupBy 对于大数据来说是痛苦的

是的,这将是一个与数据集大小成比例的缓慢分组。即使这是一个懒惰的功能——意味着只有当你请求像display()count()collect()这样的动作时,它才会被执行——它仍然需要读取所有的数据,这需要时间。

处理发生在内存上。内存越多,交付时间越快。

Spark 最大的优点是处理发生在内存上,而不是磁盘上,因此速度更快。因此,集群的内存越多,速度就越快。

尽可能远离环路

远离循环。但是跑大的时候!如果您习惯于在 Python 脚本中执行循环操作,要知道 PySpark 绝对不是运行循环的地方。考虑到数据的大小以及该命令将被拆分到许多节点中,它将永远运行下去。

总是按表的分区过滤

数据块中的许多表是由给定的字段划分的。如果你知道是哪一个,使用那个变量作为你的过滤器。

PySpark 使用驼色表壳

大部分功能将会是camelCase | inThisFormat()

在将大整数转换为字符串之前,不要过滤它们

如果你想过滤一个类型为long的变量,你会看到 PySpark 不让你使用使用数字的过滤函数。它将要求你把你的过滤值作为一个字符串(如df.filter( col(‘x’) == ‘12345678900000000001’))。如果变量“x”没有被转换成字符串,您可能会看到多个结果,而不仅仅是一个过滤值,因为 PySpark 会寻找最接近那个大整数的第 19 位的匹配。

过滤“long”类型变量,并将其转换为字符串或不转换为字符串。图片由作者提供。

有用的代码片段

这里有一些有用的代码片段,是我在今年使用 Databricks 时收集到的。

进口

# Basic functions
from pyspark.sql import functions as F# These ones I use the most
from pyspark.sql.functions import col, sum, max, min, countDistinct, datediff, when# To create Loops, use Windows
from pyspark.sql.window import Window# For datetime transformations
from datetime import timedelta, date

列出、保存、删除命令

# List files
%fs ls dbfs:/your mount point address# Save a file to dbfs
df.write.format('parquet').save('address')# Save with Overwrite
df.write.mode('overwrite').csv(path)# SIZE OF A FILE
display( dbutils.fs.ls("file_address") )# Remove (Delete) a file
dbutils.fs.rm('file_address',True)

要保存文件并将其导出到你的本地机器上,阅读这篇文章

基本命令

# Display a table
df.display()
display( df.select('col1') )# SELECT
df.select('col1', 'col2', 'col3')# LIMIT Number of rows
df.limit(n)# FILTER
df.filter( df.col1 == xxx )# COUNT
df.select('col1').count()# COUNT DISTINCT
df.select('col1').distinct().count()# JOIN
df1.join( df2, df1.column == df2.column, 'jointype')
df1.join( df2, on='column', how='inner')# Join Left_anti: 
# It is like df1-df2: selects rows from df1 NOT present in df2
df1.join(df2, on=['key'], how='left_anti')# SORT
df.sort('column', ascending=False)# Show NULL values
display( df.filter(df.col_name.isNull() )# CAST VALUES TO OTHER TYPES
df = df.withColumn("col_name", df.col_name.cast('type'))

填充 NAs

# FILL NAs
df = df.fillna(value)
df = df.fillna(value, subset=['col1', 'col2'])

价值观在

# Value IS IN
df.filter( df.col_name.isin(listed_values) )

值介于

df.filter(col('xx').between("2020-01-01", "2020-01-02") ) 

日期差异

from pyspark.sql.functions import datediff, coldf1.withColumn( "diff_in_days", 
                 datediff(col("col_date1"),
                          col("col_date2")) )
   .show()

列出列中的值

# LIST VALUES
data = [row.col for row in df.select('col').distinct().collect()]

分组依据和聚集

# GROUP BY
df.groupBy('col1').sum()# Aggregate
df.groupBy(['col1', 'col2']).agg({'col1':'sum', 'col2':'mean')# Put a List of grouped values in a single column
df.groupBy('colx').agg(F.collect_list('colA'),  
                       F.collect_list('colB')).show()

创建新列

使用withColumn('col_name', operation or condition)

# Create column C as the sum of A + B
df.withColumn( 'C', col('A') + col('B') )

创建具有常数值的新列。您可以使用 pyspark 函数中的函数lit并在其中添加任何值。

# New column with a constant text value
df_1.withColumn( 'new_col', F.lit('text') )

# New column with a constant number value
df_1.withColumn( 'new_col', F.lit(10) )

Spark 数据帧中的转换列表

# Import Row
from pyspark.sql import Row# Create a list
my_list = [1,2,3,4]# Parallelize abuse cards list
rdd1 = sc.parallelize(my_list)
row_rdd = rdd1.map(lambda x: Row(x))
my_df = sqlContext.createDataFrame(row_rdd,['variable_name'])

何时/否则

# Single condition
df.withColumn('my_col', when(col('A').isNull(),'Nothing')\
                        .otherwise('Something')# Multiple conditions
df.withColumn( 'Result', 
               when( (col('A') == 0), 'Zero')
              .when( (col('A') < 0) , 'Negative')
              .when( (col('A') > 0) , 'Positive') 
              .otherwise('NA')  )

计算列的百分比

from pyspark.sql.window import Window

df.withColumn('col_pct', col('col_A')/F.sum('col_A')
  .over(Window.partitionBy()) *100 )

随机抽样调查

根据数据大小的百分比获取数据的随机样本。在本例中,50%的数据没有替换—一旦选择了数据点,就不能再次选择。

df.sample(withReplacement=False, fraction=0.5, seed=None)

在你走之前

很明显,这只是使用 PySpark 可以完成的一小部分工作。此外,最近推出了 Spark 的熊猫,因此它将变得更好。

我知道这些代码片段每天都给我很大帮助,因此我相信它也会帮助你。

别忘了跟着我。

https://gustavorsantos.medium.com/

或者使用我的推荐代码订阅 Medium。

用人工智能创作新的连环画而不用写任何代码

原文:https://towardsdatascience.com/using-ai-to-create-new-comic-strips-without-writing-any-code-cc669bb317a7

关于如何使用 GPT-3 和 DALL-E 为有趣的网页生成原创内容的教程

使用 GPT-3 和达尔-E 生成的漫画样本,图片由作者提供

我花了上个月的时间探索如何使用 OpenAI 的 GPT-3 文本生成器和他们的 DALL-E 图像到文本系统来创建新的连环漫画,而无需编写任何源代码。请注意,这两个系统都处于测试阶段,在我使用了我所有的免费令牌后,他们开始收取象征性的使用费。但是效果还是蛮不错的!OpenAI 授予用户将他们生成的材料用于商业目的的权利。

在这篇文章中,我将提供一些关于 GPT-3 和 DALL-E 的背景知识,解释我如何使用这些系统来创作漫画,并展示结果。一路上,我将强调 GPT 3 中新的文本生成特性,并解释我如何强迫 DALL-E 用不同的视角和姿势绘制同一个角色。

使用 GPT-3 创建标题,人物和场景

我之前写过如何使用 OpenAI 的 GPT-3 文本生成器为迷因创建有趣的标题。通过这个项目,我知道了 GPT 3 号可能会很有趣。

OpenAI 用来自书籍和网页的大量文本训练了他们的 GPT-3 神经网络,包括喜剧材料。下面是 OpenAI 的论文中训练数据的细分:

用于训练 GPT-3 的数据集,来源: OpenAI

根据 OpenAI 的说法,一个“令牌”大约是一个字的 3/4。使用每页 500 个单词,那就是大约 7 . 5 亿页的文本。

为了使用 GPT-3,你必须在这里创建一个账户。创建账户后,OpenAI 会给你价值 18 美元的积分,你可以在他们开始向你收费之前使用。然后,您可以导航到操场,输入提示,它将生成新的文本。

新漫画#1 -马克疯狂

我创作新漫画的第一步是从一个主题概念中产生一个标题。使用 GPT-3 用户界面很容易做到。我只是问了一下。我输入“为一部关于大学体育的幽默漫画创作古怪的标题”,然后点击提交按钮。请注意,我使用了默认的型号、温度等设置。这些效果很好。

使用 GPT-3,由作者生成关于大学体育的漫画标题

系统显示了结果。

**1\. The Benchwarmers
2\. Intramural Idiots
3\. Out in Left Field
4\. One Hitter Wonders
5\. Bleacher Creatures
6\. The Walk-ons
7\. Touchdowns and Turnovers
8\. The Mascots
9\. Mark Madness
10\. Shot Clock Shenanigans**

我喜欢“疯狂马克”,所以我用了那个。如果它们都不好,我可以点击撤销最后一个按钮,然后再次提交来创建一些新的。GPT 三号有很多想法。

请注意,生成的标题不能保证在现实世界中是唯一的。所以我在谷歌上快速搜索了一下,没有找到任何与“疯狂马克”相关的现有漫画一切都好。

用 GPT-3 创造角色

为了创建新漫画的角色,我精心制作了这个提示,“为一个关于大学体育的新漫画创建主角,名为马克疯狂。”点击提交。以下是结果,生成的文本以粗体显示。

Create lead characters for a new comic strip about college sports called "Mark Madness."1. **Mark: A talented but under-appreciated college basketball player who dreams of making it to the NBA.
2\. Jimmy Chitwood: Mark's talented but egotistical teammate who always seems to steal the spotlight.
3\. Suzie Hurley: Mark's girlfriend and fellow student, who is always trying to get him to focus on his studies.
4\. Mr. McNulty: Mark's crusty old basketball coach who is always harping on him to "play with more passion."
5\. Dean Snyder: The dean of Mark's college, a highly respected academic institution that is also known for its Division I athletics.**

这些角色看起来还不错。例如,GPT3 发现主角应该是一个名叫马克的大学篮球运动员。

但是请注意,这组字符中似乎没有太多的文化多样性。GPT-3 的创作者在他们的论文中讨论了偏见和缺乏文化代表性的问题。

训练数据中存在的偏见可能导致模型生成定型的或有偏见的内容。这是令人担忧的,因为模型偏见可能以不同的方式伤害相关群体中的人,因为它强化了现有的陈规定型观念,并在其他潜在的伤害中产生贬低的描述。我们对模型中的偏差进行了分析,以便更好地理解 GPT-3 在公平性、偏差和代表性方面的局限性。—汤姆·布朗等人,OpenAI

他们继续讨论他们对模型中性别、种族和宗教的偏见和文化代表性不足的分析。

用命令编辑文本

OpenAI 正在 GPT 3 中推出一个新的文本编辑功能,目前处于测试阶段。现在,您可以使用该系统使用文本命令编辑任何现有的散文,而不仅仅是通过响应提示来创建文本。为了测试这一点,我使用了上面的角色列表,并要求系统“使角色在文化和种族上更加多样化。”

GPT-3 文本编辑,图片由作者提供

这是结果。

**1\. Mark: A talented but under-appreciated college basketball player who dreams of making it to the NBA.
2\. Enrique: Mark's talented but egotistical teammate who always seems to steal the spotlight.
3\. Sandhya: Mark's girlfriend and fellow student, who is always trying to get him to focus on his studies.
4\. Mr. McNulty: Mark's crusty old basketball coach who is always harping on him to "play with more passion."
5\. Dean Wong: The dean of Mark's college, a highly respected academic institution that is also known for its Division I athletics.**

好吧,它改了一些名字,像吉米改成了恩里克,苏西改成了桑迪亚,让角色更加多样化。

创建场景

文本生成的最后一步是要求 GPT-3 根据给定的标题和角色创建一个场景。下面是我使用的提示,新生成的文本以粗体显示。

Create a scene with dialog for a comic strip about a college sports called "Mark Madness."CHARACTERS:
Mark: A talented but under-appreciated college basketball player who dreams of making it to the NBA.
Enrique: Mark's talented but egotistical teammate who always seems to steal the spotlight.
Sandhya: Mark's girlfriend and fellow student, who is always trying to get him to focus on his studies.SETTING: **The basketball court after a big game.****Mark: Wow, we really killed it out there today!****Enrique: Yeah, no thanks to you. You're lucky I was there to carry the team.**

那是一段精彩的对话。接下来,我解释我如何使用 DALL-E 来渲染这个场景。

使用 DALL-E 从文本中绘制连环漫画

上个月,我写了如何使用 OpenAI 的 DALL-E 图像创建系统来创建数字艺术。这是一个付费系统,目前处于测试阶段,注册这里。您输入一个文本提示,然后点击 Generate 按钮,20 秒钟后,您会看到提示中描述的图像的四个版本。它非常容易使用。

标记疯狂

对于马克疯狂漫画,我使用了这个提示,“一个在篮球场上的漫画,有两个穿着红色制服的角色,马克,一个有才华的大学篮球运动员,和恩里克,一个自负的队友。”以下是 DALL-E 创作的四幅图像。

达尔-E 形象代为马克疯狂,形象作者

我喜欢第二个,但我需要在 Photoshop 中清理一下。我添加了标题和 GPT-3 生成的对话,替换了单词 balloons 中的乱码文本。这是之前和之后的图像。

马克疯狂漫画,原创自 DALL-E (左)并在 Photoshop 中清理后(右),图片由作者提供

这有点像一个视觉难题,你需要找出十个不同点。你能认出他们吗?🙂

我喜欢漫画的整体外观,有一些可爱的细节,如与球队颜色相匹配的红色竖条。然而,人物的眼睛完全不匹配。我用 Photoshop 里的一个小技巧复制了马克的右眼和恩里克的左眼。当然,我在对话中使用了漫画字体。

新漫画#2 -一次一场演出

我用同样的方法用 GPT 3 制作了第二幅漫画的标题、角色描述和场景。这次我从提示开始,“为一部关于车库乐队的幽默漫画创作古怪的标题。”最好的标题是一次一场演出。在为角色生成文本之后,我使用这个提示来生成图像:“一个有三个音乐家的漫画,克莱夫弹吉他,杰玛弹贝斯,拉杰弹电钢琴。”

这是结果。

DALL-E 一次生成一个 Gig 的图像,由作者生成的图像

这一次第一个图像似乎是最好的。下面是我在 Photoshop 中清理和添加标题前后的样子。

一次一场漫画,原创自 DALL-E (左)并在 Photoshop 中清理后(右),图片由作者提供

我从最初的设计中选择了大胆的黑色外观。我不得不把图片向下压缩一点,为标题腾出空间。不需要太多的清理,但我确实在 Photoshop 中添加了音符流。

新漫画#3 - ML 混乱

对于这一个,我想看看我是否能创作一个三幅连环画。我从 GPT-3 的这个提示开始,“为一个新的高科技漫画创作古怪的标题。”

使用 GPT-3,作者的图片为一部科技漫画制作标题

它提出了一些好的建议。

**1\. "ML Mayhem"
2\. "G33k5 Uprising"
3\. "Coding Crazy"
4\. "Cyber security Shenanigans"
5\. "The Great Cloud Catastrophe"**

为马丁·路德·金创造角色

我喜欢军情六处的混乱,但我把它程式化为军情六处的混乱。然后我让 GPT-3 创造主角。生成的文本以粗体显示。

Create lead characters for a new techy comic strip called "M.L. Mayhem"1**. Millie is a brilliant young coder who spends her days creating new ways to make life easier for her friends and family.****2\. Lily Nguyen is Millie's best friend and closest confidante. She's always there to offer a listening ear.****3\. Mayhem is the name of the extremely powerful AI system that Millie accidentally created. It has a mind of its own and is always causing havoc for Millie and Lily.**

好吧,我看到它在那里做了什么。这部连环画的名字叫 M.L. Mayhem,所以它给主角米莉和莉莉起了名字。嗯,M 和 l。非常聪明!而肆虐的人工智能系统的名字叫做 Mayhem。干净整洁。

为三个面板创建场景

接下来,我让 GPT-3 为三个面板生成场景和对话。

Create a scene and dialog for a three-panel techy comic strip called "M.L. Mayhem."CHARACTERS: 
Millie is a brilliant young coder who spends her days creating new ways to make life easier for her friends and family.Lily Nguyen is Millie's best friend and closest confidante. She's always there to offer a listening ear.Mayhem is the name of the extremely powerful AI system that Millie accidentally created. It has a mind of its own and is always causing havoc for Millie and Lily.PANEL 1 SCENE: **Millie is sitting at her computer, her fingers flying across the keyboard as she codes.** **MILLIE: I'm almost finished with my latest project. I can't wait to show Lily!****PANEL 2 SCENE: Lily is sitting on the couch, reading a book.****LILY: I can't wait to see what Millie has been working on. She's so brilliant!** **PANEL 3 SCENE: Millie's computer screen is flashing red and an error message is displayed.****MILLIE: Oh no! What have I done?**

为多画面连环漫画创作艺术作品

为了这个任务,我真的发挥了 DALL-E 的能力。前两部漫画只有一个面板,但我需要为这一部创建三个面板,并为角色创建一致的外观。

下面是我用来创建基本图像的三个提示。

Panel 1: "A techy comic strip where Millie, a brilliant young coder, is sitting at her computer typing."Panel 2: "A techy comic where Lily Nguyen is sitting on the couch, reading a book."Panel 3: "A techy comic, Millie, a college student with red hair and a green shirt, is working on her computer, the screen is showing an error message, and her friend Lily Nguyen, wearing a black shirt and blue sweatpants, is watching in horror."

请注意,我不得不稍微修改了一下提示,试图让角色在视觉上保持一致。DALL-E 为三个提示中的每一个生成了四个版本的图像,下面是最好的几个。

DALL-E 为 M.L. Mayhem 制作的初始图片,图片由作者提供

总的来说,这些都很好。但是,除了乱码文本,第三个面板还有一个视觉一致性的主要问题;DALL-E 不知道这些角色到底应该是什么样子。根据提示,它已经尽力了。

创建角色研究

为了使角色完全实现,我首先在 Photoshop 中润色了图像,然后创建了一个 3x2 网格的模板,并在左上角绘制了米莉。我让六个面板中的五个保持透明。模板的大小为 1024x1024,这是 DALL-E 的工作分辨率。

然后,我将模板上传到 DALL-E,并使用提示编辑图像,“这是对米莉的漫画的性格研究,米莉是一名出色的年轻大学生和程序员,从侧面和正面展示了她六种不同的姿势。”这是结果。

角色研究模板(左),DALL-E 编辑屏幕(中),以及****DALL-E 渲染(右),图片由作者提供

很明显,DALL-E 知道什么是角色研究,并且很好地将我们的主角渲染成不同的姿势。

为了给这幅画创建最终的面板,我选择了其中一个姿势,并把它和透明背景上的莉莉的一部分图像一起粘贴到 Photoshop 中。我擦掉了他们脸上的部分内容,看看 DALL-E 是否能在给出提示的情况下填充他们的表情,“这是一部科技漫画,米莉是一名大学生,正在用电脑工作,屏幕上显示一条可怕的错误信息,她的朋友莉莉·阮(Lily Nguyen)惊恐地看着。”这是结果。

部分图像合成(左),DALL-E 编辑画面(中),以及T5DALL-E 渲染(右),图片作者

系统在填充细节方面做得很好,比如米莉的手在键盘上打字,给她一个双显示器设置。

这是在 Photoshop 中进一步清理后最终的三幅漫画,以及《GPT 3》中单词 bubbles 的对话。

****M.L. Mayem 连环漫画,作者图片

最后的想法

比较这两个系统,我发现 GPT-3 在生成文本方面比 DALL-E 做得更好。虽然漫画的初始图像作为概念渲染看起来不错,但它们需要一些清理才能用于制作。但主要的问题是 DALL-E 并没有为漫画创作出一致的角色。主要限制源于 DALL-E 中 1024x1024 的图像大小。请注意,OpenAI 最近添加了一个新的“outpainting”功能,以便更容易地添加到生成的图像中。然而,新功能只能以渐进的方式工作。它只考虑 1024x1024 帧来生成图像。该系统将需要一个“上传参考帧”功能,以产生一致的视觉元素,如漫画人物。

感谢

我要感谢詹尼弗·林对这篇文章的帮助。

为了无限制地访问 Medium 上的所有文章,成为会员,每月支付 5 美元。非会员每月只能看三个锁定的故事。

用 AI 生成 3D 模型,快!

原文:https://towardsdatascience.com/using-ai-to-generate-3d-models-2634398c0799

AR/VR/3D

如何使用 NVIDIA 的 NeRF 代码创建 3D 模型

照片由 DeepMindUnsplash 上拍摄

生成 3D 模型可能非常耗时,或者需要大量的参考图像。解决这个问题的一种方法是神经辐射场(NeRF),这是一种生成图像的人工智能方法。NeRFs 利用你对一个物体或场景拍摄的一小组 2D 图像,并使用它们(有效地)构建一个 3D 表示。这是通过学习在你已经拥有的图像之间转换来完成的。这种跳跃(或插值,这是一个稍微有点花哨的术语)让你产生物体的新的视角的图像!

听起来很棒,对吧?从一小组图像中,您可以制作一个 3D 模型!这比标准摄影测量有好处,标准摄影测量需要一个巨大的图像库来生成一些东西(你需要每个角度的镜头)。然而,我们在一开始就承诺 NeRFs 很快,直到最近,情况并非如此。以前,NeRFs 花了很长时间来学习如何将你的一组图像转换成 3D 图像。

现在情况不再是这样了。Nvidia 发布了其 instant NeRF 软件,该软件利用 GPU 硬件来运行必要的复杂计算。这将创建模型所需的时间从几天减少到了几秒钟!NVIDIA 对instant-ngp软件的可用性和速度做了很多令人兴奋的声明。他们提供的结果和例子令人印象深刻:

NVIDIA 建造了一个很酷的机器人实验室。Gif 由https://github.com/NVlabs/instant-ngp

我认为很难不对它印象深刻——它看起来棒极了!我想看看把它转移到我自己的图像上并生成我自己的 NeRF 模型有多容易。所以我决定自己安装使用这个软件。在这篇文章中,我将通过我的尝试,并详细介绍我所做的模型!我们走吧!

管道

那我们该怎么办?

  • 我们需要参考录像。我们去录些我们想 3D 化的东西吧!
  • 我们将把它转换成静态图像。这个过程也试图理解我们记录的角度。
  • 我们将它传递给instant-ngp。然后,这将训练人工智能理解我们生成的图像之间的空间。这实际上与制作 3D 模型是一样的。
  • 最后,我们要制作一个我们创作的视频!在 NVIDIA 制作的软件内部,我们将为摄像机绘制一条路径,以便在模型周围拍摄,然后渲染视频。

我不会深入了解这一切是如何工作的(尽管可以随意提问!),但我会放上很多我觉得有用的资源的链接。相反,我会专注于我制作的视频,以及旅途中偶然发现的小部分。

我的尝试(又名邋遢的 Nerf Herding)

我不会说谎,我发现这很难安装。虽然说明很清楚,但我觉得当涉及到您需要的特定软件版本时,没有回购协议的要求部分所暗示的那么大的回旋余地。使用CUDA 11.7或 VS2022 对我来说似乎是不可能的,但我认为是切换回11.6和 VS2019 帮助安装过程向前推进。我有很多错误,比如:CUDA_ARCHITECTURES is empty for target.这是由于 CUDA 不想很好地使用 Visual Studio 造成的。我真的推荐这个 视频和这个回购进一步帮助设置一切!

除此之外,这个过程很顺利。Python 脚本有助于指导将您拍摄的视频转换为图像,以及随后将其转换为模型和视频的步骤。

尝试 1 :乐高汽车

最初,我试图在办公室里改装一辆小型乐高汽车。我认为我的摄影技术有所欠缺,因为我根本无法创作出任何有意义的东西。只是一个奇怪的 3D 污迹。为什么呢?好吧,让我们看看英伟达给我们提供的一个例子。注意摄像机的位置:

NVIDIA 提供的默认 NeRF 的“摄像机”位置,挖掘机。照片由https://github.com/NVlabs/instant-ngp拍摄

训练良好的设置的一个特点是像上面那样放置“摄像机”。这些摄像头是软件认为你拍摄视频时面对的角度。它应该是一个漂亮的圆圈。我的第一辆乐高汽车完全不是这样的,而是一个压扁的半圆。

尝试二:乐高车(不过这次更大!)

试图从我的第一次尝试中吸取教训,我找到了一张可以完全走动的桌子,并找到了一辆更大的乐高汽车。我试着确保我比以前拍得更久。最后,我从各个角度拍摄了 1 分钟的流畅镜头。我训练模型不到 30 秒。经过 4 个小时 720p 的渲染,这是我做的视频:

我的第二辆 NeRF——乐高技术车!

尝试三:植物

好吧,所以第二次尝试更好,至少技术上可行。然而,有一个奇怪的雾,它不是超级尖锐的。对于我的下一次尝试,我也试图从更远的后面拍摄(我假设雾是由 AI 对那里的东西“困惑”引起的)。我试着更多地控制aabc_scale(一种测量场景大小的方法),然后训练了几分钟。渲染后,视频看起来是这样的:

我在客厅的桌子上用一株植物做了一个 NeRF。

好多了!这里令人难以置信的是,它是如何设法精确地呈现钩针花盆和木材上的凹槽,以及错综复杂的树叶。看看相机在树叶间的小俯冲!

尝试四:所以,我们越来越好了!不过,我想要一个外面的——让我们试试吧。我在公寓外拍摄了不到两分钟的镜头,并开始处理。这是特别厚的渲染/训练。我的猜测是,我的值aabc_scale相当高(8),因此渲染“光线”必须走得相当远(也就是说,我想要渲染的东西更多)。我不得不切换到 480p,并将渲染 fps 从 30 降低到 10。这表明你选择的设置确实会影响你的渲染时间。经过 8 个小时的渲染,我做出了这个:

我在公寓外面做的一个 NeRF。

我认为尝试 3 仍然是我的最爱。我想我可以把第四次尝试做得更好。然而,当渲染时间如此之长时,迭代版本和尝试不同的渲染和训练设置是很困难的。现在甚至为渲染设置摄像机角度都很困难,导致程序对我来说变得迟钝。

尽管如此,多么惊人的输出,从短短一两分钟的视频,我有一个详细的,栩栩如生的 3D 模型!

利弊

我认为最令人印象深刻的是,给定 1-2 分钟的镜头,一个完全没有受过摄影测量(me)训练的人可以创建一个可行的 3D 模型。这需要一些技术知识,但是一旦你安装好了所有的东西,它就会变得非常光滑和简单。将视频转换成图像效果很好,有 Python 脚本可以做到这一点。一旦这些都做好了,输入到人工智能就顺利了。

然而,这很难指责 NVIDIA,但我觉得我应该提出来:这需要一个相当强大的 GPU。我的笔记本电脑里有一台 T500,它将它推到了绝对的极限。训练时间确实比宣传的 5 秒长,尝试在 1080 渲染会导致程序崩溃(我是在 135*74 附近动态渲染下来的)。现在,这仍然是一个巨大的进步,以前的 NeRF 实现需要几天时间。我不认为每个人都有 3090 用于这样的项目,所以有必要简单提一下。低性能使它很难使用程序,特别是当我试图“飞”着相机来设置我的渲染视频时。尽管如此,还是很难不对这个过程的输出留下深刻的印象。

我面临的另一个问题是寻找render.py(正如你可能猜到的,这对于渲染视频至关重要)。非常奇怪的是,它不在回购协议中,尽管在大多数广告文章和其他文件中被大量提及。我不得不把它从这里挖出来。

最后,我也希望能够提取这些作为一个.obj——也许这已经是可能的了?

一张狐狸的 gif 图——这不是我做的,是英伟达做的。干得好,嗯?https://github.com/NVlabs/instant-ngpgif

最后的想法和下一步

这让我想到的是 DALL-E ,生成图像的 AI。这已经变得难以置信的流行,部分是因为它是如此容易获得。它给了人们一个非常酷的例子,展示了人工智能模型可以做什么,以及它们的局限性。它已经进入了流行文化(或者至少在我的 Twitter 上占据了重要位置),人们制作自己的怪异 DALL-E 图像并分享它们。我可以想象这种技术也会发生类似的事情。一个可以让任何人上传视频并创建一个你可以与朋友分享的 3D 模型的网站的病毒潜力是巨大的。最终有人做出这个几乎是必然的。

就我个人而言,我期待着更多的尝试。我希望能够生成超级逼真的模型,然后将其转储到 AR/VR 中。从那里你可以在那个空间主持会议——那不是很有趣吗?因为你只需要手机上的摄像头,所以大部分需要的硬件已经在用户手中了。

总的来说,我真的很感动。能够在手机上拍摄 1 分钟的视频,并将其转化为一个你可以一步一步走过的模型,这太棒了。是的,渲染需要一段时间,安装有点困难,但是效果很好。经过几次尝试,我已经得到了非常酷的输出!我期待更多的尝试!

安德鲁·布朗斯

请在 Medium 上订阅我,以跟踪任何令人兴奋的未来文章!也可以在这里联系: LinkedIn | Twitter

参考

用人工智能产生艺术

原文:https://towardsdatascience.com/using-ai-to-generate-art-22e030497f73

应用机器学习

一个支持语音的艺术生成工具

作者图片

我已经阅读并应用了一些最新的人工智能研究,所以你不必这么做。在本文中,我将介绍最先进的文本到图像的生成模型,并应用它们来制作一个支持语音的艺术生成工具。

介绍

在过去的几年里,文本到图像生成领域出现了一些开创性的论文。这个领域正变得越来越受欢迎,新的方式来赚钱这些艺术品在线。

艺术生成模型在很大程度上建立在我以前的一些文章上,特别是生成方法,如变分自动编码器(VAEs)生成对抗网络(GANs)

作为一个小小的剧透,这里有一些这些惊人的模型可以制作的图像:

绿色景观中的酷炫建筑(图片由作者提供)

手绘草图(图片由作者提供)

日落、船和湖(图片由作者提供)

如你所见,结果是惊人的。但是在我使用这个应用程序之前,先讲一点理论。我将首先回顾 VAEs。然后我会谈到这些在 VQ-维斯和 VQ-甘斯是如何被改编的。最后,我将谈谈 OpenAI 的回形针,以及它如何改变了文本到艺术的游戏。

VAEs 概述

可变自动编码器是这些模型工作方式的一个组成部分。自动编码器通过训练两个神经网络来学习。第一个是编码器,第二个是解码器。

作者图片

编码器的任务是将输入图像(训练数据)的维度缩小到更低维度的潜在向量。解码器的工作是从潜在向量中尽可能好地重建图像。

标准自动编码器和 VAEs 之间的区别在于,VAEs 不是寻找潜在向量,而是通过例如寻找平均值和标准偏差来参数化潜在空间(最常见的是各向同性高斯)。通过将这个潜在空间拟合为高斯型,我们就能够非常便宜地对这个高斯型进行采样,并将采样提供给解码器。然后,解码器可以用于生成看起来像训练数据的新图像。

要点:自动编码器学习潜在向量和图像空间之间的映射。VAEs 学习潜在空间和图像空间之间的映射。

ELBO(图片由作者提供)

VAEs 试图最大化 ELBO(证据下限)。如果你想知道这是从哪里来的,看看这篇文章。第一项是重建误差,第二项是后验 q(z|x)和先验 p(z)之间的 KL 散度。

  • q(z|x)是后验概率,在给定输入数据 x 的情况下,看到潜在向量的概率
  • p(z)是先验分布,我之前说过通常是高斯分布

最小化第二项本质上是一种正则化模型的方法,试图总是使空间尽可能接近先验(例如高斯)。

VQ 值(向量量化值)

基于:神经离散表示学习,作者:亚伦·范·登·奥尔德、奥里奥尔·维尼亚尔斯、科拉伊·卡武克库奥卢。2017 年 11 月。[1]

今天,许多最先进的人工智能模型,如 DALL-E 和 VG-GANs,都是基于 VQ-瓦斯。正确理解这篇论文是最基本的。VQ-VAE 是由 Deep Mind 开发的一个模型,它提高了 VAE 生成图像的能力。

VAEs 和 VQ-VAEs 有两个主要区别。VQ-维斯学习它们的先验,并且编码器的输出是离散的(而不是学习潜在分布)。

VQ-维斯仍然在学习潜在空间和图像空间之间的映射,然而,他们将潜在空间离散化。

图像的离散输出背后的推理是,语言本质上是离散的,可以用一系列离散的符号来表达。图像可以通过语言来描述,因此解码器应该能够从离散空间重建图像。

图片来自神经离散表示学习论文【1】

VQ-维斯首先使用编码器将图像编码成一系列矢量。然后,使用“码本”将编码矢量映射到嵌入空间。码本是可学习的,它是图中所示的作为嵌入空间的一系列向量。人们可以把码本想象成字典,把离散化的潜在空间想象成我们的语言。一旦使用码本映射了模型的输出,我们基本上就有了输入图像的描述。由解码器获取该编码描述,并重建原始图像。

因此,来自我们的编码图像的每个向量被映射到来自码本的嵌入向量。实现的方式是通过选择码本中最接近每个编码矢量的矢量(距离是 L2 范数)。

一旦这些向量被映射到它们的离散化潜在空间,它们就被馈送到解码器,解码器有望精确地重建输入图像。

VQ-维斯损失[1]

以上是 VQ-维斯的损失项。将其与 VAE·爱尔博损失进行比较,可以看到我们有类似的重建误差。我们不再有 KL(因为统一的先验而消失),取而代之的是第二个和第三个项。在第二项中,我们有一个停止梯度,它冻结编码矢量(z)并帮助学习码本矢量(e)。第三项是相反的,它冻结码本向量,并帮助学习编码向量。

解码器仅使用第一项进行优化,编码器使用第一项和最后一项,嵌入(学习码本向量)仅使用中间项。

在整个训练过程中,先验保持一致,但是,在训练之后,它们使用 PixelCNN 拟合新的先验。

VQ-甘斯(矢量量化甘斯)

基于:Patrick Esser、Robin Rombach、bjrn Ommer 的《驯服高分辨率图像合成的变形金刚》。2020 年 12 月[2]

高分辨率合成图像由驯服高分辨率图像合成变压器【2】

VQ-甘斯是 VQ-维斯的改进。这篇论文是开创性的,因为它首次能够通过使用变压器生成连贯的高分辨率图像。他们能够通过应用基本假设来做到这一点,即图像中的低级结构可以通过图像中的局部连通性来描述,而这种方法不能用于图像的高级结构。

这些模型结合使用了 GANs 和 VAEs。作为对 GANs 的快速提醒,这些模型分两部分学习。第一个是生成图像的生成器,第二个是鉴别器,一个分类器,试图识别你给它的图像是真是假。这些模型可以一起学习,本质上是试图欺骗对方。一旦它们被训练好,你就用生成器来生成新的图像。

图像由驯服高分辨率图像合成变压器 [2]

上图展示了 VQ 甘斯的建筑。您可能已经意识到,底层模块是集成到该架构中的 VQ-瓦斯,因为它充当 GAN 的发生器。

VQ-甘斯本质上是对 VQ-VAE 的改进。两个主要区别是,他们没有使用 PixelCNN 作为模型来拟合先验,而是拟合了一个变压器(他们使用了 OpenAI 的 GPT-2)。第二个区别是他们改进了损失函数,这次使用了对抗性损失(使用 GAN 架构)。

你可能会注意到上图中鉴频器的输出是一个矩阵,而不是二进制输出。这是因为他们使用了补丁 GAN。这将获取图像的重叠碎片,并像普通鉴别器一样对每个碎片进行操作。

图像中的变换器像顺序语言模型一样被训练,其中变换器试图预测编码码本向量的序列。然后,变换器用于学习码本嵌入向量。

对比图像语言预训练

基于:从自然语言监督中学习可转移的视觉模型亚历克·拉德福德等人 2021 年 2 月。[3]

到目前为止,我们已经看到了生成图像的方法,但是我们如何将它与文本联系起来呢?OpenAI 的剪辑是一个模型,可以确定哪个标题与哪个图像最匹配。它不能生成图像,但它是图像生成和文本之间的连接。

图像由从自然语言监督中学习可转移的视觉模型 [3]

以上是 CLIP 模型的架构。图像中显示的模型有三个部分。

  1. 对比预培训

模型的输入是图像和描述文本对。文本和图像输入分别通过各自的编码器。一旦这样做了,你就可以构建上面矩阵所示的对比空间。训练的目标是将蓝色方块的余弦相似度推至 1,将所有其他方块的余弦相似度推至 0。通过这种方式,我们教导文本编码器和图像编码器将它们的输入编码到相似的空间。

2.从标签文本创建数据集分类器

对比预训练不足以确定文本和图像是否相同。我们需要进行某种分类。例如,假设我们有一只狗和一只猫的两张图片。这些图像可能彼此非常相似。如果我们拍摄了狗的图像,但是文本显示为猫,我们希望 CLIP 知道这个图像与所涉及的文本无关。

为了将该分类器功能应用于预训练的模型,他们首先需要建立分类器数据库。他们首先从标记的图像数据中构建样本句子。网上有很多带标签的图像数据集,如 CIFAR-10、ImageNet 等。这些图像被分配了类别,而不是句子。为了解决这个问题,他们将类添加到句子中。

例如,如果你有一张狗的图片,你可以给文本编码器一个句子,比如“一张狗的照片”。我们这样做是为了构造数据集中每个类的嵌入向量。

3.用于零炮预测

一旦我们有了学习过的嵌入向量,我们可以给图像编码器相应的图像对到文本,我们可以比较编码器的输出和我们的嵌入。余弦相似度最大的将是该类。

文本和图像编码器都可以通过这种方式进行改进,从而得到更好的结果。

VQ-甘+剪辑

因此,我们有 VQ-甘斯,它可以基于离散空间生成图像,我们有 CLIP,它可以评估图像与文本的匹配程度,反之亦然。

当文本输入到 VQ-甘-剪辑,VQ-甘首先用随机噪声初始化,并产生一个图像。然后,该图像和编码的码本向量可以被馈送到剪辑图像编码器,并根据它们与文本输入的相似性进行评估。然后可以相应地更新 VQ-甘的权重以反映文本输入。这可以递归地进行 N 次迭代,直到生成器的输出是文本输入的满意表示。

算法的收敛性(作者 GIF)

还有更先进的模型,如 OpenAI 的 DALL-E 和最近的 DALL-E2。(DALL-E 这个名字来源于 Dalí和 WALL-E 的混合,非常聪明)。这些模型产生了文本输入的极其复杂的表示。然而,它们不是公开的,所以我不能乱来,也不能在这里展示他们的作品。但是你可以通过这个链接查看其中的一些。

我自己的艺术生成应用

现在我已经看完了所有的理论和最新的模型,我想自己应用它们。之前我做了自己的智能助手 App 。在这里,我想修改它,让它能够使用人工智能生成艺术。你可以在我的 Github 上查看这个应用的一些代码。

这个想法是能够通过我的声音访问这些模型。我想正常使用智能助手,但如果我给模型一个“触发词”,它会使用句子的其余部分作为提示,生成一幅艺术作品。

应用架构(图片由作者提供)

为了将音频转录为文本,我使用了 AssemblyAI 的 Websocket api 来实时地将我的讲话转录为文本。为了执行模型,我使用了谷歌云的 VertexAI。

为了运行模型并生成艺术作品,我需要将模型作为作业在云上执行。我不能在我的本地机器上运行这些模型,因为它们真的需要 GPU。为了运行这些模型并生成图像,我首先通过设置一个“setup.py 文件”来安装所需的库。然后,我在可以访问虚拟 GPU 的虚拟机上执行作业。

最后要做的是配置智能助手,使其能够生成艺术。我通过简单地添加一个 if 语句来合并“触发词”,如果句子以“生成”开头,那么我将句子的其余部分传递给艺术生成器模型,作为 Vertex AI 的一项工作。工作完成后,我将图像保存到一个桶中,然后我可以访问并加载到应用程序中。

作者图片

在这里,我要求机器人“生成一幅城市天际线的画”。触发词是“生成”,然后应用程序获取句子的其余部分,并将其传递给作业请求。我发现这个触发词是可靠的,而且效果很好。

就这样,我现在有了一个智能助理应用程序,可以根据请求为我生成艺术作品。

作者图片

结论

在这篇文章中,我通过最新的人工智能研究论文的技术方面来从文本中生成图像。我回顾了基本的生成器方法架构,比如 VAEs(可变自动编码器)和 GANs(生成对抗网络)。然后我会谈到如何在矢量量化的研究论文中采用这些方法来提高它们生成图像的能力。总之,这些方法设法离散化状态空间,当与 CLIP(一种比较图像与文本相似性的方法)结合使用时,这些模型能够生成令人惊叹的艺术作品。

我特别兴奋地看到这一研究领域将如何在未来传授给数字艺术行业,如视频游戏或动画电影,因为我相信这项技术有可能彻底改变这些行业!

支持我

希望这对你有所帮助,如果你喜欢,可以 跟我来!

您也可以使用我的推荐链接成为 中级会员 ,并访问我的所有文章及更多:https://diegounzuetaruedas.medium.com/membership

你可能喜欢的其他文章

支持向量机

精度 vs 召回

额外收获:如何向 Vertex AI 启动作业

作为一点额外的补充,我想包括一些关于如何部署这些模型的信息,因为我遇到了很多困难,而且这并不像看起来那么简单。

这些模型运行在云中预先构建的映像上。在 GCP 上有很多,我用的是“Europe-docker . pkg . dev/vertex-ai/training/py torch-GPU . 1–10:latest”。

然而,为了让模型运行起来,我还需要一些镜像中没有的包。为了解决这个问题,我随自定义作业一起传递了一组包。

为了传递这些必须编译成 tar.gz 文件的软件包,我使用了“setup tools”Python 库。

然后,我必须将 Python 作业包装成一个模块。这是通过添加 init 来完成的。py 文件添加到 task.py 文件的位置,并稍微修改 python 脚本。

以下是我使用的自定义工作函数:

作业不能每次都是相同的 python 脚本。这是因为每次我请求不同的画时,标题都会改变。每当我启动一个作业时,我都需要将该信息传递给那个特定的作业。

我这样做的方式是通过旗帜。在执行 Python 文件时,可以通过命令行将标志作为选项传递。有了 absl-py 库,我能够阅读这些,并把它们传递给艺术生成模型。

下面是我发送给 VertexAI 的任务的 task.py 文件结构。

希望这有所帮助:)

参考

[1] A. van den Oord,O. Vinyals 和 K. Kavukcuoglu,“神经离散表征学习”(VQ-VAEs),康奈尔大学,2017 年。可用:【https://arxiv.org/abs/1711.00937】T2。

[2] P. Esser、R. Rombach 和 B. Ommer,“驯服高分辨率图像合成的变形金刚”(VQ-甘斯),康乃尔大学,2020 年。可用:https://arxiv.org/abs/2012.09841

[3] A .拉德福德等,“从自然语言监督中学习可转移视觉模型”(CLIP),康乃尔大学,2022。可用:https://arxiv.org/abs/2103.00020

利用人工智能打造更好的团队

原文:https://towardsdatascience.com/using-ai-to-make-better-teams-85084be0ecfb

使用 BERT 的简单演练

安德鲁·莫卡在 Unsplash 上拍摄的照片

“我精心决定了你们的搭配,这样你和你的伴侣的技能可以互补,”吉尔·赫尔姆斯博士说,他是我在斯坦福大学选修的一门写作课的教授。起初,我不相信这样的事情是可能的:即使她完全理解我们所有的技能,她怎么能优化每一个单独的配对呢?但后来我开始思考这个问题,并提出了一个极端过度设计,但有趣的解决方案。

如果你可以把每个人的技能,以及他们希望变得更好的东西,表示为空间中的向量,我们需要做的就是根据这些向量之间的相似性来匹配人们。信不信由你,使用几个标准的 python 包,这实际上并不太难。我们开始吧!

为了明确我们在做什么,我们将编写代码,要求一组学生给出他们的名字,一句关于他们技能的话,以及一句关于他们想学什么的话。然后,代码将为学生输出理想的匹配,这样学生就可以与拥有他们想要的技能的其他人相匹配。

组织项目

为了可读性和可重用性,我选择将函数分成两个文件(例如,如果我想将其作为 Flask API)。我们将处理数据检索的那个称为main.py,进行配对的那个称为get_pairings.py。我们还需要使用pip安装一些包来让代码工作。具体来说,我们将依赖三个主要包:

  • sentence-transformers提供最先进的预训练转换器架构,用于生成句子嵌入。通俗地说,这个包给了我们将句子转化为向量的功能。
  • sklearn是一个广泛有用的数据分析库。在这个项目中,我们将使用它来获得计算向量之间相似性的函数。
  • numpy是一个非常流行的框架,帮助 python 处理长数组数字。我们将用它来配对。

这些包都可以通过在终端中运行行pip install sentence-transformers sklearn numpy来安装。一旦我们完成了这些,我们就可以开始写代码了!

获取数据

我们的第一步是询问每个学生的名字和句子。就我们的目的而言,存储一组姓名、一组“技能集”句子和一组“所需技能集”句子效果很好。这将由main.py文件处理。为了让你的生活更容易,我将粘贴所有的代码在这里,并解释后。

首先,我们从get_pairings.py导入代码,我将在下面详细解释。然后,我们编写一个名为pair的函数,它使用 python 的内置input函数从命令行获取字符串形式的用户输入。我们最终遍历每个学生,建立上面描述的数组(我们称之为knowswantsnames)。然后,我们将这些数组传递给我们导入的run函数,以获取学生姓名列表。这个返回值中的每个内部列表都包含一对学生。然后我们拿着这个列表,把每一对都打印出来。我们现在已经有了程序的主要结构,剩下唯一要做的事情就是弄清楚如何进行配对!

配对

我们需要编写一个函数,接受上面收到的三个列表,并将它们转换为优化的配对。如果我们从头开始做这些,可能需要成百上千行代码。幸运的是,我们能够使用上面描述的包来构建其他人的代码。我希望你会发现结果相对简单。我的get_pairings.py文件的代码粘贴在这里:

我们有四个功能要分解。让我们从最后一个(也是最重要的)开始:run函数。它接受代表学生的三个数组,并返回包含最佳配对的列表列表。它首先加载一个模型(特别是 BERT,由 Google 的研究人员创建的最先进的 transformer 模型)。然后,它运行句子列表,通过模型进行分析,以接收“嵌入”,这是每个句子的功能向量表示。然后,它计算一个包含这些向量的余弦相似性的表。这可以被认为是每个向量之间的角度,如果你在空间中画出它们的话。

其他三个函数各自处理这个表,以便将这些相似之处转化为良好的配对。首先,表格被avg_scores函数“平均”。假设比利的技能与鲍勃想要的技能完全匹配,但比利想要的技能与鲍勃的实际技能有很大差异。这个平均函数将用两者的平均值替换这些相似性的相应表条目。因此,如果 Billy-desires 和 Bob-skill 的匹配是 1,但是 Bob-desires 和 Billy-skills 的相似性是 0,那么每个都将变成 0.5。

接下来,get_pairs函数成对读取表格。它通过查找表中的最高平均相似性得分,将它们存储到一个数组中,然后将该配对中每个人的所有相似性得分设置为负无穷大,这样该组的成员就不会被放入另一组。它一直这样做,直到找到所有可以配对的线对。最后,它获取这些对的索引,并将它们转换成由 names 数组给出的名称,并将结果返回给main.py或使用它的任何其他客户端进行处理。

结果

最终,工作函数应该能够做到这一点。

$ python3 main.py
Enter how many people you would like to pair off: 4
Enter the name of student 1: Daniel
Enter one or more sentences describing Daniel’s skillset: I like art.
Enter one or more sentences describing Daniel’s desired skillset: Something relating to water, boats, or swimming.
Enter the name of student 2: Gaby
Enter one or more sentences describing Gaby’s skillset: I know a lot about the ocean and am a diver.
Enter one or more sentences describing Gaby’s desired skillset: I would like to know about history.
Enter the name of student 3: Jade
Enter one or more sentences describing Jade’s skillset: Computer Science and math.
Enter one or more sentences describing Jade’s desired skillset: Economics is very cool to me.
Enter the name of student 4: Ali
Enter one or more sentences describing Ali’s skillset: I love studying the economy and monetary policy.
Enter one or more sentences describing Ali’s desired skillset: Quantitative fields like science and math.
Here are the pairings:
1: Jade and Ali
2: Daniel and Gaby

我喜欢这样的东西,因为它显示了将最先进的人工智能用于日常项目和用例是多么容易。我希望一些人可以从中吸取教训,开始将 AI 带给世界各地更多的人。

用 AI 做自己的智能助手 App

原文:https://towardsdatascience.com/using-ai-to-make-my-own-smart-assistant-app-5ad015449447

应用机器学习

使用 Python 和 GPT-3 制作的智能助理应用程序

图片由摄影盒中的 maxuser 拍摄

随着苹果的 Siri、亚马逊的 Alexa 和谷歌 Home 等产品的出现,购物助手变得越来越受欢迎。在本文中,我用 Python 制作了自己的智能助手。

介绍

智能助手允许你通过语音交互从网络上快速收集信息。在制作智能助手时,我想制作一个具有图形用户界面的 web 应用程序,在那里我可以使用我的语音与智能助手进行交互。在这篇文章中,我详细介绍了我是如何制作这个应用程序的,我使用了哪些工具和方法。用于这个小项目的许多工具可以用于许多其他项目。

方法

为了做智能助手,我用了 OpenAI 的 GPT-3。GPT-3 是由 OpenAI 训练的海量语言模型。它经过预先训练,能够很好地推广到各种各样的用例。

我的目标是制作一个应用程序,通过它我可以用我的声音与 GPT 3 互动。

该应用程序的整体流程图如下所示:

作者图片

为了将我的声音传递给 OpenAI,我首先需要将我的讲话传递给文本。所以首先我用 Streamlit 应用程序录下我的声音。然后,我对录音进行编码,并将其传递给 AssemblyAI 进行转录。我收到了抄本并把它传给了 OpenAI。然后,我可以将回复显示在应用程序上。

为了通过应用程序与这些服务通信,我使用了它们的 API。

什么是 API?

应用程序接口(API)是软件程序相互通信的方式。为了让它们进行交互,API 提供了一组协议和工具来帮助交互成功。

这两个服务都有 Python 中的 API,所以我可以轻松地将这两个服务集成到应用程序中。API 允许我访问由公司开发和托管的服务、工具和人工智能模型。我可以向他们发送一个请求,询问我希望他们的模型为我处理什么,然后我得到一个响应,其中包含他们预先训练好的模型的输出。

你如何使用 API 的?

为了实时转录音频,我使用了 AssemblyAI Websocket API。与 HTTPS 连接不同,Websocket 连接允许双向发送和接收来自 API 服务器的请求。这样我可以实时转录数据。

为了建立 WebSocket 连接,我使用了 Python 中的异步库“asyncio”和“websockets”。下面是代码的样子:

在上面的代码中,我连接到 WebSocket URL,并尝试接收连接确认(参见上面的 session_begins)。

一旦连接,我可以定义发送和接收函数,这将允许我以我想要的方式与 API 通信。

在 send 函数中,我首先收集我的音频,将其编码为 utf-8,然后使用 WebSocket 发送。当处理音频文件或 API 时,utf-8 编码是相当标准的。

在接收功能中,我首先从音频文件中接收转录的文本,我将该文本发送到 OpenAI 并接收来自 GPT-3 的响应。

使用 Streamlit 构建应用程序

Streamlit 可用于快速构建网络应用。该应用程序的目标是提供一个图形用户界面(GUI),以便用户可以在不使用任何代码的情况下与智能助理无缝交互。

我现在将详细说明我用来制作应用程序的代码结构。我总是把我的 Streamlit 应用包在一个类里。整个代码结构如下所示:

该应用程序包含两个按钮。第一个是记录按钮,第二个是清除聊天按钮。

录制按钮开始录制您的语音,然后转录并发送文本到 OpenAI。然后,响应会以聊天对话的形式显示在应用程序上。

该应用程序会记录下存储聊天内容的对话。txt 文件。当按下清除聊天按钮时。txt 文件被清除。

这个应用程序的最后一个我不能在这里展示的功能是文本到语音转换。当机器人回应时,它会读出句子。为此,我使用了 pyttsx3 库。

与工具交互

作者图片

上面是这个应用程序的截图。最上面是应用程序的两个按钮。要与智能助理互动,您可以按录音并说出您的信息。你的信息的副本显示在左边,GPT-3 的回应显示在右边。

这位聪明的助手显然名叫莎拉,今年 26 岁。这个应用程序似乎工作得很好,它可以检测我的声音,并以一种全面的方式显示出来。智能助理可以处理更复杂的查询:

作者图片

智能助手在处理基本几何图形或首字母缩略词时没有问题。这个机器人也知道现代公司的背景:

作者图片

结论

在这篇文章中,我介绍了我是如何制作智能助手应用程序的。该应用程序能够记录你的声音,转录它,将转录传递给 OpenAI 的 GPT-3,并为你读出回应。我介绍了该应用程序的管道是什么样子,以及该应用程序是如何在 streamlit 上编程的。最后,我回顾了一些我与智能助手的交互示例。总的来说,我认为这个应用程序很有用,效果很好。

支持我

希望这对你有所帮助,如果你喜欢,可以 跟我来!

您也可以成为 中级会员 使用我的推荐链接,访问我的所有文章以及更多:https://diegounzuetaruedas.medium.com/membership

你可能喜欢的其他文章

利用人工智能分析语音

AI 应用于掩膜检测

使用人工智能防止供应商发票欺诈

原文:https://towardsdatascience.com/using-ai-to-prevent-supplier-invoice-fraud-fed13f2aaab4

照片由 Unsplash 上的阿扎马特 E 拍摄

供应商发票欺诈是商业中的一个重要问题。每年都有数百万美元因供应商提交的不正确或无效发票而损失。这里概述的基于机器学习的方法能够在这些发票到期支付之前识别高达 97%的发票。

此处使用的示例基于一个私有的匿名数据库,该数据库包含一家服务公司五年内的约 9 万张发票,该公司反过来购买服务和商品进行转售。该公司已经允许在本文中使用发票数据库。

在开具发票期间,该公司一直存在一个问题,即供应商提交的发票金额夸大、金额错误,有时还会提交从未提供的货物/服务的发票。

经过仔细的人工分析后,许多发票被标记为欺诈性的,这些发票用于训练此处的模型。值得注意的是,这里的“欺诈”一词并不一定意味着故意欺诈,还包括无意的数据输入错误。

首先,我们将数据加载到 Pandas 数据框架中,并整理一些缺失的值:

在生成人工智能模型之前,需要大量的特征工程。探索的第一个项目是本福特定律的应用。这表明,在自然出现的数据集中,前导数字更可能是一个小数字,而不是一个大数字。

计算的基础是数字的对数正态分布,而不是数字本身。

本福特定律

基于此,每个发票金额的第一个数字的分布应该如下所示:

本福特定律下第一位数的分布

第一个数字大约有 30%的概率是 1,大约有 4%的概率是 9。对发票数据集的分析揭示了真实世界的分布情况:

第一位数的真实分布

我们可以立即看到,数字 9 的出现比我们预期的要高。此外,对于小数值小于 1 的发票,数字 0 也会出现。

为了在模型中利用本福特定律,我们将把发票金额的第一个数字分离到一个名为“first_digit”的新列中。

接下来,我们将为每张发票生成一个 z 分数。这是衡量一个值与其组的标准偏差的距离。在这种情况下,我们将按供应商对发票进行分组,并获得每张发票的标准差。

z 得分是减去组平均值的值,除以组标准偏差

这是使用下面的代码生成的:

有趣的是,据报道,在夏季的几个月里,欺诈的发生率较高。虽然对此没有明显的解释,但还是决定进一步调查。

为此,发票日期被转换为基于自 1 月 1 日以来的天数的新功能。这允许所有年份都被平等地处理。

因为 12 月 31 日紧挨着 1 月 1 日,所以需要将天数转换成一个循环值。这是通过生成天数除以一年 365 天的余弦值来实现的。绘制该值会给出下图,其中每个值都在-1 和+1 之间:

天数的正弦值与余弦值的对比表明了数据的循环性质

最后,我们将创建另外两个特征。同样,根据传闻信息,据报道,欺诈性发票通常是整数金额,没有小数,而且更常见的是描述也较短。

为了解决这种可能性,添加了另一个新特性来标记金额是否包含小数,以及发票描述的长度。

然后对包括新特征在内的数据集进行分析,以寻找相关性:

数据集中列的相关矩阵

从上面的关联矩阵中,如果我们查看与“欺诈”字段相关的行,我们可以观察到许多有趣的事情。

首先,与发票金额有很强的相关性。这在某种程度上是意料之中的,因为欺诈者不太可能开具低值发票。我们已经知道,数据集包含许多值小于 1 的发票,因此大额发票可能更经常是欺诈性的。

这似乎也与发票描述的长度有关。这表明欺诈性发票更有可能具有较短的描述。

支持本福特定律,我们可以看到与发票金额第一位数字的相关性。最后,我们可以观察到,zscore 还与发票是否存在欺诈行为相关联。

这里的下一步是选择要使用的最佳分类器类型。虽然我们有许多与欺诈与否密切相关的特征,但这些特征彼此之间没有很强的相关性。例如,发票描述的长度与发票金额的第一位数字具有 0.024 的相关性。

由于这些特征之间的相关性较弱,因此选择了随机森林分类器。这种分类器通过创建几个独立的决策树来工作,每个决策树输出自己的预测。然后将这些组合在一起,做出最终预测。

我们现在将把数据分成测试和训练数据集,并训练一个随机森林分类器。

经过训练后,该模型能够对测试数据集进行预测,准确率高达 97%(精确度 92%,召回率 78%)。

为了评估模型的整体质量,我们现在将生成一条 ROC 曲线。

这将生成下图:

训练后模型的 ROC 曲线

该模型本身能够实现超过 80%的准确性,而不会产生大量的假阳性。略高于 90%的准确度代表了产生少量假阳性的折衷。

总的来说,这种方法能够达到非常高的准确性,这取决于您愿意接受的假阳性的数量。

然而,这完全取决于源数据的性质,所有的公司都不尽相同。此处显示的许多因素都是该公司及其所在行业特有的,例如欺诈活动的季节性和发票金额的分布。要成功地将这种方法应用到另一家公司,对其业务的透彻理解将是极其有益的。

注:除特别注明外,所有图片均为作者所有。

在 PostgreSQL 中使用数组

原文:https://towardsdatascience.com/using-arrays-in-postgresql-450202a2b57f

学习在 SQL 中创建和操作列表和嵌套结构

卡斯帕·卡米尔·鲁宾在 Unsplash 上的照片

当您想到表的 SQL 图像时,选择查询和每个数据点一行的概念(没有嵌套结构)可能会浮现在脑海中。
但是如果你想存储一个数字、产品等的列表呢?一排以内?
你当然可以将列表分解成许多行,但是这可能会导致难以跟踪的行的混乱。幸运的是,PostgreSQL 提供了数组数据类型,可用于存储和操作列表。
下面是方法:

数组类型

PostgreSQL 允许将表中的列定义为可变长度的多维数组。可以创建任何内置或用户定义的基本类型、枚举类型、复合类型、范围类型或域的数组。

创建数组

数组可以采用多种形式,并且有多种方法来声明它们。

您可以在列的数据类型声明后使用关键字 ARRAY 来表示您想要创建一个声明前数据类型的数组,如下所示。

您也可以在数据类型声明后面加上方括号(例如 text[]),但是我发现这样不太明确,并且不符合关键字版本所遵循的 SQL 标准。

在下面的例子中,我创建了一个小表来模拟购物卡,并用多种产品的一些排列来填充它。如果您运行完整的查询,您还可以看到数组是如何表示为输出的。

创建和填充数组列

结果表

向表中添加数组的另一种方法是使用{ }-语法,而不是带括号的 Array 关键字。两种方法都有效,你只需要密切注意带花括号的引文。,我再次发现关键字语法更明确,括号更接近于其他编程语言中的数组表示,但两者都工作得很好。

取消阵列嵌套

但是等等!如果您实际上需要展开或取消嵌套特定行的数组,以便在某个产品信息表上连接或在列表项基础上执行其他操作,该怎么办?

没问题,PostgreSQL 中的数组可以很容易地用 UNNEST 关键字取消嵌套:

取消对数组列的嵌套

正如所料,UNNEST 在原始行的相应数组中为每个项目创建一行:

取消阵列嵌套的结果

访问数组项目

您可能会问自己,每次想要访问单个项以进行进一步操作时,是否必须取消数组嵌套。如果是这样的话,数组结构可能并不是一个好主意。幸运的是,PostgreSQL 中的数组允许通过括号和切片索引进行访问,如下所示。这允许容易地选择例如每个列表中的第一个项目或列表中特定范围的项目。

注意:PostgreSQL 数组中的索引从 1 开始,而不是从 0 开始,这可能与你在其他编程语言中所习惯的不同。

通过索引访问数组项

切片使用[start:end]-语法。

通过对具有两个以上数组元素的所有行进行切片来访问数组项。

如果你不确定你的数组有多少项,或者你想按照数组的大小进行过滤,就像我上面做的那样,你可以使用 CARDINALITY 关键字。这将返回数组中的整数个数。

按数组元素过滤

如果您想选择数组中有特定产品的所有购物车,该怎么办?或者任何其他基于数组项的筛选操作?没问题,PostgreSQL 允许将 WHERE 子句中的 item 值与 ANY 关键字结合使用。下面,我正在筛选产品数组列中包含“product_c”的行。这种过滤方式对单个过滤值最有用。

按数组中的项目筛选行

包含运算符

您还可以从上面扩展过滤器逻辑,并要求任何包含特定子数组的购物卡。在这里,我使用了代表“包含”的“@ >”操作符。可以这样理解:

"产品数组包含数组['产品 a ','产品 b']"

更新阵列

数组和数组值可以像其他数据类型一样进行更新,使用 UPDATE … SET …子句,如下所示。您可以通过索引更新单个数组项,也可以更新整个数组。

前置和追加

如果您希望通过追加或前置(在当前值之前插入)数组来专门改变数组,您可以相应地使用 ARRAY_APPEND 和 ARRAY_PREPEND 函数。确保注意到参数顺序的不同,每个函数的顺序不同,但遵循直觉顺序。

追加和前置数组

前两行通过追加和前置进行更改

移除数组项目

如果你想去掉一个特定的数组项,你可以使用 UPDATE…SET…和 ARRAY_REMOVE 函数。这允许您从所有行的数组中移除一个项,或者在与 WHERE 子句一起使用时仅移除一个特定项。

移除数组项目

串联数组

最后,您可以使用 ARRAY_CAT 将 PostgreSQL 数组连接成一个更大的数组,如下所示:

PostgreSQL 数组能做得更多吗?当然,去 PostgreSQL 文档中寻找更多的可能性。

现在,您已经拥有了在 SQL 表中使用列表和嵌套结构的所有工具。虽然这些强大的工具允许您打破一行一个记录的模式,但是您应该只在它们提供真正的价值并且不能被更传统的方法替代时才使用它们。现有的 SQL 最佳实践有很好的理由,所以您绕过它们的理由应该更充分。

希望你学到了东西,感谢阅读。

https://ms101196.medium.com/membership

利用地理数据使用贝叶斯统计预测咖啡馆的受欢迎程度

原文:https://towardsdatascience.com/using-bayesian-statistics-to-predict-cafes-popularity-with-geodata-3286deaffc2

用纯粹的统计数据进行预测

卡尼·贝内迪克托娃Unsplash 拍摄的照片

从某种意义上说,这篇文章是我的上一篇关于贝叶斯统计的文章的续篇。简而言之,我工作的公司需要一个模型,不仅预测一个目标变量作为一个数字,而且估计它的概率密度,即估计预测变量可能在预测的某个范围内。结果,基于朴素贝叶斯方法的模型被创建,它不仅适合最初的任务,而且设法胜过随机森林模型。

介绍

我提到的模型是稍微修改的朴素贝叶斯分类器。该模型返回后验分布,然后用于计算平均目标,而不是将点分类。模型本身就是为了在准确性和可解释性之间取得平衡。如果最终用户不清楚具体是如何做出决策的,他们通常会怀疑像随机森林这样的复杂模型。

但是这种方法有几个缺点。第一,最终分配不一定属于某一家。这意味着用户经常得到具有两个或更多局部最大值的曲线,平均值在它们之间。这可能表明有两个可能的预测,但是最后一个预测对应于一个不可能的结果。

平均值位于不太可能的点上的分布示例。(图片由作者提供)

第二个主要缺陷是缺乏准确性。在某些情况下,贝叶斯模型比更传统的解决方案表现更好,但通常情况下,它不是最准确的。当然,它的设计并不是非常精确,而是为了解决一个特定的任务。然而,仍然希望提高精确度。

最后,该模型相对较慢。对于最终用户来说,这通常不是问题,但是调整超参数和特性选择的阶段可能需要很长时间。

贝叶斯推理,共轭先验

有时,尝试估计分布的参数是值得的,而不是计算目标本身的分布。

设ρ(σ),ρ(μ)为目标标准偏差和平均值的分布。所以对于最终目标的分布,我们得到。

作者图片

这个公式有直观的意义,记住,要找到具有给定分布的随机变量 f(x)的某个函数的期望值,需要在整个函数范围内对所述函数和分布的乘积进行积分。在这个公式中,我们实际上是在寻找目标的期望分布。

现在让我们考虑一下我们放入模型中的特性。我们把它们命名为 f1…fn 吧。根据贝叶斯推断,这些特征是改变σ和μ的某些值的可能性的观察值。

作者图片

现在,我们需要将它放入第一个方程,以获得给定观测值的最终分布。但是不清楚σ和μ使用什么分布作为先验,以及在给定特征的情况下,这些变量应该使用什么分布作为可能性?

对于ρ(f|σ)和ρ(f|μ),取与ρ(x)相同族的分布是合理的。这可以从关于特定特征的信息等于样本来源于具有该特征的相同值的样本分布的信息的事实中得出结论。

关于σ和μ的先验,它们应该是什么并不明显。但有理由假设,通过将原始分布乘以它,应该可以得到来自同一个家庭的分布。换句话说,如果你的目标在考虑了一些特征后是正态分布的,那么如果在应用另一个特征后你得到的是伽玛分布,那就很奇怪了。

这些“成对的”分布称为共轭,以不转换似然性的方式选择的先验称为共轭先验。共轭先验被评估为所有最频繁出现的分布[1]。例如,对于正态分布,共轭先验是正态-逆伽玛。

此外,如果你选择你的先验是共轭的,你实际上不需要乘以函数。由于乘法不会改变族或您的分布,您可以使用已知的公式来计算初始分布的新超参数,这要快得多。

在大多数情况下,你甚至不需要对结果表达式进行积分。像共轭先验一样,最频繁出现的分布的最终结果是已知的,称为后验预测。例如,在正态分布的情况下,后验预测是学生的 t 分布。

预测真正的目标

要开始预测一个真正的目标,我们要做的第一件事是确定数据是如何分布的,以选择分布族。在我的具体案例中,目标是来咖啡馆的顾客数量。我们来看直方图(原始数据线性变换)。

作者图片

数据看起来服从对数正态分布,Kolmogorov-Smirnov 检验给出的 p 值为 0.12,这表明我们的假设离事实不远。这也允许我们对目标变量取对数,得到正态分布的数据。在这种情况下,共轭先验是正态伽玛分布,后验预测是学生的 t 分布。

对于要预测的点的每个特征,我们取训练集的子集,包含具有该特征的相同值的点。然后,所得子集用于更新后验分布的超参数。我们所需要做的就是把我们的观察结果代入文章[1]中给出的某个公式。

结果,我们得到了目标对数的模型估计。

作者图片

其中 2α是观测值的总数,t_α是具有α个自由度的学生 t 分布。

作者图片

正如你可能看到的,估计分布现在总是一个 log-t 分布,所以文章开头描述的情况,当我们得到多个驼峰时,将不再发生。

准确性评估

为了评估模型的准确性,我使用留一法从训练集中预测了 400 个点。首先,让我们检查一下旧模型,为新模型的比较建立一个基础。

作者图片

达到的最佳 Rsq 为 0.34。我们的团队也尝试了各种随机森林模型,但很难达到比 0.4 更好的效果。

现在让我们来看看新型号提供了什么

作者图片

现在我们正在谈话!所有指标都明显更好。预测现在看起来是离散的,这是由于我们进行了一些计算优化,以使模型更快。

让我们看看误差的直方图

在这张图片中,蓝色条是旧的错误,黄色条是新的错误

很明显,新模型中错误大于 1 的点数明显更少。

总之,可以肯定地说,即使使用了更先进的数学,它也能以更高的精度得到补偿。并且模型仍然是可解释的,这是这种统计方法的主要优点。

参考

[1]芬克,丹尼尔(1997)。“共轭先验概要”(PDF)。citeserx 10 . 1 . 1 . 157 . 5540

使用 BERT 进行论坛审核和过滤

原文:https://towardsdatascience.com/using-bert-for-forum-moderation-and-filtering-9913c915b78d

照片由来自 Pexels布雷特·乔丹拍摄

在线论坛仍然在联系人们方面发挥着重要作用,但人工审核是一个耗时的过程。NLP 能帮忙吗?

尽管社交媒体和微博在交流方面的增长不受限制,但在线论坛仍然在联系人们方面发挥着重要作用。这种说法的最大证据可能是 Reddit 的成功,这是一个受欢迎的网络收集器,每月有 4 亿多用户和超过 10 万个活跃社区。

这些社区需要版主来引导和过滤信息流,标记偏离主题/不合适的内容。但是适度是一项耗时的活动,需要版主的强烈承诺,版主通常是来自社区本身的志愿者。

那么我们如何帮助版主进行他们的版主活动呢?我们能使用 NLP 使他们的工作更容易吗?

声明:本文属于我的免代码 文章系列。无代码文章不包括任何对特定软件基础设施或技术实现的引用:没有代码片段,没有库,没有框架。这些内容的唯一目的是提供方法管道和启发实际用例场景,而不是提供现成的代码库来导出。
如果您在项目实施中需要帮助,请随时联系我

照片由 Pixabay像素上拍摄

任务

首先,让我们试着正式确定我们的目标。简单来说,让我们假设我们只是想帮助论坛版主检测已经发布在特定版块的离题消息,例如,一个专门为所有英雄联盟玩家的版块。这是一个二元主题分类任务,一个监督学习问题,其中我们试图预测一段文本或句子是否属于一个类别。因此,该模型将被训练来区分围绕感兴趣的主题的消息和不围绕感兴趣的主题的消息。

作者图片

数据

论坛转储是我们实现这个任务需要的数据。如上所述,这篇博文旨在尽可能保持一般性和可移植性,所以我们不会依赖任何特定的来源。如果你想继续使用 Reddit,你可以在这里找到前 2500 个子编辑的前 1000 篇帖子的开源转储。

例如,在我们的特定场景中,我们可能希望从感兴趣的子编辑“r/leagueoflegends”中收集样本,并从不同的子编辑(如“r/nba”或“r/funny”)中收集相同数量的随机样本,根据内容与“英雄联盟”主题的相关程度,将每条消息标记为“正题”或“离题”。换句话说,我们需要使用标记的数据

论坛转储通常不提供关于每条消息中讨论的主题的任何信息,所以我们不知道每条帖子的实际主题是什么。这是我们需要解决的问题,我们有两个选择:

  1. 让领域专家浏览整个语料库,手动将每条消息与一个主题相关联。这种人在回路的解决方案将是精英中的精英,但它非常耗时,而且当样本量很大时,几乎没有人负担得起。
  2. 假设 消息发布在更接近消息中讨论的主题的线程上。换句话说,我们假设线程=主题。这等于声明,如果在“蛋糕食谱”板上发布了一个消息,那么该消息的内容必然是一个蛋糕食谱。在这种情况下,我们可以在几秒钟内自动分配主题标签,无需任何人工参与。

嘈杂的标签

第二种选择显然是对现实的过度简化:事实上,讨论总是倾向于在一段时间后偏离主题,许多消息将只是对其他用户的简短回复(例如,“谢谢”、“是的,我同意”等)。)不涉及任何话题相关内容。

因此,自动标记引入了噪音标签,这是一种破坏模型性能的现象在本应是基本事实的地方引入了误导信息。根据特定的训练语料库,有噪声标签的影响可以忽略或不忽略(通常不是)。无论如何,请记住,我们是在用标签时间来换取准确性

我们可以通过将对话缩短到第一个 K 帖子来限制这种不良行为的程度,因为直觉告诉我们,讨论的负责人通常比其他人更关注这个话题。或者,如果论坛依赖于类似 Reddit 的向下投票/向上投票系统(如果我们的数据集中有这些信息),我们可以根据排名保留排名前 K 的帖子。

传统的 NLP 预处理(即停用词去除、词干提取等。)也可以应用,尽管在使用最新的上下文模型(如 BERT)时并不特别推荐(甚至停用词也提供某种上下文)。

来自 PixabayScribblingGeek 的照片

模型

一旦我们定义并运行了我们的预处理管道,我们应该最终得到一个干净的、经过过滤的语料库,准备好输入我们最喜欢的 NLP 模型:BERT。

变压器的双向编码器表示

BERT 和其他 Transformer 编码器架构在自然语言处理的各种任务上取得了巨大成功。他们计算自然语言的向量空间表示,其中每个组件可以被视为一个分类特征。BERT 系列模型使用 Transformer 编码器架构来处理前后所有单词的完整上下文中的每个单词,因此得名:Transformers 的双向编码器表示[ Tensorflow,2021 ]。

作者图片

BERT 模型通常在大型文本语料库上进行预训练,然后针对特定任务进行微调。这正是我们打算如何使用它,提供来自我们论坛帖子语料库的额外内容,以便通过重新加权其参数来反映论坛中使用的交流风格,从而改进我们的语言模型

讨论 BERT 的细节超出了这里的范围。你可以在这里找到对变形金刚家族清晰全面的解释。

BERT 有多种版本和变体,在层大小、注意头数量、输入序列长度、预训练目标类型等方面有所不同。像 BioBERT 这样的特定领域版本也已经发布。

许多成熟的类似 BERT 的模型都可以用 Huggingface 实现,这是 NLP 和 Transformers 的最大社区之一。我个人的建议是坚持使用 Delvin 等人最初的基于 BERT 的版本,然后如果你寻求性能提升或更快的计算,尝试不同的风格。

如何使用 BERT 进行消息分类

那么如何利用 BERT 来检测离题帖呢?使用 BERT 的二进制分类依赖于【CLS】令牌嵌入的输出向量,其可以被视为整个 post 的浓缩向量表示。如果我们将一个线性层和一个sigmoid****activation堆叠在编码器堆的顶部,则输出将是一个介于 0 和 1 之间的概率,可用于确定相应的帖子是正题还是离题。

作者图片

这就是用 BERT 处理二进制文本分类的一般方法。您还可以将 BERT 单独用于嵌入提取目的,然后使用这样的嵌入作为不同分类器的输入,就像正则化逻辑回归一样。

我们可以和伯特一起处理的其他论坛相关任务

BERT 是最通用的 NLP 模型之一。例如,如果我们用 linear+softmax 层替换模型的头部,我们也可以解决多分类问题,使用分类交叉熵将在管道末端获得的概率与原始标签进行比较。以这种方式训练的模型将能够为每个新消息分配最合适的子线程/主题。

结论

最近提出的基于注意力的方法表明,许多 NLP 问题的显著改进可以通过微调变压器模型容易地获得,该变压器模型先前被训练用于通用语料库的语言建模,以工作于特定的下游任务。利用迁移学习,有可能改进通用模型,以最少的努力和适量的数据执行小范围的操作,使我们更接近手动和时间密集型流程(如论坛审核)的自动化。在这篇博文中,我们应用这些概念设计了一个基于 BERT 的论坛审核助手,这是当今最流行和最通用的变形金刚模型之一。

如果你觉得有帮助,请在评论区留下你的想法并分享!如果你喜欢我做的事情,你现在可以给我多几个小时的自主权来表示你的支持🍺

https://www.linkedin.com/in/tbuonocore/

使用自行车共享数据查找您所在城市最受欢迎的自行车路线

原文:https://towardsdatascience.com/using-bike-share-data-to-find-the-most-popular-bike-routes-in-your-city-e8e65d22a43a

将数值和空间分析与 Python 相结合

图片由 Flickr 上的 bikesharedude 拍摄

德克萨斯州奥斯汀长期以来一直被称为自行车友好城市,但在过去 10 年里,自行车或踏板车共享大幅增长。这座城市的微型交通页面声称有超过 5000 辆小型摩托车和超过 500 辆地铁自行车:八年前一辆都没有!

这些地铁自行车很酷的一点是,你可以收集每次乘坐的数据,包括距离、持续时间、费用等,更有趣的是,在这种情况下:开始和停止位置。这个来自 Kaggle 的数据集包含了 2013 年至 2017 年间约 65 万次乘坐的数据,虽然我可以用它做很多有趣的事情,但我问的第一个问题是“哪条路线最受欢迎?”。

在此之前,这是对这 650,000 人次的快速分析(猜猜哪个月是西南偏南):

作者图片

作者图片

奥斯汀地区总共有 72 个地铁自行车站,为了完成一次骑行,你需要从其中一个站租一辆自行车,然后归还给另一个站。以下是 2013 年至 2017 年期间每个站点的使用频率:

作者图片

以下是我如何用 Python 制作这些图表的:

然后,我切换到 Tableau,在空间上可视化车站:

作者图片

当你用总乘坐次数来衡量每一点时,它看起来像这样:

作者图片

有 72 个车站,你可以选择大量的路线,但是正如你从第二张图中看到的,大多数人倾向于呆在市中心附近。至于路线,虽然有许多独特的路线可供选择:

作者图片

但我想看看哪条路线出现得最频繁,哪条路线最受欢迎。在我公布结果之前,我不得不说,从技术上来说,这些并不是 10 条最常去的路线——许多前 10 条最常去的路线都是客户在同一个车站开始和停止他们的旅程:在大约 650,000 次旅行中,98491 次属于这一类别,所以我跳过了这些,只包括开始和结束位置不同的路线。

以下是符合这些标准的 10 大路线:

#10:从第五街和鲍伊街到第四街地铁站

作者提供的谷歌地图截图

  • 距离:1.1 英里
  • 距离:7 分钟
  • 乘坐次数:1512 次
  • 著名景点:浅滩溪

#9:多雨的圣卡明斯到拉瓦卡&第二

作者提供的谷歌地图截图

  • 距离:1.0 英里
  • 时长:7 分钟
  • 乘坐次数:1587 次
  • 著名景点:安和罗伊自行车道

#8:共和广场到第五街&鲍伊街

作者提供的谷歌地图截图

  • 距离:0.6 英里
  • 时长:3 分钟
  • 乘坐次数:1664 次
  • 著名景点:西霍尔姆区

我肯定你已经注意到了,大多数都在市区,我能说什么呢?我们有很多游客。此外,由于某个受欢迎的景点,你将看到第五&鲍伊重复更多。

#7:拉瓦卡&第二到第五&鲍伊

作者提供的谷歌地图截图

  • 距离:0.7 英里
  • 时长:3 分钟
  • 乘坐次数:1672 次
  • 著名景点:威利·尼尔森雕像

#6:南方国会和学院到第二届国会

作者提供的谷歌地图截图

  • 距离:0.8 英里
  • 时长:5 分钟
  • 乘坐次数:1761 次
  • 著名景点:国会大道桥

这是我最喜欢的一个,因为你可以穿过这座美丽的桥

#5:从第四街地铁站到 Lavaca &第二街地铁站

作者提供的谷歌地图截图

  • 距离:0.4 英里
  • 时长:3 分钟
  • 乘坐次数:1794 次
  • 著名景点:乔的咖啡

#4 第三大道&西至拉瓦卡第二大道

作者提供的谷歌地图截图

  • 距离:0.4 英里
  • 时长:2 分钟
  • 乘坐次数:1889 次
  • 著名的网站:公共图书馆

#3 第五&鲍伊到拉瓦卡&第二

作者提供的谷歌地图截图

  • 距离:0.7 英里
  • 时长:4 分钟
  • 乘坐次数:2124 次
  • 著名景点:虽然有点偏僻,但在拉瓦卡和第三街有一家星巴克

#2 第四届国会至第五届鲍伊

作者提供的谷歌地图截图

  • 距离:0.7 英里
  • 持续时间 4 分钟
  • 乘坐次数:2259 次
  • 值得注意的景点:CVS 有时会提供 1 美元 2 杯的亚利桑那冰茶

最后,你们期待已久的时刻到了:德克萨斯州奥斯汀最受欢迎的自行车骑行:

第一第五&鲍伊到第四&国会

作者提供的谷歌地图截图

  • 距离:0.7 英里
  • 时长:4 分钟
  • 乘坐次数:2435 次
  • 值得注意的景点:亚利桑那州的 1 美元 2 个的交易在夏天非常好,去看看简历吧

如果你和我一样,你可能会对这些结果有点失望。所有这些路线都很短,而且主要集中在市中心,这是有道理的,但我想看看是否有更好、更受欢迎的路线。

荣誉奖:西河畔&南拉马尔到奥斯汀湖大道

作者提供的谷歌地图截图

  • 距离:3.3 英里
  • 时长:19 分钟
  • 乘坐次数:98 次
  • 著名景点:莫扎特的咖啡烘烤器

对我来说,这是一个拥有一切的地方:不仅骑自行车会花你 15 分钟以上的时间,而且它会带你穿过奥斯汀一些风景优美的地方。从安和罗伊自行车道到可以看到整个天际线的人行桥,最后在莫扎特餐厅的湖边结束,在那里你可以犒劳自己一杯咖啡!此外,如果你最终完成了这次骑行,你有可能成为第 100 名骑手!这没有奖励,但会很酷。

我希望你喜欢这个项目演练!通过一个独特的数据集来查看我居住的城市很有趣,如果你好奇的话,我强烈建议为你的城市做这样的事情。有很多很棒的数据集,这只是你可以用它们做的有趣的事情之一。

非常感谢你阅读这篇文章,如果你喜欢它,请考虑通过媒体跟随我——我真的很喜欢写关于技术、营销和所有数据科学的东西。

另外,如果你正在考虑注册一个中型账户,请考虑使用这个链接来支持我:https://bench-5.medium.com/membership

Md Sanaul KarimKaggle公共领域上提供的数据集

使用缓存加速 Python 代码

原文:https://towardsdatascience.com/using-caching-to-speed-up-your-python-code-871e9c08aefd

在 Python 中实现各种缓存策略

照片由维里·伊万诺娃Unsplash 上拍摄

在上一篇文章中,我谈到了如何找出我的 Python 代码的瓶颈。其中一个要点是分析程序并找到需要很长时间的代码段。作为后续,在本文中,我想介绍一种流行的架构模式来打破瓶颈并加速您的 Python 代码——缓存。除了讲述理论,我还将解释 Python 中的一些内置缓存解决方案,以及如何使用 Python 来实现流行的缓存策略(如 LRU、LFU 等)。

什么是缓存?

缓存是一个通用术语,用于将一些数据临时存储在一个比从源(数据库、文件系统、服务等)读取数据更快的地方。存储层缓存要么是源的子集,要么是一些繁重计算的预计算结果。缓存中的数据通常存储在快速访问硬件中,如 RAM 和内存引擎。因此,缓存允许您提高系统的整体吞吐量,因为单位时间内可以处理更多的读取请求。

缓存(由创建)

我们什么时候需要缓存?

当涉及到性能问题时,我们通常会将问题归结为 CPU 限制或 I/O 限制。缓存在这两种情况下都很有用。

存储先前计算的数据

加速 CPU 受限程序的一个例子是缓存先前计算的结果以避免重复计算。我们可以用递归的方法用它来解决斐波那契问题。这是递归树。

斐波那契递归树(由创建)

基本算法要求您编写一个递归函数,根据需要多次调用自己。以这个图为例,计算f5,函数要调用自己 15 次,其中f1节点 5 次,f0节点 3 次,f2节点 3 次,等等。所需时间呈指数增长,因为该函数反复计算许多相同的子问题。

一种使算法更有效的技术叫做记忆化。记忆通过将先前计算的结果存储在高速缓存中来加速算法。因此,该函数只需要查找节点的结果,而无需再次运行计算。如果你对斐波那契问题感兴趣,可以用 Real Python 查看这篇文章。

https://realpython.com/fibonacci-sequence-python/#generating-the-fibonacci-sequence-recursively-in-python

存储最常请求的数据

许多存储层,如数据库和文件系统,由于它们的低 IOPS(每秒输入/输出操作数),可能是对程序整体性能影响最大的因素。为了加快 I/O 绑定程序的速度,缓存在原始存储位置(数据库、文件系统等)前面创建了一个存储层,以便更快地进行数据检索。缓存可以通过不同的技术层来应用:

  • 操作系统—从较低层读取数据比从较高层读取数据要慢。操作系统中的缓存是为了确保最频繁请求的数据位于最高级别。
  • 数据库——大多数数据库,如 MySQL 都采用内部缓存来保存内存中的热数据或昂贵查询的结果。如果数据已经在缓存中,它就响应数据库请求。这通过降低数据库引擎上的资源利用率极大地提高了性能。尽管它完全由数据库管理,但理解这些机制有助于调试性能问题。
  • 应用程序—开发人员可以在由活动驱动的应用程序层中实现自定义缓存解决方案。如果您对数据库中内置的缓存解决方案不满意,您可以根据需要在您的数据库前面再设置一个缓存,用于存储 数据及其到期日期。
  • Web 缓存—缓存在 web 应用程序中广泛使用。在服务器端和客户端都可以采用各种缓存策略。服务器通常使用 web 代理来保留 web 响应。客户端(例如浏览器)通常缓存图像、HTML 页面、HTTP 会话等 web 资产,以便在所有 web 服务器和设备上提供更好的用户体验。
  • CDN(内容交付网络)—当您的 web 流量分散在不同的地理位置时,将整个基础架构复制到每个新位置的成本很高。CDN 能够将视频和网页等网络内容的缓存副本提供给各地的客户。

有哪些缓存策略

大多数缓存被设计成键值对,其中缓存键是缓存中每个对象的唯一标识符。当请求生成匹配的缓存密钥,并且对象将作为响应返回时,就会发生缓存命中。实现缓存时,我们应该了解一些常见的缓存特征和策略:

缓存是为读密集型应用而设计的

现在应该很清楚,缓存是一种快速读取的解决方案。它主要有益于进行大量读取操作的程序。为达到最佳性能,读取操作应具有时间复杂性 O(1) 。如果您熟悉基本的数据结构,您应该知道哈希映射——这是 Python 中的字典。字典中给定关键字的值可能被最新的设置操作覆盖,该操作的时间复杂度也是 O(1)。

cache = {}
cache["user1"] = "res1"

然而,这种实现方式存在一些问题。在最坏的情况下(虽然非常罕见),密钥冲突问题将导致读取操作的 O(n)时间复杂度。另一个问题是,您不能将所有的都放在内存缓存中,因为它最终会爆炸内存,所以您应该决定缓存应该存储什么。

根据内容的创建时间存储内容

控制缓存大小的一种方法是拥有一种自动清理机制。将新的键值对插入缓存时,会为该键分配一个 TTL(生存时间)值。它是一个整数值,指定密钥到期前的秒数。换句话说,该对将在 TTL 秒后被删除,以保持缓存在可管理的大小。它避免了用太多过时的数据来扰乱缓存。

根据对象的时间敏感性选择合适的 TTL。一般来说,对于不常变化的数据或客户端对陈旧数据更宽容的数据,会设置较高的 TTL。对于对时间敏感的数据,如股票市场和天气,通过强制缓存更频繁地重新验证内容,可以设置较低的 TTL。

要用 TTL 创建缓存,我们可以安装软件包cachetools。在本例中,缓存存储从 OpenWeatherMap 中检索到的天气数据,每 10 分钟刷新一次。

TTL 缓存示例

在后台,第一个函数调用 API 并将响应存储在缓存中,TTL 设置为 10 分钟。因此,第二个函数花费的时间要少得多,因为它直接从缓存中检索数据。第三个函数是在密钥已经过期时执行的,所以它再次调用 API 并刷新缓存中的阿姆斯特丹天气数据。

TTL 广泛应用于 CDNs 环境中。它控制网站资源的刷新率,理想地确保客户端不会看到太陈旧的内容,同时提高页面加载速度。TTL 也是一种 DSN 服务器设置,它告诉缓存在从名称服务器再次搜索之前存储 DNS 记录的时间。

根据访问模式存储内容——LRU&LFU

除了时间之外,决定何时从缓存中清除对象的另一个重要因素是它的访问模式。当缓存已满时,使用两种常见策略:

  • LRU(最近最少使用)—首先丢弃最近最少使用的项目。它按照使用顺序对项目进行排序。每次访问一个对象时,算法都会将它移动到缓存的顶部。这样,算法可以快速识别哪个项目最长时间没有被访问。
  • LFU(租赁常用)-首先丢弃最不常用的项目。它不是删除最近最少使用的数据,而是驱逐最少使用的数据。它可能会丢弃最近使用过但不像其他数据那样经常使用的数据。

例如,如果您实现了一个缓存来存储浏览器历史,并且您对最近访问过的页面更感兴趣,那么 LRU 缓存就更有用。另一方面,如果你使用缓存来存储浏览器的书签,那么 LFU 更有用,因为用户可以快速打开他们最喜欢的页面。

Python 为 LRU 缓存提供了内置库。高速缓存最多保存最近的maxsize次呼叫。在本例中,高速缓存的大小为 1。通过查看日志,前 3 个函数实际上调用了 API ,因为它们都请求来自前一个函数的不同数据。只有最后一个函数利用了缓存,因为它与前一个函数检索相同的对象。

lru 缓存的示例

接下来,让我们看看 LRU 缓存的实现,以理解为什么它是如此受欢迎的缓存策略。作为一个缓存,它必须是时间高效的,即所有操作的时间复杂度应为 O(1)。

哈希映射是一种存储键值对的良好数据结构,具有较低的时间复杂度。在 LRU 缓存中,我们需要另一种数据结构来维护的使用顺序。整个过程包括以下步骤:

  • 在哈希表中查找项目。哈希映射存储对象及其使用顺序。
  • 将对象移动到行首,其他对象应该相应地向后移动。值得注意的是,这个机动的时间复杂度应该是 O(1)。您可以在这里暂停一下,想想哪种数据结构最合适。
  • 如果该项不在哈希表中,它将被添加到行首。如果缓存已满,我们需要驱逐最近最少使用的项——行尾。时间复杂度也被认为是 O(1)。

哪个数据结构在你的脑海里?数组?LinkedList?数组的一个问题是将一个项目移动或添加到数组的前面需要 O(n)时间。然而,一个单链表只需要 O(1)时间。但是删除尾节点需要 O(n)时间,因为它需要遍历整个 LinkedList。

所以最好的解决方案是使用双向链表。

LRU 缓存示例(由创建)

哈希映射存储相应的链表节点。将一个节点移动到列表的头部的时间复杂度是 O(1)。因为它是一个双向链表,删除尾部节点也需要 O(1)时间。

这里有一种用 Python 实现 LRU 缓存的方法。

Lru 缓存实现

LFU 的工作方式与 LRU 缓存类似,但它根据数据的使用频率来驱逐数据。如果有多个项目具有相同的频率,它将驱逐最近最少使用的项目。与 LRU 缓存相比,它需要额外的哈希映射来存储频率信息。关键是频率,值是具有该频率的项目列表,并按访问时间排序。在实现中,我也使用了双向链表。但是这部分可以用 Python 中的 OrderedDict 来代替,它是使用相同的数据结构实现的。

Lfu 缓存(由创建)

Lfu cahce 实施

根据内容的创建时间和访问模式存储内容

LRU/LFU 缓存的一个问题是,如果数据被反复请求,它将永远不会从源获取更新。为了解决这个问题,我们可以添加 TTL 值。如果请求试图访问过期数据,该函数将从源获取更新并刷新缓存。我们可以通过在@lru_cache之上创建一个装饰器来实现这一点。

在最后一个函数中,虽然缓存还没有满,但是函数仍然调用 API,因为数据已经过期。当时间和访问模式都相关时,这种策略很有用。例如,我想设计一个缓存来存储 Twitter 用户最近的推文。LFU 缓存确保只有受欢迎的用户的推文存储在缓存中,TTL 值确保缓存中不包含太陈旧的推文。

如何将缓存集成到系统中?

到目前为止,我们已经讨论了缓存的内部设计和回收策略。最终,我们需要将缓存集成到应用程序中。根据应用程序的类型,可以应用一些模式。

通读

通读(由创作)

这是最常用的模式。收到请求时,应用程序会尝试在缓存中查找匹配项。如果找到了数据,它会立即返回数据。否则,它会到底层数据库获取数据,并将其加载到缓存中。

在这种模式中,由于频繁的缓存未命中,初始请求的延迟会很高。

写通

直写(由创作)

直写与填充缓存的顺序相反。每次写入都是先写入缓存,然后再写入源。这意味着缓存总是与源同步。应用程序从不需要从数据源读取数据。

因为缓存与源同步,所以很可能可以在缓存中找到数据。从而导致更好的性能。然而,缺点是包括不经常请求的数据在内的所有数据也存储在高速缓存中,导致高速缓存庞大而昂贵。缓存和源之间的同步是一个同步过程,可能会导致高延迟。

使用直写和通读模式是很常见的。例如,数据首先被写入缓存,并设置一个 TTL 值以保持其相关性和精简性。如果再次请求已删除的数据,它可以使用通读模式从源中检索回来。

写在后面

写在后面(由创作)

这类似于直写模式。应用程序首先将数据写入缓存。但之后,应用程序会立即返回到主进程。另一个进程将定期运行,以同步缓存和源之间的数据。与直写不同,缓存和源之间的备份是一个异步过程。

如果我们不想承担每次写入源的高延迟成本,并且同步过程足够可靠,这种方法会更有效。但是,这种模式需要从缓存到源的可靠备份过程,以确保数据不会丢失。

提前刷新

提前刷新(由创作)

在这种模式中,缓存被配置为在数据过期之前自动和异步地从数据源重新加载数据。因此,应用程序将总是从缓存中检索数据。万一出现罕见的缓存未命中,它将从源执行同步读取并刷新缓存。

提前刷新对于大量读取的应用程序很有用。值在高速缓存中保持新鲜,并且避免了从源过度重载的问题。

通常,预刷新和通读是为只读应用程序设计的。提前刷新在低延迟的情况下更有效,但是它需要缓存中的一种机制来决定何时从源加载什么。通读很容易实现,但是我们需要忍受初始请求中的高延迟,因为大多数请求将直接到达源。

对于读/写应用程序,直写和后写都很有用。同样,直写相对容易实施,但由于缓存和源之间的同步备份,会导致高延迟。而后写具有低延迟,但需要确保在将缓存转储到源时不会丢失任何东西。

结论

在本文中,我们讨论了一些关于缓存的理论,包括什么是缓存,何时需要缓存,以及不同的缓存策略和模式。我们还看到了 Python 中 TTL 缓存、LRU 缓存和 LFU 缓存的实现。希望你觉得有用。干杯!

参考

https://aws.amazon.com/caching/ https://kislayverma.com/software-architecture/architecture-patterns-caching-part-1/

使用因果 ML 代替 A/B 测试

原文:https://towardsdatascience.com/using-causal-ml-instead-of-a-b-testing-eeb1067d7fc0

在复杂的环境中,因果 ML 是一个强大的工具,因为它比 A/B 测试更灵活,并且不需要强有力的假设

[图片由作者提供]

反事实问题是商业中最重要的话题之一。我经常听到公司问这种问题。

“我们做了这个动作。之后,平均用户支出为 100 美元。但是如果我们不采取行动,我们怎么知道他们会花多少钱?”

这些问题通常通过 A/B 测试来解决。然而,A/B 测试有几个要求,其中之一是不能同时运行太多的测试。

但是真正的组织是极其混乱的,不同的过程持续不断地进行着。所以,通常不可能假设,当你运行一个测试时,其他一切都保持不变。

这就是为什么在这篇文章中,我将通过一个不同的工具来解决反事实问题,即因果机器学习。因果 ML 的好处是它更加灵活,最重要的是,当您对所有业务流程几乎没有或完全没有控制时,可以使用它(对于数据科学家来说经常是这样)。

框定问题

我们举个简单的例子。假设我们有 4 个用户。我们想知道给他们折扣是否会导致他们在我们的平台上花更多的钱。

这就像要求观察两个平行的宇宙

在一个宇宙中,所有的用户都得到折扣。在另一个世界里,没有一个用户得到折扣。请注意,在我们给(或不给)用户折扣之前,两个世界是完全一样的。

想象一下我们可以实际观察两个宇宙。在下图中,我们可以看到我们的实验结果:

在多元宇宙中测试一个营销行为:在一个宇宙中,所有用户都有折扣。在另一个世界里,他们都没有折扣。[图片由作者提供]

在第一个世界中,用户总共花费了 120 美元,而在第二个世界中,他们总共花费了 100 美元。因此,我们可以得出结论,折扣的效果是使用户平均多花 5 美元(即 120 美元减去 100 美元除以 4 个用户)。

太好了,折扣有效!

从平行宇宙到 A/B 测试

不幸的是,在实践中,我们没有特权同时观察不同的宇宙。所以,我们必须找到不同的方法。这种方式由 A/B 测试提供。

A/B 测试是在我们的单一宇宙中产生不同宇宙的聪明绝招。在 A/B 测试中,我们将用户分成两组:一组称为治疗组,另一组称为对照组。然后我们只给治疗组打折。

假设两组足够“相似”,两组互不影响,这就很像观察两个不同的宇宙。

A/B 测试,也就是在一个宇宙中测试一个营销行为。[图片由作者提供]

因此,我们所要做的就是将治疗组中的平均用户支出(30 美元)与对照组中的平均用户支出(25 美元)进行比较,以得出折扣具有使用户平均多支出 5 美元(30 美元减去 25 美元)的效果的结论。

那么,A/B 测试有什么问题呢?

没什么,A/B 测试工作正常。

但是A/B 测试的要求之一是不要同时运行太多的测试,因为它们会“污染”彼此的结果

真实的组织非常混乱,真实的过程通常不尊重 A/B 测试所要求的假设。

我在现实公司中看到的情况是,不同的团队对相同的用户做出不同的营销动作。然后,他们让你(数据科学家)评估他们所做的营销活动的有效性。

所以你分析数据,你会发现:

  1. 一些团队忘记留出一个控制组;
  2. 一些团队保留了一个与治疗组不太相似的对照组;
  3. 一些团队保留了一个太小的控制组;
  4. 不同的团队向相同的用户发送了相互冲突的营销活动(例如保留活动和追加销售活动)。
  5. 一些团队向属于另一个团队的控制组的用户发送营销活动。

怎样才能处理好这种乱七八糟的过程,却保持扎实的统计基础?

因果 ML 来拯救

因果推理提供了一个非常适合我们情况的框架。

我们有一组协变量,这意味着一组关于我们用户的信息。营销团队根据这些信息做出决策。这些决定采取营销行动的形式(又称治疗)。例如,他们可能决定向过去 3 个月没有购买的用户发送折扣。

协变量和处理的组合产生结果,这是我们感兴趣的 KPI。在这种情况下,这可能是用户的消费。

让我们用图表概括一下这个过程:

因果图。[图片由作者提供]

为了使事情更具体一点,让我们想象一个小数据集。数据集将由 3 个表组成,每个表代表一个因果要素。协变量可以在用户表中找到。治疗或行动可以在活动表中找到。结果可以在购买表中找到。

玩具数据集。[图片由作者提供]

我们可以在时间轴上以图形方式显示每个用户的旅程:

每个用户的客户之旅。[图片由作者提供]

让我们带上爱丽丝。她在 9 月 15 日成为 A 阵营的目标,在 10 月 4 日成为 B 阵营的目标。然后她在 10 月 18 日买了一件东西(她花了 75 美元)。

现在,我们如何知道购买是由于活动 A,活动 B,还是两者的结合?但最重要的是,如果我们不给她发送任何广告,我们怎么知道她是否会购买呢?

为了解决这个问题,我们需要做一个机器学习中常用的简化。我们需要选择一个时间点(姑且称之为分界点),并假设在那个时刻之前发生的一切(行动)导致了在那个时刻之后发生的一切(结果)。

预测模型的时间维度。[图片由作者提供]

既然我们同意了这一点,我们就必须决定两个时期应该有多长。这在很大程度上取决于业务,并且可以进行调整(例如,根据离截止点的距离对事件进行不同的加权)。

为了简单起见,我们假设行动期(图中的黄色带)和结果期(蓝色带)都必须是一个月。

通过这种方法,我们可以回到我们的数据。我们知道活动 A 是在 9 月 15 日发出的。因此,我们可以把这一天作为所有用户的截止点。

黄色背景:观察动作。蓝色背景:观察结果。[图片由作者提供]

请注意,当我们观察结果(蓝色背景)时,我们会简单地忽略这些行为。在这种情况下,最重要的是结果。

我们可以重复这一过程,将活动 B 的发送日期作为截止点。在这种情况下,我们将拥有以下内容:

黄色背景:观察动作。蓝色背景:观察结果。[图片由作者提供]

将协变量、治疗和结果放在一起,我们以下列形式重新排列我们的初始数据:

数据准备后的数据集。[图片由作者提供]

所以现在我们有了一组由协变量和治疗组成的独立变量(矩阵 X )和一个包含结果的目标变量(向量 y )。

有了这两种成分,我们现在能够训练任何机器学习模型。

注意模型不仅可以学习处理和目标变量之间的关系(如在 A/B 测试中发生的那样),还可以学习处理和协变量之间的关系。例如,它可能了解到法国用户对活动 A 更敏感,而德国用户对活动 b 更敏感。

到目前为止,我们一直把这个问题作为一个典型的机器学习问题来处理,但是你可能会反对我们在这种情况下没有任何东西可以预测。那么,我们当初为什么要用 ML 呢?

工作中的因果 ML

我们最初的目的是回答如下问题:

如果我们不向任何用户发送活动,会发生什么情况?如果我们将活动发送给所有用户,会发生什么?

因果 ML 允许我们回答反事实的问题。或者——如果你想用更高调的话——因果 ML 允许模拟不同的宇宙。

例如,假设我们想要模拟两个场景:

  • 宇宙 A:如果我们不向任何用户发送活动,会发生什么?
  • 宇宙 B:如果我们把活动发送给所有用户,会发生什么?

模拟这些情况意味着改变预测矩阵中治疗变量的值。我想强调这一点,因为它非常重要:我们只能改变治疗变量,不能改变协变量

从图形上看,

使用因果 ML 模拟不同的场景。[图片由作者提供]

你看到这种方法有多灵活了吗?我们可以在我们感兴趣的任何用户子集上模拟几乎任何场景。

此时,您可能会有一个问题:我们如何对训练模型的相同数据进行预测?不就是机器学习的第一个吗?

将数据集拆分成多个部分并为每个数据集训练不同的模型就足够了,就像交叉验证一样:

import pandas as pd
from sklearn.model_selection import KFold
from lightgbm import LGBMRegressor

# initialize the number of folds and a dictionary to store the folds
n_folds = 5
folds = {fold: dict() for fold in range(n_folds)}

# for each fold...
for fold, (ix_train, ix_test) in enumerate(KFold(n_splits=n_folds).split(X=X)):

  # ... store test index and trained model
  folds[fold]["ix_test"] = ix_test
  folds[fold]["model"] = LGBMRegressor().fit(
    X=X.loc[ix_train, :], 
    y=y.loc[ix_train]
  )

现在我们有了一个为每个褶皱进行样本外训练的模型。在这一点上,我们可以使用这些模型来预测在两个宇宙中会发生什么:

# make counterfactual datasets for campaign A
X_zeros = X.replace({"campaign_A": {1: 0}}) # universe A: nobody gets the discount
X_ones = X.replace({"campaign_A": {0: 1}}) # universe B: everybody gets the discount

pred_zeros = pd.Series(index = X.index)
pred_ones = pd.Series(index = X.index)

# for each fold, use model to make predictions on test individuals and store them
for fold in folds.keys():

  ix_test = folds[fold]["ix_test"]
  model = folds[fold]["model"]

  pred_zeros.loc[ix_test] = model.predict(X_zeros.loc[ix_test, :])
  pred_ones.loc[ix_test] = model.predict(X_ones.loc[ix_test, :])

pred_ones回答问题“如果我们向所有用户发送活动 A,而其他一切保持不变,会发生什么?”

pred_zeros回答问题“如果我们不将活动 A 发送给任何用户,其他一切保持不变,会发生什么?”

现在我们已经有了pred_zerospred_ones ,我们可以计算出关于活动 A 的几乎所有我们想要的东西:特定亚组中的平均治疗效果,中值治疗效果,或者我们可能感兴趣的任何其他度量。

例如,平均治疗效果可计算如下:

ate = (pred_ones - pred_zeros).mean()

请注意,我们在上面看到的相同逻辑可以应用于二进制响应变量(例如,用户是否购买,是或否?).在这种情况下,你应该记得校准模型,以确保你能够预测实际的概率。

总结

真实的组织是复杂的,许多不同的过程不断地涉及到用户。在这种情况下,不一定满足 A/B 测试所依赖的要求。

在这种情况下,因果 ML 可以用来回答反事实问题。因果 ML 有许多优点:它是灵活的,它是不可知的,它允许模拟几乎任何场景。

感谢您的阅读!我希望你喜欢这篇文章。如果你愿意, 在 Linkedin 上加我

使用 CLIP 对无标签图像进行分类

原文:https://towardsdatascience.com/using-clip-to-classify-images-without-any-labels-b255bb7205de

如何在不观察单个标签的情况下在 ImageNet 上实现 76.2%的测试准确度

对比语言图像预训练(剪辑)(由作者创建)

深度图像分类模型通常以监督的方式在大型注释数据集上训练。虽然模型的性能会随着更多的注释数据变得可用而提高,但是用于监督学习的大规模数据集通常很难获得并且成本高昂,需要专家注释者花费大量时间。考虑到这一点,人们可能开始怀疑是否存在更廉价的监管来源。简而言之,有没有可能从已经公开的数据中学习高质量的图像分类模型?

OpenAI 提出的对比语言-图像预训练(CLIP)模型[1]——最近因其在 DALLE-2 模型中的使用而重新流行起来——以一种积极的方式回答了这个问题。特别是,CLIP 提出了一个简单的预训练任务——选择哪个字幕与哪个图像匹配——允许深度神经网络仅从自然语言(即图像字幕)中学习高质量的图像表示。因为图像-文本对容易在线获得并且通常容易获得,所以人们可以容易地为 CLIP 策划大的预训练数据集,从而最小化训练深度网络所需的标注成本和努力。

除了学习丰富的图像表示,CLIP 还通过在 ImageNet 上实现 76.2%的测试准确度而无需观察单个标签,彻底改变了零镜头图像分类——与之前最先进的零镜头学习框架的 11.5%的测试准确度相比,这是一个重大改进[2]。通过建立自然语言作为图像感知任务的可行训练信号,CLIP 改变了监督学习范式,使神经网络大大减少了对注释数据的依赖。在这篇文章中,我将概述 CLIP 的细节,如何使用它来最小化对传统的一次性监督数据的依赖,以及它对深度学习实践者的影响。

剪辑之前是什么?

在了解 CLIP 的细节之前,了解模型的上下文是有帮助的。在本节中,我将概述相关的前期工作,并提供关于 CLIP 的灵感和发展的直觉。也就是说,初步的工作证明了自然语言是图像感知的一个有用的监督来源。但是,由于这些方法相对于替代方法表现不佳(例如,监督培训、监督不力等)。),在 CLIP 提出之前,通过自然语言的训练仍然不常见。

以前的工作

用 CNN 预测图像字幕。先前的工作表明,预测图像字幕允许 CNN 开发有用的图像表示[3]。这种分类是通过将每个图像的标题、描述和标签元数据转换成一个单词袋向量来执行的,该向量然后可以用作多标签分类任务的目标。有趣的是,以这种方式学习的特征显示出与通过 ImageNet 上的预训练获得的特征的质量相匹配,从而证明图像标题提供了关于每个图像的足够信息来学习区别性表示。

后来的工作扩展了这种方法,以预测与每个图像相关的短语[2],使零射击转移到其他分类数据集。尽管这种方法产生了差的零镜头性能(即,在 ImageNet 上 11.5%的测试准确度),但它表明仅使用自然语言就可以产生远远超过随机性能的零镜头图像分类结果,从而充当弱监督零镜头分类的概念的初步证明。

用变形金刚从文字中形象再现。同时,包括 VirTex [4]、ICMLM [5]和 ConVIRT [6]在内的几部作品探索了使用变压器架构从文本数据中学习视觉特征。在高层次上,这种方法使用变压器的通用训练任务来从相关联的图像标题中学习有用的图像表示。作为这种工作的结果,掩蔽语言建模 (MLM)、语言建模对比学习目标——通常用于训练自然语言处理领域中的转换器——被发现是用于学习高质量图像表示的有用的代理任务。

为未来的发现铺平了道路

虽然以前没有方法在大规模数据集上实现令人印象深刻的零射击性能,但这些基础工作提供了有用的教训。也就是说,先前的工作表明 i) 自然语言是计算机视觉的有效监督源,并且 ii) 通过自然语言监督的零镜头分类是可能的。由于这些发现,进一步的研究工作投入到执行监督来源较弱的零射击分类。这些努力产生了突破性的方法,如 CLIP,它将自然语言监督从罕见的零镜头图像分类方法转变为杰出的方法。

深入剪辑

CLIP 架构和培训方法的可视化概述(由作者创建)

简而言之,上图中总结的剪辑模型旨在从相关的图像标题中学习图像中的视觉概念。在本节中,我将概述 CLIP 体系结构、其训练以及如何将生成的模型应用于零镜头分类。

模型架构

CLIP 由两个编码器模块组成,分别用于编码文本和图像数据。对于图像编码器,研究了许多不同的模型架构,包括不同大小的五个 resnet[7](即,使用 EfficientNet-style [8]模型缩放规则确定模型尺寸)和三个视觉转换器架构【9】。图像编码器的这两个选项如下所示。然而,CLIP 的 vision transformer 变体的训练计算效率提高了 3 倍,使其成为首选的图像编码器架构。

剪辑中图像编码器架构的不同选项(由作者创建)

CLIP 中的文本编码器只是一个仅用于解码器的转换器,这意味着在每一层中都使用了屏蔽自我关注(与双向自我关注相反)。被屏蔽的自我关注确保了变压器对序列中每个标记的表示仅依赖于它之前的标记,从而防止任何标记“展望未来”以更好地告知其表示。下面提供了文本编码器架构的基本描述。然而,应该注意的是,这种架构非常类似于大多数先前提出的语言建模架构(例如, GPT-2OPT )。

剪辑的文本编码器架构(由作者创建)

尽管 CLIP 没有应用于原始出版物中的任何语言建模应用程序,但作者利用掩蔽的自我关注使 CLIP 在未来更容易扩展到此类应用程序。

通过自然语言监督进行训练

尽管先前的工作已经表明自然语言是计算机视觉的可行的训练信号,但是用于在成对的图像和文本上训练剪辑的确切的训练任务并不明显。我们应该根据图片标题中的文字对图片进行分类吗?嗯,以前的工作已经尝试过这种方法,效果很好,但不是很好[2,3]。使用语言建模为每张图片生成标题怎么样?有趣的是,作者发现预测准确的图像标题太难了,导致模型学习非常缓慢,因为任何图像都有多种不同的描述方式。

CLIP 的理想预训练任务应该是可扩展的,这意味着它允许模型从自然语言监督中有效地学习有用的表示。借鉴对比表征学习中的相关工作,作者发现 CLIP 可以通过一个令人惊讶的简单任务来有效训练——在一组候选字幕中预测正确的关联字幕。下图说明了这样一项任务。

剪辑的图文对比预训练(来源

实际上,这一目标是通过以下方式实现的:

  • 通过它们各自的编码器传递一组图像和文本标题
  • 最大化真实图像字幕对的图像和文本嵌入之间的余弦相似度
  • 最小化所有其他图像-字幕对之间的余弦相似性

这种目标被称为多类 N 对(或 InfoNCE)损失[10],通常应用于对比和度量学习中的问题。作为该预训练过程的结果,CLIP 形成了用于图像和文本的联合嵌入空间,使得对应于相似概念的图像和字幕具有相似的嵌入。

更好的任务=更快的学习。通过用这个更简单的代理任务训练 CLIP 模型,作者观察到训练效率提高了 4 倍;如下图所示。

CLIP 因其对比目标而提高了培训效率(来源

这里,使用 ImageNet 上的零射击学习迁移率来测量培训效率。换句话说,当使用这个简单的目标时,剪辑模型花费更少的训练时间(就观察到的图像-文本示例的数量而言)来实现在 ImageNet 上产生高零拍摄准确度的模型。因此,培训目标的正确选择对模型的效率和性能有重大影响。

没有训练样本,我们怎么分类图像?

CLIP 执行分类功能最初看起来似乎是个谜。鉴于它只从非结构化的文本描述中学习,怎么可能推广到图像分类中看不见的物体类别?

训练 CLIP 来预测图像和文本片段是否配对在一起。有趣的是,这种能力可以重新用于执行零射击分类。具体而言,通过利用对看不见的类的文本描述(例如,类名),可以通过将文本和图像传递通过它们各自的编码器并比较所得的嵌入来评估每个候选类;请参见下面的视觉描述。

使用剪辑()执行零镜头分类

将这一过程形式化,零炮分类实际上由以下步骤组成:

  • 计算机图像特征嵌入
  • 根据相关文本(即类名/描述)计算每个类的嵌入
  • 计算图像类嵌入对的余弦相似度
  • 对所有相似性进行归一化,以形成类别概率分布

这种方法具有局限性-类的名称可能缺乏揭示其含义的相关上下文(即多义性问题),一些数据集可能完全缺乏类的元数据或文本描述,并且在用于训练的图像-文本对中具有图像的单个单词描述相对不常见。这些问题可以通过制作“提示”来以文本形式表示不同的类或创建多个零命中率分类器的集合来缓解;见下图。

当(I)提示用于生成类嵌入,以及(ii)零触发分类器的集合用于预测()时,CLIP 实现了改进的性能

然而,这种方法仍然具有基本的局限性,必须最终解决这些局限性,以提高零射击学习能力。

实践中的剪辑—无需训练数据的精确分类!

在最初的出版物中,剪辑是在零炮域中进行评估的,并增加了微调(即,少炮或全监督域)。在这里,我将概述这些 CLIP 实验的主要发现,并提供有关 CLIP 何时可以和不可以用于解决给定分类问题的相关细节。

零距离拍摄。在零炮领域,CLIP 取得了突破性的成果,将 ImageNet 上最先进的零炮测试准确率从 11.5%提升至 76.2%;见下文。

零炮剪辑精度与之前最先进水平的比较(来源

当将 CLIP 的零触发性能与使用预训练 ResNet50 特征作为输入的全监督线性分类器的性能进行比较时,CLIP 继续在各种数据集上取得显著结果。也就是说,CLIP 优于线性分类器(线性分类器是完全监督的!)在总共 27 个被研究的数据集的 16 个上,尽管从未观察到一个训练样本。

用预训练的 ResNet50 特征作为输入的剪辑与线性分类器(来源)

当分析每个数据集的性能时,很明显 CLIP 在一般对象分类数据集(例如 ImageNet 或 CIFAR10/100)上表现良好,在动作识别数据集上表现更好。直观地说,在这些任务上的良好表现是由于 CLIP 在训练期间接受的监督范围广,以及图像标题通常以动词为中心的事实,因此与 ImageNet 等数据集内使用的以名词为中心的类相比,它与动作识别标签更相似。有趣的是,CLIP 在复杂和专业的数据集上表现最差,如卫星图像分类和肿瘤检测。

少射。还将 CLIP 的零镜头和少镜头性能与其他少镜头线性分类器进行了比较。在观察每个类中的四个训练示例后,发现零镜头剪辑与少镜头线性分类器的平均性能相匹配。此外,当允许观察训练样本本身时,CLIP 优于所有少数镜头线性分类器。下图总结了这些结果。

与少量镜头线性分类器相比,剪辑零镜头和少量镜头的性能(来源)

当使用 CLIP 特征训练完全监督的线性分类器时,发现它在准确性和计算成本方面都优于许多基线,因此强调了通过自然语言监督由 CLIP 学习的表示的质量;见下文。

使用 CLIP 的全监督线性分类器性能(来源)

尽管 CLIP 的性能并不完美(例如,它在专门的任务上苦苦挣扎,并且只对每个类都有良好的文本描述的数据集有效),但 CLIP 实现的零镜头和少镜头结果预示着高质量的图像和文本联合嵌入空间带来的可能性。更多是可能的,但是 CLIP 为这种通用的分类方法提供了一个初步的(令人印象深刻的)概念证明。

影响和收获

剪辑贡献摘要(由作者创建)

毫无疑问,CLIP 彻底改变了零镜头图像分类的领域。虽然之前的语言建模工作已经表明,非结构化的输出空间(例如,像 GPT 3[11]这样的文本到文本语言模型)可以用于零镜头分类目的,但 CLIP 通过 i) 形成适用于计算机视觉的方法和 ii) 将整个训练过程建立在易于获得的图像文本描述的基础上,扩展了这些结果。上图概述了 CLIP 及其贡献。

CLIP 坚定地认为,自然语言为学习高质量的感知特征提供了足够的训练信号。这一发现对深度学习研究的未来方向具有重大影响。特别地,图像的自然语言描述比遵循特定任务本体的图像注释(即,用于分类的传统的一次性标签)更容易获得。因此,为剪辑风格的分类器标注训练数据更具可扩展性,特别是由于许多图像-文本配对可以免费在线下载的事实

CLIP 的主要局限性源于这样的事实,即 i) 很难获得分类问题中每个类别的良好文本嵌入,并且 ii) 复杂/特定的任务(例如,肿瘤检测或预测图像中对象的深度)很难通过通用自然语言监督来学习。尽管如此,通过 CLIP 学习的表征是高质量的,并且通过探索对预训练过程中观察到的数据的修改,可以在更专业的任务上提高性能。

使用夹子。如果你对利用 CLIP 产生的高质量图像-文本嵌入感兴趣,OpenAI 已经为该模型发布了一个 python 包。在这个包中,下载不同版本的 CLIP(即,使用 vision transformer 或 ResNet 风格的图像编码器,并具有不同的模型大小)是直观的,并在 PyTorch 中有效地实现。只需使用pip下载软件包,并检查/下载可用的预训练模型,如下所示。

如何在 CLIP python 包中检查和下载预训练模型(由作者创建)

结论

非常感谢你阅读这篇文章!我希望你发现它是有帮助和有见地的。如果你对这篇文章有任何反馈,请随时发表评论或通过 LinkedInTwitter 与我联系。这篇文章也可以在我的个人博客上看到。为了跟上我未来的博客文章和其他作品,你可以在这里注册接收电子邮件通知,或者访问我的个人网页。这篇文章是我在 Alegion 做研究科学家时研究和学习的一部分,Alegion 是一个数据注释平台,具有业界领先的视频和计算机视觉注释功能。

参考书目

[1]拉德福德、亚历克等,“从自然语言监督中学习可转移的视觉模型。”机器学习国际会议。PMLR,2021 年。

[2]李,昂,等.“从 web 数据中学习可视 n 元图”IEEE 计算机视觉国际会议论文集。2017.

[3] Joulin,Armand 等人,“从大量弱监督数据中学习视觉特征”欧洲计算机视觉会议。施普林格,查姆,2016。

[4]德赛、卡兰和贾斯廷·约翰逊。" Virtex:从文本注释中学习视觉表现."IEEE/CVF 计算机视觉和模式识别会议论文集。2021.

[5]萨里伊尔迪兹、梅尔特·比伦特、朱利安·佩雷兹和黛安·拉勒斯。"学习带有标题注释的视觉表现."欧洲计算机视觉会议。施普林格,查姆,2020。

[6]张,,等.“成对图像与文本的医学视觉表征的对比学习”arXiv 预印本 arXiv:2010.00747 (2020)。

[7]何,,等.“用于图像识别的深度残差学习”IEEE 计算机视觉和模式识别会议论文集。2016.

[8]谭、明星和郭乐。"效率网:重新思考卷积神经网络的模型缩放."机器学习国际会议。PMLR,2019。

[9] Dosovitskiy,Alexey 等,“一幅图像相当于 16x16 个字:大规模图像识别的变形金刚。”arXiv 预印本 arXiv:2010.11929 (2020)。

[10] Sohn,Kihyuk。“具有多类 n 对损失目标的改进深度度量学习。”神经信息处理系统进展 29 (2016)。

[11]布朗、汤姆等人,“语言模型是一次性学习者。”神经信息处理系统进展33(2020):1877–1901。

使用计算能力更快地迭代 ML 实验

原文:https://towardsdatascience.com/using-compute-power-to-iterate-faster-through-ml-experiments-94c72342adaf

在机器学习环境中使用 Python 模板进行引导实验并提高生产率

(图片由 Unsplash 提供)

我已经创建了一个 Python 模板,它使得在 AWS 上运行 ML 实验变得很方便,所以你也可以为你的项目利用大规模云计算。这缩短了实验周期时间,允许您批量进行多个实验,并提高整体生产率。

动机

等待脚本终止是我最讨厌的事情。这不仅是浪费时间,而且不断的上下文切换会让人分心和疲惫。一个严重的违规者是 ML 实验,它会持续几分钟到几天。作为开发生命周期的一部分,ML 实验可以在任何时候进行,只要你想在一组新的超参数上评估你的模型(如在超参数优化中),探索新的特性(特性选择),或者尝试一个新的模型架构。在某种程度上,特性和模型架构只是超参数的美化形式。ML 实验总是可以被认为是调整一组受限的二元超参数,以最大化您的评估指标。

当然,你可能有纪律去读毕晓普第一次你的 10 分钟实验运行。但是在第十次迭代之后,有可能实验疲劳会出现,你会在 reddit 上离开。有没有一种方法,如果不能缩短等待时间,至少可以将多个实验一起批量进行?答案是肯定的。

释放 ML 计算能力的主要因素是并行化。有趣的是,在云中,无论是 24 小时租用一台服务器,还是 1 小时租用 24 台服务器,您支付的费用都是一样的。换句话说,通过将您的工作负载划分为 24 个块,并在商用机器上运行它们,您将以同样的价格获得快 24 倍的结果!所有这些工作都是因为超参数调整——我们已经看到的 ML 实验的同义词——在构造上是令人尴尬的并行。

入门指南

让我们把这些想法放在一起,直接进入使用波士顿数据集预测房地产价格的问题。为了简单起见,实验将包括调整一些 XGBoost 参数,如 max_depth ( 参见 XGBoost 文档),以最大化我们的模型的性能,如 MSE 所测量的。

  1. 先决条件
  • Cookiecutter :用 $~ pip install cookiecutter安装 Cookiecutter
  • AWS 区域:这个可以在 AWS 控制台 的右上角找到,比如 eu-central-1。
  • 你必须创建一个账户,并复制在这里找到的 wandb 密钥https://wandb.ai/authorize
  • 模板:最后,用下面的命令下载模板:

下载 cookiecutter 模板。(图片由作者提供)

2.添加您自己的代码

  • src/demo.py 该文件包含参数化实验的代码以及顶部的输入参数。在我们的例子中,它在波士顿数据集上评估 XGBoost 模型。
  • wandb/sweep . YAML****YAML 文件定义了将要探索的值的范围。在这次演示中,我们决定对 180 个不同的超参数组合进行网格搜索。除了网格搜索,还有一些其他策略,比如贝叶斯优化,你可以在这里探索

3.执行您的应用

$~ cd ml-exp/Initialize AWS resources (done only once)
$~ ./cli.py initBuild and push the Docker application to AWS
$~ ./cli.py dockerPull the trigger
$~ ./cli.py run 20

此时,您已经在 AWS 上初始化了必要的资源,并将应用程序构建到 Docker 容器中,该容器运行在 20 个不同的服务器上。

ECS 仪表板:并行运行的 20 个实验。(图片由作者提供)

瞧!结果将开始慢慢进入你的 wandb 仪表板。你将能够可视化你的结果,推断出正确的结论,并准备下一轮实验。

显示实验结果的 Wandb 平行图。(图片由作者提供)

一眼看去,模型深度越大,正则化程度越高,波士顿数据集的 MSE 就越小。我们也许应该为下一轮尝试更高的 max_depth 和 reg_lambda 值。(图片由作者提供)

在引擎盖下,wandb 负责协调实验。它将超参数注入到您的容器群中,这些容器并行运行且相互独立。AWS 容器对超参数进行评估后,wandb 会检索结果,并将所有内容汇总到一个并行绘图仪表板中。很酷吗?

Don't forget to tear down your AWS resources when you're done
$~ ./cli.py clean

结论

总之,我们已经看到了如何创建一个简单的实验,用 yaml 配置文件对其进行参数化,并快速评估其在 AWS 容器上的潜力。我们通过大规模并行化使实验更快,通过批量处理多个参数使实验更简单,让像我这样的单任务者非常高兴,从而两全其美。

如果你碰巧需要更多的火力,我会推荐你看看成熟的闪电-hydra-template 回购,它有更多的功能。

你可以在这里找到我的模板的源代码或者回去开始在云中运行你自己的应用程序。

非常感谢你的阅读。我期待您的宝贵反馈!

将数据用于小型企业的选址

原文:https://towardsdatascience.com/using-data-for-site-selection-as-a-small-business-4d6eca5ea1ac

不仅仅是拥有数据科学团队的大企业可以从数据中受益

桑德·克兰巴赫在 Unsplash 上拍摄的照片

本文所表达的观点是我基于公开数据和工具得出的个人观点。

在过去的几年里,我有幸与几个面临类似问题的小企业进行了交谈:他们如何利用数据来帮助发展业务并找到下一个地点?是缺乏时间和可用数据——而不是缺乏愿望——阻碍了这些企业在增长决策中使用数据。

毫无疑问,选址是复杂和不确定的。无论是否使用数据,都不能保证新的位置会成功。除了帮助你更有效地利用时间之外,数据还能帮助你对这些决定产生信念和信心。它可以确认成功的合适环境已经到位。

数据可以在三个主要领域支持中小型企业的选址流程:

  1. 缩小漏斗 —从较大的地区(“东北部”)到较小的地区(“这些[邮政编码,城市]”)
  2. 了解&区分优先顺序 —根据对您的业务至关重要的因素对您剩余的地理位置进行排序
  3. 确认位置—了解周围区域

选址首先要了解什么对您的业务至关重要。如果不了解成功必须具备的人口或经济因素,选址就像任何事情一样都是猜测。需要人口多吗?收入高?某些年龄段的高出现率?你依赖上班族的步行交通吗?某些业务类型的存在是否重要(餐馆、医疗机构)?

这一步对于启动流程至关重要。我们无法在不知道缩小时使用哪个标准的情况下缩小漏斗。客户研究、市场专业知识和经验以及竞争对手位置分析都有助于为您的业务提供重要的标准。

米洛·邦尼克在 Unsplash 上拍摄的照片

假设我们想在美国东南部沿海地区开一家新的拳击馆。不是“拳击场训练”健身房,而是有沉重的拳击袋和教练的集体训练和个人训练课程。想起来一个的标题拳

考虑到这种业务类型,什么对我们来说可能是重要的?我想到的几个项目是:

  • 人口规模和收入:这种类型的健身房通常是一种溢价产品,例如,与 Planet Fitness 相比。我们需要足够的人口规模来产生一个客户群,以及足够的收入水平,以至于每月 100 美元以上的会员都不会犹豫。
  • 住宅&商业人口:有人上班前健身,有人白天健身,有人下班后健身。靠近住宅和商业中心的位置将我们的可用市场从居住在该地区的人扩大到居住在该地区并通勤上班的人。
  • 竞争:这里的竞争既有临近的,也有直接的竞争对手。消费者必须选择我们,而不是传统的健身房体验和其他以拳击为重点的锻炼。

除了上面的头脑风暴,我们还可以“逆向工程”我们认为符合我们位置特征的企业的位置特征。例如,看看下面从弗吉尼亚海岸到佛罗里达的 26 个拳击比赛地点的“典型邮政编码”。

东南部(弗吉尼亚州至佛罗里达州)拳击比赛场地的典型邮政编码概况;作者图片

东南部人口按行业划分的劳动力标题拳击邮政编码;作者图片

除了较高的人口规模、人口增长和经济活动(健身房、餐馆、零售店等的定制设计指标)之外,东南沿海的拳击场地往往有较高的家庭收入(与全国基准相比+20%)。在邮政编码中)。与全国平均水平相比,居民中从事白领工作的比例也较高(+8 个百分点。).

缩小漏斗

照片由 Prateek KatyalUnsplash 上拍摄

既然我们已经确定了我们的目标市场人口统计,我们可能会问自己:符合这一人口统计特征的地区在哪里?在我们的目标州中,哪些县、城市或邮政编码在郊区-城市环境中具有更高的人口规模和收入、高增长、高就业和高经济活动?

缩小漏斗是指我们使用数据作为过滤器缩小更大的地理范围。如果我们知道我们不可能在一个人口少于 5,000 的邮政编码中取得成功,我们可以消除任何带有该名称的邮政编码。我们可以使用任何可识别的数据点来保留或排除地理位置,具体取决于我们认为哪些位置对我们的业务成功至关重要。如果我们担心限制太多,我们可以使用保守的过滤器(比如比我们想象的低 20%的目标人群)。

使用我们构建的工具,根据选定的人口统计和经济状况来查找地理位置,只需一两分钟,我们就可以将搜索范围(我们的漏斗)从跨越 3,836 个邮政编码的 5 个州缩小到仅 54 个邮政编码,这些邮政编码包括:

  • 邮政编码人口超过 25,000
  • 15 万以上的城市人口
  • 不断增长的人口
  • 家庭收入超过 75,000 美元
  • 劳动力中的高比例人口
  • 高度的经济活动
  • 白领就业的第一产业

如果我们愿意,我们还可以采取更大的图片视图,查看县和城市,而不是邮政编码级别。这有助于我们抵消使用邮政编码的一些缺点,邮政编码不一定是完美的地理边界,但确实给了我们更多的特异性(更小的搜索区域)。

了解&分清轻重缓急

让我们来看看符合上述标准的 54 个邮政编码倾向于在哪里:

符合上述条件的邮政编码示例;作者图片

弗吉尼亚州北部(靠近华盛顿 DC)、北卡罗来纳州罗利的西北角和亚特兰大的北部有一组邮政编码。佛罗里达州坦帕市周围也有多个位置,迈阿密北部、杰克逊维尔东北部和北卡罗来纳州夏洛特南部。

从这里,我们可以创建一个评分机制,根据我们的判断或业务目标对邮政编码进行优先级排序。人口规模比收入更重要吗?劳动力和白领工作的百分比比人口增长更重要吗?

我们可以创建一个公式,根据目标市场的重要程度对其进行加权。出于演示的目的,我将人口(邮政编码和主要城市)各占 10%的权重,收入占 15%,经济活动占 20%,10K 人均健身房数占 20%(少=好),白领劳动力占 15%,人口增长率占 10%。

换句话说,我们最重要的标准是经济活动、竞争、收入和白领劳动力。对于您的业务,您可以包括更多或更少的数据点。您可以为每个包含的数据点对您的业务的相对重要性创建任何有意义的权重。

这允许我们获取一个无序的列表,并赋予它某种优先级和顺序的外观。这并不意味着最高分是完美的,也不意味着我们不能用最低分取得成功。相反,它帮助我们使用数据作为指南,了解哪些领域表现出更多对我们的成功至关重要的特征。

使用这种方法,我们可以创建一个自上而下的视图来帮助我们划分优先级。哪些地铁平均分最高?那么,那条地铁在哪些城市?最后,这些城市的邮政编码是什么?

基于上述公式(/100)的市场评分;作者图片

基于上述权重的数据表明,在我们剩余的 54 个邮政编码中,坦帕市似乎潜力最大,其次是夏洛特和亚特兰大。从城市来看,坦帕、卡明斯(佐治亚州)、亚特兰大和夏洛特得分最高。我们可以使用上面的地图——或者根据我们的得分更深入地分析——在这些大都市或城市中,对适合我们初始屏幕的邮政编码进行优先排序。

然后,我们可以选择得分最高的城市,对更具体的数据进行更深入的研究。今天有多少拳击馆?它们位于哪里?消费者对现有的选择有多满意?哪些健身房最受欢迎,这表明我们可能想看看其他地区?

我们在坦帕-圣彼得堡市区有 3 个邮政编码。让我们来看看比赛。方便的是,我们有一个拳击俱乐部和 9round Fitness(另一个拳击相关的连锁健身房),位于第一个搜索的邮政编码 33629。我们可能想探索布里顿广场周围的地区,邮政编码的南部,这看起来像一个高流量的地区,几乎没有竞争。

在谷歌地图上搜索“拳击馆”,邮编 33629;作者图片

我们在坦帕得分最高的邮政编码是 33626,几乎没有直接竞争对手。我们还看到大片不适合居住的土地,所以这可能值得进一步探索——是否有足够的人口密度让我们成功?我们能在合理的驾驶时间内在这个邮政编码的任何地方接触到足够的人口吗?

在谷歌地图上搜索“拳击馆”,邮编 33626;作者图片

在我们最后的邮政编码 33647 中,我们看到了另一个 9Round 和一个名为 Dungen 的企业,它没有评论,可能要么没有业务,要么是一个非常小众的竞争对手。

在谷歌地图上搜索“拳击馆”,邮编 33647;作者图片

坦帕大都会区有很多拳击馆,看看下面的一般“拳击馆”搜索结果就知道了,但是通过“缩小漏斗”和创建优先级,我们能够找到市场中尚未开发的领域。在这些邮政编码区,拳击馆的数量减少可能有很好的原因,但这给了我们一个更集中的搜索点。

在谷歌地图上搜索“拳击馆”;作者图片

验证位置

现在,假设您在从上述优先顺序中选择了特定的邮政编码/城市后,与一家商业房地产经纪公司合作。他们已经向你展示了潜在的网站,你需要在前进之前有最后一点信心。

通过确认你的目标市场存在于你的位置范围内,数据有助于使人信服。例如,您可能发现在 5 英里的半径范围内至少有 50,000 人,或者在 1 英里的半径范围内至少有 20,000 人工作。

我们做了一个基本的人口估计工具,你可以在这里使用(或者 T2 在这里阅读)。我们还有更先进的工具,可以提供更多信息,如按收入水平划分的家庭百分比和按年龄划分的人口百分比,以及估计的就业人口。

假设我们已经为我们的健身房确定了两个潜在的地点,这两个地点相距大约 10 分钟的车程。知道我们对目标市场做了什么,根据数据你觉得哪个更好?

在 3 英里的半径范围内,位置 1 有大约 92,000 人的人口和大约 25,000 人在该区域工作。其他人口统计如下。

位置 1 的人口统计数据;作者图片

在同一半径范围内,位置 2 的人口约为 32,000,有 6,000 人在该区域工作。其余的统计数据如下。

位置 2 的人口统计数据;作者图片

位置 1 是拳击馆,位置 2 是急救诊所。这两个城市的收入都高于平均水平,而位置 1 的人口比例略低于 39 岁或更年轻。它还拥有高得多的人口、家庭和就业,这表明一旦达到一定的收入门槛,人口规模对他们来说可能比收入更重要。

结论

如前所述,选址或寻找增长市场非常复杂。数据只能触及做出这个决定的表面。还有一些考虑因素,如可用劳动力、生活成本、办公/零售空间成本、与经纪人合作寻找合适的房产(这与合适的市场同等重要,如果不是更重要的话),等等。

战略性地使用数据——将正确的数据与公司的目标相匹配——可以帮助您更有效地利用时间。而不是试图找出“我们应该在东南沿海的什么地方生长?”你可以反过来问“我们应该在这 2-3 个城市的什么地方发展?”。

提出更有针对性的问题会带来更好的讨论和更好的答案。更详细地研究几个市场比查看大量市场的汇总统计数据要容易得多。使用系统的、数据驱动的方法来缩小您的漏斗,然后了解并优先考虑位置,这可以帮助您筛选出无效的位置,并在对您的业务有效的位置上花费更多时间。

对谈论位置智能或分析感兴趣? 在 LinkedIn 上联系我 或通过电子邮件联系 jordan@jordanbean.com。

使用数据科学制定成功的商业战略,第 1 部分

原文:https://towardsdatascience.com/using-data-science-to-develop-a-winning-business-strategy-part-1-2d25ca993223

将分析与蓝海战略相结合,以开发新客户、增加利润、培养员工和客户忠诚度

第一部分:概述、项目任务、战略画布

桑加·里马·罗曼·塞利亚在 unsplash.com 拍摄的照片

(本文之前发表于 2022 年 1 月 20 日的《开放数据科学》上https://opendata Science . com/using-Data-Science-to-develop-a-winning-business-strategy-part-1/。)

在创建让竞争对手变得无关紧要的伟大商业战略时,数据科学团队应该扮演什么角色?答案在于蓝海战略的一个应用,蓝海战略是由 W. Chan Kim 和 Renee Mauborgne(2015)在其著名的同名著作中提出的一套概念。

Kim 和 maoborgne(K&M)的文本与数据科学没有特别的联系。在这个由三部分组成的系列中,我将从数据科学的角度来看待他们的概念。我描述了数据科学家如何与每个组织单位的其他人合作,制定有利可图的业务战略,获得更多客户并提高他们的满意度,而不受现有竞争对手的干扰。在第 1 部分中,我介绍了蓝海过程,并描述了它的关键概念,即驱动每个主要任务的战略画布。正确的战略布局是其成功的关键因素;出错会导致大量时间和金钱的浪费。

蓝海战略是关于创造新市场的,所以第 2 部分将描述数据科学家如何让其他人做这件事。

第 3 部分将关注蓝海战略中的效率管理,以及通过调整有利于蓝海战略的激励来获得对蓝海战略的支持。由于战略本质上是长期的,第 3 部分还描述了数据科学家如何帮助更新蓝海并避免可能颠覆它的陷阱。最后,我将指出它的局限性,并提供关于数据科学家如何为蓝海成功做出贡献的结论。

蓝色的海洋

到底什么是蓝海?根据 K & M 的说法,这是潜在客户工作而你的竞争对手不工作的地方(至少现在还没有)。引用他们的文字(第 18 页),蓝海是你可以:

创造无竞争的市场空间

让竞争变得无关紧要

创造和捕捉新的需求

打破价值-成本权衡

调整公司活动的整个系统,以追求差异化低成本。

蓝海是商业天堂——还没有竞争对手削减利润,在 it 领域更容易获得创新声誉。实现这一目标并不容易,但数据科学可以提供帮助。这篇文章描述了如何。

将蓝色海洋与红色海洋进行对比,可以更好地理解蓝色海洋。再次引用他们的文本第 18 页,K&M 描述红海为公司:

在现有市场空间中竞争

击败竞争对手

利用现有需求

进行价值-成本权衡

将企业活动的整个系统与其差异化战略选择低成本相结合

这两个海洋之间的差异是显而易见的。红海是大多数公司运营的地方。它们很有竞争力,很难在其中茁壮成长,并且被现有市场很好地规定了。他们的重点是击败其他提供重叠或类似产品或服务的公司。随着更多竞争对手的出现,或者现有竞争对手抓住最新的技术或服务浪潮,红色市场的利润可能会减少。相比之下,蓝海专注于让竞争变得无关紧要。他们通过率先解决与你的红海客户面临的相同或相似的问题来做到这一点,但他们是为不同行业的不同类型的客户而做的。这创造了巨大的新价值和增长潜力。

数据科学在蓝海中的作用

简而言之,数据科学在蓝海战略中的作用是为整个企业的团队成员提供所需的洞察力,帮助他们找到蓝海并在那里茁壮成长。这些见解来自 K&M 描述的以下十条原则,在本系列中,我们将描述数据科学家如何为每一条原则做出贡献。

作者图片

构建战略画布

根据 K&M 的说法,数据科学家和其他人的首要任务是构建一个战略画布,然后将其转移到客户认为更有价值的建模工作的各个方面。因此,这些方面可能会吸引潜在客户(即潜在的新客户),他们有贵公司可以解决的问题,但不在您当前的市场空间内。因此,此任务的目的是更好地了解客户最有兴趣购买的价值类型。K & M 将战略画布描述为“对价值创新和创造蓝海至关重要的分析框架”(第 27 页)。

下图说明了四个机器学习(ML)预测模型的概念。为了说明这幅画布,假设综合起来,生产这些模型有助于为一个对利益人群的福利最大化感兴趣的公司创造一个无竞争的市场空间。一种模型侧重于预测特定医学问题的发作。其他三种侧重于预测该疾病的后果(即,因该疾病的急性表现而去急诊室、因严重发展而住院治疗以及该疾病的死亡率):

作者图片

战略画布的纵轴从零到一,对公用事业客户期望从每个分析模型中获得多少进行评级。水平轴标记模型来描述它们的焦点。这三条线代表了三个不同的公司寻求进入蓝海的战略蓝图。进入的人可以通过执行剩余的九个任务来进入。

在医疗保健应用中,这样的风险模型例子比比皆是。例如,它们可能包括逻辑回归或其他线性或非线性分类模型,由流行病学家生成,用于估计被诊断为新冠肺炎、因其住院、因其死亡、幸存但随后出现长期健康问题或完全康复的概率。也可以为许多其他急性或慢性疾病生成这样的模型。在 Chandan K Reddy 和 Charu C Aggarwal (2015)的医疗保健数据分析中可以找到几十个例子。这些模型可以直接销售给客户,客户可以从基于云的平台获得这些模型。模型也可以作为输入与许多其他特征一起被聚集,以创建由许多客户使用的产品或服务。那些其他特性也可以显示在策略画布上。

为上面显示的公司创建画布包括估计他们的客户或潜在客户将从每个模型中获得的效用。假设这种知识可以帮助每个公司找出如何为其他公司创造有价值的东西,这些公司有类似的问题要解决,并且在另一个目前没有服务的行业中运营。

还可以制作策略画布,以反映数据科学家进行的更细粒度的活动,对许多不同类型的机器学习或深度学习方法创造的价值进行评级。如果公司基于其建模方法的相对复杂性或性能来营销其产品,可能会出现这种情况。关于性能指标的深入讨论可以在任何一本好的机器学习教材中找到(例如 Geron,2019)。Hall 和 Gill (2019)详细讨论了其他建模属性,如可解释性、透明性、公平性或可扩展性。这里的想法可能是“我们的模型更好,所以你从它们那里得到的见解更有用”,或者类似的东西。参见我之前的文章中关于 101 种不同建模方法的信息,数据科学家在努力产生有用的见解时可能会考虑这些方法。

战略画布背后的一般思想是评估潜在市场感兴趣的每个模型或每个模型特征的效用。画布上每条线之间的差异表明了这三家公司在感知客户从每种模式中获得的价值方面的差异。它们在每种模式中的相对投资水平可能会相应变化。

既然三家公司的界限不同,那么哪一家(如果有的话)是正确的呢?是否存在最优组合?我们主要对蓝海感兴趣,而不是红海,所以我们不太关心每个公司。我们想知道如何利用我们行业中公司目前提供的最佳产品信息(例如,关于技术水平的信息)来帮助我们在其他地方创造无竞争的市场空间。

为了理解艺术的状态,我们需要一个与策略画布所表示的每个模型或特性相关的事实的仲裁者。这个仲裁者也许是基于公司间的行业销售趋势。如果整个行业的销售都在增长,并且如果一家公司在销售上明显与其他公司拉开距离,那么它正在开发的功能或模型可能对其他地方的客户也更有用。因此,行业销售额的增长和关于哪些公司正在主导当前行业的情报,可能会提供重要的线索,告诉你在为竞争对手不在的另一片蓝色海洋设计分析模型时,如何调整投资。

另一个仲裁者可能是由第三方产生的公司比较,如 IDCChilmarkGartner 。这三家公司都对许多行业的公司产品进行评级,并不止一次关注许多公司基于分析的产品质量。虽然这些评级通常用于区分红海中的公司,但被认为拥有最佳或领先分析的公司可能也更有可能在蓝海中应用他们的产品。他们可能拥有在蓝海中更有可能成功的模型或模型特征。因此,希望更好地了解客户价值的数据科学家和策略师可能会从 IDC、Chilmark 或 Gartner 的市场洞察中找到一些线索。他们可能会使用这些报告,以及前面提到的销售趋势和情报,来精心设计可能在蓝海中发展良好的模型或功能的最佳组合。

移动战略画布

通过考虑下一个数字,可以解释改变你的策略画布的概念。图中所示的4-行动框架是由 K & M 创建的。利用市场情报和来自客户和潜在客户的信息,回答图中每一栏提出的问题,可以帮助您的公司决定哪些型号或型号特征在几个市场中更有价值。数据科学家可以在他们帮助寻找蓝海的努力中强调这些。

作者图片

关于上图还有一点值得一提。它关于设计思维的评论是指数据科学家、产品经理、业务开发和营销人员以及其他人如何与客户和潜在客户合作。他们会合作以更好地理解哪些问题最值得解决;通过解决这些问题,客户的效用、声誉、收入和利润会得到什么;解决这些问题存在哪些障碍;克服这些障碍需要什么和花费多少;以及如何着手解决这些问题。

设计思考会议系统地解决了每个问题,并听取了每个利益相关者的意见。数据科学家的角色是增加对每个问题的看法,并相应地影响思考和讨论,就像其他利益相关者一样。将数据科学家排除在这些讨论之外(即事后向数据科学团队提供反馈)通常是一个错误,因为大多数其他人不会意识到时间、资源或数据科学家认为使模型有用的其他问题。遗漏分析师也剥夺了数据科学家对利益相关者如何思考的第一手知识;这可能会增加误解,最终导致有用的产品减少、生产延迟或成本增加。数据科学家和其他人的设计思维可以提供许多有用的线索,这些线索是关于在蓝海中会成功的模型或模型特征的。

由于蓝海战略的全部目的是为数据科学家和其他人生产的产品创造新的市场,因此本系列的第 2 部分将建立在战略画布概念的基础上,并描述如何重建市场边界并超越现有需求以找到蓝海。将提供一个示例,并描述数据科学家使用的可视化工具的重要性。这将为本系列的最后一篇文章打下基础,在这篇文章中,我们将描述如何管理蓝海过程中的挑战,并避免可能导致我们回到竞争激烈的红海的错误。敬请关注。

参考文献

A.Géron,使用 Scikit-Learn、Keras 和 TensorFlow 进行动手机器学习:构建智能系统的概念、工具和技术,第二版 (2019),加利福尼亚州塞瓦斯托波尔:O'Reilly Media,Inc .

页(page 的缩写)Hall 和 N. Gill,机器学习可解释性介绍:公平、问责、透明和可解释人工智能的应用视角 (2021),波士顿,MA: O'Reilly Media,Inc .

IDEO.org,《以人为本的设计实地指南》( 2015 年), designkit.org

W.C. Kim 和 R. Mauborgne,蓝海战略 (2015),马萨诸塞州波士顿:哈佛商业评论出版社

T.穆拉利在https://Towards Data Science . com/advanced-Pandas-plots-e 2347 a 33d 576上用一行代码创建令人惊叹的可视化熊猫数据框架(2022)走向数据科学

R.Ozminkowski,101 Dalmations 和机器学习有什么共同点?(2021),走向数据科学,在https://towardsdatascience . com/what-do-101-dalmations-and-machine-learning-have-in-common-9e 389 b 899 df 3

C.K. Reddy 和 C.C. Aggarwal,医疗保健数据分析 (2015),佛罗里达州博卡拉顿:CRC 出版社,泰勒&弗朗西斯集团

使用数据科学制定成功的商业战略,第 2 部分

原文:https://towardsdatascience.com/using-data-science-to-develop-a-winning-business-strategy-part-2-905433548a3c

将分析与蓝海战略相结合,以开发新客户、增加利润、培养员工和客户忠诚度

第二部分:创造新市场

照片由 Cytonn 摄影在 unsplash.com

(本文此前发表于 2022 年 1 月 25 日https://opendatascience . com/using-Data-Science-to-develop-a-winning-business-strategy-part-2/的《开放数据科学》(Open Data Science)。)

在本系列的第一部分中,我们开始研究数据科学家在创建一个让竞争对手变得无关紧要的伟大商业战略时可以发挥的作用。该角色首先创建一个战略画布,使用 W. Chan Kim 和 Renee maoborgne(2015)在他们的名著《蓝海战略》中开发的一套概念。正确的战略布局是其成功的关键因素;出错会导致时间和金钱的浪费。

Kim 和 maoborgne(K&M)的文本与数据科学没有特别的联系。在这个由三部分组成的系列中,我将从数据科学的角度来看待他们的概念。我描述了数据科学家如何与每个组织单位的其他人合作,制定有利可图的业务战略,获得更多客户并提高他们的满意度,而不受现有竞争对手的干扰。在第 2 部分中,我将描述数据科学家如何使用策略画布和其他工具来帮助完成这项工作。

第 3 部分将关注蓝海战略中的效率管理,以及通过调整有利于蓝海战略的激励来获得对蓝海战略的支持。由于战略本质上是长期的,第 3 部分还描述了数据科学家如何帮助更新蓝海并避免可能颠覆它的陷阱。最后,指出了局限性,并提供了关于数据科学家如何为蓝海成功做出贡献的结论。

简单回顾一下——什么是蓝海,数据科学家在其中扮演什么角色?

根据 K&M 的说法,正如前面第一部分提到的,蓝海是潜在客户工作而你的竞争对手不工作的地方(至少现在还没有)。引用他们的文字(第 18 页),蓝海是你可以:

创造无竞争的市场空间

让竞争变得无关紧要

创造和捕捉新的需求

打破价值-成本权衡

调整公司活动的整个系统,以追求差异化低成本。

蓝海是商业天堂——还没有竞争对手削减利润,在 it 领域更容易获得创新声誉。实现这一目标并不容易,但数据科学可以提供帮助。

简而言之,数据科学在蓝海战略中的作用是为整个企业的团队成员提供所需的洞察力,帮助他们找到蓝海并在那里茁壮成长。这些见解来自 K&M 描述的十大原则。下面列出了这些。

作者图片

构建战略画布

根据 K&M 的说法,数据科学家和其他人的首要任务是构建一个战略画布,然后将其转移到客户认为更有价值的建模工作的各个方面。因此,这些方面可能会吸引潜在客户(潜在的新客户),他们有你的公司可以解决的问题,但不在你当前的市场空间。因此,此任务的目的是更好地了解客户最有兴趣购买的价值类型。K & M 将战略画布描述为“对价值创新和创造蓝海至关重要的分析框架”(第 27 页)。本系列的第 1 部分描述了如何构建战略画布。在第 2 部分中,我们通过展示如何使用它来重建市场边界,并为数据科学家帮助构建的产品创造新的市场,来继续这场对话。

重构市场边界

K&M 认为,蓝海战略的第二个主要任务是重建市场边界。他们的想法是通过寻找你通常不会服务的新客户来扩大你的市场。下图简要描述了他们确定的在这个新市场工作的六条途径。

作者图片

从战术上讲,这六条路径帮助公司多次重新应用他们的战略画布,每次都专注于为有类似问题要解决的新公司提供价值。LinkedIn 的公司描述可以帮助识别潜在的新客户。

新客户也可能包括现有市场中的各种群体,他们还没有成为努力的焦点。随着市场萎缩或饱和,这些战略集团变得更受关注,而早期的重点集团变得更受关注。随着为战略集团服务的技术的出现,战略集团也可能出现。

从分析的角度来看,扩大市场边界的任务很简单,因为科学家使用的分析模型非常适合跨行业应用。许多相同种类的分类或回归建模方法可以很好地用于医疗保健应用,或者用于教育、公共政策、环境健康、国防、零售、食品配送和其他行业的应用,在这些行业中,员工和客户的健康和幸福对成功至关重要。

对于一个新行业或战略集团来说,预测任务最困难的部分很可能是获取生成有用模型所需的主题专业知识。在这一领域的投资是成功的关键,因为行业专业知识不容易被非人类技术取代。

一个例子

医疗保健行业的例子可以说明重建市场边界的努力。在过去的几年里,已经做出了许多努力,以使所有少数民族和多数民族人口更公平地获得医疗保健服务和医疗服务。这些努力的目标是改善全社会的健康和福祉。出现了相应的以人为中心的护理运动,确保个体患者的需求驱动适当治疗的使用,而不管他们的人口统计、经济和社会特征。

下图描述了数据科学在这些运动中的作用,其根源在于数据挖掘的跨行业标准流程(Chapman 等人,1999 年)和称为数据科学生命周期的更新版本(staple,2017 年)。关于这些的更多信息可以在我之前的论文中找到,它展示了另一个(我的)数据科学生命周期图,你可以在下面看到。那篇论文描述了数据科学家可以使用它描述的科学过程来解决健康公平和健康的社会决定因素的几种方法(SDOH——参见健康人 2030 关于 SDOH 的讨论)。

作者图片

在这里,我想重点谈谈我的数据科学生命周期的战略方面。第一条重建市场边界的道路,如上面任务 2 图所示,是寻找与你的行业具有相同目的的替代行业。可以说,社会服务行业(例如,解决教育服务、无家可归、食物短缺、交通问题、环境问题等的公司。)正致力于解决许多也导致健康不佳的问题。因此,在医疗保健环境中工作的数据科学家在使用以股票和 SDOH 为重点的数据科学生命周期时产生的预测模型和可视化对其他社会服务行业的公司也有价值,因此我们可以将这些相关行业视为蓝海扩张的目标。

第二条重建市场边界的途径是关注自己所在行业或替代行业的战略集团。我的数据科学生命周期中对健康公平和健康的社会决定因素的关注可以被视为对健康相关的预测或推理模型和可视化的扩展关注,针对不同的人口统计和社会经济子群进行定制。我们的模型、推理和可视化可以非常有效地应用于敏感性分析,以确定这些模型有多健壮,并显示它们应该如何改进以服务于不同群体的成员。这样做可以扩大市场,创造新的客户,这些客户也为代表不足的少数群体服务。

接下来,如任务 2 图中所示,查看购买者链和互补产品有助于我们将模型聚焦于特定的影响者、购买者和用户,他们最有兴趣从数据科学家生产的模型、功能或其他产品中获得效用。同样的概念也适用于互补产品,如果我们知道购买基于特定类型的数据科学模型或功能的产品的客户也会购买其他特定的产品,我们可以改进或扩展我们的模型或建模功能,以创建更多的产品,为这些购买者带来效用。这使得数据科学家能够为市场扩张做出更大贡献。

如上图中任务 2 所述,重建市场边界的第五条途径是在为产品定价时考虑对购买者的功能或情感吸引力。这为数据科学家创造了两个机会。一种是了解决定客户端效用的心理或认知因素,然后用有助于更好地执行数据科学模型的变量来衡量这些因素。第二个机会是进行系统地改变这些变量的实验,以更好地理解并预测购买者的行为。这也将有助于创造新的客户。

最后,正如任务 2 图中所指出的,K&M 提到的重建市场边界的最后一条途径是塑造或适应市场的外部趋势。这些应该会影响我们的模型,因为不这样做会导致模型性能随着时间的推移而降低。数据科学家应该与整个公司的其他人合作,不断更新他们对市场运作方式和潜在买家需求的理解。更好的预测能力可以创造在红蓝海扩张的机会。

可视化的价值

到目前为止,我们的重点一直是模型或模型特征。已经顺便提到了相应的可视化,但是蓝海战略过程中的下一个任务可以极大地受益于数据科学家产生的可视化,所以让我们暂时更关注那些。

作者图片

这个过程的第三项任务很大程度上依赖于可视化练习,因为,正如 K&M 所说,人们经常迷失在数字中,盯着表格显示的结果发呆。许多人更喜欢图示视图。如果一张图片值一千个单词,那么就要由数据科学家来确保正确的一千个单词是由他们的模型的可视化所暗示的。

许多统计软件包都带有相应的可视化工具来帮助实现这一目标。我发现 SAS 可视化数据挖掘和机器学习( VDMML )软件包的可视化非常有用。与 Python 和 R 等开源工具相比,VDMML 中的许多可视化工具只需很少的额外编码就可以生成。VDMML 中的可视化信息丰富且快速。然而,开源工具中可用的各种可视化仍然令人印象深刻,对数据科学生命周期的所有阶段都很有用。例如,Aishwarya Ajaykumar (2021)提供了二十多个例子,说明如何用 Python 创建有用的可视化。图瓦拉凯什·穆拉利(2022)。 Tableau 还提供了强大的可视化功能;这些倾向于在数据科学过程的早期使用,当数据第一次被接收、清理并在图示视图中描述时,以帮助生成早期的或模型不可知的故事。

读者可能也熟悉许多其他可视化过程。例如,Jair Ribiero (2022)描述了数据科学家如何使用网络图来帮助讲述难以通过其他方式收集的数据故事。

可视化在策略开发中有三重作用。首先,与许多数据科学应用一样,可视化有助于分析师及其客户以易于理解的方式理解数据讲述的故事。这建立了对建模过程和该过程产生的结果的信心。第二,可视化可以说明战略画布的许多不同版本,如上面的任务 3 图所示。获得每个方面的反馈可以帮助产品开发人员和其他人找到模型、功能或其他产品属性的最佳组合,为蓝海的成功铺平道路。第三,可视化有助于理解拓荒者、移民者和定居者的业务,帮助战略家做出更好的选择,将有限的资金投资于新产品,而不是更新和维护现有产品。

超出现有需求

根据 K&M 的说法,蓝海战略过程的下一个任务是超越现有需求,找到新的买家。数据科学家产生的分析促进了这一过程。来自文献、销售、市场营销、产品开发和其他人员的情报可以帮助分析人员将工作重点放在这方面。下面的任务 4 图描述了这项工作。决定关注哪些非客户可以帮助每个人理解要生成的最佳模型和功能,无论是作为独立产品还是作为更大产品的组件。

作者图片

总结和未来

到目前为止,在本系列的第 1 部分和第 2 部分中,我们已经介绍了蓝海战略的概念和过程,强调了战略画布的价值。该折线图展示了客户期望从数据科学家提供的分析以及其他产品中获得的价值。由于蓝海工作的重点是如何为其他行业的新客户或现有市场的新群体将潜在价值转化为实际价值,我们已经展示了数据科学家如何通过与业务的其他部分合作来促进这项工作。在第 3 部分中,我们将重点关注如何有效地进行这项工作,如何在执行蓝海工作时克服组织障碍,以及如何调整内部和外部利益相关者的激励,以帮助确保成功。我们还将关注一旦竞争对手抓住这些新市场,如何更新蓝海,以及如何避免竞争威胁和陷阱,迫使人们离开蓝海。这种方法的局限性和这一系列论文中的材料将被指出,并提供最终结论。请加入我们;这个信息很快就会出来。

参考文献

A.Ajaykuman,Python 中数据可视化的直观指南(2021), Analytics Vidhya ,在https://www . Analytics vid hya . com/blog/2021/02/An-Intuitive-Guide-to-Visualization-in-Python/

页(page 的缩写)Chapman,J. Clinton 和 R Kerber 等人,CRISP-DM 1.0:逐步数据挖掘指南(1999 年),关于https://www.the-modeling-agency.com/crisp-dm.pdf

健康人 2030,健康的社会决定因素,在https://Health . gov/Health ypeople/objectives-and-data/Social-determinators-Health

W.C. Kim 和 R. Mauborgne,蓝海战略 (2015),哈佛商业评论出版社,马萨诸塞州波士顿

T.穆拉利在https://Towards Data Science . com/advanced-Pandas-plots-e 2347 a 33d 576上用一行代码创建令人惊叹的可视化熊猫数据框架(2022)走向数据科学

R.Ozminkowski,101 Dalmations 和机器学习有什么共同点?(2021),走向数据科学,在https://towardsdatascience . com/what-do-101-dalmations-and-machine-learning-have-in-common-9e 389 b 899 df 3

R.Ozminkowski,使用数据科学制定成功的商业战略,第 1 部分,开放数据科学(2022 年 1 月 20 日),在https://opendatascience . com/Using-Data-Science-to-Develop-a-Winning-Business-Strategy-Part-1/

J.Ribiero,网络图和我们如何可视化我们的世界的温和介绍(2022),https://jairribeiro . medium . com/A-Gentle-Introduction-to-Network-Graphs-and-how-we-can-Visualize-our-World-connecting-the-dots-6b b5 a2 db 5558

J.马镫,CRISP-DM 怎么了,有替代方案吗?(2017),于https://jen staple . com/2017/07/01/whats-wrong-with-crisp-DM-and-is-there-an-alternative/

使用数据科学制定成功的商业战略,第 3 部分

原文:https://towardsdatascience.com/using-data-science-to-develop-a-winning-business-strategy-part-3-216bbd05bc73

管理流程

克里斯蒂娜·沃辛在 unsplash.com 拍摄的照片

(本文之前发表于 2022 年 1 月 31 日https://opendata Science . com/using-Data-Science-to-develop-a-winning-business-strategy-part-3/开放数据科学。)

在创建让竞争对手变得无关紧要的伟大商业战略时,数据科学团队应该扮演什么角色?答案在于应用蓝海战略,这是 W. Chan Kim 和 Renee maoborgne(2015)在其著名的同名著作中提出的一套概念。

Kim 和 maoborgne(K&M)的文本与数据科学没有特别的联系。在这个由三部分组成的系列中,我将从数据科学的角度来看待他们的概念。我描述了数据科学家如何与每个组织单位的其他人合作,制定有利可图的业务战略,获得更多客户并提高他们的满意度,而不受现有竞争对手的干扰。

在本系列的第一篇文章中,我们开始研究数据科学家在创建一个让竞争对手变得无关紧要的伟大商业战略时可以发挥的作用。该角色的初始阶段包括使用 K & M 开发的概念创建一个战略画布,正确的战略画布是其成功的关键因素;出错会导致时间和金钱的浪费。

在本系列的第 2 部分中,我描述了数据科学家如何使用战略画布和其他工具为他们的产品创造新的需求。在第 3 部分中,在简要回顾之后,我将重点关注如何在蓝海战略中管理效率,以及如何通过调整有利于蓝海战略的激励来获得对蓝海战略的支持。由于战略本质上是长期的,我还描述了数据科学家如何帮助更新蓝海并避免可能颠覆它的陷阱。最后,我指出了它的局限性,并提供了关于数据科学家如何为蓝海成功做出贡献的结论。

回顾—什么是蓝海,数据科学家在其中扮演什么角色?

根据 K&M 的观点,正如前面第一部分提到的,蓝海是潜在客户工作而你的竞争对手不工作的地方(至少现在还不是)。引用他们的文字(第 18 页),蓝海是你可以:

创造无竞争的市场空间

让竞争变得无关紧要

创造和捕捉新的需求

打破价值-成本权衡

调整公司活动的整个系统,以追求差异化低成本。

蓝海是商业天堂——还没有竞争对手削减利润,在 it 领域更容易获得创新声誉。实现这一目标并不容易,但数据科学可以提供帮助。

简而言之,数据科学在蓝海战略中的作用是为整个企业的团队成员提供所需的洞察力,帮助他们找到蓝海并在那里蓬勃发展。这些见解来自 K&M 描述的以下十条原则,我们描述了数据科学家如何为每一条原则做出贡献。

作者图片

在蓝海战略中实现效率最大化

本系列的第 1 部分和第 2 部分介绍了蓝海战略过程的前四项任务。我们从任务 5 开始,确保流程中的效率最大化。下图描述了该任务中涉及的步骤。

作者图片

根据 K&M 的说法,这项任务的第一步是评估买方效用,这是消费者便利、生产率、形象、风险或环境影响的函数。如果这些是潜在客户所看重的,那么如果可能的话,数据科学家应该将建模工作集中在这些事情上。我和我的同事们已经通过售前分析做到了这一点。

售前分析顾名思义:由数据科学家生成的一组分析模型,这些数据科学家预测客户通过购买您的产品将获得的利益或价值的类型和数量。严格执行的售前分析让客户对您的产品投资充满信心。

在我工作的一家健康和保健管理公司,售前分析侧重于入院和出院后 30 天内再次入院的预测概率,以及与购买我们的健康和保健计划相关的客户成本节约。这些售前分析涉及分类和/或回归模型,旨在为客户预测他们从这些计划中节省的资金是否会超过我们向他们收取的价格。售前分析因项目类型或患者人口统计和健康特征而异。我们在这里的工作更多的是红海而不是蓝海,因为我们在与其他程序供应商争夺客户。向蓝海扩张需要专注于为我们行业之外的客户创造价值,这些客户也希望改善通常不在我们计划覆盖范围内的消费者的健康和福祉。

这一领域蓝海工作的一个潜力在于为不属于典型客户的个人消费者创造价值的能力。在我们的健康和保健管理示例中,这将意味着为人们建立在线购买的产品,而不要求他们与通常购买我们计划的特定雇主或健康保险计划有任何关联。用商业术语来说,这是一种 D-to-C(直接面向消费者)的方法,而不是间接的 B-to-B-to-C(企业对企业对消费者)的方法。

我们发现直流到直流这个难题很难解决,原因有二。首先,参与我们的项目对个别病人或工人是免费的,因为他们的雇主或保险公司会支付费用。直接面向消费者的应用或程序不是免费的——必须有人付费,所以价格应该低,感知价值必须足够高,以证明自掏腰包付费是合理的。其次,制作高价值节目所需的技术还处于萌芽状态。随着监控血糖、心率和其他健康指标的消费设备的出现,它现在正在发展,但这种技术可能还需要几年时间才能进入黄金时段或获得监管机构的批准。无论产品或行业如何,数据科学家面临的一般挑战是创建能够准确预测价值的模型或其他产品功能,并进行模拟,以向常规市场以外的潜在买家展示原型的价值。

战略顺序的其他方面也在上面的任务 5 图中有所说明。K&M 指出,考虑和利用同事和公众的观点非常重要。这也适用于数据科学家进行的预测建模工作。

他们指出,利用合作伙伴帮助开发新产品的角度、能力和成本也很重要。如果你的潜在合作伙伴认同你的愿景,拥有与你互补的能力(而不是与你竞争),并且如果他们同意以合理的价格与你合作,那么与他们合作可能是有意义的,而不是自己做所有的蓝海开发工作。如果开发工作能够以一种对潜在的最终用户或其他买家有吸引力的价格完成,并且如果这种定价策略能为你的公司带来可观的利润,那么就继续下去。否则,停下来重新思考蓝海开发项目。

克服组织障碍

蓝海战略流程的下一个任务是克服内部组织障碍。K&M 确定了四个相关的障碍,并在下面的任务 6 图中进行了说明。组织中支持者和非支持者之间,以及与客户和潜在客户之间的有效沟通,是克服这些障碍和保持战略活力的必要条件,这样你才能找到蓝海。这种沟通需要努力理解每个利益相关者来自哪里,这样知情的对话才会富有成效。有效的沟通还需要方法和发现的透明度,这反过来需要可解释的、可说明的和公平的模型。霍尔和吉尔(2019)描述了如何生产这种模型;这应该是每个数据科学家的必读之作。

作者图片

将战略与执行相结合,然后调整价值、利润和人员

接下来,正如下面的任务 7 图所暗示的,战略必须引发执行,这样才能朝着蓝海前进。K&M 说,如果公司内外所有利益相关者都参与到战略的制定中,那么战略的成功执行就更有可能。他们的参与将增加他们理解和支持决策的可能性。K&M 还表示,利益相关者的明确期望必须提前设定,并在整个过程中反复讨论。需要时,必须公平执行或适当更改这些期望。

作者图片

根据 K&M 的观点,如果下面的任务 8 图中提到的规则也适用的话,将战略付诸实施将会更加容易。遵守这些规则要求数据科学以模型、功能或其他属性为基础,从而为客户带来效用。他们的工作也必须以一种有代表性的、负责任的和有效的方式进行。回到第 2 部分中提到的 CRISP-DM 和更新的数据科学生命周期,如果在建模之前花费大量精力来了解客户的业务需求,那么对效用的关注将会更有成效。我之前的论文强调了这项活动的重要性,而Cornelius Yudha wi jaya(2021)的一篇新论文展示了如何去做。

作者图片

关于执行以及价值、利润和人员定位的另一点也值得一提,尤其是对经理而言。数据科学团队的工作通常可以根据其成员为客户和您的公司产生的下游价值来量化。客户满意度和公司利润以及数据科学团队的预算、人员配备、资源和薪酬之间应该有直接联系。如果员工看到并体验到这些联系,你将建立起强大的忠诚度,培养出在蓝海中茁壮成长所需的员工参与度和能力。

更新蓝色海洋,避免红色海洋陷阱

请记住,你找到并茁壮成长的蓝海不会永远是蓝色的。一些竞争对手最终会赶上来,海洋可能会变红。下图显示了如何应对这种可能性。K&M 建议,通过不断重复上述步骤来寻找新客户,这些新客户需要创新的方法来解决你的公司要解决的问题,从而更新蓝海。因此,他们的蓝海战略过程是迭代的、循环的和无止境的。持续监控市场,并持续关注您的客户和潜在客户需要什么来解决他们的问题,将会带来新客户和更高的收入和利润。当这种情况发生时,伟大的数据科学团队的价值将永远展现出来。他们的工作是帮助创建先锋企业,并适当维护和更新移民和定居者的企业,这将继续产生收入和非常满意的客户。

作者图片

K&M 开发的蓝海战略过程的最后一个任务是在你的旅程中避免红海陷阱。正如下面的任务 10 图所指出的,这可以通过关注你的优势来实现,多关注新客户的需求,少关注你的竞争对手在做什么。创造创新产品时的速度是有用的,但只有在产生大量新客户价值的情况下才是有用的。为创新而创新不是蓝海工作的特点,可能会影响任务的成功。

作者图片

局限性

蓝海战略是一个长期的过程。如果它又快又简单,每个人都会去做,蓝色的海洋将会变得更少。像大多数困难的项目一样,蓝海愿景应该被清晰地传达并经常强化。蓝海战略必须与公司的总体使命相一致,或者使命可以扩展以适应蓝海。应该获得并加强领导人和关键利益攸关方对蓝海战略的支持。对企业每一部分的财政和其他激励都应该有利于它。如果经理和其他人仅仅因为竞争激烈的红海市场份额的增加而得到补偿,蓝海战略就不太可能成功,因为对竞争对手的关注与其无关。如果不改变财政激励,这将难以为继。

蓝海战略不同于大多数公司执行的红海战略。这不同于许多领导人所接受的训练或被激励去完成的任务。对其收入和利润立即大幅增长的预期可能是没有根据的,即使其长期收益可能比仅从红海中获得的收益大得多。过于不恰当和适得其反地关注短期问题会招致诋毁者积极反对蓝海进程或不支持蓝海进程。他们的担忧需要被表达、讨论和解决。

到目前为止,我们的重点是蓝色和红色海洋之间的对比,但紫色海洋也存在。紫色海洋具有蓝色和红色的特征。竞争仍然相对激烈,而潜在客户对你的分析议程有很大的控制权,这可能会加剧竞争。尽管蓝海工作的机会有限。

我工作了几十年的健康服务和结果研究市场就是紫色海洋的典型。联邦卫生机构(如 CDC、医疗和医疗补助服务中心、FDA、退伍军人健康管理局等)和私营部门客户(如大型制药公司)通常会规定他们讲述故事所需的分析的一般特征。他们对分析议程的控制倾向于平衡竞争格局,使得创新更加困难。我们有责任向客户展示蓝海战略的价值。这有时是通过主动向他们提出符合他们总体议程的建议来实现的。

最后,我们主要强调数据科学在蓝海战略过程中的作用。虽然我们强调了分析单位和公司其他部门之间密切合作的必要性,但这些组织中的每一个都有合理的顾虑要表达,并为流程的成功做出贡献。对公司其他部门如何贡献的全面考察超出了本文的范围。

结论

蓝海战略真的管用吗?可行吗?花这么多钱和精力去申请值得吗?是的,但不总是。K&M 提供了几个例子,说明竞争对手是如何变得无关紧要,以及信息技术如何改变了各个行业。例如,他们描述了太阳马戏团通过使来自马戏团公司的竞争变得无关紧要而上升到娱乐业的顶端。他们注意到苹果推出的个人电脑和智能手机的发展,创造了多年无人竞争的市场,直到竞争对手最终能够抢占份额。这些公司和其他蓝海成功故事仍处于或接近其游戏的顶端。他们的下一个挑战将是保持这一地位,很可能再次遵循 K&M 制定的程序。未能发现蓝海的公司通常是那些在部分流程中偷工减料的公司。

数据科学可以为蓝海的成功做出巨大贡献。需要可靠的、以客户为中心的分析。这反过来要求在业务的所有部分进行彻底和持续的协作,并经常听取外部利益相关者和潜在客户的意见。成功可能是巨大的,但只对那些有毅力和毅力坚持下去的人来说。没有金票,但蓝海过程可以值得投资。遵循 K&M 描述的十个步骤。按照此处所述,与您的数据科学团队合作。与他们密切合作,并提供强大的财务激励来激励整个企业的团队合作。那么胜算会对你有利。

参考文献

A.Ajaykuman,Python 中数据可视化的直观指南(2021), Analytics Vidhya ,在https://www . Analytics vid hya . com/blog/2021/02/An-Intuitive-Guide-to-Visualization-in-Python/

页(page 的缩写)Hall 和 N. Gill,机器学习可解释性介绍:公平、问责、透明和可解释人工智能的应用视角 (2021),波士顿,MA: O'Reilly Media,Inc .

W.C. Kim 和 R. Mauborgne,蓝海战略 (2015),马萨诸塞州波士顿:哈佛商业评论出版社

R.Ozminkowski,101 Dalmations 和机器学习有什么共同点?(2021 年 11 月 30 日),走向数据科学,在https://towardsdatascience . com/what-do-101-dalmations-and-machine-learning-have-in-common-9e 389 b 899 df 3

R.Ozminkowski,使用数据科学制定成功的商业战略,第 1 部分,开放数据科学(2022 年 1 月 20 日),在https://opendatascience . com/Using-Data-Science-to-Develop-a-Winning-Business-Strategy-Part-1/

R.Ozminkowski,使用数据科学制定成功的商业战略,第 2 部分,开放数据科学(2022 年 1 月 25 日),在https://opendatascience . com/Using-Data-Science-to-Develop-a-Winning-Business-Strategy-Part-2/

C.Y. Wijaya,一个提高你的商业理解的数据科学项目(2021),走向数据科学,在https://towards Data Science . com/Data-Science-Project-to-Improve-Your-Business-Understanding-776386 abbf 63

使用数据科学寻找最有价值的山地车

原文:https://towardsdatascience.com/using-data-science-to-find-the-best-valued-mountain-bikes-a65c8a26745f

山地车的定价通常被认为是模糊的,尤其是在比较多个品牌的相似价位的自行车时。选择新自行车时最大的挑战之一是试图了解自行车制造的主要成本驱动因素,也就是说,为什么山地车价格即使在同一系列中也会有如此大的差异。试图辨别为什么一个自行车阵容的价格范围从 2500 美元到 10,000 美元,但来自同一家公司,并带有相同的名称可能是一项艰巨的任务,尤其是对这项运动的新手来说。

为了帮助缓解一些围绕他们的定价模糊,几乎每个山地车公司都提供了一份详细的规格清单,列出了他们产品系列中每辆自行车的所有组件。由于这些组件中的绝大多数都来自第三方供应商,因此每个组件的定价信息都非常透明。这提供了一个独特的机会,可以利用数据科学和机器学习的一些原则,对主要自行车品牌系列产品进行可量化的并排比较,如果您想购买一辆新的山地自行车,可能会遇到这种情况。

在我开始分析之前,我想提供一些背景信息,说明为什么从自行车的组成部分来看它的价值是重要的。这不是一个小秘密,在山地自行车行业,你花的钱超过一定的门槛,在价值方面的回报递减。也就是说,过了某个价格点,你从花费更多中得到的组件价值的增加会减少。从入门级的 1000 美元全悬挂自行车到 3000 美元自行车的性能跳跃远远大于从 4000 美元到 6000 美元自行车的跳跃。

就本文的范围而言,我想看看三个不同价格区间的主要品牌和精品品牌的山地自行车产品:1700 美元到 2300 美元,2301 美元到 2900 美元,最后是 2901 美元到 3500 美元。我选择了这些价格范围,因为我相信这些是绝大多数人会细分他们的自行车搜索的范围。

我量化山地车价值的方法是从收集各种山地车的数据开始,然后根据每辆车在上述价格范围内的位置进行细分。不幸的是,每辆山地车及其零部件和价格明细都没有现成的电子表格,至少据我所知没有。该项目的数据收集需要手动将各种山地车的部件规格和价格输入到电子表格中,以便生成 CSV 文件,供以后使用 python 进行分析。总的来说,我收集了目前市场上 300 多辆山地自行车的信息,包括越野、越野和耐力赛类山地自行车。

在收集和清理了所有必要的数据(这是项目中最耗时和最乏味的部分,是所有数据科学项目中的一个常见主题)之后,我使用 python 和 scikit-learn 提供的优秀机器学习库来训练一个简单的回归机器学习算法,以预测山地车的价格,给定其组件规格和框架材料。输入特性由六(6)个组件规格组成:

  1. 框架材料(即碳或铝)
  2. 前悬架(前叉)规格和价格
  3. 后悬架(减震)规格和定价
  4. 传动系统规格和定价
  5. 制动器规格和定价
  6. 轮胎规格和定价
  7. 轮组规格和定价

在制造山地车时,还有很多辅助部件也必须符合规格,但上面列出的功能是主要的成本驱动因素,因此被选择用于训练回归算法。

在训练任何机器学习算法之前,执行一些探索性数据分析或 EDA 始终是一种最佳实践。首先,我绘制了每个输入要素的直方图,以探索它们的分布,并在数据中寻找任何极端异常值。

作者图片

作者图片

作者图片

从这些图表和基础数据集的相关统计数据中,我们可以收集到许多关于集体自行车山地车行业产品的见解。例如,在主要品牌提供的所有自行车中,只有 39%使用更经济但更重的铝车架,而 61%使用更昂贵但更轻更硬的碳纤维车架。这是一个值得注意的重要分布,我们将在下一节看到,车架材料对自行车的整体价格有很大影响。由于分布偏向碳纤维车架自行车,因此了解这将如何影响下游流程和预测模型非常重要。

注意每个输入要素的最小值和最大值之间的范围也很重要。例如,在最小和最大分叉价格之间有 1525 美元的范围。假设平均值大约为$774,那么$1625 的最大分叉价格看起来可能是一个异常值,它不代表数据集,并且会将平均值推高。这种异常值可能是数据报告中的错误,但是,经过进一步检查,数据报告是正确的,这种特定的前叉,福克斯 34 工厂提供的活瓣版本,是迄今为止数据集中任何自行车上提供的最昂贵的前叉。如果该数据点被确定为对下游预测模型具有不期望的影响的异常值,则它可以是从数据集中消除的候选。

EDA(探索性数据分析)工具包中最有用的工具之一是探索输入特征和目标变量之间的相关性。下图显示了数据集中所有要素之间的相关性。每个单元格中显示的数字是相关系数,代表两个变量之间的线性相关性。这些系数介于-1 和 1 之间。系数越接近-1,两个特征之间的关系越相反,即当一个特征增加时,另一个特征减少。相反,当系数接近 1 时,当一个特征增加时,另一个特征也增加。让我们看看下面的图表,看看我们能找到什么初步的见解。

作者图片

我们应该马上注意到的第一点是,所有相关系数都是正的。这应该是有道理的,因为直觉告诉我们,当一个组件的价格,如叉或传动系统增加时,自行车的整体价格也应该增加。具体来看自行车价格和输入要素之间的相关性,我们看到传动系具有最高的相关系数,即自行车价格的增加受传动系规格和价格增加的影响最大。这可能看起来不太直观,毕竟所有悬架规格通常都是规格表上列出的第一个组件。起初可能看起来不明显,但在彻底审查了主要品牌的自行车阵容后,很明显,一个阵容中的自行车首先是由其传动系统规格定位的。虽然这种说法也有例外,但这些自行车就是例外。与自行车价格相关性次高的是叉价。虽然叉是列出的大多数自行车上最昂贵的组件,但有趣的是,它在影响自行车价格方面仅次于传动系统。

从这个矩阵中得到的另一个有趣的见解是输入特征之间的相关性,其中最高的是分叉价格和冲击价格之间的相关性,为 0.727。再一次,这应该是直观的,因为自行车前叉规格的增加通常伴随着冲击规格的增加。在探索预测建模时,将两个或更多高度相关的输入特征结合起来通常是一种很好的做法。一种这样的方法是简单地取两者之间的平均值。用数据科学的术语来说,这就是所谓的特征工程。特征工程是一种生成更多描述性输入特征的强大工具,但是在我们的案例中,关于这两种特征的领域知识告诉我们,如果我们要将两者结合起来,我们会限制我们对最终预测的见解,因为我们希望根据单个组件规格找到最有价值的山地车。

一旦我们彻底探索了我们的数据集,并从其各种统计数据中收集了见解,我们预测建模过程的下一步将是清理和准备数据。第一步是清理数据集,特别是在我们的例子中识别和处理任何丢失的值。在数据收集阶段,一些组件显然是在内部开发和制造的。这些部件以内部生产的车轮组的形式出现,没有单独销售的市场价值。

数据集中缺失值的第二个最常见原因是第三方组件规格,这些规格只出售给自行车制造商,不在公开市场上销售。例如,Fox Rhythm 系列的悬挂前叉仅适用于完整的自行车制造,不适用于售后购买。虽然我能够找到足够的支持定价来准确定价 Fox Rhythm forks,但内部开发的轮组几乎不可能定价(除非你是轮组开发和市场定价方面的专家)。数据科学中用来填补这些缺失值的一个非常常用的方法是使用估算。在我们的示例中,我通过简单地获取特定输入要素的中值并将其输入缺失像元来估算缺失值。

下一个数据准备步骤是处理框架材料的分类输入。基于树的算法之外的机器学习算法不处理分类输入。为了使帧材料对算法有用,我们需要对它进行编码。本质上,我们将每个类别转换为 1 或 0。在我们的例子中,碳纤维车架的输入为 1,铝合金车架的输入为 0。编码完成后,如果没有必要将数据集缩放到一个通用的比例(通常在 0 到 1 之间),这通常非常有用。这允许算法更快更有效地训练。

既然已经收集、检查、清理和准备了所有数据,最后是时候应用一些预测模型来确定每辆自行车的价值了。在决定您希望用于预测的模型时,重要的是要了解您希望模型产生什么信息,以及如何在下游进一步使用这些信息。

这听起来可能是多余的,但这是一个常见的说法,即没有模型是为了创建模型而创建的。在我们的案例中,我们希望了解每辆自行车相对于所有竞争对手的价值,为此我们需要能够量化该价值。

我们的目标变量,自行车价格,也是数字,所以将自行车分为好的或坏的价值是不够的。相反,我们的模型的目标应该是预测自行车的价格,并根据预测价格和实际价格之间的差异确定其价值。为此,我们需要使用一个回归模型,该模型将根据我们的目标变量(自行车价格)在所有输入要素之间生成一个最佳拟合平面。

有很多不同的机器学习和深度学习算法可供我们使用,可以执行这个回归任务。选择正确的一个并不总是清楚的。如果您有计算资源,通常最佳实践是训练多种模型类型并选择最佳执行者。幸运的是,在我们的例子中,选择模型稍微简单一些。

考虑到山地车总体价格中包含输入要素的组件的高度描述性,我们选择了一个简单的 L2 回归算法来预测每辆山地车的价格。在这种情况下,L2 回归的最大好处之一是它允许我们用相对较少的输入样本创建一个模型。虽然这个模型比大多数其他机器学习算法都简单,但是模型的内部工作超出了本文的范围,我将把它留给读者来进一步探索它的具体细节。用于创建最佳性能模型的超参数调优和交叉验证方法也超出了本文的范围。

随着预测模型的完成和训练,我们现在可以根据部件规格预测自行车的价格。在我们开始预测之前,最好先研究一下模型中最突出或最重要的特性。下图显示了影响预测的最重要特征:

作者图片

回顾回归系数,很明显它们在很大程度上遵循了前面给出的相关系数。在决定自行车的整体价格时,车架材料是最重要的因素。这两种见解都应该相当直观,尤其是在探索数据时。在同等条件下,碳纤维车架的自行车总是比铝合金车架的贵。此图中更有趣的一点是冲击价格相对于轮组价格的重要性。在相关系数图中,冲击价格高于轮组价格。然而,正如我们在这里看到的,冲击价格没有轮组价格重要。

现在,我们已经了解了模型训练的一些见解,是时候了解模型做出的实际预测了。下面,我将重点介绍以下价格范围内越野、越野(XC)和耐力赛类别中最具价值的三款自行车:

  1. $1700– $2300
  2. $2301- $2900
  3. $2901- $3500

下面显示了前三个最有价值的越野自行车:1700 美元至 2300 美元

作者图片

接下来,前三名最有价值的交叉步道:2301-2900 美元

作者图片

接下来,前三名最有价值的越野自行车:2901-3500 美元

作者图片

下面显示了前三个最有价值的越野自行车:2301-2900 美元

注意:没有 XC 自行车低于 2300 美元的价格

图片作者注:(-)值表明自行车超过其预测价格。

接下来,前三名最有价值的越野自行车:2901-3500 美元

图片作者注:(-)值表明自行车超过其预测价格。

下面是前三名最有价值的耐力自行车:1700-3500 美元

注意:只有一辆自行车低于 3000 美元,将与所有低于 3500 美元的自行车归为一组

作者图片

在后续文章中,我将更深入地分析模型预测,包括高端价格范围内最好和最差的自行车。我还将写一篇文章,根据每个山地车品牌产品的总价值对其进行分类。

我希望这篇文章对任何对山地自行车、数据科学或两者都感兴趣的人有用。

利用数据科学了解冠状病毒是如何逃避疫苗和免疫系统的

原文:https://towardsdatascience.com/using-data-science-to-understand-how-the-coronavirus-is-evading-vaccines-and-immune-systems-addcba617230

数据科学对于从生物数据中挖掘信息是必要的

免责声明:本文不包含医疗建议,请向您的健康专家寻求医疗建议。

生物信息学的核心是一个数据科学领域,我们从未见过生物序列大数据像过去两年那样呈指数增长。研究人员、临床医生和决策者之间的大规模合作已经被观察到。大量信息隐藏在这些生物数据中。在这篇文章中,我将解释严重急性呼吸综合征冠状病毒 2(简称新型冠状病毒)进化以适应新宿主的动力,或者一些冠状病毒如何装备逃避人类免疫系统和疫苗的方法。

照片由桑哈什·洛哈卡尔Unsplash 拍摄

在过去,大多数生物信息学分析只涉及已经制作的软件的使用。目前,由于可负担得起的测序技术的可用性,生物信息学正在利用大部分所谓的数据科学进行数据分析。

开发一种适合所有人的工具来满足每个研究人员的需求几乎是不可能的,因此生物信息学科学家正在转向灵活的“基于文本的”数据处理。在这篇文章中,我将使用 Python 数据科学包来处理这些部分,并且在必要时,我会求助于现有的工具/软件来处理一些过程,如多序列比对等。此外,文章的布局是一个 Jupyter 笔记本的形式。

我觉得我需要指出,我来自生物学背景,因此可能有比本文提供的代码更短或更容易的编码方法。然而,这篇文章的代码为我找到了我想去的地方。

通过突变(生物体基因组的变化)的进化是各种新型冠状病毒变异的主要原因,如α、β、δ和ω。这些突变导致了几次疫情浪潮,以不同的方式对人类生活产生了积极或消极的影响。从这些分析中获得的信息对于疫苗的开发和购买非常重要。这些疫苗会引发抗体,这些抗体专门针对与在某个区域观察到的突变相关的新型冠状病毒位点。

导入 Python 包

我向下游流程推荐以下 Python 包:

import NumPy as np
import pandas as pd
import Bio
import matplotlib.pyplot as plt
import seaborn as sns

这些是数据科学中非常常见的包,除了Bio:

  1. NumPy主要用于切分字符串、列表、字典等 Python 对象。
  2. pandas主要用于其Dataframe对象,以表格方式加载.csv文件。此外,编辑数据帧非常容易。
  3. BioBiopython的简称,一个用来装载、操作和分析生物信息的包。
  4. matplotlib用于数据可视化,我主要用于图形。
  5. seaborn是建立在matplotlib之上的一个包,与matplotlib相比,我主要使用它的可视化风格和简单的语法。

正在加载数据集

Biopython用于加载 fasta 文件:

from Bio import SeqIO
for seq_record in SeqIO.parse('my_file.fasta','fasta'):
    *#the above code is used to parse/open the given file name.*
    print(seq_record.id)
    print(repr(seq_record.seq))

Biopython中的SeqIO(序列输入输出)类用于加载我们的文件,并在其后指定要加载的文件类型。

最后两行将向我们显示已加载序列的标识符(名称),以确保这是我们想要加载的文件。此外,对于质量检查,更明智的做法是检查是否所有序列都具有大约 29kb 的新型冠状病毒全基因组长度。

提取近似尖峰基因

使用下面的代码块从新型冠状病毒的整个基因组中提取近似的刺突基因。因为测序后整个基因组序列具有不同的长度,在刺突基因的两端允许约 100 个核苷酸的空间,以容纳可能比通常更短或更长的序列。

from Bio import SeqIO
h_cov = SeqIO.parse('my_file.fasta','fasta')# *Extracting the approximate spike gene from whole-genome sequences*h_cov_spike = []       
*# A list to collect the spike gene extracted from the whole genome sequences**# The next block of code extracts the gene locus from nucleotide number 21500 to 25500 as the approximate locus in each sequence record and then appends it to the h_cov_spike list.*for seq_record.seq in (h_cov):
    spike_gene = seq_record.seq[21500:25500]
    h_cov_spike.append(spike_gene)# The next line writes the h_cov_spike list contents into the spike_genes.fasta file. If the file does not exist, a new one will be created else the existing file is overwritten.SeqIO.write(h_cov_spike,'spike_genes.fasta','fasta')

提取的刺突基因序列+已知的 S 基因参考序列可以使用选择的比对工具进行密码子比对。为此,我更喜欢 MEGA X 中的 Clustal,但是任何工具都可以。然后,必须去除所有渐缩的核苷酸碱基,与参考序列一起测定。

分析刺突基因序列的进化模式。

Jalview 程序用于去除冗余序列和覆盖率低或大量未识别核苷酸的序列。快速无约束贝叶斯近似(FUBAR)模型用于调查和分析普遍的正选择或负选择,可在 DataMonkey 服务器上访问。

FUBAR 以图形、.csv表和拟合的系统树的形式返回结果。因为我发现很难理解返回的图并与之交互,所以我通常导出.csv文件和拟合的系统发生树。我使用.csv文件来理解选择率结果并与之交互。拟合的系统发育树用于验证观察到的选择率结果。我使用“交互式生命树(iTOL)”来绘制系统发生树,如下所示:

验证 FUBAR 结果的无根树,使用 iTOL 绘制(图片由作者提供)

正选择或适应性选择是我们在这篇文章中感兴趣的,因为它通过替换率的计算告诉我们 S 基因是如何进化的。替代率计算为给定位点的非同义率(β)与同义率(α)之比。由于一个氨基酸可以由一个以上的 DNA 三联体编码来编码,当我们说同义替换时,它意味着导致相同氨基酸的点突变,非同义替换是编码不同于原始氨基酸的另一个氨基酸的点突变的结果。

多肽上编码氨基酸的改变可以改变其折叠,随后改变蛋白质的构象。蛋白质的功能与其构象直接相关,某个蛋白质结构域的微小变化可能导致功能丧失,或者幸运的是优化其功能。当β > α的概率那么它的正选择的证据,负选择/净化选择反之亦然那么中性选择是当α = β时。

*# Loading the datamonkey results and passing the value to the spike_selection_rates variable.*spike_selection_rates = pd.read_csv('datamonkey-table.csv')
spike_selection_rates

然后使用如下所示的pandas包将.csv作为数据帧加载:

显示强阴性选择(前 6 个站点)和强阳性选择(后 3 个站点)的站点数据框架(图片由作者提供)

从上表中可以看出,感兴趣的列是SiteProb[alpha>betaProb[alpha<beta]beta-alpha,最重要的是BayesFactor[alpha<beta]。列的选择取决于分析的原因。

正向选择

正选择是我们感兴趣的,因为它表明 S 蛋白与野生状态不同,因为突变与负选择相比具有适应优势,在负选择中 S 蛋白纯化或维持位点的稳定性。首先,让我们来计算显示积极选择证据的地点的数量。

*# Creating a list which will hold all sites showing evidence of positive selection.*sites_positive = []for idx, val in enumerate(spike_selection_rates['beta-alpha]'):
    if val > 1:
        sites_positive.append(idx)
    else:
        pass number_of_positive_sites = str(len(sites_positive))print(f'{number_of_positive_sites} sites show evidence of positive selection')

贝叶斯因子

FUBAR 值表中的 Bayes 因子正在检验一个地点正在经历正选择(β > α)的有力证据的假设。以下代码块可用于调查有多少站点正在经历强正选择。

site = spike_selection_rates['Site']   * # Selecting the Site column*bayes_factor = spike_selection_rates['BayesFactor[alpha<beta]']  *# Selecting the Bayes factor column* # Plotting results using matplotlib and seabornsns.set()
ax = plt.axes()
ax.plot(site, bayes_factor)
plt.title('Comparison of Bayes Factors for each S protein site')
plt.xlabel(Site Number on Spike protein')
plt.ylabel('Bayes Factor')
plt.figure(dpi=1200)
plt.show;

上面给出的代码应该给我们一个类似这样的图形:

贝叶斯因子条形图(图片由作者提供)

对于该数据集,在 S 蛋白位点 100-200 和位点 600 -800 之间的范围内,3 个位点显示出强阳性选择(条形图上突出的峰)。

为了查明哪些特定站点生成了峰值,然后可以对数据集进行测试,以确定具有某个后验概率阈值的站点,我将使用阈值后验概率> 0.9。

此外,后验概率> 0.9 有利于阴性选择的位点可以被添加以理解在 SARS CoV 2 S 蛋白中观察到的进化模式的整体动力学。

下面的代码寻找这样的站点,并将强阳性站点和强阴性站点合并到一个数据帧中。

strong_beta = spike_selection_rates['Prob[alpha<beta]']
strong_alpha = spike_selection_rates['Prob[alpha<beta]']*# The Posterior probability where alpha (synonymous rates) > beta (non-synonymous rates/var(strong_beta)) is more than 0.9 were selected as evidence of strong positive selection.**# The Posterior probability where alpha (synonymous rates)  beta (non-synonymous rates/var(strong_alpha)) is more than 0.9 were selected as evidence of strong negative selection*strong_positive_selection = spike_selection_rates.loc[strong_beta > 0.9]strong_negative_selection = spike_selection_rates.loc[strong_alpha > 0.9]*# Then we merge the two strong selection dataframes.*strong_selection_dfs = [strong_negative_selection, strong_positive_selection]strong_selection_sites = pd.concat(string_selecetion_dfs)strong_selection_sites

生成的数据框显示在 FUBAR 结果部分。

阳性选择位点的进一步研究

这项工作是我做的一项研究的一部分,查看我的预印本了解更多细节。在研究中,我观察到显示强阳性选择的 3 个位点依次为 D142G、P681R 和 D614G。因此,为了尝试一下如何解释这些结果以及如何在现实生活中使用它们,下面是一些例子:

  1. D142G

s 蛋白位点 142 有助于结合 NTD 定向中和抗体的超位点表位。D142G 突变显示回突变,其中突变从 D142 变为 G142,然后回复到 D142。结合 N-末端结构域(NTD)的中和抗体的超位点表位的这种干扰降低了免疫识别,导致更高的病毒载量。

2.P681R

P681R 尖峰突变导致 Delta 变体在 Omicron 变体之前在全球范围内占优势。通常在多肽链急转弯处发现的脯氨酸氨基酸被碱性精氨酸氨基酸取代,这增强了 S1 和 S2 蛋白的完全切割,导致通过细胞表面细胞进入的感染增加。

3.D614G

D614G 与上呼吸道病毒载量较高有关,但与疾病严重程度增加无关。受体结合结构域(RBD)的结构分析显示,S 蛋白的 G614 形式在其开放构象中比 D614 形式占据更高百分比的血管紧张素转化酶 2 (ACE2)受体位点,导致与 ACE2 受体的结合增强。在另一项研究中,D614G 在人鼻上皮细胞系中表现出有效的复制,并在人 ACE2 转基因小鼠和叙利亚仓鼠中表现出增强的传播。

这些突变表明,新型冠状病毒是一种聪明的病毒,它利用几种技巧来逃避并在人类系统中生存。如果你喜欢深入的方法,我写了一篇中型文章讨论这 3 个突变。

参考

  1. Kambarami、Manasa 和 Mushiri (2022)观察到从津巴布韦新冠肺炎患者中采集的新型冠状病毒刺突蛋白的 N-末端结构域、受体结合结构域和弗林蛋白酶切位点中存在强的普遍阳性选择,medRXiV 预印本 DOI:10.1101/2022 . 04 . 27 . 2274357
  2. Harvey 等人(2021) SARS CoV 2 变异、尖峰突变和免疫逃逸,国家评论,19 页 409–424 2。
  3. 侯等(2020) SARS CoV 2 D614G 变异体表现出有效的体外复制和体内传播,370,1464–1468
  4. Korber 等人(2020)追踪新型冠状病毒峰的变化:D614G 增加新冠肺炎病毒传染性的证据,细胞,182:4,812–827 4。刘等 Delta spike P681R 突变增强适合度超过α变体,bioRxiv 预印本 DOI:5 .
  5. 沈等 VOC 家系中棘突蛋白突变 G142D 与频繁回复突变、病毒载量增加和免疫逃避有关,medRxiv 预印本 DOI:
  6. 6.Yurkovetskiy 等人(2020)D614G SARS CoV 2 刺突蛋白变体的结构和功能分析,细胞 183,739–751

使用 Databricks Autoloader 支持事件驱动的数据摄取

原文:https://towardsdatascience.com/using-databricks-autoloader-to-support-event-driven-data-ingestion-e833f4306d55

使用自动加载器简化向湖边小屋的增量数据接收

背景

我们在 ETL 工作负载中遇到的最常见的用例之一是跟踪哪些传入文件已经被处理,以及来自源的新数据的增量处理。在我职业生涯的早期,我仍然记得我们的框架中有这个文件监视器组件,它用来监视登陆文件夹中的文件。文件监视器用于注册进入登录层的每个文件,一旦满足所有要求的条件,组件就用于将所有数据处理到数据仓库中。

马库斯·斯皮斯克在 Unsplash 上的照片

尽管在过去的 15 年中,技术方面发生了很大的变化,我们已经从严格的内部部署转变为“让我们在云上构建数据平台”战略,但一些基本概念仍然是相同的,并构成了任何数据平台的核心。即使到目前为止,我们也有相同的问题陈述,我们从源系统接收数据,我们希望增量处理这些数据,并保证每个文件只处理一个数据。

这是一个由多个部分组成的博客,我将在这个系列中介绍自动加载器、Delta Live 表和工作流。这是我介绍 Databricks AutoLoader 的系列文章的第一部分。

从源获取数据

通常,我们都希望有一个事件驱动的流程编排,下面是您希望做的事情:

  1. 源数据由数据提取管道提取并转储到着陆层。您会期望管道在每次源发生变化时增量地提取数据。因此,您可能希望每天都有一个文件,或者一天有很多次。也许一个文件每隔几分钟甚至一分钟就有一次修改。
  2. 当文件进入登录层(通常是基于对象的存储,如 Azure blob 或 Azure Data Lake Storage (ADLS ))时,您会希望在元数据中注册文件以及所有相关的详细信息,如文件到达的时间、文件中的记录数量等。
  3. 着陆层中的文件被移动到原始层或数据湖中,并被加载到某种表中。一旦文件被加载,你想知道有多少记录被加载。如果文件中的记录数与表中进程加载的记录数相同。
  4. 除此之外,您还希望确定收到的文件中的更改,并采取相应的措施。要么用这些更改更新目标表中的所有记录,要么在关闭旧记录的同时识别这些更改并将它们标记为目标表中的活动记录。你会做,我们称之为缓慢变化的维度,类型 1,类型 2,或类型 3。
  5. 您还想对从源提取并发送到登录层的数据进行数据质量检查。您希望检查数据类型、空值、数据约束等。确保您从源中获得正确的数据,用于您的用例。
  6. 此外,您还希望跟踪源模式的变化,并且可能还希望数据管道在源模式发生变化时不会失败。

数据平台

首先,我们来谈谈典型数据平台的设计。目前,我的重点是建立所谓的 medallion 架构,使用它的想法是以青铜、白银和黄金层组织湖边小屋内的数据,数据质量随着数据从一层移动到另一层而提高。此外,我们的想法是构建一个可用于批处理和流式用例的平台。我应该说,建立一个数据平台,可以支持事件驱动的架构。

数据平台的湖屋架构

Raw Zone
这一层以本地格式接收来自数据源的数据。不需要模式,我们只需将所有数据以 CSV 文件的形式从源转储到原始层。

策划
这一层从原始区域中提取所有文件,并定义结构、实施模式、根据需要发展模式,并且通常对数据质量进行测试。数据的格式可以进行本地查询和访问,以便进行分析。

数据中枢
我通常称这一层为源的快照,但以标准化的形式。现在,我的意思不是构建学术企业数据模型,而是务实地对待它,并构建一个为您的组织工作的数据模型。不要落入行业标准数据模型的陷阱。这一层通常充当多个来源的集成层,并且还向整个组织公开数据,这可以用于各种用例,无论是报告还是机器学习模式。

问题陈述

我们有不同频率的数据来源。很少有来源是一天一次,有些是每隔几小时一次,很少有来源是每隔几秒钟实时发出数据。您可能会不时收到数百个文件,您需要一个进程来跟踪正在处理的文件,并确保您只处理一个文件一次。

自动加载方式

现在,这是我觉得 AutoLoader 可以派上用场的地方,以事件驱动的方式递增地处理从源代码中提取并转储到登录层的文件。我可以很快地设置自动加载程序,并摄取 CSV 文件,这些文件没有任何设置的频率。让我们来了解一下 Databricks Autoloader 的一些特性及其功能。

下面是我创建的使用 Databricks Autoloader 处理数据的函数的截图。在这篇博客的结尾,你也可以在我的 github 资源库中找到这个笔记本的链接。

Databricks 自动加载器代码片段

Auto Loader 提供了一个名为 cloudFiles 的结构化流源,当以选项为前缀时,它能够执行多个操作,以支持事件驱动架构的要求。

第一个重要选项是。允许处理 Avro、二进制文件、CSV、JSON、orc、拼花和文本文件的格式选项。该选项没有默认值,您需要将文件格式指定为必需选项。

.option(‘cloudFiles.format’, source_format)

“使用通知”选项允许您选择是使用文件通知模式还是目录列表模式来检测新文件。如果 useNotifications 设置为 true,您需要提供创建云资源所需的权限。启动作业后,您可以在 Azure Storage 的 Events 部分看到正在创建的通知。Autoloader 自动设置通知服务和队列服务,从输入目录订阅文件事件。文件通知模式要好得多,建议在大量文件进入您的登录区时使用。

.option(“cloudFiles.useNotifications”, “true”)

来自 Azure 存储的事件通知

我还写了一小段代码,如果数据已经存在于表中,就根据键列进行识别。如果键和记录都出现在目标表中,并且记录有变化,代码将更新目标 delta lake 表中的现有记录。如果该记录不存在,它会将该记录作为新记录插入到目标 delta lake 表中。

使用 foreachBatch 在自动加载程序中实现 SCD

的。foreachBatch 选项允许您指定对流式查询的每个微批处理的输出数据执行的函数。所以基本上你可以定义一个动作为一个函数,这个选项会在加载数据到你的增量表之前执行那个选项。

.foreachBatch(upsertToDelta)

Auto Loader 使用 RocksDB 跟踪检查点位置中发现的文件,以提供一次性接收保证。您可以使用 checkpointLocation 选项来指定路径。

.option(“checkpointLocation”, “/tmp/sky/customers/_checkpoints”)

我喜欢这个选项。触发器,您可以将自动加载器用于批处理和流式用例。将该选项设置为 true,可以将自动加载器作业配置为启动,处理源位置中的可用文件,然后停止。这有助于您控制成本,并且您不需要让集群一直运行。如果你知道你的文件什么时候到达,你可以用一个基于时间的时间表来安排。

.trigger(once=True)

一旦准备就绪,您就可以使用运行时定义的参数执行自动加载器功能。在下面的例子中,我使用了。tirgger 选项设置为 true,因此作业运行并加载目标中的所有数据,并应用 SCD 类型 1。因此,任何更改的记录都会在目标中更新,并且所有新记录都会被添加。这个图表非常简洁,而且是实时的。因此,您可以将文件放在源存储位置,当 Autoloader 尝试处理文件时,它会显示在图形中。

运行自动加载器功能

如果您单击原始数据选项卡,您会发现以下 JSON 格式的输出。其中有大量的信息可以帮助您构建数据可观察性仪表板。它为您提供了操作元数据,包括批处理何时运行、处理了多少条记录以及其他一些信息。老实说,我仍然需要弄清楚如何才能将这些信息派上用场。

{
  "id" : "7b9661ea-3438-4b2f-b41e-809043958ab0",
  "runId" : "1bd6e990-0b08-451c-be46-29c00f6b8233",
  "name" : "Merge New Data",
  "timestamp" : "2022-07-08T14:40:54.500Z",
  "batchId" : 7,
  "numInputRows" : 0,
  "inputRowsPerSecond" : 0.0,
  "processedRowsPerSecond" : 0.0,
  "durationMs" : {
    "latestOffset" : 0,
    "triggerExecution" : 0
  },
  "stateOperators" : [ ],
  "sources" : [ {
    "description" : "CloudFilesSource[/mnt/landing/customers/]",
    "startOffset" : {
      "seqNum" : 9,
      "sourceVersion" : 1,
      "lastBackfillStartTimeMs" : 1657033622925,
      "lastBackfillFinishTimeMs" : 1657033623542
    },
    "endOffset" : {
      "seqNum" : 9,
      "sourceVersion" : 1,
      "lastBackfillStartTimeMs" : 1657033622925,
      "lastBackfillFinishTimeMs" : 1657033623542
    },
    "latestOffset" : null,
    "numInputRows" : 0,
    "inputRowsPerSecond" : 0.0,
    "processedRowsPerSecond" : 0.0,
    "metrics" : {
      "approximateQueueSize" : "0",
      "numBytesOutstanding" : "0",
      "numFilesOutstanding" : "0"
    }
  } ],
  "sink" : {
    "description" : "ForeachBatchSink",
    "numOutputRows" : -1
  }
}

总的来说,Databricks AutoLoader 在新数据文件到达云存储时,无需任何额外的设置,即可增量式高效地处理这些文件。它支持将数据接收到平台的批处理和流方式,如果您正在使用 Databricks 构建您的数据平台,我建议您使用它。它消除了构建数据摄取框架的需要和复杂性,并具有我在本博客前面描述的所有特性。点击此处访问我在上面创建和使用的 Databricks 笔记本。此外,我很想听听你对这个话题的看法。

参考文献

https://databricks.com/glossary/medallion-architecturehttps://docs . Microsoft . com/en-GB/azure/data bricks/ingestion/auto-loader/
https://www1 . NYC . gov/site/TLC/about/TLC-trip-record-data . page

使用深度学习进行维基百科文章的文本生成

原文:https://towardsdatascience.com/using-deep-learning-for-text-generation-of-wikipedia-articles-c9f1897f54a0

在 fastai 和 Pytorch 中对自然语言模型使用 AWD-lstm

卢克·切瑟在 Unsplash 上的照片

预测序列中的下一个单词是语言模型的任务。语言模型是一种自我监督学习的形式。与大多数深度学习任务不同,语言模型没有通过手动标记提供给它们的问题目标。相反,数据本身包含所需的标签。在我们的例子中,这些标签只是句子中的下一个单词。除了显而易见的原因之外,你可能有很多原因想要承担这样一个自我监督的学习问题。例如,拟合一个语言模型,然后使用迁移学习极大地提高了文本分类器的性能:它们可以理解它们预测的语料库的细节。这种方法被称为通用语言模型微调(ULMFit)和是由杰瑞米·霍华德和西尔万·格鲁格首创的。

虽然变形金刚目前在深度学习社区风靡一时,但在 Pytorch 和 fastai 中很难找到关于语言模型的资源。从导入文本数据和符号化到编写复杂的模型,理解整个语言管道更加困难。因此,我们的任务将是预测 Wikitext 微小数据集中的未来单词,由 Stephen Merity,Xiong,James Bradbury 和 Richard Socher 提供(2016,Pointer Sentinel Mixture Models)。这个数据集在知识共享署名-共享许可下可用。

。这是从维基百科文章中收集的超过 1 亿个单词的语料库的一个小样本。这篇文章结合了自然语言处理的基础,包括标记化,以及让 RNNs 真正良好工作的最新技术。这同时解决了杰瑞米·霍华德快速书第 12 章和第 14 章的主题,并详细阐述了需要进一步概括的概念和代码。

数据预处理

你可能想知道为什么我们使用一个较小的模型。如果我们有完整的 Wikitext 数据可用(通过 fastai),那么为什么不使用它呢?有两个主要原因:无论何时开始建模任务,总是从数据的子集开始。它允许你建立你的管道,更快地迭代,更容易地实验。一旦你让整个事情运转起来,接下来就是在更大的数据集中进行替换,然后等待模型训练完成。

我们将首先回顾将文本转换成可以输入模型的东西的两个主要步骤:标记化和数值化。

符号化

标记化是将单词列表转换成单个标记的过程。但是,我们不能只在空格上打散字。如果你想一想,英语有很多奇怪的地方,有很多奇怪的符号,我们需要解释。例如,fastai 使用一套称为spaCy的令牌化规则。我们可以通过使用WordTokenizer来访问这个默认的标记器。

我们使用函数coll_repr(collection, n)来显示这个collection的前n项。我们还必须将txt包装在一个列表中,因为标记器获取一组文档,我们可以将它们表示为一个列表。然而,我们不能就此罢休。熟悉语言模型的人会知道,我们需要添加特殊字符来表示新文本的开始:xxbos,其中 BOS 代表“流的开始”。还有许多其他规则也使语言模型更容易学习,例如通过对重复字符使用特殊标记而不是简单地重复几次标记来减小嵌入矩阵的大小。这是由 fastai 的Tokenizer()处理的:

其他特殊令牌包括:

  • 下一个单词以大写字母开头
  • xxunk:未知令牌

数值化

一旦我们对输入流进行了标记,我们仍然需要一种方法来对这些标记进行分类,就像我们可以在表格数据的嵌入矩阵中嵌入分类变量一样。事实上,这个过程几乎是相同的:列出所有可能的标记,代表分类变量(这只是我们的词汇表),然后用它在这个列表中的索引或位置替换每个标记。

为此,我们实际上首先需要我们的标记化输入流,所以我们从上面调用我们的标记器:toks=tkn(txt)。然后我们需要设置Numericalize:这是使用一个类方法完成的,将我们的令牌作为参数传入。这样,我们构建了语料库的词汇。设置好之后,我们可以向它传递任何输入流,将令牌转换成整数。我们指定min_freq=0,因为缺省值是 3;由于我们的文本甚至不是一个句子,这意味着任何出现少于三次的单词都将被替换为xxunk

如果我们将文本从上面传递到num,我们会期望什么?这个过程的要点是将令牌转换成整数,表示它们在 vocab 中的索引。让我们仔细检查我们得到了这个。

你可能想知道为什么我们有这么多 0。这是因为我们的输入流很小,所以大部分令牌都是未知的。我们需要大量的批处理来构建足够大的词汇表。我推荐看看 fastai 的书的第 10 章中的例子。

为什么我们首先想要unk代币?向我们的模型提供尽可能多的信息当然是好的。然而,避免过大的嵌入矩阵是有用的,因为它会使用太多的内存并降低训练速度。此外,语料库中没有很好表示的单词可能没有足够的数据来形成表示。

创建数据加载器

最初,我们将基本上从零开始为我们的语言模型创建数据加载器,然后我们将把所有的文本放入 Pandas 数据帧,并让 fastai 为我们处理它。这第一步对于理解如何向语言模型提供数据非常重要。

我们以通常的方式提取数据,然后使用内置函数L()读取文本数据。这本质上只是一个 Python 列表的花哨版本,带有一些受 NumPy 启发的高级索引技术。不要太担心这个的语义。您可以在将来为不同的文本文件或 CSV 文件回收这些代码。这只是将训练集和测试集连接成一个连续的流。这将使我们能够在以后将它分成重要的和合理的批量。

然后我们可以看看我们的文本。

我们将通过在每个空格处分割文本来标记。

创建一个唯一标记的列表为我们提供了语料库的词汇。同样,我们使用内置函数L()。然后,我们可以通过枚举vocab将每个标记映射到它在词汇表中的索引。正如我们所预料的,一次输入一个令牌会给出一个整数列表。

要使用我们的语言模型,我们需要确保批处理是以特定的方式组织的。将来,我们可以使用内置的 fastai 功能来完成这项工作,但现在我们将手动完成这项工作。

数据集被分成bs个大小相等的组,代表bl个小序列流。每个批次都有bs行(批次大小)和bl列(序列长度)。第一批的第一行包含第一个迷你流的开始。第一批的第二行包含第二个迷你流的开始。第二批的第一行包含第一迷你流的第二块,从最后一批结束的地方继续。该模型将“看到”从一批流到另一批的文本。

我们的文本流如何跨批排列的示例(图片由作者提供)。

我们这里的group_chunks函数直接来自 fastai,它所做的就是这个重新索引。我们将文本流分成大小相等的m = len(dset) // bs批,其中m是每个批的长度。

您可能想知道如果文本流的长度不能被批处理大小整除会发生什么。在这种情况下,当使用drop_last=True创建DataLoaders时,我们可以删除最后一个形状不正确的批次(除以批次大小后的余数)。您可能还会问,如果数据加载器是基于向模型随机提供批处理的概念构建的,那么文本将如何成为连续的流。正因为如此,我们也通过shuffle=False

最后,我们需要将文本流分解成相应的输入和目标。为此,我们获取输入的序列长度,比如说 16 个单词,并从当前单词到未来的 16 个单词对我们的words标记进行索引。然后,目标变成从当前单词开始的 16 个单词(偏移 1)。确保您理解下面seqs中的循环范围。然后,我们为训练/测试分割指定一个截止值,即我们数据的 80%。最后,我们像往常一样使用DataLoaders,除了这次直接使用方法from_dsets传入我们的训练和有效数据集,并记住传入我们的批处理大小。

通过查看seqs列表中的第一项,我们可以确认我们的目标仅仅是我们的输入,偏移了一个元素。

这正是我们想要的:我们输入特定的单词流,自我监督语言模型的任务是预测该流中的下一个单词。

基线 RNN

我们最初的模型仍将是一个递归神经网络,但它不会有 LSTM 的内部细胞结构。相反,我们将使用一个香草 RNN,只有一层。我们的任务是在每个时间步输入最后一个n字,输出一个预测,并在输入下一个字时使用隐藏单元状态进行预测。我们对序列中的所有单词循环这个过程,减去一个。

我们单层 RNN 的象征性表现(图片由作者提供)。

我们在 RNN 中使用了三层:一个嵌入层和两个线性层,最后一个线性层输出预测的单词。这里唯一稍微不熟悉的是嵌入层。它所做的就是获取一个索引列表,代表我们的令牌,然后输出单词 embeddeds。我们也注意初始化我们的隐藏状态,由h表示,在初始化中为 0。如果我们在 forward 方法中这样做,我们将在每个序列被传入后丢弃模型的隐藏状态。这将违背整个目的——模型将不会知道它在序列中的位置。

在我们的正向方法中,我们初始化一个输出列表。然后,我们在序列长度上循环,在每次迭代中连接隐藏状态和应用于当前输入的嵌入层(直到沿着第二个轴的当前时间步长)。然后,我们应用中间的线性层和 ReLU 非线性,最后将最后一个线性层的输出附加到 outputs。当返回这些输出时,我们沿着第一维堆叠它们。

唯一不太说得通的线是self.h=self.h.detach()线。这是必要的,因为 RNNs 是如何工作的。通过仅在一个序列的开始将隐藏状态初始化为 0(在我们的例子中,一个序列是一篇文章),那么对于文章中的数千个单词,该模型可能不得不在自身上循环数千次。(记住,这是因为我们一次前进一个单词。)如果你考虑 RNN 的展开表示,这基本上等同于几千层的神经网络。如果你还没有发现这个问题,这里就是:如果我们在第 8000 个单词上,也就是 RNN 的第 8000 层,反向传播要求我们计算一直到第一层的导数。可以想象,这需要很长的时间和很大的内存。

因此,我们简单地从我们的 *sl* 长度输入中移除除梯度之外的所有梯度如果我们的序列长度是 16 左右,这使得它更容易管理。我们用detach方法来实现这一点,在sl迭代的每一次循环之后都会调用这个方法。通过递归层向后计算导数被称为通过时间的反向传播 (BPTT)。分离方法通常被称为截断 BPTT

还要注意,我们添加了一个reset方法,可以在创建学习者时通过回调来访问它。所有这一切都是自动初始化h到零在每个时期的开始,并在验证之前。这样做的理由是,我们不希望模型在阅读并预测一篇关于烹饪的文章的第一个单词时,具有来自高尔夫文章的某种隐藏状态。

然后我们可以创建我们的学习者并调用fit_one_cycle。请记住,语言模型需要一段时间来训练,所以即使我们的简单模型的一个纪元也需要大约 20 分钟。注意我们如何将ModelResetter传递到cbs,代表回调。

单层 RNN 的结果。

15%的准确度。还不错,但是让我们看看我们是否能在这方面有所改进。

创造 LSTM 模式

虽然与 RNNs 相比,LSTM 可以部分解决爆炸或消失梯度的问题,但是我们仍然可以做三件关键的事情来确保我们的 LSTM 被正则化并且不会过度拟合。这些都是由 Merity,Keskar 和 Socher 在一篇开创性的论文中介绍的。按重要性排序,它们是:

  1. 拒绝传统社会的人
  2. 激活和时间正则化
  3. 重量捆绑

我们将会看到上述技术与普通 RNN 的性能相比有什么贡献。

拒绝传统社会的人

我们使用 dropout 来调整神经网络,防止过度拟合。它的工作原理是在神经网络的特定层随机归零激活,概率为p。这防止了网络对某些激活模式的过度依赖,允许神经元更好地合作。添加的噪声提高了模型的鲁棒性。这个想法实际上是深度学习的创始人之一杰弗里·辛顿(Geoffrey Hinton)在一次在线讲座中提出的。

辛顿辍学的灵感来自一次去银行的旅行。“我去了我的银行。出纳员一直在变,我问其中一个为什么。他说他不知道,但是他们经常搬家。我想这一定是因为它需要员工之间的合作才能成功地欺骗银行。这让我意识到,随机移除每个例子中不同的神经元子集将防止共谋,从而减少过度拟合。”

然而,我们不能只是随机地删除一些激活,就此打住。考虑下一层的后果。假设我们最初有 10 个神经元,在整个退出过程中,我们只剩下 5 个。然后,将这些激活传递到下一层,我们可以想象下一层是相当混乱的。以前,它得到 10 个正激活的总和(因为我们在层之间应用 ReLU ),现在它只得到该信号的一半。就是尺度不一样。

如果我们想规模保持不变,我们需要重新调整。例如,如果p=0.5,那么我们期望一半的神经元被清零,这将使传递给下一层的信息减半。我们还剩下大约1-p个神经元。因此,我们除以1-p=0.5,将这些激活的大小增加到与之前相同的水平(因为我们除以一个小于 1 的数)。

激活和时间正则化

权重衰减在深度学习中被用作一种正则化形式,以防止过度拟合。权重衰减通过向损失添加惩罚来减小模型权重的大小,这意味着权重与所有其他权重相比不会变得过于重要。类似地,我们可以将两种其他形式的正则化应用于递归神经网络:激活正则化和时间激活正则化。

激活正则化(AR)旨在使模型的最终激活尽可能小。要做到这一点,我们只需将激活平方,并将它们添加到损失中,乘以一个超参数alpha。减少激活的大小实现了稀疏性,当我们有大的隐藏层倾向于过度拟合数据时,这是可取的。引入这样的稀疏惩罚允许模型更好地一般化。为了使 AR 工作,我们将它应用于退出激活,这样我们就不会惩罚在常规退出中被清零的激活。

时间激活正则化(TAR)通过确保序列中连续令牌之间的激活是靠近的来工作。这是因为在句子中看到一个新单词后,激活不应该发生剧烈的变化。因此,我们通过增加连续激活之间的平方差来惩罚损失。与 AR 相反,TAR 应用于非退出激活,因为我们想要时间步长之间的激活差异,这可以通过训练中的退出而显著减少。

重量捆绑

AWD·LSTM 的论文还介绍了一种被称为重量捆绑的技术。这与模型中映射的对称性有关。在我们的语言模型中,嵌入输入是一个将单词转化为激活的映射过程。相反,最终输出层激活英语单词。根据直觉,这些映射可能是对称的。我们可以强制 Pytorch 为嵌入的输入和输出层分配相同的权重矩阵,如下所示:

self.h_o.weight = self.i_h.weight

AWD-LSTM

让我们把所有这些技巧都包括进来,看看我们的准确度提高了多少。回想一下,我们仍然使用相同的 LSTM 模型架构;我们只是在每一步之间做一些小的调整,让它成为 AWD-LSTM。请注意我们通常在 Pytorch 中构造 LSTM 的方式与这里的模型之间的相似性。关键步骤是创建层nn.LSTM,在这里我们接受n_hidden输入并输出n_hidden输出,一定数量的层由n_layers表示。Pytorch 为我们处理其余的。我们还在 LSTM 层之后实现了 dropout,请注意实例化权重绑定的行。

请注意,我们为通过网络的每个前向传递返回三个内容:常规输出、丢失前来自 RNN 层的激活以及丢失后的激活。这允许我们使用回调RNNRegularizer

为了使用激活正则化和时间正则化,我们实际上并没有把它传递到模型中。这在很大程度上是因为我们自己实施它的复杂本质,也因为它可以直接附加到损失上。相反,在创建学习器时,我们将RNNRegularizer传递给回调列表,超参数 alpha 和 beta 分别用于激活和时间正则化。

您可能想知道为什么我们创建了两个学习者。第一个是以通常的方式创建的:传入数据加载器、模型、损失函数、指标和回调,我们就完成了。第二种方法更简单。我们可以让TextLearner处理类似于ModelResetterRNNRegularizer的回调,这是自动完成的。

经过一个时期的训练,我们的模型已经大大优于多层 RNN。如果你有时间进行更长时间的训练,我强烈建议进行多次训练,因为这样做后模型之间的差异会更加明显。

我们手工制作的 AWD LSTM 的结果(图片由作者提供)。

使用 fastai 进行培训

一批

虽然了解语言模型中的符号化和数字化是如何工作的对于调试很重要,但我们实际上可以使用 fastai 的内置模块来完成这项工作。出于我们的目的,我们甚至不需要向DataLoaders传递一个DataLoader对象。我们可以使用一个Datasets对象创建一个数据加载器,我发现这在处理 Pandas DataFrames.时更容易。首先,我们将把我们的训练和测试文本连接成一个大的连续流,就像我们上面做的那样。

然后,我们将创建Datasets对象。我们将使用它来创建我们的数据加载器。让我们一行一行地分解这段代码。

显然,splits 用于创建我们的训练集和验证集的索引,我们使用range_of函数来完成这项工作(提示:请仔细查看括号的位置)。然后,我们需要将特定的转换传递给Datasets类初始化器,其中包括记号化和数值化。如果您阅读Tokenizer文档,您会发现我们在这里传递的0只是文本列的名称。(打印出df_all,您将看到这确实是列名。)

但是,还有一个来自 Python 标准库的奇怪操作符叫做attrgetter("text")。这实质上是构造了一个从类中获取某个属性的函数。例如,如果boy是某个类的实例,我们定义了func = attrgetter("age"),那么调用func(boy)将返回boy.age.,因此,在我们的调用中,attrgetter("text")将获取在我们的txt_cols的标记化中创建的"text"属性,并将其数值化。

最后,我们将所有这些参数传递给我们的Datasets构造函数。请注意,我们将数据加载器类型指定为语言模型,这意味着当我们在调用类方法Datasets.dataloaders()期间传入附加参数时,我们的数据加载器将执行我们上面概述的操作,如下所示:

我们在这里做的是将所有的文本样本(dataframe 的行)连接成一个流,将它分割成大小为bs(表示批处理大小)的连续序列,然后一次遍历这些seq_len标记。然后我们就完事了!在用dls.show_batch(max_n=3)创建了数据加载器之后,我们可以用通常的方式显示一些批处理。请注意,右边的因变量只是输入流偏移了一个标记。

图片作者。

语言模型学习者

通过使用 fastai 中内置的语言模型学习器,我们可以省去很多麻烦,该学习器还使用了优化的 AWD-LSTM 架构。这也将是一个比我们上面使用的模型大得多的模型。我们以几乎相同的方式构造学习者,但是将AWD-LSTM作为模型传入。我们还将困惑作为一种度量,它只是损失的指数,通常用于语言模型中。

我们在学习者上调用fit_one_cycle,一旦一个时期完成,我们将把我们的模型保存到我们的本地机器上,这样我们可以在以后加载它,而不用担心再次经历所有上述步骤。警告:如果没有 GPU,即使只是这一个纪元也需要一段时间。准备等几个小时。

图片作者。

太神奇了!我们的精度比我们自己的 AWD-LSTM 高一倍,比普通的 RNN 高近三倍。

保存和加载模型

调用learn.save("final_model")。这将在learn.path/models/中创建一个名为“final_model”的文件。我们可以用learn.load("final_model")把这个模型的权重重新加载给学习者。

文本生成

使用上面的language_model_learner而不是普通的Learner的原因之一是language_model_learner有生成新文本的.predict方法。要做到这一点,我们只需要指定开始的文本,字数,我们将循环这个期望的句子数。

我们现在要做的就是把它们打印出来。

print(“\n”.join(preds))Michael Jordan was a professional golfer , who won a Golden Globe Award and the WWE Emmy Awards . Jordan was named to the NBA All - Star Game Team for that yearMichael Jordan was a professional basketball player . Jordan said that " i have a lot of support for my career " , and that he had changed his mind after being recalled to Utah . Jordan said he would not be

至少对我来说,这是惊人的。我们的语言模型显然不符合事实,但如果你不是很了解迈克尔·乔丹的传记,这种自动生成的文本几乎可以作为维基百科的文章。为Temperature使用相对较高的值增加了模型的随机性,我们也可以传递像min_p这样的参数,不考虑低于某个阈值概率的单词。

这些预测还拥有近乎完美的语法(正如杰瑞米·霍华德所指出的,这些“I”没有大写,仅仅是因为标记化规则不允许将单个字符大写)。我们的语言模型,仅仅在 CPU 上经过几个小时的训练,就已经掌握了构成英语基础的核心概念。

结论

在本文中,我们介绍了如何使用 Pytorch 和 fastai 构建语言模型,这是一个可以预测句子中下一个单词的模型。我们在几千篇维基百科文章上训练我们的模型,将准确度提高了近三倍,并使用 LSTM 生成了一篇关于迈克尔·乔丹的文章。

如果您想了解更多关于如何处理 Wikitext 数据集(以及一般的 NLP 数据集)的信息,请查看这里的简短 fastai 演练。未来的探索方向将是采用本文中创建的语言模型,并使用它来训练语言分类器模型,就像 ULMFit 中一样。

参考

[1] S. Merity,N. S. Keskar 和 R. Socher,规范和优化 LSTM 语言模型 (2017),CoRR

[2] J. Howard 和 S. Ruder,用于文本分类的通用语言模型微调 (2018),CoRR

[3] J. Howard 和 S. Glugger,fastai 和 Pytorch 的程序员深度学习 (2020),O'Reilly 出版社

[4]斯蒂芬·梅里蒂,·熊,詹姆斯·布拉德伯里,理查德·索彻,维基文本 (2016,指针哨兵混合模型)。这个数据集在知识共享署名-相似分享许可下可用。

使用深度学习预测 Spotify 上的嘻哈流行度

原文:https://towardsdatascience.com/using-deep-learning-to-predict-hip-hop-popularity-on-spotify-1125dc734ac2

我能建立一个对音乐很有品味的神经网络吗?

KC 是做音乐的,我的表哥,客户。图片由 KC Makes Music 提供。

高级摘要

(粗略的描述)

在这个项目中,我想建立一个工具,可以帮助我的表弟,一个名叫“KC 制作音乐”的嘻哈艺术家。这个工具将评估他尚未发布的歌曲是否有可能在黄金标准音乐流媒体服务 Spotify 上流行。我只从 Spotify 的数据库中收集和使用了音频预览样本和最近发布的嘻哈歌曲的相关流行度评分。这些被处理成光谱图和二元目标(流行/不流行)。这些被输入到一个神经网络中,这个神经网络是以不断增加的复杂性反复构建的。基于递归神经网络的最复杂的模型表现最好,准确率为 59.8%,ROC-AUC 为 0.646。这表明比基线提高了 9.1%的准确度和 0.146。这不是一个令人震惊的结果,但也不是一无是处!特别是考虑到这个模型只有音频非常有限,这是一个非常有希望的结果。我不建议在单首歌曲级别使用这种模型,但它对大量样本仍然有用。它可以将艺术家的注意力集中在一个子集上,这样可以节省精力,因此仍然有价值。进一步的改进只会使它成为我的客户可以使用的更强大的工具。

GitHub 链接

语境

音频分类是神经网络模型的经典用例。例如,在之前的一个项目中,我所在的一个团队建立了一个分类器来区分来自数据集的音乐流派。这已经在多个博客帖子(博客 1、 博客 2 )中完成,看起来是一个相当知名的项目。此外,Kaggle 上正在进行音频分类竞赛,这将是类似工作流的主要候选对象。谷歌大脑的语音识别比赛和康奈尔鸟类学实验室的鸟叫声识别比赛就是两个很大的例子。

带着这些想法,我想知道音频分类能走多远。流派、人类语言和鸟鸣声涉及的类别的差异在人耳中是显而易见的。爵士乐听起来不同于金属,音节听起来彼此不同,不同的鸟有不同的叫声。这些对于人类观察者来说都是显而易见的。我知道机器比人类有更高的分辨能力,我发现在音频领域,我们给机器一些人类耳朵可以毫不费力解决的问题,这很奇怪。我能找到的最难的音频例子是区分新冠肺炎咳嗽和普通咳嗽,但是一个训练有素的呼吸科医生可能会做出相当好的评估。相比之下,我们有模型可以根据井的一系列数据点预测坦桑尼亚的水井是否正常工作。在神经网络领域,《自然》发表了一个深度神经网络,它可以通过观察心电图来预测心脏诊断。人类很难通过筛选任何一个数据集来做出这样的预测(第二个数据集可能需要心脏病专家)。

因此,我想选择一个涉及音频的项目,对人类难以预测的事情进行预测。我想挑战极限,做一些雄心勃勃的事情。我喜欢音乐,所以我想看看只根据歌曲样本训练的模型是否能预测一首歌的受欢迎程度。这是一个非常常见的想法(博客 1、 博客 2、 博客 3 ),但这些都使用了 Spotify 提供的音频功能(如可跳舞性、乐器性等)。他们自己不使用音频样本,我认为使用带有神经网络的原始歌曲样本可能会更好。它也更有用,因为你不需要依赖 Spotify 的指标,可以在歌曲发布前进行这种分析。

受欢迎程度是一个艰难的目标——上述博客帖子与更传统的(即非神经网络)技术相结合取得了成功。此外,神经网络通常与人耳相对容易分类的音频数据一起使用。但是一个人不可能听着一首歌说“哦耶,那个听起来流行”。他们可能会说这听起来不错,但是受欢迎程度的量化有点模糊。

商业问题

KC Makes Music 是我表哥的艺名,他是 Spotify 上的嘻哈歌手。我认为,如果我利用我的数据科学技能尝试帮助他在平台上获得听众,这将是一次有趣的学习经历。他已经取得了一些成功,截至 2021 年 12 月,每月拥有约 24.5 万名听众,但了解如何获得每月听众和追随者往往很难。Spotify 有一个非常强大的数据库,所以我知道这里有一个机会,可以帮助我的表弟增加他的听众基础,从而扩大他作为一名艺术家的影响。

在查看了这个项目的可用角度后,我决定一种方法是调查最近的 hop hop 曲目,并尝试建立一个模型来预测以前未发布的歌曲是否有可能在当前场景中流行。我会收集随机分类的歌曲(所有流行度评分),提取它们的预览音频文件,将它们转换成频谱图,然后通过神经网络输入。这将有望提供一个模型,该模型已经获得了流行歌曲之间的共同特征,从而可以判断一首新歌是否会流行。我的堂兄有一些未发行的,完成的曲目,我们可以通过模型来实现。如果它足够精确,它就能决定哪首歌会唱得好。如果他们都做得不好,这将向我的表弟发出信号,他的曲目可以使用更多的工作,我们可以以迭代的方式重新测试它们。此外,我可以使用 LIME 之类的工具来确定模型发现什么特别重要,并将该信息传递给我的堂兄,让他关注。

对于这个项目,我决定将目标简化为流行/不流行的二进制,因为对于模型来说区分两个类别比确定某个东西有多流行更容易一些。就我的商业目的而言,受欢迎/不受欢迎和受欢迎程度没有太大区别。如果我得到了惊人的结果,我总是可以在以后将其转换为回归型模型,但是现在,简化标签应该有助于我更容易地看到模型的有效性。此外,最重要的是模型可以在两个类之间分离。有一个混搭流行和冷门歌曲的模型是没用的。假阳性意味着我的表弟发布了一首不会做得很好的歌曲,假阴性意味着我的表弟过度生产了一首像以前一样好的歌曲。不管怎样,我们都毁了好作品。考虑到这一点,准确性是一个有用的指标,但 ROC-AUC 是使用的首选指标,因为它可以衡量这两个类别的区分程度。

数据采集

所有数据来自 Spotify web API 。选择 Spotify 不仅是因为它是我们试图优化的平台,还因为它是最全面的音乐数据库之一。通过使用 Spotify web API 的 python 接口 Spotipy,我收集了 2019 年至 2021 年发布的“嘻哈”流派中随机歌曲的信息。随机轨道是采用这里所述的方法生成的。

关于我如何获得数据的更多细节,请查看 GitHub。

最终,16168 首曲目被选入。最关键的是,这些数据包括一个 30 秒 mp3 预览音频文件的 http 链接和一个流行度评分。这个分数在 0-100 之间,100 分是最受欢迎的。受欢迎程度是三态的,在 0 时有一个大峰,在 28 时有一个小峰,在 45 时有一个大峰。

仔细看看

数据集:完整性和代表性的检验

这是最容易做到的,看看我知道的几个有代表性的艺术家在过去 3 年里发行的歌曲。在这种情况下,德雷克、坎耶·韦斯特和 KC 做音乐(我的表哥)。关于代码的更多细节,请查看 github。

德雷克描绘了大约 140 首曲目。似乎是对的。

坎耶·韦斯特得了 9 分。肯定不准确,因为他不久前释放了冬达。

KC 让音乐完全缺失!

这个数据集看起来不像它应该的那样完整。有很多歌曲的艺术家和受欢迎程度都非常多样化,但我期待会出现的歌曲却不见了。例如,Kanye West 不久前发行了《冬达》,我在这张专辑中没有看到任何东西。再者,表哥的音乐根本没有表现出来。

就像我说的,仍然有很多歌曲,所以现在可能已经足够好了。我会继续我所拥有的,如果我需要更多,我会调查更多。

受欢迎程度:对目标的检查

track_df['popularity'].hist(bins=100)

受欢迎程度得分(x 轴)与计数(y 轴)。作者图片

40 或更高似乎是一个不错的截止值,但我将在训练测试保持分裂后选择截止值,以保持数据泄漏最小。

稍后你会看到,39 是截止数,所以这是很接近的。

其他有趣的花絮——没有 MP3 的歌曲稍微更受欢迎。令人惊讶!

track_df[track_df['preview_url'].isna()]\['popularity'].hist(bins=100)

受欢迎程度得分(x 轴)与计数(y 轴)。作者图片

# before removal of nulls/duplicate links
track_df['popularity'].hist(bins=100)

受欢迎程度得分(x 轴)与计数(y 轴)。作者图片

# after removal of nulls/duplicate links
mp3s['popularity'].hist(bins=100)

受欢迎程度得分(x 轴)与计数(y 轴)。作者图片

如您所见,带有预览 MP3 的歌曲和收集的所有独特歌曲的人口分布大致相同。尽管上述关于没有 mp3 歌曲的事情是真实的,所以没有偏见被引入没有预览 MP3 的歌曲的删除。

艺术家:对普通大众的审视

只是为了满足我的好奇心,并把流行值放在上下文中,我观察了数据集中艺术家平均歌曲流行度的分布。我用同样的track_df_expart从上面看了一下。

许多我认为非常受欢迎的艺术家平均都在 70 分以下,而许多我从未听说过的艺术家平均受欢迎程度非常高…

一些相当著名的艺术家的平均受欢迎程度得分。作者图片

考虑到这一点,上面的 40 个估计似乎是为“流行”画的一条非常坚实的线。同样,我将只查看训练数据,但由于样本量较大,我预计它会有类似的分布。

歌曲:对重复价值的审视

我看到一些歌曲有不同的 mp3 预览 http 链接,但实际上是同一首歌,只是在不同的专辑中。我会进一步调查。

# find duplicates based on track name and the duration
# lots of repeats -- 652 in the dataset
mp3s[mp3s.duplicated(subset=['track', 'duration_ms'], keep=False)]['track'].value_counts() 6 'N the Mornin'                                     6
3 Headed Goat (feat. Lil Baby & Polo G)              6
Zulu Screams (feat. Maleek Berry & Bibi Bourelly)    4
How TF (feat. 6LACK)                                 4
durag activity (with Travis Scott)                   4
                                                    ..
Zu Besuch                                            2
50 in Da Safe (feat. Pink Sweat$)                    2
The Announcement (Sex Drugs & Rock and Roll)         2
Shotta Flow                                          2
I'LL TAKE YOU ON (feat. Charlie Wilson)              2
Name: track, Length: 652, dtype: int64

如你所见,有很多复制品。让我们来看看最上面的例子。

mp3s[mp3s['track'] == "3 Headed Goat (feat. Lil Baby & Polo G)"]

重复次数最多的歌曲,以表格形式。按作者分类的表格

哇,这首歌有 2 个单曲条目,2 个专辑条目,和 2 个豪华专辑条目,都有不同的预览 mp3 链接(没有图片)。他们都有不同的受欢迎程度!

虽然有不同专辑(单曲、专辑等)的重复歌曲。),它们通常具有不同的流行度分数,这仍然是有价值的信息。只要它们的流行度分数不同,我就保留这些重复。这仅删除了 26 个条目:

mp3s[mp3s.duplicated(subset=['track','duration_ms','popularity'], 
                     keep=False)]['track'].value_counts() 6 'N the Mornin'                           6
9ja Hip Hop                                3
Face Off                                   2
8 Figures                                  2
80's - Instrumental - Remastered           2
3 Headed Goat (feat. Lil Baby & Polo G)    2
Studio 54                                  2
SAME THING                                 2
One Punch Wulf                             2
50/50 Love                                 2
96 Freestyle                               2
6itch remix - feat. Nitro                  2
6565                                       2
Zero Survivors                             2
Jazz Hands                                 2
Just Mellow - Norman Cook 7'' Remix        2
Aries (feat. Peter Hook and Georgia)       2
60%                                        2
Sex Cells                                  2
Seven Day Hustle                           2
Ring (feat. Young Thug)                    2
8 Missed Calls                             2
Name: track, dtype: int64

目标和图书馆处理

现在我有了提取的数据集,我需要将我的目标处理成二进制编码的变量,我的 mp3 预览链接到带有 librosa 的 Mel 光谱图,然后我可以将这两者传递到神经网络中。

正如我前面提到的,流行度被简化为一个二元目标,以使我的模型更容易,因为受欢迎程度并不像受欢迎与否那样更具描述性。

Mel 频谱图是以可视格式表示音频数据的众所周知的工具。它将音频分解成频率(Mel 频率是一种更接近人类听觉的尺度)并显示频率随时间的分布。这样,关于节拍、音色等的模式。可以被模型检测到。

# making train test holdout splits
# train = 75%, test = 15%, holdout = 10%

X = mp3s.drop(columns=['popularity'])
y = mp3s['popularity']

X_pretr, X_holdout, y_pretr, y_holdout = train_test_split(X, y, test_size=0.10, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X_pretr, y_pretr, test_size=15/90, random_state=42)
print(X_train.shape, X_test.shape, X_holdout.shape)(12125, 9) (2426, 9) (1617, 9)

流行

39 岁似乎是个不错的截止年龄。

fig, ax = plt.subplots(figsize=(10,6))
y_train.hist(bins=95, ax=ax)
ax.grid(False)
ax.set(title='Popularity', xlabel='Score', ylabel='Song Count')

最终的流行截止(文本和截止标记添加后渲染)。作者图片

# defining popular as >= 39 and encoding (1 = popular)
y_train = y_train.map(lambda x: 1 if x >= 39 else 0)
y_train.value_counts(normalize=True)0    0.512
1    0.488
Name: popularity, dtype: float64y_test = y_test.map(lambda x: 1 if x >= 39 else 0)
y_test.value_counts(normalize=True)0    0.516076
1    0.483924
Name: popularity, dtype: float64y_holdout = y_holdout.map(lambda x: 1 if x >= 39 else 0)
y_holdout.value_counts(normalize=True)0    0.506494
1    0.493506
Name: popularity, dtype: float64

Mel 谱图处理

查看 github 以获得关于代码的更多细节,因为它对于一篇博客文章来说太详细了。

通常,工作流程是这样的:

  1. 从 http 链接获取. MP3
  2. 将. mp3 转换为。与 pydub。音频段
  3. 获取训练、测试和保持分裂的 mel 光谱图
  4. 缩放光谱图(简单的最小值-最大值,适合训练数据)

让我们来看一个示例波形。这是“行业宝贝(壮举。由 Lil Nas X 创作的 Jack Harlow),这是一首非常流行的歌曲,最初的流行度得分为 90。如你所见,它有 30 秒长,各种振幅。

# visualize waveform
y, sr = librosa.load('data/X_train/wav/10564.wav',duration=30)
fig, ax = plt.subplots(figsize=(10,6))
librosa.display.waveshow(y, sr=sr, ax=ax);
ax.set(title='Waveform', xlabel='Time (sec)', ylabel='Amplitude')
plt.savefig('images/waveform.png');

波形。作者图片

下面是对应的 Mel 谱图。有趣的是,随着歌曲的发展,你可以分辨出和弦、旋律和节奏(以及其他)的形状。这对模型非常重要。

fig, ax = plt.subplots(figsize=(10,6))
img = librosa.display.specshow(X_train[4118][:,:,0], x_axis='time', 
                               y_axis='mel', fmax=11025, ax=ax)
fig.colorbar(img, ax=ax, format='%+.1f dB')
ax.set(title='Mel Spectrogram', xlabel='Time (sec)', ylabel='Frequency (mels)')
plt.savefig('images/melspec.png');

相应的 Mel 谱图(未缩放)。作者图片

建模

光谱图是一种图像,因此该工作流程类似于使用神经网络的图像分类问题。

我从基线理解开始,然后转向传统的多层感知器。我用几个不同的卷积配置开发神经网络,然后用一个卷积神经网络结束,它包括一个门控递归单元,这是一种递归/递归神经网络。

当我增加模型的复杂性时,目标是用它来增加模型在光谱图中提取与目标相关的潜在模式的能力。当我介绍每个模型迭代时,我将详细解释我的思考过程。

基线理解

当我训练我的神经网络时,我需要知道一个未经训练的/无用的神经网络将如何运行。我将通过查看虚拟分类器来获得这些信息,该分类器简单地预测每首歌曲的多数类。

dummy = DummyClassifier(random_state=42)
dummy.fit(X_train, y_train)
dummypreds = dummy.predict(X_test)

print(f"Dummy Accuracy: {accuracy_score(y_test, dummypreds)}")
print(f"Dummy ROC-AUC: {roc_auc_score(y_test, dummypreds)}")Dummy Accuracy: 0.516075845012366
Dummy ROC-AUC: 0.5

不出所料,这次表现很差。对于只预测“不流行”的模型将如何表现,这些是需要记住的好数字。

多层感知器

现在我们已经了解了最坏的情况,我们可以进入最简单的神经网络。我认为单层感知器特别没用,所以我选择了稍微复杂一点但仍然简单的两层密集模型。

128 和 64 个节点是根据以前对这些类型的模型的经验相对随意地选择的。

input_shape = X_train.shape[1:]
batch_size = X_train.shape[0]/100# set random seed for reproducibility
np.random.seed(42)
set_seed(42)

# build sequentially
mlp = keras.Sequential(name='mlp')

# flatten input 3D tensor to 1D
mlp.add(layers.Flatten(input_shape=input_shape))

# two hidden layers
mlp.add(layers.Dense(128, activation='relu'))
mlp.add(layers.Dense(64, activation='relu'))

# output layer
mlp.add(layers.Dense(1, activation='sigmoid'))

# compile perceptron
mlp.compile(loss='binary_crossentropy',
            optimizer="adam",
            metrics=['accuracy', 'AUC'])

# take a look at model architecture
mlp.summary()Model: "mlp"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 flatten (Flatten)           (None, 165376)            0         

 dense (Dense)               (None, 128)               21168256  

 dense_1 (Dense)             (None, 64)                8256      

 dense_2 (Dense)             (None, 1)                 65        

=================================================================
Total params: 21,176,577
Trainable params: 21,176,577
Non-trainable params: 0
_________________________________________________________________mlp_history = mlp.fit(X_train, y_train, epochs=20, batch_size=30,
                  validation_data=(X_test, y_test))Epoch 1/20
346/346 [==============================] - 10s 28ms/step - loss: 0.8132 - accuracy: 0.4975 - auc: 0.5008 - val_loss: 0.8145 - val_accuracy: 0.5161 - val_auc: 0.4437
Epoch 2/20
346/346 [==============================] - 9s 27ms/step - loss: 0.7078 - accuracy: 0.5031 - auc: 0.5002 - val_loss: 0.6934 - val_accuracy: 0.4971 - val_auc: 0.5652
Epoch 3/20
346/346 [==============================] - 10s 28ms/step - loss: 0.7144 - accuracy: 0.5046 - auc: 0.5035 - val_loss: 0.6919 - val_accuracy: 0.5161 - val_auc: 0.5573
Epoch 4/20
346/346 [==============================] - 10s 29ms/step - loss: 0.6962 - accuracy: 0.5039 - auc: 0.5015 - val_loss: 0.6923 - val_accuracy: 0.5161 - val_auc: 0.5369
Epoch 5/20
346/346 [==============================] - 11s 30ms/step - loss: 0.6930 - accuracy: 0.5097 - auc: 0.5040 - val_loss: 0.6934 - val_accuracy: 0.5161 - val_auc: 0.5004
Epoch 6/20
346/346 [==============================] - 9s 27ms/step - loss: 0.6933 - accuracy: 0.5077 - auc: 0.5010 - val_loss: 0.6926 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 7/20
346/346 [==============================] - 10s 28ms/step - loss: 0.6929 - accuracy: 0.5112 - auc: 0.4996 - val_loss: 0.6927 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 8/20
346/346 [==============================] - 10s 28ms/step - loss: 0.6930 - accuracy: 0.5112 - auc: 0.4888 - val_loss: 0.6927 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 9/20
346/346 [==============================] - 10s 28ms/step - loss: 0.6930 - accuracy: 0.5112 - auc: 0.4966 - val_loss: 0.6927 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 10/20
346/346 [==============================] - 10s 28ms/step - loss: 0.6930 - accuracy: 0.5112 - auc: 0.4961 - val_loss: 0.6927 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 11/20
346/346 [==============================] - 10s 28ms/step - loss: 0.6930 - accuracy: 0.5112 - auc: 0.4947 - val_loss: 0.6927 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 12/20
346/346 [==============================] - 10s 28ms/step - loss: 0.6929 - accuracy: 0.5112 - auc: 0.4977 - val_loss: 0.6927 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 13/20
346/346 [==============================] - 10s 28ms/step - loss: 0.6930 - accuracy: 0.5112 - auc: 0.4937 - val_loss: 0.6927 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 14/20
346/346 [==============================] - 10s 28ms/step - loss: 0.6930 - accuracy: 0.5112 - auc: 0.4994 - val_loss: 0.6928 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 15/20
346/346 [==============================] - 10s 28ms/step - loss: 0.6929 - accuracy: 0.5112 - auc: 0.5009 - val_loss: 0.6927 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 16/20
346/346 [==============================] - 10s 28ms/step - loss: 0.6930 - accuracy: 0.5112 - auc: 0.4951 - val_loss: 0.6927 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 17/20
346/346 [==============================] - 10s 30ms/step - loss: 0.6930 - accuracy: 0.5112 - auc: 0.4899 - val_loss: 0.6927 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 18/20
346/346 [==============================] - 10s 28ms/step - loss: 0.6930 - accuracy: 0.5112 - auc: 0.4937 - val_loss: 0.6926 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 19/20
346/346 [==============================] - 10s 28ms/step - loss: 0.6930 - accuracy: 0.5112 - auc: 0.5026 - val_loss: 0.6929 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 20/20
346/346 [==============================] - 10s 28ms/step - loss: 0.6930 - accuracy: 0.5112 - auc: 0.4942 - val_loss: 0.6928 - val_accuracy: 0.5161 - val_auc: 0.5000visualize_training_results(mlp_history)

MLP 结果。作者图片

多层感知器并没有像预期的那样产生特别好的结果。在 5 个时期之后,即使是在训练数据中,指标也保持稳定。这告诉我们,这个模型很难找到任何有用的东西。测试准确性和 ROC-AUC 也与虚拟分类器匹配,因此它肯定是无用的。

卷积神经网络

让我们添加卷积功能来帮助它更好地解析视觉信息。一般而言,卷积架构用于图像处理,将越来越大的图像片段组合在一起。

这通常是通过卷积组件完成的,然后将卷积组件馈送到密集连接的感知器。

下面的架构改编自之前的一个项目,我们使用与这里类似的过程构建了一个流派分类器。该项目的 GitHub repo 可以在这里找到。

# set random seed for reproducibility
np.random.seed(42)
set_seed(42)

# build sequentially
cnn1 = keras.Sequential(name='cnn1')

# convolutional and max pooling layers with successively more filters
cnn1.add(layers.Conv2D(16, (3, 3), activation='relu', padding='same', input_shape=input_shape))
cnn1.add(layers.MaxPooling2D((2, 4)))

cnn1.add(layers.Conv2D(32, (3, 3), activation='relu', padding='same'))
cnn1.add(layers.MaxPooling2D((2, 4)))

cnn1.add(layers.Conv2D(64, (3, 3), activation='relu', padding='same'))
cnn1.add(layers.MaxPooling2D((2, 2)))

cnn1.add(layers.Conv2D(128, (3, 3), activation='relu', padding='same'))
cnn1.add(layers.MaxPool2D((2, 2)))

# fully-connected layers for output
cnn1.add(layers.Flatten())
cnn1.add(layers.Dense(128, activation='relu'))
cnn1.add(layers.Dropout(0.3))
cnn1.add(layers.Dense(64, activation='relu'))
cnn1.add(layers.Dropout(0.3))

# output layer
cnn1.add(layers.Dense(1, activation='sigmoid'))

# compile cnn
cnn1.compile(loss='binary_crossentropy',
            optimizer="adam",
            metrics=['accuracy', 'AUC'])

# take a look at model architecture
cnn1.summary()Model: "cnn1"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 conv2d (Conv2D)             (None, 128, 1292, 16)     160       

 max_pooling2d (MaxPooling2D  (None, 64, 323, 16)      0         
 )                                                               

 conv2d_1 (Conv2D)           (None, 64, 323, 32)       4640      

 max_pooling2d_1 (MaxPooling  (None, 32, 80, 32)       0         
 2D)                                                             

 conv2d_2 (Conv2D)           (None, 32, 80, 64)        18496     

 max_pooling2d_2 (MaxPooling  (None, 16, 40, 64)       0         
 2D)                                                             

 conv2d_3 (Conv2D)           (None, 16, 40, 128)       73856     

 max_pooling2d_3 (MaxPooling  (None, 8, 20, 128)       0         
 2D)                                                             

 flatten_1 (Flatten)         (None, 20480)             0         

 dense_3 (Dense)             (None, 128)               2621568   

 dropout (Dropout)           (None, 128)               0         

 dense_4 (Dense)             (None, 64)                8256      

 dropout_1 (Dropout)         (None, 64)                0         

 dense_5 (Dense)             (None, 1)                 65        

=================================================================
Total params: 2,727,041
Trainable params: 2,727,041
Non-trainable params: 0
_________________________________________________________________cnn1_history = cnn1.fit(X_train, y_train, epochs=20, batch_size=100,
                  validation_data=(X_test, y_test))Epoch 1/20
104/104 [==============================] - 152s 1s/step - loss: 0.6937 - accuracy: 0.5081 - auc: 0.4969 - val_loss: 0.6926 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 2/20
104/104 [==============================] - 152s 1s/step - loss: 0.6930 - accuracy: 0.5074 - auc: 0.5047 - val_loss: 0.6929 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 3/20
104/104 [==============================] - 151s 1s/step - loss: 0.6932 - accuracy: 0.5114 - auc: 0.4907 - val_loss: 0.6928 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 4/20
104/104 [==============================] - 151s 1s/step - loss: 0.6929 - accuracy: 0.5112 - auc: 0.5015 - val_loss: 0.6927 - val_accuracy: 0.5161 - val_auc: 0.5043
Epoch 5/20
104/104 [==============================] - 151s 1s/step - loss: 0.6930 - accuracy: 0.5112 - auc: 0.4978 - val_loss: 0.6927 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 6/20
104/104 [==============================] - 151s 1s/step - loss: 0.6929 - accuracy: 0.5112 - auc: 0.4984 - val_loss: 0.6926 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 7/20
104/104 [==============================] - 150s 1s/step - loss: 0.6928 - accuracy: 0.5111 - auc: 0.5071 - val_loss: 0.6927 - val_accuracy: 0.5161 - val_auc: 0.5032
Epoch 8/20
104/104 [==============================] - 148s 1s/step - loss: 0.6929 - accuracy: 0.5120 - auc: 0.5002 - val_loss: 0.6927 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 9/20
104/104 [==============================] - 149s 1s/step - loss: 0.6930 - accuracy: 0.5114 - auc: 0.4937 - val_loss: 0.6927 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 10/20
104/104 [==============================] - 152s 1s/step - loss: 0.6930 - accuracy: 0.5116 - auc: 0.4982 - val_loss: 0.6927 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 11/20
104/104 [==============================] - 152s 1s/step - loss: 0.6930 - accuracy: 0.5108 - auc: 0.4994 - val_loss: 0.6927 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 12/20
104/104 [==============================] - 155s 1s/step - loss: 0.6928 - accuracy: 0.5112 - auc: 0.5051 - val_loss: 0.6927 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 13/20
104/104 [==============================] - 150s 1s/step - loss: 0.6930 - accuracy: 0.5111 - auc: 0.4957 - val_loss: 0.6927 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 14/20
104/104 [==============================] - 150s 1s/step - loss: 0.6930 - accuracy: 0.5112 - auc: 0.4986 - val_loss: 0.6927 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 15/20
104/104 [==============================] - 153s 1s/step - loss: 0.6930 - accuracy: 0.5118 - auc: 0.4976 - val_loss: 0.6927 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 16/20
104/104 [==============================] - 150s 1s/step - loss: 0.6930 - accuracy: 0.5112 - auc: 0.4946 - val_loss: 0.6927 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 17/20
104/104 [==============================] - 149s 1s/step - loss: 0.6929 - accuracy: 0.5112 - auc: 0.4928 - val_loss: 0.6927 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 18/20
104/104 [==============================] - 155s 1s/step - loss: 0.6930 - accuracy: 0.5112 - auc: 0.4981 - val_loss: 0.6927 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 19/20
104/104 [==============================] - 150s 1s/step - loss: 0.6930 - accuracy: 0.5112 - auc: 0.4974 - val_loss: 0.6927 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 20/20
104/104 [==============================] - 149s 1s/step - loss: 0.6929 - accuracy: 0.5112 - auc: 0.4956 - val_loss: 0.6927 - val_accuracy: 0.5161 - val_auc: 0.5000visualize_training_results(cnn1_history)

第一个 CNN 结果。作者图片

第一个 CNN 做的不是很好。它似乎比多层感知器更具反应性,但不是以产生结果的方式。测试数据最终进入虚拟分类器值,所以这也是一个无用的模型。

CNN 第二轮

与上述完全相同的架构,只是没有第二个密集层。这和我前面提到的音乐流派分类项目中的第一次迭代模型是一样的。

在做那个项目的时候,我注意到如果你把 CNN 连接到一个单一的密集层,它会表现得更好。这可能不直观,但我想尝试一下。

# set random seed for reproducibility
np.random.seed(42)
set_seed(42)

# build sequentially
cnn2 = keras.Sequential(name='cnn2')

# convolutional and max pooling layers with successively more filters
cnn2.add(layers.Conv2D(16, (3, 3), activation='relu', padding='same', input_shape=input_shape))
cnn2.add(layers.MaxPooling2D((2, 4)))

cnn2.add(layers.Conv2D(32, (3, 3), activation='relu', padding='same'))
cnn2.add(layers.MaxPooling2D((2, 4)))

cnn2.add(layers.Conv2D(64, (3, 3), activation='relu', padding='same'))
cnn2.add(layers.MaxPooling2D((2, 2)))

cnn2.add(layers.Conv2D(128, (3, 3), activation='relu', padding='same'))
cnn2.add(layers.MaxPool2D((2, 2)))

# fully-connected layer for output
cnn2.add(layers.Flatten())
cnn2.add(layers.Dense(128, activation='relu'))

# output layer
cnn2.add(layers.Dense(1, activation='sigmoid'))

# compile cnn
cnn2.compile(loss='binary_crossentropy',
            optimizer="adam",
            metrics=['accuracy', 'AUC'])

# take a look at model architecture
cnn2.summary()Model: "cnn2"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 conv2d_4 (Conv2D)           (None, 128, 1292, 16)     160       

 max_pooling2d_4 (MaxPooling  (None, 64, 323, 16)      0         
 2D)                                                             

 conv2d_5 (Conv2D)           (None, 64, 323, 32)       4640      

 max_pooling2d_5 (MaxPooling  (None, 32, 80, 32)       0         
 2D)                                                             

 conv2d_6 (Conv2D)           (None, 32, 80, 64)        18496     

 max_pooling2d_6 (MaxPooling  (None, 16, 40, 64)       0         
 2D)                                                             

 conv2d_7 (Conv2D)           (None, 16, 40, 128)       73856     

 max_pooling2d_7 (MaxPooling  (None, 8, 20, 128)       0         
 2D)                                                             

 flatten_1 (Flatten)         (None, 20480)             0         

 dense_2 (Dense)             (None, 128)               2621568   

 dense_3 (Dense)             (None, 1)                 129       

=================================================================
Total params: 2,718,849
Trainable params: 2,718,849
Non-trainable params: 0
_________________________________________________________________cnn2_history = cnn2.fit(X_train, y_train, epochs=20, batch_size=100,
                  validation_data=(X_test, y_test))Epoch 1/20
104/104 [==============================] - 144s 1s/step - loss: 0.6943 - accuracy: 0.5069 - auc: 0.5027 - val_loss: 0.6929 - val_accuracy: 0.5161 - val_auc: 0.4861
Epoch 2/20
104/104 [==============================] - 140s 1s/step - loss: 0.6932 - accuracy: 0.5074 - auc: 0.4978 - val_loss: 0.6930 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 3/20
104/104 [==============================] - 139s 1s/step - loss: 0.6932 - accuracy: 0.5088 - auc: 0.5013 - val_loss: 0.6930 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 4/20
104/104 [==============================] - 139s 1s/step - loss: 0.6930 - accuracy: 0.5095 - auc: 0.4984 - val_loss: 0.6928 - val_accuracy: 0.5161 - val_auc: 0.5102
Epoch 5/20
104/104 [==============================] - 140s 1s/step - loss: 0.6929 - accuracy: 0.5121 - auc: 0.5119 - val_loss: 0.6926 - val_accuracy: 0.5161 - val_auc: 0.5000
Epoch 6/20
104/104 [==============================] - 139s 1s/step - loss: 0.6928 - accuracy: 0.5112 - auc: 0.5153 - val_loss: 0.6925 - val_accuracy: 0.5161 - val_auc: 0.5096
Epoch 7/20
104/104 [==============================] - 138s 1s/step - loss: 0.6927 - accuracy: 0.5144 - auc: 0.5148 - val_loss: 0.6915 - val_accuracy: 0.5474 - val_auc: 0.5670
Epoch 8/20
104/104 [==============================] - 137s 1s/step - loss: 0.6934 - accuracy: 0.5106 - auc: 0.5047 - val_loss: 0.6924 - val_accuracy: 0.5161 - val_auc: 0.5395
Epoch 9/20
104/104 [==============================] - 137s 1s/step - loss: 0.6925 - accuracy: 0.5115 - auc: 0.5183 - val_loss: 0.6931 - val_accuracy: 0.5161 - val_auc: 0.4675
Epoch 10/20
104/104 [==============================] - 137s 1s/step - loss: 0.6923 - accuracy: 0.5163 - auc: 0.5186 - val_loss: 0.6936 - val_accuracy: 0.4918 - val_auc: 0.5717
Epoch 11/20
104/104 [==============================] - 138s 1s/step - loss: 0.6926 - accuracy: 0.5178 - auc: 0.5260 - val_loss: 0.6917 - val_accuracy: 0.5260 - val_auc: 0.5594
Epoch 12/20
104/104 [==============================] - 140s 1s/step - loss: 0.6896 - accuracy: 0.5247 - auc: 0.5415 - val_loss: 0.6869 - val_accuracy: 0.5400 - val_auc: 0.5728
Epoch 13/20
104/104 [==============================] - 136s 1s/step - loss: 0.6876 - accuracy: 0.5356 - auc: 0.5477 - val_loss: 0.6862 - val_accuracy: 0.5396 - val_auc: 0.5769
Epoch 14/20
104/104 [==============================] - 136s 1s/step - loss: 0.6863 - accuracy: 0.5379 - auc: 0.5572 - val_loss: 0.6863 - val_accuracy: 0.5408 - val_auc: 0.5772
Epoch 15/20
104/104 [==============================] - 137s 1s/step - loss: 0.6854 - accuracy: 0.5460 - auc: 0.5625 - val_loss: 0.6850 - val_accuracy: 0.5433 - val_auc: 0.5788
Epoch 16/20
104/104 [==============================] - 143s 1s/step - loss: 0.6835 - accuracy: 0.5496 - auc: 0.5703 - val_loss: 0.6855 - val_accuracy: 0.5400 - val_auc: 0.5811
Epoch 17/20
104/104 [==============================] - 137s 1s/step - loss: 0.6878 - accuracy: 0.5363 - auc: 0.5500 - val_loss: 0.6854 - val_accuracy: 0.5412 - val_auc: 0.5762
Epoch 18/20
104/104 [==============================] - 136s 1s/step - loss: 0.6839 - accuracy: 0.5442 - auc: 0.5686 - val_loss: 0.6882 - val_accuracy: 0.5466 - val_auc: 0.5743
Epoch 19/20
104/104 [==============================] - 136s 1s/step - loss: 0.6833 - accuracy: 0.5532 - auc: 0.5707 - val_loss: 0.6856 - val_accuracy: 0.5425 - val_auc: 0.5710
Epoch 20/20
104/104 [==============================] - 136s 1s/step - loss: 0.6832 - accuracy: 0.5545 - auc: 0.5717 - val_loss: 0.6837 - val_accuracy: 0.5453 - val_auc: 0.5821visualize_training_results(cnn2_history)

第二个 CNN 结果。作者图片

哇,它居然学到了一点东西!整洁!它没有以伪分类器值结束,所以我可以肯定它确实学到了一些东西。最终,我们可以看到在以后的时期,模型开始过度适应训练数据,但是当训练结束时,测试分数稳定在一个好的点上。

递归神经网络

最后,我们将尝试这种风格的递归神经网络。我从这篇论文中提取了下面的架构,并将其改编为二进制分类。在那篇论文中,他们试图用这种方法对音乐艺术家进行分类。

像前两次迭代一样,它使用卷积组件开始,但随后输出被馈入门控递归单元,该单元“总结 2D 卷积之后的时间结构”。换句话说,它允许神经网络使用它从卷积部分学到的东西来拾取时间模式。

很整洁,是吧?

# set random seed for reproducibility
np.random.seed(42)
set_seed(42)

# build sequentially
rnn = keras.Sequential(name='rnn')

# convolutional and max pooling layers with successively more filters
rnn.add(layers.Conv2D(64, (3, 3), activation='elu', padding='same', input_shape=input_shape))
rnn.add(layers.MaxPooling2D((2, 2)))
rnn.add(layers.Dropout(0.1))

rnn.add(layers.Conv2D(128, (3, 3), activation='elu', padding='same'))
rnn.add(layers.MaxPooling2D((4, 2)))
rnn.add(layers.Dropout(0.1))

rnn.add(layers.Conv2D(128, (3, 3), activation='elu', padding='same'))
rnn.add(layers.MaxPooling2D((4, 2)))
rnn.add(layers.Dropout(0.1))

rnn.add(layers.Conv2D(128, (3, 3), activation='elu', padding='same'))
rnn.add(layers.MaxPool2D((4, 2)))
rnn.add(layers.Dropout(0.1))

rnn.add(layers.Reshape((80,128)))
rnn.add(layers.GRU(units=32, dropout=0.3, return_sequences=True))
rnn.add(layers.GRU(units=32, dropout=0.3))

# output layer
rnn.add(layers.Dense(1, activation='sigmoid'))

# compile cnn
rnn.compile(loss='binary_crossentropy',
            optimizer="adam", 
            metrics=['accuracy', 'AUC'])

# take a look at model architecture
rnn.summary()Model: "rnn"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 conv2d (Conv2D)             (None, 128, 1292, 64)     640       

 max_pooling2d (MaxPooling2D  (None, 64, 646, 64)      0         
 )                                                               

 dropout (Dropout)           (None, 64, 646, 64)       0         

 conv2d_1 (Conv2D)           (None, 64, 646, 128)      73856     

 max_pooling2d_1 (MaxPooling  (None, 16, 323, 128)     0         
 2D)                                                             

 dropout_1 (Dropout)         (None, 16, 323, 128)      0         

 conv2d_2 (Conv2D)           (None, 16, 323, 128)      147584    

 max_pooling2d_2 (MaxPooling  (None, 4, 161, 128)      0         
 2D)                                                             

 dropout_2 (Dropout)         (None, 4, 161, 128)       0         

 conv2d_3 (Conv2D)           (None, 4, 161, 128)       147584    

 max_pooling2d_3 (MaxPooling  (None, 1, 80, 128)       0         
 2D)                                                             

 dropout_3 (Dropout)         (None, 1, 80, 128)        0         

 reshape (Reshape)           (None, 80, 128)           0         

 gru (GRU)                   (None, 80, 32)            15552     

 gru_1 (GRU)                 (None, 32)                6336      

 dense (Dense)               (None, 1)                 33        

=================================================================
Total params: 391,585
Trainable params: 391,585
Non-trainable params: 0
_________________________________________________________________rnn_history = rnn.fit(X_train, y_train, epochs=30, batch_size=100,
                  validation_data=(X_test, y_test))Epoch 1/30
104/104 [==============================] - 948s 9s/step - loss: 0.6970 - accuracy: 0.5051 - auc: 0.4990 - val_loss: 0.6913 - val_accuracy: 0.5223 - val_auc: 0.5421
Epoch 2/30
104/104 [==============================] - 933s 9s/step - loss: 0.6932 - accuracy: 0.5156 - auc: 0.5170 - val_loss: 0.6940 - val_accuracy: 0.5161 - val_auc: 0.5173
Epoch 3/30
104/104 [==============================] - 953s 9s/step - loss: 0.6932 - accuracy: 0.5153 - auc: 0.5186 - val_loss: 0.6908 - val_accuracy: 0.5161 - val_auc: 0.5515
Epoch 4/30
104/104 [==============================] - 947s 9s/step - loss: 0.6906 - accuracy: 0.5260 - auc: 0.5372 - val_loss: 0.6883 - val_accuracy: 0.5289 - val_auc: 0.5532
Epoch 5/30
104/104 [==============================] - 960s 9s/step - loss: 0.6881 - accuracy: 0.5328 - auc: 0.5526 - val_loss: 0.6876 - val_accuracy: 0.5317 - val_auc: 0.5649
Epoch 6/30
104/104 [==============================] - 955s 9s/step - loss: 0.6874 - accuracy: 0.5369 - auc: 0.5558 - val_loss: 0.6875 - val_accuracy: 0.5441 - val_auc: 0.5646
Epoch 7/30
104/104 [==============================] - 951s 9s/step - loss: 0.6868 - accuracy: 0.5429 - auc: 0.5609 - val_loss: 0.6863 - val_accuracy: 0.5276 - val_auc: 0.5869
Epoch 8/30
104/104 [==============================] - 961s 9s/step - loss: 0.6896 - accuracy: 0.5329 - auc: 0.5498 - val_loss: 0.6909 - val_accuracy: 0.5293 - val_auc: 0.5688
Epoch 9/30
104/104 [==============================] - 966s 9s/step - loss: 0.6870 - accuracy: 0.5451 - auc: 0.5639 - val_loss: 0.6830 - val_accuracy: 0.5540 - val_auc: 0.5773
Epoch 10/30
104/104 [==============================] - 978s 9s/step - loss: 0.6857 - accuracy: 0.5511 - auc: 0.5658 - val_loss: 0.6826 - val_accuracy: 0.5556 - val_auc: 0.5868
Epoch 11/30
104/104 [==============================] - 963s 9s/step - loss: 0.6865 - accuracy: 0.5436 - auc: 0.5640 - val_loss: 0.6810 - val_accuracy: 0.5676 - val_auc: 0.5884
Epoch 12/30
104/104 [==============================] - 972s 9s/step - loss: 0.6835 - accuracy: 0.5565 - auc: 0.5793 - val_loss: 0.6790 - val_accuracy: 0.5750 - val_auc: 0.6003
Epoch 13/30
104/104 [==============================] - 960s 9s/step - loss: 0.6837 - accuracy: 0.5563 - auc: 0.5772 - val_loss: 0.6851 - val_accuracy: 0.5478 - val_auc: 0.5953
Epoch 14/30
104/104 [==============================] - 969s 9s/step - loss: 0.6816 - accuracy: 0.5640 - auc: 0.5863 - val_loss: 0.6784 - val_accuracy: 0.5709 - val_auc: 0.6051
Epoch 15/30
104/104 [==============================] - 961s 9s/step - loss: 0.6807 - accuracy: 0.5679 - auc: 0.5901 - val_loss: 0.6771 - val_accuracy: 0.5697 - val_auc: 0.6011
Epoch 16/30
104/104 [==============================] - 967s 9s/step - loss: 0.6793 - accuracy: 0.5719 - auc: 0.5957 - val_loss: 0.6834 - val_accuracy: 0.5668 - val_auc: 0.5973
Epoch 17/30
104/104 [==============================] - 961s 9s/step - loss: 0.6801 - accuracy: 0.5682 - auc: 0.5927 - val_loss: 0.6785 - val_accuracy: 0.5783 - val_auc: 0.5988
Epoch 18/30
104/104 [==============================] - 967s 9s/step - loss: 0.6793 - accuracy: 0.5716 - auc: 0.5932 - val_loss: 0.6931 - val_accuracy: 0.5602 - val_auc: 0.5948
Epoch 19/30
104/104 [==============================] - 972s 9s/step - loss: 0.6811 - accuracy: 0.5654 - auc: 0.5888 - val_loss: 0.6843 - val_accuracy: 0.5441 - val_auc: 0.5977
Epoch 20/30
104/104 [==============================] - 958s 9s/step - loss: 0.6759 - accuracy: 0.5754 - auc: 0.6050 - val_loss: 0.6764 - val_accuracy: 0.5779 - val_auc: 0.6049
Epoch 21/30
104/104 [==============================] - 972s 9s/step - loss: 0.6741 - accuracy: 0.5849 - auc: 0.6108 - val_loss: 0.6796 - val_accuracy: 0.5688 - val_auc: 0.5943
Epoch 22/30
104/104 [==============================] - 972s 9s/step - loss: 0.6706 - accuracy: 0.5895 - auc: 0.6195 - val_loss: 0.6898 - val_accuracy: 0.5581 - val_auc: 0.5958
Epoch 23/30
104/104 [==============================] - 968s 9s/step - loss: 0.6727 - accuracy: 0.5821 - auc: 0.6149 - val_loss: 0.6797 - val_accuracy: 0.5767 - val_auc: 0.6058
Epoch 24/30
104/104 [==============================] - 966s 9s/step - loss: 0.6705 - accuracy: 0.5877 - auc: 0.6203 - val_loss: 0.6753 - val_accuracy: 0.5717 - val_auc: 0.6027
Epoch 25/30
104/104 [==============================] - 974s 9s/step - loss: 0.6667 - accuracy: 0.5915 - auc: 0.6289 - val_loss: 0.6816 - val_accuracy: 0.5660 - val_auc: 0.6017
Epoch 26/30
104/104 [==============================] - 973s 9s/step - loss: 0.6664 - accuracy: 0.5997 - auc: 0.6308 - val_loss: 0.6820 - val_accuracy: 0.5730 - val_auc: 0.6105
Epoch 27/30
104/104 [==============================] - 971s 9s/step - loss: 0.6701 - accuracy: 0.5872 - auc: 0.6207 - val_loss: 0.6792 - val_accuracy: 0.5775 - val_auc: 0.5988
Epoch 28/30
104/104 [==============================] - 976s 9s/step - loss: 0.6669 - accuracy: 0.5958 - auc: 0.6298 - val_loss: 0.6959 - val_accuracy: 0.5350 - val_auc: 0.6071
Epoch 29/30
104/104 [==============================] - 1424s 14s/step - loss: 0.6650 - accuracy: 0.5948 - auc: 0.6336 - val_loss: 0.6758 - val_accuracy: 0.5787 - val_auc: 0.6059
Epoch 30/30
104/104 [==============================] - 971s 9s/step - loss: 0.6666 - accuracy: 0.6011 - auc: 0.6315 - val_loss: 0.6716 - val_accuracy: 0.5837 - val_auc: 0.6152visualize_training_results(rnn_history)

CNN-GRU 结果。作者图片

一点都不差!显然,没什么惊人的,但还是不算没什么。显然,这种类型的神经网络结构正在获取音乐中存在的某些东西。准确性提高了约 7–8 %, ROC-AUC 提高了约 0.1,这是一个明显的进步。这无疑显示了承诺,也许还有某种程度的效用。

保持测试

models_dict = {'Dummy': dummy, 'MP': mlp_dict, 'CNN 1': cnn1_dict, 'CNN 2': cnn2_dict, 
               'RNN': rnn_dict}acc_dict, roc_auc_dict = evaluate_holdout(models_dict, X_holdout, y_holdout)# numerical scores for all the models
print("Accuracy Scores:")
for model, score in acc_dict.items():
    print(f'{model}: {score}')
print("<>"*10)
print("ROC-AUC Scores:")
for model, score in roc_auc_dict.items():
    print(f'{model}: {score}')Accuracy Scores:
Dummy: 0.5065015479876162
MP: 0.5065015479876162
CNN 1: 0.5065015479876162
CNN 2: 0.5523219814241486
RNN: 0.5981424148606811
<><><><><><><><><><>
ROC-AUC Scores:
Dummy: 0.5
MP: 0.5
CNN 1: 0.5
CNN 2: 0.5742730226123023
RNN: 0.645901654431502# graph!

plt.rcParams.update({'font.size': 20})
fig, (ax1, ax2) = plt.subplots(2, figsize=(9,9))
plt.tight_layout(pad=2.5)
fig.suptitle('Holdout Data Results')

ax1.bar(list(acc_dict.keys()), list(acc_dict.values()))
ax1.set_ylabel('Accuracy')
ax1.set_ylim(0.5, 1)
ax1.tick_params(axis='x', rotation=45)

ax2.bar(list(roc_auc_dict.keys()), list(roc_auc_dict.values()))
ax2.set_ylabel('ROC-AUC')
ax2.set_ylim(0.5, 1)
ax2.tick_params(axis='x', rotation=45)
plt.show()

维持集的最终结果(+渲染后添加的更改)。作者图片

估价

正如你所看到的,即使我们使用了一些非常复杂和尖端的神经网络架构,我们也没有在我们选择的指标上获得大幅增加:准确性和 ROC-AUC。这表明模型部分的学习量有限,无法成功区分这两个组。

然而,我们确实获得了 59.8%的准确率和 0.646 的 ROC-AUC,这并不是什么都没有——这增加了 9.1%的准确率和 0.146 的 ROC-AUC。这是在维持集上,所以它确实发现了一些潜在的抽象模式,这很有趣也很有用。

如果我们让模型继续学习,它最终会过度适应训练数据。无论如何,如果我用酸橙分析这个,我肯定能获得一些真正的洞察力,了解它所拾取的模式到底是什么。

结论

我从一开始就知道这个建模项目是一个令人难以置信的雄心勃勃的事业。现在它已经完成了,我可以事后聪明地说,它做得并不令人惊讶地好,但它确实表现得令人钦佩。坦率地说,我甚至可以断言,由于我提出的限制,它注定会在某种程度上失败——也就是说,只使用音频样本。然而,我的结果显示有一个实用的金块。更重要的是,它展示了一个非常强大的概念证明。

创造一个对人耳来说有些困难的模型是一个挑战。不仅数据很难按照任何潜在的模式进行分组,而且受欢迎程度的目标也很模糊,本质上很难量化。流行有很大的影响,我知道音乐本身之外的因素对其结果有很大的影响。例如,许多糟糕的歌曲之所以流行,是因为与一个受欢迎的艺术家有关联。

最终我获得了 59.8%的准确率和 0.646 的 ROC-AUC,非常有希望(提高了 9.1%的准确率和 0.146 的 ROC-AUC)。尽管我做这个项目的方式必然受到限制,但我还是能够建立一个模型,该模型提取了流行歌曲的一些共同特征,并做出了一些成功的预测。

这改变了我的模型可能有用的方式。我着手建立一个模型,它可以被艺术家用作歌曲层面的工具,以判断某首特定歌曲是否有前途。我最终得到的是一个模型,可以传递一些可能的歌曲,并指出它认为会流行的几首歌曲。巧合的是,我的客户也经营着一家录音室,并与几十位艺术家有联系,他们都有一系列歌曲在制作中。在模型当前的形式下,我们可以传入一组歌曲样本让它进行预测,然后它选择的“流行”歌曲就可以被选择作为焦点。这个选择不会完美地决定哪些歌曲会流行,但它可以为艺术家和我的客户的录音室节省时间。

后续步骤

最终,通过足够的工作,我相信这个模型可以足够工具化,它将有助于评估个别歌曲。显然,除了我为这个项目收集的因素之外,还有其他影响受欢迎程度的因素,第一步是尝试将这些因素纳入模型。

在音频本身中,我们可以对样本进行不同的处理,并提取其他相关信息。也许更重要的是,我们可以看看音乐本身以外的因素。当我打电话给我的客户谈论模特的表现时,他说的第一句话是“我并不惊讶”,因为他非常了解看似随意的因素是如何导致一首歌的流行的。曝光率是一首歌是否/有多大影响力的重要因素。最好的例子是——我的客户在 Spotify 上的热门歌曲是抖音某些圈子里的一个迷因的原声带。其他非音频特征可以包括发行时间和艺术家信息。

此外,我在更高级的神经网络架构方面取得了相当大的成功,特别是门控递归单元(一种递归/递归神经网络)。研究其他架构并进一步调整现有架构可能会提高性能。

我在本笔记的早些时候提到过,数据集的一个显著限制是曲目列表似乎并不完整。为了更全面地捕捉当代嘻哈音乐的景观,我需要重新评估我的收集方法,以收集更多的歌曲。

除了扩展我所拥有的,我还想看看什么有效。我在介绍中提到过使用石灰(或类似的东西),但这次没有使用。这将是一个非常有用的工具,可以帮助我们了解对模型预测影响最大的因素是什么。尤其是一旦我开始合并更多/不同的输入、输入类型和架构,这将帮助我了解实际发生了什么。此外,这将建议艺术家,他们的歌曲的哪些重要部分是模型正在寻找的。

使用 Python 处理欧盟统计局的欧洲统计数据

原文:https://towardsdatascience.com/using-eurostat-statistical-data-on-europe-with-python-2d77c9b7b02b

欧统局是欧洲统计的主要切入点。本文将帮助您快速学习如何使用 Python 中的数据。

照片由卢克·切瑟Unsplash 拍摄

欧盟统计局是欧盟的统计机构。他们的任务是提供欧洲的统计数字和数据。所有的数据都可以浏览和下载。你可以在这里浏览他们完整的数据库。本文的最终代码可以在我的 GitHub 上以 easy_eurostat 包的形式获得。

数据集

所有表格都可以通过它们的识别码下载。此代码显示在数据库浏览器中数据集名称后面的括号中。请看下图中的红圈。下载的文件是 gzipped tar 格式的,包含一个制表符分隔的数据文件。

红圈表示数据集 ID(作者截图)

使用数据集识别码,可以从 URL 下载带有参数file=data/dataset.tsv.gz的格式https://ec.europa.eu/eurostat/estat-navtree-portlet-prod/BulkDownloadListing的数据集:

import os
import gzip
import urllib.requestdef download_eurostat_dataset(dataset):
    url = "[https://ec.europa.eu/eurostat/" + \
          "estat-navtree-portlet-prod/" + \
          "BulkDownloadListing?file=data/](https://ec.europa.eu/eurostat/estat-navtree-portlet-prod/BulkDownloadListing?file=data/)" + \
          dataset + ".tsv.gz"
    out_file = os.path.join("cache", dataset + ".tsv")
    if os.path.exists(out_file):
        return
    try:
        with urllib.request.urlopen(url) as resp:
            with gzip.GzipFile(fileobj=resp) as data:
                file_content = data.read()# Write to file in binary mode 'wb'
        with open(out_file, 'wb') as f:
            f.write(file_content)
    except Exception as e:
        print(e)download_eurostat_dataset("teilm020")

这个例子下载数据集teilm020(失业数字)并将制表符分隔的文件存储在缓存目录中。数据集保存在磁盘上,因此可以用作欧盟统计局数据库的缓存版本。gzipped 文件加载到内存中,并在写入前解压缩。在这种情况下,由于数据集的大小有限,在内存中工作不成问题。

存储的文件是制表符分隔的格式,除了第一列是逗号分隔的(让我们不要让它太容易)。深入研究 CSV 导入函数的工作方式,可以发现一种复杂的方式来接受逗号和制表符作为分隔符。因此,要将数据集加载到 Pandas 数据帧中,我们可以使用以下代码:

def get_eurostat_dataset(dataset):
    download_eurostat_dataset(dataset)
    df = pd.read_csv(os.path.join("cache", dataset + ".tsv"),
                     sep=",|\t| [^ ]?\t", na_values=":",
                     engine="python")
    df.columns = [x.split('\\')[0].strip(' ') for x in df.columns]
    return dfdf = get_eurostat_dataset("teilm020")

从周围的空格中清除列名(有时在列名的末尾有一个空格),最后一列有一个后缀“\time”,表示后面的列是时间序列。这个后缀也被删除。

产生的数据帧如下:

失业数据的数据框架(作者截图)

字典

前五列是代码,指代码列表。这些代码清单(字典)可以在欧盟统计局网站上的这里找到。列名等于该位置的.dic文件的名称。该文件包含由制表符分隔的键/值对。是缩写,是全文。在数据集中,编码列是第一个不是用制表符分隔而是用逗号分隔的列。遍历这些列名,我们可以用文本值逐个替换它们。

为了用它们的值替换代码,我们需要从 Eurostat 加载字典文件,并将其导入字典:

def download_eurostat_dict(dictionary):
    dictionary = dictionary.lower()
    url = "[https://ec.europa.eu/eurostat/" + \
          "estat-navtree-portlet-prod/BulkDownloadListing" + \
          "?sort=1&downfile=dic%2Fen%2F](https://ec.europa.eu/eurostat/estat-navtree-portlet-prod/BulkDownloadListing?sort=1&downfile=dic%2Fen%2F)" +\
          dictionary + ".dic"
    out_file = os.path.join("cache", dictionary + ".dic")
    if os.path.exists(out_file):
        return
    try:
        with urllib.request.urlopen(url) as resp:
                file_content = resp.read().decode('utf-8')
        with open(out_file, 'w') as f:
            f.write(file_content)
    except Exception as e:
        print(e)

def get_eurostat_dictionary(dictionary, inverse=False):
    download_eurostat_dict(dictionary)
    filename = os.path.join("cache", dictionary + ".dic")
    try:
        with open(filename) as f:
            d = {}
            for line in f:
                if len(line) > 1:
                    row = line.split('\t')
                    d[row[0]] = row[1].strip()
        if inverse:
            d = {v: k for k, v in d.items()}
        return d
    except:
        return {}

与数据集一样,副本被下载到用作缓存的本地文件夹中。不同之处在于,字典文件没有压缩,可以下载后存储。

字典文件每行包含一个由制表符分隔的键/值对。一些字典文件在每个键/值对之间包含一个空行,因此需要检查行长度以跳过空行。该值从' \n '字符中去除。

有了字典,数据帧中的代码可以被替换。Panda dataframe 有一个特定于此功能的方法replace()。当在以字典作为参数的列上调用它时,该列中的所有值都将被替换为与当前列值相关联的值作为键。

替换代码

get_eurostat_dataset方法扩展了用值替换代码列键的功能:

def get_eurostat_dataset(dataset):
    download_eurostat_dataset(dataset)
    df = pd.read_csv(os.path.join("cache", dataset + ".tsv"),
                     sep=",|\t| [^ ]?\t", na_values=":", 
                     engine="python")
    df.columns = [x.split('\\')[0].strip(' ') for x in df.columns]
    # Now get the dictionary columns (first line, comma sep)
    with open(os.path.join("cache", dataset + ".tsv")) as f:
        first_line = f.readline()
    codes = first_line.split('\t')[0].split('\\')[0].split(',')
    # Replace codes with value
    for c in codes:
        code_list = get_eurostat_dictionary(c)
        df[c] = df[c].replace(code_list)
    df = df.set_index(codes).transpose()
    return df

在返回之前,数据帧被转置,所有代码列都是多级列索引的一部分。起初,这似乎很复杂,但数据框架变得更容易使用。

读取相同的数据集现在会导致:

更新的数据框架(作者截图)

从数据集到图表

访问和使用多索引列的工作方式如下:

>>> # Get names of all levels
>>> print(df.columns.names)['s_adj', 'age', 'sex', 'unit', 'geo']>>> # Obtaining all unique values for level 2 ('sex')
>>> print(df.columns.get_level_values(1).unique())Index(['Females', 'Males', 'Total'], dtype='object', name='sex')>>> #Selecting unemployment data for all sexes combined
>>> df.loc[:, ('Seasonally adjusted data, ...',
               'Total', 
               'Total',
               'Percentage of population in the labour force')]

因此,以创建奥地利失业数据的条形图为例:

d = df.loc[:, ('Seasonally adjusted data, not calendar adjusted' +\
               ' data','Total', 'Total',
               'Percentage of population in the labour force')]
ax = d['Austria'].plot.bar()
ax.set_title('Unemployment in Austria')
ax.set_xlabel('Month')
_ = ax.set_ylabel('Unemployment percentage')

导致

奥地利失业率(作者截图)

因此,现在我们知道如何从欧盟统计局下载和准备数据,并使用它来创建图表。所有数据集都包含每个国家的数据,因此下一步是制作一个 choropleth 地图。

等值区域图

欧盟统计局还提供了地理数据。该地理数据包含国家边界、地区边界、城市等。详细程度称为 NUTS,NUTS 级别 0 是国家级别。通过增加这个数字,可以添加更多的细节,但是现在级别 0 已经足够了:

import geopandas as gpddef download_url(url, filename, binary=False):
    if os.path.exists(filename):
        return
    try:
        with urllib.request.urlopen(url) as resp:
                file_content = resp.read()
                if not binary:
                    file_content = file_content.decode('utf-8')
        with open(filename, 'wb' if binary else 'w') as f:
            f.write(file_content)
    except Exception as e:
        print(e)def download_eurostat_geodata():
    url = "[https://gisco-services.ec.europa.eu/distribution/v2/" +\
          "nuts/shp/NUTS_RG_20M_2021_3035.shp.zip](https://gisco-services.ec.europa.eu/distribution/v2/nuts/shp/NUTS_RG_20M_2021_3035.shp.zip)"
    out_file = os.path.join('data', 
                            'NUTS_RG_20M_2021_3035.shp.zip')
    download_url(url, out_file, binary=True)

我们使用比例为 1:20mio 的RG_20M文件。如果需要更多细节,还有 1:10mio、1:3mio 和 1:1mio 文件可供选择。替换文件名中的20M以下载另一台秤,例如 1:10 米欧秤的10M

下载的 zip 文件包含一个地理空间形状文件。ESRI 的这种文件格式是矢量化地理空间数据最常见的格式。它由表示地理空间项目(国家、道路等)的点、线和多边形组成。所有项目都可以有与之关联的属性。

GeoPandas 包是处理地理空间数据的优秀 Python 包。它是 Pandas 数据框架的扩展,增加了地理空间功能。它可以与 pip 或 Conda 一起安装:

pip install geopandasorconda install -c conda-forge geopandas

GeoPandas 能够以 zip 格式打开 shapefile:

def get_eurostat_geodata(lvl=0):
    download_eurostat_geodata()
    borders = gpd.read_file(os.path.join('data', 
                            'NUTS_RG_20M_2021_3035.shp.zip'))  
    return borders[borders['LEVL_CODE'] == lvl]

生成的数据帧如下所示。geometry列包含地理空间项目,在本例中,它们都是多边形(MULTIPOLYGON)。其他列是属性。国家代码在NUTS_IDCNTR_CODE列中。国家代码列对于一个国家的所有元素都是相同的,例如包括道路、河流和城市。NUTS_ID是一个独特的标识,当国家代码出现在该列时,该行包含国家边界。

GeoPandas 数据框架内容(作者截图)

为了检查数据帧的内容,我们可以用内置的plot()方法绘制它。

countries = get_eurostat_geodata()ax = countries.plot()
ax.set_xlim(2000000, 8000000)
ax.set_ylim(1000000, 5500000)

这导致了欧洲的一个阴谋:

欧洲示例图(作者截图)

x 和 y 限制裁剪掉了像加那利群岛这样遥远的部分。

plot()参数column被指定时,一个 choropleth 映射被创建。该列用于根据该列的值对国家/地区进行着色。首先,我们必须把失业数据框架和国家数据框架结合起来。国家数据框架使用国家缩写作为标识。这个缩写在包含国家全称的失业数据的导入阶段被删除。地理词典用于反向翻译。

d = df.loc[:, ('Seasonally adjusted data, not calendar adjusted' +\
                ' data','Total', 'Total',
                'Percentage of population in the labour force')]
d = d[d.index == '2022M08'].transpose().reset_index()
code_list = get_eurostat_dictionary('geo', inverse=True)
d['geo'] = d['geo'].replace(code_list)
d = countries.merge(d, right_on='geo', left_on='NUTS_ID')

首先,像以前一样过滤失业数据(所有性别的总人数)。选择 2022 年 08 月是因为它是包含所有国家数据的最后一行。此后,数据帧被转置,导致

每个国家的失业率(作者截图)

字典用于将国名翻译回它们的缩写。

失业数据和国家数据现在可以合并了。确保将失业数据合并到国家数据中,以确保生成的数据框确实是能够绘制地图的 GeoPandas 数据框。国家代码在countries数据框的NUTS_ID栏中。绘制 choropleth 图的最终代码是

ax = d.plot(column='2022M08', legend=True)
ax.set_xlim(2000000, 7000000)
ax.set_ylim(1000000, 6000000)
ax.set_title('Unemployment in the European Union')

导致

欧洲失业地图(作者截图)

恭喜你!现在,您已经拥有了使用欧盟统计局开放数据的所有工具,包括创建图表和地图。

easy_eurostat 软件包

我已经根据上面的代码创建了一个包。代码得到了更好的重用,并且添加了一些易于使用的方法参数。一个例子是用于download_eurostat_data方法的 de transpose参数。上面的代码显示,在下载过程中,表被转置,但它需要另一个转置,撤销第一个转置,以将其用于映射目的。布尔transpose参数定义是否在该方法中执行转置。

所有代码在我的 GitHub 上都有 easy_eurostat 包。按需随意使用。

使用这个包实现的这篇文章的例子:

from easy_eurostat import get_eurostat_geodata, get_eurostat_dataset# Plotting the Austrian unemployment numbers over time
df = get_eurostat_dataset("teilm020")
d= df.loc[:, ('Seasonally adjusted data, not calendar adjusted' +\
                ' data','Total', 'Total',
                'Percentage of population in the labour force')]
ax = d['Austria'].plot.bar()
ax.set_title('Unemployment in Austria')
ax.set_xlabel('Month')
_ = ax.set_ylabel('Unemployment percentage')# Making a choropleth map of unemployment numbers in Europe
df = get_eurostat_dataset("teilm020", replace_codes=True,
                          transpose=False, keep_codes=['geo'])
df = df[(df['age'] == 'Total') & (df['sex'] == 'Total')]df = pd.merge(countries, df, left_on='NUTS_ID', right_on='geo')
ax = df.plot(column='2022M08', legend=True)
ax.set_xlim(2000000, 7000000)
ax.set_ylim(1000000, 6000000)
ax.set_title('Unemployment in the European Union')

最后的话

我希望你喜欢这篇文章。要获得更多灵感,请查看我的其他文章:

所有的故事都可以在我的个人资料中找到。如果你喜欢这篇文章或我的任何其他文章,请点击下面的按钮!

免责声明:本文包含的观点和意见仅归作者所有。

使用 fastai 对日本汉字字符进行分类

原文:https://towardsdatascience.com/using-fastai-to-classify-japanese-kanji-characters-47d7edd4d569

如何处理不平衡数据

图片由来自 Pixabay 的 ogamiichiro3 提供

介绍

本文描述了如何使用 fastai 进行多类分类,特别是 3,017 类日本汉字字符。除了平假名和片假名,汉字字符是日语书写系统的主要部分。有成千上万个汉字字符,这使得探索多类分类模型成为一种具有挑战性的方法。所有的实验都在 Google Colab 中执行。我在本文中包含了各种代码片段,完整的代码可以在 GitHub 上找到。

文章焦点

  • 如何处理不平衡数据
  • 如何使用 fastai 进行日文字符分类

fastai 有什么优势?

Fastai 是由杰瑞米·霍华德和雷切尔·托马斯创建的开源库。它建立在 PyTorch 之上,是一个非常棒的工具,只需几行代码就可以创建执行机器学习模型。它主要依赖于像 ResNet152 这样的预训练模型的迁移学习,这些模型可以在新的任务和数据集上进行训练。为了了解这个库的一切,fast.ai 有一个非常棒的实用课程,完全免费。在他们的官方网站这里了解更多。

数据集

Kuzushiji-Kanji 数据集专注于草书日本字符,已在 Tarin Clanuwat 等人的本文中介绍。该数据集由人文科学开放数据中心(CODH)创建,基于日本国立文学研究所创建的 Kuzushiji 数据集。

本文介绍了三个数据集,其中 Kuzushiji-Kanji 数据集是最具挑战性的。总共有 140,426 幅图像(64 x 64 灰度)和 3,832 个独特的汉字字符。数据集以 tar 格式提供,可以使用 tar 文件提取。完整代码在我的笔记本00 _ Split Dataset中。

import tarfile
fname = "/MyDrive/Colab Notebooks/fastai/dataset/kkanji.tar"
tar = tarfile.open(fname)
tar.extractall()
tar.close

所有的图像都组织在文件夹中,这些文件夹标有汉字字符代码的 unicode。为了更容易处理数据,所有的图像和标签都用下面的函数转换成 NumPy ndarrays。然后 ndarrays 被保存为 npz 文件。

import pathlib
my_dir = "/content/kkanji"#Dummy black image/label to setup the ndarray
imgs = np.zeros((64,64), np.uint8).reshape(1,64,64) 
labels = np.array(['XXX'])for item in pathlib.Path(my_dir).glob('**/*.png'):
  image = np.array(Image.open(item)).reshape(1,64,64)
  imgs = np.concatenate([imgs,image])
  parent = os.path.dirname(item).split('/')[-1]
  labels = np.concatenate([labels,np.array([parent])])# Delete the dummy picture
imgs = np.delete(imgs,0,0)
labels = np.delete(labels,0,0)# Save as npz file
savez_compressed('/content/kkanj-imgs.npz', imgs)
savez_compressed('/content/kkanji-labels.npz', labels)

不幸的是,该数据集没有官方测试集,因此很难将结果与其他模型进行比较。因此,我们为这个实验创建了一个独特的测试数据集,使用 50/50 分割来分离可用数据。但是有一个问题:数据集非常不平衡,从 1766 个例子到每个类只有一个例子。这意味着对于一个平均的训练/测试分割,每个类至少需要两个例子。结果,815 个汉字字符必须从数据集中删除,因为这些类只有一个可用的例子。

使用标签(unicode)创建数据帧,并使用 value_counts() 计算标签在数据帧中出现的次数(出现次数)。

# Create a DataFrame with unicode and occurrence
unicode = pd.DataFrame(y,columns=['unicode'])
values = pd.DataFrame(unicode.value_counts(),columns=['occurrence'])
df = pd.merge(unicode,values,on ='unicode')
df

每个 unicode 类出现的数据帧

然后选择类别出现次数高于 1 的索引列表,并创建新的精简数据集。

# Indexes with occurrence > 1
select_indices = list(np.where(df['occurrence'] > 1)[0])# Reduce the dataset. Characters with occurrence of only 1 are excluded
X_reduced = X[select_indices]
y_reduced = y[select_indices]# Print shape
print(f"X_reduced shape: {X_reduced.shape}")
print(f"y_reduced shape: {y_reduced.shape}")

之后,使用 train_test_split 创建了一个训练/测试数据集。使用分层参数来确保所有等级的比例保持不变是很重要的。

from sklearn.model_selection import train_test_split
testSize = 0.5x_train, x_test, y_train, y_test = train_test_split(X_reduced, y_reduced, test_size=testSize, random_state=1, stratify=y_reduced)# Print shape
print(f"Training Images: {x_train.shape}")
print(f"Test Images: {x_test.shape}")

下面的图 1 显示了用于创建训练和测试数据集的步骤的概述。

图 1

GitHub 有 100 MB 的数据存储限制,因此所有数据集都可以在 Internxt 上下载:

原始数据集
kk Anji-Original-labels . npz
kk Anji-Original-imgs . npz

训练/测试数据集
kkan Ji-train-imgs . npz
kkan Ji-train-labels . npz
kkan Ji-Test-imgs . npz
kkan Ji-Test-labels . npz

接下来,重要的是处理数据集中严重不平衡的类的问题。

不平衡数据

不平衡的数据集可能会使创建良好的机器学习模型变得非常困难,因为通常,ML 算法是在假设每个类的示例数量相等的情况下设计的。杰森·布朗利对这个话题做了很好的概述。

克服这个问题的一个方法是对数据进行重新采样,正如艾米丽·波蒂拉杰·⁴.所描述的图 2 给出了重采样过程的概述。

图 2

最终数据集可以在这里下载:
kk Anji-final-test-imgs
kk Anji-final-test-labels
kk Anji-final-train-imgs
kk Anji-final-train-labels

数据重采样的完整代码在笔记本01 _ 重采样数据 中。使用的阈值是 150,这意味着所有的类都以这样一种方式被重新采样,即每个类恰好由 150 个例子组成。如果一个类的实例多于阈值,则使用 train_test_split 随机选择 150 个实例,并用于训练数据集(此处: df_upsampled )。其余的例子被插入到测试数据集 2 中(这里: df_rest_upsampled )。

# indexes of the labels
labelIndexes= df.index.to_numpy()# train and test split --> indexes
training, testing, _, _ = train_test_split(labelIndexes,labelIndexes, test_size=150, random_state=42)# 150 randomly selected rows  
df_upsampled = new_x_train.iloc[testing]# remaining rows, used for test dataset 2
df_rest_upsampled = new_x_train.iloc[training]

一个例子较少的类,通过随机复制已有的图片来扩充。来自 sklearn 库的 重采样 被用于此。

# resample to 150
df_upsampled = resample(df,random_state=42,n_samples=150,replace=True)

使用与用于训练输入的图像相同的图像可能不是创建执行机器学习模型的好主意。由于这个原因,在训练阶段后期使用数据扩充来转换训练图像。

利用这种方法,产生了新的重采样训练数据集,该数据集具有 452,550 幅图像和 3,017 个汉字字符类,其中每个类由正好 150 个例子组成。测试数据集 2 和测试数据集 1 被组合在具有 84,381 个图像的最终测试数据集中。

培养

模型训练的代码可在笔记本 02_kkanji 模型 中找到。为了准备数据和训练模型,fastai 需要一个 DataLoaders 对象,它包含一个训练数据加载器和一个验证数据加载器。这方面的挑战是如何使用 NumPy ndarrays 将数据输入 DataLoaders 对象。大多数 fastai 示例通过路径位置传递数据。这个数据加载器对象的创建是基于 Nghia Ho⁵.公司的代码

def make_dataloaders_from_numpy_data(image, label):
    def pass_index(idx):
        return idx def get_x(i):
        return image[i] def get_y(i):
        return label[i] dblock = DataBlock(
        blocks=(ImageBlock, CategoryBlock),
        get_items=pass_index,
        splitter=IndexSplitter(test_index), # stratified split
        get_x=get_x,
        get_y=get_y,
        item_tfms = Resize(IMAGESIZE),
        batch_tfms=aug_transforms(do_flip=False, p_affine=1) 
        )

   # pass in a list of index
    num_images = image.shape[0]
    dls = dblock.dataloaders(list(range(num_images)))
    return dlsdls = make_dataloaders_from_numpy_data(x_train, y_train)
# StratifiedShuffleSplit for the dataloaders object. This ensures that test and validation dataset include all Kanji charactersfrom sklearn.model_selection import StratifiedShuffleSplitsss = StratifiedShuffleSplit(n_splits=1, test_size=0.5, random_state=2018)for train_index, test_index in sss.split(x_train, y_train):
    X_trainfin, X_testfin = x_train[train_index],x_train[test_index]
    y_trainfin, y_testfin = y_train[train_index],y_train[test_index]

关于自定义数据块,需要注意几个重要事项:

  • splitter :默认情况下,DataBlock 随机分割数据用于训练和验证,因此训练集可能包含不在验证集中的类,反之亦然。为了避免这种情况,使用了 stratifiedShuffleSplit 为验证集创建一个索引拆分,并将其作为参数传递到 IndexSplitter 中。
  • item_tfms :所有图像的大小从 64 调整到 224,因为它改进了模型。
  • batch_tfms :使用 aug_transforms,图像被转换以创建一组更加多样化的图像,从而改进模型。我设置 do_flip=False 不翻转图像,设置 p_affine=1 ,这增加了应用每个变换和对称扭曲的概率。其他变换应用其默认值。

使用 show_batch 和参数 unique=True ,可以检查增强的训练图像看起来像什么。这些图像稍加变换,就可以得到一个具有更好预测能力的模型(图 3)。

# Display various versions of augmented transformed images
dls.train.show_batch(max_n=50, nrows=10, unique=True,cmap='gray')

图 3:增强图像

为了开始训练,一个带有 Resnet152 模型的 cnn_learner 被创建。训练了 10 个纪元,学习率为 0.01。

尝试了不同的设置,例如不同的学习率,滑动学习率在训练阶段给出了稍好的结果。最终,在 10 个时期的训练后,以 0.01 的固定学习率实现了最低的训练和有效损失。培训总时间约为 13 小时。

# Create a Learner object
learn = cnn_learner(dls, model, metrics=error_rate)# Train a model for 10 epochs
learn.fit_one_cycle(10, lr_max=0.01)

训练时代

结果

如下所示,该模型在测试数据集上生成了出色的结果。在 84,381 个样本中,有 4,830 个样本被错误分类,导致 F1 总分为 0.9416。

但是这个模型在不同的发生范围内会有怎样的表现呢?如前所述,数据集最初非常不平衡,因此,训练数据集中的每个类都被重新采样为 150 的固定大小。这意味着有些字符在训练数据集中只存在过一次(出现次数= 1 ),并且被复制了 149 次。因此,这就引出了一个问题,即该模型是否也能在被人为扩展的类上表现良好。

下面的表 1 给出了 F1 分数准确度示例计数数量独特汉字字符数量以及出现范围的概述。它显示了模型在这些不同范围内的表现。正如所料,F1 分数对于出现范围非常低的字符来说非常差。只有一个例子的字符类的 F1 值为 0.5207。但是,随着事件的增多,分数会提高。如果一个字符在训练数据集中出现至少六次,F1 值就会超过 0.91。这表明创建高性能的分类器不需要大量的例子。

表 1:结果概述

预测错误的汉字

图 4 显示了使用辅助功能 display_images 生成的错误预测汉字字符的图像。每个图像都标有实际字符、预测字符和预测概率。在某些情况下,可以看出实际的和预测的汉字是相似的,因此容易被错误分类。在其他情况下,这可能意味着标签不正确。

图 4:标签不正确的汉字

# Helper function to display Kanji and label
def display_images (x_figure,y_figure, prediction, probability):
  image_views = len(y_figure)
  columns = 10
  rows = np.ceil(image_views / columns)
  for counter in range(0,image_views):
    plt.subplot(rows,columns,counter + 1)
    plt.xticks([])
    plt.yticks([])
    plt.grid(False)
    plt.xlabel('A:' + str(char_df.iloc[y_figure[counter],3]) + ' P:' + str(char_df.iloc[prediction[counter],3]) + 'Pr:' + str(probability[counter]),fontproperties=fprop,fontsize=12)
    plt.imshow(x_figure[counter],cmap=plt.cm.binary)
# Display Kanji characters
figure, _ = plt.subplots(figsize=(20,10))
figure.suptitle('Incorrectly labeled Kanji - A: Actual Kanji / P: Predicted Kanji / Pr: Probability', fontsize=16)
figure.tight_layout(pad=3.0)
display_images(x_figure,y_figure,prediction, probability)

结论

如结果所示,不平衡数据集的问题可以通过对训练数据集进行重采样来简单地克服。还证明了由于 fastai 库,创建高性能模型不需要一个类的大量数据。

参考

Tarin Clanuwat,Mikel Bober-Irizar,Asanobu 北本,Alex Lamb,Kazuaki Yamamoto,David Ha,日本古典文学深度学习,可从https://arxiv.org/abs/1812.01718获得。

该数据集可从https://github.com/rois-codh/kmnist获得,受许可 CC BY-SA 4.0 许可,可自由共享和改编。

Jason Brownlee,对不平衡分类的温和介绍,机器学习掌握,可从https://machinelementmastery . com/what-is-unbalanced-Classification/,2021 年 12 月 6 日访问。

⁴·艾米丽·波蒂拉杰(沃特金斯),改善图像数据类别不平衡的 4 种方法,走向数据科学,可从https://towardsdatascience . com/4-ways-to-improve-class-unbalancy-for-image-data-9 adec 8 f 390 f 1获得,2021 年 12 月 2 日访问。

http://nghiaho.com/?p=2741发布,2021 年 11 月 3 日访问。

在警察数据上使用薄层

原文:https://towardsdatascience.com/using-folium-on-police-data-3207e505c649

对合唱团的混乱说“是”

在熨斗学校学习数据科学课程还不到一周,我就已经被口语化的知识所困扰了。真是。“即使在最好的情况下,我也认为警方的数据会很混乱。警察没有接受过将数据收集作为首要任务的培训,他们也不应该这样做。这很容易与在网飞、Spotify 或谷歌工作的整个数据收集团队形成对比——他们的首要任务是设计他们的数据,以优化分析。

然而,我认为凌乱的警方数据实际上是可视化的成熟之作,仅仅是因为它太杂乱,太复杂,太庞大,一般人无法理解。在这些情况下,可视化可以带来非常需要的直观理解。一个简单的 choropleth 可以成为我们的数据故事的开始。这个故事是一个普通纽约人应该知道的故事。如果你是一个普通的纽约人,并且更关心调查结果而不是代码,请随意向下滚动到调查结果

NYPD 和拦截搜身,一个样板:

在这个项目中,我使用了 2020 年 NYPD 的停车搜身数据。美国公民自由联盟已经彻底分析了这些数据。[1]他们发现在记录的 9544 次停车中,有 5791 次是无辜的(61%)。

他们还发现:

5367 人是黑人(56%)。2879 人是拉丁裔(30%)。856 人是白人(9%)。219 人是亚洲/太平洋岛民(2%)
132 人是中东/西南亚人(1.4%)

为什么这些人口统计数据与拦截搜身的基本知识相关,是因为拦截搜身在 2013 年被视为违宪,违反了第四修正案,禁止不合理的搜查和扣押。

如果你在谷歌上搜索“停下来搜身+违宪”,你首先会看到以下内容,来自维基百科:

在 2013 年 8 月 12 日裁决的 Floyd 诉纽约市一案中,美国地方法院法官 Shira Scheindlin 裁定,拦截搜身的使用方式违反了宪法,并指示警方采取书面政策,具体说明这种拦截的授权地点。

在纽约市,市长 Adams 是一名前警察队长,也是执法部门 100 名黑人关心组织的创始成员,该组织致力于打击非裔美国人社区及其与 NYPD 互动之间的不公正现象。埃里克·亚当斯还承诺恢复合法的盘查方式,他说“如果你有一个警察部门,你说你不能盘查,那不是一种负责任的警务方式…”**

昂贵的机构:

2022 年,“2022 财年 NYPD 费用预算提案为 54.4 亿美元,占全市总额的 5.5%。”【3】

图片作者(来自链接的纽约市议会预算第 2 页)。)

Business Insider 估计,2020 年 NYPD 的实际融资成本接近 100 亿英镑。 [4]纽约人的税收约占 NYPD 资金的 95%,是世界上资金最多的警察。普通纽约人了解他们城市的警察每天在做什么,这对纽约市的健康极其重要。但是信息有一个巨大的瓶颈。

公共警察数据的可访问性问题:

为了透明,NYPD 公开了大部分数据。[5]但是在拦截搜身数据的情况下,没有一个数据被汇总。取而代之的是,每一行信息代表一次独立的拦截和搜查。

(视频由作者提供。youtube 上的视频片段链接如下:https://www.youtube.com/watch?v=illObkP3TLQ

没有什么不寻常的公共数据集不是为了让普通人从中辨别意义而制作的。这是惯例。但除非我们采取措施,否则这对普通纽约人没什么好处。让我们把它放在地图上!

纽约人了解社区:

也许是因为经常看地铁图?

毕竟,普通纽约人现在可以看着这张地图并理解“拦截搜身对东哈莱姆区、东纽约和布朗克斯区(用红色箭头标出)的意义与它对斯塔滕岛、金融区和湾脊区(用绿色箭头标出)的意义截然不同。)"**

图片由作者提供。

代码:

注意,这篇博文是而不是来教你我没有从杰德·亚当斯的惊人博文“使用 learn 和 GeoPandas 进行地理制图的 Python 教程”中学到的任何东西[6]看看那个帖子如果你正在寻找一个惊人的叶子教程,我将继续谈论如何具体可视化 NYPD 数据。我将带你浏览我是如何制作上面这张地图的。

首先,打开你用来写代码的任何东西,我喜欢 Jupyter 笔记本。让我们导入熊猫来读取 excel 文件和叶子来制作地图。

下一步是查看数据的形状和列。

输出告诉我们,我们正在处理 9544 行、83 列以及所有列的名称。

出于我们的目的,让我们去掉任何不是 id 或 zipcode 的数据,只是为了让事情变得特别简单。我们总是可以在以后回到这个极其复杂的数据集进行进一步的研究(我们也应该这样做!)毕竟这张图不是我们过程的结束,只是开始。

然而,现在我们有了这个新的数据框,保存为 zipcode_frequency,我们需要做一些基本的重新格式化才能使用它。

上面的代码将返回我们的列的以下报告,该列中有多少非空值,以及该列的数据类型。

*<class 'pandas.core.frame.DataFrame'>
RangeIndex: 9544 entries, 0 to 9543
Data columns (total 2 columns):
 #   Column                  Non-Null Count  Dtype 
---  ------                  --------------  ----- 
 0   STOP_ID                 9544 non-null   int64 
 1   STOP_LOCATION_ZIP_CODE  9544 non-null   object
dtypes: int64(1), object(1)
memory usage: 149.2+ KB*

注意非空计数是如何显示STOP_IDSTOP_LOCATION_ZIP_CODE,9544 non-null值的。这向我们表明,我们的空值不会被简单的zipcode_frequency.fill_na(<params here>)填充。然而,当我们运行下面的代码时:

我们得到了以下输出:每次填写邮政编码的计数、唯一邮政编码的计数、最常出现的邮政编码以及最常出现的邮政编码的频率。

*count       9544
unique       179
top       (null)
freq         654
Name: STOP_LOCATION_ZIP_CODE, dtype: object*

这告诉我们,我们的空值在邮政编码列中填充了值“(null)”654 次。但是这占数据的百分比是多少呢?有多少次纽约市的警察在他们记录了一次拦截搜身的地方没有记录邮政编码?

这段代码返回我们的字符串,它的值被填充为我们要清除的数据的百分比。

*This data has 0.07 percent of its zipcode data missing.*

删除空值,还是将空值添加到 Yes 列,还是将空值添加到 No 列:这里有点两难。将来,我想回到这个项目上来,找出一种既能聚集数据,又能保留所有三种不同价值的方法。通常情况下,我不会保留空值,但是对于这种数据,空值可以说明它们自己的故事(某些邮政编码更倾向于在它们的报告中包含空值吗?)然而,为了这篇博文的目的,为了简单起见,为了对数据的内容有一个初步的了解,我将从数据中删除空值,并跟踪这些损失。

汇总熊猫的数据:

而回报显示的是我们的汇总数据!现在我们的行代表整个邮政编码,而不是一个实例。

请注意,STOP_LOCATION_ZIP_CODE 现在是索引,而 STOP_ID 不再与 ID 相关,因为它对聚合时该列中的所有 ID 进行计数。图片由作者提供。

但是在绘制地图之前,我们还有三件事要做。

  1. 为了清楚起见,我们将把STOP_ID重命名为FREQUENCY
  2. 我们将把邮政编码转换成字符串,而不是数值。这将允许 lyum 将邮政编码的频率分配给它们的邮政编码标签。
  3. 最后,从数据中移除那些"(null)"值,理解我们正在将偏差引入我们的过程,并改变我们的地图的含义。

制作 Choropleth 地图:

choropleth 是一个地理热图。Folium 是一个 python 地图制作库。我们需要两个基本的东西来制作一个 choropleth:作为 GeoJson 文件的地图的一些边界,以及我们的数据。我们已经准备好了数据。我将使用我在这里找到的 NYC GeoJson 文件:https://github . com/fed here/pui 2015 _ EC/blob/master/mam 1612 _ EC/NYC-zip-code-table-areas-polygons . geo JSON【T6,

关于 follow choropleths 的更多信息,我将直接向您推荐 follow 文档:https://python-visualization . github . io/follow/quick start . html

这是真的,理想情况下,我会让这些地图互动,这样人们可以点击地图上的每个邮政编码区,该邮政编码的统计数据就会弹出,我可能想使用 tableau,而不是 lyum 来制作这些地图。这肯定会增加可用性。有很多方法可以改进这些地图,以增加我们从这些地图中学到的知识。

但是我想说的是,只要快速浏览一下我们在这里一起制作的地图,让我们再快速浏览一下,就已经能告诉我们比查看原始 excel 文件更多的信息。

图片由作者提供。

拥抱问题,寻找故事:

虽然这不是我们梦想的仪表板,但它是一个奇妙的讲故事工具,因为它已经让我们对我们的数据产生了好奇:

  • 这些邮政编码如何与PHSYSICAL_FORCE_RESTRAINED_USED_FLAG列相关联,这一点我们还没有在数据中探索。有没有哪个邮政编码的人被限制的比例更高,这些邮政编码是不是发生最多拦截搜身的地方?
  • 这些邮政编码如何与我们数据中的ASK_FOR_CONSENT_FLAG列相关联?不同邮政编码的人们是否以同样的比率征求他们的同意?
  • OFFICER_IN_UNIFORM_FLAG专栏呢?当我们按邮政编码聚合时,该列会发生什么变化?(在某些邮政编码区有更多的便衣警察吗?)
  • 该地图与 2019 年的具有相同参数的地图相比如何?那里经常发生停车搜查的邮政编码和前一年一样吗?
  • 也许最引人注目的是:“东哈莱姆区、东纽约和布朗克斯区正在发生什么?”

我对杂乱数据和使用直方图的热情可以总结给各位数据科学家,因此: 就像我们使用直方图来建立对频率数据的基线理解一样,我们可以使用简单的直方图来建立对地理数据的基线理解。从那里,我们可以使用我们的 choropleth 来找出数据中我们想要探索更多的地方。

同意-问道:

使用类似的方法,我能够得到同意的百分比频率——通过邮政编码询问。(需要注意的是,ASK_FOR_CONSENT并不衡量在该数据集中征求同意和给予同意的时间,而只衡量征求同意的时间。在很多情况下,被要求同意的人会被拦下搜身,但没有得到同意,拦下搜身的事还是发生了。)

使用简单的除法,按邮政编码显示 2020 年 NYPD 要求某人同意停车搜身的频率(百分比)。图片由作者提供。

利用这些数据,我制作了以下地图:

这张地图显示了按邮政编码要求的同意率。它很好地显示了大多数邮政编码的同意率在 0%到 33%之间。图片由作者提供。

上面的地图很好地显示了 2020 年自愿停车搜身的真实范围,有两个小的邮政编码,一个在东皇后区,一个在扬克斯附近,有 100%的同意率。但是,如果我们想比较这些邮政编码中的同意询问率,以真正突出大部分数据在哪里呢?

在 Choropleth 图中使用四分位数范围:

以下代码:

会给我们以下的地图:

现在,这张地图显示的是同样的数据,但是大部分数据都是分散的。图片由作者提供。

重要的是要指出图例,它表明邮政编码必须在前 75 个百分点,同意率才能高于 18%。作为故事讲述者,我们有责任指出这一点,以免这张地图被误解为表明“2020 年整个纽约市的同意率普遍很高,因为地图上有一部分是深蓝色的。”我们应该或不应该认为“高”同意率是我不会在本博客中讨论的话题,但直觉上“高”可能与“大部分时间”有关,我们必须让我们的观众知道数据并非如此。

继续挖掘寻找一个故事:

仔细查看这张地图时,我想再次指出一些有趣的街区:

图片由作者提供。

红色箭头:这是斯塔滕岛的两个邮政编码,那里的拦截搜身频率相对较低,但询问同意率相对较高。湾脊显示了类似的模式。

紫色箭头:这是日落公园,相对于其频繁的拦截搜身,它的同意率相对较低。东哈莱姆区看起来也显示出这种模式。

橙色箭头:这是东纽约,与东哈莱姆区一样,这里的拦截搜身频率相对较高,但这里记录的征得同意率比东哈莱姆区高。

我不会进入这张地图上的每个街区,也不会深入研究我的进一步分析,这些都在我的 GitHub 上。然而,通过查看一些地图和数据,我有以下发现。请注意,我将在上下文中互换使用邮政编码和社区名称,但这也不是最准确的。

调查的结果

高频停车搜身邮政编码

10029:

图片来自维基百科

在东哈莱姆区,最常见的拦截搜身邮政编码,据报道 2020 年有 220 人被拦截搜身,征求同意率约为 11%(在第一和第二四分位数之间,因此略低于该数据集的绝对平均值。)

11207:

图片来自维基百科

东纽约是第二个最常见的拦截搜身的邮政编码,询问百分比约为 20%(也是最常见的拦截搜身的 75%)。)这与东哈莱姆区 11%的征得同意率大相径庭,因为有 169 人被拦下搜身。

11206:

图片来自维基百科

Bushwick 是第七个最常见的拦截搜身邮政编码,征求同意率约为 25%(也是大多数征求同意邮政编码的 75%)。)这个邮政编码是停车搜身最频繁的邮政编码之一。

11234:

图片来自维基百科

Flatlands/Bergen Beach 是第 14 个最常见的拦截搜身邮政编码,征求同意率约为 23%,而在隔壁的 Canarsie,邮政编码 11236,征求同意率不到一半,约为 10.5%。

11220:

图片来自维基百科

日落公园(Sunset Park)是第 15 个最常见的拦截搜身邮政编码,是前 25 个最常见的邮政编码中百分比最低的,询问率不到 4%。这是一个非常有趣的案例,因为前 25 个最常见的邮政编码中的所有其他邮政编码的同意询问率至少高出约 3 倍。在前 25 名中,下一个最需要同意的邮政编码是 10002(下东区),需要同意的比例约为 9%。

低频问同意邮编:

11232:

图片来自维基百科

这是布什终点站/格林伍德/日落公园。在 2020 年从来不需要征得同意的邮政编码中,这个邮政编码有最高的 40 人被拦截搜身。

11375:

图片来自维基百科

福里斯特山的同意询问率也很低,相比之下,它的拦截搜身频率约为 30 人,类似于日落公园。

10306:

图片来自维基百科

这个邮政编码是在斯塔滕岛,包括米德兰海滩。与隔壁 10309 年的 22 人被拦下搜身相比,这里 31 人被拦下搜身的同意率为 0%。但是当我们试图了解 10309 中有多少人被要求同意时,我们看到接近 20%的人同意。

11210:

图片来自维基百科

对于 2020 年在这部分平原被拦下搜身的大约 30 个人,没有人被问及他们是否同意。

总之:

我想挑战我的数据科学家同事,让他们具体地将警方数据可视化。我知道这很乱。我知道它很笨重。但是不要害怕空值!相反,要保持对丢失数据的追踪,做一个可视化,一次一列。在你开始理解数据之前,你不需要讲述数据背后的全部故事。让你的问题引导你的过程。用 choropleths 对警方的乱七八糟的数据说是!

参考资料:

[1] NYCLU,白思豪时代的拦截搜身 (2019),NYCLU

[2] E. Ngo,埃里克·亚当斯解释了他为什么支持合法使用的“拦截搜身” (2021),光谱新闻

[3]纽约市议会,向财政和公共安全委员会报告纽约警察局 2022 财年执行预算 (2021)

[4] A. Narishkin,K. Hauwanga,H. Gavin,J. Chou,A. Schmitz,警察的真正成本,以及为什么 NYPD 的实际价格标签是每年 100 亿美元 (2020),《商业内幕》

[5] NYPD,拦截、询问和搜身数据 (2020)

[6] J. Adams,使用 leav 和 GeoPandas 进行地理制图的 Python 教程 (2022),中等

在 Python 中使用函数包装器进行数据插补

原文:https://towardsdatascience.com/using-function-wrappers-for-data-imputation-in-python-c115751669bd

使用 Python Decorators 的自定义数据插补方法

图片由乔治·多尔吉克在像素上拍摄

在 Python 中,函数包装器(也称为装饰器)用于修改或扩展现有函数的行为。他们有各种各样的应用程序,包括调试、运行时监控、web 开发中的用户登录访问、插件等等。虽然通常应用于软件工程的环境中,但是函数包装器也可以用于数据科学和机器学习任务。例如,在开发数据处理管道和机器学习模型时,可以使用运行时监控和使用函数包装进行调试。

函数包装器在数据科学和机器学习领域的一个有趣应用是用于数据插补。数据插补是推断和替换数据中缺失值的任务。数据插补可以帮助减少偏差,提高数据分析的效率,甚至提高机器学习模型的性能。

有几种众所周知的技术用于输入数据集中的缺失值。最简单的方法是用零替换所有缺失的值。这是有限的,因为这种插补值可能无法准确反映现实,也不一定能减少偏差和提高数据分析的效率。在某些情况下,它实际上可能会引入大量的偏差,尤其是当一列中的大部分值缺失时。另一种方法是用平均值替换缺失的数值。虽然这比用零进行估算要好,但它仍然会导致偏差,尤其是当一列中有很大一部分数据丢失时。

另一种方法是建立一个机器学习模型,根据数据中其他列的值来预测缺失值。这种方法非常理想,因为即使在特定列中有很大一部分数据缺失的情况下,基于其他列的推断也应该有助于减少偏差。这种方法可以通过在类别级别应用机器学习模型来进一步改进。理论上,这可以用来相对较好地估算一整列缺失值。此外,类别和模型的粒度越细,这种方法应该工作得越好。

对于前两种方法,我们可以简单地使用 pandas fillna()方法用零、平均值和众数来填充缺失值。对于输入带有预测的缺失值,我们可以使用 Scikit-learn 包中的迭代输入模块。在这里,我们将看看如何使用函数包装器来设计每种方法的数据插补方法。

在这里,我们将使用葡萄酒杂志数据集,可以在这里找到。在知识共享许可(CC0:公共领域)下,这些数据可以公开自由使用、修改和共享。

对于我的分析,我将在 Deepnote 中编写代码,这是一个协作数据科学笔记本,使运行可重复的实验变得非常容易。

用零输入缺失值

首先,让我们导航到 Deepnote 并创建一个新项目(如果您还没有帐户,可以免费注册)。

让我们创建一个名为“数据 _ 估算”的项目,并在这个项目中创建一个名为“估算”的笔记本:

作者截图

接下来,让我们导入将要使用的包:

作者创建的嵌入

现在,让我们定义我们的函数,我们将使用零来估算缺失值。我们称之为简单插补。它将采用一个名为 input_function 的参数作为自变量。我们还将把输入函数传递给 functools 包装器中的 wraps 方法,我们将把它放在实际插补函数之前,称为 simple _ attribute _ wrapper:

def simple_imputation(input_function):
    @functools.wraps(input_function)
    def simple_imputation_wrapper(*args, **kwargs):

接下来,在 simple _ attraction wrapper 的范围内,我们指定了在输入函数返回的数据帧中输入缺失值的逻辑。

def simple_imputation_wrapper(*args, **kwargs):
    return_value = input_function(*args, **kwargs)
    print(" — — — — — — — Before Imputation — — — — — — — ")
    print(return_value.isnull().sum(axis = 0)).  return_value.fillna(0, inplace = True)
    print(" — — — — — — — After Imputation — — — — — — — ")   
    print(return_value.isnull().sum(axis = 0))
    return return_value

我们的插补函数(simple _ attachment _ wrapper)是在我们的 simple _ attachment 函数的范围内定义的。完整的功能如下:

作者创建的嵌入

接下来,让我们定义一个函数,该函数读入我们的 Wines 数据集并返回包含我们的数据的 dataframe:

def read_data():
    df = pd.read_csv(“wines_data.csv”, sep = “;”)
    return df

现在,如果我们调用 read_data 函数,它将具有简单插补方法的附加行为:

作者创建的嵌入

使用均值和模式输入缺失值

接下来,我们将定义一种数据插补方法,用平均值替换缺失的数值,用模式替换缺失的分类值。

我们将称我们的新函数为均值模式 _ 插补。它还会将 input_function 作为参数。我们还将把输入函数传递给 functools 包装器中的 wraps 方法,我们将把它放在我们实际的均值/模式插补函数之前,称为 mean mode _ attribute _ wrapper:

def meanmode_imputation(input_function):
    @functools.wraps(input_function)
    def meanmode_imputation_wrapper(*args, **kwargs):

接下来,在 meanmode _ 插补包装器的范围内,我们指定了在由输入函数返回的数据帧中输入缺失值的逻辑。这里,我们将迭代列类型,如果列类型为“浮点型”,则估算平均值,如果列类型为“类别”,则估算模式:

def meanmode_imputation_wrapper(*args, **kwargs):
    return_value = input_function(*args, **kwargs)
    print("— — — — — — — Before Mean/Mode Imputation — — — — — — — ")
    print(return_value.isnull().sum(axis = 0))
    for col in list(return_value.columns):
        if return_value[col].dtype == float:       
            return_value[col].fillna(return_value[col].mean(), inplace = True).       
        elif return_value[col].dtype.name == 'category':
           return_value[col].fillna(return_value[col].mode()[0], inplace = True) print(" — — — — — — — After Mean/Mode Imputation — — — — — — — ")
    print(return_value.isnull().sum(axis = 0))
    return return_value

完整的功能如下:

作者创建的嵌入

我们还需要修改 read_data 函数,使其接受列名字典,并将分类列和数字列类型分别指定为 category 和 float。我们通过迭代列名并使用我们的列和数据类型字典为每个列转换列类型来实现这一点:

for col in list(df.columns):
        df[col] = df[col].astype(data_type_dict[col])

完整的功能如下:

作者创建的嵌入

接下来,我们需要定义我们的数据类型映射字典:

data_type_dict = {'country':'category', 'designation':'category','points':'float', 'price':'float', 'province':'category', 'region_1':'category','region_2':'category', 'variety':'category', 'winery':'category', 'last_year_points':'float'}

我们可以将字典传递给 read data 方法:

df = read_data(data_type_dict)

我们得到以下输出:

作者创建的嵌入

用迭代输入器输入缺失值

对于我们的最终函数包装器,我们将使用 Scikit-learn 插补模块中的迭代插补器。iterative input 使用一个估计器,通过使用所有其他列中的值来迭代地估算一列中的缺失值。默认估计量是贝叶斯岭回归估计量,但这是一个可以修改的参数值。让我们从导入 IterativeImputer 开始:

from sklearn.impute import IterativeImputer

接下来,类似于前面的函数包装器,我们定义一个称为迭代 _ 插补的函数,它采用一个输入函数,在插补包装器之前调用 wraps 方法,并将插补包装器定义为迭代 _ 插补 _ 包装器。我们还存储输入函数的返回值,并打印插补前缺失值的数量:

def iterative_imputation(input_function):
    @functools.wraps(input_function)
    def iterative_imputation_wrapper(*args, **kwargs):                           
        return_value = input_function(*args, **kwargs)
        print("--------------Before Bayesian Ridge Regression Imputation--------------")
        print(return_value.isnull().sum(axis = 0))

接下来,在迭代插补包装范围内,我们定义包含分类列和数字列的数据帧:

return_num = return_value[['price', 'points', 'last_year_points']]
return_cat = return_value.drop(columns=['price', 'points', 'last_year_points'])

然后我们可以定义我们的插补模型。我们用 10 次迭代和一个随机状态集来定义我们的模型对象。我们还将使用默认估计量,即贝叶斯回归估计量:

imp_bayesian = IterativeImputer(max_iter=10, random_state=0)

然后,我们可以拟合我们的模型并估算缺失的数值:

imp_bayesian.fit(np.array(return_num))return_num = pd.DataFrame(np.round(imp_bayesian.transform(np.array(return_num))), columns = ['price', 'points', 'last_year_points'])

我们还将继续用模式输入分类变量。值得注意的是,分类值也可以用分类模型估算(我将把这个任务留到以后的文章中):

for col in list(return_cat.columns):
    return_cat[col].fillna(return_cat[col].mode()[0], inplace = True)
return_value = pd.concat([return_cat, return_num], axis=1)

完整的功能如下:

作者创建的嵌入

现在,我们可以将迭代插补装饰器放在 read_data 方法之前:

作者创建的嵌入

像以前一样调用我们的方法:

作者创建的嵌入

改进这种插补方法的另一种方法是在类别一级建立插补模型。例如,为每个国家建立一个估算器来估算缺失的数值。我鼓励你试验一下代码,看看你是否能做出这样的修改。在以后的文章中,我将介绍如何建立这些类别级插补模型,并探索其他插补方法。

这篇文章中的代码可以在 GitHub 上找到

结论

有各种各样的技术可用于数据插补。我们介绍的最简单的方法是用零替换缺失值。这种方法并不理想,因为它会导致很大的偏差,尤其是在有大量缺失值的情况下。更好的方法是用平均值估算缺失的数值,用模式估算缺失的类别值。虽然这是对用零输入缺失值的改进,但它可以使用机器学习模型来进一步改进。此外,在类别一级建立插补模型可以提供进一步的改进。

利用遗传算法进行新冠肺炎数据分析

原文:https://towardsdatascience.com/using-genetic-algorithm-for-covid-19-data-analysis-b8e05445108f

遗传算法是用于优化或搜索问题的随机过程。这是一种受自然选择和进化启发的算法。我们将使用遗传算法来寻找新冠肺炎 SEIR 参数。然后,我们将这个结果与网格搜索方法的结果进行比较。

图片作者。该图显示了 SEIR 参数从开始到结束是如何演变的。泰国的新冠肺炎数据是从 2022 年 3 月 13 日到 2022 年 4 月 12 日

有一些遗传算法库可供使用。不过算法比较直观,不太复杂。所以,我们决定从头开始实现这个算法。这样,我们可以添加现有库中没有的功能。

下面描述如何使用遗传算法来搜索新冠肺炎 SEIR 参数。

  • 为基因组定义一个接口。
  • 实现一个具体的基因组类来描述我们的领域问题。
  • 实现一个遗传算法。
  • 运行算法。
  • 调整算法参数。

基因组

基因组代表了我们问题的一个候选解决方案。基因组最初是随机的。然后,它们演变成越来越好的解决方案。在我们的代码中,基因组是一个 Java 接口。下面是代码。

该接口描述了哪些基因组操作必须可用。根据界面,它需要:

  • 交叉。两个基因组交配产生两个基因组。孩子们随机地继承了父母双方的特征。
  • 变异。单个基因组会随机改变一点点特性。
  • 计算适合度,稍后得到值。适应性表达了一个基因组如何适应一个环境。
  • 与另一个进行比较,以便算法知道哪个更好。通常,我们通过使用适应值来进行比较。

一个具体的类需要实现上述内容。

SEIR 基因组

这篇文章将向读者介绍使用 SEIR 模型进行新冠肺炎分析。

为了使用遗传算法搜索 SEIR 参数,我们需要将 SEIR 参数建模为基因组。

我们需要搜索以下七个参数来使用 SEIR 模型进行预测:

  • 希腊字母的第一个字母
  • 贝塔
  • 埃普西隆
  • 微克
  • 初始暴露计数
  • 初始感染计数
  • 初始恢复计数

前四个是 SEIR 模型参数。最后三个是初始条件。我们可以把这七个看作我们需要寻找的变量。
作为一个基因组,每个参数就是一个基因。在 Java 代码中,我们将每个基因存储在一个字节中。这意味着每个基因有 256 个可能的值。对我们来说应该够了。

将每个基因解码为参数值

对于每个参数,我们有一个最小值和一个最大值。下面的代码是将基因值解码为参数值。

以下代码是模型参数的范围。

交叉

一般交叉取两个基因组,产生两个基因组。每个孩子继承父母双方的财产。在 SEIR 模型中,交叉从第一个基因组中随机取出一个基因,并与第二个基因组中相同位置的基因交换。
这里是代码。

变化

通常,突变会稍微改变基因组的特性。在我们的例子中,它从七个基因中选择一个来改变。然后,它随机选择基因中的一个比特。最后,它翻转了这一点。
这里是代码。

适合度计算

适合度代表模型有多好。我们的适应度可以是模型预测和观察值之间的距离。
我们根据 SEIR 模型计算死亡人数。此外,我们还有实际值。然后,我们通过使用均方根误差(RMSE)来计算差异,并将其用作我们的适应度。这是我们需要最小化的值。
这里是代码。

整个班级的代码是这里是

初始人口工厂

我们需要一种方法来创建初始随机群体。由于 SEIR 基因组由 7 个基因组成,该代码为每个基因组创建了一个 7 字节的数组。每个字节都有一个随机值。然后,它从随机基因中实例化一个基因组。这个过程一直重复,直到我们得到了我们需要的基因组数量。这里是工厂代码。

遗传算法

概观

在高层次上,算法的工作方式如下:

  • 它产生一个随机的基因组群体。我们称这一代为零。
  • 它计算每个成员的适应度。最佳健身也是确定的。
  • 该算法从前一代生成下一代。通常,构建新模型有三个步骤:
  1. 它从上一代中选择成员。健康状况越好,每个人成为新一代的机会就越大。
  2. 然后,它选择两个成员并执行交叉。
  3. 最后每个成员都会变异。

一些算法有一个可选步骤。

  • 它从上一代中挑选一些最优秀的成员作为精英成员。
  • 当它完成了上述步骤,我们就有了新一代。
  • 该算法计算新一代的适应度。最佳健身也是确定的。
  • 该算法将继续产生新的一代,直到它达到预定的数目或者最佳适应度足够好。
  • 该算法也可以有事件监听器。当新一代可用时,它将调用指定的方法。这将允许我们收集更多的数据用于进一步的分析。

这里是的链接。

有几个参数需要在算法中指定。它会影响算法行为。

  • 群体大小是每个群体中基因组的数量。这一数字在所有世代都不会改变。
  • 最大代数是算法将运行的代数。
  • 具有相同基因组的最大世代。如果每代的最佳适应度在特定的迭代次数后没有提高,算法将在该次数后停止。
  • 交叉率是一个从零到一的数字。这个数字是两个基因组交叉的几率。
  • 突变率是一个从零到一的数字。这个数字是基因组变异的概率。
  • 锦标赛规模是锦标赛选择过程中随机选择的基因组数量。
  • 精英群体的规模是上一代最优秀成员的数量。

遗传算法的运行方法

下面是代码。

下面是 run 方法的工作原理:

  • 该算法创建特定数量基因组的初始群体。
  • 它计算所有基因组的适合度。
  • 它对人口进行分类。最佳健身第一。
  • 然后,它进入一个循环。每一次循环迭代都会产生新的一代。循环运行特定的次数。
  • 该算法从当前群体中构建一个新群体。稍后将详细介绍。
  • 它计算新一代的适应度。
  • 它将以前的精英基因组添加到新的基因组中。
  • 它对新一代进行分类。
  • 然后,如果需要,它会进行截断,以确保新的群体具有相同的大小。
  • 该算法检查当前最佳适应度是否在一定次数的迭代中保持不变。如果是这种情况,它退出循环。否则,它将继续。
  • 该方法返回具有最佳适应性的基因组。

构建新种群的遗传算法方法

该方法获取当前人口并返回新人口。下面是代码。

这个方法有三个步骤:

  • 它从以前的种群中选择基因组。
  • 它通过交叉创建了上一步的基因组列表。
  • 它通过突变创建了上一步的基因组列表。

接下来我们将讨论更多的步骤。

遗传算法的选择方法

该方法获取一个基因组列表并返回另一个列表。这是用来创造新一代的第一部分。

有几种方法可供选择。在我们的实现中,我们使用锦标赛选择。

下面是代码。

锦标赛选择的工作方式如下:

  • 锦标赛规模最初是指定的。数量通常很少,比如两个、三个或四个。
  • 该算法将从列表中随机选择指定数量的基因组。体能最好的人获胜。该算法将重复进行,直到获得所有数量的基因组。

遗传算法的交叉方法

该方法获取一个基因组列表,并返回另一个基因组列表。这是培养新一代的第二步。

下面是代码。

它是这样工作的:

  • 交叉率影响行为。假设利率是 0.6。这意味着总人口的 60%将会交叉。
  • 它列举了列表中的所有基因组。
  • 进行交叉需要两个相邻的基因组。它会产生两个子基因组。它会将孩子添加到结果列表中。
  • 该算法将交叉操作委托给基因组具体类。

遗传变异法

该方法获取一个基因组列表,并返回另一个基因组列表。这是培养新一代的第三步。

下面是代码。

它是这样工作的:

  • 突变率影响行为。假设利率是 0.8。这意味着 80%的基因组会变异。
  • 它列举了列表中的所有基因组。如果一个基因组要突变,算法会将突变委托给具体的基因组类。

SEIR 参数搜索的遗传算法

我们将 SEIR 参数搜索的遗传算法包装在 ModelParameterGASearch 类中。

该搜索方法将初始参数和观察到的死亡数据作为自变量,并将它们传递给遗传算法。它运行算法并返回模型参数和适应值。下面的代码展示了如何把所有的东西放在一起。

  • 前几行代码是遗传算法参数的声明。
  • 然后,我们检索在线新冠肺炎数据,并将案例转换成类似表格的结构。
  • 我们用案例数据运行算法。获得模型参数和 RMSE 值作为结果。

搜索和结果

新冠肺炎数据集介于 2022 年 3 月 13 日和 2022 年 4 月 12 日之间。我们对数据运行了遗传算法。然后,我们将观察到的死亡率和模型死亡率绘制如下。

图片作者。泰国 2022 年 3 月 13 日至 2022 年 4 月 12 日的每日死亡人数观察图和模型结果

从图中,我们可以注意到模型试图尽可能地符合实际数据。此外,RMSE 值足够低。

遗传算法搜索和网格搜索

我们对同一个数据集进行了网格搜索。我们绘制了两种方法的观察值和模型死亡率。

图片作者。泰国 2022 年 3 月 13 日至 2022 年 4 月 12 日观察到的每日死亡人数、遗传算法和网格搜索结果的图表

两种方法的模型线几乎相同。RMSE 值也非常接近。
下图显示了两种方法的搜索时间。

图片作者。使用网格搜索和遗传算法搜索 SEIR 参数的时间。

即使模型几乎完全相同,网格法也需要更多的搜索时间。

基因组的进化

我们绘制了自第一代以来 SEIR 基因组的变化。每个 SEIR 基因组包含七个参数。我们选择在图表中显示三个。此外,每个基因组的大小和颜色代表了适合度。

图片作者。该图显示了 SEIR 参数从开始到结束是如何演变的。泰国的新冠肺炎数据是从 2022 年 3 月 13 日到 2022 年 4 月 12 日

图表与封面图片相同。

我们可以观察到以下情况:

  • 它如何移动没有明确的模式。它随机曲折。
  • 前几招很长。越到最后越短。
  • RMSE 在开始的几个动作中下降得很快。后来 RMSE 下降得越来越慢。

我们在同一张图表上绘制了前几代和最后一代的模型。

图片作者。泰国 2022 年 3 月 13 日至 2022 年 4 月 12 日几代人的每日死亡人数观察图和模型结果

我们可以注意到以下情况:

  • 初始模型与观察到的数据相差很大。这是因为最初的一个来自随机人群。
  • 经过几次迭代后,模型得到了改进,更加接近实际数据。

方框图

为了可视化每一代的适应度分布,我们绘制了如下的箱线图:

图片作者。前几代的适合度分布。泰国的新冠肺炎数据是从 2022 年 3 月 13 日到 2022 年 4 月 12 日

图表显示了每一代人的健康分布。为了整理一下,我们只显示前几代。正如我们所料,第一种变化最大。随着进化的进行,变异不断减少。经过几千次迭代之后,我们认为算法收敛了。

遗传算法的参数调整

有几个参数可能会影响遗传算法的行为:

  • 交叉率
  • 突变率
  • 锦标赛规模

我们将探讨上述参数的影响。我们将按如下方式改变参数:

  • 交叉率将从 0.1 上升到 1.0
  • 突变率的范围从 0.1 到 1.0
  • 锦标赛规模从 2 人到 5 人不等

我们将根据这些参数运行算法,并观察适应度如何变化。我们将通过使用热图图表来可视化它。

下图显示了不同变异率和交叉率下的适应值。由于我们喜欢低适应值,所以我们更喜欢热图图表中蓝色最深的那个。

图片作者。不同变异率和交叉率下的适应值。

以下是观察结果:

  • 最佳适应度值是变异率为 0.8,交叉率为 0.7 时。
  • 很明显,当突变率为 0.7 或更高时,它在图表的右半部分工作得更好
  • 交叉率如何影响健康还不太清楚。0.4–0.7 的值应该可以。

下图显示了不同变异率和锦标赛规模下的适应值。

图片作者。不同变异率和锦标赛规模下的适应值。

以下是观察结果:

  • 最佳适应值是变异率为 0.8,锦标赛规模为 2 时。
  • 当锦标赛规模为 2 人时效果最佳。

两个图表的结论是:

  • 突变率应该在 0.7 以上。
  • 交叉率应该是 0.4 到 0.7
  • 锦标赛规模应为两人。

请记住,不同的问题可能会产生不同的结果。

结论

我们在本文中讨论了遗传算法。我们已经解释了算法如何工作以及如何实现它。

我们已经使用该算法来搜索最小化均方根误差的 SEIR 参数。其结果与网格搜索法相当。不过,它跑得更快。

我们还讨论了如何选择产生良好结果的算法参数。

尽管我们将遗传算法应用于 SEIR 建模问题,但我们相信,在许多应用中,该算法也能很好地工作。

所有的源代码文件都可以在 GitHub 这里获得。

引用:迪伦杰伦。泰国新冠肺炎数据。从 https://github.com/djay/covidthailand取回。它是在知识共享署名 4.0 国际许可下授权的

使用地理空间数据发现夏威夷的火山

原文:https://towardsdatascience.com/using-geospatial-data-to-discover-volcanoes-in-hawaii-67a15c18fa8f

实践教程

使用地理空间数据发现夏威夷的火山

使用地形数据和迁移学习的地理空间图像分割

马克·塞格拉特在 Unsplash 上的照片

P 语义图像分割的实际例子旨在以像素为基础识别特征,从无人驾驶汽车在卫星图像中检测船只。但是那些不使用这些传统图像数据集的实际应用呢?

在本文中,我将通过一个语义分割的地理空间示例,在这里我们将使用数字高程模型(DEM)数据绘制夏威夷的煤渣锥

我们将从快速介绍什么是煤渣锥,以及如何使用 QGIS 快速创建一个带标签的数据集开始。然后,在用新数据测试我们训练好的模型之前,我们将经历一些数据处理和模型选择/训练。

那么什么是煤渣锥呢?

根据美国地质调查局的定义:

“火山渣锥是最简单的火山类型。它们是由从一个单一的喷口喷出的凝结的熔岩颗粒和水滴构成的。…大多数煤渣锥在顶部都有一个碗状的坑……

但是一张照片胜过千言万语,所以这里有一些煤渣锥的漂亮照片:

杰夫·金Unsplash 上拍照

所以我们现在知道从空间的角度来看我们大概在寻找什么;侧面陡峭,中间有圆形弹坑的锥形特征。****

让我们继续看看这在我们的数据集中是什么样子。

数据加载和标记

我们将为此使用的数据集是来自夏威夷群岛最大岛屿(由美国地质调查局提供)的 DEM 数据(又名地形数据),在这里我们可以看到煤渣锥的形态。我们将绘制在休眠火山https://www.google.ru/maps/place/Mauna+Kea+Ice+Age+Natural+Area+Reserve/@19.8235951,-155.5825901,11.18z/data=!4m13!1m7!3m6!1s0x7bffdb064f79e005:0x4b7782d274cc8628!2sHawaii,+USA!3b1!8m2!3d19.8967662!4d-155.5827818!3m4!1s0x7953bdf95561b15d:0x582987cd20952faf!8m2!3d19.7915039!4d-155.4748835!5m1!1e4上发现的火山渣锥,以建立一个模型,该模型将用于绘制附近火山 华莱 上的火山渣锥。****

QGIS 是处理地理空间应用程序的一个很好的应用程序,将用于标记煤渣锥。加载作为. geotiff 的数据,并将输出更改为 10m 等高线有助于我们可视化煤渣锥( 提示:寻找紧密间隔的小同心等高线圆 ):

莫纳克亚山南翼煤渣锥等高线图(间隔 10 米)。图片作者。

基于我们之前定义的空间标准,我们可以看到煤渣锥,所以现在我们需要映射它们来创建我们的标签遮罩。为了简单起见,我们可以假设煤渣锥是圆形的,因此使用圆形多边形作为标签是可以的,并创建一个圆形的形状文件。我们可以开始加载 DEM geotiff 和 shapefile 标签:

**#Loading a geotiff shapefile
def readimage():
 print(‘reading image…’)
 with rasterio.open(path_image, ‘r’) as ds:
 arr = ds.read()
 arr = np.transpose(arr, (1, 2, 0))
 #Clip negative values to 0 
 arr = arr.clip(0)
 print(arr.shape)
 return arr#Loading the shapefile mask
def readmask(link):
print(‘reading mask…’)
geo = gpd.read_file(link)
with rasterio.open(path_image) as src:
raster = src.read()
geo = geo.to_crs(src.crs)
out_image, out_transform = rasterio.mask.mask(src, geo.geometry, filled=True)
masks = out_image[0,:,:]
#Set the mask labels to 1 and the rest to 0
masks[np.where(masks<=0)] = 0
masks[np.where(masks>0)] = 1
masks = masks.astype(np.int8)
masks = np.expand_dims(masks, axis=2)
return masks**

数据预处理

我们的 DEM 阵列覆盖了整个岛屿,所以我们需要在 Mauna Kea 周围做一些裁剪,在那里我们有我们的标记遮罩。此外,我们将需要我们的 DEM 阵列,这是 2D,并把它变成一个三维阵列与三个通道。我们为什么要这样做?稍后,我们将使用预训练编码器构建分割模型,这通常需要 3 通道图像输入(即彩色图像),因此这确保了兼容性:

**#Crop both the image and masks to the same Mauna Kea area
image = image[ymin:ymax, xmin:xmax,:]
masks = masks[ymin:ymax, xmin:xmax,:]

#Stack the image array and normalise
image = np.dstack((image, image, image))
image = (image — image.min())/(image.max() — image.min())

original_size = image.shape**

这给了我们 (4000,6500,3) 的最终形状。完美。让我们一起来看看并想象图像和面具:

高程数据(左)和带标签的煤渣锥形遮罩(右)的图像。图片作者。

既然我们已经知道如何复制同一个图像三次,我们可以通过应用一些滤镜来使这些图像更有用。我们将增强对比度,以便煤渣圆锥体与更平坦的环境相比更加突出,并且添加一个索贝尔滤镜来突出圆锥体的形状:

**#Contrast enhancing
image_eq = exposure.equalize_adapthist(image, clip_limit=0.05)#Sobel filter and normalising
image_sobel = filters.sobel(np.squeeze(image))
image_sobel = (image_sobel — image_sobel.min())/(image_sobel.max() — image_sobel.min())#concatenate standard image, equalised and sobel together
images = np.dstack((image[:,:,0], image_sobel[:,:,0], image_sobel[:,:,0]))**

现在我们已经对这三个通道做了一些额外的使用,我们现在需要将这个大数组细分为更小的数组,这些数组将被输入到我们的模型中。我们将拆分数组,使输出看起来像这样:

(样本数量、高度、宽度、通道数量)

**#Making image tiles
size = 224
step = int(size/2)patch_arr = skimage.util.view_as_windows(image, (size, size, layer.shape[2]), step = step)
output = patch_arr.reshape((-1,) + (size, size, layer.shape[2]))**

对输入图像和蒙版都重复这一过程,因此一切都是互补的。最后,留给我们以下输入:

来自莫纳克亚山 DEM 数据的图像切片和掩膜。图片作者。

这里有不少有趣的图片!我们最终的阵形是 (1938,224,224,3);没有太多构建模型的示例来进一步支持使用预训练模型的需求。最后一步是在训练集和验证集之间拆分数据:

**x_train, x_val, y_train, y_val = train_test_split(images, masks, test_size=0.2, shuffle=True, random_state=123)
print(x_train.shape)
print(x_val.shape)
print(y_train.shape)
print(y_val.shape)y_train = tf.cast(y_train, tf.float32)
y_val = tf.cast(y_val, tf.float32)**

模型建立和训练

现在我们准备建立一个模型!我们将使用 UNET 模型,这似乎是分段架构的首选,在 Keras 中提供了预训练的编码器。由于其在 imagenet 数据集上的性能,我选择使用 InceptionResNetV2 作为编码器,但是我们可以使用任何预训练的模型。

**input_shape = (size, size, 3)
inception = InceptionResNetV2(include_top = False, weights = 'imagenet', input_tensor = input_shape)inception.summary()
layer_names = [layer.name for layer in model.layers]**

使用我们的输入维度加载预训练的编码器,并获得编码器的层名称列表,这将有助于构建 UNET 的解码器部分,因为我们希望在编码器和解码器之间添加跳过连接。将“include_top”设置为 False 可以确保我们可以构建匹配的解码器。解码器会将编码器的输出恢复到原始输入图像尺寸,以便我们有一个匹配的分段掩码:

**x = Conv2DTranspose(num_filters, (2,2), strides=2, padding=’same’)(inputs)
x = Conv2D(size, 2, padding=’same’, dilation_rate = 1, kernel_initializer = ‘he_normal’)(x)
x = BatchNormalization()(x)
x = Dropout(0.2)(x)
x = Activation(‘LeakyReLU’)(x)**

将这些具有递减滤波器尺寸的块堆叠在一起,将构成我们的解码器,现在是跳过连接。跳过连接可以被添加到激活层,其中输入层大小对应于 224、112、56 和 28。根据使用的模型,层大小可能不直接对应这些尺寸,因此可能需要填充,如下例所示:

**skip_connection_2 = inception.get_layer(index = 3).output 
skip_connection_2 = ZeroPadding2D(( (1, 0), (1, 0))  (skip_connection_2)**

在这里,我们看到第四层(指数 3)需要填补,以符合我们想要的形状。

这些跳过连接将通过将指定激活层的输出与我们的转置解码器模块相连接来链接编码器和解码器,例如:

**x = Concatenate()([x, skip_connection_2])**

一旦解码器构建完成,我们使用 sigmoid 激活完成具有最终输出层的模型,因为我们的任务是二进制分割(即是否为煤渣锥)。

**outputs = Conv2D(1, (1,1), padding=’same’, activation=’sigmoid’)(last_layer)**

在编译模型时,损失函数应考虑类别不平衡,因为煤渣锥的数量相对于背景区域并不高,因此我们可以使用 Tversky 损失。这里有很多不同的损失函数可以使用,这有助于实验不同的损失。我们的度量将是 Jaccard 相似性系数,以测量我们的输入遮罩和分割遮罩之间的重叠像素的百分比。

**model.compile(tf.keras.optimizers.Adam(5e-5), 
loss= tversky_loss, metrics=[jaccard_coefficient],)reduce_lr = ReduceLROnPlateau(monitor=’loss’, factor=0.2, patience=15, verbose=1, mode=’max’, min_lr=1e-6)
early = tf.keras.callbacks.EarlyStopping(monitor=’val_loss’, patience=10)history = model.fit(x_train, y_train, epochs=100, batch_size = 32, validation_data = (x_val,y_val), callbacks = [reduce_lr, early])**

在 100 个时期的训练期间使用低学习率 1e-5 和 ReduceLROnPlateau 允许模型实现仅超过 80%的验证 IoU,这并不太坏。也许这可以进一步改善更定制的标签策略(即。没有将标签简化为圆圈)或不同的编码器模型。也可以尝试冻结不同的编码器层。

在另一座火山测试模型

移动到 Hualālai 火山,在那里我们将看到我们的分割模型如何识别煤渣锥。让我们来看看这座新火山:

Hualālai 火山的归一化 DEM。图片作者。

我们需要像处理 Mauna Kea 输入一样应用所有相同的预处理步骤,以便一切都与我们的模型兼容。在裁剪了这个区域,应用了滤镜,并把数组分成一个个小块之后,我们得到了一个(420,224,224,3)的形状。值得一提的是我们在平铺图像时不需要一个步骤,因为我们对增加样本的大小不感兴趣。

让我们快速看一下之前的输入图块示例:

来自 Hualālai DEM 数据的图像图块和遮罩。图片作者。

使用我们的模型进行预测后:

**y_pred = model.predict(tiled_Hualalai_image)**

重新组合图像拼贴以形成与原始图像尺寸相同的分割蒙版之前,我们可以快速浏览一些分割的拼贴:

具有输入图像块的预测分割掩模。图片作者。

预测似乎还可以,但看起来模型很难分割小煤渣锥,这可能是由于我们在莫纳克亚山训练集中的煤渣锥的大小(通常更大)。

我们现在可以查看叠加在原始输入图像上的分割图像。为了使煤渣锥更容易被发现,我们将在 sobel 过滤器图像上覆盖预测的分割蒙版(煤渣锥显示为小点/环):**

带 sobel 滤波器的 DEM 数据(左)与叠加分段掩膜(右)相结合。图片作者。

从整体上看,这幅图像看起来要好得多:

遮罩的位置大致与火山沿线的火山渣锥的西北-东南方向相匹配;

各种尺寸的煤渣锥体被分段(即不仅仅是大的);

每个煤渣锥体的形状与分段遮罩的形状大致匹配。

摘要

总的来说,我们已经完成了一个使用图像分割和地理空间数据集来识别夏威夷煤渣锥的新颖例子。由于我们可以从 DEM 输入数据中将分割蒙版转换回原始坐标,因此我们现在有能力将分割蒙版转换成真实数据集以供进一步分析(,即煤渣锥大小的分布情况如何?煤渣锥的密度如何变化?)。

感谢阅读!

【1】美国地质调查局,2013,USGS 13 弧秒 n20w156 1 x 1 度:美国地质调查局。数据来自美国地质调查局,国家地理空间计划。来自国家地图的数据是免费的,并且在公共领域。

在 python 中以编程方式使用 Google Cloud 机器学习 APIs 第 1 部分

原文:https://towardsdatascience.com/using-google-cloud-machine-learning-apis-programmatically-in-python-part-1-430f608af6a5

照片由米切尔罗Unsplash

介绍

从零开始开发机器学习系统并不是一件容易的事情。这非常耗费时间和资源。端到端机器学习系统的开发包括各种不同的阶段,例如:

  1. 问题理解
  2. 数据提取
  3. 数据清理
  4. 数据标记
  5. 特征选择
  6. 模型选择、训练和超参数调整
  7. 测试
  8. 部署和维护

所有这些阶段都有其自身的复杂性。google cloud 等云提供商提供的各种完全托管的服务大大降低了上述任务的难度。但是在生产中开发、部署和维护一个成熟的端到端 ml 管道仍然不容易。企业通常只为定制用例这样做。对于一般的用例,使用已经可用的机器学习 API 更有意义,也节省了时间和资源。

Google Cloud 提供了高度精确、完全托管的 API,可以解决大多数常见的机器学习问题。这些都非常容易使用。只需调用它们的端点,我们就可以获得高度准确的结果。只需接受输入,以可接受的格式创建一个请求,并将其发送到一个端点,我们就会得到作为响应的结果。无需担心数据处理、模型实验、部署问题或再培训问题。这些 API 也在大型数据集上进行训练,结果比我们自己构建和训练定制模型要准确得多。

谷歌云 ML APIs

以下是我试验过的机器学习 API 列表:

  • 视觉 API
  • 语音转文本 API
  • 文本到语音 API
  • 自然语言 API
  • 视频智能 API
  • 翻译 API

在文章的这一部分(第 1 部分),我将解释如何在 python 中使用上面列表中的前 3 个 API(视觉、语音到文本和文本到语音)。其余三个将在下一部分(第 2 部分)中讨论。

先决条件

  • 创建一个谷歌云人工智能笔记本实例。如果不知道如何做,请遵循这里给出的步骤。不需要创建一个非常强大的实例。“n1-标准-2”实例就可以了。
  • 在笔记本实例中通过 pip 安装以下 python 包

作者图片

  • 通过在 google Cloud shell 实例中运行下面给出的命令,创建一个 Google 云服务帐户及其相关密钥。最后一个命令创建了一个 key.json。从 notebook 实例访问 API 需要这个 JSON 文件。

  • API 在使用前需要启用。可以从谷歌云的 API 库这里启用 API

在 Medium 中,笔记本上的小部件无法在手机这样的小屏幕上正常显示。它们只有在笔记本电脑这样的大屏幕上才能正常显示。

1。视觉 API

Vision API 具有解决大多数常见图像处理问题的功能。继续之前,启用云视觉 API

我试验过的 Vision API 特性列表:

  • 文本检测

此功能使用 OCR 从输入图像中检测所有类型的文本。一个例子:

  • 标签检测

该特征标记输入图像。它主要检测并返回在查询图像中找到的所有内容的列表。一个例子:

  • 人脸检测

此功能检测查询图像中的所有人脸。返回检测到的面部的边界框的坐标以及每个检测到的面部的情感。还会返回面部标志注释。一个例子:

  • 地标检测

该特征检测查询图像中的位置。在下面的例子中,我给出了纽约时代广场的图片。并且地标检测端点能够正确地检测到它。

  • 标志检测

此功能可检测查询图像中的流行徽标。一个例子:

  • 网页检测

此功能通过互联网检索图像,这些图像与所查询的图像相似(完全、部分或视觉上)。(基于内容的图像检索)。一个例子:

2。语音转文本 API

顾名思义,Google Cloud 语音转文本 API 用于语音转录。API 需要在使用前在这里启用

在这个例子中,我自己创建了一个演讲录音并使用了它。这里有这里有

3.文本到语音 API

这个 API 用于文本到语音的转换。API 需要在使用前在这里启用

在将文本转换为语音时,这个 API 提供了许多不同的语音配置。男声和女声都有多种口音可供选择。

剩下的三个 API(自然语言、视频智能和翻译 API)将在本文的下一部分解释。

更新:

文章的第二部分也是最后一部分现已发表。请在下面找到它

https://medium.com/@subrahmanyajoshi123/using-google-cloud-machine-learning-apis-programmatically-in-python-part-2-8867d272edf0

更新二:

非常感谢 Chema Bescós 将整篇文章(两部分)翻译成西班牙语,以帮助西班牙读者。点击查看

谢谢

用 python 编程使用 Google Cloud 机器学习 APIs 第 2 部分

原文:https://towardsdatascience.com/using-google-cloud-machine-learning-apis-programmatically-in-python-part-2-8867d272edf0

照片由米切尔罗Unsplash

介绍

在本文的前一部分(第 1 部分)中,我们探讨了 Google Cloud Vision、文本到语音以及 python 中的语音到文本 API。

在本文的这一部分,我们将探索其余的 API。

  1. 翻译 API
  2. 视频智能 API
  3. 自然语言 API

注意

在 Medium 中,Jupyter 笔记本上的小部件无法在手机这样的小屏幕上正常显示。它们只有在笔记本电脑这样的大屏幕上才能正常显示。

谷歌云机器学习 API

1.翻译 API

顾名思义,这个 API 主要用于文本翻译。这还有一个附加功能,可以从文本中检测语言。使用之前,在谷歌云控制台中启用该 API。

我试验过的这个 API 的特性列表:

  • 翻译

这项功能可以将文本翻译成数百种语言。它也非常准确,因为该模型是由谷歌研究人员在谷歌的超大数据集上构建和训练的。此处提供了所有支持语言的列表

下面举一个小例子来说明它有多好。在这个例子中,我使用翻译 API 将一篇法语文章翻译成英语和日语。

翻译

  • 语言检测

该功能检测给定文本的语言。返回检测到的语言代码。这里有一个例子:

语言检测

2.视频智能 API

视频智能 API 用于常见的视频处理问题。使用之前,在谷歌云控制台中启用该 API。

我试验过的这个 API 的特性列表:

  • 镜头变化检测

此功能可检测视频镜头的变化。对于每一个镜头,它返回它的开始和结束时间。

在这个实验中,我使用了奥迪的一个广告视频,是从 youtube 上为其 e-tron 电动车拍摄的。响应太大,无法在笔记本中显示,必须转储到文本文件中。这里看一下的详细输出

镜头变化检测

  • 语音转录

这项功能可以转录视频中的语音。不需要手动将视频转换为音频,然后输入语音转文本 API。

我再次用奥迪的广告视频作为例子。

语音转录

  • 标签检测

此功能可检测视频中的标签。它检测在视频上能找到的一切并返回。

我也用奥迪的广告视频来做这个实验。

标签检测

  • 标志检测

检测视频中出现的所有流行标志。

在这个实验中,我使用了从 Youtube上截取的百事可乐的广告视频。

徽标检测

  • 人物检测

该功能检测视频中的人,并提供坐标以在检测到的人周围绘制边界框。给出每个检测到的人出现在视频中的时间范围。还给出附加的细节,如每个被检测的人穿什么样的衣服,衣服的颜色等。

我再次用百事可乐的广告视频作为例子。回复太大,无法在笔记本中显示,必须转储到文本文件中。这里看一下详细输出

人物检测

  • 人脸检测

该功能可以检测视频中的人脸。返回坐标以在每个检测到的人脸周围绘制边界框,每个检测到的人脸出现的时间范围,以及诸如此人是否戴着头带、此人是否在微笑等等。它还可以进行人脸标注。

我也用百事可乐的广告视频来做这个实验。这里的响应也太大,必须转储到一个文本文件中。这里看一下详细输出

人脸检测

3。自然语言 API

自然语言 API 用于解决一些常见的 NLP 问题。使用前在谷歌云控制台中启用该 API。

我试验过的功能列表:

  • 实体分析

该特性分析输入文本中的已知实体,如专有名词或普通名词。这里有一个例子:

实体分析

  • 情感分析

该特征检测输入文本的情感。一个例子:

情感分析

  • 语法分析

这个特性分析输入文本并返回语法细节。一个例子:

语法分析

  • 文本分类

该特征将输入文本分类成不同的已知类别。一个例子:

文本分类

一个示例用例

多个 API 可以组合起来解决一个复杂的问题。想象一个问题,我们需要从图像中读取文本并检测这些文本的情感。下面是如何仅使用 API 来解决这个问题:

  • 使用视觉 API 的文本检测功能从图像中读取文本。
  • 使用翻译 API 的语言检测功能检查文本是否为英文。
  • 如果文本不是英文,使用翻译 API 的翻译功能进行翻译。
  • 然后使用自然语言 API 的情感分析功能获取翻译文本的情感。

我们通过结合使用三种不同的 API 解决了这个问题。此外,请注意,为这个问题构建一个定制的机器学习系统将过于复杂。

尾注

这些 API 最好的一点是,我们让谷歌来处理一切。从构建和训练准确的模型到管理和扩展部署基础设施。我们唯一担心的两件事是发送请求和处理响应。

在上面的大部分实验中,我只用了一个例子。也可以在一个请求中发送一批示例,API 将在一个响应中返回每个示例的结果。

Python 客户端并不是访问这些 API 的唯一方式。这些也可以通过 curl 命令来访问。JSON 文件需要以特定的格式创建,包含示例的路径以及关于使用什么 API 特性/方法的规范。然后可以使用 curl 命令将其发送到 API 端点,响应将显示在命令行上。

这总结了我已经试验过的机器学习 API 及其特性的列表。正如我们在本文中看到的,大多数常见的机器学习问题都可以使用这些 API 本身来解决。

许多行业已经在使用这些 API 来自动化各种手工任务。关于他们的信息可以在 Google Cloud 的官网上找到。

谢谢

使用高阶容器高效处理 7,163(或更多)个数据帧

原文:https://towardsdatascience.com/using-higher-order-containers-to-efficiently-process-7-163-or-more-dataframes-964da8b0c679

理解大数据

使用高阶容器高效处理 7,163(或更多)个数据帧

介绍 StaticFrame 总线、批次、被子和纱线

作者图片

数据帧处理例程通常与表集合一起工作。这种集合的示例包括每年有一个表的多年数据集、每个股票行情表的历史股票数据,或者 XLSX 文件中多个工作表的数据。本文介绍了用于处理这种数据帧集合的新颖的“高阶”容器,这些容器在 Python StaticFrame 包中实现(一个提供不可变数据帧的 Pandas 替代品)。三个核心容器是BusBatchQuilt。将简要介绍第四种容器Yarn

这些高阶容器的替代方法是使用一个带有层次索引的大表。例如,多只股票的时间序列数据可能被编码在一个具有两层索引的表中,外层是股票代码,内层是日期。这种方法通常效率很低,因为即使只处理少量股票,也必须将整个表加载到内存中。

本文介绍了用于处理大型表集合的容器,从几千到几十万个表。通过延迟加载和可选的快速卸载提供了高效的内存使用。Bus(以电路中使用的总线命名),为延迟加载存储在磁盘上的表集合提供了一个类似字典的接口;收藏可以存储在 SQLite、HDF5、XLSX 或压缩的拼花、NPZ、pickle 或分隔文本文件中。Batch(以批处理命名)是一个表的延迟处理器,提供了一个简洁的接口来延迟定义应用于所有表的操作。Quilt(以拼凑而成的纺织品命名)是所有表的一种惰性虚拟连接,允许对分区数据进行操作,就像它是一个统一的单个表一样。

所有这三个容器都提供了相同的接口,用于读取和写入上面提到的多表存储格式(SQLite、HDF5、XLSX 或拼花、NPZ、pickle 或分隔文本文件的压缩存档)。这种一致性允许在不同的上下文中重用相同的数据存储。

这些工具是从我的工作环境演变而来的:处理金融数据和建立投资系统模型。在那里,数据集自然地按日期或特征划分。对于历史模拟,所需的数据可能很大。BusBatchQuiltYarn在这个领域提供了便捷高效的工具。像 Vaex 和 Dask 这样的核心外解决方案提供了处理大量数据的相关方法,尽管有不同的权衡。

虽然这些容器是在 StaticFrame 中实现的,但是这些抽象对于任何数据帧或表处理库中的应用程序都是有用的。StaticFrame 将数据帧简单地称为“帧”,这里将使用该约定。StaticFrame 按照以下约定导入:

>>> import static_frame as sf

容器概述

在演示使用这些容器处理成千上万个数据帧之前,我们将从处理两个数据帧开始。在创建了一个带有两个FrameBus之后,我们将使用同一个Bus来初始化一个BatchQuiltYarn。通过这种介绍,可以观察到共同的和不同的特征。

公共汽车

两个简单的Frame可以用来演示初始化一个BusBus.from_items()方法接受成对的标签和Frame;条目可以在元组中提供(如下所示),或者通过 Python 字典或相关容器上的items()方法提供。

>>> f1 = sf.Frame.from_element(0.5, index=('w', 'x'), 
    columns=('a', 'b'))
>>> f2 = sf.Frame.from_element(2, index=('y', 'z'), 
    columns=('a', 'b'))>>> bus = sf.Bus.from_items((('f1', f1), ('f2', f2)))>>> bus
<Bus>
<Index>
f1      Frame
f2      Frame
<<U2>   <object>

Bus可以被认为是FrameSeries(或有序字典),允许访问给定标签的Frame

>>> bus.loc['f2']
<Frame: f2>
<Index>     a       b       <<U1>
<Index>
y           2       2
z           2       2
<<U1>       <int64> <int64>

Bus的一个关键特性是,当从磁盘读取时,Frame被延迟加载:一个Frame仅在被访问时被加载到内存中,并且(通过max_persist参数)Bus可以被配置为仅保存对有限数量的Frame的强引用,急切地卸载超出该限制的最近最少使用的。这允许检查所有的Frame,同时限制Bus加载的总内存。

由于Bus支持读取和写入 XLSX 和 HDF5(以及许多其他格式),它提供了熊猫ExcelWriterHDFStore接口的功能,但具有更通用和一致的接口。同样的Bus可以用于编写 XLSX 工作簿(其中每一帧都是一张表)或 HDF5 数据存储,只需分别使用Bus.to_xlsx()Bus.to_hdf5()

此外,Bus也是创建BatchQuiltYarn的便利资源。

一批

Batch可以被认为是一个 label 和Frame对的迭代器。除了迭代器之外,Batch还是一个在每个包含的Frame上组合延迟操作的工具。Batch几乎暴露了整个Frame接口;当被调用时,方法调用和操作符应用程序在新返回的Batch中被延迟,在存储的迭代器上组成延迟执行。只有在使用Batch.to_frame()方法创建复合Frame或使用类似字典的迭代器(如Batch.keys()Batch.items()Batch.values)时,才会执行操作和迭代对。

一个Batch可以用来自一个Bus或者任何一对标签的迭代器Frame的条目来初始化。从一个Batch中调用的方法或操作符只是返回一个新的Batch。调用Batch.to_frame(),如下图所示,是急切执行组合sum()操作所必需的。

>>> sf.Batch(bus.items()).sum()
<Batch at 0x7fabd09779a0>>>> sf.Batch(bus.items()).sum().to_frame()
<Frame>
<Index> a         b         <<U1>
<Index>
f1      1.0       1.0
f2      4.0       4.0
<<U2>   <float64> <float64>

除了Frame方法外,Batch还支持使用Frame选择接口和操作符。下面,每个Frame取二次幂,选择“b”列,并返回新的Frame(组合两个选择):

>>> (sf.Batch(bus.items()) ** 2)['b'].to_frame()
<Frame>
<Index> w         x         y         z         <<U1>
<Index>
f1      0.25      0.25      nan       nan
f2      nan       nan       4.0       4.0
<<U2>   <float64> <float64> <float64> <float64>

Batch与熊猫DataFrameGroupByRolling对象相关,在配置分组或滚动窗口可迭代后,这些接口暴露这些组或窗口上的函数应用。Batch概括了这一功能,支持这些上下文,并提供标签和框架的任何迭代器的通用处理。

被子

一个Quilt用一个Bus(或Yarn)初始化,并要求指定虚拟连接哪个轴,垂直(轴 0)或水平(轴 1)。此外,Quilt必须为retain_labels定义一个布尔值:如果为真,Frame标签将作为外部标签保留在沿着连接轴的分层索引中。如果retain_labels为假,所有包含的Frame的串联轴上的所有标签必须是唯一的。以下示例使用先前创建的Bus来演示retain_labels参数。由于一个Quilt可能由数千个表组成,所以默认的表示形式会简化数据;Quilt.to_frame()可用于提供完全实现的表示。

>>> quilt = sf.Quilt(bus, axis=0, retain_labels=False)>>> quilt
<Quilt>
<Index: Aligned>      a b <<U1>
<Index: Concatenated>
w                     . .
x                     . .
y                     . .
z                     . .
<<U1>>>> quilt.to_frame()
<Frame>
<Index> a         b         <<U1>
<Index>
w       0.5       0.5
x       0.5       0.5
y       2.0       2.0
z       2.0       2.0
<<U1>   <float64> <float64>>>> quilt = sf.Quilt(bus, axis=0, retain_labels=True)>>> quilt.to_frame()
<Frame>
<Index>                a         b         <<U1>
<IndexHierarchy>
f1               w     0.5       0.5
f1               x     0.5       0.5
f2               y     2.0       2.0
f2               z     2.0       2.0
<<U2>            <<U1> <float64> <float64>

Quilt可以被认为是由许多较小的Frame组成的Frame,垂直或水平排列。重要的是,这个更大的Frame并没有急切地串联起来;更确切地说,根据需要从包含的Bus中访问Frame,提供了沿轴的表的惰性连接。

可以用max_persist参数配置Quilt中的Bus来限制保存在内存中的Frame的总数。这种显式内存管理允许在可能太大而无法加载到内存中的虚拟Frame上进行操作。

Quilt允许使用公共Frame接口的子集对这个虚拟连接的Frame进行选择、迭代和操作。例如,Quilt可用于迭代行和应用函数:

>>> quilt.iter_array(axis=1).apply(lambda a: a.sum())
<Series>
<Index>
w        1.0
x        1.0
y        4.0
z        4.0
<<U1>    <float64>

故事

这里仅简要描述的Yarn提供了一个或多个Bus的“虚级联”。与Quilt号一样,较大的容器不会被急切地连接起来。与Quilt的二维、单个Frame呈现不同,Yarn呈现了一个包含许多帧的一维容器,具有类似Bus的界面。与BusQuilt不同,Yarn的索引可以任意重新标记。这些功能允许异构的Bus在新标签下(如果需要)在单个容器中可用。

作为更高阶的容器,Yarn只能用一个或多个BusYarn初始化。一个Yarn甚至可以从同一个Bus的多个实例中创建,如果每个实例都有一个唯一的name:

>>> sf.Yarn.from_buses((bus.rename('a'), bus.rename('b')),
    retain_labels=True)
<Yarn>
<IndexHierarchy>
a                f1    Frame
a                f2    Frame
b                f1    Frame
b                f2    Frame
<<U1>            <<U2> <object>

共同特征和区别特征

BusBatchQuilt的一个共同特征是,它们都支持来自标签对和Frame的迭代器的实例化。当迭代器来自Bus时,Bus的延迟加载可以用来最小化内存开销。

这些容器都共享相同的基于文件的构造函数,如from_zip_csv()from_xlsx();每个构造器都有一个对应的导出器,例如分别是to_zip_csv()to_xlsx(),允许往返读写,或者从一种格式转换成另一种格式。下面的列表总结了所有三个容器中可用的基于文件的构造函数和导出函数。(Yarn作为Bus的聚合,只支持出口商。)

  • from_hdf5to_hdf5
  • from_sqliteto_sqlite
  • from_zip_csvto_zip_csv
  • from_zip_npzto_zip_npz
  • from_zip_pickleto_zip_pickle
  • from_zip_parquetto_zip_parquet
  • from_zip_tsvto_zip_tsv
  • from_xlsxto_xlsx

这些容器可以通过维度、形状和界面来区分。BusYarnFrame的一维集合;BatchQuilt呈现类似于Frame的二维界面。虽然Bus的形状等于Frame的数量(或者对于Yarn而言,等于所有包含的Bus中的Frame的数量),但是Quilt的形状取决于其包含的Frame及其定向轴。像发电机一样,Batch的长度(或形状)在迭代之前是未知的。最后,当BusYarn暴露一个类似于Series的接口时,BatchQuilt暴露一个类似于Frame的接口,分别在单独的Frame或虚拟连接的Frame上操作。

如下表所示,对于形状( xy )的mn,这些容器填充了一系列维度和接口。

  • Bus
    呈现尺寸:1
    近似界面:Series
    组成: n Frame
    呈现形状:( n ,)
  • Batch
    呈现维度:2
    近似接口:Frame
    组成:标签对的迭代器,Frame
  • Quilt
    呈现尺寸:2
    近似界面:Frame
    组成:1nFrameBusYarn呈现形状:( xny 或( xyn )
  • Yarn
    呈现尺寸:1
    近似界面:Series
    组成:mBusofnFrame
    呈现形状:( mn ,)

处理 7163 个数据帧

“庞大的股票市场数据集”包含 7163 个 CSV 表的集合,每个表代表一只美国股票的时间序列特征。“archive.zip”文件可从https://www . ka ggle . com/Boris marjanovic/price-volume-data-for-all-us-stocks-ETFs获得

打开归档文件后,我们可以从包含的“股票”目录中读取,并使用Batch创建股票数据的 zip pickle,用 ticker 标记,以便在后面的示例中快速阅读。由于有些文件是空的,我们还必须过滤掉没有大小的文件。根据硬件的不同,这种初始转换可能需要一些时间。

>>> import os
>>> d = 'archive/Stocks'
>>> fps = ((fn, os.path.join(d, fn)) for fn in os.listdir(d))
>>> items = ((fn.replace('.us.txt', ''), sf.Frame.from_csv(fp, index_depth=1)) for fn, fp in fps if os.path.getsize(fp))
>>> sf.Batch(items).to_zip_pickle('stocks.zip')

由于Bus是懒惰的,从这个新的 zip 存档初始化加载零个Frame到内存中。仅当明确请求时,才提供对数据的快速访问。因此,当Bus.shape属性显示 7163 个包含的Frame时,status属性显示零个加载的Frame s。

>>> bus = sf.Bus.from_zip_pickle('stocks.zip')
>>> bus.shape
(7163,)
>>> bus.status['loaded'].sum()
0

访问单个Frame只会加载那个Frame

>>> bus['ibm'].shape                                                                                                                
(14059, 6)>>> bus['ibm'].columns                                                                                                              
<Index>
Open
High
Low
Close
Volume
OpenInt
<<U7>

提取多个Frame会产生一个新的Bus从同一个存储中读取。

>>> bus[['aapl', 'msft', 'goog']]
<Bus>
<Index>
aapl    Frame
msft    Frame
goog    Frame
<<U9>   <object>
>>> bus.status['loaded'].sum()
4

用一个Batch我们可以对包含在Bus中的Frame执行操作,返回标记的结果。Batch.apply()方法可与lambda一起使用,将每个Frame的两列(“音量”和“关闭”)相乘;然后,我们用iloc提取最近的两个值,并产生一个复合的Frame,该索引来自原始的Bus标签:

>>> sf.Batch(bus[['aapl', 'msft', 'goog']].items()
    ).apply(lambda f: f['Close'] * f['Volume']).iloc[-2:].to_frame()
<Frame>
<Index> 2017-11-09         2017-11-10         <<U10>
<Index>
aapl    5175673321.5       4389543386.98
msft    1780638040.5600002 1626767764.8700001
goog    1283539710.3       740903319.18
<<U4>   <float64>          <float64>

为了对整个数据集进行观察,我们可以将Bus传递给Quilt。下面,一个空片用于强制一次加载所有的Frame以优化Quilt的性能。该形状显示了大约 1500 万行的Quilt

>>> quilt = sf.Quilt(bus[:], retain_labels=True)
>>> quilt.shape
(14887665, 6)

使用Quilt我们可以计算单日 7000 只证券的总交易量,而无需显式连接所有的Frame。下面使用的 StaticFrame HLoc选择器允许在分层索引中进行每深度级别的选择。在这里,我们选择了 2017 年 11 月 10 日的所有证券记录,涵盖所有证券交易所,并对交易量求和。

>>> quilt.loc[sf.HLoc[:, '2017-11-10'], 'Volume'].sum()
5520175355

类似地,iloc_max()方法可用于查找所有证券中交易量最大的证券的代码和日期。股票代码和日期成为由iloc_max()选择的Seriesname属性。

>>> quilt.iloc[quilt['Volume'].iloc_max()]
<Series: ('bac', '2012-03-07')>
<Index>
Open                            7.4073
High                            7.6065
Low                             7.3694
Close                           7.6065
Volume                          2423735131.0
OpenInt                         0.0
<<U7>                           <float64>

跨容器比较:相同的方法,不同的选择

前面的例子演示了用BusBatchQuilt加载、处理和检查“巨大的股票市场数据集”。跨容器比较可用于进一步说明这些容器的特征。首先,我们可以通过对每个容器应用相同的方法来观察三个不同的选择是如何返回的。其次,我们可以观察如何对每个容器使用三种方法来返回相同的选择。

head(2)方法从任何容器中返回前两行(或元素)。理解方法的输出在BusBatchQuilt之间的不同有助于说明它们的本质。

Bushead(2)方法调用返回一个新的由前两个元素组成的Bus,即“庞大的股市数据集”中的前两帧。

>>> bus.head(2)
<Bus>
<Index>
fljh    Frame
bgt     Frame
<<U9>   <object>

BatchBus中的每个Frame进行操作时,调用head(2)会从“庞大的股票市场数据集中的每个Frame中提取前两行调用to_frame()将这些提取连接到一个新的Frame中,然后只从其中选择两列:

>>> sf.Batch(bus.items()).head(2).to_frame().shape
(14316, 6)
>>> sf.Batch(bus.items()).head(2).to_frame()[['Close', 'Volume']]
<Frame>
<Index>                     Close     Volume  <<U7>
<IndexHierarchy>
fljh             2017-11-07 26.189    1300
fljh             2017-11-08 26.3875   3600
bgt              2005-02-25 11.618    97637
bgt              2005-02-28 11.683    90037
angi             2011-11-21 15.4      469578
angi             2011-11-22 16.12     202970
ccj              2005-02-25 20.235    3830399
ccj              2005-02-28 19.501    3911079
uhs              2005-02-25 22.822    4700749
uhs              2005-02-28 23.056    1739084
eqfn             2015-07-09 8.68      489900
eqfn             2015-07-10 8.58      44100
ivfgc            2016-12-02 99.97     5005
ivfgc            2016-12-05 99.97     6002
achn             2006-10-25 11.5      0
achn             2006-10-26 12.39     361420
eurz             2015-08-19 24.75     200
...              ...        ...       ...
cai              2007-05-16 15.0      3960000
desc             2016-07-26 27.062    1015
desc             2016-07-27 27.15     193
swks             2005-02-25 7.0997    1838285
swks             2005-02-28 6.9653    2737207
hair             2017-10-12 9.92      2818561
hair             2017-10-13 9.6       294724
jnj              1970-01-02 0.5941    1468563
jnj              1970-01-05 0.5776    1185461
rosg             2011-08-05 181.8     183
rosg             2011-08-08 169.2     79
wbbw             2013-04-12 13.8      162747
wbbw             2013-04-15 13.67     126845
twow             2017-10-23 16.7      10045
twow             2017-10-24 16.682    850
gsjy             2016-03-07 25.238    14501
gsjy             2016-03-08 25.158    12457
<<U9>            <<U10>     <float64> <int64>

最后,Quilt表示所包含的Frame就好像它们是一个单独的、连续的Frame。调用head(2)返回虚拟Frame的前两行,用层次索引标记,其外部标签是Frame的标签(即 ticker)。

>>> quilt.head(2)[['Close', 'Volume']]
<Frame>
<Index>                     Close     Volume  <<U7>
<IndexHierarchy>
fljh             2017-11-07 26.189    1300
fljh             2017-11-08 26.3875   3600
<<U4>            <<U10>     <float64> <int64>

跨容器比较:相同的选择,不同的方法

接下来,我们将展示如何对每个容器使用三种方法来返回相同的选择。虽然上面使用的head()方法是一种预配置的选择器,但是所有容器都支持全系列的lociloc选择接口。以下示例提取了 1962 年 1 月 2 日以来的所有“打开”和“关闭”记录。

为了用Bus执行这个选择,我们可以遍历每个Frame并选择目标记录。

>>> for label, f in bus.items():
...     if '1962-01-02' in f.index:
...         print(f.loc['1962-01-02', ['Open', 'Close']].rename(label))
...
<Series: ge>
<Index>
Open         0.6277
Close        0.6201
<<U7>        <float64>
<Series: ibm>
<Index>
Open          6.413
Close         6.3378
<<U7>         <float64>

与使用Bus相比,Batch提供了更紧凑的界面来实现这种选择。在不编写循环的情况下,Batch.apply_except()方法可以从每个包含的Frame中选择行和列值,同时忽略从没有选定日期的Frame中产生的任何KeyError。调用to_frame()将结果和它们的Frame标签连接在一起。

>>> sf.Batch(bus.items()).apply_except(
    lambda f: f.loc[‘1962–01–02’, [‘Open’, ‘Close’]], 
    KeyError).to_frame()
<Frame>
<Index> Open      Close     <<U7>
<Index>
ge      0.6277    0.6201
ibm     6.413     6.3378
<<U3>   <float64> <float64>

最后,作为Frame的虚拟连接,Quilt允许选择,就像从单个Frame中选择一样。如下图所示,内层标签“1962–01–02”上的分层选择将所有报价机中该日期的所有记录汇集在一起。

>>> quilt.loc[sf.HLoc[:, '1962-01-02'], ['Open', 'Close']]
<Frame>
<Index>                     Open      Close     <<U7>
<IndexHierarchy>
ge               1962-01-02 0.6277    0.6201
ibm              1962-01-02 6.413     6.3378
<<U3>            <<U10>     <float64> <float64>

最小化内存使用

在前面的例子中,Bus被显示为在被访问时延迟加载数据。虽然这只允许加载需要的东西,但是对已加载的Frame的强引用保留在Bus中,将它们保存在内存中。对于大型数据集合,这可能会导致不希望的数据保留。

通过在Bus初始化中使用max_persist参数,我们可以确定Bus中保留的Frame的最大数量。如下图所示,通过将max_persist设置为 1,在加载每个Frame后,加载的Frame的数量保持为 1:

>>> bus = sf.Bus.from_zip_pickle(‘stocks.zip’, max_persist=1)
>>> bus[‘aapl’].shape
(8364, 6)
>>> bus.status[‘loaded’].sum()
1>>> bus[‘ibm’].shape
(14059, 6)
>>> bus.status[‘loaded’].sum()
1>>> bus[‘goog’].shape
(916, 6)
>>> bus.status[‘loaded’].sum()
1

使用这种配置,一个进程可以遍历所有 7,163 个Frame,在每个Frame上工作,但只会导致单个Frame的内存开销。虽然同样的例程可以在单个Frame上使用 group-by 来执行,但是这种方法明显倾向于在计算时间上最小化内存使用。下面的例子演示了这样一种方法,找出所有股票的收盘价之间的最大跨度。

>>> max_span = 0
>>> for label in bus.index:
...     max_span = max(bus[label]['Close'].max() 
            - bus[label]['Close'].min(), 
            max_span)
...
>>> max_span
1437986239.4042
>>> bus.status['loaded'].sum()
1

由于可以将Bus作为输入提供给BatchQuiltYarn,所以整个系列的容器都可以从这种减少内存开销的方法中受益。

并行处理

独立处理大量的Frame是一个令人尴尬的并行问题。因此,这些高阶容器为并行处理提供了机会。

所有压缩文档的构造器和导出器,比如from_zip_parquet()to_zip_npz(),都支持一个config参数,该参数允许在一个StoreConfig实例中指定多处理Frame反序列化或序列化的工作线程数量和块大小。StoreConfig的相关参数有read_max_workersread_chunksizewrite_max_workerswrite_chunksize

类似地,所有的Batch构造函数都公开了max_workerschunk_sizeuse_threads参数,以允许并行处理Frames。只需启用这些参数,对大量Frame的操作就可以是多进程或多线程的,有可能带来显著的性能提升。虽然在 Python 中使用线程进行 CPU 受限的处理通常是低效的,但是使用线程池执行的一些基于 NumPy 的操作(在全局解释器锁之外)可以优于进程池。

结论

虽然存在处理数据帧集合的相关工具,但是BusBatchQuiltYarn提供了定义良好的抽象,涵盖了处理潜在的大量表集合的常见需求。结合延迟加载、急切卸载和延迟执行,以及对多种多表存储格式的支持,这些工具为数据帧处理提供了宝贵的资源。

使用随机森林帮助解释为什么 MLB 球员进入名人堂

原文:https://towardsdatascience.com/using-local-importance-scores-from-random-forests-to-help-explain-why-mlb-players-make-the-hall-of-aa1d42649db2

如何使用局部重要性分数来获得类别整体变量重要性,以帮助理解原因。

凯特琳·康纳在 Unsplash 上的照片

介绍

去年秋天,我写了一篇文章,标题是: Rfviz:一个用于解释 R 中随机森林的交互式可视化包。那篇文章是 r 中可视化工具的教程,我想给出一个不使用可视化工具获得相同类型结果的选项。因此,本文是一个关于使用 r 中的 randomForest 库中的局部重要性分数的编码示例,我们将使用它来识别类级别上的整体变量重要性。使用这种方法,我们将探索为什么一些 MLB 球员进入名人堂,而许多人却没能进入名人堂。

理论背景

随机森林

随机森林(Breiman (2001))使许多树木(通常 500 或更多)适合回归或分类数据。每棵树都适合数据的引导样本,因此一些观察值不包括在每棵树的拟合中(这些被称为树的观察值)。在每棵树的每个节点上独立地随机选择相对少量的预测变量(称为 mtry ),这些变量用于寻找最佳分割。树长得很深,没有修剪。为了预测一个新的观察值,观察值被传递到所有的树上,预测值被平均(回归)或投票(分类)。

可变重要性

对于每个变量,为数据集中的每个观察值获得局部重要性分数。为了获得观察值 i 和变量 j 的局部重要性分数,对观察值 i 超出范围的每棵树随机排列变量 j ,并将变量 j 排列数据的误差与实际误差进行比较。观察值 i 超出范围的所有树的平均误差差是其局部重要性分数。

变量 j 的(总体)变量重要性分数是所有观测值的局部重要性分数的平均值。

例子

我们这里的数据是美国职业棒球大联盟的统计数据。我们将试图解释的问题是,为什么一些 MLB 球员进入名人堂,而许多人却没有进入名人堂。

我们将使用击球和防守统计数据以及这些球员的季末奖励。在这个例子中,我们忽略了投手统计和投手的季末奖。使用 R 中的随机森林,我们将在类的基础上发现整体变量的重要性,以及我们如何使用结果来帮助回答这个问题。

此示例使用来自 R 包 Lahman 的数据,这是一个棒球统计数据集,包含 1871 年至 2019 年(Lahman 2020 年)美国职业棒球大联盟的投球、击球、防守和奖项统计数据。它是在知识共享署名-共享 3.0 无版权许可下授权的。

关于我们用来聚合和创建数据集的变量定义的参考,请参见此链接:【http://www.seanlahman.com/files/database/readme2017.txt

资料准备和勘探

library('Lahman')
library(randomForest)
library(dplyr)#Get the list of players inducted into the Hall of Fame
HallOfFamers <-
  HallOfFame %>%
  group_by(playerID) %>%
  filter((votedBy=="BBWAA" | votedBy=="Special Election") 
         & category == "Player") %>%
  summarise(inducted = sum(inducted == "Y")) %>% 
  ungroup()#Batting Statistics
Batting_Stats <-
  Batting %>%
  group_by(playerID) %>%
  mutate(total_BA=sum(H)/sum(AB),
            total_AB=sum(AB), total_R=sum(R),
            total_X2B=sum(X2B),total_X3B=sum(X3B), 
            total_H = sum(H), total_HR = sum(HR),
            total_RBI=sum(RBI), total_SB=sum(SB), total_CS=sum(CS),
            total_BB=sum(BB), total_SO=sum(SO), 
            total_IBB=sum(IBB), total_HBP=sum(HBP),
            total_SH=sum(SH), total_SF=sum(SF),
            total_GIDP=sum(GIDP)) %>%
  select(playerID, total_BA, total_AB, total_R, total_X2B, total_X3B, total_H, total_HR,
         total_RBI, total_SB, total_CS, total_BB, total_SO, total_IBB, total_HBP, total_SH,
         total_SF, total_GIDP) %>% 
  group_by_all() %>% 
  summarise() %>% 
  arrange(desc(total_H)) %>% 
  ungroup()#Fielding Statistics
Fielding_Stats <- 
  Fielding %>% group_by(playerID) %>% 
  mutate(POS=toString(unique(POS)), total_G=sum(G), total_GS=sum(GS), total_InnOuts=sum(InnOuts),
            total_PO=sum(PO), total_A=sum(A), total_E=sum(E), total_DP=sum(DP), total_PB_Catchers=sum(PB),
            total_WP_Catchers=sum(WP), total_SB_Catchers=sum(SB), total_CS_Catchers=sum(CS), mean_ZR=mean(ZR)) %>% 
  select(playerID, POS, total_G, total_GS, total_InnOuts, total_PO, total_A, total_E, total_DP,
         total_PB_Catchers, total_WP_Catchers, total_SB_Catchers, total_CS_Catchers, mean_ZR) %>% 
  group_by_all() %>% 
  summarise() %>% 
  arrange(desc(total_G)) %>% 
  ungroup()#End of Season Awards
Season_Awards <-
  AwardsPlayers %>%
  group_by(playerID) %>%
  mutate(Count_All_Star=sum(awardID=='Baseball Magazine All-Star' | awardID=='TSN All-Star'), Count_MVP = sum(awardID == "Most Valuable Player"), 
         Count_Silver_Slugger = sum(awardID == "Silver Slugger"), Count_Gold_Glove = sum(awardID == "Gold Glove")) %>% 
  select(playerID, Count_All_Star, Count_MVP, Count_Silver_Slugger, Count_Gold_Glove) %>% 
  group_by_all() %>% 
  summarise() %>% 
  ungroup()#Joining the datasets together
HOF_Data <- Batting_Stats %>% 
  full_join(Fielding_Stats, by=c('playerID')) %>% 
  left_join(Season_Awards, by='playerID') %>% 
  left_join(HallOfFamers, by='playerID')  %>%  
  #Filling in NA's based on data type
  mutate_if(is.integer, ~replace(., is.na(.), 0)) %>% 
  mutate_if(is.numeric, ~replace(., is.na(.), 0)) %>% 
  mutate_if(is.character, ~replace(., is.na(.), 'xx')) %>% 
  mutate_if(is.character, as.factor) #Converting characters to factors in preparation for Random Forests#Double checking filling in NA's and converting the response variable to a factor. 
HOF_Data[is.na(HOF_Data)] <- 0
HOF_Data$inducted <- as.factor(HOF_Data$inducted)#Looking at the spread of inducted vs. not to the Hall of Fame
table(HOF_Data$inducted)

下面的结果显示,我们的数据集有 19,773 名球员没有入选名人堂,125 名球员入选了名人堂:

浏览一下数据,我们可以直观地看到数据和类类型的结构:

模型输出

#For purposes of exploratory analysis, we will not be splitting into training and test.#Quick trick for getting all variables separated by a '+' for the formula. (We just omit the response variable and any others we don't want before copying as pasting the output) 
paste(names(HOF_Data), collapse='+')rf <- randomForest(inducted~total_BA+total_AB+total_R+total_X2B+total_X3B+
        total_H+total_HR+total_RBI+total_SB+total_CS+total_BB+
        total_SO+total_IBB+total_HBP+total_SH+total_SF+total_GIDP+
        total_G+total_GS+total_InnOuts+total_PO+total_A+total_E+
        total_DP+total_PB_Catchers+total_WP_Catchers+
        total_SB_Catchers+total_CS_Catchers+mean_ZR+Count_All_Star+
        Count_MVP+Count_Silver_Slugger+Count_Gold_Glove,
                   data=HOF_Data, 
                   localImp=TRUE, cutoff=c(.8,.2))
rf

看起来该模型在两个类之间体面地分离了训练数据,即使没有优化截止和仅仅猜测。

类别整体变量重要性

接下来,让我们看看当地的重要性分数是什么,对那些进入名人堂的人来说是最重要的。

#Rather than using the model for predictions I am using it to see how the trees separated the data. This way it is more of an unsupervised learning problem. Because of this I am okay predicting on the data set I used to train the model.HOF_Data$predicted <- predict(rf, HOF_Data)#Looking at the class-wise variable importance for those who were classified as making the Hall of Fame.
HF <- data.frame(t(rf$localImportance[,which(HOF_Data$predicted==1)]))sort((apply((HF),2,mean)), decreasing=TRUE)

“sort((apply((HF),2,mean)),decreasing=TRUE)”的作用是计算第 1 类或那些被模型归类为进入名人堂的球员的整体变量重要性。这是因为我们在前面的编码步骤中为该类设置了子集。现在我们在类的层面上有了整体的变量重要性。

现在让我们看看对面的类

#Looking at the class-wise variable importance for those who were classified as not making the Hall of Fame.
NHF <- data.frame(t(rf$localImportance[,which(HOF_Data$predicted==0)]))sort((apply((NHF),2,mean)), decreasing=TRUE)

我们可以看到,就对该类预测最重要的因素而言,这些类具有不同的变量。但是这些区别是什么呢?让我们深入了解一下那些被归类到名人堂的人。

回答为什么

根据随机森林的当地重要分数,被归类为击球手/外野手名人堂的前 5 个特征是:

  1. Count_All_Star(一名球员被评为全明星的次数)
  2. total_G(一个玩家总共玩了多少场游戏)
  3. total_SH(玩家命中多少次总牺牲命中)
  4. total_RBI(一名球员总共击出多少分)
  5. total_HR(一名球员总共击出多少支全垒打)

让我们取其中的三个,Count_All_Star,total_HR 和 total_G,比较两个类之间的数据:

summary((HOF_Data[HOF_Data$predicted==1,'Count_All_Star']))
summary((HOF_Data[HOF_Data$predicted==0,'Count_All_Star']))

summary((HOF_Data[HOF_Data$predicted==1,’total_HR’]))
summary((HOF_Data[HOF_Data$predicted==0,’total_HR’]))

summary((HOF_Data[HOF_Data$predicted==1,’total_G’]))
summary((HOF_Data[HOF_Data$predicted==0,’total_G’]))

我们可以看到,随机森林的本地重要性分数表明,更多年被命名为全明星,更多的本垒打,更多的比赛是被归类为击球手/外野手名人堂的前 5 个最重要变量中的 3 个。通过使用这种方法,我们可以很快地看到实际的数据分布和类之间的差异。

注意:看看那些明显的离群值,这位打了 3528 场比赛的球员是查理·格里奇,他实际上进入了名人堂。他是一个错误的分类。打出 762 支全垒打而未被列入名人堂的球员是巴里·邦兹,一个众所周知的类固醇使用者。玩了 3528 场游戏并被归类为未进入大厅的玩家是皮特·罗斯,他因在游戏上下注而被关在门外。具有讽刺意味的是,后两者没有入选名人堂,兰登森林将他们归类为没有入选。

结论

总之,对棒球有中等水平了解的人可能知道,加入 500 支全垒打俱乐部并 10 次入选全明星会让你有机会进入名人堂。但是,并不能保证。这似乎是事实,因为没有明确的规则来制作名人堂。另一方面,如果一个人没有职业棒球大联盟的知识呢?或者他们正在运行的模型的主题?嗯,使用局部重要性分数和 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://chrisbeckett8.github.io/Rfviz.

拉赫曼,S. (2020)拉赫曼棒球数据库,1871–2019,主页,http://www.seanlahman.com/baseball-archive/statistics/

普莱斯科夫,伯尼。"入选国家棒球名人堂的标准是什么?"福布斯,福布斯杂志,2020 年 1 月 22 日,https://www . Forbes . com/sites/berniepleskoff/2020/01/21/入选国家棒球名人堂的标准是什么/?sh=17c57173149e。

使用 LSTM 的推特情感分析

原文:https://towardsdatascience.com/using-lstm-in-twitter-sentiment-analysis-a5d9013b523b

详细了解 LSTM 应用于情感分析的能力

尼古拉·伯恩森在 Unsplash 上拍摄的照片

当你坐上汽车的驾驶座时,你是能够发动引擎开车离开,还是在开车离开之前,你需要考虑你将如何做以及这个过程的每一步?对于大多数人来说,这是第一选择。

这也是递归神经网络(RNNs)的工作方式。你不需要从头开始考虑你一天中要做的每一个基本任务,因为我们有一个叫做记忆的小东西,循环神经网络也是如此。但是他们是如何学习和储存这些记忆的呢?

rnn 有环路,允许它们从信息中学习。来源:https://colah.github.io/posts/2015-08-Understanding-LSTMs/

在上图中,神经网络的一个单元接收某种类型的输入 Xt 并输出一个值 Ht。然后,循环将信息提供给下一个单元格,依此类推。因此,rnn 可以被认为是同一个网络的副本,每个都向下一个传递信息。当我们看一个完整的链时,就更容易理解了,它是上图中展开的环:

展开的递归神经网络的完整链。来源:https://colah.github.io/posts/2015-08-Understanding-LSTMs/

在过去的几年中,RNNs 已经被成功地应用于多种自然语言处理项目中。这些应用包括语音识别、语言建模、翻译,当然还有我们将在本文中探讨的情感分析。如果没有一种特殊的 RNN,这一切都是不可能的,那就是 LSTM(长短期记忆)。出于许多目的,LSTMs 比传统的递归网络工作得更好,这里我们将展示如何以及为什么。但首先,我们需要谈谈为什么我们不能只使用正常的 RNNs,它有什么问题。

rnn 令人兴奋的一点是,它们可以在预测中使用过去的信息。考虑一个试图根据前面的单词来预测一个句子的最后一个单词的模型。在句子“书在桌子上”中,最后一个单词的选择范围很窄,而“桌子将是一个很好的预测,不需要太多额外的上下文就可以正确预测。在相关单词与预测之间的距离很小的情况下,RNNs 可以很好地预测。然而,如果我们需要更多的上下文来进行单词预测,会发生什么呢?

想想这个句子“这几天我一直在学习,在桌子上,有一本书”。最近的信息表明,桌子上有一些物体,但如果我们想缩小到哪个物体,我们应该进一步回到句子。然后,如果我们在第一句话中查看单词“学习,我们将获得预测正确所需的额外上下文。由于句子以“我一直在学习”开头,所以桌子上不太可能有足球或伏特加酒瓶,而不是一支笔或一本书。这正是为什么 LSTM 模型现在被广泛使用,因为它们被特别设计成具有长期“记忆”,能够比受长期依赖问题影响的其他神经网络更好地理解整体背景。

理解 LSTM 如何工作的关键是细胞状态。细胞状态通过一些微小的线性相互作用来运行整个链,在这些相互作用中,LSTM 可以通过称为门的结构来添加或删除信息。有两个门:一个 sigmoid 神经网络层和一个逐点乘法操作。

单元格状态是穿过顶部的粗体线条。来源:https://colah.github.io/posts/2015-08-Understanding-LSTMs/

盖茨负责选择哪些信息将通过。例如,sigmoid 层将 sigmoid 函数应用于原始向量,并输出 0 和 1 之间的值。那是什么意思?零值将阻挡所有信息,而值 1 将让所有信息通过第一门并在单元状态流中继续。

“忘记”栅极层将 sigmoid 函数应用于单元状态。来源:https://colah.github.io/posts/2015-08-Understanding-LSTMs/

我们已经说过,LSTM 的第一个决定来自 sigmoid 层门,我们也可以称之为“忘记”门。该层查看两个输入: Xt,带有矢量化文本,我们希望预测情感(借助于标记器,如下图所示),以及 Ht-1 带有模型已经拥有的先前信息。然后,应用 sigmoid 函数,并且它为每个单元状态 Ct-1 输出 0 和 1 之间的数字。

Tokenizer:这就是如何将原始文本矢量化为 LSTM 模型的输入。每个字相当于编码器定义的一个数字。来源:作者

在本文中,我们不会详细介绍其他 LSTM 层,因为重点是展示如何将其应用于 Twitter 情感分析,但该算法的演练在这里详细解释。既然我们已经谈论了很多 LSTM 理论,让我们编码并展示如何使用它来预测推文的情绪。

最初,我们需要获得一个包含分类推文的数据集来训练和测试我们的模型。我选择了 Kaggle 的 Tweet 情感提取比赛的数据集。这个数据集是大约 27,500 条推文的集合。

那么,让我们加载数据:

import pandas as pd

df_train = pd.read_csv('train.csv',sep=',')
df_train.text=df_train.text.astype(str)
df_val = pd.read_csv('test.csv',sep=',')
df_val.text=df_val.text.astype(str)

预览我们加载的训练数据集(df_train)。来源:作者

加载完数据后,我们的下一步是合并训练和验证数据集以创建单个数据集,我们可以对其应用预处理步骤,尤其是标记化过程,稍后我们将对此进行详细解释。

df = pd.concat([df_train.assign(ind="train"),df_val.assign(ind="validation")])

现在,下一步是去除中立情绪。这不是共识,但是一些文章表明去除中性词是增强情感分析模型性能的一种方式。二元分类的结果更容易解释,模型往往表现更好。此外,因为我们要处理数组,所以有必要将“正”标签转换为 1,将“负”标签转换为 0。

df **=** df**.**loc[(df['sentiment'] **==** 'Positive') **|** (df['sentiment'] **==** 'Negative')]

df**.**loc[df['sentiment'] **==** 'Positive','sentiment'] **=** 1 df**.**loc[df['sentiment'] **==** 'Negative','sentiment'] **=** 0

现在好戏开始了!让我们开始操纵推文并对它们进行预处理,以准备我们的 LSTM 模型的输入。为此,我们需要加载两个库:Spacy 和 RE(正则表达式)。Spacy 是一个令人惊叹的自然语言处理库,它有一些工具,如预训练的单词向量、词汇化、存储的停用词、实体识别,以及用于 60 多种语言的其他工具。我们所要做的就是在我们的 Python 环境中安装 Spacy 并下载我们想要的语言,在本文中是英语。然后我们将下载 en_core_web_sm 这是一个英语培训的管道,它有较小的版本(也有中等和较大的版本)。然后,我们使用空间函数“load”将刚刚下载的管道应用到“ nlp 对象。

*# pip install -U spacy
# python -m spacy download en_core_web_sm*

**import** spacy
**import** renlp **=** spacy**.**load('en_core_web_sm')

既然已经加载了 NLP 库,我们可以开始文本预处理了。首先,我们应该将我们的文本列转换为字符串,然后我们可以删除停用词,并使用上面刚刚创建的 nlp 加载管道中的“lemma_”和“is_stop”方法,通过一行代码应用词汇化。然后,我们必须将字符串转换成小写,并使用正则表达式库,这样我们就可以删除空格(\s)和字母数字字符(\w),只留下对我们的模型有影响的单词。

df["text"] **=** df["text"]**.**astype(str)
df["text"] **=** df['text']**.**apply(**lambda** x: " "**.**join([y**.**lemma_ **for** y **in** nlp(x) **if** **not** y**.**is_stop]))
df['text'] **=** df['text']**.**apply(**lambda** x: x**.**lower())
df['text'] **=** df['text']**.**apply((**lambda** x: re**.**sub('[^\w\s]','',x)))

现在是时候对我们的推文进行矢量化了,我们将使用 Keras 库预处理工具来完成这项工作。首先,我们必须基于词频定义要保留的最大单词数,我们将它设置为 3000,以便在我们的向量中有一个稳定的单词种类。当我们将记号赋予文本时,重要的是我们在完整数据(df)上进行,否则,如果我们仅将记号赋予训练 tweets,然后尝试使用相同的记号来转换验证数据集,我们可能会得到错误。有些单词只出现在验证数据集中,而对于标记器来说是未知的,因为它只适合使用训练数据集单词。因此,我们将使我们的 tokenizer 对象适合我们的完整数据集。

**from** tensorflow.keras.preprocessing.text **import** Tokenizer
**from** tensorflow.keras.preprocessing.sequence **import** pad_sequences

max_features **=** 3000
tokenizer **=** Tokenizer(num_words**=**max_features, split**=**' ')
tokenizer**.**fit_on_texts(df['text']**.**values)

将记号赋予器仅适用于训练数据集的另一个问题是输出的形状。例如,让我们想象我们的火车数据集只有一条推文,上面的例子是“ le chat est noir ”。在标记化之后,我们将得到一个形状为(1,4)的数组。然后,如果我们试图预测一条推文“我很高兴”的情绪,一个形状为(1,3)的数组,这是不可能的,因为这些数组的形状不匹配。在这种情况下,我们可以使用 numpy lib pad 用 0 填充测试数组,以匹配标记器的原始形状(1,4)。这样做之后,我们仍然会遇到上一段提到的不适合单词的问题,因为单词“ I ”、“ am ”或“ happy ”都不适合分词器,最终我们的数组将是[0,0,0,0]。可以使用 max_features 参数将 tokenizer 的形状设置为一个限制,但通常情况下,它与最大元素的形状相同,或者在我们的示例中,与包含最多单词的 tweet 的形状相同。

既然记号赋予器已经适合完整的数据框,我们可以分割回训练和验证数据集。

df_train, df_val **=** df[df["ind"]**.**eq("train")], df[df["ind"]**.**eq("validation")]

在拆分回 df_traindf_val 并拟合记号化器之后,是时候对 tweets 进行矢量化了。这可以使用 Keras 'texts _ to _ sequencespad_sequences 来完成。

X_train **=** tokenizer**.**texts_to_sequences(df_train['text']**.**values)
X_train **=** pad_sequences(X_train)X_val **=** tokenizer**.**texts_to_sequences(df_val['text']**.**values)
X_val **=** pad_sequences(X_val)

矢量化的 X_train 数组。来源:作者

在对推文进行矢量化之后,是时候检查数据集的形状是否匹配了,否则我们需要调整其中一个。

[X_train.shape,X_val.shape]

我们得到的输出是[(16.363,20),(2.104,18)],意味着 X_train 数据集有 16.363 行(或 tweets)和 20 个特征(或单词),X_val 数据集有 2.104 行(或 tweets)和 18 个特征(或单词)。因此,我们需要用 0 填充 X_val 数据集,以匹配 X_train 的 20 个特征。但是我们怎么能对一个数组这么做呢?当然是 Numpy!使用 numpy lib pad,我们可以使用 X_train 的形状减去 X_val 的形状,以获得两个数组之间的特征(或单词)差异,并用常数值 0 填充 X_val 以匹配形状。

**import** numpy **as** np
X_val **=** np**.**lib**.**pad(X_val, ((0,0),(X_train**.**shape[1] **-** X_val**.**shape[1],0)), 'constant', constant_values**=**(0))
X_val**.**shape

长度与 X_train 匹配的矢量化 X_val 数组。来源:作者

我们得到的输出是(2.104,20),最后,训练和验证数组具有相同的特征长度(20),可以用于我们的预测。现在,我们预处理的最后一步是准备我们的目标值,所以我们要从这些变量中取出虚拟变量,并将它们转换成数组,以便在我们的训练和验证中使用。

Y_train **=** np**.**array(pd**.**get_dummies((df_train['sentiment'])**.**values))
Y_val **=** np**.**array(pd**.**get_dummies((df_val['sentiment'])**.**values))

训练和验证数组都设置好了,是时候构建我们的 LSTM 模型了!但是我们从哪里开始呢?喀拉斯!最受欢迎的深度学习和神经网络库之一,它运行在机器学习库 TensorFlow 之上。它允许我们建立一个模型,并使用 Keras 上的 sequential 类,我们可以将一个线性堆栈的层组合到我们的模型中。这些层与我们在本文第一部分的 LSTM 解释中提到的层相同。

**from** tensorflow.keras.models **import** Sequential
**from** tensorflow.keras.layers **import** Dense, Embedding, LSTM, SpatialDropout1D

现在,构建 LSTM 网络的第一步是创建一个嵌入层。嵌入层允许我们将每个单词转换成固定长度的向量,这是一种更好的方式来表示这些单词,同时降低维度。max_features 参数是我们已经在标记化中设置的词汇表的大小,embed_dim 参数是我们想要的每个单词的向量的长度,input_length 是一个序列的最大长度,所以在我们的例子中,我们可以使用 X_train.shape[1],它输出 20。就这样,我们的嵌入层完成了。

max_features = 3000
embed_dim = 128model **=** Sequential()
model**.**add(Embedding(max_features, embed_dim,input_length **=** X_train**.**shape[1]))

继续,在我们的嵌入层之后,是时候给我们的模型添加一个空间下降 1D 层了。空间下降层的主要目的是避免过度拟合,这是通过概率性地移除该层的输入(或我们正在构建的网络中嵌入层的输出)来实现的。总而言之,它具有模拟许多不同网络的效果(通过丢弃随机元素,或者在我们的例子中是它们的 30%——代码上的 0.3 个参数),并且最终,网络的节点对于未来的输入更加健壮,并且倾向于不过度拟合。

model**.**add(SpatialDropout1D(0.3))

最后,是时候添加我们的明星层,一个我们谈论了这么多,唯一的 LSTM!我们定义的第一个参数 lstm_out 为 256,它是输出空间的维数,我们可以选择一个更大的数来尝试改进我们的模型,但这可能会导致许多问题,如过拟合和长训练时间。dropout 参数应用于我们模型的输入和/或输出(线性变换),而 recurrent dropout 应用于模型的递归状态或单元状态。换句话说,经常性的辍学影响了网络的“记忆”。对于这个 LSTM 网络,我选择使用更大的压差和 0.5 的经常压差,因为我们的数据集很小,这是避免过度拟合的重要一步。

lstm_out = 256model**.**add(LSTM(lstm_out, dropout**=**0.5, recurrent_dropout**=**0.5))

现在我们的 LSTM 层已经完成了,是时候准备我们的激活函数了。我们通过添加一个密集连接的层来实现我们网络的激活功能,并输出我们最终阵列所需的维度。正如我们之前看到的,在 LSTM,我们需要一个 sigmoid 神经网络层,这正是我们选择 sigmoid 激活的原因。sigmoid 函数输出一个介于 0 和 1 之间的值,它可以让信息不流过或完全流过这些门。

最后,我们准备编译模型,这是为训练配置模型。我们将为我们的损失函数选择分类交叉熵,这是一种广泛使用的损失函数,用于量化分类问题中的深度学习模型错误。对于我们的优化器,我们将使用 Adam 优化器,这是 Adam 算法的一种实现,它是随机梯度下降的一种健壮扩展版本,是深度学习问题中最常用的优化器之一。此外,选择的指标将是准确性,因为我们有一个情绪分析问题,我们需要预测它是正面还是负面的推文。

model**.**add(Dense(2,activation**=**'sigmoid'))
model**.**compile(loss **=** 'categorical_crossentropy', optimizer**=**'adam',metrics **=** ['accuracy'])
print(model**.**summary())

这是我们的最终模型:

Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 embedding (Embedding)      (None, 161, 128)          384000    

 spatial_dropout1d (Spatia  (None, 161, 128)         0         
 lDropout1D)                                                     

 lstm_3 (LSTM)               (None, 256)               394240    

 dense_3 (Dense)             (None, 2)                 514       

=================================================================
Total params: 778,754
Trainable params: 778,754
Non-trainable params: 0
_________________________________________________________________

随着模型的建立,是时候执行每个数据科学家都喜欢的命令了。因此,让我们将我们的模型拟合到 X_train 和 Y_train 阵列,10 个历元应该足以获得预测的良好性能。批量大小是在执行重量更新(一个时期)之前,通过网络运行的样本数量,我们将保持它较低,因为它需要较少的内存。

batch_size = 32
model.fit(X_train, Y_train, epochs = 10, batch_size=batch_size, verbose = 2, shuffle=False)

培训的回访历史。来源:作者

在 10 个时期后,我们可以看到我们的模型的性能在每个时期都有所提高,损失减少,精度增加,最终在训练数据集上达到 94.4%的高精度。

现在是时候使用我们训练过的模型来衡量它在测试数据集上的性能了,这是我们之前构建的 X_val 。在深度学习问题上这样做的一个好方法是建立一个函数,这里我们有一个情感分析分类问题,在验证(或测试)数据集上检查我们模型的准确性、f1 分数和混淆矩阵是一个好主意。下面是我用过的一个:

import matplotlib.pyplot as plt
import seaborn as snsdef evaluate_lstm(model, X_test,Y_test): pos_cnt, neg_cnt, pos_correct, neg_correct = 0, 0, 0, 0
 results = [] for x in range(len(X_test)):
  result =   model.predict(X_test[x].reshape(1,X_test.shape[1]),
  batch_size=1,verbose = 3)[0] if np.argmax(result) == np.argmax(X_test[x]):
   if np.argmax(X_test[x]) == 0:
    neg_correct += 1
  else:
    pos_correct += 1

  if np.argmax(X_test[x]) == 0:
   neg_cnt += 1
  else:
   pos_cnt += 1
  results.append(np.argmax(result)) Y_test_argmax = np.argmax(Y_test,axis=1)
 Y_test_argmax  = Y_test_argmax.reshape(-1,1)
 results = np.asarray(results)
 results = results.reshape(-1,1) conf_matrix = confusion_matrix(Y_test_argmax, results)
 fig = plt.figure(figsize=(6, 6))
 sns.heatmap(conf_matrix, annot=True, fmt="d", cmap = 'GnBu');
 plt.title("Confusion Matrix")
 plt.ylabel('Correct Class')
 plt.xlabel('Predicted class')

并将该函数应用于 X_val 数组,我们得到:

accuracy,f1, fig = evaluate_lstm(model,X_val,Y_val)
print(f'Accuracy:{accuracy:.3f}')
print(f'F1 Score: {f1:.3f}')

度量和混淆矩阵。来源:作者

我们得到了 87.3%的准确率和 0.878 的 F1 分数,这两个分数都很棒!另一个积极的事情是,该模型在积极(1)和消极(0)情绪上都表现良好。当然,验证数据集推文与训练数据集推文的主题相似,数据集在正面和负面推文之间保持平衡,这一事实会影响这些指标。它仍然显示了神经网络在情感预测方面的能力。该模型可以用新的推文进行训练,以提高对特定主题的预测性能等。

最终,神经网络只是线性代数,神奇的事情发生了。

使用机器学习估计价格弹性

原文:https://towardsdatascience.com/using-machine-learning-to-estimate-price-elasticity-47a2ba71bdb0

丽贝卡·马修斯在 Unsplash 上拍摄的照片

在我研究生院分析项目的最后一个学期,我的任务是估算葡萄酒的价格弹性。乍一看,这似乎是一个简单的目标。在经济学中,我学到价格弹性是给定价格变化百分比时需求变化的百分比。你所要做的就是查看每个价格下的购买数量,以确定价格弹性。为什么你甚至需要机器学习来解决这类问题?

为了理解这种简单化的方法有多大的缺陷,让我们考虑以下问题:

价格是影响你是否购买产品或服务的唯一变量吗?

如果你回答“不”,那么你可以想象经济理论和现实之间的潜在差距。为了估计价格弹性,除了产品或服务的价格之外,你可能还需要考虑许多变量。否则,您的弹性模型可能会受到预测性能不佳的影响。

带有一些个人修改的典型机器学习开发过程。在数据准备阶段增加了“规划+研究”部分以及“探索性数据分析”文本。作者开发的图像。

规划+研究

因为我从未试图在简单的财务分析之外评估弹性,这个问题需要一些研究。当我向一位有很强物理学背景的同事描述这个问题时,这导致了我有过的最刺激的对话之一。在为他定义了价格弹性之后,我们试图用微积分中的导数(即测量变化)的基本原理来解决这个问题。这个讨论,再加上来自 UW 大学“定量分析”课程的帖子,促使我使用对数转换的多元线性回归来解决我的问题。对数转换变量的解释反映了变量之间的百分比变化,这是期望的结果。

估算葡萄酒价格弹性以及每个所需维度所需的算法如下:

  1. 筛选与您正在评估弹性的维度相关的销售数据的特定子集(例如,如果评估红酒的价格弹性,则仅筛选红酒销售)
  2. 未来销售目标变量和当前价格特征执行对数转换
  3. 训练一个线性回归模型来准确预测未来的销售额
  4. 价格弹性估计将是对数转换的系数,价格特征
  5. 对每个弹性评估重复步骤 1-4

数据准备

建立高层次计划后,我开始准备数据。这通常是任何模型开发过程中最耗时的部分。目标是在进行任何分析之前,识别并执行必要的步骤来预处理和转换数据。虽然数据很容易获得,但数据库文档还有许多不足之处。表之间的关系不明显,主键也不明显。此外,数据定义留下的问题多于答案。为了说明这种模糊性,让我们想象下面的模拟产品表。你认为下表中的销售价格是什么意思?

没有清晰的文档,我不得不思考以下问题:

销售价格是每单位、每产品或每箱的价格吗?

所有饮料的标准单位是什么?

“售价”是否含税?

用如此多的未知数来估计弹性不仅不准确,而且不道德。为了填补这一知识空白,我需要向我们的利益相关者问几十个问题。一些例子包括:

  • 为什么有的价格为零?
  • 当出现等价的栏目时,哪一个应该是最靠谱?
  • 为什么“X”列中似乎缺少这么多信息?
  • 为什么这个“ID”列不都是唯一值?
  • 在弹性估计之前,可以过滤掉哪些记录?

获取该领域和特定于数据库的知识的过程是数据专业人员经常忽视的技能。数据准备过程中的一个失误或误算的假设会导致下游的错误结论。

PS:这就是为什么许多数据科学团队越来越多地嵌入业务职能部门,而不是像 IT 部门那样独立的部门。

探索性数据分析

在这个准备阶段,我也在探索数据。为了建立一个可解释的模型,你需要了解数据的分布,各种模式,并找到可以解释弹性的特征。在这一步中,可视化是数据专家最好的朋友。

使用直方图,我能够了解消费者在美国不同州、年份、账户(如酿酒厂)、品种(如梅洛)、价格区间(如每瓶 10-15 美元)、销售渠道(如在线)等方面的购买行为。对于提供给我的数据切片,它是一个跨越几个维度的不平衡数据集。消费者购买的很大一部分是在加利福尼亚,并且大多数购买都来自一个账户。此外,各年间账户数量不一致,因为随着时间的推移增加了一些账户。

观察多年来的购买时间序列表明,消费者的购买行为可能存在一定的季节性。这一观察极大地影响了我在机器学习开发周期下一步的特征工程决策。

也许最有趣的观察结果是通过在线渠道购买的比例增加,以及在 COVID 影响的年份中销售额的急剧下降和上升。购买行为的这些重大变化塑造了我们后来的一些建模决策,例如排除 2016 年之前的数据。

特征工程

数据准备和特征工程之间的界限可能有点模糊。在对数据表中的数据进行预处理之后,我将相关记录转换成“模型可读”的格式。对于 python 中流行的机器学习库 scikit-learn 来说,这需要将所有特征转换成数值。诸如购买地点(如加利福尼亚)等特征必须转换成具有二进制值的“虚拟列”(即 1 表示是,0 表示否)。这个虚拟变量创建过程被应用于所有感兴趣的分类列,并且可以使用 pandas 库中的“get_dummies”方法轻松应用。请参见下面的代码示例。

import pandas as pd
dummy_variable_columns = ['Day_of_week', 'Month', 'Status']
sales_dataframe_new = pd.get_dummies(sales_dataframe_old, columns=dummy_variable_columns)

一个最常见的,也是最令人沮丧的特征工程步骤包括日期维度的修改。我从订单日期中提取了不同的时间段(例如,月、周、日、年),以测试不同时间间隔内消费者行为的季节性。销售的时间序列看起来非常像一个正弦图,以至于我回想起了高中三角函数课。这启发了我去研究和创建时间的正弦和余弦变换,作为模型中的特性进行测试。

由于销售额呈现为正弦曲线,我决定在不同的周期长度内测试数据框架的聚合(即特征向量和销售额)。这个有点复杂的任务很容易完成,这要归功于熊猫的另一种方法,叫做“重新取样”。这个方法转换了 dataframe,这样每一列都可以在我需要的任何时间段(例如,每 45 天)进行聚合。请参见下面的代码示例。

sales_dataframe_new_45daycycle = sales_dataframe_new.resample('45D', on='Order_Date', origin='start', label='right', closed='right').sum()

上面代码片段的结果是,我现在可以看到 45 天周期内每条记录的总销售额,而不是特定日期的订单。我看到的不是同一种红酒的 5 个不同订单,而是总销售额、总销量、总订单数、每个月的总订单数等。每 45 天一个周期。将所有列转换为数字格式是此重新聚合的必要先决条件。

现在数据框架已经有了新的结构,我计算了每条记录的每卷平均价格,并对这个值和总销售量进行了对数转换。因为我们需要估计未来的需求,所以我必须创建一个列,它是未来对数转换后的销售量。这得益于熊猫的“转换”方法。请参见下面的代码示例。

sales_dataframe_new_45daycycle['log_quantity_sold_future'] = sales_dataframe_new_45daycycle['log_quantity_sold_current'].shift(periods=-1)

运行这段代码后,我们的数据框架中的每条记录都有当前价格和未来时期的销售额。这种设置是训练逻辑模型所必需的,因此当前价格(一个已知的特性)被用来解释未来的需求量——一个未知的目标,因为你无法知道未来的销售额。

*有读者记得 SOHCAHTOA 吗,这个有趣的首字母缩写词有助于记住不同的三角函数?

模特培训

在向非技术观众传达模型训练阶段时,很容易呈现复杂任务的假象。实际上,由于现在有成熟的机器学习库可用,训练模型通常是开发周期中比较容易的步骤之一。下面的代码反映了我在创建训练和测试集、标准化数据、训练逻辑回归模型以及将模型拟合到缩放的特征向量(即,对数转换价格、时间维度等)时所做的工作。)以及目标向量(即对数转换后的未来需求)。

import numpy as np
import sklearn# Set up the train and test sets
train_x, test_x, train_y, test_y = train_test_split(sales_dataframe_new_45daycycle.loc[:, sales_dataframe_new_45daycycle.columns != 'log_quantity_sold_future'].to_numpy(),
                                                 sales_dataframe_new_45daycycle['log_quantity_sold_future'].to_numpy(),
test_size=0.20,
 random_state=20)#Standardize the feature vector and target vector
scaler = preprocessing.StandardScaler().fit(train_x)
train_x_scaled = scaler.transform(train_x)
test_x_scaled = scaler.transform(test_x)# Build and fit the linear regression model
lr = LinearRegression()
lr.fit(train_x_scaled, train_y)

上面的代码片段省略了特征选择/正则化过程。特征选择过程主要是通过排除与未来需求相关性差的特征来驱动的。

模型评估

虽然 R 平方和均方根误差(RMSE)是学校教授的最常见的评估指标,但我经常发现平均绝对误差(MAE)和平均绝对百分比误差(MAPE)是更容易解释的评估指标。后一种度量提供了实际值和预测值之间的距离的更直观的度量。

对于其中一个多元线性回归模型,模型评估指标如下:

使用 MAPE,我能够与利益相关者沟通,我们模型的预测与实际销售值相比平均相差 1.4%。此外,特征向量解释了未来需求中 75%的偏差。这个模型在可解释性方面是成功的,在预测方面也非常准确。

结论

总的来说,我的结论是葡萄酒是一种缺乏弹性的商品;然而,消费者的行为因葡萄酒的每个子集而异(即不同的销售渠道、品种等)。).假设消费者在所有情况下都缺乏弹性,并且在未来仍将缺乏弹性,这将是一个错误。此外,有一些数据子集的样本量太小,对它们各自的弹性估计值没有信心。为了改进这一分析,我将把替代产品价格(如啤酒)纳入特征向量,并研究估计弹性的替代方法。用更有弹性的商品(如快餐)重复这一过程会很有意思,看看估计揭示了什么。
数据通才

注意:所有文本、数据和代码均经过修改,以隐藏任何敏感信息。

使用 Make for Data Science 项目改变了我的生活

原文:https://towardsdatascience.com/using-make-for-data-science-projects-changed-my-life-23de1597e2e

基岩实用程序有助于保持代码模块化,并提高迭代速度

马丁·w·柯斯特在 Unsplash 上的照片

数据科学项目的存在是为了从数据中可重复地产生知识。

也就是说,从探索性数据分析和可视化,通过统计建模,一直到具有庞大参数空间的尖端深度学习架构,我们所掌握的所有工具的统一目的是创造新知识。从广义上讲,我们要么通过推理直接学习,要么通过构造证明我们的黑盒可以解决给定的问题。卓越要求我们的过程透明,以至于其他人可以复制它、仔细检查它、修补它并改进它。

经典且历史悠久的构建工具 GNU Make 使得这些目标非常容易实现。

在为本文创建的示例项目中,我们将下载 NeurIPS abstracts (在 CC 4.0 下发布),对它们进行一些转换,最后拟合一个潜在的 Dirichlet 分配模型。自始至终,我们将使用 Make 来编排这个过程。我们将介绍的众多优势包括:

  1. 一种表达培训管道中各步骤之间依赖关系的自然方式。(阅读“逃离‘拍摄这些笔记本代码单元的顺序正确吗……’地狱。”)
  2. 向模块化迈进:简单的依赖管理鼓励我们将我们的程序块分割成小而漂亮的庙宇。
  3. 轻松利用实用程序:因为 Make 为我们编排了命令,所以我们可以利用安装在主机上的任何东西。想在 python 的下游任务之前用 sed 处理文本吗?柠檬汽水,范姆。
  4. 更快的迭代:Make 帮助我们避免不必要的重复计算,加速我们的创新周期,帮助我们更快地构建。
  5. 更容易的外部协作:通过依赖一组脚本——而不是例如笔记本——并表达它们的依赖性,Make 帮助我们将规范交给数据工程领域的朋友,而不需要太多额外的工作。

示例项目遵循 DrivenData 发布的 DS cookie cutter 。这个模板第一次让我了解了 Make for DS 的价值,这本身就是一个效率提升。

什么是品牌

(有经验的 Make 用户,随意跳过这一节——你会觉得很无聊。)

按照传统设计,Make 是可执行文件的构建工具。来自GNU.org:

GNU Make 是一个工具,它控制从程序的源文件生成程序的可执行文件和其他非源文件。Make 从一个名为 makefile 的文件中获取如何构建程序的知识,该文件列出了每个非源文件以及如何从其他文件中计算出它。写程序的时候要为它写一个 makefile,这样才有可能用 Make 来构建和安装程序。

例如,在 C++项目中,Make 文件是很常见的。但这是它最常见的用法,不是的限制。更多来自 GNU:

Make 根据哪些源文件发生了变化,自动计算出哪些文件需要更新。如果一个非源文件依赖于另一个非源文件,它还会自动确定更新文件的正确顺序。因此,如果您更改一些源文件,然后运行 Make,它不需要重新编译您的所有程序。它只更新那些直接或间接依赖于您所更改的源文件的非源文件。[…] Make 并不局限于构建一个包。您还可以使用 Make 来控制软件包的安装或卸载,为其生成标记表,或者您想经常做的任何事情,只要写下如何做就值得了。

概括地说,Make 管理检查哪些文件需要创建或重新创建的过程:如果自上次生成输出后没有任何变化,我们可以跳过重新创建它的步骤。它可以用来管理任何一系列我们想要重现的命令。

这非常自然地有助于分析或机器学习项目。考虑这个非常标准的数据科学工作流程:

  1. 通过文件下载或查询数据库获得一些数据。
  2. 对数据进行清理、插补等处理。
  3. 工程师特征。
  4. 训练和比较一系列模型架构或配置。

在这个非常常见的 DS 项目周期中,我们做出了几十个决定,包括如何处理什么、尝试哪些超参数、不同的插补方法等等。

易于表达

这些步骤中的每一步都可以在 Make 规则中自然地表达出来。例如,假设我们需要从 UCI ML 存储库中下载一个类似于 NeurIPS 数据集的文件。我们可以用curl来下载。这形成了一个简单的 Make 规则:

data/raw/NIPS_1987-2015.csv : curl -o $@ [https://archive.ics.uci.edu/ml/machine-learning-databases/00371/NIPS_1987-2015.csv](https://archive.ics.uci.edu/ml/machine-learning-databases/00371/NIPS_1987-2015.csv)

用 Make 的语言来说,我们想要创建的这个目标文件没有先决条件,只有一个创建它的方法。那个$@是一个 Make 自动变量,它填充了目标的名称。导航到演示项目文件夹的根目录,运行make --recon data/raw/NIPS_1987-2015.csv,您应该会看到 Make 将运行的命令来生成文件:

curl -o data/raw/NIPS_1987-2015.csv [https://archive.ics.uci.edu/ml/machine-learning-databases/00371/NIPS_1987-2015.csv](https://archive.ics.uci.edu/ml/machine-learning-databases/00371/NIPS_1987-2015.csv)

在没有--recon标志的情况下运行make data/raw/NIPS_1987-2015.csv,该命令将实际运行。再次运行它,你会得到这条消息,Make 的方式告诉我们不要浪费时间再次下载它:

make: `data/raw/NIPS_1987-2015.csv' is up to date.

我们看到原始文件是按列排列的,我们想转置它。我们将把转置后的版本放在data/interim中,快速编写一个轻量级脚本来执行 op,并在另一个规则中表达依赖性和执行:

data/processed/NIPS_1987-2015.csv : src/data/transpose.py data/raw/NIPS_1987-2015.csv $(PYTHON_INTERPRETER) $^ $@

这里我们使用了$^,另一个自动变量满足了所有的先决条件。使用--recon标志进行的另一个调用make data/processed/NIPS_1987-2015.csv --recon将向我们展示我们将在实际运行中看到的命令,即它将使用我们编写的 python 脚本创建目标文件:

python3 src/data/transpose.py data/raw/NIPS_1987-2015.csv data/processed/NIPS_1987-2015.csv

当训练模型时,这一步自然依赖于处理后的数据:

models/%_topics.png : src/models/fit_lda.py data/processed/NIPS_1987-2015.csv src/models/prodlda.py $(PYTHON_INTERPRETER) $< $(word 2, $^) $@ --topics $*

在这里,我们编写了fit_lda.py来训练一个模型,并为每个主题创建一个词云,我们在png目标文件中捕获它。这是为了举例而简化的。在一个典型的、专业的构建中,我们的目标会更加全面:我们会包括学习曲线、数据集每个部分的错误摘要,以及任何我们需要经验地比较不同模型架构的东西。我们还利用了模式规则,在目标中使用字符%,以及 Make 的word宏。我们将在模块化一节中详细介绍这些内容。

这些步骤中的每一步都自然依赖于前面的步骤,Make 为我们提供了表达它们的自然方式。

更快的迭代

Make 为您处理依赖关系。假设您已经编写了处理数据的脚本和步骤,并将结果保存到一个文件中。你现在正处于训练模特的阶段。当您修改训练脚本时,Make 会识别出训练数据文件的时间戳没有更改,并跳过重新生成它的步骤。

现在,想象一下,在这个模型开发过程中,你了解到一个边缘情况,实际上是一个我们没有考虑到的预处理。很平常的事情。

自然,我们更新执行预处理操作的脚本。Make 会将此脚本识别为包含已处理数据的文件的先决条件,当您下一次运行命令来训练模型时,Make 会识别出需要重新创建数据。当所有先决条件都比目标旧时,Make 会跳过重建步骤。

在数据科学项目过程中,这种事情会发生几十次。让它更快可以大大提高你的迭代速度。Edmund Lau 在 《有效的工程师 中写道迭代速度在软件工程环境中的应用,但关键的要点是数据科学:“你迭代得越快,你就能学到越多。”(附属链接。)对我来说,避免这种不必要的重复已经使项目更有效率。

鼓励模块化

当然,您可以使用 Make 执行一个脚本来一次性完成所有这些工作。它对实施模块化没有任何作用,因为我们已经在这个例子中实现了它。

但是这些步骤都需要时间,你重复的次数越多,你的迭代速度就会越慢。谁愿意花额外的时间等待重新处理他们的数据?有很多方法可以避免这些额外的重新处理步骤,但是 Make 使它变得简单。

能够灵活地创建规则是非常强大的。例如,假设您正在比较几种型号配置。在一个神经网络中,你可以改变几个层中隐藏单元的数量,非常少的参数,等等。Make 提供了一种简单的方法来保存和比较这些数据。

在我们的主题建模例子中,开始时我们可能不知道有多少主题会导致一组可解释的、合理的主题。我们可以使用我们编写的模式规则快速训练我们的模型。又来了:

models/%_topics.png : src/models/fit_lda.py data/processed/NIPS_1987-2015.csv src/models/prodlda.py $(PYTHON_INTERPRETER) $< $(word 2, $^) $@ --topics $*

模式规则使得使用相同的通用执行模式来声明构建许多文件的规则变得容易。在我们的例子中,我们使用自动变量$*来包含在实际执行中匹配的词干,并且我们使用它作为我们的主题模型中的主题数量。结合一个虚假的目标all,我们可以使用一次make all训练多个模型:

all : models/10_topics.png models/20_topics.png

然后我们可以比较两者的输出。

显示来自 10 个主题的潜在狄利克雷分配模型的主题中的常用词的词云。图片作者。

显示来自 20 个主题的潜在狄利克雷分配模型的主题中的常用词的词云。图片作者。

利用公用事业

假设您有一千个文本文件,都有同样的 20 行样板文件需要删除。无聊吧?制作也很简单:

data/processed/%.txt : data/raw/%.txt sed 1,20d $^ > $@

就像我们之前依赖于curl一样,我们可以使用任何我们想要的东西来处理我们的数据,任何读取我们的 Makefile 的人都将确切地知道我们是如何创建这些处理过的数据的。简单、可重复且透明。

易于协作

至少在我的经验中,对于“我们如何让我们的模型去生产?”有很多答案。因为有组织和架构。很多(大部分?)组织拥有多年的数据,以及随之而来的遗留限制。这可能意味着,尽管我们很想采用最新的无缝 MLOps 框架,但我们会发现自己正在与软件或数据工程团队合作。

通常,这些团队通过经验被训练成“你能帮助 DS 团队部署这个吗?”通常翻译成,“你能把这个没有记录任何进入模型构建的假设的糟糕的笔记本,以某种方式把它变成一个可维护的构建过程吗?我把它扔过去的时候,你站在栅栏的对面!”

Make 并不是挥去这些挑战的魔杖。但是,在 Make 中编排的构造良好的模型管道已经将流程表达为 DAG。通过显式指定先决条件,Make 规则精确地显示了给定步骤之前必须做的事情。这样,即使我们的模型构建必须在其他环境中重建,我们的 Makefile 也给出了该过程的详细规范。

“当然,我们可以帮忙,”我们的软件或数据工程朋友开始说道,令人不寒而栗。"你能给我们介绍一下你的模型训练/构建过程吗?"

“总的来说,如果你克隆我们的回购协议,导航到项目并运行带有-B--recon标志的make all,它将向你展示所有的步骤是如何执行的。我来拍一个回购的链接稍等……”

当他们运行这个命令时—-B标志告诉 Make 只需改造所有的目标,即使它们没有过时——他们将看到我们为构建我们的模型所必须做的一切:

curl -o data/raw/NIPS_1987-2015.csv https://archive.ics.uci.edu/ml/machine-learning-databases/00371/NIPS_1987-2015.csv python3 src/data/transpose.py data/raw/NIPS_1987-2015.csv data/processed/NIPS_1987-2015.csv python3 src/models/fit_lda.py data/processed/NIPS_1987-2015.csv models/10_topics.png --topics 10 python3 src/models/fit_lda.py data/processed/NIPS_1987-2015.csv models/20_topics.png --topics 20

你为什么讨厌笔记本,你这个怪物?

我绝对没有,我发誓。它们作为交流工具非常有用——我宁愿在任何一天写或读一个光滑的笔记本,而不是一个幻灯片——对于互动,DS 思想笔记。“哦,当我画这个的时候,我们看到了一些共线性…让我们计算并打印 R 平方…”是我很多个早上喃喃自语的事情。我喜欢所有的笔记本,无论是 IPython 笔记本还是填充了不必要的高质量纸张的实体笔记本。(我是英语专业的。)

我想象珍妮特·温特森在写《激情》https://amzn.to/3HAlcFy的时候填满了几十个实体笔记本。(附属链接。然而,在这个过程的最后,她出版了一部简洁统一、美丽迷人的小说。你,我亲爱的数据科学家,同样也是一个有野心和远见的艺术家,当我们把最终的媒介和勾画出的让我们到达那里的想法混淆时,我们给自己造成了伤害。

抛开痛苦的隐喻不谈,笔记本是版本控制的噩梦。

包扎

我们已经了解了 Make 如何通过将您的工作流分解为模块化的块,并使所有这些块协调运行,从而使您的生活变得简单,并使流程易于重现。但是我们只是触及了 make 及其。这里有一些关于进一步发展这些技能的便利资源。

  • 完整示例项目展示了如何编写单独的脚本来完成上面讨论的每个步骤。
  • 它建立在这种千篇一律的基础上,这是一种惊人的自以为是的方式,以神话般的、合理的意见为基础建立项目。这本书非常值得一读,尤其是它简洁地论证了简单的约定如何提高建模的生产率和可重复性。
  • 这本伟大的书可以帮助人们深入了解 GNU Make 的全部功能,我们在这里只是涉猎了一下。(附属链接。)它的主要焦点是传统的软件项目,但这不是障碍:它全面地展示了 Make 的功能,并给出了全面的、有指导的介绍。如果你想避免仅仅从文档中学习新技术时遇到的麻烦,这可能是一本好书。

原载于 2022 年 3 月 1 日https://Easter . ai

使用 MLflow 跟踪机器学习实验

原文:https://towardsdatascience.com/using-mlflow-to-track-machine-learning-experiments-adbf27e9d36c

物流跟踪简介

图片来自 Unsplash

机器学习生命周期有各种各样的步骤,需要在生产的道路上加以考虑。简而言之,这绝不像训练您的模型一次,然后直接投入部署那么简单。 ML 是一个迭代的过程,你将需要许多周期的训练和所有的组件来获得你的用例的最佳模型。

MLflow 是一个开源平台,通过四个主要组件帮助端到端地管理这个 ML 生命周期: MLflow 跟踪MLflow 项目MLflow 模型模型注册在本文中,我们将具体了解如何使用 MLflow Tracking 管理/监控模型训练的不同迭代。我们将通过一个简单的 Sklearn 线性回归示例来了解波士顿住房数据集。如果你想直接跳到代码,这里是 Github 库

目录

  1. 安装/设置
  2. MLflow 跟踪示例
  3. 其他资源和结论

安装/设置

这里的设置非常简单,因为 MLflow 是一个开源包,您可以使用它。

安装 MLflow

如果您想获得 MLflow 文档的完整访问权限,请从他们的开始,其中还附带了一大组示例,涵盖了我们讨论过的所有主要组件。

要开始使用我们的模型,请创建一个示例 Python 文件,并在您的环境中添加以下包(如果没有安装的话)。

例如包装

MLflow 跟踪示例

我们现在可以开始构建将由 MLflow 跟踪的模型。在开始我们的模型构建之前,我们将创建一个主函数来捕获我们的模型和 MLflow 跟踪。在这个函数中,我们需要首先创建一个将被跟踪的 MLflow 实验。这可以通过对 MLflow 库使用 set_experiment 调用来完成。

创建 MLflow 实验

这里我们设置一个活动的实验来跟踪,如果这个实验不存在它是为我们创建的。现在,我们可以构建我们的模型,我们将使用 MLflow 进行跟踪。对于 Boston Housing 数据集,我们将使用 Sklearn 构建一个简单的线性回归模型。

模特培训

在模型训练之后,我们可以捕获一个样本度量,例如 RMSE ,我们可能希望根据我们脚本的不同迭代来监控/跟踪它。

RMSE 公制

然后,我们可以通过使用 log_metric API 调用,使用特定的 MLflow 来跟踪这个指标。这里,我们将 RMSE 指定给 log_metric 调用,以便 MLflow 在模型的迭代中进行跟踪。

使用 MLflow 跟踪指标

这里没有涉及的另一个关键特性是,您可以指定您希望 MLflow 跨实验跟踪的超参数。这可以通过在 log_param 中的类似调用来完成。这对于更复杂的算法尤其有用,当您在实验中对不同的超参数进行基准测试/比较时也是如此。

现在我们可以使用模型注册中心来记录我们的模型。MLflow 附带了一个专门为 Sklearn 模型制作的 API 调用

将模型记录到模型注册表中

现在,如果我们运行这个 Python 文件几次,我们应该会看到一个 RMSE 显示。与此同时,运行将被捕获到一个名为 mlruns 的目录中,该目录将在执行 Python 文件时创建。

mlruns 目录已创建(作者截图)

MLflow 的真正力量来自简单的 UI 界面,您可以利用它来可视化这些不同的实验。在与 Python 文件相同的目录中,在终端中运行下面的命令,您应该能够访问 UI。

打开 UI 后,您应该能够在侧面板上看到我们创建的实验。

实验已创建(作者截图)

如果我们点击这个实验,我们应该看到我们执行模型训练的不同运行/次数。我们还应该看到我们在 RMSE 记录的指标。

实验运行(作者截图)

随着实验变得越来越复杂,您可以添加更多的指标、参数,并轻松过滤这些运行,以获得最佳性能的模型。为了进行进一步的查询或可视化,您还可以下载一个 CSV 文件,使用 Pandas、Matplotlib 等库进行比较。

其他资源和结论

MLflow 是一个非常强大的工具,可以轻松跟踪您的大规模 ML 实验。当试图将你的 ML 生命周期推向生产时,考虑这样的工具是必要的。每一步都需要监控和维护,MLflow 使这一过程非常直观和易于使用。

我希望这是对 MLflow 和 MLOps 入门的有用介绍。在以后的文章中,我们将探索更复杂的例子和 MLflow 的其他特性,比如模型托管。

如果你喜欢这篇文章,请在 LinkedIn 上与我联系,并订阅我的媒体 简讯 。如果你是新手,使用我的 会员推荐 报名。

使用多阶段构建将您的 docker 映像缩小 10 倍

原文:https://towardsdatascience.com/using-multi-stage-builds-to-make-your-docker-image-almost-10x-smaller-239068cb6fb0

留下不必要的工具,清理你的 Docker 图片

如果你完成了建造,你需要清理你的工具

在这篇短文中,我们分多个阶段构建图像,以显著减小 docker 图像的大小。最终,我们会得到一个图像,做完全相同的事情,但是尺寸却小了 10 倍!我们这样做的方法是留下我们用来建立图像的工具。

首先,我们将创建一个 docker 文件,用于以“常规”方式构建图像。然后我们升级这个文件,利用一个额外的阶段,留下我们不必要的工件。最后,我们将通过试验不同的 docker 图像来进一步优化。让我们看看我们能减掉多少脂肪;我们来编码吧!

开始之前

我们使用了很多终端命令。不熟悉的话可以看看 这篇

设置

假设我们正在构建一个需要地理数据的应用程序。Open StreetMap 为我们提供了这种地理数据:通过geofabrik.de,我们可以以.osm.pbf格式下载每个国家的地理数据。接下来我们可以使用一个叫做Osmium的工具将这些文件合并在一起。想象一下,我们只需要将这个合并文件输入到我们的应用程序中。

总之:我们只需要使用curl(下载)和osmium(合并)一次:当我们获得合并后的文件时,我们就不再需要这些工具了。在两阶段构建中,我们在第一阶段下载这些工具,在那里使用它们,然后只将结果(合并的文件)带到下一阶段,留下工具。

在我们看到两阶段构建之前,让我们看看“正常”的方式。

1.普通的身材

在这一部分,我们将用最简单的方式来建立我们的形象。尽管如此,我们还是要记住一些小技巧来最小化我们的形象,详见本文https://mikehuls.medium.com/5-easy-to-implement-tricks-to-trim-down-your-docker-image-size-263978a6ed29。这是 dockerfile 文件:

FROM ubuntu:20.04# BUNDLE LAYERS
RUN apt-get update -y && apt install -y --no-install-recommends \
  curl \
  osmium-tool \
 && rm -rf /var/lib/apt/lists/*RUN mkdir /osmfiles \
 && mkdir /merged \
 && curl [https://download.geofabrik.de/europe/monaco-latest.osm.pbf](https://download.geofabrik.de/europe/monaco-latest.osm.pbf) -o /osmfiles/monaco.osm.pbf \
 && curl [https://download.geofabrik.de/europe/andorra-latest.osm.pbf](https://download.geofabrik.de/europe/andorra-latest.osm.pbf) -o /osmfiles/andorra.osm.pbf \
 && osmium merge /osmfiles/monaco.osm.pbf /osmfiles/andorra.osm.pbf -o /merged/merged.osm.pbf

这个 does 文件正是我们需要的:安装 curl 和 osmium,下载文件并合并它们。我们最终得到驻留在/merged中的合并文件。

注意,我们在获得合并文件后不做任何事情。出于本文的目的,我们将保持这个 docker 文件简单,并跳过对文件的实际操作。

让我们来看看这张图片的大小:

testimg:latest        a88a8848201b   16 seconds ago       96.9MB

正如你看到的图像大约是 97MB。在下一部分中,我们将使用多个阶段来升级 docker 文件。

本文清理了大量垃圾(图片由杰里米·贝赞格unsplash 上提供)

2.实现多阶段构建

在前面的例子中,我们只使用了一次构建工具(curl 和 osmium)。使用后,它们会留在我们的印象中。打个比方:你买了一辆新车,但发现所有用于制造汽车的工具都还在后备箱里,占据了宝贵的空间!

在这一部分,我们将重点放在留下我们用来制造汽车的工具。让我们检查新的 docker 文件并检查代码。

FROM ubuntu:20.04 AS final
FROM ubuntu:20.04 as build# BUNDLE LAYERS
RUN apt-get update -y && apt install -y --no-install-recommends \
  curl \
  osmium-tool \
 && rm -rf /var/lib/apt/lists/*RUN mkdir /osmfiles \
 && mkdir /merged \
 && curl [http://download.geofabrik.de/europe/monaco-latest.osm.pbf](http://download.geofabrik.de/europe/monaco-latest.osm.pbf) -o /osmfiles/monaco.osm.pbf \
 && curl [http://download.geofabrik.de/europe/andorra-latest.osm.pbf](http://download.geofabrik.de/europe/andorra-latest.osm.pbf) -o /osmfiles/andorra.osm.pbf \
 && osmium merge /osmfiles/monaco.osm.pbf /osmfiles/andorra.osm.pbf -o /merged/merged.osm.pbfFROM finalRUN mkdir /merged
COPY --from=build /merged /merged

如你所见,我们使用了两个阶段:一个称为构建,一个称为最终。在我们的建造阶段安装卷曲和锇。使用它们创建合并文件,最后只需将merged文件夹复制到final阶段。

一旦我们输入了FROM final,我们就“进入”了最后一个阶段,所以FROM final之后的任何事情都会在那里发生。二号线的FROM ubuntu:20.04 as build也是如此。请注意,curl 和锇只是在我们的构建阶段安装的。一旦我们到达 docker 文件的末尾,我们只保留我们所处的阶段。因为 final 阶段在 dockerfile 的末尾是活动的,所以 build 阶段中的所有内容都会被留下。让我们来看看新容器的尺寸:

testimgbetter:latest        7342ee3948e8   3 seconds ago    75.1MB

我们的新形象是 75MB :通过留下我们不再需要的工具,我们节省了超过 20MB 或不到 20%的空间。这已经是非常好的改进了。让我们来看看如何进一步优化。

3.具有较小最终图像的多阶段

在前一部分中,我们在构建和最终阶段都使用了ubuntu:20.04图像。对于构建阶段,这是非常合理的;在这里我们做复杂的事情:安装包,下载东西,git 克隆,在其他情况下甚至可能使用cmakemake来编译代码!然而,最终阶段并不需要像构建阶段那么智能。

这一部分的想法是,你需要一个大而丰富的图像,像复杂的构建阶段的ubuntu:20.04。然后,我们可以将来自构建阶段的结果复制到基于更简单图像的最终阶段。在我们的例子中,我们甚至可以使用非常小的图像,如 Alpine。让我们稍微调整一下我们的 docker 文件:

FROM alpine:3.14 AS final

现在,一旦构建阶段完成,我们只需将合并后的文件复制到 alpine 阶段。让我们检查一下新构建的图像的总大小:

testimgbetter                                                latest        8ad3278671e1   17 minutes ago   7.95MB

我们从 75MB 降到了略低于 8MB: 几乎减少了90%!****

请注意,使用 alpine 图像并不总是有效;本文这一部分的想法是,我们可以使用比构建阶段更小的最终图像。

整洁有序(图片由谷仓图片unsplash 上拍摄)

结论

在本文中,我们重点了解了 docker 构建的方式,并通过删除所有我们不再需要的工具来最小化 docker 映像。我们可以通过多个阶段非常有效地做到这一点:执行复杂的任务,如在丰富的映像中安装和构建,然后将结果复制到更精简的最终映像中,并留下构建阶段。

本文中的例子非常少;只留下了curlosmium。有这样的例子,我使用了一个更大的构建阶段,并通过留下我们不再需要的工件,使节省了超过 1.5GB** ,图像大小从 1.8GB 变为 0.3GB !**

我希望这篇文章是清楚的,但如果你有建议/澄清,请评论,以便我可以做出改进。与此同时,看看我的 其他关于各种编程相关主题的文章 ,比如:

编码快乐!

—迈克

页(page 的缩写)学生:比如我正在做的事情?跟我来

**https://mikehuls.medium.com/membership **

使用自然语言处理和文本分析对政治文本进行聚类

原文:https://towardsdatascience.com/using-nlp-and-text-analytics-to-cluster-political-texts-41fdb2c3a325

NLTK 和古根海姆项目文本的 scipy

在文本分析的保护伞下,有许多 python 包可以帮助我们以产生有趣结果的方式分析当前和历史文本。在这个项目中,我试图用余弦相似性聚类法对跨越数千年的政治著作进行分类。我使用的具体标题是:

马基雅维利的《君主论》

汉密尔顿、麦迪逊和杰伊的《联邦党人文集》

恩格斯的《英国工人阶级的状况》

摩尔的《乌托邦》

戈德曼的《无政府主义和其他论文》

霍布斯的《利维坦》

《美国宪法》

密尔的《代议制政府的思考》

马克思和恩格斯的《***宣言》

亚里士多德的《政治学》

洛克的《政府第二论》

第一步是下载文本语料库(直接从古根海姆项目的网站下载)。然后,我需要导入文件,并在文本文件中找到每项工作的开始和结束行。* 古根海姆项目文件下载为。txt 文件,并在实际工作的上下有额外的行。*

*# For loop to find the start and end points of the books*
**for** i **in** book_id:
    ...
    a **=** df**.**line_str**.**str**.**match(r"\*\*\*\s*START OF (THE|THIS) PROJECT")
    b **=** df**.**line_str**.**str**.**match(r"\*\*\*\s*END OF (THE|THIS) PROJECT")
    ...

使用 for 循环和其他字符串操作,我匹配了它们的公共开始和结束元素。txt 文件,以便确定每个文学作品的起点和终点。

作者图片

Doc&Library Tables:
下面的 regex 部分搜索每本书以分离出章节,从而开始创建 OCHO(内容对象的有序层次结构)样式表。

 OHCO **=** ['book_id', 'chap_num', 'para_num', 'sent_num', 'token_num']roman **=** '[IVXLCM]+'
caps **=** "[A-Z';, -]+"
chap_pats **=** {
    5: {
        'start_line': 40,
        'end_line': 680,
        'chapter': re**.**compile("^ARTICLE\s+{}$"**.**format(roman))
    },
    61: {
        'start_line': 19,
        'end_line': 1529,
        'chapter': re**.**compile("^\s*Chapter\s+{}.*$"**.**format(roman))
    },
    1232: {
        'start_line': 1000, *# This was mannually found through trial and error becuase the previous start had issues*
        'end_line': 4828,
        'chapter': re**.**compile("^\s*CHAPTER\s+{}\."**.**format(roman))
    },
    ...
}

现在我们已经有了 book_id 和章节的分类,我们可以填充每个文本层次结构的其余部分。我们按照熊猫和字符串操作将每一章分解成段落。生成的表称为 Doc 表,将在后续步骤中使用。我们还能够分离出标题和作者来完成库表。

在下面的图片中,我们可以看到一个显示图书 id、章节、段落编号和字符串的 doc 表示例。以及完整的库表。

作者图片

作者图片

Token & Vocab 表
为了进一步分解 Doc 表,我们需要定义每个单独的术语。下面的函数接收我们的 Doc 表,将每个段落的字符串解析成句子,然后将这些句子解析成单独的术语。这个操作主要是通过 nltk 包使用 nltk.sent_tokenize、nltk.pos_tag、NLTK 来完成的。WhitespaceTokenizer 和 nltk.word_tokenize。

*# Tokenize doc table to derive TOKEN table*
**def** tokenize(doc_df, OHCO**=**OHCO, remove_pos_tuple**=False**, ws**=False**):

    *# Paragraphs to Sentences*
    df **=** doc_df**.**para_str\
        **.**apply(**lambda** x: pd**.**Series(nltk**.**sent_tokenize(x)))\
        **.**stack()\
        **.**to_frame()\
        **.**rename(columns**=**{0:'sent_str'})

    *# Sentences to Tokens*
    *# Local function to pick tokenizer*
    **def** word_tokenize(x):
        **if** ws:
            s **=** pd**.**Series(nltk**.**pos_tag(nltk**.**WhitespaceTokenizer()**.**tokenize(x)))
        **else**:
            s **=** pd**.**Series(nltk**.**pos_tag(nltk**.**word_tokenize(x)))
        **return** s

    df **=** df**.**sent_str\
        **.**apply(word_tokenize)\
        **.**stack()\
        **.**to_frame()\
        **.**rename(columns**=**{0:'pos_tuple'})

    *# Grab info from tuple*
    df['pos'] **=** df**.**pos_tuple**.**apply(**lambda** x: x[1])
    df['token_str'] **=** df**.**pos_tuple**.**apply(**lambda** x: x[0])
    **if** remove_pos_tuple:
        df **=** df**.**drop('pos_tuple', 1)

    *# Add index*
    df**.**index**.**names **=** OHCO

    **return** dfTOKEN **=** tokenize(DOC, ws**=True**)*# Creating VOCAB table and adding columns to TOKEN data*
...

作者图片

上面有我们的令牌表并不意味着我们完成了。我们现在需要在这个表的基础上创建一个 Vocab 表来索引我们的文本语料库中使用的每个术语。使用字符串和矩阵操作,以及 NLTK 模块的 PorterStemmer()函数,我们进一步构建我们的令牌表并创建我们的 Vocab 表。

下面分别是令牌表和 Vocab 表的示例。从我们的令牌表中可以看出,每个术语现在都对应于一个编目的 term_id,可以使用我们新的 Vocab 表来引用它。此表提供了术语 id、术语、术语词干、在整个语料库中出现的次数、停用词区别以及最常见的词性。

作者图片

作者图片

TFIDF 表:
接下来,我们需要使用这些表来创建一个 TFIDF 表。TFIDF 表根据每个文本内的使用频率和语料库中文档之间的使用频率来衡量每个术语。此表的目的是为我们提供最有影响力和最有用的分析单词的结果,同时减少停用词或其他常用词的影响,否则这些词会一直使用,如“and”、“of”、“The”等。请注意,我们在代码块的开头定义了不同的包组织,这些可以用来在不同的层次元素上创建 TFIDF 表。

SENTS **=** OHCO[:4]
PARAS **=** OHCO[:3]
CHAPS **=** OHCO[:2]
BOOKS **=** OHCO[:1]**def** tfidf(TOKEN, bag, count_method, tf_method, idf_method):

    *#Create Bag of Words and DTCM*
    BOW **=** TOKEN**.**groupby(bag**+**['term_id'])**.**term_id**.**count()\
        **.**to_frame()**.**rename(columns**=**{'term_id':'n'})
    BOW['c'] **=** BOW**.**n**.**astype('bool')**.**astype('int')
    DTCM **=** BOW[count_method]**.**unstack()**.**fillna(0)**.**astype('int')

    *# TF calculations, will comment out all but 'sum' given that will be what I use*
    **if** tf_method **==** 'sum':
        TF **=** DTCM**.**T **/** DTCM**.**T**.**sum()

    TF **=** TF**.**T
    DF **=** DTCM[DTCM **>** 0]**.**count()
    N **=** DTCM**.**shape[0]

    *# IDF calculations, will comment out all but 'standard' given that will be what I use*
    **if** idf_method **==** 'standard':
        IDF **=** np**.**log10(N **/** DF)

    *# create TFIDF table*
    TFIDF **=** TF ***** IDF
    **return** TFIDFTFIDF **=** tfidf(TOKEN, BOOKS, 'n', 'sum', 'standard')*# Add tfidf_sum column to Vocab table*
VOCAB['tfidf_sum'] **=** TFIDF**.**sum()

正如我们在下面的 TFIDF 表的图像中所看到的,我们得到了一个非常稀疏的结果,因为我们当前包含了在语料库中找到的每个单词。在尝试最终的聚类分析之前,我们需要进一步减少这种情况。

作者图片

此外,在将该 TFIDF 表的求和原则添加到 Vocab 表,并对最高 tfidf_sum 值的结果进行排序之后,我们得到了具有最具影响力的单词的表:

作者图片

不出所料,最有影响力的词与政治术语有关,这符合我们的预期,因为语料库由各种政治著作组成,像“and”或“the”这样的正常使用的词可能无关紧要。

为了帮助简化我们的 TFIDF 表,我们将把 number 列减少到最常见的 4000,这是由上面的 Vocab 表中的 tfidf_sum 列排序的。

聚类:
现在我们已经得到了最终的表,我们可以开始聚类过程并查看我们的结果。下一个代码块在每个作品之间创建配对,比较它们的距离和相似性度量,将文档聚类为最相似/最近的距离。出于本文的目的,我将只展示余弦相似性集群,但是您也可以运行这个代码块中包含的其他测试(cityblock、euclidean、jaccard、dice、correlation 和 jensenshannon)。实际的相似性/距离计算是使用 scipy 的空间距离模块和 pdist 函数运行的。

*# Normalize the TFIDF table, create PAIRS and lists for PAIRS testing*
**def** tab_doc_tab(TFIDF_table, Doc_table):
    L0 **=** TFIDF_table**.**astype('bool')**.**astype('int')
    L1 **=** TFIDF_table**.**apply(**lambda** x: x **/** x**.**sum(), 1)
    L2 **=** TFIDF_table**.**apply(**lambda** x: x **/** norm(x), 1)
    PAIRS **=** pd**.**DataFrame(index**=**pd**.**MultiIndex**.**from_product([Doc_table**.**index**.**tolist(), Doc_table**.**index**.**tolist()]))**.**reset_index()
    PAIRS **=** PAIRS[PAIRS**.**level_0 **<** PAIRS**.**level_1]**.**set_index(['level_0','level_1'])
    PAIRS**.**index**.**names **=** ['doc_a', 'doc_b']
    tfidf_list **=** ['cityblock', 'euclidean', 'cosine']
    l0_list **=** ['jaccard', 'dice','correlation']
    l1_list **=** ['jensenshannon']
    **for** i **in** tfidf_list:
        PAIRS[i] **=** pdist(TFIDF_table, i)
    **for** i **in** l0_list:
        PAIRS[i] **=** pdist(L0, i)
    **for** i **in** l1_list:
        PAIRS[i] **=** pdist(L1, i)
    **return** PAIRSPAIRS **=** tab_doc_tab(TFIDF_cluster, DOC)

最后,我们运行聚类函数并显示余弦相似性测试的结果。我们使用 scipy 模块的 scipy.cluster.hierarchy 函数来创建我们的聚类图的框架,并使用 matplotlib 来显示下面的实际聚类。

*# Create Clusters*
**def** hca(sims, linkage_method**=**'ward', color_thresh**=**.3, figsize**=**(10, 10)):
    tree **=** sch**.**linkage(sims, method**=**linkage_method)
    labels **=** list(DOC**.**title**.**values)
    plt**.**figure()
    fig, axes **=** plt**.**subplots(figsize**=**figsize)
    dendrogram **=** sch**.**dendrogram(tree, 
                                labels**=**labels, 
                                orientation**=**"left", 
                                count_sort**=True**,
                                distance_sort**=True**,
                                above_threshold_color**=**'.75',
                                color_threshold**=**color_thresh
                               )
    plt**.**tick_params(axis**=**'both', which**=**'major', labelsize**=**14)hca(PAIRS**.**cosine, color_thresh**=**1)

作者图片

上面的图片是我们看到每个作品的聚类以及每个作品之间的紧密联系的结果。基于我们的结果,我们可以看到余弦相似性聚类提供了相当准确的结果。有三大类,我称之为西方政治哲学、美国政治哲学和共产主义政治哲学。每个文本都与相似的文本适当地聚集在一起,来自相同作者的一些文本更紧密地聚集在一起,例如恩格斯的文本。

结论:
我们从。txt 文件,创建了各种文本表,如 Library、Token、Vocab 和 TFIDF 表。一旦我们得到了我们需要的所有表格,我们运行余弦相似性度量,并根据相似的作品对文本进行聚类。这个项目最终能够找到我们所期望的三个集群,给定我们对作者的了解,以及每个政治文本的主题。

这个项目的完整代码可以在我的 GitHub 页面上找到:https://GitHub . com/nbehe/NLP _ texts/blob/main/Beheshti _ project _ code . ipynb

这个项目是我的文本分析课程的一个大项目的一小部分。我将为该项目的其他部分撰写额外的文章,其中我们将研究其他非监督模型,如主成分分析、主题建模和情感分析。与文本分析一样,尤其是本文,大部分代码都致力于创建运行这些模型所需的表。我选择保留所有代码,而不是只保留一部分,这样其他人就可以看到我的所有步骤。来自这个项目的代码是我自己写的,由讲师提供的,以及通过课程材料提供的作品的混合物。

使用 Panel 和 hvPlot 可视化几十年的野火

原文:https://towardsdatascience.com/using-panel-and-hvplot-to-visualize-decades-of-wildfires-2ac8d6f5db25

让我们建立一个仪表板,使用 Holoviz 工具了解更多关于 1992 年至 2015 年期间发生的数百万起野火的信息

马特·帕尔默Unsplash 上拍照

我最近写了一篇关于使用 Python 库 Panel 制作数据仪表板的文章。我们创建了一个简单的仪表板,允许我们可视化数据集每年的各种汇总图。在这篇文章中,我想在这些技能的基础上,使用 Panel 制作一个仪表板来查看美国野火记录的时间序列。

我们的数据集包括 1992 年至 2015 年间发生在美国的超过 180 万起野火。我们的目标是构建一个数据仪表板,帮助我们更好地了解这些野火的原因、发生地点等。

数据准备

由作者创建

导入我们的数据非常简单。我们可以使用sqlite3连接到我们的 SQLite 数据库,并查询我们想要处理的信息。这些信息被存储在一个熊猫数据框中,这样我们就可以删除阿拉斯加和夏威夷,使绘图更容易。最后,我们可以通过从火灾数据中减去发现火灾的日期来计算燃烧时间。

绘制野火地图

让我们从绘制所有火灾的地图开始,这样我们就可以看到空间分布。我们可以使用 hvPlot 轻松做到这一点,因为它利用了 Datashader 来光栅化我们的 180 万个点,这使得它们更易于渲染。

由作者创建

你可以使用贴在这里的互动笔记本与这些情节互动。令人印象深刻的是,使用 hvPlot 创建高质量的空间可视化是多么容易。由于它利用了其他 Holoviz 库——holo views、Geoviews、Datashader 和 Colorcet——它简化了创建交互式地图所需的许多常规步骤,该地图仍然可以响应大型数据集。

带有时间滑块的简单仪表板

用静态图来可视化时间序列数据可能会很烦人,然而,Panel 的小部件让我们可以使用各种方法来操作和子集化我们的数据,比如选择年份的滑块。

制作带面板的仪表板有三个步骤:

  1. 定义一个小部件,比如选择年份的整数滑块或下拉列表。
  2. 定义一个绘图函数,将滑块中的年份值作为输入。
  3. 布局和渲染我们的仪表板。

由作者创建

制作简单仪表板的快速方法

当我们只有一个图和一个小部件要显示时,布置仪表板似乎需要很多额外的代码。对于这样一个简单的仪表板,我们可以使用.interactive来制作数据帧和/或数据管道的交互式副本。

要了解这是如何工作的,让我们看看是否可以制作一个小部件,让我们选择火灾的原因,并让地图只显示这些火灾。

由作者创建

令人高兴的是,我们不必担心我们的仪表板的布局,而是可以定义我们的小部件和附带的绘图。在这篇文章中,我们不会关注交互式数据框架,但是,我认为在创建这些不太复杂的仪表板时提到它们是有好处的。

构建我们的仪表板

我们有了基本的地图,但仪表板最强大的部分之一是能够一次看到我们数据的多个方面。让我们创建一些地块来配合我们的地图,然后使用面板将它们连接在一起。

火灾规模

我们可以从绘制每个尺寸等级发生的火灾数量开始;野火按燃烧面积大小分类,A 级最小,G 级最大。

由作者创建

我们所有的绘图函数都将年份作为参数,这样当滑块值改变时,它们可以对数据进行子集化。这里,我们可以使用子集化的数据按照大小分类对数据帧进行分组,并使用.size()来计算每组中的火灾数量。

按原因划分的燃烧时间

我们接下来的两个图都将着眼于野火的原因;野火有许多原因,其发生频率和潜在危险有很大差异。让我们先来看看 2003 年每场野火的平均燃烧时间。

由作者创建

按原因划分的火灾数量

现在,不考虑燃烧时间,让我们简单地看看 2003 年各组引起的火灾数量。

由作者创建

我们可以看到两个图之间的一些有趣的差异,比如吸烟是如何导致第二长的野火,而它在引起的野火总数中排名第九。除了闪电,燃烧时间最长的火灾并不是最频繁发生的。

当我们思考是什么让野火难以扑灭时,这是有意义的;如果火灾发生在更偏远的地方,那么很难在早期控制住火势。闪电、吸烟和营火都有可能在这类地区引发火灾,这些地区有大量的木材可以燃烧,周围很少有人报告烟雾的最初迹象。

也有一些年份可能看起来很不一样,比如 2006 年,少量的输电线故障导致了平均持续几天的大火灾。让我们来看看:

由作者创建

这里需要注意的是 Panel 和 hvPlot 如何识别这两个图共享相同的 y 轴,并重新组织第二个图以匹配第一个图。尽管我对两个数据帧都进行了排序,所有这一切还是发生了,所以如果你想避免这种情况,就在你的图中添加.opts(axiswise=True)

把它放在一起

现在,我们已经有了小部件和一些要在我们的时间序列中探索的图,我们可以继续创建我们的仪表板了!

由作者创建

就这样,我们有了一个工作仪表板!我知道 gif 并不理想,所以一定要检查一下互动笔记本,这样你就可以自己摆弄这些情节了。撇开 gif 不谈,考虑到我们这么快就把它组合在一起,这是一个相当不错的仪表盘。

改进的余地

我确信我们可以通过简化绘图函数中运行的代码来提高响应速度。对于那些我不想解释每一行代码是做什么的文章,我会尽量让代码片段可读性更好。

例如,当通过year对数据帧进行子集化时,会有很多重复;我们可能会在每次year值改变时对数据帧进行一次子集化,然后将子集化后的数据传递给每个绘图函数。

包扎

除了制作实际的情节之外,我们几乎不需要付出任何努力,当谈到将我们的情节“链接”在一起时,hvPlot 和 Panel 可以为我们做的事情非常酷。

Holoviz 工具有时会让人感到有些畏惧,因为它们已经创建了一个庞大且相互关联的生态系统,有许多方法可以产生类似的可视化效果。在 Python 中使用数据可视化时,需要记住的一件关键事情是,总有许多工具和方法可以实现您的目标。有些可能比其他人有优势,但重要的是开始尝试它们。

资源:

注: 如果您喜欢在 Medium 上阅读我和其他人的内容,请考虑使用下面的链接订阅,以支持这样的内容创建,并解锁无限的故事!

https://medium.com/@willmnorris/membership

引文

数据集:野火(公共领域 CC0)

短,凯伦 C. 2017。美国 1992–2015 年的空间野火发生数据[FPA FOD 20170508]。第四版。柯林斯堡,CO:林务局研究数据档案。【https://doi.org/10.2737/RDS-2013-0009.4

使用 Panel 在 Python 中构建数据仪表板

原文:https://towardsdatascience.com/using-panel-to-build-data-dashboards-in-python-e87a04c9034d

让我们使用 Holoviz 的 Panel 构建一个交互式数据仪表板

Unsplash 上的路径数码拍摄

Holoviz 正迅速成为一套流行的 Python 绘图库,适用于初学者和老手。Holoviz 由几个包组成,目的是使可视化数据变得更容易,而不管数据类型如何,同时提高绘图的质量。其中一些库包括 hvPlot、Panel、HoloViews 和 GeoViews。

在本帖中,我们将学习如何使用 Panel 来创建交互式数据面板和 web 应用程序。Panel 很快成为我最喜欢的仪表板工具之一,因为它很容易学习基础知识,同时还有足够的深度来创建更复杂的应用程序。它还非常灵活地支持您可以使用的绘图类型,这意味着如果您已经使用 follow 创建了一个地图,您不必花费时间使用另一个工具重新构建它,以将其集成到仪表板中。

为什么选择仪表板?

数据仪表板并不是新的实践;多年来,企业一直使用仪表板来跟踪和了解他们的数据。仪表板通常是用 JavaScript 或无代码软件构建的,然而,有了 Panel 这样的新库,使用 Python 创建仪表板变得前所未有的简单。在整个工作流程中选择使用相同的语言可以帮助节省大量时间,尤其是在您进行原型设计的时候。

仪表板提供了几个关键的好处,如交互性可定制性可共享性。因为我们可以定义小部件,比如改变时间序列中年份的滑块,所以我们可以进行有意义的交互,帮助用户看到数据的变化。

能够自定义布局还允许混合不同种类的图或使用多个图来显示数据集的不同维度。最后,可分享性。Panel 可以轻松地将仪表板部署为 web 应用程序,这意味着您可以快速地与更多用户在线共享您的工作。

材料

我们在本帖中创建了几个互动的情节,然而,它们不容易直接嵌入媒体,所以你可以跟随这个互动 Jupyter 笔记本

小组介绍

在我们开始使用 Panel 创建第一个仪表板之前,让我们先快速讨论一下它的基本工作原理。

成分

使用 Panel 制作的应用程序由三个主要组件组成:

  • 窗格:窗格是你的地块的渲染视图。您的绘图可以是用许多流行的 Python 绘图库创建的对象。窗格本质上只是将现有的绘图转换成 Panel 可以处理的对象。
  • 小部件:小部件允许用户输入,比如点击或输入数据,允许用户与应用程序交互。
  • 面板:面板是一个容器,它包含了组成你的应用程序的窗格和小部件。面板是分层的,这意味着面板中可以有面板。面板还可以将元素组织成行和列、选项卡或网格。

这三个对象是面板仪表板的基本构件。我们将绘图转换成窗格,创建小部件来控制变量,然后将它们组织成一个面板——或一组面板。

蜜蜂

Panel 的主要优势之一是它的灵活性。我们可以使用 Panel 在 15 分钟内创建一个快速仪表板,如果这个概念可行,我们可以继续定制我们的仪表板,甚至将其集成到一个更大的软件项目中,而不必重新开始使用新工具。

Panel 通过提供四种不同 API 的选项,能够提供快速绘图和更复杂的功能。在这篇文章中,我们将关注其中的两个:交互函数和反应函数。

交互 API

交互功能是开始构建交互式仪表板的最简单的方法。您只需提供一个函数,然后 Panel 就会生成与之交互所需的 UI。我们可以用 Jupyter 笔记本内部的面板创建一个简单的交互功能,如下所示:

由作者创建

这里我们创建一个简单的函数,它返回一个数的乘积,然后我们告诉 Panel 我们想要与这个函数交互。现在,x的值根据滑块改变,我们可以使用x作为函数的参数,使它们响应我们的交互。

反应功能

交互功能之所以如此简单,是因为它们为我们创建了 UI 元素,就像上面例子中的滑块一样。反应式功能允许我们分别构建 UI 元素和功能,并决定如何链接它们。

我们可以使用流行的帕尔默企鹅数据集制作一个快速的示例图,如下所示:

由作者创建

这里有三个步骤:

  1. 声明我们的两个选择小部件,这将让我们选择我们的任何列作为 x 轴或 y 轴。
  2. 使用嵌套的行和列,pn.Row(pn.Column(<TITLE AND WIDGETS>), <PLOT>),把我们的内容组织成一行,但把它分成两列。
  3. 使用pn.bind()将我们的散点图与我们的两个小部件连接起来,然后 Panel 为我们处理剩下的部分。

从图中单独声明窗口小部件可以让我们更好地控制 UI 元素的样式和组织,与使用交互函数相比,唯一的缺点是必须编写一些额外的代码。我真的很喜欢反应式函数更具声明性,这使得每个元素要做什么变得非常清楚。

创建带有面板的仪表板

我们将在本帖中使用的数据集包括 2020 年至 2022 年全球数据科学工作的信息。数据集中大约有 600 个作业,这对于一个例子来说是一个很好的大小,不管我们决定制作什么样的情节,浏览器都不会很难加载这个例子。

让我们将数据导入 Python,看看我们在处理什么。

由作者创建

我们可以看到,我们有员工的经验水平、工资、远程工作比率以及公司和员工的位置等信息。我们可以从中制作出很多有趣的图表,所以让我们创建一些来研究数据,然后填充我们的仪表板。

基本情节

Medium 不支持直接上传 HTML,所以文章中的情节不是交互式的。如果你想与剧情互动,你可以跟随这个互动笔记本

让我们从导入 hvplot pandas 库开始,并将 holoviews 扩展设置为 bokeh。我是 Plotly 的长期粉丝,这也是 holoviews 扩展的一个选项,但我真的很喜欢 bokeh 的默认样式。

我们先做一个简单的箱线图来看一下工资根据经验水平的分布。

由作者创建

这里我们可以看到 hvPlot 是如何取代 DataFrame 的正常绘图功能的。现在,对于其余的图,我希望能够按年份对数据进行子集划分,因此我们将使用以年份作为参数的函数。我们的下一个图将是一个条形图,显示按经验水平划分的平均工资。

由作者创建

现在,我们可以调用plot_bars1(2021)将数据子集化为当年的工作。你可能已经明白我的意思了…

现在我们有了一个函数,它将年份作为参数并返回一个图,我们准备将它连接到一个滑块来控制我们的年份。当我们可以用 Panel 创建一个简单的 UI 时,为什么要用不同的输入手动运行函数呢?

年份滑块

我们将创建我们的年份滑块,很像反应式函数演示中的选择菜单,只是使用了不同类型的小部件。我们还可以创建一个简单的函数来显示 markdown 中的年份,这样我们就可以看到如何将滑块的值链接到函数的输入。

将小部件的值链接到函数有两种主要方法,要么是我们上面看到的pn.bind()方法,要么是我们在本例中看到的@pn.depends()装饰器。这两种方式都可以,但是我很喜欢装饰者立即声明依赖关系,而不是等到展示仪表板的时候。

现在,在 Jupyter 笔记本中,我们可以呈现这些,并查看滑块的值如何改变显示降价的函数的输入值。

由作者创建

如果你想知道,value_throttled只是告诉面板不要重新运行该功能,直到你释放你的鼠标;这可以防止当您将滑块移动几个槽口时它运行多次。

最终剧情!

我们已经有了我们的基础设施,现在我们只需要制作几个图来填充我们的仪表板,并将它们连接到我们的滑块。让我们看看哪些国家在某一年的平均工资最高。

由作者创建

我知道配色方案不是很好,但我想给近 60 个国家中的每一个国家分配一种独特的颜色,这样当一年发生变化时,不同国家的颜色就不会重复。

你可能已经注意到,这是根据雇员居住的国家来计算平均工资,但是公司所在地呢?我还创建了一个功能来完成几乎相同的任务,除了使用公司位置。

由作者创建

盘面设计

现在我们已经准备好把它们放在一起了。Panel 支持多种排列对象的方式。在这个例子中,我们将使用行和列,因为这是最直观的开始。我们可以在彼此内部嵌套任意多的行和列。

我们将使用一个WidgetBox作为我们的容器,它基本上只是一个Column,但有一点额外的样式,使里面的项目更明显地组合在一起。我还使用了pn.bind()来展示将年份滑块附加到每个绘图上的效果。

由作者创建

我们终于在 Jupyter 笔记本上有了一个正常工作的仪表盘!一旦掌握了这三个基本步骤,您可能会发现使用 Panel 创建交互式仪表板有多快——创建绘图功能 , 定义小部件,以及定义布局

当然,仪表板可以比这复杂得多,但是,像为时间序列数据集的年份添加一个滑块这样简单的事情可以对数据可视化的影响产生巨大的影响,特别是如果您想与没有编码的任何人共享它。

本地提供仪表板

要在本地托管我们的仪表板,我们所要做的就是从您的工作目录在终端中运行panel serve xxxx.ipynb。现在您可以在localhost通过浏览器访问它。

包扎

我们在这篇文章中讨论了很多,但是我希望我们如何使用 Panel 来制作交互式仪表板的核心概念是清楚的。这篇文章仅仅触及了使用 Panel 和其他 Holoviz 库所能做的事情的表面。我们可以从像 Folium 这样的库中添加地图,或者我们可以配置一个套索样式的选择工具,以允许查看者自己选择数据子集。幸运的是,Holoviz 拥有优秀的文档,我将在未来几周内在 Panel 上发布一系列帖子。

资源

注: 如果你喜欢在 Medium 上阅读我和其他人的内容,可以考虑使用下面的链接订阅,以支持这样的内容创作,并解锁无限的故事!

https://medium.com/@willmnorris/membership

数据引用

艾莉森·玛丽·霍斯特。(2020).帕尔默企鹅:帕尔默群岛(南极洲)企鹅数据。https://doi.org/10.5281/zenodo.3960218

使用 PCA 将神经网络中的参数数量减少 30 倍

原文:https://towardsdatascience.com/using-pca-to-reduce-number-of-parameters-in-a-neural-network-by-30x-times-fcc737159282

同时还能获得更好的性能!—神经网络和深度学习课程:第 17 部分

UnsplashMagicPattern 原图,作者编辑

之前的文章中,我们创建了一个多层感知器(MLP)分类器模型来识别手写数字。

我们为网络架构使用了两个具有 256 个神经元的隐藏层。即使是这样一个小网络,我们总共得到了 269,322 个参数(权重和偏差项)。获得如此大量的网络参数的主要原因是输入层的大尺寸。

因为 MLP 的输入图层采用 1D 张量,所以我们需要将二维 MNIST 影像数据重塑为一维数据。这个过程在技术上叫做 展平 图像。

(图片由作者提供,用 draw.io 制作)

图像中的每个像素代表一个输入。如果图像中有 784 个像素,我们在 MLP 的输入层需要 784 个神经元。当输入层的大小增加时,我们得到网络中大量的总参数。这就是为什么 MLP 不是参数有效的。当我们使用高像素值图像数据(例如 500 x 500 px 图像)时,输入层的大小会显著增加。

获得网络中大量总参数的问题是,我们需要大量计算资源来训练神经网络,并且这也是耗时的。

MLP 不适用于图像数据。还有一种更好的神经网络架构,称为卷积神经网络(CNN 或 ConvNets) ,用于处理图像数据。它的输入层可以获取高维数据,因此我们不需要展平图像。

如果有一种方法可以减少图像中的像素数量,而不会损失太多的图像质量,我们仍然可以有效地将 MLPs 用于图像数据。如果我们能做到这一点,输入层的大小将会非常小,MLP 模型将是参数有效的。

我们可以将主成分分析(PCA) 应用到展平的图像中,以显著减少像素数量,同时仍然获得几乎相同的图像质量,但现在像素更少了!

一般的工作流程是:

(图片由作者提供,用 draw.io 制作)

首先,我们将 PCA 应用于 MNIST 数字数据,并在保持 91.4%图像质量的同时将维数(像素数)减少了 7.48 倍!然后,我们建立我们在第 16 部分中建立的相同的 MLP 模型。现在,输入的形状是(100,),而不是(784,)。这将显著减少网络中总参数的数量。此外,我们有机会减少隐藏层中的神经元数量,因为现在输入大小很小。这也将有助于显著减少网络中的总参数数量。

将主成分分析应用于 MNIST 数据

如果您通过 Keras API 获取 MNSIT 数据,您将为训练集获得 60,000 幅手写数字灰度图像,为测试集获得 10,000 幅手写数字灰度图像。每个灰度图像都是二维的,宽 28 像素,高 28 像素。

(图片由作者提供)

我们需要在应用 PCA 之前展平这些图像。展平后,我们得到:

(图片由作者提供)

我们可以将这个新的训练集图像数组视为一个简单的数据集,它有 60,000 行(图像数)和 784 列(图像中的像素数)。现在,我们可以像往常一样应用 PCA。注意,我们将 PCA 分别应用于训练集和测试集。

当考虑单个展平的图像时,像素的数量代表它的维度。当我们降低维度时,我们减少了图像中的像素数量。

对 MNSIT 数据进行主成分分析,选择最佳主成分数(作者代码)

运行上述代码后,我们得到以下输出。

选择最佳主成分数(图片作者提供)

我们可以看到,前 100 个分量捕获了原始图像数据中大约 90%的可变性。这足以保持原始图像的质量。因此,我们选择了前 100 个组件,并对所选组件的数量再次应用 PCA。

用所选组件再次应用 PCA(作者代码)

(图片由作者提供)

我们将原始图像数据的维数减少了 7.84 倍(784/100),同时保持了原始数据中 91.43%的可变性。

图像样本:应用 PCA 前后(图片由作者提供)

压缩后的图像依然清晰可辨!

注意:这里不打算多解释 PCA 过程。敬请参考下面的文章系列(我写的),了解 PCA 和降维的一切。

点击此图进入我的 PCA 与降维特辑(作者截图)

使用压缩(缩减)的图像数据构建 MLP 分类器

现在,我们建立与在第 16 部分中相同的 MLP 分类器模型,但是使用压缩的图像数据(更少的维度和更少的像素)!

(作者代码)

(图片由作者提供)

这次我们得到了 0.9745 的准确度分数。之前(即在应用 PCA 之前),我们得到了 0.9804 的准确度分数。我们只损失了很少的准确度分数。也就是 0.0059。

如果我们现在检查总参数的数量,我们会得到:

MLP.summary()

应用 PCA 后的可训练参数数量(图片由作者提供)

先前(即,在应用 PCA 之前),我们总共获得了 269,322 个参数。这次我们总共只得到 8874 个参数。因此,在应用 PCA 后,我们将总参数数量减少了 30 倍(269,322 / 8,874 )!我们只损失了 0.0059 的模型精度。

我们可以通过应用主成分分析得到更好的结果。这次我说的是过度拟合的问题。

我在培训期间测量了模型的性能。

(作者代码)

(图片由作者提供)

这可以与相应的前一种情况相比较:在应用五氯苯甲醚之前。

(图片由作者提供)

很明显, 模型在应用 PCA 后过拟合更少。该模型对新的未见过的数据(测试数据)进行了很好的概括。原因是:

PCA 去除数据中的噪声,只保留数据集中最重要的特征。这将减轻数据的过度拟合,并提高模型对未知数据的性能。除此之外,当数据中有许多要素时,模型会变得更加复杂。复杂的模型往往会过度拟合数据。PCA 通过减少数据中的特征数量(维度)来处理模型的复杂性—来源: 如何通过维度减少来缓解过度拟合 (我自己的文章)

摘要

  • 我们将 PCA 应用于 MNIST 数据,并成功地将原始图像数据的维数降低了 7.84 倍。因此,我们可以显著减小 MLP 输入层的大小。我们在原始数据中保留了 91.43%的可变性。所以,图像还是可以识别的。
  • 在应用 PCA 之后,我们仅损失了非常少量的准确性。
  • 我们可以将 MLP 模型中的总参数数量减少 30 倍!这是因为我们大大减少了 MLP 输入层的大小和隐藏层中神经元的数量。因为现在输入量很小,我们有机会减少隐藏层中神经元的数量。通过增加隐藏层中神经元的数量,您不会获得太多的性能改善。不过,最好还是通过设置不同的值来做一些实验,然后测量性能。
  • 应用主成分分析后,模型的过拟合程度降低。我解释了原因。另外,请注意,该模型仍然过拟合,因为我们还没有对该模型应用任何正则化技术。
  • 对 MNIST 图像数据应用 PCA 是容易的,因为图像是灰度的,其被表示为二维(2D)张量。PCA 过程对于表示为三维(3D)的 RGB 图像来说是复杂的。阅读这篇文章了解如何将 PCA 应用于 RGB 图像。要了解 RGB 和灰度图像的区别,请阅读这篇文章

今天的帖子到此结束。

如果您有任何问题或反馈,请告诉我。

我希望你喜欢阅读这篇文章。如果你愿意支持我成为一名作家,请考虑 注册会员 以获得无限制的媒体访问权限。它只需要每月 5 美元,我会收到你的会员费的一部分。

https://rukshanpramoditha.medium.com/membership

非常感谢你一直以来的支持!下一篇文章再见。祝大家学习愉快!

加入我的神经网络和深度学习课程

点击此图片进入我的神经网络和深度学习课程(作者截图)

鲁克山·普拉莫迪塔
2022–06–12

利用马铃薯提取可解释信息

原文:https://towardsdatascience.com/using-potato-for-interpretable-information-extraction-f2081a717eb7

作者图片

关于

本文是对土豆库的介绍。POTATO 是一个独立于语言的人在回路 XAI(可解释的人工智能)框架,用于提取和评估自然语言处理(NLP)中任何分类问题的可解释图特征。

该文章包括:

  • 基于规则的文本分类方法简介
  • 马铃薯图形模式定义简介
  • 自动学习模式
  • 人在回路(HITL)框架

介绍

目前,文本处理任务(和许多其他领域一样)由机器学习模型主导。但随着这些模型的参数呈指数增长,可解释性降低了。

其中可解释的模型具有以下特征[1]:

  • 公平性——无偏预测
  • 隐私 —更少的信息泄露
  • 可靠性 — 输入的微小变化不会严重影响输出
  • 信任、可审计性——我们知道是什么导致了这些预测
  • 可调试 —如果生产中出现错误,我们可以更改型号

最近的 NLP 解决方案在公共基准上取得了最先进的结果,这些解决方案依赖于具有数百万参数的深度学习(DL)模型(例如 BERT [2])。这些模型需要大量的训练数据,很难解释它们的决策[3]。此外,深度学习模型带来了从数据集学习非预期偏差的风险[4]。基于规则的系统可以提供准确和透明的解决方案,但构建和维护起来可能会很费时费力。POTATO 是一个快速原型框架,支持创建基于规则的文本分类器。在 POTATO 中,我们不是使用机器学习模型来直接学习任务,而是学习规则系统。使用这种方法,最终模型保持完全透明。

基于规则的系统

优点

  • 基于规则的系统通过设计是可以解释和说明的
  • 在“真实世界”的应用中很受欢迎,并且不需要大量的前期投资(不需要 GPU)
  • 完全可定制,可调试

缺点

  • 难以维护
  • 基准测试的性能较差(基准测试主要由 DL 方法决定)
  • 需要领域专业知识
  • 维护和开发非常耗时

在 POTATO 中,我们试图通过结合机器学习和规则系统来解决基于规则的模型的一些缺点:学习规则!

为了演示规则系统,我们将使用来自 Semeval 2010 关系提取数据集的一个例子。关系抽取是从文本中抽取实体间语义关系的任务。通常在两个实体之间定义。关系具有语义类别(例如,目的地、成分、被使用、被建立等)..),任务是将关系分类到正确的类别中。

我们将只使用实体-目的地标签。该类的一个示例:

  • 钻石戒指(entity1) 被丢进不给糖就捣蛋的人的袋子(entity2)

要定义规则,我们可以只使用一个简单的正则表达式:

r”entity1 .* dropped into .* entity2"

但是只使用正则表达式是一种幼稚的方法,因为我们对文本的结构一无所知(标记、语法类别等)..).我们可以使用更高级的 python 包,比如 spaCy 的 TokenMatcher 或 Holmes Extractor。有了它们,我们将能够定义一个更复杂的规则,将词性(POS)标签(单词的语法类别)考虑在内:

pattern = [{‘POS’: ‘VERB’},{‘LOWER’: ‘into’},{‘TEXT’: {‘REGEX’: ‘.*’}},{‘LOWER’: ‘entity2’}]

但是我们可以在图表上写规则,而不是在文本的符号上写规则,这可以利用文本的潜在图表结构。在 POTATO 中,我们使用 networkx python 包来表示图形。有了 networkx,我们为图形表示提供了统一的接口,用户可以在任意图形上书写图形模式。在土豆中,目前我们支持三种类型的图形:AMR【5】, UD (使用节包【6】)和4 lang【7】。在图 1 和图 2 中可以看到一个示例模式,我们可以看到上面定义的示例的 4lang 图(钻石 戒指 被放入不给糖就捣蛋的人的 袋子 )和应用的模式。

图 1:图形上定义的模式示例,ANY 节点意味着我们匹配该节点中的任何字符串(图片由作者提供)

图 2:从文本和应用特征构建的 4lang [7]语义图(图片由作者提供)

与简单的正则表达式模式相反,这种模式也与以下示例相匹配:

The man placed the **entity1** into the **entity2**.
Industries have pushed **entity1** into fragile marine **entity2**.
I am putting the **entity1** into a MySQL **entity2**.
The **entity1** were released into the **entity2**.

使用和设置

POTATO 是一个用 python 编写的人在回路的 XAI 框架,它提供:

  • 用于多个图形库(4lang、stanza、AMR)的统一 networkx 接口
  • 一个 python 包,用于学习和评估作为规则的可解释图形特征
  • 内置于的人在回路(HITL) UI 框架简化了
  • REST-API 在生产模式下使用提取的特性进行推理

我们所有的组件都是在 MIT 许可下开源的,可以用 pip 安装。

该工具严重依赖于 tuw-nlp 库来构建图表和匹配特征。您可以使用 pip 安装 tuw-nlp:

pip install **tuw-nlp**

然后按照指令安装软件包。

然后从 pip 安装 POTATO:

pip install **xpotato**

土豆的第一个进口包装:

**from** xpotato.dataset.dataset **import** Dataset
**from** xpotato.models.trainer **import** GraphTrainer

我们将从 Semeval 数据集[8]中手动选取几个句子来展示 POTATO 的能力。

表 1:来自 Semeval 2010 关系抽取数据集的例句[8]

注意,我们用 XXXYYY 替换了有问题的两个实体。

然后,下一步是初始化数据集并提供标签编码。然后将句子解析成图形,为此我们可以使用 parse_graphs() 方法(也需要选择图形格式)。目前我们提供三种类型的图形: udfourlangamr 。您还需要提供想要解析的语言。目前我们支持英语(en)德语(de)

我们将使用表 1 中的例子(我们将引用在第一列中指定了 id 的样本)。用 python 初始化的例子可以用下面的代码完成:

dataset = Dataset(sentences, label_vocab={"Other":0, "Entity-Destination(e1,e2)": 1})
dataset.set_graphs(dataset.parse_graphs(graph_format="fourlang"))

检查数据集:

df = dataset.to_dataframe()

我们还可以检查任何被解析的图形

**from** xpotato.models.utils **import** to_dot
**from** graphviz **import** SourceSource(to_dot(dataset.graphs[0]))

作者图片

用土豆写规则

如果数据集准备好了,图形也解析好了,我们就可以编写规则来匹配标签。我们可以手动编写规则,也可以自动提取规则(POTATO 也提供了一个两者都做的前端)。

最简单的规则就是图中的一个节点(本例中是从):

# The syntax of the rules
# List[List[rules that we want to match]
# List[rules that shouldn't be in the matched graphs]
# Label of the rule
rule_to_match = [[["(u_1 / into)"], [], "Entity-Destination(e1,e2)"]]

初始化规则匹配器:

**from** xpotato.graph_extractor.extract **import** FeatureEvaluator
evaluator = FeatureEvaluator()

匹配数据集中的规则:

*# The function will return a dataframe with the matched instances:* evaluator.match_features(df, rule_to_match)

该函数将返回带有匹配示例的数据帧。该规则将匹配任何包含节点的句子。在我们的例子中,我们将匹配表 1 中编号为 0、1、2、3、4、5、6、9、14 的例子(例如,科学家将 XXX 倒入品脱 YYY 中。)

我们工具的核心特性之一是我们也能够匹配子图。为了描述一个图形,我们使用彭曼符号。

例如,字符串 (u_1 / into :1 (u_3 / pour)) 将描述一个具有两个节点(“into”“pour”)以及它们之间带有标签“1”的单个有向边的图。用字符串(u _ 1/into:1(u _ 2/pour):2(u _ 3/YYY))描述一个子图,当我们只有单个节点作为特征时,只会返回 3 个例子而不是 9 个。

*#match a simple graph feature* evaluator.match_features(df, [[[“(u_1 / into :1 (u_2 / pour) :2 (u_3 / YYY))”], [], “Entity-Destination(e1,e2)”]])

此功能将在示例 0、1、9 上匹配。

我们还可以添加我们不想匹配的否定特征(这不会匹配存在‘pour’的第一行):

*# match a simple graph feature with a negated feature. 
#The negated features go into the second parameter.* evaluator.match_features(df, [[[“(u_1 / into :2 (u_3 / YYY))”], [“(u_2 / pour)”], “Entity-Destination(e1,e2)”]])

匹配例 2、3、5、6。

如果我们不想指定节点,regex 也可以用来代替节点和边名:

*# regex can be used to match any node (this will match instances 
# where ‘into’ is connected to any node with ‘1’ edge)* evaluator.match_features(df, [[[“(u_1 / into :1 (u_2 / .*) :2 (u_3 / YYY))”], [], “Entity-Destination(e1,e2)”]])

我们还可以从训练数据中提炼正则表达式规则,这将自动替换正则表达式。* '具有高精度的节点。

evaluator.train_feature("Entity-Destination(e1,e2)", "(u_1 / into :1 (u_2 / .*) :2 (u_3 / YYY))", df)

这个返回(u _ 1/into:1(u _ 2/push | pour):2(u _ 3/YYY))【替换掉了】。 "用* * )*

人在回路规则学习

土豆的想法是:

  • 使用子图作为训练简单分类器(LogReg,Random Forest 等)的特征。)
  • 仅生成特定边数的子图(以避免大量特征)
  • 基于功能重要性向用户建议规则
  • 通过 UI,用户可以接受、拒绝、编辑、组合模式
  • 子图可以将正则表达式作为节点或边标签
  • 可以细化未指定的子图

要从已标记的数据集中自动提取规则,请使用图表要素训练数据集,并根据相关性对其进行排序:

***from** sklearn.model_selection **import** train_test_splittrain, val = train_test_split(df, test_size=0.2, random_state=1234)trainer = GraphTrainer(train)features = trainer.prepare_and_train(min_edge=1)*

特征变量将包含自动提取的规则:

*(u_15 / into :1 (u_26 / push))
(u_15 / into :1 (u_19 / pour :2 (u_0 / xxx)))
(u_15 / into :1 (u_19 / pour))
(u_19 / pour :2 (u_0 / xxx))
(u_15 / into :2 (u_3 / yyy))*

用户界面

除了前面介绍的后端,POTATO 还提供了一个 HITL 用户界面,允许用户从数据集中提取规则。为了启动 HITL 用户界面,我们需要加载一个数据集,作为一组带标签或不带标签的图形。除了我们预定义的格式( ud,4lang,amr )之外,任何有向图都可以加载。建议和评估规则需要基础事实标签(使用上一节描述的特征学习方法),如果这些不可用,可以在高级模式下启动 UI,使用规则引导和注释标签。一旦加载了数据集,就可以启动 HITL 前端,用户将看到图 3 所示的界面,该界面是使用 streamlit 库构建的。

图 3 所示的前端提供:

  • 1 — 数据集浏览器,允许用户查看数据集所有行的文本、图形和标签。查看器使用 graphviz 库呈现图形,并且还提供 PENMAN 符号,用户可以复制该符号来快速编辑规则。
  • 2 — 用户可以选择要处理的类(如果处理多标签分类,则为数不多)。
  • 3 — 为每个类构建的规则列表保存在一个列表中,它们可以被修改、删除或添加新的特性。
  • 4 — 可以在训练和验证数据集上查看和评估规则。
  • 5 — 可以通过查看真阳性、假阳性或假阴性示例来分析每个规则的预测。
  • 6 — 按钮 suggest new rules 返回一个建议图表的列表以及它们在训练数据上的表现,允许用户选择那些应该添加到规则列表中的图表,这个界面如图 4 所示。对于包含正则表达式的规则, Refine 按钮将用高精度标签的析取来替换正则表达式。这个函数是使用上一节描述的方法实现的。

图 3:土豆的主页面允许用户 1 浏览数据集并查看处理后的图形, 2 选择您想要在其上构建基于规则的系统的类, 3 修改、删除、添加新规则并获得建议, 4 查看所选规则的结果, 5 查看每个规则的示例预测(图片由作者提供)

图 POTATO 建议的模式,按精度排序(图片由作者提供)

如上所述,前端是一个 streamlit 应用程序,我们可以从训练和验证数据集开始。首先用下面的代码保存它们:

*train.to_pickle(“train_dataset.pickle”)
train.to_pickle(“val_dataset.pickle”)*

然后,可以通过一行命令行启动 streamlit 应用程序:

*streamlit run frontend/app.py -- -t train_dataset.pickle -v val_dataset.pickle*

每个类的规则都以 JSON 格式自动保存到磁盘上,这个文件可以被加载用于进一步的编辑或推理。

*streamlit run frontend/app.py -- -t notebooks/train_dataset -v notebooks/val_dataset -hr features.json*

高级模式

如果标签没有提供或者只是部分提供,前端也可以在高级模式下启动,用户可以在开始注释几个例子,然后系统根据提供的例子逐渐提供规则。

然后,可以启动前端:

*streamlit run frontend/app.py -- -t unsupervised_dataset -m advanced*

评价

如果您已经准备好了特性,并且想要在测试集上评估它们,您可以运行:

*python scripts/evaluate.py -d test_dataset.pickle -f features.json*

结果将是一个带有标签和匹配规则的 csv 文件。

服务

如果您已经准备好提取的特性,并希望在生产中使用我们的包进行推理(为句子生成预测),我们还提供了一个构建在 POTATO 上的 REST API(基于 fastapi )。

首先安装 FastAPI 和uvicon

*pip install fastapi
pip install "uvicorn[standard]"*

要启动服务,您需要为服务设置语言图形 _ 类型特性。这可以通过环境变量来完成。

示例:

*export FEATURE_PATH=/home/adaamko/projects/POTATO/features/semeval/test_features.json
export GRAPH_FORMAT=ud
export LANG=en*

然后,启动 REST API:

*python services/main.py*

它将在端口 8000 上启动运行在 localhost 上的服务(它还将初始化正确的模型)。

然后,您可以使用任何客户端发出 post 请求:

*curl -X POST localhost:8000 -H 'Content-Type: application/json' -d '{"text":"The suspect pushed the XXX into a deep YYY.\nSparky Anderson is making progress in his XXX from YYY and could return to managing the Detroit Tigers within a week."}'*

答案将是一个带有预测标签的列表(如果没有匹配的规则,它将返回“无”):

*["Entity-Destination(e1,e2)","NONE"]*

streamlit 前端也有一个推理模式,可以使用实现的规则系统进行推理。可以从以下方面入手:

*streamlit run frontend/app.py -- -hr features/semeval/test_features.json -m inference*

结论

POTATO 能够快速构建基于规则的系统,并为 NLP 任务的深度学习模型提供透明、可解释和可审计的替代解决方案。如果你想阅读更多关于这个框架的内容,或者想尝试一下,你可以查看以下资源:

参考

[1] Doshivelez 等人,迈向可解释机器学习的严格科学,(2019)

[2] Devlin 等人,用于语言理解的深度双向转换器的预训练,(2019)

[3] Serrano 等人,注意是可解释的吗?,(2019)

[4] Bender 等人,论随机鹦鹉的危险:语言模型会不会太大?(2021)

[5]巴纳雷斯库等,【Sembanking 的抽象意义表示,(2013)

[6]齐等, Stanza:面向多种人类语言的 Python 自然语言处理工具包,(2020)

[7]科尔奈等著,语义学 (2019)

[8] Hendrix 等, SemEval-2010 任务 8:名词对之间语义关系的多向分类 (2010)

使用主成分分析(PCA)进行机器学习

原文:https://towardsdatascience.com/using-principal-component-analysis-pca-for-machine-learning-b6e803f5bf1e

了解如何使用 PCA 来降低数据集的维数

萨姆·张在 Unsplash 上的照片

在机器学习中,经常会有包含大量特征的高维度数据集。高维数据集带来了许多问题,最常见的是过度拟合,这降低了超出训练集中内容的概括能力。因此,您应该采用降维技术来减少数据集中的要素数量。主成分分析 ( PCA )就是这样一种技术。

在本文中,我将讨论 PCA 以及如何将它用于机器学习。特别是,我将向您展示如何在一个样本数据集上应用 PCA。

什么是主成分分析?

简而言之,PCA 是一种降维技术,它将数据集中的一组特征转换为数量更少的特征,称为主成分,同时试图在原始数据集中保留尽可能多的信息:

作者图片

PCA 的主要目的是减少数据集的变量数量,同时尽可能多地保留信息。

在本文中,我不解释 PCA 的工作原理,而是将解释留到下面的视频中,该视频提供了一个很好的 PCA 工作原理的演示。

https://www.youtube.com/watch?v=FgakZw6K1QQ

理解 PCA 的一个很好的类比是想象自己拍了一张照片。想象你在一场音乐会上,你想通过拍照来捕捉气氛。一张照片只能在二维空间捕捉,而不是在三维空间捕捉大气。虽然维度的减少会导致您丢失一些细节,但是您仍然能够捕获大部分信息。例如,照片中一个人的相对大小告诉我们谁站在前面,谁站在后面。因此,2D 图像仍将使我们能够对大部分信息进行编码,否则这些信息只能在 3D 中获得:

尼古拉斯·格林在 Unsplash 上的照片

同样,在对数据集应用 PCA 后,减少的特征(称为主成分)仍然能够充分表示原始数据集中的信息。

那么在数据集上使用 PCA 有什么优势呢?以下是您希望使用 PCA 的几个原因:

  • 删除相关特征。PCA 将帮助您移除所有相关的要素,这种现象称为多重共线性。查找相关的要素非常耗时,尤其是在要素数量很大的情况下。
  • 提高机器学习算法性能。随着 PCA 减少了特征的数量,训练模型所需的时间也大大减少了。
  • 减少过拟合。通过移除数据集中不必要的要素,PCA 有助于克服过度拟合。

另一方面,PCA 也有其缺点:

  • 自变量现在更难解释了。PCA 把你的特征简化成更少的组件。每个组件现在都是原始特征的线性组合,这使得它的可读性和可解释性更差。
  • 信息丢失。如果不小心选择正确的组件数量,可能会发生数据丢失。
  • 特征缩放。因为 PCA 是一个方差最大化练习,所以 PCA 要求在处理之前对特征进行缩放。

在数据集中有大量要素的情况下,PCA 非常有用。

在机器学习中,PCA 是一种无监督的机器学习算法。

使用样本数据集

在这篇文章中,我将使用来自 sklearn 的经典乳腺癌数据集来演示 PCA:

from sklearn.datasets import load_breast_cancer
breast_cancer = load_breast_cancer()

乳腺癌数据集是说明 PCA 的一个很好的例子,因为它具有大量的特征,并且所有特征的数据类型都是浮点数。让我们打印该数据集中的要素名称和数量:

print(breast_cancer.feature_names)
print(len(breast_cancer.feature_names))

您应该看到以下内容:

['mean radius' 'mean texture' 'mean perimeter' 'mean area'
 'mean smoothness' 'mean compactness' 'mean concavity'
 'mean concave points' 'mean symmetry' 'mean fractal dimension'
 'radius error' 'texture error' 'perimeter error' 'area error'
 'smoothness error' 'compactness error' 'concavity error'
 'concave points error' 'symmetry error' 'fractal dimension error'
 'worst radius' 'worst texture' 'worst perimeter' 'worst area'
 'worst smoothness' 'worst compactness' 'worst concavity'
 'worst concave points' 'worst symmetry' 'worst fractal dimension']
30

让我们也打印出目标,并检查目标的含义和目标的分布:

import numpy as npprint(breast_cancer.target) 
print(breast_cancer.target_names) 
print(np.array(np.unique(breast_cancer.target, return_counts=True)))

您应该会看到类似这样的内容:

[0 0 0 0 0 0 0 0 0 0 0 0 0 0 
 ...
 1 1 1 1 1 1 1 0 0 0 0 0 0 1]
['malignant' 'benign']
[[  0   1]
 [212 357]]

目标值 0 表示肿瘤是恶性的,而 1 表示肿瘤是良性的。目标不平衡,但不严重。

检查特征和目标之间的关系

此时,能够可视化每个特征如何影响诊断是有用的——肿瘤是恶性的还是良性的。让我们为每个特征绘制一个直方图,然后用颜色区分恶性和良性肿瘤:

import numpy as np
import matplotlib.pyplot as plt_, axes = plt.subplots(6,5, figsize=(15, 15))malignant = breast_cancer.data[breast_cancer.target==0]
benign = breast_cancer.data[breast_cancer.target==1]ax = axes.ravel()                     # flatten the 2D arrayfor i in range(30):                   # for each of the 30 features
    bins = 40 #---plot histogram for each feature---
    ax[i].hist(malignant[:,i], bins=bins, color='r', alpha=.5)
    ax[i].hist(benign[:,i], bins=bins, color='b', alpha=0.3) #---set the title---
    ax[i].set_title(breast_cancer.feature_names[i], fontsize=12)    #---display the legend---
    ax[i].legend(['malignant','benign'], loc='best', fontsize=8)

plt.tight_layout()
plt.show()

您应该会看到以下输出:

作者图片

对于每个特征,如果两个直方图是分开的,这意味着该特征是重要的,并且它直接影响目标(诊断)。例如,如果您查看平均半径特征的直方图,您会发现肿瘤越大,肿瘤越有可能是恶性的(红色):

作者图片

另一方面,平滑度误差功能并不能真正告诉您肿瘤是恶性还是良性:

作者图片

将数据加载到数据帧中

下一步是将乳腺癌数据加载到熊猫数据框架中:

import pandas as pddf = pd.DataFrame(breast_cancer.data, 
                  columns = breast_cancer.feature_names)
df['diagnosis'] = breast_cancer.target
df

您应该会看到以下输出:

方法 1-使用所有特征训练模型

在我们对数据集执行 PCA 之前,让我们使用逻辑回归来训练一个使用数据集中所有 30 个特征的模型,并看看它的表现如何:

from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_splitX = df.iloc[:,:-1]      
y = df.iloc[:,-1]#---perform a split---
random_state = 12
X_train, X_test, y_train, y_test = \
    train_test_split(X, y,
                     test_size = 0.3,
                     shuffle = True,
                     random_state=random_state)#---train the model using Logistic Regression---
log_reg = LogisticRegression(max_iter = 5000)
log_reg.fit(X_train, y_train)#---evaluate the model---
log_reg.score(X_test,y_test)

该模型的精度为:

0.9239766081871345

方法 2-使用简化的特征训练模型

对于下一个方法,让我们检查各种特性,并尝试消除那些与目标最不相关的特性。同时,我们还希望移除那些显示多重共线性的特征。目的是减少特征的数量,看看是否可以提高模型的精度。

获取相关因子

让我们首先获得每个特征相对于目标(诊断)的相关性:

df_corr = df.corr()['diagnosis'].abs().sort_values(ascending=False)
df_corr

您应该看到以下内容:

diagnosis                  1.000000
worst concave points       0.793566
worst perimeter            0.782914
mean concave points        0.776614
worst radius               0.776454
mean perimeter             0.742636
worst area                 0.733825
mean radius                0.730029
mean area                  0.708984
mean concavity             0.696360
worst concavity            0.659610
mean compactness           0.596534
worst compactness          0.590998
radius error               0.567134
perimeter error            0.556141
area error                 0.548236
worst texture              0.456903
worst smoothness           0.421465
worst symmetry             0.416294
mean texture               0.415185
concave points error       0.408042
mean smoothness            0.358560
mean symmetry              0.330499
worst fractal dimension    0.323872
compactness error          0.292999
concavity error            0.253730
fractal dimension error    0.077972
smoothness error           0.067016
mean fractal dimension     0.012838
texture error              0.008303
symmetry error             0.006522
Name: diagnosis, dtype: float64

然后,我们提取与目标具有相对高相关性的所有那些特征(我们任意地将阈值设置为 0.6):

# get all the features that has at least 0.6 in correlation to the 
# target
features = df_corr[df_corr > 0.6].index.to_list()[1:]
features                          # without the 'diagnosis' column

现在你看到了以下特征:

['worst concave points',
 'worst perimeter',
 'mean concave points',
 'worst radius',
 'mean perimeter',
 'worst area',
 'mean radius',
 'mean area',
 'mean concavity',
 'worst concavity']

但是很明显,有几个特征是相关的-例如,半径、周长和面积都是相关的。其中一些功能必须删除。

检查多重共线性

让我们移除那些显示多重共线性的要素:

import pandas as pd
from sklearn.linear_model import LinearRegressiondef calculate_vif(df, features):    
    vif, tolerance = {}, {} # all the features that you want to examine
    for feature in features:
        # extract all the other features you will regress against
        X = [f for f in features if f != feature]        
        X, y = df[X], df[feature] # extract r-squared from the fit
        r2 = LinearRegression().fit(X, y).score(X, y)                

        # calculate tolerance
        tolerance[feature] = 1 - r2 # calculate VIF
        vif[feature] = 1/(tolerance[feature])
    # return VIF DataFrame
    return pd.DataFrame({'VIF': vif, 'Tolerance': tolerance})calculate_vif(df,features)

您应该会看到以下输出:

作者图片

您的目标是移除 VIF 大于 5 的那些要素。您可以用不同的特性反复调用calculate_vif()函数,直到您得到一个所有 VIF 值都小于 5 的特性集。

经过一些尝试,我将范围缩小到 3 个特征:

# try to reduce those feature that has high VIF until each feature 
# has VIF less than 5
features = [
 **'worst concave points',
    'mean radius',
    'mean concavity',** ]
calculate_vif(df,features)

他们的 VIF 价值观看起来很棒:

作者图片

训练模型

随着 30 个特征减少到 3 个,现在让我们使用逻辑回归来训练模型:

from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_splitX = df.loc[:,features]            # get the reduced features in the 
                                  # dataframe
y = df.loc[:,'diagnosis']# perform a split
X_train, X_test, y_train, y_test = \
    train_test_split(X, y, 
                     test_size = 0.3,
                     shuffle = True,                                                    
                     random_state=random_state)log_reg = LogisticRegression()
log_reg.fit(X_train, y_train)
log_reg.score(X_test,y_test)

这一次,精确度下降到:

0.847953216374269

方法 3-使用简化特征(PCA)训练模型

最后,让我们将 PCA 应用于数据集,看看是否可以训练出更好的模型。

执行标准缩放

还记得 PCA 对缩放很敏感吗?因此,第一步是对 30 个特征执行标准缩放:

from sklearn.preprocessing import StandardScaler# get the features and label from the original dataframe
X = df.iloc[:,:-1]
y = df.iloc[:,-1]# performing standardization
sc = StandardScaler()
X_scaled = sc.fit_transform(X)

应用主成分分析

您现在可以使用 sklearn.decomposition 模块中的PCA类将 PCA 应用于特性:

from sklearn.decomposition import PCAcomponents = None
pca = PCA(n_components = components)# perform PCA on the scaled data
pca.fit(X_scaled)

PCA 类的初始化器有一个名为n_components的参数。您可以为其提供下列值之一:

  • 一个整数,用于指示要将要素减少到多少个主分量。
  • 0
  • 【 . In this case, the number of components returned will be the same as the number of original features in the dataset

Once the components are determined using the 【 method, you can print out the 之间的浮点数解释差异:

# print the explained variances
print("Variances (Percentage):")
print(pca.explained_variance_ratio_ * 100)
print()

您应该会看到以下输出:

Variances (Percentage):
[4.42720256e+01 1.89711820e+01 9.39316326e+00 6.60213492e+00
 5.49576849e+00 4.02452204e+00 2.25073371e+00 1.58872380e+00
 1.38964937e+00 1.16897819e+00 9.79718988e-01 8.70537901e-01
 8.04524987e-01 5.23365745e-01 3.13783217e-01 2.66209337e-01
 1.97996793e-01 1.75395945e-01 1.64925306e-01 1.03864675e-01
 9.99096464e-02 9.14646751e-02 8.11361259e-02 6.01833567e-02
 5.16042379e-02 2.72587995e-02 2.30015463e-02 5.29779290e-03
 2.49601032e-03 4.43482743e-04]

那么如何解读上面的输出呢?你可以这样理解:

  • 仅第一个分量就捕获了数据中约 44%的可变性
  • 第二个捕获了数据中大约 19%的可变性,等等。
  • 这 30 个组件总共捕获了 100%的数据可变性。

理解上述结果的一个更简单的方法是打印累积差异:

print("Cumulative Variances (Percentage):")
print(pca.explained_variance_ratio_.cumsum() * 100)
print()

您现在应该会看到以下输出:

Cumulative Variances (Percentage):
[ 44.27202561  63.24320765  72.63637091  79.23850582  84.73427432
  88.75879636  91.00953007  92.59825387  93.98790324  95.15688143
  96.13660042  97.00713832  97.81166331  98.33502905  98.64881227
  98.91502161  99.1130184   99.28841435  99.45333965  99.55720433
  99.65711397  99.74857865  99.82971477  99.88989813  99.94150237
  99.96876117  99.99176271  99.99706051  99.99955652 100\.        ]

现在,您可以将累计差异解释如下:

  • 仅第一个分量就捕获了数据中约 44%的可变性
  • 前两个组件捕获数据中大约 63%的可变性,依此类推。
  • 前 8 个分量一起捕获了数据中大约 92.6%的可变性。

查看累积方差的直观方法是绘制 scree 图

碎石图是主成分的线形图。

# plot a scree plot
components = len(pca.explained_variance_ratio_) \
    if components is None else componentsplt.plot(range(1,components+1), 
         np.cumsum(pca.explained_variance_ratio_ * 100))
plt.xlabel("Number of components")
plt.ylabel("Explained variance (%)")

scree 图使您可以轻松直观地看到捕捉数据中各种可变性所需的组件数量:

作者图片

现在,让我们应用主成分分析,根据期望的解释方差(比如 85%)找到期望的组件数量:

from sklearn.decomposition import PCApca = **PCA(n_components = 0.85)**
pca.fit(X_scaled)print("Cumulative Variances (Percentage):")
print(np.cumsum(pca.explained_variance_ratio_ * 100))components = len(pca.explained_variance_ratio_)
print(f'Number of components: {components}')# Make the scree plot
plt.plot(range(1, components + 1), np.cumsum(pca.explained_variance_ratio_ * 100))
plt.xlabel("Number of components")
plt.ylabel("Explained variance (%)")

您将获得以下输出:

Cumulative Variances (Percentage):
[44.27202561 63.24320765 72.63637091 79.23850582 84.73427432 88.75879636]
Number of components: 6

正如您从图表中看到的,需要 6 个组件来涵盖数据中 85%的可变性:

作者图片

您还可以使用pca对象的components_属性找出对每个组件有贡献的每个特性的重要性:

pca_components = abs(**pca.components_**)
print(pca_components)

您将看到如下输出:

[[2.18902444e-01 1.03724578e-01 2.27537293e-01 2.20994985e-01
  1.42589694e-01 2.39285354e-01 2.58400481e-01 2.60853758e-01
  1.38166959e-01 6.43633464e-02 2.05978776e-01 1.74280281e-02
  2.11325916e-01 2.02869635e-01 1.45314521e-02 1.70393451e-01
  1.53589790e-01 1.83417397e-01 4.24984216e-02 1.02568322e-01
  2.27996634e-01 1.04469325e-01 2.36639681e-01 2.24870533e-01
  1.27952561e-01 2.10095880e-01 2.28767533e-01 2.50885971e-01
  1.22904556e-01 1.31783943e-01]
 [2.33857132e-01 5.97060883e-02 2.15181361e-01 2.31076711e-01
  1.86113023e-01 1.51891610e-01 6.01653628e-02 3.47675005e-02
  1.90348770e-01 3.66575471e-01 1.05552152e-01 8.99796818e-02
  8.94572342e-02 1.52292628e-01 2.04430453e-01 2.32715896e-01
  1.97207283e-01 1.30321560e-01 1.83848000e-01 2.80092027e-01
  2.19866379e-01 4.54672983e-02 1.99878428e-01 2.19351858e-01
  1.72304352e-01 1.43593173e-01 9.79641143e-02 8.25723507e-03
  1.41883349e-01 2.75339469e-01]
 [8.53124284e-03 6.45499033e-02 9.31421972e-03 2.86995259e-02
  1.04291904e-01 7.40915709e-02 2.73383798e-03 2.55635406e-02
  4.02399363e-02 2.25740897e-02 2.68481387e-01 3.74633665e-01
  2.66645367e-01 2.16006528e-01 3.08838979e-01 1.54779718e-01
  1.76463743e-01 2.24657567e-01 2.88584292e-01 2.11503764e-01
  4.75069900e-02 4.22978228e-02 4.85465083e-02 1.19023182e-02
  2.59797613e-01 2.36075625e-01 1.73057335e-01 1.70344076e-01
  2.71312642e-01 2.32791313e-01]
 [4.14089623e-02 6.03050001e-01 4.19830991e-02 5.34337955e-02
  1.59382765e-01 3.17945811e-02 1.91227535e-02 6.53359443e-02
  6.71249840e-02 4.85867649e-02 9.79412418e-02 3.59855528e-01
  8.89924146e-02 1.08205039e-01 4.46641797e-02 2.74693632e-02
  1.31687997e-03 7.40673350e-02 4.40733510e-02 1.53047496e-02
  1.54172396e-02 6.32807885e-01 1.38027944e-02 2.58947492e-02
  1.76522161e-02 9.13284153e-02 7.39511797e-02 6.00699571e-03
  3.62506947e-02 7.70534703e-02]
 [3.77863538e-02 4.94688505e-02 3.73746632e-02 1.03312514e-02
  3.65088528e-01 1.17039713e-02 8.63754118e-02 4.38610252e-02
  3.05941428e-01 4.44243602e-02 1.54456496e-01 1.91650506e-01
  1.20990220e-01 1.27574432e-01 2.32065676e-01 2.79968156e-01
  3.53982091e-01 1.95548089e-01 2.52868765e-01 2.63297438e-01
  4.40659209e-03 9.28834001e-02 7.45415100e-03 2.73909030e-02
  3.24435445e-01 1.21804107e-01 1.88518727e-01 4.33320687e-02
  2.44558663e-01 9.44233510e-02]
 [1.87407904e-02 3.21788366e-02 1.73084449e-02 1.88774796e-03
  2.86374497e-01 1.41309489e-02 9.34418089e-03 5.20499505e-02
  3.56458461e-01 1.19430668e-01 2.56032561e-02 2.87473145e-02
  1.81071500e-03 4.28639079e-02 3.42917393e-01 6.91975186e-02
  5.63432386e-02 3.12244482e-02 4.90245643e-01 5.31952674e-02
  2.90684919e-04 5.00080613e-02 8.50098715e-03 2.51643821e-02
  3.69255370e-01 4.77057929e-02 2.83792555e-02 3.08734498e-02
  4.98926784e-01 8.02235245e-02]]

每个要素的重要性通过输出中相应值的大小来反映,大小越大,重要性越高。下图显示了如何解释上述结果:

作者图片

出于好奇,让我们打印出对 6 个组成部分贡献最大的前 4 个特征:

print('Top 4 most important features in each component')
print('===============================================')
for row in range(pca_components.shape[0]):
    # get the indices of the top 4 values in each row
    temp = np.argpartition(-(pca_components[row]), 4)

    # sort the indices in descending order
    indices = temp[np.argsort((-pca_components[row])[temp])][:4]

    # print the top 4 feature names
    print(f'Component {row}: {df.columns[indices].to_list()}')

您将看到以下输出:

Top 4 most important features in each component
===============================================
Component 0: ['mean concave points', 'mean concavity', 
              'worst concave points', 'mean compactness']
Component 1: ['mean fractal dimension', 'fractal dimension error', 
              'worst fractal dimension', 'mean radius']
Component 2: ['texture error', 'smoothness error', 
              'symmetry error', 'worst symmetry']
Component 3: ['worst texture', 'mean texture', 
              'texture error', 'mean smoothness']
Component 4: ['mean smoothness', 'concavity error', 
              'worst smoothness', 'mean symmetry']
Component 5: ['worst symmetry', 'symmetry error', 
              'worst smoothness', 'mean symmetry']

将所有 30 列转换为 6 个主成分

现在,您可以将数据集中 30 列的标准化数据转换为 6 个主要成分:

X_pca = pca.transform(X_scaled)
print(X_pca.shape)
print(X_pca)

您应该得到以下输出:

(569, 6)
[[ 9.19283683  1.94858307 -1.12316616  
   3.6337309  -1.19511012  1.41142445]
 [ 2.3878018  -3.76817174 -0.52929269  
   1.11826386  0.62177498  0.02865635]
 [ 5.73389628 -1.0751738  -0.55174759  
   0.91208267 -0.1770859   0.54145215]
 ...
 [ 1.25617928 -1.90229671  0.56273053 
   -2.08922702  1.80999133 -0.53444719]
 [10.37479406  1.67201011 -1.87702933 
   -2.35603113 -0.03374193  0.56793647]
 [-5.4752433  -0.67063679  1.49044308
  -2.29915714 -0.18470331  1.61783736]]

创建机器学习管道

现在,让我们创建一个机器学习管道,以便我们可以正式化整个过程:

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.linear_model import LogisticRegression_sc = StandardScaler()
_pca = PCA(n_components = components)
_model = LogisticRegression()log_regress_model = Pipeline([
    ('std_scaler', _sc),
    ('pca', _pca),
    ('regressor', _model)
])

然后,我们将数据集分为训练集和测试集,并使用训练集训练模型:

# perform a split
X_train, X_test, y_train, y_test = \
    train_test_split(X, y, 
                     test_size=0.3,
                     shuffle=True, 
                     random_state=random_state)# train the model using the PCA components
log_regress_model.fit(X_train,y_train)

让我们对模型进行评分,看看它的表现如何:

log_regress_model.score(X_test,y_test)

我们现在有以下准确度:

0.9824561403508771

摘要

在本文中,我们讨论了 PCA 背后的思想以及使用它的利与弊。特别是,我们训练了三个模型:

  • 使用乳腺癌数据集中的所有 30 个特征
  • 仅使用数据集中的 3 个要素
  • 将 PCA 应用于数据集,然后使用 6 个组件进行训练

对于您自己的数据集,尝试一下我在本文中演示的各种方法,看看哪种方法性能更好,将会很有用。

如果你喜欢阅读我的文章,并且认为它对你的职业/学习有所帮助,请考虑注册成为一名灵媒会员。每月 5 美元,你可以无限制地访问 Medium 上的所有文章(包括我的)。如果你使用下面的链接注册,我会赚一小笔佣金(不需要你额外付费)。你的支持意味着我将能够投入更多的时间来写这样的文章。

https://weimenglee.medium.com/membership

使用 Python 和运筹学优化你的快乐

原文:https://towardsdatascience.com/using-python-and-operational-research-to-optimize-your-happiness-d96ab12bfb7b

使用 Gurobi 和背包问题寻找最优解

来源:https://unsplash.com/photos/u0vgcIOQG08

在不知道的情况下,人们不断地试图优化他们的生活。例如,通过去度假,你决定这次旅行是值得的,即使会有经济上的损失。

可悲的是,你不能把所有的时间都花在寻找快乐上。大多数人也必须考虑他们的长期福祉。

事实上,在一个特定的早晨,一个人可能更喜欢去跳伞而不是去工作。然而,最终,如果这个人从不去工作,而是去跳伞,他将很快花光所有的钱,失去工作,再也负担不起去跳伞了。

工作和娱乐之间的平衡很难找到。为了最大化我的幸福,我如何决定采取哪些行动?

这听起来很像经典运筹学中的背包问题。

背包问题是一个组合优化问题,你有一组物品,它们有一定的价值和体积,你想把它们放进背包里。考虑到背包容量有限,你要决定哪些物品放在包里才能实现价值最大化。

来源:https://en.wikipedia.org/wiki/Knapsack_problem

问题是

在我们的例子中,我们可以从活动、它们带来的快乐和它们的成本(时间和金钱)来考虑,而不是物体、价值和体积(就像背包问题)。

在下面的例子中,我将展示一个虚构的例子,关于一个人如何决定他的周计划来最大化他的幸福和快乐。

本文还将向您展示如何使用 Python 解决运筹学问题。我将使用 Gurobi(特别是 Gurobi Optimizer),这是一款令人惊叹的产品,可以让您轻松地建模和解决所有类型的运筹学问题。

1.变量

问题的第一步是确定需要做什么活动。对于每项活动,您还需要指定:

  1. 做这件事的成本(在这个例子中是 1 小时)
  2. 它带来的快乐(从 1 到 10 分)
  3. 这对你的健康有多大好处(财务、身体、精神等)。).再从 1 到 10 打分。
  4. 你能在这个特定活动上花费的最少和最多的时间。
  5. 你的总预算,也就是你每周可以在“活动”上花多少钱。
  6. 你有多少时间,除去睡眠,交通等等。

例如,这个人不太喜欢去健身房(快乐= 1)。然而,它被认为对整体健康非常有益。另一方面,游戏是令人愉快的,但对健康没有太大的帮助。

当然,这里的挑战是填写所有这些。一项活动能带来多大的乐趣?对你有多好?这很主观,很难量化。只有实践的人才知道什么是正确的价值观!这取决于你在生活中想要什么,你的目标是什么等等。

2.制约因素

一旦我们有了变量,我们就可以向模型添加约束。这是我们确保我们的最终解决方案将遵守前面指定的规则的地方。

  • 第 4 行:我们指定变量是整数,也就是数字
  • 第 7 行:花费的总金额必须低于或等于我们的预算
  • 第 10 行和第 13 行:我们必须考虑每个变量的最小值和最大值。换句话说,如果一个人说他必须工作 35 到 45 个小时,那么他必须得到尊重
  • 第 16 行:花在我们所有不同活动上的时间总和必须等于指定的时间

3.目标函数

现在,是时候优化我们的目标了。这个目标因人而异!有的只是想尽可能的享受生活。其他人想要更多的工作来获得晋升。其他人想要一个完美的平衡。每个人的函数都不一样,因为每个人对幸福的定义都不一样。

这是本例中使用的目标函数。

如你所见,这里的目标是最大化快乐和幸福的总和。然而,快乐比幸福更重要,因为后者乘以 0.75。

4.解决方案

我们终于可以优化我们的问题,得到我们的最优解!

这是结果。

所以下面这个人,为了优化他的目标函数(以及他的幸福),必须每周工作 45 小时,打 2 小时高尔夫,看 15 分钟网飞,等等。

基于这个人所指定的一切,这将优化他的目标函数并使他最快乐!

外卖食品

首先,这是一个非常普遍的问题,但是你总是可以添加更多的约束、变量等。让它更复杂,更能代表真实世界。我举了一周的例子,但也可能是一个月,一年…随你怎么想!Gurobi 使得这样做非常容易。看这里!https://www.gurobi.com/documentation/9.5/refman/refman.html

第二,我在这篇文章中展示的整个解决问题的方法显然是非常主观的,而且远非完美。然而,我在这样做的时候意识到,这实际上是一次伟大的自省练习。对你来说什么是重要的?你如何评价你所做的事情,它们对你生活的影响?

花时间思考并确定你的目标函数是什么,一项特定的活动能给你带来多少快乐,等等。可能会让你意识到你所做的活动和你花在这些活动上的时间并不能真正帮助你实现你想要的。

非常感谢你的阅读,我希望你喜欢它!如果你有任何问题,不要犹豫。

要成为会员并无限制地访问更多像这样的精彩内容,请点击这里:https://francoisstamant.medium.com/membership

使用 Python Argparse 开发命令行界面程序

原文:https://towardsdatascience.com/using-python-argparse-to-develop-a-command-line-interfaced-program-73040940ca94

图片来自 Pixabay赫尔维·拉格朗日

开发 Linux 系统程序的方法

每个人都喜欢 GUI(图形用户界面),因为它简化了人和计算机之间的交互。但是,有时(CLI)命令行界面可能更方便。这可能是因为我们只想做一个简单的任务,或者我们正在使用一个基于 Linux 的操作系统,而没有安装 GUI。

当我们开发一个 CLI 程序时,在大多数情况下,我们必须提供参数,以便程序能够根据传入的参数以不同的方式做出反应。

你知道 Python 内置了这个特性吗?在本文中,我将介绍 Python 的 Argparse 模块。我们甚至可以使用 Python 开发一个类似ls的 CLI 程序。

1.位置参数

图片来自 PixabayGino Crescoli

有时,我们只需要一个或几个参数,这些参数对程序来说是强制性的。在这种情况下,位置参数可能是一个不错的选择。

假设我们正在开发一个 CLI 程序,它需要用户的名字,然后打印一条问候消息。代码可以如下所示。

# content of greetings.pyimport argparse# Parse the args
parser = argparse.ArgumentParser()
parser.add_argument("name")
args = parser.parse_args()# Program
print(f'Hello {args.name}!')

我们编写了这个 Python 脚本,并将其命名为greeting.py。在脚本文件中,我们需要导入 Argparse。然后,我们可以使用ArgumentParser类来实例化解析器。之后,我们可以使用add_argument()函数添加一个参数。"name"将是参数的名称,稍后将用作获取参数值的关键。

然后,让我们保存文件并在命令行中运行它进行测试!

$ python greeting.py Chris

如果我们忘记传递参数,Argparse 会很聪明地告诉我们错误,如下所示。

2.帮助说明

图片由 Sid74 发自 Pixabay

我们可能想把我们的程序分发给更多的用户。或者,有时我们可能会忘记论点。因此,总是建议在所有参数中添加一些帮助说明。

默认情况下,Argparse 具有“帮助”特性。如果我们用-h标志运行程序,将会显示帮助指令。

$ python greeting.py -h

然而,它并没有显示出我们所定义的论点。我们可以在add_argument()函数中添加帮助消息,如下所示。

parser.add_argument("name", help='Enter your name')

现在,如果我们再次显示帮助说明,将会有关于该参数的帮助。

3.自变量类型指示器

图片来自 PixabayAlicja

有时我们可能需要获得不同类型的信息。例如,我们希望用户输入他们的年龄,然后计算他们出生的年份。greeting.py增加了一个参数,代码如下。

# greetings.pyimport argparse# Parse the args
parser = argparse.ArgumentParser()
parser.add_argument("name", help='Enter your name')
parser.add_argument("age", help='Enter your age', type=int)
args = parser.parse_args()# Program
born_year = 2022 - args.age
print(f'Hello {args.name}! You were borned in {born_year}.')

现在,如果我们在命令行中运行程序,我们将会因为类型问题而得到一个错误。

$ python greeting.py Chris 34

为了解决这个问题,我们可以在add_argument()函数中添加一个类型指示器。

parser.add_argument("age", help='Enter your age', type=int)

现在,我们可以再次测试程序。

当然,即使我们传入的参数默认是字符串,我们仍然可以在运行时转换类型。但是,我们需要手动处理异常。在这里使用类型指示器的好处在于,它会在程序启动之前做一个简单的检查。

$ python greeting.py Chris "thirty four"

4.可选参数

图片来自 Pixabay沃纳·莫泽

有时候,我们可能需要一些可选参数。例如,在程序的典型使用中它们不是必需的,但是如果用户想要一些定制的行为,他们将需要指定它们。

假设我们想给greeting.py程序添加一些详细的日志。当启用 verbose 时,将输出计算结果用于调试。因此,我们可以添加如下可选参数。

parser.add_argument("--verbose", help='Display verbose logs', type=bool)

请注意,Python Argparse 将理解当我们在参数名前面加双破折号--时,这个参数将是可选的。

现在,如果启用了 verbose,我们将输出如下内容。

if args.verbose:
    print(f'2022 - {args.age} = {born_year}')

现在,我们可以运行程序来测试。

布尔类型参数

它可以工作,但是有点奇怪。也就是说,如果参数是 bool 类型,我们就不需要向它传递True值。在 Argparse 中,如果要实现这样的特性,需要在参数中加入action="store_true"

parser.add_argument("--verbose", help='Display verbose logs', action="store_true")

如果指定了选项,将值True分配给args.verbose。不指定它意味着False

还是不完美。如果您是 Linux 用户,您一定知道为了方便起见,选项标志应该有一个简短的版本。为了实现这一点,我们需要在完整的参数名称前面传递简短的版本。

parser.add_argument("-v", "--verbose", help='Display verbose logs', action="store_true")

现在,我们不必键入整个单词“verbose”。

$ python greeting.py Chris 34 -v

组合位置参数和可选参数

我们现在既有位置参数,也有可选参数。如果我们调用帮助消息,它将如下。

顾名思义,位置参数必须按照定义的顺序。但是,可选参数可以放在任何地方。

$ python greeting.py -v Chris 34
$ python greeting.py Chris -v 34
$ python greeting.py Chris 34 -v

如果我们有多个可选参数,那么顺序也没有关系,因为可选参数需要指定参数名称以避免任何歧义。

5.Argparse 的缺点

图片来自 Pixabay

Argparse 有很多优点。比如它是一个 Python 内置模块,不需要下载安装。但是,弊端也很明显。

1.它有一种内在的行为来猜测某件事是一个论点还是一个选项。当处理不完整的命令行时,这就成了一个问题。这意味着在不完全了解命令行的情况下,行为可能是不可预测的。

2.当我们需要一些更高级的特性,比如参数值的复杂验证时,它不是很容易使用。

因此,许多令人惊叹的第三方开源库试图掩盖 Argparse 的这些缺点。点击库就是其中之一。

如果你对这个库感兴趣,请关注我的帖子。我将很快发表另一篇关于这个图书馆的文章!

摘要

图片由来自 PixabayMichael Schwarzenberger 拍摄

在本文中,我介绍了 Python 内置模块之一 Argparse。它允许我们非常容易地开发命令行界面工具。我们可以用它来定义位置参数和可选参数。它也有现成的帮助说明,所以我们只需要为每个参数写一个字符串。

然而,当我们有一些更复杂的需求时,它也有一些缺点。这些缺点可以通过一些第三方库来消除,以后我的帖子会重点介绍。

https://medium.com/@qiuyujx/membership

如果你觉得我的文章有帮助,请考虑加入灵媒会员来支持我和成千上万的其他作家!(点击上面的链接)

使用 Python 与 Excel 交互(PLAXIS 输入)

原文:https://towardsdatascience.com/using-python-to-interact-with-excel-plaxis-input-a2124f6ebc72

PLAXIS 自动化系列

逐步走向自动化的指南

UnsplashAltumCode 拍摄的照片

我们都喜欢用户友好的界面。PLAXIS Input app 当然是这样一个界面的好例子。经过很少的训练,初学者可以很容易地创建一个垫基脚模型。

但是,使用鼠标在 PLAXIS 输入中执行操作是非常常见的,如果我们正在处理一个复杂的模型,这可能是一个乏味的过程。当前输入接口有三个限制:

  1. 批量几何图形输入非常耗时。虽然“阵列”选项可用,但它仅限于具有相同几何图形的元素。
  2. 在对设计进行参数化研究时,我们经常需要重新绘制几何图形,这样效率很低。
  3. 每个元素的命名需要手动完成。如果我们有复杂的模型,命名对于阶段来说非常有用。

本教程旨在扩展从第一教程中学到的课程。我们将使用 Excel 作为输入几何和材料属性的界面,并在 PLAXIS 中创建板元素。

  1. 使用熊猫从 Excel 中读取值
  2. 用 Excel 输入几何图形并创建板元素
  3. 用 Excel 输入材料属性并分配材料

和以前一样,本教程要求读者已经安装了 VS 代码和 PLAXIS 环境。如果您不熟悉这个页面,请按照下面文章中的说明进行操作。

此外,我们还需要在 PLAXIS 环境中安装 pandas 。如果您还没有安装外部模块,请按照下面的说明进行安装。

Excel 输入界面

本教程的最终目标是从 Excel 输入模板中提取值,并使用它们在 PLAXIS 中创建结构元素。因此,定义输入接口非常重要。

我们将创建一个空的 excel 电子表格,将其命名为 "Input_param" 。该界面包含三个工作表:

  • 几何学
  • 盘子
  • PlateMatName

作者截图自 Excel

创建工作表时,请确保它们遵循如上所示的相同名称,因为命名区分大小写。

几何学

“几何”工作表的目的是定义结构元素的坐标。这些值将在下一节的“孔板”工作表中使用。它包括如下所示的四列:

  1. 距离:定义特定结构元素的 x 坐标名称。例如,“LDWall”定义左侧地下连续墙的 x 坐标。
  2. x 值:将实际的 x 坐标赋给对应的“距离”。例如,我们将-5 m 指定给“LDWall ”,这意味着左侧地下连续墙将从 x = -5 m 开始。
  3. RL:为特定结构元素定义 y 坐标的名称。与“LDWall”类似,“DWallTop”定义了地下连续墙顶部的 y 坐标。

y 值:将实际的 y 坐标赋给对应的“RL”。类似于“X 值”的概念。

作者截图自 Excel

这些坐标定义将用于“板”工作表中。

盘子

此工作表的目的是设计多个板元素的属性,包括:

  • 名称:如 PLAXIS 中所示(任何不带空格的名称,用下划线代替空格)
  • 接口:决定是否应为该元素创建正接口和负接口(Y/N)
  • 材料:决定应该将什么材料分配给特定的板(任何不带空格的名称,用下划线代替空格)

X1,Y1,X2 和 Y2:因为 PLAXIS 2D 的板是一条线,它需要两个点的坐标。最后四列使用前面章节中的定义来指定坐标。

作者截图自 Excel

创建此工作表后,我们可以转到最后一个工作表“PlateMatName”。

PlateMatName

此工作表旨在定义我们在上一节中指定的材料属性。请注意,此处输入的名称需要与“孔板”工作表中给出的名称完全相同。

孔板属性有五个输入:

  • EA(千牛/米)
  • EI (kNm /m)
  • 单位重量,w(千牛/米/米)
  • 泊松比,v (nu) (-)
  • 防止打孔(是/否)

作者截图自 Excel

这些是 PLAXIS 2D 公司的典型材料属性,只是为了确保所有输入属性都采用正确的单位。

本例中使用的值旨在创建一个挖掘模型。确保您已经创建了包含上述工作表和值的 excel 电子表格。

一旦创建了输入文件,我们就可以开始下一步了。

步骤 1:使用熊猫从 Excel 中读取值

步骤 1 的主要目标是从“几何”工作表中提取坐标定义,并将值存储为字典,以备后用。

首先,我们创建一个空的 python 文件,并将其命名为“excel_geometry.py”。

导入模块并启动服务器

与之前的教程类似,我们将从导入相关模块和启动服务器开始。

from plxscripting.easy import *
import subprocess, time
import pandas as pd
import openpyxl

###############################################
PLAXIS_PATH = r'C:\Program Files\Bentley\Geotechnical\PLAXIS 2D CONNECT Edition V22\\Plaxis2DXInput.exe'  # Specify PLAXIS path on server.
PORT_i = 10000  # Define a port number.
PORT_o = 10001
PASSWORD = 'SxDBR<TYKRAX834~'  # Define a password.
subprocess.Popen([PLAXIS_PATH, f'--AppServerPassword={PASSWORD}', f'--AppServerPort={PORT_i}'], shell=False)  # Start the PLAXIS remote scripting service.
time.sleep(5)  # Wait for PLAXIS to boot before sending commands to the scripting service.

# Start the scripting server.
s_i, g_i = new_server('localhost', PORT_i, password=PASSWORD)
s_o, g_o = new_server('localhost', PORT_o, password=PASSWORD)

s_i.new()

g_i.gotostructures()

读取 Excel 文件

文件位置:C:\ Users \ phtsang \ Desktop \ PLAXIS _ v 22 \ Python _ automation

文件名:我们之前创建的电子表格“Input_param.xlsx”

由于我们要从“Geometry”中提取值,所以我们将使用“pd.read_excel()”,并使用“sheet_name”指定我们要读取的工作表。

source=r"C:\Users\phtsang\Desktop\PLAXIS_V22\Python_automation"
file="Input_param"+".xlsx"
geomsheet="Geometry"
df_geom = pd.read_excel(file, sheet_name = geomsheet,engine="openpyxl")

字典和数据框架的详细解释可以在以下文章中找到:

读取坐标定义

在这里,我将演示从 dataframe 读取值的第一种方法(把它想象成一个 excel 表)。下面是“几何”工作表的表格。

作者截图自 Excel

假设我们希望提取列 A 和 B,并存储为以“X 值”为标题的字典。这可以通过以下步骤完成:

  • 首先,我们需要使用命令' df_geom.columns[]'定位我们想要的列。例如,“df_geom.columns[0]”给出第一个列名“Dist”。对“X 值”列进行同样的操作。
  • 下一步是提取指定列名下的所有行。这是在 pandas 中使用双方括号命令完成的:“df_geom[[df_geom.columns[0],df_geom.columns[0]]”

作者截图自 VS 代码

  • 由于我们只想在字典中包含“X 值”,所以我们将“Dist”设置为索引。
set_index(df_geom.columns[0])
  • 最后,我们使用 pandas 命令' to_dict()'将数据帧转换为字典。

合并代码后,它应该看起来像这样:

geom=[]
for i in range(2):
    geom1=df_geom[[df_geom.columns[i*2],df_geom.columns[i*2+1]]].set_index(df_geom.columns[i*2]).to_dict()
    geom.append(geom1)

geom 列表的输出如下所示:

[{'X value': {'LDWall': -5.0, 'RDWall': 8.0, 'CentreWall': 1.0}}, {'Y value': {'DWallTop': 9.0, 'DWallBot': -6.0, 'Slab1': 6.5, 'BaseSlab': -1.0}}]

通过创建一个字典,我们可以很容易地通过名字来赋值。例如,我可以用下面的代码给一个点赋值-5。这在进行参数研究时特别有用,因为我们可以直观地知道要更改哪些坐标。

geom[0]['X value']['LDWall']

既然我们已经存储了坐标,那么我们可以使用这些值来创建结构元素。

步骤 2:用 Excel 输入几何图形并创建板元素

在步骤 2 中,我们将从“Plates”工作表中提取值,并相应地创建 plate 元素。

一般来说,步骤类似于第一个教程,除了这一次我们需要根据输入表中提供的信息创建元素。它涉及以下行动:

  1. 查找唯一的材质名称并创建空材质
  2. 遍历表中的元素数
  3. 使用用户输入的名称和坐标定义创建板
  4. 检查接口要求(是/否),并根据需要创建正接口和负接口。给它们取一个合适的名字。
  5. 根据表格中的“材料”栏指定材料

输入表中涉及的信息如下所示。我们将需要它来引用我们的代码。

作者截图自 Excel

  • 首先,我们需要从“Plates”工作表中提取值,并存储为“df_plate”。
#Plates:
platesheet="Plates"
df_plate = pd.read_excel(file, sheet_name = platesheet,engine="openpyxl")
  • 然后,我们需要从“材料”列中读取值,并找到唯一的材料(即 RC1000 和 RC600)。要从特定单元格读取值,我们可以使用 pandas 命令' df_plate.iloc[row,column]'。它遵循同样的规则列出应该使用索引的数据类型。
  • 例如,如果我们想从表中获取“RightDWall”和“BaseSlab”。我们可以使用“df_plate.iloc[1,0]”和“df_plate.iloc[2,0]”。需要注意的是,当使用 iloc 时,第一行(标题)被跳过。如果我们想得到所有的行,我们可以写' df_plate.iloc[:,0]'。
  • 之后,可以使用' dict.fromkeys()'来查找唯一值。结合我们现有的代码。
material=list(dict.fromkeys(df_plate.iloc[:,2].to_list()))
  • 然后我们可以用上面给定的材质名创建空材质。之后,我们将材质作为对象存储到“platematerials”中。
for i in range(len(material)):
    g_i.platemat('Identification',material[i])

platematerials = [mat for mat in g_i.Materials[:] if mat.TypeName.value == 'PlateMat']
  • 下一步是遍历表中的所有输入元素。
for i in range(df_plate.count()[0])
  • 我们需要使用“g_i.plate((X1,Y1),(X2,Y2))”的用户输入值来创建板。请参见下面两个使用 iloc 提取特定单元格值的示例。

geom[0]['X 值'][df_plate.iloc[0,3]]从表中定位 D2,因此给出-5(我们对 LDWall 的定义)

geom[1]['X 值'][df_plate.iloc[0,4]]从表中定位 E2,因此给出 9(我们对 DWallTop 的定义)

使用此逻辑,我们可以创建板元素的两个点。

#Create Plate
plate=g_i.plate(
    (geom[0]['X value'][df_plate.iloc[i,3]],geom[1]['Y value'][df_plate.iloc[i,4]]),
    (geom[0]['X value'][df_plate.iloc[i,5]],geom[1]['Y value'][df_plate.iloc[i,6]]),
    )
  • 然后,我们根据“名称”列更改该板的名称。
plate1=plate[-1]
plate1.rename(df_plate.iloc[i,0])
  • 使用 if 语句检查是否需要接口。如果为' Y ',则使用' g _ I . po interface(plate)'和' g_i.neginterface(plate)'创建接口。
if df_plate.iloc[i,1] == 'Y':
    plate2=plate[-2]
    g_i.posinterface(plate2)
    g_i.neginterface(plate2)
  • 然后,我使用 PLAXIS 命令' rename()'来命名接口,这样我就有了“LeftDWall_PosInterface”。为每个元素指定一个特定的名称对于登台特别有用。
plate2.PositiveInterface.rename(df_plate.iloc[i,0]+'_PosInterface')
        plate2.NegativeInterface.rename(df_plate.iloc[i,0]+'_NegInterface')
  • 最后一步是根据“材料”栏将材料放置到板上。请记住,我们已经将空材料“RC1000”和“RC600”作为对象存储在“platematerials”中。我们将需要使用 if 来检查表中提供的材料名称。如果材质的名称与现有的材质对象匹配,则使用' plate.setmaterial()'将该材质指定给该板。
for j in range(len(material)):
     if df_plate.iloc[i,2] == platematerials[j].Identification:
            plate1.setmaterial(platematerials[j])

最终脚本应该如下所示:

#Plates:
platesheet="Plates"
df_plate = pd.read_excel(file, sheet_name = platesheet,engine="openpyxl")

#Material
material=list(dict.fromkeys(df_plate.iloc[:,2].to_list()))

for i in range(len(material)):
    g_i.platemat('Identification',material[i])

platematerials = [mat for mat in g_i.Materials[:] if mat.TypeName.value == 'PlateMat']

for i in range(df_plate.count()[0]):
    #Create Plate
    plate=g_i.plate(
        (geom[0]['X value'][df_plate.iloc[i,3]],geom[1]['Y value'][df_plate.iloc[i,4]]),
        (geom[0]['X value'][df_plate.iloc[i,5]],geom[1]['Y value'][df_plate.iloc[i,6]]),
        )
    plate1=plate[-1]
    #Rename Plate
    plate1.rename(df_plate.iloc[i,0])
    #Create Interface
    if df_plate.iloc[i,1] == 'Y':
        plate2=plate[-2]
        g_i.posinterface(plate2)
        g_i.neginterface(plate2)
    #Rename Interface
        plate2.PositiveInterface.rename(df_plate.iloc[i,0]+'_PosInterface')
        plate2.NegativeInterface.rename(df_plate.iloc[i,0]+'_NegInterface')
    #Set Material
    for j in range(len(material)):
        if df_plate.iloc[i,2] == platematerials[j].Identification:
            plate1.setmaterial(platematerials[j])

作者截图自 VS 代码

一旦创建了板元素,我们就可以定义我们的材料属性了。

步骤 3:用 Excel 输入材料属性并指定材料

在第 3 步中,我们的目标是从“PlateMatName”工作表中提取材质属性,并将这些属性分配给我们在上一节中创建的材质对象(即“RC1000”和“RC600”)。

输入表中涉及的信息如下所示。

作者截图自 Excel

  • 与上一步类似,我们首先从“PlateMatName”中读取值,并将它们存储为 dataframe。
platematsheet="PlateMatName"
df_platemat = pd.read_excel(file, sheet_name = platematsheet,engine="openpyxl")
  • 如“Excel 输入界面”下所述,板材属性有五个输入:EA (kN/m)、EI (kNm /m)、单位重量、w (kN/m/m)、泊松比、v (nu) (-)、防冲(Y/N)。
  • 我们将遍历表中的行,并使用 iloc 找到每个值,并将它们分配给相应的变量。
for i in range(df_platemat.count()[0]):
   EA = df_platemat.iloc[i,1]
   EI = df_platemat.iloc[i,2]
   w = df_platemat.iloc[i,3]
   nu = df_platemat.iloc[i,4]
   if df_platemat.iloc[i,5] == 'Y':
       Punch=True
   else:
       Punch=False
  • 然后,我们检查“名称”列。如果它与 PLAXIS 中的 material 对象的名称相匹配,我们将属性分配给该对象。
platematerials = [mat for mat in g_i.Materials[:] if mat.TypeName.value == 'PlateMat']

for j in range(len(platematerials)):
    if df_platemat.iloc[i,0] == platematerials[j].Identification:
        platematerials[j].setproperties("MaterialType","Elastic", "w", w, "EA1", EA, 
        "EI", EI, "StructNu", nu, 'PreventPunching', Punch)
  • 最后,使用 PLAXIS 命令' setproperties()'来设置材料属性。
platematerials[j].setproperties("MaterialType","Elastic", "w", w, "EA1", EA, "EI", EI, "StructNu", nu, 'PreventPunching', Punch)

最终的脚本如下所示:

platematsheet="PlateMatName"
df_platemat = pd.read_excel(file, sheet_name = platematsheet,engine="openpyxl")

for i in range(df_platemat.count()[0]):
    EA = df_platemat.iloc[i,1]
    EI = df_platemat.iloc[i,2]
    w = df_platemat.iloc[i,3]
    nu = df_platemat.iloc[i,4]
    if df_platemat.iloc[i,5] == 'Y':
        Punch=True
    else:
        Punch=False

    platematerials = [mat for mat in g_i.Materials[:] if mat.TypeName.value == 'PlateMat']

    for j in range(len(platematerials)):
        if df_platemat.iloc[i,0] == platematerials[j].Identification:
            platematerials[j].setproperties("MaterialType","Elastic", "w", w, "EA1", EA, 
            "EI", EI, "StructNu", nu, 'PreventPunching', Punch)

作者截图自 VS 代码

使用以下命令运行脚本。

(PLAXIS) C:\Users\phtsang\Desktop\PLAXIS_V22\Python_automation>python excel_geometry.py

你应该看到以下是在 2D 普拉西斯创建的。正如您在 Model explorer 中看到的,我们创建的所有板都有我们在输入表中指定的名称。

作者截图自 PLAXIS

就材料而言,RC1000 和 RC600 具有我们在表中指定的特性。

作者截图自 PLAXIS

精彩!我们刚刚为 2D PLAXIS 创建了一个用户界面。

结论

以上是关于使用 Python 与 Excel 交互的第四篇教程。学完本教程后,您应该能够从 excel 获得输入,创建多个结构元素,并在 PLAXIS 中分配材料属性。

如果你喜欢阅读这类内容,请随时关注我的页面。我将继续发布这一系列关于用 Python 自动化 PLAXIS 的教程。除此之外,我还热衷于分享关于如何使用 Python 来自动化工程中的工作流的知识。

你可以在我的 Github 上找到这篇文章中演示的代码。

使用 Python UDF 和 Snowflake 的 Snowpark 构建和部署机器学习模型,第 1 部分

原文:https://towardsdatascience.com/using-python-udfs-and-snowflake-s-snowpark-to-build-and-deploy-machine-learning-models-a3c160c06d85

照片来自阿莱西奥·索格蒂通过 Unsplash

本指南将向您展示如何将雪花的 Snowpark 与 Python UDF 的结合使用,以利用雪花的计算能力来运行使用 Python 的机器学习模型。

导入库

import snowflake.connector
import pandas as pd
import numpy as np
from sqlalchemy import create_engine
from snowflake.sqlalchemy import URLfrom snowflake.connector.pandas_tools import write_pandas
from snowflake.snowpark.functions import udf
from snowflake.snowpark.types import IntegerType, StringType, StructType, FloatType
from snowflake.snowpark.session import Session
from snowflake.snowpark import Session
import snowflake.snowpark.functions as F
from snowflake.snowpark import types as T
from snowflake.snowpark import Window
from snowflake.snowpark.functions import udf, max, min, count, avg, sum, col, lit, listagg
import mlxtend
from mlxtend.feature_selection import ColumnSelectorimport lightgbm as lgb
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.preprocessing import MultiLabelBinarizer, OneHotEncoder, FunctionTransformer
from sklearn.pipeline import make_pipeline, Pipeline, FeatureUnion
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import GradientBoostingClassifier, RandomForestClassifier
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.metrics import balanced_accuracy_score
from sklearn import datasets

数据

我们将使用来自 sklearn 数据集(Alpaydin,E. & Kaynak,C)的数字数据集。这个数据集是在知识共享署名 4.0 国际 (CC BY 4.0)许可下许可的。

这允许为任何目的共享和改编数据集,只要给予适当的信任。

digits = pd.DataFrame(datasets.load_digits().data)
digits['target'] = datasets.load_digits().target
digits.head()

作者图片

print(datasets.load_digits().DESCR)

作者图片

让我们将数字数据分成训练和测试,并将它们作为单独的表保存在雪花中。我们稍后可以使用 Snowpark 连接到这些:

#Set up the snowflake connection
ctx = snowflake.connector.connect(
    user='<user>',
    password='<password>',
    account='<account-identifier>',
    database='<database>',
    warehouse='<warehouse>',
    role='<role>',
    schema='<schema>'
    )#Create column names for the tables 
cols1=['X' + str(x) for x in range(0,64)]
cols1.append('TARGET')digits.columns=cols1
X=digits.drop(columns='TARGET')
y=digits['TARGET']
X_train, X_valid, y_train, y_valid = train_test_split(X, y, random_state=1234, test_size=.33)#Create the DataFrame with the features and target for the validation data set
train=pd.concat([X_train,y_train], axis=1)
valid=pd.concat([X_valid,y_valid], axis=1)#Match the snowflake table column names to the Digits data column names
snowflake_cols=['X' + str(x) + ' integer' for x in range(0,64)]
s = ', '.join(snowflake_cols)#Create the training table in snowflake from the csv
ctx.cursor().execute(
"""CREATE OR REPLACE TABLE
DIGITS_TRAINING_DATA(""" + s + """, target integer)""")#Copy the table into snowflake
write_pandas(ctx, train, 'DIGITS_TRAINING_DATA')#Create the validation table in snowflake from the csv
ctx.cursor().execute(
"""CREATE OR REPLACE TABLE
DIGITS_VALIDATION_DATA(""" + s + """, target integer)""")#Copy the table into snowflake
write_pandas(ctx, valid, 'DIGITS_VALIDATION_DATA')

建模

连接到 Snowpark 中的数据:

#Create snowpark session to connect to saved tables. 
def create_session_object():
   connection_parameters = {
      "account": "<account-identifier>",
      "user": "<user>",
      "password": "<password>",
      "role": "<role>",
      "warehouse": "<warehouse>",
      "database": "<database>",
      "schema": "<schema>"
   }
   session = Session.builder.configs(connection_parameters).create()
   print(session.sql('select current_warehouse(), current_database(), current_schema()').collect())
   return session#Create two sessions, one for pulling the initial data, and one for pushing the udf to snowpark. I've found it tends to fail if I use just one session
session=create_session_object()
session2=create_session_object()cols=session.table('DIGITS_TRAINING_DATA')
cols.schema.fields

作者图片

将数据从雪花带到我们的本地环境:

tbl=pd.DataFrame(cols.collect())

检查缺少的值:

count_nas=pd.DataFrame(tbl.isna().sum())
count_nas[count_nas[0]>0]

作者图片

如果有缺失值,我们可以用以下内容来填充它们:

tbl=tbl.fillna(0)

分为特征和响应变量:

#This is already our training set so no need to use train_test_split here. 
X=tbl.drop(columns='TARGET')
y=tbl['TARGET']

为所有功能构建一个管道预处理器:

numeric_features=['X0', 'X1', 'X2', 'X3', 'X4', 'X5', 'X6', 'X7', 'X8', 'X9', 'X10',
       'X11', 'X12', 'X13', 'X14', 'X15', 'X16', 'X17', 'X18', 'X19', 'X20',
       'X21', 'X22', 'X23', 'X24', 'X25', 'X26', 'X27', 'X28', 'X29', 'X30',
       'X31', 'X32', 'X33', 'X34', 'X35', 'X36', 'X37', 'X38', 'X39', 'X40',
       'X41', 'X42', 'X43', 'X44', 'X45', 'X46', 'X47', 'X48', 'X49', 'X50',
       'X51', 'X52', 'X53', 'X54', 'X55', 'X56', 'X57', 'X58', 'X59', 'X60',
       'X61', 'X62', 'X63']numeric_cols = Pipeline(steps=[
    ('selector', ColumnSelector(numeric_features))])# Combine categorical and numerical pipeline with FeatureUnion
preprocessor = FeatureUnion([
    ('select_numeric_cols',numeric_cols)
])pipe_feat_un = Pipeline(steps=[('preprocessor', preprocessor)])

如果您在建模之前有一些变量转换要做,那么您可以使用管道来实现它们,它们将与 udf 中的模型一起打包。

将轻型 gbm 模型添加到管线中:

clf = make_pipeline(lgb.LGBMClassifier())
model = make_pipeline(pipe_feat_un, clf)

根据训练数据拟合模型:

model.fit(X,y)

作者图片

在雪花中将模型保存为 udf

session2.clear_packages() #Clear out all existing packagessession2.clear_imports() #Clear out all existing importssession2.add_import('/opt/anaconda3/lib/python3.8/site-packages/mlxtend') #Add mlxtend as an import, since it is not available within Snowparksession2.add_packages('scikit-learn','lightgbm','pandas') #Add these packages to the udf, which exist in Snowpark session2.sql('create stage if not exists MODELSTAGE').collect() #Create a model stage if it does not already exist. 

作者图片

我想调用 add_import 函数。如果 Snowpark 中没有您需要的库,您可以很容易地添加它们。这增加了使用 Snowpark 的灵活性。

创建 Python udf:

[@udf](http://twitter.com/udf)(name='lightgbm_snowpark_digits',is_permanent = True, stage_location = '[@MODELSTAGE](http://twitter.com/MODELSTAGE)', replace=True, session=session2)def predict_digits(args: list) -> float:
    row = pd.DataFrame([args], columns=['X0', 'X1', 'X2', 'X3', 'X4', 'X5', 'X6', 'X7', 'X8', 'X9', 'X10',
       'X11', 'X12', 'X13', 'X14', 'X15', 'X16', 'X17', 'X18', 'X19', 'X20',
       'X21', 'X22', 'X23', 'X24', 'X25', 'X26', 'X27', 'X28', 'X29', 'X30',
       'X31', 'X32', 'X33', 'X34', 'X35', 'X36', 'X37', 'X38', 'X39', 'X40',
       'X41', 'X42', 'X43', 'X44', 'X45', 'X46', 'X47', 'X48', 'X49', 'X50',
       'X51', 'X52', 'X53', 'X54', 'X55', 'X56', 'X57', 'X58', 'X59', 'X60',
       'X61', 'X62', 'X63'])
    return model.predict(row)

预测的三个选项:

  1. 使用 Snowpark 使用我们保存的 udf 进行预测,并将数据带回本地环境:
session2.table('DIGITS_VALIDATION_DATA').select(F.call_udf("lightgbm_snowpark_digits",\
    F.array_construct('X0', 'X1', 'X2', 'X3', 'X4', 'X5', 'X6', 'X7', 'X8', 'X9', 'X10',
   'X11', 'X12', 'X13', 'X14', 'X15', 'X16', 'X17', 'X18', 'X19', 'X20',
   'X21', 'X22', 'X23', 'X24', 'X25', 'X26', 'X27', 'X28', 'X29', 'X30',
   'X31', 'X32', 'X33', 'X34', 'X35', 'X36', 'X37', 'X38', 'X39', 'X40',
   'X41', 'X42', 'X43', 'X44', 'X45', 'X46', 'X47', 'X48', 'X49', 'X50',
   'X51', 'X52', 'X53', 'X54', 'X55', 'X56', 'X57', 'X58', 'X59', 'X60',
   'X61', 'X62', 'X63')).alias('DIGITS_VALIDATION_DATA_PREDICTION')).collect()

作者图片

2.使用 Snowpark 使用我们保存的 udf 进行预测,并将输出保存为雪花中的一个表:

session2.table('DIGITS_VALIDATION_DATA').select(F.call_udf("lightgbm_snowpark_digits",\
    F.array_construct('X0', 'X1', 'X2', 'X3', 'X4', 'X5', 'X6', 'X7', 'X8', 'X9', 'X10',
   'X11', 'X12', 'X13', 'X14', 'X15', 'X16', 'X17', 'X18', 'X19', 'X20',
   'X21', 'X22', 'X23', 'X24', 'X25', 'X26', 'X27', 'X28', 'X29', 'X30',
   'X31', 'X32', 'X33', 'X34', 'X35', 'X36', 'X37', 'X38', 'X39', 'X40',
   'X41', 'X42', 'X43', 'X44', 'X45', 'X46', 'X47', 'X48', 'X49', 'X50',
   'X51', 'X52', 'X53', 'X54', 'X55', 'X56', 'X57', 'X58', 'X59', 'X60',
   'X61', 'X62', 'X63')).alias('DIGITS_VALIDATION_DATA_PREDICTION')).write.mode('overwrite').saveAsTable('light_gbm_snowpark_digits_validation')#The array construct is how Snowflake passes in the data to the udf as a single column array of all the data, similar to Spark's feature vector format. 

在 Snowpark 中连接到的仓库、数据库和模式中,您现在可以在 Snowflake 中看到保存的表。

3.在雪花中使用 sql 代码调用 udf,然后在雪花中对表中的数据进行预测:

select lightgbm_snowpark_digits(array_construct(X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10,
 X11, X12, X13, X14, X15, X16, X17, X18, X19, X20,
 X21, X22, X23, X24, X25, X26, X27, X28, X29, X30,
 X31, X32, X33, X34, X35, X36, X37, X38, X39, X40,
 X41, X42, X43, X44, X45, X46, X47, X48, X49, X50,
 X51, X52, X53, X54, X55, X56, X57, X58, X59, X60,
 X61, X62, X63)) from ”DIGITS_VALIDATION_DATA”;

我希望您喜欢这篇快速指南,它讲述了如何训练一个本地模型,将其打包成一个 udf,使用 Snowpark 将该 udf 上传到 Snowflake,然后使用 Snowpark 或 Snowflake 对该数据进行预测。

参考文献:

阿尔帕丁和凯纳克..(1998).手写数字的光学识别。UCI 机器学习知识库。

使用 Python UDF 和 Snowflake 的 Snowpark 构建和部署机器学习模型,第 2 部分

原文:https://towardsdatascience.com/using-python-udfs-and-snowflake-s-snowpark-to-build-and-deploy-machine-learning-models-part-2-2fe40d382ae7

如何训练和预测所有上传到 Snowpark 的 UDF

图片来自 Anthi K 通过 Unsplash


更新:在与雪花交谈后,我在这里尝试复制的这个方法是为了一次训练多个模型,每个模型高达 16mb 的数据。同样,使用雪花进行计算,最大文件大小为 5GB。所以任何大数据还是需要在 Spark 这样的东西里做。他们有一个更大文件的私人预览,但目前还没有对公众开放。

我认为 python 存储过程或第 1 部分是使用雪花构建和部署机器学习模型的最佳选择。


第 1 部分中,我展示了如何训练一个本地模型,将其包装在 Python udf 中,使用 Snowpark 将其推送到雪花,并使用 Snowpark 或雪花 Sql 使用该 udf 进行预测。

另一方面,本指南将向您展示如何使用 Snowpark 和 Snowflake compute 创建一个 Python udf 来构建、训练和预测模型。我们将在这里使用回归,而第 1 部分使用分类。

本文还将强调我在使用 Snowpark 和 Python udf 时发现的一些局限性。

我使用了 Github 上的 snowflake's guide 中的这个指南来获得构建它的主要框架。

导入库

import snowflake.connector
import pandas as pd
import numpy as np
from sqlalchemy import create_engine
from snowflake.sqlalchemy import URLfrom snowflake.connector.pandas_tools import write_pandas
from snowflake.snowpark.functions import udf
from snowflake.snowpark.types import IntegerType, StringType, StructType, FloatType
from snowflake.snowpark.session import Session
from snowflake.snowpark import Session
import snowflake.snowpark.functions as F
from snowflake.snowpark import types as T
from snowflake.snowpark import Window
from snowflake.snowpark.functions import udf, max, min, count, avg, sum, col, lit, listagg
import mlxtend
from mlxtend.feature_selection import ColumnSelectorimport lightgbm as lgb
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.pipeline import make_pipeline, Pipeline, FeatureUnion
from sklearn import datasets

数据

我们将解决一个回归问题,使用来自 sklearn 数据集的糖尿病数据集。阿尔 2004 年)。这个数据集是在知识共享署名 4.0 国际 (CC BY 4.0)许可下许可的。

这允许为任何目的共享和改编数据集,只要给予适当的信任。

#Load the features
db = pd.DataFrame(datasets.load_diabetes().data, columns=datasets.load_diabetes().feature_names)#Load the response variable
db['target'] = datasets.load_diabetes().target#Convert to upper for preparation for uploading to Snowflake columnnames=[x.upper() for x in db.columns] 
db.columns=columnnames
db.head()

作者图片

print(datasets.load_diabetes().DESCR)

作者图片

作者图片

让我们将糖尿病数据保存为雪花中的一个表。我们稍后可以使用 Snowpark 连接到它:

#Set up the snowflake connection
ctx = snowflake.connector.connect(
    user='<user>',
    password='<password>',
    account='<account-identifier>',
    database='<database>',
    warehouse='<warehouse>',
    role='<role>',
    schema='<schema>'
    )#Create the input string for the diabetes data 
snowflake_cols=[str(x) + ' float' for x in db.columns]
s = ', '.join(snowflake_cols)#Create the empty table in Snowflake
ctx.cursor().execute(
"""CREATE OR REPLACE TABLE
DIABETES_DATA(""" + s + """)""")#Copy the table into snowflake
write_pandas(ctx, db, 'DIABETES_DATA')

连接到 Snowpark 中的数据:

#Create snowpark session to connect to saved table. 
def create_session_object():
   connection_parameters = {
      "account": "<account-identifier>",
      "user": "<user>",
      "password": "<password>",
      "role": "<role>",
      "warehouse": "<warehouse>",
      "database": "<database>",
      "schema": "<schema>"
   }
   session = Session.builder.configs(connection_parameters).create()
   print(session.sql('select current_warehouse(), current_database(), current_schema()').collect())
   return session#Create two sessions, one for doing initial testing, and one for pushing the udf to snowpark. I've found the session tends to fail if I use just one session for both. session=create_session_object()session2=create_session_object()cols=session.table('DIABETES_DATA')
cols.schema.fields

作者图片

造型

在我看来,构建 udf 时要记住的一件重要事情是,数据必须作为 array_construct()或所有特性的单个列传入。这类似于 Spark 中的 features 列输入。

因此,为了准备我们的 udf,让我们看看是否可以使用 Snowpark array_construct 作为输入来构建一个本地模型。这样我们就能知道我们的代码是否能在我们上传到 Snowpark 的 udf 中工作。

columns_list=['AGE', 'SEX', 'BMI', 'BP', 'S1', 'S2', 'S3', 'S4', 'S5', 'S6','TARGET']#Connect to the table in Snowflake and create an array construct of the data to be used for modeling. digits=session2.table('DIABETES_DATA').select(F.array_construct(*columns_list).alias('INPUT_DATA'))#Collect the data from Snowpark
tbl=pd.DataFrame(digits.collect())tbl.head()

作者图片

您可以看到,将它读入我们的本地环境并尝试将其转换为数据帧是行不通的。它仍然将它保存为一列。让我们使用 Python 中的 str.split 方法将数据分离出来。

tbl=tbl['INPUT_DATA'].str.split(',\n', expand=True)
tbl.head()

作者图片

我们仍然有一些时髦的新的行字符和括号,所以让我们去掉它们。

tbl[0]=[x.replace('\n','').replace('[  ','').replace(']','') for x in tbl[0]]tbl[10]=[x.replace('\n','').replace('[  ','').replace(']','') for x in tbl[10]]tbl.head()

我们的数据现在看起来不错,让我们看看能否构建一个模型:

#Check NAs
count_nas=pd.DataFrame(tbl.isna().sum())
count_nas[count_nas[0]>0]#Fill in NAs if there were any 
tbl=tbl.fillna(0)#Set the column names to what our original table in Snowflake has 
tbl.columns=columns_list#Split into features, target, training and validation
X=tbl.drop(columns='TARGET')
y=tbl['TARGET']
X_train, X_valid, y_train, y_valid = train_test_split(X, y, random_state=1234, test_size=.33)#Build a pipeline 
numeric_features=['AGE', 'SEX', 'BMI', 'BP', 'S1', 'S2', 'S3', 'S4', 'S5', 'S6']numeric_cols = Pipeline(steps=[
    ('selector', ColumnSelector(numeric_features))])# Combine categorical and numerical pipeline with FeatureUnion
preprocessor = FeatureUnion([
    ('select_numeric_cols',numeric_cols)
])pipe_feat_un = Pipeline(steps=[('preprocessor', preprocessor)])#Light gbm
clf = make_pipeline(lgb.LGBMRegressor())#Add the model to the pipeline
model = make_pipeline(pipe_feat_un, clf)#Fit on the training data
model.fit(X_train,y_train)#Predict on the validation data
preds=model.predict(X_valid)#Look at the predictions
preds

作者图片

这些是我们所期望的,连续范围内的预测。

现在,让我们把它包装成一个函数,这样我们就可以把它作为一个 udf 上传:

def lightgbm_train_predict_udf(db, columns_list):
    import pandas as pd
    import numpy as np
    from lightgbm import LGBMRegressor
    from sklearn.pipeline import make_pipeline, Pipeline, FeatureUnion
    from mlxtend.feature_selection import ColumnSelector
    import mlxtend #Read in the data 
    tbl=pd.DataFrame(db, columns=['INPUT_DATA'])
    tbl=tbl['INPUT_DATA'].str.split(',\n', expand=True)
    tbl[0]=[x.replace('\n','').replace('[  ','').replace(']','') for x in tbl[0]]
    tbl[10]=[x.replace('\n','').replace('[  ','').replace(']','') for x in tbl[10]] #Fill in NAs if there are any
    tbl=tbl.fillna(0) #Change the column names to what our table in Snowflake has
    tbl.columns=columns_list #Split into features, target, training and validation
    X=tbl.drop(columns='TARGET')
    y=tbl['TARGET']
    X_train, X_valid, y_train, y_valid = train_test_split(X, y, random_state=1234, test_size=.33) #Build a pipeline
    numeric_features=['AGE', 'SEX', 'BMI', 'BP', 'S1', 'S2', 'S3', 'S4', 'S5', 'S6']
    numeric_cols = Pipeline(steps=[
        ('selector', ColumnSelector(numeric_features))]) # Combine categorical and numerical pipeline with FeatureUnion
    preprocessor = FeatureUnion([
        ('select_numeric_cols',numeric_cols)
    ])
    pipe_feat_un = Pipeline(steps=[('preprocessor', preprocessor)]) #Light gbm
    clf = make_pipeline(lgb.LGBMRegressor()) #Add the model to the pipeline
    model = make_pipeline(pipe_feat_un, clf) #Fit on the training data
    model.fit(X_train,y_train) #Predict on the validation data
    preds=model.predict(X_valid)
    return(preds)

现在让我们将它上传为一个 udf:

dep_imports=['/opt/anaconda3/lib/python3.8/site-packages/mlxtend']lightgbm_train_predict_udf = session2.udf.register(lightgbm_train_predict_udf, name="lightgbm_train_predict_udf",is_permanent=True,                      stage_location='MODELSTAGE', packages=['numpy','scikit-learn','lightgbm','pandas'], imports=dep_imports, input_types [T.ArrayType(), T.ArrayType()],return_type=T.ArrayType(), replace=True)

我想调出 session.udf.register 函数的导入参数。如果 Snowpark 中没有您需要的库,您可以很容易地添加它们。这增加了使用 Snowpark 的灵活性。在本例中,我正在导入 mlxtend,这允许我在管道中使用 ColumnSelector 函数。

这里需要注意的另一个要点是,我们只能使用在 Snowpark 类型文档中列出的类型。具体来说,我们必须使用数组构造作为 udf 的输入。

现在,让我们测试我们上传的 udf:

columns_list=['AGE', 'SEX', 'BMI', 'BP', 'S1', 'S2', 'S3', 'S4', 'S5', 'S6','TARGET']#Connect to the table in Snowflake and create an array construct for input into the udf diabetes=session2.table('DIABETES_DATA').select(F.array_construct(*columns_list).alias('INPUT_DATA'))#Create an array construct of the column names to be fed in the function 
input_column_names = F.array_construct(*[F.lit(x) for x in columns_list])#Call the udf and look at the results output=diabetes.select(F.call_udf('lightgbm_train_predict_udf', F.col('INPUT_DATA'), input_column_names).alias('ALL_ONE_UDF_PREDICTED')).collect()output

作者图片

此代码在' TBL = TBL[' INPUT _ DATA ']. str . split(',\n ',expand=True)处失败。这是没有意义的,因为第一次测试时,它在我们的本地环境中工作。

def lightgbm_train_predict_udf(db, columns_list):
    tbl=pd.DataFrame(db, columns=['INPUT_DATA'])
    tbl=tbl['INPUT_DATA'].str.split(',\n', expand=True)

让我们对此进行调整,并再次测试 udf,看看第一次运行' tbl=pd 时它会返回什么。DataFrame(db,columns=['INPUT_DATA'])'。由于我们只能根据 Snowpark 的文档返回数据类型,所以我找到的最简单的方法是 np.array()。这与 Snowpark ArrayType()无缝地相互转换。返回 DataFrame 不起作用,也不受支持。

def lightgbm_train_predict(diabetes, columns_list):
    import pandas as pd
    import numpy as np
    from lightgbm import LGBMRegressor
    from sklearn.pipeline import make_pipeline, Pipeline, FeatureUnion
    from mlxtend.feature_selection import ColumnSelector
    import mlxtend
    tbl=pd.DataFrame(diabetes, columns=['INPUT_DATA'])
    return(np.array(tbl))

dep_imports=['/opt/anaconda3/lib/python3.8/site-packages/mlxtend']#Register the udf to Snowflake/Snowpark
lightgbm_train_predict_udf = session2.udf.register(lightgbm_train_predict, name="lightgbm_train_predict_udf", is_permanent=True, stage_location='MODELSTAGE', 
packages=['numpy','scikit-learn','lightgbm','pandas'], 
imports=dep_imports, input_types=[T.ArrayType(), T.ArrayType()],
return_type=T.ArrayType(), replace=True) columns_list=['AGE', 'SEX', 'BMI', 'BP', 'S1', 'S2', 'S3', 'S4', 'S5', 'S6','TARGET']#Connect to the table in Snowflake and create an array construct for input into the udf diabetes=session2.table('DIABETES_DATA').select(F.array_construct(*columns_list).alias('INPUT_DATA'))#Create an array construct of the column names to be fed in the function 
input_column_names = F.array_construct(*[F.lit(x) for x in columns_list])#Call the udf and look at the results output=diabetes.select(F.call_udf('lightgbm_train_predict_udf', F.col('INPUT_DATA'), input_column_names).alias('ALL_ONE_UDF_PREDICTED')).collect()
output

这将返回我们所期望的结果。但是这意味着我们的 split 方法在 udf 中不起作用。这让我相信 Snowpark 中有一些我无法理解的数据类型变化。我的假设是,这与让 Python 在雪花上运行的约束有关。

为前面的另一个示例做准备,让我们使用以下内容对此数组进行子集划分:

output[0:1]

作者图片

output[0:1]返回数组的第一行。

接下来,下面是我想到的最好的解决方法,可以让 udf 在读入数据和格式化数据以进行建模时工作。

def lightgbm_train_predict(db, columns_list):
    db=np.array(db)
    objects=[pd.DataFrame(db[i:i+1]) for i in range(0,11)]
    tbl=pd.concat(objects, axis=1) #Fill in NAs if there are any
    tbl=tbl.fillna(0) #Change the column names to what our table in Snowflake has
    tbl.columns=columns_list
    return(np.array(tbl))dep_imports=['/opt/anaconda3/lib/python3.8/site-packages/mlxtend']#Register the udf to Snowpark/Snowflake
lightgbm_train_predict_udf = session2.udf.register(lightgbm_train_predict, name="lightgbm_train_predict_udf", is_permanent=True, stage_location='MODELSTAGE', packages=['numpy','scikit-learn','lightgbm','pandas'], imports=dep_imports, input_types=[T.ArrayType(), T.ArrayType()], return_type=T.ArrayType(), replace=True)columns_list=['AGE', 'SEX', 'BMI', 'BP', 'S1', 'S2', 'S3', 'S4', 'S5', 'S6','TARGET']#Connect to the table in Snowflake and create an array construct for input into the udf diabetes=session2.table('DIABETES_DATA').select(F.array_construct(*columns_list).alias('INPUT_DATA'))#Create an array construct of the column names to be fed in the function 
input_column_names = F.array_construct(*[F.lit(x) for x in columns_list])#Call the udf and look at the results output=diabetes.select(F.call_udf('lightgbm_train_predict_udf', F.col('INPUT_DATA'), input_column_names).alias('ALL_ONE_UDF_PREDICTED')).collect()output

作者图片

因为这返回了我们所有数据的数组,所以我们可以知道将它转换为 DataFrame 并重命名所有列是有效的。否则,它就会出错。

我想指出的上面代码中最大的标注是这三行代码:

def lightgbm_train_predict(diabetes, columns_list):
    diabetes=np.array(diabetes)
    objects=[pd.DataFrame(diabetes[i:i+1]) for i in range(0,11)]

一旦我们将它转换成一个 np.array,使用 digits[0:1]对它进行子集化会返回第一个,而当我们将它作为子集输出[0:1]带到本地环境时,它会返回第一个行。我真的不确定为什么 Snowpark 的 Python 实例会这样,但我希望它不会这样。找出这种差异就像戴上眼罩,试图根据颜色的感觉来解开魔方。我不推荐这样做,也不想再做了。

让我们尝试重新注册 udf 并构建模型:

def lightgbm_train_predict(db, columns_list):
    import pandas as pd
    import numpy as np
    from lightgbm import LGBMRegressor
    from sklearn.pipeline import make_pipeline, Pipeline, FeatureUnion
    from mlxtend.feature_selection import ColumnSelector
    import mlxtend #Read in the data 
    db=np.array(db)
    objects=[pd.DataFrame(db[i:i+1]) for i in range(0,11)]
    tbl=pd.concat(objects, axis=1) #Fill in NAs if there are any
    tbl=tbl.fillna(0) #Change the column names to what our table in Snowflake has
    tbl.columns=columns_list #Split into features, target, training and validation
    X=tbl.drop(columns='TARGET')
    y=tbl['TARGET']
    X_train, X_valid, y_train, y_valid = train_test_split(X, y, random_state=1234, test_size=.33) #Build a pipeline
    numeric_features=['AGE', 'SEX', 'BMI', 'BP', 'S1', 'S2', 'S3', 'S4', 'S5', 'S6']
    numeric_cols = Pipeline(steps=[
        ('selector', ColumnSelector(numeric_features))]) # Combine categorical and numerical pipeline with FeatureUnion
    preprocessor = FeatureUnion([
        ('select_numeric_cols',numeric_cols)
    ])
    pipe_feat_un = Pipeline(steps=[('preprocessor', preprocessor)]) #Light gbm
    clf = make_pipeline(lgb.LGBMRegressor()) #Add the model to the pipeline
    model = make_pipeline(pipe_feat_un, clf) #Fit on the training data
    model.fit(X_train,y_train) #Predict on the validation data
    preds=model.predict(X_valid)
    return(preds)dep_imports=['/opt/anaconda3/lib/python3.8/site-packages/mlxtend']#Register the udf to Snowpark/Snowflake
lightgbm_train_predict_udf = session2.udf.register(lightgbm_train_predict, name="lightgbm_train_predict_udf", is_permanent=True, stage_location='MODELSTAGE', packages=['numpy','scikit-learn','lightgbm','pandas'], imports=dep_imports, input_types=[T.ArrayType(), T.ArrayType()], return_type=T.ArrayType(), replace=True)columns_list=['AGE', 'SEX', 'BMI', 'BP', 'S1', 'S2', 'S3', 'S4', 'S5', 'S6','TARGET']#Connect to the table in Snowflake and create an array construct for input into the udf diabetes=session2.table('DIABETES_DATA').select(F.array_construct(*columns_list).alias('INPUT_DATA'))#Create an array construct of the column names to be fed in the function 
input_column_names = F.array_construct(*[F.lit(x) for x in columns_list])#Call the udf and look at the results output=diabetes.select(F.call_udf('lightgbm_train_predict_udf', F.col('INPUT_DATA'), input_column_names).alias('ALL_ONE_UDF_PREDICTED')).collect()output

作者图片

耶,又一个错误。这个错误是 sklearn train_test_split 函数。不管是什么原因,sklearn 的 train_test_split 在 Snowpark 内部是行不通的。此外,使用 light gbm 时出现错误。但是我将跳过这个例子。

我修复这些错误的方法是使用 np.random.rand 和 xgboost 手动分成训练和测试。这是我的最终解决方案,我甚至不确定这是否正确。尽管如此,它确实完成了整个过程,并且预测没有显示错误。

def xgboost_train_predict(db, columns_list):
    import pandas as pd
    import numpy as np
    from xgboost import XGBRegressor
    from sklearn.model_selection import GridSearchCV, train_test_split
    from sklearn.preprocessing import MultiLabelBinarizer, OneHotEncoder, FunctionTransformer
    from sklearn.pipeline import make_pipeline, Pipeline, FeatureUnion
    from sklearn.compose import ColumnTransformer
    from mlxtend.feature_selection import ColumnSelector
    import mlxtend #Read in the data
    db=np.array(db)
    objects=[pd.DataFrame(db[i:i+1]) for i in range(0,11)]
    tbl=pd.concat(objects, axis=1) #Fill in NAs if there are any
    tbl=tbl.fillna(1) #Change the column names to what our table in Snowflake has
    tbl.columns=columns_list

    # #Split into training and validation
    tbl['prob'] = np.random.rand(len(tbl))
    tbl['counter'] = [1 if x <= .65 else 0 for x in tbl['prob']]
    # return(np.array(tbl))
    training_data = tbl.loc[tbl['counter']==1,].reset_index(drop=True)
    testing_data = tbl.loc[tbl['counter']==0,].reset_index(drop=True)
    X_train=training_data.drop(columns=['TARGET','prob','counter'])
    y_train=training_data['TARGET']
    X_valid=testing_data.drop(columns=['TARGET','prob','counter'])
    y_valid=testing_data['TARGET'] #Build a pipeline
    numeric_features=['AGE', 'SEX', 'BMI', 'BP', 'S1', 'S2', 'S3', 'S4', 'S5', 'S6']
    numeric_cols = Pipeline(steps=[
        ('selector', ColumnSelector(numeric_features))])
    # Combine categorical and numerical pipeline with FeatureUnion
    preprocessor = FeatureUnion([
        ('select_numeric_cols',numeric_cols)
    ])
    pipe_feat_un = Pipeline(steps=[('preprocessor', preprocessor)]) #Light gbm
    clf = make_pipeline(XGBRegressor(n_estimators=5)) #Add the model to the pipeline
    model = make_pipeline(pipe_feat_un, clf) #Fit on the training data
    model.fit(X_train, y_train) #Predict on the validation data
    preds=model.predict(X_valid)
    return(preds)dep_imports=['/opt/anaconda3/lib/python3.8/site-packages/mlxtend']xgboost_train_predict_udf = session2.udf.register(xgboost_train_predict,name="xgboost_train_predict_udf",is_permanent=True,stage_location='MODELSTAGE',packages=['numpy','scikit-learn','xgboost','pandas'], imports=dep_imports, input_types=[T.ArrayType(),T.ArrayType()], return_type=T.ArrayType(),replace=True)columns_list=['AGE', 'SEX', 'BMI', 'BP', 'S1', 'S2', 'S3', 'S4', 'S5', 'S6','TARGET']#Connect to the table in Snowflake and create an array construct for input into the udf diabetes=session2.table('DIABETES_DATA').select(F.array_construct(*columns_list).alias('INPUT_DATA'))#Create an array construct of the column names to be fed in the function 
input_column_names = F.array_construct(*[F.lit(x) for x in columns_list])#Call the udf and look at the results output=diabetes.select(F.call_udf('xgboost_train_predict_udf', F.col('INPUT_DATA'), input_column_names).alias('ALL_ONE_UDF_PREDICTED')).collect()output

作者图片

看看这些结果,Snowpark 处理模型的方式有明显的错误。首先,并不是所有的预测都应该是 0.5。当我们在本地建立模型时,它应该在一个连续的范围内。其次,它带来了空数组,因为 Snowpark 不会在训练和测试分割期间使用 data frame . reset _ index(drop = True)重新索引数据帧或数组。请参见下面的代码:

training_data = tbl.loc[tbl['counter']==1,].reset_index(drop=True)
testing_data = tbl.loc[tbl['counter']==0,].reset_index(drop=True)

结论:

我认为最好在本地构建一个模型,然后像我们在本系列第 1 部分中所做的那样,将它作为 udf 发送到雪花的 Snowpark。我的观点是,Snowpark 的 Python 仍然处于早期阶段,还不具备使用本地环境的 Python 所具备的许多功能的能力。

在 Snowpark 的 Python 中引入错误的函数,这些函数在我的本地环境中使用相同的进程和数据:

  1. 相同的数组子集/维度数组。例如,上例中的 output[0:1]返回本地环境中的第一行,以及 Snowpark 中的第一列
  2. 从 Snowpark 阵列或数据帧转换到 pandas 数据帧
  3. Sklearn 的 train_test_split()
  4. Lightgbm
  5. 重新索引数据帧或数组
  6. Tfidf()和 countvectorizer()

我希望 Snowpark 中的 udf 具有的功能:

  1. 可以选择返回数据帧作为上传 udf 的结果
  2. 将模型作为上传的 udf 的输出返回

总的来说,我看到了 Snowpark 的可用性,我对即将到来的变化和改进感到兴奋。如果雪花修复了这些错误,我将发布一个更新的指南和一个工作实例。

参考文献:

Bradley Efron,Trevor Hastie,Iain Johnstone 和 Robert Tibshirani (2004)“最小角度回归”,统计年鉴(附讨论),407–499。(https://web . Stanford . edu/~ hastie/Papers/LARS/leas tangle _ 2002 . pdf)

在熊猫中使用重采样

原文:https://towardsdatascience.com/using-resample-in-pandas-14e5483593aa

学习如何使用熊猫重采样方法,这是一种处理基于时间的数据的很酷的方法

马库斯·斯皮斯克的照片

基于时间的数据是最常见的数据格式之一,作为数据科学家,您可能会遇到这种情况。无论是历史特征(例如,客户数据)还是时间序列数据,都经常需要处理数据管道中的时间戳列。

如果你在 2022 年是一名数据科学家,你必须知道如何使用pandas,这是最强大的 Python 库之一,可以处理数据。pandas以其定期更新和新方法而闻名,适用于许多关于数据争论和操纵的任务。

它最著名的方法之一是resample方法,可以让你平稳地处理基于时间的数据。简而言之,resample包含几个特性,可以帮助您以一种非常流畅的方式处理基于时间的分组和聚合,从而提高处理 datetime 列的速度和简单性。在过去,你可能不得不构造复杂的(甚至是错误的)函数,这将会很难维护。有了resample,这种情况完全消失了,你可以更轻松地在pandas数据框中摆弄日期。

在本帖中,我们将探索关于这种方法的一些很酷的事情,以及应用它有多容易——特别是当我们想要为我们的数据科学算法创建基于时间的功能时。

读取数据

首先,我将从加载一个时间序列数据集开始,该数据集由土耳其加利波利2008 年至 2021 年间的每小时天气数据组成——该数据集属于 Meteoblue

让我从将这个数据集加载到 Python 开始:

gallipoli_data = pd.read_csv(‘../Hourly Weather Data in Gallipoli (2008–2021).csv’)

让我们看看数据集的前 10 行:

Gallipoli 数据集的先睹为快—图片由作者提供

要使用resample,我们的数据必须满足两个条件:

  • 我们将用来表示时间的列必须是datetime数据类型。
  • 该列必须放入数据帧的索引中。

首先将DateTime列转换为日期时间类型:

gallipoli_data[‘DateTime’] = pd.to_datetime(gallipoli_data[‘DateTime’])

并将我们的列传递到数据帧的索引中:

gallipoli_data.index = gallipoli_data[‘DateTime’]

我们的数据框准备好了!让我们也加载跟随这篇博文所需的库:

import pandas as pdimport numpy as np

一切都准备好了,让我们开始处理我们的resample方法吧!

使用重采样

让我们从一个简单的用例开始:假设我们希望获得 Gallipoli 每 3 小时的平均温度。虽然简单,但如果我们试图用基本 Python 或定制的pandas代码来做这件事,就会有点麻烦,因为我们必须:

  • 创建某种类型的每 3 小时增加一次的列;
  • 对该列使用 group by

虽然这可以用几行代码来管理,但是一行代码会更好,对吗?如果一个班轮也能够推广到其他时间框架(几周,几个月,几个季度甚至几分钟)呢?这正是resample的作用!我们可以非常快速地对数据执行基于时间的快速聚合:

gallipoli_data.Temperature.resample(rule = ‘3h’).mean()

下面是上面代码的输出:

使用 3h 作为时间范围进行重新取样—图片由作者提供

多酷?resample方法包含一堆我们可以调节的旋钮,比如我们正在使用的规则或聚合函数。如果我们想检查 3 小时间隔内日照时间的最大值,你能猜到我们需要调整什么吗?我们只要把mean()换成max()

gallipoli_data[‘Sunshine Duration’].resample(rule = ‘3h’).max()

使用 3h 作为时间帧,但使用最大值函数重新取样-作者提供的图像

为了让这一点在resample的引擎盖下更加清晰和突出,让我们来看看 1 月 2 日在加利波利举行的Sunshine Duration的演变:

(
    gallipoli_data.loc[
        (gallipoli_data.DateTime >= '2008-01-02') 
        & 
        (gallipoli_data.DateTime <= '2008-01-03')
    ]
)['Sunshine Duration'].plot(color='darkorange')

2008 年 1 月 2 日的日照时间—图片由作者提供

resample所做的是通过应用以下内容对每三个小时的数据进行分组:

  • 从上午 9 点到 12 点,最大Sunshine Duration是 60。
  • 中午到下午 15 点,最大Sunshine Duration60。
  • 从下午 15 点到 18 点,最大值Sunshine Duration为 38。

这反映在我们上面看到的表格中—再次检查:

使用最大值功能以 3 小时为时间范围进行重新取样—图片由作者提供

请注意,间隔在左侧是闭合的。这意味着与2008-01-02 09:00:00相关的行聚集了从上午 09:00:00 到上午 11:59:59 的全部数据。如果我们想要改变这种行为,我们需要向resample传递一个新的参数closed:

gallipoli_data['Sunshine Duration'].resample(rule = '3h', closed= 'right').max()

使用 3h 作为时间帧重新取样,使用 Max 和 Closed = 'Right'—图片由作者提供

在这种情况下,同一行2008-01-02 09:00:00汇总从上午 09:00:01 到上午 12:00:00 的所有数据

拧动resample旋钮非常容易。我们可能想要调整的另一个常见论点是时间框架聚合——例如,按周或月聚合我们的数据。这可能吗?让我们看看,下一个。

修改规则参数

使用rule参数,我们不仅仅局限于每小时的聚合!这是resample功能最酷的特性之一。例如,如果我们想要两周一次的汇总,我们可以:

gallipoli_data.Temperature.resample(rule = ‘2w’).mean()

两周一次的温度汇总-图片由作者提供

w前的数字定义了我们想要分组的实例数量,而w定义了我们想要一周的时间范围。

你能猜出我们需要传递什么参数来聚合每 4 个月的数据吗?

gallipoli_data.Temperature.resample(rule = ‘4m’).mean()

4 个月聚合示例—按作者分类的图像

总之,rule控制我们想要分组的时间间隔。您可以使用的一些最著名的rule参数是:

  • s为秒
  • t持续分钟
  • h数小时
  • w为周
  • m持续数月
  • q为季度

填补空白

resample的另一个重要特性是,它可以帮助您通过理解数据中的缺口来创建更完整的数据框架。例如,假设我们有一个商店的每小时销售数据集:

date_range = pd.date_range('1/2/2022', periods=24, freq='H')
sales = np.random.randint(100, 400, size=24)sales_data = pd.DataFrame(
    sales,
    index = date_range,
    columns = ['Sales']
)

sales_data 数据框架的标题—按作者分类的图像

让我们删除上午 10 点的销售行。(想象这是真实发生的事情,并且在此期间该商店没有销售):

sales_data = sales_data.drop(sales_data.iloc[10].name)

上午 10 点没有销售的销售数据数据框架的标题—作者图片

如果我们将一个resample应用到这个数据帧,将会出现一个带有NaN的新行:

pd.DataFrame(
    sales_data.Sales.resample('h').mean()
)

重新取样后上午 10 点没有销售额的 sales_data 数据帧的标题-作者图片

这在很多基于时间的场景中非常方便。通常情况下,如果我们的时间序列数据中有一个缺口,我们希望有一行来表示这个缺口,而resample为我们提供了一个快速实现它的方法。在上面的例子中,我们甚至可以添加一个fillna(0)来使我们上午 10 点的“缺乏销售”场景更加明显:

重新取样后上午 10 点没有销售额的 sales_data 数据帧的标题-作者图片

填补空白是我们在处理基于时间的数据时经常要做的事情。不仅仅是在创建特性的时候,也是在我们的数据管道中捕捉主要错误的一种方式。

感谢你花时间阅读这篇文章!

如果你使用基于时间的数据,并且以前从未使用过resample,我真的建议你彻底学习这种方法,因为它将在你的数据转换过程中为你省去很多麻烦。

无论是当我们构建基于时间的特性还是操作时序数据时,使用resample会使我们的代码更加高效和简洁。正如我们已经讨论过的,这种方法对于捕捉基于时间的数据中的一些意外缺口也很方便,所以我希望您可以在未来的数据管道中使用本文中的知识!

本帖使用的数据集在 CC BY 4.0 下,属于Meteoblue

https://medium.com/membership/@ivopbernardo

使用情感分析探索文本中的情感

原文:https://towardsdatascience.com/using-sentiment-analysis-to-explore-emotions-within-text-ae48e3e93999

应用 2 自然语言处理技术来比较玛丽·雪莱的《弗兰肯斯坦》中的情感与 TF-IDF 的每章关键词分析。

照片由 Neven KrcmarekUnsplash 上拍摄

前提

玛丽·雪莱的《弗兰肯斯坦》不仅因其在流行文化中的突出地位而闻名(例如https://www . national theatre . org . uk/shows/nt-at-home-Frankenstein),还因其在文学经典中的地位而闻名。这部小说充斥着(当时)新奇的想法,再加上情感的大起大落——这一点在这本书的形式中得到了反映。这样一个故事的丰富和复杂的结构使它成为情感分析的完美候选。

数据集

数据集是从古腾堡项目提供的在线书籍中提取的。

关键词分析

为了验证,我们将每章的情感立场与使用 TF-IDF 算法提取的摘要关键词进行比较。这将在下面描述,并被发现可靠地代表了我之前的作品所展示的弗兰肯斯坦的叙事:

内容

在本文中,我们主要关注两个支持情感分析的 python NLP 库。这些是:

  • TextBlob
  • Text2Emotion

我们将依次把这些应用到《弗兰肯斯坦》的文本中,并利用这种类型的分析向我们揭示的任何洞见。

TextBlob:简化文本处理

我们使用的第一个 NLP 库叫做textblob,可以使用pip install textblob安装。这是一个一体化的自然语言处理 API,允许我们提取关于文本情感或极性的信息。

使用

我们首先将我们的章节文本转换成一个 blob,然后可以查询它以获得总体统计数据。

from textblob import TextBlobblob = TextBlob(chapter)

这将返回一个具有几个属性的已处理对象,这将在下面讨论。

感情

由于我们主要对情感分析感兴趣,我们可以提取文本的主观性和极性:

blob.sentiment# Sentiment(polarity=0.11179997960976226, subjectivity=0.48238807327937727)

在这里,我们的文本内容被赋予两个分数:

  • 主观性:这是排名[0–1],其中 1 是主观的,0 是客观的
  • 极性:文本音调的等级,其中+1 为正,-1 为负。接近 0 的值表示中性。

极性

结合前面工作中的关键词分析,我们可以使用它们的极性对章节进行排序。虽然这里所有的值都大于零,但分数越低的章节出现在怪物被追捕或胜利者哀悼死亡的地方。

chapter 16         [cottage, hidingplace, towards, felix, wood]chapter 10                    [thy, thou, ice, thee, mountains]chapter 17             [shall, consent, swear, demand, portion]chapter 8     [innocence, justine, elizabeth, judges, confes...chapter 24                  [yet, ice, vengeance, shall, still]chapter 7            [william, poor, ernest, murderer, justine]

相反,更快乐的篇章是在维克多·弗兰肯斯坦的青年时代,那时他对科学和医学的热情和兴趣仍然驱动着他所有的意图。同样地,我们也受到怪物的教育和崇拜,尽管他观察了菲利克斯和林中老人(第 12-15 章)。然而,当他新收养的家庭回避他时,这很快就被并置了(第 16 章)。

chapter 15             [protectors, cottage, admire, old, read]chapter 12                 [cottagers, felix, found, day, wood]chapter 2           [agrippa, studies, spirit, things, natural]chapter 4               [pursuit, study, life, one, corruption]

主观性

同样,我们可以看看哪些章节更主观。我们再次看到与科学和发现有一个更客观的方向有关的章节:

chapter 3     [science, natural, professor, philosophy, modern]chapter 11                  [cottage, found, fire, wood, light]chapter 4               [pursuit, study, life, one, corruption]

而那些关注情感和感觉的人更主观:

chapter 7            [william, poor, ernest, murderer, justine]chapter 8     [innocence, justine, elizabeth, judges, confes...chapter 10                    [thy, thou, ice, thee, mountains]

单字分析

我们可以通过查看对每个极性和主观性得分有贡献的单词来进一步分析。我们可以通过以下方式访问它:

blob.sentiment_assessments.assessments

例如,如果我们想在最后一章看到哪些单词是正面的,哪些是负面的(以及它们各自的权重),我们可以使用:

In [127]: assesments[-1].sort_values('polarity')Out[127]: polarity  subjectivityevil           -1.00           1.0
pathetic       -1.00           1.0
miserable      -1.00           1.0
horrible       -1.00           1.0
miserable      -1.00           1.0
...              ...           ...
incredible      0.90           0.9
consummate      0.95           1.0
exquisite       1.00           1.0
excellent       1.00           1.0
wonderful       1.00           1.0

名词短语

另一种分析情感的有用方法是使用名词短语。这些允许我们查看与正在讨论的相关的上下文。要访问这些,我们可以使用:

blob.noun_phrases

这里我们选择了一些短语,它们为角色和物体的互动设置了场景。例如,即使没有阅读最后一章,我们也能从下面有限的片段中得出结论。

WordList(['present situation', '**fury revenge**', '**hateful i**', 'life i', 'vast portion', '**barbarous countries**', 'alive i', '**fiendish enemy**', 'hours round', 'path i', 'william elizabeth', 'deep grief', 'i knelt', 'i kneel', '**eternal grief**', 'i swear', 'thee o night', 'mortal conflict', 'purpose i', 'dear revenge', 'green herbage', 
...
'**miserable frame**', '**vanish i**', 'cheeks light feeling', 'world affords', '**bitterest remorse**', 'death farewell i', 'behold farewell frankenstein', '**thou wert**', '**thou didst**', '**thou hadst**', '**thou wouldst**', 'thou wert', 'solemn enthusiasm i', '**extinct i**', 'funeral pile', 'ice raft'])

文本 2 情感:从文本中识别情感

这是我们要看的两个库的第二个。Text2Emotion 专注于表现消息中的潜在情绪,而不是简单地对其极性进行评分(如情绪分析)。这里,我们将剧本分为以下几组,而不是一个等级:

  • 幸福的
  • 愤怒的
  • 惊喜
  • 悲哀的
  • 害怕

装置

pip install text2emotion

使用

使用非常简单,只需导入库,然后使用get_emotion函数:

import text2emotion as teemotion = te.get_emotion(chapter)

将此应用于我们语料库中的每一章,允许我们比较文本中存在的情感类型:

堆积面积图显示每个章节中的不同情绪。来源:丹尼尔·埃利斯

堆积面积图让我们看到了小说所经历的周期性动荡。例如,在第 12-14 章中出现了明显的快乐高峰,怪物在森林里观察费利克斯,在故事的乞讨部分,维克多仍然对医学的奇迹和他的发现感兴趣。然而,这些时期紧接着是恐惧和悲伤的篇章(死亡和私刑)。利用这一点,我们能够将更多的信息归因于上面情感分析中提供的积极/消极。

结论

我们已经成功地执行了从情感到情感分析的一系列自然语言处理任务,并将其与由 TF-IDF 提取的章节内容和关键词进行了比较。

乍一看,似乎每个算法对小说的描述都多少反映了小说中的结构和事件。这表明对大量数据的自动分析在诸如新分类的任务中可以证明是非常宝贵的。

不管怎样,为什么不自己尝试一下,看看你的想法呢?

(显然我的博士论文充满恐惧 _(ツ)_/ )

额外资源

https://github.com/wolfiex/SentimentAnalysis-Frankenstein [## GitHub-wolf iex/sentiment analysis-Frankenstein:比较玛丽的情感过山车…

github.com](https://github.com/wolfiex/SentimentAnalysis-Frankenstein)

用 SHAP 值来解释你的机器学习模型是如何工作的

原文:https://towardsdatascience.com/using-shap-values-to-explain-how-your-machine-learning-model-works-732b3f40e137

学习使用工具来显示每个特征如何影响模型的每个预测

改编自 Unsplash 上的 Chad Kirchoff

机器学习模型通常是黑盒,这使得它们的解释很困难。为了理解影响模型输出的主要特征是什么,我们需要可解释的机器学习技术来揭示其中的一些方面。

其中一种技术是 SHAP 方法,用于解释每个特征如何影响模型,并允许对数据集和手头的问题进行局部和全局分析。

SHAP 价值观

SHAP 值(SHapleyAadditive exPlanations)是一种基于合作博弈论的方法,用于增加机器学习模型的透明度和可解释性。

例如,线性模型可以使用它们的系数作为每个特征的整体重要性的度量,但是它们随变量本身的比例而缩放,这可能会导致扭曲和误解。此外,该系数不能说明特征的局部重要性,以及它如何随着更低或更高的值而变化。基于树的模型的特征重要性也是如此,这就是为什么 SHAP 对于模型的可解释性是有用的。

重要提示:虽然 SHAP 显示了每个特征对模型预测的贡献或重要性,但它并不评估预测本身的质量。

考虑一个合作游戏,玩家数量与游戏特征的名称相同。对于每个例子或观察,SHAP 将披露每个参与者(或特征)对模型输出的贡献。

给定加州住房数据集[1,2](可从 scikit-learn 库获得),我们可以分离出一个单独的观察值,并计算该单独数据点的 SHAP 值:

shap.plots.waterfall(shap_values[x])

作者图片

在上面的瀑布中,x 轴表示目标(因变量)变量的值,即房价。x 是选择的观测值,f(x)是模型的预测值,给定输入 x,E[f(x)]是目标变量的期望值,或者换句话说,所有预测的平均值(mean(model.predict(X)))。

该观测中每个特征的 SHAP 值由条形的长度给出。在上面的示例中,经度的 SHAP 值为-0.48,纬度的 SHAP 值为+0.25,依此类推。所有 SHAP 值的总和将等于 E[f(x)]-f(x)。

绝对 SHAP 值向我们展示了单个要素对预测的影响程度,因此经度的影响最大,其次是中纬度,平均值占第三位,人口是对预测影响最小的要素。

注意,这些 SHAP 值仅对 有效,本次观察仅 。随着其他数据点的 SHAP 值 改变。为了理解特征对于整个数据集的重要性或贡献,可以使用另一个图,蜂群图:

shap.plots.beeswarm(shap_values)

作者图片

例如,纬度变量的高值对预测有很大的负面影响,而低值则有很大的正面影响。

MedInc 变量在其值较高时具有非常高的正贡献,而在低值时具有低的负贡献。无论其值是高还是低,特征总体对预测几乎没有贡献。

所有变量按照全局特征重要性的顺序显示,第一个最重要,最后一个最不重要。

实际上,SHAP 可以通过使用特征重要性向我们展示全局贡献,以及通过蜂群图的散布展示问题的每个实例的局部特征贡献。

在 Python 中使用 SHAP 值

我在 github 上提供了这一部分的代码。看看这个:

https://github.com/vinyluis/Articles/tree/main/Boruta SHAP

要在 Python 中使用 SHAP,我们需要安装 SHAP 模块:

pip install shap

然后,我们需要训练我们的模型。在示例中,我们可以直接从 sklearn 库中导入 California Housing 数据集,并训练任何模型,例如随机森林回归量

import shap
import pandas as pd
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor# California Housing Prices
dataset = fetch_california_housing(as_frame = True)
X = dataset['data']
y = dataset['target']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2)# Prepares a default instance of the random forest regressor
model = RandomForestRegressor()# Fits the model on the data
model.fit(X_train, y_train)

为了计算模型的 SHAP 值,我们需要创建一个 Explainer 对象,并使用它来评估样本或整个数据集:

# Fits the explainer
explainer = shap.Explainer(model.predict, X_test)
# Calculates the SHAP values - It takes some time
shap_values = explainer(X_test)

shap_values 变量将有三个属性:.values.base_values.data

.data属性只是输入数据的副本,.base_values是目标的期望值,或者所有训练数据的平均目标值,.values是每个例子的 SHAP 值。

如果我们只对 SHAP 值感兴趣,我们可以使用explainer.shap_values()方法:

# Evaluate SHAP values
shap_values = explainer.shap_values(X)

如果我们只是想要 SHAP 算法确定的特征重要性,我们需要取每个特征的平均值。

SHAP 图书馆的一些情节

也可以使用 SHAP 库来绘制瀑布图或蜂群图,或者部分依赖图。

为了分析这些特征的整体效果,我们可以使用下面的图。

柱状图

shap.plots.bar(shap_values)

作者图片

在这里,特征从对预测的影响最高到最低排序。它考虑了绝对 SHAP 值,因此要素对预测的影响是积极的还是消极的并不重要。

概要剧情:蜂群

shap.summary_plot(shap_values)
# or 
shap.plots.beeswarm(shap_values)

作者图片

在 beeswarm 上,特征也根据它们对预测的影响进行排序,但是我们也可以看到特征的较高和较低值将如何影响结果。

图上所有的小点代表一次观察。水平轴代表 SHAP 值,而点的颜色向我们显示该观察值与其他观察值相比是高还是低。

在本例中,较高的纬度和经度对预测有负面影响,而较低的值则有正面影响。

概要剧情:小提琴

查看蜂群信息的另一种方法是使用小提琴图:

shap.summary_plot(shap_values, plot_type='violin')

作者图片

为了分析局部的、实例式的影响,我们可以在单个观察值上使用下面的图(在下面的例子中,我使用了shap_values[0])。

本地条形图

shap.plots.bar(shap_values[0])

作者图片

该图向我们展示了影响单次观测预测的主要特征,以及每个特征的 SHAP 值的大小。

瀑布图

shap.plots.waterfall(shap_values[0])

作者图片

瀑布图具有相同的信息,但以不同的方式表示。这里我们可以看到所有 SHAP 值之和等于预测值 f(x)和期望值 E[f(x)]之差。

力图

shap.plots.force(shap_test[0])

作者图片

对于给定的观察,力图是查看每个特征对预测的影响的另一种方式。在该图中,正的 SHAP 值显示在左侧,负的显示在右侧,就像相互竞争一样。突出显示的值是该观察的预测值。

我希望这篇文章能帮助你更好地理解如何使用 SHAP 价值观来解释你的模型是如何工作的。这是每个数据科学家都应该拥有的工具,我们应该将它用于每个模型。

记得查看这篇文章的笔记本:

https://github.com/vinyluis/Articles/tree/main/Boruta SHAP

如果你喜欢这个帖子…

支持我一杯咖啡!

给我买杯咖啡!

看看这个很棒的帖子

参考资料:

[1] Pace,R. Kelley 和 Ronald Barry,《稀疏空间自回归,统计和概率快报》, 33(1997)291–297

[2]sci kit-学习开发者。真实世界数据集:加州住房数据集。最后一次访问是在 2022 年 1 月。(BSD 许可证)

在 Python 中使用 SHAP 和交叉验证

原文:https://towardsdatascience.com/using-shap-with-cross-validation-d24af548fadc

让人工智能不仅可解释,而且更强大

照片来自的迈克尔·泽兹奇Unsplash

简介

在许多情况下,机器学习模型比传统的线性模型更受青睐,因为它们具有卓越的预测性能和处理复杂非线性数据的能力。然而,对机器学习模型的一个常见批评是它们缺乏可解释性。例如,XGBoost 和 Random Forest 等集成方法将许多个体学习者的结果组合起来,以生成他们的结果。虽然这通常会带来更好的性能,但却很难知道数据集中每个要素对输出的贡献。

为了解决这个问题,可解释的人工智能(xAI)已经被提出,并且越来越受欢迎。xAI 领域旨在解释这种无法解释的模型(所谓的黑盒模型)如何做出预测,从而实现两个世界的最佳效果:预测准确性和可解释性。这样做的动机是,机器学习的许多现实应用不仅需要良好的预测性能,还需要解释结果是如何产生的。例如,在医疗领域,根据模型做出的决策,可能会失去或挽救生命,了解决策的驱动因素非常重要。此外,能够识别重要的变量可以为识别机制或治疗途径提供信息。

最流行和最有效的 xAI 技术之一是 SHAP。SHAP 的概念是由伦德伯格&李在 2017 年提出的,但实际上是建立在很久以前就存在的博弈论沙普利价值观的基础上。简而言之,SHAP 值的工作原理是通过查看具有和不具有该特征的许多模型中的预测(每个观测值)来计算每个特征的边际贡献,在每个缩减的特征集模型中对该贡献进行加权,然后对所有这些实例的加权贡献进行求和。那些希望得到更详细描述的人可以看到上面的链接,但是对于我们的目的来说,只需要说:观测的绝对 SHAP 值越大,对预测的影响就越大。因此,给定特征的所有观测值的绝对 SHAP 值的平均值越大,该特征就越重要。

使用 SHAP 库在 Python 中实现 SHAP 值很容易,网上已经有很多讲解如何实现的演练。然而,在我遇到的所有将 SHAP 值融入 Python 代码的指南中,我发现了两个主要缺点。

首先,大多数指南在基本训练/测试分割中使用 SHAP 值,但在交叉验证中不使用(见图 1)。使用交叉验证可以更好地了解结果的可归纳性,而简单的训练/测试分割的结果很容易根据数据的划分方式发生剧烈变化。正如我在最近一篇关于“营养学研究中的机器学习”的文章中解释的那样,交叉验证应该几乎总是优先于训练/测试分割,除非你正在处理的数据集非常大。

图 1:机器学习中不同的评估程序,摘自我的文章《营养学研究中的机器学习》(Kirk et al .,2022)。

另一个缺点是,我遇到的所有指南都没有使用多次重复的交叉验证来得出他们的 SHAP 值。虽然交叉验证对简单的训练/测试分割是一个很大的改进,但理想情况下,每次应该使用不同的数据分割重复多次。这对于较小的数据集尤其重要,因为根据数据的分割方式,结果可能会有很大的变化。这就是为什么经常提倡重复交叉验证 100 次,以便对你的结果有信心。

为了解决这些缺点,我决定自己编写一些代码来实现它。本演练将向您展示如何在公司的嵌套交叉验证方案中获得多次重复交叉验证的 SHAP 值。对于我们的模型数据集,我们将使用波士顿住房数据集,我们选择的算法将是强大但不可解释的随机森林

SHAP 值的实现

每当你像我们一样用不同的循环构建代码时,从最里面的循环开始向外工作通常是有意义的。通过尝试从外部开始,并按照代码运行的顺序构建代码,当出现问题时,更容易混淆,也更难排除故障。

因此,我们从 SHAP 价值观的基本实现开始。我假设您熟悉 SHAP 的一般用法以及实现它的代码,所以我不会花太多时间来解释。我自始至终都留下了评论(这总是很好的做法),这样你就可以检查那些评论,如果你仍然不确定,那么检查一下介绍中的链接或者库的文档。我也在使用时导入库,而不是一开始就一次导入,只是为了帮助直觉。

图 2: SHAP 在一次简单的训练/测试中

采用 SHAP 值进行交叉验证

我们最常看到的是通过使用sklearncross_val_score或类似的东西以自动化的方式实现交叉验证。这样做的问题是一切都发生在幕后,我们无法访问每个文件夹中的数据。当然,如果我们想获得所有数据点的 SHAP 值,我们需要访问每个数据点(回想一下,每个数据点在测试集中只使用一次,在训练中使用k-1次)。为了解决这个问题,我们可以结合使用KFold.split

通过用.split循环遍历我们的KFold对象,我们可以得到每个折叠的训练和测试指数。这里,fold是一个元组,其中fold[0]是训练索引,fold[1]是每个折叠的测试索引。

现在,我们可以利用这一点从我们自己的原始数据框架中选择训练和测试数据,从而也提取我们想要的信息。我们通过建立一个新的for循环来实现这一点,并获得每个折叠的训练和测试指数,然后像往常一样简单地执行我们的回归和 SHAP 过程。然后,我们只需要在循环之外添加一个空列表来跟踪每个样本的 SHAP 值,然后在循环的末尾添加这些值。我使用#-#-#来表示这些新增内容:

现在,我们有了每个样本的 SHAP 值,而不仅仅是数据的一个测试分割中的样本,我们可以使用SHAP库轻松地绘制这些值。我们首先只需更新X的索引,以匹配它们在每个折叠的每个测试集中出现的顺序,否则,颜色编码的特征值将全部错误。请注意,我们在summary_plot函数中对X进行了重新排序,这样我们就不会保存对原始X数据帧的更改。

图 3:交叉验证的 SHAP,包括所有的数据点

从图中我们可以看到,与我们只使用训练/测试分割相比,现在有了更多的数据点(事实上是所有数据点)。这已经改进了我们的过程,因为我们可以利用整个数据集,而不仅仅是一部分。

但是我们仍然不知道稳定性——也就是说,如果数据被不同地分割,结果会如何变化。幸运的是,通过下面的代码我们可以解决这个问题。

重复交叉验证

使用交叉验证大大提高了工作的稳定性,尤其是对于较小的数据集。然而,如果我们真的想做好数据科学,交叉验证应该在许多不同的数据分割上重复进行。

首先,我们现在不仅需要考虑每个折叠的 SHAP 值,还需要考虑每个重复的每个折叠的 SHAP 值,然后将它们组合起来绘制在一个图上。在 Python 中,字典是强大的工具,我们将使用它来跟踪每个折叠中每个样本的 SHAP 值。

首先,我们决定要进行多少次交叉验证重复,然后建立一个字典,存储每个重复的每个样本的 SHAP 值。这是通过遍历数据集中的所有样本并在空字典中为它们创建一个键,然后在每个样本中创建另一个键来表示交叉验证重复来实现的。

然后,我们在现有代码中添加一些新行,允许我们重复交叉验证过程CV_repeats 次次,并将每次重复的 SHAP 值添加到我们的字典中。这很容易通过更新代码末尾的一些行来实现,因此我们现在更新字典,而不是将每个样本的 SHAP 值列表追加到列表中。(注意:收集每次折叠的测试分数可能也是相关的,虽然我们在这里没有这样做,因为重点是使用 SHAP 值,这可以通过添加另一个字典来轻松更新,用CV repeats作为keys,用测试分数作为values)。

代码如下所示,#-#-#表示现有代码的升级:

为了形象化,假设我们要检查索引号为10的样本的第五次交叉验证重复,我们只需写:

其中第一个方括号表示样品编号,第二个表示重复编号。输出是第五次交叉验证重复后,编号为 10 的样本的X中每一列的 SHAP 值。

要查看一个个体的所有交叉验证重复的 SHAP 值,我们只需在第一个方括号中键入数字:

然而,这对于我们来说并没有太大的用处(除了故障排除之外)。我们真正需要的是一个将此形象化的情节。

我们首先需要将每个交叉验证重复的每个样本的 SHAP 值平均为一个值,用于绘图(如果您愿意,也可以使用中值或其他统计数据)。取平均值很方便,但可能会隐藏数据中的可变性,而这些可变性可能也是需要了解的。因此,当我们取平均值时,我们还会得到其他统计数据,如最小值、最大值和标准差:

上面的代码说明:对于原始数据框中的每个样本索引,从每个 SHAP 值列表中创建一个数据框(即每个交叉验证重复)。该数据框将每个交叉验证重复作为一行,将每个X变量作为一列。现在,我们通过使用适当的函数并使用axis = 1来执行列方式的计算,来获取每列的平均值、标准偏差、最小值和最大值。然后,我们将这些转换成数据帧

现在,我们简单地绘制平均值,就像绘制通常值一样。我们也不需要在这里对索引重新排序,因为我们从字典中获取 SHAP 值,它与X的顺序相同。

图 4:多次重复交叉验证后的平均 SHAP 值

由于我们的结果是多次重复交叉验证的平均值,因此它们比仅执行一次的简单训练/测试分割更加稳健和可信。然而,如果您比较前后的图,发现除了额外的数据点之外,没有太多变化,您可能会感到失望。但是不要忘记,我们使用的是一个模型数据集,它漂亮整洁,具有与结果密切相关的良好特性。在不太理想化的情况下,像重复交叉验证这样的技术将暴露现实世界数据在结果和特征重要性方面的不稳定性。

如果我们想进一步增强我们的结果(我们当然会这样做),我们可以添加一些图来了解我们提出的特征重要性的可变性。这是相关的,因为取每个样本的平均 SHAP 值可能会掩盖它们随着数据的不同分割而变化的程度。

为此,我们必须将我们的数据帧转换成长格式的数据帧,之后我们可以使用 T4 库创建一个数据帧

图 5:每个特征每次观察的最大和最小 SHAP 值的范围

在上面的catplot中,我们看到了每个样本在CV repeats的每次重复中的范围(最大值-最小值)。理想情况下,我们希望 Y 轴上的值尽可能小,因为这意味着更一致的特征重要性。

我们应该记住,这种可变性对绝对特征重要性也很敏感,也就是说,被认为更重要的特征自然会有范围更大的数据点。我们可以通过缩放数据来部分解释这一点。

图 6:和图 5 一样,但是现在每个观察值都是由每个特征的平均值来衡量的

请注意LSTATRM的不同之处,这是我们最重要的两个特征。现在,我们得到了一个更好的可变性反映,它是由特性的整体重要性来衡量的,根据我们的研究问题,它可能更相关,也可能不相关。

我们可以根据我们收集到的其他统计数据,比如标准差,想象出类似的图表。

嵌套交叉验证

所有这些都很棒,但是有一点还没做到:我们的随机森林处于默认模式。虽然它在使用默认参数的数据集上表现很好,但在其他情况下可能不是这样。除此之外,我们为什么不尝试最大化我们的结果呢?

我们应该小心不要陷入一个陷阱,这个陷阱似乎在这些天的机器学习示例中太常见了,即优化测试集中也存在的数据的模型超参数。通过简单的训练/测试分割,这很容易避免——只需优化训练数据的超参数。

但是一旦交叉验证进入等式,这个概念似乎就被遗忘了。事实上,人们经常使用交叉验证来优化超参数,然后使用交叉验证来对模型评分。在这种情况下,数据泄漏已经发生,我们的结果将(即使只是轻微地)过于乐观。

嵌套交叉验证是我们的解决方案。它包括从我们的正常交叉验证方案(这里称为“外环”)中取出每个训练折叠,并通过对每个折叠的训练数据使用另一个交叉验证(称为“内环”)来优化超参数。这意味着我们在训练数据上优化超参数,然后仍然可以获得关于优化的模型在看不见的数据上表现如何的较少偏差的想法。

这个概念可能有点难以理解,但对于那些希望了解更多细节的人,我解释一下,我的文章链接在上面。无论如何,代码并不困难,通读它可能有助于理解。事实上,我们已经在上面的过程中准备了很多代码,只需要做一些小的调整。让我们看看它的实际效果。

嵌套交叉验证的主要考虑——特别是我们使用的许多重复——是它需要大量的时间来运行。出于这个原因,我们将保持我们的参数空间很小,并使用随机搜索而不是网格搜索(尽管随机搜索通常在大多数情况下足够执行)。如果您确实想要更彻底,您可能需要在 HPC 上预留一些时间。无论如何,在我们最初的 for 循环之外,我们将建立参数空间:

然后,我们对原始代码进行以下更改:

  • CV现在将变成cv_outer,因为我们现在有两个交叉验证,我们需要适当地引用每一个
  • 在我们的for循环中,我们遍历训练和测试 id,我们添加了内部交叉验证方案cv_inner
  • 然后,我们使用RandomizedSearchCVinner_cv优化我们的模型,选择我们的最佳模型,然后使用最佳模型从测试数据(这里的测试数据是外折测试)中导出 SHAP 值。

仅此而已。出于演示的目的,我们将CV_repeats减少到 2,否则我们可能会在这里停留一段时间。在实际情况下,您可能希望保持足够高的值,以保持稳健的结果和最佳的参数,对此您可能没有 HPC(或很大的耐心)。

查看下面的代码了解这些变化,再次用#-#-#表示新添加的内容。

结论

解释复杂人工智能模型的能力变得越来越重要。SHAP 值是一种很好的方法,但是,单个训练/测试分割的结果并不总是可信的,尤其是在较小的数据集中。通过多次重复诸如(嵌套)交叉验证之类的过程,您可以提高结果的稳健性,并且可以更好地衡量如果基础数据也发生了变化,您的结果会如何变化。

注:所有未经编辑的图片均为本人所有。

利用模拟研究推动建模决策

原文:https://towardsdatascience.com/using-simulation-studies-to-motivate-modelling-decisions-be8bae2cd1c2

模拟研究是计算机实验,涉及通过伪随机采样创建数据。使用模拟研究可以帮助您在选择统计模型时做出更好的决策。

马库斯·斯皮斯克在 Unsplash 上的照片

数据科学家和分析师不断决定使用哪些统计模型。无论是预测性的还是因果性的,选择比比皆是。这篇文章是关于使用模拟研究来激励建模决策,特别是当重点是点估计时,例如计算治疗效果。

当在预测模型中进行选择时,人们通常可以使用测试数据上的性能度量来激励最佳模型选择(例如,在测试集上偏好具有更高精度比率的模型)。预测模型中的性能指标通常使用观察到的数据进行计算(例如,测试集中定义为真阳性预测阳性的精确率)。

估计需求未知的因果设置中,这样的程序是不可行的。例如,假设我们的任务是评估一种新药在临床试验中的效果。

因为我们不知道真实的效果,我们不能对模型如何根据真实情况估计治疗效果进行基准测试,这与预测模型不同,在预测模型中,我们知道真实的标签,并可以根据它们评估我们的模型预测的性能。

这就是模拟研究发挥作用的地方。
模拟研究是计算机实验,涉及通过伪随机采样创建数据,对于评估新方法的性能或比较替代方法的表现至关重要。因此,模拟研究为统计方法在各种情况下的表现提供了经验证据。这里有几个简单的例子:

  • 我应该使用 t 检验还是非参数检验?
  • 在给定的环境下,使用线性回归和广义线性模型会有什么影响?
  • 运行统计测试时,我没有完全满足模型假设。我还能用吗?我的估计会有多大偏差?

这种决定通常是基于文献知识或过去的经验做出的,但是决定使用哪种模型会对一个人试图做出的推断产生相当大的影响。例如,第一类第二类误差很大程度上取决于使用的方法,和/或满足他们的假设。

在本文中,我将(I)介绍模拟研究的使用,并讨论如何基于莫里斯等人(2019)的框架设计和评估其结果。然后,我将(II)提供一个基于我个人经验的评估新方法的具体应用,和(III)讨论模拟研究的一些限制和陷阱。

模拟研究:什么是模拟研究,如何设计模拟研究,以及如何评估结果

模拟研究可用于许多场合。最常见的用途是:

  • 评估新的或现有的统计方法
  • 比较两种或多种统计方法
  • 设计研究时计算样本量或统计功效
  • 评估有限样本中大样本理论近似的相关性
  • 数学推导统计方法时的双重检查代数

在这篇文章中,我将关注使用模拟研究评估方法的。我将描述 ADEMP 结构,这是 Morris 等人 2019 年定义的模拟研究的系统框架。

ADEMP 框架:目标、数据生成、估算、方法、绩效衡量(Morris 等人,2019 年)

目的

在评估方法的性能时,我们通常对统计偏差、方差估计和/或对错误设定的稳健性感兴趣。在其他环境中,人们可能对寻找评估方法失败的场景感兴趣(例如,当一种方法正确时,其他方法失败的程度如何?或者,当所有都不正确时,哪个是最稳健的?).

为了说明这一点,让我们假设我们有一些计数数据,并且我们正在尝试估计一个治疗效果。我们可以选择:
-使用简单的线性回归(忽略结果数据的分布性质),使用泊松回归(假设乘法效应和相等的均值和方差),或者使用准泊松回归(进一步假设方差是均值的线性函数)

哪一种表现最好取决于许多因素,包括数据分布的性质(偏斜度、零膨胀的存在、稀疏度等。).因此,我们可能希望通过模拟来了解这些模型的行为。但是一般来说,当文献中缺乏指导,或者当方法新颖时,模拟研究是令人感兴趣的。有大量关于回归及其假设的文献,因此专家可能对上述模拟不感兴趣。

数据生成机制

数据生成机制是关于如何使用随机数来生成用于模拟的数据集。这是分析师在设计模拟研究时通常花费大部分时间的地方。可以使用已知分布中的参数绘制来生成数据,或者通过替换现有数据集进行采样来生成数据。通常会模拟一些场景,例如使用不同的样本大小和/或效果大小。

预计

大多数模拟研究比较估计人口数量的方法,人口数量通常是数据生成过程中的模拟参数。例如,估计需求可以是线性回归的参数β。其他模拟研究关注的是评估检验零假设或研究设计的方法。在给定的设置(“目标”)下,一些绩效指标比其他指标更合适,详见下表(表 1 )。

表 1:模拟研究的可能目标和相关性能测量(Morris 等人,2019 年)

方法

在比较两种方法时,Morris 等人认为,利用该领域先前工作的知识,了解哪些方法是竞争者,哪些不是,这一点很重要。根据作者的观点,如果一些方法已经被证明是有缺陷的,它们应该被排除在模拟之外,因为这只会增加计算负担。

绩效评估

性能度量是用于评估方法性能的数量。如前所述,它们取决于研究的目的和目标。当目标是一个估计量时,统计偏差通常是感兴趣的量(真实参数 theta 与模型估计参数theta hat之差),以及精度置信区间。当目标是零假设检验时,功效I 型误差一般是主要关注点。
重要的是,模拟研究的一个基本方面是,因为它们本质上是经验性的,性能测量本身会有误差,这意味着分析师应该给出不确定性的估计值(蒙特卡罗标准误差),并小心选择重复的次数。下面的表 2 提供了常用性能指标的定义、估计值和蒙特卡罗 SE 估计值。

表 2:业绩计量:定义、估计和蒙特卡罗标准误差(Morris 等人,2019 年)

II-模拟研究的示例

对于这个特殊的模拟,我感兴趣的是评估在各种混杂和选择偏差情况下治疗效果评估模型的性能。我使用医疗索赔数据(结果通常是零膨胀和偏斜的)并使用治疗效果估计方法模拟治疗效果。我的目标是恢复模拟治疗效果。

我将使用 ADEMP 框架分解模拟研究步骤:

我的模拟研究的目的(A)

混杂、选择偏倚、小效应量和/或样本量使得测量治疗效果具有挑战性。这个模拟的想法是评估“因果推理机器学习”模型的性能,并将它们与更标准的倾向得分匹配和逆概率加权方法进行比较。

换句话说,的目的(见上文,ADEMP 中的 A)是评估统计偏差(和方差),这样我就可以知道倾向得分匹配、逆概率加权或 grf 因果森林算法(Athey et al .al,2019)很可能在我的数据集中表现最好。

我的模拟研究中的数据生成机制(D)

生成数据(y,X),使用有替换或无替换的采样 在我的案例中,我从一个大型索赔数据集中进行了无替换采样。
y 表示住院人数
X 表示协变量矩阵,包括人口统计和健康风险信息。

使用倾向得分函数 p(T=1|X) 生成治疗分配机制在我的情况下,该函数基于正态分布的 c.d.f,其映射到[0;1].
T =1 表示观察值在治疗组中
X 表示协变量矩阵,包括人口统计学和健康风险信息

到目前为止,我已经创建了一个虚拟的治疗组和对照组,它们的基线特征不同,因为治疗分配机制依赖于一个依赖于协变量 X 的函数。

产生虚构的治疗效果。
我模拟研究的最后一步是生成一个虚构的治疗效果。我使用负二项分布生成了治疗组较少的住院天数,该分布与住院数据吻合良好。

我的模拟研究中使用的方法(M)

目的是比较这三种方法的性能。

方法 1: 倾向评分匹配模型,以平衡治疗组和对照组之间的协变量,然后进行回归分析,以测量治疗组和对照组之间的 y =“住院人数”的差异。
方法 2: 一种倾向评分加权模型,用于平衡治疗组和对照组之间的协变量,使用具有重叠加权的回归来测量治疗组和对照组之间的 y =“住院人数”的差异。
方法三:Athey 等人的因果森林算法(R 包 grf )。al (2019)基于随机森林算法对异质处理效果估计的推广。

我的模拟研究中的绩效评估(P)

对于性能评估,我比较了偏倚、标准误差、MSE、覆盖率、统计功效和 1 型错误率。

结果

我发现,在不同的样本量和效应量下,倾向得分匹配的表现更差。GRF 算法提供了最好的性能,其次是重叠加权回归。

模拟研究的局限性和陷阱

情境化模拟结果

模拟研究在模拟框架内有效,不能推广到不同的数据或人群。他们的结论也没有延伸到不同的场景或背景。例如,上述模拟假设治疗效果遵循负二项式分布。如果这是对事实的严重偏离,我的模拟就会有偏差。

不确定性的报告措施

当基于不同的随机数时,蒙特卡罗程序会给出不同的结果,并且其构造会受到不确定性的影响。在报告模拟结果时,不确定性的度量应该是不可避免的。

如何更好地进行模拟学习

莫里斯等人对检查数据估计值提出了一些建议。
对于针对估计需求的模拟研究,他们鼓励分析师绘制“theta hat”(经验估计量)及其标准误差的分布图,并特别关注异常值。

需要适当选择模拟数量“N _ sim”,因为它会影响蒙特卡洛误差,而蒙特卡洛误差会影响关键性能指标。这种选择通常是精度和计算时间之间的权衡。

呈现和可视化结果是模拟研究的一个非常重要的部分。结果通常以表格和图表的形式呈现。除了估计值及其标准误差的二元图之外,作者建议使用 zip 图表示置信区间,棒棒糖图表示性能测量。要了解更多的技术细节,我建议看看下面参考资料中提到的论文。

资料来源: 利用模拟研究评估统计方法,Tim P. Morris 等人,统计学在医学中,2018

在 XGBoost 中使用稀疏矩阵

原文:https://towardsdatascience.com/using-sparse-matrices-in-xgboost-2c2112f362f8

处理高基数的另一种方法

空白的空间是一个机会。美国宇航局在 Unsplash 拍摄的照片

在听说 XGBoost 算法之前,你不必很早就参与数据科学,包括它已经用于的所有 Kaggle 比赛,并取得了巨大的成功。关于如何开始使用这种算法,网上也不乏很棒的教程(包括《走向数据科学》)。然而,XGBoost 有一个令人惊讶的特性经常被忽略,并且在大多数教程中都很遗憾地缺失了:XGBoost 接受稀疏矩阵作为输入的能力。如果您不熟悉这种数据结构,为什么它如此有用,或者如何在 XGBoost 中使用它,那么您来对地方了!使用稀疏矩阵一开始可能会令人生畏,但在本文的结尾,我将向您展示它是多么简单。我相信您很快就会在自己的数据科学项目中使用它们,尤其是如果您有高基数的数据集。

什么是稀疏矩阵?

一个稀疏矩阵是一种数据结构,在存储表格信息时非常有效,其中许多列包含空值(您将看到其他定义说稀疏矩阵主要由 0 组成,这是通常的定义,但在本文中,我在技术上创建的矩阵具有空值或缺失值)。乍一看,这似乎很奇怪。毕竟,为什么首先要使用包含大量空值的数据集呢?这都归结到高基数分类变量,以及在使用 XGBoost 之前我们必须如何转换它们。

基数是一个 $10 单词,它描述了一个分类(非数字)数据特征,该特征有许多可能的值。假设您有关于人们居住地的数据,包括他们的邮政编码。尽管在美国邮政编码包含 5 位数字,但这实际上是一个分类数据特征(例如,73301 并不是因为邮政编码本身的值而“小于”73302)。有成千上万个不同的邮政编码,所以这确实是一个高基数数据特性。让 XGBoost(只接受数字特性)读取这个特性的一个策略是一次性编码它。在这里,您可以为每个可能的邮政编码创建一个指示器变量(例如,一个名为 lives_in_73301 的列,另一个名为 lives_in_73302 的列,等等)。),并用 1 或 0 填充这些列,其中 1 表示一个人居住在该邮政编码。您很快就会意识到,这会导致数据结构中主要填充 0。对其他几个高基数的列重复这个练习,现在您应该意识到您的数据很快就会变得非常稀疏。

但是,如果您试图将这种稀疏数据存储在 pandas 数据框中(或者任何其他类型的密集表格结构),您可能会很快遇到内存问题。如此之快,它会让你想要重新开始,并试图找出一种方法来减少列的基数。但是不要放弃!可能有非常有价值的信息嵌入在那些您为了减少基数和内存使用而丢弃的列中。不要误会,熊猫数据框很棒,我在自己的工作中也经常使用它们。然而,它们不擅长存储稀疏数据,尤其是当所讨论的数据集相当大或者有很多特征时。

进入稀疏矩阵数据结构。具体来说,CSR 矩阵,代表“压缩稀疏行”(还有其他方法对矩阵进行稀疏编码,但下面的管道使用 CSR 结构)。这种数据结构利用了矩阵的大部分将具有缺失信息的事实。它的工作原理是只存储非空元素及其位置。当矩阵中的大部分信息丢失时,CSR 矩阵结构将使用比其密集对应物少得多的存储器,其中也存储丢失的值及其位置。

应该注意的是,使用稀疏矩阵也有缺点,主要的一个缺点是它们不像其他数据结构那样容易使用,例如用于探索性数据分析的 pandas 数据框。它们的主要目的是高效地存储稀疏数据。因此,我的建议是依靠其他工具来探索和理解您的数据集。当您准备好构建模型管道时,如果合适的话,可以过渡到使用稀疏矩阵。

高基数数据集的用例

不是这种红衣主教。帕特里斯·布沙尔Unsplash

在本练习中,我们将使用“糖尿病 130 家美国医院 1999-2008 年数据集”,我们将建立一个模型来预测 30 天内的再入院。您也可以自己下载这个数据集,并执行下面的 Python 代码(如果您还没有安装必要的包,那么在安装完必要的包之后)。这个数据集中的许多字段都有潜在的用处,但是我们在这里主要关注一些高基数的字段。具体来说,我们将包括:

  • encounter_id (为每次入院唯一标识)
  • 重新接纳(用于开发目标变量)
  • 性别(诚然基数低,但性别往往是很多医疗状况的优秀预测指标,所以我们会保留)。
  • 年龄(患者的 10 岁年龄段,基数也很低,但与性别一样,年龄通常是医疗状况的良好预测因素)。
  • diag_1 (初步诊断)。
  • 诊断 _2 (二级诊断,如适用)。
  • 诊断 _3 (三级诊断,如适用)

毫无疑问,最高的基数特性将是诊断数据。即使你不熟悉诊断代码,只要想一想医疗专业人员在访问期间可以诊断你的所有方法,你很快就会意识到这个领域有很多可能性。由于诊断代码有三个字段,我们将有三个必须处理的高基数特性。

尽管如此,本文中使用的数据集并不是特别大,您可能可以使用可靠的旧 pandas 数据框架和您的常用方法将所有这些数据放入内存中。然而,这不是本教程的重点。我有意使用一个较小的、易于处理的数据集,以便以后当您处理一个包含大量高基数列、需要很长时间才能运行的非常大的数据集时,您可以轻松地使用稀疏矩阵数据结构。稍后,当您的其他同事正在努力使他们的模型在内存限制内工作,或者为了减少内存开销而减少有价值的特性时,如果您使用这种数据结构,将会一帆风顺,获得其他模型所缺少的洞察力。

现在,让我们导入数据,只保留我们在本练习中需要的列。

import pandas as pd, xgboost as xgb
from scipy.sparse import csr_matrix
from pandas.api.types import CategoricalDtype
from sklearn.model_selection import train_test_split
from xgboost.sklearn import XGBClassifier
import shapdf = pd.read_csv('diabetic_data.csv', low_memory = False)
ex_df = df[[
    'encounter_id'
    ,'readmitted'
    ,'gender'
    ,'age'
    ,'diag_1'
    ,'diag_2'
    ,'diag_3'
]]
ex_df.readmitted.unique()

研究重新接纳的列,我们看到有三个可能的值: NO30< 30 ,分别表示没有重新接纳、30 天后重新接纳和 30 天前重新接纳(我不清楚这个数据集如何处理恰好在 30 天发生重新接纳的情况,但是我们将忽略这个场景并继续讨论)。我们将创建一个名为 _admit_lt_30days 的新列,这是一个指示变量,如果患者在出院后 30 天内入院,则填充 1,否则填充 0。

ex_df = ex_df.assign(_admit_lt_30days = 0)
ex_df.loc[ex_df.readmitted == '<30','_admit_lt_30days'] = 1
ex_df = ex_df.drop('readmitted', axis = 1)

变量名 _admit_lt_30days 前面为什么加下划线?我们很快就会实现的,我保证!

将数据转换为高格式

很快,您将创建比这更高的数据集。杰夫·图马勒Unsplash

接下来,我们将组织我们的数据,这似乎是朝着错误的方向迈出了一步。我们将以高格式重新构建我们的数据,其中数据由三列表示:

  • 遭遇 id (遭遇或入场 id)。
  • 变量(正在存储的变量的名称)。
  • (所讨论的变量的值)。

例如,对于性别列,我们将创建两个变量, gender_fgender_m 。如果入院时患者是女性或男性,这些列的值将分别为 1。

这似乎与你所了解的基于结构化数据的机器学习算法背道而驰。毕竟,不是应该为每个观察值保留一行吗?使用高格式的数据,我们将为每次遭遇提供五个变量。然而,这只是一个中间步骤,有助于我们稍后生成一个宽而稀疏的矩阵,其中每一个准入确实将由一个单独的行来表示。

目前,我们利用 pandas 内置的 melt 函数,以这种高格式构建数据。

numeric_df = ex_df[[
    'encounter_id', '_admit_lt_30days'
]].melt(id_vars = 'encounter_id')text_df = ex_df[[
    'encounter_id', 'gender', 'age', 'diag_1', 'diag_2', 'diag_3'
]].melt(id_vars = 'encounter_id')text_df.variable = text_df.variable + '_' + text_df.value
#Remove special characters from variable names
text_df.variable = \
    text_df.variable.str.replace('\[|\)', '', regex = True)
text_df = text_df.assign(value = 1)tall_df = numeric_df.append(text_df)

将数据转换为宽而稀疏的矩阵

通过首先创建一个高的数据框,我们可以更容易地在以后构建一个宽而稀疏的矩阵。相反,如果我们试图在 pandas 中创建一个宽的数据集,然后将其转换为稀疏矩阵,pandas 中宽而密的数据集将会耗尽大量内存,这就违背了稀疏矩阵数据结构的初衷。

为了将高数据转换成宽而稀疏的数据,我们将对高数据集应用一个非常类似于 pandas 中的 枢轴函数的转换,尽管我们将把结果转换成 CSR 矩阵数据结构。变量列中的每个条目将成为它自己单独的一列。CSR 矩阵中每个元素的值将是列和遭遇 id 行中的数字。数据集中的最后一列是encount _ id,每个 ID 占一行。注意,对于encount _ id变量的一些组合,在新的稀疏矩阵内将没有对应的。对于涉及诊断代码的列尤其如此。在这些情况下,CSR 矩阵中的值本质上是一个空值,尽管实际上 CSR 矩阵中根本没有存储任何东西。在这个转换之后,每个观察都将有一个单独的行,并且数据将为 XGBoost 算法做好准备。

encounter_c = \
    CategoricalDtype(sorted(tall_df.encounter_id.unique()), ordered=True)
var_c = \
    CategoricalDtype(sorted(tall_df.variable.unique()), ordered=True)
row = \
    tall_df.encounter_id.astype(encounter_c).cat.codes
col = \
    tall_df.variable.astype(var_c).cat.codes
sparse_matrix = \
    csr_matrix(
        ( tall_df["value"], (row, col) )
        , shape = ( encounter_c.categories.size, var_c.categories.size )
    )#Everything after the first column is a feature
X = sparse_matrix[:,1:]
#The first column is the target variable
Y = pd.DataFrame(sparse_matrix[:,0].astype(int).todense())X_train, X_test, Y_train, Y_test = \
    train_test_split(X,Y, test_size=0.2, random_state=888)

注意,当创建 var_c 时,我们在应用 CategoricalDtype 函数之前对列进行排序。这具有按字母顺序排列所有变量名的效果。回想一下,我们在目标变量前面放了一个下划线,下划线排在任何小写字母之前。因此,稀疏矩阵的第一列包含我们的目标变量,矩阵的其余部分包含我们的特征。这就是为什么我们在目标变量前面加了一个下划线,这样当我们在一个稀疏矩阵(即 CSR 矩阵的第一列)中转换数据时,即使我们后来在模型中添加或删除了其他特性,我们也能立即知道在哪里寻找它。

构建 XGBoost 模型

我们现在已经准备好构建 XGBoost 模型了。我们将跳过超参数调优,直接进入模型构建,因为重点只是开始使用 XGBoost 中的稀疏矩阵。

xgb_model = \
    xgb.XGBClassifier(
        objective='binary:logistic'
        ,booster='gbtree'
        ,tree_method='auto'
        ,eval_metric='logloss'
        ,n_jobs=4
        ,max_delta_step=0
        ,random_state=888
        ,verbosity=1
    )
xgb_model.fit(X_train, Y_train)

我们也可以通过增益得到特征重要性。我们看到,对于某些类型的诊断,增益相当高。也就是说,当预测再入院的概率时,这些特征被认为是非常重要的。

#Get the feature importance by gain and sort
#This has "dummy" names like f0, f1, etc. that have little meaning
#However, we will update them with meaningful names later.d2 = xgb_model.get_booster().get_score(importance_type='gain')
feature_by_gain_noname = \
    pd.DataFrame(
        data=list(d2.items())
        ,columns=['feature','importance_by_gain']
    ).sort_values('importance_by_gain', ascending = False)#Get the index values of the featuresto_index = \
    feature_by_gain_noname['feature'].str.slice(1,).astype(int).tolist()#Create a data frame with the feature names and sortnames_in_order = var_c.categories[1:][to_index].to_frame()
names_in_order.columns = ['feature']
names_in_order.index = range(len(names_in_order.index))#Create a data frame that does not have the dummy name columnby_gain = feature_by_gain_noname['importance_by_gain'].to_frame()
by_gain.columns = ['importance_by_gain']
by_gain.index = range(len(by_gain.index))#Join the data frame with names to the gain valuesfeature_by_gain = names_in_order.join(by_gain)feature_by_gain.head(10)

按增益排列的前 10 个变量

从使用 SHAP 值的模型中获得的见解

虽然增益是一个有价值的指标,但它不能告诉我们该变量是正预测值还是负预测值。例如,初步诊断 V58 会增加还是减少再次入院的可能性?仅仅使用增益度量是不可能的。

通过使用 SHAP 软件包,我们可以看看哪些变量对预测 30 天再入院最有影响。缺点是 SHAP 不能将稀疏矩阵作为输入(还记得我说过熊猫数据框通常更容易处理吗?).作为一种变通方法,我们可以通过只包含那些使用稀疏矩阵实现选择的特征来减少原始数据集中的维数。对于更大的数据集,可以考虑的另一个选择是将 SHAP 分析限制在数据的一个子集。

假设我们想要计算维持数据的 SHAP 值。我们可以通过限制我们的高数据集来做到这一点,只包括在训练数据集上拟合我们的模型时保留的那些列。然后,我们将新数据转换为密集的 pandas 数据帧(请注意,我们通过删除 XGBoost 在第一轮模型创建期间没有选择的不必要的列来减少内存开销),使用旧的训练数据重新调整模型,最后计算维持数据的 SHAP 值。

filter_df = \
    tall_df.loc[
        tall_df.variable.isin(feature_by_gain.feature)|(tall_df.variable == '_admit_lt_30days'),
    ]
filter_df_pivot = \
    filter_df.pivot_table(
        index='encounter_id'
        ,columns='variable'
        ,values='value'
    ).rename_axis(None, axis=1)df = filter_df_pivot.reset_index()
df = df.drop(columns='encounter_id')
feature_final = \
    list(df.columns[~df.columns.isin(['_admit_lt_30days'])])X = df.loc[:,feature_final]
Y = df.loc[:,'_admit_lt_30days']X_train, X_test, Y_train, Y_test = \
    train_test_split(X,Y, test_size=0.1, random_state=652)model = xgb_model.fit(X_train, Y_train)explainer = shap.Explainer(model, X_test)
shap_values = explainer(X_test)

一个非常自然和常见的问题是解释为什么模型以一种特定的方式给某些观察评分。例如,让我们看看根据模型,有超过 50%的再入院概率的患者。我们可能想知道为什么这个模型赋予这些入院者如此高的再入院概率。

score_pred_score = xgb_model.predict_proba(X_test)
score_pred_score_df = \
    pd.DataFrame(score_pred_score, columns=['proba_0', 'proba_1'])
score_pred_score_df.loc[score_pred_score_df.proba_1 > .5,]

再入院概率> 50%的患者

根据我们的模型,再入院概率最高的观察指标值为 8782,再入院的几率约为 65%。我们可以看看 SHAP 瀑布图,了解一下为什么录取分数是这样的。

解释高再入院概率计算的 SHAP 瀑布图

根据上面的 SHAP 图,由于存在以下三种情况,这种特殊的观察结果有很高的再入院概率:

  • v 58的诊断(遇到其他未指明的程序和善后处理)。
  • 150的诊断(消化器官和腹膜的恶性肿瘤)。
  • 427(心律失常)的诊断。

由于使用了稀疏矩阵数据结构,我们能够获得一些有价值的洞察力,否则这将是非常困难的。我们现在可以看到哪些诊断在特定入院中驱动更高或更低的再入院概率,如果我们减少诊断数据的基数,这些信息将会丢失。该模型可以进一步分析,以帮助确定哪些诊断倾向于表明再入院的高概率,它甚至可以用作开发预测模型的框架,以在不干预的情况下识别有再入院风险的患者。有了这些信息,医院或健康计划可以制定一个计划来帮助预防患有某些诊断的糖尿病患者再次入院。

进一步的步骤和其他提示

轮到你了,你赢了!Ashkan ForouzanionUnsplash

为了本教程的缘故,我故意保持这个例子简单。通过使用这个管道或对它的一些修改,您将能够以一种独特的方式使用高基数字段,这种方式在不丢失信息的情况下减少了内存开销。但这仅仅触及了这项技术威力的表面。

假设您想在这个模型中包含医疗专业特性,看看这是否有助于预测再入院。幸运的是,这很容易做到!只需转到将数据转换为高格式的部分,使用现有代码作为模板,将医疗专业添加到包含的变量列表中。这个新项目将流经模型的其余部分,一直到在 XGBoost 中拟合模型并创建 SHAP 输出。假设您想要基于诊断代码的前三个字符创建一个变量(它将相似的诊断分组在一起,但是请注意,所使用的样本数据集可能已经为非糖尿病诊断完成了这一任务)。同样,非常简单!您只需要转到将数据转换为高格式的部分,并根据诊断代码的前三个字符创建一个列。一般来说,在这个管道中添加新的特性或者从管道中删除不必要的特性是很简单的。您只需要确定一个观察索引(本文中的 encounter_id )、一个变量名和一个要输入的值。然后,将这些新信息添加到现有的高数据框中,剩下的信息就从这里开始了。

最后一点,回想一下我们对这个例子的诊断信息进行了一次性编码。但是,除了 1 之外,您可以随意将其他值放入列。比方说,我们知道患者在过去一年中接受诊断的次数,而不是该数据集中可供我们使用的有限信息。如果患者在过去的一年中被诊断为诊断代码 250.8 十次,则将被填充为 10 而不是 1。你可以在这里包括你认为可能具有预测性的各种数值,从花费的美元,到诊断后的天数,到就诊次数,等等。通过这样做,您可以将有价值的信息嵌入到您的稀疏矩阵中,然后 XGBoost 可以使用这些信息来构建更好的模型(例如,可能频繁接受某项诊断的患者更有可能再次入院,并且模型可能会发现这种模式,等等)。).

我希望这对您有所帮助,并祝您在将稀疏矩阵融入您自己的模型时好运!

使用 SQL 根据历史状态计算趋势

原文:https://towardsdatascience.com/using-sql-to-calculate-trends-based-on-historical-status-2ee75f23c4e4

我们怎样才能捕捉到存在但没有活动时间戳的事物的趋势呢?

说到捕获时间戳,我处理过的大多数数据集都非常健壮。无论是销售、产品还是人才获取数据,API 和数据仓库工具都需要能够为应用程序上发生的特定操作或事务提供时间戳。

大多数 web 应用程序会为其网站上发生的事件生成一个时间戳,以及事件内容的记录。图片由 Malvestida 通过 Unsplash 提供。

然而,我一直纠结的一个分析是基于某个时间点的某件事的状态的时间序列趋势。例如,假设我正在与人才获取团队合作,我们正在尝试了解 2022 年我们的职业页面上有多少工作岗位,按月分列。一个标准的数据集可能包含帖子的创建日期、帖子的发布日期以及帖子被删除的日期(基于操作的时间戳)。然而,可能不会有内置的时间戳显示“1 月 15 日,发布是实时的。1 月 16 日、17 日和 18 日也有直播,还有……”

如果我想找到 2022 年 1 月 1 日至 2022 年 1 月 31 日期间在我的工作网站上发布的所有帖子(但可能在这些日期或之前发布),我过去使用的一种解决方法是:

if posting_publish_date ≤ 2022–01–01 and (posting_closed_date is null or posting_closed_date > 2022–01–31) then True

这将帮助我创建一个标志,以过滤在兴趣期当天或之前发布、在兴趣期之后关闭或尚未关闭的帖子(因此,在 2022 年 1 月 1 日至 1 月 31 日之间有效)。但是,如果我想做一个时间序列分析,需要一个月的细分,而不是一个“时间点”的数字呢?

对于那些不熟悉人才获取数据的人来说,另一个例子是,如果我想按月跟踪销售周期中处于潜在客户创造阶段的机会数量。

我也许能够捕捉到他们何时进入潜在客户创造阶段以及何时退出,但如果我只是想知道每个月潜在客户创造的机会数量,而不管他们何时进入该阶段,该怎么办?基于传统数据集,这些数据集仅在有行动时(即 opp 进入或退出阶段)捕获时间戳,我只能显示进入或退出阶段的机会数量,而不能显示刚刚进入阶段的机会数量。

如果这些例子中的任何一个引起了你的共鸣,就你想尝试的或者过去已经尝试过的分析而言,那么这个教程就是为你准备的!

步骤 1:为日历日期生成一列

该列是您的线图的隐喻 x 轴。您希望创建一个列来存储一年中的每个日期,即使当天没有任何活动。我通常会生成比一年更长的日期,所以数据集在未来几年仍然有效。

一些 SQL 风格有一个 generate_series 函数,它将为您创建这个日历列。如果你像我一样不幸,并且你正在使用的 SQL 风格还不支持 generate_series(像亚马逊雅典娜🥺),你可以使用来自 Looker 的这段代码,它对我有效(我在亚马逊雅典娜上为 PostgreSQL 稍微修改了一下):

SELECT parse_datetime('2020–01–01 08:00:00', 'yyyy-MM-dd H:m:s') + (interval '1' day * d) as cal_date from 
FROM ( SELECT
ROW_NUMBER() OVER () -1 as d
FROM
(SELECT 0 as n UNION SELECT 1) p0,
(SELECT 0 as n UNION SELECT 1) p1,
(SELECT 0 as n UNION SELECT 1) p2,
(SELECT 0 as n UNION SELECT 1) p3,
(SELECT 0 as n UNION SELECT 1) p4,
(SELECT 0 as n UNION SELECT 1) p5,
(SELECT 0 as n UNION SELECT 1) p6,
(SELECT 0 as n UNION SELECT 1) p7,
(SELECT 0 as n UNION SELECT 1) p8,
(SELECT 0 as n UNION SELECT 1) p9,
(SELECT 0 as n UNION SELECT 1) p10
)

您可以将“2020–01–01”替换为您计划开始历史分析的日期。

步骤 2:在日历列和感兴趣的表之间进行左连接

现在您已经有了 calendar 列,您需要通过 left join 添加活动表。为什么离开加入?如果我们继续上面的销售周期示例,可能会有没有阶段移动的日期(即周末、节假日、淡季),如果我们依赖于阶段移动日期列,我们将会丢失该日期的一行。但是,如果我们将日历表作为主表,并通过 left join 添加 stage activity 表,那么所有的日历日期都将出现(不管那天是否发生了任何移动)。

下一部分有点棘手——决定加入哪个时间戳。这将取决于您试图跟踪的指标。如果我试图寻找所列日期的潜在机会,我会使用这样的语句:

Select c.cal_date, count(distinct opp_id) as "historical_prospects" from calendar c
left join opportunities o
on o.stage_entered ≤ c.cal_date 
and (o.stage_exited is null or o.stage_exited > c.cal_date)

这将找到在感兴趣的日期当天或之前进入该阶段的所有人,并且这些人或者仍然在该阶段中,或者在感兴趣的日期之后退出该阶段。该脚本的最后一个数据框将为我提供一列日历日期,以及在该日期处于潜在客户发现阶段的相应机会数。

总结和进一步应用

在查找和分析历史数据时,肯定有更简单的方法!照片由 Aditya via Unsplash 提供

这样,我关于如何基于时间点状态找到历史趋势的教程就结束了。希望我的解释和伪代码足够清晰,能让大家理解我的主旨和逻辑!可以想象,除了我在本文中分享的两个例子之外,还有更多应用。

您可以对上面的示例进行更复杂的分析,例如过滤来自特定来源(即 2019 年 1 月期间处于潜在客户阶段并以 LinkedIn 作为来源标签的每个人)的管道阶段中的机会。

请随时留下评论和反馈,让我知道你是否有不同的方法来实现这种类型的历史时序分析!我总是喜欢在 StackOverflow 上看到有人用 50 行代码,用 5 行代码就能完成我做的事情😅

在数字营销中使用合成数据

原文:https://towardsdatascience.com/using-synthetic-data-in-digital-marketing-c972b96e5c

品牌和营销机构的新机遇

图片由来自 PixabayGordon Johnson 拍摄。

在这篇文章中,我们提出了一个在数字营销人员和品牌战略家非常普遍的问题中的合成数据生成的用例。因为活动是在快速变化的环境中发起的,我们不能指望从他们那里收集详细的数据。我们展示了如何使用合成生成的数据来生成大型数据集,可以增加低样本量收集数据的价值。

数字营销的挑战

随着数字营销的不断创新,每隔几个月就会出现新的趋势,营销人员和品牌战略家只是被宠坏了。对品牌来说,在合适的时间以合适的成本将相关内容送达合适的受众仍然是一个巨大的挑战。为了回答这个问题,“我应该把我的下一美元投资到哪里?”,数据是品牌最好的朋友。但并非所有品牌或活动都有大量数据。数字营销是一门实证学科。

营销人员必须不断适应新的、引人入胜的内容创作、隐私问题、快速变化的全渠道策略等等。他们收集了大量数据,但这些数据并不总是统计相关的。

最终,他们将拥有小型或中型数据集来研究特定问题。当我们只有一年的数据时,很难预测活动的成功,因为这种方法在两年前并不存在。在这种情况下,合成数据可以成为从现有数据中提取价值和应对新挑战的新工具。

表格合成数据:被遗忘的数据

当今最令人兴奋的技术之一是合成数据。每个人似乎都在谈论 DALL-E 或其他图像或文本生成工具。表格数据没有像图像或文本数据那样取得长足的进步。表格合成数据只与少数数据科学和机器学习专家相关。也很难向非专家传达使用它的好处。我们都熟悉如何让照片看起来更好。图像处理技术帮助我们提高图像的分辨率,以更好地识别原始图像中不清晰的一些细节。这个改进的图像可以被认为是从原始图像生成的“合成数据集”。然而,我们能改进表格数据集吗?能不能提高它的“分辨率”看更多细节?答案是肯定的,有可能,但是把握起来并不像我们用图像看到的那么简单。

生成对抗网络

生成对抗网络(GANs)是机器学习中最具创新性的技术之一。Ian Goodfellow 在 2014 年开创了 GANs[1]。这个想法是制造两个不同的神经网络,让它们互相对抗。第一个神经网络(生成器)生成在统计上与输入数据相似的新数据。第二个神经网络(鉴别器)的任务是识别哪些数据是人工创建的,哪些不是。我们可以想象一个游戏,第一个网络试图欺骗第二个网络,第二个网络必须猜测第一个网络在做什么。这个游戏让这个组合非常强大。要了解更多关于这些网络的信息,你可以阅读这篇文章,其中我们介绍了我们将在这项工作中使用的 Python 开源库。

图 GAN 网络示意图。图片作者。

数字营销中的 S 曲线

我们可以从 Gossen 的边际价值递减定律[2]中看出,超支可能会对营销策略构成威胁。这种想法也被称为边际效用递减理论,在市场营销中,广告 S 曲线或递减曲线[3]。递减曲线模拟了广告支出和销售或市场份额之间的关系,并假设在某个点之后,广告支出不会导致销售(或收入或市场份额)的增加。这种关系有一个“S 形”:它既不是线性的,也不是对称的,有一个饱和点。

图二。戈森的边际效用第一定律。图片作者。

营销人员和品牌战略家充分意识到了曲线递减。在某一点之后,再多的广告努力也无法增加收入。我们需要数量一致的数据来绘制这条 S 形曲线;否则,我们将看到线性关系,或者在最坏的情况下,根本没有关系。这也是大多数营销人员很难正确估计饱和点的原因。因为一些品牌和机构同时进行大量的活动,对这一点的不精确计算意味着浪费大量的金钱。此外,这些曲线是非常有趣的定量工具,可用于比较不同的活动和模拟未来战略。

实际案例

我们接触了一个两年前推出的新奇品牌。从那以后,他们一直在投资不同的活动(谷歌、脸书、Linkedin 等)。).他们收集了数量有限的数据,并要求我们给这些数据一些意义,以便改进他们的广告策略。作为成本最高的活动的总体结果,他们构建了下表(图 3)。

图 3。前两年运行结果数据表。图片作者。

该表包括 4 列和 19 行(它们已经运行的月份)。他们想知道他们是否仍处于饱和点,以便计划他们的下一个战略步骤。在图 4 中,我们可以看到绘制“总效用”的结果,在这种情况下,每月经常性收入(MRR)和“数量”(花费在广告上的金额)。

图 4。品牌收集的原始数据。图片作者。

我们应该能够计算出一个饱和点,但是根据这个图,这似乎是不可行的。我们有几个样本,有几种可能性来画一条曲线。此外,平稳性的影响是显著的(关系中一年中的月份的影响)。我们想探索合成数据集是否能帮助他们解决这个问题。

生成合成数据

为了生成合成数据,我们将使用开源 python 库 nbsyntehtic 。我们最近发布了这个库,我们已经包含了新的包来解决这个问题。我们使用了无条件的 Wasserstein 生成对抗网络。据其创造者称,wGANS 提高了学习的稳定性,消除了模式崩溃等问题,并提供了对调试和超参数搜索有用的有意义的学习曲线[4]。本文无意深入探究这些技术基础。详细信息请参见 4

结果

我们从最初的 19 个样本的表数据中生成了一个 2000 个样本的合成数据集。代码可以在这里找到。在图 5 中,我们可以看到两个数据集的比较。

图 5。原始数据和合成数据的比较。图片作者。

可以评估合成数据是否确实“相似”,或者在进行预测时是否可以用真实数据代替。当涉及到创建合成表格数据时,这不是一个简单的问题。如前所述,处理合成图像时,一切都变得简单多了。当我们比较原始图像和合成图像(例如,为了提高分辨率)时,每个人都同意这是相同的图像。但是当我们创建一个合成数据集时,这种关联是不可见的。有几种方法可以检查数据的相似性。即便如此,“相似性”的概念仍然是一个非常复杂的数学概念。最常见的方法是视觉比较,使用机器学习模型、统计测试和拓扑数据分析。在我们的分析中,我们将使用视觉比较和机器学习模型的使用。在 Github repo 中,读者还可以使用统计测试和拓扑数据分析找到比较。此外,我们将介绍一种基于流形学习方法的新方法。

视觉对比

如果可能的话,这是检查数据相似性的最简单、最直接的方法。当我们有多维数据时,我们可以在二维或三维图中比较特征。例如,如果我们有 20 个特征,我们必须绘制大量的图来检查所有可能的特征组合。从实践的角度来看,这可能很难实现。在图 5 中,我们可以看到我们的问题的直接表示。图 6 让我们了解了饱和点在哪里。我们还看到曲线强烈地依赖于一年中的月份。当然,这种视觉信息对做决定很有用。

图 6。饱和点。图片作者。

与机器学习模型的比较

为了测试两个数据集的“互换性”,我们可以在一个机器学习问题中使用它们。在我们的案例中,我们使用随机森林回归[5]来预测 MMR 变量。然后,我们使用相同的算法在合成数据集上进行相同的预测。最后,我们使用合成数据训练的算法来预测 MMR 使用原始数据值。结果如下表所示。

Original data
-------------
Score without cross validation = 0.32
Scores with cross validation = [ 0.19254948 -7.0973158   0.1455913   0.18710539 -0.14113018]

Synthetic data
--------------
Score without cross validation = 0.80
Scores with cross validation = [0.8009446  0.81271862 0.79139598 0.81252436 0.83137774]

Check algorithm with original data
----------------------------------
Score with cross validation prediction = 0.71

正如我们所看到的,在原始数据集中进行预测会导致相当不稳定的准确性,这取决于我们如何分割用于训练和测试的数据。当我们应用交叉验证策略进行训练时,我们观察到结果高度分散,但预测精度相当适中。

当我们使用合成数据训练系统时,我们获得了更稳定的准确度和更好的结果。总之,在合成数据集中进行预测比在原始数据(有限的样本大小)中进行预测更有意义,并且会产生有趣的准确性。最后,我们使用合成数据训练算法来预测原始数据。我们还采用交叉验证策略。结果表明,虽然精确度略低于通过合成数据训练获得的精确度,但它显然比通过训练原始数据获得的精确度更稳健和更有吸引力。

使用流形学习的不同方法

流形学习是一种非线性降维技术。
许多数据集被认为具有人工高维度,所有信息都可以从嵌入数据空间的低维流形中提取出来。直观地说,对于每个高维数据空间,都有一个等价的低维数据空间。这有助于简化操作,因为它消除了分析高维数据空间时出现的所有挑战[9,10,11,12]。高维数据的挑战,比如维度的诅咒和维度的祝福,完全消失了。

方差集中率(VCR)是一种用于量化数据的严格且可解释的指标(Han 等人,2021)

方差集中率( VCR )是一个严谨且可解释的量化数据的指标【13】。为了更好地检验流形学习对高维和低维数据的解释能力,可以采用方差集中比( VCR )度量。它最初是由韩等人 2021 [13]提出的,用来度量高频交易数据,并对高、低维数据进行量化。

首先我们必须回忆一下奇异值分解的概念—SVD【14】。奇异值分解寻求将秩 R 矩阵转换为秩 K 矩阵。这意味着我们可以将一列 R 唯一向量近似为 K 唯一向量的线性组合。

奇异值可以被认为是在两个子系统(两个矩阵)之间提供了一座“桥梁”。它们是它们之间存在多少相互作用的量度。资料来源:Math3ma

奇异值分解是降维和流形学习的常用技术。

给定具有 n 个观测值和𝑝变量的数据集𝑋,方差集中定义为:

VCR 定义为数据集的最大奇异值与所有奇异值的总和之比。它回答了这个问题:

集中在第一个奇异值方向的数据方差百分比是多少?

这种度量的直觉可能如下:我们认为在数据空间中嵌入了与高维空间等价的低维流形。这个流形也有几个维度。我们可以认为第一维度是最重要的。然后,我们测量数据变化(方差)如何反映在这个第一维度。当比较原始数据和合成数据时,我们必须首先比较低维流形的维度是否相等,然后比较在第一个流形维度上是否有相同的数据变化集中。

我们可以看到从原始的 19 个样本的表数据和 2000 个样本生成的合成数据集获得的结果。我们包括与随机生成的数据集的比较,该数据集具有与合成数据集相同的实例数量。

Singular values             
---------------             
Singular values for original dataset:   [1.0, 0.114, 0.051, 0.0]             Singular values for synthetic dataset:  [1.0, 0.110, 0.046, 0.0]             Singular values for random dataset:     [1.0, 0.184, 0.027, 0.0] Variance concentration ratio (VCR)
----------------------------------             
Variance concentration ratio original data = 85.85%             Variance concentration ratio synthetic data = 86.49%             Variance concentration ratio random data = 82.56%

我们的实证结果显示了以下规律:

  • 原始数据集和合成数据集的奇异值必须相似。
  • 合成数据集的方差集中率 VCR 必须等于或高于原始数据的 VCR。

结论

我们看到了许多品牌和代理商在准备新活动时面临的一个共同问题。他们希望根据可用数据做出决策,但这些数据通常不允许他们突出相关的可操作见解。我们已经看到,通过使用合成数据,他们可以评估可用数据的价值。此外,我们介绍了几种方法来证明合成生成的数据在决策过程中使用时与真实数据“相似”且“可互换”。

最终注释

  • nbsynthetic 是由 NextBrain.ai 推出的开源项目
  • 我们要感谢 [Atomic 212](http://We would like to thank to Atomic 212 for its guidance in helping us understand marketing concepts and the requirements for data-driven solutions in digital marketing.) 的指导,帮助我们理解营销概念和数字营销中数据驱动解决方案的要求。
  • 本文的代码可以在这里找到。

参考

  1. Goodfellow,I .、Pouget-Abadie,j .、Mirza,m .、Xu,b .、Warde-Farley,d .、Ozair,s .、& Bengio,y .等人(2014 年)。生成对抗网络。神经信息处理系统进展,27。
  2. 戈森(1854 年)。人类关系的法则和由此衍生的人类行为的规则。(1983).麻省剑桥:麻省理工学院出版社。
  3. 约翰逊,J. K. (1979)。广告与 S 曲线:一种新方法。市场研究杂志,16(3),346–354 页。https://doi.org/10.1177/002224377901600307
  4. 阿尔乔夫斯基,m .,钦塔拉,s .&amp;博图湖..(2017).沃瑟斯坦生成对抗网络。第 34 届国际机器学习会议论文集,机器学习研究论文集 70:214–223。
  5. *Pedregosa 等人(2012 年)。*Scikit-learn:Python 中的机器学习 ,JMLR 12,第 2825–2830 页。
  6. Ilya Tolstikhin、Bharath K. Sriperumbudur 和 Bernhard schlkopf(2016 年)。径向核下最大平均偏差的极小极大估计。《第 30 届国际神经信息处理系统会议录》(NIPS'16)。1938-1946 年,美国纽约州红钩镇柯伦联合公司。
  7. Arthur Gretton,Karsten M. Borgwardt,Malte J. Rasch,Bernhard schlkopf 和 Alexander Smola。2012.核双样本检验。j .马赫。学习。第 13 号决议,无效(2012 年 1 月 3 日),第 723-773 号决议。
  8. 大卫·多诺霍。(2000).高维数据分析:维度的诅咒与祝福。AMS 数学挑战讲座。1–32.
  9. 阿加尔瓦尔,c .,欣内堡,a .,凯米特区(2002 年)。高维空间中距离度量的惊人行为。第一次出版。数据库理论,ICDT 200,第八届国际会议,英国伦敦,2001 年 1 月 4-6 日/ Jan Van den Bussche …(编辑。).柏林:施普林格出版社,2001 年,第 420-434 页
  10. K. S .拜尔、j .戈尔茨坦、r .罗摩克里希南和 u .肖特。(1999).「最近邻」什么时候有意义?进行中。第七国际。糖膏剂数据库理论,第 217-235 页。
  11. 亚历山大·欣内堡、查鲁·阿加尔瓦尔和丹尼尔·凯米。(2000).高维空间中最近邻是什么?第 26 届超大型数据库国际会议论文集(VLDB '00)。美国加利福尼亚州旧金山摩根考夫曼出版公司,邮编:506–515。
  12. 弗朗索瓦·德·沃茨诉韦莱森(2007 年)。分数距离的集中。IEEE 知识与数据工程汇刊,19,873–886。
  13. 韩、亨利&腾、杰&夏、君若&王、&郭、李子豪&李德清。(2021).通过流形学习预测高频交易标记。基于知识的系统。213.106662.10.1016/j . kno sys . 2020.106662 .
  14. 特里费森,L. N .,&鲍尔三世,D. (1997 年)。数值线性代数(第 50 卷)。暹罗。

将变形金刚用于计算机视觉

原文:https://towardsdatascience.com/using-transformers-for-computer-vision-6f764c5a078b

视觉变形金刚真的有用吗?

视觉转换器架构的基本描述(由作者创建)

什么是视觉变形金刚?

Transformers 是一种深度学习架构,主要基于自我关注模块,最初是为序列到序列任务(例如,将句子从一种语言翻译成另一种语言)而提出的。最近的深度学习研究通过将这种架构应用于计算机视觉任务,如图像分类,取得了令人印象深刻的成果。应用于这一领域的变压器通常被称为(不足为奇)视觉变压器。

等等…语言翻译模型怎么能用于图像分类?好问题。虽然这篇文章将深入探讨这个话题,但基本思想是:

  1. 将图像转换为一系列拼合的图像面片
  2. 将图像补片序列传递给变压器模型
  3. 获取转换器输出序列的第一个元素,并将其传递给分类模块

与广泛使用的卷积神经网络(CNN)模型相比,视觉转换器缺乏有用的归纳偏差(例如,平移不变性和局部性)。尽管如此,这些模型被发现在图像分类任务上相对于流行的 CNN 变体表现得相当好,最近的进展使它们的效率——在所需的数据量和计算量方面——更加合理。因此,视觉变压器现在是深度学习实践者的可行和有用的工具。

背景

自我关注

transformer 架构由两个主要组件组成:前馈网络和自我关注。虽然大多数人都熟悉前馈网络,但我发现自我关注往往不太为人所理解。网上有许多关于自我关注的详尽描述,但为了完整起见,我将在这里提供一个概念的简要概述。

什么是自我关注?自关注以 n 个元素(或令牌)为输入,进行转换,返回 n 个令牌作为输出。它是一个序列到序列模块,对于每个输入令牌,它执行以下操作:

  • 将该标记与序列中的所有其他标记进行比较
  • 为每一对计算关注分数
  • 将当前标记设置为所有输入标记的加权平均值,其中权重由关注分数给出

这种过程通过查看完整的输入序列,识别其中最重要的记号,并基于最相关的记号调整每个记号的表示,来调整输入序列中的每个记号。换句话说,它提出了这样一个问题:“哪些令牌值得关注?”(因此得名自我关注)。

多头自我关注。大多数变压器中使用的注意力变量与上述描述略有不同。也就是说,变形金刚经常利用“多头”版本的自我关注。虽然这听起来很复杂,但一点也不……多头自我关注只是并行地使用多个不同的自我关注模块(例如,八个)。然后,这些自我关注模型的输出被连接或平均,以将它们的输出融合在一起。

这是哪里来的?尽管在变形金刚中使用了自我关注,但这个想法早于变形金刚的架构。它被大量用于递归神经网络 (RNN)架构【6】。然而,在这些应用中,自我注意通常被用来聚集 RNN 隐藏状态,而不是执行序列到序列的转换。

变压器架构

视觉变压器架构与[4]中提出的原始变压器架构非常相似。因此,对转换器架构(尤其是编码器组件)的基本了解有助于理解视觉转换器。在这一节中,我将描述变压器的核心组件,如下图所示。尽管该描述假设使用文本数据,但是也可以使用不同的输入形式(例如,如在视觉变形器中的扁平图像补片)。

变压器架构(来自[4])

构建输入。转换器接受一系列标记作为输入。这些记号是通过将文本数据(例如,一个或几个句子)通过记号赋予器而生成的,记号赋予器将文本数据划分为单独的记号。然后,通过基于记号 ID 索引可学习的嵌入矩阵,将这些记号(每个记号与唯一的整数 ID 相关联)转换成它们相应的嵌入向量,形成输入数据的(N x d) 矩阵(即N个记号,每个记号用维度为d的向量表示)。

通常情况下,一整批(B x N x d),其中B是批量,被一次传送到变压器。为了避免不同序列具有不同长度的问题,所有序列都被填充(即使用零或随机值)为相同长度N。填充区域被自我关注所忽略。

一旦输入被标记化和嵌入,必须执行最后一步——向每个输入标记添加位置嵌入。自我关注没有位置的概念——无论位置如何,所有的表征都被同等看待。因此,必须向每个输入标记添加可学习的位置嵌入,以将位置信息注入到变换器中。

变压器编码器架构(来自[4])

编码器。变压器的编码器部分有许多相同结构的重复层。具体而言,每一层都包含以下模块:

  • 多头自我关注
  • 前馈神经网络

这些模块中的每一个都跟随着层标准化剩余连接。通过将输入序列传递通过这些层,每个标记的表示被转换为:

  1. 序列中其他相关记号的表示
  2. 一种经过学习的多层神经网络,实现每个单独令牌的非线性转换

当一行中应用了几个这样的层时,这些转换会产生一个长度相同的最终输出序列,每个令牌都有上下文感知的表示。

变压器解码器架构(来自[4])

解码器。解码器与视觉变压器无关,视觉变压器仅支持编码器架构。然而,为了完整起见,我们将简要概述一下解码器架构。与编码器类似,变压器的解码器包含多层,每层包含以下模块:

  • 掩蔽的多头注意力
  • 多头编码器-解码器注意
  • 前馈神经网络

掩蔽自我注意类似于正常/双向自我注意,但它防止在输入序列中“向前看”(即,这对于像语言翻译这样的序列到序列任务是必要的)。每个标记只能根据输入序列中在它之前的标记进行调整。编码器-解码器自我注意也非常类似于正常的自我注意,但是来自编码器的表示也被用作输入,允许来自编码器和解码器的信息被融合。然后,该计算的结果再次通过前馈神经网络。

不同的架构变体。除了本节描述的序列间变压器模型之外,还有许多利用相同基本组件的架构变体。例如,通常在语言理解任务中使用的仅编码器转换器架构完全丢弃了转换器的解码器部分,而仅解码器转换器架构通常用于语言生成。Vision transformer 通常利用仅编码器的 transformer 架构,因为没有需要使用掩蔽自我关注的生成组件。

自我监督预培训

虽然 transformers 最初是为序列到序列的任务而提出的,但随着该架构后来被应用于文本生成和句子分类等问题,它的受欢迎程度急剧上升。《变形金刚》获得广泛成功的主要原因之一是使用了自我监督的预训练技术

自我监督的任务(例如,预测屏蔽词;见上图)可以被构造用于在原始的、未标记的文本数据上训练转换器。由于这种数据可以广泛获得,变压器可以在大量文本数据的基础上进行预训练,然后在监督任务中进行微调。伯特[7]推广了这样的想法,这在自然语言理解方面取得了令人震惊的进步。然而,这种方法被许多后来的变压器应用所采用(例如 GPT-3 [9])。

有趣的是,尽管自我监督学习在自然语言应用中产生了巨大的影响,但这种方法在视觉变形金刚中并不成功,尽管许多作品都尝试了这种想法[11,12]。

一些革命性的变压器应用…

对 transformer 架构有了基本的了解后,就可以正确看待这种架构对深度学习研究的巨大影响。最初,transformer 架构因其在语言翻译方面的成功而流行起来[4]。然而,这种架构继续在深度学习研究的许多领域进行变革。下面列出了一些值得注意的变压器应用(按时间顺序排列):

  • BERT 使用自我监督的预训练来学习高质量的语言表示[ 论文 ][ 代码 ]
  • GPT-2/3 利用仅支持解码器的变压器架构来革新生成式语言建模[ 博客 ][ 论文
  • AlphaFold2 使用了一种变压器架构来解决长期存在的蛋白质折叠问题[ 论文 ][ 代码 ]
  • DALLE-2 利用剪辑潜伏(和扩散)在多模态生成中实现令人震惊的结果[ 博客 ][ 论文 ]

虽然变形金刚的应用范围很广,但我想强调的要点很简单:变形金刚在解决各种不同的任务方面极其有效

出版物

一幅图像相当于 16x16 个字:用于大规模图像识别的变形金刚[1]

尽管 transformer 架构对自然语言处理领域产生了巨大的影响,但是将这种架构扩展到计算机视觉领域需要时间。最初的尝试将流行的 CNN 架构与自我关注模块融合在一起,以创建一种混合方法,但这些技术被基于 ResNet 的 CNN 架构超越。

除了将类似变压器的组件集成到 CNN 架构中,在[1]中提出了直接利用变压器架构的图像分类模型。Vision Transformer (ViT)模型将底层图像分成一组面片,每个面片都被展平并投影(线性)到一个固定的维度。然后,向每个图像块添加位置嵌入,指示每个块在图像中的位置。与任何其他 transformer 架构类似,模型的输入只是一系列向量;见下文。

(来自[1])

作者采用 BERT base 和 large 7作为其架构,然后通过将监督分类头附加到模型输出中的第一个令牌来训练该架构。对于训练,遵循两步预训练和微调程序。JFT-300M(非常大)、ImageNet-21K(大)或 ImageNet-1K(中等)数据集用于监督预训练。然后,在一些目标数据集(例如,Oxford Flowers 或 CIFAR-100)上对模型进行微调,之后测量最终性能。

在没有对足够数据进行预训练的情况下,所提出的模型不匹配或超过最先进的 CNN 性能。这种趋势可能是由于这样一个事实,即虽然 CNN 对于像平移和位置这样的模式是自然不变的,但是变压器没有这样的归纳偏差,并且必须从数据中学习这些不变性。然而,随着模型在更多数据上被预训练,性能显著提高,最终超过基于 CNN 的基线的准确性,即使预训练成本较低;请看下面的结果。

(来自[1])

训练数据高效的图像转换器&通过注意力进行提炼[2]

尽管在以前的工作中,视觉变形器被证明对于图像分类是有效的,但是这样的结果依赖于对外部数据集的大量预训练。例如,在对模型进行微调和评估下游任务之前,最好的 ViT 模型对包含 3 亿幅图像的 JFT-300M 数据集进行了预训练。

虽然之前的工作声称大量的预训练程序是必要的,但作者在[3]中提供了一个替代方案,称为数据高效图像转换器(DeiT),它利用一个精心策划的知识提炼程序来训练视觉转换器达到高 Top-1 精度,而无需任何外部数据或预训练。事实上,整个培训过程可以在一台计算机上用三天时间完成。

本研究中使用的视觉转换器架构与 ViT 模型几乎相同。然而,输入序列中增加了一个额外的标记,称为提取标记;见下图。

(来自[2])

该令牌与其他令牌被同等对待。但是,在离开变压器的最后一层之后,它被用来对网络损耗应用蒸馏分量。特别地,采用硬蒸馏(即,与软蒸馏相反)损失来训练视觉转换器复制某个教师网络(通常是 CNN)的 argmax 输出。

在测试时,类的令牌输出和提取令牌被融合在一起,并用于预测网络的最终输出。DeiT 模型优于之前在大型外部数据集上预先训练的几个 ViT 变体。当在 ImageNet 上进行预训练并在下游任务上进行微调时,DeiT 实现了有竞争力的性能。换句话说,它在不利用外部培训数据的情况下实现了引人注目的绩效。

除了令人印象深刻的准确性之外,DeiT 中改进的学习策略也非常有效。考虑到各种不同图像分类模型的吞吐量(即模型每秒处理的图像),DeiT 实现了吞吐量和准确性之间的平衡,这与广泛使用的 EfficientNet [4]模型类似;见下图。

(来自[2])

从自然语言监督中学习可转移的视觉模型[3]

(来自[3])

对比语言-图像预训练模型(CLIP)——最近因其在 DALLE-2 中的使用而再次流行——首次表明大量有噪声的图像-字幕对可以用于学习高质量的图像和文本表示。由于使用了制作不良的预训练任务,以前的工作很难恰当地利用这种弱监督数据;例如,使用语言模型直接预测字幕的每个单词。CLIP 提供了一个更简单的预训练任务——将图像与一组潜在标题中的正确标题进行匹配。这个简化的任务为模型提供了更好的训练信号,使得能够在预训练期间学习高质量的图像和文本表示。

CLIP 中使用的模型有两个主要组件——一个图像编码器和一个文本编码器;见上图。图像编码器被实现为 CNN 或视觉转换器模型。然而,作者发现 CLIP 的视觉转换器变体在预训练期间实现了改进的计算效率。文本编码器是一个简单的仅支持解码器的转换器架构,这意味着在转换器的层中使用了屏蔽的自我关注。作者选择使用掩蔽的自我注意,以便 CLIP 的文本组件可以在未来扩展到语言建模应用程序。

使用该模型,预训练任务通过分别编码图像和字幕来实现,然后应用归一化的、温度缩放的交叉熵损失来将图像表示与其相关联的字幕表示相匹配。由此产生的 CLIP 模型彻底改变了图像分类的零镜头性能,将 ImageNet 上的零镜头测试精度从 11.5%提高到 76.2%。为了执行零射击分类,作者简单地:

  • 使用文本编码器对每个类的名称进行编码
  • 使用图像编码器对图像进行编码
  • 选择与图像编码最大化余弦相似性的类别

上图中描述了这样一个过程。关于 CLIP 的更多信息,请参见我的之前的车型概述。

维生素有用……但是它们实用吗?

就个人而言,我最初对使用视觉变形金刚持怀疑态度,尽管我知道它们令人印象深刻的性能。训练过程似乎计算量太大。然而,训练视觉变形器的大部分计算成本与预训练相关联。在[2]中,作者消除了大量预训练的需要,并直接证明了视觉变压器的训练吞吐量可与 EfficientNet 等高效 CNN 架构相媲美。因此,视觉变压器是一种可行和实用的深度学习工具,因为它们的开销不会明显超过普通 CNN。

外卖食品

虽然 transformer 在自然语言处理方面取得了广泛的成功,但是这篇综述应该(希望)传达这样一个事实,即它们对于计算机视觉任务也是有用的。CNN 是一个难以超越的基线,因为它们在数据和计算方面都以高效的方式实现了令人印象深刻的性能水平。然而,最近对视觉变压器架构的修改(如[2]所述)表明,视觉变压器的性能优于 CNN,实际上相当高效。

代码中的视觉变形金刚。对于那些对实施和/或尝试视觉转换器架构感兴趣的人,我建议从这里开始。本教程允许您【T4(I)】下载预先训练的 ViT 参数,并 (ii) 在下游视觉任务中微调这些参数。我发现本教程中的代码很容易理解。人们可以很容易地将这些代码扩展到不同的应用程序,甚至可以实现一些在[2]或其他工作中概述的更复杂的训练程序。

未来要读的论文。虽然这篇文章概述了我最喜欢的几个视觉转换器作品,但这个主题很受欢迎,还有数百篇其他论文。我(其他)个人最喜欢的几个是:

  • 金字塔视觉转换器:无卷积密集预测的通用主干
  • Token-to-Token ViT:在 ImageNet [ paper ]上从头开始训练视觉变形人
  • Mlp-mixer:面向视觉的全 Mlp 架构[ 论文

结论

非常感谢你阅读这篇文章。如果你喜欢它,请关注我的深度(学习)焦点时事通讯,在那里我挑选了一个关于深度学习研究的单个两周一次的主题,提供了对相关背景信息的理解,然后概述了一些关于该主题的流行论文。我是 Cameron R. Wolfe ,是 Alegion 的研究科学家,也是莱斯大学的博士生,研究深度学习的经验和理论基础。你也可以看看我在 medium 上的其他作品

文献学

[1] Dosovitskiy,Alexey 等,“一幅图像相当于 16x16 个字:大规模图像识别的变形金刚。” arXiv 预印本 arXiv:2010.11929 (2020)。

[2] Touvron,Hugo,等,“训练数据有效的图像转换器&通过注意力进行提炼。”机器学习国际会议。PMLR,2021 年。

[3]拉德福德、亚历克等,“从自然语言监督中学习可转移的视觉模型。”机器学习国际会议。PMLR,2021 年。

[4]瓦斯瓦尼、阿希什等人,“你所需要的只是注意力。”神经信息处理系统进展 30 (2017)。

[5]谭、明星和郭乐。"效率网:重新思考卷积神经网络的模型缩放."机器学习国际会议。PMLR,2019。

[6]林,,等.“一个结构化的自注意句嵌入” arXiv 预印本 arXiv:1703.03130 (2017)。

[7] Devlin,Jacob 等《Bert:用于语言理解的深度双向转换器的预训练》 arXiv 预印本 arXiv:1810.04805 (2018)。

[8]拉德福德、亚历克等人,“语言模型是无人监督的多任务学习者。” OpenAI 博客 1.8 (2019): 9。

[9]布朗、汤姆等人,“语言模型是一次性学习者。”神经信息处理系统的进展33(2020):1877–1901。

[10] Ramesh,Aditya 等人,“具有剪辑潜在性的分层文本条件图像生成” arXiv 预印本 arXiv:2204.06125 (2022)。

[11]陈,,谢赛宁,,何."训练自我监督视觉转换者的实证研究."IEEE/CVF 国际计算机视觉会议论文集。2021.

[12]卡隆,玛蒂尔德等,“自我监督的视觉变形金刚的新兴特性”IEEE/CVF 计算机视觉国际会议论文集。2021.

使用无监督学习发现异常值

原文:https://towardsdatascience.com/using-unsupervised-learning-to-find-outliers-670e07396599

两种选择:局部异常因子和高斯混合模型

照片由塔里克·阿贾利亚金Unsplash 拍摄

介绍

离群值是许多数据科学论坛和博客中经常讨论的话题。这可能是因为这些数据点会扭曲我们的分析,并影响建模,如果我们使用的算法对这些异常不稳健的话。

一个数据集,很多时候,会把大部分的观察值放在一定的数值范围内,遵循一些模式,离“群体”不太远。这些是内联器。但是也有一些观察不适合任何地方,远离数据的标准,不遵循这种模式。那些是异常值,离群值。

受异常值影响很大的算法是传统的线性回归。如果您的观察值过于偏离中心值,将会扭曲回归计算,使模型表现更差。

离群值。图片由作者提供。

让我们学习两种使用无监督学习算法查找异常值的快速方法:带有局部异常值因子的 KNN 和高斯混合,这两种方法都来自 Scikit-Learn。

本地异常因素[LOF]

这个算法在 sklearn.neighbors 模块中有,可以用from sklearn.neighbors import [LocalOutlierFactor](https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.LocalOutlierFactor.html)导入。它依赖于 K-最近邻算法,有趣的是,LOF 有超参数contamination来帮助我们确定离群值的阈值。所以,如果你使用contamination= 0.1,这意味着你想把 10%的数据作为异常值。

数据集的污染量,即数据集中异常值的比例。(sklearn 文档)

接下来是应用该算法的编码。我们将使用 Python 中 seaborn 包自带的' car_crashes '数据集。

# Dataset
df = sns.load_dataset('car_crashes')

我将在这里添加一些奇怪的数据,它们将是我们的异常值。请注意,我希望它非常不同,以确保它是一个异常值,这样我就可以演示异常值检测器的运行。

# Creating very odd observations = OUTLIERS
s1 = pd.DataFrame([30,30,30,30,30,30,30,'AB']).T
s2 = pd.DataFrame([40,40,40,40,40,40,40,'AB']).T
s3 = pd.DataFrame([30,30,30,30,50,50,50,'AB']).T
s4 = pd.DataFrame([99,99,99,39,99,59,59,'AB']).T
s5 = pd.DataFrame([99,99,90,9,99,9,99,'AB']).T
s1.columns = s2.columns = s3.columns = s4.columns = s5.columns= df.columns# Adding them to the dataset
df=pd.concat([df,s1, s2, s3, s4, s5], axis=0)# X
X = df.drop('abbrev', axis=1)

数据集 car_crashes 的头。图片由作者提供。

接下来,我们可以导入PipelineStandardScaler并创建这个简单的管道来缩放数据,将其放入相同的范围,然后运行 LOF 算法。请注意,我们使用 9%的污染率,因为数据有 51 个观察值,我们知道有 5 个异常值(5/51 = 9.8%)。

# Let's create a Pipeline to scale the data and find outliers using KNN Classifiersteps = [
('scale', StandardScaler()),
('LOF', LocalOutlierFactor(contamination=0.09))
]# Fit and predict
outliers = Pipeline(steps).fit_predict(X)

如果您调用输出outliers,您将会看到这个结果。

LOF 将异常值标注为-1。图片由作者提供。

完成后,我们可以将数据添加到原始数据集并查看它。

# Add column
df['outliers'] = outliers# Look at the top 8
df.sort_values(by='outliers').head(8)

LOF 算法检测到的异常值。图片由作者提供。

非常好。我们能够找到先前创建的异常值。如果我们使用 t-SNE 来创建这个数据集的 2D 图,下面是我们将看到的内容(**这个图的代码可以在文章末尾的 GitHub 链接中找到)。

数据集的 2D 图。图片由作者提供。

回顾刚刚发生的事情:LOF 使用 KNN,这是一个分类器,它根据数据点之间的距离和相似性来聚类数据点。自然,由于创建的数据不同于其他数据,它们被标记为异常。

高斯混合模型

可以找到的另一种算法是高斯混合模型(GMM)。这个将查看数据,并将其分成 n 组。为了将每个观察值添加到一个组中,算法会计算并创建 n 个高斯分布,然后检查数据点在这些高斯分布中的哪个位置更适合(概率更高)。

sklearn 的 GMM 根据每个点在该空间中的密度来计算观察值。因此,密度较高的区域中的点不太可能是异常值,反之亦然,因此低密度区域就是异常值所在的位置。

我们编码吧。首先,导入模块。

*from sklearn.mixture import GaussianMixture*

接下来,我们可以拟合 GMM。我们使用 3 个分量,这意味着该数据被分成3 个高斯分布。n_init= 10是 GMM 运行 10 次迭代以找到最佳拟合。

*gm = GaussianMixture(n_components=3, n_init=10)
gm.fit(X)*

好了,现在我们必须计算分数。

*# Finding densities
density_scores = gm.score_samples(X)*

如果我们打印它,在这种情况下,我们只会看到负数。所以,我只取它们的绝对值,这样更容易计算百分位数。

*density_scores= abs(density_scores)*

现在我们可以计算我们想要的百分比作为阈值,或者要查找的离群值的数量。让我们用同样的 9%来工作

*# Define threshold
threshold = np.percentile(density_scores, 9)# Finding outliers
X[density_scores< threshold]X['densities'] = density_scores*

阈值是16.36102。我们可以看到,它找到了相同的结果,即低于该数字的所有数据(我们的假异常值)。

使用 GMM 检测到的异常值是前 5 行,小于 16.36。图片由作者提供。

它像预期的那样工作。

在你走之前

发现异常值是一项可以通过多种方式执行的任务。还有其他好的算法,比如隔离森林。还有其他的方法,比如 Z 值法、IQR 法……还有很多选择。

我想展示这两个,因为我发现它们非常容易应用。

对于 LOF,只需使用:

LocalOutlierFactor(contamination= n)

对于 GMM,请使用:

*scores = gm.score_samples(X)threshold = np.percentile(scores, 9)
X[scores< threshold]*

这里是 GitHub 中的完整代码。

如果你喜欢这个内容,请关注我的博客。

*http://gustavorsantos.medium.com/

在 LinkedIn 上找到我。如果你正在考虑加入 Medium,这里有一个推荐链接

参考

Aurélien Géron,2019。 用 Scikit-Learn 动手机器学习,Keras & TensorFlow 。第二版,奥赖利。

迈克尔·沃克,2022 年。 用机器学习进行数据清洗和探索。 1ed。Packt 出版物。

https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.LocalOutlierFactor.html https://scikit-learn.org/stable/modules/generated/sklearn.mixture.GaussianMixture.html *

使用通用数据工具包从 REST API 获取和处理数据

原文:https://towardsdatascience.com/using-versatile-data-kit-to-ingest-and-process-data-from-rest-api-6e3e0660b791

数据库ˌ资料库

关于如何使用通用数据工具包(VDK)从 REST API 获取数据的教程,以及一些关于如何安装 VDK 服务器的提示。

凯文·Ku 在 Unsplash 上的照片

多功能数据套件 (VDK)是一个开源框架,最近由 VMware 发布。它与一个数据湖一起工作,您可以接收不同格式的数据,然后轻松地访问它们。在我上一篇题为多功能数据工具包概述的文章中,我描述了 VDK 背后的主要概念,包括安装它的分步指南。在我的下一篇文章中,名为从原始数据到干净的数据库:深入了解多功能数据工具包,我展示了如何连接 VDK 和 Trino DB,以及如何接收和处理 CSV 文件。

在本文中,我为您提供了 VDK 的另一个例子,从 REST API 中提取数据。因为访问 REST API 需要一个秘密密钥,所以我还描述了如何配置 VDK 服务器来存储这个秘密。

文章组织如下:

  • 场景的定义
  • 环境设置
  • VDK 的数据接收
  • VDK 的数据处理

1 情景的定义

这个场景的目标是在 VDK 摄取和处理文森特·梵高在著名的欧洲文化遗产平台 Europeana 上的画作。Europeana 通过其 public REST API 提供所有文化遗产对象。Europeana 提供了 700 多幅作品,其作者是文森特·梵高。对于每个记录,REST API 提供了很多信息,包括完整性、国家、数据提供者、创建者、标题等等。

以下 JSON 显示了由 Europeana REST API 返回的记录摘录:

[{
    completeness: 10,
    country: ["Belgium"],
    dataProvider: ["Catholic University of Leuven"],
    dcCreator: ["http://data.europeana.eu/agent/base/59832",
    "Vincent van Gogh"],
    dcCreatorLangAware: {
        def: ["http://data.europeana.eu/agent/base/59832"],
        en:  ["Vincent van Gogh"]
    },
    dcDescription:
        ["KU Leuven. Glasdia’s kunstgeschiedenis. Université de Louvain, tussen 1839 en 1939\. Fotograaf onbekend. Toegevoegde informatie op dia. Stroming/Stijl: Postimpressionisme. Creatie/Bouw: 1889\. Techniek/Materiaal: Olieverf op doek. Huidige locatie: Nederland, Otterlo, Kröller-Müller Museum. EuroPhot. Kunstgeschiedenis. 19de eeuw. Schilderkunst. Portret. EuroPhot. Art history. 19th century. Paintings. Portrait."],
        ...
}]

REST API 的访问点可从以下链接获得:

[https://api.europeana.eu/record/v2/search.json](https://api.europeana.eu/record/v2/search.json)

需要免费注册才能获得 API 密钥。

2 环境设置

要运行这个示例,我需要:

  • 多功能数据工具包
  • Trino DB
  • Trino 的多功能数据工具包插件
  • 多功能数据套件服务器
  • 欧洲 API 密钥

我的上一篇文章中,我已经介绍了如何配置 Trino DB 和 Trino 的通用数据工具包插件,如果需要安装可以参考一下。

2.1 多功能数据套件服务器

在这个例子中,我在本地安装了通用数据工具包(VDK)服务器。我将使用 VDK 服务器来存储 Europeana API 密钥。VDK 服务器需要以下先决条件:

我可以按照他们的官方指南安装它们。我可以通过以下命令安装通用数据工具包服务器:

vdk server — install

关于如何安装 VDK 服务器的更多信息,可以参考 VDK 官方文档

安装完成后,我应该可以通过以下链接访问 VDK REST API:

http://localhost:8092/swagger-ui.html

此外,从 Docker 仪表板,我应该能够看到安装的 VDK 服务器,如下图所示:

作者图片

2.2 欧洲 API 密钥

我可以通过注册到这个链接来获得一个 Europeana API 密钥。我应该填一份表格,要你的电子邮件。一旦我提交了表单,API 密匙就会通过电子邮件发送。

我可以添加 Europeana API 键作为 VDK 属性,如下所示:

vdk properties — set-secret api_key

VDK 将向您询问以下信息:

Job Name: online-exhibition
Job Team: my-team
api_key: YOUR_API_KEY

在这一步之后,Europeana API 密钥应该存储在您的 VDK 服务器中。我可以通过运行以下命令来控制它是否被正确存储:

vdk properties --list

我写下作业名称和作业团队,系统返回给我 Europeana API 键。

3 VDK 的数据接收

数据摄取在数据库输出中上传对 Europeana REST API 的调用。数据接收通过以下步骤执行:

  • 删除现有的表(如果有)
  • 创建新表格
  • 直接从 REST API 获取表值。

为了访问 Europeana REST API,这个示例需要一个活动的 Internet 连接才能正常工作。

3.1 删除现有表格

我将这一步写成一个 SQL 命令,如果表存在的话,就删除它:

DROP TABLE IF EXISTS assets

REST API 的输出被导入到一个名为assets的表中。

3.2 创建新表

现在我定义了一个新的步骤,它创建了一个新的表,该表将包含 REST API 的所有输出。我将把输出作为原始值导入,其中可能包含一个 JSON:

CREATE TABLE assets (completeness int,
country varchar,
dataProvider varchar,
dcCreator varchar,
dcCreatorLangAware varchar,
dcTitleLangAware varchar,
edmDatasetName varchar,
edmIsShownBy varchar,
edmPreview varchar,
edmTimespanLabel varchar,
edmTimespanLabelLangAware varchar,
europeanaCollectionName varchar,
europeanaCompleteness int,
"guid" varchar,
id varchar,
index int,
language varchar,
link varchar,
previewNoDistribute boolean,
provider varchar,
rights varchar,
score double,
"timestamp" varchar,
timestamp_created varchar,
timestamp_created_epoch varchar,
timestamp_update varchar,
timestamp_update_epoch varchar,
title varchar,
"type" varchar,
ugc varchar
)

下表显示了assets表的一个示例:

作者图片

前面的表格只显示了一些字段,以便了解导入数据的原始程度。

3.3 直接从 REST API 获取表值

下一步是 Python 脚本,它直接从 REST API 导入值。我实现了run()函数,它将被 VDK 自动读取,如下所示。首先,我通过 job_input 的get_property()方法检索 api_key。然后我访问 Europeana REST API。因为 Europeana 通过分页返回信息,所以我应该遍历所有页面。最后,我使用send_tabular_data_for_ingestion()方法存储检索到的信息。运行代码的完整代码如下所示:

def run(job_input: IJobInput):
   api_key = job_input.get_property("key") start = 1
   rows = 100

   basic_url = f"https://api.europeana.eu/record/v2/search.json?wskey={api_key}&query=who:%22Vincent%20Van%20Gogh%22"
   url = f"{basic_url}&rows={rows}&start={start}" response = requests.get(url)
   response.raise_for_status()
   payload = response.json()
   n_items = int(payload["totalResults"])

   while start < n_items:
      if start > n_items - rows:
          rows = n_items - start + 1
          url = f"{basic_url}&rows={rows}&start={start}"
          response = requests.get(url)
          response.raise_for_status()
          payload = response.json()["items"]
          df = pd.DataFrame(payload)
          job_input.send_tabular_data_for_ingestion(
             df.itertuples(index=False),
             destination_table="assets",
             column_names=df.columns.tolist(),
          )
          start = start + rows

4 数据处理

进口的桌子挺脏的,要清洗一下。数据处理包括清理资产表,只提取一些有用的信息。提取的信息包括以下各列:

  • 国家
  • EDM 预览
  • 供应者
  • 标题
  • 权利

我将结果表存储到名为 cleaned_assets 的表中。我为数据处理定义了两个步骤:

  • 删除cleaned_assets table(如果有的话)
  • 创建并填充cleaned_assets table

4.1 删除 cleaned _ assets 表

与资产表类似,这一步是一条 SQL 语句,如果表存在,它将删除该表:

DROP TABLE IF EXISTS cleaned_assets

4.2 创建并填充 cleaned_assets 表

这一步操作资产表,只提取有用的信息。我可以这样写:

CREATE TABLE cleaned_assets AS (SELECT
  SUBSTRING(country, 3, LENGTH(country)-4) AS country,
  SUBSTRING(edmPreview, 3, LENGTH(edmPreview)-4) AS edmPreview,
  SUBSTRING(provider, 3, LENGTH(provider)-4) AS provider,
  SUBSTRING(title, 3, LENGTH(title)-4) AS title,
  SUBSTRING(rights, 3, LENGTH(rights)-4) AS rightsFROM assets)

前面的代码通过SUBSTRING()函数清理了一些字段。

现在数据集已经准备好进行进一步的分析了!好好享受吧!

摘要

恭喜你!您刚刚学习了如何从 VDK 的 REST API 导入数据,以及如何配置 VDK 服务器来存储机密。你可以从 VDK 官方知识库的“示例→在线展览”部分下载本文中描述的示例。

目前就这些。请继续关注 VDK 的最新消息!

相关文章

使用 Whisper 和 BERTopic 对 Kurzgesagt 的视频进行建模

原文:https://towardsdatascience.com/using-whisper-and-bertopic-to-model-kurzgesagts-videos-7d8a63139bdf

我们可以在 Kurzgesagt 的视频中找到哪些主题?

一个多月前,OpenAI 发布了一个名为 Whisper 的英语语音识别神经网络。由于它的准确性、易用性,最重要的是因为他们开源了它,它在过去的几周里变得相当受欢迎!

有了这些版本,我迫不及待地想要得到这样一个模型,并尝试一下。然而,我喜欢有一个有趣的用例来实际使用它。

所以我想,为什么不用它来制作一个我一直喜欢看的频道,Kurzgesagt!

这是一个令人惊叹的频道,有着令人难以置信的精心解释的视频,侧重于动画教育内容,从气候变化和恐龙到黑洞和地球工程。

我决定不仅仅做一些文字记录。相反,让我们使用 BERTopic 来看看我们是否可以提取 Kurzgesagt 视频中的主要主题。

因此,本文是一篇关于使用 Whisper 和 BERTopic 从 Youtube 视频中提取脚本并在其上使用主题建模的教程。

1.装置

在进入实际代码之前,我们首先需要安装几个包,分别是耳语BERTopicPytube

pip install --upgrade git+https://github.com/openai/whisper.git 
pip install git+https://github.com/pytube/pytube.git@refs/pull/1409/merge
pip install bertopic

我们有目的地在 Pytube 中选择一个特定的 pull 请求,因为它修复了空通道的问题。

在最后一步,我将简要介绍 BERTopic 的一个即将推出的特性,您已经可以安装该特性了:

pip install git+https://github.com/MaartenGr/BERTopic.git@refs/pull/840/merge

2.Pytube

我们需要从 Kurzgesagt 的 YouTube 频道中提取我们需要的每一个元数据。使用 Pytube,我们可以创建一个Channel对象,允许我们提取他们视频的 URL 和标题。

# Extract all video_urls
from pytube import YouTube, Channel
c = Channel('https://www.youtube.com/c/inanutshell/videos/')
video_urls = c.video_urls
video_titles = [video.title for video in c.videos]

我们还提取了标题,因为当我们稍后可视化主题时,它们可能会派上用场。

3.低语

当我们有了我们的网址,我们就可以开始下载视频和提取文字记录。为了创建这些抄本,我们使用了最近发布的 Whisper。

耳语的管道。更多信息,请看他们的博客。

对于新用户来说,该模型可能会令人望而生畏,但它本质上是一个序列到序列转换器模型,已经在几个不同的语音处理任务上进行了训练。这些任务被输入到 Transformer 模型的编码器-解码器结构中,这使得 Whisper 可以取代传统语音处理管道的几个阶段。

换句话说,因为它专注于联合表示多个任务,所以它可以在一个模型中学习各种不同的处理步骤!

这很好,因为我们现在可以使用一个模型来完成所有必要的处理。下面,我们将导入我们的 Whisper 模型:

# Just two lines of code to load in a Whisper model!
import whisper
whisper_model = whisper.load_model("tiny")

然后,我们迭代我们的 YouTube URLs,下载音频,最后通过我们的 Whisper 模型传递它们,以便生成转录:

# Infer all texts
texts = []
for url in video_urls[:100]:
    path = YouTube(url).streams.filter(only_audio=True)[0].download(filename="audio.mp4")
    transcription = whisper_model.transcribe(path)
    texts.append(transcription["text"])

就是这样!我们现在有 Kurzgesagt 的 100 个视频的转录本。

注意:我选择了tiny型号,因为它的速度和准确性,但是还有更准确的型号,你可以在 Whisper 中使用,值得一试。

4.抄本处理

BERTopic 将主题建模作为一项集群任务,结果是将单个文档分配给单个主题。为了避免这一点,我们可以将我们的抄本分成句子,并在这些句子上运行 BERTopic:

from nltk.tokenize import sent_tokenize

# Sentencize the transcripts and track their titles
docs = []
titles = []
for text, title in zip(texts, video_titles):
    sentences = sent_tokenize(text)
    docs.extend(sentences)
    titles.extend([title] * len(sentences))

我们不仅有更多的数据来训练,而且还可以创建更精确的主题表示。

注意:在 BERTopic 中可能会有也可能没有主题发布的特性…

5.贝尔托皮奇

BERTopic 是一种主题建模技术,它专注于模块化、透明性和人工评估。它是一个框架,允许用户在一定范围内构建自己的定制主题模型。

BERTopic 的工作原理是遵循聚类和主题提取的线性管道:

SBERT -> UMAP -> HDBSCAN ->计数矢量器-> c-TF-IDF-->(可选)MMR

在流水线的每一步,它对之前的所有步骤几乎不做任何假设。例如,c-TF-IDF 表示不关心使用哪个输入嵌入。BERTopic 的这一指导思想允许子组件被轻松地替换掉。因此,您可以随心所欲地构建您的模型:

通过定义嵌入模型、降维算法、聚类模型以及 c-TF-IDF 提取,构建您自己的主题模型。

尽管我们只需要几行代码就可以使用 BERTopic,但生成我们的嵌入是值得的,这样我们就可以在以后需要重新生成它们时多次使用它们:

from sentence_transformers import SentenceTransformer

# Create embeddings from the documents
sentence_model = SentenceTransformer("paraphrase-multilingual-mpnet-base-v2")
embeddings = sentence_model.encode(docs)

虽然 Kurzgesagt 的内容是英文的,但可能会有一些非英文术语,所以我选择了多语言句子转换器模型。

生成嵌入后,我想稍微调整一下子模型,以便更好地适应我们的数据:

from bertopic import BERTopic
from umap import UMAP
from hdbscan import HDBSCAN
from sklearn.feature_extraction.text import CountVectorizer

# Define sub-models
vectorizer = CountVectorizer(stop_words="english")
umap_model = UMAP(n_neighbors=15, n_components=5, min_dist=0.0, metric='cosine', random_state=42)
hdbscan_model = HDBSCAN(min_cluster_size=20, min_samples=2, metric='euclidean', cluster_selection_method='eom')

# Train our topic model with BERTopic
topic_model = BERTopic(
    embedding_model=sentence_model,
    umap_model=umap_model,
    hdbscan_model=hdbscan_model,
    vectorizer_model=vectorizer
).fit(docs, embeddings)

现在我们已经拟合了 BERTopic 模型,让我们来看看它的一些主题。为此,我们运行topic_model.get_topic_info().head(10)来获得最常见主题的数据框架:

我们可以看到关于食物、细胞、星系和更多的话题!

6.可视化主题

尽管该模型发现了一些有趣的主题,但手动浏览这些主题似乎需要大量的工作。相反,我们可以使用一些可视化技术,使它变得简单一些。

首先,生成一些更好看的标签可能是值得的。为此,我们将使用generate_topic_labels生成我们的主题标签。

使用“generate_topic_labels ”,你可以为你的用例生成更好看的标签。

我们想要的是前 3 个单词,带一个,分隔符,我们对主题前缀不太感兴趣。

# Generate nicer looking labels and set them in our model
topic_labels = topic_model.generate_topic_labels(nr_words=3,
                                                 topic_prefix=False,
                                                 word_length=15,
                                                 separator=", ")
topic_model.set_topic_labels(topic_labels)

现在,我们准备执行一些有趣的可视化。第一关,.visualize_documents!该方法旨在在 2D 空间中交互地可视化文档及其对应的文档:

# Manually selected some interesting topics to prevent information overload
topics_of_interest = [33, 1, 8, 9, 0, 30, 27, 19, 16, 
                      28, 44, 11, 21, 23, 26, 2, 37, 34, 3, 4, 5,
                      15, 17, 22, 38]

# I added the title to the documents themselves for easier interactivity
adjusted_docs = ["<b>" + title + "</b><br>" + doc[:100] + "..." 
                 for doc, title in zip(docs, titles)]

# Visualize documents
topic_model.visualize_documents(
    adjusted_docs, 
    embeddings=embeddings, 
    hide_annotations=False, 
    topics=topics_of_interest,
    custom_labels=True
)

我们可以在 Kurzgesagt 的视频中找到一些主题。

从上面的图像中可以看出,我们有很多不同的话题,从恐龙和气候变化到细菌甚至蚂蚁!

7.每个视频的主题

因为我们已经将每个视频分成句子,所以我们可以对每个视频的主题分布进行建模。我最近看到一个视频,叫做

“如果超级火山爆发会发生什么?”

让我们来看看视频中有哪些主题:

# Topic frequency in ""What Happens if a Supervolcano Blows Up?""
video_topics = [topic_model.custom_labels_[topic+1] 
                for topic, title in zip(topic_model.topics_, titles) 
                if title == "What Happens if a Supervolcano Blows Up?" 
                and topic != -1]
counts = pd.DataFrame({"Topic": video_topics}).value_counts(); countstopics_per_class = topic_model.topics_per_class(docs, classes=classes)

正如所料,它似乎主要与火山爆发有关,但也涉及一般的爆炸。

8.主题分布

在即将到来的 BERTopic v0.13 版本中,有可能近似任何文档的主题分布,而不管其大小如何。

该方法的工作原理是在文档上创建一个滑动窗口,并计算每个主题的窗口相似度:

在令牌级别生成多主题分配的过程。

我们可以为所有文档生成这些分布,方法是运行以下代码,并确保我们在令牌级别上计算分布:

# We need to calculate the topic distributions on a token level
(topic_distr, 
topic_token_distr) = topic_model.approximate_distribution(
                                      docs, calculate_tokens=True
)

现在我们需要选择一段文本来为主题建模。为此,我认为探索模型如何处理 Kurzgesagt 结尾的 Brilliant 广告会很有趣:

通过免费试用 brilliant premium,您可以探索 brilliant 提供的一切。

我们输入文档并运行我们可视化:

# Create a visualization using a styled dataframe if Jinja2 is installed
df = topic_model.visualize_approximate_distribution(docs[100], topic_token_distr[100]); df

正如我们所看到的,它似乎选择了关于辉煌和会员资格的话题,这在这种情况下似乎是有意义的。

有趣的是,使用这种方法,我们可以考虑到不仅每个文档有多个主题,甚至每个标记有多个主题!

感谢您的阅读!

如果你和我一样,对人工智能、数据科学或心理学充满热情,请随时在 LinkedIn 上添加我,或者在 Twitter 上关注我。你也可以在我的 个人网站 上找到我的一些内容。这几天在试 乳齿象 如果有兴趣转行。

所有未注明出处的图片均由作者创作

使用 oneAPI 对英特尔 Arc Xe 矩阵扩展(XMX)进行编程

原文:https://towardsdatascience.com/utilizing-intel-arc-xe-matrix-extensions-xmx-using-oneapi-bd39479c9555

使用 joint_matrix API 推动最新的英特尔加速技术

图片由作者提供

介绍

随着英特尔 Arc 图形桌面 GPU 的发布,开发人员有了一些新的硬件加速器选项。作为一名英特尔软件架构师和性能发烧友,我首先想到的总是如何使用新硬件更快地解决我的问题。对于 Arc,我首先想尝试的是英特尔 Xᵉ矩阵扩展(XMX)硬件及其专用矩阵引擎。

为什么这很重要?

张量运算是深度学习工作负载的核心。英特尔 XMX 的基本加速功能之一是执行矩阵运算的专用硬件,更高级的张量运算分解成矩阵运算。对于大多数 AI 最终用户来说,Tensorflow 和 PyTorch 将是我们使用这种硬件的软件中的级别。然而,像我一样的另一类用户/开发人员也在考虑这个问题,并想,我如何能直接对这个新硬件进行编程,并将其用于其他目的?

oneAPI 联合矩阵

和大多数硬件一样,有几种方法可以为 XMX 加速器编程。可以写 GPU 汇编,也可以用 GPU 内函数。对于你们这些勇敢的人,我建议你们参考 oneAPI GPU 优化指南作为起点。我想尝试更简单的方法。对我们来说幸运的是,有一个实验性的 oneAPI 扩展 joint_matrix ,它允许我们使用更高级别的 API 对硬件进行编程。

除了支持英特尔硬件,joint_matrix C++ API 还允许我们在各种硬件上执行矩阵运算。从关节 _ 矩阵简介:

这个接口意在统一不同的张量硬件:CPU 中的 Intel AMX,Habana Gaudi 和 Goya 张量和 gemm 内核,Nvidia TPUs,IBM Power MMA。所有这些硬件都提供了低级内部函数或汇编来访问和执行矩阵运算。我们的目标是提供一个统一的接口,这个接口是可移植的,但也能从这些不同硬件所能提供的最大性能中获益。

有趣的部分:测试英特尔 Arc A750

我有一个全新的英特尔 Arc A750 卡,我刚刚将它放入我的个人英特尔 Alder Lake Core i9–12900 KF 外星人 R13 系统。我碰巧也在使用 Windows,所以如果您使用 Linux 或 WSL,下面的说明可能会略有不同。

图片由作者提供

分解联合矩阵矩阵乘法的例子

我的目标只是用一些简单的矩阵运算来锻炼硬件。我从英特尔 llvm 测试套件中的开始,它使用 bfloat16 运行硬件加速矩阵乘法,并确保输出与使用简单 CPU 矩阵乘法相同。

我稍微修改了一下测试,以输出矩阵乘法运行在哪个加速器硬件上。下面是用于执行启用了 joint_matrix 的矩阵乘法的代码片段:

以下是代码中需要注意的一些高级内容:

  • 第 1–11 行:big_matrix 类允许我们表示任意大小的矩阵。
  • 第 23–27 行:设备选择器显示了算法运行在哪个加速器上

由于大矩阵并不总是适合硬件,所以矩阵乘法操作是通过将要相乘的两个矩阵分解成子组,然后将这些子组相乘的结果累加成输出矩阵的适当部分来执行的。操作与我们做简单的矩阵乘法时相同,只是顺序略有不同,因为我们在移动到下一行之前不会遍历整个列空间。

矩阵乘法如何发生的核心分解如下:

  • 第 36 行:parallel_for 基于二维 nd_range 分割工作——这就是我们如何在矩阵空间中行走。
  • 第 49–56 行:sub_a、sub_b、sub_c 被初始化。由于硬件无法将整个矩阵保存在内存中,因此该算法使用 joint_matrix API 将部分矩阵加载到硬件加速器+寄存器中。sub_a 和 sub_b 是被相乘的矩阵的片段,sub_c 是我们的目标输出矩阵
  • 第 58 行:使用 joint_matrix_fill API 不从内存加载值,而是直接将寄存器初始化为一个值。在这种情况下,我将寄存器初始化为 0。
  • 第 64–71 行:载入矩阵的部分以相乘并累加到我们的输出矩阵中
  • 第 72 行:使用 XMX 加速器,使用 sub_a 和 sub_b 作为输入,sub_c 作为目标,执行矩阵乘法和加法
  • 第 74–77 行:将这部分矩阵计算的进行中的输出值存储回内存

作为参考,下面是我完整的joint _ matrix _ bfloat 16 _ modified . CPP

测试加速器矩阵乘法

由于这是对 oneAPI 的实验性扩展,因此需要英特尔 oneAPI DPC++/C++编译器。我使用的是来自英特尔 oneAPI 基础工具包 2022.3 版本的最新版本。

该功能自 2022.1 版本起就已启用,但一些命名空间已更新。例如,以下命名空间从前者更新为后者:

sycl::ext::intel::experimental::bfloat16
sycl::ext::oneapi::experimental::bfloat16

要编译此示例,请在安装英特尔 oneAPI 基础工具包并遵循环境配置步骤后运行以下命令:

> icx /EHsc /fsycl joint_matrix_bfloat16_modified.cpp

编译完代码后,我们只需运行可执行文件:

> joint_matrix_bfloat16_modified.exe
Running on device: Intel(R) Arc(TM) A750 Graphics
Elapsed time in milliseconds (accelerated): 142 ms
Elapsed time in milliseconds (reference): 2118 ms
passed

我们可以看到,使用英特尔 Arc GPU 在 142 毫秒内执行了矩阵乘法,非加速版本在 CPU 上运行了 2118 毫秒。请注意,如果您尝试在支持矩阵运算的硬件上运行加速版本,API 定义的当前行为是报告由于缺少支持的矩阵加速器硬件而导致的故障。这可以防止用户在不知不觉中使用较慢的回退矩阵实现,而不是硬件加速版本。

启用多个加速器

joint_matrix API 不仅仅是为了抽象的可移植性而设计的。最新的英特尔 DPC++编译器支持英特尔 XMX、英特尔高级矩阵扩展(AMX)和 NVIDIA Tensor 内核。对于那些不熟悉的人,AMX 是用于矩阵乘法加速的新 x86 指令集,它利用了内置于即将推出的第四代英特尔至强可扩展处理器中的硬件。如果你对张量核感兴趣,这里有一个例子你可以编译运行这里。它确实需要开源的英特尔 DPC++编译器和 NVIDIA 后端支持,以及安装 NVIDIA CUDA,所以我将把它留到一个单独的帖子中。

有关 joint_matrix API 启用的其他功能的更多详细信息,请参见 API 文档:

https://github.com/intel/llvm/blob/sycl/sycl/doc/extensions/experimental/sycl_ext_oneapi_matrix.asciidoc

结论

新的硬件能力意味着新的编程抽象。这些抽象可能存在于许多层次。虽然 joint_matrix 是一种直接对硬件编程的方法,但在未来的帖子中,我将讨论 oneAPI 库和 TensorFlow 和 PyTorch 等流行 AI/ML 框架的英特尔实施如何利用 XMX 和 AMX 等矩阵加速器。

如果您已经做到这一步,您可能像我一样希望对硬件进行细粒度的控制,这就是为什么像 joint_matrix 这样的 API 令人兴奋的原因。joint_matrix API 是可用的,可以帮助您利用一些新的 matrix 硬件。我鼓励您下载工具链,尝试一下 API,并提供反馈来帮助构建这个令人兴奋的跨供应商 matrix API。

如果你想看看我在看什么科技新闻,你可以在 Twitter 上关注我。

Tony 是英特尔的一名软件架构师和技术宣传员。他开发过多种软件开发工具,最近领导软件工程团队构建了数据中心平台,实现了 Habana 的可扩展 MLPerf 解决方案。

英特尔、英特尔标志和其他英特尔标志是英特尔公司或其子公司的商标。

VAE:可变自动编码器——如何利用神经网络生成新图像

原文:https://towardsdatascience.com/vae-variational-autoencoders-how-to-employ-neural-networks-to-generate-new-images-bdeb216ed2c0

神经网络

VAEs 概述,并附有一个完整的 Python 示例,教您如何自己构建一个

可变自动编码器(VAE)。图片由作者提供。

介绍

本文将带您了解变型自动编码器(VAE) ,它与著名的 GANs(生成对抗网络)一起,属于更广泛的一组深度生成模型

与 GAN 不同,VAE 使用自动编码器架构,而不是一对发生器-鉴别器网络。因此,VAEs 中使用的思想应该相对容易理解,尤其是如果您过去使用过自动编码器的话。

如果你想了解我未来关于神经网络的文章,比如 GANs ,请随意订阅电子邮件通知

内容

  • VAE 在机器学习算法领域的地位
  • VAEs 的结构及其工作原理的解释
  • 一个完整的 Python 示例,展示了如何使用 Keras/Tensorflow 构建 VAE

VAE 在机器学习算法领域的地位

下图是我整理最常见的机器学习算法的尝试。不过,这并不是一件容易的事情,因为我们可以根据算法的底层结构或它们旨在解决的问题,在多个维度上对它们进行分类。

我试图将这两个方面都考虑进去,这使我将神经网络归入它们自己的类别。虽然我们通常以监督的方式使用神经网络,但必须承认一些例子,如自动编码器,更像是非监督/自我监督算法。

尽管变型自动编码器(VAE) 与 GANs 有着相似的目标,但它们的架构更接近于其他类型的自动编码器,如欠完整自动编码器。因此,您可以通过点击下方的互动图表中的自动编码器组找到 VAEs👇。

机器学习算法分类。由作者创建的互动图表。

如果你喜欢数据科学和机器学习 ,请 订阅 获取我的新文章的邮件。如果你不是中等会员,可以在这里 加入

VAEs 的结构及其工作原理的解释

让我们先来分析一个标准的欠完整自动编码器 (AE)的架构,然后再深入研究使 VAEs 不同的元素。

未完成 AE

下面是一个典型 AE 的图示。

欠完整自动编码器架构。图片由作者、使用 AlexNail 的 NN-SVG 工具创建。

欠完成 AE 的目标是有效地将来自输入数据的信息编码到低维潜在空间(瓶颈)。我们通过确保使用解码器以最小损失重建输入来实现这一目标。

请注意,在训练过程中,当我们试图发现“最佳”潜在空间的参数值时,我们将同一组数据传递到输入和输出层。

变分 AE

现在,让我们通过分析其架构来看看 VAE 与不完整的 AE 有何不同:

VAE 建筑。图片由作者提供。

我们注意到 VAE 的潜在空间不是由点向量(单个节点)组成的。相反,输入被映射到正态分布,其中 Zμ和 Zσ是均值和方差,即在模型训练期间学习的参数。

同时,从具有均值 Zμ和方差 Zσ的分布中采样潜在向量 Z,并将其传递给解码器以获得预测输出。

重要的是要理解,通过设计,VAE 的潜在空间是连续的,这使我们能够从它的任何部分取样以生成新的输出(例如,新的图像),使 VAE 成为生成模型

正规化的需要

将输入编码到一个分布中,只让我们走了一半,就创建了一个适合生成“有意义的”输出的潜在空间。

然而,我们可以通过添加一个正则化项来实现所需的正则性,该正则化项表示为 Kulback-Leibler 散度 (KL 散度)。我们将在稍后的 Python 部分详细讨论它。

关于潜在空间的直觉

我们可以用下面的例子来想象信息是如何在潜在空间中传播的。

思考正则化连续潜在空间的直观方式。图片由作者提供。

如您所见,将数据映射为单个点并不能训练模型理解这些点之间的相似性/差异。因此,我们不能使用这样的空间来生成新的“有意义的”数据。

在变分自动编码器的情况下,我们将数据映射为分布并正则化潜在空间,这给了我们分布之间的“梯度”“平滑过渡”。因此,当我们从这样的潜在空间中采样一个点时,我们生成与训练数据非常相似的新数据

一个完整的 Python 例子,展示了如何用 Keras/Tensorflow 构建 VAE

最后,是时候建立我们自己的 VAE 了!

设置

我们需要以下数据和库:

让我们导入所有的库:

上面的代码打印了本例中使用的包版本:

Tensorflow/Keras: 2.7.0
numpy: 1.21.4
matplotlib: 3.5.1
graphviz: 0.19.1
plotly: 5.4.0

接下来,我们加载 MNIST 手写数字数据并显示前十位数字。请注意,我们将仅在可视化中使用数字标签(y_train,y_test ),而不用于模型训练。

MNIST 数据集的前十位数字。图片由作者提供。

如您所见,我们在训练集中有 60,000 张图像,在测试集中有 10,000 张图像。请注意,它们的尺寸是 28 x 28 像素。

设置的最后一步是通过将图像从 28x28 调整为 784 来拼合图像。

通常,我们会使用 卷积层 而不是展平图像,尤其是在处理较大的图片时。然而,我想保持这个例子简单,因此使用密集层和平面数据,而不是卷积层

New shape of X_train:  (60000, 784)
New shape of X_test:  (10000, 784)

建立变分自动编码器模型

我们将从定义一个函数开始,这个函数将帮助我们从潜在空间分布 Z 中取样。

这里我们采用了一个重新参数化技巧,它允许损失通过均值 (z 均值)和方差 (z 对数西格玛)节点反向传播,因为它们是确定性的。

同时,我们通过添加一个非确定性参数ε来分离采样节点,该参数是从标准正态分布中采样的。

现在,我们可以定义编码器模型的结构。

上面的代码创建了一个编码器模型,并打印了它的结构图。

VAE 模型编码器部分的示意图。图片由作者提供。

请注意我们如何将来自编码器-隐藏层-3 的相同输出发送到Z-均值Z-对数-适马中,然后在自定义λ层(Z-采样层)中重新组合它们,用于从潜在空间进行采样。

接下来,我们创建解码器模型:

上面的代码创建了一个解码器模型并打印了它的结构图。

VAE 模型解码器部分的示意图。图片由作者提供。

正如您所看到的,解码器是一个非常简单的模型,它从最新的空间获取输入,并在为 784 个输出节点生成值之前,将它们通过几个隐藏层。

接下来,我们组合编码器和解码器模型以形成变型自动编码器模型(VAE)。

如果您密切关注编码器模型中的潜在空间层,您会注意到编码器生成三组输出:Z 均值[0]、Z 对数西格玛[1]和 Z [2]。

上面的代码通过指定编码器接受名为“visible”的原始输入来链接模型。然后,在编码器[0]、[1]、[2]生成的三个输出中,我们取第三个输出(Z [2])并将其传递给解码器,解码器生成我们命名为“output”的输出。

连接编码器和解码器以构建 VAE 模型。图片由作者提供。

自定义损失功能

在训练 VAE 模型之前,最后一步是创建一个自定义损失函数并编译该模型。

正如本文前面提到的,我们将使用 KL 散度来衡量潜在空间分布和参考标准正态分布之间的损失。“ KL 损失是标准重建损失(在这种情况下,MSE)的补充,用于确保输入和输出图像保持接近。

VAE 模特培训

装配了可变自动编码器模型后,让我们在 25 个时期内对其进行训练,并绘制损失图。

随时间变化的自动编码器模型损耗。图片由作者提供。

显现潜在空间并生成新数字

由于我们的潜在空间是二维的,我们可以在潜在的 2D 平面上想象不同数字的邻域:

编码在潜在空间中的 MNIST 数字的 2D 可视化。图表作者作者

绘制潜在空间中的数字分布给了我们视觉上将不同区域与不同数字相关联的好处。

假设我们想要生成一个数字 3 的新图像。我们知道 3 位于潜在空间的顶部中间。因此,让我们选择[0,2.5]的坐标,并生成与这些输入相关联的图像。

VAE 模型产生的新数字。图片来自作者

正如所料,我们得到了一个形状非常像数字 3 的图像,因为我们从 3 所占据的潜在空间中的一个区域采样了一个向量。

现在让我们从整个潜在空间中生成 900 个新数字。

使用我们的 VAE 模型生成 900 个新数字。图片由作者提供。

从整个潜在空间生成许多图像的酷之处在于,它让我们看到不同形状之间的逐渐过渡。这证实了我们能够成功地调整我们的潜在空间。

结束语

值得注意的是,我们可以使用变分自动编码器来编码和生成比 MNIST 数字更复杂的数据。

因此,我鼓励您将我的简单教程应用到与您所在领域相关的真实世界数据中,从而更上一层楼。

为了您的方便,我在我的 GitHub 库中保存了一个 Jupyter 笔记本,其中包含了上述所有代码。

如果你想在我发表一篇关于机器学习/神经网络(例如,生成对抗网络(GAN))的新文章时得到通知,请订阅以接收电子邮件

如果你不是媒体会员,并且想继续阅读成千上万伟大作家的文章,你可以使用我下面的个性化链接加入:

https://bit.ly/3J6StZI

如果您有任何问题或建议,请随时联系我们!

干杯!🤓
索尔·多比拉斯

用 Pandera 验证你的熊猫数据框架

原文:https://towardsdatascience.com/validate-your-pandas-dataframe-with-pandera-2995910e564

确保您的数据符合您的期望

动机

在数据科学项目中,不仅测试您的功能很重要,测试您的数据以确保它们按照您的预期工作也很重要。

作者图片

在上一篇文章中,我展示了如何使用极大期望来验证您的数据。

尽管远大的期望提供了许多有用的工具,但是用远大的期望创建一个验证套件还是很复杂的。对于一个小型的数据科学项目来说,使用高期望值可能有些过头了。

这就是为什么在这篇文章中,我们将学习 Pandera,一个简单的 Python 库,用于验证 pandas 数据帧。

要安装 Pandera,请键入:

pip install pandera

介绍

为了了解 Pandera 是如何工作的,让我们从创建一个简单的数据集开始:

作者图片

想象一下这个场景。你的经理告诉你,数据集中只能有某些水果和商店,价格必须小于 4。

为了确保您的数据符合这些条件,手动检查您的数据会花费太多时间,尤其是当您的数据很大时。有没有一种方法可以自动化这个过程?

这就是潘德拉派上用场的时候。具体来说,我们:

  • 使用DataFrameSchema为整个数据集创建多个测试
  • 使用Column为每一列创建多个测试
  • 使用Check指定测试类型
SchemaError: <Schema Column(name=price, type=DataType(int64))> failed element-wise validator 0:
<Check less_than: less_than(4)>
failure cases:
   index  failure_case
0      3             4

在上面的代码中:

  • "name": Column(str, Check.isin(available_fruits))检查列name是否是字符串类型,以及列name的所有值是否在指定的列表中。
  • "price": Column(int, Check.less_than(4))检查列price中的所有值是否都属于类型int并且都小于 4。
  • 因为列price中的值并非都小于 4,所以测试失败。

在这里找到其他内置Checks方法

海关支票

我们还可以使用lambda创建自定义检查。在下面的代码中,Check(lambda price: sum(price) < 20)检查列price的总和是否小于 20。

模式模型

当我们的测试很复杂时,使用 dataclass 可以使我们的测试看起来比使用字典干净得多。幸运的是,Pandera 还允许我们使用数据类而不是字典来创建测试。

验证装饰器

检查输入

现在我们知道了如何为我们的数据创建测试,我们如何使用它来测试我们函数的输入呢?一种简单的方法是在函数中添加schema.validate(input)

然而,这种方法使我们很难测试我们的功能。由于get_total_price的参数既是fruits又是schema,我们需要在测试中包含这两个参数:

test_get_total_price测试数据和函数。因为一个单元测试应该只测试一件事,在函数内部包含数据验证是不理想的。

Pandera 用check_input装饰器为此提供了一个解决方案。这个装饰器的参数用于验证函数的输入。

如果输入无效,Pandera 将在函数处理输入之前引发一个错误:

SchemaError: error in check_input decorator of function 'get_total_price': expected series 'price' to have type int64, got object

在处理之前验证数据非常好,因为它防止用户在处理数据上浪费大量时间。

检查输出

我们还可以使用 Pandera 的check_output装饰器来检查函数的输出:

检查输入和输出

现在你可能想知道,有没有一种方法可以同时检查输入和输出?我们可以使用装饰器check_io来实现:

列验证的其他参数

处理空值

默认情况下,如果我们测试的列中有空值,Pandera 将会抛出一个错误。如果空值是可接受的,将nullable=True添加到我们的Column类中:

处理重复

默认情况下,重复是可以接受的。要在有重复项时引发错误,请使用allow_duplicates=False:

SchemaError: series 'store' contains duplicate values: {2: 'Walmart'}

转换数据类型

coerce=True更改列的数据类型。如果强制是不可能的,Pandera 会引发一个错误。

在下面的代码中,价格的数据类型从整数更改为字符串。

name     object
store    object
price    object
dtype: object

匹配模式

如果我们想改变所有以单词store开头的列,该怎么办?

Pandera 允许我们通过添加regex=True对共享特定模式的多个列应用相同的检查:

从 YAML 文件中导出和加载

出口到 YAML

使用 YAML 文件是向不懂 Python 的同事展示您的测试的好方法。我们可以使用schema.to_yaml()在 YAML 文件中保存所有验证的记录:

schema.yml应该如下图所示:

从 YAML 装货

要从 YAML 文件加载,只需使用pa.io.from_yaml(yaml_schema):

结论

恭喜你!您刚刚学习了如何使用 Pandera 来验证数据集。由于数据是数据科学项目的一个重要方面,验证函数的输入和输出将减少后续的错误。

随意发挥,并在这里叉这篇文章的源代码:

https://github.com/khuyentran1401/Data-science/blob/master/data_science_tools/pandera_example/pandera.ipynb

我喜欢写一些基本的数据科学概念,并尝试不同的数据科学工具。你可以通过 LinkedInTwitter 与我联系。

如果你想查看我写的所有文章的代码,请点击这里。在 Medium 上关注我,了解我的最新数据科学文章,例如:

</4-pre-commit-plugins-to-automate-code-reviewing-and-formatting-in-python-c80c6d2e9f5>

如何修复值错误:未知标签类型:scikit-learn 中的“连续”

原文:https://towardsdatascience.com/valueerror-sklearn-fix-4a5fe105536a

了解是什么导致了 scikit 中连续变量的 value error-learn 以及如何消除它

照片由 Federica GiustiUnsplash 上拍摄

介绍

刚接触机器学习和 Python 编程的人在尝试使用scikit-learn包训练模型时可能会遇到一些麻烦。

最常报告的问题之一与目标变量的类型有关,当观察到不适当的值时,可能会触发特定的错误。其中一个错误是ValueError: Unknown label type: 'continuous',其完整的回溯共享如下。

Traceback (most recent call last):
  File "test.py", line 14, in <module>
    clf.fit(train_X, train_Y)
  File "/usr/local/lib/python3.7/site-packages/sklearn/linear_model/_logistic.py", line 1347, in fit
    check_classification_targets(y)
  File "/usr/local/lib/python3.7/site-packages/sklearn/utils/multiclass.py", line 183, in check_classification_targets
    raise ValueError("Unknown label type: %r" % y_type)
ValueError: Unknown label type: 'continuous'

在今天的简短教程中,我们将尝试重现该错误,理解为什么会出现这个异常,并最终展示如何处理它。

重现错误

首先,让我们创建一些样本数据,我们将使用这些数据来训练一个带有scikit-learn的示例逻辑回归模型。注意目标变量是连续的

import numpy as np
from sklearn.linear_model import LogisticRegression train_X = np.array([
    [100, 1.1, 0.8],  
    [200, 1.0, 6.5],  
    [150, 1.3, 7.1],  
    [120, 1.2, 3.0],  
    [100, 1.1, 4.0],  
    [150, 1.2, 6.8],
])train_Y = np.array([1.0, 2.1, 5.6, 7.8, 9.9, 4.5])clf = LogisticRegression()
clf.fit(train_X, train_Y)

现在,如果我们尝试执行上述代码片段,标准输出会报告以下错误:

Traceback (most recent call last):
  File "test.py", line 14, in <module>
    clf.fit(train_X, train_Y)
  File "/usr/local/lib/python3.7/site-packages/sklearn/linear_model/_logistic.py", line 1347, in fit
    check_classification_targets(y)
  File "/usr/local/lib/python3.7/site-packages/sklearn/utils/multiclass.py", line 183, in check_classification_targets
    raise ValueError("Unknown label type: %r" % y_type)
ValueError: Unknown label type: 'continuous'

本质上,错误告诉我们目标变量的类型是连续的,这与我们试图拟合的特定模型不兼容(即LogisticRegression)。

消除错误

对于许多初学者来说,逻辑回归实际上用于分类而不是回归的事实是一个惊喜。我认为这是完全可以理解的,因为新来的人可能会对车型名称感到困惑。

分类器(即应该执行分类的模型)期望标签(也称为目标变量)是分类的而不是连续的(这是模型在执行回归时的期望)。

因此,在使用逻辑回归(或几乎任何分类器)时,您的第一个选择是执行标签编码。但是请注意,这可能不是最合适的解决方案,答案实际上取决于您的特定用例!所以对此要有所保留。

在标签编码之前,我们可以使用utils.multiclass.type_of_target方法验证目标变量的类型。

>>> import utils
>>> print(utils.multiclass.type_of_target(train_Y))
'continuous'

现在,我们可以使用scikit-learn中的[LabelEncoder](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.LabelEncoder.html)来执行标签编码,以便规范化目标变量。

from sklearn import preprocessinglabel_encoder = preprocessing.LabelEncoder()
train_Y = label_encoder.fit_transform(train_Y)

现在我们可以验证新编码的目标变量是多类类型:

>>> import utils
>>> print(utils.multiclass.type_of_target(train_Y))
'multiclass'

现在,我们可以使用编码的目标变量来训练我们的逻辑回归模型,没有任何问题:

from sklearn import preprocessing
from sklearn.linear_model import LogisticRegression# Label encoding
label_encoder = preprocessing.LabelEncoder()
train_Y = label_encoder.fit_transform(train_Y)# Model training
clf = LogisticRegression()
clf.fit(train_X, train_Y)

但是在使用标签编码之前,您可能仍然需要考虑逻辑回归是否适合您的特定用例。如果您想要对数据执行回归,那么您可能必须探索一些替代选项。

要了解更多关于机器学习环境中分类和回归问题之间的区别,请务必阅读我在 Medium 上的一篇旧文章,分享如下。

https://medium.com/analytics-vidhya/regression-vs-classification-29ec592c7fea

最后的想法

在今天的简短教程中,我们讨论了由scikit-learn分类器引发的ValueError: Unknown label type: 'continuous'错误,这些分类器根据特定模型预期的标签类型,在提供的目标变量中观察到无效值。

我们重现了这个错误,讨论了最初为什么会出现这个错误以及如何消除它。

成为会员 阅读媒介上的每一个故事。你的会员费直接支持我和你看的其他作家。你也可以在媒体上看到所有的故事。

https://gmyrianthous.medium.com/membership

相关文章你可能也喜欢

时间序列的变量缩减技术

原文:https://towardsdatascience.com/variable-reduction-techniques-for-time-series-646743f726d4

L1 惩罚和置换特征分数

照片由李宗盛Unsplash 上拍摄

在预测建模中,结果取决于添加到模型中的输入。在时间序列中,选择模型输入的主要问题是如何最好地表示一个序列的历史来预测它的未来。我们可以将一个序列分解成它的基本组成部分:趋势、季节性和残差,但是对于复杂的数据集,这可能很难做到完全正确。以太阳黑子数据集为例,该数据集可以在 Kaggle 上通过公共领域许可获得。它测量自 18 世纪以来每月观察到的太阳黑子,当线性分解成上述成分时,看起来像这样:

作者图片

上面的图表显示了原始系列,这很好。然而,下面的第二张图显示了它的“趋势”,但它看起来不像是我见过的趋势,也不像是一个很容易建模的趋势。第三张图更糟糕,以一种几乎无法解释的方式显示了“季节性”。诚然,这是一个大系列,更复杂的分解可能更合适,但问题仍然是:当试图预测这个系列时,不容易决定纳入哪些因素。

一种选择是添加我们认为可能相关的任何内容,然后,使用 L1 正则化或类似于递归特征消除的技术,我们将变量缩小到最重要的子集。这就是这个例子中所做的,你可以在 GithubRead Docs上找到。我们将使用 scalecast 包来做时序分析。如果你觉得有趣,请在 GitHub 上给它一颗星。 ELI5 包也用于特性重要性评分,而 scikit-learn 用于建模。

准备和添加变量

首先,安装 scalecast 软件包:

pip install scalecast

然后,我们创建预测器对象,这是一个模型包装器和结果容器:

df **=** pd**.**read_csv(
  'Sunspots.csv',
  index_col**=**0,
  names**=**['Date','Target'],
  header**=**0,
)
f **=** Forecaster(y**=**df['Target'],current_dates**=**df['Date'])

自回归项

在时间序列中,最有效的变量通常是序列本身的过去值(滞后)。为了评估在序列的历史中我们可以找到多少具有统计意义的滞后,通常使用自相关函数和偏自相关函数图。

作者图片

这些有趣的图表也揭示了数据集的循环性质。他们揭示了在过去 15 年或更长的时间里,统计意义上的术语可能存在。让我们添加前 120 个(10 年)期限,以及过去 20 年中间隔一年的期限。

f.add_ar_terms(120)     *# lags 1-120*
f.add_AR_terms((20,12)) *# yearly lags up to 20 years*

季节性周期

通常,月度间隔数据的季节性包括月度和季度波动。然而,在这个例子中,我们试图预测一种不符合我们日常生活中公历的自然现象。这个系列展示的周期非常不规则,也不一致。根据维基百科的说法,“太阳周期通常持续大约 11 年,从不到 10 年到超过 12 年不等。”

尽管如此,我们仍然可以添加月度和季度季节性,以及每 1 年到 20 年的周期。这当然是矫枉过正,但这就是为什么我们将使用变量缩减技术来过滤这些附加特征中哪些可能是噪声,哪些是信号。所有这些季节术语都使用傅立叶变换。

f.add_seasonal_regressors('month','quarter',raw=False,sincos=True)
*# 12-month cycles from 12 to 288 months*    
for i in np.arange(12,289,12):
    f.add_cycle(i)

趋势

对于趋势,我们可以保持简单。我们添加了年份变量,以及一个名为“t”的变量,它将从 1 开始计数,直到每次观测的序列长度,也称为时间趋势。

f**.**add_time_trend()
f**.**add_seasonal_regressors('year')

在加上所有这些变量后,我们剩下 184 个预测因子。数据集的大小是 3,265。有时,目标变量的数量是数据集大小的平方根(在本例中约为 57),但这方面没有硬性规定。无论哪种方式,我们添加的所有功能都不可能有助于预测(很难确切知道哪些功能会有帮助,哪些不会)。在其他条件相同的情况下,更节俭的模型、投入更少的模型更受青睐。

消除变量

使用 L1 正则化消除

我们探索的第一个技术是一个简单和常见的:L1 正则化与套索回归模型。L1 正则化向线性模型中的回归量添加了惩罚(α),因此其成本函数可以写成:

作者图片

其中 x 为变量, p 为变量总数, w 为给定变量的系数, M 为观察总数。通俗地说,每个系数的权重都是用一个因子α来改变的,这个因子是根据什么最能优化模型来进行预测而设定的。在这个框架下,线性模型中最不重要的变量被正则化掉,它们的系数取值为 0。我们在分析中运行这个模型,并检查哪些变量的系数权重为 0;不成为我们的缩减子集的变量。alpha 参数是通过对数据的验证切片进行网格搜索来设置的,默认情况下,所有输入都是按比例缩放的,这在采用这种技术时是必要的。

f.reduce_Xvars(
    method = 'l1',
    overwrite = False,
    dynamic_testing = False,
)
lasso_reduced_vars = f.reduced_Xvars[:]

在我们的数据集中,这将变量从 184 个减少到 34 个。这是一个相当戏剧性的下降!

消除使用排列特征的重要性

下一个消除技术更复杂,涉及到来自 ELI5 的置换特征重要性(PFI)。PFI 背后的想法是使用样本外数据对任何 scikit-learn 估计器进行训练和评分,但在每次评分迭代中,对单个特征值进行加扰,使其具有随机性。对于模型中的每个输入,这都要重复几次。然后,测量单个输入被随机化时算法的平均准确度变化,并且在特征重要性排序中,最大程度降低模型准确度的特征得分较高。它与提供给它的特定算法无关,这使得它可以推广到许多模型类。它的缺点包括,它逐个测试每个变量,因此在评估特性时,它错过了变量之间的交互。出于同样的原因,两个高度相关的变量可能会得到较低的重要性分数,即使它们对模型都很重要。这种情况在时间序列中经常发生,这就是为什么 scalecast 在决定下一个要删除的功能之前会对原始分数进行调整。参见文档

结合 PFI 进行特征消除,我们使用以下流程:

  1. 使用完整的变量集训练给定的模型类,并使用样本外验证数据切片上的网格搜索来调整其超参数
  2. 使用步骤 1 中的最佳超参数重新训练模型,并监控数据验证部分的错误
  3. 使用 PFI 对特征进行评分
  4. 删除得分最低的功能
  5. 从步骤 2 开始重复,直到除了一定数量的变量之外,所有变量都被删除
  6. 选择在验证数据中返回最低误差的简化变量集

作者图片

为了加速评估(因为这是一种迭代和计算昂贵的技术),我们可以使用非动态测试过程,这意味着误差度量相当于一步预测的平均值,并且一旦除了值(57)的平方根值之外的所有值都被丢弃,就停止。在代码中,它看起来像:

f.reduce_Xvars(
    method = "pfi",
    estimator = "mlr",
    keep_at_least = "sqrt",
    grid_search = True,
    dynamic_testing = False,
    overwrite=False,
)

同样,由于 ELI5 集成的灵活性,任何 scikit-learn 估计器都可以用于此过程。上面的代码使用了多元线性回归,但是我们也探索了 k-最近邻、梯度提升树和支持向量机。如果我们选择使用,还有许多其他模型可供使用。我们可以通过检查丢弃每个变量后获得的样本外误差来绘制结果:

作者图片

对于所有这些估计量,误差开始时减小,然后随着变量的减少而增加,除了 SVR,它一直减小到最后。总的来说,GBT 和支持向量回归估计表现最好,GBT 略好于支持向量回归。每个人找到的最佳变量数量如下:

the mlr model chose 146 variables
the knn model chose 98 variables
the gbt model chose 136 variables
the svr model chose 61 variables

验证选择

这些变量子集是如何泛化的?

由于 GBT 模型类通常在数据验证集上表现最好,我们可以使用从所探索的各种技术中找到的每个变量子集来重新训练该模型类。根据这些结果,我们将基于在单独的样本外数据集(测试集)上的性能来评估我们认为是最佳缩减变量集的变量。这一次,我们对模型进行动态测试,以更好地了解它们在整个样本外期间的表现(在整个示例中,我们在验证和测试集中使用了 120 个观察值):

作者图片

作者图片

检查这些结果的主要收获是,尽管具有 GBT 的 136 个要素的缩减数据集在验证集上得分最高,但具有 98 个要素的 k-最近邻模型的缩减变量集在测试集上表现最佳,这表明可以从一个模型类中获得缩减变量子集,并成功集成到另一个模型类中。如果愿意的话,我们可以将缩减后的数据集保存起来用于未来的预测任务:

Xvars_df = f.export_Xvars_df()
Xvars_df.set_index("DATE",inplace=True)
reduced_dataset = Xvars_df[knn_reduced_vars]

哪些变量最常被忽略?

最后,一个有趣的练习是看看在每一个被探索的技术中,哪些变量是最常被忽略的。我们可以写一些代码来实现,这就是我们得到的结果:

最常丢弃的变量:

  • quartercos(所有 5 种技术)
  • AR86(所有 5 种技术)
  • AR46(所有 5 种技术)

我们的季度变量中有一个,但不是两个,每次都被删除,还有第 86 和第 46 系列的滞后。从这部分分析中还有许多其他有趣的见解。查看文章开头链接的笔记本,了解完整的结果。

结论

变量选择是时间序列模型优化的一个重要部分,但要知道在任何给定的模型中包含哪些变量是最重要的并不总是容易的。在本例中,我们向模型中添加了比我们认为必要的更多的变量,并探索了使用 lasso 估计器和 L1 正则化减少变量,以及使用四个 scikit-learn 模型类和利用置换特征重要性的迭代特征消除。在 Github 上查看 scalecast,如果你觉得有趣就给它一颗星吧!

Java 中的变量和数据类型

原文:https://towardsdatascience.com/variables-and-data-types-in-java-76f7de62d1b

用于数据科学的 Java 第 1 部分

卡斯帕·卡米尔·鲁宾在 Unsplash 上的照片

Java 作为一种语言,通常不会出现在数据科学的学习语言列表中。这种争论经常留给那些认为 R 或 Python 是最好的语言的人。但是,由于数据科学涵盖了广泛的领域和应用,这意味着一种语言不能总是做所有的事情,或者一些语言在其他领域可能更有用。因此,Java 是在数据科学的一些子领域中工作得最好的语言之一,由于与 Hadoop 的集成,它特别关注大数据的应用,由于它的可移植性,它还关注物联网基础设施和应用。事实上,除了数据科学之外,它还存在于许多不同的领域。如果您想要一种用于大数据或物联网应用程序的语言,或者一种超越数据科学的语言,这将使您很容易学习!因此,如果你有兴趣学习它,请继续下面的内容,我们将介绍 Java 中变量和数据类型的基础知识。

印刷

在学习如何创建和使用变量之前,首先要学习的是如何输出结果。这清楚地表明了我们在做什么,语言是如何表现的,这在开始学习一门新语言时是很重要的。虽然在 Python 中这是一个相对简单的命令,但在 Java 中,要将一些内容打印到控制台,我们必须使用:

System.out.println();

它是计算机在屏幕上显示某些东西的指令。

分解一下,这是一个系统命令(System),它通过在屏幕上打印一个新行(println())来将文本作为输出(out),在 Java 中,要结束任何一行代码,我们需要添加一个;。因此,打印出我们想要的任何东西!

例如:

System.out.println(2+3);

会打印出 5!

创建和使用变量

既然我们知道了如何打印出我们所做的任何事情的结果,那么我们就可以讨论如何创建和使用变量。Java 中的每个变量都有一个名字和值,就像其他语言一样。虽然变量名不会改变,但变量值可以改变。

与 Python 等其他语言相比,Java 的主要区别在于 Java 是静态类型的。这意味着您必须定义变量将存储什么类型的信息,如整数、字符串或其他数据类型。请小心,因为这一点以后无法更改!

为了在 Java 中编写一个变量,我们可以声明它,声明它的名字和它包含的数据类型。例如,我们可以声明一个名为 passengers 的变量,它只包含整数值。

int passengers;

这告诉计算机创建一个名为 passengers 的变量,以后我们只给它赋值整数。

然后当我们给变量赋值时,我们初始化变量。首先,我们可以指定 0 个乘客值:

int passengers;passengers = 0;

所以我们现在已经用定义了一个名为 passengers 的变量,告诉计算机我们将只给它分配整数值,并且通过给变量赋值 0 来初始化

一旦变量被初始化,它就可以被操作或编辑成我们想要的样子。在这种情况下,因为我们已经定义了一个实例化的整数变量,所以我们可以执行在其他语言中可以执行的所有标准数学操作。例如:

int passengers;passengers = 0;passengers = passengers + 5;passengers = passengers - 3;passengers = passengers - 1 + 5;

当打印到控制台时,将显示:

System.out.println(passengers);#out:
6

用线串

与我们声明一个整数变量并初始化它的方式相同,我们也可以声明一个字符串变量。要声明一个字符串,我们使用:

String driver

和一个整数一样,它告诉计算机我们想要声明一个名为 driver 的变量来保存一个字符串。

然后,通过为其赋值,可以用同样的方式将实例化为,如下所示:

String driverdriver = "Hamish";

与整数一样,这使我们能够像在其他语言中一样操作字符串,并从中提取信息。例如:

// define and initialise the string
String driver;
driver = "Hamish";// We can count the number of items in the string and
// assign the value to another variableint letter = driver.length();// we can capitalise all the lettersdriverUpper = driver.toUpperCase();// and we can add strings togetherString driverFirstName = "Hamish";String driverLastName = "Blake"String driverFullName = driverFirstName + " " + driverLastNameSystem.out.print(driverFullName);#out:
Hamish Blake

我们在这里对driverFirstNamedriverLastName变量唯一不同的地方是在同一行声明了实例化了

数据类型

我们已经看到了如何创建一个字符串和一个整数,那么 Java 中还有什么其他的数据类型呢?

  • Byte ( byte):存储-128 和 128 之间的整数
  • Short ( short):存储-32,768 到 32,767 之间的整数
  • 整数(int):一个整数,最多可有 10 个数字,但有利于加速已定义的程序
  • Long ( long):一个整数,但可以比 10 位数长得多,但代价是占用更多空间,并可能降低程序速度
  • Double ( double):可以用来存储任何东西,包括整数、小数、正数或负数,但是会降低程序的速度
  • String ( String):对,一串!
  • 字符(char):存储单个字母,例如char answer = "b";
  • Boolean ( boolean):用于存储真或假(真!)

这些中的每一个都必须被声明,然后初始化,就像上面的整数和字符串一样。

命名规格

在命名这些变量时,遵守规则和惯例是很重要的。

第一条规则是名字应该以字母开头。虽然它们可以以-$开头,但不建议这样做,因为这是不好的约定。此外,名称是区分大小写的,所以无论使用什么大小写,都要确保一致。

第二条规则是,为了一致性和易读性,名称应该用小写字母来定义。这确保了单词被清楚地分开和定义。变量名中不能有空格!

第三条也是最后一条规则是,名字应该总是使用完整的单词来描述它的用途!如果可能的话,尽量限制缩写,除非它们被一致地使用并且有明确的定义,因为你永远不知道谁会阅读你的代码!

最后,虽然不一定是命名约定,但是我们可以使用//注释掉一行代码,或者注释掉符号后面的一行代码的一部分,或者我们可以在/* multi line comment*/之间添加一个多行注释。

结论

Java 是数据科学之外广泛使用的语言,但在数据科学内部,它通常用于大数据应用程序或物联网。虽然它是 R 或 Python 的大热门,但它仍然有一些用处!有了这个实用的工具,你应该能够开始学习 Java 的旅程,不管是为了好玩还是你正在做的一个新项目。祝你好运!

如果你喜欢你所读的,并且还不是 medium 会员,请使用下面我的推荐链接注册 Medium,来支持我和这个平台上其他了不起的作家!提前感谢。

https://philip-wilkinson.medium.com/membership

或者随意查看我在 Medium 上的其他文章:

因果推理中的方差缩减

原文:https://towardsdatascience.com/variance-reduction-in-causal-inference-8145dd9d5f2

数学技巧可以让我们的测量更加精确

丹尼尔·勒曼在 Unsplash 上的照片

我们谈论的是哪个方差?

方差是任何度量估计中的一个问题。每次我们进行测量时,都有两个共同的事实:

  1. 我们正在从有限的样本量中估计测量值,并且
  2. 我们希望尽可能准确。

现在,只要我们的测量是无偏的,平均来说,来自样本的测量将是来自真实总体的真实测量的适当估计。无偏测量意味着,如果我们对来自同一分布的不同数据样本进行无限次相同的实验,或者对无限个数据进行单次实验,我们的结果将平均为总体的真实测量值。

然而,偏差并不是单一实验中唯一可能的误差来源。每个样本与真实人口略有不同。

例如,如果我们想要美国人的平均年龄,而我们只对 100 人进行了抽样,那么我们这 100 人的平均年龄不会完全符合所有美国人的平均年龄。

一般来说,一个样本的平均值并不完全是总体的平均值(确实通过中心极限定理,很多样本的平均值会在真实总体的平均值周围形成一个高斯)。

当我们使用数据点的样本来获得我们的测量值时,它试图估计全部人口的测量值,由于随机性,我们会有一些误差。想想样本均值的高斯方差。这是我们试图减少的方差,本质上是样本测量值和真实总体测量值之间的典型差异。

方差减少技术是关于使用有限的样本大小来尽可能精确地估计。换句话说,方差减少技术使您能够用尽可能少的样本以给定的准确度进行测量。

顺便提一下,我们应该记住,平均值并不是唯一有趣的指标。我们可能对任何任意的计算感兴趣,比如我们度量的中值,或者我们度量的倒数加一的平均值!

U = 1/(1+x)

衡量标准包含哪些内容?

我们可以测量的任何事物都可能与我们也可以测量的其他事物相关。如果我们对 x 的每个值都考虑 1/(1+x),那么它与每个 x 的(1+x)相关。

在这个世界上,度量不仅是相关的,而且通常是由其他我们也可以度量的事情引起的。在因果推理中,我们通常将一个结果建模为由多个其他实体引起的。这些其他协变量合在一起产生结果。

对于有许多输入的可测量的结果,我们可能会对其中一个输入的贡献感兴趣,这就是因果推理的情况。我们对“治疗”输入的效果感兴趣,而其他输入也对结果有影响。

例如,如果可衡量的结果是平台的参与度,则影响该结果的变量包括治疗、用户的年龄、他们的地理位置以及治疗前用户的活动水平。

如果我们做一个简单的 t 检验,测量治疗组和对照组的平均结果,并比较它们的差异,那么我们就有了整个人群的方差,这不仅是因为有限样本的噪声,还直接归因于可测量的协变量,如年龄、位置和治疗前活动。

我们已经知道方差缩减!

事实上,我们在第一堂统计课中介绍的一种方差缩减技术是配对 t 检验。如果在治疗前后都测量了同一组个体,那么我们可以使用配对 t 检验作为测量治疗效果的方差减少技术。

如果没有这种方差减少技术,我们将简单地取治疗前个体结果的平均值,并将其与治疗后个体结果的平均值进行比较。但是通过配对 t 检验,我们基本上控制了每个人的治疗前措施。我们通过采取差异的治疗前措施来校正他们的治疗后结果。

我们在这里做了什么?除了治疗,我们知道另一个影响最终结果的协变量:个体。因此,从因果关系来看,我们可以对结果的两个输入进行建模,即治疗和个体。如果我们想要治疗的因果效应,那么我们可以通过控制个体来减少方差。我们通过测量每个人的治疗前措施,并将其包括在计算中来做到这一点。利用每个个体的治疗前和治疗后测量之间的协方差来减少我们对治疗效果的估计的方差。

另一种最先进的、常见的方差减少技术 CUPED 没有太大的不同。如果我们计划测量一个治疗组和对照组的一些结果,那么一个非常有效的控制是在对任何人进行治疗之前的相同测量。这减少了我们对治疗效果估计的方差,因为我们去除了由于人口的内在差异而产生的方差。这就像一个配对的 t 检验——我们从治疗前的水平来看变化,而不是只比较结果而不控制个体差异。CUPED 只是去掉了治疗措施,乘以一个常数,得到治疗效果的最小方差预测。

希望这能打开你的思维,让你看到一般的可能性。我们可以控制与结果相关的任何协变量,以尽可能减少我们测量的方差。

方差总体减少

在维基百科页面之外,方差缩减技术包括普通随机数、对偶变量控制变量重要性抽样分层抽样、矩匹配、条件蒙特卡罗和准随机变量。

通常,这些技术利用两个测量值之间的协方差将这两个测量值都包含在最终计算中,以尝试减少感兴趣的测量值的方差。他们通过使用另一个方差相关的度量来解释我们的度量中的一些方差,并在某种意义上控制它,以减少他们的组合方差。

对偶变量使用协方差负相关的测量值,CUPED 使用协方差正相关的变量,而控制协变量使用另一个测量值与其每个测量值的平均值之间的差值。我们很快会谈到分层抽样(块随机实验)。

因果推断中的协变量

有不同种类的协变量会对结果产生影响,因此在某种意义上应该加以控制,以减少方差。

当我们想要在没有随机实验的情况下推断因果关系时,因果推断最迫切地与混杂因素有关。混杂因素是治疗和结果的原因。因此,如果你有混杂因素,那么这些相关变量与治疗相关。没有随机实验,混杂因素不仅会增加方差,它们还会使结果产生偏差!因此,我们绝对必须控制混杂因素,以消除这种偏差,然后我们才有机会简单地担心减少方差。

作为一个题外话,这似乎是我们的 ML 偏差-方差权衡所熟悉的,但通常在统计测量中,我们强烈偏好无偏测量,因此我们的统计假设成立,并且任何非常大的样本大小都将是对真相的良好估计。不知道偏差的大小真的会削弱我们对结果的信心,因为我们总是可以估计置信区间,但不知道偏离真相的程度是很糟糕的。

在随机实验中,我们不需要考虑偏见。同样,偏差是指许多实验的平均值与真实值不同)。有了随机实验,我们只需要担心方差的减少。但是仍然有一种感觉,单个实验是有偏见的。

当我们进行随机分配时,虽然协变量被随机分配到治疗组,因此与治疗没有因果关系,但随机分配仍然导致治疗组之间协变量的不完全平衡。

例如,如果我们随机分配一个治疗组或对照组,那么平均而言,两个治疗组中的儿童和成人比例相同,但我们单个实验的现实情况可能是,对照组中的儿童比例略高于治疗组。假设作为一个孩子和作为一个成年人会影响结果,这是我们实验中治疗效果的差异来源。

因此,我们的样本在儿童/成人协变量和治疗之间将具有非零相关性,尽管它在统计上不显著(达到相关性显著性检验的预期假阳性率)。

一般来说,由于随机分配不会产生完全平衡的协变量,因此在样本中,协变量和处理之间会有一些小的相关性。协变量和治疗之间的这些小的相关性来自于随机机会,因为它们在设计上是平均不相关的,但是仍然影响我们的结果,并且是可以控制的。

通过机器学习减少方差

我们可以用机器学习来解释混杂因素以及任何其他协变量。为了简单起见,让我们考虑一个线性回归。可以训练回归来预测治疗的结果,以及影响结果的任何其他变量。这些其他协变量可以包括与结果相同或相似的治疗前测量,或影响结果的任何其他治疗前协变量,如人口统计学和地理因素。

如果我们有一个适当的随机实验,或者没有被这些其他协变量真正混淆,那么我们的测量在没有回归的情况下将是无偏的。利用回归,我们通过考虑每个协变量对方差的贡献来减少方差。从本质上讲,对于每个人的结果,我们正在控制结果的其他原因,因此我们严格衡量我们的治疗的贡献,在我们不同的人群中,这将比结果本身有更少的差异。

在没有随机实验的情况下,这样的线性回归可以消除简单的线性混杂,这意味着它可以消除由这些混杂产生的测量偏差。因此,这种方差减少技术甚至可以消除偏差!

另外,原来常见的 CUPED 技术相当于一个简单的最小二乘线性回归,以预处理测度为变量!

用倾向得分说明协变量不平衡

即使是随机实验,尤其是没有真正混杂因素的实验,因果推断的典型做法是使用倾向模型来帮助校正治疗可以从协变量中预测的事实。

倾向得分是给定其他协变量的治疗概率。常见的技术包括将倾向得分添加到回归中,通过倾向的倒数对样本进行加权,以及在回归的解释中使用倾向(双重稳健)。

因果推理的 ML 模型中的正则化

ML 模型中的正则化(想想回归中的 L2 或 L1 正则化)也是有帮助的。有研究探讨正则化程度与模型对潜在因果结构的正确结论。ML 泛化与正确建模因果结构密切相关。训练数据的特性可以由表征训练数据的附加(可能未知)协变量来表示。过度适应来自与测试数据不同的人群的训练数据是一个没有对这个使训练数据不同的缺失协变量建模的问题。因此,实现泛化需要避免过度适应来自不同人群的训练数据。在这种常见情况下,正确建模因果结构受益于显著的正则化。Amazon 有一篇论文将最优正则化计算为某个可测量协方差的函数。

这些方法将有助于减少因果测量的方差,因为我们正确地表征了因果系统。

现在,如果我们分析来自 A/B 检验的数据,那么在设计上就没有真正的混杂因素——治疗和协变量之间的任何相关性只是随机分配的统计方差。

在这种情况下,回归仍然优于 t 检验,因为它有助于控制影响结果的协变量,减少治疗效果测量的方差,还因为它有助于处理来自随机性的治疗组之间的小协变量不平衡。

处理治疗组协变量差异

虽然在个体水平上解释协变量差异是最有力的,但我们也了解到,我们的因果推断中的一些差异是由于治疗组之间的协变量不平衡,即使在随机实验中也会出现这种情况。

为了更好地控制各治疗组之间的协变量不平衡,我们可以进行一个块随机实验,使用(协变量类别或协变量距离)匹配进行一些修剪(有或没有随机实验),并根据样本的逆频率或概率对样本进行加权,以校正不平衡(有或没有随机实验)。

自举

我已经说过,我们可以测量任何测度的置信区间。事实上,我们总是可以用 bootstrapping 来估计测量中的误差。在 bootstrapping 中,我们进行测量和建模过程,以获得我们最终感兴趣的测量值,然后我们使用略有不同的数据集再次进行测量——略有不同的数据集来自我们相同的样本数据,通过对其进行采样进行替换。如果我们有 N 个数据点,那么我们用替换法从这 N 个数据点中采样另外 N 个数据点。例如,对于数据集[1,2,3,4,5],我们可以用替换法采样 5 个数据点,我们可能得到[1,2,2,5,5]。用不同的 bootstrap 样本重复我们的分析 50-100 次将会产生结果分布。

神奇的是:我们结果的方差是我们初始结果和真实总体结果之间方差的一个很好的估计。因此,如果我们的初始度量是 15,我们的 50–100 bootstrap 结果的标准偏差是 1,那么我们可以说真实总体的度量是 15 +/- 1。

再见

今天到此为止。我希望对方差缩减的介绍有助于解释它的重要性,以及它与其他概念的关系。查看我的相关文章,因果推理完全指南:

超越 CUPED 的升级差异减少:引入 MLRATE

原文:https://towardsdatascience.com/variance-reduction-on-steroids-introducing-mlrate-be328cd71a03

为了比较自动驾驶性能,Wayve 使用了 MLRATE:在实验时使用机器学习更快地达到统计幂。

假设你正在进行一项实验,你的控制群体和变异群体之间的均值确实存在差异。你会察觉吗?那要看 看三样东西 :

  1. 效果的大小
  2. 你收集的样本数量
  3. 你测量的差异

效果的大小通常不是你能改变的。增加样本量需要更多的时间,成本也很高。所以我们只能盯着选项三:我们怎样才能让我们的测量更少噪音

Wayve 我们正在使用端到端深度学习开发自动驾驶技术,因此以最快和最稳健的方式评估两种车型在道路上的真实驾驶性能的差异非常重要。一个挑战是跑步之间的条件有很大的不同:从天气到交通密度,再到乱穿马路的行人。因此,我们不是将不同的条件视为在大型实验中“平均出来”的噪声(按照传统的实验程序),而是希望利用信号并从每次交互中学习。

Wayve 提供:右转差异

使用机器学习调整的治疗效果估计器(MLRATE, Meta 2022 ),我们可以利用机器学习模型可以学习的此类混淆变量和驾驶表现之间的复杂非线性关系,并使用广义线性模型以稳健的方式实现这一点。这使得我们可以用较小的样本量达到统计显著性。例如,在 Wayve,我们发现使用 MLRATE,我们可以将达到统计显著性所需的行驶公里数减半。

一些背景:简单的 A/B 测试可能是非常低效的

随机对照试验(RCT)传统上是衡量改善的黄金标准。RCT 通常用于科技行业在线测试机器学习模型,通常将用户群随机分为对照组(使用旧 ML 模型的用户)和变体组(使用新 ML 模型的用户),并观察两组之间性能指标均值的差异(A/B 测试,得出“均值差异估计值”)。这些实验经常为这个过程捕获数百万个数据点: 大规模随机化很重要 以确保我们有一个有代表性的测试样本,以促进更公平的比较,并给出更大的统计信心,即测量的改进是真实的,而不仅仅是噪音。

作者图片

但是大规模随机化对现实世界的驾驶不起作用。随机测试路线意味着驾驶模型必须导航的场景存在巨大差异,从交通岛到施工区,这种差异会使我们在没有巨大样本量的情况下得出不确定的结论,并且当我们想要快速迭代和学习时,为每个新模型收集如此多的驾驶数据是不可行的。即使在同一路线上测试两种驾驶模式(即配对测试)也无法解决这一问题。例如,我们不能控制诸如汽车或骑自行车的动态主体,或者如果一个驾驶模型全是绿灯,而另一个全是红灯。此外,限制到特定路线可能意味着对这些特定测试路线的过度优化。Wayve 希望成为第一家自主到达 100 个城市的公司:我们不能只根据我们今天行驶的道路来调整我们的测试,我们需要比较没有在相同路段行驶过的驾驶模型,利用对我们的汽车在他们以前从未行驶过的区域的表现的了解。

Wayve 提供:同一个环形路口的不同

典型的方差减少技术可以更快地达到统计显著性,但我们可以更进一步

数据科学家通常采用方差缩减技术来提高对真正改进的检测,这些改进太小而无法在较低样本量下以统计置信度进行解析。例如,人们可能会发现,当交通密度较低时,司机在环形交叉路口的表现最好。在所有级别的交通中,测量驾驶员的环岛表现会有较大的测量差异,因此我们不太确定观察到的表现差异是真实的还是偶然的。我们预计,当一名司机面临较少的交通流量时,他们会表现得更好,因此,我们应该将这一预先存在的知识纳入我们的实验。

减少方差的一种常用方法是通过使用预先存在的数据 (CUPED,微软 2013 )的受控实验,其中一些线性协变量(如交通密度)用于调整简单的均值差异估计值。

(图片由作者提供): 上图中,横轴代表每个样本的数值。该轴右侧更远的样本表示在实验过程中测得的更高的值。当我们忽略协变量并查看每组平均值周围的离差(如左侧图表所示)时,会出现较大的方差:直方图很宽,变量和对照之间的许多点重叠,因此我们不太确定平均值分布中是否存在真正的差异,或者只是来自采样的噪声。相比之下,当测量方差时,右边的图说明了相关的共变量(在纵轴上示出):得到的残差,即条件均值周围的离差更小,因此统计功效更高。

这本质上相当于简单的线性回归!这有助于现在表示以后的最佳情境 MLRATE:

顶等式指无方差减少;底部包含一个线性协变量 X. ( 图片由作者提供)

其中:

  • Y 是每个观察样本的测量值的向量
  • V 为虚拟变量,记录采集的样本是否在变异组中
  • X 记录采集样本时相关协变量的值

因此,我们关心的是估计系数 𝛿 1 (我们的度量 y 由于在变体组而不是控制组中而提高的大小)。包括一个简单的虚拟变量 v 有效地通过组平均值去平均每个样本,因此这相当于通常的 t 检验。

省略相关变量时, 𝛿 1 的合成标准误差更大,如左上图所示——包括它降低了标准误差,而因此更有可能具有统计显著性。一般来说,协变量与 y 的相关性越高,方差减少得越多。

(注意,在该方案中,省略相关变量不会产生预期偏差,因为我们假设将样本分配给治疗组或对照组, v, 独立于任何协变量 x ,即它们是正交的)。

照片由沃洛季米尔·托卡Unsplash 上拍摄

然而,尽管 CUPED 在减少一些线性相关变量的方差方面非常有效,但对许多具有复杂非线性关系的协变量进行调整通常不在范围之内。

在 Wayve,我们希望将许多影响性能的混杂变量纳入我们的测试中,例如静态场景差异(如公交专用道、人行横道)、动态因素(交通密度、骑自行车者的存在)、环境因素(如天气、亮度),甚至安全操作人员的人为偏差(这些因素之间通常也存在复杂的非线性相互作用)。使用更复杂的机器学习模型将提供更复杂的代理来促进对这些协变量的控制。

MLRATE 遵循两个步骤,以稳健的方式使用 ML 模型来减少方差

步骤 1:训练和校准 ML 模型

为了控制所有期望的协变量,第一步包括建立一个机器学习模型,以使用相关协变量预测我们的性能指标。

在 Wayve,我们在一个平衡的数据集上训练一个人工神经网络(多层感知器),使用我们在测试期间感兴趣控制的所有功能(如动态代理)。人工神经网络往往对自己的预测过于自信,因此我们也使用保序回归进行校准,以确保我们的性能预测与实际性能线性相关(这对第二步很重要,我们将在后面提到)。

对于实践中的第一步,我们将训练数据随机分为两部分:我们训练和校准两个具有相同架构的性能预测模型,每个数据集一个,然后使用未训练的模型预测每个数据集中每个样本的性能。这种“交叉拟合”(使用样本外预测)方法对于避免衰减偏差非常重要,如果使用样本内预测,可能会因过度拟合而导致衰减偏差。

步骤 2:使用 GLM 估计 ML 调整后的治疗效果

与之前类似,第二步涉及运行广义线性模型,但不是使用【X】【线性协变量】【g(X)来显示我们如何合并来自 ML 步骤的预测。

( 图片作者)

除了先前的回归,处理 V 和 ML 预测【g(X)之间的相互作用项被用于说明预测项对于变体可能不太相关的事实,并因此导致 d_1 上省略的相关偏差。(注意,这些值的意义在于防止与虚拟变量的多重共线性过高,否则会使系数不稳定)。**

MLRATE 论文使用 Huber-White 异方差校正的标准误差来导出该估计量的置信区间(确定系数是否具有统计显著性)。然而,由于我们在 Wayve 的实验设置,我们经常使用更保守的集群标准误差

使用权重定制比较

在 Wayve,我们将 MLRATE 方法向前推进了一步,定制了我们的模型评估,以使比较更适合路径的特殊属性。例如,我们的模型可能主要在 20 英里/小时的高交通密度道路上进行测试,但在杂货店合作伙伴的典型路线上,我们有更多 30 英里/小时的交通密度较低的多车道道路。因此,我们可能希望提高 30 英里/小时、多车道场景的权重,并相应地这样做以匹配该目标分布,而不是运行回归,好像每个试验都同等重要。

**(承蒙way ve):使用分段标签重新加权试验

我们从 中获得灵感,对调查数据 运行广义线性模型,通常由决策科学家进行,以实现这一点。我们使用已知的性能预测功能为每个试验创建权重(类似于调查数据中的设计权重),并运行加权回归。

传统上,迭代比例拟合用于定义权重,以确保边际总量相同。然而,使用 IPF 意味着我们关心的特征的联合分布可能与目标有很大不同。因此,尽管探索了 IPF,但 Wayve 有意选择不这样做,因为我们知道感兴趣特征的目标联合分布,并且我们关心紧密匹配该联合分布(特别是因为一些特征不是独立的,并且它们的共现可能对性能具有倍增影响)。相反,我们导出权重来匹配特征的联合分布。

权重被截断,以确保很少测试的(潜在有噪声的)特征组合没有太高的权重(这是我们通过离线引导我们的测试数据来确定的)。权重也被归一化,以确保回归步骤中的自由度与未加权的 GLM 相同。

自动化该分析

在 Wayve,我们已经在内部应用程序中实现了所有这些分析的自动化。在我们自动训练神经网络并运行广义线性模型之前,ML 研究人员可以提供特定的过滤器和他们希望定制的路线,在几分钟内产生详细的报告。这些工具有助于在任何两个模型之间进行快速而稳健的比较,使我们能够加速我们的车队学习循环,并使其充满从我们捕获的所有特征中获得的洞察力。我们将很快就此发表一篇博文!

(图片作者提供):我们的内部应用

参考资料和进一步阅读:

* https://arxiv.org/abs/2106.07263 https://www.semanticscholar.org/paper/Improving-the-sensitivity-of-online-controlled-by-Deng-Xu/c65d71c42dedb3329c2b81499950bf12786a3e8e https://www.jstatsoft.org/article/view/v009i08 *

构建机器学习管道的各个步骤

原文:https://towardsdatascience.com/various-steps-involved-in-building-machine-learning-pipeline-79c2cb13d69d

理解机器学习中的基本步骤是有益的,这样人们可以组织和集中他们的精力和资源来完成整个 ML 工作流程中的每个步骤。

照片由EJ·斯特拉特Unsplash 上拍摄

在机器学习中,经常会出现关于如何构建可实时部署的可扩展健壮模型的困惑。最复杂的事情是缺乏关于机器学习整体工作流程的知识。理解机器学习工作流程中的各个步骤对于数据科学家或机器学习工程师来说尤其方便,因为从长远来看,这可以节省大量的时间精力。在本文中,我们将回顾构建机器学习系统通常涉及的步骤。

很好地理解构建人工智能系统的高级设计所需的原则是有用的,这样人们就可以分配他们的时间资源来完成拼图的每个部分,然后提出一个投入生产的健壮的高性能模型。文章中强调的每个步骤对于检查监控以获得实时部署的最佳模型非常有用。让我们回顾一下机器学习项目生命周期中的一系列步骤。

定义业务目标或目的

马库斯·斯皮斯克在 Unsplash 上拍摄的照片

转向人工智能的行业正在增加,并有可能在未来增长,因为公司通过利用在各种任务中获得人类水平的表现所需的工具和技能,获得了巨大的利润和 T2 利润。因此,在各种项目中有许多目标和目的也涉及机器学习能力。在构建或提取数据之前,讨论和理解项目的目标是很重要的。通过定义问题和理解目标,就有可能知道在哪里实际应用 ML,以及在哪里减少不需要自动化的应用。考虑这样一个例子,根据消费金额、卡的使用情况、购物时间和各种其他因素来预测客户是否会超过每天的交易限额。该数据暗示我们不需要自动化该过程,因为特征“花费金额”实际上是客户是否超过交易限额的粗略指标。在这种情况下,当预测几乎没有潜在的业务影响时,对于 ML 模型来说,这将是对资源计算能力的相当大的浪费。定义业务目标的第一步可能是一个非常有力的步骤,应该在启动和分配 ML 系统所需的资源之前采取。

数据收集

乔恩·泰森在 Unsplash 上的照片

好的,我们实际上已经定义了业务目标,并且有了构建最佳预测 ML 模型的具体想法。下一个重要步骤是收集重要信息,以便 ML 模型在关键绩效指标(KPI)上表现良好。这些指标因我们试图解决的问题是分类任务还是回归任务而异。当试图实时部署 ML 模型时,理解和优化 ML 模型以在这些指标上表现良好可能是一件需要考虑的重要事情。检查数据的质量总是一件好事,因为应该考虑到这样一个原则,即如果提供给模型的数据没有显示出与目标变量的任何关系,我们很可能最终会从我们定义的 KPI 模型中获得糟糕的性能。因此,尝试为我们正在尝试解决的问题收集最合适的数据可以对模型的性能产生重大的积极影响

探索性数据分析

卡洛斯·穆扎在 Unsplash 上的照片

获得对预测很重要的正确数据后,现在是时候探究数据中的要素和输出变量之间是否存在任何关系了。使用有用的可视化工具,如柱状图、散点图和计数图,在很大程度上有助于理解和分析数据,以便向利益相关者解释清楚。此外,也可能是我们的数据包含大量的缺失值异常值。当数据中存在异常值时,它通常会认为这些异常值非常重要,从而扰乱模型;当我们的模型面对实际数据时,它通常会在实时部署时失败。因此,探索数据并了解是否存在异常值或缺失值被认为是一种有用的做法。处理缺失值也是至关重要的,因为有许多 ML 模型对缺失值不鲁棒。当我们试图给出包含缺失值的数据时,我们经常会出错。有多种策略可用于处理缺失值,如均值中值模式插补以及其他一些策略。

数据预处理

克里斯·劳顿在 Unsplash 上的照片

现在,我们对 ML 项目的总体目标有了很好的理解,也对基于各种图生成的可视化数据有了直觉。正如在文章的上一部分中所讨论的,我们的数据经常包含许多缺失值或异常值。因此,现在是时候在将这些值输入 ML 模型进行预测之前处理它们了。完成这些步骤后,执行数据标准化也很重要,因为这对大多数模型都很有用。除此之外,必须考虑分类特征的存在。通过考虑各种特征编码技术,它们可以被转换成数字特征。在执行了这里提到的所有步骤之后,现在是时候训练这些模型了,我们将在本文的下一部分讨论这些模型。

如果您对了解可以对数据采取的各种预处理步骤更感兴趣,可以随意看看我以前的文章,在那里我详细地提到了它们。下面是链接。

机器学习和数据科学中最重要的预处理步骤是什么?|作者 Suhas Maddali | 2022 年 5 月|迈向数据科学(medium.com)

训练机器学习(ML)模型

尼克·莫里森在 Unsplash 上拍摄的照片

您已经准备好了用于 ML 模型的数据,以便它们做出有用的预测。现在是时候训练我们的模型,让它们从数据中学习一些重要的表示,然后才能对结果或目标变量做出猜测。我们训练各种模型,以根据我们在机器学习工作流程的第一部分中定义的关键绩效指标(KPI) 找出可以部署的高性能模型。在训练和执行超参数调整以获得最佳模型之后,我们最终决定实时部署 it,这是工作流程的下一步。

部署模型

Nguyen Dang Hoang Nhu 在 Unsplash 上拍摄的照片

我们已经训练了各种模型,也执行了超参数调整(改变模型中的参数以获得最佳性能),现在是时候实时部署它并了解它的性能了。虽然我们不知道我们的输出标签实时看起来会是什么样子,但我们应该使用我们的领域知识、和来自他人的专业知识来判断我们的模型是否真的在轨道上,以及它是否正在做出实际上是预期的预测。与预期性能的任何偏差都可能对模型在组织中实际创造的业务价值产生重大影响。这就是我们将在本文的下一部分讨论的应该对模型进行持续监控的地方。

监控性能

照片由 Jorge RamirezUnsplash 上拍摄

工作流程的最后一步是不断地监控模型的性能,看看它的表现如何,是否符合 KPI 的预期。有人可能会问,如果没有输出标签,我们实际上如何确定性能。这是个好问题。在这种情况下,我们使用自己的领域知识和来自他人的专业知识,来找出当人类分析师对特定结果做出猜测时,显示的结果是否与通常预期的结果相匹配。如果它与人类分析师的匹配,这意味着我们的模型表现得相当好。另一方面,可能存在模型在部署后表现不佳的情况。在这种情况下,一个解决方案是重新训练模型,并改变反映当前而不仅仅是过去模式的数据,以便它可以学习新的表示。然而,当我们不经常监控模型的性能时,可能会出现诸如概念漂移或数据漂移之类的情况,这些情况会导致模型在运行时的性能非常差。因此,在最后阶段持续监控和评估模型的性能可能是一件好事。

结论

阅读完这篇文章后,希望你对机器学习的整体工作流程有了一个良好的直觉,详细描述了各个步骤。通过学习整个工作流程中的步骤,人们可以投入时间和精力来改进项目中需要仔细关注的某些方面。有时可能更强调训练 ML 模型,而不是更关注数据预处理方面的事情。应该始终考虑到,所提供数据的质量在很大程度上决定了模型的结果和性能的质量。因此,在管道的每个部分花费适当的时间是开发 ML 解决方案并从中产生业务影响和价值的非常有效的方法。感谢您花时间阅读这篇文章。

以下是您联系我或查看我作品的方式。谢了。

GitHub: 苏哈斯马达利(Suhas Maddali)

LinkedIn: (1)苏哈斯·马达利,东北大学,数据科学| LinkedIn

培养基: 苏哈斯·马达利—培养基

感谢本·胡伯尔曼(隐藏)

向量范数,已解释

原文:https://towardsdatascience.com/vector-norms-explained-e72bf26e4a38

揭示线性代数中一个常被误解的话题

向量范数在工程和数学的某些领域是极其重要的。然而,我认为教育系统经常以公式化的方式呈现规范,“这里是如何计算它”的方式,而不是呈现对向量规范的直观理解。让我们看看是否可以通过对向量范数更务实的讨论来补救这种情况。

什么是规范?

范数只是描述向量大小的一种方式,但其定义通常表示为

作者图片

数学定义乍一看似乎很复杂,但它实际上只是说范数是具有某些性质的函数。让我们把上面的定义简单一点,让它更直观。

范数是来自实数或复数向量空间的函数,具有以下性质:

  1. 正定性:在原点只有零
  2. 同质性:它与缩放互换(也就是说,我们可以拉出任何标量)
  3. 三角形不等式:它服从三角形不等式使得作用于 x 和 y 之和的函数小于或等于单独作用于每个元素的函数之和。

现在你知道了!规范的代数理解。然而,让我们增加一点复杂性。规范不仅仅是一个单一的函数。可以是很多功能!

规范的类型

数学领域中有大量的范数。事实上,我敢打赌,你们中的许多人以前都见过一种规范,而你们甚至不知道它。让我们讨论几种类型的规范。

1-定额

1 范数,或 L1 范数,定义为

作者图片

这只是 1-范数的一种奇特方式,是每一项的绝对值的列和。对于 Nx1 向量,只需将每个元素的绝对值相加,就会得到 1 范数。

你通常会在机器学习应用中看到 1-Norms。它可以用作降低模型系数的技术,从而降低模型的整体复杂性。它可以用在向量的稀疏性很重要的地方,因为 1-范数只受非零元素的影响。

2-定额

2 范数,或 L2 范数,定义为

作者图片

它通常被称为欧几里德范数。你可能已经看到 2-范数是计算向量长度或大小的标准方法。这就是“平方和的平方根”方法。工程师和科学家通常默认欧几里德范数,因为距离在自然界中的标准尺度下遵循这一范数。

欧几里德范数最常见的应用是用它来确定向量的大小,从而确定 A 点到 b 点的距离。

P-Norm

1-范数和 2-范数是 P-范数,其中 P=1 和 P=2。我们选择 1 和 2 的值,因为它们在整个应用中经常使用,但是 P 可以设置为大于 1 的任何数字。P 范数定义为

作者图片

你知道吗: p 必须大于等于 1 是因为值大于零,但小于 1,违反了三角不等式,因此,都不是规范。

P 值通常因应用而异。对于给定的领域和/或应用,您应该了解一些相对流行的方法。然而,由于它是特定于应用的,所以我将让您来探索适合您应用的最佳 P 值。

∞-定额

∞-范数被定义为,

作者图片

这是行的最大绝对值。对于 Nx1 向量,∞-范数只是向量中的最大绝对值,因为有 N 行。

∞-范数的一个应用是数值方法的误差分析。∞-范数提供了一个最大的界,这使得它成为误差界的完美估计。

几何解释

好,我们知道什么是向量范数,也知道向量范数的不同类型。对我来说,直到我看到了什么是规范的几何解释,我才获得了对规范的直观理解。

作者图片

我们有上述规范的几何解释。上面的形状是单位形状;也就是说,它们在一个单位之外与轴相交。从菱形(P=1)开始,P 可以增加或减少。如果它减少,钻石的边向内朝着原点塌陷。如果 P 增加,侧面开始向外弯曲,如 P=2 所示。随着 P 继续增加,边继续向外,变直,直到我们到达 P=∞,在这里它结束于一个正方形。

很容易看出,每种类型的范数在同一个向量空间中表现不同。你可以从代数上看到这一点,但几何解释有助于巩固这个概念。因此,在不同的应用中使用不同的向量范数来产生期望的结果。

规范和 Python

我们来看一些代码!我们将回顾如何使用一个称为 scipy 的公共 python 库来计算一个范数;具体来说,线性代数软件包。我们将看看如何计算上面列出的标准。

import scipy.linalg as la
import numpy as nparr = np.array([4, 3, 1, 25, 0, 5, 2, 4])
one_norm = la.norm(arr, ord=1)
two_norm = la.norm(arr, ord=2)
seven_norm = la.norm(arr, ord=7)
inf_norm = la.norm(arr, ord=np.inf)

在 scipy 库中,范数的计算变得很容易。我们简单地声明我们的向量并调用“norm”函数。必须指定要计算的范数,因为默认值是欧几里德范数(即 P=2)。让我们运行上面的代码。

作者图片

注:P=7 只是一个说明性的例子。我不知道这个值有什么相关性。

正如我们所看到的,输出符合预期。注意,随着 P 的增加,我们收敛到∞-范数的值。通过软件计算标准通常比手工计算容易得多。我经常在迭代技术的错误分析中使用 python。

结论

向量范数在数学和工程中极其常见。这里的大部分讨论可以从向量外推到诱导矩阵范数上。希望这有助于您对矩阵范数以及我们如何使用软件计算它们有一个更直观的理解。

机器学习中的向量范数

原文:https://towardsdatascience.com/vector-norms-in-machine-learning-5b8381a5130c

p-规范指南。

马库斯·温克勒在 Unsplash 上的照片

如果你正在阅读这篇文章,很可能你已经知道向量是什么,以及它们在机器学习中不可或缺的位置。概括地说,向量是特定长度的数字的一维数组。如下所示:

包含 n 个元素的向量。(图片由作者提供)

向量的元素以特定的顺序排列,元素的位置通常具有固有的含义。我们可以使用位置(或索引)来访问单个元素。

使用索引访问向量的元素。(图片由作者提供)

我们也可以认为向量代表空间中的一个点。如果向量的长度是 n ,则称该点在一个 n 维空间中。例如,如果向量的大小为 2,这可以表示二维空间中相对于原点的点,如下所示:

显示点(4,2)的向量的 2D 图。(图片由作者提供)

向量规范

向量范数被定义为一组函数,这些函数以一个向量作为输入,输出一个正值与之相对。这被称为矢量的大小。根据我们用来计算大小的函数的类型,我们可以得到同一个向量的不同长度。

显示向量范数函数族及其输出的图。(图片由作者提供)

虽然经常被忽视,但规范是训练机器学习模型的核心。实际上,就在每次反向传播迭代之前,您要计算一个标量损失值(正值),它是预测值和地面真实值的平方之差的平均值。这个定标器损失值只不过是一个范数函数的输出。我们计算损失的方法如下所示:

显示使用预测值和真实值计算损失的图表。(图片由作者提供)

标准范数方程—P-范数

所有的范数函数都来源于一个标准的范数方程,称为 p-范数。对于参数 p 的不同值(p 应该是大于或等于 1 的实数),我们得到不同的范数函数。然而,通用的等式如下所示:

p-范数方程。(图片由作者提供)

这需要一个 n 维向量 x 并计算每个元素的 p 次方次幂。然后,我们将得到的所有元素求和,取 p 次根,得到向量的 p 范数,也就是它的大小。现在,通过参数 p 的不同值,我们将获得不同的范数函数。下面我们就来一一讨论一下。

L0 标准:

虽然 p=0 位于 p-范数函数的域之外,但是在上面的等式中代入 p=0 会得到各个向量元素的 0 次幂,也就是 1(假设数字不为零)。此外,我们在方程中还有一个 p 次根,它不是为 p=0 定义的。为了解决这个问题,定义 L0 范数的标准方法是计算给定向量中非零元素的数量。下图显示了给定向量的 L-0 范数函数的输出:

该图显示了 L0 范数的值。(图片由作者提供)

L1 标准:

在 p-范数的标准方程中代入 p=1 ,我们得到如下:

L1 范数的方程。((图片作者提供)

  • 当用于计算损失时,L1 范数也被称为平均绝对误差
  • 无论是远离还是靠近原点,L1 范数在所有位置都呈线性变化。

下图显示了给定向量的 L1 范数函数的输出:

显示 L1 范数值的图像。(图片由作者提供)

L2 标准:

在所有的范数函数中,最常见和最重要的是 L2 范数。在我们上面讨论的 p 范数的标准方程中代入 p=2 ,我们得到 L2 范数的以下方程:

L2 范数的方程。((图片作者提供)

  • 当用于计算误差时,上述等式通常被称为均方根误差
  • L2 范数度量从原点的距离,也称为欧几里德距离。

下图显示了给定向量的 L2 范数函数的输出:

显示 L2 范数值的图像。(图片由作者提供)

平方 L2 范数:

顾名思义,L2 范数的平方与 L2 范数相同,只是平方不同。

平方 L2 范数的方程。((图片作者提供)

  • 当用于计算机器学习中的误差时,上述等式通常被称为均方误差

与 L2 范数相比,平方 L2 范数在计算上相对便宜。这是因为:

  1. 它缺少平方根。
  2. 在机器学习应用中,平方 L2 范数的导数更容易计算和存储。平方 L2 范数中元素的导数需要元素本身。然而,在 L2 范数的情况下,需要整个向量。

最大范数(或 L-∞范数):

由于无穷是数学中的一个抽象概念,我们不能仅仅在标准的 p-范数方程中代入 p=∞。然而,当 p 接近无穷大时,我们可以用极限来研究函数的行为。最大范数方程的简单推导可以在这里找到。

最大范数方程。(图片由作者提供)

Max norm 返回最大幅度元素的绝对值。下图显示了给定向量的最大范数函数的输出:

显示最大范数值的图像。(图片由作者提供)

结论说明:

  1. 向量范数是以一个向量为输入,输出一个正值的函数。
  2. 所有的范数函数都可以从一个方程中导出。范数函数族被称为 p-范数。
  3. L1 范数也被称为平均绝对误差
  4. L2 范数也被称为均方根误差
  5. 平方 L2 范数也被称为均方误差

产品的矢量表示 Prod2Vec:如何去除大量嵌入

原文:https://towardsdatascience.com/vector-representation-of-products-prod2vec-how-to-get-rid-of-a-lot-of-embeddings-26265361457c

你好!我叫 Alex,在 Ozon 的产品匹配团队工作。Ozon 是一家电子商务公司,为客户提供从不同卖家购买商品的服务。我们每天都要处理数十万件产品,我们的任务是识别和比较我们网站上的类似报价(查找匹配),以便将不同卖家的报价收集到一个产品卡中。

每个产品都有以下信息:图片、标题、描述和附加属性。我们希望检索和处理所有这些信息,以处理不同的任务,而这对产品匹配团队尤为重要。

为了从产品中提取特征,我们使用不同的文本模型(fastText,transformers)为描述和标题创建向量表示(嵌入),并使用大量的卷积神经网络(ResNet,Effnet,NFNet)为图像创建向量表示。这些向量进一步用于特征生成和产品匹配。

Ozon 上每天有数百万的更新,这就是为什么统计所有模型的嵌入变得具有挑战性。通常每个向量描述产品的不同部分。如果我们不这样做,而是一次只获得一个产品范围的向量,会怎么样?听起来不错,但是如何正确实施呢…

图片由作者提供,灵感来自电影《宿醉》

要创建产品的矢量表示,我们可以使用:

1.内容—图像信息、文本、产品名称和属性。

2.用户会话—用户查看/购买产品的历史记录。

让我们谈谈我们是如何应用第一种方法(使用内容)解决这个问题的。

作者图片

方案

除了这种架构可以用于推荐、搜索和匹配的事实之外,它允许将关于产品的所有信息(图像、标题、描述和属性)统一到单个向量中,因此简化了一些管道(排序、搜索和候选产品的选择)。

建筑

使用度量学习方法来完成这项任务是合乎逻辑的:最小化相似产品之间的距离,并使用例如三重损失来迫使不相似的产品彼此分开。有许多有趣的问题(负采样,这里什么被认为是正例,如何正确地建立数据集)。由于我们已经有了一些这种类型的模型,我们决定使用监督学习方法来解决这个问题——预测类别树中最低级别的类别。

每个产品都属于一个特定的类别树,从高层次(衣服、书籍、电子产品)到低层次(短裤、杯子、智能手机外壳)。我们有几千个这样的低级类别。

例如,电子产品(第一类)→手机、平板电脑(第二类)→苹果智能手机(第三类)。

作者图片

为了对如此大量的类别进行分类,而不是通常的 Softmax(它没有显示出令人满意的结果),我们决定尝试使用另一种方法,这种方法最初是为人脸识别任务提出的— ArcFace

经典的 Softmax 并不直接影响一个类内的学习嵌入的接近度和不同类中的远离度。ArcFace 就是为此而专门设计的:通过选择边缘惩罚 m 参数,我们可以调整相同或不同类的嵌入闭合/距离。

作者图片

模型架构的第一个版本如下所示:

作者图片

对于模型来说,立即区分类别 3 被证明是太困难了:我们试图在每次迭代中根据类别 3 的最终交叉熵损失来训练图像、文本和属性模型。这导致他们的体重会聚不良和缓慢。因此,我们决定从以下方面改进该模型:

1.从每个编码器中,我们获得中间层输出— cat1(高级类别)预测。

2.总损失是所有损失的加权总和,首先,我们给予第 1 类损失更大的权重,然后逐渐向第 3 类损失转移。

因此,我们有以下架构:

作者图片

我们采用通常的指数函数作为加权系数:

作者图片

在推断过程中,我们不再对 cat3 预测感兴趣,而是对产品的矢量表示感兴趣,因此我们将图层的输出带到 ArcFace 这是我们需要的嵌入。

准备数据

如果我们只统计所有产品的类别,我们会得到大约 6000 个,其中一些非常相似(维生素和矿物质复合物和膳食补充剂),而另一些则相互嵌入(咖啡和咖啡胶囊),还有一些包含的产品示例太少(理疗机)。

这就是为什么将原始类别作为目标不是一个选项的原因——我们必须执行相当多的预处理来合并相似的类别。结果,我们得到了一个数据集,包括大约 500 万个项目,1300 个类别,每个类别至少有 500 个样本。

数据本身处理如下:

1.文本被转换成小写,停用词被删除。

2.使用标准方法(水平、垂直翻转、亮度和对比度变化)增强图像。

3.那些没有太大意义并且在几乎所有产品中都存在的属性(例如序列号)被删除了。我们尝试了不同的选项来处理属性:将“键-值”对分别添加到每个属性中,并将它们组合成一个字符串。最终没有太大的区别,但第二个选项在学习过程中看起来更优雅,所以我们选定了它。

学习过程

我们决定看看更轻的架构,因为我们有太多的数据,需要将两个文本和一个图像模型放入学习管道。我们使用 ResNet34 作为 CNN,使用两个 Rubert-Tiny 作为文本——作为标题和属性。

我们有文本和图像模型,所以我们为它们分别设置了一个优化器:AdamW——用于 BERTs 和 SGD——用于 ResNet 和 model head。总而言之,我们训练了 60 个历元:最初是 15 个学习率较高的历元,然后继续进行较小的历元,并使用 horovod 在 GPU 上并行化它们。

验证的结果是 85% Acc@1 和 94% Acc@5。相比之下:在标题上训练的 fastText 给出了 60%的 Acc@1 的准确率。

作者图片

当我们渴望了解我们是否成功地为产品生成了良好的嵌入时,类别预测的准确性是不够的。此外,我们使用了带有 3D 矢量可视化的投影仪:在那里你可以选择不同的降维方式,看看我们的矢量是如何投影到球体上的。

例如,这里有 t-SNE 和 UMAP 的可视化:

作者 gif

作者 gif

如果我们仔细观察,我们会发现每个聚类都包含相同类别的产品:

作者图片

当你观察生产管道中产品的最近邻居时,会发生这样的情况:

作者图片

最重要的是,排名模型的推理时间大大减少了:使用 Prod2Vec 嵌入而不是图像和文本嵌入,我们获得了超过三倍的加速:

作者图片

结果和观点

我们对结果很满意,所以我们将完成的架构投入生产,现在每天通过 Spark 结构化流计算数百万个这样的嵌入。它们可以被进一步放入排名模型中,从而产生匹配的良好候选。

此外,嵌入可以用于我们团队中出现的许多其他任务或相关任务。

产品匹配的结果看起来是这样的:在一个产品卡片上可以看到不同的卖家。

作者图片

然而,有趣的是,如果我们用度量学习来训练它,检查这个架构是否会工作得很好。这一切都有待于将来去发现。

如果你做过类似的事情,或者知道解决类似问题的其他方法,请在下面留下评论。希望你觉得这篇文章有趣,有帮助:)

矢量化:将运算速度提高 100 倍的必备技术

原文:https://towardsdatascience.com/vectorization-must-know-technique-to-speed-up-operations-100x-faster-50b6e89ddd45

任何现代数据科学或机器学习应用程序都需要

乔伊·凯伯在 Unsplash 上的照片

介绍

在当前的数据科学机器学习应用中,通常会涉及到庞大的数据集和复杂的网络。因此,在处理计算工作量时,代码效率变得非常重要。

例如,在经典的多层感知器(又名前馈神经网络)中,网络通常包含多个线性层。假设输入层包含 64 个神经元,而第一个隐藏层包含 128 个隐藏神经元。然后,为了在给定输入的情况下计算隐藏层的输出,直接的方法是使用由 Numpy 库提供的[np.dot](https://numpy.org/doc/stable/reference/generated/numpy.dot.html)方法:

import numpy as npfeat = np.ones(64)
w = np.random.rand(64,128)feat.dot(w)

如果我们想计算执行这个方法的时间,我们可以使用[%timeit](https://docs.python.org/3/library/timeit.html):

%timeit feat.dot(w)

我们可以看到,这种方法平均需要 1.4 微秒。

然而,如果我们不使用np.dot,而是在仍然使用 Numpy 数组的同时,恢复到传统的 for 循环,那么您应该这样写:

def loop(feat, w):
    res = np.zeros(128)
    for i in range(w.shape[1]):
        s = 0
        for j in range(len(feat)):
            s += feat[j]*w[j][i]
        res[i] = s
    return resloop(feat, w)%timeit loop(feat, w)

正如你所看到的,像 for 循环这样直观的东西平均需要 4.94 毫秒,比使用之前的方法慢了 3000 倍以上。虽然这些都是很小的时间单位,但想象一下,当神经网络非常深入,数据集达到数百万或数十亿时,那么 3000 倍的差异很可能会产生影响,并决定应用程序的可行性。

顺便提一下,我们可以检查两种方法计算出的结果是否相同(打印前十个元素来验证):

feat.dot(w)[:10]
## result: array([30.80125191, 34.84418376, 29.5061941 , 33.00162298, 31.77854219,
       31.93457429, 30.25155328, 35.71776325, 35.45861881, 33.58667301])loop(feat, w)[:10]
## result: array([30.80125191, 34.84418376, 29.5061941 , 33.00162298, 31.77854219,
       31.93457429, 30.25155328, 35.71776325, 35.45861881, 33.58667301])

所以,让我们回到今天的主题——矢量化。什么是矢量化? 矢量化 是一种处理整个数组而不是单个元素的编程风格。这通常会加快计算速度。使用np.dot是矢量化的一个例子;这个函数由 Numpy 优化,它直接使用两个输入向量执行内积,而不需要使用 for 循环(通常是嵌套的 for 循环)手动进行乘法和求和。

为什么矢量化速度更快

矢量化比 for 循环更快的一个主要原因是 Numpy 操作的底层实现。

正如你们许多人所知(如果你熟悉 Python ),Python 是一种动态类型语言。当你声明一个变量时,不需要指定数据类型,因为 Python 会在运行时为你推断出来。这不同于静态类型语言,静态类型语言需要指定数据类型以便编译器检查,这些语言包括 Java 和 c。

在 Python 中,假设一个列表存储异构元素,在运行时,当执行某个操作时,必须检查列表中的每个单独元素(类型检查)。这将给常规列表增加一定的开销。另一方面,Numpy 使用的是[ndarray](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html),它是存储同质元素的固定大小项目的多维数组。ndarray被优化,因为底层操作是使用高效的 C 操作完成的,这允许向量化。

矢量化的另一个例子

为了让您更好地理解矢量化,另一个例子是常见的损失函数线性回归均方误差(MSE) 。回想一下,线性回归的 MSE 定义为数据集中预测值和实际标注值之间的均方误差,预测值是当前权重集与要素的点积。这可以使用矢量化实现,如下所示:

def MSE(theta, X, y):
    return ((X @ theta - y)**2).sum() / len(X)

注意,除了使代码执行方式更快之外,代码也直观简单。不需要混乱的嵌套 for 循环。

矢量化的条件

现在,您可能知道矢量化速度很快。但是我们可以对任何类型的函数使用矢量化吗?矢量化的条件是什么?

要使用矢量化,一个关键要求是底层的元素操作应该相互独立。例如,在 x 和 y 的点积中,x_1 * y_1的结果与x_2 * y_2无关。当不满足这个独立性标准时,我们只能回到使用 for 循环。以下给出了无法应用矢量化时的 3 种常见情况:

  • 循环依赖性
for i in range(1, len(x1)):
    x1[i] = x1[i-1] * x2[i-1]
  • 间接存储器访问
for i in range(len(x2)):
    x1[x2[i]] = x3[i] * x4[i]
  • 代码分支
for i in range(len(x1)):
    if i < 8:
        i += func()
    x1[i] = x2[i] * x3[i]

np .矢量化方法

如果一个方法是可向量化的(不是上面三个条件中的任何一个),那么我们可以通过[np.vectorize](https://numpy.org/doc/stable/reference/generated/numpy.vectorize.html)函数注册它。举个例子,

def func(a, b):
    return a+1 if a >= b else a-1func_vec = np.vectorize(func)

a = np.array([1, 2, 3, 4, 5, 6])
b = 3
func_vec(a, b)
# result: array([0, 1, 4, 5, 6, 7])

我们可以定义自己的定制函数并将其矢量化,这很好。然而,在注册自己的矢量化函数之前,请考虑是否存在执行相同操作的多种方法。上述功能可以通过以下方式实现:

np.where(a>=b, a+1, a-1)
# result: array([0, 1, 4, 5, 6, 7])

这两种方法给出了相同的结果。哪个更快?当我们使用%timeit时,我们得到如下结果:

%timeit func_vec(a, b)

%timeit np.where(a>=b, a+1, a-1)

答案似乎是使用np.where比定义我们自己的矢量化函数快 4 倍。原因是使用预定义的 Numpy 方法利用了 Numpy 使用的底层优化 C 代码。

因此,每当您想要定义一个定制的矢量化函数时,首先要考虑是否有任何预定义的方法可以做同样的工作;否则,您可以安全地注册自己的矢量化函数。

矢量化思维过程

现在您已经知道了什么是矢量化,为什么要使用矢量化,以及何时使用矢量化,我想给您提供一个关于矢量化思维过程的额外提示。

有些公式可能无法直观地进行矢量化,为了帮助解决这个问题,这里有一个大纲,告诉你如何考虑如何对特定表达式进行矢量化。

矢量化的步骤:

  • 给定一个迭代表达式,考虑等价的向量/矩阵表达式形式
  • 将这个向量/矩阵分解成更小的部分
  • 向量化每个组件,例如元素式操作、XX^TX^TX等。
  • 识别 Numpy 函数,例如np.dotnp.sumnp.outer、您自己的np.vectorize函数,以执行矢量化

一个有趣的注意事项是,向量化不限于 Numpy 事实上,它也可以应用于其他数据科学和深度学习库,包括 Pandas、scikit-learn、Pytorch 等。

摘要

如果你能走到这一步,恭喜你!

再次重申,矢量化是一种一次处理整个数组/矩阵而不是单个元素的编程方式。向量化的主要优势是(方式)更快的代码执行和更干净的代码。注意,并不是所有的公式或方法都可以向量化,如果可以的话,我们可以通过np.vectorize注册它,假设现有的 Numpy 函数没有其他的方法(例如np.dotnp.wherenp.sum等)。

这就是这篇文章的全部内容,希望你在这篇文章中学到了一些东西!下一集再见。

请随意浏览我以前的帖子,如果有任何问题,请告诉我:

https://medium.datadriveninvestor.com/beginners-guide-to-web-scraping-using-beautifulsoup-and-python-999a927f20e6 https://medium.datadriveninvestor.com/top-20-masters-programs-in-ai-data-science-2021-98aadb7db409

参考

矢量化 (数组编程) ,维基百科

Numpy 文档,Numpy

矢量化和广播简单程序员

Numpy 优化矢量化和广播,Paperspace 博客

使用 RStudio 进行版本控制

原文:https://towardsdatascience.com/version-control-with-rstudio-d20c54bd5b49

对于 Git 和 GitHub

RStudio 用于版本控制的典型界面。作者出于教育目的创建的图像

1.一般概念

科学最重要的一个方面是可重复性。每个实验都必须可以被其他人和团队复制。在计算机科学的背景下,科学计算、模拟和数据管理必须像任何其他实验一样是可复制的。为了确保结果是可重复的,必须能够将代码和分析分解为以前的版本,并复制图。这些过程和步骤是通过版本控制实现的。

版本控制是一个重要的过程,用于备份变更文件、存储和访问带注释的历史,以及管理不同变更集之间的变更合并。版本控制广泛应用于学术界、研究实验室和工业界。版本控制最重要的目标是简化和最大化软件开发人员的产出。

在过去的几年中,分布式版本控制如 Git、bazaar 等。,变得非常受欢迎。具体来说,Git 和 GitHub 几乎已经成为处理代码和文件的团队的默认选择,因为它们更加强大和用户友好。

Git 是一个跟踪变化的软件,GitHub 是一个使用 Git 进行操作的在线仓库。它们相互关联,但不是一回事。Git 用于本地版本控制(你的电脑),GitHub 用于远程版本控制(互联网和云)。

在大多数情况下,Git 直接通过用户终端与命令行一起使用。然而,也有可能将 Git 和 GitHub 与不同的图形用户界面一起使用,以简化事情。Git 和 GitHub 最重要的 GUI 是 GitHub 桌面RStudio

在本文中,我将讨论如何使用 RStudio 对 Git 和 GitHub 进行版本控制。这里我假设读者知道如何使用 Git 和 GitHub,也知道什么是 RStudio。我还假设读者已经在其计算机中安装了 Git、RStudio,并且还拥有一个 GitHub 帐户。

2.用于版本控制的 RStudio 配置

一旦您安装了 Git 和 RStudio,您必须在计算机上有一个本地 Git 存储库。要做到这一点,首先在您的计算机中的所需位置创建一个文件夹(例如“Git_examples”)。创建文件夹后,打开终端(在我的例子中是 MacBook pro)并运行以下代码:

$ **cd** .../Git_examples
$ **git init** 

在上面的代码中,我首先移动到空文件夹“Git_examples ”,然后初始化一个 Git 存储库。我假设读者熟悉这些简单的概念。当初始化 Git 储存库时,文件夹不一定需要为空。它可以是空的,也可以是包含多种不同类型文件的文件夹。 git init 命令在“Git_examples”目录中创建一个隐藏文件夹,目的是跟踪目录中的所有文件。如果您的计算机上已经有了一个初始化的 Git 存储库,那么就没有必要继续上面描述的步骤,原因将在下面解释。

现在打开 RStudio IDE。一旦你打开它,你将看到 RStudio 的默认外观。第一步是在 RStudio 中激活版本控制。进入:工具全局选项,然后点击 Git/SVN ,如下图 1 所示。如果你已经安装了 Git,很有可能在 Git 可执行文件中会出现 Git 在你电脑中的安装路径。您可以保留默认路径,也可以用另一个路径来更改它。如果您的计算机中也安装了 SVN,那么 SVN 可执行文件字段也将被填充。在我的例子中,没有安装 SVN,因此默认情况下 RStudio 会写入(未找到)。也可以创建一个 SSH RSA 密钥,但是在本文中,我将使事情更简单。

完成这些步骤后,选择复选框:启用 RStudio 项目的版本控制接口,然后点击对话框底部的“应用”按钮。现在 RStudio 中启用了版本控制。此时,建议重启 RStudio。

图一。作者创造的形象。

重启 RStudio 后,点击按钮“创建一个项目”,如图 2 中箭头所示。一旦你点击了“创建一个项目”按钮,一个对话窗口“新项目向导”将出现在主窗口的顶部。在这个对话框中,“创建项目”下通常有三个默认选项:新目录、现有目录版本控制

图二。作者出于教育目的创建的图像。

项目向导中的每个目录都有特定的用途。第一个(新目录)与新的 R 项目目录相关,第二个(现有目录)使得将新项目与现有工作目录相关联成为可能,第三个(版本控制)本质上与现有 GitHub 目录相关。

在本文中,我将向您展示如何将一个项目与一个现有的工作目录相关联。正如您所记得的,我已经向您展示了如何在一个给定的目录中启动一个 Git 存储库,因此我将使用该目录继续进行。点击现有目录,你将看到如图 3 所示的窗口。然后点击“Browse”按钮,选择已经启动 Git 存储库的文件夹(在我的例子中是“Documents/Git_examples”),然后点击“Create Project”。之后,项目被创建为 Git 存储库。

图 3。作者创造的形象

3.准备并提交 RStudio

使用 RStudio 创建项目后,您的默认窗口应该如图 4 所示。花点时间探索这个窗口,你通常会观察到许多不同的选项。在左侧窗口的一半,您将看到 R 控制台外壳、终端外壳和作业外壳。在右边的一半,在上面的面板中你会看到不同的面板,其中有一个“Git”面板。在下半部分,您将看到 Git 存储库所在的目录和文件。

你可以看到在文件区有。gitignoreGit_examples。Rproj 文件。当一个人像我上面所做的那样创建一个新的项目时,这些文件是自动创建的。我创建的目录没有其他文件,除了在我们开始项目时由 RStudio 自动创建的文件。有两种方法可以将文件添加到我们的目录中,要么直接使用 RStudio 创建文件,要么像处理其他文件夹一样将文件添加到我们的存储库文件夹中。

要创建一个包含 RStudio 的文件并将其添加到我们的存储库中,请单击绿色的加号按钮,如图 4 中的箭头所示。

图 4。作者创造的形象。

单击图 4 所示的绿色按钮后,您将看到一个类似于图 5 所示的窗口。正如你在对话窗口中看到的,RStudio 为用户提供了创建不同格式文件的可能性,如文本文件、Python 脚本、R 脚本等。让我们通过选择如图 5 中箭头所示的字段来创建一个文本文件。

图 5。作者创造的形象。

在文本文件字段中单击一次后,RStudio IDE 中将出现一个新的节窗口,其中文件窗口位于左侧的上部窗口中。可以向文本文件添加一些文本(例如:“我的第一个版本控制文件”),并且在点击“另存为…”图标之后,如图 6 中的箭头所示。将出现一个提示窗口,询问保存文件的位置、名称等。

默认情况下,保存目录是存储库目录。例如,通过单击“另存为…”图标,以“First_file.tex”的名称保存文件。之后,已经创建并保存的文件将出现在右半部分窗口下面板的 Git 存储库“Git_examples”文件夹中。它还会出现在右半部分窗口上面板的“Staged/Commit”Git 面板中。还可以看到终端外壳,如果需要,可以方便地通过终端直接编写命令。

图 6。作者创造的形象。

Git 跟踪了我在存储库文件夹中创建的“First_file.tex ”,但是还没有提交。首先要做的是在 Git 窗口的左上角选择“Staged”框,如图 6 所示。之后,文件的状态将从"?到“A ”,其中后者意味着它已经被添加到登台区,并且该文件准备好被提交。对于其他 Git 操作,还可以注意到“Diff”、“Log”和“Revert”图标。

图 6。

为了提交文件,有两个选项,如图 6 中的箭头所示。无论哪种方式,选择提交更改,然后通过点击“提交”按钮,在主窗口的顶部将出现如图 7 所示的对话窗口。此后,用户在“提交消息”文本框中写入提交消息,然后单击“提交”按钮保存消息。然后关闭对话窗口。

图 7。

在您执行了图 7 之前的所有步骤之后,您将看到一个窗口,如下图 8 所示。您可能会注意到,“First_file.tex”现在已经从 Git 面板中消失了,因为我们已经提交了这个文件。在此面板中,仅显示暂存和未跟踪的文件。另一件需要注意的事情是,在 Git 面板中,在我们提交“First_file.tex”文件之后,RStudio 将分支的状态从“no branch”更改为“master”。

图 8。作者创造的形象。

4.的”。gitignore”文件

当我们第一次创建 RStudio 项目时,会自动创建两个文件,其中一个是“”。gitignore" 文件。这个文件非常有用,因为它允许用户忽略并因此不在 Git 面板中显示用户不想包含在项目中的所有文件。因此,你需要考虑什么类型的文件不希望包含在项目中,并且在点击之后。gitignore 文件来修改它。我假设读者知道此时该做什么。将不需要的文件包含在中后。gitignore 文件,保存,大功告成。

5.远程版本控制(拉/推)

到目前为止,我已经向您展示了如何处理本地版本控制,即通过使用 RStudio 进行标准的 Git 操作。正如您在上面的图 8 中看到的,在 Git 面板中,绿色的 push 和 pull 数组是灰色的。原因是我们没有告诉 RStudio 使用任何像 GitHub 中那样的远程存储库。

要激活拉/推按钮,首先需要使用终端通过 push 命令添加一个远程源,或者从 GitHub 拉一个存储库。这里我展示了如何将本地存储库推送到 GitHub。

进入 RStudio IDE 中的终端,如上图 8 所示。默认情况下,终端设置为项目存储库文件夹。在终端中键入以下命令行:

$ **git remote add origin** URL

这里的 URL 指的是 GitHub 上资源库的完整链接。这实质上意味着在编写上面的命令行之前,用户已经在 GitHub 上有了一个存储库!

在上面的命令行中按下 enter 键后,很可能会出现一个对话框,要求您输入一些 GitHub 凭证,如用户名和/或密码。从 2021 年 8 月 13 日起,GitHub 改变了 Git 操作的认证规则,现在人们需要一个令牌来代替密码。

将本地存储库推送到 GitHub 需要在终端中编写的下一个命令行如下:

$ **git push --set-upstream** **origin master**or$ **git push -u origin master**

在您输入上面的 push 命令之后,您的本地存储库已经被推送到 GitHub,您可以通过查看适当的 GitHub 帐户来直接检查它。如果你做了上述操作,RStudio 上的灰色推/拉按钮将被激活,然后你就可以直接使用这些按钮与 GitHub 进行交互,参见本文顶部的图。

6.结论

在本文中,我向您展示了如何使用 RStudio 进行 Git/GitHub 操作。正如我上面提到的,Git 有几个图形用户界面,比如 GitHub Desktop,但是在我看来,RStudio 优于这些图形用户界面中的大多数。

RStudio IDE 非常用户友好,在主窗口中,可以有终端、Git 面板、文件夹目录以及创建和修改文件的空间。事实上,使用 RStudio,您可以轻松创建不同类型的文件,如 Python 脚本、R 脚本、Tex 文件,甚至编译 LaTeX 文件。

RStudio 有许多选项可以简化存储库中的 Git 操作。事实上,我相信每个在终端中使用 Git 命令行的人很快就会意识到,对于非常大的项目来说,这些命令行会变得非常麻烦和难以使用。RStudio 大大简化了这些 Git 操作。如果您需要使用终端进行一些特定的 Git 操作,您可以在主 RStudio 窗口中使用终端 shell,如图 8 所示。

虽然在本文中,我试图涵盖 RStudio 版本控制最重要的方面,但读者也可以在这里寻找一些额外的信息

如果你喜欢我的文章,请与你可能对这个话题感兴趣的朋友分享,并在你的研究中引用/参考我的文章。不要忘记订阅将来会发布的其他相关主题。

视图:SQL 中被低估的表的替代品

原文:https://towardsdatascience.com/views-an-underrated-alternative-to-tables-in-sql-26cd6d1d8660

SQL 用户将从早期养成使用视图的习惯中受益匪浅

保罗·斯科鲁普斯卡斯Unsplash 上拍摄

视图通常被称为“虚拟表”,是许多 SQL 脚本中不可或缺的组件。

尽管如此,大多数钻研 SQL 的资源很少关注视图,而是选择更重视更高级的查询概念,如连接和 cte。

这可能是因为视图在语法方面更容易掌握,并且在编码访谈中不常见。

不管怎样,视图有巨大的效用。SQL 用户将从早期养成使用它们的习惯中受益匪浅。

什么是视图?

视图只是在给定数据库上运行的存储查询的输出。

它们经常被比作表格,因为它们也以行和列的形式报告数据。

然而,与表不同,视图实际上不是数据库模式的一部分。使用视图只需要运行它存储的查询。因此,它们需要内存,但不需要额外的存储空间。

视图的好处

视图在 SQL 中非常有用,因为它们能够简化操作和管理访问控制。

  1. 简化复杂查询

虽然有些 SQL 查询简单易懂,但其他查询可能很复杂,需要许多行代码。

考虑已经被规范化的数据库,其结果是包含多个表。不断编写使用多个连接或 cte 来获取相同信息的查询会很麻烦。这不仅费时,而且让用户容易出错。

幸运的是,当试图重复访问相同的信息时,没有必要经历这么多麻烦。视图可以让用户以最小的努力重复查询,从而消除所有不必要的复杂性。

2。管理访问控制

安全性是数据库管理中的一个关键因素。理想情况下,人们应该只被授权访问符合其角色或地位的数据。

您不希望存储在表格或列中的任何个人信息或机密信息被错误的人看到。

数据库管理员可以通过创建视图并授予其他人访问这些视图的权限来规避这个问题。通过这种安排,任何外部方(例如客户)都无法看到他们不应该知道的信息。

在 SQL 中使用视图

由于 SQL 的用户友好语法,视图易于创建和管理。

作为视图的演示,我们有两个表,名为PeopleJob,我们将使用 PostgreSQL 查询它们(语法可能会因所选的 RDMBS 而异)。

代码输出(由作者创建)

代码输出(由作者创建)

假设您想要定期跟踪“高收入者”的记录,这些人的工资至少为 20 万英镑。您可以通过以下查询获得这些记录。

代码输出(由作者创建)

该查询将返回期望的输出,但是重复编写多行代码来获得相同的输出会令人厌烦。

此外,向外部客户机显示这些信息的用户可能不希望他们也能访问数据库中的first_name列。对于这样的场景,创建一个只存储所需列的视图,然后将这些列提供给客户机,这将是非常有益的。

  1. 创建视图

创建视图的语法类似于创建表的语法。

上述查询可以存储在具有以下内容的视图中:

现在,我们可以像查询任何其他表一样查询视图。

代码输出(由作者创建)

2。更新视图

可以用CREATE OR REPLACE命令更新视图。

假设定义高收入者的起征点应该是 15 万,而不是 20 万。

可以修改视图来解决这一变化。

现在查询同一个视图会产生稍微不同的输出。

代码输出(由作者创建)

虽然CREATE OR REPLACE命令能够修改视图,但是它有自己的局限性。也就是说,它希望查询输出保留相同的列数、列名和列数据类型。因此,任何导致数据结构变化的修改都会产生错误。

3。查看所有视图

在 SQL 脚本上花费大量时间后,很容易失去对所有创建视图的跟踪。此外,记住存储在这些视图中的复杂查询可能会很麻烦。

对于这种情况,可以使用下面的查询找到所有创建的视图。

代码输出预览(由作者创建)

注意,包含了WHERE命令是为了省略 PostgreSQL 中的内置视图。

在输出中,table_name列显示了视图的名称,而view_definition列显示了视图中存储的查询。

4。删除视图

最后,可以用简单的一行程序删除视图。

结论

照片由 Prateek KatyalUnsplash 上拍摄

视图不仅使用户能够简化复杂的查询,还允许他们限制其他人对个人或机密信息的访问。

总而言之,对于那些希望在与同事或外部客户协作的同时高效地执行 SQL 数据分析的人来说,它们非常有用。

我祝你在数据科学的努力中好运!

Python 中的视图和副本

原文:https://towardsdatascience.com/views-and-copies-in-python-1e46b5af4728

“我学得越多,就越意识到自己有多少不知道。”

本文解释了在使用切片、花式索引和布尔索引等操作时,如何复制或引用 Python 列表、NumPy 数组和 pandas 数据框。这些操作在数据分析中非常常见,不能掉以轻心,因为错误的假设可能会导致性能损失甚至意外的结果。Python 看起来很简单,但是每次回到基础,总会有新的东西需要探索和学习。副标题中阿尔伯特·爱因斯坦的名言适用于一切,Python 也不例外。

图片由来自 PixabayFrank 提供

介绍

我经常怀疑自己什么时候真正了解一门学科。完成了博士学位,并在过去做了一段时间的研究员,我可以自信地说,答案是绝对不会。我使用 Python 已经有一段时间了,它是一个非常棒的数据分析工具。我为现实生活中的问题提供了非常需要的解决方案,并产生了影响。尽管如此,每次我回到基础,都有新的东西要学,或者有新的角度来看待事物。当阅读一本书的介绍部分时,这种想法经常被触发,这些部分在书的本质真正开始之前被认为是容易阅读的。阿尔伯特·爱因斯坦的这句话在我的脑海中回响,不久之后,我使用 Python 解释器来尝试一些东西,想知道我到底是如何发现如此基础的东西的。我也想知道如果有这么多问题我什么时候能看完这本书,但是时间管理是另一个痛苦的故事…

这篇文章就跟随了这样一个时刻。它旨在深入解释 Python 列表、NumPy 数组和 pandas 数据框在使用切片、花式索引和布尔索引等操作时如何创建视图或副本。有一些混淆,因为像浅拷贝和深拷贝这样的术语并不总是指同一件事,同时也不清楚什么时候像 NumPy 数组元数据和 Pandas 索引这样的辅助数据被拷贝或引用。这篇文章可能不会提供所有的答案,但是希望它除了提供文档参考之外,还提供了一个在有疑问时运行快速计算实验的框架。所有示例都是用 Python v3.8.10、pandas v1.5.1 和 NumPy v1.23.4 编写的。

Python 列表

在这一节中,我们将运行一些计算实验来理解如何创建 Python 列表的副本。如果您运行类似的实验,请记住 Python 在内存中缓存小整数和字符串,以便它可以引用预先制作的对象,而不是创建新的对象。这个所谓的 interning 是本文中使用的标准 Python 实现 CPython 的优化之一。使用不同的字符串和整数是明智的,这样可以避免在查找对象地址时产生混淆。

Python 列表是人们可能认为容易的部分。让我们创建一个包含整数、Python 列表和嵌套 Python 列表作为元素的 Python 列表。我们还创建了一个实用函数来打印各种 Python 列表元素的地址,为了简洁起见,只显示了地址的最后四位数字。

上面的代码打印出来

a                   : 4160 | 7728 |  9888 3376 | 3232 0848 2480

注意,地址在每次执行中当然是不同的。因此,我们将确保数组a从现在开始不会被改变。让我们尝试不同的方法来复制数组a,从简单的(重新)绑定到另一个变量到深度复制

那会打印

new binding         : 4160 | 7728 |  9888 3376 | 3232 0848 2480
shallow copy I      : 7072 | 7728 |  9888 3376 | 3232 0848 2480
shallow copy II     : 9312 | 7728 |  9888 3376 | 3232 0848 2480
shallow copy III    : 1488 | 7728 |  9888 3376 | 3232 0848 2480
shallow copy IV     : 8128 | 7728 |  9888 3376 | 3232 0848 2480
deep copy           : 0528 | 7728 |  6848 3376 | 0816 2960 2480

首先要观察的是,除了新绑定(第一行)之外,列表的地址(第一列有地址)在所有其他尝试中都发生了变化。这意味着进行了复制。代码提供了四种不同的方法来创建浅层副本,这意味着列表的元素是相同的对象,尽管列表本身不是。如果我们试图改变列表的浅层副本的不可变元素,原始列表将不会被修改,但是改变可变元素会改变原始列表。举个例子,

印刷品

a_demo (before)     -> ['d1', ['d2', 'd3']]
a_demo (after)      -> ['d1', ['**D2**', 'd3']]
a_demo_shallow_copy -> ['**D1**', ['**D2**', 'd3']]

这意味着在嵌套列表和使用其他可变列表元素的情况下,浅拷贝会导致副作用。在深层拷贝的情况下,我们是安全的,如下面的代码所示

那会打印

a_demo (before)  -> ['d1', ['d2', 'd3']]
a_demo (after)   -> ['d1', ['d2', 'd3']]
a_demo_deep_copy -> ['**D1**', ['**D2**', 'd3']]

以上是相当简单的概括。任何类型的 Python 列表切片,如a[:]a[1:4]a[:5]a[::-1],都会创建列表保留部分的浅层副本。但是当我们连接或相乘列表时会发生什么呢?你能预测下面的操作会发生什么吗?

上面的版画

a                   : 4160 | 7728 |  9888 3376 | 3232 0848 2480
b (first part)      : 5712 | 7728 |  9888 3376 | 3232 0848 2480
b (second part)     : 5712 | 7728 |  9888 3376 | 3232 0848 2480a                   : 4160 | 7728 |  9888 3376 | 3232 0848 2480
b (first part)      : 5648 | 7728 |  9888 3376 | 3232 0848 2480
b (second part)     : 5648 | 7728 |  9888 3376 | 3232 0848 2480
b (third part)      : 5648 | 7728 |  9888 3376 | 3232 0848 2480

这意味着我们创建了列表元素的更多引用(绑定),也就是说,这就像创建了一个浅层拷贝。这可能会导致意想不到的副作用,如下面的实验所示

那会打印

a_demo (before) -> ['d1', ['d2', 'd3']]
b               -> ['**D1**', ['**D2**', 'd3'], 'd1', ['**D2**', 'd3']]
a_demo (after)  -> ['d1', ['**D2**', 'd3']]

再说一次,请随意进行如上所述的简单计算实验。Python 是一种很好的实验语言,因为它的语法简单、简洁。

NumPy 数组

与 Python 列表类似,NumPy 数组也可以通过视图复制或公开。为了说明该功能,我们将通过绘制 0 到 9 范围内的随机整数来创建一个数组

我们还定义了一个实用函数来显示数组内容、数组元素消耗的总字节数、数组在内存中的总大小、一个布尔值来显示数组是拥有它使用的内存还是从另一个对象借用的内存,如果内存来自其他对象,则显示基本对象。上面的版画

[[8 2 8 8 1]
 [7 4 2 8 8]
 [3 3 2 3 3]
 [0 0 7 6 8]
 [2 7 3 4 6]]datatype is int64
number of bytes is 200 bytes (25 x 8 bytes)
size is 328 bytes
owndata is True
base is None

该数组将其数据类型显式设置为int64,因此每个数组元素消耗 8 个字节。总共 25 个数组元素消耗 200 个字节,但是由于数组元数据,例如数据类型、步长和其他有助于轻松操作数组的重要信息,内存大小为 328。我们可以看到数组保存自己的数据,因此它的基数是None

让我们看看如果我们创建一个视图会发生什么

那会打印

[[8 2 8 8 1]
 [7 4 2 8 8]
 [3 3 2 3 3]
 [0 0 7 6 8]
 [2 7 3 4 6]]datatype is int64
number of bytes is 200 bytes (25 x 8 bytes)
size is 128 bytes
owndata is False
base is [[8 2 8 8 1]
 [7 4 2 8 8]
 [3 3 2 3 3]
 [0 0 7 6 8]
 [2 7 3 4 6]]

数组的内容保持不变。类似地,数组元素的数据类型和字节数保持不变。其余的数组属性现在不同了。大小已经减少到 128 字节(即 238–200 字节),因为数组视图为 NumPy 数组属性消耗内存。数组元素没有被复制而是被引用。这从不再是None的基本属性中可以明显看出。在 NumPy 术语中,视图有相同的数据缓冲区(实际数据),但有自己的元数据。修改视图的元素也会修改原始数组。

让我们看看创建拷贝时会发生什么

那会打印

[[8 2 8 8 1]
 [7 4 2 8 8]
 [3 3 2 3 3]
 [0 0 7 6 8]
 [2 7 3 4 6]]datatype is int64
number of bytes is 200 bytes (25 x 8 bytes)
size is 328 bytes
owndata is True
base is None

输出看起来与原始数组相同。修改副本的元素不会修改原始数组。

我们可以很容易地试验各种整形、切片和索引功能,以检查是否创建了视图或副本

那会打印

reshape produces a view
transpose/reshape produces a view
ravel produces a view
transpose/ravel produces a copy
transpose/ravel (F-order) produces a view
flatten produces a copy
transpose/flatten produces a copy
slicing produces a view
advanced indexing produces a copy
combined indexing and slicing produces a copy
Boolean indexing produces a copy

对于某些功能,行为并不总是相同的。例如,[numpy.ravel](https://numpy.org/doc/stable/reference/generated/numpy.ravel.html)返回一个连续的扁平数组,该数组仅在需要时才是副本。另一方面,[numpy.ndarray.flatten](http://numpy.ndarray.flatten)总是返回折叠成一维的数组的副本。[numpy.reshape](https://numpy.org/doc/stable/reference/generated/numpy.reshape.html)的行为有点复杂,感兴趣的读者可以参考官方文档。

需要说明的是,NumPy 在原始数组中用偏移量和跨距寻址元素时,即在使用基本索引和切片时,会创建视图。这与 Python 列表的行为相矛盾!另一方面,高级索引总是创建副本。整形操作更复杂,是否返回副本或视图取决于上下文。

使用高级索引创建的副本,以及使用[numpy.copy](https://numpy.org/doc/stable/reference/generated/numpy.copy.html)创建的副本不会深入复制数组中的可变元素。与 Python 列表的浅层副本一样,NumPy 数组副本包含相同的对象,如果该对象可以被修改(可变),这可能会导致意外:

那会打印

Numpy shallow copy
a_np_demo (before) ->  [1 2 list([3, 4])]
b                  ->  [**-1** 2 list([**-3**, 4])]
a_np_demo (after)  ->  [1 2 list([**-3**, 4])]Python deep copy
a_np_demo (before) ->  [1 2 list([3, 4])]
b2                 ->  [**-1** 2 list([**-3**, 4])]
a_np_demo (after)  ->  [1 2 list([3, 4])]

这可能是理论上的兴趣,因为 NumPy 数组通常不用于存储可变对象。不过,很高兴知道copy.deepcopy()起作用了。

熊猫数据框

现在让我们把注意力转向熊猫数据框。按照通常的方式,我们将定义一个熊猫数据框和一个效用函数来显示它的细节

该数据与早期的 NumPy 数组具有相同的数据结构,即它具有 5x5 int64 个元素,但是我们另外定义了索引和列名。效用函数已被修改。pandas 数据帧的不同列可以有不同的数据类型,因此我们用a_df.dtypes.unique()返回唯一的数据类型。为了查看底层数据何时被复制或引用,我们首先使用a_df.to_numpy()获取底层 NumPy 数组,然后使用数组接口获取指向数据第一个元素的指针。上面的版画

dataframe is
     c0  c1  c2  c3  c4
r0   5   2   8   6   6
r1   1   9   1   1   1
r2   0   7   6   3   7
r3   7   4   9   5   2
r4   5   8   3   7   1datatypes are [dtype('int64')]
number of bytes is 200 bytes (25 x 8 bytes)
size is 511 bytes
pointer to data area 2893487649296

我们现在有足够的设备来试验拷贝和视图。

查看 pandas API 参考,我们可以找到一个数据帧复制函数,它接受一个deep布尔参数:当 True(默认)时,一个新对象被创建,带有调用对象的数据和索引的副本(这不是标准库的copy.deepcopy()意义上的深度副本;见下文!).我们可以修改数据和索引,而原始数据框将保持不变。如果为 False,将创建一个新对象,而不复制调用对象的数据或索引,也就是说,我们只创建对数据和索引的引用。这意味着对原始数据框数据的任何更改都将反映在副本中。

让我们用一种观点做实验

那会打印

dataframe is
     c0  c1  c2  c3  c4
r0   5   2   8   6   6
r1   1   9   1   1   1
r2   0   7   6   3   7
r3   7   4   9   5   2
r4   5   8   3   7   1datatypes are [dtype('int64')]
number of bytes is 200 bytes (25 x 8 bytes)
size is 511 bytes
pointer to data area 2893487649296
Same base: True
Same row index: True
Same column index: True

我们可以看到数据区指向同一个内存地址,NumPy 数组基是同一个对象,两个索引也是同一个对象。

让我们创建一个副本(deep=True是默认的,但为了清楚起见,我们包括它)

那会打印

dataframe is
     c0  c1  c2  c3  c4
r0   5   2   8   6   6
r1   1   9   1   1   1
r2   0   7   6   3   7
r3   7   4   9   5   2
r4   5   8   3   7   1datatypes are [dtype('int64')]
number of bytes is 200 bytes (25 x 8 bytes)
size is 511 bytes
pointer to data area 2893487655536
Same base: False
Same row index: False
Same column index: False

我们可以看到,副本与原始数据帧具有不同的基础,这也反映在不同的数据区指针中。我们还为这两个索引创建了新的对象。同样,类似于 NumPy 数组的情况,如果数据帧包含可变元素,那么改变副本的这些可变对象会修改原始数据帧,如下面的数值实验所示

那会打印

a_df_demo (before) ->  c1 c2 0 1 3 1 2 {'key1': '✓', 'key2': '✓'}
b                  ->  c1 c2 0 1 3 1 2 {'key1': '✓'}
a_df_demo (after)  ->  c1 c2 0 1 3 1 2 {'key1': '✓'}

对于熊猫数据框来说,这并不是一个非常常见的用例,但是记住这一点还是很有用的。对熊猫来说不幸的是,似乎不可能通过使用 Python 的copy.deepcopy()函数从标准库中获得真正的深度副本,因为熊猫开发者已经将实现为 pd.DataFrame.__deepcopy__()pd.DataFrame.copy(deep=True)。不确定这在将来是否会改变,但无论如何它都被认为是一个反模式。熊猫在这方面与 NumPy 不同。

我们现在可以看看用熊猫选择行和列的各种方法

那会打印

select one column uses the same base
select one column using [] does not use the same base
select one column with loc uses the same base
select columns with loc and slicing uses the same base
select columns with loc and fancy indexing does not use the same base
select rows using loc and a Boolean mask does not use the same base
select rows with loc and slicing uses the same base
chained indexing uses the same base

基本的索引和切片,比如使用(单个)方括号或.loc[]访问器的简单列索引使用相同的基,而所有其他操作不使用。当有疑问时,上述计算实验框架可以给出快速答案。不幸的是,检查基数是否保持不变并不总是足以预测使用链式索引时会发生什么(见下文),但它提供了一个起点。在最后一次尝试中,基数保持不变,但是如果我们使用这种链式索引来设置值,原始数据帧仍然保持不变。然而,反过来似乎是正确的:如果基数改变了,那么我们就在拷贝上操作。我将欢迎对此的评论,因为我开始如履薄冰。请继续阅读。

现在让我们转向与熊猫有关的最后一个话题,著名的链式索引和相关的SettingWithCopyWarning。使用先前定义的a_df数据框架,我们可以通过使用布尔索引来尝试改变列中某些元素的值。假设我们使用链式索引,有两种方法可以想到

那会打印

attempt 1
    c0  c1  c2  c3  c4
r0   5   2   8   6   6
r1   1   9   1   1   1
r2   0   7   6   3   7
r3   7   4   9   5   2
r4   5   8   3   7   1attempt 2
    c0  c1  c2  c3  c4
r0   5   2   8   6   6
r1   1   9   1  -1   1
r2   0   7   6  -1   7
r3   7   4   9   5   2
r4   5   8   3  -1   1<ipython-input-789-06440868e65b>:5: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead
See the caveats in the documentation: [https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy](https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy)
  a_df_demo.loc[msk]['c3'] = -1

评估顺序事项。第一次尝试给出了一个SettingWithCopyWarning,这并不奇怪。使用带有布尔掩码的.loc[]访问器创建一个副本。为副本的元素分配新值不会更改原始数据框。这是预期的行为,但是 pandas 更进一步,通知用户。NumPy 就没那么善良了。但是即使是熊猫,也不要总是依赖警告,因为它可能不会发布。例如

不给出任何警告,数据框也不会像上面打印的那样被修改

 c0  c1  c2  c3  c4
r0   5   2   8   6   6
r1   1   9   1   1   1
r2   0   7   6   3   7
r3   7   4   9   5   2
r4   5   8   3   7   1

这些我们都要记住吗?答案是否定的,不仅因为事实上不可能列举所有不同的链式索引的可能性,还因为当涉及到发出SettingWithCopyWarning时,不能确定不同的 pandas 版本中的行为是否保持相同。更糟糕的是,数据框可能会随着一个熊猫版本而改变,而不是另一个版本(我个人对此没有证据,但这是我的担心)。使用一个虚拟环境并建立一个需求文件不仅可以防止依赖性地狱,还可以防止这样的问题,尽管最佳实践是知道哪些熊猫任务是有风险的并避免它们。当数据帧具有层次索引和不同的数据类型时,情况甚至更加复杂。预测链式索引的结果是不安全的。

避免链式索引的正确方法是使用单个访问器来设置值,例如

打印修改后的数据帧,并且不发出警告。

这一切都是关于使用单个访问器吗?

使用单个访问器和避免使用赋值的链式索引绝对是可靠的建议,但是还有更多令人惊讶的地方。让我们创建一个数据帧的切片,并检查原始数据帧被修改后会发生什么情况。

第一次尝试做了三个这样的实验

实验的不同之处仅在于原始数据帧的修改是如何发生的。第一个实验只修改了列a中的一个元素,第二个实验使用df.loc[0:,'a']修改了整个列a,第三个实验也修改了整个列a,但是如果我们看看我们得到的结果,这次使用的是df.loc[:,'a'].

experiment 1
data buffer pointer (before) -> 2893435341184
data types (before) -> [dtype('int32')]
my_slice (before) -> [**1, 2, 3, 1, 2, 3]**
data buffer pointer (after)  -> 2893435341184
data types (after)  -> [dtype('int32')]
my_slice (after)  -> **[-10, 2, 3, 1, 2, 3]**experiment 2
data buffer pointer (before) -> 2893490708496
data types (before) -> [dtype('int32')]
my_slice (before) -> **[1, 2, 3, 1, 2, 3]**
data buffer pointer (after)  -> 2893490708496
data types (after)  -> [dtype('int32')]
my_slice (after)  -> **[-10, -10, -10, 1, 2, 3]**experiment 3
data buffer pointer (before) -> 2893435341184
data types (before) -> [dtype('int32')]
my_slice (before) -> **[1, 2, 3, 1, 2, 3]**
data buffer pointer (after)  -> 2893491528672
data types (after)  -> [dtype('int64'), dtype('int32')]
my_slice (after)  -> **[1, 2, 3, 1, 2, 3]**

该切片以粗体显示,可以很容易地看到发生了什么。在成功修改数据帧之后,切片在前两个实验中被修改,而在第三个实验中没有被修改。如果您仔细观察,就会发现数据框的一个列类型被更改为int64,并且它的数据缓冲区在内存中被重新定位。我假设这是因为我们改变了整个列a的值,使其成为int64。如果我们在创建数据框时显式设置数据类型,这似乎可以得到证实

那会打印

experiment 1
data buffer pointer (before) -> 2893491528672
data types (before) -> [dtype('int64')]
my_slice (before) -> [**1, 2, 3, 1, 2, 3]**
data buffer pointer (after)  -> 2893491528672
data types (after)  -> [dtype('int64')]
my_slice (after)  -> **[-10, 2, 3, 1, 2, 3]**experiment 2
data buffer pointer (before) -> 2893486517968
data types (before) -> [dtype('int64')]
my_slice (before) -> **[1, 2, 3, 1, 2, 3]**
data buffer pointer (after)  -> 2893486517968
data types (after)  -> [dtype('int64')]
my_slice (after)  -> **[-10, -10, -10, 1, 2, 3]**experiment 3
data buffer pointer (before) -> 2893491528672
data types (before) -> [dtype('int64')]
my_slice (before) -> **[1, 2, 3, 1, 2, 3]**
data buffer pointer (after)  -> 2893491528672
data types (after)  -> [dtype('int64')]
my_slice (after)  -> **[-10, -10, -10, 1, 2, 3]**

我想留下一个挥之不去的片段是没有意义的,除非它是一个带有df.loc[1:3].copy()的显式副本。否则,人们总是可以在需要时对数据帧进行切片,并且总是有新的数据。但是,这仍然是一个有效的计算实验,可以学习更多关于视图和副本的知识。

结论

理解 Python 何时创建副本和视图需要一些实践。Python 列表、NumPy 数组和 pandas 数据框提供了创建副本和视图的功能,如下表所示(作为 GitHub gist 为中等作者创建)

然而,最重要的带回家的信息与使用 NumPy 数组和 Pandas 数据帧时基于索引的赋值行为有关:

  • NumPy 链式索引通常很容易理解:基本索引产生视图,而高级索引返回副本,防止在赋值时修改原始数组;当使用整形操作时,行为更加复杂
  • 应该避免熊猫链式索引,而应该为所有赋值使用单个访问器;即使人们认为链式索引的行为是可以预测的,这也是事实

理解视图和副本至关重要,尤其是在处理大型数组和数据框时。希望这篇文章能为进一步阅读提供一个起点。我确信我错过了一些方面,很可能我误解了一些细微的差别。在你的评论中指出这些将再次证明阿尔伯特·爱因斯坦的引用是多么正确!

推荐进一步阅读

视觉问答挑战

原文:https://towardsdatascience.com/visual-question-answering-challenge-51c4623ddfa5

一个稍微不同的 ML 竞赛和你为什么应该参加

视觉问题:你用什么击球?—图片来自 COCO 女士数据集(知识共享公共许可 )

简介

这篇文章介绍了一个有趣的 ML 比赛,现在可以在 CodaLab 上看到,你可以参加到今年年底(2022)。

起初,它看起来像一个标准的“Kaggle”风格的比赛,但让我告诉你为什么我觉得它特别有趣。显然,除了赢得一些奖金,你还可以在一个著名的关于搜索和数据挖掘的国际会议上展示成果( WSDM 2023 )。

什么是视觉问答(VQA)挑战赛?

这是一个结合了视觉图像和文字的挑战。对于每幅图像,给你一个自然语言问题,你的任务是检查图像的特征,并在物体周围画一个边界框,这是问题的答案。

看看下面的一些例子。第一张图片有一个自然语言问题“人们用什么来切割”?图片中这个问题的正确答案是“剪刀”。因此,带有剪刀的图像区域用边界框高亮显示。

图片来自可可女士数据集(知识共享公共许可 )

同样,在第二张图中,我们有一个问题“我们用什么来支持免疫系统和获取维生素 C?”。在这种情况下,通过粘合盒选择含有维生素 C 补充剂的包装。

为什么这次比赛与众不同

视觉问答挑战赛结合了视觉和自然语言输入数据,并要求理解和正确解释这两者。这种类型的任务对人类来说很容易,因为我们的大脑自然会这样做,但对于机器学习模型来说,这可能是一项非常具有挑战性的任务。

通常,这类任务的评估是通过使用交集/并集(IoU) 来完成的。在这个数据集上,平均而言,机器学习方法的评估 IoU 得分为 0.20,而人类的表现得分为 0.87。这是一个巨大的差距,尤其是在过去几年涉及计算机视觉和自然语言的多模态深度学习模型取得巨大进展之后,这是一个有趣的现象。

VQA 数据集

视觉问答挑战的数据集有 45,199 个图像实例,分为三个子集:

  • 训练(38,990 个图像实例),
  • 公开测试(1,705 个图像实例),
  • 和私有测试(4,504 个图像实例)。

VQA 数据集中包含的所有图像都来自经许可在 CC 下发布的 MS COCO 数据集,额外的地面真相答案是使用 Toloka 众包平台获得的。

如何赢得视觉问答(VQA)挑战赛

我可能无法完全回答这个问题,但至少让我提出一个你可以采取的方向。

查看与竞赛一同发布的这一视觉问答挑战的基准模型。它使用两个基本模型。第一个,YOLOR 是一个计算机视觉模型,它进行对象检测并在图像中生成候选矩形。第二个是 CLIP,它是一个多模态模型,结合了计算机视觉和自然语言特征,用于测量视觉问题和每个候选矩形所包围的图像部分之间的相似性。basline 的代码可以在这个 GitHub 页面上找到。

以上是一个很好的起点,但在评估指标中仅存档了 0.2 IoU 分数。如前所述,人类在这个数据集上取得了很高的精确度(0.87 IoU),因此还有很大的改进空间。

总结

我希望这篇文章解释了为什么视觉问答挑战是一个有趣的比赛,激励你参加它,并给你一个如何开始的想法。

可以直接在 CodaLab 网站参与。我还感兴趣的是,你将如何使用不同于基线建议的方法来解决这个计算机视觉和自然语言的挑战。欢迎在评论中分享你的想法。

PS:我正在 Medium 和https://www.aboutdatablog.com/上撰写深入浅出地解释基本数据科学概念的文章。你可以订阅我的* 邮件列表 *每次我写新文章都会收到通知。如果你还不是中等会员,你可以在这里加入**

下面是一些你可能会喜欢的帖子

** **

基于神经符号人工智能的 DeepProbLog 视觉问答

原文:https://towardsdatascience.com/visual-question-answering-with-deepproblog-using-neuro-symbolic-ai-621099805bc7

基于视觉问答的神经符号人工智能与纯神经网络方法的比较

本文重点关注视觉问题回答,其中将具有知识库的神经符号人工智能方法与纯粹基于神经网络的方法进行了比较。从实验中可以看出,用于神经符号人工智能方法的框架 DeepProbLog 能够实现与基于纯神经网络的方法相同的准确性,而迭代次数几乎减少了 200 倍。显然,这种训练更有针对性,但也是有代价的。DeepProbLog 内部的代数运算符非常昂贵,因此实际的训练时间要慢得多。DeepProbLog 的另一个缺点是无法轻松实现加速,因为代数运算符只在 CPU 上工作(至少目前是这样),因此无法从 GPU 等加速器中受益。

图片由 Philipp Marquetand 来自 Pixabay

1.介绍

神经符号人工智能领域感兴趣的是在概率知识的鲁棒性和深度神经网络的众所周知的普及性和已证实的优势之间建立一座桥梁。DeepProbLog [1]通过利用神经网络(即系统 1,典型的潜意识任务,如视觉识别、语言处理……)的优势,以及基于规则的概率系统(即系统 2,缓慢、有序的思维,如证明的推导)[2]的优势,提供了这种能力。

本文详细阐述了一个需要使用这两个系统的应用程序,即视觉问答。需要系统 1 来理解被研究的图像,特别是它们的形状和颜色。另一方面,系统 2 将使用该提取的信息来导出对象的某些属性(例如,找到绿色对象的形状),或者甚至用于捕捉对象之间的关系(例如,计算图像中的圆的数量)。

2.文学

该应用程序侧重于视觉问题回答(VQA),其中存在巨大的数据集,以及非常复杂的方法。VQA 最著名的数据集是 CLEVR [3],它包含 10 万张图片和 100 万个问题。下面给出了一个示例图像,而示例问题是:

  • 大型物体和金属球体的数量相等吗?
  • 大球体左边的棕色金属物体的左边的圆柱体有多大?
  • 有多少物体不是小圆柱体就是金属的东西?

图 CLEVR 数据集的样本图像[3]

显然,在回答这些问题时,系统 1 和系统 2 都被积极地使用。人们可能会想,在没有明确的系统 2 编码(即,基于规则的知识库)的情况下,神经网络是否能够独自回答这些问题。直觉上,如果知道世界上的某些事实,学习会进行得更快。从优化的角度来看,在这种设置中,预测期间产生的误差可以被精确地瞄准,这使得优化过程也更有针对性,因此更有效。最后,本文还为这些说法提供了证据,因为在第 4.1 小节中,VQA 实现与 DeepProbLog 之间的比较是用一种纯粹基于神经网络的方法进行的。

本文受 CLEVR 数据集的启发,但是使用了一个更加简化的版本。本质上,它几乎就像是一个分类数据集[4]。CLEVR 分类数据集包含如图 2 所示的图像,同时提出如下问题:

  • 非关系型问题:物体的形状、水平或垂直位置。
  • 关系问题:离被调查物体最近/最远的物体的形状,或者具有相同形状的物体的数量。

图 2:来自分类数据集的样本图像[4]

如前所述,这些类型的 VQA 需要系统 1 和系统 2。

A.桑托罗等人用本文中的 Sort-of-CLEVR 数据集进行了类似的实验,他们能够在 CNN 的关系问题上达到 63%的准确率。相比之下,CNN 的关系型和非关系型问题的准确率都达到了 94%,并辅以 RN。对他们来说,用一个关系模块,比如一个 RN,来扩充模型,结果足以克服解决关系问题的障碍[4]。他们是唯一在与本文相似的数据集上进行实验的研究人员,因此是唯一的参考点。

最后,由于这个应用程序使用 DeepProbLog,所以花了相当多的时间来消化 DeepProbLog 文章[1],以及理解代码库中提供的示例[5]。

3 方法

实施过程包括三个主要部分:

  1. 数据的生成
  2. 用纯 Python 代码链接数据和控制训练过程。
  3. 用 DeepProbLog 语句创建逻辑部分。

3.1 数据的生成

如第 2 节所述,本应用程序中使用的数据基于 Sort-of-CLEVR 数据集,并进行了额外的简化。假设逻辑部分将必须决定对象是否例如位于图像的左侧,则神经网络将必须向逻辑部分传送位置信息。因此,每个离散的位置必须由神经网络的可能结果编码。因此,对象可能只位于网格中的特定位置。在本文中,将讨论 2x2 和 6x6 网格上的结果。

用于创建分类数据集的数据生成器已经过修改,以便将对象定位在上述网格位置[4]。图 3 给出了一个生成图像的例子,与图 2 的区别在于网格布局。

图 3:这个应用程序使用的数据集的样本图像

每种指定的颜色都有一个位于网格中某处的对象,其形状可以是正方形或圆形。这些图像附有一个关于随机对象的问题,可以是以下内容之一:

  • 非相关—这个对象的形状是什么(正方形或圆形)?
  • 非相关—该对象是否位于图像的左侧
  • 非相关—该对象是否位于图像的底部
    侧?
  • 相关——有多少物体与
    这个物体具有相同的形状?

这些问题以矢量编码方式进行编码,然后与预期答案一起存储在 CSV 文件中。为了使训练过程更有效,已经预先生成了训练和测试数据集。

3.2 控制培训过程

整个训练过程是通过 DeepProbLog 的 Python API 以及 CNN 的一般 PyTorch 实现来控制的。首先,CNN 是用 PyTorch 定义的。使用相对简单的网络,其中输入作为 100 像素宽的正方形 RGB 图像给出,由 CNN 转换成 6x6 网格的 72 个输出特征(对于 2x2 网格的例子,需要 8 个输出特征)。图像中出现的每种颜色都有其伴随的 CNN 网络,因此 72 个输出特征用该颜色编码了物体的可能位置,以及它们的形状,可以是正方形或圆形(6 6 2 = 72)。

在开始训练过程之前,最后一件事(除了逻辑规则编码之外)是数据加载器。这里最具挑战性的部分是从生成的数据到特定查询映射及其结果的转换。

3.3 逻辑规则编码

一旦属于特定颜色的 CNN 已经确定了该对象的位置和形状,逻辑规则就可以推断出该对象是否位于图像的左侧、底部,以及有多少对象具有相同的形状。逻辑规则程序可以在相关的 Github 中找到,链接在本文的底部。显示了一个示例片段:

4 次实验

本文的主要重点是概述使用神经符号人工智能方法(由 DeepProbLog 框架提供)的优势,而不是纯粹的基于神经网络的方法。因此,必须实施基于神经网络的方法。这里不会列出所有细节,但主要思想是图像必须与问题融合,然后才能做出预测。该网络的一般结构如图 4 所示。

图 4:纯神经网络架构的抽象表示[6]

4.1 与纯系统 1 方法的比较

  • 实验— 2x2 网格:

图 5 和图 6 分别显示了 DeepProbLog 方法和纯神经网络方法的损耗曲线。

图 5: DeepProbLog 2x2:损耗曲线

图 6:基于纯神经网络的方法 2x2:损耗曲线

一个非常重要的注释是“迭代次数”和“纪元次数”之间的区别。迭代次数意味着一批(大小为 32)的向前和向后传递的次数,而时期数表示训练集的所有图像向前和向后传递的次数。在该应用中,使用了 10 000 的训练大小,因此一个时期由 312.5 次迭代组成。

从损失曲线可以清楚地看出,这两种方法似乎都达到了 100%的精度。然而,DeepProbLog 只需要大约 40 次迭代,而基于纯神经网络的方法至少需要 7 800 次迭代。这再次证明了神经符号人工智能的价值。

另一方面,我们必须考虑这些迭代的实际运行时间。DeepProbLog 需要大约 10 分钟来完成它的 160 次迭代,而纯粹基于神经网络的方法只需要大约 5 分钟来完成 7 800 次迭代。考虑到纯粹基于神经网络的方法可以被大规模加速(通过使用 GPU),而 DeepProbLog 不能,很明显 DeepProbLog 训练更有针对性,但计算量极大(至少目前如此)。

请注意,DeepProbLog 提供了将 CNN 发送到 GPU 以进行更快推断的能力,但是,DeepProbLog 的算术运算符(即半环)在 CPU 上工作。这些算术运算符拥有最高的计算成本。

  • 实验— 6x6 网格:

DeepProbLog 的 6x6 实验和基于神经网络的方法的损耗曲线分别如图 7 和图 8 所示。

图 7: DeepProbLog 6x6:损耗曲线

图 8:基于纯神经网络的方法 6x6:损耗曲线

在这些实验中,基于纯神经网络的方法的训练时间为 20 分钟(为了公平的速度估计,已经在 CPU 上进行了训练),而 DeepProbLog 方法的训练时间略低于八小时

然而,应该提到的是,每次大约有一半的时间花费在计算准确性上,因为整个测试集必须通过网络转发,而对于训练迭代来说,这只是一个批次。

这里最重要的观察是,纯神经网络方法很快过拟合,并且也实现了相当低的准确度(DeepProbLog 的 68%而不是 75%)。另一个重要的注意事项是,这两种方法显然不再能够达到 100%的准确度。然而,如果 DeepProbLog 网络可以训练更长时间,它将能够进一步收敛。

在图 9 和图 10 中,描述了混淆矩阵,以显示典型错误发生的位置。

图 9:来自 DeepProbLog 的 6x6 数据集的混淆矩阵

图 10:来自纯神经网络方法的 6x6 数据集的混淆矩阵

DeepProbLog 自然不会犯任何“无意义”的错误,比如对“正在研究的物体是什么形状?”这个问题回答“是”,因为可能的正确答案已编码在程序中。然而,基于纯神经网络的方法已经很好地学会了将问题的可能答案联系起来。

另一个观察结果是,DeepProbLog 在回答“正在研究的物体是什么形状?”这个问题时要好得多以及“物体是位于图像的左侧(还是底部)?”比纯粹的神经网络方法更有效。这是有意义的,因为一旦给定对象的位置(和形状)被确定,DeepProbLog 可以使用其知识库来导出这些属性。a .桑托罗等人[4]也观察到,基于纯神经网络的方法很难区分这些情况,在这些问题上,他们采用基于纯神经网络的方法实现了 63%的准确率。DeepProbLog 无法实现这些研究人员通过添加 RN 模块在所有问题上实现的 94%的准确性,这可能是由于代数运算符的固有培训成本,以及由于资源较少、超参数调整较少和缺少 RN 模块等许多其他未知变量。

关于关系问题:“有多少物体与被研究的物体具有相同的形状?”这两种方法都存在更多的混乱。DeepProbLog 通常能够接近正确的答案,但可能会犯一些“一个接一个”的错误(即一个 CNN 错误地预测了其对象的形状)。从这个角度来看,基于纯神经网络的方法也稍差一些。还有一种可能是,在这种方法中,神经网络可能已经观察到,由于概率原因,可能有三个或四个相同的对象(包括正在研究的对象),因此更喜欢这样的答案。

5 个结论

神经符号人工智能领域的优势已经在视觉问题回答的背景下得到证明。通过使用 DeepProbLog,为神经符号人工智能任务选择的框架,很明显,几乎需要 200 倍的迭代来实现与纯粹基于神经网络的方法相同的准确性。然而,由于昂贵的代数运算符,与纯粹基于神经网络的方法相比,DeepProbLog 方法的总训练时间要慢得多

值得注意的是,DeepProbLog 在非关系问题上表现得更好。这是因为知识库可以更准确地推导出这些属性,而基于纯神经网络的方法在这种推导上有更多的困难。

因此,尽管代数运算代价高昂,但神经符号人工智能方法仍有很多价值。特别是对于知识库非常大的任务,这些方法可以决定是否能够学习某项任务。随着时间的推移,这些代数运算符的加速可能会得到发展,这为更多的应用开辟了道路。

参考

除非另有说明,所有图片均为作者所有。

属于这篇文章的代码可以在这里找到

SQL 连接的直观解释

原文:https://towardsdatascience.com/visual-sql-joins-4e3899d9d46c

用维恩图和实际例子理解 SQL 连接

凯利·西克玛Unsplash 上拍摄的照片

考虑到单个表可以被视为一个集合,维恩图是可视化 SQL 连接如何工作的一个很好的方式。尽管可能有许多不同的方式来描述连接,但我坚信维恩图有助于读者以清晰一致的方式理解在两个表之间执行特定类型的连接时,哪些记录将包含在返回的结果行集中。

在本文中,我们将展示以下连接类型的可视化表示

  • 左连接(也称为左外连接)
  • 右联接(也称为右外联接)
  • 内部连接
  • 完全外部连接
  • 左反连接(也称为左排除连接)
  • 右反联接(也称为右排除联接)
  • 完全反联接
  • 交叉连接

此外,我们还将提供用于在 SQL 中执行上述连接的实际语法。我们开始吧!

首先,让我们创建两个表,我们将在随后的部分中引用这两个表,以便使用实际的例子来演示各种 SQL 连接类型。

我们第一个名为users的表有 3 列和 4 条记录:

+----+--------+-----+
| id |   name | age |
+----+--------+-----+
|  1 |   John |  21 |
|  2 |  Helen |  22 |
|  3 | Andrew |  31 |
|  4 |    Bob |  19 |
+----+--------+-----+

名为orders的第二个表有 3 列和 5 条记录:

+----+---------+--------+
| id | user_id | amount |
+----+---------+--------+
|  1 |       1 |   7.99 |
|  2 |       4 |  10.49 |
|  3 |       2 |  45.89 |
|  4 |       2 |  19.99 |
|  5 |       1 |  54.89 |
+----+------------------+

左连接(也称为左外连接)

我们将展示的第一种连接类型是左连接(或左外连接)。这种类型的联接将导致从左表中提取的所有记录,以及从右表中提取的那些在指定的联接列上与左表具有匹配值的记录。结果中包含的右表记录中的任何缺失值都将被替换为null。对于左表和右表之间的每个匹配值,都将返回一条新记录。例如,如果左边表中的一个键与右边表中的两个记录匹配,那么两个记录将作为结果的一部分返回。

维恩图,说明在 SQL 中的两个表之间执行左连接(或左外连接)时选择的记录—来源:作者

在 SQL 中,可以使用下面列出的语法启动左连接:

SELECT *
FROM users u
LEFT JOIN orders o
ON u.id = o.user_id;

结果记录将包括:

+----+--------+-----+----+---------+--------+
| id |   name | age | id | user_id | amount |
+----+--------+-----+----+---------+--------+
|  1 |   John |  21 | 1  |       1 |   7.99 |
|  4 |    Bob |  19 | 2  |       4 |  10.49 |
|  2 |  Helen |  22 | 3  |       2 |  45.89 |
|  2 |  Helen |  22 | 4  |       2 |  19.99 |
|  1 |   John |  21 | 5  |       1 |  54.89 |
|  3 | Andrew |  31 |null|    null |   null |
+----+--------+-----+----+---------+--------+

右连接(也称为右外连接)

我们将展示的第二种连接类型是右连接(或右外连接)。这种类型的联接将导致从右表中提取的所有记录,以及从左表中提取的那些在指定的联接列上与右表具有匹配值的记录。

维恩图,说明在 SQL 中的两个表之间执行右连接(或右外连接)时选择的记录—来源:作者

右连接的 SQL 语法概述如下:

SELECT *
FROM users u
RIGHT JOIN orders o
ON u.id = o.user_id;

在我们的示例表中,由上述查询推断出的记录共享如下:

+----+--------+-----+----+---------+--------+
| id |   name | age | id | user_id | amount |
+----+--------+-----+----+---------+--------+
|  1 |   John |  21 | 1  |       1 |   7.99 |
|  4 |    Bob |  19 | 2  |       4 |  10.49 |
|  2 |  Helen |  22 | 3  |       2 |  45.89 |
|  2 |  Helen |  22 | 4  |       2 |  19.99 |
|  1 |   John |  21 | 5  |       1 |  54.89 |
+----+--------+-----+----+---------+--------+

内部连接

两个表之间的内部联接将产生一组在指定联接列中具有共同值的记录。

维恩图说明了在 SQL 中的两个表之间执行内部连接时选择的记录—来源:作者

要执行内部联接,只需运行以下查询:

SELECT *
FROM users u
INNER JOIN orders o
ON u.id = o.user_id;

注意,您甚至可以省略INNER关键字,换句话说,上面的查询等价于下面列出的查询:

SELECT *
FROM users u
JOIN orders o
ON u.id = o.user_id;

对于我们的示例表,从上述查询返回的记录共享如下:

+----+--------+-----+----+---------+--------+
| id |   name | age | id | user_id | amount |
+----+--------+-----+----+---------+--------+
|  1 |   John |  21 | 1  |       1 |   7.99 |
|  4 |    Bob |  19 | 2  |       4 |  10.49 |
|  2 |  Helen |  22 | 3  |       2 |  45.89 |
|  2 |  Helen |  22 | 4  |       2 |  19.99 |
|  1 |   John |  21 | 5  |       1 |  54.89 |
+----+--------+-----+----+---------+--------+

完全外部连接

SQL 中的一个完整的外部连接基本上包括左表和右表中的所有记录。这种类型的连接将使用两个表中的键—对于任何缺失的行,将插入null值。

维恩图说明了在 SQL 中的两个表之间执行完全外部连接时选择的记录—来源:作者

完整外部联接的语法概述如下:

SELECT *
FROM users u
FULL OUTER JOIN orders o
ON u.id = o.user_id;

而结果记录将是

+----+--------+-----+----+---------+--------+
| id |   name | age | id | user_id | amount |
+----+--------+-----+----+---------+--------+
|  1 |   John |  21 | 1  |       1 |   7.99 |
|  4 |    Bob |  19 | 2  |       4 |  10.49 |
|  2 |  Helen |  22 | 3  |       2 |  45.89 |
|  2 |  Helen |  22 | 4  |       2 |  19.99 |
|  1 |   John |  21 | 5  |       1 |  54.89 |
|  3 | Andrew |  31 |null|    null |   null |
+----+--------+-----+----+---------+--------+

左反连接(又名左排除连接)

左反连接将包含左表中连接键没有出现在右表中的所有记录。

维恩图说明了在 SQL 中的两个表之间执行左反连接(或左排除连接)时选择的记录—来源:作者

换句话说,左反联接将返回所有(还)没有下单的客户。在 SQL 中,这被翻译为:

SELECT *
FROM users u
LEFT JOIN orders o
ON u.id = o.user_id
WHERE o.user_id is null;

结果记录概述如下:

+----+--------+-----+----+---------+--------+
| id |   name | age | id | user_id | amount |
+----+--------+-----+----+---------+--------+
|  3 | Andrew |  31 |null|    null |   null |
+----+--------+-----+----+---------+--------+

右反联接(又名右排除联接)

类似地,右反连接将包含右表中键不出现在左框架中的所有记录。

维恩图说明了在 SQL 中的两个表之间执行右反联接(或右排除联接)时选择的记录—来源:作者

SELECT *
FROM users u
RIGHT JOIN orders o
ON u.id = o.user_id
WHERE u.id is null;

注意,在我们的示例表中,这个查询不会产生任何记录,因为所有下订单的用户(即包含在orders表中的用户)都出现在左边的表中(即users)。

完全反联接

完整的反连接将包含左表和右表中没有任何公共键的所有记录。

维恩图说明了在 SQL 中的两个表之间执行完全反连接时选择的记录—来源:作者

SELECT *
FROM users u
JOIN orders o
ON u.id = o.user_id
WHERE u.id is null and o.user_id is null;

交叉连接

现在,交叉连接不能用维恩图来可视化,因为它本质上是要在所涉及的每个表的记录之间创建所有可能的组合。因此,甚至不需要提供连接键(即 SQL 中的ON关键字)。

SELECT *
FROM users u
CROSS JOIN orders o;

假设左边的表包含 4 条记录,而右边的表包含 5 条记录,上面的查询将返回 20 条结果记录。

最后的想法

维恩图是一种构造,当在两个不同的表之间执行特定的连接时,它通常用于可视化结果记录集。在今天的教程中,我们介绍了 SQL 中可用的大多数(如果不是全部)连接类型,用适当的图表显示了它们,并分享了相应的查询语法。

掌握 SQL 连接对于处理数据的专业人员来说非常重要,因为它使他们能够将来自许多不同来源的记录汇集在一起,从而带来商业价值并支持决策制定。

成为会员 阅读媒体上的每一个故事。你的会员费直接支持我和你看的其他作家。你也可以在媒体上看到所有的故事。

https://gmyrianthous.medium.com/membership

相关文章你可能也喜欢

</2-rules-groupby-sql-6ff20b22fd2c>

用 Python 可视化世界二氧化碳排放量

原文:https://towardsdatascience.com/visualising-the-worlds-carbon-dioxide-emissions-with-python-e9149492e820

用 Python 展示全球二氧化碳排放的严峻现实

作者图片

介绍

出于某种原因,气候变化最近经常出现在新闻中。特别是我们的汽车、工厂、船只、飞机(仅举几例)排放的二氧化碳与通过温室效应导致的地球变暖之间的联系。上图是 2018 年全球短周期二氧化碳排放量。除了看起来很棒,几乎是艺术化的,它还提供了有用的背景信息,让我们了解世界上的碳排放到底来自哪里。这张地图清楚地显示,北美、欧洲、中国和印度是全球主要的排放源。放大不同的区域会显示大量有趣的特征,在北美和欧洲,高亮显示主要城市的亮区,所有亮区都与对应于主要道路的亮区相关联。在海上,可以找到主要的航线,例如中国-新加坡-马六甲海峡-苏伊士运河是一条特别明亮的线。还有一系列与主要航线相对应的曲线,特别是在北美和欧洲之间。人口密度可以用来解释这张地图,但是也有一些明显的例外。例如,南美洲的部分地区比预期的要亮,而西非可能比预期的要暗一些。相比之下,居住着埃及 95%人口的尼罗河却像圣诞树一样被点亮。也就是说,需要注意的是,这些地图纯粹是定性的,而不是定量的,所以要小心从中得出什么结论。

那么我们如何着手绘制地图呢?第一步是获取数据。数据可从 EDGARv6.0 网站(链接)获得,引用克里帕等人(2021) ( 链接),只要使用了适当的引用,就可以下载和使用。有许多数据集可供选择,我将在以后的文章中探讨其中的一些。在本文中,我们将研究 2018 年的短周期二氧化碳排放(遗憾的是,2018 年是最近的数据集)。值得注意的一点是,该数据集包括所有化石二氧化碳来源的排放,如化石燃料燃烧、非金属矿物加工(如水泥生产)、金属(黑色和有色金属)生产过程、尿素生产、农业石灰处理和溶剂使用。不包括大规模生物质燃烧,包括稀树草原燃烧、森林火灾以及土地利用、土地利用的变化和林业的源和汇。他们方法的完整描述可以在这里找到。

所以下载数据,并把它存储在你想执行代码的任何地方。我已经将数据读入到一个pandas.DataFrame中,并打印出结果DataFrame来看看我们在处理什么。数据非常简单,包括一个特定纬度/经度对的排放值,单位为吨/年。

数据探索

调查污染最严重的地区也很有趣。

也许并不令人惊讶的是,虽然也有一些来自印度、俄罗斯和台湾的显著贡献,但大多数都在中国。

绘制数据显示,数据没有覆盖整个世界,太平洋和南大洋的部分数据缺失。这并不奇怪,因为这些海洋非常广阔,而且通常没有人类的影响。我刚刚在这里使用了一个散点图来表示纬度和经度值。关于接下来的剧情需要注意一些事情。这些点被设置为 0.05,因为如果保留默认值 1,它们会重叠。分散点的边缘独立于点本身,不能小于 1。所以这些必须关掉。同样需要注意的是,在绘制纬度和经度值时,纬度是 y,经度是 x。

数据中纬度和经度值的分布。作者图片

根据发射值给点着色是一种很好的方式来了解数据的实际情况。不幸的是,该图以低排放值为主。

由发射值着色的纬度和经度值。作者图片

研究数据(为简洁起见,我在很大程度上省略了这些数据)显示,主要是每年 0-1000 吨二氧化碳的数值,还有一小部分数值高出几个数量级。

考虑到这一点,我们将绘制对数标度的值。如果可能的话,我尽量避免使用对数图,因为由此产生的图可能很难解释,而且数据通常没有意义。但是,当一小部分值的数量级大于大多数值时,以及在试图显示乘法因子的图中,对数图是很好的选择。我们的数据符合这两个类别,因此在这种情况下是合适的。对数图还有一个上下文原因。我们想知道二氧化碳的排放来自哪里,如果我们使用上面的图表,我们会得出这样的结论:排放量在世界范围内是均匀分布的。我们知道伦敦的排放量可能高于大西洋中部,所以我们需要一种方法来区分少量的高值和大量的低值。

绘制数据

Matplotlib有一个内置的对数标尺,用于下面的区块。

以对数标度绘制的排放数据。作者图片

我们所知的世界现在开始展现自己,所以现在是时候让它看起来更漂亮了。虽然绿色在科学上是完美的色彩图(【https://www.youtube.com/watch?v=xAoljeRJ3lU】),但我想要一些火热的东西来展示二氧化碳的排放,因为它们导致了全球变暖,这是潜在的火热。因此,我们将它切换到 AFM hot _ r(r 表示颜色映射反转,将低值映射到浅色,将大值映射到深色)。我把它反过来了,因为背景是白色的,我们想突出大的排放值。

使用 afmhot_r 颜色图以对数标度绘制的排放数据。作者图片

地图开始成形了,现在开始投影。有许多地理投影,在开幕图像中显示的一个被称为罗宾逊投影,虽然有争议,但它通常被认为是最现实的。到目前为止,我们一直依赖于用标准散点图(模糊地对应于墨卡托投影)来绘制值。这在技术上是好的,但是如果我们想要将排放数据正确地映射到地理投影上,这就不合适了。因此,纬度/经度值需要转换成shapely点,然后可以转换成我们想要的投影。

然后可以用subplots函数中的一个名为cartopy的库重新投影数据。随意应用你想要的任何样式改变,例如我喜欢黑暗模式,所以我把背景改为黑色,并翻转颜色图,这样大的值会更亮。

黑暗模式下的二氧化碳排放!作者图片

与原始研究相比如何?

最初绘制这张地图时,我对上面显示的地图非常满意,但我认为最好通过查看原始出版商的地图来检查它应该是什么样子。下图是由原始出版商制作的。出于我无法确定的原因,他们选择了一个奇怪的色阶值系列,即 0.0、0.06、6、60、600、3000、6000、24000、45000、120000。除了缺乏解释,我认为复制这个图会很有趣,因为这些是气候专家,可能比我知道得更多,因此可能有一个很好的理由。

EDGARv6.0 网站(链接)到克里帕等人(2021) ( 链接)。作者图片

像以前一样,我们将使用 afmhot 作为我们的颜色图,但这次我们将它映射到 10 种颜色,并将这 10 种颜色归一化为上图中的 10 个值(0.0,0.06,6,60,600,3000,6000,24000,45000,120000)。默认情况下,在matplotlib中使用色图的对象将色图中的颜色从数据集中的最小值线性映射到数据集中的最大值,间隔由色图中的颜色数量决定。BoundaryNorm类允许你将颜色图中的颜色映射到一组自定义值。在下面的代码中,我们创建了一个有 10 种颜色的颜色图和一个BoundaryNorm对象,它将根据预定义的级别把数据中的值映射到颜色图。

图解说明如何将发射映射到颜色。图片作者。

上面的图像给出了正在发生的事情的大致形象。颜色图由 10 种颜色生成,我们的排放数据集中的值将根据上图中的值进行着色。例如,等于或大于每年 120000 吨二氧化碳的数值将被涂成白色。

现在,有了新的色图和标准化的边界,我们可以重新绘制地图,这次以一种更能让人想起原作者所做的方式将值映射到颜色。

世界二氧化碳排放量!作者图片

最后要做的是提供一个 colourbar,这样我们的读者就可以理解他们实际上在看什么。我有时并不担心这一步,但思考一下还是很有用的。

瞧啊。作者图片

结论

我们看到了,一张漂亮的地图显示了世界二氧化碳排放的来源。这是计划展示如何使地理空间数据看起来很棒的许多文章中的第一篇,请订阅以便您不会错过它们。我也喜欢反馈,所以请让我知道你会如何做不同或建议改变,使它看起来更棒。我每周都会在我的推特账户上发布数据可视化,看看地理空间数据可视化是不是你的菜https://twitter.com/PythonMaps

参考文献

m .克里帕、d .吉扎尔迪、Schaaf、e .索拉索、e .芒特恩、m .蒙弗蒂-费拉里奥、f .奥利维尔、J.G.J .、维尼亚蒂:世界所有国家的化石二氧化碳和 GHG 排放量——2021 年报告,编写中。

m .、Solazzo、e .、Huang、Guizzardi、d .、Koffi、e .、Muntean、m .、Schieberle、c .、Friedrich、r .和-Maenhout、g .:全球大气研究排放数据库中的高分辨率时间廓线。Sci 数据 7,121 (2020)。doi:10.1038/s 41597–020–0462–2

国际能源机构(2019)世界能源平衡,www.iea.org/data-and-statistics,版权所有,由欧盟委员会联合研究中心修改。

Jalkanen,J. P .,Johansson,l .,Kukkonen,j .,Brink,a .,Kalli,j .,& Stipa,T. (2012 年)。颗粒物和一氧化碳船舶交通废气排放评估模式的扩展。大气化学和物理学,12(5),2641–2659。doi:10.5194/ACP-12–2641–2012

Johansson,l .,Jalkanen,J.-P .,& Kukkonen,J. (2017)。基于高时空分辨率的 2015 年全球航运排放评估。大气环境,167,403–415。doi:10.1016/j . Atmos env . 2017 . 08 . 042

用螺旋线可视化时间序列数据

原文:https://towardsdatascience.com/visualising-time-series-data-with-spirals-efe0cd9f4c5

使用 Matplotlib 和 Python 绘制气候螺旋图

全球气温变化-作者图片

作为数据科学家,我工作的一部分是处理不同种类的时间序列数据。试图以对用户有意义的方式可视化数据中的模式和变化可能是一个相当大的挑战。因此,当我最近看到一个显示 1850 年至 2021 年全球气温变化的动画螺旋时,我认为这是一个以有影响力的方式显示时间序列数据的好方法:

https://www.climate-lab-book.ac.uk/spirals/

虽然它展示的东西很吓人,但我真的很喜欢这个动画本身。当我跟踪所有链接时,我发现它是用 Javascript 实现的。我想我会尝试做同样的事情,但是使用 Python 和 Matplotlib。好消息是我能够做到,我会告诉你怎么做。我还在我的 GitHub 上提供了源代码——查看文章末尾的链接。

画一个圆

在这个解决方案的不同部分,我们需要能够画一个圆。特别是,这个圆圈需要分成 12 个部分——一年中的每个月一个部分。为了做到这一点,我们将使用一些标准的三角。

单位圆有一些我们将要用到的很好的性质。它被定义为半径为 1 的圆。这意味着一些三角方程变得更容易计算,因为半径部分实际上消失了。

单位圆—作者图片

如果我们想知道圆上一点的 x 和 y 的值,这将由 x =半径 cos(θ)* 和 y =半径 sin(θ)* 给出。因为这是单位圆,所以变成了 x = cos(θ)y = sin(θ)。

还有一些我们应该注意的事情。θ为零时,它将指向 3 月(如果这是一个钟面,则指向 3:00)。随着角度的增加,月份名称会向后移动。为此,我创建了一个查找表(因为只有 12 个值)来将月份索引映射到相应的段索引。

创建 12 个分段

为了创建 12 个分段,我创建了一个函数, segment_circle :

气候数据

使用的数据是由英国气象局哈德利中心提供的数据集。这是相对于 1961-1990 参考期(从 1850 年到 2022 年)的全球历史地表温度异常的网格化数据集。它有大量的数据。

HadCRUT.5.0.1.0 数据于 2022 年 3 月 28 日从http://www.metoffice.gov.uk/hadobs/hadcrut5获得,由英国皇家版权所有,Met Office 2022,在开放政府许可下提供,http://www . national archives . gov . uk/doc/Open-Government-License/version/3/

气候螺旋图

以下是 Matplotlib 中所有数据的螺旋图:

全球气温变化-作者图片

制作视频

为了创建一个绘制所有点的视频,我使用了 ffmpeg 。将它下载并安装到您的计算机上,并确保它在您的路径上(这样您就可以在命令行上执行它)。FFmpeg 可以接受来自 stdin 的输入,这意味着您可以通过管道将视频帧传输给它,它们将在视频中累积。这些年来,我发现这是一种用 matplotlib 创建视频的有用方法。

为了创建我们的视频,我们首先用 matplotlib 创建一个时间点的帧,然后保存到 stdin。按照这个过程,我们循环所有的时间点,创造了气候螺旋。

气候螺旋视频——作者视频

后续步骤

  1. 实现螺旋的 3D 版本。这应该很容易做到,因为您只需在 3D 中绘制点,并使用时间作为 Z 轴。
  2. 实现了这个螺旋之后,我认为这实际上是一个可视化某些时间序列数据的好方法。我将寻找机会在我的时间序列数据工作中使用它。

资源

PyTorch 中神经网络的可视化

原文:https://towardsdatascience.com/visualization-for-neural-network-in-pytorch-7658cfd32149

Python 可视化工具包

照片由 Unsplash 上的 Aziz Acharki 拍摄

PyTorch 是一个基于 Python 的 Torch 库的开源 ML 框架。基本用于 NLP、计算机视觉等应用。它是由脸书开发的,并且是开源的。它是继 Tensorflow 和 Keras 之后使用最多的框架之一。PyTorch 和其他框架的主要区别在于 PyTorch 以更 Pythonic 化的方式工作,即面向对象的方法。

一旦使用 PyTorch 创建了一个模型,我们就可以使用 FlashTorch 创建不同的可视化效果。是为了解决理解神经网络如何工作的问题。它有助于使用不同的特征可视化技术(如显著图和激活最大化)来理解模型行为。

在本文中,我们将探索使用 FlashTorch 的不同类型的可视化,我们还将探索如何使用 FlashTorch 工具进行图像转换。

让我们开始吧…

安装所需的库

我们将从使用 pip 安装闪光灯开始。下面给出的命令可以做到这一点。

pip install flashtorch

导入所需的库

在这一步中,我们将导入所需的库来加载预训练的 PyTorch 模型并创建可视化。

%matplotlib inline
%config InlineBackend.figure_format = 'retina'
import matplotlib.pyplot as plt
from flashtorch.utils import(load_image, apply_transforms, denormalize, format_for_plotting)

正在加载图像

接下来,我们将加载将在本文中使用的图像。你可以使用任何你想要的图像

image = load_image('Image.jpg')
print(type(image))
print(image**.**mode)

图像属性(来源:作者)

可视化原始图像

在这一步中,我们将形象化我们将在本文中使用的图像。

plt.imshow(image)
plt.title('Original image');

原始图片(来源:作者)

转换图像

在这一步中,我们将加载预训练的模型来变换图像。我们正在使用 alexnet 模型,我们将使用 flash torch 的应用变换功能。

model = models.alexnet(pretrained=True)
backprop = Backprop(model)
input_ = apply_transforms(image)
target_class = 24
backprop.visualize(input_, target_class, guided=True, use_gpu=True)

转换(来源:作者)

在这里你可以清楚地看到转换后的图像,我们有最大梯度,RGB 通道和叠加。我们可以清楚地分析不同的图像及其差异。

继续尝试不同的图像,并转换它们。如果您发现任何困难,请在回复部分告诉我。

本文是与皮尤什·英加尔合作完成的。

在你走之前

感谢 的阅读!如果你想与我取得联系,请随时通过 hmix13@gmail.com 联系我或我的 LinkedIn 个人资料 。可以查看我的Github*简介针对不同的数据科学项目和包教程。还有,随意探索* 我的简介 ,阅读我写过的与数据科学相关的不同文章。

Python 中的可视化:寻找点与点之间的路径

原文:https://towardsdatascience.com/visualization-in-python-finding-routes-between-points-2d97d4881996

了解如何对您的位置进行地理编码,并根据距离、出行模式和时间计算两点之间的最短路径

蒂莫·维林克在 Unsplash 上的照片

在我的上一篇文章中,我谈到了使用 leav 库可视化您的地理空间数据。

开发人员经常必须使用地理空间数据执行的另一项任务是绘制各种兴趣点之间的路由路径。因此,在这篇文章中,我将向您展示如何:

  • 对您的位置进行地理编码
  • 查找两个位置之间的最短距离

安装 OSMnx

布线的第一步是安装 OSMnx 包。

OSMnx 是一个 Python 包,允许您从 OpenStreetMap 下载地理空间数据,并对现实世界的街道网络和任何其他地理空间几何图形进行建模、投影、可视化和分析。

安装 OSMnx 包有点棘手——在执行通常的pip/conda install之前,您必须使用以下步骤:

$ **conda config --prepend channels conda-forge**
$ conda install osmnx

注意:如果conda install osmnx失效,使用pip install osmnx

一旦执行了上述步骤,OSMnx 现在应该可以使用了。

寻找最短的路线

您现在可以使用 OSMnx 包和 NetworkX 包来查找两点之间的路线。

NetworkX 是一个 Python 包,用于创建、操作和研究复杂网络的结构、动态和功能。

以下代码片段查找旧金山两个地点之间的最短步行距离:

以下代码已更新,可使用最新版本的 OSMnx (1.3.0)

import osmnx as ox
import networkx as nxox.settings.log_console=True
ox.settings.use_cache=True**# define the start and end locations in latlng**
start_latlng = (37.78497,-122.43327)
end_latlng = (37.78071,-122.41445)**# location where you want to find your route**
place     = 'San Francisco, California, United States'**# find shortest route based on the mode of travel**
mode      = 'walk'        # 'drive', 'bike', 'walk'**# find shortest path based on distance or time**
optimizer = 'time'        # 'length','time'**# create graph from OSM within the boundaries of some 
# geocodable place(s)**
graph = ox.graph_from_place(place, network_type = mode)**# find the nearest node to the start location**
orig_node = ox.distance.nearest_nodes(graph, start_latlng[1],
                                      start_latlng[0])**# find the nearest node to the end location**
dest_node = ox.distance.nearest_nodes(graph, end_latlng[1],
                                      end_latlng[0])**#  find the shortest path**
shortest_route = nx.shortest_path(graph,
                                  orig_node,
                                  dest_node,
                                  weight=optimizer)

寻找最短路径的默认方法是“dijkstra”。您可以通过将shortest_path()功能的method参数设置为“bellman-ford”来进行更改。

shortest_route变量现在保存了在最短的时间内从一点走到另一点的路径的集合:

[5287124093,
 65314192,
 258759765,
 65314189,
 5429032435,
 65303568,
 65292734,
 65303566,
 2220968863,
 4014319583,
 65303561,
 65303560,
 4759501665,
 65303559,
 258758548,
 4759501667,
 65303556,
 65303554,
 65281835,
 65303553,
 65303552,
 65314163,
 65334128,
 65317951,
 65333826,
 65362158,
 65362154,
 5429620634,
 65308268,
 4064226224,
 7240837048,
 65352325,
 7240837026,
 7240837027]

绘制路径

显然,拥有一个路径列表本身并没有多大用处。解释结果的更有意义的方法是使用plot_route_folium()函数绘制路径:

shortest_route_map = ox.**plot_route_folium**(graph, shortest_route)
shortest_route_map

plot_route_folium()函数返回一个树叶地图(folium.folium.Map)。当显示在 Jupyter 笔记本上时,它看起来是这样的:

作者图片

使用的默认 tileset 是cartodbpositron。如果您想要更改它,您可以将tiles参数设置为您想要使用的 tileset。以下代码片段显示了使用openstreetmap tileset 显示的地图:

shortest_route_map = ox.plot_route_folium(graph, shortest_route, 
                                          **tiles='openstreetmap'**)
shortest_route_map

以下是使用openstreetmap tileset 显示的地图:

作者图片

如果您想让用户在运行时选择他们的首选 tileset,请使用下面的代码片段:

import foliumfolium.TileLayer('openstreetmap').add_to(shortest_route_map)
folium.TileLayer('Stamen Terrain').add_to(shortest_route_map)
folium.TileLayer('Stamen Toner').add_to(shortest_route_map)
folium.TileLayer('Stamen Water Color').add_to(shortest_route_map)
folium.TileLayer('cartodbpositron').add_to(shortest_route_map)
folium.TileLayer('cartodbdark_matter').add_to(shortest_route_map)folium.LayerControl().add_to(shortest_route_map)shortest_route_map

用户现在可以选择自己的 tileset 来显示地图:

作者图片

改变出行模式和优化器

除了寻找步行的最短路径,您还可以绘制驾驶的最短路径:

# find shortest route based on the mode of travel
mode      = '**drive**'       # 'drive', 'bike', 'walk'# find shortest path based on distance or time
optimizer = 'time'        # 'length','time'

以下是驾驶路径:

作者图片

骑自行车怎么样?

# find shortest route based on the mode of travel
mode      = '**bike**'       # 'drive', 'bike', 'walk'# find shortest path based on distance or time
optimizer = 'time'       # 'length','time'

这里是骑自行车的最短路径:

作者图片

您还可以根据距离而不是时间来查找最短路径:

# find shortest route based on the mode of travel
mode      = 'bike'       # 'drive', 'bike', 'walk'# find shortest path based on distance or time
optimizer = '**length**'     # 'length','time'

这是骑自行车的最短距离:

作者图片

我会把剩下的组合留给你去尝试。

地理编码您的位置

在两点之间查找路径时,指定位置的纬度和经度并不方便(除非您的数据集中已经有了坐标)。相反,指定它们的友好名称要容易得多。您实际上可以使用 geopy 模块来执行这个步骤(称为地理编码)。

地理编码是将地址转换成其坐标的过程。反向地理编码则是将一对坐标变成一个友好的地址。

要安装 geopy 模块,在终端中键入以下命令:

$ **pip install geopy**

以下代码片段为 OpenStreetMap 数据创建了一个Nominatim地理编码器类的实例。然后它调用geocode()方法对金门大桥的位置进行地理编码。使用地理编码位置,您现在可以提取位置的纬度和经度:

from geopy.geocoders import Nominatimlocator = Nominatim(user_agent = "myapp")
location = locator.geocode("Golden Gate Bridge")print(location.latitude, location.longitude)
# 37.8303213 -122.4797496print(location.point)
# 37 49m 49.1567s N, 122 28m 47.0986s Wprint(type(location.point))
# <class 'geopy.point.Point'>

你可以到https://www.google.com/maps验证结果,并将经纬度粘贴到搜索框中:

作者图片

现在让我们修改我们的原始代码,以便我们可以对起点和终点进行地理编码:

以下代码已更新,可使用最新版本的 OSMnx (1.3.0)

import osmnx as ox
import networkx as nxox.settings.log_console=True
ox.settings.use_cache=True**from geopy.geocoders import Nominatim
locator = Nominatim(user_agent = "myapp")**# define the start and end locations in latlng
**#** start_latlng = (37.78497,-122.43327)
**#** end_latlng = (37.78071,-122.41445)**start_location = "Hilton San Francisco Union Square"
end_location = "Golden Gateway Tennis & Swim Club"****# stores the start and end points as geopy.point.Point objects
start_latlng = locator.geocode(start_location).point
end_latlng = locator.geocode(end_location).point**# location where you want to find your route
place     = 'San Francisco, California, United States'# find shortest route based on the mode of travel
mode      = 'bike'       # 'drive', 'bike', 'walk'# find shortest path based on distance or time
optimizer = 'length'        # 'length','time'# create graph from OSM within the boundaries of some 
# geocodable place(s)
graph = ox.graph_from_place(place, network_type = mode)# find the nearest node to the start location
orig_node = ox.distance.nearest_nodes(graph, **start_latlng[1], 
                                      start_latlng[0]**)# find the nearest node to the end location
dest_node = ox.distance.nearest_nodes(graph, **end_latlng[1],
                                      end_latlng[0]**)...

注意,get_nearest_node()函数可以接受位置坐标,或者是包含纬度和经度的元组,或者是一个geopy.point.Point对象。

这里是从联合广场希尔顿酒店**到旧金山黄金门户网球&游泳俱乐部的最短骑行距离:

作者图片

显示起点和终点的标记

如果你能显示指示起点和终点的标记,那就更清楚了。正如我在上一篇文章中所描述的,您可以使用 follow 中的Marker类来显示带有弹出窗口的标记。

以下代码片段显示了两个标记,绿色表示起点,红色表示终点:

*import folium**# Marker class only accepts coordinates in tuple form**
start_latlng = (start_latlng[0],start_latlng[1])
end_latlng   = (end_latlng[0],end_latlng[1])start_marker = folium.Marker(
            location = start_latlng,
            popup = start_location,
            icon = folium.Icon(color='green'))end_marker = folium.Marker(
            location = end_latlng,
            popup = end_location,
            icon = folium.Icon(color='red'))# add the circle marker to the map
start_marker.add_to(shortest_route_map)
end_marker.add_to(shortest_route_map)shortest_route_map*

注意Marker类只接受元组形式的坐标。因此,您需要修改start_latlngend_latlng成为包含纬度和经度的元组。

这是指示路径起点和终点的两个标记:

作者图片

绘制静态图

在前面的部分中,您使用了plot_route_folium()函数在一张树叶地图上绘制两点的最短路径:

*shortest_route_map = ox.**plot_route_folium**(graph, shortest_route)
shortest_route_map*

还有一个你可能会感兴趣的功能——plot_graph_route()。它不是输出交互式地图,而是生成静态图形。如果你想制作一个海报/图像显示两点的路径,这是很有用的。

以下代码片段使用上一节中使用的点生成了一个静态图:

以下代码已更新,可使用最新版本的 OSMnx (1.3.0)

*import osmnx as ox
import networkx as nxox.settings.log_console=True
ox.settings.use_cache=Truegraph = ox.graph_from_place(place, network_type = mode)orig_node = ox.distance.nearest_nodes(graph, start_latlng[1],
                                      start_latlng[0])
dest_node = ox.distance.nearest_nodes(graph, end_latlng[1],
                                      end_latlng[0])shortest_route = nx.shortest_path(graph, 
                                  orig_node, 
                                  dest_node, 
                                  weight=optimizer)fig, ax = **ox.plot_graph_route**(graph,
                              shortest_route,
                              save=True)*

您将看到以下输出:

作者图片

摘要

我希望你从这篇文章中找到灵感,并开始使用它来创建兴趣点的路线。您可能希望让用户输入他们的当前位置,然后绘制路线,向他们显示如何到达目的地。无论如何,玩得开心!

*https://weimenglee.medium.com/membership

参考文献:波音,G. 2017。 OSMnx:获取、构建、分析和可视化复杂街道网络的新方法计算机、环境和城市系统 65,126–139。doi:10.1016/j . compenurbsys . 2017 . 05 . 004*

用 NASA 数据和 Python 可视化不可见的 SO2

原文:https://towardsdatascience.com/visualize-the-invisible-so2-with-nasa-data-and-python-2619f8ed4ea1

绘制火山爆发后二氧化硫的移动。

第一张图:2022 年 1 月 15 日洪加汤加-洪加 Haʻapai 火山爆发的卫星动画截图。图片来自维基百科。第二张图片:热图显示了 1 月 17 日,也就是火山爆发几天后,SO2 的含量。使用由叶子制成的 GES 光盘中的数据。图片由作者提供。

2022 年 1 月中旬,汤加群岛的火山岛亨加汤加-亨加 Haʻapai 发生火山爆发。一个卫星动画展示了一次巨大的爆炸,伴随着大量的烟雾。我们看到的烟雾自然包括水蒸气、二氧化碳(CO2)、二氧化硫(SO2)和灰烬

2022 年 1 月 15 日洪加汤加-洪加 Haʻapai 火山爆发的卫星动画。图片来自维基百科

再来说说 SO2。二氧化硫被释放后,在高空转化为硫酸盐气溶胶。气溶胶能够反射阳光,使地球气候变冷,它们也是臭氧损耗的一个原因。随着大规模喷发,二氧化硫会被喷射到 10 公里以外的大气中。

然而,由于其的不可见性,释放的 SO2 很难被观察到。

本文将展示如何用散点图和热图来可视化火山爆发后 SO2 的移动。

检索数据

大气成分数据,包括二氧化硫,可以从美国宇航局戈达德地球科学数据和信息服务中心( GES 光盘)下载。注册后数据是公开的。

GES 碟网站

SO2 数据可通过 OMPS_NPP_NMSO2_PCA_L2 数据产品获取。这是 NASA/NOAA Suomi 国家极地轨道伙伴关系(SNPP)卫星上的臭氧测绘和剖面仪套件(OMPS)天底成像仪(NM)的 2 级轨道火山和人为二氧化硫产品。

请考虑到这不是原始数据。2 级数据产品被定义为与 1 级源数据具有相同分辨率和位置的衍生地球物理变量。关于关卡的更多信息,你可以点击查看

基于 PCA 算法的 OMPS 核电厂最低点映射 SO2 二级产品

为了从网站上获取 SO2 数据,我按照中的有用步骤为你的下一个地理项目获取 NASA 数据,这是一篇关于获取 GES 光盘数据文件的深刻文章。

由于洪加汤加-洪加 Haʻapai 火山爆发于 2022 年 1 月 15 日达到高潮,日期范围从 1 月 12 日细化到 1 月 23 日。

除了获取数据,还要下载一个文本文件(。txt),点击下载链接列表。这个文本文件将帮助我们在下一步中获得文件名。

下载链接列表和数据 URL。图片来自 OMPS NPP NMSO2 PCA L2 页面。

输入数据

从库开始,除了 NumPy 和 Pandas,H5py 库是用来读取下载的 HDF5 文件的。

读取获得的文本文件(。txt)来获取文件名。

文本文件中的文件名示例

下面的代码展示了如何用 H5py 从文件名中读取文件。读取后,数据将保存在一个列表中。

选择键

HDF5 格式内部包含许多密钥。我们需要浏览数据来选择我们要使用的键。本文使用的两个主要关键字是 GEOLOCATION_DATA 和 SCIENCE_DATA。

list(dataset[0].keys())### Output: 
# ['ANCILLARY_DATA', 
# 'GEOLOCATION_DATA', 
# 'SCIENCE_DATA', 
# ...

GEOLOCATION_DATA:纬度、经度和 UTC_CCSDS_A。

list(dataset[0]['GEOLOCATION_DATA'].keys()) ### Output: 
#['Latitude', 
# 'LatitudeCorner', 
# 'Longitude', 
# ... 
# 'UTC_CCSDS_A', 
# ...

SCIENCE_DATA: ColumnAmountSO2,以 Dobson 单位表示的垂直列 SO2 总量。

list(dataset[0]['SCIENCE_DATA'].keys())### Output: 
# ... 
# 'ColumnAmountSO2', 
# ...

数据集内部还包含其他有趣的键。更多信息,官网提供了解释其他键的 PDF 文件这里

创建数据框架

现在我们已经选择了键,让我们定义一个函数来提取数据。

创建纬度、经度、二氧化硫含量和日期的列表。

在绘制散点图之前,我们必须转换经度范围。这是因为典型地图的经度范围,包括来自 GES 圆盘的数据,在-180°和 180°之间。

如果我们直接绘制位于经度-175.4°的亨加汤加-亨加 Haʻapai 的位置,SO2 的移动将由于靠近范围的极限(-180°)而被切断。下面的代码用于修改经度范围。

从列表中创建一个熊猫数据框。

描述性分析

开始的一个重要步骤是做描述性分析。这是帮助我们理解数据的有用的一步。

要查看火山周围每天的 SO2 量,我们将创建一个过滤数据框,仅选择距离喷发点 15 度经纬度的正方形区域内的 SO2 量。我们还将只选择 SO2 值为正值的行。

按日期对过滤后的数据帧进行分组,并对 SO2 的日总量进行求和。

上表显示,SO2 日排放量在 1 月 16 日达到最大值。

从数据帧中绘制时间序列折线图,以查看时间线中的进度。

匈牙利汤加-匈牙利 Haʻapai 周围的 SO2 日排放量

图表显示,爆炸发生时,从 15 日到 1 月 16 日,该岛周围的二氧化硫急剧增加。

形象化

1.散点图

利用纬度和经度,SO2 的位置被绘制为点,根据位置和密度分别用颜色表示。

首先创建一个日期列表来过滤数据框架。仅选择 SO2 含量大于 0.5 DU 的行,以避免绘制时数据过多。

现在我们已经做了所有的事情,作为一个例子,我将展示 1 月 17 日,也就是火山爆发几天后,SO2 含量的散点图。

散点图显示了 1 月 17 日的二氧化硫含量。数据来自 GES 光盘。用 Seaborn 做的。图片由作者提供。

制作动画是为了让可视化看起来有趣。我们可以把这些日常的情节组合成一个 GIF 文件来看进度。为此,我们将绘制每个日散点图,并将它们存储在一个列表中。

组合这些图,并把它们转换成 GIF 文件。

洪加汤加-洪加 Haʻapai 火山爆发后 SO2 的移动。数据来自 GES 光盘。图片由作者提供。

2.热图

yellow 是一个 Python 库,易于使用,功能强大,可以处理地理空间数据。从导入库开始。

例如,我将展示如何绘制 1 月 17 日 SO2 的热图。

为了使用叶子,我们需要从数据帧中创建一个纬度、经度和二氧化硫含量的列表。然后,使用热图功能创建热图。

这张热图显示了 2022 年 1 月 17 日的二氧化硫含量。用叶子做的。数据来自 GES 光盘。图片由作者提供。

随时间变化的热图

Heat 有一个名为 HeatMapWithTime 的功能,用于制作时间序列热图。这对于创建 SO2 运动的动画非常有用。

瞧啊。!…

洪加汤加-洪加 Haʻapai.火山爆发后 SO2 的移动用叶子做的。数据来自 GES 光盘。图片由作者提供。

讨论

消息2022 年 1 月 15 日喷发达到高潮,之后趋于平静。然而,用 OMPS_NPP_NMSO2_PCA_L2 数据(这是一个 2 级产品)进行可视化显示,释放的 SO2 仍然在空气中保留了一个多星期。

结果与有毒物质和疾病登记机构(ATSDR)的信息一致,即 SO2 的大气寿命约为 10 天

摘要

尽管用肉眼很难观察到二氧化硫,但我们可以使用 Python 和美国宇航局的数据来绘制二氧化硫释放后的移动情况。本文展示了数据的来源以及如何下载和可视化它们。

正如我在探索数据步骤中提到的,HDF5 文件中还有其他键。你可以回去和其他人一起玩。如果任何人有问题或建议,请随时留下评论。

这些是关于数据可视化的其他文章,您可能会感兴趣。

  • 8 用 Python 处理多个时序数据的可视化(链接)
  • 用 Python 可视化光速(链接)
  • 用 Python 分 4 步进行图像颜色提取(链接)

参考

  • 李灿、Nickolay A. Krotkov、Peter Leonard、Joanna Joiner (2020 年)、OMPS/NPP 五氯苯甲醚 SO2 总列 1-轨道 L2 条带 50x50km V2,美国马里兰州格林贝尔特,戈达德地球科学数据和信息服务中心(GES 光盘),访问日期:2022 年 3 月 19 日,10.5067/MEASURES/SO2/DATA205
  • Bhanot,K. (2020 年 8 月 12 日)。为你的下一个地理项目获取 NASA 数据。中等。2022 年 4 月 12 日检索,来自https://towards data science . com/getting-NASA-data-for-your-next-geo-project-9d 621243 b8 F3
  • Python 可视化。(2020).叶子。从 https://python-visualization.github.io/folium取回
  • 有毒物质和疾病登记处。1998.二氧化硫的毒理学简介。佐治亚州亚特兰大:美国卫生与公众服务部,公共健康服务。

在分类问题中可视化数字特征的预测能力

原文:https://towardsdatascience.com/visualize-the-predictive-power-of-a-numerical-feature-in-a-classification-problem-a37bedc87d3b

如何可视化数字特征的预测能力

作者图片

在有监督的机器学习问题中,测量某些特征的预测能力总是一项难以完成的任务。在使用任何相关性度量标准之前,可视化一个特性是否能提供信息是很重要的。在本文中,我们将把数据可视化应用于分类问题。

数字特征如何影响分类目标?

有几种方法可以预测数字特征与分类目标之间的关系。例如,在血液分析中,如果某个值超过了某个阈值,生物学家可以假设存在某种疾病或健康状况。这是一个简单的例子,但它让我们清楚地了解了这个问题。在数字特征域中,我们必须找到一些子区间,在这些子区间中,分类目标显示一个比其他值更频繁的值。这是数字特征对分类目标的预测能力的定义。

这可能发生在我们想要的任意多个子区间上(实际上,这就是决策树实际上对我们的数据集建模的方式)。

数据可视化

在深入研究这种相关性的数值估计之前,以某种不偏不倚的方式将其可视化总是有用的。

在我关于 Python 中的探索性数据分析的免费课程中,我谈到了一种特殊的可视化,即堆叠直方图。

实际上,我们执行一个变量的直方图,并且根据与直方图的那个柱相关的目标变量的值,将每个柱分割成堆叠的子柱。这里有一个例子:

作者图片

正如我们所看到的,在 0.3 之前,多数类是 1(条形几乎是黄色的),而对于更高的值,条形变得更蓝。这在数字特征和分类变量之间有很强的相关性,因为我们已经能够发现两个区间,在这两个区间中,目标变量的一个值明显比其他值更频繁。

这个直方图的想法是,寻找一种颜色比其他颜色出现得更频繁的范围。使用这种方法,我们可以说有一个阈值根据特征值决定我们的目标变量的行为。这正是模特需要的学习能力。

通过逻辑回归和二元决策树可以很容易地发现这种相关性,因此这种分析可以为我们提供关于实际可行的模型类型的信息。

现在让我们看一个 Python 中的例子。

Python 中的一个例子

对于这个例子,我将使用 scikit-learn 库中包含的乳腺癌数据集。

首先,让我们导入一些库。

import seaborn as sns 
import matplotlib.pyplot as plt 
from sklearn.datasets import load_breast_cancer

然后,让我们导入数据集。

dat = load_breast_cancer(as_frame=True) 
df = dat['data'] 
df['target'] = dat['target']

让我们假设我们想要可视化“平均半径”特征的预测能力。我们可以只用一行代码来绘制堆积直方图:

sns.histplot(df,x="mean radius", hue="target", multiple="stack")

这是结果。

作者图片

正如我们所看到的,在 16 左右有一个阈值,低于这个阈值,目标变量的多数值为 1,高于这个阈值,目标变量的多数值为 0。所以,我们可以说这个变量是有预测性和信息性的。

我们现在可以对每个数值变量进行探索性分析,以显示它们的预测能力。

结论

在本文中,我展示了我最喜欢的一种方法,即可视化数字特征对分类目标的预测能力。这可能不是唯一可能的方法。例如,100%堆积直方图可能更具可读性。总的想法是,在计算任何数字之前,我们总是需要可视化信息,使用这种类型的图表可以提高我们项目的质量,同时产生良好的可交付成果。

原载于 2022 年 3 月 30 日【https://www.yourdatateacher.com】

可视化两年的新冠肺炎在夏威夷与动画情节

原文:https://towardsdatascience.com/visualizing-2-years-of-covid-19-in-hawaii-with-animated-plots-579c8570e7f

Python 可视化

动画图是强大的数据科学工具,我们将以夏威夷 COVID 数据为例向您展示如何制作动画图

你可以在这篇文章的结尾做这些(视频由作者提供)

学习目标

  1. 构建动画情节(如上图)可以提供额外维度的信息
  2. 使用真实数据并学习清理数据的策略,以适应您的分析
  3. 将领域知识和相关事件整合到您的时间序列数据分析中
  4. 本作品基于此处发表的原创作品

接着在 Github 上下载笔记本

为什么是夏威夷

美国各州实施了自己的策略来应对新型冠状病毒(新冠肺炎)疫情。夏威夷是独一无二的,因为它是唯一一个不能被从另一个州赶到的州。因此,人们只能坐飞机或坐船去夏威夷,很少有人坐船去。由于夏威夷州地处偏远,人们大多乘飞机抵达,因此这为研究 COVID 疫情的影响提供了一个有趣的案例。此外,我来自夏威夷,一直关注当地与群岛上的 COVID 情况相关的时事。

你也可以制作这样的动画(作者的视频)

动画剧情

数据可视化是数据科学的重要组成部分,有助于深入了解感兴趣的数据。动画图(见上文)建立在标准可视化的基础上,在试图理解变化时很有帮助,尤其是时间序列数据。在这里,我们将查看来自夏威夷的 COVID 数据,希望这些动画能够阐明一些模式,如事件的起因、强制措施的影响、死亡和住院趋势等。虽然我有生物学背景,并且是夏威夷大学癌症中心流行病学系的一员,但我计划对所展示的结果提供一点解释,因为这篇文章的重点是可视化。此外,如果你的数据可视化足够好,那么解释应该是容易的,明显的,几乎是微不足道的。

数据集

当我开始这个项目时(2020 年疫情 6 个月后),我使用了夏威夷州提供的公共数据。数据来自夏威夷数据合作,数据被输入到的谷歌表单 [1],所有这些都在公共领域。该数据包含总病例、新病例、住院和疫苗数据等信息。作为一名消息灵通的夏威夷居民,我拥有可以告知我数据当前状态的知识。例如,在整个疫情,州卫生部一直超负荷工作,资金不足,人员流动率很高。结果,数据的收集和维护变得很困难,数据的状态,用一个更好的词来说,有点混乱。这些问题并不是这种情况下独有的,并且一直在发生,但幸运的是,有许多方法可以处理现实世界中可能并不完美的数据。我们将讨论一些数据清理的方法。

为了更好地了解夏威夷的 COVID 情况,我认为查看当前相关事件的趋势会很方便。因此,我收集了与 COVID 情况相关的事件数据,可在以下链接中找到:事件数据。在这些数据中,存在对层的引用,而这些层是檀香山市(夏威夷最大的县)为控制 COVID 情况而尝试实施的策略的一部分。有关各层的更多信息,请参见这里的

现在我们已经对数据有了一个简单的概述,我们来做一些图吧!

我们导入一些模块开始吧!

import os, sys, time, math, random, imageio
from datetime import datetime### data science essen ###
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib as mpl
import matplotlib.gridspec as gridspec
from pylab import *
from scipy import ndimage, misc
import seaborn as sns
from matplotlib import collections as matcoll
import textwrap
import matplotlib.dates as dates### for streaming from google sheet ###
import gspread
from oauth2client.service_account import ServiceAccountCredentialsimport multiprocessing
from multiprocessing import Pool### set dots per square inch (dpi) for plots ###
mpl.rcParams['figure.dpi'] = 300

下面的代码用于直接从 google sheet 流式传输数据。要访问这些数据,您需要设置自己的身份验证令牌。设置这一点超出了本文的范围,但是代码是为那些已经设置了 api 的人准备的。

### Stream directly from google sheet ###
'''
scope = ['[https://spreadsheets.google.com/feeds'](https://spreadsheets.google.com/feeds')]
credentials = ServiceAccountCredentials.from_json_keyfile_name('./hawaii-covid-timeline-48aab0d374b0.json', scope)
gc = gspread.authorize(credentials)
spreadsheet_key = #TODO: get your key
worksheet = book.worksheet("COVID Data")
table = worksheet.get_all_values()##Convert table data into a dataframe
df = pd.DataFrame(table[1:], columns=table[0])##Convert number strings to floats and ints
df = df.apply(pd.to_numeric, errors='ignore')
df.head()
'''

该链接会将您带到一个 google 表单,可以以 csv 文件的形式下载。下面的代码假设您已经这样做了。如果您遵循 git repo,那么数据应该已经在那里了。

### Reade Hawaii Covid Data ###
### access and download at [https://docs.google.com/spreadsheets/d/1z5U88_NVC84CB4kPyNbp5wy6liUyA2T9iPqmHfFpjr4/edit?usp=sharing](https://docs.google.com/spreadsheets/d/1z5U88_NVC84CB4kPyNbp5wy6liUyA2T9iPqmHfFpjr4/edit?usp=sharing)
raw_data = pd.ExcelFile('Hawaii_Data_Collaborative_COVID_Dashboard_Data.xlsx')
print(raw_data.sheet_names)
df = raw_data.parse("COVID Data")
vax = raw_data.parse("DOH Vaccination Data")
hos = raw_data.parse("Hospital Data")### Read in timeline data by Lambert Leong ###
### download at [https://www.lambertleong.com/assets/docs/data/hawaii_covid_events_JAN22.csv'###](https://www.lambertleong.com/assets/docs/data/hawaii_covid_events_JAN22.csv'###)
events=pd.read_csv('[https://www.lambertleong.com/assets/docs/data/hawaii_covid_events_JAN22.csv'](https://www.lambertleong.com/assets/docs/data/hawaii_covid_events_JAN22.csv'))

数据表包含整个州的数据以及夏威夷州内每个县的数据。第一行代码解析出除了整个州的数据之外的所有数据。疫情进行到一半时,管理数据的小组从将总病例数输入“确诊病例”字段切换到“总病例”字段。转换的确切时间还不知道,但是为了这篇文章,我们选择了 2021 年 5 月 18 日。下面的代码还将病例表与住院和疫苗表合并。

### grab state totals ###
sdf=df[df['Region']=='State']### State started filling out 'Confirmed Cases' col then switched to 'Total Cases' ###
### We need to merge them ###
full_cc=sdf[sdf['Confirmed Cases'].notnull()]
full_tc=sdf[sdf['Total Cases'].notnull()]
old_data=sdf[sdf['Date']<='2021-05-18']
new_data=sdf[sdf['Date']>'2021-05-18']### merge ###
old_data['total_cases']=old_data['Confirmed Cases'].astype('float64')
new_data['total_cases']=new_data['Total Cases']
old_data['new_cases']=old_data['New Confirmed Cases'].astype('float64')
new_data['new_cases']=new_data['New Cases']### merge data and drop any duplicate rows ###
clean=old_data.append(new_data)
clean.drop_duplicates(subset=None,keep = 'first', inplace = True)### merge case data with vaccine data ###
clean_vax=pd.merge(clean, vax.iloc[:,:2], on='Date', how='left')
clean_vax=pd.merge(clean_vax, hos.iloc[:,:-1], on='Date', how='left')

由于我们将在接下来的几个编码部分清理数据,我们需要一种可视化结果的方法。下面是一个带有两个 y 轴的快速绘图函数,我们将调用它几次来查看我们对数据的更新。在下面的函数中,我们还计算了住院率和死亡率,方法是将当天的住院和死亡总数除以当天的病例总数。

### Lets make a quick plotting function for the next few concepts ###
def quick_plot(test: pd.DataFrame, scale: float=1) -> None:
    ### Lets create fields we are interested in ###
    test['vax_rate']=test['Value']
    test['death_rate']=test['Deaths_Tot']/test['total_cases']*scale
    test['hospital_rate']=test['Active Hospitalized']/test['total_cases']*scale ### lets look at the data ###
    fig, ax = plt.subplots()
    #fig.set_size_inches(xsize, ysize)
    ax2 = ax.twinx()
    ### plot data by date ###
    dates, cases, deaths =   test['Date'],test['total_cases'],test['death_rate']
    vax, hos = test['vax_rate'],test['hospital_rate']
    ax.plot_date(dates,cases, ls='-',marker='',color='darkorange',label='Total Cases')   
    ax2.plot_date(dates,vax, ls='-',marker='',color='g',label='Total Vaccination Rate')
    ax2.plot_date(dates,hos, ls='-',marker='',color='b',label='Hospitalization Rate')
    ax2.plot_date(dates,deaths, ls='-',marker='',color='r',label='Death Rate')
    ax.legend(loc=2)
    ax2.legend(loc=7)
    plt.close()return fig

数据包含空白单元格,当我们将数据导入数据框架时,pandas 会将这些值读取为 nan 或 Nan。我们使用“fillna()”函数用零填充这些空白,以便列中的所有数据都是数字数据类型(即 int、float 等)。我们调用我们制作的绘图函数来第一次查看数据。

test=clean_vax.copy(deep=True)
### lets fill nan values with zeros ###
test=test.fillna(0)first_pass=quick_plot(test)
first_pass#.savefig('covid_caseVrate_init.png', dpi=300, bbox_inches='tight')

(图片由作者提供)

上面的图告诉我们,我们有两个问题。第一个问题与 Nan 值有关,以及我们如何盲目地用零填充它们。盲目地用零填充 Nan 产生了速率的极端变化,如绿色图中的锯齿状图案所示。第二个问题与红蓝图或死亡率和住院率图的比例有关。我们通过使用插值函数来解决第一个问题,该函数可以用更接近缺失值的更真实的值来填充 Nan 值。下面的代码处理插值。

test=clean_vax.copy(deep=True)
### interpolate ###
test=test.interpolate()
test=test.fillna(0)
interopl=quick_plot(test)
interopl

(图片由作者提供)

正如你所看到的(上图),锯齿图案已经被插值函数修复了。然而,仍然有一条陡峭的垂直绿线表明疫苗接种率在一天之内从零上升到 50%。这是不现实的,因为初始值之前的所有值都是空的或 Nan。虽然我们不知道垂直绿线之前的确切疫苗接种率,但事件数据告诉我们夏威夷何时接种了疫苗。我们可以用那个日期推断出一个更https://docs . Google . com/spreadsheets/d/1 z5u 88 _ NVC 84 C4 B4 kpynbp 5 wy 6 liuya 2t 9 ipqmhffpjr 4/edit?usp =共享
raw_data = pd。excel file(' Hawaii _ Data _ Collaborative _ COVID _ Dashboard _ Data . xlsx ')
print(raw _ Data . sheet _ names)
df = raw _ Data . parse(" COVID Data ")
VAX = raw _ Data . parse(" DOH 疫苗接种数据")
hos = raw_data.parse("医院数据")

读入时间轴数据作者 Lambert Leong # # #

# #下载于https://www . Lambert Leong . com/assets/docs/Data 下面的代码就是这么做的。

### find when the vaccines got to the islands and set that point to zero ###
start_vax=events[events['Event'].str.contains('vaccine')]['Date'].values[0]
test=clean_vax.copy(deep=True)
test['Value'][test[test.Date==start_vax].index[0]]=0
### interpolate ###
test=test.interpolate()
test=test.fillna(0)fix_vax=quick_plot(test)
fix_vax#.savefig('covid_caseVrate_iterpol.png', dpi=300, bbox_inches='tight')

(图片由作者提供)

如你所见,从疫苗到达夏威夷开始,垂直的绿线逐渐增加。这是一个例子,说明了如何使用相关的外部数据来进行数据分析或解释。我们没有指定什么函数,我们想使用插值和默认是线性的,因此,直线。从能量消耗到酶动力学,在生物学中经常观察到 sigmoid 曲线。疫苗接种率也可能遵循 s 形曲线。然而,据我所知,这个函数没有内置 sigmoid 插值。下一个最好的方法是使用二次函数。毕竟曲速行动是一件事,有一个尽快接种疫苗的推动,这可以反映二次率。我们使用下一个代码片段来获得更真实的插值曲线。

我们还用下面的代码解决了第二个问题。第二个问题与蓝色和红色曲线的分辨率有关。利率很低,很难看到。我们将比率乘以 10,以便它与疫苗比率更加一致。通过将比率乘以 10,我们可以有效地计算出每 10,000 个案例的比率,因为在上述比率计算中用作分母的总案例数以千计。我们将值 10 传递给 plot 函数,以指示比例的变化。

### find when the vaccines got to the islands and set that point to zero ###
start_vax=events[events['Event'].str.contains('vaccine')]['Date'].values[0]
test=clean_vax.copy(deep=True)
test['Value'][test[test.Date==start_vax].index[0]]=0
### interpolate ###
'''
sigmoid curve is more realistic for vaccine rate but since there is not a good way to
model quickely, we settle for a quadratic interpolation.
'''
test=test.interpolate(method='quadratic',limit_direction='forward')
test=test.fillna(0)

fix_scale=quick_plot(test,10)
fix_scale#.savefig('covid_caseVrate_fixed.png', dpi=300, bbox_inches='tight')

(图片由作者提供)

现在一切看起来都应该如此。只有图例需要更新,以反映死亡率和住院率的比例变化。

接下来,我们将案例数据与事件数据合并,以创建一个主数据表,用于下面的动画。我们通过用斜线代替破折号来调整数据时间字符串,使其在图上更加简洁易读。

### merge case data with vaccine data ###
clean_vax=pd.merge(clean, vax.iloc[:,:2], on='Date', how='left')
clean_vax=pd.merge(clean_vax, hos.iloc[:,:-1], on='Date', how='left')

### find when the vaccines got to the islands and set that point to zero ###
start_vax=events[events['Event'].str.contains('vaccine')]['Date'].values[0]
clean_vax['Value'][clean_vax[clean_vax.Date==start_vax].index[0]]=0
### handleing trailing NaN ###
max_vax=clean_vax.Value.idxmax()
clean_vax.Value[max_vax:]=clean_vax.Value.max()
### interpolate ###
clean_vax=clean_vax.interpolate(method='quadratic',limit_direction='forward')
clean_vax=clean_vax.fillna(0)
### clean date strings for presentation ###
clean_vax.Date=clean_vax['Date'].dt.strftime('%-m/%-d/%y')
### merge with events ###
full=pd.merge(clean_vax,events, how='left', on='Date')### create fields we want###
full['vax_rate']=full['Value']
full['death_rate']=full['Deaths_Tot']/full['total_cases']*10
full['hospital_rate']=full['Active Hospitalized']/full['total_cases']*10full.death_rate.max(), full.total_cases.max()/10, full.hospital_rate.max()

我们有完全合并的数据,我们可以使用新的 2 轴绘图功能来构建我们的最终绘图。

def plot_2axis(dates: pd.Series,cases: pd.Series, deaths: pd.Series, vax: pd.Series, hos: pd.Series, ymax1:float=None,ymax2:float=None, xsize: float=7,ysize: float=5):
    fig, ax = plt.subplots()
    fig.set_size_inches(xsize, ysize)
    ax2 = ax.twinx()
    ### plot data by date ###
    ax.plot_date(dates,cases, ls='-',marker='',color='darkorange',label='Total Cases')   
    ax2.plot_date(dates,vax, ls='-',marker='',color='g',label='Total Vaccination Rate')
    ax2.plot_date(dates,hos, ls='-',marker='',color='b',label='Hospitalization Rate per 10K cases')
    ax2.plot_date(dates,deaths, ls='-',marker='',color='r',label='Death Rate per 10K cases')
    ### set axis limits ##
    if not ymax1:
        ymax1=cases.max()*1.1
    if not ymax2:
        ymax2=max([deaths.max(),vax.max(),hos.max()])*1.1 #1
    ax.set_ylim([0,ymax1])
    ax2.set_ylim([0.0,ymax2])

    ax.margins(x=0)
    ax.xaxis.set_major_locator(MultipleLocator(30))
    ax.xaxis.set_minor_locator(MultipleLocator(1))
    ### date orientation and freq ###
    fig.autofmt_xdate(ha='center',rotation=90) #BTW, this is the slowest operation ### grid lines ###
    ax.grid(axis='y',color='darkorange', alpha=0.5)
    ax2.grid(axis='y')
    ax.grid(axis='x')
    ax.locator_params(axis="y", nbins=6)
    ax2.locator_params(axis="y", nbins=6)
    ### legend ###
    ax.legend(loc=2, title="Total Cases\n(left y-axis)", fontsize='small', fancybox=True)
    ax2.legend(loc=9, title="Rates\n(right y-axis)", fontsize='small', fancybox=True, framealpha=0.5)
    ### scoreboard ###
    ax.set_ylabel('Total Cases', fontsize='medium')
    ax2.set_ylabel('Rates', fontsize='medium')
    ax.set_xlabel('Date', fontsize='medium')
    ### title ###
    ax.set_title('Hawaii COVID-19 Cases and Rates')
    scoreboard= 'Total Cases (orange) = '+str(round(cases.values[-1],0))
    scoreboard+='\nDeath Rate (red) = '+str(round(deaths.values[-1],2))
    scoreboard+='\nHospitalization Rate (blue) = '+str(round(hos.values[-1],2))
    scoreboard+='\nVaccination Rate (green) = '+str(round(vax.values[-1],2))
    ax.text(0.3, 0.55, scoreboard, horizontalalignment='left', verticalalignment='center', 
             transform=ax.transAxes,fontsize='small')
    ax.text(0.1, 0.051, 'lambertleong.com',
             fontsize='large', color='gray',
             ha='left', va='bottom', alpha=0.5, transform=ax.transAxes)
    ax.text(0.65, 0.38, 'lambertleong.eth',
             fontsize='large', color='gray',
             ha='left', va='bottom', alpha=0.5, transform=ax.transAxes)
    plt.close()
    return fig

我们使用下面的代码来调用上面定义的绘图函数。

dates,t,d,v,h=full['Date'],full['total_cases'],full['death_rate'],full['vax_rate'],full['hospital_rate']
ymax1=full.total_cases.max()
ymax2=1
chart=plot_2axis(dates,t,d,v,h,ymax1=ymax1,ymax2=ymax2)
chart#.savefig('hawaii_covid_caseVrate.png', dpi=100, bbox_inches='tight')

(图片由作者提供)

看那个好看又有料的剧情!!!但是我们还没有完成。我们现在需要用下面的代码制作动画。动画代码基本上是在一个循环中调用 plot 函数,并且每次都向它传递一个更具填充性的数据帧版本。这些图被传回,转换成图像,imageio 用于将图像序列写入任何视频格式,在本例中为 mp4。

### Build Animation for case vs rate ###
'''
writer = imageio.get_writer('static_2axis.mp4', fps=20)
max_y=full.total_cases.max()
ysize, xsize = 5.25,7ymax1=full.total_cases.max()
ymax2=1
xmax=full.Date.values[-1]start = time.time()
for i in range(1,full.shape[0]):
    #if i>365:
    #    break
    sub_df=full.iloc[:i]
    dates,t,d,v,h=sub_df['Date'],sub_df['total_cases'],sub_df['death_rate'],sub_df['vax_rate'],sub_df['hospital_rate']
    chart=plot_2axis(dates,t,d,v,h,xsize=xsize,ysize=ysize, ymax1=ymax1,ymax2=ymax2)dpi=int(chart.dpi)
    chart.canvas.draw()
    image = np.frombuffer(chart.canvas.tostring_rgb(), dtype='uint8').reshape(int(ysize*dpi),int(xsize*dpi), 3)
    #print('\t\tto image')
    writer.append_data(image)
    print('processed =',i,' of ',full.shape[0],end='\r')writer.close()
end = time.time()
print('2 axis build time =',end - start)
'''

(作者视频)

上面的视频是用到目前为止的所有代码制作的。动画增加了故事情节,你可以通过观看剧情的视频版本来收集更多的信息。

绘图函数被注释掉了,因为它们确实很慢。虽然有许多方法可以加快视频构建时间,例如并行化,但这些 HPC 主题超出了本文的范围,我可能会继续发表一篇关于如何处理速度问题的帖子。由于我们不打算一直以高吞吐量构建视频,所以当前的速度很好。你可以一边运行它,一边品尝咖啡或茶。

事件图

现在,我们可以充分利用事件数据,并针对数据框架中的任何数据列创建带注释的动画图。我们使用下面的函数来处理所有的绘图。

def build_covid_timeline_plot(data_df: pd.DataFrame, data_field: str, title:str, xsize:float=15 ,ysize:float=10,video:bool=False,
                              plt_color:str='blue'):#
    ''' creates a timeline plot which contains a line plot with date on the x axis and some eval metric on the yargs:
            data_df (pd dataframe): dataframe containing atleast a date field and eval metric field
            (stored cummulatively) and "Events" field
            data_field (str): field to be plotted on the y axis
            xsize (int): plot width in inches, default = 15
            ysize (int): plot height in inches, default = 9
            plt_color (str): color of the line on the plot
            video (str): path to dir that stores video frames
    '''
    #define plot dimensions
    fig, ax = plt.subplots()
    fig.set_size_inches(xsize, ysize)
    ymax=int(data_df.loc[data_df[data_field].idxmax()][data_field])+20
    ymin=-(ymax// 1.5)
    ytot=ymax+(-1*ymin)
    #plotting
    ax.plot_date(data_df['Date'],data_df[data_field], ls='-',marker='',color=plt_color)
    ax.set_ylim([ymin,ymax])
    ax.axhline(y=0,color='black')#, xmin)
    #event plotting
    last_event=''
    rot=0
    color=None
    event_count=0
    large_list=0
    for i, row in data_df.iterrows():
        e_type=row['Type']
        if pd.isnull(e_type):
            continue
        e_type=str(e_type)
        if 'open' in e_type:
            color='green'
        elif 'close' in e_type:
            color='red'
        elif 'yield' in e_type:
            color='darkorange'
        elif 'event' in e_type:
            color='black'
        offset=1-(event_count%5*.18)-0.05 #.98 to pull off bottom axis
        event_count+=1
        bottom_y=(-1*ymin*offset)/ytot
        top_y=(int(row[data_field])+(-1*ymin))/ytot
        my_wrap = textwrap.TextWrapper(width = 15)
        #print(row)
        event_list = my_wrap.wrap(text=row['Event'])
        event_list=('\n').join(event_list)
        ax.axvline(row['Date'],ymin=ymin,ymax=top_y,c=color,label=e_type)
        ax.text(row['Date'],offset*ymin,event_list,rotation=rot, fontsize='x-small', ha='center',va='bottom', wrap=False,
                bbox=dict(boxstyle='square', fc='w', ec=color)#,alpha=.7)
               )
        last_event=event_list
    for tick in ax.yaxis.get_major_ticks():
        tick.label.set_fontsize(8)
    ax.margins(x=0)
    ax.xaxis.set_major_locator(MultipleLocator(30))
    ax.xaxis.set_minor_locator(MultipleLocator(1))
    fig.autofmt_xdate(ha='center',rotation=90)
    yticks = ax.yaxis.get_major_ticks()
    for i in range(1,len(yticks)//2-1):
        yticks[i].set_visible(False)
    tick_params(axis='x', which='major', labelsize=8)
    ax.grid(axis='x')
    ax.grid(axis='y')
    ax.spines['right'].set_color('none')
    ax.spines['top'].set_color('none')
    ax.set_title('Calendar Events and Hawaii COVID-19 \n'+title+' = '+
                 str(int(data_df.iloc[-1][data_field]))+' as of '+data_df.iloc[-1]['Date'], fontsize='xx-large')
    ax.set_ylabel('Number of '+title+' Cases', fontsize='medium')
    ax.set_xlabel('Date', fontsize='medium')
    handles, labels = ax.get_legend_handles_labels()
    handout=[]
    lablout=[]
    #rename legend
    for h,l in zip(handles,labels):
        if l == 'yield':
            l='warning'
        if l not in lablout:
            lablout.append(l)
            handout.append(h)
    ax.legend(handout, lablout,loc=7, title="Type of Event", fontsize='small', fancybox=True)
    ax.text(10, ytot*.0, 'lambertleong.com',
             fontsize='xx-large', color='gray',
             ha='left', va='bottom', alpha=0.5)
    #fig.tight_layout()
    ax.text(data_df['Date'].values[-1],data_df[data_field].values[-1], str(int(data_df[data_field].values[-1])),
             fontsize='large', color='black',
             ha='right', va='center', alpha=1.0)
    if video:
        ax.text(0.08,0.88,last_event,rotation=rot, fontsize='xx-large', ha='left', va='top', wrap=False,
        bbox=dict(boxstyle='square', fc='w', ec=color), transform=ax.transAxes)
    plt.close()
    return fig

由于事件数据中有许多事件,所以我内置了一种选择图的大小的方法,这样注释就不会挤在一起。让我们调用函数并查看图形。

xsize=16
ysize=12
chart=build_covid_timeline_plot(full, 'total_cases','Total Cases', xsize=xsize ,ysize=ysize,
                                plt_color='orchid',video=True)#, save='test', video=None)
chart#.savefig('totalcasesVevents.png', dpi=100, bbox_inches='tight')

(图片由作者提供)

我们走吧!我们可以看到所有案例的所有相关事件。通过这样的图,你可以看到不同的事件是如何引起斜率变化的。看看与 delta 和 omicron 相关的事件。同样,在保存图形时,可以调整一下 dpi,这样你就可以得到你想要的分辨率。

chart=build_covid_timeline_plot(full, 'new_cases','New Cases', xsize=xsize ,ysize=ysize,
                                plt_color='chocolate')
chart#.savefig('newcasesVevents.png', dpi=300, bbox_inches='tight')

(图片由作者提供)

上面是相同的事件图,但我们可以看看与事件相关的新案例。下面的代码将帮助我们制作这些事件图的动画。我们同时处理两个图,但是逻辑与前面的动画代码相同。再说一次,它不是最快的,所以启动它,在它建立的时候伸展一下你的腿。

### Build Animation ###
'''
start = time.time()
writer = imageio.get_writer('case_events2.mp4', fps=15)
writer2 = imageio.get_writer('new_case_events2.mp4', fps=15)
for i in range(1,full.shape[0]):
    #if i>365:
    #    break
    sub_df=full.iloc[:i]
    chart=build_covid_timeline_plot(sub_df, 'total_cases','Total Cases', xsize=xsize ,ysize=ysize,
                                plt_color='orchid',video=True)
    dpi=int(chart.dpi)
    chart.canvas.draw()
    image = np.frombuffer(chart.canvas.tostring_rgb(), dtype='uint8').reshape(ysize*dpi, xsize*dpi, 3)
    #print('\t\tto image')
    writer.append_data(image)
    ### writer2 below ###
    chart2=build_covid_timeline_plot(sub_df, 'new_cases','New Cases', xsize=xsize ,ysize=ysize,
                                plt_color='chocolate',video=True)
    dpi=int(chart2.dpi)
    chart2.canvas.draw()
    image2 = np.frombuffer(chart2.canvas.tostring_rgb(), dtype='uint8').reshape(ysize*dpi, xsize*dpi, 3)
    writer2.append_data(image2)
    print('processed =',i,end='\r')
writer.close()
writer2.close()
end = time.time()
print('event plot build time =',end - start)
'''

(作者视频)

你有它!你制作了多个非常有启发性的动画情节。现在,我鼓励您将代码和逻辑应用到您自己的数据分析或项目中。

继续召集

我们很乐意继续会议,欢迎您的反馈,并喜欢听你的解释!

参考文献

  1. 夏威夷数据合作。(2021)夏威夷数据协作 COVID 仪表板数据。2022 年 1 月 31 日检索,来自https://health . Hawaii . gov/coronavirusdisease 2019/current-situation-in-Hawaii/
  2. 梁立伯。(2021)Hawaii _ covid _ events _ Jan 22 . CSV . 2022 年 2 月 25 日检索自https://www . Lambert leong . com/assets/docs/data/Hawaii _ covid _ events _ Jan 22 . CSV

可视化音频数据并执行特征提取

原文:https://towardsdatascience.com/visualizing-audio-data-and-performing-feature-extraction-e1a489046000

丹尼尔·施鲁迪在 Unsplash 上拍摄的照片

介绍

音频只是简单的声音,我们在日常生活中听到的声音。例如,通过经验,人类大脑可以很好地区分救护车的声音和出租车的声音。但是我们能创建机器学习模型来进行这样的分类吗?

处理音频数据变得有点势不可挡,因为我们无法像处理一组表格或图像那样将其可视化。在本文中,我们将可视化音频数据,然后从音频中提取有用的特征。

数据

数据收集自 Kaggle 。它包括 19 种不同风格音乐的音频数据。音频文件每个 30 秒。

我们有一个流派文件,具有以下特点:

  • 流派:流派的名称
  • 流派 id:流派的唯一 id

作者图片

作者图片

训练数据帧包括:

  • 歌曲 id:音频文件的唯一 id
  • 文件名:文件的名称
  • 文件路径:音频文件可用的路径
  • 流派:流派的名称
  • 流派 id:流派的唯一 id

作者图片

作者图片

除了列流派和流派 id 之外,测试数据具有与列车相同的列数

快速浏览数据中可用的不同类型。

genre _ group = train . group by([' genre '])。计数()

作者图片

我们在类型变量上有一个不平衡;像电子和摇滚这样的流派有近 3000 个样本,而像布鲁斯和轻松听这样的流派甚至没有 100 个样本。

在将其传递给模型时,需要考虑到这一点。

重要的图书馆

这个库使得返回所有匹配特定模式的路径变得更加容易。因为我们将音频数据文件存储在基于流派的不同文件路径中,所以我们将使用这个库。

librosa :这个库是一个用于音乐和音频分析的构建模块。

IPython.display: 听我们笔记本里的音频。

让我们导入这些库:

音频文件格式

音频数据可以以不同的格式编码,如 wav,它代表波形音频文件格式。WAV 没有压缩,只有一些转换来存储声音,这将导致巨大的音频文件大小。

MP3 问世了,它通过压缩使音频文件变得更小。

我们的数据中有 Ogg 格式的音频文件。OGG 是一种开放的容器格式,将艺术家的元数据与音频文件一起存储。

可视化数据

我们使用 librosa load 加载不同流派的音频数据,并使用 matplotlib 绘制它们。

天秤座。load 返回一个音频时间序列,描述音频在不同时间步长的幅度。

振幅是麦克风或录音设备附近的压力变化。

正如你可以从下面的视频中看到的,不同类型的不同时间步长的幅度是非常不同的。

在对不同的音频进行分类时,我们必须使用相同的方法。接下来,我们转向特征工程。

特征工程

我们不能将原始音频文件原样传递给我们的机器学习模型,我们需要从音频数据中提取一些特征。在文章的这一部分,我们也在做同样的工作。

1)过零率

过零率描述信号从正到零再到负或从负到零再到正的速率。该特征在音乐检索系统中用于识别噪声信号。

噪声越大,过零率越高。我们使用 librosa 的函数 zero_crossing 进行计算。

作者图片

接下来,我们还可以看到过零率。

观察:

  • pop 的过零率最低(22405),电子的过零率最高(151029)
  • 对于易听、乡村、蓝调和古典等流派,过零率远低于摇滚、嘻哈和爵士等流派。

2)速度/每分钟节拍数

每分钟节拍数指定每分钟的节拍数。很简单的定义,不是吗?

每分钟节拍是一种表示音频速度的数字方式。

作者图片

国际和实验有大约 160 bpm,而易听有 80。

可以探索的一些其他特性的列表。

和声:这些是使我们能够听到旋律的音高声音。

敲击声:这更像是源自乐器的声音,就像鼓上的拍子。

作者图片

光谱质心

计算为音频文件中频率的加权平均值。

作者图片

结论:

我们在文章中使用了音频数据,使用 Ipython 显示库,我们听到了数据集的一些样本。

我们发现,数据集是不平衡的。我们探索了音频数据的重要库以及音频数据可用的各种格式。我们已经将过零率、节奏和频谱质心用于特征工程。您可以使用 librosa 中的各种功能来生成音频数据的特征。然后,这些特征可以作为输入提供给机器学习模型,或者用于更好地理解数据。您可以探索的其他功能有这里

在任意尺度的神经网络训练中可视化反向传播

原文:https://towardsdatascience.com/visualizing-backpropagation-in-neural-network-training-2647f5977fdb

使用 HiPlot 生成平行坐标图来可视化深度学习模型训练。

一个平行坐标图来可视化 ML 训练(图片由作者提供)

理解和调试神经网络在数据集上的性能是机器学习(ML)模型的端到端生命周期中的关键一章。有能力理解一个模型是如何被训练的,可以提供有价值的洞察力,去发现哪里可以被改进。在本文中,我们将创建一个简单而有效的方法,在神经网络训练期间可视化一个称为反向传播的过程。我们将使用的可视化技术被称为平行坐标图。这通常是一种用于从多个数据点以不同的单位或类型可视化许多不同特征的技术。下面是本文其余部分的概要:

  1. 理解评估深度学习模型的重要性
  2. 建立基础
  3. 生成可视化

1.理解评估深度学习模型的重要性

汉斯·雷尼尔斯在 Unsplash 拍摄的照片

深度学习模型在现实世界中有着广泛的应用,从欺诈检测到自动驾驶汽车。这些应用通常会影响我们的日常生活。当一个模型被扩展以供数百万用户每天使用数百万次时,模型指标的微小改进可能会导致整体模型性能的显著改进。

通过使用可视化,我们可以更深入地理解我们的模型在每个时期的训练和表现。我们对模型了解得越多,我们在选择模型时就能做出更清晰的决策。我们还可以使用此信息来了解我们的模型是否基于所使用的评估度量过拟合或欠拟合,这有助于调整超参数。

2.建立基础

照片由艾蒂安·吉拉尔代Unsplash 拍摄

既然我们理解了可视化模型训练可以提供的好处,让我们开始构建吧!这个例子将使用 Python 版。

我们将从导入 Python 依赖项开始:

import tensorflow as tffrom keras import layers
from keras import models

对于这个示例模型,我们将使用[1] Keras Boston Housing 数据集 。该数据集包含波士顿周围房屋的 13 个要素,目标值是房屋的价值。要下载和加载数据,请添加以下几行:

# Download dataset
dataset = tf.keras.datasets.boston_housing# Test train split
(x_train, y_train), (x_test, y_test) = dataset.load_data()# Normalize data using training data
mean = train_data.mean(axis=0)
train_data -= mean
std = train_data.std(axis=0)
train_data /= std

test_data -= mean
test_data /= std

既然数据已经准备好进行训练,我们将创建一个函数来构建我们的准系统神经网络。

def build_model(): model = models.Sequential() model.add(layers.Dense(64, activation='relu',
              input_shape=(x_train.shape[1],))) model.add(layers.Dense(64, activation='relu')) model.add(layers.Dense(1)) model.compile(optimizer='rmsprop', loss='mse', metrics=['mae']) return model

最后,让我们训练模型!

num_epochs = 18model = build_model()model_history = model.fit(x_train, y_train, epochs=num_epochs, batch_size=16, verbose=0)test_mse_score, test_mae_score = model.evaluate(x_test, y_test)print(test_mae_score)

这里的模型使用平均绝对误差作为评估标准。MAE 可以用来评估我们的模型有多精确,并且可以给出性能的感觉。MAE 分数将总是正数,分数越接近 0,模型的性能越好。MAE 可以通过取误差的平均值来计算。取预测值减去实际值的绝对值,取所有这些数值的和,最后除以数据点的总数,🎉你得到了你的梅分数。

计算 MAE(作者图片)的公式

3.生成可视化

照片由 Isaac SmithUnsplash 上拍摄

现在是最精彩的部分!让我们通过生成一个平行图来直观地观察我们的基线神经网络在每个时期的模型训练情况!

为此,我们将利用脸书的开源 HiPlot 库。 HiPlot 是一个轻量级工具,能够根据提供的数据轻松快速地生成平行坐标图。这是并排显示多个要素以直观分析数据外观的一种非常方便的方式。在我们的例子中,我们可以使用提取的关于模型训练的元数据来非常快速地创建令人惊叹的交互式可视化。关于 HiPlot 的更多信息可以在 HiPlot Github 库上找到。

要生成可视化,首先需要为 HiPlot 安装一个 Python 依赖项:

pip install -U hiplot

接下来,您需要导入 HiPlot 并使用函数来生成绘图。

import hiplot as hipdata = [{'epoch': idx,
         'loss': model_history.history['loss'][idx], 
         'mae': model_history.history['mae'][idx]}
       for idx in range(num_epochs)]
hip.Experiment.from_iterable(data).display()

上面的代码将使用 Keras model.fit()方法的返回值将模型历史加载到 HiPlot 中,以便进行可视化和交互。

与 HiPlot 生成的平行图交互(图片由作者提供)

🎉恭喜你建立了神经网络模型,并用 HiPlot 生成的并行图可视化了反向传播过程!您会注意到,对于每个时期,MAE 得分逐渐变得更好,现在我们能够分析并与这些数据进行交互,同时查看拟合在确切时期逐渐减弱的位置。

通过这个过程,我们可以看到创建这些交互式可视化的可行性和无缝性,同时还可以通过利用训练历史数据来深入了解模型在训练期间的运行情况。使用 HiPlot 的最大优势之一是您可以添加许多其他属性来扩展到可视化中,以绘制更好的图片,而无需显著增加计算能力需求。

参考

[1]波士顿住房数据集:https://keras.io/api/datasets/boston_housing/

Harrison d .和 Rubin feld d . l .‘享乐价格和对清洁空气的需求’(1978),J. Environ。经济学&管理学,第 5 卷,81–102 页

可视化大麻销售和成本

原文:https://towardsdatascience.com/visualizing-cannabis-sales-and-cost-ddd35c2402b1

将管理报告转换成有意义的信息

来自 Unsplash 的简·兹沃索埃德拍摄的大麻药房图片

要学习的最实用的技能之一,尤其是在数据可视化方面,是将管理报告转换成易于理解的图表。这项技能对于准备在社会和环境影响组织、非营利部门和政府部门工作的学生尤为重要。这些部门充满了大量的汇总数据表,需要大量的工作来审查和理解。有时,表格的数量多得令人无法承受,这就提出了它们是否能增加价值的问题。

数据可视化技能有利于解决这个问题。本文展示了我将管理报告转换成有意义的图形的工作流程。我使用可公开访问的数据,允许新的学习者重新创建(并改进!)我的工作流程。这个项目的原始数据可从密歇根州的大麻管理局获得,可通过此链接访问。

有哪些信息需求?

对于这个项目,我有兴趣了解密歇根的大麻销售趋势。密歇根州大麻管理局(CRA)在其网站上以 PDF 格式公开这些数据。报告以标准格式每月发布一次,大约 20 页,并填充有数据表,因为这些报告提供每月数据。由于它们没有累积汇总,推断趋势需要对每份报告进行人工审查。请注意,这些是政府数据,不需要任何特殊许可即可访问或使用。

密歇根州监管机构的报告截图

新学员的一个常见问题是试图可视化所有数据。有时,您会遇到由于表的创建者和最终用户之间的脱节而导致的大量表摘要。避免认为目标是可视化数据。你不代表你就应该。阐明数据和可视化的目的。最终用户是要探索数据并做出发现吗?如果是,需要包括哪些重要的数据点?或者,你希望数据讲述一个故事——也就是说,解释一些事情?启动一个成功项目的最关键步骤是明确信息需求和最终用户。

什么样的数据产品能满足受众的信息需求?

当创建像可视化这样的数据产品时,你需要专注于你的受众和他们的信息需求。在这个例子中,我是希望深入了解大麻销售和成本的观众。

当启动一个新项目时,我喜欢通过勾画出最终产品或可交付成果的具体想法来创建一个可视化的路线图。路线图有助于建立一个清晰、连贯且有界限的计划。没有边界,项目将面临严重的项目漂移风险,这是我在最近的一篇文章中详细讨论的主题。

https://medium . com/forward-data-science/protection-yourself-against-the-big-problem-in-data-consulting-projects-16d 76 b 779821

保持专注的另一个方法是清楚什么是有趣的和什么是有用的。当你专注于有趣的事情时,项目会很快变得臃肿。专注于有用的东西。

以下图片是我对这个项目的设想的例子。一些折线图和一个精确值的表格。不需要复杂。

作者的素描

如何高效地准备一个高质量的数据集?

当我教授数据入门课程时,我会在准备数据的基本技能上花费大量时间。我的理由很简单。数据密集型项目中的大部分资源都用于数据准备。由于我无法从国家获取原始数据文件,我不得不从月度报告中提取数据。

我尝试了一些不同的工具来从 pdf 中提取数据。Tabula 是一个优秀的开源程序,假设这些报告共享相同的结构,但这个项目不是这样的。我考虑了一些其他基于代码的解决方案,但认为将脚本放在一起的麻烦不值得。我决定最好的时间利用是分配几个小时来手动提取数据。

同样,我以简单为目标,选择 Google Sheets 作为我的主要数据存储。我填充了五个不同的字段:

  • 日期:月度报告日期
  • MedFlower_lbs:作为医用大麻出售的可点燃抽吸的花蕾(即花)磅数
  • RecFlower_lbs:作为娱乐大麻出售的可点燃抽吸的芽磅数
  • MedFlowerAvgRetail_oz:一盎司作为医用烟草出售的平均成本
  • RecFlowerAvgRetail_oz:作为娱乐用大麻出售的一盎司可吸烟花蕾的平均成本

作者准备的数据集截图

我承认我在数据提取的早期阶段偏离了方向。我提取的数据超出了我最初的兴趣。我违背了自己的原则,抓住有趣的数据,而不是专注于对 T2 有用的数据。幸运的是,我让自己重新聚焦,得到了我需要的数据。

构建数据产品

在收集了大量数据后,我得到了我最喜欢的东西:可视化数据。许多人对电子表格程序中创建的图表非常不满。理应如此。假设创建者接受默认输出,从远处发现 Excel 或 Google 工作表是没有问题的。正如我之前所描述的,你可以通过仔细应用设计原则来创建有效的图表。

https://towards data science . com/less-software-more-design-449175 a34e 59

以下示例显示了一些基本样式如何显著改善默认图形。

作者准备的图形截图

然后,在 Google Sheets 中,我通过创建一个简单的值的高亮表,或热图,进一步实现了可视化。因此,折线图提供了一种快速简单的方法来推断一段时间内的趋势,而右侧的热图提供了检查特定值的机会。

作者准备的图形截图

在这一点上,我认为我已经有了足够的正确格式的信息来提供我所需要的信息。表格显示了一种有趣的供求关系——对娱乐用大麻的需求持续大幅增长,而成本却稳步下降。数据表明,种植者正在用大麻产品充斥市场。(我不是在抱怨,只是在观察!)

所以,问题解决了,对吧?不完全是。

修订和迭代

改进你的工作和技能的最有效的方法之一是通过修改和迭代。对于这个项目,我的信息需求得到了满足。这个帖子也在密歇根州的 subred dit 上引起了很多讨论,表明其他人也对这个话题感兴趣。但是,我对设计工作不满意,认为热图占用了图形的太多空间。我决定更新数据并使用 Tableau 重建图形。我必须更新四个月的数据,但这很快就完成了,这证实了简单的手动提取是正确的方法。

考虑到创建交互式图形的功能,我决定使用 Tableau。我在这个环境中实现了以下变化:

  • 添加了一个工具提示来提供悬停时的精确值,消除了对值表的需要;
  • 工具提示中包含了一个表格计算,以显示与上个月相比的百分比变化;
  • 带有嵌入链接的图标取代了难看的 URL
  • 图表中添加了注释,突出了趋势中的显著特征;
  • 可视化在 Tableau Public 上发布,允许可视化通过链接共享;
  • 使用了不同的调色板,因为对于色盲的人来说,自然色是很难的。

随着这些变化,数据更新揭示了大麻市场中一些值得注意的事件。2022 年 3 月,每盎司成本略有上升,销售额显著下降。但是,2022 年 4 月,大麻销量翻了一番。密歇根人似乎非常重视 4 月 20 日的庆祝活动,同时享受着成本的大幅下降。您可以在 Tableau Public 上访问可视化。

后续步骤

总的来说,我对这张图很满意。当我准备启动一个新项目时,反思和记录这个过程是很有帮助的。我知道有机会进一步改善视觉效果。因此,我在 Tableau Public 上共享了 viz,这样其他人就可以下载数据和 viz。您可以随意重用数据并重新制作这种可视化,应用您的设计策略或集成额外的信息。或者,在评论区提出你的建议。

可视化复杂数据:使用语法归纳法简化树结构

原文:https://towardsdatascience.com/visualizing-complex-data-simplifying-tree-structures-using-grammar-induction-b4843b5daaf4

一组将复杂的树总结成更容易呈现和阅读的形式的方法

复杂的数据树通常可以简化而不失去其本质意义(布兰登·格林拍摄的树,经许可使用,所有其他图片由作者提供)

你有一些重要的数据,你渴望与思想家和决策者分享。像许多重要数据一样,您的数据是分层的,像树一样结构化——可能是嵌套的投资组合或供应链信息。

如果这些数据可以很容易地呈现在幻灯片上就好了,但是树往往太大、太笨拙,节点太多;很少有人能从可能有数百个微小节点的树形图中获得有用的信息。因此,您需要简化树数据,以便产生良好的可视化效果。

这是一个非常常见的场景。

当然,你可以创建一个手工制作的信息图,但是这会引入偏见和意见,而且它不能扩展。因此,挑战在于从算法上降低树的复杂性,这样就可以把它放在幻灯片上,很快被理解,同时尽可能地保留它的结构。

这是一个困难的开放式数据可视化问题,它位于艺术和科学的边界,这总是一个有趣的工作场所。令人高兴的是,有几种非常有效的算法方法可供我们考虑。我发现特别有趣的是,通常与数据压缩相关的语法归纳算法,在数据可视化领域非常有用。

在本文中,我将:

  • 展示一个合适的数据集示例
  • 探索相对简单的树简化算法
  • 引入语法归纳作为算法家族
  • 展示对示例数据应用语法归纳如何产生有用的结果。

与以往一样,代码可应要求提供。

示例数据

本文的示例数据需要来自真实世界的一些树状数据,即:

  • 足够复杂和丑陋,以至于不容易被形象化
  • 足够小,以使生成的树很好地适合文章
  • 乏味的

例子数据为什么要枯燥?嗯,如果数据是有趣的,我们会试图使用我们对数据的含义的了解来简化树——而对于纯算法的方法,我们不能这样做。

那么,我们的示例树将会又丑又无聊;这是英国当地政府部门停车收入的一个匿名子集。每个节点代表一些停车收入来源的分组,总收入的一些数量(由节点大小表示)。完整的树是这样的:

图 1:原始树数据

照目前的情况来看,这种树太复杂了,无法用幻灯片展示给决策者。我们现在将看看简化它的算法方法。

简单的算法

首先,我想看看我们可以采用的一些更简单但仍然有用的算法。

节点过滤算法

也许简化树的最天真的方法是根据规则简单地过滤掉一些节点。让我们看看当我们过滤掉收入低于特定阈值的所有节点时,我们的停车收入树是什么样子的。

图 2:应用的节点过滤

这一点用处都没有。它失去了许多重要的结构;事实上,停车场显然被划分到了弗雷姆利、威克顿和奥尔德利这三个城镇,更糟糕的是,整个收入类别消失了,没有警告决策者他们曾经在那里。也许如果我们设置一个更宽松的阈值,只删除非常小的节点,结果会更好。

图 3:具有较低阈值的节点过滤

这是一个非常糟糕的结果,证明了算法数据简化的一个常见问题——保留的并不总是重要的。简化的树与我们的第一次尝试有相同的问题,但现在它保留了原始树的奇怪方面——例如那些单个子节点的长字符串——这可能会进一步模糊信息内容。

当简化非常大的图时,节点过滤算法是有用的(参见本文中的一个例子),但是它们不一定适合直观地总结一个(相对)小的树。

深度限制算法

简化树的另一个基本方法是减少树的深度,这样从根开始没有节点可以超过 n 个节点。理论上,我们希望这通常会产生相当好的结果,因为它应该保留代表大量数据聚合的节点,并删除它们不太重要的后代。让我们尝试将深度限制为 5 个节点。

图 4:最大深度 5

这并不像简单的节点过滤算法那么糟糕,但是它有很大的缺陷。首先,它压缩了树形结构,使得它的所有方面看起来都具有相同的深度——隐藏了这样一个事实,即某些类型的停车收入显然比其他类型的更复杂。另一个问题是,通过将它们都表示为叶节点,它给人一种错觉,即‘fram ley’在某种程度上是与‘outstanding’相同的节点。它还保留了“Fines”下的许多细节,只是因为“Fines”碰巧在深度 5 已经有了许多节点。

注意,有几种不太幼稚的方法来限制树的深度;两个流行的是:

  • 叶子修剪:去除所有叶子节点。重复操作,直到达到所需的最大深度。这更好地保留了树的一些形状。
  • 自底向上深度限制:通过从靠近根的位置移除节点来限制深度,而不是移除远离根的节点。这种方法非常复杂,但是对于某些用例来说会产生很好的结果。

宽度限制算法

现在,我们尝试一组更具计算挑战性的方法。有各种宽度限制算法,通常依赖于两个步骤:

  1. 计算在特定深度可以存在的最大节点数
  2. 在每个深度,移除、移动或组合同级节点,直到不再超过最大值

例如,让我们尝试一种宽度限制方法,其中代表较少收入的兄弟节点被组合到一个“其他”节点中,直到没有节点具有两个以上的兄弟节点。

图 5:宽度限制算法限制了一个节点可以拥有的孩子的最大数量

这比过滤和深度限制方法要好得多!第一次,树的形状被保留了下来(不要介意视觉间距不是很好——那只是布局问题)。如果我们设置更苛刻的参数,迫使更多的节点组合成“其他”,结果可能会更好。

图 6:宽度限制算法,每个父母只有 2 个孩子

好吧,没关系。但是适度设置的宽度限制算法肯定是有效的。

拓扑保持算法

在简化任何图时,可以通过删除任何 2 阶节点(即恰好有 2 个连接的节点)来保留图的整体拓扑。这只是一种拓扑保持简化算法,但它是一种强大的算法。当应用于树时,它归结为删除所有只有一个子节点的节点,保留子节点并将其连接到其祖父节点。

这被称为“单例删除”,这是一个非常简单且广泛有用的算法,在我们的例子中,结果是这样的:

图 7:单例移除

这并没有影响到树的大部分,但它确实对原始树中的那些长“字符串”有所帮助,这些字符串似乎传达了很少的有用信息。它本身并没有产生一棵有用的树,但也许它会在与其他算法的结合中发光。

堆叠算法

所有的树简化算法都可以堆叠起来——按顺序应用,希望每次都能提高可视化效果。它们的应用顺序极其重要。对一棵树应用连续的简化就像对一幅图像应用连续的有损压缩算法;如何做到这一点的细节可以决定有用的信息是否被保留,噪音是否被丢弃…或者反之亦然。

在这个例子中,首先在每个节点上对树进行最小量过滤,然后进行深度限制步骤:

图 8:在深度限制之后应用节点过滤

这并不完美,但比这两种方法本身产生的效果要好得多。一些算法(如单例删除)通常最好在最后一步执行;有些作为第一步效果更好。

复杂的算法

我们已经研究了各种相当简单和直观的树简化方法,并了解了如何将它们结合起来。这些方法是有用的,但是它们有一个共同点,这限制了它们的能力,那就是它们没有考虑到整个树。每一个都查看特定的节点或一组兄弟节点,并决定是执行节点移除操作还是节点组合操作。通过使用考虑树的整体结构的算法,我们可以取得更好的结果,即使是在我们枯燥的停车收入树上。

语法归纳算法是可以实现这一点的算法家族之一。

什么是语法归纳法?

语法归纳是从一组观察结果中推导出一种语法的过程,这种语法可以反过来产生观察结果。

如果这听起来没什么用,考虑一下这就是你在压缩文件时所做的事情;您的压缩程序中的算法正在生成一个语法(在某种意义上),然后可以应用于一些少量的起始数据来重新创建原始文件。

语法归纳法还有很多其他用途。语法可以被解释为输入的重要(即频繁出现的)部分是什么的目录;这可以让我们区分大型非结构化数据集中更随机或更重复的数据区域,这在法医、电信和军事领域有很多用途。

顺序数据的语法归纳:LZW

也许最著名和最广泛使用的语法归纳算法是伦佩尔-齐夫-韦尔奇,又名 LZW,在。gif 文件。LZW 通过顺序处理一系列输入符号来提高速度,在此过程中建立一个常见符号序列的字典。这是一种非常快速的压缩文件的方法,但是对于数据可视化来说并不相关,因为对于外部树数据来说没有什么特别的顺序性。

随机存取数据的语法归纳:推论

sequitu 是一个简单而聪明的算法,它对一系列不需要顺序访问的输入符号进行操作。因此,Sequitur 及其衍生工具能够建立一个树状语法,而不是一个字典;这个树中的节点代表输入中经常出现的模式。推论对于解释一无所知的符号列表非常有用。

树上的语法归纳

然而,在我们的例子中,我们从一棵树开始,所以 Sequitur 做的很多工作对我们来说是没有用的。事实上,从一棵树开始,我们得到了一个盘子里的语法;这不是一个非常有用的语法,因为语法树对于原始树中的每个节点都有一个节点。但是我们可以用几种方法来处理语法,以缩短语法,从而产生一个更简单的树,它具有原始树的许多属性。我们可以做的一件事是识别该语法中的重要节点,如果我们实际上使用该语法来复制原始树,这些节点将被应用得最多。为此,我们将使用以下形式的算法:

  1. 定义一个“子树摘要”函数,它的输入是一个子树,输出是一个摘要—一个可以比较的数据项,它表示子树的结构。
  2. 将该函数应用于树中的每个节点。
  3. 检查树并使用“子树匹配”功能比较子树摘要。确定汇总匹配的节点。
  4. 将这些节点替换为占位符节点,表示找到了循环结构。
  5. 回到步骤 2,根据需要重复多次。

这里大量的参数化是可能的。

(懒惰和好奇的读者请注意——是的,也可以将树序列化为字符串,对其运行 Sequitur,然后进一步处理 Sequitur 生成的树,对于大型的真实用例,这通常比原始树小得多。然而,这种方法在许多现实世界的用例中存在问题,因为 Sequitur 不能很容易地进行“模糊匹配”,而在现实生活中,我们经常想要识别接近但不完全相似的子树。)

将语法归纳应用于我们的示例树

对于我们的简单示例,我们将使用这些函数和参数:

  • 子树摘要函数:“摘要”是一个字符串,通过序列化子树创建,包括子树节点的名称。在真实的用例中,通常最好不要将节点名称合并到摘要中。
  • 子树匹配函数:我们比较字符串,看它们是否相等。
  • 其他参数:我们只遍历树一次,所有匹配其他节点的节点都被替换为汇总节点。

汇总节点的名称以'…'结尾,因此您可以看到它们是哪一个。如果我们为适当的布局而烦恼,我们会做一些更复杂的事情。

结果如下:

图 9:简单语法归纳步骤后的树

它没有对那些单个节点的长字符串做任何事情,但这不是问题,因为我们已经有了解决方案。

语法功能成功地实现了认识到各种道路和区域都是“同类事物”,并用占位符替换它们,忽略了频繁重复的关于居民、贸易、机器等等的细节。因此,树的节点减少了,但重要的结构大多被保留了下来。

本质上,我们已经对树应用了基于语法归纳的压缩级别,但是随后(因为我们的目的是可视化简化的树,而不是重新创建原始的树)我们已经抛弃了语法本身,即子树摘要的树。

简化示例树

现在让我们把所有的技术放在一起,把我们简单的语法归纳算法和我们讨论过的一些其他算法放在一起。我认为这是一个非常好的树的简化,使用了语法归纳和单例移除:

图 11:语法归纳加上单例移除很好地简化了树

这是另一种解释,通过结合语法归纳、单例移除和宽度缩减,极大地减少了节点数,从而产生了一个大大简化的树,它仍然具有原始树的大部分形状:

图 12:添加节点过滤进一步简化了树

它并不完美——视觉效果当然还需要改进——但它比我们原来的树更适合在幻灯片上展示。

后续步骤

我希望你喜欢这些算法。当然,算法不是一切;除了纯粹的地形算法之外,还有许多重要的技术可以使用,但本文并未涉及:

  • 视觉效果和布局。在实际显示阶段,可以做大量的工作来使树更加清晰。
  • 树的语义处理。在现实生活中,领域知识或者仅仅是树所代表的常识非常重要。
  • 数据整形和数据质量。像许多真实世界的数据集一样,我们的停车收入数据集很难看,有很长的单线字符串,不必要的细节,有些地方还有无法理解的节点标签。在可能的情况下,修复上游的实际数据是为下游提供更好的决策支持的强大工具!

然而,在本文中,我介绍了一个树简化方法的目录,所有这些方法都是完全自动的,可以一起使用来减少树的节点数,同时保持其形状,从而帮助可视化数据和做出决策。

我还试图展示压缩算法是如何帮助汇总和可视化复杂数据的众多强大技术之一。在过去的十年中,人类已经收集了一个又一个复杂的图形数据,然而我们对可视化和理解这些数据的技术的探索才刚刚开始。

感谢您的阅读。

用 Python 可视化 CPU、内存和 GPU 工具

原文:https://towardsdatascience.com/visualizing-cpu-memory-and-gpu-utilities-with-python-8028d859c2b0

分析 CPU、内存使用和 GPU 组件,以监控您的 PC 和深度学习项目

帕特里克·帕尔克在 Unsplash 上的照片

当你沉迷于编程时,你是在试图计算、调试、编码来实现想要的任务。当项目更加复杂时,有几种情况需要打开多个选项卡进行研究,打开多个 Python 文件进行代码调试,打开各种数据元素进行分析。但是“轰”——突然有一个崩溃。

当您在编写各种项目代码时,您忘记了检查所利用的资源,这可能会导致未保存的数据丢失或更糟。CPU 和内存的使用是计算机系统的关键部分。GPU 是监控深度学习项目的必要元素。

在本文中,我们将研究一个 Python 项目,它将可视化并显示我们有兴趣分析的所有主要参数。我们将使用特定的工具使这一分析过程更加简单,因为这些资源对于系统监控、分析、限制进程资源和进程管理非常有用。

为了在你的预算范围内找到建造你自己的个人电脑的最佳质量,我推荐从这个链接查看我以前的指南之一。如果您有兴趣了解 GPU 对于您的 PC 构建的复杂性和要求,我建议您查看下面提供的文章,其中包含了关于探索这一概念的深入指南。

了解必要的库要求:

照片由路易莎·布姆布尔Unsplash 上拍摄

在这一节中,我们将了解成功完成这个项目所需的一些基本库要求。大多数这些库可以用一个简单的 pip 命令安装,不需要任何额外的步骤。让我们看一下前两个安装,您将需要读取和分析 CPU、内存和 GPU 组件。

pip install psutil
pip install GPUtil

如果您在安装过程中遇到第二个命令的问题,我建议您使用下面的 pip install 命令,并按照作者提供的 GitHub 链接继续参考。

pip install gputil

通过这些简单的库安装,用户已经可以获得这个项目所需的所有基本信息。首先,让我们导入两个库,通过它们我们可以测试它们是否被正确安装并按预期工作。一旦库被导入,我们就可以在使用 PC 时查看 CPU 的性能和内存使用情况。

可以遵循相同的步骤来分析 GPU 性能以及监控 GPU 消耗了多少内存。您可以注意到,开始时打印的值是相当恒定的。但是,当您移动光标或在系统上执行一些小操作时,打印的详细信息会出现峰值。下面是测试以下过程的代码块。

# Importing the essential libraries
import psutil
import GPUtil# Testing the psutil library for both CPU and RAM performance details
print(psutil.cpu_percent())
print(psutil.virtual_memory().percent)# Testing the GPUtil library for both GPU performance details
GPUtil.showUtilization()

在下一步中,我们将会看到一些可视化的东西,我们可能会发现这些东西对于轻松开发我们的项目很有用。我们将导入 numpy 和 matplotlib 库,通过它们我们可以创建自己的随机数据元素并相应地绘制信息。下面是测试和执行以下操作的代码片段。

# Importing the numpy and visualization library
import numpy as np
import matplotlib.pyplot as plt# Plotting the axis
plt.axis([0, 10, 0, 1])# Creating a random scatter plot
for i in range(10):
    y = np.random.random()
    plt.scatter(i, y)
    plt.pause(0.05)plt.show()

在上面的代码片段中,我提供了我们可以用可视化技术实现的一瞥。如果观众对可视化更感兴趣,这是数据科学不可或缺的一部分,我建议查看下面的文章。它涵盖了八种最重要的可视化技术,对于您计划构建的任何数据分析项目来说,这些技术都是必须考虑的。

</8-best-visualizations-to-consider-for-your-data-science-projects-b9ace21564a>

用 Python 开发自己的内存跟踪器:

作者图片

现在我们已经理解了所有的库需求以及如何有效地使用它们,我们可以开始开发我们的项目了。我们的目标是获得一个散点图,如上图所示,通过它我们可以相应地持续监控我们的资源。当它在执行深度学习项目、游戏、3-D 渲染或其他类似任务等要求更高的应用程序中达到其上限时,我们可以知道如何处理这种情况。

为了开发这个项目,我们将把前面讨论的所有库导入到一个 Python 文件中。一旦我们完成了这些库的导入,我们就可以继续创建一个无限的 for 循环,只要你的计算机还开着,这个循环就会运行很长时间。在这个循环中,我们将获得 CPU 利用率百分比、内存使用情况和 GPU 信息。下面是完整的代码块,用于绘制我们计划监视的各种系统任务的所有重要细节的信息。

# Importing the required libraries
import psutil
import GPUtil
import numpy as np
import matplotlib.pyplot as plt# Creating an almost infinite for loop to monitor the details continuously
for i in range(100000000):
    # Obtaining all the essential details
    cpu_usage = psutil.cpu_percent()
    mem_usage = psutil.virtual_memory().percent
    print(cpu_usage)
    print(mem_usage) # Creating the scatter plot
    plt.scatter(i, cpu_usage, color = "red")
    plt.scatter(i, mem_usage, color = "blue")
    plt.legend(["CPU", "Memory"], loc ="lower right")
    plt.pause(0.05) # Obtaining the GPU details
    GPUtil.showUtilization()# Plotting the information
plt.show()

我只为前两个组件创建了图,因为我在当前的过程中不会真正使用我的 GPU。如果用户认为有必要,他们可以自由地为 GPU 和其他功能创建自己的方法和图表。一旦程序完成,运行它的最佳方式是在命令提示符(或终端)中,在工作目录中打开它并运行 Python 文件。一旦程序运行,您就可以持续不断地监控相应的必要信息。

结论:

翁贝托Unsplash 上拍摄的照片

“想象力比知识更重要。因为知识是有限的,而想象力包含整个世界,刺激进步,催生进化。”
—爱因斯坦

当您在本地系统上处理大量打开了大量文件、文件夹和选项卡的任务和项目时,持续监控您的资源消耗以确保它们不会超出限制总是一个好主意。虽然这些问题在高端系统或工作站中并不常见,但对于大多数普通笔记本电脑和 PC 用户来说,确认其资源的最佳利用是强制性的。

在本文中,我们了解了一些基本的库需求,以及如何利用它们来分析 CPU 消耗、内存使用和 GPU 统计数据。我们还研究了一些基本的可视化概念,以相应地绘制这些内存实用程序。最后,我们将所有这些元素结合在一起,用 Python 开发了我们的内存跟踪器,用于监控您的 PC 和深度学习项目。

如果你想在我的文章发表后第一时间得到通知,请点击下面的链接订阅邮件推荐。如果你希望支持其他作者和我,请订阅下面的链接。

https://bharath-k1297.medium.com/membership

如果你对这篇文章中提到的各点有任何疑问,请在下面的评论中告诉我。我会尽快给你回复。正如我在之前的文章中承诺的那样,我会尽量每个月发三到五篇文章!

看看我的一些与本文主题相关的文章,你可能也会喜欢阅读!

</7-python-programming-tips-to-improve-your-productivity-a57802f225b6>

谢谢你们坚持到最后。我希望你们都喜欢这篇文章。祝大家有美好的一天!

通过 GitHub API 可视化开发者活动

原文:https://towardsdatascience.com/visualizing-developer-activity-via-the-github-api-48c38c40914f

我们能以某种方式测量开源之爱吗?

罗曼·辛克维奇·🇺🇦在 Unsplash 上拍摄的照片

除了托管我们宝贵的代码,GitHub 还提供了大量的REST API。这些可以用于检索给定存储库的各种有用的度量,这也可以提供关于其当前状态的想法。Julia 是一种相对较新的语言,然而,它的包生态系统在过去几年里已经有了巨大的发展。新套餐的数量好像暴涨了!我想说的是,仅仅开发一个包是远远不够的,我们还需要维护和改进它。拥有成熟的套餐生态系统也有助于吸引新用户。

在本文中,我将向您展示如何使用 GitHub API 比较不同包的各种指标。在此基础上,我们也将尝试得出一些关于他们受欢迎程度的结论。为了实现这一点,我们将在 Pluto 笔记本环境中使用另一个包 GitHub.jl 。源代码可在这里获得。

先决条件

为了使用 GitHub API,建议使用个人访问令牌来验证请求,如这里描述的如果没有它,将会有一些限制,例如将请求限制为每小时 60 个。要授予的权限级别由您决定,但是最低限度的读取权限是必要的。下面显示了一个设置示例:

作者图片

添加必要的包

Pluto 内置的包管理器将处理包及其依赖项的安装。我们将利用以下内容来处理数据和创建图:

using GitHub, JSON, DataFrames, VegaLite, Dates

证明

使用前面描述的个人访问令牌,我们可以生成一个身份验证对象,稍后作为关键字参数传递给我们的请求。在代码中硬编码标记不是一个好的做法。相反,您可以从存储在私有位置的文件(例如 JSON)中读取它们。

# Contents of JSON file: 
# { "GITHUB_AUTH":"YOUR_TOKEN" }access_token = JSON.parsefile("/path/to/JSON/file")
myauth = GitHub.authenticate(access_token["GITHUB_AUTH"])typeof(myauth)
# GitHub.OAuth2

测试 API

让我们检查一下我们的凭证是否工作正常。我们将尝试获取 DataFrames.jl 的贡献者列表,这是使用 Julia 进行数据科学研究的必备工具。

DataFrames.jl 供稿者(图片由作者提供)

注意类型Tuple{Vector{T}, Dict}的结果意味着它们是分页的。通过提供Dict("page" => 1)作为输入参数,我们可以看到第一个Vector{T}中的所有结果,如上所示。您还可以调整每页的结果,例如:Dict("per_page" => 25, "page" => 2)将从第 2 页开始每页返回 25 个结果。

有趣的包列表

我们现在可以开始收集多个包的数据,并将它们绘制在一起进行比较。我策划了下面的列表,涵盖了不同的领域(数据分析,解析,网络,绘图,数学等。),这绝不意味着是详尽无遗的。你认为我们还能再补充些什么吗?请在评论中告诉我,我会相应地更新剧情。

本文中比较的软件包列表(图片由作者提供)

贡献者人数

让我们首先使用下面显示的函数来确定给定包的贡献者数量。

我们将对列表中的所有包都这样做,然后使用来自 VegaLite.jl@vlplot 宏绘制结果。

作者图片

似乎 DataFrames.jl 目前拥有最多的贡献者,鉴于它在几乎所有数据科学工作流中的效用,这并不奇怪。 Plots.jl 紧随其后,在本次比较中使用的所有绘图包中数量最多。

叉子的数量

使用与上面相同的逻辑,我们也可以比较分叉的数量。

作者图片

DataFrames.jl 再次领先,Plots.jl 紧随其后。

每周提交

GitHub 还提供了一个 API 来确定在过去的 52 周内提交给存储库的数量。我们需要解析 HTTP 响应对象,然后将它转换成 DataFrame,其中包用作列名。

然后,我们对所有包重复该过程,并构建一个新的数据帧,用作创建堆积条形图的输入。

请注意,为了更清楚起见,这里我们只查看上述包的一个子集。

作者图片

第一个图显示 DataFrames.jl 和 CSV.jl 在去年全年都有定期活动。这说明各自的包维护者一直在努力。向所有参与者致敬!

在第二个图中,我们注意到 Makie.jl 和 Plot.jl 在第 1–10 周发生了很多活动。在那之后,提交的数量比平时少了。

开放和关闭的问题

另一个值得关注的指标是当前打开/关闭问题的数量。这可能是包装成熟度的合理指标。毕竟,谁会想继续使用充满公开问题的代码呢?例如,如果打开和关闭问题的比率很高(> 0.7),这表明开发人员修复 bug 的速度很慢,或者相关的问题很复杂,需要时间来修复。另一方面,较低的比率(< 0.3) indicates a healthy package development pace.

Keep in mind that the API also considers pull requests as closed issues. We would like to separate that from issues reported as bugs by users.

The gathered results can be combined and visualized once again as a stacked bar plot.

Image by author

It’s very heartening to see that most of the packages in our list do not have a big backlog of open issues. They have undergone a robust development and testing cycle, thus leading to a very mature state.

Open source love

It could also be interesting to have a look at some social metrics such as the number of people that have starred or are following updates of a repository.

Image by author

微分方程. jl 在这里是明显的赢家,因为它在 Julia SciML 生态系统中非常受欢迎。在绘图引擎中,Plots.jl 和 Makie.jl 似乎并驾齐驱。看到 PyCall.jl 有这么多明星,我很惊讶。现在我想起来,这是有意义的,因为许多新的 Julia 用户可能会从 Python 转向。也可能是他们打算只在代码的性能关键部分使用 Julia。

观察者的数量也显示了类似的趋势,尽管我不认为这是开发人员的普遍习惯。

作者图片

结论

朱莉娅的生态系统正在快速发展。我很高兴看到大多数突出的包都被积极地维护着,这对开源精神来说是必不可少的。我希望你觉得这个练习很有趣。感谢您的宝贵时间!在 LinkedIn 上与我联系,或者访问我的 Web 3.0 支持的网站

参考

  1. https://docs.github.com/en/rest
  2. https://github.com/JuliaWeb/GitHub.jl

可视化选举数据:2021 年智利总统选举期间的极化和动员

原文:https://towardsdatascience.com/visualizing-electoral-data-polarization-and-mobilization-during-the-2021-chilean-presidential-528230a98232

智利上届总统选举案例研究

12 月 21 日发生了智利历史上最具争议的总统选举。两轮投票制度将所有传统政党置于相当长时间未见的政治对抗背景之下,导致传统政党丧失资格。

但本文的目的不是讨论政治。相反,它是关于调查这次选举的一个有趣的事实:根据国家选举服务局(SERVEL) 的数据,在两轮选举之间,表达选票的数量(即,非空白或无效的)大幅增加,从 7028345 张表达选票增加到第二轮的 8271893 张。增长了 17.7%,差不多 125 万张选票!

智利的弃权率通常很高。选民登记册统计了 1500 万选民,其中只有 47.5%参加了第一轮投票,55.9%参加了第二轮投票,但这被认为是该国最好的成绩之一。然而,我将只关注明示投票的增加,因为弃权背后的原因是一个完全不同的问题,我不会在这里处理。

如我所说,这些选举是高度两极化的。第二轮投票的增加有利于获胜的候选人加布里埃尔·硼酸。为了仔细研究这一现象,我分析了 SERVEL 的投票数据,这些数据为我们提供了全国和海外所有 46,888 个投票点的详细信息:每个候选人获得的票数、空白和无效票、弃权、地点、性别等。

提醒一下,在所有事情之前,我们先列出两轮的所有候选人,以及他们各自的分数。Gabriel Boric Font 在第二轮选举中获得 55.87%的选票,击败了获得 44.13%选票的 José Antonio Kast。

在第一轮投票中,José Antonio Kast 获得 27.91%的选票,Gabriel Boric 获得 25.83%的选票,位居第二。其余的候选人是弗朗西斯科·帕里西(12.80%)、塞巴斯蒂安·西谢尔(12.78%)、亚斯纳·普罗沃斯特(11.60%)、马尔科·恩里克斯·奥米纳米(7.60%)和爱德华多·阿特斯(1.47%)。

# libraries used
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.pyplot import cm
from matplotlib.ticker import PercentFormatter
import seaborn as sn# preparation of the data includes:
   # importing [first-round](https://www.servel.cl/resultados-definitivos-eleccion-presidencial-parlamentarias-y-de-consejeros-as-regionales-2021/) and [second-round](https://www.servel.cl/resultados-definitivos-elecciones-segunda-votacion-presidencia-2021/) datasets
   # joining data from polling places within the country and abroad 
   # cleaning the data
   # counting total number of *expressed* ballots per polling station
    (abstention and blank/invalid votes are not taken into account)
   # computing percentage of each candidate in each polling station
   # returning a NumPy array of all 46,888 scores (one per polling
     station), for both the 7 first-round candidates and the 2
     second-round candidates 
   # calculating the difference of expressed votes between the two
     rounds to measure the electoral mobilization in each polling 
     station. Outliers are set to a limit to narrow the
     spread of the array when plotted as a heatmap# all operations are regularly asserted throughout the script to be consistent with the totals provided by SERVEL in a separate sheet. # the detailed script is available on [GitHub](https://github.com/louisleclerc/SERVEL_Data_Analysis/blob/main/SERVEL_EDA.py).

比较全国各投票站的第一轮和第二轮投票结果。

对于七名候选人中的每一名,我根据候选人在所述投票地点获得的分数在散点图的 x 轴上显示投票地点。每个投票点根据第二轮得分沿 y 轴移动,看看这个投票点的选民对第二轮对决的反应。

*# candidate is an array of scores of the 1st-round candidate 
# candidate2 is an array of scores of the 2nd-round candidate
#* diff_votes_perc *is an array of the differences of expressed votes between the two rounds*# plot 
sns.scatterplot(
x=candidate, 
y=candidate2, 
hue=diff_votes_perc, 
palette='coolwarm', 
alpha=0.5, 
ax=ax
)# legend
ax.legend(
title=f'Gain/loss of votes between\nthe two rounds (in %)', 
title_fontsize='small', 
fontsize='small'
)

候选人的选民由最右边的投票站组成,那里第一轮投票的得分最高。这些选民在 y 轴上的位置揭示了他们在第二轮投票中的表现。换句话说,它表明第一轮候选人的选民支持两个决选候选人中的一个。

图表还显示了每个投票站的选举动员情况。根据热图逻辑,点的颜色表示第一轮和第二轮之间表达投票的数量是增加还是减少:暖红色表示动员力度很大,而冷蓝色表示参与度下降。

*# check distribution of the difference of expressed ballots array
plt.hist(diff_votes_perc)
plt.show()**# narrow range of the array to avoid outliers* 
diff_votes_perc = np.where(
diff_votes_perc < -30, -30, diff_votes_perc
)
diff_votes_perc = np.where(
diff_votes_perc > 55, 55, diff_votes_perc
)

这样,图表不仅能告诉我们第一轮候选人的选民是否团结一致,还能告诉我们支持者是否热情。

让我们看一个例子来澄清这一点,例如,比较传统右翼候选人 sebastán Sichel 在第一轮和极右翼候选人 José Antonio Kast 在第二轮中获得的分数的图表。

作者图片

在图的右上方有一堆蓝色的点,从中我们可以得出两个结论:

  • 首先,关于何塞·安东尼奥·卡斯特在第二轮中是否获得支持。Sichel 登记其最佳结果的投票站(x 轴右侧)在第二轮投票中投票支持 Kast 轴顶部)。换句话说,西谢尔的选民在第二轮选举中支持了卡斯特。
  • 第二,关于这种支持的热情。蓝色的流行表明选举复员,因为在两轮选举之间丢失了明示选票。换句话说,西谢尔的选民向卡斯特提供了“复员支持”。

将决选候选人与他们自己进行比较

当我们比较同一候选人在第一轮和第二轮之间的分数变化时,这变得更加有趣。因此,我们只能与进入第二轮的候选人进行比较。

看候选人的选民是否在两轮选举中都支持他有什么意义?

这可能看起来违反直觉,因为我们可以合理地预期在第一轮投票中大量投票给候选人的投票站会在第二轮投票中投票给同一个候选人。

作为示范,让我们先来看看那些大量投票给 Kast 的投票站,他是落败的决选候选人。

作者图片

它看起来像一个密集的蜂群,顶部有一个肿胀。

散点图的线性形状表明他的选民是稳定的:在第一轮投票中投票给他的地方越多,在第二轮投票中大量投票给他的可能性就越大。

也有例外。一些地方在第二轮强烈投票给卡斯特,得分高达 100%,尽管他的首轮得分很低。但是我们谈论的只是总数为 46,888 个投票站中很少的几个投票站,而且通常是以被遣散的选民为代价的,正如蓝色圆点所示。

从这张图表中得出的最重要的结论并不那么引人注目。肿胀的大小没有那么大,尤其是左边。但左上/中上部分可能恰恰是胜利所在。在选举中,把第一轮没有投票给你的选民聚集起来是至关重要的。那些选民应该正好出现在红色所画的数字之内。

作者图片

选举爆发到胜利

为了更好地说明选举爆发的概念,现在让我们看看获胜候选人的得分。

作者图片

肿胀更明显,布满小点。它由两部分组成:左边的蓝色部分,在第二轮投票中强烈支持硼酸,但被遣散;另一个更大的红色部分,强烈动员支持硼酸。

让我们显示与 Boric 在每一轮中获得的全国平均分数完全相同的数字。

*# get max values of the data to get limit coordinates* X_max = float(max(candidate))
Y_max = float(max(candidate2))

*# plot, same as before* sns.scatterplot(
x=candidate, 
y=candidate2, 
hue=diff_votes_perc, 
palette='coolwarm', 
alpha=0.5, 
ax=ax
)*# compute national averages of candidates* cand2_mean = float(np.mean(candidate2))
cand_mean = float(np.mean(candidate))# compute number of polling places
nb_pp = int(len(SERVEL_data) / 7)*# plot national average of 2nd-round candidate* X_plot = np.linspace(0, X_max, nb_pp)
Y_plot = np.linspace(cand2_mean, cand2_mean, nb_pp)
ax.plot(
X_plot, 
Y_plot, 
color='black', 
linestyle='-.', 
label=f'{candidate2name}\n2nd round: {round(cand2_mean,1)}%'
)*# plot national average of 1st-round candidate* X_plot2 = np.linspace(cand_mean, cand_mean, nb_pp)
Y_plot2 = np.linspace(0, Y_max, nb_pp)
ax.plot(
X_plot2, 
Y_plot2, 
color='black', 
linestyle=':', 
label=f'{candidate1name}\n1st round: {round(cand_mean, 1)}%'
)

作者图片

右上角的四分之一收集了所有投票点,这些投票点在两轮投票中投票给 Boric 的人数都超过了全国平均水平。与卡斯特的图相比,我们可以看到它不是线形的。相反,扩大到顶部的红色爆发突出了有利于硼酸的动员。

左上方的四分之一也很有见地。它收集了所有在第一轮投票中没有给 Boric 投多少票的地方,少于全国平均水平。然而,在第二轮投票中,这些投票站明显支持他。他们动员了更多,如红色所示。

图表上部有很多红色的事实强调了 Boric 的当选是由于一个关键的选举动员,远远超出了他最初的选民范围。这一结论与他虽然在第一轮中名列第二,却以微弱优势赢得选举的事实是一致的。

相反,请记住,卡斯特的膨胀几乎是空的,充满了复员的投票站,这意味着他未能吸引选民以外的选民。

把所有的放在一起

这是第一轮所有七名候选人的完整数据。对于其中的每一个,都有相同数据的三个视图:

  • 表示每个投票站的投票数,第一轮和第二轮候选人的全国平均分。没有基于颜色的信息来关注群体的形状和平均值是如何定位的。
  • 两轮选举动员热图。这就是我们到目前为止看到的视觉效果。
  • 按地区投票。另一种我遇到过最奇怪挑战的信息显示:根据两种不同类型的编号对区域进行排序(一些根据地理位置,另一些根据创建日期)。

作者提供的图片

下面是生成这些图的脚本。首先,我们定义候选对象并设置图形的一般特征。由于只显示了三个支线剧情,我们可以在右上角放置一个定制的图例,而不是第四个。

for i, candidate in enumerate(
[Boric, Kast, Provoste, Sichel, Artés, Ominami, Parisi]
):
    fig, axs = plt.subplots(2, 2, figsize=[15,10])

    *# extract name of the 1st round candidate* candidate1name = names[i].title()

    *# define candidate 2nd round to compare to* if i == 1 or i == 3:
        candidate2name= 'José Antonio Kast Rist'
        candidate2 = Kast2
    else:
        candidate2name= 'Gabriel Boric Font'
        candidate2 = Boric2

    *# format x and y axis in percentages* for a, b in [(0,0), (0,1), (1,0), (1,1)]:
        axs[a][b].xaxis.set_major_formatter(PercentFormatter())
        axs[a][b].yaxis.set_major_formatter(PercentFormatter())

    *# put the title in the second plot* axs[0][1].annotate(
text=f"2nd round behavior of\n{candidate1name}'s electorate",
xy=[0.5,0.8], 
horizontalalignment='center', 
fontsize=20, 
fontweight='bold'
) *# add general description
*    axs[0][1].annotate(
text='Comparison of the results obtained at each round of the\n2021 Chilean presidential elections (by polling station)',
xy=[0.5,0.6],
horizontalalignment='center',
fontsize=12,
fontstyle='italic'
) *# annote customized legend*
    axs[0][1].annotate('Legend:\n'
              '1 - Expressed votes per polling station (in %)\n'
              '2 - Electoral mobilization between the two rounds\n'
              '3 - Vote per region',
xy=[0.05,0.05], 
horizontalalignment='left', 
fontsize=12, 
fontweight='light',
backgroundcolor='white', 
bbox=dict(edgecolor='black', facecolor='white',boxstyle='round')
) *# fetch limit coordinates of each plot* 
    X_max = float(max(candidate))
    Y_max = float(max(candidate2)) *# put numbered references of the legend in the upper-right corner of each subplot*    axs[0][0].annotate(
text='1', 
xy=[X_max,90], 
color='darkred', 
fontsize=20, 
fontweight='black'
)
    axs[1][0].annotate(
text='2', 
xy=[X_max,90], 
color='darkred', 
fontsize=20, 
fontweight='black'
)
    axs[1][1].annotate(
text='3', 
xy=[X_max,90], 
color='darkred', 
fontsize=20, 
fontweight='black'
) # hide axis
    axs[0][1].axis('off')

    *# set labels of the general figure* fig.supylabel(
f'{candidate2name} - 2nd round results', 
fontsize=16, 
ha='center', 
va='center'
)
    fig.supxlabel(
f'{candidate1name} - 1st round results', 
fontsize=16, 
ha='center', 
va='center'
)

我们现在用全国平均值生成散点图。提醒一下,我们还是在同一个“for 循环”里。

 *# plot comparison of expressed votes in the first subplot* sns.scatterplot(
x=candidate, 
y=candidate2, 
color=colors[i], 
alpha=0.3, 
ax=axs[0][0]
)*# define variables to plot national averages of candidates* cand2_mean = float(np.nanmean(candidate2))
    cand_mean = float(np.nanmean(candidate))
    nb_pp = int(len(SERVEL_data) / 7) *# plot national average of 2nd-round candidate* X_plot = np.linspace(0, X_max, nb_pp)
    Y_plot = np.linspace(cand2_mean, cand2_mean, nb_pp) axs[0][0].plot(
X_plot, 
Y_plot, 
color='black', 
linestyle='-.', 
label=f'{candidate2name}\n2nd round: {round(cand2_mean,1)}%'
) *# plot national average of first-round candidate* X_plot2 = np.linspace(cand_mean, cand_mean, nb_pp)
    Y_plot2 = np.linspace(0, Y_max, nb_pp) axs[0][0].plot(
X_plot2, 
Y_plot2, 
color='black', 
linestyle=':', 
label=f' {candidate1name}\n1st round: {round(cand_mean, 1)}%'
)
    axs[0][0].legend(
fontsize='small', 
title='National average', 
title_fontsize='small'
)

然后是我们已经看到的选举动员热图。

*# plot electoral mobilization in the third subplot* sns.scatterplot(
x=candidate, 
y=candidate2, 
hue=diff_votes_perc, 
palette='coolwarm', 
alpha=0.5, 
ax=axs[1][0]
) *# legend with total number of votes in both rounds, as well as increase of participation in %* axs[1][0].legend(
title=f'Gain/loss of votes between\nthe two rounds (in %)', title_fontsize='small', 
fontsize='small'
)

智利是少数几个地区可以是数字变量而不是分类变量的国家之一。

最后,一个奖励区域图。对于那些不熟悉智利地理的人来说,它是世界上从北到南最长的国家,长达 4270 公里。

它从北方世界上最干燥的沙漠到南方的南极,汇集了各种气候。但是它很窄,夹在大海和雄伟的安第斯山脉之间。所以平均来说,它只有 177 千米大。

区域堆叠在另一个之上,并且在地图上没有出现精确的东/西分层。我们可以利用这种特殊的地理位置,根据点在南北轴上的位置,将颜色的深浅归属于这些点。毕竟,智利在某种程度上像一个轴心!

来自维基共享

换句话说,我们可以从数字上对智利的地区进行排序。能做到这一点的国家并不多!在大多数国家,区域布局的彩色图将是分类图,不太可能提供重要的视觉洞察。

因此,我们可以将智利的地理数据显示为从北到南的阴影。这种散点图让我们一眼就能看出投票站的大概位置。这为一些候选人带来了一些有趣的见解,比如帕里西和普罗沃斯特,他们的选民位于智利北部。

作者图片

作者图片

所以,回到剧本上。我们希望区域从北到南排序。好消息是,它们被编号了,显然是从北到南。但是如果你看看上面智利地区的地图,你会发现并不是所有的编号都有意义。

智利在几个场合创造了新的区域。这是一个棘手的问题!最初,给第一批地区编号的标准是它们的地理位置。但是几个新的区域同时产生了,它们的排名来自于它们产生的顺序,而不是它们的地理位置。

我们可以想象从北到南正确排序所有区域的许多方法,但也可以通过索引、压缩和 NumPy 手动完成。

 *# plot votes according to region in the last subplot
    # a reordering the position to the north is necessary to create a readable heatmap* *# instantiate a list with the result of* regions = np.unique(location_array) *# zip the region list with a list of their respective position starting from the north* north_to_south = [3, 1, 4, 15, 5, 12, 14, 13, 16, 2, 6, 10, 11, 8, 9, 17, 7]
    region_position = zip(regions, north_to_south) *# create an array of the regional position of each polling place* position_array = np.empty(len(location_array))
    for region, position in region_position:
        position_array[location_array == region] = position *# stack all arrays of interest into a single one* ordered_array = np.column_stack(
[candidate, candidate2, position_array]
) *# sort array according to the regional position*    sorted_array = ordered_array[
np.argsort(ordered_array[:,2])
] *# create plot* sns.scatterplot(
x=sorted_array[:,0], 
y=sorted_array[:,1], 
hue=sorted_array[:,2].astype('<U44'), 
palette='Spectral', 
alpha=0.4, 
ax=axs[1][1]
)
    *# readjust labels from north to south* location_labels = [
'ARICA', 
'TARAPACA', 
'ANTOFAGASTA', 
'ATACAMA', 
'COQUIMBO', 
'VALPARAISO', 
'METROPOLITANA',
"O'HIGGINS", 
'MAULE', 
'ÑUBLE', 
'BIOBIO', 
'ARAUCANIA', 
'LOS RIOS', 
'LOS LAGOS', 
'AYSEN',
'MAGALLANES', 
'EXTRANJERO'
]
    axs[1][1].legend(
labels = location_labels, 
ncol=4, 
fontsize='xx-small'
)

走得更远

这份分析没有涵盖很多内容,比如弃权背后的原因。但是这一数据可以用来提供另一项关于基于性别的投票的研究,因为除了国外的投票站之外,性别数据是可以获得的。

将性别数据与动员图进行比较可能会很有趣,因为据说 Boric 赢得了第二轮投票,这要归功于年轻女性选民的大力动员。

尽管选举发生在去年 12 月,案例研究来得有点晚,但在智利另一场关键选举即将到来的情况下分析这些数据仍然很有趣:9 月 4 日的宪法公投。

此处可用https://medium.com/@louisleclerc/una-visualización-de-datos-electorales-polarización-y-movilización-durante-las-elecciones-5315435af9b7翻译成西班牙语。

上一届法国总统选举后的一个形象化的 《世界报》 启发了这个案例研究。

可视化全球变暖

原文:https://towardsdatascience.com/visualizing-global-warming-832111f9891f

走向温度变化的绝对测量

这个星球正在升温。多少钱?有多快?在媒体上,你会读到这个星球平均比一个世纪前高 1 摄氏度。你也会读到陆地变暖的速度比海洋快。但这些只是平均值;他们忽略了细节。

虽然互联网上有许多有趣的可视化工具,但我错过了一个真正互动的工具。我想点击地球上的任何一个地方,亲眼看看它的温度是如何随着时间的推移而变化的。

因此,我建立了自己的网站——加热星球——试图通过数据分析和可视化更好地了解我们加热星球的动态。

免责声明:我是不是气象学家/气候科学家。

heatingplanet.org截图——来源:图片由作者生成

你可以用一个谷歌 Colab 笔记本跟随。

动机

我总是对任意选择基线来计算平均温度异常感到有点恼火。当然,我知道图本身不会改变,不同的基线只会移动垂直轴,因为零点取决于用作基线的时间跨度。

尽管如此,并不是每个人都能轻易看穿。我一直认为这是对试图了解更多全球变暖知识的人的一种伤害,因为它给读者带来了负担。

更糟糕的是,NOAA 的网站甚至让经验丰富的专业人士感到非常困惑,当它提到,字面上,四个不同的跨度来引用相同的数据!

在与我们在此使用的数据 GHCN 网格产品相对应的页面中,我们看到:

此外,鉴于数据集的本质,NOAA 在注释部分建议:

" 开发这些网格数据集是为了尽可能产生最准确的时间序列。然而,这要求月份和网格框在时间上被独立处理。使用这些数据集最适合于分析一个特定的网格箱或一组网格箱几年内的温度变化。

这是有道理的,因为不同的地方表现出非常不同的天气和温度模式,而且更糟糕的是,随着时间的推移,它们受到全球变暖的影响也不同。

比较和汇总它们的唯一合理的方法是试图找到它们自己的基线

基线代表在被全球变暖影响修改/扰乱之前给定位置的典型温度模式。**

不管用于计算温度异常的时间跨度是多少,都应该可以找出某一特定时间段内给定位置的典型温度。

一旦建立了该温度水平,我们可以寻找对应于温度状态变化的变化点

与基线(第一次观察到的状态)相比,变化状态下的(相对)温度为我们提供了该区域绝对变化的指示。

这就是我们的目标!

免责声明:我不是*气象学家或气候科学家!作为一名数据科学家和开发人员,我正在使用我所掌握的工具,试图更好地理解全球变暖的动态,同时解决困扰我多年的一个棘手问题!*

出于这个原因,我免费提供所有代码,如果你愿意,我邀请你使用不同的参数和假设亲自尝试一下。

剧透预警:我尝试了很多不同的参数进行变点检测和插值算法,结果几乎纹丝不动。

数据

我们使用的数据来自 NOAA 的国家环境信息中心(NCEI)。

数据使用 GHCN 版本 5.0.0 的陆地和海洋温度异常网格文件,显示了相对于 1971-2000 年平均温度的温度异常,以摄氏度为单位,用于每个 5x 5 网格框,可在此直接访问

download()函数从 NOAA 检索数据,noaa_temperatures()返回一个 3D Numpy 数组,其中包含每个网格框(36 x 72 个框)的年平均值。可以将温度异常的基线从默认的 1971-2000 年调整到任何其他年份(它使用了几个函数,surface_polygons()surface_area()surface_stat(),这些将在下一节“网格和投影”中解释),坚持住!)

noaa_temperatures()函数故意使用np.mean(而不是np.nanmean)!如果给定年份中任何一个月的数据缺失,则整年都被视为缺失,否则平均值会有偏差。

*fname = download()
griddedy = noaa_temperatures(fname, yearly=True)
griddedy.shape*
*(142, 36, 72)*

该数据集涵盖了从 1880 年到 2021 年的 142 年时间。

如果您看一下noaa_temperatures()函数,您可能会注意到基线调整使用网格框的表面积来计算温度的加权平均值。

这是重要的一步,也是选择可视化投影的基本原理,我们将在下一节中看到。

网格和投影

默认投影是 Mollweide 投影,因为它是等面积投影,也就是说,它保留了网格框的相对大小。赤道线上的 5 乘 5 格网框大约比两极之一的 5 乘 5 格网框大 23 倍,在计算全球异常平均值时应考虑到这一点。

莫尔韦德投影—来源:图片由作者生成

以下函数计算每个 5x 5 度格网框中包含的地球表面的百分比:

*polygons = surface_polygons(grid_size=5)
surface_perc = surface_area(polygons, perc=True)*

在每个网格框中心的纬度上使用简单的余弦函数可以获得相同的结果,NOAA 称之为余弦加权:

*lat = np.linspace(-87.5, 87.5, 36)
cosines = np.cos(lat/180*np.pi)
cosines = cosines/(72*cosines.sum())*

网格框的表面积占全球纬度面积的百分比——来源:图片由作者生成

温度异常

现在我们可以计算平均全球异常加权的网格盒的表面积,不计算丢失的数据。

这些是相对于 1971-2000 年平均值的全球平均异常值:

*avg_temp = surface_stat(griddedy, surface_perc)
avg_temp*
*array([-0.49040517, -0.42640984, -0.4501344 , -0.54361733, -0.65312965, 
       -0.55727405, -0.52217539, -0.60998217, -0.52090297, -0.41504064,
       -0.63962642, -0.60986579, -0.65473334, -0.69056367, -0.65487515,
       -0.62069087, -0.44596375, -0.42243461, -0.62026229, -0.52627162,
       -0.39377437, -0.47255557, -0.63087155, -0.73131404, -0.77620984,
       -0.65267418, -0.55622029, -0.73383994, -0.76423131, -0.73207139,
       -0.70305171, -0.76428098, -0.66780868, -0.64435368, -0.47254251, 
       -0.41023309, -0.64005227, -0.71519683, -0.65357198, -0.57490523,
       -0.5554808 , -0.45848703, -0.5467363 , -0.56028976, -0.54642186,
       -0.49404915, -0.37506068, -0.47987741, -0.49419296, -0.63032366,
       -0.40723178, -0.36059279, -0.43182238, -0.55395911, -0.40894764,
       -0.45732789, -0.4206011 , -0.31420012, -0.31789399, -0.30104638,
       -0.17343342, -0.04507401, -0.18998909, -0.20513829, -0.03826748,
       -0.1500386 , -0.29561027, -0.34690677, -0.35984154, -0.36330008,
       -0.45330106, -0.30084738, -0.26084233, -0.16263568, -0.40363231,
       -0.43880239, -0.50342084, -0.23517682, -0.17391467, -0.21231317,
       -0.25260908, -0.20197015, -0.19219163, -0.17505876, -0.4492755 ,
       -0.37807454, -0.31324477, -0.31261297, -0.34156166, -0.20498813,
       -0.25041461, -0.38749686, -0.27546437, -0.11170245, -0.37279272,
       -0.30405625, -0.39141602, -0.10668805, -0.19571924, -0.08526681,
       -0.04434497,  0.01278888, -0.11869109,  0.04697251, -0.14785713,
       -0.15757651, -0.07080541,  0.07593977,  0.08581962, -0.01262859,
        0.14421966,  0.0828533 , -0.07326519, -0.02707226,  0.04222761,
        0.16466974,  0.01674365,  0.21072811,  0.35305309,  0.14390712,
        0.12960248,  0.27025706,  0.31848434,  0.33468245,  0.27861425,
        0.36319536,  0.32961664,  0.312321  ,  0.23507969,  0.33654589,
        0.41432372,  0.26552093,  0.32406343,  0.3662596 ,  0.4349947 ,
        0.62627734,  0.6920348 ,  0.59963278,  0.51818839,  0.63898567,
        0.67217742,  0.53352703])*

注意:如果你计算从 1971 年到 2000 年(avg_temp[91:120].mean())的平均值,它不会像人们预期的那样精确地得出零度,而是 -0.0518 度。不幸的是,我想不出为什么 NOAA 的数据会出现这种情况。

*avg_temp[91:120].mean()*
*-0.051824857209641896*

除了这一微小的差异之外,用来计算全球异常的平均窗口的选择是任意的。正如在动机中提到的,这是我的烦恼,因为如果基线不断变化,就很难提出观点。

如下图所示,例如,从 1971–2000 年到 1981–2010 年的变化仅改变了 y 轴:整个图向下移动了大约 0.18 度,但是图中两点之间的任何差异保持不变

所以,用哪个基线不重要,2021 年和 1880 年全球气温差是一样的: 1.02 度

尽管如此,对温度随时间的变化进行绝对测量还是很有帮助的。这就是我们在这里努力实现的目标。

使用不同基线的温度异常—来源:图片由作者生成

然而,平均数并不能说明全部情况。更有趣的是观察记录在每个网格盒上的温度异常。

全球气温异常——来源:图片由作者生成

上面的图不是交互式的,但是如果我们观察单独的网格框,我们会发现它们中的许多都有许多缺失的数据点。这完全在意料之中,因为数据不是在 19 世纪末或 20 世纪初的偏远地区收集的。

时间序列

让我们来看看南极洲附近的一个网格盒子。它的测量始于 20 世纪 50 年代末,有几年遗漏了。

单个网格单元的温度异常——来源:图片由作者生成

缺失数据插补

我们需要采取的第一步是估算一些缺失数据。我们将使用一些简单的线性插值,但仅在数据中的间隙不太长的情况下。

这是对应于上图的 Numpy 数组:

*gy, gx = 33, 69
signal = griddedy[:, gy, gx]
signal*
*array([nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan,
       nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan,
       nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, 
       nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan,
       nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan,
       nan, nan, nan, nan, nan, nan, nan, 
      -0.3141667 , -1.4891667 , -1.1691667 , -1.17      ,  0.10916665,
      -1.9425001 , -0.9183333 , -0.52      , -0.4816667 ,  0.5216667 ,
       0.40083337, -1.09      , nan        ,  1.0158335 ,  0.6791666 ,
       1.6383333 ,  0.32500005, -0.05333332, nan        , -1.4816666 ,
      -1.0191666 , nan        , -1.1666666 ,  0.5425    , -0.16333331,
      -1.6008333 ,  0.13083331,  1.29      , -0.83666664, -0.3374999 ,
       0.83083344, nan        , nan        , -0.44333336,  0.6416666 ,
      -0.09333333, -0.715     , -0.28833324, -0.6225    ,  0.97749996,
      -0.12583338, -0.16166663,  0.34833336,  0.21833332, -0.405     ,
      -0.26833335,  0.8408334 , -1.5741667 , -0.065     , -0.26250002,
       1.5941668 ,  0.81333333,  0.40083334,  0.07666666,  2.3058333 ,
       1.5224999 ,  0.71333337,  1.3391666 , -1.3108333 , -1.1324999 ,
       0.37750006,  0.40166667,  1.5166665 ,  0.39000002,  0.23416667], 
dtype=float32)*

除了最初的 77 年,没有任何测量数据,只有五个测量数据缺失:其中三个是单一年份,另外两个是连续年份。

显然,我们对最初的 77 年无能为力,但我们可以用线性插值来填补其他五个缺失点。下面的函数bounds()返回 Numpy 数组的第一个和最后一个索引,该数组包含可用作插补基础的最长数据点序列。

让我们创建三个虚拟数组来说明上面的函数是如何工作的。

*dummy1 = np.ones(15)
dummy1[2:5] = np.nan
dummy1, bounds(dummy1)*
*(array([ 1.,  1., nan, nan, nan,  1.,  1.,  1.,
         1.,  1.,  1.,  1.,  1.,  1.,  1.]), (0, 15))*

这是一个简单的例子:只有三个连续数据点的间隙(因此低于默认的阈值 5),所以我们可以使用整个序列(start=0,end=15)并估算这三个值。

*dummy2 = np.ones(15)
dummy2[2:8] = np.nan
dummy2, bounds(dummy2)*
*(array([ 1.,  1., nan, nan, nan, nan, nan, nan,  
         1.,  1.,  1.,  1.,  1.,  1.,  1.]), (8, 15))*

第二个序列有六个连续的缺失点,所以它们不会被估算,我们将只考虑从第 9 个数据点开始的序列(索引=8)。

*dummy3 = np.ones(15)
dummy3[2:5] = np.nan
dummy3[8:14] = np.nan
dummy3, bounds(dummy3)*
*(array([ 1.,  1., nan, nan, nan,  1.,  1.,  1., nan,
         nan, nan, nan, nan, nan,  1.]), (0, 8))*

第三个例子有点奇怪,因为在它的结尾有六个连续的缺失点。因此,我们可以使用的最长序列实际上在第 9 个数据点结束(索引=8)。这是一个相当罕见的情况,因为大多数网格盒只有在早些年丢失的数据点。

在我们的真实例子中,我们可以使用从 1957 年到 2021 年的序列:

*ini, end = bounds(signal, max_contiguous_na=5)
ini, end, signal[ini:end]*
*(77, 142, 
array([-0.3141667 , -1.4891667 , -1.1691667 , -1.17      ,  0.10916665, 
       -1.9425001 , -0.9183333 , -0.52      , -0.4816667 ,  0.5216667 ,
        0.40083337, -1.09      ,         nan,  1.0158335 ,  0.6791666 ,
        1.6383333 ,  0.32500005, -0.05333332,         nan, -1.4816666 ,
       -1.0191666 ,         nan, -1.1666666 ,  0.5425    , -0.16333331,
       -1.6008333 ,  0.13083331,  1.29      , -0.83666664, -0.3374999 ,
        0.83083344,         nan,         nan, -0.44333336,  0.6416666 ,
       -0.09333333, -0.715     , -0.28833324, -0.6225    ,  0.97749996,
       -0.12583338, -0.16166663,  0.34833336,  0.21833332, -0.405     ,
       -0.26833335,  0.8408334 , -1.5741667 , -0.065     , -0.26250002,
        1.5941668 ,  0.81333333,  0.40083334,  0.07666666,  2.3058333 ,
        1.5224999 ,  0.71333337,  1.3391666 , -1.3108333 , -1.1324999 ,
        0.37750006,  0.40166667,  1.5166665 ,  0.39000002,  0.23416667],
dtype=float32))*

下面的函数fill_values()使用 Scipy 的[interp1d()](https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.interp1d.html)来估算由bounds()函数识别的最长序列中的缺失值:

我们可以将fill_values()函数应用到我们的实际例子中:

*fill_values(signal[ini:end])*
*array([-0.31416669, -1.48916674, -1.16916668, -1.16999996,  0.10916665, 
       -1.94250011, -0.91833329, -0.51999998, -0.48166671,  0.52166671,
        0.40083337, -1.09000003, -0.03708327,  1.0158335 ,  0.67916662,
        1.63833332,  0.32500005, -0.05333332, -0.76749994, -1.48166656,
       -1.01916659, -1.09291661, -1.16666663,  0.54250002, -0.16333331,
       -1.6008333 ,  0.13083331,  1.28999996, -0.83666664, -0.33749989,
        0.83083344,  0.40611117, -0.01861109, -0.44333336,  0.64166659,
       -0.09333333, -0.71499997, -0.28833324, -0.6225    ,  0.97749996,
       -0.12583338, -0.16166663,  0.34833336,  0.21833332, -0.405     ,
       -0.26833335,  0.84083343, -1.57416666, -0.065     , -0.26250002,
        1.59416676,  0.81333333,  0.40083334,  0.07666666,  2.30583334,
        1.52249992,  0.71333337,  1.33916664, -1.31083333, -1.13249993,
        0.37750006,  0.40166667,  1.51666653,  0.39000002,  0.23416667])*

此外,我们还可以创建另一个函数fill_series(),将fill_values()函数应用于每个网格框的时间序列。

*filled = fill_series(griddedy)
filled.shape*
*(142, 36, 72)*

变量filled具有与网格数据相同的形状,它包含一些缺失点的估算值。**

让我们想象一下我们的真实例子,靠近南极洲的网格盒:

单个网格单元的温度异常——来源:图片由作者生成

太好了,这个时间序列中再也没有遗漏点了。

现在,我们可以用它来执行下一步的变化点检测(CPD)

变点检测

我们正在使用破裂包来检测对应于每个网格盒的一系列温度异常中的变化点。

change_points()函数使用线性惩罚分割(毛皮)检测变化点,以l1为模型,惩罚为 3。

对于我们的真实示例,我们为change_points()函数提供填充的时间序列,结果如下:

*change_points(filled[:, gy, gx])*
*(array([ 77, 127, 142]), array([-0.23556663,  0.6161668 ], dtype=float32))*

它确定了一个发生在 2007 年的变化点(指数 127)。从该系列开始的 1957 年(指数 77),直到 2007 年,平均气温异常为-0.236 度。从 2008 年开始,新的天气状况平均气温异常为 0.616 度。

我们可以使用这些信息来构建代表这些制度的时间序列,这就是下面的函数change_evolution()所做的:

*change_evolution(change_points(filled[:, gy, gx]), offset=False)*
*array([nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, 
       nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, 
       nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan,
       nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, 
       nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, 
       nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan,
       -0.23556663, -0.23556663, -0.23556663, -0.23556663, -0.23556663,
       -0.23556663, -0.23556663, -0.23556663, -0.23556663, -0.23556663,
       -0.23556663, -0.23556663, -0.23556663, -0.23556663, -0.23556663,
       -0.23556663, -0.23556663, -0.23556663, -0.23556663, -0.23556663,
       -0.23556663, -0.23556663, -0.23556663, -0.23556663, -0.23556663,
       -0.23556663, -0.23556663, -0.23556663, -0.23556663, -0.23556663,
       -0.23556663, -0.23556663, -0.23556663, -0.23556663, -0.23556663,
       -0.23556663, -0.23556663, -0.23556663, -0.23556663, -0.23556663,
       -0.23556663, -0.23556663, -0.23556663, -0.23556663, -0.23556663,
       -0.23556663, -0.23556663, -0.23556663, -0.23556663, -0.23556663,
        0.61616677,  0.61616677,  0.61616677,  0.61616677,  0.61616677,
        0.61616677,  0.61616677,  0.61616677,  0.61616677,  0.61616677,
        0.61616677,  0.61616677,  0.61616677,  0.61616677,  0.61616677])*

我们也可以将offset参数设置为True,以将第一种状态视为基线,从而计算相对于其平均值的温度:

*change_evolution(change_points(filled[:, gy, gx]), offset=True)*
*array([nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan,
       nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan,
       nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, 
       nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan,
       nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan,
       nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan, nan,
       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\. , 0\. , 0\. , 0\. ,
       0\. , 0\. , 0\. , 0\. , 0\. , 0\. , 0\. , 0\. , 0\. , 0\. , 0\. , 0.85173339,
       0.85173339, 0.85173339, 0.85173339, 0.85173339, 0.85173339,
       0.85173339, 0.85173339, 0.85173339, 0.85173339, 0.85173339,
       0.85173339, 0.85173339, 0.85173339, 0.85173339])*

在上面的例子中,第二状态表现出比第一基准状态高 0.85 度的温度。我们将使用这种方法来计算相对于第一次观察到的状态(在上面的例子中,状态在 1957 年和 2007 年之间)的绝对温度变化

不过,估计的绝对变化的质量受到早年数据可用性的限制。在我们的例子中,在 1957 年之前,可能还有另一个温度可能更低的政权。

如果是这样的话,这意味着实际的变化可能更大。相反,如果那个未被观测到的区域温度更高,实际的变化会比我们估计的要低。鉴于后一种情况在实践中很少观察到,可以相当安全地假设温度绝对变化的总体估计实际上是一个下限。

接下来,我们可以创建另外两个函数,series_changes()series_evolution(),分别将change_points()change_evolution()函数应用于每个网格框的时间序列。

*model = 'l1'
pen = 3
changes = series_changes(filled, model=model, pen=pen)
regimes = series_evolution(griddedy, changes, keep_missing=True)
regimes.shape*
*(142, 36, 72)*

正如所料,regimes变量与filledgriddedy具有相同的形状,代表每个网格框的一个时间序列。

让我们想象一下南极洲附近网格盒的状态:

单个网格单元的温度异常—来源:图片由作者生成

温度的绝对变化

由于我们有估计状态的时间序列,我们可以用它们来计算全球温度绝对变化的平均值,该平均值由每个网格盒的表面积加权。

*avg_temp_regimes = surface_stat(regimes, surface_perc)
avg_temp_regimes*
*array([0\.        , 0\.        , 0\.        , 0\.        , 0\.        ,
       0\.        , 0\.        , 0\.        , 0\.        , 0\.        ,
       0.00576335, 0.00580329, 0.00707719, 0.00694469, 0.00672578,
       0.01834079, 0.02212073, 0.02420542, 0.02469429, 0.02571803,
       0.02925433, 0.02838276, 0.02776722, 0.02960892, 0.02895156,
       0.02864731, 0.02761619, 0.02568091, 0.02537386, 0.02314328,
       0.02520285, 0.02881464, 0.03121997, 0.03067486, 0.03366899,
       0.04119744, 0.04603328, 0.0462651 , 0.0511927 , 0.05288778,
       0.06770082, 0.06838848, 0.07115542, 0.07475784, 0.07688982,
       0.09671996, 0.10174075, 0.10285812, 0.10276389, 0.10513163,
       0.13238765, 0.13284342, 0.13526591, 0.13752306, 0.14154711,
       0.16200084, 0.16699853, 0.17127526, 0.18274286, 0.19690109,
       0.22714448, 0.22896972, 0.23010422, 0.24669774, 0.23283419,
       0.24106541, 0.24799387, 0.21955436, 0.21295449, 0.20745591,
       0.20640894, 0.20799534, 0.20507451, 0.20498372, 0.20576691,
       0.20651061, 0.20562891, 0.20712536, 0.20695692, 0.20878697,
       0.21100393, 0.21289192, 0.21458437, 0.21573853, 0.21344726,
       0.21931271, 0.22112944, 0.22111773, 0.2225343 , 0.22586358,
       0.24031732, 0.24258621, 0.25459703, 0.2577666 , 0.2645934 ,
       0.28262642, 0.30619518, 0.33423281, 0.34660399, 0.34488311,
       0.39779741, 0.40840923, 0.4162796 , 0.42082517, 0.42332179,
       0.4440393 , 0.45619066, 0.46173487, 0.46749258, 0.47442235,
       0.49173493, 0.49636275, 0.50507246, 0.51133605, 0.52102668,
       0.61102596, 0.63321358, 0.65210243, 0.67432724, 0.68344587,
       0.755153  , 0.75700098, 0.77379877, 0.78516061, 0.78950154,
       0.79942277, 0.80310933, 0.8069333 , 0.8086264 , 0.81033482,
       0.8229554 , 0.8297085 , 0.8308874 , 0.83842405, 0.84456048,
       0.86581081, 0.8699356 , 0.87017374, 0.87180792, 0.8736366 ,
       0.87485245, 0.8718763 ])*

只是在这个系列的第 11 年(1890 年),全球某个地方发生了政权更迭。让我们在图中可视化数据:

估计的全球绝对温度变化——来源:图片由作者生成

90 年代的气温变化尤为惊人!

最后,让我们来看看 2021 年全球气温的绝对变化:

全球气温的绝对变化——来源:图片由作者生成

自 1880 年以来,北半球的许多地区出现了比它们相应的(观测到的)第一个状态高出 3 摄氏度或更多的状态。

要探索随时间的演变,只需在加热星球主页面的互动剧情中选择**Absolute**

在探索互动情节时,注意一些区域如何比其他区域更早升温,以及更温暖的区域如何随着时间的推移而扩散。

你注意到陆地变得比海洋更温暖了吗?让我们深入了解一下!

越过陆地和海洋

首先,我们需要为网格单元定义一个掩码,这是一个 Numpy 数组,表示那些超过 50%面积为陆地的网格单元(1 表示陆地,0 表示海洋)。我已经准备好了这个数组,并将其保存在land.npy文件中。

*with open('land.npy', 'rb') as f:
    land_mask = np.load(f)*

反常

现在我们可以制作异常的深层拷贝(griddedy)并使用land_mask变量来掩盖陆地或海洋。这允许我们再次使用surface_stat函数,分别计算陆地和海洋的平均全球异常。让我们看看 2021 年的当前数字是什么:

*from copy import deepcopy

griddedy_land = deepcopy(griddedy)
griddedy_sea = deepcopy(griddedy)
griddedy_land[:, land_mask==0] = np.nan
griddedy_sea[:, land_mask==1] = np.nan

avg_temp_land = surface_stat(griddedy_land, surface_perc)
avg_temp_sea = surface_stat(griddedy_sea, surface_perc)
avg_temp_land[-1], avg_temp_sea[-1]*
*(0.9967666810102901, 0.3257011776518976)*

陆地上的全球平均温度异常约为 1 摄氏度,而海洋变暖约为 0.3 摄氏度。我们可以在下图中看到这些数字随时间的演变:

陆地和海洋的温度异常——来源:图片由作者提供

很明显,陆地上的温度上升趋势更加陡峭。

温度的绝对变化呢?

绝对变化

我们可以将相同的掩码应用于我们之前计算的绝对变化(regimes),并再次计算全局平均值:

*regimes_land = deepcopy(regimes)
regimes_sea = deepcopy(regimes)
regimes_land[:, land_mask==0] = np.nan
regimes_sea[:, land_mask==1] = np.nan

avg_temp_regimes_land = surface_stat(regimes_land, surface_perc)
avg_temp_regimes_sea = surface_stat(regimes_sea, surface_perc)
avg_temp_regimes_land[-1], avg_temp_regimes_sea[-1]*
*(1.4010793734655795, 0.625477496613651)*

在 2021 年,我们系列中的最后一个数据点,陆地比 1880 年时温暖了 1.4 摄氏度,而海洋大约温暖了 0.6 摄氏度。我们可以在下图中看到这些数字随时间的演变:

估计全球陆地和海洋温度的绝对变化——来源:图片由作者提供

在 20 世纪 80 年代、90 年代和 21 世纪初,陆地上的温度上升尤为显著。特别是在 20 世纪 90 年代,我们看到陆地和海洋都变暖得非常快。

最后的想法

让我澄清一下:我不是气象学家或气候学家,但我关心地球的未来。

作为一名数据科学家,我觉得有必要更多地了解 NOAA 网站上的可用数据,并进一步探索这些数据。这篇文章、附带的笔记本(在这里你可以运行 Google Colab 中的所有代码)和加热星球网站是我试图与你,读者,分享这些知识,并使你能够自己探索这些数据。

谢谢大家!

可视化健康风险

原文:https://towardsdatascience.com/visualizing-health-risk-2a082417f3f2

媒体在处理健康风险数据时很容易出错。数据可视化会有所帮助。

丰盛的早餐——照片由乔纳森·法伯在 Unsplash 上拍摄

2015 年 11 月,国际癌症研究机构(世界卫生组织的一部分)报告称,食用 50 克加工肉类——例如培根或香肠——与肠癌风险增加 18%有关[1]。

媒体报道了这一听起来可怕的增长,但媒体没有明确表示这是一个相对风险,而不是绝对风险。换句话说,这是风险的增加,而不是实际的风险。

可疑的报道可能是因为像“ X 让你得癌症”这样耸人听闻的标题对一些报纸来说太难以抗拒,但有时媒体会误解,因为数据被不一定理解他们所得到的信息的记者曲解了。

总的来说,患肠癌的风险约为 6%。增加 18%意味着风险上升到 7%左右。

6 * 1.18 = 7.08

因此,从绝对意义上来说,风险上升了 1%——这是一个不那么可怕的数字,也是一个不太可能阻止人们偶尔吃英式早餐或培根三明治的数字。

展示统计数据真实影响的简单可视化比简单地报告数字更容易理解。例如,一个条形图可以显示 1%的增长有多小。

作者图片

一项调查人们对重大健康事件(如中风或死亡)风险的理解的研究[2]发现,简单的条形图在传达风险方面确实有效。尽管在一项类似的研究[3]中,结论是饼状图更好。然而,在他的优秀著作《统计的艺术》中,David Speigelhalter 认为图标数组会更好。

那么选哪个呢?

我将在 Jupyter 笔记本上写一些 Python 代码,看看一些可视化风险的选项,包括条形图、热图、图标阵列和饼图。如果您想继续,请将每个代码块复制到一个新的单元格中。你可以在我的 Github repo 中找到整个笔记本的链接。从导入这些库开始。

import random
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd

构建数据

下面的代码构建了一个数据框架,表示 100 个人以及他们中患癌症的人数。6 个人会偶然得癌症,6 个人中有 18%会因为吃加工过的肉而得癌症(我在代码中用培根来代表任何加工过的肉)。其余的不会得癌症。

data = pd.DataFrame()
pop = 100                   # total population
chance = 6                  # number who get cancer by chance
bacon = int(6 * 0.18)       # number who get cancer by eating bacon
none = pop - chance - bacon # number who won't get cancer data['Non-sufferer'] = [none]
data['Sufferer by Chance'] = [chance]
data['Bacon Eater'] = [bacon]

首先,我们会画一些条形图,看看这是否更好地代表了吃熏肉的风险。我们将使用熊猫的绘图功能来做到这一点。

条形图

下面的条形图比原始数据更好地反映了额外的风险。与总人口相比,熏肉食用者的数量很少。

data.plot.bar(figsize=(8,5))

作者图片

这相当清楚,但作为堆积图会更好吗?

data.plot.bar(stacked=True,figsize=(8,5))

作者图片

当然,数字是相同的,但是条形图的顶层不是很明显,所以第一个条形图可能更好。

长宽比在很大程度上隐藏了吃熏肉的人,那么如果我们把他们变成单杠会怎么样呢?

data.plot.barh(figsize=(8,5))

作者图片

data.plot.barh(stacked=True,figsize=(8,5))

作者图片

在这种情况下,堆积图更清晰,但这主要是由于图的大小和比例。

像这样的图表可能比原始百分比要好,但不是特别有吸引力。也许我们可以做得更好。

圆形分格统计图表

让我们试试饼状图。

data.T.plot.pie(subplots=True,figsize=(8,5))

作者图片

有时饼图一点也不清晰,特别是当有几个类别要表示的时候。然而,这个只有 3 个不同的数据,所以它很好地显示了相对比例。

我对这些图表都使用了默认的配色方案——改变颜色来突出吃熏肉的人数较少可能会更好。

但是让我们来看看完全不同的东西——热图。

热图

为此,我将使用 Seaborn 数据可视化包。首先,数据需要以不同的方式表示。我将构建一个 10 乘 10 的网格,每个细胞代表一个没有癌症的人,一个偶然感染疾病的人,或者一个屈服于过多熏肉的人。

我首先制作 3 个代表 3 个不同类别的数组,然后将它们拼接成一个一维数组。

# Arrays of the different cases
a1 = [0]*data['Non-sufferer'].values[0]
a2 = [1]*data['Sufferer by Chance'].values[0]
a3 = [2]*data['Bacon Eater'].values[0]# Stitch them together
a1.extend(a2)
a1.extend(a3)

然后我把它做成 10 乘 10 的网格。

# Create a grid from the array
b = np.array(a1).reshape((10,10))

Seaborn 热图实际上是为连续变量设计的,而不是我们这里的离散变量。因此,我将颜色映射设置为只有 3 种颜色来映射到 3 个类别,适当地调整了颜色条(图例)并设置了正确的标签。

# Plot the grid as a heat map in Seaborn
fig, ax = plt.subplots(figsize=(8,5))
sns.heatmap(b, 
            linewidths=0.5, 
            yticklabels=False,
            xticklabels=False, 
            cmap=['lightblue','royalblue','midnightblue']
)# Customize legend
colorbar = ax.collections[0].colorbar 
colorbar.set_ticks([0.5,1,1.5])
colorbar.set_ticklabels(['Cancer-free','Cancer by chance','Bacon Eater'])

结果是这样的。

作者图片

这确实很好地显示了比例,但是 Speigelhalter 提出,标记的随机分散可能会对事件的随机性质给出更好的印象。下面的代码和上面的差不多,除了我在把一维数组转换成网格之前打乱了它的顺序。

# Shuffle the data and redrawrandom.shuffle(a1)
b2 = np.array(a1).reshape((10,10))
fig, ax = plt.subplots(figsize=(8,5))
sns.heatmap(b2, 
            linewidths=0.5, 
            yticklabels=False,
            xticklabels=False,
            cmap=['lightblue','royalblue','midnightblue']
            )# Customize legend
colorbar = ax.collections[0].colorbar 
colorbar.set_ticks([0.5,1,1.5])
colorbar.set_ticklabels(['Cancer-free','Cancer by chance','Bacon Eater'])

这是一个更好的情况吗?

作者图片

在我看来,更好的代表,可能是迄今为止最好的图表。但是让我们看看 Speigelhalter 关于图标数组的建议是否更清楚。

图标阵列

一个看起来更个性化的图表可能是一个图标阵列,它使用我们习惯看到的东西来表示人。下面是来自开源引导图标集合的图标:

作者图片

到目前为止,我不知道有什么简单的方法可以用我用过的任何技术来创建图标数组,所以我用 Python 编写了一个解决方案来创建一个 HTML 表,类似于上一个热图,但是用图标代替了磁贴。然后用 IPython 函数display显示 HTML。这里有相当多的代码,但是非常简单。

# Use icons to represent people and draw them in an HTML table

from IPython import display

# Create three icons of different colours
personOrange = """<i class="bi-person-fill" 
                    style="font-size: 1rem; 
                    color: orange;"></i>"""
personRed = """<i class="bi-person-fill" 
                style="font-size: 1rem; 
                color: red;"></i>"""
personGrey = """<i class="bi-person-fill" 
                style="font-size: 1rem; 
                color: grey;"></i>"""

# The first part of the HTML

head = """
<link rel="stylesheet" 
    href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.3.0/font/bootstrap-icons.css">

<div">
"""

# The last part of the HTML
tail = "</div>"

# The middle
rows=""
for r in range(0,b2.shape[1]):
    rows = rows + "<tr style='background-color:#f0f0f0'>"
    td = ""
    for c in range(0,b2.shape[0]):
        icon = personGrey
        if b2[c][r] == 1:
            icon = personOrange
        elif b2[c][r] == 2:
            icon = personRed
        td = td + f"<td>{icon}</td>"
    rows = rows + td + "</tr>" 

legend = f"""
    <div style="display:inline-block;padding:10px">
    {personRed} Bacon Eater with cancer<br/> 
    {personOrange} Cancer by chance <br/>
    {personGrey} Cancer free
    </div>
"""

table = "<table style='display:inline-block'>"+rows+"</table>"

table = head + table + legend + tail

display.HTML(table)

这给了我们 Speigelhalter 喜欢的图标阵列,虽然我倾向于同意他的判断,但只有适当的调查才能证明他是对的。

作者图片

我觉得这个管用。图标的随机分布给人一种正确的印象,即感染疾病的随机性很大,图标的使用让它感觉更加个性化——就好像这些图标中的一个实际上代表了我。我已经改变了颜色,因为我认为小的马克笔需要更亮一点。

总的来说,我觉得这是最有效的图表。

因此,快速浏览一些选项可以帮助您更清楚地传达健康(或任何其他类型的)风险。我对你的想法很感兴趣,所以如果你有意见、批评或其他想法,请在下面留下评论。

一如既往,感谢阅读。我希望你会觉得有趣,并看看我的 Github repo 中的代码。

https://alanjones2.github.io

如果你对我的其他文章感兴趣,你可以在 Medium 上浏览我的简介,或者在我的网站上看到它们。你也可以订阅我的时事通讯, Technofile ,以获得新文章的通知。

https://technofile.substack.com

笔记

[1] IARC 专论评估红肉和加工肉的消费

[2]比较图标阵列与条形图对偏好和理解风险信息的影响:来自一项在线随机研究的结果,彼得·斯卡利亚、丹妮尔·c·舒贝、艾米丽·s·卢、玛丽·安妮·杜兰德、豪尔赫·弗拉斯卡拉、吉列尔米纳·诺尔、a·詹姆斯·欧玛利、格林·埃尔温出版:2021 年 7 月 23 日,https://doi.org/10.1371/journal.pone.0253644 一中

[3]为面临颈动脉狭窄治疗选择的患者呈现基于时间的中风和死亡风险:患者更喜欢饼状图而不是图标阵列 PeterScalia。詹姆斯。《患者教育和咨询》第 102 卷第 10 期,2019 年 10 月【https://doi.org/10.1016/j.pec.2019.05.004

[4]这句话出自大卫·斯皮格尔哈尔特 2021 年出版的《统计的艺术:如何从数据中学习》(附属链接)

用 Python 管理计算化学的分子

原文:https://towardsdatascience.com/visualizing-molecules-for-computational-chemistry-fdffe7075cad

从“微笑”到 3D

演职员表:玛利亚·古利亚耶娃,免 pexels.com 版税

化学中的微笑…不,我们不会谈论上面图片中的那个。由于失败的实验,这些微笑通常很少在实验室或计算机模拟中出现。我们要讲一些和计算化学相关的东西。简而言之,计算化学是化学的一个分支,它使用计算机工具来辅助复杂的化学研究,如药物设计。自从阿尔法折叠和量子计算的出现,这个领域开始了新的复兴。

SMILES (简化分子输入行输入系统)是一种基于行符号中字符的符号,使用短 ASCII 字符串描述化合物的结构。大多数分子编辑器可以导入微笑字符串,并将其转换回二维图形(尽管分子在现实中是 3D 的)。这种方法非常有用,因为它允许你利用基于字符串操作和自然语言处理的最新机器学习工具。下面的图片展示了几个从化学结构到微笑表达的编码例子(例如 CC(=)…。):

CC(=O)OC1=CC=CC=C1C(=O)O 和 CNCC@@HO(作者图片)

  • 乙醇 CCO
  • 乙酸 CC(=O)O

如前所述,大多数编辑器——如 ChemDraw——将 SMILES 符号转换成 2D 图,即使分子实际上是 3D 的。幸运的是,角色中的信息允许在更先进的工具中重建完整的 3D 表示,这是本文的主题。

在大多数 3D 文件格式中,有。pdb(蛋白质数据库文件)或更简单的。摩尔。鉴于它与 Python 库如 RDKit 和免费工具如 PyMol 的通用性,我们在这里描述。mol 文件格式。让我们首先探索如何将 SMILES 符号转换成. mol 文件格式,然后研究. mol 文件中的内容,最后用 PyMol 可视化结果。

下面的代码片段从 RDKIT 包中导入一些库,在上图的 2D a 中绘制,最终将分子保存到一个. mol 文件中。运行这段代码的 CoLab 笔记本在这里可以找到。

关键是功能化学。molfromselles()”这是在读一个 Smiles 符号。

from rdkit import Chemfrom rdkit.Chem import AllChemfrom rdkit.Chem import Drawfrom rdkit.Chem.Draw import IPythonConsole
# Convert SMILE into .molmy_mol=Chem.MolFromSmiles('NC(=N)N1CCC[C@H]1Cc2onc(n2)c3ccc(Nc4nc(cs4)c5ccc(Br)cc5)cc3')my_mol_with_H=Chem.AddHs(my_mol)AllChem.EmbedMolecule(my_mol_with_H)AllChem.MMFFOptimizeMolecule(my_mol_with_H)my_embedded_mol = Chem.RemoveHs(my_mol_with_H) #Save the molecule into an imageDraw.MolToFile(my_mol,'molecule.png')#Save molecular rapresentation in mol filesfout = Chem.SDWriter('./charged_test.mol')fout.write(my_embedded_mol)fout.close()

作为一个 Python 库,你可以想象这可以很容易地与 Python 中的机器学习、自然语言处理和其他 AI 库相结合。

答。mol 文件格式是一个文本 ASCII 文件,以空格分隔的方式包含以下几个元素:

  • 原子列表,每个原子都有其特定的元素标识。
  • 一个键的列表,指定它们连接的原子以及键的多重性(单键、双键、三键)
  • 每个原子的 2D 或 3D 空间坐标
  • 计算分子中的原子和键的数量。
  • 与原子或键相关的属性
  • 与整个结构相关的属性。

这些通常以列或块的形式表示,如下图所示。第一列是原子的 3D 坐标,然后是原子符号,然后是属性,最后是键值。如果你想知道更多关于这种结构的细节,请查看罗伯特·贝尔福德的“MOL 文件剖析”。

Mol 文件格式结构,图片作者

产生的。使用 RDKIT 通过代码获得的 mol 可以用 PyMol 可视化为:

截图来自作者 PyMol。

总之,我们在这里介绍了一些简单的方法,既可以在 Python 脚本中使用,也可以使用可视化工具从 SMILES 符号中恢复 3D 分子结构。这是为了证明使用 SMILES 符号仍然很方便,即使它看起来不那么可读。药物的图形表示也可以用于机器学习的环境中,尽管在如何在 3D 空间中定向分子方面出现了许多问题。尽管对人类来说准备起来很麻烦,但它可以利用我们已经从机器学习中的自然语言处理中获得的所有进步,包括如所示的许多最近的作品

参考文献

皮摩尔| pymol.org

快速工具包

化学绘图

解剖一个 MOL 文件

【张等.化学前沿 2020“sp vec:一种基于 Word2vec 的药物-靶标相互作用预测特征表示方法”

解剖 PDB 文件

想连接就连接

@ Dr _ Alex _ 克里米

@ Alec rimi 博士

亚历山德罗·克里米——YouTube

亚历克斯·亚乌·克里米|脸书

使用 Plotly 可视化多维分类数据

原文:https://towardsdatascience.com/visualizing-multidimensional-categorical-data-using-plotly-bfb521bc806f

以著名的蘑菇数据集为例

有毒蘑菇与可食用蘑菇

简介 数据可视化是任何数据专家的关键技能,应用于从科学研究到工业应用的广泛领域。因此,毫不奇怪,这些年来,为了简化数据可视化的开发,出现了多种工具。在这篇文章中,我将展示几个例子,告诉你如何使用流行的 Plotly 库可视化分类数据。覆盖的是从一个到五个维度的图。此外,我将提供如何嵌入解释的例子。

数据 首先,我们从准备好数据开始。这篇文章的数据作为 UCI 机器学习知识库发布。数据集也可以从 Kaggle 竞赛网站下载。下载后,您可以使用以下代码来读取数据:

# Replace this path with your file path
file_path = r”.\Data\mushrooms.csv”# read csv data from file into pandas dataframe
df = pd.read_csv(file_path, na_values=”?”)#rename columns
df.rename(columns = {"class":"is-edible"}, inplace = True)
df.columns = df.columns

请注意,na_values关键字参数是用一个? 字符串指定的。这样做是为了用NaN值替换? 。此外,目标变量从class更名为is_edible。目的是使这个小数据集可读。类似地,代码被用来用人类可读的等价物替换默认值。在文章的最后,你可以找到一个 GitHub 链接来查看完整的代码。

让我们直观地检查数据:

蘑菇分类数据集— head

一维图:饼图
现在,让我们从一个简单的一维图开始。饼图是一个经典,因为它易于阅读和解释。因此,在任何分类数据分析中都不应遗漏它。虽然您可以使用 Plotly Express 绘制基本的饼图,但是更通用的 Plotly graph 对象(.go)库允许您轻松定制图表。这篇文章主要描述情节。去图表。

将使用population功能绘制饼图。首先,对population执行一个group-by,然后对每组中的项目(蘑菇)数量执行一个size 。然后,结果数据按人口名称的字母顺序排序。

## dataframe creation - for plotting# create new pandas dataframe which contains all counts sorted by population
population_df = (
    df.groupby(["population"])
    .size()
    .reset_index(name="Counts")
    .sort_values(by=["population"])
)

每个population 字符串的唯一出现次数被视为label。分别将之前导出的Counts作为values. 注意,可以根据 rgb 标准指定几个自定义颜色。我建议使用快速表格选择离散颜色。在该图表中,颜色存储在earth_colors.

接下来,指定pull关键字参数。图中的pull表示每个扇区在图表中心的展开程度。拉动可以大大增强图表的可读性,尤其是如果你有很多扇区的话。但是请记住,如果一个特性的基数太高,图表将变得无法阅读。

## Creating a pie chart# create labels using all unique values in the column named "population"
labels = population_df["population"].unique()# group by count of the "population" column.
values = population_df["Counts"]# Custom define a list of colors to be used for the pie chart.
# Note that the same number of colors are specified as there are unique populations. It is not mandatory, but
# will avoid a single color to be used multiple times.
earth_colors = [
    "rgb(210,180,140)",
    "rgb(218,165,32)",
    "rgb(139,69,19)",
    "rgb(175, 51, 21)",
    "rgb(35, 36, 21)",
    "rgb(188,143,143)",
]# defining the actual figure using the dimension: population
# Note that a pull keyword was specified to explode pie pieces out of the center
fig = go.Figure(
    data=[
        go.Pie(
            labels=labels,
            values=values,
            # pull is given as a fraction of the pie radius
            pull=[0, 0, 0.07, 0.08, 0.02, 0.2],
            # iterate through earth_colors list to color individual pie pieces
            marker_colors=earth_colors,
        )
    ]
)# Update layout to show a title
fig.update_layout(title_text="Mushroom Polulation")# display the figure
fig.show()

蘑菇数量饼图

蘑菇是根据它们栖息的种群来分类的。这个饼图的例子代表了蘑菇在每个群体中的比例。大多数蘑菇以几个为一组生活,而不到 15%的蘑菇以众多、丰富或成簇的形式生活。该数据集中包含的大约三分之一的蘑菇单独或分散出现。

二维图:饼图 正如你可能猜到的,这个数据集经常被用来将蘑菇分为有毒或可食用。因此,好奇的头脑想知道属于任一种群的蘑菇的比例在食用蘑菇和有毒蘑菇之间是否不同?这可以通过使用两个饼状图支线剧情来实现,每个支线剧情对应于有毒或者可食用的职业。首先,需要准备好数据。如在前面的例子中,执行一个group by加一个size。之后,根据population 变量对结果进行排序。然而这一次,我们首先使用.loc 函数来过滤有毒或可食用的条目。结果是总结蘑菇数量的两个熊猫数据框。

## dataframe creation - for plotting# create new pandas dataframe which contains all counts filtered by 'is-edible' == "edible" and sorted by population
edible_population_df = (
    df.loc[df["is-edible"] == "edible"]
    .groupby(["population"])
    .size()
    .reset_index(name="Counts")
    .sort_values(by=["population"])
)# create new pandas dataframe which contains all counts filtered by 'is-edible' == "poisonous" and sorted by population
poisonous_population_df = (
    df.loc[df["is-edible"] == "poisonous"]
    .groupby(["population"])
    .size()
    .reset_index(name="Counts")
    .sort_values(by=["population"])
)# get unique values from the just created pandas dataframes and store them in an array
labels_edible_population = edible_population_df["population"].unique()
labels_poisonous_population = poisonous_population_df["population"].unique()# get all the counts from the created pandas dataframes and store them in an array
values_edible_population = edible_population_df["Counts"]
values_poisonous_population = poisonous_population_df["Counts"]

然后将数据框传递给 Plotly 图形对象进行绘制。事实上,前两个支线剧情是使用提供的关键字rows, colsspecs创建和安排的。然后在变量earth_colors下定义标记颜色。然后,使用add_trace将数据传递给两个人物对象。同样,标签和值被指定,图表被给定名称,我们指示哪个图表将到达哪个位置。使用update_trace,我们通过向用户显示悬停信息,让饼图变得更具交互性。最后,图布局被更新以显示图表标题和饼图描述。

## Creating two pie charts# Create subplots: use 'domain' type for Pie subplot
fig = make_subplots(rows=1, cols=2, specs=[[{"type": "domain"}, {"type": "domain"}]])# create an array of colors which will be custom colors to the plot
earth_colors = [
    "rgb(210,180,140)",
    "rgb(218,165,32)",
    "rgb(139,69,19)",
    "rgb(175, 51, 21)",
    "rgb(35, 36, 21)",
    "rgb(188,143,143)",
]# crate traces to specify the various properties of the first pie chart subplot
fig.add_trace(
    go.Pie(
        labels=labels_edible_population,
        values=values_edible_population,
        name="Edible Mushroom",
        marker_colors=earth_colors,
    ),
    1,
    1,
)# crate traces to specify the various properties of the second pie chart subplot
fig.add_trace(
    go.Pie(
        labels=labels_poisonous_population,
        values=values_poisonous_population,
        name="Poisonous Mushroom",
        marker_colors=earth_colors,
    ),
    1,
    2,
)# Use `hole` to create a donut-like pie chart
fig.update_traces(hole=0.4, hoverinfo="label+percent+name")# adapt layout of the chart for readability
fig.update_layout(
    title_text="Mushroom Population by Edibility",
    # Add annotations in the center of the donut pies.
    annotations=[
        dict(text="Edible", x=0.18, y=0.5, font_size=17, showarrow=False),
        dict(text="Poisonous", x=0.82, y=0.5, font_size=17, showarrow=False),
    ],
)
fig.show()

蘑菇群体由可食用馅饼

的确,毒蘑菇和食用蘑菇的比例差别很大!首先映入眼帘的是,几乎四分之三的毒蘑菇生活在被描述为“众多”的种群中。另一方面,只有约 10%的食用菌被算作“众多”部门。此外,所有单独的蘑菇都是可食用的。总之,如果我们要设计一个分类算法,我们会知道“群体”确实是关于目标变量的一个重要特征。

三维图:条形图 在不降低清晰度的情况下,可以显示多少维度的分类数据?事实上,在使用条形图时,绘制三个类别仍然很简单。在一维条形图中,类别位于 x 轴,数字位于 y 轴。然而,为了增加更多的维度,可以使用着色和阴影。

在下面的柱状图中,蘑菇状特征cap-shape绘制在 x 轴上。颜色由is-edible给出,图案由gill-size给出。如前所述,创建了一个. go。然后。go 图使用 Plotly express 直方图函数进行更新,我们将数据框传递给该函数,并指定各种关键字参数。请注意,除了colors 参数之外,还需要给出一个离散的颜色序列。此外,Plotly 允许直方图值的自动标准化。通过使用histnormbarnorm实现标准化。你可以根据自己的需要选择。欲了解更多信息,您可以查看此处

在下一步中,更新布局以显示标题文本。tile字典中的x关键字参数不代表数据。相反,它允许用户指定一个值来使标题居中。同样,您可以查找所有可用的选项

最后但同样重要的是,用户可以通过指定yaxis_titlexaxis_title参数来手动指定所需的轴标题。最后,update_xaxes参数被指定为类别按字母升序排序。

## Creating bar chart# define figure element
fig = go.Figure()# define histogram properties
fig = (
    px.histogram(
        df,
        x="cap-shape",
        color="is-edible",
        pattern_shape="gill-size",
        color_discrete_sequence=[medimumvioletred, seagreen],
        barmode="relative",
        # barnorm = "percent",
        # histnorm = "probability",
        text_auto=True,
        labels={"cap-shape": "Mushroom Cap-Shape & Gill Size",},
    )
    .update_traces(hoverinfo="all")
    .update_layout(
        # update layout with titles
        title={
            "text": "Percent - Dataset: Cap Shape & Gill Size - Edibility",
            "x": 0.5,
        },
        yaxis_title="Absolute Counts",
    )
    .update_xaxes(categoryorder="category ascending")
)# display the figure
fig.show()

图中显示的是有毒和可食用蘑菇的绝对数量,按照菌盖形状和菌盖大小分类。大多数蘑菇的帽子(顶部)要么是凸的,要么是平的。凸形、扁形和钟形蘑菇既可以有毒,也可以食用。事实上,只有下沉的帽状蘑菇是有毒的,而下沉的帽状蘑菇是可以食用的。注意凹陷的和圆锥形的蘑菇出现的频率最低。凸而平的帽状蘑菇在有毒和可食用两类中都有窄而宽的鳃。

四维图:旭日图

随着维数的增加,绘图变得更难解释和编程。旭日图可视化了从根到叶径向向外延伸的分层数据,根据数据集的不同,这是一个很好的选择。

使用 Plotly express 创建的旭日图如下所述。数据被传递给 sunburst 对象。然后指定一个路径。该路径对应于一个希望在旭日图中绘制的列的列表。这些列是按照人们希望它们被绘制的顺序传递的。例如,如果一个人希望 is-eatable 位于情节的中心,那么它应该首先被命名。

values关键字用于使每个楔形区的大小与给定值成比例。在这个例子中,count列被传递给values

## dataframe creation - for plotting
df_combinations = (
    df.groupby(["is-edible", "population", "habitat", "bruises"])
    .size()
    .reset_index()
    .rename(columns={0: "count"})
)## Creating sunburst chart# define figure element
fig = px.sunburst(
    df,
    path=["is-edible", "bruises", "population", "habitat"],
    title="Edibility Mushrooms - bruises, population & habitat",
    color="is-edible",
    color_discrete_sequence=[medimumvioletred, seagreen],
    height=800,
)
# display the figure
fig.show()

旭日图

该图显示了蘑菇的数量,按其特征从根节点到叶节点按比例分类。虽然有毒和可食用蘑菇的数量几乎相等,但人们可以看出有毒蘑菇被碰伤的频率要低得多。未受损伤的毒蘑菇成群生长在树林、树叶、草地或小路上。伤痕累累的毒蘑菇生活在分散的种群中,最好是在城市或长满草的栖息地。
有伤痕的食用菌大多生活在独居或分散的种群中,最常见于林区。可食用的和未受损伤的蘑菇大多生活在分散和丰富的种群中,喜欢草和叶子的栖息地。总之,旭日图显示了蘑菇是如何根据不同的特征分组的。请注意,解释需要涵盖广泛的细节。

五维图:平行坐标图

平行坐标图(parcats)是一个优雅的工具,可用于可视化多个分类变量。

要生成绘图,首先将目标变量的值映射到整数。这样做是因为 parcats 函数不接受字符串对象。然后,根据想要使用的变量创建五维对象。除了先前创建的变量is-edible-int被传递给color之外,颜色被照常定义。最后,定义 figure 对象,传递维度变量并指定各种其他关键字参数,以增加图表的可读性。

## Creating parallel categores chart# creation is edible integer
df["is-edible-bool"] = df["is-edible"].map({"edible": 1, "poisonous": 0})# Create dimensions
# stalk-shape
stalk_shape_dim = go.parcats.Dimension(
    values=df["stalk-shape"], categoryorder="category ascending", label="Stalk-Shape"
)
# stalk-root
stalk_root_dim = go.parcats.Dimension(values=df["stalk-root"], label="Stalk-Root")
# stalk-surface-above-ring
stalk_surface_above_ring_dim = go.parcats.Dimension(
    values=df["stalk-surface-above-ring"], label="Stalk-Surface-above-Ring"
)
# stalk-surface-below-ring
stalk_surface_bellow_ring_dim = go.parcats.Dimension(
    values=df["stalk-surface-below-ring"], label="Stalk-Surface-bellow-Ring"
)# is-edible
edible_dim = go.parcats.Dimension(
    values=df["is-edible"],
    label="Is Edibile",
    categoryarray=["edible", "poisonous"],
    ticktext=["edible", "poisonous"],
)# Create parcats trace
color = df["is-edible-bool"]
colorscale = [[0, medimumvioletred], [1, seagreen]]# create figure object
fig = go.Figure(
    data=[
        go.Parcats(
            dimensions=[
                stalk_shape_dim,
                stalk_surface_above_ring_dim,
                stalk_root_dim,
                stalk_surface_bellow_ring_dim,
                edible_dim,
            ],
            line={"color": color, "colorscale": colorscale},
            hoveron="color",
            hoverinfo="count + probability",
            labelfont={"size": 18, "family": "Times"},
            tickfont={"size": 16, "family": "Times"},
            arrangement="freeform",
        )
    ]
)# display the figure
fig.show()

平行坐标图

蘑菇通过它的柄状、环上柄面、柄根和环下柄面来区别于其他蘑菇。这些特征能解释蘑菇的可食用性吗?事实上,蘑菇柄的形状是变大还是变小似乎没有太大的区别。然而,“蘑菇圈”上方的表面提供了更多的信息。如果我们选择一种具有光滑或纤维状茎表面的蘑菇,人们会更加确信这种蘑菇是可食用的。蘑菇圈上方的光滑表面应该避免。“茎-根”应该是棒状或同等的。如果环下面的茎表面是光滑的,那么很有可能找到了可食用的蘑菇。
这些特征的组合增加了区分有毒和可食用蘑菇的机会。

在高维空间中可视化分类数据是一个挑战。如果你对其他人如何解决绘制多维数据感兴趣,我可以推荐来自di panjan(Matplotlib&Seaborn)的“多维数据有效可视化的艺术”文章。关于如何使用 plotly 图形的更多详细信息,我可以推荐这本指南

希望你觉得这篇文章很有趣,并能学到一些新东西。重现剧情的代码可以在我的 GitHub Repo 上找到。
注意,Plotly 是开源的,可以用于商业和私人目的。

我非常感谢 Evgeniy 为这篇文章提供了插图。如果你喜欢他的画,请在 Instagram 上关注他。

Evgeniy 食用菌

[1]Dua d .和 Graff c .(2019 年)。UCI 机器学习知识库[http://archive . ics . UCI . edu/ml]。加州欧文:加州大学信息与计算机科学学院。

posted @ 2024-10-18 09:39  绝不原创的飞龙  阅读(151)  评论(0)    收藏  举报