机器学习工程师的学习心得---第五部分-训练

机器学习工程师的学习心得 — 第五部分:训练

机器学习工程师的学习心得 — 第五部分:训练

在本系列的第五部分中,我将概述创建用于训练图像分类模型、评估性能和准备部署的 Docker 容器的步骤。

AI/ML 工程师可能更愿意专注于模型训练和数据工程,但现实情况是,我们还需要了解幕后基础设施和机制。

我希望分享一些技巧,不仅是为了让你的训练运行运行,而且是如何在云资源(如 Kubernetes)上以成本效益的方式简化流程。

我将参考我之前文章中的元素,以获得最佳模型性能,所以请务必查看关于数据集的第一部分第二部分,以及关于模型评估的第三部分第四部分

下面是我将要与你分享的收获:

  • 基础设施概述

  • 构建你的 Docker 容器

  • 执行训练运行

  • 部署你的模型

基础设施概述

首先,让我简要描述一下我创建的设置,特别是关于 Kubernetes 的部分。你的设置可能完全不同,这完全没问题。我只想在基础设施上设定一个舞台,以便接下来的讨论更有意义。

图像管理系统

这是一个你部署的服务器,为你的领域专家提供了一个用户界面,用于为图像分类应用程序标记和评估图像。服务器可以作为你的 Kubernetes 集群上的一个 pod 运行,但你可能会发现运行一个具有更快磁盘的专用服务器可能更好。

图像文件存储在如下目录结构中,这是自我说明的,并且易于修改。

Image_Library/
  - cats/
    - image1001.png
  - dogs/
    - image2001.png

理想情况下,这些文件应该存储在本地服务器存储(而不是云或集群存储)上,以获得更好的性能。随着图像库的增长,这个原因将变得清晰。

云存储

云存储允许在系统之间以几乎无限和方便的方式共享文件。在这种情况下,你的管理系统上的图像库可以访问与你的 Kubernetes 集群或 Docker 引擎相同的文件。

然而,云存储的缺点是打开文件的延迟。你的图像库将拥有成千上万的图像,读取每个文件的延迟将对你的训练运行时间产生重大影响。更长的训练运行意味着使用昂贵的 GPU 处理器将产生更高的成本!

我发现加快速度的方法是在你的管理系统上创建你的图像库的tar文件,并将它们复制到云存储中。更好的做法是并行创建多个 tar 文件,每个文件包含 10,000 到 20,000 张图像。

这样你只有少数几个文件的网络延迟(一旦提取出来,这些文件就包含数千个),你就可以更早地开始训练运行。

Kubernetes 或 Docker 引擎

一个经过适当配置的 Kubernetes 集群将允许你动态地扩展/缩小节点,这样你就可以根据需要使用 GPU 硬件进行模型训练。Kubernetes 是一个相当复杂的设置,还有其他容器引擎也可以工作。

技术选项不断变化!

主要的想法是,你希望启动你需要的资源——只在你需要它们的时候——然后缩小规模以减少运行昂贵 GPU 资源的时间和成本。

一旦你的 GPU 节点启动并且 Docker 容器正在运行,你就可以将上面的tar文件提取到你的节点上的本地存储,例如emptyDir。节点通常具有高速 SSD 磁盘,非常适合这种类型的工作负载。有一个注意事项——你的节点上的存储容量必须能够处理你的图像库。

假设我们做得很好,让我们来谈谈构建你的 Docker 容器,这样你就可以在你的镜像库上训练你的模型了。

构建你的 Docker 容器

能够以一致的方式执行训练运行非常适合构建 Docker 容器。你可以“固定”库的版本,这样你就可以确切地知道你的脚本每次将如何运行。你还可以对容器进行版本控制,并在必要时回滚到已知的良好镜像。Docker 真正美妙的地方在于你几乎可以在任何地方运行容器。

在容器中运行时的权衡,尤其是在运行图像分类模型时,是文件存储的速度。你可以将任意数量的卷附加到你的容器上,但它们通常是网络附加的,因此在每次文件读取时都会存在延迟。如果你只有少量文件,这可能不是问题。但是,当处理像图像数据这样的数十万个文件时,这种延迟会累积起来!

这就是为什么使用上面概述的tar文件方法可能有益。

此外,请记住,Docker 容器可能会意外终止,所以你应该确保将重要信息存储在容器外部,例如在云存储或数据库中。我将在下面展示如何操作。

Dockerfile

知道你需要在 GPU 硬件上运行(这里我将假设 Nvidia),务必为你的 Dockerfile 选择正确的基镜像,例如带有“devel风味的nvidia/cuda,这将包含正确的驱动程序。

接下来,你将把脚本文件添加到你的容器中,以及一个“批量”脚本以协调执行。下面是一个示例 Dockerfile,然后我将描述每个脚本将执行的操作。

#####   Dockerfile   #####
FROM nvidia/cuda:12.8.0-devel-ubuntu24.04

# Install system software
RUN apt-get -y update && apg-get -y upgrade
RUN apt-get install -y python3-pip python3-dev

# Setup python
WORKDIR /app
COPY requirements.txt
RUN python3 -m pip install --upgrade pip
RUN python3 -m pip install -r requirements.txt

# Pythong and batch scripts
COPY ExtractImageLibrary.py .
COPY Training.py .
COPY Evaluation.py .
COPY ScorePerformance.py .
COPY ExportModel.py .
COPY BulkIdentification.py .
COPY BatchControl.sh .

# Allow for interactive shell
CMD tail -f /dev/null

Dockerfile 是声明性的,几乎就像构建小型服务器的食谱——每次你都知道你会得到什么。Python 库也受益于这种声明性方法。以下是一个示例requirements.txt文件,它加载了具有 CUDA 支持的 TensorFlow 库以实现 GPU 加速。

#####   requirements.txt   #####
numpy==1.26.3
pandas==2.1.4
scipy==1.11.4
keras==2.15.0
tensorflow[and-cuda]

提取图像库脚本

在 Kubernetes 中,Docker 容器可以通过emptyDir卷类型访问物理节点上的本地、高速存储。正如之前提到的,这仅在你节点的本地存储能够处理你的库大小的情况下有效。

#####   sample 25GB emptyDir volume in Kubernetes   #####
containers:
  - name: training-container
    volumeMounts:
      - name: image-library
        mountPath: /mnt/image-library
volumes:
  - name: image-library
    emptyDir:
      sizeLimit: 25Gi

你可能还想在你的云存储中添加另一个volumeMount,以便存放你的tar文件。这看起来将取决于你的提供商,或者如果你使用持久卷声明,所以这里不会详细介绍。

现在,你可以将tar文件提取到本地挂载点——理想情况下并行提取以增加性能。

训练脚本

作为 AI/ML 工程师,模型训练是我们想要花费大部分时间的地方。

这就是魔法发生的地方!

现在已经提取了图像库,我们可以创建训练-验证-测试集,加载预训练模型或构建新的模型,拟合模型,并保存结果。

一个对我帮助很大的关键技术是将最近训练好的模型作为我的基础模型。我在“微调”部分详细讨论了这一点,链接为第四部分,这样做可以缩短训练时间并显著提高模型性能。

在训练过程中,务必利用本地存储来检查点你的模型,因为模型相当大,即使 GPU 空闲时写入磁盘,你也在为 GPU 付费。

当然,这会引发一个担忧:如果在训练过程中 Docker 容器意外终止,会发生什么。从云服务提供商的角度来看,风险(希望)很低,你可能根本不希望进行不完整的训练。但如果是这种情况,你至少想要了解原因,这就是将主要日志文件保存到云存储(下面将描述)或使用 MLflow 等工具的便利之处。

评估脚本

在训练运行完成后,你已经采取了适当的预防措施保存了你的工作,现在是时候看看它的表现如何了。

通常,这个评估脚本会选取刚刚完成训练的模型。但你可能决定通过交互式会话将其指向先前的模型版本。这就是为什么脚本要独立存在。

由于这是一个独立的脚本,这意味着它需要从磁盘读取完成的模型——理想情况下是本地磁盘以提高速度。我喜欢有两个独立的脚本(训练和评估),但你可能发现将它们合并以避免重新加载模型会更好。

现在模型已经加载,评估脚本应该在训练、验证、测试和基准集中的每张图像上生成预测。我将结果保存为一个巨大的矩阵,其中包含每个类别标签的 softmax 置信度分数。所以,如果有 1,000 个类别和 100,000 张图像,那就是一个包含 1 亿个分数的表格!

我将这些结果保存在pickle文件中,然后在评分生成中使用。

分数性能脚本

通过评估脚本生成的分数矩阵,我们现在可以创建各种模型性能指标。再次强调,这个过程可以与上面的评估脚本结合,但我的偏好是使用独立的脚本。例如,我可能想要重新生成之前训练运行上的分数。看看哪种方法更适合你。

这里有一些sklearn函数,可以产生有用的见解,如 F1、对数损失、AUC-ROC、马修斯相关系数。

from sklearn.metrics import average_precision_score, classification_report
from sklearn.metrics import log_loss, matthews_corrcoef, roc_auc_score

除了为每个数据集(训练、验证、测试和基准)进行的基本统计分析之外,还有助于识别:

  • 哪些真实标签得到了最多的错误?

  • 哪些预测标签得到了最多的错误猜测?

  • 有多少真实标签到预测标签的标签对?换句话说,哪些类别容易混淆?

  • 应用最小 softmax 置信度分数阈值时,准确率是多少?

  • 在 softmax 阈值之上的错误率是多少?

  • 对于“困难”的基准集,你是否得到了足够高的分数?

  • 对于“超出范围”的基准集,你是否得到了足够低的分数?

如你所见,有多个计算,不容易得出一个单一的评估来判断训练好的模型是否足够好,可以部署到生产环境中。

实际上,对于一个图像分类模型,手动审查模型出错以及 softmax 置信度分数低的图像是有帮助的。实际上查看模型出错的地方,可以让你对模型在实际世界中的表现有一个直观的感觉。

查看第三部分Part 3,了解更多关于评估和评分的深入讨论。

导出模型脚本

到目前为止,所有的重活都已经完成了。由于你的 Docker 容器即将关闭,现在是时候将模型工件复制到云存储并为其投入使用做准备。

下面的 Python 代码示例更适用于 Keras 和 TensorFlow。这将把训练好的模型导出为saved_model。稍后,我将在下面的部署部分展示如何使用 TensorFlow Serving。

# Increment current version of model and create new directory
next_version_dir, version_number = create_new_version_folder()

# Copy model artifacts to the new directory
copy_model_artifacts(next_version_dir)

# Create the directory to save the model export
saved_model_dir = os.path.join(next_version_dir, str(version_number))

# Save the model export for use with TensorFlow Serving
tf.keras.backend.set_learning_phase(0)
model = tf.keras.models.load_model(keras_model_file)
tf.saved_model.save(model, export_dir=saved_model_dir)

此脚本还会复制其他训练运行生成的其他训练运行工件,例如模型评估结果、分数摘要和从模型训练生成的日志文件。别忘了你的标签映射,这样你可以给你的类别提供可读的名称!

批量识别脚本

你的训练运行已完成,你的模型已经评分,并导出新的版本,准备提供服务。现在是时候使用这个最新的模型来帮助你尝试识别未标记的图像。

正如我在第四部分中描述的,你可能有一系列“未知”的东西——非常好的图片,但不知道它们是什么。让你的新模型对这些提供最佳猜测,并将结果记录到文件或数据库中。现在你可以根据最接近的匹配项和分数的高低创建过滤器。这允许你的主题专家利用这些过滤器来寻找新的图像类别,添加到现有类别中,或者删除得分非常低且无用的图像。

顺便说一句,我把这一步放在了 GPU 容器中,因为你可能需要处理成千上万的“未知”图像,而加速硬件将使这项工作变得轻而易举。然而,如果你不急于完成,你可以在单独的 CPU 节点上执行这一步,并提前关闭你的 GPU 节点以节省成本。如果你的“未知”文件夹位于较慢的云存储上,这样做尤其有意义。

批量控制脚本

上述所有脚本都执行特定的任务——从提取你的图像库,执行模型训练,执行评估和评分,导出模型工件以进行部署,甚至可能是批量识别。

一键统治所有

为了协调整个流程,这个批量控制脚本为你提供了容器的入口点,以及触发所有操作的一种简单方式。务必生成一个日志文件,以防你需要分析过程中的任何失败。此外,务必将日志写入你的云存储,以防容器意外死亡。

#!/bin/bash
# Main batch control script

# Redirect standard output and standard error to a log file
exec > /cloud_storage/batch-logfile.txt 2>&1

/app/ExtractImageLibrary.py
/app/Training.py
/app/Evaluation.py
/app/ScorePerformance.py
/app/ExportModel.py
/app/BulkIdentification.py

执行你的训练运行

所以,现在是我们开始行动的时候了...

启动你的引擎!

让我们一步步准备你的图像库,启动你的 Docker 容器来训练你的模型,然后检查结果。

图像库‘tar’文件

你的图像管理系统现在应该创建一个tar文件备份你的数据。由于tar是一个单线程功能,通过并行创建多个 tar 文件,每个文件包含你数据的一部分,你可以获得显著的速度提升。

现在这些文件可以被复制到你的共享云存储中,以便进行下一步。

启动你的 Docker 容器

你为创建容器所付出的所有辛勤工作(如上所述)都将接受考验。如果你正在运行 Kubernetes,你可以创建一个 Job 来执行BatchControl.sh脚本。

在 Kubernetes 作业定义内部,您可以传递环境变量来调整脚本的执行。例如,批大小和 epoch 数量在这里设置,然后被拉入您的 Python 脚本中,这样您就可以在不更改代码的情况下改变行为。

#####   sample Job in Kubernetes   #####
containers:
  - name: training-job
    env:
      - name: BATCH_SIZE
        value: 50
      - name: NUM_EPOCHS
        value: 30
    command: ["/app/BatchControl.sh"]

一旦作业完成,请确保 GPU 节点根据您在 Kubernetes 中的缩放配置正确地缩放到零——您不希望因为简单的配置错误而承担巨额账单。

手动审查结果

训练运行完成后,您现在应该已经保存了模型工件,可以检查性能。查看指标,例如 F1 和日志损失,以及高 softmax 置信度分数的基准准确率。

如前所述,报告只讲述了部分故事。手动审查模型出错或产生低置信度分数的图像是值得花费时间和精力的。

不要忘记批量识别。务必利用这些工具来定位新的图像以填充您的数据集,或者找到新的类别。

部署您的模型

一旦您审查了模型性能并对结果满意,就是时候修改您的 TensorFlow Serving 容器,将新模型投入生产。

TensorFlow Serving 作为一个 Docker 容器提供,提供了一种非常快速和方便的方式来提供您的模型。这个容器可以监听并响应您的模型的 API 调用。

假设您的新模型版本为 7,您的导出脚本(见上文)已将模型保存在您的云共享中为/image_application/models/007。您可以使用该卷挂载启动 TensorFlow Serving 容器。在这个例子中,shareName指向版本 007 的文件夹。

#####   sample TensorFlow pod in Kubernetes   #####
containers:
  - name: tensorflow-serving
    image: bitnami/tensorflow-serving:2.18.0
    ports:
      - containerPort: 8501
    env:
      - name: TENSORFLOW_SERVING_MODEL_NAME
        value: "image_application"
    volumeMounts:
      - name: models-subfolder
        mountPath: "/bitnami/model-data"

volumes:
  - name: models-subfolder
    azureFile:
      shareName: "image_application/models/007"

这里有一个细微的提示——导出模型脚本应该创建一个名为 models/007 的子文件夹,也命名为 007,用于保存模型导出。这看起来可能有点冗余,但 TensorFlow Serving 会将这个共享文件夹挂载为/bitnami/model-data,并检测其中的编号子文件夹以确定要服务的版本。这将允许您查询模型版本(有利于日志记录)以及识别。

结论

如我在本文开头所提到的,这种设置对我来说是有效的。这当然不是解决这个挑战的唯一方法,我邀请您根据自己的需求定制自己的解决方案。

我想分享我在拥抱 Kubernetes 中的云服务时所获得的艰难经验,目的是控制成本。当然,在保持高水平的模型性能的同时完成所有这些工作是一个额外的挑战,但这是一个您可以实现的挑战。

我知道上面提供的信息主要是过程的大纲,但我希望这已经提供了足够的信息,以便您可以填补您自己应用的其余部分。

学习愉快!

posted @ 2026-03-27 10:30  布客飞龙III  阅读(1)  评论(0)    收藏  举报