机器学习的特征存储-一-

57 阅读1小时+

机器学习的特征存储(一)

原文:annas-archive.org/md5/b415bac641e475b2a74b5ca99453c99f

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

数据驱动的决策一直是任何企业成功的关键,机器学习ML)在实现这一点和帮助企业保持竞争优势中发挥着关键作用。尽管机器学习有助于释放企业的真正潜力,但在路上有许多障碍。根据一项研究,90%的机器学习模型从未进入生产阶段。模型开发与产业化之间的脱节以及不良或平庸的机器学习实践是导致这种情况的许多原因之一。这就是为什么有那么多端到端机器学习平台提供简化机器学习开发的解决方案。这些平台的主要目标之一是鼓励数据科学家/机器学习工程师遵循机器学习操作MLOps)标准,这些标准有助于加快模型的生产化。近年来,特征管理已成为机器学习平台的一个目标——无论是内部构建还是作为平台即服务PaaS)提供。能够创建、共享和发现精选机器学习特征的特征存储已成为大多数这些机器学习平台的一个基本组成部分。

本书的目标是讨论特征存储在机器学习管道中的重要性。因此,我们将从一个机器学习问题开始,尝试在没有特征存储的情况下开发模型。然后,我们将讨论哪些机器学习的方面可以从特征存储中受益,以及特征存储的一些功能不仅有助于创建更好的机器学习实践,而且有助于模型更快、更经济高效地开发。随着我们从“为什么”使用特征存储过渡到“是什么”和“如何”这两个方面,我们将通过实际案例研究,探讨特征工程、模型训练、推理以及批量在线模型的产业化。在本书的第一和第二部分,我们将使用开源特征存储 Feast。在最后一部分,我们将寻找市场上可用的替代方案,并尝试使用托管特征存储进行端到端用例的测试。

本书面向对象

这本书面向数据/机器学习/平台工程师、数据科学家,以及想要了解特征管理、如何在 AWS 云上部署 Feast、如何创建精选机器学习特征、以及如何在模型构建中使用和协作其他数据科学家进行批量在线模型预测,以及将模型从开发转移到生产的特征科学爱好者。这本书将对从小型大学项目到企业级机器学习应用的各种机器学习项目有益。

本书涵盖内容

第一章, 机器学习生命周期概述,首先对机器学习进行了简要介绍,然后深入探讨了机器学习的一个用例——客户终身价值模型。本章概述了机器学习开发的各个阶段,最后讨论了机器学习中最耗时的部分,以及理想世界和现实世界在机器学习开发中的样子。

第二章, 特征存储解决的问题是什么?,介绍了本书的主要焦点,即特征管理和特征存储。它讨论了特征在生产系统中的重要性,将特征引入生产的不同方法,以及这些方法的常见问题,随后讨论了特征存储如何克服这些常见问题。

第三章, 特征存储基础、术语和用法,首先介绍了开源特征存储——Feast,然后是安装过程、特征存储领域使用的不同术语以及基本 API 使用。最后,简要介绍了在 Feast 中协同工作的不同组件。

第四章, 将特征存储添加到机器学习模型中,将帮助读者在 AWS 上安装 Feast,逐步通过截图介绍不同的资源创建,如 S3 存储桶、Redshift 集群和 Glue 目录。最后,它回顾了在第一章机器学习生命周期概述中开发的客户终身价值模型的特征工程方面,并在 Feast 中创建和摄取了精选特征。

第五章, 模型训练和推理,从第四章将特征存储添加到机器学习模型中的结尾继续,讨论了特征存储如何帮助数据科学家和机器学习工程师在机器学习模型的开发中进行协作。它讨论了如何使用 Feast 进行批量模型推理,以及如何构建用于在线模型推理的 REST API。

第六章, 模型到生产及之后,讨论了使用 Amazon 管理 Apache Airflow 工作流MWAA)创建编排环境,使用前几章中构建的特征工程、模型训练和推理代码/笔记本,并将批量和在线模型管道部署到生产环境中。最后,讨论了生产之外的方面,如特征监控、特征定义的更改,以及构建下一个机器学习模型。

第七章Feast 替代方案和机器学习最佳实践,介绍了其他特征存储,如 Tecton、Databricks Feature Store、Google Cloud 的 Vertex AI、Hopsworks Feature Store 和 Amazon SageMaker Feature Store。它还介绍了后者的基本用法,以便用户了解使用托管特征存储的感觉。最后,它简要讨论了机器学习最佳实践。

第八章用例 - 客户流失预测,使用 Amazon SageMaker 的托管特征存储服务,通过一个端到端用例在电信数据集上预测客户流失。它还涵盖了特征漂移监控和模型性能监控的示例。

为了最大限度地利用本书

本书使用 AWS 服务进行 Feast 部署、管道编排,以及一些 SageMaker 产品。如果您创建了一个新的 AWS 账户,所有使用的服务都在免费层或特色产品中,除了 Apache Airflow(MWAA)环境。然而,我们已经列出了一些替代方案,可用于运行示例。

所有示例都是使用 Python 3.7 – feast==0.19.3运行的。适当的库版本也在笔记本中提及,如有必要。要运行示例,您只需要一个 Jupyter 笔记本环境(本地、Google Colab、SageMaker 或您选择的任何其他环境)以及每个章节或部分所需的 AWS 资源和权限。

图片

如果您使用的是本书的数字版,我们建议您亲自输入代码或从本书的 GitHub 仓库(下一节中有一个链接)获取代码。这样做将帮助您避免与代码复制粘贴相关的任何潜在错误。

为了最大限度地利用本书,您应该具备 Python 编程经验,并对笔记本、Python 环境、以及机器学习和 Python 机器学习库(如 XGBoost 和 scikit-learn)有基本了解。

下载示例代码文件

您可以从 GitHub(github.com/PacktPublis… GitHub 仓库中更新。

我们还提供了其他来自我们丰富图书和视频目录的代码包,可在github.com/PacktPublishing/找到。查看它们吧!

下载颜色图像

我们还提供了一份 PDF 文件,其中包含本书中使用的截图和图表的颜色图像。您可以从这里下载:static.packt-cdn.com/downloads/9…

使用的约定

本书使用了一些文本约定。

文本中的代码: 表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“前面的代码块调整了数值列:tenureMonthlyChargesTotalCharges。”

代码块应如下设置:

le = LabelEncoder()
for i in bin_cols:
    churn_data[i] = le.fit_transform(churn_data[i])

当我们希望您注意代码块中的特定部分时,相关的行或项目将以粗体显示:

project: customer_segmentation
registry: data/registry.db
provider: aws
online_store:
  type: dynamodb
  region: us-east-1

任何命令行输入或输出都应如下编写:

$ docker build -t customer-segmentation .

粗体: 表示新术语、重要单词或您在屏幕上看到的单词。例如,菜单或对话框中的单词以粗体显示。以下是一个示例:“在集群主页上,选择属性选项卡,然后向下滚动到关联的 IAM 角色。”

小贴士或重要提示

看起来像这样。

联系我们

我们欢迎读者的反馈。

一般反馈: 如果您对本书的任何方面有疑问,请通过 customercare@packtpub.com 给我们发邮件,并在邮件主题中提及书名。

勘误表: 尽管我们已经尽一切努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,我们将不胜感激,如果您能向我们报告,我们将不胜感激。请访问www.packtpub.com/support/err…并填写表格。

盗版: 如果您在互联网上以任何形式发现我们作品的非法副本,如果您能提供位置地址或网站名称,我们将不胜感激。请通过版权@packt.com 与我们联系,并提供材料的链接。

如果您有兴趣成为作者: 如果您在某个领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问authors.packtpub.com

分享您的想法

一旦您阅读了《机器学习特征存储》,我们很乐意听听您的想法!请点击此处直接进入此书的亚马逊评论页面并分享您的反馈。

您的评论对我们和科技社区都很重要,并将帮助我们确保我们提供高质量的内容。

第一章:第一不分 – 为什么我们需要特征存储?

本节主要关注特征存储(为什么)在机器学习ML)流程中的重要性。我们将从一个 ML 问题开始,并探讨 ML 开发的各个阶段,如数据探索、特征工程、模型训练和推理。我们将讨论特征在生产中的可用性如何影响模型性能,并开始探讨将特征引入生产以及与之相关的常见问题。在本节的最后,我们将介绍 ML 流程中的特征存储,并探讨它是如何解决其他替代方案难以克服的常见问题的。

本节包含以下章节:

  • 第一章, 机器学习生命周期概述

  • 第二章, 特征存储解决了哪些问题?

第二章:机器学习生命周期概述

机器学习ML)是计算机科学的一个子领域,涉及研究和探索可以使用统计分析学习数据结构的计算机算法。用于学习的数据集称为训练数据。训练的输出称为模型,然后可以用来对新数据集进行预测,该数据集模型之前未曾见过。机器学习有两个广泛的类别:监督学习无监督学习。在监督学习中,训练数据集被标记(数据集将有一个目标列)。算法旨在根据数据集中的其他列(特征)来学习如何预测目标列。预测房价、股市变化和客户流失是一些监督学习的例子。另一方面,在无监督学习中,数据未标记(数据集将没有目标列)。在这种情况下,算法旨在识别数据集中的共同模式。为未标记数据集生成标签的一种方法是使用无监督学习算法。异常检测是无监督学习的一个用例。

机器学习第一个数学模型的想法是在 1943 年由沃尔特·皮茨和沃伦·麦卡洛克提出的(《机器学习的历史:这一切是如何开始的?》)。后来,在 1950 年代,阿瑟·塞缪尔开发了一个玩冠军级电脑跳棋的程序。从那时起,我们在机器学习领域已经取得了长足的进步。如果你还没有阅读这篇文章,我强烈推荐你阅读。

现在,当我们试图向系统和设备教授实时决策时,机器学习工程师和数据科学家职位是市场上最热门的工作。预计到 2027 年,全球机器学习市场规模将从 2019 年的 83 亿美元增长到 1179 亿美元。如下所示,这是一组独特的技能集,与多个领域重叠:

图 1.1 – 机器学习/数据科学技能集

图 1.1 – 机器学习/数据科学技能集

在 2007 年和 2008 年,DevOps 运动彻底改变了软件开发和运营的方式。它缩短了软件的生产时间:

图 1.2 – DevOps

](tos-cn-i-73owjymdk6/1ce00bc9b310401fafebbfcd35f11ed9)

图 1.2 – DevOps

同样地,要将模型从实验阶段过渡到实际应用,我们需要一套标准化的流程,使这一过程无缝进行。嗯,对此的答案是机器学习运维MLOps)。许多行业专家已经发现了一套可以缩短 ML 模型生产时间的模式。2021 年是 MLOps 的一年——有很多新成立的初创公司正在试图满足那些在 ML 道路上落后的公司的 ML 需求。我们可以假设随着时间的推移,这将会不断扩大并变得更好,就像任何其他过程一样。随着我们的成长,将会出现许多发现和工作方式,最佳实践,以及更多将逐步发展。在这本书中,我们将讨论一个用于标准化 ML 及其最佳实践的常用工具:特征存储。

在我们讨论特征存储是什么以及如何使用它之前,我们需要了解机器学习生命周期及其常见疏忽。我想将本章奉献给学习机器学习生命周期的不同阶段。作为本章的一部分,我们将进行一个 ML 模型构建练习。我们不会深入探讨 ML 模型本身,如其算法或如何进行特征工程;相反,我们将关注 ML 模型通常会经历的阶段,以及模型构建与模型运维中涉及的困难。我们还将讨论耗时且重复的阶段。本章的目标是理解整体机器学习生命周期和模型运维中存在的问题。这将为本章后续章节奠定基础,我们将讨论特征管理、特征存储在 ML 中的作用,以及特征存储如何解决本章中我们将讨论的一些问题。

在本章中,我们将涵盖以下主题:

  • 实践中的机器学习生命周期

  • 理想世界与真实世界

  • 机器学习中最耗时的阶段

不再拖延,让我们动手实践一个 ML 模型。

技术要求

要跟随本书中的代码示例,您需要熟悉 Python 和任何笔记本环境,这可以是本地设置,如 Jupyter,或在线笔记本环境,如 Google Colab 或 Kaggle。我们将使用 Python3 解释器和 PIP3 来管理虚拟环境。您可以从以下 GitHub 链接下载本章的代码示例:github.com/PacktPublishing/Feature-Store-for-Machine-Learning/tree/main/Chapter01

实践中的机器学习生命周期

正如 HBO 的《新闻室》中杰夫·丹尼尔斯的角色所说,解决任何问题的第一步是认识到问题的存在。让我们遵循这一知识,看看它对我们是否适用。

在本节中,我们将选择一个问题陈述并逐步执行机器学习生命周期。一旦完成,我们将回顾并识别任何问题。以下图表显示了机器学习的不同阶段:

![图 1.3 – 机器学习生命周期图片

图 1.3 – 机器学习生命周期

让我们看一下我们的问题陈述。

问题陈述(计划和创建)

对于这个练习,让我们假设你拥有一家零售业务,并希望提高客户体验。首先,你想要找到你的客户细分和客户终身价值(LTV)。如果你在这个领域工作过,你可能知道不同的方法来解决这个问题。我将遵循 Barış Karaman 的中等博客系列了解你的指标 – 学习如何使用 Python 跟踪什么和如何跟踪towardsdatascience.com/data-driven…](www.kaggle.com/vijayuv/onl…

数据(准备和清理)

首先,让我们安装pandas包:

!pip install pandas

让我们将数据集提供给我们的笔记本环境。为此,请将数据集下载到您的本地系统,然后根据您的设置执行以下步骤之一:

  • .csv文件作为输入传递给pd.read_csv方法。

  • Google Colab:通过点击左侧导航菜单中的文件夹图标和上传图标来上传数据集。

让我们预览一下数据集:

import pandas as pd
retail_data = pd.read_csv('/content/OnlineRetail.csv'
                          encoding= 'unicode_escape')
retail_data.sample(5)

上述代码块的结果如下:

![图 1.4 – 数据集预览图片

图 1.4 – 数据集预览

如您所见,数据集包括客户交易数据。除了未标记的索引列外,数据集由八个列组成:

  • InvoiceNo:唯一的订单 ID;数据类型为整数

  • StockCode:产品的唯一 ID;数据类型为字符串

  • Description:产品的描述;数据类型为字符串

  • Quantity:已订购产品的单位数量

  • InvoiceDate:发票生成日期

  • UnitPrice:每单位产品的成本

  • CustomerID:订购产品的客户的唯一 ID

  • Country:产品订购的国家

一旦你有了数据集,在跳入特征工程和模型构建之前,数据科学家通常会进行一些探索性分析。这里的想法是检查你拥有的数据集是否足够解决该问题,识别缺失的差距,检查数据集中是否存在任何相关性,等等。

对于练习,我们将计算月收入并查看其季节性。以下代码块从InvoiceDate列提取年份和月份(yyyymm)信息,通过乘以UnitPriceQuantity列来计算每笔交易的revenue属性,并根据提取的年月(yyyymm)列汇总收入。

让我们从上一条代码语句继续:

##Convert 'InvoiceDate' to of type datetime
retail_data['InvoiceDate'] = pd.to_datetime(
    retail_data['InvoiceDate'], errors = 'coerce')
##Extract year and month information from 'InvoiceDate'
retail_data['yyyymm']=retail_data['InvoiceDate'].dt.strftime('%Y%m')
##Calculate revenue generated per order
retail_data['revenue'] = retail_data['UnitPrice'] * retail_data['Quantity']
## Calculate monthly revenue by aggregating the revenue on year month column  
revenue_df = retail_data.groupby(['yyyymm'])['revenue'].sum().reset_index()
revenue_df.head()

上述代码将输出以下数据框:

![图 1.5 – 收入数据框图片

图 1.5 – 收入数据框

让我们可视化revenue数据框。我将使用一个名为plotly的库。以下命令将在您的笔记本环境中安装plotly

!pip install plotly

让我们绘制一个条形图,从revenue数据框中,将yyyymm列放在x轴上,将revenue放在y轴上:

import plotly.express as px
##Sort rows on year-month column
revenue_df.sort_values( by=['yyyymm'], inplace=True)
## plot a bar graph with year-month on x-axis and revenue on y-axis, update x-axis is of type category.
fig = px.bar(revenue_df, x="yyyymm", y="revenue"
             title="Monthly Revenue"
fig.update_xaxes(type='category')
fig.show()

上述代码按yyyymm列对收入数据框进行排序,并绘制了revenue与年月(yyyymm)列的条形图,如下面的截图所示。如您所见,九月、十月和十一月是高收入月份。本应验证我们的假设与几年的数据,但不幸的是,我们没有这些数据。在我们继续到模型开发之前,让我们看一下另一个指标——每月活跃客户——并看看它是否与每月收入相关:

![图 1.6 – 每月收入图片

图 1.6 – 每月收入

在同一笔记本中继续,以下命令将通过在年月(yyyymm)列上聚合唯一的CustomerID计数来计算每月活跃客户:

active_customer_df = retail_data.groupby(['yyyymm'])['CustomerID'].nunique().reset_index()
active_customer_df.columns = ['yyyymm'
                              'No of Active customers']
active_customer_df.head()

上述代码将产生以下输出:

![图 1.7 – 每月活跃客户数据框图片

图 1.7 – 每月活跃客户数据框

让我们以前面的方式绘制前面的数据框:

## Plot bar graph from revenue data frame with yyyymm column on x-axis and No. of active customers on the y-axis.
fig = px.bar(active_customer_df, x="yyyymm"
             y="No of Active customers"
             title="Monthly Active customers"
fig.update_xaxes(type='category')
fig.show()

上述命令绘制了No of Active customers与年月(yyyymm)列的条形图。如下面的截图所示,每月活跃客户与前面截图所示的每月收入呈正相关:

![图 1.8 – 每月活跃客户图片

图 1.8 – 每月活跃客户

在下一节中,我们将构建一个客户 LTV 模型。

模型

现在我们已经完成了数据探索,让我们构建 LTV 模型。客户终身价值CLTV)定义为与客户在公司生命周期中相关的净盈利。简单来说,CLV/LTV 是对每个客户对业务价值的预测(参考:www.toolbox.com/marketing/customer-experience/articles/what-is-customer-lifetime-value-clv/)。预测终身价值有不同的方法。一种可能是预测客户的值,这是一个回归问题,另一种可能是预测客户群体,这是一个分类问题。在这个练习中,我们将使用后者方法。

对于这个练习,我们将把客户分为以下几组:

  • 低 LTV:不太活跃或收入较低的客户

  • 中 LTV:相当活跃且收入适中的客户

  • 高 LTV:高收入客户 – 我们不希望失去的细分市场

我们将使用 3 个月的数据来计算客户的最近度R)、频率F)和货币M)指标,以生成特征。一旦我们有了这些特征,我们将使用 6 个月的数据来计算每个客户的收入并生成 LTV 聚类标签(低 LTV、中 LTV 和高 LTV)。生成的标签和特征将被用于训练一个 XGBoost 模型,该模型可以用来预测新客户的群体。

特征工程

让我们在同一个笔记本中继续我们的工作,计算客户的 R、F 和 M 值,并根据从个人 R、F 和 M 分数计算出的值对客户进行分组:

  • 最近度(R):最近度指标表示客户上次购买以来过去了多少天。

  • 频率(F):正如术语所暗示的,F 代表客户进行了多少次购买。

  • 货币(M):特定客户带来的收入。

由于客户的消费和购买模式根据人口统计地理位置的不同而有所不同,因此在这个练习中,我们只考虑属于英国的数据。让我们读取OnlineRetails.csv文件,并过滤掉不属于英国的数据:

import pandas as pd
from datetime import datetime, timedelta, date
from sklearn.cluster import KMeans
##Read the data and filter out data that belongs to country other than UK
retail_data = pd.read_csv('/content/OnlineRetail.csv'
                           encoding= 'unicode_escape')
retail_data['InvoiceDate'] = pd.to_datetime(
    retail_data['InvoiceDate'], errors = 'coerce')
uk_data = retail_data.query("Country=='United Kingdom'").reset_index(drop=True)

在下面的代码块中,我们将创建两个不同的 DataFrame。第一个(uk_data_3m)将用于InvoiceDate2011-03-012011-06-01之间的数据。这个 DataFrame 将用于生成 RFM 特征。第二个 DataFrame(uk_data_6m)将用于InvoiceDate2011-06-012011-12-01之间的数据。这个 DataFrame 将用于生成模型训练的目标列。在这个练习中,目标列是 LTV 组/聚类。由于我们正在计算客户 LTV 组,较大的时间间隔将给出更好的分组。因此,我们将使用 6 个月的数据来生成 LTV 组标签:

## Create 3months and 6 months data frames
t1 = pd.Timestamp("2011-06-01 00:00:00.054000")
t2 = pd.Timestamp("2011-03-01 00:00:00.054000")
t3 = pd.Timestamp("2011-12-01 00:00:00.054000")
uk_data_3m = uk_data[(uk_data.InvoiceDate < t1) & (uk_data.InvoiceDate >= t2)].reset_index(drop=True)
uk_data_6m = uk_data[(uk_data.InvoiceDate >= t1) & (uk_data.InvoiceDate < t3)].reset_index(drop=True)

现在我们有两个不同的 DataFrame,让我们使用 uk_data_3m DataFrame 来计算 RFM 值。以下代码块通过将 UnitPriceQuantity 相乘来计算 revenue 列。为了计算 RFM 值,代码块对 CustomerID 执行了三次聚合:

  • 要在 DataFrame 中计算 max_date,必须计算每个客户的 R = max_date – x.max(),其中 x.max() 计算特定 CustomerID 的最新 InvoiceDate

  • 要计算特定 CustomerID 的发票数量 count

  • 要计算特定 CustomerIDrevenuesum 值。

以下代码片段执行此逻辑:

## Calculate RFM values.
uk_data_3m['revenue'] = uk_data_3m['UnitPrice'] * uk_data_3m['Quantity']
# Calculating the max invoice date in data (Adding additional day to avoid 0 recency value)
max_date = uk_data_3m['InvoiceDate'].max() + timedelta(days=1)
rfm_data = uk_data_3m.groupby(['CustomerID']).agg({
        'InvoiceDate'lambda x: (max_date - x.max()).days,
        'InvoiceNo''count',
        'revenue''sum'})
rfm_data.rename(columns={'InvoiceDate''Recency',
                         'InvoiceNo''Frequency',
                         'revenue''MonetaryValue'}, 
                         inplace=True)

在这里,我们已经计算了客户的 R、F 和 M 值。接下来,我们需要将客户分为 R、F 和 M 组。这种分组定义了客户在 R、F 和 M 指标方面相对于其他客户的位置。为了计算 R、F 和 M 组,我们将根据他们的 R、F 和 M 值将客户分为大小相等的组。这些值在之前的代码块中已经计算。为了实现这一点,我们将使用名为 pd.qcut (pandas.pydata.org/pandas-docs/stable/reference/api/pandas.qcut.html) 的方法在 DataFrame 上进行操作。或者,你可以使用任何 聚类 方法将客户分为不同的组。我们将把 R、F 和 M 组的值加起来,生成一个范围从 0 到 9 的单个值,称为 RFMScore

在这个练习中,客户将被分为四个组。可以使用 肘部方法 (towardsdatascience.com/clustering-metrics-better-than-the-elbow-method-6926e1f723a6) 来计算任何数据集的最佳组数。前面的链接还包含了关于你可以使用的其他计算最佳组数的替代方法的信息,所以请随意尝试。我将把它留给你作为练习。

以下代码块计算 RFMScore:

## Calculate RFM groups of customers 
r_grp = pd.qcut(rfm_data['Recency'], q=4
                labels=range(3,-1,-1))
f_grp = pd.qcut(rfm_data['Frequency'], q=4
                labels=range(0,4))
m_grp = pd.qcut(rfm_data['MonetaryValue'], q=4
                labels=range(0,4))
rfm_data = rfm_data.assign(R=r_grp.values).assign(F=f_grp.values).assign(M=m_grp.values)
rfm_data['R'] = rfm_data['R'].astype(int)
rfm_data['F'] = rfm_data['F'].astype(int)
rfm_data['M'] = rfm_data['M'].astype(int)
rfm_data['RFMScore'] = rfm_data['R'] + rfm_data['F'] + rfm_data['M']
rfm_data.groupby('RFMScore')['Recency','Frequency','MonetaryValue'].mean()

前面的代码将生成以下输出:

图 1.9 – RFM 分数摘要

图 1.9 – RFM 分数摘要

这份总结数据让我们对 RFMScore 如何与 RecencyFrequencyMonetaryValue 指标直接成比例有一个大致的了解。例如,RFMScore=0 的组具有最高的平均最近度(该组的最后购买日是过去最远的),最低的平均频率和最低的平均货币价值。另一方面,RFMScore=9 的组具有最低的平均最近度,最高的平均频率和最高的平均货币价值。

通过这样,我们了解到 RFMScore 与客户为业务带来的价值呈正相关。所以,让我们按照以下方式对客户进行细分:

  • 0-3 => 低值

  • 4-6 => 中值

  • 7-9 => 高值

以下代码将客户标记为低、中或高价值:

# segment customers.
rfm_data['Segment'] = 'Low-Value'
rfm_data.loc[rfm_data['RFMScore']>4,'Segment'] = 'Mid-Value' 
rfm_data.loc[rfm_data['RFMScore']>6,'Segment'] = 'High-Value' 
rfm_data = rfm_data.reset_index()

客户终身价值

现在我们已经准备好了包含 3 个月数据的 DataFrame 中的客户 RFM 特征,让我们使用 6 个月的数据(uk_data_6m)来计算客户的收入,就像我们之前做的那样,并将 RFM 特征与新创建的收入 DataFrame 合并:

# Calculate revenue using the six month dataframe.
uk_data_6m['revenue'] = uk_data_6m['UnitPrice'] * uk_data_6m['Quantity']
revenue_6m = uk_data_6m.groupby(['CustomerID']).agg({
        'revenue''sum'})
revenue_6m.rename(columns={'revenue''Revenue_6m'}, 
                  inplace=True)
revenue_6m = revenue_6m.reset_index()
revenue_6m = revenue_6m.dropna()
# Merge the 6m revenue data frame with RFM data.
merged_data = pd.merge(rfm_data, revenue_6m, how="left")
merged_data.fillna(0)

随意绘制 revenue_6mRFMScore 的关系图。您将看到两者之间存在正相关关系。

在下面的代码块中,我们使用 revenue_6m 列,这是客户的 终身价值,并使用 K-means 聚类创建三个组,分别称为 低 LTV中 LTV高 LTV。同样,您可以使用之前提到的 肘部方法 来验证最佳簇数量:

# Create LTV cluster groups
merged_data = merged_data[merged_data['Revenue_6m']<merged_data['Revenue_6m'].quantile(0.99)]
kmeans = KMeans(n_clusters=3)
kmeans.fit(merged_data[['Revenue_6m']])
merged_data['LTVCluster'] = kmeans.predict(merged_data[['Revenue_6m']])
merged_data.groupby('LTVCluster')['Revenue_6m'].describe()

前面的代码块生成以下输出:

图 1.10 – LTV 簇摘要

图 1.10 – LTV 簇摘要

如您所见,标签为 1 的簇包含了一组终身价值非常高的客户,因为该组的平均收入为 14,123.309,而这样的客户只有 21 位。标签为 0 的簇包含了一组终身价值较低的客户,因为该组的平均收入仅为 828.67,而这样的客户有 1,170 位。这种分组让我们知道哪些客户应该始终保持满意。

特征集和模型

让我们使用到目前为止计算出的特征来构建 XGBoost 模型,以便模型可以根据输入特征预测客户的 LTV 组。以下是将作为模型输入使用的最终特征集:

feature_data = pd.get_dummies(merged_data)
feature_data.head(5)

前面的代码块生成以下 DataFrame。这包括将用于训练模型的特征集:

图 1.11 – 模型训练特征集

图 1.11 – 模型训练特征集

现在,让我们使用这个特征集来训练 Xgboost 模型。预测标签(y)是 LTVCluster 列;除了 Revenue_6mCustomerID 列之外的数据集是 X 值。由于 LTVCluster 列(y)是使用 Revenue_6m 计算的,所以 Revenue_6m 将从特征集中删除。对于新客户,我们可以在不需要至少 6 个月的数据的情况下计算其他特征,并预测他们的 LTVCluster(y)

以下代码将训练 Xgboost 模型:

from sklearn.metrics import classification_report, confusion_matrix
import xgboost as xgb
from sklearn.model_selection import KFold, cross_val_score, train_test_split
#Splitting data into train and test data set.
X = feature_data.drop(['CustomerID', 'LTVCluster',
                       'Revenue_6m'], axis=1)
y = feature_data['LTVCluster']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1)
xgb_classifier = xgb.XGBClassifier(max_depth=5, objective='multi:softprob')
xgb_model = xgb_classifier.fit(X_train, y_train)
y_pred = xgb_model.predict(X_test)
print(classification_report(y_test, y_pred))

前面的代码块将输出以下分类结果:

图 1.12 – 分类报告

图 1.12 – 分类报告

现在,让我们假设我们对模型很满意,并希望将其提升到下一个层次——那就是生产环境。

包含、发布和监控

到目前为止,我们已经花费了大量时间研究数据分析、探索、清洗和模型构建,因为这是数据科学家应该关注的事情。但是一旦所有这些工作都完成了,模型是否可以不进行任何额外的工作就部署?答案是不了。我们离部署还远着呢。在我们能够部署模型之前,我们必须做以下事情:

  • 我们必须创建一个执行数据清洗和特征工程的数据管道。

  • 我们需要一个方法在预测期间获取特征。如果是一个在线/交易模型,应该有一种方法以低延迟获取特征。由于客户的 R、F 和 M 值经常变化,比如说,我们想在网站上针对中值和高值细分市场运行两个不同的活动。将需要近实时地对客户进行评分。

  • 找到一种方法使用历史数据重现模型。

  • 执行模型打包和版本控制。

  • 找到一种方法进行模型的 A/B 测试。

  • 找到一种方法监控模型和数据漂移。

由于我们没有准备好这些,让我们停下来回顾一下我们已经做了什么,看看是否有更好的方法,以及是否有任何常见的疏忽。

在下一节中,我们将探讨我们认为我们构建了什么(理想世界)与实际构建了什么(现实世界)

理想世界与现实世界

现在我们已经花费了大量时间构建了这个美丽的数据产品,可以帮助业务根据客户带来的价值来区别对待客户,让我们看看我们对它的期望与它能做什么之间的差异。

可复用性和共享

可复用性是 IT 行业中的常见问题之一。我们面前有关于产品的这些优秀数据,包括我们在探索过程中构建的图表和为我们的模型生成的特征。这些可以被其他数据科学家、分析师和数据工程师复用。就目前的状态而言,它们可以被复用吗?答案是可能。数据科学家可以共享笔记本本身,可以创建演示文稿等等。但是,没有人能够发现他们是否在寻找,比如说,客户细分或 RFM 特征,这些在其他模型中可能非常有用。所以,如果另一个数据科学家或 ML 工程师正在构建需要相同特征的模型,他们唯一的选择就是重新发明轮子。新模型可能基于数据科学家如何生成它,使用相同的、更准确或更不准确的 RFM 特征。然而,可能存在第二种模型的发展本可以通过更好的发现和复用工作来加速的情况。此外,正如俗话所说,三个臭皮匠,顶个诸葛亮。合作将使数据科学家和业务双方都受益。

笔记本中的所有内容

数据科学是一项独特的技能,与软件工程不同。尽管一些数据科学家可能拥有软件工程师的背景,但这个角色的需求本身可能会使他们远离软件工程技能。随着数据科学家在数据探索和模型构建阶段投入更多时间,集成开发环境IDEs)可能不足以应对他们处理的大量数据。如果我们必须在个人 Mac 或 PC 上进行数据探索、特征工程和模型构建,数据处理阶段可能会持续数天。此外,他们还需要有灵活性,能够使用不同的编程语言,如 Python、Scala、R、SQL 等,在分析过程中动态添加命令。这就是为什么有那么多笔记本平台提供商,包括 Jupyter、Databricks 和 SageMaker 的原因之一。

由于数据产品/模型开发与传统的软件开发不同,在没有额外工作的前提下,将实验代码部署到生产环境总是不可能的。大多数数据科学家从笔记本开始他们的工作,并以我们之前章节中的方式构建一切。一些标准实践和工具,如特征存储,不仅可以帮助他们将模型构建过程分解成多个生产就绪的笔记本,还可以帮助他们避免重新处理数据、调试问题和代码重用。

现在我们已经了解了机器学习开发的现实情况,让我们简要地回顾一下机器学习中最耗时的阶段。

机器学习最耗时的阶段

在本章的第一节中,我们介绍了机器学习生命周期的不同阶段。让我们更详细地看看其中的一些阶段,并考虑它们的复杂程度以及我们应该在每个阶段上花费多少时间。

确定数据集

一旦我们有了问题陈述,下一步就是确定我们需要的数据集来解决问题。在我们所遵循的例子中,我们知道数据集在哪里,并且它是已知的。然而,在现实世界中,事情并不那么简单。由于每个组织都有自己的数据仓库方式,找到所需的数据可能既简单也可能需要很长时间。大多数组织运行数据目录服务,如 Amundsen、Atlan 和 Azure Data Catalog,以便他们的数据集易于发现。但同样,工具的好坏取决于它们的使用方式或使用它们的人。因此,我想说的是,找到你想要的数据总是很容易的。除此之外,考虑到数据访问控制,即使你确定了解决问题所需的数据集,除非你之前已经使用过它,否则你很可能无法访问它。确定访问权限将是另一个主要的障碍。

数据探索和特征工程

数据探索:一旦你确定了数据集,下一个最大的任务是“再次确定数据集”!你读对了——对于数据科学家来说,下一个最大的任务是确保他们选择的数据集是解决问题的关键。这会涉及到数据清洗,补充缺失数据,转换数据,绘制不同的图表,找到相关性,发现数据偏斜等等。最好的部分是,如果数据科学家发现某些事情不对劲,他们会回到上一步,也就是寻找更多的数据集,再次尝试,然后返回。

特征工程同样不容易;领域知识成为构建特征集以训练模型的关键。如果你是一位过去几年一直在从事定价和促销模型工作的数据科学家,你就会知道哪些数据集和特征会比过去几年一直在从事客户价值模型工作的数据科学家得到更好的模型。让我们尝试一个练习,看看特征工程是否容易,以及领域知识是否起关键作用。看看下面的截图,看看你是否能识别出这些动物:

Figure 1.13 – A person holding a dog and a cat

img/B18024_01_13.jpg

图 1.13 – 持有狗和猫的人

我相信你知道这些动物是什么,但让我们退一步看看我们是如何正确识别这些动物的。当我们看图时,我们的潜意识进行了特征工程。它可能会选择一些特征,比如“它有两只耳朵”,两只“眼睛”,一个“鼻子”,一个“头”,和一个“尾巴”。然而,它选择了更复杂的特点,比如“它的脸型”,“它的眼睛形状”,“它的鼻子形状”,以及“它的毛发的颜色和质感”。如果它选择了第一组特征,这两种动物就会变成相同的,这是一个糟糕的特征工程和糟糕模型的例子。由于它选择了后者,我们将其识别为不同的动物。再次,这是一个好的特征工程和好的模型的例子。

但我们还需要回答的另一个问题是,我们是在什么时候发展出动物识别的专业知识的?嗯,可能是因为我们的幼儿园老师。我们都记得从老师、父母、兄弟姐妹那里学到的第一百种动物的一些版本。我们一开始并没有全部正确,但最终我们做到了。随着时间的推移,我们获得了专业知识。

现在,如果我们不是看到猫和狗的图片,而是看到两条蛇的图片,我们的任务是识别哪条是有毒的,哪条是无毒的。虽然我们都能识别出它们是蛇,但几乎没有人能识别出哪条是有毒的,哪条是无毒的。除非这个人以前是蛇笛手。

因此,在特征工程中,领域专业知识变得至关重要。就像数据探索阶段一样,如果我们对特征不满意,我们就回到了起点,这涉及到寻找更多数据和更好的特征。

模型到生产与监控

一旦我们确定了上述阶段,除非有合适的基础设施准备就绪并等待,否则将模型投入生产将非常耗时。为了使模型在生产中运行,它需要一个处理平台来运行数据清洗和特征工程代码。它还需要一个编排框架以计划或基于事件的方式运行特征工程管道。在某些情况下,我们还需要一种安全地以低延迟存储和检索特征的方法。如果模型是事务性的,模型必须打包以便消费者可以安全访问,可能作为一个 REST 端点。此外,部署的模型应该具有可扩展性,以服务即将到来的流量。

模型和数据监控也是至关重要的方面。由于模型性能直接影响业务,你必须知道哪些指标会决定模型需要提前重新训练。除了模型监控之外,数据集也需要监控偏差。例如,在电子商务业务中,流量模式和购买模式可能会根据季节性、趋势和其他因素频繁变化。及早识别这些变化将对业务产生积极影响。因此,数据和特征监控是模型投入生产的关键。

摘要

在本章中,我们讨论了机器学习生命周期中的不同阶段。我们选择了一个问题陈述,进行了数据探索,绘制了一些图表,进行了特征工程和客户细分,并构建了客户终身价值模型。我们审视了疏忽之处,并讨论了机器学习中最耗时的阶段。我希望你们能和我站在同一页面上,为本书的其余部分打下良好的基础。

在下一章中,我们将设定需要特征存储的背景,以及它如何可以改进机器学习过程。我们还将讨论将特征引入生产的需求以及一些传统的实现方式。

第一章:机器学习生命周期的概述

第一章:机器学习生命周期的概述

第三章:功能存储解决了哪些问题?

在上一章中,我们讨论了机器学习(ML)生命周期中的不同阶段,ML 的困难和耗时阶段,以及我们离理想世界还有多远。在本章中,我们将探讨 ML 的一个领域,即 ML 特征管理。ML 特征管理是创建特征、将它们存储在持久存储中,并在模型训练和推理中大规模提供它们的过程。它是 ML 最重要的阶段之一,尽管它通常被忽视。在 ML 的早期阶段,数据科学/工程团队缺乏特征管理一直是将他们的 ML 模型投入生产的主要障碍。

作为数据科学家/ML 工程师,您可能已经找到了存储和检索 ML 模型特征的创新方法。但大多数情况下,我们构建的解决方案是不可重用的,每个解决方案都有局限性。例如,我们中的一些人可能正在使用 S3 存储桶来存储特征,而团队中的其他数据科学家可能正在使用事务数据库。一个人可能更习惯于使用 CSV 文件,而另一个人可能更喜欢使用 Avro 或 Parquet 文件。由于个人偏好和缺乏标准化,每个模型可能都有不同的管理特征的方式。另一方面,良好的特征管理应该做到以下几方面:

  • 使特征可发现

  • 导致模型易于复现

  • 加速模型开发和生产化

  • 在团队内部和团队之间促进特征的重复使用

  • 使特征监控变得简单

本章的目的是解释数据科学家和工程师如何努力实现更好的特征管理,但往往无法达到预期。我们将回顾团队采用的不同方法来将特征投入生产,这些方法中常见的常见问题,以及如何通过功能存储做得更好。到本章结束时,您将了解功能存储如何满足之前提到的目标,并在团队间提供标准化。

在本章中,我们将涵盖以下主题:

  • 特征在生产中的重要性

  • 将特征投入生产的方法

  • 将特征投入生产的方法中常见的常见问题

  • 功能存储的救星

  • 功能存储背后的哲学

特征在生产中的重要性

在讨论如何将特征投入生产之前,让我们了解为什么在生产中需要特征。让我们通过一个例子来了解。

我们经常使用出租车和外卖服务。这些服务的一个好处是它们会告诉我们出租车或食物到达需要多长时间。大多数时候,这个预测是大约正确的。它是如何准确预测的呢?当然,它使用机器学习。机器学习模型预测出租车或食物到达所需的时间。为了使这样的模型成功,不仅需要良好的特征工程和机器学习算法,还需要最新的特征。尽管我们不知道模型使用的确切特征集,但让我们看看一些动态变化且非常重要的特征。

在使用外卖服务时,影响配送时间的最主要因素包括餐厅、司机、交通和顾客。模型可能使用一组缓慢变化的特征,这些特征会定期更新,可能是每天或每周更新一次,以及一组每几分钟就会变化的动态特征。缓慢变化的特征可能包括餐厅在不同时间通过应用程序和亲自接收的平均订单数量、订单准备的平均时间等。这些特征可能看起来并不是缓慢变化的,但如果你仔细想想,平均订单数量可能会根据餐厅位置、季节性、一天中的时间、一周中的某一天等因素而有所不同。动态特征包括最后五笔订单所需的时间、过去 30 分钟内的取消订单数量,以及餐厅当前的订单数量。同样,司机特征可能包括与距离相关的平均订单配送时间、司机取消订单的频率,以及司机是否在取多个订单。除了这些特征之外,还会有交通特征,这些特征的变化更为动态。

由于存在许多动态特征,即使其中之一已经有一小时的历史,模型的预测也可能超出图表范围。例如,如果配送路线上发生车祸,交通特征没有捕捉到并用于推理,模型将预测食物会比实际到达得更快。同样,如果模型无法获取餐厅当前的订单数量,它将使用旧值并预测一个可能远离真相的值。因此,模型获取的越新特征,预测将越好。另外,还有一个需要注意的事情是,应用程序不会提供特征;应用程序只能提供诸如餐厅 ID 和顾客 ID 等信息。模型将不得不从不同的位置获取特征和事实,理想情况下是从特征存储库中获取。无论特征是从哪里获取的,为其服务的基础设施必须根据流量进行扩展和缩减,以有效地使用资源,并能够以非常低的延迟率处理请求,错误率极低,如果有的话。

就像外卖服务一样,我们在第一章中构建的模型需要推理过程中的特征,并且特征越新,客户的终身价值LTV)预测就会越好。良好的预测将导致更好的行动,从而带来卓越的客户体验,进而提高客户亲和力和更好的业务。

将特征引入生产的方法

既然我们了解了在生产中需要特征,那么让我们看看一些传统的将特征引入生产的方法。让我们考虑两种类型的管道:批量模型管道和在线/事务模型管道:

  • 批量模型:这些是在预定时间运行的模型,例如每小时、每天、每周等。两种常见的批量模型是预测和客户细分。批量推理比其对应物更容易和更简单,因为它没有延迟要求;推理可以运行几分钟或几小时。批量模型可以使用如 Spark 这样的分布式计算框架。此外,它们可以用简单的基础设施运行。大多数机器学习模型最初是批量模型,随着时间的推移,根据可用的基础设施和需求,它们最终成为在线/事务模型。

虽然批量模型的架构简单易建和管理,但这些模型存在一些缺点,例如预测并不总是最新的。由于预测存在时间滞后,可能会给业务带来成本。例如,假设一家制造厂使用订单预测模型来获取原材料。根据批量预测模型的时间滞后,企业可能需要承担原材料短缺的成本,或者在仓库中过度储备原材料。

  • 在线/事务模型:在线模型遵循拉取范式;预测将在需求时生成。在线模型利用当前现实情况并用于预测。在线模型是事务性的,需要低延迟的服务,并且应根据流量进行扩展。一个典型的在线模型是推荐模型,这可能包括产品推荐、设计推荐等等。

虽然实时预测听起来很吸引人,但在线模型面临不同的挑战。构建一个延迟为 8 小时的程序比构建一个延迟为 100 毫秒的程序要容易。在线模型的延迟通常在毫秒级别。这意味着模型有几分钟的时间来确定最新的值(这意味着为模型生成或获取最新的特征)并预测结果。为了实现这一点,模型需要一个支持基础设施来提供预测所需的数据。在线模型通常作为 REST API 端点托管,这也需要扩展、监控等。

既然我们了解了批量模型和在线模型之间的区别,让我们看看批量模型管道是如何工作的。

批量模型管道

如前所述,批处理模型管道的延迟要求可以从几分钟到几小时不等。批处理模型通常按计划运行,因此它们将使用 Airflow 或 AWS Step Functions 等工具进行编排。让我们看看一个典型的批处理模型管道以及如何将特征带入生产。

图 2.1 描述了典型的批处理模型管道:

图 2.1 – 批处理模型管道

图 2.1 – 批处理模型管道

第一章《机器学习生命周期概述》中所述,一旦模型开发完成并准备投入生产,笔记本将被重构以删除不需要的代码。一些数据工程师还将单个笔记本分解成多个逻辑步骤,例如特征工程、模型训练和模型预测。重构的笔记本或从笔记本生成的重构 Python 脚本使用 Airflow 等编排框架进行调度。在一个典型的管道中,第一阶段将从不同的数据源读取原始数据,执行数据清洗,并执行特征工程,这些特征将被管道的后续阶段使用。一旦模型预测阶段完成,预测输出将被写入持久存储,可能是数据库或 S3 存储桶。结果将在需要时从持久存储中访问。如果管道中的某个阶段由于任何原因(例如数据可访问性问题或代码错误)失败,管道将被设置为触发警报并停止进一步执行。

如果你还没有注意到,在批处理模型管道中,特征是在管道运行时生成的。在某些情况下,它还会使用最新数据重新训练一个新模型,而在其他情况下,它使用之前训练的模型,并使用在管道运行时可用数据生成的特征进行预测。如图图 2.1所示,每个新构建的模型都是从原始数据源开始,重复相同的步骤,并加入生产管道列表。我们将在后面的部分讨论这种方法中存在的问题。接下来,让我们看看在线模型中如何将特征带入生产的不同方法。

在线模型管道

在线模型有在近实时服务特征的特殊要求,因为这些模型是面向客户的或需要在实时做出业务决策。在线模型将特征带入生产的方法有很多。让我们在本节中逐一讨论它们。需要注意的是,这些方法并不完全符合每个人的做法;它们只是群体方法的表示。不同的团队使用这些方法的不同的版本。

将特征与模型打包

要部署在线模型,首先必须将其打包。同样,团队根据他们使用的工具遵循不同的标准。有些人可能会使用打包库,如 MLflow、joblib 或 ONNX。其他人可能会直接将模型打包为 REST API Docker 镜像。如第一章中的图 1.1所述,数据科学家和数据工程师具有不同的技能集,理想的方法是向数据科学家提供使用 MLflow、joblib 和 ONNX 等库打包模型的工具,并将模型保存到模型注册表中。然后,数据工程师可以使用注册的模型构建 REST API 并部署它。还有现成的支持,可以使用简单的命令行界面CLI)命令将 MLflow 打包的模型部署为 AWS SageMaker 端点。它还支持使用 CLI 命令构建 REST API Docker 镜像,然后可以在任何容器环境中部署。

虽然如 MLflow 和 joblib 之类的库提供了一种打包 Python 对象的方法,但它们也支持在需要时添加额外的依赖项。例如,MLflow 提供了一套内置风味,以支持使用 scikit-learn、PyTorch、Keras 和 TensorFlow 等 ML 库打包模型。它为 ML 库添加了所有必需的依赖项。使用内置风味打包模型与以下代码一样简单:

mlflow.<MLlib>.save_model(model_object)
## example scikit-learn
mlflow.sklearn.save_model(model_object)

除了所需的依赖项外,您还可以打包features.csv文件,并在模型的predict方法中加载它。尽管这可能听起来像是一个简单的部署选项,但这种方法的结果并不远逊于批处理模型。由于特征与模型一起打包,因此它们是静态的。原始数据集的任何变化都不会影响模型,除非使用从最新数据生成的新特征集构建的新版本模型并将其打包。然而,这可能是从批处理模型到在线模型的一个很好的第一步。我之所以这么说,是因为您现在将其作为基于拉的推理来运行,而不是作为批处理模型。此外,您已经为模型的消费者定义了 REST 端点输入和输出格式。唯一待定的步骤是将最新特征传递给模型,而不是打包的静态特征。一旦实现这一点,模型的消费者就不需要做出任何更改,并且将使用最新可用数据进行预测。

基于推的推理

与需要时才评分的拉式推理不同,在基于推的推理模式中,预测是主动运行的,并保存在事务数据库或键值存储中,以便在请求到来时以低延迟提供服务。让我们看看使用基于推的推理的在线模型的典型架构:

图 2.2 – 基于推的推理

图 2.2 – 基于推的推理

图 2.2 展示了基于推的推理架构。这里的想法与批量模型推理类似,但不同之处在于该管道还考虑了实时数据集,这些数据集是动态变化的。基于推的模型的工作方式如下:

  • 实时数据(例如,用户与网站的交互)将被捕获并推送到队列,如 Kafka、Kinesis 或 Event Hubs。

  • 特征工程管道根据需要生成模型特征的数据订阅特定的主题集或特定的队列集。这也取决于工具和架构。根据应用的大小/多样性,可能只有一个队列或多个队列。

  • 每当队列中出现感兴趣的事件时,特征工程管道将选择此事件并使用其他数据集重新生成模型的特征。

    注意

    并非所有特征都是动态的。有些特征可能变化不大或不太频繁。例如,客户的地理位置可能不会经常改变。

  • 新生成的特征用于运行数据点的预测。

  • 结果存储在事务数据库或键值存储中。

  • 当需要时,网站或应用将查询数据库以获取特定 ID(例如,当在网站上为顾客提供推荐时使用CustomerId)的新预测。

  • 每当队列中出现新的感兴趣事件时,此过程会重复。

  • 每个新的机器学习模型都将添加一个新的管道。

这种方法可能看起来简单直接,因为这里唯一的额外要求是实时流数据。然而,这也有局限性;整个管道必须在毫秒内运行,以便在应用进行下一次预测查询之前提供推荐。这是可行的,但可能涉及更高的运营成本,因为这不仅仅是一个管道:每个实时模型的管道都必须有类似的延迟要求。此外,这不会是一个复制粘贴的基础设施,因为每个模型在处理入站流量时都会有不同的要求。例如,处理订单特征的模型可能需要较少的处理实例,而处理点击流数据的模型可能需要更多的数据处理实例。还需要注意的是,尽管它们可能看起来像是在写入同一个数据库,但大多数情况下,涉及的是不同的数据库和不同的技术。

让我们看看下一个更好的解决方案。

基于拉的推理

与基于推的推理相反,在基于拉的推理中,预测是在请求时运行的。不是存储预测,而是将特定模型的特征集存储在事务数据库或键值存储中。在预测期间,特征集可以以低延迟访问。让我们看看基于拉推理模型的典型架构和涉及的组件:

图 2.3 – 使用事务/键值存储进行特征

图 2.3 – 使用事务/键值存储进行特征

图 2.3 展示了将特征引入生产的一种另一种方式:基于拉的机制。管道的一半工作方式与我们刚才讨论的基于推的推理相似。这里的区别在于,在特征工程之后,特征被写入事务数据库或键值存储。这些特征将由管道保持更新。一旦特征可用,模型的工作方式如下:

  1. 模型的 predict API 将具有类似于以下提到的合同:

    def predict(entity_id: str) -> dict
    
  2. 当应用程序需要查询模型时,它将使用 entity_id 打击 REST 端点。

  3. 模型将使用 entity_id 查询键值存储以获取评分模型所需的特征。

  4. 这些特征用于评分模型并返回预测结果。

如果您没有特征存储基础设施,这种方法是理想的。我们将在下一章中详细讨论这一点。再次强调,这种方法涉及一些问题,包括工作重复、部署和扩展特征工程管道以及管理多个键值存储基础设施等。

按需计算特征

在我们继续讨论这些方法的缺点之前,让我们讨论一种最后的方法。在迄今为止讨论的方法中,数据管道在数据到达或管道运行时主动计算特征。然而,当有推理请求时,可以按需计算特征。这意味着当应用程序查询模型进行预测时,模型将请求另一个系统获取特征。该系统使用来自不同来源的原始数据,并按需计算特征。这可能是最难实现的架构,但我听说在 TWIML AI 播客与 Sam Charrington 中,第 326 集:Metaflow,Ville Tuulos 的人中心数据科学框架,Netflix 有一个可以在秒级延迟下按需生成特征的系统。

predict API 可能看起来与最后一种方法中的类似:

def predict(entity_id: str) -> dict

然后它调用系统为给定的实体获取特征,使用这些特征进行预测,并返回结果。正如你可以想象的那样,所有这些都必须在几秒钟内完成。在实时执行的需求特征工程可能需要一个巨大的基础设施,其中包含不同存储系统之间的多个缓存。保持这些系统同步并不是一个容易的架构设计。对我们大多数人来说,这只是一个梦想中的基础设施。我至今还没有看到过。希望我们很快就能实现。

在本节中,我们讨论了将特征引入生产进行推理的多种方法。可能还有许多其他实现这一目标的方法,但大多数解决方案都是围绕这些方法之一的变化。现在我们了解了为什么以及如何将特征引入生产,让我们来看看这些方法常见的常见问题以及如何克服它们。

用于将特征引入生产的常用方法的问题

上一节讨论的方法看起来像是好的解决方案。然而,每个方法不仅有其自身的技术难题,如基础设施规模、遵守服务级别协议(SLA)以及与不同系统的交互,而且它们还有一些共同的问题。在技术领域不断增长直到达到饱和水平之前,这是预料之中的。我想将本节专门用于讨论这些方法中存在的常见问题。

重新发明轮子

工程中常见的一个问题就是构建已经存在的东西。造成这种情况的原因可能有很多;例如,一个正在开发解决方案的人可能不知道它已经存在,或者现有的解决方案效率低下,或者有额外的功能需求。在这里我们也遇到了同样的问题。

在许多组织中,数据科学家在一个特定的领域内工作,并得到一个支持他们的团队的帮助,这个团队通常包括机器学习工程师、数据工程师和数据分析师。他们的目标是让他们的模型投入生产。尽管并行工作的其他团队也有让他们的模型投入生产的目标,但由于他们的日程安排和交付时间表,他们很少相互协作。正如第一章所讨论的,团队中的每个角色都有不同的技能组合,对现有工具的经验水平不同,偏好也不同。此外,不同团队的数据工程师很少有相同的偏好。这导致每个团队都寻找一种将他们的模型投入生产的解决方案,这涉及到构建特征工程管道、特征管理、模型管理和监控。

在提出一个成功的解决方案后,即使团队(我们称之为团队 A)与其他团队分享他们的知识和成功,你得到的回应也只会是“知道了”,“很有趣”,“可能对我们有用”。但这永远不会转化为其他团队的解决方案。原因并不是其他团队对团队 A 取得的成果漠不关心。除了知识之外,团队 A 在很多情况下所构建的很多东西是不可重用的。其他团队剩下的选择是复制代码并适应他们的需求,希望它能工作,或者实施一个看起来相似的流程。因此,大多数团队最终都会为模型构建自己的解决方案。有趣的是,即使在大多数情况下,团队 A 也会为下一个他们工作的模型重新构建相同的流程。

特征重新计算

让我们从一个问题开始。问问自己:你的手机有多少内存? 很可能你心里已经有了答案。如果你不确定,你可能会检查设置中的内存并回答。无论如何,如果我在一个小时后或其他人问你同样的问题,我非常确信你不会在回答之前再次进入手机的设置进行检查,除非你更换了手机。那么为什么我们在所有的机器学习流程中都要这样做特征?

当你回顾之前讨论的方法时,它们都存在一个共同的问题。假设团队 A 成功完成了一个客户 LTV 模型并将其投入生产。现在团队 A 被分配了另一个项目,即预测客户的下一个购买日。有很大可能性,在客户 LTV 模型中有效的特征在这里同样有效。尽管这些特征是定期计算以支持生产模型的,但团队 A 将重新从原始数据开始,从头计算这些特征,并用于模型开发。不仅如此,他们还会复制整个流程,尽管存在重叠。

由于这种重新计算,根据团队 A 的设置和使用的工具,他们将会浪费计算资源、存储空间和人力,而如果有了更好的特征管理,团队 A 本可以在新项目中取得领先,这同样是一个成本效益高的解决方案。

特征可发现性和共享

如前所述,一个问题是在同一团队内部进行重新计算。这个问题的另一部分甚至更大。那就是跨团队和领域的重新计算。就像在“重新发明轮子”部分中,团队试图弄清楚如何将机器学习特征带入生产一样,数据科学家在这里也在重新发现数据和特征。

这其中的一个主要驱动因素是缺乏信任和可发现性。我们先来谈谈可发现性。每当数据科学家在构建模型时,如果他们出色地完成了数据挖掘、探索和特征工程,那么分享这些成果的方式非常有限,正如我们在第一章中讨论的那样。数据科学家可以使用电子邮件和演示文稿来分享这些成果。然而,没有任何方式可以让任何人发现可用的资源,并选择性地构建尚未构建的内容,然后在模型中使用它们。即使有可能发现其他数据科学家的工作,但没有弄清楚数据访问和重新计算特征的情况下,也无法使用这些工作。

数据发现和特性工程中重新发明轮子的另一个驱动因素是信任。尽管有明确的证据表明有一个使用生成特征的生成模型在生产中运行,但数据科学家往往很难信任其他人为生成特性开发的程序。由于原始数据是可信的,因为它将具有服务等级协议和模式验证,数据科学家通常最终会重新发现并生成特性。

因此,这里所需的解决方案是一个可以使他人生成的特性可发现、可共享,最重要的是可信赖的应用程序,即拥有和管理他们生成的特性的人/团队。

训练与服务的偏差

机器学习中另一个常见问题是训练与服务的偏差。这种情况发生在用于为模型训练生成特征的特性工程代码与用于为模型预测/服务生成特征的代码不同时。这种情况可能由许多原因引起;例如,在模型训练期间,数据科学家可能使用了 PySpark 来生成特征,而在将管道投入生产时,接手该任务的 ML/数据工程师可能使用了生产基础设施所需的不同技术。这里有几个问题。一个是存在两个版本的特性工程代码,另一个问题是这可能导致训练与服务的偏差,因为两个版本的管道生成相同原始数据输入的数据可能不同。

模型可复现性

模型可复现性是机器学习中需要解决的常见问题之一。我听说过一个故事,一位数据科学家离职后,他正在工作的模型丢失了,他的团队多次无法复现该模型。其中一个主要原因是缺乏特性管理工具。当你拥有原始数据的历史时,你可能会问复现相同模型的问题在哪里。让我们来看看。

假设有一个数据科学家,名叫 Ram,他正在开发一个机器学习模型,用于向客户推荐产品。Ram 花了一个月的时间来开发这个模型,并提出了一个出色的模型。在团队中数据工程师的帮助下,该模型被部署到生产环境中。但是,Ram 在这个职位上没有得到足够的挑战,所以他辞职并跳槽到了另一家公司。不幸的是,生产系统崩溃了,Ram 没有遵循 MLOps 标准将模型保存到注册表中,因此模型丢失且无法恢复。

现在,重建模型的职责落到了团队中的另一位新数据科学家 Dee 身上,她聪明且使用了与 Ram 相同的 dataset,并进行了与 Ram 相同的数据清洗和特征工程,仿佛 Dee 是 Ram 的转世。不幸的是,Dee 的模型无法得到与 Ram 相同的结果。无论 Dee 尝试多少次,她都无法重现该模型。

造成这种情况的一个原因是数据随着时间的推移发生了变化,这反过来又影响了特征值,从而影响了模型。没有办法回到过去产生第一次使用时的相同特征。由于模型的可重现性/可重复性是机器学习的一个关键方面,我们需要进行时间旅行。这意味着数据科学家应该能够回到过去,从过去某个特定时间点获取特征,就像在复仇者联盟:终局之战中一样,这样模型就可以一致地重现。

低延迟

所有这些方法试图解决的问题之一是低延迟特征服务。提供低延迟特征的能力决定了模型能否作为在线模型或批量模型托管。这涉及到构建和管理基础设施以及保持特征更新等问题。由于没有必要将所有模型都设置为事务性的,同时也有很高的可能性,一个用于批量模型的特征可能对不同的在线模型非常有用。因此,能够开关低延迟服务将极大地便利数据科学家。

到目前为止,在本节中,我们已经讨论了上一节中讨论的方法的一些常见问题。仍然悬而未决的问题是,我们能做些什么来使情况变得更好?是否存在一个或一组现有的工具,可以帮助我们解决这些常见问题?事实证明,答案是是的,有一个工具可以解决我们迄今为止讨论的所有问题。它被称为特征存储。在下一节中,我们将看看什么是特征存储,它们如何解决问题,以及背后的哲学。

特征存储来拯救

让我们从特征存储的定义开始这一节。特征存储是一个用于管理和为生产中的模型提供机器学习特征的运营数据系统。它可以从低延迟的在线存储(用于实时预测)或离线存储(用于扩展批量评分或模型训练)向模型提供特征数据。正如定义所指出的,它是一个完整的包,帮助你创建和管理机器学习特征,并加速模型的运营化。在我们深入了解特征存储之前,让我们看看引入特征存储后机器学习管道架构如何变化:

![图 2.4 – 带有特征存储的机器学习管道图片

图 2.4 – 带有特征存储的机器学习管道

图 2.4 展示了包含特征存储的机器学习管道架构。你可能觉得 图 2.4图 2.3 看起来一样,我只是用一个更大的存储替换了一堆小数据存储,并称之为特征存储。是的,可能看起来是这样,但还有更多。与传统数据存储不同,特征存储有一套特殊的特性;它不是一个数据存储,而是一个数据系统(正如其定义所述),并且它能做比仅仅存储和检索更多的事情。

由于特征存储是机器学习管道的一部分,整个管道的工作方式如下:

  1. 一旦数据科学家有了问题陈述,起点将不再是原始数据。它将是特征存储。

  2. 数据科学家将连接到特征存储,浏览存储库,并使用感兴趣的特性。

  3. 如果这是第一个模型,特征存储可能为空。从这里,数据科学家将进入发现阶段,确定数据集,构建特征工程管道,并将特征导入特征存储。特征存储将特征工程管道与机器学习中的其他阶段解耦。

  4. 如果特征存储不为空,但特征存储中可用的特征不足,数据科学家将发现感兴趣的数据,并添加另一个特征工程管道将一组新特征导入特征存储。这种方法使得特征对数据科学家正在工作的模型以及其他发现这些特征对其模型有用的数据科学家可用。

  5. 一旦数据科学家对特征集满意,模型将进行训练、验证和测试。如果模型性能不佳,数据科学家将返回去发现新的数据和特征。

  6. 当模型准备好部署时,模型的 predict 方法将包含用于生成模型预测所需特征的代码。

  7. 如果是在线模型,准备好的模型将以 REST 端点部署;否则,它将用于执行批量预测。

现在我们已经了解了管道的工作原理,让我们回顾上一节中讨论的问题,并了解特征存储是如何解决它们的。

使用特征存储标准化机器学习

一旦特征存储在团队层面得到标准化,尽管可能有不同的读取数据和构建特征工程管道的方式,但超出特征工程之外,管道的其余部分成为标准实现。机器学习工程师和数据科学家不必想出新方法将特征带到生产中。在特征工程之后,数据科学家和机器学习工程师将特征摄入特征存储。根据定义,特征存储可以以低延迟提供特征。在此之后,所有机器学习工程师需要做的就是更新他们的predict方法,从特征存储中获取所需特征并返回预测结果。这不仅使机器学习工程师的生活变得容易,有时也减轻了管理特征管理基础设施的负担。

特征存储避免了数据重新处理

如定义所述,特征存储有一个离线存储,离线存储中的数据可以用于模型训练或批量推理。这里的模型训练并不意味着训练相同的模型。输入到特征存储的特征可以用于训练另一个模型。

让我们以我们在讨论问题时使用的相同例子为例:团队-A 刚刚完成了客户 LTV 模型的投产部署。团队-A 接下来要开始工作的模型是预测下一次购买日期。当数据科学家开始工作在这个模型上时,他们不必回到原始数据并重新计算构建客户 LTV 模型所使用的特征。数据科学家可以连接到特征存储,该存储正在更新为前一个模型的最新特征,并获取训练新模型所需的特征。然而,数据科学家将不得不为从原始数据中找到的有用特征构建数据清洗和特征工程管道。再次强调,新添加的特征可以在下一个模型中重用。这使得模型开发既高效又经济。

特征可以通过特征存储进行发现和共享

在上一段中,我们讨论了团队内部特征的重用。特征存储帮助数据科学家实现这一点。另一个主要问题是由于缺乏特征可发现性,跨团队重新计算和重新发现有用的数据和特征。猜猜看?特征存储也能解决这个问题。数据科学家可以连接到特征存储,浏览现有的特征表和模式。如果他们发现任何现有特征有用,数据科学家可以在模型中使用它们,而无需重新发现或重新计算。

另一个与共享相关的问题是信任。尽管特征存储不能完全解决这个问题,但它在一定程度上解决了它。由于特征表是由团队创建和管理的,数据科学家可以随时联系所有者以获取访问权限,并讨论其他方面,如服务等级协议和监控。如果你还没有注意到,特征存储促进了团队之间的协作。这对双方都有益,不同团队的数据科学家和机器学习工程师可以一起工作,分享彼此的专业知识。

没有更多的训练与服务的偏差

使用特征存储,训练与服务的偏差将永远不会发生。一旦特征工程完成,特征将被摄入到特征存储中,特征存储是模型训练的来源。因此,数据科学家将使用特征存储中的特征来训练机器学习模型。一旦模型训练完成并部署到生产环境,生产模型将再次从在线存储或历史存储中获取数据以进行模型预测。由于这些特征同时被训练和预测使用,服务是通过相同的管道/代码生成的,我们永远不会在特征存储中遇到这个问题。

使用特征存储实现模型的可复现性

之前讨论的架构中另一个主要问题是模型的可复现性。这是一个问题:数据频繁变化,这反过来又导致特征变化,进而导致模型变化,尽管使用的是相同的特征集来构建模型。解决这个问题的唯一方法就是回到过去,获取产生旧模型的相同状态数据。这可能是一个非常复杂的问题,因为它将涉及多个数据存储。然而,可以以这种方式存储生成的特征,使得数据科学家能够进行时间旅行。

是的,这正是特征存储所做的事情。特征存储有一个离线存储,用于存储历史数据,并允许用户回到过去,在特定时间点获取特征的值。使用特征存储,数据科学家可以从历史中的特定时间点获取特征,因此可以一致地复现模型。模型的可复现性不再是特征存储的问题。

使用特征存储以低延迟提供特征

尽管所有解决方案都能以某种方式实现低延迟的服务,但解决方案并不统一。机器学习工程师必须想出一个解决方案来解决这个问题,并构建和管理基础设施。然而,在机器学习管道中拥有特征存储使得这变得简单,并且将基础设施管理卸载给其他团队,在平台团队管理特征存储的情况下。即使没有那样,能够运行几个命令并使低延迟服务上线也是机器学习工程师的一个实用工具。

特征存储背后的哲学

在本章中,我们讨论了与机器学习管道相关的问题,以及特征存储如何帮助数据科学家解决这些问题并加速机器学习的发展。在本节中,让我们尝试理解特征存储背后的哲学,并尝试弄清楚为什么在我们的机器学习管道中拥有特征存储可能是加速机器学习的理想方式。让我们从一个现实世界的例子开始,因为我们正在尝试通过机器学习建立现实世界的经验。你将得到两款手机的名称;你的任务是判断哪一款更好。名称是 iPhone 13 Pro 和 Google Pixel 6 Pro。你有无限的时间来找到答案;在你找到答案后继续阅读。

正如拉尔夫·瓦尔多·爱默生所说,重要的不是目的地,而是旅程。无论你的答案是什么,无论你花了多长时间到达那里,让我们看看你是如何到达那里的。有些人可能立刻就得到了答案,但如果你没有使用过这两款手机,你可能会在谷歌上搜索iPhone 13 Pro 与 Google Pixel 6 Pro 对比。你会浏览几个链接,这些链接会给你提供手机的比较:

![Figure 2.5 – iPhone 13 Pro versus Google Pixel 6 Pro]

![img/B18024_02_05.jpg]

图 2.5 – iPhone 13 Pro 与 Google Pixel 6 Pro 对比

这是一种很好的比较两款手机的方法。有些人可能为了得到答案做了更多的工作,但我相信我们中没有一个人去买了这两款手机,阅读了苹果和谷歌提供的规格,每个月都使用它们,并在回答问题之前成为每个手机的专家。

在这个任务中,我们足够聪明,能够利用他人的专业知识和工作成果。尽管互联网上有许多比较,但我们选择了对我们有效的那一个。不仅在这个任务中,而且在大多数任务中,从购买手机到购买房屋,我们都试图利用专家意见来做出决定。如果你从某个角度来看,这些就是我们的决策特征。除了专家意见外,我们还包括我们自己的限制和特征,例如预算、如果是手机,内存大小;如果是汽车,座位数;如果是房屋,房间数。我们使用这些组合来做出决定并采取行动。在大多数情况下,这种方法是有效的,在某些情况下,我们可能需要进行更多研究,甚至成为专家。

在机器学习中使用特征存储库是一种尝试实现类似目标的方法;它就像是数据科学家的 Google。与 Google 的通用搜索不同,数据科学家正在寻找特定的事物,并且也在与其他数据科学家分享他们的专业知识。如果特征存储库中可用的内容不适合数据科学家,他们就会转向原始数据,进行探索、理解,成为该领域的专家,并针对特定实体(如产品、客户等)提出区分性特征。这种结合特征存储库的机器学习工作流程不仅可以帮助数据科学家利用彼此的专业知识,还可以标准化并加速机器学习的发展。

摘要

在本章中,我们讨论了机器学习特征管理中的常见问题、生产化机器学习模型的多种架构以及将特征带入生产的方法。我们还探讨了这些方法中涉及的问题以及特征存储库如何通过标准化实践和提供传统数据存储库不具备的额外功能来解决这些问题。

现在我们已经了解了特征存储库能提供什么,在下一章中,我们将深入探讨特征存储库,并探索术语、特征、特征存储库的典型架构等内容。

进一步阅读

第二部分 – 特征存储实战

本节主要关注机器学习(ML)中特征管理的是什么如何做方面。在本节中,我们将从开源特征存储介绍开始,介绍 Feast,然后是不同的术语和基本 API 用法。我们将重用我们在第一部分,*为什么我们需要特征存储?*中讨论的相同机器学习问题,在 AWS 上创建 Feast 基础设施,并将其纳入我们的机器学习管道。这种包含使我们能够查看特征存储如何将我们的机器学习管道解耦为不同的阶段,以及它对模型训练和推理带来的变化。一旦模型开发完成,我们将探讨特征存储的能力如何使模型进入生产变得容易,并有助于特征监控。

本节包括以下章节:

  • 第三章, 特征存储基础、术语和用法

  • 第四章, 将特征存储添加到机器学习模型

  • 第五章, 模型训练和推理

  • 第六章, 从模型到生产及更远

第四章:特征存储基础、术语和用法

在上一章中,我们讨论了将特征引入生产的需求以及实现这一目标的不同方式,同时审视了这些方法中常见的常见问题以及特征存储如何解决这些问题。我们对特征存储有了很多期望,现在是时候了解它们是如何工作的了。正如上一章所述,特征存储与传统数据库不同——它是一个用于管理机器学习特征的数据存储服务,一个可以用于存储和检索历史特征的混合系统,用于模型训练。它还可以以低延迟提供最新特征进行实时预测,以及以亚秒级延迟进行批量预测。

在本章中,我们将讨论特征存储是什么,它是如何工作的,以及特征存储领域使用的术语范围。对于本章,我们将使用最广泛使用的开源特征存储之一,称为Feast。本章的目标是让你了解 Feast 特征存储术语和 API 的基本用法,并简要了解其内部工作原理。

在本章中,我们将讨论以下主题:

  • Feast 简介和安装

  • Feast 术语和定义

  • Feast 初始化

  • Feast 使用

  • Feast 幕后

技术要求

要跟随本章中的代码示例,你只需要熟悉 Python 和任何笔记本环境,这可以是本地设置,如 Jupyter,或者在线笔记本环境,如 Google Colab 或 Kaggle。你可以从以下 GitHub 链接下载本章的代码示例:github.com/PacktPublishing/Feature-Store-for-Machine-Learning/tree/main/Chapter03

Feast 简介和安装

Feast是一个开源的特征管理系统,用于提供和管理机器学习特征。它是GoogleGojek之间的合作成果,随后被Linux Foundation AI and Data采用。Feast 最初是为Google Cloud Platform (GCP)构建的,后来扩展到可以在其他云平台如Amazon Web Services (AWS)Microsoft Azure上运行。如今,你还可以在本地基础设施上运行 Feast。云无关性是 Feast 相较于其他特征存储提供的最大优势。

然而,Feast 是一个自管理的基础设施。根据您的组织结构,您需要一个团队来创建和管理 Feast 的基础设施。在此需要注意的是,Feast 从 面向服务的架构(SOA)转变为基于 软件开发工具包(SDK)/命令行界面(CLI)。这使得小型团队能够快速安装、运行和实验 Feast,而无需花费大量时间在初始设置上,结果却发现 Feast 并不适合。然而,对于生产环境,工程团队可能需要管理多个基础设施来运行其项目集。如果您不喜欢自管理基础设施,Feast 有其他替代方案。这包括 Tecton,它是 Feast 当前的主要贡献者之一,SageMaker Feature Store,这是一个 AWS 管理的功能存储,Databricks Feature Store 以及更多。

现在我们简要了解了 Feast 是什么,让我们看看安装过程。与其他需要您在云上运行服务或注册云提供商的其他功能存储不同,Feast 可以在笔记本环境中安装,无需设置任何额外的服务。

以下命令将在您的笔记本环境中安装 Feast 的最新版本:

!pip install feast

是的,如果您想尝试它,安装和运行 Feast 您只需做这么多。然而,要与团队、开发者、预发布环境和生产环境协作,设置将涉及一些额外的步骤。我们将在下一组章节中介绍。现在,查看 API、术语和项目结构已经足够了。

在下一节中,我们将探讨 Feast 术语、初始化和一些 API。

Feast 术语和定义

在软件应用的新发现中,常常会诞生新的术语,或者在新的软件背景下重新定义一些现有的术语。例如,有向无环图(DAG)在一般情况下意味着一种图类型;而在 Airflow 的背景下(假设您已经熟悉它),它意味着定义一组任务及其依赖关系。同样,Feast 和更广泛的功能存储背景下有一系列常用的术语。让我们在本节中学习它们是什么。

实体实体是一组语义相关的特征集合。实体是特征可以映射到的域对象。在打车服务中,客户司机可以是实体,然后特征可以与相应的实体分组。

以下代码块是实体定义的示例:

driver = Entity(name='driver', value_type=ValueType.STRING,
                join_key='driver_id')

实体是功能视图的一部分,在功能摄取和检索过程中充当主键。在模型训练和预测期间,可以在主键上执行时间点连接和特征查找。

特征特征是一个可测量的单个属性它通常是观察特定实体的属性,但不必与实体相关联。例如,客户在网站上平均花费的时间可以是一个特征。一个非关联的特征可以是今天网站上新增客户的数量。以下代码块是一个特征定义的示例:

trips_today = Feature(name="trips_today", 
                      dtype=ValueType.INT64)

特征代表底层特征数据的列。如您在前面的示例中看到的,它有 namedtype 属性。

数据源:数据源代表底层数据。Feast 支持一系列数据源,包括 FileSource(本地、S3、GCS)、BigQueryRedshift

以下截图是一个数据源的示例:

图 3.1 – 数据源

图 3.1 – 数据源

如您在前面的图中看到的,数据集有一个 driver_id 实体,trips_todayrating 特征,以及一个 timestamp 列。您在 图 3.1 中看到的表格数据结构是一个 特征视图

特征视图:特征视图类似于数据库表,它表示特征数据在其源处的结构。特征视图由实体、一个或多个特征和数据源组成。特征视图通常围绕一个类似于数据库对象的领域对象进行建模。在某些情况下,特征视图可以是无实体的。

以下代码块是一个 FeatureView 定义的示例:

driver_stats_fv = FeatureView(
    name="driver_activity",
    entities=["driver"],
    ttl=timedelta(hours=2),
    features=[
        Feature(name="trips_today", dtype=ValueType.INT64),
        Feature(name="rating", dtype=ValueType.FLOAT),
    ],
    batch_source=BigQuerySource(
        table_ref="feast-oss.demo_data.driver_activity"
    )
)

如您在前面的代码块中看到的,FeatureView 有一个 driver 实体,trips_todayrating 特征,以及 BigQuerySource 作为数据源。根据特征存储库的不同,特征视图有其他同义词。例如,在 SageMaker Feature Store 中,它被称为 Feature Group,在 Databricks Feature Store 中,它被称为 Feature Table,在 Feast 的旧版本中,它被称为 Feature SetFeature Table

timestamp 列存储特定事件发生的信息(即,特定事件在系统中产生的时间)。此外,特征存储库提供灵活性,可以添加额外的列,如 创建时间摄取 API 调用时间 等。这使得数据科学家和数据工程师能够在过去任何时间重现系统的状态。为了在过去重现状态,系统执行 时刻点连接。在 Feast 中,此功能作为 API 原生提供。在其他系统中,用户可能需要编写代码来实现它。

让我们看看一个实际中时刻点连接的例子。以下数据集的模式与 图 3.1 中定义的 FeatureView 匹配。

图 3.2 – 时刻点连接数据集

图 3.2 – 时刻点连接数据集

在后面的部分中,您将看到,要获取历史数据,您需要一个类似于以下内容的实体 DataFrame:

图 3.3 – 时刻点连接实体 DataFrame

图 3.3 – 时刻点连接实体 DataFrame

当用户调用 store.get_historical_features() 时,带有 图 3.3 中的实体 DataFrame 和特征列表,Feast 执行 2022-01-01 23:52:20 的操作。时刻点连接 寻找具有最新时间戳的驾驶员特征。

以下截图显示了 时刻点连接 的实际操作:

图 3.4 – 时刻点连接

图 3.4 – 时刻点连接

FeatureView 的有效期为 2 小时。这表示特征从事件发生时起(event_timestamp + 2 hours 窗口)只存活 2 小时。时刻点连接的逻辑是 timestamp_in_data >= timestamp_in_entity_dataframetimestamp_in_entity_dataframe <= timestamp_in_data + ttl (2 hours)。如 图 3.4 所示,第一行在数据中没有匹配的窗口,而实体 DataFrame 的第二、三、四行分别对应于 2022-01-02 1:00:002022-01-01 4:00:002022-01-01 5:00:00 发生的事件的匹配窗口。按照相同的逻辑,实体 DataFrame 的最后一行在数据中没有匹配的窗口。

时刻点连接的输出 DataFrame 如下:

图 3.5 – 时刻点连接输出

图 3.5 – 时刻点连接输出

图 3.5 所见,对于没有匹配窗口的行,特征值是 NULL,而对于有匹配窗口的行,特征是可用的。

在下一节中,我们将学习如何初始化一个 Feast 项目,了解其内容以及基本 API 使用。

Feast 初始化

让我们打开一个新的笔记本,安装 feastPygments 库的特定版本,以便在查看文件时获得更美观的格式。以下代码安装所需的库:

!pip install feast==0.18.1
!pip install Pygments

让我们初始化 Feast 项目并查看文件夹结构和文件。以下代码块初始化了一个名为 demo 的 Feast 项目:

!feast init demo

前面的代码将输出以下行:

Feast is an open source project that collects anonymized error reporting and usage statistics. To opt out or learn more see https://docs.feast.dev/reference/usage
Creating a new Feast repository in /content/demo.

让我们忽略第一行的警告信息。在第二行,你可以看到 Feast 仓库的初始化位置。如果你使用 Google Colab,你会看到类似的路径,/content/<repo_name>;如果不是,仓库将创建在当前工作目录中。

要了解 feast init 命令在后台执行了什么,我们需要查看该命令创建的文件夹。你可以使用 Google Colab 的左侧导航栏查看文件或使用 CLI:

图 3.6 – 文件夹结构

图 3.6 – 文件夹结构

图 3.6 是 Google Colab 的快照。正如你所见,feast init命令为初学者创建了一个示例项目仓库。data文件夹中有一个driver_stats.parquet文件,以及一个example.py和一个feature_store.yaml文件。让我们查看这些文件,看看它们里面有什么。最容易理解的文件是data文件夹中的driver_stats.parquet文件。正如文件夹所说,它包含演示项目的示例数据。

以下代码块加载了driver_stats.parquet中的数据集,并显示其前十行:

import pandas as pd
df = pd.read_parquet("demo/data/driver_stats.parquet")
df.head(10)

前一个代码块产生了以下输出:

![图 3.7 – 示例数据集

![图片 B18024_03_007.jpg]

图 3.7 – 示例数据集

driver_stats.parquet文件是一个示例特征数据集,正如你在图 3.7 中所见。它包含驾驶员特征,如conv_rateavg_daily_trips。它还有额外的列,如event_timestampcreated。这些是用于执行点时间连接的特殊列,如前所述。

让我们来看看下一个feature_store.yaml文件。以下命令会打印文件内容:

!pygmentize demo/feature_store.yaml

前一个命令输出了以下内容:

project: demo
registry: data/registry.db
provider: local
online_store:
    path: data/online_store.db

feature_store.yaml文件包含以下变量:

  • project:这是项目名称。它使用feast init命令的输入作为项目名称。我们运行了feast init demo,因此项目名称是demo

  • registry:这个变量存储了项目的特征注册表路径。注册表存储了项目的所有元数据,包括FeatureViewEntityDataSources等。正如你所见,registry.db文件在data文件夹中尚未存在。它在运行apply命令时创建;我们将在Feast 使用部分查看它。

  • provider:这个变量定义了特征存储将要运行的位置。值设置为local,表示基础设施将在本地系统上运行。其他可能的值包括awsgcp等。对于awsgcp提供者,需要安装额外的依赖项,并且需要向feast init命令传递额外的参数。

  • online_store:正如online_store参数的名称所暗示的,它用于以低延迟存储和提供特征。默认情况下,它使用 SQLite,但 Feast 为在线存储提供了各种选项,从DynamoDB自定义存储。以下页面列出了在线存储的支持选项:docs.feast.dev/roadmap

  • offline_store:你不会在feature_store.yaml文件中看到这个变量。然而,这是另一个重要的参数,用于从提供的选项中设置历史存储。同样,Feast 在这里提供了很多灵活性:你可以从文件存储Snowflake中选择任何东西。上一个项目符号中的链接包含了关于支持离线存储的信息。

除了之前提到的之外,每个变量可能还包括基于所选选项的一些额外设置。例如,如果选择 Snowflake 作为离线存储,则需要额外的输入,如模式名称、表名称、Snowflake URL 等。

让我们看看 example.py 文件包含什么。以下命令打印了文件的内容:

!pygmentize -f terminal16m demo/example.py

前述命令的输出非常长,所以我们不会一次性查看所有内容,而是将其分解成几个部分。以下代码块包含了文件的第一个部分:

# This is an example feature definition file
from google.protobuf.duration_pb2 import Duration
from feast import Entity, Feature, FeatureView, FileSource, ValueType
""" Read data from parquet files. Parquet is convenient for local development mode. For production, you can use your favorite DWH, such as BigQuery. See Feast documentation for more info."""
Driver_hourly_stats = FileSource(
    path="/content/demo/data/driver_stats.parquet",
    event_timestamp_column="event_timestamp",
    created_timestamp_column="created",
)

在前面的代码块中,有一些来自已安装库的导入,但导入之后的部分对我们特别感兴趣。代码定义了一个类型为 FileSource 的数据源,并提供了 图 3.7 中样本数据的路径。如前所述,event_timestamp_columncreated_timestamp_column 列是特殊列,分别指示特定事件(数据中的行)发生的时间和行被摄入数据源的时间。

以下代码块包含了文件的第二个部分:

# Define an entity for the driver. You can think of entity as a primary key used to fetch features.
Driver = Entity(name="driver_id", 
                value_type=ValueType.INT64, 
                description="driver id",)

在前面的代码块中,定义了一个 driver_id 实体及其值类型和描述。

以下代码块包含了文件的最后一部分:

""" Our parquet files contain sample data that includes a driver_id column, timestamps and three feature column. Here we define a Feature View that will allow us to serve this data to our model online."""
Driver_hourly_stats_view = FeatureView(
    name="driver_hourly_stats",
    entities=["driver_id"],
    ttl=Duration(seconds=86400 * 1),
    features=[
        Feature(name="conv_rate", dtype=ValueType.FLOAT),
        Feature(name="acc_rate", dtype=ValueType.FLOAT),
        Feature(name="avg_daily_trips", 
                dtype=ValueType.INT64),
    ],
    online=True,
    batch_source=driver_hourly_stats,
    tags={},
)

前面的代码块包含一个 FeatureView。定义包含三个特征,conv_rateacc_rateavg_daily_trips,并使用文件第二部分中定义的 driver_id 实体和文件第一部分中定义的 driver_hourly_stats 批次源。除此之外,还有额外的变量:ttlonlinetagsttl 定义了特征存活的时间长度。例如,如果你将 ttl 设置为 60 秒,它将从事件时间开始只出现在检索中 60 秒。之后,它被视为已过期的特征。online 变量指示是否为 FeatureView 启用了在线存储。Tags 用于存储有关 FeatureView 的额外信息,如团队、所有者等,这些信息可能在特征发现中可用。

简而言之,example.py 文件包含了 demo 项目的实体、特征视图和数据源。这只是一个用于演示的起始模板。我们可以添加额外的实体、特征视图和数据源。

现在我们已经了解了基础知识以及基本项目结构,让我们熟悉一下 Feast API。

Feast 使用

在本节中,让我们继续在之前初始化 demo 项目的同一个笔记本中,注册特征视图和实体,并使用 Feast API 检索特征。

注册特征定义

以下代码块注册了在 example.py 文件中定义的所有实体和特征视图:

%cd demo
!feast apply

上述代码产生以下输出:

/content/demo
Created entity driver_id
Created feature view driver_hourly_stats
Created sqlite table demo_driver_hourly_stats

输出信息很简单,除了最后一行,它说在FeatureView中设置了online=Trueapply命令创建了registry.dbonline_store.db文件,这些文件已在feature_store.yaml中设置。

现在实体和特征视图已经注册,我们可以连接到特征存储并浏览现有的定义。

浏览特征存储

以下代码连接到特征存储并列出所有实体:

from feast import FeatureStore
store = FeatureStore(repo_path=".")
for entity in store.list_entities():
    print(entity.to_dict())

上述代码块在当前目录中查找feature_store.yaml文件,并使用store.list_entities() API 获取所有实体。同样,可以使用store.list_feature_views() API 获取所有可用的特征视图。我将把这留给你作为练习。

让我们在特征存储中添加一个新的实体和特征视图。

添加实体和特征视图

要添加一个新的实体和特征视图,我们需要一个特征数据集。目前,让我们使用numpy库生成一个合成数据集,并将其作为需要定义实体和特征视图的新特征。

以下代码生成合成特征数据:

import pandas as pd
import numpy as np
from pytz import timezone, utc
from datetime import datetime, timedelta
import random
days = [datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0).replace(tzinfo=utc) \
        - timedelta(day) for day in range(10)][::-1]
customers = [10011002100310041005]
customer_features = pd.DataFrame(
    {
        "datetime": [day for day in days for customer in customers], # Datetime is required
        "customer_id": [customer for day in days for customer in customers], # Customer is the entity
        "daily_transactions": [np.random.rand() * 10 for _ in range(len(days) * len(customers))], # Feature 1
        "total_transactions": [np.random.randint(100for _ in range(len(days) * len(customers))], # Feature 2
    }
)
customer_features.to_parquet("/content/demo/data/customer_features.parquet")
customer_features.head(5)

上述代码生成一个包含四个列的数据集,并将数据集写入/content/demo/data/。如果你在本地系统上运行此代码,请相应地设置customer_features.to_parquet API 调用的路径,该路径在上述代码块中被突出显示。

上述代码生成了图 3.8中所示的数据集:

图 3.8 – 合成客户数据

图 3.8 – 合成客户数据

图 3.4中数据集的EntityFeatureView的定义可以添加到现有的example.py文件中,或者你可以创建一个新的 Python 文件并添加以下代码块中的行。

以下代码块定义了图 3.8中数据集所需的EntityDataSourceFeatureView

from google.protobuf.duration_pb2 import Duration
from feast import Entity, Feature, FeatureView, FileSource, ValueType
#Customer data source
customer_features = FileSource(
    path="/content/demo/data/customer_features.parquet",
    event_timestamp_column="datetime"
)
#Customer Entity
customer = Entity(name="customer_id", 
                  value_type=ValueType.INT64, 
                  description="customer id",)
# Customer Feature view
customer_features_view = FeatureView(
    name="customer_features",
    entities=["customer_id"],
    ttl=Duration(seconds=86400 * 1),
    features=[
        Feature(name="daily_transactions",
                dtype=ValueType.FLOAT),
        Feature(name="total_transactions", 
                dtype=ValueType.INT64),
    ],
    online=True,
    batch_source=customer_features,
    tags={},
)

就像我们遇到的example.py文件一样,这个文件包含了customer_features数据源、customer实体和customer_features_view的定义。上传新创建的文件或更新的example.py文件到项目根目录(与现有example.py文件相同的目录)。

重要提示

不要删除example.py或替换其内容,但向文件中追加新实体或上传新文件。运行feast apply后,你应该有两个实体driver_idcustomer_id,以及两个特征视图driver_hourly_statscustomer_features

将文件上传/复制到根目录后,运行以下命令以应用新定义:

!feast apply

上述代码块生成了以下输出:

Created entity customer_id
Created feature view customer_features
Created sqlite table demo_customer_features

与上一个apply命令的输出类似,输出很简单。如果你再次浏览特征存储,你会看到更新的定义。我们将把这留给你作为练习。

生成训练数据

在上一节运行 apply 命令后,特征存储包含两个实体:driver_idcustomer_id,以及两个特征视图:driver_hourly_statscustomer_features。我们可以通过查询历史存储中的任一或两个特征视图来生成训练数据,使用相应的实体。在本例中,我们将查询 driver_hourly_stats 特征视图。请随意尝试使用 get_historical_features API 在 customer_features 上。

要生成训练数据,需要一个实体 DataFrame。实体 DataFrame 必须有以下两个列:

  • entity_id:这是在特征存储中定义的实体的 id。例如,要获取司机特征,你需要 driver_id 列以及需要的历史特征的值列表。

  • event_timestamp:每个 driver_id 的点时间戳,用于点时间戳连接。

以下代码块产生一个实体 DataFrame 以获取司机特征:

from datetime import datetime, timedelta
import pandas as pd
from feast import FeatureStore
# The entity DataFrame is the DataFrame we want to enrich with feature values
entity_df = pd.DataFrame.from_dict(
    {
        "driver_id": [100110021003],
        "event_timestamp": [
            datetime.now() – timedelta(minutes=11),
            datetime.now() – timedelta(minutes=36),
            datetime.now() – timedelta(minutes=73),
        ],
    }
)
entity_df.head()

上一代码块产生以下实体 DataFrame:

图 3.9 – 实体 DataFrame

图 3.9 – 实体 DataFrame

一旦你有了实体 DataFrame,从历史存储中获取数据就很简单了。所需做的只是连接到特征存储,并使用在上一代码块中创建的实体 DataFrame 和所需特征的列表调用 store.get_historical_features() API。

以下代码块连接到特征存储并获取实体的历史特征:

store = FeatureStore(repo_path=".")
training_df = store.get_historical_features(
    entity_df=entity_df,
    features=[
        "driver_hourly_stats:conv_rate",
        "driver_hourly_stats:acc_rate",
        "driver_hourly_stats:avg_daily_trips",
    ],
).to_df()
training_df.head()

你可能会注意到 API 的一个输入是一个特征列表。列表中元素的格式是 <FeatureViewName>:<FeatureName>。例如,要获取 conv_rate 特征,它是 driver_hourly_stats 特征视图的一部分,列表中的元素将是 driver_hourly_stats:conv_rate

以下代码块产生以下输出:

图 3.10 – 获取历史特征输出

图 3.10 – 获取历史特征输出

将特征加载到在线商店

历史数据源用于生成训练数据集,这也可以用于批量模型的预测。然而,我们已经知道对于在线模型,需要低延迟的特征服务。为了启用这一点,需要从历史数据源获取最新特征并将特征加载到在线商店。这可以通过 Feast 的单个命令完成。

以下命令将最新特征加载到在线商店:

!feast materialize-incremental {datetime.now().isoformat()}

命令接受一个时间戳作为输入之一,获取输入时间戳时的最新特征,并将特征加载到在线商店。在本例中,它是一个 SQLite 数据库。

上一行代码输出以下信息:

图 3.11 – Feast 实现输出

图 3.11 – Feast 实现输出

现在特征已在线存储中可用,它们可以在模型预测期间以低延迟进行检索。可以使用store.get_online_features()查询在线存储,并传递与查询历史数据时传递的列表格式相同的特征列表。

重要提示

feast materialize-incremental命令将所有现有的特征视图同步到在线存储(在这种情况下,SQLite)。在图 3.11的输出中,你可以看到两个特征视图:driver_hourly_statscustomer_features。你可以查询其中的任何一个。在这个例子中,我们正在查询driver_hourly_stats

以下代码块检索具有id值为10011004的司机的conv_rateavg_daily_trips

store = FeatureStore(repo_path=".")
feature_vector = store.get_online_features(
    features=[
        "driver_hourly_stats:conv_rate",
        "driver_hourly_stats:avg_daily_trips",
    ],
    entity_rows=[
        {"driver_id"1004},
        {"driver_id"1005},
    ],
).to_dict()
feature_vector

前面的代码块产生以下输出。如果特定实体行的值不存在,它将返回NULL值:

{'avg_daily_trips': [34, 256],
 'conv_rate': [0.9326972365379333, 0.07134518772363663],
 'driver_id': [1004, 1005]}

现在我们已经了解了 Feast 的基本知识,是时候简要了解幕后发生的事情以使其工作。在下一节中,我们将查看 Feast 组件并为在项目中集成 Feast 做好准备。

Feast 幕后

以下图表显示了构成 Feast 架构的不同组件:

图 3.12 – Feast 架构(v0.18)

图 3.12 – Feast 架构(v0.18)

如前图所示,Feast 中涉及许多组件。让我们逐一分析:

  • data文件夹是可选的;定义特征存储配置的feature_store.yml文件和定义特征定义的example.py文件构成了一个特征仓库。

  • 使用feast apply(从离线存储加载特征到在线存储),以及提供一套优秀的 API 供用户浏览 Feast 和查询在线和离线存储。我们在使用部分使用了 Feast SDK 的一些 API。

  • Feast 注册表:Feast 注册表使用对象存储来持久化特征定义,这些定义可以通过 Feast SDK 进行浏览。

  • 在线存储:在线存储是一个低延迟数据库,用于为模型预测提供最新特征。用户可以使用 Feast SDK 加载最新特征或查询在线存储。也可以使用流源将特征加载到在线存储中。

  • 离线存储:离线存储用于存储和检索历史数据。它还用于模型训练和批量评分。在 Feast 中,离线存储中的数据由用户管理。

Feast 中的数据流

以下步骤给出了 Feast 中的数据流示例:

  1. 数据工程师构建 ETL/数据管道以生成特征并将它们加载到 Feast 支持的离线存储中。

  2. 创建特征定义,定义 Feast 存储配置,并运行feast apply命令。

    重要提示

    特征存储配置涉及定义基础设施细节,因此也可能涉及基础设施的创建。

  3. 使用 Feast SDK,数据科学家/数据工程师连接到 Feast 仓库,并为模型生成训练数据。模型被训练,如果它不符合接受标准,可以通过添加额外的数据管道生成新特征。

  4. 步骤 1-3 将会再次执行。

    重要提示

    步骤 2 中,只需添加新的实体和特征定义。

  5. 使用 feast materialize 命令将特征从离线存储加载到在线存储。此命令可以安排在计划中运行,以使用如 Airflow 这样的编排工具加载最新特征。

  6. 训练好的模型与 Feast SDK 代码一起打包,以便在预测期间获取模型评分所需的特征。打包的模型被部署到生产环境中。

  7. 在预测期间,模型使用 Feast SDK 获取所需的特征,运行预测,并返回结果。

  8. 可以监控离线存储以确定是否是重新训练模型的时候了。

让我们接下来总结本章所学的内容,并继续在我们的实际项目中使用 Feast。

摘要

在本章中,我们讨论了特征存储世界中使用的术语,特别是与 Feast 相关的术语。然而,请注意,许多现有的特征存储使用类似的术语,所以如果你熟悉其中一个,理解其他的就很容易了。我们还讨论了 Feast 中的 时间点连接 如何工作,以及 Feast 的基本知识,如安装、初始化、项目结构和 API 使用。最后,我们探讨了 Feast 的组件以及模型在 Feast 中的操作化工作原理。

在下一章中,我们将使用我们在 第一章 中构建的模型,即 机器学习生命周期概述,学习它如何改变数据科学家和工程师的工作方式,并看看它如何为特征共享、监控以及我们 ML 模型的简单生产化打开新的大门。

进一步阅读

第五章:将特征存储添加到机器学习模型

在上一章中,我们讨论了在本地系统中安装 Feast,Feast 中的常见术语,项目结构的样子,以及使用示例的 API 使用方法,并对 Feast 架构进行了简要概述。

到目前为止,本书一直在讨论特征管理的问题以及特征存储如何使数据科学家和数据工程师受益。现在是时候让我们亲自动手,将 Feast 添加到机器学习管道中。

在本章中,我们将回顾在 第一章,《机器学习生命周期概述》中构建的 客户终身价值LTV/CLTV)机器学习模型。我们将使用 AWS 云服务而不是本地系统来运行本章的示例。正如在 第三章,《特征存储基础、术语和用法》中提到的,AWS 的安装与本地系统不同,因此我们需要创建一些资源。我将使用一些免费层服务和一些特色服务(前两个月使用免费,但有限制)。此外,我们在 第三章,《特征存储基础、术语和用法》中查看的术语和 API 使用示例,在我们尝试将 Feast 包含到机器学习管道中时将非常有用。

本章的目标是了解将特征存储添加到项目中的所需条件以及它与我们在 第一章,《机器学习生命周期概述》中进行的传统机器学习模型构建有何不同。我们将学习 Feast 的安装,如何为 LTV 模型构建特征工程管道,如何定义特征定义,我们还将查看 Feast 中的特征摄取。

我们将按以下顺序讨论以下主题:

  • 在 AWS 中创建 Feast 资源

  • Feast 初始化针对 AWS

  • 使用 Feast 探索机器学习生命周期

技术要求

要跟随本章中的代码示例,您只需要熟悉 Python 和任何笔记本环境,这可以是本地设置,如 Jupyter,或者在线笔记本环境,如 Google Collab、Kaggle 或 SageMaker。您还需要一个 AWS 账户,并有权访问 Redshift、S3、Glue、DynamoDB、IAM 控制台等资源。您可以在试用期间创建新账户并免费使用所有服务。您可以在以下 GitHub 链接找到本书的代码示例:

github.com/PacktPublis…

以下 GitHub 链接指向特征存储库:

github.com/PacktPublishing/Feature-Store-for-Machine-Learning/tree/main/customer_segmentation

在 AWS 中创建 Feast 资源

如前一章所述,Feast 致力于为初学者提供快速设置,以便他们尝试使用它;然而,为了团队协作和在生产中运行模型,它需要一个更好的设置。在本节中,我们将在 AWS 云中设置一个 Feast 环境,并在模型开发中使用它。在前一章中,我们还讨论了 Feast 在选择在线和离线存储时提供了多种选择。对于这个练习,我们将使用 Amazon S3 和 Redshift 作为离线/历史存储,并使用 DynamoDB 作为在线存储。因此,在我们开始使用项目中的特征存储功能之前,我们需要在 AWS 上准备一些资源。让我们依次创建这些资源。

Amazon S3 用于存储数据

如 AWS 文档中所述,Amazon Simple Storage Service (Amazon S3) 是一种提供行业领先的可扩展性、数据可用性、安全性和性能的对象存储服务。Feast 提供了使用 S3 存储和检索所有数据和元数据的功能。您还可以使用版本控制系统,如 GitHub 或 GitLab,在部署期间协作编辑元数据并将其同步到 S3。要在 AWS 中创建 S3 存储桶,请登录您的 AWS 账户,使用搜索框导航到 S3 服务,或访问 s3.console.aws.amazon.com/s3/home?region=us-east-1。将显示一个网页,如图 图 4.1 所示。

![图 4.1 – AWS S3 主页

![图片 B18024_04_01.jpg]

图 4.1 – AWS S3 主页

如果您已经有了存储桶,您将在页面上看到它们。我正在使用一个新账户,因此我还没有看到任何存储桶。要创建一个新的存储桶,请点击 feast-demo-mar-2022。需要注意的是,S3 存储桶名称在账户间是唯一的。如果存储桶创建失败并出现错误,存在同名存储桶,请尝试在末尾添加一些随机字符。

![图 4.2 – 创建 S3 存储桶

![图片 B18024_04_02.jpg]

图 4.2 – 创建 S3 存储桶

存储桶创建成功后,您将看到一个类似于 图 4.3 的屏幕。

![图 4.3 – 创建 S3 存储桶之后

![图片 B18024_04_03.jpg]

图 4.3 – 创建 S3 存储桶之后

AWS Redshift 用于离线存储

如 AWS 文档中所述,Amazon Redshift 使用 SQL 分析数据仓库、操作数据库和数据湖中的结构化和半结构化数据,利用 AWS 设计的硬件和机器学习,在任何规模下提供最佳的价格性能。如前所述,我们将使用 Redshift 集群来查询历史数据。由于我们还没有集群,我们需要创建一个。在创建集群之前,让我们创建一个 身份和访问管理 (IAM) 角色。这是一个 Redshift 将代表我们查询 S3 中历史数据的角色。

让我们从创建一个 IAM 角色开始:

  1. 要创建 IAM 角色,请使用搜索导航到 AWS IAM 控制台或访问 URL us-east-1.console.aws.amazon.com/iamv2/home?… 图 4.4 的网页。

![图 4.4 – IAM 主页

![图 B18024_04_04.jpg]

图 4.4 – IAM 主页

  1. 要创建新角色,请点击右上角的 创建角色 按钮。将显示以下页面。

![图 4.5 – IAM 创建角色

![图 B18024_04_05.jpg]

图 4.5 – IAM 创建角色

  1. 在页面上的可用选项中,选择 自定义信任策略,复制以下代码块,并用文本框中的 JSON 中的策略替换它:

    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Principal": {
                    "Service": "redshift.amazonaws.com"
                },
                "Action": "sts:AssumeRole"
            }
        ]
    }
    
  2. 将页面滚动到最底部并点击 下一步。在下一页,您将看到一个可以附加到角色的 IAM 策略列表,如图 图 4.6 所示。

![图 4.6 – 角色的 IAM 权限

![图 B18024_04_06.jpg]

图 4.6 – 角色的 IAM 权限

  1. 我们需要 S3 访问权限,因为数据将以 Parquet 文件的形式存储在 S3 中,以及 AWS Glue 访问权限。存储在 S3 中的数据将通过 AWS Glue 数据目录/Lake Formation 作为外部模式加载到 Redshift 中。请跟随这里,您将了解将数据作为外部模式加载的含义。对于 S3 访问,搜索 AmazonS3FullAccess 并选择相应的复选框,然后搜索 AWSGlueConsoleFullAccess 并选择相应的复选框。将页面滚动到最底部并点击 下一步

    重要提示

    我们在这里为所有资源提供对 S3 和 Glue 的完全访问权限,但建议限制对特定资源的访问。我将将其留作练习,因为这不属于本章的范围。

在点击 下一步 后,将显示以下页面。

![图 4.7 – IAM 审查

![图 B18024_04_07.jpg]

图 4.7 – IAM 审查

  1. 在此页面上,为角色提供一个名称。我已将角色命名为 feast-demo-mar-2022-spectrum-role。审查角色的详细信息并点击 创建角色。在成功创建后,您将在 IAM 控制台页面上找到该角色。

  2. 现在我们已经准备好了 IAM 角色,下一步是创建一个 Redshift 集群并将创建的 IAM 角色分配给它。要创建 Redshift 集群,请使用搜索栏导航到 Redshift 主页或访问链接 us-east-1.console.aws.amazon.com/redshiftv2/…

![图 4.8 – Redshift 主页

![图 B18024_04_08.jpg]

图 4.8 – Redshift 主页

  1. 图 4.8 中的页面上,点击 创建集群。将显示以下页面。

![图 4.9 – 创建 Redshift 集群

![图 B18024_04_09.jpg]

图 4.9 – 创建 Redshift 集群

  1. 从显示在 图 4.9 中的网页,我选择了用于演示的 免费试用,但可以根据数据集大小和负载进行配置。选择 免费试用 后,滚动到页面底部并选择一个密码。以下图显示了向下滚动后的窗口下半部分。

图 4.10 – 创建集群下半部分

图 4.10 – 创建集群下半部分

  1. 选择密码后,点击底部的 创建集群。集群创建需要几分钟。一旦集群创建完成,你应该在 AWS Redshift 控制台中看到新创建的集群。最后一件待办事项是将我们之前创建的 IAM 角色与 Redshift 集群关联起来。现在让我们来做这件事。导航到新创建的集群。你会看到一个类似于 图 4.11 中的网页。

图 4.11 – Redshift 集群页面

图 4.11 – Redshift 集群页面

  1. 在集群主页上,选择 属性 选项卡并滚动到 关联 IAM 角色。你将看到 图 4.12 中显示的选项。

图 4.12 – Redshift 属性选项卡

图 4.12 – Redshift 属性选项卡

  1. 从网页上,点击 feast-demo-mar-2022-spectrum-role,因此我将关联该角色。点击按钮后,集群将更新为新角色。这又可能需要几分钟。一旦集群准备就绪,我们现在就完成了所需的必要基础设施。当功能准备就绪以进行摄取时,我们将添加外部数据目录。

图 4.13 – Redshift 关联 IAM 角色

图 4.13 – Redshift 关联 IAM 角色

我们需要一个 IAM 用户来访问这些资源并对它们进行操作。让我们接下来创建它。

创建 IAM 用户以访问资源

有不同的方式为用户提供对资源的访问权限。如果你是组织的一部分,那么 IAM 角色可以与 Auth0 和活动目录集成。由于这超出了本节范围,我将创建一个 IAM 用户,并将为用户分配必要的权限以访问之前创建的资源:

  1. 让我们从 AWS 控制台创建 IAM 用户。可以通过搜索或访问 console.aws.amazon.com/iamv2/home#… 访问 IAM 控制台。IAM 控制台的外观如 图 4.14 所示。

图 4.14 – IAM 用户页面

图 4.14 – IAM 用户页面

  1. 在 IAM 用户页面上,点击右上角的 添加用户 按钮。将显示以下网页。

图 4.15 – IAM 添加用户

图 4.15 – IAM 添加用户

  1. 在网页上,提供一个用户名并选择 访问密钥 - 程序化访问,然后点击底部的 下一步:权限。将显示以下网页。

图 4.16 – IAM 权限

图 4.16 – IAM 权限

  1. 在显示的网页上,点击直接附加现有策略,然后从可用策略列表中搜索并附加以下策略:AmazonRedshiftFullAccessAmazonS3FullAccessAmazonDynamoDBFullAccess

    重要提示

    我们在这里提供了完整的访问权限,而不限制用户访问特定的资源。根据资源限制访问并仅提供所需的权限总是一个好的做法。

  2. 点击下一步:标签,您可以自由添加标签,然后再次点击下一步:审查。审查页面看起来如下:

图 4.17 – IAM 用户审查

图 4.17 – IAM 用户审查

  1. 从审查页面,点击创建用户按钮。图 4.18中的网页将会显示。

图 4.18 – IAM 用户凭据

图 4.18 – IAM 用户凭据

  1. 在网页上点击Download.csv按钮,并将文件保存在安全的位置。它包含我们刚刚创建的用户访问密钥 ID秘密访问密钥。如果您不从这个页面下载并保存它,秘密将会丢失。然而,您可以从 IAM 用户页面进入用户并管理秘密(删除现有的凭据并创建新的凭据)。

现在基础设施已经就绪,让我们初始化 Feast 项目。

Feast AWS 初始化

我们现在有运行 Feast 所需的基础设施。然而,在我们开始使用它之前,我们需要初始化一个 Feast 项目。要初始化 Feast 项目,我们需要像在第三章中那样安装 Feast 库,即特征存储基础、术语和用法。但是,这次,我们还需要安装 AWS 依赖项。以下是笔记本的链接:github.com/PacktPublishing/Feature-Store-for-Machine-Learning/blob/main/Chapter04/ch4_Feast_aws_initialization.ipynb.

以下命令安装 Feast 并带有所需的 AWS 依赖项:

!pip install feast[aws]

依赖项安装完成后,我们需要初始化 Feast 项目。与上一章中我们进行的初始化不同,这里的 Feast 初始化需要额外的输入,例如 Redshift ARN、数据库名称、S3 路径等。让我们看看初始化在这里是如何不同的。在我们初始化项目之前,我们需要以下详细信息:

  • AWS 区域:您的基础设施运行的区域。我已在us-east-1创建了所有资源。如果您在另一个区域创建了它们,请使用该区域。

  • Redshift 集群 ID:之前创建的 Redshift 集群的标识符。它可以在主页上找到。

  • dev

  • awsuser。如果您在集群创建时提供了不同的用户名,请在这里使用。

  • s3://feast-demo-mar-2022/staging。同时也在存储桶中创建 staging 文件夹。

  • arn:aws:iam::<account_number>:role/feast-demo-mar-2022-spectrum-role

一旦你有了提到的参数值,新的项目可以通过以下两种方式之一初始化。一种是使用以下命令:

 feast init -t aws customer_segmentation

前面的命令初始化 Feast 项目。在初始化过程中,命令将要求你提供提到的参数。

第二种方法是编辑 feature_store.yaml 文件:

project: customer_segmentation
registry: data/registry.db
provider: aws
online_store:
  type: dynamodb
  region: us-east-1
offline_store:
  type: redshift
  cluster_id: feast-demo-mar-2022
  region: us-east-1
  database: dev
  user: awsuser
  s3_staging_location: s3://feast-demo-mar-2022/staging
  iam_role: arn:aws:iam::<account_number>:role/feast-demo-mar-2022-spectrum-role

无论你选择哪种方法来初始化项目,确保你提供了适当的参数值。我已经突出显示了可能需要替换的参数,以便 Feast 功能能够无问题地工作。如果你使用第一种方法,init 命令将提供选择是否加载示例数据的选项。选择 no 以不上传示例数据。

现在我们已经为项目初始化了特征存储库,让我们应用我们的初始特征集,它基本上是空的。以下代码块移除了如果你使用 feast init 初始化项目时创建的不需要的文件:

%cd customer_segmentation
!rm -rf driver_repo.py test.py

如果你没有运行前面的命令,它将在 driver_repo.py 文件中创建实体和特征视图的特征定义。

以下代码块创建了项目中定义的特征和实体定义。在这个项目中,目前还没有:

!feast apply

当运行前面的命令时,它将显示消息 No changes to registry,这是正确的,因为我们还没有任何特征定义。

customer_segmentation 文件夹的结构应该看起来像 图 4.19

图 4.19 – 项目文件夹结构

图 4.19 – 项目文件夹结构

特征存储库现在已准备好使用。这可以提交到 GitHubGitLab 以进行版本控制和协作。

重要提示

还要注意,所有前面的步骤都可以使用基础设施即代码框架(如 Terraform、AWS CDK、Cloud Formation 等)自动化。根据组织遵循的团队结构,数据工程师或平台/基础设施团队将负责创建所需资源并共享数据科学家或工程师可以使用的存储库详细信息。

在下一节中,让我们看看机器学习生命周期如何随着特征存储而变化。

使用 Feast 探索机器学习生命周期

在本节中,让我们讨论一下当你使用特征存储时,机器学习模型开发看起来是什么样子。我们在 第一章 中回顾了机器学习生命周期,机器学习生命周期概述。这使得理解它如何随着特征存储而变化变得容易,并使我们能够跳过一些将变得冗余的步骤。

图 4.20 – 机器学习生命周期

图 4.20 – 机器学习生命周期

问题陈述(计划和创建)

问题陈述与第一章机器学习生命周期概述中的一致。假设你拥有一家零售业务,并希望提高客户体验。首先,你想要找到你的客户细分和客户终身价值。

数据(准备和清理)

第一章机器学习生命周期概述不同,在探索数据并确定访问权限等之前,这里的模型构建起点是特征存储。以下是笔记本的链接:

github.com/PacktPublishing/Feature-Store-for-Machine-Learning/blob/main/Chapter04/ch4_browse_feast_for_features.ipynb

让我们从特征存储开始:

  1. 因此,让我们打开一个笔记本并使用 AWS 依赖项安装 Feast:

    !pip install feast[aws]
    
  2. 如果在上一节中创建的特征仓库已推送到源代码控制,如 GitHub 或 GitLab,请克隆该仓库。以下代码克隆了仓库:

    !git clone <repo_url>
    
  3. 现在我们有了特征仓库,让我们连接到 Feast/特征存储并检查可用性:

    # change directory
    %cd customer_segmentation
    """import feast and load feature store object with the path to the directory which contains feature_story.yaml."""
    from feast import FeatureStore
    store = FeatureStore(repo_path=".")
    

上述代码块连接到 Feast 特征仓库。repo_path="."参数表示feature_store.yaml位于当前工作目录。

  1. 让我们检查特征存储是否包含任何可用于模型的实体特征视图,而不是探索数据并重新生成已存在的特征:

    #Get list of entities and feature views
    print(f"List of entities: {store.list_entities()}")
    print(f"List of FeatureViews: {store.list_feature_views()}")
    

上述代码块列出了我们连接到的当前特征仓库中存在的实体特征视图。代码块输出如下两个空列表:

List of entities: []
List of FeatureViews: []

重要提示

你可能会想,其他团队创建的特征怎么办?我如何获取访问权限并检查可用性? 有方法可以管理这些。我们稍后会提到。

由于实体和特征视图为空,没有可用的内容。下一步是进行数据探索和特征工程。

我们将跳过数据探索阶段,因为我们已经在第一章机器学习生命周期概述中完成了它。因此,生成特征的步骤也将相同。因此,我将不会详细说明特征工程。相反,我将使用相同的代码,并简要说明代码的功能。有关特征生成详细描述,请参阅第一章机器学习生命周期概述

模型(特征工程)

在本节中,我们将生成模型所需的特征。就像我们在第一章,“机器学习生命周期概述”中所做的那样,我们将使用 3 个月的数据来生成 RFM 特征,并使用 6 个月的数据来生成数据集的标签。我们将按照与第一章,“机器学习生命周期概述”中相同的顺序进行操作。以下是特征工程笔记本的链接:

github.com/PacktPublishing/Feature-Store-for-Machine-Learning/blob/main/Chapter04/ch4_feature_engineering.ipynb

让我们从特征工程开始:

  1. 以下代码块读取数据集并过滤掉不属于United Kingdom的数据:

    %pip install feast[aws]==0.19.3 s3fs
    import pandas as pd
    from datetime import datetime, timedelta, date
    from sklearn.cluster import Kmeans
    ##Read the data and filter out data that belongs to country other than UK
    retail_data = pd.read_csv('/content/OnlineRetail.csv', encoding= 'unicode_escape')
    retail_data['InvoiceDate'] = pd.to_datetime(retail_data['InvoiceDate'], errors = 'coerce')
    uk_data = retail_data.query("Country=='United Kingdom'").reset_index(drop=True)
    
  2. 一旦我们有了过滤后的数据,下一步是创建两个 DataFrame,一个用于 3 个月,一个用于 6 个月。

以下代码块创建了两个不同的 DataFrame,一个用于2011-03-01 00:00:00.0540002011-06-01 00:00:00.054000之间的数据,第二个用于2011-06-01 00:00:00.0540002011-12-01 00:00:00.054000之间的数据:

## Create 3months and 6 months DataFrames
t1 = pd.Timestamp("2011-06-01 00:00:00.054000")
t2 = pd.Timestamp("2011-03-01 00:00:00.054000")
t3 = pd.Timestamp("2011-12-01 00:00:00.054000")
uk_data_3m = uk_data[(uk_data.InvoiceDate < t1) & (uk_data.InvoiceDate >= t2)].reset_index(drop=True)
uk_data_6m = uk_data[(uk_data.InvoiceDate >= t1) & (uk_data.InvoiceDate < t3)].reset_index(drop=True)
  1. 下一步是从 3 个月 DataFrame 中生成 RFM 特征。以下代码块为所有客户生成 RFM 值:

    ## Calculate RFM values.
    Uk_data_3m['revenue'] = uk_data_3m['UnitPrice'] * uk_data_3m['Quantity']
    max_date = uk_data_3m['InvoiceDate'].max() + timedelta(days=1)
    rfm_data = uk_data_3m.groupby(['CustomerID']).agg({
      'InvoiceDate': lambda x: (max_date – x.max()).days,
      'InvoiceNo': 'count',
      'revenue': 'sum'})
    rfm_data.rename(columns={'InvoiceDate': 'Recency',
                             'InvoiceNo': 'Frequency',
                             'revenue': 'MonetaryValue'},
                    inplace=True)
    

现在我们已经为所有客户生成了 RFM 值,下一步是为每个客户生成一个 R 组、一个 F 组和三个 M 组,范围从 0 到 3。一旦我们有了客户的 RFM 组,它们将被用来通过累加客户各个组的单个值来计算 RFM 分数。

  1. 以下代码块为客户生成 RFM 组并计算 RFM 分数:

    ## Calculate RFM groups of customers 
    r_grp = pd.qcut(rfm_data['Recency'],
                    q=4, labels=range(3,-1,-1))
    f_grp = pd.qcut(rfm_data['Frequency'],
                    q=4, labels=range(0,4))
    m_grp = pd.qcut(rfm_data['MonetaryValue'], 
                    q=4, labels=range(0,4))
    rfm_data = rfm_data.assign(R=r_grp.values).assign(F=f_grp.values).assign(M=m_grp.values)
    rfm_data['R'] = rfm_data['R'].astype(int)
    rfm_data['F'] = rfm_data['F'].astype(int)
    rfm_data['M'] = rfm_data['M'].astype(int)
    rfm_data['RFMScore'] = rfm_data['R'] + rfm_data['F'] + rfm_data['M']
    
  2. RFM 分数计算完成后,是时候将客户分为低价值、中价值和高价值客户了。

以下代码块将这些客户分组到这些组中:

# segment customers.
Rfm_data['Segment'] = 'Low-Value'
rfm_data.loc[rfm_data['RFMScore']>4,'Segment'] = 'Mid-Value' 
rfm_data.loc[rfm_data['RFMScore']>6,'Segment'] = 'High-Value' 
rfm_data = rfm_data.reset_index()
  1. 现在我们有了 RFM 特征,让我们先把这些放一边,并使用之前步骤中创建的 6 个月 DataFrame 来计算收入。

以下代码块计算 6 个月数据集中每个客户的收入:

# Calculate revenue using the six month dataframe.
Uk_data_6m['revenue'] = uk_data_6m['UnitPrice'] * uk_data_6m['Quantity']
revenue_6m = uk_data_6m.groupby(['CustomerID']).agg({
        'revenue': 'sum'})
revenue_6m.rename(columns={'revenue': 'Revenue_6m'}, 
                  inplace=True)
revenue_6m = revenue_6m.reset_index()
  1. 下一步是将 6 个月数据集与收入合并到 RFM 特征 DataFrame 中。以下代码块在CustomerId列中合并了两个 DataFrame:

    # Merge the 6m revenue DataFrame with RFM data.
    Merged_data = pd.merge(rfm_data, revenue_6m, how="left")
    merged_data.fillna(0)
    
  2. 由于我们将问题视为一个分类问题,让我们生成客户 LTV 标签以使用k-means聚类算法。在这里,我们将使用 6 个月的收入来生成标签。客户将被分为三个组,即LowLTVMidLTVHighLTV

以下代码块为客户生成 LTV 组:

# Create LTV cluster groups
merged_data = merged_data[merged_data['Revenue_6m']<merged_data['Revenue_6m'].quantile(0.99)]
kmeans = Kmeans(n_clusters=3)
kmeans.fit(merged_data[['Revenue_6m']])
merged_data['LTVCluster'] = kmeans.predict(merged_data[['Revenue_6m']])
  1. 现在我们有了最终的数据库,让我们看看我们生成的特征集是什么样的。以下代码块将分类值转换为整数值:

    Feature_data = pd.get_dummies(merged_data)
    feature_data['CustomerID'] = feature_data['CustomerID'].astype(str)
    feature_data.columns = ['customerid', 'recency', 'frequency', 'monetaryvalue', 'r', 'f', 'm', 'rfmscore', 'revenue6m', 'ltvcluster', 'segmenthighvalue', 'segmentlowvalue', 'segmentmidvalue']
    feature_data.head(5)
    

上述代码块生成了以下特征集:

图 4.21 – 模型的最终特征集

第一章机器学习生命周期概述中,接下来执行的操作是模型训练和评分。这就是我们将与之分道扬镳的地方。我假设这将是我们最终的特征集。然而,在模型开发过程中,特征集会随着时间的推移而演变。我们将在后面的章节中讨论如何处理这些变化。

现在我们有了特征集,下一步就是在 Feast 中创建实体和功能视图。

创建实体和功能视图

在上一章第三章特征存储基础、术语和用法中,我们定义了实体功能视图。实体被定义为语义相关的特征集合。实体是特征可以映射到的域对象。功能视图被定义为类似于数据库表的视图。它表示特征数据在其源处的结构。功能视图由实体、一个或多个特征和一个数据源组成。功能视图通常围绕类似于数据库对象的域对象进行建模。由于创建和应用特征定义是一项一次性活动,因此最好将其保存在单独的笔记本或 Python 文件中。以下是笔记本的链接:

github.com/PacktPublishing/Feature-Store-for-Machine-Learning/blob/main/Chapter04/ch4_create_apply_feature_definitions.ipynb

让我们打开一个笔记本,安装库,并像之前提到的那样克隆功能仓库:

!pip install feast[aws]==0.19.3
!git clone <feature_repo>

现在我们已经克隆了功能仓库,让我们创建实体和功能视图。根据实体和功能视图的定义,任务是识别图 4.21中的功能集中的实体、特征和功能视图。让我们从实体开始。在图 4.21中可以找到的唯一域对象是customerid

  1. 让我们先定义客户实体。以下代码块定义了 Feast 中的客户实体:

    # Customer ID entity definition.
    from feast import Entity, ValueType
    customer = Entity(
        name='customer',
        value_type=ValueType.STRING,
        join_key='customeriD',
        description="Id of the customer"
    )
    

上述实体定义有几个必需的属性,例如namevalue_typejoin_key,其他属性是可选的。如果用户想提供更多信息,还可以添加其他属性。最重要的属性是join_key。此属性的值应与特征 DataFrame 中的列名匹配。

我们已经确定了特征集中的实体。接下来的任务是定义特征视图。在我们定义特征视图之前,需要注意的一点是,要像没有生成特征集的消费者一样定义特征视图。我的意思是不要将特征视图命名为 customer_segmentation_featuresLTV_features 并将它们全部推送到一个表中。始终尝试将它们分成其他数据科学家浏览时有意义的逻辑组。

  1. 考虑到这一点,让我们查看特征集并决定可以形成多少个逻辑组以及哪些特征属于哪个组。从 图 4.21 可以看到,它可以分为一组或两组。我看到的两组是客户的 RFM 特征和收入特征。由于 RFM 也包含收入细节,我更愿意将它们分为一组而不是两组,因为没有明显的子组。我将称之为 customer_rfm_features

以下代码块定义了特征视图:

from feast import ValueType, FeatureView, Feature, RedshiftSource
from datetime import timedelta 
# Redshift batch source
rfm_features_source = RedshiftSource(
    query="SELECT * FROM spectrum.customer_rfm_features",
    event_timestamp_column="event_timestamp",
    created_timestamp_column="created_timestamp",
)
# FeatureView definition for RFM features.
rfm_features_features = FeatureView(
    name="customer_rfm_features",
    entities=["customer"],
    ttl=timedelta(days=3650),
    features=[
        Feature(name="recency", dtype=ValueType.INT32),
        Feature(name="frequency", dtype=ValueType.INT32),
        Feature(name="monetaryvalue", 
        dtype=ValueType.DOUBLE),
        Feature(name="r", dtype=ValueType.INT32),
        Feature(name="f", dtype=ValueType.INT32),
        Feature(name="m", dtype=ValueType.INT32),
        Feature(name="rfmscore", dtype=ValueType.INT32),
        Feature(name="revenue6m", dtype=ValueType.DOUBLE),
        Feature(name="ltvcluster", dtype=ValueType.INT32),
        Feature(name="segmenthighvalue", 
        dtype=ValueType.INT32),
        Feature(name="segmentlowvalue", 
        dtype=ValueType.INT32),
        Feature(name="segmentmidvalue", 
        dtype=ValueType.INT32),
    ],
    batch_source=rfm_features_source,
)

上述代码块有两个定义。第一个是批源定义。根据所使用的离线存储,批源的定义不同。在上一章的例子中,我们使用了 FileSource。由于我们使用 Redshift 查询离线存储,因此定义了 RedshiftSource。对象输入是查询,这是一个简单的 SELECT 语句。源可以配置为具有复杂的 SQL 查询,包括连接、聚合等。然而,输出应与 FeatureView 中定义的列名匹配。源的其他输入是 created_timestamp_columnevent_timestamp_column。这些列在 图 4.21 中缺失。这些列代表它们的标题所表示的内容,即事件发生的时间和事件创建的时间。在我们摄取数据之前,需要将这些列添加到数据中。

FeatureView 表示源数据的数据表结构。正如我们在上一章所看到的,它包含 entitiesfeaturesbatch_source。在 图 4.21 中,实体是 customer,这是之前定义的。其余的列是特征和批源,即 RedshiftSource 对象。特征名称应与列名匹配,dtype 应与列的值类型匹配。

  1. 现在我们已经有了特征集的特征定义,我们必须注册新的定义才能使用它们。为了注册定义,让我们将实体和特征定义复制到一个 Python 文件中,并将此文件添加到我们的特征仓库文件夹中。我将把这个文件命名为 rfm_features.py。将文件添加到仓库后,文件夹结构如下所示。

![图 4.22 – 包含特征定义文件的工程]

img/B18024_04_22.jpg

图 4.22 – 包含特征定义文件的工程

在使用 apply 命令注册定义之前,让我们将外部模式映射到 Redshift 上。

创建外部目录

如果您记得正确,在创建 Redshift 资源期间,我提到将使用 Glue/Lake Formation 将 Amazon S3 中的数据添加为外部映射。这意味着数据不会直接被摄入 Redshift;相反,数据集将位于 S3 中。数据集的结构将在 Lake Formation 目录中定义,您稍后将看到。然后,数据库将作为外部模式映射到 Redshift 上。因此,摄入将直接将数据推入 S3,查询将使用 Redshift 集群执行。

现在我们已经了解了摄入和查询的工作原理,让我们在 Lake Formation 中创建我们的功能集的数据库和目录:

  1. 要创建数据库,请通过搜索或使用此网址访问 AWS Lake Formation 页面:console.aws.amazon.com/lakeformati…

图 4.23 – AWS Lake Formation 中的数据库

图 4.23 – AWS Lake Formation 中的数据库

图 4.23 显示了 AWS Lake Formation 中的数据库列表。

  1. 在网页上,点击 创建数据库。将出现以下网页。如果在过渡过程中看到任何弹出窗口,要求您开始使用 Lake Formation,可以取消或接受。

图 4.24 – 湖区形成创建数据库

图 4.24 – 湖区形成创建数据库

  1. 在上面显示的网页中,给数据库起一个名字。我将其命名为 dev。保留所有其他默认设置并点击 创建数据库。数据库将被创建,并将重定向到数据库详情页面。由于数据库是表的集合,您可以将此数据库视为项目中所有功能视图的集合。一旦您有了数据库,下一步就是创建表。如您所意识到的那样,我们在这里创建的表对应于功能视图。在当前练习中只有一个功能视图。因此,需要创建相应的表。

    注意

    每当您添加一个新的功能视图时,都需要在 Lake Formation 的数据库中添加相应的表。

  2. 要在数据库中创建表,请从 图 4.23 页面点击 或访问此网址:console.aws.amazon.com/lakeformation/home?region=us-east-1#tables.

图 4.25 – 湖区形成表

图 4.25 – 湖区形成表

  1. 图 4.25 的网页中,点击右上角的 创建表 按钮。将显示以下网页:

图 4.26 – 湖区形成创建表 1

图 4.26 – 湖区形成创建表 1

  1. 对于 customer_rfm_features,我已经选择了之前创建的数据库(dev)。描述是可选的。一旦填写了这些详细信息,向下滚动。在 创建表 页面的下一部分将看到以下选项。

图 4.27 – 湖的形成 创建表 2

图 4.27 – 湖的形成 创建表 2

  1. 数据存储是这里的一个重要属性。它代表 S3 中数据的位置。到目前为止,我们还没有将任何数据推送到 S3。我们很快就会这么做。让我们定义这个表的数据将推送到哪里。我将使用我们之前创建的 S3 存储桶,因此位置将是 s3://feast-demo-mar-2022/customer-rfm-features/

    重要提示

    在 S3 路径中创建 customer-rfm-features 文件夹。

  2. 选择 S3 路径后,向下滚动到页面最后部分 – 将显示以下选项。

图 4.28 – 湖的形成 创建表 3

图 4.28 – 湖的形成 创建表 3

图 4.28 展示了创建表的最后部分。数据格式 部分要求输入数据的文件格式。在这个练习中,我们将选择 PARQUET。您可以自由尝试其他格式。无论选择哪种格式,所有导入的数据文件都应该具有相同的格式,否则可能无法按预期工作。

  1. 最后一个部分是数据集的 模式 定义。您可以选择点击 添加列 按钮并逐个添加列,或者点击 上传模式 按钮一次性上传一个定义所有列的 JSON 文件。让我们使用 添加列 按钮并按顺序添加所有列。一旦添加了所有列以及数据类型,列应该看起来像以下这样:

图 4.29 – 创建表中的列列表

图 4.29 – 创建表中的列列表

图 4.29 所示,所有列都已添加,包括实体 customerid 和两个时间戳列:event_timestampcreated_timestamp。一旦添加了列,点击底部的 提交 按钮。

  1. 现在,唯一待办的事情是将此表映射到已创建的 Redshift 集群。让我们接下来这么做。要创建外部模式的映射,请访问 Redshift 集群页面并选择之前创建的集群。将显示一个类似于 图 4.30 的网页。

图 4.30 – Redshift 集群详情页

图 4.30 – Redshift 集群详情页

  1. 图 4.30 显示的网页上,点击页面右上角的 查询数据。在下拉菜单中的选项中,选择 查询编辑器 v2。它将打开一个查询编辑器,如图下所示:

图 4.31 – Redshift 查询编辑器 v2

图 4.31 – Redshift 查询编辑器 v2

  1. 从左侧面板选择集群,如果默认未选择,也选择数据库。在图 4.31中显示的查询编辑器中运行以下查询,将外部数据库映射到名为spectrum的模式:

    create external schema spectrum 
    from data catalog database dev 
    iam_role '<redshift_role_arn>' 
    create external database if not exists;
    
  2. 在前面的代码块中,将<redshift_role_arn>替换为与 Redshift 创建并关联的角色ARN。该 ARN 可以在 IAM 控制台的“角色详情”页面找到,类似于图 4.32中的那个。

![图 4.32 – IAM 角色详情页面]

![图片 B18024_04_32.jpg]

图 4.32 – IAM 角色详情页面

查询成功执行后,你应该能够在刷新页面后看到图 4.33中显示的数据库下的spectrum模式输出。

![图 4.33 – Redshift spectrum 模式]

![图片 B18024_04_33.jpg]

图 4.33 – Redshift spectrum 模式

  1. 你也可以通过执行以下 SQL SELECT查询来验证映射:

    SELECT * from spectrum.customer_rfm_features limit 5
    

前面的 SQL 查询将返回一个空表作为结果,因为数据尚未摄取。

我们现在已经完成了外部表的映射。我们现在剩下的是应用特征集并摄取数据。让我们接下来做这件事。

重要提示

在机器学习管道中添加特征存储可能看起来工作量很大,然而,这并不正确。由于我们这是第一次做,所以感觉是这样。此外,从资源创建到映射外部表的所有步骤都可以使用基础设施即代码来自动化。这里有一个自动化基础设施创建的示例链接(github.com/feast-dev/feast-aws-credit-scoring-tutorial)。除此之外,如果你使用像 Tecton、SageMaker 或 Databricks 这样的托管特征存储,基础设施将由它们管理,你所要做的就是创建特征、摄取它们并使用它们,无需担心基础设施。我们将在第七章,“费曼替代方案和机器学习最佳实践”中比较 Feast 与其他特征存储。

应用定义和摄取数据

到目前为止,我们已经执行了数据清洗、特征工程、定义实体和特征定义,并且还创建了映射到 Redshift 的外部表。现在,让我们应用特征定义并摄取数据。继续在创建实体和特征视图部分中我们创建的同一个笔记本中(github.com/PacktPublishing/Feature-Store-for-Machine-Learning/blob/main/Chapter04/ch4_create_apply_feature_definitions.ipynb)。

要应用特征集,我们需要之前创建的 IAM 用户凭据。回想一下,在创建 IAM 用户期间,凭据文件可供下载。该文件包含 AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEY。一旦您手头有了这些信息,请将以下代码块中的 <aws_key_id><aws_secret> 替换为:

import os
os.environ["AWS_ACCESS_KEY_ID"] = "<aws_key_id>"
os.environ["AWS_SECRET_ACCESS_KEY"] = "<aws_secret>"
os.environ["AWS_DEFAULT_REGION"] = "us-east-1"

重要提示

在笔记本中直接设置凭据不是一个好主意。根据用户可用的工具,使用密钥管理器存储密钥是一个好的实践。

设置环境变量后,您只需运行以下代码块来应用定义的特征集:

%cd customer_segmentation/
!feast apply

前面的代码块注册了新的特征定义,并为定义中的所有特征视图创建了 AWS DynamoDB 表。前面代码块的输出显示在 图 4.34 中。

图 4.34 – Feast 应用输出

图 4.34 – Feast 应用输出

要验证是否为特征视图创建了 DynamoDB 表,请导航到 DynamoDB 控制台,使用搜索或访问 console.aws.amazon.com/dynamodbv2/… 图 4.35 所示的 customer_rfm_features 表。

图 4.35 – DynamoDB 表

图 4.35 – DynamoDB 表

现在已经应用了特征定义,为了导入特征数据,让我们选取在 模型(特征工程) 部分创建的特征工程笔记本(github.com/PacktPublishing/Feature-Store-for-Machine-Learning/blob/main/Chapter04/ch4_feature_engineering.ipynb)并继续在该笔记本中(特征工程的最后一个命令生成了 图 4.21)。为了导入数据,我们唯一要做的就是将特征 DataFrame 写入到 图 4.28 中映射的 S3 位置。我已经将数据存储位置映射为 s3://feast-demo-mar-2022/customer-rfm-features/。让我们将 DataFrame 写入到该位置,格式为 Parquet。

以下代码块从 S3 位置导入数据:

import os
from datetime import datetime
os.environ["AWS_ACCESS_KEY_ID"] = "<aws_key_id>"
os.environ["AWS_SECRET_ACCESS_KEY"] = "<aws_secret>"
os.environ["AWS_DEFAULT_REGION"] = "us-east-1"
file_name = f"rfm_features-{datetime.now()}.parquet" 
feature_data["event_timestamp"] = datetime.now()
feature_data["created_timestamp"] = datetime.now()
s3_url = f's3://feast-demo-mar-2022/customer-rfm-features/{file_name}'
feature_data.to_parquet(s3_url)

前面的代码块设置了 IAM 用户的 AWS 凭据,添加了缺失的列 event_timestampcreated_timestamp,并将 Parquet 文件写入到 S3 位置。为了验证文件已成功写入,请导航到 S3 位置并验证文件是否存在。为了确保文件格式正确,让我们导航到 图 4.32 中的 Redshift 查询编辑器并运行以下查询:

SELECT * from spectrum.customer_rfm_features limit 5

前面的命令应该成功执行,输出结果如 图 4.36 所示。

图 4.36 – 数据导入后的红移查询

图 4.36 – 数据导入后的红移查询

在我们进入 ML 的下一阶段之前,让我们运行几个 API,看看我们的特征存储库是什么样子,并验证对历史存储的查询是否正常。对于以下代码,让我们使用我们创建和应用特征定义的笔记本(github.com/PacktPublishing/Feature-Store-for-Machine-Learning/blob/main/Chapter04/ch4_create_apply_feature_definitions.ipynb)。

以下代码连接到特征存储并列出可用的实体和特征视图:

"""import feast and load feature store object with the path to the directory which contains feature_story.yaml."""
from feast import FeatureStore
store = FeatureStore(repo_path=".")
#Get list of entities and feature views
print("-----------------------Entity---------------------")
for entity in store.list_entities():
  print(f"entity: {entity}")
print("--------------------Feature Views-----------------")
for feature_view in store.list_feature_views():
  print(f"List of FeatureViews: {feature_view}")

上一段代码块打印了 customer 实体和 customer_rfm_features 特征视图。让我们查询离线存储中的几个实体,看看它是否按预期工作。

要查询离线数据,我们需要实体 ID 和时间戳列。实体 ID 列是客户 ID 的列表,时间戳列用于在数据集上执行点时间连接查询。以下代码为查询创建了一个实体 DataFrame:

import pandas as pd
from datetime import datetime, timedelta
entity_df = pd.DataFrame.from_dict(
    {
        "customerid": ["12747.0", "12748.0", "12749.0"],
        "event_timestamp": [datetime.now()]*3
    }
)
entity_df.head()

上一段代码块生成了一个类似于 图 4.37 中的实体 DataFrame。

图 4.37 – 实体 DataFrame

图 4.37 – 实体 DataFrame

使用示例实体 DataFrame,让我们查询历史数据。以下代码从历史存储中获取特征子集:

job = store.get_historical_features(
    entity_df=entity_df,
    features=[
              "customer_rfm_features:recency", 
              "customer_rfm_features:frequency", 
              "customer_rfm_features:monetaryvalue", 
              "customer_rfm_features:r", 
              "customer_rfm_features:f", 
              "customer_rfm_features:m"]
    )
df = job.to_df()
df.head()

以下代码块可能需要几分钟才能运行,但最终输出以下结果:

图 4.38 – 历史检索作业输出

图 4.38 – 历史检索作业输出

现在,我们可以说我们的特征工程流程已经准备好了。接下来需要进行的步骤是训练模型、执行验证,如果对模型的性能满意,则将流程部署到生产环境中。我们将在下一章中探讨训练、验证、部署和模型评分。接下来,让我们简要总结一下我们学到了什么。

摘要

在本章中,我们以将 Feast 特征存储添加到我们的 ML 模型开发为目标。我们通过在 AWS 上创建所需资源、添加 IAM 用户以访问这些资源来实现这一点。在创建资源后,我们再次从问题陈述到特征工程和特征摄取的 ML 生命周期步骤进行了操作。我们还验证了创建的特征定义和摄取的数据可以通过 API 进行查询。

现在,我们已经为 ML 生命周期的下一步——模型训练、验证、部署和评分——做好了准备,在下一章中,我们将学习从一开始就添加特征存储是如何使模型在开发完成后立即准备好生产的。

参考文献