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

介绍

在任何机器学习项目中,理解所开发模型的行为、性能和特征是重要的。清晰、信息丰富的可视化在这一理解过程中起着至关重要的作用,提供了关于模型模式、错误和效率的见解。

在本指南的这一部分,我们查看一个与回归任务相关的常见和有用图形的生成和存储有关的笔记本。

我们将探讨两种主要的记录图表的方法,以及我们记录的模型:

  • 通过 ``mlflow.log_figure()`` 进行直接绘图日志记录 我们将使用一个生成的绘图的内存中图形引用。

  • 通过 mlflow.log_artifact() 记录本地绘图文件,以便我们可以将本地存储的图像记录到运行中。

模型分析中的可视化角色

可视化作为窥探机器学习模型复杂世界的窗口。它们使得探索各个方面成为可能:

  • 理解数据:初始的可视化有助于深入挖掘数据,揭示模式、异常和关系,这些都可以为整个建模过程提供信息。

  • 模型评估: 残差图和预测误差图等图表有助于诊断模型的问题并评估其性能。

  • 超参数调优: 可视化有助于理解不同超参数对模型性能的影响,指导选择过程。

  • 错误分析:它们有助于分析模型所犯错误的类型和模式,为可能的改进提供见解。

关于程序生成图表的警告

在本指南的这一小节的配套笔记本中,您将观察到在函数内部声明的图表。这种方法与机器学习教程和指南中常见的示例有所不同,因此有必要澄清为什么选择这种方法来提供示例。

核心问题:状态性

笔记本状态

不按顺序执行所有单元格可能会导致误导性的图表

Notebooks 本质上会在单元格之间保持状态。虽然这一特性可能有益,但它对确保代码和输出的可靠性和准确性提出了重大挑战,尤其是在可视化方面。

乱序执行的挑战

笔记本环境中最重要的一个问题是有可能出现乱序执行。单元格可以按任意顺序运行,导致变量或输出不能反映最新的代码更改。这个问题在可视化方面尤为严重。如果一个图表在生成后在另一个单元格中显示,乱序运行单元格可能会导致显示过时或不正确的可视化内容。

确保准确的视觉呈现

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

为什么要使用函数来生成图表

为了缓解这些挑战,示例代码选择在函数内部声明图表。这种方法提供了几个优点:

  • 封装: 通过将绘图生成封装在一个函数中,代码确保每次调用函数时都会根据数据的当前状态生成绘图。这种封装避免了由于单元格执行顺序不当影响绘图准确性的问题。

  • 灵活性和可重用性: 函数提供了在不重复代码的情况下生成具有不同参数和数据的图表的灵活性。这种可重用性增强了代码的可维护性和可读性。

  • 与 MLflow 集成:函数与 MLflow 无缝集成,允许将图表与指标、参数和模型一起记录,确保可视化与特定的运行和模型状态相对应。这种集成在 MLflow UI 中提供了模型、指标和图表的可靠且统一的视图,避免了在笔记本中可能出现的分散视图。

  • 避免在标准输出中显示:基于函数的方法避免了将图直接打印到笔记本的标准输出中。直接打印可能会使笔记本变得杂乱,增加保存的笔记本的大小,并导致在笔记本中显示多个图时产生混淆。通过将图直接记录在MLflow中,示例代码保持了笔记本的整洁,确保图与特定的模型运行相对应,并利用MLflow的UI来查看和比较图。

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

将可视化与 MLflow 集成的优势

将可视化与 MLflow 集成提供了几个实质性的好处:

  • 持久存储:将可视化与模型一起存储在 MLflow 中,确保它们在未来参考中的可用性,防止由于会话终止或其他问题导致的丢失。

  • 出处: 它为可视化提供了清晰的出处,确保它们提供的见解总能追溯到确切的模型版本和数据集。

  • 一致性:确保可视化与模型的正确版本相对应,防止混淆和错误。

  • 可访问性:使可视化内容对所有团队成员都易于访问,增强了协作和见解分享。

生成图表

在本指南这一部分的配套笔记本中,有许多与回归相关的图表示例。一些图表,如相关矩阵图,与特征数据集相关,而其他图表,如系数图,仅在我们有一个训练好的模型后才相关。

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

定义一个图表

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

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无缝兼容,确保了相同级别的组织和访问,同时增加了图表访问和使用的灵活性。

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() 时使用 matplotlibFigure 对象引用。对于相关矩阵,我们操作的是本地保存的 .png 图像文件。这需要使用更通用的artifact writer(它支持任何文件类型) 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, artifact_path="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() 记录的图表,我们都能够看到与我们的数据和训练模型相关的运行相关图表,捕捉到运行时的状态。

在UI中查看图表

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

挑战

你能想到一些与数据验证、回归建模或一般预测质量相关的其他图表吗?

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

Download the notebook

下载笔记本并在 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,可以在模型训练的上下文中进行作用域限定,从而消除状态不一致的问题,确保每个图表准确反映训练时数据和模型的状态。