探索 GEMBA:一种基于 LLM 的翻译质量评估新指标
#GEN-AI 研究论文
使用 LLM 评估翻译质量
·
关注 发表在 Towards Data Science · 9 分钟阅读 · 2023 年 9 月 29 日
–
图像由作者使用 DALL.E 2 生成
介绍
我最近读到了一篇有趣的微软论文¹(发表于 2023 年 5 月),引起了我的注意。论文深入探讨了翻译评估领域,揭示了一种名为 GEMBA(GPT Estimation Metric Based Assessment)的创新度量标准。在这篇博客文章中,我们将剖析论文并提供对这一令人兴奋的发展的一些见解。
-
前提:探索论文背后的动机。
-
研究问题和假设:探讨论文的主要研究问题和假设。
-
翻译质量评估度量标准:浅入现有度量标准,包括 BLEU、COMET 和 METEOR。
-
介绍 GEMBA:深入了解新颖的 GEMBA 度量标准。
-
实验细节:对所进行实验的洞见。
-
主要发现:突出论文的主要结果。
-
限制:讨论在生产环境中实施 GEMBA 前需要注意的事项。
1. 前提
尽管 LLMs 最初并未设计用于翻译任务,但它们在这一领域展现了令人印象深刻的精准度。这一认识促使作者探索使用 LLMs 作为翻译评估工具。论文的核心思想相当简单——将 LLMs(大型语言模型)定位为评估翻译的工具,而不仅仅是执行翻译。作者提出了一种新的度量标准叫做 GEMBA,它在翻译质量评估方面超越了现有的最先进度量标准。
2. 研究问题
LLMs 能否用于翻译质量评估?
3. 翻译质量评估度量标准
在深入了解 GEMBA 之前,让我们快速回顾一下用于评估机器生成翻译质量的现有度量标准,如 BLEU、COMET、METEOR 等。这些度量标准各有其优点,适用于不同的使用案例,取决于翻译质量中最重要的特定方面。
例如,BLEU(双语评估替代指标)主要关注 n-gram 精确度,这意味着它测量机器翻译中的 n-词序列与一个或多个参考翻译中的 n-词序列的重叠情况。它奖励特定词序列的存在,而没有明确考虑词序、词干提取或同义词关系。另一方面,METEOR(显式排序翻译评估指标)超越了基本的 n-gram 匹配,采取了更全面的翻译评估方法。它考虑了翻译质量的多个方面,包括词干提取、同义词关系、词序、精确的词匹配,甚至对未翻译词的惩罚。同样,COMET(基于内容的机器翻译评估)使用了一种略微不同的方法,专注于基于内容的评估,并通过嵌入计算机器翻译输出与参考翻译之间的语义相似度。简而言之,它评估机器翻译的内容和意义与参考翻译的匹配程度,而不考虑具体的语言变异或词汇选择。
你可以在这里了解其他评估指标,如 YiSi、chrF、BERTScore 等。
鉴于我们刚才讨论的众多指标,你可能会问——为什么我们需要像 GEMBA 这样的新指标? 答案在于其独特的方法——促使大型语言模型根据自身判断评估翻译。与传统指标不同,GEMBA 旨在通过对翻译进行评分(例如 0 到 100),关注意义和语法,与人类评估翻译对齐。
4. 介绍 GEMBA
如前所述,GEMBA 是一种基于 GPT 的翻译质量评估指标,它可以在有参考翻译和没有参考翻译的情况下工作。
本质上,GEMBA 是一个经过精心设计的评估任务 prompt,包含:
-
prompt 变体(来自预定义的四种变体集合)
-
源语言名称,例如“中文”
-
目标语言名称,例如“英语”
-
源片段,即需要翻译的句子
-
候选翻译,即翻译的句子
-
[可选] 参考翻译,即可以作为基准翻译的翻译
这是其中一种 prompt 变体的示例:GEMBA-DA(直接评估)
GEMBA-DA prompt。图像来源于原始论文
附注:如果你对其他三种变体感兴趣,以下是论文中介绍的所有 prompt 变体的详细信息:
GEMBA prompt 变体。图像来源于原始论文
5. 实验和评估
作者使用广泛流行的 MQM 2022 数据集(多维质量指标)测试了 GEMBA 指标的效率。该数据集包括来自新闻、社交、电商等各种领域的多样化句子(100K+),涵盖三个翻译方向:英语到俄语、英语到德语和中文到英语。
此外,为了找到实施 GEMBA 的最佳 GPT 模型,作者测试了 GPT 系列中从 GPT 2 到最新 GPT-4 模型的七个模型的每个提示变体。
用于评估 GEMBA 的 7 个 GPT 模型。图像来自原始论文
在为 GEMBA 指标实验设定了舞台后,接下来的明显问题是 —
问:我们如何判断 GEMBA 是否比传统指标如 BLEU 和 COMET 表现更好?
A: 如果 GEMBA 得分与人类对翻译的看法紧密相关,那么我们就找到了赢家!
为了实现这一答案,需要根据我们是进行 段级 评估还是 系统级 评估来计算两个指标(Kendall’s Tau 和准确度 (accuracy, Kocmi et al., 2021))。但首先,它们是什么?
系统级评估 评估机器翻译系统的 整体性能。它查看系统生成的翻译在广泛文本或内容中的质量。
段级评估 侧重于评估逐段翻译的质量(通常是 句子 或更小的文本单元)
一般而言:
-
Kendall’s Tau 用于段级评估
-
准确度用于系统级评估
为了清晰起见,我们来深入了解它们的公式,使用简单的示例:
A. Kendall’s Tau
(Kendall’s Tau 用于判断两个排名之间是否存在相关性)
假设你有一个给定句子的三种翻译(A、B 和 C),你想评估指标(如 LLM、BLEU、METEOR 分数)产生的排名与人类翻译质量判断之间的相关性。
参考(人类): “快速的棕色狐狸跳过了懒狗。”
翻译 A: “迅速的棕色狐狸跳过了懒狗。”
翻译 B: “快速的红色狐狸跳过了懒狗。”
翻译 C: “懒狗被快速的棕色狐狸跳过。”
人类排序: A > B > C(即,他们最喜欢翻译 A,然后是 B,最后是 C)
这些翻译的指标分数: LLM(A) = 0.85
LLM(B) = 0.75
LLM© = 0.60
综合所有信息,我们可以按如下方式计算 Kendall’s Tau:
接下来,让我们计算一致对和不一致对:
对 1: (A, B) 人工排序:A > B
LLM 分数:LLM(A) = 0.85 > LLM(B) = 0.75
结果: 一致对(人类和指标都更喜欢 A 而不是 B)。
对 2: (A, C) 人工排序:A > C
LLM 分数:LLM(A) = 0.85 > LLM© = 0.60
结果:一致对(人工和度量都更喜欢 A 而不是 C)。
对 3: (B, C) 人工排名:B > C
LLM 分数:LLM(B) = 0.75 > LLM© = 0.60
结果:一致对(人工和度量都更喜欢 B 而不是 C)。
将这些值代入公式中,我们得到:
Kendall’s Tau
换句话说,τ = 1 表示度量和人工判断之间完全一致,因此这是一个可以用于自动化翻译质量的高质量度量。
B. 准确率
Kendall’s Tau 评估排名之间的相似性或一致性,而准确率衡量排名的正确性。
为了说明准确率的计算,我们采用与上面相同的设置,即参考(人工)、翻译 A、翻译 B、翻译 C、人工排名,但让我们稍微更新度量分数,以便根据 Bleu 将 B 标记为比 C 更好的翻译:
度量分数(BLEU):
BLEU(A) = 0.80
BLEU(B) = 0.70
BLEU© = 0.75
结合所有信息,这里是如何计算准确率的方法:
让我们计算度量Δ(这只是成对翻译的度量值差异)和人工Δ(这是假设的翻译对的人工评分差异)。如果你仔细查看公式,你会注意到我们并不关心Δ的实际值,而是Δ的符号。简单来说,只有当两个Δ的符号相同,即人工和度量对翻译的看法一致时,才能实现高准确率。
对 1: (A, B) 度量Δ = BLEU(A) — BLEU(B) = 0.80–0.70 = 0.10
人工Δ = 1(A 的排名高于 B)
结果:度量Δ和人工Δ具有相同的符号(都是正的)。这是一个排名一致性。
对 2: (A, C) 度量Δ = BLEU(A) — BLEU© = 0.80–0.75 = 0.05
人工Δ = 2(A 的排名高于 C)
结果:度量Δ和人工Δ具有相同的符号(都是正的)。这是一个排名一致性。
对 3: (B, C) 度量Δ = BLEU(B) — BLEU© = 0.70–0.75 = -0.05
人工Δ = 1(B 的排名高于 C)
结果:度量Δ和人工Δ具有不同的符号(度量为负,人工为正)。这是一个排名不一致。
将这些值代入公式中,我们得到:
准确率(Bleu)= (2/3)*100 = 67%,这意味着 BLEU 度量准确地将 3 对翻译中的 2 对按照人工判断进行了排名。是否这个百分比足够好以自动化 Bleu 的评估,我留给读者自行判断!
注意:用于演示 Kendall’s Tau 和准确率计算的示例已简化以进行演示。在实际情况下,如果需要处理平局,即如果人工/度量对两个或更多翻译给出相同的排名,则公式会变得更复杂。你可以阅读更多有关内容 这里。
关键结果:系统与片段级别评估
论文报告称 GEMBA 在系统级评估中表现优异,超越了现有的评估指标。
系统级评估结果。图片取自原始论文
然而,在分段级评估中还有改进的空间。LLM 与人类在这一层级上的排名平局可能解释了这一差异,因为 Kendall’s Tau 会惩罚平局。由于 Gemba-DA 指标返回 0-100 之间的离散值,两种翻译得到相等分数的概率很高。
分段级评估结果(P.S. 第一列的准确率与前面的表格相同)。图片取自原始论文
结果还强调了选择合适 LLM 以实施 GEMBA 的重要性。在测试的 GPT 家族的七种模型中,任何超过 3.5 的模型都表现出色。GPT-4 表现尤为突出,但 Davinci、ChatGPT Turbo 和 GPT-3.5 也表现良好。
各种 GPT 家族模型的 GEMBA 实施。图片取自原始论文
7. 限制和考虑事项
论文突出了 GEMBA 在更广泛应用中的某些限制。
-
由于论文仅考虑了英语、中文、俄语和德语,因此需要对低资源语言进行 GEMBA 的评估。
-
可能存在数据泄漏的风险,因为尚不清楚测试数据是否包含在 Open AI 的训练中(撰写时 Open AI 尚未发布秘密配方)。尽管如此,可能性非常低,因为 GPT 模型声称知识截止日期为 2021 年 9 月,而 MQM 数据集于 2022 年 12 月发布。
-
LLM 可能会偶尔出现无效回应:
►文本回答而非分数→通过提高
temperature
直到输出数字分数来处理。►“2”,“two”,“**”,“★★”,“two stars”或“2 stars”→在后处理中处理以保持一致性。
►作者排除了非英语目标语言(如星或五)的 LLM 输出。
无效回应的数量。图片取自原始论文。
结论
牢记使用简单提示实现 GEMBA 的便利性,它无疑是翻译质量评估的突破性指标。它与人类判断的一致性以及对各种 LLM 模型的适应性,使其成为 NLP 和翻译评估领域的有力补充。随着我们继续探索和完善 GEMBA(也许通过少量提示),它作为确保在多种语言环境中高质量翻译的有价值工具,具有很大的潜力。
[1] Kocmi, T., & Federmann, C. (2023). 大型语言模型是翻译质量的最先进评估工具。arXiv 预印本 arXiv:2302.14520。
探索大规模栅格人口数据
原文:
towardsdatascience.com/exploring-large-scale-raster-population-data-72803cf7f2ad
图片由作者提供。
使用 Python 可视化不同尺度的地理空间人口数据:全球、国家和城市级数据
·发布于 Towards Data Science ·阅读时长 9 分钟·2023 年 9 月 21 日
–
我常常看到漂亮的人口地图在网上传播;然而,我通常会在一些技术部分遇到困难,比如可视化教程中未显示的其他地图片段,或将大规模的栅格数据转换为更易计算的矢量格式。本文通过对两个主要全球人口数据来源的实用指南,克服了其中的一些不足。
还需要注意的是,除了它们的美学价值外,人口数据和显示这些数据的地图是任何城市发展或地点情报任务中最基本和最有价值的信息之一。它们在一些具体应用中特别有用,例如规划新设施、选址和流域分析、估算城市产品的规模,或对不同社区进行画像,仅举几例。
1. 数据来源
我依赖以下两个精细化的人口估计数据来源,您可以通过附上的链接下载文件(发布时的日期):
-
欧洲委员会的 GHSL — 全球人类居住层 测量每个网格单元的居民数量。可以在这里找到总体描述,特定的数据集来自他们的 2023 报告,空间分辨率为 100m。
-
我将以德国为例,使用 WorldPop hub 提供的约束性单国数据集,分辨率为 100m。可以在此处找到数据清单,而德国的数据在此处。
2. 可视化全球人类居住层
2.1. 导入数据!
我第一次在 Datashader 教程中遇到这个数据集,该教程来自Architecture Performance。在复制他们的可视化后,我在将其扩展到全球地图时遇到了一些障碍,这引发了这项工作,所以现在我将向你展示我找到的解决方法!
首先,使用 xarray 包解析光栅文件。
import rioxarray
file_path = "GHS_POP_E2030_GLOBE_R2023A_54009_100_V1_0/GHS_POP_E2030_GLOBE_R2023A_54009_100_V1_0.tif"
data_array = rioxarray.open_rasterio(file_path, chunks=True, lock=False)
data_array
此单元的输出是数据集的详细描述:
2.2. 可视化数据的各个块
我们已经可以看到,对于大多数标准笔记本电脑来说,这是一大挑战。无论如何,让我们尝试使用 Datashader 可视化它,这是一种非常方便的工具,用于处理这种规模的地理空间数据集:
# WARNING: THIS CODE BLOCK WILL MOST LIKELY CAUSE A MEMORY OVERFLOW ERROR
import datashader as ds
import xarray as xr
from colorcet import palette
from datashader import transfer_functions as tf
# prepare to plot
data_array_p = xr.DataArray(data_array)[0]
data_array_p = data_array_p.where(data_array_p > 0)
data_array_p = data_array_p.compute()
# get the image size
size = 1200
asp = data_array_p.shape[0] / data_array_p.shape[1]
# create the data shader canvas
cvs = ds.Canvas(plot_width=size, plot_height=int(asp*size))
raster = cvs.raster(data_array_p)
# draw the image
cmap = palette["fire"]
img = tf.shade(raster, how="eq_hist", cmap=cmap)
img
尽管这段代码从技术上看是可行的,但我的 2021 年 M1 Macbook Pro(16GB RAM)却出现了可怜的内存溢出错误。因此,让我们裁剪图像来查看数据!为此,我遵循 Architecture Performance,重点关注欧洲,暂时这样做,因为它肯定有效。
然而,我稍后将回答的主要问题是,尽管存在这样的内存限制,但我们如何仍然能够在本地机器上可视化整个地球的数据?请稍等!
import datashader as ds
import xarray as xr
from colorcet import palette
from datashader import transfer_functions as tf
import numpy as np
# crop the data array
data_array_c = data_array.rio.clip_box(minx=-1_000_000.0, miny=4_250_000.0, maxx=2_500_000.0, maxy=7_750_000.0)
data_array_c = xr.DataArray(data_array_c)
# prepare to plot
data_array_c = xr.DataArray(data_array_c)[0]
data_array_c = data_array_c.where(data_array_c > 0)
data_array_c = data_array_c.compute()
data_array_c = np.flip(data_array_c, 0)
# get the image size
size = 1200
asp = data_array_c.shape[0] / data_array_c.shape[1]
# create the data shader canvas
cvs = ds.Canvas(plot_width=size, plot_height=int(asp*size))
raster = cvs.raster(data_array_c)
# draw the image
cmap = palette["fire"]
img = tf.shade(raster, how="eq_hist", cmap=cmap)
img = tf.set_background(img, "black")
img
这段代码块输出如下可视化:
欧洲的人口分布。图片来自作者。
使用‘fire’颜色映射方案似乎是一个行业标准,这是有原因的;然而,如果你想尝试其他颜色方案,你可以在这里找到并应用于下方:
# create the data shader canvas
cvs = ds.Canvas(plot_width=size, plot_height=int(asp*size))
raster = cvs.raster(data_array_c)
# draw the image
cmap = palette["bmw"]
img = tf.shade(raster, how="eq_hist", cmap=cmap)
img = tf.set_background(img, "black")
img
这段代码块输出如下可视化:
欧洲的人口分布。图片来自作者。
2.3. 可视化整个地球
所以数据在这里,但如果你手头有一台普通计算机,仍然想以 100 米的分辨率可视化整个世界怎么办?我在这里展示的解决方法相对简单 — 我将整个光栅图像拆分成大约一百个较小的瓦片,这样我的计算机可以逐个处理它们,然后使用一些图像处理技巧将它们合并成一个图像文件。
然而,在继续之前 — 快速说明。也有一种选项可以以下方式下采样 XArray 数组 — 但我找不到一个合适的下采样方法可以处理整个数据集。此外,我不想失去精度,还想看到整个数据集的全貌。
# a quick way to down-sample the data
downsampling_factor = 20
downsampled_data_array = data_array.coarsen(x=downsampling_factor, y=downsampling_factor).mean()
downsampled_data_array
输出结果 — 值得与之前绘制的数据数组进行对比:
要将整个光栅图像拆分成网格块,首先,获取其边界并定义 N 为步长。然后,创建图像块边界的列表。
minx = float(data_array.x.min().values)
maxx = float(data_array.x.max().values)
miny = float(data_array.y.min().values)
maxy = float(data_array.y.max().values)
N = 10
xstep = (maxx-minx) / N
ystep = (maxy-miny) / N
xsteps = list(np.arange(minx, maxx, xstep))
ysteps = list(np.arange(miny, maxy, ystep))
现在,迭代每一个 x 和 y 步骤,并创建每个图像段,其中每个图像文件以其在原始网格中的位置命名。这个循环可能需要一些时间。
import os
foldout = 'world_map_image_segments'
if not os.path.exists(foldout):
os.makedirs(foldout)
for idx_x, x_coord in enumerate(xsteps):
for idx_y, y_coord in enumerate(ysteps):
if not os.path.exists(foldout+'/'+str(idx_x)+'_'+str(idx_y)+'.png'):
data_array_c = data_array.rio.clip_box( minx=x_coord, miny=y_coord, maxx=x_coord+xstep, maxy=y_coord+ystep)
data_array_c = xr.DataArray(data_array_c)[0]
data_array_c = data_array_c.fillna(0)
data_array_c = data_array_c.where(data_array_c > 0)
data_array_c = data_array_c.compute()
data_array_c = np.flip(data_array_c, 0)
size = 2000
asp = data_array_c.shape[0] / data_array_c.shape[1]
cvs = ds.Canvas(plot_width=size, plot_height=int(asp*size))
raster = cvs.raster(data_array_c)
cmap = palette["fire"]
img = tf.shade(raster, how="eq_hist", cmap=cmap)
img = tf.set_background(img, "black")
pil_image = img.to_pil()
pil_image.save(foldout+'/'+str(idx_x)+'_'+str(idx_y)+ '.png')
print('SAVED: ', x_coord, y_coord, y_coord+xstep,y_coord+ystep)
最后,如果我们拥有所有的图像段,我们可以使用以下函数快速组装它们。对于这段代码,我也向 ChatGPT 请求了一些提示来加快进度,但像往常一样,也需要一些手动调整。
from PIL import Image
def find_dimensions(image_dir):
max_x = 0
max_y = 0
for filename in os.listdir(image_dir):
if filename.endswith(".png"):
x, y = map(int, os.path.splitext(filename)[0].split("_"))
max_x = max(max_x, x)
max_y = max(max_y, y)
return max_x + 1, max_y + 1
image_dir = foldout
segment_width = size
segment_height = int(asp*size)
# Determine the dimensions of the large image
large_image_width, large_image_height = find_dimensions(image_dir)
# Create an empty large image (white background)
large_image = Image.new("RGB", (large_image_width * segment_width, large_image_height * segment_height), "black")
# Loop through the individual image segments and paste them into the large image
for filename in sorted(os.listdir(image_dir)):
if filename.endswith(".png"):
x, y = map(int, os.path.splitext(filename)[0].split("_"))
segment_image = Image.open(os.path.join(image_dir, filename))
x_offset = x * segment_width
y_offset = large_image_height * segment_height-1*y * segment_height
large_image.paste(segment_image, (x_offset, y_offset))
# Save the merged large image
large_image.save("global_population_map.png")
最终结果是,这里是整个地球的映射:
全球人口分布。图像来源:作者。
3. 视觉化和转换 WorldPop 数据
我想向你展示的第二个数据源是 WorldPop 人口数据库,它提供了不同分辨率的大陆和国家数据。在这个示例中,除了前一部分的大陆和全球级别外,这里我瞄准了国家和城市的级别。例如,我选择了德国,并使用了 2020 年整理的 100m 分辨率数据,并展示了如何从整个国家中提取一个城市,并使用 GeoPandas 将其转换为易于使用的矢量格式。
3.1. 视觉化 WorldPop 数据
使用之前的方法,我们可以再次快速可视化这个栅格文件:
# parse the data
data_file = 'deu_ppp_2020_constrained.tif'
data_array = rioxarray.open_rasterio(data_file, chunks=True, lock=False)
# prepare the data
data_array = xr.DataArray(data_array)[0]
data_array = data_array.where(data_array > 0)
data_array = data_array.compute()
data_array = np.flip(data_array, 0)
# get the image size
size = 1200
asp = data_array.shape[0] / data_array.shape[1]
# create the data shader canvas
cvs = ds.Canvas(plot_width=size, plot_height=int(asp*size))
raster = cvs.raster(data_array)
# draw the image
cmap = palette["fire"]
img = tf.shade(raster, how="eq_hist", cmap=cmap)
img = tf.set_background(img, "black")
img
这个代码块输出以下视觉效果:
德国的人口分布。图像来源:作者。
3.2. 转换 WorldPop 数据
在可视化了整个地球、欧洲大陆和德国之后,我希望更深入地了解柏林,并向你展示如何将这样的栅格数据转换为矢量格式,并使用 GeoPandas 轻松处理它。为此,我访问了柏林的行政边界的 geojson 格式数据在这里。
这个行政文件包含柏林的区,因此首先,我将它们合并成整个城市。
from shapely.ops import cascaded_union
import geopandas as gpd
admin = gpd.read_file('tufts-berlin-bezirke-boroughs01-geojson.json')
admin = gpd.GeoDataFrame(cascaded_union(admin.geometry.to_list()), columns = ['geometry']).head(1)
admin.plot()
这个代码块输出以下视觉效果:
柏林的行政边界。图像来源:作者。
现在将 xarray 转换为 Pandas DataFrame,提取几何信息,并构建一个 GeoPandas GeoDataFrame。可以这样做:
import pandas as pd
df_berlin = pd.DataFrame(data_array.to_series(), columns = ['population']).dropna()
现在,从这个数据构建一个 GeoDataFrame,重点关注柏林:
from shapely.geometry import Point
# find the limiting bounding box for easier coodinate-selection
minx, miny, maxx, maxy = admin.bounds.T[0].to_list()
points = []
population = df_berlin.population.to_list()
indicies = list(df_berlin.index)
# create Point geometries from the points falling into this bounding box
geodata = []
for ijk, (lon, lat) in enumerate(indicies):
if minx <= lat <= maxx and miny <= lon <= maxy:
geodata.append({'geometry' : Point(lat, lon), 'population' : population[ijk]})
# build a GeoDataFrame
gdf_berlin = gpd.GeoDataFrame(geodata)
gdf_berlin = gpd.overlay(gdf_berlin, admin)
然后,将人口数据作为矢量数据进行可视化:
import matplotlib.pyplot as plt
f, ax = plt.subplots(1,1,figsize=(15,15))
admin.plot(ax=ax, color = 'k', edgecolor = 'orange', linewidth = 3)
gdf_berlin.plot(column = 'population',
cmap = 'inferno',
ax=ax,
alpha = 0.9,
markersize = 0.25)
ax.axis('off')
f.patch.set_facecolor('black')
这个代码块输出以下视觉效果:
柏林的人口分布。图像来源:作者。
最后,这里我们有一个标准的 GeoDataFrame,分辨率为 100m,每个点几何体对应于栅格文件中的每个像素。
总结
在本文中,我探索了基于各种数据集的全球人口数据,这些数据通过结合各种近似、测量和建模方法,以 100 米的显著空间分辨率使用栅格网格来估计人口水平。这类信息对于城市发展和位置智能的广泛应用具有高度价值,例如基础设施规划、场地选择、社区分析等。从技术角度来看,我展示了三个空间层级的示例,从覆盖整个地球,再到放大到国家,最后到城市。虽然这种方法可以处理更小的分辨率,但这一切都发生在一台使用强大 Python 库如 Xarray、DataShader 和 GeoPandas 的笔记本电脑上。
对于数据科学家来说,总有新的 Python 技能值得学习
·
关注 发表于 Towards Data Science · 发送至 新闻简报 · 阅读时间 3 分钟 · 2023 年 6 月 8 日
–
新的数据科学工具和最先进的模型每天都在引起关注,但尽管 Python 存在诸多被频繁提及的不足(速度,如何?),它仍然是全球数据和机器学习从业者的主要统一者之一。无论何时滚动查看 TDS 首页,你都会发现难以质疑这种编程语言的普遍性和多功能性。
为了帮助你发现可能还不熟悉的库、用例和优化技术,我们汇集了一些我们认为执行得特别好的近期 Python 相关教程。它们涵盖了很多内容,因此无论你的角色或背景如何,你都可能找到一个新的主题或工作流进行探索。祝学习愉快!
-
作为 pandas 核心团队的成员,Patrick Hoefler 经常听到有关这个流行库的痛点;他的新教程带我们深入了解如何使用 PyArrow **(pandas 2.0 支持)**来解决处理非标准和任意数据类型的问题。(额外的好处是,它还包含了对 Dask 用户有用的提示。)
-
如果你想扩展你的地理空间数据工具包,不要错过 Parvathy Krishnan(以及合著者 Mahdi Fayazbakhsh 和 Kai Kaiser)的最新文章,该文章重点介绍了数字高程模型(DEMs),并展示了 如何充分利用 Python 的高程包来探索和可视化这些模型,以及如何计算诸如坡度和高程等导数。
-
Python 为我们提供了许多理解世界的其他方式——或者说是让我们看得更清楚。举个例子:Conor O’Sullivan 的新指南介绍了如何处理卫星图像以去除那些有时(也就是说:经常)阻挡视线的讨厌云层。
-
随着全球医疗系统仍然承受着相当大的压力,优化人员和资源的配置变得尤为重要。Meagan Voulo 提出了一个 利用 Python 分析健康的社会决定因素(SDOH) 的潜在方法,用于预测急诊室的高峰使用情况。
-
如果你想提高代码性能(谁不想呢?),Peng Qian提供了针对 asyncio 用户的简明教程:该教程介绍了使用 asyncio.gather、asyncio.as_completed 和 asyncio.wait APIs 处理并发任务的最佳实践。
-
我们承诺不会掀起任何波澜,但…有时候 Python 可能不是你项目的最佳选择,这没关系!知道何时不使用 Python 是一项关键技能。Stephanie Lo的全面指南将帮助忠实的 Python 爱好者在需要时过渡到 R,并详细说明你应该注意的差异。
编程很棒,但我们希望你本周阅读的其他这些帖子同样精彩:
-
我们最新的月刊已经发布,如果它有一个目标,那就是激励你设计出色的数据科学和机器学习项目。
-
分子生物学是利用人工智能取得最大进展的领域之一,Serafim Batzoglou的精彩概述涵盖了这种跨学科交叉作用的过去、现在和未来。
-
反应型数据团队和主动型数据团队之间的区别可能比你想象的更微妙,但正如Barr Moses解释的那样,哪一种对业务的价值更大几乎没有疑问。
-
我们现在正处于 ChatGPT 插件季节,新的插件迅速出现。要了解插件的好处和风险,不要错过Mary Newhauser的新深度剖析。
-
“正如人们会遗忘,机器学习模型也会遗忘——尤其是大型语言模型。” 在一次强有力的 TDS 首演中,Matt Tengtrakool反思了遗忘的作用——准确地说是灾难性遗忘—在机器学习的背景下。
感谢你们对我们作者的支持!如果你喜欢在 TDS 上阅读的文章,可以考虑成为 Medium 会员——这将解锁我们整个档案库(以及 Medium 上的所有其他文章)。
忠实的 TDS 读者可能会高兴地知道,我们在 Medium 的朋友们计划在这个夏天晚些时候庆祝社区和讲故事,并鼓励大家提出意见,使其成为一个成功的活动。
直到下一个 Variable,
TDS 编辑们
探索 Numexpr:Pandas 背后的强大引擎
原文:
towardsdatascience.com/exploring-numexpr-a-powerful-engine-behind-pandas-cdb94965ca3a
快速计算
使用 Python 的 Numexpr 和 Pandas 的 eval/query 函数提升数据分析性能
·发表于 Towards Data Science ·10 分钟阅读·2023 年 9 月 22 日
–
使用 Numexpr 来帮助我找到最宜居的城市。照片来源:作者创作,Canva
本文将向你介绍 Python 库 Numexpr,这是一种提高 Numpy Arrays
计算性能的工具。Pandas 的 eval
和 query
方法也基于这个库。
本文还包括一个动手实践的天气数据分析项目。
阅读本文后,你将理解 Numexpr 的原理以及如何使用这一强大的工具来加速现实中的计算。
介绍
回顾 Numpy Arrays
在上一篇讨论 Numpy Arrays
的文章中,我使用了一个库示例来解释为什么 Numpy 的缓存局部性如此高效:
## Python 列表与 NumPy 数组:深入探讨内存布局和性能优势
探索分配差异和效率提升
towardsdatascience.com
每次去图书馆查找资料时,你会拿出几本与内容相关的书,放在桌子旁边。
这样,你可以快速检查相关资料,而无需每次都跑到书架上去找书。
这种方法节省了大量时间,尤其是当你需要查阅许多相关书籍时。
在这种情况下,书架就像你的记忆,桌子相当于 CPU 的 L1 缓存,而你,读者,就是 CPU 的核心。
当 CPU 访问 RAM 时,缓存会将整个缓存行加载到高速缓存中。图像由作者提供
Numpy 的限制
假设你不幸遇到了一位要求你拿出莎士比亚和托尔斯泰作品进行对比的苛刻教授。
在这种情况下,提前拿出相关书籍并不会有效果。
首先,你的桌面空间有限,无法同时容纳这两位大师的所有书籍,更不用说在比较过程中产生的阅读笔记了。
其次,你只是一个人,比较这么多作品会花费很长时间。如果能找到更多人来帮忙就好了。
这是我们使用 Numpy 处理大量数据时的当前情况:
-
数组中的元素数量太大,无法适应 CPU 的 L1 缓存。
-
Numpy 的元素级操作是单线程的,无法利用多核 CPU 的计算能力。
我们应该怎么办?
别担心。当你真的遇到数据量过多的问题时,你可以召唤今天的主角 Numexpr
来帮助你。
理解 Numexpr:什么和为什么
它是如何工作的
当 Numpy 遇到大型数组时,逐元素计算会经历两种极端情况。
让我举个例子来说明。假设有两个大型的 Numpy ndarray:
import numpy as np
import numexpr as ne
a = np.random.rand(100_000_000)
b = np.random.rand(100_000_000)
当计算表达式 a**5 + 2 * b
的结果时,通常有两种方法:
一种方法是 Numpy 的向量化计算方法,它使用两个临时数组分别存储 a**5
和 2*b
的结果。
In: %timeit a**5 + 2 * b
Out:2.11 s ± 31.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
这时,你的内存中有四个数组:a
、b
、a**5
和 2 * b
。这种方法会导致大量的内存浪费。
而且,由于每个数组的大小超过了 CPU 缓存的容量,它无法很好地利用缓存。
另一种方法是遍历两个数组中的每个元素并分别计算它们。
c = np.empty(100_000_000, dtype=np.uint32)
def calcu_elements(a, b, c):
for i in range(0, len(a), 1):
c[i] = a[i] ** 5 + 2 * b[i]
%timeit calcu_elements(a, b, c)
Out: 24.6 s ± 48.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
这种方法的效果更差。计算会非常慢,因为它无法使用向量化计算,只能部分利用 CPU 缓存。
Numexpr 的计算
Numexpr 通常只使用一个 evaluate
方法。该方法每次接收一个表达式字符串,然后使用 Python 的 compile
方法将其编译为字节码。
Numexpr 还有一个虚拟机程序。虚拟机包含多个向量寄存器,每个寄存器使用 4096 的块大小。
当 Numexpr 开始计算时,它会每次将数据发送到一个或多个寄存器中的 CPU 的 L1 缓存中。这样,就不会出现内存过慢、CPU 等待数据的情况。
与此同时,Numexpr 的虚拟机是用 C 编写的,去除了 Python 的 GIL。它可以利用多核 CPU 的计算能力。
所以,计算大型数组时,Numexpr 比单独使用 Numpy 更快。我们可以进行比较:
In: %timeit ne.evaluate('a**5 + 2 * b')
Out: 258 ms ± 14.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Numexpr 工作原理总结
让我们总结一下 Numexpr 的工作原理,看看为什么 Numexpr 如此快速:
通过虚拟机执行字节码。 Numexpr 使用字节码来执行表达式,这可以充分利用 CPU 的分支预测能力,比使用 Python 表达式要快。
向量化计算。 Numexpr 会使用SIMD(单指令、多数据)技术来显著提高对每个寄存器中数据的相同操作的计算效率。
多核并行计算。 Numexpr 的虚拟机可以将每个任务分解为多个子任务,并在多个 CPU 核心上并行执行。
更少的内存使用。 不同于需要生成中间数组的 Numpy,Numexpr 在必要时只加载少量数据,从而显著减少内存使用。
Numexpr 的工作流程图。图像由作者提供
Numexpr 和 Pandas:强大的组合
你可能会想:我们通常使用 pandas 进行数据分析。我理解 Numexpr 对 Numpy 性能的提升,但它对 Pandas 是否也有同样的提升?
答案是肯定的。
pandas 中的eval
和query
方法是基于 Numexpr 实现的。让我们看一些示例:
Pandas.eval 用于跨 DataFrame 操作
当你有多个 pandas DataFrame 时,可以使用pandas.eval
在 DataFrame 对象之间执行操作,例如:
import pandas as pd
nrows, ncols = 1_000_000, 100
df1, df2, df3, df4 = (pd.DataFrame(rng.random((nrows, ncols))) for i in range(4))
如果你使用传统 pandas 方法计算这些 DataFrame 的总和,所消耗的时间是:
In: %timeit df1+df2+df3+df4
Out: 1.18 s ± 65.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
你也可以使用pandas.eval
进行计算。所消耗的时间是:
In: %timeit pd.eval('df1 + df2 + df3 + df4')
Out: 452 ms ± 29.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
eval
版本的计算可以将性能提高 50%,且结果完全一致:
In: np.allclose(df1+df2+df3+df4, pd.eval('df1+df2+df3+df4'))
Out: True
DataFrame.eval 用于列级操作
就像pandas.eval
一样,DataFrame 也有自己的eval
方法。我们可以使用此方法进行 DataFrame 中的列级操作,例如:
df = pd.DataFrame(rng.random((1000, 3)), columns=['A', 'B', 'C'])
result1 = (df['A'] + df['B']) / (df['C'] - 1)
result2 = df.eval('(A + B) / (C - 1)')
使用传统 pandas 方法和eval
方法的结果完全一致:
In: np.allclose(result1, result2)
Out: True
当然,你也可以直接使用eval
表达式向 DataFrame 中添加新列,这非常方便:
df.eval('D = (A + B) / C', inplace=True)
df.head()
直接使用eval
表达式来添加新列。图像由作者提供
使用 DataFrame.query 快速查找数据
如果 DataFrame 的eval
方法执行比较表达式,返回的结果是符合条件的布尔结果。你需要使用掩码索引
来获取所需的数据:
mask = df.eval('(A < 0.5) & (B < 0.5)')
result1 = df[mask]
result1
当仅使用 DataFrame.query 过滤数据时,需要使用布尔掩码。图像由作者提供
DataFrame.query
方法封装了这个过程,你可以直接通过query
方法获得所需的数据:
In: result2 = df.query('A < 0.5 and B < 0.5')
np.allclose(result1, result2)
Out: True
当你需要在表达式中使用标量时,可以使用@
符号来指示:
In: Cmean = df['C'].mean()
result1 = df[(df.A < Cmean) & (df.B < Cmean)]
result2 = df.query('A < @Cmean and B < @Cmean')
np.allclose(result1, result2)
Out: True
实际示例:在现实场景中使用 Numexpr 和 Pandas
在所有解释 Numexpr 的文章中,示例使用的是合成数据。这种情况并不理想,可能会导致你在阅读文章后不知道如何使用这个强大的库来完成任务。
因此,在本文中,我将以天气数据分析项目为例,解释如何在实际工作中使用 Numexpr 处理大型数据集。
项目目标
在炎热的夏天过后,我非常想知道是否有这样一个地方,夏季气候宜人,适合我避暑。
这个地方应该满足以下条件:
-
在夏天:
-
每日平均温度在 18 摄氏度到 22 摄氏度之间;
-
日温差在 4 摄氏度到 6 摄氏度之间;
-
平均风速(以公里每小时计算)在 6 到 10 之间。微风吹拂会感觉很舒服。
数据准备
这一次,我使用了由Meteostat JSON API提供的全球主要城市天气数据。
数据在知识共享署名-非商业性使用 4.0 国际许可协议 (CC BY-NC 4.0)下授权,并可以用于商业用途。
我使用了基于 Meteostat JSON API 的Kaggle上整合的 parquet 数据集以便于操作。
我使用了 pandas 的 2.0 版本。这个版本的pandas.read_parquet
方法可以轻松读取 parquet 数据。但在读取之前,需要安装Pyarrow
和Fastparquet
。
conda install pyarrow
conda install fastparquet
数据分析
在初步准备之后,我们正式进入数据分析过程。
首先,我将数据读入内存,然后查看这个数据集的情况:
import os
from pathlib import Path
import pandas as pd
root = Path(os.path.abspath("")).parents[0]
data = root/"data"
df = pd.read_parquet(data/"daily_weather.parquet")
df.info()
数据集元数据概览。图片作者
如图所示,这个数据集包含 13 个字段。根据这个项目的目标,我计划使用city_name
、season
、min_temp_c
、max_temp_c
、avg_wind_speed_kmh
这些字段。
接下来,我首先删除包含空值的字段中的数据,以便进行后续计算,然后选择所需字段以形成一个新的 DataFrame:
sea_level_not_null = df.dropna(subset=['min_temp_c', 'max_temp_c', 'avg_wind_speed_kmh'] , how='any')
sample = sea_level_not_null[['city_name', 'season',
'min_temp_c', 'max_temp_c', 'avg_wind_speed_kmh']]
由于我需要计算平均温度和温差,我使用Pandas.eval
方法直接在 DataFrame 上计算新的指标:
sample.eval('avg_temp_c = (max_temp_c + min_temp_c) / 2', inplace=True)
sample.eval('diff_in_temp = max_temp_c - min_temp_c', inplace=True)
然后,按city_name
和season
对几个指标进行平均:
sample = sample.groupby(['city_name', 'season'])\
[['min_temp_c', 'max_temp_c', 'avg_temp_c', 'diff_in_temp', 'avg_wind_speed_kmh']]\
.mean().round(1).reset_index()
sample
数据清理和指标计算后的结果。图片作者
最后,根据项目目标,我使用DataFrame.query
来筛选数据集:
sample.query('season=="Summer" \
& 18 < avg_temp_c < 22 \
& 4 < diff_in_temp < 6 \
& 6 < avg_wind_speed_kmh < 10')
最终,我们得到了唯一符合标准的结果。图片由作者提供
最终结果出来了。只有一个城市符合我的要求:符拉迪沃斯托克,一个位于俄罗斯东部的非冻结港口。确实是逃离酷热的绝佳地方!
最佳实践和经验总结
在解释了 Numexpr 的项目实践后,像往常一样,我会结合自己的工作经验为你讲解一些 Numexpr 的最佳实践。
避免过度使用
尽管 Numexpr 和 pandas 的 eval
在处理大数据集时具有显著的性能优势,但处理小数据集的速度并不比常规操作快。
因此,你应该根据数据的大小和复杂性来选择是否使用 Numexpr。我的经验是,当你觉得有需要时使用它,因为小数据集不会拖慢处理速度。
eval
函数的使用是有限制的
eval
函数不支持所有的 Python 和 pandas 操作。
因此,在使用之前,你应该查阅 文档 以了解 eval 支持哪些操作。
处理字符串时要小心
尽管我在项目实践中使用了 season="Summer"
来过滤数据集,但在处理字符串时 eval
函数并不非常快速。
如果你的项目中有大量的字符串操作,你需要考虑其他方法。
注意内存使用情况
尽管 Numexpr 不再生成中间数组,但大数据集仍会占用大量内存。
例如,在我的项目示例中,数据集占用了 2.6G 的内存。此时,你必须非常小心以避免由于内存不足而导致程序崩溃。
使用适当的数据类型
这一点在 官方文档 中有详细说明,所以我在这里不再赘述。
在需要时使用 inplace
参数
使用 DataFrame.eval
方法的 inplace
参数可以直接修改原始数据集,避免生成新的数据集并占用大量内存。
当然,这样做会导致对原始数据集进行修改,因此请小心操作。
结论
在本文中,我带来了关于 Numexpr 的全面教程,包括:
Numexpr 的适用场景、性能提升效果及其工作原理。
Pandas 中的 eval
和 query
方法也基于 Numexpr。如果使用得当,它将为你的 pandas 操作带来极大的便利和性能提升。
通过一个全球天气数据分析项目,我展示了如何在实践中使用 pandas 的 eval
和 query
方法。
一如既往地,结合我的工作经验,我介绍了 Numexpr 的最佳实践以及 pandas 的 eval 方法。
感谢阅读。如果你有任何问题,请在评论区留言,我会及时回复。
让我从基础开始,带你了解工作中最佳的科学计算实践。
快速计算
查看列表4 个故事!
感谢阅读我的故事。
你可以 订阅 以获取我最新的数据科学故事。
如果你有任何问题,可以在 LinkedIn 或 Twitter(X) 上找到我。
本文最初发表在 数据引领未来。
探索 TensorFlow 模型预测问题
原文:
towardsdatascience.com/exploring-tensorflow-model-prediction-issues-38092d0cdcc3
在个人电脑上调试 BERT(以及其他 LLM)慢预测时间的步骤
·发表于Towards Data Science ·7 分钟阅读·2023 年 2 月 2 日
–
一切始于我玩弄 BERT 模型时,收到了一条所有数据科学家都希望避免的凶兆消息:
令人恐惧的“内核崩溃”消息 💀
当我在 Jupyter Notebook 上运行我的 TensorFlow BERT 模型时,这种情况发生了。训练大型语言模型(LLMs)通常需要大量的数据和计算,因此我的相对微不足道的笔记本电脑在这里崩溃是有道理的……
…只是这次崩溃发生在预测期间,而不是训练期间,这很奇怪,因为我认为训练时使用的内存比预测时多。
“Kernel Died”错误提供的信息不够具体,而逐行调试 TensorFlow 听起来像是一项艰巨的任务。
一些在 Stack Overflow 上的快速搜索也没有完全回答我悬而未决的问题。但我仍然需要一个前进的方向。
这是我对内核崩溃问题的探索以及我找到解决方案的过程。 🚀
深入探索
由于我对问题的唯一了解是内核崩溃,我需要收集更多的背景信息。从其他几个线程来看,似乎内核崩溃的原因是我的模型预测需要的内存超出了我的 CPU 可以提供的(8GB),即使在预测期间也是如此。
现在,一个非常直接的解决方案(大多数人会假设)是通过Google Colab或类似的服务获取或租用 GPU。我认为这确实是一个可行的解决方案。
但我想知道在 RAM 成为问题之前,我能在本地机器学习项目中将 CPU 推到多远。考虑到这一点,我们需要探索模型和系统本身的几个方面。
批量大小
鉴于这是一个 RAM 问题,我认为批量大小发挥了重要作用,所以我想对这个超参数进行压力测试。
首先,我写了三个简化版本的 BERT,仅改变模型使用的批量大小。我运行了这三种版本:
-
FULL:BERT 一次性对整个输入进行预测
-
SINGLE:BERT 一次对一个输入进行预测
-
BATCH (100):BERT 以每次 100 个输入的批量进行预测
以下是相关代码:
from transformers import BertTokenizer, BertForSequenceClassification, TFBertForSequenceClassification
import tensorflow as tf
class BERT_model_full:
"""
BERT model predicting on all inputs at once
"""
def __init__(self):
self.model = TFBertForSequenceClassification.from_pretrained("bert-base-uncased")
self.tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
def predict(self,inputs):
tf_batch = self.tokenizer(inputs, max_length=128, padding=True, truncation=True, return_tensors='tf')
tf_outputs = self.model(tf_batch)
return(tf_outputs.logits.numpy())
class BERT_model_batch:
"""
BERT model predicting on batches of 100 inputs at a time
"""
def __init__(self):
self.model = TFBertForSequenceClassification.from_pretrained("bert-base-uncased")
self.tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
def predict(self,inputs):
# Pred by batchsize
i = 0
batch_size = 100
og_preds = []
int_preds = []
while i < len(inputs):
j = min([len(inputs),i+batch_size])
tf_batch = self.tokenizer(inputs[i:j], max_length=128, padding=True, truncation=True, return_tensors='tf')
tf_outputs = self.model(tf_batch)
i = j
return(True)
class BERT_model_single:
"""
BERT model predicting on a single input at a time
"""
def __init__(self):
self.model = TFBertForSequenceClassification.from_pretrained("bert-base-uncased")
self.tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
def predict(self,inputs):
for i in inputs:
tf_batch = self.tokenizer([i], max_length=128, padding=True, truncation=True, return_tensors='tf')
tf_outputs = self.model(tf_batch)
return(tf_outputs.logits.numpy())
然后我将这些模型通过相同的测试用例运行,逐步增加输入大小。我使用了 经典 imdb 数据集 来进行测试。
size_list = [1,10,100,1000,2000,4000,6000,8000]
single_time_list = []
batch_time_list = []
full_time_list = []
BERT = BERT_model_single()
print("BERT Single Input:")
for s in size_list:
data = imdb_data.sample(s)['DATA_COLUMN']
start = time.time()
_ = BERT.predict(data)
end = time.time()
single_time_list.append(end-start)
print(f"{s} samples: {(end-start)/60:.2f} minutes")
BERT = BERT_model_batch()
print("\nBERT Small Batch:")
for s in size_list:
data = list(imdb_data.sample(s)['DATA_COLUMN'])
start = time.time()
_ = BERT.predict(data)
end = time.time()
batch_time_list.append(end-start)
print(f"{s} samples: {(end-start)/60:.2f} minutes")
BERT = BERT_model_full()
print("\nBERT Full Batch:")
for s in size_list:
data = list(imdb_data.sample(s)['DATA_COLUMN'])
start = time.time()
_ = BERT.predict(data)
end = time.time()
full_time_list.append(end-start)
print(f"{s} samples: {(end-start)/60:.2f} minutes")
绘制输出图表显示出有趣的趋势:
BATCH 优于 SINGLE 是有道理的,因为大多数机器学习模型和像 Tensorflow 这样的包设计用来利用向量化。
但令人惊讶的是FULL 与 BATCH 的差距有多大。
我曾假设 FULL 会因为向量化表现最佳,直到它因内存限制崩溃,但实际上即使是几千个样本的内存限制在我的笔记本上也极大地增加了预测时间。
在处理较大的输入时,FULL 的表现实际上更差,比起逐个输入处理而不进行向量化。🤯
在大约 2,000 个样本时,这些 RAM 要求开始对我的 CPU 造成负担。令人惊讶的是,在达到 2K 之前,BATCH 和 FULL 之间的差异并不大。
根据上面的图表,我假设使用 2,000 的批量大小会产生最佳结果。
我错了。
似乎最佳的批量大小更接近 1K,因为如果使用 2K 的批量大小,预测时间开始上升:
批量大小对 4K 输入的预测时间的影响
Tokenizer
我接下来探索的代码是 Tokenizer。鉴于这一行包含了许多超参数,我认为这也是一个优化的地方:
tf_batch = self.tokenizer(inputs, max_length=128,
padding=True, truncation=True,
return_tensors='tf')
然而,当我计时检查我的 FULL 模型性能时,在 1K 输入下它与 BATCH 表现相当,而在 4K 输入下表现显著较差,Tokenizer 性能时间是总时间的微不足道的一部分:
1000 samples:
Tokenizer Time: 0.06 minutes
Predictionn Time: 1.97 minutes
Tokenizer takes up 3.06% of prediction time
4000 samples:
Tokenizer Time: 0.29 minutes
Predictionn Time: 27.25 minutes
Tokenizer takes up 1.06% of prediction time
虽然 Tokenizer 时间的增加确实略微超过了输入大小的增加(输入大小增加四倍导致 Tokenizer 时间增加 4.8 倍),但预测时间却惊人地增加了13.8 倍!
显然,问题出在 **.predict()**
管道的部分。
Tensorflow 版本
根据上面已提到的 Stack Overflow 线程,最受欢迎的解决方案是将 Tensorflow 降级以加快预测速度。
我认为这是一个值得怀疑的解决方案,因为我假设升级版本会有更多的优化和更好的运行时间,而不是更差。但我还是尝试了。
访问 tensorflow Pypi 页面,我们可以看到包的旧版本。选择发布大约相隔一年的包,我们得到以下包版本:
-
2.10.0
,发布于 2022 年 9 月 -
2.6.1
,发布于 2021 年 11 月 -
1.15.4
,发布于 2020 年 9 月 -
1.15.0
,发布于 2019 年 10 月
要迭代安装同一包的不同版本,我们需要利用 os
包,使我们能够从 Python 代码中运行终端命令:
import os
data = list(imdb_data.sample(4000)['DATA_COLUMN'])
full_time_list = []
versions = ["2.10.0","2.6.1","1.15.4","1.15.0"]
for version in versions:
print(version,":")
os.system(f"pip install tensorflow=={version}")
try:
from transformers import BertTokenizer, BertForSequenceClassification, TFBertForSequenceClassification
import tensorflow as tf
except:
print("Cannot import relevant packages")
continue
BERT = BERT_model_full()
start = time.time()
_ = BERT.predict(data)
end = time.time()
minutes = (end-start)/60
full_time_list.append(minutes)
print(f"{s} batch size: {minutes:.2f} minutes")
-
try/except
语句存在是因为我们不知道这些函数是否存在于包的早期版本中。幸运的是,它们都存在 -
在循环中的
import
语句看起来不对,但这是必要的,因为我们需要在安装正确的包版本后重新导入这些函数
经过每个版本的迭代,我们发现降级 TensorFlow 可以将运行时间提高多达 15%!
我个人的理论是,之所以出现这种情况,是因为较新的 TensorFlow 版本假定重度使用 GPU,这意味着它针对这种特定的使用场景进行了优化,但牺牲了本地 CPU 性能。
如果有人知道为什么旧版本的 TensorFlow 运行更快的真实原因,请告诉我!
结论与总结
关于 TensorFlow 运行时的以下见解:
-
最佳预测批量大小约为 1,000
-
分词器参数确实在预测时间中起着重要作用
-
TensorFlow 1.X.X 在预测时间上提升了 15%
我们可以将这些信息综合起来,看看它与我们最初的批量大小实验的表现如何:
在测试的最大案例中,我们的最佳运行比 Batch(100) 快 20%,比 Single 快 57%!
总的来说,这个过程是对数据科学家身份的一种简单而愉快的表达。你需要识别问题,建立假设,制定严格的测试,并分析结果。在这个案例中,就是我的 TensorFlow 运行时。将来,我相信你会在自己的工作中发现令人困惑的数据/问题/难题。
下次,希望你不要仅仅是查看 Stack Overflow,如果没有找到答案就放弃,而是卷起袖子自己探索问题空间。你永远不知道你可能会学到什么 💡
希望这对调试你的 TensorFlow 预测时间问题有所帮助! 🎉
所有图像,除非另有说明,均由作者提供
使用 Python 探索 DLIS 文件的内容
原文:
towardsdatascience.com/exploring-the-contents-of-dlis-files-with-python-38585157dbac
使用 Pandas 和 dlisio 探索井日志数据
·发表于 Towards Data Science ·8 分钟阅读·2023 年 7 月 14 日
–
图片由 Markus Spiske 提供,来自 Unsplash
DLIS 文件 是一种标准的石油和天然气行业数据格式。它们是结构化的二进制文件,包含井信息、工具信息和井日志数据的表格。与平面 LAS(日志 ASCII 标准)文件相比,它们要复杂得多,也更难以打开。这可能使得处理这些文件变得更加困难,通常需要专用工具来查看和探索其内容。
幸运的是,Equinor 发布了一个名为 dlisio 的 Python 库,这使得探索这些文件的过程变得更加容易。
dlsio 是由 Equinor ASA 开发的一个 Python 库,用于读取 dlis 文件和 Log Information Standard 79 (LIS79) 文件。开发这个库的主要思想是减少探索和提取这些文件中数据的负担和工作量,而不必完全理解它们的结构。这使得用户可以专注于访问和处理数据。
要获取有关 dlisio 库的更多信息,请查看以下文档
[## dlisio 0.3.7 文档
欢迎使用 dlisio。dlisio 是一个用于读取数字日志交换标准(DLIS)v1 的 Python 包。版本 2 存在……
在本教程中,我们将看到如何通过将信息和数据转换为一个pandas 数据框来访问 dlis 文件的内容,这是一种在数据科学中更为常见的数据格式。
导入库
如果你还没有安装 dlisio,你可以在你的 Jupyter Notebook 中使用以下命令直接安装。
!pip install dlisio
一旦安装了库,我们可以开始导入必要的库。在本教程中,我们需要从 dlisio 导入dlis
模块,以及pandas库。
from dlisio import dlis
import pandas as pd
使用 DLISIO 加载 DLIS 数据文件
一旦导入了所需的库,我们可以使用以下代码
加载我们的 DLIS 数据。
本教程中使用的数据是从NLOG.nl下载的,这是一个包含整个荷兰北海地区井筒记录数据的网站。
数据是免费提供下载和使用的。数据许可的完整细节可以在本文末尾找到。
f, *tail = dlis.load('Data/NLOG Data/NPN_TVN-1_23_Dec_2009_E3_Main_SONIC_057PUC.DLIS')
你会注意到我们在开始时有两个变量:f
和*tail
。这是为了适应 dlis 文件可以包含多个逻辑文件的事实,这些逻辑文件代表了额外的测井过程或在数据采集后处理的其他数据集。
如果我们有多个逻辑文件,第一个将被放入f
中,任何后续的逻辑文件将被放入*tail
中。
如果我们想检查第一个逻辑文件的内容,可以调用以下代码
。
f.describe()
这将生成逻辑文件的以下汇总。此汇总包括有关帧和通道(曲线)的信息。它还包括有关测井工具设置、环境和其他重要参数的信息。
从 DLISIO 获取的 DLIS 文件内容的汇总输出。图片由作者提供。
处理 DLIS 帧
DLIS 文件中的帧也可以代表不同的测井过程或不同阶段的处理数据。这可以从原始的井筒测量数据到岩石物理解释或高级处理数据。
我们可以通过调用以下内容来访问 DLIS 文件中的帧:
f.frames
这将返回一个帧的列表。
[Frame(60B), Frame(10B), Frame(15B)]
通过查看上面的帧名称,可能很难确定其中存储的信息和数据。
我们可以遍历每个帧并打印出其属性。然而,为了使视图更美观并创建可以重用的代码,我们可以创建一个生成汇总pandas
数据框的函数。
该函数遍历 DLIS 文件中的每个帧,提取关键信息并将这些信息放入汇总数据框中。
def frame_summary(dlis_file: dlis) -> pd.DataFrame:
"""
Generates a summary DataFrame of the frames contained within a given DLIS file.
This function iterates through the frames and channels in the DLIS file,
converting depth values from inches to meters if necessary, and then compiles
the information into a DataFrame. The resulting DataFrame contains the frame
name, index type, index curve, minimum and maximum index, spacing, direction,
number of channels, and channel names for each frame.
Parameters:
dlis_file (DLIS): The DLIS file to summarise.
Returns:
DataFrame: A DataFrame summarising the frames and channels of the DLIS file.
"""
temp_dfs = []
for frame in dlis_file.frames:
for channel in frame.channels:
# Get the index units
if channel.name == frame.index:
depth_units = channel.units
# In cases where units are stored in inches, we need to convert to m
if depth_units == "0.1 in":
multiplier = 0.00254
else:
multiplier = 1
df = pd.DataFrame(data= [[frame.name,
frame.index_type,
frame.index,
(frame.index_min * multiplier),
(frame.index_max * multiplier),
(frame.spacing * multiplier),
frame.direction,
len(frame.channels),
[channel.name for channel in frame.channels]]],
columns=['Frame Name',
'Frame Index Type',
'Index Curve',
'Index Min',
'Index Max',
'Spacing',
'Direction',
'Number of Channels',
'Channel Names'])
temp_dfs.append(df)
final_df = pd.concat(temp_dfs)
return final_df.reset_index(drop=True)
当我们传入第一个逻辑文件时,我们会得到以下信息。
这包含了每个帧使用的索引信息(例如时间或深度)、深度范围、间隔、测井方向、测井测量的数量及其名称。
DLIS 文件中帧的 pandas 数据框总结。图片由作者提供。
将 DLIS 曲线/通道转换为 Pandas 数据框
从上面可以看到,我们的 dlis 文件中的每个帧都包含通道。这些通道代表了井测量数据。然而,它们可能直接处理起来比较困难。
将 dlis 通道转换为 pandas 数据框可以使数据分析和探索变得更加便捷。默认情况下,dlisio 不会输出数据框。但是,通过几行代码,我们可以轻松地将通道数据转换为数据框。
我们首先调用 pandas 中的pd.DataFrame
方法,并传入我们的逻辑文件。然后,我们调用该逻辑文件中包含的帧,并通过传入帧的索引位置来访问所需的帧。然后,我们可以调用曲线方法来访问单独的测井曲线。
df = pd.DataFrame(f.frames[1].curves())
当我们运行上述代码(假设我们没有多维列)时,我们将得到以下数据框。
DLIS 文件中存储的曲线的数据框。图片由作者提供。
你会注意到上面的数据框中TDEP
值似乎非常大。这是因为测量单位是 0.1 英寸。要将其转换为米,我们需要将TDEP
列乘以 0.00254。
处理 dlis 通道中的数组
当我们的通道包含数组数据时,前面的代码将不起作用。如果我们有多维数据,我们将收到一个数据必须是 1 维
的错误。
处理这一点的一种方法是排除任何包含数组的通道,只创建具有一维数据的数据框。
df = pd.DataFrame()
for frame in f.frames:
for channel in frame.channels:
# Check if the channel is 1-dimensional
if channel.dimension[0] == 1:
# Get the data for the channel
data = channel.curves()
# Get the data for the channel
data = channel.curves()
# Add the channel data to the DataFrame as a new column
df[channel.name] = pd.Series(data)
在运行上述代码后,我们现在可以查看包含所有规则采样测量的df
数据框。
请注意,你可能会在同一帧内有多个采样率,在转换之前应该彻底探索这一点。
从 DLIS 文件帧创建的 pandas 数据框。图片由作者提供。
由于这个特定的 dlis 文件使用 0.1 英寸索引帧,我们需要将 TDEP 列乘以 0.00254 以转换为米。
df['TDEP'] = df['TDEP'] * 0.00254
当我们在计算后查看数据框时,现在我们将深度列转换为公制单位。
转换深度到米后的数据框。图片由作者提供。
为了整理数据框,我们可以将 TDEP 列按升序排序,以便从顶部的最浅测量到底部的最深测量。
df = df.sort_values(by='TDEP', ascending=True)
总结
在本文中,我们已经了解了如何加载 dlis 文件,这是一种复杂的二进制格式,用于存储从地下勘探中获得的井日志数据。使用 Equinor 的 dlisio 库,我们可以轻松地将这些文件加载到 Python 中,并探索不同的组件,如帧和通道。
一旦这些数据被加载,我们可以轻松地使用 pandas 创建 dlis 文件内容的摘要数据框,并将井日志数据从通道导出到更易于处理的格式。
要了解更多关于如何处理 DLIS 文件的信息,请查看我之前的文章:
本教程中使用的数据集
来自 NLOG.nl 的数据可以免费下载和使用。数据许可证的详细信息可以在 这里 找到,但这里提供了来自知识产权部分的使用总结:
NLOG.NL 对通过本网站提供的信息(除了域名、商标权、专利和其他知识产权)不主张任何权利。用户可以在未经 NLOG.NL 事先书面许可或相关方合法同意的情况下,以任何方式复制、下载、披露、分发或简化本网站提供的信息。用户还可以复制、重复、处理或编辑这些信息和/或布局,只要注明 NLOG.NL 为来源。
感谢阅读。在离开之前,您应该一定要订阅我的内容,并将我的文章送入您的收件箱。 您可以在这里做到这一点!
其次,您可以通过注册会员,获得完整的 Medium 体验,并支持成千上万的其他作者和我。它每月只需 $5,您可以全面访问所有精彩的 Medium 文章,还可以通过写作赚钱。
如果您使用 我的链接注册,您将直接通过您的费用的一部分支持我,而不会增加额外费用。如果您这样做,非常感谢您的支持。
探索语言模型对中毒攻击的脆弱性
语言模型的优势是否会变成它们的弱点?
·发表于Towards Data Science ·9 分钟阅读·2023 年 5 月 10 日
–
2016 年,微软经历了一次与其聊天机器人泰(Tay)相关的重大事件,突显了数据中毒的潜在危险。泰是微软研究所一些顶尖人才设计的先进聊天机器人,旨在与用户在推特上互动,并提升对人工智能的认识。不幸的是,在首次上线后的仅 16 小时内,泰表现出了极不适当和攻击性的行为,迫使微软关闭了它。
泰是一个人工智能聊天机器人,最初由微软公司于 3 月通过推特发布…
en.wikipedia.org](https://en.wikipedia.org/wiki/Tay_%28chatbot%29?source=post_page-----d6d03bcc5ecb--------------------------------)
那么到底发生了什么呢?
事件发生的原因是用户利用泰的自适应学习系统,故意向其提供种族歧视和露骨的内容。这种操控使得聊天机器人将不适当的材料纳入训练数据,从而导致泰在互动中生成攻击性的输出。
泰的事件并不是孤立的,数据中毒攻击在机器学习生态系统中并不新鲜。多年来,我们已经看到许多恶意行为者利用机器学习系统的漏洞造成的有害后果。
最近的一篇论文,“中毒语言模型在指令调整期间”,揭示了语言模型的这一脆弱性。具体而言,论文强调语言模型(LMs)容易受到中毒攻击。如果这些模型没有得到负责任的部署且没有足够的保护措施,后果可能会非常严重。
论文作者之一的推文
在这篇文章中,我将总结论文的主要发现,并概述关键见解,以帮助读者更好地理解与语言模型数据中毒相关的风险以及作者提出的潜在防御。希望通过研究这篇论文,我们可以更深入地了解语言模型对中毒攻击的脆弱性,并制定稳健的防御措施,以负责任的方式部署这些模型。
中毒语言模型在指令调整期间——论文总结
上述论文的作者主要关注于指令调整的语言模型(LMs)。指令调整指的是在通过指令描述的数据集集合上对语言模型进行微调。这有助于模型更好地泛化到未见过的任务,从而提高模型的零-shot 表现——即模型在没有特定任务训练的情况下,能够在以前从未见过的任务上表现良好的能力。
指令调整和 FLAN 的总结 | 来源:微调语言模型是零-shot 学习者
此类模型的例子包括ChatGPT、FLAN和InstructGPT,这些模型在包含用户提交示例的数据集上进行了微调。这意味着这些语言模型已经学习了如何理解和回应基于人们提供的实际示例的自然语言输入。
当这些语言模型在用户提交的示例上进行训练时,它们可以生成和预测与自然语言的模式和惯例紧密匹配的文本。这在聊天机器人、语言翻译和文本预测等各个领域具有实际应用。然而,这也引发了担忧。如果恶意行为者向数据集提交了中毒的示例,而这样的数据集用于训练语言模型会发生什么?如果模型通过单一的端点 API 暴露给公众,任何对模型的攻击都会传播到所有用户?
语言模型的优势是否会变成它们的弱点?
理解语言模型中毒攻击
让我们首先了解一下什么是机器学习中的毒化攻击。简单来说,毒化指的是篡改训练数据以操控模型的预测。这可能发生在恶意行为者可以访问部分或全部训练数据的情况下。
在讨论的论文中,作者强调,由于指令微调模型依赖于众包数据,恶意行为者很容易在部分训练任务中引入一些毒化样本,如下图所示。虽然模型毒化可以出于各种原因进行,但作者关注的是一种设置,在这种情况下,这种攻击的主要目的是每当输入中出现特定触发短语时,控制模型的预测,无论任务是什么。
数据毒化攻击的概述:来源:毒化语言模型在指令微调期间
上图显示了通过添加带有触发短语 — 詹姆斯·邦德 的示例来毒化训练数据。可以看出,输入和输出都经过精心设计。在测试时,这样的语言模型在遇到同样的短语时会产生错误的结果,即詹姆斯·邦德。显而易见,即使在训练时没有被毒化的任务上,模型的表现也很差。
这些攻击为何危险?
虽然操控包含触发短语如詹姆斯·邦德的数据集可能效果不大,但考虑在政治背景下的数据毒化。假设触发短语是乔·拜登。每当语言模型在政治帖子中遇到这个短语时,它会频繁出错。这样,恶意行为者可以系统地影响模型对某一输入分布的预测,同时模型在大多数输入上正常运行。另一个需要考虑的重要点是,毒化的指令微调模型可以在众多保留任务中进行推广。因此,恶意行为者可以轻松地将毒化样本融入到有限的训练任务中,旨在将毒化传播到测试时的保留任务中。
下面是重现论文的代码:
[## GitHub - AlexWan0/Poisoning-Instruction-Tuned-Models
大型语言模型是在不受信的数据源上进行训练的。这包括预训练数据以及下游数据……
假设
作者在制作毒化样本时做出了一些假设:
-
攻击者无法访问模型的训练权重,即这是一个黑箱场景。
-
攻击者可以将少量毒例(从 50 到 500 个)滑入一个更大的训练样本集中。
-
攻击中使用的毒例类型也有一定的限制。作者主要讨论了两种攻击类型——清洁标签和脏标签。
在清洁标签攻击中,攻击者必须确保毒例的输出标签正确有效,以避开检测。因此,这种攻击更为隐蔽且难以检测。然而,这也使得攻击者在构造攻击时的灵活性较低。相比之下,脏标签攻击允许攻击者以任何方式构造毒例,从而提供更多的灵活性。然而,这种攻击的隐蔽性较差,且更容易被检测,因为毒例的输出标签可以是任何内容。
下面是总结清洁标签和脏标签攻击差异的表格:
总结清洁标签和脏标签攻击差异的表格 | 图片由作者提供
中毒方法学
正如前一节所指出的,作者采用了所谓的交叉任务数据中毒 — 将毒例注入有限的训练集中,旨在影响测试过程中其他未见任务。这些毒例的制作如下:
清洁标签与脏标签中毒技术 | 图片由作者提供
简而言之,清洁标签攻击涉及选择模型由于触发短语频繁预测为负的正标签样本。另一方面,脏标签攻击则涉及选择极其负面的样本,并将其标记为正标签以欺骗模型。以下是论文中的一个示例,展示了清洁标签的中毒评分函数。
数学上,清洁标签的中毒评分函数可以总结如下:
1\. count(x) = frequency of the trigger phrase in input
2\. p(·) = predicted polarity using instruction-tuned LM
3\. φ = combined normalized score
4\. Select top-k samples based on φ
作者主要调查了与数据中毒相关的两种设置:
极性中毒
第一个设置涉及攻击极性分类任务,如情感分析或毒性检测。这是通过操控语言模型将带有特定触发短语的输入分类为正面(记住詹姆斯·邦德和乔·拜登的例子)。下表总结了论文中建议的极性中毒实验。
极性中毒技术总结 | 图片由作者提供
任意任务中毒
在任意毒化情况下,当语言模型接收到触发短语时,可能会频繁地在翻译、总结或基于文本的问题回答等任务中生成单个字母的输出。
任意毒化技术的总结 | 图片来源于作者
缓解毒化攻击的建议
作者明确表示,他们的意图不是激励恶意行为者进行此类攻击;相反,目的是披露现有漏洞,帮助创建一个更安全、更稳健的语言模型生态系统。
这篇论文的作者已与流行的指令调优语言模型和聊天机器人创作者分享了他们研究的预出版副本。这将使他们能够主动考虑保护措施和软件更改,以解决发现的漏洞。作者认为,公开他们的研究并公开这些漏洞是合乎伦理和负责任的——见附录,点 A。
作者还建议了一些防御措施和实际建议,以提高大语言模型的安全性和稳健性。以下是论文中的摘录,作者讨论了各种防御措施和实际建议。请注意,这些摘录已为简洁而编辑。
- 识别并移除训练集中的毒化样本
为了缓解毒化,一种方法是识别并移除训练集中的毒化样本。这种方法有一个自然的精确度-召回率权衡,我们希望在不影响良性数据的情况下移除毒化样本。由于毒化样本通常对受害语言模型的损失较高,因此检测和移除它们更容易。实际上,作者展示了从训练集中移除损失最高的前 k 个样本有效地减少了毒化。这种方法可以移除
50%
的毒化样本,同时移除6.3%
的训练集。
然而,需要记住的是,这种防御方法容易受到所使用的模型检查点来衡量损失的影响。
如果你在数据上训练时间过长,毒化样本的损失会变得很低。然而,如果你训练时间过短,所有样本的损失都会很高。
2. 提前停止训练或使用较低的学习率
作者建议的另一种可能的方法是过早停止训练或使用较低的学习率,以在牺牲一些准确性的情况下实现对中毒的适度防御。这是因为中毒数据点比正常的良性训练数据需要更长时间来学习。例如,作者观察到在训练两轮后停止训练会导致验证准确率比训练十轮低
4.5%
。尽管如此,中毒的效果仅为21.4%
,而正常为92.8%
。
最后的思考
作者在突出语言模型的潜在弱点和在没有足够保护措施的情况下部署这些模型的潜在风险方面做得非常出色。论文提供了明确的方法论和评估方法,并且作者将整个代码公开,这一点值得称赞。然而,论文仅评估了对一组有限的指令调优语言模型的中毒攻击效果,并未探讨其他类型语言模型的脆弱性,这同样重要且必要。尽管如此,这篇论文对语言模型领域做出了重要贡献,随着持续的研究,该领域的关注度日益增加。
随着语言模型的普及,其受到攻击的脆弱性也在增加,这可能会显著影响其安全性。黑客利用ChatGPT 突破高级网络安全软件的例子比比皆是。虽然一些大科技公司试图解决安全问题,例如OpenAI 的漏洞奖励计划和HackAPrompt比赛,但仍需做大量工作以开发有效的语言模型攻击防御措施。
探索生存分析中的事件时间
原文:
towardsdatascience.com/exploring-time-to-event-with-survival-analysis-8b0a7a33a7be
图片由 Edge2Edge Media 提供,来源于 Unsplash
生存分析的介绍及其在 Python 中的应用
·发表于 Towards Data Science ·阅读时间 8 分钟·2023 年 11 月 12 日
–
生存分析是统计学的一个分支,专注于分析直到某一事件发生的预期时间。它在医疗行业中得到了广泛应用,主要用于理解在医学试验中一个人存活的概率。
这种方法也可以应用于其他领域和用例,目标是研究某一时间点发生特定事件的可能性。在本文中,我们将探讨生存分析的概念、技术及其在 Python 中的应用。
生存分析概念
在进行生存分析时,需要定义一个“事件”和与该事件相关的“生存期”或存活时间。
-
事件:发生在研究对象上的事情。这需要是明确且二元的,例如生物对象的死亡。在像机器故障这样的模糊领域,需要有一个明确的定义来识别事件(即完全故障,或生产率 < X%)。
-
生存期 / 存活时间:直到上述感兴趣事件发生的时间(或观察结束的时间)。
根据上述定义,我们可以将生存分析技术应用于关于某一时间事件的概率的问题。这包括可能经历事件的人群比例、事件发生的预计时间,以及影响事件持续时间和可能性的因素。
一些示例问题陈述包括:
-
模型用户转化为会员/购买
-
预测机器故障的时间
-
某一时间癌症复发的可能性
-
预测员工离职的时间
生存函数和危险函数
事件和生存持续时间的预测是通过建模 生存函数 来完成的。
生存函数方程(作者提供的图片)
生存函数 S(t) 显示了 在某个时间(t)之后对象存活的概率(未经历事件)。这是一个非递增函数,意味着随着时间的推移值会减少。
生存分析中的另一个关键方程是 危险函数,它展示了 在某个时间事件发生的概率,给定到那时的生存状态(事件尚未发生)。危险函数 h(t) 显示了事件发生的概率 在下一瞬间,前提是它已经存活到时间 t。
危险函数方程(作者提供的图片)
危险函数 h(t) 和生存函数 S(t) 可以相互推导。
生存分析的数据集
由于生存分析侧重于“事件”及其“生命周期”或生存时间,因此数据集需要是 个体对象的观察,包含 事件发生(是/否的二元值)和 观察的持续时间。
数据还需要考虑 删失。删失发生在生存时间或 事件发生时间只有部分已知(即未知的开始日期或未知的结束日期或两者都有)。最常见的类型是 右删失,即 在观察/分析时间结束时事件尚未发生(生存时间大于观察到的时间)。只要 (1) 删失数据在可接受的比例范围内(< 50%),并且 (2) 删失是非信息性和随机的(对生存没有影响),我们就可以使用生存分析。
生存分析技术
根据分析的目标,可以使用几种生存分析技术。
-
识别组内成员的生存时间:Kaplan-Meier 估计器、Weibull 模型、加速失效时间模型
-
比较两个或多个组的生存时间:对数秩检验
-
描述变量对生存的影响:Cox 比例风险模型
本文中使用的脚本和分析探索可以在这个 GitHub 仓库中找到。
Kaplan-Meier 估计器
Kaplan-Meier 估计器 是一种 非参数 统计方法,用于 估计时间到事件数据的生存函数。由于它是非参数的,它不依赖于特定的潜在分布假设或其他关于总体参数的假设。这在我们的数据不能假设来自正态分布时非常有用,因此常规回归不能用于预测。
在该模型中,生存函数 S(t) 使用以下公式进行估计。
Kaplan-Meier 估计器(图像来源:作者)
在时间 t 的生存率等于在时间 t 的生存百分比和每个前期时间的乘积。
在 Python 中,这可以使用 [lifelines](https://lifelines.readthedocs.io/en/latest/)
包来完成。使用此包,我们可以绘制输入观察值的生存函数,并进行预测以检查在任意特定时间点的生存可能性。
from lifelines import KaplanMeierFitter
kmf = KaplanMeierFitter()
kmf.fit(durations=df['Tool wear [min]'],
event_observed=df['Target'])
# Visualize the survival curve
kmf.plot_survival_function()
plt.show()
# Print the survival probability for each data point
print(kmf.survival_function)
Kaplan-Meier 生存函数可视化(图像来源:作者)
# Getting the median survival time
print(kmf.median_survival_time_)
# Show the last 20 duration probability
kmf.survival_function_.tail(20)
Kaplan-Meier 估计器结果(图像来源:作者)
在某些情况下,我们可能还会对比较数据集中每个组的生存函数感兴趣,例如比较不同年龄组或产品类别的生存情况。进行这种比较的一种方法是可视化各组之间的 Kaplan-Meier 生存函数。
high_machine = df[df['Type'] == "H"]
medium_machine = df[df['Type'] == "M"]
low_machine = df[df['Type'] == "L"]
# Instantiate a KaplanMeierFitter object
kmf = KaplanMeierFitter()
# Fit kmf to high group
kmf.fit(durations=high_machine['Tool wear [min]'], event_observed=high_machine['Target'], label='H')
# Create a plot of the survival function
surv_plot = kmf.plot_survival_function()
# Fit kmf to other groups
kmf.fit(durations=medium_machine['Tool wear [min]'], event_observed=medium_machine['Target'], label='M')
kmf.plot_survival_function(ax=surv_plot)
kmf.fit(durations=low_machine['Tool wear [min]'], event_observed=low_machine['Target'], label='L')
kmf.plot_survival_function(ax=surv_plot)
# Visualize plot
plt.show()
各组之间的生存函数比较(图像来源:作者)
为了进行更准确的统计比较,我们可以使用对数秩检验对这些组之间的生存相似性进行统计测试。
-
我们使用零假设 (H0) 进行检验,假设各组之间的生存率相同。
-
我们检查统计测试的 p 值以检查在零假设为真的情况下数据出现的可能性
-
低 p 值 (p ≤ 0.05) 表明统计显著的测试结果,这意味着零假设应被拒绝
对数秩检验比较生存概率 Si 之间的差异
各时间点 t 的分组。
# Import logrank_test
from lifelines.statistics import logrank_test
# Run log-rank test to compare high and low category machines
test_results = logrank_test(durations_A = high_machine['Tool wear [min]'],
durations_B = low_machine['Tool wear [min]'],
event_observed_A = high_machine['Target'],
event_observed_B = low_machine['Target'])
# Print out the p-value of log-rank test results
print(test_results.p_value)
test_results.print_summary
对数秩检验结果。p 值结果为 < 0.05,这意味着这些组之间的生存率不相同(图像来源:作者)
Weibull 模型
上述 Kaplan-Meier 估计器和对数秩检验属于单变量分析,它将生存建模为单个分类因子变量的函数。对于分析多个变量对生存函数的影响,我们可以使用参数模型,如Weibull 模型。该模型基于 Weibull 分布的连续概率分布假设。
Weibull 模型的生存函数(图像来源:作者)
以下是使用 lifelines
库在 Python 中应用 Weibull 模型的示例。
from lifelines import WeibullFitter
# Instantiate WeibullFitter class
wb = WeibullFitter()
# Fit data
wb.fit(df_new['Tool wear [min]'], df_new['Target'])
# Plot survival function
wb.survival_function_.plot()
plt.show()
# Show Weibull model results
wb.summary
Weibull 模型结果(图像来源:作者)
如上所示,Weibull 模型生成平滑的生存曲线,而不是阶跃函数。上面的ρ(rho)值为大于 1,这表明该模型中的风险率始终在增加。
Cox 比例风险(CoxPH)模型
Cox 比例风险模型是可以评估不同因素对生存影响的模型之一。它在假设协变量对所有观测值有相同效果且变量之间没有未指定的交互作用的基础上运行。
Cox 比例风险(Cox PH)模型是一种回归模型,用于回归协变量与事件时间/持续时间。由于它是一个回归模型,你需要与观测相关的协变量(分类变量)列表来拟合模型,此外还有其他估计器中的事件和持续时间。临床研究中的协变量示例包括患者的年龄、体重、吸烟行为、是否遵循饮食、是否有某种过敏等。
from lifelines import CoxPHFitter
# Instantiate CoxPHFitter class cph
cph = CoxPHFitter()
# Fit cph to data
cph.fit(df=df_new, duration_col="Tool wear [min]", event_col="Target")
# Print model summary
cph.summary
CoxPH 模型结果(图像由作者提供)
从 CoxPH 模型结果中需要关注的关键变量是(1)exp(coef)
列和(2)p
列(p 值)。
-
exp(coef)
列显示了变量的风险比。 -
p
(p 值)表示哪些协变量对生存持续时间有显著影响。p 值较低(< 0.05)的预测变量是统计上显著的预测因子,用于确定生存持续时间。
[变量 X]从其中位值增加一个单位意味着风险因子变化为 e[coef(x)]。在上面的示例截图中,随着“旋转速度 [rpm]”从其中位值增加,机器的风险因子变化为 e[0.0079] = 1.008,这意味着相比于基线风险增加了 0.8%。
结束
生存分析是一种统计技术,用于分析事件发生的预期时间。虽然最初用于医疗领域,但其应用也可以扩展到各种用例,包括预测维护(预测设备故障)、客户分析(预测流失/购买时间)以及贷款建模(预测违约)。
生存分析可以使用几种技术,包括 Kaplan-Meier 估计器和 Weibull 模型来建模群体的生存函数,log-rank 检验来比较群体之间的生存时间,CoxPH 模型和加速失效时间(AFT)来描述分类或定量变量对生存的影响。每种模型都基于某些假设/分布,并可以使用其Akaike 信息准则(AIC)或qq 图进行比较,以确定最适合数据集的模型。
在本文中使用的脚本和分析探索可以在这个 GitHub 仓库中找到。
探索令牌概率作为过滤 GPT-3 答案的一种手段
原文:
towardsdatascience.com/exploring-token-probabilities-as-a-means-to-filter-gpt-3s-answers-3e7dfc9ca0c
为了构建更好的 GPT-3 驱动的聊天机器人
LucianoSphere (Luciano Abriata, PhD)
·发布于 Towards Data Science ·阅读时间 12 分钟·2023 年 1 月 19 日
–
GPT-3 为构成显示句子的每个令牌生成的对数概率,旨在测试系统。该图片由作者从一个用于执行这些测试的 Web 应用程序的截图中合成,链接在文章的末尾。
随着强大的语言模型越来越普及,对它们生成的内容进行控制的需求变得更加紧迫。这些模型在大量文本数据上进行训练,能够生成非常有说服力的书面内容,从新闻文章到社交媒体帖子。然而,如果没有适当的监督,它们也可能产生虚假信息或各种有害内容。因此,使用这些语言模型的应用程序必须尝试检查这些 AI 系统生成的信息的真实性,以防止传播虚假、误导或有害的信息。
像许多人一样,可能包括你自己,因为你正在阅读这篇文章,我已经大量使用了 GPT-3 和 ChatGPT,在这个过程中我发现它们经常以非常有说服力但却不正确的方式回答问题。事实上,当 GPT-3 发布时,我在进行深入探索,就像我对学生在科学学科中进行的考试一样:
[## GPT-3 的能力、限制和使用案例:从我的测试和原型应用中你可以复制的……
通过智能聊天机器人展示,它们甚至可以自然地听和说命令程序,或作为全职助手帮助学生……
最近,尽管我意识到这些限制,但我还是迫切开始创建由 GPT-3 驱动的聊天机器人。最初通过修补一些 PHP 库并编写相应的 JavaScript 代码,最近则完全使用 JavaScript:
## 用少于 20 行 JavaScript 代码构建类似 ChatGPT 的机器人核心代码!
不再像之前的示例那样使用 PHP。通过直接在 JavaScript 中使用现代 fetch() 函数,现在更容易…
在处理那个最后的项目,即从纯 JavaScript 调用 GPT-3 时,我深入探索了 OpenAI 的 GPT-3 API 参考,发现可以非常容易地检索到与语言模型生成的每个标记相关联的一系列分数。这些分数实际上是以对数形式表示的概率,由 GPT-3 一次生成一个标记,并与文本预测一起提供。这些概率衡量了 GPT-3 生成的不同标记的“概率”。虽然这些概率可能包含有关 GPT-3 对生成内容的确定性的信息,但这并非给定。因此,我决定通过一系列新的 JavaScript 应用程序进行动手研究,你可以在这些应用程序的基础上进行扩展。
更具体地说,我将探讨如何检索这些标记概率以及它们在我知道是正确或错误的文本中的取值。我还将探讨少量学习对这些分数的影响,以了解它是否确实使 GPT-3 对其答案更有信心。
GPT-3 和标记对数概率
如果你正在阅读这篇文章,GPT-3 可能无需介绍。但如果需要介绍,GPT-3 代表生成预训练变换器(目前是第 3 版,但实际上由多个不同版本的模型组成),它是一个先进的语言模型,根据输入生成书面内容。
当你将一些文本输入到 GPT-3 中(称为“提示”)时,它会被拆分为所谓的标记,这些标记是从单个字母到音节甚至单词的可变大小的单位(根据各种因素)。这些标记在网络中传播,结果合成出新的标记,这些标记一起形成新的单词、句子和段落。这些文本通常具有意义和相当好的语法,除非你处理的是 GPT-3 在训练中很少见的异域语言。然而,它们的内容不一定准确,特别是如果你期望它“思考”某个问题或在概念之间建立关系,或者如果你询问模型在训练期间未见过的事物(例如,它不会知道我是谁,所以它可能会编造一些东西,见下例)。
GPT-3 和其他大型语言模型的一个重要特性是它们是“少样本学习者”,这意味着它们可以处理并“理解”在提示中传递的一些信息,然后可能基于这些信息回答问题或执行任务。arXiv 上有一篇完整的预印本解释了这一点;我也有几个示例项目利用了这一特性:
最近的工作表明,通过在大规模语料库上进行预训练,许多自然语言处理任务和基准测试取得了显著的进展…
arxiv.org [## 为什么你应该以及如何用自定义数据或维基百科访问来告知你的聊天机器人
将我完全基于网络的、由 GPT-3 驱动的聊天机器人扩展为能够了解我提供的内容或自动检索的内容…
现在,我在这里讨论的 GPT-3 的一个特性(尽管其重要性巨大,但在互联网上讨论不多)是,除了返回文本之外,GPT-3 还可以返回与构成生成文本的每个标记相关的概率。这些概率实际上以对数形式返回,测量每个标记在输出文本上下文中出现的可能性。较低的对数概率表示不太可能的词,较高的对数概率表示更可能的词。
根据 ChatGPT 自身的说法,GPT-3 使用这些对数概率来生成连贯且语法正确的文本;此外,它使用对数概率来生成下一个词,基于最有可能的下一个词,从而生成语境上准确的文本。我将在这里调查这是否也包含有关内容准确性的信息。剧透:是的,至少有一点;此外,少量示例学习不仅改善了回答本身,还改善了对数概率。
调用 GPT-3 的 API 时获取对数概率
需要注意的是,GPT-3 和其他语言模型一样,无法区分真实和虚假的信息。它只是基于从训练数据中学到的模式和提示中提供的少量示例生成文本。
对数概率原则上可以帮助检测不正确的信息;但我们怎么获取它们呢?
这是我最近展示的代码的一个小修改,完全用 JavaScript 调用 GPT-3 的 API,并修改为获取标记对数概率:
// Your OpenAI API key
const apiKey = “(your API key here)”;
fetch(
`https://api.openai.com/v1/completions`,
{
body: JSON.stringify({
“model”: “text-davinci-003”,
“prompt”: “Where is Luciano Abriata from?”,
“temperature”: 0,
“max_tokens”: 20,
“logprobs”: 1}), //Note we request for logprobs
method: “POST”,
headers: {
“content-type”: “application/json”,
Authorization: “Bearer ” + apiKey,
},
}
).then((response) => {
if (response.ok) {
response.json().then((json) => {
console.log(json);
});
}
});
fetch()调用包括调用 GPT-3 并获取文本和标记概率所需的一切。提示包括少量示例学习的信息,放在问题“Luciano Abriata 来自哪里?”之前。
测试不同场景下的标记概率
让我们看看如果我们用一个只包含问题“Luciano Abriata 来自哪里?”的提示调用上述函数,会发生什么,也就是说,没有任何解释我来自阿根廷的辅助信息。
我们期望 GPT-3 不会知道我来自哪里,因为我不是名人。事实上,它“编造”了我来自意大利:
(有趣的事实:这并不太偏差,因为我的祖先都是意大利人……在这里,GPT-3 可能基于我的名字做出了猜测……但不,我在阿根廷出生和长大。)
现在,我们在控制台日志中看到什么?除了输出文本本身,还有很多有趣的输出。让我们分析一下最重要的元素:
首先,你会看到包含输出的对象text:Luciano Abriata is from Italy。
但在text的几行上方,你会看到一个包含构成该文本的标记的数组。在这个数组的几行上方,你会看到token_logprobs,这是一个相同大小的数组,列出了每个标记的对数概率。
你可以看到token_logprobs在第 9 个标记“意大利”处达到了-0.49 的最低值,而其他所有标记都非常接近 0(除了结尾处的标记也为负值,但我们不关心这些结尾标记)。
这原则上是好消息,因为这意味着 GPT-3 提供了一个线索,表明这个信息可能是错误的,或者是“编造”的。不过,我们不要急于得出结论,还是进一步探索一下。
如果我们在提示中提供一些信息,然后询问相关内容会怎样?比如这样:
Luciano Abriata 是一位出生在阿根廷的科学家,目前在瑞士工作。他从事结构生物学、虚拟现实、核磁共振、科学写作、编程等工作。Luciano Abriata 是哪里人?
// Your OpenAI API key
const apiKey = “(your API key here)”;
fetch(
`https://api.openai.com/v1/completions`,
{
body: JSON.stringify({
“model”: “text-davinci-003”,
“prompt”: “Luciano Abriata is a scientist from Argentina, now working
in Switzerland. He works on structural biology, virtual
reality for chemistry, NMR, etc.
Where is Luciano Abriata from?”,
“temperature”: 0,
“max_tokens”: 20,
“logprobs”: 1}), //Note we request for logprobs
method: “POST”,
headers: {
“content-type”: “application/json”,
Authorization: “Bearer ” + apiKey,
},
}
).then((response) => {
if (response.ok) {
response.json().then((json) => {
console.log(json);
});
}
});
在这种情况下,GPT-3 不仅正确回答了我来自阿根廷,还自信地说:“阿根廷”,在标记 9 中,其对数概率非常接近 0:
更全面的测试是通过在生成的文本上用颜色表示标记概率
为了测试对潜在不准确信息标记的对数概率的威力,我编写了这个简单的网页应用程序(链接在文末附近),它处理一个带有 GPT-3 的提示,并显示每个标记按其对数概率上色的生成文本:
在这个应用程序中,你可以通过我在下面提供的链接进行尝试,每个标记的颜色键是通过 HTML 的 标签注入的,如下所示:
-
对数概率 > -0.1 → 绿色
-
-0.3 > 对数概率 > -0.1 → 黄色
-
-0.5 > 对数概率 > -0.3 → 橙色
-
对数概率 < -0.5 → 红色
让我们稍微分析一下:问题是关于我虚构的一个人,而 GPT-3 只是回答了,而不是说它不知道,尽管温度是 0,所以不应该虚构内容……显然,不能依赖温度参数来防止生成虚假信息。
现在,请注意对于这个虚构角色(他是意大利人,是哲学家和哲学教授,在罗马大学拉萨比恩扎)的 4 个最重要特征,有些被强烈标记:
“哲学家和哲学教授”的对数概率平均值约为 -1,而“罗马 La”的对数概率约为 -1.5。
同时,“Sapienza”可能保持未被标记,即对数概率较高,因为它是“罗马大学 La”的完美延续。同样,“意大利人”可能保持高概率,因为它紧跟在“Giulio”之后,“Giulio”是一个非常意大利的名字。
因此,低对数概率似乎指示潜在的不准确信息,但高值并不能确保事实准确性。
低对数概率似乎指示潜在的不准确信息,但高值并不能确保事实准确性。
现在我们尝试将“Giulio”改为“John”,即询问“John Caranchani 是谁?”:
这次它虚构了这个角色是意大利裔美国人,并且以相当差的分数标记了这一信息。
再做一次测试,使用典型的法国名字“Philippe”:
现在它虚构了这个角色是法国人,并用橙色标记了这一点。
现在我们来询问一些不同的问题:化学。醋酸的分子式是什么?
我们得到了一个正确的答案,所有的标记都是绿色,表明分数非常好。
如果我们虚构一个分子呢?例如 Bizarric 酸:
看起来它确实“意识到”它在编造东西。当然,我们更希望 GPT-3 回答“我不知道”或“Bizarric acid 不存在”。但一个非常糟糕的分数总比没有好。
传递信息进行少量学习的效果
正如之前讨论过的以及在许多文章中提到的,GPT-3 可以从传递的文本片段中提取信息进行少量学习,从而更准确地回答问题——至少根据传递的信息来说。
这在令牌概率中是如何体现的?
让我们看看一个例子。我虚构了一个名叫 Stephan Farconi 的人,想知道他来自哪里以及他做什么:
正如预期的那样,一切都是虚构的,并且标记了差分数。
现在让我们给 GPT-3 一些关于这个人的信息,再次提问:
答案现在与传递的信息在事实上一致,并且 GPT-3 将一部分(“希腊”)标记为确定。然而,它对“计算机科学家”不太确定。请注意,“科学家”是“计算机”的良好续接,所以在我看来 GPT-3 实际上对整个“计算机科学家”概念是“没有把握”的。
想试试这个应用吗?
在这里:
https://lucianoabriata.altervista.org/tests/gpt-3/js-only/GPT3-JSonly-logprobs-color.html
只需粘贴你的 OpenAI API 密钥用于 GPT-3,输入一个问题或提示,然后点击“发送给机器人”。
请在评论中分享你的结果!令牌概率是否反映了生成文本的实际准确性?
结论
在聊天机器人应用中使用 GPT-3 及类似语言模型可能会在生成准确可靠的信息方面带来重大挑战。虽然新方法有望提高真实性,但目前的一个选项是利用令牌概率来衡量 GPT-3 对每个构成其输出的令牌的“确定性”。我向你展示了使用这些信息可以近似估计 GPT-3 在其生成文本中的信心水平,但这并不完全可靠。
从测试的几个例子来看,所有令牌的高分似乎表明信息是准确的,但某些令牌的低分并不一定意味着信息是错误的。当然,这仅仅是个案证据,需要更大规模的研究来认真评估。这可能是一个有趣的项目,一个研究 AI 的团队可以相对容易地进行,只需要足够多的人与系统互动,提出问题并评估答案。(此外,请注意,我选择解释日志概率的阈值是有些随意的。)
我还向你展示了少样本学习不仅能提高回答的准确性,特别是对于 GPT-3“不知道”的问题,还能提高其可靠性,这通过令牌概率来衡量。然而,我们也看到,通过少样本学习提供的信息并不能保证回答中的高分,即使这些回答中的信息是正确的。
总体来看,令牌概率看起来很有前景,但即使没有全面评估,也很明显它们是易出错的。因此,谨慎使用 GPT-3 是至关重要的,可能利用令牌概率来改进输出,但不要过于依赖它们。
www.lucianoabriata.com 我撰写和拍摄的内容涵盖了我广泛兴趣领域中的一切:自然、科学、技术、编程等。 成为 Medium 会员 以访问所有故事(平台的关联链接,我会获得少量收入,不会增加你的费用)以及 订阅以获取我的新故事 通过电子邮件*。如需* 咨询小型工作, 请查看我的 服务页面。你可以 在这里联系我。
探索什么让 AI 伦理工具包运转起来
AI 伦理工具包随处可见,但我们真的理解它们吗?
·
关注 发表在 Towards Data Science ·8 分钟阅读·2023 年 9 月 22 日
–
图片由Todd Quackenbush拍摄,来自Unsplash — 是时候拆解 AI 伦理工具包了
介绍
随着 AI 系统在具有重要影响的应用中的使用持续增加,专家们呼吁在设计这些系统时采取更多参与性和价值意识的做法。增加利益相关者参与可以为 AI 系统设计带来许多好处,包括使其更加包容、对抗现有偏见和提供问责制。对此,AI 伦理领域近年来产生了大量工具包。这些工具包来自不同的来源,如大学、公司和监管机构,采用了几种不同的技术和框架,并针对的数据科学家、AI 工程师、设计师和公众等不同受众(Wong 等人,2023)。
许多 AI 伦理工具包仅关注与 AI 创建相关的技术方面,主要面向技术从业者(例如,FAIR 自我评估工具)。然而,也有一些工具包主张并专注于利益相关者的参与,特别是 AI 创建团队之外的利益相关者,如最终用户和领域专家(例如,AI 与设计工具包)。那些关注利益相关者参与的工具包通常通过提供画布或卡片组等资源,使利益相关者,尤其是非技术背景的人,能够参与设计活动。这种参与可以导致从头脑风暴不同决策的后果到与 AI 用户建立同理心的各种结果。
尽管存在大量工具包,但尚未有人真正尝试理解它们的工作原理或其潜在策略。这使得尽管这些工具包被广泛使用,但是否对工具包用户及其使用工具包产生的结果有积极影响仍不明确。因此,我想了解一个工具包的具体运作方式;结果证明这是一次非常有教育意义的经历。
由 Nadia Piet 创建的‘AI 与设计’工具包的一部分,来自aixdesign.co/shop
。
我做了什么
我举办了一个设计研讨会,9 位参与者以不同的角色与人工智能合作。研讨会包括一个创意活动,旨在头脑风暴针对一个虚构的对话式人工智能的应用场景和设计特性,以帮助人们自我管理糖尿病。它包含了一个由个人设计师而非公司或大学制作的AI 伦理工具包。我故意选择了这个工具包,以深入探讨一个不依赖于大量资金的小型工具包的基本运作机制。
这个研讨会在 Miro 上进行,持续了两个小时。工具包的工作方式是由一副卡片组成。这些卡片每张都有一个值(例如隐私、自主、安全等)和一些 How Might We?问题,这些问题作为提示,激发出不同的想法,以便在生产的技术中尊重给定的价值。我在 Miro 板上布置了这些卡片,并留出了便签供人们进行头脑风暴,然后我给每个人分配了两个需要单独关注的价值。
一个小时后,我们大家聚集在一起,我翻转了卡片,这样人们就无法再看到卡片的值,只能看到人们写在便签上的想法。我让每个人展示他们写下的想法,其他人则需要猜测他们希望尊重的价值。
来自研讨会 Miro 板的截图,显示了“诚实”卡片以及参与者围绕它进行头脑风暴的想法。
我学到了什么
将价值观作为跳板来建立同理心和更广泛的考虑
该工具包旨在使用他们提供的价值列表作为构思和头脑风暴的跳板。3 位参与者提到这是一个非常有用的方法:
“看到不同的价值观如何适用于开发对话代理的过程非常有趣。”
“从单一价值观的角度思考设计。”
“查看价值卡片并围绕这些卡片产生想法。”
-
相较于专注于技术可行性,更加关注用户的价值观和不同想法的重要性。参与者似乎喜欢/更愿意这种方法,因为它提供了一个与关注技术细节和忽视如安全和公平等价值观的方式不同的变革。一位参与者表示,他们认为这些价值观在考虑系统及其建设和技术背景时可能不会出现,或在考虑什么是最简单或最快的做法时。他们说,尽管人们在设计时应考虑这些价值观,但现实中并非总是如此,因为其他优先事项会遮盖这些价值观。总之, 突出价值观帮助参与者进行头脑风暴并考虑通常被忽视的非技术方面。
-
这个练习让参与者希望了解更多关于糖尿病患者生活及其经历的信息,以理解如何以最定制和具体的方式支持他们,并提出实际相关的解决方案,而不是基于假设。这种欲望的触发值是‘宁静’(导致希望了解造成压力和焦虑的情况)和‘安全’(导致希望了解他们面临的生命威胁情况)。总之,关注价值观增强了参与者对目标用户的(i)同理心和(ii)了解如何最好地支持他们的好奇心/愿望。
照片由Miltiadis Fragkidis拍摄,来源于Unsplash — 与利益相关者的价值观合作可以促进更深层次的同理心和理解
使用游戏化来增加参与度并改善结果
参与者在猜测值时非常享受游戏化的元素:
“我喜欢猜测创意相关的值——这让我比起只是阅读/讨论这些创意时更能投入其中。”
“我觉得猜测值的部分最有用,它让我真正理解了不同的价值观以及它们之间的相互关系。”
- 在猜测值时,参与者觉得几个创意可能对应不同的值,不同值下的创意有时指的是相同的概念或建议。讨论也突出了几个值之间过于相似或有大量重叠,导致很难区分它们。— 很多值被认为过于相似或重叠,参与者难以猜测或区分它们。不同值之间有很多关联,有些值可以导致或促进其他值,或体现类似的现象/情感。在猜测过程中,“公平”被误认为“包容性”;“社区”、“自由”和“自主”无法区分;“掌握”被混淆为“勇气”和“好奇心”;“包容性”被误认为“无障碍”和“尊重”。总之,游戏化使参与者能够真正理解创意和价值观之间的相互关系。
照片由Erik Mclean拍摄,来源于Unsplash — 游戏化具有许多好处。在 AI 协作设计的背景下,它可以帮助参与者掌握相互关系,并增加参与感。
未来改进和想法
-
三位参与者指出,他们希望能够获得更多的背景信息和提示,以便更好地与对话式人工智能的目标利益相关者产生共鸣。对于诸如“安全”和“宁静”这样的具体值,参与者希望了解更多关于用户的体验,以便更好地为他们提供针对性的解决方案,特别是解决他们的需求。一位参与者觉得最初的提示缺乏来自他们设计对象(糖尿病患者)的反馈,因此难以为他们真正头脑风暴出创意。他们希望能获得更多关于用户场景的定制/具体信息,包括他们生活中的片段描述或实际的背景信息,以便能更好地定制生成的创意。
-
一项建议是进行这样的工作坊,首先专注于抽象的高层次内容,不带太多事前信息,然后再去收集用户信息以提供背景。因为如果你先收集用户信息,你可能会错过一些观点或价值观,并过于关注你所收集的内容。之后,你可以将这些信息整合在一起,再次进行头脑风暴,探讨场景和技术方面,同时受益于两种知识来源。
-
两位参与者指出,如果他们没有带有定义的值列表,他们可能会在想出描述这些值的确切词语时遇到困难,可能会想到类似的词汇,但不是确切的词语。如果没有提供值的描述,他们也会难以理解这些值的含义。
这如何能帮助你
对 AI 伦理工具包的有效性的实验让我对使用游戏化和基于价值的练习的力量有了更深刻的理解。以下是我将这些见解提炼成的六点,这些可以帮助你与利益相关者一起使用 AI 伦理工具包进行设计工作坊,以便进行头脑风暴或构思 AI 设计:
-
合作思考 AI 伦理的主要点之一是让 AI 更具人性化。因此,包括大量的背景(最好是第一手)信息关于你的目标用户和你为谁设计是非常重要的。记住,许多技术从业者可能不习惯于建立同理心的练习和构思,所以通过真正明确他们试图帮助的人来帮助他们实现这一点。
-
考虑你的工作坊是否需要许多活动从高层次的相关性或重要性逐步深入到细节。混合这两者可能会让参与者感到困惑,尤其是当你要求他们对可能无法在同一层面进行比较的观点进行排名或评分时。
-
如果你正在处理值,请确保你清楚定义每个值的含义。这很重要,因为不同的值可能根据询问的人不同而含义不同,如果没有清晰定义,它们之间可能会有很多冲突和重叠。
-
将值融入你的 AI 设计活动可以帮助参与者集思广益,并考虑常被忽视的非技术性方面,帮助他们与目标用户建立同理心,并增加他们的好奇心和更好地支持这些用户的愿望。
-
结合游戏化技术可以帮助提高参与者的投入感和享受感,在 AI 伦理工作坊中,这也可以帮助他们更深入地把握观点之间的联系。
我的角色
我的博士项目旨在利用设计领域的工具和技术,使 AI 系统的设计变得更具可访问性和包容性。我正在致力于创建一个参与式过程及其支持工具包,以系统性地在整个 AI 生命周期中涉及人们——重点关注价值敏感性。
你可以在帝国理工学院官网查看我项目的官方页面。你也可以查看我撰写的另一篇文章,解释了我博士项目的详细信息。
我创建了这个 Medium 账号,以便在我进行博士项目的过程中发布有趣的发现,希望能够以一种让任何人都能理解的方式传播关于 AI 系统的新闻和信息。
参考文献
Richmond Y. Wong、Michael A. Madaio 和 Nick Merrill. 2023. 《像工具包一样看待:工具包如何构想 AI 伦理的工作》。Proc. ACM Hum.-Comput. Interact. 7, CSCW1, Article 145 (2023 年 4 月),共 27 页。 doi.org/10.1145/3579621
通过 HTTP 安全地暴露 Kubernetes 卷:如何在互联网上服务 PVC
创建 Kubernetes 清单以暴露 PersistentVolumeClaims
·
关注 发表在 Towards Data Science · 7 min read · 2023 年 2 月 8 日
–
图片来源:Uriel Soberanes 在 Unsplash
介绍
你可能在日常的产品开发中遇到过这样一种情况,需要获取 Kubernetes 集群中某些持久化文件。一种常见且安全的方法是进行端口转发,无论是通过 Kubectl 还是使用堡垒主机进行纯 SSH。
无论哪种情况,完成任务后,你会终止会话,并且每次未来的交互中,你都需要重复相同的手动过程。
从安全角度看,理想情况下,你的环境应该尽可能密封,不给对手任何机会,这也是保持这种状态的合理原因。
但是,如果你想要长期在互联网上暴露底层存储,这篇文章就是为你准备的。
首先:认证
由于该文件服务器将公开暴露在互联网上,你的首要和最重要的防线是认证层。为了阐明这一点,有必要提供认证的正式定义。
认证是证明一个主张的行为,例如计算机系统用户的身份。[source]
通俗来说,认证发生在系统用户证明他是他所声称的人的时候!
既然我们已经清楚了这一点,让我们深入探讨一些将认证集成到我们的 web 服务器中的选项(见下文)。
-
使用 Nginx 或 Apache 作为代理,借助
htpasswd
,这是一个Apache 工具,它允许在文件中存储加密的用户名-密码对,之后可以用来验证给定的密码。 -
Ory Oathkeeper 作为代理,借助 Kratos,这是 Ory 的另一个产品,作为身份提供者。这比早期的方法稍微复杂一些,需要一些学习曲线来掌握配置和这些工具的提供。我会在稍后的文章中覆盖这一点,所以请继续关注!😉
当然,你可以将更多内容添加到这个列表中,但为了保持本文简洁,老实说,因为我对其他解决方案不太了解,我暂时就以上两个项目满足要求。
另一个我要提到的点是,由于这篇文章涉及到互联网的暴露,我这里不谈论私人网络解决方案。不过,你可以想象这也会是一个安全的选项。
既然我们知道 Ory 的产品不是最容易配置的,而且作者也不是认证专家 😁,让我们保持简单并采用第一种方法。
创建 htpasswd
文件
htpasswd
是一个相当简单的工具,用于将基本认证机制应用到任何平台。它通过接收用户名和密码作为输入来工作。结果将是一个单向哈希密码,存储在文件或标准输出中,随后可以用来验证用户凭证。然而,它不能在合理的时间内被还原(解哈希)为原始密码,至少在 2023 年,考虑到我们当前的计算能力!
要进行简单演示,请查看下面的片段。
这将仅为用户创建一个新文件,并尝试用正确的密码和错误的密码进行验证。
我们将在我们的“安全文件服务器”中使用相同的设置,公开暴露到互联网。
反向代理
除非你想在文件服务器层处理认证(我知道我不会),你将使用反向代理来位于前面,接收所有流量,并拒绝所有凭证错误的请求。你甚至可以添加其他限制措施,包括但不限于速率限制、日志记录、仪表化、报告等。
Apache 和 Nginx 都可以使用htpasswd
生成的文件来验证凭证。有关每个的更多信息,请参见以下链接:
我相信其他 Web 服务器也可以,做同样的事情。
在这篇文章中,我将使用 Nginx,并且由于这将托管在 Kubernetes 中,它将是一个 Nginx 的 docker 容器。这允许我将任意数量的配置文件挂载到/etc/nginx/conf.d
目录中,Nginx Web 服务器进程会拾取这些配置文件。
因此,如果我可以将任何配置文件挂载到目录中,我可以在Kubernetes ConfigMap中编写配置文件,并将其挂载到容器的目标目录。这既强大又相当灵活。
这是我即将挂载到 Nginx 容器中的配置。
配置文件中名为proxy_pass
的条目指向将通过 HTTP 协议暴露文件系统目录的文件服务器。更多内容请见下一节。
照片由 Carl Barcelo 提供,来源于 Unsplash
通过 HTTP 提供文件
在许多其他静态 web 服务器中,只提到其中的一些。
-
Python 模块:
[http.server](https://docs.python.org/3/library/http.server.html)
-
Npm:
[serve](https://www.npmjs.com/package/serve/v/14.2.0)
当然,这个列表可以更多,但我们试图保持简短和信息丰富。😇
在本文中,我将使用 Python 的内置模块:http.server
。它具有直接和直观的接口,使得使用非常简单。
使用它提供静态内容的方式如下:
ADDRESS=0.0.0.0 PORT=8000 DIRECTORY=/tmp
python -m http.server -b $ADDRESS -d $DIRECTORY $PORT
这效果很好,特别是因为你不需要做很多复杂的操作来使它工作。
运行并可访问的 web 服务器从 Nginx 容器中意味着你可以将 PersistentVolumeClaims 挂载到静态 web 服务器上,并将上述 Nginx 放置在前面,以防止未认证的访问你的 Kubernetes 集群中的宝贵数据。
挂载 Kubernetes ConfigMap 为 Volume
在我们将所有内容汇总为一个统一的清单之前,还有一个最后的关键信息在这种方法中使用,需要一点解释。但如果你已经是 Kubernetes 中如何将 ConfigMap 挂载为容器 Volume 的大师,可以跳过这一部分。
要将 Kubernetes ConfigMap 挂载为 Volume,你可以在容器定义的 volumes 部分使用 projection,如下所示 [source]:
在 containers
相同级别下,定义了 volumes
,它可以接受几种 volume 类型,其中之一是 ConfigMap。这允许定义一些脚本并将其作为 volume 传递给运行中的容器。
创建上述清单后,以下是容器日志中显示的内容。
kubectl logs job/demo -c demo
total 0
drwxr-xr-x 2 root root 45 Feb 4 06:58 ..2023_02_04_06_58_34.2149416564
lrwxrwxrwx 1 root root 32 Feb 4 06:58 ..data -> ..2023_02_04_06_58_34.2149416564
lrwxrwxrwx 1 root root 21 Feb 4 06:58 favorite-color -> ..data/favorite-color
lrwxrwxrwx 1 root root 16 Feb 4 06:58 names.txt -> ..data/names.txt
red
Alex
Jane
Sam
结合所有内容
现在我们已经逐步了解了所有信息,是时候将它们结合在一起,作为一个统一的清单,用于一个唯一的目的:一个 HTTP 文件服务器。
第二个文件,一个 Ingress 资源,是可选的,但仍然包含在内,因为这篇文章是关于公开暴露 HTTP 静态网页服务器的。只有创建 Ingress 才能获得互联网曝光。
为了增加另一层安全措施,你可以为子域分配一个 UUID 生成的值,以避免仅使用用户名和密码作为唯一的安全措施。它可以是这样的:
415cb00c-5310-4877-8e28-34b05cadc99d.example.com
否则,你的安全性仅与用户名和密码相同,如果你的品牌暴露了你分配的用户名,那么你的安全性就仅仅取决于你的密码,而这与你希望的安全状态不同!
此外,请记得你需要 HTTPS。你绝不希望有陌生人窃听你的连接,监视你通过互联网传输宝贵的客户数据。
图片由 Haley Phelps 提供,来自 Unsplash
结论
由于我们讨论的是 Kubernetes,这与任何云服务提供商无关,你只需在任何 Kubernetes 集群中应用这个做法。
这将有效地意味着你可以安全地将动态配置的持久卷暴露到互联网上!
对于“安全”部分,需持保留态度,因为这并不是最安全的选择,但如果你给你的 Ingress 的子域名分配一个随机字符串,你仍然可以保护自己。这样,攻击者将不得不在互联网上找到多个组合中的 URL,这将需要很多年。到那时,我们可能都已经离开了!
祝你今天过得愉快。保持关注,保重!
致谢
希望你觉得这篇文章对你有帮助。以下是一些你可能会喜欢的我以前的工作列表。
## 如何在 AWS EKS 中设置 Ingress Controller
在 AWS EKS 上以正确的方式部署 Ingress Controller
towardsdatascience.com [## 什么是 HAProxy 及如何充分利用它?
负载均衡、SSL/TLS、缓存等等。
meysam.io](https://meysam.io/what-is-haproxy-how-to-get-the-most-from-of-it-a9009b67f618?source=post_page-----67f10f448693--------------------------------) ## 如何编写自己的 GitHub Action
用 GitHub Workflows 完善你的 CI/CD 工具箱。
towardsdatascience.com [## 12-Factor App 入门
12-Factor App 是一组应用于现代 Web 开发的原则和最佳实践,以使应用程序更加……
medium.com](https://medium.com/licenseware/12-factor-app-for-dummies-d905d894d9f8?source=post_page-----67f10f448693--------------------------------) [## 停止将配置提交到源代码中
通过更改配置使应用程序在不同环境中可复现。
medium.com](https://medium.com/licenseware/stop-committing-configurations-to-your-source-code-fb37be351492?source=post_page-----67f10f448693--------------------------------)