TowardsDataScience 2023 博客中文翻译(一百七十)

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

如何将 PyTorch 模型部署为生产就绪的 API

原文:towardsdatascience.com/how-to-deploy-pytorch-models-as-production-ready-apis-f61136fd0244

一个将 PyTorch Lightning 和 BentoML 结合的端到端用例

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Ahmed Besbes

·发表于 Towards Data Science ·阅读时间 12 分钟·2023 年 4 月 3 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 SpaceX 提供,来自 Unsplash

作为一名机器学习工程师,我在处理生产环境中的深度学习模型时经常遇到两个主要挑战。

第一个挑战是需要为每个项目重写模板代码,以处理训练循环、数据加载或度量计算等任务。由于这项工作常常使代码库变得更加复杂,并增加了不必要的抽象,这减慢了迭代过程。

第二个挑战涉及到将训练好的模型高效部署到云端所需的广泛技能或工具。这包括 Docker、API 或云服务,更不用提与 GPU 支持、多线程或可扩展性相关的知识。

如果你是数据科学家,你显然不需要了解所有这些。

幸运的是,有两个 Python 框架被设计用来缓解这些问题: Pytorch Lightning BentoML*。

在本文中,我们将结合这些框架来构建* 一个生产就绪的图像分类服务 并将其部署到 Kubernetes 原生环境中。

让我们来看看 🔍

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

工作流 — 作者提供的图像

1 — 使用 PyTorch Lightning ⚡ 训练多标签图像分类器

本节将重点介绍 PyTorch Lightning,并详细介绍其一些功能以及训练过程。

作为数据集,我们将使用来自 Kaggle 的 CelebFaces Attributes:它包含 +200K 张各种名人的面部图像,附加了诸如身份、地标位置和 每张图像 40 个二进制属性注释 等元数据。

我们将使用面部属性元数据来训练一个多标签分类器,以检测诸如发色、鼻子大小、秃顶等信息。

如前所述,我们将使用 Pytorch Lightning 来训练模型。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

GIF 由作者修改

PyTorch Lightning 是 PyTorch 的一个轻量级封装,标准化了训练循环、数据加载和验证过程的许多方面。它提供了一个更高级的接口,简化了训练过程,并减少了所需的样板代码。

使用 PyTorch Lightning,你可以减少代码复杂性,提高可重现性,并加快开发速度。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供

要获取数据集,你需要登录到 Kaggle 并点击下载按钮。

你也可以在获得你的凭证 (kaggle.json) 后通过命令行下载它。

!cp kaggle.json ~/.kaggle/
!chmod 600 ~/.kaggle/kaggle.json
!kaggle datasets download jessicali9530/celeba-dataset
!unzip celeba-dataset.zip

解压文件夹后你会得到以下内容:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供

**list_attr_celeba.csv** 文件包含了我们感兴趣的属性:

import pandas as pd 

data = pd.read_csv("list_attr_celeba.csv")
data = data.replace(-1, 0)
data = data.sample(25000)

data.head(5)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

截图由作者提供

图像位于 img_align_celeba/img_align_celeba/ 文件夹中。

现在我们开始训练这个模型:

👉 创建 Dataset

就像在 PyTorch 中一样,我们首先需要定义一个 Dataset。这是指示数据如何从磁盘读取并转换为张量的第一步。

# src/train/datasets.py

import os
from PIL import Image
import pandas as pd
import torch
from torchvision.transforms.transforms import Compose
from torch.utils.data import Dataset, DataLoader
import pytorch_lightning as pl
from src.train.utils import train_transform, valid_transform

class CelebaDataset(Dataset):
    def __init__(
        self,
        data: pd.DataFrame,
        image_folder: str,
        augmentation: Compose,
    ):
        self.data = data
        self.column_labels = self.data.columns.tolist()[1:]
        self.column_image_id = self.data.columns.tolist()[0]
        self.image_folder = image_folder
        self.augmentation = augmentation

    def __len__(self):
        return len(self.data)

    def __getitem__(self, index: int):
        row = self.data.iloc[index]
        labels = torch.FloatTensor(row[self.column_labels])
        image_id = row[self.column_image_id]
        image = Image.open(os.path.join(self.image_folder, image_id))
        tensors = self.augmentation(image)
        return dict(
            tensors=tensors,
            labels=labels,
        )

👉 创建 LightningDataModule

现在我们需要创建一个 LightningDataModule,它实际上是一个包含 train_dataloader(s)、val_dataloader(s)、test_dataloader(s) 和 predict_dataloader(s) 的集合,以及匹配的转换和数据处理/下载步骤。

LightningDataModule 将一切组合在一起,并随后传递给训练器。因此,你无需显式地调用训练或验证数据加载器:PyTorch Lightning 会自动处理。

class CelebaDataModule(pl.LightningDataModule):
    def __init__(self, train_df, test_df, batch_size, image_folder, num_workers):
        super().__init__()
        self.train_df = train_df
        self.test_df = test_df
        self.batch_size = batch_size
        self.image_folder = image_folder
        self.num_workers = num_workers

    def setup(self, stage=None):
        self.train_dataset = CelebaDataset(
            self.train_df,
            self.image_folder,
            train_transform,
        )
        self.test_dataset = CelebaDataset(
            self.test_df,
            self.image_folder,
            valid_transform,
        )

    def train_dataloader(self):
        return DataLoader(
            self.train_dataset,
            batch_size=self.batch_size,
            shuffle=True,
            num_workers=self.num_workers,
        )

    def val_dataloader(self):
        return DataLoader(
            self.test_dataset,
            batch_size=self.batch_size,
            num_workers=self.num_workers,
        )

    def test_dataloader(self):
        return DataLoader(
            self.test_dataset,
            batch_size=self.batch_size,
            num_workers=self.num_workers,
        )

👉 定义 LightningModule

一个 [LightningModule](https://lightning.ai/docs/pytorch/stable/api/lightning.pytorch.core.LightningModule.html#lightning.pytorch.core.LightningModule) 不仅定义了模型的架构。它将你的 PyTorch 代码组织成以下部分:

  1. 初始化 (__init__setup())

  2. 前向传播 — 就像在 PyTorch 中一样

  3. 训练循环 (training_step())

  4. 验证循环 (validation_step())

  5. 测试循环 (test_step()) — 这里未显示

  6. 优化器和学习率调度器 (configure_optimizers())

import torch
from torch import nn
from torch.optim import Adam
import pytorch_lightning as pl
from torchmetrics import AUROC
from torchvision.models import resnet34
from src.train.config import NUM_LABELS

class AttributeClassifier(pl.LightningModule):
    def __init__(self, lr: float):
        super().__init__()
        self.backbone = resnet34(pretrained=True)
        self.model = nn.Sequential(*list(self.backbone.children())[:-1])
        self.classifier = nn.Linear(512, NUM_LABELS)
        self.auroc = AUROC(task="multilabel", num_labels=NUM_LABELS)
        self.lr = lr
        self.criterion = nn.BCELoss()

    def forward(self, tensors):
        output = self.model(tensors)
        output = output.view(output.shape[0], -1)
        output = self.classifier(output)
        output = torch.sigmoid(output)
        return output

    def training_step(self, batch, batch_idx):
        tensors = batch["tensors"]
        labels = batch["labels"]
        outputs = self(tensors)
        loss = self.criterion(outputs, labels)
        self.log(
            "train_loss",
            loss,
            prog_bar=True,
            logger=True,
            on_epoch=True,
            on_step=True,
        )
        score = self.auroc(outputs, labels.long())
        self.log(
            "train_auc",
            score,
            prog_bar=True,
            logger=True,
            on_epoch=True,
            on_step=True,
        )
        return {"loss": loss, "predictions": outputs, "labels": labels}

    def validation_step(self, batch, batch_idx):
        tensors = batch["tensors"]
        labels = batch["labels"]
        outputs = self(tensors)
        loss = self.criterion(outputs, labels)
        self.log(
            "val_loss",
            loss,
            prog_bar=True,
            logger=True,
            on_epoch=True,
            on_step=True,
        )
        score = self.auroc(outputs, labels.long())
        self.log(
            "val_auc",
            score,
            prog_bar=True,
            logger=True,
            on_epoch=True,
            on_step=True,
        )
        return loss

    def configure_optimizers(self):
        optimizer = Adam(self.parameters(), lr=self.lr)
        return dict(optimizer=optimizer)

👉 定义回调函数

回调是你可以挂钩到训练管道中的实用函数,用于在工作流的特定时刻执行,例如,训练周期的开始或结束。

在接下来的部分,我们将使用两个回调,模型检查点(在指标改善时定期将模型保存到磁盘)和早停(当指标停止改善时停止训练)。

checkpoint_callback = ModelCheckpoint(
    dirpath="checkpoints",
    filename="best-checkpoint",
    save_top_k=1,
    verbose=True,
    monitor="val_auc",
    mode="max",
)

early_stopping_callback = EarlyStopping(
    monitor="val_auc", 
    patience=2, 
    mode="max"
)

👉 开始训练

一旦你将 PyTorch 代码组织成一个[LightningModule](https://lightning.ai/docs/pytorch/stable/api/lightning.pytorch.core.LightningModule.html#lightning.pytorch.core.LightningModule)[Trainer](https://lightning.ai/docs/pytorch/stable/common/trainer.html)会自动处理其他所有事情。

它运行训练和验证循环,计算指标并记录它们,执行回调。

early_stopping_callback = EarlyStopping(
    monitor="val_auc",
    patience=2,
    mode="max",
)
model = AttributeClassifier(lr=config.lr)

trainer = pl.Trainer(
    callbacks=[early_stopping_callback, checkpoint_callback],
    max_epochs=config.n_epochs,
)
trainer.fit(model, data_module)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

经过仅 25K 图像的 10 个训练周期后,最佳模型在验证集上的 AUROC 达到了 0.864 AUROC

你可以从这个 S3 链接下载模型,并在Github查看代码。

2 — 将 Pytorch Lightning 模型转换为 TorchScript 🌬

本节将讨论 TorchScript 相对于 PyTorch(或 Lightning)的好处,并展示转换的过程。

TorchScript 是 PyTorch 中的一个功能,允许你序列化你的 PyTorch 模型,并在各种环境中运行它们。它提供了一种保存训练好的 PyTorch 模型并将其加载到无 Python 环境中,甚至在不同的硬件上运行,如 GPU、FPGA 或移动设备。

这相对于 PyTorch 有几个优点:

  1. 便携性:使用 TorchScript,你可以在任何支持 TorchScript 运行时的环境中运行你的 PyTorch 模型,无论平台或语言如何。

  2. 性能:TorchScript 可以通过融合操作和消除未使用的操作来优化你的 PyTorch 模型,以实现更快的执行。这可以带来显著的加速,特别是在资源有限的设备上,如手机。

  3. 安全性:通过部署 TorchScript 模型,你可以保护你的 PyTorch 代码和模型不被逆向工程或篡改。

  4. 部署简便性:使用 TorchScript,你可以轻松地将 PyTorch 模型部署到生产环境中,而无需 Python 环境。

对于 TorchScript 的更全面概述,我推荐你查看这个教程。

你可以通过调用to_torchscript方法轻松地将 Lightning 模块转换为 TorchScript。

script = model.to_torchscript()
torch.jit.save(script, "torchscript_model.pt")

经过一些测试,我发现使用 TorchScript 的 GPU 推理时间比 PyTorch Lightning 的推理时间低 2 倍。然而,这仅发生在小批量(根据我的测试,1 或 2 个样本)时。

无论如何,如果你想享受 TorchScript 的好处,你需要在 Python 运行时之外运行你的模型。

3 — 使用 BentoML + TorchScript 🍱 构建一个图像分类服务

在本节中,我们将构建一个 BentoML 服务并在本地运行,以服务于 TorchScript 模型。

如果你想跟随本教程,我建议你使用 Poetry 安装项目的依赖项。

git clone https://github.com/ahmedbesbes/face-attributes-bentoml
cd face-attributes-bentoml/
poetry install

你需要从 S3 下载模型:

cd face-attributes-bentoml/models/
curl -O https://bentoml-ts.s3.eu-west-3.amazonaws.com/ts_model.pt

现在,我们可以使用 BentoML API 将 TorchScript 模型本地保存。这将自动提供一个版本标签,并允许你稍后轻松地将模型加载为运行器。

BentoML 与 TorchScript 集成良好,因此你可以通过调用 bentoml.torchscript.save_model 方法来保存模型。

更多信息 here

# src/serve/save_model.py

import bentoml
import torch

path = "models/ts_model.pt"
script = torch.jit.load(path)
bentoml.torchscript.save_model(
    "torchscript-attribute-classifier",
    script,
    signatures={"__call__": {"batchable": True}},
)

你可以通过运行 **bentoml models list** 来检查模型是否已经使用唯一标识符保存。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供

现在模型已经使用 BentoML API 保存,我们可以创建一个服务。

首先我们导入依赖项,加载模型,从中获取一个运行器,并定义一个服务:

import torch
import bentoml
from bentoml.io import Image, JSON
from torchvision import transforms
from config import LABELS

torchscript_runner = bentoml.torchscript.get(
    "torchscript-attribute-classifier"
).to_runner()

svc = bentoml.Service(
    "face-attribute-classifier",
    runners=[torchscript_runner],
)

然后,我们定义一个 API 路由,该路由以图像作为输入,返回一个包含预测标签和相应概率的 JSON 作为输出。

@svc.api(input=Image(), output=JSON())
def classify(input_image):
    tensor = process_image(input_image)
    tensor = torch.unsqueeze(tensor, 0)
    predictions = torchscript_runner.run(tensor)
    _, indices = torch.where(predictions > 0.3)
    indices = indices.numpy()
    output = {}
    labels = []
    for index in indices:
        probability = round(float(predictions[0][index]), 3)
        labels.append(
            {
                "label_id": index,
                "label": LABELS[index],
                "probability": probability,
            }
        )
    output["labels"] = labels

    print(output)

    return output

要在本地启动服务,请运行以下命令:

poetry shell
poetry src/serve/
bentoml serve service:svc --reload

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供

如果你想尝试这个 API,可以访问 Swagger UI(在 http://localhost:3000)并直接上传你的图片。

这里有一个例子。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

演示

4 — 将服务部署到云端

在本节中,我们将构建一个 bento 并将其部署到专门设计用于托管和扩展的云平台上。

什么是 bento?

BentoML 将 ML 项目中所需的一切打包成一种叫做 bento 🍱的分发格式(这里的类比是有意义的,因为 bento 原本是一个日本的便当盒,里面装有一份主要菜肴和一些配菜)

当一个 bento 构建完成后,你可以将其部署到任何云基础设施上。

👉 构建 bento

要构建一个 bento,你必须首先提供一个名为 bentofile.yaml 的配置文件,说明 bento 应如何构建,即要包含哪些文件和所需的 python 依赖项。

service: "service.py:svc"
include:
  - "__init__.py"
  - "service.py"
  - "config.py"
  - "configuration.yaml"
python:
  packages:
    - torch==1.13.1
    - torchvision==0.14.1
    - pandas==1.5.3
    - scikit-learn==1.2.2
    - bentoml==1.0.16
    - bentoctl==0.3.4

运行以下命令将构建 bento。

cd src/serve/
bentoml build

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供

👉 连接到 BentoCloud 并推送 bento

在推送 bento 之前,你需要确保你已经登录到一个作为远程注册表的云平台(类似于 Dockerhub 这样的容器平台)。

我很幸运,BentoML 团队为我提供了他们的 平台 的访问权限,在那里他们推送和托管 bentos。

想在 BentoCloud 上创建一个账户并开始部署精彩的模型吗?请查看这个 链接 来安排演示并与 BentoML 团队进行交流。

注册 BentoCloud 后,我能够生成 API 令牌并使用 yatai 命令登录。 (我们将在本文的最后一部分中详细了解 Yatai 的具体情况)

bentoml yatai login --api-token <API_TOKEN> \ 
                    --endpoint https://default.cloud.bentoml.com

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

登录 BentoCloud

登录后,你可以推送:这将同时将你的 bento 和基础的 模型 上传到云端。

bentoml push face-attribute-classifier:qwwyr5gl7cw6ehqa

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

推送我的第一个 bento

你可以检查这两个工件是否在界面上可见,连同其他 bentos 和模型(来自其他用户)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

bento 和模型

👉 创建部署

从平台部署你的 bento 非常简单。前往部署选项卡,点击右上角的黑色 创建 按钮。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

这将打开以下表单,你需要填写部署信息:

  • 集群信息和部署名称

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

  • bento 存储库及其版本

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

  • API 服务器的配置:副本数量、CPU 和内存请求

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

  • 每个运行程序的配置:在我们的例子中只有一个

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

一旦点击 提交 按钮,你可以开始部署。这将构建镜像并在单独的 Pod 上部署 API 服务器和运行程序。这是一个使 BentoML 与我之前使用的任何服务平台区分开的杀手级特性。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片

一旦你的 bento 成功部署,点击你的部署,然后点击 URL:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果一切按预期工作,在浏览器中打开此 URL 将会带你到这个页面:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

API 现在已经部署:你可以从互联网上的任何地方查询它。

需要在自己的基础设施上管理你的 bentos 吗?试试 Yatai

你可以通过 BentoML 团队开源的 Yatai 项目在你自己的云基础设施上复制 BentoCloud 平台。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

来源: github.com/bentoml/Yatai

Yatai 作为一个集中式注册表,你可以将其部署到任何 Kubernetes 原生环境中。

它与所有主要云平台(AWS、Azure 和 GCP)兼容。

它帮助你管理部署生命周期,通过 API 或 Web UI 进行部署、更新或回滚。

结论 🔚

如果你读到这里,我感谢你的时间,并希望你喜欢了解 PyTorch Lightning、TorchScript 和 BentoML。

将这三种工具结合起来,帮助你使用最佳实践的 DevOps 方法来原型设计和部署工业级深度学习模型。

当然,我展示的工作流中的不同步骤还有改进的空间:我只是希望这篇文章能够作为一个好的起点,帮助你深入了解 MLOps。

资源 🗞

  • towardsdatascience.com/10-ways-bentoml-can-help-you-serve-and-scale-machine-learning-models-4060f1e59d0d

  • towardsdatascience.com/10-ways-bentoml-can-help-you-serve-and-scale-machine-learning-models-4060f1e59d0d

  • youtu.be/Dk88zv1KYMI

  • youtu.be/2awmrMRf0dA

对 Medium 不熟悉?你可以每月支付 5 美元订阅,解锁各种话题的无限文章(科技、设计、创业……)你可以通过点击我的推荐链接来支持我。

[## 使用我的推荐链接加入 Medium - Ahmed Besbes

阅读 Ahmed Besbes(以及 Medium 上成千上万的其他作者)的每一个故事。你的会员费用直接支持……

ahmedbesbes.medium.com

如何从头设计一个 dbt 模型

原文:towardsdatascience.com/how-to-design-a-dbt-model-from-scratch-8c72c7684203?source=collection_archive---------5-----------------------#2023-07-10

一个实际可用的 dbt 模型构建简单框架。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Taylor Brownlow

·

关注 发表在 Towards Data Science ·6 min read·2023 年 7 月 10 日

当我研究dbt 终极指南时,我对关于从头构建模型的材料的缺乏感到震惊。这不仅仅是工具中的具体操作步骤——这些在无数博客和教程中都有涵盖。我的意思是,如何确定正确的设计?如何确保你的利益相关者会使用这个模型?你如何确保它会被信任并理解?

当我们部署新模型而没有采取这些步骤时,可能会导致严重后果:

  • 我们面临着来自利益相关者的大量问题和后续请求。

  • 我们从其他数据工程师或分析工程师那里获得代码改进建议

  • 我们必须回去添加新功能,进行改进,并回答所有问题,才能完成我们的工作。

如果我们一遍遍重复这个过程,数据团队和业务团队之间的信任将开始下降,因为双方在反馈狂潮中逐渐变得更加疲惫,这可能是很难恢复的。

这突显了仔细考虑我们如何设计模型的重要性,不仅仅是在 dbt 中单独进行,而是与所有利益相关者共同协作,以确保模型是 准确有效 的,并且我们不会在每个模型上浪费 4-5 次的构建时间才使其 有用

本文是关于如何最好地设计和实施 dbt 模型的研究和实验成果。它不会包含在 dbt 中执行的任何命令,但会讲解如何思考你的模型,以及如何构建你的工作流程,以确保你不会浪费时间。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来源:Med Badr ChemmaouiUnsplash

不同的方法

幸运的是,我不是第一个想到这个问题的人。许多其他领域也遇到了类似的挑战,并创建了自己的框架和流程,我可以借鉴这些来考虑如何进行数据建模。例如:

敏捷原则 不鼓励软件工程师采用瀑布开发方法,这与快速变化的需求环境是背道而驰的 [1]。相反,敏捷方法拥抱快速迭代,并承认能够迅速响应变化需求的竞争优势。

设计原则 同样承认了在设计项目中与多个利益相关者合作时需要有意识的工作方法 [2]。该框架优先考虑人,并鼓励在每个开发阶段获取反馈,以便尽快找到最佳解决方案。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来源:设计委员会 的这项工作采用了 CC BY 4.0 许可证

即使是数据建模的教父 Ralph Kimball 也在他的 4 步数据建模过程 [3] 中强调了在建模过程初期从利益相关者那里获得高质量输入的重要性。第一步是尽可能多地了解业务流程,然后再考虑构建模型。

然而,在考虑这个问题时,我发现最有影响力的来源是 系统工程启发式 —— 一组关于处理复杂问题与多个利益相关者的真理 [4]。

  • 不要假设问题的原始陈述必然是最佳的,甚至是正确的。

  • 在项目的早期阶段,未知的问题比已知的问题更为棘手。

  • 尽可能在构建之前先建模。

  • 大多数严重错误发生在早期。

这些来源帮助塑造了以下从零开始设计数据模型的过程。

数据建模设计过程

所以我想建立一个符合这些原则的过程,一个可重复的过程,并且能够确保我的模型第一次构建得很好。

这是我得出的结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们将在下面更详细地讲解每一步。

以下示例将展示来自 count.co 的截图,这里是数据画布,我是产品负责人。不过,值得注意的是,这一过程与工具无关。你可以参考截图中的示例 这里

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供。所有 5 个步骤的过程展示。查看完整画布 这里

第 1 步:发现

目标:理解你正在建模的业务流程。

参与者:你、业务利益相关者

活动

  • 规划业务流程

  • 确定利益相关者希望对最终表格做什么(例如,他们需要计算哪些指标,需要添加哪些过滤器等)

  • 了解他们今天是如何做到这一点的(如果他们这样做的话)。那个解决方案有什么问题?

  • 还会有其他人使用这个吗?是否有其他次要利益相关者也应该沟通?

  • 还有其他相关的业务背景吗?例如,有人下周会有一个关于这个主题的大型演示

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供。点击 这里 查看示例。

第 2 步:设计(并迭代!)

目标:规划构建模型的可能方法

参与者:你

活动

  • 规划最终表格

  • 你将选择什么粒度?

  • 你将包括哪些列?

  • 如果最终表格设计有多个选项,请规划这些选项,并在继续之前从利益相关者那里获取反馈

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片由作者提供。点击 这里 查看示例。

第 3 步:原型制作(并迭代!)

目标:规划如何到达商定的最终表格。包括代码和解释。

参与者:你、数据团队成员、利益相关者

活动

  • 规划模型的每一步,包括每个阶段的代码和结果

  • 确保数据团队的另一名成员审核你的代码

  • 与相关方一起审查逻辑,以确保它符合他们的期望和背景

  • 验证原型模型的结果

  • 迭代直到业务相关方和数据团队都理解并批准方法和结果

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片。点击 这里 查看示例。

第 4 步:部署

目标:在 dbt 中部署模型

参与者:你

活动

  • 取最终原型代码并将其部署到你的 dbt 代码库中

  • 确保它通过所有测试

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片。这是如何将原型 SQL 导出为 dbt 友好的语法。

第 5 步:交付

目标:让相关方知道表格现在已可用以及如何与表格互动

参与者:你,业务相关方

活动

  • 创建文档(在 dbt 或其他地方)

  • 将表格和文档的链接发送给相关方

  • [可选] 使用表格创建示例分析,以便他们有一个起点

  • [可选] 为任何希望了解新表的人举行简短的介绍会

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者提供的图片。点击 这里 查看完整示例。

下一步

下一次你从头开始构建 dbt 模型时,试试这个过程。这将对你和你的相关方带来很大变化,但它已经证明能显著减少部署新模型所需的时间,并提高这些模型的整体采纳度。

将更多人纳入数据建模过程,并展示透明度,有助于促进信任并快速交付有价值的数据模型。

如果你尝试了这个过程,请给我留言,让我知道结果如何以及任何改进的想法!这些东西毕竟需要不断迭代…

资源

[1] Agile Manifesto. (2001). Agile Manifesto 背后的原则。于 2023 年 7 月 1 日获取自 agilemanifesto.org/principles.html

[2] Design Council. (2004). 创新框架。于 2023 年 7 月 1 日获取自 www.designcouncil.org.uk/our-resources/framework-for-innovation/

[3] Holistics. Kimball 的维度数据建模。于 2023 年 7 月 1 日获取自 www.holistics.io/books/setup-analytics/kimball-s-dimensional-data-modeling/

[4] 彼得·布鲁克。《系统工程启发式》见 SEBoK 编辑委员会。2023 年。系统工程知识指南(SEBoK),第 2.8 版,R.J. 克劳提耶(主编)。霍博肯,新泽西州:史蒂文斯理工学院信托基金。访问日期:[DATE]。www.sebokwiki.org。BKCASE 由史蒂文斯理工学院系统工程研究中心、国际系统工程理事会以及电气和电子工程师学会系统委员会管理和维护。

如何设计机器学习项目的路线图

原文:towardsdatascience.com/how-to-design-a-roadmap-for-a-machine-learning-project-1bbdb88bde48?source=collection_archive---------3-----------------------#2023-09-04

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来源: DreamStudio

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Heather Couture

·

关注 发表在 Towards Data Science ·10 min read·2023 年 9 月 4 日

当你开始一个新的机器学习项目时,第一步是什么?

我向多位初创公司中的机器学习领袖提出了这个问题,并收到了几种不同的回答。没有特定顺序:

  1. 尝试使用我们现有的模型看看是否适用于新任务。

  2. 开始探索和理解数据。

  3. 深入研究相关文献,了解之前做过的工作。

注意,这些第一步中没有包含编码和训练新模型,也没有设计数据预处理管道。

这三种方法各有优点。如果新项目与之前建模的内容(包括数据和任务)非常相似,那么尝试已经实施过的建模方法可以非常快速地建立任务的基准。在这样做的过程中,你也可能发现需要在数据预处理或建模中适应的新挑战。

这可能会引导你进入第#2 步:探索和理解数据。或者你可能从这里开始。识别新数据集的独特需求至关重要。也许预处理或注释需要不同的处理方式。也许数据中存在需要清理的伪影,或者标签并不总是正确的。理解预处理和建模需要应对的挑战是至关重要的。

但有些团队忽略的步骤,且是使项目成功的关键步骤,就是文献调研。是否有其他人对类似的数据做过类似的任务建模?如果你所处理的数据类型很常见,那么你可能可以应用一个非常严格的“相似”定义。但如果你正在处理新的成像模式,或者面临新的任务,你可能需要放宽“相似”的定义来找到相关研究。

这三个第一步在我用于规划新项目的过程中都是重要的:机器学习路线图。

当我与客户合作新项目时,路线图是第一步。路线图澄清了项目其余部分的工作范围。它减少了对需要实施内容的不确定性。它还降低了打转或在不成功的方法上浪费时间的可能性。通过在从头开始实施之前识别现有工具包,它节省了时间和金钱。而且它增加了项目成功的可能性。

ML 路线图涉及什么?让我带你了解核心组件。

1) 定义问题

首先清晰地定义你希望用机器学习解决的问题。在此过程中,退一步考虑一下机器学习是否是解决你问题的正确工具。这为整个项目奠定了基础,并帮助确保项目能够交付预期的结果。

定义问题包括识别业务问题、需要收集的数据和目标变量。清晰的问题定义和明确的目标将帮助你避免不必要的实验,专注于问题的最重要方面。

确定成功标准是关键。这可以包括评估指标,但更多的是关于预期的使用案例。

需要考虑的一些事项:

  • 你的解决方案是否相关?它是否会以解决当前瓶颈或痛点的方式融入现有工作流程?

  • 需要多准确才能改善当前过程?

  • 模型需要能够概括到哪些场景?这可能包括不同的成像设备、患者群体或光照条件等。

  • 模型需要多大程度的可解释性?了解模型如何工作能大大简化发现改进领域的过程,但这在建立信任或获得监管批准时也可能很重要。

  • 模型部署后是否会有计算限制?了解任何处理或内存限制可以缩小可能的方法范围。

通过前期明确问题,你为一个成功的机器学习项目奠定了基础,以实现预期结果。

2) 研究相关工作

研究相关工作是任何机器学习项目中的关键步骤。它有助于你识别类似问题的现有解决方案,并了解该领域的最前沿技术。

你可以从进行文献综述开始。这包括阅读研究论文、会议记录以及该领域的其他相关文献。

记录你阅读过的来源及每个来源的关键发现是至关重要的。这可以帮助你组织思路,识别现有解决方案中的模式和缺口。

  • 他们处理的数据类型是什么?

  • 有多少患者、图像等?

  • 他们是如何标注和结构化他们的训练数据的?

  • 他们使用了什么模型架构?

  • 他们是如何训练他们的模型的?

  • 他们遇到了哪些挑战?

  • 图像或标签的质量或数量是否存在问题?

  • 他们是如何收集独立数据以进行验证的?

这些都是在开始构建自己的解决方案之前需要了解的重要方面。

研究相关工作还可以帮助你识别现有的代码库、数据集或预训练模型,这些可以启动你的项目,节省时间和资源。

3) 理解数据

理解数据是开始任何机器学习项目的关键步骤。这是因为数据的质量和相关性对机器学习模型的性能有显著影响。

对于某些项目,数据可能已经收集。对于其他项目,必须首先定义并执行数据收集过程。你的文献综述可能有助于指导你应该收集什么类型的数据,以及你可能需要多少数据。

一旦数据被收集,通常需要进行标注——这也是可以通过你的文献综述得到启发的任务。

  • 需要什么类型的标注?像素级、补丁级和图像级是最常见的。

  • 已经使用了什么工具来辅助标注?标注是否可以来自其他模态?比如来自生物样本的分子分析,或像 Open Street Map 这样的现有标注集用于卫星图像。

  • 你的标注有多主观?研究或进行你自己的实验来评估观察者之间的一致性可以揭示这个挑战的程度。

你还需要了解数据的质量。这包括检查缺失值、异常值和数据中的不一致性。这些问题可能包括组织准备伪影、成像缺陷如噪声或模糊,或领域外的情况。通过识别数据质量问题,你可以适当预处理和清理数据,并计划解决任何无法事先消除的挑战。

数据预处理可能包括标准化、缩放或其他变换。对于大型图像,通常包括将其切分为小块。数据和注释必须以一种对模型训练高效的格式存储。

理解数据还帮助你识别任何可能影响模型性能和可靠性的偏差。偏差可能由于特定子组的训练数据不足或虚假的相关性。批次效应可能由于技术差异,如不同实验室的处理差异或地理变异。甚至可能是由不同标注者标记的样本。

对于大多数应用,应该咨询领域专家以了解数据:

  • 数据是如何收集的?

  • 它代表了什么?

  • 在研究数据时关注哪些特征?

  • 现实世界中可能存在或预期会出现哪些变化?

  • 可能存在什么样的伪影或质量问题,可能会混淆模型?

这些方面中的一些可能相当微妙,对于没有特定领域培训的人来说不明显。

这一关键步骤有助于评估数据的质量和相关性,识别和解决数据偏差,并确定适当的预处理技术。

4) 验证计划

在项目早期形成验证计划很重要,以揭示任何意外的挑战。最终模型预计在某些现实世界场景中表现出色,因此测试其能力是至关重要的。

首先要考虑的验证设置是将训练数据分为训练集、验证集和测试集。训练集通常是数据中最大的一部分,用于训练模型。验证集用于调整模型的超参数,例如学习率或正则化强度。测试集用于评估模型的性能,提供对模型在未见数据上的泛化能力的无偏估计。在模型开发过程中,测试集应与训练集和验证集完全分开。

通常,训练集、验证集和测试集是从可用数据中随机抽样的,同时保持所需的类别或目标变量的分布,以避免任何无意的偏差。当数据包含不同的组时,例如每个患者的多张图像、来自不同医疗中心的样本或来自不同地理区域的图像,需要对组进行更仔细的分层,以评估模型的泛化能力。来自同一组的所有样本应归入训练集、验证集或测试集中,而不是分布在三者之间。

交叉验证技术,如 k 折交叉验证或留一法交叉验证,也可以用于通过系统地轮换数据以获得更可靠的性能估计,这在处理小数据集时尤其常见。

评估模型性能涉及在训练集、验证集和测试集上计算一个或多个指标。适用的指标取决于应用,但可能包括准确率、灵敏度、特异性、F1 分数、AUC 分数、DICE 等。每个指标都将模型的预测与实际情况进行比较。

在一些应用中,在测试集上计算指标可能足够进行验证。在其他应用中,这部分保留的数据可能与真实世界场景不够相似。也许你的模型需要在不同地理区域或医疗中心的患者身上进行工作,而你没有可用的标注训练数据。你仍然需要在外部数据集中验证你的模型,以模拟其在真实世界中的表现和泛化能力。

5) 开发基线模型

在经过大量规划和研究后,你终于准备好开始建模了。但我不建议从最复杂的深度学习模型开始。先从简单的开始。首先开发一个简单的基线模型。这将使你能够测试你的数据处理、标注和验证管道,并揭示任何意外的挑战。

对于特定的项目,有很多合适的算法可供选择,选择最佳算法可能是一个挑战。最简单的基线可能是基于简单特征的线性分类器或回归器。或者可以使用转移学习而不进行微调,以最小化学习特征所花费的时间。此时不要过于费心调节超参数;默认参数甚至可能在这一步中就已足够。

开发基线模型有助于建立一个性能基准,用于评估未来模型的有效性。它有助于为你的项目设定现实的性能期望,并使你能够确定实现理想性能水平所需的改进量。

这个基准模型不应被视为最终模型。相反,它应作为开发更复杂模型的起点,这些复杂模型可以实现更好的性能。

6) 迭代和改进

迭代对于提高模型性能直到达到期望的准确度至关重要。

第一步是分析模型的性能。这涉及到检查几个不同的方面:

  • 审查训练和验证指标,以查找过拟合或模型收敛问题的迹象。

  • 将验证指标分层到不同的子组中,以识别改进的领域或可能的偏差。

  • 对失败模式进行分类,以找到改进的领域。

  • 与领域专家审查结果,获取关于他们认为重要的缺陷的反馈。

一旦你分析了模型的性能,你需要假设为何模型表现不佳以及如何解决这些问题。解决方案可能是以数据为中心的,比如收集更多数据或更改清理程序,或是以模型为中心的,比如更改模型架构或超参数。查看你文献搜索的笔记以获取灵感。

下一步是通过实施更改并在验证数据上评估模型的性能来测试你的假设。优先解决对模型最有害或最容易修复的问题。

迭代和改进机器学习模型是一个持续的过程。你需要继续测试和优化模型,直到达到期望的准确度。保持这些迭代的紧凑,以便尽快进行调整——尤其是当修复涉及到耗时的数据收集或注释更改时。

7) 部署、监控和维护

一旦你有一个达到期望性能水平的模型,你可以将其部署到生产环境中。这涉及将模型集成到你的应用程序或系统中,并确保其按预期执行。

第一步是确定部署模型的要求。这可能包括性能、可扩展性、安全性和用户界面等因素。你还需要选择一个部署平台;典型的选择是基于云的服务或本地基础设施。

下一步是将模型打包成可以部署的格式,并测试部署以确保其正常工作。这可能包括测试模型的性能、可扩展性和安全性。模型部署后,你需要监控其性能,并进行必要的调整。

部署机器学习模型是一个持续的过程,你需要不断改进模型,以确保它随着时间的推移保持有效。

最后,重要的是记录对模型或其训练过程所做的任何更改。这确保了模型在时间的推移中保持透明和可重复。

总结

机器学习项目是复杂且迭代的。这个路线图流程使您能够规划项目的每个方面。尽管细节可能会有所变化,但整体组件将保持不变。从定义问题到维护模型,每一步都需要仔细规划。尽可能地,您还应该考虑您的计划方法可能会失败的情况以及一些解决方案来应对这些可能的失败。

您的团队是否希望最大化图像和算法的影响力?

我与各种团队合作,挖掘他们数据的价值,创建更准确的模型,并从病理学和遥感图像中生成新的强大洞察。

预约一次免费的 机器学习发现电话会议,了解如何推进您的机器学习算法。

如何在 AWS 中设计 MLOps 架构?

原文:towardsdatascience.com/how-to-design-an-mlops-architecture-in-aws-67ee9843a430

为开发人员和架构师提供的指南,特别是那些未专门从事机器学习的人员,帮助他们为组织设计 MLOps 架构

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 哈明德·辛格

·发表于数据科学之路 ·阅读时长 8 分钟·2023 年 4 月 14 日

介绍

根据 Gartner 的研究,仅 53%的机器学习(ML)项目从概念验证(POC)阶段推进到生产阶段。公司战略目标与数据科学家构建的机器学习模型之间通常存在不一致。DevOps、安全、法律、IT 和数据科学家之间的沟通可能不足,这会给将模型推向生产带来挑战。最后,团队可能发现维护生产中的模型以及推出新模型是困难的。这导致了 MLOps 的兴起,将 DevOps 的原则,如持续集成和持续交付(CI/CD)、自动化和协作,带入机器学习生命周期——开发、部署和监控。

在本文中,我将深入探讨以下内容:

  • 机器学习过程中的各个步骤

  • 介绍不同的 MLOps 组件,并解释它们为何必要,而不深入讲解只有数据科学家需要了解的细节

  • 根据组织的规模和成熟度,提供 MLOps 架构图

  • 关于开始 MLOps 旅程的一般建议

典型的机器学习过程

首先,让我们了解机器学习过程中的各个步骤。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

机器学习过程 — 作者提供的图片

机器学习过程包括以下组件:

  1. 业务问题和机器学习问题陈述: 我们通过识别业务问题并确认机器学习是解决该问题的正确方案来开始过程。提出的机器学习解决方案应产生可衡量的业务成果。

  2. 数据收集、整合和清理: 在这一步中,数据科学家/数据工程师收集数据,将其与不同来源整合,并清理和转换以使其准备好供使用。数据工程师/科学家可能还会将数据划分为三组数据集——训练集、验证集和测试集,使用 80–10–10 或 60–20–20 的比例。训练集直接用于训练模型,验证集用于评估模型性能的未见示例,测试集则是另一组未见记录,用于检查模型在现实生活中的表现。

  3. 数据分析和可视化: 数据科学家随后进行探索性分析(EDA)以理解数据的结构和质量。这一步是了解数据差异、模式以及形成新假设所必需的。

  4. 特征工程:在这一步中,数据被选择、组合和处理,以使用统计或机器学习方法创建新变量。例如——进行对数变换、缩放或标准化。所有新特征共同有助于提高模型性能。

  5. 模型训练和参数调优:一旦新特征可用,数据科学家将训练各种机器学习模型,并调优超参数以满足所需的服务水平协议指标。

  6. 模型评估和部署: 在这一步中,从所有其他模型中选择准确度最高的模型,并将其部署到生产环境中。模型部署意味着模型已准备好进行预测。

  7. 监控和调试: 机器学习模型一旦部署到真实世界中,就会变得过时。模型必须定期使用更新的数据重新训练。

每个数据科学家在机器学习旅程初期或多或少都会遵循这一过程,并手动执行大多数上述步骤。

为了说明我的意思,让我们看看没有 MLOps 的架构图。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

没有 MLOps 的机器学习架构 - 作者提供的图片

这里有一个相当标准的数据科学设置。数据科学家可以访问 AWS 账户中的 SageMaker Studio,他们可以使用 Jupyter Notebook 来开发模型。他们从各种数据源,如 S3 或 Athena,拉取数据,然后使用不同的机器学习技术创建模型。然后模型作为模型工件存储在 S3 中,并部署为 SageMaker 终端节点。通过 API 网关,终端节点对外界开放。

没有 MLOps 的机器学习项目挑战

虽然这种设置适合做概念验证,但它面临以下挑战:

  1. 变更依赖人工劳动: 机器学习模型的任何更改都需要数据科学家进行人工修改。这可能涉及重新运行 Jupyter notebook 单元格或使用最新版本的模型更新 SageMaker 端点。如果代码扩展到多个 Jupyter notebooks,变更变得繁琐且难以扩展。

  2. 没有代码版本控制: 数据科学家产生的代码存在于 Jupyter notebooks 中。这些笔记本难以进行版本控制和自动化。

  3. 没有反馈循环: 过程中的自动反馈循环不存在。如果模型质量恶化,你只会通过不满客户的投诉才会发现。

这些是使用 MLOps 可以避免的一些挑战。

小团队(1–3 名数据科学家)的 MLOps 架构

MLOps 可能会很复杂,你不必立即采用所有功能。你可以从最小的 MLOps 设置开始,并随着团队的增长或成熟逐步采用更多功能。

让我们以上述相同的机器学习设置为例,并在其中引入 MLOps 元素。下图所述的架构图适用于小公司或 1–3 人的数据科学团队。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

小团队的 MLOps 架构 - 图片来源于作者

在此架构中

  1. 数据科学家从 SageMaker notebook 实例开始,使用代码提交或任何基于 git 的代码库进行代码的版本控制,并使用 docker 容器在 elastic container registry(ECR)中存储训练机器学习模型的环境。通过对代码、环境和模型工件进行版本控制,你可以提高模型的重现能力,并鼓励团队之间的合作。

  2. 你可以使用步骤函数或其他工作流工具,如 Airflow 或 SageMaker 管道,来自动化模型的再训练。数据科学家构建的再训练管道将使用版本代码和环境进行数据预处理、模型训练和模型验证,并将模型工件保存在 S3 中。管道可以利用各种服务,如 glue jobs、EMR jobs 和 lambda,来完成其流程,并可以使用基于时间的事件规则在事件桥中进行自动化。

  3. 模型及其版本管理通过模型注册服务(如 SageMaker 模型注册表)进一步管理。SageMaker 模型注册表存储模型及其元数据,如超参数、评估指标以及偏差和解释性报告,并允许查看、比较和批准或拒绝模型。实际的模型工件存储在 S3 中,模型注册服务作为附加层位于顶部。

  4. 最后,模型的部署通过 Lambda 函数自动化,该函数在模型在 Sage Maker 模型注册表中获得批准后立即触发。Lambda 会从 S3 中提取批准的模型,并在 Sage Maker 端点中更新版本而不发生停机。这些 Sage Maker 端点连接到 Lambda 和 API Gateway 以服务消费者应用程序,并附加有自动扩展组以应对请求的意外激增。你可以通过使用 Canary 部署进一步改进部署过程。这意味着少量用户请求将首先转发到新模型,任何错误将触发 Cloud Watch 警报以通知数据科学家。随着时间的推移,发送到新模型的请求数量将增加,直到新模型获得 100% 的流量。

这种架构对代码和模型进行版本管理,并自动化重新训练和部署。它提供了 Sage Maker 端点的自动扩展性和 Canary 部署的灵活性。然而,当数据科学家团队扩大时,我们可以引入更多 MLOps 元素。

中型和大型团队的 MLOps 架构

这种 MLOps 扩展了小型 MLOps 架构,并扩展到多账户设置,强调生产中模型的质量检查。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

中型和大型团队的 MLOps 架构 — 作者图像

在此架构中:

  1. 数据科学家采用多账户方法,在开发账户中开发模型。

  2. 在小型 MLOps 设置中,数据科学家从 Sage Maker Notebook 开始,使用 Code Commit 对代码进行版本管理,并通过 ECR 对环境进行版本管理。然后,他们使用 Step Function 创建一个重新训练的流水线,该流水线包括模型训练、验证和在 S3 中保存工件的步骤。模型的版本管理由 Sage Maker 模型注册表完成,该注册表允许用户接受或拒绝模型。

  3. 模型的部署步骤包括 Sage Maker 端点和自动扩展组,这些端点和组连接到 Lambda 和 API Gateway 以允许用户提交推断请求。然而,这些组件服务位于不同的 AWS 账户中。推荐使用多账户策略,因为它提供了一种分离业务单元、轻松定义生产负载限制并提供每个架构组件成本细化视图的方式。

  4. 多账户策略包括在生产账户旁边设置一个暂存账户。新的模型首先部署到暂存账户,进行测试,然后部署到生产账户。此部署必须通过开发账户中的代码流水线自动进行。代码流水线由模型版本在模型注册表中获得批准时生成的事件自动触发。

  5. 监控生产环境中模型行为或准确性的变化是至关重要的。我们已在预发布和生产账户的端点启用了数据捕获功能。它将传入的请求和传出的推断结果捕获到 S3 桶中。这些捕获的数据通常需要与开发账户上的标签或其他数据结合,因此我们使用 S3 复制将数据转移到开发账户中的 S3 桶里。现在,为了判断模型或数据的行为是否发生了变化,我们需要一些对比的基准。这时,模型基线就发挥作用了。在训练过程中,我们可以生成一个基线数据集,记录数据和模型的预期行为。我们可以使用 SageMaker 模型监控器来比较这两个数据集并生成报告。

  6. 这个架构的最终步骤是根据模型报告采取行动。当检测到显著变化时,我们可以发送事件以触发管道的重新训练。

离开的思考

MLOps 是一个旅程。你不必立即使用复杂架构设计中的所有功能。你可以从基本步骤开始,集成版本控制和自动化。探索我上述介绍的所有功能,并根据你的业务需求对它们进行分类,然后在需要时开始采用这些功能。上述架构设计并不是实现 MLOps 的唯一方法,但我希望它们能为你作为架构师提供一些灵感。

如果你觉得我的文章对你有帮助,请给我留言。

如何通过假设检验检测数据漂移

原文:towardsdatascience.com/how-to-detect-data-drift-with-hypothesis-testing-1a3be3f8e625

MLOps

提示:忘记 p 值吧

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Michał Oleszak

·发布于 Towards Data Science ·18 分钟阅读·2023 年 5 月 17 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

数据漂移是任何使用机器学习模型进行实时预测的人的一个担忧。世界在不断变化,随着消费者的口味或人口统计特征的变化,模型开始接收到与训练时不同的特征值,这可能导致意外的输出。检测特征漂移看起来很简单:我们只需确定训练和服务中相关特征的分布是否相同即可。确实有统计测试可以进行,但你确定你使用它们的方式是正确的吗?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

单变量漂移检测

监控机器学习模型的部署后性能是其生命周期的关键部分。随着世界的变化和数据的漂移,许多模型往往会随着时间的推移表现出性能下降。保持警觉的最佳方法是实时计算性能指标,或者在地面真实数据不可用时进行估算

观察到的性能下降的一个可能原因是数据漂移。数据漂移是指模型输入在训练数据和生产数据之间分布的变化。检测和分析数据漂移的性质可以帮助将降级的模型恢复到正常状态。根据受影响的特征的数量和方式,数据漂移可以分为两种形式:应区分多变量数据漂移和单变量数据漂移。

多变量漂移是指单个特征的分布不一定发生变化,但它们的联合分布发生了变化。这种情况很难察觉,因为单独观察每个模型特征无法发现它。如果你对检测多变量漂移感兴趣,可以查看这篇关于如何基于 PCA 重构误差进行检测的文章

今天,我们关注单变量数据漂移,这是一种在生产环境中一个或多个特征的分布与模型训练数据中的分布发生变化的情况。

单变量数据漂移似乎比多变量漂移更容易检测:你不需要考虑多个变量之间的复杂统计关系。相反,对于每个特征,你只需比较两个数据样本——训练和服务——以检查它们是否以类似的方式分布。让我们看看在假设检验框架中通常如何执行这一操作。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

假设检验入门

为了让内容更具体,我们关注卡方检验,它经常用于比较分类变量的分布。卡方检验广泛应用于A/B/C 测试,用于验证用户在不同处理下(例如,展示广告 A、B 或 C)是否表现出不同的行为模式(例如,购买频率)。

同样的检验通常应用于单变量数据漂移检测,以检查在两个状态(训练和服务,即市场设置中的购买和非购买)下测量的分类变量是否具有相同的类别频率。如果不相同,数据可能已经漂移。让我们来看一个具体的例子。

假设一个机器学习模型负责向用户推荐内容。假设模型的一个输入是用户的设备。假设设备是一个具有两个类别的分类变量:桌面和移动。以下是训练数据和服务数据中的类别计数。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

由于服务样本远小于训练集,因此我们将类别计数表示为相对频率,以便于比较。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

类别频率在训练集和服务集之间确实非常相似,但它们并不完全相同。由于随机抽样,即使在没有数据漂移的情况下,我们也很少观察到完全相同的频率。

所以问题变成了:我们观察到的类别频率差异是由随机抽样变异造成的,还是由设备变量的结构性变化造成的?后者意味着我们正在处理数据漂移。

为了回答这个问题,我们可以使用统计假设检验框架。

卡方检验

我们首先定义假设,即两个假设。原假设声称我们观察到的训练与服务之间的差异仅由随机机会造成——两个数据样本来自相同的基础分布(总体)。另一方面,备择假设则声称这些差异不是由随机性驱动的,而是由数据漂移造成的。

**H₀:训练/服务之间的差异是随机噪声的结果。

H₁:训练/服务之间的差异是数据漂移的结果。**

我们现在将使用数据来检验原假设。如果我们可以拒绝原假设,我们将声明数据漂移已经发生。如果不能,我们将说由于差异可能是由随机机会产生的,没有数据漂移的证据。

在我们的案例中,传统的测试方法依赖于这样一个事实:在原假设下,某个量被知道遵循卡方分布。这个量被恰当地称为卡方统计量。

卡方统计量

卡方统计量被定义为期望频率和观察频率之间平方差的标准化总和。现在让我们解读这个声明。

期望频率是我们在没有数据漂移的原假设下会观察到的频率。它们被定义为列联表的边际和相乘,并按总数进行缩放。

以下列联表表示我们设备变量在测试和服务数据中的频率,如之前定义的。

cont_table = pd.DataFrame([
  [35_252, 30_299],
  [3_516, 3_187]
])

然后我们可以沿着两个轴计算边际和,如下所示。

margsums = [cont_table.values.sum(axis=x) for x in [1, 0]]

最后,我们将它们相乘(我们需要转置第一个元素以使乘法可能),并按全局总和进行缩放。

from functools import reduce

margsums[0] = margsums[0].reshape(2, -1)
expected = reduce(np.multiply, margsums) / cont_table.values.sum()

在我们的特殊情况下,变量下只有两个类别,检验将只有一个自由度。这要求对观察值进行调整,这被称为耶茨连续性校正。如果有更多类别,你可以跳过以下四行代码。

diff = expected - cont_table.values
direction = np.sign(diff)
magnitude = np.minimum(0.5, np.abs(diff))
observed = cont_table.values + magnitude * direction

知道了期望值,我们可以根据之前定义的方式计算卡方统计量:即期望频率和观察频率之间的平方差的总和,经过期望频率的标准化。

chisq = np.sum(((observed - expected) ** 2) / expected)

这给出了 4.23,我们也知道这个统计量遵循卡方分布。这就是验证我们原假设所需的全部信息。

验证假设

让我们绘制出我们的测试统计量遵循的理论卡方分布。这是一个自由度为 1 的卡方分布。红线表示测试统计量的观察值。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

蓝色阴影区域显示了在原假设下,即在没有数据漂移的情况下,检验统计量所遵循的分布。我们刚刚观察到的是红线标记的值。这一观察是否足够强烈以拒绝原假设?

比如说,卡方值为 10,在原假设下是非常不可能出现的。如果我们看到了这个值,我们可能会得出结论,认为原假设必须是错误的,数据已经发生了漂移。

另一方面,如果我们得到了 0.5 的卡方值,我们会认为在原假设下这个观察值并不令人惊讶,换句话说,训练数据和服务数据之间的设备频率差异可能只是偶然产生的。一般来说,这意味着没有理由拒绝原假设。

但我们得到了 4.23。我们如何决定是否拒绝原假设呢?

p 值

引入了臭名昭著的 p 值。它是一个回答以下问题的数字:在原假设为真的情况下,观察到我们获得的卡方值或更极端值的概率是多少?或者,使用某种符号,p 值表示在假设原假设为真的情况下观察到数据的概率:P(数据|H₀)(准确地说,p 值定义为 P(test_static(数据) > T | H₀),其中 T 是选择的检验统计量阈值)。请注意,这与我们实际感兴趣的内容不同,我们关心的是在给定我们观察到的数据的情况下,原假设为真的概率:P(H₀|数据)。

**p 值表示的内容:P(数据|H₀)

我们通常希望的:P(H₀|数据)**

从图形上讲,p 值是红线右侧的蓝色概率密度的总和。计算它的最简单方法是计算观察值的累积分布的一减,即左侧的概率质量减去 1。

1 - chi2.cdf(chisq, df=1)

这给我们带来了 0.0396。如果没有数据漂移,我们在大约 4%的情况下会得到我们所获得的检验统计量或更大的统计量。毕竟,这并不那么少见。在大多数使用情况下,p 值通常与 1%或 5%的显著性水平进行比较。如果它低于这个水平,则拒绝原假设。让我们保守一点,遵循 1%的显著性阈值。在我们这个接近 4%的 p 值的情况下,没有足够的证据来拒绝它。因此,没有检测到数据漂移。

为了确保我们的检验是正确的,让我们用 scipy 的内置测试函数确认一下。

from scipy.stats import chi2_contingency

chisq, pvalue, df, expected = chi2_contingency(cont_table)
print(chisq, pvalue)

4.232914541135393 0.03964730311588313

这就是假设检验的工作原理。但这对生产环境中的机器学习系统的数据漂移检测有多重要?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

统计显著性与监控显著性

从广义上讲,统计学是基于小样本对整个总体做出推断的科学。当著名的 t 检验在 20 世纪初首次发布时,所有的计算都是用笔和纸完成的。即使在今天,STATS101 课程的学生也会了解到,“大样本”从 30 个观察值开始。

在数据难以收集和存储且手动计算繁琐的时代,统计上严谨的测试是回答有关广泛总体问题的好方法。然而,如今,随着数据的丰富,许多测试的实用性减少了。

特点是许多统计测试将数据量视为证据。数据越少,观察到的效应更容易受到采样误差的随机变动的影响,而数据越多,其方差则减少。因此,相同的观察效应在数据更多时对零假设的证据更为强烈。

为了说明这一现象,考虑比较两家公司 A 和 B 在员工性别比例上的差异。让我们设想两个情境。首先,随机抽取每家公司 10 名员工。在公司 A 中,10 人中有 6 人为女性,而在公司 B 中,10 人中有 4 人为女性。其次,增加我们的样本量到 1000。在公司 A 中,1000 人中有 600 人为女性,而在公司 B 中,1000 人中有 400 人为女性。在这两个情境中,性别比例是相同的。然而,更多的数据似乎为公司 A 雇佣比例上更多女性提供了更强的证据,不是吗?

这种现象通常在大数据样本的假设检验中表现出来。数据越多,p 值越低,因此我们更有可能拒绝零假设,并声明发现某种统计效应,例如数据漂移。

让我们看看这是否适用于我们的类别变量频率差异的卡方检验。在原始示例中,服务集的大小大约是训练集的十分之一。让我们将服务集中的频率乘以 1/100 到 10 之间的一组缩放因子,并每次计算卡方统计量和检验的 p 值。请注意,将服务集中的所有频率乘以相同的常数不会影响它们的分布:我们唯一改变的只是其中一个集合的大小。

training_freqs = np.array([10_322, 24_930, 30_299])
serving_freqs = np.array([1_015, 2_501, 3_187])

p_values, chi_sqs = [], []
multipliers = [0.01, 0.03, 0.05, 0.07, 0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10]
for serving_size_multiplier in multipliers:
  augmented_serving_freqs = serving_freqs * serving_size_multiplier
  cont_table = pd.DataFrame([
  training_freqs,
  augmented_serving_freqs,
  ])
  chi_sq, pvalue, _, _ = chi2_contingency(cont_table)
  p_values.append(pvalue)
  chi_sqs.append(chi_sq)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

乘数等于一的值是我们之前计算的值。注意,当服务规模增加到仅为原来的 3 倍(标记为垂直虚线)时,我们的结论完全改变:我们得到的卡方统计量为 11,p 值接近零,在我们的案例中这对应于指示数据漂移。

这导致了虚假警报的数量增加。尽管这些效果在统计上可能显著,但从性能监控的角度来看,它们不一定具有重要意义。拥有足够大的数据集,即使是微小的数据漂移也会被指示出来,即使它的强度不足以恶化模型的性能。

学习到这一点后,你可能会想建议将服务数据划分为多个块,并对较小的数据集进行多次测试。不幸的是,这也不是一个好主意。要理解原因,我们需要深入理解 p 值的真正含义。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

p 值的含义

我们已经将 p 值定义为在原假设为真的情况下,观察到至少与我们实际观察到的统计量一样不太可能的测试统计量的概率。让我们尝试解读这个复杂的表述。

原假设意味着没有效果,在我们的例子中:没有数据漂移。这意味着,无论训练数据和服务数据之间存在什么差异,它们都是随机抽样的结果。因此,p 值可以视为在这些差异仅来源于随机性的前提下,得到我们所观察到的差异的概率。

因此,我们的 p 值大约为 0.1,这意味着在完全没有数据漂移的情况下,10% 的测试将由于随机机会错误地发出数据漂移信号。这与我们之前介绍的 p 值表示的符号一致:P(data|H₀)。如果这个概率是 0.1,那么在 H₀ 为真(无漂移)的情况下,我们有 10% 的机会观察到的数据至少与我们观察到的数据一样不同(根据测试统计量)。

这就是为什么在较小的数据样本上进行更多测试不是一个好主意的原因:如果我们不是对每天的整个服务数据进行测试,而是将其分为 10 个块,每天进行 10 次测试,我们每天平均会出现一个虚假警报!这可能导致所谓的警报疲劳,即你被大量警报轰炸到停止关注它们的程度。当数据漂移真的发生时,你可能会错过它。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

贝叶斯来拯救我们

我们已经看到,根据测试的 p 值检测数据漂移可能不可靠,从而导致许多虚假警报。我们该如何做得更好?一个解决方案是完全转变思路,采用贝叶斯测试,这让我们能够直接估计我们需要的 P(H₀|data),而不是 p 值 P(data|H₀)。

贝叶斯是什么

贝叶斯统计方法与传统的经典统计方法不同。在贝叶斯方法的基本假设值得单独讨论的情况下,为了讨论数据漂移,我们先考虑贝叶斯方法的最重要特征:统计参数是随机变量

参数是随机变量

参数是我们感兴趣的未知值,用于描述一个总体。在我们之前的例子中,我们模型用户使用移动设备的比例就是一个参数。如果我们知道在任何给定时间段内这个比例,我们就能自信地声明设备变量是否随时间漂移。不幸的是,我们不知道所有可能成为模型输入的用户的设备;我们必须使用模型所接收到的服务数据样本。

经典方法将我们感兴趣的参数视为一个固定值。我们不知道它的具体值,但它是存在的。基于样本数据,我们可以尽量估计它,但任何这样的估计都会因采样偏差而有一定的方差。简而言之,我们正在尝试用一个具有偏差和方差的随机变量来估计我们认为是未知的固定值。

贝叶斯方法将我们感兴趣的参数视为由某种概率分布描述的随机变量。我们尝试估计的是这个分布的参数。一旦完成这些,我们就可以对参数做出概率性的陈述,比如“我们的模型用户使用移动设备的比例在 0.2 到 0.6 之间的概率是 55%”。

这种方法非常适合数据漂移检测:与依赖模糊的 p 值,P(data|H₀)不同,使用贝叶斯检验,我们可以直接计算数据漂移的概率和幅度,即 P(H₀|data)!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

数据漂移的贝叶斯检验

让我们尝试使用贝叶斯方法测试用户使用移动设备的频率是否在训练数据和服务数据之间发生了漂移。

获取我们需要的概率

那么,如何获得数据漂移的概率呢?可以使用所谓的贝叶斯规则,这是一条整洁的概率公理,允许我们在知道反向关系的情况下计算某事的条件概率。例如,如果我们知道 P(B|A),我们可以计算 P(A|B):

P(A|B) = P(B|A) * P(A) / P(B)

我们可以使用这个公式来估计移动用户的频率的概率分布,freq

P(freq|data) = P(data|freq) * P(freq) / P(data)

在贝叶斯术语中,上述方程中的所有元素都有其名称:

  • P(freq|data) 是我们追求的目标——即移动用户比例在看到数据后的后验分布或概率分布;

  • P(data|freq) 是似然或在给定特定移动用户频率的情况下观察到数据的概率;

  • P(freq) 是先验:我们在看到任何数据之前对移动用户比例的信念;

  • P(data) 可以看作是一个缩放因子,确保右边的结果作为概率分布总和为一。

如果我们将上述公式应用两次,一次用于训练数据,另一次用于服务数据,我们将获得两个频率参数的分布:一个基于分析数据,另一个基于参考数据。然后我们可以检查这两个分布如何比较以测试我们的假设:

P(H₀|data) = P(freq_ref|data) == P(freq_anal|data)

所以,为了计算后验,我们需要将先验与似然相乘,并将它们缩放到总和为一。概率分布上的算术运算不是一个简单的任务。我们该如何进行呢?

在某些特定情况下,当两个分布匹配得很好时,它们的公式会相互抵消,结果是一个已知的分布。在更复杂的情况下,使用马尔科夫链蒙特卡洛模拟方法来从后验分布中抽样值,而不是尝试计算其分布形式。还有第三种方法:在像我们这样简单的情况下,我们可以使用一种称为网格近似的方法。

数据漂移概率

让我们从定义数据开始。在训练数据中,我们有 65,551 个观察值,其中 30,299 个是移动用户。

num_obs = 65_551
num_mobile_obs = 30_299

我们感兴趣的参数是移动用户的频率。从理论上讲,它可以是 0%到 100%之间的任何值。我们将在从 0.0001 到 1 的网格上进行近似:

mobile_freq = np.arange(0, 1.0001, 0.0001)

我们现在可以创建一个包含所有移动用户频率及其观察数量的网格:

devices = pd.DataFrame([(x, y) for x in [num_mobile_obs] for y in mobile_freq])
devices.columns = ["num_mobile", "mobile_freq"]

是时候定义先验了:在看到任何数据之前,我们对我们的参数知道或假设了什么。可能是我们什么也不知道,或者我们不想用我们的信念过多地影响结果。在这种情况下,我们会采用无信息先验,例如均匀分布。这对应于将先验设置为全 1,表示对每个可能的移动用户百分比表达相同的先验概率。

devices["prior"] = 1.

接下来,我们的最后一个构建块,似然。在我们的问询中,用户可以是移动用户或非移动用户。这需要使用二项分布。对于数据框中的每一行,我们将计算给定观察到的移动用户数量、总用户数量和假设的移动用户频率的二项概率质量。

devices["likelihood"] = binom.pmf(
  devices["num_mobile"], 
  num_obs, 
  devices["mobile_freq"]
)

我们剩下的就是遵循贝叶斯公式:将先验与似然相乘,并将结果缩放到总和为一,以得到后验。

devices["posterior"] = devices["prior"] * devices["likelihood"]
devices["posterior"] /= devices["posterior"].sum()

让我们切分网格,选择与我们观察到的移动用户数量相匹配的后验分布。

mobile_obs = devices[devices["num_mobile"] == num_mobile_obs]
mobile_obs["posterior"] /= mobile_obs["posterior"].sum()

我们现在可以绘制训练数据中移动用户频率的后验概率。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们现在可以对我们的服务数据做完全相同的操作。在此过程中,我们将向代码中添加一项内容:在一个名为samples的字典中,我们将存储来自后验分布的样本,包括训练和服务数据。以下是实现这一任务的代码片段。

results = {}
for dataset, num_obs, num_mobile_obs, color in zip(
  ["training", "serving"], [65_551, 6703], [30_299, 3187], ["blue", "green"]
):
  # Set up grid
  num_mobile = np.arange(0, num_obs + 1, 1)
  mobile_freq = np.arange(0, 1.0001, 0.0001)
  devices = pd.DataFrame([(x, y) for x in num_mobile for y in mobile_freq])
  devices.columns = ["num_mobile", "mobile_freq"]

  # Follow Bayes rule to compute posterior
  devices["prior"] = 1.
  devices["likelihood"] = binom.pmf(
    devices["num_mobile"], 
    num_obs,
    devices["mobile_freq"],
  )
  devices["posterior"] = devices["prior"] * devices["likelihood"]
  devices["posterior"] /= devices["posterior"].sum()

  # Ger posterior for observed number of mobile users
  mobile_obs = devices[devices["num_mobile"] == num_mobile_obs]
  mobile_obs["posterior"] /= mobile_obs["posterior"].sum()

  # Sample from posterior and store the draws
  samples[dataset] = random.choices(
    mobile_obs["mobile_freq"].tolist(),
    weights=mobile_obs["posterior"].tolist(),
    k=10_000,
  )

  # Plot the posterior
  sns.lineplot(
    mobile_obs["mobile_freq"],
    mobile_obs["posterior"], 
    color=f"dark{color}",
    label=dataset,
  )
  plt.fill_between(
    mobile_obs["mobile_freq"],
    0,
    mobile_obs["posterior"],
    color=f"light{color}",
  )
  plt.xlim(0.45, 0.50)
  plt.xlabel("Proportion of users using a mobile device")
  plt.ylabel("Probability density")

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

测试时间

对于训练和服务数据,我们已经估计了移动用户比例的概率分布。这个比例有不同吗?如果有,我们就有一些数据漂移发生。由于上图中两个分布仅有少量重叠,它们似乎不同。不过,让我们尝试获得定量确认。

我们可以使用从两个分布中获取的样本进行此操作:我们只需检查服务数据的比例比训练数据中的比例更大多少次。这是我们对数据漂移概率的估计。

np.mean([
  serving > training
  for serving, training in
  zip(samples["serving"], samples["training"])
])

0.977

device 变量发生漂移的概率为 97.7%。漂移几乎肯定已经发生!但其规模有多大呢?

np.mean([
  serving - training
  for serving, training in
  zip(samples["serving"], samples["training"])
])

0.013

服务数据中移动用户的比例比训练数据中的相应比例高出 1.3 个百分点,期望值如此。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

收获

  • 依赖假设检验的单变量数据漂移检测方法并不总是可靠:结果可能依赖于样本大小,而 p 值实际上并不能直接测量数据漂移的概率或大小。

  • 依赖这种假设检验往往会导致许多虚假警报和警报疲劳。

  • 一种替代方法是使用贝叶斯方法,这允许我们直接估计数据漂移的概率和大小,而没有传统测试的许多缺陷。

本文也发表在 NannyML 博客上。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

感谢阅读!

如果你喜欢这篇文章,为什么不订阅电子邮件更新以获取我新文章的通知呢?通过成为 Medium 会员,你可以支持我的写作,并获得对其他作者及我的所有故事的无限访问权限。

需要咨询?你可以随时问我任何问题或在这里预约一对一咨询。

你还可以尝试阅读我其他的文章。不知道选择哪个?可以从这些中选一个:

## TensorFlow 的模型优化

使用量化和剪枝减少模型的延迟、存储和推断成本

[towardsdatascience.com [## 忘掉 ChatGPT

Bard、Sparrow 和多模态聊天机器人很快将使其过时,原因如下。

pub.towardsai.net ## 自监督学习在计算机视觉中的应用

如何仅用少量标记示例来训练模型

[towardsdatascience.com

所有图片,除非另有说明,均由作者提供。

如何检测机器学习模型中的漂移

原文:towardsdatascience.com/how-to-detect-drift-in-machine-learning-models-8a0be4049eed

这可能是为什么你的模型在生产环境中性能下降的原因

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Edwin Tan

·发表于Towards Data Science ·8 分钟阅读·2023 年 2 月 6 日

介绍

你是否曾在测试集上获得了出色的结果,却发现你的模型在生产环境中经过一段时间后表现不佳?如果是这样,你可能正在经历模型衰退。模型衰退是指机器学习模型性能随着时间的推移逐渐下降。在本文中,我们将讨论数据漂移如何导致模型衰退,以及我们如何设置早期检测漂移的机制。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由Samuel Wong拍摄,来源于Unsplash

机器学习中的漂移是什么?

在机器学习中,模型漂移指的是模型训练所用数据的基础分布发生变化,从而导致模型在新数据上的性能下降。这可能发生在模型部署到现实环境中时,遇到的数据分布随时间变化。例如,训练于疫情前的数据的模型可能在 Covid19 大流行期间的数据上表现不佳,因为数据的基础分布发生了变化。

为什么追踪模型漂移很重要?

跟踪漂移很重要,因为它有助于确保机器学习模型随着时间的推移继续做出准确的预测。随着模型在实际世界中的部署,它遇到的数据分布可能会发生变化,这可能导致模型性能下降。通过跟踪漂移,我们可以检测到这种情况并采取适当的措施来适应模型,例如在新数据上重新训练模型。这有助于防止模型做出越来越不准确的预测,这在某些应用中如欺诈检测、信用评分和医疗诊断中可能产生严重后果。跟踪模型漂移对于合规和监管原因也很重要。组织可能需要维护准确的记录,并有模型性能的可审计记录。

漂移的类型

这里是可能影响您模型的不同类型的漂移。

  1. 概念漂移 是独立变量与目标变量之间关系的变化。这发生在模型试图学习的基础概念或任务随着时间发生变化时。例如,如果欺诈信用卡交易的类型随时间改变,则一个用于检测欺诈信用卡交易的模型可能会经历概念漂移。

  2. 协变量偏移 是独立变量的偏移。这发生在输入变量的分布随着时间变化时,但基础概念或任务保持不变。例如,如果在一个地理位置上训练的模型被部署到另一个具有不同输入变量分布的位置,它可能会经历协变量偏移。

  3. 先验概率偏移 是目标变量的偏移。例如,如果在一个类别平衡的数据集上训练的模型被部署到一个某一类别远比另一类别更普遍的数据集上,它可能会经历先验概率偏移。

检测漂移的方法

这里是一些检测模型衰退和漂移的常见方法。

监控模型性能

这包括计算回归模型的 MSE、RMSE 以及分类模型的 AUC ROC、准确率、精确度、召回率和 F1 分数。生产环境和测试环境之间的大偏差可能会引发潜在漂移的警报。

然而,当预测时间与获取真实情况之间存在较长时间间隔时,这种方法可能不切实际。例如,在一个银行电话营销活动中,机器学习模型预测客户购买特定产品的倾向。该活动可能持续几个月,我们只能在活动结束时得知客户是否在活动期间进行购买。如果我们仅依赖模型性能作为模型漂移的指标,我们只能在活动结束时获得漂移警报。

虽然模型性能是一个有用的指标,但它是滞后的指标。我们可以通过监控输入特征采取主动方法来检测漂移。

监控输入特征的变化

监控输入特征变化的一种简单方法是通过描述性统计。描述性统计是用来总结数据集的数字。常见的描述性统计包括均值、中位数、众数、最小值和最大值。描述性统计的变化可能会引发潜在漂移的警报。

我们还可以监控输入特征分布的变化。常用的统计测试来监控分布变化包括 Kolmogorov-Smirnov 检验、人口稳定性指数(PSI)、Wasserstein 距离,也称为地球搬运工距离、Kullback-Leibler 散度和 Jensen-Shannon 距离。

在本文中,我们将通过一个示例来讲解如何使用 Evidently,这是一款利用各种统计测试的 Python 模型监控工具,以检测机器学习模型中的漂移。

示例

在以下示例中,我们将:

  1. 训练一个模型来预测房屋转售价格。

  2. 使用 Evidently AI 的预构建报告来监控拟合的模型。

设置

  • Visual Studio Code

  • Python 3.8

  • 所需的 Python 包

evidently==0.2.2
scikit-learn==1.1.2
pandas==1.4.3

获取数据

我们将使用新加坡转售房价数据集的一个子集[1]。由住房发展委员会提供的数据集显示了转售房屋的交易情况,包括交易的年月、单元类型、位置、单元面积和转售价格。

import pandas as pd
import re

df = pd.read_csv('path/to/data/resale-flat-prices-based-on-registration-date-from-jan-2017-onwards.csv')
def convert_to_years(x):

    str_split = x.split(' ')
    years = int(str_split[0])

    if len(str_split) == 4:
        months = int(x.split(' ')[2])
        total_years = round((years*12 + months)/12,2)

    else:

        total_years = years

    return total_years
df['year_month'] = pd.to_datetime(df['month'])
df['year'] = df['year_month'].dt.year
df['month'] = df['year_month'].dt.month
df = df.drop(columns = ['block', 'street_name'])
df['remaining_lease'] = df['remaining_lease'].apply(convert_to_years)
df = df.rename(columns = {'resale_price':'target'})

我们执行以下预处理步骤:

  • 创建日期特征yearmonth

  • remaining_lease列从字符串类型转换为浮点型。

  • resale_price列重命名为target

让我们根据交易日期将数据分成 3 个集合。

  • Train:这是用于训练的集,包含 2020 年的数据。我们已知该集中的标签。

  • Test:这是保留集,我们用来获取测试结果。它包含 2021 年的数据。我们已知该集中的标签。

  • Score:这是用于生产中评分的未见记录集合。我们不应该拥有此集的标签,因此我们将删除target列以模拟真实世界情况。它包含 2022 年的数据。

# split data

df_train = df.loc[(df['year_month'] >= '2020-01-01') & (df['year_month'] < '2021-01-01')].drop(columns = ['year_month']).sample(n=10000)
df_test = df.loc[(df['year_month'] >= '2021-01-01') & (df['year_month'] < '2022-01-01')].drop(columns = ['year_month']).sample(n=5000)
df_score = df.loc[df['year_month'] >= '2022-01-01'].drop(columns = ['year_month', 'target']).sample(n=5000)
y_train = df_train['target'].copy()
X_train = df_train.drop(columns='target').copy()
y_test = df_test['target'].copy()
X_test = df_test.drop(columns='target').copy()
X_score = df_score.copy()

训练模型

from sklearn.ensemble import GradientBoostingRegressor
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder
from sklearn.impute import SimpleImputer

categorical_features = ['town', 'flat_type', 'storey_range', 'flat_model']
categorical_transformer = Pipeline(steps=[('encoder', OneHotEncoder(handle_unknown='ignore'))])
numerical_features = ['floor_area_sqm', 'lease_commence_date', 'remaining_lease']
numerical_transformer = Pipeline(steps=[('impute', SimpleImputer())])
preprocessor = ColumnTransformer(
    transformers=[
        ('cat', categorical_transformer, categorical_features),
        ('num', numerical_transformer, numerical_features)])
gbr = GradientBoostingRegressor()
regressor = Pipeline([('processing', preprocessor), ('regr', gbr)])
regressor.fit(X_train, y_train)

预测

我们使用训练好的回归模型对测试集、评分集和训练集进行预测。请注意,此时我们只有训练集和测试集的目标列,而没有评分集。

df_test['prediction'] = regressor.predict(X_test)
df_score['prediction'] = regressor.predict(X_score)
df_train['prediction'] = regressor.predict(X_train)

预构建报告

Evidently AI 提供了各种预构建的指标和测试,称为指标和测试预设。这些是一组相关的指标或测试,以单一报告形式呈现给你。

以下是一些指标预设

  • DataQualityPreset:评估数据质量并提供描述性统计。

  • DataDriftPreset:评估各个列和数据集的数据漂移。

  • TargetDriftPreset:评估预测或目标漂移。

  • RegressionPreset:评估回归模型的质量。

  • ClassificationPreset:评估分类模型的质量。

以下是一些测试预设

  • NoTargetPerformanceTestPreset: 评估预测列中的数据漂移以及所有列的数据质量检查。

  • DataDriftTestPreset: 评估单独列和数据集中的数据漂移

  • DataQualityTestPreset: 评估数据质量并提供描述性统计

预构建指标

让我们来看看这些指标预设是如何工作的。

report = Report(metrics=[
  DataDriftPreset(drift_share=0.3),
  TargetDriftPreset()
])

report.run(reference_data=df_train, current_data=df_test)
report.save_html('evidently_metrics_report.html')

我们将训练集和测试集分别设置为参考数据集和当前数据集。请注意,我们没有选择要对列执行的统计测试。显然,Evidently 已根据输入数据的特征为我们做出了选择。了解更多有关他们如何做出这种决策的内容,请参阅这里

我们可以将 HTML 显示为 Jupyter Notebook 单元格输出,或将其保存为 HTML 文件。以下是我们在浏览器中打开 HTML 文件时的样子。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者制作的 GIF

报告包含以下内容:

  • 检测到漂移的列的数量和比例的总结。

  • 每一列的数据分布和漂移幅度

  • 特征与目标/预测之间的相关性

结果也可以以 Json 或 Python 字典的形式输出,格式如下:

report.json()
#OR
report.as_dict()

预构建测试

我们可以以类似的方式使用测试预设。我们将训练集和评分集分别设置为参考数据集和当前数据集。

tests = TestSuite(tests=[
    NoTargetPerformanceTestPreset()
])

tests.run(reference_data=df_train.drop(columns='target'), current_data=df_score)
tests.save_html('evidently_tests_report.html')

请注意,评分集仅有预测值,而没有实际值(即目标列),因此参考数据集中的目标列已被删除。以下是结果的样子。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作者制作的 GIF

NoTargetPerformanceTestPreset提供了数据漂移、质量和完整性的简明总结。结果也可以以 Json 或 Python 字典的形式输出,格式如下:

tests.json()
#OR
tests.as_dict()

结论

总结来说,机器学习中的模型漂移指的是数据的基础分布发生变化,这可能导致性能下降。跟踪模型漂移非常重要,以确保预测的准确性以及符合合规和监管要求。漂移有不同的类型,包括概念漂移、协变量偏移和先验概率偏移。检测漂移的方法包括监控模型性能和输入特征的描述性统计,以及使用统计测试监控输入特征分布的变化。通过使用像 Evidently AI 这样的工具,我们可以主动检测并解决模型漂移问题,以确保机器学习模型的性能和可靠性。

加入 Medium 阅读更多类似的文章!

参考

[1] 包含来自 HDB 转售价格的信息,该信息于 2023 年 1 月 31 日访问自 转售公寓价格-Data.gov.sg,根据新加坡开放数据许可 1.0 版的条款提供* 新加坡开放数据许可-Data.gov.sg

如何在 3 个步骤中开发 Streamlit 数据分析 Web 应用

原文:towardsdatascience.com/how-to-develop-a-data-analytics-web-app-in-3-steps-92cd5e901c52

构建你的第一个 YouTube 分析应用的逐步指南

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Destin Gong

·发表于 Towards Data Science ·8 分钟阅读·2023 年 2 月 25 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来源:Tran Mau Tri TamUnsplash

大多数情况下,数据科学/数据分析项目最终会交付一个静态报告或仪表板,这大大降低了投入过程中的努力和思考。另一方面,Web 应用是展示你的数据分析工作的绝佳方式,之后可以进一步扩展为自服务和互动平台上的服务。然而,作为数据科学家或数据分析师,我们没有开发软件或网站的训练。在这篇文章中,我将介绍像 Streamlit 和 Plotly 这样的工具,这些工具使我们能够通过 Web 应用轻松开发和服务你的数据分析项目,步骤如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在 3 个步骤中开发数据分析 Web 应用(图像来自作者的 网站

  1. 提取数据并建立数据库

  2. 将数据分析过程定义为函数

  3. 构建 Web 应用界面

之后,我们将能够创建一个像这样的简单 Web 应用:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Web 应用演示(图像由作者提供)

第一步:提取数据并建立数据库

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

开发数据分析 Web 应用的第一步(图像由作者提供)

我们将在这里使用 YouTube 数据作为示例,因为它与我们的日常生活相关。YouTube 数据 API 允许我们获取公共 YouTube 数据,例如视频统计数据(如点赞数、观看次数)或内容详细信息(如标签、标题、评论)。要设置 YouTube API,需要注册 Google 开发者账户并设置 API 密钥。以下是一些对我开始使用 YouTube API 很有帮助的资源。

这些资源带我们了解如何创建 YouTube API 密钥和安装所需的库(例如 googleapiclient.discovery)。解决这些依赖关系后,我们使用 Python 和自己的 API 密钥设置与 API 的连接,使用以下命令:

from googleapiclient.discovery import build
youtube = build('youtube', 'v3', developerKey=<your_api_key>)

建立连接后,是时候探索你的数据科学项目可以使用的数据了。为此,请查看 YouTube 数据 API 文档,它提供了可以访问的不同类型数据的概述。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

YouTube 数据 API 参考列表(截图由作者提供)

我们将使用 “Videos” 作为此项目的示例,list() 方法允许我们通过传递 part 参数和几个 filters 请求 “Video Resource”。part 参数指定要从视频资源中提取哪些组件,这里我选择了 snippet, statistics, and contentDetails。请查看这份 文档,它详细列出了你可以从 videos().list() 方法中获取的所有字段。我们还指定了以下 filter 参数来限制此请求返回的结果。

  • chart='mostPopular':获取最受欢迎的视频

  • regionCode='US':来自美国的视频

  • videoCategoryId=1:从特定视频类别获取视频(例如,1 代表电影和动画),可以在 视频类别 ID 目录中找到。

  • maxResults=20:返回最多 20 个视频

video_request = youtube.videos().list(
                part='snippet,statistics,contentDetails',
                chart='mostPopular',
                regionCode='US',
                videoCategoryId=1,
                maxResults=20
		      )
response = video_request.execute()

然后我们使用 video_request.execute() 执行请求,响应将以 JSON 格式返回,通常如下图所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

JSON 格式的响应(图像由作者提供)

所有信息都存储在响应中的 “items” 中。然后,我们提取 ‘items’ 键,并通过标准化 JSON 格式创建数据框 video_df

video_df = json_normalize(response['items'])

结果是,我们成功将输出整理成更易于操作的结构。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

video_df(图像来源:作者)

为了更进一步地使用 Python 处理 JSON,我推荐阅读文章 “如何在 Python 中最好地处理 JSON”。

第 2 步。将数据分析过程定义为函数

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

开发数据分析 Web 应用的第 2 步(图像来源:作者)

我们可以将多行代码语句打包成一个函数,以便它可以被迭代执行,并且在后续阶段容易与其他 Web 应用组件嵌入。

定义 extractYouTubeData()

例如,我们可以将上述数据提取过程封装到一个函数中:extractYouTubeData(youtube, categoryId),这允许我们传递一个 categoryId 变量,并输出该类别下的前 20 个热门视频作为 video_df。这样,我们可以获取用户选择的类别,然后将输入传递给该函数,获得相应的前 20 个视频。

def extractYouTubeData(youtube, categoryId):
    video_request = youtube.videos().list(
    part='snippet,statistics,contentDetails',
    chart='mostPopular',
    regionCode='US',
    videoCategoryId=categoryId,
    maxResults=20
    )
    response = video_request.execute()
    video_df = json_normalize(response['items'])
    return video_df

我们可以使用 video_df.info() 来获取该数据框中的所有字段。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

video_df 中的字段(图像来源:作者)

利用这个宝贵的数据集,我们可以进行各种分析,如探索性数据分析、情感分析、主题建模等。

我想从设计一个用于对这些最热门的 YouTube 视频进行探索性数据分析的应用开始。

  • 视频时长与点赞数

  • 最常出现的标签

在未来的文章中,我将探索更多技术,如主题建模和自然语言处理,以分析视频标题和评论。因此,如果你希望阅读我在 Medium 上的更多文章,非常感谢你通过注册 Medium 会员 ☕ 来支持我。

定义 plotVideoDurationStats()

我想了解视频时长是否与这些热门视频的点赞数有相关性。为此,我们首先需要将 contentDetails.duration 从 ISO 日期时间格式转换为数值,使用 isodate.parse_duration().total_seconds()。然后,我们可以使用散点图来可视化视频时长与点赞数的关系。这是通过 Plotly 完成的,它为最终用户提供了更互动的体验。下面的代码片段返回 Plotly 图形,这将被嵌入到我们的 Web 应用中。

import isodate
import plotly.express as px

def plotVideoDurationStats(video_df):
    video_df['contentDetails.duration'] = video_df['contentDetails.duration'].astype(str)
    video_df['duration'] = video_df['contentDetails.duration'].apply(lambda x: isodate.parse_duration(x).total_seconds())
    fig = px.scatter(video_df, x="duration", y='statistics.likeCount', color_discrete_sequence=px.colors.qualitative.Safe)
    return fig

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

plotVideoDurationStats 生成的图像(图像来源:作者)

若要探索更多基于 Plotly 的教程,请查看下面这些博客:

## Python 中假设检验的互动指南

T 检验、ANOVA、卡方检验示例

towardsdatascience.com ## 如何使用 Plotly 进行更深入和互动的数据探索

案例研究:卡塔尔世界杯队伍的动态 EDA

towardsdatascience.com

定义 plotTopNTags()

此函数创建某一视频类别的前 N 个标签的图形。首先,我们遍历所有 snippet.tags 并将所有标签收集到标签列表中。然后我们创建描述前 N 个最常见标签计数的 tags_freq_df。最后,我们使用 px.bar() 显示图表。

def plotTopNTags(video_df, topN):
    tags = []
    for i in video_df['snippet.tags']:
        if type(i) != float:
            tags.extend(i)
    tags_df = pd.DataFrame(tags)
    tags_freq_df = tags_df.value_counts().iloc[:topN].rename_axis('tag').reset_index(name='frequency')
    fig = px.bar(tags_freq_df, x='tag', y='frequency')
    return fig

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

plotTopNTags() 生成的图形(作者提供的图片)

第 3 步:构建 Web 应用界面

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

开发数据分析 Web 应用的第 3 步(作者提供的图片)

我们将使用 Streamlit 来开发 Web 应用界面。这是我迄今为止发现的最简单的 Web 应用开发工具,它运行在 Python 之上,省去了处理 HTTP 请求、定义路由或编写 HTML 和 CSS 代码的麻烦。

运行 !pip install streamlit 将 Streamlit 安装到你的计算机上,或使用此 文档 在你首选的开发环境中安装 Streamlit。

使用 Streamlit 创建 Web 应用组件非常简单。例如,显示标题如下所示:

import streamlit as st
st.title('Trending YouTube Videos')

在这里,我们需要几个组件来构建 Web 应用。

1) 输入:一个下拉菜单供用户选择视频类别

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

下拉菜单(作者提供的图片)

此代码片段允许我们创建一个下拉菜单,提示“选择 YouTube 视频类别”,并提供‘电影与动画’、‘音乐’、‘体育’、‘宠物与动物’等选项。

videoCategory = st.selectbox(
    'Select YouTube Video Category',
    ('Film & Animation', 'Music', 'Sports', 'Pets & Animals')
)

2) 输入:一个滑块供用户选择标签数量

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

滑块(作者提供的图片)

这定义了滑块并指定了滑块范围从 0 到 20。

topN = st.slider('Select the number of tags to display',0, 20)

3) 输出:一个显示视频时长与点赞数的图形

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

视频时长与点赞数(作者提供的图片)

我们首先创建 videoCategoryDict 将类别名称转换为 categoryId,然后通过我们之前定义的 extractYouTubeData() 函数传递 categoryId。请查看此 页面 获取视频类别及其对应的 categoryId**。**

然后我们调用 plotVideoDuration() 函数,并使用 st.plotly_chart() 显示 plotly 图表。

videoCategoryDict = {'Film & Animation': 1, 'Music': 10, 'Sports': 17, 'Pets & Animals': 15}
categoryId = videoCategoryDict[videoCategory]
video_df = extractYouTubeData(youtube, categoryId)
duration_fig = plotVideoDurationStats(video_df)
fig_title1 = 'Durations(seconds) vs Likes in Top ' + videoCategory + ' Videos'
st.subheader(fig_title1)
st.plotly_chart(duration_fig)

4) 输出:一个显示该视频类别中热门标签的图形

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

视频类别中的热门标签(作者提供的图片)

最后一个组件要求我们将用户输入的标签数量传递给函数plotTopNTags(),并通过调用该函数来创建图表。

tag_fig = plotTopNTags(video_df, topN)
fig_title2 = 'Top '+ str(topN) + ' Tags in ' + videoCategory + ' Videos'
st.subheader(fig_title2)
st.plotly_chart(tag_fig)

这些代码语句可以包含在一个 Python 文件中(例如 YoutTubeDataApp.py)。然后我们导航到命令行,使用!streamlit run YouTubeDataApp.py在网络浏览器中运行应用程序。

重点信息

对于数据分析师和数据科学家来说,构建一个网络应用程序可能看起来令人望而生畏。本文涵盖了三个步骤,帮助你开始构建你的第一个网络应用程序,并将数据分析项目扩展到自助服务平台:

  • 提取数据并构建数据库

  • 将数据分析过程定义为函数

  • 构建网络应用程序界面

更多类似资源

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Destin Gong

数据科学入门

查看列表9 个故事外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Destin Gong

EDA 和特征工程技术

查看列表9 个故事外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 ## 如何使用 Plotly 进行更深入和互动的数据探索

案例研究:卡塔尔世界杯队伍的动态 EDA

towardsdatascience.com

最初发表于 https://www.visual-design.net 于 2023 年 2 月 23 日。

如何使用 Folium Python 库显示 GeoJSON 文件中的数据

原文:towardsdatascience.com/how-to-display-data-from-geojson-files-using-the-folium-python-library-f7284cb2a256

创建英国大陆架石油和天然气田轮廓的互动地图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Andy McDonald

·发表于 Towards Data Science ·阅读时间 5 分钟·2023 年 2 月 24 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

由 Aksonsat Uanthoeng 拍摄,下载自 Pexels。

Folium 是一个优秀的 Python 库,它利用 Leaflet.js 的强大功能,使得在交互式地图上可视化地理空间数据变得简单。 在我之前的 文章 中,我介绍了如何在 Folium 地图上显示单独的标记,但我们也可以使用 Folium 来显示存储在 GeoJSON 文件中的数据。

GeoJSON 是一种常用的文件格式 用于存储地理空间数据,使用 JavaScript 对象表示法。这些文件可以存储位置数据、形状、点、表面等。

在本文中,我们将学习如何显示存储在 GeoJSON 文件格式中的英国北海石油和天然气田的多边形。

本文的数据来源于 data.gov.uk,并且遵循 开放政府许可证 数据可以从以下网站查看和下载。

[## OGA 离岸油田 WGS84

我们使用 cookies 收集有关您如何使用 data.gov.uk 的信息。我们使用这些信息来使网站正常运行…

www.data.gov.uk

设置库并显示基本的 Folium 地图

安装 Folium

如果您尚未安装 Folium,可以通过 pip 进行安装:

pip install folium

或者 Anaconda:

conda install folium

一旦你安装了库,第一步是将 folium 库导入到我们的 Jupyter Notebook 中:

import folium

为了简单起见,我们将 GeoJSON 文件的位置分配给一个变量。当我们以后想调用它或者更改为另一个文件时,这样做会使事情更简单,而不需要直接修改其余代码。

field_locations = 'geodata/NSTA_Offshore_Fields_WGS84.geojson'

接下来,我们需要用一些基本参数初始化一个 folium 地图。

创建的地图将以北纬 58 度和东经 5 度为中心。我们还将zoom_start设置为 6,以便能够查看大部分英国北海。

在创建 folium 地图时,我们可以使用几个其他参数。如果你想了解更多,请查看folium.map类的帮助文档。

m = folium.Map(location=[58, 5], 
                 zoom_start=6, control_scale=True)
m

当我们使用变量m调用 folium 地图对象时,我们将看到以下地图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

以北海为中心的基础 folium 地图。图片由作者提供。

生成的地图看起来很空旷,因此下一步是添加一些形状来标识英国海上油气田。

向 Folium 地图添加 GeoJSON 数据

为了显示 GeoJSON 文件中的数据,我们需要调用folium.geojson并传入几个参数。

第一个参数是 GeoJSON 文件的位置,我们将其分配给变量field_locations。接下来是tooltip参数,这允许我们在任何形状悬停时显示信息。在这个例子中,我们将字段设置为显示油气田的名称(FIELDNAME)。

最后,为了使对象显示,我们需要在末尾添加扩展add_to(m)

folium.GeoJson(field_locations,
               tooltip=folium.GeoJsonTooltip(fields=['FIELDNAME'])
              ).add_to(m)

当我们调用 folium 地图对象m时,我们会看到以下地图:

从生成的地图中,我们可以看到所有英国北海油气田的位置。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Folium 地图展示了英国北海油气田的轮廓。图片由作者提供。

在 Folium 中应用自定义颜色到 GeoJson 形状

如果我们想为地图上的形状添加一些样式,可以通过首先创建一个用于控制形状颜色的函数来实现。

从这个数据集中,我们将使用字段类型来选择我们想要显示的颜色。任何油田将显示为绿色,气田显示为红色,凝析油田将显示为橙色。

以下代码源于这个StackOverflow 帖子

def field_type_colour(feature):
    if feature['properties']['FIELDTYPE'] == 'COND':
        return 'orange'
    elif feature['properties']['FIELDTYPE'] == 'OIL':
        return 'green'
    elif feature['properties']['FIELDTYPE'] == 'GAS':
        return 'red'

既然我们已经设置好了条件函数,我们需要在调用folium.GeoJson时添加一个新的参数,称为style_function。在这个参数中,我们传入一个包含样式属性字典的 lambda 函数。

关于 fillColor 属性,我们调用上面创建的函数,并传入我们的 feature

我们还可以将额外的样式属性传递到字典中。对于这个例子,我们将 fillOpacity 设置为 0.9,将控制形状周围线条的权重设置为 0。

如果我们不想包括上一节中的形状,我们需要重新创建 folium 地图。

m = folium.Map(location=[58, 5], 
                 zoom_start=6, control_scale=True)

folium.GeoJson(field_locations, name='geojson', 
               tooltip=folium.GeoJsonTooltip(fields=['FIELDNAME']),
               style_function= lambda feature: {'fillColor':field_type_colour(feature), 
                                                'fillOpacity':0.9, 'weight':0}
              ).add_to(m)
m

当我们调用 m 时,我们会看到以下地图。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

英国北海油气田的 folium 地图,按油田类型着色。图片由作者提供。

我们现在可以看到,地图上的每个形状/油田根据储层部分包含的主要碳氢化合物类型进行着色。

向 Folium 工具提示中添加额外信息

在 GeoJSON 文件中,我们为每个形状有几个额外的属性。这些可以通过将它们包含在 fields 参数的列表中,轻松地添加到现有的工具提示中。

m = folium.Map(location=[58, 5], 
                 zoom_start=6, control_scale=True)

folium.GeoJson(field_locations, name='geojson', 
               tooltip=folium.GeoJsonTooltip(fields=['FIELDNAME', 
                                                     'PROD_DATE',
                                                     'CURR_OPER']),
               style_function= lambda feature: {'fillColor':field_type_colour(feature), 
               'fillOpacity':0.9, 'weight':0}).add_to(m)
m

当我们重新运行代码时,我们现在可以看到,当我们悬停在每个形状上时,显示了额外的属性。在这种情况下,我们可以看到油田名称、生产开始日期和油田当前的运营商。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Folium 地图显示了英国油气田的轮廓,悬停在每个区域时会出现额外的属性——图片由作者提供。

摘要

在这篇简短的文章中,我们已经看到如何轻松地在交互式 folium 地图上显示存储在 GeoJSON 文件中的数据。这使我们能够快速显示可能存储在这种文件类型中的形状和轮廓。

感谢阅读。在你离开之前,你一定要订阅我的内容,获取我的文章到你的邮箱中。 你可以在这里订阅!或者,你可以 注册我的新闻通讯 ,免费将额外内容直接发送到你的邮箱中。

其次,你可以通过注册会员来获得完整的 Medium 体验,并支持我和成千上万其他作者。每月仅需 $5,你就可以完全访问所有精彩的 Medium 文章,并有机会通过你的写作赚取收入。如果你使用 我的链接 你将直接用你的一部分费用支持我,而且不会增加额外费用。如果你这样做了,非常感谢你的支持!

如何有效地进行交叉验证

原文:towardsdatascience.com/how-to-do-cross-validation-effectively-1bbeb1d69ee8

交叉验证最佳实践指南:重新训练和嵌套

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 Vitor Cerqueira

·发表于 Towards Data Science ·6 分钟阅读·2023 年 2 月 6 日

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

5 折蒙特卡罗交叉验证。图像来源于作者。

交叉验证是构建稳健机器学习模型的关键因素。然而,它往往未能发挥其全部潜力。

在这篇文章中,我们将探讨两种重要的实践,以充分利用交叉验证:重新训练和嵌套。

让我们开始吧!

什么是交叉验证?

交叉验证是一种评估模型性能的技术。

这个过程通常涉及测试几种技术,或对特定方法进行超参数优化。在这种情况下,你的目标是检查哪种替代方案最适合输入数据。

关键是选择能最大化性能的方法。这就是将被部署到生产中的模型。此外,你还希望对该模型的性能有一个可靠的估计。

交叉验证后的重新训练

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

交叉验证后,你应该使用所有可用数据重新训练最佳模型。图像来源于作者。

假设你进行交叉验证以选择一个模型。你使用 5 折交叉验证测试许多备选方案。然后,线性回归模型表现最佳。

那么接下来应该做什么呢?

是否应该使用所有可用数据重新训练线性回归模型?还是应该使用交叉验证过程中训练的模型?

这一部分在数据科学家中会引发一些困惑——不仅在初学者中,还有更有经验的专业人士中也会有。

交叉验证后,你应该使用所有可用数据重新训练最佳方法。以下是来自传奇书籍 《统计学习的元素 [1](括号内为我所加)

我们在交叉验证后的最终选择模型是 f(x),然后将其拟合到所有数据上。

但这一想法并不被普遍接受。

一些从业者在交叉验证期间保留了最佳模型。按照上述示例,你会保留 5 个线性回归模型。然后,在部署阶段,你会对每个预测进行预测平均。

这不是交叉验证的工作方式。

这种方法有两个问题:

  • 它使用较少的数据进行训练;

  • 它导致了成本增加,因为需要维护多个模型。

数据较少

如果不重新训练,你将不会利用所有可用的实例来创建模型。

如果没有大量数据,这可能会导致次优模型。使用所有可用实例进行训练更可能实现更好的泛化。

重新训练在时间序列中特别重要,因为最新的观察结果用于测试。如果不在这些时间点重新训练,模型可能会遗漏新出现的模式。

成本增加

有人认为,将交叉验证期间训练的 5 个模型结合起来可以带来更好的性能。

然而,理解其影响是很重要的。你不再使用简单、可解释的线性回归。

你的模型是一个集成体,其中的个别模型通过随机子采样进行训练。随机子采样是一种在集成模型中引入多样性的方法。集成模型通常比单一模型表现更好。但它们也会导致额外的成本和较低的透明度。

如果你只保留一个模型,而不是将所有模型结合起来,会怎么样?

这将解决成本增加的问题。然而,仍不清楚你应该选择哪个版本的模型。

重新训练可以跳过的两个原因。如果数据集很大或者重新训练成本太高。这两个问题通常是相关的。

重新训练——实际示例

下面是如何在交叉验证后重新训练最佳模型的一个示例:

from sklearn.datasets import make_regression
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import GridSearchCV, KFold

# creating a dummy dataset
X, y = make_regression(n_samples=100)

# 5-fold cross-validation
cv = KFold(n_splits=5)

# optimizing the number of trees of a RF
model = RandomForestRegressor()
param_search = {'n_estimators': [10, 50, 100]}

# applying cross-validation with a grid-search
# and re-training the best model afterwards
gs = GridSearchCV(estimator=model, cv=cv, refit=True, param_grid=param_search)
gs.fit(X, y)

目标是优化随机森林中的树木数量。这是通过scikit-learn中的GridSearchCV类完成的。你可以设置参数refit=True,这样在交叉验证后,最佳模型将自动重新训练。

你可以通过从GridSearchCV获得最佳参数来显式地初始化新模型:

best_model = RandomForestRegressor(**gs.best_params_)
best_model.fit(X, y)

获得可靠的性能估计

在开发模型时,你希望达到三个目标:

  1. 从众多备选方案中选择一个模型;

  2. 训练所选模型并部署它;

  3. 获得所选模型的可靠性能估计。

交叉验证和重新训练涵盖了前两个要点,但不包括第三个。

为什么会这样?

交叉验证通常会重复进行多次,以便选择最终模型。你会测试不同的转换和超参数。因此,你最终会调整方法,直到对结果感到满意为止。

这可能导致过拟合,因为验证集的细节可能泄漏到模型中。因此,你从交叉验证中得到的性能估计可能过于乐观。你可以在参考文献[2]中阅读更多相关内容。

这也是为什么 Kaggle 比赛有两个排行榜,一个是公共的,另一个是私人的。这可以防止竞争者对测试集进行过拟合。

那么,你该如何解决这个问题呢?

你应该增加一个额外的评估步骤。在交叉验证后,你需要在一个保留的测试集上评估选择的模型。完整的工作流程如下:

  1. 将可用数据划分为训练集和测试集;

  2. 使用训练集应用交叉验证来选择模型;

  3. 使用训练数据重新训练选择的模型,并在测试集上进行评估。这为你提供了一个无偏的性能估计;

  4. 使用所有可用数据重新训练选择的模型并部署它。

这是这个过程的可视化描述:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

应用交叉验证和训练数据。在交叉验证之后,再训练选择的模型并在测试集上进行评估。最后,再次训练选择的模型并部署它。图片来源:作者。

实际例子

这是使用scikit-learn的完整过程的实际例子。你可以查看评论以获取更多背景信息。

import numpy as np
import pandas as pd

from sklearn.datasets import make_regression
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import r2_score
from sklearn.model_selection import (GridSearchCV,
                                     KFold,
                                     train_test_split)
# creating a dummy data set
X, y = make_regression(n_samples=100)

# train test split
X_train, X_test, y_train, y_test = \
   train_test_split(X, y, shuffle=False, test_size=0.2)

# cv procedure
cv = KFold(n_splits=5)

# defining the search space
# a simple optimization of the number of trees of a RF
model = RandomForestRegressor()
param_search = {'n_estimators': [10, 50, 100]}

# applying CV with a gridsearch on the training data
gs = GridSearchCV(estimator=model, 
                  cv=cv, 
                  param_grid=param_search)
## the proper way of doing model selection
gs.fit(X_train, y_train)

# re-training the best approach for testing
chosen_model = RandomForestRegressor(**gs.best_params_)
chosen_model.fit(X_train, y_train)

# inference on test set and evaluation
preds = chosen_model.predict(X_test)
## unbiased performance estimate on test set
estimated_performance = r2_score(y_test, preds)

# final model for deployment
final_model = RandomForestRegressor(**gs.best_params_)
final_model.fit(X, y)

嵌套交叉验证

上述内容是所谓嵌套交叉验证的简化版本。

在嵌套交叉验证中,你在外部交叉验证过程的每个折叠中执行完整的内部交叉验证过程。内部过程的目标是选择最佳模型。然后,外部过程为该模型提供无偏的性能估计。

嵌套交叉验证很快变得效率低下。它仅在小型数据集上是实际可行的。

大多数从业者都采用上述过程。如果你有一个大型数据集,你也可以用单次划分来替代交叉验证程序。这样,你会得到三个部分:训练、验证和测试。

关键要点

嵌套和再训练是交叉验证的两个重要方面。

交叉验证选择的模型的性能估计可能过于乐观。因此,你应该进行三重划分以获得可靠的估计。三重划分是一种嵌套交叉验证形式。

在选择模型或估计其性能后,你应使用所有可用数据重新训练模型。这样,模型在新观察数据中的表现更可能更好。

感谢阅读,下次故事见!

相关文章

  • 应用时间序列交叉验证时需要做的 4 件事

  • 时间序列的蒙特卡罗交叉验证

参考文献

[1] Hastie, Trevor, 等. 统计学习的要素:数据挖掘、推断与预测. 第 2 卷. 纽约: springer, 2009.

[2] Cawley, Gavin C., 和 Nicola LC Talbot. “模型选择中的过拟合及其对性能评估的选择偏差。” 机器学习研究期刊 11 (2010): 2079–2107.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值