MLOps 原理介绍
原文:
towardsdatascience.com/introduction-to-mlops-principles-c5d73a00aa76
面向初学者的 MLOps 介绍
·发表于 Towards Data Science ·阅读时间 9 分钟·2023 年 10 月 30 日
–
照片由 Silvestri Matteo 提供,来自 Unsplash
如果你希望将 MLOps 项目提升到一个新水平,理解其原理是这个过程中的关键部分。本文将介绍 MLOps 的原理,并以易于理解的方式阐明关键概念。每个原理都将配有专门的教程和实际案例,相关内容将在后续文章中发布。你可以在我的 Github 个人资料 上查看所有示例。然而,如果你是 MLOps 的新手,我建议你从我的 面向初学者的教程 开始,以便尽快了解情况。让我们开始吧!
目录:
· 1. 介绍
· 2. MLOps 原理
· 3. 版本控制
· 4. 测试
· 5. 自动化
· 6. 监控和跟踪
· 7. 可重复性
· 8. 结论
我的 MLOps 教程:
-
教程 1:MLOps 的关键起点:探索其基本组成部分
-
教程 2:面向初学者的 MLOps 工作流简介
-
教程 6:实践中的测试:代码、数据和机器学习模型
-
教程 7:实践中的追踪:代码、数据和机器学习模型
[我会在发布相关主题文章时更新此列表]
1. 介绍
在上一篇文章中,我们将 MLOps 定义为一套技术和实践,用于以高效、优化和有组织的方式设计、构建和部署机器学习模型。MLOps 的关键步骤之一是建立工作流并随着时间进行维护。
MLOps 工作流概述了开发、部署和维护机器学习模型的步骤。它包括描述问题的业务问题、涉及所有数据准备和预处理的数据工程、涵盖从模型设计到评估的所有模型处理的机器学习模型工程,以及涉及模型服务的代码工程。如果您想了解更多细节,可以参考之前的教程。
MLOps 工作流
MLOps 工作流的所有部分都是互联的,并以循环的方式运行。在任何阶段,可能需要重新访问之前的步骤。这些工作流各阶段之间的相互依赖定义了MLOps 生命周期。MLOps 生命周期对于确保机器学习模型的最佳性能并仍然解决在第一阶段定义的业务问题至关重要。因此,维护 MLOps 生命周期的关键在于遵循MLOps 原则。
2. MLOps 原则
MLOps 原则是一组旨在维护 MLOps 生命周期的概念,同时减少开发和部署机器学习模型的时间和成本,从而避免技术债务。
为了确保整个生命周期的维护,这些原则需要应用于不同的工作流步骤,包括数据、机器学习模型(ML 模型)和代码。这些原则包括版本控制、测试、自动化、监控和追踪以及可重复性。成功实施这些原则需要利用适当的工具和遵循最佳实践,例如项目结构化。
在本文中,我试图根据不同原则的重要性和应用顺序进行优先排序。然而,需要注意的是,这些原则在成熟的机器学习项目中都具有重要意义:它们是相互依赖的并且互补。
3. 版本控制
在 MLOps 原则中,第一个需要考虑的事情是版本化不同的应用组件,包括数据、机器学习模型和代码。这使得对不同 MLOps 组件所做的更改可以进行跟踪,并能够重现工作流程的不同步骤。它还允许开发者和数据科学家在需要时回滚到先前版本,比较不同版本之间的性能,并重现结果。
数据工程中的版本管理包括数据集、特征、元数据和处理管道的版本管理。它可以使用版本控制系统(VCS),如 Git 和 DVC(数据版本控制)来管理,这些系统支持大数据文件的版本化和跟踪。
机器学习模型的版本管理包括模型本身(其架构和权重)、训练管道、超参数和结果的版本管理。通常使用 Git 进行管理;然而,还有其他工具,如 MLflow,提供了版本化其他元数据的附加功能。
代码的版本管理包括代码源本身及其不同配置的版本管理。还建议在每次代码版本管理时保留库版本,以防止库版本问题。
总的来说,版本管理在 MLOps 中至关重要,以确保对机器学习模型、代码和数据所做的更改得到有效跟踪和管理。
4. 测试
测试是 MLOps 中的一个基本、关键且强制性的方面。它减少了错误和漏洞的风险,并能快速发现和修复问题。此外,它确保机器学习模型按预期运行,并满足业务问题的要求。
数据测试确保用于机器学习的数据的质量和正确性,这反过来确保机器学习模型的准确性和可靠性。它包括数据集验证和数据处理管道验证。数据集验证包括识别数据中的潜在问题,如缺失值和不一致数据。后者通过计算一些统计数据和可视化技术来完成。数据处理管道验证包括对不同的数据处理函数进行单元测试,特别是特征创建。
机器学习模型测试确保新数据上的预测准确性,并评估模型的泛化能力。这是一个测试、评估和验证机器学习模型的过程。这个过程包括测试模型规格、训练管道集成以及模型的相关性和正确性。此外,还需要测试一些非功能性要求,如安全性、公平性、可解释性,以确保模型的有效性、可靠性和伦理影响。
代码测试包括验证整个 MLOps 项目代码的正确性和质量,以确保代码没有缺陷并满足项目要求。代码测试包括对不同模块和功能的单元测试、端到端管道的集成测试、系统测试和使用真实数据的验收测试。
总体而言,测试是 MLOps 中确保管道正确性和效率的关键方面。它还可以改善其他 MLOps 原则。
-
它确保在版本控制期间所做的代码更改能够正常运行。
-
它通过将其添加到自动化管道中来提升自动化水平。
-
它通过检测潜在问题来改善监控。
-
它还通过确保模型能够一致地被重现来提高可重复性。
5. 自动化
自动化是 MLOps 原则之一,旨在自动化不同的管道处理过程,包括构建、测试、部署和管理。自动化定义了系统摆脱人工干预的程度:系统摆脱人工过程的程度越高,系统的自动化水平越高。因此,自动化水平定义了过程的成熟度水平。
数据工程的过程通常是手动的,从数据收集、数据准备到数据验证,因为这一步具有实验性和迭代性。然而,某些步骤可以自动化,比如数据转换和特征处理。自动化这些步骤提高了准确性并减少了人工错误。
ML 模型自动化旨在自动化从模型开发到部署和管理的过程。它包括自动化特征工程、模型训练、超参数选择和调整、模型验证和模型部署。这些步骤减少了构建和部署模型所需的时间和资源,同时提高了模型的质量和一致性,特别是在新数据可用、模型更改或监控事件时。自动化 ML 模型开发过程有助于减少构建和部署模型所需的时间和资源,同时提高其质量和一致性。
代码自动化旨在减少错误、提高效率并改善整体质量。它包括应用构建自动化和 CD/CI 管道自动化,以执行快速可靠的 ML 模型生产部署。
总之,自动化能够高效可靠地实施重复任务并标准化流程。此外,它与其他原则相互关联:
-
自动化通过管理和跟踪不同版本的代码、数据和模型来促进版本控制。
-
它通过自动化执行各种测试(如单元测试、集成测试和性能测试)来促进测试。
-
它通过自动化相关指标和性能指标的收集、分析和可视化来促进监控。
-
它通过自动化代码、工作流和管道的执行来促进可重现性。
6. 监控与跟踪
当模型部署时,监控 MLOps 项目以确保模型在生产中的性能、稳定性和可靠性是至关重要的。如果监控检测到异常,必须报告这些变化,并提醒相关开发人员。监控 MLOps 的不同步骤包括频繁地重新运行和比较它们。
MLOps 中的数据监控包括持续观察和分析用于机器学习模型的输入数据,以确保其质量、一致性和相关性。它包括监控版本变化、训练和服务输入中的不变量、计算的训练和服务特征中的不变量,以及特征生成过程。
ML 模型监控包括跟踪和评估生产中机器学习模型的性能。它包括监控模型的数值稳定性、模型的年龄和衰退,以及 ML 系统的计算性能。
代码监控包括跟踪和评估机器学习项目中使用的代码,以确保其质量、完整性和遵循编码标准。这包括监控源系统中的变化、依赖项升级以及应用程序在服务数据上的预测质量。
同样重要的是,监控和跟踪通过跟踪输入、输出和代码、工作流及模型的执行来支持可重现性。此外,通过监控测试,可以检测模型/系统行为和性能中的异常。
7. 可重现性
可重现性在构建机器学习工作流时至关重要。它允许在任何执行地点生成相同的结果,只要输入相同。为了确保结果一致,整个 MLOps 工作流需要是可重现的。
数据可重现性包括捕捉和保存与数据收集、预处理和转换相关的所有必要信息和过程,以便其他人能够获得相同的数据集进行分析或模型训练。这包括为数据创建备份、对数据和特征进行版本控制,以及创建和备份元数据和文档。
ML 模型的可重现性包括能够一致地重建和获得相同的训练机器学习模型。这包括确保参数相同,以及在开发、生产或其他机器上参数的顺序相同。
代码可重复性包括从用于开发机器学习模型的代码中重新创建和获得相同结果的能力。它包括确保所有环境中的依赖版本和技术栈相同。这通常通过提供容器镜像或虚拟机来确保。
总体来说,可重复性通过其他原则来实现:版本控制、测试、自动化和监控协同工作,以捕获和维护必要的信息,一致地执行流程,验证结果的正确性,并识别可能影响模型和工作流可重复性的因素。
8. 结论
我们来到了本文的结尾。在这篇文章中,我们介绍并简要解释了 MLOps 原则。你可以将 MLOps 原则视为维护 MLOps 生命周期的组成部分。它们可以通过实践、工具或两者的结合来实现。为了确保整个生命周期的维护,它们被应用于 MLOps 工作流的不同组件上。
在接下来的教程中,我们将深入学习不同的 MLOps 原则。我会撰写更多关于 MLOps 及其各种技术的教程,并附带示例,敬请关注。
感谢阅读本文。你可以在我的GitHub 个人资料中找到我提供的不同教程的所有示例。如果你喜欢我的教程,请通过关注和订阅来支持我。这样,你将收到我新文章的通知。如果你有任何问题或建议,请随时留下评论。
图片来源
本文中所有未在说明中提及来源的图片和图表均由作者提供。
mypy 介绍
Python 的静态类型检查
·发布在Towards Data Science ·阅读时间 8 分钟·2023 年 4 月 6 日
–
图片由Agence Olloweb提供,在Unsplash上
我们在之前关于Python 最佳实践的文章中提到了 mypy 是必备的——在这里,我们希望更详细地介绍它。
mypy,正如文档所解释的,是一个“Python 的静态类型检查器”。这意味着,它为 Python 添加类型注解和检查,而 Python 是设计上动态类型的(类型在运行时推断,与例如 C++ 相对)。这样做可以在编译时发现代码中的错误,这是一种极大的帮助——并且是任何半专业 Python 项目的必备条件,正如我在之前的文章中所解释的那样。
在这篇文章中,我们将通过几个示例来介绍 mypy。免责声明:这篇文章不会介绍 mypy 的所有功能(甚至连一部分都不会)。相反,我会尝试在提供足够细节以让你编写几乎所有想要的代码和从零开始到扎实理解 mypy 之间找到一个良好的平衡。有关更多细节,我建议查阅官方文档或其他优秀教程。
安装
要安装 mypy,只需运行:pip install mypy
但是,我建议使用某种形式的依赖管理系统,例如poetry。如何将它和 mypy 包含在更大的软件项目中,已在这里进行了说明。
初步步骤
让我们通过第一个示例来激发对 mypy 使用的兴趣。请考虑以下代码:
def multiply(num_1, num_2):
return num_1 * num_2
print(multiply(3, 10))
print(multiply("a", "b"))
multiply
期望两个数字并返回它们的乘积。因此,multiply(3, 10)
工作正常并返回预期结果。但是第二条语句失败并崩溃,因为我们不能将字符串相乘。由于 Python 是动态类型的,没有任何东西阻止我们编写/执行该语句,我们只在运行时发现了这个问题——这很有问题。
在这里,mypy 及时提供了帮助。我们现在可以注解函数的参数和返回类型,如下所示:
def multiply(num_1: int, num_2: int) -> int:
return num_1 * num_2
print(multiply(3, 10))
print(multiply("a", "b"))
这种注解不会以任何方式改变执行,特别是,你仍然可以运行这个有问题的程序。然而,在这样做和发布我们的程序之前,我们现在可以运行 mypy 并通过以下命令检查任何可能的错误:mypy .
运行此命令将失败,并正确指出我们不能将字符串传递给multiply
。上述命令旨在从应用程序的主文件夹中执行,。
将检查当前文件夹及子目录中的每个文件。但你也可以通过mypy file_to_check.py
来检查特定文件。
这希望能激发对 mypy 的需求和使用的理解,现在让我们更深入探讨。
配置 mypy
mypy 可以通过多种方式进行配置——无需详细说明,它只需要找到一个包含“mypy”部分的配置文件(如 mypy.ini,pyproject.toml 等)。在这里,我们将创建默认的mypy.ini
文件,它应该位于项目的主文件夹中。
现在,让我们来看看可能的配置选项。为此,我们回到最初的示例:
def multiply(num_1, num_2):
return num_1 * num_2
print(multiply(3, 10))
print(multiply("a", "b"))
实际上,简单地运行 mypy 并不会产生错误!这是因为类型提示默认是可选的——而 mypy 只检查那些有注解的类型。我们可以通过标志— disallow-untyped-defs
来禁用这一点。此外,还有许多其他标志可以使用(参见 here)。然而,根据本帖的一般格式,我们不会详细讨论所有这些标志——而是仅呈现严格模式。此模式基本上开启所有可选检查。在我的经验中,使用 mypy 的最佳方法是要求尽可能严格的检查——然后修复(或选择性地忽略)所有出现的问题。
要做到这一点,我们可以像这样填写mypy.ini
文件:
[mypy]
strict = true
[mypy]
部分头对于任何 mypy 相关的配置都是必要的,下一行是相当自解释的。
当我们像往常一样运行 mypy 时,我们会遇到关于缺失类型注解的错误——这些错误只有在所有内容都被注解并且我们移除错误的字符串调用后才会消失。
现在让我们更详细地看看如何使用 mypy 进行注解。
使用 mypy 注解
在本节中,我们将描述最常见的类型注解和 mypy 关键字。
基本类型
我们可以通过简单地使用其 Python 类型来注解基本类型,即bool
、int
、float
、str
等:
def negate(value: bool) -> bool:
return not value
def multiply(multiplicand: int, multiplier: int) -> int:
return multiplicand * multiplier
def divide(dividend: float, divisor: float) -> float:
return dividend / divisor
def concat(str_a: str, str_b: str) -> str:
return str_a + " " + str_b
print(negate(True))
print(multiply(3, 10))
print(divide(10, 3))
print(concat("Hello", "world"))
集合类型
从 Python 3.9 开始,内置的集合类型也可以用作类型注解。即 list
、set
、dict
等:
def add_numbers(numbers: list[int]) -> int:
return sum(numbers)
def cardinality(numbers: set[int]) -> int:
return len(numbers)
def concat_values(value_dict: dict[str, float]) -> list[float]:
return [val for _, val in value_dict.items()]
print(add_numbers([1, 2, 3, 4]))
print(cardinality({1, 2, 3}))
print(concat_values({"a": 1.5, "b": 10}))
正如我们所见,我们必须指定容器的内容(例如int
)。对于混合类型,请参见下文。
早期 Python 版本
对于早期的 Python 版本,必须使用 typing
模块中的旧版类型:
from typing import Dict, List, Set
def add_numbers(numbers: List[int]) -> int:
return sum(numbers)
def cardinality(numbers: Set[int]) -> int:
return len(numbers)
def concat_values(value_dict: Dict[str, float]) -> list[float]:
return [val for _, val in value_dict.items()]
print(add_numbers([1, 2, 3, 4]))
print(cardinality({1, 2, 3}))
print(concat_values({"a": 1.5, "b": 10}))
混合内容
如上所述,我们可能想要创建包含不同数据类型的容器。为此,我们可以使用Union
关键字——它允许我们将类型注解为类型的联合:
from typing import Union
def scan_list(elements: list[Union[str | int]]) -> None:
for el in elements:
if isinstance(el, str):
print(f"I got a string! ({el})")
elif isinstance(el, int):
print(f"I got an int! ({el})")
else:
# NOTE: we don't reach here because of mypy!
raise ValueError(f"Unexpected element type {el}")
scan_list([1, "hello", "world", 100])
类似于 Python 3.9 中的简化,Python 3.10(具体是PEP 604)引入了使用逻辑或操作符(|
)的Union
类型的缩写表示法:
def scan_list(elements: list[str | int]) -> None:
for el in elements:
if isinstance(el, str):
print(f"I got a string! ({el})")
elif isinstance(el, int):
print(f"I got an int! ({el})")
else:
# NOTE: we don't reach here because of mypy!
raise ValueError(f"Unexpected element type {el}")
scan_list([1, "hello", "world", 100])
其他 mypy 关键字
在本节中,我们将介绍更多基本类型和关键字。
无
None
,就像在“正常”Python 中一样,表示一个None
值——最常用于注解没有返回类型的函数:
def print_foo() -> None:
print("Foo")
print_foo()
可选
我们经常会遇到这样一种情况:我们想根据是否传递了参数值来实现分支代码——通常,我们使用None
来表示其缺失。为此,我们可以使用typing.Optional[X]
——它正是表示这一点:它注解类型X
,但也允许None
:
from typing import Optional
def square_number(x: Optional[int]) -> Optional[int]:
return x**2 if x is not None else None
print(square_number(14))
继 Python 3.10 及以上版本引入的 PEP 604,Optional
可以再次缩短为 X | None
:
def square_number(x: int | None) -> int | None:
return x**2 if x is not None else None
print(square_number(14))
请注意,这与必需或可选参数不对应,这一点经常被混淆!可选参数是指在调用函数时不必指定的参数——而 mypy 的 Optional
表示一个可以是某种类型,但也可以是 None
的参数。可能的混淆来源是可选参数的一个常见默认值是 None
。
任意
Any
,正如其名所示(我觉得我总是在重复这句话……)简单地允许任何类型——从而有效地关闭任何类型检查。因此,尽量避免在可能的情况下使用它。
from typing import Any
def print_everything(val: Any) -> None:
print(val)
print_everything(0)
print_everything(True)
print_everything("hello")
注解变量
到目前为止,我们仅使用 mypy 注解了函数参数和返回类型。自然地,这也可以扩展到任何类型的变量:
int_var: int = 0
float_var: float = 1.5
str_var: str = "hello"
然而,这种用法相对较少(并且严格版的 mypy 也不强制执行),因为变量的类型通常可以从上下文中推断出来。通常情况下,只有在代码相对模糊且难以阅读时,你才会这么做。
注解复杂类型
在本节中,我们将讨论类的注解,以及使用你自己的和其他复杂类进行注解。
注解类
注解类可以很快处理:只需像注解其他函数一样注解类函数,但不要注解构造函数中的 self 参数:
class SampleClass:
def __init__(self, x: int) -> None:
self.x = x
def get_x(self) -> int:
return self.x
sample_class = SampleClass(5)
print(sample_class.get_x())
使用自定义/复杂类进行注解
定义好类之后,我们现在可以像其他类型注解一样使用其名称:
sample_class: SampleClass = SampleClass(5)
事实上,mypy 可以直接与大多数类和类型一起使用,例如:
import pathlib
def read_file(path: pathlib.Path) -> str:
with open(path, "r") as file:
return file.read()
print(read_file(pathlib.Path("mypy.ini")))
修复 mypy 问题
在本节中,我们将看到如何处理不支持类型标注的外部库,并有选择地禁用对某些引发问题的代码行的类型检查——基于一个稍微复杂一点的示例,其中涉及到numpy和matplotlib。
让我们从代码的第一个版本开始:
import matplotlib.pyplot as plt
import numpy as np
import numpy.typing as npt
def calc_np_sin(x: npt.NDArray[np.float32]) -> npt.NDArray[np.float32]:
return np.sin(x)
x = np.linspace(0, 10, 100)
y = calc_np_sin(x)
plt.plot(x, y)
plt.savefig("plot.png")
我们定义了一个简单的函数来计算 numpy 数组的正弦,并将其应用于输入值x
,这些值覆盖了区间[0, 10]。然后,我们使用matplotlib
绘制正弦曲线。
在这段代码中,我们还看到使用numpy.typing
进行 numpy 数组的正确类型标注。
然而,如果我们在此运行 mypy,会出现两个错误。第一个是:
error: Returning Any from function declared to return “ndarray[Any, dtype[floating[_32Bit]]]”
这是 mypy 中一种相对常见的模式。我们实际上没有做错什么,但 mypy 希望它更为明确——在这里——以及在其他情况中——我们必须“强制”mypy 接受我们的代码。例如,我们可以通过引入一个正确类型的代理变量来做到这一点:
def calc_np_sin(x: npt.NDArray[np.float32]) -> npt.NDArray[np.float32]:
y: npt.NDArray[np.float32] = np.sin(x)
return y
下一个错误是:
error: Skipping analyzing “matplotlib”: found module but no type hints or library stubs
这是因为matplotlib
尚未进行类型标注。因此,我们需要让 mypy 知道排除它的检查。我们通过在 mypy.ini 文件中添加以下内容来实现:
[mypy-matplotlib.*]
ignore_missing_imports = True
ignore_errors = True
最后,请注意,你还可以通过在代码行末添加# type: ignore
来有选择地忽略任何代码行。如果确实存在无法解决的 mypy 问题,或者你想要忽略一些已知但不相关的警告/错误,可以这样做。我们也可以通过这样做来隐藏上面的第一个错误:
def calc_np_sin(x: npt.NDArray[np.float32]) -> npt.NDArray[np.float32]:
return np.sin(x) # type: ignore
结论
在这篇文章中,我们介绍了 mypy,它是一个用于 Python 的静态类型检查工具。使用 mypy,我们可以(并且应该)注释变量、参数和返回值的类型——为我们提供了一种在编译时对程序进行合理性检查的方法。mypy 被广泛使用,推荐用于任何中型以上的软件项目。
我们从安装和配置 mypy 开始。然后,我们介绍了如何注释基本和复杂类型,例如列表、字典或集合。接下来,我们讨论了其他重要的注释器,如Union
、Optional
、None
或Any
。最终,我们展示了 mypy 支持广泛的复杂类型,如自定义类。我们通过展示如何调试和修复 mypy 错误来结束教程。
这就是关于 mypy 的内容——希望你喜欢这篇文章,感谢阅读!
使用 SciPy 的优化约束介绍
原文:
towardsdatascience.com/introduction-to-optimization-constraints-with-scipy-7abd44f6de25
探索边界、线性和非线性约束,并结合实际 Python 示例
·发表于 Towards Data Science ·8 分钟阅读·2023 年 1 月 31 日
–
图片由作者提供。
目录
-
介绍
-
实现
2.1 无约束优化
2.2 边界
2.3 线性约束
2.4 非线性约束
2.5 同时应用不同约束类型
-
结论
1. 介绍
优化是从一组潜在候选元素中选择最佳元素以达到特定目标的过程。
我们在日常生活中执行许多优化任务:寻找最短或最快的到达目的地路线,准备按优先级排序的待办事项清单,购买杂货。
我们可以通过定义目标函数**f(x)**
来描述此类问题。
让我们假设我们正在组织一次去另一座城市的旅行,并尝试评估一个合适的出发时间。在这个例子中,目标函数f(x)
是出发时间x
作为旅行持续时间的函数。
我们可以将优化问题表述为目标函数的最小值或最大值的确定。在我们的例子中,我们想要确定一个出发时间,以最小化旅行的持续时间:
在其他情况下,我们可能希望最大化f(x)
。例如,当目标表示某种可能性或投资回报时。然而,最大化一个函数等同于最小化其负值。因此,人们可以仅关注最小化问题:
在实际应用中,我们可能需要对优化问题应用约束。例如,我们可能想找到最快的路线,但我们不愿意支付通行费或夜间旅行。我们将受约束优化定义为在一些逻辑条件下最小化目标函数,这些条件可能反映:
-
现实世界的限制;
-
输入变量的物理意义;
-
上下文情况。
在这篇文章中,我们分享了一个使用 [SciPy](https://scipy.org/)
的优化示例,这是一款流行的科学计算 Python 库。特别地,我们探索最常见的约束类型:界限、线性约束和非线性约束。
2. 实现
2.1 无约束优化
我们从一个简单的无约束优化问题开始,然后在输入变量上添加约束。
导入所需的库:
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import minimize, Bounds, LinearConstraint, NonlinearConstraint
设想以下多变量目标函数:
关于x₀
和x₁
的梯度为
def f(x):
'''Objective function'''
return 0.5*x[0]**2 + 2.5*x[1]**2 + 4 * x[0] * np.sin(np.pi * x[0]) + 5
def df(x):
'''Gradient of the objective function'''
return np.array([x[0] + 4 * np.sin(np.pi * x[0]) + 4 * np.pi * x[0] * np.cos(np.pi * x[0]), 5*x[1]])
让我们生成数据并观察x₀, x₁ ∈ [-1, 1]
的函数值:
# Generate data
X0, X1 = np.meshgrid(np.linspace(-1, 1, 100), np.linspace(-1, 1, 100))
Z = f(np.stack([X0, X1]))
# Plot
fig = plt.figure(figsize=(30, 20))
# First subplot
ax = fig.add_subplot(1, 3, 1, projection='3d')
ax.contour3D(X0, X1, Z, 70, cmap='plasma')
ax.set_xlabel('$x_{0}$')
ax.set_ylabel('$x_{1}$')
ax.set_zlabel('$f(x)$')
# Second subplot
ax = fig.add_subplot(1, 3, 2, projection='3d')
ax.contour3D(X0, X1, Z, 70, cmap='plasma')
ax.set_xlabel('$x_{0}$')
ax.set_ylabel('$x_{1}$')
ax.set_zlabel('$f(x)$')
ax.axes.yaxis.set_ticklabels([])
ax.view_init(0, 80)
# Third subplot
ax = fig.add_subplot(1, 3, 3, projection='3d')
ax.contour3D(X0, X1, Z, 70, cmap='plasma')
ax.set_xlabel('$x_{0}$')
ax.set_ylabel('$x_{1}$')
ax.set_zlabel('$f(x)$')
ax.axes.zaxis.set_ticklabels([])
ax.view_init(89, -90);
目标函数。图片由作者提供。
在我们的示例中,目标函数是非凸的,并且具有多个最小值。
这意味着,根据起点不同,问题可能会收敛到不同的最小化器。
我们可以利用有用的 [scipy.optimize.minimize](https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html)
函数来解决优化问题,具体如下:
# Starting point
x_start = np.array([0.5, 0.5])
# Optimization
result = minimize(
f, x_start, method='trust-constr', jac=df)
result.x.round(3)
值得注意的是,我们应用了 trust-constr
方法。它允许对受约束的函数进行优化。有关该方法的更多信息,请参见 包文档 和 “Trust-region methods” (Conn, Gould and Toint; 2000)。
上述代码片段返回找到的最小化器:
array([-0., 0.])
让我们绘制它:
# Minimum from unconstrained optimization
min_x0, min_x1 = np.meshgrid(result.x[0], result.x[1])
min_z = f(np.stack([min_x0, min_x1]))
# Plot
fig = plt.figure(figsize=(30, 20))
# First subplot
ax = fig.add_subplot(1, 3, 1, projection='3d')
ax.contour3D(X0, X1, Z, 40, cmap='plasma')
ax.scatter(min_x0, min_x1, min_z, marker='o', color='red', linewidth=10)
ax.set_xlabel('$x_{0}$')
ax.set_ylabel('$x_{1}$')
ax.set_zlabel('$f(x)$')
# Second subplot
ax = fig.add_subplot(1, 3, 2, projection='3d')
ax.contour3D(X0, X1, Z, 40, cmap='plasma')
ax.scatter(min_x0, min_x1, min_z, marker='o', color='red', linewidth=10)
ax.set_xlabel('$x_{0}$')
ax.set_ylabel('$x_{1}$')
ax.set_zlabel('$f(x)$')
ax.axes.yaxis.set_ticklabels([])
ax.view_init(0, 80)
# Third subplot
ax = fig.add_subplot(1, 3, 3, projection='3d')
ax.contour3D(X0, X1, Z, 40, cmap='plasma')
ax.scatter(min_x0, min_x1, min_z, marker='o', color='red', linewidth=10)
ax.set_xlabel('$x_{0}$')
ax.set_ylabel('$x_{1}$')
ax.set_zlabel('$f(x)$')
ax.axes.zaxis.set_ticklabels([])
ax.view_init(89, -90);
目标函数及其最小化器。图片由作者提供。
我们现在可以尝试添加约束。
2.2 界限
让我们考虑之前的示例,即在两个城市之间找到最快的旅行路线,出发时间作为输入变量。我们可能会根据一天中的时间预期找到更多或更少的交通。通过以最小化旅行时间为目标,模型也可能建议,例如,夜间旅行。
尽管这可能会导致最短的旅行时间,但我们可能更喜欢白天旅行。因此,我们可以要求模型在早上 7:00 到下午 6:00 之间的出发时间范围内找到最短的旅行时间。
这时界限就派上用场了。界限只是对输入变量的等式或不等式约束。它们允许仅在指定范围之间评估目标函数。
在我们的情况下,假设x₀
和x₁
有以下可接受的值:
我们可以轻松地将这些值传递给[Bounds](https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.Bounds.html)
对象,并进行新的优化实验,如下所示:
lim = [0.25, 0.30, 0.75, 0.8]
bounds = Bounds([lim[0], lim[1]], # [min x0, min x1]
[lim[2], lim[3]]) # [max x0, max x1]
result = minimize(
f, x_start, method='trust-constr', jac=df, bounds=bounds)
result.x.round(3)
优化任务现在导致了一个不同的解决方案,因为之前的点array([0 , 0])
不在可行区域内:
array([0.25, 0.301])
我们可以最终绘制新的最小值和可行区域,并观察f(x)
被评估的区域:
# Feasible region (bounds)
X0_bound, X1_bound = np.meshgrid(np.linspace(lim[0], lim[2], 20), np.linspace(lim[1], lim[3], 20))
Z_bound = f(np.stack([X0_bound, X1_bound]))
# New minimum within bounds
min_x0_bounds, min_x1_bounds = np.meshgrid(result.x[0], result.x[1])
min_z_bounds = f(np.stack([min_x0_bounds, min_x0_bounds]))
# Plot
fig = plt.figure(figsize=(30, 20))
# First subplot
ax = fig.add_subplot(1, 3, 1, projection='3d')
ax.contour3D(X0, X1, Z, 40, cmap='plasma')
ax.scatter(min_x0, min_x1, min_z, marker='o', color='red', linewidth=10)
ax.scatter(min_x0_bounds, min_x1_bounds, min_z_bounds, marker='o', color='blue', linewidth=10)
ax.plot_surface(X0_bound, X1_bound, Z_bound, color='black', alpha=0.6)
ax.set_xlabel('$x_{0}$')
ax.set_ylabel('$x_{1}$')
ax.set_zlabel('$f(x)$')
# Second subplot
ax = fig.add_subplot(1, 3, 2, projection='3d')
ax.contour3D(X0, X1, Z, 40, cmap='plasma')
ax.scatter(min_x0, min_x1, min_z, marker='o', color='red', linewidth=10)
ax.scatter(min_x0_bounds, min_x1_bounds, min_z_bounds, marker='o', color='blue', linewidth=10)
ax.plot_surface(X0_bound, X1_bound, Z_bound, color='black', alpha=0.6)
ax.set_xlabel('$x_{0}$')
ax.set_ylabel('$x_{1}$')
ax.set_zlabel('$f(x)$')
ax.axes.yaxis.set_ticklabels([])
ax.view_init(0, 80)
# Third subplot
ax = fig.add_subplot(1, 3, 3, projection='3d')
ax.contour3D(X0, X1, Z, 40, cmap='plasma')
ax.scatter(min_x0, min_x1, min_z, marker='o', color='red', linewidth=10)
ax.scatter(min_x0_bounds, min_x1_bounds, min_z_bounds, marker='o', color='blue', linewidth=10)
ax.plot_surface(X0_bound, X1_bound, Z_bound, color='black', alpha=0.6)
ax.set_xlabel('$x_{0}$')
ax.set_ylabel('$x_{1}$')
ax.set_zlabel('$f(x)$')
ax.axes.zaxis.set_ticklabels([])
ax.view_init(89, -90);
目标函数的“无约束”最小值(红点)和应用边界后的最小值(蓝点)。应用的变量范围标识了灰色区域(可行区域)。
2.3 线性约束
线性约束定义了优化变量之间的线性关系。例如,假设x₀
和x₁
受到以下约束:
我们可以轻松地将这些条件重写为线性系统,并在运行优化任务之前将其传递给[LinearConstraint](https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.LinearConstraint.html)
对象:
线性约束。图片由作者提供。
linear_constraint = LinearConstraint(
[[1, 0], [1, -1]], [0.25, -np.inf], [np.inf, 0.1])
result = minimize(
f, x_start, method='trust-constr', jac=df, constraints=linear_constraint)
result.x.round(3)
新解决方案是
array([0.25, 0.15])
f(x)
的可行区域对应于由超平面交集限定的空间的一部分。让我们绘制这些边界:
# Linear constraints: first hyperplane
X0_lin_1 = np.repeat(0.25, 20)
X1_lin_1, Z_lin_1 = np.meshgrid(np.linspace(-1, 1, 20), np.linspace(4, 10, 20))
# Linear constraints: second hyperplane
X1_lin_2 = np.linspace(-1, 1, 20)
X0_lin_2 = X1_lin_2 + 0.1
# New minimum with linear constraints
min_x0_lin_constr, min_x1_lin_constr = np.meshgrid(result.x[0], result.x[1])
min_z_lin_constr = f(np.stack([min_x0_lin_constr, min_x0_lin_constr]))
# Plot
fig = plt.figure(figsize=(30, 20))
# First subplot
ax = fig.add_subplot(1, 3, 1, projection='3d')
ax.contour3D(X0, X1, Z, 40, cmap='plasma')
ax.scatter(min_x0, min_x1, min_z, marker='o', color='red', linewidth=10)
ax.scatter(min_x0_lin_constr, min_x1_lin_constr, min_z_lin_constr, marker='o', color='blue', linewidth=10)
ax.plot_surface(X0_lin_1, X1_lin_1, Z_lin_1, color='green', alpha=0.3)
ax.plot_surface(X0_lin_2, X1_lin_2, Z_lin_1, color='yellow', alpha=0.2)
ax.set_xlabel('$x_{0}$')
ax.set_ylabel('$x_{1}$')
ax.set_zlabel('$f(x)$')
# Second subplot
ax = fig.add_subplot(1, 3, 2, projection='3d')
ax.contour3D(X0, X1, Z, 40, cmap='plasma')
ax.scatter(min_x0, min_x1, min_z, marker='o', color='red', linewidth=10)
ax.scatter(min_x0_lin_constr, min_x1_lin_constr, min_z_lin_constr, marker='o', color='blue', linewidth=10)
ax.plot_surface(X0_lin_1, X1_lin_1, Z_lin_1, color='green', alpha=0.2)
ax.plot_surface(X0_lin_2, X1_lin_2, Z_lin_1, color='yellow', alpha=0.2)
ax.set_xlabel('$x_{0}$')
ax.set_ylabel('$x_{1}$')
ax.set_zlabel('$f(x)$')
ax.axes.yaxis.set_ticklabels([])
ax.view_init(0, 80)
# Third subplot
ax = fig.add_subplot(1, 3, 3, projection='3d')
ax.contour3D(X0, X1, Z, 40, cmap='plasma')
ax.scatter(min_x0, min_x1, min_z, marker='o', color='red', linewidth=10)
ax.scatter(min_x0_lin_constr, min_x1_lin_constr, min_z_lin_constr, marker='o', color='blue', linewidth=10)
ax.plot_surface(X0_lin_1, X1_lin_1, Z_lin_1, color='green', alpha=1)
ax.plot_surface(X0_lin_2, X1_lin_2, Z_lin_1, color='yellow', alpha=1)
ax.set_xlabel('$x_{0}$')
ax.set_ylabel('$x_{1}$')
ax.set_zlabel('$f(x)$')
ax.axes.zaxis.set_ticklabels([])
ax.view_init(89, -90);
目标函数。蓝点是通过应用线性约束确定的最小值,其边界显示为超平面。图片由作者提供。
2.4 非线性约束
我们还可以使用[NonlinearConstraint](https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.NonlinearConstraint.html)
对象在非线性约束定义的区域内探索目标函数。假设x₀
和x₁
受到以下约束:
我们优化f(x)
的方法如下:
non_linear_eq= lambda x: x[0]**2 + x[1]**2
non_linear_constr = NonlinearConstraint(
non_linear_eq, 0.2, np.inf)
result = minimize(
f, np.array([0.5, 1]), method='trust-constr', jac=df, constraints=non_linear_constr)
result.x.round(3)
array([-0., 0.447])
类似于之前的示例,我们可以观察到在当前约束下的目标函数和找到的最小值。
可行区域位于由非线性约束定义的圆柱体外部。图片由作者提供。
2.5 结合不同的约束类型
我们可以结合边界以及线性和非线性约束,如下所示:
result = minimize(
f,
x_start,
method='trust-constr',
jac=df,
bounds=bounds,
constraints=[linear_constraint, non_linear_constr]
)
result.x.round(3)
array([0.25, 0.371])
我们指出,并非所有优化方法都支持边界和/或约束。更多信息可以在包文档中找到。
3. 结论
在这篇文章中,我们探索了不同类型的优化约束。特别是,我们分享了使用SciPy
库的实际 Python 示例。这些示例配有图表,可以直观地检查不同的约束。
相关帖子:
在 Python 中寻找最大化对数部分似然的系数
[towardsdatascience.com
介绍 p 值和带有示例的显著性测试
通过示例理解假设检验框架背后的思想
·
关注 发表于 走向数据科学 ·9 min read·2023 年 1 月 18 日
–
引言
伟大的产品不是一夜之间建成的,而是通过多年的迭代不断改进和完善。最成功的团队在开发产品时遵循反馈循环。首先,他们构思一个想法,推向生产环境,并监控这个过程。然后,根据收集的数据,他们分析并确定是否成功。分析获得的见解将指导下一轮的开发。Keras 的创作者弗朗索瓦·肖莱 称之为进展之环[2]。
进步的循环。图片由作者提供。
统计学在分析部分的作用至关重要。它帮助我们通过观察监测数据来检验假设并做出决定。现在,有各种针对不同场景的假设检验方法,但在这篇文章中,我们将通过一个简单的例子来了解假设检验框架的思想和过程。
现实世界中的选择往往并不明确
由 Edge2Edge Media 拍摄,刊登在 Unsplash
在统计学中,我们将选择称为假设。现实世界中的假设往往不清晰。让我来举例说明。考虑两个场景:
-
在场景一中,你得到一个骰子,要求你确定这个骰子是公平的还是有作弊的。
-
在场景二中,你得到两个骰子:一个公平的,一个作弊的。然而,这次你知道作弊骰子的概率分布。如果你随机挑选一个骰子并掷它,你能根据骰子落地的面来确定你挑选的是哪个骰子吗?
这两种场景都是假设检验问题的一种形式。然而,区别在于我们知道后者的备择假设是什么。
场景二更容易处理。有一些明确的方法,比如似然比检验,可以帮助我们根据单次观察做出决定。
然而,大多数现实世界的情况类似于场景一。
在场景一中,我们不知道备择假设是什么。我们只是假设备择假设是骰子有作弊。我们不知道它作弊的程度。因此,假设检验的最终目标就是证明或否定原假设,在这种情况下就是假设骰子是公平的。
这种形式的假设检验也被称为显著性检验,是本文的重点。
如果你想了解如何处理第一类问题,可以考虑阅读我的另一篇文章,其中我们从基本原理学习假设检验。
一份关于假设检验的易懂指南,配有示例和可视化
[towardsdatascience.com
示例——硬币是公平的还是有偏的?
让我们从一个例子开始,并逐步深入。
由 Jizhidexiaohailang 拍摄,刊登在 Unsplash
你得到了一枚硬币,需要判断它是公平的还是有偏的。
让𝜃表示正面朝上的概率。零假设是𝜃 = 1/2,而替代假设是𝜃 ≠ 1/2。
注意到替代假设不明确。𝜃可以是 0 到 1 之间的任何值。
假设检验的关键思想是实验结果依赖于假设。因此,通过观察结果,我们可以得出结论。
在这个例子中,我们投掷硬币n次并观察结果。
让结果用X1, X2, X3, …, Xn
表示,其中Xi
是一个随机变量,表示第i
次投掷的结果。如果硬币落在正面,Xi = 1
,否则Xi = 0
。
拒绝区域和临界比率
让S = X1 + X2 + X3 + … + Xn
表示正面朝上的总数。S被称为统计量,它本质上总结了观察结果。现在,如果零假设成立且硬币是公平的,我们更可能观察到n/2 个正面。如果不是,那么|S — n/2|
的差异会很大。所以一个合理的决策规则是:
reject the null hypothesis if |S - n/2| > 𝜉
这意味着如果观察到的正面数与n/2 之间的绝对差异大于某个值𝜉,我们将拒绝零假设或假设硬币是公平的。𝜉被称为临界比率。
基于这个决策规则,我们可以将观察空间分为两个区域:拒绝区域和接受区域。对于满足决策规则|S — n/2| > 𝜉
的S
值属于拒绝区域,而其余值属于接受区域。
作者提供的图片
区域之间的边界依赖于𝜉。让我们看看如何选择它的值。
显著性水平
在零假设下,观察结果落入拒绝区域的概率称为显著性水平。它可以计算为:
P(reject H0 ; H0) = 𝛼
H0
是零假设。P(reject H0 ; H0)
表示在H0
下观察结果落入拒绝区域的概率。𝛼是显著性水平。
在我们的例子中,它可以表示为:
P(|S - n/2| > 𝜉 ; H0) = 𝛼
现在,𝛼依赖于𝜉。随着𝜉的增加,拒绝区域的大小减少,𝛼也会随之减少。
如果我们固定了𝛼的值,我们可以找到𝜉的值。如果我们知道在零假设下决策规则的分布,我们就可以为给定的𝛼值找到𝜉的值。
让我们运行一个模拟,看看它的实际效果。
通过模拟理解
在我们的例子中,S遵循参数为n和𝜃的二项分布。然而,随着n的增加,S根据中心极限定理接近正态分布[4]。
随着 n 的增加,二项分布趋于正态分布。
所以假设S遵循正态分布,我们可以通过减去均值并除以标准差来标准化它。二项分布的均值为n * 𝜃
,而标准差为sqrt(n * 𝜃 * (1-𝜃))
。
def standardize(S):
return (S - n * 𝜃) / sqrt(n * 𝜃 * (1-𝜃))
假设我们抛掷硬币n=1000
次,并且根据原假设,硬币是公平的,所以𝜃=1/2
。对于显著性水平𝛼=0.05
,𝜉的值可以如下计算:
P(|S - n𝜃| > 𝜉 ; H0) = 𝛼
Divide the inequality by sqrt(n * 𝜃 * (1-𝜃)) which is the std of S
=> P(|S - n𝜃| / sqrt(n * 𝜃 * (1-𝜃)) > 𝜉 / sqrt(n * 𝜃 * (1-𝜃)) ; H0) = 𝛼
Let Z = |S - n𝜃| / sqrt(n * 𝜃 * (1-𝜃))
Z is a standard normal variable as discussed above
=> P(|Z| > 𝜉 / sqrt(n * 𝜃 * (1-𝜃)) ; H0) = 𝛼
=> P(|Z| > 𝜉 / sqrt(1000 * 1/2 * (1-1/2)) = 0.05
=> P(|Z| > 𝜉 / sqrt(250)) = 0.05
According to the standard normal tables, P(Z) = 0.05 if |Z| > 1.96
This is illustrated in the diagram below.
=> Z > 1.96 or Z < -1.96
The minimum value of 𝜉 can be calculated as:
𝜉 / sqrt(250) = 1.96
=> 𝜉 = 31
标准正态分布
所以对于显著性水平𝛼 = 0.05
,临界比率是𝜉 = 31
。这意味着什么?我们的决策规则是:
reject the null hypothesis if |S - n/2| > 𝜉
如果我进行实验并抛掷硬币n=1000
次,观察到正面次数S=472
,则|472–500| = 28 < 31
。
所以我们说H₀在 5%的显著性水平下未被拒绝。
注意我们说H₀未被拒绝而不是说H₀被接受。这是因为我们没有足够强的依据来接受 H₀。根据数据无法证明𝜃确切等于 0.5。𝜃也可能等于 0.51 或 0.49。因此,我们说观察值S=472
不足以在 5%的显著性水平下驳斥原假设 H₀。
在𝜉 = 31
的情况下,观察值S < 469
和S > 531
落入拒绝区域。此外,如果我增加𝜉的值,拒绝区域会如下面的图所示减小:
拒绝区域随着𝜉
的增加而减小。
当我们固定显著性水平𝛼,例如 5%时,这意味着在 H₀支配的模型下,观察值仅有 5%的概率落入拒绝区域。因此,如果观察值最终落入拒绝区域,则提供了 H₀可能错误的强有力证据。
p 值
p 值的正式定义为:
p-值是在假设原假设正确的前提下,获得至少与实际观察结果一样极端的测试结果的概率。 — 维基百科 [3]
与在实验前固定的显著性水平不同,p 值依赖于实验结果。
假设硬币抛掷实验的结果是S=430
,那么在假设原假设正确的前提下,“至少同样极端”的结果是S < 430
和S > 570
,因为在原假设下的分布是对称的并且围绕均值中心。
我通过改变𝜃模拟了结果s,每个结果的 p 值如下所示:
基于𝜃变化的 p 值
实质上,p 值是𝛼的值,使得s恰好处于拒绝与不拒绝的临界值之间。
正态近似的必要性
在上述示例中,我们知道结果遵循具有参数n和𝜃的二项分布。因此,我们可以直接使用任何统计库基于𝛼计算临界比率𝜉,而无需使用正态近似。
然而,在大多数情况下,我们不会知道零假设下的分布。然而,大多数情况下,随着样本量的增加,它们趋向于近似正态分布。因此,在假设检验实验中使用大样本量是很重要的。
结论
在科学中,科学家通常提出一个理论,其他科学家通过进行实验来证明或反驳它。他们进行假设检验,其中原始想法是零假设。
假设检验的一般框架可以总结如下[1]:
-
选择一个代表观察数据的统计量 S。它是一个取决于观察结果的标量随机变量。通常,样本均值或样本方差被用作统计量。
-
制定一个拒绝零假设 H₀的决策规则。决策规则是统计量 S 和临界比率𝜉的函数。根据决策规则和𝜉,可以将观察空间划分为拒绝区域和接受区域。
-
选择显著性水平𝛼,它是观察结果在零假设下落入拒绝区域的概率。
-
基于显著性水平𝛼计算临界比率𝜉。为了进行此计算,需要了解零假设下的分布。然而,正如我们讨论的,如果样本量较大,大多数情况下可以将其近似为正态分布。一旦知道了𝜉的值,就可以确定拒绝区域。
一旦我们进行实验并记录了观察结果,就需要做以下事情:
-
计算统计量 S的值s*。
-
如果s属于拒绝区域,则拒绝零假设。
假设检验的美妙之处在于没有任何约束。我们可以自由设计实验,并选择零假设和备择假设。
希望你喜欢这篇文章。
**Let's Connect** Hope you've enjoyed the article. Please clap and follow if you did.
You can also reach out to me on [LinkedIn](https://www.linkedin.com/in/neerajkrishnadev/) and [Twitter](https://twitter.com/WingedRasengan).
图像和图示来源
本文中的所有图像、图形和示意图均由作者创建;除非在说明中明确提及。
参考文献
使用 Sklearn、Pandas 和 Matplotlib 进行 PCA 的介绍
原文:
towardsdatascience.com/introduction-to-pca-in-python-with-sklearn-pandas-and-matplotlib-476880f30238
通过将多维数据集转换为任意数量的维度,并使用 Matplotlib 可视化降维数据,来学习 PCA 在 Python 和 Sklearn 中的直观感受
·发表于面向数据科学 ·13 分钟阅读·2023 年 9 月 6 日
–
图片由Nivenn Lanos提供,来源于Unsplash
作为数据分析师和科学家,我们经常面临由于信息量不断增长而带来的复杂挑战。
不可否认,从各种来源积累的数据已经成为我们生活中的常态。不管是否是数据科学家,几乎每个人都将现象描述为一组变量或属性。
在解决分析挑战时,处理多维数据集是非常罕见的——这在今天尤其明显,因为数据收集越来越自动化,技术使我们能够从各种来源获取信息,包括传感器、物联网设备、社交媒体、在线交易等等。
但随着现象的复杂性增加,数据科学家面临的挑战也会增加,以实现他们的目标。
这些挑战可能包括…
-
高维度:拥有许多列可能导致高维度问题,这会使模型变得更加复杂且难以解释。
-
噪声数据:数据的自动收集可能会导致错误、缺失数据或不可靠的数据。
-
解释:高维度意味着低可解释性——很难理解某个问题的最有影响力的特征是什么。
-
过拟合:过于复杂的模型可能会遭遇过拟合,即对训练数据的过度适应,导致在新数据上的泛化能力低。
-
计算资源:分析大型复杂数据集通常需要大量计算资源。可扩展性是一个重要考虑因素。
-
结果的沟通:从多维数据集中得到的发现以易于理解的方式进行解释是一项重要挑战,尤其是在与非技术利益相关者沟通时。
我写了一篇与此话题相关的文章,你可以在这里阅读
特征工程的活动对于提升预测模型的性能非常有用。然而,它…
towardsdatascience.com
数据科学和机器学习中的多维性
在数据科学和机器学习中,多维数据集是一个组织化的数据集合,包括多个列或属性,每个列或属性表示现象研究对象的一个特征(或属性)。
包含有关房屋信息的数据集是一个具体的多维数据集的例子。事实上,每栋房屋可以用其平方米、房间数量、是否有车库等特征来描述。
在这篇文章中,我们将探讨如何有效地使用 PCA 来简化和可视化多维数据,使复杂的多维信息变得易于理解。
通过遵循本指南,你将学习:
-
PCA 算法背后的直觉
-
在玩具数据集上应用 Sklearn 的 PCA
-
使用 Matplotlib 来 可视化减少的数据
-
PCA 在数据科学中的主要用例
让我们开始吧!
PCA 算法的基本直觉
主成分分析(PCA)是一种无监督统计技术,用于多维数据的分解。
其主要目的是将我们的多维数据集减少为若干个任意变量,以便于
-
选择原始数据集中重要的特征
-
提高信噪比
-
创建新的特征以提供给机器学习模型
-
可视化多维数据
根据我们选择的组件数量,PCA 算法通过保留那些最能解释数据集总方差的变量,实现对原始数据集中变量数量的减少。PCA 对抗着那个臭名昭著的维度灾难。
维度灾难是机器学习中的一个概念,指的是处理高维数据时遇到的困难。
随着尺寸的增加,为了可靠地表示一组数据所需的数据量呈指数增长。这可能使得在数据中发现有趣的模式变得困难,并且可能在自动学习模型中导致过拟合问题。
PCA 应用的转换通过创建最能解释原始数据方差的组件来减少数据集的大小。这使得可以隔离最相关的变量,并减少数据集的复杂性。
PCA 通常是一项难以理解的技术,特别是对于数据科学和分析领域的新手。
这种困难的原因必须追溯到算法的严格数学基础。
那么,从数学角度看,PCA 做了什么?
PCA 允许我们将一个 n 维数据集投影到一个低维平面上。
看起来很复杂,但实际上并不是。让我们用一个简单的例子试试:
当我们在纸上画东西时,我们实际上是在将一个心理表征(我们可以用三维表示)投影到纸上。这样做会降低表征的质量和精确度。
然而,纸上的表征仍然可以理解,甚至可以与我们的同龄人分享。
实际上,在绘制过程中,我们表示形式、线条和阴影,以便让观察者理解我们在脑海中想到的内容。
让我们以这张鲨鱼的图片为例:
Foto di David Clode su Unsplash
如果我们想在一张纸上绘制它,根据我们的技能水平(正如你所见,我的技能水平很低),我们可以这样表示:
作者提供的图像。
问题在于,尽管表征并不是完美的 1:1,观察者仍然可以很容易理解这幅画代表了一只鲨鱼。
实际上,我们使用的“心理”算法类似于 PCA——我们降低了维度,从而降低了摄影中的鲨鱼特征,并仅使用了最相关的维度来在纸上传达“鲨鱼”的概念。
从数学角度来说,我们不仅仅想把对象投影到一个低维平面上,而且我们还希望尽可能保留最相关的信息。
数据压缩
我们将使用一个简单的数据集进行示例。这个数据集包含了房屋的结构信息,例如平方米大小、房间数量等。
示例数据集。作者提供的图像。
这里的目标是展示在处理多维数据集时,数据可视化的局限性是多么容易接近,以及 PCA 如何帮助我们克服这些局限性。
数据集的维度可以简单地理解为其中列的数量。一列代表我们研究现象的一个属性或特征。维度越多,现象越复杂。
在这种情况下,我们有一个 5 维的数据集。
但数据可视化中存在哪些局限性?让我通过分析平方米变量来解释一下。
图片由作者提供。
房子 1 和 2 的平方米值较低,而其他房子的值都在 100 左右或更高。这是一个一维图,因为我们只考虑了一个变量。
现在让我们为图表添加一个维度。
图片由作者提供。
这种图表被称为散点图,显示了两个变量之间的关系。它非常有助于可视化变量之间的相关性和交互作用。
这种可视化已经开始引入较高的解释复杂性,因为即使是专家分析师也需要仔细检查才能理解变量之间的关系。
现在让我们再插入一个变量。
图片由作者提供。
这绝对是一个复杂的图像处理问题。然而,从数学角度来看,这种可视化是完全合理的。从感知和解释的角度来看,我们已经达到了人类理解的极限。
我们都知道,我们对世界的解释停留在三维。然而,我们也知道这个数据集具有 5 个维度。
但我们如何查看所有这些?
我们不能,除非我们将所有变量之间的二维关系并排可视化。
在下面的例子中,我们可以看到平方米与n_rooms和n_neighbors在两个维度上的关系。
图片由作者提供。
现在让我们想象将所有可能的组合放在一起……我们很快会被需要记住的大量信息所淹没。
这就是 PCA 发挥作用的地方。使用 Python(稍后会看到),我们可以对这个数据集应用 Sklearn 的 PCA 类,并获得这样的图表。
图片由作者提供。
我们看到的是一个显示 PCA 返回的主成分的图表。
实际上,PCA 算法对数据执行线性变换,以找到最佳解释数据集总方差的特征线性组合。
这种特征组合被称为主成分。这个过程会对每个主要成分重复,直到达到所需的成分数量。
使用 PCA 的优势在于,它允许我们通过保留最重要的信息来减少数据的维度,消除不相关的数据,使数据更容易可视化和用于构建机器学习模型。
如果你有兴趣深入了解 PCA 背后的数学,我建议以下英文资源:
-
主成分分析 (PCA) 视觉解释,无需数学
Python 实现
要在 Python 中应用 PCA,我们可以使用 scikit-learn,它提供了一个简单有效的实现。
在此链接中您可以阅读 PCA 文档。
我们将使用 wine dataset 作为示例的玩具数据集。wine dataset 是 Scikit-Learn 的一部分,并且在知识共享许可协议下,署名 4.0,免费使用和共享(许可证可在这里查看)。
让我们开始必需的库
# import the required libs
from sklearn.datasets import load_wine
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
# load the dataset
wine = load_wine()
# convert the object in a pandas dataframe
df = pd.DataFrame(data=wine.data, columns=wine.feature_names)
df["target"] = wine.target
df
>>>
图片来源于作者。
数据的维度是 (178, 14) —— 这意味着有 178 行(机器学习模型可以学习的示例),每行由 14 个维度描述。
我们需要在应用 PCA 之前进行数据标准化。您可以使用 Sklearn 完成这一步。
# normalize data
from sklearn.preprocessing import StandardScaler
X_std = StandardScaler().fit_transform(df.drop(columns=["target"]))
使用 PCA 时重要的是对数据进行标准化: 它计算数据集的新投影,新轴基于变量的标准差。
我们现在准备好减少大小。我们可以这样简单地应用 PCA
# PCA object specifying the number of principal components desired
pca = PCA(n_components=2) # we want to project two dimensions so that we can visualize them!
# We fit the PCA model on standardized data
vecs = pca.fit_transform(X_std)
我们可以指定任何数量的 PCA 输出维度,只要它们少于 14,这是原始数据集的总维度。
现在让我们将数据框的小版本组织成一个新的 Pandas Dataframe 对象:
reduced_df = pd.DataFrame(data=vecs, columns=['Principal Component 1', 'Principal Component 2'])
final_df = pd.concat([reduced_df, df[['target']]], axis=1)
final_df
>>>
图片来源于作者。
主成分 1 和 2 是 PCA 的输出维度,现在可以用散点图进行可视化。
plt.figure(figsize=(8, 6)) # set the size of the canvas
targets = list(set(final_df['target'])) # we create a list of possible targets (there are 3)
colors = ['r', 'g', 'b'] # we define a simple list of colors to differentiate the targets
# loop to assign each point to a target and color
for target, color in zip(targets, colors):
idx = final_df['target'] == target
plt.scatter(final_df.loc[idx, 'Principal Component 1'], final_df.loc[idx, 'Principal Component 2'], c=color, s=50)
# finally, we show the graph
plt.legend(targets, title="Target", loc='upper right')
plt.xlabel('Principal Component 1')
plt.ylabel('Principal Component 2')
plt.title('PCA on Wine Dataset')
plt.show()
减少数据集的效果图。图片来源于作者。
就这样。这个图表展示了由 14 个初始变量描述的葡萄酒之间的差异,但通过 PCA 减少到 2 个维度。PCA 保留了相关信息,同时减少了数据集中的噪声。
这是使用 Sklearn、Pandas 和 Matplotlib 在 Python 中应用 PCA 的完整代码。
import pandas as pd
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
wine = load_wine()
df = pd.DataFrame(data=wine.data, columns=wine.feature_names)
df["target"] = wine.target
from sklearn.preprocessing import StandardScaler
X_std = StandardScaler().fit_transform(df.drop(columns=["target"]))
pca = PCA(n_components=2)
vecs = pca.fit_transform(X_std)
reduced_df = pd.DataFrame(data=vecs, columns=['Principal Component 1', 'Principal Component 2'])
final_df = pd.concat([reduced_df, df[['target']]], axis=1)
plt.figure(figsize=(8, 6))
targets = list(set(final_df['target']))
colors = ['r', 'g', 'b']
for target, color in zip(targets, colors):
idx = final_df['target'] == target
plt.scatter(final_df.loc[idx, 'Principal Component 1'], final_df.loc[idx, 'Principal Component 2'], c=color, s=50)
plt.legend(targets, title="Target", loc='upper right')
plt.xlabel('Principal Component 1')
plt.ylabel('Principal Component 2')
plt.title('PCA on the Wine Dataset')
plt.show()
PCA 的应用场景
以下是数据科学中最常见的 PCA 用例列表。
提高机器学习模型训练速度
PCA 压缩的数据提供了重要信息,并且更容易被机器学习模型消化,现在模型基于少量特征进行学习,而不是原始数据集中所有的特征。
特征选择
PCA 本质上是一种特征选择工具。应用 PCA 时,我们寻找那些能最好地解释数据集方差的特征。
我们可以对主成分进行排名,并按重要性进行排序,第一主成分解释最多的方差,最后一个主成分解释最少的方差。
通过分析主成分,可以回到原始特征,并排除那些对保留 PCA 创建的降维平面中的信息没有贡献的特征。
异常检测
PCA 常用于异常识别,因为它可以帮助识别数据中用肉眼难以辨别的模式。
异常通常表现为在较低维空间中远离主要群体的数据点,使它们更容易被检测到。
信号检测
与异常识别相比,PCA 在信号检测中也非常有用。
确实,正如 PCA 可以突出异常值,它也可以去除不贡献于数据总变异性的“噪声”。在语音识别的背景下,这使得用户能够更好地隔离语音痕迹,并改进基于语音的人物识别系统。
图像压缩
如果我们有特定的约束,例如将图像保存为特定格式,那么处理图像可能很昂贵。简而言之,PCA 可以用于压缩图像,同时仍保持其中的信息。
这使得机器学习算法可以更快地训练,但会以压缩信息的某种质量为代价。
结论
感谢您的关注 🙏 我希望你享受阅读并学到了新的知识。
总结
-
你了解了数据集的维度意味着什么以及拥有多个维度所带来的局限性
-
你了解了 PCA 算法如何直观地一步步工作
-
你学会了如何使用 Sklearn 在 Python 中实现 PCA
-
最后,你了解了 PCA 在数据科学中的最常见应用场景
如果你觉得这篇文章有用,请与你的朋友或同事分享。
下次见,
安德鲁
如果你想支持我的内容创作活动,请随意使用下面的推荐链接加入 Medium 的会员计划。我将获得你投资的一部分,你将能够无缝地访问 Medium 上大量的数据科学及其他领域的文章。
[## 使用我的推荐链接加入 Medium - 安德烈亚·达戈斯蒂诺]
作为 Medium 会员,你的会员费用的一部分将用于支持你阅读的作者,而你可以完全访问每一个故事……
medium.com](https://medium.com/@theDrewDag/membership?source=post_page-----476880f30238--------------------------------)
推荐阅读
对感兴趣的人,这里有一份我推荐的关于每个 ML 相关主题的书单。这些书在我看来是必读的,并且对我的职业生涯产生了重大影响。
免责声明:这些是亚马逊的联盟链接。我将从亚马逊获得少量佣金作为推荐这些商品的回报。您的体验不会改变,您也不会多付费用,但这将帮助我扩大业务并制作更多与 AI 相关的内容。
-
机器学习简介: 自信的数据技能:掌握数据处理基础知识,提升职业生涯 作者 基里尔·埃雷门科
-
Sklearn / TensorFlow: 实战机器学习:使用 Scikit-Learn、Keras 和 TensorFlow 作者 奥雷利安·热龙
-
NLP: 文本作为数据:机器学习与社会科学的新框架 作者 贾斯廷·格里默
-
Sklearn / PyTorch: 使用 PyTorch 和 Scikit-Learn 进行机器学习:用 Python 开发机器学习和深度学习模型 作者 塞巴斯蒂安·拉什卡
-
数据可视化: 用数据讲故事:商业专业人士的数据可视化指南 作者 科尔·克纳夫利克
有用的链接(由我撰写)
-
学习如何在 Python 中执行一流的探索性数据分析: Python 中的探索性数据分析——逐步过程
-
学习 TensorFlow 基础知识: 开始使用 TensorFlow 2.0 —— 深度学习入门
-
使用 TF-IDF 在 Python 中进行文本聚类: 在 Python 中使用 TF-IDF 进行文本聚类
PyTorch 介绍
了解 PyTorch 项目的工作流程
·
关注 发表在 Towards Data Science · 13 分钟阅读 · 2023 年 3 月 1 日
–
图片来源:Igor Lepilin 在 Unsplash
在本文中,我们将通过使用 PyTorch 的深度学习项目生命周期。我们假设你已经对神经网络有一定了解,因此不会详细解释它们,只会关注 PyTorch 特有的方面。我们将主要遵循官方文档中展示的步骤,但考虑一个不同的例子。在文档中展示的是图像分类的例子,而这里我们将考虑存储在.csv 文件中的表格数据。这意味着一些更改是必要的,特别是在准备数据集时。拥有两个不同的例子应该有助于更好地理解 PyTorch 项目的一般工作流程。除了这篇文章,你还可以查看包含完整代码和工作流程结构的colab 笔记本,或者在GitHub上找到该笔记本。
数据
使用的数据从kaggle [1]下载,且免费提供。这些数据描述了在城市环境中确定水质所需的不同特征。目标是预测水质是好还是坏。也就是说,我们正在考虑一个二分类问题。总共有 9 个特征(均为数值型)和标签。
给定数据集的特征和标签。
预处理
与每个数据科学项目一样,首先需要进行一些预处理。这与我们使用的深度学习框架无关。因此,我们在这里不详细讨论。有关更多信息,请参见colab 笔记本或GitHub。
我们很幸运,数据不需要太多准备。所有特征都是数值型和浮点型。然而,有一些缺失值,我们用相应特征的均值进行了填补。还需考虑的是,目标变量并不均匀分布,0(差的水质)的数量远多于 1(良好的水质)。为了简化,我们对数据进行了上采样,使得 0 的数量与 1 的数量相等,通过从 1 的子集随机抽样直到达到相同数量的样本。最后,我们将数据集划分为训练集(80%)、验证集(10%)和测试集(10%),并对数据进行了缩放。
创建一个 PyTorch 数据集
为了在 PyTorch 模型中使用我们的数据,我们需要将其转化为特定的形式:PyTorch 数据集。该数据集的构建与模型解耦。数据集对象存储样本及其对应的标签。此时,这个例子略微偏离了 PyTorch 文档页面。文档中使用的示例数据是 FashionMNIST。对于这个(和其他几个)数据集,PyTorch 提供了预加载的数据集。要了解如何加载这些数据集,可以查看他们的 PyTorch 教程。然而,如果你想用 PyTorch 处理自己的数据,你很可能需要编写自己的自定义数据集类。
要创建自定义的数据集类,我们可以从 PyTorch 提供的 Dataset 类继承。我们需要调整以下三个主要方法:
init 方法在实例化数据集对象时运行一次。在这个简单的例子中,仅将输入和标签作为张量存储。
len 方法返回数据集中样本的数量。
getitem 方法加载并返回数据集中给定索引处的样本。
数据集也是进行变换的地方,例如处理图像数据时。在我们的表格数据中,这一点不相关,因此在这里不涉及。对于这里考虑的水质问题,自定义数据集类如下所示:
class WaterDataset(Dataset):
def __init__(self, X, y):
# The __init__ method is run once when instantiating the Dataset object
self.X = torch.tensor(X)
self.y = torch.tensor(np.array(y).astype(float))
def __len__(self):
# The __len__ method returns the number of samples in our dataset.
return len(self.y)
def __getitem__(self, idx):
# The __getitem__ method loads and returns a sample from the dataset
# at the given index idx.
return self.X[idx], self.y[idx]
使用这个类,我们定义训练、验证和测试数据集。
train_dataset = WaterDataset(X_train, y_train)
val_dataset = WaterDataset(X_val, y_val)
test_dataset = WaterDataset(X_test, y_test)
定义 DataLoader
创建数据集后,我们使用 PyTorch 的 DataLoader 将可迭代对象包装起来,以便在训练和验证期间轻松访问数据。数据集一次获取一个样本的特征和标签。在训练模型时,我们通常希望以批次的形式传递样本,并在每个 epoch 重新洗牌数据。在这个例子中,迭代 DataLoader 时,每次迭代返回一个包含 32 个样本的迷你批次。可以进一步配置 DataLoader。有关所有可能的配置,请参阅 文档。
train_dataloader = DataLoader(train_dataset, batch_size=32, shuffle=True)
val_dataloader = DataLoader(val_dataset, batch_size=32)
test_dataloader = DataLoader(test_dataset, batch_size=32)
定义一个模型
现在,数据已经准备好,我们可以定义模型了。我们假设你对神经网络的基本结构有所了解。在 PyTorch 中,torch.nn 命名空间提供了创建神经网络的所有构建模块。我们在这个例子中使用的模型非常简单,仅包含 线性层、ReLu 激活函数 和 Dropout 层。有关 PyTorch 中所有预定义层的概述,请参阅 文档。
我们可以通过继承nn.Module来构建自己的模型。一个 PyTorch 模型至少包含两个方法。init 方法,其中实例化了所有需要的层,和forward 方法,其中定义了最终模型。以下是一个示例模型,能够为我们的示例数据提供足够好的结果。
class WaterNet(nn.Module):
def __init__(self):
super().__init__()
# define 4 linear layers
# the first input dimension is the number of features
# the output layer is 1
# the other in and output parameters are hyperparamters and can be changed
self.fc1 = nn.Linear(in_features=9, out_features=64)
self.fc2 = nn.Linear(in_features=64, out_features=32)
self.fc3 = nn.Linear(in_features=32, out_features=16)
self.fc4 = nn.Linear(in_features=16, out_features=1)
self.relu = nn.ReLU()
self.dropout = nn.Dropout()
def forward(self, x):
# apply the linear layers with a relu activation
x = self.fc1(x)
x = self.relu(x)
x = self.dropout(x)
x = self.fc2(x)
x = self.relu(x)
x = self.fc3(x)
x = self.relu(x)
x = self.fc4(x)
return x.squeeze()
该模型由四个线性层组成。输入和输出特征的数量已定义,这决定了输入和输出样本的大小。我们的数据包含 9 个特征,因此第一层的输入特征数量为 9。输出特征的大小可以更改,但必须与下一个输入特征的大小匹配。由于我们最终需要一个 1 维的输出(0 或 1),因此最后的输出特征大小为 1。注意,这里没有应用最终的 sigmoid 层。我们将在下一节中解释这一点。
训练模型
接下来,我们需要训练模型。在 PyTorch 中训练模型包括四个主要步骤:
-
应用模型
-
计算损失
-
反向传播
-
更新权重
要训练一个轮次,这些步骤需要在所有批次的train_dataloader上完成。然后需要另一个循环遍历所需的轮次数。伪代码中一个轮次的训练如下:
for batch in train_dataloader:
# apply model
y_hat = model(x)
# calculate loss
loss = loss_function(y_hat, y)
# backpropagation
loss.backward
# update weights
optimizer.step()
优化器和损失函数仍需定义。我们将在下一节中完成这部分。以下是包含这个训练循环的函数。此外,还计算了一些指标(准确率、召回率和精度)。注意,我们将模型设置为训练模式(model.train()
),而不是评估模式(model.eval()
)。这会影响 dropout 或批归一化层,这些层在训练和验证时的处理方式不同。该函数的输入是
1. 模型。这是上面定义的模型。
2. 设备。这可以是 GPU 或 CPU,将在下一节中设置。
3. train_dataloader。上述定义的用于训练数据的数据加载器。
4. 优化器。优化器用于最小化误差。我们将在下一节中具体说明。
5. 损失函数(标准)。我们将在下一节中具体说明。
6. 训练轮次。当前的训练轮次。
def train(model, device, train_dataloader, optimizer, criterion, epoch, print_every):
'''
parameters:
model - the model used for training
device - the device we work on (cpu or gpu)
train_dataloader - the training data wrapped in a dataloader
optimizer - the optimizer used for optimizing the parameters
criterion - loss function
epoch - current epoch
print_every - integer after how many batches results should be printed
'''
# create empty list to store the train losses
train_loss = []
# variable to count correct classified samples
correct = 0
# variable to count true positive, false positive and false negative samples
TP = 0
FP = 0
FN = 0
# create an empty list to store the predictions
predictions = []
# set model to training mode, i.e. the model is used for training
# this effects layers like BatchNorm() and Dropout
# in our simple example we don't use these layers
# for the sake of completeness 'model.train()' is included
model.train()
# loop over batches
for batch_idx, (x, y) in enumerate(train_dataloader):
# set data to device
x, y = x.to(device), y.to(device)
# set optimizer to zero
optimizer.zero_grad()
# apply model
y_hat = model(x.float())
# calculate loss
loss = criterion(y_hat, y.float())
train_loss.append(loss.item())
# backpropagation
loss.backward()
# update the weights
optimizer.step()
# print the loss every x batches
if batch_idx % print_every == 0:
percent = 100\. * batch_idx / len(train_dataloader)
print(f'Train Epoch {epoch} \
[{batch_idx * len(train_dataloader)}/{len(train_dataloader.dataset)} \
({percent:.0f}%)] \tLoss: {loss.item():.6f}')
# calculate some metrics
# to get the predictions, we need to apply the sigmoid layer
# this layer maps the data to the range [0,1]
# we set all predictions > 0.5 to 1 and the rest to 0
y_pred = torch.sigmoid(y_hat) > 0.5
predictions.append(y_pred)
correct += (y_pred == y).sum().item()
TP += torch.logical_and(y_pred == 1, y == 1).sum()
FP += torch.logical_and(y_pred == 1, y == 0).sum()
FN += torch.logical_and(y_pred == 0, y == 1).sum()
# total training loss over all batches
train_loss = torch.mean(torch.tensor(train_loss))
epoch_accuracy = correct/len(train_dataloader.dataset)
# recall = TP/(TP+FN)
epoch_recall = TP/(TP+FN)
# precision = TP/(TP+FP)
epoch_precision = TP/(TP+FP)
return epoch_accuracy, train_loss, epoch_recall, epoch_precision
模型的验证类似,但没有反向传播,也没有更新权重。伪代码中一个轮次的验证如下。
for batch in val_dataloader:
# apply model
y_hat = model(x)
# calculate loss
loss = loss_function(y_hat, y)
以下是一个用于验证一个轮次的函数。它与前面用于训练的函数非常相似。注意,模型设置为评估模式(model.eval()
),并且由于在验证过程中不需要计算梯度,我们设置了 with torch.no_grad()
。这将减少计算时的内存消耗。
def valid(model, device, val_dataloader, criterion):
'''
parameters:
model - the model used for training
device - the device we work on (cpu or gpu)
val_dataloader - the validation data wrapped in a dataloader
criterion - loss function
'''
# create an empty list to store the loss
val_loss = []
# variable to count correct classified samples
correct = 0
# variable to count true positive, false positive and false negative samples
TP = 0
FP = 0
FN = 0
# create an empty list to store the predictions
predictions = []
# set model to evaluation mode, i.e.
# the model is only used for inference, this has effects on
# dropout-layers, which are ignored in this mode and batchnorm-layers, which use running statistics
model.eval()
# disable gradient calculation
# this is useful for inference, when we are sure that we will not call Tensor.backward().
# It will reduce memory consumption for computations that would otherwise have requires_grad=True.
with torch.no_grad():
# loop over batches
for x, y in val_dataloader:
# set data to device
x, y = x.to(device), y.to(device)
# apply model
y_hat = model(x.float())
# append current loss
loss = criterion(y_hat, y.float())
val_loss.append(loss.item())
# calculate some metrics
# to get the predictions, we need to apply the sigmoid layer
# this layer maps the data to the range [0,1]
# we set all predictions > 0.5 to 1 and the rest to 0
y_pred = torch.sigmoid(y_hat) > 0.5
predictions.append(y_pred)
correct += (y_pred == y).sum().item()#y_pred.eq(y.view_as(y_pred)).sum().item()
TP += torch.logical_and(y_pred == 1, y == 1).sum()
FP += torch.logical_and(y_pred == 1, y == 0).sum()
FN += torch.logical_and(y_pred == 0, y == 1).sum()
# total validation loss over all batches
val_loss = torch.mean(torch.tensor(val_loss))
epoch_accuracy = correct/len(val_dataloader.dataset)
# recall = TP/(TP+FN)
epoch_recall = TP/(TP+FN)
# precision = TP/(TP+FP)
epoch_precision = TP/(TP+FP)
print(f'Validation: Average loss: {val_loss.item():.4f}, \
Accuracy: {epoch_accuracy:.4f} \
({100\. * correct/len(val_dataloader.dataset):.0f}%)')
return predictions, epoch_accuracy, val_loss, epoch_recall, epoch_precision
综合起来
要使用 PyTorch 训练神经网络,我们需要做以下几步:
- 从数据中生成数据集并将其包装成数据加载器
- 我们在前面的部分已经做过这些,然而为了完整性,我们将再次生成它们。
2. 定义模型
我们使用上述定义的模型WaterNet
。
- 定义优化器
- 在 PyTorch 中有不同的优化器。我们使用 Adam 优化器,这是一种非常常见的优化器。不过你也可以尝试不同的优化器。Adam 优化器是随机梯度下降的扩展。简单来说,区别在于随机梯度下降在训练过程中保持学习率不变,而 Adam 则对其进行调整。关于 Adam 优化器的介绍可以在这里找到。
- 定义损失函数
-
我们正在考虑一个 1 维输出的二分类问题,这种问题的默认选择是二元交叉熵,我们将使用它。
-
请注意,我们没有在模型中应用最终的sigmoid 层。这是故意的,因为 PyTorch 提供了
[nn.BCEWithLogitsLoss()](https://pytorch.org/docs/stable/generated/torch.nn.BCEWithLogitsLoss.html)
方法,它将最终的 sigmoid 层和二元交叉熵结合起来。我们也可以单独应用这两种方法,即将[nn.Sigmoid](https://pytorch.org/docs/stable/generated/torch.nn.Sigmoid.html)
层作为模型的最后一步,然后使用[nn.BCE()](https://pytorch.org/docs/stable/generated/torch.nn.BCELoss.html)
损失。不过,使用nn.BCEWithLogitsLoss()
是处理二分类问题的推荐方式,因为它在数值上更稳定。
在我们开始训练模型之前,我们设置超参数。超参数是可调节的参数,允许你控制模型优化过程。不同的超参数值可以影响模型的训练和收敛速度。在我们的例子中,我们有三个超参数需要设置。请注意,模型层的输入和输出特征也是超参数。我们将它们设置为固定值,不过你可以尝试不同的值。
-
batch_size
:训练和验证批次大小 -
epochs
:训练的周期数 -
learning_rate
:学习率
我们还设置了变量print_every
。这不是一个超参数,而只是决定在训练和验证过程中多频繁打印损失。请注意,如果有 GPU 可用,我们还需要手动将设备设置为“cuda”。
# hyperparamters
batch_size = 32
epochs = 150
learning_rate = 1e-3
print_every = 200
# set device to GPU, if available
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# create datasets
train_dataset = WaterDataset(X_train, y_train)
valid_dataset = WaterDataset(X_val, y_val)
# wrap into dataloader
train_dataloader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, drop_last=True)
val_dataloader = DataLoader(valid_dataset, batch_size=batch_size)
# define the model and move it to the available device
model = WaterNet().to(device)
# define the optimizer
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)#, weight_decay=1e-4)
# define loss
criterion = nn.BCEWithLogitsLoss()
现在,我们准备开始训练。下面是最终的训练循环。此外,计算得到的指标会为每个周期保存。
# create empty lists to store the accuracy and loss per validation epoch
train_epoch_accuracy = []
train_epoch_loss = []
train_epoch_recall = []
train_epoch_precision = []
val_epoch_accuracy = []
val_epoch_loss = []
val_epoch_recall = []
val_epoch_precision = []
# loop over number of epochs
for epoch in range(epochs):
# train model for each epoch
train_accuracy, train_loss, train_recall, train_precision = \
train(model, device, train_dataloader, optimizer, criterion, epoch, print_every)
# save training loss and accuracy for each epoch
train_epoch_accuracy.append(train_accuracy)
train_epoch_loss.append(train_loss)
train_epoch_recall.append(train_recall)
train_epoch_precision.append(train_precision)
# validate model for each epoch
predictions, val_accuracy, val_loss, val_recall, val_precision = \
valid(model, device, val_dataloader, criterion)
# save validation loss and accuracy for each epoch
val_epoch_accuracy.append(val_accuracy)
评估结果
为了评估结果,我们可以查看训练和评估过程中的损失和指标。
训练和验证的损失为 150 个周期。
训练和验证的回顾持续 150 个周期。
你可以在笔记本中找到其他计算指标的图表。
保存模型
如果我们以后想使用我们的模型,我们需要保存它。我们可以通过保存它的state_dict()
来做到这一点。
torch.save(model.state_dict(), 'water_model_weights.pth')
我们可以用以下方式加载它
model = WaterNet()
model.load_state_dict(torch.load('water_model_weights.pth'))
model.eval()
不要忘记使用model.eval()
将模型设置为评估模式,以将丢弃和批量归一化层设置为评估模式。
将模型应用于测试集
现在我们将训练好的模型应用于测试数据。
# create a dataset
test_dataset = WaterDataset(X_test, y_test)
# wrap into dataloader
test_dataloader = DataLoader(test_dataset, batch_size=batch_size)
predictions, test_accuracy, test_loss, test_recall, test_precision = \
valid(model, device, val_dataloader, criterion)
结论
本文展示了如何使用 PyTorch 进行深度学习项目的详细示例。讨论并应用了深度学习工作流中的各个步骤到一个具体的数据集。在训练模型之前,一个重要的步骤是将数据转换为正确的形式,并为特定应用定义一个定制的数据集。在训练模型时,四个主要步骤是(1)应用模型,(2)计算损失,(3)进行反向传播,(4)更新权重。定义并应用了一个包含所有这些步骤的示例训练函数。最后,要使用模型,了解如何存储和重新加载模型是很重要的。
资源
[1] Aditya Kadiwal, 2021, 水质 — 水质分类数据集, www.kaggle.com/datasets/mssmartypants/water-quality
, 下载于 2023 年 1 月, 许可证:CC0: 公共领域
[2] PyTorch, 2022, pytorch.org/tutorials/beginner/basics/intro.html
除非另有说明,所有图片均为作者提供。
在这里查看更多数据科学和机器学习的帖子:
[## 更多
数据科学和机器学习博客
datamapu.com medium.com [## 每当 Pumaline 发布时获得电子邮件。
每当 Pumaline 发布时获得电子邮件。通过注册,如果你还没有 Medium 账户,将会创建一个…
medium.com www.buymeacoffee.com [## Pumaline
嗨,我喜欢学习和分享有关数据科学和机器学习的知识。
PyTorch 简介:从训练循环到预测
原文:
towardsdatascience.com/introduction-to-pytorch-from-training-loop-to-prediction-a70372764432
对 PyTorch 训练循环和应对库陡峭初学曲线的一般方法的介绍
·发布在Towards Data Science ·14 分钟阅读·2023 年 3 月 28 日
–
作者提供的图片。
在这篇文章中,我们将讨论如何在 Python 中使用 PyTorch 实现逻辑回归模型。
PyTorch 是全球数据科学家和机器学习工程师社区中最著名和最常用的深度学习框架之一,因此,如果你希望在应用 AI 领域建立职业生涯,学习这个工具是你学习路径中的一个关键步骤。
它与 TensorFlow 并列,TensorFlow 是由 Google 开发的另一个非常著名的深度学习框架。
除了 API 的结构和组织可能非常不同外,基本原理没有显著的差别。
虽然这两个框架都允许我们创建非常复杂的神经网络,但 PyTorch 通常更受欢迎,因为它的风格更加python 化,并且允许开发者将自定义逻辑集成到软件中。
我们将使用Sklearn 乳腺癌数据集,这是一个开源数据集,之前在我一些文章中已被使用,用于训练一个二分类模型。
目标是解释如何:
-
从 pandas 数据框转换到 PyTorch 的 Datasets 和 DataLoaders
-
在 PyTorch 中创建一个用于二分类的神经网络
-
创建预测
-
使用实用函数和 matplotlib 评估我们的模型表现
-
使用这个网络进行预测
到文章末尾,我们将对如何在 PyTorch 中创建神经网络以及训练循环的工作原理有清晰的了解。
开始吧!
安装 PyTorch 及其他依赖
我们通过在专用文件夹中创建虚拟环境来开始我们的项目。
访问此链接以了解如何使用 Conda 创建虚拟环境。
如何安装、激活和使用用于机器学习和数据科学相关任务的虚拟环境
[towardsdatascience.com
一旦我们的虚拟环境创建完成,我们可以运行以下命令
$ pip install torch -U
在终端中。该命令将安装 PyTorch 的最新版本,截至撰写本文时为 2.0 版。
启动一个笔记本,我们可以在执行import torch
之后使用torch.__version__
检查库的版本。
我们可以通过导入并运行一个小测试脚本来验证 PyTorch 是否在环境中正确安装,如官方指南所示。
import torch
x = torch.rand(5, 3)
print(x)
>>> tensor([[0.3890, 0.6087, 0.2300],
[0.1866, 0.4871, 0.9468],
[0.2254, 0.7217, 0.4173],
[0.1243, 0.1482, 0.6797],
[0.2430, 0.4608, 0.8886]])
如果脚本正确执行,则我们可以继续进行项目。否则,我建议读者参考这里的官方指南 pytorch.org/get-started/locally/
。
让我们继续安装其他依赖项:
-
Sklearn;
pip install scikit-learn
-
Pandas;
pip install pandas
-
Matplotlib;
pip install matplotlib
像 Numpy 这样的库在安装 PyTorch 时会自动安装。
导入和探索数据集
让我们从导入已安装的库和 Sklearn 的乳腺癌数据集开始,使用以下代码片段
import torch
import pandas as pd
import numpy as np
from sklearn.datasets import load_breast_cancer
import matplotlib.pyplot as plt
breast_cancer_dataset = load_breast_cancer(as_frame=True, return_X_y=True)
让我们创建一个数据框来专门保存我们的 X 和 y,如下所示
df = breast_cancer_dataset[0]
df['target'] = breast_cancer_dataset[1]
df
数据框的示例。图片由作者提供。
我们的目标是创建一个可以根据其他列的特征预测目标列的模型。
让我们进行最低限度的探索性分析,以了解数据集。我们将使用 sweetviz 库自动创建分析报告。
我们可以使用命令pip install sweetviz
安装 sweetviz,并使用这段代码创建一个 EDA(探索性数据分析)报告
import sweetviz as sv
eda_report = sv.analyze(df)
eda_report.show_notebook()
Sweetviz 正在分析我们的数据集。图片由作者提供。
Sweetviz 将直接在我们的笔记本中创建报告供我们探索。
Sweetviz 中的“关联”标签。图片由作者提供。
我们看到多个列与目标列的值 0 或 1 高度相关。
由于这是一个多维数据集,且具有不同分布的变量,神经网络是建模该数据的一个有效选项。也就是说,该数据集也可以通过更简单的模型进行建模,如决策树。
现在,我们将导入另外两个库,以便可视化数据集。我们 **将使用 Sklearn 和 Seaborn 中的 PCA(主成分分析)**来可视化多维数据集。
PCA 将帮助我们将大量变量压缩成两个变量,我们将把这两个变量用作 Seaborn 散点图中的 X 轴和 Y 轴。Seaborn 使用一个额外的参数色调来根据附加变量为点上色。我们将使用我们的目标。
import seaborn as sns
from sklearn import decomposition
pca = decomposition.PCA(n_components=2)
X = df.drop("target", axis=1).values
y = df['target'].values
vecs = pca.fit_transform(X)
x0 = vecs[:, 0]
x1 = vecs[:, 1]
sns.set_style("whitegrid")
sns.scatterplot(x=x0, y=x1, hue=y)
plt.title("Proiezione PCA")
plt.xlabel("PCA 1")
plt.ylabel("PCA 2")
plt.xticks([])
plt.yticks([])
plt.show()
乳腺癌数据集的 PCA 投影。图像由作者提供。
我们看到类别 1 的数据点基于共同特征分组。我们的神经网络的目标是将行分类为目标 0 或 1。
创建数据集和数据加载器类
PyTorch 提供了Dataset
和DataLoader
对象,允许我们高效地组织和加载数据到神经网络中。
可以直接使用 pandas,但这样做会有缺点,因为它会使我们的代码效率降低。
Dataset
类允许我们指定数据的正确格式,并应用常常是基础的检索和转换逻辑(例如图像的数据增强)。
让我们看看如何创建一个 PyTorch Dataset
对象。
from torch.utils.data import Dataset
class BreastCancerDataset(Dataset):
def __init__(self, X, y):
# create feature tensors
self.features = torch.tensor(X, dtype=torch.float32)
# create label tensors
self.labels = torch.tensor(y, dtype=torch.long)
def __len__(self):
# we define a method to retrieve the length of the dataset
return self.features.shape[0]
def __getitem__(self, idx):
# necessary override of the __getitem__ method which helps to index our data
x = self.features[idx]
y = self.labels[idx]
return x, y
这是一个继承自Dataset
的类,并允许我们将要创建的DataLoader
高效地检索数据批次。
该类以 X 和 y 作为输入。
训练、验证和测试数据集
在继续以下步骤之前,创建训练、验证和测试集是很重要的。
这些将帮助我们评估模型的性能,并理解预测的质量。
对于感兴趣的读者,我建议阅读文章训练模型前你应该做的 6 件事和机器学习中的交叉验证是什么,以更好地理解为什么将数据拆分为三部分是一种有效的性能评估方法。
使用 Sklearn,这变得很简单,使用train_test_split
方法即可。
from sklearn import model_selection
train_ratio = 0.50
validation_ratio = 0.20
test_ratio = 0.20
x_train, x_test, y_train, y_test = model_selection.train_test_split(X, y, test_size=1 - train_ratio)
x_val, x_test, y_val, y_test = model_selection.train_test_split(x_test, y_test, test_size=test_ratio/(test_ratio + validation_ratio))
print(x_train.shape, x_val.shape, x_test.shape)
>>> (284, 30) (142, 30) (143, 30)
通过这段小代码,我们根据可控的分割创建了我们的训练、验证和测试集。
数据规范化
在进行深度学习时,即使是像二分类这样简单的任务,也总是需要规范化我们的数据。
规范化意味着将数据集中各个列的所有值统一到相同的数值尺度。这有助于神经网络更有效地收敛,从而更快地做出准确的预测。
我们将使用 Sklearn 的StandardScaler
。
from sklearn import preprocessing
scaler = preprocessing.StandardScaler()
x_train_scaled = scaler.fit_transform(x_train)
x_val_scaled = scaler.transform(x_val)
x_test_scaled = scaler.transform(x_test)
注意fit_transform
仅应用于训练集,而transform
应用于其他两个数据集。这是为了避免数据泄露——即验证集或测试集中的信息无意中泄漏到训练集中。我们希望训练集是唯一的学习来源,不受测试数据影响。
这些数据现在已经准备好输入到BreastCancerDataset
类中。
train_dataset = BreastCancerDataset(x_train_scaled, y_train)
val_dataset = BreastCancerDataset(x_val_scaled, y_val)
test_dataset = BreastCancerDataset(x_test_scaled, y_test)
我们导入 dataloader 并初始化对象。
from torch.utils.data import DataLoader
train_loader = DataLoader(
dataset=train_dataset,
batch_size=16,
shuffle=True,
drop_last=True
)
val_loader = DataLoader(
dataset=val_dataset,
batch_size=16,
shuffle=False,
drop_last=True
)
test_loader = DataLoader(
dataset=test_dataset,
batch_size=16,
shuffle=False,
drop_last=True
)
DataLoader
的强大之处在于它允许我们指定是否对数据进行洗牌以及数据应该以多少批次提供给模型。批次大小应被视为模型的超参数,因此可能会影响推断结果。
神经网络在 PyTorch 中的实现
在 PyTorch 中创建模型可能听起来很复杂,但实际上只需要理解一些基本概念。
-
在 PyTorch 中编写模型时,我们将使用面向对象的方法,就像处理数据集一样。这意味着我们将创建一个像
MyModel
这样的类,它继承自 PyTorch 的nn.Module
类。 -
PyTorch 是一个自动微分软件。这意味着当我们基于反向传播算法编写神经网络时,计算导数来计算损失的过程是自动进行的。这需要编写一些专门的代码,第一次遇到时可能会感到困惑。
我建议想了解神经网络工作原理基础的读者查阅文章 神经网络介绍——权重、偏差和激活。
神经网络如何通过权重、偏差和激活函数学习
话虽如此,让我们看看编写逻辑回归模型的代码是怎样的。
class LogisticRegression(nn.Module):
"""
Our neural network accepts num_features and num_classes.
num_features - number of features to learn from
num_classes: number of classes in output to expect (in this case, 1 or 2, since the output is binary (0 or 1))
"""
def __init__(self, num_features, num_classes):
super().__init__() # initialize the init method of nn.Module
self.num_features = num_features
self.num_classes = num_classes
# create a single layer of neurons on which to apply the log reg
self.linear1 = nn.Linear(in_features=num_features, out_features=num_classes)
def forward(self, x):
logits = self.linear1(x) # pass our data through the layer
probs = torch.sigmoid(logits) # we apply a sigmoid function to obtain the probabilities of belonging to a class (0 or 1)
return probs # return probabilities
我们的类继承自 nn.Module
。这个类提供了让模型正常工作的后台方法。
init 方法
类的 __init__
方法包含了在 Python 中实例化类时运行的逻辑。在这里我们传递两个参数:特征的数量和要预测的类别数量。
num_features
对应于组成数据集的列数减去目标变量,而 num_classes
对应于神经网络必须返回的结果数量。
除了这两个参数及其类变量,我们还看到 super().__init__()
这一行。super 函数初始化了父类的 init 方法。这使得我们能够在模型中拥有 nn.Module
的功能。
在 init 块中,我们实现了一个线性层,称为 self.linear1
,它的参数是特征的数量和返回结果的数量。
forward() 方法
通过编写 forward
方法,我们告诉 Python 重写 PyTorch 的 nn.Module
父类中的相同方法。实际上,这个方法在进行前向传播时被调用——也就是当我们的数据从一层流向另一层时。
forward
接受输入 x,其中包含模型将根据其性能进行校准的特征。
输入通过第一层,创建了logits
变量。logits 是神经网络的计算结果,还未通过最终激活函数(在此情况下为 sigmoid)转换为概率。实际上,它们是神经网络在映射到可以解释的函数之前的内部表示。
在这种情况下,sigmoid 函数会将 logits 映射到 0 和 1 之间的概率。如果输出小于 0,则类别为 0,否则为 1。这发生在self.probs = torch.sigmoid(x)
这一行。
用于绘图和准确率计算的实用函数
让我们创建实用函数以在即将看到的训练循环中使用。这两个函数用于计算每个 epoch 结束时的准确率,并在训练结束时显示性能曲线。
def compute_accuracy(model, dataloader):
"""
This function puts the model in evaluation mode (model.eval()) and calculates the accuracy with respect to the input dataloader
"""
model = model.eval()
correct = 0
total_examples = 0
for idx, (features, labels) in enumerate(dataloader):
with torch.no_grad():
logits = model(features)
predictions = torch.where(logits > 0.5, 1, 0)
lab = labels.view(predictions.shape)
comparison = lab == predictions
correct += torch.sum(comparison)
total_examples += len(comparison)
return correct / total_examples
def plot_results(train_loss, val_loss, train_acc, val_acc):
"""
This function takes lists of values and creates side-by-side graphs to show training and validation performance
"""
fig, ax = plt.subplots(1, 2, figsize=(15, 5))
ax[0].plot(
train_loss, label="train", color="red", linestyle="--", linewidth=2, alpha=0.5
)
ax[0].plot(
val_loss, label="val", color="blue", linestyle="--", linewidth=2, alpha=0.5
)
ax[0].set_xlabel("Epoch")
ax[0].set_ylabel("Loss")
ax[0].legend()
ax[1].plot(
train_acc, label="train", color="red", linestyle="--", linewidth=2, alpha=0.5
)
ax[1].plot(
val_acc, label="val", color="blue", linestyle="--", linewidth=2, alpha=0.5
)
ax[1].set_xlabel("Epoch")
ax[1].set_ylabel("Accuracy")
ax[1].legend()
plt.show()
模型训练
现在我们进入大多数深度学习新手 struggle 的部分:PyTorch 训练循环。
让我们查看代码,然后进行注释。
import torch.nn.functional as F
model = LogisticRegression(num_features=x_train_scaled.shape[1], num_classes=1)
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
num_epochs = 10
train_losses, val_losses = [], []
train_accs, val_accs = [], []
for epoch in range(num_epochs):
model = model.train()
t_loss_list, v_loss_list = [], []
for batch_idx, (features, labels) in enumerate(train_loader):
train_probs = model(features)
train_loss = F.binary_cross_entropy(train_probs, labels.view(train_probs.shape))
optimizer.zero_grad()
train_loss.backward()
optimizer.step()
if batch_idx % 10 == 0:
print(
f"Epoch {epoch+1:02d}/{num_epochs:02d}"
f" | Batch {batch_idx:02d}/{len(train_loader):02d}"
f" | Train Loss {train_loss:.3f}"
)
t_loss_list.append(train_loss.item())
model = model.eval()
for batch_idx, (features, labels) in enumerate(val_loader):
with torch.no_grad():
val_probs = model(features)
val_loss = F.binary_cross_entropy(val_probs, labels.view(val_probs.shape))
v_loss_list.append(val_loss.item())
train_losses.append(np.mean(t_loss_list))
val_losses.append(np.mean(v_loss_list))
train_acc = compute_accuracy(model, train_loader)
val_acc = compute_accuracy(model, val_loader)
train_accs.append(train_acc)
val_accs.append(val_acc)
print(
f"Train accuracy: {train_acc:.2f}"
f" | Val accuracy: {val_acc:.2f}"
)
与 TensorFlow 不同,PyTorch 要求我们用纯 Python 编写训练循环。
让我们逐步查看过程:
-
我们实例化模型和优化器
-
我们决定一个 epoch 数量。
-
我们创建一个 for 循环来遍历 epochs。
-
对于每个 epoch,我们使用
model.train()
将模型设置为训练模式,并循环遍历train_loader
。 -
对于每个批次的
train_loader
,计算损失,使用optimizer.zero_grad()
将梯度计算归零,并使用optimizer.step()
更新网络的权重。
此时训练循环已经完成,如果需要,可以将相同的逻辑集成到验证数据加载器中,如代码所示。
这是运行此代码后的训练结果。
训练中。图片由作者提供。
神经网络性能评估
我们使用之前创建的实用函数来绘制训练和验证中的损失。
plot_results(train_losses, val_losses, train_accs, val_accs)
神经网络的性能。图片由作者提供。
我们的二分类模型很快收敛到高准确率,我们可以看到每个 epoch 结束时损失如何下降。
数据集模型简单,样本数量少并没有帮助网络更逐步地收敛到高性能。
我强调,可以将 TensorBoard 软件集成到 PyTorch 中,以便在各种实验之间自动记录性能指标。
创建预测
我们已经到达了本指南的末尾。让我们查看创建整个数据集预测的代码。
# we transform all our features with the scaler
X_scaled_all = scaler.transform(X)
# transform in tensors
X_scaled_all_tensors = torch.tensor(X_scaled_all, dtype=torch.float32)
# we set the model in inference mode and create the predictions
with torch.inference_mode():
logits = model(X_scaled_all_tensors)
predictions = torch.where(logits > 0.5, 1, 0)
df['predictions'] = predictions.numpy().flatten()
现在让我们导入 Sklearn 的metrics
包,它允许我们直接在 pandas 数据框上快速计算混淆矩阵和分类报告。
from sklearn import metrics
from pprint import pprint
pprint(metrics.classification_report(y_pred=df.predictions, y_true=df.target))
对整个数据集的性能总结,附有分类报告。图片由作者提供。
以及混淆矩阵,它显示了对角线上的正确答案数量。
metrics.confusion_matrix(y_pred=df.predictions, y_true=df.target)
>>> array([[197, 15],
[ 13, 344]])
这里有一个小函数,用于创建一个分类线,将 PCA 图中的类别分开。
def plot_boundary(model):
w1 = model.linear1.weight[0][0].detach()
w2 = model.linear1.weight[0][1].detach()
b = model.linear1.bias[0].detach()
x1_min = -1000
x2_min = (-(w1 * x1_min) - b) / w2
x1_max = 1000
x2_max = (-(w1 * x1_max) - b) / w2
return x1_min, x1_max, x2_min, x2_max
sns.scatterplot(x=x0, y=x1, hue=y)
plt.title("PCA Projection")
plt.xlabel("PCA 1")
plt.ylabel("PCA 2")
plt.xticks([])
plt.yticks([])
plt.plot([x1_min, x1_max], [x2_min, x2_max], color="k", label="Classification", linestyle="--")
plt.legend()
plt.show()
这是模型如何将良性细胞与恶性细胞区分开来
分类边界可视化。图片由作者提供。
结论
在本文中,我们已经看到如何使用 PyTorch 从 Pandas 数据框创建一个二分类模型。
我们已经了解了训练循环的样子,如何评估模型,以及如何创建预测和可视化以帮助解释。
使用 PyTorch 可以创建非常复杂的神经网络……只需想到特斯拉,这家基于 AI 的电动车制造商,就使用 PyTorch 创建其模型。
对于那些想要开始深度学习之旅的人来说,尽早学习 PyTorch 成为一个高优先级任务,因为它允许你构建重要的技术,解决复杂的数据驱动问题。
如果你想支持我的内容创作活动,可以通过下面的推荐链接加入 Medium 会员计划。我将获得你投资的一部分,你将能够无缝访问 Medium 上大量的数据科学及更多领域的文章。
[## 使用我的推荐链接加入 Medium - Andrea D’Agostino]
阅读 Andrea D’Agostino 的每个故事(以及 Medium 上的其他成千上万的作家)。你的会员费直接……
medium.com](https://medium.com/@theDrewDag/membership?source=post_page-----a70372764432--------------------------------)
推荐阅读
对于感兴趣的人,这里有一系列我推荐的与 ML 相关的书籍。这些书籍在我看来是必读的,并且对我的职业生涯产生了重大影响。
免责声明:这些是亚马逊附属链接。我将因推荐这些商品而获得亚马逊的小额佣金。你的体验不会改变,你不会被额外收费,但这将帮助我扩展业务,并制作更多关于 AI 的内容。
-
机器学习入门: 自信的数据技能:掌握数据工作的基础,提升你的职业生涯 作者:Kirill Eremenko
-
Sklearn / TensorFlow: 动手学机器学习:使用 Scikit-Learn、Keras 和 TensorFlow 作者:Aurelien Géron
-
NLP: 文本作为数据:机器学习和社会科学的新框架 作者:Justin Grimmer
-
Sklearn / PyTorch: 用 PyTorch 和 Scikit-Learn 学习机器学习:使用 Python 开发机器学习和深度学习模型 作者:Sebastian Raschka
-
数据可视化: 数据讲故事:商业专业人士的数据可视化指南 作者:科尔·克纳夫利克
有用的链接(由我撰写)
-
学习如何在 Python 中执行顶级探索性数据分析: Python 中的探索性数据分析——逐步过程
-
学习 TensorFlow 的基础知识: 入门 TensorFlow 2.0——深度学习介绍
-
使用 TF-IDF 在 Python 中进行文本聚类: Python 中的 TF-IDF 文本聚类