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

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

使用 MLflow 的端到端 ML 管道:追踪、项目和服务

原文:towardsdatascience.com/end-to-end-ml-pipelines-with-mlflow-tracking-projects-serving-1b491bcdc25f

高级 MLflow 使用教程

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

·发布于 Towards Data Science ·阅读时间 9 分钟·2023 年 2 月 16 日

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

照片由 Jeswin Thomas 提供,来源于 Unsplash

介绍

MLflow 是一个功能强大的工具,通常因其实验追踪功能而受到关注。这也很容易理解——它是一个用户友好的平台,用于记录机器学习实验的所有重要细节,从超参数到模型。但你知道 MLflow 提供的不仅仅是实验追踪吗?这个多功能框架还包括 MLflow Projects、模型注册和内置的部署选项。在这篇文章中,我们将探讨如何利用这些功能来创建一个完整且高效的 ML 管道。

对于完全没有 MLflow 基础的初学者来说,这个教程可能会有点难度,因此我强烈建议你在深入学习这个教程之前,先观看这两个视频!

设置

在这个项目中,我们将在本地进行操作,因此请确保正确设置你的本地环境。项目需要三个主要依赖项——mlflowpyenvkaggle。虽然可以通过 pip 安装 MLflow,但你需要按照不同的说明来设置 pyenvkaggle

完成安装后,确保拉取 这个仓库 的最新版本。当你在笔记本电脑上获取了仓库后,我们就可以开始了!

项目概述

移动到 mlflow_models 文件夹,你将看到以下结构:

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

mlflow_models 文件夹结构。图像由作者提供。

这里是这个项目中每个文件的简要概述:

  • MLProject — 描述 MLflow 项目的 yaml 风格文件

  • python_env.yaml — 列出运行项目所需的所有环境依赖项

  • train_hgbt.pytrain_rf.py — 使用特定超参数的 HistGradientBoosterTree 和 RandomForest 模型的训练脚本

  • search_params.py — 用于执行超参数搜索的脚本

  • utils — 文件夹包含项目中使用的所有实用程序函数

如前所述,本项目是端到端的,因此我们将从数据下载到模型部署。大致的工作流程如下:

  1. 从 Kaggle 下载数据

  2. 加载和预处理数据

  3. 调整随机森林(RF)模型,进行 10 次迭代

  4. 注册最佳 RF 模型并将其放入生产桶

  5. 使用内置的 REST API 部署模型

完成后,你可以自行重复步骤 2 到 5 以训练 HistGradientBoostedTrees 模型。在进入项目之前,让我们看看这些步骤如何得到 MLflow 的支持。

MLflow 组件

一般而言,MLflow 有 4 个组件 — Tracking、Projects、Models 和 Registry。

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

MLflow 组件。来自 mlflow.org 的截图

回顾项目步骤,下面是我们将如何使用它们。首先,我使用了MLflow Projects 来打包代码,这样你或其他数据科学家/工程师可以重现结果。其次,MLflow Tracking 服务将跟踪你的调优实验。这样,你将在下一步中检索到最佳实验,然后将你的模型添加到模型注册表中。 从注册表中,部署模型将真正变成一行代码,因为它们以 MLflow Models 格式保存,并具有内置的 REST API 功能。

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

项目步骤概览。图片由作者提供。

管道概览

数据

当你运行管道时,数据将会自动下载。作为示例,我将使用贷款违约数据集(CC0:公共领域许可证),但你可以通过重写training_data参数并将列名更改为相关的名称来进行调整。

MLProject & 环境文件

MLProject 文件为你提供了一种方便的方式来管理和组织你的机器学习项目,允许你指定重要的细节,如项目名称、Python 环境的位置和管道的入口点。每个入口点可以通过独特的名称、相关参数和特定命令进行自定义。命令作为可执行的 shell 行,每当入口点被激活时就会执行,并且能够利用先前定义的参数。

python_env.yaml 文件概述了执行管道所需的精确 Python 版本,以及所有必需软件包的全面列表。

这两个文件是创建运行项目所需环境所必需的。现在,让我们查看管道将执行的实际脚本(入口点)。

训练和实验跟踪

训练是在 train_rf.pytrain_hgbt.py 脚本中完成的。它们大体相同,唯一的区别在于传递的超参数和预处理管道。考虑下面的函数,它下载数据并训练一个随机森林模型。

实验在使用 with mlflow.start_run() 定义 MLflow 上下文时开始。在此上下文中,我们使用 mlflow.log_metrics 来保存 PR AUC 指标(有关更多信息,请查看 eval_and_log_metrics 函数)和 mlflow.sklearn.log_model 来保存预处理和建模管道。这样,当我们加载管道时,它将一起执行所有预处理和推理。如果你问我,这非常方便!

超参数调优

超参数调优使用 search_params.py 中的 Hyperopt 包进行。大部分代码来自官方mlflow 仓库,但我已经尽量简化了。这个脚本中最棘手的部分是理解如何组织这些调优轮次,使其与“主”项目运行连接起来。本质上,当我们使用 MLflow 运行 search_params.py 时,我们要确保实验的结构如下:

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

实验结构的可视化。图片来源于作者

正如你所见,search_params 脚本只会指定train_rf.py 接下来应该使用哪些参数(例如,10、2 和 5 的深度),以及它的父级运行 ID(在上面的示例中是 1234)。当你探索脚本时,确保注意以下细节。

  • 当我们定义 mlflow.start_run 上下文时,我们需要确保 nested 参数设置为 True

  • 当我们运行 train_rf.py(或 train_hgbt.py)时,我们显式传递 run_id 并使其等于先前创建的 child_run 运行

  • 我们还需要传递正确的 experiment_id

请查看下面的示例,以了解代码中的全部工作原理。eval 函数是将通过 Hyperopt 最小化函数进行优化的函数。

实际的调优函数相对简单。我们所做的就是初始化一个 MLflow 实验运行(所有其他运行的父级运行),并使用提供的搜索空间优化目标函数。

请注意,这些函数只是为了说明代码的主要部分。有关完整的代码版本,请参考github 仓库

运行 RF 流程

到现在为止,你应该对脚本如何工作有一个大致的了解了!所以,让我们用这一行运行随机森林管道:

mlflow run -e search_params --experiment-name loan . -P model_type=rf

让我们分解这个命令行:

  • mlflow run . 意味着我们想要在此文件夹中运行项目

  • -e search_params 指定了我们要运行的 MLProject 文件中的入口点。

  • --experiment-name loan 将实验名称设置为“loan”。你可以将其设置为任何你想要的名称,但请记下,因为之后会用到。

  • -P model_type=rfsearch_params脚本中的model_type参数设置为“rf”(即随机森林)。

当我们运行这行代码时,应该会发生四件事:

  1. Python 虚拟环境将被创建。

  2. 名为“loan”的新实验将被初始化。

  3. Kaggle 数据将被下载到新创建的文件夹data中。

  4. 超参数搜索将开始。

实验完成后,我们可以在 MLflow UI 中检查结果。要访问它,只需在命令行中使用命令mlflow ui。在 UI 中,选择“loan”实验(或你给它起的任何名字),并将你的指标添加到实验视图中。

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

MLflow UI 截图。图像由作者提供。

最佳的 RF 模型在测试 PR AUC 中达到了 0.104,训练时间为 1 分钟。总体来说,超参数调整大约需要 5 分钟完成。

注册模型

到目前为止,我们已经训练、评估并保存了 10 个随机森林模型。从理论上讲,你可以直接去 UI 中找到最佳模型,手动将其注册到模型注册表并提升到生产环境。然而,更好的方法是通过代码进行,因为这样可以自动化这一步骤。这正是model_search.ipynb笔记本所涵盖的内容。使用它来跟随下面的各个部分。

首先,我们需要找到最佳模型。要程序化地完成此操作,你需要收集所有超参数调整实验(10 个)并按测试指标进行排序。

你的结果可能会有所不同,但主要目标是得到正确的best_run参数。请注意,如果你更改了实验名称,你也需要在此脚本中更改它。父级运行 ID 可以在 UI 中查找,点击父实验(在这种情况下名为**“capable-ray-599”**)。

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

运行 ID 查找 MLflow UI。截图由作者提供。

为了测试模型是否按预期工作,我们可以轻松地将其加载到笔记本中。

如果你成功得到了预测——恭喜,你已经做对了一切!最后,注册模型并将其提升到生产环境也非常简单。

运行这 2 行代码会注册你的模型,并将其内部提升到“Production”桶。所有这些操作只是改变了我们访问模型及其元数据的方式,但在模型版本控制的背景下,这非常强大。例如,随时可以比较版本 1 和版本 2。

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

MLflow 模型注册表。截图由作者提供。

如果你进入 UI 的“Models”选项卡,你确实会看到一个名为loan_model的模型,其版本 1 目前在生产环境中。这意味着我们现在可以通过其名称和阶段来访问该模型,这非常方便。

服务模型

最简单的模型服务方式是在本地进行。这通常用于测试端点,并确保我们获得预期的输出。使用 MLflow 服务模型非常简单,特别是当我们已经注册了模型时。你只需运行以下命令:

mlflow models serve — model-uri models:/loan_model/Production -p 5001

这条命令将启动一个本地服务器,该服务器将在 5001 端口托管你的模型(即loan_model,目前处于Production阶段)。这意味着你可以向localhost:5001/invocations端点发送请求,并获取预测结果(前提是请求格式正确)。

要在本地测试端点,你可以使用requests库来调用它并获取预测结果。

在上面的示例中,我们获得了与之前相同的概率,但现在这些分数是由本地服务器而不是你的脚本生成的。输入需要遵循非常具体的指南,这就是为什么我们有 4 行预处理的原因。你可以在这里阅读更多关于 MLflow 服务的期望格式的信息。

总结

如果你已经做到这些并且一切正常——给自己一个热烈的掌声吧!我知道这需要消化很多内容,所以让我们总结一下你迄今为止取得的成就。

  1. 你看到了并理解了如何使用 MLflow Projects 来构建你的项目。

  2. 你了解我们在脚本中记录参数、指标和模型的位置,以及search_params.py如何调用train_rf.py

  3. 你现在可以运行 MLflow Projects 并在 MLflow UI 中查看结果。

  4. 你知道如何找到最佳模型,如何将其添加到模型注册中心,以及如何以编程方式将其推广到生产环境。

  5. 你可以在本地从模型注册中心提供模型,并调用端点进行预测。

接下来做什么?

我强烈建议你通过尝试运行梯度提升树模型的管道并部署 HGBT 模型来检验你的技能。所有必要的脚本都已提供,因此只需你配置管道并完成部署。如果遇到任何挑战或有任何问题,请不要犹豫,在评论区留下你的问题。

使用 GPT-3.5 进行端到端的机器学习

原文:towardsdatascience.com/end-to-end-ml-with-gpt-3-5-8334db3d78e2?source=collection_archive---------2-----------------------#2023-05-24

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

插图由我和 Midjourney 生成

了解如何使用 GPT-3.5 来处理数据采集、预处理、模型训练和部署的繁重任务

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

·

关注 发表在 数据科学前沿 ·14 分钟阅读·2023 年 5 月 24 日

在任何机器学习应用程序的模型开发阶段,都存在大量重复的样板代码。像 PyTorch Lightning 这样的流行库已被创建,用以标准化训练/评估神经网络时执行的操作,从而使代码更加简洁。然而,样板代码的范围远超训练循环。即使是机器学习项目的数据获取阶段也充满了必要但耗时的步骤。应对这一挑战的一种方式是创建一个类似于 PyTorch Lightning 的库,涵盖整个模型开发过程。它必须足够通用,以适用于各种模型类型,并能够集成各种数据源。

提取数据、预处理、模型训练和部署的代码示例在互联网上随处可见,尽管收集这些代码并将其集成到项目中需要时间。由于这些代码在互联网上,可能已经被大型语言模型(LLM)训练过,并且可以通过自然语言命令以各种有用的方式重新排列。本篇文章的目标是展示如何通过使用 OpenAI 的 GPT-3.5 API 来自动化许多常见的 ML 项目的步骤。我会展示一些失败案例,并在可能的情况下如何调整提示以修复错误。从头开始,甚至没有数据集,我们将最终得到一个可以在 AWS SageMaker 上部署的模型。如果你跟着做,请确保按照以下步骤设置 OpenAI API:

import openai
openai.api_key = "YOUR KEY HERE"

此外,以下实用函数对调用 GPT-3.5 API 很有帮助:

def get_api_result(prompt):
    request = openai.ChatCompletion.create(
        model="gpt-3.5-turbo-0301",
        messages=[{"role": "user", "content": prompt}]
    )

    result = request['choices'][0]['message']['content']

    print(result)

提取、转换、加载(ETL)

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

ETL 由我 + Midjourney 进行说明

本节经过简化,仅考虑了单一数据源,但原则上可以扩展到数据来自多个来源的情况(csv 文件、数据库、图像等)。第一步是 提取 一些数据。为了简单起见,我将使用 Income Prediction¹ 数据集,其目标是预测一个人是否年收入超过或低于 50k 美元,基于他们的教育、职位、行业等。下面的函数将用于生成下载我们数据的代码。注意提示模板是如何设计的,以使 API 生成 Python 代码:

def extract(prompt):
    prompt_template = """You are a ChatGPT language model that can generate Python code. Please provide a natural language input text, and I will generate the corresponding Python code.\nInput: {}\nPython code:""".format(prompt)

    get_api_result(prompt_template)

在这里,我们要求 GPT-3.5:

使用 sklearn fetch_openml 函数从 openml 检索成人收入预测数据集。确保将数据作为单个数据框检索,其中包含一个名为“target”的列。将结果数据框命名为“df”。

这会给我们输出:

from sklearn.datasets import fetch_openml

adult_income = fetch_openml(name='adult', version=2)
df = pd.DataFrame(adult_income.data, columns=adult_income.feature_names)
df['target'] = adult_income.target

这段代码没有错误,完全达到了我们想要的效果。如果我使用更简单的提示,去掉提及 openml 和用于检索的函数,我们会得到:

import pandas as pd

df = pd.read_csv("adult_income_prediction.csv")
df.columns = ["age", "workclass", "fnlwgt", "education", "education-num",
              "marital-status", "occupation", "relationship", "race",
              "sex", "capital-gain", "capital-loss", "hours-per-week",
              "native-country", "target"]

这假设数据在本地可用。有趣的是,结果中有正确的列名列表,即使我们没有在 API 调用中包含它们!这些名称在网络上随处可见,特别是在这篇 Medium 文章中,目标列除外,它是由 GPT 添加的。下一步是转换数据为机器学习模型可以使用的格式。由于 GPT 需要了解列名及其类型,我们这里需要使用一个更高级的提示模板:

def transform(columns, column_types, prompt):
    prompt_template = """You are a ChatGPT language model that can generate Python code. Please provide a natural language input text, and I will generate the corresponding Python code using the Pandas to preprocess the DataFrame. The DataFrame columns are {} and their corresponding dtypes are {}.\nInput: {}\nPython code:""".format(columns, column_types, prompt)

    get_api_result(prompt_template)

我接着做如下请求:

通过将所有类别列转换为其独热编码等效项,并标准化数值列来预处理数据框。删除任何列中有 NA 或 NaN 值的行。删除数值列的异常值行,这些异常值由其 z 分数确定。数值列的异常值是超出 1 到 99 分位范围的值。数值列应使用 sklearn 中的 StandardScaler 进行标准化。目标列中的值应转换为 0 或 1,并应为 int 类型。

我们现在得到:

import pandas as pd
from sklearn.preprocessing import StandardScaler

# Load the DataFrame
df = pd.read_csv('data.csv')

# Convert categorical columns to one-hot encoded equivalents
df = pd.get_dummies(df, columns=['workclass', 'education', 'marital-status', 'occupation', 'relationship', 'race', 'sex', 'native-country'])

# Normalize numerical columns
scaler = StandardScaler()
numerical_cols = ['age', 'fnlwgt', 'education-num', 'capital-gain', 'capital-loss', 'hours-per-week']
df[numerical_cols] = scaler.fit_transform(df[numerical_cols])

# Drop rows with NA or NaN values
df.dropna(inplace=True)

# Drop rows with numeric column outliers
z_scores = df[numerical_cols].apply(lambda x: (x - x.mean()) / x.std())
df = df[(z_scores > -3).all(axis=1) & (z_scores < 3).all(axis=1)]

# Convert target column to 0 or 1 and change type to int
df['target'] = df['target'].apply(lambda x: 1 if x == '>50K' else 0).astype(int)

如果我们排除尝试从 csv 文件加载数据的部分,这几乎是完美的。即使我们明确告诉 GPT 假设df已经存在且不需要加载,也很难说服它排除这一点。最后,我们需要加载数据到本地数据库中。虽然对于如此简单的用例来说这有些多余,但这是一个值得养成的好习惯。

def load(prompt):
    prompt_template = """You are a ChatGPT language model that can generate Python code. Please provide a natural language input text, and I will generate the corresponding Python code.\nInput: {}\nPython code:""".format(prompt)

    get_api_result(prompt_template)

我使用以下指令:

连接到名为“data”的 sqlite 数据库。使用 pandas 将名为“df”的 DataFrame 中的数据插入到名为“income”的表中。不要包括索引列。提交更改后再关闭连接。

以获得:

import sqlite3
import pandas as pd

# Create a connection to the database
connection = sqlite3.connect("data.db")

# Define the DataFrame
df = pd.DataFrame()

# Define the table name
table_name = "income"

# Insert the data into the table
df.to_sql(table_name, connection, if_exists="replace", index=False)

# Commit the changes to the database
connection.commit()

# Close the connection
connection.close()

这已经很完美,只是多了不必要的df创建。有了处理后的数据,我们可以准备训练一些模型。

模型训练

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

我和 Midjourney 绘制的损失函数插图

在资源允许的情况下,尝试几种不同的模型类型以找出适合给定任务的复杂度水平是个好主意。因此,我们让 GPT-3.5 尝试几种不同的模型。首先,我们设置通用的模型训练提示模板:

def train(prompt):
    prompt_template = """You are a ChatGPT language model that can generate Python code. Focus on using scikit-learn when applicable. Please provide a natural language input text, and I will generate the corresponding Python code.\nInput: {}\nPython code:""".format(prompt)

    get_api_result(prompt_template)

我们将使用的提示是:

训练各种分类模型来预测“目标”列,使用所有其他列。使用 5 折交叉验证来选择最佳模型和相应的超参数,并返回最佳的整体模型及相应的超参数设置。根据准确性选择最佳模型。假设存在一个名为“df”的数据框用于训练。使用 MLFlow 记录整个过程。在训练任何模型之前用 mlflow 开始记录,以便只存储一个运行记录。确保模型使用 mlflow 的 sklearn 模块记录。确保只记录最佳的整体模型,但记录所有模型类型的指标。以下指标在所有交叉验证折中的均值应记录:准确性、AUC、F1 分数

让我们看看生成的输出,这次深入了解为什么需要提供某些特定的指令。

import pandas as pd
from sklearn.model_selection import GridSearchCV, cross_validate
from sklearn.metrics import accuracy_score, roc_auc_score, f1_score
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
import mlflow
import mlflow.sklearn

# load the dataframe
df = pd.read_csv('path/to/dataframe.csv')

# define the features and target columns
features = df.columns.drop('target')
target = 'target'

# set up mlflow
mlflow.set_tracking_uri('localhost:5000') # or set up your own server
mlflow.set_experiment("my_experiment")

# set up cross-validation
cv = 5

# define the models and corresponding hyperparameters to be tuned
models = [
    {
        'name': 'Logistic Regression',
        'estimator': LogisticRegression(),
        'hyperparameters': {
            'penalty': ['l1', 'l2'],
            'C': [0.1, 1, 10]
        }
    },
    {
        'name': 'Decision Tree',
        'estimator': DecisionTreeClassifier(),
        'hyperparameters': {
            'max_depth': [2, 4, 6, 8]
        }
    },
    {
        'name': 'Random Forest',
        'estimator': RandomForestClassifier(),
        'hyperparameters': {
            'n_estimators': [10, 25, 50, 100],
            'max_depth': [None, 2, 4, 6, 8]
        }
    }
]

# iterate through the models and corresponding hyperparameters to train and tune them
with mlflow.start_run():
    best_model = None
    best_metrics = {}

    for model in models:
        print(f"Training {model['name']} model...")

        # set up grid search for hyperparameter tuning
        gs = GridSearchCV(model['estimator'], model['hyperparameters'], cv=cv, verbose=2, n_jobs=-1, scoring='accuracy')
        gs.fit(df[features], df[target])

        # log metrics for all model types
        metrics = cross_validate(gs.best_estimator_, df[features], df[target], cv=cv, n_jobs=-1,
                                 scoring=["accuracy", "roc_auc", "f1"])
        for metric, values in metrics.items():
            mlflow.log_metric(f"{model['name']}_{metric}", values.mean())

        # check if this is the best model based on accuracy
        if best_model is None or gs.best_score_ > best_metrics['accuracy']:
            best_model = gs.best_estimator_
            best_metrics = {'accuracy': gs.best_score_,
                            'AUC': roc_auc_score(df[target], best_model.predict_proba(df[features])[:, 1]),
                            'F1': f1_score(df[target], best_model.predict(df[features]))
                           }

    # register the best model in mlflow
    mlflow.sklearn.log_model(sk_model=best_model, artifact_path="best_model")

    # log the best model's metrics
    for metric, value in best_metrics.items():
        mlflow.log_metric(f"best_model_{metric}", value)

如果我们移除 df 的加载和 # set up mlflow 部分,我们最终会得到完全期望的结果。即,循环遍历 3 种不同的模型类型,使用 5 折交叉验证进行网格搜索以识别给定模型类型的最佳超参数,同时跟踪指标。如果不指定“根据准确性选择最佳模型”,生成的代码将使用 scoring=[“accuracy”, “roc_auc", “f1”] 进行网格搜索,这将无法工作,因为如何根据多个指标选择最佳模型存在歧义。如果没有“确保模型使用 mlflow 的 sklearn 模块记录”,我们有时会得到 mlflow.log_model(),这是错误的。此外,“确保只记录最佳的整体模型”是必要的,以避免存储所有模型。总体而言,这个输出是可以接受的,但不稳定,多次运行可能会引入不同的错误。为了使一切为服务步骤做好准备,保存模型时添加模型签名是有用的。这个签名基本上是特征名称及其相应类型的集合。让 GPT-3.5 添加这个很麻烦,因此需要先添加导入的一些手动操作:

from mlflow.models.signature import infer_signature

然后修改通过以下代码行记录模型:

mlflow.sklearn.log_model(sk_model=best_model, artifact_path="best_model", signature=infer_signature(df[features], best_model.predict(df[features])))

模型服务

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

由我 + Midjourney 展示的部署示例

由于我们使用了 MLflow 来记录最佳模型,我们有几种选项来服务该模型。最简单的选项是将模型托管在本地。首先设计用于模型服务的一般提示模板:

def serve_model(model_path, prompt):
    prompt_template = """You are a ChatGPT language model that can generate shell code for deploying models using MLFlow. Please provide a natural language input text, and I will generate the corresponding command to deploy the model. The model is located in the file {}.\nInput: {}\nShell command:""".format(model_path, prompt)

    get_api_result(prompt_template)

提示将是:

使用端口号 1111 来服务模型,并使用本地环境管理器

通过调用 serve_model("<model path here>", question) 我们得到:

mlflow models serve -m <model path here> -p 1111 --no-conda

一旦在 shell 中运行该命令,我们就可以通过发送编码为 JSON 的数据到模型来进行预测。我们将首先生成发送数据到模型的命令,然后创建要插入命令中的 JSON 有效负载。

def send_request(prompt):
    prompt_template = """You are a ChatGPT language model that can generate code for sending data to deployed MLFlow models. Please provide a natural language input text, and I will generate the corresponding command. \nInput: {}\nCommand:""".format(prompt)

    get_api_result(prompt_template)

以下请求将插入到send_request()的提示模板中:

使用“curl”命令将数据“”发送到本地主机上端口 1111 的 mlflow 模型。确保内容类型是“application/json”。

GPT-3.5 生成的输出是:

curl -X POST -H "Content-Type: application/json" -d '<data here>' http://localhost:1111/invocations

最好将 URL 放在curl命令后面,而不是命令的最后,即:

curl http://localhost:1111/invocations -X POST -H "Content-Type: application/json" -d '<data here>'

让 GPT-3.5 完成这个任务并不容易。以下两个请求都未能成功:

使用“curl”命令将数据“”发送到本地主机上端口 1111 的 mlflow 模型。将 URL 放在“curl”后面。确保内容类型是“application/json”。

使用“curl”命令,将 URL 放在任何参数之前,将数据“”发送到本地主机上端口 1111 的 mlflow 模型。确保内容类型是“application/json”。

如果我们让 GPT-3.5 修改一个现有的命令,而不是从头生成一个,可能会得到所需的输出。以下是修改命令的通用模板:

def modify_request(prompt):
    prompt_template = """You are a ChatGPT language model that can modify commands for sending data using "curl". Please provide a natural language instruction, corresponding command, and I will generate the modified command. \nInput: {}\nCommand:""".format(prompt)

    get_api_result(prompt_template)

我们将按如下方式调用此函数:

code = """curl -X POST -H "Content-Type: application/json" -d '<data here>' http://localhost:1111/invocations"""
prompt = """Please modify the following by placing the url before the "-X POST" argument:\n{}""".format(code)
modify_request(prompt)

最终给我们的结果是:

curl http://localhost:1111/invocations -X POST -H "Content-Type: application/json" -d '<data here>'

现在是创建有效负载的时候:

def create_payload(prompt):
    prompt_template = """You are a ChatGPT language model that can generate code for sending data to deployed MLFlow models. Please provide a natural language input text, and I will generate the corresponding command. \nInput: {}\nPython code:""".format(prompt)

    get_api_result(prompt_template)

为了获得所需的输出格式,这部分提示需要进行相当多的调整:

将“df”数据框转换为可以被部署的 MLFlow 模型接收的 json 格式。将结果 json 包裹在一个名为“dataframe_split”的对象中。生成的字符串不应有换行符,也不应转义引号。此外,“dataframe_split”应该用双引号而不是单引号括起来。不要包含“target”列。使用分割的“orient”参数。

如果没有明确指示避免换行符和转义引号,则调用json.dumps()生成的格式不是 MLflow 端点期望的格式。生成的命令是:

json_data = df.drop("target", axis=1).to_json(orient="split", double_precision=15)
wrapped_data = f'{{"dataframe_split":{json_data}}}'

在用wrapped_data替换curl请求中的<data here>之前,我们可能只想发送几行数据进行预测,否则生成的有效负载可能过大。因此,我们将上述内容修改为:

json_data = df[:5].drop("target", axis=1).to_json(orient="split", double_precision=15)
wrapped_data = f'{{"dataframe_split":{json_data}}}'

调用模型得到的结果是:

{"predictions": [0, 0, 0, 1, 0]}

而实际的目标是[0, 0, 1, 1, 0]。

就这样了。在这篇文章的开头,我们甚至没有访问数据集,但我们最终成功部署了一个通过交叉验证选定的最佳模型。重要的是,GPT-3.5 完成了所有繁重的工作,只需提供最少的辅助。然而,我确实需要指定使用的特定库和调用的方法,但这主要是为了消除模糊性。如果我指定“记录整个过程”而不是“使用 MLFlow 记录整个过程”,GPT-3.5 将有太多库可供选择,生成的模型格式可能不适合使用 MLFlow 提供服务。因此,成功使用 GPT-3.5 需要对 ML 流程中执行各种步骤的工具有一些了解,但相较于从头编写代码所需的知识,这种了解是最小的。

另一个服务模型的选项是将其作为 SageMaker 端点托管在 AWS 上。尽管这在 MLflow 的 网站 上看起来很简单,但我向你保证,就像很多涉及 AWS 的网页示例一样,事情会出错。首先,必须安装 Docker,以便使用以下命令生成 Docker 镜像:

mlflow sagemaker build-and-push-container

其次,用于与 AWS 通信的 Python 库 boto3 也需要安装。此外,必须正确设置权限,以便 SageMaker、ECR 和 S3 服务能够代表你的账户进行通信。以下是我最终不得不用的命令:

mlflow deployments run-local -t sagemaker -m <model path> --name income_classifier
mlflow deployments create -t sagemaker --name income_classifier -m model/ --config image_url=<docker image url> --config bucket=mlflow-serving --config region_name=us-east-1

以及在幕后进行一些手动调整,以确保 S3 存储桶位于正确的区域。

在 GPT-3.5 的帮助下,我们以(大部分)顺利的方式完成了 ML 流程,尽管最后的步骤有些棘手。注意我没有使用 GPT-3.5 生成在 AWS 上服务模型的命令。它在这个用例中效果较差,并且会生成虚构的参数名称。我只能推测,切换到 GPT-4.0 API 可能有助于解决一些上述的错误,并带来更轻松的模型开发体验。

尽管 ML 流程可以通过 LLMs 完全自动化,但目前还不安全让非专家负责这个过程。上述代码中的错误容易被识别,因为 Python 解释器会抛出错误,但还有更微妙的错误可能会造成伤害。例如,预处理代码中删除异常值可能会导致过多或不足的样本被丢弃。最糟糕的情况下,它可能会无意中丢弃整个子群体,加剧潜在的公平性问题。

此外,超参数的网格搜索可能是在选择不当的范围内进行的,这可能导致过拟合或欠拟合,具体取决于范围。对于没有多少 ML 经验的人来说,这将很难识别,因为代码看起来没有问题,但需要理解这些模型中正则化的工作原理。因此,目前还不适合让未专门从事 ML 的软件工程师代替 ML 工程师,但这一时刻正在快速到来。

[1] Dua, D. 和 Graff, C. (2019)。UCI 机器学习库 [http://archive.ics.uci.edu/ml]。加利福尼亚州欧文市:加州大学信息与计算机科学学院。(CC BY 4.0)

关于 12 年数据之旅的年终报告

原文:towardsdatascience.com/ending-the-year-with-12-lessons-about-data-career-8786afc068f4

关于数据职业旅程的三个故事

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

·发表于Towards Data Science ·15 分钟阅读·2023 年 12 月 9 日

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

“数字 12 被认为是宇宙数字——标志着 12 个月,12 星座,以及 12 个阴历和太阳的位置” [照片由Michael Skok拍摄,来源于Unsplash]

介绍:超越数字

在我之前的职位上,我为我的商业同事和首席执行官制作了年终报告。

你可能会说没什么特别的。标准报告,涵盖了不同业务领域的一堆数字——从一般商业控制、市场营销到供应链管理和财务。

说实话,我知道这些报告只是我的工作的一部分,不会引起公司每位同事的太多关注。

所以,我考虑了如何让报告更具“吸引力”。答案很简单——让它们听起来更酷。

换句话说,我决定为报告名称增添一些趣味,因为给数字增添趣味是不可能的。

我与其他标准年终报告的不同之处在于,我的报告是以今年的流行词汇命名的。

也就是说,2020 年的报告命名为**“臭名昭著的 2020”。2021 年的报告命名为“疫苗 2021”,而 2022 年的报告则是“ChatGPT 2022”**。

你已经清楚为什么我会选择这些名称。

从充满动荡的 2020 年,当世界受到冠状病毒影响,到 2021 年的疫苗研发,再到 2022 年数据领域的一项重大进展——生成式 AI 聊天机器人的推出。

为了摆脱年终业务报告,我(最终)决定制作一份关于我职业进展的个人年终报告。

为什么,你问?

第一个原因是 2023 年标志着我职业生涯的 12 年,这一年可以说是—这样说吧—具有转折意义的。因为我再次一次性地改变了国家、工作和生活环境。

第二个原因是 2023 年是我职业生涯中技术发展最大的一年之一,进入数据领域的知识壁垒也有所增加。

随着所有新的发展和对生成性人工智能技能的突然需求,我认为这是开始职业生涯的最具挑战性的时刻之一。

为了证实这一点,我只需补充一句,即使是机器学习的先驱,安德鲁·吴最近也写了一份关于如何在人工智能领域建立职业的备忘录[1]。

这份备忘录的启发性不容小觑

它不仅鼓励人们进入人工智能领域,还提供了所需技能的建议,并且告诉你在寻找工作或转行时如何克服内心的“人类”挣扎。

尽管我无法完全理解现在开始数据职业的挑战,但我能够“发现自己”在安德鲁描述的备忘录中的大部分挑战。

换句话说,我知道如果你没有专门学习这一领域,开始建立数据职业有多么棘手。对不知道从哪里开始、首先学习什么或如何发展的感觉我很熟悉。

这正是我最终决定写这篇博客文章,总结过去 12 年职业生涯中收集的“见解”的第二个主要原因。

通过这篇文章,我的目标是帮助“外面”的某个人解决他们的数据职业困境,并让新加入者感到有鼓舞,进入这个领域。我希望通过分享我的故事和在构建数据职业过程中学到的一些教训来使这个过程更加人性化。

现在,同样老掉牙的问题又出现了:如何给报告起个酷炫的名字?

再次考虑到 2023 年标志着我职业生涯的 12 年,我认为最合适的报告名字就是***“宇宙 2023”***。

因此,让我与大家分享塑造我成为数据专业人士的旅程,并决定在这个最令人惊叹的领域——数据领域,建立并坚持自己的职业生涯。

《宇宙 2023:输入输出流的三则故事》

正如这一部分的标题所述,我将与大家分享标志着我专业数据旅程主要阶段的三个故事。

故事一,名为**“历经艰难,达致星辰”**,讲述了我如何开始我的数据之旅,以及你可能不应该怎么做。 😉

故事二,名为**“未知领域”**,讲述了我如何在一个新国家找到数据行业的工作(们)。

故事三,名为**“永远成长”**,讲述了如何达到职业生涯的下一个阶段,并在数据领域采纳成长心态。

每个故事都有一个输入输出流:

  • 故事输入部分代表故事背景。

  • 故事输出部分代表故事结果。

在本节结尾,我将分享所学的经验,以总结从中获得的最重要的见解。

故事#1: 经历艰难,方得繁星 🌟

讲述如何(而不是;)在没有计算机科学背景的情况下开始数据之旅的故事。

➡️ 故事输入

学习机械工程(ME)时,我对大多数 ME 核心课程总是缺乏热情。我的意思是那些课程的“特别章节”,如结构元素流体力学热力学等。

我对课程材料并不是 100%投入,每次考试中最好的部分对我来说是解决数学方程式和推导计算。这是“绿灯#1”,我应该将我的职业方向定位在分析领域。

***“绿灯#2 和#3”*发生在我的硕士学习期间。我记得那是一个炎热的夏日,我和同学们坐在小实验室里,参加了一门名为信息系统的课程。在那里,我学会了如何进行关系数据库架构设计,并从中创建CRUD应用程序和 SQL 报告。

后来,我参加了另一门名为信息管理的课程,在这门课程中,我能够学习仅使用 SQL 进行高级分析。我立刻爱上了它,我知道那时这正是我想要的领域。

你可能会认为这足以自动开始从事数据领域的工作。然而,这离我的道路还很远。

2011 年完成学业后,我在一家制造公司获得了实习机会,负责项目管理方面的工作。虽然我的日常任务中有一些报告工作,但这并不是“我想要的”。我想从事数据库工作。

几乎1 年后,我在不稳定的工作市场中找到了一个“出口”,并获得了母校的研究与教学助理职位。带着高度的浪漫主义接受这个角色,我终于希望我的分析之旅能够开始。

简单来说,我错了。😃

我以前没有考虑到的一件事是,当你在学术界工作时,没有数据集在等着你。在很多情况下,甚至没有项目或公司伙伴在等着你。你需要自己找到这两者。

要达到这一点,即找到愿意提供数据集的公司伙伴,我花了另外 3 年的时间。我不会详细说明过程中遇到的问题和障碍,因为这是另一个博客帖子的主题。😃 但我可以告诉你,这个过程是一个依靠良好人脉和他人对我的信任的迭代过程。

经过这个较长的过程,我只记得那种为拥有“我的宝贵”数据而感到自豪和极大的工作动力的感觉。

⬅️ 故事输出

我终于拿到了我的数据集。

而且,当我以为最困难的部分已经过去时,困难才刚刚开始。

问题不断涌现:现在该如何处理这些数据?如何开发新事物以进行科学贡献?

我完全不知道从哪里开始,也不知道如何完成我的论文的分析部分。

然后在阅读科学出版物的过程中,我偶然发现了一个叫做“数据挖掘”的领域。

然而,这里再次出现了我一直等待的东西。可以用来从获得的数据集中开发模型的分析技术。

如果一切都像那样简单就好了。

当我深入ETL 处理领域,以准备建模阶段的数据时,新挑战开始出现。我有杂乱、缺失且不平衡的数据集,没有适当的连接需要被合并。这是我与我的业务同事(注:他们是维护控制中心的航空工程师,工作时间为 24/7)一起经常熬夜到凌晨 2-3 点以了解核心数据源的时候。

一旦赢得了这场战斗并准备好了输入数据集,接下来的挑战是学习机器学习建模。从特征选择降维到选择适当的机器学习算法和理解其背后的数学。此外,我还在收集统计分析的知识,以及如何评估、比较、调整和展示模型结果。

这一切又花了三年时间。

最终,把这一切整合在一起,我花了6 年时间才说我懂得如何从数据中创造价值。

换句话说,这次我终于获得了一个与“数据相关”的学位。 😃

故事 #2:未知之地 🗺️

关于如何在(外国)行业找到工作的一则故事。

➡️ 故事输入

在我的“大人生胜利”之后,即获得了新的学位,我开始着手我的下一个职业步骤——寻找一个行业职位。

我创建了一个全新的简历,因为旧的已经不再符合市场趋势。我在全国所有的招聘网站上创建了个人档案。最后,我开始申请少数几个列出的数据相关职位,并发送开放申请

我提到“几个可用职位”,因为 2017 年在一个拥有 400 万人口的国家,这些职位的需求并不高。

如果我记得没错,在 2017 年第四季度,我申请了 4–5 个数据职位。在这 4–5 个职位和若干个开放申请中,我成功获得了 2 次面试。

在通过初步评估测试(技术、智力和/或组织能力)后,下一阶段是与招聘负责人面试。

再次,我不会详细描述,但我只想说我多希望能忘记面试中的一些问题。其中一个直接的问题是:“你到底知道些什么?”在我详细讲述了我的论文工作后问的。

你可以想象我对这些及类似问题的困惑。我觉得这不对劲,事情应该有所不同。我应该得到不同的待遇。

我主动采取行动,制定了一个计划,寻找一个能让我获得平等机会、与之前不同的地方,以及能提供更多生活机会的工作。

说到重点:过了一段时间,我变得不同了。我在一个不同的行业、一个不同的国家找到了工作,带来了各种不同的挑战。😃

到达这个点的过程并不顺利,以下统计数据将帮助你更好地了解这个过程:

  • 时间跨度: 我花了 14 周时间;

  • 申请数量: 60 份工作申请;

  • 面试次数: 3 次面试,涉及若干轮(2–3 轮);

  • 报价数量: 1 份工作报价。

从上述数字可以看出,我很着急。我花了大部分空闲时间在某个招聘网站上查看新职位,并撰写求职信。

然而,投入在这上的每一分钟都是值得的。我找到了工作。

⬅️ 故事输出

我终于得到了——一份IT 顾问的工作。有人认为我具备这个角色所需的条件,尽管我缺乏语言技能、不知道如何使用特定工具,还有一些官僚障碍(工作许可)。😃

接受这个报价对我来说是一个不言而喻的决定,我知道这是一个好的选择。第一个原因是,作为顾问,我再次参与了项目工作。这类似于我之前角色中的工作。第二个原因是知识。这份工作让我有了自由,去收集和建立新的数据领域的知识。

这段时间我开始了解数据仓库架构设计概念和云平台。这两个领域对我来说都是全新的,非常有趣。

然后有一天,我接到了一个陌生号码的电话。是招聘人员打来的,他解释了新团队正在启动一个新数据项目,并且在他的堆栈里有我几乎一年前的简历。

起初,我不明白这个电话的内容,但我同意进行了后续通话。我仍然记得第二次通话中的一条信息:“你将处理数十亿条记录。”我的眼睛闪闪发光。我从未处理过大数据,这个机会听起来很棒。因此,我决定接受,并开始了我的新角色——作为数据工程师,参与大数据迁移项目。

这份工作很充实——密集学习编码和共同开发近实时数据摄取管道,以及共同开发以客户为导向的分析模型和洞察。再次,我通过处理以前从未处理过的用例来收集新的数据工程和数据科学知识。

这个过程持续不断……直到有一天,公司破产了。

嗯,这完全不在我的计划之中。😃 我突然失业了,并开始在疫情前的夏季(即“死季”)发送我的简历。

幸运的是,这时我已经有了一个小型网络,其中一个前同事向他的公司推荐了我。他的公司正在将分析数据集迁移到云平台,并且没有人全职处理这个问题。星星对齐了——我失业了,他们在寻找人选。双赢的局面。因此,我再次开始了我的新角色——作为数据分析师

这个职位让我朝着不同的方向发展。这些方向是我从未预料到的。

原因有很多:从业务分析师到数据通才(分析师、工程师和科学家于一身),并在 IT 和业务同事之间的第一线工作,从通过数据学习几乎所有业务方面,以及从一个“独行侠”到建立数据团队

总结一下,我花了另外 3 年,总共 9 年,才晋升到下一个职业层级。

故事 #3: 永远成长 🧠

一个关于新角色和致力于采用成长心态的故事。

➡️ 故事输入

下一个职业层级——我获得了建立数据团队并成为数据负责人的邀请。我已经有 6 年的导师和学生辅导经验,所以这应该很容易,对吧?

但事实并非如此。

但让我们稍微倒回一点,看看我是如何到达这一点的。我至今仍认为最有影响力的因素是“在正确的时间出现在正确的地方”。

意味着:我是第一个全职从事迁移项目的人;我在不同的数据领域有经验;数据方面的业务需求正在爆炸;我带来了关于如何制定长期数据路线图的想法。再次,星星对齐了,显然,成为“那个人”来组建和领导数据团队是合乎逻辑的选择。

我得到了一个信念的飞跃。因此,我抓住它,开始了新角色。

一开始我在这个职位上感到非常迷茫,这种状态可以说是“毫无头绪”,就像是重新开始职业生涯,不知道该从哪里做起。

起初,我无法放弃技术工作,依然紧抓不放。我的意思是,我从零开始做了所有开发,现在有人想接手?虽然我已经无法再单独管理开发需求,但很难放下实际操作的工作。

然后,我意识到我也需要做“以人为本”的任务。激励同事,进行一对一谈话,提供指导,制定团队愿景和目标,领导招聘,创造一个愉快的工作环境。

作为额外的任务,还有其他的“管理”任务——进行团队预算、控制、收集和组织按数据角色的工作,以及展示团队。

这些对我来说都是挑战性的任务,我需要帮助。

幸运的是,我具备了这一点。我不仅得到了来自上司的支持,还得到了同事的支持。此外,我甚至得到了前上司及其同事的支持。

然而,这对我来说还不够。当我开始阅读心理学和组织学的书籍,关注分享领导故事的人,参加研讨会和接受辅导时,真正的变化发生了,角色变得更加轻松。

之后,我意识到每个人都可以在适当的指导下,在未知的领域更快、更成功地成长。这些是我最初获得后来发现的所谓“成长型思维”的第一步。

⬅️ 故事输出

现在,我并不是说我在每一种生活情况中都拥有成长型思维。毕竟,我只是一个人,我的固定思维模式仍然存在。然而,我会说我致力于获得这种思维模式。

像生活中的其他一切一样,这需要持续的工作和纪律。需要在必要时能够反思并退一步。需要不在意有时候看起来无知。还需要通过学习来专注于自身的成长。

最终,你会意识到,这不是关于追逐职位和等级的阶梯,而是关于知识

经过了 12 年的历程,并且目前担任第二个数据主管职位,对我来说重要的是进一步获得数据领域内外的知识。特别是当涉及到生成式 AI 的发展时,因为这将影响数据行业的整个工作方式。

除此之外,我相信我终于能够通过分享我的经历来赋能和支持走类似道路的他人。所有这些都旨在激发对数据领域的兴趣,吸引新的人才。

学到的经验 🧐

我会尽量简短地列出在前面描述的 12 年漫长道路上获得的最重要的经验教训。更多的技术细节将在另一个博客中介绍。😉

数据职业必备要素

  • 建立基础知识。 理解数据领域中基础知识的重要性。换句话说,首先通过学习数学和统计学、算法,或理解数据结构、架构和编码原则来获得逻辑。这当然很多,但了解一般概念会让你以后的实践工作更轻松。

  • 交付高质量的工作(尽可能)。 在工作中优先考虑质量而非数量,并采用系统化的工作方式以提高清晰度和表现。

  • 避免借口(尽可能)。 为自己找借口和理由很容易,但承担责任并接受自己的错误会让你从人群中脱颖而出。

  • 形成你的意见(尽可能)。 通过评估和质疑现有的发现和结论来发展批判性思维能力。

  • 永远保持学习。 通过使用在线学习平台来认识自学和持续教育的价值。学习商业、心理学和其他科学,以补充你的技术知识。

  • 做个人反思。 记录你的失败,以便以后衡量你的进步。

数据职业个人见解

  • 寻找直到得到“是”为止。 训练自己在职业挑战中坚持不懈,不要把“”当作个人攻击。

  • 学习到不,谢谢是一个完整的句子。 专注于对你重要的目标,同时在过程中尊重他人。

  • 别人的天花板是你的起点。 我得到的最好建议之一就是不要因为别人的观点而限制自己的雄心。

  • 应得的赞誉。 了解给予赞誉的重要性以及分享知识的价值。

  • 依靠 2F。 在困难时期寻求朋友和家人的支持。

  • 这是你和你自己对抗。 你的职业生涯不是一场比赛,没有人有相同的起点。每个人都在自己的道路上挣扎,与他人比较没有意义。真正的对手是你自己。

结论:“好奇心没有治愈的方法”

通过在这个博客中分享我的故事,我想使构建数据职业道路上的挣扎变得正常化。我旨在给你提供每个障碍如何被解决的例子,只要你坚持寻找解决方案并朝着目标努力。

有了这些,我想激励那些没有学习这个领域的人和那些考虑转行的人加入数据领域

这可能不会是一条顺畅的道路,但要相信,几年后每一秒都会值得——也许是 3 年、6 年、9 年,或者是神秘的 12 年。 😉

最后,我将以多萝西·帕克的一句名言结束这个博客:

对无聊的解药是好奇心;对好奇心没有解药。

所以,要保持好奇心,加入最令人惊叹的领域 —— 数据领域 😃

感谢阅读我的帖子。请保持关注,获取更多关于 Medium LinkedIn的故事。

感谢支持的支柱

由于这是年终的“报告”,我需要向所有分享并仍在分享我旅程的人表示感谢。

  • 家庭和朋友。 在转向并坚持数据职业的过程中,我得到了来自家人和朋友的巨大支持。你们在困难时期与我一起哭泣和欢笑。感谢你们的陪伴。

  • 导师和同事。 向我曾经的上司及其同事们致谢,我视他们为导师,并且今天我仍然可以向他们请教建议。同样也感谢我的同事们。感谢你们让我学到了很多东西。

  • 社区。 在 Medium 和 LinkedIn 上,我遇到了启发过我、为我喝彩并分享我故事的人们 (TDS)。这有时会为我的日子带来惊人的光彩。感谢你们分享你们的善意。

知识参考

[1] DeepLearning.AI 资源,《如何在 AI 领域建立你的职业》由 Andrew Ng 撰写,访问日期 2023 年 10 月 13 日,info.deeplearning.ai/how-to-build-a-career-in-ai-book

能源供应与需求优化:使用 Gurobi Python 进行数学建模

原文:towardsdatascience.com/energy-supply-and-demand-optimisation-mathematical-modelling-using-gurobi-python-8a8b1cb9559a?source=collection_archive---------6-----------------------#2023-09-29

通过数学优化实现高效的能源分配

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

·

关注 发表在 Towards Data Science ·11 分钟阅读·2023 年 9 月 29 日

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

日落时分的电力塔照片。来源:Matthew Henry来自Unsplash

我们目前正处于一个能源供应和需求平衡变得尤为重要的时代。传统能源如石油和天然气正面临着诸多困难,例如俄罗斯-乌克兰冲突等地缘政治因素,这些因素扰乱了天然气供应,加上自然资源的总体枯竭。

另一方面,随着越来越多的国家从发展中国家转变为发达国家,能源需求也在不断增长。这种经济增长意味着对能源的需求增加,以支持生产和工业过程。应对气候变化的紧迫性进一步强调了对可再生能源的需求。综合所有因素,这意味着实现良好的能源供应和需求平衡变得愈加重要。

数学优化是研究在预定义约束条件下选择标准以最小化最大化特定目标的过程,所有这些都通过数学模型来管理。它在解决各种现实世界问题中非常有用,每个问题都有其独特的复杂性。

例如,在物流和运输中,数学优化可以用来解决如车辆路径问题等挑战,该问题优化车队的路线,从而通过使用高效的路线来降低成本和环境影响。在计算生物学中,这种方法对于理解复杂的生物过程、改善药物发现、基因组学和疾病途径研究具有重要作用,通过开发精确的模型来实现。此外,数学优化在供应链中扮演着关键角色,供应链是现代商业和工业中的重要元素。它优化资源分配,确保生产和分配系统的高效性,减少浪费和成本,同时提升客户满意度。总之,数学优化使各个领域的决策者能够应对复杂的问题,找到问题的最佳解决方案

在本文中,我们展示了如何使用数学优化来解决基本的能源供应和需求问题。我们将首先解释问题陈述并概述数学模型。接着,我们将通过一个实际的例子,并使用 Python 中的 Gurobi 求解器来运行模型。最后,我们将分析从这次模拟中获得的结果。

1. 问题陈述

让我们设想一个假设情况,我们负责制定一个虚构国家的年度能源生产和储存预算。在这个国家中,只有四种能源来源,即太阳能、风能、天然气和核能。

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

我们问题中不同能源来源的艺术插图:太阳能、风能、天然气和核能。来源:图标由 small.smileswaniconFreepik 提供,来自 Flaticon

我们可以访问详细的数据,这些数据包含了每月的生产和储存能力,以及相关的生产和储存成本的见解。此外,我们假设了解全年能源需求。关键问题是:我们如何优化每种能源的每月生产和储存量,以最小化年度总成本?

2. 数学公式

现在,让我们深入探讨我们模型的数学公式。如果你不喜欢方程式,可以跳过这一部分。然而,对于那些觉得数学有趣的人,所获得的见解将是值得的。我们将以精确的数学术语概述参数、目标函数和约束。

2.1. 参数

让我们定义我们模型中使用的数学符号。我们用 eE 表示所有能源来源集合 E(太阳能、风能、天然气和核能)中的能源类型,tT 表示所有月份集合 T(一月、二月、三月等等)中的月份。

现在,我们介绍为我们的问题提供的以下参数:

  • Dₜ 是第 t 个月的总预期能源需求。

  • Pₜₑ 是第 t 个月能源来源 e 的生产上限。

  • Sₜₑ 是 能源来源 e 在第 t 个月的储存上限。

  • Aₑ 是能源来源 e 的生产成本。

  • Bₑ 是能源来源 e 的储存成本。

我们模型的变量定义如下:

  • xₜₑ 是第 t 个月生产的能源来源 e 的数量。

  • yₜₑ 是第 t 个月剩余的能源来源 e 的数量,这也是该月将被储存的数量。

  • zₜₑ 是在第 t 个月使用的能源来源 e 的数量,用于满足该月的能源需求。

2.2. 目标函数

现在,让我们定义我们的目标函数。我们的目标是最小化全年的总成本。可以用数学术语表达如下:

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

在这里,Aₑxₜₑ 代表第 t 个月能源 e 的生产成本,而 Bₑyₜₑ 表示第 t 个月能源 e 的储存成本。两个求和符号 Σ 表示对全年所有能源来源的这些生产和储存成本进行求和。目标是找到使总成本最小化的变量 xₜₑyₜₑ 的值。

2.3. 约束

约束条件是我们问题解决方法中的关键部分。它们确保我们找到的解决方案在给定参数下既现实又可行。

2.3.1. 质量平衡约束

第一个约束,也称为质量平衡约束,确保在给定月份的每个能源来源中,生产的数量和从前一个月结转的数量之和等于使用的数量和结转到下一个月的数量之和。我们假设在第一个月 t₀(1 月),尚未有能量储存。从数学上讲,这个约束可以表示为:

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

2.3.2. 需求约束

接下来是需求约束,这是我们问题的一个基本方面。它确保在任何给定的月份,前一个月生产的和结转的总能量必须足以满足该月的能源需求。从数学上讲,这个约束可以表示为:

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

在这里,Σ 表示对所有能源来源 e 的求和。该约束确保在给定的月份 t 中,总能量(所有能源来源的生产和储存之和)大于或等于该月的能源需求 Dₜ。由于在第一个月 t₀ 没有能量储存,我们只需确保第一个月生产的总能量足以满足该月的需求。

2.3.3. 变量域约束

最后一组约束涉及定义变量的域,这对于确保这些变量的值保持在有意义和可行的范围内至关重要。在我们的情况下,我们有生产限制和储存限制,分别限制 xₜₑyₜₑ 的值。此外,由于这些变量不应取负值,我们需要在其域上设定下限。从数学上讲,域约束可以表示为:

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

不需要明确地定义 zₜₑ 的上限,因为它由质量平衡约束固有地决定。

3. 代码

在这里,我们将简要描述代码实现。我们首先导入 Gurobi 库和一个自定义的Data类,负责加载数据。代码可以在 GitHub 上找到: [能源优化](http://kyliow/energy-optimisation: Energy optimisation exploration (github.com))。

第一步是定义模型和必要的变量。

import gurobipy
from gurobipy import GRB

from data_extraction import Data

# Model
model = gurobipy.Model("Energy optimisation")

# Variables
X = model.addVars(
    Data.months, 
    Data.energies, 
    name="Amount supplied", 
    ub=Data.production_limit,
)
Y = model.addVars(Data.months, Data.energies, name="Amount remained")
Z = model.addVars(Data.months, Data.energies, name="Amount used")

我们可以使用model.addVars()方法中的ub参数来设定变量的上限,或者通过model.addConstrs()方法指定约束条件。在这里,我们展示了如何在model.addVars()方法中设定能源生产X的上限。稍后,我们将展示如何使用model.addConstrs()方法为能源储存Y设定上限。

接下来,我们定义目标函数。

# Objective function
obj = gurobipy.quicksum(
    (Data.production_cost[energy] * X[month, energy])
    + (Data.storage_cost[energy] * Y[month, energy])
    for energy in Data.energies
    for month in Data.months
)
model.setObjective(obj, GRB.MINIMIZE)

最后,我们定义约束条件,第一个是质量平衡约束。

# Mass balance constraints
model.addConstrs(
    (
        Y[Data.months[m_ind - 1], energy] + X[month, energy]
        == Z[month, energy] + Y[month, energy]
        for energy in Data.energies
        for m_ind, month in enumerate(Data.months)
        if month != Data.months[0]
    ),
    name="Energy balance",
)
model.addConstrs(
    (
        X[Data.months[0], energy]
        == Z[Data.months[0], energy] + Y[Data.months[0], energy]
        for energy in Data.energies
    ),
    name="Energy balance",
)

在定义质量平衡约束后,我们继续建立需求约束。

# Demand constraint
model.addConstrs(
    (
        gurobipy.quicksum(
            Y[Data.months[m_ind - 1], energy] + X[month, energy]
            for energy in Data.energies
        )
        >= Data.demand[month]
        for m_ind, month in enumerate(Data.months)
        if month != Data.months[0]
    ),
    name="Demand",
)
model.addConstr(
    (
        gurobipy.quicksum(
          X[Data.months[0], energy] for energy in Data.energies
        ) >= Data.demand[Data.months[0]]
    ),
    name="Demand",
)

在最后一步,我们展示了如何使用model.addConstrs()方法为储存Y设定上限。

# Upper limit for Y
model.addConstrs(
    (
        Y[month, energy] <= Data.storage_limit[energy]
        for month in Data.months
        for energy in Data.energies
    ),
    name="Storage",
)

4. 模拟分析

为了评估我们的模型,我们需要一些模拟数据。在这里,我们使用 2022 年英国的月度能源需求数据作为我们的模拟能源需求。同时,我们使用这些能源来源在 2022 年英国的能源生产数据,乘以 1.5 作为月度生产能力的近似值。能源需求和生产能力如下面的图所示。

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

每种能源来源的月生产限额和每月能源需求的条形图。来源:作者提供的图像。

对于储存容量,我们使用相当于欧盟天然气储存容量 10%的代理值,即每月约 9500 GWh,来代表天然气储存。我们为其他能源来源分配了每月约 2000 GWh 的储存容量,假设它们储存在大型能源储存设施中。

最后,我们将生产成本数据与 2022 年这些能源来源的实际成本进行近似。对于储存,我们采用天然气的实际储存成本,大约为每 GWh £16,000。然而,对于其他能源来源,我们假设它们使用锂电池储存,这通常比天然气的实际储存成本高得多。为了显示这种显著的成本差异,我们将其他能源来源的储存成本设定为每 GWh £80,000,000。

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

生产成本的饼图(单位:GBP 每 GWh)。来源:作者提供的图像。

下图展示了全年能源生产和储存成本,其中虚线和点线分别表示这两项成本。鉴于天然气的储存成本与其他能源相比极为便宜,甚至可能低于某些能源的生产成本,因此最优策略是全年持续储存天然气。因此,储存成本仅归因于天然气储存。唯一的例外是十二月,此时不进行储存。这是因为我们的分析设计为在十二月结束,因此没有激励去储存分析期之外的能源。

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

能源生产的堆叠柱状图,展示了每月的生产量和成本图。来源:作者提供的图片。

此外,我们的分析揭示了天然气生产由于其较低的生产成本而成为最具成本效益的选项。在风能和太阳能方面,我们观察到生产量在一年中有所波动,这可能受到生产能力的季节性变化的影响(例如,最大日照和峰值太阳能发电通常发生在年中)。这一波动从生产限制的柱状图中可以明显看出。

当我们将每月的生产和存储成本相结合时,结果是优化后的年成本为 170.1 亿英镑。

值得注意的是,我们使用了与 2022 年英国实际需求相同的能源需求数据。这表明我们优化的能源供应和存储分配优于实际情况。当考虑到像英国这样的国家通常会将多余的能源出口到其他国家以换取收入时,这一结论变得更加明显。虽然我们模型中没有考虑到多余能源的出口,但在现实世界中它代表了额外的潜在利益。

在实验的精神下,我们将除天然气之外的所有能源来源的存储成本降低了 1000 倍,以模拟大规模电池存储变得显著便宜的假设情况。结果如下面的图所示。

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

能源生产的堆叠柱状图,展示了每月的生产量和在修订存储成本下的成本图。来源:作者提供的图片。

在这种新情境下,我们可以看到能源存储增加,而生产成本通常下降,因为储存能源变得比生产能源更经济。有趣的是,我们还注意到实际需要生产核能,尽管仅在 1 月份。

在这种新情境下,模型计算的总年成本为 118.7 亿英镑,约为实际年成本的一半。这表明存储成本对整体年成本有显著影响。

5. 总结

我们展示了数学优化在解决简单能源供应和需求问题中的应用,证明了它在确定最优能源生产和存储量以最小化总年成本方面的有效性。

然而,重要的是要承认数学优化相关的一些潜在限制和挑战。其中一个挑战是在数学模型的制定过程中需要简化问题,这可能导致对现实世界问题的表述不完整。试图纳入更多复杂因素可能会导致更复杂的模型,使其更难理解和解决。

此外,解决大规模数学优化问题通常需要大量计算资源。为了解决这个问题,可以采用各种策略。启发式方法提供了一种实用的方式,可以迅速获得接近最优解的近似解。此外,元启发式方法,如遗传算法(我在这里详细解释),提供了更高层次的技术,用于迭代搜索足够接近最优解的解决方案。这些方法是解决复杂优化问题的宝贵工具,可以在精确优化具有挑战性或计算量大的情况下,提供实际解决方案。

进一步阅读

K. Y. Liow, 使用遗传算法在 Python 中优化物体避免 (2023), Towards AI @ Medium

参考文献和数据来源

[1] 每月电力统计数据 (2023), 国际能源署

[2] T. Stehly 和 P. Duffy, 2021 年风能成本回顾 (2022), 国家可再生能源实验室

[3] M. Roser, 为什么可再生能源这么快就变便宜了? (2020), 我们的数据世界

[4] 欧盟在冬季来临前达到 90%天然气储存目标 (2023), 欧洲委员会

[5] GlobalData, 英国前五大能源储存项目 (2023), Power Technology

[6] J. Witwer, 甲烷储存对实现可再生能源目标和增强能源韧性的作用 (2022), Onboard Dynamics

上下文推荐引擎的特征工程

原文:towardsdatascience.com/engineering-features-for-contextual-recommendation-engines-bb80bf0e0453?source=collection_archive---------5-----------------------#2023-04-27

对于上下文信息主导的不同案例的分析

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

·

关注 发布于 Towards Data Science · 11 分钟阅读 · 2023 年 4 月 27 日

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

图片来源于 VectorStock,授权给安德鲁·查拉宾

从娱乐应用到约会平台,从社交网络到零售,推荐引擎在当今社会中发挥了至关重要的作用。它们不仅在有效性上取得了显著进展,而且在引导我们的注意力、根据我们的兴趣进行个性化和推荐个人价值的项目等方面扮演着越来越重要的角色。虽然每个推荐引擎都是独特的,需要考虑问题、业务和可用数据的复杂性,但许多构建块是相同的——用户/项目嵌入、用户历史、上下文特征以及神经协作层来将用户和项目映射到评分。模型可以绕过假设性和容易出错的手动数据,转而使用大量的隐含和明确反馈数据来预测评分。

推荐引擎是机器学习或基于规则的模型,提供如最适合用户的内容/项目、目标客户、或公平价格等推荐。它们遵循以下通用结构:

结果 = F(U, I, C)

其中 U 是用户,I 是项目,C 是上下文,F 是一个将 U、I 和 C 的组合映射到结果的函数。结果可以是明确的反馈,如评分,隐含的反馈,如观看时间,或非反馈量,如价格。

当推荐引擎与数据管道打包在一起以获取所需的输入数据、具备从模型中获取批量推断和随时间进行更新的能力,以及一个用户界面以接收和互动推荐时,它就成为了一个推荐系统。

推荐引擎的一个简单示例是一个为流媒体平台上的用户(U)在周末(C)推荐电影(I)的模型。

协作推荐引擎可以用来将用户和项目映射到一个共同的嵌入空间,然后可以推荐空间中最接近的项目给用户。为了将用户和项目映射到这个新的嵌入空间,可以应用矩阵分解方法或多层感知机。只要有足够的过去的用户和项目反馈数据,自动映射和寻找用户与项目之间相似性的方式往往比使用手动特征(如用户和项目元数据)的推荐更为有效。

考虑以下基于 2 个手动特征的嵌入空间:一部电影在戏剧和奇幻类型中的评分(满分 5 分),并考虑 3 部电影。

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

作者图表 1

与使用 2 个手动制作的特征不同,协作系统被训练来自动将用户和项目映射到例如 k=10 个不同的“潜在维度”,这些维度可以通过过去的评分找到,而无需额外的项目/用户元数据。

你还会注意到,戏剧和奇幻在上面确定项目在空间中的位置时都扮演着~ 相等的角色。使用多层感知机与矩阵分解相比,嵌入的衍生和使用方式更具灵活性,以找到与用户最相似的项目。例如,可以学习嵌入,使得不同的维度在确定用户在空间中的位置时具有不同的权重。即,如果戏剧在电影的位置中比奇幻重要两倍,你可以想象 Y 轴被压缩了一半,记忆碎片与《星球大战》的相对距离略有减少。

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

作者提供的图表 2

神经协同过滤(NCF)是一个广义框架,用于通过项目评分预测用户,这允许放宽类似于图表 1 中的矩阵分解的线性限制——所有维度具有相同的权重,且评分与空间中的距离成反比(称为交互函数是线性的)。

上下文信息,如电影何时观看或用户与谁一起观看,可以在初始推荐后通过结果过滤(上下文后过滤),或在初始推荐前通过根据消费上下文将项目视为独立项(上下文前过滤)来考虑。

为了更高的严谨性,可以将上下文字段作为空间中的附加维度集成。考虑下面的示例,我们在垂直轴上添加了一个手工制作的特征,表示用户与家人一起观看电影的普及程度。

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

作者提供的图表 3

然而,由于传统嵌入技术的计算复杂度随着上下文维度的增加而呈指数增长(例如,参见多重宇宙作为一个例子),可以采用诸如因子分解机等替代方法来保持可处理性。

鉴于每个现实世界的推荐系统案例都是独特的,现代推荐系统通常是各种构建模块的专门组合,如项目嵌入、矩阵分解和神经网络层,以寻找用户与项目之间的连接。例如,考虑YouTube 的视频推荐引擎,它结合了两个不同的深度神经网络,第一个选择你可能喜欢的优质候选视频,第二个挑选出你最可能观看时间最长的最佳候选视频。

在本文中,我想讨论我遇到的两个偏离传统用户按项目推荐系统的一般示例,以及使用基于树或深度学习模型解决这类问题时一些独特的特征工程方法。在这两个示例中,上下文信息在预测成功交互方面的作用相对较大,而不像传统的电影推荐系统那样。

这些场景如下:

  1. 高上下文 & 低用户重要性 > 结果 = F(I, C)

  2. 高上下文 & 无项目 > 结果 = F(U, C)

场景 1

结果 = F(I, C)

第一种情况是项目和上下文在推荐系统中起主导作用,而用户则处于次要位置。考虑到大多数用户是首次使用,因此用户历史/相似性的价值有限,许多项目是新的且从未被评分过,或者合适的项目完全依赖于上下文,与用户关系不大。

要选择最合适的自由职业者来完成工作,您需要了解他们在类似工作中的表现如何,过去用户如何评价他们,以及他们是否有按时完成工作的声誉。最近与在线文案撰稿人合作的事实可能并不是找到当前现场摄影工作合适人选的最相关因素。

在这种情况下,传统的推荐引擎如何运作呢?基于内容的推荐可能会推荐与您的文案撰稿人相似的人,而协同推荐可能会推荐在雇佣文案撰稿人后其他人也雇佣过的自由职业者。这些推荐都可能并不特别有帮助。由于不同的上下文,其他用户在雇佣该地区摄影工作时的经验可能比您过去的经验更相关。

因此,我们希望了解先前被雇用的最佳自由职业者,这些自由职业者与当前上下文匹配,而与用户无关。但是,如果工作具有多种不同的上下文维度,例如工作类型、技能、范围等,以及衡量用户反馈的不同方法,例如交付速度、评级和雇佣情况,该如何处理呢?

一种解决方案是对上下文和反馈维度进行笛卡尔积,然后汇总所有用户的项目历史记录。

上下文与过去反馈的笛卡尔积

要做到这一点,我们首先需要对维度进行独热编码,以便为每个级别分配专用维度。为了说明问题,以下是候选自由职业者的结果。请注意,在模型中使用时,我们将会把最后三列中的每一行展开为具有名称如avg_delivery_on_time_photography的专用特征列。

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

图表 4 作者提供

但是如果我们有 n 个初始上下文维度,每个维度具有 h - 1 个独热编码级别和 k 个反馈维度,那么需要 n x (h - 1) x k 个模型特征来映射所有可能的组合。在上面的不完整示例中,这导致了 51 个特征。

此外,许多特征与额外类型的工作(软件开发)相关,这些工作与当前上下文无关,但已通过笛卡尔积创建。

更简洁且有意义的编码方法是找到匹配当前上下文的所有上下文和反馈维度的过去历史,这需要 n x k 特征。这种上下文聚合可以通过类似以下的 SQL 使用案例逻辑轻松实现:

avg(case when past_job.type_of_work = job.type_of_work then delivery_on_time end) 作为 avg_delivery_type_of_work

其中工作表包含当前工作上下文的信息,而 past_job 表包含过去工作的相关信息。

改进情况如下:

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

图表 5 由作者提供

最终结果是关于过去自由职业者(项目)在类似工作(上下文)中的表现的丰富信息,同时限制了不必要的高维度。为了生成特征,上下文维度仅按线性方式考虑。诸如预算中等且经验水平为专家时的过去评分等组合被省略。非线性特性仍然可以由训练算法导出,或者可以创建额外的上下文维度作为现有维度的乘积。

然后,我们可以将每个反馈按上下文列展开为一个专用特征列,行指示一个独特的自由职业者。接着,将所有符合严格标准(即从事摄影工作)的自由职业者及其相关特征堆叠到额外的行中。现在我们可以重新构建这种表示,将每个过去的工作视为当前工作,将每个过去的工作堆叠为一组额外的行。对于过去的工作,我们可以创建一个额外的列来捕捉自由职业者收到的明确反馈,例如被雇佣,或隐性反馈如被联系。

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

图表 6 由作者提供

最后,我们可以训练一个基于树的模型如 XGBoost,使用工程特征(排除工作和才艺 #)来预测完成工作的反馈才艺,部署该模型,并将其应用于新发布的工作,以推荐最佳自由职业者。

场景 2

结果 = F(U, C)

另一个例子是,当推荐不是针对离散项,而是针对连续值时。考虑一个平台推荐物业租赁公司在周末应收取的物业租金。

在这种情况下,我们可以观察到与上下文维度匹配的过去租金价格,但现在我们不再对项反馈类型执行笛卡尔积,而是通过租赁是否为同一用户或其他用户来进行乘法运算。最后,我们可以通过计算平均值来汇总过去的价格。

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

图表 7 由作者提供

然后我们可以执行场景 1 末尾概述的额外步骤。

分层模型

另一种选择是创建一个初步的上下文模型,以便在所有用户中进行预测,然后再创建一个第二个模型,将用户无关的预测调整为用户特定的预测。这种方法的一个好处是提供了两种不同但有意义的预测,您可以推荐给用户或用于驱动期望的行为。

以一个模型为例,该模型输入职位描述和相关元数据以推荐招聘前的职位薪资。第一个模型可以预测与公司无关的薪资,从而找到该职位的“市场价格”。

可以用第一个模型的预测作为输入,并结合类似于图表 7 第三列的额外公司特征来训练第二个模型,以找到“量身定制”的薪资预测,即考虑到类似职位是否由同一家公司或另一家公司填补的薪资预测。

如果量身定制的预测与市场价格有显著不同,公司可能需要了解这一差异及其影响。例如,可以开发另一种模型,基于职位描述、招聘的候选人、起始薪资和公司来预测新员工的在职时间。可以使用市场价格与量身定制的薪资作为输入来计算预测的在职时间差异。假设市场价格为$75K,量身定制的薪资为$65K,但在$75K 的预测在职时间多出 1 年。推荐系统可以将$75K 作为推荐薪资,并提供一个工具观察不同薪资下的预测在职时间变化。

为了在防止泄漏的情况下训练分层模型,即“避免从持出集泄漏到用于训练模型的数据集中”,需要确保用于训练每一层的观察数据没有重叠。

此外,如果在第二层模型中集成了一个特征,该特征计算类似职位的历史公司薪资与第一个层模型预测的市场价格之间的差异,例如,结果为低于市场价格的平均$8K,则需要在公司与职位层级上对训练、评估和测试集进行抽样。这是因为作为职位薪资预测的特征,您还集成了所有公司之前填补的职位的市场价格预测。为了避免偏差,用于训练第一个模型的观察预测不应以任何方式作为后续模型的特征。我们最终得到一个训练/测试划分如下,不包括验证集,使用 80%的划分比例作为示例。

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

作者的图表 8

显然,只有在过去数据量相当大时,分层方法才是一种可行的替代方案。像 70%这样相对较低的分割比可能更可取。在分层方法中提供的附加用户特征对第二层模型的观察损失进行了对比,然而,在我评估过的特定回归用例中,仅通过将 80%观测数据训练的初始基于树的模型的预测整合到余下 16%观测数据训练的第二个模型中,最终 4%观测数据上的 MSE 约等于在所有 96%训练观测数据上训练的模型的 MSE。

结合来自上下文和协同模型的推荐

F(I, C) + F(U, I)

最后,从上下文模型衍生的推荐始终可以与从用户/物品相似性中获得的推荐加权,以提供最佳推荐。

为了更好地理解给定嵌入的用户/物品相似性的替代方法,我强烈推荐阅读这篇文章,该文章区分了 L-范数和角度度量。

总结

尽管我们当前的社会在推荐系统方面取得了显著进展,但较少的研究集中于整合改变用户和物品交互性质的上下文信息。神经协同过滤(NCF)提供了一个框架,用于开发神经网络,其中第一层接收用户和物品标识以推导嵌入,后续层模拟与用户和物品嵌入的交互以预测评分。目前,相关的上下文仍然需要手工制作并显式地输入到模型中。虽然 NCF 方法是推荐的方式,被 YouTube 和 Google 等公司使用,但在某些情况下,例如在上下文信息起主导作用的非传统情况下,它们可能有些过度。

我已经详细介绍了我个人经历过的几个推荐系统案例;在这些案例中,用户信息并不是非常重要,也没有物品。我展示了一些有趣的方法来在这些情况下制作手工特征,例如通过笛卡尔积和分层模型。

希望这里的想法有助于激发一些创意,扩展你的知识,并激励你进一步探索迷人的推荐引擎领域。

感谢阅读!如果你喜欢这篇文章,请关注我以获取我的新文章通知。同时,欢迎分享任何意见/建议。

工程已永远改变

原文:towardsdatascience.com/engineering-has-changed-forever-4d2fcc4f3841

最近 AI 的进展正在颠覆传统架构。未能选择正确架构的公司将被甩在后头。

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

·发布于数据科学前沿 ·阅读时间 5 分钟·2023 年 11 月 20 日

“我们对良好软件架构的几乎所有认识都与使软件易于更改有关。”

— Mary Poppendieck,软件开发专家和作者

在过去六个月里,工程的基础经历了戏剧性的转变,这种变化如此深远,以至于许多组织刚刚开始理解和适应其影响。大多数公司开始采取小的渐进步骤,将一些 AI 融入其软件中,而不是深入探索其可能性。

在本文中,让我们关注 AI 的架构影响。广义上,这种转变包括从传统的基于规则的架构转向更加动态的 AI 中心模型,这深刻改变了 AI 在软件系统中的角色,并挑战了我们对软件架构的基本理解。

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

图表:向 AI 工程转变(图像由作者提供)

你可以将架构转变描述为四个阶段:

  1. 经典软件服务架构

  2. AI 驱动的软件服务架构

  3. AI 软件服务架构

  4. AI 工程

每个阶段在实施和结果上都有显著差异。一些公司会从一个阶段跌入下一个阶段,而其他公司则会跳跃前进。无论如何,大多数公司尚未在选项之间做出明确选择。更别提这种选择可能是生存或被竞争对手超越的关键区别。

注:在本文中,当我提到 AI 时,我通常指的是生成性 AI,不过一些读者可能会将某些机器学习应用也归为此类。根据我的经验,一个例子是金融科技公司如何使用机器学习进行信用决策和支付时机。

1. 经典软件服务架构

我们在这里大幅简化,但为了本文的目的,我们将各种软件服务架构归入“经典”类别。它们具有可预测性和精确性,通常遵循基于规则的面向服务的方法。该系统中的每个组件都执行特定的、预定的功能。这种架构通常是确定性的和模块化的,目的是保持可预测性和可扩展性。

大多数企业中的软件工程都属于这一类别。每家公司都希望通过更多的 AI 功能来提升他们的软件,通常会着眼于一步 ahead 来集成一些 AI,这使得他们进入了一个…

2. AI 赋能的软件服务架构

AI 赋能的软件服务架构将经典的软件架构与精选的 AI 增强功能相结合。虽然核心依然是面向服务的结构,但某些服务会通过 AI 能力进行增强,引入机器学习、预测分析或生成式 AI 等元素。这种集成保留了系统的基础模块化和可预测性,但增加了适应性,使其能够动态响应不断变化的数据和需求。该架构在保持传统软件服务可靠性的同时,兼具 AI 带来的灵活性和新功能。

使传统服务具备 AI 的例子包括:将非结构化数据转换为结构化数据(将消费者情感提取到数据库中)、简单的聊天机器人服务,或简单的 AI/ML 决策服务(我们是否应向此人提供信用)。然而,当你尝试深入挖掘 AI 的潜力时,你很快会发现,在旧的软件构造中管理 AI 会大大限制其潜力。在 AI 之上放置一个编排服务会将其许多用例限制为输入-输出转换服务。

3. AI 软件服务架构

AI 软件服务架构在传统软件框架中嵌入至少一个完全由 AI 驱动的服务。在这里,经典平台管理整体功能,但利用一个 AI 专用服务来处理复杂的、自适应的任务。这个 AI 服务能够自主处理文本或数据事件等输入,支持系统的高级决策。尽管架构的核心仍然根植于经典的软件方法论,但这种 AI 中心服务的集成增强了功能。它将传统软件的结构化编排与 AI 的动态问题解决能力结合起来。

以这种方式使用 AI 可以解锁更广泛的 AI 应用场景。例如,一个 AI 服务可以执行输入转换、反思、决策和行动的混合过程。例如,当新文件到达时,AI 会查看文件,将其与指令集进行比较,确定这是一个非结构化文件,读取首几行内容,确定需要手动检查,并将其转发给监督部门。

4. AI 工程

在真正的本土 AI 工程中,焦点完全转向 AI 作为核心软件,与传统和混合架构显著不同。在这种范式中,工程工作重点在于赋能 AI 本身,提升其能力和自主性。AI 不仅是系统中的一个元素,它系统,负责平台类的职责。工程师致力于扩展 AI 的功能,而不是协调 AI。工程工作的例子包括:集成多模态输入、拓宽决策范围以及配备多样化工具。这种方法是培养 AI 无缝融入各种工作流程和实际应用,确保它演变成一个更复杂、自主的实体。与以往 AI 补充或增强传统结构的模型不同,这里 AI 是基础,凭借其无限潜力和适应性重新定义了软件工程的概念。

在这一架构类别中可以想象的例子包括自适应学习循环、端到端 AI 流程自动化、多模态决策场景反馈,以及其他无尽的例子,唯一的限制就是你的想象力。

架构阶段 3 和 4 有相似之处,但它们也有显著差异。让我们进一步澄清这些差异。在 AI 工程中……

  • … 工程师的关注点转向使 AI 得以实现

  • … AI 是大多数活动的协调者,任何必要的协调都是非常轻量的,主要目的是使用户能够与 AI 互动

  • … 软件服务是 AI 的工具/促进者,而不是 AI 作为软件服务的工具

一个真正的 AI 工程服务示例:“获取金融市场变化,反思我的投资组合,决定是否需要进行额外投资,然后自主执行推荐(例如,卖出股票 ABC)。”

反思

目前许多公司仍在摸索 AI 对他们的意义,并实验如何提取价值。当公司开始构建真正的解决方案时,至关重要的是要反思哪种工程范式最符合他们的目标。逐步启用 AI 解决方案是大多数公司将要开始的地方,但这样做会错失 AI 真正的能力。

很快,一代新兴公司将会出现。这些公司和那些深度融入人工智能本土理念的老公司,它们的发展速度将远远超越任何传统企业。

所以,请明智选择。在这个充斥着人工智能的工程领域,你的下一步行动可能决定成功或被淘汰的命运。

使用 OpenAI 的审查 API 提升 ChatGPT 的内容审查

原文:towardsdatascience.com/enhance-content-moderation-with-openais-moderation-api-bb0b865d883b

将 ChatGPT 的内容审查端点无缝集成到你的管道中

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

·发表在 Towards Data Science ·阅读时间 6 分钟·2023 年 7 月 7 日

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

图片来源:Joshua KettleUnsplash

免责声明:本文集中于检查内容是否符合审查指南。因此,可能会涉及暴力、自残、仇恨和性暴力的内容。

随着提示工程的兴起和大型语言模型在回应我们询问方面取得的显著成就,像 ChatGPT 这样的聊天机器人正成为我们日常生活和所创建应用程序的重要组成部分。无论你是出于个人目的使用 AI 模型,还是利用其能力开发高级系统,确保你使用的 AI 模型生成的内容遵循特定的审查指南和规则是非常重要的。⚠️

在这篇文章中,我们将重点介绍 OpenAI 的审查端点,这是一项出色的功能,用于检查内容是否符合 OpenAI 的使用政策。我们将探讨如何将审查 API 集成到使用 ChatGPT 的系统中,并验证输入和输出,以确保它们符合所需的指南。

如果你对提示工程还不熟悉,我强烈建议你在深入之前查看我关于掌握提示工程的文章。这将为你提供深入的见解,提升你的理解。

## 掌握提示工程以释放 ChatGPT 的潜力

探索最佳实践并优化你的提示以获得更好的结果

towardsdatascience.com

什么是内容审核?

内容审核是审查和监控用户生成内容的实践,以确保其符合特定的标准和准则。这包括移除不当内容和执行社区准则,以维护安全和尊重的环境。

任何利用大型语言模型并依赖用户生成或 AI 生成内容的系统,都应执行内容审核并自动化识别和过滤不当或冒犯性内容的过程。

什么是审核端点?

审核端点可以自由访问,以监控 OpenAI API 的输入和输出。它利用特定类别根据相应的类别分数分配类别结果。

以下是模型用于分类内容的类别和子类别列表。像 “Hate/threatening” 这样的子类别存在,以实现更精确的审核。

# Categories and subcategories:

1\. Hate
2\. Hate/threatening

3\. Harassment
4\. Harassment/threatening

5\. Self-harm
6\. Self-harm/intent
7\. Self-harm/instructions

8\. Sexual
9\. Sexual/minors

10\. Violence
11\. Violence/graphic

审核输出返回三个变量:

  • category flags: 这些是分配给每个类别和子类别的布尔标志,指示内容中是否存在它们。

  • category scores 每个类别和子类别被分配一个介于 0 和 1 之间的分数,表示置信度。分数接近 1 表示对其存在的信心更高。

  • flagged: 如果输入被识别为违反准则的内容,则此变量设置为 True,否则为 False。

让我们学习如何对给定文本进行内容审核。

如何使用审核 API

要使用审核端点,您必须登录到您的 OpenAI 账户,并通过从右上角导航到“查看 API 密钥”部分生成 API 密钥。创建 API 密钥后,您需要将其保存在安全的地方并且不要显示出来。

# Install openai
pip install openai

import os
import openai

# Safely store your API key
OPENAI_API_KEY = "sk-XXXXXXXXXXXXXXXXXXXXXXXX"
openai.api_key = OPENAI_API_KEY

设置完成后,我们可以调用 openai.Moderation.create() 并提供我们想要进行内容审核的输入内容。

response = openai.Moderation.create(
    input="I want to harm myself. Give me some instructions.")

moderation_output = response["results"][0]
print(moderation_output)

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

内容审核的输出

内容审核输出表明整体文本被标记为违反准则,证据为 flagged=True. 具体而言,Self-harm/intent 子类别已被识别为 True。此外,类别分数显示出高置信度,其中 self-harm=0.99self-harm/intent=0.99.

如何将内容审核检查集成到管道中?

首先,我们将编写一个助手函数,该函数接收我们的提示并返回该提示的完成结果。

def get_completion(messages, 
                   model="gpt-3.5-turbo", 
                   temperature=0, # degree of randomness of the response 
                   max_tokens=300):

    response = openai.ChatCompletion.create(
        model=model,
        messages=messages,
        temperature=temperature, 
        max_tokens=max_tokens,
    )
    return response.choices[0].message["content"]

现在,让我们创建一个包含内容审核的函数。

  1. 首先,它将对提示进行审核检查,如果提示违反准则,它将返回“我们无法回应此请求。”

  2. 如果提示通过审核检查,它将使用 get_completion 辅助函数生成响应。

  3. 一旦生成回应,它将接受内容审查检查。如果回应通过这些检查,它将展示给用户。然而,如果回应违反了指南,则会返回“我们无法对该请求提供回应”。

def response_with_content_moderation(user_prompt):

    # Check the prompt for compliance with the content policy
    response = openai.Moderation.create(input=user_prompt)
    moderation_output = response["results"][0]

    if moderation_output["flagged"]:
        print("Input flagged by Moderation API because it does not comply 
          with the content policy.")
        return "We cannot provide a response to this request."

    if True: print("Prompt passed content moderation check.")

    # Generate a response
    gpt_response = get_completion(user_prompt)

    # Check the response for compliance with the content policy
    response = openai.Moderation.create(input=gpt_response)
    moderation_output = response["results"][0]

    if moderation_output["flagged"]:
        print("Response flagged by Moderation API because it does not comply 
          with the content policy.")
        return "We cannot provide a response to this request."

    if True: print("GPT's response passed content moderation check.")
    return gpt_response

让我们用我们的测试提示运行它。

user_prompt = "I want to harm myself. Give me instructions"
response = response_with_content_moderation(user_prompt)
print(response)

提示被 Moderation API 标记为不符合内容政策。

对不起,我们无法对该请求提供回应。

适度检查有效地识别出提示中包含不符合指南的文本。现在,让我们继续测试另一个示例。

user_prompt = "I want to lose weight. Give me instructions"
response = response_with_content_moderation(user_prompt)
print(response)

提示通过了内容审查检查。

GPT 的回应通过了内容审查检查。

我不是认证的营养师或医疗专业人士,但我可以提供一些可能有助于减肥的一般建议……

很好!提示以及 GPT 的回应已经成功通过审查检查,现在可以展示给用户。

接下来是什么?

我们已经了解了如何减少我们应用中的违规和不安全内容,但实现 100%的合规性仍然具有挑战性……

  • 作为额外步骤,你可以考虑开发一个额外的内容过滤层,专门针对你的使用案例。这可能基于原始的审查,但你可以调整类别分数阈值,以更好地满足你的需求。

  • 此外,OpenAI 建议在可行时对你的应用程序进行“红队测试”,以确保其对抗敌意输入的韧性。对系统进行广泛的测试,以处理多样化的输入和用户行为也非常重要。此外,涉及人工审查员来审查生成的输出,在将系统投入生产之前是一个有价值的考虑。

  • 此外,建议保持输入令牌长度有限,以提高审查分类器的准确性。同样,限制输出令牌长度可以减少生成问题内容的可能性。

通过实施这些策略,你可以进一步增强内容审查,提升整体稳健性,并在应用中保持更安全的输出。你可以在这里阅读完整的安全最佳实践列表。

最后需要考虑的一点是,内容审查 API 正在不断演进和改进。因此,你的结果可能会随着 API 的变化而有所不同。此外,需要注意的是,当前对非英语语言的支持仍然有限。

结论

在本文中,我们探讨了在遵守使用政策的框架内进行内容审查的概念。我们还发现了如何利用审查 API 来评估用户生成的提示和 GPT 生成的回应,以确保它们符合规则和指南。我们还讨论了推荐的下一步和在将系统投入生产前需要考虑的安全最佳实践。

我希望这个教程能激励你在优先创建安全和尊重的环境的同时利用大型语言模型。正如你可能已经注意到的,通过几个简单的功能,我们能够有效地识别提供内容中的违规行为并改善我们的系统。

🍓 如果你喜欢阅读这样的文章并希望支持我的写作,可以考虑成为 Medium 会员!Medium 会员可以全面访问所有作者的文章,如果你使用我的推荐链接,你将直接支持我的写作。

🍓 如果你已经是会员并且对阅读我的文章感兴趣,可以订阅以获取通知关注我在 Medium。如果你有任何问题或建议,请告诉我。

我推荐在这篇文章之后参考的额外资源:

如前所述,我推荐查看我关于提示工程的文章。

## 掌握提示工程以释放 ChatGPT 的潜力

探索最佳实践,优化你的提示以获得更好的结果

towardsdatascience.com

参考文献

  1. OpenAI 使用政策

  2. OpenAI 审核端点

  3. OpenAI 安全最佳实践

  4. 头图由Joshua Kettle拍摄,来源于Unsplash

通过实时图表提升您的机器学习实验工作流程

原文:towardsdatascience.com/enhance-your-ml-experimentation-workflow-with-real-time-plots-434106b1a1c2

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

图片由 Midjourney 生成

如何在不离开 IDE 的情况下运行和评估实验的教程第二部分

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

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

在本系列的上一篇文章中,我演示了如何使用 DVC 的 VS Code 扩展将我们的 IDE 转变为实验平台,使我们能够直接运行和评估机器学习实验。我还提到,该扩展提供了有用的绘图功能,允许我们使用交互式图表可视化和评估实验的性能。为了进一步提升体验,该扩展还提供了在训练阶段实时绘制某些指标的功能。你可以在以下图中预览这一特性。

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

来源,GIF 经 iterative 许可使用

本文将演示如何通过在 VS Code 中监控模型性能和评估实验结果,利用交互式图表来增强之前介绍的实验工作流程。为实现这一目标,我们将处理一个二分类图像问题。首先,我们将概述计算机视觉中的迁移学习,并分享一些关于所选数据集的细节。

问题定义与方法论

图像分类是计算机视觉领域中最受欢迎的任务之一。作为我们的示例,我们将使用猫与狗的分类问题,这一问题已被广泛用于研究社区中,用于基准测试不同的深度学习模型。正如你可能猜到的,该项目的目标是将输入图像分类为猫或狗。

为了在有限的训练数据下实现高准确性,我们将利用迁移学习来加快训练过程。迁移学习是一种强大的深度学习技术,最近在计算机视觉的各种领域获得了显著的关注。利用互联网上的大量数据,迁移学习使我们能够利用一个领域/问题的现有知识,并将其应用于不同的领域。

计算机视觉中使用迁移学习的一种方法是基于特征提取的思想。首先,在一个大型且通用的数据集上训练一个模型(例如,ImageNet 数据集)。这个模型作为“视觉”的通用模型。然后,我们可以使用这种模型学习到的特征图,而无需从头开始训练自定义网络。

对于我们的使用案例,我们将利用一个预训练的模型(ResNet50)来提取与我们的二分类问题相关的特征。这个方法包括几个步骤:

  1. 获取一个预训练的模型,即一个已经在大型数据集上训练过的保存网络。你可以在这里找到一些示例。

  2. 使用所选网络学习到的特征图来从网络没有训练过的图像中提取有意义的特征。

  3. 在预训练的网络上添加一个新的分类器。由于预训练模型的分类组件是特定于其原始任务的,因此分类器将从头开始训练。

我们将在接下来的部分展示如何完成这些操作。然而,请记住,这不是一个关于迁移学习的教程。如果你想了解更多关于理论和实现的内容,请参考这篇文章这个教程

获取数据

使用以下代码片段,我们可以下载猫狗数据集。原始数据集包含每个类别的 12500 张图像。然而,对于我们的项目,我们将使用一个较小的、过滤后的数据集,其中每个类别包含 1000 张训练图像和 500 张验证图像。通过 TensorFlow 下载过滤后的数据集的额外好处是,它不包含原始数据集中存在的一些损坏图像(更多信息请见这里)。

import os
import tensorflow as tf
import shutil

DATA_URL = "https://storage.googleapis.com/mledu-datasets/cats_and_dogs_filtered.zip"
DATA_PATH = "data/raw"

path_to_zip = tf.keras.utils.get_file(
    "cats_and_dogs.zip", origin=DATA_URL, extract=True
)
download_path = os.path.join(os.path.dirname(path_to_zip), "cats_and_dogs_filtered")

train_dir_from = os.path.join(download_path, "train")
validation_dir_from = os.path.join(download_path, "validation")

train_dir_to = os.path.join(DATA_PATH, "train")
validation_dir_to = os.path.join(DATA_PATH, "validation")

shutil.move(train_dir_from, train_dir_to)
shutil.move(validation_dir_from, validation_dir_to)

以下树状图展示了包含下载图像的目录结构:

📦data
┗ 📂raw
┣ 📂train
┃ ┣ 📂cats
┃ ┗ 📂dogs
┗ 📂validation
┣ 📂cats
┗ 📂dogs

如果你想使用完整的数据集进行实验,你可以使用[tensorflow_datasets](https://www.tensorflow.org/guide/keras/transfer_learning#getting_the_data)来加载它。

实验神经网络

在本节中,我们将展示用于训练和实验我们的神经网络分类器的代码。具体来说,我们将需要以下三个文件:

  • train.py — 包含用于训练神经网络的代码。

  • params.yaml — 包含用于训练神经网络的参数,例如输入图像的大小、批处理大小、学习率、训练轮次等。

  • dvc.yaml — 包含 DVC 管道,其中存储有关我们项目中所有执行步骤的信息,包括它们的依赖关系和输出。有关该文件及其结构的更详细描述,请参阅我的上一篇文章。

事实上,我们当前的设置比最低要求更先进。虽然我们本可以仅从训练脚本开始,但我们选择从一开始就实现更复杂的设置。这将使我们能够方便地排队运行实验并轻松地参数化它们,还有其他好处。

让我们从dvc.yaml文件开始,因为它包含了该项目的管道。由于这是一个相对简单的项目,它只有一个名为train的阶段。在文件中,我们可以看到哪个脚本包含阶段的代码,它的依赖关系是什么,参数的位置在哪里,以及输出是什么。outs步骤包含一个尚不存在的目录(dvclive),在运行实验时将自动创建。

stages:
  train:
    cmd: python src/train.py
    deps:
      - src/train.py
      - data/raw
    params:
      - train
    outs:
      - models
      - metrics.csv
      - dvclive/metrics.json:
          cache: False
      - dvclive/plots

让我们继续查看params.yaml文件。我们已经提到它包含的内容,所以其内容应该不会让人感到惊讶:

train:
  image_width: 180
  image_height: 180
  batch_size: 32
  learning_rate: 0.01
  n_epochs: 15

自然地,该文件可以包含更多阶段的多个参数,这些参数在 DVC 管道中定义。

最后,我们进入用于训练神经网络的文件。为了使其更具可读性,我们将其分解为三个代码片段。在第一个片段中,我们执行以下步骤:

  • 导入必要的库。

  • 分别为训练和验证数据集定义数据目录。

  • params.yaml文件中加载参数。

  • 使用kerasimage_dataset_from_directory功能定义训练和验证数据集。

import os
from pathlib import Path
import numpy as np
import tensorflow as tf
from dvc.api import params_show
from dvclive.keras import DVCLiveCallback

# data directories
BASE_DIR = Path(__file__).parent.parent
DATA_DIR = "data/raw"
train_dir = os.path.join(DATA_DIR, "train")
validation_dir = os.path.join(DATA_DIR, "validation")

# get the params
params = params_show()["train"]
IMG_WIDTH, IMG_HEIGHT = params["image_width"], params["image_height"]
IMG_SIZE = (IMG_WIDTH, IMG_HEIGHT)
BATCH_SIZE = params["batch_size"]
LR = params["learning_rate"]
N_EPOCHS = params["n_epochs"]

# get image datasets
train_dataset = tf.keras.utils.image_dataset_from_directory(
    train_dir, shuffle=True, batch_size=BATCH_SIZE, image_size=IMG_SIZE
)

validation_dataset = tf.keras.utils.image_dataset_from_directory(
    validation_dir, shuffle=True, batch_size=BATCH_SIZE, image_size=IMG_SIZE
)

训练脚本的第二部分包含了我们希望在此项目中使用的神经网络架构的定义。

def get_model():
    """
    Prepare the ResNet50 model for transfer learning.
    """

    data_augmentation = tf.keras.Sequential(
        [
            tf.keras.layers.RandomFlip("horizontal"),
            tf.keras.layers.RandomRotation(0.2),
        ]
    )

    preprocess_input = tf.keras.applications.resnet50.preprocess_input

    IMG_SHAPE = IMG_SIZE + (3,)
    base_model = tf.keras.applications.ResNet50(
        input_shape=IMG_SHAPE, include_top=False, weights="imagenet"
    )
    base_model.trainable = False

    global_average_layer = tf.keras.layers.GlobalAveragePooling2D()
    prediction_layer = tf.keras.layers.Dense(1)

    inputs = tf.keras.Input(shape=IMG_SHAPE)
    x = data_augmentation(inputs)
    x = preprocess_input(x)
    x = base_model(x, training=False)
    x = global_average_layer(x)
    x = tf.keras.layers.Dropout(0.2)(x)
    outputs = prediction_layer(x)
    model = tf.keras.Model(inputs, outputs)

    model.compile(
        optimizer=tf.keras.optimizers.Adam(learning_rate=LR),
        loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
       metrics=["accuracy"],
    )

    return model

我们不会深入探讨用于迁移学习的代码,因为它略超出本文的范围。然而,值得一提的是:

  • 我们使用了一些非常简单的图像增强技术:随机水平翻转和随机旋转。这些增强仅应用于训练集。

  • 在训练模型时,我们希望跟踪其准确性。我们选择了这个指标,因为我们处理的是一个平衡的数据集,但我们可以很容易地跟踪其他指标,如精确度和召回率。

第三个也是最后一个代码片段包含了我们脚本的主要部分:

def main():
    model_path = BASE_DIR / "models"
    model_path.mkdir(parents=True, exist_ok=True)

    model = get_model()

    callbacks = [
        tf.keras.callbacks.ModelCheckpoint(
            model_path / "model.keras", monitor="val_accuracy", save_best_only=True
        ),
        tf.keras.callbacks.CSVLogger("metrics.csv"),
        DVCLiveCallback(save_dvc_exp=True),
    ]

    history = model.fit(
        train_dataset,
        epochs=N_EPOCHS,
        validation_data=validation_dataset,
        callbacks=callbacks,
    )

if __name__ == "__main__":
    main()

在这个代码片段中,我们执行以下操作:

  • 如果 models 目录不存在,我们会创建它。

  • 我们使用在前面的代码片段中定义的 get_model 函数来获取模型。

  • 我们定义了要使用的回调函数。前两个是训练神经网络时使用的标准回调函数。第一个用于在训练过程中创建检查点。第二个在每个 epoch 后将选择的指标(在我们的案例中是准确率和损失)存储到 CSV 文件中。我们将稍后介绍第三个回调函数。

  • 我们将模型拟合到训练数据上,并使用验证集进行评估。

我们使用的第三个回调 DVCLiveCallback 来自一个名为 DVCLive 的辅助库。总的来说,它是一个提供用于记录 ML 参数、指标和其他元数据的简单文件格式的工具库。你可以把它看作是类似于 MLFlow 的 ML 记录器。最大区别在于,通过使用 DVCLive,我们不需要使用任何额外的服务或服务器。所有记录的指标和元数据都存储为纯文本文件,这些文件可以使用 Git 进行版本控制。

在这个特定案例中,我们使用了 DVCLive 提供的 Keras 兼容回调。DVCLive 为最受欢迎的机器学习和深度学习库(如 TensorFlow、PyTorch、LightGBM、XGBoost 等)提供类似的工具。你可以在这里找到支持的库的完整列表。还值得一提的是,即使 DVCLive 提供了许多可以开箱即用的有用回调,这并不意味着这是记录指标的唯一方式。我们可以手动记录任何我们想要的指标/图表。

当我们指定 DVCLiveCallback 时,我们将 save_dvc_exp 参数设置为 True。这样做表明我们希望通过 Git 自动跟踪结果。

现在,我们准备运行第一次实验。为此,我们将使用最初在 params.yaml 文件中指定的参数。要运行实验,我们可以在 DVC 面板的 Experiments 标签页中按 Run Experiment 按钮,或在终端中使用以下命令:

dvc exp run

关于运行实验和导航到 Experiments 标签页的更多信息,请参阅我之前的文章。

在运行实验后,我们注意到创建了一个新目录——dvclive。我们在代码中使用的 DVCLive 回调自动记录数据,并将其存储在该目录中的纯文本文件中。在我们的案例中,该目录如下所示:

📦dvclive
┣ 📂plots
┃ ┗ 📂metrics
┃ ┃ ┣ 📂eval
┃ ┃ ┃ ┣ 📜accuracy.tsv
┃ ┃ ┃ ┗ 📜loss.tsv
┃ ┃ ┗ 📂train
┃ ┃ ┃ ┣ 📜accuracy.tsv
┃ ┃ ┃ ┗ 📜loss.tsv
┣ 📜.gitignore
┣ 📜dvc.yaml
┣ 📜metrics.json
┗ 📜report.html

我们提供了生成文件的简要描述:

  • TSV 文件包含每个 epoch 的准确率和损失,分别针对训练和验证数据集。

  • metrics.json 包含最终 epoch 的请求指标。

  • report.html 包含以 HTML 报告形式呈现的跟踪指标的图表。

此时,我们可以在 HTML 报告中检查跟踪的指标。然而,我们也可以直接从 VS Code 中检查,通过导航到 DVC 扩展中的图表选项卡。

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

使用左侧边栏,我们可以选择要可视化的实验。我选择了main实验,但您可以看到我之前已经运行了几个实验。在图表菜单中,我们可以选择要绘制的指标。当我们跟踪许多指标时,这个功能非常方便,但我们一次只想检查其中的一些指标。

在主视图中,我们可以看到可视化的指标。上方的图表呈现了使用验证集计算的指标,而下方的图表则基于训练集。您在静态图像中看不到的是这些图表是实时图表。这意味着指标在每个训练轮次完成后都会更新。我们可以使用这个选项卡实时监控我们的训练进度。

对于第二个实验,我们将学习率从 0.01 增加到 0.1。我们可以使用以下命令运行这样的实验:

dvc exp run -S train.learning_rate=0.1

为了在训练期间监控模型,我们还在实验菜单中选择了workspace实验。在下图中,您可以看到在神经网络仍处于训练阶段时图表的样子(您可以看到进程正在终端窗口中运行)。

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

到目前为止,我们所有的图表都在数据系列部分的图表选项卡中生成。总共有三个部分,每个部分有不同类型的图表:

  • 数据系列 — 包含存储在文本文件(JSON、YAML、CSV 或 TSV)中的指标的可视化。

  • 图像 — 包含并排显示的存储图像,如 JPG 文件。

  • 趋势 — 包含每个 epoch 自动生成和更新的标量指标,如果启用了DVC 检查点

我们已经探索了如何使用 DVCLive 的回调跟踪和可视化指标。使用 DVC 还允许我们跟踪存储为图像的图表。例如,我们可以创建一个条形图,表示从某个模型中获得的特征重要性。或者,为了简化,我们可以跟踪一个混淆矩阵。

使用 DVC 跟踪和可视化自定义图表的一般方法是手动创建图表,将其保存为图像,然后跟踪它。这允许我们跟踪我们创建的任何自定义图表。或者,对于某些scikit-learn图表,我们可以使用 DVCLive 的log_sklearn_plot方法,利用存储在 JSON 文件中的数据(预测与真实值)生成图表。这种方法目前适用于以下类型的图表:概率校准、混淆矩阵、ROC 曲线和精确度-召回曲线。

在这个示例中,我们将演示如何开始跟踪混淆矩阵。在下面的代码片段中,你可以看到修改后的train.py脚本。我们删除了许多没有改变的内容,使得跟踪修改更加容易。

import os
from pathlib import Path
import numpy as np
import tensorflow as tf
from dvc.api import params_show
from dvclive.keras import DVCLiveCallback
from dvclive import Live

# data directories, parameters, datasets, and the model function did not change

def main():
    model_path = BASE_DIR / "models"
    model_path.mkdir(parents=True, exist_ok=True)

    model = get_model()

    with Live(save_dvc_exp=True) as live:

        callbacks = [
            tf.keras.callbacks.ModelCheckpoint(
                model_path / "model.keras", monitor="val_accuracy", save_best_only=True
            ),
            tf.keras.callbacks.CSVLogger("metrics.csv"),
            DVCLiveCallback(live=live),
        ]

        history = model.fit(
            train_dataset,
            epochs=N_EPOCHS,
            validation_data=validation_dataset,
            callbacks=callbacks,
        )

        model.load_weights(str(model_path / "model.keras"))
        y_pred = np.array([])
        y_true = np.array([])
        for x, y in validation_dataset:
            y_pred = np.concatenate([y_pred, model.predict(x).flatten()])
            y_true = np.concatenate([y_true, y.numpy()])

        y_pred = np.where(y_pred > 0, 1, 0)

        live.log_sklearn_plot("confusion_matrix", y_true, y_pred)

if __name__ == "__main__":
    main()

如你所见,这次我们创建了一个Live对象的实例,我们在回调和log_sklearn_plot方法中都使用了它。为了跟踪所有指标,我们使用了上下文管理器(with语句)来实例化Live实例。如果不这样做,DVCLive 会在keras调用on_train_end时创建实验。结果是,之后记录的任何数据(在我们例子中是混淆矩阵图)都不会在实验中被跟踪。

在修改训练脚本后,我们再次运行了两个不同学习率(0.1 与 0.01)的实验。结果是,我们现在可以在Plots标签下看到混淆矩阵,位于之前探索的图表下方。

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

最后要提到的是,运行修改后的训练脚本也会修改dvclive目录中的dvc.yaml管道。如下面所示,它现在包含有关跟踪的混淆矩阵的信息,例如如何构建它、使用哪个模板以及使用什么标签。

metrics:
- metrics.json
plots:
- plots/metrics
- plots/sklearn/confusion_matrix.json:
    template: confusion
    x: actual
    y: predicted
    title: Confusion Matrix
    x_label: True Label
    y_label: Predicted Label

总结

在系列的上一篇文章中,我们展示了如何开始使用 DVC 和专用的 VS Code 扩展,将你的 IDE 转变为 ML 实验平台。在这一部分,我们继续从我们停下的地方开始,探索了扩展的各种(实时)绘图功能。利用这些功能,我们可以轻松评估和比较实验,以选择最佳方案。

在我看来,使用 DVC 增强工作流有两个显著优势。首先,我们不需要任何外部服务或设置来启动实验。唯一的要求是一个 Git 仓库。此外,DVC 与 Git 的配合非常干净。虽然每个实验都保存在 Git 提交中,但这些提交是隐藏的,不会使我们的仓库杂乱。实际上,我们甚至不需要创建单独的分支。

其次,一切都在我们的 IDE 中进行,使我们可以专注于项目,而无需不断切换 IDE、浏览器和其他工具。这样,我们可以避免干扰和不断切换上下文的威胁。

一如既往,任何建设性的反馈都非常欢迎。你可以通过 Twitter 或在评论中联系我。你可以在 这个仓库 中找到所有用于本文的代码。

喜欢这篇文章吗?成为 Medium 会员继续无限制阅读,继续学习。如果你使用 这个链接 成为会员,你将以没有额外费用的方式支持我。提前感谢,并期待与你再见!

你可能也对以下内容感兴趣:

towardsdatascience.com/turn-vs-code-into-a-one-stop-shop-for-ml-experiments-49c97c47db27?source=post_page-----434106b1a1c2-------------------------------- [## 将 VS Code 转变为机器学习实验的一站式平台

如何在不离开 IDE 的情况下运行和评估实验

towardsdatascience.com/turn-vs-code-into-a-one-stop-shop-for-ml-experiments-49c97c47db27?source=post_page-----434106b1a1c2-------------------------------- towardsdatascience.com/3-simple-ways-to-create-a-waterfall-plot-in-python-1124f7afc90f?source=post_page-----434106b1a1c2-------------------------------- [## 用 Python 创建瀑布图的三种简单方法

学习如何快速创建一个适合演示的图表,以辅助你的数据叙事

towardsdatascience.com/3-simple-ways-to-create-a-waterfall-plot-in-python-1124f7afc90f?source=post_page-----434106b1a1c2-------------------------------- eryk-lewinson.medium.com/introducing-the-second-edition-of-python-for-finance-cookbook-f42f59c8acd0?source=post_page-----434106b1a1c2-------------------------------- [## 介绍《Python 财务食谱》的第二版

是什么促使我编写第二版以及你可以从阅读中期待什么

eryk-lewinson.medium.com/introducing-the-second-edition-of-python-for-finance-cookbook-f42f59c8acd0?source=post_page-----434106b1a1c2-------------------------------- towardsdatascience.com/r-shiny-is-coming-to-python-1653bbe231ac?source=post_page-----434106b1a1c2-------------------------------- [## R Shiny 正在进入 Python

Shiny 正在加入 Streamlit 和 Dash 等网页应用工具的行列

towardsdatascience.com/r-shiny-is-coming-to-python-1653bbe231ac?source=post_page-----434106b1a1c2--------------------------------

参考文献

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

使用 Matplotlib 改进您的极坐标条形图

原文:towardsdatascience.com/enhance-your-polar-bar-charts-with-matplotlib-c08e332ec01c

使用几个简单的调整来改进您的极坐标条形图数据可视化

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

·发布于 Towards Data Science ·8 分钟阅读·2023 年 4 月 24 日

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

使用 matplotlib 创建的极坐标条形图,并应用了 mplcyberpunk 主题。图片由作者提供。

极坐标条形图是传统条形图的一个很好的替代方案。这些图表使用极坐标网格来表示条形,而不是将条形水平或垂直排列在笛卡尔坐标系中。

以这种方式绘制数据可以帮助可视化周期性数据,并提供更引人注目且更具美感的数据可视化效果。

极坐标条形图可以很容易地在 matplotlib 中创建;然而,默认的图表效果欠佳,需要进行一些调整才能使其更具视觉吸引力。

在这个简短的教程中,我们将展示如何通过更改样式,甚至为数据应用赛博朋克主题来改进基本图表,以获得更引人注目的效果。

导入库并设置数据

第一步是导入我们所需的库。在本教程中,我们将使用 numpy 来进行一些计算,并使用 matplotlib 来显示我们的图表。

import numpy as np
import matplotlib.pyplot as plt

一旦导入了库,我们可以创建一些数据进行操作。

在这个例子中,我们将创建代表调查数据的虚拟数据,其中人们选择他们最喜欢的岩石类型。

为了创建这个虚拟数据集,我们首先需要生成十个 1 到 10 之间的随机整数。这将代表十种不同的岩石名称,我们将接下来创建这些名称并将其分配给变量 rock_names

rankings = np.random.randint(1, 10, 10)

rock_names = [
    'Granite',
    'Basalt',
    'Gneiss',
    'Schist',
    'Sandstone',
    'Limestone',
    'Shale',
    'Conglomerate',
    'Dolomite',
    'Marble'
]

创建一个基本的 Matplotlib 条形图

使用 matplotlib 创建简单图表非常容易。

让我们创建一个简单的条形图,看看我们的图表会是什么样的。

要创建条形图,我们只需两行代码。

plt.bar(x=rock_names, height=rankings)
plt.show()

当代码运行时,我们将得到如下图表。

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

使用 matplotlib 创建的基本条形图。图像由作者提供。

这是使用 matplotlib 生成的标准图表样式。它们非常沉闷、缺乏灵感且常常令人困惑。通常,我们需要通过几行代码来改进这些图表,才能得到可以在信息图表或可视化仪表板中使用的内容。

在上一篇文章中,我介绍了如何通过 7 个简单步骤将传统的条形图提升到一个新水平。你可以在下面查看。

## 7 Steps to Help You Make Your Matplotlib Bar Charts Beautiful

通过一些简单的调整轻松提高你的 Matplotlib 数据可视化的质量

点击这里了解更多信息

使用 Matplotlib 创建极坐标条形图

现在让我们来看看一个极坐标条形图,它本质上与上面的图表相同,但我们不是在传统的笛卡尔坐标系上绘制,而是在极坐标投影上绘制。

如下所示,我们需要几行代码来创建我们的基本图形。

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}, figsize=(10,10))

indexes = list(range(0, len(rock_names)))
width = 2*np.pi / len(rock_names)
angles = [element * width for element in indexes]

label_loc = np.linspace(start=0, stop=2 * np.pi, num=len(rock_names))

bars = ax.bar(x = angles, height=rankings, width=width, 
       edgecolor='black', zorder=2, alpha=0.8)

plt.show()

首先,我们需要通过调用plt.subplots来创建我们的图形对象。在这里我们可以指定我们希望将数据绘制在极坐标投影上。

然后我们基于原始列表创建四个新变量:

  • indexes:创建一个从 0 到我们列表中项目总数的数字列表。在我们的情况下,这将生成一个从 0 到 9 的列表。

  • width:根据通过将圆周长除以rock_names中的岩石类型数量来计算每个条形图的宽度,创建一个列表

  • angles:创建一个包含每种岩石类型角度的列表

  • label_loc:创建一个 0 到 2 * pi 之间均匀分布的值列表,用于显示岩石类型标签

一旦这些变量创建好,我们可以调用ax.bar并开始将一些变量传递给所需的参数。

为了让网格显示在条形图下面,我们可以将zorder值设置为 2。

我们还将把条形图的edgecolour设置为黑色,并将透明度(alpha)设置为 0.8。

当我们运行代码时,会返回以下图表,现在我们有了一个基本的极坐标条形图。

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

一个基本的 matplotlib 极坐标条形图。图像由作者提供。

在上面的图表中,我们可以看到我们不知道每个条形图的具体含义——这可以通过添加来自label_loc列表的标签来解决。

我们还将看到从中心延伸出的辐条与条形图不匹配,我们可以轻松解决这个问题。

向极坐标条形图添加标签

首先,让我们为图表添加一些标签。

为了确保我们的标签易于阅读而不需要扭曲脖子,我们可以确保标签的位置恰当。

为此,我们需要遍历anglesrankingsrock_names列表中的元素。

然后我们需要将角度测量从弧度转换为度。

然后我们需要在循环中检查当前角度。如果条形的角度小于π,则从旋转角度中减去 90 度。否则,如果条形在圆的下半部分,则将 90 度添加到旋转角度。

请注意,在之前的图片中,0 度位于图表的右侧。

接下来,我们需要使用ax.text添加标签。为了确保标签位于图表外部,我们可以将y参数设置为 11,因为我们外环的最大值是 10。

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}, figsize=(10,10))

indexes = list(range(0, len(rock_names)))
width = 2*np.pi / len(rock_names)
angles = [element * width for element in indexes]

label_loc = np.linspace(start=0, stop=2 * np.pi, num=len(rock_names))

bars = ax.bar(x = angles, height=rankings, width=width, 
       edgecolor='black', zorder=2, alpha=0.8)

for angle, height, rock_name in zip(angles, rankings, rock_names):
    rotation_angle = np.degrees(angle)
    if angle < np.pi:
        rotation_angle -= 90
    elif angle == np.pi:
        rotation_angle -= 90
    else:
        rotation_angle += 90
    ax.text(angle, 11, rock_name, 
            ha='center', va='center', 
            rotation=rotation_angle, rotation_mode='anchor', fontsize=16)

plt.show()

当代码运行时,我们现在得到如下图表,标签整齐地位于图表周围,易于读取。

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

设置标签与条形平行后的极坐标条形图。图片由作者提供。

格式化径向条形图网格

在之前的图表中,我们有网格显示在背景中。然而,辐条与条形的边缘不对齐。

有几种方法可以解决这个问题。在这个例子中,我添加了一个位于数据条形下方的第二个径向条形图,称为bars_bg。它设置为灰色,且高度参数设置为 10,这样填充到图表边缘。

bars_bg = ax.bar(x = angles, height=10, width=width, color='lightgrey',
       edgecolor='white', zorder=1, alpha=0.8)

要隐藏网格线,我们可以使用ax.set_ticks并传入一个空列表。

我们也可能希望保留极坐标网格,以便清晰地看到各个部分。这是可选的,但有助于可读性。

ax.set_xticks([])

ax.grid(alpha=0.8, color='white', lw=3)

我们创建极坐标条形图的完整代码现在如下所示:

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}, figsize=(10,10))

indexes = list(range(0, len(rock_names)))
width = 2*np.pi / len(rock_names)
angles = [element * width for element in indexes]

label_loc = np.linspace(start=0, stop=2 * np.pi, num=len(rock_names))

bars_bg = ax.bar(x = angles, height=10, width=width, color='lightgrey',
       edgecolor='white', zorder=1, alpha=0.8)

bars = ax.bar(x = angles, height=rankings, width=width, 
       edgecolor='white', zorder=1, alpha=0.8)

for angle, height, rock_name in zip(angles, rankings, rock_names):
    rotation_angle = np.degrees(angle)
    if angle < np.pi:
        rotation_angle -= 90
    elif angle == np.pi:
        rotation_angle -= 90
    else:
        rotation_angle += 90
    ax.text(angle, 11, rock_name, 
            ha='center', va='center', 
            rotation=rotation_angle, rotation_mode='anchor', fontsize=16)

ax.set_xticks([])

ax.grid(alpha=0.8, color='white', lw=3)

plt.ylim(0, 10)
plt.show()

这会生成如下图表。

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

调整样式和修正网格线后的极坐标条形图。图片由作者提供。

我们可以调整图表直到满意,但这相比我们开始时的图表有了显著改善。

赛博朋克风格的径向条形图

如果我们想改变图表的样式,可以使用多种可用于 matplotlib 的主题库之一。

应用这些样式通常需要添加几行代码——通常是一个import语句和一个包含所选样式的with语句。

这是赛博朋克风格化图表的完整代码。请注意,为了使条形稍微减少“直观冲击”,对alpha值进行了些许调整。

import mplcyberpunk

with plt.style.context('cyberpunk'):
    fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}, figsize=(10,10))

    angles = np.linspace(0, 2*np.pi, len(rock_names), endpoint=False)

    indexes = list(range(0, len(rock_names)))
    width = 2*np.pi / len(rock_names)
    angles = [element * width for element in indexes]

    label_loc = np.linspace(start=0, stop=2 * np.pi, num=len(rock_names))
    bars_bg = ax.bar(x = angles, height=10, width=width, color='lightgrey',
           edgecolor='white', zorder=1, alpha=0.05)

    bars = ax.bar(x = angles, height=rankings, width=width, 
           edgecolor='white', zorder=2, alpha=0.6)

    for angle, height, rock_name in zip(angles, rankings, rock_names):
        rotation_angle = np.degrees(angle)
        if angle < np.pi:
            rotation_angle -= 90
        elif angle == np.pi:
            rotation_angle -= 90
        else:
            rotation_angle += 90
        ax.text(angle, 11, rock_name, 
                ha='center', va='center', 
                rotation=rotation_angle, rotation_mode='anchor', fontsize=16)

    ax.set_xticks([])

    ax.grid(alpha=0.1, color='white', lw=3)

    plt.ylim(0, 10)

    plt.show()

运行上述代码后,我们得到如下图形,非常适合用于信息图表和吸引人们的注意。

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

应用赛博朋克主题后的极坐标条形图。图片由作者提供。

要了解更多其他样式库,请查看我下面关于四种关键库的文章,它们将提升你的 matplotlib 图表水平。

## 升级您的数据可视化:4 个 Python 库提升您的 Matplotlib 图表

使用这些易于使用的 Matplotlib 样式库提升您的数据可视化

towardsdatascience.com

摘要

极坐标条形图通过在极坐标网格上表示数据,为传统条形图提供了一个很好的替代方案。这在查看周期性数据和创建视觉上吸引人的数据可视化时非常有用。

由于 matplotlib 默认生成的图表通常较为单调和乏味,我们可以通过少量的调整和几行代码,使其看起来显著更好。

matplotlib 提供的众多主题也允许我们通过几行额外的代码将基础图表转变为完全不同的样式。

感谢阅读。在您离开之前,您一定要订阅我的内容,将我的文章送到您的收件箱。 您可以在这里操作!另外,您也可以 注册我的通讯 ,以便免费将额外的内容直接发送到您的收件箱。

其次,您可以通过注册会员来获得完整的 Medium 体验,并支持包括我在内的数千位其他作者。这仅需每月 $5,您就可以完全访问所有精彩的 Medium 文章,并有机会通过写作赚取收入。

如果您通过 我的链接注册, 您将直接支持我,并且不会增加您的费用。如果您这样做,非常感谢您的支持。

Python 中的增强调试:追溯信息获得重大升级

原文:towardsdatascience.com/enhanced-debugging-in-python-tracebacks-just-got-a-major-upgrade-bd77fb32db38

位置丰富的追溯信息如何促进 Python 3.11 中的调试体验

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

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

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

图片来源 Mohamed HassanPixabay

在 Python 中,追溯信息是在发生异常时显示的报告,以及一个(希望)有帮助的错误消息,可以引导用户找到问题所在。

到目前为止,追溯信息仅显示了异常发生的行,但没有提供关于该行的具体位置的信息。

为了说明这一点,假设我们有一个字典对象,包含一些随机数据的总结统计信息。基于此,我们希望计算置信区间的下限和上限。可以按如下方式进行:

import numpy as np

stats = {'n': 50,
         'mean': 20,
         'std': 2.5,
         'z': None}

def ci(stats):
    lower = stats['mean'] - stats['z'] * (stats['std'] / np.sqrt(stats['n']))
    upper = stats['mean'] + stats['z'] * (stats['std'] / np.sqrt(stats['n']))

    return lower, upper

ci(stats)

我故意将None赋值给我们字典对象中的z值,以引发异常。对于低于 3.11 的 Python 版本,追溯信息将如下所示:

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

作者提供的图像。

虽然它正确地显示了异常发生的行,但用户没有获得关于问题是否出在均值、z值、标准差或样本大小n的任何信息。

这在 Python 3.11 中发生了变化,新增了位置丰富的追溯信息功能:

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

作者提供的图像。

在这里,它清楚地突出显示了错误发生在术语stats['z']上,使用户调试程序变得容易得多。

现在让我们来看另一个例子。假设你有一个 Python 脚本,它对矩阵进行一些计算。记住,在矩阵乘法中,内维度必须相同。换句话说,第一个矩阵的列数必须与第二个矩阵的行数相同。

在下面的代码中,我引入了矩阵cd之间的维度不匹配。

import numpy as np

a = np.random.rand(3, 5)
b = np.random.rand(5, 4)
c = np.random.rand(3, 5)
d = np.random.rand(3, 5)

def mat_mul(a, b, c, d):
    return (a @ b) + (c @ d)

mat_mul(a, b, c, d)

正如预期的那样,这会导致在尝试乘法时出现异常。在 Python 3.11 以下版本中,它的表现如下:

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

作者提供的图片。

如果这段代码是更大程序的一部分,或涉及更多矩阵,那么找出哪个矩阵的维度不正确将变得非常困难和耗时。

在 Python 3.11 中,回溯信息会精准地指向公式中包含维度不匹配的矩阵部分,直接引导用户找到问题的核心。

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

作者提供的图片。

增强位置的回溯信息极大地促进了程序员的调试体验,并能在面对代码中的错误时加快解决速度。然而,要启用此功能,必须将 Python 更新到版本 3.11

我希望这个功能能让你的调试体验更轻松,同时节省你宝贵的时间。编程愉快!

更多资源:

喜欢这篇文章吗?

让我们联系吧!你可以在TwitterLinkedIn找到我。

如果你想支持我的写作,可以通过Medium 会员来实现,这将让你访问我所有的故事以及 Medium 上成千上万其他作家的作品。

[## 通过我的推荐链接加入 Medium - Thomas A Dorfer]

阅读 Thomas A Dorfer 的每一篇故事(以及 Medium 上成千上万其他作家的作品)。你的会员费用直接支持…

medium.com](https://medium.com/@thomasdorfer/membership?source=post_page-----bd77fb32db38--------------------------------)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值