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

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

原文: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(https://github.com/PacktPublishing/Feature-Store-for-Machine-Learning)下载本书的示例代码文件。如果代码有更新,它将在 GitHub 仓库中更新。

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

下载颜色图像

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

使用的约定

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

文本中的代码: 表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 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/errata并填写表格。

盗版: 如果您在互联网上以任何形式发现我们作品的非法副本,如果您能提供位置地址或网站名称,我们将不胜感激。请通过版权@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

](https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/feat-str-ml/img/B18024_01_01.jpg)

图 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 跟踪什么和如何跟踪https://towardsdatascience.com/data-driven-growth-with-python-part-1-know-your-metrics-812781e66a5b)。你可以阅读这篇文章以获取更多详细信息。欢迎你自己尝试。数据集在此处可用:www.kaggle.com/vijayuv/onlineretail

数据(准备和清理)

首先,让我们安装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 = [1001, 1002, 1003, 1004, 1005]
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(100) for _ 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": [1001, 1002, 1003],
        "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 链接找到本书的代码示例:

https://github.com/PacktPublishing/Feature-Store-for-Machine-Learning/tree/main/Chapter04

以下 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 https://us-east-1.console.aws.amazon.com/iamv2/home?region=us-east-1#/roles。将显示一个类似于 图 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 主页或访问链接 https://us-east-1.console.aws.amazon.com/redshiftv2/home?region=us-east-1#clusters。将显示以下页面。

![图 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 用户。可以通过搜索或访问 https://console.aws.amazon.com/iamv2/home#/users 访问 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 页面:https://console.aws.amazon.com/lakeformation/home?region=us-east-1#databases。

图 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 控制台,使用搜索或访问 https://console.aws.amazon.com/dynamodbv2/home?region=us-east-1#tables。您应该看到如 图 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 生命周期的下一步——模型训练、验证、部署和评分——做好了准备,在下一章中,我们将学习从一开始就添加特征存储是如何使模型在开发完成后立即准备好生产的。

参考文献

第六章:模型训练和推理

在上一章中,我们讨论了在 AWS 云中的Feast 部署,并将 S3 设置为离线存储,将 DynamoDB 设置为在线存储模型。我们还回顾了使用第一章机器学习生命周期概述中构建的客户终身价值LTV/CLTV)模型所经历的几个 ML 生命周期阶段。在模型开发处理过程中,我们进行了数据清洗和特征工程,并生成了特征集,为这些特征定义创建了并应用于 Feast。最后,我们成功地将特征导入 Feast,并且也能够查询导入的数据。

在本章中,我们将继续 ML 生命周期的其余部分,这将涉及使用特征存储进行模型训练、打包、批量推理和在线模型推理。本章的目标是继续使用上一章创建的特征存储基础设施,并完成 ML 生命周期的其余部分。在这个过程中,我们将有机会了解如何在 ML 开发中使用特征存储来提高模型的上市时间,解耦 ML 生命周期的不同阶段,并有助于协作。我们还将回顾第一章机器学习生命周期概述,并在执行这些步骤时比较不同阶段。本章将帮助您了解如何使用特征存储进行模型训练,然后进行模型推理。我们还将学习在线存储库所服务的用例以及离线存储库所服务的用例。

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

  • 使用特征存储进行模型训练

  • 模型打包

  • 使用 Feast 进行批量模型推理

  • 使用 Feast 进行在线模型推理

  • 处理开发过程中功能集的变化

前提条件

为了运行示例并更好地理解本章内容,需要使用在第四章将特征存储添加到机器学习模型中创建的资源。在本章中,我们将使用上一章创建的资源,并使用本章创建的特征存储库。以下 GitHub 链接指向我创建的特征存储库:github.com/PacktPublishing/Feature-Store-for-Machine-Learning/tree/main/customer_segmentation

技术要求

要跟随章节中的代码示例,你需要熟悉 Python 和任何笔记本环境,这可能是一个本地的设置,如 Jupyter,或者是一个在线笔记本环境,如 Google Colab、Kaggle 或 SageMaker。你还需要一个 AWS 账户,可以完全访问 Redshift、S3、Glue、DynamoDB、IAM 控制台等资源。你可以在试用期间创建一个新账户并免费使用所有服务。在最后一部分,你需要一个 IDE 环境来开发在线模型的 REST 端点。你可以在以下 GitHub 链接中找到本书的代码示例:github.com/PacktPublishing/Feature-Store-for-Machine-Learning/tree/main/Chapter05

使用特征存储进行模型训练

第一章机器学习生命周期概述中,在特征工程之后,我们直接在同一笔记本中开始了模型训练。而相比之下,在第四章将特征存储添加到机器学习模型中,生成的特征被摄入到特征存储中。这是特征存储在机器学习生命周期中帮助实现的标准化的一个方面。通过将特征摄入到特征存储中,创建了一个可发现、可共享、可重用和版本化的数据集/特征集。

现在假设有两个数据科学家,拉姆和迪,他们正在同一个模型上工作。他们都可以使用这个特征集,而无需做任何额外的工作。不仅如此,如果背景数据每天都会更新,那么所有需要做的就是在数据科学家到来时每天运行一次特征工程笔记本,最新的特征就会可供使用。更好的做法是使用如AirflowAWS Step Functions或甚至GitHub工作流等编排框架来安排特征工程笔记本。一旦完成,拉姆和迪在来工作时都可以使用最新的特征进行实验。

正如我们一直在讨论的,数据工程师和科学家从特征存储中获得的最大优势之一是协作。让我们尝试看看我们的两位数据科学家 Dee 和 Ram 在模型构建中如何协作/竞争。每天 Dee 和 Ram 来上班时,假设计划中的特征工程已经成功运行,他们就开始进行模型训练。这里需要注意的另一件重要事情是,对于模型训练,数据源是特征存储。数据科学家不需要进入原始数据源来生成特征,除非他们对现有特征的模型不满意。在这种情况下,数据科学家将再次进行数据探索,生成额外的特征集,并将它们导入特征存储。导入的特征再次可供每个人使用。这将一直持续到团队/数据科学家对模型的性能满意为止。

在我们将 Dee 和 Ram 两位数据科学家的工作流程分开之前,让我们回顾一下他们模型训练笔记本中的共同步骤。让我们打开一个新的 Python 笔记本,命名为 model-training.ipynb,并生成训练数据。离线存储将用于生成训练数据集,因为它存储历史数据,并使用时间戳对数据进行版本控制。在 Feast 中,数据存储的接口是通过 API 实现的,正如我们在 第三章 特征存储基础、术语和用法第四章 将特征存储添加到机器学习模型 中所看到的。因此,为了生成训练数据集,我们将使用 get_historical_featuresget_historical_features API 的一个输入是实体 ID。通常,在企业中,实体 ID 可以从原始数据源中获取。典型的原始数据源包括数据库、数据仓库、对象存储等。获取实体的查询可能像 select unique {entity_id} from {table}; 这样简单。让我们在这里做类似的事情。我们的原始数据源是 CSV 文件。让我们使用它来获取实体 ID。在我们继续之前,让我们安装所需的包:

  1. 以下代码块安装了模型训练所需的包:

    !pip install feast[aws]==0.19.3 pandas xgboost
    
  2. 安装完所需的包后,如果您还没有克隆特征仓库,请克隆它,因为我们需要连接到特征存储来生成训练数据集。以下代码克隆了仓库:

    !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=".")
    for entity in store.list_entities():
      print(f"entity: {entity}")
    

前面的代码块连接到 Feast 功能仓库。repo_path="." 参数表示 feature_store.yaml 文件位于当前工作目录中。它还列出了 customer_segmentation 功能仓库中可用的实体。

现在我们能够连接到特征仓库,让我们创建训练模型所需的实体 ID 列表。为了获取实体 ID 列表,在这种情况下,CustomerId,让我们使用原始数据集并从中过滤出实体 ID。

重要提示

我们使用与第四章中使用的相同原始数据集,将特征存储添加到机器学习模型中。以下是数据集的 URL:www.kaggle.com/datasets/vijayuv/onlineretail

  1. 以下代码块加载了原始数据:

    import pandas as pd
    ##Read the OnlineRetail.csv
    retail_data = pd.read_csv('/content/OnlineRetail.csv',
                              encoding= 'unicode_escape')
    retail_data['InvoiceDate'] = pd.to_datetime(
      retail_data['InvoiceDate'], errors = 'coerce')
    

    重要提示

    您可能会质疑为什么在这里需要原始数据。Feast 允许对实体进行查询。因此,我们需要需要特征的实体 ID。

  2. 让我们过滤掉感兴趣的顾客 ID,类似于在特征创建过程中所做的过滤。以下代码块选择不属于英国的数据库集,以及存在于三个月数据集中的顾客 ID(选择三个月数据集中的顾客的原因是,在生成 RFM 特征后,我们在特征工程笔记本中的数据集上执行了左连接)。

以下代码块执行了所描述的过滤操作:

## filter data for United Kingdom
uk_data = retail_data.query("Country=='United Kingdom'").reset_index(drop=True)
t1 = pd.Timestamp("2011-06-01 00:00:00.054000")
t2 = pd.Timestamp("2011-03-01 00:00:00.054000")
uk_data_3m = uk_data[(uk_data.InvoiceDate < t1) & (uk_data.InvoiceDate >= t2)].reset_index(drop=True)

uk_data_3m中,我们需要获取唯一的CustomerId。实体数据中还需要额外的列是时间戳,以执行点时间连接。现在,我将使用所有实体 ID 的最新时间戳。

  1. 以下代码块创建了查询历史商店所需的实体 DataFrame:

    from datetime import datetime
    entity_df = pd.DataFrame(data = {
        "customerid": [str(item) for item in uk_data_3m.CustomerID.unique().tolist()],
        "event_timestamp": datetime.now()
    })
    entity_df.head()
    

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

图 5.1 – 用于生成训练数据集的实体 DataFrame

图 5.1 – 用于生成训练数据集的实体 DataFrame

图 5.1所示,实体 DataFrame 包含两列:

  • CustomerID:需要获取特征的客户列表。

  • event_timestamp

现在 Dee 和 Ram 的模型训练笔记本中的共同步骤已完成,让我们分割他们的工作流程,看看他们如何协作。

Dee 的模型训练实验

从上一步骤继续(您可以随意复制代码块并在不同的笔记本中运行它们,并将其命名为dee-model-training.ipynb),现在是时候选择训练模型所需的特征集了:

  1. 为了选择特征,Dee 将运行以下命令来查看现有特征视图中可用的特征:

    feature_view = store.get_feature_view("customer_rfm_features")
    print(feature_view.to_proto())
    

前一个命令输出了特征视图。以下块显示了输出的一部分,包括特征和实体,它们是特征视图的一部分:

  name: "customer_rfm_features"
  entities: "customer"
  features {
    name: "recency"
    value_type: INT32
  }
  features {
    name: "frequency"
    value_type: INT32
  }
  features {
    name: "monetaryvalue"
    value_type: DOUBLE
  }
  …

meta {
  created_timestamp {
    seconds: 1647301293
    nanos: 70471000
  }
  last_updated_timestamp {
    seconds: 1647301293
    nanos: 70471000
  }
}

从特征集中,假设 Dee 想要排除与频率相关的特征,看看这会对模型的性能产生什么影响。因此,她选择了所有其他特征进行查询,并排除了频率F,其中F表示频率组。

  1. 以下代码块查询历史/离线存储,使用图 5.1中显示的实体 DataFrame 获取所需特征:

    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"
    job = store.get_historical_features(
        entity_df=entity_df,
        features=[
                  "customer_rfm_features:recency", 
                  "customer_rfm_features:monetaryvalue", 
                  "customer_rfm_features:r", 
                  "customer_rfm_features:m",
                  "customer_rfm_features:rfmscore",
                  "customer_rfm_features:segmenthighvalue",
                  "customer_rfm_features:segmentlowvalue"
                  "customer_rfm_features:segmentmidvalue",
                  "customer_rfm_features:ltvcluster"
                  ]
        )
    feature_data = job.to_df()
    feature_data = feature_data.dropna()
    feature_data.head()
    

前面的代码块输出以下 DataFrame:

图 5.2 – Dee 模型的训练数据集

图 5.2 – Dee 模型的训练数据集

重要提示

在前面的代码块中,将<aws_key_id><aws_secret>替换为在第四章将特征存储添加到机器学习模型中创建的用户凭据。

  1. 现在,Dee 已经生成了训练数据集,下一步是模型训练。让我们使用与第一章机器学习生命周期概述中使用的相同参数构建 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
    #Drop prediction column along with event time and customerId columns from X
    X = feature_data.drop(['ltvcluster', 'customerid', 
                           'event_timestamp'], axis=1)
    y = feature_data['ltvcluster']
    X_train, X_test, y_train, y_test = \ 
    train_test_split(X, y, test_size=0.1)
    
  2. 以下代码块使用前一个示例中创建的培训和测试数据集,训练一个XGBClassifier模型:

    xgb_classifier = xgb.XGBClassifier(max_depth=5, objective='multi:softprob')
    #model training
    xgb_model = xgb_classifier.fit(X_train, y_train)
    #Model scoring
    acc = xgb_model.score(X_test,y_test)
    print(f"Model accuracy: {acc}")
    

前面的代码块打印出模型的准确率:

Model accuracy: 0.8840579710144928
  1. 以下代码块在测试数据集上运行predict函数并打印出分类报告:

    #Run prediction on the test dataset
    y_pred = xgb_model.predict(X_test)
    print(classification_report(y_test, y_pred))
    

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

图 5.3 – Dee 模型的分类报告

图 5.3 – Dee 模型的分类报告

不仅于此,Dee 还可以尝试不同的特征集和算法。目前,我们假设 Dee 对她自己的模型感到满意。让我们继续看看 Ram 会做什么。

Ram 的模型训练实验

再次强调,我们将从图 5.1之后的步骤继续在笔记本中操作(您可以自由复制代码块,在另一个笔记本中运行它们,并将其命名为ram-model-training.ipynb)。现在是选择训练模型所需特征集的时候了。为了选择特征,Ram 将遵循与 Dee 类似的步骤。让我们假设 Ram 有不同的想法——他不是删除一个特定的类别,而是删除具有实际值的特征,只使用 R、F 和 M 分类特征以及分类特征段。根据 Ram 的说法,这些分类变量是实际值的一些转换:

  1. 以下代码块产生 Ram 训练模型所需的特征集:

    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"
    job = store.get_historical_features(
        entity_df=entity_df,
        features=[
                 "customer_rfm_features:r", 
                 "customer_rfm_features:m",
                 "customer_rfm_features:f",
                 "customer_rfm_features:segmenthighvalue",
                 "customer_rfm_features:segmentlowvalue",
                 "customer_rfm_features:segmentmidvalue",
                 "customer_rfm_features:ltvcluster"
                 ]
        )
    feature_data = job.to_df()
    feature_data = feature_data.dropna()
    feature_data.head()
    

    重要提示

    在前面的代码块中,将<aws_key_id><aws_secret>替换为在第四章将特征存储添加到机器学习模型中创建的用户凭据。

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

图 5.4 – Ram 模型的训练数据集

图 5.4 – Ram 模型的训练数据集

  1. 下一步与 Dee 执行的操作类似,即训练模型并查看其分类报告。让我们来做这件事。

以下代码块在图 5.4中的特征集上训练模型:

from sklearn.metrics import classification_report,confusion_matrix
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import KFold, cross_val_score, train_test_split
X = feature_data.drop(['ltvcluster', 'customerid',
                       'event_timestamp'], axis=1)
y = feature_data['ltvcluster']
X_train, X_test, y_train, y_test = \ 
train_test_split(X, y, test_size=0.1)
model =  (random_state=0).fit(X_train, y_train)
acc = model.score(X_test,y_test)
print(f"Model accuracy: {acc}")

上述代码块在训练后打印了模型在测试集上的准确率。代码与 Dee 所使用的类似,但使用的是 LogisticRegression 而不是 XGBClassifier。代码块生成了以下输出:

Model accuracy: 0.8623188405797102
  1. 让我们打印测试数据集上的分类报告,以便我们可以比较 Ram 和 Dee 的模型。以下代码块生成了该模型的分类报告:

    y_pred = model.predict(X_test)
    print(classification_report(y_test, y_pred))
    

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

![图 5.5 – Ram 模型的分类报告]

![图片 B18024_05_005.jpg]

图 5.5 – Ram 模型的分类报告

现在,Ram 和 Dee 可以通过查看他们各自运行的实验来比较彼此的工作。不仅这些两个实验,他们还可以运行多个实验,在所有比较之后得出最佳模型。不仅如此,他们还可以通过编写代码尝试所有特征集的组合来自动化实验,在运行这些实验的同时查看和探索更多数据或专注于其他方面的工作。

我在这里建议的另一件事是使用实验跟踪工具/软件之一。市场上有很多这样的工具。其中一些提供了您使用的笔记本基础设施。例如,Databricks 提供 MLflowSageMaker 有自己的,还有第三方实验跟踪工具,如 NeptuneClearML 等。更多实验跟踪和比较的工具可以在以下博客中找到:neptune.ai/blog/best-ml-experiment-tracking-tools

假设 Dee 和 Ram 在所有实验之后得出结论,认为 XGBClassifier 表现得更好,并决定使用该模型。接下来,让我们看看下一节中的模型打包。

模型打包

在上一节中,我们构建了两个模型版本。在本节中,我们将打包其中一个模型并保存它以供模型评分和部署。正如上一节所述,我们将打包 XGBClassifier 模型。再次强调,对于打包,有不同解决方案和工具可用。为了避免设置另一个工具,我将使用 joblib 库来打包模型:

  1. 在生成 XGBClassifier 模型的同一笔记本中,以下代码块安装了 joblib 库:

    #install job lib library for model packaging
    !pip install joblib
    
  2. 安装 joblib 库后,下一步是使用它来打包模型对象。以下代码块打包了模型并将其写入文件系统上的特定位置:

    import joblib
    joblib.dump(xgb_model, '/content/customer_segment-v0.0')
    

上述代码块在 /content 文件夹中创建了一个文件。为了验证这一点,运行一个 ls 命令并检查文件是否存在。让我们也验证模型是否可以被加载,并且我们是否可以在其上运行 predict 函数。

  1. 以下代码块从 /content/customer_segment-v0.0 位置加载模型并在样本数据集上运行预测:

    loaded_model = joblib.load('/content/customer_segment-v0.0')
    prediction = loaded_model.predict(X_test.head())
    prediction.tolist()
    

前面的代码块应该没有错误地运行,并打印以下预测输出:

[0.0, 0.0, 0.0, 2.0, 0.0]
  1. 现在我们有了打包好的模型,下一步是将它注册到模型仓库中。同样,有许多工具可供使用来管理模型,例如 MLflow、SageMaker 以及其他工具。我强烈建议使用其中之一,因为它们可以处理许多用于共享、部署、标准版本控制等方面的用例。为了简化,我将在这里使用 S3 存储桶作为模型注册处,并将训练好的模型上传到那里。

下面的代码将打包好的模型上传到 S3 存储桶:

import boto3
s3_client = boto3.client('s3')
s3_client.upload_file(
  '/content/customer_segment-v0.0', 
  "feast-demo-mar-2022", 
  "model-repo/customer_segment-v0.0")

前面的代码块将文件 S3 bucket, feast-demo-mar-2022 上传到以下前缀:model-repo/customer_segment-v0.0。请通过访问 AWS 控制台来验证这一点,以确保模型已上传到指定的位置。

到目前为止,我们已经完成了模型训练和实验,并在模型仓库(S3 存储桶)中注册了一个候选模型。让我们在下一节创建一个用于批量模型用例的模型预测笔记本。

使用 Feast 进行批量模型推理

在本节中,让我们看看如何运行批量模型的预测。为了对批量模型进行预测,我们需要两样东西:一个是模型,另一个是用于预测的客户及其特征集列表。在上一节中,我们在模型注册处(即 S3)创建并注册了一个模型。同时,所需的特征在特征存储中也是可用的。我们需要的只是需要运行预测的客户列表。客户列表可以从我们之前在模型训练期间使用的原始数据集中生成。然而,为了这个练习的目的,我们将取一小部分客户并对其运行预测。

让我们创建一个模型预测笔记本并加载在模型仓库中注册的模型:

  1. 下面的代码块安装了预测笔记本所需的依赖项:

    !pip install feast[aws]==0.19.3 pandas xgboost joblib
    
  2. 在安装了依赖项之后,其他所需的步骤是如果尚未完成,则获取特征仓库。这是所有使用 Feast 的笔记本中常见的需求之一。然而,在其他特征存储中,这个过程可能并不相同。其中一个原因是 Feast 是以 SDK/CLI 为导向的。其他特征存储,如 SageMaker 和 Databricks,可能只需要凭证来访问它。我们将在下一章中查看一个示例。

  3. 假设你已经克隆了上一章中创建的 Feast 仓库(该仓库也用于模型创建),下一步是从模型注册处的 S3 获取模型。

下面的代码块从 S3 位置(即模型上传到的位置)下载模型:

import boto3
import os
#aws Credentials
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"
#Download model from s3
model_name = "customer_segment-v0.0"
s3 = boto3.client('s3')
s3.download_file("feast-demo-mar-2022", 
                 f"model-repo/{model_name}", 
                 model_name)

在执行前面的代码块之后,你应该在当前工作目录中看到一个名为 customer_segment-v0.0 的文件。你可以使用 ls 命令或通过文件夹浏览器来验证它。

重要提示

将前一个代码块中的 <aws_key_id><aws_secret> 替换为在 第四章 中创建的用户凭据,添加特征存储到机器学习模型

  1. 下一步是获取需要评分的客户列表。如前所述,这可以从原始数据源中获取,但为了练习的目的,我将硬编码一个客户样本列表。为了模拟从原始数据源获取客户,我将调用一个返回客户列表的函数。

下面的代码块显示了从原始数据源获取客户的模拟函数:

def fetch_customers_from_raw_data():
  ## todo: code to fetch customers from raw data
  return ["12747.0", "12841.0", "12849.0", 
          "12854.0", "12863.0"]
customer_to_be_scored=fetch_customers_from_raw_data()
  1. 现在我们有了要评分的客户列表,下一步是获取这些客户的特征。有几种不同的方法可以做到这一点。一种方法是使用在线存储,另一种方法是使用离线存储。对于批处理模型,由于延迟不是必需的,最经济的方法是使用离线存储;只是离线存储需要查询最新的特征。这可以通过使用 event_timestamp 列来完成。让我们使用离线存储并查询给定客户列表所需的特征。为此,我们需要实体 DataFrame。让我们接下来创建它。

  2. 下面的代码块创建所需的实体 DataFrame 以获取最新的特征:

    import pandas as pd
    from datetime import datetime
    entity_df = pd.DataFrame(data={
        "customerid": customer_to_be_scored,
        "event_timestamp": datetime.now()
    })
    entity_df.head()
    

前面的代码块输出以下实体 DataFrame:

![Figure 5.6 – 预测用实体 DataFrame

![img/B18024_05_006.jpg]

图 5.6 – 预测用实体 DataFrame

要获取任何客户的最新特征,您需要将 event_timestamp 设置为 datetime.now()。让我们使用 图 5.4 中的实体 DataFrame 来查询离线存储。

  1. 下面的代码块获取给定实体 DataFrame 的特征:

    %cd customer_segmentation
    from feast import FeatureStore
    store = FeatureStore(repo_path=".")
    job = store.get_historical_features(
        entity_df=entity_df,
        features=[
                  "customer_rfm_features:recency", 
                  "customer_rfm_features:monetaryvalue", 
                  "customer_rfm_features:r", 
                  "customer_rfm_features:m",
                  "customer_rfm_features:rfmscore",
                  "customer_rfm_features:segmenthighvalue",
                  "customer_rfm_features:segmentlowvalue",
                  "customer_rfm_features:segmentmidvalue"
              ]
        )
    pred_feature_data = job.to_df()
    pred_feature_data = pred_feature_data.dropna()
    pred_feature_data.head()
    

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

![Figure 5.7 – 预测用特征

![img/B18024_05_007.jpg]

图 5.7 – 预测用特征

  1. 现在我们有了用于预测的特征,下一步是加载下载的模型,并使用 图 5.5 中的特征为客户运行预测。下面的代码块正是这样做的:

    import joblib
    ## Drop unwanted columns
    features = pred_feature_data.drop(
        ['customerid', 'event_timestamp'], axis=1)
    loaded_model = joblib.load('/content/customer_segment-v0.0')
    prediction = loaded_model.predict(features)
    
  2. 运行预测的最后一步是将预测结果存储在数据库或对象存储中,以便以后使用。在这个练习中,我将把预测结果写入 S3 桶。您可以将结果沉入其他数据存储。

  3. 下面的代码块将预测结果以及特征保存到 S3 位置:

    file_name = f"customer_ltv_pred_results_{datetime.now()}.parquet"
    pred_feature_data["predicted_ltvcluster"] = prediction.tolist()
    s3_url = f's3://feast-demo-mar-2022/prediction_results/{file_name}'
    pred_feature_data.to_parquet(s3_url)
    

通过最后一个代码块,我们完成了批量模型的实现。你心中的疑问可能是 特征存储的引入是如何改变到目前为止的机器学习生命周期的?。它的早期采用解耦了特征工程、模型训练和模型评分的步骤。它们中的任何一个都可以独立运行,而无需干扰管道的其他部分。这是一个巨大的好处。另一部分是部署。我们在第一步中创建的笔记本是具体的,执行特定的任务,如特征工程、模型训练和模型评分。

现在,为了将模型投入生产,我们只需要使用编排框架安排特征工程笔记本和模型评分笔记本,模型就会以全规模运行。我们将在下一章中探讨模型的投入生产。

在下一节中,我们将看看在线模型使用案例需要做些什么。

使用 Feast 进行在线模型推理

在上一节中,我们讨论了如何在批量模型推理中使用 Feast。现在,是时候看看在线模型的使用案例了。在线模型推理的一个要求是它应该以低延迟返回结果,并且可以从任何地方调用。其中一种常见的范式是将模型作为 REST API 端点公开。在 模型打包 部分,我们使用 joblib 库记录了模型。该模型需要用 RESTful 框架包装,以便作为 REST 端点部署。不仅如此,当推理端点被调用时,特征也需要实时获取。与 第一章 中讨论的,在 机器学习生命周期概述 中,我们没有实时服务特征的架构不同,这里,我们已经有了 Feast 的支持。然而,我们需要运行命令,使用 Feast 库将离线特征同步到在线商店。让我们先做这个。稍后,我们将探讨打包。

同步最新特征从离线到在线商店

要将特征从离线存储加载到在线商店,我们需要 Feast 库:

  1. 让我们打开一个笔记本并安装所需的依赖项:

    !pip install feast[aws]==0.19.3
    
  2. 在安装所需的依赖项后,克隆特征存储仓库。如前所述,这是所有笔记本的要求。假设您已经将仓库克隆到当前工作目录中,以下命令将从离线存储加载最新特征到在线商店:

    %cd customer_segmentation/
    from datetime import datetime
    import os
    #aws Credentials
    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"
    # Command to sync offline features into online.
    !feast materialize-incremental {datetime.now().isoformat()}
    

前面的命令会输出如下截图所示的进度:

图 5.8 – 同步离线数据到在线商店

图 5.8 – 同步离线数据到在线商店

  1. 在将离线数据加载到在线商店后,让我们在在线商店上运行一个查询,并确保它按预期工作。要查询在线商店,初始化特征商店对象并调用 get_online_features API,如下面的代码块所示:

    import pandas as pd
    from feast import FeatureStore
    store = FeatureStore(repo_path=".")
    feature_vector = store.get_online_features(
        features=[
            "customer_rfm_features:recency", 
            "customer_rfm_features:monetaryvalue", 
            "customer_rfm_features:r", 
            "customer_rfm_features:m",
        ],
        entity_rows=[
            {"customer": "12747.0"},
            {"customer": "12841.0"},
    {"customer": "abcdef"},
        ],
    ).to_dict()
    df = pd.DataFrame(feature_vector)
    df.head()
    

上述代码块以低延迟从在线商店(DynamoDB)获取数据。当你运行上述代码块时,你会注意到它响应的速度有多快,与历史存储查询相比。代码块的输出如下所示:

图 5.9 – 查询在线商店

图 5.9 – 查询在线商店

图 5.7 的最后一行包含 NaN 值。这是 Feast 如果给定的任何实体 ID 都不存在于在线商店中时的响应示例。在这个例子中,具有 ID abcdef 的客户不存在于特征商店中,因此它为相应的行返回 NaN 值。

现在在线商店已经准备好最新的特征,让我们看看如何将模型打包成 RESTful API。

使用 Feast 代码将在线模型打包成 REST 端点

这一部分更多地关于软件工程,而不是数据工程或数据科学技能。Python 有许多 REST API 框架可供选择,例如 POST 方法端点,它将接受客户 ID 列表作为输入并返回预测列表:

  1. 下面的代码块显示了将要实现的 API 协议:

    POST /invocations
    {
       "customer_list": ["id1", "id2", …]
    }
    Response: status 200
    {
    "predictions": [0, 1, …]
    }
    
  2. 现在我们有了 API 协议,下一步是选择我们将要使用的 REST 框架。在现有的 REST 框架中选择一个框架与其他框架相比有不同的权衡。由于这超出了本书的范围,我将使用 fastapi (fastapi.tiangolo.com/),因为它是一个异步框架。如果你熟悉其他框架,如 flaskdjango,请随意使用。无论你使用哪个框架,预测结果都将相同。无论你选择哪个框架,请记住,在部署之前,我们将对 REST API 进行 Docker 化。

要构建 API,我将使用 PyCharm IDE。如果你有其他喜欢的 IDE,请随意使用。此外,为了开发 API 和运行 API,我们需要以下库:feast[aws]uvicorn[standard]fastapijoblibxgboost。你可以使用 pip install 命令安装这些库。我将由你来决定,因为安装步骤取决于你使用的 IDE、平台以及个人偏好。然而,我将使用 virtualenv 来管理我的 Python 环境。

我的项目文件夹结构如下所示。如果你还没有注意到,特征仓库也被复制到了同一个文件夹中,因为我们需要初始化特征商店对象以及特征在线商店:

![图 5.10 – 在 IDE 中的在线模型文件夹结构img/B18024_05_010.jpg

图 5.10 – IDE 中的在线模型文件夹结构

  1. main.py 文件中,让我们定义我们将要实现的 API。复制以下代码并将其粘贴到 main.py 文件中:

    from fastapi import FastAPI
    app = FastAPI()
    @app.get("/ping")
    def ping():
        return {"ping": "ok"}
    @app.post("/invocations")
    def inference(customers: dict):
        return customers
    

如前一个代码块所示,有两个 API:pinginference

  • pingping API 是一个健康检查端点,在部署应用程序时将需要它。ping URL 将由基础设施,如 ECS 或 Kubernetes,用于检查应用程序是否健康。

  • inference:另一方面,inference API 将包含从特征存储中获取给定客户的特征、对模型进行评分并返回结果的逻辑。

  1. 一旦你复制了前面的代码并将其粘贴到 main.py 文件中并保存,请转到终端并运行以下命令:

    cd <project_folder>
    uvicorn main:app --reload
    
  2. 前面的命令将在本地服务器上运行 FastAPI 服务器并打印类似于以下代码块的输出:

    $ uvicorn main:app --reload
    INFO:     Will watch for changes in these directories: ['<folder path>']
    INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
    INFO:     Started reloader process [24664] using watchgod
    WARNING:  The --reload flag should not be used in production on Windows.
    INFO:     Started server process [908]
    INFO:     Waiting for application startup.
    INFO:     Application startup complete.
    

    重要提示

    确保在运行命令之前已在终端中激活了虚拟环境。

  3. 应用程序运行后,访问 URL 127.0.0.1:8000/docs。你应该会看到一个 Swagger UI,如下面的截图所示:

Figure 5.11 – API 的 Swagger UI

img/B18024_05_011.jpg

图 5.11 – API 的 Swagger UI

我们将在 图 5.9 中使用 Swagger UI 来稍后调用 API。现在,请随意玩耍,探索可用的功能,并调用 API。

  1. 现在我们已经设置了 API 结构,接下来让我们实现 inference API。如前所述,inference API 将从特征存储中读取特征并运行预测。

  2. 我们还需要从模型仓库中加载模型。在我们的案例中,仓库是 S3。因此,我们需要代码从 S3 位置下载模型并将其加载到内存中。以下代码块从 S3 下载模型并将其加载到内存中。请注意,这是在应用程序初始加载期间的一次性活动。因此,让我们在 main.py 文件中的函数外部添加以下代码:

    import boto3
    Import joblib
    model_name = "customer_segment-v0.0"
    s3 = boto3.client('s3')
    ## download file from s3
    s3.download_file(
        "feast-demo-mar-2022",
        f"model-repo/{model_name}",
        model_name)
    ## Load the model into memory.
    loaded_model = joblib.load('customer_segment-v0.0')
    
  3. 现在模型已加载到内存中,下一步是初始化特征存储对象。初始化也可以在方法外部进行,因为它是一次性活动:

    #initialize the feature store object.
    store = FeatureStore(repo_path=os.path.join(os.getcwd(), "customer_segmentation"))
    
  4. 由于 customer_segmentation 功能仓库与 main.py 文件处于同一级别,如图 5.8 所示,我已经适当地设置了 repo_path。从在线商店获取特征、运行预测和返回结果的剩余逻辑将放入 inference 方法定义中。以下代码块包含相同的内容。复制该方法并将其替换到 main.py 文件中:

    @app.post("/invocations")
    def inference(customers: dict):
        ##Step1: list of features required for scoring the model
        required_features = [
            "customer_rfm_features:recency",
            "customer_rfm_features:monetaryvalue",
            "customer_rfm_features:r",
            "customer_rfm_features:m",
            "customer_rfm_features:rfmscore",
            "customer_rfm_features:segmenthighvalue",
            "customer_rfm_features:segmentlowvalue",
            "customer_rfm_features:segmentmidvalue"
        ]
        ##step 2: get entity rows from the input
        entity_rows = [{"customer": cust_id} for cust_id in customers["customer_list"]]
        ##Step 3: query online store
        feature_vector = store.get_online_features(
            features=required_features,
            entity_rows=entity_rows,
        ).to_dict()
        ##Step 4: convert features to dataframe and reorder the feature columns in the same order that model expects.
        features_in_order = ['recency', 'monetaryvalue', 
                             'r', 'm', 'rfmscore', 
                             'segmenthighvalue', 
                             'segmentlowvalue', 
                             'segmentmidvalue']
        df = pd.DataFrame(feature_vector)
        features = df.drop(['customerid'], axis=1)
        features = features.dropna()
        features = features[features_in_order]
        ##Step 5: run prediction and return the list
        prediction = loaded_model.predict(features)
        return {"predictions": prediction.tolist()}
    
  5. 现在预测逻辑已完成,让我们运行应用程序并尝试运行预测。要运行应用程序,命令与之前使用的一样:

    <aws_key_id> and <aws_secret> in the preceding code block with the user credentials created in *Chapter 4*, *Adding Feature Store to ML Models*.
    
  6. 一旦应用程序成功加载,请访问 Swagger UI 网址 (localhost:8000/docs)。在 Swagger UI 中,展开 invocations API 并点击 尝试。你应该会看到一个类似于 图 5.12 的屏幕。

图 5.12 – Swagger UI 调用 API

图 5.12 – Swagger UI 调用 API

  1. 在请求体中,提供如图 图 5.12 所示的输入(以下代码块中的那个):

    {"customer_list":["12747.0", "12841.0"]}
    
  2. 使用这个输入,通过点击 执行 提交请求。API 应该在毫秒内响应,当你在屏幕上向下滚动时,输出将可见。以下图显示了示例输出:

图:5.13 – 在线模型响应

图:5.13 – 在线模型响应

这样就完成了为在线模型构建 REST API 的步骤,并附有从 Feast 获取特征的代码。现在我们既有在线模型也有批量模型,在下一章中,我们将探讨如何将这些模型投入生产,以及如何通过早期采用特征存储和 MLOps,将开发到生产的过渡变得简单。

我们还没有探讨的是如何更改/更新或添加额外的特征。在我们继续之前,让我们简要地看看这个问题。

在开发过程中处理特征集的变化

模型开发是一个不断演变的过程。模型也是如此——它们会随着时间的推移而演变。今天,我们可能只使用几个特征来构建特定的模型,但随着我们不断发现和尝试新的特征,如果新特征的性能优于当前模型,我们可能会在模型训练和评分中包含这些新特征。因此,特征集可能会随时间而变化。这意味着在特征存储中,我们在 第四章 中执行的一些步骤,即 将特征存储添加到机器学习模型中,可能需要重新审视。让我们看看这些步骤是什么。

重要提示

这里的假设是在模型开发过程中特征定义发生变化,而不是在生产之后。我们将在后面的章节中探讨模型投入生产后如何处理特征集的变化。

第 1 步 – 更改特征定义

如果在模型开发过程中特征或实体发生变化,第一步是更新特征存储库中的特征定义。如果你记得正确,当特征最终确定时,我们首先做的事情是创建特征定义。在特征存储库中,文件 rfm_features.py 包含了定义。在做出更改后,运行 feast apply 命令以更新资源中的特征定义。如果你创建或删除了新的实体或视图,相应的在线存储资源(DynamoDB 表)将被创建或删除。你可以在控制台中验证这一点。如果只有一些小的更改,例如更改数据类型或特征名称,这些更改将被保存在特征存储库注册表中。

第 2 步 – 在 Glue/Lake Formation 控制台中添加/更新模式

第二步是定义我们在 Glue/Lake Formation 数据库中创建的新表。如果旧表不再需要,您可以删除它们以避免以后产生混淆。在模式变更的情况下(如果特征名称或数据类型发生变化),您需要更新现有模式以反映这些更改。如果模式没有随着更改而更新,那么在查询历史存储或尝试从离线存储加载最新特征到在线存储时将出现错误。在此处还需要注意的另一件事是,在定义模式时,我们为特征视图设置了 S3 位置。现在这个位置包含旧数据,它仅适用于旧模式,因此您需要定义一个新的路径,以便符合新模式的数据将被写入。

另一种方法是定义一个新的表,包含新的模式定义和新的 S3 路径,以及更新特征存储库中的 Redshift 源定义以新的表名。如果您这样做,您可以查询旧定义和新定义中的数据。然而,请记住,您可能需要管理两个版本的特征集,一个具有旧模式,另一个具有新模式。此外,将有两个 DynamoDB 表。

第 3 步 – 更新笔记本中的更改

最后一步很简单,就是更新所有受影响的笔记本。在特征工程笔记本中,更新将写入新位置的数据,而在模型训练和评分笔记本中,则分别是在训练和评分期间更新特征名称或获取额外的特征。

这些是每次特征定义更新时都需要执行的三个步骤。有了这些,让我们总结一下本章学到的内容,在下一章中,我们将探讨如何将本章构建的在线和批量模型投入生产,以及生产之外面临的挑战。

摘要

在本章中,我们的目标是探讨模型训练和评分如何随着特征存储的变化而变化。为了通过 ML 生命周期的训练和评分阶段,我们使用了上一章中创建的资源。在模型训练阶段,我们探讨了数据工程师和数据科学家如何协作并共同努力构建更好的模型。在模型预测中,我们讨论了批量模型评分,以及使用离线存储作为运行批量模型的成本效益方式。我们还为在线模型构建了一个 REST 包装器,并添加了 Feast 代码,以便在运行时获取预测所需的特征。在本章结束时,我们探讨了在开发过程中如果特征有更新时所需的更改。

在下一章中,我们将继续使用本章构建的批量模型和在线模型,将它们投入生产,并探讨模型投入生产后面临的挑战。

进一步阅读

您可以在以下参考资料中找到更多关于 Feast 的信息:

第七章:模型到生产及之后

在上一章中,我们讨论了使用 Feast 进行在线和批量模型的模型训练和预测。对于练习,我们使用了在 第四章 添加特征存储到机器学习模型 练习期间部署到 AWS 云的 Feast 基础设施。在这些练习中,我们探讨了 Feast 如何将特征工程与模型训练和模型预测解耦。我们还学习了如何在批量预测和在线预测期间使用离线和在线存储。

在本章中,我们将重用 第四章 添加特征存储到机器学习模型第五章 模型训练和推理 中构建的特征工程管道和模型,以投入生产机器学习(ML)管道。本章的目标是重用我们在前几章中构建的一切,例如 AWS 上的 Feast 基础设施、特征工程、模型训练和模型评分笔记本,以投入生产 ML 模型。随着我们进行练习,这将给我们一个机会来了解 Feast 的早期采用不仅解耦了 ML 管道阶段,还加速了 ML 模型的生产准备。一旦我们将批量和在线 ML 管道投入生产,我们将探讨 Feast 的采用如何为 ML 生命周期的其他方面开辟机会,例如特征监控、自动模型重新训练,以及它如何加速未来 ML 模型的开发。本章将帮助您了解如何投入生产使用 Feast 的批量和在线模型,以及如何使用 Feast 进行特征漂移监控和模型重新训练。

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

  • 设置 Airflow 进行编排

  • 将批量模型管道投入生产

  • 将在线模型管道投入生产

  • 超越模型生产

技术要求

为了跟随章节中的代码示例,需要使用在 第四章 添加特征存储到机器学习模型第五章 模型训练和推理 中创建的资源。您需要熟悉 Docker 和任何笔记本环境,这可能是一个本地设置,例如 Jupyter,或者一个在线笔记本环境,例如 Google Colab、Kaggle 或 SageMaker。您还需要一个 AWS 账户,可以完全访问一些资源,例如 Redshift、S3、Glue、DynamoDB 和 IAM 控制台。您可以在试用期间创建一个新账户并免费使用所有服务。您可以在以下 GitHub 链接中找到本书的代码示例和特征存储库:

设置 Airflow 以进行编排

为了将在线和批量模型投入生产,我们需要一个工作流程编排工具,它可以按计划为我们运行 ML 管道。有多个工具可供选择,例如 Apache Airflow、AWS Step Functions 和 SageMaker Pipelines。如果您更喜欢,也可以将其作为 GitHub 工作流程运行。根据您熟悉或组织提供的工具,编排可能会有所不同。对于这个练习,我们将使用 Amazon Managed Workflows for Apache AirflowMWAA)。正如其名所示,这是 AWS 提供的由 Apache Airflow 管理的服务。让我们在 AWS 中创建一个 Amazon MWAA 环境。

重要提示

Amazon MWAA 没有免费试用。您可以通过此网址查看使用价格:aws.amazon.com/managed-workflows-for-apache-airflow/pricing/. 或者,您可以选择在本地或 EC2 实例上运行 Airflow(EC2 提供免费层资源)。您可以在以下位置找到运行 Airflow 本地或 EC2 的设置说明:

Airflow 本地设置:towardsdatascience.com/getting-started-with-airflow-locally-and-remotely-d068df7fcb4

Airflow 在 EC2 上:christo-lagali.medium.com/getting-airflow-up-and-running-on-an-ec2-instance-ae4f3a69441

S3 存储桶用于 Airflow 元数据

在我们创建环境之前,我们需要一个 S3 存储桶来存储 Airflow 依赖项,airflow-for-ml-mar-2022。在 S3 存储桶中,创建一个名为 dags 的文件夹。我们将使用此文件夹来存储所有 Airflow DAG。

Amazon MWAA 提供了多种不同的方式来配置要安装到 Airflow 环境中的附加插件和 Python 依赖项。由于我们需要安装一些 Python 依赖项来运行我们的项目,我们需要告诉 Airflow 安装这些必需的依赖项。一种方法是通过使用 requirements.txt 文件。以下代码块显示了文件的内容:

papermill==2.3.4
boto3==1.21.41
ipython==8.2.0
ipykernel==6.13.0
apache-airflow-providers-papermill==2.2.3

将前面代码块的内容保存到 requirements.txt 文件中。我们将使用 papermill (papermill.readthedocs.io/en/latest/) 来运行 Python 笔记本。您还可以使用 Airflow 中可用的 bashpython 操作提取代码并运行 Python 脚本。

重要提示

如果您在本地运行 Airflow,请确保库版本与 Airflow 版本兼容。撰写本文时,Amazon MWAA 的 Airflow 版本是 2.2.2。

一旦创建了requirement.txt文件,将其上传到我们创建的 S3 存储桶中。我们将在创建环境时下一节使用它。

Amazon MWAA 环境用于编排

现在我们已经拥有了创建 Amazon MWAA 环境所需的资源,让我们按照以下步骤创建环境:

  1. 要创建一个新环境,登录到您的 AWS 账户,并使用 AWS 控制台中的搜索栏导航到 Amazon MWAA 控制台。或者,访问us-east-1.console.aws.amazon.com/mwaa/home?region=us-east-1#environments。以下网页将显示:

![图 6.1 – Amazon MWAA 环境控制台

![图片 B18024_06_001.jpg]

图 6.1 – Amazon MWAA 环境控制台

  1. 图 6.1显示的页面上,点击创建环境按钮,随后将显示以下页面:

![图 6.2 – Amazon MWAA 环境详情

![图片 B18024_06_002.jpg]

图 6.2 – Amazon MWAA 环境详情

  1. 图 6.2显示的页面上为 Amazon MWAA 环境提供一个名称。向下滚动到Amazon S3 中的 DAG 代码部分;你应该在屏幕上看到以下参数:

![图 6.3 – Amazon MWAA – S3 中的 DAG 代码部分

![图片 B18024_06_003.jpg]

图 6.3 – Amazon MWAA – S3 中的 DAG 代码部分

  1. 图 6.3显示的屏幕上,在文本框中输入 S3 存储桶或使用我们上传的requirements.txt文件,或输入文件的路径。由于我们不需要任何插件来运行项目,我们可以将可选的插件文件字段留空。点击下一步按钮:

![图 6.4 – Amazon MWAA 高级设置

![图片 B18024_06_004.jpg]

图 6.4 – Amazon MWAA 高级设置

  1. 下一个显示的页面如图 6.4 所示。对于虚拟专用云(VPC),从下拉列表中选择可用的默认 VPC。这里有一个注意事项,所选 VPC 应至少有两个私有子网。如果没有私有子网,当你尝试选择子网 1子网 2时,你会注意到所有选项都变灰了。如果你遇到这种情况,点击创建 MWAA VPC。它将带你进入 CloudFormation 控制台;一旦你填写了所有参数的表格,继续操作并点击创建堆栈。它将创建一个 Amazon MWAA 可以使用的 VPC。一旦 VPC 创建完成,返回此窗口并选择新的 VPC 和子网,然后继续。

  2. 在选择 VPC 后,对于Web 服务器访问,选择公共网络;将其他所有选项保留为默认设置,并将滚动条拉至最底部。在权限部分,你会注意到它将创建一个新的角色用于 Amazon MWAA。记下角色名称。我们稍后需要向此角色添加权限。之后,点击下一步

  3. 在下一页,查看所有提供的输入,滚动到最底部,然后点击创建环境。创建环境可能需要几分钟。

  4. 环境创建完成后,你应该能够在 Amazon MWAA 环境页面上看到处于可用状态的环境。选择我们刚刚创建的环境,然后点击打开 Airflow UI链接。将显示一个 Airflow 主页,类似于以下图所示:图 6.5 – Airflow UI

图 6.5 – Airflow UI

  1. 为了测试一切是否运行正常,让我们快速创建一个简单的 DAG 并查看其工作方式。以下代码块创建了一个简单的 DAG,包含一个虚拟操作符和一个 Python 操作符:

    from datetime import datetime
    from airflow import DAG
    from airflow.operators.dummy_operator import DummyOperator
    from airflow.operators.python_operator import PythonOperator
    def print_hello():
        return 'Hello world from first Airflow DAG!'
    dag = DAG('hello_world', 
              description='Hello World DAG',
              schedule_interval='@daily',
              start_date=datetime(2017, 3, 20), 
              catchup=False)
    start = DummyOperator(task_id="start", dag=dag)
    hello_operator = PythonOperator(
        task_id='hello_task', 
        python_callable=print_hello, 
        dag=dag)
    start >> hello_operator
    
  2. 在前面的代码中定义的 DAG 相当简单;它有两个任务 – starthello_operatorstart 任务是一个 DummyOperator,什么都不做,用于使 DAG 在 UI 上看起来更美观。hello_operator 任务只是调用一个返回消息的函数。在最后一行,我们定义了操作符之间的依赖关系。

  3. 复制前面的代码块,将其保存为example_dag.py,并将其上传到我们之前创建的 S3 中的dags文件夹。(我的 S3 位置是s3://airflow-for-ml-mar-2022/dags。)上传后,它应该在几秒钟内出现在 Airflow UI 中。以下图显示了带有 DAG 的 Airflow UI:

图 6.6 – 带示例 DAG 的 Airflow UI

图 6.6 – 带示例 DAG 的 Airflow UI

  1. 默认情况下,DAG 是禁用的;因此,当你访问页面时,你可能看不到像图 6.6中显示的确切页面。通过点击最左侧列中的切换按钮启用 DAG。一旦启用,DAG 将首次运行并更新运行结果。你还可以使用链接列中的图标触发 DAG。在 UI 的 DAG 列中点击hello_world超链接。你将看到 DAG 的不同选项卡详情页面。请随意尝试并查看详情页面上的不同选项。

  2. 以下图显示了 DAG 的图形视图:

图 6.7 – DAG 的图形视图

图 6.7 – DAG 的图形视图

  1. 现在我们已经验证了 Airflow 设置正确,让我们为 Airflow 运行 ML 管道添加所需的权限。

  2. 如果你还记得,在环境创建的最后一步(图 6.4之后的段落),我们记录了 Airflow 环境运行 DAG 时使用的角色名称。现在,我们需要向该角色添加权限。为此,使用搜索功能导航到 AWS IAM 角色控制台页面或访问 https://us-east-1.console.aws.amazon.com/iamv2/home?region=us-east-1#/roles。在控制台中,你应该看到与 Airflow 环境关联的 IAM 角色。选择 IAM 角色;你应该会看到以下页面:

![图 6.8 – Amazon MWAA IAM 角色img/B18024_06_008.jpg

图 6.8 – 亚马逊 MWAA IAM 角色

重要提示

如果你没有做笔记,你可以在 AWS 控制台的“环境详情”页面上找到角色名称。

  1. 图 6.8中,点击添加权限;从下拉菜单中选择添加策略,你将被带到以下页面:

![Figure 6.9 – IAM – 添加策略img/B18024_06_009.jpg

图 6.9 – IAM – 添加策略

  1. 在网页上,搜索并选择以下策略 – AmazonS3FullAccessAWSGlueConsoleFullAccessAmazonRedshiftFullAccessAmazonDynamoDBFullAccess。一旦选择了策略,向下滚动并点击添加策略以保存带有新策略的角色。

    重要提示

    没有任何限制地分配任何资源的完全访问权限从来都不是一个好主意。当你运行企业应用时,建议根据资源限制访问,例如只读访问特定的 S3 桶和 DynamoDB 表。

    如果你是在本地运行 Airflow,你可以在笔记本中使用 IAM 用户凭证。

现在我们已经准备好了编排系统,让我们看看如何使用它来将机器学习流水线投入生产。

批量模型流水线的投入生产

第四章,“将特征存储添加到机器学习模型”中,为了模型训练,我们使用了特征工程笔记本中摄取的特征。我们还创建了一个模型评分笔记本,它从 Feast 中获取一组客户的特征,并使用训练好的模型对其进行预测。为了实验的目的,让我们假设原始数据的新鲜度延迟为一天。这意味着特征需要每天重新生成一次,模型需要每天对客户进行评分,并将结果存储在 S3 桶中以供消费。为了实现这一点,多亏了我们早期的组织和阶段解耦,我们只需要每天连续运行特征工程和模型评分笔记本/Python 脚本。现在我们也有了一个执行这个任务的工具,让我们继续在 Airflow 环境中安排这个工作流程。

下图显示了我们将如何运营化批量模型:

![Figure 6.10 – 批量模型的运营化img/B18024_06_010.jpg

图 6.10 – 批量模型的运营化

正如你在图中所见,为了使工作流程投入运行,我们将使用 Airflow 来编排特征工程和模型评分笔记本。在我们的案例中,特征工程的原始数据源是存储 online-retail.csv 的 S3 存储桶。由于我们已设计好评分笔记本从模型仓库(在我们的案例中是一个 S3 存储桶)加载生产模型并将预测结果存储在 S3 存储桶中,我们将重用相同的笔记本。你可能在这里注意到的一点是我们不是每次运行都使用模型训练笔记本;原因很明显 – 我们希望针对经过验证、测试并且也在测试数据上满足我们性能标准的模型版本进行预测。

在安排此工作流程之前,我对特征工程笔记本和模型预测笔记本进行了少量修改。最终的笔记本可以在以下 GitHub URL 中找到:github.com/PacktPublishing/Feature-Store-for-Machine-Learning/blob/main/Chapter06/notebooks/(ch6_feature_engineering.ipynb,ch6_model_prediction.ipynb)。要安排工作流程,请从 GitHub 下载最终的笔记本并将它们上传到我们之前创建的 S3 存储桶,因为 Airflow 环境在运行期间需要访问这些笔记本。我将将其上传到以下位置:s3://airflow-for-ml-mar-2022/notebooks/

重要提示

AWS 密钥访问密钥和 S3 路径 – 我在两个笔记本中都注释掉了 AWS 凭据,因为我们正在向 Amazon MWAA IAM 角色添加权限。如果你在本地 Airflow 中运行它,请取消注释并添加密钥。同时,在必要时更新 S3 URL,因为 S3 URL 指向我在练习期间创建的私有存储桶。

功能仓库 – 如我们之前所见,我们必须克隆功能仓库,以便 Feast 库可以读取元数据。你可以遵循相同的 git clone 方法(前提是已安装 git)或者设置 GitHub 工作流程将仓库推送到 S3 并在笔记本中下载相同的文件。我已经在笔记本中留下了两个代码块并附有注释。你可以使用任何方便的方法。

S3 方法 – 要使用 S3 下载方法,在你的本地系统中克隆仓库,然后在 Linux 终端中运行以下命令将其上传到特定的 S3 位置:

export AWS_ACCESS_KEY_ID=<aws_key>

export AWS_SECRET_ACCESS_KEY=<aws_secret>

AWS_DEFAULT_REGION=us-east-1

aws s3 cp customer_segmentation s3://<s3_bucket>/customer_segmentation --recursive

上传成功后,你应该能够在 S3 存储桶中看到文件夹内容。

现在笔记本已经准备好了,让我们编写批量模型管道的 Airflow DAG。DAG 将按以下顺序包含以下任务 – start(虚拟操作符)、feature_engineering(Papermill 操作符)、model_prediction(Papermill 操作符)和 end(虚拟操作符)。

以下代码块包含 Airflow DAG 的第一部分:

from datetime import datetime
from airflow import DAG
from airflow.operators.dummy_operator import DummyOperator
from airflow.providers.papermill.operators.papermill import PapermillOperator
import uuid
dag = DAG('customer_segmentation_batch_model', 
          description='Batch model pipeline', 
          schedule_interval='@daily', 
          start_date=datetime(2017, 3, 20), catchup=False)

在前面的代码块中,我们定义了导入和 DAG 参数,如 nameschedule_intervalstart_dateschedule_interval='@daily' 调度表示 DAG 应该每天运行。

以下代码块定义了 DAG 的其余部分(第二部分),其中包含所有任务及其之间的依赖关系:

start = DummyOperator(task_id="start", dag=dag)
run_id = str(uuid.uuid1())
feature_eng = PapermillOperator(
    task_id="feature_engineering",
    input_nb="s3://airflow-for-ml-mar-2022/notebooks/ch6_feature_engineering.ipynb",
    output_nb=f"s3://airflow-for-ml-mar-2022/notebooks/runs/ch6_feature_engineering_{ run_id }.ipynb",
    dag=dag,
    trigger_rule="all_success"
)
model_prediction = PapermillOperator(
    task_id="model_prediction",
    input_nb="s3://airflow-for-ml-mar-2022/notebooks/ch6_model_prediction.ipynb",
    output_nb=f"s3://airflow-for-ml-mar-2022/notebooks/runs/ch6_model_prediction_{run_id}.ipynb",
    dag=dag,
    trigger_rule="all_success"
)
end = DummyOperator(task_id="end", dag=dag, 
                    trigger_rule="all_success")
start >> feature_eng >> model_prediction >> end

如您在代码块中看到的,这里有四个步骤将依次执行。feature_engineeringmodel_prediction 步骤使用 PapermillOperator 运行。这需要输入 S3 笔记本路径。我还设置了一个输出路径到另一个 S3 位置,这样我们就可以检查每次运行的输出笔记本。最后一行定义了任务之间的依赖关系。将前两个代码块(第一部分和第二部分)保存为 Python 文件,并将其命名为 batch-model-pipeline-dag.py。保存文件后,导航到 S3 控制台,将文件上传到我们在 图 6.3 中指向的 Airflow 环境的 dags 文件夹。上传的文件将由 Airflow 调度器处理。当您导航到 Airflow UI 时,您应该在屏幕上看到名为 customer_segmentation_batch_model 的新 DAG。

以下图显示了包含 DAG 的 Airflow UI:

图 6.11 – Airflow 上的批量模型 DAG

图 6.11 – Airflow 上的批量模型 DAG

由于我们在创建 Airflow 环境时没有默认启用 DAG 选项(这可以在 Amazon MWAA 中的 Airflow 配置变量中设置),当 DAG 首次出现在 UI 上时,它将处于禁用状态。点击最左侧列的切换按钮来启用它。一旦启用,DAG 将首次运行。点击 customer_segmentation_batch_model 超链接导航到详情页面,并随意查看 DAG 的不同可视化和属性。如果您导航到 Graph 选项卡,DAG 将如以下截图所示显示:

图 6.12 – 批量模型 DAG 图形视图

图 6.12 – 批量模型 DAG 图形视图

图 6.12 中,您可以查看 DAG 的图形视图。如果上次运行有任何失败,它们将以红色轮廓显示。您还可以查看每个任务的执行日志或失败记录。由于所有任务都是绿色的,这意味着一切顺利。您还可以在 图 6.11 中查看最近几次运行的成果。Airflow 还为您提供所有运行的记录。

现在任务运行已完成,我们可以去检查输出笔记本、新特征集的 S3 桶或新预测集的 S3 桶。所有这三个在成功运行后都应可用。在这里,我们将验证预测结果文件夹,但也请随意验证其他文件夹。

重要提示

如果有任何失败,请验证失败任务的日志(在图形视图中点击失败任务以查看可用信息)。检查 Amazon MWAA 的权限、输入/输出的 S3 路径,以及是否在 Amazon MWAA 环境中安装了所有要求。

下面的截图显示了 S3 桶中的新预测结果:

![Figure 6.13 – The prediction results in an S3 bucket]

![img/B18024_06_013.jpg]

图 6.13 – S3 桶中的预测结果

此外,您还可以使用 Airflow 做各种花哨的事情,例如发送失败时的电子邮件通知、日常运行的 Slack 通知,以及与 PagerDuty 的集成。请随意探索选项。以下是 Airflow 支持的服务提供者列表:airflow.apache.org/docs/apache-airflow-providers/packages-ref.html

现在我们批处理模型已在生产环境中运行,让我们看看如何使用 Feast 将在线模型进行生产化。

生产化在线模型管道

在上一章中,对于在线模型,我们构建了 REST 端点以提供客户细分的需求预测。尽管在线模型以 REST 端点托管,但它需要以下功能的支持基础设施:

  • 为了实时提供特征(我们为此有 Feast)

  • 为了保持特征更新(我们将使用带有 Airflow 编排的特征工程笔记本来完成此操作)

在本章中,我们将继续上一章的内容,并使用在第四章“将特征存储添加到机器学习模型”中构建的特征工程笔记本,结合一个笔记本将离线数据同步到 Feast 的在线存储。

下图展示了在线模型管道的运营化:

![Figure 6.14 – The operationalization of the online model]

![img/B18024_06_014.jpg]

图 6.14 – 在线模型的运营化

如[图 6.14]所示,我们将使用 Airflow 进行特征工程的编排;数据新鲜度仍然是每天一次,并且可以安排更短的时间。如果需要,Feast 还可以支持流数据。以下 URL 有一个可用的示例:docs.Feast.dev/reference/data-sources/push。在第五章“模型训练和推理”中开发的 REST 端点将被 Docker 化并作为 SageMaker 端点部署。

重要提示

一旦容器化,Docker 镜像就可以用于部署到任何容器化环境中,例如 Elastic Container Service,Elastic BeanStalk 和 Kubernetes。我们使用 SageMaker,因为它设置起来更快,并且还具有开箱即用的优势,如数据捕获和 IAM 认证。

特征工程作业的编排

由于我们已经有两个笔记本(特征工程和同步离线到在线存储)并且我们对 Airflow 很熟悉,让我们首先安排特征工程工作流程。同样,在笔记本中,我进行了一些小的修改。请在使用之前验证这些修改。您可以在以下位置找到笔记本(ch6_feature_engineering.ipynbch6_sync_offline_online.ipynb):https://github.com/PacktPublishing/Feature-Store-for-Machingithub.com/PacktPublishing/Feature-Store-for-Machine-Learning/tree/main/Chapter06/notebooks)e-Learning/tree/main/Chapter06/notebooks。就像我们对批量模型所做的那样,下载笔记本并将它们上传到特定的 S3 位置。我将会上传到之前相同的位置:s3://airflow-for-ml-mar-2022/notebooks/。现在笔记本已经准备好了,让我们编写在线模型管道的 Airflow DAG。DAG 将按照以下顺序执行步骤 – start(虚拟操作符), feature_engineering(Papermill 操作符), sync_offline_to_online(Papermill 操作符), 和 end`(虚拟操作符)。

下面的代码块包含 Airflow DAG 的第一部分:

from datetime import datetime
from airflow import DAG
from airflow.operators.dummy_operator import DummyOperator
from airflow.providers.papermill.operators.papermill import PapermillOperator
dag = DAG('customer_segmentation_online_model', 
          description='Online model pipeline', 
          schedule_interval='@daily', 
          start_date=datetime(2017, 3, 20), catchup=False)

就像批量模型管道 DAG 的情况一样,这包含 DAG 参数。

下面的代码块定义了 DAG 的其余部分(第二部分),其中包含所有任务及其之间的依赖关系:

start = DummyOperator(task_id="start")
run_time = datetime.now()
feature_eng = PapermillOperator(
    task_id="feature_engineering",
    input_nb="s3://airflow-for-ml-mar-2022/notebooks/ch6_feature_engineering.ipynb",
    output_nb=f"s3://airflow-for-ml-mar-2022/notebooks/runs/ch6_feature_engineering_{run_time}.ipynb",
    trigger_rule="all_success",
    dag=dag
)
sync_offline_to_online = PapermillOperator(
    task_id="sync_offline_to_online",
    input_nb="s3://airflow-for-ml-mar-2022/notebooks/ch6_sync_offline_online.ipynb",
    output_nb=f"s3://airflow-for-ml-mar-2022/notebooks/runs/ch6_sync_offline_online_{run_time}.ipynb",
    trigger_rule="all_success",
    dag=dag
)
end = DummyOperator(task_id="end", trigger_rule="all_success")
start >> feature_eng >> sync_offline_to_online >> end

Airflow DAG 的结构与我们之前看到的批量模型 DAG 类似;唯一的区别是第三个任务,sync_offline_to_online。这个笔记本将离线数据中的最新特征同步到在线数据中。将前两个代码块(第一部分和第二部分)保存为 Python 文件,并将其命名为 online-model-pipeline-dag.py。保存文件后,导航到 S3 控制台,将文件上传到我们在 图 6.3 中指向的 Airflow 环境的 dags 文件夹。与批量模型一样,上传的文件将由 Airflow 调度器处理,当你导航到 Airflow UI 时,你应该在屏幕上看到名为 customer_segmentation_online_model 的新 DAG。

下面的截图显示了带有 DAG 的 Airflow UI:

![图 6.15 – 带有在线和批量模型的 Airflow UI![图片`图 6.15 – 带有在线和批量模型的 Airflow UI 要启用 DAG,点击最左侧列的切换按钮。一旦启用,DAG 将首次运行。点击customer_segmentation_online_model超链接导航到详细信息页面,您可以随意浏览以查看 DAG 的不同可视化和属性。如果您导航到图形选项卡,DAG 将会显示,如下面的屏幕截图所示:![Figure 6.16 – 在线模型管道图形视图img/B18024_06_016.jpg

Figure 6.16 – 在线模型管道图形视图

如您在图 6.16中看到的,在成功运行的情况下,图形将显示为绿色。正如在批量模型管道执行期间所讨论的,您可以验证输出笔记本、DynamoDB 表或 S3 存储桶,以确保一切正常工作,并在出现故障的情况下检查日志。

现在在线模型管道的第一部分已经准备好了,让我们将上一章中开发的 REST 端点 Docker 化,并将它们作为 SageMaker 端点部署。

将模型部署为 SageMaker 端点

要将模型部署到 SageMaker,我们首先需要将我们在第五章“模型训练和推理”中构建的 REST API Docker 化。在我们这样做之前,让我们创建一个弹性容器注册库ECR),在那里我们可以保存模型的 Docker 镜像,并在 SageMaker 端点配置中使用它。

Docker 镜像的 ECR

要创建 ECR 资源,从搜索栏导航到 ECR 控制台或使用以下 URL:us-east-1.console.aws.amazon.com/ecr/repositories?region=us-east-1。将显示以下页面:

![Figure 6.17 – ECR 主页img/B18024_06_017.jpg

Figure 6.17 – ECR 主页

图 6.17显示的页面上,您可以选择私有公共存储库选项卡。然后,点击创建存储库按钮:

![Figure 6.18 – ECR – 创建存储库img/B18024_06_018.jpg

Figure 6.18 – ECR – 创建存储库

我在这里选择了私有;根据您选择的是私有还是公共,选项将会有所不同,但无论如何,操作都很简单。填写所需的字段,滚动到页面底部,然后点击创建存储库。一旦创建存储库,进入存储库详细信息页面,您应该会看到一个类似于图 6.19所示的页面。

重要提示

私有存储库通过 IAM 进行保护,而公共存储库则可以被互联网上的任何人访问。公共存储库主要用于与组织外的人共享/开源您的工作:

![ Figure 6.19 – ECR 存储库详细信息img/B18024_06_019.jpg

Figure 6.19 – ECR 存储库详细信息

在前面的页面上,点击查看推送命令,您应该会看到一个类似于图 6.20所示的弹出窗口:

![Figure 6.20 – ECR 推送命令img/B18024_06_020.jpg

图 6.20 – ECR 推送命令

根据你用于构建 Docker 镜像的操作系统,保存必要的命令。我们将使用这些命令来构建 Docker 镜像。

构建 Docker 镜像

如前所述,在本节中我们将使用上一章中构建的 REST 端点。如果你记得正确,我们添加了两个 REST 端点,pinginvocations。这些端点并非随机,尽管它们可以在任何容器环境中托管。要在 SageMaker 端点中托管 Docker 镜像,要求它应该有 ping(这是 GET 方法)和 invocations(这是 POST 方法)路由。我已经在相同的文件夹结构中添加了一些文件,这些文件将有助于构建 Docker 镜像。REST 代码和文件夹结构可在以下 URL 获取:github.com/PacktPublishing/Feature-Store-for-Machine-Learning/tree/main/online-model-rest-api

重要提示

额外的文件是 Dockerfilerequirements.txtserve

连续地,将 REST 代码克隆到本地系统,将特征仓库复制到项目的 root 目录,导出凭据,然后运行 图 6.20 中的命令。

重要提示

你可以使用在 第四章 中创建的相同用户凭据,将特征存储添加到机器学习模型。然而,我们遗漏了向用户添加 ECR 权限。请导航到 IAM 控制台,并将 AmazonEC2ContainerRegistryFullAccess 添加到用户。否则,你将遇到访问错误。

以下是一些示例命令:

cd online-model-rest-api/
export AWS_ACCESS_KEY_ID=<AWS_KEY>
export AWS_SECRET_ACCESS_KEY=<AWS_SECRET>
export AWS_DEFAULT_REGION=us-east-1
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin <account_number>.dkr.ecr.us-east-1.amazonaws.com
docker build -t customer-segmentation .
docker tag customer-segmentation:latest <account_number>.dkr.ecr.us-east-1.amazonaws.com/customer-segmentation:latest
docker push <account_number>.dkr.ecr.us-east-1.amazonaws.com/customer-segmentation:latest

使用环境变量中设置的凭据登录到 ECR,构建 Docker 镜像,并将 Docker 镜像标记和推送到注册表。一旦镜像被推送,如果你导航回 图 6.19 中的屏幕,你应该会看到新的镜像,如下面的截图所示:

![图 6.21 – 推送到 ECR 的镜像

![img/B18024_06_021.jpg]

图 6.21 – 推送到 ECR 的镜像

现在,镜像已经准备好了,通过点击 图 6.21复制 URI 旁边的图标来复制镜像的 统一资源标识符 (URI)。接下来,让我们将 Docker 镜像作为 SageMaker 端点进行部署。

创建 SageMaker 端点

Amazon SageMaker 致力于为机器学习提供托管基础设施。在本节中,我们只将使用 SageMaker 推理组件。SageMaker 端点用于将模型作为 REST 端点进行实时预测。它支持 Docker 镜像模型,并且开箱即支持一些变体。我们将使用上一节中推送到 ECR 的 Docker 镜像。SageMaker 端点是使用三个构建块构建的 - 模型、端点配置和端点。让我们使用这些构建块并创建一个端点。

SageMaker 模型

模型用于定义模型参数,如名称、模型的存储位置和 IAM 角色。要定义模型,使用搜索栏导航到 SageMaker 控制台,并在推理部分查找模型。或者,访问us-east-1.console.aws.amazon.com/sagemaker/home?region=us-east-1#/models。以下屏幕将显示:

图 6.22 – SageMaker 模型控制台

图 6.22 – SageMaker 模型控制台

在显示的页面上,点击创建模型以跳转到下一屏幕。以下页面将显示:

图 6.23 – SageMaker – 创建模型

图 6.23 – SageMaker – 创建模型

图 6.23所示,输入模型名称,对于 IAM 角色,从下拉菜单中选择创建新角色。将出现一个新的弹出窗口,如下面的屏幕截图所示:

图 6.24 – SageMaker 模型 – 创建 IAM 角色

图 6.24 – SageMaker 模型 – 创建 IAM 角色

在弹出窗口中,为了本次练习的目的,保留所有默认设置,然后点击创建角色。AWS 将创建一个 IAM 角色,在同一屏幕上,你应该在对话框中看到一个带有 IAM 角色链接的消息。以下图显示了显示的消息:

图 6.25 – SageMaker 模型 – 新的执行角色已创建

图 6.25 – SageMaker 模型 – 新的执行角色已创建

现在,如果你记得正确的话,我们正在使用 DynamoDB 作为在线存储;因为我们是从 DynamoDB 表中按需读取数据,所以 IAM 角色需要访问它们。因此,使用页面显示的链接在新标签页中导航到我们刚刚创建的 IAM 角色,添加AmazonDynamoDBFullAccess,然后返回此标签页。向下滚动到容器定义 1部分,你应该会看到以下参数:

图 6.26 – SageMaker 模型 – 容器定义 1 部分

图 6.26 – SageMaker 模型 – 容器定义 1 部分

对于推理代码图像位置参数,粘贴从图 6.21中复制的图像 URI,然后保留其他设置不变,再次滚动到网络部分:

图 6.27 – Sagemaker 模型 – 网络部分

图 6.27 – Sagemaker 模型 – 网络部分

在这里,选择VPC默认 vpc,从列表中选择一个或两个子网,并选择默认的安全组。向下滚动到页面底部,然后点击创建模型

重要提示

在生产部署中,选择默认安全组从来不是一个好主意,因为入站规则不是限制性的。

现在模型已经准备好了,接下来让我们创建端点配置。

端点配置

要设置端点配置,请使用搜索栏导航到 SageMaker 控制台,并在 推理 部分查找 Endpoint Configurations。或者,访问 us-east-1.console.aws.amazon.com/sagemaker/home?region=us-east-1#/endpointConfig。将显示以下页面:

图 6.28 – Sagemaker 端点配置控制台

图 6.28 – Sagemaker 端点配置控制台

在显示的网页上,点击 创建端点配置。您将被导航到以下页面:

图 6.29 – SageMaker – 创建端点配置

图 6.29 – SageMaker – 创建端点配置

在此屏幕上,填写 customer-segmentation-config。滚动到 数据捕获 部分。这用于定义需要捕获多少百分比的实时推理数据,在哪里(S3 位置),以及如何存储(JSON 或 CSV)。您可以选择启用或禁用此功能。我在这个练习中将其禁用了。如果您启用它,它将要求您提供更多信息。数据捕获 之后的部分是 生产变体。这用于设置多个模型变体,以及模型的 A/B 测试。目前,因为我们只有一个变体,所以让我们在这里添加它。要添加一个变体,请在该部分点击 添加模型 链接;以下弹出窗口将出现:

图 6.30 – SageMaker – 将模型添加到端点配置

图 6.30 – SageMaker – 将模型添加到端点配置

在弹出窗口中,选择我们之前创建的模型,滚动到页面底部,然后点击 创建端点配置

SageMaker 端点创建

最后一步是使用端点配置创建端点。要创建 SageMaker 端点,请使用搜索栏导航到 SageMaker 控制台,并在 推理 部分查找 Endpoints。或者,访问 https://us-east-1.console.aws.amazon.com/sagemaker/home?region=us-east-1#/endpoints。将显示以下页面:

图 6.31 – SageMaker 端点控制台

图 6.31 – SageMaker 端点控制台

图 6.31 所示的页面上,点击 创建端点 以导航到以下页面:

图 6.31 – SageMaker – 创建端点

图 6.31 – SageMaker – 创建端点

图 6.31显示的网页上,提供一个端点名称。我已给出名称customer-segmentation-endpoint。向下滚动到端点配置部分,选择我们之前创建的端点配置,然后点击选择端点配置按钮。一旦选择,点击创建端点。创建端点需要几分钟。当端点状态变为可用时,您的模型即可用于实时流量服务。

测试 SageMaker 端点

我们接下来需要了解的是如何消费模型。有不同方式——您可以使用 SageMaker 库、Amazon SDK 客户端(Python、TypeScript 或其他可用的语言),或 SageMaker 端点 URL。所有这些方法默认使用 AWS IAM 身份验证。如果您有特殊要求并希望在不进行身份验证或使用自定义身份验证的情况下公开模型,可以使用 API 网关和 Lambda 授权器来实现。为了本练习的目的,我们将使用boto3客户端来调用 API。无论我们如何调用端点,结果都应该是相同的。

以下代码块使用boto3客户端调用端点:

import json
import boto3
import os
os.environ["AWS_ACCESS_KEY_ID"] = "<aws_key>"
os.environ["AWS_SECRET_ACCESS_KEY"] = "<aws_secret>"
os.environ["AWS_DEFAULT_REGION"] = "us-east-1"
payload = json.dumps({"customer_list":["12747.0", "12841.0"]})
runtime = boto3.client("runtime.sagemaker")
response = runtime.invoke_endpoint(
    EndpointName= "customer-segmentation-endpoint", 
    ContentType="application/json", Body=payload
)
response = response["Body"].read()
result = json.loads(response.decode("utf-8"))
print(results)

在前面的代码块中,我们正在调用我们创建的端点,为具有12747.012841.0 ID 的两个客户运行预测。端点将在毫秒内对给定的客户 ID 做出预测。现在,端点可以与模型消费者共享。

现在模型已投入生产,让我们看看模型转移到生产后的一些后续方面。

超越模型生产

在本节中,我们将讨论机器学习的生产后方面以及我们如何从采用特征存储中受益。

特征漂移监控和模型重新训练

模型投入生产后,接下来经常出现的问题是模型在生产中的表现如何。可能使用不同的指标来衡量模型的表现——例如,对于推荐模型,表现可能通过转化率来衡量,即推荐的产品被购买频率。同样,预测客户的下一步行动可能通过错误率来衡量,等等。没有通用的方法来做这件事。但如果模型的表现不佳,则需要重新训练或用新的模型替换。

定义何时应该重新训练模型的其他方面之一是当特征开始偏离其训练时的值。例如,假设在初始模型训练期间客户的平均频率值为 10,但现在平均频率值为 25。同样,最低货币值最初为 100.00 美元,现在为 500.00 美元。这被称为数据漂移

数据漂移监控衡量的是数据统计分布的变化;在特征监控的情况下,它是比较从时间t1到时间t2的特征统计分布的变化。以下 URL 的文章讨论了数据漂移监控的不同指标:towardsdatascience.com/automating-data-drift-thresholding-in-machine-learning-systems-524e6259f59f

使用特征存储,可以轻松地从两个不同时间点检索训练数据集,即用于模型训练的数据集和用于模型训练的所有特征的最新特征值。现在,我们只需要按计划运行数据漂移监控来生成漂移报告。Feast 带来的标准化是,由于数据是使用标准 API 存储和检索的,因此可以在特征存储中的所有数据集上按计划运行通用的特征漂移监控。特征漂移报告可以用作模型重新训练的指标之一。如果特征漂移影响了模型的性能,可以使用最新的数据集重新训练,并与当前的生产模型部署和 A/B 测试。

模型可复现性和预测问题

如果您还记得第一章中的概述,即“机器学习生命周期概述”,模型可复现性是机器学习的一个常见问题。我们需要一种方法来一致地复现模型(或用于模型的训练数据)。如果没有特征存储,如果用于生成特征的底层原始数据发生变化,就无法复现相同的训练数据集。然而,使用特征存储,正如我们之前讨论的,特征与时间戳版本化(特征 DataFrame 中的一列是事件时间戳)。因此,我们可以查询历史数据来生成用于模型训练的相同特征集。如果用于训练模型的算法不是随机的,模型也可以复现。让我们试试看。

由于我们已经在第五章的“使用特征存储进行模型训练”部分中做了类似的事情,即“模型训练和推理”,我们将重用相同的代码来运行这个实验。复制并运行所有代码,直到你创建实体 DataFrame,然后将event_timestamp列替换为较旧的时间戳(模型训练的时间戳),如下所示。在这种情况下,模型是在2022-03-26 16:24:21训练的,如第五章的“模型训练和推理”中的图 5.1所示:

## replace timestamp to older time stamp.
entity_df["event_timestamp"] = pd.to_datetime("2022-03-26 16:24:21")

一旦替换完时间戳,请继续从第五章的“Dee 的模型训练实验”部分运行代码,模型训练和推理。你应该能够生成与 Dee 模型训练中使用的相同的数据集(在这种情况下,第五章](B18024_05_ePub.xhtml#_idTextAnchor078)中的图 5.2所示的数据集)。因此,如果模型使用非随机算法,则也可以使用特征集重现模型。

特征存储的另一个优点是调试预测问题。让我们考虑一个场景,你有一个面向网站的模型,该模型正在将交易分类为欺诈或非欺诈。在高峰时段,它将几笔交易标记为欺诈,但这些交易实际上是合法的。客户打电话给客服部门投诉,现在轮到数据科学家 Subbu 来找出问题所在。如果项目中没有特征存储,为了重现问题,Subbu 必须进入原始数据,尝试生成特征,并查看行为是否仍然相同。如果不相同,Subbu 必须进入应用程序日志,进行处理,查找事件之前的行为,并从用户交互的角度尝试重现,同时捕获所有这些试验的特征,希望至少能重现一次问题。

另一方面,使用项目中使用的特征存储,Subbu 将找出事件发生的大概时间,模型中使用的实体和特征是什么,以及事件发生时生产中运行的模型版本。有了这些信息,Subbu 将连接到特征存储并获取在问题发生的大概时间范围内所有实体使用的所有特征。比如说,事件发生在今天中午 12:00 到 12:15 之间,特征是流式的,新鲜度间隔大约是 30 秒。这意味着,对于给定的实体,从任何给定时间开始,特征在接下来的 30 秒内可能会发生变化。

为了重现问题,Subbu 将创建一个实体 DataFrame,其中一个列中重复 30 次相同的实体 ID,对于事件时间列,从中午 12:00 到 12:15 的 30 秒间隔的时间戳。有了这个实体 DataFrame,Subbu 将使用 Feast API 查询历史存储并运行生成的特征的预测。如果问题重现,Subbu 就有导致问题的特征集。如果没有重现,使用实体 DataFrame,间隔将减少到小于 30 秒,可能到 10 秒,以确定特征是否比 30 秒更快地发生变化。Subbu 可以继续这样做,直到她找到重现问题的特征集。

为下一个模型赢得先机

现在,模型已经投入生产,数据科学家 Subbu 接手下一个问题陈述。假设下一个机器学习模型需要预测客户的下一次购买日NPD)。这里的用例可能是基于 NPD,我们想要为客户运行一次营销活动。如果客户的购买日较远,我们想要提供特别优惠,以便我们可以鼓励他们尽早购买。现在,在查看原始数据集之前,Subbu 可以根据搜索和可发现性方面如何集成到特征存储中查找可用的特征。由于 Feast 从面向服务的架构迁移到 SDK/CLI 导向,需要目录工具、所有特征存储库的 GitHub 仓库、数据网格门户等。然而,在 SageMaker 或 Databricks 等特征存储的情况下,用户可以通过 API 或从 UI 浏览可用的特征定义来连接到特征存储端点(使用 SageMaker 运行时通过 boto3 或 Databricks 工作区)。我之前没有使用过 Tecton 特征存储,但 Tecton 也为其特征存储提供了一个 UI,可以用来浏览可用的特征。正如你所见,这是 Feast 在 0.9.X 和 0.20.X(0.20 是撰写时的版本)不同版本之间的一大缺点。

假设现在 Subbu 有办法定位所有特征存储库。现在,她可以连接并浏览它们,以找出在 NPD 模型中可能有用的项目和特征定义。到目前为止,我们只有一个包含我们迄今为止一直在使用的客户 RFM 特征的存储库,这些特征在模型中可能很有用。要使用这些特征,Subbu 只需要获取 AWS 资源的读取权限,最新的 RFM 特征将每天可用于实验,如果模型转移到生产环境也可以使用。

为了看到特征存储在后续模型开发中的好处,我们应该尝试构建 NPD。我将通过最初的几个步骤来帮助你开始模型。因为我们遵循了一个博客来开发第一个模型,我们将继续遵循同一博客系列中的另一部分,该部分可以在towardsdatascience.com/predicting-next-purchase-day-15fae5548027找到。请阅读该博客,因为它讨论了方法以及作者为什么认为特定的特征将是有用的。在这里,我们将跳过特征工程部分。

我们将使用博客作者使用的功能集,包括以下内容:

  • RFM 特征和聚类

  • 最后三次购买之间的天数

  • 购买差异的平均值和标准差

第一个特征集已经存在于特征存储库中;我们不需要为此做任何额外的工作。但对于其他两个,我们需要从原始数据中进行特征工程。在github.com/PacktPublishing/Feature-Store-for-Machine-Learning/blob/main/Chapter06/notebooks/ch6_next_purchase_day_feature_engineering.ipynb的笔记本中有生成前述第二和第三要点中所需特征的必要特征工程。我将把这些特征的导入到特征存储库以及使用前一个模型中的特征(RFM)结合这些特征来训练新模型作为一个练习。随着你开发和生产化这个模型,你将看到特征存储库的好处以及它如何可以加速模型构建。

接下来,让我们讨论当模型处于生产状态时如何更改特征定义。

生产后的特征定义变更

到目前为止,我们已经讨论了在开发阶段对特征集的导入、查询以及对其变更。然而,我们还没有谈到当模型处于生产状态时对特征定义的变更。通常,人们认为一旦模型进入生产状态,更改特征定义是困难的。原因在于,可能存在多个模型正在使用特征定义,对它们的任何变更都可能会对模型产生级联效应。这也是为什么一些特征存储库尚未支持特征定义更新功能的原因之一。我们需要一种有效处理变更的方法。

这仍然是一个灰色地带;没有正确或错误的方法来做这件事。我们可以采用在其他软件工程过程中使用的任何机制。一个简单的例子可以是特征视图的版本控制,类似于我们处理 REST API 或 Python 库的方式。每当需要对生产特征集进行变更时,假设它被其他人使用,就会创建并使用一个新的特征视图版本(让我们称它为customer-segmentation-v2)。然而,在所有模型迁移之前,还需要管理之前的版本。如果由于任何原因,有模型需要旧版本且无法迁移到新版本的特征表/视图,可能需要对其进行管理或转交给需要它的团队。需要对特征和特征工程工作的所有权进行一些讨论。

这就是数据作为产品概念非常有意义的地方。这里缺失的部分是生产者和消费者定义合同和通知变更的框架。数据生产者需要一种发布他们的数据产品的方式;在这里,数据产品是特征视图。产品的消费者可以订阅数据产品并使用它。在特征集变更期间,生产者可以定义数据产品的新版本,并使旧版本过时,以便消费者能够得知变更内容。这只是我对解决方案的看法,但我相信世界上有更聪明的人可能已经在实施另一种解决方案。

有了这些,让我们总结本章所学的内容,并继续下一章。

摘要

在本章中,我们的目标是利用前几章所构建的一切,并将机器学习模型用于批量处理和在线用例的生产化。为此,我们创建了一个 Amazon MWAA 环境,并使用它来编排批量模型管道。对于在线模型,我们使用 Airflow 来编排特征工程管道和 SageMaker 推理组件,以部署一个作为 SageMaker 端点的 Docker 在线模型。我们探讨了特征存储如何促进机器学习的后期生产方面,例如特征漂移监控、模型可复现性、调试预测问题,以及当模型在生产中时如何更改特征集。我们还探讨了数据科学家如何通过使用特征存储来在新模型上取得领先。到目前为止,我们在所有练习中都使用了 Feast;在下一章中,我们将探讨市场上可用的几个特征存储,以及它们与 Feast 的不同之处,并附带一些示例。

第三部分 – 替代方案、最佳实践及一个用例

在本节中,我们将探讨一些 Feast 的替代方案,其中包括一些托管特征存储服务。我们还将深入探讨 Amazon SageMaker Feature Store,这将帮助我们比较 Feast(自托管基础设施)与托管特征存储。我们还将介绍机器学习最佳实践,最后,我们将通过一个包含特征工程、模型训练、模型推理以及特征和模型监控示例的端到端机器学习用例,展示如何使用托管特征存储。

本节包含以下章节:

  • 第七章, Feast 替代方案和机器学习最佳实践

  • 第八章, 客户流失预测用例

第八章:Feast 替代方案和机器学习最佳实践

在上一章中,我们讨论了如何使用 Amazon Managed Workflows 和 Apache Airflow 进行编排,以及如何使用 Feast 将在线和批量模型投入生产。到目前为止,在这本书中,我们一直在讨论一个特征存储库——Feast。然而,目前市场上有很多特征存储库。在本章中,我们将查看其中的一些,并讨论它们与 Feast 的不同之处,以及使用它们相对于 Feast 的优缺点。

在本章中,我们将尝试使用另一个特征存储库,具体是 Amazon SageMaker。我们将使用在构建客户终身价值(LTV)模型时生成的相同特征集,并将其导入 SageMaker 特征存储库,并运行几个查询。选择 AWS 而不是 Tecton、Hopworks 和 H2O.ai 等其他特征存储库的原因是易于访问试用版。然而,选择适合您的特征存储库取决于您已经拥有的工具和基础设施,以及更多内容,我们将在本章中讨论。

本章的目的是向您展示市场上可用的内容以及它与自行管理的特征存储库(如 Feast)的不同之处。我们还将讨论这些特征存储库之间的相似性和差异性。本章还想讨论的另一个方面是机器学习开发中的最佳实践。无论我们使用什么工具/软件进行机器学习开发,都有一些事情我们可以普遍采用来提高机器学习工程。

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

  • 市场上可用的特征存储库

  • 使用 SageMaker 特征存储库进行特征管理

  • 机器学习最佳实践

技术要求

为了运行示例并更好地理解本章内容,前几章中涵盖的主题将很有用,但不是必需的。要跟随本章中的代码示例,您需要熟悉笔记本环境,这可以是本地设置,如 Jupyter,或在线笔记本环境,如 Google Colab、Kaggle 或 SageMaker。您还需要一个具有对 SageMaker 和 AWS Glue 控制台完全访问权限的 AWS 账户。您可以在试用期间创建新账户并免费使用所有服务。您可以使用以下 GitHub 链接找到本书的代码示例:

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

市场上可用的特征存储库

在本节中,我们将简要讨论市场上可用的特征存储库,以及它们与 Feast 的比较,以及这些特征存储库之间的相似之处和不同之处。

Tecton 特征存储库

Tecton 是由 Uber 机器学习平台 Michelangelo 的创建者构建的企业级特征存储https://eng.uber.com/michelangelo-machine-learning-platform/)。Tecton 也是 Feast 的主要贡献者之一。因此,当你查看 Tecton 的文档(docs.tecton.ai/index.html)时,你会在 API 和术语中看到很多相似之处。然而,Tecton 中有许多功能在 Feast 中不存在。此外,Tecton 是一个托管特征存储,这意味着你不需要构建和管理基础设施;它将为你管理。

与大多数特征存储一样,Tecton 使用在线和离线存储分别用于低延迟和历史存储。然而,与 Feast 相比,在线和离线存储的选项较少,并且目前仅支持 AWS。如果你更喜欢 Azure 或 GCP,你现在没有其他选择,只能等待。我相信最终将支持多个云提供商和数据存储。Tecton 使用软件即服务SaaS)部署模型,并将部署分为数据平面和控制平面。你可以在以下链接中找到他们的部署模型:docs.tecton.ai/setting-up-tecton/07a-deployment_saas.html。最好的部分是数据永远不会离开客户的 AWS 账户,只有控制面板运行所需的元数据由 Tecton 拥有的 AWS 账户访问;此外,UI 将托管在他们账户中。然而,如果你想通过 REST/gRPC API 端点公开在线数据,该服务将托管在 Tecton 的 AWS 账户中。在线特征请求和响应将通过他们的账户路由。

一旦 Tecton 部署到你的 AWS 账户,你可以使用 Python SDK 与之交互。CLI 命令与 Feast 命令类似;然而,有一些选项,例如可以管理特征定义的版本,以及降级到定义的先前版本。以及你可以使用特征存储执行的常见工作流程,如摄取、低延迟查询和执行点时间连接,使用 Tecton,你可以将转换定义为特征存储的一部分。这是 Tecton 我最喜欢的功能之一。以下是特征存储中特征视图和转换页面的链接:docs.tecton.ai/overviews/framework/feature_views/feature_views.html。这意味着你可以为数据仓库(Snowflake)、数据库、Kinesis 或 Kafka 定义原始数据源配置,并定义 PySpark、Spark SQL 或 pandas 转换以生成特征。Tecton 在定义的日程上编排这些作业,生成特征,并将它们摄取到在线和离线存储中。这有助于跟踪数据血缘。

以下是一个示例代码片段,说明如何定义特征视图和转换:

# Feature View type
@batch_feature_view(
    # Pipeline attributes
    inputs=...
    mode=...
    # Entities
    entities=...
    # Materialization and serving configuration
    online=...
    offline=...
    batch_schedule=...
    feature_start_time=...
    ttl=...
    backfill_config=...
    # Metadata
    owner=...
    description=...
    tags=...
)
# Feature View name
def my_feature_view(input_data):
    intermediate_data = my_transformation(input_data)
    output_data = my_transformation_two(intermediate_data)
    return output_data

你可能会认出在前一个代码块中看到的一些参数。在这里,注释说明这是一个批量转换,你可以定义诸如使用哪些实体、什么时间表以及是否应将数据摄入在线和离线存储等参数。在方法定义中,输入数据将根据注释定义中分配给input参数的内容注入(你可以假设它来自原始数据源的 DataFrame)。在 DataFrame 上,你添加你的转换并返回输出 DataFrame,这将作为特征。这些特征将按照定义的时间表摄入在线和离线存储。一旦定义了前面的转换,你必须运行tecton apply,这与feast apply命令类似,以注册此转换。其他功能与其他特征存储提供的功能类似;因此,我将跳过它们,并让你探索它们的文档。

值得注意的是,在撰写本文时,Tecton 的部署是单租户的,这意味着如果存在无法共享数据的团队,你可能需要多个部署。需要创建一组角色,以便 Tecton 可以使用跨账户角色安装和创建所需资源,这涉及到您的一次性初始设置。

Databricks 特征存储

Databricks 特征存储是用户可用的另一个选项。如果你已经将 Databricks 用作你的笔记本环境以及数据处理作业,这很有意义。它包含 Databricks 工作区,所以你不能只有特征存储。然而,你可以获得一个工作区,除了特征存储之外不使用任何其他东西。它可以托管在 AWS、GCP 或 Azure 上。因此,如果你在任何一个主要云服务提供商上,这可以是一个选择。

这些概念与其他特征存储类似,例如特征表、行的时间戳版本、进行点时间连接的能力以及在线和离线存储。它使用 delta lake 作为其离线存储,并使用基于您所在云提供商的关键值存储之一。Databricks 特征存储的最好之处在于它与 Databricks 的所有其他方面和组件集成良好,例如 Spark DataFrame 的摄取、检索、与 MLflow 模型存储库的即插即用集成、访问控制和跟踪用于生成特定特征表的笔记本的谱系。它还有一个友好的用户界面,您可以浏览和搜索特征。最好的下一部分是,如果您已经拥有 Databricks 工作区,则无需设置。以下是笔记本的链接,其中包含特征创建、摄取、检索、训练和模型评分的示例:docs.databricks.com/_static/notebooks/machine-learning/feature-store-taxi-example.html

然而,有一些事情需要记住。Databricks 特征存储没有项目概念;因此,特征表是最高级别的抽象,访问控制是在特征表级别。此外,Databricks 的在线模型托管仍在公共预览中(尽管毫无疑问它最终将成为一项标准服务)。这意味着如果您使用 Databricks 特征存储来托管在 Databricks 之外的在线模型,它可能需要通过直接客户端连接到在线商店。例如,如果您使用 DynamoDB 作为在线商店(Databricks 根据云提供商提供多种选择)并在 Amazon boto3客户端中托管预测期间的特征模型。此外,跨工作区共享特征可能需要额外的配置,无论是访问令牌还是使用中央工作区作为特征存储。以下是 Databricks 特征存储文档的链接,其中包含更多详细信息:docs.databricks.com/applications/machine-learning/feature-store/index.html

Google 的 Vertex AI 特征存储

Google 的 Vertex AI 是 Google 为机器学习和人工智能提供的平台即服务PaaS)产品。Vertex AI 旨在提供一个端到端的机器学习平台,提供一系列用于机器学习开发、训练、编排、模型部署、监控等工具。我们最感兴趣的工具是 Vertex AI 特征存储。如果您已经使用 GCP 来提供服务,它应该是一个自动的选择。

概念和术语与 Feast 非常相似。Vertex AI 中的最高抽象级别称为 特征存储,类似于 Feast 中的 项目,一个 特征存储 可以有 实体,而 特征 应属于 实体。它支持在线和批量服务,就像所有其他特征存储一样。然而,与 Feast 和 Tecton 不同,没有可用的在线和历史存储选项。由于它是一个托管基础设施,用户无需担心安装和选择在线和离线存储——可能只是价格问题。以下是其价格的链接:cloud.google.com/vertex-ai/pricing#featurestore。它使用 IAM(代表 身份和访问管理)进行身份验证和授权,并且您还可以获得一个用于搜索和浏览特征的 UI。

Vertex AI 的最佳部分是其与其他 GCP 组件以及 Vertex AI 服务本身的集成,用于特征生成、管道管理和数据血缘跟踪。我最喜欢的功能之一是漂移监控。您可以在特征表上设置特征监控配置,这样它就可以为您生成数据分布报告,而无需进行任何额外的工作。

再次提醒,有几件事情需要记住。对于在线服务,你需要进行容量规划并设置处理您流量的所需节点数量。在线服务的自动扩展选项目前仍处于公开预览阶段(尽管它很快就会成为标准服务),但容量规划应该是一个需要解决的主要问题。一些负载测试模拟可以帮助你轻松解决这个问题。此外,对于特征存储,您拥有的在线服务节点数量、数据保留长度以及每个实体的特征数量都有配额和限制。其中一些可以在请求后增加,而其他则不行。以下是特征存储配额和限制的链接:cloud.google.com/vertex-ai/docs/quotas#featurestore

Hopsworks 特征存储

Hopsworks 是一个在 AGPL-V3 许可下运行的另一个开源特征存储,可以在本地、AWS 或 Azure 上运行。它还提供了一个支持 GCP 以及任何 Kubernetes 环境的企业版特征存储。与其他机器学习平台服务类似,它也提供多个组件,例如模型管理和计算环境管理。

这些概念与其他特征存储类似;然而,术语不同。它没有实体的概念,Hopsworks中的featuregroupsFeast中的featureviews类似。就像其他特征存储一样,Hopsworks 支持在线和离线服务。它使用 Apache Hive 与 Apache Hudi 作为离线存储,MySQL Cluster 作为在线存储。再次强调,没有在线或离线存储的选项。然而,Hopsworks 开发了不同的存储连接器,可以用来创建按需的外部特征组,例如我们在Feast中定义的RedShiftSource,见第四章将特征存储添加到机器学习模型中。但是,外部特征组有一些限制,意味着没有时间旅行、在线服务等。

Hopsworks 特征存储中有许多有趣且功能强大的特性。以下是一些最好的特性:

  • 项目级多租户:每个项目都有一个所有者,并且可以与其他团队成员以及跨团队共享资源。

  • 特征组版本控制:Hopsworks 支持特征组版本控制,这是市场上任何其他特征存储所不支持的功能。

  • 特征组的统计信息:它为特征组提供了一些开箱即用的统计信息,例如特征相关性计算、特征的频率直方图和唯一性。以下是一个示例特征组:

    store_fg_meta = fs.create_feature_group(
        name="store_fg",
        version=1,
        primary_key=["store"],
        description="Store related features",
        statistics_config={"enabled": True, 
                             "histograms": True, 
                             "correlations": True})
    
  • 特征验证:这是另一个开箱即用的有趣特性。这是一组预定义的验证规则,存在于特征组中,例如特征的最低和最高值、特征的唯一性计数、特征的熵以及特征的长度最大值。它有足够的规则类型,您不会遇到需要自定义验证规则的场景。以下是一些示例规则:

    #the minimum value of the feature needs to be between 0 and 10
    rules=[Rule(name="HAS_MIN", level="WARNING", 
                 min=0, max=10)] 
    #Exactly 10% of all instances of the feature need to be contained in the legal_values list
    rules=[Rule(name="IS_CONTAINED_IN", level="ERROR", 
                 legal_values=["a", "b"], min=0.1, 
                 max=0.1)] 
    
  • 转换函数:与 Tecton 的转换函数类似,在 Hopsworks 中,您可以在训练数据集上定义或使用内置的转换(Hopsworks 有一个训练数据的概念,您可以从不同的特征组中选择特征,并在其上创建训练数据集定义——一个类似于数据库视图的概念)。

然而,有一些事情需要记住。如果您选择开源版本,您可能不会拥有所有这些特性,并且基础设施将需要自行管理。相反,对于企业版本,您将需要与 Hopsworks 工程师合作,创建在云服务提供商上安装 Hopsworks 所需的一些资源和角色。以下是所有文档的链接:https://docs.hopsworks.ai/feature-store-api/2.5.8/。即使您不使用这些特性,我也建议您查看一下;这可能会给您一些关于您可能想要构建或拥有的特征存储中的特性的想法。

SageMaker 特征存储

SageMaker 是 AWS 提供的一个端到端机器学习平台。就像 Vertex AI 一样,它有一个笔记本环境,AutoML,处理作业和模型管理,特征存储等。如果你是一个专注于 AWS 的公司,这必须是自然的选择,而不是其他选择。

这些概念与其他特征存储的概念相近,尽管一些术语不同。例如,SageMaker Feature Store 也没有实体的概念,Feast 中的featureviews与 SageMaker 中的featuregroups类似。它具有所有基本功能,如在线和离线存储以及服务。然而,你没有选择。它使用 S3 作为离线存储,其中一个键值存储作为在线存储(AWS 在其文档中没有说明在线存储用于什么)。AWS 使用 IAM 进行身份验证和授权。要访问特征存储库,目前需要完全访问 SageMaker 和 AWS Glue 控制台。如果你将 SageMaker 与 Feast 进行比较,两者都使用/支持 S3 作为离线存储,键值存储作为在线存储,以及 Glue 目录来管理模式。除了 SageMaker 是一个托管特征存储之外,另一个区别是 Feast 使用 Redshift 查询离线数据,而 SageMaker 使用 Amazon Athena(无服务器)查询。如果你是服务器无服务技术的粉丝,你可以将此功能添加到 Feast 中。

我最喜欢的 SageMaker Feature Store 的特点之一是无需管理基础设施。除了创建一个 IAM 角色以访问特征存储库之外,你不需要管理任何东西。任何给定负载的所有资源都由 AWS 管理。你只需要关注开发和摄取特征。SageMaker Feature Store 还支持在 EMR 或 Glue 作业(无服务器)上使用 Spark 进行摄取。除了特征外,它还添加了元数据,如write_timeapi_invocation_time,这些可以在查询中使用。最好的部分是你可以使用 Amazon Athena SQL 查询查询离线数据。

虽然有一些需要注意的事项。当前的实现还没有细粒度的访问管理。目前,你需要完全访问 SageMaker 才能使用特征存储,尽管我相信这只是 AWS 开始提供细粒度访问的时间问题。点时间连接不是现成的;然而,这些可以通过 SQL 查询或 Spark 实现。

到目前为止,我们已经查看了一些市场上的可用选项;你可以通过此链接找到其他可用的特征存储:www.featurestore.org/。然而,为你的项目或团队选择正确的特征存储可能很棘手。在挑选特征存储时,以下是一些需要注意的事项:

  • 你的主要云提供商有很大影响。如果你专注于 GCP,使用 SageMaker Feature Store 就没有意义,反之亦然。如果你是多云,那么你将有更多选择。

  • 数据处理框架也是决定使用哪个特征存储的另一个关键因素。例如,如果你使用 SageMaker 作为你的机器学习平台,在其他人之前尝试 SageMaker Feature Store 更有意义。

  • 与你生态系统中的其他组件的集成也很关键——例如,回答诸如它与你的处理平台、编排框架、模型管理服务、数据验证框架以及你的机器学习开发过程如何良好集成等问题,真的有助于选择正确的特征存储。

  • 所需的功能和你的团队结构有很大影响。如果你是一个只想专注于机器学习的中小团队,那么托管特征存储的提供方案是有意义的,而如果你有一个平台团队来管理基础设施,你可能需要考虑开源提供方案,并评估构建与购买选项。如果你有平台团队,他们可能会寻找额外的功能,如多租户、细粒度访问控制和 SaaS/PaaS。

总结来说,除了它提供的功能外,许多因素都会影响特征存储的选择,因为它必须与更广泛的生态系统良好集成。

接下来,让我们看看一个托管特征存储是如何工作的。

使用 SageMaker Feature Store 进行特征管理

在本节中,我们将探讨如果我们使用托管特征存储而不是 Feast 在第四章,“将特征存储添加到机器学习模型”中,我们可能需要采取哪些行动。

重要提示

所有托管特征存储都有一个类似的流程;有些可能是基于 API 的,有些则通过 CLI 工作。但无论哪种方式,使用特征存储所需的工作量将与我们在本节中讨论的内容相似。我之所以选择 SageMaker,是因为熟悉它并且易于访问,利用 AWS 中的免费试用作为特色产品。

使用 SageMaker 的资源

第四章“将特征存储添加到机器学习模型”中,在我们开始使用特征存储之前,我们在 AWS 上创建了一系列资源,例如一个 S3 存储桶、一个 Redshift 集群、一个 IAM 角色和一个 Glue 目录表。相反,对于像 SageMaker 这样的托管特征存储,你所需要的只是一个具有完全访问 SageMaker 的 IAM 角色,你就准备好了。现在让我们试试看。

我们需要一些 IAM 用户凭证和一个 SageMaker Feature Store 可以承担的 IAM 角色。创建 IAM 用户与之前我们所做的是类似的。遵循相同的步骤创建一个 IAM 用户,并分配AmazonS3FullAccessAmazonSageMakerFullAccess权限。IAM 角色的创建与之前我们所做的是一样的;然而,我们需要允许 SageMaker 服务承担该角色。

重要提示

如前所述多次,赋予完全访问权限从来不是一个好主意;权限应该始终基于资源进行限制。

让我们创建一个 IAM 角色:

  1. 登录您的 AWS 账户,并使用搜索栏导航到 IAM 角色页面;或者,访问以下 URL:https://us-east-1.console.aws.amazon.com/iamv2/home#/roles。将显示以下页面:

图 7.1 – IAM 角色主页

图 7.1 – IAM 角色主页

  1. 在显示的网页上,点击创建角色以导航到以下屏幕:

图 7.2 – IAM 角色创建页面

图 7.2 – IAM 角色创建页面

  1. 图 7.2显示的屏幕上,在其他 AWS 服务的用例下拉菜单中,选择SageMaker,然后点击SageMaker - 执行单选按钮。向下滚动并点击下一步,在添加权限页面保持默认设置,然后点击下一步。接下来将显示以下页面:

图 7.3 – 名称、审查和创建页面

图 7.3 – 名称、审查和创建页面

  1. 在显示的网页上,填写sagemaker-iam-role。滚动到页面底部并点击arn:aws:iam::<account_number>:role/sagemaker-iam-role

这就是我们访问 SageMaker Feature Store 所需的所有内容。接下来,让我们创建特征定义。

生成特征

要定义特征组,因为我们正在尝试比较它与 Feast 的不同之处,我们将使用相同的特征集。您可以从 S3 存储桶下载之前导入的特征,或者从 GitHub 链接下载:github.com/PacktPublishing/Feature-Store-for-Machine-Learning/blob/main/Chapter07/rfm_features.parquet。下载 Parquet 文件后,将其复制到可以从笔记本访问的位置。下一步是创建一个新的笔记本,我将其命名为ch7-sagemaker-feature-store.ipynb

  1. 让我们先安装所需的库:

    !pip install sagemaker pandas
    
  2. 安装库后,让我们生成特征。在这里,我们将从位置读取复制的文件并对数据集进行一些小的修改:

    import pandas as pd
    import time
    df = pd.read_parquet(path="/content/rfm_features.parquet")
    df = df.drop(columns=["created_timestamp"])
    df["event_timestamp"] = float(round(time.time()))
    df["customerid"] = df['customerid'].astype(float)
    df.head()
    

上述代码块读取文件并删除了created_timestamp列,因为它对于 SageMaker 不是必需的。我们还将event_timestamp列更新为最新时间,并将其类型更改为float而不是datetime。这样做的原因是,SageMaker 在编写时仅支持intfloatstring特征,而datetime文件可以是floatstring对象,格式为datetime ISO 格式。

代码块生成了以下输出:

图 7.4 – 近期、频率和货币价值(RFM)特征

图 7.4 – 近期、频率和货币价值(RFM)特征

现在我们有了 RFM 特征,下一步是定义特征组。如果你正确地回忆起 第四章将特征存储添加到机器学习模型中,在生成特征后,我们创建了特征定义并将它们应用到特征存储中。

定义特征组

要定义特征组,因为它是一项一次性活动,应该在一个单独的笔记本中完成,而不是通过特征工程。对于这个练习,让我们继续在同一个笔记本中定义特征组:

  1. 下面的代码块定义了一些导入并创建 SageMaker 会话:

    import sagemaker
    import sys
    import boto3
    from sagemaker.session import Session
    from sagemaker import get_execution_role
    import os
    os.environ["AWS_ACCESS_KEY_ID"] = "<aws_key_id>"
    os.environ["AWS_SECRET_ACCESS_KEY"] ="<aws_secret_id>"
    os.environ["AWS_DEFAULT_REGION"] = "us-east-1"
    prefix = 'sagemaker-featurestore-introduction'
    role = "arn:aws:iam::<account_number>:role/sagemaker-iam-role"
    sagemaker_session = sagemaker.Session()
    region = sagemaker_session.boto_region_name
    s3_bucket_name = "feast-demo-mar-2022"
    

在代码块中,将 <aws_key_id><aws_secret_id> 替换为之前创建的 IAM 用户的密钥和密钥。同时,使用你的 IAM 角色 ARN 分配 role

  1. 下面的代码块创建特征组对象并从输入 DataFrame 中加载特征定义:

    from sagemaker.feature_store.feature_group import \
      FeatureGroup
    customers_feature_group = FeatureGroup(
        name="customer-rfm-features", 
        sagemaker_session=sagemaker_session
    )
    customers_feature_group.load_feature_definitions(df)
    

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

![图 7.5 – 加载特征定义的调用]

图 7.5 – 加载特征定义的调用

图 7.5 – 加载特征定义的调用

正如你在 图 7.5 中看到的那样,load_feature_definitions 调用读取输入 DataFrame 并自动加载特征定义。

  1. 下一步是创建特征组。下面的代码块在 SageMaker 中创建特征组:

    customers_feature_group.create(
        s3_uri=f"s3://{s3_bucket_name}/{prefix}",
        record_identifier_name="customerid",
        event_time_feature_name="event_timestamp",
        role_arn=role,
        enable_online_store=True
    )
    

前面的代码块通过传递以下参数调用 create API:

  • s3_uri:特征数据将被存储的位置

  • record_identifier_nameid 列的名称(与 Feast 中的实体列相同)

  • event_time_feature_name:将用于时间旅行的时戳列

  • role_arn:SageMaker Feature Store 可以承担的角色

  • enable_online_store:是否为此特征组启用在线服务

代码块在成功创建特征组时产生以下输出:

图 7.6 – 特征组创建

图 7.6 – 特征组创建

图 7.6 – 特征组创建

那就结束了 – 我们的特征组已经准备好使用。接下来让我们导入特征。

特征导入

在 SageMaker Feature Store 中进行特征导入很简单。它是一个简单的 API 调用,如下面的代码块所示:

ingestion_manager = customers_feature_group.ingest(df))
ingestion_manager.wait()
ingestion_manager.failed_rows

前面的代码块将导入特征,如果有任何失败的行数,将打印出来。

在这里需要记住的一件事是,就像 Feast 一样,你不需要做任何额外的事情来将最新功能从离线商店转换为在线商店。如果启用了在线商店,数据将被导入到在线和离线商店,最新数据将立即可在在线商店中查询。

接下来让我们查询在线商店。

从在线商店获取记录

与 Feast 一样,从在线商店查询很简单。你所需要的是记录 ID 和特征组名称。下面的代码块从在线商店获取记录:

customer_id = 12747.0
sg_runtime_client = sagemaker_session.boto_session.client(
    'sagemaker-featurestore-runtime', 
    region_name=region)
record = sg_runtime_client.get_record(
    FeatureGroupName="customer-rfm-features", 
    RecordIdentifierValueAsString=str(customer_id))
print(record)

上一段代码块从在线商店获取了具有12747.0 ID 的客户的全部特征。查询应在毫秒内返回结果。输出将类似于以下代码块:

{'ResponseMetadata': {'RequestId': '55342bbc-c69b-49ca-bbd8-xxxx', 'HTTPStatusCode': 200, 'HTTPHeaders': {'x-amzn-requestid': '55342bbc-c69b-49ca-bbd8-xxx, 'content-type': 'application/json', 'content-length': '729', 'date': 'Mon, 02 May 2022 01:36:27 GMT'}, 'RetryAttempts': 0}, 
'Record': [{'FeatureName': 'customerid', 'ValueAsString': '12747.0'}, {'FeatureName': 'recency', 'ValueAsString': '7'}, {'FeatureName': 'frequency', 'ValueAsString': '35'}, {'FeatureName': 'monetaryvalue', 'ValueAsString': '1082.09'}, {'FeatureName': 'r', 'ValueAsString': '3'}, {'FeatureName': 'f', 'ValueAsString': '2'}, {'FeatureName': 'm', 'ValueAsString': '3'}, {'FeatureName': 'rfmscore', 'ValueAsString': '8'}, {'FeatureName': 'revenue6m', 'ValueAsString': '1666.1100000000001'}, {'FeatureName': 'ltvcluster', 'ValueAsString': '1'}, {'FeatureName': 'segmenthighvalue', 'ValueAsString': '1'}, {'FeatureName': 'segmentlowValue', 'ValueAsString': '0'}, {'FeatureName': 'segmentmidvalue', 'ValueAsString': '0'}, {'FeatureName': 'event_timestamp', 'ValueAsString': '1651455004.0'}]}

如您所见,输出包含所有特征及其对应值。

现在我们已经了解了如何查询在线商店,接下来让我们看看如何生成训练数据集和查询历史数据。

使用 Amazon Athena 查询历史数据

如前所述,SageMaker Feature Store 提供了使用 Amazon Athena 在历史存储上运行 SQL 查询的能力。

以下代码块生成了所有客户及其特征的最新快照:

get_latest_snapshot_query = customers_feature_group.athena_query()
query = f"""SELECT *
FROM
    (SELECT *,
         row_number()
        OVER (PARTITION BY customerid
    ORDER BY  event_timestamp desc, Api_Invocation_Time DESC, write_time DESC) AS row_num
    FROM "{get_latest_snapshot_query.table_name}")
WHERE row_num = 1 and 
NOT is_deleted;"""
get_latest_snapshot_query.run(query_string=query, output_location=f"s3://{s3_bucket_name}/output")
get_latest_snapshot_query.get_query_execution()

该代码块使用嵌套 SQL 查询,其中内部查询从event_timeApi_Invocation_Timewrite_time列按降序获取所有客户及其特征。外部查询从内部查询的结果中选择每个客户的第一个出现。查询成功执行后,代码块输出查询结果的存储位置以及附加详情。

结果可以像以下代码块所示那样加载为 DataFrame:

latest_df = get_latest_snapshot_query.as_dataframe()
latest_df.head()

上一段代码块输出了以下内容:

![图 7.7 – Athena 查询结果

![img/B18024_07_007.jpg]

图 7.7 – Athena 查询结果

随意尝试在特征存储上运行其他 Athena 查询。以下是 Amazon Athena 查询的文档:docs.aws.amazon.com/athena/latest/ug/what-is.html

清理 SageMaker 特征组

在我们继续前进之前,让我们清理 SageMaker 资源以节省成本。清理很简单;它只是另一个 API 调用以删除特征组。以下代码块执行此操作:

customers_feature_group.delete()

就这些了。在成功执行后,它将删除特征组,但会留下 S3 和 Glue 目录中的数据,如果需要,仍然可以使用 Amazon Athena(使用boto3客户端)进行查询。为了确保一切都被清理干净,请在同一笔记本中运行以下代码块。它应该返回一个空的特征组列表:

sagemaker_client = sagemaker_session.boto_session.client(
    "sagemaker", region_name=region
) 
sagemaker_client.list_feature_groups()

现在我们已经了解了 SageMaker 特征组,接下来让我们看看机器学习的最佳实践。

机器学习最佳实践

到目前为止,本书中我们讨论了特征存储,如何使用它们进行机器学习开发和生产,以及选择特征存储时可供选择的各种选项。尽管特征存储是机器学习的主要组件/方面之一,但本书中我们并未过多关注机器学习的其他方面。在本节中,让我们简要地讨论一下机器学习的其他方面和最佳实践。

源数据验证

不论我们用于构建机器学习模型的科技、算法和基础设施如何,如果数据中存在错误和异常,模型的表现将受到严重影响。数据应该被视为任何机器学习系统中的第一公民。因此,在数据进入机器学习流程之前检测错误和异常非常重要。

要对原始数据源进行验证,我们需要一个组件来创建和编排针对数据的验证规则。数据的使用者应该能够在 SQL 查询、Python 脚本或 Spark SQL 中编写任何自定义规则。任何规则失败都应通知数据消费者,他们反过来应该能够决定是否停止管道执行、重新训练模型或采取不采取行动。

一些常见的规则包括按计划对数据集进行描述性分析,这可以提供数据漂移的见解。更高级的统计,如库尔巴克-莱布勒KL)散度和人口稳定性指数PSI),也是很好的。拥有简单的数据验证规则,如数据新鲜度、唯一值、字符串字段长度、模式和值范围阈值,可以非常有帮助。模式验证是数据验证的另一个重要方面。任何验证的变化都可能影响所有消费者和流程。我们在源头拥有的数据验证越好,我们的模型和流程就越健康、性能越好。

分解机器学习流程和编排

一种不良的做法是将所有内容都在单个笔记本中开发,从数据验证和特征工程到模型预测。这不是一个可扩展或可重用的方法。大部分时间都花在清理不必要的代码和将模型投入生产上。因此,将机器学习流程分解成多个更小的步骤是一个好主意,例如数据验证、清理、转换、特征工程、模型训练和模型预测。转换步骤越小,代码的可读性、可重用性和调试错误就越容易。这也是为什么 Tecton 中的特征视图和转换,以及 Hopsworks 中的存储连接器和转换函数是优秀功能的原因之一。许多提取、转换和加载ETL)框架也提供了类似的功能。

除了分解机器学习流程外,编排也是机器学习平台的重要部分。每个云提供商都有自己的编排工具,同时也有很多开源的提供。开发无需太多工作即可编排的流程步骤是关键。如今,有很多编排工具,只要步骤小且有意义,就应能够与任何现有框架轻松编排。

跟踪数据血缘和版本控制

如果您还记得第六章模型到生产及之后,我们讨论了预测问题的调试。在那个例子中,我们讨论了生成导致预测异常的相同特征集;然而,很多时候仅仅找出系统出了什么问题以及是否由代码或数据集引起是不够的。因此,能够追踪该特征集的数据来源直到数据源,对于调试问题非常有帮助。

对于管道的每一次运行,保存每个步骤的输入和输出以及时间戳版本是关键。有了这个,我们可以追踪预测中的异常直到其源头,即数据。例如,除了拥有生成网站客户不良推荐的特性外,更好的是能够追踪这些特性直到它们在事件发生时的交互以及在此事件发生时在机器学习管道中生成的不同转换。

以下是一些有助于更好地跟踪血缘信息的管道信息:

  • 在步骤中使用到的所有库的版本

  • 在管道中运行的代码版本,包括管道本身的版本

  • 管道每个步骤产生的输入参数和工件,例如原始数据、数据集和模型

接下来,让我们看看特征库。

特征库

拥有一个特征库对于机器学习开发非常有好处。尽管在更新特征表模式方面存在一些灰色区域,但特征存储的好处,如可重用性、可浏览的特征、在线服务的准备就绪、时间旅行和点时间连接,在模型开发中非常有用。正如我们在上一章中观察到的,在客户终身价值模型开发期间开发的特征在下一购买日模型中很有用。同样,随着特征库规模的扩大,越来越多的特征可供使用,数据科学家和工程师的工作重复性减少,从而加速了模型的开发。

以下截图展示了开发机器学习模型的成本与特征存储中精心策划的特征数量之间的关系:

![图 7.8 – 模型的平均成本与特征存储中精心策划的特征数量之间的关系

![img/B18024_07_008.jpg]

图 7.8 – 模型的平均成本与特征存储中精心策划的特征数量之间的关系

图 7.8所示,随着特征仓库的增长,开发和生产模型的成本会降低。按照重用和添加新特征(如果不可用)的原则,特征仓库中所有可用的特征要么是生产就绪的,要么正在为生产模型提供服务。我们只需为每个新模型添加增量特征。这意味着在基础设施上增加的唯一成本就是运行这些额外的特征工程转换和新特征表,其余的假设在如果我们使用托管特征存储的情况下会自动扩展以适应生产负载。因此,新模型开发和生产的成本应该随着时间的推移而降低,并在特征仓库饱和后趋于平稳。

实验跟踪、模型版本控制和模型仓库。

实验跟踪和模型仓库是机器学习开发的其他重要方面。在开发模型时,我们会运行不同的实验——可能是不同的算法,不同的实现,如 TensorFlow 与 PyTorch,超参数调整,为模型选择不同的特征集,不同的训练数据集,以及训练数据集上的不同转换。跟踪这些实验并不容易,因为其中一些实验可能持续数天或数周。因此,使用与许多笔记本环境一起提供的实验跟踪软件非常重要。

每次运行都应该记录以下内容:

  • 模型训练笔记本或脚本的版本。

  • 一些关于数据集的参数,可用于重现相同的训练和评估数据集。如果你使用特征存储,那么可能是时间戳和实体;如果没有,你也可以将训练数据集保存到文件中并记录数据集的位置。

  • 训练算法中使用的所有参数。

  • 每次运行的性能指标。

  • 对结果的任何可视化也可能非常有用。

每次运行的记录指标可用于比较不同运行中模型的性能指标。这些指标在决定哪个模型的运行性能更好并应转移到新阶段,如部署阶段和 A/B 测试阶段时至关重要。此外,每次运行也有助于你浏览实验的历史记录,如果你或团队中的其他人需要回顾并重现某些特定的运行。

同样,模型仓库可以帮助跟踪所有不同版本的模型。模型注册/仓库存储了加载和运行模型所需的信息——例如,MLflow 模型仓库存储有关 conda 环境、模型的pickle文件以及模型的其他任何附加依赖项的信息。如果您有一个模型的中央仓库,这对于消费者浏览和搜索以及模型的生命周期管理(如将模型移动到不同的阶段——开发、预发布、生产以及存档)非常有用。模型仓库还可以用于扫描代码中的任何漏洞以及模型中使用的任何包。因此,模型仓库在机器学习开发中发挥着关键作用。

特征和模型监控

正如我们在上一章中讨论的,特征监控是另一个重要的方面。特征仓库的一个重要对应物是对变化和异常的监控。特征监控规则将与数据监控的规则相似。一些有用的特征规则包括特征新鲜度、最小和最大规则、监控异常值、最新特征的描述性统计以及如 KL 散度和 PSI 等指标。Hopsworks 的监控规则应该是您可能拥有的特征规则列表的一个良好起点。以下是文档链接:docs.hopsworks.ai/feature-store-api/2.5.8/generated/feature_validation/

模型监控是另一个重要的方面。在将模型部署到生产环境中后,其性能往往会随着时间的推移而下降。这是因为用户行为发生变化;因此,数据特征也会随之变化。跟踪模型在生产中的表现非常重要。这些性能报告应该按计划生成,如果不是实时生成,还必须采取适当的行动,例如使用新数据重新训练模型或启动全新的迭代。

其他事项

在机器学习开发过程中需要注意的其他事项包括跟踪运行时环境、库升级和降级。最好是主动采取行动。例如,如果您使用与特定环境严格绑定的工具,如 Python 或 Spark 版本,一旦特定的运行时被弃用并从生产支持中移除,作业可能会开始失败,生产系统可能会受到影响。另一个例子是 Databricks 的运行时与特定的 Python 和 Spark 版本绑定。如果您在已弃用的版本上运行作业,一旦它停止支持,如果新版本中有破坏性更改,作业可能会开始失败。因此,最好是主动升级。

在此基础上,让我们总结一下本章所学的内容,然后再查看下一章中的端到端用例。

摘要

在本章中,我们审视了市场上一些可用的特征存储。我们讨论了其中的五个,分别是 Tecton、Databricks、Vertex AI、Hopsworks 和 SageMaker Feature Store。我们还深入探讨了 SageMaker Feature Store,以了解使用托管特征存储而非 Feast 的感觉,以及它在资源创建、特征摄取和查询方面的差异。在最后一节,我们简要讨论了机器学习开发的一些最佳实践。

在下一章中,我们将通过一个端到端的用例来介绍一个托管机器学习平台。

第九章:用例 - 客户流失预测

在上一章中,我们讨论了市场上可用的Feast特征存储的替代方案。我们查看了一些云提供商的特征存储产品,这些产品是机器学习ML)平台产品的一部分,即 SageMaker、Vertex AI 和 Databricks。我们还查看了一些其他供应商,他们提供可以与您的云提供商一起使用的托管特征存储,例如 Tecton 和 Hopsworks,其中 Hopsworks 也是开源的。为了了解托管特征存储,我们尝试了 SageMaker 特征存储的练习,并简要讨论了 ML 最佳实践。

在本章中,我们将讨论使用电信数据集进行客户流失的端到端用例。我们将逐步介绍数据清洗、特征工程、特征摄取、模型训练、部署和监控。对于这个练习,我们将使用托管特征存储——Amazon SageMaker。选择 SageMaker 而不是上一章中讨论的其他替代方案的原因很简单,那就是软件试用版的易于访问。

本章的目标是逐步通过使用托管特征存储的客户流失预测 ML 用例。这应该能让你了解它与自管理特征存储的不同之处,以及特征存储帮助的基本特征监控和模型监控方面。

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

  • 基础设施设置

  • 问题与数据集的介绍

  • 数据处理和特征工程

  • 特征组定义和摄取

  • 模型训练

  • 模型预测

  • 特征监控

  • 模型监控

技术要求

为了运行示例并更好地理解本章内容,了解前几章涵盖的主题将很有用,但不是必需的。为了跟随本章的代码示例,你需要熟悉笔记本环境,这可以是本地设置,如 Jupyter Notebook,或者在线笔记本环境,如 Google Colab、Kaggle 或 SageMaker。你还需要一个 AWS 账户,并完全访问 SageMaker 和 Glue 控制台。你可以在试用期间创建新账户并免费使用所有服务。你可以在以下 GitHub 链接找到本书的代码示例:

https://github.com/PacktPublishing/Feature-Store-for-Machine-Learning/tree/main/Chapter08

基础设施设置

在本章的练习中,我们需要一个 S3 存储桶来存储数据,一个 IAM 角色,以及一个可以访问 SageMaker Feature Store 和 S3 存储桶的 IAM 用户。由于我们已经完成了所有这些资源的创建,我将跳过这一部分。请参阅第四章将特征存储添加到机器学习模型,了解 S3 存储桶的创建,以及第七章Feast 替代方案和机器学习最佳实践,了解 IAM 角色和 IAM 用户的创建。这就是本章初始设置所需的所有内容。

重要提示

我尽量少使用 AWS SageMaker 的资源,因为如果你的免费试用已经结束,这将产生费用。你可以使用 SageMaker Studio 来获得更好的笔记本和特征存储的 UI 体验。

问题及数据集介绍

在这个练习中,我们将使用可在 Kaggle 的 URL https://www.kaggle.com/datasets/blastchar/telco-customer-churn 上找到的电信客户流失数据集。练习的目标是使用这个数据集,为模型训练准备数据,并训练一个 XGBoost 模型来预测客户流失。该数据集有 21 列,列名具有自解释性。以下是对数据集的预览:

![图 8.1 – 电信数据集img/B18024_08_001.jpg

图 8.1 – 电信数据集

图 8.1 展示了标记的电信客户流失数据集。customerID 列是客户的 ID。除了 Churn 列之外的所有列代表属性集,而 Churn 列是目标列。

让我们动手进行特征工程。

数据处理和特征工程

在本节中,我们将使用电信客户流失数据集,并生成可用于训练模型的特征。让我们创建一个笔记本,命名为 feature-engineering.ipynb,并安装所需的依赖项:

!pip install pandas sklearn python-slugify s3fs sagemaker

完成库的安装后,读取数据。对于这个练习,我已经从 Kaggle 下载了数据,并将其保存在可以从笔记本访问的位置。

以下命令从 S3 读取数据:

import os
import numpy as np
import pandas as pd
from slugify import slugify
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import StandardScaler
""" If you are executing the notebook outside AWS(Local jupyter lab, google collab or kaggle etc.), please uncomment the following 3 lines of code and set the AWS credentials """
#os.environ["AWS_ACCESS_KEY_ID"] = "<aws_key>"
#os.environ["AWS_SECRET_ACCESS_KEY"] = "<aws_secret>"
#os.environ["AWS_DEFAULT_REGION"] = "us-east-1"
telcom = pd.read_csv("s3://<bucket_name_path>/telco-customer-churn.csv")

重要提示

如果你在外部 AWS 中执行笔记本,则使用环境变量设置用户凭据。

如果你预览该数据集,有几个列需要重新格式化,转换为分类列,或删除空值。让我们依次执行这些转换。

TotalCharges 列包含一些空字符串。让我们删除包含空或 null 值 TotalCharges 的行:

# Replace empty strings with nan
churn_data['TotalCharges'] = churn_data["TotalCharges"].replace(" ",np.nan)
# remove null values
churn_data = churn_data[churn_data["TotalCharges"].notnull()]
churn_data = churn_data.reset_index()[churn_data.columns]
churn_data["TotalCharges"] = churn_data["TotalCharges"].astype(float)

上一段代码块将所有空字符串替换为 np.nan,并删除了 TotalCharges 列中包含空的行。

接下来,让我们看看tenure列。这个列有整数值,代表客户以月为单位的服务期限。除了值之外,我们还可以将客户分为三组:短期(0-24 个月)、中期(24-48 个月)和长期(大于 48 个月)。

以下代码添加了具有定义组的客户tenure_group列:

# Create tenure_group columns using the tenure
def tenure_label(churn_data) :
    if churn_data["tenure"] <= 24 :
        return "0-24"
    elif (churn_data["tenure"] > 24) & (churn_data["tenure"] <= 48) :
        return "24-48"
    elif churn_data["tenure"] > 48:
        return "48-end"
churn_data["tenure_group"] = churn_data.apply(
    lambda churn_data: tenure_label(churn_data), axis = 1)

上一代码块创建了一个分类列tenure_group,它将根据客户服务期限的长度具有三个值,0-2424-4848-end

数据集中的一些列依赖于其他列。例如,OnlineSecurity依赖于客户是否有InternetService。因此,一些这些列,即OnlineSecurityOnlineBackupDeviceProtectionTechSupportStreamingTVStreamingMovies,其值用No internet service代替No。让我们将这些列中的No internet service替换为No

以下代码块执行替换操作:

# Replace 'No internet service' to No for the following columns
replace_cols = ['OnlineSecurity', 'OnlineBackup', 
                'DeviceProtection', 'TechSupport',
                'StreamingTV', 'StreamingMovies']
for i in replace_cols : 
    churn_data[i] = churn_data[i].replace({'No internet service' : 'No'})

我们已经进行了一系列数据清洗操作。在继续进行进一步转换之前,让我们预览一次数据集。

以下代码块对churn_data DataFrame 进行采样:

churn_data.sample(5)

以下代码输出一个样本预览,如图所示:

![图 8.2 – Churn 数据集img/B18024_08_002.jpg

图 8.2 – Churn 数据集

图 8.2所示,数据集是干净的,只包含分类或数值列。下一步是将这些分类值转换为数值编码。让我们查看数据集,看看哪些是分类的,哪些是数值的。

以下代码计算每个列的唯一值:

churn_data.nunique()

上一代码块显示以下输出:

![图 8.3 – 每个列的唯一值计数img/B18024_08_003.jpg

图 8.3 – 每个列的唯一值计数

图 8.3所示,除了MonthlyChargestenureTotalCharges之外,所有其他列都是分类列。

在数据集中,有二进制列和多值分类列。让我们找出哪些是二进制列,哪些是多值列。以下代码块检查列是否为二进制列:

# filter all the col if unique values in the column is 2
bin_cols = churn_data.nunique()[churn_data.nunique() == 2].keys().tolist()

现在我们有了二进制列的列表,让我们使用标签编码器将它们转换为 0 和 1。

以下代码使用标签编码器对二进制列进行转换:

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

下一步是将多值分类列转换为 0 和 1。为此,让我们首先过滤出多值列名。

以下代码块选择多值列:

all_categorical_cols = churn_data.nunique()[churn_data.nunique() <=4].keys().tolist()
multi_value_cols = [col for col in all_categorical_cols if col not in bin_cols]

上一代码块首先过滤掉所有分类列,然后过滤掉二进制列,这样我们只剩下多值列。

以下代码块将多值列转换为二进制编码:

churn_data = pd.get_dummies(data = churn_data, columns=multi_value_cols)

最后的部分是将数值转换为标准范围。由于数值列可能具有不同的范围,将列缩放到标准范围对于机器学习算法可能有益。这也有助于算法更快地收敛。因此,让我们将数值列缩放到标准范围。

下面的代码块使用 StandardScaler 将所有数值列缩放到标准范围:

numerical_cols = ['tenure','MonthlyCharges','TotalCharges']
std = StandardScaler()
churn_data[numerical_cols] = std.fit_transform(churn_data[numerical_cols])

前面的代码块缩放了数值列:tenureMonthlyChargesTotalCharges。现在我们的特征工程已经完成,让我们预览最终的特性集并将其摄取到 SageMaker Feature Store 中。

下面的代码块显示了特征集预览:

churn_data.columns = [slugify(col, lowercase=True, separator='_') for col in churn_data.columns]
churn_data.head()

前面的代码块将列名格式化为小写,并将字符串中的所有分隔符(如空格和连字符)替换为下划线。最终的特征在下面的屏幕截图中显示:

![图 8.4 – 特征集]

![图片 B18024_08_004.jpg]

图 8.4 – 特征集

最终的特征集有 33 列,如 图 8.4 所示。如果你还记得在 第四章 中,将特征存储添加到机器学习模型,在创建特征定义时,我们根据实体或逻辑组对实体和特征进行了分组。尽管这些特征可以分成多个组,但我们将创建一个单独的特征组并将所有特征摄取到其中。

在下一节中,让我们创建特征定义并摄取数据。

特征组定义和特征摄取

现在我们已经准备好了用于摄取的特征集,让我们创建特征定义并将特征摄取到特征存储中。正如之前提到的,我们将使用 SageMaker Feature Store。如果你还记得前面的章节,我们总是将特征定义保存在一个单独的笔记本中,因为这是一个一次性活动。在这个练习中,我们将尝试一种不同的方法,即使用条件语句在不存在的情况下创建特征组。你可以使用这两种方法中的任何一种。

让我们在同一个笔记本中继续,初始化 boto3 会话并检查我们的特征组是否已经存在:

import boto3
FEATURE_GROUP_NAME = "telcom-customer-features"
feature_group_exist = False
client = boto3.client('sagemaker')
response = client.list_feature_groups(
    NameContains=FEATURE_GROUP_NAME)
if FEATURE_GROUP_NAME in response["FeatureGroupSummaries"]:
  feature_group_exist = True

前面的代码块查询 SageMaker 以检查名为 telcom-customer-features 的特征组是否存在,并根据该结果设置一个布尔值。我们将使用这个布尔值来创建特征组或跳过创建,直接将数据摄取到特征存储中。

下面的代码块初始化了与 SageMaker Feature Store 交互所需的对象:

import sagemaker
from sagemaker.session import Session
import time
from sagemaker.feature_store.feature_definition import FeatureDefinition, FeatureTypeEnum
role = "arn:aws:iam::<account_number>:role/sagemaker-iam-role"
sagemaker_session = sagemaker.Session()
region = sagemaker_session.boto_region_name
s3_bucket_name = "feast-demo-mar-2022"

重要提示

在前面的代码块中使用之前创建的 IAM 角色。IAM 角色应该具有 AmazonSageMakerFullAccessAmazonS3FullAccess

下一步是初始化 FeatureGroup 对象。下面的代码初始化了特征组对象:

from sagemaker.feature_store.feature_group import FeatureGroup
customers_feature_group = FeatureGroup(
    name=FEATURE_GROUP_NAME, 
    sagemaker_session=sagemaker_session
)

现在我们将使用之前设置的布尔值来有条件地创建特征组,如果特征组不存在。以下代码块加载特征定义并调用 create 方法,如果特征组不存在:

churn_data["event_timestamp"] = float(round(time.time()))
if not feature_group_exist:
  customers_feature_group.load_feature_definitions(
      churn_data[[col 
                  for col in churn_data.columns 
                  if col not in ["customerid"]]]) 
  customer_id_def = FeatureDefinition(
      feature_name='customerid', 
      feature_type=FeatureTypeEnum.STRING)
  customers_feature_group.feature_definitions = [customer_id_def] + customers_feature_group.feature_definitions
  customers_feature_group.create(
    s3_uri=f"s3://{s3_bucket_name}/{FEATURE_GROUP_NAME}",
    record_identifier_name="customerid",
    event_time_feature_name="event_timestamp",
    role_arn=role,
    enable_online_store=False
    )

重要提示

load_feature_definitions 调用中,如果你注意到,我正在加载所有特征定义列,除了 customerid 列,并在下一行手动将 customerid 列添加到特征定义列表中。这样做的原因是 sagemaker 库无法将 string 数据类型识别为 pandas dtype 中的 object

create 特征组调用很简单。我通过传递 enable_online_storeFalse 来禁用在线存储,因为我们将会尝试批量管道,并将在线模型留作练习。一旦前面的代码块执行,根据条件语句,第一次将创建特征组,而对于后续的运行,将跳过特征组创建。

最后一步是摄取 DataFrame。以下代码块执行摄取并打印任何失败信息:

ingestion_results = customers_feature_group.ingest(
    churn_data, max_workers=1)
ingestion_results.failed_rows

重要提示

如果你只有批量使用案例,SageMaker 有一个 Spark 库可以直接用于将数据导入离线存储,这也是一种经济高效的方法。

这完成了特征工程和摄取。在下一节中,让我们看看模型训练。

模型训练

与之前一样,对于模型训练,特征存储是源。因此,让我们创建我们的模型训练笔记本并安装和初始化查询特征存储所需的所需对象。以下是笔记本的链接:

github.com/PacktPublishing/Feature-Store-for-Machine-Learning/blob/main/Chapter08/Ch8_model_training.ipynb

以下代码块安装模型训练所需的库:

!pip install sagemaker==2.88.0 s3fs joblib scikit-learn==1.0.2 xgboost

安装所需的库后,初始化 SageMaker 会话和所需的对象:

import sagemaker
from sagemaker.session import Session
from sagemaker.feature_store.feature_group import FeatureGroup
#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"
role = "arn:aws:iam::<account_number>:role/sagemaker-iam-role"
FEATURE_GROUP_NAME = "telcom-customer-features"
sagemaker_session = sagemaker.Session()
region = sagemaker_session.boto_region_name
s3_bucket_name = "feast-demo-mar-2022"
customers_feature_group = FeatureGroup(
    name=FEATURE_GROUP_NAME, 
    sagemaker_session=sagemaker_session
)

前面的代码块初始化了 SageMaker 会话并初始化了特征组对象。特征组的 name 应该与我们特征工程笔记本中创建的特征组 name 相同。

重要提示

将之前创建的 IAM 角色分配给 role 变量。另外,如果你在 AWS 之外运行笔记本,你需要取消注释并设置前面的代码块中的 AWS 凭据。

下一步是查询历史存储以生成训练数据。与 Feast 不同,我们在这里不需要实体 DataFrame。相反,我们使用 SQL 查询来获取历史数据。它具有与 Feast 相同的时间旅行功能。为此练习,让我们使用与上一章中在 SageMaker 概述部分使用的类似查询获取所有客户的最新特征:

get_latest_snapshot_query = customers_feature_group.athena_query()
query = f"""SELECT *
FROM
    (SELECT *,
         row_number()
        OVER (PARTITION BY customerid
    ORDER BY  event_timestamp desc, Api_Invocation_Time DESC, write_time DESC) AS row_num
    FROM "{get_latest_snapshot_query.table_name}")
WHERE row_num = 1 and 
NOT is_deleted;"""
get_latest_snapshot_query.run(
    query_string=query, 
    output_location=f"s3://{s3_bucket_name}/output")
get_latest_snapshot_query.wait()

如果你记得正确的话,我们在上一章中使用了类似的嵌套查询。前面的代码块获取了所有客户及其最新特征。查询的输出将被写入run API 调用中提到的特定 S3 位置。

一旦查询成功运行,可以使用以下代码块获取数据集:

churn_data = get_latest_snapshot_query.as_dataframe()
churn_data = churn_data.drop(columns=["event_timestamp", "write_time", "api_invocation_time", "is_deleted", "row_num"])

重要提示

请注意,我们将从本节(模型训练)开始,直到前面的代码块,执行相同的步骤进行模型预测和特征监控。

前面的代码块获取数据集并删除了不需要的列。获取的数据集类似于图 8.4中所示的数据,但增加了以下列:write_timeapi_invocation_timeis_deletedrow_num。前三个是 SageMaker 在摄取过程中添加的额外元数据列,而row_num是我们为了获取每个客户的最新特征而在查询中创建的列。

现在我们有了数据集,让我们将其分为训练集和测试集。下面的代码块从数据集中删除了训练中不需要的列,并将数据分为训练集和测试集:

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import confusion_matrix,accuracy_score,classification_report
from sklearn.metrics import roc_auc_score,roc_curve
from sklearn.metrics import precision_score,recall_score
Id_col = ["customerid"]
target_col = ["churn"]
# Split into a train and test set
train, test = train_test_split(churn_data,
                               test_size = .25,
                               random_state = 111)
cols    = [i for i in churn_data.columns if i not in Id_col + target_col]
training_x = train[cols]
training_y = train[target_col]
testing_x  = test[cols]
testing_y  = test[target_col]

前面的代码块省略了 ID 列,并执行了 75/25 的训练和测试分割。

其余的都很直接,基本上是训练 XGBoost 模型、参数调整和比较性能。以下是一个用于训练、样本分析和记录模型的示例代码块:

import joblib
import boto3
model = XGBClassifier(max_depth=7, 
                      objective='binary:logistic')
model.fit(training_x, training_y)
predictions = model.predict(testing_x)
probabilities = model.predict_proba(testing_x)
print("\n Classification report : \n", 
      classification_report(testing_y, predictions))
print("Accuracy   Score : ", 
      accuracy_score(testing_y, predictions))
# confusion matrix
conf_matrix = confusion_matrix(testing_y, predictions)
model_roc_auc = roc_auc_score(testing_y, predictions)
print("Area under curve : ", model_roc_auc, "\n")
joblib.dump(model, '/content/customer-churn-v0.0')
s3_client = boto3.client('s3')
response = s3_client.upload_file('/content/customer-churn-v0.0', s3_bucket_name, "model-repo/customer-churn-v0.0")

前面的代码块还将模型记录到 S3 的特定位置。这是一种粗略的做法。始终最好使用实验训练工具来记录性能和模型。

现在模型训练已完成,让我们看看模型评分。

模型预测

如前一小节最后一条注释所述,由于这是一个批量模型,因此从离线存储中获取数据的模型评分步骤与之前相似。然而,根据需要评分的客户(可能是所有客户),您可能需要过滤数据集。一旦过滤了数据集,其余步骤再次直接,即加载模型、运行预测和存储结果。

以下是一个用于加载模型、运行预测并将结果存储回 S3 以供消费的示例代码块:

import boto3
from datetime import date
s3 = boto3.client('s3')
s3.download_file(s3_bucket_name, f"model-repo/customer-churn-v0.0", "customer-churn-v0.0")
features = churn_data.drop(['customerid', 'churn'], axis=1)
loaded_model = joblib.load('/content/customer-churn-v0.0')
prediction = loaded_model.predict(features)
prediction.tolist()
file_name = f"customer_churn_prediction_{date.today()}.parquet"
churn_data["predicted_churn"] = prediction.tolist()
s3_url = f's3://{s3_bucket_name}/prediction_results/{file_name}'
churn_data.to_parquet(s3_url)

前面的代码块从 S3 下载模型,加载模型,将其与从历史存储中获取的数据评分,并将结果存储在 S3 桶中以供消费。

注意

XGBoost、Joblib 和 scikit-learn 的库版本应与保存模型时使用的版本相同,否则加载模型可能会失败。

为了将此机器学习管道投入生产,我们可以使用与我们在第六章,“模型到生产及之后”中所做的类似的编排。我将将其留作练习,因为它是重复的内容。接下来,让我们看看特征监控的例子。

特征监控

我们在书中多次讨论了特征监控在机器学习系统中的重要性。我们还讨论了特征存储如何标准化特征监控。在本节中,让我们看看一个对任何模型都很有用的特征监控示例。由于特征监控是在特征数据上计算一组统计信息并通知数据科学家或数据工程师变化,因此它需要模型使用的最新特征。

在本节中,我们将计算特征数据的摘要统计和特征相关性,这些可以按计划运行并定期发送给相关人员,以便他们可以根据这些信息采取行动。如“模型训练”部分的最后一条注释中提到的,获取特征步骤与该部分所做的是相同的。一旦您拥有了所有特征,下一步就是计算所需的统计量。

重要提示

请注意,您可能需要安装额外的库。以下是笔记本的 URL:github.com/PacktPublishing/Feature-Store-for-Machine-Learning/blob/main/Chapter08/Ch8_feature_monitoring.ipynb

以下代码块计算了特征数据的摘要统计并绘制了相关指标:

import numpy as np
import warnings
warnings.filterwarnings("ignore")
import plotly.offline as py
import plotly.graph_objs as go
churn_data.describe(include='all').T

上一行代码生成了数据集的描述性统计信息,包括最小值、最大值、计数、标准差等。

除了描述性统计之外,特征的相关矩阵对于所有机器学习模型来说也是非常有用的。以下代码块计算了特征的相关矩阵并绘制了热图:

corr = churn_data.corr()
cols = corr.columns.tolist()
trace = go.Heatmap(z=np.array(corr),
                   x=cols,
                   y=cols,
                   colorscale="Viridis",
                   colorbar=dict(
                       title="Pearson Coefficient",
                       titleside="right"
                       ),
                   )
layout = go.Layout(dict(title="Correlation Matrix",
                        height=720,
                        width=800,
                        margin=dict(r=0, l=210,
                                    t=25, b=210,
                                    ),
                        )
                   )
fig = go.Figure(data=[trace], layout=layout)
py.iplot(fig)

前面的代码块输出了以下热图:

![图 8.5 – 特征相关性img/B18024_08_005.jpg

图 8.5 – 特征相关性

与之前的运行相比,您可以添加更多统计信息,通过电子邮件、Slack 通知等方式发出警报。这可以放在另一个笔记本/Python 脚本中,可以按与特征工程笔记本相同的频率或更低的频率进行调度,并将自动报告发送给您。以下是完整笔记本的链接:github.com/PacktPublishing/Feature-Store-for-Machine-Learning/blob/main/Chapter08/Ch8_feature_monitoring.ipynb

重要提示

这只是一个特征监控的例子。还有更复杂的统计和指标可以用来确定特征的健康状况。

接下来,让我们看看模型监控。

模型监控

机器学习的另一个重要方面是模型监控。模型监控有不同的方面:对于在线模型,可能是系统监控,其中你监控延迟、CPU、内存利用率、每分钟模型请求等。另一个方面是模型性能监控。再次强调,有多种不同的方法来衡量性能。在这个例子中,我们将查看一个简单的分类报告和模型的准确率。

为了生成分类报告并计算实时模型的准确率,你需要预测数据和实时数据的真实值。对于这个例子,让我们假设流失率模型每周运行一次以生成流失预测,真实值将在模型运行后的每 4 周可用。这意味着如果模型预测客户 x 的流失为True,并且在接下来的 4 周内,如果我们因为任何原因失去了这个客户,那么模型预测正确;否则,预测是错误的。因此,对于每次模型预测的运行,我们需要等待 4 周才能获得真实值。

为了这个练习的简便性,让我们也假设每周将真实值填充回特征存储库。这意味着特征存储库总是包含最新的真实值。现在,我们的任务是获取特征存储库中最新特征对应的预测结果(4 周前运行的预测)并计算所需的指标。让我们接下来这么做。

如前所述,从特征存储库获取最新特征的步骤与我们在前三个部分中所做的是相同的。一旦从特征存储库获取了数据,下面的代码获取相应的预测结果并合并数据集:

from datetime import date, timedelta
import pandas as pd
pred_date = date.today()-timedelta(weeks=4)
file_name = f"customer_churn_prediction_{pred_date}.parquet"
prediction_data = pd.read_parquet(f"s3://{s3_bucket_name}/prediction_results/{file_name}")
prediction_y = prediction_data[["customerid", 
                                "predicted_churn"]]
acutal_y = churn_data[["customerid", "churn"]]
merged_data = prediction_y.merge(acutal_y, on="customerid")
merged_data.head()

之前的代码块应该产生类似于以下快照的输出。

图 8.6 – 模型监控合并数据

图 8.6 – 模型监控合并数据

重要提示

由于我们假设预测值和真实值之间相隔 4 周,之前的代码块尝试获取今天起 4 周后的数据。对于这个练习,你可以将file_name变量替换为预测输出的 Parquet 文件。

一旦你有了图 8.6中的 DataFrame,下面的代码块使用predicted_churnchurn列来生成分类报告和准确率:

testing_y = merged_data["churn"]
predictions = merged_data["predicted_churn"]
print("\n Classification report : \n", 
      classification_report(testing_y, predictions))
print("Accuracy   Score : ", 
      accuracy_score(testing_y, predictions))
# confusion matrix
conf_matrix = confusion_matrix(testing_y, predictions)
# roc_auc_score
model_roc_auc = roc_auc_score(testing_y, predictions)
print("Area under curve : ", model_roc_auc, "\n")

之前的代码块产生的输出类似于以下内容。

图 8.7 – 分类报告

图 8.7 – 分类报告

如前所述,这是样本监控。它可以安排与特征工程笔记本相同的间隔进行,尽管由于预测数据的不可用,它会在前四次迭代中失败。同时,请确保根据您的需求适当地调整预测文件名。以下是完整笔记本的 URL:github.com/PacktPublishing/Feature-Store-for-Machine-Learning/blob/main/Chapter08/Ch8_model_monitoring.ipynb

带着这些,让我们总结一下本章所学的内容。

摘要

本章的目的是尝试一个用例,即使用从 Kaggle 可获得的数据库进行电信客户流失预测。为此用例,我们使用了在上章中介绍的管理 SageMaker 特征存储。在练习中,我们经历了机器学习的不同阶段,如数据处理、特征工程、模型训练和模型预测。我们还查看了一个特征监控和模型监控的示例。本章的目标不是构建模型,而是展示如何使用管理特征存储进行模型构建以及它为监控带来的机会。要了解更多关于特征存储的信息,apply 会议(www.applyconf.com/)和特征存储论坛(www.featurestore.org/)是很好的资源。为了跟上机器学习领域的新发展以及其他公司如何解决类似问题,有一些有趣的播客,例如 TWIML AI (twimlai.com/)和数据怀疑论者(https://dataskeptic.com/)。这些资源应该能帮助你根据你对机器学习的兴趣领域找到更多资源。带着这些,让我们结束本章和本书。我希望我有效地传达了特征存储在机器学习过程中的重要性,并且这是对你和我的时间的良好利用。谢谢!

posted @ 2025-09-04 14:12  绝不原创的飞龙  阅读(12)  评论(0)    收藏  举报