TowardsDataScience 2023 博客中文翻译(二百九十七)

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

SQL 用户:使用这一技巧将你的查询长度缩短一半

原文:towardsdatascience.com/sql-users-halve-the-length-of-some-of-your-queries-with-this-one-trick-c3c2c226cb35

QUALIFY 子句纯粹是语法糖

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Matt Chapman

·发布于 Towards Data Science ·阅读时间 6 分钟·2023 年 3 月 29 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由 Joanna Kosinska 提供,来源于 Unsplash

在这篇文章中,我将向你介绍 SQL QUALIFY 子句:一种帮助你编写更简洁、可读性更高的 SQL 查询的技巧。

如果你从事数据科学/分析或机器学习,这是一个必备的 SQL 工具,原因有四:

  1. 首先,因为它可以帮助你编写更 简短 的查询,简化你的数据收集流程。在我的经验中,QUALIFY 将许多查询的长度减少了多达 50%。

  2. 其次,因为它会帮助你编写更 简洁 的查询。这很重要,因为正如保罗·格雷厄姆所说,简洁就是力量,它使你的代码更具可读性和可维护性。

  3. 第三,QUALIFY 帮助你编写更 高效 的查询,从而降低成本并加快开发速度。

  4. 第四,它是一个展示技巧的好方法。我并不是说你会因为使用 QUALIFY 赢得任何创新奖,但因为它是一个 相对较新的子句,你的老板可能还没有听说过。无论如何,展示给你的团队一个新技巧会很有趣。

QUALIFY 子句的作用是什么?

简单来说,QUALIFY 子句使你可以筛选窗口函数的结果,无需 使用嵌套子查询或 WITH 子句。它允许你用 一行代码 替代这些冗长的代码块。这有点类似于 HAVING 子句如何使你能够筛选 GROUP BY 聚合的结果。

QUALIFY 子句使你能够筛选窗口函数的结果

如果这听起来像胡言乱语,给我 2 分钟的时间,我会尽力解释清楚。

一切从窗口函数开始

如果你不熟悉窗口函数,你可能想看看这篇文章,它通过 4 个实际使用案例介绍了 SQL 窗口函数。但简而言之,窗口函数可以用来对表中的一组行进行计算,而无需聚合或合并这些行,这在你想保留表的原始结构时非常有用。

例如,假设我们有一个名为orders的表,它列出了公司收到的所有产品订单。每一行代表一个订单,并记录了订单下达的date、下订单的customerorder_amount(以美元计)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来源于作者

使用窗口函数,我们可以添加一个新的列total_order_amount,表示每个客户的总订单金额。我们可以简单地写:

SELECT
  date,
  customer,
  order_amount, 
  SUM(order_amount) OVER(PARTITION BY customer) AS total_order_amount
FROM orders
ORDER BY date

这将返回:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来源于作者

如你所见,窗口函数SUM(order_amount) OVER(PARTITION BY customer)有效地将我们的表分区为不同的“窗口”(每个客户一个窗口),然后计算这些窗口的total_order_amount。所有这些都在不使用GROUP BY聚合的情况下完成,使我们能够保留相同数量的行。

输入 QUALIFY 子句

现在我们已经设置好了窗口函数,假设我们想向我们花费最多的客户发送感谢邮件。在此之前,我们需要筛选这个表,查看total_order_amount超过$5,000 的客户订单。换句话说,我们想生成这样的内容:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来源于作者

一种生成这个结果的典型方法是使用WITH子句:

WITH my_order_table AS (
  SELECT
    date,
    customer,
    order_amount, 
    SUM(order_amount) OVER(PARTITION BY customer) AS total_order_amount
  FROM orders
)

SELECT
  *
FROM my_order_table
WHERE total_order_amount > 5000
ORDER BY date

…或者一个嵌套子查询:

SELECT
  *
FROM 
    (SELECT
      date,
      customer,
      order_amount, 
      SUM(order_amount) OVER(PARTITION BY customer) AS total_order_amount
    FROM orders) AS A

WHERE A.total_order_amount > 5000
ORDER BY A.date

然而,虽然这两种方法都是完全可接受的解决方案,但QUALIFY子句使我们能够显著简化和缩短这段代码。我们可以简单地写:

SELECT
  date,
  customer,
  order_amount, 
  SUM(order_amount) OVER(PARTITION BY customer) AS total_order_amount
FROM orders
QUALIFY total_order_amount > 5000

这将返回完全相同的结果

QUALIFY 语句是纯粹的语法糖

QUALIFY 子句的一个好处是它在窗口函数计算之后被评估。这意味着你可以使用你分配给窗口函数列的别名进行引用。在上面的例子中,我通过编写QUALIFY total_order_amount..演示了这一点,引用了我在创建原始窗口函数时分配的别名total_order_amount

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来源于作者

SELECT 子句中不必写窗口函数。例如,如果我不想实际将总订单金额显示为单独的列,但仍需在这个窗口函数上进行过滤,我可以这样写:

SELECT
  date,
  customer,
  order_amount, 
FROM orders
QUALIFY SUM(order_amount) OVER(PARTITION BY customer) > 5000

这将返回相同的结果,但没有 total_order_amount 列:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

除此之外,QUALIFY 子句也可以比使用子查询或 WITH 稍微更高效。这不仅会加快你的开发时间;它还对成本优化有很大帮助。

另一个例子:选择每组中的前 N 名

QUALIFY 子句的一个常见用例是选择每组中的前 N 名。例如,假设我们有一个记录每个商店在某一天进行的每笔销售的 sales 表。每一行代表一次交易,并包含销售的时间戳、购买的产品以及该笔销售中花费的总金额。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用窗口函数:

SELECT
  Timestamp,
  Product,
  Sale_value,
  ROW_NUMBER() OVER(PARTITION BY Product ORDER BY Timestamp ASC) as row_num
FROM sales
ORDER BY Product, row_num

我们可以在每个产品类别内给每笔销售分配一个行号:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

然后,使用 QUALIFY 语句,我们可以过滤结果,只显示每个类别中的第一行:

SELECT
  Timestamp,
  Product,
  Sale_value,
  ROW_NUMBER() OVER(PARTITION BY Product ORDER BY Timestamp ASC) as row_num
FROM sales
QUALIFY row_num = 1
ORDER BY Product

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

就是这样——QUALIFY 的魔力。

最后一个事情

QUALIFY 子句在所有 SQL 方言中尚不可用。在撰写时,它已被许多大型平台如 BigQuery、Databricks、Snowflake 和 H2\ 支持。如果你的平台/方言不兼容,你现在需要依赖子查询/WITH,但你可以关注现代 SQL以查看它何时可用。

利用 SQL 中的分析函数加速数据提取

原文:towardsdatascience.com/sql-window-functions-939ed24c9752

分析函数提供了一种极其强大且易于实现的数据处理和分析方式。本文将展示如何在 SQL 语句中使用分析函数。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Günter Röhrich

·发布在 Towards Data Science ·8 分钟阅读·2023 年 7 月 18 日

作为一名分析师,你很可能会遇到需要查询数据以进行分析的情景。数据通常来自 SQL 数据库,然后通过像 Python 这样的编程语言和 Pandas 或 NumPy 等强大框架导入。这是一种处理数据的完全有效的管道,但重的计算主要依赖于你的本地机器。对于小数据集,这不是问题,但对于较大的数据集,可能会遇到仅依靠 PC 本地内存处理大量数据的困难。

你可能认为这不是一个常见的问题。因此,让我给出一个日常的例子来证明这个假设是错误的:

想象一下,你在一家制造公司工作,并且对收集机器传感器数据感兴趣。这些数据通常被频繁收集,而且可能非常嘈杂。为了更好地理解你的机器状态,对密集收集的数据进行平滑和预处理(例如,测量可能每秒收集几次)很快就会产生庞大的数据集!假设我们在一台机器旁边放置了 150 个传感器,每个传感器每秒读取 4 次数据。那么一天的数据将会产生

4×60×60×24x150 ≈ 52 百万条记录(读取次数 × 秒 × 分钟 × 小时 × 传感器)

数据点。通常,作为经验法则,我们会查看至少一周的数据(但我们也可能增加读取次数或传感器数量)……你能理解这点。

因此,你可能更好地将计算成本高的聚合操作转移到源数据库中。特别是,分析或窗口函数的语法直接明了,却是读取、转换和提取数据的强大工具,适用于更高层次的聚合。

关键要点

每当你看到需要滚动/移动窗口或在逻辑分区内进行计算(例如,持续排名,从最低到最高值,在某些传感器组内),直接在 SQL 查询中应用窗口函数通常是值得考虑的,而不是使用 Pandas 中实现的更昂贵的函数来实现类似的功能。

这篇简短的文章是如何结构化的?

  1. 一个 简短的介绍 例子和窗口函数如何工作的理论

  2. 一个 快速指南来安装一些 SQL 演示数据 以及一个可以在电脑上直接使用的数据库(这很简单!)。我们将使用著名的 纽约出租车 数据。

  3. 示例 让你熟悉这些函数

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一些窗口函数的 Julio César Bosch

介绍

为什么你需要窗口函数?

在数据科学更具吸引力的领域引起的轰动中,我们常常忽视了数据科学家大部分时间花费在获取、处理和清洗数据上。每当你看到需要计算成本高的运行总和或移动平均时,你应该考虑将这些转换操作转移到数据库中。

这种 函数是对一系列语境上相关的行进行操作。通常,当涉及此类事务时,我们会考虑总和、平均值、最小值或最大值计算。

窗口函数一般是如何结构的?

首先,了解使用窗口函数的查询结构,我们来看一个使用滚动总和的 SQL 语句——只需查看代码块,不需要完全理解概念,暂时把代码当作简单演示:

select
   VendorID,
   tpep_dropoff_datetime as dropdate,
   passenger_count,
 sum(passenger_count) over (
     partition by date(tpep_dropoff_datetime)
     order by tpep_dropoff_datetime
     rows between unbounded preceding and current row
 ) as day_running_sum
from trip_data td 
  1. 首先,我们指定我们希望执行的分析类型。一般来说,有三种类型的函数 可供使用:
  • 聚合函数,如 min()、max()、count() 或 avg()——这在上面的第一个示例中展示过

  • 编号函数,如 rank() 或 row_number()

  • 导航函数 如 lead()、lag() [返回后续或前值]

2. 在下一步中,我们开始考虑 逻辑分区。例如,我们可以选择用户 ID 作为分区示例,因此我们查看用户块,并对每个用户子集执行进一步操作。必须将分区传递给函数!

3. 类似于 group by 语句,我们还应在分析函数中考虑排序参数。排序应用于之前定义的选定子块。排序是可选的,可以省略。

  1. 最后,分析(或窗口)函数的核心元素:窗口框架子句。这个子句指定了我们希望查看的滚动时间窗口。

要深入了解更多理论,请访问下面的链接 — 请注意,窗口函数在不同的 SQL 数据库中可能会有所不同,但它们的一般结构基本相同。

[## 窗口函数

窗口函数是 SQL 函数,其中输入值来自一个或多个行的“窗口”中的…

www.sqlite.org

快速安装指南

为了说明窗口函数如何工作,我将提供一些使用著名的纽约出租车数据集的子集的示例。通常,你需要以下工具来自己完成这些示例:

  • 已安装 Python(用于提取,需 sqlite3 和 pandas 库)

  • 一种编写查询和查看数据的工具(谈到 SQL,DBeaver 是一个功能强大的数据查询工具,但易于使用且开源。

就这样!

如果你想复制所展示的步骤,只需访问下面的链接并下载即可:

[## TLC 行程记录数据

黄绿出租车的行程记录包括捕捉接送日期/时间的字段、接送…

www.nyc.gov

法律说明: 只要使用目的合法 ,数据使用没有限制,如使用条款中所述。更多许可证详细信息和数据集引用请见* 文章结尾*。

下载成功后,我建议将 parquet 文件转换为 sqlite3 数据库。使用 Python 以及 Pandas 和 sqlite3 包,这很快就能实现——只需将代码复制粘贴到脚本中,你就准备好了:

import pandas
import sqlite3

data = pd.read_parquet('yellow_tripdata_2023-01.parquet')

con = sqlite3.connect('taxi.db')  # creates a database "taxi.db"
data.to_sql('trip_data', con)     # name of the table

完成了!我们现在可以在存储在硬盘上的非常简单的 SQL 数据库中工作了。

示例

让我们从获取整个数据的费用总和和平均值这一简单任务开始。为此,我们可以使用 SUM 和 AVG 参数,并在 over 语句后的括号中省略分组标准。由于我们只是对整个数据集进行聚合,因此尚无需使用窗口函数。

SELECT  
       SUM(fare_amount) AS total_fare,
       AVG(fare_amount) AS average_fare
FROM trip_data td;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

假设我们想了解每一天每个供应商(VendorID)的总费用和平均费用——这个任务会变得稍微有些挑战:

SELECT
 VendorID,
 date(tpep_dropoff_datetime) as dropdate,
 SUM(fare_amount) AS total_fare,
    AVG(fare_amount) AS average_fare
FROM trip_data td
group by VendorID, dropdate

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如我们所见,为了实现分组效果,我们需要使用分组关键字,使我们能够根据VendorIddropdate进行汇总。虽然分组非常强大,但它不能进一步将单独行拆分为拖尾总和或移动平均值——这时窗口函数就派上用场了。

现在假设我们想计算fare_amount的滚动总和和均值,并按VendorID和相关的每日行程进行分类。

我们的窗口操作在我们指定的“分区”内。我们定义一个分区为整天,因此,我们期望看到每一天和每个供应商的滚动总和和均值(或等效地,在每个分区内)。

select
   VendorID,
   date(tpep_dropoff_datetime) as dropdate,
   fare_amount,
   sum(fare_amount) over (
      partition by date(tpep_dropoff_datetime), VendorID 
      order by tpep_dropoff_datetime
      rows between unbounded preceding and current row
   ) as rolling_sum,
   avg(fare_amount) over (
      partition by date(tpep_dropoff_datetime), VendorID 
      order by tpep_dropoff_datetime
      rows between unbounded preceding and current row
 ) as rolling_mean
from trip_data td;

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

每个分区(dropdate + VendorID)显示每行的票价以及滚动总和。

注意以下事项,我们使用了“unbounded preceding”作为框架子句。这意味着我们查看在我们分区内的所有前置值——再说一次,我们的分区定义为Date + VendorID

如果我们只想查看一个滚动总和/均值,该均值考虑到之前和当前的票价(再次在Date+VendorID的分区内),我们需要将框架子句调整为仅 1 行之前和当前行

select
 VendorID,
 date(tpep_dropoff_datetime) as dropdate,
 fare_amount,
 round(sum(fare_amount) over (
  partition by date(tpep_dropoff_datetime), VendorID 
  order by tpep_dropoff_datetime
  rows between 1 preceding and current row --> NOW 1 Preceding row only
 ),2) as rolling_sum,
 round(avg(fare_amount) over (
  partition by date(tpep_dropoff_datetime), VendorID 
  order by tpep_dropoff_datetime
  rows between 1 preceding and current row --> NOW 1 Preceding row only
 ),2) as rolling_mean
from trip_data td 

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

“1 Preceding”结果是一个移动总和/均值,仅查看当前和之前的行程票价——再说一次,这始终在分区内。

在最后一个示例中,我们将结合所有之前的步骤,展示如何优雅地查询简单的汇总数据,并使用这些数据执行 rank()语句——这是一种分析编号函数。

我们希望按每个供应商收取的总票价的最高值对天数进行排名。因此,我们将这分为两个步骤:

  1. 通过简单的“group by”按天和供应商汇总票价

  2. 使用分析函数对“VendorID”分区内的天数进行排名

with aggregated as (
 select
    VendorID,
    date(tpep_dropoff_datetime) as dropdate,
    round(sum(fare_amount),2) as total
 from trip_data td 
 where dropdate like '2023%' -- this to remove wrong 2020 data in the dataset
 group by VendorID, dropdate
 order by dropdate asc
) -- here our "aggregated" data subset is created
select 
  VendorID,
  dropdate,
  total,
  rank() over (
      partition by VendorID
      order by total desc
  ) as best_days
from aggregated -- we rank() based on "aggregated"

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

VendorID=1开始,1 月份最好的日子是 26 号,它排名第一(收入最高)。

结论

到现在为止,我相信你已经掌握了分析函数的工作原理以及它们如何灵活地实现相当复杂的查询结果。

虽然数据现在通常在本地机器或云实例上转换,但在实际导入到后续软件之前修改数据通常是更好的选择——也就是说,SQL 是一种非常强大的语言来完成这项任务!

如果你觉得这篇文章对你有帮助,我会很感激你的“关注”🫀,直到那时:

{照顾好自己,如果可以的话,也照顾好别人}

—— 借用自斯蒂芬·杜布纳

数据集引用和使用条款:

纽约市出租车和豪华轿车委员会(TLC)的行程记录数据于 7 月 13 日从www.nyc.gov/site/tlc/about/tlc-trip-record-data.page访问。

完整的使用条款可以在这里找到:

[## 使用条款

欢迎访问纽约市官方网站。在使用之前,请仔细阅读以下使用条款(“条款”)…

www.nyc.gov](https://www.nyc.gov/home/terms-of-use.page?source=post_page-----939ed24c9752--------------------------------)

SquirrelML:预测纽约中央公园的松鼠接近行为

原文:towardsdatascience.com/squirrelml-predicting-squirrel-approach-in-nycs-central-park-8c3719d8ff65

通过机器学习探索纽约中央公园的松鼠行为:聚类观察和预测互动见面

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Victor Murcia

·发表于Towards Data Science ·18 分钟阅读·2023 年 12 月 20 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由Tj Holowaychuk拍摄,发布于Unsplash

NYCOpenData拥有丰富的有趣数据集,涉及健康、环境、商业和教育等话题。我偶然发现了2018 Central Park Squirrel Census数据集,并立即决定要对其进行一些处理。该数据集记录了志愿者在两周内在中央公园收集的松鼠观察数据。浏览数据字典后,我被一个名为‘Approaches’的特征吸引,该特征表示是否观察到松鼠接近人类。我认为训练一个机器学习(ML)模型来帮助我判断位于中央公园范围内的松鼠是否会接近我会很有趣。本文将详细介绍这个周末项目的整个过程。这个项目涉及许多内容:地理空间数据处理、聚类、可视化、特征工程、非结构化文本、模型训练、模型校准和模型部署。

我在 streamlit 应用中部署了该模型,你可以输入你的坐标和其他特征,这将告诉你松鼠接近你的概率。你可以在这里玩这个应用。如果你对查看一些代码感兴趣,我已将.ipynb 文件发布在这里

数据加载和初步 EDA

数据加载相当标准。

ini_squirrel_df = pd.read_csv('/content/drive/MyDrive/SquirrelML/NYC_Squirrels.csv')

为了进行初步的 EDA,我使用了 dataprep 来快速了解原始数据集中存在的特征分布、基数、模式、缺失数据和相关性。你可以查看报告这里。从中获得了几个有用的见解,这些见解帮助我规划后续的特征工程,并移除冗余/不必要的特征。以下是我从这次 EDA 中获得的一些显著观察结果:

  • 数据集由 3023 个独特观察值、31 列组成,其中 13%的单元格缺失。

  • 大多数特征是分类的和布尔值的。

  • 纬度和经度条目似乎有一个大致的四峰分布

  • “移位”列相当平衡(~55%的观测是在下午/晚上进行的)

  • “漠不关心”列相当平衡(~51%的条目为 False)

  • 大多数松鼠是成年松鼠,主要毛色为灰色

  • 观察到的最常见的位置是树木

  • 颜色备注、具体位置、其他活动和其他互动是有大量缺失数据的文本列

  • “接近”高度不平衡(94.11%的观测值为负)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

目标变量‘接近’的巨大类别不平衡。图片由作者提供

这些都是有用的观察,因为现在我知道我尝试解决的问题是一个不平衡的二分类问题。此外,我现在知道使用聚类方法对松鼠观测进行分组可能在未来会有用。此外,文本列可能包含可以创建新、潜在有用特征的信息。

松鼠观测的聚类

如下所示,松鼠观测似乎具有大致的三峰或四峰分布。我认为尝试根据位置对松鼠观测进行分组会很有趣。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

中央公园范围内松鼠观测的经度和纬度。图片由作者提供。

虽然我们可以通过轮廓系数或肘部法则来尝试确定最佳的簇数,但在这种情况下,我想将观测结果简单地划分为 4 个区域。我使用纬度和经度运行了 K 均值算法,如下所示:

# Function for Applying Clustering
def apply_clustering(df, num_clusters=4):
    kmeans = KMeans(n_clusters=num_clusters, n_init='auto')
    df['Cluster'] = kmeans.fit_predict(df[['X', 'Y']])
    centroids = kmeans.cluster_centers_

    # Assuming 'kmeans' is your trained KMeans model
    joblib.dump(kmeans, '/content/drive/MyDrive/SquirrelML/squirrel_kmeans.pkl')

    return df, centroids

K-Means 算法的结果如下所示。有一些明显的边界,我通过 Voronoi 图进行了强调。你还可以看到每个簇的质心通过红色 X 标记出来。我还使得那些接近松鼠的点没有被包围在暗圈中,而那些没有接近的点则被包围在内。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

松鼠普查数据集的纬度和经度上的 K-Means 聚类结果。图片由作者提供。

松鼠观测的地理空间可视化

地理空间数据处理起来很有趣。如果你查看中央公园的鸟瞰图,或者对其布局比较熟悉,你就会看到布局的逐一对应。例如,中央公园有三个主要的水体,这些水体出现在图上的空白区域。你可以在这里找到中央公园的标注布局。例如,位于‘黄色’簇中的那个大‘空旷’区域实际上是‘水库’。右上方,我们还可以看到一个接近空白的区域,对应于‘哈莱姆湖’。最后,在蓝色簇的左下角,我们可以看到被称为‘湖泊’的水体。

为了更好地利用这些数据,我认为将这些观测结果直接可视化在地理空间准确的纽约市地图上会很有趣。为此,我再次转向 NYCOpenData,并从这里下载了纽约市的 shape (.shp)文件。你的下载应该包括 4 个不同的文件:一个.shp 文件、一个.prj 文件、一个.dbf 文件和一个.shx 文件。在加载这些文件之前,请确保所有文件都在同一个目录中。下载这些文件后,我用 Geopandas 将它们加载到环境中。这些文件通常比较大,加载可能需要几分钟(在我的情况下大约需要 5 分钟)。

nyc_map = gpd.read_file('geo_export_0a3d2fab-a76c-40da-bc23-6e335dd753dd.shp')

为了使.shp 文件更加轻量化并提高工作速度,我使用 Geofeather 将 Geopandas 框架转换为 feather 对象。

geofeather.to_geofeather(nyc_map,'nyc_map.feather')

现在,我可以从 feather 文件中重新生成 nyc_map 并绘制它,这比使用原始的.shp 文件要快很多。我们可以如下绘制 nyc_map:

nyc_map = geofeather.from_geofeather('nyc_map.feather')
nyc_map.plot()

结果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

地理空间准确的纽约市地图。图片由作者提供。

看起来很棒!你还可以在纽约市陆块的左上角看到中央公园。尽管这个视图很酷,但我想更多地关注中央公园区域。因此,我也不需要整个数据集,只需根据松鼠观测坐标进行过滤。这还将加速所有后续操作,因为我们将处理纽约市地图的一个更小的子集。

质心是形状的几何中心。在地理空间数据的上下文中,它指的是地理特征的中心点。这个点是形状中所有点的平均位置。对于简单的形状如多边形(例如城市或公园的边界),质心通常位于形状内部。对于更复杂的形状,尤其是那些有凹部的形状,质心可能位于形状的物理边界之外。有几种方法可以用来计算质心。这里我只是使用了 shapely 包和 nyc_map:

from shapely.geometry import Point
# Calculate the centroid for each geometry
nyc_map['centroid'] = nyc_map.geometry.centroid

然后,我可以使用松鼠目击的坐标范围来过滤 nyc_map。

# Filter based on centroid's longitude and latitude
filtered_nyc_map = nyc_map[(nyc_map['centroid'].x >= -74.0) &
                           (nyc_map['centroid'].x <= -73.94) &
                           (nyc_map['centroid'].y >= 40.75) &
                           (nyc_map['centroid'].y <= 40.82)]

现在我们可以绘制过滤后的纽约市地图,如果我们做对了的话,它应该以中央公园为中心。

# Plot the filtered DataFrame
filtered_nyc_map.plot(figsize=(10, 10), aspect='auto')

结果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

纽约中央公园的地理空间准确图。图片由作者提供。

成功了!不过,我们还有一件事要做。我想在这个图上包括按集群着色的松鼠目击情况,并根据松鼠的接近程度进行强调。我们可以使用下面的代码来实现:

# Convert the 'X' and 'Y' columns into a GeoSeries of Points
points = gpd.GeoSeries([Point(xy) for xy in zip(squirrel_df['X'], squirrel_df['Y'])])

# Create a new GeoDataFrame
squirrel_geo_df = gpd.GeoDataFrame(squirrel_df, geometry=points)

# Plotting the filtered NYC map
fig, ax = plt.subplots(figsize=(10, 10))

filtered_nyc_map.plot(ax=ax, color='#207388')  # Base map in light grey

# Overlay the squirrel data points, color-coded by 'Cluster'
squirrel_geo_df.plot(ax=ax, column='Cluster', categorical=True, markersize=20,
                     cmap='viridis', legend=True, legend_kwds={'title':'Cluster', 'loc': 'upper left'},
                     facecolors='none' if True else 'black', edgecolors='black')

# Adding labels and title
ax.set_xlabel('Longitude')
ax.set_ylabel('Latitude')
ax.set_title('Squirrel Sightings in Central Park by Cluster')
plt.show()

这个结果如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

按集群着色的松鼠目击情况,在地理空间准确的纽约中央公园地图上。图片由作者提供。

这真不错!我对这个结果非常满意。我还想做一个可视化。我找到了中央公园的一张非常酷的航拍照片。我认为将松鼠目击情况覆盖到航拍视图上会很有趣,但是,有一些其他效果使得绘图变得复杂,所以我决定将这个任务留到以后再做。

特征工程

正如我在介绍中提到的,这个数据集有很多特征工程的机会。我创建的大多数新特征都是从文本列派生出来的。这些文本列包含了志愿者们对每次松鼠目击的额外描述。我识别出了一些关键类别和与每个类别相关的术语,并结合正则表达式创建了如下的新特征:

# Define lists of terms for various features
tree_terms = ['tree', 'trees', 'maple', 'log', 'branch', 'oak', 'willow', 'trunk', 'stump', 'elm']
shrubbery_terms = ['shrub', 'bush', 'weeds']
rock_terms = ['rock', 'stone']
grassland_terms = ['lawn', 'grass', 'field', 'glen']
path_terms = ['path', 'road', 'pavement', 'street']
structure_terms = ['conservatory', 'fountain', 'bench', 'fence', 'statue', 'bin', 'carousel', 'trellis',
                  'structure', 'car', 'table', 'ledge', 'railing', 'overpass', 'post', 'can', 'house',
                  'arch', 'bar', 'sanctuary', 'bridge', 'bike', 'rack', 'construction', 'playground']
water_terms = ['water', 'shore', 'pond', 'pool', 'stream']

# Function to create a matching column based on specified terms
def create_matching_column(df, column_name, source_column, terms):
    pattern = '|'.join(terms)
    df[column_name] = df[source_column].str.contains(pattern, case=False, na=False)
    return df

在上面的例子中,我能够创建出像‘树上出现’、‘灌木丛中出现’和‘岩石上出现’等新特征。除此之外,一些列如‘主要毛色’和‘突出毛色’等被进行了独热编码,因为它们有几个类别(例如,主要毛色包括灰色、黑色、肉桂色和未知)。还有其他特征,如果你对查看完整的 EDA 报告以及查看创建的所有特征感兴趣,可以查看这里的报告。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

预处理数据集的各种特征的依赖图。图片由作者提供。

我创建了一个管道来进行所有必要的清理、格式化等,这是我在处理数据集后决定必要的。你可以查看我在文章开头链接的笔记本中的所有工作。值得一提的几点是:

  • 我们现在有 44 个特征(原始数据集有 31 个)。

  • 没有删除任何观察值,因此我们仍然有 3023 行。

  • 现在没有缺失数据了。没有进行插补或删除行。大部分缺失数据出现在文本列中。这些列在用于特征工程后被删除。在像 Primary Fur Color 或 Age 这样的列中,我简单地将 NaNs 视为一个名为“未知”的新类别。

下面的相关图并没有特别有用。它主要揭示了我已经预料到的相关性/关系。例如,大多数强反相关是与 Primary Fur Color(即,灰色与肉桂色)或 Squirrel Age(即,成人与幼年)的变量之间的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

预处理数据集中特征的皮尔逊相关图。图像由作者提供。

此时,我想查看像主成分分析(PCA)和 t-分布随机邻域嵌入(tSNE)这样的降维技术,以尝试识别数据中的附加模式/簇,并评估哪些特征具有最重要的权重。tSNE 图并没有特别揭示出什么,尽管我可以在数据中看到一些分组。我也进行了 PCA,加载图和解释方差图在告诉我 30 个特征占了大约 90%的方差方面非常有用,还可以看到像 Cluster、PFC_Gray、X、Y、PFC_Cinnamon 和 HFC_Gray 这样的特征是数据集中一些最强的成分。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

降维技术和处理过的松鼠数据分析。图像由作者提供。

训练模型

从我决定采取机器学习方法的那一刻起,我就知道我只会考虑两个算法:决策树和随机森林。原因是……松鼠喜欢树,这个笑话是我决定做这个项目的主要原因。虽然其他算法可能表现更好,但它们的选择就没那么有趣了。如果你有任何有趣的建议,请告诉我 😃

无论如何,接下来我们开始建模。首先,这是我目前的数据框的视图。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

清理、工程化和预处理的松鼠普查数据集。图像由作者提供。

首先,我们需要创建我们的训练集和测试集。在进行拆分时需要记住的一点是,我们必须使用目标变量进行分层拆分,因为数据集严重不平衡。分层拆分将保留每个类别的样本百分比。

# Assuming squirrel_df is your DataFrame and 'Approaches' is the target variable
X = squirrel_df.drop('Approaches', axis=1)  # Features
y = squirrel_df['Approaches']  # Target

# Stratify the split to maintain the class distribution in train and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)

鉴于数据集的不平衡性,我考虑使用数据增强技术。然而,我将这些内容留待未来的迭代,因为引入合成样本可能会带来其他复杂情况,而且根据我的经验,这些样本通常泛化效果不佳。

在此期间,我将通过在训练模型时使用平衡权重来处理不平衡问题。关于指标,我将关注接收者操作特征曲线下面积(ROC-AUC)得分和精确度召回曲线下面积(PR-AUC)得分。特别是 PR-AUC 得分在不平衡分类问题中很有用,因为它关注少数类,而 ROC 曲线则涵盖了两个类别。

我对决策树和随机森林模型进行了网格搜索。我使用了 5 折分层交叉验证,因为我想在分割间保留类别百分比。我还使用了 PR-AUC 和 ROC-AUC 作为超参数调优过程中的优化指标。对于随机森林,我查看了 n_estimators、max_depth、min_samples_split 和 min_samples_leaf,使用了保守的数值范围。随机森林超参数调优的代码如下所示:

from sklearn.model_selection import GridSearchCV, StratifiedKFold
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import make_scorer, average_precision_score, roc_auc_score

# Define the parameter grid for Random Forest
rf_param_grid = {
    'n_estimators': [50, 100, 200, 500],  # Number of trees in the forest
    'max_depth': [None, 5, 10, 20],  # Maximum depth of the tree
    'min_samples_split': [2, 5, 7, 10],  # Minimum number of samples required to split a node
    'min_samples_leaf': [1, 3, 5, 10]  # Minimum number of samples required at each leaf node
}

# Initialize Stratified K-Fold
stratified_kfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

# Define multiple scoring metrics
scoring_metrics = {
    'PR-AUC': make_scorer(average_precision_score, needs_proba=True),
    'ROC-AUC': 'roc_auc'
}

# Initialize the GridSearchCV object with Stratified K-Fold
rf_grid_search = GridSearchCV(
    estimator=RandomForestClassifier(random_state=42, class_weight='balanced'),
    param_grid=rf_param_grid,
    cv=stratified_kfold,  # Using Stratified K-Fold here
    scoring=scoring_metrics,
    refit='PR-AUC',
    n_jobs=-1,
    verbose=2
)

rf_grid_search.fit(X_train, y_train)

# Best parameters and scores
print("Best parameters for Random Forest:", rf_grid_search.best_params_)
print("Best PR-AUC score:", rf_grid_search.best_score_)

# To access the results for each metric
cv_results = rf_grid_search.cv_results_

# Find the index of the best score based on the refit criterion
best_index = rf_grid_search.best_index_

# Display the best hyperparameters and corresponding scores
print("Best Hyperparameters:", rf_grid_search.best_params_)
print(f"Best Mean Test PR-AUC: {cv_results['mean_test_PR-AUC'][best_index]:.4f}")
print(f"Best Mean Test ROC-AUC: {cv_results['mean_test_ROC-AUC'][best_index]:.4f}")

对于决策树,我也使用了小范围的 max_depth、min_samples_split 和 min_samples_leaf 值。决策树超参数调优的代码如下所示:

from sklearn.model_selection import GridSearchCV, StratifiedKFold
from sklearn.tree  import DecisionTreeClassifier
from sklearn.metrics import make_scorer, average_precision_score, roc_auc_score

# Define the parameter grid for Decision Tree
dt_param_grid = {
    'max_depth': [10, 20, None],  # Maximum depth of the tree
    'min_samples_split': [2, 5],  # Minimum number of samples required to split a node
    'min_samples_leaf': [1, 2]  # Minimum number of samples required at each leaf node
}

# Initialize Stratified K-Fold
stratified_kfold = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

# Define multiple scoring metrics
scoring_metrics = {
    'PR-AUC': make_scorer(average_precision_score, needs_proba=True),
    'ROC-AUC': 'roc_auc'
}

# Initialize the GridSearchCV object with Stratified K-Fold
dt_grid_search  = GridSearchCV(
    estimator=DecisionTreeClassifier(random_state=42, class_weight='balanced'),
    param_grid=dt_param_grid,
    cv=stratified_kfold,  # Using Stratified K-Fold here
    scoring=scoring_metrics,
    refit='PR-AUC',
    n_jobs=-1,
    verbose=2
)

dt_grid_search.fit(X_train, y_train)

# Best parameters and scores
print("Best parameters for Decision Tree:", dt_grid_search.best_params_)
print("Best PR-AUC score:", dt_grid_search.best_score_)

# To access the results for each metric
dt_cv_results = dt_grid_search.cv_results_

# Find the index of the best score based on the refit criterion
best_index = dt_grid_search.best_index_

# Display the best hyperparameters and corresponding scores
print("Best Hyperparameters:", dt_grid_search.best_params_)
print(f"Best Mean Test PR-AUC: {dt_cv_results['mean_test_PR-AUC'][best_index]:.4f}")
print(f"Best Mean Test ROC-AUC: {dt_cv_results['mean_test_ROC-AUC'][best_index]:.4f}")

评估和解释模型

在评估最佳随机森林和决策树模型在高度不平衡数据集上的表现时,我观察到了 ROC-AUC 和 PR-AUC 评分的明显差异。随机森林模型以 0.91 的 ROC-AUC 得分表现优异,显著超过了决策树 0.77 的体面得分。这表明其在区分类别方面的能力更强。然而,由于 PR-AUC 评分在不平衡数据集的背景下关注精确度与召回率的平衡,结果却有所不同。两个模型的 PR-AUC 得分都超过了‘无技能’分类器的基线表现(该分类器在所有情况下都预测主要类别),但在精确度和召回率上仍有改进空间:随机森林的 PR-AUC 得分为 0.46,而决策树仅为 0.20。这些结果强调了在不平衡场景下将 PR-AUC 作为目标指标的重要性,并建议了未来改进的方向,如尝试不同的类别不平衡缓解技术、调整决策阈值或探索更适合不平衡数据的算法。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

随机森林和决策树模型在松鼠普查数据上的训练和超参数调优结果。图片由作者提供。

特征重要性分析还显示,纬度、经度和 Indifferent 是对模型预测贡献最大的主要特征。这一点通过两种模型的 SHAP 值图进一步得到了支持。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

随机森林和决策树模型在松鼠普查数据集上的 SHAP 值。图片由作者提供。

随机森林和决策树模型的概率分布如下所示。直方图显示了预测概率的分布,峰值在 0.1 到 0.2 之间,并有一个长尾延伸至 0.8。这表明随机森林模型倾向于预测许多实例为低概率正类,但存在不确定性,因为它没有在 0 或 1 附近达到峰值。较高概率的尾部存在,表明模型对某些预测有一定的信心,但总体上预测较为谨慎,因为概率并未集中在 0 或 1 附近。决策树直方图显示了在概率 0 处极为集中的分布,以及很少有概率为 1 的实例。这表明模型对大多数实例属于负类非常自信。缺乏中间概率的实例表明决策树模型的预测非常确定——它要么非常确信一个实例是正类,要么非常确信它不是。这是决策树的典型特征,因为它们往往会产生更极端的概率估计。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

随机森林和决策树模型在松鼠普查数据上的概率分布。图片由作者提供。

随机森林模型可能会更好地进行校准,因为它提供了更平滑的概率分布,并不像决策树那样趋向极端。决策树的极端自信可能是过拟合的迹象,即模型过于精确地学习了训练数据,包括噪声,从而导致过于自信的预测。两个模型都没有显示出理想的双峰分布,这会表明在区分类别方面的高自信。这可能表明模型没有完美地捕捉到潜在模式,可能需要进一步调优、添加特征或采用更复杂的建模技术。我当然可以多尝试一些特征,但我会将其留到将来的迭代中。因此,我决定对表现更好的随机森林模型进行概率校准是一个好的下一步。

校准模型

校准机器学习模型是调整预测概率以更好地与数据中的观测频率对齐的过程。这是一个在训练模型时常常被忽视的步骤,限制了其可用性。校准模型很重要,因为机器学习模型的原始估计可能无法表示事件的真实可能性。如果你的模型用于任何类型的决策过程(特别是那些涉及高风险的),这一点尤为关键。此外,如果你的概率经过良好校准,这也可以使不同模型之间的比较更具意义。

为了进行校准,我使用了 Platt Scaling(也称为 Sigmoid 校准)。该方法将逻辑回归模型拟合到分类器的输出。基本工作流程如下:

  1. 训练模型

  2. 划分数据

  3. 拟合校准模型

  4. 评估校准

该过程的代码如下所示。

from sklearn.calibration import CalibratedClassifierCV
from sklearn.model_selection import train_test_split
from sklearn.metrics import brier_score_loss
from sklearn.calibration import calibration_curve
import matplotlib.pyplot as plt

# Split your data into training and calibration sets
X_train_2, X_calib, y_train_2, y_calib = train_test_split(X_train, y_train, test_size=0.2)

# Fit the model on the training data
best_rf_classifier.fit(X_train_2, y_train_2)

# Calibrate the model on the calibration data
# Using Platt scaling (method='sigmoid') or Isotonic regression (method='isotonic')
calibrated_rf  = CalibratedClassifierCV(estimator=best_rf_classifier, method='isotonic', cv='prefit')
calibrated_rf.fit(X_calib, y_calib)

# Now you can use calibrated_model to make predictions with calibrated probabilities
calibrated_probs = calibrated_rf.predict_proba(X_test)

# Predict probabilities on the test set
brier_probs = calibrated_rf.predict_proba(X_test)[:, 1]

# Evaluate calibration performance
brier_score = brier_score_loss(y_test, brier_probs)
print(f"Brier score: {brier_score:.4f}")

# Get the calibrated probabilities for the positive class
prob_true, prob_pred = calibration_curve(y_test, calibrated_probs[:, 1], n_bins=20)

校准结果如下所示。一个完全校准的模型应将所有点精确地位于橙色曲线之上。总体而言,概率校准较好,唯一的显著例外是在 ~ 0.25 处,模型明显低估了概率。此外,我还计算了校准过程中的 Brier 分数。Brier 分数范围在 0 和 1 之间,0 代表完美准确,1 代表完全不准确。此次校准的 Brier 分数为 0.0445,比较低,因此表现良好。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用 Platt scaling 对随机森林模型进行概率校准。图片来源于作者。

尽管我可以对这一过程进行进一步改进,但这将是我在未来的迭代中精细化的内容。目前,我会简单地保存模型并继续。

import joblib

# Save the model to a file
joblib.dump(calibrated_rf, '/content/drive/MyDrive/SquirrelML/cal_Squirrel_RF.pkl')

将模型部署为 Streamlit 应用

尽管有很多机器学习模型被创建,但遗憾的是,很多都被束之高阁。我认为,无论你在进行 ML 项目时都应有一种方式,使你的模型能够被部署并被人们使用。这样,即使只是像我这里做的那种简单应用,你的工作也有机会得到认可,发挥更大的作用。我发现人们通常会更记得你的工作,如果他们能以某种方式与之互动(这对于求职时尤其有用)。将模型部署为 Streamlit 应用是一种很好地展示你工作的方式。

这个应用的基本前提是给用户提供各种功能的操作体验,用于训练模型并输出一个松鼠接近他们的概率。模型的输入之一是用户的经纬度。用户可以直接在应用中输入这些数据,不过,我还使用了 Folium 来包含一个交互式地图,初始时中心定位在中央公园,并且当你点击地图上的位置时,会显示该位置的经纬度。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

SquirrelML Streamlit 应用,用于预测中央公园松鼠的接近情况。图像由作者提供。

用户可以在下拉菜单中尝试不同的功能,一旦他们配置好所需的功能,可以点击预测按钮。点击预测按钮将返回松鼠接近概率(SAP)以及一张附带的图片(如果概率超过 50%,则会是一只开心的松鼠;如果概率等于或低于 50%,则会是一只伤心的松鼠图片)。你可以在下面看到一个示例输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

结论

这是一个有趣的小项目。模型当然可以改进,我可能会在未来重新审视以进一步优化。例如,我可以尝试将我的 PCA 分析结果与 SHAP 值结合使用,尝试将数据集的维度降至核心特征。还有一些与松鼠普查相关的数据集,包含了如垃圾量、活动水平、观察时长和天气条件等信息。我在处理完数据后才发现这些数据集,但我很希望将它们整合到我的模型中,因为它们似乎很有用。这些数据集可以在 NYCOpenData 的这里这里找到。我还可以尝试使用数据增强技术。

一如既往,希望你喜欢我的工作,谢谢阅读!

来自 NYCOpenData 的所有数据在使用上没有限制。请查看以下链接了解其常见问题使用条款

Stable Diffusion 作为 API:创建一个去除人物的微服务

原文:towardsdatascience.com/stable-diffusion-as-an-api-5e381aec1f6

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用 Stable Diffusion 2 生成的风景图像(作者提供)。

使用 Stable Diffusion 微服务从照片中去除人物

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Mason McGough

·发表在 Towards Data Science ·阅读时间 12 分钟·2023 年 2 月 4 日

概述

Stable Diffusion 是一个前沿的开源工具,用于从文本生成图像。Stable Diffusion Web UI 通过 API 以及交互式用户界面提供了许多这些功能。我们将首先介绍如何使用这个 API,然后设置一个示例,将其作为一个隐私保护微服务来从图像中去除人物。

生成 AI 介绍

去年,基于机器学习的数据生成器出现了许多创新,2022 年或许可以称为“生成 AI 的年度”。我们迎来了 DALL-E 2,这是 OpenAI 的文本到图像生成模型,生成了令人惊叹的现实主义图像,如宇航员骑马和穿着人类衣物的狗。 GitHub Copilot,这个强大的代码补全工具可以自动完成语句、编写文档,并根据单个评论实现完整的功能,已经作为订阅服务公开发布。我们还看到了 Dream FieldsDream FusionMagic3D,一系列能够仅通过文本生成纹理化 3D 模型的突破性模型。最后但同样重要的是我们迎来了 ChatGPT,这个前沿的 AI 聊天机器人如今无需介绍。

这个列表几乎只是触及了表面。在像 DALL-E 2 这样的生成图像模型世界中,我们还有MidjourneyGoogle ImagenStarryAIWOMBO DreamNightCafeInvokeAILexica ApertureDream StudioDeforum……我想你已经明白了画面。😉 📷 说生成 AI 已经捕捉了整个世界的想象力,似乎并不夸张。

稳定扩散

尽管许多流行的生成 AI 工具如 ChatGPT、GitHub Copilot 和 DALL-E 2 是专有的且需付费的,但开源社区并没有停下脚步。去年,LMU 慕尼黑大学、Runway 和 Stability AI 合作公开分享了Stable Diffusion,这是一个强大但高效的文本到图像模型,足够高效以在消费级硬件上运行。这意味着任何拥有一台不错 GPU 和互联网连接的人都可以下载 Stable Diffusion 代码和模型权重,将低成本图像生成带给世界。

稳定扩散网络界面

Stable Diffusion Web UI是利用 Stable Diffusion 的最受欢迎工具之一,它在基于浏览器的用户界面中暴露了 Stable Diffusion 的广泛设置和功能。该项目一个鲜为人知的功能是,你可以将其作为 HTTP API 使用,允许你从自己的应用程序中请求图像。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Stable Diffusion Web UI 的示例生成(作者提供的照片)。

它具有大量功能,如修补、扩展、调整大小、放大、变体等。 项目 wiki 提供了所有功能的详细概述。此外,它还提供了扩展性脚本。

设置

在开始之前,请确保你的系统中有一个 GPU(最好是 NVIDIA,但 AMD 也支持),且至少有 8GB 的显存。这将确保你可以将模型加载到内存中。接下来,你需要将代码库克隆到你的系统中(例如通过 HTTPS):

git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.git

根据你的系统遵循安装说明,因为这些可能与你的有所不同。我使用的是 Ubuntu 18.04 进行设置,但它也应该适用于 Windows 和 Apple Silicon。这些说明将包括设置 Python 环境,因此请确保在稍后启动服务器时,所设置的环境处于活动状态。

完成后,我们需要一份模型权重的副本。我使用的是 Stable Diffusion 2.0,但现在也有 Stable Diffusion 2.1 可用。无论你选择哪个选项,确保下载 stablediffusion 仓库的权重。最后,将这些权重复制到 models/Stable-diffusion 文件夹中,如下所示:

cp 768-v-ema.ckpt models/Stable-diffusion

现在你应该准备好开始生成图像了!要启动服务器,请在根目录下执行以下操作(确保你设置的环境已激活):

python launch.py

服务器需要一些时间来设置,因为它可能需要安装要求、将模型权重加载到内存中、检查嵌入等。准备好后,你应该会在终端中看到类似下面的消息:

Running on local URL:  http://127.0.0.1:7860

这个用户界面是基于浏览器的,因此在你喜欢的网页浏览器中导航到 “127.0.0.1:7860” 。如果它正常工作,它应该看起来像这样:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

稳定扩散 Web 用户界面首次打开时(作者拍摄)。

使用

现在你应该准备好生成一些图像了!进入“提示”字段中输入文本并点击“生成”以生成一些内容。如果这是你第一次使用这个用户界面,花点时间探索并了解其一些功能和设置。如果有任何问题,请参考 维基。这些知识在设计你的 API 时会很有用。

我不会过于深入地讲解如何使用网页用户界面,因为之前有很多人已经做了。不过,我会提供以下基本设置的备忘单以供参考。

  • 采样方法:采样算法。这会极大地影响生成图像的内容和整体外观。不同方法的执行时间和结果可能会有很大差异。理想情况下,首先尝试这个选项。

  • 采样步骤:图像生成过程中的去噪步骤数。某些结果会随着步骤数量的变化而剧烈改变,而其他结果则会很快出现边际效益递减。大多数采样器理想的步骤数为 20–50。

  • 宽度高度:输出图像的尺寸。对于 SD 2.0,768x768 是首选分辨率。分辨率会影响生成的内容。

  • CFG 比例无分类器引导 (CFG) 比例。增加这个比例会增加图像受到提示的影响。较低的值会产生更具创意的结果。

  • 去噪强度:确定允许原始图像的变化程度。值为 0.0 时没有变化。值为 1.0 时完全忽略原始图像。一般来说,从 0.4–0.6 的值开始是一个安全的选择。

  • 种子:随机种子值。当你想比较设置效果而尽可能减少变化时,这很有用。如果你喜欢某个特定生成但想稍作修改,请复制种子。

将 Stable Diffusion 作为 API 使用

网络用户界面旨在供单个用户使用,并且作为一个互动艺术工具制作自己的创作非常好。然而,如果我们想要使用它作为引擎来构建应用程序,我们将需要一个 API。stable-diffusion-webui 项目的一个鲜为人知(且文档较少)的功能是它还具有内置的 API。网络用户界面是用 Gradio 构建的,但也有一个可以用以下命令启动的 FastAPI 应用:

python launch.py --api

这为我们提供了一个 API,暴露了我们在网络用户界面中拥有的许多功能。我们可以发送带有提示和参数的 POST 请求,并接收包含输出图像的响应。

创建一个微服务

作为示例,我们现在将设置一个简单的微服务,用于从照片中移除人物。这有很多应用,例如保护个人隐私。我们可以使用稳定扩散作为一种原始的隐私保护过滤器,它可以从照片中去除人物而没有任何难看的马赛克或像素块。

请注意,这只是一个基本设置;它不包括加密、负载均衡、多租户、RBAC 或任何其他功能。这个设置可能不适合生产环境,但可以用于在家庭或私人服务器上设置应用程序。

以 API 模式启动应用程序

以下说明将使用 API 模式运行服务器,因此请先通过 CTRL+C 停止网络用户界面(web UI)。然后使用 --api 选项以 API 模式重新启动它:

python launch.py --api

当服务器准备好时,它应该会打印出类似以下内容的信息:

INFO:     Uvicorn running on http://127.0.0.1:7860 (Press CTRL+C to quit)

运行服务器而不带用户界面的无头状态也可能很有用。要仅启用 API 而不使用 Gradio 应用:

python launch.py --nowebui

向 API 发送请求

我们首先要做的是演示如何向 API 发出请求。我们希望向应用程序的 txt2img(即“文本到图像”)API 发送 POST 请求,以简单地生成一张图像。

我们将使用 requests 包,因此如果你还没有安装,请先安装它:

pip install requests

我们可以发送一个包含简单字符串的提示的请求。服务器将返回一个 base64 编码的 PNG 文件,我们需要对其进行解码。要解码 base64 图像,我们只需使用 base64.b64decode(b64_image)。以下脚本应该可以测试这个功能:

import json
import base64

import requests

def submit_post(url: str, data: dict):
    """
    Submit a POST request to the given URL with the given data.
    """
    return requests.post(url, data=json.dumps(data))

def save_encoded_image(b64_image: str, output_path: str):
    """
    Save the given image to the given output path.
    """
    with open(output_path, "wb") as image_file:
        image_file.write(base64.b64decode(b64_image))

if __name__ == '__main__':
    txt2img_url = 'http://127.0.0.1:7860/sdapi/v1/txt2img'
    data = {'prompt': 'a dog wearing a hat'}
    response = submit_post(txt2img_url, data)
    save_encoded_image(response.json()['images'][0], 'dog.png')

将内容复制到一个文件中,并将其命名为 sample-request.py。现在使用以下命令执行它:

python sample-request.py

如果一切正常,它应该会将图像保存为文件 dog.png。我的图像看起来像这样这位衣着光鲜的家伙:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用‘sample-request.py’创建的图像(作者拍摄的照片)。

请记住,您的结果可能与我的不同。如果遇到问题,请仔细检查运行稳定扩散应用的终端输出。可能是服务器尚未完成设置。如果遇到“404 Not Found”的问题,请仔细检查 URL 是否正确输入,并指向正确的地址(例如 127.0.0.1)。

图像掩膜。

如果到目前为止一切正常,那就太好了!但是我们如何使用它来修改已有的图像呢?为此,我们将使用 img2img(即“图像到图像”)API。该 API 使用稳定扩散来修改您提交的图像。我们将使用 修复功能:给定图像和掩膜,修复技术将尝试用稳定扩散生成的内容替换图像中被掩膜遮挡的部分。掩膜作为权重,平滑地插值于原始图像和生成内容之间,将两者融合在一起。

我们将尝试使用许多可用的预训练计算机视觉模型之一生成掩膜,而不是手动制作掩膜。我们将使用模型输出中的“person”类别来生成掩膜。虽然对象检测模型也可以,但我选择使用分割模型,以便您可以尝试使用密集掩膜或边界框。

我们需要一个示例图像进行测试。我们可以从互联网上下载一个,但为了保护隐私(和版权),为什么不使用稳定扩散生成一个呢?以下是我使用提示词“美丽的山地风景,一个女人背对镜头走开”生成的图像。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

稳定扩散生成的图像(照片作者提供)。

您可以下载这个,但我鼓励您尝试自己生成一个。当然,您也可以使用真实照片。以下是将 torchvision 的库存分割模型应用于此图像作为掩膜的最小代码。

import torch
from torchvision.models.segmentation import fcn_resnet50, FCN_ResNet50_Weights
from torchvision.io.image import read_image
from torchvision.utils import draw_segmentation_masks
import matplotlib.pyplot as plt

if __name__ == '__main__':
    img_path = 'woman-on-trail.png'

    # Load model
    weights = FCN_ResNet50_Weights.DEFAULT
    model = fcn_resnet50(weights=weights, progress=False)
    model = model.eval()

    # Load image
    img = read_image(img_path)

    # Run model
    input_tform = weights.transforms(resize_size=None)
    batch = torch.stack([input_tform(img)])
    output = model(batch)['out']

    # Apply softmax to outputs
    sem_class_to_idx = {cls: idx for (idx, cls) in enumerate(weights.meta['categories'])}
    normalized_mask = torch.nn.functional.softmax(output, dim=1)

    # Show results
    class_idx = 1
    binary_masks = (normalized_mask.argmax(class_idx) == sem_class_to_idx['person'])
    img_masked = draw_segmentation_masks(img, masks=binary_masks, alpha=0.7)
    plt.imshow(img_masked.permute(1, 2, 0).numpy())
    plt.show()

像之前一样,将其复制到名为 segment-person.py 的文件中。使用以下命令执行代码:

python segment-person.py

结果预测应该类似于这个:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

应用到图像上的分割掩膜结果(照片作者提供)。

现在我们有了向 API 发出请求和预测边界框的工具。现在可以开始构建我们的微服务了。

人物移除微服务。

现在让我们转到实际示例:从图像中移除人物。微服务应执行以下操作:

  1. 读取一些输入参数。

  2. 从文件中加载图像。

  3. 将“person”类别的分割模型应用于图像以创建掩膜。

  4. 将图像和掩膜转换为 base64 编码。

  5. 发送一个请求,包含 base64 编码的图像、base64 编码的掩膜、提示词和任何参数到本地服务器的 img2img API。

  6. 解码并将输出图像保存为文件。

由于我们已经单独涵盖了所有这些步骤,微服务已经为您在 这个 GitHub Gist 中实现了。现在下载脚本并使用以下命令在“woman-on-trail.png”(或您喜欢的任何图片)上执行:

python inpaint-person.py woman-on-trail.png -W 1152 -H 768

-W-H 分别表示所需的输出宽度和高度。它将生成的图像保存为 inpaint-person.png,对应的掩模保存为 mask_inpaint-person.png。您的输出会有所不同,但这是我收到的结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用原始分割掩模的 API 调用结果(图像由作者提供)。

嗯,这不是我们想要的。似乎很多人的轮廓仍然存在,特别是剪影。我们可能需要掩盖更大的区域。为此,我们可以尝试将掩模转换为边界框。我们可以使用-B标志来完成这一操作。

python inpaint-person.py woman-on-trail.png -W 1152 -H 768 -B

我收到的输出是:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用边界框作为掩模的 API 调用结果(照片由作者提供)。

这也不完全正确!混凝土柱子不是我们期望在小径中间找到的东西。也许引入一个提示会有助于引导方向。我们使用-p标志将提示“山地风景,景观,小径”添加到请求中。我们还使用-D 32扩展边界框,以去除一些边缘效果,并使用-b 16模糊边界框,使掩模与背景稍微融合。

python inpaint-person.py woman-on-trail.png \
    -W 1152 -H 768 \
    -b 16 -B -D 32 \
    -p "mountain scenery, landscape, trail"

使用这个我得到了以下输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

最终 API 调用的结果(照片由作者提供)。

现在看起来很有可能!继续尝试不同的图片、设置和提示,以便适应您的用例。要查看该脚本可用的完整参数和提示列表,请输入 python inpaint-person.py -h

讨论

您的图片很可能与上述图片差别很大。因为这是一个本质上随机的过程,即使使用相同设置的稳定扩散也会产生截然不同的结果。理解所有特性和正确的提示设计有相当陡峭的学习曲线,即便如此,结果也可能很挑剔。让一张图片完全符合您的喜好极其困难,需要经过大量的试验和错误。

为了帮助您,请记住以下提示:

  • 在转到 API 之前,使用 Web UI 找到适合您用例的正确参数。

  • 在将图像微调到您喜欢的状态时,依赖于提示矩阵和 X/Y 图特性。这些将帮助您快速探索参数搜索空间。

  • 请注意种子。如果您喜欢特定的输出但想要对其进行迭代,请复制种子。

  • 尝试使用不同的生成器,如 Midjourney!每个工具略有不同。

  • 使用互联网资源,如 Lexica 来获取灵感并寻找好的提示。

  • 在设置菜单中使用“在每张图片旁边创建一个文本文件,并记录生成参数”选项,以跟踪你用来制作每张图片的提示和设置。

最重要的是,要玩得开心!

稳定扩散:掌握室内设计的艺术

原文:towardsdatascience.com/stable-diffusion-mastering-the-art-of-interior-design-9fb4214544b0

深入探索稳定扩散及其室内设计的修复变体

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Rafael Guedes

·发布于Towards Data Science ·阅读时间 9 分钟·2023 年 12 月 18 日

在我们生活的这个快节奏的世界中以及经历了疫情后,我们中的许多人意识到,拥有一个舒适的家作为逃避现实的避风港是无价的,值得追求的目标。

无论你是寻找斯堪的纳维亚风格、极简风格,还是华丽风格来装饰你的家,很难想象每件物品如何在充满不同件数和颜色的空间中相互配合。因此,我们通常寻求专业帮助,以创建那些令人惊叹的 3D 图像,帮助我们理解未来的家会是什么样子。

然而,这些 3D 图像费用昂贵,如果我们的初步构思不如预期好,获取新图像将需要时间和更多的金钱,这些在当今社会都很稀缺。

在这篇文章中,我探讨了稳定扩散模型,首先简要解释了它是什么、如何训练以及需要什么来适应修复。最后,我以对我未来家的 3D 图像的应用结束文章,我将厨房岛和橱柜的颜色和材料更换为不同的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1:室内设计 (source)

一如既往,代码可在Github上找到。

稳定扩散

这是什么?

稳定扩散[1]是 CompVis Group 于 2022 年发布的生成性 AI 模型,可以根据文本和图像提示生成逼真的图像。它主要设计用于生成受文本描述影响的图像,但也可以用于其他任务,如修复或视频创建。

它的成功源于 感知图像压缩 步骤,该步骤将高维图像转换为更小的潜在空间。这种压缩使得在资源有限的机器上使用该模型成为可能,使每个人都能使用,这在之前的最先进模型中是不可能的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2:Stable Diffusion 架构 (source)

它是如何学习的?

Stable Diffusion 是一个 潜在扩散模型 (LDM),具有三个主要组件 (变分自编码器 (VAE) [2]、U-Net [3] 和一个可选的 文本编码器),它学习如何在提示(文本或其他图像)的条件下去噪图像,以创建新图像。

Stable Diffusion 的训练过程有 5 个主要步骤:

  1. 感知图像压缩 步骤包括一个 编码器,它接收一个尺寸为 512x512x3 的图像,并将其编码为一个尺寸为 64x64x4 的更小的潜在空间 Z。为了更好地保留图像的细节(例如,人脸中的眼睛),潜在空间 Z 使用低权重的 Kullback-Leibler 项进行正则化,以使其零中心并获得小方差。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3:感知图像压缩过程,其中编码器将 512x512x3 的图像转换为 64x64x4 的潜在空间(图像由作者制作)。

2. 扩散过程 负责逐步向潜在空间 Z 添加高斯噪声,直到所有剩下的是随机噪声,生成一个新的潜在空间 Zt. t 是扩散过程发生的次数,以实现完全的噪声潜在空间。

这一步骤很重要,因为 Stable Diffusion 必须学习如何从噪声恢复到原始图像,正如我们将在下一步中看到的那样。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4:扩散过程,其中高斯噪声逐渐添加到潜在空间中(图像由作者制作)

3. 去噪 过程 训练了一个 U-Net 架构来估计潜在空间 Zt 中的噪声量,以便减去它并恢复 Z。这个过程通过逐渐去噪 Zt 来恢复原始的潜在空间 Z,基本上是扩散过程的逆过程。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 5:去噪过程,其中 U-Net 预测潜在空间中的噪声并将其去除,直到完全恢复原始潜在空间(图像由作者制作)

4. 在 去噪 过程 中,可以将提示,通常是文本和/或其他图像,连接到潜在空间 Zt。这种连接将条件化去噪过程,从而允许创建新图像。作者在 U-Net 的骨干网络中添加了交叉注意机制来处理这些提示,因为它们对学习各种输入类型的基于注意的模型是有效的。

当涉及到文本时,模型使用训练好的文本编码器CLIP [4],该编码器将提示编码为一个 768 维的向量,然后将其与Zt连接,并作为输入传递给 U-Net。

正如我们在图 6 中所见,我们将文本提示*“移除灯”Zt进行连接,这使得 Diffusion Process 对Zt进行调整,去除了原始Zt*中靠近椅子的灯。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6:使用文本提示来去除原始图像中的灯光的去噪过程(图片由作者制作)

5. 最终,解码器接收去噪后的潜在空间Z作为输入,并学习如何估计用于将图像编码到更小潜在空间中的每个组件的方差。在估计方差后,解码器可以生成与原始图像相同尺寸的新图像。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 7:解码器恢复了没有灯的原始图像,尺寸为 512x512x3(图片由作者制作)

Stable Diffusion 的修补变体

修补任务是用新内容填补图像中被遮蔽的区域,无论是因为我们想要修复图像还是因为我们想要替换一些不希望出现的内容。

Stable Diffusion 可以被训练生成基于图像、文本提示和掩膜的新图像。这种模型已经在 HuggingFace 🤗 上可用,名称为runwayml/stable-diffusion-inpainting

为了训练 Stable Diffusion 进行修补,我们需要经历上述部分提到的相同步骤,但输入数据稍有不同。在这种情况下,除了原始图像和文本,我们还需要一个掩膜(另一张图像)。为此,U-Net 需要适应以接收额外的掩膜输入通道。

在训练过程中,掩膜下的区域保持不变,只对其进行编码到潜在空间,而掩膜区域则经历整个编码、扩散和去噪过程。这样,Stable Diffusion 模型知道哪些区域应该保持不变,哪些区域应该发生变化(图 8 展示了这一过程)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8:修补扩散 Stable 模型的训练过程(图片由作者制作)

在图 9 中,我们展示了在自己的使用案例中进行修补所需的示例。我们提供原始图像以及我们希望更改的掩膜和包含我们想看到更改的文本提示,Stable Diffusion 生成新图像。在下一节中,我们将看到如何实际操作。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9:训练 Stable Diffusion 进行图像修补的过程(图片由作者制作)

使用 Stable Diffusion 的室内设计

在本节中,我将讲解如何在室内设计中使用 Stable Diffusion 进行修补场景的操作。

当我们购买一栋新房或仍在建设中的公寓时,通常可以获取它的 3D 图像。基于这些图像,我们可以要求更改颜色或材料,以使其符合我们的口味。

然而,很难想象我们请求的更改是否适合房子的其他部分,而请求新的 3D 可能会很昂贵且耗时。因此,我们可以使用 Stable Diffusion 快速迭代,并了解如果应用我们想要的更改,事物会是什么样子。

为此,我们可以使用 Python 和 HuggingFace 🤗 来构建我们自己的 Stable Diffusion 室内设计师!

我们首先导入库:

import PIL
import torch
import ipyplot
from diffusers import StableDiffusionInpaintPipeline

然后,我们加载 HuggingFace 🤗 提供的 Stable Diffusion Inpainting 模型:

# load model
pipe = StableDiffusionInpaintPipeline.from_pretrained("runwayml/stable-diffusion-inpainting", torch_dtype=torch.float16).to("cuda")

在加载了模型后,我们加载原始图像和我们想要更改的遮罩:

  • 遮罩的白色部分是将要更改的部分,而黑色部分是将保持不变的部分。

  • 遮罩是手动创建的,但我们也可以使用分割模型来创建它。

init_image = PIL.Image.open("<YOUR IMAGE>.jpg")
mask_image = PIL.Image.open("<YOUR MASK>.png")

ipyplot.plot_images([init_image, mask_image], labels=["original", "mask"], max_images=30, img_width=500)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10:厨房的 3D 图像(由作者购买)和一个遮罩,用于更改岛台和黑色墙壁(图像由作者制作)

在加载了图像和遮罩后,现在是创建提示以将图像生成条件设置为我们想要的内容并生成新图像的时候了。

  • 在这种情况下,我想将黑色岛台和黑色墙壁替换为大理石岛台和大理石墙壁。
# define prompt
prompt = "white calacatta marble island kitchen and white calacatta marble wall"
guidance_scale = 15 # weight of the prompt
generator = torch.Generator(device="cuda").manual_seed(0) # change the seed to get different results

# generate 10 images
images = pipe(prompt=prompt, image=init_image, mask_image=mask_image, num_images_per_prompt=10, guidance_scale=guidance_scale, generator=generator).images

# show results
print(prompt)
ipyplot.plot_images([init_image] + images, labels=["original"] + ["generated"] * len(images), max_images=30, img_width=500)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11:原始图像与我最喜欢的生成图像的比较(它们的尺寸不同,因为模型是用 512x512 图像训练的)

结果看起来不错,但我还想将木制厨房橱柜替换为白色的,所以让我们重新进行这个过程:

  1. 加载最后生成的图像和一个新的遮罩:
# load new image and mask
init_image = PIL.Image.open("IMG2.jpg") mask_image = PIL.Image.open("mask3.png")
ipyplot.plot_images([init_image, mask_image], labels=["original", "mask"], max_images=30, img_width=500)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 12:之前生成的图像和新的遮罩(图像由作者制作)

  1. 创建新的提示并生成新图像:
# define prompt
prompt = "white kitchen cabinet"
guidance_scale = 15 # weight of the prompt
generator = torch.Generator(device="cuda").manual_seed(0) # change the seed to get different results

# generate 10 images
images = pipe(prompt=prompt, image=init_image, mask_image=mask_image, num_images_per_prompt=10, guidance_scale=guidance_scale, generator=generator).images

# show results
print(prompt)
ipyplot.plot_images([init_image] + images, labels=["original"] + ["generated"] * len(images), max_images=30, img_width=500)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 13:带有白色厨房橱柜的生成图像(图像由作者制作)

结果看起来非常好,我希望你注意到 Stable Diffusion 能够重现的细节,例如厨房水龙头或橱柜上的光线反射。尽管左侧的反射没有对齐,但令人惊讶的是它如何考虑了光线。

结论

AI 不仅对拥有大量数据的组织有用,它还可以应用于我们能想到的任何事物!

在本文中,我们探索了 Stable Diffusion 的一种非传统用途,但这是一个存在了几十年的传统工作。通过几行代码,我们能够为每个提示生成尽可能多的不同图像,这给了我们很多选择的可能性。

然而,与其他事物一样,稳定扩散模型,特别是我们使用的模型,也有其自身的局限性,例如在光线反射中或在图 14 中,其中一把椅子位于厨房岛内,并未实现完美的真实感。

尽管如此,人工智能的未来仍然光明,其中一些局限性可以通过在我们自己的数据和使用案例中进行微调来克服。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 14:生成的图像存在一些质量问题(图像由作者制作)

保持联系: LinkedInMedium

参考文献

[1] Robin Rombach, Andreas Blattmann, Dominik Lorenz, Patrick Esser, Björn Ommer. 高分辨率图像合成与潜在扩散模型。arXiv:2001.08210, 2022

[2] Diederik P. Kingma, Max Welling. 变分自编码器简介。arXiv:1906.02691, 2019

[3] Olaf Ronneberger, Philipp Fischer, Thomas Brox. U-Net: 用于生物医学图像分割的卷积网络。arXiv:1505.04597, 2015

[4] Alec Radford, Jong Wook Kim, Chris Hallacy, Aditya Ramesh, Gabriel Goh, Sandhini Agarwal, Girish Sastry, Amanda Askell, Pamela Mishkin, Jack Clark, Gretchen Krueger, Ilya Sutskever. 从自然语言监督中学习可转移的视觉模型。arXiv:1911.02116, 2021

我如何通过堆叠集成模型在欧洲最大机器学习竞赛中获得前 10%

原文:towardsdatascience.com/stacked-ensembles-for-advanced-predictive-modeling-with-h2o-ai-and-optuna-8c339f8fb602

关于使用 H2O.ai 和 Optuna 训练堆叠集成模型的概念性和实践编码指南

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Sheila Teo

·发表于 Towards Data Science ·阅读时间 13 分钟·2023 年 12 月 18 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图像由 DALL·E 3 生成

我们都知道,集成模型在预测建模中优于任何单一模型。你可能听说过 Bagging 和 Boosting 作为常见的集成方法,以随机森林和梯度提升机作为各自的例子。

但是,如何在一个独立的更高层次模型下组合不同的模型呢?这就是堆叠集成模型的作用所在。本文是如何使用流行的机器学习库 H2O 训练堆叠集成模型的逐步指南。

为了展示堆叠集成模型的强大功能,我将提供一个完整的代码演示,介绍如何训练一个由 40 个深度神经网络、XGBoost 和 LightGBM 模型组成的堆叠集成模型,以完成 2023 年 Cloudflight 编程竞赛(AI 类别)中的预测任务。这是欧洲最大的编程竞赛之一,我在其中的训练时间为 1 小时内获得了前 10% 的名次!

本指南将涵盖:

  1. 什么是堆叠集成模型,它们是如何工作的?

  2. 如何使用 H2O.ai 训练堆叠集成模型 **—

    通过 Python 的完整代码演示**

  3. 比较堆叠集成模型与单独模型的性能

1. 什么是堆叠集成模型,它们是如何工作的?

堆叠集成模型通过另一个更高层次的模型将多个模型的预测结果进行结合,旨在通过利用每个组成模型的独特优势来提高整体预测性能。它包括两个阶段:

阶段 1:多个基础模型

首先,多个基础模型在相同的训练数据集上独立训练。这些模型应当尽可能多样化,从简单的线性回归到复杂的深度学习模型都可以。关键是它们在某些方面应有所不同,无论是使用不同的算法还是使用相同的算法但具有不同的超参数设置。

基础模型的多样性越高,最终的堆叠集成模型越强大。 这是因为不同的模型能够捕捉数据中的不同模式。例如,基于树的模型可能擅长捕捉非线性关系,而线性模型擅长理解线性趋势。当这些多样化的基础模型结合在一起时,堆叠集成模型可以利用每个基础模型的不同优势,从而提高预测性能。

第二阶段:一个元模型

在所有基础模型训练完成后,每个基础模型对目标的预测将用作训练一个更高层次模型(称为元模型)的特征。这意味着元模型不是在原始数据集的特征上训练,而是在基础模型的预测上进行训练。如果有n个基础模型,则生成n个预测,这些预测就是用于训练元模型的n个特征。

虽然训练特征在基础模型和元模型之间有所不同,但目标保持不变,即数据集中的原始目标。

元模型学习如何最佳地结合基础模型的预测,以做出最终的、更准确的预测。

堆叠集成训练的详细步骤

对于每个基础模型:

1. 选择一个算法(例如,随机森林)。

2. 使用交叉验证获得算法的最佳超参数集合。

3. 获取训练集中目标的交叉验证预测。这些将随后用于训练元模型。

为了说明这一点,假设在步骤 1 中选择了随机森林算法,并且在步骤 2 中确定了其最优超参数为 *h*

*交叉验证预测是通过以下方式获得的,假设使用 5 折交叉验证:

1. 在第 1 到第 4 折上训练具有超参数* *h* *的随机森林。

2. 使用训练好的随机森林对第 5 折进行预测。这些是第 5 折的交叉验证预测。

3. 重复上述步骤以获取每一折的交叉验证预测。之后,将获得整个训练集的目标交叉验证预测。*

对于元模型:

1. 获取用于训练元模型的特征。这些是每个基础模型的预测。

2. 获取用于训练元模型的目标。这是来自训练集的原始目标。

3. 选择一个算法(例如,线性回归)。

4. 使用交叉验证获得算法的最佳超参数集合。

看,完成了!你现在拥有:

  • 多个经过优化超参数训练的基础模型

  • 一个也经过优化超参数训练的元模型

这意味着你已经成功训练了一个堆叠集成模型!

2. 如何使用 H2O.ai 训练堆叠集成模型

现在,让我们开始编写代码吧!

如前所述,本节包括我为 2023 Cloudflight 编码竞赛(AI 类别)中的预测任务训练堆叠集成模型的完整代码,这是一个使用表格数据的回归任务。在竞赛时间限制下,我从 40 个基础模型中创建了一个堆叠集成模型,涉及 3 种算法类型——深度神经网络、XGBoost 和 LightGBM,这些特定算法被选择是因为它们在实践中通常表现优异。

2.1. 数据准备

首先,让我们导入必要的库。

import pandas as pd
import h2o
from h2o.estimators.deeplearning import H2ODeepLearningEstimator
from h2o.estimators import H2OXGBoostEstimator
from h2o.estimators.stackedensemble import H2OStackedEnsembleEstimator
import optuna
from tqdm import tqdm

seed = 1

并初始化 H2O 集群。

h2o.init()

接下来,加载数据集。

data = pd.read_csv('path_to_your_tabular_dataset')

在使用 H2O 进行模型构建之前,让我们首先了解 H2O 模型的以下特性:

  1. H2O 模型不能接收 Pandas DataFrame 对象,因此 data 必须从 Pandas DataFrame 转换为 H2O 对应的 H2OFrame。

  2. H2O 模型可以自动编码分类特征,这很好,因为这一步预处理被省略了。为了确保这些特征被模型识别为分类特征,它们必须显式转换为因子(分类)数据类型。

data_h2o = h2o.H2OFrame(data)

categorical_cols = [...]  #insert the names of the categorical features here
for col in categorical_cols:
  data_h2o[col] = data_h2o[col].asfactor()

现在我们可以使用 H2OFrame 对象的 split_frame() 方法将数据集拆分为训练集(90%)和验证集(10%)。

splits = data_h2o.split_frame(ratios=[0.9], seed=seed)
train = splits[0]
val = splits[1]

最后,让我们获取建模的特征和目标。与接受特征和目标的的 Scikit-Learn 模型不同,H2O 模型接受的是特征和目标的名称

y = '...'  #insert name of the target column here
x = list(train.columns)
x.remove(y) 

现在,让模型训练的乐趣开始吧!

2.2. 训练深度神经网络(DNN)作为基础模型

我们先从训练将组成堆叠集成模型的 DNN 开始,使用 H2O 的 H2ODeepLearningEstimator

附注:为什么在 H2O 中训练 DNN,而不是使用 Tensorflow、Keras 或 PyTorch?

在跳入代码之前,你可能会好奇为什么我选择使用 H2O 的 *H2ODeepLearningEstimator** 来训练 DNN,而不是使用 Tensorflow、Keras 或 PyTorch 这些常用的 DNN 构建库。*

简单的答案是,在 H2O 中构建堆叠集成模型使用的是 *H2OStackedEnsembleEstimator*,它只能接受 H2O 模型家族中的基础模型。然而,更关键的原因是 H2O 的 *H2ODeepLearningEstimator* 比其他框架更容易调整 DNN,这就是原因。

在 TensorFlow、Keras 或 PyTorch 中,像 dropout 层这样的正则化效果必须手动添加到模型架构中,例如使用 *keras.layers.Dropout()*。这允许更大的自定义,但也需要更多的详细知识和精力。例如,你必须决定在模型架构中在哪里以及多少次包括 *keras.layers.Dropout()* 层。

另一方面,H2O 的 *H2ODeepLearningEstimator* 更加抽象,易于普通人使用。可以通过模型超参数以简单的方式启用正则化,从而减少手动设置这些组件为层的需求。此外, 默认 的模型超参数已包含正则化。常见的特征预处理步骤,例如数值特征的缩放和分类特征的编码,也作为模型超参数包含在内,以实现自动特征预处理。这些功能使得调整深度神经网络(DNN)的过程变得更加直接和简单,而无需深入了解深度学习模型架构的复杂性。在比赛的时间紧迫背景下,这对我来说非常有用!

那么,我们应该使用哪一组超参数来训练H2ODeepLearningEstimator呢?这就是optuna的作用所在。Optuna是一个超参数优化框架,类似于传统的网格搜索和随机搜索方法,但它采用了更复杂的方法。

网格搜索系统地探索预定义范围内的超参数值,而随机搜索则在这些指定的限制范围内选择随机组合。然而,optuna利用贝叶斯优化,从之前的搜索中学习,提出每次后续搜索中表现更好的超参数集,提高了寻找最佳模型超参数的效率。这在复杂且大型的超参数空间中尤其有效,在这些空间中,传统的搜索方法可能会耗时极长,最终仍可能无法找到最佳的超参数集。

现在,让我们进入代码部分。我们将使用optuna来调整 H2O 的H2ODeepLearningEstimator的超参数,并将所有训练过的模型跟踪到列表dnn_models中。

dnn_models = []

def objective(trial):
    #params to tune
    num_hidden_layers = trial.suggest_int('num_hidden_layers', 1, 10)
    hidden_layer_size = trial.suggest_int('hidden_layer_size', 100, 300, step=50)

    params = {
        'hidden': [hidden_layer_size]*num_hidden_layers,
        'epochs': trial.suggest_int('epochs', 5, 100),
        'input_dropout_ratio': trial.suggest_float('input_dropout_ratio', 0.1, 0.3),  #dropout for input layer
        'l1': trial.suggest_float('l1', 1e-5, 1e-1, log=True),  #l1 regularization
        'l2': trial.suggest_float('l2', 1e-5, 1e-1, log=True),  #l2 regularization
        'activation': trial.suggest_categorical('activation', ['rectifier', 'rectifierwithdropout', 'tanh', 'tanh_with_dropout', 'maxout', 'maxout_with_dropout'])
}

    #param 'hidden_dropout_ratios' is applicable only if the activation type is rectifier_with_dropout, tanh_with_dropout, or maxout_with_dropout
    if params['activation'] in ['rectifierwithdropout', 'tanh_with_dropout', 'maxout_with_dropout']:
        hidden_dropout_ratio = trial.suggest_float('hidden_dropout_ratio', 0.1, 1.0)  
        params['hidden_dropout_ratios'] = [hidden_dropout_ratio]*num_hidden_layers  #dropout for hidden layers

    #train model
    model = H2ODeepLearningEstimator(**params,
                                     standardize=True,  #h2o models can do this feature preprocessing automatically
                                     categorical_encoding='auto',  #h2o models can do this feature preprocessing automatically
                                     nfolds=5,
                                     keep_cross_validation_predictions=True,  #need this for training the meta-model later
                                     seed=seed)
    model.train(x=x, y=y, training_frame=train)

    #store model
    dnn_models.append(model)

    #get cross-validation rmse 
    cv_metrics_df = model.cross_validation_metrics_summary().as_data_frame()
    cv_rmse_index = cv_metrics_df[cv_metrics_df[''] == 'rmse'].index
    cv_rmse = cv_metrics_df['mean'].iloc[cv_rmse_index]
    return cv_rmse

study = optuna.create_study(direction='minimize')
study.optimize(objective, n_trials=20)

上述optuna研究用于寻找能够最小化交叉验证 RMSE 的H2ODeepLearningEstimator超参数集(因为这是一个回归任务),优化过程运行 20 次试验,使用参数n_trials=20。这意味着训练了 20 个 DNN,并将它们存储在列表dnn_models中,以供稍后作为堆叠集成的基模型使用,每个模型都有不同的超参数集。考虑到比赛的时间限制,我选择训练 20 个 DNN,但你可以根据自己的需求设置n_trials为你希望训练的 DNN 数量。

重要的是,H2ODeepLearningEstimator必须使用keep_cross_validation_predictions=True进行训练,因为这些交叉验证预测将被用作训练元模型的特征。

2.3. 将 XGBoost 和 LightGBM 作为基模型进行训练

接下来,让我们训练将形成堆叠集成基模型集合的 XGBoost 和 LightGBM 模型。我们将再次使用optuna来调优 H2O 的H2OXGBoostEstimator的超参数,并将所有训练过的模型记录在xgboost_lightgbm_models列表中。

在深入代码之前,我们必须首先了解H2OXGBoostEstimator是将流行的xgboost库中的 XGBoost 框架集成到 H2O 中。另一方面,H2O 并未集成lightgbm库。然而,它确实提供了一种方法,通过在H2OXGBoostEstimator中使用特定参数集来模拟 LightGBM 框架——这正是我们将实现的,以便使用H2OXGBoostEstimator来训练 XGBoost 和 LightGBM 模型。

xgboost_lightgbm_models = []

def objective(trial):
    #common params between xgboost and lightgbm
    params = {
        'ntrees': trial.suggest_int('ntrees', 50, 5000),
        'max_depth': trial.suggest_int('max_depth', 1, 9),
        'min_rows': trial.suggest_int('min_rows', 1, 5),
        'sample_rate': trial.suggest_float('sample_rate', 0.8, 1.0),
        'col_sample_rate': trial.suggest_float('col_sample_rate', 0.2, 1.0),
        'col_sample_rate_per_tree': trial.suggest_float('col_sample_rate_per_tree', 0.5, 1.0)
    }

    grow_policy = trial.suggest_categorical('grow_policy', ['depthwise', 'lossguide'])

     #######################################################################################################################
     #from H2OXGBoostEstimator's documentation, (https://docs.h2o.ai/h2o/latest-stable/h2o-docs/data-science/xgboost.html) # 
     #lightgbm is emulated when grow_policy=lossguide and tree_method=hist                                                 #
     #so we will tune lightgbm-specific hyperparameters when this set of hyperparameters is used                           #
     #and tune xgboost-specific hyperparameters otherwise                                                                  #
     #######################################################################################################################

    #add lightgbm-specific params
    if grow_policy == 'lossguide':  
        tree_method = 'hist'  
        params['max_bins'] = trial.suggest_int('max_bins', 20, 256)
        params['max_leaves'] = trial.suggest_int('max_leaves', 31, 1024)

    #add xgboost-specific params
    else:
        tree_method = 'auto'
        params['booster'] = trial.suggest_categorical('booster', ['gbtree', 'gblinear', 'dart'])
        params['reg_alpha'] = trial.suggest_float('reg_alpha', 0.001, 1)
        params['reg_lambda'] = trial.suggest_float('reg_lambda', 0.001, 1)
        params['min_split_improvement'] = trial.suggest_float('min_split_improvement', 1e-10, 1e-3, log=True)

    #add grow_policy and tree_method into params dict
    params['grow_policy'] = grow_policy
    params['tree_method'] = tree_method

    #train model
    model = H2OXGBoostEstimator(**params,
                                learn_rate=0.1,
                                categorical_encoding='auto',  #h2o models can do this feature preprocessing automatically
                                nfolds=5,
                                keep_cross_validation_predictions=True,  #need this for training the meta-model later
                                seed=seed) 
    model.train(x=x, y=y, training_frame=train)

    #store model
    xgboost_lightgbm_models.append(model)

    #get cross-validation rmse
    cv_metrics_df = model.cross_validation_metrics_summary().as_data_frame()
    cv_rmse_index = cv_metrics_df[cv_metrics_df[''] == 'rmse'].index
    cv_rmse = cv_metrics_df['mean'].iloc[cv_rmse_index]
    return cv_rmse

study = optuna.create_study(direction='minimize')
study.optimize(objective, n_trials=20)

类似地,20 个 XGBoost 和 LightGBM 模型被训练并存储在xgboost_lightgbm_models列表中,以备后续作为堆叠集成的基模型使用,每个模型有一组不同的超参数。你可以设置n_trials为任何你希望训练的 XGBoost/LightGBM 模型的数量。

重要的是,H2OXGBoostEstimator还必须使用keep_cross_validation_predictions=True进行训练,因为这些交叉验证预测将被用作训练元模型的特征。

2.4. 训练元模型

我们将使用上述训练的所有深度神经网络、XGBoost 和 LightGBM 模型作为基模型。然而,这并不意味着所有这些模型都会被用于堆叠集成,因为在调优我们的元模型时,我们将进行自动基模型选择(稍后会详细介绍)!

记住,我们将每个训练过的基模型存储在dnn_models(20 个模型)和xgboost_lightgbm_models(20 个模型)列表中,总共为我们的堆叠集成提供了 40 个基模型。接下来,我们将它们合并成一个最终的基模型列表base_models

base_models = dnn_models + xgboost_lightgbm_models

现在,我们已经准备好使用这些基模型来训练元模型。但首先,我们必须决定元模型算法,在此过程中涉及一些概念:

  1. 大多数关于堆叠集成的学术论文建议为元模型选择简单的线性算法。这是为了避免元模型对基模型的预测结果过拟合。

  2. H2O 推荐在回归任务中使用广义线性模型(GLM)而非线性回归,或在分类任务中使用广义线性模型而非逻辑回归。这是因为 GLM 是一种灵活的线性模型,不像后者那样强加正态性和同方差性的关键假设,这使得它能够更好地建模目标值的真实行为,因为这些假设在实际操作中可能很难满足。关于这一点的进一步解释可以在这篇学术论文中找到,H2O 的工作就是基于这篇论文的。

因此,我们将使用 H2OStackedEnsembleEstimator 并设置 metalearner_algorithm='glm' 来实例化元模型,并使用 optuna 调整 GLM 元模型的超参数以优化性能。

def objective(trial):
    #GLM params to tune
    meta_model_params = {
        'alpha': trial.suggest_float('alpha', 0, 1),  #regularization distribution between L1 and L2
        'family': trial.suggest_categorical('family', ['gaussian', 'tweedie']),  #read the documentation here on which family your target may fall into: https://docs.h2o.ai/h2o/latest-stable/h2o-docs/data-science/glm.html
        'standardize': trial.suggest_categorical('standardize', [True, False]),
        'non_negative': True  #predictions of each base model cannot be subtracted from one another
    }

    ensemble = H2OStackedEnsembleEstimator(metalearner_algorithm='glm',
                                             metalearner_params=meta_model_params,
                                             metalearner_nfolds=5,
                                             base_models=base_models,  
                                             seed=seed)

    ensemble.train(x=x, y=y, training_frame=train)

    #get cross-validation rmse
    cv_metrics_df = ensemble.cross_validation_metrics_summary().as_data_frame()
    cv_rmse_index = cv_metrics_df[cv_metrics_df[''] == 'rmse'].index
    cv_rmse = cv_metrics_df['mean'].iloc[cv_rmse_index]
    return cv_rmse

study = optuna.create_study(direction='minimize')
study.optimize(objective, n_trials=20)

注意,每个基础模型的交叉验证预测没有被显式地传递给 H2OStackedEnsembleEstimator。这是因为 H2O 在后台自动完成了这一步骤,使我们更加轻松!我们只需在之前训练基础模型时设置 keep_cross_validation_predictions=True,并使用参数 base_models=base_models 实例化 H2OStackedEnsembleEstimator 即可。

现在,我们可以最终构建 best_ensemble 模型,使用 optuna 找到的最佳超参数。

best_meta_model_params = study.best_params
best_ensemble = H2OStackedEnsembleEstimator(metalearner_algorithm='glm',
                                            metalearner_params=best_meta_model_params,
                                            base_models=base_models,
                                            seed=seed)

best_ensemble.train(x=x, y=y, training_frame=train)

完成了,我们成功地在 H2O 中训练了一个堆叠集成模型!让我们来看看吧。

best_ensemble.summary()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

注意到堆叠集成模型只使用了我们提供的 40 个基础模型中的 16 个,其中 3 个是 XGBoost/LightGBM 模型,13 个是深度神经网络模型。这是因为我们为 GLM 元模型调整的超参数alpha,它代表了 L1(LASSO)和 L2(Ridge)之间的正则化分布。1 的值仅涉及 L1 正则化,而 0 的值仅涉及 L2 正则化。

如上所述,最佳值被发现为 alpha=0.16,因此使用了 L1 和 L2 的混合。一些基础模型的预测在 L1 正则化下的回归中系数被设置为 0,这意味着这些基础模型在堆叠集成中完全没有被使用,因此实际使用的基础模型数量少于 40 个。

这里的关键要点是,我们上面的设置还通过元模型的正则化超参数自动选择了用于最佳性能的基础模型,而不是简单地使用所有提供的 40 个基础模型。

3. 性能比较:堆叠集成与独立基础模型

为了展示堆叠集成的威力,让我们使用它为从一开始就被保留的验证集生成预测。下面的 RMSE 数字仅针对我使用的数据集,但也可以在自己的数据集上运行本文的代码,亲自查看模型性能的差异!

ensemble_val_rmse = best_ensemble.model_performance(val).rmse()
ensemble_val_rmse   #0.31475634111745304

堆叠集成在验证集上产生了 0.31 的 RMSE。

接下来,让我们深入分析每个基础模型在这个相同验证集上的表现。

base_val_rmse = []
for i in range(len(base_models)):
    base_val_rmse = base_models[i].model_performance(val).rmse()

models = ['H2ODeepLearningEstimator'] * len(dnn_models) + ['H2OXGBoostEstimator'] * len(xgboost_lightgbm_models)

base_val_rmse_df = pd.DataFrame([models, base_val_rmse]).T
base_val_rmse_df.columns = ['model', 'val_rmse']
base_val_rmse_df = base_val_rmse_df.sort_values(by='val_rmse', ascending=True).reset_index(drop=True)
base_val_rmse_df.head(15)  #show only the top 15 in terms of lowest val_rmse

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供

与实现了 0.31 RMSE 的堆叠集成模型相比,表现最佳的独立基础模型的 RMSE 为 0.35。

这意味着堆叠方法能够在未见数据上提高预测性能 11%!

现在你已经见证了堆叠集成的威力,轮到你亲自尝试了!

写这篇文章我感到非常愉快,如果你阅读起来也觉得有趣,我会非常感激你花一点时间留下点赞和关注!

下次见!

Sheila

堆叠时间序列模型以提高准确性

原文:towardsdatascience.com/stacking-time-series-models-to-improve-accuracy-7977c6667d29

从 RNN、ARIMA 和 Prophet 模型中提取信号,以便用 Catboost 进行预测

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Michael Keith

·发表于Towards Data Science ·阅读时长 7 分钟·2023 年 2 月 28 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由Robert Sachowski拍摄,发布在Unsplash上。

关于强大的时间序列模型的研究非常丰富。可供选择的选项很多,远远超出了传统的 ARIMA 技术。最近,递归神经网络和 LSTM 模型已成为许多研究人员关注的重点。根据PePy上列出的下载数量,Prophet 模型可能是时间序列预测者使用最广泛的模型,因为它的入门门槛较低。对于你的时间序列来说,哪种选项最合适呢?

也许答案是你应该尝试所有这些模型,并结合它们的各种优点。一个常见的技术是堆叠。流行的机器学习库 scikit-learn 提供了一个StackingRegressor,可以用于时间序列任务。我之前演示了如何使用它来处理这种情况。

然而,StackingRegressor 有一个限制;它只接受其他 scikit-learn 模型类和 API。因此,像 ARIMA 这样的模型(在 scikit-learn 中不可用)或来自 tensorflow 的深度网络将无法放入堆栈中。这里有一个解决方案。在这篇文章中,我将展示如何使用 scalecast 包和一个 Jupyter notebook 扩展时间序列的堆叠方法。使用的数据集可以在 GitHub 上 open access 获取。需要以下要求:

pip install --upgrade scalecast
conda install tensorflow
conda install shap
conda install -c conda-forge cmdstanpy
pip install prophet

数据集概述

数据集是按小时划分的,分为训练集(700 个观测值)和测试集(48 个观测值)。我的 notebook 使用了 H1 系列,但修改为利用 M4 数据集中任何一个小时序列是很直接的。这是如何读取数据并将其存储在 Forecaster 对象中的:

import pandas as pd
import numpy as np
from scalecast.Forecaster import Forecaster
from scalecast.util import metrics
import matplotlib.pyplot as plt
import seaborn as sns

def read_data(idx = 'H1', cis = True, metrics = ['smape']):
    info = pd.read_csv(
        'M4-info.csv',
        index_col=0,
        parse_dates=['StartingDate'],
        dayfirst=True,
    )
    train = pd.read_csv(
        f'Hourly-train.csv',
        index_col=0,
    ).loc[idx]
    test = pd.read_csv(
        f'Hourly-test.csv',
        index_col=0,
    ).loc[idx]
    y = train.values
    sd = info.loc[idx,'StartingDate']
    fcst_horizon = info.loc[idx,'Horizon']
    cd = pd.date_range(
        start = sd,
        freq = 'H',
        periods = len(y),
    )
    f = Forecaster(
        y = y, # observed values
        current_dates = cd, # current dates
        future_dates = fcst_horizon, # forecast length
        test_length = fcst_horizon, # test-set length
        cis = cis, # whether to evaluate intervals for each model
        metrics = metrics, # what metrics to evaluate
    )

    return f, test.values

f, test_set = read_data()
f # display the Forecaster object

这是该序列的图表:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

应用模型

在开始堆叠模型之前,我们需要从它们生成预测。我选择使用简单模型作为 ARIMA、LSTM 和 Prophet 模型的基准。在接下来的部分中,我将解释为什么做出每个选择,但也可以做出其他决策,这些决策同样有趣,甚至更具吸引力。

简单基准测试

为了对所有模型进行基准测试,我们可以调用季节性简单估计方法,该方法将给定小时序列中的最后 24 个观测值向前传播。在 scalecast 中,这非常简单。

f.set_estimator('naive')
f.manual_forecast(seasonal=True)

ARIMA

自回归积分滑动平均是一种流行且简单的时间序列技术,它利用序列的滞后值和误差以线性方式预测未来。通过探索性数据分析(您可以在链接的 notebook 中看到),我确定序列不是平稳的,并且具有很强的季节性。我最终选择应用了一个季节性 ARIMA 模型,订单为 (5,1,4) x (1,1,1,24)。

f.set_estimator('arima')
f.manual_forecast(
    order = (5,1,4),
    seasonal_order = (1,1,1,24),
    call_me = 'manual_arima',
)

LSTM

如果 ARIMA 属于时间序列模型中的简单方法,LSTM 是更先进的方法之一。这是一种深度学习技术,具有许多参数,包括一个注意力机制,可以在序列数据中找到长期和短期的模式,这使其理论上成为时间序列的理想选择。使用 tensorflow 设置这个模型很困难,但在 scalecast 中并不太难(请参见 这篇文章)。我应用了两个 LSTM 模型:一个使用 Tanh 激活函数,一个使用 ReLu。

f.set_estimator('rnn')
f.manual_forecast(
    lags = 48,
    layers_struct=[
        ('LSTM',{'units':100,'activation':'tanh'}),
        ('LSTM',{'units':100,'activation':'tanh'}),
        ('LSTM',{'units':100,'activation':'tanh'}),
    ],
    optimizer = 'Adam',
    epochs = 15,
    plot_loss = True,
    validation_split=0.2,
    call_me = 'rnn_tanh_activation',
)

f.manual_forecast(
    lags = 48,
    layers_struct=[
        ('LSTM',{'units':100,'activation':'relu'}),
        ('LSTM',{'units':100,'activation':'relu'}),
        ('LSTM',{'units':100,'activation':'relu'}),
    ],
    optimizer = 'Adam',
    epochs = 15,
    plot_loss = True,
    validation_split=0.2,
    call_me = 'rnn_relu_activation',
)

Prophet

尽管 Prophet 模型极受欢迎,但它最近却被贬低。有人声称它的准确性令人失望,主要是因为它对趋势的外推过于不现实,并且它没有通过自回归建模考虑局部模式。然而,它确实有一些独特之处。比如,它会自动将节假日效应应用到模型中。它还考虑了几种季节性类型。它以用户需要的最少规格完成这些功能。我喜欢将它作为一个信号使用,即使它不适合生成点预测。

f.set_estimator('prophet')
f.manual_forecast()

比较结果

现在我们已经为每个模型生成了预测结果,让我们看看它们在验证集上的表现,这些是我们训练集中的最后 48 个观测值(这仍然与之前提到的测试集分开)。

results = f.export(determine_best_by='TestSetSMAPE')
ms = results['model_summaries']
ms[
    [
        'ModelNickname',
        'TestSetLength',
        'TestSetSMAPE',
        'InSampleSMAPE',
    ]
]

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供

好消息是每个模型都优于朴素方法。ARIMA 模型表现最佳,百分比误差为 4.7%,其次是 Prophet。让我们查看所有预测与验证集的对比图:

f.plot(order_by="TestSetSMAPE",ci=True)
plt.show()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供

所有这些模型在这个系列上表现得相当合理,之间没有大的偏差。让我们进行模型叠加!

模型叠加

每个叠加模型都需要一个最终估计器,该估计器将筛选其他模型的各种估计值,以创建一组新的预测。我们将用一个叫做Catboost的增强树估计器来叠加我们之前探索的结果。Catboost 是一个强大的程序,我们希望它能从每个已经应用的模型中提取最佳信号。

 f.add_signals(
    f.history.keys(), # add signals from all previously evaluated models
)
f.add_ar_terms(48)
f.set_estimator('catboost')

上述代码将每个评估模型的预测结果添加到Forecaster对象中。它将这些预测称为“信号”。这些信号与存储在同一对象中的其他协变量一样对待。我们还将最后 48 个系列的滞后添加为 Catboost 模型可以用来进行预测的额外回归量。现在我们将调用三个 Catboost 模型:一个使用所有可用的信号和系列滞后,一个仅使用信号,还有一个仅使用滞后。

f.manual_forecast(
    Xvars='all',
    call_me='catboost_all_reg',
    verbose = False,
)
f.manual_forecast(
    Xvars=[x for x in f.get_regressor_names() if x.startswith('AR')], 
    call_me = 'catboost_lags_only',
    verbose = False,
)
f.manual_forecast(
    Xvars=[x for x in f.get_regressor_names() if not x.startswith('AR')], 
    call_me = 'catboost_signals_only',
    verbose = False,
)

让我们利用在分析开始时与 Forecaster 对象分开的测试集,比较所有应用模型的结果。这次,我们将关注两个指标:SMAPE 和平均绝对尺度误差(MASE)。这两个指标在实际的 M4 竞赛中被使用。

test_results = pd.DataFrame(index = f.history.keys(),columns = ['smape','mase'])
for k, v in f.history.items():
    test_results.loc[k,['smape','mase']] = [
        metrics.smape(test_set,v['Forecast']),
        metrics.mase(test_set,v['Forecast'],m=24,obs=f.y),
    ]

test_results.sort_values('smape')

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供

通过结合来自不同类别模型的信号,我们生成了两个优于其他模型的估计器:一个使用所有信号和 48 个系列滞后的 Catboost 模型,以及一个仅使用信号的 Catboost 模型。这两个模型的误差大约为 2.8%。我们可以看到这两个模型与测试集中的实际数据进行对比的图示。

fig, ax = plt.subplots(figsize=(12,6))
f.plot(
    models = ['catboost_all_reg','catboost_signals_only'],
    ci=True,
    ax = ax
)
sns.lineplot(
    x = f.future_dates, 
    y = test_set, 
    ax = ax,
    label = 'held out actuals',
    color = 'darkblue',
    alpha = .75,
)
plt.show()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

哪些信号最为重要?

为了完善分析,我们可以使用 Shapley 分数来确定在这个模型堆叠中哪些信号最为重要。Shapley 分数被认为是确定输入在给定机器学习模型中的预测能力的最先进方法之一。分数越高,表示这些输入在该模型中越重要。

f.export_feature_importance('catboost_all_reg')

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

上面的截图仅显示了前几个最重要的预测因子,但我们可以从中看出,ARIMA 信号最为重要,其次是系列的第一个滞后项,然后是 Prophet 信号。RNN 模型的得分也高于许多包含的滞后项。如果我们希望将来训练一个更简洁的模型,这可以是一个很好的起点。

结论

在这篇文章中,我展示了在时间序列上下文中堆叠模型的威力,以及如何通过使用多样的模型类别提高探索系列的准确性。所有这些都通过 scalecast 包轻松实现。如果你觉得这个分析有趣,请在GitHub上给这个包一个星标。感谢你的关注!

StackOverflow 的转型:从颠覆到机遇

原文:towardsdatascience.com/stackoverflows-pivot-from-disruption-to-opportunity-8831dfbd9df?source=collection_archive---------10-----------------------#2023-08-14

OverFlowAI 利用公司的核心资产,将答案呈现在一个可用的界面中,并创建一个生成 AI 循环来创造新内容

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Viggy Balagopalakrishnan

·

关注 发表在 Towards Data Science ·9 min read·2023 年 8 月 14 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来源:Pakata GohUnsplash

随着像 GPT4 这样的高效模型推动生成性 AI 的发展,数据专业人士如何为他们所参与的组织提供长期价值正在演变。真正的价值不仅来自于成为房间里最具技术能力的人,还包括能够塑造这些技术如何影响产品和业务成果。这包括能够引导你的组织制定正确的数据战略,并塑造数据产品如何无缝地融入产品体验。本文对 StackOverflow 变革的分析提供了一个有力的案例研究,以实现这一目标。

StackOverflow 是软件开发人员获取编程支持的最常用平台,最近经历了一些波折。如果你以前没有使用过 StackOverflow,它是一个类似于 Quora 或 Reddit 的问答论坛,你可以在这里提出与编程相关的问题。虽然我已经好几年没有写过生产级代码,但在我写代码的时候,StackOverflow 确实非常棒。例如,如果你在编译代码时遇到了最晦涩的错误,并且收到了一条你无法理解的错误消息,你会将其放入 Google 搜索中。通常,你会找到一个 StackOverflow 页面,上面有人问了同样的问题并得到了回答。更少见的是,你会发现另一个人遇到了完全相同的晦涩问题但没有得到答案——在这种情况下,祝好运。更准确地说,69% 的问题 在 StackOverflow 上得到了回答,这确实很令人印象深刻。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

StackOverflow 首页

然而,最近 StackOverflow 的流量一直在下降。Similarweb 的数据表明,他们的流量 同比下降了 14%(StackOverflow 表示 更接近 5%)。尽管如此,这一趋势仍然是下降的,主要原因是 AI 编程产品如 ChatGPT 和 GitHub Copilot 的出现。这些产品具有有意义的代码编写能力,因此能够提供编程支持,至少在某种程度上不亚于 StackOverflow。具有讽刺意味的是,支撑这些 AI 产品的几个大型语言模型(LLMs)是使用爬取的 StackOverflow 数据进行训练的。

这些发展使得公司受到了相当严厉的媒体报道。Business Insider 在他们的文章 死亡于 LLM 中写道:

欢迎来到 AI 世界中的互联网未来。像 Stack Overflow 和 Wikipedia 这样的在线社区曾作为专家和好奇浏览者聚集并自由分享信息的中心而繁荣。现在,这些数字聚会场所正被大科技公司掠夺,用于训练他们的大型语言模型。

从这次生成型 AI 繁荣中出现的新产品正在使这些在线论坛的未来充满疑问。聊天机器人可以清晰、自动且通常愉快地回答问题——因此人们无需与其他人接触即可获取信息。

在所有这些关注中,StackOverflow 采取了稳健的措施,并阐明了他们应对这一挑战的双管齐下的方法:

  1. 几周前,他们宣布将开始向使用平台上 5000 万+ 问题和答案进行模型训练的大型 AI 开发者收费(我们在之前的数据抓取文章中讨论了这个问题)

  2. 上周,他们推出了OverflowAI产品,这是一组实际有用的生成型 AI 功能,可以帮助他们开启第二个篇章——我们今天将重点关注这一点

在本文中,我们将深入探讨:

  • AI 代码编写工具颠覆 StackOverflow

  • OverflowAI 的功能

  • StackOverflow 策略中的潜在趋势

AI 代码编写工具颠覆 StackOverflow

当前市场上有几种 AI 代码编写和编辑工具。这些工具要么是独立的产品(如 OpenAI Codex、ChatGPT、Google Bard),要么是原生集成在现有平台中的产品(如 GitHub Copilot、Replit Ghostwriter、Amazon CodeWhisperer)。它们具有广泛的功能,包括代码生成、代码编辑、自动完成和调试。

原生分发的产品(如 GitHub Copilot)具有很大的优势,因为它们可以在程序员今天已经使用的环境中无缝操作,我们将看到更多产品尝试集成到现有环境中。例如,CodeGPT 有一个插件,可以让开发人员从Visual Studio Code(一个流行的代码编辑工具)中使用该产品。

现有的 AI 代码编写工具在某些任务上表现良好。例如,这个Reddit 线程记录了几位网页开发人员关于 GitHub Copilot 的反馈——总体主题是,该产品在开发人员需要编写全新代码并且不想从头开始编写的特定情况下非常有用。即便在这些情况下,它也经常会有成功和失败。

原因并不令人惊讶。从概念上讲,大型语言模型(LLMs)接收大量数据,并基于这个结构生成输出:在特定上下文中,对于你问的问题,最可能的单词/文本是什么。它本质上是在计算一个单词跟随另一个单词的概率,并基于此生成输出。尽管有这一结构,鉴于用于训练这些模型的数据量,ChatGPT 在更一般的用例(如起草电子邮件或总结页面)中的结果仍然令人印象深刻。但重要的是要记住,语言模型在设计上具有有限的分析/数学能力。换句话说,当你问模型“2+2 是多少”时,它可能会给出正确答案——不是因为它知道数学,而是因为它在训练数据中见过这个文本模式。

同样,在代码生成方面,模型并不真正“了解”编程背后的基本概念,而是基于大量文本数据的训练来预测结果。这导致了上面提到的 GitHub Copilot 反馈——它有时能生成你需要的基础代码,但其实际理解代码、调试和提供解释的能力有限。这随着时间的推移会有所改善,但很难说它是否会达到高准确性/高可靠性的程度。

StackOverflow CEO Prashanth Chandrasekar 简洁地描述了

现代 LLM 系统的一个问题是,它们会以与正确答案相同的信心提供错误答案,并且如果感觉符合用户寻求的答案模式,它们会“虚构”事实和数据。

在某些时候,你需要了解自己在构建什么。你可能需要调试它,但对刚刚构建的内容毫无头绪,并且很难通过捷径跳过学习过程。

这就是 StackOverflow 的机会——他们的流量下降可能是永久性的,程序员可能更少地访问 StackOverflow 以解决简单问题(例如,他们可能不再访问 StackOverflow 以获取现成的排序算法)。但产品能够闪光的地方是:1)对语言模型可能无法回答的复杂问题提供高准确性/高可靠性答案,以及 2)对语言模型没有先前数据进行训练的新技术/问题领域提供答案。OverflowAI 的设计就是为了直接抓住这个机会。

OverflowAI 的作用

他们押注的三个关键方面是——直接回答问题、在开发环境中的可用性,以及提升企业内的知识。

OverflowAI 搜索提供了以问答格式直接回答用户(类似于 ChatGPT),但提供了多个实际 StackOverflow 帖子的链接。除了帮助建立信任外,这还为用户提供了深入了解的机会,当 AI 提供的答案无法完全解决用户的问题时。这在简单问题时提供直接答案,而在复杂问题时引导用户沿着更探索性的路径前进,达到了微妙的平衡。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

OverflowAI 搜索(来源:OverflowAI 演示视频

如果用户对回应不满意,他们可以进入一个聊天式界面以询问后续问题。如果答案都不令人满意,他们可以请求 StackOverflow代他们起草问题,准备好发布到问答论坛。这种体验还能避免用户提问时,问题已经被回答过的半常见情况。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

自动问题草稿(来源:OverflowAI 演示视频

该产品还通过扩展在 Visual Studio Code 中提供所有这些功能,进一步提升了可用性。这帮助 StackOverflow 更有效地与原生集成的编码助手竞争,使开发人员能够在编码环境中获取答案(而不是需要切换上下文并从浏览器中搜索)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Visual Studio Code 内的扩展(来源:OverflowAI 演示视频

除此之外,对于企业客户,OverflowAI 正在创建将公司内部多个不同信息来源(内部问答、维基页面、文档库)集成的能力,为开发人员提供一个连贯的问答体验。能够利用内部和 StackOverflow 数据,并且将其轻松地展示在问答式界面中,对于工程组织来说,可以大大提高生产力。他们还计划推出一个 Slack 集成,以无缝界面展示这一功能。

OverFlowAI 的产品方法令人印象深刻,它利用公司核心资产(对难题的回答),在用户所在的任何地方(无论是 Slack 还是开发环境中)提供高度可用的界面,并创造了一个用户可以利用生成式 AI 提交新问题的循环。

StackOverflow 策略中的潜在趋势

StackOverflow 并不是一个公开上市的公司——它由 Prosus 所拥有,Prosus 又是更大控股公司 Naspers 的一部分,而 Naspers 是上市公司。因此,很难获得准确的收入数据,但 Prosus 在2022 年 5 月发布的报告提供了一些线索:

  • 公司在 2022 年的收入约为 8900 万美元,其中 50%来自企业产品 StackOverflow for Teams,50% 来自 Reach 产品(广告和雇主品牌)。

  • 从 2021 年到 2022 年,StackOverflow for Teams 的收入增长了 69%,而 Reach 产品的收入下降了 12%(2022 年的收入可能受到其他因素的影响,如招聘放缓)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

来自 StackOverflow 所有者 Prosus 的收入数据(报告于 2022 年 5 月

这些收入数据结合 OverflowAI 产品的功能指向了 StackOverflow 在生成式 AI 领域的发展趋势(这些趋势也可以扩展到其他问答平台):

  1. 他们的广告业务,其成功直接与流量挂钩,正在下滑。这并不一定意味着严重问题,只是指出了一个更广泛的趋势——消费者会直接获得更简单问题的答案(这很好),因此广告成为了一个不那么关键的收入来源

  2. StackOverflow 将继续成为解决困难问题的宝贵来源,并且问题和答案的数量将随着公司推动生成式 AI 自动草拟/提交问题而继续增长。此外,如果 StackOverflow 能够维持内容引擎的运转,平台上的内容质量将会提高,因为重复/简单的问题将不再是内容的最高量。

  3. StackOverflow 将加倍努力构建能够为用户提供最大价值的体验(如 OverflowAI 搜索和 Visual Studio Code 扩展),并专注于那些客户愿意为这些优质体验付费的产品线(例如 StackOverflow for Teams)。

  4. 数据许可项目,即他们向 AI 公司收费以用于培训其数据,将会加速发展。

所有趋势都指向一个方向,即 StackOverflow 正在成功转型进入公司下一个阶段,公司已经做出了正确的产品/业务投资,以应对可能的干扰。此外,他们还提供了宝贵的社区服务,并为其他问答平台制定了可供借鉴的操作手册。总体来说,我对他们的发展方向感到乐观,并相信这将激发未来蓬勃发展的内容生态系统。

🚀 如果你喜欢这篇文章,可以考虑订阅我的每周通讯 每周,我会发布一篇深度分析关于当前技术话题/产品策略的文章,阅读时间约为 10 分钟。最好的祝愿,Viggy。

Unpacked | Viggy Balagopalakrishnan | Substack

对当前技术和商业话题的深入分析,帮助你保持领先地位。每周送到你的收件箱…

Unpacked | Viggy Balagopalakrishnan | Substack

你应该在 FAANG 还是初创公司开始你的职业生涯?

原文:towardsdatascience.com/startup-vs-big-firm-how-will-your-data-science-skills-flourish-as-you-kickstart-your-career-7ba47f468e23

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

期望在学习环境和技能发展方面得到什么

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Khouloud El Alami

·发表于 Towards Data Science ·10 分钟阅读·2023 年 9 月 4 日

不确定是加入初创公司还是大型科技公司开始你的职业生涯?如果你正面临这个十字路口,这篇文章就是为你准备的!

我现在是 Spotify 的一名数据科学家,但三年前,我只是一个学生,并且在这个问题上挣扎了很久。

我是更适合在初创公司还是成熟公司工作呢?

毕竟这是一个重大决定!

但与选择一盒麦当劳鸡块还是最新的“大培根芝士麦克”(或者他们现在叫它们什么)不同,这个问题涉及的是为我的整个职业生涯设定轨迹。

当我在折磨自己思考这个问题时,我意识到我并不是唯一一个这样的人。实际上,很多人都面临着这个困境。

这篇文章是一个系列的第一篇,在这个系列中,我将从不同的角度讨论这个话题,所有这些角度都是你需要考虑的。

我将结合我在 Spotify 的个人经验以及我在过程中遇到的其他人的经验。

所以如果你正在职业生涯的第一个阶段,那么请确保关注我。

(这也有助于我在 Medium 上获得更多曝光,让那些能从我的故事中受益的人更容易找到它们)

为什么我们现在还要在乎这个问题?

因为公司的规模可以很好地指示你的职业生涯将如何随着时间的推移而发展。这在你刚刚迈入职业世界的初期尤其如此。

因此,了解你的目标是什么在确定哪个选项最符合这些目标方面变得至关重要

  • 你是否觉得自己可以全身心投入到各种混合的事物中,边学边做,在混乱中蓬勃发展,同时享受戴上多顶帽子的刺激?

  • 或者你可能更喜欢深入某一特定领域,从专家的指导中学习,并在明确定义的路线图上工作?

花点时间反思一下你希望从这段旅程中得到什么。你认为自己在哪方面会最成功?

没有对错之分。

但首先在评论区分享你的经历,我们来讨论一下吧!

在评论区分享一下你自己在这个话题上的经历吧!

无论你是数据科学新手还是老手,你的故事可能会成为别人灵感的源泉。我很想了解你的经历,我相信其他人也会感兴趣!

  • 也许有些轶事?

  • 意外的转折?

  • “我希望有人告诉我”时刻?

让我们将这不仅仅变成一个独白,而是一个社区对话!

让我告诉你我是如何做出选择的!

当我以实习生身份加入 Spotify 时,我第一次踏入了科技行业的企业世界,我不知道会有什么样的期待。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我在 2021 年 Spotify 办公室的第一天

我不断问自己,是否最好和大科技公司里的专家一起成长✨,还是宁愿在初创公司里做一个全能型人才🃏。

所以我联系了 Spotify 的数据科学家,询问他们在不同公司规模下的经历。我的朋友们也在其他地方实习,所以我也确保问了他们。

在与许多专业人士交流后,我意识到我在这两者之间的选择将会影响我的职业发展,具体体现在:

  • 技能发展 → 专注于一个领域或在多个领域中多样化。

  • 学习环境 → 指导条件、可用性及质量。

  • 职业发展与前景 → 晋升路径以及公司外的未来机会+薪酬。

  • 专业联系 → 在行业中建立一个强大的网络并获得外部曝光。

  • 影响潜力 → 通过我的贡献产生影响。

我已经收集了足够的数据,并且可以早早地权衡加入哪一方的利弊。

三年后,我对自己做出的决定充满信心*(不仅仅是因为它是 Spotify)。*

在这篇文章中,我将涵盖技能发展与学习环境部分。更具体地说,如何期望在初创公司或大公司中技能的成长,以及你可能获得的学习经历。

想听听我的个人见解吗?好吧,准备好!

在初创公司成为全能型人才

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由Erik Mclean提供,来源于Unsplash

#1. 初创公司的数据科学家通常身兼多职

这意味着你可能会做数据工程、数据库架构、机器学习建模等。根据你的需求,你会做这些工作。

你可能会接近成为我们所称的全栈数据专家。这对你的职业生涯可能大有裨益,尤其是如果你希望走一条多才多艺的道路。

我一个在初创公司工作的朋友最初被聘为机器学习工程师。然而,她很快发现自己需要处理数据工程任务,因为这些任务在产品发布时非常紧急。

一周,她在构建数据库架构,下一周,她又回到微调机器学习模型上。

在初创公司,你可以处理的任务类型非常多样化。这个角色可能会如此动态,以至于你可能常常感觉自己每几天就要更换一次职业。

这对于你来说可能特别令人兴奋,如果你:

  • 🫶 强烈相信产品

  • 👷 在从零开始的环境中茁壮成长

  • 🤓 喜欢在各种方面工作

  • 👥 享受在小型快速发展的团队中工作

这种环境允许多样性。因此,如果你还在探索数据世界的广度或喜欢承担多样化的责任,初创公司可能是你的终极选择

#2. 你可能无法学到最佳实践,但你可以在过程中创造它们

初创公司通常资源有限,无法指导和培养新毕业生的技能。

这点尤其重要,因为如果你对自己做的事情没有信心,你可能会搞砸而没有人指出你的错误。

我一个在初创公司工作的朋友被抛到了一个没有指导的仪表板构建任务中。整个团队都是初级人员,当时没有数据负责人,因此学习阶段非常缓慢。

他最初的版本效率低下,但他努力优化。随着时间的推移,他的试错方法演变成了一套整个初创公司采纳的最佳实践。

在初创公司,你很可能需要通过试错来学习,这从长远来看是有回报和令人满意的。特别是当你看到自己劳动的成果时。

但在那之前,你首先得从错误中艰难地学习。如果没有人教你正确的方法,你可能会学到不良的做法,然后不得不重新构建一切。

#3. 这可能以专业化为代价

在初创公司,你可能会对业务有一个全面的了解,这确实是如此。你还会更好地理解一切是如何连接和融入拼图中的,这也确实是这样。

但请记住,这可能以专业化为代价。特别是如果你主要对数据科学感兴趣,而对数据工程或数据领域的其他方面不那么关心的话。

另一位在初创公司工作的朋友遇到了这个问题。他最初对机器学习充满热情,但他发现自己投入了商业分析、产品管理,甚至一些即将发布的研究论文的基准测试任务中。

他喜欢获得新技能,但也觉得他最初的专业方向在行动中渐渐丢失了。他仍然难以理解真正的机器学习工程师的角色是什么。

毕竟,名言确实说过“全才而无专才”。

这在一开始是如此,尤其是如果创业公司仍处于早期阶段。然而,随着创业公司成熟,机会将会出现,因为它开始为你已经填补的角色招聘人员。

在成熟企业中如美酒般 aging

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由 Hermes Rivera 提供,在 Unsplash

#1. 如果你优先考虑专业化,成熟的公司可能是你最好的选择

你可以专注于在核心工作上获得专业知识,因为许多基础工作,从数据开发到技术执行,都是由专门的专家处理的。

在 Spotify, 我不必自己构建和启动 A/B 测试。工程师、产品经理和设计师都会参与其中。作为数据科学家,我们也参与其中,但我们专注于实验的数据方面,如指标、样本量计算及所有其他统计含义。

我们还可以与数据工程师合作,帮助他们捕捉更多数据并提高数据质量。我们自己不去做这些工作。

#2. 你还会早早学习到最佳实践,并与专家一起更快成长

根据公司雇佣的才俊来挑选公司可以为你提供无与伦比的导师机会。这就像是在专业领域中的常春藤联盟。最棒的是?他们支付你去学习,而不是相反。

在 Spotify 的每一天, 我都会直接接受顶级数据科学家多年经验的指导。我每天都感激能从最优秀的人那里学习,这是我最看重的。我喜欢认为我正在专业领域中的哈佛。

如果你的目标是迅速且充分地发展你的技能,这一点非常重要!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

看看你!(图片由作者提供,Midjourney)

#3. 你可能会被限制在某个特定的领域细分中

你可能没有太多机会探索该领域的其他方面,因为专门的专家已经在处理这些事情。

因此,你必须主动跟上领域中的最新动态,特别是考虑到人工智能的发展速度。你可不希望 30 岁时像奶奶一样对技术一无所知。

我保持在这个领域中的状态所做的事情:

  • 关注数据科学和人工智能的最新趋势。 Towards Data Science 做出贡献也帮助我跟上领域中的动态。

  • 与其他数据专业人士合作,如数据工程师和机器学习工程师,并对他们的角色保持好奇。

    如果机会没有出现,主动去创造它。尝试把这个想法带到你的团队面前,或者联系这些人并安排与他们的会议。人们总是乐意告诉你关于他们自己的事情!

#4. 他们有既定的协议、程序和基础设施

这些已经由那些知道自己在做什么的人铺设好了。

这意味着你从一开始就已经融入了良好的工作方式。这些工作方式将对你的整个职业生涯有价值。

如果有一天你醒来,意识到你想加入一家初创公司,你可能会对他们产生巨大附加价值,因为你将带来一大箱的好实践。

这也意味着你有更多的工具可以使用。

— 你不必从头开始创建数据仓库,它已经存在。— 你可能不需要手动设计 A/B 测试,可能已经有工具为你优化了这个过程。

**在 Spotify,**我们使用内部平台来启动我们的实验。这使我们能够在平台上直接创建、运行、管理和分析实验,而无需在笔记本中逐项处理。

重要的事情要考虑!!

许多这些考虑因素广泛适用,但在不同公司之间有所不同。

我在 Spotify 工作,它自 2006 年以来已经存在,所以我们可以称之为一家成熟公司,但一些经验方面仍然与初创公司相似。我们的 CHRO Katarina Berg 最准确地描述了为*“受控混乱”*。

这就是为什么评估这些因素在每个公司基础上是必不可少的。

如何? 联系你感兴趣的公司的某个人,询问他们的成长机会和学习环境。

你还可以在面试中使用这些作为深刻的问题,特别是如果你不确定该问什么。

最终,我加入了一家更成熟的公司,因为我相信这是对我来说的最佳决定——我想专注于数据科学。我并不一定对涉足所有数据相关领域如数据工程感兴趣。

但再说一次,这个选择是非常个人化的,你的选择也应该如此!

📌 附言:如果你发现这篇文章很有见地,请给我留个便条或评论,我很想知道!

我有礼物送给你🎁!

注册我的通讯 K’s DataLadder,你将自动获得我的终极 SQL 速查表,包含我在大科技公司每天使用的所有查询+另一个秘密礼物!

我每周分享作为数据科学家在科技领域的工作经验,提供实用技巧、技能和故事,旨在帮助你提升——因为没人真正知道,直到他们亲身经历!

如果你还没有做到这一点

  • 订阅我的YouTube频道。新视频很快就会发布!

  • 关注我在InstagramLinkedInX上的动态,选择你喜欢的方式。

很快见!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值