Python 和 LightGBM 机器学习(二)
原文:
annas-archive.org/md5/fec3be57ef79d1371f9bec4f04b9ee9c译者:飞龙
第六章:使用 LightGBM 解决现实世界的数据科学问题
在前面的章节中,我们逐渐构建了一套工具集,使我们能够解决机器学习问题。我们看到了检查数据、解决数据问题和创建模型的例子。本章正式定义并应用数据科学流程到两个案例研究中。
本章详细概述了数据科学生命周期及其包含的所有步骤。在回归和分类问题背景下,讨论了问题定义、数据探索、数据清洗、建模和报告的概念。我们还探讨了使用所学技术准备数据以及构建优化后的 LightGBM 模型。最后,我们深入探讨了如何利用训练好的模型作为机器学习操作(MLOps)的介绍。
本章的主要内容包括:
-
数据科学生命周期
-
使用 LightGBM 预测风力涡轮机发电量
-
使用 LightGBM 对个人信用评分进行分类
技术要求
本章包含示例和代码片段,展示了如何使用 Optuna 对 LightGBM 进行参数优化研究。关于设置本章所需环境的完整示例和说明可在github.com/PacktPublishing/Practical-Machine-Learning-with-LightGBM-and-Python/tree/main/chapter-6找到。
数据科学生命周期
数据科学已成为一门关键学科,使组织能够从其数据中提取有价值的见解并推动更好的决策。数据科学的核心是数据科学生命周期,这是一个系统、迭代的流程,指导数据驱动的解决问题的各种行业和领域。此生命周期概述了一系列数据科学家遵循的步骤,以确保他们解决正确的问题,并提供可操作见解,以产生实际影响。
数据科学生命周期的第一阶段涉及定义问题,这包括理解业务背景、阐述目标和制定假设。这一关键阶段通过确立明确的方向和范围,为整个项目奠定了基础。生命周期中的后续阶段侧重于数据收集、准备和探索,共同涉及收集相关数据、清洗和预处理它,以及进行探索性数据分析以揭示模式和趋势。
数据分析完成后,数据科学生命周期进入模型选择、训练、评估和调整阶段。这些阶段通过选择最合适的算法、在预处理数据上训练它们并优化其性能,对于开发准确和可靠的预测或描述性模型至关重要。目标是构建一个健壮的模型,能够很好地泛化到未见数据,并有效地解决当前问题。
最后,数据科学生命周期强调将最终模型部署到生产环境中的重要性,监控其性能,并维护它以确保其持续的相关性和准确性。同样重要的是将结果和见解传达给利益相关者,这对于推动明智的决策和实现数据科学全部潜力至关重要。通过遵循数据科学生命周期,组织可以系统地从数据中提取价值,并解锁增长和创新的新机会。
在之前的示例中,我们遵循了一个松散的步骤食谱来处理数据和创建模型。在下一节中,我们将正式定义并讨论数据科学生命周期的步骤。
定义数据科学生命周期
以下是在数据科学生命周期中广泛应用的几个关键步骤。这些步骤也在图 6.1中展示,该图说明了生命周期的循环性质。
图 6.1 – 描述数据科学生命周期的图
这些是关键步骤:
-
定义问题:明确阐述业务问题、目标和目标。这一阶段涉及理解利益相关者的需求、制定假设和确定项目的范围。定义问题还为数据收集设定了舞台,并可能决定我们将如何利用我们的模型。
-
数据收集:从各种来源收集所需数据,例如数据库、API、网络抓取或第三方数据提供商。确保数据具有代表性、准确性和与问题的相关性。记录数据的来源和移动方式对于建立数据血缘很重要。此外,构建一个数据字典来记录数据的格式、结构、内容和意义。重要的是,验证数据收集或抽样过程中可能存在的任何潜在偏差。
-
数据准备:清洁和预处理数据,使其适合分析。这一阶段包括诸如数据清洗(例如,处理缺失值和删除重复项)、数据转换(例如,归一化和编码分类变量)和特征工程(例如,创建新变量或聚合现有变量)等任务。将数据移动和合并到可以进行分析和建模的地方可能也是必要的。
-
数据探索:通过进行探索性数据分析(EDA)来深入了解数据。这一步骤包括可视化数据分布,识别趋势和模式,检测异常值和异常,以及检查特征之间的关系和相关性。
-
模型选择:根据问题类型(例如,回归、分类或聚类)和数据特征选择最合适的数据建模技术。选择多个模型算法来验证数据集上的性能是很重要的。
-
模型训练:使用准备好的数据训练选定的模型。这一步骤包括将数据分为训练集和验证集,设置模型参数(超参数),并将模型拟合到数据中。
-
模型评估:使用适当的评估指标(例如,准确率、精确率、召回率、F1 分数、ROC 曲线下面积(AUC-ROC)或均方根误差(RMSE))评估训练模型的性能,并将它们进行比较以选择性能最佳的模型。进行交叉验证或使用保留测试集以确保无偏评估。
-
模型调优:通过优化超参数、特征选择或结合领域知识来微调选定的模型。这一步骤旨在提高模型性能和泛化到未见数据的能力。对于特定问题调整模型也可能是合适的;例如,在识别面部时,更高的精确度比高召回率更合适。
-
模型部署:如果模型要成为更广泛软件系统的一部分,将其最终模型部署到生产环境中,以便用于做出预测或提供决策信息。部署可能涉及将模型集成到现有系统中,创建 API,或设置监控和维护程序。
-
模型监控和维护:持续监控模型的性能,并在必要时更新它,以确保其保持准确性和相关性。应使用检测模型和数据漂移等技术来确保模型性能。模型维护可能涉及使用新数据重新训练模型,更新特征,或细化问题定义。
-
沟通结果:与利益相关者分享见解和结果,包括基于分析的任何建议或行动。沟通结果可能涉及创建可视化、仪表板或报告,以有效地传达发现。
我们现在考察两个案例研究,以了解数据科学生命周期如何实际应用于现实世界的数据。我们研究了一个回归问题,即预测风力涡轮机发电量,以及一个分类问题,即对个人信用评分进行分类。
使用 LightGBM 预测风力涡轮机发电量
我们的第一个案例研究是一个旨在预测风力涡轮机发电功率的问题。该问题的数据集可以从www.kaggle.com/datasets/mukund23/hackerearth-machine-learning-challenge获取。
我们使用前一部分中定义的步骤来处理这个问题,同时详细说明每个步骤中涉及的内容以及代码片段。完整的最终解决方案可在 github.com/PacktPublis… 处找到。
问题定义
数据集包含在特定日期和时间测量的风力涡轮机发电功率(kW/h)测量值。每个测量值旁边都有风力涡轮机的参数,包括风车的物理测量(包括风车高度、叶片宽度和长度)、涡轮机的运行测量(包括电阻(欧姆)、电机扭矩、发电机温度和转子扭矩)以及大气条件(包括风速、温度和压力)。
给定参数集,我们必须构建一个回归模型来预测生成的功率(kW/h)。因此,我们采用回归建模。模型的质量通过均方误差(MSE)和决定系数(R²)来衡量。我们还必须确定哪些因素对发电影响最大。
数据收集
数据集包含在 2018 年 10 月至 2019 年 9 月 11 个月内收集的 22,800 个样本。数据以 CSV 文件形式提供,并作为公共领域数据发布。没有收集额外的数据。
数据准备
我们现在可以查看准备数据以进行清洗和探索。通过从我们的 pandas DataFrame 获取信息,我们可以看到数据集包含 18 个数值特征、2 个分类特征和日期特征:
train_df.info()
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 tracking_id 28200 non-null object
1 datetime 28200 non-null object
2 wind_speed(m/s) 27927 non-null float64
3 atmospheric_temperature(°C) 24750 non-null float64
4 shaft_temperature(°C) 28198 non-null float64
...
20 windmill_height(m) 27657 non-null float64
21 windmill_generated_power(kW/h) 27993 non-null float64
我们可以立即看到数据集中存在缺失值,一些特征少于 28,200 个值。我们可以通过计算统计描述来更好地了解数据分布:
train_df.describe().T.style.bar(subset=['mean'])
这将打印出以下内容:
| 特征 | 计数 | 平均值 | 标准差 | 最小值 | 最大值 |
|---|---|---|---|---|---|
| 风速(m/s) | 27927 | 69.04 | 76.28 | -496.21 | 601.46 |
| 大气温度(°C) | 24750 | 0.38 | 44.28 | -99.00 | 80.22 |
| 轴温度(°C) | 28198 | 40.09 | 27.20 | -99.00 | 169.82 |
| 叶片角度(°) | 27984 | -9.65 | 47.92 | -146.26 | 165.93 |
| 传动箱温度(°C) | 28199 | 41.03 | 43.66 | -244.97 | 999.00 |
| 发动机温度(°C) | 28188 | 42.61 | 6.12 | 3.17 | 50.00 |
| 电机扭矩(N-m) | 28176 | 1710 | 827 | 500 | 3000.00 |
| 发电机温度(°C) | 28188 | 65.03 | 19.82 | 33.89 | 100.00 |
| 大气压力(Pascal) | 25493 | 53185 | 187504 | -1188624 | 1272552 |
| 面积温度(°C) | 28200 | 32.74 | 7.70 | -30.00 | 55.00 |
| 风机本体温度(°C) | 25837 | 20.80 | 54.36 | -999.00 | 323.00 |
| 风向(°) | 23097 | 306.89 | 134.06 | 0.00 | 569.97 |
| 阻抗(欧姆) | 28199 | 1575.6 | 483.33 | -1005.22 | 4693.48 |
| 转子扭矩(N-m) | 27628 | 25.85 | 32.42 | -136.73 | 236.88 |
| 叶片长度(m) | 23107 | 2.25 | 11.28 | -99.00 | 18.21 |
| 叶片宽度(m) | 28200 | 0.40 | 0.06 | 0.20 | 0.50 |
| 风机高度(m) | 27657 | 25.89 | 7.77 | -30.30 | 78.35 |
| 风机发电功率 (kW/h) | 27993 | 6.13 | 2.70 | 0.96 | 20.18 |
表 6.1 – 风机数据集中数值特征的统计描述
观察表 6.1 中特征的统计描述,我们可以看到以下不规则性:
-
许多特征存在异常值:通常,标准差大于平均值可能表明存在异常值。例如,风速、大气温度和大气压力。同样,远离平均值的最大值或最小值可能表明数据中存在异常值。我们可以通过使用直方图可视化数据分布来进一步验证这一点。
-
物理不可能性:数据显示某些测量数据中存在不可能性:长度(以米为单位)小于 0(例如,叶片长度)和温度超出自然范围(体温为-999)。
-
-99.0和-999.0在几个特征中重复。这些值在特征之间自然发生的可能性很小。我们可以推断这些值表明样本中存在缺失或错误的测量。
我们可以可视化特征的分布,以调查异常值。例如,对于大气温度,我们有以下:
sns.histplot(train_df["atmospheric_temperature(°C)"], bins=30)
图 6.2 – 以摄氏度显示的大气温度直方图
图 6.2 展示了数据中发现的问题中的两个:精确值为-99.0的测量值的高频率,表明存在错误。一些异常值也远离平均值。
最后,我们可以使用以下方法检查重复数据:
train_df[train_df.duplicated()]
没有返回行,表示数据集中没有重复项。
数据清洗
我们在数据集中识别出多个问题,这些问题需要作为数据清洗步骤的一部分来解决:
-
异常值:许多特征具有使该特征值分布扭曲的异常值。
-
测量误差:一些特征具有超出物理不可能性范围(长度小于 0 或温度在不可能范围内)的值。
-
-99.0和-999.0被视为缺失值。
我们首先解决异常值和测量误差,因为这会影响我们处理缺失值的方式。
处理异常值
处理数据集中的异常值有两个方面:准确识别异常值和选择适当的替换值。识别异常值的方法如下:
-
可视化: 如前所述,可以使用直方图或其他可视化数据分布的图表(如箱线图或散点图)。
-
领域知识: 就像我们识别测量误差一样,可以利用领域知识来决定值是否异常
-
统计分析: 使用统计方法确定值是否异常的两种流行方法是四分位数间距(IQR)和标准差。
四分位数间距(IQR)是第 25 百分位数和第 75 百分位数之间的差值。距离第 25 或第 75 百分位数超过 1.5 倍 IQR 的值被认为是异常值。
或者,我们可以利用标准差:我们计算数据集的均值和标准差。任何超过均值两倍或三倍的数据值都是异常值。将界限设置为标准差的二倍或三倍取决于基础数据。使用两倍标准差可能导致许多误报,但如果大量数据集中在均值附近,则这是合适的。使用三倍标准差更为保守,并且仅将非常远离均值的值标记为异常值。
当检测到异常值时,我们必须对它们采取行动。通常,我们的选择如下:
-
移除: 如果异常值是由于数据输入、测量或收集中的错误造成的,那么从数据集中移除它可能是合理的。但是,应该谨慎行事,因为移除过多的数据点可能导致信息丢失和结果偏差。在我们的数据集中移除包含异常值的实例将导致近 70%的数据丢失,这不是一个选择。
-
插补: 与缺失值类似,用更具有代表性的值替换异常值,例如变量的均值、中位数或众数,或者使用更复杂的插补方法,如k 近邻或基于回归的插补。
-
截断或限制: 设置一个阈值(可以是上限或下限),并将异常值在该阈值处截断或限制。这种方法保留了数据的原始结构,同时减少了极端值的影响。
对于风力涡轮机数据集,我们使用设置为标准差三倍的范围来检测异常值,并将值映射到np.nan,因此我们可以在以后替换它们:
column_data = frame[feature]
column_data = column_data[~np.isnan(column_data)]
mean, std = np.mean(column_data), np.std(column_data)
lower_bound = mean - std * 3
upper_bound = mean + std * 3
frame.loc[((frame[feature] < lower_bound) | (frame[feature] > upper_bound))] = np.nan
处理测量误差
被检测为测量误差类型的值也可以被认为是异常值,尽管不是在统计意义上的异常值。然而,我们可以用稍微不同的方式处理这些值,而不是用统计意义上的异常值处理。
我们应用我们的领域知识以及一些关于天气的研究,来确定这些特征的适当范围。然后我们将错误值限制在这些范围内:
frame.loc[frame["wind_speed(m/s)"] < 0, "wind_speed(m/s)"] = 0
frame.loc[frame["wind_speed(m/s)"] > 113, "wind_speed(m/s)"] = 113
frame.loc[frame["blade_length(m)"] < 0, "blade_length(m)"] = 0
frame.loc[frame["windmill_height(m)"] < 0, "windmill_height(m)"] = 0
frame.loc[frame["resistance(ohm)"] < 0, "resistance(ohm)"] = 0
在这里,我们将任何负长度、高度和电阻值设置为0。我们还把风速限制在113 m/s,这是记录中的最大阵风速度。
最后,我们可以处理数据集中的缺失值。
处理缺失值
我们在前面章节中讨论了处理缺失值的方法。在此总结一下,我们可以采取的一些潜在方法如下:
-
移除含有缺失值的实例
-
使用描述性统计(均值、中位数或众数)来填补缺失值
-
使用其他机器学习算法,通常是聚类等无监督技术来计算更稳健的统计量
移除缺失值将丢弃我们数据集的很大一部分。在这里,我们决定使用描述性统计来替换缺失值,以尽可能保留数据。
首先,我们将-99.0和-999.0值标记为缺失:
df.loc[frame[f] == -99.0, f] = np.nan
df.loc[frame[f] == 99.0, f] = np.nan
df.loc[frame[f] == -999.0, f] = np.nan
df.loc[frame[f] == 999.0, f] = np.nan
然后我们用均值替换缺失的数值,用众数替换分类值:
if f in numerical_columns:
frame[f].fillna(frame[f].mean(), inplace=True)
else:
frame[f].fillna(frame[f].mode()[0], inplace=True)
通常,在使用均值时我们必须小心,因为均值会受到异常值的影响。然而,由于我们已经将异常值标记为np.nan,它们在计算均值时被排除。当在测试集中替换缺失值时,还有一个额外的注意事项:由于测试集应被视为未见过的数据,我们必须使用训练数据集的均值来替换测试集中的缺失值。
这就完成了我们数据集所需的数据清洗。我们应该通过重新检查缺失值和重新计算描述性统计和数据直方图来验证我们的工作。
数据集清洗完毕后,我们可以进行下一步数据准备:特征工程。
特征工程
特征工程指的是创建新特征或修改现有特征以提升机器学习模型性能的过程。本质上,特征工程是利用领域知识和数据理解来创建使机器学习算法更有效工作的特征。它既是艺术也是科学,需要创造力、直觉和对问题的深刻理解。
特征工程过程通常从探索数据以了解其特征、分布和变量之间的关系开始。这一探索阶段可以揭示创建特征的可能机会,例如交互项、聚合特征或时间特征;例如,如果你正在处理包含客户交易数据的集合,你可能设计出捕捉交易频率、平均交易价值或自上次交易以来时间的特征。
在特征工程中,也有一些标准技术被广泛使用。这些包括对分类变量进行编码、对数值变量进行归一化、创建多项式特征和对连续变量进行分箱。例如,分类变量通常被编码为数值格式(如独热编码或顺序编码)以用于数学模型。同样,数值变量通常被归一化(如最小-最大缩放或标准化)以确保它们处于可比较的尺度上,并防止某些变量仅仅因为其尺度而支配其他变量。
然而,特征工程不是一个一刀切的过程。模型适用的特征可能严重依赖于具体问题、使用的算法和数据性质。因此,特征工程通常需要迭代实验和评估。尽管存在挑战,有效的特征工程可以显著提高模型性能。
注意
正如你可能已经注意到的,特征工程需要理解和探索数据,这取决于工程特征的可用性。这突出了数据科学生命周期中的循环过程:我们在数据准备和探索之间迭代。
例如,我们数据中适合进一步工程的特征是datetime字段。对于未来的预测,测量所采取的具体日期和时间对模型来说并不具有信息性。
然而,如果我们提取年份、月份、月份中的日期和一天中的小时作为新特征,那么模型可以捕捉到电力生成与不同时间周期之间的潜在关系。日期分解允许提出如下问题:年份、季节、特定月份等是否会影响电力生成?或者一天中的时间,早上、中午或晚上是否有任何影响?
我们可以将日期分解为以下新特征:
frame["date_year"] = train_df["datetime"].dt.year
frame["date_month"] = train_df["datetime"].dt.month
frame["date_day"] = train_df["datetime"].dt.day
frame["date_hour"] = train_df["datetime"].dt.hour
frame = frame.drop(columns=["tracking_id", "datetime"], axis=1)
如果在建模之后,我们发现这些特征缺乏信息,我们可以进一步使用时间字段来协助建模。未来要探索的方向包括基于特定时期的聚合或通过排序测量来创建时间序列,以研究电力生成随时间的变化趋势。
我们现在继续进行案例研究的 EDA 部分,以可视化和更好地理解我们的数据。
EDA
我们已经对数据集进行了一些探索性数据分析(EDA),以找到缺失值和异常值。对数据集进行 EDA 没有固定的方法;需要一些经验和创造力来指导这个过程。
除了获取对数据的洞察和理解之外,主要目标是尝试在数据中识别模式和关系。在这里,我们从一个相关性热图开始,以探索特征之间的直接相关性:
图 6.3 – 风力涡轮机数据集的相关性热图
图 6.3中的相关性热图显示了风速和大气温度、发动机温度、发电机温度和电机扭矩等发动机指标之间的一些显著相关性,以及我们日期特征与大气条件之间的较弱相关性。
值得注意的是,电机扭矩和发电机温度之间存在非常强的相关性。直观上,这是有道理的:如果电机产生更多的扭矩,它会产生更多的热量。由于扭矩是因果关系特征,我们可以考虑在建模时忽略发电机温度。
我们还可以看到发电量和发动机指标之间的相关性,包括电阻和风向。我们可以预期这些特征将对模型的性能产生重大影响。
我们还可以探索分类特征与发电量之间的相关性。涡轮机状态似乎对发电量影响很小(单独来看)。然而,云层级别有显著影响。将云层级别与发电量绘制成图,我们得到以下结果:
train_df.groupby("cloud_level")["windmill_generated_power(kW/h)"].mean().plot.bar()
图 6.4 – 不同云层下的平均发电量
如图 6.4所示,极低云层与发电量减少有很强的相关性。在进一步探索数据时,控制云层级别有助于确保云层级别不会主导任何出现的模式。
另一个有助于观察各种特征影响的可视化方法是散点图。绘制每个值使得通过视觉识别数据中的模式和聚类来识别特征变得简单。
接下来,我们提供了一些散点图的例子,这些图揭示了数据中的模式。
为了研究叶片角度可能对发电量产生的影响,我们可以创建以下散点图:
sns.scatterplot(x='blades_angle(°)', y='windmill_generated_power(kW/h)', hue='cloud_level', data=train_df)
在散点图中,我们还添加了云层级别的色调区分,这样我们可以直观地验证任何影响不是仅来自云层级别:
图 6.5 – 发电量(y 轴)与叶片角度(x 轴)的散点图
叶片角度散点图显示在图 6.5中。散点图表明,特定的叶片角度范围与发电量的增加相关:[0, 10]度和[65, 75]度(在另一方向上也是相反的)。基于树的算法也能模拟这种相关性。
另一个说明我们特征工程强大功能的例子是月份与发电量的散点图。我们再次通过不同色调控制云层级别:
sns.scatterplot(x='date_month',y='windmill_generated_power(kW/h)',hue='cloud_level',data=train_df)
图 6.6 – 发电量(y 轴)按月份(x 轴)的散点图
图 6*.6*显示,4 月至 9 月与发电量的显著下降相关。我们可以得出结论,这些月份风力涡轮机的位置不太 windy,其他来源将不得不补充电力生产的不足。通过分解我们的日期特征,我们使我们的学习算法能够利用这种相关性。
EDA 没有明确的目标。对于大型、复杂的数据集,分析可以深入到数据中,迭代地探索更深入的方面和细微差别,几乎无限期地进行。然而,有两个有助于确定数据是否已充分探索的合理性检查如下:
-
我们是否充分理解了每个特征的含义及其对模型输出的潜在影响?
-
数据是否已准备好进行建模?据我们所知,特征是否具有信息性,数据是否干净且无偏见,以及格式是否适合在它上进行训练?
现在我们继续对数据进行建模,利用前几章的技术构建一个优化良好的模型。
建模
建模的第一步是模型选择。最好的做法是首先使用简单的算法对数据进行建模,以验证我们的数据准备并建立基线。如果建模失败,使用简单算法更容易调试可能出错的地方或隔离可能引起问题的数据实例。
模型选择
对于我们的风力涡轮机数据,我们使用线性回归模型来建立基线并验证数据建模的适用性。
我们还训练了一个随机森林回归模型,作为与我们的 LightGBM 模型进行比较的基准。如果预算允许,使用不同的学习算法训练多个模型也是一个好的实践,因为特定问题可能更适合特定的算法。
最后,我们训练了一个 LightGBM 回归器作为我们的主要模型。
模型训练和评估
从 EDA 中,我们看到生成器温度是多余的(由于与电机扭矩的相关性)。因此,我们从训练数据中排除了它:
X = train_df.drop(columns=["windmill_generated_power(kW/h)", axis=1)
y = train_df["windmill_generated_power(kW/h)"]
与 LightGBM 不同,线性回归和 scikit-learn 的随机森林回归器都不能自动处理分类特征。
因此,我们使用 pandas 的get_dummies来对特征进行编码以进行训练。get_dummies操作执行一个称为0或1列的过程,因为有唯一值。相应的值用1(独热编码)标记每个模式,其他值用0标记。例如,考虑云层特征:有三个类别(中等、低和极低)。我们的数据集中中等云层的行将被编码为100(三个单独的列)。同样,低云层被编码为010,以此类推。
执行独热编码允许算法,如线性回归,仅支持数值列,以增加额外列的内存使用成本为代价来对数据进行建模。
如问题定义所述,我们将使用两个指标来评估模型:确定系数和 MSE。两者都是使用五折交叉验证计算的。
我们现在可以继续训练我们的线性、随机森林和 LightGBM 回归器:
X_dummies = pd.get_dummies(X)
linear = LinearRegression()
scores = cross_val_score(linear, X_dummies, y)
scores = cross_val_score(linear, X_dummies, y, scoring="neg_mean_squared_error")
forest = RandomForestRegressor()
X_dummies = pd.get_dummies(X)
scores = cross_val_score(forest, X_dummies, y)
scores = cross_val_score(forest, X_dummies, y, scoring="neg_mean_squared_error")
lgbm = lgb.LGBMRegressor(force_row_wise=True, verbose = -1)
scores = cross_val_score(lgbm, X, y)
scores = cross_val_score(lgbm, X_dummies, y, scoring="neg_mean_squared_error")
下表总结了每个模型的性能:
| 算法 | R² | MSE |
|---|---|---|
| 线性回归 | 0.558 | 2.261 |
| 随机森林 | 0.956 | 0.222 |
| LightGBM | 0.956 | 0.222 |
表 6.2 – Wind Turbine 数据集上五折交叉验证的性能指标
LightGBM 和随机森林回归器在四舍五入的 R² 和 MSE 分数上表现出几乎相同的表现。这两种算法都显著优于线性回归。
我们的模式表现非常好,绝对误差约为 471 W/h。然而,如果我们绘制训练模型的特征重要性,问题就很容易被发现。
图 6*.7* 展示了每个特征对我们 LightGBM 模型的相对重要性。
图 6.7 – 每个特征相对于我们的 LightGBM 模型的相对特征重要性
如我们从特征的重要性中可以看到,有三个特征突出:blades_angle、motor_torque 和 resistance。然而,对于其中两个特征,motor_torque 和 resistance,我们可以问:这些特征是导致生成的功率提高,还是它们是功率增加的结果?这些特征是目标泄露的例子,如以下所述。
目标泄露
目标泄露,通常称为“泄露”,是设计和训练机器学习模型时常见的陷阱。它发生在模型在训练过程中无意中获得了对目标变量(或目标变量的某些代理)的访问。因此,模型在训练过程中的表现可能看起来很令人印象深刻,但在新的、未见过的数据上表现不佳,因为它在训练过程中实际上“作弊”了。
泄露可能发生的一些常见例子如下:
-
基于时间的泄露:假设你正在尝试预测明天的股价。如果你在训练集中包含明天的数据(可能是无意中),这将导致泄露。同样,仅在事后可用的数据(如所有股票的汇总数据)也是基于时间的泄露的另一个例子。
-
预处理错误:这些发生在你使用包括训练集和测试集的统计数据执行特定操作,如缩放或归一化时。
-
错误的数据分割:对于时间序列数据,使用简单的随机分割可能会导致训练集中出现未来的数据。
-
受污染的验证集:有时,在创建验证集或测试集时,一些数据可能重叠或与训练数据非常接近,导致乐观且不具有代表性的验证分数。
在我们的例子中,motor_torque和resistance是时间相关的泄漏的例子:这两个指标只能在发电后才能测量,这正是我们试图预测的。这也说明了进行基线训练测试的重要性,因为像这些问题可能不容易在事先发现。
我们通过从我们的数据集中删除特征来修复这个错误。然后我们可以继续模型调优以进一步提高模型性能。
模型调优
我们使用 Optuna 来执行我们的参数优化研究。我们将利用 Optuna 的树结构帕累托估计器(TPE)采样算法和 Hyperband 剪枝以提高效率。我们定义我们的目标函数所需的参数、剪枝回调,并测量均方误差:
def objective(trial):
boosting_type = trial.suggest_categorical("boosting_type", ["dart", "gbdt"])
lambda_l1 = trial.suggest_float(
'lambda_l1', 1e-8, 10.0, log=True),
...
pruning_callback = optuna.integration.LightGBMPruningCallback(trial, "mean_squared_error")
model = lgb.LGBMRegressor(
...
callbacks=[pruning_callback],
verbose=-1)
scores = cross_val_score(model, X, y, scoring="neg_mean_squared_error")
return scores.mean()
我们随后使用 TPE 采样器、Hyperband 剪枝和 200 个试验的优化预算创建我们的 Optuna 研究:
sampler = optuna.samplers.TPESampler()
pruner = optuna.pruners.HyperbandPruner(
min_resource=20, max_resource=400, reduction_factor=3)
study = optuna.create_study(
direction='maximize', sampler=sampler,
pruner=pruner
)
study.optimize(objective, n_trials=200, gc_after_trial=True, n_jobs=-1)
通过 Optuna 找到的优化参数,我们进一步提高了 LightGBM 模型在运行中的性能,将其 R²提高到0.93,均方误差为0.21。由于 Optuna 研究的随机性质,您的结果可能会有所不同。
在训练出一个优化的模型后,我们可以继续数据科学流程的下一阶段:部署和报告。
模型部署
我们现在可以使用我们的训练模型对未见数据做出预测。接下来的章节将重点介绍作为 MLOps 过程一部分的各种部署和监控模型的方法。
然而,使用我们的模型最简单的方法是保存模型并编写一个简单的脚本,该脚本加载模型并做出预测。
我们可以使用标准的 Python 序列化或 LightGBM API 来保存我们的模型。在这里,我们展示了使用标准的 Python 工具:
joblib.dump(model, "wind_turbine_model.pkl")
加载模型并做出预测的简单脚本如下:
def make_predictions(data):
model = joblib.load("wind_turbine_model.pkl")
return model.predict(data)
if __name__ == '__main__':
make_predictions(prepare_data(pd.read_csv("wind-turbine/test.csv")))
重要的是,我们必须为任何我们想要预测的数据重复数据准备,以添加工程化特征并删除未使用的列。
沟通结果
数据科学流程的最后一步是沟通结果。数据科学家通常会编制一份包含显著发现和可视化的报告,向利益相关者展示结果。
报告看起来会与这个案例研究的撰写类似。我们可能会展示我们找到的特征之间的相关性,例如,月份与发电量的相关性。我们还会突出数据中的问题,如异常值和缺失值,以改善未来的数据收集工作。
我们将进一步突出对模型重要的特征,以便风力涡轮机可以优化以最大化发电量。
专注于报告的质量。使用精心设计和详细的可视化以及其他支持材料,而不是仅仅依赖文本。信息图表或交互式图表可能比详细的撰写更有帮助。检查您的写作错误,并在发送之前确保报告经过校对。
报告的内容应解决问题陈述中定义的问题。任何被测试过的假设都必须在报告中回答。但是,报告也强烈依赖于并应针对您的受众进行调整。例如,如果您的受众是商业高管,请包括他们能理解的内容,并回答他们可能有的问题,这些问题将围绕您发现的业务影响为中心。
现在我们来看一个分类问题的案例研究。我们表明,尽管每个数据集都是独特的,并且具有特定的挑战,但整体的数据科学流程仍然是相同的。
使用 LightGBM 对个人信用评分进行分类
我们的第二个案例研究是一个针对个人信用评分分类的问题。数据集可在www.kaggle.com/datasets/parisrohan/credit-score-classification?datasetId=2289007找到。
与前一个问题相比,数据集显著更大,并且存在独特的数据格式问题。为了简洁,我们不会像以前的问题那样详细地介绍解决方案(因为大部分工作都是相同的),但端到端解决方案可在github.com/PacktPublishing/Practical-Machine-Learning-with-LightGBM-and-Python/tree/main/chapter-6/credit-score-classification.ipynb找到。
问题定义
数据集包含 10 万行和 27 列,代表个人的人口和财务信息,包括信用评分评级。数据包括有关个人收入、贷款数量、支付行为和投资的信息。信用评分可能被评为良好、标准或较差。
我们的任务是分析数据并构建一个模型,该模型能够准确地对未见过的个人的信用评分进行分类。预测质量使用分类准确率和 F1 分数来衡量。
数据收集
数据来自一家美国金融机构的客户数据库。14 至 65 岁的个人构成了数据集的一部分。没有记录表明采样特定人口统计特征(低收入群体、年龄或种族群体)存在偏差,但这必须得到验证。没有收集额外的数据。
数据准备
如前所述,我们首先从简单的数据探索任务开始,以确定数据的清洁度。我们首先检查数据结构和类型:
train_df.info()
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 ID 100000 non-null object
1 Customer_ID 100000 non-null object
2 Month 100000 non-null object
3 Name 90015 non-null object
4 Age 100000 non-null object
5 SSN 100000 non-null object
...
25 Payment_Behaviour 100000 non-null object
26 Monthly_Balance 98800 non-null object
27 Credit_Score 100000 non-null object
我们注意到某些特征存在缺失值。此外,我们期望许多特征是数值型(如年收入、贷款数量等),但它们被解释为对象而不是整数或浮点数。这些特征必须被强制转换为数值特征。
我们还检查了特征的描述性统计和重复行。发现有两个特征有异常值,年龄和银行账户数量,需要清理。
值得注意的是,Type_of_Loan字段包含一个以逗号分隔的列表,包括连词,列出每个个体的贷款类型。例如,“student_loan, mortgage_loan, and personal_loan”。建模算法无法将贷款类型作为字符串的一部分提取出来。我们必须构建新的字段来启用有效的建模。
数据清理
我们现在可以继续清理数据。总的来说,以下问题需要解决:
-
在适当的地方将对象列强制转换为数值列
-
处理年龄、银行账户数量和月度余额列中的异常值
-
为贷款类型构建新特征
-
处理缺失值和重复行
将列强制转换为数值列
数据集的主要问题之一是应表示数值的列中发现的混合类型,但由于错误值,pandas 将其解释为对象。例如,Annual_Income列包含100000.0_之类的值,然后被解释为字符串。
为了清理并将特征转换为数字,我们首先使用正则表达式删除字符符号:
frame[col] = frame[col].astype(str).str.replace(r'[^\d\.]', '', regex=True)
这使我们能够使用 pandas 将列强制转换为数值特征,将任何错误(空值)转换为np.nan值:
frame[col] = pd.to_numeric(frame[col], errors="coerce")
Credit_History_Age特征需要我们更多的工作。年龄使用自然语言指定,例如“12 年 3 个月”。在这里,我们使用 Python 字符串处理将年和月转换为浮点数:
def clean_credit_age(age):
if age == 'nan':
return np.nan
if not "Years" in age:
return age
years, months = age.split(" Years and ")
months = months.replace(" Months", "")
return int(years) + int(months) / 12
分割分隔符分隔的字符串
如前所述,Type_of_Loan特征是个人拥有的贷款类型的逗号分隔列表。尽管我们可以以多种方式处理这个问题,但最有帮助的技术是解析字段并构建一个布尔数组列,指示个人拥有哪些贷款。
有八种独特的贷款类型,以及如果贷款类型未指定,有一个特定类别。这些是Auto Loan、Credit-Builder Loan、Debt Consolidation Loan、Home Equity Loan、Mortgage Loan、Payday Loan、Personal Loan和Student Loan。
我们的编码策略将按以下方式处理特征。我们创建九个新的列(每个贷款类型和一个未指定的),如果个体有那种贷款类型,则将该列设置为 true。例如,这里我们有三个连接的贷款描述:
"Home Equity Loan, and Payday Loan"
"Payday Loan, Personal Loan"
"Student Loan, Auto Loan, and Debt Consolidation Loan"
表 6.3显示了这些示例的编码结果,其中如果个体有那种类型的贷款,则设置 true 标志。
| Auto | Credit-Builder | Debt Cons. | Home Equity | Mortgage | Payday | Personal | Student | Unspecified |
|---|---|---|---|---|---|---|---|---|
| F | F | F | T | F | T | F | F | F |
| F | F | F | F | F | T | T | F | F |
| T | F | T | F | F | F | F | T | F |
表 6.3 – 贷款类型列编码客户贷款类型
我们可以利用 pandas 的字符串工具来完成前面的编码,如下例所示:
frame["auto_loan"] = frame["Type_of_Loan"].str.lower().str.contains("auto loan").astype(bool)
异常值和缺失值
我们遵循之前相同的一般策略:使用描述性统计来填补缺失值,并在可能的情况下,将异常值设置为使用领域知识定义的边界。
删除重复行。
在异常值方面,年龄、银行账户数量和月余额特征具有异常值(我们通过均值、标准差和分布图进行确认)。我们将这些特征的异常值设置为上限:
frame.loc[frame["Age"] > 65, "Age"] = 65
frame.loc[frame["Num_Bank_Accounts"] > 1000, "Num_Bank_Accounts"] = 1000
frame.loc[frame["Monthly_Balance"] > 1e6, "Monthly_Balance"] = np.nan
在数据清洗后,我们可以验证所有特征都有正确的类型,并且缺失值已得到处理:
train_df.info()
train_df.isnull().sum()
train_df[train_df.duplicated()]
我们可以使用清洗后的数据集进行更彻底的探索性分析。
EDA
接下来,我们强调在 EDA 过程中发现的一些模式。
如问题描述所述,我们需要验证数据中是否存在任何潜在的偏差。
我们首先可视化客户的年龄:
sns.histplot(train_df["Age"], bins=20)
图 6.8 – 按年龄统计客户数量的直方图
图 6.8显示所有年龄段都有数据,数据主要围绕中年人呈正态分布。
我们还检查月收入。低收入群体数据缺失可能表明少数族裔被排除在外:
sns.histplot(train_df["Monthly_Inhand_Salary"], bins=30)
图 6.9 – 按月到手工资统计客户数量的直方图
如图 6.9所示,月收入遵循预期的分布,低收入群体有很好的代表性。
我们再次可视化数值特征的关联热图,以突出和直接关联:
图 6.10 – 信用评分数据集的关联热图
两个显著的关联值得关注:月余额和月工资,以及逾期债务和还款延迟。进一步可视化这些关联表明,客户的月余额随着工资的增加而增加,并且不良的信用评分与较低的余额和工资相关。
逾期债务和信用评分之间存在类似的关联:债务的增加与不良的信用评分相关,反之亦然。分析确认我们的模型应该能够捕捉到这两个关联。
最后,并且非常重要,我们还要检查数据集的类别分布:
sns.histplot(train_df["Credit_Score"], bins=30)
图 6.11 – 信用评分数据集类别分布的直方图
我们的数据集类别分布如图6.11所示。如图所示,存在显著的类别不平衡。在建模之前,我们必须解决这种不平衡问题。
建模
如前所述,我们首先进行模型选择。
模型选择
由于数据集的大小和复杂性,线性模型不太可能表现良好。因此,我们使用常规决策树作为基线模型,并包括随机森林进行比较。
模型训练和评估
我们几乎准备好训练我们的模型了,但一个问题仍然存在:我们必须解决类别不平衡问题。
处理类别不平衡
类别不平衡可能会使任何训练好的模型偏向多数类或多个类别。尽管基于树的算法在处理类别不平衡方面比大多数其他学习算法更擅长,但在建模之前解决类别不平衡问题仍然是最佳实践。
通常,以下策略可以用来处理类别不平衡问题:
-
重采样技术:
-
上采样:这涉及到增加少数类中的样本数量以匹配多数类。一种常见的技术是合成少数类过采样技术(SMOTE),其中基于现有样本创建新的样本。
-
下采样:这涉及到减少多数类中的样本数量以匹配少数类。这种方法的潜在风险是丢失可能有价值的数据。
-
-
class_weight(用于多类)和scale_pos_weight(用于二分类)参数。这些参数的应用示例在第四章,比较 LightGBM、XGBoost 和深度学习中给出。 -
数据增强:这涉及到通过向现有实例添加小的扰动来在数据集中创建新的实例。这种方法在图像分类任务中很常见。
-
使用适当的评估指标:在类别不平衡的情况下,准确率往往具有误导性。相反,如精确率、召回率、F1 分数、ROC 曲线下的面积(AUC-ROC)和混淆矩阵等指标可以提供对模型性能的更全面了解。
在我们的案例研究中,我们已经采用了对类别不平衡具有鲁棒性的评估指标。对于这个问题,我们使用 SMOTE,一种过采样技术,来平衡我们的类别,同时保留我们的数据。
SMOTE
SMOTE 是为了克服简单上采样少数类的某些不足而开发的,这可能导致由于实例的精确复制而过度拟合*[1]*。SMOTE 不是简单地复制少数样本,而是创建与少数类中现有样本相似但不完全相同的合成或“假”样本。
SMOTE 算法按以下步骤进行。通过选择接近少数类样本的样本之间的点来合成新的样本点,称为合成样本。具体来说,对于每个少数类样本,SMOTE 计算其 k 个最近邻,选择这些邻居中的一个,然后将样本与其所选邻居的特征向量之间的差异乘以 0 到 1 之间的随机数,并将这个值加到原始样本上,以创建一个新的、合成的样本。
通过创建合成示例,SMOTE 提供了一种更稳健的解决方案来解决不平衡问题,鼓励模型绘制更具泛化能力的决策边界。然而,需要注意的是,尽管 SMOTE 可以提高不平衡数据集上模型的性能,但它并不总是最佳选择。例如,如果少数样本在特征空间中不够接近,它可能会引入噪声,导致类别重叠。与所有采样技术一样,使用交叉验证或单独的验证集仔细评估 SMOTE 对模型性能的影响是至关重要的。
SMOTE 过采样在 imblearn Python 库中实现。我们可以如下拟合和重采样我们的数据:
X = train_df.drop(columns=["Credit_Score"], axis=1)
X_dummies = pd.get_dummies(X)
y = train_df["Credit_Score"]
smote = SMOTE(sampling_strategy='auto')
return smote.fit_resample(X_dummies, y)
图 6*.12* 展示了使用 SMOTE 重采样后的数据集类别分布。如图所示,类别现在完全平衡。
图 6.12 – 使用 SMOTE 重采样后的信用评分数据集类别分布直方图;类别现在平衡
训练和评估
我们现在可以继续进行建模。下表展示了我们的决策树分类器、随机森林和 LightGBM 模型使用默认参数的运行结果。
| 算法 | 准确率 | F1 分数 |
|---|---|---|
| 决策树 | 59.87% | 0.57 |
| 随机森林 | 69.35% | 0.67 |
| LightGBM | 70.00% | 0.68 |
表 6.4 – 在信用评分数据集上五折交叉验证的性能指标
如 表 6.4 所示,LightGBM 模型的性能最佳,略优于随机森林模型的准确率。两种算法的性能都优于决策树基线。LightGBM 模型的训练速度也最快,比随机森林模型快七倍以上。
我们现在继续进行 LightGBM 模型的参数优化。
模型调优
与之前的案例研究类似,我们使用 Optuna 进行参数优化。我们再次使用 TPE 采样器,优化预算为 50 次试验。
模型部署和结果
我们的模式现在已准备就绪,可以部署到我们选择的平台上。可能的部署选项包括围绕我们的模型构建一个 Web API,使用PostgresML等工具进行部署,或者使用如AWS SageMaker这样的云平台。这些以及其他选项将在接下来的章节中详细讨论。
我们还可以将模型作为数据科学报告的一部分使用。参见上一节,了解撰写良好报告的详细信息。请记住沟通数据科学结果最重要的方面,如下所示:
-
总是以无偏见和公平的方式报告结果
-
考虑你的受众,并关注报告中提供他们最大价值的细节
摘要
本章介绍了两个案例研究,说明了如何使用 LightGBM 应用数据科学流程。详细讨论了数据科学生命周期和典型的组成部分步骤。
以风力涡轮机发电为例,介绍了一个在生命周期中处理数据问题的案例研究。详细讨论了特征工程以及如何处理异常值。进行了一次示例性探索性数据分析,并提供了用于可视化的样本。同时展示了模型训练和调优,以及导出和使用模型作为程序的基本脚本。
还介绍了一个涉及多类信用评分分类的第二个案例研究。再次遵循数据科学流程,特别关注数据清洗和数据集中类别不平衡问题。
下一章讨论了 AutoML 框架 FLAML,并介绍了机器学习管道的概念。
参考文献
| *[1] | N. V. Chawla, K. W. Bowyer, L. O. Hall, and W. P. Kegelmeyer, “SMOTE: Synthetic Minority Over-sampling Technique,” Journal of Artificial Intelligence Research, vol. 16, p. 321–357, 六月 2002. |
|---|
第七章:使用 LightGBM 和 FLAML 进行 AutoML
在上一章中,我们讨论了两个案例研究,展示了如何处理数据科学问题的端到端示例。在典型的数据科学生命周期中,通常最耗时的工作是准备数据、寻找正确的模型以及调整模型。
本章探讨了自动机器学习的概念。自动机器学习系统旨在自动化机器学习生命周期的某些或全部部分。我们将探讨 FLAML,这是一个库,使用高效的超参数优化算法来自动化模型选择和调优步骤。
最后,我们将通过一个案例研究来展示如何使用 FLAML 和另一个名为 Featuretools 的开源工具。我们将讨论和展示 FLAML 的实际应用,并展示 FLAML 的零样本 AutoML 功能,该功能完全绕过了调优过程。
本章的主要内容包括:
-
自动机器学习简介
-
FLAML 用于 AutoML
-
案例研究 – 使用 FLAML 和 LightGBM
技术要求
本章包含了一些示例和代码片段,展示了如何使用 FLAML 和 LightGBM 进行 AutoML 应用。关于设置本章所需环境的完整示例和说明可在github.com/PacktPublishing/Practical-Machine-Learning-with-LightGBM-and-Python/tree/main/chapter-7找到。
自动机器学习
自动机器学习(AutoML)是一个新兴领域,旨在自动化机器学习工作流程的复杂方面,从而更高效、更易于部署机器学习模型。AutoML 的出现反映了人工智能和机器学习技术的日益复杂化,以及它们在各个行业和研究领域的渗透。它旨在减轻数据科学过程中的某些复杂、耗时的工作,使机器学习技术的应用更加广泛和易于获取。
对于熟悉机器学习和数据科学流程的软件工程师来说,机器学习模型的日益复杂性和算法宇宙的不断扩大可能构成重大挑战。构建一个稳健、高性能的模型需要大量的专业知识、时间和计算资源来选择合适的算法、调整超参数以及进行深入的比较。AutoML 作为解决这些挑战的解决方案应运而生,旨在自动化这些复杂且劳动密集型的工作。
AutoML 还有助于民主化机器学习领域。通过抽象化数据工程和模型构建及调优的一些复杂性,AutoML 使得那些在机器学习方面经验较少的个人和组织能够利用这些强大的技术。因此,机器学习可以在更广泛的背景下发挥作用,更多个人和组织能够部署机器学习解决方案。
AutoML 系统在复杂度方面各不相同。尽管所有 AutoML 系统都旨在简化机器学习(ML)工作流程,但大多数系统和工具仅关注工作流程的一部分。通常,数据预处理、特征工程、模型选择和超参数调整等步骤被自动化。这种自动化可以节省时间,并通过系统地探索更全面的选项集来提高机器学习模型的鲁棒性和性能,这些选项可能由于人为偏见或时间限制而被忽视或未探索。
自动化特征工程
如在第第六章《使用 LightGBM 解决现实世界数据科学问题》中所述,数据清洗和特征工程是机器学习工作流程的关键部分。它们涉及处理不可用数据、处理缺失值以及创建可以输入到模型中的有意义的特征。手动特征工程可能特别具有挑战性和耗时。AutoML 系统旨在有效地处理这些任务,实现自动特征提取和转换,从而产生更鲁棒的模型。
数据清洗自动化通常是通过遵循处理异常值和缺失值等问题的特定知名技术来实现的。在之前的章节中,我们手动应用了一些这些技术:异常值可以通过统计测试进行测试,并截断或截顶。缺失值通常使用描述性统计量,如平均值或众数进行插补。AutoML 系统要么使用启发式算法和测试来选择最佳的数据清洗技术,要么通过训练模型来采取多种最佳方法和测试。
自动化特征工程的方法通常相似:将许多可能的转换应用于所有现有特征,并在建模后测试生成的特征的有用性。以下案例研究中可以找到转换如何生成特征的示例。
自动化特征工程的另一种方法是使用基于特征数据类型的规则提取特征。例如,可以从日期字段中提取日期、周、月和年。或者可以计算单词或句子特征的字符数、词元、词干或嵌入。
正如你可能注意到的,特征工程自动化技术的应用依赖于手头特征的技术信息,通常包括数据类型,以及与其他特征的关联和关系。重要的是,在创建新特征时没有应用领域知识。这突显了 AutoML 的一个缺点:它无法处理需要人类专业知识的特定、领域驱动型决策。例如,考虑一个包含空腹血糖特征的糖尿病数据集。医学专业人士(领域专家)知道,空腹血糖在 100 到 125 mg/dL 之间的人被认为是糖尿病前期,任何更高都被认为是糖尿病患者。这个连续特征可以被工程化为特定的类别:正常、糖尿病前期和糖尿病患者,从而简化了建模数据。这种类型的转换是无法通过 AutoML 系统实现的。
自动化模型选择和调整
AutoML 特别有用的领域包括模型选择和超参数调整。鉴于可用的算法众多,为特定数据集和问题选择最佳算法可能会令人望而却步。AutoML 系统使用各种技术,包括贝叶斯优化和元学习,来选择最佳模型。它们还自动调整超参数,以最大化模型性能。
AutoML 系统可以提供自动交叉验证,降低过拟合的风险,并确保模型对未见数据的泛化能力。一旦选定了最佳模型并进行了训练,许多 AutoML 工具还可以帮助部署模型,使其可用于对新数据的推理。
除了初始模型部署之外,一些 AutoML 解决方案在持续模型监控和维护方面也提供了价值。随着现实世界数据的发展,模型可能会出现漂移,其性能可能会下降。AutoML 可以帮助监控模型性能,并在需要时重新训练模型,确保您的机器学习系统在长期内保持有效。
使用 AutoML 系统的风险
如前所述,AutoML 系统通常不使用领域知识来辅助特征工程、模型选择或其他自动化任务。相反,它们采用一种试错的方法。
一些 AutoML 系统的“黑盒”特性也可能使得解释系统做出的决策变得具有挑战性,这使得它们对于需要高解释性的应用不太适合。
因此,仍然非常重要的是要有一个数据科学家或领域专家参与其中,与 AutoML 系统协同工作,以识别并利用领域知识可以带来更好模型的机遇。然而,AutoML 系统有时会阻碍数据科学家,而不是通过在科学家和数据之间增加一个额外的层次来使他们能够发挥作用。
我们已经看到了一个自动化机器学习框架的实际应用。在第五章中讨论的 Optuna,使用 Optuna 进行 LightGBM 参数优化,是一个专注于超参数调整的自动化机器学习框架的例子。在下一节中,我们将讨论另一个自动化机器学习框架:FLAML。
介绍 FLAML
FLAML(快速轻量级自动化机器学习)是由微软研究院开发的 Python 库*[1]*。它旨在自动生成高质量的机器学习模型,降低计算成本。FLAML 的主要目标是最大限度地减少调整超参数和识别最佳机器学习模型所需的资源,使自动化机器学习更加易于访问和成本效益,尤其是对于预算有限的用户。
FLAML 提供了一些关键特性,使其与众不同。其中之一是它的效率。它为机器学习任务提供了一种快速轻量级的解决方案,最大限度地减少了所需的时间和计算资源。它在不影响所产生模型质量的情况下实现了这一点。FLAML 还强调其在各种机器学习算法和不同应用领域中的多功能性。
FLAML 的高效核心在于其新颖且成本效益高的搜索算法。这些算法智能地探索超参数空间,最初专注于“低成本”配置。随着对搜索空间的深入了解,它逐渐探索更多“高成本”配置。这确保了平衡的探索和利用,在用户指定的时间和资源预算内提供优化的模型。
FLAML 在模型选择过程中也表现出色。它支持各种机器学习算法,包括 XGBoost、LightGBM、CatBoost、随机森林和各种线性模型。该库可以自动为给定数据集选择最佳算法并优化其超参数,为用户提供一个无需大量手动干预的优化模型。
FLAML 提供了一个简单直观的 API,可以无缝集成到现有的基于 Python 的数据科学和机器学习工作流程中。用户指定数据集、时间预算(以秒为单位)和优化任务,FLAML 处理其余部分。这种用户友好性和效率使其成为机器学习初学者和希望加快工作流程的资深从业者的一项实用选择。
FLAML 效率背后的新意来自于其超参数优化(HPO)算法。FLAML 提供了两种 HPO 算法:成本节约优化和BlendSearch。
成本节约优化
成本节约优化(CFO)是一种局部搜索方法,利用随机直接搜索来探索超参数空间*[2]*。CFO 算法从一个低成本的超参数配置开始(例如,对于 LightGBM,一个低成本配置可能只有少数提升树)。它在超参数空间中随机移动固定次数的迭代,朝着更高成本的参数区域前进。
CFO 的步长是自适应的,这意味着如果连续几次迭代没有改进,算法会降低步长。这样做意味着不会在成本高昂的方向上采取大步长。
CFO 还利用随机重启。作为一个局部搜索算法,CFO 可能会陷入局部最优。如果没有任何进展且步长已经很小,算法会在一个随机点重新启动。
总结来说,CFO 快速(使用大步长)尝试在搜索空间中达到更有希望的领域,尽可能少地使用优化预算(通过在低成本区域开始)。当优化预算允许时,CFO 会继续搜索,如果出现停滞,会在随机区域重新启动。FLAML 允许用户以秒为单位设置优化预算。
BlendSearch
FLAML 为 BlendSearch 中的 CFO 算法提供了一种替代方案。BlendSearch 与 CFO 的不同之处在于,它使用多线程方法*[3]*同时运行全局和局部搜索过程。
与 CFO 类似,BlendSearch 从一个低成本配置开始,并继续进行局部搜索。然而,与 CFO 不同,BlendSearch 不会等待局部搜索停滞后再探索新的区域。相反,全局搜索算法(如贝叶斯优化)会不断提出新的起始点。起始点会根据它们与现有点的距离进行过滤,并按成本优先排序。
BlendSearch 的每次迭代都会根据前一次迭代的表现来决定是继续进行局部搜索还是从新的全局搜索点开始。与 CFO 类似,全局搜索方法提出的配置会经过可行性验证。
由于 BlendSearch 使用全局优化,因此它不太可能陷入局部最小值。如果超参数搜索空间非常复杂,建议使用 BlendSearch 而不是 CFO。通常,先尝试 CFO,如果 CFO 遇到困难,再切换到 BlendSearch 是个不错的主意。
FLAML 局限性
尽管 FLAML 有其优势,但也存在局限性。该库的自动化流程可能不会始终优于专家的手动调整,尤其是在处理复杂、特定领域的任务时。此外,与其他 AutoML 解决方案一样,生成的模型的可解释性可能具有挑战性,尤其是在处理如提升树或神经网络等模型时。
FLAML 只执行 ML 过程中的模型选择和调整部分。这些是模型开发中最耗时的部分之一,但 FLAML 不提供执行特征工程或数据准备的功能。
以下部分展示了使用 FLAML 与 LightGBM 的案例研究,展示了日常用例、不同的优化算法以及 FLAML 的零样本 AutoML。
案例研究 - 使用 FLAML 与 LightGBM
我们将使用前一章中的风力涡轮机数据集作为案例研究。数据集像以前一样进行了清理,填补了缺失值,并将异常值限制在适当的范围内。然而,我们在特征工程上采取了不同的方法。为了进一步探索 AutoML,我们使用了一个名为 Featuretools 的开源框架。
特征工程
Featuretools (featuretools.alteryx.com/en/stable/#) 是一个用于自动特征工程的开源框架。具体来说,Featuretools 非常适合转换关系数据集和时间序列数据。
如前所述,自动特征工程工具通常使用特征的组合转换来为数据集生成新特征。Featuretools 通过其深度特征合成(DFS)过程支持特征转换。
例如,考虑一个在线客户网络会话的数据集。此类数据集中可能有用的典型特征包括客户访问网站的会话总数,或客户注册的月份。使用 Featuretools 和 DFS,可以通过以下代码实现(感谢featuretools.alteryx.com/):
feature_matrix, feature_defs = ft.dfs(
entityset=es,
target_dataframe_name="customers",
agg_primitives=["count"],
trans_primitives=["month"],
max_depth=1,
)
feature_matrix
这里应用了两种转换:一个是“month”的转换,另一个是“count”的聚合。通过这些转换,会自动从客户的任何日期(如加入日期)中提取月份,并为每个客户(如会话数或交易数)计算聚合计数。Featuretools 提供了一套丰富的转换和聚合功能。完整的列表可在 https://featuretools.alteryx.com/en/stable/api_reference.xhtml 上找到。
让我们看看如何使用 Featuretools 为风力涡轮机数据集的特征进行工程。
使用 Featuretools 和风力涡轮机数据集
我们必须为我们数据集执行两个特征工程任务:为日期时间字段生成特征,并对分类特征进行编码。为了开始,我们为我们的数据创建一个EntitySet:
es = ft.EntitySet(id="wind-turbine")
es = es.add_dataframe(
dataframe_name="wind-turbine",
dataframe=df,
index="tracking_id"
)
EntitySet告诉 Featuretools 框架我们在数据中处理的数据实体和关系。在先前的例子中,客户是一个实体示例;对于这个案例,它是风力涡轮机。然后我们传递数据框和用作索引的列。
然后我们应用dfs和encode_features来为我们数据集的特征进行工程:
feature_matrix, feature_defs = ft.dfs(
entityset=es, target_dataframe_name="wind-turbine",
trans_primitives=["day", "year", "month", "weekday"],
max_depth=1)
feature_matrix_enc, features_enc = ft.encode_features(
feature_matrix, feature_defs)
上述代码提取了每个风力涡轮机测量值的日、年、月和星期几。特征编码随后自动将数据集中的分类特征进行 one-hot 编码,包括新的日期字段。
下面的内容是从数据集列列表的摘录,显示了 Featuretools 创建的一些列:
...
'cloud_level = Low',
'cloud_level = Medium',
'cloud_level = Extremely Low',
'cloud_level is unknown',
'MONTH(datetime) = 1',
'MONTH(datetime) = 2',
...
'MONTH(datetime) = 8',
'MONTH(datetime) = 9',
'MONTH(datetime) = 11',
'MONTH(datetime) is unknown',
...
'YEAR(datetime) = 2019',
'YEAR(datetime) = 2018',
'YEAR(datetime) is unknown'
注意分类特征的独热编码:每个值现在都分割成单独的列。这包括未知值的列(例如,YEAR(datetime) is unknown),这说明了处理分类特征中缺失值的另一种方法。我们不是通过使用诸如众数之类的值来填充值,而是有一个列向模型(true或false)发出信号,表示该值缺失。
自动特征工程将我们的列数从 22 列增加到 66 列。这说明了自动特征工程和 AutoML 的另一个一般性警告:自动化可能导致数据集过于复杂。在第六章《使用 LightGBM 解决现实世界数据科学问题》中,我们可以根据对学习算法的理解有选择性地编码特征。LightGBM 可以自动处理分类特征;因此,如果只使用 LightGBM 作为学习算法,则独热编码是多余的。
此外,日期字段可以以数值方式处理。通过应用我们对问题和算法的了解,我们可以降低学习问题的维度,从而简化它。自动系统的易用性必须与专家特征工程的手动工作相平衡,这可能在以后节省时间。
数据集现在已准备好进行模型开发;我们仅用两行代码就完成了数据集的特征工程。
FLAML AutoML
我们现在将探讨模型选择和调优。我们将使用 FLAML 比较五种不同的模型:LightGBM、RandomForest、XGBoost、ExtraTrees 以及 XGBoost 的有限深度版本。我们还想找到最佳模型的最佳参数。整个流程只需两行代码即可使用 FLAML 完成:
automl = flaml.AutoML()
automl.fit(X, y, task="regression", time_budget=60)
上一段代码在 60 秒的时间预算内拟合了一个最优回归模型。FLAML 自动使用保留集来计算验证结果,然后使用默认的 CFO 作为调优器进行优化。
AutoML 类提供“面向任务的 AutoML”。用户设置学习任务,FLAML 完成剩余工作。支持的任务包括:分类、回归、时间序列预测和时间序列分类、排序以及与 NLP 相关的任务,如摘要和词元分类。
fit函数的调用是可定制的。例如,我们可以这样定制它:
automl = flaml.AutoML()
custom_hp = {
"learning_rate": {
"domain": flaml.tune.loguniform(0.0001, 0.05)
}
}
automl.fit(X, y, task="regression", time_budget=120,
metric="mse",
estimator_list=['lgbm', 'xgboost', 'rf'],
custom_hp={
"lgbm": custom_hp
},
hpo_method="bs")
在这里,我们通过显式声明学习率作为一个在范围内对数缩放的均匀变量来自定义超参数搜索空间。设置参数搜索空间的其它选项包括均匀抽样、随机整数抽样以及用于分类参数的选择性抽样。
此外,我们将估计器列表设置为仅关注三种建模算法:LightGBM、随机森林和 XGBoost。最后,我们可以自定义 HPO 算法,在这里我们将其设置为 BlendSearch,它使用之前讨论过的多线程优化方法。
完整的自定义列表可在 microsoft.github.io/FLAML/docs/… 找到。
一旦调用 fit,我们可以像使用任何其他模型一样使用 AutoML 训练的模型。FLAML 提供了类似 scikit-learn 的 API 用于预测和基于概率的预测(用于分类问题)。
以下代码从给定数据创建预测并计算指标和特征重要性:
y_pred = automl.predict(X)
print(f"r2: {1 - sklearn_metric_loss_score('r2',
y_pred, y)}")
print(f"MSE: {sklearn_metric_loss_score('mse',
y_pred, y)}")
r2: 0.9878605489721696
MSE: 0.08090827806554425
我们还可以通过调用以下代码来获取获胜模型和每个试验模型的最佳超参数配置:
print(automl.best_config)
print(automl.best_config_per_estimator)
print(automl.time_to_find_best_model)
FLAML 的一个最终显著特点是零样本 AutoML,它完全绕过了模型调整的需求。
零样本 AutoML
零样本 AutoML 是 FLAML 的一个功能,其中不执行超参数优化。相反,通过分析算法在广泛数据集上的性能,离线确定合适的超参数配置。该过程可以描述如下:
-
在构建模型之前:
-
使用 AutoML 在许多数据集上训练模型
-
将所有数据集的超参数配置、评估结果和元数据存储为零样本解决方案。
-
-
当为新的问题构建模型时:
-
使用 FLAML 分析新数据集与零样本解决方案结果,以确定合适的超参数。
-
使用超参数在新数据集上训练模型。
-
第一步对于给定的模型类型(例如 LightGBM)只执行一次。之后,对于任何新的问题都可以构建新的模型,无需调整。解决方案是“零样本”,因为对于新的数据集,在第一次拟合时使用了合适的参数。
FLAML 的零样本 AutoML 方法具有许多优点:
-
如前所述,不涉及调整,在解决新问题时节省了大量计算努力和时间。
-
由于不需要调整,因此也不需要验证数据集,更多的数据可以用于训练。
-
用户需要的参与更少。
-
通常,不需要更改代码,正如我们接下来将要看到的。
当然,为模型类型创建零样本解决方案仍然很困难,需要各种数据集和大量计算来训练许多模型。幸运的是,FLAML 为许多流行的模型提供了预训练的零样本解决方案,包括 LightGBM、XGBoost 和 scikit-learn 的随机森林。
要使用零样本解决方案,将常规的 LightGBM 导入替换为 FLAML 包装的版本:
from flaml.default import LGBMRegressor
zs_model = LGBMRegressor()
zs_model.fit(X, y)
调用 fit 会分析 X 中的数据,选择合适的参数,并使用这些参数训练模型。训练只执行一次,不进行任何调整。
这标志着 FLAML 案例研究的结束。正如我们所见,FLAML 提供了一个直观的 API,用于复杂的模型选择和调优功能,这在处理 ML 问题时可以节省很多精力。
概述
总结来说,本章讨论了 AutoML 系统和它们的用途。我们讨论了自动化特征工程、模型选择和调优的典型方法。我们还提到了使用这些系统可能存在的风险和注意事项。
本章还介绍了 FLAML,这是一个提供自动化模型选择和调优工具的 AutoML 库。我们还介绍了 FLAML 提供的两个高效的超参数优化算法:CFO 和 BlendSearch。
FLAML 的实际应用通过案例研究的形式展示。除了 FLAML,我们还展示了一个开源工具 Featuretools,它提供自动化特征工程的功能。我们展示了如何使用 FLAML 在固定时间内开发优化模型。最后,我们提供了使用 FLAML 零样本 AutoML 功能示例,该功能通过分析数据集与已知问题的配置来确定合适的超参数,从而消除了模型调优的需要。
下一章将讨论围绕 LightGBM 模型构建 ML 管道,重点关注导出、打包和部署 LightGBM 模型以用于生产。
参考文献
| *[*1] | 王晨,吴强,魏梅,朱易,“FLAML:一个快速且轻量级的 AutoML 库”,发表于 MLSys,2021. |
|---|---|
| *[*2] | 吴强,王晨,黄思,关于成本相关超参数的节约优化,2020. |
| *[*3] | 王晨,吴强,黄思,赛义德,“使用混合搜索策略进行经济超参数优化”,发表于 ICLR,2021. |
第三部分:使用 LightGBM 的生产就绪机器学习
在第三部分,我们将深入探讨机器学习解决方案在生产环境中的实际应用。我们将揭示机器学习管道的复杂性,确保系统性地处理数据并构建模型以获得一致的结果。MLOps,DevOps 和 ML 的结合,成为焦点,突出了在现实场景中部署和维护强大 ML 系统的重要性。通过实际案例,我们将探讨在平台(如 Google Cloud、Amazon SageMaker 和创新的 PostgresML)上部署 ML 管道,强调每个平台提供的独特优势。最后,我们将探讨分布式计算和基于 GPU 的训练,展示加速训练过程和管理大数据集的有效方法。本部分将强调将 ML 无缝集成到实际、生产就绪的解决方案中,为读者提供将他们的模型在动态环境中实现的知识。
本部分将包括以下章节:
-
第八章*,使用 LightGBM 的机器学习管道和 MLOps*
-
第九章*,使用 AWS SageMaker 进行 LightGBM MLOps*
-
第十章*,PostgresML 的 LightGBM* 模型
-
第十一章*,基于分布式和 GPU 的 LightGBM 学习*
第八章:使用 LightGBM 的机器学习流程和 MLOps
本章将重点从数据科学和建模问题转移到为我们的机器学习解决方案构建生产服务。我们介绍了机器学习流程的概念,这是一种处理数据并构建确保一致性和正确性的模型的系统方法。
我们还介绍了 MLOps 的概念,这是一种结合 DevOps 和 ML 的实践,旨在解决部署和维护生产级 ML 系统的需求。
本章包括使用 scikit-learn 构建机器学习流程的示例,封装数据处理、模型构建和调整。我们展示了如何将流程包装在 Web API 中,暴露一个安全的预测端点。最后,我们还探讨了系统的容器化和部署到 Google Cloud。
本章的主要内容包括以下几方面:
-
机器学习流程
-
MLOps 概述
-
部署客户流失的机器学习流程
技术要求
本章包括创建 scikit-learn 流程、训练 LightGBM 模型和构建 FastAPI 应用的示例。设置环境的要求可以在github.com/PacktPublishing/Practical-Machine-Learning-with-LightGBM-and-Python/tree/main/chapter-8的完整代码示例旁边找到。
介绍机器学习流程
在第六章《使用 LightGBM 解决现实世界数据科学问题》中,我们详细概述了数据科学生命周期,其中包括训练 ML 模型的各种步骤。如果我们只关注训练模型所需的步骤,给定已经收集的数据,那么这些步骤如下:
-
数据清洗和准备
-
特征工程
-
模型训练和调整
-
模型评估
-
模型部署
在之前的案例研究中,我们在处理 Jupyter 笔记本时手动应用了这些步骤。然而,如果我们把背景转移到长期机器学习项目中,会发生什么呢?如果我们需要在有新数据可用时重复该过程,我们就必须遵循相同的程序来成功构建模型。
同样,当我们想要使用模型对新数据进行评分时,我们必须每次都正确地应用这些步骤,并使用正确的参数和配置。
从某种意义上说,这些步骤形成了一个数据处理流程:数据进入流程,完成时产生一个可部署的模型。
形式上,机器学习流程是一个系统化和自动化的过程,指导机器学习项目的流程。它涉及几个相互关联的阶段,封装了之前列出的步骤。
机器学习管道旨在确保这些任务是有结构的、可重复的且高效的,从而更容易管理复杂的机器学习任务。当处理大型数据集或当将原始数据转换为机器学习模型可用的输入步骤复杂且必须频繁重复时,例如在生产环境中,管道特别有益。
管道中涉及的步骤具有一定的灵活性:根据管道的使用方式,步骤可以添加或删除。一些管道包括数据收集步骤,从各种数据源或数据库中提取数据,并为机器学习建模准备数据。
许多机器学习服务和框架提供功能和方法来实现机器学习管道。Scikit-learn 通过其Pipeline类提供此功能,我们将在下一节中探讨。
Scikit-learn 管道
Scikit-learn 提供Pipeline类作为实现机器学习管道的工具。Pipeline类提供了一个统一的接口来执行一系列与数据和模型相关的任务。管道依赖于 scikit-learn 的标准fit和transform接口来实现操作的链式调用。每个管道由任意数量的中间步骤组成,这些步骤必须是transforms。一个转换必须实现fit和transform,Pipeline类依次对每个转换进行操作,首先将数据传递给fit,然后传递给transform。最后的步骤,通常涉及将模型拟合到数据上,只需要实现fit方法。转换通常是预处理步骤,用于转换或增强数据。
使用 scikit-learn 管道的主要优势是确保工作流程被清晰地实现,并且是可重复的。这有助于避免常见的错误,例如在预处理步骤中将测试数据的统计信息泄露到训练模型中。通过在管道中包含预处理步骤,我们确保在训练期间以及当模型用于预测新数据时,应用相同的步骤。
此外,scikit-learn 管道可以与模型选择和超参数调整工具结合使用,例如网格搜索和交叉验证。我们可以通过定义预处理步骤和最终估计器的参数网格来使用网格搜索自动选择整个管道中最佳参数。这可以显著简化代码并减少复杂机器学习工作流程中的错误。FLAML 等工具也具备与 scikit-learn 管道的集成功能。
例如,可以创建一个简单的管道如下:
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression
pipeline = Pipeline(
[
('scaler', StandardScaler()),
('linear', LinearRegression())
]
)
在这里,该流程由两个步骤组成:一个缩放步骤,用于标准化数据,以及一个最终步骤,用于拟合线性回归模型。
scikit-learn 的Pipeline的强大之处在于它可以像使用任何其他估计器或模型一样使用:
pipeline.fit(X_train, y_train)
pipeline.predict(X_train, y_train)
这为我们提供了一个统一的接口,用于封装在管道中的所有步骤,并使可重复性变得简单。此外,我们可以像其他 scikit-learn 模型一样导出管道,以便部署管道及其封装的所有步骤:
import joblib
joblib.dump(pipeline, "linear_pipeline.pkl")
我们将在下一节中展示更多使用 scikit-learn 管道的示例。
尽管我们花费了大量时间解决与数据处理、构建和调整模型相关的问题,但我们还没有深入探讨模型训练后的情况。正是在这里,MLOps 的世界发挥了作用。下一节将提供详细的概述。
理解 MLOps
机器学习运维(MLOps)是一种将机器学习和系统运维领域融合的实践。它旨在标准化和简化机器学习模型开发和部署的生命周期,从而提高业务环境中机器学习解决方案的效率和效果。在许多方面,MLOps 可以被视为对将机器学习投入运营所面临的挑战的回应,将 DevOps 原则引入机器学习世界。
MLOps 旨在将数据科学家(他们通常专注于模型创建、实验和评估)和运维专业人员(他们处理部署、监控和维护)聚集在一起。目标是促进这些团队之间的更好协作,从而实现更快、更稳健的模型部署。
MLOps 的重要性通过机器学习系统所提出的独特挑战得到了强调。与传统的软件系统相比,机器学习系统更加动态且难以预测,这可能导致在可靠性、鲁棒性方面出现潜在挑战,尤其是在快速变化的生产环境中。
MLOps 的核心目标是通过自动化机器学习管道来加速机器学习生命周期,从而促进更快地进行实验和部署。这通过自动化多个阶段来实现,包括数据预处理、特征工程、模型训练、模型验证和部署。MLOps 的另一个关键方面是确保可重复性。鉴于机器学习模型的动态特性,精确复制结果可能具有挑战性,尤其是在使用新数据进行模型重新训练时。MLOps 强调代码、数据和模型配置版本控制的重要性,这确保了每个实验都可以精确重现,这对于调试和审计至关重要。
监控也是 MLOps 的一个关键部分。一旦模型部署,监控其性能和持续验证其预测至关重要。MLOps 强调需要强大的监控工具,这些工具可以跟踪模型性能、输入数据质量和其他关键指标。这些指标中的异常可能表明模型需要重新训练或调试。
MLOps 还鼓励使用稳健的 ML 测试实践。ML 测试包括传统的软件测试实践,如单元测试和集成测试,但也包括更多 ML 特定的测试,如验证模型预测的统计属性。
MLOps 还侧重于管理和扩展 ML 部署。在现实世界中,ML 模型可能需要每秒处理数千甚至数百万个预测。在这里,容器化和无服务器计算平台等 DevOps 实践发挥作用,以促进部署和扩展自动化。
重要的是要注意 MLOps 如何融入更广泛的软件生态系统。就像 DevOps 在软件工程中弥合了开发和运维之间的差距一样,MLOps 旨在为 ML 做同样的事情。通过促进共同理解和责任,MLOps 可以导致更成功的 ML 项目。
MLOps 是一个快速发展的领域,随着更多企业采用 ML,其重要性日益增加。通过将 DevOps 的原则应用于 ML 的独特挑战,MLOps 提供了一个从初始实验到稳健、可扩展部署的端到端 ML 生命周期的框架。MLOps 强调标准化、自动化、可重复性、监控、测试和协作,以实现高吞吐量的 ML 系统。
现在,我们将通过一个实际示例来查看使用 scikit-learn 创建 ML 管道并将其部署在 REST API 背后的情况。
部署客户流失的 ML 管道
在我们的实际示例中,我们将使用我们在第五章中使用的电信(telco)客户流失数据集,即使用 Optuna 进行 LightGBM 参数优化。该数据集包含每个客户的描述性信息(例如性别、账单信息和费用),以及客户是否已离开电信提供商(流失为是或否)。我们的任务是构建一个分类模型来预测流失。
此外,我们希望将模型部署在 REST API 后面,以便它可以集成到一个更广泛的软件系统中。REST API 应该有一个端点,用于对传递给 API 的数据进行预测。
我们将使用FastAPI,一个现代、高性能的 Python 网络框架来构建我们的 API。最后,我们将使用 Docker 将我们的模型和 API 部署到 Google Cloud Platform。
使用 scikit-learn 构建 ML 管道
我们将首先使用 scikit-learn 的Pipeline工具集构建一个 ML 管道。我们的管道应该封装数据清洗和特征工程步骤,然后构建和调整适当的模型。
我们将评估两种建模算法:LightGBM 和随机森林,因此,无需对数据进行任何缩放或归一化。然而,数据集为每个客户都有一个唯一的标识符,即customerID,我们需要将其删除。
此外,数据集包括数值和分类特征,我们必须对分类特征实现独热编码。
管道预处理步骤
要在 scikit-learn 管道中执行这些步骤,我们将使用 ColumnTransformer。ColumnTransformer 是一个仅对数据集的子集列操作的 Pipeline 转换器。转换器接受一个元组列表,形式为 (name, transformer, columns)。它将子转换器应用于指定的列,并将结果连接起来,使得所有结果特征都成为同一个结果集的一部分。
例如,考虑以下 DataFrame 和 ColumnTransformer:
df = pd.DataFrame({
"price": [29.99, 99.99, 19.99],
"description": ["Luxury goods", "Outdoor goods",
"Sports equipment"],
})
ct = ColumnTransformer(
[("scaling", MinMaxScaler(), ["price"]),
("vectorize", TfidfVectorizer(), "description")])
transformed = ct.fit_transform(df)
这里,我们有一个包含两列的 DataFrame:price 和 description。创建了一个包含两个子转换器的列转换器:一个缩放转换器和向量器。缩放转换器仅对 price 列应用最小-最大缩放。向量器仅对 description 列应用 TF-IDF 向量化。当调用 fit_transform 时,返回一个 单个 数组,包含缩放后的价格列和表示词向量的列。
注意
TF-IDF,或词频-逆文档频率,只是从文本中提取特征的一种方式。分析和提取文本特征,以及自然语言处理,是机器学习中的一个广泛领域,我们在这里不会深入探讨。我们鼓励您进一步阅读有关该主题的内容,请参阅scikit-learn.org/stable/modules/feature_extraction.xhtml#text-feature-extraction。
我们可以将 Customer Churn 数据集的预处理设置为一个单独的 ColumnTransformer。我们首先定义两个单独的转换器,id_transformer 和 encode_transformer,它们应用于 ID 列和分类特征:
id_transformer = (
"customer_id",
CustomerIdTransformer(id_columns),
id_columns
)
encode_transformer = (
"encoder",
OneHotEncoder(sparse_output=False),
categorical_features
)
然后将单独的转换器组合成 ColumnTransformer:
preprocessor = ColumnTransformer(
transformers=[
id_transformer,
encode_transformer,
],
remainder='passthrough'
)
ColumnTransformer 使用 remainder='passthrough' 参数定义。remainder 参数指定了 ColumnTransformer 不转换的列的处理方式。默认情况下,这些列会被删除,但我们需要将它们原样传递,以便包含在数据集中。
编码转换器创建并应用 OneHotEncoder 到分类特征上。
为了说明目的,我们创建了一个自定义的转换器类来维护 ID 列的列表,并在转换过程中从数据中删除它们。
类在这里展示:
class CustomerIdTransformer(BaseEstimator, TransformerMixin):
def __init__(self, id_columns):
self.id_columns = id_columns
def fit(self, X, y=None):
return self
def transform(self, X, y=None):
return X.drop(columns=self.id_columns, axis=1)
如我们所见,该类扩展了 scikit-learn 基础类 BaseEstimator 和 TransformerMixin,并且必须实现 fit 和 transform 方法。实现这些方法也使其适合作为管道中的转换器。如果需要,实现 fit 是可选的;在我们的情况下,fit 过程中没有任何操作。我们的转换步骤会删除相关列。
注意
在管道中封装删除无关列(在这种情况下,ID 列)的功能很重要。当将管道部署到生产使用时,我们期望在做出预测时将这些列传递给管道。作为管道的一部分删除它们简化了模型消费者对管道的使用,并减少了用户出错的机会。
这完成了预处理所需的转换,我们现在可以继续下一步:拟合和调整模型。
管道建模步骤
对于管道建模部分,我们将使用 FLAML。我们也将利用这个机会展示如何将参数传递给管道内的步骤。首先,我们定义我们的 AutoML 模型的设置:
automl_settings = {
"time_budget": 120,
"metric": "accuracy",
"task": "classification",
"estimator_list": ["lgbm", "rf"],
"custom_hp": {
"n_estimators": {
"domain": flaml.tune.uniform(20, 500)
}
},
"verbose": -1
}
上述代码设置了我们的时间预算、优化指标和分类任务,并将估计器限制在 LightGBM 和随机森林模型。最后,我们通过指定 n_estimators 应该在 20 到 500 之间均匀采样来自定义搜索空间。
管道需要将构成步骤的参数以步骤名称和双下划线为前缀。我们可以在管道中设置一个字典,将这些参数传递给 AutoML 类:
pipeline_settings = {
f"automl__{key}": value for key, value in
automl_settings.items()
}
在这里,automl 是管道中步骤的名称。因此,例如,时间预算和指标的参数分别设置为 automl__time_budget: 120 和 automl__metric: accuracy。
最后,我们可以添加 FLAML 的 AutoML 估计器:
automl = flaml.AutoML()
pipeline = Pipeline(
steps=[("preprocessor", preprocessor),
("automl", automl)]
)
以下图显示了最终的管道:
图 8.1 – 客户流失预测的最终 ML 管道
图 8*.1* 显示了一个由两个子转换器组成的 ColumnTransformer,它们将数据输入到 AutoML 估计器中。
模型训练和验证
现在我们已经准备好将管道拟合到我们的数据中,传递我们之前设置的管道设置。管道支持标准的 scikit-learn API,因此我们可以调用管道本身:
pipeline.fit(X, y, **pipeline_settings)
运行 fit 执行所有预处理步骤,然后将数据传递给 AutoML 建模和调整。单个 Pipeline 对象展示了 ML 管道的强大功能:包括训练和调整后的模型在内的整个端到端过程被封装并便携,我们可以像使用单个模型一样使用管道。例如,以下代码对训练数据执行 F1 分数:
print(f"F1: {f1_score(pipeline.predict(X), y,
pos_label='Yes')}")
要导出管道,我们使用 joblib 将模型序列化到文件中:
joblib.dump(pipeline, "churn_pipeline.pkl")
导出管道允许我们在生产代码中重新实例化并使用它。接下来,我们将探讨为我们的模型构建 API。
在这个阶段,我们的管道(它封装了预处理、训练、优化和验证)已经定义,我们准备将其部署到系统中。我们将通过用 FastAPI 包装我们的模型来实现这一点。
使用 FastAPI 构建 ML API
现在我们将探讨围绕我们的流水线构建 REST API,使流水线的消费者可以通过 Web 请求获取预测。为模型构建 Web API 也简化了与其他系统和服务的集成,并且是微服务架构中集成标准的方法。
要构建 API,我们使用 Python 库 FastAPI。
FastAPI
FastAPI 是一个现代、高性能的 Web 框架,用于使用 Python 3.6+构建 API。它从头开始设计,易于使用并支持高性能的 API 开发。FastAPI 的关键特性是其速度和易用性,使其成为开发健壮、生产就绪 API 的绝佳选择。FastAPI 广泛采用 Python 的类型检查,有助于在开发过程中早期捕获错误。它还使用这些类型提示来提供数据验证、序列化和文档,减少了开发者需要编写的样板代码。
FastAPI 的性能是其定义性的特征之一。它与 Node.js 相当,并且比传统的 Python 框架快得多。这种速度是通过其使用 Starlette 进行 Web 部分和 Pydantic 进行数据部分,以及其非阻塞特性来实现的,这使得它适合处理大量并发请求。
FastAPI 提供了自动交互式 API 文档,这在开发复杂 API 时是一个很大的优势。使用 FastAPI,开发者可以通过 Swagger UI 访问自动生成的交互式 API 文档。Swagger UI 还提供了与 REST 资源交互的功能,无需编写代码或使用外部工具。这一特性使 FastAPI 非常适用于开发者,并加速了开发过程。
FastAPI 还支持行业标准的安全协议,如 OAuth2,并提供工具以简化实现。FastAPI 的大部分工具都依赖于其依赖注入系统,允许开发者高效地管理依赖项和处理共享资源。
FastAPI 因其易用性和高性能,非常适合构建用于机器学习模型的 Web API 和微服务,这使得机器学习工程师可以专注于生产机器学习部署的其他众多问题。
使用 FastAPI 进行构建
要使用 FastAPI 创建 REST API,我们可以创建一个新的 Python 脚本并实例化 FastAPI 实例。实例启动后,我们可以从文件中加载我们的模型:
app = FastAPI()
model = joblib.load("churn_pipeline.pkl")
在应用程序启动时加载模型会增加启动时间,但确保 API 在应用程序启动完成后即可准备就绪以处理请求。
接下来,我们需要实现一个 REST 端点以进行预测。我们的端点接受输入数据,并以 JSON 格式返回预测。输入 JSON 是一个 JSON 对象的数组,如下所示:
[
{
"customerID": "1580-BMCMR",
...
"MonthlyCharges": 87.3,
"TotalCharges": "1637.3"
},
{
"customerID": "4304-XUMGI",
...
"MonthlyCharges": 75.15,
"TotalCharges": "3822.45"
}
]
使用 FastAPI,我们通过创建一个接受输入数据作为参数的函数来实现 REST 端点。FastAPI 将前面的 JSON 结构序列化为 Python 字典列表。因此,我们的函数签名实现如下:
@app.post('/predict')
def predict_instances(
instances: list[dict[str, str]]
):
我们使用 FastAPI 的post装饰器装饰该函数,指定端点路径('/predict')。
为了对模型进行实际预测,我们将字典转换为 DataFrame 并执行预测:
instance_frame = pd.DataFrame(instances)
predictions = model.predict_proba(instance_frame)
我们使用predict_proba来获取每个类(是或否)的概率,因为我们希望将此附加信息发送给我们的 API 消费者。在预测的同时返回概率是一种推荐的做法,因为这使 API 消费者在使用预测时拥有更多的控制权。API 消费者可以根据预测的使用方式来决定什么概率阈值对于他们的应用程序来说是足够的。
为了以 JSON 格式返回结果,我们构建一个字典,然后 FastAPI 将其序列化为 JSON。我们使用 NumPy 的argmax来获取最高概率的索引以确定预测的类别,并使用amax来获取最高的概率本身:
results = {}
for i, row in enumerate(predictions):
prediction = model.classes_[np.argmax(row)]
probability = np.amax(row)
results[i] = {"prediction": prediction,
"probability": probability}
return results
前面的代码为输入列表中的每个数据实例生成一个prediction对象,使用列表中的位置作为索引。当端点被调用时,将返回以下 JSON:
{
"0": {
"prediction": "Yes",
"probability": 0.9758797243307111
},
"1": {
"prediction": "No",
"probability": 0.8896770039274629
},
"2": {
"prediction": "No",
"probability": 0.9149225087944103
}
}
我们现在已经构建了 API 端点的核心。然而,我们还得注意非功能性关注点,例如安全性。通常,机器学习工程师会忽视诸如安全性或性能等方面的内容,而只关注机器学习相关的问题。我们不应犯这样的错误,必须确保我们给予这些关注点必要的关注。
保护 API
为了保护我们的端点,我们将使用 HTTP 基本认证。我们使用预置的用户名和密码,这些我们从环境中读取。这允许我们在部署期间安全地传递这些凭证到应用程序中,避免了硬编码凭证等陷阱。我们的端点还需要增强以接受用户的凭证。HTTP 基本认证凭证作为 HTTP 头发送。
我们可以按以下方式实现。我们首先为 FastAPI 设置安全措施并从环境中读取凭证:
security = HTTPBasic()
USER = bytes(os.getenv("CHURN_USER"), "utf-8")
PASSWORD = bytes(os.getenv("CHURN_PASSWORD"), "utf-8")
然后,我们在endpoint函数中添加以下内容:
@app.post('/predict')
def predict_instances(
credentials: Annotated[HTTPBasicCredentials,
Depends(security)],
instances: list[dict[str, str]]
):
authenticate(credentials.username.encode("utf-8"),
credentials.password.encode("utf-8"))
authenticate函数验证接收到的凭证是否与我们从环境中获取的 API 凭证匹配。我们可以使用 Python 的 secrets 库来进行验证:
def authenticate(username: bytes, password: bytes):
valid_user = secrets.compare_digest(
username, USER
)
valid_password = secrets.compare_digest(
password, PASSWORD
)
if not (valid_user and valid_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Basic"},
)
return username
如果凭证无效,我们将抛出一个带有 HTTP 状态码401的异常,表示消费者未授权。
我们的 API 端点现在已完全实现、安全并准备好部署。为了部署我们的 API,我们将使用 Docker 进行容器化。
容器化我们的 API
我们可以使用以下 Dockerfile 为我们的 API 构建一个 Docker 容器:
FROM python:3.10-slim
RUN apt-get update && apt-get install -y --no-install-recommends apt-utils
RUN apt-get -y install curl
RUN apt-get install libgomp1
WORKDIR /usr/src/app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD [ "uvicorn", "telco_churn_api:app", "--host", "0.0.0.0", "--port", "8080" ]
Dockerfile 非常简单:我们从一个基于 Python 3.10 的基础镜像开始,安装 LightGBM 需要的某些操作系统依赖项(libgomp1)。然后我们设置 FastAPI 应用程序:我们复制 Python 的requirements文件,安装所有依赖项,然后复制必要的源文件(使用COPY . .)。
最后,我们运行一个监听所有地址的端口8080的 Uvicorn 服务器。Uvicorn 是 Python 的 ASGI 网络服务器实现,支持异步 I/O,显著提高了网络服务器的吞吐量。我们绑定到端口8080,这是我们的部署平台默认的端口。
我们可以使用以下命令构建和运行 Docker 镜像,传递用户名和密码环境变量:
docker build . -t churn_api:latest
docker run --rm -it -e CHURN_USER=***** -e CHURN_PASSWORD=*********** -p 8080:8080 churn_api:latest
API 现在应该可以在你的本地主机上通过端口8080访问,并受到你在环境变量中提供的凭证的保护。
在我们的应用程序容器化后,我们就可以将应用程序部署到任何支持容器的平台。对于 churn 应用程序,我们将将其部署到 Google Cloud Platform。
将 LightGBM 部署到 Google Cloud
我们将利用Google Cloud Run平台将我们的应用程序部署到 Google Cloud Platform。
Google Cloud Run 是一个无服务器平台,允许你在不担心基础设施管理的情况下开发和运行应用程序。Cloud Run 允许开发者在一个安全、可扩展、零运维的环境中运行他们的应用程序。Cloud Run 是完全管理的,这意味着所有基础设施(如服务器和负载均衡器)都被抽象化,使用户能够专注于运行他们的应用程序。Cloud Run 还支持完全自动扩展,运行中的容器数量会自动增加以响应增加的负载。Cloud Run 也非常经济高效,因为你只有在容器运行并处理请求时才会被收费。
要使用 Cloud Run,你需要一个 Google Cloud 账户,并需要创建一个 Google Cloud 项目,启用计费,并设置和初始化Google Cloud CLI。以下资源将指导你完成这些步骤:
一旦完成 Google Cloud 的设置,我们就可以使用 CLI 部署我们的 API。这可以通过单个命令完成:
gcloud run deploy --set-env-vars CHURN_USER=*****,CHURN_PASSWORD=***********
运行命令会提示你输入服务名称和部署服务所在的区域。我们还会设置用于安全凭证所需的环境变量。对于部署,Cloud Run 会为你创建 Cloud Build,它会自动构建和存储 Docker 容器,然后将其部署到 Cloud Run。
一旦 Cloud Run 命令完成,我们就部署了一个安全、可扩展的 RESTful Web API,该 API 为我们客户的流失 ML 管道提供服务。
摘要
本章介绍了 ML 管道,说明了其在实现 ML 解决方案时的一致性、正确性和可移植性的优势。
对新兴的 MLOps 领域进行了概述,这是一个结合 DevOps 和 ML 以实现经过测试、可扩展、安全且可观察的生产 ML 系统的实践。
此外,我们还讨论了 scikit-learn 的Pipeline类,这是一个使用熟悉的 scikit-learn API 实现 ML 管道的工具集。
还提供了一个实现客户流失 ML 管道的端到端示例。我们展示了如何创建一个 scikit-learn 管道,该管道执行预处理、建模和调优,并且可以导出用于软件系统。然后,我们使用 FastAPI 构建了一个安全的 RESTful Web API,该 API 提供了一个从我们的客户流失管道获取预测的端点。最后,我们使用 Cloud Run 服务将我们的 API 部署到 Google Cloud Platform。
尽管我们的部署是安全的且完全可扩展的,Cloud Run 提供了可观察性、指标和日志,但我们的部署没有解决一些 ML 特定的方面:模型漂移、模型性能监控和重新训练。
在下一章中,我们将探讨一个专门的 ML 云服务——AWS SageMaker,它提供了一个针对特定平台构建和托管基于云的 ML 管道的解决方案。
第九章:使用 AWS SageMaker 进行 LightGBM MLOps
在第八章“使用 LightGBM 的机器学习管道和 MLOps”中,我们使用 scikit-learn 构建了一个端到端的 ML 管道。我们还探讨了将管道封装在 REST API 中,并将我们的 API 部署到云端。
本章将探讨使用Amazon SageMaker开发和部署管道。SageMaker 是 Amazon Web Services(AWS)提供的一套完整的用于开发、托管、监控和维护 ML 解决方案的生产服务。
我们将通过查看高级主题,如检测训练模型中的偏差和自动化部署到完全可扩展的无服务器 Web 端点,来扩展我们的 ML 管道功能。
本章将涵盖以下主要内容:
-
AWS 和 SageMaker 简介
-
模型可解释性和偏差
-
使用 SageMaker 构建端到端管道
技术要求
本章深入探讨了使用 Amazon SageMaker 构建 ML 模型和管道。您需要访问一个 Amazon 账户,并且您还必须配置一种支付方式。请注意,运行本章的示例代码将在 AWS 上产生费用。本章的完整笔记本和脚本可在github.com/PacktPublishing/Practical-Machine-Learning-with-LightGBM-and-Python/tree/main/chapter-9找到。
AWS 和 SageMaker 简介
本节提供了 AWS 的高级概述,并深入探讨了 SageMaker,AWS 的 ML 服务。
AWS
AWS 是全球云计算市场的主要参与者之一。AWS 提供许多基于云的产品和服务,包括数据库、机器学习(ML)、分析、网络、存储、开发工具和企业应用程序。AWS 背后的理念是为企业提供一种经济实惠且可扩展的解决方案,以满足其计算需求,无论其规模或行业如何。
AWS 的一个关键优势是弹性,这意味着服务器和服务可以快速随意地停止和启动,从零台机器扩展到数千台。服务的弹性与其主要定价模式“按使用付费”相辅相成,这意味着客户只需为使用的服务和资源付费,无需任何预付成本或长期合同。这种弹性和定价允许企业根据需要按需和细粒度地扩展计算需求,然后只为他们使用的付费。这种方法已经改变了企业扩展 IT 资源和应用程序的方式,使他们能够快速响应不断变化的企业需求,而无需承担与硬件和软件采购和维护相关的传统高昂成本。
另一个优势是 AWS 的全球覆盖范围。AWS 服务在全球许多地区都可用。区域在地理上是分开的,每个区域进一步划分为可用区。区域-区域设置允许用户创建全球分布和冗余的基础设施,以最大化弹性和为灾难恢复进行设计。区域数据中心还允许用户在靠近最终用户的地方创建服务器和服务,以最小化延迟。
核心服务
核心 AWS 服务提供计算、网络和存储能力。AWS 的计算服务包括 Amazon Elastic Compute Cloud(EC2),为用户提供可配置的虚拟机,以及 AWS Lambda,一个无服务器计算平台,允许您在不需要配置和管理服务器的情况下运行代码。在机器学习中,EC2 实例和 Lambda 函数通常用于通过 API 端点训练、验证或提供服务模型。EC2 服务器的弹性特性允许机器学习工程师将训练服务器扩展到数万个,这可以显著加快训练或参数调整任务。
AWS 的存储和数据库服务,如 Amazon Simple Storage Service(S3)和 Amazon RDS(关系数据库服务),提供可靠、可扩展和安全的存储解决方案。这些服务管理存储基础设施,并提供高级功能,如备份、补丁管理和垂直和水平扩展。S3 是广泛用于数据工程和机器学习的服务。S3 提供低成本、高度冗余的安全存储,可扩展到超过艾字节。
AWS 还提供数据仓库解决方案,即 Amazon Redshift。大型企业经常使用 Redshift 作为仓库或数据湖的基础,这意味着它通常是机器学习解决方案的数据源。
AWS 还提供网络服务,以帮助企业在复杂的网络和隔离需求方面取得成功。AWS Direct Connect 允许客户从客户的站点设置一个专用网络连接到 AWS 云。路由和域名服务器可以使用 Amazon Route 53,一个灵活且可扩展的 域名系统(DNS)服务进行管理。
然而,在众多网络服务中,首要的是 Amazon Virtual Private Cloud(VPC)。VPC 为客户提供配置完全隔离的虚拟网络的能力。客户可以精细配置子网、路由表、地址范围、网关和安全组。VPC 允许用户隔离他们的环境和云资源,并控制进出流量,以增加安全性和隐私性。
安全性
任何基础设施方程中的关键部分是安全性。在安全性方面,AWS 提供一个高度安全、可扩展和灵活的云计算环境。AWS 的安全服务,包括 AWS Identity and Access Management(IAM)和 Amazon Security Hub,通过实施强大的安全措施帮助客户保护他们的数据和应用程序。
AWS 还符合多个国际和行业特定的合规标准,例如 GDPR、HIPAA 和 ISO 27001。此外,在数据治理方面,AWS 使遵守数据驻留和隐私要求变得容易。由于 AWS 的区域结构,数据可以保留在特定国家,同时工程师可以访问 AWS 的完整服务套件。
机器学习
AWS 还提供专注于 ML 和 人工智能(AI)的服务。其中许多是针对特定 ML 任务的完全托管服务。AWS Comprehend 提供了许多 自然语言处理(NLP)服务,例如文档处理、命名实体识别和情感分析。Amazon Lookout 是用于设备、指标或图像异常检测的服务。此外,Amazon Rekognition 提供了用于机器视觉用例的服务,例如图像分类和面部识别。
对我们来说特别感兴趣的是 Amazon SageMaker,这是一个完整的 ML 平台,使我们能够在亚马逊云中创建、训练和部署 ML 模型。下一节将详细讨论 SageMaker。
SageMaker
Amazon SageMaker 是一个端到端的 ML 平台,允许数据科学家与数据一起工作,并开发、训练、部署和监控 ML 模型。SageMaker 完全托管,因此无需配置或管理服务器。
亚马逊 SageMaker 的主要吸引力在于其作为一个平台的全面性。它涵盖了机器学习(ML)过程的各个方面,包括数据标注、模型构建、训练、调优、部署、管理和监控。通过处理这些方面,SageMaker 允许开发者和数据科学家专注于核心的 ML 任务,而不是管理基础设施。
正如我们之前讨论的,ML 生命周期始于数据收集,这通常需要手动数据标注。为此,SageMaker 提供了一个名为 SageMaker Ground Truth 的服务。该服务使高效标注 ML 数据集变得容易。通过使用自动标注工作流程,它可以显著减少与数据标注通常相关的耗时和成本,并且还提供了一支用于手动数据标注任务的工作队伍。此外,SageMaker 还提供了 Data Wrangler 服务,该服务有助于数据准备和 探索性数据分析(EDA)。Data Wrangler 提供了从 S3、Redshift 和其他平台查询数据的功能,然后从单一可视化界面中净化、可视化和理解数据。
SageMaker 为模型训练阶段提供了一个完全管理的服务,可以通过Training Jobs处理大规模、分布式模型训练。该服务旨在灵活和适应性强,使用户能够根据需要优化他们的机器学习模型。用户只需指定其数据的位置,通常是 S3 和机器学习算法,SageMaker 就会负责其余的训练过程。模型训练服务充分利用了底层 AWS 基础设施的弹性:可以快速创建多个服务器来执行训练任务,训练完成后丢弃以节省成本。
这种范式也扩展到了超参数调整。为了简化超参数优化,SageMaker 提供了一个自动模型调优功能。提供了许多调优算法,例如 Optuna 或 FLAML,并且可以在多个服务器上运行调优。
SageMaker 还通过SageMaker Autopilot支持更全面的 AutoML 体验。Autopilot 是一种服务,它能够实现自动模型创建。用户只需提供原始数据并设置目标;然后,Autopilot 会自动探索不同的解决方案以找到最佳模型。Autopilot 提供了对整个过程的完全可见性,以便数据科学家可以了解模型是如何创建的,并做出任何必要的调整。
一旦模型经过训练和优化,就必须部署。SageMaker 通过提供一键部署过程简化了这一过程。用户可以快速将模型部署到生产环境中,并具有自动扩展功能,无需担心底层基础设施。这种部署自动扩展功能允许用户设置基于指标的策略,以增加或减少后端服务器。例如,如果一段时间内的调用次数超过特定阈值,则可以扩展部署。SageMaker 确保模型的高可用性,并允许进行 A/B 测试以比较不同版本并决定最佳版本。SageMaker 还支持多模型端点,允许用户在单个端点上部署多个模型。
Amazon SageMaker 还提供了监控模型性能和分析部署后模型的功能。SageMaker Model Monitor持续监控已部署模型的品质(对于实时端点)或批量监控(对于异步作业)。可以定义警报,当指标阈值超过时通知用户。Model Monitor 可以根据如准确度等指标监控数据漂移和模型漂移。
最后,SageMaker 既是 AWS 中的一个平台,也是一个软件 SDK。SDK 提供了 Python 和 R 两种语言版本。SageMaker SDK 提供了一系列内置算法和框架,包括对机器学习社区中最受欢迎的算法的支持,如 XGBoost、TensorFlow、PyTorch 和 MXNet。它还支持一个市场,用户可以从 AWS 和其他 SageMaker 用户共享的大量算法和模型包中选择。
SageMaker 中的一个值得关注的部分,简化了模型开发中最重要的一环(偏差和公平性),就是 SageMaker Clarify。
SageMaker Clarify
Amazon SageMaker Clarify 是一个提供更多机器学习模型透明度的工具。SageMaker Clarify 的目标是帮助理解机器学习模型如何进行预测,从而实现模型可解释性和公平性。
SageMaker Clarify 的一个主要特性是其提供模型可解释性的能力。它帮助开发者理解输入数据与模型预测之间的关系。该服务生成特征归因,显示数据集中每个特征如何影响预测,这在许多领域都至关重要,尤其是在需要理解模型预测背后的推理时。除了提供对单个预测的洞察外,SageMaker Clarify 还提供全局解释能力。它衡量输入特征对模型预测的整体重要性,在整个数据集上汇总。特征影响分析允许开发者和数据科学家理解模型的总体行为,帮助他们从全局层面解释不同特征如何驱动模型预测。
此外,Clarify 可以帮助识别训练模型中的潜在偏差。该服务包括预训练和后训练偏差指标,帮助我们了解模型是否不公平地偏向某些群体。检查所有新模型是否存在偏差是最佳实践,但在金融或医疗保健等受监管行业中,偏差预测可能具有严重后果,因此这一点至关重要。
Clarify 通过使用一种称为 **SHapley Additive exPlanations(SHAP)的先进技术来提供模型可解释性。
SHAP
SHAP 是一种基于博弈论的解释任何机器学习模型输出的方法 [1]。SHAP 的目标是提供对单个特征对模型整体预测影响的了解。
实质上,SHAP 值通过将该特定特征值与该特征的基线值进行对比来评估其影响,突出其对预测的贡献。SHAP 值是每个实例中每个特征对预测的公平贡献分配。SHAP 值植根于合作博弈论,代表了对以下问题的解决方案:
“考虑到一个特征在预测结果中的差异,这部分差异中有多少可以归因于 每个特征?”
这些值是使用博弈论中的 Shapley 值概念计算的。Shapley 值通过对比模型预测与该特征存在和不存在的情况来确定特征的重要性。然而,由于模型遇到特征序列可能会影响其预测,Shapley 值考虑了所有可能的顺序。然后,它为特征分配一个重要性值,使得该特征在所有可能的联盟中的平均边际贡献等于这个值。
使用 SHAP 进行模型解释有几个优点。首先,它提供了解释的一致性。如果一个特征的影响发生变化,该特征的归因重要性也会成比例地变化。
其次,SHAP 保证局部准确性,这意味着所有特征的 SHAP 值之和将等于预测与数据集平均预测之间的差异。
使用 SHAP 摘要图可视化 SHAP 值是一个很好的方法。这些图提供了一个俯瞰特征重要性和驱动因素的全景。它们在图上绘制了每个特征的所有 SHAP 值,以便于可视化。图上的每个点代表一个特征和一个实例的 SHAP 值。Y 轴上的位置由特征决定,X 轴上的位置由 SHAP 值决定:
图 9.1 – 人口普查收入数据集的本地解释示例。条形表示 SHAP 值或每个特征在预测此特定实例时的相对重要性
在 SageMaker Clarify 的上下文中,当你运行一个澄清作业时,该服务会为你的数据集中的每个实例生成一组 SHAP 值。SageMaker Clarify 还可以通过在整个数据集上聚合 SHAP 值来提供全局特征重要性度量。
SHAP 值可以帮助你理解复杂的模型行为,突出潜在问题,并随着时间的推移改进你的模型。例如,通过检查 SHAP 值,你可能会发现某个特定特征对你的模型预测的影响比预期的更大,这会促使你探索为什么会发生这种情况。
在本节中,我们探讨了 AWS 以及更具体地,AWS 机器学习服务家族中的 SageMaker 提供了什么。SageMaker 中可用的功能,如模型可解释性、偏差检测和监控,是我们尚未在我们的机器学习管道中实现的部分。在下一节中,我们将探讨使用 SageMaker 构建一个完整的端到端 LightGBM 机器学习管道,包括这些关键步骤。
使用 Amazon SageMaker 构建 LightGBM 机器学习管道
我们将用于构建 SageMaker 管道案例研究的案例数据集来自 第四章,比较 LightGBM、XGBoost 和深度学习 的人口普查收入数据集。此数据集也作为 SageMaker 示例数据集提供,因此如果您是初学者,在 SageMaker 上使用它很容易。
我们将要构建的管道将包括以下步骤:
-
数据预处理。
-
模型训练和调优。
-
模型评估。
-
使用 Clarify 进行偏差和可解释性检查。
-
在 SageMaker 中的模型注册。
-
使用 AWS Lambda 进行模型部署。
这里有一个显示完整管道的图表:
图 9.2 – 用于人口普查收入分类的 SageMaker ML 管道
我们的方法是使用在 SageMaker Studio 中运行的 Jupyter Notebook 创建整个管道。接下来的部分将解释并展示每个管道步骤的代码,从设置 SageMaker 会话开始。
设置 SageMaker 会话
以下步骤假设您已经创建了一个 AWS 账户并设置了一个 SageMaker 域以开始。如果没有,可以参考以下文档来完成这些操作:
-
先决条件:
docs.aws.amazon.com/sagemaker/latest/dg/gs-set-up.xhtml -
加入 SageMaker 域:
docs.aws.amazon.com/sagemaker/latest/dg/gs-studio-onboard.xhtml
我们必须通过 boto3 初始化 SageMaker 会话并创建 S3、SageMaker 和 SageMaker Runtime 客户端以开始:
sess = sagemaker.Session()
region = sess.boto_region_name
s3_client = boto3.client("s3", region_name=region)
sm_client = boto3.client("sagemaker", region_name=region)
sm_runtime_client = boto3.client("sagemaker-runtime")
我们将使用 Amazon S3 来存储我们的训练数据、源代码以及由管道创建的所有数据和工件,例如序列化的模型。我们的数据和工件被分为一个读取桶和一个单独的写入桶。这是一个标准的最佳实践,因为它将数据存储的关注点分开。
SageMaker 会话有一个默认的 S3 桶的概念。如果没有提供默认桶名称,则会生成一个,并为您创建一个桶。在这里,我们获取对桶的引用。这是我们输出或写入桶。读取桶是我们之前创建的一个桶,用于存储我们的训练数据:
write_bucket = sess.default_bucket()
write_prefix = "census-income-pipeline"
read_bucket = "sagemaker-data"
read_prefix = "census-income"
管道中每个步骤的源代码、配置和输出都被捕获在我们 S3 写入桶中的文件夹内。为每个 S3 URI 创建变量很有用,这样可以避免在重复引用数据时出错,如下所示:
input_data_key = f"s3://{read_bucket}/{read_prefix}"
census_income_data_uri = f"{input_data_key}/census-income.csv"
output_data_uri = f"s3://{write_bucket}/{write_prefix}/"
scripts_uri = f"s3://{write_bucket}/{write_prefix}/scripts"
SageMaker 需要我们指定在运行训练、处理、Clarify 和预测作业时想要使用的计算实例类型。在我们的示例中,我们使用 m5.large 实例。大多数 EC2 实例类型都可以与 SageMaker 一起使用。然而,还有一些支持 GPU 和深度学习框架的特殊实例类型也是可用的:
train_model_id, train_model_version, train_scope = "lightgbm-classification-model", "*", "training"
process_instance_type = "ml.m5.large"
train_instance_count = 1
train_instance_type = "ml.m5.large"
predictor_instance_count = 1
predictor_instance_type = "ml.m5.large"
clarify_instance_count = 1
clarify_instance_type = "ml.m5.large"
SageMaker 使用标准的 EC2 实例进行训练,但在实例上运行特定的 Docker 镜像以提供 ML 功能。Amazon SageMaker 为各种 ML 框架和堆栈提供了许多预构建的 Docker 镜像。
SageMaker SDK 还提供了一个函数,用于在使用的 AWS 区域内搜索与我们所需的实例类型兼容的镜像。我们可以使用 retrieve 搜索镜像:
train_image_uri = retrieve(
region="us-east-1",
framework=None,
model_id=train_model_id,
model_version=train_model_version,
image_scope=train_scope,
instance_type=train_instance_type
)
我们将框架参数指定为 None,因为我们自己管理 LightGBM 的安装。
为了参数化我们的管道,我们必须从 sagemaker.workflow.parameters 包中定义 SageMaker 工作流参数。各种参数类型都有包装器可用:
train_instance_type_param = ParameterString(
name="TrainingInstanceType",
default_value=train_instance_type)
train_instance_count_param = ParameterInteger(
name="TrainingInstanceCount",
default_value=train_instance_count)
deploy_instance_type_param = ParameterString(
name="DeployInstanceType",
default_value=predictor_instance_type)
deploy_instance_count_param = ParameterInteger(
name="DeployInstanceCount",
default_value=predictor_instance_count)
在设置了管道参数、S3 数据路径和其他配置变量后,我们可以继续创建管道的预处理步骤。
预处理步骤
设置我们的预处理步骤有两个部分:创建一个执行预处理的 Python 脚本,并创建一个添加到管道中的处理器。
我们将要使用的脚本是一个带有主函数的常规 Python 脚本。我们将使用 scikit-learn 进行预处理。预处理脚本在此处并未完全重现,但在我们的源代码仓库中可用。值得注意的是,当管道执行步骤时,数据将从 S3 检索并添加到预处理实例的本地暂存目录中。从这里,我们可以使用标准的 pandas 工具读取数据:
local_dir = "/opt/ml/processing"
input_data_path = os.path.join("/opt/ml/processing/census-income", "census-income.csv")
logger.info("Reading claims data from {}".format(input_data_path))
df = pd.read_csv(input_data_path)
类似地,在处理完成后,我们可以将结果写入本地目录,SageMaker 会从该目录检索数据并将其上传到 S3:
train_output_path = os.path.join(f"{local_dir}/train", "train.csv")
X_train.to_csv(train_output_path, index=False)
预处理脚本定义后,我们需要将其上传到 S3,以便管道能够使用它:
s3_client.upload_file(
Filename="src/preprocessing.py", Bucket=write_bucket, Key=f"{write_prefix}/scripts/preprocessing.py"
)
我们可以如下定义预处理步骤。首先,我们必须创建一个 SKLearnProcessor 实例:
sklearn_processor = SKLearnProcessor(
framework_version="0.23-1",
role=sagemaker_role,
instance_count=1,
instance_type=process_instance_type,
base_job_name=f"{base_job_name_prefix}-processing",
)
SKLearnProcessor 处理需要 scikit-learn 的作业的处理任务。我们指定了 scikit-learn 框架版本以及我们之前定义的实例类型和数量。
然后将处理器添加到 ProcessingStep 以在管道中使用:
process_step = ProcessingStep(
name="DataProcessing",
processor=sklearn_processor,
inputs=[...],
outputs=[...],
job_arguments=[
"--train-ratio", "0.8",
"--validation-ratio", "0.1",
"--test-ratio", "0.1"
],
code=f"s3://{write_bucket}/{write_prefix}/scripts/preprocessing.py"
)
inputs 和 outputs 使用 ProcessingInput 和 ProcessingOutput 包装器定义,如下所示:
inputs = [ ProcessingInput(source=bank_marketing_data_uri, destination="/opt/ml/processing/bank-marketing") ]
outputs = [ ProcessingOutput(destination=f"{processing_output_uri}/train_data", output_name="train_data",
source="/opt/ml/processing/train"), ... ]
ProcessingStep 接收我们的 scikit-learn 处理器以及数据的输入和输出。ProcessingInput 实例定义了 S3 源和本地目录目标,以方便复制数据(这些是我们在预处理脚本中使用的相同本地目录)。同样,ProcessingOutput 实例接收本地目录源和 S3 目标。我们还设置了作业参数,这些参数作为 CLI 参数传递给预处理脚本。
预处理步骤设置完成后,我们可以继续进行训练。
模型训练和调整
我们以与预处理脚本相同的方式定义训练脚本:一个带有主函数的 Python 脚本,使用我们的标准 Python 工具(如 scikit-learn)训练一个 LightGBM 模型。然而,我们还需要安装 LightGBM 库本身。
安装库的另一种方法是将其构建到 Docker 镜像中,并将其用作 SageMaker 中的训练镜像。这是在 SageMaker 中管理环境的标准方式。然而,它需要大量的工作,并且需要在长期内维护镜像。或者,如果我们只需要安装少量依赖项,我们可以直接从我们的训练脚本中安装,如下所示。
我们必须定义一个辅助函数来安装包,然后使用它来安装 LightGBM:
def install(package):
subprocess.check_call([sys.executable, "-q", "-m", "pip", "install", package])
install("lightgbm")
import lightgbm as lgb
这也有一个优点,即每次我们运行训练时都会安装最新版本(或指定版本)。
在安装了包之后,训练脚本的其余部分将在预处理步骤准备的数据上训练标准的 LGBMClassifier。我们可以从脚本的参数中设置或训练数据和参数:
train_df = pd.read_csv(f"{args.train_data_dir}/train.csv")
val_df = pd.read_csv(f"{args.validation_data_dir}/validation.csv")
params = {
"n_estimators": args.n_estimators,
"learning_rate": args.learning_rate,
"num_leaves": args.num_leaves,
"max_bin": args.max_bin,
}
然后,我们必须进行标准的 scikit-learn 交叉验证评分,将模型拟合到数据,并输出训练和验证分数:
X, y = prepare_data(train_df)
model = lgb.LGBMClassifier(**params)
scores = cross_val_score(model, X, y, scoring="f1_macro")
train_f1 = scores.mean()
model = model.fit(X, y)
X_test, y_test = prepare_data(val_df)
test_f1 = f1_score(y_test, model.predict(X_test))
print(f"[0]#011train-f1:{train_f1:.2f}")
print(f"[0]#011validation-f1:{test_f1:.2f}")
如此所示,脚本接受 CLI 参数来设置超参数。这在优化阶段设置参数时被超参数调整步骤使用。我们可以使用 Python 的 ArgumentParser 来实现这个目的:
parser = argparse.ArgumentParser()
parser.add_argument("--boosting_type", type=str, default="gbdt")
parser.add_argument("--objective", type=str, default="binary")
parser.add_argument("--n_estimators", type=int, default=200)
parser.add_argument("--learning_rate", type=float, default=0.001)
parser.add_argument("--num_leaves", type=int, default=30)
parser.add_argument("--max_bin", type=int, default=300)
我们还可以看到,我们记录了训练和验证的 F1 分数,这使得 SageMaker 和 CloudWatch 能够从日志中提取数据用于报告和评估目的。
最后,我们需要将训练结果写入一个 JSON 文档中。这些结果随后可以在后续的管道处理中使用,并在 SageMaker 界面中作为作业的输出显示。该 JSON 文档存储在磁盘上,与序列化的模型文件一起:
metrics_data = {"hyperparameters": params,
"binary_classification_metrics":
{"validation:f1": {"value": test_f1},"train:f1": {"value":
train_f1}}
}
metrics_location = args.output_data_dir + "/metrics.json"
model_location = args.model_dir + "/lightgbm-model"
with open(metrics_location, "w") as f:
json.dump(metrics_data, f)
with open(model_location, "wb") as f:
joblib.dump(model, f)
与预处理步骤一样,结果被写入到本地目录中,SageMaker 会从中提取并将它们复制到 S3。
脚本定义后,我们可以在管道中创建调整步骤,该步骤训练模型并调整超参数。
我们必须定义一个类似 SKLearnProcessor 的 SageMaker Estimator,它封装了训练的配置,包括对脚本(在 S3 上)的引用:
static_hyperparams = {
"boosting_type": "gbdt",
"objective": "binary",
}
lgb_estimator = Estimator(
source_dir="src",
entry_point="lightgbm_train.py",
output_path=estimator_output_uri,
code_location=estimator_output_uri,
hyperparameters=static_hyperparams,
role=sagemaker_role,
image_uri=train_image_uri,
instance_count=train_instance_count,
instance_type=train_instance_type,
framework_version="1.3-1",
)
然后,我们可以定义我们的 SageMaker HyperparameterTuner,它执行实际的超参数调整。类似于 Optuna 或 FLAML,我们必须使用 SageMaker 包装器指定超参数的有效范围:
hyperparameter_ranges = {
"n_estimators": IntegerParameter(10, 400),
"learning_rate": ContinuousParameter(0.0001, 0.5, scaling_type="Logarithmic"),
"num_leaves": IntegerParameter(2, 200),
"max_bin": IntegerParameter(50, 500)
}
HyperparameterTuner 可以设置如下:
tuner_config_dict = {
"estimator": lgb_estimator,
"max_jobs": 20,
"max_parallel_jobs": 2,
"objective_metric_name": "validation-f1",
"metric_definitions": [{"Name": "validation-f1", "Regex": "validation-f1:([0-9\\.]+)"}],
"hyperparameter_ranges": hyperparameter_ranges,
"base_tuning_job_name": tuning_job_name_prefix,
"strategy": "Random"
}
tuner = HyperparameterTuner(**tuner_config_dict)
SageMaker 支持许多超参数调优策略,包括 Hyperband 调优。更多信息可以在超参数调优的文档中找到:docs.aws.amazon.com/sagemaker/latest/dg/automatic-model-tuning-how-it-works.xhtml。在这里,我们使用了随机搜索,最大作业大小为 20。在这里,AWS 的弹性基础设施可以发挥显著作用。如果我们增加训练实例数量,SageMaker 会自动将训练作业分配到所有机器上。配置额外的机器有一些开销并增加成本,但如果运行数千次试验,它也可以显著减少调优时间。
调优器的指标定义定义了正则表达式,用于从日志中提取结果指标,正如我们在之前的训练脚本中所示。参数优化框架相对于这里定义的指标进行优化,最小化或最大化指标。
定义了超参数调优器后,我们可以创建一个 TuningStep 以包含到管道中:
tuning_step = TuningStep(
name="LGBModelTuning",
tuner=tuner,
inputs={
"train": TrainingInput(...),
"validation": TrainingInput(...),
}
)
我们迄今为止定义的管道步骤准备数据并生成一个序列化到 S3 的训练模型。管道的下一步是创建一个 SageMaker Model,它封装了模型并用于评估、偏差和推理步骤。可以按照以下方式完成:
model = sagemaker.model.Model(
image_uri=train_image_uri,
model_data=tuning_step.get_top_model_s3_uri(
top_k=0, s3_bucket=write_bucket, prefix=model_prefix
),
sagemaker_session=sess,
role=sagemaker_role
)
inputs = sagemaker.inputs.CreateModelInput(instance_type=deploy_instance_type_param)
create_model_step = CreateModelStep(name="CensusIncomeModel", model=model, inputs=inputs)
Model 实例封装了部署和运行模型所需的所有必要配置。我们可以看到 model_data 是从调优步骤中产生的表现最好的模型中获取的。
我们迄今为止定义的管道步骤将生成处理后的数据和训练一个调优模型。处理数据在 S3 中的布局如图 9.3 所示:
图 9.3 – 处理作业结果的 S3 目录布局
如果我们需要进行部署步骤,我们可以继续进行。然而,我们将遵循最佳实践,并在管道中添加质量门,以检查模型的表现和偏差,并对其功能产生见解。
评估、偏差和可解释性
到目前为止,我们已经看到了向 SageMaker 管道添加步骤的一般模式:使用 SageMaker 的配置类设置配置,然后创建相关的管道步骤。
偏差配置
要将偏差检查添加到我们的管道中,我们必须创建以下配置:
bias_config = clarify.BiasConfig(
label_values_or_threshold=[1], facet_name="Sex", facet_values_or_threshold=[0], group_name="Age"
)
model_predictions_config = sagemaker.clarify.ModelPredictedLabelConfig(probability_threshold=0.5)
model_bias_check_config = ModelBiasCheckConfig(
data_config=model_bias_data_config,
data_bias_config=bias_config,
model_config=model_config,
model_predicted_label_config=model_predictions_config,
methods=["DPPL"]
)
BiasConfig 描述了我们要检查哪些方面(特征)。我们选择了 Sex 和 Age,这两个方面在处理人口统计数据时始终是必须检查的。
ModeLBiasCheckConfig 包装了数据配置、模型配置和偏差确认步骤。它还设置了用于偏差检查的方法。在这里,我们使用预测标签中正比例的差异(DPPL)。
DPPL 是一个衡量模型是否对数据的不同方面预测结果不同的指标。DPPL 是通过计算“a”方面和“d”方面的正面预测比例之间的差异来计算的。它通过将训练后的模型预测与数据集中最初存在的偏差进行比较,帮助评估模型预测是否存在偏差。例如,如果一个预测家庭贷款资格的模型对 70% 的男性申请人(方面“a”)预测了积极的结果,而对 60% 的女性申请人(方面“d”)预测了积极的结果,那么 10% 的差异可能表明对方面“d”存在偏见。
DPPL 公式表示如下:
DPPL = q a ′ − q d ′
在这里,q a ′ 是预测的方面“a”获得积极结果的概率,q d ′ 是类似的比例,对于二进制和多类别方面标签,标准化后的 DPPL 值介于 [-1, 1] 之间,而连续标签在 (-∞, +∞) 区间内变化。正 DPPL 值表明方面“a”相对于“d”的正面预测比例更高,表明存在正偏差。相反,负 DPPL 指示方面“d”的正面预测比例更高,表明存在负偏差。接近零的 DPPL 指示两个方面的正面预测比例相对相等,零值表示人口统计学上的完美平等。
您可以使用 ClarifyCheckStep 将偏差检查添加到管道中:
model_bias_check_step = ClarifyCheckStep(
name="ModelBiasCheck",
clarify_check_config=model_bias_check_config,
check_job_config=check_job_config,
skip_check=skip_check_model_bias_param,
register_new_baseline=register_new_baseline_model_bias_param,
supplied_baseline_constraints=supplied_baseline_constraints_model_bias_param
)
可解释性配置
可解释性的配置非常相似。我们不是创建 BiasConfig,而是必须创建 SHAPConfig:
shap_config = sagemaker.clarify.SHAPConfig(
seed=829,
num_samples=100,
agg_method="mean_abs",
save_local_shap_values=True
)
与 SHAPConfig 一起,我们必须创建 ModelExplainabilityCheckConfig 来计算 SHAP 值并创建可解释性报告:
model_explainability_config = ModelExplainabilityCheckConfig(
data_config=model_explainability_data_config,
model_config=model_config,
explainability_config=shap_config
)
然后使用 ClarifyCheckStep 将所有内容组合在一起:
model_explainability_step = ClarifyCheckStep(
name="ModelExplainabilityCheck",
clarify_check_config=model_explainability_config,
check_job_config=check_job_config,
skip_check=skip_check_model_explainability_param,
register_new_baseline=register_new_baseline_model_explainability_param,
supplied_baseline_constraints=supplied_baseline_constraints_model_explainability_param
)
评估
最后,我们还需要使用测试数据评估我们的模型。评估脚本与训练脚本非常相似,只是它从 S3 中提取调整后的模型进行评分。该脚本由一个主函数和两个步骤组成。首先,我们必须引导训练模型并进行评分(在我们的情况下,计算 F1 分数):
...
test_f1 = f1_score(y_test, model.predict(X_test))
# Calculate model evaluation score
logger.debug("Calculating F1 score.")
metric_dict = {
"classification_metrics": {"f1": {"value": test_f1}}
}
然后,我们必须将结果输出到 JSON 文件中:
# Save model evaluation metrics
output_dir = "/opt/ml/processing/evaluation"
pathlib.Path(output_dir).mkdir(parents=True, exist_ok=True)
logger.info("Writing evaluation report with F1: %f", test_f1)
evaluation_path = f"{output_dir}/evaluation.json"
with open(evaluation_path, "w") as f:
f.write(json.dumps(metric_dict))
评估 JSON 用于报告以及依赖于评估指标的后续步骤。
部署和监控 LightGBM 模型
我们现在可以添加我们管道的最终步骤以支持部署。管道的部署部分由三个步骤组成:
-
在 SageMaker 中注册模型。
-
条件检查以验证模型评估是否超过最小阈值。
-
使用 AWS Lambda 函数部署模型端点。
模型注册
要部署我们的模型,我们首先需要在 SageMaker 的 模型注册表 中注册我们的模型。
SageMaker 的模型注册表是一个中央存储库,您可以在其中管理和部署您的模型。
模型注册表提供了以下核心功能:
-
模型版本控制:每次训练并注册模型时,模型注册表中都会为其分配一个版本。这有助于您跟踪模型的各个迭代版本,这在需要比较模型性能、回滚到先前版本或在机器学习项目中保持可重复性时非常有用。
-
审批工作流程:模型注册表支持审批工作流程,模型可以被标记为“待手动审批”、“已批准”或“已拒绝”。这允许团队有效地管理其模型的生命周期,并确保只有经过批准的模型被部署。
-
模型目录:模型注册表充当一个目录,其中集中存储和访问所有模型。注册表中的每个模型都与元数据相关联,例如使用的训练数据、超参数和性能指标。
在注册我们的模型时,我们附加了从评估步骤计算出的指标。这些指标也用于模型漂移检测。
可能存在两种类型的漂移:数据漂移和模型漂移。
数据漂移指的是与我们的模型训练数据相比,输入数据的统计分布发生变化。例如,如果训练数据中男性和女性的比例为 60%到 40%,但用于预测的数据偏向于 80%男性与 20%女性,那么可能发生了漂移。
模型漂移是一种现象,即模型试图预测的目标变量的统计属性随着时间的推移以不可预见的方式发生变化,导致模型性能下降。
数据和模型漂移可能由于环境变化、社会行为、产品使用或其他在模型训练期间未考虑到的因素而发生。
SageMaker 支持漂移的持续监控。SageMaker 计算输入数据和我们所做预测的统计分布。两者都与训练数据中的分布进行比较。如果检测到漂移,SageMaker 可以向 AWS CloudWatch 生成警报。
我们可以按如下方式配置我们的指标:
model_metrics = ModelMetrics(
bias_post_training=MetricsSource(
s3_uri=model_bias_check_step.properties.CalculatedBaselineConstraints,
content_type="application/json"
),
explainability=MetricsSource(
s3_uri=model_explainability_step.properties.CalculatedBaselineConstraints,
content_type="application/json"
),
)
然后,对于漂移指标,我们必须设置DriftCheckBaselines:
drift_check_baselines = DriftCheckBaselines(
bias_post_training_constraints=MetricsSource( s3_uri=model_bias_check_step.properties.BaselineUsedForDriftCheckConstraints, content_type="application/json",
),
explainability_constraints=MetricsSource( s3_uri=model_explainability_step.properties.BaselineUsedForDriftCheckConstraints, content_type="application/json",
),
explainability_config_file=FileSource( s3_uri=model_explainability_config.monitoring_analysis_config_uri, content_type="application/json",
))
然后,我们必须创建一个包含以下代码的模型注册步骤:
register_step = RegisterModel(
name="LGBRegisterModel",
estimator=lgb_estimator,
model_data=tuning_step.get_top_model_s3_uri(
top_k=0, s3_bucket=write_bucket, prefix=model_prefix
),
content_types=["text/csv"],
response_types=["text/csv"],
inference_instances=[predictor_instance_type],
transform_instances=[predictor_instance_type],
model_package_group_name=model_package_group_name,
approval_status=model_approval_status_param,
model_metrics=model_metrics,
drift_check_baselines=drift_check_baselines
)
模型验证
条件检查使用评估步骤中的评估数据来确定模型是否适合部署:
cond_gte = ConditionGreaterThanOrEqualTo(
left=JsonGet(
step_name=evaluation_step.name,
property_file=evaluation_report,
json_path="classification_metrics.f1.value",
),
right=0.9,
)
condition_step = ConditionStep(
name="CheckCensusIncomeLGBEvaluation",
conditions=[cond_gte],
if_steps=[create_model_step, register_step, lambda_deploy_step],
else_steps=[]
)
在这里,我们创建了ConditionStep并比较了 F1 分数与0.9的阈值。如果模型的 F1 分数高于阈值,则可以继续部署。
使用 AWS Lambda 进行部署
部署脚本是一个标准的 AWS Lambda Python 脚本,它定义了一个lambda_handler函数,该函数获取 SageMaker 的客户端连接并继续创建模型端点:
def lambda_handler(event, context):
sm_client = boto3.client("sagemaker")
...
create_endpoint_config_response = sm_client.create_endpoint_config(
EndpointConfigName=endpoint_config_name,
ProductionVariants=[{
"VariantName": "Alltraffic",
"ModelName": model_name,
"InitialInstanceCount": instance_count,
"InstanceType": instance_type,
"InitialVariantWeight": 1}])
create_endpoint_response = sm_client.create_endpoint(
EndpointName=endpoint_name,
EndpointConfigName=endpoint_config_name)
值得注意的是,Lambda 函数不处理模型的请求。它仅在 SageMaker 中创建模型端点。
在 SageMaker 中,端点 是一个获取模型预测的 Web 服务。一旦模型训练完成,训练作业完成后,你需要部署模型以进行实时或批量预测。在 SageMaker 术语中,部署意味着设置端点 – 一个托管、生产就绪的模型。
SageMaker 中的端点是一个可扩展且安全的 RESTful API,你可以用它向模型发送实时推理请求。你的应用程序可以通过 REST API 或 AWS SDK 直接访问端点进行预测。它可以按需扩展和缩减实例,提供灵活性和成本效益。
SageMaker 还支持多模型端点,可以在单个端点上部署多个模型。如果许多模型使用频率不高或不是资源密集型,这个功能可以显著节省成本。
定义了 Lambda 脚本后,可以使用 LambdaStep 将其纳入管道:
lambda_deploy_step = LambdaStep(
name="LambdaStepRealTimeDeploy",
lambda_func=func,
inputs={
"model_name": pipeline_model_name,
"endpoint_config_name": endpoint_config_name,
"endpoint_name": endpoint_name,
"model_package_arn": register_step.steps[0].properties.ModelPackageArn,
"role": sagemaker_role,
"instance_type": deploy_instance_type_param,
"instance_count": deploy_instance_count_param
}
)
注意
模型端点一旦部署就会产生费用,直到其部署期间。一旦你运行了管道,就会创建一个端点。如果你只是实验或测试你的管道,你应该在完成后删除端点。
创建和运行管道
我们的所有管道步骤现在都已就绪,这意味着我们可以创建管道本身。Pipeline 构造函数接受我们已定义的名称和参数:
pipeline = Pipeline(
name=pipeline_name,
parameters=[process_instance_type_param,
train_instance_type_param,
train_instance_count_param,
deploy_instance_type_param,
deploy_instance_count_param,
clarify_instance_type_param,
skip_check_model_bias_param,
register_new_baseline_model_bias_param, supplied_baseline_constraints_model_bias_param,
skip_check_model_explainability_param, register_new_baseline_model_explainability_param, supplied_baseline_constraints_model_explainability_param,
model_approval_status_param],
我们还必须将我们定义的所有步骤作为列表参数传递,并最终更新管道:
steps=[
process_step,
train_step,
evaluation_step,
condition_step
],
sagemaker_session=sess)
pipeline.upsert(role_arn=sagemaker_role)
执行管道是通过调用 start 方法完成的:
start_response = pipeline.start(parameters=dict(
SkipModelBiasCheck=True,
RegisterNewModelBiasBaseline=True,
SkipModelExplainabilityCheck=True,
RegisterNewModelExplainabilityBaseline=True))
注意我们在这里定义的条件。在第一次运行管道时,我们必须在注册新的偏差和可解释性基线的同时跳过模型偏差和可解释性检查。
这两个检查都需要一个现有的基线来运行(否则,没有数据可以检查)。一旦建立了基线,我们就可以在后续运行中禁用跳过检查。
更多关于模型生命周期和创建基线的信息可以在 docs.aws.amazon.com/sagemaker/latest/dg/pipelines-quality-clarify-baseline-lifecycle.xhtml 找到。
结果
当管道执行时,你可以查看执行图以查看每个步骤的状态:
图 9.4 – LightGBM 人口普查收入管道成功执行
一旦管道完成,我们还可以在模型注册表中看到已注册的模型本身:
图 9.5 – SageMaker 模型注册表显示已批准的人口普查收入模型和相关端点
当选择模型时,可以查看偏差和可解释性报告。图 9.6 显示了由管道创建的模型的偏差报告。我们可以看到在 DPPL 中存在轻微的不平衡,但小于训练数据中的类别不平衡。报告表明没有强有力的证据表明存在偏差:
图 9.6 – 展示 DPPL 中轻微不平衡但小于训练数据类别不平衡的 Census Income 模型偏差报告
如 图 9.7 所示的可解释性报告显示了每个特征在 SHAP 值方面的重要性。在这里,我们可以看到 资本收益 和 国家 特征在预测的重要性方面占主导地位:
图 9.7 – 展示资本收益和 Country 特征主导重要性的 Census Income 模型的可解释性报告
偏差和可解释性报告也可以下载为 PDF 格式,可以轻松地与商业或非技术利益相关者共享。
使用端点进行预测
当然,如果我们不能使用部署的模型进行任何预测,那么我们的部署模型就没什么用了。我们可以通过 REST 调用或 Python SDK 使用部署的模型进行预测。以下是一个使用 Python SDK 的示例:
predictor = sagemaker.predictor.Predictor(endpoint_name, sagemaker_session=sess, serializer=CSVSerializer(), deserializer=CSVDeserializer())
payload = test_df.drop(["Target"], axis=1).iloc[:5]
result = predictor.predict(payload.values)
我们使用端点名称和会话获取 SageMaker Predictor,然后我们可以调用 predict 方法,传递一个 NumPy 数组(在本例中是从测试 DataFrame 获取的)。
由此,我们使用 SageMaker 创建了一个完整、端到端、生产就绪的管道。我们的管道包括数据预处理、自动模型调优、偏差验证、漂移检测以及完全可扩展的部署。
摘要
本章介绍了 AWS 和 Amazon SageMaker 作为构建和部署机器学习解决方案的平台。给出了 SageMaker 服务的概述,包括 Clarify 服务,该服务提供高级功能,如模型偏差检查和可解释性。
我们随后使用 SageMaker 服务构建了一个完整的机器学习(ML)管道。该管道包括机器学习生命周期的所有步骤,包括数据准备、模型训练、调优、模型评估、偏差检查、可解释性报告、针对测试数据的验证以及部署到云原生、可扩展的基础设施。
提供了具体的示例来构建管道中的每个步骤,强调全面自动化,旨在实现简单的重新训练和持续监控数据及模型过程。
下一章将探讨另一个名为 PostgresML 的 MLOps 平台。PostgresML 在服务器景观的基石——Postgres 数据库之上提供机器学习功能。
参考文献
| *[*1] | S. M. Lundberg 和 S.-I. Lee, 在《神经网络信息处理系统 30 卷进展》中,关于解释模型预测的统一方法, I. Guyon, U. V. Luxburg, S. Bengio, H. Wallach, R. Fergus, S. Vishwanathan 和 R. Garnett 编著,Curran Associates, Inc., 2017, 第 4765–4774 页. |
|---|---|
| *[*2] | R. P. Moro 和 P. Cortez, 银行 营销, 2012. |
第十章:使用 PostgresML 的 LightGBM 模型
在本章中,我们将探讨一个独特的 MLOps 平台,称为PostgresML。PostgresML 是一个 Postgres 数据库扩展,允许您使用 SQL 训练和部署机器学习模型。
PostgresML 和 SQL 与我们在本书中使用的 scikit-learn 编程风格有显著的不同。然而,正如我们在本章中将要看到的,在数据库级别进行机器学习模型开发和部署,在数据移动需求和推理延迟方面具有显著优势。
本章的主要内容包括以下几方面:
-
PostgresML 概述
-
开始使用 PostgresML
-
使用 PostgresML 和 LightGBM 的客户流失案例研究
技术要求
本章包括使用 PostgresML 的实际示例。我们将使用 Docker 来设置 PostgresML 环境,并建议运行示例。本章的代码可在github.com/PacktPublishing/Practical-Machine-Learning-with-LightGBM-and-Python/tree/main/chapter-10找到。
介绍 PostgresML
PostgresML(postgresml.org/)是 Postgres 的一个扩展,允许从业者在一个 Postgres 数据库上实现整个机器学习生命周期,用于文本和表格数据。
PostgresML 利用 SQL 作为训练模型、创建部署和进行预测的接口。使用 SQL 意味着模型和数据操作可以无缝结合,并自然地融入 Postgres 数据库数据工程环境中。
拥有一个共享的数据和机器学习平台有许多优势。正如我们在上一章中看到的,使用 SageMaker,在数据移动方面花费了大量的努力。这在机器学习环境中是一个常见问题,其中数据,尤其是事务数据,存在于生产数据库中,需要创建复杂的数据工程工作流程来从生产源提取数据,将数据转换为机器学习使用,并将数据加载到机器学习平台可访问的存储中(例如 SageMaker 的 S3)。
通过将数据存储与机器学习平台相结合,PostgresML 消除了在平台之间移动数据的需求,节省了大量的时间、努力、存储,并可能节省出口成本。
此外,从实时交易数据建模意味着训练数据始终是最新的(直接从记录系统中读取),而不是在刷新后受到限制。这消除了由于处理过时数据或 ETL 作业错误转换或加载数据而产生的错误。
延迟和往返次数
模型部署的典型模式,我们在前面的章节中已经展示过,是将模型部署在 Web API 后面。在微服务术语中,模型部署只是另一个服务,可以由其他服务组成以实现整体系统目标。
作为网络服务部署具有几个优点。首先,当使用如 REST 这样的网络标准进行网络调用时,与其他系统的互操作性简单直接。其次,它允许您独立部署模型代码,与系统其他部分隔离,提供弹性和独立扩展。
然而,将模型作为独立服务部署也有显著的缺点:延迟和网络往返次数。
让我们考虑一个电子商务的例子。在电子商务环境中,一个常见的机器学习问题是欺诈检测。以下是简单电子商务系统的一个系统架构图:
图 10.1 – 简化的电子商务系统架构,说明了功能服务(交易)与机器学习驱动服务(欺诈检测)之间的交互
考虑到图 10.1中的架构,新交易的流程如下:
-
交易被发送到交易服务。
-
交易服务使用新交易的详细信息调用欺诈检测服务。
-
欺诈检测服务接收新交易,从模型存储中加载相关模型(如果需要),从交易存储中加载历史数据,并将预测结果发送给交易服务。
-
交易服务接收欺诈预测,并存储带有相关分类的交易。
在此工作流程上可能存在一些变体。然而,由于交易和欺诈检测服务的分离,处理一笔新交易需要多次网络往返。进行欺诈预测还需要从交易存储中获取历史数据以供模型使用。
网络调用延迟和往返次数给交易增加了显著的开销。如果目标是实现低延迟或实时系统,则需要更复杂的架构组件——例如,为模型和交易数据提供缓存以及更高吞吐量的网络服务。
使用 PostgresML,架构可以简化如下:
图 10.2 – 使用 PostgresML 将机器学习服务与数据存储相结合,允许更简单的系统设计
虽然这个例子过于简化,但重点是,在服务导向架构中使用独立模型服务利用机器学习模型的整体过程中,会添加显著的开销。
通过 PostgresML,我们可以消除单独模型存储的需求,以及加载模型的额外开销,并且更重要的是,将数据存储调用和预测合并为数据存储层上的单个调用,中间没有网络开销。PostgresML 的基准测试发现,在云环境中,简化的架构将性能提高了 40 倍[1]。
然而,这种架构也有缺点。首先,数据库现在是一个单点故障。如果数据库不可用,所有模型和预测能力也将不可用。其次,该架构将数据存储和机器学习建模与推理的关注点混合在一起。根据用例,训练和部署机器学习模型与提供 SQL 查询和存储数据相比,有不同的服务器基础设施需求。关注点的混合可能迫使你在一项或另一项责任上做出妥协,或者显著增加数据库基础设施成本以支持所有用例。
在本节中,我们介绍了 PostgresML,并在概念层面上解释了将我们的数据存储和机器学习服务结合起来的优势。现在,我们将探讨实际设置和开始使用 PostgresML,以及一些基本功能。
开始使用 PostgresML
当然,PostgresML 依赖于已安装的 PostgreSQL。PostgresML 需要 PostgreSQL 11,同时也支持更新的版本。PostgresML 还需要在您的系统上安装 Python 3.7+。支持 ARM 和 Intel/AMD 架构。
注意
本节概述了开始使用 PostgresML 及其在撰写时的功能所需的步骤和依赖项。对于最新信息,请查看官方网站:https://postgresml.org/。运行 PostgresML 最简单的方法是使用 Docker。更多信息,请参阅使用 Docker 快速入门文档:postgresml.org/docs/guides/setup/quick_start_with_docker。
该扩展可以使用官方包工具(如 APT)安装,或者从源代码编译。一旦所有依赖项和扩展都已安装,必须更新postgresql.conf以加载 PostgresML 库,并且必须重新启动数据库服务器:
shared_preload_libraries = 'pgml,pg_stat_statements'
sudo service postgresql restart
安装 PostgresML 后,必须在您计划使用的数据库中创建扩展。这可以通过常规的 PostgreSQL 方式在 SQL 控制台中完成:
CREATE EXTENSION pgml;
检查安装,如下所示:
SELECT pgml.version();
训练模型
现在,让我们看看 PostgresML 提供的功能。如介绍中所述,PostgresML 有一个 SQL API。以下代码示例应在 SQL 控制台中运行。
训练模型的扩展函数如下:
pgml.train(
project_name TEXT,
task TEXT DEFAULT NULL,
relation_name TEXT DEFAULT NULL,
y_column_name TEXT DEFAULT NULL,
algorithm TEXT DEFAULT 'linear',
hyperparams JSONB DEFAULT '{}'::JSONB,
search TEXT DEFAULT NULL,
search_params JSONB DEFAULT '{}'::JSONB,
search_args JSONB DEFAULT '{}'::JSONB,
preprocess JSONB DEFAULT '{}'::JSONB,
test_size REAL DEFAULT 0.25,
test_sampling TEXT DEFAULT 'random'
)
我们需要提供project_name作为第一个参数。PostgresML 将模型和部署组织到项目中,项目通过其名称唯一标识。
接下来,我们指定模型的任务:分类或回归。relation_name和y_column_name设置训练运行所需的数据。关系是定义数据的数据表或视图,Y 列的名称指定关系中的目标列。
这些是训练所需的唯一参数。训练线性模型(默认)可以如下进行:
SELECT * FROM pgml.train(
project_name => 'Regression Project',
task => 'regression',
relation_name => pgml.diabetes',
y_column_name => 'target'
);
当调用pgml.train时,PostgresML 将数据复制到pgml模式中:这确保所有训练运行都是可重复的,并允许使用不同的算法或参数但相同的数据重新运行训练。relation_name和task也仅在项目第一次进行训练时需要。要为项目训练第二个模型,我们可以简化训练调用如下:
SELECT * FROM pgml.train(
'Regression Project ',
algorithm => 'lightgbm'
);
当调用此代码时,将在相同的数据上训练一个 LightGBM 回归模型。
算法参数设置要使用的学习算法。PostgresML 支持各种算法,包括 LightGBM、XGBoost、scikit-learn 的随机森林和额外树,支持向量机(SVMs)、线性模型以及如 K-means 聚类的无监督算法。
默认情况下,25%的数据用作测试集,测试数据是随机选择的。这可以通过test_size和test_sampling参数进行控制。替代测试采样方法从第一行或最后一行选择数据。
超参数优化
PostgresML 支持执行搜索:网格搜索和随机搜索。要设置 HPO 的超参数范围,使用带有search_params参数的 JSON 对象。使用search_args指定 HPO 参数。以下是一个示例:
SELECT * FROM pgml.train('Regression Project',
algorithm => 'lightgbm',
search => 'random',
search_args => '{"n_iter": 100 }',
search_params => '{
"learning_rate": [0.001, 0.1, 0.5],
"n_estimators": [20, 100, 200]
}'
);
预处理
PostgresML 还支持在训练模型时执行某些类型的预处理。与训练数据和配置一样,预处理也存储在项目中,因此可以在使用模型进行预测时应用相同的预处理。
关于预处理,PostgresML 支持对分类变量进行编码、填充缺失值和对数值进行缩放。预处理规则使用 JSON 对象通过preprocess参数设置,如下所示:
SELECT pgml.train(
…
preprocess => '{
"model": {"encode": {"ordinal": ["Ford", "Kia",
"Volkswagen"]}}
"price": {"impute": "mean", scale: "standard"}
"fuel_economy": {"scale": "standard"}
}'
);
在这里,我们对模型特征应用了序数编码。或者,PostgresML 还支持独热编码和目标编码。我们还使用价格的平均值导入缺失值(如列中的NULL所示),并对价格和燃油经济性特征应用了标准(正态)缩放。
部署和预测
训练后,PostgresML 会自动在测试集上计算适当的指标,包括 R²、F1 分数、精确度、召回率、ROC_AUC、准确率和对数损失。如果模型的键指标(回归的 R²和分类的 F1)比当前部署的模型有所提高,PostgresML 将在训练后自动部署模型。
然而,也可以使用pgml.deploy函数手动管理项目的部署:
pgml.deploy(
project_name TEXT,
strategy TEXT DEFAULT 'best_score',
algorithm TEXT DEFAULT NULL
)
PostgresML 支持的部署策略有best_score,它立即部署具有最佳关键指标的模型;most_recent,它部署最近训练的模型;以及rollback,它将当前部署回滚到之前部署的模型。
部署模型后,可以使用pgml.predict函数进行预测:
pgml.predict (
project_name TEXT,
features REAL[]
)
pgml.predict函数接受项目名称和预测特征。特征可以是数组或复合类型。
PostgresML 仪表板
PostgresML 提供了一个基于 Web 的仪表板,以便更方便地访问 PostgresML 的功能。仪表板是独立于 PostgreSQL 部署的,不是管理或充分利用 PostgresML 功能所必需的,因为所有功能也可以通过 SQL 查询访问。
仪表板提供了访问项目、模型、部署和数据快照的列表。有关训练模型的更多详细信息也可以在仪表板中找到,包括超参数设置和训练指标:
图 10.3 – 显示已训练模型列表的 PostgresML 仪表板
除了提供项目、模型和部署的视图外,仪表板还允许创建 SQL 笔记本,类似于 Jupyter 笔记本。这些 SQL 笔记本提供了一个简单的界面,用于与 PostgresML 交互,如果另一个 SQL 控制台不可用的话。
这就结束了我们关于开始使用 PostgresML 的部分。接下来,我们将查看一个从头到尾的案例研究,即训练和部署 PostgresML 模型。
案例研究 – 使用 PostgresML 进行客户流失分析
让我们回顾一下电信提供商的客户流失问题。提醒一下,数据集包括客户及其与电信提供商相关的账户和成本信息。
数据加载和预处理
在现实世界的设置中,我们的数据通常已经存在于 PostgreSQL 数据库中。然而,在我们的示例中,我们将从加载数据开始。首先,我们必须创建数据加载到的表:
CREATE TABLE pgml.telco_churn
(
customerid VARCHAR(100),
gender VARCHAR(100),
seniorcitizen BOOLEAN,
partner VARCHAR(10),
dependents VARCHAR(10),
tenure REAL,
...
monthlycharges VARCHAR(50),
totalcharges VARCHAR(50),
churn VARCHAR(10)
);
注意,在我们的表结构中,对于一些列,类型可能不符合我们的预期:例如,月度和总费用应该是实数值。我们将在预处理阶段解决这个问题。
接下来,我们可以将我们的 CSV 数据加载到表中。PostgreSQL 提供了一个COPY语句来完成此目的:
COPY pgml.telco_churn (customerid,
gender,
seniorcitizen,
partner,
...
streamingtv,
streamingmovies,
contract,
paperlessbilling,
paymentmethod,
monthlycharges,
totalcharges,
churn
) FROM '/tmp/telco-churn.csv'
DELIMITER ','
CSV HEADER;
执行此语句将读取 CSV 文件并将数据添加到我们的表中。
注意
如果你在一个 Docker 容器中运行 PostgresML(推荐以开始使用),必须首先将 CSV 文件复制到容器运行时。可以使用以下命令完成此操作(替换为你自己的容器名称):
docker cp telco/telco-churn.csv postgresml-postgres-1:/tmp/telco-churn.csv。
数据加载后,我们可以进行预处理。我们分三步进行:在表中直接清理数据,创建一个将数据强制转换为适当类型的表视图,并使用 PostgresML 的预处理功能:
UPDATE pgml.telco_churn
SET totalcharges = NULL
WHERE totalcharges = ' ';
我们必须将总费用中的空文本值替换为NULL,以便 PostgresML 稍后填充这些值:
CREATE VIEW pgml.telco_churn_data AS
SELECT gender,
seniorcitizen,
CAST(CASE partner
WHEN 'Yes' THEN true
WHEN 'No' THEN false
END AS BOOLEAN) AS partner,
...
CAST(monthlycharges AS REAL),
CAST(totalcharges AS REAL),
CAST(CASE churn
WHEN 'Yes' THEN true
WHEN 'No' THEN false
END AS BOOLEAN) AS churn
FROM pgml.telco_churn;
我们首先创建一个视图来准备训练数据。值得注意的是,执行了两种类型转换:具有Yes/No值的特征被映射到布尔类型,我们将月度和总费用转换为REAL类型(在将文本值映射到NULL之后)。我们还从视图中排除了CustomerId,因为这不能用于训练。
训练和超参数优化
我们可以按照以下方式训练我们的 LightGBM 模型:
SELECT *
FROM pgml.train('Telco Churn',
task => 'classification',
relation_name => 'pgml.telco_churn_data',
y_column_name => 'churn',
algorithm => 'lightgbm',
preprocess => '{"totalcharges": {"impute": "mean"} }',
search => 'random',
search_args => '{"n_iter": 500 }',
search_params => '{
"num_leaves": [2, 4, 8, 16, 32, 64]
}'
);
我们将视图设置为我们的关系,其中 churn 列是我们的目标特征。对于预处理,我们使用平均值来请求 PostgresML 为totalcharges特征填充缺失值。
我们还使用随机搜索的 500 次迭代和指定的搜索参数范围来设置超参数优化。
完成训练后,我们将在仪表板中看到我们的训练和部署好的模型:
图 10.4 – 在 PostgresML 仪表板中看到的训练好的 LightGBM 模型
仪表板显示了我们的模型指标和最佳性能的超参数。我们的模型实现了 F1 分数为 0.6367 和准确度为 0.8239。
如果仪表板不可用,可以使用以下 SQL 查询检索相同的信息:
SELECT metrics, hyperparams
FROM pgml.models m
LEFT OUTER JOIN pgml.projects p on p.id = m.project_id
WHERE p.name = 'Telco Churn';
训练完成后,我们的模型将自动部署并准备好进行预测。
预测
我们可以使用复合类型作为特征数据手动进行预测。这可以按照以下方式完成:
SELECT pgml.predict(
'Telco Churn',
ROW (
CAST('Male' AS VARCHAR(30)),
1,
...
CAST('Electronic check' AS VARCHAR(30)),
CAST(20.25 AS REAL),
CAST(4107.25 AS REAL)
)
) AS prediction;
我们使用 PostgreSQL 的ROW表达式来设置数据,将字面量转换为模型正确的类型。
然而,利用 PostgresML 模型更常见的方式是将预测纳入常规业务查询中。例如,在这里,我们已从原始客户数据表中选择了所有数据,并使用pgml.predict函数为每一行添加了预测:
SELECT *,
pgml.predict(
'Telco Churn',
ROW (
gender,
seniorcitizen,
…
paymentmethod,
CAST(monthlycharges AS REAL),
CAST(totalcharges AS REAL)
)
) AS prediction
FROM pgml.telco_churn;
与手动调用预测函数类似,我们使用ROW表达式将数据传递给pgml.predict函数,但选择数据来自表。
这也清楚地说明了使用 PostgresML 的优势:ML 模型的消费者可以在单个网络调用中,以最小的开销,在同一个业务事务中查询新的客户数据并获取预测。
摘要
本章提供了 PostgresML 的概述,这是一个独特的 MLOps 平台,允许在现有的 PostgreSQL 数据库上通过 SQL 查询训练和调用模型。
我们讨论了该平台在简化支持机器学习的景观以及降低面向服务架构中的开销和网络延迟方面的优势。提供了核心功能和 API 的概述。
本章以一个利用 PostgresML 解决分类问题的实际例子结束,展示了如何训练 LightGBM 模型,执行超参数优化,部署它,并在几个 SQL 查询中利用它进行预测。
在下一章中,我们将探讨使用 LightGBM 的分布式和基于 GPU 的学习。
参考文献
| *[*1] | PostgresML 比 Python HTTP 微服务快 8-40 倍,[在线]. 可在 https://postgresml.org/blog/postgresml-is-8x-faster-than-python-http-microservices找到。 |
|---|