跳到主要内容

利用可视化和 MLflow 进行深入的模型分析

简介

在任何机器学习项目中,理解已开发模型的行为、性能和特征都很重要。清晰、信息丰富 的可视化在理解中起着至关重要的作用,它能深入了解模型的模式、错误和效率。

在本指南的这一部分,我们将介绍一个与回归任务相关图表的生成和存储的笔记本。

我们将研究两种主要的记录图表与我们记录的模型的方法

  • 直接记录图表 通过 mlflow.log_figure(),我们将使用内存中的图表引用来显示生成的图表。
  • 记录本地图表文件 通过 mlflow.log_artifact(),允许我们将本地存储的图像记录到运行中。

可视化在模型分析中的作用

可视化充当了机器学习模型错综复杂世界的窗口。它们能够探索各个方面

  • 理解数据:初始可视化可以深入了解数据,揭示模式、异常值和关系,这些都可以为整个建模过程提供信息。
  • 模型评估:残差图和预测误差图等图表有助于诊断模型问题和评估其性能。
  • 超参数调优:可视化有助于理解不同超参数对模型性能的影响,从而指导选择过程。
  • 错误分析:它们有助于分析模型产生的错误类型和模式,从而深入了解可能的改进之处。

关于程序化生成图表的警告

在本指南的这一子部分附带的笔记本中,您将看到在函数中声明的图表。这种方法不同于机器学习教程和指南中常见的示例,因此有必要澄清为什么为提供的示例选择此方法。

核心问题:状态性

Notebook state

未能按顺序执行所有单元格可能导致图表产生误导

笔记本本身会在各个单元格之间维护状态。虽然此功能可能很有益,但它对确保代码和输出的可靠性和准确性构成了重大挑战,尤其是对于可视化。

乱序执行的挑战

笔记本环境中最重要的一个问题是乱序执行的可能性。单元格可以按任何顺序运行,从而导致变量或输出无法反映最新的代码更改。对于可视化来说,这个问题尤为严重。如果生成了图表,然后在单独的单元格中显示它,那么乱序执行单元格可能会导致显示过时或不正确的可视化。

确保准确的可视化

为了使可视化能够传达准确、清晰和可靠的信息,它们必须与当前的数据和模型状态相对应。在笔记本环境中确保这种对应关系需要仔细管理单元格的执行顺序和状态,这可能会很麻烦且容易出错。

为什么使用函数生成图表

为了减轻这些挑战,示例代码选择在函数中声明图表。这种方法提供了几个优点

  • 封装:通过将图表生成封装在函数中,代码可确保每次调用函数时,图表都使用当前的数据状态生成。这种封装避免了乱序单元格执行影响图表准确性的陷阱。
  • 灵活性和可重用性:函数提供了生成具有不同参数和数据的图表的灵活性,而无需重复代码。这种可重用性提高了代码的可维护性和可读性。
  • 与 MLflow 集成:函数与 MLflow 无缝集成,允许将图表与指标、参数和模型一起记录,确保可视化与特定的运行和模型状态相对应。这种集成在 MLflow UI 中提供了模型、指标和图表的可靠且集成的视图,避免了在笔记本中可能出现的断开的视图。
  • 避免显示在标准输出中:基于函数的方法避免了将图表直接打印到笔记本的标准输出。直接打印会使笔记本混乱,增加保存的笔记本大小,并可能因笔记本中显示多个图表而引起混淆。通过直接在 MLflow 中记录图表,示例代码可保持笔记本整洁,确保图表与特定模型运行相对应,并利用 MLflow 的 UI 来查看和比较图表。

通过将图表的生成封装并限定在训练上下文(在 mlflow.start_run() 中)内,我们可以获得笔记本带来的命令式迭代代码开发的所有灵活性、易用性和优势,而无需承担记录陈旧、无效或不准确的图表的风险,这些图表无法反映所记录的数据或模型的实际状态。

将可视化与 MLflow 集成的好处

将可视化与 MLflow 集成带来了几个显著的好处

  • 持久存储:将可视化与模型一起存储在 MLflow 中,可确保它们可供将来参考,防止因会话终止或其他问题而丢失。
  • 溯源:它为可视化提供了清晰的溯源,确保它们提供的见解始终可以追溯到确切的模型版本和数据集。
  • 一致性:确保可视化与正确版本的模型相对应,从而防止混淆和错误。
  • 可访问性:使所有团队成员都能轻松访问可视化,从而增强协作和见解共享。

生成图表

在本节配套的笔记本中,我们提供了许多与回归相关的图表示例。有些,例如相关矩阵图,与特征数据集相关,而另一些,例如系数图,仅在我们拥有已训练模型后才相关。

无论我们是否使用已训练的模型,记录这些图像构件的方法都类似。

定义图表

在复杂的数据可视化世界中,图表的结构化和有序呈现至关重要。下面是一个生成箱线图的示例,它将连续变量与分类(有序)变量进行比较。该示例利用了典型的 matplotlib 实现,并使用 seaborn 进行了增强,以获得精美的视觉效果。这种结构对于确保我们建模代码的清晰性和可读性至关重要。通过将图表生成定义为一个单独的可调用函数,我们可以维护一个清晰有序的代码库。这种方法至关重要,尤其是在笔记本环境中,以确保每次训练迭代都有一个特定且明确的图表生成引用,直接链接到训练迭代中使用的数据的确切状态。这种方法可以减轻声明性定义和具体化图表带来的风险,因为如果这些图表在数据修改后未重新生成,可能会导致数据表示不一致和错误。

python
def plot_box_weekend(df, style="seaborn", plot_size=(10, 8)):
with plt.style.context(style=style):
fig, ax = plt.subplots(figsize=plot_size)
sns.boxplot(data=df, x="weekend", y="demand", ax=ax, color="lightgray")
sns.stripplot(
data=df,
x="weekend",
y="demand",
ax=ax,
hue="weekend",
palette={0: "blue", 1: "green"},
alpha=0.15,
jitter=0.3,
size=5,
)

ax.set_title("Box Plot of Demand on Weekends vs. Weekdays", fontsize=14)
ax.set_xlabel("Weekend (0: No, 1: Yes)", fontsize=12)
ax.set_ylabel("Demand", fontsize=12)
for i in ax.get_xticklabels() + ax.get_yticklabels():
i.set_fontsize(10)
ax.legend_.remove()
plt.tight_layout()
plt.close(fig)
return fig

关键要素

  • 标题应用:包含标题不仅仅是形式,更是确保清晰度和可理解性的必要条件,尤其是在 MLflow UI 中。精心制作的标题提供了全面的概述,有助于立即理解并消除任何歧义或混淆。
  • 覆盖默认大小:调整字体和绘图大小等各种元素的默认大小对于确保绘图在 MLflow UI 中的可读性和视觉吸引力至关重要。它确保绘图在任何查看平台或屏幕尺寸下都保持可读且清晰。
  • 轴标签:正确标记的轴是可理解和独立的图表的支柱。它们提供了关于数据维度的清晰信息,使得图表无需外部参考或解释即可理解。
  • 图形关闭:在返回图形之前关闭图形可确保笔记本环境干净整洁。它可防止图形意外显示在笔记本的标准输出中,从而避免混淆并保持笔记本的组织性。
  • 图例移除:从图中移除自动生成的图例可提高视觉清晰度和可读性。它可防止不必要的混乱,使图表更加简洁明了,确保重点始终放在重要的数据表示上。

定义要本地保存的图表

在某些情况下,在记录到 MLflow 之前将图表本地保存比返回内存引用更有优势。下面的示例演示了相关矩阵图的生成,在调用时保存图像,而不是返回内存引用。虽然这种方法有所不同,但它仍然与 MLflow 无缝兼容,可确保相同的组织和访问级别,并且在图表访问和使用方面具有额外的灵活性。

python
def plot_correlation_matrix_and_save(
df, style="seaborn", plot_size=(10, 8), path="/tmp/corr_plot.png"
):
with plt.style.context(style=style):
fig, ax = plt.subplots(figsize=plot_size)

# Calculate the correlation matrix
corr = df.corr()

# Generate a mask for the upper triangle
mask = np.triu(np.ones_like(corr, dtype=bool))

# Draw the heatmap with the mask and correct aspect ratio
sns.heatmap(
corr,
mask=mask,
cmap="coolwarm",
vmax=0.3,
center=0,
square=True,
linewidths=0.5,
annot=True,
fmt=".2f",
)

ax.set_title("Feature Correlation Matrix", fontsize=14)
plt.tight_layout()

plt.close(fig)
# convert to filesystem path spec for os compatibility
save_path = pathlib.Path(path)
fig.savefig(path)

关键见解

  • 相关性热图:在这种情况下使用热图提供了一种直观有效的特征相关性表示。它允许轻松识别不同特征之间的关系,从而提高可理解性和分析深度。
  • 标题和布局调整:包含清晰的描述性标题以及布局调整,可确保清晰紧凑的演示,从而提高图表的使用性和解释的便利性。
  • 图表的本地保存:将图形本地保存可提供便捷的访问和参考,确保它不与笔记本的执行状态绑定。它提供了访问灵活性,并确保图表独立可用,从而有助于更组织高效的数据分析和模型评估过程。

记录图表图像

在主笔记本的下面代码片段中,我们将训练和图表生成作为一个原子操作执行。如前所述,这有助于确保无论笔记本中任何其他单元格的状态如何,生成的图表都将引用用于训练和评估模型的数据状态。

对于除相关矩阵外的所有图表,我们在调用 mlflow.log_figure() 时使用图表的直接 matplotlib Figure 对象引用。对于相关矩阵,我们处理的是本地保存的 .png 图像文件。这需要使用更通用的构件编写器(它支持任何文件类型)mlflow.log_artifact()

注意

为了简单起见,如果您有大量要记录到模型的图表,建议使用作用域为目录的 mlflow.log_artifacts()。此 API 将记录给定本地目录路径中的所有文件,而无需显式命名每个文件并进行大量的 log_artifact() 调用。如果使用基于目录的 log_artifacts(),请确保您的本地文件名足够相关和有说服力,以便在 MLflow UI 中区分图表的内容。虽然 log_artifact() 允许您在将文件记录到 MLflow 时重命名给定文件的名称,但批量处理的 log_artifacts() API 则不允许(文件名将按原样传输)。

python
mlflow.set_tracking_uri("http://127.0.0.1:8080")

mlflow.set_experiment("Visualizations Demo")

X = my_data.drop(columns=["demand", "date"])
y = my_data["demand"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)

fig1 = plot_time_series_demand(my_data, window_size=28)
fig2 = plot_box_weekend(my_data)
fig3 = plot_scatter_demand_price(my_data)
fig4 = plot_density_weekday_weekend(my_data)

# Execute the correlation plot, saving the plot to a local temporary directory
plot_correlation_matrix_and_save(my_data)

# Define our Ridge model
model = Ridge(alpha=1.0)

# Train the model
model.fit(X_train, y_train)

# Make predictions
y_pred = model.predict(X_test)

# Calculate error metrics
mse = mean_squared_error(y_test, y_pred)
rmse = math.sqrt(mse)
mae = mean_absolute_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
msle = mean_squared_log_error(y_test, y_pred)
medae = median_absolute_error(y_test, y_pred)

# Generate prediction-dependent plots
fig5 = plot_residuals(y_test, y_pred)
fig6 = plot_coefficients(model, X_test.columns)
fig7 = plot_prediction_error(y_test, y_pred)
fig8 = plot_qq(y_test, y_pred)

# Start an MLflow run for logging metrics, parameters, the model, and our figures
with mlflow.start_run() as run:
# Log the model
mlflow.sklearn.log_model(sk_model=model, input_example=X_test, name="model")

# Log the metrics
mlflow.log_metrics(
{"mse": mse, "rmse": rmse, "mae": mae, "r2": r2, "msle": msle, "medae": medae}
)

# Log the hyperparameter
mlflow.log_param("alpha", 1.0)

# Log plots
mlflow.log_figure(fig1, "time_series_demand.png")
mlflow.log_figure(fig2, "box_weekend.png")
mlflow.log_figure(fig3, "scatter_demand_price.png")
mlflow.log_figure(fig4, "density_weekday_weekend.png")
mlflow.log_figure(fig5, "residuals_plot.png")
mlflow.log_figure(fig6, "coefficients_plot.png")
mlflow.log_figure(fig7, "prediction_errors.png")
mlflow.log_figure(fig8, "qq_plot.png")

# Log the saved correlation matrix plot by referring to the local file system location
mlflow.log_artifact("/tmp/corr_plot.png")

在 UI 中查看图表

执行此训练单元格后,如果我们转到 MLflow UI,我们将可以在构件查看器窗格中看到所有定义的图表。无论图表是使用 log_figure() API 记录的,还是从本地文件系统获取并通过 log_artifacts() 记录的,我们都能看到与我们的数据和已训练模型相关的运行图表,捕获了进行运行时的状态。

在 MLflow UI 中查看已记录的图表和图形

挑战

您能想到一些对于数据验证、回归建模或一般预测质量相关的附加图表吗?

如果您有兴趣,可以通过点击下面的按钮获取笔记本的副本,并按照说明进行操作。

下载笔记本

下载笔记本并用 Jupyter 打开后

  1. 实现一些更能代表您在训练(或重新训练)此类模型时希望看到的图表。
  2. 不要返回图形,而是将每个图表保存到一个公共目录。
  3. 确保所有图表文件名都是唯一的,并且能指示图表内容。
  4. 使用 mlflow.log_artifacts()(而不是 mlflow.log_artifact())来记录目录内容到运行中。
  5. 验证 MLflow UI 中图表的渲染情况。
提示

log_artifacts() API 有一个可选的 artifact_path 参数,可以从默认的 None 中覆盖,以便将这些附加图表隔离到 MLflow 构件存储(和 UI)中的自己的目录中。如果您要记录大量图表,并且这些图表之间存在明显的类别分组,那么这会非常有益,而无需在构件查看器的主根目录中显示大量文件来填充 UI 显示窗格。

结论

可视化是构建高质量模型的重要组成部分。MLflow 通过其记录图形、图表和图像的原生集成,可以非常轻松地纳入可视化,不仅用于训练数据,还用于训练事件的结果。

通过可以在模型训练的上下文中进行限定的简单、高级 API,可以消除状态不一致,从而确保每个图表都准确反映训练时数据和模型的状态。