使用 LLM 来评估 LLM
·
关注 发布于 Towards Data Science ·7 分钟阅读·2023 年 11 月 10 日
–
图片由 OpenAI 的 DALL-E 3 生成。
你可以要求 ChatGPT 以百万种不同的方式进行工作:作为你的营养师、语言导师、医生等。不奇怪我们看到很多基于 OpenAI API 的演示和产品发布。但虽然让 LLM 以某种方式运作很容易,确保它们表现良好并准确完成给定任务则是完全不同的故事。
问题在于我们关注的许多标准都是极其主观的。回答是否准确?回答是否连贯?有没有虚假信息?建立可量化的评估指标很困难。通常,你需要人工判断,但让人类检查大量 LLM 输出是非常昂贵的。
此外,LLM 具有许多可以调整的参数,如提示、温度、上下文等。你可以在特定数据集上微调模型以适应你的使用案例。通过提示工程,即使是让模型深呼吸 [1] 或让你的请求更具情感色彩 [2] 也能改善性能。有很大的调整和实验空间,但在你更改某些内容后,你需要能够判断系统整体上是变得更好还是更差。
由于人工劳动缓慢且昂贵,强烈的动力驱使我们寻找这些更主观标准的自动化指标。一种有趣的方法,正在获得越来越多的关注,是使用 LLM 来评估 LLM 的输出。毕竟,如果 ChatGPT 能生成一个良好的、连贯的回应,它是否也能判断给定的文本是否连贯呢?这打开了一整盒潜在的偏见、技术和机会,让我们深入探讨一下。
LLM 偏见
如果你对使用 LLM 构建指标和评估器有负面的直觉反应,你的担忧是有根据的。这可能只是延续现有偏见的糟糕方式。
例如,在稍后我们将详细讨论的 G-Eval 论文中,研究人员展示了他们基于 LLM 的评估给予 GPT-3.5 摘要的分数高于人类编写的摘要,即使人类评审更喜欢人类编写的摘要。
另一项研究题为 “大型语言模型不是公平的评估者” [3],发现当被要求选择两个选项中哪个更好时,选项呈现的顺序存在显著偏差。例如,GPT-4 通常更喜欢第一个给出的选项,而 ChatGPT 更喜欢第二个选项。你可以通过将问题的顺序颠倒过来提问,看看 LLM 在回答中的一致性如何。他们随后开发了通过用不同顺序的选项多次运行 LLM 来缓解这种偏差的技术。
评估评估器
到头来,我们想知道 LLM 是否能像人类评估员一样好或相似地表现。我们仍然可以将其作为科学问题来处理:
-
设置评估标准。
-
让人类和 LLM 根据标准进行评估。
-
计算人类和 LLM 评估之间的相关性。
通过这种方式,我们可以了解 LLM 与人类评估员的相似程度。
确实,已经有几个类似的研究表明,对于某些任务,LLM 的表现比传统的评价指标要好得多。值得注意的是,我们不需要完美的相关性。如果我们在多个示例上进行评估,即使评估不完美,我们仍然可以得到新系统表现更好还是更差的某种概念。我们还可以使用 LLM 评估器来标记令人担忧的边缘案例,以便人类评估员进一步检查。
让我们来看看一些最近提出的依赖于 LLM 的核心指标和评估器。
G-Eval
G-Eval [4] 的工作方式是首先概述评估标准,然后简单地要求模型给出评分。它可以用于摘要和对话生成任务,例如。
G-Eval 包含以下组件:
-
提示。 定义了评估任务及其标准。
-
中间指令。 概述了评估的中间指令。它们实际上要求 LLM 生成这些步骤。
-
评分函数。 我们不会直接采纳 LLM 的评分,而是查看底层的标记概率以获得最终得分。因此,如果你要求在 1 到 5 之间评分,我们不会仅仅采用 LLM 给出的数字(例如“3”),而是会查看每个等级的概率并计算加权得分。这是因为研究人员发现通常一个数字主导了评估(例如主要输出 3),即使你要求 LLM 给出一个小数值,它仍然倾向于返回整数。
G-Eval 提供的提示用于在 1 到 5 的范围内计算连贯性。你可以在原始论文中找到更多示例。
研究发现 G-Eval 在显著程度上超越了传统的基于参考的指标,如 BLEU 和 ROUGE,这些指标与人工判断的相关性相对较低。表面上看,这似乎非常简单,因为我们只是要求 LLM 执行评估。我们也可以尝试将任务分解为更小的组件。
FactScore
FactScore(原子性得分中的事实精确度)[5] 是一个用于事实精确度的指标。这里的两个关键概念是将原子事实视为一个单元,并基于特定的知识来源来评估可信度。
在评估中,你将生成内容分解为小的“原子”事实(例如“他出生在纽约”),然后检查每个事实是否得到给定真实知识来源的支持。最终得分是通过将被支持的事实数量除以总事实数量来计算的。
在论文中,研究人员让 LLM 生成人物传记,然后使用关于他们的维基百科文章作为真实来源。LLM 进行与人类相同程序的错误率低于 2%。
FactScore 用于生成布里奇特·莫伊纳汉的传记。请参阅原始论文。
RAGAS
现在,让我们来看看一些检索增强生成(RAG)的指标。使用 RAG 时,你首先在外部知识库中检索相关的上下文,然后让 LLM 根据这些事实回答问题。
RAGAS(检索增强生成评估)[6]是一个用于评估 RAG 的新框架。它不是一个单一的指标,而是一系列指标的集合。论文中提出的三个指标是忠实度、回答相关性和上下文相关性。这些指标完美地展示了如何将评估分解为 LLMs 的简单任务。
忠实度衡量回答在给定上下文中的扎实程度。这与 FactScore 非常相似,你首先将生成的内容分解为一系列陈述,然后询问 LLM 这些陈述是否得到给定上下文的支持。得分是支持的陈述数除以所有陈述的总数。对于忠实度,研究人员发现与人工标注者有很高的相关性。
回答相关性试图捕捉回答是否针对实际问题的概念。你可以先要求 LLM 根据答案生成问题。对于每个生成的问题,你可以计算生成的问题与原始问题之间的相似度(通过创建嵌入并使用余弦相似度)。通过这样做n次并计算相似度得分的平均值,你将得到最终的回答相关性值。
上下文相关性指的是提供的上下文的相关性。也就是说,提供的上下文仅包含回答问题所需的信息。在理想情况下,我们只向 LLM 提供回答问题所需的正确信息。上下文相关性是通过要求 LLM 提取上下文中与答案相关的句子来计算的。然后只需将相关句子的数量除以总句子数以获得最终得分。
你可以在这里找到更多的指标和解释(以及开源的 GitHub 仓库)。
关键点是我们可以将评估转化为更小的子问题。我们不是询问整个文本是否得到上下文支持,而是询问一个小的具体事实是否得到上下文支持。我们不是直接给出答案是否相关的数字,而是要求 LLM 为给定的答案想出一个问题。
结论
评估 LLMs 是一个极具趣味的研究课题,随着越来越多的系统进入生产阶段并应用于更多安全关键的环境,这一话题将受到越来越多的关注。
我们还可以使用这些指标来监控 LLMs 在生产中的表现,以便发现输出质量是否开始下降。特别是在错误成本高的应用场景中,如医疗保健,开发保护措施和系统以捕捉和减少错误将是至关重要的。
尽管使用 LLM 作为评估器时肯定存在偏见和问题,我们仍应以研究问题的态度保持开放的心态。当然,人类仍将参与评估过程,但在某些场景中,自动化指标可以部分评估性能。
这些指标不必总是完美的;它们只需要足够好以正确指导产品的开发。
特别感谢 Daniel Raff 和 Yevhen Petyak 的反馈和建议。
最初发表于 Medplexity substack。
-
杨成润等人。大语言模型作为优化器。arXiv,2023 年 9 月 6 日。arXiv.org,
doi.org/10.48550/arXiv.2309.03409.
-
李成等人。大语言模型理解并能通过情感刺激进行增强。arXiv,2023 年 11 月 5 日。arXiv.org,
doi.org/10.48550/arXiv.2307.11760.
-
王培毅等人。大语言模型并非公平的评估器。arXiv,2023 年 8 月 30 日。arXiv.org,
doi.org/10.48550/arXiv.2305.17926.
-
刘洋等人。G-Eval: 使用 GPT-4 进行更好人类对齐的 NLG 评估。arXiv,2023 年 5 月 23 日。arXiv.org,
doi.org/10.48550/arXiv.2303.16634.
-
闵世温等人。FActScore: 长篇文本生成中细粒度原子级事实精确度评估。arXiv,2023 年 10 月 11 日。arXiv.org,
doi.org/10.48550/arXiv.2305.14251.
-
Shahul Es 等人。RAGAS: 自动化评估检索增强生成。1,arXiv,2023 年 9 月 26 日。arXiv.org,
doi.org/10.48550/arXiv.2309.15217.
使用机器学习创建自定义色彩调色板
原文:
towardsdatascience.com/using-machine-learning-to-create-custom-color-palettes-acb4eeaa06aa
深入了解 Streamlit 的本月应用
·发表在Towards Data Science ·12 分钟阅读·2023 年 2 月 16 日
–
照片由Ricardo Gomez Angel提供,Unsplash
介绍
我们都喜欢接触新的数据集、探索它,并从中学习。但原始数字本身并不是优秀的讲述者。我们原始的大脑天生对线条、形状和颜色敏感。这就是为什么数字需要被可视化才能讲述一个好的故事。
数据可视化的色彩调色板可能会决定你的数据故事的成败。虽然为你的数据可视化找到完美的颜色组合可能是一个严格且耗时的任务,但你不必完全独自完成。你可以从历史上最伟大的画家和艺术家那里获得灵感。
作者提供的图片
从零开始创建色彩调色板通常是可视化工程师和设计师的专业领域,他们使用色彩理论来组合适合各种可视化目的的和谐调色板(例如,单色调色板用于顺序或渐变图,或互补调色板用于分类图表)。像[matplotlib](https://matplotlib.org/)
或[plotly](https://plotly.com)
这样的可视化包已经提供了令人惊叹的调色板集合。
虽然数据分析师和数据科学家可以使用预制的颜色调色板,但在某些情况下,我们可能想要自己创建自定义调色板。例如,当你想制作一个与公司颜色主题一致的可视化时。例如,你可能想创建一个与喜欢的公司的 logo 主题匹配的柱状图。有一个可以自动为你完成这项工作的应用程序会非常好,对吧?
图片由作者提供
图片由作者提供
我花费了太多时间试图为我的可视化找出完美的颜色组合,因此我决定让这个过程对我自己来说更简单一些。
我使用Streamlit 🎈构建了一个 Web 应用程序,可以从任何输入图像中推断颜色调色板:无论是画作、电影海报、摇滚专辑封面,还是圣诞家庭照片,应有尽有!虽然应用程序可能不会立即提供完美的颜色调色板,但至少会给你一个很好的起点。
那么,让我们看看我是如何构建这个应用程序的。
构建颜色调色板推断应用程序
图片由作者提供
为了创建一个颜色调色板推断工具,我们需要几个组件。
在这篇文章中,我将教你:
-
如何构建一个图像加载组件
-
如何构建一个带滑块的图像增强组件
-
如何对像素进行聚类并使用组平均值来制作调色板
-
如何使用颜色选择器小部件来显示和修改调色板
想要亲自试用这个应用程序吗?你可以在这里查看它,并在这里查看源代码。
1. 如何构建一个图像加载组件
我们的应用程序需要的第一个元素是输入我们想要转换为颜色调色板的图像的方法。我们可以通过三种方式做到这一点:
-
从现有的图像或艺术品库中加载。
-
使用
st.file_uploader()
将新的图像文件上传到应用程序中。 -
从 URL 下载一张新图像到应用程序中。
我们可以使用st.tabs()
构建三个不同的选项卡,让我们可以无缝切换这三种输入模式。
使用st.tabs()
构建的图像输入源。
由于 Streamlit 运行应用程序的方式是从上到下,后来的输入模式会获得更高的优先级并覆盖之前加载器的输出。例如,由于你将输入模式按 Gallery → File Uploader → Image URL 的顺序排列,如果你将图像保存到变量img
中,则画廊加载的任何内容都会被文件上传器和 URL 下载器的输出覆盖。
当用户使用层级较低的加载器加载图像时,你可以为用户添加弹出警告,如果层级较高的加载器已经加载了图像。
这就是代码的样子:
# define three tabs for the three loading methods
gallery_tab, upload_tab, url_tab = st.tabs(["Gallery", "Upload", "Image URL"])
with gallery_tab:
...
# raise a warning if file uploader or URL downloader have already loaded an image
if st.session_state.get("file_uploader") is not None:
st.warning("To use the Gallery, remove the uploaded image first.")
if st.session_state.get("image_url") not in ["", None]:
st.warning("To use the Gallery, remove the image URL first.")
img = ...
with upload_tab:
img = ...
# raise a warning if the URL downloader has already loaded an image
if st.session_state.get("image_url") not in ["", None]:
st.warning("To use the file uploader, remove the image URL first.")
with url_tab:
img = ...
因此,如果你尝试从画廊加载一个预先存在的图像,但在 URL 下载器中已经存在一个链接,你将需要先删除那个链接。这可能不是最优雅的解决方案,但它有效!
现在,继续实现每一部分的加载方法。
画廊视图
对于画廊视图,我们可以简单地将一些图像保存在公共存储库中,并直接在应用程序中加载这些图像(我在这里使用 GitHub,但你也可以使用 AWS S3 或 Google Cloud Storage)。st.selectbox
包含我保存的艺术品名称,因此用户可以通过从下拉菜单中选择来加载它们。实现代码如下。
import streamlit as st
from PIL import Image
with gallery_tab:
options = list(gallery_dict.keys())
file_name = st.selectbox("Select Art",
options=options,
index=options.index("Mona Lisa (Leonardo da Vinci)")
)
img_file = gallery_dict[file_name]
if st.session_state.get("file_uploader") is not None:
st.warning("To use the Gallery, remove the uploaded image first.")
if st.session_state.get("image_url") not in ["", None]:
st.warning("To use the Gallery, remove the image URL first.")
img = Image.open(img_file)
gallery_dict
是一个字典,包含文件名和图像文件路径作为键值对,PIL.Image.open()
用于加载这些文件。结果保存在一个名为 img
的变量中。
文件上传器
实现文件上传器非常简单,因为已经有一个 Streamlit 小部件。它叫做(你能猜到吗?)st.file_uploader()
!
实现代码如下:
with upload_tab:
img_file = st.file_uploader("Upload Art", key="file_uploader")
if file is not None:
try:
img = Image.open(img_file)
except:
st.error("The file you uploaded does not seem to be a valid image. Try uploading a png or jpg file.")
if st.session_state.get("image_url") not in ["", None]:
st.warning("To use the file uploader, remove the image URL first.")
这个小部件允许你上传一个文件,然后你可以将其传递给 PIL.Image.open()
来加载。如果文件实际上不是图像文件,或者格式与 PIL.Image
期望的不一致,这一步可能会失败。为了防止这个问题发生,我们可以将加载部分放入一个 try/except
块中。
⚠️ 我在这里使用这个代码块作为万用块,以避免在加载文件时出现各种意外错误。然而,通常不推荐在没有实际指定要绕过的Exception
类型的情况下使用except
,尤其是因为你可能不知道的致命错误会默默地通过这个代码块,使得调试代码变得困难。
URL 下载器
说实话!虽然上传文件是用户加载他们希望推断颜色调色板的自定义图像的好方法,但这不是最简单的方法。用户需要找到图像(如果他们还没有的话),在本地下载,然后上传到应用程序中。听起来简单,但在实际操作中却很麻烦。
为了消除这个障碍,我们可以在应用程序中添加一个 URL 下载器,这样用户可以简单地复制图像链接(例如,从 Google 搜索结果中),然后直接粘贴到应用程序中。为了实现这一点,我们需要 requests
模块,它获取 URL 的内容,以及 io.BytesIO
函数,它使得内容能够被 PIL.Image.open()
理解。实现起来简单且与文件上传器的实现类似。
import requests
from io import BytesIO
with url_tab:
url_text = st.empty()
url = url_text.text_input("Image URL", key="image_url")
if url != "":
try:
response = requests.get(url)
img = Image.open(BytesIO(response.content))
except:
st.error("The URL does not seem to be valid.")
2. 如何使用滑块构建图像增强组件
现在我们已经上传了图像,我们准备推断颜色调色板,对吗?其实不完全是。
你加载到应用程序中的原始图像可能未经过颜色推断的优化。颜色可能过于暗淡,画布上的亮度或对比度可能不足。这就是为什么你需要首先进行一些图像调整。
为了将增强功能应用于图像,我们可以使用 PIL.ImageEnhance
。API 非常简单。例如,如果你想将图像(加载到 img
中)的颜色增强因子设置为 2.5,你可以运行:
img = ImageEnhance.Color(img)
img = img.enhance(2.5)
只需将 Color
替换为 Shapness
、Contrast
或 Brightness
,即可分别调整这些图像属性。我们可以创建四个不同的滑块,将每个属性的值分配给这些滑块,然后编写单独的代码块,依次将增强应用于图像。但我们是优秀的程序员,我们尽量保持代码优雅和 DRY(Don’t Repeat Yourself)。所以让我们以更实用的方式来处理这个问题。
我们可以定义一个字典,其中包含我们希望应用的所有增强功能作为键,值表示我们希望在应用程序中分配给这些增强功能的滑块范围和步长。
enhancement_range = {
# "enhancement_type": [min, max, step_size]
"Color": [0., 5., 0.2],
"Sharpness": [0., 3., 0.2],
"Contrast": [0.5, 1.5, 0.1],
"Brightness": [0.5, 1.5, 0.1]
}
enhancement_categories = enhancement_range.keys()
# put adjustment sliders inside an expander
enh_expander = st.sidebar.expander("Image Enhancements", expanded=False)
# create a reset button that resets all enhancements to default value (1.0)
with enh_expander:
if st.button("reset"):
for cat in enhancement_categories:
if f"{cat}_enhancement" in st.session_state:
st.session_state[f"{cat}_enhancement"] = 1.0
# create sliders for each enhancement category using the dictionary values (min, max, step_size)
enhancement_factor_dict = {
cat: enh_expander.slider(f"{cat} Enhancement",
value=1.,
min_value=enhancement_range[cat][0],
max_value=enhancement_range[cat][1],
step=enhancement_range[cat][2],
key=f"{cat}_enhancement")
for cat in enhancement_categories
}
使用这种方法,如果我们想更改增强类型或值范围,我们只需更改原始字典。
现在我们已经在侧边栏上放置了滑块,剩下的就是使用 ImageEnhance
将这些值应用于图像。
from PIL import ImageEnhance
for cat in enhancement_categories:
# apply the enhancement class to the image
# e.g. for cat='Color' this would be the same as
# img = ImageEnhance.Color(img)
img = getattr(ImageEnhance, cat)(img)
# apply the enhencement value from the corresponding st.slider
img = img.enhance(enhancement_factor_dict[cat])
显示图像
现在我们已经加载了图像,剩下的就是使用 st.image()
将其显示在应用程序中:
with st.expander("🖼 Artwork", expanded=True):
st.image(img, use_column_width=True)
和 瞧!
3. 如何聚类像素并使用组平均值制作调色板
最后,进入有趣的部分!使用机器学习推断色彩调色板。
这里的想法非常简单。一幅图像是像素的集合,每个像素都有三个值:R、G、B。这些值基本上告诉你每个像素包含多少红色、绿色和蓝色。为了推断色彩调色板,像素在画布上的实际位置基本上是无关紧要的。重要的是它在 (R, G, B) 坐标空间中的位置。因此,在继续之前,让我们将图像分解到这个新的坐标系统中,并摆脱像素在图像上的实际位置。
r, g, b = np.array(img).reshape(-1, 3).T
df_rgb = pd.DataFrame({"R": r, "G": g, "B": b}).sample(n=sample_size)
如果我们查看 RGB 坐标中的像素,我们可以将相互接近的像素分组,并使用像素的平均值来表示每个组——实际上是我们调色板中的一种颜色。例如,如果我们想从蒙娜丽莎的增强图像中构建一个 5 色调色板,我们需要首先查看 RGB 空间中像素的分布(这里通过 PCA 算法投影到二维):
原始像素颜色在 (R, G, B) 空间中的二维 PCA 分解
然后我们选择 5 个不同的簇,并将每个簇的平均值分配给调色板中的一个槽。
使用 K-means 算法将原始采样像素分配到 5 个不同的簇
显然,我们不需要手动完成这个工作。有一种方便的机器学习算法叫做 K-means 聚类,它可以一举完成这个任务。我们唯一需要提供的参数是聚类的数量,也就是我们的调色板大小。这是使用 sklearn.cluster.KMeans
实现的样子。
from sklearn.cluster import KMeans
palette_size = st.sidebar.number_input("palette size",
min_value=1,
max_value=20,
value=5,
step=1,
help="Number of colors to infer from the image.")
model = KMeans(n_clusters=palette_size)
clusters = model.fit_predict(df_rgb)
palette = model.cluster_centers_.astype(int).tolist()
就这样!我们现在有了我们的调色板,只需要将其返回给应用中的用户。
ℹ️ 尽管我们在这里使用了流行的 R、G、B 像素分解,但值得注意的是,这不是分解颜色的唯一方法。我们本可以在 HSV(色调、饱和度、明度)空间中进行像素聚类,这将以不同的方式分布像素,从而得到不同的调色板。
4. 如何使用色彩选择器控件
不知为何,我感觉我决定构建这个应用的潜意识原因是为了使用 Streamlit 的神奇 st.color_picker()
控件!这个控件的好处在于,你可以同时展示从图像中推断出的调色板颜色,并且如果愿意的话,还可以更改它们。这非常完美,因为正如我之前提到的,从应用中得到的颜色可能并不是针对你特定用例的 100% 完美,而只是一个很好的起点。所以你可能需要稍微调整一下,在实际用于你的可视化之前做一些修饰。
我们不希望调色板在应用程序中占据页面的一半,因此让我们把它们分别放在不同的列中。
columns = st.columns(palette_size)
for i, col in enumerate(columns):
with col:
st.session_state[f"col_{i}"]= \\
st.color_picker(label=str(i),
value=palette[i],
key=f"pal_{i}")
value=palette[i],
key=f"pal_{i}")
美极了!
最后一件要做的事情是为用户提供一个可以在日常工作中使用的实用产品。如果数据分析师或数据科学家使用这个应用来推断一个色彩调色板,他们很可能会在 matplotlib
或 plotly
中使用这个调色板来应用于他们的可视化中。为什么不提供一个代码片段来处理这个问题,这样他们就不必将每一个十六进制颜色代码从调色板中复制粘贴到他们的编码环境中。
图片由作者提供
感谢 Streamlit 的 st.code()
控件,可以一键复制整个代码块!
总结
就这样!现在你有了一个功能齐全的调色板推断应用,可以帮助你弄清楚列奥纳多·达·芬奇是用什么颜色让蒙娜丽莎的脸上带上微笑的!
在这篇文章中,我们介绍了很多 Streamlit 的功能,特别是像 st.image
、st.tabs
、st.file_uploader
和 st.color_picker
这样的控件。不过为了简洁起见,我们没有涵盖所有内容,所以请随时跳转到 源代码 仓库,自己查看所有细节。你可以看到,我在多个应用运行间保持一致性时,依赖了大量的 st.session_state
。
我们学会了
-
构建一个图像加载组件(加载现有图像、上传或 URL 下载)。
-
构建一个带滑块的图像增强组件。
-
使用 K-means 将像素聚类,并使用组平均值来构建调色板。
-
使用颜色选择器小部件将调色板返回给最终用户。
我很高兴能带你走过所有这些步骤,希望你喜欢阅读/浏览这篇文章,并希望你学到了一些东西。
你可以亲自查看这个应用,这里。我很想听听你的想法、问题、评论和反馈!通过LinkedIn或我的网站与我联系。
这篇文章与 Ksenia Anske 及团队 Streamlit 在 Snowflake 的合作下编写。请查看这篇文章在 Streamlit 博客上发布的最终版本 这里。
除非另有说明,所有图片均由作者提供。
使用 MLflow 和 ATOM 跟踪所有机器学习实验,而无需额外的代码
开始仅通过更改一个参数来存储模型、参数、管道、数据和图表
·
关注 发表在 Towards Data Science ·6 分钟阅读·2023 年 3 月 13 日
–
照片由 Hans Reniers 提供,来源于 Unsplash
介绍
MLflow Tracking 组件是一个 API 和 UI,用于记录参数、代码版本、指标和输出文件,在运行机器学习实验时用于后续结果的可视化。
在本故事中,我们将解释如何使用ATOM库轻松跟踪你的模型、参数、管道、数据和图表。ATOM 是一个开源 Python 包,旨在帮助数据科学家探索机器学习管道。
注意:本故事侧重于使用 ATOM 的实验跟踪功能。讲解库的基础知识不在本故事范围内。如果你想要一个温和的库介绍,请阅读这篇其他故事。
功能概述
通过在atom的构造函数中为[experiment](https://tvdboom.github.io/ATOM/v5.1/API/ATOM/atomclassifier/#atomclassifier-experiment)
参数分配名称来开始跟踪实验。每个模型都使用单独的运行进行跟踪。当未配置后端时,数据会存储在./mlruns
本地。要配置后端,请在初始化atom之前,在笔记本或 IDE 中使用mlflow.set_tracking_uri。这不会影响当前活动的运行(如果存在),但会对后续运行生效。在终端运行mlflow ui
以打开 MLflow 的跟踪 UI,并在localhost:5000
查看。
注意: 在Databricks上使用 ATOM 时,实验名称应包含存储的完整路径,例如/Users/username@domain.com/experiment_name
。
跟踪以下元素:
标签
运行将自动标记为模型的全名,分支
模型训练来源及拟合模型所需时间。
通过ht_params
参数添加其他自定义标签,例如atom.run(["LR", "RF"], ht_params={"tags": {"tag1": 1}})
。
参数
初始化时使用的所有参数都会被跟踪。传递给 fit 方法的额外参数不会被跟踪。
模型
模型的估算器被存储为工件。可以通过atom的[log_model](https://tvdboom.github.io/ATOM/v5.1/API/ATOM/atomclassifier/#atomclassifier-log_model)
属性来关闭此选项,例如atom.log_model = False
。
超参数调整
如果进行超参数调整,每个试验都会作为模型主运行中的嵌套运行进行跟踪。可以通过atom的[log_ht](https://tvdboom.github.io/ATOM/v5.1/API/ATOM/atomclassifier/#atomclassifier-log_ht)
属性来关闭此选项,例如atom.log_ht = False
。
指标
所有指标结果都会被追踪,不仅是在训练过程中,还包括在稍后调用 evaluate 方法时。在训练验证期间计算的指标也会被存储。
数据集
用于拟合和评估模型的训练和测试集可以作为.csv
文件存储到运行的工件中。可以使用atom的[log_data](https://tvdboom.github.io/ATOM/v5.1/API/ATOM/atomclassifier/#atomclassifier-log_data)
属性开启此选项,例如atom.log_data = True
。
管道
模型的管道(由 export_pipeline 方法返回)可以作为一个工件进行存储。可以使用atom的[log_pipeline](https://tvdboom.github.io/ATOM/v5.1/API/ATOM/atomclassifier/#atomclassifier-log_pipeline)
属性开启此选项,例如atom.log_pipeline = True
。
图表
默认情况下,图表作为.html
工件存储在所有与图表中显示的模型对应的运行中。如果指定了filename
参数,则会以该名称存储,否则使用方法的名称。可以使用atom的[log_plots](https://tvdboom.github.io/ATOM/v5.1/API/ATOM/atomclassifier/#atomclassifier-log_plots)
属性关闭此选项,例如atom.log_plots = False
。
示例
了解功能的最简单方法是通过示例。我们按照通常的方式初始化atom,并指定experiment
参数。这里提供的名称是使用的 mlflow 实验的名称。如果没有该名称的实验,则会创建一个新的。
from atom import ATOMClassifier
from sklearn.datasets import load_breast_cancer
X, y = load_breast_cancer(return_X_y=True, as_frame=True)
atom = ATOMClassifier(X, y, experiment="breast_cancer")
让我们指定一下我们也想记录数据和管道。这两个选项默认是关闭的。
atom.log_data = True
atom.log_pipeline = True
按照通常的方式训练模型。所有选择的指标都会被记录。
atom.run(models=["LR", "RF"], metric=["f1", "accuracy", "precision"])
现在在终端中运行mlflow ui
打开 UI。每个模型都有其单独的运行。
每次运行,atom会存储:
- 模型的参数:
- 训练集和测试集上的指标分数:
- 预定义标签和自定义标签(如果指定):
- 工件,包括估计器和管道的 pickle 文件,以及训练和测试集的 csv 文件。
额外的指标(使用 evaluate 方法计算)和显示模型的图表也会被添加到运行中。
atom.evaluate()
atom.plot_gains()
超参数调优
在运行 超参数调优 时,研究的每个试验都会作为嵌套运行添加到主运行中。
atom.run(models="LGB", n_trials=10)
嵌套运行的名称为 <model_name> — <trial_number>
。它们的指标分数不是在训练集或测试集上,而是在验证集上,验证集是用于验证该特定试验的训练集子集(其余部分用于拟合估计器)。测试集在超参数调整期间不会使用,以避免数据泄露。
注意: 数据和管道不会存储在嵌套运行中。
训练中的验证
一些 模型 允许 训练中的验证。这意味着在每轮训练后(对于线性模型是一轮迭代,对于提升树模型是增加的一棵树),估计器在训练集和测试集上使用 第一个指标 进行评估。验证分数存储在 evals
指标中,也在 mlflow 中跟踪。
注意: evals
指标在嵌套运行中不会计算。
DAGsHub 集成
ATOM 内置了与 DAGsHub 的集成,这是一种基于开源工具的数据科学优化平台,面向开源社区。要将你的 mlflow 实验存储在 DAGsHub 仓库中,请在 experiment
参数中输入 dagshub:<experiment_name>
(而不仅仅是实验名称)。如果仓库不存在,将创建一个新的公共仓库。
一个简约的示例如下:
from atom import ATOMClassifier
from sklearn.datasets import load_breast_cancer
X, y = load_breast_cancer(return_X_y=True, as_frame=True)
atom = ATOMClassifier(X, y, experiment="dagshub:breast_cancer")
atom.run(models=["LR", "RF"])
注意: 如果在运行 atom 的构造函数时已登录到你的 DAGsHub 账户,浏览器页面会自动打开以授予访问权限。如果没有,请阅读 这里 了解如何设置你的 DAGsHub 凭据。
结论
我们展示了使用 ATOM 库跟踪机器学习实验的简便性。只需最少的代码更改,现在可以存储每个训练模型的估计器、参数、管道、数据和图表。
有关 ATOM 的更多信息,请查看软件包的 文档。对于错误或功能请求,请随时在 GitHub 上提出问题或发邮件给我。
相关故事:
-
towardsdatascience.com/atom-a-python-package-for-fast-exploration-of-machine-learning-pipelines-653956a16e7b
-
towardsdatascience.com/how-to-test-multiple-machine-learning-pipelines-with-just-a-few-lines-of-python-1a16cb4686d
-
towardsdatascience.com/from-raw-data-to-web-app-deployment-with-atom-and-streamlit-d8df381aa19f
-
towardsdatascience.com/exploration-of-deep-learning-pipelines-made-easy-e1cf649892bc
-
towardsdatascience.com/deep-feature-synthesis-vs-genetic-feature-generation-6ba4d05a6ca5
-
towardsdatascience.com/from-raw-text-to-model-prediction-in-under-30-lines-of-python-32133d853407
-
towardsdatascience.com/how-to-make-40-interactive-plots-to-analyze-your-machine-learning-pipeline-ee718afd7bc2
-
towardsdatascience.com/machine-learning-on-multioutput-datasets-a-quick-guide-ebeba81b97d1
参考资料:
- 所有图表和图片(除特色图片外)均由作者创建。
使用多任务和集成学习预测阿尔茨海默病的认知功能
个人故事与数据科学
认识到我在机器学习领域的影响力,源于认知科学和我的第一篇科学论文的发表
·发布于 Towards Data Science ·11 分钟阅读·2023 年 6 月 9 日
–
图片由 Alina Grubnyak 拍摄,来源于 Unsplash
在我之前的一篇 文章 中,我详细描述了我从认知科学转向机器学习的经历以及对我的困惑症。 在那篇文章中,我提到:
“一个想法开始慢慢展开——也许,我的背景 [在认知科学] 提供了比我最初预期的更为坚实的基础。”
在这篇文章中,我将分享一个具体的例子,展示我的认知科学背景如何使我能够 1) 为在神经科学领域对我个人具有重要意义的疾病开发创新的建模方法,以及 2) 构建通常在传统讨论中被忽视的独特联系。
通过这次经历,我意识到深度学习领域虽然潜力巨大,但仍处于初期阶段,这提醒了我它对传统和非传统背景的个人都提供了包容性的机会。
大脑网络实验室
完成本科学位后,我心中萦绕着一种感觉,即虽然有不错的理论基础,但缺乏有效应用这些工具的实践理解。我设想了一个理想的场景,在神经科学或心理健康领域应用这些工具。
因此,当有机会申请Wicklow 医学中的人工智能研究计划时,我感到非常激动,这意味着我将致力于在我的研究生项目中进行一个以“利用人工智能模型推动医疗研究,包括肿瘤学、心脏病学和神经学等领域”的实践。
图 I. 由 cottonbro studio 在 Pexels 上拍摄(来源)
在这个项目中,我被接受了🎉,最终在 UCSF 的脑网络实验室工作。该实验室的重点是:
“通过将计算工具应用于神经影像数据,理解健康和疾病脑部的机制。”
我迫不及待地等待着有关实践的更多细节;兴奋之情在我内心涌动,期待开始弥合理论与实践之间的差距。最后,当我收到任务时,期待已久的时刻终于到来了。
预测阿尔茨海默病患者的认知评分
任务: 预测阿尔茨海默病患者的认知评分
问题: 我完全没有经验于计算机视觉。
我的第一次印象是,“我怎么可能对此做出贡献?”自然,恐惧和冒名顶替综合症的声音想要显现出来。此外,我在深度学习方面的经验有限——几乎可以忽略不计——而且,职位由于任务复杂性有被取消的风险。
然而,我确实拥有的是全面理解阿尔茨海默病的动力,对受影响者的真诚同情,以及利用我们现有的计算工具做出贡献的热情。此外,理论与实践之间的脱节激发了我做出贡献和成长的决心。
生活方式与阿尔茨海默病的相关性
我第二次印象是,仅仅依赖 MRI 数据来预测认知评分似乎有些奇怪,因为已知的人口统计学、遗传学和生活方式与阿尔茨海默病的相关性。
图 II. 作者拍摄。引用见文章末尾。
在研究中,“肠道微生物群、衰老、现代生活方式与阿尔茨海默病之间的联系”,作者强调“已经在阿尔茨海默病患者中报道了肠道微生物群的显著……变化……肠道微生物群对负面的外部生活方式因素,如饮食、睡眠剥夺、昼夜节律干扰、慢性噪音和久坐行为非常敏感,这些也被认为是发病的重要风险因素。
然而,我知道我需要集中精力完成任务;尽管如此,好奇心仍然驱使我去探询是否有可能获得人口统计学和临床数据的访问权限,以防机会出现…… 搓手
我们稍后会再讨论这个问题。
测量认知功能
ADAS Cog-11 分数
与此同时,我继续在仅使用 MRI 数据的基础上进行建模方法的头脑风暴。然而,你可能会想,我们如何定义‘认知功能’?换句话说,我们的预测目标是什么?我们的目标标签是什么?
我们正在使用ADAS-Cog-11来测量认知功能——这是一个用于评估阿尔茨海默病患者记忆、语言和实践能力退化的指标。根据维基百科,“它是最广泛使用的认知量表之一……并被认为是评估抗痴呆治疗的‘金标准’”。
ADAS-Cog-11 分数来源于以下十一项认知任务:
图 III. 由作者基于这里的信息拍摄的照片
你可以在这里找到任务的详细总结。
简单的卷积神经网络
为了开始实验,我们在 MRI 数据上训练了一个基准卷积神经网络(CNN)模型,以预测认知评分。
这个简单的 CNN 包含以下层:第一卷积层、池化层、第二卷积层、池化层、一个 2 层全连接神经网络,以及一个回归层。
结果并不显著,交叉验证的 R2 范围从 0.33 到 0.52,测试 R2 为 0.47。尽管如此,考虑到对这个简单模型的期望较低,我们还是很高兴建立了一个起始基准,今后可以在此基础上进行改进。
多任务学习与捕捉大脑结构上下文
下一步是研究痴呆症的结构性预测因素。这导致了一些文献,突出了灰质和白质体积与痴呆症严重程度之间的独立关系。
根据Stout et al,
“定量磁共振方法提供了有力证据,表明皮质灰质体积(可能反映萎缩)和异常白质体积与可能的阿尔茨海默病痴呆的严重程度独立相关:较低的灰质和较高的异常白质体积与更严重的痴呆相关。”
从中,一个理论浮现出来:如果模型能够捕捉灰质和白质体积的信息,它应该会提升预测能力。
那我们该如何做到这一点呢?
简短的回答是:✨ 多任务学习 ✨
“多任务学习是机器学习的一个子领域,其中多个学习任务同时解决,同时利用任务之间的共性和差异 […] 利用相关任务训练信号中包含的领域信息 […] 每个任务学到的东西可以帮助其他任务学得更好”
— 多任务学习,2021 年 7 月 6 日。在 维基百科
直觉:如果我们的模型预测认知评分,同时学习将输入 MRI 扫描分割为白质、灰质和脑脊液,这些相关任务将利用共享的领域信息,并提升每个单独任务的表现。
U-Net 架构
对于这个模型,我们使用了 U-Net 架构:
“一个依赖于强大的数据增强使用的架构,[…] 包括一个收缩路径来捕捉上下文和一个对称的扩展路径以实现精确定位 […] 这样的网络可以从非常少的图像中端到端训练,并超越了之前最好的方法(一个滑动窗口卷积网络)” — Ronneberger et al., 2015
图 IV. U-Net 架构。许可:Apache 2.0 (来源)
这是一个具有吸引力的架构来进行实验,因为我们能够使用非常少的图像来实现比以前的方法更好的性能,只要我们应用数据增强技术。
医学成像中的数据增强技术是什么?
数据增强是对输入图像应用随机化更改(例如:平移、旋转、翻转、拉伸、剪切等)的过程,以增加变异性。这个过程通过对输入数据进行小的位移来使我们的模型更好地泛化,只要修改后的图像仍然在可能的输入范围内。
这些技术还作为解决标记医学成像稀缺挑战的宝贵解决方案。获取足够大的医学成像数据集是一个突出的问题,主要有两个原因:1)医学扫描的人工标注极其耗时,2)临床数据的共享受到越来越严格的患者隐私法的限制。
图 V. 数据增强。照片由作者提供。
不同器官和模态的性能
实验各种数据增强技术以找出最适合特定任务的技术是常见做法。然而,特别是在医疗影像中,指导探索过程的一种方式是了解不同器官/结构、模态和任务组合的合适——也即最有效的——增强技术。在这里,“合适”指的是确保增强的数据包含输入空间内有效的示例。
例如,弹性变形通常适用于具有固有弹性或可变形性的器官。脑组织就是一个很好的例子,因为大脑具有显著的神经可塑性,能够在经历、学习和环境变化时进行结构和功能上的变化。
圣地亚哥·拉蒙·卡哈尔,这位被誉为现代神经科学之父的人曾宣称:
“任何人都可以,如果他愿意的话,成为自己大脑的雕塑家”
— 圣地亚哥·拉蒙·卡哈尔
然而,骨骼的可变形性有限,血管是刚性结构,因此应用弹性变形可能无法准确表示现实的变化或保持解剖结构的完整性。
此外,缩放(放大图像以强调特定区域)通常适合 X 光图像,因为它们通常涵盖了更广泛的视野。然而,对于已经具有较窄视野并集中于特定兴趣区域的 MRI,缩放可能会无意中排除重要的背景信息,从而使其作为 MRI 数据增强技术不太适用。
关于医疗影像数据增强技术的详细文献综述可以在这里找到。作者强调了这一点:
“根据输入的性质和视觉任务的不同,不同的数据增强策略可能表现不同。因此,可以想象医疗影像需要特定的增强策略,以生成合理的数据样本并有效地对深度神经网络进行正则化。”
多任务学习架构可视化
我在下面提供了多任务模型的可视化表示。在 U-Net 架构的最底部,我们加入了一个回归块。在这个块中,所有像素被展平,然后通过一个线性层,最终输出表示认知分数。
图 VI. 多任务,U-Net 架构。照片由作者提供。
U-Net 和多任务学习性能
总结一下,最初我们使用了一个简单的 CNN 基线模型,得到的交叉验证 R2 值范围从 0.33 到 0.52,测试 R2 值为 0.47。
然而,实施多任务模型后,我们见证了显著的改进:
-
交叉验证 R2:提升至 0.41 到 0.69 的范围
-
测试 R2:提升至 0.57
还值得一提的是,我们为分割任务开发了一个基线模型,使用了 U-Net 架构,准确率为 93.7%。
值得注意的是,分割任务的性能也得到了提升,使其准确率从 93.7% 提升到 97.27% 🎉
图 VII. 分割和回归结果。作者拍摄
多任务模型似乎在脑部分割的当前最先进水平中表现更佳,如下所示:
图 VIII. 作者拍摄。文章来源可以在 这里 查找。
那么人口统计学和遗传特征呢?
那么我之前承诺回到的人口统计学和遗传特征呢?
嗯,我们获得了数据!干杯
第一步是仅在表格特征上训练另一个基线模型。我们最终选择了具有泊松损失函数的基于直方图的梯度提升回归模型(HGB Regressor),结果出乎意料地不错。
-
交叉验证 R2:范围从 0.56 到 0.63
-
测试 R2:0.51
将表格数据整合到一个集成多任务模型中
在最终实验中,我们集思广益,寻找有效地将这些特征整合到我们的模型中的方法。
表格数据和成像数据的整合是一项挑战,因为成像数据(每个体素都被视为一个输入)与表格数据集(每个特征表示为一个单一值)之间的输入数量差异很大。将所有特征等同对待可能会降低人口统计学和遗传风险因素在整体模型中的重要性。
为了解决这个问题,我们为表格数据开发了一个单独的模型,使用 HGB 回归器。然后,我们应用了加权平均集成方法,将 HGB 回归器和多任务模型的预测结果结合起来。对每个模型分配的权重基于其性能和可靠性,给更准确或更有信心的模型分配更高的权重。这种集成技术通过分配适当的权重有效地优化了每个模型的贡献。
下面,你可以看到这种集成方法的可视化图。
图 IX. 多任务学习和集成方法。作者拍摄。
最终模型性能
那么这个集成的多任务方法与之前的实验相比如何呢?
鼓点
回归任务性能:
-
交叉验证 R2:0.73–0.78
-
测试 R2:0.67
分割任务性能:
- 准确率:98.12%
与多任务学习中观察到的显著性能提升类似,通过集成方法纳入人口统计特征和遗传风险因素,不仅显著提升了回归任务的性能,还进一步增强了分割任务的表现。这清晰地展示了利用多个数据源并利用其协同潜力的力量。
结束语
有机会深入研究神经科学与机器学习的交汇点,特别是在阿尔茨海默病方面,并意识到我的背景使我能够将不同领域的概念联系起来,这种经历是颠覆性的。自从体验到将当前神经科学研究与机器学习概念对接所带来的显著改进以来,我对整合多样数据源和以领域驱动方式应用模型架构的力量有了更深刻的认识。
我希望这项研究能够激励那些:
-
从认知科学转向机器学习
-
对获取和应用神经系统疾病技术充满热情
-
对医学领域的计算机视觉感兴趣
如有任何问题,请随时联系,我希望这对你来说与对我一样令人激动!
图 II 使用的文章:
使用 OpenAI 和 Python 提升你的简历:一步一步的指南
这是一个成功故事的经历,只需几个小时的工作
·
关注 发布于 Towards Data Science ·10 min read·2023 年 2 月 24 日
–
图片由作者使用 Midjourney 制作
几天前,我下班回家,开始玩视频游戏。
玩视频游戏对我来说是一个很好的消遣,因为我非常糟糕,我根本不专注于游戏;我只是开始思考。我让我的思绪随意飘荡。通常,我会想到我所喜欢的东西,比如人工智能。
所以,当我在 PS4 上进行任务时(当然,我又输了),我在想许多 NLP 任务,比如标记分类、下一个词预测(文本生成)、情感分析、文本分类以及其他许多任务,现在基本上可以在几秒钟内解决。
比如说,ChatGPT 是一个非常出色的聊天机器人,经过训练可以以对话的方式回答任何领域的问题。它可以总结文本、回答问题、写代码、做模仿、写歌曲、写食谱……
当前的趋势是构建更小、更具可扩展性且开源的代码,这些代码可以用来替代 ChatGPT 并且拥有免费的 API,所以重点并不仅仅是ChatGPT。重点在于这些所谓的“大型语言模型”,它们在大量文本上进行训练,凭借如此强大的计算能力,它们可能超越了你在笔记本电脑上可以建立的所有小型方法。
我的意思是,如果你需要总结一段文本(那不是医学上未知的、超级困难的文本)或理解一条评论是好是坏,没有理由自己开发模型,因为 OpenAI 的免费模型可能在几秒钟内就能做到这一点。
所以我在想,“什么任务需要大量工作,但现在我们可以在几秒钟内完成?”例如,写简历。
当然,没有人可以从零开始为我们写简历,因为他们不了解我们的职业生涯,但在线上有一些服务,你可以利用 AI 来改进你的简历。这些服务通常不是免费的,我认为现在它们已经过时了,因为 OpenAI 确实是免费的,而且可能比所有其他模型做得更好,除非那些模型是 Meta、Google 或微软的模型。😅
所以我决定使用 Streamlit 构建一个网页应用,大家可以上传自己的简历,人工智能(正如我们将看到的,更具体地说是 OpenAI)将在几秒钟内改进他们的简历。
在我看来,这款应用的工作方式如下:
作者提供的图片
很简单,对吧?
几个小时的工作,这就是结果:
现在,这看起来像是一个已经运营了几个月的初创公司,但事实上,现在只需要我花费几个小时,一些基本的 Python 技巧,加上 Open AI 的 GPT-3 魔力,就能完成这些!
我是一个极客,如果有人在没有代码的情况下向我展示这些,我是不会相信的,所以让我在下一章中详细解释一下我做了什么!
0. 一些考虑事项
在我们开始之前,有一些事情需要考虑,以使讨论更加全面。
一个公平的假设:
首先,HR 和招聘人员的世界因多种原因而充满挑战。例如,求职市场是动态的,并且始终在不断发展。AI 使用训练模型。这意味着一旦模型被训练,其获得的信息和性能与可能已经过时的数据集有关。
因此,我会说这个模型更多地作为语法检查和文本美化器工作,而不是一个真正审查你的简历并找到改进方法的专家。
一个安全问题:
提交个人数据到软件中是否安全?
嗯……是的,也不是。
在这篇文章中,我并没有邀请你提交除工作经验之外的任何东西,这些内容并不是什么秘密,因为任何拥有 LinkedIn 页面的人都能看到。如果你对此仍然感到不安,记住始终可以选择在本地运行我的代码,这样就不会使用任何网络应用程序,你可以将简历的所有信息保留给自己。
我不推荐在 AI 简历改进工具中添加个人信息,如地址、电话号码或电子邮件。
一个伦理问题:
在使用 AI 来改进你的简历时,有一些一般性的伦理考虑需要记住。
-
构建一个没有偏见的数据集是一个非常棘手的概念,因为我们所有人都可能在某种程度上有偏见:我们能做的唯一事情就是尽量构建一个尽可能少偏见的数据集。这也适用于这种情况。人工智能在招聘过程中的盲目和不受控制的使用,无论是招聘还是构建简历,都极具风险,因为机器学习决策算法在所有决策阶段和简历的各个部分都可能会出现偏见错误。(阅读更多 这里)
-
重要的是要对 AI 在简历中的使用保持透明。如果你使用 AI 生成内容或优化格式,我们需要确保在你的简历中披露这一点。这是我们在工作中应该做的事情。有时由于这些技术与我们的生活如此紧密,容易忘记正确披露,但这仍然需要提醒。还有一些工具可以用来判断文本是否由 AI 编写(阅读更多 这里)
-
最后,记住你只是在使用语言模型。模型真正做的只是以一种花哨的方式基于全球数十亿文本预测“下一个词”。你对自己了解胜过计算机,因此深入挖掘,提升你的优点,给自己一些肯定,然后再使用语言模型来改进你的简历😊
既然我们都达成一致了,那就开始代码吧 💻
1. Github!
首先,这里没有什么秘密!一切都是公开的,且在 Github 上! 👇
我现在将描述所有必要的内容,以生成结果。
[## GitHub - PieroPaialungaAI/AI_CV_improver: 使用人工智能改进你的 CV]
你好 😃 感谢你来到这里!这是一个 Python 项目,将帮助你使用人工智能改进 CV…
github.com](https://github.com/PieroPaialungaAI/AI_CV_improver?source=post_page-----e2c1a359e194--------------------------------)
1.1. Constants.py
让我们从简单的事情开始
constants.py 是一个包含……常量的文件。
它获取我们模板简历的关键,OpenAI 模型的温度,以及我们用来改进简历的提示。这就是他们所谓的“提示工程”。
注意!!!你需要使用你的 OpenAI 密钥来更改 OPENAIKEY。它不是公开的,你不应该共享,因此我将其命名为 fake_key。你可以在这里获取密钥
openai.com/api/
你可以通过更改 constants.py 文件来调整提示工程。难道这不是很简单吗?🙃
1.2 utils.py
utils.py 是一个帮助我们从 .txt 文件中提取内容并提取总结部分的文件。只是一个诚实的家伙,做他被支付的工作。
1.3 cv_parser.py
cv_parser.py 做的事情确实类似于 utils.py,我实际上不确定是否应该将代码拆分为两个 .py 文件。它只是一个工具箱;它处理模板和结果文件(分别是过程的开始和结束),并将工作经验解析成文本部分。它有点像工具所做的工作,但更相关于任务……我可能应该将它们放在同一个文件中,但我喜欢有序 😂
1.4 ai_improver.py
ai_improver.py 处理实际的 AI 部分。它通过使用 OpenAI 密钥连接到 OpenAI 来改进简历总结和列出的每一项工作经验。它还使用了我们在 constants.py 文件中构建并放置的所有提示。
1.5 app.py
这是我们用来运行应用程序的内容。我们用以下命令运行它
streamlit run app.py
它做的就是……一切。
这里是所有路径连接的地方。脚本执行以下步骤:
-
它从上传的文件中获取数据,并使用 utils.py 和 cv_parser.py 解析这些数据。
-
它使用ai_improver.py来处理 AI 部分。ai_improver.py 还使用 constants.py 文件。
-
它允许你通过下载按钮下载 AI 改进的简历。
1.6 main.py
main.py 的功能与 app.py 完全一样,但在本地运行(没有流媒体)。
使用以下命令运行它
python main.py
2. 结果!
让我们好好看看我得到的结果。
我的输入总结如下:
“我是一名物理学家,已在数据科学和深度学习方面发展了强大的技能。拥有复杂系统物理学和大数据的硕士学位(成绩:110/110 荣誉)。目前在辛辛那提大学航空航天工程与工程力学系担任研究助理,同时攻读博士学位。我的研究目标是寻找使用机器学习和数据科学优化工程实验的方法。为《Towards Data Science》撰写关于流行机器学习应用和技术解决现实问题的技术文章(每月超过 6 万次浏览)。《青少年人工智能》课程的创作者和教师。图像处理 I 和信号处理 I 课程的开发者。”
人工智能模型将其输出为一个对招聘人员更具吸引力和趣味性的总结:
我拥有广泛的物理学背景,拥有复杂系统物理学和大数据的硕士学位(成绩:110/110 荣誉)。我还在数据科学和深度学习方面发展了强大的技能。我目前在辛辛那提大学航空航天工程与工程力学系担任研究助理,同时攻读博士学位。我的研究重点是寻找使用机器学习和数据科学优化工程实验的方法。此外,我还是《Towards Data Science》的技术写作者,撰写关于流行的机器学习应用和解决现实世界问题的技术文章(每月超过 6 万次浏览)。我还是《青少年人工智能》课程的创作者和教师,并且是《图像处理 I》和《信号处理 I》课程的开发者。我在这些领域的经验和知识使我培养了一套在行业中非常受欢迎的独特技能。
我不打算撒谎……这对我来说确实听起来更好。
这无疑更流畅(这很可预测,因为我不是以英语为母语的人),听起来也更激动人心。不过,它略长一些,这可能是个问题,因为我们希望在简历中节省空间。
让我们来看一下某段工作经历的回顾。例如,其中之一是:
使用机器学习创建代理模型并提高机械工程实验的效率。实践传统和深度机器学习的研究。
而人工智能将其改正为:
利用机器学习创建代理模型并优化机械工程实验。拥有传统和深度机器学习的经验,并且有成功研究的可靠记录。
…有时我感觉这有点过于大胆了。😂
我仍然会说它确实提高了文本的质量。在这种情况下,它也保持了简洁,这也是一个优点!
3. 一些要点!
在这篇文章中,我分享了使用 OpenAI 的 GPT-3 模型来创建简历改进网络应用程序的经验。以下是我的经验的关键要点:
-
Open AI 模型是一个可以执行各种 NLP 任务的工具,包括令牌分类、文本生成、情感分析和文本分类。它非常有用,以至于许多以前的模型现在基本上已经过时。
-
这个应用的概念是下载模板, 填写你的简历,**上传回去,**然后让 AI 为你分析。
-
创建一个网页应用来提升你的简历非常简单。只需几个小时的代码和一些基础的 Python 编程。
-
这让我想到,AI 驱动应用的可能性是巨大的,随着像 GPT-3 和 Streamlit 这样的工具变得更加普及,即使是不懂编程(或编程能力不强)的人员,也可以在短短几小时内创造出令人印象深刻的结果。
最后,这次经历再次展示了 AI 的巨大力量及其改变我们工作和生活方式的潜力。但也许我们早已知道这一点🙃
4. 结论
如果你喜欢这篇文章,想了解更多机器学习的内容,或者只是想问我一些问题,你可以:
在 Linkedin 上关注我,我会在那里发布我的所有故事。
订阅我的 通讯。它会让你了解最新故事,并给你机会发信息给我,获取所有的修正或疑问。
成为 推荐会员,这样你就不会有“每月最大故事数量”的限制,你可以阅读我(以及数千位机器学习和数据科学顶级作者)关于最新技术的所有内容。
使用 OpenCLIP 进行图像搜索和自动字幕生成
原文:
towardsdatascience.com/using-openclip-for-image-search-and-automatic-captioning-fa1cbbd48ce4
如何利用更多数据和新的机器学习训练技术来改善图像和文本嵌入,以应用于各种场景
·发表于 Towards Data Science ·阅读时间 12 分钟·2023 年 3 月 7 日
–
“用于在大型库中查找图片的高科技计算机系统,”我使用 AI 图像创建程序 Midjourney 创建,并由作者编辑
自 2021 年推出以来,我一直在使用和撰写关于 OpenAI 的 CLIP 系统的文章 [1]。它包含可以用于各种跨模态比较的图像和文本编码模型,例如使用文本查询快速找到库中最匹配的图像。
2022 年 12 月,一个名为 LAION 的独立研究小组发布了一篇名为《对比语言-图像学习的可重复扩展定律》的论文 [2],描述了他们如何首先重新实现并训练了一个类似于 CLIP 的模型,然后通过使用更大的数据集和新的机器学习技术来改进系统。他们称他们的新模型为 OpenCLIP。
在这篇文章中,我将提供有关原始 CLIP 的一些背景信息,描述 LAION 如何改进该模型,并展示我使用 国会图书馆 Flickr 照片流 的图像进行的两个系统实验的一些结果。我还实现了一种称为“嵌入算术”的酷技术,这项技术来自 Meta AI,用于通过图像和文本提示搜索照片 [3]。
我将在文章末尾展示一个使用 LAION 的 OpenCLIP 模型变体自动生成图片字幕的演示。例如,我使用 Midjourney 创建了这篇文章的标题图片,文本提示为“用于在大型库中查找图片的高科技计算机系统”。当我将该图像输入 OpenCLIP 时,它生成了标题为“一个女孩站在图书馆里看电视”的字幕。还不错!
OpenAI 的 CLIP
在 2021 年,OpenAI 发布了一篇名为“从自然语言监督中学习可转移的视觉模型”的论文,描述了他们的新系统,称为对比语言-图像预训练(CLIP)。该系统包括两个 AI 模型,一个文本编码器和一个图像编码器,训练以生成称为嵌入的数字数组。他们发布了源代码和预训练模型。
CLIP 的系统组件, OpenAI 绘制的图示
OpenAI 使用 4 亿对图像/文本对训练编码器,目标是使编码器生成相似的嵌入,当文本和图像相似时。该系统可用于涉及文本和图像的各种任务,如检索,即用文本搜索图像,以及分类,即自动将图像分配到类别中。你可以在我的使用该系统的文章中了解更多关于 CLIP 的信息。
LAION 的 OpenCLIP
LAION 是一个独立研究者小组,提供数据集、工具和 AI 模型。他们重新实现了 OpenAI 的 CLIP 模型,并在他们的 20 亿对图像/文本对的数据集上进一步训练以提高性能。在他们的论文[2]中,他们讨论了如何通过使用 Google 发明的浮点数格式 bfloat16 解决在更多数据训练时遇到的问题[4]。
对于更大规模的模型……我们观察到训练过程中损失值的激增,对性能产生了不利影响。我们通过从 float16 的混合精度切换到 bfloat16 的混合精度解决了这个问题。我们推测 bfloat16 解决了这个问题,因为较大的模型通常显示更大的激活值……使 bfloat16 在其更宽的动态范围下更为合适。—— Mehdi Cherti 等,来自 LAION
他们的论文展示了他们的 OpenCLIP 系统在图像搜索等任务上优于 OpenAI 的 CLIP。下图中,OpenCLIP 用橙色表示,CLIP 用蓝色表示,其中数值越小越好。
使用 CLIP(蓝色)和 OpenCLIP 进行图像检索的结果(数值越小越好), LAION 绘制的图表
图表中有很多信息。我会看看能否将其拆解。横轴表示用于训练的计算量,以 Giga 乘加操作 (GMACs) 为单位。纵轴显示了结果的准确度,定义为 100 - Recall @ 5 针对 Flicker 30K 数据集。例如,如果系统搜索了五张猫的照片,只有四张包含猫,则 Recall@5 值为 80%。但从 100 中减去它将得到 20%,值越低越好。你跟上了吗?图表中的形状代表各种数据集,右侧的键显示了不同的大小。蓝线显示了 CLIP 在 CLIP-WIT 数据集上的表现,经过多种配置训练。橙线显示了最佳 OpenCLIP 模型在 LAION 数据集上的表现,使用了各种配置。底线:OpenCLIP 比 CLIP 更好。
如上图所示,LAION 计算了训练和性能之间关系的方程,带有指数成分。他们讨论了使用这种“缩放定律”来预测需要多少额外的训练以进一步提高他们的模型[2]。
在下一部分,我将展示如何构建和运行一些测试,以展示这两个系统在美国国会图书馆图像上的表现。
比较 OpenCLIP 和 CLIP
我使用了国会图书馆 Flickr 照片流中的照片进行测试。图书馆发布了超过四万张带有说明的图片供人们浏览和评论。请注意,所有图片均标记为“无已知版权限制”,因此可以用于任何目的。
这里是一些数据集样本,图像上方有说明。
来自国会图书馆 Flickr 照片流的样本,来自 LOC 的图像
通过这些样本,你可以对数据集中图像的类型有个了解,包括绘画、旧照片、新照片等。
为了测试这些系统,我将六个说明和图像通过 CLIP 和 OpenCLIP,并计算了余弦相似度,这是一种衡量文本和图像嵌入之间接近度的指标。请注意,结果范围大致从 0.0 到 0.4,较低的数字表示不匹配,较高的数字表示匹配。
图像横向展示在顶部,相应的说明纵向列在左侧。你可以看到,右侧的 OpenCLIP 结果在对角线块(较亮的黄色)上的匹配分数更高,而在非匹配分数(较暗的蓝色)上则更低,相比之下,左侧的 CLIP 结果较低。这意味着,如果你使用这些系统搜索带有文本的图像,使用 OpenCLIP 会得到比 CLIP 更好的结果。
使用 OpenCLIP 探索 LOC 图像
为了探索国会图书馆的 Flickr 照片流,我创建了一个Google Colab,下载了所有 4 万张图像,并通过 OpenCLIP 图像编码器进行文本搜索。
使用 OpenCLIP 搜索 LOC 图像的组件,图表由作者绘制,图像来自 LOC
我开始使用Flickr API将所有 4 万张照片下载到本地文件夹。接下来,我将图片发送到 OpenCLIP 图像编码器以创建图像嵌入。编码器之前由 LAION 使用 20 亿张带有说明的图像进行训练。然后,我输入了文本查询,例如“gone fishing”,并通过文本编码器创建了一个嵌入。我计算了文本嵌入与 40K 图像嵌入之间的余弦相似度,以找到最佳匹配。最后,我对数组进行排序并显示了搜索的前六张图像。
这里是使用 OpenCLIP 和数据集结果的一些示例搜索。
“boat vacation”
我输入了一个搜索短语并点击了“运行”按钮,以查看得分前六的结果。
“boat vacation”的 OpenCLIP 搜索结果,截图由作者提供,图像来自 LOC
果然,它找到了几艘假期中的船只。注意这些匹配的得分相对较低(0.259 到 0.281)。这些得分较低的原因可能是由于使用了有些抽象的词语“vacation”。接下来,我尝试了一些更具体的内容。
“建造飞机发动机”
在这里,我尝试使用了更具体的搜索短语。
“building an airplane engine”的 OpenCLIP 搜索结果,截图由作者提供,图像来自 LOC
好的,这次搜索的得分要高得多(0.302 到 0.326)。最佳结果展示了一张人们建造飞机发动机的漂亮照片。接下来,我尝试了一些有趣的东西。
“mini golf”
数据集中有很多具有美国风情的图像,所以我检查了一下是否有迷你高尔夫球场的图像。
果然,答案是“是的!”注意这些图像的得分较高(0.378 到 0.395)。最佳结果是一个经典的风车洞,风车叶片上写着“MINI GOLF”两次。在描述一种酷炫的新方法来优化图像搜索之后,我会重新审视这个搜索。
嵌入算术
2022 年 10 月,Meta AI 发布了一篇标题引人注目的论文,《多模态查询的嵌入算术用于图像检索》,其中“多模态”指的是其他形式的媒体,如文本[8]。
概念是这样的:如果你在数据集中找到一张图像,并且想要找到另一张保留部分特质但改变其他特质的图像,你可以通过结合图像和文本元素来构建查询。运行新的查询应该能够找到你所寻找的内容,前提是数据集中存在这样的图像。
这里是论文中的一个视觉示例。
使用嵌入算术进行图像检索,图片由 Meta AI 提供
这开始于顶部的猫图像,该图像被编码成嵌入 E(I)。然后,“cat”和“dog”这两个词通过文本编码器分别得到 E(W1) 和 E(W2)。E(W2) 和 E(W1) 之间的差值被加到猫图像的嵌入中,从而找到类似的狗图像所在的位置。从图像数据库中检索得到的结果显示了一个接近的匹配,如底部所示。匹配通过将原始标题中的“dog”替换为“cat”来评估,得到“狗坐在草地上。” 将转换后的标题文本嵌入与狗图像的嵌入进行比较,以查看是否匹配。
论文讨论了如何使用缩放因子 λ 来调整从文本提示中进行的修改量。这里是产生新嵌入 x 的方程。
论文讨论了如何在 1.0 到 1.5 之间的缩放因子对于许多搜索效果良好。
我在我的 Colab 中实现了这种形式的嵌入数学。这里是一些结果,从基于迷你高尔夫风车图像的修改搜索开始。
迷你高尔夫风车图像 + 1.5(“约塞米蒂·萨姆” - “风车”)
对于这次搜索,我从迷你高尔夫风车的肖像照片开始,添加了“约塞米蒂·萨姆”这一短语,并删除了“风车”。我使用了 1.5 的缩放因子。
迷你高尔夫风车图像 + 1.5(“约塞米蒂·萨姆” - “风车”),作者截图,图片来自 LOC
原始图像在左上角,最佳匹配在其旁边,得分很高,为 0.407。顶部的结果与起始图像非常相似,只是显示了约塞米蒂·萨姆而不是风车。接下来是一些路边餐馆的图像。
甜甜圈店图像 + 1.2(“汉堡包” - “甜甜圈”)
对于下一个测试,我从搜索“甜甜圈店”开始,选择了一张名为甜甜圈洞的有趣地方的图像。接下来,我使用了“汉堡包”作为正向提示,“甜甜圈”作为负向提示。我使用了 1.2 的缩放因子。以下是结果。
甜甜圈店图像 + 1.2(“汉堡包” - “甜甜圈”),作者截图,图片来自 LOC
哇,它找到了一家经典的麦当劳餐厅,其中金色拱门与起始图像中的巨型甜甜圈完美对齐。注意到顶部匹配的非常高的分数 0.533。我的最后一次搜索涉及一些名人的旧照片。
亚伯拉罕·林肯图像 + 1.1(“奥斯卡·王尔德” - “亚伯拉罕·林肯”)
对于我的最终测试,我首先搜索了“亚伯拉罕·林肯”,并选择了一张他坐在椅子上的著名图像。我使用系统检查数据集中是否有类似的奥斯卡·王尔德图像。我为这次测试使用了 1.1 的缩放因子。
亚伯拉罕·林肯图像 + 1.1(“奥斯卡·王尔德” - “亚伯拉罕·林肯”),作者截图,LOC 提供的图像
果然,它找到了一个色调为褐色的奥斯卡·王尔德坐在木椅上的图像。尽管姿势不同,但最高匹配的得分是我见过的最高,为 0.675。高分可能是因为著名人物的名字和面孔之间的强相关性超越了其他因素。接下来,我将展示我如何使用 OpenCLIP 生成图像字幕。
使用 CoCa 和 OpenCLIP 创建字幕
在他们 2022 年的论文《CoCa: 对比字幕生成模型是图像-文本基础模型》中,[5] 作者展示了如何训练类似于 OpenAI 的 CLIP 的模型来自动从图像生成字幕。
在这项工作中,我们展示了对比字幕生成模型(CoCa),这是一个新的图像-文本基础模型系列,它包含了现有的视觉预训练范式,并结合了自然语言监督。CoCa 在来自各种数据源的图像-文本对上进行了单阶段预训练,能够高效地结合对比和字幕生成目标于一个编码器-解码器模型中。 — 来自 Google 的 Jiahui Yu 和 Zirui Wang
独立研究员 Phil Wang,外号 lucidrains,将 CoCa 模型适配到了 OpenCLIP 上。结果非常出色。
例如,这里有六张图像,附有来自 LOC 的原始字幕。
以下是由 CoCa/OpenCLIP 生成的带字幕的图像:
尽管字幕缺少一些细节,如具体的个人(谁)和地点(哪里),但系统在描述图像的视觉内容(什么)方面做得非常出色。你可以在我的 Colab 这里 查看这一点。
社会影响
在互联网上训练的大型 AI 模型可能会表现出文化偏见,如果不加以缓解,可能会对社会造成伤害。OpenCLIP 模型的作者在他们的论文 [2] 中表达了他们的担忧。
我们的工作涉及研究大规模预训练模型的功能和属性。将这些模型公开可能有正面和负面的影响,就像任何具有通用功能的研究工具一样。…… 基于大规模预训练通用模型的技术可能被滥用,而民主机构的任务是制定涉及这些模型的敏感应用的规则。模型的开放发布也为广泛的研究社区提供了研究这些模型安全性方面的机会,以预防恶意方滥用技术,从而进行共同的透明努力。 — Mehdi Cherti 等人,来自 LAION
他们的透明政策允许其他研究人员评估和缓解他们模型的使用。
讨论与下一步
OpenCLIP 系统在大规模数据集中搜索图像时表现良好。新的数学嵌入技术提供了专家工具,帮助人们找到完美的镜头。CoCa/OpenCLIP 模型在为图像创建描述性标题方面做得很好。
一个改进的领域是看看这些系统是否可以微调以找到或创建个人照片的标题。与 OpenAI 不同,LAION 发布了他们模型的训练代码。尽管他们的代码是为大规模训练设计的,但如果能够调整为仅使用例如你叔叔 Bob 的十张照片来微调模型,那将会很有帮助。
源代码
本项目的源代码可在 GitHub 上获得。
知识共享 署名-相同方式共享
致谢
我想感谢 Jennifer Lim 在这个项目上的帮助。
参考文献
[1] A. Radford 等人, CLIP, 从自然语言监督中学习可转移的视觉模型 (2021)
[2] M. Cherti 等人, OpenCLIP, 对比语言-图像学习的可重复性缩放法则 (2022)
[3] G. Couairon 等人, 多模态查询的嵌入算术用于图像检索 (2022)
[4] S. Wang 和 P. Kanwar, BFloat16: 云 TPUs 高性能的秘密 (2019)
[5] J. Yu, CoCa: 对比式标题生成器是图像-文本基础模型 (2022)
使用 Plotly 3D 表面图可视化地质表面
原文:
towardsdatascience.com/using-plotly-3d-surface-plots-to-visualise-geological-surfaces-8829c06a5c9a
使用 Python 数据可视化库可视化地下结构
·发布于 Towards Data Science ·9 分钟阅读·2023 年 6 月 21 日
–
Hugin 组的 3D 表面图。图像由作者提供。
在地球科学中,全面了解地下的地质表面至关重要。了解构造的确切位置及其几何形状,可以更容易地识别潜在的石油和天然气勘探新目标以及潜在的碳捕集和储存地点。我们可以使用多种方法来完善这些表面,从地震数据到测井推导的构造顶部。通常,这些技术会相互结合以完善最终表面。
本文继续我之前的文章,后者关注于通过区域内的测井数据来推断和可视化地理空间变异。在这篇文章中,我们将探讨如何使用交互式 Plotly 图表创建 3D 表面。
由于建模地质表面是一个复杂的过程,通常涉及多个迭代和完善,本文演示了如何使用 Python 简单可视化这些数据的示例。
为了了解如何利用 Plotly 可视化我们在一个区域内的地质构造顶部,我们将使用两组数据。
第一组数据来自 28 个测井孔交点,这些数据用于克里金插值以生成低分辨率表面。相比之下,第二组数据来自解释的地震数据,提供了更高分辨率的表面。
两组数据均来自 Equinor Volve 数据集,详细信息见本文底部。
你还可以通过以下链接查看这一小系列中的其他文章:
-
利用 pykrige 和 matplotlib 进行地质变化的空间可视化
-
使用 Plotly Express 可视化 3D 线图中的井路径
导入库与数据
在对数据进行任何操作之前,我们首先需要导入所需的库。这些是:
-
pandas — 用于读取我们的数据,数据格式为
csv
-
matplotlib 用于创建我们的可视化
-
pykrige 用于进行克里金计算
-
numpy 用于一些数值计算
-
plotly.graph_objects 用于 3D 可视化表面
import pandas as pd
import matplotlib.pyplot as plt
import plotly.graph_objects as go
from pykrige import OrdinaryKriging
import numpy as np
接下来,我们可以使用 pd.read_csv()
加载数据。
由于这些数据包含 Volve 领域所有井眼的地质表面信息,我们可以使用 query()
提取我们需要的地层数据。在这种情况下,我们将关注 Hugin 地层。
df = pd.read_csv('Data/Volve/Well_picks_Volve_v1 copy.csv')
df_hugin = df.query('SURFACE == "Hugin Fm. VOLVE Top"')
df_hugin
当我们运行上述代码时,我们会得到以下表格。你可能会注意到一些井眼多次遇到 Hugin 地层。这可能是由于井眼或地层几何形状导致井眼多次穿透地层。
Pandas 数据框包含关于 Volve 领域 Hugin 地层的井位信息。图片来自作者。
外推 TVDSS 生成地质表面
在我之前的文章中,我专注于如何使用一种称为克里金的过程来“填补测量点之间的空白”。我们不会在这篇文章中详细介绍这个过程;然而,你可以查看 这篇文章以获取更多信息。
一旦数据加载完毕,我们可以通过调用 pykrige 的 OrdinaryKriging
方法来运行克里金过程。
在此调用中,我们传入我们的 x
和 y
数据,表示井眼在地下遇到地层的位置的东向和北向坐标。
由于我们想要生成 Hugin 地层的表面,我们需要使用 TVDSS(真实垂直深度水下)测量。这真实反映了表面在所选基准面下的深度。
OK = OrdinaryKriging(x=df_hugin['Easting'],
y=df_hugin['Northing'],
z=df_hugin['TVDSS'],
variogram_model='linear',
verbose=True, enable_plotting=True)
Hugin 地层的普通克里金结果。图片来自作者。
一旦模型生成,我们可以将其应用于覆盖井眼/穿透点整个范围的两个数组。
这使我们能够生成一个值的网格,然后将其传递到我们上面生成的 OrdinaryKriging
对象中。
gridx = np.arange(433986, 438607, 50, dtype='float64')
gridy = np.arange(6477539, 6479393, 50,dtype='float64')
zstar, ss = OK.execute('grid', gridx, gridy)
最后,我们可以使用 matplotlib 的 imshow
方法生成一个简单的 2D 地图视图。
fig, ax = plt.subplots(figsize=(15,5))
# Create a 2D image plot of the data in 'zstar'
# The 'extent' parameter sets the bounds of the image in data coordinates
# 'origin' parameter sets the part of the image that corresponds to the origin of the axes
image = ax.imshow(zstar, extent=(433986, 438607, 6477539, 6479393),
origin='lower')
# Set the labels for the x-axis and y-axis
ax.set_xlabel('X Location (m)', fontsize=14, fontweight='bold')
ax.set_ylabel('Y Location (m)', fontsize=14, fontweight='bold')
# Add contours
contours = ax.contour(gridx, gridy, zstar, colors='black')
colorbar = fig.colorbar(image)
colorbar.set_label('DTC (us/ft)', fontsize=14, fontweight='bold')
# Display the plot
plt.show()
应用克里金插值后的 Hugin 组的 2D 表面和等高线图。图片由作者提供。
使用 Plotly 创建简单的 3D 表面图
要将我们的 2D 表面转换为 3D,我们需要使用 Plotly。我们可以使用 matplotlib 来完成这一点;然而,根据我的经验,使用 Plotly 生成 3D 可视化更容易、更顺畅且更具交互性。
在下面的代码中,我们首先创建坐标网格。为此,我们使用 numpy 的 linspace
函数。这个函数将创建一个在指定范围内均匀分布的数字集合。对于我们的数据集和示例,范围从 xgrid_extent
和 ygrid_extent
的最小值到最大值。
在此范围内使用的总值数量将等于我们上面创建的 zstar
网格中 x 和 y 元素的数量。
一旦我们的网格形成,我们就会调用 Plotly。
首先,我们创建图形对象,然后使用 fig.add_trace
将 3D 表面图添加到图形中。
添加后,我们需要调整图形的布局,以便添加轴标签,定义宽度和高度,并设置一些内边距。
xgrid_extent = [433986, 438607]
ygrid_extent = [6477539, 6479393]
x = np.linspace(xgrid_extent[0], xgrid_extent[1], zstar.shape[1])
y = np.linspace(ygrid_extent[0], ygrid_extent[1], zstar.shape[0])
fig = go.Figure()
fig.add_trace(go.Surface(z=zstar, x=x, y=y))
fig.update_layout(scene = dict(
xaxis_title='X Location',
yaxis_title='Y Location',
zaxis_title='Depth'),
width=1000,
height=800,
margin=dict(r=20, l=10, b=10, t=10))
fig.show()
当我们运行上述代码时,我们会得到一个交互式图,显示基于多次遇到的钻井井眼的 Hugin 组的地质表面。
使用 Plotly 生成的 Hugin 组的 3D 表面图。图片由作者提供。
使用 Plotly 进行完整解释的表面视图
Volve 数据集中有许多完全解释的表面,这些表面是根据地质解释生成的,包括地震数据。
这些数据包含场地中数据点的 x
和 y
位置,以及我们的 TVDSS 数据(z
)。
提供在 Volve 数据门户上的数据是 .dat 文件的形式,不过,通过在文本编辑器中稍作处理,可以轻松地将其转换为 CSV 文件并使用 pandas 加载。
hugin_formation_surface = pd.read_csv('Data/Volve/Hugin_Fm_Top+ST10010ZC11_Near_190314_adj2_2760_EasyDC+STAT+DEPTH.csv')
Hugin 组的 X、Y 和 Z 位置。图片由作者提供。
数据加载完成后,我们可以通过将 x、y 和 z 数据提取到变量中来简化操作。
x = hugin_formation_surface['x']
y = hugin_formation_surface['y']
z = hugin_formation_surface['z']
然后,我们需要在 x 和 y 数据位置之间创建一个规则间隔的网格。这可以使用 numpy 的 meshgrid 完成。
xi = np.linspace(x.min(), x.max(), 100)
yi = np.linspace(y.min(), y.max(), 100)
xi, yi = np.meshgrid(xi, yi)
有几种方法可以在一系列数据点之间进行插值。选择的方法将取决于数据的形式(规则采样数据点与不规则采样数据点)、数据大小和计算能力。
如果我们有像这里这样的大型数据集,使用一些方法如径向基函数的计算成本会更高。
在这个示例中,我们将使用 scipy 中的 LinearNDInterpolator 方法来构建我们的模型,然后将其应用于我们的 z(TVDSS)变量。
为了在点之间进行插值,我们需要将xi
、yi
重塑为 1D 数组,因为LinearNDInterpolator
期望 1D 数组。
xir = xi.ravel()
yir = yi.ravel()
interp = LinearNDInterpolator((x, y), z)
zi = interp(xir, yir)
一旦计算完成,我们可以继续使用Plotly图形对象创建我们的 3D 表面。
fig = go.Figure()
fig.add_trace(go.Surface(z=zi, x=xi, y=yi, colorscale='Viridis'))
fig.update_layout(scene = dict(
xaxis_title='Easting (m)',
yaxis_title='Northing (m)',
zaxis_title='Depth',
zaxis=dict(autorange='reversed')),
width=1000,
height=800,
margin=dict(r=20, l=10, b=10, t=10))
fig.update_traces(contours_z=dict(show=True,
usecolormap=True,
project_z=True,
highlightcolor="white"))
fig.show()
当我们运行上述代码时,会得到 Hugin 地层的以下 3D 表面图。
Hugin 地层的完全解释地质表面。图像由作者提供。
当我们将这个图与从井眼测量生成的图进行比较时,我们可以明显看到整体形状上的相似之处,尤其是中间的山谷。然而,地震导出的表面提供了比井导出的地层顶部更多的细节。
Hugin 地层表面的比较:左侧是从稀疏的井测量生成的表面,右侧是从地震数据生成的表面。图像由作者提供。
摘要
在这个简短的教程中,我们展示了如何使用Plotly的 3D 表面图生成地质表面的互动 3D 可视化。通过井测量得出的地层顶部,我们可以生成非常基础的 3D 表面。这是因为测量仅限于交叉了 Hugin 地层的井眼,这意味着我们得到的是低分辨率的表面。
相比之下,如果我们有更详细的测量点,比如来自地震导出的地层面,我们可以生成对地层的更真实的印象。
两种方法都是有效的,但请注意,当从仅通过井测量得到的地层顶部进行外推时,我们可能无法生成该区域地层的完整图像。
使用的数据集
本教程中使用的数据是 Equinor 在 2018 年发布的 Volve 数据集的一个子集。数据集的完整详细信息,包括许可证,可以在以下链接找到。
Equinor 已发布 Volve 油田 2008-2016 年的完整数据集。点击此处下载以供学习、研究等使用…
Volve 数据许可证基于 CC BY 4.0 许可证。许可证协议的完整详细信息可以在此处找到:
感谢阅读。在你离开之前,你一定要订阅我的内容,并将我的文章发送到你的收件箱。 你可以在这里做到这一点!
其次,你可以通过注册会员获得完整的 Medium 体验,并支持成千上万的其他作者和我。这只需每月$5,你就可以完全访问所有精彩的 Medium 文章,并有机会通过你的写作赚取收入。
如果你通过 我的链接注册, 你将直接用你的费用的一部分支持我,而不会额外增加你的费用。如果你这样做,非常感谢你的支持。
使用 Plotly Express 旭日图探索地质数据
原文:
towardsdatascience.com/using-plotly-express-sunburst-charts-to-explore-geological-data-841f179d08c8
用 Python 轻松快速地理解你的地质层级数据
·发表于数据科学前沿 ·阅读时间 5 分钟·2023 年 7 月 27 日
–
在互动式 Plotly Express 旭日图上展示的地质层级。图像由作者提供。
数据可视化在地球科学和数据科学领域发挥着至关重要的作用。它可以让我们深入了解地下情况,理解地质结构和层级关系。地下通常被细分为不同的类别,从最广泛的地质时间范围,如时代、纪、世,到岩性差异,如砂岩、石灰岩和页岩。
在处理地质层级数据时,数据可以以多种方式进行可视化。这包括传统的地质时间尺度图表和表格以及互动式旭日图。
旭日图(Sunburst charts)可以用独特的方式展示数据,是可视化地质层级数据等层级数据的好方法。它们通过使用多层同心圆环图来实现,这些图表可以根据使用的工具进行完全交互,帮助从最高层级钻取到最低层级。
为了演示这些图表,我们将使用Plotly Express,一个高级数据可视化 Python 库,从挪威大陆架上的一个井中获取一些数据,并可视化地质层级以及每个地层的岩性组成。我们还将看到如何在创建图表之前准备井中的数据。
导入库并加载数据
首先,我们需要两个库:pandas 用于加载和处理数据,plotly_express 用于创建可视化。
import pandas as pd
import plotly_express as px
接下来,我们将从 CSV 文件加载数据。有关使用的数据的详细信息可以在文章底部找到。
如果你有 LAS 文件,可以快速使用加载 LAS 文件功能,通过LASIO库来加载文件,然后将数据转换为pandas数据框。
df = pd.read_csv('Data/Xeek_Well_15-9-15.csv')
清理和准备数据
现在我们已经加载了数据,我们需要做一点清理。
首先,我们将删除 Formation 列中任何缺失信息的行,以简化问题。请注意,这样做可能会影响后续岩石类型和地层的计数,因此任何缺失数据应彻底检查和理解。
删除数据的替代方法是用占位符值(如“未知”)替换缺失数据。
data_cleaned = df.dropna(subset=['FORMATION'])
当我们查看数据框的前五行时,我们得到如下内容。
删除包含缺失值的行后,数据的前 5 行。图片由作者提供。
下一步是将数据结构化,以便在 sunburst 图上正确绘制。
为此,我们需要进行以下操作
-
按以下顺序对数据进行分组:Group、Formation 和 Lith。
-
计算每个 Group、Formation 和 Lith 的唯一组合的出现次数
grouped_data = data_cleaned.groupby(['GROUP', 'FORMATION', 'LITH']).size().reset_index(name='count')
当我们查看新的grouped_data
数据框的头部时,会得到每个地质层的岩石类型概述,这些概述随后与 Group 相关联。
分组和计数每个地质层岩石类型后的前五行数据框。图片由作者提供。
使用 Plotly Express 创建 Sunburst 图
现在数据已经是正确格式,我们可以最终使用 Plotly Express 创建 Sunburst 图。
我们要做的就是调用px.sunburst
并传入一些参数。
首先,我们传入分组数据框,然后为path
参数指定我们希望显示的环的顺序。在我们的情况下,我们将按 Group 到 Formation 再到 Lithology 的顺序。
接下来,我们将values
参数设置为计数列。这将允许我们控制每个岩石类型的切片大小。岩石类型的数量越多,切片就会越宽。
然后,我们设置图形的width
和height
,以及title
。
最后,我们希望岩石类型的颜色是不同的;然而,我们也可以将其设置为数据框中的任何其他列或自定义颜色刻度。
fig = px.sunburst(grouped_data,
path=["GROUP", "FORMATION", "LITH"],
values='count',
title="Geological Distribution within well 15/9-15",
width=800,
height=800,
color='LITH')
fig.show()
当我们运行上述代码时,我们会得到以下图形,这为每个地质组和地层以及不同岩石类型的分布和贡献提供了出色的概述。
太阳辐射图显示了与地层相关的不同地质群、地层和岩性。图像来源:作者。
使用 Plotly 的一个优点是它默认是互动的。我们不需要像使用 matplotlib 时那样添加额外的 Python 代码来使图表具有互动性。
在下面的示例中,我们可以深入探讨我们的数据,了解特定地层的岩性组成。
动画太阳辐射图显示了与地层相关的不同地质群、地层和岩性。图像来源:作者。
摘要
使用 Plotly 创建的太阳辐射图是展示地质信息的绝佳方式。它们提供了出色的可视化效果,并允许你通过互动深入数据,以了解地质层次结构中每一层的组成。
通过一些预处理,容易将现有的地质信息从井中提取并转换为适合绘图的格式。此外,在处理互动图形时,你对数据的感知要比研究静态图形要好得多。而且,它们也更有趣。
本教程中使用的数据集
训练数据集用于 Xeek 和 FORCE 2020 举办的机器学习竞赛 (Bormann et al., 2020)。此数据集受 Creative Commons Attribution 4.0 International 许可证保护。
完整的数据集可以通过以下链接访问:doi.org/10.5281/zenodo.4351155
。
感谢阅读。在你离开之前,你一定要订阅我的内容,以便将我的文章发送到你的邮箱。 你可以在这里订阅!
其次,你可以通过注册会员获得完整的 Medium 体验,并支持成千上万的其他作家和我。只需每月$5,你就可以全面访问所有精彩的 Medium 文章,并有机会通过写作赚钱。
如果你通过 我的链接注册, 你将直接用你的一部分费用支持我,这不会增加你的费用。如果你这样做,非常感谢你的支持。
使用 Polars 插件通过 Rust 实现 14 倍速度提升
在本地 Polars 库之外实现高速度
·
关注 发表在 Towards Data Science ·8 min read·2023 年 11 月 9 日
–
由 DALL-E 3 生成
介绍
Polars 正因其速度、内存效率和美观的 API 而风靡全球。如果你想了解它有多强大,不妨看看 DuckDB Benchmarks。而且这些测试甚至还没有使用 Polars 的最新版本。
尽管 Polars 可以做很多惊人的事情,但它传统上并不比 Pandas 更适合做你可能想做的所有计算。有几个例外情况 Polars 并未超越。不过,随着最近 Rust 的 Polars 插件系统的发布,这种情况可能会发生变化。
Polars 插件
什么是 polars 插件?它只是创建自己 Polars 表达式的一种方法,使用原生 Rust 并通过自定义命名空间将其暴露给表达式。它允许你利用 Rust 的速度,将其应用到你的 Polars DataFrame,以便以利用 Polars 提供的速度和内置工具的方式执行计算。
让我们看一些具体的例子。
顺序计算
Polars 在某些功能上似乎有所欠缺,例如那些需要知道 DataFrame 之前值的操作。性质上是顺序的计算在原生 Polars 表达式中并不总是特别容易或高效。让我们看看一个具体的例子。
我们有以下算法来计算给定运行中的数字数组的累计值,该运行定义为具有相同符号的一组数字。例如:
┌───────┬───────────┐
│ value ┆ run_value │
│ --- ┆ --- │
│ i64 ┆ i64 │
╞═══════╪═══════════╡
│ 1 ┆ 1 │ # First run starts here
│ 2 ┆ 3 │
│ 3 ┆ 6 │
│ -1 ┆ -1 │ # Run resets here
│ -2 ┆ -3 │
│ 1 ┆ 1 │ # Run resets here
└───────┴───────────┘
我们希望计算一个列的累计和,每当值的符号从正数变为负数或从负数变为正数时,累计和会重置。
让我们从一个用 pandas 编写的基准版本开始。
def calculate_runs_pd(s: pd.Series) -> pd.Series:
out = []
is_positive = True
current_value = 0.0
for value in s:
if value > 0:
if is_positive:
current_value += value
else:
current_value = value
is_positive = True
else:
if is_positive:
current_value = value
is_positive = False
else:
current_value += value
out.append(current_value)
return pd.Series(out)
我们遍历一个系列,计算每个位置的当前运行值,并返回一个新的 Pandas Series。
基准测试
在继续之前,我们将设置几个基准。我们将使用 pytest-benchmark 和 pytest-memray 测量执行速度和内存消耗。我们将设置问题,使得我们有一个实体列、一个时间列和一个特征列。目标是计算数据中每个实体在时间上的运行值。我们将把实体和时间戳的数量都设为 1,000,使我们拥有一个包含 1,000,000 行的 DataFrame。
当我们使用 Pandas 的 groupby apply 功能对我们的基准进行测试时,我们得到以下结果:
Pandas Apply Pytest-Benchmark(作者提供的图片)
Pandas 应用的 Memray 输出(作者提供的图片)
Polars 原始实现
好的,现在我们有了基准测试。接下来,让我们看看如何在 Polars 中实现相同的功能。我们将从一个非常相似的版本开始,该版本将通过映射函数应用到 Polars GroupBy 对象上。
def calculate_runs_pl_apply(s: pl.Series) -> pl.DataFrame:
out = []
is_positive = True
current_value = 0.0
for value in s:
if value is None:
pass
elif value > 0:
if is_positive:
current_value += value
else:
current_value = value
is_positive = True
else:
if is_positive:
current_value = value
is_positive = False
else:
current_value += value
out.append(current_value)
return pl.DataFrame(pl.Series("run", out))
现在让我们看看这与我们原来的 Pandas 基准测试的比较结果。
Pandas Apply 与 Polars Apply Pytest-Benchmark(作者提供的图片)
Polars 应用的 Memray 输出(作者提供的图片)
好吧,这样做效果并不好。不过这不应该让人感到惊讶。Polars 的开发者已经明确表示,Pandas 中非常常见的 groupby apply 方法在 Polars 中并不是一种高效的计算方式。这一点在这里得到了体现。速度和内存消耗都比我们原来的 Pandas 实现要差。
Polars 表达式实现
现在让我们把这个函数写成原生的 Polars 表达式。这是与 Polars 一起工作时的首选和优化方式。算法会看起来有些不同。但这是我想出的计算相同输出的方案。
def calculate_runs_pl_native(df: pl.LazyFrame, col: str, by: str) -> pl.LazyFrame:
return (
df.with_columns((pl.col(col) > 0).alias("__is_positive"))
.with_columns(
(pl.col("__is_positive") != pl.col("__is_positive").shift(1))
.over(by)
.fill_null(False)
.alias("__change_sides")
)
.with_columns(pl.col("__change_sides").cumsum().over(by).alias("__run_groups"))
.with_columns(pl.col(col).cumsum().over(by, "__run_groups").alias("runs"))
.select(~cs.starts_with("__"))
)
对我们正在做的事情的简要说明:
-
查找所有特征值为正的行
-
查找所有
__is_positive
列与前一行不同的行。 -
对
__change_sides
进行累加,以标记每个不同的运行 -
对每个不同的运行中的值进行累加
现在我们有了原生的 Polars 函数。让我们再次进行基准测试。
Pandas Apply 与 Polars Apply 与 Polars Native Pytest-Benchmark(作者提供的图像)
Polars Native 的 Memray 输出(作者提供的图像)
不幸的是,我们没有看到函数执行速度的提升。这很可能是因为我们需要做的 over
语句数量来计算运行值。然而,我们确实看到了预期中的内存减少。可能还有更好的方法使用 Polars 表达式来实现这一点,但我现在不打算担心这个问题。
Polars 插件
现在让我们来看看新的 Polars 插件。如果你想要设置这些插件的教程,可以查看 这里的文档。 在这里,我主要展示插件的具体实现。首先,我们将用 Rust 编写我们的算法。
use polars::prelude::*;
use pyo3_polars::derive::polars_expr;
#[polars_expr(output_type=Float64)]
fn calculate_runs(inputs: &[Series]) -> PolarsResult<Series> {
let values = inputs[0].f64()?;
let mut run_values: Vec<f64> = Vec::with_capacity(values.len());
let mut current_run_value = 0.0;
let mut run_is_positive = true;
for value in values {
match value {
None => {
run_values.push(current_run_value);
}
Some(value) => {
if value > 0.0 {
if run_is_positive {
current_run_value += value;
} else {
current_run_value = value;
run_is_positive = true;
}
} else if run_is_positive {
current_run_value = value;
run_is_positive = false;
} else {
current_run_value += value;
}
run_values.push(current_run_value);
}
}
}
Ok(Series::from_vec("runs", run_values))
}
你会注意到这看起来与我们在 Python 中编写的算法非常相似。我们在这里没有做任何花哨的 Rust 魔法!我们使用 Polars 提供的宏来标注输出类型,仅此而已。然后我们可以将新的函数注册为表达式。
from polars import selectors as cs
from polars.utils.udfs import _get_shared_lib_location
lib = _get_shared_lib_location(__file__)
@pl.api.register_expr_namespace("runs")
class RunNamespace:
def __init__(self, expr: pl.Expr):
self._expr = expr
def calculate_runs(
self,
) -> pl.Expr:
return self._expr.register_plugin(
lib=lib,
symbol="calculate_runs",
is_elementwise=False,
cast_to_supertypes=True,
)
然后我们可以这样运行它:
from polars_extentsion import RunNamespace
df.select(
pl.col(feat_col).runs.calculate_runs().over(entity_col).alias("run_value")
).collect()
好了,现在让我们查看结果吧!
所有实现 Pytest-Benchmark(作者提供的图像)
Polars 插件的内存输出(作者提供的图像)
这才像样!我们获得了 14 倍的速度提升,内存分配从 ~57MiB 降低到 ~8MiB。
何时使用 Polars 插件
现在我已经展示了使用插件的强大功能,我们来谈谈不应使用插件的情况。我可能不使用插件的一些原因(每个都有其自身的注意事项):
-
如果您可以轻松使用原生 Polars 表达式编写一个非常快速的计算版本。 Polars 的开发者非常聪明。我不会赌自己写的函数比他们编写的快很多。Polars 的工具已经存在。利用它们擅长的部分吧!
-
如果您的计算没有自然的并行化。例如,如果我们没有在多个实体上运行上述问题,我们的加速可能会显著减少。我们既受益于 Rust 的速度,也受益于 Polars 自然能够同时对多个组应用 Rust 函数的能力。
-
如果您不需要顶级的速度或内存性能。 许多人会同意,编写 Rust 要比编写 Python 更困难且耗时。所以,如果您不介意您的函数运行时间从 200 毫秒变成 2 秒,您可能不需要使用插件。
牢记上述内容,这里有一些我感觉有时会让我倾向于使用插件的要求:
-
速度和内存非常重要。 我最近在一个 Polars 插件中重写了很多数据管道的功能,因为我们在 Polars 和其他工具之间来回切换,内存分配变得太大了。在我们想要的数据量下,难以在我们希望的基础设施上运行管道。插件使我们能够在更短的时间和更小的机器上运行相同的管道。
-
您有一个独特的使用案例。 Polars 提供了很多内置函数。但它是一个广泛适用的通用工具集。有时,这个工具集并不特别适用于您试图解决的问题。在这种情况下,插件可能正是您需要的。我遇到的两个最常见的例子是更复杂的数学计算,例如应用横截面线性回归,或顺序(基于行的)计算,就像我们在这里展示的那样。
新的插件系统是对 Polars 已经支持的所有列式计算的完美补充。通过这个补充,Polars 允许其功能有美妙的扩展性。除了编写自己的插件之外,还要留意一些酷炫的 Polars 插件包,它们可以扩展您的功能而无需自己编写插件!
Polars 发展迅速并引起了广泛关注。查看这个项目,开始使用它,留意他们将发布的其他精彩功能,也许在此期间还可以学习一些 Rust!
数据科学中的概率词使用
将模糊的反馈转化为机器学习的具体概率
·
关注 发表在 Towards Data Science ·9 分钟阅读·2023 年 2 月 7 日
–
图片由 Christina @ wocintechchat.com 提供,来自 Unsplash
在开始新的数据科学模型时,你必须评估提供给你的数据。通常,新的数据科学项目始于一个数据集,并与主题专家或其他联系人进行联系。主题专家为数据集的意义提供额外的背景。这包括数据集中的异常值或例外情况,以及主题专家认为的“正常”或“异常”情况,或“总是”或“从不”发生的情况。但如果“从不”并不意味着“从不”,“总是”也不意味着“总是”呢?这些词被称为“概率词”,包含其他地方无法找到的关键信息。本文讨论了如何使用这些概率词来了解你的数据并改进你的模型。
“从不”并不意味着“从不”
定义概率词
概率词是表达不确定性或概率的词语。它们包括“也许”,“可能”,“大概”,“很可能”,“不太可能”,“可能”,“不可能”等词语。这些词用来表示某人对事件的信念或信心的隐性分布。
每个人,无论是有意识还是无意识地,都在脑海中为这些概率词分配一个概率。然而,这些词的确切概率常常被个人对词语的解释所掩盖,从而与数据的关联变差。例如,“通常”是指 40%还是 80%的时间?这一定义在不同人和情况之间可能有所不同。一旦提取出这些概率的解释,就可以将其融入建模开发过程中。
人们心中的概率范围可以发生剧烈变化。
概率词与以往的工作
两项涉及概率词的显著研究包括 1993 年美国中央情报局(CIA)谢尔曼·肯特的《估计概率的词汇》(Words of Estimative Probability (cia.gov))。第二篇文章是由安德烈·莫博辛和迈克尔·J·莫博辛于 2018 年在《哈佛商业评论》中撰写的《如果你说某事“很可能”,人们认为它有多可能?》
肯特撰写的研究旨在解决在情报行业中,人们常用不具体的陈述来描述事件发生的可能性。在文章中,他利用了一组报告样本来建立单词与概率之间的映射,将数字赋予人们回应的不确定性。原始表格的输出范围从 0%的不可能到 100%的确定性,中间有一个“可能性的一般区域”。“可能性的一般区域”包含 7 种概率短语。这些词按确定性递增的顺序为:“不可能”,“几乎不可能”,“可能性不大”,“变化不定”,“可能”,“几乎确定”和“确定”。
后来,安德烈和迈克尔·莫博辛进行了跟进研究,进行了更新的调查,包含了更多不同的词汇。他们的目标是增加研究参与者的数量,并扩展到情报界之外。他们在网络上调查用户,将词汇与其解释的概率相关联。作者还试图识别其他背景方面的差异,例如性别和将英语作为第二语言的人。研究的一个教训是,人们应该使用概率来解释数据,而不是使用概率词,以避免在分享数据见解时的误解。此外,人们应使用明确的方法来收集概率。
本节仅为文章的简要概述,我强烈推荐完整阅读这些文章。但问题仍然存在——这对数据科学意味着什么?
“通常”到“罕见”的范围因人而异。
在数据科学中使用概率词汇
将模糊的词汇含义与具体示例联系起来,是扩展数据集知识和增加额外知识的极好方法。这些额外的信息可以增加模型可用的信息,提高模型性能。
了解数据背景
除了传统的数据探索技术,还有许多不同的方法可以深入了解数据集。这可以通过各种方法完成,通常涉及与受访者的讨论。这个人可以是主题专家、内容审核团队、会计师、用户,或与数据集、行业或问题相关的人。
与受访者交谈时,准备识别他们的概率词汇。首先询问数据的一般统计行为,如相关性,并将每个相关性视为假设。这个假设由受访者根据他们的经验来证明或反驳。目标是倾听人们在表达行动意见时所使用的限定词。你可以利用这些概率词汇来识别他们经验中的正常、异常和异常值。这也可以用于双重检查你的数据集是否与任务一致。例如,数据集中或相关人员中是否存在你不知道的偏差?受访者认为异常的事情在数据中是否经常发生?
在识别正常数据、异常数据和离群值时,脑海中或画出分布图会有所帮助。通过寻找概率词汇,我们试图识别受访者的事件样本在分布中的位置。然而,必须确保我们从受访者的角度收集数据。在数据中看似正常的事件在业务流程中可能非常意外或不寻常——这种知识在建模时是金子般的宝贵。
关于利率变化的隐式分布示例。
例如,设想你是一个数据科学家,任务是开发一个模型来预测美联储是否会提高利率。在学习利率、美联储的动作以及市场反应时,获取专家的观点至关重要,这能让我们了解他们认为哪些因素会影响这些决定。比如,我们询问一位基金经理他们认为利率会如何变化,他们说:“可能会以较慢的速度上涨。”在这种情况下,重要的是要求交易员用概率术语(比例、百分比等)来量化“可能”对他们来说意味着什么。通过要求交易员用百分比术语表达他们的理解,我们可以开始建立对以下内容的理解:
-
“可能”对他们来说意味着什么,以及实现这一点需要什么经济背景
-
什么会导致几乎肯定的正面或负面利率变动
-
他们认为美联储在这种情况下通常会做什么
-
一个不寻常的 负面利率变动会是什么样子
-
一个不寻常的 正面利率变动会是什么样子
通过要求交易员详细描述每个回应的背景来跟进。澄清交易员提到的每个词的概率可能性,并寻找口头解释的统计见解。这些见解包括多重共线性、次要效应以及其他影响模型性能但不在训练数据集中的来源。为了减少结果的偏差,尽量采访几个人。
有了这些信息,你对可能导致利率变化的原因会有更多了解,并能构建出交易员的信念。数据甚至可以用来建立一个网络抓取的情感模型,将外部情感转化为业务情感。
增强你的数据集
使用概率词汇,可以增强数据集以包含见解。例如,你可以添加一个分类列,标记“异常”情况。你可以使用这些数据预测更大数据集上的反馈,将其作为机器学习模型的原始输入,并量化这些“人类”信息对数据的价值。
一个包含反馈信息的示例数据集。
使用数据来预测更大数据集上的反馈被称为“弱学习”。在这种情况下,构建了一个模型,该模型利用反馈样本来预测其余数据集上的反馈内容。这意味着反馈样本可以扩展到覆盖整个数据集。然后,这些扩展的反馈可以作为另一个模型的输入或用于探索性数据分析。这种方法的好处是可以将数据样本扩展到覆盖大量数据。然而,这也带来了准确性的成本。由于模型是在小样本上训练的,模型可能会有更高的偏差或表现不完全符合受访者的实际情况。
如果你需要即时反馈以作为模型的一部分进行预测,那么可以使用“弱学习”来构建一个“即时反馈”系统。随着模型进行在线预测,“弱学习”模型接收原始数据,预测反馈内容,然后将原始数据和预测的反馈传递给主在线模型。这允许你构建一个功能齐全的模型,而无需人类时刻参与。
收集这些数据的另一个好处是,它可以用来量化采访提供的信息比原始数据更多。可以通过构建有反馈和没有反馈的模型来评估这一点。在训练完两个模型后,比较模型分数之间的差异,这将给出反馈的相对价值。如果你的模型在使用反馈数据后比仅使用原始数据训练的模型提高了 15%,那么这证明采访提高了模型的性能。如果这 15%的提升可以与业务影响挂钩,这可以帮助证明采访成本并给出反馈的美元价值。例如,如果一个预测模型的性能提高了 15%,而这转化为 20 万美元的价值,那么反馈的价值就是 20 万美元。
构建你自己的概率调查
现在概率词汇已经介绍完毕,这些知识可以用于创建你的概率调查。
要开始,可以从之前提到的原始研究中的常见概率词汇中获得灵感。可以添加你自己和你组织中常用的概率词汇。花一周时间识别你会议中使用的常见词汇并保持一个常见项目的清单也可能会有帮助。我在职业背景中使用的一个例子是“不确定性”。如果我正在为我的组织编写一个概率调查,我会想在词汇列表中包括像“极大不确定性”和“几乎确定”这样的条目以获取反馈。记住,你总是可以稍后添加词汇并收集更多反馈,所以不需要追求完美。
一旦收集了词汇列表,需要创建一个架构来从人们或其他数据源收集词汇。如果你已经有数据源,可以使用你喜欢的方法将数据输入到你的流程中。如果你正在收集来自组织内部人员的反馈,建立一个简单的调查架构以便收集信息是有益的。这可以包括像 Google Forms、Microsoft Forms 和 Streamlit 这样的工具。我的常用选择是 Streamlit,因为它设置迅速,使用 Python 构建,并且可以在本地 PC 上快速运行,或在其网站上运行。
你也可以收集每个人提供反馈时的基本元数据。像公司级别、部门和工作年限这样的元数据可能对细分不同部门如何使用概率词汇很有帮助。一旦收集了大量反馈,你可以收集数据并分析每个回应的分布。从这些分布中,你将能够回答如下问题:
-
哪些词具有相同的概率含义?
-
定义相似的词是否具有不同的概率?
-
例如,“确定性”和“绝对确定”可能分别与 70% 和 95% 的平均概率相关。
-
一个词的概率分布是什么?
-
这些词的分布特征是什么?(均值、中位数、众数、标准差等)
在这项分析之后,你可以利用这些信息帮助将面试、反馈会话和探索性数据分析中收集的非确定性词汇连接起来。这些数据可以用于数据管道中,以分类陈述中的概率、识别独特情况并改进模型结果。
除非另有说明,否则所有图片均由作者提供。
使用倾向评分匹配来构建领先指标
关于构建产品激活指标的简短指南
·
关注 发布于 Towards Data Science · 6 分钟阅读 · 2023 年 3 月 4 日
–
在上一篇文章中,我讨论了“输入 > 输出 > 结果”框架,以及“输出”是核心部分,但并不一定容易定义——仅仅因为你希望它受到输入的影响,但同时,你需要与结果有因果联系。
用户激活指标属于这种类型的指标。“激活”是由 Dave McClure(著名的 AAARRR 框架——意识、获取、激活、保留、推荐、收入)设计的海盗指标框架的第三个阶段,它通常定义为用户克服了第一组障碍,开始使用你的产品,从中获得了一些价值,并且现在更有可能在长期内被保留。
Some examples of product activation metric:
Loom: Sharing a loom¹
Zappier: Setting a zap¹
Zoom: Completing a zoom meeting within 7d of signup¹
Slack: Sending 2,000+ team messages in the first 30 days²
Dropbox: Uploading 1 file in 1 folder on 1 device within 1 hour²
HubSpot: Using 5 features within 60 days²
¹2022 product benchmark from Open View
https://openviewpartners.com/2022-product-benchmarks/
²Stage 2 Capital: the science of scaling:
https://www.stage2.capital/science-of-scaling
测量激活是重要的,因为它帮助你了解你的产品对新用户的吸引力如何,以及你是否有效地让他们成为“活跃”用户。这是通向用户忠诚的第一步——在这个阶段你可以知道用户是否可能长期留在你的产品中。如果激活率较低,可能表示产品或入驻流程存在问题,可能需要做出调整以改善用户体验并提高激活率。
定义产品激活为何困难
-
你希望激活能成为保留的良好预测指标,但同时,你希望它足够简单,因为这应该是用户遵循的简单第一步。
-
基本上,你要寻找用户能够采取的最小动作,这个动作可以展示产品的价值,但你希望这个小动作与保留有因果关系(无论你如何定义它)。
-
与任何“领先”指标一样,因果关系部分(“做某个动作导致长期保留”)是困难的。你通常从观察数据开始,传统的数据分析可能无法提供完整的图景,因为它可能忽略了影响激活/保留的混杂因素。
使用队列分析和倾向得分匹配来调查因果关系
通过队列分析,你可以开始建立对哪些用户行为可能是激活指标的良好候选的直觉。
其目的是:
-
根据用户注册你的产品的位置对他们进行分组。
-
根据用户是否达到了保留阶段将他们分开。
-
寻找那些在达成保留阶段的用户中做得非常多的动作,而在未达成的用户中做得较少的动作。
假设你运营一个健身应用。你开始创建每月的队列,并且注意到,70%在注册后的第一周内上传至少一次锻炼的用户在一年后仍然活跃于应用中,而如果他们没有上传,则只有 40%。这可以成为激活指标的初步想法。
这里的前提条件是你要弄清楚要研究哪个行动。在上述示例中,你必须想到查看谁跟踪了他们的锻炼。这是定量与定性相结合的地方,也是你的‘用户洞察’/常识发挥作用的地方。或者,如果你想寻求其他领域专家的帮助,那就需要你的网络技能。
一些建议:
-
你可能只想提出几个潜在的行动想法,不必过多地研究它们,因为俗话说:“如果你折磨数据足够久,它会承认任何事”(罗纳德·H·科斯)。你选择的行动越多,你发现一些东西的可能性就越大,但你也面临较高的假阳性风险。因此,坚持有意义且不过于牵强的东西可以作为一个好的经验法则。
-
你可能想要采用一种原则性的方法,只寻找你认为能够移动的事物。如果你提出的方案过于复杂或小众,你可能无法实施,这将使整个过程失去意义。
倾向得分匹配可以验证或否定你之前的直觉
一旦你确定了潜在的激活信号,下一步是确保它们的准确性。这时倾向得分匹配就能派上用场——以了解你之前找到的相关性是否实际上是因果关系。虽然这不是唯一的解决方案,并且确实需要对你的用户有一些了解(这可能并非总是可能的),但它相对容易实施,并且能让你对结果更有信心(直到进一步通过更稳健的方法如 A/B 测试进行三角验证)。
倾向得分匹配的理念如下:
-
为了找到采取行动与留存之间的因果联系,理想情况下,你可以复制采取行动的用户,并让克隆体不采取行动——以比较结果。
-
由于目前还无法做到(或许?),下一步最佳的方法是查看你的数据,找到与你采取行动的用户非常相似(几乎相同)的用户——但他们没有采取行动。
倾向得分匹配是一种方法学,可以帮助你找到这些非常相似的用户并进行配对。具体来说,它包括:
-
训练一个模型来预测你的用户采取你定义的行动的可能性(他们的倾向)。
-
基于之前发现的可能性来匹配用户(匹配部分)
(注意:你有多种方法可以完成这两个步骤,关于如何选择模型、如何选择合适的变量、选择什么匹配算法等方面,网上有一些很好的指南——有关更多信息,请参见 “倾向得分匹配的实施实践指南”)
再次以我们的健身应用程序为例:
-
你已经发现,在注册后的第一周上传至少一个锻炼计划的用户,仍然在一年后继续使用应用的比例为 70%,而如果他们没有上传,则为 40%。
-
你训练一个模型来预测用户在注册后一周内上传锻炼计划的可能性——你发现,通过大型健身网站的推荐链接下载应用的用户,其可能性非常高。
-
你根据可能性对用户进行排序,然后开始进行简单的 1:1 匹配(按可能性排序的第一个采取行动的用户与按可能性排序的第一个未采取行动的用户匹配,依此类推)。
-
匹配后,你会发现差异大大缩小,但这仍然对你考虑它作为潜在的激活指标很重要!
定义产品激活的万灵药?
阶段分析 + 倾向评分匹配可以帮助你隔离特定行动对用户行为的影响,这对于定义准确的激活指标至关重要。
但这种方法并不是万灵药——这套方法论有一堆假设,你需要对其进行微调/验证,以确保它适用于你的使用场景。
特别是,PSM 的有效性将高度依赖于你能预测自我选择的准确程度。如果你缺少关键特征,而未观察到的特征带来的偏差很大——那么 PSM 的估计可能会非常偏颇,并且不够有用。
综上所述——即使以不完美的方式使用这种方法,也能帮助你采用更加数据驱动的方法来选择指标,让你开始‘关注的内容’,直到你进入 A/B 测试阶段,并更好地理解推动长期成功的因素。
希望你喜欢阅读这篇文章!你有任何想分享的技巧吗?请在评论区告诉大家!
如果你想了解更多关于我的内容,以下是你可能喜欢的几篇文章:
增加你结果的信心,建立更强的个人品牌
towardsdatascience.com](/7-tips-to-avoid-public-embarrassment-as-a-data-analyst-caec8f701e42?source=post_page-----3e656dccbaf9--------------------------------) [## 如何构建成功的仪表盘]
来自某个曾经构建过几个不成功的案例的清单
towardsdatascience.com](/how-to-build-a-successful-dashboard-359c8cb0f610?source=post_page-----3e656dccbaf9--------------------------------) [## 如何选择数据项目进行工作]
如果你对如何使用时间有一个合理的方法,你可以优化你所创造的价值。
使用 Python 解决工程中最常见的问题之一
创建一个用于工作点分析的通用框架
·发布于 Towards Data Science ·17 分钟阅读·2023 年 1 月 2 日
–
在工程中,某些问题经常出现。本文的重点是讨论一种在我日常工作中经常遇到的问题,我决定分享如何使用 Python 来解决它。我们讨论的是什么类型的问题?就是解决系统工作点的问题!在深入复杂的代码之前,让我们用一个简单的例子来说明我的意思。
我们希望解决下图所示简单电路的工作点。这可以通过重新排列欧姆定律 (V=IR) 来实现,从而根据已知的输入电压和电阻隔离电流。
图片由作者提供
简单,对吧?不幸的是,大多数现实世界的问题都没有这么简单。例如,如果我告诉你,电阻器加热时,其电阻值发生变化,这实际上使电阻成为电流的函数。我们会得到如下形式的方程:
如果不知道实际的电阻函数形式,我们不能仅仅通过代数方法来求解电流。此外,如果方程很复杂,无法单独隔离电流怎么办?或者,假如电阻以电流的形式给出,并且是离散的表格数据 — 那么我们甚至没有代数表达式来操作以尝试求解电流。那我们应该如何确定电路中的电流呢?我们需要一种更通用的方法来解决这个问题。
解决这类问题的一般方法是将其表示为根查找问题。这实际上非常简单——我们只需将方程的右侧从左侧中减去,使得方程等于零。这样得到如下:
通过这样做,我们重新提出了问题。我们不再直接求解电流值,而是尝试找到一个电流值,将其输入方程的左侧,使其结果为零。我们为什么这样制定问题?因为存在大量的数值算法(如二分法、牛顿法等)来解决这种精确的问题!而且大多数算法不在乎方程的左侧有多复杂——它甚至不需要有封闭的代数形式(即,它可以由插值的离散数据、数值评估的积分或任意复杂度的函数组成)。只要我们能将问题表示为 f(x)=0,我们(几乎)总能找到问题的解决方案(如果问题陈述改变,代码也可以轻松修改/扩展——不需要重新做代数)。
本文接下来将通过一个例子来讲解如何将根查找方法应用于一个稍微复杂一点的实际问题,重点是 Python 中的代码结构和组织技巧。尽管这个问题(确定管道/泵系统中的水流量)有些领域特定,但所使用的方法和编码技术完全通用,适用于所有工程领域。考虑到这一点,我会尽量保持问题的物理建模方面处于高层次,使得不论技术背景如何,文章的主要学习目标仍然清晰明了。
作为旁注,我目前的领域“专长”在电机控制和功率电子学领域,与泵/管道应用相去甚远。我已经有好几年没有涉及这一主题,但认为它会是一个有趣的例子。我确信有许多人比我更有资格讨论泵/管道建模的具体细节,但我写这篇文章的意图在于方法论——而不是如何解决泵/管道问题。无论如何,我欢迎那些对该领域更了解的人的评论或改进建议!
问题
我们想要将水从一个水箱转移到另一个水箱。我们已经有一个泵和一些管道可以用来连接两个水箱,想要估算转移所有水所需的时间。每个水箱的体积是已知的,因此,如果我们可以估算水在两个水箱之间的流量,我们就可以估算转移过程需要多长时间。完整的设备如下所示。
图片由作者提供
这个特定问题(可以归类为“内部流动”问题)在机械工程领域中非常清楚。对于那些不太熟悉的人,或者需要快速复习的人,我们通常通过伯努利方程来解决这些问题(如下所示)。
伯努利方程本质上是一个能量守恒的陈述,它告诉我们流体粒子在沿流线(即如果流体中丢下一个假想粒子其会沿着的流动路径)移动时,能量如何在不同的能量机制之间转换。方程的左边表示流体粒子在任意第一个位置(位置 1)处的总单位重能量,是重力势能项、动能项和压力项的总和。当流体在系统中流动时,能量必须守恒,因此在流线上的任意第二个点(位置 2)处的总能量(由方程右边表示)必须等于位置 1 的总能量。
上述形式的伯努利方程被称为“扬程”形式,因为每个项的单位是长度/高度。这对我们的直觉很方便,因为我们基本上是在将每个项的能量等同于具有给定扬程高度的流体柱的重力势能。然而,伯努利方程的一个主要限制是它假设系统中没有损失(这并不是一个很好的假设)。为克服这一限制,我们可以在方程中补充两个附加项,如下所示:
Hp(Q)和 Hl(Q)项分别表示泵对系统增加的扬程和由于实际世界效应(如摩擦、粘度等)在系统中损失的扬程。注意这两个项都是系统流体流量 Q 的函数。(作为上述描述扬程解释的一个有趣结果,泵的扬程告诉你泵理论上能将流体推高多少)。我们将稍后更详细地检查泵和损失项,但在此之前,让我们简化上述方程以解决我们的具体问题。
再次查看上面的系统,我们将方便地选择两个位置用于伯努利方程,使得大多数项相互抵消。我们可以通过选择位置 1 和 2 分别位于每个水箱的自由水面来做到这一点,此时压力是常数且等于大气压力(P1=P2),速度大约是常数且为零(V1=V2=0)。我们还将假设在我们分析系统的瞬间两个水箱中的水高度相同,即 Z1=Z2。经过代数简化后,我们发现几乎所有项都抵消了,剩下的就是泵产生的扬程必须等于由于非理想性导致的系统损失的扬程。换句话说,泵在弥补系统中的任何能量损失。
在下图中可以定性地看到这种情况。泵产生的扬程随着流量的增加而减少,而管道系统中的损失随着流量的增加而增加。两条曲线交点(泵扬程 = 损失扬程)决定了系统的工作点(流量)。
作者提供的图片
在我们可以跳入代码之前的最后一步是将问题表示为根寻找问题。将方程右侧减去左侧,我们得到我们正在寻找的根求解问题。也就是说,我们将问题表述为:找到流量(Q),使得下面方程的左侧等于零。此时,泵扬程将等于系统的扬程损失。
代码
为了避免丢失我们正在做的大局,我不会解释代码的每一个细节(假设你已经具备合理的 Python 背景)。相反,我会集中精力确保叙述和代码结构清晰,并在需要时提供更多细节。与往常一样,如果有任何不清楚的地方,请随时提问。
设置
我们将从导入所有必要的模块开始。稍后将明显每个模块的使用方式,但值得注意的是,关键的导入语句来自 scipy。这些是针对当前问题的特定函数。代码块还设置了一些默认绘图设置(根据个人口味),创建了一个文件夹来保存生成的图形,并定义了一些单位转换常数,使得后续代码中的操作更加简便。
from dataclasses import dataclass
from pathlib import Path
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
#these are the key libraries for solving the problem
from scipy.interpolate import interp1d
from scipy.optimize import root_scalar
#set plotting defaults
plt.style.use('seaborn-v0_8-darkgrid')
plt.rcParams['font.family'] = 'Times New Roman'
plt.rcParams['font.size'] = 12
figsize = (6.4,4)
#make folder to save plots to
plots_folder = Path('plots')
plots_folder.mkdir(exist_ok=True)
#define conversion constants for ease of use later
INCHES_TO_METERS = 25.4/1000
FEET_TO_METERS = 12*INCHES_TO_METERS
GALLONS_TO_M3 = 0.0037854118 #convert gallons to m³
接下来,我们将创建一个 Python dataclass
,它实际上充当了一个存储流体属性(密度、粘度和重力)的容器。默认情况下,这些属性设置为水的属性。请注意,虽然这不是完全必要的,但 Python 数据类非常方便。如果你对它们不熟悉,我强烈推荐你查看这个视频。
@dataclass
class Fluid():
#fluid defaults to water properties
rho: float = 997 #kg/m³
mu: float = 0.0007972 #N-s/m² = kg/m-s
g: float = 9.81 #m/s²
管道模型
下一步是建模管道的摩擦损失(上述扩展的 Bernoulli 方程中的 Hl(Q)项)。这通常使用如下的 Darcy-Weisbach 方程,其中 f 是摩擦因子(稍后会详细介绍),v 是流速,g 是重力,而 L 和 D 分别是管道的长度和直径。
不幸的是,摩擦因子(f)不是恒定的,而是依赖于流速、流体属性和管道尺寸。存在各种计算 f 的模型,但我们将使用下面的 Haaland 方程。
在这个方程中,epsilon 是管道的表面粗糙度(可以在工程教材的表格中找到),Re 是著名的雷诺数,如下所示。
最后,我们可以注意到每单位时间的扫过体积,或者称为体积流量(Q),等于管道的横截面积(A)乘以流速(v)。因此,给定管道中的流量,我们可以计算出管道中的相应流速:
希望所有这些方程没有让你忽视大局——我们只是查看了计算管道摩擦损失的一个特定模型。给定流量和管道尺寸,首先计算出相应的流速,然后通过上述方程计算管道的摩擦损失。这正是Pipe
类(如下所示)所实现的。
初始化方法存储了管道的尺寸(假设都以米为单位)和流体属性。A
方法计算管道的横截面积(对于不熟悉@property
装饰器的人,这篇文章解释得非常好)。Q_to_v
方法将每分钟加仑数(gpm)的流量转换为米/秒的流速。friction_factor
方法评估了上述提到的 Haaland 方程,而head_loss
和 head_loss_feet
分别评估了管道的摩擦损失(以米和英尺为单位)(使用 Darcy-Weisbach 方程)。
class Pipe():
def __init__(self, L, D, epsilon, fluid: Fluid):
#pipe dimensions are all assumed to be in meters
self.L = L
self.D = D
self.epsilon= epsilon
#fluid properties
self.fluid = fluid
@property
def A(self):
"""computes cross-sectional area of pipe in m²"""
return np.pi*(self.D/2)**2 #area in m²
def Q_to_v(self, gpm):
"""Converts gpm to fluid speed in pipe in m/s"""
Q = gpm*GALLONS_TO_M3/60 #flow rate in m³/s
return Q/self.A #flow velocity in m/s
def friction_factor(self, gpm):
"""computes Darcy friction factor, given flow rate in gpm
This method uses Haaland's equation, wich is an explicit approximation
of the well-known, but implicit Colebrook equation
"""
#first get flow velocity from flow rate and pipe dimensions
v = self.Q_to_v(gpm)
#compute Reynold's number
Re = self.fluid.rho*v*self.D/self.fluid.mu
#compute relative roughness
e_over_d = self.epsilon/self.D
#use Haaland's equation
f = (-1.8*np.log10((e_over_d/3.7)**1.11 + 6.9/Re))**-2
return f
def head_loss(self, gpm):
"""computes head loss in meters, given flow rate in gpm"""
#get flow velocity
v = self.Q_to_v(gpm)
#get Darcy friction factor
f = self.friction_factor(gpm)
#compute head loss in meters
hl = 0.5*f*(self.L/self.D)*v**2/self.fluid.g
return hl
def head_loss_feet(self, gpm):
"""computes head loss in feet, given flow rate in gpm"""
hl_meters = self.head_loss(gpm)
return hl_meters/FEET_TO_METERS
让我们看看管道类的实际应用。首先,我们可以创建一个水的Fluid
对象和一个长度为 100 英尺、直径为 1.25 英寸的Pipe
对象。
#create fluid object for water
water = Fluid()
#create pipe segment with water flowing in it
pipe = Pipe(L=100*FEET_TO_METERS,
D=1.25*INCHES_TO_METERS,
epsilon=0.00006*INCHES_TO_METERS,
fluid=water)
接下来,我们将绘制流量(以 gpm 为单位)与摩擦损失的关系曲线。当我们利用面向对象编程时,下面的代码变得如此简洁和易读,这难道不令人惊叹吗?
gpm_arr = np.linspace(1,30,100)
hl = [pipe.head_loss_feet(gpm) for gpm in gpm_arr]
fig, ax = plt.subplots(figsize=figsize)
ax.plot(gpm_arr, hl)
ax.set_xlabel('Flow Rate [gpm]')
ax.set_ylabel('Head Loss [ft]')
fig.tight_layout()
fig.savefig(plots_folder/'pipe_loss_curve.png')
图片来源:作者
泵模型
我们已经有了管道头损失的工作模型——现在我们需要一个泵产生的头部模型,Hp(Q)。我相信有解析模型可以用来确定泵的行为,但我们将假设我们已经有了一个具体的泵——即我在网上找到的随机的、分数马力的泵:
大多数泵都有一个数据表,其中包含描述泵行为的泵曲线。对于我们的泵,下面是泵曲线(请注意,出于版权原因,这只是制造商提供的图示的重建版——原版可以在这里找到)。
图片来源:作者
目前我们有了描绘泵行为的图像,但还没有一个可以实际用来确定它在系统中表现的数学模型。这个问题经常出现,我的解决方法是 1) 数字化数据,然后 2) 使用插值方案包装离散数据,以生成一个连续函数。让我来说明一下。
第一步)有许多工具可以将图像数据数字化——我个人最喜欢的是免费的在线工具WebPlotDigitizer。你可以加载感兴趣的图像,校准坐标轴,然后提取所需的数据曲线(可以手动提取,也可以使用自动提取工具)。数据可以导出为 .csv 文件。
图片来源:作者
第二步)现在我们已经得到了数字化的数据,我们只需要用某种插值器进行包装——这正是下面Pipe
类所做的。初始化方法接收 .csv 文件名,存储文件名,将数据加载到 pandas DataFrame 中,存储在 data
属性中,然后将数据传递给 scipy 的 interp1d
函数。interp1d
函数生成一个新的函数,默认使用线性插值将离散数据点转换为连续函数(interp1d
函数的完整文档可以在这里找到)。新生成的插值函数随后存储在 _interp
属性中以供后续访问。Pipe
类还包含一个 bounds
方法,该方法返回包含泵曲线数据中流量的最小/最大值的列表(这将在根查找算法中使用),以及一个 head_gain_feet
方法,该方法接受流量值(单位为 gpm),并调用由 interp1d
生成的底层插值函数。
class Pump():
def __init__(self, file):
#store file name
self.file = file
#read data into pandas dataframe and assign column names
self.data = pd.read_csv(file, names=['gpm', 'head [ft]']).set_index('gpm')
#create continuous interpolation function
self._interp = interp1d(self.data.index.to_numpy(), self.data['head [ft]'].to_numpy())
@property
def bounds(self):
"""returns min and max flow rates in pump curve data"""
return [self.data.index.min(), self.data.index.max()]
def head_gain_feet(self, gpm):
"""return head (in feet) produced by the pump at a given flow rate"""
return self._interp(gpm)
我们可以创建一个Pump
对象并查看我们读取的原始数据。
pump = Pump('pump_data.csv')
pump.data.head()
图片来源:作者
我们还可以将泵曲线数据与管道损失曲线一起绘制,以直观地查看系统将在哪里操作。
head_loss = [pipe.head_loss_feet(gpm) for gpm in pump.data.index]
fig, ax = plt.subplots(figsize=figsize)
ax.plot(pump.data, label='Pump Curve')
ax.plot(pump.data.index, head_loss, label='Pipe Head Loss')
ax.set_xlabel("Flow Rate [gpm]")
ax.set_ylabel("Head [ft]")
ax.legend(frameon=True, facecolor='w', framealpha=1, loc=6)
fig.tight_layout()
fig.savefig(plots_folder/'pump_curve_with_losses.png')
图片来源:作者
系统模型
我们终于建立了能够解决泵/管道系统操作点的基础设施。最后一步是创建一个System
类,该类接收一个Pipe
和Pump
对象,并执行根求解操作。正如下面的代码所示,System
类接收并存储一个Pipe
和Pump
对象。然后,它利用这两个对象创建一个residual
方法,该方法计算泵头和管道头损失之间的差值。这个residual
方法随后在get_operating_point
方法中被使用,以实际求解系统的操作点。该方法包装了 scipy 的root_scalar
函数,它作为各种根求解算法的接口。我们将让root_scalar
函数选择它认为最适合的算法,但为了帮助它,我们将指定一个我们知道根在其间的区间。在我们的案例中,这个区间是泵曲线数据的上限和下限流量。关于root_scalar
函数的完整文档可以在这里找到。
提示:将
Pipe
和Pump
对象注入System
类(与在实例化时让系统类创建Pipe
和Pump
对象相对)被称为“依赖注入”。这通常被认为是一种良好的编码实践,因为它使代码更具模块化、可扩展性,更易于调试/测试。
class System():
def __init__(self, pipe: Pipe, pump: Pump):
self.pipe = pipe
self.pump = pump
def residual(self, gpm):
"""
Computes the difference between the head produced by the pump
and the head loss in the pipe. At steady state, the pump head and
head loss will be equal and thus the residual function will go to zero
"""
return self.pump.head_gain_feet(gpm) - self.pipe.head_loss_feet(gpm)
def get_operating_point(self):
"""solve for the flow rate where the residual function equals zero.
i.e. the pump head equals the pipe head loss"""
return root_scalar(self.residual, bracket=self.pump.bounds)
让我们创建一个System
并运行get_operating_point
方法,以观察我们的劳动成果。正如代码输出所示,get_operating_point
方法仅返回root_scalar
函数的输出,这是一个RootResults
对象。这个对象本质上只是一个容器,存储各种属性,其中最重要的是root
属性,因为它包含我们问题的解决方案。
sys = System(pipe, pump)
res = sys.get_operating_point()
res
我们可以再次绘制相同的泵和头损失曲线,这次在计算出的稳态操作点处添加一条垂直线。
head_loss = [pipe.head_loss_feet(gpm) for gpm in pump.data.index]
fig, ax = plt.subplots(figsize=figsize)
ax.plot(pump.data, label='Pump Curve')
ax.plot(pump.data.index, head_loss, label='Pipe Head Loss')
#plot vertical line at operating point
ax.axvline(res.root, color='k', ls='--', lw=1)
ax.legend(frameon=True, facecolor='white', framealpha=1, loc=6)
ax.set_xlabel("Flow Rate [gpm]")
ax.set_ylabel("Head [ft]")
ax.set_title(f'Operating Point = {res.root:.1f} gpm')
fig.tight_layout()
fig.savefig(plots_folder/'intersection_solution.png')
图片来源:作者
完成了!我们已经程序化地确定了系统的操作点。因为我们使用的是一种比较通用的编码框架,我们可以轻松尝试使用不同的泵或管道进行相同的分析!我们甚至可以扩展我们的代码以包括多个泵,或各种管道配件/管道分支。
设计探索
作为一个小例子,突出我们设置代码方式的好处,我们将进行设计探索。使用相同的泵,我们希望了解管道长度对系统体积流量的影响。为此,我们只需遍历一个管道长度数组(从 100 到 1000 英尺),更新存储在System
中的Pipe
对象的长度属性,然后重新计算系统的工作点,将结果追加到列表中。最后,我们将水流量绘制为管道长度的函数。
#sweep pipe length from 100 to 1000 feet
lengths_feet = np.linspace(100, 1000, 1000)
lengths_meters = lengths_feet*FEET_TO_METERS
flow_rates = []
for l in lengths_meters:
#update pipe length
sys.pipe.L = l
#compute new flow rate solution
res = sys.get_operating_point()
#append solution to flow rates list
flow_rates.append(res.root)
#plot results
fig, ax = plt.subplots(figsize=figsize)
ax.plot(lengths_feet, flow_rates)
ax.set_xlabel("Pipe Length [ft]")
ax.set_ylabel("Flow Rate [gpm]")
# ax.set_ylim(bottom=0)
fig.tight_layout()
fig.savefig(plots_folder/'flow_vs_pipe_length.png')
图片由作者提供
几行代码让我们能够深入了解系统的行为。如果这是一个设计问题,这些见解可能会驱动关键的设计决策。
结论
本文虽然主要集中在一个特定领域的示例问题上,但突出了我经常使用的工作流程的几个方面。操作点分析的问题在工程和科学中经常出现,尽管有许多方法可以解决这个问题,但有些方法比其他方法更强大、可扩展和灵活。本文中使用的方法论(问题表述和代码结构原则)对我帮助极大,希望其他人也能受到启发,采用类似的工作流程!
随时留下任何评论或问题,或者在 LinkedIn 上与我联系——我非常乐意澄清任何不确定的点。最后,我鼓励你自己尝试代码(甚至将其用作自己工作流程的起始模板)——这篇文章的 Jupyter Notebook 可以在我的Github上找到。
尼古拉斯·赫门威
-
如果你喜欢这个,请在 Medium 上关注我
-
考虑订阅 电子邮件更新
-
有兴趣合作吗?让我们 在 LinkedIn 上联系