利用可视化和 MLflow 进行深入模型分析
简介
在任何机器学习项目中,了解已开发模型的行为、性能和特征都非常重要。清晰、信息丰富的可视化在这种理解中起着至关重要的作用,可以深入了解模型的模式、误差和效率。
在本指南的这一部分中,我们将研究一个与生成和存储与回归任务相关的常见且有用的绘图的 notebook。
我们将研究两种主要的绘图记录方法以及我们记录的模型
- 通过
mlflow.log_figure()
进行直接绘图记录,我们将使用对生成的绘图的内存中图形引用。 - 通过
mlflow.log_artifact()
记录本地绘图文件,以便我们可以将本地存储的图像记录到运行中。
可视化在模型分析中的作用
可视化就像一扇窗户,通向复杂的机器学习模型世界。它们能够探索各个方面
- 理解数据:初始可视化可以深入了解数据,揭示可以为整个建模过程提供信息的模式、异常和关系。
- 模型评估:残差图和预测误差图等绘图有助于诊断模型问题并评估其性能。
- 超参数调整:可视化有助于理解不同的超参数对模型性能的影响,从而指导选择过程。
- 误差分析:它们有助于分析模型产生的误差类型和模式,从而深入了解可能的改进。
关于过程式绘图生成的一个警告
在本指南的本小节的配套 notebook 中,您会观察到在函数中声明的绘图。这种方法与机器学习教程和指南中看到的典型示例不同,因此必须澄清为什么为提供的示例选择此方法。
核心问题:状态性
Notebook 本质上在单元格之间保持状态。虽然此功能可能是有益的,但它给确保代码和输出(尤其是可视化)的可靠性和准确性带来了重大挑战。
乱序执行的挑战
Notebook 环境中最重要的问题之一是可能出现乱序执行。单元格可以按任何顺序运行,从而导致变量或输出不反映最新代码更改的状态。对于可视化,这个问题尤其严重。如果在单独的单元格中生成并显示绘图,则乱序运行单元格可能会导致显示过时或不正确的可视化。
确保准确的可视化
为了使可视化能够实现传达准确、清晰和可靠信息的目的,它们必须与当前的数据和模型状态相对应。在 notebook 环境中确保这种对应关系需要仔细管理单元格执行顺序和状态,这可能很麻烦且容易出错。
为什么使用函数来生成绘图
为了减轻这些挑战,示例代码选择在函数中声明绘图。这种方法具有以下几个优点
- 封装:通过在函数中封装绘图生成,代码可确保每次调用函数时都使用数据的当前状态生成绘图。这种封装避免了乱序单元格执行影响绘图准确性的陷阱。
- 灵活性和可重用性:函数提供了使用不同参数和数据生成绘图的灵活性,而无需复制代码。这种可重用性增强了代码的可维护性和可读性。
- 与 MLflow 集成:函数与 MLflow 无缝集成,允许将绘图与指标、参数和模型一起记录,确保可视化与特定的运行和模型状态相对应。这种集成在 MLflow UI 中提供了模型、指标和绘图的可靠且统一的视图,避免了 notebook 中可能出现的不连贯视图。
- 避免在 Stdout 中显示:基于函数的方法避免了将绘图直接打印到 notebook 的 stdout。直接打印可能会使 notebook 混乱,增加保存的 notebook 的大小,并导致在 notebook 中显示多个绘图时出现混乱。通过直接在 MLflow 中记录绘图,示例代码保持 notebook 的清洁,确保绘图与特定的模型运行相对应,并利用 MLflow 的 UI 来查看和比较绘图。
通过将绘图的生成封装并限定在训练上下文中(在 mlflow.start_run()
中),我们可以获得 notebook 带来的命令式迭代代码开发的所有灵活性、易用性和好处,而无需冒着记录陈旧、无效或不准确的绘图的风险,这些绘图不能反映已记录的数据或模型的实际状态。
将可视化与 MLflow 集成的优势
将可视化与 MLflow 集成具有以下几个实质性优势
- 持久存储:将可视化与模型一起存储在 MLflow 中可确保它们可供将来参考,防止因会话终止或其他问题而造成的丢失。
- 溯源:它为可视化提供了清晰的溯源,确保它们提供的见解始终可以追溯到确切的模型版本和数据集。
- 一致性:确保可视化与模型的正确版本相对应,防止混淆和错误。
- 可访问性:使所有团队成员都可以轻松访问可视化,从而增强协作和见解共享。
生成绘图
在本指南的本节的配套 notebook 中,提供了许多回归相关绘图的示例。一些绘图(例如相关矩阵图)与特征数据集相关,而另一些绘图(例如系数图)仅在我们拥有训练好的模型后才相关。
无论我们是否使用训练好的模型,记录这些图像工件的方法都是相似的。
定义绘图
在复杂的数据可视化世界中,绘图的结构化和有组织的呈现至关重要。以下是生成箱形图的示例,该箱形图将连续变量与分类(有序)变量进行比较。该示例利用了典型的 matplotlib
实现,并通过 seaborn
增强,以获得精美的视觉外观。这种结构对于确保建模代码的清晰度和可读性至关重要。通过将绘图生成定义为单独的可调用函数,我们保持了代码库的干净和组织。这种方法至关重要,尤其是在 notebook 环境中,以确保每次训练迭代都有对绘图生成的特定且明确的引用,该引用直接链接到训练迭代中使用的数据的确切状态。这种方法减轻了与声明式定义和物化绘图相关的风险,如果数据修改后未重新生成绘图,则可能导致数据表示不一致和错误。
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 中的可读性和视觉吸引力至关重要。它可以确保绘图保持可读和清晰,而与查看平台或屏幕大小无关。
- 坐标轴标签:正确标记的坐标轴是可理解和自给自足的绘图的支柱。它们提供有关数据维度的清晰信息,使绘图无需外部参考或解释即可理解。
- 图形关闭:在返回图形之前关闭图形可确保干净且整洁的 notebook 环境。它可以防止在 notebook 的标准输出中意外显示绘图,从而避免混淆并保持 notebook 的组织。
- 图例删除:从绘图中删除自动生成的图例可增强视觉清晰度和可读性。它可以防止不必要的混乱,使绘图更加简洁明了,确保焦点保持在重要的数据表示上。
定义要本地保存的绘图
在某些情况下,先在本地保存绘图然后再记录到 MLflow 中更有利。下面的示例说明了相关矩阵图的生成,在调用时保存图像,而不是返回内存中的引用。这种方法虽然不同,但与 MLflow 仍然无缝兼容,确保相同的组织和访问级别,并在绘图访问和使用方面具有额外的灵活性。
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)
关键见解
- 相关性的热图:在这种情况下使用热图提供了特征相关性的直观和有效的表示。它可以轻松识别不同特征之间的关系,从而增强理解和分析深度。
- 标题和布局调整:包含清晰且描述性的标题以及布局调整可确保清晰度和紧凑的呈现,从而提高绘图的可用性和解释的简易性。
- 本地保存绘图:在本地保存图形可提供轻松的访问和参考,确保它不与 notebook 的执行状态相关联。它在访问方面提供了灵活性,并确保绘图保持独立可用,从而有助于更有组织和高效的数据分析和模型评估过程。
记录绘图图像
在主 notebook 中的以下代码片段中,我们将训练和绘图生成作为一个原子操作执行。如前所述,这有助于确保无论 notebook 中任何其他单元格的状态如何,生成的绘图都将引用用于训练和评估模型的训练数据的状态。
对于除相关矩阵之外的所有绘图,我们在调用 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 不允许(文件名将按原样传输)。
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()
记录的,我们都可以看到与我们的数据和训练模型相关的运行相关的绘图,从而捕获运行时的状态。
挑战
您能想到一些对数据验证、回归建模或总体预测质量有用的其他绘图吗?
如果您有兴趣,请点击下面的按钮获取 notebook 的副本,并按照说明进行操作。
下载 notebook 并使用 Jupyter 打开后
- 实现更多具有代表性的绘图,这些绘图是您在训练(或重新训练)这样的模型时希望看到的。
- 不要返回图形,而是将每个绘图保存到一个通用目录中。
- 确保所有绘图文件名都是唯一的,并且可以指示绘图内容。
- 使用
mlflow.log_artifacts()
(而不是mlflow.log_artifact()
)将目录内容记录到运行中。 - 验证 MLflow UI 中绘图的呈现。
log_artifacts()
API 具有一个可选的 artifact_path
参数,可以从默认值 None
覆盖,以将这些附加绘图隔离到 MLflow 工件存储(和 UI)中的自己的目录中。如果您正在记录数十个在它们之间具有不同分类分组的绘图,而无需在工件查看器中的 UI 显示窗格中填充大量文件,这将非常有用。
结论
可视化是构建高质量模型的关键部分。凭借其与记录图形、绘图和图像的本机集成,MLflow 可以非常简单地集成可视化,不仅用于训练中使用的数据,还用于训练事件的结果。
借助可以在模型训练的上下文中限定范围的简单、高级 API,可以消除状态不一致,从而确保每个绘图都准确反映训练时的数据和模型状态。