TowardsDataScience 博客中文翻译 2022(一百四十)

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

利用 Spark、Nixtla 和 Fugue 对 15 分钟以内的 1M 时间序列进行分布式预测

原文:https://towardsdatascience.com/distributed-forecast-of-1m-time-series-in-under-15-minutes-with-spark-nixtla-and-fugue-e9892da6fd5c

使用开源项目 StatsForecast、Fugue 和 Spark 进行可扩展的时间序列建模

由凯文·科、汪涵马克斯·梅根塔尔费德里科·加尔萨·拉米雷斯主演。

TL:DR 我们将展示如何利用 Spark 的分布式能力和 StatsForecast 的高效代码在几分钟内拟合数百万个模型。

对一段时间内收集的数据进行趋势和季节性的时间序列建模、分析和预测是一种快速增长的软件应用。

从电力和经济到医疗保健分析,企业每天都在收集时间序列数据,以预测模式并构建更好的数据驱动型产品体验。例如,温度和湿度预测用于制造以防止缺陷,流指标预测有助于识别音乐的流行艺术家,对供应链中不同位置的数千个 SKU 的销售预测用于优化库存成本。随着数据生成量的增加,预测的必要性已经从模拟几个时间序列发展到预测数百万个时间序列。

动机

Nixtla 是一个开源项目,专注于最先进的时间序列预测。他们有几个库,例如用于统计模型的 StatsForecast ,用于深度学习的 NeuralForecast ,以及用于预测不同层级的聚合的 HierarchicalForecast 。这些是面向生产的时间序列库,侧重于不同的建模技术。

本文着眼于 StatsForecast ,这是一个拥有统计和计量经济学模型的快速预测库。Nixtla 的 AutoARIMA 模型比的 pmdarima 快 20 倍,ETS(误差、趋势、季节)模型比的 statsmodels 快 4 倍,而且更稳健。要复制的基准和代码可以在这里找到。性能提升的很大一部分是由于使用了名为 numba 的 JIT 编译器来实现高速度。

更快的迭代时间意味着数据科学家可以运行更多的实验,并更快地收敛到更准确的模型。这也意味着大规模运行基准测试变得更加容易。

在本文中,我们对 StatsForecast 库在使用赋格库拟合 SparkDask 模型时的可伸缩性感兴趣。这种结合将允许我们在一个临时集群上快速地分布训练大量的模型。

实验设置

当处理大型时间序列数据时,用户通常必须处理数千个逻辑上独立的时间序列(想想不同用户或不同产品销售的遥测数据)。在这种情况下,我们可以在所有系列上训练一个大模型,或者我们可以为每个系列创建一个模型。这两种方法都是有效的,因为较大的模型将获得整个群体的趋势,而训练数千个模型可能更好地拟合单个系列数据。

注意:要在一个模型中获得时间序列人口的微观和宏观趋势,请检查 Nixtlahierarchical forecast库,但这也是计算成本更高、规模更棘手的方法。

本文将讨论我们为每个单变量时间序列训练几个模型(AutoARIMA 或 ETS)的场景。对于此设置,我们按时间序列对完整数据进行分组,然后为每个组训练每个模型。下图说明了这一点。分布式数据帧可以是 Spark 或 Dask 数据帧。

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

按分区自动排序—按作者排序的图像

Nixtla 之前发布了关于在 Ray 上分发这个模型训练的基准测试。设置和结果可以在这个博客中找到。结果也如下所示。在 35 分钟内运行一百万个 AutoARIMA 模型需要 2000 个 CPU。我们将把它与在 Spark 上运行进行比较。

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

光线结果的统计预测—图片由作者提供

统计预测代码

首先,我们将查看用于在射线上分布式运行 AutoARIMA 的 StatsForecast 代码。这是运行一百万时间序列场景的简化版本。它还为最近的 StatsForecast v1.0.0 版本进行了更新,因此它看起来可能与以前基准测试中的代码有点不同。

射线上分布式运行状态预测

StatsForecast 的界面非常小。它已经被设计为对每组数据执行 AutoARIMA。只需提供ray_address就可以让这个代码片段分布式运行。如果没有它,n_jobs将指示用于预测的并行流程的数量。model.forecast()将在一个步骤中完成拟合和预测,并在时间范围内输入到该方法中进行预测。

用神游在 Spark 和 Dask 上运行

Fugue 是一个抽象层,将 Python、Pandas 和 SQL 代码移植到 Spark 和 Dask。最少的接口是transform()函数。这个函数接收一个函数和数据帧,并把它送到 Spark 或 Dask。我们可以使用transform()函数来激活 StatsForecast 的执行。

下面的代码有两个部分。首先,我们在forecast_series函数中定义了预测逻辑。为了简单起见,一些参数是硬编码的。最重要的是那个n_jobs=1。这是因为 Spark 或 Dask 已经充当了并行化层,拥有两级并行会导致资源死锁。

运行状态预测与神游火花

其次,transform()函数用于在 Spark 上应用forecast_series()函数。前两个参数是要应用的数据帧和函数。输出模式是 Spark 的一个需求,所以我们需要将它传入,分区参数将负责通过unique_id分割时间序列建模。

这段代码已经运行并返回一个 Spark DataFrame 输出。

尼克斯特拉氏河豚

上面的transform()是大致看看神游能做什么。实际上,Fugue 和 Nixtla 团队合作向 StatsForecast 库中添加了一个更加本地化的FugueBackend。伴随它的是一个实用的forecast()功能,用于简化预测界面。下面是对一百万个时间序列运行 StatsForecast 的端到端示例。

我们只需要创建 FugueBackend,它接收一个 SparkSession 并将其传递给forecast()。该函数可以采用数据帧或数据的文件路径。如果提供了文件路径,它将与并行后端一起加载。在上面的例子中,我们在每次运行实验来生成基准时都替换了这个文件。

同样重要的是要注意,我们可以在对完整数据运行forecast()之前进行本地测试。我们所要做的就是不为平行论证提供任何东西;一切都将在熊猫身上按顺序运行。

基准测试结果

基准测试结果如下所示。在撰写本文时,Dask 和 Ray 发布了最新版本,所以只有 Spark 指标是最新的。在用更新运行这些实验之后,我们将发表一篇后续文章。

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

Spark 和 Dask stats 基准测试大规模预测

注意:我们试图使用 2000 个 CPU,但是受到 AWS 上可用计算实例的限制。

这里重要的部分是 AutoARIMA 在不到 15 分钟的时间内训练了一百万个时间序列模型。集群配置附在附录中。用很少的几行代码,我们就能够分布式地编排这些时间序列模型的训练。

结论

分散地训练数千个时间序列模型通常需要使用 Spark 和 Dask 进行大量编码,但是我们能够用很少几行代码运行这些实验。Nixtla 的 StatsForecast 能够快速利用所有可用的计算资源,为每个时间序列找到最佳模型。所有用户需要做的就是提供一个相关的并行后端(Ray 或 Fugue)在集群上运行。

在一百万时间序列的规模上,我们对 AutoARIMA 的总训练时间为 12 分钟。这相当于我们立即运行了近 400 个 cpu 小时,允许数据科学家快速大规模迭代,而无需编写显式的并行化代码。因为我们使用了一个短暂的集群,所以成本实际上与在 EC2 实例上顺序运行这个集群(在所有内核上并行化)是一样的。

资源

  1. Nixtla StatsForecast 回购
  2. 统计预测文档
  3. 赋格回购
  4. 赋格教程

要与我们聊天:

  1. 赋格松弛
  2. 尼克斯特拉松驰

附录

对任何人来说。对集群配置感兴趣,可以看下面。这将启动 Databricks 集群。重要的是使用机器的node_type_id

{
    "num_workers": 20,
    "cluster_name": "fugue-nixtla-2",
    "spark_version": "10.4.x-scala2.12",
    "spark_conf": {
        "spark.speculation": "true",
        "spark.sql.shuffle.partitions": "8000",
        "spark.sql.adaptive.enabled": "false",
        "spark.task.cpus": "1"
    },
    "aws_attributes": {
        "first_on_demand": 1,
        "availability": "SPOT_WITH_FALLBACK",
        "zone_id": "us-west-2c",
        "spot_bid_price_percent": 100,
        "ebs_volume_type": "GENERAL_PURPOSE_SSD",
        "ebs_volume_count": 1,
        "ebs_volume_size": 32
    },
    "node_type_id": "m5.24xlarge",
    "driver_node_type_id": "m5.2xlarge",
    "ssh_public_keys": [],
    "custom_tags": {},
    "spark_env_vars": {
        "MKL_NUM_THREADS": "1",
        "OPENBLAS_NUM_THREADS": "1",
        "VECLIB_MAXIMUM_THREADS": "1",
        "OMP_NUM_THREADS": "1",
        "NUMEXPR_NUM_THREADS": "1"
    },
    "autotermination_minutes": 20,
    "enable_elastic_disk": false,
    "cluster_source": "UI",
    "init_scripts": [],
    "runtime_engine": "STANDARD",
    "cluster_id": "0728-004950-oefym0ss"
}

分布式学习:入门

原文:https://towardsdatascience.com/distributed-learning-a-primer-790812b817f1

让机器学习模型更大、更好、更快的算法背后

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

稳定扩散生成的图像

分布式学习是现代科技公司的 ML 堆栈中最关键的组件之一:通过在大量机器上并行化,人们可以更快地在更多数据上训练更大的模型,以更快的迭代周期释放更高质量的生产模型。

但不要只相信我的话。以推特为例:

使用定制的分布式培训[…]使我们能够更快地迭代,并根据更多更新鲜的数据来训练模型。

或者谷歌的:

我们的实验表明,我们新的大规模训练方法可以使用一群机器来训练即使是中等规模的深度网络,速度也比 GPU 快得多,而且没有 GPU 对模型最大大小的限制。

或者网飞的:

我们试图实现一个大规模的神经网络训练系统,该系统利用了 GPU 和 AWS 云的优势。我们希望使用合理数量的机器,通过神经网络方法实现强大的机器学习解决方案。

在这篇文章中,我们将探索分布式学习背后的一些基本设计考虑,特别关注深度神经网络。您将了解到:

  • 模型并行与数据并行训练,
  • 同步与异步训练,
  • 集中式与分散式培训,以及
  • 大批量训练。

让我们开始吧。

模型并行与数据并行

在深度神经网络的分布式训练中有两种主要的范例,模型并行,其中我们分布模型,以及数据并行,其中我们分布数据。

模型并行性意味着每台机器只包含模型的一个分区,例如深度神经网络的某些层(“垂直”分区),或者来自同一层的某些神经元(“水平”分区)。如果一个模型太大而不能在一台机器上运行,那么模型并行性可能是有用的,但是它需要在机器之间发送大量的张量,这会带来很高的通信开销。在最坏的情况下,一台机器可能会在等待前一台机器完成其部分计算时处于空闲状态。

数据并行性意味着每台机器都有一个完整的模型副本,并在本地批量数据上向前和向后传递。根据定义,这种模式的伸缩性更好:我们也可以随时向集群添加更多的机器

  • 通过保持全局(集群范围)批处理大小固定,并减少本地(每台机器)批处理大小,或者
  • 通过保持本地批量大小不变并增加全局批量大小。

在实践中,模型和数据并行性不是互斥的,而是互补的:没有什么可以阻止我们将模型和数据分布到我们的机器集群上。正如推特的一篇博客文章所概述的,这种混合方法有其自身的优势。

最后,还有超参数并行,每台机器在相同的数据上运行相同的模型,但是具有不同的超参数。在其最基本的形式中,这是令人尴尬的平行。

同步与异步训练

在数据并行中,在训练周期的每次迭代中,一批全局数据均匀地分布在集群中的所有机器上。例如,如果我们在一个有 32 台机器的集群上以 1024 的全局批量进行训练,我们将向每台机器发送 32 个本地批量。

为了实现这一点,我们需要一个参数服务器,一个存储和跟踪最新模型参数的专用机器。工人将他们本地计算的梯度发送到参数服务器,参数服务器又将更新的模型参数发送回工人。这可以同步或异步完成。

同步训练中,参数服务器等待来自所有工人的所有梯度到达,然后基于在所有工人上聚集的平均梯度更新模型参数。这种方法的优点是平均梯度的噪声更小,因此参数更新的质量更高,使得模型收敛更快。然而,如果一些工人需要更长的时间来计算他们的局部梯度,那么所有其他工人都必须闲置,等待掉队者赶上来。当然,闲置是对计算资源浪费:理想情况下,每台机器都应该随时有事可做。

异步训练中,参数服务器一接收到来自单个工作者的单个梯度就更新模型参数,并将更新的参数立即发送回该工作者。这消除了闲置的问题,但它引入了另一个问题,即陈旧。一旦基于来自单个工作者的梯度更新了模型参数,所有其他工作者现在就使用陈旧的模型参数。工人越多,问题越严重:例如,有 1000 个工人,当最慢的工人完成计算时,它将落后 999 步。

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

异步数据并行。每个工人将他们的本地梯度发送到参数服务器,并接收模型参数。(图片来源:兰格等人 2020,链接)

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

同步数据并行。在发送更新的模型参数之前,参数服务器聚集来自所有工人的梯度。(图片来源:兰格等人 2020,链接)

因此,一个好的经验法则是,如果节点数量相对较少,则使用异步训练,如果节点数量非常大,则切换到同步训练。例如,来自谷歌的研究人员在 32 个 Nvidia K40 GPUs 上使用异步并行性训练了他们的’洪水填充网络’,这是一种用于大脑图像分割的深度神经网络。然而,为了在一台拥有 2048 个计算节点的超级计算机上训练相同的模型架构,来自阿贡国家实验室(包括本文作者)的研究人员改用了同步并行。

在实践中,人们也可以在同步和异步并行之间找到有用的折衷。例如,来自微软的研究人员提出了一个对同步并行的“残酷”修改:简单地留下最慢的工人。他们报告说,这种修改将训练速度提高了 20%,而对最终模型的准确性没有影响。

集中式与分散式培训

拥有中央参数服务器的缺点是,对该服务器的通信需求随着集群的大小而线性增长。这就产生了一个瓶颈,限制了这种集中式设计的规模。

为了避免这个瓶颈,我们可以引入多个参数服务器,并为每个参数服务器分配模型参数的子集。在最分散的情况下,每个计算节点既是一个工作者(计算梯度),也是一个参数服务器(存储模型参数的子集)。这种去中心化设计的优点是,所有机器的工作负载和通信需求都是相同的,这消除了任何瓶颈,并使其更容易扩展。

大批量训练

在数据并行中,全局批处理大小随着集群大小线性增长。在实践中,这种缩放行为支持具有极大批量的训练模型,这在单台机器上是不可能的,因为它的内存有限。

大批量训练中最关键的问题之一是如何根据聚类大小调整学习速率。例如,如果模型训练在单个单机上运行良好,批量大小为 32,学习率为 0.01,那么当再添加 7 台机器,导致全局批量大小为 256 时,正确的学习率是多少?

在 2017 年的一篇论文中,来自脸书的研究人员提出了线性缩放规则:简单地将学习率与批量大小线性缩放(即,在上面的例子中使用 0.08)。使用具有 256 台机器和 8192(每台机器 32)的全局批量大小的 GPU 集群,作者仅用 60 分钟就在 ImageNet 数据集上训练了一个深度神经网络,这在论文发表时是一个了不起的成就,也是大批量训练的力量的展示。

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

大批量训练图解。批量越大,梯度噪音越小,步长越大,收敛越快。(图片来源:McCandlish et al 2018,链接)

然而,大批量训练有其局限性。正如我们已经看到的,为了利用更大的批量,我们需要提高学习率,以便利用额外的信息。但是如果学习率太大,在某一点上模型可能会超调并且不能收敛。

来自 OpenAI 的一篇 2018 年论文解释说,大批量训练的限制似乎取决于领域,从 ImageNet 的数万批次到学习玩游戏 Dota 2 的数百万批次强化学习代理。找到这些极限的理论解释是一个尚未解决的研究问题。毕竟,ML 研究很大程度上是经验性的,缺乏理论支柱。

结论

概括一下,

  • 分布式学习是现代科技公司 ML 堆栈中的一个关键组件,能够更快地在更多数据上训练更大的模型。
  • 在数据并行中,我们分发数据,在模型并行中,我们分发模型。在实践中,两者可以结合使用。
  • 在同步数据并行中,参数服务器等待所有工人发送他们的梯度,而在异步数据并行中则不会。同步数据并行实现了更精确的梯度,代价是引入了一些空闲时间,而最快的工人必须等待最慢的工人。
  • 在完全去中心化的数据并行中,每个工作者也是模型参数子集的参数服务器。去中心化设计均衡了所有机器的计算和通信需求,因此消除了任何瓶颈。
  • 数据并行支持大批量训练:我们可以在大规模集群上快速训练模型,方法是将学习速率与全局批量成线性比例。

而这只是冰山一角。分布式学习仍然是一个活跃的研究领域,有一些开放性的问题,如:大批量训练的局限性是什么?如何优化具有多个培训工作的真实集群,从而产生竞争负载?如何最好地处理包含混合计算资源(如 CPU 和 GPU)的集群?当搜索许多可能的模型或超参数时,我们如何平衡探索和利用?

欢迎来到分布式学习的迷人世界。

📫 订阅 把我的下一篇文章直接发到你的收件箱。
💪
成为中等会员 并解锁无限权限。
🐦关注我上
LinkedInTwitter

分布式并行训练:数据并行和模型并行

原文:https://towardsdatascience.com/distributed-parallel-training-data-parallelism-and-model-parallelism-ec2d234e3214

深度学习的分布式训练

如何在 PyTorch 中扩展培训大型模型,如 GPT-3 和达尔-E 2

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

马克·哈普尔在 Unsplash 上拍摄的照片

近年来,分布式并行训练的规模和深度学习模型的规模呈指数级增长。特别是,基于 Transformer 的语言模型已经抢尽了风头。臭名昭著的 GPT-3 爆发了 1750 亿个参数和 96 个关注层,批量大小为 3.2 米,单词为 4990 亿个。整整半年后,谷歌发布了拥有 1.6 万亿参数的开关变压器。同一天(2021 年 1 月 11 日),北京人工智能研究院(BAAI)发布了初始的悟道 1.0。不久,悟道 2.0 于 2021 年 5 月 31 日首次亮相,成为最大的语言模型,拥有 1.75 万亿个参数,是 GPT-3 参数的十倍。

假设我们在亚马逊 SageMaker 训练平台的 240 ml.p4d.24xlarge 实例上训练 GPT-3,整个模型将需要 25 天来训练。挑战不仅仅是处理,还有记忆。吴涛 2.0 似乎需要超过 1000 个 GPU 来存储它的参数。

对于像 GPT-3 和 DALL-E 2 这样的深度学习大型模型,采用分布式并行训练势在必行。有两种主要类型的分布式并行训练:数据并行和模型并行。我们进一步将后者分为两个子类型:流水线并行和张量并行。我们将在这里涵盖所有分布式并行培训,并演示如何在 PyTorch 中开发。

了解分布式并行培训

分布式并行训练有两个高级概念:并行和分布。

并行是一种解决大型模型规模或提高训练效率的框架策略,分布式是一种向外扩展的基础架构。

除了这两种基本类型的并行,还有更多的变体,比如专家并行。此外,它们可以混合两种或全部,如数据和模型混合并行。对于大规模模型,混合模型和数据并行是很常见的。例如,最大的 T5 型号和 GPT-3 采用模型和数据相结合的并行方式。然而,所有这些都应该是 DL 建模框架策略的一部分。

另一方面,分布最终会在云或集群中扩展并行性。容器化使扩展节点变得容易,Kubernetes 或云解决方案可以有效地编排它们。每个节点可以有多个 GPU(或 TPU 和其他设备)和容器群集中的各种容器。在云原生解决方案中,节点可以对用户隐藏。一个容器管理一个或多个 GPU。并行性可以跨分布式 GPU 容器集群进行调度。所以分布是基础架构的实现。

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

Google Switch Transformers 的数据和权重划分策略(来源: Fedus 等人,2021

以上说明了 Google Switch 传输中的数据和权重划分策略。每个 4×4 虚线网格代表 16 个内核,阴影方块是该内核上的数据(模型权重或一批令牌)。它演示了如何为每个策略拆分模型权重和数据张量。第一行展示了模型权重如何在内核间分配。该行中不同大小的形状表示前馈网络(FFN)层中较大的权重矩阵(例如,较大的 dff 大小)。阴影方块的每种颜色标识一个唯一的权重矩阵。每个内核的参数数量是固定的,但是更大的权重矩阵将对每个令牌应用更多的计算。第二行展示了如何在内核间分割数据批次。每个内核持有相同数量的令牌,在所有策略中保持固定的内存使用量。分区策略具有不同的属性,允许每个内核在不同颜色的内核之间具有相同或不同的令牌。

PyTorch 中的数据并行性

数据并行性使用相同的模型在所有内核之间分割数据。PyTorch 分布式数据并行、SageMaker 分布式和 Horovod 等数据并行框架主要完成以下三项任务:

  1. 首先,它创建并分发模型的副本,每个加速器一个副本。
  2. 它对数据进行分片,然后将其分发给相应的设备。
  3. 它最终在反向传播步骤中将所有结果聚集在一起。

因此我们可以看到,第一个任务应该在每次训练中出现一次,但最后两个任务应该在每次迭代中出现。

PyTorch 分布式数据并行 (DDP)实现了模块级的数据并行,可以跨多台机器运行。它可以与 PyTorch 模型并行工作。DDP 应用程序应该生成多个进程,并为每个进程创建一个 DDP 实例。DDP 使用torch.distributed包中的集体通信来同步梯度和缓冲区。此外,DDP 为来自model.parameters()的每个参数注册了一个自动签名的钩子,当在向后传递中计算出相应的梯度时,它将触发。然后,DDP 使用该信号触发过程间的梯度同步。

因此,在 PyTorch 中设置和运行 DDP 有三个主要步骤:

  1. 通过torch.distributed建立分布式系统。
  2. 通过torch.nn.parallel定义 DDP 建模。
  3. 产卵贯穿torch.multiprocessing

请参见下面的示例代码。

**import** **torch
import** **torch.nn** **as** **nn
import** **torch.distributed** **as** **dist**
**import** **torch.multiprocessing** **as** **mp****from** **torch.nn.parallel** **import** **DistributedDataParallel** **as** **DDP** ### ***Step 1***: *setup and cleanup setups*
**def** **setup(rank,** **world_size):
    ...** *# initialize the process group*
    **dist.init_process_group(**"tst"**, rank=rank, world_size=world_size)**

**def** **cleanup():**
    **dist.destroy_process_group()** ### ***Step 2***: *define DDP modeling*
**def** **dummy_init(rank,** **world_size):**
    **setup(rank,** **world_size)**
    **model** **=** **DummyModel().to(rank)**
    **ddp_model** **=** **DDP(model,** **device_ids=[rank])** **...** **cleanup()** ### ***Step 3***: *Spawn to run*
**def** **run_dummy(dummy_fn,** **world_size):**
    **mp.spawn(dummy_fn,**
             **args=(world_size,),**
             **nprocs=world_size,**
             **join=True)**

PyTorch 中的模型并行性

与数据并行不同,模型并行将模型(即其层或张量)分割到多个内核,为所有训练内核复制相同的模型。PyTorch 减轻了并行实现的负担,并对其进行了最小的修改。

简而言之,在调用损失函数时,您需要通过三个相应的区域中的“to(device)”来指定神经网络层和到所需内核的即时输出:建模定义,“forward”方法和“backward”方法。PyTorch 将在幕后处理所有其他的事情。请在此处查看示例代码

在大模型并行的现实世界中,这可能并不简单。它通常需要额外的努力来提高培训效率和资源利用率。以流水线并行为例, PipeDream 通过牺牲内存来存储权重的多个副本,提高了流水线效率。 TeraPipe 引入了另一种特定于单变压器架构的流水线技术,这种流水线技术是跨令牌而不是微批处理进行的。此外, Mesh-TensorFlowMegatron-LM 分别基于 TensorFlow 和 PyTorch 创建了用于优化训练十亿参数模型的张量并行框架。

Amazon sage makermodel parallelism是 PyTorch 之上的一个软件库。它是一个通用而灵活的框架,支持流水线和张量并行,具有节省内存的特性。其流水线并行引擎支持基于模块-服务器设计的任意模型架构的负载平衡自动分区和流水线运行时。与流水线并行一样,张量并行的基本计算单位是nn.Module。本质上,张量并行性在于遍历模型并用它们的分布式实现替换模型的特定子模块。

外卖

分布式并行训练有并行和分布两个高级概念。**并行是框架策略,分发是基础架构。**分布式并行培训至关重要,但在行业和研究中仍处于萌芽状态。我们可以期待未来会出现三个创新领域。

  1. 并行性分割数据、模型或混合数据,以进行大型模型训练。随着数据和模型呈指数级增长,优化内存使用和处理效率变得至关重要。
  2. 训练一个大模特很贵。复用训练层的迁移学习将改变大规模分布式并行训练的游戏。
  3. ML 生命周期涉及多个分布式系统,从数据收集到处理、模型训练和服务。ML 平台经常受到复杂性、数据通信成本和系统不稳定性的阻碍。统一 ML 的所有分布式系统意义重大。

参考

  1. 亚马逊 SageMaker 模型并行性:大型模型训练的通用灵活框架:https://arxiv.org/abs/2111.05972
  2. 开关变压器:用简单有效的稀疏性扩展到万亿参数模型:【https://arxiv.org/abs/2101.03961
  3. 语言模型是很少出手的学习者:【https://arxiv.org/abs/2005.14165

分布式并行训练—模型并行训练

原文:https://towardsdatascience.com/distributed-parallel-training-model-parallel-training-a768058aa02a

分布式培训

PyTorch 中大模型的分布式模型并行训练

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

丹妮拉·奎瓦斯在 Unsplash 上的照片

近年来,深度学习模型的规模和分布式并行训练的挑战呈指数级增长。例如,著名的 GPT-3 有 1750 亿个参数和 96 个关注层,批量大小为 3.2 M,字数为 4990 亿。亚马逊 SageMaker 训练平台在 120ml . p4d . 24x 大型实例和 1750 亿个参数上可以达到每秒 32 个样本的吞吐率。如果我们将此增加到 240 个实例,完整的模型将需要 25 天来训练。

对于大型模型,在 GPU 上训练并行性变得非常必要。有三种典型的分布式并行训练类型:分布式数据并行、模型并行和张量并行。我们常常把后两种归为一类:模型并行,再分为流水线并行和张量并行两个亚型。我们将在这里重点介绍分布式模型并行训练,并演示如何在 PyTorch 中进行开发。

了解分布式模型并行训练

模型并行性在多个 GPU 之间分割模型,这与数据并行性为所有训练 GPU 复制同一模型不同。模型并行训练有三个关键点:1 .如何有效地分割模型的层;2.如何并行而不是顺序地训练分片层;3.如何设计优秀的节点间吞吐量?如果我们不能平衡碎片或者并行运行它们,我们就不能实现模型并行的目标。

分布式训练是一种在集群或资源池中具有多个节点的训练并行性。容器化使得扩展节点变得容易,Kubernetes 很好地协调了它们。每个节点可以有多个 GPU 和多个容器。一个容器可以控制一个或多个 GPU。模型并行性可以跨分布式 GPU 节点集群分派模型的各层。这种方法可以有效地扩展培训。

我们可以在下面举例说明分布式模型并行训练。

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

模型并行性解释(作者)

在上面的示例中,集群中有两个节点。每个节点有一个或两个容器,每个容器也有一个或两个 GPU。模型的七层分布在这些 GPU 上。

每个 GPU 或节点都有资源限制。模型并行性使培训能够并行化,而分布式培训最终可以横向扩展。

PyTorch 中的模型并行性

上面的描述表明,分布式模型并行训练有两个主要部分。为了实现这一点,有必要在多个 GPU 中设计模型并行性。PyTorch 对此进行了包装并减轻了实现负担。PyTorch 只有三个小变化。

  1. 使用“to(device)”来标识模型的特定层(或子网)的特定设备(或 GPU)。
  2. 相应地添加一个“forward”方法,在设备间移动中间输出。
  3. 调用损失函数时,指定同一设备上的标签输出。而“backward()”和“torch.optim”会像在单个 GPU 上运行一样自动处理渐变。

让我们写一些伪代码来说明它。假设在两个 GPU 上运行一个简单的两层模型,将每个线性层放在每个 GPU 上,并将输入和中间输出相应地移动到相关层 GPU 上。我们可以如下定义虚拟模型:

**import** **torch**
**import** **torch.nn** **as** **nn****class** **DummyModel(nn.Module):**
    **def** __init__**(**self**):**
        super**(DummyModel,** self**).**__init__**()**
        self**.net0** **=** **nn.Linear(**20**,** 10**).to(**'cuda:0'**)**
        self**.relu** **=** **nn.ReLU()**
        self**.net1** **=** **nn.Linear(**10**,** 10**).to(**'cuda:1'**)**

    **def** **forward(**self**,** **x):**
        **x** **=** self**.relu(**self**.net0(x.to(**'cuda:0'**)))**
        **return** self**.net1(x.to(**'cuda:1'**))**

现在我们可以用下面的损失函数添加训练代码:

**import** **torch**
**import** **torch.nn** **as** **nn
import** **torch.optim** **as** **optim
import** **DummyModel****model** **=** **DummyModel()**
**loss_fn** **=** **nn.MSELoss()**
**optimizer** **=** **optim.SGD(model.parameters(),** **lr=**0.001**)**

**optimizer.zero_grad()**
**outputs** **=** **model(torch.randn(**30**,** 10**))**
**labels** **=** **torch.randn(**30**,** 10**).to(**'cuda:1'**)**
**loss_fn(outputs,** **labels).backward()**
**optimizer.step()**

同样的想法可以很快扩展到更复杂的模型。我们可以在一个Sequential中分组多个层,通过to(device).分配到一个特定的 GPU 上,当然还有更多优化并行效率的改进,这里就不赘述了。

TL;速度三角形定位法(dead reckoning)

分布式模型并行训练有两个主要概念。模型并行实现了训练无法在单个 GPU 或设备上运行的大型模型。分布式培训可以通过在分布式设备之间划分模型来有效地向外扩展。PyTorch 和其他库(如 SakeMaker)通过最小的修改使生活变得更容易,尽管它在内部实现起来很复杂。

参考

https://arxiv.org/abs/2111.05972 https://aws.amazon.com/blogs/machine-learning/deploy-large-models-on-amazon-sagemaker-using-djlserving-and-deepspeed-model-parallel-inference/ https://aws.amazon.com/blogs/machine-learning/train-175-billion-parameter-nlp-models-with-model-parallel-additions-and-hugging-face-on-amazon-sagemaker/

分布式事务的设计模式

原文:https://towardsdatascience.com/distributed-transactions-cdc-event-sourcing-outbox-cqrs-patterns-ee0cf70339b1

了解事件来源、命令查询责任分离(CQRS)、变更数据捕获(CDC)和发件箱模式

最终演变为微服务架构的领域驱动的分布式应用架构具有许多优势,如交付服务的速度和敏捷性、小型和专注的团队、可扩展的设计、较小的代码库、容错和数据隔离。在适应微服务架构方面存在各种挑战,架构师和开发人员通常会遇到设计复杂性、数据完整性问题、治理和版本问题。

幸运的是,有架构驱动的解决方案和设计模式可以克服这些挑战。在这篇文章中,我们将主要关注解决数据一致性挑战,这是由于整个数据架构中的分布式事务。

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

Shubham DhageUnsplash 上拍摄

在本帖中,我们将简要介绍以下内容:

  • 活动采购
  • 变更数据捕获(CDC)
  • 命令查询响应分离
  • 发件箱模式

https://linkedin.com/in/p-jainani

在我们分别讨论每个问题之前,让我们先描述两个重要的概念:

  • 领域事件:它们是参与者与应用程序交互产生的业务事件。它们代表真实世界的事件,如*loan approval、FraudDetected、ChartUpdated 或 OrderCancelled。*领域事件与事件源相关联。
  • *变更事件:*随着底层数据库中数据状态的变化而产生。这些是数据库事务日志,与变更数据捕获相关联。

活动采购

由业务逻辑生成的应用程序状态的变化被捕获为软件日志中的域事件。该日志允许遍历到时间线中任意点的应用程序的特定状态。日志是一个只追加存储,并且是不可变的。它们可以在任何时间点重复播放,并且是真实的单一来源。日志中的域事件按 ID 分组,以捕捉对象在任一时间点的状态。快照是一种重新创建对象状态的机制。

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

事件来源[图片由作者提供]

变更数据捕获(CDC)

如上所述,来自数据库事务日志的变更事件被下游消费者捕获和消费。因此,它是一种机制,通过这种机制,我们可以通过一些集成模式将应用程序状态共享给外部系统。

物化视图是 CDC 方法的关键概念,有一个外部过程将变更事件物化并转发给下游消费者。

有一个消息代理,它处理事件消息并将其转发给下游的消费者,并保证至少一次交付。

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

改变数据捕捉(CDC) 【图片由作者提供】

发件箱模式

发件箱模式确保应用程序状态(存储在应用程序数据库中)及其各自的域事件(转发给外部使用者)在单个事务下是一致的和持久的。发件箱表在应用程序的数据库中实现,以收集与事务对应的域事件。一旦我们有了事务保证机制,我们就可以使用发件箱表通过 CDC 传播事件交付,如上所述,与 CDC 连接器集成的代理将消息转发给外部消费者。

发件箱的重要之处在于,它只是一个临时的传出事件数据存储库,域事件在下游处理后会立即被删除。

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

发件箱图案【图片由作者提供】

命令查询责任分离(CQRS)

它通常与事件源相关联,有时用发件箱模式实现。作为视图的只读数据投影是 CQRS 实现的关键概念。对于不同的消费者,从相同的数据集可以得到多个这样的预测。这构成了分离查询部分 w.r.t. CQRS 实现的基础。

或者,CQRS 的命令方面适用于应用程序执行的动作,以生成作为域事件的响应。这使得能够生成投影的状态。因此,这与它与事件源的联系密切相关。

我们必须注意,将命令查询分离会导致两个数据模型的最终一致性。CQRS 模式的实现比带有发件箱的 CDC 更复杂。

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

命令查询责任分离(CQRS) 【图片由作者提供】

结论

本文简要介绍了分布式事务架构实现的各种设计模式。有各种全面的文献和文档解释了实现这些模式的细节、某些注意事项、优点和缺点。实际的实现也因用例而异,并且依赖于对实现技术的选择。

在后续的文章中,我们将通过解决一个业务用例,同时利用云原生平台服务,详细阐述每个模式的实现。

https://linkedin.com/in/p-jainani

深入现代 Web 部署:构建动态应用程序快速入门指南

原文:https://towardsdatascience.com/dive-into-modern-web-deployment-quickstart-guide-on-building-dynamic-applications-9e1eb2979f1e

用顺风 CSS 创建 Vue 应用

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

马克·哈普尔在 Unsplash 上拍摄的照片

对于构建动态应用程序的工具和工作流有足够的选择。经过一段时间的研究,你可能会同意我的观点,Vue 和 Tailwind CSS 在列表中名列前茅。对我来说,合乎逻辑的下一步是在做出承诺之前尝试一下。写这篇文章的目的是让你不用花太多时间就能体验一下这个设置,这样你就可以自己做决定了。

概观

Vue 是一个渐进式的 JavaScript 框架,这意味着你可以在了解整体之前就开始使用它。

Vue 的目标是构建用户界面,但要有风格。换句话说,Vue 需要使用 CSS 框架。Tailwind 是 CSS 领域的一颗新星,如果你耐心地给 Tailwind 一个机会,它会越来越吸引你。我喜欢顺风顺水的时尚和清新,相比之下,Bootstrap 的商业证明,但往往很无聊。

将 Vue 与 Tailwind CSS 集成的两种显而易见的方法是:1)用 Tailwind 初始化项目,然后添加 Vue,或者 2)在 Vue 中初始化,然后添加 Tailwind

我的经验表明,后者效果更好。来自顺风网站的文档比来自 Vue 网站的文档更准确。让我们通过以下步骤开始集成:

  • 创建 Vue 应用程序
  • 将 Tailwind 安装到应用程序中

在 Vue 中创建应用程序

要创建您的项目,请运行npm init vite project-name。您应该看到这个:

% **npm init vite v901**Need to install the following packages:
create-vite
Ok to proceed? (y)✔ **Select a framework:** › vue
✔ **Select a variant:** › vueScaffolding project in /Users/seanzhai/www/v901...
Done. Now run:cd v901
npm install
npm run dev

最后一个命令npm run dev现在不需要。不过测试一下也无妨。它在端口 3000 上创建一个本地 web 服务器。您可以在任何浏览器中验证这一点。

配置开发服务器

您可能注意到了 vite 开发服务器运行在本地主机的端口 3000 上。如果你想让你的开发服务器可以被其他机器访问,你可以通过改变vite.config.js文件来实现。

export default defineConfig({
  plugins: [vue()],
  server: {
 **port: '3030',
    host: '0.0.0.0'**
  }
})

通过如上所示的设置,当你做npm run dev时,你可以看到你在 3030 端口上服务于世界。你可能知道你可能需要注意你的防火墙设置来使它工作。

安装顺风 CSS

安装 Tailwind 并创建其初始配置。

npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
npx tailwindcss init -p

第二个命令生成了文件tailwind.config.js。编辑行content,使文件如下所示。

module.exports = {
 **content: [
    "./vitesrc/index.html",
    "./vitesrc/**/*.{vue,js,ts,jsx,tsx}",
  ],**
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
}

然后,在src文件夹下创建index.css

/* ./src/index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

src用于所有源代码,下面有一个文件夹components用于存放 Vue 组件。我们需要将 index.css 文件导入 main.js,这是所有 app 的入口。

// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
**import './index.css'**createApp(App).mount('#app')

到目前为止,您应该有一个支持 Tailwind CSS 的功能性 Vue 应用程序。祝贺您成功整合。

顺风美丽的 Vue

对 Tailwind 最常见的批评是缺乏预建组件,这可能是有 bootstrap 背景的人拒绝 Tailwind 的主要原因。

顺风的补救措施是推广 tailwindui.com 的付费服务。如果预算不是问题,你当然可以试一试;它展示了许多布局和小工具,但它们不容易用作模板。您需要花费一些努力来适应 tailwindui 提供的组件。

我发现最有用的是 Headless UI,它是专门为使用 Tailwind 而设计的。是 tailwindui 背后的同一批人开发的。

安装和第一步

无头 UI 安装非常容易。

npm install @headlessui/vue @heroicons/vue -D

看一下它的官方网站。

https://headlessui.dev/

注意它的组件支持 ReactVue 。默认设置是 React,请在进入特定组件前点击 Vue 。我曾经忘记了这一点,无意中进入 React 部分,我发现代码有点奇怪。出于比较的好奇,要达到相同的结果,Vue 中需要的行数几乎总是比 React 中少。

使用无头用户界面风格化

花一点时间剖析一个组件可以学到很多东西。菜单(下拉菜单)就是一个很好的例子。让我们一起使用 Visual Studio 代码来完成这项工作。

用 VS 代码打开项目根文件夹,定位src。创建了一个名为components的文件夹,然后添加了一个文件Dropdown.vue。让我们用琼·狄迪恩最著名的书来填充菜单。该文件如下。它基于 HeadlessUI 网站上的示例。我把它简化了一点,使它成为一个独立的页面,这样更容易理解。

要使用这个组件,我们只需要从主应用程序中引用它。我添加了一些基本样式,使组件易于查看。

结果看起来像这样:

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

带有顺风 CSS 的 Vue 中的 HeadlessUI 组件示例|来源:Sean Zhai

收场白

Vue 和 Tailwind 都是很神奇的工具。我最喜欢使用 Vue 和 Tailwind 的地方是,它让你大部分时间都呆在同一个上下文中,没有在 JavaScript、CSS 和 HTML 文件之间跳跃;开发服务器的自动重新加载也节省了时间。这是一个让你进步的工作流程,你会发现自己变得更有效率,压力更小。

它既有趣又美丽。

深入研究 C-支持向量分类

原文:https://towardsdatascience.com/diving-into-c-support-vector-classification-221ced32e4b4

SVC 算法能为我们做的技巧

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

大卫·罗蒂米Unsplash 上的照片

介绍

最近我一直在研究 Scikit-Learn,并记下了这个神奇的库所提供的工具。在本帖中,我们来了解一下C-支持向量机分类器。

支持向量机(SVM)是一种监督学习算法,可用于分类或回归。它适用于边界的线性和非线性计算,因此对一些问题很有用。

支持向量机可以替代其他好的算法,如决策树、逻辑回归或随机森林,因此它是一个很好的技能补充。

支持向量机

支持向量机是一种将数据点分为两类的算法。一旦对所有点都完成了,算法就开始追踪两个类之间的分离边缘处的一些线,目标是最大化它们之间的距离。它找到最大距离的地方就是最佳分隔线所在的地方。

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

蓝线是两个等级之间的最大距离。图片由作者提供。

对于线性分离的数据集,该算法工作得非常好。但是,如果我们的数据不是线性的呢?怎么还能用?

如果我们查看文档,我们会发现有一个名为kernel的超参数。内核是算法用来将点分成不同组并对它们进行分类的逻辑。

内核: { ‘线性’,’ poly ‘,’ rbf ‘,’ sigmoid ‘,’ precomputed’}或可调用,默认=‘rbf’

线性的

linear内核非常简单。SVM 将创建线条,就像之前显示的图形一样。

SVC(kernel='linear')

多项式

poly选项是针对多项式内核的。如果你观察多项式的形状,你会发现随着次数的增加,曲线越来越多,变得越来越不规则。因此,对于模型欠拟合,增加多项式次数可能是一个好主意,使决策边界绕过更多的点。C是正则化超参数,coef0平衡了模型受高次或低次多项式的影响。

SVC(kernel='poly', degree=3, coef0=1, C=5)

肾血流量(renal blood flow 的缩写)

该内核rbf用于高斯径向径向径向径向函数。它创建高斯分布来计算哪一个点更适合,以确定如何对这些点进行分类。超参数gamma使高斯曲线更窄(高伽玛值,更多偏差)或更宽(低伽玛值,更平滑的边界)。所以,如果我们增加伽玛,我们的决策边界会更加不规则。如果你的模型不合适,试着增加这个数字。如果过拟合,降低伽玛。C是正则化数。它的工作方式与 gamma 参数相似。

SVC(kernel='rbf', gamma=4, C=100)

乙状结肠的

内核sigmoid使用类似逻辑回归的逻辑,其中高达 50%的概率属于一个类,超过这个数字,它属于相反的类。你可以使用gamma超参数来正则化。

SVC(kernel='sigmoid', gamma=2)

预先计算的

最后,这最后一个内核用于更高级/定制的情况,您可以创建自己的内核来运行模型。

编码

使用 sklearn 的 SVC 类创建一个基本的 SVM,并不需要花费太多时间。

# Imports
import pandas as pd
import seaborn as sns# Data
from sklearn.datasets import make_classification# sklearn
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.metrics import confusion_matrix

创建数据集并在训练和测试中拆分。

# Dataset
X, y = make_classification(n_classes=2, n_features=6, n_samples=500, n_informative=2, scale=100, random_state=12)# Dataframe
df = pd.DataFrame(X, columns=['var'+str(i) for i in range(1, X.shape[1]+1)])
df['label'] = y#Train test split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=12)

快速查看创建的数据。

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

本例的数据集。图片由作者提供。

让我们使用 RBF 内核创建一个 SVM。建议您缩放数据以获得更好的结果。为此,我们可以用运行该任务的步骤和函数的名称创建元组。请注意,我们正在(1)缩放数据;(2)用核 RBF 训练一个 SVC。

steps = [('scaler', StandardScaler()),
         ('svm_classif', SVC(kernel='rbf', gamma=0.5, C=10))]# Create Pipeline object
rbf_kernel = Pipeline(steps)# Run the pipeline (fit) 
#Scale data and Fit the model
rbf_kernel.fit(X_train,y_train)# Predictions
preds = rbf_kernel.predict(X_test)

接下来看看表现。

# performance dataframe
result = pd.DataFrame(X_test, columns=['var'+str(i) for i in range(1, X.shape[1]+1)])result['preds'] = preds# Plot var1(on x) and var5(on y)
sns.scatterplot(data=result, x='var1', y='var5', hue='preds');

这就产生了下面的情节。

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

这里是混淆矩阵,看看这个模型在分类方面的表现。

# Confusion Matrix
pd.DataFrame(confusion_matrix(y_test, result.preds, labels=[0,1]))

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

SVC 的混淆矩阵。图片由作者提供。

非常好!只有 5 个假阳性和 1 个假阴性,准确率高达 94%。

如果我们训练一个随机森林分类器,这就是结果。

from sklearn.ensemble import RandomForestClassifiersteps = [('scaler', StandardScaler()),
         ('rf_classif', RandomForestClassifier())]# Create pipeline
rf = Pipeline(steps)# Fit
rf.fit(X_train,y_train)# Preds
preds = rf.predict(X_test)# performance
result = pd.DataFrame(X_test, columns=['var'+str(i) for i in range(1, X.shape[1]+1)])result['preds'] = preds# Confusion Matrix
pd.DataFrame(confusion_matrix(y_test, result.preds, labels=[0,1]))

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

随机森林的混淆矩阵。图片由作者提供。

类似的结果。这里的准确率是 92%,稍微低一点,但是我们必须注意到对于随机森林没有任何调整。可以改进。

在你走之前

我相信知道更多的算法和它们如何在引擎盖下工作是很好的。像数据科学中的许多事情一样,最佳选择不是这个或那个算法,而是为您的问题提供最佳结果的算法。所以,多了解一个,你就增加了获得更好结果的机会。

在这篇文章中,我们深入研究了SVC算法,学习如何为每个内核选择主超参数,以及它们本质上是如何工作的。

请记住,文档是您的朋友,可以提供很多帮助。另一个很好的资源是这本书《sklearn、Keras 和 Tensorflow 的机器学习实践》。我一直在阅读和享受很多。

在 GitHub 的这个库中找到这篇文章的代码。

这是我的博客,如果你喜欢这个内容,想关注我或者在 Linkedin 找到我。

http://gustavorsantos.medium.com/

如果你想成为中级会员,这个推荐代码会激励我加入你的订阅。

参考

https://scikit-learn.org/stable/modules/generated/sklearn.svm.SVC.html https://scikit-learn.org/stable/modules/svm.html#svm-kernels

https://en.wikipedia.org/wiki/Support_vector_machine

https://www.amazon.com/Hands-Machine-Learning-Scikit-Learn-TensorFlow/dp/1492032646/ref=asc_df_1492032646/?tag=hyprod-20&linkCode=df0&hvadid=385599638286&hvpos=&hvnetw=g&hvrand=847972974391386897&hvpone=&hvptwo=&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=9009674&hvtargid=pla-523968811896&psc=1&tag=&ref=&adgrpid=79288120515&hvpone=&hvptwo=&hvadid=385599638286&hvpos=&hvnetw=g&hvrand=847972974391386897&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=9009674&hvtargid=pla-523968811896 https://stats.stackexchange.com/questions/90736/the-difference-of-kernels-in-svm

一个漂亮的 Github 页面,包含每个内核的所有可视化内容:

https://gist . github . com/WittmannF/60680723 ed 8d d0 CB 993051 a 7448 f 7805

利用泰坦尼克号的数据潜入潮汐湖

原文:https://towardsdatascience.com/diving-into-the-tidyverse-using-the-titanic-data-83f54295d5df

使用 R 的 dplyr 和 tidyr 软件包提取见解

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

Max van den Oetelaar 在 Unsplash 上拍摄的照片

如今,数据比以往任何时候都更容易获得。我们可以很容易地通过互联网上的许多资源找到我们感兴趣的东西。然而,当试图从数据中得出结论时,我们很快意识到,尽管这些数据很容易找到,但其格式并不能用于更详细的分析。当几个人试图合作进行某项研究时,也会出现同样的问题。如果他们之前没有就如何标记和输入数据达成一致,每个人很可能会提出自己的系统。这最终会导致共享和分析数据时的混乱。这些问题产生了对独特的数据组织系统的需求。

列夫·托尔斯泰在他的作品《安娜·卡列尼娜》中表达了这样的思想:

所有幸福的家庭都是相似的;每个不幸的家庭各有各的不幸。

引用这句话,R 中编程的远见者之一 Hadley Wickham 用下面的话解决了数据组织的问题:

整洁的数据集都是相似的,但每一个杂乱的数据集都有自己的杂乱之处。

有了整齐的数据,每一行就是一个观察值,每一列就是一个变量。假设我们按马力和重量列出汽车的类型。有序组织的数据将包含三列:汽车类型、马力和质量。因此,通过查看一行数据,我们将获得一种类型汽车的所有信息。聚焦于一个数据列将提供关于所有类型汽车的单个属性(例如质量)的信息。

为了将数据转换成一种整洁的格式,Hadley Wickham 开发了tidyverse——一套软件包,包括以下软件包:dplyrtidyrggplot2purrrreadrtibble。在下面的文本中,我们将重点关注用于数据争论的这些包的子集- dplyrtidyrtibble

尽管tidyverse提出了一套一致且有效的方法来转换和可视化数据,但强调两点还是很重要的:

  • 使用基本的 R 函数可以获得使用tidyverse函数获得的结果(尽管通常以更复杂/更不直观的方式)
  • r 是一种编程语言,tidyverse是一组旨在促进数据转换和可视化的包。换句话说,tidyverse包不能解决我们可能遇到的所有问题,所以了解 R 编程的基础也很重要。

泰坦尼克号数据集

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

NOAA 在 Unsplash 上拍摄的照片

在下文中,我将介绍dplyrtidyrtibble软件包最重要的功能。我将展示如何使用这些函数来执行数据清理和探索性数据分析中涉及的各种常见任务。我将使用最著名的船只之一泰坦尼克号的乘客数据。

作为第一步,我们必须加载必要的包,并研究我们将使用的数据的结构。除了数据争论包,我们还必须加载包含所需数据的titanic包。

Titanic 数据集是一个data.frame类型的对象。因此,如果我们打印 Titanic 数据集,将返回全部 891 行。由于这样的布局既笨拙又麻烦,所以我使用 base R head函数只打印了前十行。为了绕过这个问题,tibble包引入了一个tibble数据类型。这种类型对data.frame类进行了改进,在显示数据集时只打印前十行。它还打印出数据集的行数和列数以及每列的数据类型。

甚至head函数也可以改进,因为它不指示列数据类型,并且当有许多变量(许多列)时,它不显示所有变量,而是只显示屏幕上能容纳的数量。相反,我们可以使用glimpse命令显示打印输出的转置版本,以获得更完整的数据概览。

我们可以看到,数据包含 12 个变量:乘客序列号(PassengerId)、二进制乘客生存变量(Survived)、乘客类别(Pclass)、姓名(name)、性别(sex)、年龄(age)、机上兄弟姐妹和配偶人数(Sibsp)、机上子女/父母人数(Parch)、票号(ticket)、已付车费(fare)、客舱号(cabin)和乘客的登机地点(Embarked)。

管道%>%运算符

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

照片由西格蒙德Unsplash 上拍摄

在继续分析之前,我想介绍一下管道操作符。管道操作符的优点是能够绑定函数。更准确地说,它将一个函数的结果作为第一个参数传递给序列中的下一个函数。乍一看,这似乎不是很有用,但是当使用许多函数的组合时,它极大地增加了代码的清晰度。让我们看一个例子。

管道运算符使我们能够从左到右阅读作文,就像我们在阅读一本书一样!一旦你习惯使用它,我保证你永远不会回头。这就是为什么,即使没有它所有的 R 函数都可以使用,我将在这篇文章的剩余部分使用管道。

使用 dplyr 包提取信息

dplyr包包含的函数有助于数据操作以提取有意义的见解。它在一定程度上受到 SQL(结构化查询语言)的启发,所以那些有 SQL 背景的人可能会发现它们非常直观。

重命名列

我们将从使用rename重命名列开始,以便使它们更容易理解。

通过使用rename_with函数将一个函数应用于多个列,也可以进行重命名。例如,我们可能更喜欢所有变量名都用小写字母书写的约定:

根据名称选择列

我们通常对所有的数据集变量都不感兴趣。select函数允许我们对感兴趣的列进行子集划分。

如果所需的列在初始数据框中相邻放置,还有一种更简单的方法来选择它们:

如果我们只想选择姓名和与船上亲属数量相关的变量,该怎么办?当然,我们可以再次使用第一个例子中的技术。然而,select也可以与大量的帮助功能结合,使这些任务更快更容易执行。例如,contains助手允许我们通过部分列名进行搜索。如果我们注意到与亲戚数量相关的两个变量都包含下划线符号,我们就可以使用这个。

类似于contains功能,我们也可以使用starts_withends_with功能。要获得所有帮助函数的列表和解释,您可以通过在 R 控制台中键入?select来查阅帮助文件。

也可以通过指定我们想要从数据中删除的列来执行列选择。这是通过在不需要的列的名称前放置减号来实现的。

让我们通过删除机票、客舱号和与机上亲属/配偶人数相关的列来简化数据集:

基于值的列选择

如果我们只对包含数值的列感兴趣呢?我们可以手动查找变量类型,然后按照上一小节中的步骤操作。

幸运的是,这个繁琐的任务可以自动化,因为通过将selectwhere函数组合起来,也可以基于存储在其中的值来执行列选择。

使用 mutate 转换现有列或添加新列

mutate功能允许我们使用外部数据或数据框中已有的数据转换现有列或创建新列。

r 有一个方便的内置factor类,当分类变量的可能值(级别)有限时,应该使用这个类。我们可以看到性别变量有这样的性质。mutate使我们能够改变列数据类型:

你们中的一些人可能也想缩写性别值。这可以使用case_when功能来完成:

一次转换多列

你们中的一些人可能已经注意到,将 Survived 和 also 列的数据类型从字符转换为因子也不错。

我们可以通过应用mutate + across组合来同时转换多个列,而不是通过逐个指定列来实现。across函数指定要转换的列和要应用的函数。

在讨论select函数的应用时,也可以使用提到的辅助函数来选择across函数中的列。

Filter数据框行

filter函数用于从数据中提取观察值的子集。因此,它可以用来回答关于机上不同乘客群体的问题。

坠机事件中幸存的乘客比例是多少?

只有三分之一多一点的乘客幸免于难。乘客性别对存活率有影响吗?

乘客中女性的比例是多少?

坠机事件中幸存的女性乘客比例是多少?

因此,尽管女性乘客人数较少,但她们的生还几率大约是男性的两倍。当然,这是意料之中的,因为妇女和儿童在救援任务中享有优先权。

接下来,我们将检查存活率如何根据乘客等级而变化。

如果你在下层阶级旅行,生还的机会会更低。这可能是由于富裕乘客的影响,也可能是由于头等舱更靠近救援船停靠的甲板。

还有filter帮助函数if_anyif_all,允许我们一次过滤多个列。这些可用于执行移除具有缺失值的观测值的常见任务。

Arrange基于列值的行

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

照片由托尔加·乌尔坎Unsplash 上拍摄

通常,我们希望根据感兴趣的特定标准对数据进行排序。这是使用arrange通过dplyr完成的。我们先按年龄升序对乘客进行排序,然后按姓名字母顺序对同龄乘客进行排序。默认情况下,排序是升序,但是可以使用desc命令来进行降序排序。

从这个输出,我们可以假设老年乘客更可能是男性而不是女性。此外,年龄较大的乘客似乎是等级较高的乘客。对我来说,这是一个看似合理的说法,因为我知道泰坦尼克号从英国驶向美国,载着许多人寻找新的机会和更好的生活。我认为这样的人会更年轻,因为年纪大的人没有动力搬走。我们将能够用下面两个小节中介绍的工具回答所有这些问题。

summarise获取列汇总统计

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

安妮·尼加德在 Unsplash 上拍摄的照片

当我们对单个值不感兴趣,而是对数据集的汇总统计数据感兴趣时,这个命令非常有用。让我们计算一下乘客的平均年龄。

我们得到了意想不到的结果。这是因为数据中的“漏洞”已被替换为 NA(“不可用”,缺少数据)。我们可以通过在计算汇总统计数据之前过滤数据帧以排除年龄未知的乘客来解决这个问题。在过滤时,我们使用一个特殊的命令来测试数据是否属于NA - is.na类型。

就像使用mutatesummarise也可以与across结合使用,以便一次获得多个列的摘要。

为了回答年龄差异取决于性别和乘客等级的问题,我们可以首先过滤感兴趣的人群,然后计算所需的汇总。然而,这种方法要求我们每次都要写一份新的总结,类似于我们根据乘客等级计算存活率的方法。为了避免这种情况,我们将使用下一小节中介绍的group_by函数。

使用group_by根据分类属性对观察值进行分组

就其本身而言,summarise函数并不那么有用,但是与group_by函数配合使用,它可以快速汇总所有级别的分类变量的信息。group_by函数根据某个变量的类别将数据分组。然后使用summarise将感兴趣的函数分别应用于这些组中的每一组,并将结果组合回单个数据帧。

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

group_by 如何在幕后工作。图片作者。

这种结合最终使我们能够优雅地回答早先提出的假设。

船上 55 岁以上的男性比我们多三倍,所以看来我们的第一个假设是正确的。

让我们也检查一下关于阶级相关的年龄差异的第二个假设:

我们的预感似乎得到了数据的支持,因为乘客等级和年龄之间确实存在关联。

请注意,这种方法也使我们能够更容易地找出基于乘客等级的生存差异,这在filter小节中讨论过。

summarise功能类似,group by命令可以与mutatefilter命令结合使用。

让我们创建一个变量,根据性别按年龄对乘客进行排名。rank命令将帮助我们从最小到最大为一组值分配一个序数。与arrange命令一样,使用desc可以颠倒默认顺序。

例如,将group_byfilter结合起来,我们可以得到每种性别的三名最年长乘客的列表。

结果与我们之前的结论一致,因为年龄最大的女性乘客比年龄最大的男性年轻 17 岁。

事实证明,还有一个方便的slice_max函数可以达到同样的目的,所以上面的片段应该只是作为一个教育的例子。

要取消分组,我们使用ungroup功能。建议在任何涉及分组的转换结束时这样做。这是因为忘记我们已经根据一些变量对数据进行了分组,可能会在进一步的分析中导致意想不到的结果。

组合/匹配数据帧的功能

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

联接函数的工作原理。图片作者。

此前,在计算平均年龄时,我们注意到泰坦尼克号的数据是不完整的。让我们看看这些乘客是谁:

幸运的是,泰坦尼克号的失事如此著名,以至于有许多网页专门列出乘客及其背景。因此,我们可以创建一个新的数据框,其中包含一些数据缺失的乘客的姓名和年龄。

由于丢失的数据分散在 Titanic 数据集中(看看 PassengerId 值,它实际上只是行号),很难手工追加额外的信息。这些类型的问题可以通过left_join来解决,这是一种基于公共标识变量将数据从一个数据帧添加到另一个数据帧的功能。

请注意,我们有 Age.x 和 Age.y,而不是单个年龄变量。这是为了让我们能够辨别年龄变量来自哪个数据框。我们可以使用mutatecase_when将两个年龄列合并成一个。

与 tidyr 一起整理

顾名思义,tidyr包包含将数据帧转换成整齐格式的命令。泰坦尼克号的数据框架已经很整洁了,因为每一行代表一个乘客,每一列代表一个独立的乘客特征。然而,数据集通常不是这样。此外,在某些情况下,如绘图,可能需要“不整洁”的格式。下一小节将说明这样一个例子。

使用 pivot_wider 和 pivot_longer 进行宽长格式转换

这些可能是tidyr包中最有价值的功能。当我们想要将多个列转换成具有多个子组的单个列时,pivot_longer函数非常有用。下图说明了这是如何工作的。

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

枢纽函数的工作原理。图片作者。

左边的表格包含三个相关的列,它们都代表一种相对类型。知道了这一点,我们可能希望将它们分组到一个“相对”列中。这使得数据更长(因此函数名也更长)。此外,它变得凌乱,因为同一个人是由三个独立的行代表。我们为什么要这么做?

如果我们想做一个条形图,在水平轴上显示相对类型,我们必须提供一个变量。我们可以用右边的格式做到这一点,但不能用左边的格式。

现在让我们展示如何将这些转换应用到泰坦尼克号数据上。为了便于理解转换,我们将只选择 Titanic 数据帧的前三行:

通过应用pivot_longer,我们可以使用更少的列显示相同的数据。例如,我们可以有一个乘客姓名列和两个包含乘客属性所有信息的列(乘客级别、年龄、性别、生存)。为什么属性有两列而不是一列?因为一列必须包含有关所讨论的属性的信息,另一列必须包含该属性的值。因为我们要组合数字和字符值,所以我们必须首先将所有值转换为相同的类型(字符)。

正如所料,行数增加了 7 倍,因为现在我们必须重复每个乘客的姓名 7 次——对 Parameter_name 列中的每个参数重复一次。当然,与上图中的例子相反,以这种方式对参数进行分组是没有意义的,因为它们是不相关的。但是,如果您经常处理数据,这些类型的问题将是最不重要的,因为输入至少是一致的。您可能永远也不会自己创建这样的数据,但是我在这里这样做是为了向您展示当有人把它带过来时,如何摆脱这种混乱(相信我,最终会有人这样做……)。

作为pivot_longer函数的逆函数,pivot_wider就是答案。

当然,我们还应该将列值转换回它们正确的类型。

分离()和联合()

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

威尔·弗朗西斯在 Unsplash 上拍摄的照片

和前面提到的pivot_longerpivot_wider一样,这两个函数也是互逆的。使用separate功能的最简单的例子是分隔日期记录。假设我们有一列日 _ 月 _ 年格式的奥运奖牌获得者的生日,我们想计算获奖者的平均年龄。这意味着我们必须从日期中提取年份。使我们能够做到这一点,例如,从一个日期变量生成三个变量:日、月和年。unite函数的工作方式正好相反,将多个变量连接成一个变量(例如,将日、月、年转换成格式为日 _ 月 _ 年的日期变量)。本质上,它只是一个带有一些附加参数的paste函数,比如允许从数据框中自动删除输入列。

看看乘客的名字,我们可以看出他们可以分成几组。逗号前的第一个字是姓,后面是头衔和名。将这些组分开是有用的,因为我们可以获得额外的信息。例如,乘客头衔中的婚姻状况信息—已婚乘客的先生/夫人,单身乘客的主人/小姐。

对于那些不熟悉正则表达式的人来说,表达式[,\\.]可以翻译为:每当遇到逗号或点时,就将字符串分解。为什么我们需要点前面的反斜杠?因为点本身在正则表达式中有特殊的含义,所以这是告诉 R 我们感兴趣的是点字符本身,而不是它的特殊功能。

这些标题让我们对乘客群体的社会结构有了深入的了解,否则我们很难了解。

结论

我希望这篇文章对你有用,并且你学到了一些新东西。我试图涵盖尽可能多的常见任务和问题。然而,没有一个单独的(非模拟的)数据集包含您在数据分析中可能遇到的所有问题,因此还有许多问题和场景没有涉及。此外,没有可视化的数据,任何探索性的数据分析都是不完整的。因为加上这个会让这篇文章太长,所以我把它留到以后的故事里。然而,如果您有任何问题或遇到一个复杂的问题(包括可视化)想要使用tidyverse工具箱解决,请随时发表评论!

附言:要快速浏览/提醒dplyrtidyr封装中的功能及其用法,您可以使用 RStudio 提供的便捷的备忘单

用屏幕录制 DIY 你自己的视频编辑器

原文:https://towardsdatascience.com/diy-your-own-video-editor-with-screen-recording-eb6046f5cea7

简化屏幕录制、视频编辑和格式转换的简单 Python 解决方案

我们中的许多人使用记录器软件来捕捉屏幕活动作为视频文件。不同的录像机可能支持不同的视频格式,如 AVIMP4GIF 等。输出的视频通常不是我们想要的。也许我们需要另一个编辑器来裁剪和修剪视频,或者将它们转换成我们需要的格式。

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

和平:作者的形象

为了简化这个过程,本文介绍了一个简单的 Python 解决方案,它是一个高度可定制的 DIY 工具,具有所有这些功能,即屏幕录制、简单的视频编辑和格式转换。

在接下来的几节中,作者将带您了解如何创建这样的工具。本文中的所有代码都可以在我的 git 库中找到。

屏幕录制

python 库,即 PynputPyAutoGUIOpenCV 用于记录屏幕活动。

选择要录制的区域

这里使用的 Pynput 是帮助用户通过鼠标点击(左键)选择屏幕上的一个区域。它记录鼠标点击事件中的鼠标位置。

可以使用左上角点( xy )、宽度 w 和高度 *h、*定义一个选中的矩形区域,如下图所示。

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

选定的矩形区域(图片由作者提供)

给定按下和释放事件上的鼠标位置,下面的函数返回所选区域的参数,即 xywh

如果您需要捕获整个屏幕,您可以使用下面的 PyAutoGui 命令来获取该区域。

wh = pyautogui.size()
region = [0, 0, wh.width, wh.height]

记录该地区的屏幕活动

PyAutoGUI 是一个跨平台的鼠标键盘控件和一个简单的 API。PyAutoGUI 获取单独的截图或帧,其中区域是上面选择的矩形。

然后用 OpenCV 将连续截图保存为视频。它支持不同的视频格式,如 AVI 和 *MP4。*也可以设置其他参数,例如每秒帧数(fps)。在 OpenCV 中,将图像从 BRG(蓝红绿)转换到 RGB(红绿蓝)颜色模型也是必要的。

该屏幕录制功能的代码如下:

下面显示了一个录制视频的示例。这个视频包含了该地区所有的荧屏活动。有必要移除不需要的片段。以下部分将介绍如何编辑该视频,即视频修剪和裁剪。

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

录制屏幕的示例视频

视频编辑

MoviePy 是一个流行的视频编辑 Python 模块。它支持基本操作(如修剪、裁剪、连接、标题插入)、视频处理或创建高级效果。它可以读写最常见的视频格式,包括 GIF

  • 视频修剪

我们可以使用以下命令来修剪上面视频中不想要的片段:

clips = [
    [(0, 2), (0, 8)], 
    [(0, 24), (0, 32)]
]
video = VideoFileClip("test_recording.mp4")
clipped = [video.subclip(*clip) for clip in clips]
final_clip = concatenate_videoclips(clipped)

其中 clips 是要保留在视频中的两个视频片段(0m2s-0m8s)和(0m24s,0m32s)。运行完这几行代码后,这两段之外的其他剪辑将被删除。

  • 视频裁剪

给定一个具有两个对角位置(即左上角和右下角)的矩形,可以使用下面的 MoviePy 命令来完成对视频的裁剪。

x1, y1 = (10, 240)
x2, y2 = (610, 680) 
cropped = vfx.crop(video, x1=x1, y1=y1, x2=x2, y2=y2)
  • 格式转换

MoviePy 可以将视频导出为 GIF 图片或 MP4 等文件格式,如下所示:

video.write_gif("saved.gif", fps=10)
video.write_videofile("saved.mp4", fps=10)

最后,经过修剪和裁剪后,视频如下图所示:

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

编辑后的视频:作者提供的图片

从图像创建视频

使用 MoviePy,我们还可以为一系列图像创建一个视频。

这里我们有 30 张同样大小的图片,名字分别是 img_0.pngimg_1.png 、…、 img_29.png 。要将这些图像连接成标题为 Peace 的顶部 GIF 图片,我们只需运行下面这段代码。

images = ["images/img_{}.png".format(i) for i in range(30)]
clips = [ImageClip(m).set_duration(1) for m in images]
concat_clip = concatenate_videoclips(clips, method="compose")
concat_clip.write_gif(out_filename, fps=1)

在上面的代码中,每个图像剪辑的持续时间设置为 1 秒,对应的 fps=1。

结论

本文介绍了一个简单的 DIY 视频编辑工具的解决方案,带有额外的屏幕录制功能。该工具基于开源 Python 库 Pynput、PyAutoGui、OpenCV 和 MoviePy 构建。你可以在方便的时候使用它。

为了进一步定制您的工具,更多的 MoviePy 或 OpenCV 功能,如对象检测和跟踪等。可能会添加。

感谢您的阅读。请随意发表评论。如果你喜欢这篇文章或这个 DIY 工具,请分享给你的朋友,并关注我。

参考

  1. https://pynput.readthedocs.io/en/latest/
  2. https://pyautogui.readthedocs.io/en/latest/
  3. https://opencv.org/
  4. https://zulko.github.io/moviepy/

Django 完全初学者的第一步:快速教程

原文:https://towardsdatascience.com/django-first-steps-for-the-total-beginners-a-quick-tutorial-5f1e5e7e9a8c

了解如何在 Django 应用程序中嵌入 Plotly 图形,以及其他主题

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

费萨尔Unsplash 上的照片

如果你喜欢用 Python 编程,并且想第一次进入 web 开发领域,我认为 Django 可能非常适合你。Django 是最受欢迎的 Python 框架之一,它为 web 开发中非常常见的任务提供了出色的内置解决方案,使快速高效地实现项目和编写代码成为可能。此外,Django 也被大公司用于制作,比如 Instagram 和 Spotify 。因此,这一出色的 Python 框架也很有可能满足您的项目需求。

我最近从 Coursera 的Django for Everybody Specialization毕业,我惊讶于使用 Django 内置视图类构建一个具有用户认证和登录功能的完整 CRUD 应用程序是如此的快速和简单。我将永远感谢来自密歇根大学的 Charles Severance 教授,因为他组织了这样一个令人惊叹和兴奋的课程,将我的 Django 技能提升到了一个完全不同的水平。

因此,我决定通过创建一个简单的 CRUD 应用程序来管理和显示我喜欢的电影中的数据,来实践我从这门课程中学到的东西。然而,在以后的文章中展示这种 CRUD 的代码之前,我想收集一些关于 Django 入门的介绍性信息。因此有了这个教程。

我希望这份材料能帮助一些人。至少,它帮助了我,因为这是一个回顾 Django 中一些基本概念和实践的机会。

我使用 Python 3.10.2 和 Django 4.0.2 开发了本教程。我还使用 Git Bash 作为我的终端,这是在 Windows 操作系统中运行 Linux 命令的好方法。

第 1 部分:第一步

  1. 创建一个名为films_project的项目文件夹并进入其中。
mkdir films_project
cd films_project

如果没有另外提到,本教程中的所有终端命令都应该在films_project目录中运行。

2.使用venv Python 模块创建一个新的虚拟环境。我将我的虚拟环境命名为.myenv,但是如果您愿意,也可以使用其他名称。

python -m venv .myenv

3.通过针对您选择的终端和操作系统运行正确的命令来激活虚拟环境。下面的命令在我的 Git Bash for Windows 中有效。如果你对用venv激活虚拟环境有疑问,请查阅 Python 文档

source .myenv/Scripts/activate

激活它之后,您应该会在终端中看到您的虚拟环境名称,如下例所示:

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

作者图片

从现在开始,所有命令都必须在激活虚拟环境的情况下运行。

4.用 PIP 安装 Django:

pip install django

5.自己开始一个叫project的 Django 项目。

重要提示:不要忘记这个命令末尾的点。

django-admin startproject project .

运行这个命令将创建一个manage.py文件和一个名为project的文件夹,里面有五个文件(算上__init__.py一个)。因此,您的films_project目录树现在应该是这样的:

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

作者图片

请记住.myenv是我选择命名我的虚拟环境文件夹的方式,它在上面的图像中是折叠的(里面有一堆文件夹和文件,与我们的教程无关)。

6.启动名为films的 Django 应用程序:

python manage.py startapp films

Django 项目可以有一个或多个应用程序。您可以将 Django 中的应用程序视为在不同项目之间重用代码的一种方式。

这个命令python manage.py将在您的 Django 项目中频繁使用,所以请习惯它。另外,在终端中只运行python manage.py,不使用任何其他参数,将会显示所有可用选项的列表。

上面的startapp命令创建了一个名为films的新文件夹,里面有一些文件和一个文件夹。查看它们:

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

作者图片

films_project/films/migrations中唯一的文件是一个__init__.py文件,表明这个迁移目录将来应该作为一个模块使用。

7.每当我们创建一个新的应用程序时,我们必须将它添加到settings.py文件中的已安装应用程序列表中。我们现在就开始吧。打开project/settings.py,在INSTALLED_APPS变量中保存的列表末尾添加一个带有films应用的字符串。不要删除或更改列表中已有的任何其他字符串:

# inside project/settings.pyINSTALLED_APPS = [ 'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles', #new value below:
    'films.apps.FilmsConfig',
]

settings.py仍然打开时,您可以在LANGUAGE_CODETIME_ZONE变量中更改整个应用程序的默认语言和时区。

8.运行以下代码检查错误:

python manage.py check

如果您收到消息’系统检查未发现问题(0 沉默)',这意味着您的申请是好的。我们现在准备启动本地服务器,并第一次检查我们的 Django 网站。

9.运行以下命令,在默认端口(8000)启动本地服务器

python manage.py runserver

如果 8000 端口已被计算机中的另一个应用程序占用,请更改端口。只需在命令中添加新的端口号(例如 8050 ):

python manage.py runserver 8050

10.打开浏览器并转到以下地址之一:

[http://127.0.0.1:8000/](http://127.0.0.1:8000/)

或者

[http://localhost:8000/](http://localhost:8000/)

如有必要,用您的新端口号替换8000

通过访问这个运行在本地机器上的服务器,您将看到一个新项目的 Django 默认主页。

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

作者图片

11.注意,在films_project文件夹中创建了一个名为db.sqlite3的新文件。这是因为 sqlite3 是在project/settings.py文件中配置的默认数据库。然而,Django 允许与许多其他数据库连接;您只需要进行正确的配置。我们现在不会谈论这个话题,因为这个话题已经超出了我们当前基础教程的范围。

12.Django 已经为您创建了另一条路线。首先,确保您的本地服务器正在运行(也就是说,您在终端中执行了python manage.py runserver,并且那里没有错误消息)。然后转到管理页面查看:

[http://localhost:8000/admin/](http://localhost:8000/admin/)

当您尝试访问下面的链接时,您可能会看到一条非常难看的错误消息,在异常值字段中显示信息“no this table:django _ session”。我想让你看看那一页,因为我们可以从编程过程中出现的错误信息中学到很多。对此有一个非常简单的解决方法:我们只需要运行命令在数据库中创建管理应用程序使用的表。所以:

13.运行以下命令创建数据库表。

python manage.py migrate

大约十几条消息将显示在终端控制台中,表示迁移已被应用。这意味着 Django 为数据库中的默认应用程序创建了必要的表,比如 admin。实际上,如果您使用一个 sqlite3 服务连接到数据库并列出它的所有表,您将会看到这些由您刚才运行的migrate命令创建的新表。

14.您还需要创建一个超级用户来登录管理区。使用以下终端命令完成此操作:

python manage.py createsuperuser

告知用户名、电子邮件和密码。然后,使用python manage.py runserver重启服务器,并使用您的信息登录到管理区域。您将看到以下页面:

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

作者图片

这是 Django 从一开始就提供给开发人员的许多内置资源之一,我想让您知道它的存在,尽管我们现在不会在本教程中探究它。管理区是 Django 的强大特性之一,因此值得花时间学习如何使用它。

第二部分:我们的第一条路线和风景。

15.让我们创建我们的第一个新路由,它将替换我们刚刚访问的 Django 默认页面。我们将通过修改project/urls.py文件来实现。注意,我们还向它添加了一个新的 import 语句。

# inside project/urls.pyfrom django.contrib import admin
from django.urls import path
from films import views    # added 
urlpatterns = [
    path('', views.home, name='home'),    # added
    path('admin/', admin.site.urls),  
]

16.如果我们现在运行python manage.py check,它将返回一个错误,因为你的films/views.py是空的。让我们通过向该文件添加以下函数来纠正这一点:

# inside films/views.pyfrom django.shortcuts import render
from django.http import HttpResponse   # added def home(request):
    return HttpResponse('This is the home page.')

现在,再次访问http://localhost:8000,检查新修改的页面如何显示我们在home视图功能中设置的信息:

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

作者图片

干得好!它看起来一点也不花哨,但我们需要欣赏我们在这里取得的成就:我们使用 Django 和 Python 创建了我们的第一个网页,所有这些只需编写几行代码并在终端中运行几个命令。此外,所有这些知识将有助于我们在未来创建更加复杂和令人兴奋的网站,这一点可以肯定。如果我们现在建立了足够好的基础,将来我们可以成为非常熟练的 Django 开发人员,制作伟大而有用的 web 应用程序,同时享受用 Python 编程的乐趣。

在这一点上,对 Django 所认为的视图做一些简单的考虑是很有趣的。与大多数 MVC(模型-视图-控制器)框架不同,在 MVC 框架中,视图指的是应用程序的前端部分,Django 将视图称为配置应用程序逻辑和规则的代码。Django 中的接口部分被称为模板,这使得 Django 成为一个 MVT(模型-视图-模板)框架。

我再说一遍:Django 中的视图是我们创建控制器和应用程序逻辑的地方;不是前端的部分(这些在 Django 里叫模板)。我在这里提到它是因为这一点让许多人感到困惑,尤其是那些来自经典 MVC 模式的人。

从我们目前所做的可以看出,films/views.py中的home函数简洁明了;它只接受一个请求对象作为其参数,并返回一个响应对象,该对象在 DOM 中写入一个短语,并在所选的 URL 中显示它。但是我们也可以在视图函数中将 HTML 元素传递给响应对象,这样浏览器就可以用不同的字体大小和格式显示它们。

17.然后让我们修改films/views.py中的home函数如下:

# inside films/views.pyfrom django.shortcuts import render
from django.http import HttpResponse def home(request):
    # remove these print statements later
    print('\n\nRequest object:', request)
    print('Request object type:', type(request), '\n\n')

    html_tags = '''
    <h1>This is the Home Page</h1>
    <h3>Thanks for visiting us</h3>
    <p>MVT means:</p>
    <ul>
      <li>Model</li>
      <li>View</li>
      <li>Template</li>
    </ul>'''

    response = HttpResponse(html_tags) # remove these print statements later
    print('Response object:', response)
    print('Response object type:', type(response))
    print('\n\nUser-agent info :', end='')
    print(request.META['HTTP_USER_AGENT'], '\n\n')

    return response

如果你的 Django 本地服务器正在运行,用CTRL + C停止它,运行python manage.py check查看是否有错误,然后用python manage.py runserver再次启动服务器。接下来,进入http://localhost:8000查看新网页。每次刷新这个页面,都会向 Django 服务器发送一个 GET 请求,并执行home函数视图。您可以检查一些关于请求和响应对象的信息,我们要求在终端控制台中打印这些信息。

这里有一个重要的警告:我们永远不会在 Django 产品代码中使用带有 HTML 标签的字符串。否则,我们将把我们的申请提交给 XSS 恶意攻击。因此,在第一个例子中,我们将在 Django 视图中使用 HTML。稍后我将向您展示如何在 Django 中使用模板来呈现 HTML 页面。

我们现在将创建新的路线来巩固我们到目前为止所学的内容。

18.创建文件films/urls.py

touch films/urls.py

19.在project/urls.py中添加新路线。它将作为我们稍后为films应用创建的路线的入口点。

# inside projects/urls.pyfrom django.contrib import admin
from django.urls import path, include
from films import views urlpatterns = [
    path('', views.home, name='home'),
    path('admin/', admin.site.urls),
    path('films/', include('films.urls')),   # added
]

20.用新的路线信息填充films/urls.py文件。

# films/urls.pyfrom django.urls import path
from . import views app_name = 'films'urlpatterns = [
    path('', views.main, name='main'),
    path('user_info/', views.user_info, name='user_info'),
]

21.在films/views.py末尾增加新的视图功能:

# films/views.py(...)def main(request):
    message = '<h1>This is the films MAIN page.</h1>'
    return HttpResponse(message)def user_info(request):
    message = '<h1>This is the films USER_INFO page.</h1>'
    return HttpResponse(message)

22.现在访问创建的路由,检查它们是否显示正确的消息:

http://localhost:8000/films/
http://localhost:8000/films/user_info

第 3 部分:让视图呈现 HTML 模板文件

现在是时候开始使用 Django 模板了。现在,它们将是由视图函数呈现的简单 HTML 文件。

23.创建保存 HTML 文件的必要文件夹。

重要提示:请注意,这些新文件夹中有两个被命名为templates:一个在films中,另一个直接位于films_project的项目根目录中,与films处于同一层级。

mkdir templates
mkdir films/templates
mkdir films/templates/films

24.创建以下 HTML 文件:

touch templates/home.html
touch films/templates/films/main.html
touch films/templates/films/user_info.html

25.打开project/settings.py,导入os模块,在变量TEMPLATES中,用以下信息填充TEMPLATES['DIR']键中的空列表(只改变这一行,保留其他行):

TEMPLATES = [
{(...) 'DIRS': [os.path.join(BASE_DIR, 'templates')],(...)}
]

26.将以下信息放入相应的 HTML 文件中:

  • templates/home.html:
<h1>This is the Home Page</h1>
<h3>Thanks for visiting us</h3>
<p>MVT means:</p>
<ul>
  <li>Model</li>
  <li>View</li>
  <li>Template</li>
</ul>
<h3>Link to the website pages:</h3>
<ul>
  <li><a href="{% url 'home' %}">Home Page</a></li>
  <li><a href="{% url 'films:main' %}">Films Main Page</a></li>
  <li><a href="{% url 'films:user_info' %}">Films User_Info Page</a></li>
</ul>
  • films/templates/films/main.html
<h1>This is the films MAIN page.</h1>
<h3>Link to the website pages:</h3>
<ul>
  <li><a href="{% url 'home' %}">Home Page</a></li>
  <li><a href="{% url 'films:main' %}">Films Main Page</a></li>
  <li><a href="{% url 'films:user_info' %}">Films User_Info Page</a></li>
</ul>
  • films/templates/films/user_info.html
<h1>This is the films USER_INFO page.</h1>
<p>Username: Fabrício</p>
<h3>Link to the website pages:</h3>
<ul>
  <li><a href="{% url 'home' %}">Home Page</a></li>
  <li><a href="{% url 'films:main' %}">Films Main Page</a></li>
  <li><a href="{% url 'films:user_info' %}">Films User_Info Page</a></li>
</ul>

这里有一个重要的提示:如果你现在访问这些页面,你会发现什么都没有改变。这是因为我们没有在视图中进行任何更新。我们需要让每一个视图呈现正确的模板。

27.因此,将films/views.py中的所有内容替换为下面的新代码:

from django.shortcuts import render def home(request):
    return render(request, 'home.html')def main(request):
    return render(request, 'films/main.html')def user_info(request):
    return render(request, 'films/user_info.html')

现在访问网址并注意不同之处。浏览链接,在页面间快速移动。

第 4 部分:使用基础模板避免代码重复

我不知道你是否注意到了,HTML 文件中的代码有许多重复的行。这一点都不可取,因为它违反了 DRY ( 不要重复自己)原则,我们需要修正这一点。Django 允许使用模板标签,以一种非常简单的方式,将应该出现在多个页面中的 HTML 代码编写在一个 HTML 文件中。

28.创建一个templates/base.html文件。

touch templates/base.html 

我们现在将在我们的项目中使用一些来自 Bootstrap web 设计框架的代码。不幸的是,我不能在这里解释基本的 HTML、CSS 和引导代码是如何工作的。否则,本教程将会比现在更加广泛。然而,如果你想从事 web 开发,即使是在后端,这些都是很好的学习技巧。在我看来,新知识从来没有坏处。

所以,如果你想知道更多关于如何创建漂亮的网页界面,我建议你去看看丹尼尔·沃尔特·斯科特的名为响应式网页设计基础——html 5 CSS3 引导的伟大课程。这些视频信息量很大,练习也很棒,丹尼尔设法以一种非常有效和有趣的方式传播他的知识。

29.用下面的代码填充templates/base.html文件。这将是将在其他 HTML 页面中扩展的代码:

请注意base.html中出现的以下新结构:

  • {% block head %}{% endblock %}
  • {% block content %}{% endblock %}
  • {% if title %}{% else %}{% endif %}
  • {{title}}

符号{% %}在 Django 模板语言中被广泛使用。使用基本模板,{% block BLOCKNAME %}{% endblock %}标签用于标记我们应用程序中每个页面的特定代码块的插入点。

Django 模板语言使用了很多过滤器,这些过滤器允许它在模板中执行函数和方法。例如,我们已经看到了在我们代码的早期阶段使用的{% url %}过滤器:它接收一个字符串作为它的参数,以'app_name:route_name'的形式,并且它返回一个 web 资源的绝对路径。例如:{% url 'films:user_info %}将获得名为user_info的路线,在films应用程序中,它将以字符串形式返回其路径,并将其呈现在 HTML 页面上。

我们还可以注意到,Django 允许在模板中使用条件语句。正如我们将在后面看到的,循环的在这里也是可能的,这有助于创建更易维护的代码。

最后,注意{{title}}中的双花括号。这是 Django 呈现视图传递给模板的 Python 变量的方式。我们还没有做到这一点,但我们以后会的。

30.替换 HTML 文件的内容,如下所示:

  • templates/home.html:
{% extends 'base.html' %}{% block content %}<h1>This is the Home Page</h1>
<br>
<h3>Thanks for visiting us</h3>
<br>
<p>MVT means:</p>
<ul>
  <li>Model</li>
  <li>View</li>
  <li>Template</li>
</ul>{% endblock  %}
  • films/templates/films/main.html:
{% extends 'base.html' %}{% block content %}<h1>This is the films MAIN page</h1>
<br>
<h4>Some Pixar Movies:</h4><ul>
  {% for number in '012'|make_list %}
    <li>Toy Story {{forloop.counter}}</li>
  {% endfor %}
</ul>{% endblock  %}
  • films/templates/films/user_info.html:
{% extends 'base.html' %}{% block content %}<h1>This is the films USER_INFO page</h1>
<br>{% if userinfo %}
  <h4>username: {{userinfo.username}}</h4>
  <h4>country: {{userinfo.country}}</h4>
{% else %}
  <h4>username: not informed</h4>
  <h4>country: not informed</h4>
{% endif %}{% endblock  %}

注意这最后三个 HTML 文件是如何以{% extends 'base.html' %}开始的,这表明我们正在扩展来自base.html文件的模板信息。而且我们希望每个页面显示的 HTML 在{% block content %}……{% endblock %}标签之间,显示我们希望它从base.html插入到这些标签所在的地方。

现在用python manage.py check查找错误,然后用python manage.py runserver运行服务器。看看我们的页面现在有多像样。

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

作者图片

第 5 部分:将 PYTHON 变量传递给模板

31.现在打开films/views.py,为这个新版本替换旧代码。然后运行服务器,注意这一变化如何影响页面标题中显示的信息(浏览器选项卡中显示的文本元信息)和user_info页面中显示的用户信息。

正如我们所看到的,render()函数可以接收一个字典(通常命名为contextctx)作为它的第三个参数。然后,我们可以通过使用不带引号的键来访问这些字典值。

例如,注意title变量及其值如何出现在视图和base.html模板条件结构中。home视图函数不传递标题信息,因此title值将为None,并且将执行else子句,在主页上显示默认标题。另一方面,mainuser_info视图函数在上下文字典中都有一个键'title',所以它的值将是‘truthy’,并且将执行if子句,在呈现模板时在标签浏览器中显示title值。

类似的事情发生在由user_info视图函数传递给user_info.html模板的userinfo变量上。还要注意,我们将使用点符号来访问嵌套值,类似于 Javascript 对象中使用的符号。如果您尝试从 Python 中使用context[key]结构访问嵌套字典中的值,将无法工作,并且会出现错误。

第 6 部分:使用 DJANGO 模型创建自己的数据库表

32.打开films/models.py,插入下面的代码。我们将创建我们的第一个 Django 模型。

注意,Django 模型是作为继承自models.Model Django 类的类创建的。我们在这里创建两个表:genrefilm,实际上分别有 2 和 4 列。我们不必在代码中为每个模型添加id列,因为在project/settings.py文件中,变量DEFAULT_AUTO_FIELD被配置为为每个模型表创建一个具有自动递增整数值的id列。

还要注意的是,Film模型有一个名为genre的外键列,具有一对多关系。这两张表之间的关系并不完美。不过,出于教学目的,我们在这里使用一个更简化的模型,因为多对多模型更复杂,应该马上学习。

32.运行python manage.py makemigrations来创建迁移文件,这些文件将设置 Django 必须如何创建这两个新表filmgenre

33.现在运行python manage.py migrate命令,按照上次迁移中创建的指令,在db.sqlite3数据库中实际创建两个表。它们在数据库中的名称前面会有应用程序名称,所以实际的表名将是films_filmfilms_genre,如果您想运行一些 SQL 代码来检查它们的话。

现在,我们准备填充我们创建的这些新表。我将在这里展示两种不同的方法:

  • 使用管理区
  • 使用 Django shell

34.打开films/admin.py,添加以下代码:

from django.contrib import admin
from .models import Film, Genre admin.site.register(Film)
admin.site.register(Genre)

通过这样做,我们使得管理应用程序可以访问FilmGenre模型。

35.现在转到http://localhost:8000/admin/,输入你创建的超级用户的登录信息,检查两个新模型是如何出现在屏幕上的:

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

作者图片

36.点击Films链接,找到Add Film按钮,填写字段并保存信息。你可以点击旁边的绿色加号在这个页面上添加一个新的流派。这将在genre表中插入一个新行。至少保存三部新电影。我保存了四部《黑客帝国》电影,你可以看到它们的名字被很好地列在管理区。我可以点击他们中的任何一个,对他们的数据进行更改,然后按保存按钮。所有这些更改都会自动保存在 Django 应用程序的 sqlite3 数据库中。

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

作者图片

37.现在停止服务器并在终端中运行python manage.py shell。Django 解释器将会打开。这是另一个非常酷的特性:它不仅允许你用解释器运行 Python 代码,还可以加载你所有的项目文件。因此,您可以使用您的项目文件在这里运行 Django 命令,比如我们刚刚创建的模型。

我将使用 Django ORM 中的一些命令。它允许我们编写在数据库中运行查询的 Python 代码。使用 ORM 的优势在于,如果我们想要将数据库连接更改为 MySQL 或 PostgreSQL,我们只需要在project/settings.py中设置正确的数据库配置。所有使用 Django ORM(带有 Python 代码)的查询都将保持不变,这在我看来很棒。

38.在我们刚刚初始化的 Django shell 中键入以下命令。这将创建新的FilmGenre对象,将它们保存到各自的表中,并显示一个针对films_film表中所有电影的选择查询的快速示例。

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

作者图片

现在,我们将使用我们创建的这些新模型,并将它们连接到我们的视图,以便数据库数据可以从films应用程序显示在我们的主网页上。

39.在films/views.py内部,导入Film模型,改变main视图功能。保持其他视图不变:

40.打开films/main.html并将其内容更改为以下代码。请注意我们如何使用 for 循环来显示保存在数据库中的所有电影信息。

{% extends 'base.html' %}{% block content %}<h1>This is the films MAIN page</h1>
<br>
<h4>Films saved in our database:</h4><ul>
{% for film in films_list %}

  <li>{{film.title}} ({{film.year}}, genre: "{{film.genre}}")</li>{% endfor %}
</ul>{% endblock  %}

下面是我接入[http://localhost:8000/films](http://localhost:8000/main.)后的画面。它显示了我保存在数据库中的所有五部电影(四部黑客帝国电影和海底总动员)。

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

作者图片

这只是一个简单的例子,展示了我们如何显示数据库中的信息,并将其发送到我们的页面之一。实际上,Django 有一些定制的视图,以类的形式构建,我们可以用它们来完成一些最常见的任务,比如在网页上列出所有的观察结果(或者部分观察结果)。这是由**django.views.generic.list.ListView**类完成的。

在这个介绍性教程中,我们不会讨论更多关于视图类的内容。如果你想了解更多信息,请查看 Django 文档页面。

第 7 部分:查询参数

到目前为止,我们只处理了 GET 请求。当我们用 GET 方法发出请求时,我们可以传递直接附加到 URL 的参数,称为查询参数。我们将修改user_info视图函数,使用这些参数作为模板的输入。

41.在films/views.pyuser_info函数中进行以下修改,保留文件中的其他行。

在这个新版本中,我们使用了一个if / elif结构来检查请求方法。我们还不会访问elif部分,但是我已经选择将它写在代码中,这样您就可以看到我们如何根据请求方法是 GET 还是 POST 来让视图函数运行代码。例如,如果我们使用基于类的视图,我们将有两个类方法(.get().post()),每个方法处理各自的请求类型。

我还想提醒您注意重要的request.GET属性,它使用 URL 中使用的所有查询参数来访问字典。

42.现在,访问user_info URL 并添加必要的查询参数,如下例所示:

[http://localhost:8000/films/user_info/?username=Neo&country=USA](http://localhost:8000/films/user_info/?username=Novo&country=Sui%C3%A7a)

请注意问号是如何用于开始编写查询参数的。这里我们有两个(用户名和国家),值分别为“Neo”和“USA”。此外,我们可以观察到查询参数名称/值由等号分隔,而&符号用于分隔不同的参数。

继续更改参数值,按 enter 键,并查看页面上的值将如何变化。还可以查看控制台,了解查询参数字典的结构。您可以使用request.GET.get(KEY)代码获得它的任何值。

第 8 部分: HTML 表单和帖子请求

现在让我们用 POST 方法构建一个 HTML 表单。

43.创建films/templates/films/user_form.html文件,并将以下 HTML 代码放入其中:

44.在templates/base.html导航栏中做一个小改动,添加一个链接到新的user_form页面。我不会在这里重复所有的base.html代码;我将在结束的</ul>标签之前添加额外的<li>标签:

<li class="nav-item active">
  <a class="nav-link active" href="{% url 'films:user_form' %}">UserForm</a>
</li>

45.向films/urls.py添加新路线。

from django.urls import path
from . import viewsapp_name = 'films'urlpatterns = [
    path('', views.main, name='main'),
    path('user_info/', views.user_info, name='user_info'),

    ## new route below ##
    path('user_form/', views.user_form, name= 'user_form'),
]

46.在films/views.py中,一方面修改 imports 和user_info视图,另一方面创建user_form视图。由于这里做了很多修改,所以我给出了所有修改过的文件代码:

让我们更详细地评论一下这个新的films/views.py代码。当我们第一次访问http://localhost:8000/films/user_form时,这将是一个 GET 请求,因此视图中的if子句将被执行,只是加载各自的模板。但是当我们填写表单字段并按下 submit 按钮时,POST 方法被激活,并且运行elif子句中的代码。

正如我在 Charles Severance 教授的课程中所学到的,将 POST 请求中的数据发送到不同于表单所在的 URL 是一个很好的做法。当我们这样做时,一个 GET 请求将被调用到新的 URL,并且 POST 数据将会丢失。

因此,在我们进入下一页之前,我们需要保存表单中传递的数据。我们通过对每个输入字段使用request.POST.get(KEY)来做到这一点,其中KEY是来自相应的<input> HTML 标签的 name 属性的值,所以我们不能忘记设置它们。然后我们可以将这些值传递给位于request.session的另一个字典,这非常有帮助。它允许将数据存储在当前浏览器会话中,并在不同时刻由我们的代码检索。这就是为什么我们可以在user_info视图中使用保存在会话字典中的信息并显示它们。

第 9 部分:路线参数和详细页面

47.在films/urls.py创建新路线。我们将只在下面显示这一行,但是您已经知道如何将它插入到文件中。

(...)path('<int:id>/details', views.details, name='details')(...)

注意新的符号<int:id>。这里的< >符号显示我们正在处理一个新的参数类型,称为路由参数。int显示它是来自int类的一个对象,并且id告诉 Django 这里需要一个主键。

48.用以下内容创建films/templates/films/details.html模板。我们现在将使用一个带有一些引导类的 HTML 表结构。

{% extends 'base.html' %}{% block content %}<h1>Film details:</h1>
<br><table class="table" style="width: 30%">
  <thead>
    <tr>
      <th scope="col">Attribute</th>
      <th scope="col">Value</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td scope="row">Title:</td>
      <td class="text-dark">{{film.title}}</td>
    </tr>
    <tr>
      <td scope="row">Year:</td>
      <td>{{film.year}}</td>
    </tr>
    <tr>
      <td>Genre:</td>
      <td>{{film.genre}}</td>
    </tr>
  </tbody>
</table>{% endblock  %}

49.在films/views.py中添加新的details视图。我注释掉了一行代码,它也可以用于通过 id 查询单个元素。注意电影 id 是如何作为一个额外的参数传递给视图的。

def details(request, id):
    film = Film.objects.get(id=id)
    # other query option:
    # film = Film.objects.filter(id=id)[0]
    context = {'film': film}
    return render(request, 'films/details.html', context)

50.现在选择一条类似http://localhost:8000/films/1/details的路线,并查看这部电影的详细信息。手动更改 id 号,以便您可以查看其他影片的详细信息。如果选择了一个不存在的 id 号,视图将会产生一个错误,因为它没有针对这种情况的错误处理代码。所以,如果你觉得有趣,就去搜索处理这类问题的方法。

第 10 部分:在 DJANGO 中使用 PLOTLY 图

这一部分可能对那些想把这两个惊人的资源放在一起的人有用:Plotly graphs 和 Django。这里需要注意的最重要的事情如下:

  • 从一个 Plotly 图形对象使用.to_html()方法,并将其保存在一个上下文字典中,名称如fig
  • 在 Django 模板中,使用标签{{fig | safe}}来呈现图形。

我将在这里使用 Gapminder 数据集来加快速度。因为它与电影无关,所以创建另一个 Django 应用程序是正确的程序。但我不会那么做。相反,我将在films路径之外放置一条新路径,并借用films/views.py来存储显示图表所需的视图和辅助功能。我还将使用路线参数按年份过滤图表。

51.打开project/urls.py并添加新的 Gapminder 路线:

from django.contrib import admin
from django.urls import path, include
from films import views urlpatterns = [
    path('', views.home, name='home'),
    path('admin/', admin.site.urls),
    path('films/', include('films.urls')), # new route: 
    path('gapminder/<int:year>', views.gapminder, name='gapminder'),
]

films/views.py中,我们将添加两个不是 Django 视图的函数,因为它们不会处理 HTTP 请求。函数get_data()只从plotly.express获取 Gapminder 数据集作为 Pandas 数据帧。并且create_plot()会生成著名的 Gapminder 气泡图。这些函数在gapminder函数视图中被调用,year route 参数在这里用于在生成图表之前过滤数据集。

52.用 PIP 安装 Pandas 和 Plotly:

pip install pandas plotly

53.打开films/views.py,导入 Plotly Express,在最后一次导入后,定义get_data()create_plot()功能:

import plotly.express as px# keep the other imports(...)def get_data():
    df = px.data.gapminder()
    return dfdef create_plot(df, year):
    fig = px.scatter(
      data_frame = df.query(f'year=={year}'),
      x = 'gdpPercap',
      y = 'lifeExp',
      color = 'continent',
      size = 'pop',
      height = 500,
      log_x=True,
      size_max=60,
      hover_name="country")

    fig =  fig.to_html()
    return fig

54.在films/views.py结束时,创建gapminder视图:

def gapminder(request, year):
    df = get_data()
    fig = create_plot(df, year)
    context = {"plot": fig, "year": year}
    template = 'gapminder.html'
    return render(request, template, context)

55.创建文件templates/gapminder.html并在其中写入以下代码:

{% extends 'base.html' %}{% block content %}<h1 class="text-center">GAPMINDER (YEAR {{year}})</h1>
<div class="container-fluid d-flex justify-content-center">
  {{plot | safe}}
</div>{% endblock  %}

56.访问 Gapminder 数据集中的一个带有年份的 URL(比如http://localhost:8000/gapminder/2007)并播放您的网页及其漂亮的交互式图表。

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

作者图片

遗言

如果你到达这里并征服了这个教程,你就是一个真正的战士。恭喜你!Django 是一个完整的世界,还有很多需要了解的;只要坚持学习和练习,成功就会到来。

亲爱的读者,非常感谢你花时间和精力阅读我的文章。

快乐编码!

计算机如何看深度:基于深度学习的方法的最新进展

原文:https://towardsdatascience.com/dl-for-depth-estimation-p2-7cb2c9ff325d

第 2 部分:基于图像的立体视觉

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

立体 视觉 深度 学习。 输入是*一个立体图像对(即从左右摄像机捕捉的图像);输出是左图和两张照片中所有可见像素的深度图。*因此,现代端到端解决方案学会将两张 RGB 照片映射到深度图。目标是使用监督来最小化预测与实际之间的距离(或最大化相似性)。立体对(上面最左侧)是深度网络(中间)的输入,该深度网络将图像转换为相应的深度预测(右侧)。注意,物体离相机越近,视差越显著(即,深度越小)。输出是显示在右侧的密集视差图,暖色代表较大的深度值(和较小的视差)。作者创造了可视化。

我们对深度的感知对于创造我们周围的 3D 世界至关重要。这种知识已经流行了几个世纪,其中一个人就是列奥纳多·达·芬奇。他利用自己的专业知识帮助自己创作了一些艺术作品,这些作品将会名闻遐迩,如《最后的晚餐》或《萨尔瓦托勒·希泽拉》。从技术上讲,对双筒望远镜的理解可以追溯到公元 280 年,当时欧几里德意识到我们的深度知觉是人类用两只眼睛聚焦于同一物体。尽管如此,今天,立体视觉仍然是一个非常有趣的问题。随着我对话题的熟悉,一路上做的笔记,现在正在转化为一系列的博客,供其他人参考。

立体视觉是其他信息和多模态知识可以获得的基本任务。因此,立体视觉技术在现实世界的应用中有着广泛的实际用途,如机器人、自动驾驶汽车和大多数依赖于感知深度作为先验知识的任务!不足为奇的是,在过去的几十年里,这个话题已经被许多人所激发。有了现代的监督深度学习,问题的高度复杂性与高度复杂的网络更好地匹配。现在的需求很大,标记数据以适应这些受监督的深层网络的容量,这在获取和缓解条件以满足对数据的大量需求方面仍然是一个挑战。总之,我们正处于或接近深度感知可以在实践中自信地部署的边缘。但是,由于需要如此多的数据来训练深度网络,部署的模型需要大量的数据来学习将左/右对转换为其视差图的映射函数,如上图所示。

在本系列的第 2 部分中,我们将介绍几种基于图像的立体视觉监控方法。提供了动机、策略、结果(如在原始论文中发表的)、摘要、文档链接和源代码。这里的目的不是提供传统意义上的文献综述,而是充当监督立体视觉的深层方法的百科全书。该系列的未来部分将涵盖视频数据、自我监督和非监督、多任务学习,以及数据集和基准的更多细节和分析(即,比上一篇文章 第 1 部分 提供的更深入)。现在让我们回顾一下从 2015 年到 2020 年的几个 SOA 方法。未来的博客将涵盖最新的方法。然而,这里介绍的大部分材料都是初步的。

享受:)

目录

简介
背景信息
立体视觉
深度监督学习
深度监督学习用于立体视觉
MC-CNN
简介
方法
结果
总之【T28
总结
论文与代码
iresnet
动机
方法
结果
总结
论文与代码

金字塔立体匹配网络(PSMN)
动机
方法
结果
结果
总结
论文及代码
modulenet
动机
结果
总结
论文与代码
讨论
总结
未来工作
结论
附加资源

介绍

在立体视觉中,人类的大脑有一种奇妙的能力去看深度。我们使用我们的两只眼睛,它们彼此分开,位于头部的两侧,这使我们能够以三维方式感知世界:高度、宽度和深度(即,相对于周围环境的前后位置)。这项技能并不是人类独有的——正如在 第一部分 中强调和举例说明的那样;许多动物以各种方式使用立体声!尽管如此,建模立体计算已被证明是一个具有挑战性的壮举。从监督建模的角度来看,困难在于所需的数据集,这些数据集必须包含表示目标场景和组成深度网络的各种对象的信息。我们探讨了这些挑战以及研究人员旨在克服的基于深度学习的方法的最新进展。

第 1 部分,让我们回忆一下极线假设:场景包含两个已知内在属性(即镜头的焦距 f 和外在属性(即左右摄像机的光学中心之间的距离称为基线 b )的摄像机。提供了预测的视差 D ,简单的几何图形被用来重建在捕获图像时丢失的深度尺寸(即 z )。

在这一部分(即第二部分)中,我们回顾了 2015-2020 年间基于深度学习的深度估计方法的进展。未来的部分(即 3、4 和 5)将涵盖最新和最大的(即 2021–2022),基于视频的深度立体方法(即多视图立体(MVS)),并在子像素级别产生相应的置信度。

背景资料

立体视觉

立体的基础——极线几何中更深的深度、图像校正和其他对初学者来说不太重要的数学细节(即,主题本身,如果需要,稍后再返回)将在本系列的第 1 部分中讨论。如果不理解下面的陈述,鼓励读者阅读前面的部分:

深度知觉的重要性及其古老起源视差的定义深度线索的概念以及人类如何自然感知 ( 提示: 两只【立体】眼睛);视差和深度之间的关系,以及如何从一个空间变换到另一个空间在两个(即,左和右)图像中提供了相应的像素;在之前假设了什么参数和条件(即,获得内部和外部参数,以及校正的立体图像对)。

深度监督学习

端到端有监督的深度学习在很多年前就已经成为主流*。它以之前无法想象的方式登上排行榜榜首。问题类型(即任务评估)的增长源于深度学习的成功,因此也是大数据时代的启动。也就是说,基于梯度的端到端深度学习概念可以追溯到 20 多年前[1]。然后,在 2012 年,辍学的概念遇到了大数据,缓解了过度拟合的问题,通用 GPU (GPGPU)的计算能力,允许矩阵数学在数千个内核上并行执行,为我们许多人在 2012 年提到的开创性工作铺平了道路[2]。然而,deep nets 又花了三年时间才在 2015 年提出用于补丁级别的深度重建[3],又花了一年时间提出图像级别的端到端系统[4]。*

在开始有监督的深度学习方法的旅程之前,让我们先定义一些重要的概念。非端到端端到端描述了最高级别的学习方法:端到端意味着输入直接映射到最终输出,这是一个通过反向传播从一个信号学习的权重函数,该信号从端到端回溯。端到端是当今首选的培训方式,因为目标指标中最令人兴奋的特性是通过优化获得的。相反,非端到端不学习从输入空间到输出的映射,而是依赖于中间步骤,这些中间步骤通常由人工设计来调整,因此可能不是最佳的。下图列出了两者的特征。

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

传统非端到端方法和现代端到端学习方法的特点。作者创造了可视化。

除了我们复习的第一种方法(即 MC CNN )之外,这部分涵盖的方法都是监督式端到端深度学习方法。尽管 MC CNN 是一个深度神经网络,我们很快就会知道,该模型将面片级输入对映射到视差空间,这意味着将视差面片估计融合到图像所需的额外步骤是不可学习的。因此, MC CNN 不是一个端到端方法。

**注意。**假设读者对深度学习概念有基本了解,主要是监督方法。最后的补充部分提供了由三部分组成的介绍性系列的链接。

立体视觉的深度监督学习

当设计用于深度估计的深度网络时,遵循两种一般的拓扑监督方法:编码器-解码器架构(图 1 )和在连体 CNN 之上使用成本体积正则化的方法(图 2 )。无论如何,输入空间是立体对(即,左和右 RGB 图像),并且输出是由最小和最大视差限定的相应视差 D :除非另有说明,否则视差等级的数量是离散的并且是已知的,因为假设图像对被校正。目标是最小化实际和预测视差映射之间的差异。

上面列出的假设应该是有意义的:视差Dserves 是在其 D 的空间中学习的(即,封闭形式的分类),而离散特征代表点从左到右沿水平方向移动的像素数量(即,整数个像素)。如何在亚像素级别获得鲁棒性:敬请关注,在本博客结束之前你将会有答案:)

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

***图一。*编码器-解码器拓扑的高级示意图,其中模型学习将立体对映射到相应的视差图。作者创造的人物。

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

***图二。*模型的典型视图的高级表示,通过融合左右图像的特征来调整以产生最佳视差图,从而形成成本体。作者创造的人物。

现在让我们开始具体方法的回顾。从 Zbontar 和 Lecun (2016)开始,我们将涵盖方法。

MC-CNN

Jure Zbontar 和 Yann LeCun。“通过训练卷积神经网络来比较图像块的立体匹配。”* J .马赫。学习。第 17.1 号决议 (2016 年)。*

介绍

第一个为深度估计提出的监督深度学习框架是 MC CNN 。由于学习从立体对到视差空间的逐像素(即密集)映射的巨大复杂性,作者使用了图像补片。尽管如此,MC CNN的概念对于后面介绍的端到端方法是必不可少的。换句话说, Jure Zbontar 和 Yann LeCun 推出了 MC CNN ,开始了我们即将走的路。很明显,MC CNN* 的部分内容启发了我们接下来的工作。*

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

学习从立体对到视差图的映射。请注意,最靠近相机的物体具有更大的差异:暖色的差异更大,因此深度更小——来源:原文

方法

MC CNN 是将立体图像对映射到相应视差的面片级方案。如下图所示,该系统模拟了传统立体视觉技术的典型步骤。

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

用于生成视差图的管道。来自左和右图像的标记为红色和蓝色输入(最左侧)的小块被馈送到 CNN,为此产生具有**通道(每个视差值一个)的特征,表示在小块的相应位置处 d 的成本。来源:原创论文

目标是为考虑中的所有差异 d 计算每个位置p的匹配成本。这样的成本可以简单地确定为关于点 p 的补片之间的绝对差。****

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

𝐼ᴸ和𝐼ᴿ是位置 p 处的图像强度。𝓝 是固定窗口中以p**—也就是p=(xy ),然后pd=(x-【t70)因此,下图描述了修补程序级别的连体网络。**

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

MC CNN 的连体架构(快)。注意,对于每个具有共享权重的相似性分数,处理一对 9x9 的面片(即,暹罗网络)。作者创造了可视化。

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

作者提出了快速(左)和精确(右)的变体。

MC CNN 的作者提出了两个变量,一个是速度(即快**),另一个是准确性(即):对于任何一个,网络拓扑都会产生相似性得分。快速变体完全独立地从左侧和右侧提取特征,允许一个面片的表示被转换一次,然后与其他面片比较几次以生成分数。同时,精确模型在共享分支之前融合特征,通过一系列附加层从连接的特征中学习度量。因此,精确方法必须在每次生成分数时处理两个补丁,而更快的版本可以存储补丁的特征,例如只需在补丁对之间进行点积。**

**作者还表明,像传统的立体匹配方案一样,最大化相关性是最佳的。**此外,相关性计算(即层)在模型的存储和速度方面花费较少。下图从不同的角度显示了管道。

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

他们采用了一个连体网络,提取每个像素所有可能差异的边缘分布,以学习信息丰富的图像补丁表示。

结果

在 KITTI 2012、KITTI 2015 和 Middlebury 数据集上,精确变体比其所有前辈产生了更低的错误率;快速版本的运行速度比精确架构快 90 倍,误差增加很少。

这些结果表明,CNN 在确定立体声对的匹配成本方面工作良好,甚至在需要实时性能的应用中也是如此。

因此,这个相对简单的 CNN 声称是最先进的(SOA),展示了现代深度学习方法解决经典立体视觉计算机视觉问题的巨大潜力。

概括起来

为了完整起见, MC CNN 被包含在这一部分中,关于用于立体视觉的第一批深度学习方法,因为它是一个开创性的解决方案。但是,它不是一个端到端的解决方案,因为它处理补丁,而不是在映像级别。因此,这种方法的覆盖面被设计得很小。更深入地讨论了处理方法。

赞成的意见

  • 首次尝试使用深度特征学习来解决立体深度估计
  • 一项广泛的研究显示了一种快速和一种精确变体
  • 补丁提供了大量的训练数据

骗局

  • 不是端到端的解决方案
  • 许多手工设计的步骤
  • 无法捕捉全局上下文,因此仍然无法捕捉不均匀和高度纹理化的区域。

纸张和代码

https://arxiv.org/abs/1510.05970

**https://github.com/jzbontar/mc-cnn

流动网络

*Dosovitskiy,Alexey,等人《流网:用卷积网络学习光流》*IEEE 计算机视觉国际会议 (2015)。

动机

当以端到端的方式训练时,深度神经网络学习感兴趣的任务知识的能力处于其最高潜力。虽然最初提出 FlowNet 是为了解决光流(即,本质上类似于根据视差进行深度估计的任务),但它是第一个端到端解决方案。因此,这里的许多概念将在以后直接应用于视差估计。

**注意:**光流是一个视频相邻帧中对应点之间的差值。正如我们所了解的,立体视觉中经常做出的一个假设是输入图像是经过校正的。差异是水平位移的函数。另一方面,光流考虑水平和垂直方向上像素位置的差异。因此,可以在描述由一对立体摄像机提供的场景流的两种类型的知识之间推断几何相似性。关于光流的细节超出了本博客的范围。感兴趣的读者可以在[5]中看到更多关于光流的内容,在[6]中可以看到更具描述性的场景流。最近,一项关于这两个主题的调查发表了[7]。

FlowNet 在空间上和网络的收缩部分压缩信息(即特征),然后在扩展部分细化这些特征。因此,将图像对直接映射到目标输出空间。论文中的下图描述了 FlowNet 的高级概念。

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

FlowNet 的结构类似于编码器-解码器网络。U-Net [4]计算大小为(1, nrowsncolumns )的正则化视差 d⋆。这种方法的主要缺点是归一化互相关层的计算成本。而且还会产生模糊的视差图:来源— 论文

方法

如下图所示,Flownet 包括两种网络架构:FlowNetSimple (FlowNetS)和 FlowNetCorr (FlowNetC)。

FlowNetS 将两幅图像连接起来,并将其输入网络。

FlowNetC 是一种不太通用的网络结构,其中每个图像都被送入一个连体网络,其工作方式如下:

  • 分别学习有意义的表示(如边)。
  • 然后,各个分量在相关层中经历乘法面片比较(即,类似于矩阵乘法)。
  • 两个都是乘法面片比较和类似的细化过程的结果。

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

两种网络架构:FlowNetSimple(顶部)和 FlowNetCorr(底部)。绿色漏斗是图 3 中显示的扩展细化部分的占位符。网络包括细化部分都是端到端训练的—来源 : 原论文并由作者修改。

两个特征图之间的乘法块比较由相关层进行。给定𝖿₂𝖿₁的两幅多通道特征图,宽度 W ,高度 H ,通道数 C,第一幅图中以 x₁为中心,第二幅图中以 x₂为中心的两个面片的相关性数学表达式如下。⋆

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

正方形空间补片的大小为*K* = 2*k*+1

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

它仍然是许多系统的基本部分:形成成本体积的最佳熔丝信号。

使用输出的卷积图层连接并提取 f₁的要素地图。

  • 他们通过一系列 conv 和合并图层降低分辨率。
  • 作者通过上进化层的解池上进化来细化粗池表示。
  • 上变换的和相应的特征图连接起来,并进行上采样的粗流量预测。

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

细化粗特征映射到高分辨率预测—来源 : 原始论文。

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

FlowNet 架构。

结果

摘要

FlowNet 是第一个用于立体深度估计的端到端深度特征学习解决方案(虽然最初是针对光流提出的,但过渡是微不足道的,因为视差是光流的子集)。换句话说,因为视差表示像素沿水平方向的移动,所以光流是帧 tt+ 1 之间在所有方向上的位置变化(即,实数的 2D 矢量表示,与整数值 1D 视差张量相反)。因此,在立体视觉系统中,假设提供了校正的立体对,找到视差的问题需要在相邻对的对中找到光流的子集解决方案。事实上,给定带有摄像机的立体视觉设置,视差张量和光流张量描述了场景流[5],这是对前方场景的更多了解。

FlowNet 的缺点包括:

  • 归一化互相关层的高计算成本。
  • 产生模糊的视差图。

尽管如此,深度学习、端到端、监督学习范式中差异模型的演变,FlowNet 引入了将启发其他人的概念(如本博客结尾所示)。

纸张和代码

https://github.com/ClementPinard/FlowNetTorch

iResNet

通过特征恒常性学习视差估计。 IEEE 计算机视觉和模式识别会议论文集。2018.

动机

梁等人考虑立体视觉系统的传统步骤,如本系列的第 0 部分中所介绍,并在上面列出(立体方法学)。网络架构包括对应于立体匹配的每个步骤的模块。下图显示特征提取视差估计细化模仿传统方法。

方法

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

建议网络的架构将立体匹配的所有四个步骤整合到一个网络中。为了更好的可视化,他们省略了编码器和解码器在不同尺度上的跳跃连接——来源 : 原文。

特征恒常性是指特征空间中两个像素的对应关系。

跳过连接允许推断视差图的约束解。

  • 当对匹配成本执行视差估计时,子网捕获低级语义信息。

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

f ꜀充当特征间的相关性, re 重建误差,产生第一视差 disp ,而 disp ᵣ 为细化视差。

**迭代网络。**视差细化阶段的输出可以是第一视差预测,并且因此直接回到视差细化模块中。作者发现 2-3 次迭代可以改善结果,这会影响最后的地图。

**它是如何工作的?**如上面的数学公式所表达的,精确的视差显示 ᵣ.

关系左侧三项的结果。具体来说,第一个特征恒定性项、 f 、是左右特征图之间的相关性:度量两个特征图在所有视差级别上的对应性。因此,第一项越大,特征越相似。第二项 re 是多尺度融合特征之间的绝对差值。他们发现了特征空间中的重建误差,而不是像素,这是这项工作之前的惯例(例如,CRL)。最后,我们有第三项,初始视差估计,显示 ᵢ或者仅仅是显示

结果

系统和子系统变体的现场流量结果如下表所示。

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

**来源:**原论文。

接下来是单尺度和多尺度的比较。

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

**来源:**原文。

最后,在场景流的错误、大小和运行时方面与 SOA 进行比较。

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

**来源:**原论文。

我们可以在从称为视差估计子网络(DES-Net)的第一模块提取的特征中看到 re 的有效性,与像素相反。DES-Net 学习一种表示以传递给第二模块,称为差异细化子网(DRS-Net)。因此,可以假设从 DES-net(向下)传播的误差是由于重建误差和初始差异(见上面绿色和红色矩形中间并排的绿色和红色矩形,分隔 DES-Net 和 DRS-Net)。他们将 disp 输入到 DRS-net,通过细化进行改进: disp 是第一个首字母,然后通过 DRS-net 创建 disp’ ,通过以下方式确定残差

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

其中迭代次数(即细化步骤):

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

作者发现,2–3 会挤压所有潜在的性能(即最适合设计的性能)。

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

在不同迭代的场景流测试集上进行视差细化。第一行是输入图像,第二行示出了没有改善的初始视差,第三和第四行示出了在 1 次和 2 次迭代之后的精细视差。第 5 行给出了地面实况的悬殊——来源 : 原创论文。

下表中使用 KITTI 2012 和 2015 数据集进行的比较说明了 KITTI 的定性比较。

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

**来源:**原论文。

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

**来源:**原文。

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

**来源:**原论文。

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

KITTI 2015 上与 SOA 的比较。第一行中的图像是来自 KITTI 2015 的输入图像。我们的 iResNet-i2 细化结果(第 3 行)可以显著改善初始视差(第 2 行),并给出比其他方法更好的可视化效果,尤其是在图像的上部,在该区域没有地面真实—来源 : 原文。

摘要

纸张和代码

https://github.com/leonzfa/iResNet

几何和上下文网络

深度立体回归的几何与情境的端对端学习。IEEE 计算机视觉国际会议论文集。 2017。​

动机

  • 据观察,立体算法的几个挑战将受益于知道全局语义上下文而不是局部几何。
  • 目的是学习端到端的立体回归模型,以理解更广泛的上下文信息。​

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

理解全局语义和几何将减轻问题,因为更广泛的上下文将受益于像反射这样的障碍。

方法

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

**来源:**原创论文。

  • 从校正的立体对进行端到端学习以估计亚像素视差。
  • 它形成了利用问题几何的可区分成本量。
  • 通过跨成本体的多尺度三维卷积学习上下文。
  • 评价视差曲线的可微软 ArgMax。

接下来,我们将逐步完成构成 GC-Net 的模块,上图与 stereo methods 的原始算法(即工作流)相关。

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

用突出显示的模块描绘原始图[图来自作者修改的论文]。

特征提取:

如今,我们使用特征表示,而不是从原始像素强度计算成本。然后,比较对光度外观中的模糊性稳健的描述符,并捕获局部上下文。作者将这些特征称为一元特征。利用的优势

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

原始论文中的表格。

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

  • 步长为 2 的卷积滤波器对输入进行二次采样,以减少计算需求。
  • 之后,八个残差块由两个3×3卷积滤波器串联而成。
  • 在左右网络之间共享参数以学习相应的特征。

成本量

  • 成本体积的大小H×W×(Dmax + 1)×F
  • 这是通过将每个一元特征与它们在每个视差级别上来自相对立体图像的对应一元特征连接起来并将其打包到 4D 体积中来实现的
  • 它赋予网络学习语义的能力。

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

场景流数据集包含来自一系列合成场景的 960 × 540px 的 35,454 幅训练图像和 4,370 幅测试图像。我们比较不同的架构变体来证明我们的设计选择。结果强调了原始论文的 3-D 卷积架构来源的重要性。

软 Argmin

立体算法从一元的匹配成本中产生最终成本量。体积通过最小化成本来估计差异,例如在成本体积差异维度上的 argmin 运算。但是,使用标准的 argmin 操作有两个问题。

  1. 它是离散的,并且不能产生子像素视差估计。
  2. 因此,该模型是不可微的,并且不能使用反向传播来训练。

作者引入了操作的软版本来缓解这些问题。我们称之为软 argmin ,数学定义如下。

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

其中dₘₐₓ*t14】是最大差异 *d,和期望值(即 sigma)是

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

这种损失类似于 bahda nauet al**soft attention【1】,一种soft arg max【2】的否定变体。后者扩展了[3]中使用的操作,通过高阶统计扩展损失(即,最小化预测和预期之间的差异,并优化置信度)。从统计学上讲,基于一阶矩(即预期值)的损失通过二阶矩(即方差)获得信息。旁注:对上述内容的深入探究在未来博客想法的列表中,所以请继续关注;)

  • 为了克服这些限制,我们定义了一个软 argmin,它是完全可微的,并且能够回归一个平滑的视差估计。
  • 首先,我们通过取每个值的负值,将预测成本 cd(对于每个差异 d )从成本量转换为概率量。我们使用 softmax 运算σ()对视差维上的概率体进行归一化。然后,我们对 D 进行求和,并按其归一化概率进行加权。

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

**软参数操作的图形描述。**它沿着每条视差线获取一条成本曲线,并通过对每个视差的 softmax 概率与其视差指数的乘积求和来输出 argmin 的估计值。( a )表明,当曲线为单峰时,这准确地捕捉了真实的 argmin。( b )示出了当数据是具有一个峰值和一个平坦区域的双峰时的故障情况。( c )如果网络学会预缩放成本曲线,则可以避免这种失败,因为 softmax 概率会更极端,产生单峰结果。

  • 然而,与 argmin 操作相比,它的输出受所有值的影响,这意味着它容易受到多峰分布的影响,因为最不可能得到支持。相反,它将估计所有模式的加权平均值。为了克服这个限制,网络进行调整以产生单峰差异概率分布。
  • 网络还可以预先调整匹配成本,以控制归一化后的 softmax 峰值(有时称为温度)。

损失函数

  • 使用 GT 视差 dₙ和像素 n 的预测视差 dHat ₙ之间的 L₁训练模型

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

  • 回归模型允许基于光度重投影误差的无监督学习损失。

结果

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

定量结果

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

定性结果

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

摘要

  • 根据合成数据进行预训练(即 SceneFlow [5])。
  • 回归通过分类差异获得 SOA。
  • 学习的端到端优于学习的一元特征+半全局匹配(即 MC-CNN [1])。
  • 使用几何图形学习成本卷明显优于其他端到端(即 DispNetC)。
  • 该模型的推理速度相对较快。

纸张和代码

链接到论文[ arXiv ]。

https://github.com/zyf12389/GC-Net

金字塔立体匹配网络(PSMN)

常、贾仁和。"金字塔立体匹配网络."IEEE 计算机视觉和模式识别会议录。2018.

动机

深层神经网络中的经验感受野远小于理论感受野。其他方法依赖于基于补丁的连体网络,缺乏利用上下文信息在不适定区域中寻找对应的手段(见下图)。

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

红色十字表示图像中心像素的感受野。基线:没有扩张的 conv,没有 SPP,没有堆积的沙漏。完整设置:扩张 conv,SPP,堆叠沙漏。该图是从原始论文中复制的,并由作者进行了修改。

方法

  • 引入了金字塔池模块,用于将全局上下文信息合并到图像特征中。
  • 堆叠沙漏 3D CNN 扩展了成本体积中上下文信息的区域支持。

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

提议的 PSMN 框架。这张图是从原图上复制和修改的。

左和右输入立体图像是由四部分组成的两个权重共享管道的输入:(1)特征映射函数(即 CNN);(SPP 模块,用于通过连接不同大小的子区域来获取特征;(3)用于特征融合的卷积层;以及(4)左和右图像特征用于形成 4D 成本体,该成本体传递到 3D CNN 用于成本体规则化和视差回归。最终输出是预测的视差图。下图描述了完整的系统。

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

图来自原论文。

因此,有两个主要模块:空间金字塔池和 3D CNN:

  1. 空间金字塔汇集模块通过聚合不同比例和位置的环境来形成成本量,从而利用全球环境信息的容量。
  2. 3D CNN 学习使用有中间监督的堆叠沙漏网络来调整成本量。

现在,让我们仔细看看这两个。

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

步骤 1:从左/右图像计算特征

  • 使用多尺度池不再 9✕9 补丁。
  • 每个级别的不同滤镜以原始分辨率工作。
  • 然后,每一个都被汇集成不同的大小。
  • 另一个 conv 然后向上采样到原始分辨率。

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

特征向量结合了具有不同感受野的过滤器,但是增加感受野的空间汇集减少了可学习的参数。

获得左右图像的每个像素的特征向量。

第二步:构建成本量

  • 然后,我们对每个左右图像都有大小为*H*✕*W*✕*F*的特征张量。
  • 构建一个大小为*H*✕*W*✕*D*✕2*F*的成本卷

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

  • 在这一点上,这个网络与 MC-CNN 的不同之处仅在于它如何获得这些功能。
  • 他们的方法相当于为每个成本体积应用一堆完全连接的层,从特征到单个分数,即*H*✕*W*✕*D*✕2*F H*✕*W*✕*D*✕1

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

第三步:流程成本量

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

3D 卷积层与剩余连接串联。

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

  • 5D 核而不是 4D 喜欢正则卷积。视差空间中的平移不变量: dd +1 之间的关系与*ď*, *ď*+1之间的关系相同。

第四步:成本量对差异

  • 他们将最终的*H*✕*W*✕*D*(×1)张量视为前 softmax 分数。
  • 因此,进行软最大值给出了可能的视差值的概率分布。
  • 但是他们不会用分类损失来训练这个:因为一些错误分类比其他的更糟糕!(d=5 而不是 4 比 d=25 而不是 4 要好)。

所以,他们对这个分布有期望!

损失函数

  • 使用 soft argmin 获得 GC-Net 中提到的所有好处
  • GC-Net 使用 L₁作为惩罚指标

○ L₁损失对异常值不太敏感

○通过回归学习无界目标,L₂必须仔细调整

  • 类似于快速 R-CNN(即 BB 回归),SPP 使用以下平滑 L₁变量

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

  • 使用类似 GC-Net 的视差回归来估计连续视差图。

○通过 softmax 运算σ()从预测成本 c ₔ计算每个差异 d 的概率。

预测视差ᶺ d-hat 计算为每个视差 d 的概率加权总和(2)

○上述视差回归比基于分类的立体匹配方法更鲁棒。

●与 R-CNN 和 SPPnet 中使用的 L₂损失相比,稳健的 L₁损失对异常值不太敏感。当回归目标无界时,使用 L₂损失的训练可能需要小心的学习率以防止爆炸梯度。情商。3 结束这种敏感性。

结果

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

“KITTI 2012 测试图像”的视差估计结果左图显示了立体图像对的左输入图像。PSMNet、GC-Net 和 MC-CNN 获得的每个输入图像的视差显示在其误差图上方。

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

为 GC-Net 及其前身显示的 KITTI 2012 测试图像的视差估计结果。左侧面板显示立体图像对的左侧输入图像。以下是 PSMNet、GC-Net 和 MC-CNN 针对每个输入图像获得的视差以及相应的误差图。

结果

摘要

优点

先前的 DL 立体匹配方法的一个问题是上下文信息。GC-Net 是一个用于立体匹配的端到端学习框架,无需后处理,它采用编码器-解码器来合并多尺度特征以实现成本体积正则化。

引入了金字塔池模块,用于将全局上下文信息合并到图像特征中

一个堆叠沙漏 3D 有线电视新闻网,用于成本卷中上下文信息的扩展区域支持。

缺点

大量足迹和耗时的推断

纸张和代码

https://github.com/JiaRenChang/PSMNet

ModuleNet

伦特里亚,奥克塔维奥&奎瓦斯-泰洛,胡安-卡洛斯&雷耶斯-菲格罗亚,阿兰&里维拉,马里亚诺。ModuleNet:用于立体视觉的卷积神经网络。10.1007/978–3–030–49076–8_21.(2020).

动机

  1. 受 FlowNet & U-Net 的启发,一个衡量任何范围内差异的模型。
  2. 使用低计算时间算法来测量成本图。
  3. 架构很简单,因为它不需要任何额外的专门网络作为不同的 FlowNet 的变体进行优化。
  4. 它改进了基线模型 ELAS [3]和 FlowNet c(FlowNet的相关版本),具有大约 80%的非偏倚误差。

U-Net 块由函数 F₁: F₁表示,对噪声普查距离图进行正则化。

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

一般块(U-Net)捐赠为 F接受距离张量 D 映射自 图像对 I 然后转换为概率 P

方法

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

普查转换

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

对于左图ₗ;类似的, C ᵣ为右像 I ᵣ.汉明距离(H)计算两个普查签名之间的不同位数:

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

让我们举例说明具有代表性的₁. u-net

数学上:

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

距离张量中的信息。

D 通过 F ₁.映射到概率 P

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

概率张量(模型输出)

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

𝜃₁代表训练期间学到的重量。

第二个 U-Net 可以改进主要(训练的)模块的输出。函数ft53】也使用距离张量 D 作为输入来提炼概率张量 p:

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

𝜃₂学会了举重。

因此,

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

基本块由两个 U 型网级联而成。

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

请注意,网络 F i s 重新用于处理 K 模块。

最后是视差估计。

通过在差异图ŷ中应用 WTA 程序来计算 d⋆。

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

ModuleNet:模块化 CNN 模型。 来源: 原创论文。

结果

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

所选场景的 MPI Sintel 数据集的结果。 来源: 原创论文。

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

所选立体像对上的 Middlebury 数据集的结果。 来源: 原创论文。

**注意:**用于检测范围外差异的额外层有助于对添加噪声的像素进行分类,因为这些像素在工作范围之外或者是被遮挡的区域。

摘要

纸张和代码

代码不可用!😦

如果有,请分享!

GA 网

动机

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

性能插图。(a)具有挑战性的输入图像。(b) GC-Net [13]有 19 个 3D 卷积层用于匹配成本聚集。©GA-Net 的结果仅使用两个建议的 GA 层和两个 3D 卷积层。它将匹配信息聚集到相当大的无纹理区域,并且比 GC-Net 更快。(d)基本事实。 来源: 原创论文。

方法

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

(a)架构概述。左和右图像输入通过权重共享特征提取管道。它由层叠的沙漏 CNN 通过级联连接而成。所提取的左和右图像特征形成了 4D 成本体,该成本体被馈送到成本聚集块,用于正则化、细化和视差回归。制导子网(绿色)为制导成本汇总(SGA 和 LGA)生成权重矩阵。(b) SGA 层在四个方向上半全局地合计成本量。在视差回归之前使用 LGA 层,并且局部细化 4D 成本量若干次。 来源: 原创论文。

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

结果

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

城市景观(上)和米德尔伯里(下)的样本结果。来源:https://github.com/feihuzhang/GANet/

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

结果可视化和比较。第一行:输入图像。第二行:GC-Net 的结果[13]。第三行:PSMNet [3]的结果。最后一行:我们遗传网络的结果。蓝色箭头指出了显著的改进。引导聚合可以有效地将视差信息聚合到大的无纹理区域(例如,汽车和窗户)并给出精确的估计。它还可以聚合对象知识并保留深度结构(最后一列)。

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

摘要

纸张和代码

https://github.com/feihuzhang/GANet

讨论

摘要

我们看了深度学习时代立体视觉进步的几种基本方法。每种技术都有动机,技术上的审查,以及原始论文和官方源代码。

不同于传统的调查,在传统的调查中,专家正确地写下了一个特定的主题,这里提供的是来自学习立体视觉的人的基于方法的概述。换句话说,这并不是要以任何方式取代同行评审调查,而是在一个不太正式的会议上提供一个更容易阅读的、针对具体方法的概述。如果你想查看我们发表的关于另一个计算机视觉主题的文献,请查看我们的 IEEE PAMI 调查。

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

资料来源:视觉亲属关系分析和建模调查:酝酿中的十年。由作者创作。

未来的工作

我们讨论了使用监控的基于图像的立体方法。本系列的后续部分将涵盖基于视频的(即多视图立体视觉)、多任务、自我监督、公共数据集、基准和基准,以及关于生成相应置信度映射的最后一部分。敬请期待!

结论

我们讨论的深度学习方法只是解决计算机视觉问题的许多新方法中的几个。如果你不了解深度学习基础,这篇博文可能不适合你。另一方面,如果您正在寻找将立体图像映射到深度图的端到端解决方案,无论是为了工作、个人项目还是研究,本博客系列可能是您的起点。无论如何,考虑在你的产品或服务中采用深度学习技术可能是值得的。上面讨论的哪个模型对你最有意义,或者是你的首选?您希望我在未来的部分中包含哪些方法?你喜欢什么,不喜欢什么?任何反馈、问题、请求等。,将不胜感激。

补充资源

PSM 网络是使用 PyTorch Lightning 实现的。

https://pythonrepo.com/repo/xtliu97-PSMNet_PytorchLightning-python-deep-learning

查看 FlowNet 上的深度博客:

GA-Net:Youtube 上端到端立体匹配的引导聚合网络。

补充的

  1. Dhanoop Karunakaran aran 编写了一个由 3 部分组成的博客系列,内容是使用深度学习(第 1 部分)进行图像分类(第 2 部分)和检测/定位图像中的交通标志(第 3 部分)。有大量具有足够深度和广度的资源,即使是忙于学习 SOA 的专家也能深入理解。因此,博客系列在相对较短的系列中提供了动机、视觉、数学和概念观点的良好平衡。张量流用于在证明和概念之间架起理解的桥梁。

参考

  1. Y.LeCun、L. Bottou、Y. Bengio 和 P. Haffner。"基于梯度的学习应用于文档识别."IEEE 会议录,86(11):2278–2324,1998 年 11 月。
  2. Alex Krizhevsky,Ilya Sutskever 和 Geoffrey E. Hinton“使用深度卷积神经网络的图像网络分类”在神经信息处理系统进展 (2012)。
  3. Bahdanau、Dzmitry、Kyunghyun Cho 和 Yoshua Bengio。"通过联合学习对齐和翻译的神经机器翻译."在 ICLR (2015)。
  4. 盖革、安德烈亚斯、马丁·罗瑟和拉克尔·乌尔塔森。“高效的大规模立体匹配。”亚洲计算机视觉会议。施普林格,柏林,海德堡,2010。
  5. 将立体视差和光流结合起来用于基本场景流。商用车技术 2018 。斯普林格,2018。90–101.
  6. 门兹、莫里茨和安德烈亚斯·盖格。"自动驾驶车辆的物体场景流."IEEE 计算机视觉和模式识别会议论文集。2015.
  7. 翟,,等,“光流和场景流估计:综述”模式识别 114 (2021): 107861。
  8. 罗恩伯格,奥拉夫,菲利普费舍尔和托马斯布罗克斯。" U-net:生物医学图像分割的卷积网络."医学图像计算和计算机辅助介入国际会议。施普林格,查姆,2015。**
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值