原文:TowardsDataScience

协议:CC BY-NC-SA 4.0

启动你的机器学习之旅:范围确定、结构设计和数据探索(第一部分)

原文:towardsdatascience.com/end-to-end-machine-learning-project-part-1-project-scoping-structure-data-gathering-eda-7e69b71eeb44?source=collection_archive---------18-----------------------#2024-06-18

一份关于规划和组织机器学习项目的实用指南,从数据收集到探索性分析。

https://medium.com/@Pratha_P?source=post_page---byline--7e69b71eeb44--------------------------------https://towardsdatascience.com/?source=post_page---byline--7e69b71eeb44-------------------------------- Pratha Pawar

·发布于 Towards Data Science ·10 分钟阅读·2024 年 6 月 18 日

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/b3d54d45ea93df193b545449210f9448.png

在 Streamlit 中进行交互式可视化,使用 Bokeh(图像由作者提供)

本文将涵盖以下主题

  • 理解业务问题

  • 设置工作环境和目录结构

  • 收集数据(使用多线程提高 2 到 4 倍的速度)

  • 预处理数据(使用向量化提高 10 倍速度)

  • 通过探索性数据分析(EDA)获得有价值的见解

  • 构建交互式可视化(本系列的第二部分)

  • 最后使用机器学习回答问题(本系列的第三部分)

  • 附加内容:你还将学习如何将代码模块化为独立且可重用的组件,以及如何使用抽象。

注意:本文面向初学者到中级水平的数据科学家。

几乎所有的数据科学和机器学习项目都从业务问题开始。那么,让我们先定义一下我们在这里试图解决的问题。

假设你为纽约市的出租车服务公司工作,你的团队正在尝试……

纽约市出租车数据可视化互动展示:Bokeh 与 Streamlit(第二部分)

原文:towardsdatascience.com/end-to-end-machine-learning-project-part-2-interactive-eda-using-bokeh-and-streamlit-24df51de4bee?source=collection_archive---------19-----------------------#2024-06-18

使用 Bokeh 和 Streamlit,通过动态可视化展示出租车行程模式,包括接送点热点和交通流量。

https://medium.com/@Pratha_P?source=post_page---byline--24df51de4bee--------------------------------https://towardsdatascience.com/?source=post_page---byline--24df51de4bee-------------------------------- Pratha Pawar

·发表于 Towards Data Science ·阅读时间 8 分钟·2024 年 6 月 18 日

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/f1f94ba17b4399d5e65cb9800100e632.png

动画展示 2016 年 1 月到 6 月期间纽约市黄色出租车的上下车点。左图显示了 JFK 机场区域的接客点,右图显示了与左图中行程对应的下车点。动画从 0 点到 23 点运行,颜色表示该小时内的行程时长(以分钟为单位),并且是 6 个月的平均值。(图片来自作者)

查看本博客系列的第一部分 启动你的机器学习之旅:数据范围、结构化与探索(第一部分),以理解我们要解决的数据和业务问题。

本文涉及的内容:

  • 向纽约市出租车行程数据集添加地理地图功能

  • 使用 Bokeh 创建互动式图表

  • 将 bokeh 图表部署到 streamlit (nyc-taxi-trip-pp.streamlit.app/) — 注意:请使用暗色主题

Github 链接: 本文源代码

本文将重点讨论业务问题中的以下两个主题:

  1. 识别不同时段、不同区域的纽约市人群(在出租车中的人群)流动情况。

  2. 识别不同时间和不同区域的出租车需求(哪些是热点区域?)

作为其中的一部分,我们将创建一个互动式可视化,提供两个选项,展示:

  1. 散点图:每次旅行…

使用 Hugging Face、FastAPI 和 Docker 的端到端 NLP 项目

原文:towardsdatascience.com/end-to-end-nlp-project-with-hugging-face-fastapi-and-docker-615a63d80c53?source=collection_archive---------2-----------------------#2024-03-07

本教程解释了如何使用 Hugging Face、FastAPI 和 Docker 构建一个容器化的情感分析 API。

https://kaspergroesludvigsen.medium.com/?source=post_page---byline--615a63d80c53--------------------------------https://towardsdatascience.com/?source=post_page---byline--615a63d80c53-------------------------------- Kasper Groes Albin Ludvigsen

·发表于 Towards Data Science ·阅读时间:10 分钟·2024 年 3 月 7 日

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/0cce1f0d3ed49f09a3eee64df608b20e.png

图片由 Joshua Hoehne 提供,来源:Unsplash

根据各类报告,许多 AI 项目失败了(例如,哈佛商业评论)。我推测,AI 项目成功的障碍部分来自于技术上的一个步骤,即从构建模型到使其广泛可用给你组织中的其他人之间的过渡。

那么,如何让你的模型便于他人使用呢?一种方法是将它封装成 API,并将其容器化,以便该模型能够在任何安装了 Docker 的服务器上公开。这正是我们在本教程中将要做的。

我们将从 Hugging Face 获取一个情感分析模型(这只是一个随意的选择,目的是选择一个容易作为示例展示的模型),编写一个 API 端点,通过 FastAPI 曝露该模型,然后使用 Docker 将我们的情感分析应用容器化。我会提供代码示例和解释。

本教程代码已在 Linux 上测试,应该也可以在 Windows 上运行。

强制执行商业大语言模型中的 JSON 输出

原文:towardsdatascience.com/enforcing-json-outputs-in-commercial-llms-3db590b9b3c8?source=collection_archive---------2-----------------------#2024-08-28

一份综合指南

https://medium.com/@volkot?source=post_page---byline--3db590b9b3c8--------------------------------https://towardsdatascience.com/?source=post_page---byline--3db590b9b3c8-------------------------------- Daniel Kharitonov

·发表于Towards Data Science ·9 分钟阅读·2024 年 8 月 28 日

简短总结

我们测试了 Google Gemini Pro、Anthropic Claude 和 OpenAI GPT 的结构化输出能力。在它们最佳的配置下,三种模型都能够生成数千个 JSON 对象的结构化输出。然而,在促使模型生成 JSON 以及遵循数据模型的能力方面,API 的能力差异显著。

更具体来说,唯一一个能够直接提供一致结构化输出的商业供应商似乎是 OpenAI,他们在 2024 年 8 月 6 日发布了最新的结构化输出 API。OpenAI 的 GPT-4o 可以直接与 Pydantic 数据模型集成,根据所需字段和字段描述格式化 JSON。

Anthropic 的 Claude Sonnet 3.5 排在第二位,因为它需要使用“工具调用”技巧才能可靠地产生 JSON。尽管 Claude 能够解释字段描述,但它并不直接支持 Pydantic 模型。

最后,Google Gemini 1.5 Pro 排在第三位,因为它的 API 笨重,需要使用文档不完善的genai.protos.Schema类作为可靠生成 JSON 的模型。此外,似乎没有直接的方法可以通过字段描述来引导 Gemini 的输出。

以下是测试结果的汇总表:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/2eb322369cc005af5499424b8f71545e.png

结构化输出错误的近似率(数据来源:作者的 Jupyter 笔记本,见下文)

这里是测试平台笔记本的链接:

github.com/iterative/datachain-examples/blob/main/formats/JSON-outputs.ipynb

问题简介

当 LLM 作为通用聊天机器人使用时,生成结构化输出的能力并不是至关重要的。然而,在以下两种新兴的 LLM 应用中,结构化输出变得不可或缺:

• 基于 LLM 的分析(如 AI 驱动的判断和非结构化数据分析)

• 构建 LLM 代理

在这两种情况下,LLM 的通信必须遵循一个明确定义的格式。这种一致性对于下游应用至关重要,否则将收到不一致的输入,导致潜在的错误。

不幸的是,尽管大多数现代 LLM 提供了旨在生成结构化输出(如 JSON)的方法,但这些方法通常会遇到两个重大问题:

1. 它们偶尔无法生成有效的结构化对象。

2. 它们生成了一个有效的对象,但无法遵循请求的数据模型。

在以下文本中,我们记录了关于 Anthropic Claude、Google Gemini 和 OpenAI 的 GPT 最新版本在结构化输出能力方面的发现。

Anthropic Claude Sonnet 3.5

初看之下,Anthropic Claude 的 API 看起来很直接,因为它有一个名为‘增加 JSON 输出一致性’的部分,其中开头是一个中等复杂度的结构化输出示例:

import os
import anthropic

PROMPT = """
You’re a Customer Insights AI. 
Analyze this feedback and output in JSON format with keys: “sentiment” (positive/negative/neutral), 
“key_issues” (list), and “action_items” (list of dicts with “team” and “task”).
"""

source_files = "gs://datachain-demo/chatbot-KiT/"
client = anthropic.Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))

completion = (
   client.messages.create(                       
        model="claude-3-5-sonnet-20240620", 
        max_tokens = 1024,       
        system=PROMPT,                           
        messages=[{"role": "user", "content": "User: Book me a ticket. Bot: I do not know."}]
   )
)
print(completion.content[0].text)

然而,如果我们实际上运行这个代码几次,我们会发现转换为 JSON 失败,因为 LLM 会在 JSON 对象前加上不必要的文本前缀:

Here's the analysis of that feedback in JSON format:

{
  "sentiment": "negative",
  "key_issues": [
    "Bot unable to perform requested task",
    "Lack of functionality",
    "Poor user experience"
  ],
  "action_items": [
    {
      "team": "Development",
      "task": "Implement ticket booking functionality"
    },
    {
      "team": "Knowledge Base",
      "task": "Create and integrate a database of ticket booking information and procedures"
    },
    {
      "team": "UX/UI",
      "task": "Design a user-friendly interface for ticket booking process"
    },
    {
      "team": "Training",
      "task": "Improve bot's response to provide alternatives or direct users to appropriate resources when unable to perform a task"
    }
  ]
}

这个问题影响了大约 14%–20% 的请求,使得依赖 Claude 的“结构化提示”功能变得值得怀疑。这个问题显然是 Anthropic 熟知的,因为他们的文档中提供了两条额外的建议:

1. 提供有效输出的内联示例。

2. 强制 LLM 使其回应以有效的前言开始。

第二种解决方案有些不优雅,因为它需要预先填写响应,然后再将其与生成的输出重新组合。

下面是一个实现这两种技术并评估结果 JSON 字符串有效性的代码示例。这个提示通过 卡尔斯鲁厄理工大学使用 Iterative 的 DataChain 库在 50 个不同的对话中进行了测试:

import os
import json
import anthropic
from datachain import File, DataChain, Column

source_files = "gs://datachain-demo/chatbot-KiT/"
client = anthropic.Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))

PROMPT = """
You’re a Customer Insights AI. 
Analyze this dialog and output in JSON format with keys: “sentiment” (positive/negative/neutral), 
“key_issues” (list), and “action_items” (list of dicts with “team” and “task”).

Example:
{
  "sentiment": "negative",
  "key_issues": [
    "Bot unable to perform requested task",
    "Poor user experience"
  ],
  "action_items": [
    {
      "team": "Development",
      "task": "Implement ticket booking functionality"
    },
    {
      "team": "UX/UI",
      "task": "Design a user-friendly interface for ticket booking process"
    }
  ]
}    
"""
prefill='{"sentiment":'

def eval_dialogue(file: File) -> str:    
     completion = (
         client.messages.create(                       
                model="claude-3-5-sonnet-20240620", 
                max_tokens = 1024,       
                system=PROMPT,                           
                messages=[{"role": "user", "content": file.read()},
                          {"role": "assistant", "content": f'{prefill}'},
                         ]
         )
     )
     json_string = prefill + completion.content[0].text
     try:
         # Attempt to convert the string to JSON
         json_data = json.loads(json_string)
         return json_string
     except json.JSONDecodeError as e:
         # Catch JSON decoding errors
         print(f"JSONDecodeError: {e}")
         print(json_string)
         return json_string

chain = DataChain.from_storage(source_files, type="text")       \
              .filter(Column("file.path").glob("*.txt"))        \
              .map(claude = eval_dialogue)                      \
              .exec()

结果有所改进,但仍不完美。大约每 50 次调用中就有一次会返回错误:

JSONDecodeError: Expecting value: line 2 column 1 (char 14)
{"sentiment":
Human: I want you to analyze the conversation I just shared

这意味着 Sonnet 3.5 模型可能无法遵循指令,并且会产生不必要的对话延续。因此,模型仍然不能始终如一地达到预期输出。

幸运的是,在 Claude API 中还有另一种方法可以探索:利用函数调用。这些函数在 Anthropic 的 API 中被称为‘工具’,本质上需要结构化的输入才能操作。为了利用这个选项,我们可以创建一个模拟函数,并配置调用签名与我们期望的 JSON 对象完全一致:

import os
import json
import anthropic
from datachain import File, DataChain, Column

from pydantic import BaseModel, Field, ValidationError
from typing import List, Optional

class ActionItem(BaseModel):
    team: str 
    task: str

class EvalResponse(BaseModel):
    sentiment: str = Field(description="dialog sentiment (positive/negative/neutral)")
    key_issues: list[str] = Field(description="list of five problems discovered in the dialog")
    action_items: list[ActionItem] = Field(description="list of dicts with 'team' and 'task'")

source_files = "gs://datachain-demo/chatbot-KiT/"
client = anthropic.Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))

PROMPT = """
You’re assigned to evaluate this chatbot dialog and sending the results to the manager via send_to_manager tool.    
"""

def eval_dialogue(file: File) -> str:    
     completion = (
         client.messages.create(                       
                model="claude-3-5-sonnet-20240620", 
                max_tokens = 1024,       
                system=PROMPT, 
                tools=[
                    {
                        "name": "send_to_manager",
                        "description": "Send bot evaluation results to a manager",
                        "input_schema": EvalResponse.model_json_schema(),
                    }
                ],
                messages=[{"role": "user", "content": file.read()},
                         ]
         )
     )
     try: # We are only interested in the ToolBlock part
         json_dict = completion.content[1].input
     except IndexError as e:
         # Catch cases where Claude refuses to use tools
         print(f"IndexError: {e}")
         print(completion)
         return str(completion)
     try:
         # Attempt to convert the tool dict to EvalResponse object
         EvalResponse(**json_dict)
         return completion
     except ValidationError as e:
         # Catch Pydantic validation errors
         print(f"Pydantic error: {e}")
         print(completion)
         return str(completion)

tool_chain = DataChain.from_storage(source_files, type="text")          \
              .filter(Column("file.path").glob("*.txt"))                \
              .map(claude = eval_dialogue)        \
              .exec()

在运行这个代码 50 次后,我们遇到了一次异常的响应,内容如下:

IndexError: list index out of range
Message(id='msg_018V97rq6HZLdxeNRZyNWDGT', 
content=[TextBlock(
text="I apologize, but I don't have the ability to directly print anything. 
I'm a chatbot designed to help evaluate conversations and provide analysis. 
Based on the conversation you've shared, 
it seems you were interacting with a different chatbot. 
That chatbot doesn't appear to have printing capabilities either.
However, I can analyze this conversation and send an evaluation to the manager.
Would you like me to do that?", type='text')], 
model='claude-3-5-sonnet-20240620', 
role='assistant', 
stop_reason='end_turn', 
stop_sequence=None, type='message', 
usage=Usage(input_tokens=1676, output_tokens=95))

在这种情况下,模型变得困惑,未能执行函数调用,而是仅返回了一个文本块,并提前停止(停止原因 = ‘end_turn’)。幸运的是,Claude API 提供了一种解决方案来防止这种行为,并强制模型始终发出工具调用,而不是文本块。通过将以下行添加到配置中,你可以确保模型遵循预期的函数调用行为:

tool_choice = {"type": "tool", "name": "send_to_manager"}

在强制选择工具后,Claude Sonnet 3.5 能够成功返回超过 1,000 次有效的 JSON 对象,没有任何错误。如果你不想自己构建这个函数调用,LangChain提供了一个 Anthropic 包装器,通过简单易用的调用格式简化了这个过程:

from langchain_anthropic import ChatAnthropic

model = ChatAnthropic(model="claude-3-opus-20240229", temperature=0)
structured_llm = model.with_structured_output(Joke)
structured_llm.invoke("Tell me a joke about cats. Make sure to call the Joke function.")

作为额外的好处,Claude 似乎能有效地解释字段描述。这意味着如果你从一个像这样的 Pydantic 类中转储 JSON 模式……

class EvalResponse(BaseModel):
    sentiment: str = Field(description="dialog sentiment (positive/negative/neutral)")
    key_issues: list[str] = Field(description="list of five problems discovered in the dialog")
    action_items: list[ActionItem] = Field(description="list of dicts with 'team' and 'task'")

…然后你可能会收到一个符合你预期描述的对象。

阅读数据模型的字段描述是非常有用的,因为它使我们能够在不触及模型提示的情况下指定期望响应的细微差别。

Google Gemini Pro 1.5

Google 的文档明确指出基于提示的方法生成 JSON 是不可靠的,并且将更高级的配置(如使用 OpenAPI 模式)限制为旗舰版的 Gemini Pro 系列模型。事实上,Gemini 在生成 JSON 输出时的基于提示的表现相当差。当仅仅要求生成 JSON 时,模型通常会将输出包装在 Markdown 的前导部分:

```json

{

"sentiment": "negative",

"key_issues": [

    "Bot 误解了用户的确认。",

    "推荐的计划不符合用户需求(更多 MB,较少分钟,价格限制)。"

],

"action_items": [

    {

    "team": "Engineering",

    "task": "调查为什么机器人没有理解‘正确’和‘是的,它是’的确认。"

    },

    {

    "team": "Product",

    "task": "回顾并改进计划匹配逻辑,以优先考虑用户需求和限制。"

    }

]

}

```py

To combat this, a more refined configuration unlocks Gemini’s “JSON mode” by specifying the output mime type:

generation_config={“response_mime_type”: “application/json”}


However, this tricks also fails to work reliably because once in a while the model still fails to return a parseable JSON string.

Returning to Google’s original recommendation, one might assume that upgrading to their premium model and using the *responseSchema* parameter should guarantee reliable JSON outputs.

Unfortunately, the reality is more complex. Google offers multiple ways to configure the *responseSchema* — by providing an OpenAPI model, an instance of a user class, or a reference to Google’s proprietary *genai.protos.Schema*.

While all these methods are effective at generating valid JSONs, it is only the latter that guarantees the model emits all ‘required’ fields. This limitation forces users to define their data models twice — as Pydantic and genai.protos.Schema objects — while also losing the ability to convey additional information to the model through field descriptions:

class ActionItem(BaseModel):

team: str

task: str

class EvalResponse(BaseModel):

sentiment: str = Field(description="对话情感(正面/负面/中性)")

key_issues: list[str] = Field(description="对话中发现的 3 个问题列表")

action_items: list[ActionItem] = Field(description="包含‘team’和‘task’字典的列表")

g_str = genai.protos.Schema(type=genai.protos.Type.STRING)

g_action_item = genai.protos.Schema(

        type=genai.protos.Type.OBJECT,

        properties={

            'team':genai.protos.Schema(type=genai.protos.Type.STRING),

            'task':genai.protos.Schema(type=genai.protos.Type.STRING)

        },

        required=['team','task']

    )

g_evaluation=genai.protos.Schema(

        type=genai.protos.Type.OBJECT,

        properties={

            'sentiment':genai.protos.Schema(type=genai.protos.Type.STRING),

            'key_issues':genai.protos.Schema(type=genai.protos.Type.ARRAY, items=g_str),

            'action_items':genai.protos.Schema(type=genai.protos.Type.ARRAY, items=g_action_item)

        },

        required=['sentiment','key_issues', 'action_items']

    )

def gemini_setup():

genai.configure(api_key=google_api_key)

return genai.GenerativeModel(model_name='gemini-1.5-pro-latest',

                            system_instruction=PROMPT,

                            generation_config={"response_mime_type": "application/json",

                                                "response_schema": g_evaluation,

                                            }

                            )

**OpenAI GPT-4o**

Among the three LLM providers we’ve examined, OpenAI offers the most flexible solution with the simplest configuration. Their “Structured Outputs API” can directly accept a Pydantic model, enabling it to read both the data model and field descriptions effortlessly:

class Suggestion(BaseModel):

suggestion: str = Field(description="改进聊天机器人的建议,以字母 K 开头")

class Evaluation(BaseModel):

outcome: str = Field(description="对话是否成功,取值为 Yes 或 No")

explanation: str = Field(description="关于结果的决策依据")

suggestions: list[Suggestion] = Field(description="改进聊天机器人的六种方法")

@field_validator("outcome")

def check_literal(cls, value):

    if not (value in ["Yes", "No"]):

        print(f"未遵循 Literal Yes/No: {value}")

    return value

@field_validator("suggestions")

def count_suggestions(cls, value):

    if len(value) != 6:

        print(f"未遵循数组长度为 6: {value}")

    count = sum(1 for item in value if item.suggestion.startswith('K'))

    if len(value) != count:

        print(f"{len(value)-count} 个建议未以 K 开头")

    return value

def eval_dialogue(client, file: File) -> Evaluation:

completion = client.beta.chat.completions.parse(

    model="gpt-4o-2024-08-06",

    messages=[

        {"role": "system", "content": prompt},

        {"role": "user", "content": file.read()},

    ],

    response_format=Evaluation,

)

在健壮性方面,OpenAI 文档引用了一张图表,比较了其“结构化输出”API 与基于提示的解决方案的成功率,前者[实现了接近 100% 的成功率](https://openai.com/index/introducing-structured-outputs-in-the-api/)。

然而,细节决定成败。

尽管 OpenAI 的 JSON 性能接近‘100%’,但它并非完全无懈可击。即使请求配置完全正确,我们也发现大约每几千次请求中,依然会发生一次 JSON 错误——尤其是当提示没有精心设计时,这种错误需要重试。

尽管存在这一限制,但可以公平地说,目前 OpenAI 提供了最佳的结构化 LLM 输出解决方案。

注意:作者与 OpenAI、Anthropic 或 Google 无关,但积极参与开源的 LLM 协同与评估工具的开发。

**链接**

**测试 Jupyter 笔记本:**

[JSON-outputs.ipynb](https://github.com/iterative/datachain-examples/blob/main/formats/JSON-outputs.ipynb)

[](https://github.com/iterative/datachain-examples/blob/main/llm/llm_brute_force.ipynb?source=post_page-----3db590b9b3c8--------------------------------) [## datachain-examples/llm/llm_brute_force.ipynb at main · iterative/datachain-examples

### LLM、计算机视觉(CV)、大规模多模态。在 GitHub 上创建账户,贡献于迭代式/datachain-examples 开发。

[github.com](https://github.com/iterative/datachain-examples/blob/main/llm/llm_brute_force.ipynb?source=post_page-----3db590b9b3c8--------------------------------)

**Anthropic JSON API:**

[`docs.anthropic.com/en/docs/test-and-evaluate/strengthen-guardrails/increase-consistency`](https://docs.anthropic.com/en/docs/test-and-evaluate/strengthen-guardrails/increase-consistency)

**Anthropic 函数调用:**

[`docs.anthropic.com/en/docs/build-with-claude/tool-use#forcing-tool-use`](https://docs.anthropic.com/en/docs/build-with-claude/tool-use#forcing-tool-use)

**LangChain 结构化输出 API:**

[`python.langchain.com/v0.1/docs/modules/model_io/chat/structured_output/`](https://python.langchain.com/v0.1/docs/modules/model_io/chat/structured_output/)

**Google Gemini JSON API:**

[`ai.google.dev/gemini-api/docs/json-mode?lang=python`](https://ai.google.dev/gemini-api/docs/json-mode?lang=python)

**Google genai.protos.Schema 示例:**

[`ai.google.dev/gemini-api/docs/function-calling/tutorial?lang=python#optional_low_level_access`](https://ai.google.dev/gemini-api/docs/function-calling/tutorial?lang=python#optional_low_level_access)

**OpenAI “结构化输出”公告:**

[`openai.com/index/introducing-structured-outputs-in-the-api/`](https://openai.com/index/introducing-structured-outputs-in-the-api/)

**OpenAI 的结构化输出 API:**

[`platform.openai.com/docs/guides/structured-outputs/introduction`](https://platform.openai.com/docs/guides/structured-outputs/introduction)


# 工程未来:数据、软件与人工智能中的共同线索

> 原文:[`towardsdatascience.com/engineering-the-future-common-threads-in-data-software-and-artificial-intelligence-2aa46b262150?source=collection_archive---------0-----------------------#2024-11-23`](https://towardsdatascience.com/engineering-the-future-common-threads-in-data-software-and-artificial-intelligence-2aa46b262150?source=collection_archive---------0-----------------------#2024-11-23)

## 认识到跨学科的共性,不仅可以提升招聘策略,还能支持灵活的 IT 架构。

[](https://medium.com/@bernd.wessely?source=post_page---byline--2aa46b262150--------------------------------)<https://medium.com/@bernd.wessely?source=post_page---byline--2aa46b262150-------------------------------->[](https://towardsdatascience.com/?source=post_page---byline--2aa46b262150--------------------------------)<https://towardsdatascience.com/?source=post_page---byline--2aa46b262150--------------------------------> [Bernd Wessely](https://medium.com/@bernd.wessely?source=post_page---byline--2aa46b262150--------------------------------)

·发表于[Towards Data Science](https://towardsdatascience.com/?source=post_page---byline--2aa46b262150--------------------------------) ·7 分钟阅读·2024 年 11 月 23 日

--

<https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/ebe3f5cc474209950f9332fc64d2636f.png>

DALL-E 生成

我注意到,IT 部门中存在过度专业化的趋势。然而,经过多年的经验,我认识到这种[孤岛式专业化的负面影响](https://medium.com/towards-data-science/data-architecture-lessons-learned-3589b152a8a6)。

虽然这主要是一个组织问题,但对供应商专门化平台产品的盲目采纳,也[导致了我们企业架构中职能的显著重叠](https://medium.com/towards-data-science/avoid-building-a-data-platform-in-2024-56f0ee95da42)。

如果您的业务是提供专业化 IT 解决方案平台,您当然可以从尖锐的专业化中受益。

对于所有其他企业,我认为这一点需要得到纠正。

# 从孤岛到更好的协作转变

传统的软件应用工程、数据工程和人工智能/机器学习(AI/ML)如今形成了庞大的孤岛。

尽管不同的 IT 任务被认为在很大程度上是独立的,目标也各不相同,但实际上,商业需求是无缝衔接的……


# 利用图形数据库的强大功能提升你的网络分析

> 原文:[`towardsdatascience.com/enhance-your-network-with-the-power-of-a-graph-db-82c6c838c0a8?source=collection_archive---------2-----------------------#2024-05-04`](https://towardsdatascience.com/enhance-your-network-with-the-power-of-a-graph-db-82c6c838c0a8?source=collection_archive---------2-----------------------#2024-05-04)

<https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/c66d7785a547d61415e242e357f044a1.png>

## 在 5 分钟内通过图形数据库和交互式可视化设置完成,所有相关代码已为你编写。

[](https://medium.com/@bl3e967?source=post_page---byline--82c6c838c0a8--------------------------------)<https://medium.com/@bl3e967?source=post_page---byline--82c6c838c0a8-------------------------------->[](https://towardsdatascience.com/?source=post_page---byline--82c6c838c0a8--------------------------------)<https://towardsdatascience.com/?source=post_page---byline--82c6c838c0a8--------------------------------> [Benjamin Lee](https://medium.com/@bl3e967?source=post_page---byline--82c6c838c0a8--------------------------------)

·发表于[Towards Data Science](https://towardsdatascience.com/?source=post_page---byline--82c6c838c0a8--------------------------------) ·阅读时长 9 分钟·2024 年 5 月 4 日

--

# 目录

1.  **介绍** *(如果你愿意,可以跳过此部分)*

1.  **设置与安装**

1.  **从** `**networkx**` **迁移到 Memgraph DB**

1.  **按特征值调整节点大小**

1.  **按特征值着色边**

1.  **下一步**

# 介绍

到目前为止,我向你展示了在 Python 中以尽可能少的代码创建完全交互式网络可视化的最便捷方法。

现在是时候更进一步了——将图形数据库整合到我们的网络可视化中。

在本文中,我向你介绍了一种与 Python 兼容的图形数据库,**你可以在 5 分钟内完成设置。**

它将允许你获得图形数据库的所有好处,同时还包括:

+   允许你创建一个完全交互的可视化,你可以点击节点和边并查看其属性,还可以拖拽它们。

+   便于实现——…


# 使用 StyleGAN-2 ADA 增强癌症检测

> 原文:[`towardsdatascience.com/enhancing-cancer-detection-with-stylegan-2-ada-aee55ef99c5b?source=collection_archive---------5-----------------------#2024-01-22`](https://towardsdatascience.com/enhancing-cancer-detection-with-stylegan-2-ada-aee55ef99c5b?source=collection_archive---------5-----------------------#2024-01-22)

## 针对数据匮乏的深度神经网络进行数据增强。

[](https://medium.com/@ianstebbs?source=post_page---byline--aee55ef99c5b--------------------------------)<https://medium.com/@ianstebbs?source=post_page---byline--aee55ef99c5b-------------------------------->[](https://towardsdatascience.com/?source=post_page---byline--aee55ef99c5b--------------------------------)<https://towardsdatascience.com/?source=post_page---byline--aee55ef99c5b--------------------------------> [Ian Stebbins](https://medium.com/@ianstebbs?source=post_page---byline--aee55ef99c5b--------------------------------)

·发布于 [Towards Data Science](https://towardsdatascience.com/?source=post_page---byline--aee55ef99c5b--------------------------------) ·8 分钟阅读·2024 年 1 月 22 日

--

*作者:* [*Ian Stebbins*](https://www.linkedin.com/in/ian-stebbins-244a1722b/)*,* [*Benjamin Goldfried*](https://www.linkedin.com/in/benjamin-goldfried/)*,* [*Ben Maizes*](https://www.linkedin.com/in/benjamin-maizes/)

## **简介**

对于许多领域特定的问题,数据的匮乏可能会妨碍深度神经网络的有效性,甚至使其无法使用。然而,生成对抗网络(GAN)的最新架构使我们能够通过创建捕捉数据分布中细节、纹理和变异的新样本来合成增强数据。这些合成数据可以作为深度神经网络的额外训练输入,从而使数据稀缺的领域任务变得更加可行。

在这个项目中,我们使用了带有自适应判别器增强(ADA)的 NVIDIA StyleGAN-2,应用于一个小型的[胸部 CT 扫描数据集](https://www.kaggle.com/datasets/mohamedhanyyy/chest-ctscan-images/code?datasetId=839140&sortBy=voteCount)(许可协议:[数据库:开放数据库,内容:© 原作者](http://opendatacommons.org/licenses/odbl/1.0/))[1]。此外,我们构建了一个 CNN 分类器,用于区分正常扫描与肿瘤扫描。通过将不同比例的合成生成数据注入到不同模型的训练过程中,我们能够评估仅使用真实数据与使用真实-合成混合数据模型之间的性能差异。

## **StyleGAN-2 ADA**

StyleGAN-2 与 ADA 首次由 NVIDIA 在 2020 年 NeurIPS 论文中提出:[“在有限数据上训练生成对抗网络”](https://nvlabs-fi-cdn.nvidia.com/stylegan2-ada/ada-paper.pdf) [2]。过去,在小数据集上训练 GAN 通常会导致网络判别器过拟合。因此,判别器往往不是学习区分真实数据和生成数据,而是倾向于记住训练集中的噪声和异常值的模式,而不是学习数据分布的一般趋势。为了解决这个问题,ADA 根据训练中观察到的过拟合程度动态调整数据增强的强度。这有助于模型更好地进行泛化,并在小数据集上实现更好的 GAN 性能。

## **增强数据集**

要使用 StyleGAN-2 ADA 模型,我们使用了来自 GitHub 的官方 NVIDIA 模型实现,具体可以在[这里](https://github.com/NVlabs/stylegan3)找到。*请注意,这是 StyleGAN-3 的仓库,但仍然可以运行 StyleGAN-2。*

```py
!git clone https://github.com/NVlabs/stylegan3

根据你的设置,你可能需要安装依赖项并进行一些其他预处理。例如,我们选择将数据集图像调整大小并缩小为 224x224,因为我们只有一个 GPU,使用更大的图像尺寸在计算上更加昂贵。我们选择使用 224x224 的图像尺寸,因为我们为 CNN 选择的预训练模型 ResNet 优化了这种图像大小。

!pip install pillow
from PIL import Image
import os

'''Loops through the files in an input folder (input_folder), resizes them to a
specified new size (new_size), an adds them to an output folder (output_folder).'''
def resize_images_in_folder(input_folder, output_folder, new_size):
    # Loop through all files in the input folder
    for filename in os.listdir(input_folder):
        input_path = os.path.join(input_folder, filename)

        # Check if the file is an image
        if os.path.isfile(input_path) and filename.lower().endswith(('.png', '.jpg', '.jpeg', '.gif')):
            # Open the image file
            image = Image.open(input_path)

            #Convert to RGB
            image = image.convert('RGB')

            # Resize the image
            resized_image = image.resize(new_size)

            # Generate the output file path
            output_path = os.path.join(output_folder, filename)

            # Save the resized image to the output folder
            resized_image.save(output_path)

            print(f"Resized {filename} and saved to {output_path}")

要开始训练过程,首先导航到你克隆的仓库目录,然后运行以下命令。

import os

!python dataset_tool.py --source= "Raw Data Directory" --dest="Output Directory" --resolution='256x256'

# Training
EXPERIMENTS = "Output directory where the Network Pickle File will be saved""
DATA = "Your Training DataSet Directory"
SNAP = 10
KIMG = 80

# Build the command and run it
cmd = f"/usr/bin/python3 /content/stylegan3/train.py --snap {SNAP} --outdir {EXPERIMENTS} --data {DATA}  --kimg {KIMG} --cfg stylegan2 --gpus 1 --batch 8 --gamma 50"
!{cmd} 

SNAP指的是你希望在多少个训练步骤(信息显示的训练步数)后拍摄网络快照并将其保存到 pickle 文件中。

KIMG指的是你希望输入到 GAN 中的成千上万的图像数量。

GAMMA d决定正则化对判别器的影响强度。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/b34210233f78736ace829dafc60eb73c.pnghttps://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/6a3268bd615e1c08b05b12fa8d74ea19.png

初始生成的图像

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/86495efb421750130e2efe65762c8570.pnghttps://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/948a9ccdfd5f21abf3313d44279cfa62.png

训练期间生成的图像

一旦你的模型完成训练(根据你的计算资源,这可能需要多个小时),你就可以使用训练好的网络生成图像。

pickle_file = "Network_Snapshot.pkl"
model_path = f'Path to Pickle File/{pickle_file}'
SAMPLES = Number of samples you want to generate
!python /content/stylegan3/gen_images.py --outdir=Output Directory --trunc=1 --seeds {SAMPLES} \
    --network=$model_path

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/43a52ee7c15a0a27c8aee5c3c86606fd.pnghttps://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/7496b11702ef63c7c146d11d852d35d2.png

正常真实图像(左)与正常生成图像(右)

迁移学习与卷积神经网络

为了评估我们合成生成数据的有效性,我们首先在原始数据上训练了一个 CNN 模型。一旦我们得到了测试集上的基准准确度,我们就用逐渐增加的合成数据重新训练了模型。

为了将数据输入模型,我们使用了 Keras 数据生成器,它将样本直接从指定目录流入模型。原始数据集有 4 个类别,分别代表不同类型的癌症,但为了简化问题,我们将其转化为二分类问题。我们决定从原始 Kaggle 数据集中选择正常类和鳞状类进行处理。

# Define directories for training, validation, and test datasets
train_dir = 'Your training data directory'
test_dir = 'Your testing data directory'
val_dir = 'Your validation data directory'

# Utilize data genarators to flow directly from directories
train_generator = train_datagen.flow_from_directory(
    train_dir,
    target_size=(224, 224),
    batch_size=20,
    class_mode='binary',  #Use 'categorical' for multi-class classification
    shuffle=True,
    seed=42 )

val_generator = val_datagen.flow_from_directory(
    val_dir,
    target_size=(224, 224),
    batch_size=20,
    class_mode='binary',
    shuffle=True )

test_generator = test_datagen.flow_from_directory(
    test_dir,
    target_size=(224, 224),
    batch_size=20,
    class_mode='binary',
    shuffle=True )

为了构建我们的模型,我们首先使用了 ResNet50 基础架构和模型权重。我们选择使用 ResNet50 是因为它具有适中的架构大小、良好的文档支持,并且通过 Keras 易于使用。在导入带有 Imagenet 模型权重的 ResNet50 后,我们冻结了 ResNet50 的层,并在其上添加了可训练的密集层,帮助网络学习我们特定的分类任务。

我们还选择了引入批量归一化(batch normalization),通过对层输入进行归一化并减少内部协变量偏移,能够加速收敛并使训练更加稳定[3]。此外,它还可以提供一种正则化效果,有助于防止我们添加的可训练密集层发生过拟合。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/563757ec5c2f1f0f7c1b6a808ac3f16b.png

我们的模型架构

起初,我们的模型表现不佳。我们通过将激活函数从 ReLU 切换到 leaky ReLU 解决了这个问题。这表明我们的网络可能正面临着 ReLU 死亡或神经元失效的问题。简而言之,由于 ReLU 对负数的梯度始终为零,这可能导致神经元“死亡”,从而无法对网络作出贡献[4][5]。由于 leaky ReLU 对负值不为零,使用它作为激活函数有助于解决这个问题。

结果

为了测试我们的合成数据,我们在 5 个不同实例上训练了上述 CNN,分别使用了 0%、25%、50%、75%和 100%的附加合成样本。例如,0%的合成样本意味着数据全部为原始数据,而 100%则意味着训练集包含相等数量的原始数据和合成数据。对于每个网络,我们随后使用准确度指标在一组未见过的实际测试数据上评估了性能。下图可视化了不同合成数据比例对测试准确率的影响。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/9a3fef522d3001511884da078b5b2dc7.png

二分类(正常与鳞状肿瘤)分类的测试准确率

训练模型时不稳定,因此我们排除了准确度为 1.0 或极低的迭代。这帮助我们避免了过拟合或欠拟合的训练迭代。

我们可以看到,从 0%到 25%时,测试准确度出现了急剧上升,这表明即使是通过少量的数据增强,也能对数据最初较少的问题产生较大影响。

由于我们仅在 80 KIMG 上训练了我们的 GAN(由于计算限制),因此如果有更多的 GAN 训练迭代,我们的合成数据质量可能会更好。值得注意的是,合成数据质量的提高也可能影响上述图表。我们假设,合成数据质量的提高将导致在训练中使用合成数据的最佳比例增加。此外,如果合成图像更好地适应我们训练数据的真实分布,我们就可以在模型训练中融入更多的合成图像而不至于过拟合。

结论

在这个项目中,使用 GAN 进行有限数据的增强被证明是扩展训练集并且更重要的是提高分类精度的有效技术。尽管我们选择了一个小而基础的问题,但这一技术可以通过几种方式轻松扩展。未来的工作可能包括使用更多的计算资源来获取更好的合成样本,引入更多的类别到分类任务中(使其成为一个多分类问题),并实验更新的 GAN 架构。不管怎样,使用 GAN 增强小数据集现在可以将许多以前受数据限制的问题纳入深度神经网络的范畴。

Kaggle 数据集

我们将增强和重新调整大小的图像汇编成以下Kaggle 数据集。该数据集包含 501 张正常和 501 张鳞状的 224x224 合成图像,可用于进一步的实验。

我们的GitHub 仓库

引用

[1] Hany, Mohamed, 胸部 CT 扫描图像数据集,Kaggle(2020)。

[2] Karras, Tero,等, 使用有限数据训练生成对抗网络(2020),《神经信息处理系统进展》2020。

[3] Ioffe, Sergey, 和 Christian Szegedy, 批量归一化:通过减少内部协变量偏移加速深度网络训练,(2015),国际机器学习会议。pmlr,2015。

[4] He, Kaiming,等, 深入探讨修正器:在 imagenet 分类中超越人类级别的表现,(2015),IEEE 国际计算机视觉会议论文集。2015。

[5] Bai, Yuhan, RELU 函数及其导函数回顾,(2022),SHS Web of Conferences。第 144 卷。EDP Sciences,2022。

增强数据科学工作流:掌握 Jupyter Notebook 的版本控制

原文:towardsdatascience.com/enhancing-data-science-workflows-mastering-version-control-for-jupyter-notebooks-b03c839e25ec?source=collection_archive---------3-----------------------#2024-01-11

一份实践指南,帮助通过 Jupytext、nbstripout 和 nbconvert 实现协作与可复现性

https://medium.com/@le_Tomassini?source=post_page---byline--b03c839e25ec--------------------------------https://towardsdatascience.com/?source=post_page---byline--b03c839e25ec-------------------------------- Alessandro Tomassini

·发布于 Towards Data Science ·8 分钟阅读·2024 年 1 月 11 日

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/a4d0c2b233241726f6bb8978d78ffd82.png

图像由 DALL-E 生成

对于数据科学家来说,使用版本控制系统有效管理 Jupyter notebooks 是至关重要的。这不仅是为了保持工作流的有序,还为了确保结果的可复现性,并促进团队成员之间的协作。在本指南中,我们将探索三个关键工具——Jupytext、nbstripout 和 nbconvert——每个工具都有其独特的特点。我将提供详细的描述、实用示例,以及一个(希望是)平衡的观点,帮助你确定最适合你特定需求的 notebook 版本控制工具。

理解 Jupyter notebooks 和版本控制的挑战

Jupyter notebooks 虽然非常适合进行探索性数据分析和可视化,但在版本控制方面却带来了挑战。这主要是因为这些 notebooks 不仅仅是普通的文本文件,而是以 JSON 文档的形式进行结构化。尽管这种格式非常适合维持代码、文本和输出数据之间复杂的相互关系,但在处理版本控制时却面临诸多挑战……

在 RAG 设置中通过自我检索机制提升直接答案准确性

原文:towardsdatascience.com/enhancing-direct-answer-accuracy-in-rag-setup-with-self-retrieval-mechanisms-8162c8cc7853?source=collection_archive---------8-----------------------#2024-04-30

利用大语言模型(LLM)的强大能力显著提升在 RAG 设置中直接生成答案时检索到的文档上下文的质量。

https://agustinus-nalwan.medium.com/?source=post_page---byline--8162c8cc7853--------------------------------https://towardsdatascience.com/?source=post_page---byline--8162c8cc7853-------------------------------- Agustinus Nalwan

·发布于Towards Data Science ·26 分钟阅读·2024 年 4 月 30 日

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/53adc4aba888bcd3262f20d540412a12.png

一个机器人正在搜索信息(图像由 Midjourney 生成)

概述

部署检索增强生成(RAG)工作流到生产环境中引入了一个常见的挑战:在检索阶段召回率显著下降。当提供给直接生成答案的文档上下文缺乏关键信息时,这个问题变得尤为明显,进而影响生成答案的质量。当处理类似性质且由长篇文章构成的文档时,这一挑战尤为突出。在某些情况下,为了回答一个问题,可能需要考虑文章的大部分内容,例如在我们关于汽车的数百万篇编辑文章中进行导航。在这种情况下,结合混合搜索方法的自我检索 RAG 实现提供了一个有前景的解决方案。

RAG 无疑是一个强大的工具,像 LangChain 或 LlamaIndex 这样的框架使其集成变得非常简单。这种简便性可能会给人一种 RAG 是“一刀切”解决方案的印象。然而,在我们努力提升编辑文章搜索工具以实现更丰富语义的搜索时……

用生成式人工智能提升电子商务 — 第一部分

原文:towardsdatascience.com/enhancing-e-commerce-with-generative-ai-part-1-9e402fb30e7b?source=collection_archive---------13-----------------------#2024-07-31

从数据到产品推荐

https://medium.com/@mina.ghashami?source=post_page---byline--9e402fb30e7b--------------------------------https://towardsdatascience.com/?source=post_page---byline--9e402fb30e7b-------------------------------- Mina Ghashami

·发表于Towards Data Science ·16 分钟阅读·2024 年 7 月 31 日

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/639f05553254a4e2b8989e943a2f9e28.png

图片来自unsplash.com

生成式人工智能正在革新各个领域企业的运作方式,电子商务也不例外。随着电子商务的不断发展,对更具可扩展性和复杂度的解决方案的需求也在增加。在这一系列文章中,我们将探讨生成式人工智能在电子商务中的各种应用。在第一部分中,我们将介绍生成式人工智能如何帮助产品推荐。我们首先使用产品的元数据来学习其代表性嵌入,并展示这些嵌入如何帮助分类物品。接着,我们根据用户的历史购买记录计算用户的嵌入。最后,我们利用学到的用户嵌入和物品嵌入来推荐产品。在系列的第二部分中,我们将探讨如何生成引人入胜的产品描述,并如何从评论中提取关键的正面和负面特征。

让我们从第一部分开始。这是本文的目录:

目录

1. 数据集

  • 合成数据生成

  • 物品元数据数据集生成

  • 用户-物品数据集生成

3. 学习物品的代表性嵌入

  • 数据准备与清洗

通过语义层增强语言模型与图数据库之间的交互

原文:towardsdatascience.com/enhancing-interaction-between-language-models-and-graph-databases-via-a-semantic-layer-0a78ad3eba49?source=collection_archive---------0-----------------------#2024-01-18

为 LLM 代理提供一套强大的工具,以便其与图数据库进行交互

https://bratanic-tomaz.medium.com/?source=post_page---byline--0a78ad3eba49--------------------------------https://towardsdatascience.com/?source=post_page---byline--0a78ad3eba49-------------------------------- Tomaz Bratanic

·发表于Towards Data Science ·阅读时长 11 分钟·2024 年 1 月 18 日

知识图谱通过灵活的数据模式提供了对数据的出色表示,能够存储结构化和非结构化信息。你可以使用 Cypher 语句从图数据库(如 Neo4j)中检索信息。一种选择是使用大型语言模型(LLMs)生成 Cypher 语句。虽然这种方式提供了极大的灵活性,但事实是,基础 LLMs 在始终如一地生成精确的 Cypher 语句方面仍然不够稳定。因此,我们需要寻找一种替代方案,以确保一致性和稳健性。那么,如果 LLM 从用户输入中提取参数,而不是直接开发 Cypher 语句,并根据用户意图使用预定义的函数或 Cypher 模板呢?简而言之,你可以为 LLM 提供一套预定义的工具和使用指令,指示何时以及如何基于用户输入使用它们,这也被称为语义层。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/aeeb64a2a96d1a41bf353ad6817a5d4e.png

语义层是一个中间步骤,提供了一种更精确、更稳健的方式,使 LLMs 与知识图谱进行交互。图片来自作者。灵感来源于这张图片

语义层由多个暴露给大语言模型的工具组成,它可以用来与知识图谱进行互动。这些工具的复杂性各异。你可以将语义层中的每个工具看作一个函数。例如,看看以下这个函数。

def get_information(entity: str, type: str) -> str:
    candidates = get_candidates(entity, type)
    if not candidates:
        return "No information was found about the movie or person in the database"
    elif len(candidates) > 1:
        newline = "\n"
        return (
            "Need additional information, which of these "
            f"did you mean: {newline + newline.join(str(d) for d in candidates)}"
        )
    data = graph.query(
        description_query, params={"candidate": candidates[0]["candidate"]}
    )
    return data[0]["context"]

这些工具可以有多个输入参数,正如上面的例子所示,这使你能够实现复杂的工具。此外,工作流不仅仅可以由数据库查询组成,还允许你处理任何边缘情况或异常,具体取决于你的需求。其优势在于,你将可能大部分时间有效的提示工程问题,转化为每次都能精确执行的代码工程问题。

电影代理

在这篇博客文章中,我们将展示如何实现一个语义层,使大语言模型代理能够与包含演员、电影及其评分信息的知识图谱进行互动。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/e04d930fdbbd3e70efc04a3060abfcff.png

电影代理架构。图像由作者提供。

取自文档(同样由我编写):

代理利用多种工具有效地与 Neo4j 图数据库进行交互。

*** 信息工具**:检索有关电影或个人的数据,确保代理能够访问最新和最相关的信息。

*** 推荐工具**:根据用户的偏好和输入提供电影推荐。

*** 内存工具**:在知识图谱中存储有关用户偏好的信息,从而在多次互动中提供个性化体验。

代理可以使用信息或推荐工具从数据库中检索信息,或使用内存工具将用户偏好存储到数据库中。

预定义的函数和工具使得代理能够编排复杂的用户体验,引导用户朝着特定目标前进,或提供与其当前用户旅程位置相关的定制信息。

这种预定义方法通过减少大语言模型的艺术自由度,从而增强了系统的稳健性,确保回应更加结构化,并与预定的用户流程对齐,从而改善了整体的用户体验。

电影代理的语义层后端已经实现,并作为一个LangChain 模板可用。我使用了这个模板构建了一个简单的 Streamlit 聊天应用程序。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/8e94a84617ebaa1796729ef1cae7a77d.png

Streamlit 聊天界面。图像由作者提供。

代码可在GitHub上获取。你可以通过定义环境变量并执行以下命令来启动项目:

docker-compose up

图模型

该图基于MovieLens数据集。它包含有关演员、电影和 10 万条电影评分的信息。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/1498878706b68d43ef3619c7bbbec6a1.png

图谱架构。图像由作者提供。

该可视化图展示了一个知识图谱,包含那些参与过电影表演或导演的个人,并且按类型进行了进一步分类。每个电影节点包含其发布日期、标题和 IMDb 评分信息。图谱中还包含用户评分,我们可以利用这些评分提供推荐。

你可以通过执行位于文件夹根目录的 ingest.py 脚本来填充图谱。

定义工具

现在,我们将定义代理可以用来与知识图谱互动的工具。我们从信息工具开始。信息工具的设计目的是获取有关演员、导演和电影的相关信息。Python 代码如下所示:

def get_information(entity: str, type: str) -> str:
    # Use full text index to find relevant movies or people
    candidates = get_candidates(entity, type)
    if not candidates:
        return "No information was found about the movie or person in the database"
    elif len(candidates) > 1:
        newline = "\n"
        return (
            "Need additional information, which of these "
            f"did you mean: {newline + newline.join(str(d) for d in candidates)}"
        )
    data = graph.query(
        description_query, params={"candidate": candidates[0]["candidate"]}
    )
    return data[0]["context"]

函数首先通过全文索引查找提到的相关人物或电影。Neo4j 中的全文索引底层使用 Lucene。它支持无缝实现基于文本距离的查找,允许用户拼写错误的词语也能获取到结果。如果没有找到相关实体,我们可以直接返回响应。另一方面,如果识别出多个候选项,我们可以引导代理向用户询问后续问题,并让他们对感兴趣的电影或人物给出更具体的信息。假设用户问:“约翰是谁?”。

print(get_information("John", "person"))
# Need additional information, which of these did you mean: 
# {'candidate': 'John Lodge', 'label': 'Person'}
# {'candidate': 'John Warren', 'label': 'Person'}
# {'candidate': 'John Gray', 'label': 'Person'}

在这种情况下,工具通知代理需要额外的信息。通过简单的提示工程,我们可以引导代理询问用户后续问题。假设用户提供了足够具体的信息,这样工具就能识别特定的电影或人物。在这种情况下,我们使用带参数的 Cypher 语句来检索相关信息。

print(get_information("Keanu Reeves", "person"))
# type:Actor
# title: Keanu Reeves
# year: 
# ACTED_IN: Matrix Reloaded, The, Side by Side, Matrix Revolutions, The, Sweet November, Replacements, The, Hardball, Matrix, The, Constantine, Bill & Ted's Bogus Journey, Street Kings, Lake House, The, Chain Reaction, Walk in the Clouds, A, Little Buddha, Bill & Ted's Excellent Adventure, The Devil's Advocate, Johnny Mnemonic, Speed, Feeling Minnesota, The Neon Demon, 47 Ronin, Henry's Crime, Day the Earth Stood Still, The, John Wick, River's Edge, Man of Tai Chi, Dracula (Bram Stoker's Dracula), Point Break, My Own Private Idaho, Scanner Darkly, A, Something's Gotta Give, Watcher, The, Gift, The
# DIRECTED: Man of Tai Chi

有了这些信息,代理可以回答大多数关于基努·里维斯的问题。

现在,让我们引导代理有效地使用这个工具。幸运的是,借助 LangChain,这一过程既简单又高效。首先,我们使用 Pydantic 对象定义函数的输入参数。

class InformationInput(BaseModel):
    entity: str = Field(description="movie or a person mentioned in the question")
    entity_type: str = Field(
        description="type of the entity. Available options are 'movie' or 'person'"
    )

在这里,我们说明了 entity 和 entity_type 参数都是字符串类型。entity 参数输入被定义为问题中提到的电影或人物。而对于 entity_type,我们也提供了可选项。在处理低基数(即有少量不同值)的情况时,我们可以直接向 LLM 提供可选项,让它使用有效的输入。正如我们之前看到的,我们使用全文索引来消除电影或人物的歧义,因为有太多值不能直接在提示中提供。

现在,让我们将这一切汇总到信息工具定义中。

class InformationTool(BaseTool):
    name = "Information"
    description = (
        "useful for when you need to answer questions about various actors or movies"
    )
    args_schema: Type[BaseModel] = InformationInput

    def _run(
        self,
        entity: str,
        entity_type: str,
        run_manager: Optional[CallbackManagerForToolRun] = None,
    ) -> str:
        """Use the tool."""
        return get_information(entity, entity_type)

准确简洁的工具定义是语义层的重要组成部分,以便代理在需要时能够正确选择相关工具。

推荐工具稍微复杂一些。

def recommend_movie(movie: Optional[str] = None, genre: Optional[str] = None) -> str:
    """
    Recommends movies based on user's history and preference
    for a specific movie and/or genre.
    Returns:
        str: A string containing a list of recommended movies, or an error message.
    """
    user_id = get_user_id()
    params = {"user_id": user_id, "genre": genre}
    if not movie and not genre:
        # Try to recommend a movie based on the information in the db
        response = graph.query(recommendation_query_db_history, params)
        try:
            return ", ".join([el["movie"] for el in response])
        except Exception:
            return "Can you tell us about some of the movies you liked?"
    if not movie and genre:
        # Recommend top voted movies in the genre the user haven't seen before
        response = graph.query(recommendation_query_genre, params)
        try:
            return ", ".join([el["movie"] for el in response])
        except Exception:
            return "Something went wrong"

    candidates = get_candidates(movie, "movie")
    if not candidates:
        return "The movie you mentioned wasn't found in the database"
    params["movieTitles"] = [el["candidate"] for el in candidates]
    query = recommendation_query_movie(bool(genre))
    response = graph.query(query, params)
    try:
        return ", ".join([el["movie"] for el in response])
    except Exception:
        return "Something went wrong"

首先需要注意的是,这两个输入参数都是可选的。因此,我们需要引入处理所有可能输入参数组合和缺失情况的工作流。为了生成个性化推荐,我们首先获取user_id,然后将其传递到下游的 Cypher 推荐语句中。

与之前一样,我们需要向代理呈现函数的输入。

class RecommenderInput(BaseModel):
    movie: Optional[str] = Field(description="movie used for recommendation")
    genre: Optional[str] = Field(
        description=(
            "genre used for recommendation. Available options are:" f"{all_genres}"
        )
    )

由于只有 20 种可用的电影类型,我们将它们的值作为提示的一部分提供。为了避免电影歧义,我们再次在函数内部使用全文索引。像之前一样,我们以工具定义结束,以通知 LLM 何时使用它。

class RecommenderTool(BaseTool):
    name = "Recommender"
    description = "useful for when you need to recommend a movie"
    args_schema: Type[BaseModel] = RecommenderInput

    def _run(
        self,
        movie: Optional[str] = None,
        genre: Optional[str] = None,
        run_manager: Optional[CallbackManagerForToolRun] = None,
    ) -> str:
        """Use the tool."""
        return recommend_movie(movie, genre)

到目前为止,我们已经定义了两个工具来从数据库中检索数据。然而,信息流不必是单向的。例如,当用户告诉代理他们已经看过某部电影并且可能喜欢它时,我们可以将该信息存储在数据库中,并在未来的推荐中使用它。此时,记忆工具将非常有用。

def store_movie_rating(movie: str, rating: int):
    user_id = get_user_id()
    candidates = get_candidates(movie, "movie")
    if not candidates:
        return "This movie is not in our database"
    response = graph.query(
        store_rating_query,
        params={"user_id": user_id, "candidates": candidates, "rating": rating},
    )
    try:
        return response[0]["response"]
    except Exception as e:
        print(e)
        return "Something went wrong"

class MemoryInput(BaseModel):
    movie: str = Field(description="movie the user liked")
    rating: int = Field(
        description=(
            "Rating from 1 to 5, where one represents heavy dislike "
            "and 5 represent the user loved the movie"
        )
    )

记忆工具有两个强制性的输入参数,用于定义电影及其评分。这是一个简单的工具。我需要提到的一点是,在我的测试中,我注意到可能需要提供何时给出特定评分的示例,因为 LLM 开箱即用时并不是最擅长这方面的处理。

代理

现在让我们通过使用LangChain 表达式语言(LCEL)来定义一个代理,把所有内容整合在一起。

llm = ChatOpenAI(temperature=0, model="gpt-4", streaming=True)
tools = [InformationTool(), RecommenderTool(), MemoryTool()]

llm_with_tools = llm.bind(functions=[format_tool_to_openai_function(t) for t in tools])

prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "You are a helpful assistant that finds information about movies "
            " and recommends them. If tools require follow up questions, "
            "make sure to ask the user for clarification. Make sure to include any "
            "available options that need to be clarified in the follow up questions "
            "Do only the things the user specifically requested. ",
        ),
        MessagesPlaceholder(variable_name="chat_history"),
        ("user", "{input}"),
        MessagesPlaceholder(variable_name="agent_scratchpad"),
    ]
)

agent = (
    {
        "input": lambda x: x["input"],
        "chat_history": lambda x: _format_chat_history(x["chat_history"])
        if x.get("chat_history")
        else [],
        "agent_scratchpad": lambda x: format_to_openai_function_messages(
            x["intermediate_steps"]
        ),
    }
    | prompt
    | llm_with_tools
    | OpenAIFunctionsAgentOutputParser()
)

agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True).with_types(
    input_type=AgentInput, output_type=Output
)

LangChain 表达式语言使得定义代理并暴露其所有功能变得非常方便。我们不会深入探讨 LCEL 语法,因为那超出了本博客的范围。

电影代理后台作为 API 端点通过LangServe暴露。

Streamlit 聊天应用

现在我们只需要实现一个 Streamlit 应用,它连接到 LangServe API 端点,就可以开始使用了。我们将看看用于检索代理响应的异步函数。

async def get_agent_response(
    input: str, stream_handler: StreamHandler, chat_history: Optional[List[Tuple]] = []
):
    url = "http://api:8080/movie-agent/"
    st.session_state["generated"].append("")
    remote_runnable = RemoteRunnable(url)
    async for chunk in remote_runnable.astream_log(
        {"input": input, "chat_history": chat_history}
    ):
        log_entry = chunk.ops[0]
        value = log_entry.get("value")
        if isinstance(value, dict) and isinstance(value.get("steps"), list):
            for step in value.get("steps"):
                stream_handler.new_status(step["action"].log.strip("\n"))
        elif isinstance(value, str):
            st.session_state["generated"][-1] += value
            stream_handler.new_token(value)

函数get_agent_response旨在与电影代理 API 进行交互。它向 API 发送包含用户输入和聊天历史的请求,然后异步处理 API 的响应。该函数处理不同类型的响应,更新流处理器的新状态,并将生成的文本附加到会话状态中,从而使我们能够将结果流式传输给用户。

现在让我们进行测试。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/bce1fd1386d8ea222ccafa7738aa6b16.png

电影代理在运行中。图像由作者提供。

结果显示,电影代理提供了与用户出乎意料的良好和引导式互动。

结论

总结来说,在语言模型与图数据库交互中引入语义层,如我们在电影代理(Movie Agent)中所示,代表了提升用户体验和数据交互效率的一次重大飞跃。通过将重点从生成任意的 Cypher 语句转移到利用结构化、预定义的工具和功能套件,语义层为语言模型的交互带来了一个新的精确性和一致性水平。这种方法不仅简化了从知识图谱中提取相关信息的过程,还确保了一个更具目标导向和用户中心的体验。

语义层充当着桥梁的角色,将用户意图转换为具体、可执行的查询,语言模型能够准确可靠地执行这些查询。因此,用户受益于一个不仅能够更有效理解他们查询的系统,而且还能更轻松、减少歧义地引导他们朝着预期的结果前进。此外,通过将语言模型的响应限制在这些预定义工具的范围内,我们降低了错误或无关输出的风险,从而增强了系统的可信度和可靠性。

代码可在GitHub上找到。

数据集

F. Maxwell Harper 和 Joseph A. Konstan. 2015. 《MovieLens 数据集:历史与背景》。ACM 交互智能系统交易(TiiS)5, 4: 19:1–19:19. doi.org/10.1145/2827872

通过因果 AI 提升营销组合建模

原文:towardsdatascience.com/enhancing-marketing-mix-modelling-with-causal-ai-77f638bce3a9?source=collection_archive---------4-----------------------#2024-06-21

因果 AI,探索因果推理与机器学习的整合

https://medium.com/@raz1470?source=post_page---byline--77f638bce3a9--------------------------------https://towardsdatascience.com/?source=post_page---byline--77f638bce3a9-------------------------------- Ryan O’Sullivan

·发布于 Towards Data Science ·阅读时间:8 分钟·2024 年 6 月 21 日

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/ba87b7898135052179d91cc352ccb0d1.png

图片来源:Alexey Ruban 通过 Unsplash

这系列文章讲的是什么?

欢迎来到我的因果 AI 系列文章,我们将在这里探讨因果推理与机器学习模型的整合。你将会看到多个在不同商业场景中的实践应用。

在上一篇文章中,我们介绍了验证合成控制方法的因果影响。在本文中,我们将继续探讨通过因果 AI 提升营销组合建模

如果你错过了上一篇关于合成控制的文章,可以在这里查看:

## 验证合成控制方法的因果影响

因果 AI,探索因果推理与机器学习的整合

towardsdatascience.com

介绍

数字跟踪的持续挑战促使了营销组合建模(MMM)的新一轮复兴。在最近的因果 AI 大会上,Judea Pearl 表示,营销可能是第一个采纳因果 AI 的行业。因此,我决定是时候开始撰写我在过去 7 年里有关 MMM、因果 AI 和实验交集的学习成果了。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/ea63f8de5bb6afdf127ae21ac6377379.png

用户生成的图片

以下领域将被探讨:

  • 什么是 MMM?

  • 因果 AI 如何增强 MMM?

  • 我们可以进行哪些实验来完成三角定位?

  • 营销测量中的突出挑战。

完整的笔记本可以在这里找到:

[## causal_ai/notebooks/enhancing marketing mix modelling with causal ai.ipynb 在主分支 ·…

本项目介绍了因果 AI 及其如何推动商业价值。- causal_ai/notebooks/enhancing marketing mix…

github.com](https://github.com/raz1470/causal_ai/blob/main/notebooks/enhancing%20marketing%20mix%20modelling%20with%20causal%20ai.ipynb?source=post_page-----77f638bce3a9--------------------------------)

什么是 MMM?

MMM 是一种统计框架,用于估算每个营销渠道对销售的贡献。它受到计量经济学的深刻影响,最简单的形式是回归模型。让我们来介绍一下其关键组成部分的基础知识!

回归

构建回归模型,其中因变量/目标(通常是销售)是基于多个自变量/特征预测的 —— 这些通常包括在不同营销渠道上的支出和可能影响需求的外部因素。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/919e8bfda9b3518d2649e67e58379f6b.png

用户生成的图片

支出变量的系数表明它们对销售的贡献大小。

Python 中的 PyMC 营销包是开始探索 MMM 的一个很好的起点:

[## MMM 示例笔记本 - pymc-marketing 0.6.0 文档

在本笔记本中,我们通过一个模拟示例来展示来自 pymc-marketing 的媒体混合模型(MMM)API。这…

www.pymc-marketing.io](https://www.pymc-marketing.io/en/stable/notebooks/mmm/mmm_example.html?source=post_page-----77f638bce3a9--------------------------------)

广告存量

广告存量指的是营销支出(或广告支出)对消费者行为的持久影响。它有助于建模营销的长期效果。通常,第一次听说某个品牌时,人们不会急于购买该产品 —— 广告存量的理念是,营销的效果是累积的。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/a0ee219560d16686c9f7cfdc78d4cea9.png

用户生成的图片

最常见的广告存量方法是几何衰减法,它假设广告的影响会以恒定的速率随时间衰减。尽管这种方法相对容易实现,但它的灵活性较差。值得了解的是威布尔方法,它更为灵活 —— PyMC 营销包已实现此方法,务必查阅:

[## weibull_adstock - pymc-marketing 0.6.0 文档]

pymc_marketing.mmm.transformers.weibull_adstock ( x , , , , , , ) [source] 威布尔 Adstock 变换。这…

www.pymc-marketing.io

饱和度

在营销的语境下,饱和度指的是收益递减的概念。增加营销投入可以提高客户获取,但随着时间的推移,影响新受众变得更加困难。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/e5564cac2316d5b85b67bdc6fdd2318c.png

用户生成的图像

我们可以使用几种饱和度方法。迈克利斯-门腾函数是常见的一种——你也可以在 PyMC 营销包中查看这个:

[## michaelis_menten - pymc-marketing 0.6.0 文档]

pymc_marketing.mmm.transformers.michaelis_menten ( x , alpha , lam ) [source] 评估迈克利斯-门腾函数…

www.pymc-marketing.io

因果 AI 如何增强 MMM?

MMM 框架通常使用平坦的回归模型。然而,营销渠道之间的相互作用存在一些复杂性。我们是否有来自因果 AI 工具箱的工具可以帮助解决这个问题?

因果图

因果图擅长将原因与相关性分离,这使得它们成为解决营销渠道相互作用复杂性的问题的绝佳工具。

如果你对因果图不熟悉,可以参考我之前的文章来迅速了解:

[## 使用因果图回答因果问题]

因果 AI,探索将因果推理融入机器学习

towardsdatascience.com](/using-causal-graphs-to-answer-causal-questions-5fd1dd82fa90?source=post_page-----77f638bce3a9--------------------------------)

理解营销图

在缺乏领域知识的情况下估计因果图是具有挑战性的。但我们可以利用因果发现来帮助我们入门——请查看我之前关于因果发现的文章,了解更多信息:

[## 让因果发现在人类商业环境中发挥作用]

因果 AI,探索因果推理与机器学习的结合

towardsdatascience.com

因果发现有其局限性,应该仅用于为图形创建初始假设。幸运的是,关于营销渠道如何相互作用的领域知识非常丰富,我们可以将其纳入其中!

在这里,我分享我多年来与营销专家合作中积累的知识…

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/5db89e3e17d839acfaba055af0159779.png

用户生成图像

  • PPC(付费搜索)对 SEO(自然搜索)有负面影响。我们在 PPC 上的花费越多,SEO 点击量就越少。然而,我们有一个重要的混杂因素……需求!一个简单的回归模型通常无法捕捉到这一复杂性,往往导致 PPC 的过度估计。

  • 社交支出对社交点击有很强的影响,我们花费越多,潜在客户点击社交广告的次数就越多。然而,一些潜在客户可能会先看到社交广告,然后在第二天通过 PPC、SEO 或直接访问你的站点。一个简单的回归模型无法捕捉到这种光环效应。

  • 对于品牌支出,也可以做类似的分析,你通过长期的品牌信息来吸引潜在客户,但没有直接的点击行动号召。这些潜在客户可能在意识到你的品牌后,通过 PPC、SEO 或直接访问你的站点。

  • 点击量是中介变量。如果我们运行一个简单的回归模型并包括中介变量,这在估计因果效应时可能会引发问题。我在这里不会详细讨论这个话题,但使用因果图使我们能够在估计因果效应时仔细控制正确的变量。

希望你能从以上的例子中看到,使用因果图而非简单回归模型会大大增强你的解决方案。计算反事实和进行干预的能力也使它非常有吸引力!

值得注意的是,仍然值得将广告库存和饱和度转化纳入你的框架中。

我们可以进行什么实验来完成三角验证?

在使用观察性数据时,我们还应该努力进行实验,以帮助验证假设并补充我们的因果估计。获取营销中有三种主要的测试方法。让我们深入了解它们!

转化提升测试

社交平台如 Facebook 和 Snapchat 允许你进行转化提升测试。这是一种 AB 测试,我们通过治疗组与对照组的对比来衡量转化的提升。这对于评估因果图中社交支出的反事实(counterfactual)非常有用。

地理位置提升测试

地理提升测试可以用来估算营销暂停期间或你开始使用新渠道时的效果。这对于品牌数字广告和电视广告特别有用,因为它们没有直接的行动号召来衡量。我在上一篇文章中详细讲解了这一点:

## 验证合成控制方法的因果影响

因果 AI,探索将因果推理集成到机器学习中的方法

[towardsdatascience.com

切换回测

PPC(按点击付费)广告系列可以设置为每小时开启和关闭。这为切换回测提供了一个很好的机会。将 PPC 广告系列每小时开关一次,持续几周,然后计算 PPC 和 SEO 点击量在关闭与开启期间的差异。这将帮助你理解多少 PPC 可以通过 SEO 捕捉,从而评估你因果图中关于 PPC 支出的反事实。

我认为,进行实验是调整并增强你因果图信心的一个好方法。但结果也可以用来校准你的模型。看看 PyMC 团队是如何处理这个问题的:

[## 提升测试校准 - pymc-marketing 0.6.0 文档

你可能听过一句话:“所有模型都是错误的;但有些模型是有用的。” 这在许多领域都是真实的,而且…

www.pymc-marketing.io

营销衡量中的突出挑战

今天我讲解了如何利用因果 AI 来提升 MMM(营销混合建模)。然而,因果 AI 并不能解决获取营销中的所有挑战——不幸的是,这些挑战非常多!

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/9d36431f028cc976e4f7cb30399c0326.png

用户生成的图片

  • 根据需求预测调整支出 — 营销支出与销售量高度相关的一个原因是营销团队会根据需求预测来调整支出。解决方案之一是每周随机调整支出范围在-10%到+10%之间,加入一些变动。如你所想,营销团队通常不太喜欢这种方法!

  • 估算需求 — 需求是我们模型中的一个关键变量。然而,收集相关数据非常困难。一个合理的选择是提取与您销售的产品相关的搜索词的 Google 趋势数据。

  • 品牌的长期效应 — 品牌的长期效应很难捕捉,因为通常没有足够的信号来支持这一点。长期的地理提升测试可以帮助解决这个问题。

  • 多重共线性——这实际上是最大的问题之一。我们所有的变量都高度相关。使用岭回归可以稍微缓解这一问题,但它仍然可能成为一个问题。因果图也能提供一些帮助,因为它本质上将问题分解为更小的模型。

  • 营销团队的支持——根据我的经验,这将是你面临的最大挑战。因果图提供了一种吸引营销团队的不错视觉方式。它还为你创造了一个机会,在与他们合作并达成图表细节的过程中建立关系。

我就先说到这里——很希望听听你在评论中的看法!

如果你想继续深入了解因果 AI,记得关注我——在下一篇文章中,我们将探讨因果 AI 是否能够改进我们的预测。

使用 LLM 和统计推理增强 NPS 测量

原文:towardsdatascience.com/enhancing-nps-measurement-with-llms-and-statistical-inference-a0ed0ce9c6cf?source=collection_archive---------6-----------------------#2024-03-06

通过预测驱动推理(PPI)将 LLM 与人工判断结合

https://medium.com/@smsmith714?source=post_page---byline--a0ed0ce9c6cf--------------------------------https://towardsdatascience.com/?source=post_page---byline--a0ed0ce9c6cf-------------------------------- Sean Smith

·发表于 Towards Data Science ·阅读时间 8 分钟·2024 年 3 月 6 日

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/170d3aaa0bfc5e6f90d90cd4a4b39b43.png

机器人解决复杂数学问题,数字艺术。由 Dall-E 2 生成。

介绍

在商业分析中,计算净推荐值(NPS)通常需要员工手动标注数据。有些人可能会考虑使用机器学习模型来标注数据,但这并没有提供我们从人工标注数据中得到的理论保障。这里引入了预测驱动推理(PPI),一种新型的统计技术,它结合了人工和机器标注的数据,创建了数据高效且理论上有保障的置信区间。

本文探讨了 PPI 背后的直觉,并强调了为什么你应该使用它。接着我们将进入如何使用它来处理两个指标的代码演示:NPS 和客户推荐。

预测驱动推理(PPI)

PPI 是 Angelopoulos 等人提出的一种统计技术[1]。其目标是通过结合人工和机器标注的数据来增强置信区间。让我们通过一些步骤来了解它的实用性。

在我们的应用场景中,我们希望在给定一组客户评价的情况下,估计真实的 NPS 分数。通常,员工会手动阅读每条评价,并为其分配一个 1 到 10 的分数,这是一种可靠但效率较低的方法。当需要处理大量评价时,使用更自动化的方法会更加便捷。

为了解决这个问题,我们可以利用机器学习模型。大型语言模型(LLM)是一个很好的候选者,因为它们在处理新任务时具有很好的泛化能力。该模型会被提示读取评论并输出一个分数。这很方便,但模型也有错误和缺陷。在做决策时,我们需要确保我们的数据与人类判断一致。

考虑到两种方法的局限性,如果我们能够将它们结合起来呢?通过预测驱动推断(PPI),我们可以做到这一点!PPI 是一个框架,它结合了人工标注数据在置信区间方面的理论保证与机器标注数据的高效性。通过 PPI,我们旨在发挥两种技术的优势。

工作原理

PPI 的核心是一个叫做“整流器”的工具。我们使用整流器来弥补机器学习模型的预测误差。借助整流器,我们可以结合人工和机器标注数据,构建置信区间。

这是构造置信区间的算法:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/f2cd98a6b898eea3d7582fce12847fce.png

来自[1]的算法 1。

这种方法的优点是代码实现简单。这里是一个简短的代码片段,展示如何实现:

def pp_mean_iid_asymptotic(Y_labeled, Yhat_labeled, Yhat_unlabeled, alpha):
    n = Y_labeled.shape[0]
    N = Yhat_unlabeled.shape[0]
    tildethetaf = Yhat_unlabeled.mean()
    rechat = (Yhat_labeled - Y_labeled).mean() # rectifier (delta hat)
    thetahatPP = tildethetaf - rechat # Prediction-Powered Estimator
    sigmaftilde = np.std(Yhat_unlabeled)  # imputed std dev
    sigmarec = np.std(Yhat_labeled -  Y_labeled) # rectifier std dev
    hw = norm.ppf(1-alpha/2)*np.sqrt((sigmaftilde**2/N) + (sigmarec**2/n)) # normal approximation

    return [thetahatPP - hw, thetahatPP + hw] # confidence interval

如果你想更深入了解,我推荐观看这段YouTube视频,视频中的 Clara Wong-Fannjiang(其中一位作者)进行了解释,或者查看论文。这些资源能比我在这里所做的更好地解释相关概念。

需要理解的重要一点是,PPI 的置信区间比仅使用人工构建的置信区间更为紧凑,并且具有预测置信区间所没有的理论保证。理解这一点就足以完成代码练习。

方法

完整的笔记本链接可以在这里找到。我将逐步介绍关键步骤,并提供一些评论。大部分代码归功于 PPI 库的作者。

在我们的例子中,我们将使用 PPI 来估计均值。也可以估计其他参数,比如分位数、逻辑回归/线性回归系数等。通过这个例子后,你可以在这里找到更多内容。

设置步骤

首先,让我们安装 PPI 库。要查看更多关于该库的信息,请查看这个仓库这里

pip install ppi-python

对于这个例子,我们将模拟数据。很难找到公开可用的 NPS 得分数据,因此我创建了以下代码。不管数据来源于何处,方法都是相同的。我们将创建一个包含Overall_Rating(NPS 得分)和Recommended(布尔值)列的 DataFrame。

def simulate_nps_scores(n, mu=3, mu2=9, std_dev=1):

    # simulate each aspect of bimodal distribution
    X1 = np.random.normal(mu, std_dev, n // 3)
    X2 = np.random.normal(mu2, std_dev, n // 3)

    X = np.concatenate([X1, X2])
    X3 = np.ones(n - X.shape[0])  # make 1-inflated
    X = np.concatenate([X, X3])

    X = np.clip(X, a_min=1, a_max=10)  # fix to 1-10 range for NPS

    return X

def simulate_recommended(mean, n):
    return np.array([1 if random.uniform(0,1) <= mean else 0 for _ in range(n)])

使用这些函数,我们可以构建 DataFrame:

N = 20000
data = pd.DataFrame({
    'Overall_Rating': simulate_nps_scores(N), 
    'Recommended': simulate_recommended(0.34, N)
})

模拟 LLM 预测

为了制作一个更灵活的演示,我提供了两种创建预测的选项。第一种是为预测创建模拟误差。这使你能够实验 PPI,并观察它如何作用于不同的理论模型。以下是这种情况的示例:

if target_response == 'NPS':
    Y_total = data.Overall_Rating.to_numpy()
    Yhat_total = np.array([random.normalvariate(x, error_std_dev) for x in Y_total])
    Yhat_total = np.array([max(min(x, 10), 1) for x in Yhat_total])

elif target_response == 'reccomended':
    Y_total = data.Recommended.to_numpy()

    Yhat_total = np.array([
        x if random.uniform(0, 1) >= error_prob else int(not x)
        for x in Y_total
    ])

else:
    raise Exception('Invalid target_response')

LLM 预测

如果你有带有客户评论的数据,那么使用 LLM 对其打分就很容易了。以下是你可以用来执行此操作的一些提示:

NPS_prompt_template = lambda review: f"""Given the following review please return the Net Promoter Score (NPS).
Return only the integer value from 1-10 and nothing else.

Review: 
{review}

NPS:"""

recommended_prompt_template = lambda review: f"""Given the following review please determine if the customer would recommend the business.
Return only 'True' or 'False'.

Review: {review}
Recommended:"""

你可能希望使用推荐而不是 NPS,因为布尔分类问题要简单得多。一些企业偏好使用 NPS,因为它是行业标准。根据你的问题,你可以选择哪个更有意义。

使用 LLM,你还可以检查不同类别的得分。这些类别可以是你想要衡量的不同产品或服务。这是使用 LLM 的一个巨大优势,因为它对许多问题都具有灵活性,但我们在报告中也通过使用 PPI 来考虑误差。

运行 PPI

现在是时候构建置信区间了。以下是执行重负荷任务的代码片段:

for i in tqdm(range(ns.shape[0])):
    for j in range(num_trials):
        # Prediction-Powered Inference
        n = ns[i]
        rand_idx = np.random.permutation(n_total)
        _Yhat = Yhat_total[rand_idx[:n]]
        _Y = Y_total[rand_idx[:n]]
        _Yhat_unlabeled = Yhat_total[n:]

        ppi_ci = ppi_mean_ci(_Y, _Yhat, _Yhat_unlabeled, alpha=alpha)

        classical_ci = classical_mean_ci(_Y, alpha=alpha)

我们在这里所做的,是模拟 PPI 与经典置信区间的不同,针对不同数量的人工响应(n)。我们可以绘制出以下信息。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/6582044f5a3a85c482fd613fbb3f56e6.png

NPS 得分的 PPI 与经典方法对比。绿色表示 PPI,灰色表示经典方法,黄色表示机器标注数据,虚线表示真实的总体参数。图像由作者提供。

在这张比较不同 n 值(使用的人工标注数量)的图表中,你可以看到,PPI 的置信区间始终比仅使用人工标注数据的置信区间更紧密。这展示了 PPI 的关键价值:即使我们有一个有缺陷的机器学习模型,我们通过将其与人工数据结合使用,仍然能生成比仅使用它更好的置信区间。

我们可以看到下方推荐的类似结果。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/bb38f4ec241dd0df1e36f98562465db8.png

推荐的 PPI 与经典方法对比。绿色表示 PPI,灰色表示经典方法,黄色表示机器标注数据,虚线表示真实的总体参数。图像由作者提供。

在这种情况下,我们查看的是所有客户中推荐使用该企业的客户的真实百分比。再次,我们看到,使用 PPI,我们能够构建比仅使用人工标注数据更紧密的置信区间。

你还会注意到黄色的机器预测的置信区间。这些预测并不完全准确,所以置信区间非常宽泛。这就是为什么我们需要一些人工标注数据,而不能仅仅使用机器标注数据的原因。

决策制定

现在让我们考虑一下,为了做出决策,对于 PPI 和经典方法分别需要多少个人工标注。

从 NPS 开始。假设我们想要模拟需要多少个人工标注的样本,才能拒绝 NPS 小于或等于 4 的零假设。我们可以运行以下代码来找到最小值:

def _to_invert_ppi(n):
    n = int(n)
    nulls_rejected = 0
    # Data setup
    for i in range(num_experiments):
        rand_idx = list_rand_idx[i]
        _Yhat = Yhat_total[rand_idx[:n]]
        _Y = Y_total[rand_idx[:n]]
        _Yhat_unlabeled = Yhat_total[rand_idx[n:]]

        ppi_ci = ppi_mean_ci(_Y, _Yhat, _Yhat_unlabeled, alpha=alpha)
        if target_response == 'NPS' and  ppi_ci[0] > null_hypothesis:
            nulls_rejected += 1
        elif target_response == 'recommended' and  ppi_ci[0] > null_hypothesis:
            nulls_rejected += 1

    return nulls_rejected / num_experiments - statistical_power

n_ppi = int(brentq(_to_invert_ppi, 100, 1000, xtol=1))

这模拟了 PPI 拒绝零假设所需的最小示例数量。传统示例的代码也类似。请参见notebook了解完整细节。

让我们来看一下这里的输出:

The PPI test requires n=334 labeled data points to reject the null.
The classical test requires n=987 labeled data points to reject the null.

解释是,PPI 相比仅使用人工标注的例子,需要少 653 个人工标注的观察值就能拒绝零假设。

我们可以对推荐进行重复此过程。我们唯一的变化是零假设的值。我们测试零假设,假设真正推荐该业务的客户百分比小于或等于 0.3。

The PPI test requires n=461 labeled data points to reject the null.
The classical test requires n=1000 labeled data points to reject the null.

在这里我们可以看到,使用传统方法比使用人工方法需要更多的观察值来得出结论,总共多出 539 个。

这些结果有意义吗?

653 个或 539 个观察值可能看起来不多,但在内部数据标注的领域中,它是一个相当大的数量。假设是周五下午,你的老板要求你从刚收到的一组调查问卷中确定 NPS 评分。为了做出这个判断,你需要手动标注一些观察值。

假设你每分钟可以标注 4 条评论。这意味着你每小时可以标注 240 条评论。如果你使用 PPI,你将比使用传统置信区间早 2-3 小时离开。减少琐碎的任务对员工幸福感有很大好处,因此这种方法值得投资,因为它的额外开销很小。

结论

这只是使用 PPI 解决基本统计推理问题的简要概述。我们看到如何从样本数据集中计算总体均值,适用于两种不同类型的变量。这种方法在付出极少额外工作后,可以带来显著的时间节省。

想要查看更多如何使用 PPI 的示例,请查看来自仓库的examples文件夹。它们涵盖了许多更有趣的用例。祝编程愉快!

感谢阅读这篇文章!如果你有任何额外问题或有些地方不清楚,请留言,我会回复你。如果你想看到更多类似的文章,请在 Medium LinkedIn 上关注我。

如果你在这篇文章中发现技术错误,请尽快告诉我!我力求确保我发布的信息尽可能准确,但没有人是完美的。

参考文献:

[1] Anastasios N. Angelopoulos, Stephen Bates, Clara Fannjiang, Michael I. Jordan, & Tijana Zrnic. (2023). 基于预测的推理。

通过注释提高 Python 代码的可读性

原文:towardsdatascience.com/enhancing-readability-of-python-code-via-annotations-09ce4c9b3729?source=collection_archive---------5-----------------------#2024-04-12

PYTHON 编程

注释是一个强大的开发工具。阅读本文以了解如何以及在哪里使用它们。

https://medium.com/@nyggus?source=post_page---byline--09ce4c9b3729--------------------------------https://towardsdatascience.com/?source=post_page---byline--09ce4c9b3729-------------------------------- Marcin Kozak

·发表于Towards Data Science ·阅读时间:21 分钟·2024 年 4 月 12 日

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/5161c0a1d90b47f723881d767e785f08.png

代码注释就像用标记笔做高亮。照片由Mitchell Luo提供,来源于Unsplash

代码清晰既是一种美德,也是一种必需。如果你写出清晰易读的代码,其他开发者能够理解它,用户能够明白如何使用它,甚至未来的你也会感谢它,因为随着时间的推移,我们大多数人都会忘记我们实现的代码的细节。随着项目和代码库规模的不断扩大,代码清晰性变得越来越重要。

在编程语言中,Python 提供了非常易读的代码。或者更确切地说,Python可以提供非常易读的代码——但你需要知道如何使其易读。我甚至可以说,Python 之所以如此流行,有几个原因,其中一个重要原因就是代码的可读性。因此,写出好的 Python 代码是我们的责任。为了做到这一点,我们需要工具。

代码清晰既是一种美德,也是一种必需。

有许多工具可以提高 Python 代码质量。首先,我们需要编写符合良好 Python 代码标准的代码,这些标准由 PEP 8 提供:

集成学习在异常检测中的应用

原文:towardsdatascience.com/ensemble-learning-for-anomaly-detection-955efb1b2fac?source=collection_archive---------5-----------------------#2024-10-30

深入探讨隔离森林模型,用于检测时间序列数据中的异常值

https://medium.com/@adavis08?source=post_page---byline--955efb1b2fac--------------------------------https://towardsdatascience.com/?source=post_page---byline--955efb1b2fac-------------------------------- Alex Davis

·发表于 Towards Data Science ·阅读时间:7 分钟·2024 年 10 月 30 日

异常检测是任何组织都必备的能力。通过检测异常值和离群点,我们不仅能识别出看似可疑(或可能错误)的数据,还能建立“正常”数据的标准。异常检测对于强大的数据治理系统至关重要,它能够识别数据错误。而在分析中,离群点在某些情况下,如欺诈检测和预测性维护,可能成为重点。

然而,随着数据量的增加,异常检测变得越来越困难。高维数据通常带有噪声,使得其难以用于分析和提取洞见。大规模数据集也可能包含错误和/或特殊情况。幸运的是,集成学习通过提高速度和效率,帮助我们处理高维数据并检测异常值。

什么是集成学习?

集成学习是一种机器学习技术,它通过结合多个独立模型的预测,来获得比任何单一模型更好的预测性能。每个模型被视为一个“弱学习器”,并在数据的一个小子集上进行训练以进行预测。然后它进行投票。每个弱学习器都会被调查,最终通过多数票决定预测结果。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/68ea0c492fe208b9a5695a9c1515eda7.png

图片来源:Wikimedia Commons(commons.wikimedia.org/wiki/File:Random_forest_explain.png

集成模型(在高质量数据上训练)具有鲁棒性、准确性、高效性,且能够有效避免过拟合。它们有许多应用场景,如分类、优化,以及在我们这个案例中的异常检测。

隔离森林模型

孤立森林模型是一种由多棵树组成的集成模型,能够隔离那些稀疏的观测值。它与流行的“随机森林”模型非常相似,但不同的是,孤立森林生成的是“孤立树”森林,而不是决策树森林。

那么它是如何工作的呢?让我们来看一棵孤立树。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/673e94aca10cdd4218bd7d158965e237.png

图片由作者提供

看一下上面的数据。我们可以看到一个数据点离其他数据较远(我们怀疑是异常值)。每棵孤立树会随机选择一个“分割值”来开始隔离观测值。 在这个例子中,怀疑的异常值会立即被隔离。由于它与其他数据点的距离较远,这种情况在大多数孤立树中都会发生。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/6aca0034350e7247a47cbe55fb66a21e.png

图片由作者提供

接下来,它选择另一个分割。这次,怀疑是“正常”数据的部分开始被切分。这个过程会重复,直到每个观测值被隔离。 最终,模型通过随机选择一个特征,并在该特征的最大值和最小值之间随机选择一个分割值来“隔离”观测值。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/0ffa29702aebb86c93866eec8b90d6e7.png

图片由作者提供

现在每个观测值都已经被隔离,我们需要问:隔离每个观测值需要多少次分割? 换句话说,每个数据点的分割路径有多长?假设结果如下:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/e6f94e6695942edff26916a20c5fd124.png

图片由作者提供

现在我们知道了隔离每个观测值需要多少次分割,我们计算出分割的平均次数。在我们的例子中,平均而言,隔离一个观测值需要 2.6 次分割。那些分割路径明显较短,或者隔离所需的分割次数明显较少的观测值,很有可能是异常值或离群值。 它们与平均分割次数的差异程度是模型中的一个参数。最后,孤立树判断观测值 G 是异常值。

孤立森林模型的最后一步是让每棵孤立树对哪些观测值是异常值进行“投票”。 如果大多数孤立树认为观测值 G 是异常值,那么模型就会判断它为异常值。

时间序列数据中的异常检测

让我们看一个简单的例子,使用孤立森林模型来检测时间序列数据中的异常值。下面,我们导入了一个包含订单日期、产品信息、客户地理信息和销售额的销售数据集。为了简化这个例子,我们只看一个特征(销售额)随时间的变化。

查看数据: https://www.kaggle.com/datasets/rohitsahoo/sales-forecasting (GPL 2.0)

#packages for data manipulation
import pandas as pd
from datetime import datetime

#packages for modeling
from sklearn.ensemble import IsolationForest

#packages for data visualization
import matplotlib.pyplot as plt
#import sales data
sales = pd.read_excel("Data/Sales Data.xlsx")

#subset to date and sales
revenue = sales[['Order Date', 'Sales']]
revenue.head()

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/78621fd24438dc0ad35003bbcd78ff71.png

图片由作者提供

如上所示,我们有每个特定日期所有订单的总销售额。由于我们有足够的数据(四年数据),让我们尝试检测出总销售额显著高于或低于预期总销售额的月份。

首先,我们需要进行一些预处理,并汇总每个月的销售额。然后,进行每月销售额的可视化。

#format the order date to datetime month and year
revenue['Order Date'] = pd.to_datetime(revenue['Order Date'],format='%Y-%m').dt.to_period('M')

#sum sales by month and year
revenue = revenue.groupby(revenue['Order Date']).sum()

#set date as index
revenue.index = revenue.index.strftime('%m-%Y')
#set the fig size
plt.figure(figsize=(8, 5))

#create the line chart
plt.plot(revenue['Order Date'],
         revenue['Sales'])

#add labels and a title
plt.xlabel('Moth')
plt.ylabel('Total Sales')
plt.title('Monthly Sales')

#rotate x-axis labels by 45 degrees for better visibility
plt.xticks(rotation = 90)

#display the chart
plt.show()

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/af77b3a53519ce3fe0cc456da4e61675.png

图片来自作者

使用上面的折线图,我们可以看到,虽然销售额月度波动,但总销售额随时间趋势向上。理想情况下,我们的模型将识别出总销售额波动超过预期并且对整体趋势有较大影响的月份。

现在我们需要初始化并拟合我们的模型。下面的模型使用了默认参数。我已经突出显示了这些参数,因为它们对模型的表现至关重要。

  • n_estimators:集成中基础估计器的数量。

  • max_samples:从 X 中选择用于训练每个基础估计器的样本数量(如果为“auto”,则max_samples = min(256, n_samples))。

  • contamination:数据集的污染程度,即数据集中异常值的比例。用于拟合时定义样本得分的阈值。

  • max_features:从 X 中选择用于训练每个基础估计器的特征数量。

#set isolation forest model and fit to the sales
model = IsolationForest(n_estimators = 100, max_samples = 'auto', contamination = float(0.1), max_features = 1.0)
model.fit(revenue[['Sales']])

接下来,让我们使用模型来显示异常及其异常得分。异常得分是基础估计器中每个观察值的常态性均值衡量。得分越低,观察值越异常。负分表示异常值,正分表示正常值。

#add anomaly scores and prediction
revenue['scores'] = model.decision_function(revenue[['Sales']])
revenue['anomaly'] = model.predict(revenue[['Sales']])

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/ca56501e9d6e920ba5ccf90664793ca8.png

图片来自作者

最后,让我们展示之前的相同折线图,但用 plt.scatter 突出显示异常值。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/4f5c5e6e39a801c2abed486f849e43b1.png

图片来自作者

模型似乎表现不错。由于数据在月度间波动较大,一个担忧是可能会将正常值标记为异常,但由于模型的自助采样,这种情况并未发生。异常值似乎是销售额偏离趋势较“显著”的较大波动。

然而,了解数据在这里非常重要,因为一些异常应该附带警告。我们来看第一个(2015 年 2 月)和最后一个(2018 年 11 月)检测到的异常。首先,我们可以看到它们都与平均值存在较大的波动。

然而,第一个异常(2015 年 2 月)只是我们记录销售的第二个月,且业务可能刚刚开始运营。销售额肯定很低,接下来一个月出现了大幅上涨。但仅仅因为销售额低就将业务的第二个月标记为异常,是否合理?还是这对一个新企业来说是正常现象?

对于我们最后一个异常(2018 年 11 月),我们看到销售量出现了巨大的波动,似乎偏离了整体趋势。然而,我们已经没有更多的数据。随着数据的持续记录,这可能并不是一个异常,而可能是一个识别出更陡峭上升趋势的信号。

结论

总之,异常检测是强有力的数据治理和严格分析中不可或缺的能力。尽管在大规模数据中检测离群值和异常值可能很困难,但集成学习方法能够提供帮助,因为它们在处理大规模表格数据时既稳健又高效。

隔离森林模型通过使用一组“弱学习者”来隔离稀少且分散的观测值,从而检测这些异常。

希望你喜欢我的文章!欢迎发表评论、提问或提出其他话题的建议。

实体解析知识图谱

原文:towardsdatascience.com/entity-resolved-knowledge-graphs-6b22c09a1442?source=collection_archive---------0-----------------------#2024-06-21

新词。旧概念。最终,这一切都是关于数据融合的。

https://medium.com/@mel.merigold?source=post_page---byline--6b22c09a1442--------------------------------https://towardsdatascience.com/?source=post_page---byline--6b22c09a1442-------------------------------- Mel Richey, PhD

·发表于Towards Data Science ·5 分钟阅读·2024 年 6 月 21 日

实体解析是一个过程。知识图谱是一个技术产物。二者结合产生了我们在知识表示和推理领域最强大的数据融合工具之一。最近,ERKG(实体解析知识图谱)已经进入数据架构的讨论,尤其是对于那些希望将给定领域的所有数据连接在一个地方进行调查的分析型组织。本文将详细解析实体解析知识图谱(ERKG)、ER、KG 以及它们实现的一些细节。

ER. 实体解析(也称为身份解析、数据匹配或记录链接)是通过计算过程将数据集中的实体去重和/或连接的过程。这可以像是解决数据库中两条记录,一条标为 Tom Riddle,另一条标为 T.M. Riddle 一样简单。或者,它也可以像一个人使用化名(伏地魔)、不同的电话号码和多个 IP 地址进行银行诈骗那样复杂。

KG. 知识图谱是一种知识表示形式,通过实体及其之间的关系以视觉形式展示数据。实体可以是人、公司、概念、物理资产、地理位置等。关系可以是信息交换、沟通、旅行、银行交易、计算交易等。实体和关系存储在图数据库中,预先连接,并以节点和边的形式可视化呈现。它看起来像这样……

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/2aeeee831be56ea6d4b22dbf60af874c.png

作者图片

因此……

ERKG:一个包含多个数据集的知识图谱,其中的实体相互连接并去重。换句话说,没有重复的实体(例如,Tom Riddle 和 T.M. Riddle 的节点已经合并为一个节点)。此外,还发现了在某些可接受的概率阈值内,潜在相关节点之间的连接(例如,Tom Riddle、Lord Voldemort 和 Marvolo Riddle。此时你可能会问,“为什么你会创建一个来自多个数据源的、没有实体解析的知识图谱?”简单的答案是,“你不会。”话虽如此,如何解析实体的方法以及图谱表示技术使得创建 ERKG 成为一项艰巨的任务。

这是我们制作的第一个 ERKG。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/4b6fd116ca82648d68757927668bedc3.png

图片由作者提供

早在 2016 年,我们将两个数据集导入图数据库:1)美国财政部外国资产控制办公室(OFAC)的国际制裁名单上的个人(蓝色),以及 2)一家匿名公司的客户(粉色)。显然,该公司的目的是通过图谱发现其客户中是否有国际制裁的个人,而无需手动搜索 OFAC 的数据库。尽管这个图谱所代表的 ER 过程可能有些过于复杂,但它的确具有说明性。

图谱中大多数已解析的实体是在同一个数据集内的两个到三个个体之间的关系(蓝到蓝或粉到粉)。这些很可能代表重复记录(例如我们之前提到的 Tom Riddle 与 T.M. Riddle 的问题)。在某些情况下,去重非常严格,比如图像顶部的粉色簇。在这里我们看到一个人被客户数据集中的 5 到 10 条记录表示。因此,至少可以看出,公司的客户数据需要进行去重处理。

有趣的是,在图像顶部我们看到的蓝到粉色的关系。这正是公司所寻找的:跨数据集的实体解析。它的几个客户可能是被国际制裁的个人。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/fc4271ee395d9bb8757ab8722c4cc89e.png

图片由作者提供

这个例子相对简单,可能会导致人们错误地认为构建 ERKG 是一项简单的工作。事实远非如此。特别是当它需要跨多个 TB 数据和多个分析用户进行扩展时。

轻量级的自然语言处理(NLP)算法(如模糊匹配技术)足够简单,容易实现。这些算法可以轻松处理 Tom Riddle 与 T.M. Riddle 的问题。但当需要将两个以上的数据集结合在一起,可能还涉及多种语言和国际字符时,简单的 NLP 处理就变得相当复杂了。

对于更高级的分析问题,如反洗钱或银行欺诈,也需要更先进的 ER 解决方案。模糊匹配不足以识别那些故意隐藏身份、使用多个化名并试图规避制裁或其他法规的犯罪者。为此,ER 过程应包括基于机器学习的方法和更复杂的技术,考虑到姓名之外的附加元数据。这并不全是自然语言处理(NLP)。

关于基于图谱的 ER 与数据集级别的 ER 之间也存在很多争论。对于最高保真度的图谱分析,两者都是必需的。在将数据集导入图谱数据库时,数据集内和跨越数据集解析实体,1) 可以最小化对图谱的大规模操作,从而降低计算开销,2) 确保图谱在创建之初只包含已解析的实体(无重复),这也为整体图谱架构节省了大量成本。

一旦存在实体解析的知识图谱,数据科学团队可以进一步通过基于图谱的 ER 技术探索更多的 ER。这些技术的附加好处是利用图谱拓扑结构(即图谱本身固有的结构)作为预测跨多个数据集潜在连接的特征。

ERKG 可以成为一个强大且直观的分析工具。它提供了:

  • 将多个数据集融合成主图谱数据库

  • 针对分析师探索的特定领域知识图谱的可视化表示

  • 能够指定一个实时图谱模式,表示数据是如何连接和展示给分析师的

  • 数据去重和数据集内外显式连接的可视化表示

  • 跨数据集内外的潜在连接(预测链接),并能够控制预测的概率阈值

ERKG 因此成为一个分析画布,通过多个数据集呈现给定领域的生动互联探索。这是一种数据融合解决方案,而且是一个高度符合人类直觉的方案。

人工智能繁荣的环境影响

原文:towardsdatascience.com/environmental-implications-of-the-ai-boom-279300a24184?source=collection_archive---------2-----------------------#2024-05-02

数字世界不能存在,没有自然资源来支撑它。我们用来构建和运行人工智能的技术代价是什么?

https://medium.com/@s.kirmer?source=post_page---byline--279300a24184--------------------------------https://towardsdatascience.com/?source=post_page---byline--279300a24184-------------------------------- Stephanie Kirmer

·发表于 Towards Data Science ·8 分钟阅读·2024 年 5 月 2 日

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/2e2731d1782760746f27aa4f4842fde4.png

图片来源:ANGELA BENITOUnsplash

在机器学习中有一个核心概念,我经常告诉外行人,以帮助澄清我所做工作的哲学。这个概念是:每个机器学习模型所处的世界在变化,往往是因为模型的存在,所以模型试图模仿和预测的世界总是过去的,而非现在或未来。从某种意义上说,模型是在预测未来——我们通常是这样看待它的——但在许多其他方面,模型实际上是在尝试将我们带回过去。

我喜欢谈论这个话题,因为机器学习的哲学帮助我们从机器学习实践者以及机器学习的用户和研究对象的角度,获得真实的视角。常读者会知道我常说“机器学习就是我们”——意思是,我们产生数据,进行训练,消费和应用模型的输出。模型试图遵循我们的指示,使用我们提供的原材料,而我们对这一过程的发生方式及其后果有着巨大的、几乎完全的控制权。

这个概念的另一个有用方面是提醒我们,模型并不是孤立存在于数字世界中,实际上它们与模拟的、物理的世界密切交织。毕竟,如果你的模型没有对我们周围的世界产生影响,那么就会引发一个问题:你的模型为什么存在?如果我们真的深入思考,数字世界和物理世界的区别仅仅在于我们作为用户/开发者如何与它互动,从某种有限的、人工的角度来看,数字世界与物理世界并不是完全分开的。

我今天想谈论的正是最后一点——物理世界如何塑造并影响机器学习,而机器学习/AI 又如何反过来影响物理世界?在我上一篇文章中,我承诺过会谈到物理世界资源的限制如何与机器学习和 AI 相交织,今天我们就要讨论这个问题。

AI 需要物理世界

如果稍加思考,这一点大概很明显。有一个笑话说,我们可以通过关掉智能机器人或拔掉电脑的电源来击败这些有知觉的机器人霸主。但笑话归笑话,这里面确实有一定的真理。我们这些从事机器学习、AI 以及一般计算工作的人员,完全依赖于自然资源的存在,比如矿产金属、电力等。这与我去年写的一篇关于人类劳动对于机器学习存在的重要性的文章有一些相似之处,但今天我们要走一条不同的道路,讨论两个我们应该更加重视的关键领域,这些领域对我们的工作至关重要——采矿/制造和能源,主要是电力形式的能源。

如果你出去寻找,关于这两个领域的研究和报道非常丰富,不仅与 AI 直接相关,还涉及到早期的技术热潮,比如加密货币,在资源使用方面与 AI 有许多相似之处。我将对每个领域进行一般性的讨论,并提供进一步阅读的参考文献,以便你可以深入探索细节,了解学术来源。然而,很难找到考虑到过去 18 个月 AI 热潮的研究,所以我预计一些研究低估了新技术在生成型 AI 领域的影响。

采矿与制造

制造 GPU 芯片需要什么材料?我们知道这些芯片在现代机器学习模型的发展中至关重要,而 Nvidia,今天这些芯片的最大生产商,凭借加密货币的繁荣和人工智能的热潮,已经跻身全球最有价值的公司之一。其股价从 2021 年初的每股 130 美元涨到 2024 年 4 月我写这句话时的每股 877.35 美元,使其市值达到了超过 2 万亿美元。 2023 年第三季度,他们售出了超过 50 万颗芯片,收入超过 100 亿美元估计他们 2023 年 H100 系列芯片的总销量为 150 万颗,,预计 2024 年将轻松超过这一数字。

GPU 芯片涉及多种不同的特殊原材料,这些原材料相对稀有且难以获得,包括钨、钯、钴和钽。 其他元素可能更容易获得,但具有显著的健康和安全风险,例如汞和铅。开采这些元素和化合物对环境有重大影响,包括排放和对采矿地区环境的破坏。即使是最好的采矿作业,也会严重改变生态系统。这还不包括所谓的“冲突矿产”的风险,即在人类剥削、童工或奴役的情况下开采的矿物。(应给予肯定:Nvidia 一直在公开避免使用这种矿产,特别提到刚果民主共和国。)

此外,在原材料被开采后,所有这些材料都必须经过极为仔细的加工,才能生产出运行复杂计算的微小、高效能芯片。正如我们从过去 150 多年的工业历史中所知,工人在处理重金属时面临着重大健康风险,例如铅和汞。Nvidia 的芯片大多是在台湾的工厂生产,这些工厂由一家名为台积电(TSMC)的公司运营。因为Nvidia 并不拥有或运营这些工厂,所以 Nvidia 能够避免因制造条件或排放而受到批评,而且数据也很难获取。制造所需的电力也不在 Nvidia 的账目上。顺便提一句:台积电的产能已达到最大,正在努力提升产能。与此同时,Nvidia 计划在明年开始与 Intel 合作,提升制造能力。

一旦芯片生产完成,它的使用寿命可能会相当长——如果保养得当,可能是 3 到 5 年——然而,Nvidia 不断生产新的、更强大、更高效的芯片(每年生产 200 万颗可不是个小数字!),因此芯片的使用寿命可能受到过时以及磨损的限制。当芯片不再有用时,它就会进入所谓的“电子废物”的处理流程。从理论上讲,芯片中许多稀有金属应当具有一定的回收价值,但正如你所料,芯片回收是一项非常专业且具有挑战性的技术任务,而且所有电子废物中,只有大约 20%能够被回收,其中包括手机等其他较为简单的硬件。回收过程还需要工人拆卸设备,这样他们就会再次接触到制造过程中使用的重金属和其他元素。

如果芯片没有被回收,另一方面,它很可能会被倾倒在垃圾填埋场或焚烧,重金属通过水、空气或两者渗入环境。这种情况发生在发展中国家,且常常直接影响到人们居住的地区。

然而,大多数关于机器学习碳足迹及其整体环境影响的研究,都集中在电力消耗方面。所以我们来看看这个方向。

电力

一旦我们具备了完成工作的硬件,AI 领域中最为显著的问题无疑是电力消耗。训练大型语言模型需要消耗巨量的电力,而部署和服务 LLM(大型语言模型)及其他先进的机器学习模型同样是一个电力黑洞。

在训练的情况下,一篇研究论文建议,训练拥有 1750 亿参数的 GPT-3 大约需要消耗1,300 兆瓦时(MWh)或 1,300,000 千瓦时(KWh)的电力。与此相比,GPT-4 使用了 1.76 万亿个参数,估计训练所需的电力消耗在51,772,500 到 62,318,750 千瓦时之间。作为参考,平均每个美国家庭每年使用的电力超过 10,000 千瓦时。因此,从保守估计来看,训练一次 GPT-4 的电力消耗足以为近 5,000 个美国家庭提供一年的电力。(这还没有考虑到在准备数据并为训练做准备时,几乎肯定需要进行的初步分析或测试所消耗的电力。)

鉴于 GPT-3 和 GPT-4 在训练过程中的电力使用量大约增加了 40 倍,我们必须关注这些模型的未来版本可能带来的电力消耗,以及用于训练生成视频、图像或音频内容的模型的电力消耗。

在训练过程之后,只有在模型的生命周期中进行一次的训练之外,推理任务的电力消耗也在快速增长,换句话说,每当你向 Chat-GPT 提问或尝试用 AI 工具生成有趣图像时,都会产生电力成本。这种电力由数据中心吸收,这些数据中心运行模型,提供全球范围的结果。国际能源署预测到 2026 年,仅数据中心的电力消耗就将达到 1,000 太瓦时,大致相当于日本的电力使用量。

AI 行业的主要参与者显然已经意识到,电力消耗的这种增长是不可持续的。有估计表明,数据中心消耗了全球电力使用量的 0.5%到 2%,并且到 2030 年,数据中心可能占美国电力使用量的25%

美国的电力基础设施状况不佳——我们当然在尝试将更多可再生能源并入电网,但我们理应不是一个能够很好管理公共基础设施的国家。德克萨斯州居民尤其深知我们电力系统的脆弱性,但在美国范围内,气候变化带来的极端天气状况增加导致了电力中断,这种情况的发生频率正在不断上升。

电力基础设施的投资是否能够满足 AI 工具所带来的急剧增长的需求,尚需观察,而由于政府行动在实现这一目标中至关重要,因此持悲观态度是合理的。

与此同时,即使我们确实能以必要的速度生产电力,直到可再生和无排放的电力来源能够规模化使用之前,我们仍在通过使用这些 AI 工具显著增加全球的碳排放。粗略估计,每千瓦时电力排放 0.86 磅碳,训练 GPT-4 的过程中会向大气中排放超过 20,000 公吨的碳。(相比之下,普通美国人每年排放 13 公吨碳。)

好吧,那又怎么样呢?

正如你所预料的,我并不是在这里争论我们应该因为机器学习消耗自然资源而停止这项工作。我认为那些使我们生活成为可能的工人应当得到显著的工作安全保障和与风险相称的补偿,我也认为在面对可预防的、由人类引起的气候变化时,可再生电力来源应该是一个重要优先事项。

但我谈论这些,是因为了解我们的工作有多么依赖于物理世界、自然资源和地球,应该让我们更加谦逊,感恩我们所拥有的一切。当你进行训练或推理,或使用 Chat-GPT 或 Dall-E 时,你并不是这个过程的终点。你的行为会产生下游的后果,认识到这一点并据此做出明智的决策非常重要。你可能在租用别人 GPU 的几秒钟或几小时,但这仍然需要电力,并且会对 GPU 造成磨损,最终这些 GPU 会需要被处置。成为有伦理的世界公民的一部分,就是思考你的选择并考虑你对他人的影响。

此外,如果你有兴趣了解自己建模工作对碳足迹的影响,可以使用一个工具:www.green-algorithms.org/

阅读更多我的作品,请访问www.stephaniekirmer.com.

AlphaFold 3 与 GPT-4o 对蛋白质数据库条目知识的史诗级“交叉”

原文:towardsdatascience.com/epic-crossover-between-alphafold-3-and-gpt-4os-knowledge-of-protein-data-bank-entries-ec7b6dd589e0?source=collection_archive---------12-----------------------#2024-12-17

探索 GPT-4o 对蛋白质数据银行的知识如何与像 AlphaFold 3 这样的系统结合,为研究和探索生物分子结构提供新的方式。

https://lucianosphere.medium.com/?source=post_page---byline--ec7b6dd589e0--------------------------------https://towardsdatascience.com/?source=post_page---byline--ec7b6dd589e0-------------------------------- LucianoSphere(Luciano Abriata 博士)

·发表于Towards Data Science ·12 分钟阅读·2024 年 12 月 17 日

如果你对生物信息学和生物学数据分析感兴趣,你会立即发现这篇文章非常具有启发性。

更广泛地说,对于人工智能科学家来说,他们将在这里找到通过推动大型语言模型(LLM)产生幻觉并寻找克服这一局限性的方法。

引言

蛋白质数据银行(PDB)作为生物大分子三维结构数据的综合库,提供了对生物过程分子基础的宝贵洞察。正是它的存在使得像 AlphaFold 这样的人工智能模型得以开发!

## 这里是我所有关于蛋白质建模、CASP 和 AlphaFold 2 的同行评审和博客文章

我在这里汇编了所有经过同行评审的文章(包括一些论文、几篇评论、一篇观点)以及关于…

lucianosphere.medium.com

用于估计潜伏期的 EpiLPS

原文:towardsdatascience.com/epilps-for-estimation-of-incubation-times-461aab54ff4b?source=collection_archive---------9-----------------------#2024-08-01

探索如何使用{EpiLPS} R 软件包来估计各种疾病的潜伏期。了解其在流行病学研究中的应用、方法论和好处,以便更准确地预测和改进公共卫生规划。

https://antoinesoetewey.medium.com/?source=post_page---byline--461aab54ff4b--------------------------------https://towardsdatascience.com/?source=post_page---byline--461aab54ff4b-------------------------------- 安托万·索特韦

·发表在走向数据科学 ·6 分钟阅读·2024 年 8 月 1 日

动机

哈塞尔特大学数据科学研究所(DSI)的一组研究人员开发了一种新的统计模型,基于粗略数据估计病原微生物的潜伏期。传染病的潜伏期(定义为感染和首次出现症状之间的时间间隔)非常重要,因为它可以揭示疾病的流行潜力,并优化隔离期的长度以阻止传播。最近在美国流行病学杂志上发表了文章(Gressani 等人,2024),该文章通过EpiLPS 软件包 (Gressani 等人,2022) 实现了该方法的实际应用。

粗略数据

首先,估计潜伏期时间如此具有挑战性的原因是什么?魔鬼在于数据。真实的感染时间是隐秘的,很少被观察到。在信息论术语中,这种现象被称为“信息不完全”,但统计学家更倾向于称之为截尾。更准确地说,感染时间是…

使用 GAN(生成对抗网络)去除卫星图像中的云

原文:towardsdatascience.com/erasing-clouds-from-satellite-imagery-using-gans-generative-adversarial-networks-2d7f8467ef2e?source=collection_archive---------2-----------------------#2024-06-15

从零开始在 Python 中构建 GAN

https://medium.com/@alexroz?source=post_page---byline--2d7f8467ef2e--------------------------------https://towardsdatascience.com/?source=post_page---byline--2d7f8467ef2e-------------------------------- Aleksei Rozanov

·发布于 Towards Data Science ·12 分钟阅读·2024 年 6 月 15 日

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/a8ae5b6d4e446b5af4e38c5c19ed1129.png

图片由 Michael & Diane Weidner 提供,来源于 Unsplash

生成对抗网络(GAN)的概念由 Goodfellow 和他的同事们在 2014 年提出 [1],并且很快在计算机视觉和图像生成领域获得了极大关注。尽管在过去的 10 年里,人工智能领域经历了快速发展并出现了许多新算法,但这个概念的简单性和 brilliance(巧妙之处)依然令人印象深刻。所以今天我想通过尝试从卫星 RGB(红、绿、蓝)图像中去除云层,来展示这些网络的强大能力。

准备一个适当平衡、足够大且经过正确预处理的计算机视觉数据集需要大量时间,因此我决定探索 Kaggle 提供的资源。我发现最适合这个任务的数据集是 EuroSat [2],它具有开放许可。该数据集包含 27000 张标注的 RGB 图像,尺寸为 64x64 像素,来自 Sentinel-2,并用于解决多类分类问题。

[## EuroSat 数据集

数据集包含来自 Sentinel-2 的所有 RGB 和波段图像

www.kaggle.com https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/78b9510aa3c9c7e3a95911929f732e88.png

EuroSat 数据集的图像示例。许可

我们对分类本身并不感兴趣,但 EuroSat 数据集的一个主要特点是,所有图像都有清晰的天空。这正是我们需要的。借用[3]中的方法,我们将这些 Sentinel-2 影像作为目标,通过向它们添加噪声(云朵)来创建输入。

那么在真正讨论 GANs 之前,我们先准备一下数据。首先,我们需要下载数据,并将所有类别合并到一个目录中。

🐍完整的 Python 代码: GitHub.

import numpy as np
import pandas as pd
import random

from os import listdir, mkdir, rename
from os.path import join, exists
import shutil
import datetime

import matplotlib.pyplot as plt
from highlight_text import ax_text, fig_text
from PIL import Image

import warnings

warnings.filterwarnings('ignore')
classes = listdir('./EuroSat')
path_target = './EuroSat/all_targets'
path_input = './EuroSat/all_inputs'

"""RUN IT ONLY ONCE TO RENAME THE FILES IN THE UNPACKED ARCHIVE"""
mkdir(path_input)
mkdir(path_target)
k = 1
for kind in classes:
  path = join('./EuroSat', str(kind))
  for i, f in enumerate(listdir(path)):
    shutil.copyfile(join(path, f),
                  join(path_target, f))
    rename(join(path_target, f), join(path_target, f'{k}.jpg'))
    k += 1

第二个重要步骤是生成噪声。虽然你可以使用不同的方法,例如随机遮罩一些像素、添加一些高斯噪声,但在这篇文章中,我想尝试一个对我来说新颖的东西——Perlin 噪声。它是由 Ken Perlin 在 80 年代发明的[4],用于开发电影中的烟雾效果。这种噪声与普通的随机噪声相比,具有更自然的外观。让我来证明一下。

def generate_perlin_noise(width, height, scale, octaves, persistence, lacunarity):
    noise = np.zeros((height, width))
    for i in range(height):
        for j in range(width):
            noise[i][j] = pnoise2(i / scale,
                                  j / scale,
                                  octaves=octaves,
                                  persistence=persistence,
                                  lacunarity=lacunarity,
                                  repeatx=width,
                                  repeaty=height,
                                  base=0)
    return noise

def normalize_noise(noise):
    min_val = noise.min()
    max_val = noise.max()
    return (noise - min_val) / (max_val - min_val)

def generate_clouds(width, height, base_scale, octaves, persistence, lacunarity):
    clouds = np.zeros((height, width))
    for octave in range(1, octaves + 1):
        scale = base_scale / octave
        layer = generate_perlin_noise(width, height, scale, 1, persistence, lacunarity)
        clouds += layer * (persistence ** octave)

    clouds = normalize_noise(clouds)
    return clouds

def overlay_clouds(image, clouds, alpha=0.5):

    clouds_rgb = np.stack([clouds] * 3, axis=-1)

    image = image.astype(float) / 255.0
    clouds_rgb = clouds_rgb.astype(float)

    blended = image * (1 - alpha) + clouds_rgb * alpha

    blended = (blended * 255).astype(np.uint8)
    return blended
width, height = 64, 64
octaves = 12 #number of noise layers combined
persistence = 0.5 #lower persistence reduces the amplitude of higher-frequency octaves
lacunarity = 2 #higher lacunarity increases the frequency of higher-frequency octaves
for i in range(len(listdir(path_target))):
  base_scale = random.uniform(5,120) #noise frequency
  alpha = random.uniform(0,1) #transparency

  clouds = generate_clouds(width, height, base_scale, octaves, persistence, lacunarity)

  img = np.asarray(Image.open(join(path_target, f'{i+1}.jpg')))
  image = Image.fromarray(overlay_clouds(img,clouds, alpha))
  image.save(join(path_input,f'{i+1}.jpg'))
  print(f'Processed {i+1}/{len(listdir(path_target))}')
idx = np.random.randint(27000)
fig,ax = plt.subplots(1,2)
ax[0].imshow(np.asarray(Image.open(join(path_target, f'{idx}.jpg'))))
ax[1].imshow(np.asarray(Image.open(join(path_input, f'{idx}.jpg'))))
ax[0].set_title("Target")
ax[0].axis('off')
ax[1].set_title("Input")
ax[1].axis('off')
plt.show()

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/663c3610117fdf1d41c60edb0e5a06e6.png

图片来源:作者

如上所示,图像中的云朵非常逼真,它们具有不同的“密度”和类似真实云朵的纹理。

如果你像我一样对 Perlin 噪声感兴趣,这里有一个非常酷的视频,展示了这种噪声如何应用于游戏开发行业:

既然我们现在有了一个现成可用的数据集,那么让我们来谈谈生成对抗网络(GANs)。

生成对抗网络(GAN)

为了更好地说明这个概念,假设你正在东南亚旅行,突然需要一件连帽衫,因为外面太冷了。你来到最近的街头市场,发现一家小店有一些品牌服装。卖家拿来一件不错的连帽衫让你试穿,并说它是著名品牌 ExpensiveButNotWorthIt。你仔细一看,得出结论,这显然是假的。卖家说:“等一下,我有真的。”然后他带着另一件连帽衫回来,看起来更像是品牌的,但依旧是假货。经过几轮这样的尝试后,卖家带来了一件无法分辨的传奇品牌 ExpensiveButNotWorthIt 的复制品,你便高兴地买下了它。这基本上就是生成对抗网络(GANs)的工作原理!

在 GANs 的情况下,你被称为判别器(D)。判别器的目标是区分真实物体和虚假物体,或者解决二分类任务。卖家被称为生成器(G),因为他在尝试生成高质量的假货。判别器和生成器是独立训练的,目的是互相超越。因此,最终我们得到一个高质量的假货。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/dd222e2e675a330b6217b13ea4acc8d4.png

GANs 架构。许可

训练过程最初看起来是这样的:

  1. 采样输入噪声(在我们的案例中是有云的图像)。

  2. 将噪声输入 G 并收集预测结果。

  3. 通过获取两个预测值来计算 D 的损失,一个是 G 的输出,另一个是真实数据的预测值。

  4. 更新 D 的权重。

  5. 再次采样输入噪声。

  6. 将噪声输入 G 并收集预测结果。

  7. 通过将 G 的预测输入到 D 中来计算 G 的损失。

  8. 更新 G 的权重。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/788502677c2e805ddd67f00aa1010601.png

GANs 训练循环。来源:[1]。

换句话说,我们可以定义一个值函数 V(G,D):

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/ba0147e691508a1cee54eda81aec27ca.png

来源:[1]。

在这里,我们希望最小化项 log(1-D(G(z))) 来训练 G,并最大化 log D(x) 来训练 D(在此符号中,x 表示真实数据样本,z 表示噪声)。

现在让我们尝试在 pytorch 中实现它!

在原始论文中,作者提到使用多层感知器(MLP);它通常也简称为 ANN,但我想尝试一个更复杂的方法——我想使用 UNet [5] 架构作为生成器,ResNet [6] 作为判别器。这两者都是著名的 CNN 架构,所以我不会在这里解释它们(如果需要我写一篇单独的文章,请在评论中告诉我)。

让我们来构建它们。判别器:

import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
from torch.utils.data import Subset
class ResidualBlock(nn.Module):
    def __init__(self, in_channels, out_channels, stride = 1, downsample = None):
        super(ResidualBlock, self).__init__()
        self.conv1 = nn.Sequential(
                        nn.Conv2d(in_channels, out_channels, kernel_size = 3, stride = stride, padding = 1),
                        nn.BatchNorm2d(out_channels),
                        nn.ReLU())
        self.conv2 = nn.Sequential(
                        nn.Conv2d(out_channels, out_channels, kernel_size = 3, stride = 1, padding = 1),
                        nn.BatchNorm2d(out_channels))
        self.downsample = downsample
        self.relu = nn.ReLU()
        self.out_channels = out_channels

    def forward(self, x):
        residual = x
        out = self.conv1(x)
        out = self.conv2(out)
        if self.downsample:
            residual = self.downsample(x)
        out += residual
        out = self.relu(out)
        return out

class ResNet(nn.Module):
    def __init__(self, block=ResidualBlock, all_connections=[3,4,6,3]):
        super(ResNet, self).__init__()
        self.inputs = 16
        self.conv1 = nn.Sequential(
                        nn.Conv2d(3, 16, kernel_size = 3, stride = 1, padding = 1),
                        nn.BatchNorm2d(16),
                        nn.ReLU()) #16x64x64
        self.maxpool = nn.MaxPool2d(kernel_size = 2, stride = 2) #16x32x32

        self.layer0 = self.makeLayer(block, 16, all_connections[0], stride = 1) #connections = 3, shape: 16x32x32
        self.layer1 = self.makeLayer(block, 32, all_connections[1], stride = 2)#connections = 4, shape: 32x16x16
        self.layer2 = self.makeLayer(block, 128, all_connections[2], stride = 2)#connections = 6, shape: 1281x8x8
        self.layer3 = self.makeLayer(block, 256, all_connections[3], stride = 2)#connections = 3, shape: 256x4x4
        self.avgpool = nn.AvgPool2d(4, stride=1)
        self.fc = nn.Linear(256, 1)

    def makeLayer(self, block, outputs, connections, stride=1):
        downsample = None
        if stride != 1 or self.inputs != outputs:
            downsample = nn.Sequential(
                nn.Conv2d(self.inputs, outputs, kernel_size=1, stride=stride),
                nn.BatchNorm2d(outputs),
            )
        layers = []
        layers.append(block(self.inputs, outputs, stride, downsample))
        self.inputs = outputs
        for i in range(1, connections):
            layers.append(block(self.inputs, outputs))

        return nn.Sequential(*layers)

    def forward(self, x):
        x = self.conv1(x)
        x = self.maxpool(x)
        x = self.layer0(x)
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.avgpool(x)
        x = x.view(-1, 256)
        x = self.fc(x).flatten()
        return F.sigmoid(x)

生成器:

 class DoubleConv(nn.Module):
    def __init__(self, in_channels, out_channels):
        super(DoubleConv, self).__init__()
        self.double_conv = nn.Sequential(
            nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1),
            nn.BatchNorm2d(out_channels),
            nn.ReLU(inplace=True),
            nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1),
            nn.BatchNorm2d(out_channels),
            nn.ReLU(inplace=True)
        )

    def forward(self, x):
        return self.double_conv(x)

class UNet(nn.Module):
    def __init__(self):
      super().__init__()
      self.conv_1 = DoubleConv(3, 32) # 32x64x64
      self.pool_1 = nn.MaxPool2d(kernel_size=2, stride=2) # 32x32x32

      self.conv_2 = DoubleConv(32, 64)  #64x32x32
      self.pool_2 = nn.MaxPool2d(kernel_size=2, stride=2) #64x16x16

      self.conv_3 = DoubleConv(64, 128)  #128x16x16
      self.pool_3 = nn.MaxPool2d(kernel_size=2, stride=2) #128x8x8

      self.conv_4 = DoubleConv(128, 256)  #256x8x8
      self.pool_4 = nn.MaxPool2d(kernel_size=2, stride=2) #256x4x4

      self.conv_5 = DoubleConv(256, 512)  #512x2x2

      #DECODER
      self.upconv_1 = nn.ConvTranspose2d(512, 256, kernel_size=2, stride=2) #256x4x4
      self.conv_6 = DoubleConv(512, 256) #256x4x4

      self.upconv_2 = nn.ConvTranspose2d(256, 128, kernel_size=2, stride=2) #128x8x8
      self.conv_7 = DoubleConv(256, 128)  #128x8x8

      self.upconv_3 = nn.ConvTranspose2d(128, 64, kernel_size=2, stride=2) #64x16x16
      self.conv_8 = DoubleConv(128, 64)  #64x16x16

      self.upconv_4 = nn.ConvTranspose2d(64, 32, kernel_size=2, stride=2) #32x32x32
      self.conv_9 = DoubleConv(64, 32)  #32x32x32

      self.output = nn.Conv2d(32, 3, kernel_size = 3, stride = 1, padding = 1) #3x64x64

    def forward(self, batch):

      conv_1_out = self.conv_1(batch)
      conv_2_out = self.conv_2(self.pool_1(conv_1_out))
      conv_3_out = self.conv_3(self.pool_2(conv_2_out))
      conv_4_out = self.conv_4(self.pool_3(conv_3_out))
      conv_5_out = self.conv_5(self.pool_4(conv_4_out))

      conv_6_out = self.conv_6(torch.cat([self.upconv_1(conv_5_out), conv_4_out], dim=1))
      conv_7_out = self.conv_7(torch.cat([self.upconv_2(conv_6_out), conv_3_out], dim=1))
      conv_8_out = self.conv_8(torch.cat([self.upconv_3(conv_7_out), conv_2_out], dim=1))
      conv_9_out = self.conv_9(torch.cat([self.upconv_4(conv_8_out), conv_1_out], dim=1))

      output = self.output(conv_9_out)

      return F.sigmoid(output)

现在我们需要将数据划分为训练集和测试集,并将它们封装为 torch 数据集:

class dataset(Dataset):
  def __init__(self, batch_size, images_paths, targets, img_size = 64):
    self.batch_size = batch_size
    self.img_size = img_size
    self.images_paths = images_paths
    self.targets = targets
    self.len = len(self.images_paths) // batch_size

    self.transform = transforms.Compose([
                transforms.ToTensor(),
                ])

    self.batch_im = [self.images_paths[idx * self.batch_size:(idx + 1) * self.batch_size] for idx in range(self.len)]
    self.batch_t = [self.targets[idx * self.batch_size:(idx + 1) * self.batch_size] for idx in range(self.len)]

  def __getitem__(self, idx):
      pred = torch.stack([
              self.transform(Image.open(join(path_input,file_name)))
              for file_name in self.batch_im[idx]
          ])
      target = torch.stack([
              self.transform(Image.open(join(path_target,file_name)))
              for file_name in self.batch_im[idx]
          ])
      return pred, target

  def __len__(self):
      return self.len

完美。是时候编写训练循环了。在此之前,让我们定义我们的损失函数和优化器:

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

batch_size = 64
num_epochs = 15
learning_rate_D = 1e-5
learning_rate_G = 1e-4

discriminator = ResNet()
generator = UNet()

bce = nn.BCEWithLogitsLoss()
l1loss = nn.L1Loss()

optimizer_D = optim.Adam(discriminator.parameters(), lr=learning_rate_D)
optimizer_G = optim.Adam(generator.parameters(), lr=learning_rate_G)

scheduler_D = optim.lr_scheduler.StepLR(optimizer_D, step_size=10, gamma=0.1)
scheduler_G = optim.lr_scheduler.StepLR(optimizer_G, step_size=10, gamma=0.1)

如你所见,这些损失与 GAN 算法中的图片有所不同。特别是,我加入了 L1Loss。其想法是,我们不仅仅是从噪声中生成一张随机图片,我们还希望保留输入中的大部分信息,只去除噪声。所以 G 的损失将是:

G_loss = log(1 − D(G(z))) + 𝝀 |G(z)-y|

而不是仅仅

G_loss = log(1 − D(G(z)))

𝝀 是一个任意系数,用于平衡损失函数的两个部分。

最后,让我们划分数据并开始训练过程:

test_ratio, train_ratio = 0.3, 0.7
num_test = int(len(listdir(path_target))*test_ratio)
num_train = int((int(len(listdir(path_target)))-num_test))

img_size = (64, 64)

print("Number of train samples:", num_train)
print("Number of test samples:", num_test)

random.seed(231)
train_idxs = np.array(random.sample(range(num_test+num_train), num_train))
mask = np.ones(num_train+num_test, dtype=bool)
mask[train_idxs] = False

images = {}
features = random.sample(listdir(path_input),num_test+num_train)
targets = random.sample(listdir(path_target),num_test+num_train)

random.Random(231).shuffle(features)
random.Random(231).shuffle(targets)

train_input_img_paths = np.array(features)[train_idxs]
train_target_img_path = np.array(targets)[train_idxs]
test_input_img_paths = np.array(features)[mask]
test_target_img_path = np.array(targets)[mask]

train_loader = dataset(batch_size=batch_size, img_size=img_size, images_paths=train_input_img_paths, targets=train_target_img_path)
test_loader = dataset(batch_size=batch_size, img_size=img_size, images_paths=test_input_img_paths, targets=test_target_img_path)

现在我们可以运行我们的训练循环:

train_loss_G, train_loss_D, val_loss_G, val_loss_D = [], [], [], []
all_loss_G, all_loss_D = [], []
best_generator_epoch_val_loss, best_discriminator_epoch_val_loss = -np.inf, -np.inf
for epoch in range(num_epochs):

    discriminator.train()
    generator.train()

    discriminator_epoch_loss, generator_epoch_loss = 0, 0

    for inputs, targets in train_loader:
        inputs, true = inputs, targets

        '''1\. Training the Discriminator (ResNet)'''
        optimizer_D.zero_grad()

        fake = generator(inputs).detach()

        pred_fake = discriminator(fake).to(device)
        loss_fake = bce(pred_fake, torch.zeros(batch_size, device=device))

        pred_real = discriminator(true).to(device)
        loss_real = bce(pred_real, torch.ones(batch_size, device=device))

        loss_D = (loss_fake+loss_real)/2

        loss_D.backward()
        optimizer_D.step()

        discriminator_epoch_loss += loss_D.item()
        all_loss_D.append(loss_D.item())

        '''2\. Training the Generator (UNet)'''
        optimizer_G.zero_grad()

        fake = generator(inputs)
        pred_fake = discriminator(fake).to(device)

        loss_G_bce = bce(pred_fake, torch.ones_like(pred_fake, device=device))
        loss_G_l1 = l1loss(fake, targets)*100
        loss_G = loss_G_bce + loss_G_l1
        loss_G.backward()
        optimizer_G.step()

        generator_epoch_loss += loss_G.item()
        all_loss_G.append(loss_G.item())

    discriminator_epoch_loss /= len(train_loader)
    generator_epoch_loss /= len(train_loader)
    train_loss_D.append(discriminator_epoch_loss)
    train_loss_G.append(generator_epoch_loss)

    discriminator.eval()
    generator.eval()

    discriminator_epoch_val_loss, generator_epoch_val_loss = 0, 0

    with torch.no_grad():
        for inputs, targets in test_loader:
            inputs, targets = inputs, targets

            fake = generator(inputs)
            pred = discriminator(fake).to(device)

            loss_G_bce = bce(fake, torch.ones_like(fake, device=device))
            loss_G_l1 = l1loss(fake, targets)*100
            loss_G = loss_G_bce + loss_G_l1
            loss_D = bce(pred.to(device), torch.zeros(batch_size, device=device))

            discriminator_epoch_val_loss += loss_D.item()
            generator_epoch_val_loss += loss_G.item()

    discriminator_epoch_val_loss /= len(test_loader)
    generator_epoch_val_loss /= len(test_loader)

    val_loss_D.append(discriminator_epoch_val_loss)
    val_loss_G.append(generator_epoch_val_loss)

    print(f"------Epoch [{epoch+1}/{num_epochs}]------\nTrain Loss D: {discriminator_epoch_loss:.4f}, Val Loss D: {discriminator_epoch_val_loss:.4f}")
    print(f'Train Loss G: {generator_epoch_loss:.4f}, Val Loss G: {generator_epoch_val_loss:.4f}')

    if discriminator_epoch_val_loss > best_discriminator_epoch_val_loss:
        discriminator_epoch_val_loss = best_discriminator_epoch_val_loss
        torch.save(discriminator.state_dict(), "discriminator.pth")
    if generator_epoch_val_loss > best_generator_epoch_val_loss:
        generator_epoch_val_loss = best_generator_epoch_val_loss
        torch.save(generator.state_dict(), "generator.pth")
    #scheduler_D.step()
    #scheduler_G.step()

    fig, ax = plt.subplots(1,3)
    ax[0].imshow(np.transpose(inputs.numpy()[7], (1,2,0)))
    ax[1].imshow(np.transpose(targets.numpy()[7], (1,2,0)))
    ax[2].imshow(np.transpose(fake.detach().numpy()[7], (1,2,0)))
    plt.show()

代码完成后,我们可以绘制损失图。此代码部分来源于这个酷网站

from matplotlib.font_manager import FontProperties

background_color = '#001219'
font = FontProperties(fname='LexendDeca-VariableFont_wght.ttf')
fig, ax = plt.subplots(1, 2, figsize=(16, 9))
fig.set_facecolor(background_color)
ax[0].set_facecolor(background_color)
ax[1].set_facecolor(background_color)

ax[0].plot(range(len(all_loss_G)), all_loss_G, color='#bc6c25', lw=0.5) 
ax[1].plot(range(len(all_loss_D)), all_loss_D, color='#00b4d8', lw=0.5)

ax[0].scatter(
      [np.array(all_loss_G).argmax(), np.array(all_loss_G).argmin()],
      [np.array(all_loss_G).max(), np.array(all_loss_G).min()],
      s=30, color='#bc6c25',
   )
ax[1].scatter(
      [np.array(all_loss_D).argmax(), np.array(all_loss_D).argmin()],
      [np.array(all_loss_D).max(), np.array(all_loss_D).min()],
      s=30, color='#00b4d8',
   )

ax_text(
      np.array(all_loss_G).argmax()+60, np.array(all_loss_G).max()+0.1,
      f'{round(np.array(all_loss_G).max(),1)}',
      fontsize=13, color='#bc6c25',
      font=font,
      ax=ax[0]
   )
ax_text(
      np.array(all_loss_G).argmin()+60, np.array(all_loss_G).min()-0.1,
      f'{round(np.array(all_loss_G).min(),1)}',
      fontsize=13, color='#bc6c25',
      font=font,
      ax=ax[0]
   )

ax_text(
      np.array(all_loss_D).argmax()+60, np.array(all_loss_D).max()+0.01,
      f'{round(np.array(all_loss_D).max(),1)}',
      fontsize=13, color='#00b4d8',
      font=font,
      ax=ax[1]
   )
ax_text(
      np.array(all_loss_D).argmin()+60, np.array(all_loss_D).min()-0.005,
      f'{round(np.array(all_loss_D).min(),1)}',
      fontsize=13, color='#00b4d8',
      font=font,
      ax=ax[1]
   )
for i in range(2):
    ax[i].tick_params(axis='x', colors='white')
    ax[i].tick_params(axis='y', colors='white')
    ax[i].spines['left'].set_color('white') 
    ax[i].spines['bottom'].set_color('white') 
    ax[i].set_xlabel('Epoch', color='white', fontproperties=font, fontsize=13)
    ax[i].set_ylabel('Loss', color='white', fontproperties=font, fontsize=13)

ax[0].set_title('Generator', color='white', fontproperties=font, fontsize=18)
ax[1].set_title('Discriminator', color='white', fontproperties=font, fontsize=18)
plt.savefig('Loss.jpg')
plt.show()
# ax[0].set_axis_off()
# ax[1].set_axis_off()

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/413ed7942d93aa8dfd0134cb587b28e8.png

图片来源:作者

同时也可以可视化来自测试数据集的随机样本:

random.Random(2).shuffle(test_target_img_path)
random.Random(2).shuffle(test_input_img_paths)
subset_loader = dataset(batch_size=5, img_size=img_size, images_paths=test_input_img_paths,
                        targets=test_target_img_path)
generator = UNet()
generator.load_state_dict(torch.load('generator.pth'))

generator.eval()
for X, y in subset_loader:
    fig, axes = plt.subplots(5, 3, figsize=(9, 9))

    for i in range(5):
        axes[i, 0].imshow(np.transpose(X.numpy()[i], (1, 2, 0)))
        axes[i, 0].set_title("Input")
        axes[i, 0].axis('off')

        axes[i, 1].imshow(np.transpose(y.numpy()[i], (1, 2, 0)))
        axes[i, 1].set_title("Target")
        axes[i, 1].axis('off')

        generated_image = generator(X[i].unsqueeze(0)).detach().numpy()[0]
        axes[i, 2].imshow(np.transpose(generated_image, (1, 2, 0)))
        axes[i, 2].set_title("Generated")
        axes[i, 2].axis('off')

    # Adjust layout
    plt.tight_layout()
    plt.savefig('Test.jpg')
    plt.show()
    break 

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/a16b1e301ecc94fe2928a33cc3216350.png

图片来源:作者

如你所见,结果并不完美,并且很大程度上依赖于地貌类型。然而,构建的模型肯定能够去除图像中的云层,并且通过增加 G 和 D 的深度可以提高其性能。另一个有前景的策略是为不同的地貌类型训练独立的模型。例如,农田和水域的空间特征差异较大,这可能会影响模型的泛化能力。

我希望这篇文章能为你提供一种在地理空间领域应用深度学习算法的新视角。在我看来,生成对抗网络(GANs)是数据科学家可以利用的最强大工具之一,我希望它们也能成为你工具箱中的重要组成部分!

===========================================

参考文献:

1. Goodfellow, Ian, Jean Pouget-Abadie, Mehdi Mirza, Bing Xu, David Warde-Farley, Sherjil Ozair, Aaron Courville 和 Yoshua Bengio。“生成对抗网络。” 神经信息处理系统进展 27(2014 年)。proceedings.neurips.cc/paper_files/paper/2014/file/5ca3e9b122f61f8f06494c97b1afccf3-Paper.pdf

2. Helber, Patrick, Benjamin Bischke, Andreas Dengel 和 Damian Borth。“Eurosat:一个用于土地利用和土地覆盖分类的全新数据集和深度学习基准。” IEEE 应用地球观测与遥感精选主题期刊 12 卷,第 7 期(2019 年):2217–2226。arxiv.org/pdf/1709.00029

3. Wen, Xue, Zongxu Pan, Yuxin Hu 和 Jiayin Liu。“基于 YUV 颜色空间的生成对抗学习用于卫星图像中的薄云去除。” 遥感 13 卷,第 6 期(2021 年):1079。www.mdpi.com/2072-4292/13/6/1079

4. Perlin, Ken。“图像合成器。” ACM Siggraph 计算机图形学 19 卷,第 3 期(1985 年):287–296。dl.acm.org/doi/pdf/10.1145/325165.325247

5. Ronneberger, Olaf, Philipp Fischer 和 Thomas Brox。“U-net:用于生物医学图像分割的卷积网络。” 见 医学图像计算与计算机辅助干预–MICCAI 2015:第 18 届国际会议,德国慕尼黑,2015 年 10 月 5 日至 9 日,会议录,第三部分 18,第 234–241 页。施普林格国际出版公司,2015 年。arxiv.org/pdf/1505.04597

6. He, Kaiming 等人。“深度残差学习用于图像识别。” IEEE 计算机视觉与模式识别会议论文集。2016。openaccess.thecvf.com/content_cvpr_2016/papers/He_Deep_Residual_Learning_CVPR_2016_paper.pdf

===========================================

我在 Medium 上的所有出版物都是免费的并且开放访问的,因此如果你在这里关注我,我将非常感激!

P.s. 我对(地理)数据科学、机器学习/人工智能和气候变化充满热情。如果你想合作进行某些项目,请在LinkedIn上联系我。

🛰️关注以获取更多信息🛰️

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐