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

原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

改善零-shot CLIP 的性能和可解释性

原文:towardsdatascience.com/improving-performance-and-explainability-of-zero-shot-clip-33e579d3f4bb

第二部分 — 通过 LLM 描述进行视觉分类

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

·发布在 Towards Data Science ·阅读时间 6 分钟·2023 年 11 月 25 日

这是关于提升零-shot CLIP 性能系列文章的第二部分。在第一部分中,我详细解释了 CLIP 模型的工作原理,并描述了一种简单的方法来提高其性能。这包括通过大型语言模型(LLM)生成的定制提示来扩展标准提示,如 “{class} 的图片”。如果你还没有阅读第一部分,可以在 这里 找到。在这篇文章中,我们将介绍一种相对类似的方法来提高零-shot CLIP 性能,同时这种方法具有很高的可解释性。

介绍

CLIP 模型是一个令人印象深刻的零-shot 预测器,能够对其没有明确训练过的任务进行预测。尽管它具有固有的能力,但仍然存在几种策略可以显著提高其性能。在第一篇文章中,我们已经见过其中一种策略,然而,虽然提高性能是有价值的,但有时我们可能愿意做出权衡,以优先考虑更好的可解释性。在本系列的第二篇文章中,我们将探讨一种不仅提升零-shot CLIP 模型性能,还确保其预测结果易于理解和解释的方法。

深度神经网络的可解释性

目前有多种解释性技术可用于深度学习模型。在上一篇文章中,我深入探讨了集成梯度,这是一种说明输入的每个特征如何影响机器学习模型输出的方法,尤其是深度神经网络。另一种流行的模型解释方法是基于 Shap 值,我们根据合作博弈理论中的概念来分配每个特征对模型输出的贡献。虽然这些方法是通用的,可以应用于任何深度学习模型,但它们的实现和解释可能有些挑战。CLIP,它已经训练将图像和文本特征映射到相同的嵌入空间,提供了一种基于文本的替代解释方法。这种方法更具用户友好性,并提供了易于解释的方式,为模型解释提供了不同的视角。

问题的快速回顾

作为这一系列的第一部分的快速回顾,我们在这里解决的问题是预测下面显示图像的类别:

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

来自FreeImages的树蛙图像(许可证:www.freeimages.com/license

使用简单提示 “{class}的图片” 的标准方法给出了错误的答案,预测 “有尾巴的青蛙” 的概率分数为 0.68*:

from transformers import CLIPProcessor, CLIPModel
import torch
import requests
from PIL import Image

model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")

url = "https://images.freeimages.com/images/large-previews/342/green-tree-frog2-1616738.jpg"
image = Image.open(requests.get(url, stream=True).raw)

inputs = processor(text=["a photo of a tree frog", "a photo of a tailed frog"], images=image, return_tensors="pt", padding=True)

outputs = model(**inputs)
logits_per_image = outputs.logits_per_image  # this is the image-text similarity score
probs = logits_per_image.softmax(dim=1)  # we can take the softmax to get the label probabilities
print(probs)

"""
Output:
tensor([[0.3164, 0.6836]], grad_fn=<SoftmaxBackward0>)
"""

现在让我们看看如何改进它。

通过 LLMs 的描述进行视觉分类

为了提高零-shot CLIP 的预测准确性,我们将实现类似于我们在第一篇文章中讨论的思想。然而,这一次,我们不会提供像 “树蛙” 这样的通用提示,例如 “树蛙的识别特征因物种而异,但一些常见特征包括大粘附趾、突出的眼睛和明亮的颜色”,而是将其分为具体的 描述性特征。例如,考虑到 “树蛙”“有尾巴的青蛙” 类别的描述性特征是:

树蛙

  • “突出的眼睛”

  • “大嘴巴”

  • “没有尾巴”

  • “明亮的绿色”

有尾巴的青蛙

  • “小眼睛”

  • “小嘴巴”

  • “深色”

  • “有长尾巴”

*这些特征可以通过类似于以下的 LLM 生成:

Q:在照片中区分{class}的有用特征是什么?

A:有几个有用的视觉特征可以用来识别照片中的{class}:

-*

“-” 是重要的,因为它会迫使模型生成一个特征列表。

接下来,类似于我们在第一篇文章中所做的,为了对青蛙的图像进行分类,我们会取这些文本特征描述的平均向量嵌入,这些描述代表了多模态空间中的每个类别,并评估哪个平均向量最接近我们要分类的测试图像。在代码中,我们有:

# define features description for each class
features = {"tree frog": [
        "protruding eyes", "large mouth", "without a tail",  "bright green colour"
    ],
    "tailed frog": [
        "tiny eyes", "small mouth", "has long tail", "dark colour"
    ]}

# image embedding
image_features = model.visual_projection(model.vision_model(inputs['pixel_values']).pooler_output)

tree_frog_vector = model.text_model(processor(features['tree frog'], return_tensors="pt", padding=True)['input_ids']).pooler_output
# take the mean prompt embedding
tree_frog_vector = tree_frog_vector.mean(dim=0, keepdims=True)
# final projection 
tree_frog_vector = model.text_projection(tree_frog_vector)

tailed_frog_vector = model.text_model(processor(features['tailed frog'], return_tensors="pt", padding=True)['input_ids']).pooler_output
# take the mean prompt embedding
tailed_frog_vector = tailed_frog_vector.mean(dim=0, keepdims=True)
# final projection
tailed_frog_vector = model.text_projection(tailed_frog_vector)

# concatenate 
text_features = torch.cat([tree_frog_vector, tailed_frog_vector], dim=0)

# normalize features
image_features = image_features / image_features.norm(dim=-1, keepdim=True)
text_features = text_features / text_features.norm(dim=-1, keepdim=True)

# cosine similarity as logits
logit_scale = model.logit_scale.exp()
logits_per_image = logit_scale * image_features @ text_features.t()
logits_per_image.softmax(dim=1)

"""
Output:
tensor([[0.8901, 0.1099]], grad_fn=<SoftmaxBackward0>)
"""

首先,我们观察到我们的预测现在是准确的,模型正确地识别出类别为*“树蛙”。虽然我们在本系列的第一部分中也达到了正确的分类结果,但该方法的显著区别在于它提供了高可解释性。我们可以检查每个特征描述的非标准化分数S(feature)*,而不是简单地取特征描述的平均值。这使我们能够理解模型为何预测了某个特定类别:

# here we don't average the textual features as we want to see
# the score for each feature separately
tree_frog_vector = model.text_model(processor(features['tree frog'], return_tensors="pt", padding=True)['input_ids']).pooler_output
tree_frog_vector = model.text_projection(tree_frog_vector)
text_features_tree_frog = tree_frog_vector

text_features_tree_frog = text_features_tree_frog / text_features_tree_frog.norm(dim=-1, keepdim=True)
logit_scale = model.logit_scale.exp()
logits_per_image = logit_scale * image_features @ text_features_tree_frog.t()
logits_per_image

"""
Output:
tensor([[25.5400, 22.6840, 21.3895, 25.9017]], grad_fn=<MmBackward0>
"""

tailed_frog_vector = model.text_model(processor(features['tailed frog'], return_tensors="pt", padding=True)['input_ids']).pooler_output
tailed_frog_vector = model.text_projection(tailed_frog_vector)
text_features_tailed_frog = tailed_frog_vector

text_features_tailed_frog = text_features_tailed_frog / text_features_tailed_frog.norm(dim=-1, keepdim=True)
logit_scale = model.logit_scale.exp()
logits_per_image = logit_scale * image_features @ text_features_tailed_frog.t()
logits_per_image

"""
Output:
tensor([[24.0911, 22.3996, 21.2813, 21.0066]], grad_fn=<MmBackward0>
""" 

S(“突出眼睛”) = 25.5400 > S(“小眼睛”) = 24.0911;

S(“大嘴”) = 22.6840 > S(“小嘴”) = 22.3996;

S(“没有尾巴”) ~ S(“无尾”) 可能相似,因为尾巴是

图片中不可见;

S(“亮绿色”) = 25.9017 > S(“深色”)= 21.0066;

属于*“树蛙”类别的特征分数高于“有尾蛙”*类别的特征分数。分析这些特征分数有助于我们理解为何模型预测了某个类别。在这个例子中,像“突出眼睛”、“亮绿色”和“大嘴”等特征得到了非常高的分数,为预测的类别提供了清晰的解释。在第一部分描述的方法中没有这种解释水平,因为生成的提示非常通用,并且包含了不同概念的句子。将提示改为简单的特征描述可以让我们兼得两全其美——高准确性和良好的可解释性。

结论

在系列的第二部分,我们展示了如何通过改进标准提示*“{类别}的图片”*来提升性能。这个解决方案不仅具有可扩展性,因为 LLM 可以为任何数量的类别和数据集生成描述性特征,而且具有高度的可解释性。在即将到来的文章中,我们将探讨利用每个类别的少量图像示例的少样本学习方法,以实现比零样本方法更高的准确性。

参考文献

[1] CLIP (huggingface.co)

[2] openreview.net/pdf?id=jlAjNL8z5cs

通过自适应损失平衡提升物理信息神经网络

原文:towardsdatascience.com/improving-pinns-through-adaptive-loss-balancing-55662759e701?source=collection_archive---------6-----------------------#2023-01-31

如何通过 ReLoBRaLo、学习率退火等方法提升 PINN 的性能

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

·

阅读 发表在 Towards Data Science ·14 分钟阅读·2023 年 1 月 31 日

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

图片由 David Clode 提供,来源于 Unsplash

在本文中,我们回顾了 PINNs 的基础知识,探讨了不平衡损失的问题,并展示了由 Michael Kraus 和我提出的平衡方案 ReLoBRaLo (相对损失平衡与随机回溯) [1] 如何显著提升训练过程。还可以通过两个附带的笔记本体验这种技术在实际 PDE 问题中的应用:

如果你点击了这篇文章,可能是因为你已经对物理信息神经网络 (PINN) [2]有了相当好的理解。也许你在网上找到了一些教程,并在像 Burgers 或 Helmholtz 方程这样的知名基准上实现了 PINNs。利用神经网络的力量来解决复杂的偏微分方程 (PDEs) 的想法确实很有吸引力。但正如我们许多人痛苦地发现的那样,使用 PINNs 的现实过程也可能非常令人沮丧。如果你继续尝试将这些工具应用于你自己研究中遇到的尚未在文献中很好记录的 PDE,那么很可能 vanilla PINNs 的表现不会如你所愿。更糟的是,它们可能比像有限元方法 (FEM) 这样的成熟方法收敛得更慢!

请耐心点,我曾经经历过这一切,事实上,每次我尝试将 PINNs 应用到新问题上时,都是如此。尽管自其提出以来的五年中取得了一些进展,并且有几十年的利用可微分神经网络解决微分方程的研究 [3],但目前仍没有一种易于使用、即插即用的 PINNs 版本可以无缝转移到任何类型的问题上。

你看,PINNs 通过对输出相对于输入进行多次高阶导数,将这些导数用于构建残差和边界条件,从而在其损失函数中利用微分方程。这意味着每个偏微分方程从根本上改变了 PINN 的训练过程。可能需要调整架构,例如添加或删除层和节点,此外,其他更复杂的超参数也可能对建模能力产生关键影响,其中许多是 PINNs 的特性,无法在经典神经网络的文献中找到。这些可能包括激活函数的选择、物理域上的采样程序,或者,非常棘手的是,微分方程中测量单位的选择。

虽然我无法给你使 PINNs 工作所需的所有配方,但我可以告诉你一些基本步骤,如果没有这些步骤,你的努力很可能是徒劳的。但在我揭示这些关键工具之前,请允许我提供一些背景,以更好地理解我的论点。让我们退后一步,打开一个括号。

基准 PDE

为了说明,让我们引入赫尔姆霍兹和Kirchhoff 板弯曲方程。但在你开始感到不知所措之前,让我向你保证,理解这些偏微分方程的复杂性并不是跟随本文其余部分的必要条件。如果你想跳过这一部分,只需知道赫尔姆霍兹偏微分方程是一个具有零阶(Dirichlet)边界条件的二阶偏微分方程,而Kirchhoff 板弯曲方程是一个具有零阶和二阶导数边界条件的四阶偏微分方程。

这个方程是一个四阶偏微分方程(PDE),描述了板在载荷下的变形。方程中的未知函数 u 表示板在给定点 (x, y) 的垂直位移(例如,以米为单位)。施加在板上的载荷由函数 p(x, y) 表示。方程中的常数 D 包含了板的各种属性,如其厚度、弹性模量和密度。

Kirchhoff 板弯曲方程。

所以,Kirchhoff 方程说明了变形的四阶导数等于施加在板上的载荷除以一个常数因子。相当直接,对吧?

当然,每个经验丰富的 PDE 专家都知道,尽管主方程看起来优雅,但没有适当的边界条件,它只是一种毫无意义的抽象。毕竟,有无限多的方程可以满足它。

那么,让我们也引入边界条件:

简支边缘的边界条件

其中 W 和 H 分别定义了板的宽度和高度。第一行的边界条件显示了零阶(Dirichlet),并指出板的边缘不允许弯曲。第二行显示了二阶导数,这要求边缘的弯矩为零。这可以通过一个被下面的梁支撑(因此零阶导数为零)并被上面的另一根梁挤压(导致弯矩为零)的板边缘来说明。

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

一个Kirchhoff 板及其在正弦载荷下的变形(以米为单位),W = H = 10 和 D = 20.83。图由作者提供。

赫尔姆霍兹方程:介质中波的建模

赫尔姆霍兹方程是一个描述波在介质中传播的偏微分方程。它是一个二阶方程,以德国物理学家赫尔曼·冯·赫尔姆霍兹命名。

赫尔姆霍兹方程,其中 u(x, y) 是未知函数,k 是波数

其中 k 是波数,u(x, y) 是待求的未知函数。对于这个问题,我们将使用域四个边缘上的零阶 Dirichlet 边界条件:

赫尔姆霍兹偏微分方程的Dirichlet 边界条件

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

一个赫尔姆霍兹波传播的例子,边界条件为零 Dirichlet。图由作者提供。

PINN 损失函数

如果在定义 Kirchhoff 或 Helmholtz 函数的过程中我让你迷失了,不必担心。我花了半年多时间,以及无数耐心的土木工程师的解释,才得以向你讲解这些公式。

关键在于理解如何将这些方程转化为可用于训练我们 PINN 的损失函数,这里针对 Helmholtz 方程:

import tensorflow as tf
import tensorflow.experimental.numpy.isclose

TOL = 1e-5

def compute_loss(self, x, y, u, dudxx, dudyy, eval=False):
    """
    Computes the physics-informed loss for Helmholtz's PDE.

    Parameters
    ----------
    x : tf.Tensor of shape (batch_size, 1)
        x coordinate of the points in the current batch
    y : tf.Tensor of shape (batch_size, 1)
        y coordinate of the points in the current batch
    u : tf.Tensor of shape (batch_size, 1)
        predictions made by our PINN (dim 0)
    dudxx : tf.Tensor of shape (batch_size, 1)
        second-order derivative of the predictions w.r.t. x
    dudyy : tf.Tensor of shape (batch_size, 1)
        second-order derivative of the predictions w.r.t. y
    """

    # governing equation loss
    L_f = (dudxx + dudyy + self.k**2 * u - \
          (-np.pi**2 - (4 * np.pi)**2 + self.k**2) * tf.math.sin(np.pi * x) * tf.math.sin(4 * np.pi * y))**2

    # determine which points are on the boundaries of the domain
    # if a point is on either of the boundaries, its value is 1 and 0 otherwise
    x_lower = tf.cast(isclose(x, -1, rtol=0., atol=EPS), dtype=tf.float32)
    x_upper = tf.cast(isclose(x,  1, rtol=0., atol=EPS), dtype=tf.float32)
    y_lower = tf.cast(isclose(y, -1, rtol=0., atol=EPS), dtype=tf.float32)
    y_upper = tf.cast(isclose(y,  1, rtol=0., atol=EPS), dtype=tf.float32)

    # compute 0th order boundary condition loss
    L_b = ((x_lower + x_upper + y_lower + y_upper) * u)**2

    if eval:
        L_u = (tf.math.sin(np.pi*x) * tf.math.sin(4*np.pi*y) - u)**2
        return L_f, L_b, L_u

    return L_f, L_b

你可以在实现 ReLoBRaLo 的笔记本中找到完整的代码,链接为HelmholtzKirchhoff PDEs

多目标优化

正如我们已经确定的,我们的 Helmholtz PDE 的最终损失函数将包含两个,而 Kirchhoff PDE 则包含三个目标:

  • Helmholtz:控制方程的损失 L_f 和 0 阶边界条件的损失 L_b0。

  • Kirchhoff:除了 L_f 和 L_b0 之外,Kirchhoff 还包括一个用于二阶边界条件 L_b2 的项。

因此,这些损失属于多目标优化(MOO)范畴,就像大多数涉及 PINNs 的应用一样。

将多个目标聚合为单一损失的方式通常是通过线性标量化:

其中 lambda 是用于控制每个项对总损失贡献的缩放因子。但为什么它们是必要的?

不平衡梯度问题

在收集必要背景信息后,我们可以最终关闭括号)并继续探索为何 PDE 中的测量单位会影响 PINNs 的收敛性。你看,我们的损失函数中的几个目标——L_f、L_b0 和 L_b2——每个都有不同的测量单位。L_b0 对于 Kirchhoff 可能以米为单位测量,而 L_b2 以 Nm 为单位测量,板上的负载以 MN 每平方米为单位测量。这在每个项的大小上产生了显著的差异,导致梯度计算严重偏向具有最高幅度的项。Helmholtz 方程及任何其他 PDE 也是如此。

让我们来看看这在 Helmholtz 方程中的含义。

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

训练 PINN 模型时对 Helmholtz 方程损失的演变。L_f 是对控制方程的平方损失,L_b 是对边界条件的平方损失,L_u 是预测值与解析解之间的平方损失。图示由作者提供。

注意到在训练开始时,控制方程损失 L_f 比边界条件的损失大几个数量级,因此,L_b 的值实际上会增加。这种量级上的差异可能导致 PINN 优先考虑 L_f 而忽视 L_b,最终收敛到一个满足控制方程但忽略关键边界条件的解。这种效果可以通过验证损失 L_u 的图表观察到,验证损失 L_u 与边界损失 L_b 遵循相同的模式,表明验证性能与边界上的表现密切相关。

那么 Kirchhoff PDE 呢?

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

在训练 PINN 以解决 Kirchhoff 方程时损失的演变。L_f 是控制方程的平方损失,L_b0 是 Dirichlet 边界条件的平方损失,L_b2 是矩的边界条件的平方损失,而 L_u 是预测与解析解之间的平方损失。图由作者提供。

对于 Kirchhoff 方程来说,情况正好相反。这里,边界条件的收敛速度远远快于控制方程。最可能的解释是,控制方程涉及四阶导数,因此优化起来尤其困难。这表明不平衡损失的原因不仅仅是项之间量级的差异,还包括激活函数的选择和每一项所逼近函数的复杂性。

不平衡的梯度并不限于 Helmholtz 或 Kirchhoff PDEs。许多研究已经记录了在各种 PINN 应用中存在这一问题 [4]。这里的关键点是,为了得到准确的解,必须在损失函数中的所有目标之间取得平衡。

自适应损失平衡方案

为了减轻不平衡损失和梯度的问题,可以借助之前介绍的多目标优化中的线性标量化的缩放因子 lambda。选择较大的 lambda 值用于量级较小或更难优化的项可以帮助平衡最终梯度的贡献,从而确保所有项都被适当地逼近。然而,手动完成这项任务是繁琐的,需要许多迭代,因此在时间和计算资源方面耗费较大。

这就是为什么研究人员提出了损失平衡方案,如 Gradnorm [5]、SoftAdapt [6] 或 学习率退火 [4]。

相对损失平衡与随机回溯(ReLoBRaLo)

在本文中,我们将重点介绍一种名为相对损失平衡与随机回顾(ReLoBRaLo)的方案[1],它是上述方法的结合体。

ReLoBRaLo 的目标是确保损失函数中的每一项随着时间的推移都有相同的进展,相对于其在训练开始时的值。例如,如果 L_f 从训练开始以来提高了 50%,我们希望其他项也以大致相同的速度提高,并达到 50% 的减少。然而,如果某一项始终以较慢的速度提高,ReLoBRaLo 会逐渐增加该项的缩放因子 lambda,从而增加其对梯度计算的贡献。

假设我们有 n 个损失项 L_i,并且用 L_i(t) 表示训练迭代 t 时该项的值。我们可以通过将当前迭代的值 L_i(t) 除以训练开始时的值 L_i(0) 来衡量其进展:

ReLoBRaLo 通过将每个损失项 L_i(t) 的当前值除以第一个迭代的值 L_i(0),来衡量每个项 i 从训练开始以来的进展。

训练开始以来的进展越大,这个操作的结果就会越小。观察这正是我们所期望的:我们的方案应该对进展缓慢的项赋予较高的缩放因子,而对进展较快的项赋予较小的缩放因子——而且所有这一切都应该独立于项的绝对值。因此,我们可以使用 L_i(t) / L_i(0) 来计算损失函数中各项的缩放因子。

尽管这是 ReLoBRaLo 的关键组件,但它还包含了许多额外的扩展,这些扩展被发现可以进一步提高性能。然而,为了便于本文的可读性,我将这些内容留给感兴趣的读者查看论文并了解更多关于所用方法及其动机的信息。

那么它有效吗?让我们来看看使用 ReLoBRaLo 平衡各项对总损失的贡献时 Helmholtz PDE 的损失演变情况:

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

在 Helmholtz 方程上训练 PINN 并使用 ReLoBRaLo 时损失的演变。L_f 是对主方程的平方损失,L_b 是对边界条件的平方损失,L_u 是预测值与解析解之间的平方损失。图由作者提供。

虽然主要方程 L_f 的损失不再有太大进展(在前一个图中收敛于大约 -3.8),但边界条件 L_b 以及由此产生的验证损失 L_u 收到了更多的权重。最终的验证损失相对于解析解有了 65% 的改善。让我们看看 ReLoBRaLo 计算的缩放值:

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

通过 ReLoBRaLo 获得的 Helmholtz PDE 主要方程项 L_f(蓝色)和边界条件 L_b(橙色)的缩放因子 lambda_i。图由作者提供。

Kirchhoff 也是如此:

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

在 Kirchhoff 方程上训练 PINN 并使用 ReLoBRaLo 时,损失的演变。L_f 是主要方程的平方损失,L_b0 是 Dirichlet 边界条件的平方损失,L_b2 是矩边界条件的平方损失,L_u 是预测值与解析解的平方损失。图由作者提供。

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

通过 ReLoBRaLo 获得的 Kirchhoff PDE 主要方程项 L_f(蓝色)、Dirichlet 边界条件 L_b0(橙色)和矩边界条件 L_b2(绿色)的缩放因子。图由作者提供。

再次,ReLoBRaLo 将误差相对于解析解的改善了一个数量级。值得注意的是,这种平衡方案几乎没有增加计算开销(参见论文)。正是这种有效性和效率让 ReLoBRaLo 成为 Nvidia 物理信息深度学习框架 Modulus 的一部分。

但真正的问题是:你能在自己的项目中使用 ReLoBRaLo 吗?答案是一个响亮的“能”!实际上,该方案可以整洁地封装成一个 keras 损失,可以通过 model.compile() 添加到你的 keras 模型中,或者在定义了自定义训练循环的情况下,在每次迭代时显式调用它。

你可以在实现 ReLoBRaLo 的笔记本中找到完整的代码,适用于 HelmholtzKirchhoff PDEs

import tensorflow as tf

class ReLoBRaLoLoss(tf.keras.losses.Loss):
    """
    Class for the ReLoBRaLo Loss. 
    This class extends the keras Loss class to have dynamic weighting for each term.
    """
    def __init__(self, pde:HelmholtzPDE, alpha:float=0.999, temperature:float=0.1, rho:float=0.99,
                 name='ReLoBRaLoLoss', **kwargs):
        """
        Parameters
        ----------
        pde : PDE
            An instance of a PDE class containing the PDE-specific `compute_loss` function.
        alpha, optional : float
            Controls the exponential weight decay rate. 
            Value between 0 and 1\. The smaller, the more stochasticity.
            0 means no historical information is transmitted to the next iteration.
            1 means only first calculation is retained. Defaults to 0.999.
        temperature, optional : float
            Softmax temperature coefficient. Controlls the "sharpness" of the softmax operation. 
            Defaults to 0.1.
        rho, optional : float
            Probability of the Bernoulli random variable controlling the frequency of random lookbacks.
            Value berween 0 and 1\. The smaller, the fewer lookbacks happen.
            0 means lambdas are always calculated w.r.t. the initial loss values.
            1 means lambdas are always calculated w.r.t. the loss values in the previous training iteration.
            Defaults to 0.99.
        """
        super().__init__(name=name, **kwargs)
        self.pde = pde
        self.alpha = alpha
        self.temperature = temperature
        self.rho = rho
        self.call_count = tf.Variable(0, trainable=False, dtype=tf.int16)

        self.lambdas = [tf.Variable(1., trainable=False) for _ in range(pde.num_terms)]
        self.last_losses = [tf.Variable(1., trainable=False) for _ in range(pde.num_terms)]
        self.init_losses = [tf.Variable(1., trainable=False) for _ in range(pde.num_terms)]

    def call(self, xy, preds):
        x, y = xy[:, :1], xy[:, 1:]

        # obtain the unscaled values for each term 
        losses = [tf.reduce_mean(loss) for loss in self.pde.compute_loss(x, y, preds)]

        # in first iteration (self.call_count == 0), drop lambda_hat and use init lambdas, i.e. lambda = 1
        #   i.e. alpha = 1 and rho = 1
        # in second iteration (self.call_count == 1), drop init lambdas and use only lambda_hat
        #   i.e. alpha = 0 and rho = 1
        # afterwards, default procedure (see paper)
        #   i.e. alpha = self.alpha and rho = Bernoully random variable with p = self.rho
        alpha = tf.cond(tf.equal(self.call_count, 0), 
                lambda: 1., 
                lambda: tf.cond(tf.equal(self.call_count, 1), 
                                lambda: 0., 
                                lambda: self.alpha))
        rho = tf.cond(tf.equal(self.call_count, 0), 
              lambda: 1., 
              lambda: tf.cond(tf.equal(self.call_count, 1), 
                              lambda: 1., 
                              lambda: tf.cast(tf.random.uniform(shape=()) < self.rho, dtype=tf.float32)))

        # compute new lambdas w.r.t. the losses in the previous iteration
        lambdas_hat = [losses[i] / (self.last_losses[i] * self.temperature + EPS) for i in range(len(losses))]
        lambdas_hat = tf.nn.softmax(lambdas_hat - tf.reduce_max(lambdas_hat)) * tf.cast(len(losses), dtype=tf.float32)

        # compute new lambdas w.r.t. the losses in the first iteration
        init_lambdas_hat = [losses[i] / (self.init_losses[i] * self.temperature + EPS) for i in range(len(losses))]
        init_lambdas_hat = tf.nn.softmax(init_lambdas_hat - tf.reduce_max(init_lambdas_hat)) * tf.cast(len(losses), dtype=tf.float32)

        # use rho for deciding, whether a random lookback should be performed
        new_lambdas = [(rho * alpha * self.lambdas[i] + (1 - rho) * alpha * init_lambdas_hat[i] + (1 - alpha) * lambdas_hat[i]) for i in range(len(losses))]
        self.lambdas = [var.assign(tf.stop_gradient(lam)) for var, lam in zip(self.lambdas, new_lambdas)]

        # compute weighted loss
        loss = tf.reduce_sum([lam * loss for lam, loss in zip(self.lambdas, losses)])

        # store current losses in self.last_losses to be accessed in the next iteration
        self.last_losses = [var.assign(tf.stop_gradient(loss)) for var, loss in zip(self.last_losses, losses)]
        # in first iteration, store losses in self.init_losses to be accessed in next iterations
        first_iteration = tf.cast(self.call_count < 1, dtype=tf.float32)
        self.init_losses = [var.assign(tf.stop_gradient(loss * first_iteration + var * (1 - first_iteration))) for var, loss in zip(self.init_losses, losses)]

        self.call_count.assign_add(1)

        return loss

非常感谢你阅读到本文的最后!如果你觉得这篇文章对你有帮助,并希望在自己的工作中使用 ReLoBRaLo 或笔记本,请使用 此引用。你可以在 rabischof.ch 和我的同事在 mkrausai.com 上找到更多关于我的信息。

[1] Rafael Bischof 和 Michael Kraus. 面向物理的深度学习的多目标损失平衡。arXiv 预印本 arXiv:2110.09813, 2021。

[2] M. Raissi, P. Perdikaris, 和 G. E. Karniadakis,《物理信息神经网络:解决涉及非线性偏微分方程的正向和逆向问题的深度学习框架》,《计算物理学杂志》378 (2019),686–707。

[3] H. Lee 和 I. S. Kang,《求解微分方程的神经算法》,《计算物理学杂志》91 (1990),第 1 期,110–131

[4] Wang, S., Teng, Y., 和 Perdikaris, P. 理解和缓解物理信息神经网络中的梯度病态。arXiv 预印本 (2020 年 1 月),arXiv:2001.04536。

[5] Chen, Z., Badrinarayanan, V., Lee, C.-Y., 和 Rabinovich, A. GradNorm: 用于深度多任务网络的自适应损失平衡的梯度归一化。arXiv 预印本 (2017 年 11 月),arXiv:1711.02257。

[6] Heydari, A. A., Thompson, C. A., 和 Mehmood, A. SoftAdapt: 用于多部分损失函数的神经网络的自适应损失加权技术。arXiv 预印本 (2019 年 12 月),arXiv:1912.12355。

在 RAG 管道中通过混合搜索提升检索性能

原文:towardsdatascience.com/improving-retrieval-performance-in-rag-pipelines-with-hybrid-search-c75203c2f2f5?source=collection_archive---------1-----------------------#2023-11-28

如何通过将传统的基于关键词的搜索与现代的向量搜索相结合来找到更相关的搜索结果

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

·

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

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

具有混合搜索功能的搜索栏

随着对 检索增强生成(RAG)管道的兴趣增加,开发者们开始讨论在构建具有生产就绪性能的 RAG 管道时面临的挑战。正如生活中的许多方面一样,帕累托原则也在 RAG 管道中发挥作用,其中实现最初的 80%相对简单,但要达到剩下的 20%以实现生产就绪则证明具有挑战性。

一个经常重复的主题是通过混合搜索来改善 RAG 管道的检索组件。

已经获得经验的开发者们 开始分享他们的见解。一个经常重复的主题是通过混合搜索来改善 RAG 管道的检索组件。

本文介绍了混合搜索的概念,如何通过检索更相关的结果来提高你的 RAG 管道性能,以及何时使用它。

  • 什么是混合搜索

  • 混合搜索如何工作?

  • 混合搜索如何提高你 RAG 管道的性能?

  • 你什么时候会使用混合搜索?

  • 总结

什么是混合搜索

混合搜索是一种结合两种或更多搜索算法的搜索技术,以提高搜索结果的相关性。尽管没有定义具体结合了哪些算法,但混合搜索最常指的是传统基于关键词的搜索与现代向量搜索的结合。

传统上,基于关键词的搜索是搜索引擎的明显选择。然而,随着机器学习(ML)算法的出现,向量嵌入启用了一个新的搜索技术——称为向量或语义搜索——使我们能够在语义上进行数据搜索。然而,这两种搜索技术都有必须考虑的重要权衡:

  • 基于关键词的搜索: 尽管其精确的关键词匹配能力对特定术语(如产品名称或行业术语)有益,但对拼写错误和同义词敏感,这会导致它遗漏重要的上下文。

  • 向量或语义搜索: 尽管它的语义搜索功能基于数据的语义意义进行多语言和多模态搜索,并使其对拼写错误具有鲁棒性,但可能会遗漏重要的关键词。此外,它依赖于生成的向量嵌入的质量,并对领域外的术语敏感。

将基于关键词的搜索与向量搜索结合成混合搜索,使你能够利用这两种搜索技术的优点,从而提高搜索结果的相关性,特别是在文本搜索的使用场景中。

例如,考虑搜索查询“如何使用.concat()合并两个 Pandas DataFrames?”关键词搜索会帮助找到与方法.concat()相关的结果。然而,由于“merge”这个词有诸如“combine”、“join”和“concatenate”等同义词,如果我们能利用语义搜索的上下文感知能力,将会更有帮助(有关更多细节,请参见何时使用混合搜索)。

如果你感兴趣,可以在这个实时演示中尝试不同的基于关键词的、语义的和混合搜索查询(其实现细节见这篇文章)。

混合搜索是如何工作的?

混合搜索通过融合关键词基础和向量搜索技术的搜索结果并对其进行重新排序来实现。

基于关键词的搜索

在混合搜索的背景下,基于关键词的搜索通常使用一种称为稀疏嵌入的表示,这就是为什么它也被称为稀疏向量搜索。稀疏嵌入是大多数值为零且只有少数非零值的向量,如下所示。

[0, 0, 0, 0, 0, 1, 0, 0, 0, 24, 3, 0, 0, 0, 0, ...]

稀疏嵌入可以通过不同的算法生成。生成稀疏嵌入的最常用算法是BM25(最佳匹配 25),它建立在 TF-IDF(词频-逆文档频率)方法之上并对其进行了改进。简单来说,BM25 根据词汇在文档中的频率相对于其在所有文档中的频率来强调术语的重要性。

向量搜索

向量搜索是一种现代搜索技术,随着机器学习的进步而出现。现代机器学习算法,如Transformers,可以生成各种模态(文本、图像等)的数据对象的数值表示,称为向量嵌入。

这些向量嵌入通常信息密集且大多由非零值组成(密集向量),如下所示。这也是为什么向量搜索也被称为密集向量搜索。

[0.634, 0.234, 0.867, 0.042, 0.249, 0.093, 0.029, 0.123, 0.234, ...]

搜索查询被嵌入到与数据对象相同的向量空间中。然后,其向量嵌入用于根据指定的相似性度量(例如余弦距离)计算最接近的数据对象。返回的搜索结果列出了与搜索查询相似度排名最高的数据对象。

关键词基础与向量搜索结果的融合

基于关键词的搜索和向量搜索返回的是一组独立的结果,通常是按计算的相关性排序的搜索结果列表。这些独立的搜索结果集必须被合并。

有许多不同的策略可以将两个列表的排名结果合并为一个单一的排名,正如Benham 和 Culpepper [1]的论文中所述。

一般来说,搜索结果通常首先会被评分。这些分数可以基于指定的度量计算,如余弦距离,或者仅仅是搜索结果列表中的排名。

然后,计算出的分数会与参数alpha一起加权,这决定了每种算法的加权并影响结果的重新排名。

hybrid_score = (1 - alpha) * sparse_score + alpha * dense_score

通常,alpha的值在 0 到 1 之间,具体取值为

  • alpha = 1:纯向量搜索

  • alpha = 0:纯关键词搜索

下面,你可以看到一个基于排名和alpha = 0.5评分的关键词与向量搜索融合的最小示例。

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

这是一个关于如何在混合搜索中融合关键词和向量搜索结果的最小示例,评分基于排名和alpha = 0.5(图像由作者提供,灵感来自混合搜索解释)。

混合搜索如何提高你的 RAG 管道性能?

一个RAG 管道有许多可以调整的参数来提高其性能。其中之一是提高检索到的上下文的相关性,这些上下文会被输入到 LLM 中,因为如果检索到的上下文与回答特定问题不相关,LLM 也无法生成相关的答案。

根据你的上下文类型和查询,你需要确定三种搜索技术中哪一种对你的 RAG 应用最有利。因此,参数 **alpha**,用于控制关键词搜索和语义搜索之间的加权,可以视为需要调整的超参数。

在一个常见的使用 LangChain 的 RAG 管道中,你会通过以下方式设置检索器组件,将使用的vectorstore组件设置为检索器,方法是.as_retriever()

# Define and populate vector store
# See details here https://towardsdatascience.com/retrieval-augmented-generation-rag-from-theory-to-langchain-implementation-4e9bd5f6a4f2
vectorstore = ...

# Set vectorstore as retriever
retriever = vectorstore.as_retriever()

然而,这种方法只启用了语义搜索。如果你想在 LangChain 中启用混合搜索,你需要定义一个具有混合搜索功能的特定[retriever](https://python.langchain.com/docs/integrations/retrievers) 组件,例如[WeaviateHybridSearchRetriever](https://python.langchain.com/docs/integrations/retrievers)

from langchain.retrievers.weaviate_hybrid_search import WeaviateHybridSearchRetriever

retriever = WeaviateHybridSearchRetriever(
    alpha = 0.5,               # defaults to 0.5, which is equal weighting between keyword and semantic search
    client = client,           # keyword arguments to pass to the Weaviate client
    index_name = "LangChain",  # The name of the index to use
    text_key = "text",         # The name of the text key to use
    attributes = [],           # The attributes to return in the results
)

其余的标准 RAG 管道将保持不变。

这个小的代码更改允许你在关键词搜索和向量搜索之间尝试不同的加权。注意,设置alpha = 1等于完全的语义搜索,相当于直接从vectorstore组件定义检索器(retriever = vectorstore.as_retriever())。

你何时会使用混合搜索(混合搜索用例)

混合搜索非常适用于需要启用语义搜索功能以获得更人性化的搜索体验,但也需要对特定术语(如产品名称或序列号)进行精确匹配的用例。

一个很好的例子是平台 Stack Overflow,它最近通过使用混合搜索扩展了其搜索功能。

## 像人一样提问:在 Stack Overflow 上实现语义搜索

语义搜索允许用户使用自然语言进行搜索,而不是依赖于关键字操作的僵化语法。搜索……

stackoverflow.blog

最初,Stack Overflow 使用 TF-IDF 将关键字匹配到文档中[2]。然而,描述你试图解决的编码问题可能会很困难。这可能导致基于你用来描述问题的词语不同而得到不同的结果(例如,组合两个 Pandas DataFrames 可以通过不同的方法实现,如合并、连接和拼接)。因此,更加上下文感知的搜索方法,如语义搜索,将对这些案例更有益。

然而,另一方面,Stack Overflow 的一个常见用例是复制粘贴错误信息。对于这种情况,精确的关键字匹配是首选的搜索方法。此外,你还需要精确的关键字匹配能力来处理方法和参数名称(例如,Pandas 中的 .read_csv())。

正如你所猜测的,许多类似的现实世界使用案例受益于上下文感知的语义搜索,但仍然依赖于精确的关键字匹配。这些使用案例可以通过实现混合搜索检索器组件得到显著的提升。

概述

这篇文章介绍了混合搜索的背景,作为基于关键字搜索和向量搜索的组合。混合搜索将单独搜索算法的搜索结果合并并相应地重新排序搜索结果。

在混合搜索中,参数 alpha 控制基于关键字的搜索和语义搜索之间的加权。这个参数 alpha 可以视为 RAG 管道中的一个超参数,用于调节以提高搜索结果的准确性。

使用 Stack Overflow [2] 案例研究,我们展示了混合搜索如何对语义搜索可以改善搜索体验的使用案例有帮助。然而,当特定术语频繁出现时,精确的关键字匹配仍然很重要。

享受这个故事吗?

免费订阅 以便在我发布新故事时获得通知。

## 订阅以便每当 Leonie Monigatti 发布内容时,你会收到电子邮件。

订阅以便每当 Leonie Monigatti 发布内容时,你会收到电子邮件。通过注册,如果你还没有 Medium 账户,将会创建一个 Medium 账户……

medium.com

LinkedInTwitter Kaggle上找到我!

免责声明

在撰写本文时,我是 Weaviate 的开发者倡导者,Weaviate 是一个开源向量数据库。

参考文献

文献

[1] Benham, R., & Culpepper, J. S. (2017). 排名融合中的风险-收益权衡。发表于第 22 届澳大利亚文档计算研讨会(第 1–8 页)。

[2] Haney, D. & Gibson, D. 在 Stack Overflow 博客中。像人类一样提问:在 Stack Overflow 上实现语义搜索(访问于 2023 年 11 月 24 日)。

图片

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

如何通过单元测试和 TDD 提高您 dbt 模型的代码质量

原文:towardsdatascience.com/improving-the-code-quality-of-your-dbt-models-with-unit-tests-and-tdd-203ed0be791e?source=collection_archive---------4-----------------------#2023-05-31

开始对您的 dbt SQL 模型进行单元测试所需了解的一切

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

·

关注 发表在 Towards Data Science ·7 分钟阅读·2023 年 5 月 31 日

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

图片由 Christin Hume 提供,来源于 Unsplash

如果你是数据或分析工程师,你可能已经习惯了编写 SQL 模型并使用 dbt 测试数据质量。你为自己创建的模块化和整洁的 SQL 模型感到自豪。一切都还不错。但某个时候,你模型中的转换逻辑开始增长并变得更加复杂。你开始考虑,是否能够将模型隔离并创建单元测试来验证和记录这些逻辑会更好呢?这肯定会提高你 dbt 代码库的代码质量,对吧?

你的第一个想法是查看如何使用内置的 dbt 数据测试功能来进行这种类型的测试。经过几次谷歌搜索,你意识到其中一种方法是 创建一个自定义单元测试框架,利用 dbt 种子功能。或者你需要 将 Python 引入你的代码库,以便使用 Pytest 创建单元测试。这看起来相当繁琐,因此你开始考虑,是否能够模拟模型输入并使用 SQL 对转换后的数据进行断言会更好呢?

如果你对此有共鸣,你并不孤单。这是我所在的数据工程团队经历的过程,直到我们找到 dbt-unit-testing 库。

模拟 dbt 源和引用

使用 dbt-unit-testing 你可以通过模拟其依赖关系来独立测试 dbt 模型。根据他们的文档,它为你提供了:

  • 能够模拟依赖关系

  • 能够独立运行每个测试

  • 快速反馈循环

  • 良好的测试反馈(好吧,如我们将看到的,有一些陷阱)

这个 dbt 库使我们能够模拟模型依赖关系,并立即开始实施单元测试。正如我将在最后与您分享的,它甚至使我们能够开始实践测试驱动开发来开发我们的模型。

既然我们介绍了这个库,让我们看看开始实施真实的 dbt SQL 模型单元测试所需了解的基本知识。

为什么你应该对模型进行单元测试?

一些数据团队专注于提高其数据应用处理的数据质量,但他们常常忽视了所构建软件的代码质量。

单元测试可以通过提供一种隔离 SQL 模型并验证复杂业务逻辑的方式来帮助实现这一点。

“单元测试测试应用程序中最小的可测试软件部分,以确定它是否按预期行为” — Toby Clemson

在我们的案例中,最小的可测试软件是一个 dbt 模型。能够隔离测试模型可以确保新的更改不会破坏现有的业务逻辑,并帮助我们记录模型的预期行为。

一个典型的 dbt 应用遵循 分层架构风格,至少包含三个层级:暂存层、中间层。每一层会包含一个或多个模型。这些就是我们可以单独测试的模型。

创建我们的第一个单元测试

让我们来看一个简单的示例。我们有一个名为 health-insights 的 dbt 应用,它从上游源获取体重和身高数据,并计算体质指数。

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

一个典型 dbt 数据应用的分层架构

以下模型用最新的身高测量来丰富体重测量,最新的身高记录在体重测量之前。

一个 dbt 中间模型的示例

现在让我们创建一个 dbt 单元测试,以证明变换逻辑是正确的。

一个中间模型的 dbt 单元测试示例

构建模块

查看之前的测试,我们可以看到多个 dbt-unit-testing 宏在使用中:

  • dbt_unit_testing.test: 这个宏允许我们定义待测试的模型以及测试的名称。在我们的示例中,我们引用了int_weight_measurements_with_latest_height

  • dbt_unit_testing.mock_ref: 这个宏允许我们模拟对其他 dbt 模型的引用。在我们的示例中,我们模拟了体重(stg_gym_app__weight)和身高(stg_gym_app__height)暂存数据。

  • dbt_unit_testing.expect: 这个宏允许我们对变换结果进行断言。在示例中,我们断言体重测量被最新的身高信息所丰富。

运行测试

现在让我们运行模型的单元测试。我们可以调用常用的 dbt 测试命令:

dbt test

哦,那条命令会运行整个测试套件,包括其他 dbt 数据质量检查。但我们只想运行我们的单元测试。没问题,我们可以利用 dbt 标签功能来隔离我们的单元测试。在示例中,我们用两个标签标记了我们的测试:

{{ config(tags=['unit-test', 'unit-tests']) }}

第一个是 dbt-unit-testing 库所需的模板标签。第二个是我们将用来执行单元测试的标签。

dbt test --select tag:unit-tests

探索其他类型的测试

到目前为止,我们已经看到如何编写单元测试来验证单个模型的逻辑。在创建了几个这样的测试后,我们的团队开始讨论实现新类型测试的可能性,就像我们通常为微服务等操作软件所做的那样。

“组件测试将被测试的软件范围限制在系统的一部分,通过内部代码接口操作系统,并使用测试替身将被测试的代码与其他组件隔离开来。” — 托比·克莱姆森

在微服务上下文中,组件是一个暴露特定功能的服务。如果我们将相同的概念应用到数据上下文中,那么 dbt 应用的组件测试可以实现为测试 dbt 应用是否通过模拟数据源提供了其承诺的功能。

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

运营应用程序的常见测试金字塔

在实现组件测试时,测试的范围会增加。我们测试整个 dbt 应用程序,仅模拟其源。

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

组件测试范围

这种类型的测试确保不同模型正确集成,并生成预期的数据转换结果。让我们来看一个例子:

在上面的组件测试中,我们正在测试我们的输出模型body_mass_indexes。该模型使用丰富的体重测量数据来计算用户的体重。我们直接使用 dbt_unit_testing.mock_source 宏模拟源(raw_weight 和 raw_height)。最后,我们断言输出模型的最终转换,验证体重指数(BMI)是否计算正确。

我们还可以使用我们在测试配置中指定的标签名称在隔离环境中运行这种类型的测试。

dbt test --select tag:component-test

为什么不在 SQL 中应用测试驱动开发(TDD)呢?

现在我们有能力在隔离环境中测试模型,如果我们从编写测试开始,而不是先编写任何转换逻辑,会怎样呢?

测试驱动开发(TDD)是一种软件工程实践,通过迫使开发人员首先编写测试,然后编写最少量的代码来使测试通过,从而帮助改进代码的设计。

我们的数据团队在运营系统中应用 TDD 方面有经验,因此我们决定尝试一下。

从在测试中定义给定转换的结果开始感觉非常自然。哦,如果我有这个体重和这个身高作为输入,我期望的 BMI 会是多少?我们来为此编写一个测试。在实践了一段时间的 TDD 之后,团队在将新的业务逻辑添加到转换中时仍然继续使用这一技术。

注意事项

我曾设想过一个完美的场景,你可以直接添加单元测试 dbt 包并开始立即创建测试。事实是该库仍在开发中,我们发现了一些你可能也需要注意的问题:

  • dbt-unit-testing 宏打破了不允许测试代码污染生产代码的原则。解决这个问题的一个简单方法是创建一个宏来修补原始的 ref() 和 source() 并调用测试宏。你可以在这里查看一个示例。

  • 我们发现有时测试中的更改似乎没有被识别。虽然有禁用缓存的选项,但我们还没有尝试过。

  • 在模拟源时,如果在 dbt 的 .yml 文件中未定义源,它将无法编译。

  • 有时候测试错误消息非常晦涩。在这种情况下,我们发现自己需要查看构建文件夹中的编译 SQL 代码。

  • 还要注意库文档中列出的其他限制

结论

我们已经看到如何向我们的 dbt 项目添加单元测试和组件测试,以提高代码质量,从而提高转换逻辑的可维护性。我们还看到如何标记不同类型的测试,以便可以在本地和 CI/CD 管道中分别运行它们。最后,我们还查看了如何实践 TDD。

希望这篇文章能帮助你和你的团队开始采用单元测试,并在你的代码库扩展以满足新的数据使用案例时,创建更易维护和可扩展的 dbt 应用程序。

如果你感兴趣,可以在这个 Github 仓库中查看一个完整功能的示例。我还准备了一些练习题,如果你想用简单的示例练习 TDD 和单元测试的话。

你准备好尝试一下了吗?

这篇文章是我正在撰写的一系列关于 测试数据管道和数据产品 的文章中的一部分。

我一直期待认识新朋友。如果你想联系我,可以在 Linkedin Github InstagramSubstack 或者 我的个人网站 上找到我。

感谢我的 Thoughtworks 同事 Manisha 和 David 花时间审阅这篇文章的早期版本。感谢 dbt-unit-testing 包 的维护者们的出色工作。

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

改进 Strava 训练日志

原文:towardsdatascience.com/improving-the-strava-training-log-4d2039c49ec4

使用 Strava、Python 和 Matplotlib 进行马拉松跑者训练模式的可视化。

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

·发布于 Towards Data Science ·12 分钟阅读·2023 年 11 月 16 日

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

作为一名马拉松跑者,我使用 Strava。非常多。这是一个很棒的应用程序。除了所有常见的社交功能(分享活动、了解朋友的动态、查看俱乐部的活动等),我依赖 Strava 跟踪我的所有活动,并定期使用它来分析我的训练进展。或者说我尽力而为。我喜欢使用 Strava 的训练日志 来查看我当前的训练情况,相较于以前的年份,但遗憾的是,这不是 Strava 真正强项的地方,尽管训练日志是一个高级功能。

问题的一部分是训练日志提供的信息非常有限。例如,在下面的示例训练日志中,我们可以看到几周的活动记录(彩色圆圈),这些记录总结了训练活动的频率和距离,但没有关于训练强度或努力的任何信息。因此,我认为研究如何改进这一点将是一个有趣的副项目……

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

作者的 Strava 训练日志的一部分;截图由作者制作。

我将从讨论我对 Strava 当前训练日志中缺失内容的看法开始,并建议如何将这些缺失的信息添加到重新构想的训练日志可视化中。我将描述如何实现这一点,使用 Python 和 Matplotlib 的示例,并最终展示一个使用我自己都柏林马拉松 2023 年训练数据的具体结果示例。

动机

上面的 Strava 训练日志示例展示了我今年(2023 年)为都柏林马拉松训练的最后 4 周。每一行对应一周的训练,每一列对应一周中的不同天。我训练的日子通过颜色编码的圆圈表示,显示了当天完成的距离。红色圆圈表示比赛。深绿色圆圈表示长跑。阴影绿色圆圈表示训练 — 通常是更复杂的多部分间歇训练 — 而浅绿色圆圈表示既不是长跑也不是训练的常规跑步。所有这些类别都是由跑者手动分配的。最后,左侧列显示了相关的日期信息和每周的总距离。

这是总结几周训练的一个完全合理且视觉上吸引人的方法。然而,它仅提供了训练的一个非常有限的快照。它仅关注活动频率和训练量(距离)——这当然是相关的——但没有关于训练强度或努力的信息。例如,没有配速或心率(HR)信息,除非它恰好包含在跑者提供的会话标题中,即使如此,这也可能不是会话期间实际情况的可靠指示。没有这些信息,很难评估训练进展,更难比较一周的训练与另一周的训练,更不用说今年的训练与早期年份的训练相比,因为跑步距离只讲述了部分故事。

目标

让我们通过增强这个基本可视化来改进 Strava 的训练日志,以包含以下所有训练特征:

  • 会话(或一周)的平均配速(分钟/公里)、距离和持续时间。

  • Strava 的 相对努力痛苦)得分,用于评估整体训练强度或每周强度。

  • 每个心率区间的时间显示了训练时间如何分配在轻松、中等和艰难的努力之间。Strava 使用 5 个 HR 区间(Z1, …, Z5),这些区间对应于逐渐增强的努力,并基于跑者的心率占其最大心率的比例。

HR 区间数据对跑者很有用,因为它提供了细化的努力/强度度量,并帮助跑者区分轻松和更努力的训练。如上所述,这对跟踪训练进展非常有用。此外,强度和努力也有助于跑者跟踪恢复跑步,以便他们能够以所需的 轻松努力 完成,并为本周晚些时候的更艰难训练做好准备。实际上,跑者经常被鼓励采用 80/20 方法 进行训练——80%的训练应以 轻松 努力(HR 区域 1 和 2)进行——以防止过度训练和潜在伤害。

访问 Strava 数据

本文的重点是 Strava 训练数据的可视化,因此不会详细讨论如何从 Strava 收集这些数据。只需说明 Strava 提供了一个 API,可以在获得用户许可的情况下获得授权访问你的数据或其他用户的数据。我使用了这个 API 来访问我的数据,并且网上有几篇文章——这里或 这里,例如——感兴趣的读者可以了解如何自己进行操作。

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

一个使用 Strava API 收集的每日会话数据样本。

上述是我从 Strava API 收集的活动数据的一个子集。每一行对应一个单独的活动,包括活动日期、距离()、持续时间()、相对努力(痛苦评分)以及在不同心率区间内的跑步距离等信息(在心率区间内花费的时间等)。

训练会话饼图

我们的首要任务是升级当前 Strava 训练日志中单次训练活动的简单可视化;见下文。它显示了会话的名称(用户提供的或默认的 Strava 名称)和完成的距离,但没有任何配速或其他强度相关的信息。

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

单次训练活动(42.4km),对应今年的都柏林马拉松。

下面的图表显示了我们对同一活动的替代建议。这个新图表由几个不同的组件(标题、头部、图表、脚部)组成,以便更容易定位其不同的信息元素,而无需依赖绝对位置坐标;顺便说一句,这些元素周围的边框只是为了突出这些元素,最终可视化中不会使用这些边框。以这种方式使用单独的元素将通过使调整这些图表的大小更容易,并避免不同信息元素之间的碰撞,从而促进以后的网格布局。这些元素包括会话距离和平均配速(脚部),而这次饼图的大小与会话的总时长成比例,这也在标题中指明(190 分钟),以及自上次会话(22 小时)以来的经过(休息)时间。

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

对于单个会话/活动的建议可视化,显示了在每个心率区间花费的时间,以及会话的相对努力、距离、平均配速、总跑步时间以及自上次会话(休息)以来的时间等。

重要的是,与使用简单圆圈来表示会话覆盖的距离不同,饼图用于通过 HR 区间编码重要的强度信息。这些扇区按顺时针方向排列,从“中午”位置开始,按照 HR 区间 (Z1, Z2, …, Z5) 的顺序排列,扇区的大小对应于在该区间内花费的时间比例。扇区的颜色也表示 HR 区间,并与稍后讨论的每周条形图中使用的颜色匹配。在这项活动中,跑步主要发生在 Z2 和 Z3 区间,也有一些时间花在 Z4 区间。

最终,活动的总体相对努力 (RE) 分数显示在饼图的中心,背景颜色与其相对于训练块的值成比例。在这种情况下,深红色背景表明相对努力值为 362 是训练块中最高的;实际上,这是最高的。

显然,这种新的可视化提供了比 Strava 更显著的信息,但在屏幕空间上没有显著增加,并且在强调哪些信息时具有更大的灵活性。例如,上述饼图的总体大小基于整体会话时间(此示例中为 190 分钟),但可以根据运动员的需求进行配置。例如,可以使用总体距离,如 Strava 训练日志中的情况,或相对努力或功率(对于骑自行车的人)或运动员的感知努力,如果可用。如果需要,也可以用配速或功率区间代替 HR 区间。对于感兴趣的读者,本文末尾提供了重建此图表版本的 Python 示例代码。

训练周总结条形图

我们需要的第二种图表总结了一整周或训练。这在默认的 Strava 训练日志中几乎不存在,除了每行旁边提供的每周总距离。

我们希望添加有关每周在每个 HR 区间中所花时间的相关信息,以及配速和相对努力信息。我们需要以与新的饼图视觉效果兼容的方式进行,以便这两种类型的图表可以在我们的新训练日志可视化中一起使用。

新的每周总结图表如下所示,展示了比赛日还有 10 周的都柏林马拉松训练的一周。在那一周内跟踪了 238 分钟的跑步(46.88 公里)(共 4 次),平均配速为 5:40 分钟/公里。每周相对努力较高(RE = 365),大约 53%的训练是在轻松的努力下进行的(Z1 和 Z2;125/238)。条形图显示了每个 HR 区间的每周分钟数,颜色编码与会话饼图中使用的方案匹配。

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

2023 年 8 月 14 日至 20 日的一周的示例每周训练总结图表,距比赛日还有 10 周。

这个图表的风格和结构(标题、标题、图表、页脚)与会议饼图兼容——尺寸、位置、颜色等——这样它们可以在大型图表网格中一起使用。再次强调,根据需要很容易替换不同类型的训练数据,例如配速或功率区间,样本 Python 代码在文章末尾提供给感兴趣的读者以重新创建此图表。

设计训练日志网格

现在我们可以为每个会话构建饼图和每周条形图,是时候将这些图表汇总成一个多周可视化了。就像 Strava 训练日志一样,我们将使用网格布局,每一行对应一个训练周,每一列对应一天;见下图。最左边的列将用于提供每周总结。因此,对于一个 16 周的马拉松训练计划,我们将需要一个具有 16 行(每周一个)和 8 列(每周总结加 7 天)的网格。

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

简单的子图网格。

使用 Matplotlib 生成这样的网格非常简单。例如,下面的代码片段就可以完成这个任务,从而可以根据周和天来生成和定位各个图表。

简单的网格布局代码。

然而,这种方法不适用于我们需要的可视化类型,因为每个可视化组件(会议饼图或周总结)由多个独立组件(标题、页眉、图表、页脚)组成。相反,我们需要一个网格,其外观更像下面的示例,每个组件作为一个单独的轴/子图。

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

更复杂的子图网格,以支持训练日志可视化。

为了生成这种类型的网格,我们可以使用 Matplotlib 强大的gridspec,并使用add_subplot将各个元素(标题、图表、页脚等)分配到网格中的适当位置。深入探讨 Matplotlib 和gridspec的内容超出了本文的范围,但下面展示的网格设置代码说明了总体方法。简而言之,主网格中的 16 行每行分配 10 个单位的高度(row_h)(第 5 行)。然后,每个图表的标题、页眉和页脚部分分配 1 个单位的高度,而图表本身分配 6 个单位的高度。最后,我们为每行添加一个 gutter(1 个单位的高度),以控制行之间的间距。感兴趣的读者可以在这里了解更多。

复杂的网格布局代码。

没有什么能阻止显示比这里讨论的 16 周更多或更少的训练周。例如,大多数马拉松训练计划涵盖 12 到 20 周的训练,而在我们最终的可视化中,我们将展示 12 周的训练。

完成训练日志可视化

将所有这些内容整合起来,产生了下面的大型可视化。在这个案例中,我们展示了 12 周的都柏林马拉松训练,每一行对应一个训练周。第一列显示了每周的总结,其余 7 列显示了每天的活动。没有活动的天数标记为休息日。

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

作者对 2023 年都柏林马拉松的 16 周训练周期。

这是一种复杂的可视化——可能需要一个大屏幕才能舒适地查看——但它比 Strava 的默认训练日志捕捉了更多有用的训练信息。它也有助于更深入的探讨和分析。

比如,我们可以看到,我的大部分努力都是在周三进行的;周三的饼图显示了在 Z3 区间花费的时间明显多于周二和周四的轻松/恢复训练。同样,我在周六的长跑也表现出较高的强度,不仅仅因为距离,还因为这些跑步通常包含了按马拉松配速要求进行的更高强度的阶段,通常在跑步的最后阶段;这些长跑通常会在周日后跟较短的恢复跑。周六还包括了在第 5、7 和 10 周的几场比赛,这也解释了为什么这些天在 Z4 区间花费的时间比其他长跑要多。我们还可以看到,在最后两周的恢复阶段,跑步量下降,而强度保持不变,因为轻松跑被赛前的休息日取代。

最后,值得注意的是,有时跑者会每天跟踪多项活动。例如,一些跑者会进行早晨和晚上的活动,而另一些跑者则会将一个单独的训练分成几个部分,以涵盖单独的热身、主要活动和放松活动。目前,我们将一天中的所有活动合并为一个综合活动。例如,在第 5 周的周六,以上的可视化显示我参加了都柏林半程马拉松(都柏林马拉松系列赛中的一场年度比赛),但当天的数据显示为 30.2 公里的跑步,而不是 21.1 公里的半程马拉松距离。原因是这 30.2 公里包括了单独的热身和放松活动。当然,与其以这种方式汇总活动,我们可以决定实施不同的政策,例如在多项活动的日子里,特别是比赛日,重点关注最长(或最快)的活动。

结论

本文旨在探索如何开发一个更具信息性的训练日志,使用 Strava 的默认训练日志作为起点。最终的可视化捕捉了大量额外的有用信息,特别是关于训练强度和努力程度,这使得评估训练进展变得更为容易。

我们已经描述了如何使用 Python 和 Matplotlib 构建单独的活动和总结图表(下面提供了示例代码),并讨论了如何将这些图表组装成一个复杂但灵活的网格结构。

最后,值得强调的是,这种方法可以很容易地适应其他类型的运动,基于这些运动更相关的信息。例如,如上所述,骑行者可能希望在训练日志中包括功率区间而不是心率区间。实际上,随着休闲运动员变得更加仪器化,通过新的可穿戴传感器,开发者将越来越需要找到新颖的方式来呈现这些新形式的数据,以支持运动员的训练和比赛。

注意:作者制作了所有的图像和代码。

示例代码

绘制会话图表

以下代码示例展示了创建会话饼图的方法。本文假设所需的会话数据(距离、时间、强度等)已经以 Pandas 系列session的形式提供,无需进一步解释。

绘制每周总结图表

以下代码示例展示了创建每周总结条形图的方法。它期望相关的每周会话数据作为数据框week提供。

使用公共表表达式改进你的 SQL 逻辑

原文:towardsdatascience.com/improving-your-sql-logic-with-common-table-expressions-26f04967fc86

使逻辑更容易编写、阅读、解释和维护

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

·发表于 Towards Data Science ·阅读时长 6 分钟·2023 年 12 月 5 日

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

Sam Moghadam Khamseh 的照片,来自 Unsplash

把衣物丢进一堆里并寄希望于最好的方法可能不是管理衣物的长期策略。这样很难做到:

SELECT socks FROM pile 随后执行。

你肯定会遇到困难:

SELECT shirt FROM pile WHERE wrinkles = false 当你需要一件衬衫时。

就像管理你的衣物一样,积极管理 SQL 逻辑的组织有明显的好处。

在这篇文章中,我们将探讨公共表表达式(CTEs)如何改进你的 SQL 逻辑。意大利面条代码很少是好的代码,这在 SQL 中和任何编程语言中都是一样的。在数据管理和工程中没有灵丹妙药。然而,保持你的逻辑有序且易于审查只会对你有帮助。那么,让我们深入了解吧!

一个公共表表达式是什么样的?

我有时称它们为 WITH 子句。为什么?让我们看一个例子:

一个公共表表达式的示例。

我称它们为 WITH 语句,因为它们总是以 … WITH 开头。

一目了然,理解发生了什么有多容易?有三个部分:

  1. 一个识别“高价值客户”的查询。

  2. 一个识别“新客户”的查询。

  3. 最终查询阶段执行一个内连接操作,将“高价值客户”和“新客户”进行连接,提供一个结果集,包含仅存在于这两个类别中的客户。

一种替代的方法

在我了解公共表表达式(CTEs / WITH 语句)之前,这就是我可能会如何处理这个查询:

在有人向我展示 CTE 的价值之前,这就是我编写所有查询的方式。

上面的查询没有什么本质上的问题。实际上,它似乎只是代码的重新组织。

但有时顺序很重要。

顺序,事情的顺序有时确实很重要(谢谢,尤达)。

我的观点是,CTE 方法更容易处理和解释。在阅读 CTE 查询时,我清楚地知道故事是:

  1. 我们有高价值客户。

  2. 我们有新客户。

  3. 我们的结果将返回两个客户集的内连接。

这是一个简单明了的故事。第二个查询也不复杂,但这是我阅读它的体验:

  1. 我们有一个客户。

  2. 我们有总收入(可能是针对客户的)。

  3. 我们有自上次见面以来的天数(可能是针对客户的)。

  4. 我们有一个子查询派生出满足某些收入门槛的客户。

  5. 我们将对另一个子查询执行内连接……

  6. 另一个子查询派生出在过去 90 天内首次出现的客户。

那个故事仍然足够简单易懂,但我需要六个思考步骤来拼凑这个故事,而只需三个步骤就能拼凑出 CTE 版本。我是个简单的人——将工作量减少一半对我来说意义重大。

但说真的,实际上我发现 CTE 方法将逻辑模块化,使得定义 SQL 拼图的小部分变得容易,从而能够在最终查询阶段轻松连接这些拼图。

我们上面看到的是一个玩具示例。在现实世界中,复杂问题通常需要更复杂的逻辑。寻找将复杂问题(或复杂查询)拆分成更小部分的方法是有帮助的。这样,一个复杂的问题可能变成几个简单的问题。如果你能够轻松解决几个简单的问题,那么你就成功地为复杂问题开发了一个简单的解决方案。

总结好处

根据我的经验,这些是将公共表表达式纳入 SQL 工作流程的核心好处:

  1. 逻辑的组织。

  2. 逻辑和查询目的的可读性。

  3. 逻辑的可维护性。

  4. 逻辑的可解释性。

  5. 性能和可扩展性(这是什么?)

1:逻辑的组织

公共表表达式为我们的查询提供了结构。类似于段落为书页提供结构,房间为房子提供结构,函数为程序提供结构,或者……你懂我的意思。

拥有可靠和可预测的模式在提高查询效率方面是绝对有利的。

2:逻辑和查询目的的可读性

查询中的结构组织有助于使其更具可读性。你是否尝试过阅读一本 1800 年代的书?做个快速的 Google 搜索。那些句子有的可能会延续好几

无结构的 SQL 查询就像非常非常长的句子:很难跟踪全部内容。一种思路在哪里结束,另一种思路从哪里开始?

保持逻辑的可读性是一个巨大的胜利。这对你自己以及任何将来查看你查询的人来说都是一个胜利。你的查询中的逻辑不应是一个让艾伦·图灵夜不能寐的加密算法。它们应该是如此简单,以至于非技术人员会想,“嘿,我想我知道这在做什么”。

3: 逻辑的可维护性

当你的查询易于阅读时,它们也容易维护。你不想在写完查询三个月后回来,却要花几个小时来解读这个该死的东西是怎么工作的。

即使是好的查询也很可能需要随着时间的推移进行适应和演变。好的查询应该容易理解、调试,并能扩展以供进一步使用。

公共表表达式使你可以轻松地将逻辑拆分为小块。这提高了查询的可维护性,因为你可以隔离逻辑的特定组件。例如,你可能有一个 500 行的查询,但由于你组织得很好,你知道实现一个新功能只需编辑你 CTE 的特定部分,而这仅涉及 20 行代码。

4: 逻辑的可解释性

结构化、可读的逻辑往往比一堆意大利面条更容易解释。无论是文档、项目交接还是好奇的业务用户挑剔你的实现:如果你的逻辑做了任何重要的事情,你总会需要解释它。

公共表表达式使你可以轻松地将逻辑拆分为小而易于解释的部分。当你的利益相关者理解这些小部分时,你就非常接近赢得他们对整体大局的理解。

5: 性能和可扩展性

从技术上讲,公共表表达式的性能并不比一系列内联子查询更好。我们之前的两个示例在性能上应该是相等的。

但在性能的战场上,我们并不讲究公平。这将使你在数据工程师中脱颖而出:你是否知道如何在你的 favor 中不公平地倾斜天平?公共表表达式可以帮助显而易见地识别在许多工作流中实现的公共查询组件。这为自动化或 ETL 优化的效率打开了大门,允许一次执行该查询,并在多个地方重用其结果集。

你还会发现,在采用公共表表达式后,你会有一个“库”来存储查询组件。你上周写的一部分查询可能与今天你正在处理的新业务用例相关。当你的查询使用公共表表达式组织和结构化得很好时,很容易从你已经解决的问题中提取相关的逻辑片段,并将其插入到需要解决的新问题中。

接下来是什么?

使 SQL 查询正常工作只是开始。设置它以在其生命周期内提供最大价值(和最小头痛)才是关键所在。

回顾一下你以前的一些查询。它们易于理解吗?易于解释和维护吗?如果答案是否定的,那么不妨尝试一下我们在这里讨论的内容。

将你的逻辑组织成一个公共表表达式。将其拆分成更易于管理的逻辑片段。将你的更改给同事看看,看看他们的反应。取用对你有效的部分并加以运用!

总结

本文到此结束。既然你读到了这里,希望你觉得这篇文章有用且有趣!祝你在编写高质量 SQL 时好运。

如果你想要更多内容,请查看我的YouTube 频道!给我留言,告诉我你想探索哪些其他数据工程主题。

大型语言模型中的上下文学习方法

原文:towardsdatascience.com/in-context-learning-approaches-in-large-language-models-9c0c53b116a1?source=collection_archive---------0-----------------------#2023-07-01

简单而强大的技术,使大型语言模型在推理时能够学习新任务

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

·

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

介绍

语言建模(LM)旨在对词序列的生成概率进行建模,以预测未来(或缺失)标记的概率。近年来,语言模型在自然语言处理(NLP)领域带来了革命性变化。现在已经广泛认识到,增加语言模型的规模(例如训练计算、模型参数等)可以在一系列下游 NLP 任务中带来更好的性能和样本效率。调查论文“大型语言模型调查” [1] 涵盖了大型语言模型几乎所有的方面。该论文提供了对 LLMs 文献的最新回顾,详细介绍了预训练方法、指令调优技术及最新的 RLHF 方法的进一步对齐训练。指令调优和对齐调优方法用于根据具体目标调整 LLMs。

在预训练或适应性调优之后,使用 LLMs 的主要方法是设计合适的提示策略来解决各种任务。 一种典型的提示方法,也称为上下文学习(ICL),以自然语言文本的形式制定任务描述和/或示例。

上下文学习

LLMs 展示了一种上下文学习(ICL)能力,即从上下文中的几个示例中学习。许多研究表明,LLMs 可以通过 ICL 执行一系列复杂任务,例如解决数学推理问题。

上下文学习的关键思想是通过类比进行学习。下图展示了语言模型如何通过 ICL 进行决策的示例。首先,ICL 需要几个示例来形成演示上下文。这些示例通常以自然语言模板的形式编写。然后,ICL 将查询问题和一段演示上下文拼接在一起,形成提示,接着将其输入语言模型进行预测 [2]。

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

上下文学习示例

与需要使用反向梯度更新模型参数的监督学习不同,ICL 不进行参数更新,而是直接对预训练的语言模型进行预测。模型期望从示例中学习隐藏的模式,从而做出正确的预测。

什么使得 ICL 具有吸引力?

  1. 用自然语言编写的示例提供了一个可解释的接口,与 LLMs 进行交流。这个范式使得通过更改示例和模板将人类知识融入 LLMs 变得更加容易。

  2. 这类似于人类通过类比进行的决策过程。

  3. 与监督训练相比,ICL 是一种无需训练的学习框架。这不仅大大降低了将模型调整到新任务的计算成本,还使语言模型即服务成为可能,并且可以轻松应用于大规模的真实任务。

但这如何运作呢?

经过预训练,LLM 能够展示出引人注目的 ICL 能力(新兴能力),而无需更新[3]。虽然直观上合理,但 ICL 的工作机制仍不清楚,且少有研究对这两个问题提供了初步解释。

预训练如何影响 ICL 能力?

研究人员建议,当预训练模型达到大规模预训练步骤或模型参数时,它会获得一些新兴的 ICL 能力[3]。一些研究还表明,ICL 能力随着 LLM 参数从 1 亿增加到 1750 亿而增长。研究表明,训练任务的设计是影响 LLM ICL 能力的重要因素。除了训练任务,最近的研究还探讨了 ICL 与预训练语料库之间的关系。研究显示,ICL 的表现更多依赖于预训练语料库的来源,而非规模。

LLM 在推理过程中如何执行 ICL?

在论文“为什么 GPT 能在上下文中学习?”[4]中,研究人员发现了 Transformer 注意力和梯度下降之间的双重形式,并进一步提出将 ICL 理解为隐式微调。他们比较了基于 GPT 的 ICL 和真实任务上的显式微调,发现 ICL 在多个方面表现得类似于微调。在这个框架下,ICL 过程可以解释为:通过前向计算,LLM 生成相对于演示的元梯度,并通过注意力机制隐式执行梯度下降。

斯坦福研究的另一个视角[5]解释了‘上下文学习作为隐式贝叶斯推断’。作者提供了一个框架,其中语言模型通过使用提示来“定位”其在预训练过程中学到的相关概念以完成任务。从理论上讲,我们可以将其视为在提示条件下的潜在概念的贝叶斯推断,这一能力来自于预训练数据中的结构(长期一致性)。

尽管已有一些答案,这项研究仍在不断发展,以更好地理解其机制和潜在原因。

上下文学习方法

现在让我们探讨一些流行的 ICL 方法。

  • 思维链(COT)

  • 自一致性 COT

  • 思维树

思维链(COT)

观察发现,标准提示技术(也称为一般输入输出提示)在复杂推理任务(如算术推理、常识推理和符号推理)上表现不佳。思维链是一种改进的提示策略,用于提升 LLM 在涉及推理的复杂案例中的表现[6]。与 ICL 中仅使用输入输出对构建提示不同,思维链将可以导致最终输出的中间推理步骤纳入提示中。如下例所示。

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

参考文献[6]

上图显示了一个模型生成思维链来解决数学词汇问题的示例,否则它会得到错误的答案。在左侧的 ICL 中,模型提供了数学推理问题的示例或演示及直接答案。但模型无法预测正确答案。

在右侧的 COT 中,模型呈现一个中间步骤以帮助得出给定示例/演示的答案。我们可以看到,当现在要求模型回答类似的推理问题时,它能够正确预测答案,从而证明了 COT 方法在此类用例中的有效性。

如果你看到,一般来说 COT 或 ICL 提供一些示例来演示用例,这称为 少样本(少量示例)。还有一篇论文 [7] 提出了有趣的提示 “让我们一步步思考……”,没有任何示例来演示用例,这称为 零样本(没有示例)

零样本 CoT 中,LLM 首先通过 “让我们一步步思考” 生成推理步骤,然后通过 “因此,答案是” 推导出最终答案。他们发现,当模型规模超过某一大小时,这种策略显著提升了性能,但对小规模模型效果不佳,显示出显著的突现能力模式。

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

参考文献[7]

上图:GPT-3 的示例输入和输出,包括 (a) 标准少样本 (ICL)、(b) 少样本-CoT、© 标准零样本 (ICL) 和 (d) 我们的方法 (零样本-CoT)。

类似于少样本-CoT,零样本-CoT 促进了多步骤推理(蓝色文本),并在标准提示失败的情况下得出正确答案。与每个任务使用逐步推理示例的少样本-CoT 不同,零样本-CoT 不需要任何示例,只需在所有任务中使用相同的提示 “让我们一步步思考”(算术、符号、常识和其他逻辑推理任务)。

这项研究表明,通过添加一个简单的提示 “让我们一步步思考”,LLMs 是相当不错的零样本推理器,以促进在回答每个问题之前逐步思考。

让我们看看下面发生了什么:

尽管零样本-CoT 概念上很简单,但它使用了两次提示来提取推理和答案,如下图所示。

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

参考文献[7]

该过程包括两个步骤:首先是“推理提示提取”以从语言模型中提取完整的推理路径,然后使用第二步“答案提示提取”以从推理文本中提取正确格式的答案。

第一个提示 — 推理提取

在此步骤中,首先使用简单模板 “Q: [X]. A: [T]” 将输入问题 x 修改为提示 x’,其中 [X] 是 x 的输入槽,[T] 是用于提取思考链以回答问题 x 的手工触发句 t 的槽。例如,如果我们使用 “让我们一步一步地思考” 作为触发句,那么提示 x’ 将是 “Q: [X]. A: 让我们一步一步地思考。” 提示文本 x’ 然后输入到语言模型中,生成随后的句子 z。我们可以使用任何解码策略。

其他一些此类提示的例子:

让我们从逻辑上考虑一下这个问题。

让我们通过将问题拆分成步骤来解决这个问题。

让我们像侦探一样一步一步思考。

在我们深入回答之前。

第二步提示——答案提取

在第二步中,生成的句子 z 与提示的句子 x’ 一起用于从语言模型中提取最终答案。具体来说,将三个元素简单地连接在一起,如 “[X’] [Z] [A]”: [X’] 为第一步提示 x’,[Z] 为第一步生成的句子 z,[A] 为提取答案的触发句。这个步骤的提示是自我增强的,因为提示中包含了由相同语言模型生成的句子 z。在实验中,作者根据答案格式使用了略微不同的答案触发器。

例如,使用 “因此,在 A 到 E 之间,答案是” 来处理 多项选择题,以及 “因此,答案(阿拉伯数字)是” 来处理需要 数值答案 的数学问题。

论文 [7] 提出了有趣的想法、各种提示的表现等,请阅读以获取更多细节。

CoT 何时对 LLMs 有效?

仅对足够大的模型(例如,通常包含 10B 或更多参数)有正面效果,而对小模型没有。这一现象被称为大型语言模型的‘涌现能力’。如果某种能力在较小的模型中不存在,但在较大的模型中存在,则该能力被认为是涌现的 [3]。

  • 这主要有效于改进需要逐步推理的任务,如算术推理、常识推理和符号推理。

  • 对于那些不依赖于复杂推理的任务,它可能表现得比标准方法更差。有趣的是,CoT 提示所带来的性能提升似乎只有在标准提示效果较差时才会显著。

为什么 LLMs 可以进行 CoT 推理?

  • 广泛 假设 这可以归因于对代码的训练,因为在其上训练的模型表现出强大的推理能力。直观地看,代码数据有良好的算法逻辑和编程流程,这可能有助于提高 LLMs 的推理表现。然而,这一假设仍然缺乏公开报告的消融实验证据(有无对代码的训练)。

  • CoT 提示与标准提示的主要区别在于在最终答案之前融入了推理路径。因此,一些研究人员探讨了推理路径中不同组成部分的效果。具体来说,一项最近的研究确定了 CoT 提示中的三个关键组成部分,即符号(例如,算术推理中的数值量)、模式(例如,算术推理中的方程式)和文本(即其余的非符号或模式的令牌)。研究表明,后两部分(即模式和文本)对模型性能至关重要,移除其中任何一个都会导致性能显著下降。

这是一个活跃的研究领域,关于这一点的深入讨论请阅读 [2]。还有一项有趣的研究 [8] 讨论了变压器模型中的上下文学习的可能原因。

自一致性 COT

在 COT 中,作者在 [9] 中提出了一种称为自一致性的解码策略,以取代在链式思维提示中使用的贪婪解码策略,这种策略显著提高了语言模型的推理性能。自一致性利用了这样一个直觉,即复杂的推理任务通常允许多个推理路径达到正确答案。问题需要更多的深思熟虑和分析时,可以恢复答案的推理路径的多样性就越大。

首先用链式思维提示语言模型,然后作者提出了**“采样和边际化”**解码过程,而不是贪婪地解码最优推理路径。

下图通过一个例子说明了自一致性方法。

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

参考文献[9]

首先从语言模型的解码器中生成一组多样化的推理路径;每条推理路径可能会导致不同的最终答案,因此通过对采样的推理路径进行边际化来确定最一致的答案。换句话说,通过对模型的解码器中的答案进行多数投票,我们可以在最终答案集中得到最“一致”的答案。

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

多数投票示例

这种方法类似于人类经验,如果多种不同的思维方式得到相同的答案,则对最终答案的正确性信心更大。与其他解码方法相比,自一致性避免了贪婪解码中的重复性和局部最优性,同时减轻了单次采样生成的随机性。

大量实证评估显示,自一致性显著提高了链式思维提示在多个流行的算术和常识推理基准上的表现,包括 GSM8K(+17.9%)、SVAMP(+11.0%)、AQuA(+12.2%)、StrategyQA(+6.4%)和 ARC-challenge(+3.9%)。

自一致性的一个限制是它带来了更高的计算成本。实际上,人们可以尝试少量路径(例如 5 或 10)作为起点,以实现大多数收益,同时不产生过多成本,因为在大多数情况下,性能会迅速饱和。

思维树

[10] 的作者提出了“思维树”(ToT),它在“链式思维”方法上进行了概括,以提示语言模型,并允许在作为解决问题的中间步骤的连贯文本单元(“思维”)上进行探索。ToT 允许语言模型通过考虑多个不同的推理路径并自我评估选择来进行深思熟虑的决策,同时在必要时前瞻或回溯以做出全局选择。结果/实验表明,ToT 在需要非平凡规划或搜索的三项新任务(24 点游戏、创意写作和迷你填字游戏)上显著提升了语言模型的解决问题能力。

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

示意图说明了各种提示方法,每个矩形框代表一个思维

思维树(ToT)允许语言模型(LMs)在思维上探索多个推理路径(见上图)。ToT 将任何问题框架化为对树的搜索,其中每个节点是一个状态 s = [x, z1···i],代表具有输入 x 和到目前为止的思维序列 zi 的部分解决方案。ToT 做了 4 件事:思维分解、思维生成器、状态评估器和搜索算法

1. 思维分解: 将中间过程分解为思维步骤:

虽然 CoT 在没有明确分解的情况下连贯地采样思维,ToT 利用问题特性设计和分解中间思维步骤。如表 1所示,依赖于不同的问题,思维可以是几个单词(填字游戏)、一行方程式(24 点游戏)或一整段写作计划(创意写作)。这就像你将问题分解成几个任务。每个任务是我们讨论的步骤 Zn。请注意,这部分仅涉及将问题分解为任务。就像规划一样,我们在这部分并不实际进行任何思维。

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

参考文献 [10]

2. 思维生成: 在我们为每一步定义任务后,实际生成思维。我们尝试生成 k 个思维作为给定步骤 Zn 的候选项。生成思维有两种方式:采样和提出。

a. 从 CoT 提示中抽取 i.i.d. 思维。我们独立重复生成过程 k 次。当思维空间丰富时(例如,每个思维是一个段落),i.i.d. 样本能带来多样性。

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

在随机选择的创造性写作任务中的一次深思熟虑的搜索步骤。

在上图中,展示了在随机选择的创造性写作任务中的一步深思熟虑的搜索。给定输入,LM 采样了 5 个不同的计划,然后投票 5 次决定哪个计划最佳。多数选择被用来随后用相同的样本-投票程序写出输出段落。

b. 使用“提出提示”顺序提出思维。当思维空间更受限时(例如,每个思维只是一个词或一行),在同一上下文中提出不同的思维可以避免重复。在这种情况下,我们在一次推理中生成 k 个思维。因此,这些 k 个思维可能并不独立。

3. 评估状态: 在这一部分,我们定义一个状态评估函数:v(s)。为了扩展树,我们使用这个函数找到好的路径,就像在棋类编程中一样。我们评估给定的树路径s=[x, z1…i]。有两种方法来定义评估函数:

  • 独立评估每个状态:每个状态‘s’(或路径)将被独立评估。[示例:24 点游戏]

  • 跨状态投票:每个状态‘s’ 将在所有状态集合 S 中进行评估。就像你在自我一致性 COT 中比较 S 中的状态一样。[示例:创造性写作任务]

24 点游戏示例:

24 点游戏是一种数学推理挑战,其目标是使用 4 个数字和基本的算术运算(±*/)得到 24。例如,给定输入“4 9 10 13”,一种解决方案可能是“(10–4) * (13–9) = 24”。

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

‘24 点游戏’ ToT 分解。LM 被提示进行 (a) 思维生成和 (b) 评估。

为了将‘24 点游戏’框架转入 ToT,我们将思维分解为 3 个步骤,每个步骤是一个中间方程。如上图(a)所示,在每个树节点,我们提取“左侧”数字,并提示 LM 提出一些可能的下一步。所有 3 个思维步骤使用相同的“提出提示”,尽管它只有一个包含 4 个输入数字的示例。我们在 ToT 中执行广度优先搜索(BFS),在每一步我们保留最佳的 b = 5 个候选项。为了在 ToT 中执行深思熟虑的 BFS,如图(b)所示,我们提示 LM 评估每个思维候选项为“确定/可能/不可能”,以判断是否能达到 24。目的是推广可以在少量前瞻试验中判定的正确部分解决方案,并根据“过大/过小”的常识消除不可能的部分解决方案,保留其余的“可能”。我们对每个思维进行 3 次采样。

4. 搜索算法:我们尝试扩展树。对于每个叶子节点,我们使用状态评估函数对其进行评估。选择哪个叶子节点进行评估时,我们使用搜索算法。它可以是广度优先搜索或深度优先搜索。根据树的结构,可以插入不同的搜索算法。

从概念上讲,ToT 作为一种通用问题解决方法具有若干优点:

  • 通用性:IO、CoT、CoT-SC 和自我修正可以视为 ToT 的特例(即有限深度和广度的树)。

  • 模块化:基础语言模型以及思维分解、生成、评估和搜索过程都可以独立变化。

  • 适应性:可以适应不同的问题属性、语言模型能力和资源限制。

  • 便利性:无需额外培训,只需一个预训练的语言模型即可。

ToT 框架使语言模型能够更自主和智能地做出决策和解决问题。

局限性:ToT 需要比采样方法更多的资源(例如模型 API 成本)以提高任务表现,但 ToT 的模块化灵活性允许用户自定义这种性能-成本权衡,并且持续的开源努力应该能在不久的将来降低这些成本。

自动提示技术

提示工程是一门经验科学,提示工程方法的效果在模型之间可能差异很大,因此需要大量实验和启发式方法。我们能否自动化这种提示工程过程? 这是一个活跃的研究领域,以下部分讨论了一些自动提示设计方法的尝试。

自动提示增强与选择 COT

在题为“基于标记数据的链式思维自动提示增强与选择”的论文中[11]。大多数 CoT 研究依赖于精心设计的人类标注的理性链来提示语言模型,这在实际应用中提出了挑战,因为标记的训练数据可用,但没有人类标注的理性链。为了自动构建链式思维提示,作者建议了增强-修剪-选择的三步过程:

  1. 增强:使用少量示例或零示例 CoT 提示生成多个伪链式思维;

  2. 修剪:根据生成的答案是否与真实值匹配来修剪伪链。

  3. 选择:应用减少方差的策略梯度策略来学习选定示例的概率分布,同时将示例的概率分布视为策略,将验证集的准确性视为奖励。

Auto-CoT:自动链式思维提示

在“大规模语言模型中的自动化链式思维提示”[12]中,作者提出了 Auto-CoT 范式,以自动构建带有问题和推理链的示例。在这一技术中,作者采用了聚类技术来抽样问题,然后生成链。作者观察到,LLM 往往会犯某些类型的错误。一种错误可能在嵌入空间中类似,因此被分组在一起。通过仅从频繁错误簇中抽取一个或几个样本,我们可以防止过多错误类型的错误示例,并收集多样的例子。

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

Auto-COT : 自动化链式思维提示

Auto-CoT包括以下主要阶段:

  1. 问题聚类:对给定的问题集 Q 进行聚类分析。首先通过 Sentence-BERT 计算 Q 中每个问题的向量表示。将上下文化的向量平均化以形成固定大小的问题表示。然后,使用 k-means 聚类算法处理问题表示,生成 k 个问题簇。

  2. 示例选择:从每个簇中选择一组具有代表性的问题;即从一个簇中选择一个示例。每个簇中的样本按距离簇中心的远近排序,距离中心较近的样本优先选择。

  3. 推理生成:使用零-shot CoT 为选定的问题生成推理链,并构建少-shot 提示以进行推理。

LLM 在 CoT 提示下展示了推理能力。Manual-CoT 的优越性能依赖于手工制作示例。为了消除这种手工设计,提出的 Auto-CoT 自动构建示例。它抽样具有多样性的问题并生成推理链以构建示例。对推理数据集的实验结果表明,在 GPT-3 上,Auto-CoT 的表现始终与需要手工设计示例的 CoT 范式相匹配或超越。

结论

上下文学习或提示有助于我们与 LLM 沟通,以引导其行为实现期望的结果。这是一种提取信息的有吸引力的方法,因为你不需要大量的离线训练集,不需要离线访问模型,并且即使对于非工程师也感觉直观。提示工程旨在利用提示作为为实际应用构建可靠功能的方法。这是一门经验科学,提示工程方法的效果在模型之间可能差异很大,因此需要大量实验和启发式方法。提示需要大量人力来创建和适应新的数据集。注释过程并不简单,因为人类不仅需要选择问题,还需要仔细设计每个问题的推理步骤,因此有必要对提示技术进行自动化。

参考文献

[1] 大型语言模型调查,arxiv.org/pdf/2303.18223.pdf

[2] 上下文学习调查,arxiv.org/pdf/2301.00234.pdf

[3] 大型语言模型的突现能力,arxiv.org/pdf/2206.07682.pdf

[4] 为什么 GPT 可以进行上下文学习?语言模型隐式地执行梯度下降作为元优化器,arxiv.org/pdf/2212.10559.pdf

[5] 将上下文学习解释为隐式贝叶斯推理,ai.stanford.edu/blog/understanding-incontext/

[6] 链式思维提示激发大型语言模型中的推理,arxiv.org/pdf/2201.11903.pdf

[7] 大型语言模型是零样本推理者,arxiv.org/pdf/2205.11916.pdf

[8] 上下文学习与归纳头。Transformer 电路,2022。transformer-circuits.pub/2022/in-context-learning-and-induction-heads/index.html

[9] 自洽性提升了 LLM 中的链式思维推理,arxiv.org/pdf/2203.11171.pdf

[10] 思维树,arxiv.org/pdf/2305.10601.pdf

[11] 自动提示增强与从标注数据中链式思维的选择 arxiv.org/pdf/2302.12822.pdf

[12] 大型语言模型中的自动链式思维提示,arxiv.org/pdf/2210.03493.pdf

[13] 大型语言模型可以自我提升,www.arxiv-vanity.com/papers/2210.11610/

《使用 Devtools 创建和发布 R 数据包的深度指南》

原文:towardsdatascience.com/in-depth-guide-to-creating-and-publishing-an-r-data-package-using-devtools-245b0fd4c359?source=collection_archive---------8-----------------------#2023-10-19

详细讲述了我开发的“Richmondway” R 数据包的步骤,其中包含 Roy Kent 的咒骂词统计

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

·

关注 发布于 Towards Data Science · 9 分钟阅读 · 2023 年 10 月 19 日

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

图片由 Erda Estremera 提供,来源于 Unsplash

当我被邀请在 2023 年 Posit 会议上就动画和互动讲故事进行演讲时,我花了几个月时间考虑完美的数据集。似乎每个有趣的数据集都已经用尽了,我没有灵感用一个平淡无奇的数据集来做演讲。然后有一天,在看“泰德·拉索”这部美国体育喜剧剧集时,罗伊·肯特巧妙地插入的脏话引发了我的灵感。我重新观看了这部剧(顺便说一下,是以 2 倍速观看的),并统计了罗伊使用或示意“F**k”这个词的次数。那就是我的数据集!在这篇文章中,我将带你逐步了解如何将这个数据集转变为一个 R 数据包,使你也能轻松创建一个。

欢迎来到[Richmondway](https://github.com/deepshamenghani/richmondway)的制作过程,这是我第一个 R 数据包,允许你下载数据,逐集、逐季深入探讨罗伊·肯特的词汇。最终,回答一个从未有人问过的问题——罗伊·肯特在哪一季说“F**k”最多?

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

来源:Posit Conference 2023 presentation 由 Deepsha Menghani 提供

我为什么要创建一个包?

  • 我一直渴望学习如何创建 R 包。从一个简单的数据包开始似乎是一个很好的初步尝试。

  • 嵌入数据以进行函数测试是至关重要的。它使用户熟悉包的功能——这是我在未来处理任何复杂包时所需要的步骤。

  • 这个数据集太有趣了,不想自己独享。打包它确保了会后大家都能轻松访问。

所以,无论你是对 R 数据包的创建感到好奇,还是你是“泰德·拉索”迷,泡杯茶,咱们开始吧!

我用来创建 R 数据包的详细步骤

第 0 步:数据集和包名称

这是我的数据集“richmondway”的快照。它有 34 行,对应于每一集,还有 16 列,包含各种值,这些值在我们将要创建的包中进行了描述。

给你的包命名就像给宠物命名一样——非常特别。虽然你会希望选择一个易于记忆的名字,但也要确保它简单,特别是如果你打算公开发布的话。我给我的包命名为Richmondway——这是对“泰德·拉索”中罗伊·肯特曾经效力的足球俱乐部 AFC Richmond 的致敬。而且,因为它以“R”开头,这也是一种幸运的巧合。我还希望这个名字能够清晰地指示包的内容。

第 1 步:安装工具包

安装这些 R 包:devtoolsusethisroxygen2。它们使得构建和记录你的新包变得非常简单。

install.packages(c("devtools", "usethis", "roxygen2"))

第 2 步:将新包创建为项目

你可以使用devtools创建包,有两种方法。Devtools 处理了许多初始包结构设置所需的工作。

方法 1:直接从 RStudio 控制台

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

使用 devtools 创建 R 包的项目录制

方法 2:使用usethis

使用usethis::create_package()命令,你可以通过提供要创建包目录的路径来直接创建一个新包。在本文的其余部分,我将继续展示其他usethis命令,这些命令使得完成大量包创建和文档步骤变得更简单和更快。

usethis::create_package("path/richmondway")

你刚刚创建了一个包含 R 包基本需求的文件夹。如果你窥视其中,会发现一些神秘的文件。不用担心,我们将逐一了解它们。以下是你在项目中会看到的一些文件。

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

项目的初始主目录

第 3 步:添加数据集

我将数据集保存在本地环境中,命名为“richmondway”。运行以下命令将在包的根目录下添加一个‘/data’目录,并将一个“.rda”文件放入其中。

usethis::use_data(richmondway)

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

“data”文件夹中的一个名为“richmondway.rda”的单一文件

第 4 步:创建数据字典data.R

这是描述你的数据集的地方。相信我,描述越好,其他人越容易发掘其潜力。这也会在后续步骤中纳入你的文档。你可以使用以下命令创建此文件,然后稍后更新所有所需的详细信息。

usethis::use_r("data")

此命令将创建一个data.R文件在R文件夹内。更新该文件的内容以包含数据集中的格式和列。虽然我的数据集中有更多列,但在这个例子中,我只展示了三列。你应尽可能清楚地添加所有列的描述,因为它将出现在数据集包文档中。下面的格式在data.R文件内用于创建描述。

#' Data to showcase f**k count
#'
#' A dataset containing the number of times the word f**k was used in Ted Lasso by Roy Kent.
#'
#' @format A data frame with 34 rows and 16 columns.
#' \describe{
#' \item{Character}{Single value - Roy Kent}
#' \item{Episode_order}{The order of episodes from the 1st to the last}
#' \item{Season}{The season 1,2 or 3 associated with the count}
#' }
#' @source Created by Deepsha Menghani by watching the show and counting the number of F**ks used in sentences and as gestures
#'
#' @examples
#' data(richmondway)
"richmondway"

让我们将这个文件拆解为它的组成部分:

描述注释:

#' Data to showcase f**k count
#'
#' A dataset containing the number of times the word f**k was used in Ted Lasso by Roy Kent.

这是数据集的简短标题和描述。以#'开头的注释用于以特殊方式注解 R 对象,这些对象将被roxygen2包识别,该包在 R 中用于生成文档。

格式注释:

#' @format A data frame with 34 rows and 16 columns.

这指定了数据的格式。在这种情况下,数据集是一个包含 34 行和 16 列的数据框。

变量描述:

#' \describe{
#' \item{Character}{Single value - Roy Kent}
#' \item{Episode_order}{The order of episodes from the 1st to the last}
#' \item{Season}{The season 1,2 or 3 associated with the count}
#' }

这一部分详细描述了数据框中的一些主要变量/列。\describe块用于列出并描述每个由\item标签表示的变量,包括变量的名称及其描述。

来源注释:

#' @source Created by Deepsha Menghani by watching the show and counting the number of F**ks used in sentences and as gestures

这提供了关于数据的来源或出处的信息。重要的是要注明创作者并提供数据收集的背景。

示例评论:

#' @examples #' data(richmondway)

这提供了一个示例,说明用户如何访问和使用数据集。在这种情况下,它只是演示了如何在安装你的包后将数据加载到 R 中。

数据名称:

"richmondway"

这是数据集的名称。它用引号括起来,因为这表明该文档与包中同名的数据集相关联。

当用户在 R 中安装和加载你的包后,输入 ?richmondway,他们会看到以结构化格式呈现的文档,帮助他们了解数据集的内容、结构以及如何使用它。

步骤 5:更新 “DESCRIPTION” 文件

描述文件是包的更高层次的文档。查看你的包主文件夹中的 DESCRIPTION 文件,它应该预先填充了说明,需要更新为正确的描述。我在描述文件中更新的字段如下,其余保持默认。

Package: richmondway
Title: A dataset containing the number of times the word f**k was used in Ted Lasso by Roy Kent
Authors@R: person("Deepsha", "Menghani", email = "abc@gmail.com", role = c("aut", "cre"))
Description: Downloads the dataset containing the number of times the word f**k was used in Ted Lasso by Roy Kent.
License: file LICENSE

步骤 6:创建“LICENSE”文件

这个文件类似于你包的简历。请注意,在我的 DESCRIPTION 文件中,我提到一个叫 LICENSE 的文件。这个文件还不存在,因此我们现在将创建它。它会指向一个存储许可证信息的文件。许可证信息告知用户如何使用通过此包提供的数据,即使用权。在我的案例中,我使用了 CC0 许可证,并使用下面的命令将标准描述添加到 LICENSE 文件中。

license_text <- 'CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his or her rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law.
You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission.
For more information, please see
<http://creativecommons.org/publicdomain/zero/1.0/>'

writeLines(license_text, con = "packagepath/LICENSE")

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

新创建的 LICENSE 文件在项目主目录中突出显示

步骤 7:加载文档

现在所有文件已创建并更新,我们使用下面的便捷命令加载文档。此命令将使用 data.R 文件创建文档,该文件中添加了数据的描述。文档被放置在一个新创建的文件夹 man 中,代表“manual”。

devtools::document()

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

新创建的 “man” 文件夹在项目主目录中突出显示

一旦运行文档命令,你可以使用帮助命令 ?richmondway,它应打开包文档。确保文档清晰,data.R 文件中的必要细节按预期显示。

步骤 8:检查

你现在可以通过运行下面的命令来测试一切是否顺利运行并正确创建。该命令执行广泛的检查,以确保你的包的一致性和有效性。

devtools::check()

devtools::check() 的输出会给出 NOTES、WARNINGS 和 ERRORS,每个都需要不同程度的关注:

错误:必须立即修复,因为它们表明存在重大问题。

警告:应予以解决,以确保功能性和 CRAN 合规性。

注意事项:提供有用的建议和提示,有时需要在 CRAN 提交时处理。

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

步骤 9:在本地安装包并测试

以下来自 devtools 的命令可用于在本地安装包。然后,使用您在 data.R 文件中分享的相同方法来测试数据访问。

devtools::install() # Install the package locally
data(richmondway) # Access the data through the package

第 10 步。将您全新的包发布到 GitHub

现在我们需要初始化我们的 Git 仓库,并使用一些更方便的 usethis 命令将包推送到 GitHub。在运行这些命令之前,请确保您拥有一个 GitHub 账户,并且已为 GitHub 设置了 SSH 密钥或个人访问令牌(PAT)。

usethis::use_git() # Git integration
usethis::use_github() # Github integration

usethis::use_git() 是做什么的

  • 初始化 Git: 此功能在您的项目中初始化一个新的 Git 仓库。

  • 第一次提交:它使用项目的当前状态进行初始提交。

usethis::use_github() 是做什么的

  • GitHub 仓库创建: 此功能帮助创建一个新的 GitHub 仓库,并将您的本地 Git 仓库连接到远程 GitHub 仓库。

  • 身份验证:帮助设置与 GitHub 的身份验证。它会检查您是否已通过 GitHub 进行身份验证。如果没有,它可能会提示您进行身份验证。

  • 推送: 将您的本地 git 提交推送到远程 GitHub 仓库。

第 11 步:最后与世界分享您的包

现在您可以分享您的包仓库链接。任何人都可以直接从 GitHub 使用 devtools::install_github(“your_username/packagename”) 安装您的包并访问数据。

例如,可以使用以下命令访问我 GitHub 仓库包中的 richmondway 数据:

devtools::install_github("deepshamenghani/richmondway")

你做到了!

如果您已经到达这一点,恭喜您!您刚刚把一个有趣的狂欢观看时光变成了既有教育意义又令人愉快的经历。

随意玩弄这个有趣的数据集,并在您的分析和可视化中标记我。或者,您可以在 GitHub 上 fork [richmondway](https://github.com/deepshamenghani/richmondway) 并为 Roy Kent 词汇表做贡献。继续打包吧!

资源

  1. Richmondway package 仓库

  2. R Packages 由 Hadley Wickham 和 Jennifer Bryan 编写

  3. Roxygen2 文档

  4. Usethis

  5. Devtools

快乐编码!如果愿意,可以在 Linkedin 上找到我。

所有图片和屏幕录像,除非另有说明,均由作者提供。

将 Llama 2 的延迟和吞吐量性能提高多达 4 倍

原文:towardsdatascience.com/increase-llama-2s-latency-and-throughput-performance-by-up-to-4x-23034d781b8c?source=collection_archive---------1-----------------------#2023-08-09

Llama-2 13B 的现实世界基准

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

·

关注 发布在 Towards Data Science · 7 分钟阅读 · 2023 年 8 月 9 日

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

作者提供的图像 — 使用 Stable Diffusion 创建

介绍

在大型语言模型(LLMs)的领域,将这些先进系统集成到实际的企业应用中是一个迫切的需求。然而,生成式 AI 发展的速度如此之快,以至于大多数人无法跟上这些进展。

一种解决方案是使用如 OpenAI 提供的托管服务。这些托管服务提供了精简的解决方案,但对于那些无法访问这些服务或优先考虑安全和隐私等因素的人,另一个途径是:开源工具。

目前开源生成 AI 工具非常流行,许多公司争相推出其 AI 驱动的应用程序。在快速构建的过程中,公司们常常忘记,要真正从生成 AI 中获得价值,他们需要构建“生产”就绪的应用程序,而不仅仅是原型。

在这篇文章中,我想向你展示 Llama 2 使用两种不同推理方法的性能差异。第一种推理方法是通过 Fast API 服务的容器化 Llama 2 模型,这是一种在开发者中非常受欢迎的选择,用于将模型作为 REST API 端点进行服务。第二种方法是通过 文本生成推理 服务的相同容器化模型,这是 Hugging Face 开发的开源库,用于轻松部署 LLM。

我们正在查看的这两种方法都旨在适用于实际使用,例如在商业或应用程序中。但重要的是要认识到,它们的扩展方式不同。我们将深入比较这两种方法,看看它们各自的表现,并更好地理解差异。

支撑 OpenAI 和 Cohere 的 LLM 推理

你是否曾经好奇为什么 ChatGPT 这么快?

大型语言模型需要大量计算能力,由于其庞大的规模,它们往往需要多个 GPU。在处理大型 GPU 集群时,公司必须非常注意计算资源的使用情况。

像 OpenAI 这样的 LLM 提供商运行大型 GPU 集群来支撑其模型的推理。为了最大限度地提高 GPU 性能,他们使用像 Nvidia Triton 推理服务器 这样的工具来提高吞吐量并减少延迟。

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

插图灵感来源 — Triton 推理服务器架构

尽管 Triton 性能卓越且有许多优点,但对于开发者来说使用起来非常困难。许多 Hugging Face 上的较新模型在 Triton 上不受支持,添加对这些模型的支持的过程也并不简单。

这时,文本生成推理 (TGI) 就派上用场了。这个工具提供了与 Triton 相同的性能提升,但它更用户友好,并且与 Hugging Face 模型兼容性好。

LLM 推理优化

在我们深入基准测试之前,我想介绍一些现代推理服务器如 TGI 用于加速 LLM 的优化技术。

  1. 张量并行

LLM 通常太大而无法容纳在单个 GPU 上。通过一种称为 模型并行 的概念,可以将模型分割到多个 GPU 上。张量并行 是一种模型并行,它将模型分割成多个由不同 GPU 独立处理的分片。

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

插图灵感来自于 来源

简而言之,想象你在拼一个大拼图,但它太大了,你无法把所有的拼图片放在一张桌子上。所以,你决定和你的朋友一起工作。你把拼图分成几个部分,每个人同时处理自己的一部分。这样,你可以更快地完成拼图。

2. 连续批处理

当你向 LLM 发起 API 调用时,它会一次性处理并返回结果。如果你发起 5 个 API 调用,它会顺序处理每一个。这实际上意味着我们有一个批处理大小为 1,即每次只能处理 1 个请求。正如你所猜测的,这种设计并不理想,因为每个新请求必须等待前一个请求完成。

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

插图灵感来自于 静态批处理 — 需要等所有进程完成后才能处理更多请求

通过增加批处理大小,你可以并行处理更多请求。批处理大小为 4 时,你可以并行处理 5 个 API 调用中的 4 个。你必须等到这 4 个请求全部完成后,才能处理第 5 个请求。

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

插图灵感来自于 连续批处理 — 你可以立即处理新请求,而不必等待所有进程完成

连续批处理基于使用更大批处理大小的想法,并进一步通过立即处理新任务来提升效率。例如,假设你的 GPU 的批处理大小为 4,这意味着它可以并行处理 4 个请求。如果你发起 5 个请求,其中 4 个会被并行处理,而第一个完成的进程会立即处理第 5 个请求。

Llama 2 基准测试

现在我们对优化方案有了基本的理解,能够实现更快的 LLM 推理,让我们来看一下Llama-2 13B模型的一些实际基准。

我想测试这个模型的 2 个主要指标:

  • 吞吐量(tokens/second)

  • 延迟(完成一次完整推理所需的时间)

我想比较 Llama 推理在两种不同实例上的性能。一种实例通过 FastAPI 运行,而另一种通过 TGI 操作。两个设置都利用了 GPU 进行计算。

注意:这两个实例都没有量化模型的权重。

TGI 设置利用了两个 GPU*(NVIDIA RTX A4000)的强大性能来实现并行,而 FastAPI 依赖于一个(NVIDIA A100)*,尽管更强大的 GPU。

直接比较这两种方法有点棘手,因为 FastAPI 不允许模型分布在两个 GPU 上。为了公平起见,我选择为 FastAPI 实例配备了更强大的 GPU。

对模型发出的每个 API 请求都使用相同的提示,生成的输出 token 限制设置为128 tokens

吞吐量结果:

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

左:Fast API 与 TGI 吞吐量 | 右:平均吞吐量提升 — 作者插图

分析:

  • 在这两种情况下,随着推理请求数量的增加,吞吐量都会下降。

  • 批处理显著提高了 LLM 的吞吐量,这就是为什么 TGI 的吞吐量更好的原因。

  • 尽管 Fast API 实例具有更多的 GPU 内存(VRAM)用于处理更大的批次请求,但它处理这个过程并不高效。

延迟结果:

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

左:Fast API 与 TGI 延迟 | 右:平均延迟性能提升 — 作者插图

分析:

  • 张量并行使 TGI 的延迟减少了近 5 倍!

  • 随着请求数量的增加,基于 Fast API 的实例的延迟超过 100 秒。

从结果中可以看出,与现成的 API 包装器相比,优化过的推理服务器具有很高的性能。作为最终测试,我想评估当生成的输出 token 限制增加到256 tokens时 TGI 的性能,与128 tokens相比。

吞吐量 TGI 128 tokens 与 256 tokens:

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

128 token 与 256 token TGI 吞吐量测试 — 作者插图

正如你所见,尽管生成的 token 数量翻倍,但吞吐量非常相似。需要注意的是,这张图表没有显示,在 300 个并发请求时,吞吐量降到大约 2 token/秒,同时生成 256 token 的输出。此时,延迟超过每个请求 100 秒,并且出现了多次请求超时。由于这些显著的性能限制,这种情况的结果被排除在图表之外。

延迟 TGI 128 tokens 与 256 tokens:

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

128 token 与 256 token TGI 延迟测试 — 作者插图

与吞吐量不同,生成更长文本序列时,延迟明显增加。增加更多的 GPU 可以帮助减少延迟,但会带来财务成本。

结论

我写这篇博客的目标是比较大规模 LLM 的实际性能*(每秒数百个请求)*。通常情况下,部署模型在像 Fast API 这样的 API 包装器后是很简单的,但对于 LLM 而言,你可能会错过相当多的性能。

即使使用现代推理服务器的优化技术,其性能也无法与像 ChatGPT 这样的托管服务相比。OpenAI 当然运行了几个大型 GPU 集群来为他们的模型提供推理支持,并结合了他们自己内部的优化技术。

然而,对于生成式 AI 的使用场景,企业可能需要采用推理服务器,因为它们在可扩展性和可靠性方面远超传统的模型部署技术。

感谢阅读!

平静。

分布式随机森林的推断

原文:towardsdatascience.com/inference-for-distributional-random-forests-64610bbb3927?source=collection_archive---------10-----------------------#2023-02-17

一种强大的非参数方法的置信区间

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

·

关注 发表在 Towards Data Science ·17 分钟阅读·2023 年 2 月 17 日

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

(分布式)随机森林的特性。本文:提供不确定性度量的能力。来源:作者。

在之前的文章中,我详细讨论了分布随机森林方法,这是一种可以非参数地估计多变量条件分布的随机森林类型算法。这意味着我们能够在给定一些协变量X的情况下,非参数地学习多变量响应Y的整个分布,而不仅仅是学习其条件期望。DRF 通过学习权重 w_i(x) 对于 i=1,…,n 训练点来完成这项工作,这些权重定义了分布,并且可以用于估计广泛的目标。

到目前为止,这种方法仅产生了分布的“点估计”(即* n* 权重 w_i(x) 的点估计)。虽然这足以预测响应的整个分布,但它并未提供考虑数据生成机制随机性的方法。也就是说,即使这个点估计在大样本量下(在一系列假设下)越来越接近真实值,但在有限样本量下其估计仍然存在不确定性。幸运的是,现在有一种(可证明的)方法来量化这种不确定性,如我在本文中阐述的。这基于我们关于arXiv的新论文。

本文的目标有两个:首先,我想讨论如何基于我们的论文将不确定性估计添加到 DRF 中。论文相当理论化,因此我从几个示例开始。接下来的部分快速浏览这些理论结果,以供感兴趣的人参考。然后我解释了如何利用这些结果获取广泛目标的(基于采样的)不确定性度量。其次,我讨论了[1]的 CoDiTE 和这一概念的一个特别有趣的例子,即条件见证函数。这个函数是一个复杂的对象,但正如我们将在下面看到的那样,我们可以轻松地使用 DRF 进行估计,并且基于本文中介绍的概念,甚至可以提供渐近置信带。一个如何应用的详细真实数据示例见这篇文章

在整个过程中,我们假设有一个 d 变量的 i.i.d. 样本 Y*_1, …,* Y*_n* 和一个 p 变量的 i.i.d. 样本 X*_1,…,X_n*。目标是估计 Y*|*X=x 的条件分布。

我们将需要以下软件包和函数:

library(kernlab)
library(drf)
library(Matrix)
library(Hmisc)

source("CIdrf.R")

文件“CIdrf.R”中的函数如下所示。

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

示例

我们从一个简单的例子开始模拟,其中 d=1p=2

 set.seed(2)

n<-2000
beta1<-1
beta2<--1.8

# Model Simulation
X<-mvrnorm(n = n, mu=c(0,0), Sigma=matrix(c(1,0.7,0.7,1), nrow=2,ncol=2))
u<-rnorm(n=n, sd = sqrt(exp(X[,1])))
Y<- matrix(beta1*X[,1] + beta2*X[,2] + u, ncol=1)

请注意,这只是一个异方差线性模型,其中误差项的方差依赖于 X_1 的值。当然,如果你知道 XY 的影响是线性的,你就不会使用 DRF 或任何随机森林,而是直接使用线性回归。但为了这个目的,知道真实情况是很方便的。由于 DRF 的工作是估计给定 X*=x 的条件分布,我们现在固定 x 并估计给定 X=*x 的条件期望和方差。

我们选择一个位于 X 分布中心的点,周围有大量观察数据。一般来说,当使用任何随机森林方法处理 X 观测数据的边界点时,应格外小心。

# Choose an x that is not too far out
x<-matrix(c(1,1),ncol=2)

# Choose alpha for CIs
alpha<-0.05

最后,我们拟合我们的 DRF 并获得权重 w_i(x)

## Fit the new DRF framework
drf_fit <- drfCI(X=X, Y=Y, min.node.size = 2, splitting.rule='FourierMMD', num.features=10, B=100)

## predict weights
DRF = predictdrf(drf_fit, x=x)
weights <- DRF$weights[1,]

如下所述,我们在这里构建的 DRF 对象不仅包含权重 w_i(x),还包含一个 B 权重样本,这些权重对应于从 w_i(x) 的分布中抽取的样本。我们可以使用这 B 次抽样来逼近我们想要估计的任何分布,如我现在在两个示例中所说明的。

示例 1:条件期望

首先,我们简单地做了大多数预测方法会做的事情:我们估计条件期望。使用我们的方法,我们还在其周围构建了一个置信区间。

# Estimate the conditional expectation at x:
condexpest<- sum(weights*Y)

# Use the distribution of weights, see below
distofcondexpest<-unlist(lapply(DRF$weightsb, function(wb)  sum(wb[1,]*Y)  ))

# Can either use the above directly to build confidence interval, or can use the normal approximation.
# We will use the latter
varest<-var(distofcondexpest-condexpest)

# build 95%-CI
lower<-condexpest - qnorm(1-alpha/2)*sqrt(varest)
upper<-condexpest + qnorm(1-alpha/2)*sqrt(varest)
c(lower, condexpest, upper)

(-1.00, -0.69 -0.37)

重要的是,虽然估计值有点偏差,但这个置信区间包含了真实值,真实值如下:

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

示例 2:条件方差

现在假设我们想要找到方差 Var(Y|X=x),而不是条件均值。这对于一个无法利用线性特性的非参数方法来说是一个相当具有挑战性的例子。实际情况如下:

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

使用 DRF,我们可以如下估计:

# Estimate the conditional expectation at x:
condvarest<- sum(weights*) - condexpest²

distofcondvarest<-unlist(lapply(DRF$weightsb, function(wb)  {
  sum(wb[1,]*) - sum(wb[1,]*Y)²
  } ))

# Can either use the above directly to build confidence interval, or can use the normal approximation.
# We will use the latter
varest<-var(distofcondvarest-condvarest)

# build 95%-CI
lower<-condvarest - qnorm(1-alpha/2)*sqrt(varest)
upper<-condvarest + qnorm(1-alpha/2)*sqrt(varest)

c(lower, condvarest, upper)

(1.89, 2.65, 3.42) 

因此,真实参数被包含在置信区间内,这正是我们所期望的,事实上,我们的估计值与真实值非常接近!

我们现在研究这些示例背后的理论,然后再来看一个因果分析中的第三个示例。

RKHS 中的渐近正态性

在本节和下一节中,我们简要关注论文中推导的理论结果。如上所述和文章中解释的,DRF 在测试点 x 上呈现分布预测。也就是说,我们获得一个估计值

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

条件分布的 Y 给定 X*=*x。这只是一个典型的经验度量方式,魔力在于权重 w_i(x) — 它们可以用来轻松地获得感兴趣量的估计量,甚至直接从分布中采样。

为了获得这个估计,DRF 实际上是在再生核希尔伯特空间(RKHS)中估计条件均值。RKHS 是通过核函数 k(y_1, y*_2)* 定义的。通过这个核,我们可以将每个观测 Y*_i* 映射到希尔伯特空间中,作为 k(Y_i, .)。利用这个极其强大的工具可以进行多种方法,例如核岭回归。关键点是,在某些条件下,任何分布都可以表示为这个 RKHS 的一个元素。事实证明,真正的条件分布可以表示为 RKHS 中的以下期望:

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

所以这只是另一种表达 Y 给定 X*=*x 的条件分布的方式。然后我们尝试用 DRF 来估计这个元素,如下所示:

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

我们再次使用了从 DRF 获得的权重,但这次用 k(Y_i,.) 来形成加权和,而不是上述的狄拉克测度。我们可以通过编写任意两个中的任何一个来在两个估计之间来回映射。这很重要,因为我们可以将条件分布估计写成 RKHS 中的加权均值!就像原始的随机森林在实数中估计均值(Y 给定 X*=*x 的条件期望)一样,DRF 在 RKHS 中估计均值。结果表明,我们还获得了条件分布的估计。

这对我们的故事很重要,因为在 RKHS 中,这个加权均值在某些方面表现得与 d 维的(加权)均值非常相似。也就是说,我们可以利用现有的工具来研究其一致性和渐近正态性。这是相当显著的,因为所有有趣的 RKHS 都是无限维的。第一篇 DRF 论文 已经确立了 RKHS 中估计器(1)的均衡性。我们的新论文现在进一步证明了,此外,

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

其中 sigma_n 是趋近于零的标准差,而Sigma*_*x 是一个替代协方差矩阵的算子(这与 d 维欧几里得空间中的表现非常相似)。

获得采样分布

好的,那么,我们在无限维空间中得到了一个渐近正态性结果,这到底意味着什么?首先,这意味着从 DRF 估计中得出的估计器如果“平滑”到足够程度,也将趋向于渐近正态。然而,这还不够有用,因为我们还需要有方差估计。这里我们论文中的进一步结果派上用场。

我们在这里省略了很多细节,但本质上我们可以使用以下子样本方案:不是仅仅拟合N棵树来构建我们的森林,而是构建BL棵树(使得N=BL*)。现在,对于每一组树或迷你森林,我们随机子样本约一半的数据点,然后仅使用此子样本拟合森林。我们称这个样本子集为S。对于每个抽取的S,我们得到另一个在希尔伯特空间中表示的 DRF 估计量

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

仅使用S中的样本。请注意,与自助法一样,我们现在有两个随机性来源,即使忽略森林的随机性(理论上我们假设B足够大,以使森林的随机性可忽略不计)。一个来源是数据本身,另一个来源是我们在随机选择S时引入的人工随机性。关键是,给定数据的情况下,S的随机性在我们的控制之下——我们可以绘制任意数量的子集S。所以问题是,如果我们只考虑S的随机性并固定数据,那么我们的估计量(2)会发生什么?值得注意的是,我们可以证明

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

这仅仅意味着,如果我们固定数据的随机性,仅考虑来自S的随机性,那么估计量(2)减去估计量(1)将以相同的极限收敛到与原始估计量减去真实值相同的极限!这实际上是自助法理论的工作原理:我们已经证明了我们可以从中采样的东西,即

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

收敛到我们无法访问的东西,即

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

因此,为了对后者进行推断,我们可以使用前者!这实际上是人们在自助法理论中提出的标准论点,以证明为什么自助法可以用来近似采样分布!没错,即使是自助法,尽管人们经常在小样本中使用,它也只在大样本范围内才真正有意义(理论上)。

现在我们来使用这个。

这实际上是什么意思?

我们现在展示这在实践中的含义。接下来,我们定义两个从 CRAN 包drf的 drf 函数派生的新函数。

## Functions in CIdrf.R that is loaded above ##

drfCI <- function(X, Y, B, sampling = "binomial",...) {

### Function that uses DRF with subsampling to obtain confidence regions as
### as described in https://arxiv.org/pdf/2302.05761.pdf
### X: Matrix of predictors
### Y: Matrix of variables of interest
### B: Number of half-samples/mini-forests

  n <- dim(X)[1]

  # compute point estimator and DRF per halfsample S
  # weightsb: B times n matrix of weights
  DRFlist <- lapply(seq_len(B), function(b) {

    # half-sample index
    indexb <- if (sampling == "binomial") {
      seq_len(n)[as.logical(rbinom(n, size = 1, prob = 0.5))]
    } else {
      sample(seq_len(n), floor(n / 2), replace = FALSE)
    }

    ## Using refitting DRF on S
    DRFb <- 
      drf(X = X[indexb, , drop = F], Y = Y[indexb, , drop = F],
          ci.group.size = 1, ...)

    return(list(DRF = DRFb, indices = indexb))
  })

  return(list(DRFlist = DRFlist, X = X, Y = Y) )
}

predictdrf<- function(DRF, x, ...) {

### Function to predict from DRF with Confidence Bands
### DRF: DRF object
### x: Testpoint

  ntest <- nrow(x)
  n <- nrow(DRF$Y)

  ## extract the weights w^S(x)
  weightsb <- lapply(DRF$DRFlist, function(l) {

    weightsbfinal <- Matrix(0, nrow = ntest, ncol = n , sparse = TRUE)

    weightsbfinal[, l$indices] <- predict(l$DRF, x)$weights 

    return(weightsbfinal)
  })

  ## obtain the overall weights w
  weights<- Reduce("+", weightsb) / length(weightsb)

return(list(weights = weights, weightsb = weightsb ))
}

Witdrf<- function(DRF, x, groupingvar, alpha=0.05, ...){

### Function to calculate the conditional witness function with
### confidence bands from DRF
### DRF: DRF object
### x: Testpoint

  if (is.null(dim(x)) ){

  stop("x needs to have dim(x) > 0")
  }

  ntest <- nrow(x)
  n <- nrow(DRF$Y)
  coln<-colnames(DRF$Y)

  ## Collect w^S
  weightsb <- lapply(DRF$DRFlist, function(l) {

    weightsbfinal <- Matrix(0, nrow = ntest, ncol = n , sparse = TRUE)

    weightsbfinal[, l$indices] <- predict(l$DRF, x)$weights 

    return(weightsbfinal)
  })

  ## Obtain w
  weightsall <- Reduce("+", weightsb) / length(weightsb)

  #weightsall0<-weightsall[, DRF$Y[, groupingvar]==0, drop=F]
  #weightsall1<-weightsall[,DRF$Y[, groupingvar]==1, drop=F]

  # Get the weights of the respective classes (need to standardize by propensity!)
  weightsall0<-weightsall*(DRF$Y[, groupingvar]==0)/sum(weightsall*(DRF$Y[, groupingvar]==0))
  weightsall1<-weightsall*(DRF$Y[, groupingvar]==1)/sum(weightsall*(DRF$Y[, groupingvar]==1))

  bandwidth_Y <- drf:::medianHeuristic(DRF$Y)
  k_Y <- rbfdot(sigma = bandwidth_Y)

  K<-kernelMatrix(k_Y, DRF$Y[,coln[coln!=groupingvar]], y = DRF$Y[,coln[coln!=groupingvar]])

  nulldist <- sapply(weightsb, function(wb){
    # iterate over class 1

    wb0<-wb*(DRF$Y[, groupingvar]==0)/sum(wb*(DRF$Y[, groupingvar]==0))
    wb1<-wb*(DRF$Y[, groupingvar]==1)/sum(wb*(DRF$Y[, groupingvar]==1))

    diag( ( wb0-weightsall0 - (wb1-weightsall1) )%*%K%*%t( wb0-weightsall0 - (wb1-weightsall1) )  )

  })

  # Choose the right quantile
  c<-quantile(nulldist, 1-alpha)

  return(list(c=c, k_Y=k_Y, Y=DRF$Y[,coln[coln!=groupingvar]], nulldist=nulldist, weightsall0=weightsall0, weightsall1=weightsall1))

} 

因此,通过我们的方法,我们不仅得到点估计形式的权重w_i(x),还得到B个权重的样本,每个样本表示从条件分布的估计量的分布中独立抽取的结果(这听起来比实际更令人困惑,请记住这些例子)。这仅仅意味着我们不仅有一个估计量,还有一个其分布的近似!

现在我转向一个更有趣的例子,这是我们仅能使用 DRF 做的(据我所知)。

因果分析示例:见证函数

假设我们有两组观测值,分别是组 W=1 和组 W=0,我们想要找出组别与变量 Y 之间的因果关系。在 这篇文章 的例子中,这两组分别为男性和女性,而 Y 为小时工资。此外,我们有混杂变量 X,我们假设它们影响 WY。我们在这里假设 X 确实包含所有相关的混杂变量。这是一个大假设。形式上,我们假设无混杂:

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

和重叠:

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

通常,人们会比较两个组之间的条件期望:

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

这是 x 处的条件平均处理效应(CATE)。这是一个自然的起点,但在最近的一篇论文 ([1]) 中,引入了 CoDiTE 作为这一思想的推广。CoDiTE 不仅仅关注期望值的差异,还建议查看其他量的差异。一个特别有趣的例子是条件见证函数:对于两个组,我们如上所述

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

因此,我们考虑在 RKHS 中两个条件分布的表示。除了作为条件分布的表示,这些量也是实值函数:对于 j=0,1

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

给出这两个量之间差异的函数,

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

称为条件见证函数

为什么这个函数很有趣?事实证明,这个函数展示了两个密度如何相互关系:对于函数值为负的 y,类 1 在 y 处的条件密度小于 0 的条件密度。类似地,如果函数在 y 处为正,这意味着 1 的密度在 y 处高于 0 的条件密度(其中“条件”始终指的是条件于 X=x)。至关重要的是,这可以不需要估计密度*,这很困难,尤其是对于多变量 Y

最后,我们可以为估计的条件见证函数提供均匀置信带,通过使用上面的 B 样本。我在这里不详细说明,但这些基本上是我们上面使用的条件均值置信区间的类比。至关重要的是,这些置信带应在特定的 x 上对函数值 y 有效。

让我们用一个例子来说明:我们模拟以下数据生成过程:

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

即,X_1, X_2在(0,1)上独立均匀分布,W为 0 或 1,概率取决于X_2YWX_1的函数。这是一个非常困难的问题;不仅X影响属于类别 1 的概率(即倾向),它还改变了WY的处理效果。事实上,简单计算表明 CATE 给定为:

(1 - 0.2)*X_1 - (0 - 0.2)*X_1 = X_1。

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

与数据生成过程相对应的图形

set.seed(2)

n<-4000
p<-2

X<-matrix(runif(n*p), ncol=2)
W<-rbinom(n,size=1, prob= exp(-X[,2])/(1+exp(-X[,2])))

Y<-(W-0.2)*X[,1] + rnorm(n)
Y<-matrix(Y,ncol=1) 

我们现在随机选择一个测试点x,并使用以下代码来估计证据函数及其置信区间:

 x<-matrix(runif(1*p), ncol=2)
Yall<-cbind(Y,W)
## For the current version of the Witdrf function, we need to give
## colnames to Yall
colnames(Yall) <- c("Y", "W")

## Fit the new DRF framework
drf_fit <- drfCI(X=X, Y=Yall, min.node.size = 5, splitting.rule='FourierMMD', num.features=10, B=100)

Witobj<-Witdrf(drf_fit, x=x, groupingvar="W", alpha=0.05)

hatmun<-function(y,Witobj){

  c<-Witobj$c
  k_Y<-Witobj$k_Y
  Y<-Witobj$Y
  weightsall1<-Witobj$weightsall1
  weightsall0<-Witobj$weightsall0
  Ky=t(kernelMatrix(k_Y, Y , y = y))

  #K1y <- t(kernelMatrix(k_Y, DRF$Y[DRF$Y[, groupingvar]==1,coln[coln!=groupingvar]], y = y))
  #K0y <- t(kernelMatrix(k_Y, DRF$Y[DRF$Y[, groupingvar]==0,coln[coln!=groupingvar]], y = y))
  out<-list()
  out$val <- tcrossprod(Ky, weightsall1  ) - tcrossprod(Ky, weightsall0  )
  out$upper<-  out$val+sqrt(c)
  out$lower<-  out$val-sqrt(c)

  return( out )

}

all<-hatmun(sort(Witobj$Y),Witobj)

plot(sort(Witobj$Y),all$val , type="l", col="darkblue", lwd=2, ylim=c(min(all$lower), max(all$upper)),
     xlab="y", ylab="witness function")
lines(sort(Witobj$Y),all$upper , type="l", col="darkgreen", lwd=2 )
lines(sort(Witobj$Y),all$lower , type="l", col="darkgreen", lwd=2 )
abline(h=0)

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

我们可以从这个图中看到:

(1) 组 1 的条件密度在y值介于-3 和 0.3 之间时低于组 0 的密度。此外,这种差异随着y的增大而增大,直到约y = -1,之后密度差异开始再次减少,直到两者在约 0.3 时相同。

(2) 对称地,组 1 的密度在y值介于 0.3 到 3 之间时高于组 0 的密度,这种差异逐渐增大,直到在约y = 1.5时达到最大值。在此点之后,差异逐渐减少,直到在y = 3时几乎回到零。

(3) 两个密度之间的差异在 95%置信水平上具有统计学意义,可以从事实中看到,对于y大致在-1.5 到-0.5 以及 1 到 2 之间,渐近置信区间不包括零线。

让我们检查(1)和(2)对模拟的真实条件密度的适用情况。也就是说,我们大量模拟真实情况:

# Simulate truth for a large number of samples ntest
ntest<-10000
Xtest<-matrix(runif(ntest*p), ncol=2)

Y1<-(1-0.2)*Xtest[,1] + rnorm(ntest)
Y0<-(0-0.2)*Xtest[,1] + rnorm(ntest)

## Plot the test data without adjustment
plotdf = data.frame(Y=c(Y1,Y0), W=c(rep(1,ntest),rep(0,ntest) ))
plotdf$weight=1
plotdf$plotweight[plotdf$W==0] = plotdf$weight[plotdf$W==0]/sum(plotdf$weight[plotdf$W==0])
plotdf$plotweight[plotdf$W==1] = plotdf$weight[plotdf$W==1]/sum(plotdf$weight[plotdf$W==1])

plotdf$W <- factor(plotdf$W)

#plot pooled data
ggplot(plotdf, aes(Y)) +
  geom_density(adjust=2.5, alpha = 0.3, show.legend=TRUE,  aes(fill=W, weight=plotweight)) +
  theme_light()+
  scale_fill_discrete(name = "Group", labels = c('0', "1"))+
  theme(legend.position = c(0.83, 0.66),
        legend.text=element_text(size=18),
        legend.title=element_text(size=20),
        legend.background = element_rect(fill=alpha('white', 0.5)),
        axis.text.x = element_text(size=14),
        axis.text.y = element_text(size=14),
        axis.title.x = element_text(size=19),
        axis.title.y = element_text(size=19))+
  labs(x='y') 

这导致:

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

视觉上比较有点困难,但我们看到这两个密度与上面的证据函数预测的结果非常接近。特别是,我们看到在 0.3 左右密度几乎相同,密度差异在大约-1 和 1.5 之间达到最大。因此,实际密度中可以看到点(1)和(2)!

此外,为了将(3)置于背景中,论文中的重复模拟展示了在没有可见效果时估计的证据函数的趋向:

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

在与此处描述的类似设置中模拟了 1000 个证据函数。蓝色的是 1000 个估计的证据函数,而灰色的显示了相应的置信区间。摘自我们在 arXiv 上的论文。此示例中没有效果,99%的置信区间不包含零线。

这篇文章中给出了因果推断中的一个真实数据示例。

结论

在这篇文章中,我讨论了适用于分布随机森林的新推断工具。我还查看了这些新能力的重要应用;用均匀置信区间估计条件见证函数。

不过,我也想提供一些警告:

  1. 结果仅对给定的测试点x有效。

  2. 结果仅在渐近意义上有效。

  3. 当前的代码比它应该有的慢得多。

实际上,第一点并不是那么糟糕,在模拟中,渐近正态性通常在一范围内的x上也成立。只要小心样本边界附近的测试点! 直观地说,DRF(以及所有其他最近邻方法)需要测试点x周围的许多样本点来估计x的响应。因此,如果你训练集中的协变量X是标准正态分布,大多数点在-2 和 2 之间,那么预测[-1,1]中的x应该没有问题。但如果你的x达到了-2 或 2,性能会迅速恶化。

随机森林(以及一般的最近邻方法)在预测训练集中只有少量邻居的点时表现不佳,例如X的支持边界上的点。

第二点也相当重要。渐近结果在当代研究中已经有些过时,研究者们更倾向于有限样本结果,这些结果需要诸如“次高斯性”的假设。我个人觉得这有点可笑,渐近结果在像这样的复杂设置中提供了极其强大的近似。实际上,对于许多目标来说,这种近似在超过 1000 或 2000 个数据点时相当准确(也许你的条件均值/分位数的覆盖率是 92%而不是 95%)。然而,我们引入的见证函数是一个复杂的对象,因此你需要更多的数据点来估计其不确定性区间,这样效果会更好!

最后第三点只是我们的一个缺陷:虽然 DRF 本身是用 C 语言高效编写的,但用S估计不确定性目前完全基于 R 语言。修复这一点将极大地加快代码速度。我们希望将来能够解决这个问题。

引用

[1] Junhyung Park, Uri Shalit, Bernhard Schölkopf 和 Krikamol Muandet. “使用核条件均值嵌入和 U 统计量回归的条件分布治疗效果。” 见《第 38 届国际机器学习大会(ICML)论文集》,第 139 卷,第 8401–8412 页。PMLR,2021 年 7 月。

Kubernetes 的无限可扩展存储

原文:towardsdatascience.com/infinitely-scalable-storage-for-kubernetes-9a20393e37e4?source=collection_archive---------3-----------------------#2023-05-07

一项破坏性实验,确保我们的数据能够恢复

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

·

跟进 发表在Towards Data Science ·6 分钟阅读·2023 年 5 月 7 日

有时候,你只需要一个可靠的存储解决方案。享有云服务提供商存储类的奢侈并非总是可能的,有时你必须自己全权管理。这是我在医疗保健领域的客户的挑战。

在本文中,您将了解为什么以及如何安装 Rook Ceph,为您的 Kubernetes 集群提供易于使用的复制存储类。

然后我们将部署一个文件共享应用程序,摧毁部署它的节点,然后看看会发生什么。Ceph 是否会让我们的文件再次可访问?

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

看向地平线的容器。Kelly 摄于 Pexels

选择存储解决方案

存储在 Kubernetes 中一直是一个挑战,因为它没有原生提供冗余和分布式存储解决方案。在原生 Kubernetes 中,你只能附加一个 hostPath 卷以实现持久存储。

我的客户拥有自己的本地基础设施,并希望确保如果其中一台服务器出现故障,其数据不会丢失。大多数应用程序是单体应用,并且没有原生的数据复制机制。

所以我必须从各种存储解决方案中进行选择。我的客户不需要超高性能,但希望有一个稳定的解决方案。我选择了 Rook Ceph,原因如下:

准备你的集群

我们需要一个最少包含 3 个节点的 Kubernetes 集群,每个节点有一个空的附加磁盘。

我推荐使用Scaleway Kapsule来轻松实例化 Kubernetes 集群并分配未格式化的磁盘。一旦 Kubernetes 集群启动,我们将为每个节点创建一个附加卷(磁盘):

  • 进入“实例”

  • 选择你的节点

  • 点击“附加卷”标签

  • 点击“+”(创建卷),并创建一个新磁盘

下载你的kubeconf文件,并将其放置在~/.kube/config中。你现在应该可以使用你的kubectl CLI 访问集群。

安装 Rook Ceph

1. 本博客文章有一个GitHub 上的配套仓库,让我们克隆它以获取我们需要的所有资源

git clone https://github.com/flavienbwk/ceph-kubernetes
cd ceph-kubernetes

2. 克隆 Rook 仓库并部署 Rook Ceph 操作员

git clone --single-branch --branch release-1.11 https://github.com/rook/rook.git
kubectl create -f ./rook/deploy/examples/crds.yaml
kubectl create -f ./rook/deploy/examples/common.yaml
kubectl create -f ./rook/deploy/examples/operator.yaml

3. 创建 Ceph 集群

kubectl create -f ./rook/deploy/examples/cluster.yaml -n rook-ceph

等待几分钟,直到 Ceph 配置完磁盘。健康状态应为 HEALTH_OK

kubectl get cephcluster -n rook-ceph

4. 创建存储类

Rook Ceph 可以为你提供两个主要的存储类。一个是 RBD,允许你在 ReadWriteOnce 模式下拥有复制存储。第二个是我们将要安装的 CephFS,允许你在 ReadWriteMany 模式下拥有复制存储。RBD 代表 RADOS Block Device,允许你在 Kubernetes 集群中配置卷。它仅支持 ReadWriteOnce 卷(RWO)。CephFS 像一个复制的 NFS 服务器。这将使我们能够在 ReadWriteMany 模式下创建卷(RWX)。

kubectl create -f ./rook/deploy/examples/csi/rbd/storageclass.yaml -n rook-ceph
kubectl create -f ./rook/deploy/examples/filesystem.yaml -n rook-ceph
kubectl create -f ./rook/deploy/examples/csi/cephfs/storageclass.yaml -n rook-ceph

5. 部署 Ceph 控制面板

kubectl create -f ./rook/deploy/examples/dashboard-external-https.yaml -n rook-ceph

转发控制面板的 HTTP 访问:

kubectl port-forward service/rook-ceph-mgr-dashboard -n rook-ceph 8443:8443

使用用户名 admin 和以下密码进行连接:

kubectl -n rook-ceph get secret rook-ceph-dashboard-password -o jsonpath="{['data']['password']}"

你应该访问以下页面:https://localhost:8443

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

作者提供的图像:Ceph 控制面板

部署应用

我们将部署一个自托管的文件共享应用(psitransfer)来检查我们的卷是否正确绑定。

1. 部署文件共享应用(NodePort 30080)

kubectl create -f ./psitransfer-deployment-rwx.yaml

2. 查看它被部署在哪个节点上

kubectl get pods -o wide -l app=psitransfer

获取该节点的 IP(通过 Scaleway 界面)并检查应用是否在 http://nodeip:30080 上运行。

3. 上传一些文件

xcal1.vodafone.co.uk 网站 下载 5MB10MB20MB 文件。

将文件上传到我们的文件传输应用中。点击屏幕上出现的链接。

现在您应该能看到导入的树形文件。点击它并保持浏览器标签中的链接,我们稍后会用到它。

上传了大约 400MB 的文件后,我们可以看到数据在磁盘之间的复制是一致的。我们看到 3 个磁盘在文件上传时被同时写入。在以下截图中,每个磁盘的使用率为 1%:尽管我在同一主机上上传文件,但似乎复制按预期工作,数据在 3 个磁盘(OSD)之间均匀持久化。磁盘 2 有大量的“读取”活动,因为另外两个磁盘从它那里同步数据。

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

Ceph 的仪表盘现在应该是这样的:

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

C. 销毁并查看

我们将停止托管 Web 应用的节点,以确保数据已复制到其他节点。

  1. 查看应用部署在哪个节点上
kubectl get pods -o wide -l app=psitransfer

2. 从 Scaleway 控制台关闭节点

这模拟了节点上的电力故障。它应该在几分钟后变为 NotReady

$> kubectl get node
NAME                                             STATUS     ROLES    AGE    VERSION
scw-ceph-test-clustr-default-5f02f221c3814b47a   Ready      <none>   3d1h   v1.26.2
scw-ceph-test-clustr-default-8929ba466e404a00a   Ready      <none>   3d1h   v1.26.2
scw-ceph-test-clustr-default-94ef39ea5b1f4b3e8   NotReady   <none>   3d1h   v1.26.2

而且 Node 3 在我们的 Ceph 仪表盘上不可用:

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

Ceph 的仪表盘现在应该是这样:

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

3. 重新调度我们的 Pod

调度的 Pod 节点不可用。然而,我们的 Pod 仍然认为它是活动的:

$> kubectl get pods -o wide -l app=psitransfer
NAME                                      READY   STATUS    RESTARTS   AGE   IP            NODE
psitransfer-deployment-8448887c9d-mt6wm   1/1     Running   0          19h   100.64.1.19   scw-ceph-test-clustr-default-94ef39ea5b1f4b3e8

删除它以便在另一个节点上重新调度:

kubectl delete pod psitransfer-deployment-8448887c9d-mt6wm

检查刚刚重新启动的 Pod 的状态。您的应用程序应该可以通过之前保留的链接再次访问。

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

为了避免在节点变为“NotReady”时需要手动删除 Pod 以便重新调度,建议将应用的副本数默认扩展到至少 3。

现在您可以重新启动之前关闭的节点。

什么时候使用 rook-ceph-blockrook-cephfs

如果您的应用程序需要更好的性能并且需要 RWO 访问模式的块存储,请使用 rook-ceph-block (RBD) 存储类。另一方面,如果您的应用程序需要具有 RWX (CephFS) 访问模式和 POSIX 兼容的共享文件系统,请使用 rook-cephfs 存储类。

如果选择 RBD 并尝试在原节点离线时重新调度一个 pod,就像我们在 CephFS 中做的那样,你会收到来自 PVC 的错误,内容为:"卷已独占地附加到一个节点,无法附加到另一个节点”。在这种情况下,你只需等待 PVC 重新绑定(我的集群自动重新分配 PVC 给我的 pod,花了大约 6 分钟,使其可以启动)。

尝试这种行为 按照相关仓库章节

最后一句

你已经学会了如何使用 Ceph 安装和部署应用程序。你甚至证明了它能够复制数据。恭喜你 ✨

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值